rvx
Version:
A signal based rendering library
1,595 lines (1,576 loc) • 46.1 kB
TypeScript
/*!
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 };