/*! MIT License Copyright (c) 2024 Max J. Polster Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Create a new abort controller that aborts when the current lifecycle is disposed. */ declare function useAbortController(reason?: unknown): AbortController; /** * Get an abort signal that aborts when the current lifecycle is disposed. */ declare function useAbortSignal(reason?: unknown): AbortSignal; /** * Common interface for components. */ interface Component { (props: Props): Content; } /** * Utility for defining phantom typed context key-value pairs. * * @example * ```ts * import { ContextKey } from "rvx"; * * const key = Symbol("example") as SymbolFor<"exampleValue">; * * inject(key, "exampleValue", () => { * const value = extract(key); // type = "exampleValue" * }); * ``` */ type ContextKey = symbol & { PHANTOM_CONTEXT_KEY_FOR: V & never; }; /** * The value type for a specific type of key. */ type ContextValue = K extends ContextKey ? V : unknown; /** * Interface for a context that should not be modified. * * Note that this is always a {@link Map} instance. */ interface ReadonlyContext { get(key: K): ContextValue | undefined; has(key: unknown): boolean; readonly size: number; } /** * Interface for a context that may be modified. * * Note that this is always a {@link Map} instance. */ interface Context extends ReadonlyContext { clear(): void; delete(key: unknown): boolean; set(key: K, value: ContextValue | undefined): void; } /** * Create a new empty context. */ declare function createContext(): Context; /** * Get the current context. * * @returns The current context or undefined if there is no context. */ declare function getContext(): ReadonlyContext | undefined; /** * Get a value from the current context. * * @param key The key to find. * @returns The value or undefined if not found. */ declare function extract(key: K): ContextValue | undefined; /** * Run a function within a copy of the current context that also contains an additional entry. * * + For injecting multiple entries prefer using {@link deriveContext}. * * @param value The key value pair or instance to inject. * @param fn The function to run. * @returns The function's return value. */ declare function inject(key: K, value: ContextValue | undefined, fn: () => R): R; /** * Inject an entry. * * @example * ```tsx * import { Inject, extract } from "rvx"; * * * {() =>

{extract("message")}

} *
* ``` */ declare function Inject(props: { key: K; value: ContextValue | undefined; children: Component; }): unknown; /** * Run a function within a copy of the current context. * * @param fn The function to run. * @returns The function's return value. */ declare function deriveContext(fn: (context: Context, parent?: ReadonlyContext) => R): R; /** * Render content with a copy of the current context. * * @example * ```tsx * import { DeriveContext, extract } from "rvx"; * * * {context => { * context.set("message", "Hello World!"); * return

{extract("message")}

; * }} *
* ``` */ declare function DeriveContext(props: { /** * A function to render content. */ children: (context: Context, parent?: ReadonlyContext) => unknown; }): unknown; /** * Run a function within the specified or without a context. * * @param context The context or undefined to use no context. * @param fn The function to run. * @returns The function's return value. */ declare function runInContext(context: ReadonlyContext | undefined, fn: () => R): R; /** * Wrap a function to be run with the current context. * * @param fn The function to wrap. * @returns The wrapper. */ declare function wrapContext any>(fn: T): T; /** * A function used in signals to determine if the signal should update during a value assignment. */ interface SignalEqualsFn { /** * @param previous The previous value. * @param current The current value. * @returns False to update. */ (previous: T, current: T): boolean; } /** * Represents a value that changes over time. */ declare class Signal { #private; /** * Create a new signal. * * @param value The initial value. * @param equals True to skip updates when an assigned value is strictly equal to the previous one or a function to determine of the values are equal. Default is true. */ constructor(value: T, equals?: SignalEqualsFn | boolean); /** * Access the current value. */ get value(): T; /** * Set the current value. * * @example * ```tsx * import { sig, watch } from "rvx"; * * const count = sig(0); * * watch(count, count => { * console.log("Count:", count); * }); * * count.value++; * ``` */ set value(value: T); /** * Update the current value in place. * * @param fn A function to update the value. If false is returned, dependants are not notified. * * @example * ```tsx * import { sig, watch } from "rvx"; * * const items = sig([]); * * watch(items, items => { * console.log("Items:", items); * }); * * items.update(items => { * items.push("foo"); * items.push("bar"); * }); * ``` */ update(fn: (value: T) => void | boolean): void; /** * Check if this signal has any active dependants. */ get active(): boolean; /** * Manually access this signal. */ access(): void; /** * Manually notify dependants. */ notify(): void; /** * Pass this signal to a function and get it's result. * * @example * ```tsx * const value = sig(42); * * * ``` */ pipe(fn: (self: this, ...args: A) => R, ...args: A): R; } /** * Create a new signal. * * @param value The initial value. * @param equals True to skip updates when an assigned value is strictly equal to the previous one or a function to determine if the values are equal. Default is true. * @returns The signal. */ declare function sig(): Signal; declare function sig(value: T, equals?: SignalEqualsFn | boolean): Signal; /** * A value, signal or function to get a value. * * @example * ```tsx * import { sig, watch } from "rvx"; * * const message = sig("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 | Signal | (() => T); /** * Type for the result of an expression. */ type ExpressionResult = T extends Expression ? R : never; /** * Watch an expression until the current lifecycle is disposed. * * @param expr The expression to watch. * @param fn The function to call with the expression result. This is guaranteed to be called at least once immediately. Lifecycle hooks are called before the next function call or when the current lifecycle is disposed. * * @example * ```tsx * import { sig, watch } from "rvx"; * * const count = sig(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: Expression, fn: (value: T) => void): void; /** * Watch an expression until the current lifecycle is disposed. * * @param expr The expression to watch. * @param fn The function to call with the expression result when any updates occur. * @returns The first expression result. */ declare function watchUpdates(expr: Expression, fn: (value: T) => void): T; /** * Run and watch a function until the current lifecycle is disposed. * * Note, that this doesn't separate signal accesses from side effects which makes it easier to accidentally cause infinite loops. If possible, use {@link watch} or {@link watchUpdates} instead. * * @param fn The function to run. Lifecycle hooks are called before the next function call or when the current lifecycle is disposed. */ declare function effect(fn: () => void): void; /** * 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, sig, watch } from "rvx"; * * const a = sig(2); * const b = sig(3); * * watch(() => a.value + b.value, value => { * console.log("Sum:", value); * }); * * batch(() => { * a.value = 4; * b.value = 5; * }); * ``` */ declare function batch(fn: () => T): T; /** * Watch an expression and create a function to reactively access it's latest result. * * This is similar to {@link lazy}, but the expression is always evaluated and then updates it's dependants. * * @param expr The expression to watch. * @param equals True to skip updates when a result is strictly equal to the previous one or a function to determine if the results are equal. Default is true. * @returns A function to access the latest result. * * @example * ```tsx * import { sig, memo, watch } from "rvx"; * * const count = sig(42); * * const computed = memo(() => someExpensiveComputation(count.value)); * * watch(computed, count => { * console.log("Count:", count); * }); * ``` */ declare function memo(expr: Expression, equals?: SignalEqualsFn | boolean): () => T; /** * Run a function while not tracking signal accesses. * * This is the opposite of {@link track}. * * @param fn The function to run. * @returns The function's return value. * * @example * ```tsx * import { sig, untrack, watch } from "rvx"; * * const a = sig(2); * const b = sig(3); * * watch(() => a.value + untrack(() => b.value), sum => { * console.log("Sum:", sum); * }); * * a.value = 4; * b.value = 5; * ``` */ declare function untrack(fn: () => T): T; /** * Run a function while tracking signal accesses. This is the default behavior. * * This is the opposite of {@link untrack}. * * @param fn The function to run. * @returns The function's return value. */ declare function track(fn: () => T): T; /** * Check if a currently evaluating expression is tracking signal accesses. */ declare function isTracking(): boolean; interface TriggerPipe { (expr: Expression): 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 fn The callback to invoke when a signal is updated. * @returns The pipe to evaluate expressions. */ declare function trigger(fn: () => void): TriggerPipe; /** * Evaulate an expression. * * This can be used to access reactive and non reactive inputs. * * @param expr The expression to evaluate. * @returns The expression result. * * @example * ```tsx * import { sig, get } from "rvx"; * * const count = sig(42); * * get(42) // 42 * get(count) // 42 * get(() => 42) // 42 * ``` */ declare function get(expr: Expression): T; type MapFn = (input: I) => O; /** * Map an expression value while preserving if the expression is static or not. * * @example * ```tsx * import { sig, map, get } from "rvx"; * * const count = sig(42); * const doubleCount = map(count, value => value * 2); * * get(doubleCount) // 84 * ``` */ declare function map(input: Expression, mapFn: MapFn): Expression; /** * Map an expression value to strings. * * See {@link map}. * * @example * ```tsx * import { string } from "rvx"; * *
; //
*
; //
*
; //
* ``` */ declare function string(input: Expression): Expression; /** * Map an expression value to strings unless it's null or undefined. * * See {@link map}. * * @example * ```tsx * import { optionalString } from "rvx"; * *
; //
*
; //
* ``` */ declare function optionalString(input: Expression): Expression>>; /** * 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; /** * Key for setting the namespace URI for newly created elements. * * @example * ```tsx * import { XMLNS, SVG, Inject } from "rvx"; * * * {() => ...} * * ``` */ declare const XMLNS: ContextKey; type ClassValue = Expression> | ClassValue[]>; type HyphenCase = T extends `${infer A}${infer B}` ? `${A extends Capitalize ? "-" : ""}${Lowercase}${HyphenCase}` : T; type StyleMap = { [K in keyof CSSStyleDeclaration as HyphenCase]?: Expression; } & { [K in string]?: Expression; }; type StyleValue = Expression; type EventListener = (event: E) => void; /** * **This is experimental API.** */ declare const NODE: unique symbol; /** * **This is experimental API.** */ interface NodeTarget { [NODE]: Node; } /** * A function that can be 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 that may dispose itself. * * + 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(fn: (dispose: TeardownHook) => T): T; /** * Run a function without capturing any teardown hooks. * * This is the opposite of {@link capture}. * * @param fn The function to run. * @returns The function's return value. */ declare function uncapture(fn: () => T): T; /** * Run a function and explicitly un-support teardown hooks. * * Teardown hooks are still supported when using {@link capture}, {@link captureSelf} or {@link uncapture} inside of the function. * * This should be used in places where lifecycle side are never expected. * * @param fn The function to run. * @returns The function's return value. */ declare function nocapture(fn: () => T): T; /** * Run a function within an error isolation boundary. * * + 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 isolate(fn: () => T): T; /** * Register a teardown hook. * * 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. * @throws An error if teardown hooks are {@link nocapture explicitly un-supported}. */ declare function teardown(hook: TeardownHook): void; type Falsy = null | undefined | false | 0 | 0n | ""; type TagNameMap = HTMLElementTagNameMap & SVGElementTagNameMap & MathMLElementTagNameMap; /** * 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; } /** * 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: View): 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 it's 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; /** * Get all nodes of this view as a single node for moving them into a new place. * * If there are multiple nodes, a document fragment containing all nodes of this view is returned. */ take(): Node | DocumentFragment; /** * 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. */ detach(): void; } /** * 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(<> *

Hello World!

* ); * * for (const node of viewNodes(view)) { * console.log(node); * } * ``` */ declare function viewNodes(view: View): IterableIterator; /** * A component that renders content depending on an expression. * * + 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. * * @example * ```tsx * import { Nest, sig } from "rvx"; * * const count = sig(0); * * * {() => { * const value = count.value; * return () => <>{value}; * }} * * ``` */ declare function Nest(props: { /** * An expression that returns a function to create content or null or undefined to render nothing. */ children: Expression; }): View; /** * A component that renders 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. * * @example * ```tsx * import { sig, Show } from "rvx"; * * const message = sig("Hello World!"); * * <>No message...}> * {value =>

{value}

} *
* ``` */ declare function Show(props: { /** * The expression to evaluate. */ when: Expression; /** * A function to create content if the value is truthy. */ children: Component; /** * An optional function to create content if the value is falsy. */ else?: Component; }): View; /** * A function to create content for a specific value. */ interface ForContentFn { /** * @param value The value. * @param index An expression to get the current index. * @returns The content. */ (value: T, index: () => number): unknown; } /** * A component that renders content for each unique value in an iterable. * * If an error is thrown by iterating or by rendering an item, the update is stopped as if the previous item was the last one and the error is re-thrown. * * @example * ```tsx * import { ForUnique, sig } from "rvx"; * * const items = sig([1, 2, 3]); * * * {value =>
  • {value}
  • } *
    * ``` */ declare function For(props: { /** * The expression. */ each: Expression>; /** * A function to create content for a specific value. */ children: ForContentFn; }): View; /** * A function to create content for a specific index and value. */ interface IndexForContentFn { /** * @param value The value. * @param index The index. * @returns The content. */ (value: T, index: number): unknown; } /** * A component that renders content for each value in an iterable, keyed by index and value. * * If an error is thrown by iterating or by rendering an item, the update is stopped as if the previous item was the last one and the error is re-thrown. * * @example * ```tsx * import { IndexFor, sig } from "rvx"; * * const items = sig([1, 2, 3]); * * * {value =>
  • {value}
  • } *
    * ``` */ declare function IndexFor(props: { /** * The expression. */ each: Expression>; /** * A function to create content for a specific index and value. */ children: IndexForContentFn; }): 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. */ move(): View; /** * Detach the wrapped view if attached. */ detach(): void; } /** * Render and wrap arbitrary content so that it can be moved and reused. */ declare function movable(content: unknown): MovableView; /** * A component that attaches or detaches content depending on an expression. * * Content is kept alive when detached. * * @example * ```tsx * import { sig, Attach } from "rvx"; * * const showMessage = sig(true); * * *

    Hello World!

    *
    * ``` */ declare function Attach(props: { /** * The expression to evaluate. */ when: Expression; /** * The content to attach when the expression is truthy. */ children?: unknown; }): View; /** * Renders content depending on the state of an async function or promise. * * This task is tracked using the current {@link ASYNC async context} if any. It is guaranteed, that the view is updated before the tracked task completes. */ declare function Async(props: { /** * The async function or promise. */ source: (() => Promise) | Promise; /** * A function render content while pending. * * By default, nothing is rendered. */ pending?: () => unknown; /** * A function to render content when resolved. * * By default, nothing is rendered. */ children?: (value: T) => unknown; /** * A function to render content when rejected. * * By default, nothing is rendered. */ rejected?: (value: unknown) => unknown; }): View; /** * Represents pending operations in an asynchronously rendered tree. * * This can be used to wait until an entire async tree is rendered or to check if any unhandled errors occurred. */ declare class AsyncContext { #private; constructor(parent?: AsyncContext); /** * Reactively check if there are any pending tasks in this context. * * @example * ```tsx * asyncCtx.pending}> *
    Please wait...
    *
    * ``` */ get pending(): boolean; /** * Track the specified task in this and all parent contexts. */ track(task: Promise): void; /** * Wait until all tracked tasks in this and all child contexts have completed. * * This also includes new tasks that are tracked while waiting. * * @throws Errors thrown by any tracked task or an {@link AsyncError} if multiple tasks failed. */ complete(): Promise; /** * Create a new async context using the {@link extract current} context as parent. */ static fork(): AsyncContext; } /** * Thrown by {@link AsyncContext.complete} if multiple unhandled {@link errors} occurred. */ declare class AsyncError extends Error { errors: unknown[]; constructor(errors: unknown[]); } /** * Context key for the current {@link AsyncContext}. */ declare const ASYNC: ContextKey; /** * A queue for sequentially running async tasks that can be triggered by both the user and side effects. */ declare class TaskSlot { #private; /** * Create a new task slot. * * When the current lifecycle is disposed, all side effects are aborted and removed from the queue. */ constructor(); /** * Queue a side effect to run if this slot isn't blocked. * * This will abort and remove all other side effects from the queue. * * @param task The side effect to queue. */ sideEffect(task: (signal: AbortSignal) => unknown | Promise): void; /** * Queue a task to run and block this slot until it completes. * * This will abort and remove all other side effects from the queue. * * @param task The blocking task to queue. * @returns The result of the task. */ block(task: () => T | Promise): Promise; } type TaskSource = (() => unknown) | Promise | null | undefined; interface TasksOptions { /** * If true, focus is restored on the last active element when there are no more pending tasks in this instance. * * By default, this is inherited from the parent or true of there is none. */ restoreFocus?: boolean; } /** * Represents a set of pending tasks in a specific context. * * This is meant to be used for preventing concurrent user interaction in a specific context. */ declare class Tasks { #private; /** * Create a new tasks instance with the specified parent. * * @param parent The parent to use. Default is no parent. */ constructor(parent?: Tasks, options?: TasksOptions); /** * The parent instance or undefined if there is none. */ get parent(): Tasks | undefined; /** * True if this instance has any pending tasks. * * @example * ```tsx *
    tasks.selfPending}>...
    * ``` */ get selfPending(): boolean; /** * True if this instance or any of it's parents has any pending tasks. * * @example * ```tsx * * ``` */ get pending(): boolean; /** * Pretend, that there is a pending task until the current lifecycle is disposed. */ setPending(): void; /** * Wait for an async function or a promise. * * @param source The async function or promise to wait for. */ waitFor(source: TaskSource): void; /** * Create a new tasks instance using the {@link extract current} instance as parent. */ static fork(options?: TasksOptions): Tasks; } /** * Context key for the current {@link Tasks} instance. */ declare const TASKS: ContextKey; /** * Check if there are any pending tasks in the current tasks instance. * * This can be used in conjuction with {@link waitFor} to indicate if there are any pending tasks. * * This is meant to be used for preventing concurrent user interaction in a specific context. * * @example * ```tsx *
    ...
    * ``` */ declare function isSelfPending(): boolean; /** * Check if there are any pending tasks in the current tasks instance or any of it's parents. * * This can be used in conjunction with {@link waitFor} to disable inputs and buttons while there are any pending tasks. * * This is meant to be used for preventing concurrent user interaction in a specific context. * * @example * ```tsx * * ``` */ declare function isPending(): boolean; /** * Pretend, that there is a pending task in the current tasks instance until the current lifecycle is disposed. * * This is meant to be used for preventing concurrent user interaction in a specific context. * * @example * ```tsx * import { inject, TASKS, Tasks, capture, setPending, isPending } from "rvx"; * * inject(TASKS, new Tasks(), () => { * isPending(); // => false * const stop = capture(setPending); * isPending(); // => true * stop(); * isPending(); // => false * }); * ``` */ declare function setPending(): void; /** * Use the current tasks instance to wait for an async function or promise. * * This is meant to be used for preventing concurrent user interaction in a specific context. * * @param source The async function or promise to wait for. */ declare function waitFor(source: TaskSource): void; type WatchGuardCondition = (value: T) => value is R; type WatchCondition = (value: T) => boolean; declare class WatchForTimeoutError extends Error { } /** * Utility to watch an expression until it's output satisfies a condition. * * @param expr The expression to watch. * @param condition The condition to test. By default, all truthy values are matched. * @param timeout An optional timeout. Default is no timeout. * @returns A promise that resolves with the first matched output or rejects with a {@link WatchForTimeoutError}. */ declare function watchFor(expr: Expression, timeout?: number): Promise; declare function watchFor(expr: Expression, condition?: WatchGuardCondition, timeout?: number): Promise; declare function watchFor(expr: Expression, condition?: WatchCondition, timeout?: number): Promise; declare class ElementBuilder implements NodeTarget { elem: E; get [NODE](): Node; constructor(elem: E); on(name: K, listener: EventListener, options?: AddEventListenerOptions): this; on(name: string, listener: EventListener, options?: AddEventListenerOptions): this; style(value: StyleValue): Omit; class(value: ClassValue): Omit; set(name: string, value: Expression): this; prop(name: K, value: Expression): this; append(...content: unknown[]): this; } /** * Create a new element builder. * * @param tagName The tag name. * @returns The builder. */ declare function e(tagName: K): ElementBuilder; declare function e(tagName: string): ElementBuilder; type RefFn = (element: T) => void; type RefValue = (RefFn) | RefFn[]; /** * Represents an object with element attributes. */ type Attributes = { class?: ClassValue; style?: StyleValue; ref?: RefValue; } & { [K in keyof HTMLElementEventMap as `on:${K}`]?: EventListener | EventArgs; } & { [K in `prop:${string}`]?: Expression; } & { [K in `attr:${string}`]?: Expression; } & { [K in string]?: Expression; }; type EventArgs = [ listener: EventListener, options?: AddEventListenerOptions ]; /** * Create an element. * * @param tagName The tag name. * @param attrs The attributes to set. * @param content The content to append. * @returns The element. */ declare function createElement(tagName: K, attrs: Attributes, content: unknown): TagNameMap[K]; declare function createElement(tagName: string, attrs: Attributes, content: unknown): E; interface EventFn { (...args: T): void; } interface Event$1 { /** * Subscribe to this event until the current lifecycle is disposed. */ (listener: EventFn): 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 { #private; /** * Subscribe to this event until the current lifecycle is disposed. */ event: Event$1; /** * Emit this event. */ emit(...args: T): void; } /** * 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 it's children. * * @example * ```tsx * import { UseUniqueId } from "rvx"; * * * {id => <> * * * } * * ``` */ declare function UseUniqueId(props: { children: Component; }): unknown; /** * 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 itself if it's already a view. * * @example * ```tsx * import { render, sig } from "rvx"; * * // Not displayed: * render(null); * render(undefined); * * // Arbitrarily nested arrays/fragments of content: * render([["Hello"], " World!"]); * render(<>{<>"Hello"}{" World!"}); * * // DOM nodes: * render(

    Hello World!

    ); * 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({() => "Hello World!"}); * * // Text: * render("Hello World!"); * render(() => "Hello World!"); * render(42); * render(sig(42)); * ``` */ declare function render(content: unknown): 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, *

    Hello World!

    * ); * ``` * * 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 } from "rvx"; * * function Popover(props: { text: unknown, children: unknown }) { * const visible = sig(false); * * mount( * document.body, * * {props.children} * * ); * * return ; * } * * mount( * document.body, * * Hello World! * * ); * ``` */ declare function mount(parent: Node, content: unknown): View; type StartTrigger = "on-connect" | "manual"; type DisposeTrigger = "on-disconnect" | "manual"; interface RvxElementOptions { /** * Shadow root options to use or false to attach content to the element directly. * * By default and when `true`, an open shadow root is attached immediately. */ shadow?: boolean | ShadowRootInit; /** * When to render this element's content. * * + `on-connect` - Default. Render when this element is connected. * + `manual` - Render only when `.init()` is called. */ start?: StartTrigger; /** * When to dispose this element's content. * * + `on-disconnect` - Default. Dispose when this element is disconnected or when `.dispose()` is called. * + `manual` - Dispose only when `.dispose()` is called. */ dispose?: DisposeTrigger; } declare abstract class RvxElement extends HTMLElement { #private; static observedAttributes?: string[]; constructor(options?: RvxElementOptions); /** * Called to render the content of this element on this element. * * @returns The content to attach to this element or the shadow root if it exists. */ abstract render(): unknown; /** * Get a signal that reflects an attribute value. * * + `null` represents a missing attribute. * + This signal is only updated if the name is part of the static `observedAttributes` array. * + Updating the signal value will also update or remove the attribute. * + This signal will be kept alive until neither this element nor the signal is referenced anymore. * * @param name The attribute name. * @returns The signal. */ reflect(name: string): Signal; /** * Manually initialize this element. * * This has no effect if the element is already initialized. */ start(): void; /** * Manually dispose this element. * * This will leave rendered content as is. */ dispose(): void; connectedCallback(): void; disconnectedCallback(): void; attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void; } /** * Represents a path with optional query parameters that may change over time. */ interface Router { /** * The root router. */ get root(): Router; /** * The parent of this router if any. */ get parent(): Router | undefined; /** * Reactively get the remaining normalized path in this context. */ get path(): string; /** * Reactively get the search parameters in this context. */ get query(): URLSearchParams | undefined; /** * Navigate to the specified path within the path this router is mounted in. * * @param path The path. This may not be normalized. * @param query The query part. * * @example * ```tsx * import { extract } from "rvx"; * import { ROUTER } from "rvx/router"; * * extract(ROUTER)!.root.push("/home"); * ``` */ push(path: string, query?: QueryInit): void; /** * Same as {@link push}, but replaces the URL in history if possible. * * @example * ```tsx * import { extract } from "rvx"; * import { ROUTER } from "rvx/router"; * * extract(ROUTER)!.root.replace("/home"); * ``` */ replace(path: string, query?: QueryInit): void; } type QueryInit = ConstructorParameters[0]; /** * Context key for the current {@link Router} instance. */ declare const ROUTER: ContextKey; /** * A router that is located at a specific path and navigates within that path. */ declare class ChildRouter implements Router { #private; /** * Create a new child router. * * @param parent The parent router. * @param mountPath The path this router is mounted at. * @param path An expression to get the normalized rest path. */ constructor(parent: Router, mountPath: string, path: Expression); get root(): Router; get parent(): Router | undefined; get path(): string; get query(): URLSearchParams | undefined; push(path: string, query?: QueryInit): void; replace(path: string, query?: QueryInit): void; } interface HashRouterOptions { /** * The current location is parsed when one of these events occur. * * @default ["hashchange"] */ parseEvents?: string[]; } /** * A router that uses `location.hash` as the path ignoring the leading `"#"`. * * Everything after the first `"?"` is treated as query parameters. */ declare class HashRouter implements Router { #private; constructor(options?: HashRouterOptions); get root(): Router; get parent(): Router | undefined; get path(): string; get query(): URLSearchParams | undefined; push(path: string, query?: QueryInit): void; replace(path: string, query?: QueryInit): void; } interface HistoryRouterOptions { /** * The current location is parsed when one of these events occur. * * @default ["popstate", "rvx:router:update"] */ parseEvents?: string[]; /** * The leading base path to ignore when matching routes. * * @default "" */ basePath?: string; } /** * A router that uses the history API. */ declare class HistoryRouter implements Router { #private; constructor(options?: HistoryRouterOptions); get root(): Router; get parent(): Router | undefined; get path(): string; get query(): URLSearchParams | undefined; push(path: string, query?: QueryInit): void; replace(path: string, query?: QueryInit): void; } interface MemoryRouterOptions { /** * The initial path. */ path?: string; /** * The initial query. */ query?: QueryInit; } /** * A router that keeps it's state in memory instead of the actual browser location. */ declare class MemoryRouter implements Router { #private; constructor(options?: MemoryRouterOptions); get root(): Router; get parent(): Router | undefined; get path(): string; get query(): URLSearchParams | undefined; push(path: string, query?: QueryInit): void; replace(path: string, query?: QueryInit): void; } /** * Normalize a path: * + Empty paths are represented as an empty string. * + Non-empty paths start with a slash. * * @param path The path to normalize. * @param preserveDir True to keep trailing slashes in non-empty paths. Default is `true`. * @returns The normalized path. */ declare function normalize(path: string, preserveDir?: boolean): string; /** * Join two paths. * * Note, that this dosn't handle empty, ".." or "." parts. * * @param parent The parent path. * @param child The child path. * @param preserveDir True to keep trailing slashes. Default is `true`. * @returns A {@link normalize normalized} path. */ declare function join(parent: string, child: string, preserveDir?: boolean): string; /** * Get a normalized relative path. * * Note, that this dosn't handle empty, ".." or "." parts, but inserts ".." parts if necessary. * * @param from The path from which the relative path starts. * @param to The path to which the relative path points. * @param preserveDir True to keep trailing slashes from the `to` path. Trailing slashes from the `from` path are always discarded. Default is `true`. * @returns A {@link normalize normalized} path. */ declare function relative(from: string, to: string, preserveDir?: boolean): string; interface RouteMatchResult { /** * The normalized matched path. */ path: string; /** * The parameters extracted from the matched path. */ params?: unknown; } interface RouteMatchFn { /** * Check if this route matches the specified path. * * @param path The path to match against. * @returns The match result or undefined if the path doesn't match. */ (path: string): RouteMatchResult | undefined; } interface Route { /** * The paths this route matches. */ match?: string | RegExp | RouteMatchFn; } interface ParentRouteMatch { /** * The route that has been matched. */ route: T; /** * The normalized matched path. */ path: string; /** * The parameters extracted from the matched path. */ params?: unknown; } interface RouteMatch extends ParentRouteMatch { /** * The normalized remaining rest path. */ rest: string; } /** * Find the first matching route. * * @param path The {@link normalize normalized} path to match against. Non normalized paths result in undefined behavior. * @param routes The routes to test in order. * @returns A match or undefined if none of the routes matched. */ declare function matchRoute(path: string, routes: Iterable): RouteMatch | undefined; interface WatchedRoutes { match: () => ParentRouteMatch | undefined; rest: () => string; } /** * Watch and match routes against an expression. * * @param path The normalized path. * @param routes The routes to watch. * @returns An object with individually watchable route match and the unmatched rest path. */ declare function watchRoutes(path: Expression, routes: Expression>): WatchedRoutes; /** * A route where the content is a component to render. */ interface ComponentRoute extends Route { content: (props: { /** * Matched route parameters. */ params: unknown; }) => unknown; } /** * Match and render routes in the current context. * * A {@link ChildRouter} is injected as a replacement for the current router when rendering route content. */ declare function Routes(props: { /** * The routes to match. */ routes: Expression>; }): unknown; /** * An object that is used to convert between reactive wrappers and their targets. */ interface Barrier { /** * Get a reactive wrapper for the specified value. * * This should always return the same wrapper for the same value. * * @param value The target. * @returns The wrapper or the value itself if it was already wrapped. */ wrap(value: T): T; /** * Get the target for the specified reactive wrapper. * * This should always return the same target for the same value. * * @param value The wrapper or a non-wrapped value. * @returns The target or the value itself if it was already unwrapped. */ unwrap(value: T): T; } interface WrapInstanceFn { (instance: T): T; } /** * Get a deep reactive wrapper for the specified value. * * This always returns the same wrapper for the same value. * * @param value The value to wrap. * @returns The wrapper or the value itself if it was already wrapped. */ declare function wrap(value: T): T; /** * Get the target for a reactive wrapper. * * This always returns the same target for the same value. * * @param value The value to unwrap. * @returns The target or the value itself if it was already unwrapped. */ declare function unwrap(value: T): T; /** * Allow instances of the specified target class to be wrapped. * * @param targetClass The target class. * @param wrap A function to wrap an instance. By default `createReactiveProxy` is used with `wrap` and `unwrap` for inner values. * * @example * ```tsx * class Example { * static { * // Using the default "createReactiveProxy": * wrapInstancesOf(this); * * // Or a custom wrapper: * wrapInstancesOf(this, instance => { * return createSomeWrapperFor(instance); * }); * } * } * ``` */ declare function wrapInstancesOf(targetClass: new (...args: any) => T, wrap?: (instance: T) => T): void; /** * A signal for tracking accesses to values that may not exist. */ declare class ProbeSignal extends Signal { #private; /** * Create a new probe signal. * * @param onDisposable A function to call when it is guaranteed that this signal is no longer watched. * @param value The initial value. * @param equals True to skip updates when an assigned value is strictly equal to the previous one or a function to determine of the values are equal. Default is true. */ constructor(onDisposable: () => void, value: T, equals?: boolean | SignalEqualsFn); notify(): void; } /** * A map for tracking keyed accesses to values that may not exist. */ declare class ProbeMap { #private; /** * Create a new probe map. * * @param get A function to get the current value for a key at any time. */ constructor(get: (key: K) => V); /** * Access the specified key. * * If an expression is currently evaluated to track signal accesses, this will create and access a probe signal for the specified key. That probe signal is automatically removed when it is guaranteed that this key is no longer watched. */ access(key: K): void; /** * Update a key-value pair and notify it's dependants. */ update(key: K, value: V): void; /** * Set the value of all existing probe signals in this map. */ fill(value: V): void; } declare function createReactiveArrayProxy(target: T[], barrier: Barrier): T[]; /** * A reactive wrapper for a map. */ declare class ReactiveMap extends Map { #private; /** * Create a new wrapper. * * @param target The target. * @param barrier The barrier to convert values. Keys are not reactive. */ constructor(target: Map, barrier: Barrier); get size(): number; get(key: K): V | undefined; has(key: K): boolean; set(key: K, value: V): this; delete(key: K): boolean; clear(): void; entries(): MapIterator<[K, V]>; keys(): MapIterator; values(): MapIterator; forEach(callback: (value: V, key: K, map: Map) => void, thisArg?: unknown): void; [Symbol.iterator](): MapIterator<[K, V]>; get [Symbol.toStringTag](): string; } /** * A reactive wrapper for a set. */ declare class ReactiveSet extends Set { #private; /** * Create a new wrapper. * * @param target The target. * @param barrier The barrier to convert values. */ constructor(target: Set, barrier: Barrier); get size(): number; has(value: T): boolean; add(value: T): this; delete(value: T): boolean; clear(): void; entries(): SetIterator<[T, T]>; keys(): SetIterator; values(): SetIterator; forEach(callback: (value: T, value2: T, set: Set) => void, thisArg?: unknown): void; [Symbol.iterator](): SetIterator; get [Symbol.toStringTag](): string; } /** * Create a signal that reflects a property of an arbitrary object. * * @param target The target object. * @param key The property key. * @returns The signal. */ declare function reflect(target: T, key: K): Signal; type TeardownLeakHook = (hooK: TeardownHook) => void; /** * Register a hook to be called when any teardown hooks are registered outside of any capture calls. * * Errors thrown from the leak hook will be thrown by the **teardown** calls. */ declare function onTeardownLeak(hook: TeardownLeakHook): void; interface AsyncTestContext { ctx: Context; asyncCtx: AsyncContext; use: (fn: () => T) => T; } type AsyncTestFn = (ctx: AsyncTestContext) => Promise; declare function runAsyncTest(fn: AsyncTestFn): Promise; type TestFn = (ctx: Context) => T; declare function runTest(fn: TestFn): T; /** * Run an exclusive action for a specific purpose. * * @param key The key to identify the purpose. * @param action The action to run. */ declare function exclusive(key: unknown, action: () => T | Promise): Promise; /** * The same as **querySelector**, but for {@link View views}. */ declare function querySelector(view: View, selector: K): TagNameMap[K] | null; declare function querySelector(view: View, selector: string): E | null; /** * The same as **querySelectorAll**, but for {@link View views}. */ declare function querySelectorAll(view: View, selector: K): TagNameMap[K][]; declare function querySelectorAll(view: View, selector: string): E[]; export { ASYNC, Async, AsyncContext, AsyncError, type AsyncTestContext, type AsyncTestFn, Attach, type Attributes, type Barrier, ChildRouter, type ClassValue, type Component, type ComponentRoute, type Context, type ContextKey, type ContextValue, DeriveContext, type DisposeTrigger, ElementBuilder, Emitter, type Event$1 as Event, type EventArgs, type EventFn, type EventListener, type Expression, type ExpressionResult, For, type ForContentFn, HTML, HashRouter, type HashRouterOptions, HistoryRouter, type HistoryRouterOptions, IndexFor, type IndexForContentFn, Inject, MATHML, type MapFn, MemoryRouter, type MemoryRouterOptions, MovableView, NODE, Nest, type NodeTarget, type ParentRouteMatch, ProbeMap, ProbeSignal, type QueryInit, ROUTER, ReactiveMap, ReactiveSet, type ReadonlyContext, type RefFn, type RefValue, type Route, type RouteMatch, type RouteMatchFn, type RouteMatchResult, type Router, Routes, RvxElement, type RvxElementOptions, SVG, Show, Signal, type SignalEqualsFn, type StartTrigger, type StyleMap, type StyleValue, TASKS, TaskSlot, type TaskSource, Tasks, type TasksOptions, type TeardownHook, type TeardownLeakHook, type TestFn, type TriggerPipe, UseUniqueId, View, type ViewBoundaryOwner, type ViewInitFn, type ViewSetBoundaryFn, type WatchCondition, WatchForTimeoutError, type WatchGuardCondition, type WatchedRoutes, type WrapInstanceFn, XMLNS, batch, capture, captureSelf, createContext, createElement, createReactiveArrayProxy, deriveContext, e, effect, exclusive, extract, get, getContext, inject, isPending, isSelfPending, isTracking, isolate, join, map, matchRoute, memo, mount, movable, nocapture, normalize, onTeardownLeak, optionalString, querySelector, querySelectorAll, reflect, relative, render, runAsyncTest, runInContext, runTest, setPending, sig, string, teardown, track, trigger, uncapture, uniqueId, untrack, unwrap, useAbortController, useAbortSignal, viewNodes, waitFor, watch, watchFor, watchRoutes, watchUpdates, wrap, wrapContext, wrapInstancesOf };