watch-selector
Version:
Runs a function when a selector is added to dom
259 lines • 16.6 kB
TypeScript
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;
export type ElementHandler<El extends HTMLElement = HTMLElement> = (element: El) => void;
export type ElementFn<El extends Element = HTMLElement, T = void> = (element: El) => T;
export type Selector = string;
export interface GeneratorContext<El extends HTMLElement = HTMLElement> {
readonly element: El;
readonly selector: string;
readonly index: number;
readonly array: readonly El[];
}
export type ElementProxy<El extends HTMLElement = HTMLElement> = El & {
<T extends HTMLElement = HTMLElement>(selector: string): T | null;
all<T extends HTMLElement = HTMLElement>(selector: string): T[];
};
export type SelfFunction<El extends HTMLElement = HTMLElement> = () => El;
export type GeneratorFunction<El extends HTMLElement = HTMLElement, T = void> = (() => Generator<ElementFn<El, any>, T, unknown>) | (() => AsyncGenerator<ElementFn<El, any>, T, unknown>);
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>;
export interface ParentContext<ParentEl extends HTMLElement = HTMLElement, ParentApi = any> {
element: ParentEl;
api: ParentApi;
}
export type ContextFunction<El extends HTMLElement = HTMLElement> = () => WatchContext<El>;
export interface TypedGeneratorContext<El extends HTMLElement = HTMLElement> {
self(): El;
el<T extends HTMLElement = HTMLElement>(selector: string): T | null;
all<T extends HTMLElement = HTMLElement>(selector: string): T[];
cleanup(fn: CleanupFunction): void;
ctx(): WatchContext<El>;
readonly element: El;
readonly selector: string;
readonly index: number;
readonly array: readonly El[];
}
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";
}
export interface WatchContextOptions extends Record<string, unknown> {
debounce?: number;
throttle?: number;
once?: boolean;
data?: Record<string, unknown>;
filter?: (element: HTMLElement) => boolean;
priority?: number;
}
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>;
};
export type ElementEventHandler<El extends HTMLElement = HTMLElement, K extends keyof HTMLElementEventMap = keyof HTMLElementEventMap> = (event: HTMLElementEventMap[K], element: El) => void;
export type CustomEventHandler<El extends HTMLElement = HTMLElement, T = any> = (event: CustomEvent<T>, element: El) => void;
export type EventHandler<El extends HTMLElement = HTMLElement, K extends keyof HTMLElementEventMap = keyof HTMLElementEventMap, T = any> = ElementEventHandler<El, K> | CustomEventHandler<El, T>;
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;
}
/**
* 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;
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>);
export interface DebounceOptions {
/** Wait time in milliseconds */
wait: number;
/** Execute on leading edge */
leading?: boolean;
/** Execute on trailing edge (default: true) */
trailing?: boolean;
}
export interface ThrottleOptions {
/** Limit in milliseconds */
limit: number;
/** Execute on leading edge (default: true) */
leading?: boolean;
/** Execute on trailing edge */
trailing?: boolean;
}
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";
}
export type FormElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
export type Prettify<T> = {
[K in keyof T]: T[K];
} & {};
export type CSSPropertyName = keyof CSSStyleDeclaration;
export type AttributeName = string;
export type DataAttributeKey = string;
export type EventName<_El extends HTMLElement = HTMLElement> = keyof HTMLElementEventMap;
export type ElementMatcher<El extends HTMLElement = HTMLElement> = (element: HTMLElement) => element is El;
export interface TypedGeneratorContext<El extends HTMLElement = HTMLElement> {
self: () => El;
el: <T extends HTMLElement = HTMLElement>(selector: string) => T | null;
all: <T extends HTMLElement = HTMLElement>(selector: string) => T[];
cleanup: (fn: CleanupFunction) => void;
ctx: () => WatchContext<El>;
readonly element: El;
readonly selector: string;
readonly index: number;
readonly array: readonly El[];
}
export type AdvancedMatcher = (element: HTMLElement) => "skip" | "observe" | "queue";
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>;
};
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[];
}
export type MountHandler<El extends HTMLElement = HTMLElement> = (element: El) => void;
export type UnmountHandler<El extends HTMLElement = HTMLElement> = (element: El) => void;
export type CleanupFunction = () => void;
export type SelectorRegistry = Map<string, Set<ElementHandler>>;
export type UnmountRegistry = WeakMap<HTMLElement, Set<UnmountHandler>>;
export type TypedState<T = any> = {
get(): T;
set(value: T): void;
update(fn: (current: T) => T): void;
init(value: T): void;
};
export type Operation<TReturn, El extends HTMLElement = HTMLElement> = (context: WatchContext<El>) => TReturn | Promise<TReturn>;
export type SyncWorkflow<TReturn = void> = Generator<Operation<any, any>, TReturn, any>;
export type AsyncWorkflow<TReturn = void> = AsyncGenerator<Operation<any, any>, TReturn, any>;
export type Workflow<TReturn = void> = SyncWorkflow<TReturn>;
export interface EnhancedWatchContext<El extends HTMLElement = HTMLElement> extends Omit<WatchContext<El>, "state"> {
readonly element: El;
readonly selector: string;
readonly index: number;
readonly array: readonly El[];
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;
};
readonly parentContext?: EnhancedWatchContext;
readonly cleanup: (fn: () => void) => void;
readonly addObserver: (observer: MutationObserver | IntersectionObserver | ResizeObserver) => void;
}
export type OperationContext<El extends HTMLElement = HTMLElement> = EnhancedWatchContext<El>;
export type OperationResult<T = any> = T;
export type WorkflowFunction<El extends HTMLElement = HTMLElement, T = void> = (context: OperationContext<El>) => SyncWorkflow<T>;
export type OperationFactory<TArgs extends readonly unknown[], TReturn, El extends HTMLElement = HTMLElement> = (...args: TArgs) => Operation<TReturn, El>;
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;
}
export interface WatchedInstance<El extends HTMLElement = HTMLElement> {
element: El;
state: Record<string, any>;
observers: Set<MutationObserver | IntersectionObserver | ResizeObserver>;
cleanupFns: (() => void)[];
}
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;
}
export interface WatchContext<El extends HTMLElement = HTMLElement> {
element: El;
selector: string;
index: number;
array: readonly El[];
state: Map<string, any>;
observers: Set<MutationObserver | IntersectionObserver | ResizeObserver>;
el: ElementProxy<El>;
self: SelfFunction<El>;
cleanup: (fn: CleanupFunction) => void;
addObserver: (observer: MutationObserver | IntersectionObserver | ResizeObserver) => void;
signal?: AbortSignal;
}
//# sourceMappingURL=types.d.ts.map