Derived
Combine independent keyed claims through an explicit resolution policy.
Demo
resolved value
0'use client'
import { Derived, maxOf } from '@joycostudio/xyz'
import { useMemo, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Cluster } from '@/components/ui/cluster'
import { DemoPanel, ValueRow, ValueRows } from '@/components/demos/demo-ui'
export default function DerivedDemo() {
const derived = useMemo(() => new Derived(0, maxOf), [])
const [, render] = useState(0)
const update = (key: string, value?: number) => {
if (value === undefined) derived.clear(key)
else derived.set(key, value)
render((current) => current + 1)
}
return (
<DemoPanel>
<ValueRows>
<ValueRow label="resolved value">{String(derived.value)}</ValueRow>
</ValueRows>
<Cluster wrap className="mt-5">
<Button onClick={() => update('scroll', 0.45)}>Scroll asks 0.45</Button>
<Button onClick={() => update('modal', 0.9)}>Modal asks 0.90</Button>
<Button variant="secondary" onClick={() => update('modal')}>
Clear modal
</Button>
<Button variant="ghost" onClick={() => update('camera')}>
Clear camera
</Button>
<Button variant="ghost" onClick={() => update('scroll')}>
Clear scroll
</Button>
</Cluster>
</DemoPanel>
)
}
Usage
import { Derived, latestOf, maxOf } from '@joycostudio/xyz'
const blur = new Derived(0, maxOf)
blur.set('scroll', 0.4)
blur.set('modal', 0.9)
blur.value // 0.9
blur.clear('modal')
blur.value // 0.4
const focus = new Derived('scene', latestOf<string>)
focus.set('modal', 'settings')
focus.value // 'settings'
focus.clear('modal')
focus.value // 'scene'
API
new Derived
new Derived<In, Out = In>(fallback: Out, combine: (slots: In[], fallback: Out) => Out)
Creates a keyed value with an explicit fallback and resolution policy.
| Argument | Description |
|---|---|
fallback | Value passed to the resolver as its default. The resolver decides how it participates in the result. |
combine | Receives current slot values in insertion order and the fallback whenever .value is read. Returns the resolved value, which may have a different type from the inputs. |
const blur = new Derived(0, maxOf)
blur.value // 0
set
derived.set(key: string, value: In): void
Creates or replaces one owner's claim. Updating an existing key preserves its insertion position.
| Argument | Description |
|---|---|
key | Identifier for the owner making the claim. |
value | The owner's input to the resolver. |
const blur = new Derived(0, maxOf)
blur.set('scroll', 0.4)
blur.set('scroll', 0.8)
blur.value // 0.8
clear
derived.clear(key: string): void
Removes one owner's claim. Missing keys are ignored.
| Argument | Description |
|---|---|
key | Identifier of the claim to remove. |
const blur = new Derived(0, maxOf)
blur.set('scroll', 0.4)
blur.set('modal', 0.9)
blur.clear('modal')
blur.value // 0.4
value
derived.value // Out
Read-only resolved value. Each read calls combine with the current claims without mutating them.
size
derived.size // number
Read-only count of active claims.
maxOf
maxOf(values: number[], fallback: number): number
Returns the highest active value, never below the fallback. Pass it as Derived's combine argument for numeric maximum resolution.
| Argument | Description |
|---|---|
values | Current numeric claims. |
fallback | Minimum result, also returned when there are no claims. |
maxOf([0.4, 0.9], 0) // 0.9
maxOf([], 0.2) // 0.2
latestOf
latestOf<T>(values: T[], fallback: T): T
Returns the last value in the array. With Derived, this selects the most recently inserted active claim; updating an existing key does not move it to the end.
| Argument | Description |
|---|---|
values | Current claims in insertion order. |
fallback | Value returned when there are no claims. |
latestOf(['scene', 'modal'], 'scene') // 'modal'
latestOf([], 'scene') // 'scene'