rvx
Version:
A signal based rendering library
324 lines (313 loc) • 11.7 kB
TypeScript
/*!
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 };