UNPKG

watch-selector

Version:

Runs a function when a selector is added to dom

1,433 lines 67.7 kB
/** * DOM API Implementation with Sync Generators and Branded Types * * This module implements DOM manipulation functions that support multiple API patterns: * 1. Direct element manipulation: text(element, 'content') * 2. CSS selector manipulation: text('#id', 'content') * 3. Generator with yield*: yield* text('content') * * All functions use sync generators and the yield* pattern for better type safety. */ import type { Workflow } from "../types"; import { type CSSSelector, type ClassName } from "../utils/selector-types"; type CSSLengthUnit = "px" | "em" | "rem" | "%" | "vh" | "vw" | "vmin" | "vmax" | "ch" | "ex" | "cm" | "mm" | "in" | "pt" | "pc"; type CSSLength = `${number}${CSSLengthUnit}` | number | "0" | "auto" | "inherit" | "initial" | "unset"; type CSSColor = `#${string}` | `rgb(${string})` | `rgba(${string})` | `hsl(${string})` | `hsla(${string})` | "transparent" | "currentColor" | "inherit"; type StyleValue = string | number | null | undefined; type StyleObject<K extends keyof CSSStyleDeclaration = keyof CSSStyleDeclaration> = { [P in K]?: CSSStyleDeclaration[P] | StyleValue; }; type AttributeObject = Record<string, string | number | boolean | null | undefined>; type DataObject<T = any> = Record<string, T>; type CSSStyleProperties = Partial<CSSStyleDeclaration>; type DisplayValue = "none" | "block" | "inline" | "inline-block" | "flex" | "inline-flex" | "grid" | "inline-grid" | "table" | "table-row" | "table-cell" | "contents" | "list-item" | "run-in"; type PositionValue = "static" | "relative" | "absolute" | "fixed" | "sticky"; type FormElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement; type FocusableElement = HTMLElement & { focus(): void; blur(): void; }; type ValueElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | HTMLOutputElement; type ElementConstraint = Element | HTMLElement | SVGElement; type HTMLElementConstraint = HTMLElement | HTMLDivElement | HTMLSpanElement | HTMLButtonElement | HTMLInputElement; type QueryConstraint<T = Element> = T extends Element ? T : Element; type StrictElementMap<K extends keyof HTMLElementTagNameMap> = HTMLElementTagNameMap[K]; type StrictSVGElementMap<K extends keyof SVGElementTagNameMap> = SVGElementTagNameMap[K]; /** * Type guard: Check if a value is an HTML element with generic constraint */ declare function isHTMLElement<T extends HTMLElement = HTMLElement>(value: unknown, tagName?: keyof HTMLElementTagNameMap): value is T; /** * Type guard for specific HTML element types */ /** * Type guard: Check if a value is an Element with generic constraint */ declare function isElement<T extends Element = Element>(value: unknown, tagName?: keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap): value is T; /** * Resolve single element with generic type support */ declare function resolveElement<T extends Element = HTMLElement>(selector: string, root?: Element | Document): T | null; /** * Sets or gets text content of elements with full type safety and multiple usage patterns. * * This function provides a unified API for text manipulation that works in three distinct patterns: * 1. Direct element manipulation - Pass an element directly * 2. CSS selector targeting - Use a selector string to find and modify elements * 3. Generator context - Use within watch() generators with yield * * @example Direct element manipulation * ```typescript * const button = document.querySelector('button'); * // Set text * text(button, 'Click me'); * // Get text * const content = text(button); // returns string * ``` * * @example CSS selector pattern * ```typescript * // Set text for all matching elements * text('.button', 'Click me'); * // Get text from first matching element * const content = text('.button'); // returns string | null * ``` * * @example Generator context with yield * ```typescript * import { watch, text } from 'watch-selector'; * * watch('.dynamic-content', function* () { * // Set text * yield text('Loading...'); * * // Get current text * const current = yield text(); * console.log('Current text:', current); * * // Update with dynamic content * yield text(`Loaded at ${new Date().toLocaleTimeString()}`); * }); * ``` * * @example With template literals and variables * ```typescript * text('button', 'Click me'); * ``` * * @example Generator pattern * ```typescript * watch('button', function* () { * yield* text('Click me'); * const content = yield* text(); * }); * ``` */ export declare function text(element: HTMLElement, content: string | number): void; export declare function text(selector: string, content: string | number): void; export declare function text(selector: CSSSelector, content: string | number): void; export declare function text<T extends HTMLElement = HTMLElement>(element: T, content: string | number): void; export declare function text<T extends HTMLElement = HTMLElement>(element: T): string; export declare function text(selector: string | CSSSelector, content: string | number): void; export declare function text(selector: string | CSSSelector): string | null; export declare function text(content: string | number): Workflow<void>; export declare function text(): Workflow<string>; /** * Sets or gets HTML content of elements with full type safety. * * ⚠️ WARNING: Setting HTML content can expose your application to XSS attacks. * Always sanitize user input before using it as HTML content. * * @example Direct element manipulation * ```typescript * const container = document.querySelector('.content'); * // Set HTML (be careful with user input!) * html(container, '<strong>Bold text</strong>'); * // Get HTML * const markup = html(container); // returns string * ``` * * @example CSS selector pattern * ```typescript * // Set HTML for all matching elements * html('.card-body', '<p>Card content</p>'); * // Get HTML from first matching element * const markup = html('.card-body'); // returns string | null * ``` * * @example Generator context * ```typescript * import { watch, html } from 'watch-selector'; * * watch('.markdown-output', function* () { * yield html('<p>Rendering...</p>'); * * // Fetch and render markdown * const response = await fetch('/api/content'); * const rendered = await response.text(); * * // ⚠️ Only use with trusted content! * yield html(rendered); * }); * ``` * * @param element - The element to manipulate (direct pattern) * @param selector - CSS selector to find elements (selector pattern) * @param content - HTML content to set (optional, if not provided, gets content) * @returns void when setting, string when getting, or Workflow in generator context */ export declare function html<T extends HTMLElement = HTMLElement>(element: T, content: string): void; export declare function html<T extends HTMLElement = HTMLElement>(element: T): string; export declare function html(selector: string | CSSSelector, content: string): void; export declare function html(selector: string | CSSSelector): string | null; export declare function html(content: string): Workflow<void>; export declare function html(): Workflow<string>; /** * Adds one or more CSS classes to elements with intelligent deduplication. * * Supports space-separated class names and automatically handles duplicates. * Classes are only added if they don't already exist on the element. * * @param element - HTMLElement to add classes to (direct pattern) * @param selector - CSS selector to find elements (selector pattern) * @param className - Single class or space-separated classes to add * @returns void for direct/selector patterns, Workflow for generator pattern * * @example Adding classes in generators with yield* * ```typescript * import { watch, addClass, click } from 'watch-selector'; * * watch('.card', function* () { * // Add single class * yield* addClass('interactive'); * * // Add multiple classes at once * yield* addClass('shadow-lg rounded bordered'); * * yield* click(function* () { * // Add state classes reactively * yield* addClass('selected highlighted'); * }); * }); * ``` * * @example Conditional class addition * ```typescript * watch('.notification', function* () { * const type = yield* getState('type', 'info'); * * // Add classes based on state * yield* addClass('notification'); * yield* addClass(`notification-${type}`); * * if (type === 'error') { * yield* addClass('urgent shake-animation'); * } * }); * ``` * * @example Animation and transition classes * ```typescript * watch('.modal', function* () { * // Prepare for animation * yield* addClass('modal-base'); * * // Trigger animation after a frame * yield* onMount(function* () { * requestAnimationFrame(() => { * yield* addClass('fade-in slide-up'); * }); * }); * }); * ``` */ export declare function addClass<T extends Element = HTMLElement>(element: T, className: string | ClassName): void; export declare function addClass<T extends Element = HTMLElement>(selector: string | CSSSelector, className: string | ClassName): void; export declare function addClass(className: string | ClassName): Workflow<void>; /** * Removes one or more CSS classes from elements. * * Supports space-separated class names and safely handles non-existent classes. * * @example Direct element manipulation * ```typescript * const modal = document.querySelector('.modal'); * // Remove single class * removeClass(modal, 'hidden'); * // Remove multiple classes * removeClass(modal, 'hidden fade-out disabled'); * ``` * * @example CSS selector pattern * ```typescript * // Remove classes from all matching elements * removeClass('.error-field', 'error highlighted'); * // Clear loading states * removeClass('.loading', 'loading spinner'); * ``` * * @example Generator context * ```typescript * import { watch, removeClass, addClass, delay } from 'watch-selector'; * * watch('.notification', function* () { * yield addClass('visible slide-in'); * yield delay(3000); * yield removeClass('visible'); * yield addClass('slide-out'); * }); * ``` * * @param element - The element to remove classes from (direct pattern) * @param selector - CSS selector to find elements (selector pattern) * @param className - Space-separated class names to remove * @returns void when used directly, Workflow in generator context */ /** * Removes one or more CSS classes from elements. * * Supports space-separated class names and safely handles non-existent classes. * No error is thrown if a class doesn't exist on the element. * * @param element - HTMLElement to remove classes from (direct pattern) * @param selector - CSS selector to find elements (selector pattern) * @param className - Single class or space-separated classes to remove * @returns void for direct/selector patterns, Workflow for generator pattern * * @example Removing classes with yield* in generators * ```typescript * import { watch, removeClass, addClass, click } from 'watch-selector'; * * watch('.toggle-button', function* () { * yield* click(function* () { * // Remove multiple classes at once * yield* removeClass('inactive disabled'); * yield* addClass('active enabled'); * }); * }); * ``` * * @example State transitions with class swapping * ```typescript * watch('.status-indicator', function* () { * const status = yield* getState('status', 'pending'); * * // Clear all possible status classes * yield* removeClass('status-pending status-loading status-success status-error'); * * // Add the current status class * yield* addClass(`status-${status}`); * }); * ``` * * @example Animation cleanup * ```typescript * watch('.animated-element', function* () { * yield* click(function* () { * // Trigger animation * yield* addClass('animating bounce'); * * // Clean up after animation completes * setTimeout(() => { * yield* removeClass('animating bounce'); * yield* addClass('animation-complete'); * }, 1000); * }); * }); * ``` */ export declare function removeClass<T extends Element = HTMLElement>(element: T, className: string | ClassName): void; export declare function removeClass<T extends Element = HTMLElement>(selector: string | CSSSelector, className: string | ClassName): void; export declare function removeClass(className: string | ClassName): Workflow<void>; /** * Sets or gets inline style properties on elements. * * Supports multiple usage patterns: * - Set single property: style(element, 'color', 'red') * - Set multiple properties: style(element, { color: 'red', fontSize: '16px' }) * - Get property value: style(element, 'color') * * @example Direct element - setting styles * ```typescript * const box = document.querySelector('.box'); * // Set single property * style(box, 'backgroundColor', '#ff0000'); * style(box, 'padding', '20px'); * // Set with number (adds 'px' for applicable properties) * style(box, 'width', 200); // becomes '200px' * // Set multiple properties at once *al force parameter. * * @example Direct element manipulation * ```typescript * const panel = document.querySelector('.panel'); * // Toggle class * toggleClass(panel, 'expanded'); * // Force add (true) or remove (false) * toggleClass(panel, 'active', isActive); * ``` * * @example CSS selector pattern * ```typescript * // Toggle on all matching elements * toggleClass('.accordion-item', 'open'); * // Force state based on condition * toggleClass('.menu', 'visible', window.innerWidth > 768); * ``` * * @example Generator context * ```typescript * import { watch, toggleClass, click } from 'watch-selector'; * * watch('.toggle-switch', function* () { * yield click(function* () { * yield toggleClass('on'); * const isOn = yield hasClass('on'); * console.log('Switch is:', isOn ? 'ON' : 'OFF'); * }); * }); * ``` * * @param element - The element to toggle classes on (direct pattern) * @param selector - CSS selector to find elements (selector pattern) * @param className - Space-separated class names to toggle * @param force - Optional: true to add, false to remove, undefined to toggle * @returns void when used directly, Workflow in generator context */ /** * Toggles CSS classes on elements with optional force flag. * * Intelligently adds or removes classes based on their current presence. * The optional force parameter allows explicit control over the operation. * * @param element - HTMLElement to toggle classes on (direct pattern) * @param selector - CSS selector to find elements (selector pattern) * @param className - Single class or space-separated classes to toggle * @param force - If true, adds class; if false, removes class; if undefined, toggles * @returns void for direct/selector patterns, Workflow for generator pattern * * @example Basic toggle with yield* in generators * ```typescript * import { watch, toggleClass, click } from 'watch-selector'; * * watch('.expandable', function* () { * yield* click(function* () { * // Toggle expanded state * yield* toggleClass('expanded'); * * // Toggle multiple classes * yield* toggleClass('open active highlighted'); * }); * }); * ``` * * @example Forced toggle based on conditions * ```typescript * watch('.theme-toggle', function* () { * yield* click(function* () { * const isDark = yield* hasClass('dark-mode'); * * // Force toggle based on current state * yield* toggleClass('dark-mode', !isDark); * yield* toggleClass('light-mode', isDark); * }); * }); * ``` * * @example Accordion behavior with toggles * ```typescript * watch('.accordion-item', function* () { * yield* click(function* () { * // Close all other items * const siblings = yield* siblings(); * for (const sibling of siblings) { * toggleClass(sibling, 'expanded', false); * } * * // Toggle current item * yield* toggleClass('expanded'); * }); * }); * ``` */ export declare function toggleClass<T extends Element = HTMLElement>(element: T, className: string | ClassName, force?: boolean): void; export declare function toggleClass(selector: string | CSSSelector, className: string | ClassName, force?: boolean): void; export declare function toggleClass(className: string | ClassName, force?: boolean): Workflow<void>; /** * Checks if an element has ALL specified CSS classes. * * Returns true only if the element contains every specified class. * For checking if element has ANY of the classes, use multiple calls. * * @example Direct element manipulation * ```typescript * const button = document.querySelector('button'); * // Check single class * if (hasClass(button, 'active')) { * console.log('Button is active'); * } * // Check multiple classes (ALL must be present) * if (hasClass(button, 'primary large')) { * console.log('Button is primary AND large'); * } * ``` * * @example CSS selector pattern * ```typescript * // Check first matching element * const isVisible = hasClass('.modal', 'visible'); * // Check multiple classes * const isReady = hasClass('.component', 'initialized loaded'); * ``` * * @example Generator context * ```typescript * import { watch, hasClass, addClass, removeClass } from 'watch-selector'; * * watch('.toggle-element', function* () { * const wasActive = yield hasClass('active'); * * if (wasActive) { * yield removeClass('active'); * yield addClass('inactive'); * } else { * yield removeClass('inactive'); * yield addClass('active'); * } * }); * ``` * * @param element - The element to check (direct pattern) * @param selector - CSS selector to find element (selector pattern) * @param className - Space-separated class names to check for * @returns boolean indicating if ALL classes are present, or Workflow<boolean> in generator context */ export declare function hasClass(element: HTMLElement, className: string | ClassName): boolean; /** * Checks if an element has a specific attribute. * * @example Direct element * ```typescript * const input = document.querySelector('input'); * if (hasAttr(input, 'required')) { * console.log('Field is required'); * } * ``` * * @example CSS selector pattern * ```typescript * const hasPlaceholder = hasAttr('input.search', 'placeholder'); * const isDisabled = hasAttr('button.submit', 'disabled'); * ``` * * @example Generator context * ```typescript * import { watch, hasAttr, attr, addClass } from 'watch-selector'; * * watch('input', function* () { * if (yield hasAttr('required')) { * yield addClass('required-field'); * yield attr('aria-required', 'true'); * } * }); * ``` * * @param element - The element to check (direct pattern) * @param selector - CSS selector to find element (selector pattern) * @param name - Attribute name to check for * @returns boolean indicating if attribute exists, Workflow<boolean> in generator context */ export declare function hasClass(selector: string | CSSSelector, className: string | ClassName): boolean; export declare function hasClass(className: string | ClassName): Workflow<boolean>; /** * Sets or gets CSS style properties on elements with type safety. * * Supports setting individual properties, multiple properties via object, * or getting computed style values. Automatically handles vendor prefixes * and unit conversion for numeric values. * * @example Direct element - single property * ```typescript * const div = document.querySelector('.box'); * // Set single style * style(div, 'backgroundColor', 'red'); * style(div, 'width', 100); // Automatically adds 'px' * style(div, 'opacity', 0.5); * * // Get computed style * const width = style(div, 'width'); // returns "100px" * ``` * * @example Direct element - multiple properties * ```typescript * const panel = document.querySelector('.panel'); * style(panel, { * backgroundColor: '#f0f0f0', * padding: 20, // becomes '20px' * borderRadius: '8px', * opacity: 0.9, * display: 'flex' * }); * ``` * * @example CSS selector pattern * ```typescript * // Style all matching elements * style('.card', 'boxShadow', '0 2px 4px rgba(0,0,0,0.1)'); * * // Apply multiple styles * style('.highlighted', { * backgroundColor: 'yellow', * fontWeight: 'bold', * padding: 10 * }); * * // Get style from first match * const bgColor = style('.card', 'backgroundColor'); * ``` * * @example Generator context with animations * ```typescript * import { watch, style, delay } from 'watch-selector'; * * watch('.animate-box', function* () { * // Fade in animation * yield style('opacity', 0); * yield style('transform', 'translateY(20px)'); * * yield delay(100); * * yield style({ * opacity: 1, * transform: 'translateY(0)', * transition: 'all 0.3s ease' * }); * }); * ``` * * @example Responsive styling * ```typescript * import { watch, style, onResize } from 'watch-selector'; * * watch('.responsive-element', function* () { * yield onResize(function* (entry) { * const width = entry.contentRect.width; * * if (width < 600) { * yield style({ fontSize: '14px', padding: '10px' }); * } else { * yield style({ fontSize: '18px', padding: '20px' }); * } * }); * }); * ``` * * @param element - The element to style (direct pattern) * @param selector - CSS selector to find elements (selector pattern) * @param prop - CSS property name (camelCase or kebab-case) * @param value - Style value (numbers auto-convert to px for applicable properties) * @param styles - Object of property-value pairs for multiple styles * @returns void when setting, string when getting, Workflow in generator context */ /** * Manipulates inline styles on elements with support for objects and individual properties. * * Handles CSS property names in both camelCase and kebab-case formats. * Automatically adds 'px' units to numeric values for applicable properties. * Setting a value to null or empty string removes the style property. * * @param element - HTMLElement to style (direct pattern) * @param selector - CSS selector to find elements (selector pattern) * @param prop - CSS property name or object of property-value pairs * @param value - CSS value (string, number, or null to remove) * @returns void when setting, string when getting single property, Workflow for generators * * @example Setting styles with yield* in generators * ```typescript * import { watch, style, click } from 'watch-selector'; * * watch('.animated-box', function* () { * // Set single style property * yield* style('background-color', '#3498db'); * yield* style('padding', 20); // Auto-adds 'px' * * // Set multiple styles with object * yield* style({ * width: 200, // Becomes '200px' * height: 100, // Becomes '100px' * backgroundColor: '#2ecc71', * borderRadius: '8px', * transition: 'all 0.3s ease' * }); * }); * ``` * * @example Dynamic styling based on state * ```typescript * watch('.progress-bar', function* () { * const progress = yield* getState('progress', 0); * * yield* style({ * width: `${progress}%`, * backgroundColor: progress === 100 ? '#27ae60' : '#3498db', * transition: 'width 0.5s ease' * }); * * // Get computed style * const currentWidth = yield* style('width'); * console.log('Current width:', currentWidth); * }); * ``` * * @example Animations with dynamic styles * ```typescript * watch('.floating-element', function* () { * let position = 0; * * yield* onMount(function* () { * const animate = () => { * position += 1; * yield* style('transform', `translateY(${Math.sin(position * 0.1) * 10}px)`); * requestAnimationFrame(animate); * }; * animate(); * }); * }); * ``` * * @example Removing styles * ```typescript * watch('.resettable', function* () { * yield* click(function* () { * // Remove specific styles by setting to null * yield* style('backgroundColor', null); * yield* style('border', ''); * * // Or remove multiple at once * yield* style({ * width: null, * height: null, * position: null * }); * }); * }); * ``` */ export declare function style<T extends HTMLElement = HTMLElement>(element: T, prop: keyof CSSStyleDeclaration | string, value: StyleValue): void; export declare function style<T extends HTMLElement = HTMLElement>(element: T, styles: Partial<CSSStyleDeclaration> | StyleObject): void; export declare function style<T extends HTMLElement = HTMLElement>(element: T, prop: keyof CSSStyleDeclaration | string): string; export declare function style(selector: string | CSSSelector, prop: keyof CSSStyleDeclaration | string, value: StyleValue): void; export declare function style(selector: string | CSSSelector, styles: Partial<CSSStyleDeclaration> | StyleObject): void; export declare function style(selector: string | CSSSelector, prop: keyof CSSStyleDeclaration | string): string | null; export declare function style(prop: keyof CSSStyleDeclaration | string, value: StyleValue): Workflow<void>; export declare function style(styles: Partial<CSSStyleDeclaration> | StyleObject): Workflow<void>; export declare function style(prop: keyof CSSStyleDeclaration | string): Workflow<string>; export declare function attr<T extends Element = HTMLElement>(element: T, name: string, value: string | number | boolean): void; export declare function attr<T extends Element = HTMLElement>(element: T, attrs: AttributeObject): void; export declare function attr<T extends Element = HTMLElement>(element: T, name: string): string | null; export declare function attr(selector: string | CSSSelector, name: string, value: string | number | boolean): void; export declare function attr(selector: string | CSSSelector, attrs: AttributeObject): void; export declare function attr(selector: string | CSSSelector, name: string): string | null; export declare function attr(name: string, value: string | number | boolean): Workflow<void>; export declare function attr(attrs: AttributeObject): Workflow<void>; export declare function attr(name: string): Workflow<string | null>; export declare function removeAttr<T extends HTMLElement = HTMLElement>(element: T, name: string | string[]): void; export declare function removeAttr(selector: string | CSSSelector, name: string | string[]): void; export declare function removeAttr(name: string | string[]): Workflow<void>; export declare function hasAttr<T extends HTMLElement = HTMLElement>(element: T, name: string): boolean; export declare function hasAttr(selector: string | CSSSelector, name: string): boolean; export declare function hasAttr(name: string): Workflow<boolean>; /** * Manipulates JavaScript properties on DOM elements. * * Properties are part of the DOM object and can be any JavaScript type (boolean, number, object, etc). * Use this for properties like 'checked', 'disabled', 'value', 'selectedIndex', custom properties, etc. * Properties reflect the current state, while attributes represent the initial HTML markup. * * @param element - HTMLElement to manipulate properties on (direct pattern) * @param selector - CSS selector to find elements (selector pattern) * @param name - Property name to get or set * @param value - Property value of any type * @returns void when setting, T when getting, Workflow for generators * * @example Boolean properties with yield* * ```typescript * import { watch, prop, click } from 'watch-selector'; * * watch('.checkbox-wrapper', function* () { * // Set boolean properties * yield* prop('checked', true); * yield* prop('disabled', false); * yield* prop('required', true); * * yield* click(function* () { * // Toggle checked state * const isChecked = yield* prop('checked'); * yield* prop('checked', !isChecked); * * // Update related elements * if (!isChecked) { * yield* addClass('selected'); * } else { * yield* removeClass('selected'); * } * }); * }); * ``` * * @example Form element properties * ```typescript * watch('.form-select', function* () { * // Set selected index * yield* prop('selectedIndex', 2); * * // Get current selection * const index = yield* prop('selectedIndex'); * const selectedOption = yield* prop('selectedOptions'); * * yield* change(function* () { * const value = yield* prop('value'); * console.log('Selected:', value); * * // Enable submit button when something is selected * const submitBtn = yield* query('.submit-btn'); * if (submitBtn) { * prop(submitBtn, 'disabled', !value); * } * }); * }); * ``` * * @example Custom properties and objects * ```typescript * watch('.data-container', function* () { * // Store complex data as properties * yield* prop('customData', { * id: 123, * name: 'Test Item', * metadata: { created: new Date() } * }); * * // Store functions as properties * yield* prop('validator', (value: string) => { * return value.length > 0 && value.length < 100; * }); * * // Retrieve and use custom properties * const data = yield* prop('customData'); * const validator = yield* prop('validator'); * * if (validator && data) { * const isValid = validator(data.name); * yield* toggleClass('valid', isValid); * } * }); * ``` * * @example Video/Audio element properties * ```typescript * watch('video', function* () { * // Control playback properties * yield* prop('volume', 0.5); * yield* prop('muted', false); * yield* prop('playbackRate', 1.25); * * // Monitor properties * const duration = yield* prop('duration'); * const currentTime = yield* prop('currentTime'); * * yield* text(`${currentTime}s / ${duration}s`); * }); * ``` */ export declare function prop<T = any>(element: HTMLElement, name: string, value: T): void; export declare function prop<T = any>(element: HTMLElement, props: DataObject): void; export declare function prop<T = any>(selector: string | CSSSelector, name: string, value: T): void; export declare function prop<T = any>(selector: string | CSSSelector, props: DataObject): void; export declare function prop<T = any>(name: string, value: T): Workflow<void>; export declare function prop(props: DataObject): Workflow<void>; export declare function prop<T = any>(name: string): Workflow<T>; /** * Manipulates HTML5 data-* attributes with automatic camelCase conversion. * * Provides a convenient API for working with data attributes. Automatically handles * the conversion between camelCase property names and kebab-case attribute names. * For example, 'userId' becomes 'data-user-id' in the HTML. * * @param element - HTMLElement to manipulate data attributes on (direct pattern) * @param selector - CSS selector to find elements (selector pattern) * @param key - Data key (without 'data-' prefix) or object of key-value pairs * @param value - Data value (automatically serialized if not a string) * @returns void when setting, T when getting single, object when getting all, Workflow for generators * * @example Setting and getting data attributes with yield* * ```typescript * import { watch, data, click } from 'watch-selector'; * * watch('.product-card', function* () { * // Set single data attribute (becomes data-product-id) * yield* data('productId', '12345'); * * // Set multiple data attributes with object * yield* data({ * productId: '12345', * categoryName: 'Electronics', // becomes data-category-name * inStock: true, // becomes data-in-stock="true" * price: 99.99 // becomes data-price="99.99" * }); * * // Get single data attribute * const productId = yield* data('productId'); * console.log('Product ID:', productId); * }); * ``` * * @example Complex data serialization * ```typescript * watch('.user-profile', function* () { * // Store complex data (automatically JSON stringified) * yield* data('userPreferences', { * theme: 'dark', * language: 'en', * notifications: true * }); * * // Retrieve and parse complex data * const prefs = yield* data('userPreferences'); * const parsed = typeof prefs === 'string' ? JSON.parse(prefs) : prefs; * * yield* addClass(`theme-${parsed.theme}`); * }); * ``` * * @example Dynamic data attributes for tracking * ```typescript * watch('.trackable-element', function* () { * // Set tracking data * yield* data({ * trackCategory: 'engagement', * trackAction: 'view', * trackLabel: 'hero-banner', * trackValue: Date.now() * }); * * yield* click(function* () { * // Update tracking on interaction * yield* data('trackAction', 'click'); * yield* data('trackValue', Date.now()); * * // Send to analytics * const trackingData = { * category: yield* data('trackCategory'), * action: yield* data('trackAction'), * label: yield* data('trackLabel') * }; * console.log('Track event:', trackingData); * }); * }); * ``` * * @example Component state in data attributes * ```typescript * watch('.accordion', function* () { * // Initialize component state * yield* data({ * expanded: false, * animating: false, * height: 'auto' * }); * * yield* click(function* () { * const isExpanded = yield* data('expanded') === 'true'; * * // Toggle state * yield* data('expanded', !isExpanded); * yield* data('animating', true); * * // Update UI based on state * yield* toggleClass('expanded', !isExpanded); * * setTimeout(() => { * yield* data('animating', false); * }, 300); * }); * }); * ``` */ export declare function data<T = any>(element: HTMLElement, data: DataObject): void; export declare function data<T = any>(element: HTMLElement, key: string, value: T): void; export declare function data<T = any>(element: HTMLElement, data: DataObject): void; export declare function data<T = any>(selector: string | CSSSelector, key: string, value: T): void; export declare function data<T = any>(selector: string | CSSSelector, data: DataObject): void; export declare function data<T = any>(key: string, value: T): Workflow<void>; export declare function data(data: DataObject): Workflow<void>; export declare function data<T = any>(key: string): Workflow<T | undefined>; /** * Removes focus from an element. * * @example Direct element * ```typescript * const input = document.querySelector('input'); * blur(input); * ``` * * @example CSS selector pattern * ```typescript * blur('input:focus'); // Blur currently focused input * ``` * * @example Generator context * ```typescript * import { watch, blur, keydown } from 'watch-selector'; * * watch('input.auto-blur', function* () { * yield keydown(function* (e) { * if (e.key === 'Enter') { * yield blur(); // Remove focus on Enter * } * }); * }); * ``` * * @param element - The element to blur * @param selector - CSS selector to find element to blur * @returns void when used directly, Workflow<void> in generator context */ /** * Hides an element by setting 'display: none'. * * @example Direct element * ```typescript * const popup = document.querySelector('.popup'); * hide(popup); * ``` * * @example CSS selector pattern * ```typescript * hide('.loading-spinner'); * hide('[data-temporary]'); * ``` * * @example Generator context with timing * ```typescript * import { watch, hide, show, delay } from 'watch-selector'; * * watch('.flash-message', function* () { * yield show(); * yield delay(3000); // Show for 3 seconds * yield hide(); * }); * ``` * * @param element - The element to hide * @param selector - CSS selector to find elements to hide * @returns void when used directly, Workflow<void> in generator context **/ export declare function value<T extends ValueElement>(element: T, val: string | number): void; export declare function value<T extends ValueElement>(element: T): string; export declare function value(selector: string | CSSSelector, val: string | number): void; export declare function value(selector: string | CSSSelector): string | null; export declare function value(val: string | number): Workflow<void>; export declare function value(): Workflow<string>; export declare function checked<T extends HTMLInputElement = HTMLInputElement>(element: T, val: boolean): void; export declare function checked<T extends HTMLInputElement = HTMLInputElement>(element: T): boolean; export declare function checked(selector: string | CSSSelector, val: boolean): void; export declare function checked(selector: string | CSSSelector): boolean | null; export declare function checked(val: boolean): Workflow<void>; export declare function checked(): Workflow<boolean>; /** * Sets focus on an element, making it the active element for keyboard input. * * Triggers focus events and updates the document's activeElement. Useful for * improving accessibility, managing form navigation, and creating keyboard-driven * interfaces. The element must be focusable (inputs, buttons, links, or elements * with tabindex). * * @param element - HTMLElement to focus (direct pattern) * @param selector - CSS selector to find element to focus * @returns void for direct/selector patterns, Workflow for generators * * @example Auto-focus search input with yield* * ```typescript * import { watch, focus, onMount } from 'watch-selector'; * * watch('.search-modal', function* () { * yield* onMount(function* () { * // Focus search input when modal opens * const searchInput = yield* query('.search-input'); * if (searchInput) { * // Small delay to ensure modal animation completes * setTimeout(() => { * focus(searchInput); * }, 100); * } * }); * }); * ``` * * @example Form field navigation * ```typescript * watch('.form-field', function* () { * yield* on('keydown', function* (event) { * if (event.key === 'Enter') { * event.preventDefault(); * * // Move to next field on Enter * const fields = yield* queryAll('.form-field'); * const currentIndex = fields.indexOf(event.target as HTMLElement); * * if (currentIndex < fields.length - 1) { * focus(fields[currentIndex + 1]); * } else { * // Focus submit button at the end * yield* focus('.submit-btn'); * } * } * }); * }); * ``` * * @example Focus management in dropdown * ```typescript * watch('.dropdown', function* () { * let focusIndex = -1; * * yield* click('.dropdown-trigger', function* () { * yield* toggleClass('open'); * * if (yield* hasClass('open')) { * // Focus first item when opening * const firstItem = yield* query('.dropdown-item'); * if (firstItem) { * focus(firstItem); * focusIndex = 0; * } * } * }); * * // Keyboard navigation * yield* on('keydown', function* (event) { * const items = yield* queryAll('.dropdown-item'); * * if (event.key === 'ArrowDown') { * event.preventDefault(); * focusIndex = Math.min(focusIndex + 1, items.length - 1); * focus(items[focusIndex]); * } else if (event.key === 'ArrowUp') { * event.preventDefault(); * focusIndex = Math.max(focusIndex - 1, 0); * focus(items[focusIndex]); * } * }); * }); * ``` * * @example Focus trap for modal accessibility * ```typescript * watch('.modal', function* () { * yield* onMount(function* () { * // Store previously focused element * const previousFocus = document.activeElement as HTMLElement; * * // Focus first focusable element in modal * const focusableElements = yield* queryAll( * 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' * ); * * if (focusableElements.length > 0) { * focus(focusableElements[0]); * } * * // Restore focus on close * yield* onUnmount(() => { * if (previousFocus) { * focus(previousFocus); * } * }); * }); * }); * ``` */ export declare function focus<T extends FocusableElement = FocusableElement>(element: T): void; export declare function focus<T extends FocusableElement = FocusableElement>(element: T): void; export declare function focus(selector: string | CSSSelector): void; export declare function focus(): Workflow<void>; /** * Removes focus from an element, triggering blur events. * * The blur method causes an element to lose focus, making it no longer the * activeElement. This triggers blur events and can be used for validation, * saving data, or hiding UI elements. After blur, focus typically returns * to the document body unless another element is explicitly focused. * * @param element - HTMLElement to blur (direct pattern) * @param selector - CSS selector to find element to blur * @returns void for direct/selector patterns, Workflow for generators * * @example Input validation on blur with yield* * ```typescript * import { watch, blur, addClass, removeClass } from 'watch-selector'; * * watch('.validate-on-blur', function* () { * yield* on('input', function* () { * // Remove validation classes while typing * yield* removeClass('valid invalid'); * }); * * yield* on('blur', function* () { * const value = yield* value(); * * // Validate when user leaves field * if (value.length < 3) { * yield* addClass('invalid'); * yield* attr('aria-invalid', 'true'); * } else { * yield* addClass('valid'); * yield* attr('aria-invalid', 'false'); * } * * // Save draft * yield* setState('draft', value); * }); * }); * ``` * * @example Auto-save on blur * ```typescript * watch('.auto-save-field', function* () { * let isDirty = false; * * yield* input(function* () { * isDirty = true; * yield* addClass('unsaved'); * }); * * yield* on('blur', function* () { * if (isDirty) { * const content = yield* value(); * * // Show saving indicator * yield* addClass('saving'); * yield* removeClass('unsaved'); * * // Save data * try { * await fetch('/api/save', { * method: 'POST', * body: JSON.stringify({ content }) * }); * * yield* removeClass('saving'); * yield* addClass('saved'); * isDirty = false; * * // Clear saved indicator after delay * setTimeout(() => { * yield* removeClass('saved'); * }, 2000); * } catch (error) { * yield* removeClass('saving'); * yield* addClass('error'); * } * } * }); * }); * ``` * * @example Dropdown close on blur * ```typescript * watch('.searchable-dropdown', function* () { * yield* on('focus', '.dropdown-input', function* () { * yield* addClass('open'); * yield* show('.dropdown-menu'); * }); * * yield* on('blur', '.dropdown-input', function* (event) { * // Delay to allow clicking on dropdown items * setTimeout(() => { * // Check if focus moved to a dropdown item * const focusedElement = document.activeElement; * const isInDropdown = yield* query('.dropdown-menu')?.contains(focusedElement); * * if (!isInDropdown) { * yield* removeClass('open'); * yield* hide('.dropdown-menu'); * } * }, 200); * }); * }); * ``` * * @example Programmatic blur for closing popups * ```typescript * watch('.popup-trigger', function* () { * yield* click(function* () { * const popup = yield* query('.popup'); * * if (popup) { * yield* toggleClass('active', popup); * * if (yield* hasClass('active', popup)) { * // Focus popup for keyboard navigation * focus(popup); * } else { * // Blur to remove focus from popup * blur(popup); * // Return focus to trigger * yield* focus(); * } * } * }); * * // Close on Escape key * yield* on('keydown', function* (event) { * if (event.key === 'Escape') { * const popup = yield* query('.popup'); * if (popup && yield* hasClass('active', popup)) { * blur(popup); * yield* removeClass('active', popup); * yield* focus(); // Return focus to trigger * } * } * }); * }); * ``` */ export declare function blur<T extends FocusableElement = FocusableElement>(element: T): void; export declare function blur<T extends FocusableElement = FocusableElement>(element: T): void; export declare function blur(selector: string | CSSSelector): void; export declare function blur(): Workflow<void>; /** * Shows a hidden element by removing 'display: none' style. * * Removes any inline display: none style, allowing the element to return to its * default or CSS-defined display value. This is more reliable than setting a * specific display value as it respects the element's natural display type. * * @param element - HTMLElement to show (direct pattern) * @param selector - CSS selector to find elements to show * @returns void for direct/selector patterns, Workflow for generators * * @example Basic show/hide toggle with yield* * ```typescript * import { watch, show, hide, click } from 'watch-selector'; * * watch('.collapsible', function* () { * // Initially hide content * yield* hide('.content'); * * yield* click('.toggle-btn', function* () { * const content = yield* query('.content'); * const isHidden = content?.style.display === 'none'; * * if (isHidden) { * yield* show('.content'); * yield* text('.toggle-btn', 'Hide Content'); * } else { * yield* hide('.content'); * yield* text('.toggle-btn', 'Show Content'); * } * }); * }); * ``` * * @example Conditional visibility based on state * ```typescript * watch('.notification-area', function* () { * const hasNotifications = yield* getState('notificationCount', 0) > 0; * * if (hasNotifications) { * yield* show('.notification-badge'); * yield* show('.notification-list'); * } else { * yield* hide('.notification-badge'); * yield* hide('.notification-list'); * yield* show('.empty-state'); * } * }); * ``` * * @example Progressive disclosure pattern * ```typescript * watch('.progressive-form', function* () { * // Hide advanced sections initially * yield* hide('.advanced-options'); * yield* hide('.expert-settings'); * * yield* change('#user-level', function* () { * const level = yield* value(); * * switch(level) { * case 'beginner': * yield* hide('.advanced-options'); * yield* hide('.expert-settings'); * break; * case 'intermediate': * yield* show('.advanced-options'); * yield* hide('.expert-settings'); * break; * case 'expert': * yield* show('.advanced-options'); * yield* show('.expert-settings'); * break; * } * }); * }); * ``` * * @example Loading states with visibility * ```typescript * watch('.data-container', function* () { * yield* click('.load-btn', async function* () { * // Show loading, hide content and error * yield* show('.loading-spinner'); * yield* hide('.content'); * yield* hide('.error-message'); * * try { * const data = await fetch('/api/data').then(r => r.json()); * * // Show content, hide loading * yield* hide('.loading-spinner'); * yield* show('.content'); * yield* text('.content', data.message); * } catch (error) { * // Show error, hide loading * yield* hide('.loading-spinner'); * yield* show('.error-message'); * yield* text('.error-message', 'Failed to load data'); * } * }); * }); * ``` */ export declare function show<T extends HTMLElement = HTMLElement>(element: T): void; export declare function show(selector: string | CSSSelector): void; export declare function show(): Workflow<void>; /** * Hides an element by setting 'display: none' inline style. * * Applies an inline style of display: none, which takes precedence over * CSS rules. The element remains in the DOM but is completely removed from * the document flow and is not visible or accessible to screen readers. * * @param element - HTMLElement to hide (direct pattern) * @param selector - CSS selector to find elements to hide * @returns void for direct/selector patterns, Workflow for generators * * @example Dismissible alerts with yield* * ```typescript * import { watch, hide, addClass } from 'watch-selector'; * * watch('.alert', function* () { * yield* click('.dismiss-btn', function* () { * // Add fade out animation * yield* addClass('fade-out'); * * // Hide after animation completes * setTimeout(() => { * yield* hide(); * }, 300); * }); * * // Auto-dismiss after 5 seconds * yield* onMount(function* () { * setTimeout(() => { * yield* addClass('fade-out'); * setTimeout(() => { * yield* hide(); * }, 300); * }, 5000); * }); * }); * ``` * * @example Filter/search with hiding * ```typescript * watch('.filter-container', function* () { * yield* input('.search-input', function* () { * const searchTerm = (yield* value()).toLowerCase(); * const items = yield* queryAll('.item'); * * for (const item of items) { * const text = it