# Tempo Documentation This file is generated from the Tempo documentation site. # Tempo | [![npm @tempots/dom](https://img.shields.io/npm/v/@tempots/dom?label=@tempots/dom)](https://www.npmjs.com/package/@tempots/dom) | [![npm @tempots/std](https://img.shields.io/npm/v/@tempots/std?label=@tempots/std)](https://www.npmjs.com/package/@tempots/std) | [![npm @tempots/ui](https://img.shields.io/npm/v/@tempots/ui?label=@tempots/ui)](https://www.npmjs.com/package/@tempots/ui) | [![codecov](https://codecov.io/gh/fponticelli/tempots/branch/main/graph/badge.svg)](https://codecov.io/gh/fponticelli/tempots) | [![CI](https://github.com/fponticelli/tempots/workflows/CI/badge.svg)](https://github.com/fponticelli/tempots/actions) | | --- | --- | --- | --- | --- | > A new UI Framework for the web ## Why Choose Tempo? - Simple TypeScript functions - Predictable rendering without surprises - Zero dependencies - No hidden complexities - Directly updates the DOM (no virtual DOM) - Fine-grained control when required - Lightweight and efficient To get started, see the [Quick Start](#quick-start) guide. ## Documentation - [Quick Start](#quick-start): Get started with Tempo in a few simple steps - [Installation](#installation): Install Tempo using npm or yarn - [How It Works](#how-does-it-work): Learn how Tempo works under the hood - [Renderables](#renderables): The building blocks of Tempo applications - [Signals](#signals): Reactive state management - [Build your own Renderables](#build-your-own-renderables): Create reusable components - [Providers](#providers): Dependency injection and context sharing - [Examples & Best Practices](#examples-patterns): Common patterns and best practices - [Troubleshooting & FAQ](#troubleshooting-faq): Common issues and solutions ## Libraries Tempo provides a set of libraries to help you build your applications: - [@tempots/dom](#tempotsdom): The core UI framework - [@tempots/std](#tempotsstd): A [standard library](#standard-library) with utility functions and types - [@tempots/ui](#tempotsui): A collection of [reusable UI components](#ui-components) ## Demos See Tempo in action with these demo applications: - [Hackers News PWA](https://tempo-ts.com/demo/hnpwa.html): A Hacker News reader - [7GUIs](https://tempo-ts.com/demo/7guis.html): Implementation of the 7GUIs benchmark - [TodoMVC](https://tempo-ts.com/demo/todomvc.html): The classic TodoMVC example - [Counter Demo](https://tempo-ts.com/demo/counter.html): A simple counter application ## LLM-Friendly Documentation This documentation is available in LLM-optimized formats following the [llms.txt](https://llmstxt.org/) convention: - [`llms.txt`](/llms.txt) — Documentation index for AI tools - [`llms-full.txt`](/llms-full.txt) — Complete documentation in a single file --- # Quick Start Tempo can be used for fully fledged single-page applications or simple widgets. Either way, it is easy to get started: 1. Define a template or component 2. Render it to the DOM at a specific location ## Hello World A classic “Hello World” example:```ts import { html, render } from '@tempots/dom' // Define a template or renderable const HelloWorld = html.h1('Hello World') // Render it to the DOM render(HelloWorld, document.body) ``` The `render()` call returns a function that can be used to remove the rendered template from the DOM: ```ts // Render it to the DOM const remove = render(HelloWorld, document.body) // Remove it from the DOM after 1 second setTimeout(() => { console.log('Removing HelloWorld') remove() }, 1000) ``` ## Add state and interactivity State in Tempo is managed through signals. Signals are reactive and can be used to update the DOM (or anything else) when they change. A `Signal` is a readonly object that can be observed but not updated. A `Prop` is a writable object that can be updated. A `Computed` is a readonly object that is derived from other signals. Here is an example of a simple counter: ```ts import { html, on, render, prop } from '@tempots/dom' // Define a writeable signal const count = prop(0) // Define a template const Counter = html.div( html.h1(count.map(v => `count: ${v}`)), // ✨ Auto-disposed html.div( html.button(on.click(() => count.value++), '+'), html.button(on.click(() => count.value--), '-'), ) ) // Render it to the DOM render(Counter, document.body) ``` The `count` signal is updated by mutating the `count.value` that computes a new value based on the current value. `count` can also be set directly by calling `count.set()` or update with a function with `count.update(v => v+1)`. What about ``count.map(v => `count: ${v}`)``? This is a `Computed` signal that is derived from the `count` signal. It will update whenever `count` changes. The `map()` function is a helper function that maps the value of the signal to a new value. Events are handled by using the functions associated to the `on` object. The `on.click()` function creates an event listener for the `click` event. ### Automatic Memory Management **Important:** Signals created within renderables (like `count` and `count.map(...)` above) are automatically tracked and disposed when the component is removed from the DOM. This means: - ✅ No manual cleanup needed - ✅ No memory leaks - ✅ Works with all signal types: `prop()`, `signal()`, `computed()` - ✅ Works with derived signals: `.map()`, `.filter()`, `.flatMap()`, etc. Tempo automatically creates a disposal scope for each renderable and tracks all signals created within it. When the renderable is removed from the DOM, all tracked signals are automatically disposed. Note that differently from other frameworks, Tempo does not make a distinction between children nodes, attributes, properties, or event handlers. Everything satisfies the same `Renderable` type and `Renderable`s can be nested in any component that accept children. This brings a lot of flexibility and simplicity to the API. One example is that you can use the `Portal` component not just to render the content of a selected element but also change/add to its attributes, classes and event handlers. ## Next Steps - [Installation](#installation) - Learn more about [how Tempo works](#how-does-it-work) - [Renderables](#renderables), the building blocks of Tempo applications - [Signals](#signals), the reactive core of Tempo - [Learn more about Building your own Renderables](#build-your-own-renderables) - [Render](#render), the function to apply Renderables to the DOM --- # Installation Tempo is available as a package on npm. You can install it using npm or yarn:```bash # npm npm install @tempots/dom # yarn yarn add @tempots/dom ``` Tempo is written in TypeScript so types are generated and included in the package. You can use Tempo with or without TypeScript. If you are using TypeScript, you will get full type checking and autocompletion. `@tempots/dom` doesn't have any dependency. If you use `@tempots/ui` you will also need to install `@tempots/dom` and `@tempots/std` as they are peer dependencies. ## Additional Packages Tempo provides additional packages for specific use cases: | Package | Purpose | |---------|---------| | `@tempots/std` | Standard library with utilities and common patterns | | `@tempots/ui` | Pre-built UI components | | `@tempots/server` | Server-side rendering to strings and streams | | `@tempots/client` | Client-side hydration and islands architecture | | `@tempots/vite` | Vite plugin for SSG with automatic route discovery | ### For SSR/SSG Projects If you're building a server-rendered or static site, install the SSR packages: ```bash npm install @tempots/dom @tempots/server @tempots/client @tempots/vite ``` See [SSR & Headless Rendering](#ssr-headless-rendering) for detailed documentation. ## Next Steps - [How does Tempo work?](#how-does-it-work) - [Learn more about Renderables](#renderables) - [Learn more about Signals](#signals) - [Learn more about Building your own Renderables](#build-your-own-renderables) - [SSR & Headless Rendering](#ssr-headless-rendering) - [Explore Examples & Best Practices](#examples-patterns) - [Learn more about render](#render) --- # How Does It Work? ## Renderable The core of Tempo is the `Renderable` type. A `Renderable` is a function with the following signature:```ts type Renderable = (context: DOMContext) => Clear type Clear = (removeTree: boolean) => void ``` The `Renderable` function takes a `DOMContext` object, which provides access to the DOM and other utilities. The function returns a `Clear` function that removes the rendered template from the DOM. This simple signature offers flexibility and ease of use. For instance, consider the `Fragment` component, which can have multiple children but doesn’t contribute to the DOM itself. It’s used to group multiple renderables together. Here’s its implementation: ```ts export const Fragment = (...children: TNode[]): Renderable => (ctx: DOMContext) => { const clears = children.map(child => renderableOfTNode(child)(ctx)) return (removeTree: boolean) => { clears.forEach(clear => clear(removeTree)) } } ``` In Tempo, you often pass a `TNode` (short for Tempo Node) to existing renderables. A `TNode` is a union type that includes commonly used types in Tempo: `Renderable`, `Value`, `Value`, `Value`, `Signal`, `undefined`, `null`, or an `Array`. The `renderableOfTNode` function helps convert a `TNode` to a `Renderable`. Using `TNode` makes the API more flexible and allows for a more declarative syntax. ```ts html.div('Hello World') // equivalent to html.div(TextNode('Hello World')) ``` ### Clear The `Clear` function removes the rendered template from the DOM. Its argument, `removeTree`, indicates whether to remove side effects, such as clearing an interval or timeout, and all DOM modifications (`true`) or only side effects (`false`). ### TNode A `TNode` is treated differently based on its type: - `string`, `number`, `boolean` or their `Signal` counterparts: Treated as a text node. - Signals containing `null` or `undefined` values render as empty text (equivalent to `''`). - `undefined` or `null`: Ignored (renders nothing). - `Renderable`: Left unmodified. - `Renderable[]`: Gets wrapped into a `Fragment`. ## DOMContext The `DOMContext` object, passed to the Renderable function, provides access to the DOM and other utilities. It has the following properties: - `element`: The Element instance associated with this context. - `reference`: An optional Node instance serving as a reference for this context. - `document`: The Document instance associated with this context. The reference node, `TextNode` with an empty string, acts as a placeholder when elements are added or removed between siblings. It’s useful for `Renderables` like `ForEach` or `When`, which need to track element positions in the DOM. Additionally, `DOMContext` contains a collection of providers for shared state between Renderables, avoiding prop drilling and keeping the API clean. The `isFirstLevel` property, still experimental, marks nodes for server-side rendering and hydration. ## Next Steps - [Learn more about Renderables](#renderables) - [Learn more about Signals](#signals) - [Learn more about Building your own Renderables](#build-your-own-renderables) - [Explore the Standard Library](#standard-library) - [Discover UI Components](#ui-components) - [Learn more about render](#render) --- # Renderables Renderables or components are the building blocks of Tempo applications. They are the templates that are rendered to the DOM. Tempo provides a set of functions to create and manipulate renderables. The Renderable functions use the convention of starting with a capital letter. This is to differentiate them from regular functions and to make it easier to identify them in the code. The notable exception is for basic html/svg elements, which are lowercase. ## HTML/SVG, text and Attributes To create HTML or SVG elements, use the `html` and `svg` objects. Each of them contains the full list of available tags as functions. For example, to create a `div` element, use `html.div()`. To create a `circle` element, use `svg.circle()`. These functions take an arbitrary number of `TNode` arguments. A `TNode` can be a primitive (`string`, `number`, `boolean`), a `Signal` of a primitive (including nullable signals like `Signal`), a `Renderable`, a `Renderable[]`, or `null`/`undefined`. To create text nodes, you can just pass a primitive or a `Signal` of a primitive where a `TNode` is expected. Signals containing `null` or `undefined` render as empty text. Alternatively you can be explicit and use the `TextNode()` function. ```ts const titleSignal = signal('Hello, World!') html.div( 'Hello, World!', // <-- this is a text node titleSignal, // <-- this is a signal that automatically updates a text node html.span('This is a span'), TextNode('This is also a text node') ) ``` To create DOM attributes and properties use the `attr` object. It contains functions for all the standard attributes and properties. For example, to set the `id` attribute, use `attr.id('my-id')`. ```ts html.img( attr.id('my-id'), attr.src('https://example.com/image.jpg'), attr.title(titleSignal) ) ``` You will have noticed that text nodes and attribute values accept both literal values (ex. `'Hello, World!'`) and signals (ex. `titleSignal`). This is because the arguments are typed as `Value` which is a union of `T` and `Signal`. `Value` also happens to be part of the `TNode` union. ### attr.class The `class` attribute is special in the sense that can be used multiple times in the same element. ```ts const classSignal = signal('class3 class4') html.div(attr.class('class1 class2'), attr.class(classSignal)) ``` A class attribute can be a string or a signal that emits a string. The string can contain multiple classes separated by spaces. ### Other attributes and elements There are helpers to create arbitrary data-attributes, math/svg elements and attributes, and style attributes. ```ts html.div( dataAttr('mydata', 'myvalue'), math.math( mathAttr.display('inline'), math.mfrac(math.msup(math.mi('π'), math.mn('2')), math.mn('6')) ), svg.svg(svg.circle(svgAttr.cx(50), svgAttr.cy(50), svgAttr.r(40))), style.color('red') ) ``` ## Events Similar to attributes, events can be set using the `on` object. The `on` object contains functions for all the standard events. For example, to set the `click` event, use `on.click(fn)`. It is fine to use `signal.value` or `signal.get()` to get the value of a signal in an event handler. ### emit helpers Tempo provides a set of functions to simplify event handling by extracting values from DOM events: | Helper | Description | | --------------------------------- | ---------------------------------------------- | | `emitValue(fn)` | Extract string value from input/textarea | | `emitValueAsNumber(fn)` | Extract numeric value (uses `valueAsNumber`) | | `emitValueAsDate(fn)` | Extract Date from date input | | `emitValueAsNullableDate(fn)` | Extract Date or null from date input | | `emitValueAsDateTime(fn)` | Extract Date from datetime-local input | | `emitValueAsNullableDateTime(fn)` | Extract Date or null from datetime-local input | | `emitChecked(fn)` | Extract boolean from checkbox/radio | | `emitTarget(fn)` | Get the target element directly | ```ts const name = prop('') const age = prop(0) const birthDate = prop(null) const isSubscribed = prop(false) html.form( // Text input - use prop.set directly as callback html.input( attr.type('text'), attr.value(name), on.input(emitValue(name.set)) ), // Number input - use filter for validation html.input( attr.type('number'), attr.value(age.map(String)), on.input( emitValueAsNumber(v => { if (!isNaN(v)) age.set(v) }) ) ), // Date input - prop.set works directly html.input( attr.type('date'), on.change(emitValueAsNullableDate(birthDate.set)) ), // Checkbox - prop.set works directly html.input( attr.type('checkbox'), attr.checked(isSubscribed), on.change(emitChecked(isSubscribed.set)) ), // Direct element access html.input( on.focus( emitTarget((input: HTMLInputElement) => { input.select() // Select all text on focus }) ) ) ) ``` **Emit Options:** All emit helpers accept an optional second argument for event control: ```ts type EmitOptions = { preventDefault?: boolean stopPropagation?: boolean stopImmediatePropagation?: boolean } // Example: prevent form submission on.submit( emitTarget( () => { console.log('Form submitted') }, { preventDefault: true } ) ) ``` ### Delegated Events For containers with many similar children (such as lists rendered with `ForEach`), attaching individual `on` handlers to each item creates one listener per element. The `delegate` object provides an alternative: a single event listener on the **container** that matches children by CSS selector using `Element.closest()`. ```ts import { html, delegate, ForEach, prop } from '@tempots/dom' const items = prop(['Apple', 'Banana', 'Cherry']) html.ul( delegate.click('li', (event) => { const li = (event.target as Element).closest('li')! console.log('Clicked:', li.textContent) }), ForEach(items, (item) => html.li(item)) ) ``` `delegate` uses the same proxy pattern as `on`, so all standard events are available (`delegate.click`, `delegate.input`, `delegate.keydown`, etc.). It accepts an optional third argument for `HandlerOptions` (`once`, `passive`, `signal`). **When to use `delegate` vs `on`:** | Use `on` | Use `delegate` | | --- | --- | | Small/static number of elements | Large or dynamic lists (`ForEach`, `Repeat`) | | Need per-element context | One handler for many similar children | | Non-bubbling events (`focus`, `blur`, `mouseenter`, `mouseleave`) | Standard bubbling events (`click`, `input`, `keydown`, etc.) | > **Note:** Delegated events rely on event bubbling. Events that do not bubble (`focus`, `blur`, `mouseenter`, `mouseleave`) will not be captured by delegation. Use `on` for those. ## input elements When using `input` elements it is very common you want to specify the type of the input. Tempo provides a set of functions to create `input` elements with the correct type. For example, to create a `number` input, use `input.number()`. ## bind Tempo provides functions to bind `Props` to input elements. For example, to bind a `string` prop to an `input` element, use `BindText`. Other bind functions include `BindNumber`, `BindDate`, `BindDateTime`, and `BindChecked`. These set a bidirectional binding between the prop and the input element. ```ts import { prop, input, BindText, BindNumber, BindDate, BindDateTime, BindChecked } from '@tempots/dom' // Text binding - syncs input value with prop const email = prop('') input.text(BindText(email)) // Number binding - parses input as number const age = prop(0) input.number(BindNumber(age)) // Date binding - syncs with date input const birthDate = prop(new Date()) input.date(BindDate(birthDate)) // DateTime binding - syncs with datetime-local input const appointmentTime = prop(new Date()) input['datetime-local'](BindDateTime(appointmentTime)) // Checkbox binding - syncs checked state const isSubscribed = prop(false) input.checkbox(BindChecked(isSubscribed)) ``` ## Conditionals Tempo has a set of functions to create conditional renderables. For example, to render a `div` element only if a condition is met, use `When` (or `Unless` for its negation). ```ts const showSignal = signal(true) When(showSignal, () => html.div('This is visible')) ``` A second argument can be passed to `When` to specify a renderable to show when the condition is false. An interesting aspect of conditionals in Tempo, is that there is no DOM rebuilding unless the branch is changed. ### Ensure In TypeScript it is common to work with values that can be `null` or `undefined`. To render a value only if it is not `null` or `undefined`, use `Ensure`. ```ts const valueSignal = signal('Hello, World!') Ensure(valueSignal, v => html.div(v.map(text => `This is visible: ${text}`))) ``` Unlike `When`, `Ensure` takes a function that returns a renderable. This function is called with a new signal that is guaranteed to be not `null` or `undefined`. ### OneOf `OneOf` helpers allow matching a signal and rendering a branch based on its value. Several variations exist depending on what you want to match. ```ts const status = signal<{ loading: true } | { error: string }>({ loading: true }) OneOf(status, { loading: () => html.div('Loading...'), error: e => html.div('Error:', e), }) ``` #### OneOfValue ```ts const mode = signal<'view' | 'edit'>('view') OneOfValue(mode, { view: () => html.div('Viewing'), edit: () => html.div('Editing'), }) ``` #### OneOfTuple ```ts const pair = signal(['A', 1] as ['A' | 'B', number]) OneOfTuple(pair, { A: n => html.div('A:', n.map(String)), B: n => html.div('B:', n.map(String)), }) ``` #### OneOfField ```ts type State = | { state: 'loading' } | { state: 'error'; message: string } | { state: 'ready'; content: string } const state = signal({ state: 'loading' }) OneOfField(state, 'state', { loading: () => html.div('Loading...'), error: s => html.div('Error:', s.$.message), ready: s => html.div('Ready:', s.$.content), }) ``` #### OneOfKind ```ts type MyType = { kind: 'A'; text: string } | { kind: 'B'; value: number } const valueSignal = signal({ kind: 'A', text: 'Hello, World!' }) OneOfKind(valueSignal, { A: v => html.div('A:', v.$.text), B: v => html.div('B:', v.$.value.map(String)), }) ``` #### OneOfType ```ts type Msg = { type: 'inc'; value: number } | { type: 'dec'; value: number } const msg = signal({ type: 'inc', value: 1 }) OneOfType(msg, { inc: m => html.div('Inc', m.$.value.map(String)), dec: m => html.div('Dec', m.$.value.map(String)), }) ``` ## Loops Of course you can also render lists of elements. Tempo provides a set of functions to create loops. For example, to render a list of `div` elements, use `ForEach`. ```ts const itemsSignal = signal(['Item 1', 'Item 2', 'Item 3']) ForEach(itemsSignal, (item, position) => html.div(position.$.counter.map(String), ': ', item) ) ``` The renderable function takes two arguments. The first is a signal that represent an element of the list. The second is a signal that represents the position of the element in the list. The position signal has a `counter` field that is the 1-based index of the element in the list as well as a `isFirst`, `isLast` and `index`. `ForEach` accepts an optional third argument to render a separator between elements. You can wrap your loop in a `NotEmpty` renderable if you want to ensure that the list is not empty. This is useful in the case of structures like `UL` or `OL` where an empty list would not be desired. ```ts const itemsSignal = signal(['Item 1', 'Item 2', 'Item 3']) NotEmpty( itemsSignal, items => html.ul(ForEach(items, item => html.li(item))), () => 'No items' ) ``` ### KeyedForEach When list items have stable identities (e.g., database IDs), `KeyedForEach` provides efficient reconciliation by tracking items by key rather than by index. When items are reordered, existing DOM nodes are **moved** rather than recreated, and signal identities are preserved. ```ts const todos = prop([ { id: 1, text: 'Buy groceries' }, { id: 2, text: 'Walk the dog' }, { id: 3, text: 'Read a book' }, ]) html.ul( KeyedForEach( todos, (todo) => todo.id, // key function (todo, pos) => html.li( // item renderer todo.map((t) => t.text) ), () => html.hr() // optional separator ) ) ``` The key differences between `ForEach` and `KeyedForEach`: | | `ForEach` | `KeyedForEach` | |---|---|---| | **Tracking** | By index (position) | By key (identity) | | **Reorder** | Signals at each position update with new values | DOM nodes move; signals keep their identity | | **Position** | `ElementPosition` (static `index`) | `KeyedPosition` (reactive `index`, all fields update) | | **Best for** | Simple lists, append-only, rarely reordered | Sortable lists, drag-and-drop, items with stable IDs | Each item's callback receives a `KeyedPosition` with fully reactive fields: `index`, `counter`, `isFirst`, `isLast`, `isEven`, `isOdd`, and `total`. All of these update automatically when an item moves to a new position. `Repeat` takes a signal that represents the number of times to repeat the renderable. It is useful when you want to repeat a renderable a fixed number of times. ```ts const countSignal = signal(3) Repeat(countSignal, pos => html.div(`${pos.counter} of `, pos.$.total.map(String)) ) ``` `counter` is a fixed value so it is not wrapped in a signal but `total` is a signal as it can vary when `countSignal` changes. If you know ahead of time the number and content of the elements, you can use a regular loop to create an array of renderables. ```ts const items = ['Item 1', 'Item 2', 'Item 3'] html.div(items.map((item, index) => html.div(String(index), ': ', item))) ``` ### TransitionKeyedForEach `TransitionKeyedForEach` is a drop-in replacement for `KeyedForEach` that supports enter and exit animations. Items that leave the list are kept in the DOM for a configurable duration with an `isExiting` signal set to `true`, allowing CSS transitions or animations to play before removal. ```ts import { TransitionKeyedForEach } from '@tempots/dom' TransitionKeyedForEach( todoItems, (item) => item.id, (item, position, isExiting) => html.div( attr.class('todo-item'), attr.class(isExiting.map(e => e ? 'todo-item--exiting' : '')), item.map(i => i.text), ), { exitDuration: 300 } ) ``` ```css .todo-item--exiting { animation: fade-out 300ms ease-out forwards; } ``` The `config` object supports: - `exitClass` / `exitDuration` — class and duration for exit animations - `enterClass` / `enterDuration` — class and duration for enter animations - `useAnimationEvents` — listen for `animationend`/`transitionend` instead of using a fixed duration ## Animation Renderables ### RafLoop A renderable that runs a `requestAnimationFrame` loop for the lifetime of the component. The loop is automatically stopped when the component is disposed. ```ts import { RafLoop } from '@tempots/dom' html.canvas( RafLoop((dt) => { // dt is delta time in milliseconds since last frame // Update canvas, particles, etc. }) ) ``` For imperative use inside `WithElement`, use `createRafLoop()` which returns a handle with a `dispose()` method. ## Gesture Renderables ### PinchZoom Attaches two-finger pinch-to-zoom with simultaneous pan to the parent element: ```ts import { PinchZoom, prop } from '@tempots/dom' import type { PinchZoomState } from '@tempots/dom' const viewport = prop({ scale: 1, panX: 0, panY: 0 }) html.div( attr.style(viewport.map(v => ({ transform: `translate(${v.panX}px, ${v.panY}px) scale(${v.scale})`, }))), PinchZoom(viewport, { minScale: 0.25, maxScale: 4 }), ) ``` ### Inertia Attaches physics-based inertia drag-and-scroll to the parent element. On pointer up, the surface continues scrolling with exponential velocity decay. ```ts import { Inertia, prop } from '@tempots/dom' const offset = prop({ x: 0, y: 0 }) html.div( Inertia( (dx, dy) => offset.set({ x: offset.get().x + dx, y: offset.get().y + dy, }), { friction: 0.95 } ), ) ``` ## Lifecycle For more advanced use cases, Tempo provides a set of functions to handle the lifecycle of a renderable. For example, to run a function when a renderable is mounted, use `WithElement`. This will take a callback function that will be called with the HTML DOM Element just mounted. Similarly `WithCtx` will take a callback function that will be called with the current `DOMContext`, and `WithBrowserCtx` for browser-specific contexts. ```ts html.div( // element is the DIV Element just mounted WithElement(element => { console.log('Mounted', element) }) ) ``` Whenever you want to cleanup resources when a renderable is unmounted, use `OnDispose`. ```ts html.div( WithElement(element => { const listener = () => console.log('Clicked') element.addEventListener('click', listener) return OnDispose((removeTree, ctx) => { if (removeTree) { element.removeEventListener('click', listener) } }) }) ) ``` ## Error Boundaries `Catch` wraps a renderable subtree and renders a fallback if it throws during rendering. This prevents a single failing component from crashing the entire app. ```ts import { Catch, html, on } from '@tempots/dom' Catch( MyComponent(), (error, retry) => html.div( html.p('Something went wrong: ', error.map(e => e.message)), html.button(on.click(retry), 'Retry') ) ) ``` - `error` is a `Signal` — the fallback reactively displays the error message - `retry` is a function that disposes the fallback and re-attempts rendering the children - If retry also fails, the error signal updates with the new error (fallback stays rendered) - Catches synchronous render errors only (not event handlers or async code) ## Fragment/Empty You can use `Fragment` where a single renderable is expected but you want to render multiple components. Similarly, you can use `Empty` to fill a slot with nothing. ## Providers To simplify the structure of a larger project, it is often useful to use a Provide/Use pattern. In a high-level component, you can provide a value (or function, or signal, or anything really) that is consumed by a lower-level component. You can use `Provide` to provide a single value (or a record or a function), and `Use` to consume it. A provider is a simple object that knows how to provide a context value and how to identify itself. ```ts const Preferences = { mark: makeProviderMark>('Preferences'), create: () => { const preferences = signal({ theme: 'bubbly' }) // the implementation, it must return an object with return { value: preferences, dispose: preferences.dispose } }, } ``` The provider can be made available this way: ```ts const MyComponent = Provide( Preferences, {}, // options (can be empty) () => html.div(...) ) ``` And it can be used this way: ```ts Use(Preferences, value => html.div(value.$.theme)) ``` If you want to `set` and/or `use` multiple providers at once, you can use `WithProvider`. ```ts WithProvider(({ set, use }) => { set(Preferences, {}) const preferences = use(Preferences) return html.div(preferences.$.theme) }) ``` ## Asynchronous Operations Tempo provides two renderables for handling async operations: `Async` and `Task`. ### Async vs Task - **`Async(promise, options)`** - Wraps an existing Promise. The promise starts executing immediately when created. - **`Task(fn, options)`** - Wraps a function that returns a Promise. The function is called when the component renders (lazy execution). ```ts import { Async, Task } from '@tempots/dom' // Async: promise executes immediately when this line runs const immediateLoad = Async( fetch('/api/data').then(r => r.json()), { pending: () => html.div('Loading...'), then: data => html.div('Data: ', JSON.stringify(data)), error: err => html.div('Error: ', String(err)) } ) // Task: fetch only happens when the component renders const lazyLoad = Task( () => fetch('/api/data').then(r => r.json()), { pending: () => html.div('Loading...'), then: data => html.div('Data: ', JSON.stringify(data)), error: err => html.div('Error: ', String(err)) } ) // Shorthand: just pass a function for the success case const simpleTask = Task( () => fetch('/api/data').then(r => r.json()), data => html.div('Data: ', JSON.stringify(data)) ) ``` ### Using Signals for Reactive Data More often you'll want to combine `Signal`s with `Promise` for reactive data fetching: ```ts const dataSignal = Signal.ofPromise( fetch('https://api.example.com/data').then(res => res.text()), null // initial value before the promise resolves ) html.div(Ensure(dataSignal, data => html.div(data), html.div('Loading...'))) ``` When you need to refetch based on changing parameters, use `mapAsync`: ```ts const idSignal = signal(1) const dataSignal = idSignal.mapAsync( async id => { const res = await fetch(`https://api.example.com/data/${id}`) return res.text() }, null // default value before the promise resolves ) // dataSignal automatically refetches when idSignal changes ``` ## Portal A `Portal` is a way to render a renderable in a different part of the DOM. This is useful when you want to render a modal, a tooltip or you want to make changes to the `head` element. The `HTMLTitle` renderable defined in the `@tempots/ui` package is a good example of this. ```ts export const HTMLTitle = (title: Value) => Portal('head > title', attr.innerText(title)) ``` ## Next Steps - [Learn more about Signals](#signals) - [Learn more about Building your own Renderables](#build-your-own-renderables) - [Learn more about Providers](#providers) - [Discover UI Components](#ui-components) - [Explore Examples & Best Practices](#examples-patterns) - [Learn more about render](#render) --- # Signals Signals are the reactive data stores. They are used to manage state and notify state changes. ## Create signals There are three types of signals: `Signal`, `Prop`, and `Computed`. A `Signal` is a readonly object that can be observed but not updated. A `Prop` is a writable object that can be updated. A `Computed` is a readonly object that is derived from other signals. To create a signal, use the `signal()`, `prop()`, and `computed()` (or `computedOf`) functions.```tsx // create a signal that cannot be updated const s = signal(0) // create a signal that can be updated const p = prop(0) p.value = 1 console.log(p.value) // 1 // create a computed signal from a single dependency const c1 = computed(() => s.value * 2, [s]) // for multiple signals, prefer computedOf - cleaner syntax with type-safe values const c2 = computedOf(s, p)((sVal, pVal) => sVal + pVal) ``` When you create a Computed signal, you need to provide a function that returns the value of the signal. The function will be called whenever the dependency signals in the second argument change. There is no magic here, if you don't provide the dependency signals, the computed signal will not update. Signals can also be created from promises using the `Signal.ofPromise()` static method. ```ts const userSignal = Signal.ofPromise( fetch('/api/user').then(r => r.json()), null, // initial value before promise resolves error => ({ error: String(error) }) // optional error recovery function ) ``` The first argument is a promise that resolves to the value of the signal. The second argument is the initial value used until the promise resolves. An optional third argument is an error recovery function. ## Read signals Once you have a signal, you can read its value using the `value` property (or `get()` function). You generally don't access the `value` property directly unless you are referring it within an event handler or a computed signal. Since signals are reactive, you can listen to changes using the `on()` method. The method returns a function that you can call to stop listening to changes. This is how tempo monitors changes and updates the DOM. When you add a callback to a signal, the callback is called immediately with the current value of the signal. ## Modify props You can update a prop using the `set()` method or using the `value` setter. They both take a new value and update the signal. You can also update a prop using the `update()` method. The method takes a function that receives the current value and returns the new value. ```ts const p = prop(0) p.set(1) p.value = 2 p.update(v => v + 1) ``` ## Effects You can also create side effects using the `effect()` function. The function takes a function that performs the side effect and an array of signals that the side effect depends on. The function is called immediately and whenever the dependency signals change. The function returns a function that you can call to stop the side effect. ## Transform signals You can transform signals using the `map()`, `filter()`, `flatMap()`, and other functions. These functions create a new signal that is derived from the original signal. ```ts const count = prop(0) const doubled = count.map(x => x * 2) // ✨ Auto-disposed const positive = count.filter(x => x > 0) // ✨ Auto-disposed ``` **Automatic Disposal:** All derived signals (created with `.map()`, `.filter()`, `.flatMap()`, etc.) are automatically tracked and disposed when used within renderables. No manual cleanup needed! Since you will often work with signals of objects, you might find the `$` property useful. `$` is an object that contains signals for each property of the object. This makes it easy to work with signals of objects. ```ts const prop = prop({ name: 'John', age: 30 }) console.log(prop.$.name.value) // John ``` The `at()` function is equivalent to `$` and it takes the key as an argument. ## Automatic Memory Management When you create signals within a renderable, Tempo automatically tracks them and disposes them when the component is removed from the DOM. This applies to: - **Signal creation**: `prop()`, `signal()`, `computed()`, `computedOf()` - **Signal transformations**: `.map()`, `.filter()`, `.flatMap()`, `.filterMap()`, etc. - **Effects**: `effect()` functions ```ts import { html, prop, render } from '@tempots/dom' const MyComponent = () => { const count = prop(0) // ✨ Auto-disposed const doubled = count.map(x => x * 2) // ✨ Auto-disposed return html.div('Count: ', count, ' Doubled: ', doubled) } const clear = render(MyComponent(), document.body) // Later: clear() will automatically dispose count and doubled ``` ### Long-Lived Signals If you need to create a signal that outlives the current component scope, use `untracked()`: ```ts import { untracked, prop } from '@tempots/dom' const globalState = untracked(() => prop(0)) // Not auto-disposed // Remember to dispose manually when done: globalState.dispose() ``` ## Signal Methods Reference ### Listening Methods | Method | Description | | ------------------------------ | --------------------------------------------------------------------------------------------- | | `on(listener, options?)` | Listen to value changes. Called immediately with current value. Returns unsubscribe function. | | `onChange(listener, options?)` | Like `on()` but skips the initial call - only fires on actual changes. | | `hasListeners()` | Returns `true` if the signal has any registered listeners. | **Listener Options:** ```ts type ListenerOptions = { skipInitial?: boolean // Don't call immediately with current value once?: boolean // Unsubscribe after first call abortSignal?: AbortSignal // Cancel via AbortController } ``` ### Transformation Methods | Method | Description | | -------------------------------------- | ------------------------------------------------------------ | | `map(fn, equals?)` | Transform values to a new type. Returns a Computed signal. | | `flatMap(fn, equals?)` | Map then flatten nested signals. | | `filter(predicate, startValue?)` | Only emit values matching predicate. | | `filterMap(fn, startValue, equals?)` | Map + filter in one operation. Skips null/undefined results. | | `mapMaybe(fn, alt)` | Map with fallback for null/undefined results. | | `mapAsync(fn, alt, recover?, equals?)` | Async transformation with abort support. | | `tap(fn)` | Execute side effect without modifying value. | ```ts const count = prop(5) // Transform to different types const doubled = count.map(n => n * 2) const message = count.map(n => `Count is ${n}`) // Filter values const positive = count.filter(n => n > 0) // Async transformation const userData = userId.mapAsync( async (id, { abortSignal }) => { const res = await fetch(`/api/users/${id}`, { signal: abortSignal }) return res.json() }, null // initial value ) // Side effects without modifying const logged = count.tap(n => console.log('Value:', n)) ``` ### Object Access Methods | Method | Description | | --------- | -------------------------------------------------------------------------------------------------------- | | `at(key)` | Get a signal for a specific property of the value. | | `$` | Proxy object providing signals for all properties. `signal.$.name` is equivalent to `signal.at('name')`. | ### Disposal Methods | Method | Description | | --------------------- | ----------------------------------------------- | | `dispose()` | Dispose the signal and release all resources. | | `isDisposed()` | Returns `true` if the signal has been disposed. | | `onDispose(listener)` | Register a callback to run when disposed. | ### Prop-Specific Methods | Method | Description | | --------------- | -------------------------------------------------------- | | `set(value)` | Set a new value. | | `update(fn)` | Update value using a function: `prop.update(v => v + 1)` | | `reducer(fn)` | Create a reducer function with effects. | | `iso(get, set)` | Create a bidirectional transformation (isomorphism). | | `atProp(key)` | Get a writable Prop for a specific property. | ```ts const user = prop({ name: 'John', age: 30 }) // Get writable access to nested property const nameProp = user.atProp('name') nameProp.value = 'Jane' // Updates user.value.name ``` ### Static Methods | Method | Description | | ---------------------------------------------------- | ------------------------------- | | `Signal.ofPromise(promise, init, recover?, equals?)` | Create signal from a Promise. | | `Signal.is(value)` | Check if a value is a Signal. | | `Prop.is(value)` | Check if a value is a Prop. | | `Computed.is(value)` | Check if a value is a Computed. | ## Storage Utilities Tempo provides utilities for persisting signals to browser storage with automatic synchronization across tabs. ### localStorageProp Creates a Prop backed by localStorage: ```ts import { localStorageProp } from '@tempots/dom' const theme = localStorageProp({ key: 'app-theme', defaultValue: 'light', }) // Value persists across page reloads theme.value = 'dark' ``` ### sessionStorageProp Creates a Prop backed by sessionStorage (cleared when browser closes): ```ts import { sessionStorageProp } from '@tempots/dom' const formData = sessionStorageProp({ key: 'checkout-form', defaultValue: { email: '', address: '' }, }) ``` ### storedProp Options Both `localStorageProp` and `sessionStorageProp` accept these options: ```ts type StorageOptions = { key: Value // Storage key (can be reactive) defaultValue: T | (() => T) // Default when not in storage serialize?: (v: T) => string // Custom serialization (default: JSON.stringify) deserialize?: (v: string) => T // Custom deserialization (default: JSON.parse) equals?: (a: T, b: T) => boolean // Equality function syncTabs?: boolean // Sync across browser tabs (default: true) onKeyChange?: 'load' | 'migrate' | 'keep' // Behavior when key changes } ``` ### syncProp For cross-tab synchronization of any Prop: ```ts import { syncProp, prop } from '@tempots/dom' // Create a synchronized prop const sharedState = syncProp({ key: 'shared-state', prop: prop({ count: 0 }), }) // Changes in one tab automatically appear in other tabs sharedState.value = { count: 1 } ``` ## Animation Tempo provides built-in signal animation support to smoothly transition between values. ### animateSignal Creates a new signal that smoothly interpolates whenever the source signal changes: ```ts import { prop, animateSignal, easeInOutCubic } from '@tempots/dom' const position = prop(0) const animated = animateSignal(position, { duration: 300, easing: easeInOutCubic, }) // When position changes, animated smoothly transitions to the new value position.set(100) // animated smoothly goes from 0 to 100 ``` ### Easing Functions Tempo includes 25 standard easing functions covering all common animation curves: | Family | In | Out | InOut | |--------|------|------|-------| | Quad | `easeInQuad` | `easeOutQuad` | `easeInOutQuad` | | Cubic | `easeInCubic` | `easeOutCubic` | `easeInOutCubic` | | Quart | `easeInQuart` | `easeOutQuart` | `easeInOutQuart` | | Sine | `easeInSine` | `easeOutSine` | `easeInOutSine` | | Expo | `easeInExpo` | `easeOutExpo` | `easeInOutExpo` | | Back | `easeInBack` | `easeOutBack` | `easeInOutBack` | | Bounce | `easeInBounce` | `easeOutBounce` | `easeInOutBounce` | | Elastic | `easeInElastic` | `easeOutElastic` | `easeInOutElastic` | Plus `linear` (identity) and three **combinators** for building custom easings: ```ts import { reverseEasing, mirrorEasing, chainEasing, easeInQuad, easeOutElastic } from '@tempots/dom' // Reverse: plays easing backwards (easeIn → easeOut) const myEaseOut = reverseEasing(easeInQuad) // Mirror: symmetric in-out from a single ease-in const myEaseInOut = mirrorEasing(easeInQuad) // Chain: compose two easings (first half + second half) const dramatic = chainEasing(easeInQuad, easeOutElastic) ``` ### createTween For imperative control over animations (e.g., animate to a target on user action), use `createTween`: ```ts import { createTween, easeInOutCubic, interpolateNumber } from '@tempots/dom' const tween = createTween(0, { duration: 300, easing: easeInOutCubic, }) // Animate to target value on demand tween.tweenTo(100) // Read current animated value tween.value.get() // smoothly approaches 100 // Cancel mid-animation tween.cancel() // Clean up tween.dispose() ``` `createTween` supports a `reducedMotion` signal to respect the user's accessibility preference — when `true`, `tweenTo()` sets the value immediately without animation. ### Reduced Motion Track the user's `prefers-reduced-motion` system preference reactively: ```ts import { createReducedMotionSignal, createTween } from '@tempots/dom' const reducedMotion = createReducedMotionSignal() const tween = createTween(0, { duration: 300, reducedMotion, // automatically skips animation when user prefers reduced motion }) ``` The `ReducedMotion` provider makes this available app-wide via the `Provide`/`Use` pattern — see the [Providers page](#providers). ## Next Steps - [Learn more about Building your own Renderables](#build-your-own-renderables) - [Explore the Standard Library](#standard-library) - [Troubleshooting & FAQ](#troubleshooting-faq) - [Learn more about render](#render) --- # Build your own Renderables or Components A reusable Renderable or Component can be exported as either a constant value or a function.```ts const Logo = html.div( attr.class('logo'), html.img(attr.src('logo.png')) ) ``` In this example, `Logo` is a reusable component. The content of the component remains the same; it doesn’t change. However, it is still reusable because it doesn’t generate a new DOM unless it is `render`ed. It can be applied multiple times in different locations, making it versatile across applications. Using a capitalized name for your component is a common convention to indicate that it is a component. You can create a component that is still constant and reusable but has dynamic content. ```ts const UserView = Async( fetchUser(), (user) => html.div(user.name) ) ``` The rendered content of the component is dynamic and depends on the result of the `fetchUser()` promise but the promise is executed only once for any instantiation of the component. ## Options More commonly, you'll see components that take a set of options as argument. In this case the component is a function that returns a `Renderable`. Tempo uses "options" instead of "props" to avoid confusion with the `prop()` signal type. ```ts const UserView = (user: User) => html.div(user.name) ``` This is a valid component but it is not often what you want in the context of Tempo because it does not allow to update the DOM when the options change. Each application of this component will result in a DOM state that will not change unless the component is re-rendered. To address this, you want to use `Signal`s instead. ```ts const UserView = (user: Signal) => html.div(user.$.name) ``` Now, any time the `user` signal changes, the DOM will be updated. Notice that the `$` property is used to access the value of a field wrapped in the signal. It is convenient and type-safe. The equivalent method is `user.at('name')`. To get the best of both worlds, you can use `Value`. ```ts const UserView = (user: Value) => html.div( Value.map(user, ({ name }) => name) ) ``` `Value` is a union type that can be either a `Signal` or a `T`. It is a convenient way to work with signals and values in the same way. `Value.map` is used to map a `Value` to a new `Value`. Most Renderables in Tempo accept `Value` instead of `Signal` or `T`. If you want to create components that take multiple arguments or options, you can shape them the way you want. ## Next Steps - [Discover UI Components](#ui-components) - [Explore Examples & Best Practices](#examples-patterns) - [Learn more about render](#render) --- # Providers Providers in Tempo offer a powerful dependency injection system that allows you to share state, services, and functionality across your application without prop drilling. This pattern is similar to React's Context API or Angular's dependency injection system, but with Tempo's functional approach. ## Core Concepts The provider system in Tempo consists of several key components: 1. **Provider Mark**: A unique identifier for a provider 2. **Provider**: An object that knows how to create and dispose of a value 3. **Provide**: A renderable that makes a provider available to its children 4. **Use**: A renderable that consumes a provider's value (throws if missing) 5. **UseOptional**: A renderable that consumes a provider's value safely (returns `undefined` or a fallback if missing) 6. **WithProvider**: A more flexible API for working with multiple providers ## Creating a Provider A provider is an object with two properties: - `mark`: A unique identifier created with `makeProviderMark` - `create`: A function that creates the provider's value and returns an object with: - `value`: The actual value to be provided - `dispose`: A cleanup function called when the provider is no longer needed - `onUse` (optional): A function called when the provider is used Here's how to create a simple provider:```typescript import { makeProviderMark, signal, Signal, Provider } from '@tempots/dom' // Define the type for our preferences interface Preferences { theme: 'light' | 'dark' fontSize: number } // Create a provider for preferences const PreferencesProvider: Provider> = { // Create a unique mark with the correct type mark: makeProviderMark>('Preferences'), // Create function returns the value and cleanup create: () => { // Create a signal to hold the preferences const preferences = signal({ theme: 'light', fontSize: 16 }) // Return the value and cleanup function return { value: preferences, dispose: () => preferences.dispose() } } } ``` ## Providing Values Once you have a provider, you can make it available to child components using the `Provide` renderable: ```typescript import { html, Provide } from '@tempots/dom' // Make the preferences available to all children const App = () => Provide( PreferencesProvider, // The provider to use {}, // Options (if any) () => html.div( // Child components that can access the provider html.h1('My App'), SettingsPanel(), MainContent() ) ) ``` The `Provide` function takes three arguments: 1. The provider object 2. Options to pass to the provider's `create` function (can be empty `{}`) 3. A function that returns the child components ## Consuming Provider Values To use a provider's value, use the `Use` renderable: ```typescript import { html, Use } from '@tempots/dom' const ThemeToggle = () => Use( PreferencesProvider, // The provider to use preferences => html.div( // Function that receives the provider value html.button( on.click(() => { // Toggle the theme preferences.update(prefs => ({ ...prefs, theme: prefs.theme === 'light' ? 'dark' : 'light' })) }), 'Toggle Theme: ', preferences.$.theme // Access theme property using $ shorthand ) ) ) ``` The `Use` function takes two arguments: 1. The provider to consume 2. A function that receives the provider's value and returns a renderable ## Using Multiple Providers If you need to use multiple providers, you can use `UseMany`: ```typescript import { html, UseMany } from '@tempots/dom' const SettingsPanel = () => UseMany( PreferencesProvider, UserProvider )( (preferences, user) => html.div( html.h2('Settings for ', user.$.name), html.div('Theme: ', preferences.$.theme), html.div('Font Size: ', preferences.$.fontSize.map(String)) ) ) ``` ## Optional Providers with UseOptional Sometimes a provider may or may not be available in the component tree. `Use` throws a `ProviderNotFoundError` when a provider is missing, but `UseOptional` handles this gracefully. ### Without a fallback When called with just a provider and a child function, `UseOptional` passes `T | undefined` to the child: ```typescript import { html, UseOptional } from '@tempots/dom' const OptionalTheme = () => UseOptional( ThemeProvider, theme => html.div( theme !== undefined ? `Theme: ${theme.value}` : 'No theme provider — using system defaults' ) ) ``` ### With a fallback value When called with a fallback, the child always receives `T` — either the provider value or the fallback: ```typescript import { html, UseOptional } from '@tempots/dom' const defaultPreferences = { theme: 'light', fontSize: 16 } const ThemedContent = () => UseOptional( PreferencesProvider, defaultPreferences, prefs => html.div( `Theme: ${prefs.theme}, Font size: ${prefs.fontSize}` ) ) ``` This is useful for building reusable components that can work standalone or adapt when a parent provides configuration. ## Advanced Usage with WithProvider For more complex scenarios, you can use `WithProvider` which gives you direct access to `set`, `use`, and `tryUse` functions: ```typescript import { html, WithProvider } from '@tempots/dom' const AdvancedComponent = () => WithProvider(({ set, use, tryUse }) => { // Set up multiple providers set(PreferencesProvider, {}) set(UserProvider, { userId: 123 }) // Use the providers (throws if missing) const preferences = use(PreferencesProvider) const user = use(UserProvider) // Optionally use a provider (returns undefined if missing) const analytics = tryUse(AnalyticsProvider) analytics?.trackPageView('advanced') // Return a renderable using the providers return html.div( html.h2(`Hello, ${user.$.name}`), html.div(`Your theme is: ${preferences.$.theme}`) ) }) ``` ## Provider with Options Providers can accept options when they're created: ```typescript // Provider that accepts options const ThemeProvider: Provider, { initialTheme: string }> = { mark: makeProviderMark>('Theme'), create: (options = { initialTheme: 'light' }) => { const theme = signal(options.initialTheme) return { value: theme, dispose: () => theme.dispose() } } } // Using the provider with options const App = () => Provide( ThemeProvider, { initialTheme: 'dark' }, // Pass options here () => html.div( // ... ) ) ``` ## Real-World Example: Theme Provider Here's a complete example of a theme provider that detects the user's system preferences: ```typescript import { makeProviderMark, signal, Signal, Provider, html, Provide, Use, attr, on } from '@tempots/dom' // Define theme types type Theme = 'light' | 'dark' // Create the theme provider const ThemeProvider: Provider> = { mark: makeProviderMark>('Theme'), create: () => { // Detect system preference const prefersDark = window.matchMedia('(prefers-color-scheme: dark)') const theme = signal(prefersDark.matches ? 'dark' : 'light') // Listen for system preference changes const handler = (e: MediaQueryListEvent) => { theme.value = e.matches ? 'dark' : 'light' } prefersDark.addEventListener('change', handler) return { value: theme, dispose: () => { prefersDark.removeEventListener('change', handler) theme.dispose() } } } } // App component that provides the theme const App = () => Provide( ThemeProvider, {}, () => html.div( // Apply theme class to body Use( ThemeProvider, theme => html.body( attr.class(theme.map(t => `theme-${t}`)), html.h1('Themed App'), ThemeToggle(), Content() ) ) ) ) // Theme toggle button const ThemeToggle = () => Use( ThemeProvider, theme => html.button( on.click(() => { theme.value = theme.value === 'light' ? 'dark' : 'light' }), 'Toggle Theme: ', theme.map(t => t === 'light' ? '🌞' : '🌙') ) ) // Content that uses the theme const Content = () => Use( ThemeProvider, theme => html.div( attr.class('content'), html.p(`Current theme: ${theme.value}`) ) ) ``` ## Built-in Providers ### ReducedMotion Tempo ships a `ReducedMotion` provider that tracks the user's `prefers-reduced-motion` system preference. This is useful for any component that performs animations: ```typescript import { Provide, Use, ReducedMotion, html, When } from '@tempots/dom' // Provide at app level (once) const App = () => Provide(ReducedMotion, undefined, () => html.div( AnimatedWidget(), ) ) // Consume anywhere in the tree const AnimatedWidget = () => Use(ReducedMotion, (reducedMotion) => When( reducedMotion, html.div('Static content'), // reduced motion: skip animation html.div(/* animated content */), // full motion: animate ) ) ``` The signal updates automatically when the user changes their system preference. ## Next Steps - [Learn more about Building your own Renderables](#build-your-own-renderables) - [Explore Examples & Best Practices](#examples-patterns) - [Learn more about render](#render) --- # Standard Library (@tempots/std) The `@tempots/std` package is a comprehensive standard library for TypeScript that provides utility functions and types commonly used in web applications. This package serves as a natural complement to the Tempo libraries but can be used independently in any TypeScript project. ## Installation```bash # npm npm install @tempots/std # yarn yarn add @tempots/std # pnpm pnpm add @tempots/std ``` ## Features The library provides utility functions organized into several modules: ### Array Operations ```typescript import { filterMapArray, uniqueByPrimitive, range, chunk, partition, groupBy, } from '@tempots/std' // Filter and map in one pass const numbers = [1, 2, 3, 4, 5] const evenDoubled = filterMapArray(numbers, n => n % 2 === 0 ? n * 2 : undefined ) // [4, 8] // Generate a range of numbers const oneToFive = range(5, 1) // [1, 2, 3, 4, 5] // Get unique values by a key extractor const users = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 1, name: 'Clone' }, ] const uniqueUsers = uniqueByPrimitive(users, user => user.id) // [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }] // Chunk an array into groups const chunked = chunk([1, 2, 3, 4, 5], 2) // [[1, 2], [3, 4], [5]] // Partition by predicate const [evens, odds] = partition(numbers, n => n % 2 === 0) // [[2, 4], [1, 3, 5]] ``` ### String Utilities ```typescript import { capitalizeWords, ellipsis } from '@tempots/std' // or import { capitalizeWords, ellipsis } from '@tempots/std/string' // Capitalize words const capitalized = capitalizeWords('hello world') // 'Hello World' // Truncate text with ellipsis (uses unicode ellipsis character) const truncated = ellipsis('This is a long text', 10) // 'This is a…' ``` ### Result Type The `Result` type provides a way to handle operations that might fail: ```typescript import { Result } from '@tempots/std' // or import { Result } from '@tempots/std/result' // Create a success result const success = Result.success(42) // Create a failure result const failure = Result.failure(new Error('Something went wrong')) // Match on a result const value = Result.match( success, value => `Success: ${value}`, error => `Error: ${error.message}` ) // 'Success: 42' ``` ### AsyncResult Type Similar to `Result`, but for asynchronous operations with loading states: ```typescript import { AsyncResult } from '@tempots/std' // Create from a promise const asyncResult = await AsyncResult.ofPromise( fetch('https://api.example.com/data').then(r => r.json()) ) // Handle the result with pattern matching AsyncResult.match(asyncResult, { success: data => console.log('Data:', data), failure: error => console.error('Error:', error), loading: () => console.log('Loading...'), notAsked: () => console.log('Not started'), }) ``` ### Validation Type The `Validation` type is useful for form validation and data checking. It represents either a valid state or an invalid state with an error: ```typescript import { Validation } from '@tempots/std' // Define validation rules const validateEmail = (email: string): Validation => { if (!email.includes('@')) { return Validation.invalid('Email must contain @') } if (email.length < 5) { return Validation.invalid('Email too short') } return Validation.valid } const validateAge = (age: number): Validation => { if (age < 0) return Validation.invalid('Age cannot be negative') if (age > 150) return Validation.invalid('Age seems unrealistic') return Validation.valid } // Use validation const emailResult = validateEmail('user@example.com') // Pattern matching Validation.match( emailResult, () => console.log('Email is valid!'), error => console.log('Invalid:', error) ) // Type guards if (Validation.isValid(emailResult)) { console.log('Proceed with valid email') } if (Validation.isInvalid(emailResult)) { console.log('Error:', emailResult.error) } // Execute side effects conditionally Validation.whenValid(emailResult, () => { submitForm() }) Validation.whenInvalid(emailResult, error => { showError(error) }) // Convert to Result type for further processing const result = Validation.toResult(emailResult, 'user@example.com') ``` **Form Validation Example with Tempo:** ```typescript import { html, prop } from '@tempots/dom' import { Validation } from '@tempots/std' const email = prop('') const emailError = email.map(value => { const validation = validateEmail(value) return Validation.isInvalid(validation) ? validation.error : null }) html.form( html.input( attr.type('email'), attr.value(email), on.input(emitValue(email.set)) // Use prop.set directly ), Ensure(emailError, error => html.span(attr.class('error'), error)) ) ``` ## Available Modules The library is organized into the following modules: - `array` - Array manipulation utilities (filterMapArray, uniqueByPrimitive, range, chunk, partition, groupBy, etc.) - `async-result` - Asynchronous result handling with loading states - `bigint` - BigInt utilities - `boolean` - Boolean utilities - `date` - Date manipulation utilities - `deferred` - Promise deferral utilities - `domain` - Domain-specific types (Maybe, Nothing, Compare, etc.) - `equal` - Deep equality comparison - `function` - Function composition and manipulation - `iterator` - Iterator utilities (take, skip, filter, map, reduce, find, etc.) - `json` - JSON utilities - `map` - Map utilities (mapFromEntries, mapFilter, mapMerge, mapGroupBy, etc.) - `number` - Number utilities (clamp, interpolate, snapToGrid, etc.) - `object` - Object manipulation - `promise` - Promise utilities - `random` - Random value generation - `regexp` - Regular expression utilities - `result` - Result type for error handling - `set` - Set utilities (setUnion, setIntersection, setDifference, etc.) - `string` - String manipulation utilities (80+ functions) - `timer` - Timing utilities (delayed, interval, throttle, debounce) - `union` - Union type utilities - `url` - URL/path utilities (parseUrl, buildUrl, joinPaths, etc.) - `validation` - Data validation utilities ## Next Steps - [Learn more about the UI components library](#ui-components) - [Learn more about render](#render) --- # UI Components (@tempots/ui) The `@tempots/ui` package is a collection of reusable UI components and renderables built on top of `@tempots/dom` to accelerate development with Tempo. This package provides higher-level abstractions for common UI patterns and components. ## Installation```bash # npm npm install @tempots/dom @tempots/std @tempots/ui # yarn yarn add @tempots/dom @tempots/std @tempots/ui # pnpm pnpm add @tempots/dom @tempots/std @tempots/ui ``` Note: `@tempots/dom` and `@tempots/std` are peer dependencies and must be installed alongside `@tempots/ui`. ## Features ### UI Components The library provides a set of reusable UI components: #### AutoFocus and AutoSelect ```typescript import { html, render } from '@tempots/dom' import { AutoFocus, AutoSelect } from '@tempots/ui' // Create an input that automatically gets focus const focusedInput = html.input( AutoFocus(), // Automatically focus this input when rendered AutoSelect() // Automatically select all text when focused ) // Render it to the DOM render(focusedInput, document.body) ``` #### InViewport Detect when an element is in the viewport: ```typescript import { html, render } from '@tempots/dom' import { InViewport } from '@tempots/ui' // Create an element that detects when it's in the viewport const lazyLoadedContent = InViewport( { mode: 'partial', once: false }, // mode: 'partial' | 'full', once?: boolean (isVisible) => isVisible.map(v => v ? html.div('Content is visible!') : html.div('Loading...')) ) // Render it to the DOM render(lazyLoadedContent, document.body) ``` #### HTMLTitle Set the document title: ```typescript import { html, render, prop } from '@tempots/dom' import { HTMLTitle } from '@tempots/ui' // Create a title that updates when the signal changes const title = prop('Welcome to my app') const app = html.div( HTMLTitle(title), html.h1(title) ) // Render it to the DOM render(app, document.body) // Update the title title.value = 'New page title' ``` #### Router Client-side routing with `RootRouter` and `ChildRouter`: ```typescript import { html, render, Provide, Use } from '@tempots/dom' import { RootRouter, ChildRouter, Location, NavigationService } from '@tempots/ui' // Define routes - handlers receive a Signal const app = Provide(Location, {}, () => RootRouter({ '/': () => html.div('Home page'), '/about': () => html.div('About page'), '/users/:id': (info) => html.div('User ID: ', info.$.params.$.id), '/admin/*': () => AdminRoutes(), '*': () => html.div('404 - Not found') }) ) // Nested routes with ChildRouter const AdminRoutes = () => ChildRouter({ '/users': () => html.div('Admin Users'), '/settings': () => html.div('Admin Settings'), '*': () => html.div('Admin 404') }) // Render it to the DOM render(app, document.body) // Navigate programmatically using NavigationService NavigationService.navigate('/about') ``` #### Query Handle async data loading with loading/error states: ```typescript import { html, prop, render } from '@tempots/dom' import { Query } from '@tempots/ui' // Create a query that loads data from an API const userId = prop(1) const userQueryView = Query({ request: userId, load: async ({ request, abortSignal }) => { const response = await fetch(`https://api.example.com/user/${request}`, { signal: abortSignal }) if (!response.ok) throw new Error('Failed to load user') return response.json() }, convertError: error => error instanceof Error ? error.message : String(error), pending: ({ previous, reload }) => html.div('Loading...'), failure: ({ error, reload }) => html.div( error.map(message => `Error: ${message}`), html.button(on.click(reload), 'Retry') ), success: ({ value, reload }) => html.div( value.map(u => `Hello, ${u.name}!`), html.button(on.click(reload), 'Refresh') ), }) // Render it to the DOM render(userQueryView, document.body) // Trigger a reload by changing the request value userId.value = 2 ``` ## Available Components The library includes the following components and utilities: ### Input & Focus - `AutoFocus` - Automatically focus an element - `AutoSelect` - Automatically select text in an input - `SelectOnFocus` - Select all text when an input is focused ### Viewport & Layout - `InViewport` - Detect when an element is in the viewport - `WhenInViewport` - Conditional rendering based on viewport visibility - `WindowSize` - Track window dimensions - `ElementRect` - Track element size and position - `PopOver` - Create popup/popover elements - `HiddenWhenEmpty` - Hide an element when its content is empty ### Routing - `RootRouter` - Root-level client-side routing - `ChildRouter` - Nested routing for sub-routes - `Location` - Provider for reactive location state - `NavigationService` - Programmatic navigation utilities - `Anchor` - Navigation-aware anchor element ### Async Operations - `Query` - Async data loading with loading/error states - `Mutation` - Handle async mutations (POST/PUT operations) - `AsyncResultView` - Display async operation results - `ResultView` - Display success/failure results ### Events & Interaction - `OnClickOutside` - Detect clicks outside an element - `OnKeyPressed` - Handle keyboard events with modifier support - `OnEnterKey` - Handle Enter key press - `OnEscapeKey` - Handle Escape key press ### Utilities - `HTMLTitle` - Set the document title - `Appearance` - Detect and react to light/dark mode - `classes` - Conditional CSS class binding - `Ticker` / `ticker` - Counter/timer signal utilities - `makeRelativeTime` - Human-readable relative time formatting ## Next Steps - [Learn more about render](#render) - Check out the [demos](https://tempo-ts.com/demo/counter.html) to see these components in action --- # SSR & Headless Rendering Tempo provides comprehensive support for server-side rendering (SSR), static site generation (SSG), and client-side hydration through dedicated packages: | Package | Purpose | |---------|---------| | `@tempots/server` | Server-side rendering to strings and streams | | `@tempots/client` | Client-side hydration and islands architecture | | `@tempots/vite` | Vite plugin for SSG with automatic route discovery | ## Why SSR/SSG? - **SEO optimization** - Pre-render pages for search engine crawlers - **Performance** - Send pre-rendered HTML for faster initial page loads - **Static hosting** - Deploy to CDNs without a server - **Progressive enhancement** - Pages work before JavaScript loads ## @tempots/server The server package provides functions to render Tempo components to HTML strings or streams. ### Installation```bash npm install @tempots/server ``` ### renderToString Renders a component to an HTML string: ```typescript import { renderToString } from '@tempots/server' import { html } from '@tempots/dom' const App = () => html.div( html.h1('Hello, World!'), html.p('Rendered on the server.') ) const htmlString = await renderToString(App(), { url: 'https://example.com/page', generatePlaceholders: true, // Enable hydration markers }) ``` ### renderToStream Renders to a Node.js Readable stream for streaming SSR: ```typescript import { renderToStream } from '@tempots/server' app.get('/', (req, res) => { const stream = renderToStream(App(), { url: req.url, onShellReady: () => res.write(''), onAllReady: () => res.end(), }) stream.pipe(res) }) ``` ### createRenderer High-level convenience function for SSR entry points: ```typescript // entry-server.ts import { createRenderer } from '@tempots/server' import { App } from './App' export const { render, renderStream } = createRenderer( (options) => App(options), { hydrate: true, getData: async (url) => { // Fetch data for this URL return { user: await fetchUser() } } } ) ``` ## @tempots/client The client package provides hydration and islands architecture support. ### Installation ```bash npm install @tempots/client ``` ### hydrate Hydrates server-rendered HTML with client-side interactivity: ```typescript import { hydrate } from '@tempots/client' import { App } from './App' // Server-rendered HTML is already in the DOM const cleanup = hydrate(App(), document.getElementById('app')!) ``` ### startClient High-level client initialization with islands support: ```typescript import { startClient } from '@tempots/client' import { App } from './App' import { Counter, TodoList } from './islands' startClient({ app: () => App(), islands: { Counter, TodoList }, container: '#app', debug: true, }) ``` ### Islands Architecture Islands allow you to hydrate only interactive components while keeping the rest static: ```typescript // Define an island component import { html, prop, on } from '@tempots/dom' export const Counter = (options: unknown) => { const { initial = 0 } = (options ?? {}) as { initial?: number } const count = prop(initial) return html.div( html.button(on.click(() => count.update(n => n - 1)), '-'), html.span(count.map(String)), html.button(on.click(() => count.update(n => n + 1)), '+'), ) } // Mark islands in your SSR template import { islandMarker, attr } from '@tempots/dom' const CounterIsland = (initial: number) => html.div( ...islandMarker('Counter', { initial }, 'visible').map( ({ name, value }) => attr[name](value) ), // Static placeholder content html.span(String(initial)) ) ``` Hydration strategies: - `"immediate"` - Hydrate as soon as possible - `"idle"` - Hydrate when browser is idle - `"visible"` - Hydrate when scrolled into view - `{ media: "(min-width: 768px)" }` - Hydrate when media query matches ## @tempots/vite The Vite plugin provides SSG with automatic route discovery. ### Installation ```bash npm install @tempots/vite ``` ### Configuration ```typescript // vite.config.ts import { defineConfig } from 'vite' import { tempo } from '@tempots/vite' export default defineConfig({ plugins: [ tempo({ mode: 'ssg', // or 'ssr', 'islands', 'hybrid' routes: 'crawl', // Auto-discover routes (default) seedRoutes: ['/'], // Starting points for crawling ssrEntry: 'src/entry-server.ts', container: '#app', hydrate: true, }) ] }) ``` ### Route Discovery By default, the plugin crawls your site starting from `/` and discovers all internal links: ```typescript // Explicit routes tempo({ routes: ['/', '/about', '/contact'], }) // Dynamic routes tempo({ routes: async () => { const posts = await fetchBlogPosts() return ['/', ...posts.map(p => `/blog/${p.slug}`)] }, }) // Crawl with multiple entry points tempo({ routes: 'crawl', seedRoutes: ['/', '/docs', '/api'], }) ``` ### Project Structure ``` my-app/ ├── src/ │ ├── App.ts # Main app component │ ├── entry-client.ts # Client entry (hydration) │ └── entry-server.ts # Server entry (rendering) ├── index.html # HTML template └── vite.config.ts # Vite configuration ``` ### Entry Files **entry-server.ts:** ```typescript import { renderToString } from '@tempots/server' import { App } from './App' export async function render(url: string): Promise { return renderToString(App(), { url, generatePlaceholders: true, }) } ``` **entry-client.ts:** ```typescript import { render } from '@tempots/dom' import { App } from './App' render(App(), document.getElementById('app')!) ``` ## Low-Level API: runHeadless For advanced use cases, you can use the low-level headless rendering API: ```typescript import { runHeadless, html } from '@tempots/dom' const App = () => html.div( html.h1('Hello, World!'), html.p('Headless rendered') ) const { root, clear, currentURL } = runHeadless(() => App(), { startUrl: 'https://example.com', selector: 'body', }) // Get HTML output const htmlOutput = root.contentToHTML(true) // true = include placeholders // Clean up clear() ``` ## Context-Aware Rendering Conditionally render based on environment: ```typescript import { html, WithBrowserCtx, WithHeadlessCtx } from '@tempots/dom' const App = () => html.div( // Only in browser WithBrowserCtx(() => html.div('Window width: ', window.innerWidth.toString()) ), // Only in headless/SSR WithHeadlessCtx(() => html.div('Server-rendered placeholder') ), // Both environments html.p('Universal content') ) ``` ## Next Steps - [Quick Start](#quick-start) - Get started with Tempo - [Renderables](#renderables) - Learn about the building blocks - [Signals](#signals) - Reactive state management - [Examples](#examples-patterns) - Common patterns and best practices --- # Examples & Patterns This page provides practical examples and patterns for common scenarios when building applications with Tempo. ## Form Handling ### Complete Form with Validation```typescript import { html, prop, computedOf, render, Ensure, attr, on, emitValue } from '@tempots/dom' import type { Prop, Signal } from '@tempots/dom' import { Validation } from '@tempots/std' // Validation functions const validateEmail = (value: string): Validation => { if (!value) return Validation.valid // Don't show error when empty if (!value.includes('@')) return Validation.invalid('Invalid email format') return Validation.valid } const validatePassword = (value: string): Validation => { if (!value) return Validation.valid // Don't show error when empty if (value.length < 8) return Validation.invalid('At least 8 characters') if (!/[A-Z]/.test(value)) return Validation.invalid('Needs uppercase letter') if (!/[0-9]/.test(value)) return Validation.invalid('Needs a number') return Validation.valid } // Form component with real-time validation const RegistrationForm = () => { const name = prop('') const email = prop('') const password = prop('') // Real-time validation errors (derived from field values) const emailError = email.map(v => { const result = validateEmail(v) return Validation.isInvalid(result) ? result.error : null }) const passwordError = password.map(v => { const result = validatePassword(v) return Validation.isInvalid(result) ? result.error : null }) // Check if form can be submitted const canSubmit = computedOf(name, email, password)((n, e, p) => n.trim().length > 0 && Validation.isValid(validateEmail(e)) && Validation.isValid(validatePassword(p)) ) const handleSubmit = () => { console.log('Form submitted:', { name: name.value, email: email.value }) } return html.form( on.submit(e => { e.preventDefault(); handleSubmit() }), FormField('Name', name), FormField('Email', email, emailError, 'email'), FormField('Password', password, passwordError, 'password'), html.button( attr.type('submit'), attr.disabled(canSubmit.map(v => !v)), 'Register' ) ) } // Reusable form field component const FormField = ( label: string, value: Prop, error?: Signal, type: string = 'text' ) => html.div( attr.class('form-field'), html.label(label), html.input( attr.type(type), attr.value(value), on.input(emitValue(value.set)), error ? attr.class(error.map(e => e ? 'error' : '')) : null ), error ? Ensure(error, err => html.span(attr.class('error-message'), err)) : null ) ``` ## Data Fetching with Query ### Basic Query Usage ```typescript import { html, prop, render, attr, on, emitValue, Ensure } from '@tempots/dom' import { Query } from '@tempots/ui' interface User { id: number name: string email: string } const UserProfile = () => { const userId = prop(1) return html.div( // User selector html.select( on.change(emitValue(v => userId.value = parseInt(v))), html.option(attr.value('1'), 'User 1'), html.option(attr.value('2'), 'User 2'), html.option(attr.value('3'), 'User 3') ), // Query with loading/error states Query({ request: userId, load: async ({ request, abortSignal }) => { const res = await fetch(`/api/users/${request}`, { signal: abortSignal }) if (!res.ok) throw new Error('Failed to load user') return res.json() }, convertError: e => e instanceof Error ? e.message : 'Unknown error', pending: ({ previous }) => html.div( 'Loading...', // Show previous data while loading Ensure(previous, user => html.div( attr.class('stale'), 'Previous: ', user.$.name )) ), failure: ({ error, reload }) => html.div( html.p('Error: ', error), html.button(on.click(reload), 'Retry') ), success: ({ value, reload }) => html.div( html.h2(value.$.name), html.p(value.$.email), html.button(on.click(reload), 'Refresh') ) }) ) } ``` ### Mutation for POST/PUT Operations ```typescript import { html, prop, attr, on, emitValue } from '@tempots/dom' import { Mutation } from '@tempots/ui' interface User { id: number name: string email: string } const CreateUserForm = () => { const name = prop('') const email = prop('') return html.div( Mutation<{ name: string; email: string }, User, string>({ mutate: async ({ request, abortSignal }) => { const res = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), signal: abortSignal }) if (!res.ok) throw new Error('Failed to create user') return res.json() }, convertError: e => e instanceof Error ? e.message : 'Unknown error', idle: ({ trigger }) => html.form( on.submit(e => { e.preventDefault() trigger({ name: name.value, email: email.value }) }), html.input( attr.placeholder('Name'), attr.value(name), on.input(emitValue(name.set)) ), html.input( attr.type('email'), attr.placeholder('Email'), attr.value(email), on.input(emitValue(email.set)) ), html.button(attr.type('submit'), 'Create User') ), pending: () => html.div('Creating user...'), failure: ({ error, reset }) => html.div( html.p('Error: ', error), html.button(on.click(reset), 'Try Again') ), success: ({ value, reset }) => html.div( html.p('User created: ', value.$.name), html.button(on.click(reset), 'Create Another') ) }) ) } ``` ## Router with Authentication ### Protected Routes ```typescript import { html, prop, Provide, Use, makeProviderMark, When, Fragment, attr, on, emitValue } from '@tempots/dom' import type { Signal, TNode } from '@tempots/dom' import { RootRouter, ChildRouter, Location, NavigationService, Anchor } from '@tempots/ui' interface User { id: number name: string email: string } // Auth provider const Auth = { mark: makeProviderMark>('Auth'), create: () => { const user = prop(null) return { value: user, dispose: user.dispose } } } // Protected route wrapper const ProtectedRoute = (content: () => TNode) => Use(Auth, user => When( user.map(u => u !== null), content, () => { // Redirect to login NavigationService.navigate('/login') return html.div('Redirecting to login...') } ) ) // App with routing const App = () => Provide(Auth, {}, () => Provide(Location, {}, () => html.div( Navigation(), RootRouter({ '/': () => html.div('Home - Public'), '/login': () => LoginPage(), '/dashboard': () => ProtectedRoute(() => Dashboard()), '/dashboard/*': () => ProtectedRoute(() => DashboardRoutes()), '*': () => html.div('404 - Not Found') }) ) ) ) // Nested dashboard routes const DashboardRoutes = () => ChildRouter({ '/profile': () => html.div('Profile Page'), '/settings': () => html.div('Settings Page'), '*': () => html.div('Dashboard Home') }) // Navigation component const Navigation = () => Use(Auth, user => html.nav( Anchor('/', 'Home'), When( user.map(u => u === null), () => Anchor('/login', 'Login'), () => Fragment( Anchor('/dashboard', 'Dashboard'), html.button( on.click(() => user.value = null), 'Logout' ) ) ) ) ) // Login page const LoginPage = () => Use(Auth, user => { const email = prop('') const password = prop('') const handleLogin = async () => { // Simulate login user.value = { id: 1, name: 'John', email: email.value } NavigationService.navigate('/dashboard') } return html.form( on.submit(e => { e.preventDefault(); handleLogin() }), html.h1('Login'), html.input( attr.type('email'), attr.placeholder('Email'), on.input(emitValue(email.set)) ), html.input( attr.type('password'), attr.placeholder('Password'), on.input(emitValue(password.set)) ), html.button(attr.type('submit'), 'Login') ) }) ``` ## Keyboard Shortcuts ### Global Keyboard Handler ```typescript import { html, prop, attr, on, emitValue, When } from '@tempots/dom' import { OnKeyPressed, OnEnterKey, OnEscapeKey } from '@tempots/ui' const KeyboardShortcutsDemo = () => { const isModalOpen = prop(false) const searchQuery = prop('') return html.div( // Global keyboard shortcuts OnKeyPressed({ key: 'k', ctrl: true, handler: () => { // Ctrl+K to open search document.querySelector('#search')?.focus() } }), OnKeyPressed({ key: 's', ctrl: true, handler: (e) => { e.preventDefault() console.log('Save triggered!') } }), // Search input with keyboard handling html.div( html.input( attr.id('search'), attr.placeholder('Search... (Ctrl+K)'), attr.value(searchQuery), on.input(emitValue(searchQuery.set)), OnEnterKey(() => { console.log('Searching for:', searchQuery.value) }), OnEscapeKey(() => { searchQuery.value = '' document.querySelector('#search')?.blur() }) ) ), // Modal with escape to close html.button( on.click(() => isModalOpen.value = true), 'Open Modal (Esc to close)' ), When(isModalOpen, () => html.div( attr.class('modal'), OnEscapeKey(() => isModalOpen.value = false), html.div( attr.class('modal-content'), html.h2('Modal Title'), html.p('Press Escape to close this modal'), html.button( on.click(() => isModalOpen.value = false), 'Close' ) ) ) ) ) } ``` ### Input with Key Modifiers ```typescript const TextEditor = () => { const text = prop('') const history = prop([]) const saveToHistory = () => { history.update(h => [...h, text.value]) } const undo = () => { history.update(h => { if (h.length === 0) return h const prev = h[h.length - 1] text.value = prev ?? '' return h.slice(0, -1) }) } return html.div( html.textarea( attr.value(text), on.input(emitValue(text.set)), // Ctrl+S to save OnKeyPressed({ key: 's', ctrl: true, handler: e => { e.preventDefault() saveToHistory() console.log('Saved!') } }), // Ctrl+Z to undo OnKeyPressed({ key: 'z', ctrl: true, handler: e => { e.preventDefault() undo() } }) ), html.div( 'History: ', history.map(h => h.length.toString()), ' saves' ) ) } ``` ## Click Outside Detection ```typescript import { html, prop, attr, on, When } from '@tempots/dom' import { OnClickOutside } from '@tempots/ui' const Dropdown = () => { const isOpen = prop(false) return html.div( attr.class('dropdown'), html.button( on.click(() => isOpen.update(v => !v)), 'Toggle Dropdown' ), When(isOpen, () => html.div( attr.class('dropdown-menu'), OnClickOutside(() => isOpen.value = false), html.ul( html.li('Option 1'), html.li('Option 2'), html.li('Option 3') ) ) ) ) } ``` ## Viewport Detection ```typescript import { html, attr, When } from '@tempots/dom' import { InViewport, WhenInViewport } from '@tempots/ui' const LazyLoadedSection = () => html.div( // Load content when element comes into view InViewport( { mode: 'partial', once: true }, isVisible => When(isVisible, () => html.div( attr.class('loaded'), 'Content loaded when scrolled into view!' ), () => html.div( attr.class('placeholder'), 'Scroll to load...' ) ) ) ) // Or use the convenience wrapper const AnimatedOnScroll = () => html.div( WhenInViewport( { mode: 'full' }, () => html.div( attr.class('animate-in'), 'This animates when fully visible' ) ) ) ``` ## Next Steps - [Learn about Signals](#signals) - [Explore UI Components](#ui-components) - [Server-Side Rendering](#ssr-headless-rendering) - [Troubleshooting](#troubleshooting-faq) --- # Render Rendering is the final act of applying `Renderable`s to the DOM. When defining renderables, nothing in the DOM is changed or updated, not until those renderables are applied using either `render()` or `renderWithContext()`. Both functions return a `cancel` function that can be used to undo the modifications described in the applied renderables. ## render() A rendering function that takes two mandatory arguments, a `Renderable` that describes the change to apply to the DOM and a parent node (either as an instance or as a CSS selector) where to apply those changes. Optionally you can pass an object with the following options: * a `document` instance to be used during the rendering. This is only necessary in special context, like rendering in a non-browser context when using a string selector for `parent`. * the `clear` option indicates if the rendering operation should also remove the contents that were potentially generated by the SSR component. * the `disposeWithParent` option indicates if the rendering operation should be automatically disposed when the parent node is removed from the DOM.```ts function render( node: Renderable, parent: Node | string, { doc, clear, disposeWithParent = true }: { doc?: Document clear?: boolean disposeWithParent?: boolean } = {} ): () => void ``` ## renderWithContext() `renderWithContext()` is a more atomic operation that requires an already instantiated `DOMContext`. Like `render` it returns a `cancel` function. ```ts function renderWithContext(node: Renderable, ctx: DOMContext): () => void ``` ## Next Steps Check out these resources: - [Explore Examples & Best Practices](#examples-patterns) - [Troubleshooting & FAQ](#troubleshooting-faq) - [Standard Library](#standard-library) - [UI Components](#ui-components) Or explore the API documentation for the Tempo libraries: - [@tempots/dom](#tempotsdom) - [@tempots/std](#tempotsstd) - [@tempots/ui](#tempotsui) --- # Troubleshooting & FAQ This page addresses common issues and questions that may arise when working with Tempo. ## Common Mistakes ### Renderables should not be payloads to signals The following is an anti-pattern.```typescript function Counter() { const p = prop(1); // DON'T! return p.map((value) => html.div(String(value))); } ``` This is how it should work: ```typescript function Counter() { const p = prop(1); // DO! return html.div(p.map(String)); } ``` In the rare case where a signal should really contain a Renderable, the `MapSignal` component is the way to go. The reason to avoid it is that the entire sub-tree DOM is re-rendered when the `prop` changes which is potentially inefficient. ```typescript function Counter() { const p = prop(1); // Correct but inefficient return MapSignal(p, (value) => html.div(String(value))); } ``` ## Frequently Asked Questions ### How does Tempo compare to React, Vue, or Angular? Tempo is a lightweight UI framework that takes a different approach: - **No Virtual DOM**: Tempo directly updates the DOM, which can be more efficient for many use cases. - **Fully Typed**: Built from the ground up with TypeScript for excellent type safety. - **Zero Dependencies**: Tempo has no external dependencies, making it lightweight. - **Functional Approach**: Uses plain functions rather than classes or JSX. - **Fine-Grained Reactivity**: Uses signals for precise updates rather than re-rendering components. ### Can I use Tempo with existing libraries? Yes! Tempo can be integrated with most JavaScript libraries. Since Tempo directly manipulates the DOM, you can use it alongside other libraries that do the same. You can: 1. Use the `WithElement` renderable to get a reference to a DOM element 2. Initialize third-party libraries with that element 3. Clean up resources with `OnDispose` ```typescript import { html, WithElement, OnDispose } from '@tempots/dom' import SomeThirdPartyLib from 'some-third-party-lib' const ThirdPartyComponent = () => html.div( WithElement(element => { // Initialize the third-party library const instance = new SomeThirdPartyLib(element) // Return a cleanup function return OnDispose(() => { instance.destroy() }) }) ) ``` The other way around is also possible. You can use Tempo to create renderables that can be used in other libraries. Just get access to a parent node and use `render` to render Tempo content. The function returned by `render` can be used to dispose of the rendered content. ```ts import { render } from '@tempots/dom' const cancel = render( html.div('Hello World'), document.getElementById('root') ) // later cancel() ``` ### Does Tempo support Server-Side Rendering (SSR)? Tempo has experimental support for server-side rendering. The `DOMContext` includes an `isFirstLevel` property that can be used to mark nodes for server-side rendering and hydration. However, this feature is still under development. ## Common Issues ### Signals not updating the UI If your signals are changing but the UI isn't updating, check: 1. **Signal Dependencies**: For computed signals, make sure you've included all dependencies in the dependency array. ```typescript // Incorrect - missing dependency // This still works but it only updates on variations of signal1 const computed = computed(() => signal1.value + signal2.value, [signal1]) // Correct const computed = computed(() => signal1.value + signal2.value, [signal1, signal2]) ``` 2. **Signal Equality**: Signals use reference equality by default. For objects, you might need to provide a custom equality function. ```typescript // Custom equality function for objects const userSignal = signal( { name: 'John', age: 30 }, (a, b) => a.name === b.name && a.age === b.age ) ``` 3. **Immutable Updates**: When updating objects or arrays in props, make sure to create new references. ```typescript // Incorrect - mutating the array const items = prop([1, 2, 3]) items.value.push(4) // UI won't update! // Correct - creating a new array items.value = [...items.value, 4] ``` ### Memory Leaks If your application is experiencing memory leaks, check: 1. **Cleanup Functions**: Make sure you're properly cleaning up resources with `OnDispose`. ```typescript WithElement(element => { const interval = setInterval(() => { // Do something }, 1000) return OnDispose(() => { clearInterval(interval) }) }) ``` 2. **Signal Listeners**: When working outside the renderable context, if you manually add listeners to signals, make sure to remove them. ```typescript const clear = signal.on(value => { // Do something with value }) // Later, when no longer needed clear() ``` If a Signal is disposed, it will automatically remove all listeners and you don't need to call `clear`. Within renderables, the scope is automatically tracked and signals are automatically disposed. The exception to that is if you define a signal in an async context where the scope cannot be automatically tracked. In this case you will have to manually dispose the signal. 3. **Event Listeners**: If you manually add DOM event listeners, make sure to remove them. ```typescript WithElement(element => { const handler = () => console.log('Clicked') element.addEventListener('click', handler) return OnDispose(() => { element.removeEventListener('click', handler) }) }) ``` ### TypeScript Errors If you're encountering TypeScript errors: 1. **Check TypeScript Version**: Tempo requires TypeScript 4.7 or later. 2. **Import Types**: Make sure you're importing types correctly. ```typescript // Import types import { Renderable, Signal, Prop } from '@tempots/dom' ``` 3. **Generic Type Parameters**: Make sure you're providing the correct type parameters. ```typescript // Specify the type parameter const userSignal = signal(null) ``` ## Next Steps - [Learn more about Signals](#signals) - [Explore Examples & Best Practices](#examples-patterns) - [Check out the Demos](https://tempo-ts.com/demo/hnpwa.html) --- # @tempots/core Core types and utilities for the multi-context Tempo framework. This package provides the foundational signal system, disposal management, and rendering primitives shared across all Tempo rendering contexts. ## Installation```bash npm install @tempots/core ``` ## Features - **Signal System** - Reactive state management with `Signal`, `Prop`, and `Computed` - **Disposal Scopes** - Automatic resource lifecycle management - **Renderables** - Type-safe renderable abstraction for multi-context rendering - **Providers** - Dependency injection via `makeProviderMark` - **Signal Utilities** - Rich set of derived signal operators ## Signals Signals are the reactive primitives that drive all state management in Tempo: ```typescript import { prop, computed, effect } from '@tempots/core' // Prop: read-write signal const count = prop(0) // Computed: derived signal const doubled = computed(() => count.value * 2) // Effect: side-effect on signal changes effect(() => console.log('Count:', count.value)) // Update triggers reactivity count.set(5) // logs "Count: 5", doubled.value === 10 ``` ### Signal Transformations ```typescript // Map, filter, flatMap const label = count.map(n => `Count: ${n}`) const positive = count.filter(n => n > 0) // Async mapping const data = count.mapAsync(async n => fetch(`/api/${n}`).then(r => r.json())) // Feed into another prop const target = prop('') count.feedProp(target, n => String(n)) ``` ### Signal Utilities ```typescript import { and, or, not, notNil, throttleSignal, distinctUntilChanged, accumulateSignal } from '@tempots/core' // Boolean combinators const canSubmit = and(isValid, isNotLoading) const showWarning = or(hasError, isExpired) const isHidden = not(isVisible) // Throttle rapid updates const throttled = throttleSignal(mouseMoveSignal, 16) // ~60fps // Skip duplicate values const unique = distinctUntilChanged(searchQuery) // Accumulate values (scan/reduce) const sum = accumulateSignal(valueSignal, (acc, val) => acc + val, 0) ``` ### Storage-Backed Props ```typescript import { localStorageProp, sessionStorageProp, storedProp } from '@tempots/core' const theme = localStorageProp({ key: 'theme', defaultValue: 'light' }) const token = sessionStorageProp({ key: 'auth-token', defaultValue: '' }) ``` ### History / Undo-Redo ```typescript import { prop, propHistory } from '@tempots/core' const counter = prop(0) const history = propHistory(counter) counter.set(1); counter.set(2); counter.set(3) history.undo() // counter.value === 2 history.redo() // counter.value === 3 history.go(0) // counter.value === 0 ``` ## Disposal Scopes Signals created within a `DisposalScope` are automatically tracked and disposed when the scope is disposed: ```typescript import { DisposalScope, prop, computed } from '@tempots/core' const scope = new DisposalScope() const count = scope.prop(0) const doubled = scope.computed(() => count.value * 2) // Later: disposes both signals scope.dispose() ``` ## Easing Functions A comprehensive set of easing functions for smooth animations. These are used by `animateSignal` and `createTween` but can be used standalone: ```typescript import { easeInOutCubic, easeOutElastic, reverseEasing, mirrorEasing, chainEasing } from '@tempots/core' import type { EasingFn } from '@tempots/core' // Use with animateSignal const animated = animateSignal(position, { duration: 300, easing: easeInOutCubic, }) // Combine easings with combinators const customEasing = chainEasing(easeInQuad, easeOutElastic) const symmetric = mirrorEasing(easeInCubic) const reversed = reverseEasing(easeInQuad) // produces easeOut curve ``` ### Available Easings | Family | In | Out | InOut | |--------|------|------|-------| | Quad | `easeInQuad` | `easeOutQuad` | `easeInOutQuad` | | Cubic | `easeInCubic` | `easeOutCubic` | `easeInOutCubic` | | Quart | `easeInQuart` | `easeOutQuart` | `easeInOutQuart` | | Sine | `easeInSine` | `easeOutSine` | `easeInOutSine` | | Expo | `easeInExpo` | `easeOutExpo` | `easeInOutExpo` | | Back | `easeInBack` | `easeOutBack` | `easeInOutBack` | | Bounce | `easeInBounce` | `easeOutBounce` | `easeInOutBounce` | | Elastic | `easeInElastic` | `easeOutElastic` | `easeInOutElastic` | Plus `linear` for no easing. ### Combinators - **`reverseEasing(fn)`** — Plays the easing backwards. `reverseEasing(easeIn)` produces an ease-out curve. - **`mirrorEasing(fn)`** — First half uses `fn`, second half plays it in reverse. Creates symmetric in-out easings from a single ease-in. - **`chainEasing(a, b)`** — Uses `a` for the first half, `b` for the second half. Compose any two easings sequentially. ## Core Types ### Renderable The universal rendering abstraction that all platform packages build upon: ```typescript import { createRenderable } from '@tempots/core' const MY_TYPE = Symbol('MY_RENDERABLE') const myComponent = createRenderable(MY_TYPE, (ctx) => { // Render logic here return (removeTree) => { // Cleanup logic here } }) ``` ### TNode Represents any content that can be rendered — renderables, strings, signals of strings, arrays, or null: ```typescript type TNode = Renderable | Value | null | undefined | Renderable[] ``` ### Value A type that can be either a static value or a reactive signal: ```typescript type Value = T | Signal ``` ## Usage `@tempots/core` is typically used indirectly through platform-specific packages like `@tempots/dom` or `@tempots/native`. Those packages re-export the signal system and provide higher-level APIs built on these core types. For more information, see the [Tempo documentation](/). --- # @tempots/render Platform-agnostic shared renderables for the Tempo framework. This package provides the `createRenderKit` factory that each platform (DOM, Native, etc.) calls with its own renderable factory to get correctly-branded shared renderables. ## Installation```bash npm install @tempots/render ``` ## Overview `@tempots/render` sits between `@tempots/core` (signals, types) and platform packages (`@tempots/dom`, `@tempots/native`). It implements all the shared rendering logic — conditional rendering, list rendering, async data loading, dependency injection — in a platform-agnostic way. Each platform package calls `createRenderKit()` with its own factory function to get renderables branded for that platform's type system. ## Features - **Conditional Rendering** - `When`, `Unless`, `Ensure`, `EnsureAll`, `NotEmpty` - **List Rendering** - `ForEach`, `Repeat` - **Pattern Matching** - `OneOf`, `OneOfValue`, `OneOfField`, `OneOfKind`, `OneOfType`, `OneOfTuple` - **Async Data** - `Task`, `Async` with pending/success/error states - **Dependency Injection** - `Provide`, `Use` for provider-based context sharing - **Composition** - `Fragment`, `Empty`, `Conjunction` - **Signal Mapping** - `MapSignal` for rendering signal-driven content ## How It Works ### The RenderKit Factory ```typescript import { createRenderKit } from '@tempots/render' // Each platform creates its own branded kit const domKit = createRenderKit({ type: DOM_TYPE, create: (renderFn) => ({ type: DOM_TYPE, render: renderFn }), renderTNode: (ctx, node) => { /* DOM-specific TNode rendering */ }, wrapContext: (ctx, providers) => ctx.withProviders(providers), getProviders: (ctx) => ctx.providers, }) // domKit now has: When, ForEach, Repeat, OneOf, Task, Provide, Use, etc. // All correctly typed for DOMContext ``` ### Shared Renderables All renderables returned by `createRenderKit` share the same implementation but are branded for their specific platform: ```typescript // These work identically on DOM and Native When(isVisible, () => content) ForEach(items, (item, position) => renderItem(item)) Provide(ThemeProvider, darkTheme, content) Use(ThemeProvider, theme => renderWithTheme(theme)) Task({ run: () => fetchData(), success: data => renderData(data) }) ``` ## Architecture ``` @tempots/core (signals, types) | @tempots/render (shared renderables via createRenderKit) | +----+--------+ | | @tempots/dom @tempots/native ``` This layered architecture ensures: - **Code reuse**: Conditional rendering, list rendering, etc. are implemented once - **Type safety**: Each platform's renderables are branded and cannot be mixed - **Extensibility**: New platforms only need to implement the factory config For more information, see the [Tempo documentation](/). --- # @tempots/dom Tempo DOM is a lightweight UI Framework for building web applications. It has no dependencies and it is built with [TypeScript](https://www.typescriptlang.org/). To install use:```bash # npm npm install @tempots/dom # yarn yarn add @tempots/dom ``` ## Animation & Motion ### Easing Functions All easing functions from `@tempots/core` are re-exported for convenience. See the core package documentation for the full list of 25 standard easings and 3 combinators. ```typescript import { easeInOutCubic, easeOutElastic, animateSignal, prop } from '@tempots/dom' const position = prop(0) const animated = animateSignal(position, { duration: 300, easing: easeInOutCubic, }) ``` ### Signal Tween An imperative tween that drives a reactive signal from its current value to a target using easing. Complements `animateSignal` (declarative) with explicit `tweenTo()` control. ```typescript import { createTween, easeInOutCubic } from '@tempots/dom' const tween = createTween({ x: 0, y: 0 }, { duration: 300, easing: easeInOutCubic, interpolate: (a, b, t) => ({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t, }), }) tween.tweenTo({ x: 100, y: 200 }) // Later: tween.cancel() or tween.dispose() ``` Supports `reducedMotion: Signal` to skip animations for accessibility. ### RAF Loop A `requestAnimationFrame` loop with delta-time tracking. Available as both an imperative utility and a Tempo renderable. ```typescript import { createRafLoop, RafLoop, html } from '@tempots/dom' // Imperative (inside WithElement callbacks) const handle = createRafLoop((dt) => { offset = (offset + speed * dt) % totalLength }) // Later: handle.dispose() // Renderable (auto-disposes with component lifecycle) html.canvas( RafLoop((dt) => { // update canvas each frame }) ) ``` ### Reduced Motion A reactive signal that tracks the user's `prefers-reduced-motion` system preference. Available as both a factory function and a Tempo Provider. ```typescript import { createReducedMotionSignal, ReducedMotion, Provide, Use } from '@tempots/dom' // Standalone const reducedMotion = createReducedMotionSignal() if (reducedMotion.get()) { // skip animation } // As a Provider (app-wide singleton) Provide(ReducedMotion, undefined, Use(ReducedMotion, (reducedMotion) => When(reducedMotion, html.span('Animations disabled')) ) ) ``` ## List Transitions ### TransitionKeyedForEach A drop-in replacement for `KeyedForEach` that supports exit animations. Items leaving the list stay in the DOM for a configurable duration with an `isExiting` signal, allowing CSS transitions to play before removal. ```typescript import { TransitionKeyedForEach, prop, html, attr } from '@tempots/dom' const items = prop([ { id: '1', text: 'Buy groceries' }, { id: '2', text: 'Walk the dog' }, ]) TransitionKeyedForEach( items, (item) => item.id, (item, position, isExiting) => html.div( attr.class('todo-item'), attr.class(isExiting.map(e => e ? 'todo-item--exiting' : '')), item.map(i => i.text), ), { exitDuration: 300 } ) ``` ```css .todo-item--exiting { animation: fade-out 300ms ease-out forwards; } ``` Configuration options: - `exitClass` — CSS class added during exit animation - `exitDuration` — Milliseconds before DOM removal - `enterClass` — CSS class added during enter animation - `enterDuration` — Milliseconds before enter class is removed - `useAnimationEvents` — Listen for `animationend`/`transitionend` instead of fixed duration ## Touch & Gesture Handlers ### Pinch-Zoom Two-finger pinch-to-zoom with simultaneous pan. Available as both an imperative handler and a Tempo renderable. ```typescript import { PinchZoom, prop, html } from '@tempots/dom' import type { PinchZoomState } from '@tempots/dom' const viewport = prop({ scale: 1, panX: 0, panY: 0 }) html.div( attr.style(viewport.map(v => ({ transform: `translate(${v.panX}px, ${v.panY}px) scale(${v.scale})`, }))), PinchZoom(viewport, { minScale: 0.25, maxScale: 4 }), // ... content ) ``` ### Inertia Scroll Physics-based inertia for pan/scroll interactions. When the user releases a drag, the surface continues scrolling with exponential velocity decay. ```typescript import { Inertia, prop, html } from '@tempots/dom' const offset = prop({ x: 0, y: 0 }) html.div( Inertia( (dx, dy) => offset.set({ x: offset.get().x + dx, y: offset.get().y + dy, }), { friction: 0.95 } ), // ... scrollable content ) ``` ## DOM Utilities ### isInputFocused Checks whether the currently focused element is an editable form control. Useful for keyboard shortcut systems. ```typescript import { isInputFocused } from '@tempots/dom' document.addEventListener('keydown', (e) => { if (isInputFocused()) return // don't intercept typing if (e.key === 'Delete') deleteSelected() }) ``` For more information, see the [Tempo documentation](/). --- # @tempots/native JSI Bridge <--> Native Views (iOS/Android) ``` ## Quick Start ```typescript import { renderNative, view, nativeStyle, nativeOn, prop } from '@tempots/native' function Counter() { const count = prop(0) return view.View( view.Text( count.map(n => `Count: ${n}`), nativeStyle.style({ fontSize: 48, textAlign: 'center', color: '#333' }) ), view.View( view.View( nativeOn.press(() => count.set(count.value - 1)), view.Text('-'), nativeStyle.style({ padding: 20, backgroundColor: '#ff6b6b' }) ), view.View( nativeOn.press(() => count.set(count.value + 1)), view.Text('+'), nativeStyle.style({ padding: 20, backgroundColor: '#51cf66' }) ), nativeStyle.style({ flexDirection: 'row', justifyContent: 'center', gap: 20 }) ), nativeStyle.style({ flex: 1, justifyContent: 'center', alignItems: 'center' }) ) } renderNative(Counter()) ``` ## Native Views Create native views using the `view` proxy object: ```typescript import { view } from '@tempots/native' view.View(...) // Container view view.Text('Hello') // Text display view.Image(...) // Image display view.ScrollView(...) // Scrollable container view.TextInput(...) // Text input field view.SafeAreaView(...) // Safe area container view.FlatList(...) // Optimized list view.Modal(...) // Modal overlay view.Switch(...) // Toggle switch view.MyCustomView(...) // Any custom native view type ``` ## Styles Apply styles statically or reactively: ```typescript import { nativeStyle, applyStyle } from '@tempots/native' import { prop } from '@tempots/core' // Static styles view.View( nativeStyle.style({ flex: 1, flexDirection: 'row', backgroundColor: '#f0f0f0', padding: 16, borderRadius: 8, }) ) // Reactive styles const bg = prop('#f0f0f0') view.View( applyStyle(bg.map(color => ({ backgroundColor: color }))) ) ``` ## Events Handle native touch and input events: ```typescript import { nativeOn } from '@tempots/native' view.View( nativeOn.press((e) => console.log('Pressed at', e.pageX, e.pageY)), nativeOn.longPress((e) => console.log('Long pressed')), ) view.TextInput( nativeOn.changeText((e) => console.log('Text:', e.text)), nativeOn.submitEditing((e) => console.log('Submitted:', e.text)), ) ``` ## Shared Renderables All standard Tempo renderables work with native contexts: - `When` / `Unless` - Conditional rendering - `ForEach` - List rendering from reactive arrays - `Repeat` - Render N items - `OneOf` / `OneOfValue` - Pattern matching - `Task` / `Async` - Async data loading - `Provide` / `Use` - Dependency injection ## Navigation In-memory navigation with a history stack: ```typescript import { createNavigator } from '@tempots/native' type Route = 'home' | 'about' | 'settings' const nav = createNavigator('home') nav.navigate('about') // push "about" onto the stack nav.back() // pop back to "home" ``` ## Lifecycle Signals Track app-level state changes: ```typescript import { createAppStateSignal, createDimensionsSignal, createKeyboardSignal } from '@tempots/native' const appState = createAppStateSignal(bridge) // "active" | "background" | "inactive" const dims = createDimensionsSignal(bridge) // { width, height, scale, fontScale } const keyboard = createKeyboardSignal(bridge) // { visible: boolean, height: number } ``` For more information, see the [Tempo documentation](/). --- # @tempots/server Tempo Server provides server-side rendering (SSR) utilities for Tempo applications. Render your Tempo components to HTML strings or streams for faster initial page loads and better SEO. ## Installation```bash npm install @tempots/server ``` ## Features - **renderToString** - Render components to HTML strings - **renderToStream** - Stream rendered HTML for faster time-to-first-byte - **createRenderer** - High-level convenience function for SSR entry points - Hydration placeholder generation for client-side rehydration ## Quick Example ```typescript import { renderToString } from '@tempots/server' import { html } from '@tempots/dom' const App = () => html.div( html.h1('Hello, SSR!'), html.p('Rendered on the server.') ) const htmlString = await renderToString(App(), { url: 'https://example.com/page', generatePlaceholders: true, }) ``` For complete documentation, see [SSR & Headless Rendering](#ssr-headless-rendering). --- # @tempots/client Tempo Client provides client-side hydration utilities for Tempo SSR applications. Hydrate server-rendered HTML to make it interactive, or use the islands architecture to selectively hydrate only interactive components. ## Installation```bash npm install @tempots/client ``` ## Features - **hydrate** - Hydrate server-rendered HTML with client-side interactivity - **startClient** - High-level client initialization with islands support - **Islands Architecture** - Hydrate only interactive components while keeping the rest static - Multiple hydration strategies: immediate, idle, visible, and media query-based ## Quick Example ```typescript import { startClient } from '@tempots/client' import { App } from './App' import { Counter, TodoList } from './islands' startClient({ app: () => App(), islands: { Counter, TodoList }, container: '#app', }) ``` ## Islands Architecture Islands allow selective hydration of interactive components: ```typescript import { islandMarker, attr, html } from '@tempots/dom' const CounterIsland = (initial: number) => html.div( ...islandMarker('Counter', { initial }, 'visible').map( ({ name, value }) => attr[name](value) ), html.span(String(initial)) // Static placeholder ) ``` Hydration strategies: - `"immediate"` - Hydrate as soon as possible - `"idle"` - Hydrate when browser is idle - `"visible"` - Hydrate when scrolled into view - `{ media: "(min-width: 768px)" }` - Hydrate when media query matches For complete documentation, see [SSR & Headless Rendering](#ssr-headless-rendering). --- # Tempo Standard Library (@tempots/std) A comprehensive standard library for TypeScript that provides utility functions and types commonly used in web applications. This package serves as a natural complement to the Tempo libraries but can be used independently in any TypeScript project. ## Overview The Tempo Standard Library fills the gaps in JavaScript's standard library with a collection of well-tested, type-safe utility functions. It follows functional programming principles and provides consistent APIs across all modules. ## Key Features - **Zero Dependencies**: Lightweight with no external dependencies - **Type Safe**: Full TypeScript support with comprehensive type definitions - **Functional**: Immutable operations and functional programming patterns - **Modular**: Import only what you need for optimal bundle size - **Well Tested**: Comprehensive test coverage for reliability - **Consistent APIs**: Uniform naming and parameter conventions ## Module Categories ### Data Manipulation - **array**: Array operations (map, filter, reduce, unique, etc.) - **object**: Object manipulation and transformation utilities - **string**: String processing and formatting functions ### Async Operations - **promise**: Promise utilities and helpers - **deferred**: Promise deferral and cancellation - **async-result**: Asynchronous result handling with error management ### Type Safety - **result**: Result type for error handling without exceptions - **validation**: Data validation and type checking utilities - **domain**: Common domain types and type guards ### Utilities - **function**: Function composition and manipulation - **timer**: Timing utilities and delays - **equal**: Deep equality comparison functions - **json**: JSON parsing and serialization helpers ### Numeric Operations - **number**: Number utilities and mathematical operations (clamp, interpolate, snapToGrid, wrap, etc.) - **bigint**: BigInt manipulation functions ## Design Principles ### Functional Programming All functions are pure and side-effect free where possible:```typescript // Pure function - doesn't modify input const doubled = mapArray([1, 2, 3], x => x * 2) // [2, 4, 6] // Immutable operations const filtered = filterArray(numbers, x => x > 5) ``` ### Type Safety Comprehensive TypeScript support with generic types: ```typescript // Type-safe array operations const strings: string[] = ['a', 'b', 'c'] const lengths: number[] = mapArray(strings, s => s.length) // Result type for error handling const result: Result = parseNumber('42') ``` ### Consistent APIs Uniform parameter ordering and naming conventions: ```typescript // Data first, function second pattern mapArray(array, fn) filterArray(array, predicate) foldLeftArray(array, reducer, initial) ``` ## Usage Patterns ### Error Handling with Result Type ```typescript import { Result, success, failure } from '@tempots/std/result' function divide(a: number, b: number): Result { if (b === 0) { return failure('Division by zero') } return success(a / b) } const result = divide(10, 2) result.match({ success: value => console.log(`Result: ${value}`), failure: error => console.error(`Error: ${error}`) }) ``` ### Array Processing ```typescript import { mapArray, filterArray, foldLeftArray } from '@tempots/std/array' const numbers = [1, 2, 3, 4, 5] const evenSquares = mapArray( filterArray(numbers, n => n % 2 === 0), n => n * n ) // [4, 16] const sum = foldLeftArray(numbers, (acc, n) => acc + n, 0) // 15 ``` ### Async Operations ```typescript import { deferred } from '@tempots/std/deferred' import { delayed } from '@tempots/std/timer' // Create a deferred promise const { promise, resolve, reject } = deferred() // Delay execution const cancel = delayed(() => resolve('Done!'), 1000) // Use the promise promise.then(value => console.log(value)) ``` ## Integration with Tempo While @tempots/std can be used independently, it integrates seamlessly with Tempo: ```typescript import { prop, computed } from '@tempots/dom' import { mapArray, filterArray } from '@tempots/std' const items = prop([1, 2, 3, 4, 5]) const evenItems = computed(() => filterArray(items.value, n => n % 2 === 0) ) const doubled = computed(() => mapArray(evenItems.value, n => n * 2) ) ``` ## Performance Considerations - **Tree Shaking**: Import specific functions to minimize bundle size - **Immutability**: Functions create new objects rather than mutating inputs - **Lazy Evaluation**: Some operations support lazy evaluation patterns - **Memory Efficiency**: Optimized algorithms for common operations ## Best Practices 1. **Import Specifically**: Import only the functions you need ```typescript import { mapArray } from '@tempots/std/array' // Instead of: import { mapArray } from '@tempots/std' ``` 2. **Use Result Types**: Prefer Result types over throwing exceptions ```typescript // Good function parseNumber(str: string): Result // Avoid function parseNumber(str: string): number // throws on error ``` 3. **Compose Functions**: Build complex operations from simple functions ```typescript const processData = (data: string[]) => mapArray( filterArray(data, s => s.length > 0), s => s.toUpperCase() ) ``` ## Contributing See the main [CONTRIBUTING.md](../../CONTRIBUTING.md) for development setup and guidelines. ## Documentation For detailed API documentation, see the [Tempo Documentation Site](https://tempo-ts.com/library/tempots-std.html). --- # @tempots/vite Tempo Vite is a Vite plugin for building Tempo applications with SSR, SSG, or islands architecture. It provides automatic route discovery, static site generation, and seamless integration with Vite's build system. ## Installation```bash npm install @tempots/vite ``` ## Features - **SSG Mode** - Static site generation with pre-rendered HTML - **SSR Mode** - Server-side rendering for dynamic content - **Islands Mode** - Selective hydration of interactive components - **Route Crawling** - Automatic route discovery by crawling internal links - Seamless Vite integration ## Quick Example ```typescript // vite.config.ts import { defineConfig } from 'vite' import { tempo } from '@tempots/vite' export default defineConfig({ plugins: [ tempo({ mode: 'ssg', ssrEntry: 'src/entry-server.ts', routes: 'crawl', // Auto-discover routes hydrate: true, }) ] }) ``` ## Route Discovery ```typescript // Explicit routes tempo({ routes: ['/', '/about', '/contact'] }) // Dynamic routes tempo({ routes: async () => { const posts = await fetchBlogPosts() return ['/', ...posts.map(p => `/blog/${p.slug}`)] } }) // Crawl with seed routes (default) tempo({ routes: 'crawl', seedRoutes: ['/', '/docs'], }) ``` For complete documentation, see [SSR & Headless Rendering](#ssr-headless-rendering). --- # Tempo UI (@tempots/ui) A collection of reusable UI components and renderables built on top of @tempots/dom to accelerate development with Tempo. This package provides higher-level abstractions for common UI patterns and components. ## Overview Tempo UI bridges the gap between the low-level DOM manipulation of @tempots/dom and the high-level components needed for real applications. It provides a curated set of components, utilities, and patterns that solve common UI development challenges. ## Key Features - **Component Library**: Pre-built components for common UI patterns - **Routing System**: Client-side routing with URL synchronization - **Query Management**: Async data loading with loading/error states - **Form Utilities**: Enhanced form controls and input handling - **Accessibility**: Built-in accessibility features and ARIA support - **Performance**: Optimized components with minimal overhead - **Type Safety**: Full TypeScript support with comprehensive types ## Component Categories ### Input Enhancement - **AutoFocus**: Automatically focus elements when rendered - **AutoSelect**: Automatically select text in inputs - **SelectOnFocus**: Select text when input receives focus ### Layout & Visibility - **InViewport**: Detect when elements enter/exit the viewport - **HiddenWhenEmpty**: Hide elements when they have no content - **PopOver**: Create popup and popover elements - **Size**: Responsive size utilities ### Navigation & Routing - **Router**: Client-side routing system - **Location**: Navigation and location utilities - **Anchor**: Enhanced anchor link handling ### Data Loading - **Query**: Async data loading with loading/error states - **AsyncResultView**: Display async operation results - **ResultView**: Display success/failure results ### Utilities - **HTMLTitle**: Dynamic document title management - **Appearance**: Theme and appearance utilities - **Ticker**: Time-based updates and animations ## Design Philosophy ### Composition Over Configuration Components are designed to be composed together rather than configured with many options:```typescript // Compose multiple behaviors html.input( AutoFocus(), AutoSelect(), SelectOnFocus(), attr.placeholder('Enter text...') ) ``` ### Minimal API Surface Each component has a focused, minimal API that does one thing well: ```typescript // Simple, focused APIs const AutoFocus = (delay: number = 10): Renderable const InViewport = (options: IntersectionObserverInit, render: (isVisible: Signal) => TNode): Renderable ``` ### Framework Integration Components integrate seamlessly with Tempo's reactive system: ```typescript // Reactive integration const isVisible = prop(false) InViewport({}, visible => { isVisible.set(visible.value) return html.div('Content is visible!') }) ``` ## Routing System ### Basic Routing ```typescript import { Router, Location } from '@tempots/ui' const AppRouter = Router({ '/': () => html.div('Home Page'), '/about': () => html.div('About Page'), '/users/:id': (info) => { const userId = info.$.params.$.id return html.div('User Profile: ', userId) }, '*': () => html.div('404 - Not Found') }) render(AppRouter, document.body) ``` ### Programmatic Navigation ```typescript // Navigate to different routes Location.navigate('/about') Location.navigate('/users/123') // Access current location Use(Location, location => { return html.div('Current path: ', location.$.pathname) }) ``` ### Route Parameters ```typescript // Extract route parameters '/users/:id/posts/:postId': (info) => { const userId = info.$.params.$.id const postId = info.$.params.$.postId return UserPost({ userId, postId }) } ``` ## Query Management ### Basic Query Loading ```typescript import { Query } from '@tempots/ui' const userQuery = Query({ load: () => fetch('/api/user').then(r => r.json()), loading: () => html.div('Loading user...'), error: (err) => html.div('Error: ', err.message), success: (user) => html.div( html.h2(user.name), html.p(user.email) ) }) ``` ### Advanced Query Patterns ```typescript // Query with dependencies const userPosts = Query({ load: async () => { const user = await fetchUser() const posts = await fetchUserPosts(user.id) return { user, posts } }, loading: () => SkeletonLoader(), error: (err) => ErrorMessage({ error: err }), success: ({ user, posts }) => UserPostsList({ user, posts }) }) ``` ## Form Enhancement ### Input Focus Management ```typescript import { AutoFocus, AutoSelect, SelectOnFocus } from '@tempots/ui' function LoginForm() { const username = prop('') const password = prop('') return html.form( html.input( AutoFocus(), // Focus on render SelectOnFocus(), // Select text on focus attr.placeholder('Username'), attr.value(username), on.input(e => username.set(e.target.value)) ), html.input( attr.type('password'), attr.placeholder('Password'), attr.value(password), on.input(e => password.set(e.target.value)) ) ) } ``` ### Form Validation ```typescript // Combine with @tempots/std for validation import { Result, success, failure } from '@tempots/std' const validateEmail = (email: string): Result => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ return emailRegex.test(email) ? success(email) : failure('Invalid email format') } const emailInput = prop('') const emailValidation = emailInput.map(validateEmail) ``` ## Viewport Detection ### Lazy Loading ```typescript import { InViewport } from '@tempots/ui' const LazyImage = ({ src, alt }: { src: string, alt: string }) => InViewport( { threshold: 0.1 }, isVisible => isVisible.value ? html.img(attr.src(src), attr.alt(alt)) : html.div(attr.class('placeholder'), 'Loading...') ) ``` ### Infinite Scrolling ```typescript const InfiniteList = ({ items, loadMore }: { items: Signal, loadMore: () => void }) => html.div( ForEach(items, item => ItemComponent(item)), InViewport( { threshold: 1.0 }, isVisible => { if (isVisible.value) loadMore() return html.div('Loading more...') } ) ) ``` ## Performance Considerations - **Lazy Loading**: Components only render when needed - **Event Cleanup**: Automatic cleanup of event listeners and observers - **Memory Management**: Proper disposal of resources and subscriptions - **Bundle Size**: Tree-shakeable components for optimal bundle size ## Integration Patterns ### With @tempots/dom ```typescript // Enhance basic DOM elements html.input( AutoFocus(), attr.value(signal), on.input(handler) ) ``` ### With @tempots/std ```typescript // Use std utilities for data processing import { mapArray, filterArray } from '@tempots/std' const processedItems = computed(() => mapArray( filterArray(items.value, item => item.visible), item => ({ ...item, processed: true }) ) ) ``` ## Best Practices 1. **Compose Components**: Build complex UI by composing simple components 2. **Use Signals**: Leverage reactive signals for dynamic behavior 3. **Handle Errors**: Always provide error states for async operations 4. **Accessibility**: Use semantic HTML and ARIA attributes 5. **Performance**: Use lazy loading and viewport detection for large lists ## Contributing See the main [CONTRIBUTING.md](../../CONTRIBUTING.md) for development setup and guidelines. ## Documentation For detailed API documentation, see the [Tempo Documentation Site](https://tempo-ts.com/library/tempots-ui.html). --- # @tempots/eslint-plugin ESLint plugin for TempoTS to help catch common signal usage issues and prevent memory leaks. ## Installation```bash npm install -D @tempots/eslint-plugin ``` ## Quick Setup Use the recommended configuration: ```javascript // eslint.config.js import tempots from '@tempots/eslint-plugin' export default [ tempots.configs.recommended, // ... your other configs ] ``` This enables all rules with sensible defaults. For maximum safety, use `tempots.configs.strict` instead (all rules set to `error`). ## Rules ### Signal Lifecycle - **`no-module-level-signals`** (warn) - Warns about signals created at module level outside renderables. Use `untracked()` for intentionally long-lived signals. - **`require-untracked-disposal`** (error) - Requires disposal of signals created with `untracked()` to prevent memory leaks. - **`require-async-signal-disposal`** (warn) - Requires proper disposal for signals created in async contexts (setTimeout, Promise callbacks) where auto-disposal doesn't apply. - **`no-unnecessary-disposal`** (warn) - Warns about redundant manual disposal of auto-disposed signals. Auto-fixable. - **`no-signal-reassignment`** (error) - Prevents reassignment of signal variables, which would leak the original signal. - **`prefer-const-signals`** (warn) - Prefers `const` for signal declarations to prevent accidental reassignment. Auto-fixable. ### Renderable Patterns - **`no-renderable-signal-map`** (warn) - Warns when mapping signals to renderables (e.g., `signal.map(v => html.div(v))`). Signals can be passed directly into renderables. - **`no-empty-fragment`** (warn) - Warns about `Fragment()` with no children. Use `Empty` instead. - **`no-single-child-fragment`** (warn) - Warns about `Fragment()` with a single child, which is unnecessary wrapping. ## Examples ### Correct signal usage ```typescript const MyComponent = ctx => { const count = prop(0) // Auto-disposed const doubled = count.map(x => x * 2) // Auto-disposed return html.div(count, ' x 2 = ', doubled) } ``` ### Async signals need manual tracking ```typescript const MyComponent = ctx => { const data = prop(null) // Create synchronously (auto-disposed) setTimeout(() => { data.set(fetchedValue) // Just update the value }, 1000) return html.div(data) } ``` ### Module-level signals need untracked ```typescript const globalCount = untracked(() => prop(0)) // Remember to dispose when done: globalCount.dispose() ``` ## Custom Configuration ```javascript // eslint.config.js import tempots from '@tempots/eslint-plugin' export default [ { plugins: { tempots }, rules: { 'tempots/no-module-level-signals': 'warn', 'tempots/require-untracked-disposal': 'error', 'tempots/no-signal-reassignment': 'error', 'tempots/prefer-const-signals': 'warn', }, }, ] ``` For more information, see the [Tempo documentation](/). --- # tempots-core api index [Home](#tempots-ui-api-index) ## tempots-core: API Reference ## tempots-core: Packages | Package | Description | | --- | --- | | [@tempots/core](#core) | Core types and utilities for multi-context Tempo framework. This package provides the foundational types and utilities that are shared across all Tempo rendering contexts (DOM, ThreeJS, Konva, PixiJS, etc.). | --- ## core.accumulatesignal: accumulateSignal() function Creates a signal that accumulates values over time using a reducer function, similar to `Array.reduce` but reactive. Each time the source signal changes, the reducer is called with the current accumulator and the new value. **Signature:**```typescript accumulateSignal: (signal: Signal, reducer: (acc: A, value: T) => A, initial: A, equals?: (a: A, b: A) => boolean) => Signal ```## core.accumulatesignal: Parameters | Parameter | Type | Description | | --- | --- | --- | | signal | [Signal](#core-signal)<T> | The source signal. | | reducer | (acc: A, value: T) => A | Function that takes the accumulator and the new value, returns the next accumulator. | | initial | A | The initial accumulator value. | | equals | (a: A, b: A) => boolean | _(Optional)_ Equality function for the accumulator. Defaults to `===`. | **Returns:** [Signal](#core-signal)<A> A new signal that emits the accumulated value. ## core.accumulatesignal: Example```typescript const clicks = prop(0) const total = accumulateSignal(clicks, (sum, n) => sum + n, 0) clicks.set(5) // total.value === 5 clicks.set(3) // total.value === 8 ``` --- ## core.and: and() function Creates a computed signal that emits true if all input signals are true. **Signature:**```typescript export declare function and(...args: Value[]): Computed; ```## core.and: Parameters | Parameter | Type | Description | | --- | --- | --- | | args | [Value](#core-value)<boolean>\[\] | The input signals. | **Returns:** [Computed](#core-computed)<boolean> - The computed signal. --- ## core.animatesignal: animateSignal() function Animates a signal by creating a new signal that transitions from an initial value to the current value of the input signal. **Signature:**```typescript animateSignal: (signal: Signal, options?: AnimateSignal) => Prop ```## core.animatesignal: Parameters | Parameter | Type | Description | | --- | --- | --- | | signal | [Signal](#core-signal)<T> | The input signal to animate. | | options | AnimateSignal<T> | _(Optional)_ The animation options. | **Returns:** [Prop](#core-prop)<T> - The animated signal. --- ## core.animatesignals: animateSignals() function Animates signals based on the provided options. **Signature:**```typescript animateSignals: (initialValue: T, fn: () => T, dependencies: Array, options?: AnimateSignalsOptions) => Prop ```## core.animatesignals: Parameters | Parameter | Type | Description | | --- | --- | --- | | initialValue | T | The initial value of the animation. | | fn | () => T | A function that returns the end value of the animation. | | dependencies | Array<[AnySignal](#core-anysignal)> | An array of signals that the animation depends on. | | options | AnimateSignalsOptions<T> | _(Optional)_ Optional options for the animation. | **Returns:** [Prop](#core-prop)<T> - The animated value as Prop --- ## core.anysignal: AnySignal type Represents any type of signal. It can be a Signal, Prop, or Computed. **Signature:**```typescript export type AnySignal = Signal | Prop | Computed; ``` **References:** [Signal](#core-signal), [Prop](#core-prop), [Computed](#core-computed) --- ## core.atgetter: AtGetter type Represents a type that maps each property of `T` to a `Signal` of its corresponding type. **Signature:**```typescript export type AtGetter = { [K in keyof T]-?: Signal; }; ``` **References:** [Signal](#core-signal) --- ## core.basevaluetype: BaseValueType type Gets the base value type of a given Value type. **Signature:**```typescript export type BaseValueType = NonNullable>; ``` **References:** [ValueType](#core-valuetype) --- ## core.bind: bind() function Binds a function or signal of a function to a set of signals and literals. **Signature:**```typescript bind: R, R = ReturnType>(fn: Value) => (...args: Values>) => Computed ```## core.bind: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | [Value](#core-value)<FN> | The function to bind. | **Returns:** (...args: [Values](#core-values)<Parameters<FN>>) => [Computed](#core-computed)<R> - A function that takes a set of signals and literals and returns a computed signal. --- ## core.chaineasing: chainEasing() function Chains two easing functions: `a` is used for the first half of the animation, `b` for the second half. **Signature:**```typescript chainEasing: (a: EasingFn, b: EasingFn) => EasingFn ```## core.chaineasing: Parameters | Parameter | Type | Description | | --- | --- | --- | | a | [EasingFn](#core-easingfn) | Easing for the first half. | | b | [EasingFn](#core-easingfn) | Easing for the second half. | **Returns:** [EasingFn](#core-easingfn) A new easing function. --- ## core.clear: Clear type A function that clears a resource. Clear functions are returned by renderables and are called when the rendered content needs to be removed. The `removeTree` parameter indicates whether the entire tree should be removed (true) or just the event listeners and reactive subscriptions (false). **Signature:**```typescript export type Clear = (removeTree: boolean) => void; ``` --- ## core.coalesce: coalesce() function **Signature:**```typescript export declare function coalesce(...args: readonly [...unknown[], L]): Computed>; ```## core.coalesce: Parameters | Parameter | Type | Description | | --- | --- | --- | | args | readonly \[...unknown\[\], L\] | | **Returns:** [Computed](#core-computed)<[ValueType](#core-valuetype)<L>> --- ## core.computed._constructor_: Computed.(constructor) Creates a new Computed signal. \*\*Auto-Registration:\*\* This constructor automatically registers the signal with the current disposal scope (if one exists). This ensures that computed signals created by methods like `.map()`, `.flatMap()`, `.filter()`, etc. are automatically tracked and disposed. When a computed signal is created within a renderable or `WithScope()`, it will be automatically disposed when the component unmounts or the scope is disposed. To create a computed signal that outlives the current scope, use `untracked()`:```typescript const globalSignal = untracked(() => mySignal.map(x => x * 2)); // Remember to dispose manually: globalSignal.dispose() ``` **Signature:** ```typescript constructor(_fn: () => T, equals?: (a: T, b: T) => boolean); ```## core.computed._constructor_: Parameters | Parameter | Type | Description | | --- | --- | --- | | \_fn | () => T | The function that computes the value of the signal. | | equals | (a: T, b: T) => boolean | _(Optional)_ The function used to compare two values of type T for equality. | --- ## core.computed.dispose: Computed.dispose() method Disposes the computed signal and cancels any pending recomputations. Uses structural parent references to remove itself from parents' derivative lists, then directly disposes own derivatives. **Signature:**```typescript dispose(): void; ``` **Returns:** void --- ## core.computed.get: Computed.get() method Gets the current value of the signal. **Signature:**```typescript get(): T; ``` **Returns:** T The current value of the signal. --- ## core.computed.is: Computed.is() method Checks if a value is an instance of `Computed`. **Signature:**```typescript static is(value: unknown): value is Computed; ```## core.computed.is: Parameters | Parameter | Type | Description | | --- | --- | --- | | value | unknown | The value to check. | **Returns:** value is [Computed](#core-computed)<T> `true` if the value is an instance of `Computed`, `false` otherwise. --- ## core.computed: computed() function Creates a computed signal that depends on other signals and updates when any of the dependencies change. **Signature:**```typescript computed: (fn: () => T, dependencies: Array, equals?: (a: T, b: T) => boolean) => Computed ```## core.computed: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | () => T | The function that computes the value. | | dependencies | Array<[AnySignal](#core-anysignal)> | The array of signals that the computed value depends on. | | equals | (a: T, b: T) => boolean | _(Optional)_ The equality function used to compare the previous and current computed values. | **Returns:** [Computed](#core-computed)<T> - The computed signal. --- ## core.computed.setdirty: Computed.setDirty() method Marks the signal as dirty, indicating that its value has changed and needs to be recalculated. If the signal is already dirty or disposed, this method does nothing. It also marks all dependent signals as dirty and schedules a notification to update their values. **Signature:**```typescript setDirty(): void; ``` **Returns:** void --- ## core.computed.value: Computed.value property Gets the value of the signal. **Signature:**```typescript get value(): T; ``` --- ## core.computedof: computedOf() function Creates a computed signal that depends on other signals or literal values and updates when any of the dependencies change. **Signature:**```typescript computedOf: []>(...args: T) => (fn: (...args: ValueTypes) => O, equals?: (a: O, b: O) => boolean) => import('./signal').Computed ```## core.computedof: Parameters | Parameter | Type | Description | | --- | --- | --- | | args | T | | **Returns:** <O>(fn: (...args: [ValueTypes](#core-valuetypes)<T>) => O, equals?: (a: O, b: O) => boolean) => import('./signal').[Computed](#core-computed)<O> - The computed signal. --- ## core.computedofasync: computedOfAsync() function Creates a computed signal that depends on other signals or literal values and performs an asynchronous computation when any of the dependencies change. This is the async version of `computedOf`. It handles Promise-based computations by providing an alternative value while the async operation is pending and optional error recovery. **Signature:**```typescript computedOfAsync: []>(...args: T) => (fn: (...args: [...ValueTypes, { abortSignal: AbortSignal; }]) => Promise, alt: O, recover?: (error: unknown) => O, equals?: (a: O, b: O) => boolean) => Prop ```## core.computedofasync: Parameters | Parameter | Type | Description | | --- | --- | --- | | args | T | The signals or literal values that the computation depends on. | **Returns:** <O>(fn: (...args: \[...[ValueTypes](#core-valuetypes)<T>, { abortSignal: AbortSignal; }\]) => Promise<O>, alt: O, recover?: (error: unknown) => O, equals?: (a: O, b: O) => boolean) => [Prop](#core-prop)<O> A function that takes the async computation function and configuration. ## core.computedofasync: Example```ts const userId = prop(1) const userData = computedOfAsync(userId)( async (id, { abortSignal }) => { const response = await fetch(`/api/users/${id}`, { signal: abortSignal }) return response.json() }, { name: 'Loading...', id: 0 }, // alt value while loading (error) => ({ name: 'Error', id: -1 }) // optional recovery ) ``` --- ## core.computedofasyncgenerator: computedOfAsyncGenerator() function Creates a computed signal that depends on other signals or literal values and performs an asynchronous generator computation when any of the dependencies change. This is the async generator version of `computedOf`. It handles streaming computations where multiple values are yielded over time. Each yield updates the signal. **Signature:**```typescript computedOfAsyncGenerator: []>(...args: T) => (fn: (...args: [...ValueTypes, { abortSignal: AbortSignal; }]) => AsyncGenerator, alt: O, recover?: (error: unknown) => O, equals?: (a: O, b: O) => boolean) => Prop ```## core.computedofasyncgenerator: Parameters | Parameter | Type | Description | | --- | --- | --- | | args | T | The signals or literal values that the computation depends on. | **Returns:** <O>(fn: (...args: \[...[ValueTypes](#core-valuetypes)<T>, { abortSignal: AbortSignal; }\]) => AsyncGenerator<O, void, unknown>, alt: O, recover?: (error: unknown) => O, equals?: (a: O, b: O) => boolean) => [Prop](#core-prop)<O> A function that takes the async generator function and configuration. ## core.computedofasyncgenerator: Example```ts const query = prop('hello') const streamingResponse = computedOfAsyncGenerator(query)( async function* (q, { abortSignal }) { const response = await fetch(`/api/stream?q=${q}`, { signal: abortSignal }) const reader = response.body!.getReader() let accumulated = '' while (true) { const { done, value } = await reader.read() if (done) break accumulated += new TextDecoder().decode(value) yield accumulated } }, '', // alt value while loading (error) => 'Error: ' + error // optional recovery ) ``` --- ## core.computedrecord: computedRecord() function Computes a value based on a record of signals and literals. **Signature:**```typescript computedRecord: >, O>(record: T, fn: (value: RemoveSignals) => O) => Computed ```## core.computedrecord: Parameters | Parameter | Type | Description | | --- | --- | --- | | record | T | The record containing signals and literals. | | fn | (value: [RemoveSignals](#core-removesignals)<T>) => O | The function to compute the value based on the literals. | **Returns:** [Computed](#core-computed)<O> - The computed value as a signal. --- ## core.createrenderable: createRenderable() function Creates a renderable object from a render function and type symbol. This is a helper function for creating renderables. Most users will use context-specific helpers like `domRenderable()` or `threeRenderable()` instead of calling this directly. **Signature:**```typescript export declare function createRenderable(type: TType, renderFn: (ctx: CTX) => Clear): Renderable; ```## core.createrenderable: Parameters | Parameter | Type | Description | | --- | --- | --- | | type | TType | The symbol type for runtime type checking | | renderFn | (ctx: CTX) => [Clear](#core-clear) | The function that renders content into the context | **Returns:** [Renderable](#core-renderable)<CTX, TType> A renderable object ## core.createrenderable: Example```typescript const DOM_RENDERABLE_TYPE = Symbol('DOM_RENDERABLE') const myComponent = createRenderable( DOM_RENDERABLE_TYPE, (ctx: DOMContext) => { const divCtx = ctx.makeChildElement('div', undefined) divCtx.makeChildText('Hello, World!') return (removeTree) => { divCtx.clear(removeTree) } } ) ``` --- ## core.createselector: createSelector() function Creates an O(1) selection primitive. Instead of creating a computed per item that ALL re-evaluate when the source changes, only the previously-selected and newly-selected items are notified. **Signature:**```typescript createSelector: (source: Signal, equals?: (a: T, b: T) => boolean) => ((key: T) => Signal) ```## core.createselector: Parameters | Parameter | Type | Description | | --- | --- | --- | | source | [Signal](#core-signal)<T> | The signal containing the currently selected value. | | equals | (a: T, b: T) => boolean | _(Optional)_ Equality function. Defaults to `===`. | **Returns:** ((key: T) => [Signal](#core-signal)<boolean>) A function that takes a key and returns a `Signal` that is `true` when that key matches the current source value. ## core.createselector: Example ```typescript const selected = prop(0) const isSelected = createSelector(selected) // Each call returns a Signal that only updates // when this specific key becomes or stops being selected const isItem1 = isSelected(1) // Signal const isItem2 = isSelected(2) // Signal selected.set(1) // isItem1 -> true, isItem2 unchanged selected.set(2) // isItem1 -> false, isItem2 -> true ``` --- ## core.delaysignal: delaySignal() function Delays the value of a signal by a specified amount of time. **Signature:**```typescript delaySignal: (signal: Signal, ms: number | ((value: T) => number)) => Signal ```## core.delaysignal: Parameters | Parameter | Type | Description | | --- | --- | --- | | signal | [Signal](#core-signal)<T> | The signal to delay. | | ms | number \\| ((value: T) => number) | The amount of time to delay the signal in milliseconds. | **Returns:** [Signal](#core-signal)<T> - The delayed signal. --- ## core.disposalscope.computed: DisposalScope.computed() method Creates a computed signal and tracks it in this scope. Use this method in async contexts where automatic tracking doesn't work. **Signature:**```typescript computed(fn: () => T, dependencies: Array, equals?: (a: T, b: T) => boolean): Computed; ```## core.disposalscope.computed: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | () => T | The computation function | | dependencies | Array<[AnySignal](#core-anysignal)> | Array of signals this computed depends on | | equals | (a: T, b: T) => boolean | _(Optional)_ Optional equality function | **Returns:** [Computed](#core-computed)<T> A tracked Computed signal --- ## core.disposalscope.computedof: DisposalScope.computedOf() method Creates a computed signal with curried signature and tracks it in this scope. Use this method in async contexts where automatic tracking doesn't work. **Signature:**```typescript computedOf[]>(...args: T): (fn: (...args: ValueTypes) => O, equals?: (a: O, b: O) => boolean) => Computed; ```## core.disposalscope.computedof: Parameters | Parameter | Type | Description | | --- | --- | --- | | args | T | Values or signals to compute from | **Returns:** <O>(fn: (...args: [ValueTypes](#core-valuetypes)<T>) => O, equals?: (a: O, b: O) => boolean) => [Computed](#core-computed)<O> A function that takes the computation function and returns a tracked Computed signal --- ## core.disposalscope.dispose: DisposalScope.dispose() method Dispose all signals tracked by this scope. This method is idempotent - calling it multiple times is safe. **Signature:**```typescript dispose(): void; ``` **Returns:** void --- ## core.disposalscope.disposed: DisposalScope.disposed property Check if this scope has been disposed. **Signature:**```typescript get disposed(): boolean; ``` --- ## core.disposalscope.effect: DisposalScope.effect() method Creates an effect and tracks it in this scope. Use this method in async contexts where automatic tracking doesn't work. **Signature:**```typescript effect(fn: () => void, signals: Array, options?: ListenerOptions): () => void; ```## core.disposalscope.effect: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | () => void | The effect function | | signals | Array<[AnySignal](#core-anysignal)> | Array of signals to listen to | | options | [ListenerOptions](#core-listeneroptions) | _(Optional)_ Optional listener options | **Returns:** () => void A clear function (the effect itself is tracked in the scope) --- ## core.disposalscope.effectof: DisposalScope.effectOf() method Creates an effect with curried signature and tracks it in this scope. Use this method in async contexts where automatic tracking doesn't work. **Signature:**```typescript effectOf[]>(...args: T): (fn: (...args: ValueTypes) => void, options?: ListenerOptions) => (() => void); ```## core.disposalscope.effectof: Parameters | Parameter | Type | Description | | --- | --- | --- | | args | T | Values or signals to listen to | **Returns:** (fn: (...args: [ValueTypes](#core-valuetypes)<T>) => void, options?: [ListenerOptions](#core-listeneroptions)) => (() => void) A function that takes the effect function and returns a clear function --- ## core.disposalscope.gettrackedsignals: DisposalScope.getTrackedSignals() method Returns the signals tracked by this scope. Used by dev tools (HMR, signal inspector) to introspect scope contents. **Signature:**```typescript getTrackedSignals(): ReadonlyArray; ``` **Returns:** ReadonlyArray<[AnySignal](#core-anysignal)> Read-only array of tracked signals, empty if none or after disposal --- ## core.disposalscope: DisposalScope class A DisposalScope tracks signals created during its lifetime and disposes them when the scope ends. This enables automatic signal disposal without manual OnDispose() calls. **Signature:**```typescript export declare class DisposalScope implements Scope ```**Implements:** [Scope](#core-scope) ## core.disposalscope: Properties | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [disposed](#core-disposalscope-disposed) | `readonly` | boolean | Check if this scope has been disposed. | ## core.disposalscope: Methods | Method | Modifiers | Description | | --- | --- | --- | | [computed(fn, dependencies, equals)](#core-disposalscope-computed) | | Creates a computed signal and tracks it in this scope. Use this method in async contexts where automatic tracking doesn't work. | | [computedOf(args)](#core-disposalscope-computedof) | | Creates a computed signal with curried signature and tracks it in this scope. Use this method in async contexts where automatic tracking doesn't work. | | [dispose()](#core-disposalscope-dispose) | | Dispose all signals tracked by this scope. This method is idempotent - calling it multiple times is safe. | | [effect(fn, signals, options)](#core-disposalscope-effect) | | Creates an effect and tracks it in this scope. Use this method in async contexts where automatic tracking doesn't work. | | [effectOf(args)](#core-disposalscope-effectof) | | Creates an effect with curried signature and tracks it in this scope. Use this method in async contexts where automatic tracking doesn't work. | | [getTrackedSignals()](#core-disposalscope-gettrackedsignals) | | Returns the signals tracked by this scope. Used by dev tools (HMR, signal inspector) to introspect scope contents. | | [onDispose(callback)](#core-disposalscope-ondispose) | | Register a disposal callback to be called when this scope is disposed. Callbacks are called before signals are disposed. Use this for cleanup that doesn't need the `removeTree` parameter. | | [prop(value, equals)](#core-disposalscope-prop) | | Creates a prop signal and tracks it in this scope. Use this method in async contexts where automatic tracking doesn't work. | | [track(signal)](#core-disposalscope-track) | | Register a signal with this scope for automatic disposal. | --- ## core.disposalscope.ondispose: DisposalScope.onDispose() method Register a disposal callback to be called when this scope is disposed. Callbacks are called before signals are disposed. Use this for cleanup that doesn't need the `removeTree` parameter. **Signature:**```typescript onDispose(callback: () => void): void; ```## core.disposalscope.ondispose: Parameters | Parameter | Type | Description | | --- | --- | --- | | callback | () => void | The callback to call on disposal | **Returns:** void --- ## core.disposalscope.prop: DisposalScope.prop() method Creates a prop signal and tracks it in this scope. Use this method in async contexts where automatic tracking doesn't work. **Signature:**```typescript prop(value: T, equals?: (a: T, b: T) => boolean): Prop; ```## core.disposalscope.prop: Parameters | Parameter | Type | Description | | --- | --- | --- | | value | T | The initial value | | equals | (a: T, b: T) => boolean | _(Optional)_ Optional equality function | **Returns:** [Prop](#core-prop)<T> A tracked Prop signal --- ## core.disposalscope.track: DisposalScope.track() method Register a signal with this scope for automatic disposal. **Signature:**```typescript track(signal: AnySignal): void; ```## core.disposalscope.track: Parameters | Parameter | Type | Description | | --- | --- | --- | | signal | [AnySignal](#core-anysignal) | The signal to track | **Returns:** void --- ## core.distinctuntilchanged: distinctUntilChanged() function Creates a signal that only emits when the value changes according to the provided equality function. Useful downstream of `.map()` chains where a transformation may produce the same output for different inputs. **Signature:**```typescript distinctUntilChanged: (signal: Signal, equals?: (a: T, b: T) => boolean) => Signal ```## core.distinctuntilchanged: Parameters | Parameter | Type | Description | | --- | --- | --- | | signal | [Signal](#core-signal)<T> | The input signal. | | equals | (a: T, b: T) => boolean | _(Optional)_ Equality function to compare consecutive values. Defaults to `===`. | **Returns:** [Signal](#core-signal)<T> A new signal that skips consecutive equal values. --- ## core.easeinback: easeInBack variable Back ease-in (overshoots at start, s = 1.70158). **Signature:**```typescript easeInBack: EasingFn ``` --- ## core.easeinbounce: easeInBounce variable Bounce ease-in. **Signature:**```typescript easeInBounce: EasingFn ``` --- ## core.easeincubic: easeInCubic variable Cubic ease-in. **Signature:**```typescript easeInCubic: EasingFn ``` --- ## core.easeinelastic: easeInElastic variable Elastic ease-in. **Signature:**```typescript easeInElastic: EasingFn ``` --- ## core.easeinexpo: easeInExpo variable Exponential ease-in. **Signature:**```typescript easeInExpo: EasingFn ``` --- ## core.easeinoutback: easeInOutBack variable Back ease-in-out (overshoots both ends). **Signature:**```typescript easeInOutBack: EasingFn ``` --- ## core.easeinoutbounce: easeInOutBounce variable Bounce ease-in-out. **Signature:**```typescript easeInOutBounce: EasingFn ``` --- ## core.easeinoutcubic: easeInOutCubic variable Cubic ease-in-out. **Signature:**```typescript easeInOutCubic: EasingFn ``` --- ## core.easeinoutelastic: easeInOutElastic variable Elastic ease-in-out. **Signature:**```typescript easeInOutElastic: EasingFn ``` --- ## core.easeinoutexpo: easeInOutExpo variable Exponential ease-in-out. **Signature:**```typescript easeInOutExpo: EasingFn ``` --- ## core.easeinoutquad: easeInOutQuad variable Quadratic ease-in-out. **Signature:**```typescript easeInOutQuad: EasingFn ``` --- ## core.easeinoutquart: easeInOutQuart variable Quartic ease-in-out. **Signature:**```typescript easeInOutQuart: EasingFn ``` --- ## core.easeinoutsine: easeInOutSine variable Sine ease-in-out. **Signature:**```typescript easeInOutSine: EasingFn ``` --- ## core.easeinquad: easeInQuad variable Quadratic ease-in. **Signature:**```typescript easeInQuad: EasingFn ``` --- ## core.easeinquart: easeInQuart variable Quartic ease-in. **Signature:**```typescript easeInQuart: EasingFn ``` --- ## core.easeinsine: easeInSine variable Sine ease-in. **Signature:**```typescript easeInSine: EasingFn ``` --- ## core.easeoutback: easeOutBack variable Back ease-out (overshoots at end). **Signature:**```typescript easeOutBack: EasingFn ``` --- ## core.easeoutbounce: easeOutBounce variable Bounce ease-out. **Signature:**```typescript easeOutBounce: EasingFn ``` --- ## core.easeoutcubic: easeOutCubic variable Cubic ease-out. **Signature:**```typescript easeOutCubic: EasingFn ``` --- ## core.easeoutelastic: easeOutElastic variable Elastic ease-out. **Signature:**```typescript easeOutElastic: EasingFn ``` --- ## core.easeoutexpo: easeOutExpo variable Exponential ease-out. **Signature:**```typescript easeOutExpo: EasingFn ``` --- ## core.easeoutquad: easeOutQuad variable Quadratic ease-out. **Signature:**```typescript easeOutQuad: EasingFn ``` --- ## core.easeoutquart: easeOutQuart variable Quartic ease-out. **Signature:**```typescript easeOutQuart: EasingFn ``` --- ## core.easeoutsine: easeOutSine variable Sine ease-out. **Signature:**```typescript easeOutSine: EasingFn ``` --- ## core.easingfn: EasingFn type A function that maps a normalized time value `t` in \[0, 1\] to a progress value, typically also in \[0, 1\] but may overshoot for back/elastic easings. **Signature:**```typescript export type EasingFn = (t: number) => number; ``` --- ## core.effect: effect() function Executes the provided function `fn` whenever any of the signals in the `signals` array change. Returns a disposable object that can be used to stop the effect. **Signature:**```typescript effect: (fn: () => void, signals: Array, options?: ListenerOptions) => () => void ```## core.effect: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | () => void | The function to execute when the signals change. | | signals | Array<[AnySignal](#core-anysignal)> | An array of signals to watch for changes. | | options | [ListenerOptions](#core-listeneroptions) | _(Optional)_ | **Returns:** () => void A disposable object that can be used to stop the effect. --- ## core.effectof: effectOf() function Creates an effect that depends on other signals or literal values and updates when any of the dependencies change. **Signature:**```typescript effectOf: []>(...args: T) => (fn: (...args: ValueTypes) => void, options?: ListenerOptions) => () => void ```## core.effectof: Parameters | Parameter | Type | Description | | --- | --- | --- | | args | T | The array of signals or literal values that the effect depends on. | **Returns:** (fn: (...args: [ValueTypes](#core-valuetypes)<T>) => void, options?: [ListenerOptions](#core-listeneroptions)) => () => void A disposable object that can be used to stop the effect. --- ## core.elementposition._constructor_: ElementPosition.(constructor) Creates a new instance of `ElementPosition`. **Signature:**```typescript constructor( index: number, total: Signal); ```## core.elementposition._constructor_: Parameters | Parameter | Type | Description | | --- | --- | --- | | index | number | The index of the element. | | total | [Signal](#core-signal)<number> | The total number of elements in the collection. | --- ## core.elementposition.counter: ElementPosition.counter property The counter of the element starting from 1. **Signature:**```typescript readonly counter: number; ``` --- ## core.elementposition.dispose: ElementPosition.dispose() method Disposes the internal signal created by `isLast`. \*\*Note:\*\* With automatic signal disposal, this method is now a no-op when used within a disposal scope (e.g., inside a renderable). The signal created by `isLast` is automatically tracked and disposed when the scope ends. This method is kept for backward compatibility and for cases where ElementPosition is used outside a scope. **Signature:**```typescript dispose(): void; ``` **Returns:** void --- ## core.elementposition.index: ElementPosition.index property The index of the element. **Signature:**```typescript readonly index: number; ``` --- ## core.elementposition.iseven: ElementPosition.isEven property Checks if the counter of the element is even. **Signature:**```typescript readonly isEven: boolean; ``` --- ## core.elementposition.isfirst: ElementPosition.isFirst property Checks if the element is the first element in the collection. **Signature:**```typescript readonly isFirst: boolean; ``` --- ## core.elementposition.islast: ElementPosition.isLast property Checks if the element is the last element in the collection. **Signature:**```typescript get isLast(): Signal; ``` --- ## core.elementposition.isodd: ElementPosition.isOdd property Checks if the counter of the element is odd. **Signature:**```typescript readonly isOdd: boolean; ``` --- ## core.elementposition: ElementPosition class Represents the position of an element in a collection. **Signature:**```typescript export declare class ElementPosition ```## core.elementposition: Constructors | Constructor | Modifiers | Description | | --- | --- | --- | | [(constructor)(index, total)](#core-elementposition-constructor) | | Creates a new instance of `ElementPosition`. | ## core.elementposition: Properties | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [counter](#core-elementposition-counter) | `readonly` | number | The counter of the element starting from 1. | | [index](#core-elementposition-index) | `readonly` | number | The index of the element. | | [isEven](#core-elementposition-iseven) | `readonly` | boolean | Checks if the counter of the element is even. | | [isFirst](#core-elementposition-isfirst) | `readonly` | boolean | Checks if the element is the first element in the collection. | | [isLast](#core-elementposition-islast) | `readonly` | [Signal](#core-signal)<boolean> | Checks if the element is the last element in the collection. | | [isOdd](#core-elementposition-isodd) | `readonly` | boolean | Checks if the counter of the element is odd. | | [total](#core-elementposition-total) | `readonly` | [Signal](#core-signal)<number> | The total number of elements in the collection. | ## core.elementposition: Methods | Method | Modifiers | Description | | --- | --- | --- | | [dispose()](#core-elementposition-dispose) | | Disposes the internal signal created by `isLast`. \*\*Note:\*\* With automatic signal disposal, this method is now a no-op when used within a disposal scope (e.g., inside a renderable). The signal created by `isLast` is automatically tracked and disposed when the scope ends. This method is kept for backward compatibility and for cases where ElementPosition is used outside a scope. | --- ## core.elementposition.total: ElementPosition.total property The total number of elements in the collection. **Signature:**```typescript readonly total: Signal; ``` --- ## core.endinterpolate: endInterpolate() function A fake interpolate function that always returns the end value. **Signature:**```typescript endInterpolate: (_start: T, end: T) => T ```## core.endinterpolate: Parameters | Parameter | Type | Description | | --- | --- | --- | | \_start | T | | | end | T | | **Returns:** T --- ## core.getcurrentscope: getCurrentScope() function Get the current active scope. **Signature:**```typescript getCurrentScope: () => Scope | null ``` **Returns:** [Scope](#core-scope) \| null The current scope, or null if no scope is active --- ## core.getparentscope: getParentScope() function Get the parent scope of the current scope. Most users don't need this. Accessing parent scopes can lead to unexpected behavior. Only use this for debugging or advanced use cases. **Signature:**```typescript getParentScope: () => Scope | null ``` **Returns:** [Scope](#core-scope) \| null The parent scope or null if no parent exists --- ## core.getscopestack: getScopeStack() function Get the full scope stack. Useful for debugging scope hierarchy. Most users don't need this. Use getCurrentScope() instead. **Signature:**```typescript getScopeStack: () => readonly Scope[] ``` **Returns:** readonly [Scope](#core-scope)\[\] Read-only array of active scopes --- ## core.guessinterpolate: guessInterpolate() function Returns an interpolation function based on the type of the value. **Signature:**```typescript guessInterpolate: (value: T) => Interpolate ```## core.guessinterpolate: Parameters | Parameter | Type | Description | | --- | --- | --- | | value | T | The value to be interpolated. | **Returns:** [Interpolate](#core-interpolate)<T> An interpolation function that takes a start value, an end value, and a delta, and returns an interpolated value. --- ## core.hierarchicalcontext.makeref: HierarchicalContext.makeRef() method Creates a reference marker at the current position. Reference markers are used to preserve exact insertion points when conditionally rendering or swapping children. For example, when using `When(condition, () => Child())`, the reference marker ensures that `Child` is inserted at the correct position when the condition becomes true. **Signature:**```typescript makeRef(): this; ``` **Returns:** this A new context with a reference to the marker --- ## core.hierarchicalcontext: HierarchicalContext interface Extended interface for contexts that support hierarchical rendering with ordered children. Most rendering contexts (DOM, ThreeJS, Konva, PixiJS) have ordered children where position matters for rendering order, z-index, or scene graph traversal. This interface provides methods for creating reference markers that preserve exact insertion points when conditionally rendering or swapping children. Reference markers are invisible placeholders that mark positions in the children list. Implementation varies by context: - DOM: Comment nodes (``) - ThreeJS: Empty `Object3D` with `visible=false` - Konva: Invisible `Group` with `listening=false` - PixiJS: Empty `Container` with `renderable=false` **Signature:**```typescript export interface HierarchicalContext extends RenderContext ```**Extends:** [RenderContext](#core-rendercontext) ## core.hierarchicalcontext: Methods | Method | Description | | --- | --- | | [makeRef()](#core-hierarchicalcontext-makeref) | Creates a reference marker at the current position. Reference markers are used to preserve exact insertion points when conditionally rendering or swapping children. For example, when using `When(condition, () => Child())`, the reference marker ensures that `Child` is inserted at the correct position when the condition becomes true. | --- ## core.interpolate: Interpolate type Represents a function that interpolates between two values. **Signature:**```typescript export type Interpolate = (start: T, end: T, delta: number) => T; ``` --- ## core.interpolatedate: interpolateDate variable Interpolates between two dates based on a delta value. **Signature:**```typescript interpolateDate: Interpolate ``` --- ## core.interpolatenumber: interpolateNumber variable Interpolates a number between a start and end value based on a delta. **Signature:**```typescript interpolateNumber: Interpolate ``` --- ## core.interpolatestring: interpolateString variable Interpolates between two strings based on a delta value. **Signature:**```typescript interpolateString: Interpolate ``` --- ## core.joinsignals: joinSignals() function Joins a set of signals into a single signal that emits a record of the values. **Signature:**```typescript joinSignals: >>(values: T) => Signal<{ [K in keyof T]: T[K]; }> ```## core.joinsignals: Parameters | Parameter | Type | Description | | --- | --- | --- | | values | T | The set of signals to join as a record of `Value`s. | **Returns:** [Signal](#core-signal)<{ \[K in keyof T\]: T\[K\]; }> A signal that emits a record of the values. --- ## core.keyedposition._constructor_: KeyedPosition.(constructor) Creates a new instance of `KeyedPosition`. **Signature:**```typescript constructor(initialIndex: number, total: Signal); ```## core.keyedposition._constructor_: Parameters | Parameter | Type | Description | | --- | --- | --- | | initialIndex | number | The initial index of the element. | | total | [Signal](#core-signal)<number> | A reactive signal representing the total number of elements in the collection. | --- ## core.keyedposition.counter: KeyedPosition.counter property The 1-based counter (index + 1). **Signature:**```typescript get counter(): Signal; ``` --- ## core.keyedposition.dispose: KeyedPosition.dispose() method Disposes the internal signals created by this position. \*\*Note:\*\* With automatic signal disposal, this method is typically a no-op when used within a disposal scope (e.g., inside a renderable). Kept for backward compatibility and edge cases. **Signature:**```typescript dispose(): void; ``` **Returns:** void --- ## core.keyedposition.index: KeyedPosition.index property The reactive index of the element. Created lazily on first access. **Signature:**```typescript get index(): Signal; ``` --- ## core.keyedposition.iseven: KeyedPosition.isEven property Whether the counter is even. **Signature:**```typescript get isEven(): Signal; ``` --- ## core.keyedposition.isfirst: KeyedPosition.isFirst property Whether this is the first element in the collection. **Signature:**```typescript get isFirst(): Signal; ``` --- ## core.keyedposition.islast: KeyedPosition.isLast property Whether this is the last element in the collection. **Signature:**```typescript get isLast(): Signal; ``` --- ## core.keyedposition.isodd: KeyedPosition.isOdd property Whether the counter is odd. **Signature:**```typescript get isOdd(): Signal; ``` --- ## core.keyedposition: KeyedPosition class Represents the position of an element in a keyed collection. Unlike [ElementPosition](#core-elementposition) where `index` and derived fields are static, `KeyedPosition` makes \*\*all fields reactive\*\*. When an item moves to a new position (e.g., due to array reordering in `KeyedForEach`), all derived fields (`counter`, `isFirst`, `isEven`, `isOdd`, `isLast`) update automatically. The index signal and all derived fields are created lazily — only when first accessed — to avoid unnecessary signal overhead when they are not used. **Signature:**```typescript export declare class KeyedPosition ```## core.keyedposition: Constructors | Constructor | Modifiers | Description | | --- | --- | --- | | [(constructor)(initialIndex, total)](#core-keyedposition-constructor) | | Creates a new instance of `KeyedPosition`. | ## core.keyedposition: Properties | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [counter](#core-keyedposition-counter) | `readonly` | [Signal](#core-signal)<number> | The 1-based counter (index + 1). | | [index](#core-keyedposition-index) | `readonly` | [Signal](#core-signal)<number> | The reactive index of the element. Created lazily on first access. | | [isEven](#core-keyedposition-iseven) | `readonly` | [Signal](#core-signal)<boolean> | Whether the counter is even. | | [isFirst](#core-keyedposition-isfirst) | `readonly` | [Signal](#core-signal)<boolean> | Whether this is the first element in the collection. | | [isLast](#core-keyedposition-islast) | `readonly` | [Signal](#core-signal)<boolean> | Whether this is the last element in the collection. | | [isOdd](#core-keyedposition-isodd) | `readonly` | [Signal](#core-signal)<boolean> | Whether the counter is odd. | | [total](#core-keyedposition-total) | `readonly` | [Signal](#core-signal)<number> | The reactive total number of elements in the collection. | ## core.keyedposition: Methods | Method | Modifiers | Description | | --- | --- | --- | | [dispose()](#core-keyedposition-dispose) | | Disposes the internal signals created by this position. \*\*Note:\*\* With automatic signal disposal, this method is typically a no-op when used within a disposal scope (e.g., inside a renderable). Kept for backward compatibility and edge cases. | --- ## core.keyedposition.total: KeyedPosition.total property The reactive total number of elements in the collection. **Signature:**```typescript readonly total: Signal; ``` --- ## core.linear: linear variable Identity easing: `f(t) = t`. **Signature:**```typescript linear: EasingFn ``` --- ## core.listeneroptions: ListenerOptions type **Signature:**```typescript export type ListenerOptions = { skipInitial?: boolean; once?: boolean; abortSignal?: AbortSignal; noAutoDispose?: boolean; }; ``` --- ## core.localstorageprop: localStorageProp() function Creates a prop that is backed by the localStorage or a MemoryStore. **Signature:**```typescript localStorageProp: (options: StorageOptions) => Prop ```## core.localstorageprop: Parameters | Parameter | Type | Description | | --- | --- | --- | | options | StorageOptions<T> | The options for creating the prop. | **Returns:** [Prop](#core-prop)<T> The created prop. --- ## core.makeprovidermark: makeProviderMark() function Creates a unique symbol that can be used as a provider mark for a specific type `T`. **Signature:**```typescript makeProviderMark: (identifier: string) => ProviderMark ```## core.makeprovidermark: Parameters | Parameter | Type | Description | | --- | --- | --- | | identifier | string | A string that uniquely identifies the provider | **Returns:** [ProviderMark](#core-providermark)<T> A unique symbol that can be used as a provider mark ## core.makeprovidermark: Example```typescript interface UserService { getUser(id: string): Promise } const USER_SERVICE = makeProviderMark('UserService') ``` --- ## core package Core types and utilities for multi-context Tempo framework. This package provides the foundational types and utilities that are shared across all Tempo rendering contexts (DOM, ThreeJS, Konva, PixiJS, etc.). ## core: Classes | Class | Description | | --- | --- | | [Computed](#core-computed) | Represents a computed signal that derives its value from a function. It extends the `Signal` class. | | [DisposalScope](#core-disposalscope) | A DisposalScope tracks signals created during its lifetime and disposes them when the scope ends. This enables automatic signal disposal without manual OnDispose() calls. | | [ElementPosition](#core-elementposition) | Represents the position of an element in a collection. | | [KeyedPosition](#core-keyedposition) | Represents the position of an element in a keyed collection. Unlike [ElementPosition](#core-elementposition) where `index` and derived fields are static, `KeyedPosition` makes \*\*all fields reactive\*\*. When an item moves to a new position (e.g., due to array reordering in `KeyedForEach`), all derived fields (`counter`, `isFirst`, `isEven`, `isOdd`, `isLast`) update automatically. The index signal and all derived fields are created lazily — only when first accessed — to avoid unnecessary signal overhead when they are not used. | | [MemoryStore](#core-memorystore) | Represents a memory store that stores key-value pairs. | | [Prop](#core-prop) | Represents a property signal that holds a value of type T. It extends the `Signal` class. | | [Signal](#core-signal) | | ## core: Functions | Function | Description | | --- | --- | | [accumulateSignal(signal, reducer, initial, equals)](#core-accumulatesignal) | Creates a signal that accumulates values over time using a reducer function, similar to `Array.reduce` but reactive. Each time the source signal changes, the reducer is called with the current accumulator and the new value. | | [and(args)](#core-and) | Creates a computed signal that emits true if all input signals are true. | | [animateSignal(signal, options)](#core-animatesignal) | Animates a signal by creating a new signal that transitions from an initial value to the current value of the input signal. | | [animateSignals(initialValue, fn, dependencies, options)](#core-animatesignals) | Animates signals based on the provided options. | | [bind(fn)](#core-bind) | Binds a function or signal of a function to a set of signals and literals. | | [chainEasing(a, b)](#core-chaineasing) | Chains two easing functions: `a` is used for the first half of the animation, `b` for the second half. | | [coalesce(args)](#core-coalesce) | | | [computed(fn, dependencies, equals)](#core-computed) | Creates a computed signal that depends on other signals and updates when any of the dependencies change. | | [computedOf(args)](#core-computedof) | Creates a computed signal that depends on other signals or literal values and updates when any of the dependencies change. | | [computedOfAsync(args)](#core-computedofasync) | Creates a computed signal that depends on other signals or literal values and performs an asynchronous computation when any of the dependencies change. This is the async version of `computedOf`. It handles Promise-based computations by providing an alternative value while the async operation is pending and optional error recovery. | | [computedOfAsyncGenerator(args)](#core-computedofasyncgenerator) | Creates a computed signal that depends on other signals or literal values and performs an asynchronous generator computation when any of the dependencies change. This is the async generator version of `computedOf`. It handles streaming computations where multiple values are yielded over time. Each yield updates the signal. | | [computedRecord(record, fn)](#core-computedrecord) | Computes a value based on a record of signals and literals. | | [createRenderable(type, renderFn)](#core-createrenderable) | Creates a renderable object from a render function and type symbol. This is a helper function for creating renderables. Most users will use context-specific helpers like `domRenderable()` or `threeRenderable()` instead of calling this directly. | | [createSelector(source, equals)](#core-createselector) | Creates an O(1) selection primitive. Instead of creating a computed per item that ALL re-evaluate when the source changes, only the previously-selected and newly-selected items are notified. | | [delaySignal(signal, ms)](#core-delaysignal) | Delays the value of a signal by a specified amount of time. | | [distinctUntilChanged(signal, equals)](#core-distinctuntilchanged) | Creates a signal that only emits when the value changes according to the provided equality function. Useful downstream of `.map()` chains where a transformation may produce the same output for different inputs. | | [effect(fn, signals, options)](#core-effect) | Executes the provided function `fn` whenever any of the signals in the `signals` array change. Returns a disposable object that can be used to stop the effect. | | [effectOf(args)](#core-effectof) | Creates an effect that depends on other signals or literal values and updates when any of the dependencies change. | | [endInterpolate(\_start, end)](#core-endinterpolate) | A fake interpolate function that always returns the end value. | | [getCurrentScope()](#core-getcurrentscope) | Get the current active scope. | | [getParentScope()](#core-getparentscope) | Get the parent scope of the current scope. Most users don't need this. Accessing parent scopes can lead to unexpected behavior. Only use this for debugging or advanced use cases. | | [getScopeStack()](#core-getscopestack) | Get the full scope stack. Useful for debugging scope hierarchy. Most users don't need this. Use getCurrentScope() instead. | | [guessInterpolate(value)](#core-guessinterpolate) | Returns an interpolation function based on the type of the value. | | [joinSignals(values)](#core-joinsignals) | Joins a set of signals into a single signal that emits a record of the values. | | [localStorageProp(options)](#core-localstorageprop) | Creates a prop that is backed by the localStorage or a MemoryStore. | | [makeProviderMark(identifier)](#core-makeprovidermark) | Creates a unique symbol that can be used as a provider mark for a specific type `T`. | | [merge(options)](#core-merge) | Merges a record of signals and literals into a single signal. | | [mirrorEasing(fn)](#core-mirroreasing) | Mirrors an easing function: the first half uses `fn`, the second half plays it in reverse. Useful for creating symmetric in-out easings from a single ease-in. | | [not(arg)](#core-not) | Creates a signal or value that is the boolean negation of the input. If the input is a Signal, returns a mapped Signal. If it is a literal, returns the negated value. | | [notNil(arg)](#core-notnil) | Creates a signal or value that is `true` when the input is not `null` or `undefined`. If the input is a Signal, returns a mapped Signal. If it is a literal, returns the boolean result. | | [or(args)](#core-or) | Creates a computed signal that emits true if any input signal is true. | | [previousSignal(signal)](#core-previoussignal) | Creates a signal that emits the previous value of the input signal. | | [prop(value, equals)](#core-prop) | Creates a new Prop object with the specified value and equality function. | | [propHistory(prop, options)](#core-prophistory) | Creates a history controller for a Prop, enabling undo/redo navigation. | | [propHistory(signal, set, options)](#core-prophistory1) | Creates a history controller for a Signal with an external setter, enabling undo/redo navigation. | | [reverseEasing(fn)](#core-reverseeasing) | Reverses an easing function: the result plays the easing curve backwards. `reverseEasing(easeIn)` produces an ease-out curve. | | [scoped(fn)](#core-scoped) | Execute a function in a new scope and dispose the scope immediately after. Useful for one-off scoped operations. | | [sessionStorageProp(options)](#core-sessionstorageprop) | Creates a prop that stores its value in the session storage. | | [signal(value, equals)](#core-signal) | Creates a signal with the specified initial value and equality function. | | [slidingWindowSignal({ size, signal, }, input)](#core-slidingwindowsignal) | Creates a signal that emits a sliding window of values from the input signal. | | [storedProp({ key, defaultValue, store, serialize, deserialize, equals, onLoad, syncTabs, onKeyChange, }, input)](#core-storedprop) | Creates a stored property that persists its value in a storage mechanism. | | [strictEquals(a, b)](#core-strictequals) | Singleton strict equality function. Used as default `equals` for all signals to avoid allocating a new arrow function per signal instance. | | [syncProp(propToSync, { channel: channelName, serialize, deserialize, equals, }, input)](#core-syncprop) | Synchronizes a prop across browser tabs using BroadcastChannel. When the prop value changes in one tab, all other tabs with the same channel will be updated. | | [throttleSignal(signal, ms)](#core-throttlesignal) | Creates a signal that throttles the input signal, emitting at most once per interval. The first change is emitted immediately, then subsequent changes within the interval are batched — the most recent value is emitted when the interval expires. | | [untracked(fn)](#core-untracked) | Execute a function without any scope tracking. Signals created inside will NOT be automatically tracked. | | [withScope(scope, fn)](#core-withscope) | Execute a function within a scope context. The scope is pushed before the function executes and popped after. The scope is NOT disposed - the caller is responsible for disposal. | ## core: Interfaces | Interface | Description | | --- | --- | | [HierarchicalContext](#core-hierarchicalcontext) | Extended interface for contexts that support hierarchical rendering with ordered children. Most rendering contexts (DOM, ThreeJS, Konva, PixiJS) have ordered children where position matters for rendering order, z-index, or scene graph traversal. This interface provides methods for creating reference markers that preserve exact insertion points when conditionally rendering or swapping children. Reference markers are invisible placeholders that mark positions in the children list. Implementation varies by context: - DOM: Comment nodes (``) - ThreeJS: Empty `Object3D` with `visible=false` - Konva: Invisible `Group` with `listening=false` - PixiJS: Empty `Container` with `renderable=false` | | [Renderable](#core-renderable) | A renderable object that can be rendered into a specific context. Renderables are the fundamental building blocks of Tempo applications. They are objects with a `render()` method that receives a context and returns a cleanup function, and a `type` symbol for runtime type checking. | | [RenderContext](#core-rendercontext) | Base interface for all rendering contexts. A RenderContext provides the minimal interface needed for rendering content. Context-specific implementations (DOM, ThreeJS, Konva, etc.) extend this interface with their own methods. | | [Scope](#core-scope) | Minimal interface for scope tracking. Both DisposalScope and lightweight inline scopes implement this. Only `track` and `onDispose` are needed by the signal system. | ## core: Variables | Variable | Description | | --- | --- | | [easeInBack](#core-easeinback) | Back ease-in (overshoots at start, s = 1.70158). | | [easeInBounce](#core-easeinbounce) | Bounce ease-in. | | [easeInCubic](#core-easeincubic) | Cubic ease-in. | | [easeInElastic](#core-easeinelastic) | Elastic ease-in. | | [easeInExpo](#core-easeinexpo) | Exponential ease-in. | | [easeInOutBack](#core-easeinoutback) | Back ease-in-out (overshoots both ends). | | [easeInOutBounce](#core-easeinoutbounce) | Bounce ease-in-out. | | [easeInOutCubic](#core-easeinoutcubic) | Cubic ease-in-out. | | [easeInOutElastic](#core-easeinoutelastic) | Elastic ease-in-out. | | [easeInOutExpo](#core-easeinoutexpo) | Exponential ease-in-out. | | [easeInOutQuad](#core-easeinoutquad) | Quadratic ease-in-out. | | [easeInOutQuart](#core-easeinoutquart) | Quartic ease-in-out. | | [easeInOutSine](#core-easeinoutsine) | Sine ease-in-out. | | [easeInQuad](#core-easeinquad) | Quadratic ease-in. | | [easeInQuart](#core-easeinquart) | Quartic ease-in. | | [easeInSine](#core-easeinsine) | Sine ease-in. | | [easeOutBack](#core-easeoutback) | Back ease-out (overshoots at end). | | [easeOutBounce](#core-easeoutbounce) | Bounce ease-out. | | [easeOutCubic](#core-easeoutcubic) | Cubic ease-out. | | [easeOutElastic](#core-easeoutelastic) | Elastic ease-out. | | [easeOutExpo](#core-easeoutexpo) | Exponential ease-out. | | [easeOutQuad](#core-easeoutquad) | Quadratic ease-out. | | [easeOutQuart](#core-easeoutquart) | Quartic ease-out. | | [easeOutSine](#core-easeoutsine) | Sine ease-out. | | [interpolateDate](#core-interpolatedate) | Interpolates between two dates based on a delta value. | | [interpolateNumber](#core-interpolatenumber) | Interpolates a number between a start and end value based on a delta. | | [interpolateString](#core-interpolatestring) | Interpolates between two strings based on a delta value. | | [linear](#core-linear) | Identity easing: `f(t) = t`. | | [Value](#core-value) | | ## core: Type Aliases | Type Alias | Description | | --- | --- | | [AnySignal](#core-anysignal) | Represents any type of signal. It can be a Signal, Prop, or Computed. | | [AtGetter](#core-atgetter) | Represents a type that maps each property of `T` to a `Signal` of its corresponding type. | | [BaseValueType](#core-basevaluetype) | Gets the base value type of a given Value type. | | [Clear](#core-clear) | A function that clears a resource. Clear functions are returned by renderables and are called when the rendered content needs to be removed. The `removeTree` parameter indicates whether the entire tree should be removed (true) or just the event listeners and reactive subscriptions (false). | | [EasingFn](#core-easingfn) | A function that maps a normalized time value `t` in \[0, 1\] to a progress value, typically also in \[0, 1\] but may overshoot for back/elastic easings. | | [Interpolate](#core-interpolate) | Represents a function that interpolates between two values. | | [ListenerOptions](#core-listeneroptions) | | | [Nil](#core-nil) | Represents a value that can be null or undefined. | | [Primitive](#core-primitive) | Primitive types that can be rendered as text content. The rendering context coerces these to strings automatically. | | [PropHistory](#core-prophistory) | A history controller that provides undo/redo navigation for a signal. | | [PropHistoryOptions](#core-prophistoryoptions) | Options for configuring a PropHistory controller. | | [ProviderMark](#core-providermark) | Represents a provider mark for dependency injection. Provider marks are unique symbols used to identify providers in a dependency injection system. The type parameter ensures type safety when retrieving providers. | | [RemoveSignals](#core-removesignals) | Removes signals from a given object type and returns a new object type with only the non-signal properties. | | [StoredPropOptions](#core-storedpropoptions) | Represents the properties required for storing and retrieving a value of type `T`. | | [SyncPropOptions](#core-syncpropoptions) | Options for synchronizing a prop across browser tabs. | | [TNode](#core-tnode) | | | [Value](#core-value) | Represents a value that can either be a `Signal` or a generic type `T`. | | [Values](#core-values) | Wraps all non-`Value` types in the array in `Value`. | | [ValueType](#core-valuetype) | Gets the value type of a given Value type. If the type is a `Signal`, it returns the inferred value type. Otherwise, it returns the type itself. | | [ValueTypes](#core-valuetypes) | Gets the value types of a given array of Value types. | --- ## core.memorystore.getitem: MemoryStore.getItem property Retrieves the value associated with the specified key from the memory store. **Signature:**```typescript readonly getItem: (key: string) => string | null; ``` --- ## core.memorystore: MemoryStore class Represents a memory store that stores key-value pairs. **Signature:**```typescript export declare class MemoryStore ```## core.memorystore: Properties | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [getItem](#core-memorystore-getitem) | `readonly` | (key: string) => string \\| null | Retrieves the value associated with the specified key from the memory store. | | [setItem](#core-memorystore-setitem) | `readonly` | (key: string, value: string) => void | Sets the value associated with the specified key in the memory store. | --- ## core.memorystore.setitem: MemoryStore.setItem property Sets the value associated with the specified key in the memory store. **Signature:**```typescript readonly setItem: (key: string, value: string) => void; ``` --- ## core.merge: merge() function Merges a record of signals and literals into a single signal. **Signature:**```typescript merge: >>(options: T) => Signal<{ [K in keyof T]: ValueType; }> ```## core.merge: Parameters | Parameter | Type | Description | | --- | --- | --- | | options | T | The record containing signals and literals. | **Returns:** [Signal](#core-signal)<{ \[K in keyof T\]: [ValueType](#core-valuetype)<T\[K\]>; }> - The merged signal. --- ## core.mirroreasing: mirrorEasing() function Mirrors an easing function: the first half uses `fn`, the second half plays it in reverse. Useful for creating symmetric in-out easings from a single ease-in. **Signature:**```typescript mirrorEasing: (fn: EasingFn) => EasingFn ```## core.mirroreasing: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | [EasingFn](#core-easingfn) | The easing function to mirror. | **Returns:** [EasingFn](#core-easingfn) A new easing function. --- ## core.nil: Nil type Represents a value that can be null or undefined. **Signature:**```typescript export type Nil = null | undefined; ``` --- ## core.not: not() function Creates a signal or value that is the boolean negation of the input. If the input is a Signal, returns a mapped Signal. If it is a literal, returns the negated value. **Signature:**```typescript export declare function not(arg: Value): Value; ```## core.not: Parameters | Parameter | Type | Description | | --- | --- | --- | | arg | [Value](#core-value)<boolean> | A boolean value or signal. | **Returns:** [Value](#core-value)<boolean> The negated value or signal. --- ## core.notnil: notNil() function Creates a signal or value that is `true` when the input is not `null` or `undefined`. If the input is a Signal, returns a mapped Signal. If it is a literal, returns the boolean result. **Signature:**```typescript export declare function notNil(arg: Value): Value; ```## core.notnil: Parameters | Parameter | Type | Description | | --- | --- | --- | | arg | [Value](#core-value)<T> | A value or signal to check. | **Returns:** [Value](#core-value)<boolean> A value or signal that emits `true` when the input is not nil. --- ## core.or: or() function Creates a computed signal that emits true if any input signal is true. **Signature:**```typescript export declare function or(...args: Value[]): Computed; ```## core.or: Parameters | Parameter | Type | Description | | --- | --- | --- | | args | [Value](#core-value)<boolean>\[\] | The input signals. | **Returns:** [Computed](#core-computed)<boolean> - The computed signal. --- ## core.previoussignal: previousSignal() function Creates a signal that emits the previous value of the input signal. **Signature:**```typescript previousSignal: (signal: Signal) => Signal ```## core.previoussignal: Parameters | Parameter | Type | Description | | --- | --- | --- | | signal | [Signal](#core-signal)<T> | The input signal. | **Returns:** [Signal](#core-signal)<T \| undefined> - The signal that emits the previous value of the input signal. --- ## core.primitive: Primitive type Primitive types that can be rendered as text content. The rendering context coerces these to strings automatically. **Signature:**```typescript export type Primitive = string | number | boolean; ``` --- ## core.prop.atprop: Prop.atProp() method Returns a `Prop` that represents the value at the specified key of the current value. **Signature:**```typescript atProp(key: K): Prop; ```## core.prop.atprop: Parameters | Parameter | Type | Description | | --- | --- | --- | | key | K | The key of the value to access. | **Returns:** [Prop](#core-prop)<T\[K\]> A `Prop` that represents the value at the specified key. --- ## core.prop.is: Prop.is property Checks if a value is a Prop. **Signature:**```typescript static is: (value: T_1 | Prop | Signal | Computed) => value is Prop; ``` --- ## core.prop.iso: Prop.iso() method Creates an isomorphism for the Signal. An isomorphism is a pair of functions that convert values between two types, along with an equality function to compare values of the second type. **Signature:**```typescript iso(to: (value: T) => O, from: (value: O) => T, equals?: (a: O, b: O) => boolean): Prop; ```## core.prop.iso: Parameters | Parameter | Type | Description | | --- | --- | --- | | to | (value: T) => O | A function that converts values from type T to type O. | | from | (value: O) => T | A function that converts values from type O to type T. | | equals | (a: O, b: O) => boolean | _(Optional)_ An optional function that compares values of type O for equality. Defaults to a strict equality check (===). | **Returns:** [Prop](#core-prop)<O> A Prop object representing the isomorphism. --- ## core.prop: prop() function Creates a new Prop object with the specified value and equality function. **Signature:**```typescript prop: (value: T, equals?: (a: T, b: T) => boolean) => Prop ```## core.prop: Parameters | Parameter | Type | Description | | --- | --- | --- | | value | T | The initial value of the Prop. | | equals | (a: T, b: T) => boolean | _(Optional)_ The equality function used to compare values. Defaults to strict equality (===). | **Returns:** [Prop](#core-prop)<T> A new Prop object. --- ## core.prop.reducer: Prop.reducer() method Creates a reducer function that combines the provided reducer function and effects. **Signature:**```typescript reducer(fn: (acc: T, value: A) => T, ...effects: ReducerEffect[]): (action: A) => void; ```## core.prop.reducer: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | (acc: T, value: A) => T | The reducer function that takes the current state and an action, and returns the new state. | | effects | ReducerEffect<T, A>\[\] | An array of effects to be executed after the state is updated. | **Returns:** (action: A) => void A dispatch function that can be used to update the state and trigger the effects. --- ## core.prop.set: Prop.set() method Changes the value of the property and notifies its listeners. **Signature:**```typescript set(value: T): void; ```## core.prop.set: Parameters | Parameter | Type | Description | | --- | --- | --- | | value | T | The new value of the property. | **Returns:** void --- ## core.prop.update: Prop.update() method Updates the value of the signal by applying the provided function to the current value. **Signature:**```typescript update(fn: (value: T) => T): void; ```## core.prop.update: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | (value: T) => T | The function to apply to the current value. | **Returns:** void --- ## core.prop.value: Prop.value property Access for the current value of the property. **Signature:**```typescript get value(): T; set value(value: T); ``` --- ## core.prophistory_1: propHistory() function Creates a history controller for a Signal with an external setter, enabling undo/redo navigation. **Signature:**```typescript export declare function propHistory(signal: Signal, set: (value: T) => void, options?: PropHistoryOptions): PropHistory; ```## core.prophistory_1: Parameters | Parameter | Type | Description | | --- | --- | --- | | signal | [Signal](#core-signal)<T> | The Signal to track. | | set | (value: T) => void | The setter function. | | options | [PropHistoryOptions](#core-prophistoryoptions)<T> | _(Optional)_ Optional configuration. | **Returns:** [PropHistory](#core-prophistory)<T> A PropHistory controller. ## core.prophistory_1: Example```typescript const mySignal = signal(0) const mySet = (v: number) => mySignal._setAndNotify(v) const history = propHistory(mySignal, mySet) history.set(5) history.undo() // calls mySet(0) ``` --- ## core.prophistory: PropHistory type A history controller that provides undo/redo navigation for a signal. **Signature:**```typescript export type PropHistory = { readonly signal: Signal; readonly set: (value: T) => void; readonly undo: () => void; readonly redo: () => void; readonly go: (index: number) => void; readonly canUndo: Signal; readonly canRedo: Signal; readonly entries: Signal; readonly index: Signal; readonly clear: (resetValue?: T) => void; readonly pause: () => () => void; readonly transaction: (fn: () => void) => void; readonly dispose: () => void; }; ``` **References:** [Signal](#core-signal) --- ## core.prophistoryoptions: PropHistoryOptions type Options for configuring a PropHistory controller. **Signature:**```typescript export type PropHistoryOptions = { maxSize?: number; filter?: (value: T, previousValue: T) => boolean; }; ``` --- ## core.providermark: ProviderMark type Represents a provider mark for dependency injection. Provider marks are unique symbols used to identify providers in a dependency injection system. The type parameter ensures type safety when retrieving providers. **Signature:**```typescript export type ProviderMark = symbol & { readonly __type: T; }; ``` --- ## core.removesignals: RemoveSignals type Removes signals from a given object type and returns a new object type with only the non-signal properties. **Signature:**```typescript export type RemoveSignals>, K extends (string | number | symbol) & keyof T = keyof T> = { [k in K]: ValueType; }; ``` **References:** [ValueType](#core-valuetype) --- ## core.renderable: Renderable interface A renderable object that can be rendered into a specific context. Renderables are the fundamental building blocks of Tempo applications. They are objects with a `render()` method that receives a context and returns a cleanup function, and a `type` symbol for runtime type checking. **Signature:**```typescript export interface Renderable ``` ## core.renderable: Example ```typescript // DOM renderable const DOM_RENDERABLE_TYPE = Symbol('DOM_RENDERABLE') type DOMRenderable = Renderable const myComponent: DOMRenderable = { type: DOM_RENDERABLE_TYPE, render: (ctx: DOMContext) => { const divCtx = ctx.makeChildElement('div', undefined) divCtx.makeChildText('Hello, World!') return (removeTree) => { divCtx.clear(removeTree) } } } ```## core.renderable: Properties | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [type](#core-renderable-type) | `readonly` | TType | Symbol type for runtime type checking. This symbol is used to distinguish between renderables for different contexts at runtime. Each context defines its own unique symbol. | ## core.renderable: Methods | Method | Description | | --- | --- | | [render(ctx)](#core-renderable-render) | Renders the content into the provided context. | --- ## core.renderable.render: Renderable.render() method Renders the content into the provided context. **Signature:**```typescript render(ctx: CTX): Clear; ```## core.renderable.render: Parameters | Parameter | Type | Description | | --- | --- | --- | | ctx | CTX | The context to render into | **Returns:** [Clear](#core-clear) A cleanup function that removes the rendered content --- ## core.renderable.type: Renderable.type property Symbol type for runtime type checking. This symbol is used to distinguish between renderables for different contexts at runtime. Each context defines its own unique symbol. **Signature:**```typescript readonly type: TType; ``` --- ## core.rendercontext.clear: RenderContext.clear() method Clears the context and optionally removes the rendered tree. **Signature:**```typescript clear(removeTree: boolean): void; ```## core.rendercontext.clear: Parameters | Parameter | Type | Description | | --- | --- | --- | | removeTree | boolean | Whether to remove the entire rendered tree | **Returns:** void --- ## core.rendercontext: RenderContext interface Base interface for all rendering contexts. A RenderContext provides the minimal interface needed for rendering content. Context-specific implementations (DOM, ThreeJS, Konva, etc.) extend this interface with their own methods. **Signature:**```typescript export interface RenderContext ```## core.rendercontext: Methods | Method | Description | | --- | --- | | [clear(removeTree)](#core-rendercontext-clear) | Clears the context and optionally removes the rendered tree. | --- ## core.reverseeasing: reverseEasing() function Reverses an easing function: the result plays the easing curve backwards. `reverseEasing(easeIn)` produces an ease-out curve. **Signature:**```typescript reverseEasing: (fn: EasingFn) => EasingFn ```## core.reverseeasing: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | [EasingFn](#core-easingfn) | The easing function to reverse. | **Returns:** [EasingFn](#core-easingfn) A new easing function. --- ## core.scope: Scope interface Minimal interface for scope tracking. Both DisposalScope and lightweight inline scopes implement this. Only `track` and `onDispose` are needed by the signal system. **Signature:**```typescript export interface Scope ```## core.scope: Methods | Method | Description | | --- | --- | | [onDispose(callback)](#core-scope-ondispose) | | | [track(signal)](#core-scope-track) | | --- ## core.scope.ondispose: Scope.onDispose() method **Signature:**```typescript onDispose(callback: () => void): void; ```## core.scope.ondispose: Parameters | Parameter | Type | Description | | --- | --- | --- | | callback | () => void | | **Returns:** void --- ## core.scope.track: Scope.track() method **Signature:**```typescript track(signal: AnySignal): void; ```## core.scope.track: Parameters | Parameter | Type | Description | | --- | --- | --- | | signal | [AnySignal](#core-anysignal) | | **Returns:** void --- ## core.scoped: scoped() function Execute a function in a new scope and dispose the scope immediately after. Useful for one-off scoped operations. **Signature:**```typescript scoped: (fn: (scope: DisposalScope) => T) => T ```## core.scoped: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | (scope: [DisposalScope](#core-disposalscope)) => T | The function to execute, receives the scope as parameter | **Returns:** T The result of the function --- ## core.sessionstorageprop: sessionStorageProp() function Creates a prop that stores its value in the session storage. **Signature:**```typescript sessionStorageProp: (options: StorageOptions) => Prop ```## core.sessionstorageprop: Parameters | Parameter | Type | Description | | --- | --- | --- | | options | StorageOptions<T> | The options for the storage prop. | **Returns:** [Prop](#core-prop)<T> A prop that stores its value in the session storage. --- ## core.signal._: Signal.$ property Represents a collection of signals mapping to each key/field in the wrapped value. **Signature:**```typescript get $(): AtGetter; ``` --- ## core.signal._constructor_: Signal.(constructor) Represents a signal with a value of type T. **Signature:**```typescript constructor(value: T, equals?: (a: T, b: T) => boolean); ```## core.signal._constructor_: Parameters | Parameter | Type | Description | | --- | --- | --- | | value | T | The initial value of the signal. | | equals | (a: T, b: T) => boolean | _(Optional)_ A function that determines whether two values of type T are equal. | --- ## core.signal.at: Signal.at() method Returns a new Signal that emits the value at the specified key of the current value. **Signature:**```typescript at(key: K): Signal; ```## core.signal.at: Parameters | Parameter | Type | Description | | --- | --- | --- | | key | K | The key of the value to retrieve. | **Returns:** [Signal](#core-signal)<T\[K\]> A new Signal that emits the value at the specified key. --- ## core.signal.count: Signal.count() method Returns a signal that emits the count of values received so far. **Signature:**```typescript count(): Computed; ``` **Returns:** [Computed](#core-computed)<number> A signal that emits the count of values received so far. --- ## core.signal.derive: Signal.derive() method Derives a new signal from the current signal. Useful to create a new signal that emits the same values as the current signal but can be disposed independently. **Signature:**```typescript derive(): Computed; ``` **Returns:** [Computed](#core-computed)<T> A new signal that emits the same values as the current signal. --- ## core.signal.deriveprop: Signal.deriveProp() method Derives a new property from the current signal. **Signature:**```typescript deriveProp(input?: { autoDisposeProp?: boolean; equals?: (a: T, b: T) => boolean; }): Prop; ```## core.signal.deriveprop: Parameters | Parameter | Type | Description | | --- | --- | --- | | { autoDisposeProp, equals, } | (not declared) | _(Optional)_ | | input | { autoDisposeProp?: boolean; equals?: (a: T, b: T) => boolean; } | _(Optional)_ | **Returns:** [Prop](#core-prop)<T> The derived property. --- ## core.signal.dispose: Signal.dispose() method Disposes the signal, releasing any resources associated with it. This clears all listeners, derivatives, and disposal callbacks. **Signature:**```typescript dispose(): void; ``` **Returns:** void --- ## core.signal.feedprop: Signal.feedProp() method Feeds a property into the signal and sets up disposal behavior. **Signature:**```typescript feedProp(prop: Prop, autoDisposeProp?: boolean): Prop; ```## core.signal.feedprop: Parameters | Parameter | Type | Description | | --- | --- | --- | | prop | [Prop](#core-prop)<T> | The property to feed into the signal. | | autoDisposeProp | boolean | _(Optional)_ Determines whether the property should be automatically disposed when the signal is disposed. | **Returns:** [Prop](#core-prop)<T> The input property. --- ## core.signal.filter: Signal.filter() method **Signature:**```typescript filter(fn: (value: T) => boolean, startValue?: T): Computed; ```## core.signal.filter: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | (value: T) => boolean | | | startValue | T | _(Optional)_ | **Returns:** [Computed](#core-computed)<T> --- ## core.signal.filtermap: Signal.filterMap() method Returns a new Computed object that applies the provided mapping function to the value of this Signal, and filters out values that are `undefined` or `null`. **Signature:**```typescript filterMap(fn: (value: T) => O | undefined | null, startValue: O, equals?: (a: O, b: O) => boolean): Computed; ```## core.signal.filtermap: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | (value: T) => O \\| undefined \\| null | The mapping function to apply to the value of this Signal. | | startValue | O | The initial value for the Computed object. | | equals | (a: O, b: O) => boolean | _(Optional)_ Optional equality function to determine if two values are equal. | **Returns:** [Computed](#core-computed)<O> - A new Computed object with the mapped and filtered values. --- ## core.signal.flatmap: Signal.flatMap() method Returns a new Signal that applies the given function to the value of the current Signal, and then flattens the resulting Signal. **Signature:**```typescript flatMap(fn: (value: T) => Signal, equals?: (a: O, b: O) => boolean): Computed; ```## core.signal.flatmap: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | (value: T) => [Signal](#core-signal)<O> | The function to apply to the value of the current Signal. | | equals | (a: O, b: O) => boolean | _(Optional)_ A function that determines whether two values of type O are equal. Defaults to a strict equality check (===). | **Returns:** [Computed](#core-computed)<O> A new Signal that emits the values of the resulting Signal. --- ## core.signal.get: Signal.get() method Gets the current value of the signal. **Signature:**```typescript get(): T; ``` **Returns:** T The current value of the signal. --- ## core.signal.haslisteners: Signal.hasListeners() method Checks if the signal has any registered listeners. **Signature:**```typescript hasListeners(): boolean; ``` **Returns:** boolean `true` if the signal has listeners, `false` otherwise. --- ## core.signal.is: Signal.is property Checks if a value is a Signal. **Signature:**```typescript static readonly is: (value: O | Signal) => value is Signal; ``` --- ## core.signal.isdisposed: Signal.isDisposed() method Checks whether the signal is disposed. **Signature:**```typescript isDisposed(): boolean; ``` **Returns:** boolean True if the signal is disposed, false otherwise. --- ## core.signal.map: Signal.map() method Creates a new computed signal by applying a transformation function to this signal's value. The `map` method is one of the most commonly used signal operations. It creates a new computed signal that automatically updates whenever the source signal changes. The transformation function is called with the current value and should return the new value. **Signature:**```typescript map(fn: (value: T) => O, equals?: (a: O, b: O) => boolean): Computed; ```## core.signal.map: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | (value: T) => O | Function that transforms the signal's value to a new value | | equals | (a: O, b: O) => boolean | _(Optional)_ Optional function to determine if two transformed values are equal (defaults to strict equality) | **Returns:** [Computed](#core-computed)<O> A new computed signal with the transformed value (auto-registered with current scope) ## core.signal.map: Example 1```typescript const count = prop(5) // Transform to different types const doubled = count.map(n => n * 2) const message = count.map(n => `Count is ${n}`) const isEven = count.map(n => n % 2 === 0) // Use in UI html.div( html.div('Original: ', count.map(String)), html.div('Doubled: ', doubled.map(String)), html.div('Message: ', message), html.div('Is even: ', isEven.map(String)) ) ``` ## core.signal.map: Example 2 ```typescript // Chain multiple transformations const user = prop({ name: 'John', age: 30 }) const greeting = user .map(u => u.name) .map(name => name.toUpperCase()) .map(name => `Hello, ${name}!`) ``` ## core.signal.map: Example 3 ```typescript // With custom equality function for objects const items = prop([{ id: 1, name: 'Item 1' }]) const itemNames = items.map( items => items.map(item => item.name), (a, b) => JSON.stringify(a) === JSON.stringify(b) // deep equality ) ``` \*\*Auto-Disposal:\*\* The returned computed signal is automatically registered with the current disposal scope (if one exists). When used within a renderable or `WithScope()`, the signal will be automatically disposed when the component unmounts. No manual `OnDispose()` needed! ```typescript const MyComponent: Renderable = (ctx) => { const count = prop(0); const doubled = count.map(x => x * 2); // ✅ Auto-disposed return html.div(doubled); }; ``` --- ## core.signal.mapasync: Signal.mapAsync() method Maps the values emitted by the signal to a new value asynchronously using the provided function. If the function throws an error, it will be caught and logged. If a recovery function is provided, it will be called with the error and its return value will be used as the mapped value. If no recovery function is provided, the error will be logged as an unhandled promise rejection. **Signature:**```typescript mapAsync(fn: (value: T, options: { abortSignal: AbortSignal; }) => Promise, alt: O, recover?: (error: unknown) => O, equals?: (a: O, b: O) => boolean): Prop; ```## core.signal.mapasync: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | (value: T, options: { abortSignal: AbortSignal; }) => Promise<O> | The function to map the values emitted by the signal. The second argument to this function allows to cancel the previously running mapping function if it has not completed by the time a new value is emitted. | | alt | O | The alternate value to use if the signal is disposed or the mapping function throws an error. | | recover | (error: unknown) => O | _(Optional)_ The recovery function to handle errors thrown by the mapping function. | | equals | (a: O, b: O) => boolean | _(Optional)_ The equality function to compare the mapped values for equality. | **Returns:** [Prop](#core-prop)<O> A property that holds the mapped value and can be observed for changes. --- ## core.signal.mapasyncgenerator: Signal.mapAsyncGenerator() method Maps the values emitted by the signal to multiple values over time using an async generator. Each time the source signal changes, a new async generator is created and iterated. Previous generators are aborted when a new value arrives. This is useful for streaming data, such as: - AI/LLM streaming responses - Server-Sent Events (SSE) - Paginated data loading - WebSocket message streams **Signature:**```typescript mapAsyncGenerator(fn: (value: T, options: { abortSignal: AbortSignal; }) => AsyncGenerator, alt: O, recover?: (error: unknown) => O, equals?: (a: O, b: O) => boolean): Prop; ```## core.signal.mapasyncgenerator: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | (value: T, options: { abortSignal: AbortSignal; }) => AsyncGenerator<O, void, unknown> | The async generator function that yields values over time. The second argument provides an AbortSignal to cancel the generator when a new value arrives. | | alt | O | The initial value to use before the first yield. | | recover | (error: unknown) => O | _(Optional)_ Optional function to handle errors thrown by the generator. | | equals | (a: O, b: O) => boolean | _(Optional)_ Optional equality function to compare yielded values. | **Returns:** [Prop](#core-prop)<O> A property that updates each time the generator yields a value. ## core.signal.mapasyncgenerator: Example```typescript const query = prop('hello') // Stream AI response tokens const streamingResponse = query.mapAsyncGenerator( async function* (q, { abortSignal }) { const response = await fetch('/api/stream?q=' + q, { signal: abortSignal }) const reader = response.body!.getReader() let accumulated = '' while (true) { const { done, value } = await reader.read() if (done) break accumulated += new TextDecoder().decode(value) yield accumulated // Update signal with each chunk } }, '', // alt: empty string while loading (err) => 'Error: ' + err // recover ) ``` --- ## core.signal.mapmaybe: Signal.mapMaybe() method Maps the values of the signal using the provided function `fn`, and returns a new signal containing the mapped values. If the mapped value is `undefined` or `null`, it is replaced with the provided `alt` value. **Signature:**```typescript mapMaybe(fn: (value: T) => O | undefined | null, alt: O): Computed; ```## core.signal.mapmaybe: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | (value: T) => O \\| undefined \\| null | The function used to map the values of the signal. | | alt | O | The alternative value to use when the mapped value is `undefined` or `null`. | **Returns:** [Computed](#core-computed)<O> A new signal containing the mapped values. --- ## core.signal: signal() function Creates a signal with the specified initial value and equality function. **Signature:**```typescript signal: (value: T, equals?: (a: T, b: T) => boolean) => Signal ```## core.signal: Parameters | Parameter | Type | Description | | --- | --- | --- | | value | T | The initial value of the signal. | | equals | (a: T, b: T) => boolean | _(Optional)_ The equality function used to compare signal values. Defaults to a strict equality check (`===`). | **Returns:** [Signal](#core-signal)<T> A new Signal instance. --- ## core.signal.ofpromise: Signal.ofPromise property Creates a Signal that holds the result of a Promise, with proper error handling. This static method creates a signal that starts with an initial value and updates when the promise resolves. If the promise rejects, an optional recovery function can provide a fallback value. **Signature:**```typescript static readonly ofPromise: (promise: Promise, init: O, recover?: (error: unknown) => O, equals?: (a: O, b: O) => boolean) => Signal; ``` ## core.signal.ofpromise: Example 1 ```typescript // Basic usage with API call const userData = Signal.ofPromise( fetch('/api/user').then(r => r.json()), { loading: true }, // initial state error => ({ error: error.message, loading: false }) // error recovery ) // Use in UI Ensure(userData, (user) => html.div('Welcome, ', user.map(u => u.name)), () => html.div('Loading...') ) ``` ## core.signal.ofpromise: Example 2 ```typescript // With custom equality function const config = Signal.ofPromise( loadConfig(), {}, () => ({}), (a, b) => JSON.stringify(a) === JSON.stringify(b) // deep equality ) ``` --- ## core.signal.on: Signal.on() method Registers a listener function to be called whenever the value of the signal changes. The listener function will be immediately called with the current value of the signal. Returns a function that can be called to unregister the listener. When called within a DisposalScope (e.g., inside a renderable), the listener is automatically cleaned up when the scope is disposed. This prevents memory leaks when listening to outer-scope signals from inner scopes. **Signature:**```typescript on(listener: (value: T, previousValue: T | undefined) => void, options?: ListenerOptions): () => void; ```## core.signal.on: Parameters | Parameter | Type | Description | | --- | --- | --- | | listener | (value: T, previousValue: T \\| undefined) => void | The listener function to be called when the value of the signal changes. | | options | [ListenerOptions](#core-listeneroptions) | _(Optional)_ Options for the listener. | **Returns:** () => void ## core.signal.on: Example```typescript // Automatic cleanup when scope disposes const MyComponent = () => { const outerSignal = prop(0) return html.div( When(someCondition, () => { // This listener is automatically cleaned up when the When() disposes outerSignal.on(value => console.log(value)) return html.span('Inner content') }) ) } ``` --- ## core.signal.onchange: Signal.onChange() method Registers a listener function to be called whenever the value of the signal changes. The listener function will not be called with the current value of the signal. Returns a function that can be called to unregister the listener. **Signature:**```typescript onChange(listener: (value: T, previousValue: T) => void, options?: ListenerOptions): () => void; ```## core.signal.onchange: Parameters | Parameter | Type | Description | | --- | --- | --- | | listener | (value: T, previousValue: T) => void | The listener function to be called when the value of the signal changes. | | options | [ListenerOptions](#core-listeneroptions) | _(Optional)_ Options for the listener. | **Returns:** () => void --- ## core.signal.ondispose: Signal.onDispose() method Adds a listener function to be called when the object is disposed. **Signature:**```typescript onDispose(listener: () => void): void; ```## core.signal.ondispose: Parameters | Parameter | Type | Description | | --- | --- | --- | | listener | () => void | The listener function to be called when the object is disposed. | **Returns:** void A function that can be called to remove the listener. --- ## core.signal.setderivative: Signal.setDerivative() method Adds a computed value as a derivative of the signal. Uses structural parent↔child references instead of closure-based disposal wiring. Parent.dispose() directly disposes derivatives; Computed.dispose() removes itself from parents. **Signature:**```typescript setDerivative(computed: Computed): void; ```## core.signal.setderivative: Parameters | Parameter | Type | Description | | --- | --- | --- | | computed | [Computed](#core-computed)<O> | The computed value to add as a derivative. | **Returns:** void --- ## core.signal.tap: Signal.tap() method Invokes a callback function with the current value of the signal, without modifying the signal. **Signature:**```typescript tap(fn: (value: T) => void): Computed; ```## core.signal.tap: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | (value: T) => void | The callback function to be invoked with the current value of the signal. | **Returns:** [Computed](#core-computed)<T> A new signal that emits the same value as the original signal and invokes the callback function. --- ## core.signal.value: Signal.value property Gets the value of the signal. **Signature:**```typescript get value(): T; ``` --- ## core.slidingwindowsignal: slidingWindowSignal() function Creates a signal that emits a sliding window of values from the input signal. **Signature:**```typescript slidingWindowSignal: (input: { size: number | undefined; signal: Signal; }) => Computed ```## core.slidingwindowsignal: Parameters | Parameter | Type | Description | | --- | --- | --- | | { size, signal, } | (not declared) | | | input | { size: number \\| undefined; signal: [Signal](#core-signal)<T>; } | | **Returns:** [Computed](#core-computed)<T\[\]> - The signal that emits the sliding window of values. --- ## core.storedprop: storedProp() function Creates a stored property that persists its value in a storage mechanism. **Signature:**```typescript storedProp: (input: StoredPropOptions) => Prop ```## core.storedprop: Parameters | Parameter | Type | Description | | --- | --- | --- | | { key, defaultValue, store, serialize, deserialize, equals, onLoad, syncTabs, onKeyChange, } | (not declared) | | | input | [StoredPropOptions](#core-storedpropoptions)<T> | | **Returns:** [Prop](#core-prop)<T> - The created stored property. --- ## core.storedpropoptions: StoredPropOptions type Represents the properties required for storing and retrieving a value of type `T`. **Signature:**```typescript export type StoredPropOptions = { key: Value; defaultValue: T | (() => T); store: { getItem: (key: string) => string | null; setItem: (key: string, value: string) => void; }; serialize?: (v: T) => string; deserialize?: (v: string) => T; equals?: (a: T, b: T) => boolean; onLoad?: (value: T) => T; syncTabs?: boolean; onKeyChange?: 'load' | 'migrate' | 'keep'; }; ``` **References:** [Value](#core-value) --- ## core.strictequals: strictEquals() function Singleton strict equality function. Used as default `equals` for all signals to avoid allocating a new arrow function per signal instance. **Signature:**```typescript strictEquals: (a: T, b: T) => boolean ```## core.strictequals: Parameters | Parameter | Type | Description | | --- | --- | --- | | a | T | | | b | T | | **Returns:** boolean --- ## core.syncprop: syncProp() function Synchronizes a prop across browser tabs using BroadcastChannel. When the prop value changes in one tab, all other tabs with the same channel will be updated. **Signature:**```typescript syncProp: (propToSync: Prop, input: SyncPropOptions) => (() => void) ```## core.syncprop: Parameters | Parameter | Type | Description | | --- | --- | --- | | propToSync | [Prop](#core-prop)<T> | The prop to synchronize across tabs. | | { channel: channelName, serialize, deserialize, equals, } | (not declared) | | | input | [SyncPropOptions](#core-syncpropoptions)<T> | | **Returns:** (() => void) A disposal function to stop synchronization. ## core.syncprop: Example```ts const counter = prop(0) const dispose = syncProp(counter, { channel: 'my-counter' }) // Now when counter changes in this tab, it will update in all other tabs // and vice versa ``` --- ## core.syncpropoptions: SyncPropOptions type Options for synchronizing a prop across browser tabs. **Signature:**```typescript export type SyncPropOptions = { channel: string; serialize?: (v: T) => string; deserialize?: (v: string) => T; equals?: (a: T, b: T) => boolean; }; ``` --- ## core.throttlesignal: throttleSignal() function Creates a signal that throttles the input signal, emitting at most once per interval. The first change is emitted immediately, then subsequent changes within the interval are batched — the most recent value is emitted when the interval expires. **Signature:**```typescript throttleSignal: (signal: Signal, ms: number) => Signal ```## core.throttlesignal: Parameters | Parameter | Type | Description | | --- | --- | --- | | signal | [Signal](#core-signal)<T> | The input signal to throttle. | | ms | number | The minimum interval between emissions in milliseconds. | **Returns:** [Signal](#core-signal)<T> A new signal that emits throttled values. --- ## core.tnode: TNode type **Signature:**```typescript export type TNode = Renderable | Value | Value | Value | Signal | Signal | Signal | undefined | null | Renderable[]; ``` **References:** [RenderContext](#core-rendercontext), [Renderable](#core-renderable), [Signal](#core-signal), [Nil](#core-nil) --- ## core.untracked: untracked() function Execute a function without any scope tracking. Signals created inside will NOT be automatically tracked. **Signature:**```typescript untracked: (fn: () => T) => T ```## core.untracked: Parameters | Parameter | Type | Description | | --- | --- | --- | | fn | () => T | The function to execute | **Returns:** T The result of the function --- ## core.value: Value variable **Signature:**```typescript Value: { map: (value: Value, fn: (value: T) => U) => Value; toSignal: (value: Value, equals?: (a: T, b: T) => boolean) => Signal; maybeToSignal: (value: Value | undefined | null, equals?: (a: T, b: T) => boolean) => Signal | undefined; get: (value: Value) => T; on: (value: Value, listener: (value: T) => void) => (() => void); dispose: (value: Value) => void; disposeFn: (value: Value) => () => void; deriveProp: (value: Value, { autoDisposeProp, equals, }?: { autoDisposeProp?: boolean; equals?: (a: T, b: T) => boolean; }) => Prop; truthy: (value: Value) => Value; falsy: (value: Value) => Value; nil: (value: Value) => Value; defined: (value: Value) => Value; } ``` --- ## core.values: Values type Wraps all non-`Value` types in the array in `Value`. **Signature:**```typescript export type Values = { [K in keyof T]: T[K] extends Signal | Computed | Prop ? T[K] : Value; }; ``` **References:** [Signal](#core-signal), [Computed](#core-computed), [Prop](#core-prop) --- ## core.valuetype: ValueType type Gets the value type of a given Value type. If the type is a `Signal`, it returns the inferred value type. Otherwise, it returns the type itself. **Signature:**```typescript export type ValueType = T extends Computed ? V : T extends Prop ? V : T extends Signal ? V : T; ``` **References:** [Computed](#core-computed), [Prop](#core-prop), [Signal](#core-signal) --- ## core.valuetypes: ValueTypes type Gets the value types of a given array of Value types. **Signature:**```typescript export type ValueTypes[]> = { [K in keyof T]: ValueType; }; ``` **References:** [ValueType](#core-valuetype) --- ## core.withscope: withScope() function Execute a function within a scope context. The scope is pushed before the function executes and popped after. The scope is NOT disposed - the caller is responsible for disposal. **Signature:**```typescript withScope: (scope: Scope, fn: () => T) => T ```## core.withscope: Parameters | Parameter | Type | Description | | --- | --- | --- | | scope | [Scope](#core-scope) | The scope to use | | fn | () => T | The function to execute | **Returns:** T The result of the function --- # tempots-dom api index [Home](#tempots-ui-api-index) ## tempots-dom: API Reference ## tempots-dom: Packages | Package | Description | | --- | --- | | [@tempots/dom](#dom) | | --- ## dom._isfragment: \_isFragment() function **Signature:**```typescript _isFragment: (node: Node) => node is DocumentFragment ```## dom._isfragment: Parameters | Parameter | Type | Description | | --- | --- | --- | | node | Node | | **Returns:** node is DocumentFragment --- ## dom.aria: aria variable An object that provides a convenient way to create mountable attributes for ARIA properties. The type of the value is inferred from the attribute name. **Signature:**```typescript aria: { activedescendant: (value: SplitNValue) => Renderable; atomic: (value: SplitNValue) => Renderable; autocomplete: (value: SplitNValue<"none" | "inline" | "list" | "both">) => Renderable; braillelabel: (value: SplitNValue) => Renderable; brailleroledescription: (value: SplitNValue) => Renderable; busy: (value: SplitNValue) => Renderable; checked: (value: SplitNValue) => Renderable; colcount: (value: SplitNValue) => Renderable; colindex: (value: SplitNValue) => Renderable; colindextext: (value: SplitNValue) => Renderable; colspan: (value: SplitNValue) => Renderable; controls: (value: SplitNValue) => Renderable; current: (value: SplitNValue) => Renderable; describedby: (value: SplitNValue) => Renderable; description: (value: SplitNValue) => Renderable; details: (value: SplitNValue) => Renderable; disabled: (value: SplitNValue) => Renderable; dropeffect: (value: SplitNValue<"none" | "copy" | "execute" | "link" | "move" | "popup">) => Renderable; errormessage: (value: SplitNValue) => Renderable; expanded: (value: SplitNValue) => Renderable; flowto: (value: SplitNValue) => Renderable; grabbed: (value: SplitNValue) => Renderable; haspopup: (value: SplitNValue) => Renderable; hidden: (value: SplitNValue) => Renderable; invalid: (value: SplitNValue) => Renderable; keyshortcuts: (value: SplitNValue) => Renderable; label: (value: SplitNValue) => Renderable; labelledby: (value: SplitNValue) => Renderable; level: (value: SplitNValue) => Renderable; live: (value: SplitNValue<"off" | "assertive" | "polite">) => Renderable; modal: (value: SplitNValue) => Renderable; multiline: (value: SplitNValue) => Renderable; multiselectable: (value: SplitNValue) => Renderable; orientation: (value: SplitNValue<"undefined" | "horizontal" | "vertical">) => Renderable; owns: (value: SplitNValue) => Renderable; placeholder: (value: SplitNValue) => Renderable; posinset: (value: SplitNValue) => Renderable; pressed: (value: SplitNValue) => Renderable; readonly: (value: SplitNValue) => Renderable; relevant: (value: SplitNValue) => Renderable; required: (value: SplitNValue) => Renderable; roledescription: (value: SplitNValue) => Renderable; rowcount: (value: SplitNValue) => Renderable; rowindex: (value: SplitNValue) => Renderable; rowindextext: (value: SplitNValue) => Renderable; rowspan: (value: SplitNValue) => Renderable; selected: (value: SplitNValue) => Renderable; setsize: (value: SplitNValue) => Renderable; sort: (value: SplitNValue<"none" | "ascending" | "descending" | "other">) => Renderable; valuemax: (value: SplitNValue) => Renderable; valuemin: (value: SplitNValue) => Renderable; valuenow: (value: SplitNValue) => Renderable; valuetext: (value: SplitNValue) => Renderable; } ``` ## dom.aria: Example ```ts const button = html.button( aria.label('Click me!'), // maps to the `aria-label` attribute // maps to the `aria-pressed` attribute where pressed is a `Signal` aria.pressed(pressed) ) ``` --- ## dom.ariaattributes: AriaAttributes type Represents a collection of ARIA attributes and their corresponding types. **Signature:**```typescript export type AriaAttributes = { activedescendant: string; atomic: boolean; autocomplete: 'none' | 'inline' | 'list' | 'both'; braillelabel: string; brailleroledescription: string; busy: boolean; checked: boolean | 'mixed'; colcount: number; colindex: number; colindextext: string; colspan: number; controls: string; current: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false' | string; describedby: string; description: string; details: string; disabled: boolean; dropeffect: 'none' | 'copy' | 'execute' | 'link' | 'move' | 'popup'; errormessage: string; expanded: boolean | 'undefined'; flowto: string; grabbed: boolean; haspopup: boolean | 'false' | 'true' | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog'; hidden: boolean | 'undefined'; invalid: boolean | 'false' | 'true' | 'grammar' | 'spelling'; keyshortcuts: string; label: string; labelledby: string; level: number; live: 'off' | 'assertive' | 'polite'; modal: boolean; multiline: boolean; multiselectable: boolean; orientation: 'horizontal' | 'vertical' | 'undefined'; owns: string; placeholder: string; posinset: number; pressed: boolean | 'mixed' | 'undefined'; readonly: boolean; relevant: 'additions' | 'removals' | 'text' | 'all' | string; required: boolean; roledescription: string; rowcount: number; rowindex: number; rowindextext: string; rowspan: number; selected: boolean | 'undefined'; setsize: number; sort: 'none' | 'ascending' | 'descending' | 'other'; valuemax: number; valuemin: number; valuenow: number; valuetext: string; }; ``` --- ## dom.async: Async() function **Signature:**```typescript Async: (promise: Promise, options: import('@tempots/render').AsyncOptions | ((value: T) => import('@tempots/core').TNode)) => import('@tempots/core').Renderable ```## dom.async: Parameters | Parameter | Type | Description | | --- | --- | --- | | promise | Promise<T> | | | options | import('@tempots/render').AsyncOptions<T, [DOMContext](#dom-domcontext), typeof [DOM\_RENDERABLE\_TYPE](#dom-domrenderabletype)> \\| ((value: T) => import('@tempots/core').TNode<[DOMContext](#dom-domcontext), typeof [DOM\_RENDERABLE\_TYPE](#dom-domrenderabletype)>) | | **Returns:** import('@tempots/core').Renderable<[DOMContext](#dom-domcontext), typeof [DOM\_RENDERABLE\_TYPE](#dom-domrenderabletype)> --- ## dom.attr_2: Attr\_2() function Creates a renderable for an HTML attribute with the specified name and value. This is the functional equivalent of using `attr[name](value)` with a dynamic attribute name. The `class` attribute is special and can be used multiple times on the same element. Multiple class values will be merged together. **Signature:**```typescript Attr: (name: string, value: unknown) => Renderable ```## dom.attr_2: Parameters | Parameter | Type | Description | | --- | --- | --- | | name | string | The name of the attribute. | | value | unknown | The value of the attribute (can be a literal or Signal). | **Returns:** [Renderable](#dom-renderable) A renderable that sets the attribute. ## dom.attr_2: Example```ts const button = html.button( Attr('type', 'button'), Attr('disabled', disabledSignal), // Multiple class attributes Attr('class', 'btn btn-primary'), Attr('class', 'active'), // Both classes will be applied // ... ) ``` --- ## dom.attr: attr variable The `attr` object allows to create any HTML attribute. Either a literal value or `Signal` can be passed as a value. The type of the value is inferred from the attribute name. **Signature:**```typescript attr: { accept: (value: SplitNValue) => Renderable; 'accept-charset': (value: SplitNValue) => Renderable; accesskey: (value: SplitNValue) => Renderable; action: (value: SplitNValue) => Renderable; align: (value: SplitNValue) => Renderable; allow: (value: SplitNValue) => Renderable; allowfullscreen: (value: SplitNValue) => Renderable; allowpaymentrequest: (value: SplitNValue) => Renderable; alt: (value: SplitNValue) => Renderable; as: (value: SplitNValue) => Renderable; async: (value: SplitNValue) => Renderable; autocapitalize: (value: SplitNValue<"none" | "off" | "on" | "sentences" | "words" | "characters">) => Renderable; autocomplete: (value: SplitNValue) => Renderable; autofocus: (value: SplitNValue) => Renderable; autoplay: (value: SplitNValue) => Renderable; bgcolor: (value: SplitNValue) => Renderable; border: (value: SplitNValue) => Renderable; capture: (value: SplitNValue) => Renderable; charset: (value: SplitNValue) => Renderable; checked: (value: SplitNValue) => Renderable; cite: (value: SplitNValue) => Renderable; class: (value: SplitNValue) => Renderable; color: (value: SplitNValue) => Renderable; cols: (value: SplitNValue) => Renderable; colspan: (value: SplitNValue) => Renderable; content: (value: SplitNValue) => Renderable; contenteditable: (value: SplitNValue) => Renderable; controls: (value: SplitNValue) => Renderable; coords: (value: SplitNValue) => Renderable; crossorigin: (value: SplitNValue<"" | "anonymous" | "use-credentials">) => Renderable; data: (value: SplitNValue) => Renderable; datetime: (value: SplitNValue) => Renderable; decoding: (value: SplitNValue<"sync" | "async" | "auto">) => Renderable; default: (value: SplitNValue) => Renderable; defer: (value: SplitNValue) => Renderable; dir: (value: SplitNValue<"auto" | "ltr" | "rtl">) => Renderable; dirname: (value: SplitNValue) => Renderable; disabled: (value: SplitNValue) => Renderable; download: (value: SplitNValue) => Renderable; draggable: (value: SplitNValue) => Renderable; dropzone: (value: SplitNValue) => Renderable; enctype: (value: SplitNValue) => Renderable; enterkeyhint: (value: SplitNValue<"enter" | "done" | "go" | "next" | "previous" | "search" | "send">) => Renderable; for: (value: SplitNValue) => Renderable; form: (value: SplitNValue) => Renderable; formaction: (value: SplitNValue) => Renderable; formenctype: (value: SplitNValue) => Renderable; formmethod: (value: SplitNValue) => Renderable; formnovalidate: (value: SplitNValue) => Renderable; formtarget: (value: SplitNValue) => Renderable; headers: (value: SplitNValue) => Renderable; height: (value: SplitNValue) => Renderable; hidden: (value: SplitNValue) => Renderable; high: (value: SplitNValue) => Renderable; href: (value: SplitNValue) => Renderable; hreflang: (value: SplitNValue) => Renderable; 'http-equiv': (value: SplitNValue) => Renderable; icon: (value: SplitNValue) => Renderable; id: (value: SplitNValue) => Renderable; imagesizes: (value: SplitNValue) => Renderable; imagesrcset: (value: SplitNValue) => Renderable; inputmode: (value: SplitNValue<"none" | "text" | "search" | "decimal" | "numeric" | "tel" | "email" | "url">) => Renderable; integrity: (value: SplitNValue) => Renderable; is: (value: SplitNValue) => Renderable; ismap: (value: SplitNValue) => Renderable; itemid: (value: SplitNValue) => Renderable; itemprop: (value: SplitNValue) => Renderable; itemref: (value: SplitNValue) => Renderable; itemscope: (value: SplitNValue) => Renderable; itemtype: (value: SplitNValue) => Renderable; keytype: (value: SplitNValue) => Renderable; kind: (value: SplitNValue) => Renderable; label: (value: SplitNValue) => Renderable; lang: (value: SplitNValue) => Renderable; language: (value: SplitNValue) => Renderable; list: (value: SplitNValue) => Renderable; loading: (value: SplitNValue<"eager" | "lazy">) => Renderable; loop: (value: SplitNValue) => Renderable; low: (value: SplitNValue) => Renderable; manifest: (value: SplitNValue) => Renderable; max: (value: SplitNValue) => Renderable; maxlength: (value: SplitNValue) => Renderable; media: (value: SplitNValue) => Renderable; method: (value: SplitNValue) => Renderable; min: (value: SplitNValue) => Renderable; minlength: (value: SplitNValue) => Renderable; multiple: (value: SplitNValue) => Renderable; muted: (value: SplitNValue) => Renderable; name: (value: SplitNValue) => Renderable; nonce: (value: SplitNValue) => Renderable; novalidate: (value: SplitNValue) => Renderable; open: (value: SplitNValue) => Renderable; optimum: (value: SplitNValue) => Renderable; part: (value: SplitNValue) => Renderable; pattern: (value: SplitNValue) => Renderable; ping: (value: SplitNValue) => Renderable; placeholder: (value: SplitNValue) => Renderable; playsinline: (value: SplitNValue) => Renderable; popover: (value: SplitNValue<"" | "auto" | "manual">) => Renderable; popovertarget: (value: SplitNValue) => Renderable; popovertargetaction: (value: SplitNValue<"hide" | "show" | "toggle">) => Renderable; poster: (value: SplitNValue) => Renderable; preload: (value: SplitNValue) => Renderable; property: (value: SplitNValue) => Renderable; radiogroup: (value: SplitNValue) => Renderable; readonly: (value: SplitNValue) => Renderable; referrerpolicy: (value: SplitNValue<"no-referrer" | "no-referrer-when-downgrade" | "origin" | "origin-when-cross-origin" | "same-origin" | "strict-origin" | "strict-origin-when-cross-origin" | "unsafe-url">) => Renderable; rel: (value: SplitNValue) => Renderable; required: (value: SplitNValue) => Renderable; reversed: (value: SplitNValue) => Renderable; role: (value: SplitNValue) => Renderable; rows: (value: SplitNValue) => Renderable; rowspan: (value: SplitNValue) => Renderable; sandbox: (value: SplitNValue) => Renderable; scope: (value: SplitNValue) => Renderable; scoped: (value: SplitNValue) => Renderable; seamless: (value: SplitNValue) => Renderable; selected: (value: SplitNValue) => Renderable; shape: (value: SplitNValue) => Renderable; size: (value: SplitNValue) => Renderable; sizes: (value: SplitNValue) => Renderable; slot: (value: SplitNValue) => Renderable; span: (value: SplitNValue) => Renderable; spellcheck: (value: SplitNValue) => Renderable; src: (value: SplitNValue) => Renderable; srcdoc: (value: SplitNValue) => Renderable; srclang: (value: SplitNValue) => Renderable; srcset: (value: SplitNValue) => Renderable; start: (value: SplitNValue) => Renderable; step: (value: SplitNValue) => Renderable; style: (value: SplitNValue) => Renderable; tabindex: (value: SplitNValue) => Renderable; target: (value: SplitNValue) => Renderable; title: (value: SplitNValue) => Renderable; translate: (value: SplitNValue<"yes" | "no">) => Renderable; type: (value: SplitNValue) => Renderable; usemap: (value: SplitNValue) => Renderable; value: (value: SplitNValue) => Renderable; valueAsNumber: (value: SplitNValue) => Renderable; valueAsDate: (value: SplitNValue) => Renderable; width: (value: SplitNValue) => Renderable; wrap: (value: SplitNValue) => Renderable; textContent: (value: SplitNValue) => Renderable; innerText: (value: SplitNValue) => Renderable; innerHTML: (value: SplitNValue) => Renderable; outerHTML: (value: SplitNValue) => Renderable; } ``` ## dom.attr: Example ```ts const button = html.button( attr.type('button'), attr.disabled(disabled), // where disabled is a `Signal` // ... ) ``` --- ## dom.bindchecked: BindChecked() function Binds a `boolean` property to the checked value of an input element. The binding is two-way. **Signature:**```typescript BindChecked: (prop: Prop) => Renderable ```## dom.bindchecked: Parameters | Parameter | Type | Description | | --- | --- | --- | | prop | Prop<boolean> | The `boolean` property to bind. | **Returns:** [Renderable](#dom-renderable) A Renderable. --- ## dom.binddate: BindDate() function Binds a `Date` property to an input element. The binding is two-way. Changes to the input element will update the property but will only be affected by day changes and ignore time changes. **Signature:**```typescript BindDate: (prop: Prop, handler?: keyof HTMLEvents) => Renderable ```## dom.binddate: Parameters | Parameter | Type | Description | | --- | --- | --- | | prop | Prop<Date> | The `Date` property to bind. | | handler | keyof [HTMLEvents](#dom-htmlevents) | _(Optional)_ The event handler to use (default: 'input'). | **Returns:** [Renderable](#dom-renderable) A Renderable. --- ## dom.binddatetime: BindDateTime() function Binds a `Date` property to an input element. The binding is two-way. **Signature:**```typescript BindDateTime: (prop: Prop, handler?: keyof HTMLEvents) => Renderable ```## dom.binddatetime: Parameters | Parameter | Type | Description | | --- | --- | --- | | prop | Prop<Date> | The `Date` property to bind. | | handler | keyof [HTMLEvents](#dom-htmlevents) | _(Optional)_ The event handler to use (default: 'input'). | **Returns:** [Renderable](#dom-renderable) A Renderable. --- ## dom.bindnumber: BindNumber() function Binds a `number` property to an input element. The binding is two-way. **Signature:**```typescript BindNumber: (prop: Prop, handler?: keyof HTMLEvents) => Renderable ```## dom.bindnumber: Parameters | Parameter | Type | Description | | --- | --- | --- | | prop | Prop<number> | The `number` property to bind. | | handler | keyof [HTMLEvents](#dom-htmlevents) | _(Optional)_ The event handler to use (default: 'input'). | **Returns:** [Renderable](#dom-renderable) A Renderable. --- ## dom.bindtext: BindText() function Binds a `string` property to an input element. The binding is two-way. **Signature:**```typescript BindText: (prop: Prop, handler?: keyof HTMLEvents) => Renderable ```## dom.bindtext: Parameters | Parameter | Type | Description | | --- | --- | --- | | prop | Prop<string> | The `string` property to bind. | | handler | keyof [HTMLEvents](#dom-htmlevents) | _(Optional)_ The event handler to use (default: 'input'). | **Returns:** [Renderable](#dom-renderable) A Renderable. --- ## dom.browsercontext._constructor_: BrowserContext.(constructor) Constructs a new `DOMContext` instance. **Signature:**```typescript constructor( document: Document, element: HTMLElement, reference: Node | undefined, providers: Providers); ```## dom.browsercontext._constructor_: Parameters | Parameter | Type | Description | | --- | --- | --- | | document | Document | The `Document` instance associated with this context. | | element | HTMLElement | The `Element` instance associated with this context. | | reference | Node \\| undefined | An optional `Node` instance that serves as a reference for this context. | | providers | Providers | The `Providers` instance associated with this context. | --- ## dom.browsercontext.addclasses: BrowserContext.addClasses() method Adds classes to the element. **Signature:**```typescript addClasses(tokens: string[]): void; ```## dom.browsercontext.addclasses: Parameters | Parameter | Type | Description | | --- | --- | --- | | tokens | string\[\] | The class names to add. | **Returns:** void --- ## dom.browsercontext.appendorinsert: BrowserContext.appendOrInsert() method Appends or inserts a child node to the element, depending on whether a reference node is provided. **Signature:**```typescript appendOrInsert(child: Node): void; ```## dom.browsercontext.appendorinsert: Parameters | Parameter | Type | Description | | --- | --- | --- | | child | Node | The child node to append or insert. | **Returns:** void --- ## dom.browsercontext.clear: BrowserContext.clear() method **Signature:**```typescript clear(removeTree: boolean): void; ```## dom.browsercontext.clear: Parameters | Parameter | Type | Description | | --- | --- | --- | | removeTree | boolean | | **Returns:** void --- ## dom.browsercontext.createelement: BrowserContext.createElement() method Creates a new DOM element (eg: HTML or SVG) with the specified tag name and namespace. **Signature:**```typescript createElement(tagName: string, namespace: string | undefined): HTMLElement; ```## dom.browsercontext.createelement: Parameters | Parameter | Type | Description | | --- | --- | --- | | tagName | string | The tag name of the element to create. | | namespace | string \\| undefined | The namespace URI to create the element in, or `undefined` to create a standard HTML element. | **Returns:** HTMLElement The newly created element. --- ## dom.browsercontext.createtext: BrowserContext.createText() method Creates a new text node with the specified text content. **Signature:**```typescript createText(text: Primitive): Text; ```## dom.browsercontext.createtext: Parameters | Parameter | Type | Description | | --- | --- | --- | | text | Primitive | The text content for the new text node. | **Returns:** Text A new `Text` node with the specified text content. --- ## dom.browsercontext.detach: BrowserContext.detach() method **Signature:**```typescript detach(): void; ``` **Returns:** void --- ## dom.browsercontext.document: BrowserContext.document property The `Document` instance associated with this context. **Signature:**```typescript readonly document: Document; ``` --- ## dom.browsercontext.element: BrowserContext.element property The `Element` instance associated with this context. **Signature:**```typescript readonly element: HTMLElement; ``` --- ## dom.browsercontext.getclasses: BrowserContext.getClasses() method Gets the classes of the element. **Signature:**```typescript getClasses(): string[]; ``` **Returns:** string\[\] The classes of the element. --- ## dom.browsercontext.getprovider: BrowserContext.getProvider() method Retrieves a provider for the given provider mark. **Signature:**```typescript getProvider(mark: ProviderMark): { value: T; onUse: (() => void) | undefined; }; ```## dom.browsercontext.getprovider: Parameters | Parameter | Type | Description | | --- | --- | --- | | mark | ProviderMark<T> | The provider mark to retrieve the provider for. | **Returns:** { value: T; onUse: (() => void) \| undefined; } The provider for the given mark. ## dom.browsercontext.getprovider: Exceptions Throws `ProviderNotFoundError` if the provider for the given mark is not found. --- ## dom.browsercontext.getstyle: BrowserContext.getStyle() method Gets the style of the element. **Signature:**```typescript getStyle(name: string): string; ```## dom.browsercontext.getstyle: Parameters | Parameter | Type | Description | | --- | --- | --- | | name | string | The name of the style to get. | **Returns:** string The value of the style. --- ## dom.browsercontext.gettext: BrowserContext.getText() method Gets the text content of the current element or text node. **Signature:**```typescript getText(): string; ``` **Returns:** string The text content of the current element or text node. --- ## dom.browsercontext.getwindow: BrowserContext.getWindow() method **Signature:**```typescript getWindow(): Window & typeof globalThis; ``` **Returns:** Window & typeof globalThis --- ## dom.browsercontext.isbrowser: BrowserContext.isBrowser() method Returns `true` if the context is a browser context. **Signature:**```typescript isBrowser(): this is BrowserContext; ``` **Returns:** this is [BrowserContext](#dom-browsercontext) `true` if the context is a browser context. --- ## dom.browsercontext.isbrowserdom: BrowserContext.isBrowserDOM() method > Warning: This API is now obsolete. > > Use `isBrowser()` instead. > Returns `true` if the context is a browser DOM context. **Signature:**```typescript isBrowserDOM(): this is BrowserContext; ``` **Returns:** this is [BrowserContext](#dom-browsercontext) `true` if the context is a browser DOM context. --- ## dom.browsercontext.isheadless: BrowserContext.isHeadless() method Returns `true` if the context is a headless context. **Signature:**```typescript isHeadless(): this is HeadlessContext; ``` **Returns:** this is [HeadlessContext](#dom-headlesscontext) `true` if the context is a headless context. --- ## dom.browsercontext.isheadlessdom: BrowserContext.isHeadlessDOM() method Returns `true` if the context is a headless DOM context. **Signature:**```typescript isHeadlessDOM(): this is HeadlessContext; ``` **Returns:** this is [HeadlessContext](#dom-headlesscontext) `true` if the context is a headless DOM context. --- ## dom.browsercontext.makeaccessors: BrowserContext.makeAccessors() method **Signature:**```typescript makeAccessors(name: string): { get: () => any; set: (value: unknown) => void; }; ```## dom.browsercontext.makeaccessors: Parameters | Parameter | Type | Description | | --- | --- | --- | | name | string | | **Returns:** { get: () => any; set: (value: unknown) => void; } --- ## dom.browsercontext.makechildelement: BrowserContext.makeChildElement() method Creates a new child element and appends it to the current element, returning a new context. This method creates a new DOM element with the specified tag name and namespace, appends it to the current element, and returns a new DOMContext focused on the newly created child element. This is the primary method for building DOM trees. **Signature:**```typescript makeChildElement(tagName: string, namespace: string | undefined): DOMContext; ```## dom.browsercontext.makechildelement: Parameters | Parameter | Type | Description | | --- | --- | --- | | tagName | string | The tag name of the element to create (e.g., 'div', 'span', 'svg') | | namespace | string \\| undefined | The namespace URI for the element, or undefined for HTML elements | **Returns:** [DOMContext](#dom-domcontext) A new DOMContext focused on the newly created child element ## dom.browsercontext.makechildelement: Example```typescript // Create HTML elements const divCtx = ctx.makeChildElement('div', undefined) const spanCtx = divCtx.makeChildElement('span', undefined) // Create SVG elements const svgCtx = ctx.makeChildElement('svg', 'http://www.w3.org/2000/svg') const circleCtx = svgCtx.makeChildElement('circle', 'http://www.w3.org/2000/svg') ``` --- ## dom.browsercontext.makechildtext: BrowserContext.makeChildText() method Creates a new text node with the specified text content and appends it to the current element. **Signature:**```typescript makeChildText(text: Primitive): DOMContext; ```## dom.browsercontext.makechildtext: Parameters | Parameter | Type | Description | | --- | --- | --- | | text | Primitive | The text content for the new text node. Primitives are coerced to strings by the DOM. | **Returns:** [DOMContext](#dom-domcontext) A new `DOMContext` with a reference to the new text node. --- ## dom.browsercontext.makemarker: BrowserContext.makeMarker() method Creates a lightweight Comment marker node and appends/inserts it. Used as boundary references for keyed list items and conditional renderables. **Signature:**```typescript makeMarker(): DOMContext; ``` **Returns:** [DOMContext](#dom-domcontext) --- ## dom.browsercontext.makeportal: BrowserContext.makePortal() method Creates a portal to render content in a different part of the DOM tree. Portals allow you to render child components into a DOM node that exists outside the parent component's DOM hierarchy. This is useful for modals, tooltips, dropdowns, and other UI elements that need to break out of their container's styling or z-index context. **Signature:**```typescript makePortal(selector: string | HTMLElement): DOMContext; ```## dom.browsercontext.makeportal: Parameters | Parameter | Type | Description | | --- | --- | --- | | selector | string \\| HTMLElement | CSS selector string or HTMLElement reference for the portal target | **Returns:** [DOMContext](#dom-domcontext) A new DOMContext focused on the portal target element ## dom.browsercontext.makeportal: Exceptions {Error} When the selector doesn't match any element in the document ## dom.browsercontext.makeportal: Example 1```typescript // Portal to a modal container const modalCtx = ctx.makePortal('#modal-root') const modal = modalCtx.makeChildElement('div', undefined) // Add modal content modal.makeChildText('This renders in #modal-root') ``` ## dom.browsercontext.makeportal: Example 2 ```typescript // Portal to an existing element reference const tooltipContainer = document.getElementById('tooltip-container')! const tooltipCtx = ctx.makePortal(tooltipContainer) // Render tooltip content const tooltip = tooltipCtx.makeChildElement('div', undefined) tooltip.addClasses(['tooltip', 'tooltip-top']) ``` ## dom.browsercontext.makeportal: Example 3 ```typescript // Portal for dropdown menu const dropdownCtx = ctx.makePortal('body') // Render at body level const dropdown = dropdownCtx.makeChildElement('div', undefined) dropdown.addClasses(['dropdown-menu']) dropdown.setStyle('position', 'absolute') dropdown.setStyle('top', '100px') dropdown.setStyle('left', '50px') ``` --- ## dom.browsercontext.makeref: BrowserContext.makeRef() method Creates a new `DOMContext` with a reference to a newly created Comment node. The Comment node is appended or inserted to the current `DOMContext`. The new `DOMContext` with the reference is returned. **Signature:**```typescript makeRef(): DOMContext; ``` **Returns:** [DOMContext](#dom-domcontext) --- ## dom.browsercontext: BrowserContext class Browser implementation of DOMContext for real DOM manipulation in web browsers. BrowserContext provides a comprehensive API for creating, manipulating, and managing DOM elements in a browser environment. It handles element creation, text nodes, event listeners, styling, and provider management while maintaining immutability through context chaining. The context uses a reference system to track insertion points within sibling elements, allowing precise control over where new elements are inserted in the DOM tree. **Signature:**```typescript export declare class BrowserContext implements DOMContext ``` **Implements:** [DOMContext](#dom-domcontext) ## dom.browsercontext: Example 1 ```typescript // Create a context for the document body const ctx = BrowserContext.of(document.body, undefined, {}) // Create child elements const divCtx = ctx.makeChildElement('div', undefined) const textCtx = divCtx.makeChildText('Hello, World!') // Add event listeners divCtx.on('click', (event, ctx) => { console.log('Div clicked!', event.target) }) ``` ## dom.browsercontext: Example 2 ```typescript // Working with providers const themeProvider = makeProviderMark('theme') const ctxWithProvider = ctx.setProvider(themeProvider, 'dark', undefined) // Later retrieve the provider const { value: theme } = ctxWithProvider.getProvider(themeProvider) ``` ## dom.browsercontext: Example 3 ```typescript // Portal to different DOM location const modalCtx = ctx.makePortal('#modal-root') const modalContent = modalCtx.makeChildElement('div', undefined) ```## dom.browsercontext: Constructors | Constructor | Modifiers | Description | | --- | --- | --- | | [(constructor)(document, element, reference, providers)](#dom-browsercontext-constructor) | | Constructs a new `DOMContext` instance. | ## dom.browsercontext: Properties | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [document](#dom-browsercontext-document) | `readonly` | Document | The `Document` instance associated with this context. | | [element](#dom-browsercontext-element) | `readonly` | HTMLElement | The `Element` instance associated with this context. | | [providers](#dom-browsercontext-providers) | `readonly` | Providers | The `Providers` instance associated with this context. | | [reference](#dom-browsercontext-reference) | `readonly` | Node \\| undefined | An optional `Node` instance that serves as a reference for this context. | ## dom.browsercontext: Methods | Method | Modifiers | Description | | --- | --- | --- | | [addClasses(tokens)](#dom-browsercontext-addclasses) | | Adds classes to the element. | | [appendOrInsert(child)](#dom-browsercontext-appendorinsert) | | Appends or inserts a child node to the element, depending on whether a reference node is provided. | | [clear(removeTree)](#dom-browsercontext-clear) | | | | [createElement(tagName, namespace)](#dom-browsercontext-createelement) | | Creates a new DOM element (eg: HTML or SVG) with the specified tag name and namespace. | | [createText(text)](#dom-browsercontext-createtext) | | Creates a new text node with the specified text content. | | [detach()](#dom-browsercontext-detach) | | | | [getClasses()](#dom-browsercontext-getclasses) | | Gets the classes of the element. | | [getProvider(mark)](#dom-browsercontext-getprovider) | | Retrieves a provider for the given provider mark. | | [getStyle(name)](#dom-browsercontext-getstyle) | | Gets the style of the element. | | [getText()](#dom-browsercontext-gettext) | | Gets the text content of the current element or text node. | | [getWindow()](#dom-browsercontext-getwindow) | | | | [isBrowser()](#dom-browsercontext-isbrowser) | | Returns `true` if the context is a browser context. | | [isBrowserDOM()](#dom-browsercontext-isbrowserdom) | | Returns `true` if the context is a browser DOM context. | | [isHeadless()](#dom-browsercontext-isheadless) | | Returns `true` if the context is a headless context. | | [isHeadlessDOM()](#dom-browsercontext-isheadlessdom) | | Returns `true` if the context is a headless DOM context. | | [makeAccessors(name)](#dom-browsercontext-makeaccessors) | | | | [makeChildElement(tagName, namespace)](#dom-browsercontext-makechildelement) | | Creates a new child element and appends it to the current element, returning a new context. This method creates a new DOM element with the specified tag name and namespace, appends it to the current element, and returns a new DOMContext focused on the newly created child element. This is the primary method for building DOM trees. | | [makeChildText(text)](#dom-browsercontext-makechildtext) | | Creates a new text node with the specified text content and appends it to the current element. | | [makeMarker()](#dom-browsercontext-makemarker) | | Creates a lightweight Comment marker node and appends/inserts it. Used as boundary references for keyed list items and conditional renderables. | | [makePortal(selector)](#dom-browsercontext-makeportal) | | Creates a portal to render content in a different part of the DOM tree. Portals allow you to render child components into a DOM node that exists outside the parent component's DOM hierarchy. This is useful for modals, tooltips, dropdowns, and other UI elements that need to break out of their container's styling or z-index context. | | [makeRef()](#dom-browsercontext-makeref) | | Creates a new `DOMContext` with a reference to a newly created Comment node. The Comment node is appended or inserted to the current `DOMContext`. The new `DOMContext` with the reference is returned. | | [moveRangeBefore(startRef, endRef, targetRef)](#dom-browsercontext-moverangebefore) | | | | [of(element, ref, providers)](#dom-browsercontext-of) | `static` | Creates a new `DOMContext` instance for the given `Element` and optional reference `Node`. | | [on(event, listener, options)](#dom-browsercontext-on) | | Adds an event listener to the element. | | [reattach()](#dom-browsercontext-reattach) | | | | [removeAllBefore(ref)](#dom-browsercontext-removeallbefore) | | | | [removeClasses(tokens)](#dom-browsercontext-removeclasses) | | Removes classes from the element. | | [removeRange(startRef, endRef)](#dom-browsercontext-removerange) | | | | [setProvider(mark, value, onUse)](#dom-browsercontext-setprovider) | | Sets a provider for the given provider mark. | | [setStyle(name, value)](#dom-browsercontext-setstyle) | | Sets the style of the element. | | [setText(text)](#dom-browsercontext-settext) | | Sets the text content of the current element. | | [tryGetProvider(mark)](#dom-browsercontext-trygetprovider) | | | | [withElement(element)](#dom-browsercontext-withelement) | | Creates a new `DOMContext` instance with the provided `element`. | | [withReference(reference)](#dom-browsercontext-withreference) | | Creates a new `DOMContext` instance with the specified reference. | --- ## dom.browsercontext.moverangebefore: BrowserContext.moveRangeBefore() method **Signature:**```typescript moveRangeBefore(startRef: DOMContext, endRef: DOMContext, targetRef: DOMContext): void; ```## dom.browsercontext.moverangebefore: Parameters | Parameter | Type | Description | | --- | --- | --- | | startRef | [DOMContext](#dom-domcontext) | | | endRef | [DOMContext](#dom-domcontext) | | | targetRef | [DOMContext](#dom-domcontext) | | **Returns:** void --- ## dom.browsercontext.of: BrowserContext.of() method Creates a new `DOMContext` instance for the given `Element` and optional reference `Node`. **Signature:**```typescript static of(element: HTMLElement, ref: Node | undefined, providers: Providers): DOMContext; ```## dom.browsercontext.of: Parameters | Parameter | Type | Description | | --- | --- | --- | | element | HTMLElement | The `HTMLElement` to create the `DOMContext` for. | | ref | Node \\| undefined | A reference `Node` to associate with the `DOMContext` or undefined . | | providers | Providers | The providers to associate with the `DOMContext`. | **Returns:** [DOMContext](#dom-domcontext) A new `DOMContext` instance. --- ## dom.browsercontext.on: BrowserContext.on() method Adds an event listener to the element. **Signature:**```typescript on(event: string, listener: (event: E, ctx: BrowserContext) => void, options?: HandlerOptions): Clear; ```## dom.browsercontext.on: Parameters | Parameter | Type | Description | | --- | --- | --- | | event | string | The event to listen for. | | listener | (event: E, ctx: [BrowserContext](#dom-browsercontext)) => void | The listener to call when the event occurs. | | options | [HandlerOptions](#dom-handleroptions) | _(Optional)_ The options for the event listener. | **Returns:** Clear A function to remove the event listener. --- ## dom.browsercontext.providers: BrowserContext.providers property The `Providers` instance associated with this context. **Signature:**```typescript readonly providers: Providers; ``` --- ## dom.browsercontext.reattach: BrowserContext.reattach() method **Signature:**```typescript reattach(): void; ``` **Returns:** void --- ## dom.browsercontext.reference: BrowserContext.reference property An optional `Node` instance that serves as a reference for this context. **Signature:**```typescript readonly reference: Node | undefined; ``` --- ## dom.browsercontext.removeallbefore: BrowserContext.removeAllBefore() method **Signature:**```typescript removeAllBefore(ref: DOMContext): void; ```## dom.browsercontext.removeallbefore: Parameters | Parameter | Type | Description | | --- | --- | --- | | ref | [DOMContext](#dom-domcontext) | | **Returns:** void --- ## dom.browsercontext.removeclasses: BrowserContext.removeClasses() method Removes classes from the element. **Signature:**```typescript removeClasses(tokens: string[]): void; ```## dom.browsercontext.removeclasses: Parameters | Parameter | Type | Description | | --- | --- | --- | | tokens | string\[\] | The class names to remove. | **Returns:** void --- ## dom.browsercontext.removerange: BrowserContext.removeRange() method **Signature:**```typescript removeRange(startRef: DOMContext, endRef: DOMContext): void; ```## dom.browsercontext.removerange: Parameters | Parameter | Type | Description | | --- | --- | --- | | startRef | [DOMContext](#dom-domcontext) | | | endRef | [DOMContext](#dom-domcontext) | | **Returns:** void --- ## dom.browsercontext.setprovider: BrowserContext.setProvider() method Sets a provider for the given provider mark. **Signature:**```typescript setProvider(mark: ProviderMark, value: T, onUse: undefined | (() => void)): DOMContext; ```## dom.browsercontext.setprovider: Parameters | Parameter | Type | Description | | --- | --- | --- | | mark | ProviderMark<T> | The provider mark to set the provider for. | | value | T | The provider to set for the given mark. | | onUse | undefined \\| (() => void) | | **Returns:** [DOMContext](#dom-domcontext) A new `DOMContext` instance with the specified provider. --- ## dom.browsercontext.setstyle: BrowserContext.setStyle() method Sets the style of the element. **Signature:**```typescript setStyle(name: string, value: string): void; ```## dom.browsercontext.setstyle: Parameters | Parameter | Type | Description | | --- | --- | --- | | name | string | The name of the style to set. | | value | string | The value of the style to set. | **Returns:** void --- ## dom.browsercontext.settext: BrowserContext.setText() method Sets the text content of the current element. **Signature:**```typescript setText(text: Primitive): void; ```## dom.browsercontext.settext: Parameters | Parameter | Type | Description | | --- | --- | --- | | text | Primitive | The text content to set. Primitives are coerced to strings by the DOM. | **Returns:** void --- ## dom.browsercontext.trygetprovider: BrowserContext.tryGetProvider() method **Signature:**```typescript tryGetProvider(mark: ProviderMark): { value: T; onUse: (() => void) | undefined; } | undefined; ```## dom.browsercontext.trygetprovider: Parameters | Parameter | Type | Description | | --- | --- | --- | | mark | ProviderMark<T> | | **Returns:** { value: T; onUse: (() => void) \| undefined; } \| undefined --- ## dom.browsercontext.withelement: BrowserContext.withElement() method Creates a new `DOMContext` instance with the provided `element`. **Signature:**```typescript withElement(element: HTMLElement): BrowserContext; ```## dom.browsercontext.withelement: Parameters | Parameter | Type | Description | | --- | --- | --- | | element | HTMLElement | The DOM element to use in the new `DOMContext` instance. | **Returns:** [BrowserContext](#dom-browsercontext) A new `DOMContext` instance with the provided `element`. --- ## dom.browsercontext.withreference: BrowserContext.withReference() method Creates a new `DOMContext` instance with the specified reference. **Signature:**```typescript withReference(reference: Node | undefined): DOMContext; ```## dom.browsercontext.withreference: Parameters | Parameter | Type | Description | | --- | --- | --- | | reference | Node \\| undefined | The optional `Node` to use as the reference for the new `DOMContext`. | **Returns:** [DOMContext](#dom-domcontext) A new `DOMContext` instance with the specified reference. --- ## dom.catch: Catch() function **Signature:**```typescript Catch: (children: import('@tempots/core').TNode, fallback: (error: import('@tempots/core').Signal, retry: () => void) => import('@tempots/core').TNode) => import('@tempots/core').Renderable ```## dom.catch: Parameters | Parameter | Type | Description | | --- | --- | --- | | children | import('@tempots/core').TNode<[DOMContext](#dom-domcontext), typeof [DOM\_RENDERABLE\_TYPE](#dom-domrenderabletype)> | | | fallback | (error: import('@tempots/core').Signal<Error>, retry: () => void) => import('@tempots/core').TNode<[DOMContext](#dom-domcontext), typeof [DOM\_RENDERABLE\_TYPE](#dom-domrenderabletype)> | | **Returns:** import('@tempots/core').Renderable<[DOMContext](#dom-domcontext), typeof [DOM\_RENDERABLE\_TYPE](#dom-domrenderabletype)> --- ## dom.class_placeholder_attr: CLASS\_PLACEHOLDER\_ATTR variable **Signature:**```typescript CLASS_PLACEHOLDER_ATTR = "data-tts-class" ``` --- ## dom.conjunction: Conjunction() function **Signature:**```typescript Conjunction: (separator: () => import('@tempots/core').TNode, options?: import('@tempots/render').ConjunctionOptions | undefined) => (pos: import('@tempots/core').Signal) => import('@tempots/core').Renderable ```## dom.conjunction: Parameters | Parameter | Type | Description | | --- | --- | --- | | separator | () => import('@tempots/core').TNode<[DOMContext](#dom-domcontext), typeof [DOM\_RENDERABLE\_TYPE](#dom-domrenderabletype)> | | | options | import('@tempots/render').ConjunctionOptions<[DOMContext](#dom-domcontext), typeof [DOM\_RENDERABLE\_TYPE](#dom-domrenderabletype)> \\| undefined | _(Optional)_ | **Returns:** (pos: import('@tempots/core').Signal<import('@tempots/core').ElementPosition>) => import('@tempots/core').Renderable<[DOMContext](#dom-domcontext), typeof [DOM\_RENDERABLE\_TYPE](#dom-domrenderabletype)> --- ## dom.createinertiahandler: createInertiaHandler() function Creates an inertia handler for pan/scroll interactions. During a drag, call `track(x, y)` on each pointer move to build up velocity. On release, call `release()` to start an exponential decay animation. **Signature:**```typescript export declare function createInertiaHandler(onDelta: (dx: number, dy: number) => void, config?: InertiaConfig): InertiaHandler; ```## dom.createinertiahandler: Parameters | Parameter | Type | Description | | --- | --- | --- | | onDelta | (dx: number, dy: number) => void | Called with `(dx, dy)` deltas each frame during the decay. | | config | [InertiaConfig](#dom-inertiaconfig) | _(Optional)_ Optional friction and threshold settings. | **Returns:** [InertiaHandler](#dom-inertiahandler) An inertia handler. --- ## dom.createpinchzoomhandler: createPinchZoomHandler() function Creates event handlers for two-finger pinch-to-zoom with simultaneous pan. **Signature:**```typescript export declare function createPinchZoomHandler(state: Prop, getContainerRect: () => DOMRect, config?: PinchZoomConfig): PinchZoomHandler; ```## dom.createpinchzoomhandler: Parameters | Parameter | Type | Description | | --- | --- | --- | | state | Prop<[PinchZoomState](#dom-pinchzoomstate)> | A `Prop` holding the current zoom/pan state. Updated on each touch move. | | getContainerRect | () => DOMRect | Returns the bounding rect of the zoomable container. | | config | [PinchZoomConfig](#dom-pinchzoomconfig) | _(Optional)_ Optional scale limits. | **Returns:** [PinchZoomHandler](#dom-pinchzoomhandler) Touch event handlers to attach to the target element. --- ## dom.createrafloop: createRafLoop() function Creates a `requestAnimationFrame` loop that calls `callback` on every frame with the elapsed time in milliseconds since the previous frame. The first frame always receives `dt = 0`. **Signature:**```typescript export declare function createRafLoop(callback: (dt: number) => void): RafLoopHandle; ```## dom.createrafloop: Parameters | Parameter | Type | Description | | --- | --- | --- | | callback | (dt: number) => void | Called each frame with delta time in milliseconds. | **Returns:** [RafLoopHandle](#dom-rafloophandle) A handle with a `dispose()` method to stop the loop. --- ## dom.createreducedmotionsignal: createReducedMotionSignal() function Creates a reactive signal that tracks the user's `prefers-reduced-motion` system preference. The signal updates automatically when the preference changes. SSR-safe: returns a signal with `false` when `window` is unavailable. **Signature:**```typescript export declare function createReducedMotionSignal(): Signal; ``` **Returns:** Signal<boolean> A signal that is `true` when reduced motion is preferred. --- ## dom.createtween: createTween() function Creates an imperative tween that drives a reactive signal from its current value to a target using an easing function over a fixed duration. Complements `animateSignal` (declarative) with explicit `tweenTo()` control. **Signature:**```typescript export declare function createTween(initial: T, config?: TweenConfig): TweenHandle; ```## dom.createtween: Parameters | Parameter | Type | Description | | --- | --- | --- | | initial | T | The initial value. | | config | [TweenConfig](#dom-tweenconfig)<T> | _(Optional)_ Optional tween configuration. | **Returns:** [TweenHandle](#dom-tweenhandle)<T> A tween handle. --- ## dom.cssstyles: CSSStyles type Represents a subset of CSS styles. It is a type that excludes certain properties from the `CSSStyleDeclaration` type. **Signature:**```typescript export type CSSStyles = Omit; ``` **References:** [ExcludeFromStyle](#dom-excludefromstyle) --- ## dom.dataattr: DataAttr() function Creates a renderable for a data attribute with the specified name and value. This is an alias for `dataAttr(name, value)` that accepts `unknown` values. **Signature:**```typescript DataAttr: (name: string, value: Value) => Renderable ```## dom.dataattr: Parameters | Parameter | Type | Description | | --- | --- | --- | | name | string | The name of the data attribute (without the 'data-' prefix). | | value | Value<string> | The value of the attribute (can be a literal or Signal). | **Returns:** [Renderable](#dom-renderable) A renderable that sets the data attribute. ## dom.dataattr: Example```ts const button = html.button( DataAttr('myinfo', 'something'), // maps to the `data-myinfo` attribute ) ``` --- ## dom.delegate: delegate variable Provides type-safe delegated event handlers for all HTML events. The `delegate` object is a proxy that creates event handlers attached to the \*\*container element\*\* rather than individual children. Events are matched against a CSS selector using `Element.closest()`, making this ideal for lists and other containers with many similar children. Unlike `on`, which attaches one listener per element, `delegate` attaches a single listener on the container regardless of how many children match. **Signature:**```typescript delegate: { abort: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; afterprint: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; animationcancel: (selector: string, handler: (event: AnimationEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; animationend: (selector: string, handler: (event: AnimationEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; animationiteration: (selector: string, handler: (event: AnimationEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; animationstart: (selector: string, handler: (event: AnimationEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; auxclick: (selector: string, handler: (event: MouseEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; beforeinput: (selector: string, handler: (event: InputEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; beforeprint: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; beforeunload: (selector: string, handler: (event: BeforeUnloadEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; blur: (selector: string, handler: (event: FocusEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; cancel: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; canplay: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; canplaythrough: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; change: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; click: (selector: string, handler: (event: MouseEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; close: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; compositionend: (selector: string, handler: (event: CompositionEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; compositionstart: (selector: string, handler: (event: CompositionEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; compositionupdate: (selector: string, handler: (event: CompositionEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; contextmenu: (selector: string, handler: (event: MouseEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; copy: (selector: string, handler: (event: ClipboardEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; cuechange: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; cut: (selector: string, handler: (event: ClipboardEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; dblclick: (selector: string, handler: (event: MouseEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; drag: (selector: string, handler: (event: DragEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; dragend: (selector: string, handler: (event: DragEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; dragenter: (selector: string, handler: (event: DragEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; dragexit: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; dragleave: (selector: string, handler: (event: DragEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; dragover: (selector: string, handler: (event: DragEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; dragstart: (selector: string, handler: (event: DragEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; drop: (selector: string, handler: (event: DragEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; durationchange: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; emptied: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; ended: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; error: (selector: string, handler: (event: ErrorEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; focus: (selector: string, handler: (event: FocusEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; focusin: (selector: string, handler: (event: FocusEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; focusout: (selector: string, handler: (event: FocusEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; formdata: (selector: string, handler: (event: FormDataEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; fullscreenchange: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; fullscreenerror: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; gotpointercapture: (selector: string, handler: (event: PointerEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; hashchange: (selector: string, handler: (event: HashChangeEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; input: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; invalid: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; keydown: (selector: string, handler: (event: KeyboardEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; keypress: (selector: string, handler: (event: KeyboardEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; keyup: (selector: string, handler: (event: KeyboardEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; languagechange: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; load: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; loadeddata: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; loadedmetadata: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; loadend: (selector: string, handler: (event: ProgressEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; loadstart: (selector: string, handler: (event: ProgressEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; lostpointercapture: (selector: string, handler: (event: PointerEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; message: (selector: string, handler: (event: MessageEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; messageerror: (selector: string, handler: (event: MessageEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; mousedown: (selector: string, handler: (event: MouseEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; mouseenter: (selector: string, handler: (event: MouseEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; mouseleave: (selector: string, handler: (event: MouseEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; mousemove: (selector: string, handler: (event: MouseEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; mouseout: (selector: string, handler: (event: MouseEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; mouseover: (selector: string, handler: (event: MouseEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; mouseup: (selector: string, handler: (event: MouseEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; offline: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; online: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; orientationchange: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; pagehide: (selector: string, handler: (event: PageTransitionEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; pageshow: (selector: string, handler: (event: PageTransitionEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; paste: (selector: string, handler: (event: ClipboardEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; pause: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; play: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; playing: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; pointercancel: (selector: string, handler: (event: PointerEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; pointerdown: (selector: string, handler: (event: PointerEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; pointerenter: (selector: string, handler: (event: PointerEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; pointerleave: (selector: string, handler: (event: PointerEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; pointerlockchange: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; pointerlockerror: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; pointermove: (selector: string, handler: (event: PointerEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; pointerout: (selector: string, handler: (event: PointerEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; pointerover: (selector: string, handler: (event: PointerEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; pointerrawupdate: (selector: string, handler: (event: PointerEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; pointerup: (selector: string, handler: (event: PointerEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; popstate: (selector: string, handler: (event: PopStateEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; progress: (selector: string, handler: (event: ProgressEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; ratechange: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; readystatechange: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; rejectionhandled: (selector: string, handler: (event: PromiseRejectionEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; reset: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; resize: (selector: string, handler: (event: UIEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; scroll: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; scrollend: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; securitypolicyviolation: (selector: string, handler: (event: SecurityPolicyViolationEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; seeked: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; seeking: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; select: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; selectionchange: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; selectstart: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; slotchange: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; stalled: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; storage: (selector: string, handler: (event: StorageEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; submit: (selector: string, handler: (event: SubmitEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; suspend: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; timeupdate: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; toggle: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; touchcancel: (selector: string, handler: (event: TouchEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; touchend: (selector: string, handler: (event: TouchEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; touchmove: (selector: string, handler: (event: TouchEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; touchstart: (selector: string, handler: (event: TouchEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; transitioncancel: (selector: string, handler: (event: TransitionEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; transitionend: (selector: string, handler: (event: TransitionEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; transitionrun: (selector: string, handler: (event: TransitionEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; transitionstart: (selector: string, handler: (event: TransitionEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; unhandledrejection: (selector: string, handler: (event: PromiseRejectionEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; unload: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; visibilitychange: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; volumechange: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; waiting: (selector: string, handler: (event: Event, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; wheel: (selector: string, handler: (event: WheelEvent, ctx: DOMContext) => void, options?: HandlerOptions) => Renderable; } ``` ## dom.delegate: Remarks Delegated events rely on event bubbling. Events that do not bubble (such as `focus`, `blur`, `mouseenter`, `mouseleave`) will not be captured by delegation. Use the regular `on` handler for those events. ## dom.delegate: Example 1 ```typescript // Delegated click on list items — one listener on the