rvx
Version:
A signal based rendering library
1,411 lines (1,393 loc) • 41 kB
TypeScript
/*!
MIT License
Copyright (c) 2025 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.
*/
/**
* 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 injecting the specified value for this context.
*
* See {@link Inject `<Inject>`} when using JSX.
*
* @param value The value to inject.
* @param fn The function to run.
* @param args The function arguments.
* @returns The function's return value.
*/
inject<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 injecting the specified states.
*
* @param states The states to inject.
* @param fn The function to run.
* @param args The function arguments.
* @returns The function's return value.
*/
static window<F extends (...args: any) => any>(states: ContextState<unknown>[], fn: F, ...args: Parameters<F>): ReturnType<F>;
/**
* Run a function while injecting the specified states.
*
* See {@link Inject `<Inject>`} when using JSX.
*
* @param states The states to inject. 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 inject<F extends (...args: any) => any>(states: ContextState<unknown>[], fn: F, ...args: Parameters<F>): ReturnType<F>;
/**
* Capture all current context states.
*/
static capture(): ContextState<unknown>[];
/**
* Capture all current context states and wrap a function to always run with only these states injected.
*
* @param fn The function to wrap.
* @returns The wrapped function.
*/
static wrap<T extends (...args: any) => any>(fn: T): T;
}
/**
* A context-value pair.
*/
interface ContextState<T> {
context: Context<T>;
/**
* The value that is injected when using this state with {@link Inject `<Inject>`}, {@link Context.inject} or {@link Context.window}.
*/
value: T | null | undefined;
}
/**
* Component for injecting context values while rendering.
*
* See {@link Context.inject} when not using JSX.
*/
declare function Inject<T>(props: {
/** The context to inject into. */
context: Context<T>;
/** The value to inject. */
value: T | null | undefined;
children: () => unknown;
} | {
/** The context states to inject. */
states: ContextState<unknown>[];
children: () => unknown;
}): unknown;
/**
* 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;
/**
* 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>;
/**
* Update the current value in place.
*
* @param fn A function to update the value. If false is returned, observers are not notified.
*
* @example
* ```tsx
* import { $, watch } from "rvx";
*
* const items = $([]);
*
* 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 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 it's 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);
/**
* 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: Expression<T>, effect: (value: T) => void): 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 it's 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;
/**
* 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 { $, untrack, watch } from "rvx";
*
* const a = $(2);
* const b = $(3);
*
* watch(() => a.value + untrack(() => b.value), sum => {
* console.log("Sum:", sum);
* });
*
* a.value = 4;
* b.value = 5;
* ```
*/
declare function untrack<T>(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<T>(fn: () => 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>(fn: 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: 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: Expression<I>, mapFn: MapFn<I, O>): Expression<O>;
/**
* 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<undefined | null | string>;
} & {
[K in string]?: Expression<undefined | null | string>;
};
/**
* 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 {
[NODE]: Node;
}
/**
* A function that is called immediately when the `ref` jsx attribute is initialized.
*/
type RefFn<T> = (element: T) => void;
/**
* Value for the `ref` jsx attribute.
*/
type RefValue<T> = (RefFn<T>) | RefFn<T>[];
/**
* Represents an object with jsx element attributes.
*/
type Attributes<T extends Element> = {
class?: ClassValue;
style?: StyleValue;
ref?: RefValue<T>;
} & {
[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;
/**
* 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;
}
declare class ElementBuilder<E extends Element> implements NodeTarget {
#private;
/**
* The target element this builder is modifying.
*/
elem: E;
get [NODE](): Node;
/**
* 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 prpoerty.
*
* @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.inject(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>;
}
/**
* Run a function in isolation from the following side effect causing APIs:
* + Teardown hooks are leaked. To isolate only teardown hooks, use {@link uncapture} instead.
* + Signal accesses are not tracked and the default tracking behavior is restored. To only control the tracking behavior, use {@link track} or {@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>;
type LeakHook = (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 onLeak(hook: LeakHook): void;
/**
* 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 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<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.
* @throws An error if teardown hooks are {@link nocapture explicitly un-supported}.
*/
declare function teardown(hook: TeardownHook): 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.
*
* 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 it's 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 itself if it's 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(<When value={true}>{() => "Hello World!"}</When>);
*
* // 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 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;
/**
* 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 it's 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);
*
* nest(count, count => {
* switch (count) {
* 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;
/**
* Watch an expression and render content from it's 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);
*
* <Nest watch={count}>
* {count => {
* switch (count) {
* case 0: return <h1>Hello World!</h1>;
* case 1: return "Something else...";
* }
* }}
* </Nest>
* ```
*/
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 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;
/**
* 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;
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 unique value in an iterable.
*
* If an error is thrown while iterating or while rendering an item, the update is stopped as if the previous item was the last one and the error is re-thrown.
*
* 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 unique 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;
/**
* Render content for each unique value in an iterable.
*
* If an error is thrown while iterating or while rendering an item, the update is stopped as if the previous item was the last one and the error is re-thrown.
*
* 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 unique value.
*/
children: ForContentFn<T>;
}): View;
interface IndexContentFn<T> {
/**
* @param value The value.
* @param index The index.
* @returns The content.
*/
(value: T, index: number): Content;
}
/**
* Render 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.
*
* 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 value/index pair.
*
* @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;
/**
* Render 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.
*
* 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 value/index pair.
*/
children: 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 it's 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;
/**
* 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, Inject, MATHML, MovableView, NODE, Nest, SVG, Show, Signal, UseUniqueId, View, XMLNS, attachWhen, batch, capture, captureSelf, e, forEach, get, indexEach, isTracking, isolate, map, memo, mount, movable, nest, onLeak, render, teardown, teardownOnError, track, trigger, uncapture, 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, LeakHook, MapFn, NodeTarget, Reactive, RefFn, RefValue, SignalSource, StyleMap, StyleValue, TagNameMap, TeardownHook, TriggerPipe, UninitView, ViewBoundaryOwner, ViewInitFn, ViewSetBoundaryFn };