UNPKG

rvx

Version:

A signal based rendering library

1,595 lines (1,576 loc) 46.1 kB
/*! This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ /** * A context for implicitly passing values along the call stack. * * If you need a global default value, use {@link DefaultContext} instead. */ declare class Context<T> { #private; /** * @param defaultValue The default value. This is used if the {@link current} value is `null` or `undefined`. */ constructor(defaultValue: T); constructor(...defaultValue: T extends undefined ? [] : [T]); /** * Get or set the default value. * * This is used if the {@link current} value is `null` or `undefined`. */ default: T; /** * Get the current value for this context. */ get current(): T; /** * Run a function while providing the specified value for this context. * * See {@link Provide `<Provide>`} when using JSX. * * @param value The value to provide. * @param fn The function to run. * @param args The function arguments. * @returns The function's return value. */ provide<F extends (...args: any) => any>(value: T | null | undefined, fn: F, ...args: Parameters<F>): ReturnType<F>; /** * Shorthand for creating a context-value pair for this context. */ with(value: T | null | undefined): ContextState<T>; /** * Run a function in a new context window (ignoring all current contexts) while providing the specified states. * * @param states The states to provide. * @param fn The function to run. * @param args The function arguments. * @returns The function's return value. */ static isolate<F extends (...args: any) => any>(states: ContextState<unknown>[], fn: F, ...args: Parameters<F>): ReturnType<F>; /** * Run a function while providing the specified states. * * See {@link Provide `<Provide>`} when using JSX. * * @param states The states to provide. When providing multiple values for the same context, the last one is used. * @param fn The function to run. * @param args The function arguments. * @returns The function's return value. */ static provide<F extends (...args: any) => any>(states: ContextState<unknown>[], fn: F, ...args: Parameters<F>): ReturnType<F>; /** * Capture all current context states. */ static capture(): ContextState<unknown>[]; /** * Bind a function to the current context. * * @param fn The function to bind. * @returns The bound function. */ static bind<T extends (...args: any) => any>(fn: T): T; } /** * A context-value pair. * * Fields are considered internal and not subject to semantic versioning. */ interface ContextState<T> { c: Context<T>; v: T | null | undefined; } /** * Component for providing context values while rendering. * * See {@link Context.provide} or {@link Context.prototype.provide} when not using JSX. */ declare function Provide<T>(props: { /** The context to provide a value for. */ context: Context<T>; /** The value to provide. */ value: T | null | undefined; children: () => unknown; } | { /** The context states to provide. */ states: ContextState<unknown>[]; children: () => unknown; }): unknown; /** * A function that is called to dispose something. */ type TeardownHook = () => void; /** * Run a function while capturing teardown hooks. * * + If an error is thrown by the specified function, teardown hooks are called in reverse registration order and the error is re-thrown. * + If an error is thrown by a teardown hook, remaining ones are not called and the error is re-thrown. * * @param fn The function to run. * @returns A function to run all captured teardown hooks in reverse registration order. */ declare function capture(fn: () => void): TeardownHook; /** * Run a function while capturing teardown hooks. * * + When disposed before the specified function finishes, teardown hooks are called in reverse registration order immediately after the function finishes. * + If an error is thrown by the specified function, teardown hooks are called in reverse registration order and the error is re-thrown. * + If an error is thrown by a teardown hook, remaining ones are not called and the error is re-thrown. * * @param fn The function to run. * @returns The function's return value. */ declare function captureSelf<T>(fn: (dispose: TeardownHook) => T): T; /** * Run a function while intentionally leaking teardown hooks. * * @param fn The function to run. * @returns The function's return value. */ declare function leak<T>(fn: () => T): T; /** * Run a function and immediately call teardown hooks if it throws an error. * * + If an error is thrown, teardown hooks are immediately called in reverse registration order and the error is re-thrown. * + If no error is thrown, teardown hooks are registered in the outer context. * * @param fn The function to run. * @returns The function's return value. */ declare function teardownOnError<T>(fn: () => T): T; /** * Register a teardown hook to be called when the current lifecycle is disposed. * * This has no effect if teardown hooks are not captured in the current context. * * @param hook The hook to register. This may be called multiple times. */ declare function teardown(hook: TeardownHook): void; /** * Run a function in isolation from the following side effect causing APIs: * + Teardown hooks are leaked. To isolate only teardown hooks, use {@link leak} instead. * + Signal accesses are not tracked and the default tracking behavior is restored. To only isolate signal accesses, use {@link untrack} instead. * * Note, that batches and contexts are not isolated. * * @param fn The function to run. * @param args The function arguments. * @returns The function's return value. */ declare function isolate<F extends (...args: any) => any>(fn: F, ...args: Parameters<F>): ReturnType<F>; /** * Represents the source that a signal has been derived from. * * When deriving a signal, the source should be passed via the signal constructor or shorthand. * This has no impact on how a signal behaves, but allows other APIs to locate metadata about a signal's source. * * @example * ```js * function trim(source: Signal<string>) { * const input = $(source.value, source); * ... * return input; * } * ``` */ type SignalSource = Signal<unknown> | undefined; /** * Represents a value that changes over time. */ declare class Signal<T> { #private; /** * The current value without access tracking or observer notifications when set. */ inert: T; /** * Create a new signal. * * @param value The initial value. * @param source The {@link SignalSource source} this signal has been derived from. */ constructor(value: T, source?: SignalSource); /** * Reactively access the current value. */ get value(): T; /** * Set the current value. * * If the new value is the same as the previous one, no observers are notified. * * @example * ```tsx * import { $, watch } from "rvx"; * * const count = $(0); * * watch(count, count => { * console.log("Count:", count); * }); * * count.value++; * ``` */ set value(value: T); /** * The {@link SignalSource source}, this signal has been derived from. */ get source(): SignalSource; /** * The root {@link SignalSource source}, this signal has been derived from or this signal itself if it hasn't been derived. */ get root(): Signal<unknown>; /** * Check if this signal has any active observers. */ get active(): boolean; /** * Manually access this signal. */ access(): void; /** * Manually notify observers. * * During batches, notifications are deferred. */ notify(): void; /** * Pass this signal to a function and get its result. * * @example * ```tsx * const value = $(42); * * <TextInput value={ * value * .pipe(parseInt) * .pipe(trim) * } /> * ``` */ pipe<A extends any[], R>(fn: (self: this, ...args: A) => R, ...args: A): R; } /** * Create a new signal. * * @param value The initial value. * @param source The {@link SignalSource source} this signal has been derived from. * @returns The signal. */ declare function $(): Signal<void>; declare function $<T>(value: T, source?: SignalSource): Signal<T>; /** * A value, signal or function to get a value. * * @example * ```tsx * import { $, watch } from "rvx"; * * const message = $("Example"); * * // Not reactive: * watch(message.value, message => { * console.log("A:", message); * }); * * // Reactive: * watch(message, message => { * console.log("B:", message); * }); * * // Reactive: * watch(() => message.value, message => { * console.log("C:", message); * }); * * message.value = "Hello World!"; * ``` */ type Expression<T> = T | Signal<T> | (() => T); /** * Utility to get the result type of an expression. */ type ExpressionResult<T> = T extends Expression<infer R> ? R : never; /** * Utility type for expressions that should never be static values. * * This can be used instead of the {@link Expression} type in places where accepting static values doesn't make sense. */ type Reactive<T> = Signal<T> | (() => T); /** * Utility type to require `T` to not be reactive. */ type Static<T> = unknown extends T ? never : Exclude<T, Reactive<any>>; /** * Watch an expression until the current lifecycle is disposed. * * + Both the expression and effect are called at least once immediately. * + Lifecycle hooks from the expression or effect are called before a signal update is processed or when the current lifecycle is disposed. * * @param expr The expression to watch. * @param effect An optional function to call with each expression result without tracking signal accesses. * * @example * ```tsx * import { $, watch } from "rvx"; * * const count = $(0); * * // Capture teardown hooks registered by "watch": * const dispose = capture(() => { * // Start watching: * watch(count, count => { * console.log("Count:", count); * }); * }); * * count.value = 1; * * // Stop watching: * dispose(); * * count.value = 2; * ``` */ declare function watch<T>(expr: Reactive<T>, effect: (value: T) => void): void; /** * @deprecated * This call can be removed because the expression is always static. You can call the effect directly. */ declare function watch<T>(expr: Static<T>, effect: (value: T) => void): void; /** * Watch an expression until the current lifecycle is disposed. * * + Both the expression and effect are called at least once immediately. * + Lifecycle hooks from the expression or effect are called before a signal update is processed or when the current lifecycle is disposed. * * @param expr The expression to watch. * @param effect An optional function to call with each expression result without tracking signal accesses. * * @example * ```tsx * import { $, watch } from "rvx"; * * const count = $(0); * * // Capture teardown hooks registered by "watch": * const dispose = capture(() => { * // Start watching: * watch(count, count => { * console.log("Count:", count); * }); * }); * * count.value = 1; * * // Stop watching: * dispose(); * * count.value = 2; * ``` */ declare function watch(expr: () => void): void; declare function watch<T>(expr: Expression<T>, effect: (value: T) => void): void; /** * Watch an expression until the current lifecycle is disposed. * * @param expr The expression to watch. * @param effect A function to call with each subsequent expression result without tracking signal accesses. * @returns The first expression result. */ declare function watchUpdates<T>(expr: Reactive<T>, effect: (value: T) => void): T; /** * @deprecated * This call can be removed because the expression is always static. */ declare function watchUpdates<T>(expr: Static<T>, effect: (value: T) => void): T; /** * Watch an expression until the current lifecycle is disposed. * * @param expr The expression to watch. * @param effect A function to call with each subsequent expression result without tracking signal accesses. * @returns The first expression result. */ declare function watchUpdates<T>(expr: Expression<T>, effect: (value: T) => void): T; /** * Wrap an expression to re-run only when any accessed signal has been updated. * * + Lifecycle hooks in the expression are not supported. * + The context from where `lazy` was called is available within the expression. * * @param expr The expression to wrap. */ declare function lazy<T>(expr: () => T): () => T; /** * Defer signal updates until a function finishes. * * + When nesting batches, updates are processed after the most outer batch has completed. * + When updates cause immediate side effects, these side effects will run as part of the batch. * * @param fn The function to run. * @returns The function's return value. * * @example * The example below outputs `5` and `9` once. Without batching the output would be `5, 7, 9`. * ```tsx * import { batch, $, watch } from "rvx"; * * const a = $(2); * const b = $(3); * * watch(() => a.value + b.value, value => { * console.log("Sum:", value); * }); * * batch(() => { * a.value = 4; * b.value = 5; * }); * ``` */ declare function batch<T>(fn: () => T): T; /** * {@link watch Watch} an expression and get a function to reactively access its result. * * @param expr The expression to watch. * @returns A function to reactively access the latest result. * * @example * ```tsx * import { $, memo, watch } from "rvx"; * * const count = $(42); * * const computed = memo(() => someExpensiveComputation(count.value)); * * watch(computed, count => { * console.log("Count:", count); * }); * ``` */ declare function memo<T>(expr: Reactive<T>): () => T; /** * @deprecated * This call can be removed because the expression is always static. You can use the value directly. */ declare function memo<T>(expr: Static<T>): () => T; /** * {@link watch Watch} an expression and get a function to reactively access its result. * * @param expr The expression to watch. * @returns A function to reactively access the latest result. * * @example * ```tsx * import { $, memo, watch } from "rvx"; * * const count = $(42); * * const computed = memo(() => someExpensiveComputation(count.value)); * * watch(computed, count => { * console.log("Count:", count); * }); * ``` */ declare function memo<T>(expr: Expression<T>): () => T; /** * {@link get Evaluate an expression} without tracking signal accesses. * * @param expr The expression to evaluate. * @returns The function's return value. * * @example * ```tsx * import { $, untrack, watch } from "rvx"; * * const a = $(2); * const b = $(3); * * watch(() => a.value + untrack(b), sum => { * console.log("Sum:", sum); * }); * * // This causes an update: * a.value = 4; * * // This has no effect: * b.value = 5; * ``` */ declare function untrack<T>(expr: Reactive<T>): T; /** * @deprecated * This call can be removed because the expression is always static. You can use the value directly. */ declare function untrack<T>(expr: Static<T>): T; /** * {@link get Evaluate an expression} without tracking signal accesses. * * @param expr The expression to evaluate. * @returns The function's return value. * * @example * ```tsx * import { $, untrack, watch } from "rvx"; * * const a = $(2); * const b = $(3); * * watch(() => a.value + untrack(b), sum => { * console.log("Sum:", sum); * }); * * // This causes an update: * a.value = 4; * * // This has no effect: * b.value = 5; * ``` */ declare function untrack<T>(expr: Expression<T>): T; /** * Check if signal accesses are currently tracked. */ declare function isTracking(): boolean; /** * Run a function while tracking signal accesses to invoke the trigger callback when updated. * * See {@link trigger}. */ interface TriggerPipe { <T>(expr: Reactive<T>): T; /** * @deprecated * This call can be removed because the expression is always static. You can use the value directly. */ <T>(expr: Static<T>): T; <T>(expr: Expression<T>): T; } /** * Create an expression evaluator pipe that calls a function once when any accessed signals from the latest evaluated expression are updated. * * + When the lifecycle at which the pipe was created is disposed, the callback function will not be called anymore. * + It is guaranteed that the function is called before any other observers like {@link watch} or {@link effect} are notified. * + If pipes are nested, the callback for the most inner one is called first. * * @param callback The callback to invoke when a signal is updated. * @returns The pipe to evaluate expressions. */ declare function trigger(callback: () => void): TriggerPipe; /** * Manually evaluate an expression in the current context. * * This can be used to access reactive and non reactive inputs. * * @param expr The expression to evaluate. * @returns The expression result. * * @example * ```tsx * import { $, get } from "rvx"; * * const count = $(42); * * get(42) // 42 * get(count) // 42 * get(() => 42) // 42 * ``` */ declare function get<T>(expr: Reactive<T>): T; /** * @deprecated * This call can be removed because the expression is always static. You can use the value directly. */ declare function get<T>(expr: Static<T>): T; /** * Manually evaluate an expression in the current context. * * This can be used to access reactive and non reactive inputs. * * @param expr The expression to evaluate. * @returns The expression result. * * @example * ```tsx * import { $, get } from "rvx"; * * const count = $(42); * * get(42) // 42 * get(count) // 42 * get(() => 42) // 42 * ``` */ declare function get<T>(expr: Expression<T>): T; type MapFn<I, O> = (input: I) => O; /** * Map an expression value while preserving if the expression is static or not. * * @example * ```tsx * import { $, map, get } from "rvx"; * * const count = $(42); * const doubleCount = map(count, value => value * 2); * * get(doubleCount) // 84 * ``` */ declare function map<I, O>(input: Reactive<I>, mapFn: MapFn<I, O>): Expression<O>; /** * @deprecated * This call can be removed because the input is always static. You can call the map function directly. */ declare function map<I, O>(input: Static<I>, mapFn: MapFn<I, O>): Expression<O>; /** * Map an expression value while preserving if the expression is static or not. * * @example * ```tsx * import { $, map, get } from "rvx"; * * const count = $(42); * const doubleCount = map(count, value => value * 2); * * get(doubleCount) // 84 * ``` */ declare function map<I, O>(input: Expression<I>, mapFn: MapFn<I, O>): Expression<O>; /** * Utility type for any falsy value. */ type Falsy = null | undefined | false | 0 | 0n | ""; /** * Type alias for `unknown` to indicate rendered content. */ type Content = unknown; /** * Common interface for components. */ interface Component<TProps = void, TContent = Content> { (props: TProps): TContent; } /** * Namespace URI for HTML elements. */ declare const HTML = "http://www.w3.org/1999/xhtml"; /** * Namespace URI for SVG elements. */ declare const SVG = "http://www.w3.org/2000/svg"; /** * Namespace URI for MathML elements. */ declare const MATHML = "http://www.w3.org/1998/Math/MathML"; type XMLNS = typeof HTML | typeof SVG | typeof MATHML; /** * Context for setting the namespace URI for new elements. */ declare const XMLNS: Context<XMLNS>; /** * Represents any supported "class" attribute value. */ type ClassValue = Expression<undefined | null | false | string | Record<string, Expression<boolean | undefined>> | ClassValue[]>; type HyphenCase<T> = T extends `${infer A}${infer B}` ? `${A extends Capitalize<A> ? "-" : ""}${Lowercase<A>}${HyphenCase<B>}` : T; /** * An object with css property expressions used in {@link StyleValue}. */ type StyleMap = { [K in keyof CSSStyleDeclaration as HyphenCase<K>]?: Expression<unknown>; } & { [K in string]?: Expression<unknown>; }; /** * Represents any supported "style" attribute value. */ type StyleValue = Expression<undefined | StyleMap | StyleValue[]>; type EventListener<E extends Event> = (event: E) => void; /** * Symbol for specifying a DOM node that is used as content. * * See {@link NodeTarget}. */ declare const NODE: unique symbol; /** * If an object used as content has a {@link NODE} property, this node is inserted as content instead. */ interface NodeTarget<T extends Node = Node> { [NODE]: T; } /** * Represents an object with jsx element attributes. */ type Attributes<_T extends Element> = { children?: Content; class?: ClassValue; style?: StyleValue; } & { [K in keyof HTMLElementEventMap as `on:${K}`]?: EventListener<HTMLElementEventMap[K]> | EventArgs<HTMLElementEventMap[K]>; } & { [K in `prop:${string}`]?: Expression<unknown>; } & { [K in `attr:${string}`]?: Expression<unknown>; } & { [K in string]?: Expression<unknown>; }; /** * Type for specifying a jsx event listener with options. */ type EventArgs<E extends Event> = [ listener: EventListener<E>, options?: AddEventListenerOptions ]; /** * Type used for mapping tag names to element types. */ type TagNameMap = HTMLElementTagNameMap & SVGElementTagNameMap & MathMLElementTagNameMap; declare class ElementBuilder<E extends Element> implements NodeTarget<E> { #private; /** * The target element this builder is modifying. */ elem: E; get [NODE](): E; /** * Create a new element builder for the specified element. * * For also creating an element, use the {@link e} shorthand. * * @param elem The element to modify. */ constructor(elem: E); /** * Append an event listener. * * @param type The event type to listen for. * @param listener The event listener function. The current context is available within this listener. * @param options Optional add event listener options. * * @example * ```tsx * e("button").on("click", event => { ... }) * * e("div").on("scroll", event => { ... }, { capture: true }) * ``` */ on<K extends keyof HTMLElementEventMap>(type: K, listener: EventListener<HTMLElementEventMap[K]>, options?: AddEventListenerOptions): this; on<E extends Event>(type: string, listener: EventListener<E>, options?: AddEventListenerOptions): this; /** * Set element styles. * * @param value Any combination of arrays, objects and expressions. * + Properties use the same casing as in css. E.g. `font-family`, not `fontFamily`. * + Properties specified later take precedence over earlier properties. * * @example * ```tsx * e("div").style({ color: "red" }) * * e("div").style([ * { * "color": "red", * "font-size": "1rem", * }, * () => ({ "color": () => "blue" }), * { "color": someSignal }, * [ * { "width": "42px" }, * ], * ]) * ``` */ style(value: StyleValue): Omit<this, "style">; /** * Set element classes. * * @param value Any combination of class tokens, arrays and objects with boolean expressions to determine which classes are added. `undefined`, `null` and `false` is ignored. * * @example * ```tsx * e("div").class("example") * * e("div").class([ * "foo", * () => "bar", * { * baz: true, * boo: () => false, * }, * ]) * ``` */ class(value: ClassValue): Omit<this, "class">; /** * Set an attribute. * * @param name The name. * @param value An expression for the value. * * @example * ```tsx * e("img").set("src", someSignal).set("alt", "Example") * ``` */ set(name: string, value: Expression<unknown>): this; /** * Set a property. * * @param name The name. * @param value An expression for the value. * * @example * ```tsx * e("input").prop("value", someValue) * ``` */ prop<K extends keyof E>(name: K, value: Expression<E[K]>): this; /** * Append content. * * + Any expression will be rendered as text. * + DOM nodes. * + Views. * + Arrays, jsx fragments & any combination of the above. * * @example * ```tsx * e("h1").append("Hello World!") * * e("div").append([ * ["Hello World!"], * e("div"), * ... * ]) * ``` */ append(...content: Content[]): this; } /** * Create a new element builder. * * This uses the current {@link XMLNS} value to determine the namespace. * * @param tagName The tag name. * @returns The builder. * * @example * ```tsx * e("h1").append("Hello World!") * * XMLNS.provide(SVG, () => { * return e("svg").set("viewbox", "0 0 ...") * }) * ``` */ declare function e<K extends keyof TagNameMap>(tagName: K): ElementBuilder<TagNameMap[K]>; declare function e<E extends Element>(tagName: string): ElementBuilder<E>; interface EnvContext extends Context<unknown> { get current(): typeof globalThis; } /** * A context that is used to access all DOM related APIs. * * This can be used to run rvx applications in non browser environments. */ declare const ENV: EnvContext; interface EventFn<T extends unknown[]> { (...args: T): void; } interface Event$1<T extends unknown[]> { /** * Subscribe to this event until the current lifecycle is disposed. */ (listener: EventFn<T>): void; } /** * An emitter for a single event type. * * @example * ```tsx * import { Emitter } from "rvx"; * * const emitter = new Emitter<[address: string, port: number]>(); * * emitter.event((address, port) => { * console.log("Connected:", address, port); * }); * * emitter.emit("127.0.0.1", 12345); * ``` */ declare class Emitter<T extends unknown[]> { #private; /** * Subscribe to this event until the current lifecycle is disposed. */ event: Event$1<T>; /** * Emit this event. * * Note, that listeners will run immediately in the current context. */ emit: EventFn<T>; } type MapArrayFn<I, O> = (input: I, index: () => number) => O; /** * Run a function for each value in an iterable expression keyed by value. * * + The current context is available in the map function. * + Evaluation is stopped when the current lifecycle is disposed. * + Teardown hooks from within the map function are called when a value is removed or when the current lifecycle is disposed. * + Returns a function to reactively access the latest result. */ declare function mapArray<I, O>(inputs: Expression<Iterable<I>>, fn: MapArrayFn<I, O>): () => O[]; /** * Utility for modifying an existing element or {@link ElementBuilder element builder} returned from a component. * * + Event listeners are additive. * + Unique styles, classes, attributes and properties are additive. * + **Styles, classes, attributes and properties that are already set by the component may cause conflicts.** * * @example * ```tsx * override( * SomeComponent(...) * ).class("some-class") * ``` */ declare function override<E extends Element>(target: E | NodeTarget<E>): ElementBuilder<E>; /** * Allocate an ID that is unique in the current thread. * * @returns The unique id in the form `rvx_123`. */ declare function uniqueId(): string; /** * A component that provides a unique id in the form `rvx_123` to its children. * * See {@link UseUniqueId `<UseUniqueId>`} when using JSX. * * @example * ```tsx * import { useUniqueId, e } from "rvx"; * * useUniqueId(id => [ * e("label").set("for", id).append("Text"), * e("input").set("type", "text").set("id", id), * ]) * ``` */ declare function useUniqueId<T = Content>(component: Component<string, T>): T; /** * A component that provides a unique id in the form `rvx_123` to its children. * * See {@link useUniqueId} when not using JSX. * * @example * ```tsx * import { UseUniqueId } from "rvx"; * * <UseUniqueId> * {id => <> * <label for={id}>Text</label> * <input type="text" id={id} /> * </>} * </UseUniqueId> * ``` */ declare function UseUniqueId<T = Content>(props: { children: Component<string, T>; }): T; /** * Get a {@link uniqueId unique id} for the specified object. * * @param target The target object. * @returns The id. This is always the same id for the same object. */ declare function uniqueIdFor(target: object): string; /** * Render arbitrary content. * * Supported content types are: * + Null and undefined (not displayed). * + Arbitrarily nested arrays/fragments of content. * + DOM nodes. Children will be removed from document fragments. * + {@link View Views}. * + Anything created with rvx's jsx runtime. * + Anything else is displayed as text. * * @param content The content to render. * @returns A view instance or the `content` parameter itself if its already a view. * * @example * ```tsx * import { $, render } from "rvx"; * * // Not displayed: * render(null); * render(undefined); * * // Arbitrarily nested arrays/fragments of content: * render([["Hello"], " World!"]); * render(<>{<>"Hello"</>}{" World!"}</>); * * // DOM nodes: * render(<h1>Hello World!</h1>); * render(document.createElement("input")); * render(document.createTextNode("Hello World!")); * render(someTemplate.content.cloneNode(true)); * * // Views: * render(render("Hello World!")); * render(when(true, () => "Hello World!")); * render(<Show when={true}>{() => "Hello World!"}</Show>); * * // Text: * render("Hello World!"); * render(() => "Hello World!"); * render(42); * render($(42)); * ``` */ declare function render(content: Content): View; /** * Render arbitrary content and append it to the specified parent until the current lifecycle is disposed. * * @param parent The parent node. * @param content The content to render. See {@link render} for supported types. * @returns The view instance. * * @example * ```tsx * import { mount } from "rvx"; * * mount( * document.body, * <h1>Hello World!</h1> * ); * ``` * * Since the content is removed when the current lifecycle is disposed, this can also be used to temporarily append * content to different elements while some component is rendered: * ```tsx * import { mount, Content } from "rvx"; * * function Popover(props: { text: Content, children: Content }) { * const visible = $(false); * * mount( * document.body, * <Show when={visible}> * {props.children} * </Show> * ); * * return <button on:click={() => { visible.value = !visible.value; }}> * {props.text} * </button>; * } * * mount( * document.body, * <Popover text="Click me!"> * Hello World! * </Popover> * ); * ``` */ declare function mount(parent: Node, content: Content): View; /** * A function that is called when the view boundary may have been changed. */ interface ViewBoundaryOwner { /** * @param first The current first node. * @param last The current last node. */ (first: Node, last: Node): void; } /** * A function that must be called after the view boundary has been changed. */ interface ViewSetBoundaryFn { /** * @param first The first node if changed. * @param last The last node if changed. */ (first: Node | undefined, last: Node | undefined): void; } interface UninitViewProps { /** * The current first node of this view. * * + This may be undefined while the view is not fully initialized. * + This property is not reactive. */ get first(): Node | undefined; /** * The current last node of this view. * * + This may be undefined while the view is not fully initialized. * + This property is not reactive. */ get last(): Node | undefined; } type UninitView = UninitViewProps & Omit<View, keyof UninitViewProps>; /** * A function that is called once to initialize a view instance. * * View creation will fail if no first or last node has been set during initialization. */ interface ViewInitFn { /** * @param setBoundary A function that must be called after the view boundary has been changed. * @param self The current view itself. This can be used to keep track of the current boundary and parent nodes. */ (setBoundary: ViewSetBoundaryFn, self: UninitView): void; } /** * Represents a sequence of at least one DOM node. * * Consumers of the view API need to guarantee that: * + The sequence of nodes is not modified from the outside. * + If there are multiple nodes, all nodes must have a common parent node at all time. */ declare class View { #private; /** * Create a new view. * * View implementations need to guarantee that: * + The view doesn't break when the parent node is replaced or when a view consisting of only a single node is detached from its parent. * + The boundary is updated immediately after the first or last node has been updated. * + If there are multiple nodes, all nodes remain in the current parent. * + If there are multiple nodes, the initial nodes must have a common parent. */ constructor(init: ViewInitFn); /** * The current first node of this view. * * Note, that this property is not reactive. */ get first(): Node; /** * The current last node of this view. * * Note, that this property is not reactive. */ get last(): Node; /** * The current parent node or undefined if there is none. * * Note, that this property is not reactive. */ get parent(): Node | undefined; /** * Set the boundary owner for this view until the current lifecycle is disposed. * * @throws An error if there currently is a boundary owner. */ setBoundaryOwner(owner: ViewBoundaryOwner): void; /** * Append all nodes of this view to the specified parent. * * @param parent The parent to append to. */ appendTo(parent: Node): void; /** * Insert all nodes of this view before a reference child of the specified parent. * * @param parent The parent to insert into. * @param ref The reference child to insert before. If this is null, the nodes are appended to the parent. */ insertBefore(parent: Node, ref: Node | null): void; /** * Detach all nodes of this view from the current parent if there is one. * * If there are multiple nodes, they are moved into a new document fragment to allow the view implementation to stay alive. * * @returns The single removed node or the document fragment they have been moved into. */ detach(): Node | DocumentFragment; } /** * Get an iterator over all current top level nodes of a view. * * @param view The view. * @returns The iterator. * * @example * ```tsx * import { render, viewNodes } from "rvx"; * * const view = render(<> * <h1>Hello World!</h1> * </>); * * for (const node of viewNodes(view)) { * console.log(node); * } * ``` */ declare function viewNodes(view: View): IterableIterator<Node>; /** * Watch an expression and render content from its result. * * + If an error is thrown during initialization, the error is re-thrown. * + If an error is thrown during a signal update, the previously rendered content is kept in place and the error is re-thrown. * + Content returned from the component can be directly reused within the same `nest` instance. * * See {@link Nest `<Nest>`} when using JSX. * * @param expr The expression to watch. * @param component The component to render with the expression result. If the expression returns a component, null or undefined, this can be omitted. * * @example * ```tsx * import { $, nest, e } from "rvx"; * * const count = $(0); * * // Using the expression result in a component: * nest(count, count => { * switch (count) { * case 0: return e("h1").append("Hello World!"); * case 1: return "Something else..."; * } * }) * * // Or directly returning a component from the expression: * nest(() => { * switch (count.value) { * case 0: return () => e("h1").append("Hello World!"); * case 1: return () => "Something else..."; * } * }) * ``` */ declare function nest(expr: Expression<Component | null | undefined>): View; declare function nest<T>(expr: Expression<T>, component: Component<T>): View; /** * Render conditional content. * * + Content is only re-rendered if the expression result is not strictly equal to the previous one. If this behavior is undesired, use {@link nest} instead. * + If an error is thrown by the expression or component during initialization, the error is re-thrown. * + If an error is thrown by the expression or component during a signal update, the previously rendered content is kept and the error is re-thrown. * * See {@link Show `<Show>`} when using JSX. * * @param condition The condition to watch. * @param truthy The component to render when the condition result is truthy. * @param falsy An optional component to render when the condition is falsy. * * @example * ```tsx * import { $, when, e } from "rvx"; * * const message = $<null | string>("Hello World!"); * * when(message, value => e("h1").append(value), () => "No message...") * ``` */ declare function when<T>(condition: Expression<T | Falsy>, truthy: Component<T>, falsy?: Component): View; interface ForContentFn<T> { /** * @param value The value. * @param index An expression to get the current index. * @returns The content. */ (value: T, index: () => number): Content; } /** * Render content for each value in an iterable. * * Errors thrown by the component or while updating an index result in undefined behavior. * * See {@link For `<For>`} for use with JSX. * * @param each The expression to watch. Note, that signals accessed during iteration will also trigger updates. * @param component The component to render for each value. * * @example * ```tsx * import { $, forEach, e } from "rvx"; * * const items = $([1, 2, 3]); * * forEach(items, value => e("li").append(value)) * ``` */ declare function forEach<T>(each: Expression<Iterable<T>>, component: ForContentFn<T>): View; interface IndexContentFn<T> { /** * @param value An expression to get the current value. * @param index The index. * @returns The content. */ (value: () => T, index: number): Content; } /** * Render content for each index in an iterable. * * Errors thrown by the component or while updating a value result in undefined behavior. * * See {@link Index `<Index>`} when using JSX. * * @param each The expression to watch. Note, that signals accessed during iteration will also trigger updates. * @param component The component to render for each index. * * @example * ```tsx * import { $, indexEach, e } from "rvx"; * * const items = $([1, 2, 3]); * * indexEach(items, value => e("li").append(value)) * ``` */ declare function indexEach<T>(each: Expression<Iterable<T>>, component: IndexContentFn<T>): View; /** * A wrapper that can be used for moving and reusing views. */ declare class MovableView { #private; constructor(view: View); /** * Create a new view that contains the wrapped view until it is moved again or detached. * * If the lifecycle in which `move` is called is disposed, the created view no longer updates its boundary and nodes may be silently removed. */ move: Component<void, View>; /** * Detach content from the currently active view. */ detach(): void; } /** * Render and wrap arbitrary content so that it can be moved and reused. */ declare function movable(content: Content): MovableView; /** * Attach or detach content depending on an expression. * * Content is kept alive when detached. * * See {@link Attach `<Attach>`} when using JSX. * * @param condition The condition to watch. * @param content The content to attach when the condition result is truthy. * * @example * ```tsx * import { $, attach } from "rvx"; * * const showMessage = $(true); * * attachWhen(showMessage, e("h1").append("Hello World!")) * ``` */ declare function attachWhen(condition: Expression<boolean>, content: Content): View; /** * Set rvx jsx attributes. * * @param elem The element. * @param attrs The attributes to set. */ declare function applyElement<E extends Element>(elem: E, attrs: Attributes<E>): void; /** * Utility for modifying an existing element or {@link ElementBuilder element builder} returned from a component. * * + Event listeners and the _ref_ attribute are additive. * + Unique styles, classes, attributes and properties are additive. * + **Styles, classes, attributes and properties that are already set by the component may cause conflicts.** * * @example * ```tsx * <Override class="some-class"> * <SomeComponent /> * </Override> * ``` */ declare function Override<E extends Element>(props: { children: unknown; } & Attributes<E>): unknown; /** * Watch an expression and render content from its result. * * + If an error is thrown during initialization, the error is re-thrown. * + If an error is thrown during a signal update, the previously rendered content is kept in place and the error is re-thrown. * + Content returned from the component can be directly reused within the same `<Nest>` instance. * * See {@link nest} when not using JSX. * * @example * ```tsx * import { $, Nest } from "rvx"; * * const count = $(0); * * // Using the expression result in a component: * <Nest watch={count}> * {count => { * switch (count) { * case 0: return <h1>Hello World!</h1>; * case 1: return "Something else..."; * } * }} * </Nest> * * // Or directly returning a component from the expression: * <Nest watch={() => { * switch (count.value) { * case 0: return () => <h1>Hello World!</h1>; * case 1: return () => "Something else..."; * } * }} /> * ``` */ declare function Nest<T>(props: { /** * The expression to watch. */ watch: T; /** * The component to render with the expression result. * * If the expression returns a component, null or undefined, this can be omitted. */ children: Component<ExpressionResult<T>>; } | { /** * The expression to watch. */ watch: Expression<Component | null | undefined>; }): View; /** * Render conditional content. * * + Content is only re-rendered if the expression result is not strictly equal to the previous one. If this behavior is undesired, use {@link Nest} instead. * + If an error is thrown by the expression or component during initialization, the error is re-thrown. * + If an error is thrown by the expression or component during a signal update, the previously rendered content is kept and the error is re-thrown. * * See {@link when} when not using JSX. * * @example * ```tsx * import { $, Show } from "rvx"; * * const message = $<null | string>("Hello World!"); * * <Show when={message} else={() => <>No message...</>}> * {value => <h1>{value}</h1>} * </Show> * ``` */ declare function Show<T>(props: { /** * The condition to watch. */ when: Expression<T | Falsy>; /** * The component to render when the condition result is truthy. */ children: Component<T>; /** * An optional component to render when the condition result is falsy. */ else?: Component; }): View; /** * Render content for each value in an iterable. * * Errors thrown by the component or while updating an index result in undefined behavior. * * See {@link forEach} when not using JSX. * * @example * ```tsx * import { $, For } from "rvx"; * * const items = $([1, 2, 3]); * * <For each={items}> * {value => <li>{value}</li>} * </For> * ``` */ declare function For<T>(props: { /** * The expression to watch. Note, that signals accessed during iteration will also trigger updates. */ each: Expression<Iterable<T>>; /** * The component to render for each value. */ children: ForContentFn<T>; }): View; /** * Render content for each index in an iterable. * * Errors thrown by the component or while updating a value result in undefined behavior. * * See {@link indexEach} when not using JSX. * * @example * ```tsx * import { $, Index } from "rvx"; * * const items = $([1, 2, 3]); * * <Index each={items}> * {value => <li>{value}</li>} * </Index> * ``` */ declare function Index<T>(props: { /** * The expression to watch. * * Note, that signals accessed during iteration will also trigger updates. */ each: Expression<Iterable<T>>; /** * The component to render for each index pair. */ children: IndexContentFn<T>; }): View; /** * Attach or detach content depending on an expression. * * Content is kept alive when detached. * * See {@link attachWhen} when not using JSX. * * @example * ```tsx * import { $, Attach } from "rvx"; * * const showMessage = $(true); * * <Attach when={showMessage}> * <h1>Hello World!</h1> * </Attach> * ``` */ declare function Attach(props: { /** * The condition to watch. */ when: Expression<boolean>; /** * The content to attach when the condition result is truthy. */ children?: Content; }): View; export { $, Attach, Context, ENV, ElementBuilder, Emitter, For, HTML, Index, MATHML, MovableView, NODE, Nest, Override, Provide, SVG, Show, Signal, UseUniqueId, View, XMLNS, applyElement, attachWhen, batch, capture, captureSelf, e, forEach, get, indexEach, isTracking, isolate, lazy, leak, map, mapArray, memo, mount, movable, nest, override, render, teardown, teardownOnError, trigger, uniqueId, uniqueIdFor, untrack, useUniqueId, viewNodes, watch, watchUpdates, when }; export type { Attributes, ClassValue, Component, Content, ContextState, Event$1 as Event, EventArgs, EventFn, EventListener, Expression, ExpressionResult, Falsy, ForContentFn, IndexContentFn, MapArrayFn, MapFn, NodeTarget, Reactive, SignalSource, Static, StyleMap, StyleValue, TagNameMap, TeardownHook, TriggerPipe, UninitView, ViewBoundaryOwner, ViewInitFn, ViewSetBoundaryFn };