/** * Component types and render context definitions. * * @module bquery/component */ import type { SanitizeOptions } from '../security/types'; /** * Defines a single prop's type and configuration. * * @template T - The TypeScript type of the prop value * * @example * ```ts * const myProp: PropDefinition = { * type: Number, * required: false, * default: 0, * }; * ``` */ export type PropDefinition = { /** Constructor or converter function for the prop type */ type: | StringConstructor | NumberConstructor | BooleanConstructor | ObjectConstructor | ArrayConstructor | { new (value: unknown): T } | ((value: unknown) => T); /** Whether the prop must be provided */ required?: boolean; /** Default value when prop is not provided */ default?: T; /** Optional validator function to validate prop values */ validator?: (value: T) => boolean; /** * Explicitly control whether to invoke `type` with `new` (constructor) or as a plain function. * - `true`: Always use `new type(value)` (for class constructors, Date, etc.) * - `false`: Always call `type(value)` (for converter functions) * - `undefined` (default): Auto-detect based on heuristics with fallback */ construct?: boolean; }; /** * Resolves the concrete runtime state shape exposed by component APIs. * * When no explicit state generic is provided, component state falls back to * an untyped string-keyed record for backwards compatibility. */ export type ComponentStateShape | undefined = undefined> = TState extends Record ? TState : Record; /** * Component state keys are string-based because runtime state access is backed * by plain object properties. */ export type ComponentStateKey | undefined = undefined> = keyof ComponentStateShape & string; /** * Public component element instance shape exposed by lifecycle hooks and * `defineComponent()` return values. */ export type ComponentElement | undefined = undefined> = HTMLElement & { /** * Updates a state property and triggers a re-render. * * @param key - The state property key * @param value - The new value */ setState>( key: TKey, value: ComponentStateShape[TKey] ): void; /** * Gets a state property value. * * @param key - The state property key * @returns The current value */ getState>(key: TKey): ComponentStateShape[TKey]; /** * Gets a state property value with an explicit cast for backwards * compatibility with the pre-typed-state API. * * @param key - The state property key * @returns The current value cast to `TResult` */ getState(key: string): TResult; }; /** * Constructor returned by `defineComponent()`. */ export type ComponentClass | undefined = undefined> = CustomElementConstructor & { new (): ComponentElement; prototype: ComponentElement; readonly observedAttributes: string[]; }; /** * Minimal reactive source shape supported by component `signals`. * * @template T - Value exposed by the signal-like source */ export type ComponentSignalLike = { /** Gets the current reactive value */ readonly value: T; /** Gets the current value without dependency tracking */ peek(): T; }; /** * Named reactive sources that can drive component re-renders. */ export type ComponentSignals = Record>; /** * Render context passed into a component render function. * * @template TProps - Type of the component's props * @template TState - Type of the component's internal state * @template TSignals - Declared reactive sources available during render */ export type ComponentRenderContext< TProps extends Record, TState extends Record | undefined = undefined, TSignals extends ComponentSignals = Record, > = { /** Typed props object populated from attributes */ props: TProps; /** Internal mutable state object */ state: ComponentStateShape; /** External reactive sources subscribed for re-rendering */ signals: TSignals; /** Emit a custom event from the component */ emit: (event: string, detail?: unknown) => void; }; /** * Describes an observed attribute change that triggered a component update. */ export type AttributeChange = { /** The observed attribute name */ name: string; /** The previous serialized attribute value */ oldValue: string | null; /** The next serialized attribute value */ newValue: string | null; }; /** * Complete component definition including props, state, styles, and lifecycle. * * @template TProps - Type of the component's props * @template TState - Type of the component's internal state */ /* * Lifecycle hooks use dynamic `this` when declared with method/function syntax. * Arrow functions capture outer scope, so component APIs like `this.getState()` * are only available from method/function syntax. */ type ComponentSanitizeOptions = Pick; type ComponentHook< TState extends Record | undefined = undefined, TResult = void, > = { (this: ComponentElement): TResult; (): TResult; }; type ComponentHookWithProps< TProps extends Record, TState extends Record | undefined = undefined, TResult = void, > = { (this: ComponentElement, newProps: TProps, oldProps: TProps): TResult; (newProps: TProps, oldProps: TProps): TResult; }; type ComponentUpdatedHook< TState extends Record | undefined = undefined, TResult = void, > = { (this: ComponentElement, change?: AttributeChange): TResult; (change?: AttributeChange): TResult; }; type ComponentErrorHook | undefined = undefined> = { (this: ComponentElement, error: Error): void; (error: Error): void; }; type ComponentAttributeChangedHook | undefined = undefined> = { ( this: ComponentElement, name: string, oldValue: string | null, newValue: string | null ): void; (name: string, oldValue: string | null, newValue: string | null): void; }; type ComponentStateDefinition | undefined = undefined> = TState extends Record ? { /** Initial internal state */ state: TState; } : { /** Initial internal state */ state?: Record; }; type ComponentSignalsDefinition> = TSignals extends Record ? { /** External signals/computed values that should trigger re-renders */ signals?: TSignals; } : { /** External signals/computed values that should trigger re-renders */ signals: TSignals; }; /** * Controls Shadow DOM mode for the component. * * - `true` or `'open'` — attach an open shadow root (default) * - `'closed'` — attach a closed shadow root * - `false` — no shadow root; render directly into the host element */ export type ShadowMode = boolean | 'open' | 'closed'; export type ComponentDefinition< TProps extends Record = Record, TState extends Record | undefined = undefined, TSignals extends ComponentSignals = Record, > = ComponentStateDefinition & ComponentSignalsDefinition & { /** Prop definitions with types and defaults */ props?: Record; /** * CSS styles injected for the component. * * When `shadow` uses a shadow root (`true`, `'open'`, or `'closed'`), these * styles are scoped to that shadow tree. When `shadow` is `false`, the * generated `