UNPKG

watch-selector

Version:

Runs a function when a selector is added to dom

662 lines (578 loc) 27.8 kB
// Core types for Watch v5 // Element type inference from selectors export type ElementFromSelector<S extends string> = S extends `input[type="text"]${string}` ? HTMLInputElement : S extends `input[type="email"]${string}` ? HTMLInputElement : S extends `input[type="password"]${string}` ? HTMLInputElement : S extends `input[type="search"]${string}` ? HTMLInputElement : S extends `input[type="tel"]${string}` ? HTMLInputElement : S extends `input[type="url"]${string}` ? HTMLInputElement : S extends `input[type="number"]${string}` ? HTMLInputElement : S extends `input[type="range"]${string}` ? HTMLInputElement : S extends `input[type="date"]${string}` ? HTMLInputElement : S extends `input[type="time"]${string}` ? HTMLInputElement : S extends `input[type="datetime-local"]${string}` ? HTMLInputElement : S extends `input[type="month"]${string}` ? HTMLInputElement : S extends `input[type="week"]${string}` ? HTMLInputElement : S extends `input[type="color"]${string}` ? HTMLInputElement : S extends `input[type="file"]${string}` ? HTMLInputElement : S extends `input[type="hidden"]${string}` ? HTMLInputElement : S extends `input[type="checkbox"]${string}` ? HTMLInputElement : S extends `input[type="radio"]${string}` ? HTMLInputElement : S extends `input[type="submit"]${string}` ? HTMLInputElement : S extends `input[type="reset"]${string}` ? HTMLInputElement : S extends `input[type="button"]${string}` ? HTMLInputElement : S extends `input[type="image"]${string}` ? HTMLInputElement : S extends `input${string}` ? HTMLInputElement : S extends `button${string}` ? HTMLButtonElement : S extends `form${string}` ? HTMLFormElement : S extends `a${string}` ? HTMLAnchorElement : S extends `img${string}` ? HTMLImageElement : S extends `select${string}` ? HTMLSelectElement : S extends `textarea${string}` ? HTMLTextAreaElement : S extends `div${string}` ? HTMLDivElement : S extends `span${string}` ? HTMLSpanElement : S extends `p${string}` ? HTMLParagraphElement : S extends `h1${string}` ? HTMLHeadingElement : S extends `h2${string}` ? HTMLHeadingElement : S extends `h3${string}` ? HTMLHeadingElement : S extends `h4${string}` ? HTMLHeadingElement : S extends `h5${string}` ? HTMLHeadingElement : S extends `h6${string}` ? HTMLHeadingElement : S extends `ul${string}` ? HTMLUListElement : S extends `ol${string}` ? HTMLOListElement : S extends `li${string}` ? HTMLLIElement : S extends `table${string}` ? HTMLTableElement : S extends `tr${string}` ? HTMLTableRowElement : S extends `td${string}` ? HTMLTableCellElement : S extends `th${string}` ? HTMLTableCellElement : S extends `thead${string}` ? HTMLTableSectionElement : S extends `tbody${string}` ? HTMLTableSectionElement : S extends `tfoot${string}` ? HTMLTableSectionElement : S extends `canvas${string}` ? HTMLCanvasElement : S extends `video${string}` ? HTMLVideoElement : S extends `audio${string}` ? HTMLAudioElement : S extends `iframe${string}` ? HTMLIFrameElement : S extends `script${string}` ? HTMLScriptElement : S extends `link${string}` ? HTMLLinkElement : S extends `style${string}` ? HTMLStyleElement : S extends `meta${string}` ? HTMLMetaElement : S extends `title${string}` ? HTMLTitleElement : S extends `head${string}` ? HTMLHeadElement : S extends `body${string}` ? HTMLBodyElement : S extends `html${string}` ? HTMLHtmlElement : HTMLElement; // Handler types export type ElementHandler<El extends HTMLElement = HTMLElement> = ( element: El, ) => void; export type ElementFn<El extends Element = HTMLElement, T = void> = ( element: El, ) => T; // Selector type export type Selector = string; // Context for generator execution - this will hold the current element export interface GeneratorContext<El extends HTMLElement = HTMLElement> { readonly element: El; readonly selector: string; readonly index: number; readonly array: readonly El[]; } // Current element proxy type - combines function and proxy behaviors export type ElementProxy<El extends HTMLElement = HTMLElement> = El & { <T extends HTMLElement = HTMLElement>(selector: string): T | null; all<T extends HTMLElement = HTMLElement>(selector: string): T[]; }; // Self function type export type SelfFunction<El extends HTMLElement = HTMLElement> = () => El; // Generator function type - element type is inferred and maintained throughout export type GeneratorFunction<El extends HTMLElement = HTMLElement, T = void> = | (() => Generator<ElementFn<El, any>, T, unknown>) | (() => AsyncGenerator<ElementFn<El, any>, T, unknown>); // Generator yield types - expanded to support more patterns export type GeneratorYield<El extends HTMLElement = HTMLElement> = | ElementFn<El, any> | Promise<ElementFn<El, any>> | Generator<ElementFn<El, any>, void, unknown> | AsyncGenerator<ElementFn<El, any>, void, unknown> | Promise<any>; // Parent context type for child components export interface ParentContext< ParentEl extends HTMLElement = HTMLElement, ParentApi = any, > { element: ParentEl; api: ParentApi; } // Context function type - returns typed context export type ContextFunction<El extends HTMLElement = HTMLElement> = () => WatchContext<El>; // Type-safe generator context that maintains element type through inference export interface TypedGeneratorContext<El extends HTMLElement = HTMLElement> { // These functions are properly typed for the current element self(): El; el<T extends HTMLElement = HTMLElement>(selector: string): T | null; all<T extends HTMLElement = HTMLElement>(selector: string): T[]; cleanup(fn: CleanupFunction): void; // Context access ctx(): WatchContext<El>; // Element info readonly element: El; readonly selector: string; readonly index: number; readonly array: readonly El[]; } // Pre-defined watch context for enhanced type safety export interface PreDefinedWatchContext< S extends string = string, El extends HTMLElement = ElementFromSelector<S>, Options extends Record<string, unknown> = Record<string, unknown>, > { readonly selector: S; readonly elementType: El; readonly options: Options; readonly __brand: "PreDefinedWatchContext"; } // Options for creating watch contexts export interface WatchContextOptions extends Record<string, unknown> { // Debounce setup function calls debounce?: number; // Throttle setup function calls throttle?: number; // Only run once once?: boolean; // Custom data to attach to context data?: Record<string, unknown>; // Custom element filter function filter?: (element: HTMLElement) => boolean; // Priority for execution order priority?: number; } // Overloaded function patterns for dual API export type DualAPI< DirectArgs extends readonly unknown[], GeneratorArgs extends readonly unknown[], El extends HTMLElement = HTMLElement, ReturnType = void, > = { (...args: [...DirectArgs, El]): ReturnType; (...args: GeneratorArgs): ElementFn<El, ReturnType>; }; // Event handler type that receives element as second parameter export type ElementEventHandler< El extends HTMLElement = HTMLElement, K extends keyof HTMLElementEventMap = keyof HTMLElementEventMap, > = (event: HTMLElementEventMap[K], element: El) => void; // Custom event handler type for CustomEvent support export type CustomEventHandler< El extends HTMLElement = HTMLElement, T = any, > = (event: CustomEvent<T>, element: El) => void; // Union type for all possible event handlers export type EventHandler< El extends HTMLElement = HTMLElement, K extends keyof HTMLElementEventMap = keyof HTMLElementEventMap, T = any, > = ElementEventHandler<El, K> | CustomEventHandler<El, T>; // Enhanced event listener options with delegation support export interface WatchEventListenerOptions extends AddEventListenerOptions { /** Enable delegation - listen on parent and match against selector */ delegate?: string; /** Debounce the event handler (milliseconds) */ debounce?: number; /** Throttle the event handler (milliseconds) */ throttle?: number; /** Only handle events from specific elements */ filter?: (event: Event, element: HTMLElement) => boolean; } // In your types.ts file or at the top of the context module /** * The possible return types for an event handler, which can be a regular * function, an async function, or a synchronous/asynchronous generator. */ export type EventHandlerResult = | void | Promise<void> | Generator<any, void, any> | AsyncGenerator<any, void, any>; /** * A generic event handler function type. * @template E - The specific Event type (e.g., MouseEvent, KeyboardEvent). */ export type EventHandler<E extends Event = Event> = (event: E) => EventHandlerResult; // Hybrid event handler types that support both regular functions and generators export type HybridEventHandler< El extends Element = HTMLElement, K extends keyof HTMLElementEventMap = keyof HTMLElementEventMap, > = | ((event: HTMLElementEventMap[K], element?: El) => void) | ((event: HTMLElementEventMap[K], element?: El) => Promise<void>) | (( event: HTMLElementEventMap[K], element?: El, ) => Generator<ElementFn<El>, void, unknown>) | (( event: HTMLElementEventMap[K], element?: El, ) => AsyncGenerator<ElementFn<El>, void, unknown>) | ((event: HTMLElementEventMap[K]) => void) | ((event: HTMLElementEventMap[K]) => Promise<void>) | ((event: HTMLElementEventMap[K]) => Generator<ElementFn<El>, void, unknown>) | (( event: HTMLElementEventMap[K], ) => AsyncGenerator<ElementFn<El>, void, unknown>); export type HybridCustomEventHandler< El extends Element = HTMLElement, T = any, > = | ((event: CustomEvent<T>, element?: El) => void) | ((event: CustomEvent<T>, element?: El) => Promise<void>) | (( event: CustomEvent<T>, element?: El, ) => Generator<ElementFn<El>, void, unknown>) | (( event: CustomEvent<T>, element?: El, ) => AsyncGenerator<ElementFn<El>, void, unknown>) | ((event: CustomEvent<T>) => void) | ((event: CustomEvent<T>) => Promise<void>) | ((event: CustomEvent<T>) => Generator<ElementFn<El>, void, unknown>) | ((event: CustomEvent<T>) => AsyncGenerator<ElementFn<El>, void, unknown>); // Debounce configuration options export interface DebounceOptions { /** Wait time in milliseconds */ wait: number; /** Execute on leading edge */ leading?: boolean; /** Execute on trailing edge (default: true) */ trailing?: boolean; } // Throttle configuration options export interface ThrottleOptions { /** Limit in milliseconds */ limit: number; /** Execute on leading edge (default: true) */ leading?: boolean; /** Execute on trailing edge */ trailing?: boolean; } // Enhanced options for hybrid event handling export interface HybridEventOptions extends Omit<AddEventListenerOptions, "signal"> { /** Enable delegation - listen on parent and match against selector */ delegate?: string; /** Delegation phase - bubble (default) or capture */ delegatePhase?: "bubble" | "capture"; /** Debounce configuration */ debounce?: number | DebounceOptions; /** Throttle configuration */ throttle?: number | ThrottleOptions; /** Filter function to conditionally handle events */ filter?: (event: Event, element: HTMLElement) => boolean; /** AbortSignal for cleanup */ signal?: AbortSignal; /** Queue concurrent async generators: 'latest' | 'all' | 'none' (default: 'all') */ queue?: "latest" | "all" | "none"; } // Form element types export type FormElement = | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; // Utility types export type Prettify<T> = { [K in keyof T]: T[K]; } & {}; // CSS property name type export type CSSPropertyName = keyof CSSStyleDeclaration; // Attribute name type export type AttributeName = string; // Data attribute key type export type DataAttributeKey = string; // Event name type export type EventName<_El extends HTMLElement = HTMLElement> = keyof HTMLElementEventMap; // Matcher function type export type ElementMatcher<El extends HTMLElement = HTMLElement> = ( element: HTMLElement, ) => element is El; // TypedGeneratorContext - the context object passed to generator functions export interface TypedGeneratorContext<El extends HTMLElement = HTMLElement> { // Core element access self: () => El; el: <T extends HTMLElement = HTMLElement>(selector: string) => T | null; all: <T extends HTMLElement = HTMLElement>(selector: string) => T[]; // Cleanup registration cleanup: (fn: CleanupFunction) => void; // Context information ctx: () => WatchContext<El>; // Direct properties for convenience readonly element: El; readonly selector: string; readonly index: number; readonly array: readonly El[]; } // Enhanced matcher function with more control export type AdvancedMatcher = ( element: HTMLElement, ) => "skip" | "observe" | "queue"; // Watch target types - now with advanced matcher and special objects export type WatchTarget<El extends HTMLElement = HTMLElement> = | string | El | El[] | NodeListOf<El> | ElementMatcher<El> | AdvancedMatcher | { parent: HTMLElement; childSelector: string } | { parent: HTMLElement; selector: string } | { parent: HTMLElement; matcher: ElementMatcher<any> }; // Observer event types export interface AttributeChange { attributeName: string; oldValue: string | null; newValue: string | null; } export interface TextChange { oldText: string; newText: string; } export interface VisibilityChange { isVisible: boolean; intersectionRatio: number; boundingClientRect: DOMRectReadOnly; } export interface ResizeChange { contentRect: DOMRectReadOnly; borderBoxSize: readonly ResizeObserverSize[]; contentBoxSize: readonly ResizeObserverSize[]; devicePixelContentBoxSize?: readonly ResizeObserverSize[]; } // Lifecycle event types export type MountHandler<El extends HTMLElement = HTMLElement> = ( element: El, ) => void; export type UnmountHandler<El extends HTMLElement = HTMLElement> = ( element: El, ) => void; // Cleanup function type export type CleanupFunction = () => void; // Registry types for global observer export type SelectorRegistry = Map<string, Set<ElementHandler>>; export type UnmountRegistry = WeakMap<HTMLElement, Set<UnmountHandler>>; // Enhanced state management types export type TypedState<T = any> = { get(): T; set(value: T): void; update(fn: (current: T) => T): void; init(value: T): void; }; // ============================================================================ // NEW GENERATOR API TYPES - Phase 1: Foundational Types // ============================================================================ // An "Operation" is a pure function that describes work to be done. // It receives a WatchContext and returns a result (sync or async). export type Operation<TReturn, El extends HTMLElement = HTMLElement> = ( context: WatchContext<El>, ) => TReturn | Promise<TReturn>; // A "Workflow" is what users write with the new API - sync generator by default // that yields Operations and returns a final value. export type SyncWorkflow<TReturn = void> = Generator< Operation<any, any>, TReturn, any >; export type AsyncWorkflow<TReturn = void> = AsyncGenerator< Operation<any, any>, TReturn, any >; // Workflow is sync by default - better performance, no async overhead export type Workflow<TReturn = void> = SyncWorkflow<TReturn>; // Enhanced WatchContext for the new pattern - extends existing for compatibility export interface EnhancedWatchContext<El extends HTMLElement = HTMLElement> extends Omit<WatchContext<El>, "state"> { // Core context properties (readonly for immutability) readonly element: El; readonly selector: string; readonly index: number; readonly array: readonly El[]; // State management interface (replaces Map<string, any> from base) readonly state: { get<T>(key: string, defaultValue?: T): T; set<T>(key: string, value: T): void; update<T>(key: string, updater: (current: T) => T): void; has(key: string): boolean; delete(key: string): boolean; watch<T>( key: string, callback: (newValue: T, oldValue: T) => void, ): () => void; }; // Parent context for hierarchical operations readonly parentContext?: EnhancedWatchContext; // Enhanced cleanup registry readonly cleanup: (fn: () => void) => void; // Observer registration readonly addObserver: ( observer: MutationObserver | IntersectionObserver | ResizeObserver, ) => void; } // Type alias for the new context pattern export type OperationContext<El extends HTMLElement = HTMLElement> = EnhancedWatchContext<El>; // Generator operation result - what gets yielded in workflows export type OperationResult<T = any> = T; // Enhanced generator function type for the new API export type WorkflowFunction<El extends HTMLElement = HTMLElement, T = void> = ( context: OperationContext<El>, ) => SyncWorkflow<T>; // Operation factory type - functions that return operations export type OperationFactory< TArgs extends readonly unknown[], TReturn, El extends HTMLElement = HTMLElement, > = (...args: TArgs) => Operation<TReturn, El>; // Workflow composer type - for composing multiple workflows export type WorkflowComposer<El extends HTMLElement = HTMLElement> = ( ...workflows: WorkflowFunction<El>[] ) => WorkflowFunction<El>; /** * Represents a managed instance of a watched element, providing access to * its element and a snapshot of its state for introspection purposes. */ export interface ManagedInstance { readonly element: HTMLElement; getState: () => Readonly<Record<string, any>>; } /** * The controller object returned by the watch() function. It provides a handle * to the watch operation, enabling advanced control like behavior layering, * instance introspection, and manual destruction. * * For backward compatibility, controllers are callable as cleanup functions. */ export interface WatchController<El extends HTMLElement = HTMLElement> { /** The original subject (selector, element, etc.) that this controller manages. */ readonly subject: WatchTarget<El>; /** Returns a read-only Map of the current elements being managed by this watcher. */ getInstances(): ReadonlyMap<El, ManagedInstance>; /** * Adds a new behavior "layer" to the watched elements. This generator will be * executed on all current and future elements matched by this controller. */ layer( generator: ( ctx: TypedGeneratorContext<El>, ) => | Generator<ElementFn<El, any>, any, unknown> | AsyncGenerator<any, any, unknown>, ): void; /** * Destroys this watch operation entirely, cleaning up all its layered behaviors * and removing all listeners and observers for all managed instances. */ destroy(): void; /** * Backward compatibility: Allow controller to be called as a cleanup function */ (): void; } // Individual element instance with its own state export interface WatchedInstance<El extends HTMLElement = HTMLElement> { element: El; state: Record<string, any>; observers: Set<MutationObserver | IntersectionObserver | ResizeObserver>; cleanupFns: (() => void)[]; } // Shared configuration for all instances of a watch subject export interface WatchConfig<El extends HTMLElement = HTMLElement> { subject: WatchTarget<El>; parentScope: HTMLElement; mountFns: ((instance: WatchedInstance<El>) => void)[]; unmountFns: ((instance: WatchedInstance<El>) => void)[]; instances: Map<HTMLElement, WatchedInstance<El>>; globalObserver?: MutationObserver; } // Context state for generators - enhanced export interface WatchContext<El extends HTMLElement = HTMLElement> { element: El; selector: string; index: number; array: readonly El[]; // Enhanced state management state: Map<string, any>; observers: Set<MutationObserver | IntersectionObserver | ResizeObserver>; // Proxy element access el: ElementProxy<El>; // Self function self: SelfFunction<El>; // Enhanced cleanup registration cleanup: (fn: CleanupFunction) => void; addObserver: ( observer: MutationObserver | IntersectionObserver | ResizeObserver, ) => void; // AbortSignal for cancellation signal?: AbortSignal; }