/*! 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. */ /** * Utility for defining phantom typed context key-value pairs. * * @example * ```ts * import { ContextKey } from "@mxjp/gluon"; * * 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): 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, fn: () => R): R; /** * Inject an entry. * * @example * ```tsx * import { mount, Inject, extract } from "@mxjp/gluon"; * * mount( * document.body, * * {() =>

{extract("message")}

} *
* ); * ``` */ declare function Inject(props: { key: K; value: ContextValue; children: () => unknown; }): 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 { mount, DeriveContext, extract } from "@mxjp/gluon"; * * mount( * document.body, * * {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 "@mxjp/gluon"; * * 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 "@mxjp/gluon"; * * 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 triggers or dependants to notify. * * When this is false, it is guaranteed that updating the value does not result in immediate side effects. */ 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 "@mxjp/gluon"; * * 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. * @param trigger If true, batches are ignored and the expression and function run before all non-triggers. Default is false. * * @example * ```tsx * import { sig, watch } from "@mxjp/gluon"; * * 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, trigger?: boolean): 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. * @param trigger If true, batches are ignored and the expression and function run before all non-triggers. Default is false. * @returns The first expression result. */ declare function watchUpdates(expr: Expression, fn: (value: T) => void, trigger?: boolean): 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. * @param trigger If true, batches are ignored and the expression and function run before all non-triggers. Default is false. */ declare function effect(fn: () => void, trigger?: boolean): void; /** * Evaluate an expression and call a function once when any accessed signals are updated. * * It is guaranteed that all triggers are called before other non-trigger dependants per signal update or batch. * * @param expr The expression evaluate. * @param fn The function to call when any accessed signals are updated. * @param cycle An arbitrary number to pass back to the function. * * @example * ```tsx * import { sig, trigger } from "@mxjp/gluon"; * * const count = sig(0); * * console.log("Count:", trigger(count, cycle => { * console.log("Count is being updated:", cycle); * }, 42)); * * count.value++; * ``` */ declare function trigger(expr: Expression, fn: (cycle: number) => void, cycle?: number): T; /** * Defer signal updates while calling a function and call them immediately after all current batches have finished. * * @param fn The function to run. * @returns The function's return value. * * @example * ```tsx * import { batch, sig, watch } from "@mxjp/gluon"; * * 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 also evaluated if it isn't used. * * @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 * ```ts * import { sig, memo, watch } from "@mxjp/gluon"; * * const count = sig(42); * * const memoized = memo(() => count.value); * * watch(memoized, count => { * console.log("Count:", count); * }); * ``` */ declare function memo(expr: Expression, equals?: SignalEqualsFn | boolean): () => T; /** * Wrap an expression to be evaulated only when any of the accessed signals have been updated. * * This is similar to {@link memo}, but the expression is only evaulated if it is used. * * @param expr The expression to wrap. * @returns A function to lazily evaluate the expression. */ declare function lazy(expr: Expression): () => 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 "@mxjp/gluon"; * * 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 an expression is currently evaluated to track signal accesses. */ declare function isTracking(): boolean; /** * 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 "@mxjp/gluon"; * * 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 "@mxjp/gluon"; * * 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 "@mxjp/gluon"; * *
; //
*
; //
*
; //
* ``` */ 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 "@mxjp/gluon"; * *
; //
*
; //
* ``` */ declare function optionalString(input: Expression): Expression>>; type Falsy = null | undefined | false | 0 | 0n | ""; type TagNameMap = HTMLElementTagNameMap & SVGElementTagNameMap & MathMLElementEventMap; /** * 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"; /** * Key for setting the namespace URI for newly created elements. * * @example * ```tsx * import { mount, XMLNS, SVG, Inject } from "@mxjp/gluon"; * * mount( * document.body, * <> * * {() => ...} * * * ); * ``` */ declare const XMLNS: ContextKey<"http://www.w3.org/1999/xhtml" | "http://www.w3.org/2000/svg" | "http://www.w3.org/1998/Math/MathML">; /** * Append content to a node. * * @param node The node. * @param content The content to append. */ declare function appendContent(node: Node, content: unknown): void; 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; /** * Represents an object with element attributes. */ type Attributes = { class?: ClassValue; style?: StyleValue; } & { [K in keyof HTMLElementEventMap as `$${K}` | `$$${K}`]?: (event: HTMLElementEventMap[K]) => void; } & { [K in `prop:${string}`]?: Expression; } & { [K in `attr:${string}`]?: Expression; } & { [K in string]?: Expression; }; /** * Set attributes on an element. * * @param elem The element. * @param attrs The attributes to set. */ declare function setAttributes(elem: Element, attrs: Attributes): void; /** * 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; /** * Create an element. * * @param tagName The tag name. * @param attrs The attributes to set. * @param content The content to append. * @returns The element. * * @example * ```ts * import { mount, e } from "@mxjp/gluon"; * * mount( * document.body, * // Element with content only: * e("div", [ * // Element with attributes only: * e("div", { class: "example" }), * * // Element with attributes and content: * e("div", { class: "example" }, [ * "Hello World!", * ]), * ]), * ); * ``` */ declare function e(tagName: K, content?: unknown[]): TagNameMap[K]; declare function e(tagName: K, attrs?: Attributes, content?: unknown[]): TagNameMap[K]; declare function e(tagName: string, content?: unknown[]): E; declare function e(tagName: string, attrs?: Attributes, content?: unknown[]): E; interface EventFn { (...args: T): void; } interface Event { /** * 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 "@mxjp/gluon"; * * 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; /** * Emit this event. */ emit(...args: T): void; } /** * Get or create a shared global value. * * When using this, the caller must guarantee, that all future versions are compatible with the earliest version this has been used in. * * @param symbolKey A unique key that is used with `Symbol.for` to store the value on the `globalThis` object. * @param createNew A function to create a new value if there is none. * @returns The value. * * @example * ```tsx * const example = sharedGlobal("example-key", () => new Example()); * ``` */ declare function sharedGlobal(symbolKey: string, createNew: () => T): T; /** * Ensure that instances of the target class are also recognized as instances of the same target class from other library versions. * * When using this, the caller must guarantee, that all future versions of the target class are compatible with the earliest version this has been used on. * * @example * ```tsx * class Example { * static { * shareInstancesOf(this, "example-key"); * } * } * ``` */ declare function shareInstancesOf(targetClass: { prototype: object; }, symbolKey: string): void; /** * Allocate an ID that is unique in the current thread. * * @returns The unique id in the form `gluon_123`. */ declare function uniqueId(): string; /** * A component that provides a unique id in the form `gluon_123` to it's children. * * @example * ```tsx * import { mount, UseUniqueId } from "@mxjp/gluon"; * * mount( * document.body, * * {id => <> * * * } * * ); * ``` */ declare function UseUniqueId(props: { children: (id: string) => unknown; }): unknown; /** * A function that can be called to dispose something. */ type TeardownHook = () => void; /** * Run a function while capturing teardown hooks. * * @param fn The function to run. * @returns A function to run all captured teardown hooks. */ declare function capture(fn: () => void): TeardownHook; /** * Run a function while capturing teardown hooks that may dispose itself. * * @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; /** * 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; /** * 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 single parent node. */ declare class View { #private; /** * Create a new view. * * View implementations need to guarantee that: * + The view doesn't break if the parent is replaced from the outside. * + The boundary is updated when the first or last node has been replaced. * + If there are multiple nodes, all nodes remain in the current parent. * + If there are multiple nodes, the initial nodes must have a 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 "@mxjp/gluon"; * * 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. * * @example * ```tsx * import { mount, Nest, sig } from "@mxjp/gluon"; * * const count = sig(0); * * mount( * document.body, * * {() => { * 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<(() => unknown) | null | undefined>; }): 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. * * @example * ```tsx * import { mount, sig, Show } from "@mxjp/gluon"; * * const message = sig("Hello World!"); * * mount( * document.body, * <>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: (value: T) => unknown; /** * An optional function to create content if the value is falsy. */ else?: () => unknown; }): 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. * * @example * ```tsx * import { ForUnique, mount, sig } from "@mxjp/gluon"; * * const items = sig([1, 2, 3]); * * mount( * document.body, * * {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. * * @example * ```tsx * import { mount, IndexFor, sig } from "@mxjp/gluon"; * * const items = sig([1, 2, 3]); * * mount( * document.body, * * {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 { mount, sig, Attach } from "@mxjp/gluon"; * * const showMessage = sig(true); * * mount( * document.body, * *

    Hello World!

    *
    * ); * ``` */ declare function Attach(props: { /** * The expression to evaluate. */ when: Expression; /** * The content to attach when the expression is truthy. */ children?: unknown; }): View; /** * Create a text node that displays the result of an expression. * * Null and undefined are displayed as an empty string. */ declare function createText(expr: Expression): Text; /** * 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 gluons 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 "@mxjp/gluon"; * * // 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 "@mxjp/gluon"; * * 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 "@mxjp/gluon"; * * 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; /** * 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; /** * 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 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; } 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 "@mxjp/gluon"; * * 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 WaitGuardCondition = (value: T) => value is R; type WaitCondition = (value: T) => boolean; declare class WaitForTimeoutError 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 WaitForTimeoutError}. */ declare function watchFor(expr: Expression, timeout?: number): Promise; declare function watchFor(expr: Expression, condition?: WaitGuardCondition, timeout?: number): Promise; declare function watchFor(expr: Expression, condition?: WaitCondition, timeout?: number): Promise; /** * 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 "@mxjp/gluon"; * import { ROUTER } from "@mxjp/gluon/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 "@mxjp/gluon"; * import { ROUTER } from "@mxjp/gluon/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", "gluon: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; } /** * 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; export { ASYNC, Async, AsyncContext, AsyncError, Attach, type Attributes, ChildRouter, type ClassValue, type ComponentRoute, type Context, type ContextKey, type ContextValue, DeriveContext, Emitter, type Event, type EventFn, type Expression, type ExpressionResult, For, type ForContentFn, HTML, HashRouter, type HashRouterOptions, HistoryRouter, type HistoryRouterOptions, IndexFor, type IndexForContentFn, Inject, MATHML, type MapFn, MovableView, Nest, type ParentRouteMatch, type QueryInit, ROUTER, type ReadonlyContext, type Route, type RouteMatch, type RouteMatchFn, type RouteMatchResult, type Router, Routes, SVG, Show, Signal, type SignalEqualsFn, type StyleMap, type StyleValue, TASKS, TaskSlot, type TaskSource, Tasks, type TasksOptions, type TeardownHook, UseUniqueId, View, type ViewBoundaryOwner, type ViewInitFn, type ViewSetBoundaryFn, type WaitCondition, WaitForTimeoutError, type WaitGuardCondition, type WatchedRoutes, XMLNS, appendContent, batch, capture, captureSelf, createContext, createElement, createText, deriveContext, e, effect, extract, get, getContext, inject, isPending, isSelfPending, isTracking, join, lazy, map, matchRoute, memo, mount, movable, nocapture, normalize, optionalString, relative, render, runInContext, setAttributes, setPending, shareInstancesOf, sharedGlobal, sig, string, teardown, track, trigger, uncapture, uniqueId, untrack, useAbortController, useAbortSignal, viewNodes, waitFor, watch, watchFor, watchRoutes, watchUpdates, wrapContext };