Skip to content

Framework Agnostic Design

useTemporal is built to work with any JavaScript framework or vanilla JavaScript. Here's how it achieves framework independence while still providing reactive features.

Core Architecture

Not Vue, Just Reactivity

While useTemporal uses @vue/reactivity, it does not depend on the Vue framework:

json
{
  "dependencies": {
    "@vue/reactivity": "^3.4.0"  // Only the reactivity system
  }
}

The @vue/reactivity package is:

  • Standalone - Can be used without Vue framework
  • Lightweight - Only 12KB gzipped
  • Tree-shakeable - Only imports what you use
  • Battle-tested - Powers Vue 3's reactivity

Pure JavaScript Core

All core functionality is pure JavaScript:

typescript
// No framework-specific imports
import { ref, computed } from '@vue/reactivity'

export function createTemporal(options) {
  // Pure JavaScript implementation
  const browsing = ref(period(/*...*/))
  const now = ref(period(/*...*/))
  
  return {
    browsing,
    now,
    adapter: options.adapter,
    // ...
  }
}

Using with Different Frameworks

Vanilla JavaScript

Works directly without any framework:

javascript
import { createTemporal, usePeriod, divide } from '@allystudio/usetemporal'

// Create instance
const temporal = createTemporal({ date: new Date() })

// Use reactive periods
const month = usePeriod(temporal, 'month')

// Watch for changes
month.watch((newMonth) => {
  console.log('Month changed:', newMonth)
  updateUI(newMonth)
})

// Navigate
document.getElementById('next').onclick = () => {
  temporal.browsing.value = next(temporal.adapter, month.value)
}

React

Integrate with React's state system:

jsx
import { useState, useEffect, useSyncExternalStore } from 'react'
import { createTemporal, usePeriod } from '@allystudio/usetemporal'

// Create adapter hook
function useTemporalValue(getValue) {
  return useSyncExternalStore(
    (callback) => getValue().watch(callback),
    () => getValue().value
  )
}

// Use in component
function Calendar() {
  const [temporal] = useState(() => 
    createTemporal({ date: new Date() })
  )
  
  const month = useTemporalValue(() => 
    usePeriod(temporal, 'month')
  )
  
  return (
    <div>
      <h2>{month.date.toLocaleDateString()}</h2>
      <button onClick={() => {
        temporal.browsing.value = next(temporal.adapter, month)
      }}>
        Next Month
      </button>
    </div>
  )
}

Angular

Use with Angular's change detection:

typescript
import { Component, NgZone } from '@angular/core'
import { createTemporal, usePeriod, divide } from '@allystudio/usetemporal'

@Component({
  selector: 'app-calendar',
  template: `
    <div>
      <h2>{{ monthName }}</h2>
      <button (click)="nextMonth()">Next</button>
      <div *ngFor="let day of days">
        {{ day.date.getDate() }}
      </div>
    </div>
  `
})
export class CalendarComponent {
  temporal = createTemporal({ date: new Date() })
  month = usePeriod(this.temporal, 'month')
  monthName = ''
  days = []
  
  constructor(private ngZone: NgZone) {
    // Subscribe to changes
    this.month.watch((newMonth) => {
      this.ngZone.run(() => {
        this.monthName = newMonth.date.toLocaleDateString('en', { month: 'long' })
        this.days = divide(this.temporal, newMonth, 'day')
      })
    })
  }
  
  nextMonth() {
    this.temporal.browsing.value = next(this.temporal, this.month.value)
  }
}

Svelte

Create Svelte stores from temporal:

javascript
// temporal-store.js
import { writable } from 'svelte/store'
import { createTemporal, usePeriod } from '@allystudio/usetemporal'

export function createTemporalStore() {
  const temporal = createTemporal({ date: new Date() })
  
  // Create custom store
  const { subscribe, set } = writable({
    month: usePeriod(temporal, 'month').value,
    temporal
  })
  
  // Watch for changes
  temporal.browsing.watch(() => {
    set({
      month: usePeriod(temporal, 'month').value,
      temporal
    })
  })
  
  return {
    subscribe,
    next: () => temporal.browsing.value = next(temporal.adapter, temporal.browsing.value),
    previous: () => temporal.browsing.value = previous(temporal.adapter, temporal.browsing.value)
  }
}
svelte
<!-- Calendar.svelte -->
<script>
  import { createTemporalStore } from './temporal-store'
  import { divide } from '@allystudio/usetemporal'
  
  const temporal = createTemporalStore()
  
  $: days = divide($temporal.temporal, $temporal.month, 'day')
</script>

<div>
  <h2>{$temporal.month.date.toLocaleDateString('en', { month: 'long' })}</h2>
  <button on:click={temporal.previous}>Previous</button>
  <button on:click={temporal.next}>Next</button>
  
  {#each days as day}
    <div>{day.date.getDate()}</div>
  {/each}
</div>

Building Framework Adapters

Create reusable adapters for any framework:

typescript
// Generic adapter pattern
export function createFrameworkAdapter(framework) {
  return {
    createReactive(temporal) {
      // Framework-specific reactive wrapper
    },
    
    watchValue(value, callback) {
      // Framework-specific watcher
    },
    
    triggerUpdate() {
      // Framework-specific update trigger
    }
  }
}

Example: Solid.js Adapter

javascript
import { createSignal, createEffect } from 'solid-js'

export function useTemporalSolid(temporalInstance) {
  const [browsing, setBrowsing] = createSignal(temporalInstance.browsing.value)
  
  // Sync with temporal
  createEffect(() => {
    const unwatch = temporalInstance.browsing.watch((newValue) => {
      setBrowsing(newValue)
    })
    
    return unwatch
  })
  
  return {
    browsing,
    navigate: (period) => {
      temporalInstance.browsing.value = period
    }
  }
}

Server-Side Rendering (SSR)

useTemporal works in SSR environments:

javascript
// Node.js / SSR
import { createTemporal, divide } from '@allystudio/usetemporal'

export async function getServerSideProps() {
  const temporal = createTemporal({ 
    date: new Date('2024-03-15') 
  })
  
  const month = usePeriod(temporal, 'month')
  const days = divide(temporal.adapter, month.value, 'day')
  
  return {
    props: {
      initialMonth: month.value,
      days: days.map(d => ({
        date: d.date.toISOString(),
        dayNumber: d.date.getDate()
      }))
    }
  }
}

Web Components

Create framework-agnostic web components:

javascript
import { createTemporal, usePeriod, next, previous } from '@allystudio/usetemporal'

class TemporalCalendar extends HTMLElement {
  constructor() {
    super()
    this.attachShadow({ mode: 'open' })
    this.temporal = createTemporal({ date: new Date() })
  }
  
  connectedCallback() {
    this.month = usePeriod(this.temporal, 'month')
    
    // Watch for changes
    this.month.watch(() => this.render())
    
    this.render()
  }
  
  render() {
    const monthName = this.month.value.date.toLocaleDateString('en', { 
      month: 'long', 
      year: 'numeric' 
    })
    
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
          font-family: system-ui;
        }
      </style>
      <div>
        <h2>${monthName}</h2>
        <button id="prev">Previous</button>
        <button id="next">Next</button>
      </div>
    `
    
    this.shadowRoot.getElementById('prev').onclick = () => {
      this.temporal.browsing.value = previous(this.temporal, this.month.value)
    }
    
    this.shadowRoot.getElementById('next').onclick = () => {
      this.temporal.browsing.value = next(this.temporal, this.month.value)
    }
  }
}

customElements.define('temporal-calendar', TemporalCalendar)

Benefits of Framework Agnostic Design

  1. Future Proof - Works with frameworks that don't exist yet
  2. Smaller Bundle - No framework overhead
  3. Flexibility - Use the same code across projects
  4. Learning Once - Same API everywhere
  5. Performance - No framework abstraction layers

See Also

Released under the MIT License.