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
- Future Proof - Works with frameworks that don't exist yet
- Smaller Bundle - No framework overhead
- Flexibility - Use the same code across projects
- Learning Once - Same API everywhere
- Performance - No framework abstraction layers
See Also
- Reactive Time Units - How reactivity works
- Vue Integration - Vue examples
- React Integration - React examples
- Installation - Setup instructions