Coreclass

Derived

Combine independent keyed claims through an explicit resolution policy.

Demo

resolved value0
'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.

ArgumentDescription
fallbackValue passed to the resolver as its default. The resolver decides how it participates in the result.
combineReceives 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.

ArgumentDescription
keyIdentifier for the owner making the claim.
valueThe 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.

ArgumentDescription
keyIdentifier 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.

ArgumentDescription
valuesCurrent numeric claims.
fallbackMinimum 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.

ArgumentDescription
valuesCurrent claims in insertion order.
fallbackValue returned when there are no claims.
latestOf(['scene', 'modal'], 'scene') // 'modal'
latestOf([], 'scene') // 'scene'