import { RenderableChild } from './core.js'; import { JSXElement } from './elements.js'; /** * Layout helper. * * A layout is just a normal component that wraps children. * Persistence and reuse are handled by the runtime via component identity. * * This helper exists purely for readability and convention. * * POLICY DECISIONS (LOCKED): * * 1. Return Type is Opaque (unknown) * Layout components return `unknown` to remain runtime-agnostic. * The runtime owns concrete JSX element types. * * 2. Children Positioning * Layout receives children as first argument (router-friendly). * Props come second. This matches route layout conventions where * children represent the nested route content. * * 3. Props Spreading * Props are spread into the layout component. This is intentional * and deterministic — no merging or composition. */ /** A component that receives its route children via `props.children`. */ type LayoutComponent

= ( props: P & { children?: RenderableChild; } ) => unknown; /** * Wrap a {@link LayoutComponent} so it can be invoked as `(children, props)`, * matching route layout conventions. */ declare function layout

( Layout: LayoutComponent

): (children?: RenderableChild, props?: P) => unknown; /** Check whether `value` is a JSX element vnode. */ declare function isElement(value: unknown): value is JSXElement; /** Clone a JSX element, shallow-merging `props` over its existing props. */ declare function cloneElement( element: JSXElement, props: Record ): JSXElement; /** Props for {@link Slot}: `asChild` selects prop-merging vs. fragment mode. */ type SlotProps = | { asChild: true; children: JSXElement; [key: string]: unknown; } | { asChild?: false; children?: RenderableChild; }; /** * Slot * * Structural primitive for prop forwarding patterns. * * POLICY DECISIONS (LOCKED): * * 1. asChild Pattern * When asChild=true, merges props into the single child element. * Child must be a valid JSXElement; non-element children return null. * **Slot props override child props** (injection pattern). * * 2. Fallback Behavior * When asChild=false, returns a Fragment (structural no-op). * No DOM element is introduced. * * 3. Type Safety * asChild=true requires exactly one JSXElement child (enforced by type). * Runtime validates with isElement() check. */ declare function Slot(props: SlotProps): JSXElement | null; /** Props for {@link Presence}. */ interface PresenceProps { /** Whether the children should be mounted, or a function returning it. */ present: boolean | (() => boolean); children?: RenderableChild; } /** * Presence * * Structural policy primitive for conditional mount/unmount. * - No timers * - No animation coupling * - No DOM side-effects * * POLICY DECISIONS (LOCKED): * * 1. Present as Function * Accepts boolean OR function to support lazy evaluation patterns. * Function is called once per render. Use boolean form for static values. * * 2. Children Type * Presence forwards normal renderable child content only. * Imperative DOM nodes are not part of the public contract. * * 3. Immediate Mount/Unmount * No exit animations or transitions. When `present` becomes false, * children are removed immediately. Animation must be layered above * this primitive. */ declare function Presence({ present, children, }: PresenceProps): JSXElement | null; /** * A named portal channel created by {@link definePortal}: call it as a * component to render the host, and call `.render(props)` to write content. */ interface Portal { (): T | JSXElement | null | undefined; render(props: { children?: T }): unknown; } /** Props for the {@link Portal} component. */ interface PortalProps { children?: RenderableChild; } /** Create a new named {@link Portal} channel with its own host and content. */ declare function definePortal< T extends RenderableChild = RenderableChild, >(): Portal; /** * The implicit portal channel that {@link Portal} writes to and that any * host rendered without an explicit portal falls back to. */ declare const DefaultPortal: Portal; /** Write children to the {@link DefaultPortal} host wherever it is rendered. */ declare function Portal(props: PortalProps): JSXElement | null; /** * createCollection * * Ordered descendant registry for coordinating items without DOM queries. * * INVARIANTS: * 1. Registration order determines item order (no DOM queries) * 2. Stable ordering across renders (insertion order preserved) * 3. Each item may have metadata (type-safe, user-defined) * 4. No implicit global state (explicit collection instances) * 5. No automatic cleanup (caller controls lifecycle) * * DESIGN: * - Returns a registry API ({ register, items, clear }) * - Items are stored in insertion order * - Registration returns an unregister function * - No side effects on registration (pure data structure) * * USAGE: * const collection = createCollection(); * const unregister = collection.register(element, { disabled: false }); * const allItems = collection.items(); * unregister(); */ /** A registered node paired with its metadata inside a {@link Collection}. */ type CollectionItem = { node: TNode; metadata: TMetadata; }; /** Ordered descendant registry returned by {@link createCollection}. */ interface Collection { /** * Register a node with optional metadata. * Returns an unregister function. */ register(node: TNode, metadata: TMetadata): () => void; /** * Get all registered items in insertion order. */ items(): ReadonlyArray>; /** * Clear all registered items. */ clear(): void; /** * Get the count of registered items. */ size(): number; } /** Create an empty, insertion-ordered {@link Collection} registry. */ declare function createCollection(): Collection< TNode, TMetadata >; /** * USAGE EXAMPLE: * * // Create a collection for menu items * const menuItems = createCollection(); * * // Register items * const unregister1 = menuItems.register(element1, { disabled: false }); * const unregister2 = menuItems.register(element2, { disabled: true }); * * // Query items * const allItems = menuItems.items(); * const enabledItems = allItems.filter(item => !item.metadata.disabled); * * // Cleanup * unregister1(); * unregister2(); */ /** * createLayer * * Manages stacking order and coordination for overlays (modals, popovers, etc). * * INVARIANTS: * 1. Layers are ordered by registration time (FIFO) * 2. Only the top layer handles Escape key * 3. Only the top layer handles outside pointer events * 4. Nested layers are supported * 5. Does not implement portals (orthogonal concern) * 6. No automatic DOM insertion (caller controls mounting) * * DESIGN: * - Returns a layer manager with register/unregister API * - Each layer has a unique ID and can query if it's the top layer * - Escape and outside pointer coordination via callbacks * - No z-index management (CSS concern) * * USAGE: * const manager = createLayer(); * * const layer = manager.register({ * onEscape: () => { ... }, * onOutsidePointer: () => { ... } * }); * * layer.isTop(); // true if this is the topmost layer * layer.unregister(); */ /** Options for registering a layer with {@link LayerManager.register}. */ interface LayerOptions { /** * Called when Escape is pressed and this is the top layer */ onEscape?: () => void; /** * Called when pointer event occurs outside and this is the top layer */ onOutsidePointer?: (e: PointerEvent) => void; /** * Optional node reference for outside pointer detection */ node?: Node | null; } /** A registered layer entry returned by {@link LayerManager.register}. */ interface Layer { /** * Unique layer ID */ id: number; /** * Check if this layer is the topmost */ isTop(): boolean; /** * Remove this layer from the stack */ unregister(): void; } /** Stacking coordinator returned by {@link createLayer}. */ interface LayerManager { /** * Register a new layer */ register(options: LayerOptions): Layer; /** * Get all active layers in order */ layers(): ReadonlyArray; /** * Manually trigger escape handling on the top layer */ handleEscape(): void; /** * Manually trigger outside pointer handling on the top layer */ handleOutsidePointer(e: PointerEvent): void; } /** Create a new, empty {@link LayerManager} for coordinating overlay stacking. */ declare function createLayer(): LayerManager; /** * USAGE EXAMPLE: * * const layerManager = createLayer(); * * function Modal({ onClose }) { * const modalRef = ref(); * * const layer = layerManager.register({ * node: modalRef.current, * onEscape: onClose, * onOutsidePointer: onClose, * }); * * onUnmount(() => layer.unregister()); * * return ( *

*

Modal

* {layer.isTop() &&

I am on top!

} *
* ); * } */ export { isElement, Collection, DefaultPortal, definePortal, Presence, cloneElement, SlotProps, createLayer, Portal, Slot, LayerManager, CollectionItem, PresenceProps, LayerOptions, createCollection, Layer, PortalProps, LayoutComponent, layout, };