import { CustomEventTarget } from "./event-target.js"; import { setProfiling } from "./_utils.js"; /** * A type which represents all valid values for an element tag. */ export type Tag = string | symbol | Component; /** * A helper type to map the tag of an element to its expected props. * * @template TTag - The tag associated with the props. Can be a string, symbol * or a component function. */ export type TagProps = TTag extends string ? JSX.IntrinsicElements[TTag] : TTag extends Component ? TProps & JSX.IntrinsicAttributes : Record & JSX.IntrinsicAttributes; /** * Describes all valid values of an element tree, excluding iterables. * * Arbitrary objects can also be safely rendered, but will be converted to a * string using the toString() method. We exclude them from this type to catch * potential mistakes. */ export type Child = Element | string | number | boolean | null | undefined; /** * An arbitrarily nested iterable of Child values. * * We use a recursive interface here rather than making the Children type * directly recursive because recursive type aliases were added in TypeScript * 3.7. * * You should avoid referencing this type directly, as it is mainly exported to * prevent TypeScript errors. */ export interface ChildIterable extends Iterable { } /** * Describes all valid values for an element tree, including arbitrarily nested * iterables of such values. * * This type can be used to represent the type of the children prop for an * element or the return/yield type of a component. */ export type Children = Child | ChildIterable; /** * Represents all functions which can be used as a component. * * @template [TProps=*] - The expected props for the component. */ export type Component = any> = (this: Context, props: TProps, ctx: Context) => Children | PromiseLike | Iterator | AsyncIterator; /*** SPECIAL TAGS ***/ /** * A special tag for grouping multiple children within the same parent. * * All non-string iterables which appear in the element tree are implicitly * wrapped in a fragment element. * * This tag is just the empty string, and you can use the empty string in * createElement calls or transpiler options directly to avoid having to * reference this export. */ export declare const Fragment = ""; export type Fragment = typeof Fragment; /** * A special tag for rendering into a new root node via a root prop. * * This tag is useful for creating element trees with multiple roots, for * things like modals or tooltips. * * Renderer.prototype.render() implicitly wraps top-level in a Portal element * with the root set to the second argument passed in. */ export declare const Portal: Component<{ root?: object; }> & symbol; export type Portal = typeof Portal; /** * A special tag which preserves whatever was previously rendered in the * element's position. * * Copy elements are useful for when you want to prevent a subtree from * rerendering as a performance optimization. Copy elements can also be keyed, * in which case the previously rendered keyed element will be copied. */ export declare const Copy: Component<{}> & symbol; export type Copy = typeof Copy; /** * A special tag for rendering text nodes. * * Strings in the element tree are implicitly wrapped in a Text element with * value set to the string. */ export declare const Text: Component<{ value: string; }> & symbol; export type Text = typeof Text; /** A special tag for injecting raw nodes or strings via a value prop. */ export declare const Raw: Component<{ value: string | object; }> & symbol; export type Raw = typeof Raw; type ChildrenIteratorResult = IteratorResult; declare const ElementSymbol: unique symbol; export interface Element { /** * @internal * A unique symbol to identify elements as elements across versions and * realms, and to protect against basic injection attacks. * https://overreacted.io/why-do-react-elements-have-typeof-property/ * * This property is defined on the element prototype rather than per * instance, because it is the same for every Element. */ $$typeof: typeof ElementSymbol; /** * The tag of the element. Can be a string, symbol or function. */ tag: TTag; /** * An object containing the "properties" of an element. These correspond to * the attribute syntax from JSX. */ props: TagProps; } /** * Elements are the basic building blocks of Crank applications. They are * JavaScript objects which are interpreted by special classes called renderers * to produce and manage stateful nodes. * * @template {Tag} [TTag=Tag] - The type of the tag of the element. * * @example * // specific element types * let div: Element<"div">; * let portal: Element; * let myEl: Element; * * // general element types * let host: Element; * let component: Element; * * Typically, you use a helper function like createElement to create elements * rather than instatiating this class directly. */ export declare class Element { constructor(tag: TTag, props: TagProps); } export declare function isElement(value: any): value is Element; /** * Creates an element with the specified tag, props and children. * * This function is usually used as a transpilation target for JSX transpilers, * but it can also be called directly. It additionally extracts special props so * they aren't accessible to renderer methods or components, and assigns the * children prop according to any additional arguments passed to the function. */ export declare function createElement(tag: TTag, props?: TagProps | null | undefined, ...children: Array): Element; /** Clones a given element, shallowly copying the props object. */ export declare function cloneElement(el: Element): Element; /** * A helper type which repesents all possible rendered values of an element. * * @template TNode - The type of node produced by the associated renderer. * * When asking the question, what is the "value" of a specific element, the * answer varies depending on the tag: * * For intrinsic elements, the value is the node created for the element, e.g. * the DOM node in the case of the DOMRenderer. * * For portals, the value is undefined, because a Portal element's root and * children are opaque to its parent. * * For component or fragment elements the value can be a node or an array of * nodes, depending on how many children they have. */ export type ElementValue = Array | TNode | undefined; /** * @internal * Retainers are objects which act as the internal representation of elements, * mirroring the element tree. */ declare class Retainer { /** A bitmask. See RETAINER FLAGS above. */ f: number; el: Element; ctx: ContextState | undefined; children: Array | undefined> | Retainer | undefined; fallback: Retainer | undefined; value: ElementValue | undefined; scope: TScope | undefined; oldProps: Record | undefined; pendingDiff: Promise | undefined; onNextDiff: Function | undefined; graveyard: Array> | undefined; lingerers: Array> | undefined> | undefined; constructor(el: Element); } /** * Interface for adapting the rendering process to a specific target environment. * * The RenderAdapter defines how Crank elements are mapped to nodes in your target * rendering environment (DOM, Canvas, WebGL, Terminal, etc.). Each method handles * a specific part of the element lifecycle, from creation to removal. * * @template TNode - The type representing a node in your target environment * @template TScope - Additional context data passed down the component tree * @template TRoot - The type of the root container (defaults to TNode) * @template TResult - The type returned when reading element values (defaults to ElementValue) * * @example * ```typescript * const adapter: RenderAdapter = { * create: ({ tag, props }) => new MyNode(tag, props), * patch: ({ node, props }) => node.update(props), * arrange: ({ node, children }) => node.replaceChildren(children), * // ... other methods * }; * ``` */ export interface RenderAdapter> { /** * Creates a new node for the given element tag and props. * * This method is called when Crank encounters a new element that needs to be * rendered for the first time. You should create and return a node appropriate * for your target environment. * * @param data.tag - The element tag (e.g., "div", "sprite", or a symbol) * @param data.tagName - String representation of the tag for debugging * @param data.props - The element's props object * @param data.scope - Current scope context (can be undefined) * @returns A new node instance * * @example * ```typescript * create: ({ tag, props, scope }) => { * if (tag === "sprite") { * return new PIXI.Sprite(props.texture); * } * throw new Error(`Unknown tag: ${tag}`); * } * ``` */ create(data: { tag: string | symbol; tagName: string; props: Record; scope: TScope | undefined; root: TRoot | undefined; }): TNode; /** * Adopts existing nodes during hydration. * * Called when hydrating server-rendered content or reusing existing nodes. * Should return an array of child nodes if the provided node matches the * expected tag, or undefined if hydration should fail. * * @param data.tag - The element tag being hydrated * @param data.tagName - String representation of the tag * @param data.props - The element's props * @param data.node - The existing node to potentially adopt * @param data.scope - Current scope context * @returns Array of child nodes to hydrate, or undefined if adoption fails * * @example * ```typescript * adopt: ({ tag, node }) => { * if (node && node.tagName.toLowerCase() === tag) { * return Array.from(node.children); * } * return undefined; // Hydration mismatch * } * ``` */ adopt(data: { tag: string | symbol; tagName: string; props: Record; node: TNode | undefined; scope: TScope | undefined; root: TRoot | undefined; }): Array | undefined; /** * Creates or updates a text node. * * Called when rendering text content. Should create a new text node or * update an existing one with the provided value. * * @param data.value - The text content to render * @param data.scope - Current scope context * @param data.oldNode - Previous text node to potentially reuse * @param data.hydrationNodes - Nodes available during hydration * @returns A text node containing the given value * * @example * ```typescript * text: ({ value, oldNode }) => { * if (oldNode && oldNode.text !== value) { * oldNode.text = value; * return oldNode; * } * return new TextNode(value); * } * ``` */ text(data: { value: string; scope: TScope | undefined; oldNode: TNode | undefined; hydrationNodes: Array | undefined; root: TRoot | undefined; }): TNode; /** * Computes scope context for child elements. * * Called to determine what scope context should be passed to child elements. * The scope can be used to pass rendering context like theme, coordinate systems, * or namespaces down the component tree. * * @param data.tag - The element tag * @param data.tagName - String representation of the tag * @param data.props - The element's props * @param data.scope - Current scope context * @returns New scope for children, or undefined to inherit current scope * * @example * ```typescript * scope: ({ tag, props, scope }) => { * if (tag === "svg") { * return { ...scope, namespace: "http://www.w3.org/2000/svg" }; * } * return scope; * } * ``` */ scope(data: { tag: string | symbol; tagName: string; props: Record; scope: TScope | undefined; root: TRoot | undefined; }): TScope | undefined; /** * Handles raw values (strings or nodes) that bypass normal element processing. * * Called when rendering Raw elements or other direct node insertions. * Should convert string values to appropriate nodes for your environment. * * @param data.value - Raw string or node value to render * @param data.scope - Current scope context * @param data.hydrationNodes - Nodes available during hydration * @returns ElementValue that can be handled by arrange() * * @example * ```typescript * raw: ({ value, scope }) => { * if (typeof value === "string") { * const container = new Container(); * container.innerHTML = value; * return Array.from(container.children); * } * return value; * } * ``` */ raw(data: { value: string | TNode; scope: TScope | undefined; hydrationNodes: Array | undefined; root: TRoot | undefined; }): ElementValue; /** * Updates a node's properties. * * Called when element props change. Should efficiently update only the * properties that have changed. This is where you implement prop-to-attribute * mapping, event listener binding, and other property synchronization. * * @param data.tag - The element tag * @param data.tagName - String representation of the tag * @param data.node - The node to update * @param data.props - New props object * @param data.oldProps - Previous props object (undefined for initial render) * @param data.scope - Current scope context * @param data.copyProps - Props to skip (used for copying between renderers) * @param data.isHydrating - Whether currently hydrating * @param data.quietProps - Props to not warn about during hydration * * @example * ```typescript * patch: ({ node, props, oldProps }) => { * for (const [key, value] of Object.entries(props)) { * if (oldProps?.[key] !== value) { * if (key.startsWith("on")) { * node.addEventListener(key.slice(2), value); * } else { * node[key] = value; * } * } * } * } * ``` */ patch(data: { tag: string | symbol; tagName: string; node: TNode; props: Record; oldProps: Record | undefined; scope: TScope | undefined; root: TRoot | undefined; copyProps: Set | undefined; isHydrating: boolean; quietProps: Set | undefined; }): void; /** * Arranges child nodes within their parent. * * Called after child elements are rendered to organize them within their * parent node. Should efficiently insert, move, or remove child nodes to * match the provided children array. * * @param data.tag - The parent element tag * @param data.tagName - String representation of the tag * @param data.node - The parent node * @param data.props - The parent element's props * @param data.children - Array of child nodes in correct order * @param data.oldProps - Previous props (for reference) * * @example * ```typescript * arrange: ({ node, children }) => { * // Remove existing children * node.removeChildren(); * // Add new children in order * for (const child of children) { * node.addChild(child); * } * } * ``` */ arrange(data: { tag: string | symbol; tagName: string; node: TNode; props: Record; children: Array; oldProps: Record | undefined; scope: TScope | undefined; root: TRoot | undefined; }): void; /** * Removes a node from its parent. * * Called when an element is being unmounted. Should clean up the node * and remove it from its parent if appropriate. * * @param data.node - The node to remove * @param data.parentNode - The parent node * @param data.isNested - Whether this is a nested removal (child of removed element) * * @example * ```typescript * remove: ({ node, parentNode, isNested }) => { * // Clean up event listeners, resources, etc. * node.cleanup?.(); * // Remove from parent unless it's a nested removal * if (!isNested && parentNode.contains(node)) { * parentNode.removeChild(node); * } * } * ``` */ remove(data: { node: TNode; parentNode: TNode; isNested: boolean; root: TRoot | undefined; }): void; /** * Reads the final rendered value from an ElementValue. * * Called to extract the final result from rendered elements. This allows * you to transform the internal node representation into the public API * that users of your renderer will see. * * @param value - The ElementValue to read (array, single node, or undefined) * @returns The public representation of the rendered value * * @example * ```typescript * read: (value) => { * if (Array.isArray(value)) { * return value.map(node => node.publicAPI); * } * return value?.publicAPI; * } * ``` */ read(value: ElementValue): TResult; /** * Performs final rendering to the root container. * * Called after the entire render cycle is complete. This is where you * trigger the actual rendering/presentation in your target environment * (e.g., calling render() on a canvas, flushing to the screen, etc.). * * @param root - The root container * * @example * ```typescript * finalize: (root) => { * // Trigger actual rendering * if (root instanceof PIXIApplication) { * root.render(); * } * } * ``` */ finalize(root: TRoot): void; } /** * An abstract class which is subclassed to render to different target * environments. Subclasses call super() with a custom RenderAdapter object. * This class is responsible for kicking off the rendering process and caching * previous trees by root. * * @template TNode - The type of the node for a rendering environment. * @template TScope - Data which is passed down the tree. * @template TRoot - The type of the root for a rendering environment. * @template TResult - The type of exposed values. */ export declare class Renderer> { /** * @internal * A weakmap which stores element trees by root. */ cache: WeakMap>; adapter: RenderAdapter; constructor(adapter: Partial>); /** * Renders an element tree into a specific root. * * @param children - An element tree. Rendering null deletes cached renders. * @param root - The root to be rendered into. The renderer caches renders * per root. * @param bridge - An optional context that will be the ancestor context of * all elements in the tree. Useful for connecting different renderers so * that events/provisions/errors properly propagate. The context for a given * root must be the same between renders. * * @returns The result of rendering the children, or a possible promise of * the result if the element tree renders asynchronously. */ render(children: Children, root?: TRoot | undefined, bridge?: Context | undefined): Promise | TResult; hydrate(children: Children, root: TRoot, bridge?: Context | undefined): Promise | TResult; } interface PullController { iterationP: Promise | undefined; diff: Promise | undefined; onChildError: ((err: unknown) => void) | undefined; } interface ScheduleController { promise: Promise; onAbort: () => void; } /** * @internal * The internal class which holds context data. */ declare class ContextState { /** The adapter of the renderer which created this context. */ adapter: RenderAdapter; /** The root node as set by the nearest ancestor portal. */ root: TRoot | undefined; /** * The nearest ancestor host or portal retainer. * * When refresh is called, the host element will be arranged as the last step * of the commit, to make sure the parent's children properly reflects the * components's childrenk */ host: Retainer; /** The parent context state. */ parent: ContextState | undefined; /** The actual context associated with this state. */ ctx: Context; /** The value of the scope at the point of element's creation. */ scope: TScope | undefined; /** The internal node associated with this context. */ ret: Retainer; /** * Any iterator returned by a component function. * * Existence of this property implies that the component is a generator * component. It is deleted when a component is returned. */ iterator: Iterator | AsyncIterator | undefined; inflight: [Promise, Promise] | undefined; enqueued: [Promise, Promise] | undefined; pull: PullController | undefined; onPropsProvided: ((props: unknown) => unknown) | undefined; onPropsRequested: (() => unknown) | undefined; index: number; schedule: ScheduleController | undefined; constructor(adapter: RenderAdapter, root: TRoot, host: Retainer, parent: ContextState | undefined, scope: TScope | undefined, ret: Retainer); } export type ComponentProps = T extends () => unknown ? {} : T extends (props: infer U) => unknown ? U : never; export type ComponentPropsOrProps = T extends Function ? ComponentProps : T; declare const _ContextState: unique symbol; /** * A class which is instantiated and passed to every component as its this * value/second parameter. Contexts form a tree just like elements and all * components in the element tree are connected via contexts. Components can * use this tree to communicate data upwards via events and downwards via * provisions. * * @template [T=*] - The expected shape of the props passed to the component, * or a component function. Used to strongly type the Context iterator methods. * @template [TResult=*] - The readable element value type. It is used in * places such as the return value of refresh and the argument passed to * schedule and cleanup callbacks. */ export declare class Context extends CustomEventTarget { /** * @internal * DO NOT USE READ THIS PROPERTY. */ [_ContextState]: ContextState; constructor(state: ContextState); /** * The current props of the associated element. */ get props(): ComponentPropsOrProps; /** * The current value of the associated element. * * @deprecated */ get value(): TResult; get isExecuting(): boolean; get isUnmounted(): boolean; [Symbol.iterator](): Generator, undefined>; [Symbol.asyncIterator](): AsyncGenerator, undefined>; /** * Re-executes a component. * * @param callback - Optional callback to execute before refresh * @returns The rendered result of the component or a promise thereof if the * component or its children execute asynchronously. */ refresh(callback?: () => unknown): Promise | TResult; /** * Registers a callback which fires when the component's children are * created. Will only fire once per callback and update. */ schedule(): Promise; schedule(callback: (value: TResult) => unknown): void; /** * Registers a callback which fires when the component's children are fully * rendered. Will only fire once per callback and update. */ after(): Promise; after(callback: (value: TResult) => unknown): void; /** * @deprecated the flush() method has been renamed to after(). */ flush(): Promise; flush(callback: (value: TResult) => unknown): void; /** * Registers a callback which fires when the component unmounts. * * The callback can be async to defer the unmounting of a component's children. */ cleanup(): Promise; cleanup(callback: (value: TResult) => unknown): void; consume(key: TKey): ProvisionMap[TKey]; consume(key: unknown): any; provide(key: TKey, value: ProvisionMap[TKey]): void; provide(key: unknown, value: any): void; [CustomEventTarget.dispatchEventOnSelf](ev: Event): void; } /** * An interface which can be extended to provide strongly typed provisions. * See Context.prototype.consume and Context.prototype.provide. */ export interface ProvisionMap extends Crank.ProvisionMap { } export interface EventMap extends Crank.EventMap { } type MappedEventListener = (ev: Crank.EventMap[T]) => unknown; type MappedEventListenerOrEventListenerObject = MappedEventListener | { handleEvent: MappedEventListener; }; export interface Context extends Crank.Context { addEventListener(type: T, listener: MappedEventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void; removeEventListener(type: T, listener: MappedEventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; dispatchEvent(ev: EventMap[T] | Event): boolean; } declare global { namespace Crank { interface EventMap { [tag: string]: Event; } interface ProvisionMap { } interface Context { } } namespace JSX { interface IntrinsicElements { [tag: string]: any; } interface IntrinsicAttributes { children?: unknown; key?: unknown; ref?: unknown; copy?: unknown; hydrate?: unknown; } interface ElementChildrenAttribute { children: {}; } } } /** * A re-export of some Crank exports as the default export. * * Some JSX tools expect things like createElement/Fragment to be defined on * the default export. Prefer using the named exports directly. */ export { setProfiling }; declare const _default: { createElement: typeof createElement; Fragment: string; }; export default _default;