UNPKG

watch-selector

Version:

Runs a function when a selector is added to dom

428 lines (365 loc) 15.9 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 HTMLElement = 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; } // Hybrid event handler types that support both regular functions and generators export type HybridEventHandler<El extends HTMLElement = 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 HTMLElement = 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; // 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: ResizeObserverSize[]; contentBoxSize: 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; }; /** * 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: () => Generator<ElementFn<El, 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: Record<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; }