UNPKG

rvx

Version:

A signal based rendering library

324 lines (313 loc) 11.7 kB
/*! This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ import { Context } from './rvx.js'; import { Component, Falsy } from './rvx.js'; import { View } from './rvx.js'; import { Expression } from './rvx.js'; /** * 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; interface AsyncContextParent { /** * Called by async contexts to track a pending task. */ track(task: Promise<unknown>): void; } /** * Represents pending operations in an asynchronously rendered tree. * * This can be used to wait until an entire async tree is rendered or to check if any unhandled errors occurred. */ declare class AsyncContext { #private; constructor(parent?: AsyncContextParent); /** * Reactively check if there are any pending tasks in this context. * * @example * ```tsx * <Show when={() => asyncCtx.pending}> * <div class="overlay">Please wait...</div> * </Show> * ``` */ get pending(): boolean; /** * Track the specified task in this and all parent contexts. */ track(task: Promise<unknown>): 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<void>; /** * Create a new async context using the current context as parent. */ static fork(): AsyncContext; } /** * Thrown by {@link AsyncContext.complete} if multiple unhandled {@link errors} occurred. */ declare class AsyncError extends Error { errors: unknown[]; constructor(errors: unknown[]); } /** * Context for the current {@link AsyncContext}. */ declare const ASYNC: Context<AsyncContext | undefined>; /** * Render content depending on the state of an async function or promise. * * See {@link Async `<Async>`} when using JSX or when named properties are preferred. * * 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. * * @param source The async function or promise. * + If this is a function, it runs {@link isolate isolated}. * @param component A component to render content when resolved. * + The resolved value is passed as the first argument. * + Nothing is rendered by default. * @param pending A component to render while pending. * + Nothing is rendered by default. * @param rejected A component to render content when rejected. * + The rejected error is passed as the first argument. * + Nothing is rendered by default. */ declare function nestAsync<T>(source: (() => Promise<T>) | Promise<T>, component?: Component<T>, pending?: Component, rejected?: Component<unknown>): View; /** * Render content depending on the state of an async function or promise. * * See {@link nestAsync} when not using JSX or when positional arguments are preferred. * * 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<T>(props: { /** * The async function or promise. * * If this is a function, it runs {@link isolate isolated}. */ source: (() => Promise<T>) | Promise<T>; /** * A component to render content when resolved. * * + The resolved value is passed as the first argument. * + Nothing is rendered by default. */ children?: Component<T>; /** * A component to render content while pending. * * + Nothing is rendered by default. */ pending?: Component; /** * A component to render content when rejected. * * + The rejected error is passed as the first argument. * + Nothing is rendered by default. */ rejected?: Component<unknown>; }): View; /** * A queue for sequentially running async tasks that can be triggered by both the user and side effects. */ declare class Queue { #private; /** * Create a new queue. * * 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 queue isn't currently 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<unknown>): void; /** * Queue a task to run and block this queue 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<T>(task: () => T | Promise<T>): Promise<T>; } type TaskSource = (() => unknown) | Promise<unknown> | 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 * <div inert={() => tasks.selfPending}>...</div> * ``` */ get selfPending(): boolean; /** * True if this instance or any of its parents has any pending tasks. * * @example * ```tsx * <button disabled={() => tasks.pending}>...</button> * ``` */ 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. * + If this is a function, it runs {@link isolate isolated}. */ waitFor(source: TaskSource): void; /** * Create a new tasks instance using the current instance as parent. */ static fork(options?: TasksOptions): Tasks; } /** * Context for the current {@link Tasks} instance. */ declare const TASKS: Context<Tasks | undefined>; /** * Check if there are any pending tasks in the current tasks instance. * * This can be used in conjunction with {@link Tasks.prototype.waitFor `TASKS.current.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 * <div inert={isSelfPending}>...</div> * ``` */ declare function isSelfPending(): boolean; /** * Check if there are any {@link Tasks.prototype.pending pending} tasks in the current tasks instance or any of its parents. * * This can be used in conjunction with {@link Tasks.prototype.waitFor `TASKS.current.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 * <button disabled={isPending}>...</button> * ``` */ declare function isPending(): boolean; /** * The same as {@link queueMicrotask}, but with context & lifecycle support. * * + If the current lifecycle is disposed, the callback is never called. * + The lifecycle within the callback is treated as the current lifecycle. * + The current context is available inside the callback. * * @param callback The callback to run as a microtask. * @throws An error if teardown hooks are explicitly un-supported in this context. */ declare function useMicrotask(callback: () => void): void; /** * The same as {@link setTimeout}, but with context & lifecycle support. * * + If the current lifecycle is disposed, the timeout is {@link clearTimeout cleared}. * + The lifecycle within the callback is treated as the current lifecycle. * + The current context is available inside the callback. * * @param callback The callback to run. * @param timeout The timeout in milliseconds. See {@link setTimeout} for details. * @throws An error if teardown hooks are explicitly un-supported in this context. */ declare function useTimeout(callback: () => void, timeout: number): void; /** * The same as {@link setInterval}, but with context & lifecycle support. * * + If the current lifecycle is disposed, the interval is {@link clearInterval cleared}. * + The lifecycle within the callback is disposed when the interval is cleared and before each call. * + The current context is available inside the callback. * * @param callback The callback to run. * @param interval The interval in milliseconds. See {@link setInterval} for details. * @throws An error if teardown hooks are explicitly un-supported in this context. */ declare function useInterval(callback: () => void, interval: number): void; /** * Repeatedly {@link requestAnimationFrame request animation frames}. * * + If the current lifecycle is disposed, the latest request is cancelled. * + The lifecycle within the callback is disposed before each call and when the current lifecycle is disposed. * + The current context is available inside the callback. * * @param callback The callback to run with a {@link performance.now high resolution timestamp}. * @throws An error if teardown hooks are explicitly un-supported in this context. */ declare function useAnimation(callback: (now: number) => void): void; type WatchGuardCondition<T, R extends T> = (value: T) => value is R; type WatchCondition<T> = (value: T) => boolean; declare class WatchForTimeoutError extends Error { } /** * Utility to watch an expression until its output satisfies a condition. * * @param expr The expression to watch. * @param condition The condition to test. By default, all truthy values are matched. * @param timeout An optional timeout. Default is no timeout. * @returns A promise that resolves with the first matched output or rejects with a {@link WatchForTimeoutError}. */ declare function watchFor<T>(expr: Expression<T | Falsy>, timeout?: number): Promise<T>; declare function watchFor<T, R extends T>(expr: Expression<T>, condition?: WatchGuardCondition<T, R>, timeout?: number): Promise<R>; declare function watchFor<T>(expr: Expression<T>, condition?: WatchCondition<T>, timeout?: number): Promise<T>; export { ASYNC, Async, AsyncContext, AsyncError, Queue, TASKS, Tasks, WatchForTimeoutError, isPending, isSelfPending, nestAsync, useAbortController, useAbortSignal, useAnimation, useInterval, useMicrotask, useTimeout, watchFor }; export type { AsyncContextParent, TaskSource, TasksOptions, WatchCondition, WatchGuardCondition };