/** * A single CSS class value. Falsy variants (`undefined` and `null`) are * automatically filtered out when building a class list, so conditional * classes can be expressed without explicit guards. */ type CssClass = string | undefined | null; /** * One or more CSS class values accepted by the `classes` prop of every HTML * element created via `create()` or `append()`. * * - A single string is used as-is. * - An array of {@link CssClass} values is joined with spaces after falsy * entries are removed. * * @see {@link cssClass} for composing conditional class names. */ type CssClasses = Array | CssClass; /** * Returns `className` when `visible` is truthy, otherwise `undefined`. * * Designed to compose cleanly with {@link CssClasses}: falsy values are * filtered out automatically, so static and conditional classes can be mixed * in a single array without branching. * * @param className - The CSS class to apply. * @param visible - When `true` (the default) the class is returned; any other * value produces `undefined`. * * @example * ```ts * append('button', { * classes: [ * 'btn', * cssClass('btn--active', isActive), * cssClass('btn--disabled', isDisabled), * ], * }) * ``` */ declare function cssClass(className: string | CssClass, visible?: boolean | null | undefined): CssClass; /** * Well-known symbol used as a non-enumerable property key on every * {@link CssModule} to store the public URLs of the pre-scoped CSS artifacts. */ declare const cssArtifacts: unique symbol; /** Public URLs of the two pre-scoped CSS artifacts emitted by the CSS loader plugin. */ type CssArtifacts = { /** Public URL of the `@scope`-wrapped CSS artifact. */ readonly scoped: string; /** Public URL of the attribute-selector-wrapped CSS artifact (fallback). */ readonly tagged: string; /** The `r` attribute value used to scope CSS to this component's element. */ readonly scopeId: string; }; /** * The type returned when importing a `.css` file through the rooted CSS loader plugin. * * @example * ```ts * import styles from './component.css' * * styles.myClass // 'my-class' (typed as CssClass) * styles['my-class'] // 'my-class' (typed as CssClass) * ``` */ type CssModule = Record & { readonly [cssArtifacts]: CssArtifacts; }; /** * Constructor shape expected by {@link RootedElement.register}. * * Any class that extends {@link RootedElement} automatically satisfies this * type as long as it declares a `static tagName` property. */ type RootedElementConstructor = CustomElementConstructor & { tagName: string; }; /** * Abstract base class for native custom elements in a rooted application. * * Extend this class when you need to create a reusable, low-level HTML element * (rather than a higher-level functional {@link Component}). `RootedElement` * wraps the standard custom-element lifecycle to: * * - Guard against spurious `connectedCallback` / `disconnectedCallback` calls * caused by DOM re-parenting (both callbacks are deferred with `queueMicrotask` * and only fire when the element's connection state has actually changed). * - Enforce valid, hyphenated custom-element tag names at registration time. * * @example Defining and registering a custom element * ```ts * import { RootedElement } from '@rooted/components/elements' * * export class MyCounter extends RootedElement { * static tagName = 'my-counter' * * protected onMount() { * this.textContent = 'mounted' * } * * protected onUnmount() { * this.textContent = '' * } * } * * RootedElement.register(MyCounter) * ``` * * @example Using a custom element inside a component * ```ts * import { component } from '@rooted/components' * import { MyCounter } from './my-counter.mts' * * export const Page = component({ * name: 'page', * onMount({ append }) { * append(MyCounter, {}) * } * }) * ``` */ declare abstract class RootedElement extends HTMLElement { static rootedElement: boolean; /** * Validates that `name` is a legal custom-element tag name. * * Throws if the name: * - Does not match `[a-z][a-z0-9\-]*` * - Does not contain at least one hyphen (required by the custom-elements spec) * * @param name - The tag name to validate. * @throws {Error} When the name is invalid. */ static validateTagName(name: string): void; /** * Validates the element's `tagName` and registers it with * `customElements.define`. * * Call this once per element class, typically at module level after the * class definition. * * @param element - The element class to register. * @throws {Error} When `element.tagName` is not a valid custom-element name. */ static register(element: TElement): void; /** * Called once after the element is connected to the document. * * Implement your DOM setup logic here: create child nodes, attach event * listeners, start timers, etc. * * @remarks * Deferred via `queueMicrotask` and guarded so it only fires when the * element is truly connected — not when it is being re-parented. */ protected abstract onMount(): void; /** * Called once after the element is disconnected from the document. * * Override to clean up resources created in {@link onMount} (event * listeners, subscriptions, timers, etc.). The default implementation is * a no-op. * * @remarks * Deferred via `queueMicrotask` and guarded so it only fires when the * element is truly disconnected — not when it is being re-parented. */ protected onUnmount(): void; connectedCallback(): void; disconnectedCallback(): void; } type RootedElementClass = (new () => TComponent) & RootedElementConstructor; type IfEquals = (() => T extends X ? 1 : 2) extends (() => T extends Y ? 1 : 2) ? A : B; type WritableKeys = { [P in keyof T]-?: IfEquals<{ [Q in P]: T[P]; }, { -readonly [Q in P]: T[P]; }, P, never>; }[keyof T]; type RootedElementProperties = Pick & Exclude>; type HtmlElementProperties = Partial & Exclude>> & { children?: Array | Node; classes?: CssClasses; }; type RequiredKeys = { [K in keyof T]-?: {} extends Pick ? never : K; }[keyof T]; type NoRequiredProperties = RequiredKeys extends never ? true : false; /** * Creates a new DOM node — either a rooted {@link Component}, a native * {@link RootedElement} subclass, or a standard HTML element. * * The node is **not** appended to the document; use * {@link ComponentContext.append} to create-and-append in one step. * * **`classes`** — use the `classes` prop to set CSS classes on HTML elements. * Accepts a single class string or a {@link CssClasses} array; falsy entries * are filtered out automatically. Use {@link cssClass} for conditional classes: * ```ts * import { cssClass } from '@rooted/components' * append('button', { classes: ['btn', cssClass('btn--active', isActive)] }) * ``` * * **DOM property names** — other properties are set via `Object.assign` and * must use DOM property names, not HTML attribute names: * | HTML attribute | DOM property | * |---------------|--------------| * | `for` | `htmlFor` | * | `readonly` | `readOnly` | * * **Children** — pass a single `Node` or an array of `Node`s via the * `children` property; they are appended in order. * * **Event listeners** — attach them with `addEventListener` after creation, or * use the `signal` from {@link ComponentContext} for automatic cleanup on * unmount. * * @example Creating a component * ```ts * const el = create(MyComponent) * const elWithOptions = create(MyComponent, { label: 'hello' }) * ``` * * @example Creating an HTML element * ```ts * const div = create('div', { * classes: 'card', * children: [ * create('h2', { textContent: 'Title' }), * create('p', { textContent: 'Body' }), * ], * }) * ``` */ declare function create(component: Component): GenericComponent; declare function create(component: Component, ...arguments_: {} extends TOptions ? [options?: TOptions] : [options: TOptions]): GenericComponent; declare function create(component: RootedElementClass, properties: NoInfer>): TComponent; declare function create(element: KElement, properties: NoInfer>): HTMLElementTagNameMap[KElement]; declare function create(element: KElement): NoRequiredProperties> extends true ? HTMLElementTagNameMap[KElement] : never; /** * ## `ComponentContext` * * Properties for internal component logic */ type ComponentContext = [TOptions] extends [never] ? BaseComponentContext : BaseComponentContext & { options: Readonly; }; type BaseComponentContext = { /** * Creates a new DOM node — either a rooted {@link Component}, a native * {@link RootedElement} subclass, or a standard HTML element. * * The node is **not** appended to the document; use * {@link ComponentContext.append} to create-and-append in one step. * * **`classes`** — use the `classes` prop to set CSS classes on HTML elements. * Accepts a single class string or a {@link CssClasses} array; falsy entries * are filtered out automatically. Use {@link cssClass} for conditional classes: * ```ts * import { cssClass } from '@rooted/components' * append('button', { classes: ['btn', cssClass('btn--active', isActive)] }) * ``` * * **DOM property names** — other properties are set via `Object.assign` and * must use DOM property names, not HTML attribute names: * | HTML attribute | DOM property | * |---------------|--------------| * | `for` | `htmlFor` | * | `readonly` | `readOnly` | * * **Children** — pass a single `Node` or an array of `Node`s via the * `children` property; they are appended in order. * * **Event listeners** — attach them with `addEventListener` after creation, or * use the `signal` from {@link ComponentContext} for automatic cleanup on * unmount. * * @example Creating a component * ```ts * const el = create(MyComponent) * const elWithOptions = create(MyComponent, { label: 'hello' }) * ``` * * @example Creating an HTML element * ```ts * const div = create('div', { * classes: 'card', * children: [ * create('h2', { textContent: 'Title' }), * create('p', { textContent: 'Body' }), * ], * }) * ``` */ create: typeof create; /** * Inserts nodes after the last child of node, while replacing strings in nodes with equivalent Text nodes. * * Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/append) */ append: { (node: T): T; (...nodes: T[]): T[]; (...nodes: (Node | string | GenericComponent)[]): Node[]; }; /** * Inserts nodes before the first child of node, while replacing strings in nodes with equivalent Text nodes. * * Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/prepend) */ prepend: { (node: T): T; (...nodes: T[]): T[]; (...nodes: (Node | string | GenericComponent)[]): Node[]; }; /** * The **`insertBefore()`** method of the Node interface inserts a node before a _reference node_ as a child of a specified _parent node_. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/insertBefore) */ insertBefore: (node: T, child: Node | null | undefined) => T; /** * The **`replaceChild()`** method of the Node interface replaces a child node within the given (parent) node. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/replaceChild) */ swap: (node: Node, child: T) => T; /** * Replace all children of node with nodes, while replacing strings in nodes with equivalent Text nodes. * * Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/replaceChildren) */ replace: { (node: T): T; (...nodes: T[]): T[]; (...nodes: (Node | string | GenericComponent)[]): Node[]; }; /** * The **`removeChild()`** method of the Node interface removes a child node from the DOM and returns the removed node. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/removeChild) */ remove: { (node: T): T; (...nodes: T[]): T[]; (...nodes: (Node | string | GenericComponent)[]): Node[]; }; /** * Lifetime signal for the component, aborts when unmounted \ * automatically aborts when page unloads */ signal: AbortSignal; }; declare const componentBrand: unique symbol; declare const definedAt: unique symbol; /** * A rooted component — a {@link ComponentConstructor} enriched with an internal * brand symbol so the runtime can identify it and wrap it in a `` custom * element. * * Create components with the {@link component} factory rather than * constructing this type directly. * * @typeParam TOptions - The options type the component expects when mounted. * Use `never` (default) for components that take no external options. */ type Component = ComponentConstructor & { readonly [componentBrand]: TOptions; }; /** * ## `ComponentConstructor` * * Define a new component * * @example * ```ts * import styles from './example.css' * * export const Example = component({ * name: 'example', * styles, * onMount({ append, create }) { * append( * create('p', { * classes: styles.message, * textContent: 'This is just an example' * }) * ) * } * }) * ``` * * @remarks * The onMount signature has a typed `this` in scope. \ * This is by design, offering you the option to destructure the context * but also using `this` if necessary */ type ComponentConstructor = { /** * Name of the component. * * Must be * - Html-valid, `[a-z][a-z0-9\-]*` * - Unique across the application. \ * Duplicate names will result in duplicate style injection. */ name: string; /** * CSS for this component, provided as a {@link CssModule} * imported from a `.css` file via the rooted CSS loader Vite plugin. */ styles?: CssModule; /** Custom component constructor */ onMount(context: ComponentContext): void | Promise; [definedAt]?: string; }; /** * ## Create a new `component` * * @example * ```ts * import styles from './example.css' * * export const Example = component({ * name: 'example', * styles, * onMount({ append, create }) { * append( * create('p', { * classes: styles.message, * textContent: 'This is just an example' * }) * ) * } * }) * ``` */ declare function component(constructor: ComponentConstructor): Component; declare function component(constructor: ComponentConstructor): Component; /** * The internal custom element that wraps every functional {@link Component}. * * When you call `create(MyComponent)`, rooted creates an instance of * `GenericComponent` (tag name `` in production, `` * in development) and stores the component constructor and options in a private * `WeakMap`. On `connectedCallback` the component's `onMount` is invoked with * a fully typed {@link ComponentContext}. * * You will encounter this type in TypeScript signatures — for example, * `create(MyComponent)` returns `GenericComponent` — but you should not * instantiate or subclass it directly. Always use {@link component} and * {@link create} instead. * * In development the element also exposes `component`, `options`, and * `definedAt` as direct properties so they are visible in browser DevTools. * These properties are absent in production builds. * * @see {@link component} * @see {@link create} * @see {@link componentStore} */ declare class GenericComponent extends RootedElement { static tagName: string; private abortController; protected onMount(): void; protected onUnmount(): void; } export { type Component, type ComponentConstructor, type ComponentContext, type CssArtifacts, type CssClass, type CssClasses, type CssModule, GenericComponent, RootedElement as R, type RootedElementConstructor as a, create as c, component, cssArtifacts, cssClass };