{"id":14990361,"url":"https://github.com/doeixd/watch","last_synced_at":"2026-01-31T18:05:01.437Z","repository":{"id":239778847,"uuid":"800524661","full_name":"doeixd/watch","owner":"doeixd","description":"Watch is a bare-bones web component alternative. It adds event listeners to selectors + a bit extra.  😉","archived":false,"fork":false,"pushed_at":"2025-09-16T12:29:19.000Z","size":1750,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-25T08:26:38.610Z","etag":null,"topics":["dom-manipulation","javascript-framework","watch-selector","webcomponents"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/doeixd.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-05-14T13:53:55.000Z","updated_at":"2025-09-16T12:29:22.000Z","dependencies_parsed_at":null,"dependency_job_id":"2111a7d7-e606-4620-9490-4e6f05f1f240","html_url":"https://github.com/doeixd/watch","commit_stats":{"total_commits":31,"total_committers":1,"mean_commits":31.0,"dds":0.0,"last_synced_commit":"faa6b49f9c3a0f5ba069f19141850fdadcb9e465"},"previous_names":["doeixd/watch"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/doeixd/watch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doeixd%2Fwatch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doeixd%2Fwatch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doeixd%2Fwatch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doeixd%2Fwatch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/doeixd","download_url":"https://codeload.github.com/doeixd/watch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doeixd%2Fwatch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28949274,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-31T14:26:55.697Z","status":"ssl_error","status_checked_at":"2026-01-31T14:26:52.545Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dom-manipulation","javascript-framework","watch-selector","webcomponents"],"created_at":"2024-09-24T14:19:56.797Z","updated_at":"2026-01-31T18:05:01.429Z","avatar_url":"https://github.com/doeixd.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)\n[![Deno](https://img.shields.io/badge/Deno-Compatible-green.svg)](https://deno.land/)\n[![JSR](https://img.shields.io/badge/JSR-Published-orange.svg)](https://jsr.io/)\n\n# Watch 🕶️\n\n**A type-safe DOM observation library that keeps your JavaScript working when the HTML changes.**\n\nEver tried adding interactivity to a server-rendered site? You write event listeners, but then the DOM updates and your JavaScript stops working. Or you need different behavior for each instance of an element, but managing that state gets messy fast.\n\nWatch solves this by letting you attach persistent behaviors to CSS selectors. When new elements match your selector, they automatically get the behavior. When they're removed, everything cleans up automatically.\n\n**Perfect for:** Server-rendered sites, Chrome extensions, e-commerce templates, htmx apps, and anywhere you don't control the markup.\n\n## The Problem Watch Solves\n\nTraditional DOM manipulation breaks when content changes:\n\n```typescript\n// ❌ This stops working when buttons are re-rendered\ndocument.querySelectorAll('button').forEach(btn =\u003e {\n  let clicks = 0;\n  btn.addEventListener('click', () =\u003e {\n    clicks++; // State is lost if button is removed/added\n    btn.textContent = `Clicked ${clicks} times`;\n  });\n});\n```\n\nServer-rendered sites, Chrome extensions, and dynamic content make this worse. You need:\n- **Persistent behavior** that survives DOM changes\n- **Instance-specific state** for each element\n- **Automatic cleanup** to prevent memory leaks\n- **Type safety** so you know what elements you're working with\n\nWatch handles all of this automatically.\n\n\u003cbr\u003e\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Why Choose Watch?](#why-choose-watch)\n- [Installation](#installation)\n- [Documentation](#documentation)\n- [Core Concepts](#core-concepts)\n- [Real-World Examples](#real-world-examples)\n- [Advanced Features](#advanced-features)\n  - [Advanced Composition: Controllers \u0026 Behavior Layering](#advanced-composition-controllers--behavior-layering)\n  - [Component Composition: Building Hierarchies](#component-composition-building-hierarchies)\n  - [Building Higher-Level Abstractions](#building-higher-level-abstractions)\n  - [Scoped Watch: Isolated DOM Observation](#scoped-watch-isolated-dom-observation)\n- [Complete API Reference](#complete-api-reference)\n- [Performance \u0026 Browser Support](#performance--browser-support)\n- [Frequently Asked Questions](#frequently-asked-questions)\n- [License](#license)\n\n\u003cbr\u003e\n\n## Quick Start\n\n```typescript\nimport { watch, click, text } from 'watch-selector';\n\n// Main API - Make all buttons interactive\nwatch('button', function* () {\n  yield* click(() =\u003e {\n    yield* text('Button clicked!');\n  });\n});\n\n// Enhanced API - Direct function calls, cleaner syntax\nwatch('.counter-btn', function* (ctx) {\n  let count = 0;\n  \n  yield* ctx.click(function* () {\n    count++;\n    ctx.text(`Clicked ${count} times`);\n    ctx.addClass('clicked');\n  });\n});\n```\n\nThat's it! Watch handles all the DOM observation, state management, and cleanup automatically.\n\n\u003cbr\u003e\n\n## Why Choose Watch?\n\n### 🔍 **Persistent Element Behavior**\nYour code keeps working even when the DOM changes:\n```typescript\n// Traditional approach breaks when elements are added/removed\ndocument.querySelectorAll('button').forEach(btn =\u003e {\n  btn.addEventListener('click', handler); // Lost if button is re-rendered\n});\n\n// Watch approach persists automatically\nwatch('button', function* () {\n  yield* click(handler); // Works for all buttons, present and future\n});\n```\n\n### 🎯 **Type-Safe by Design**\nTypeScript knows what element you're working with:\n```typescript\nwatch('input[type=\"email\"]', function* () {\n  // TypeScript knows this is HTMLInputElement\n  yield on('blur', () =\u003e {\n    if (!self().value.includes('@')) { // .value is typed\n      yield addClass('error');\n    }\n  });\n});\n```\n\n### 🧠 **Element-Scoped State**\nEach element gets its own isolated state:\n```typescript\nwatch('.counter', function* () {\n  let count = 0; // This variable is unique to each counter element\n  yield click(() =\u003e {\n    count++; // Each counter maintains its own count\n    yield text(`Count: ${count}`);\n  });\n});\n```\n\n### 🔄 **Works Both Ways**\nFunctions work directly on elements and in generators:\n```typescript\n// Direct usage\nconst button = document.querySelector('button');\ntext(button, 'Hello');\n\n// Generator usage\nwatch('button', function* () {\n  yield text('Hello');\n});\n```\n\n### ⚡ **High Performance**\n- Single global observer for the entire application\n- Efficient batch processing of DOM changes\n- Automatic cleanup prevents memory leaks\n- Minimal memory footprint with WeakMap storage\n\n\u003cbr\u003e\n\n## Installation\n\n### npm/pnpm/yarn\n\n```bash\nnpm install watch-selector\n```\n\n### ESM CDN (no build required)\n\n```typescript\nimport { watch } from 'https://esm.sh/watch-selector';\n\n// Start using immediately\nwatch('button', function* () {\n  yield click(() =\u003e console.log('Hello from CDN!'));\n});\n```\n\n\u003cbr\u003e\n\n## Documentation\n\n### 📚 Comprehensive Guides\n\n- **[API Reference](./docs/API.md)** - Complete documentation of all functions, types, and patterns\n- **[Quick Reference](./docs/QUICK-REFERENCE.md)** - Concise guide to commonly used functions\n- **[Type Definitions](./docs/TYPES.md)** - Full TypeScript type reference\n- **[Explicit API Spec](./docs/EXPLICIT-API-SPEC.md)** - Specification for non-overloaded functions\n- **[Examples](./examples/)** - Real-world usage examples and patterns\n\n### 🚀 Getting Started\n\nThe library provides multiple ways to interact with the DOM:\n\n1. **Direct manipulation** - Call functions directly on elements\n2. **CSS selectors** - Target elements by selector strings  \n3. **Generator functions** - Compose behaviors with `yield`\n4. **Async generators** - Use `yield*` for type-safe async flows\n5. **Pure workflows** - Import from `/generator` for clean syntax\n\n```typescript\n// All these patterns are supported:\nimport { text, addClass } from 'watch-selector';\n\n// Direct element manipulation\nconst button = document.querySelector('button');\ntext(button, 'Click me!');\n\n// CSS selector manipulation  \ntext('#my-button', 'Click me!');\n\n// Generator pattern with yield*\nwatch('button', function* () {\n  yield* text('Click me!');\n  yield* addClass('interactive');\n});\n\n// Direct yield* - no $ helper needed\nwatch('button', function* () {\n  yield* text('Click me!');\n  yield* addClass('interactive');\n});\n\n// Enhanced context with direct method calls\nwatch('button', function* (ctx) {\n  ctx.text('Click me!');\n  ctx.addClass('interactive');\n  \n  // Event handlers still use yield*\n  yield* ctx.click(function* () {\n    ctx.toggleClass('clicked');\n  });\n});\n```\n\n### 🎯 Three API Styles + Enhanced Context\n\nWatch Selector offers three distinct API styles to match your preferences, plus an enhanced context option for maximum ergonomics:\n\n#### 1. **Overloaded API** (Default)\nThe flexible, context-aware API with intelligent overloading:\n```typescript\nimport { text, addClass, click } from 'watch-selector';\n\n// Functions adapt to context\ntext(element, 'Hello');        // Direct element\ntext('#button', 'Hello');      // Selector\nyield* text('Hello');          // Generator with type safety\n```\n\n#### 2. **Explicit API** \nClear, unambiguous function names that specify exactly what they do:\n```typescript\nimport * as explicit from 'watch-selector/explicit';\n\nexplicit.setTextElement(element, 'Hello');     // Set text on element\nexplicit.setTextFirst('#button', 'Hello');     // Set text on first match\nexplicit.setTextAll('.items', 'Updated');      // Set text on all matches\nexplicit.getTextElement(element);              // Get text from element\n```\n\n#### 3. **Fluent API**\njQuery-like chainable interface for elegant DOM manipulation:\n```typescript\nimport { selector, element, $ } from 'watch-selector/fluent';\n\nselector('#button')\n  .text('Click me!')\n  .addClass('primary', 'large')\n  .style('backgroundColor', 'blue')\n  .click(() =\u003e console.log('Clicked!'));\n\n// Or use $ for jQuery familiarity\n$('.items')\n  .addClass('found')\n  .each((el, i) =\u003e console.log(`Item ${i}:`, el));\n```\n\n#### 4. **Enhanced Context API** (Recommended)\nThe most ergonomic option with direct function calls:\n```typescript\nimport { watch } from 'watch-selector';\n\nwatch('button', function* (ctx) {\n  ctx.text('Click me!');           // Direct calls - no yield* needed\n  ctx.addClass('interactive');     // Clean, readable syntax\n  \n  yield* ctx.click(function* () {  // Event handlers still use yield*\n    ctx.toggleClass('clicked');\n  });\n});\n```\n\nChoose the style that best fits your needs:\n- **Enhanced Context**: Maximum ergonomics with direct calls (recommended)\n- **Overloaded**: Maximum flexibility and conciseness\n- **Explicit**: Crystal clear intent, no ambiguity\n- **Fluent**: Elegant chaining for multiple operations\n\nSee [examples/api-comparison.ts](./examples/api-comparison.ts) for detailed comparisons.\n\n\u003cbr\u003e\n\n## Core Concepts\n\n### Watchers\nObserve DOM elements and run generators when they appear:\n```typescript\nconst controller = watch('selector', function* () {\n  // This runs for each matching element\n  yield elementFunction;\n});\n```\n\n### Generators \u0026 Yield\nGenerators create persistent contexts that survive DOM changes:\n```typescript\nwatch('.component', function* () {\n  let state = 0; // Persists for each element's lifetime\n  \n  yield* click(() =\u003e {\n    state++; // State is maintained across events\n    yield* text(`State: ${state}`);\n  });\n  \n  // Cleanup happens automatically when element is removed\n});\n```\n\n**Why generators?** They provide:\n- **Persistent execution context** that lives with the element\n- **Declarative behavior** through yield statements\n- **Automatic cleanup** when elements are removed\n- **Composable patterns** for building complex behaviors\n\n### Element Context\nAccess the current element and its state:\n```typescript\nwatch('.counter', function* () {\n  const counter = createState('count', 0);\n  const element = self(); // Get current element\n  \n  yield* click(() =\u003e {\n    counter.update(c =\u003e c + 1);\n    yield* text(`Count: ${counter.get()}`);\n  });\n});\n```\n\n#### Context Parameter\nGenerators can optionally receive a context parameter for enhanced ergonomics:\n```typescript\n// Traditional approach (still works)\nwatch('button', function* () {\n  const element = self();\n  yield* click(() =\u003e console.log('Clicked!'));\n});\n\n// New context parameter approach\nwatch('button', function* (ctx) {\n  const element = ctx.self(); // Direct access via context\n  yield* click(() =\u003e console.log('Clicked!'));\n});\n```\n\n#### Enhanced Context API\nFor the most ergonomic experience, use `watch` with direct function calls:\n```typescript\nimport { watch } from 'watch-selector';\n\nwatch('button', function* (ctx) {\n  // DOM functions work as direct synchronous calls\n  ctx.text('Click me!');\n  ctx.addClass('interactive');\n  \n  const parent = ctx.parent();  // Direct return value\n  const children = ctx.children('.child');  // Direct return value\n  \n  // State management with direct calls\n  ctx.setState('count', 0);\n  const count = ctx.getState('count', 0);\n  \n  // Event handlers still use yield* for generator composition\n  yield* ctx.click(function* () {\n    const newCount = ctx.getState('count', 0) + 1;\n    ctx.setState('count', newCount);\n    ctx.text(`Clicked ${newCount} times`);\n    ctx.toggleClass('active');\n  });\n});\n```\n\n**Enhanced Context Benefits:**\n- **Direct synchronous calls** - No `yield*` needed for DOM/state functions\n- **Better discoverability** - TypeScript autocomplete shows all available methods\n- **Cleaner code** - More readable without yield* everywhere\n- **Type safety** - Full TypeScript inference for all return values\n- **Mixed patterns** - Event handlers still use generators for composition\n\n**API Comparison:**\n```typescript\n// Main API - uses yield*\nwatch('button', function* () {\n  yield* text('Hello');\n  const content = yield* text();\n  yield* addClass('active');\n});\n\n// Enhanced API - direct calls\nwatch('button', function* (ctx) {\n  ctx.text('Hello');\n  const content = ctx.text();\n  ctx.addClass('active');\n});\n```\n\n### State Management\nType-safe, element-scoped reactive state:\n```typescript\nconst counter = createState('count', 0);\nconst doubled = createComputed(() =\u003e counter.get() * 2, ['count']);\n\nwatchState('count', (newVal, oldVal) =\u003e {\n  console.log(`${oldVal} → ${newVal}`);\n});\n```\n\n\u003cbr\u003e\n\n## Real-World Examples\n\n### E-commerce Product Cards\n```typescript\nwatch('.product-card', function* () {\n  const inCart = createState('inCart', false);\n  \n  yield click('.add-to-cart', () =\u003e {\n    inCart.set(true);\n    yield text('.add-to-cart', 'Added to Cart!');\n    yield addClass('in-cart');\n  });\n  \n  yield click('.remove-from-cart', () =\u003e {\n    inCart.set(false);\n    yield text('.add-to-cart', 'Add to Cart');\n    yield removeClass('in-cart');\n  });\n});\n```\n\n### Form Validation\n```typescript\nwatch('input[required]', function* () {\n  yield on('blur', () =\u003e {\n    if (!self().value.trim()) {\n      yield addClass('error');\n      yield text('.error-message', 'This field is required');\n    } else {\n      yield removeClass('error');\n      yield text('.error-message', '');\n    }\n  });\n});\n```\n\n### Dynamic Content Loading\n```typescript\nwatch('.lazy-content', async function* () {\n  yield text('Loading...');\n  \n  yield onVisible(async () =\u003e {\n    const response = await fetch(self().dataset.url);\n    const html = await response.text();\n    yield html(html);\n  });\n});\n```\n\n\u003cbr\u003e\n\n## Advanced Features\n\n### Advanced Composition: Controllers \u0026 Behavior Layering\n\nWatch introduces **WatchController** objects that transform the traditional fire-and-forget watch operations into managed, extensible systems. Controllers enable **Behavior Layering** - the ability to add multiple independent behaviors to the same set of elements.\n\n### **WatchController Fundamentals**\n\nEvery `watch()` call returns a `WatchController` instead of a simple cleanup function:\n\n```typescript\nimport { watch, layer, getInstances, destroy } from 'watch-selector';\n\n// Basic controller usage\nconst cardController = watch('.product-card', function* () {\n  // Core business logic\n  const inCart = createState('inCart', false);\n  yield on('click', '.add-btn', () =\u003e inCart.set(true));\n  yield text('Add to Cart');\n});\n\n// The controller provides a handle to the watch operation\nconsole.log(`Watching ${cardController.getInstances().size} product cards`);\n```\n\n### **Behavior Layering**\n\nAdd multiple independent behaviors to the same elements:\n\n```typescript\n// Layer 1: Core functionality\nconst cardController = watch('.product-card', function* () {\n  const inCart = createState('inCart', false);\n  yield on('click', '.add-btn', () =\u003e inCart.set(true));\n});\n\n// Layer 2: Analytics (added later, different module)\ncardController.layer(function* () {\n  yield onVisible(() =\u003e analytics.track('product-view', {\n    id: self().dataset.productId\n  }));\n});\n\n// Layer 3: Animations (added conditionally)\nif (enableAnimations) {\n  cardController.layer(function* () {\n    yield watchState('inCart', (inCart) =\u003e {\n      if (inCart) {\n        yield addClass('animate-add-to-cart');\n      }\n    });\n  });\n}\n```\n\n### **Dual API: Methods vs Functions**\n\nControllers support both object-oriented and functional patterns:\n\n```typescript\n// Method-based (OOP style)\nconst controller = watch('.component', baseGenerator);\ncontroller.layer(enhancementGenerator);\ncontroller.layer(analyticsGenerator);\n\nconst instances = controller.getInstances();\ncontroller.destroy();\n\n// Function-based (FP style)\nconst controller = watch('.component', baseGenerator);\nlayer(controller, enhancementGenerator);\nlayer(controller, analyticsGenerator);\n\nconst instances = getInstances(controller);\ndestroy(controller);\n```\n\n### **Instance Introspection**\n\nControllers provide read-only access to managed instances:\n\n```typescript\nconst controller = watch('button', function* () {\n  const clickCount = createState('clicks', 0);\n  yield click(() =\u003e clickCount.update(n =\u003e n + 1));\n});\n\n// Inspect all managed instances\nconst instances = controller.getInstances();\ninstances.forEach((instance, element) =\u003e {\n  console.log(`Button ${element.id}:`, instance.getState());\n});\n\n// State is read-only from the outside\nconst buttonState = instances.get(someButton)?.getState();\n// { clicks: 5 } - snapshot of current state\n```\n\n### **Real-World Example: Composable Product Cards**\n\nThis example demonstrates how behavior layering enables clean separation of concerns:\n\n```typescript\n// --- Core product card functionality ---\n// File: components/product-card.ts\nexport const productController = watch('.product-card', function* () {\n  const inCart = createState('inCart', false);\n  const quantity = createState('quantity', 1);\n  \n  yield on('click', '.add-btn', () =\u003e {\n    inCart.set(true);\n    // Update cart through global state or API\n  });\n  \n  yield on('click', '.quantity-btn', (e) =\u003e {\n    const delta = e.target.dataset.delta;\n    quantity.update(q =\u003e Math.max(1, q + parseInt(delta)));\n  });\n});\n\n// --- Analytics layer ---\n// File: analytics/product-tracking.ts\nimport { productController } from '../components/product-card';\n\nproductController.layer(function* () {\n  // Track product views\n  yield onVisible(() =\u003e {\n    analytics.track('product_viewed', {\n      product_id: self().dataset.productId,\n      category: self().dataset.category\n    });\n  });\n  \n  // Track cart additions\n  yield watchState('inCart', (inCart, wasInCart) =\u003e {\n    if (inCart \u0026\u0026 !wasInCart) {\n      analytics.track('product_added_to_cart', {\n        product_id: self().dataset.productId,\n        quantity: getState('quantity')\n      });\n    }\n  });\n});\n\n// --- Animation layer ---\n// File: animations/product-animations.ts\nimport { productController } from '../components/product-card';\n\nproductController.layer(function* () {\n  // Animate cart additions\n  yield watchState('inCart', (inCart) =\u003e {\n    if (inCart) {\n      yield addClass('animate-add-to-cart');\n      yield delay(300);\n      yield removeClass('animate-add-to-cart');\n    }\n  });\n  \n  // Hover effects\n  yield on('mouseenter', () =\u003e yield addClass('hover-highlight'));\n  yield on('mouseleave', () =\u003e yield removeClass('hover-highlight'));\n});\n\n// --- Usage in main application ---\n// File: main.ts\nimport './components/product-card';\nimport './analytics/product-tracking';\nimport './animations/product-animations';\n\n// All layers are automatically active\n// Analytics and animations are completely independent\n// Each can be enabled/disabled or modified without affecting others\n```\n\n### **State Communication Between Layers**\n\nLayers communicate through shared element state:\n\n```typescript\n// Layer 1: Set up shared state\nconst formController = watch('form', function* () {\n  const isValid = createState('isValid', false);\n  const errors = createState('errors', []);\n  \n  yield on('input', () =\u003e {\n    const validation = validateForm(self());\n    isValid.set(validation.isValid);\n    errors.set(validation.errors);\n  });\n});\n\n// Layer 2: React to validation state\nformController.layer(function* () {\n  yield watchState('isValid', (valid) =\u003e {\n    yield toggleClass('form-invalid', !valid);\n  });\n  \n  yield watchState('errors', (errors) =\u003e {\n    yield text('.error-display', errors.join(', '));\n  });\n});\n\n// Layer 3: Conditional behavior based on state\nformController.layer(function* () {\n  yield on('submit', (e) =\u003e {\n    if (!getState('isValid')) {\n      e.preventDefault();\n      yield addClass('shake-animation');\n    }\n  });\n});\n```\n\n### **Controller Lifecycle Management**\n\nControllers are singleton instances per target - calling `watch()` multiple times with the same selector returns the same controller:\n\n```typescript\n// These all return the same controller instance\nconst controller1 = watch('.my-component', generator1);\nconst controller2 = watch('.my-component', generator2); // Same controller!\nconst controller3 = watch('.my-component', generator3); // Same controller!\n\n// All generators are layered onto the same controller\nconsole.log(controller1 === controller2); // true\nconsole.log(controller1 === controller3); // true\n\n// Clean up destroys all layers\ncontroller1.destroy(); // Removes all behaviors for '.my-component'\n```\n\n### **Integration with Scoped Utilities**\n\nControllers work seamlessly with scoped watchers:\n\n```typescript\n// Create a scoped controller\nconst container = document.querySelector('#dashboard');\nconst scopedController = scopedWatchWithController(container, '.widget', function* () {\n  yield addClass('widget-base');\n});\n\n// Layer additional behaviors on the scoped controller\nscopedController.controller.layer(function* () {\n  yield addClass('widget-enhanced');\n  yield on('click', () =\u003e console.log('Scoped widget clicked'));\n});\n\n// Inspect scoped instances\nconst scopedInstances = scopedController.controller.getInstances();\nconsole.log(`Managing ${scopedInstances.size} widgets in container`);\n```\n\n\u003cbr\u003e\n\n### Component Composition: Building Hierarchies\n\nWatch supports full parent-child component communication, allowing you to build complex, nested, and encapsulated UIs with reactive relationships.\n\n### Child-to-Parent: Exposing APIs with `createChildWatcher`\n\nA child component can `return` an API from its generator. The parent can then use `createChildWatcher` to get a live collection of these APIs.\n\n**Child Component**\n```typescript\n// Counter button that exposes an API\nfunction* counterButton() {\n  let count = 0;\n  \n  // Set initial text and handle clicks\n  yield text(`Count: ${count}`);\n  yield click(() =\u003e {\n    count++;\n    yield text(`Count: ${count}`);\n  });\n\n  // Define and return a public API\n  return {\n    getCount: () =\u003e count,\n    reset: () =\u003e {\n      count = 0;\n      yield text(`Count: ${count}`);\n      console.log(`Button ${self().id} was reset.`);\n    },\n    increment: () =\u003e {\n      count++;\n      yield text(`Count: ${count}`);\n    }\n  };\n}\n```\n\n**Parent Component**\n```typescript\nimport { watch, child, click } from 'watch-selector';\n\nwatch('.button-container', function*() {\n  // `childApis` is a reactive Map: Map\u003cHTMLButtonElement, { getCount, reset, increment }\u003e\n  const childApis = child('button.counter', counterButton);\n\n  // Parent can interact with children's APIs\n  yield click('.reset-all-btn', () =\u003e {\n    console.log('Resetting all child buttons...');\n    for (const api of childApis.values()) {\n      api.reset();\n    }\n  });\n  \n  yield click('.sum-btn', () =\u003e {\n    const total = Array.from(childApis.values()).reduce((sum, api) =\u003e sum + api.getCount(), 0);\n    console.log(`Total count across all buttons: ${total}`);\n  });\n});\n```\n\n### Parent-to-Child: Accessing the Parent with `getParentContext`\n\nA child can access its parent's context and API, creating a top-down communication channel.\n\n**Parent Component**\n```typescript\nwatch('form#main-form', function*() {\n  const isValid = createState('valid', false);\n  \n  // Form validation logic...\n  \n  // The parent's API\n  return {\n    submitForm: () =\u003e {\n      if (isValid.get()) {\n        self().submit();\n      }\n    },\n    isValid: () =\u003e isValid.get()\n  };\n});\n```\n\n**Child Component (inside the form)**\n```typescript\nimport { getParentContext, on, self } from 'watch-selector';\n\nwatch('input.submit-on-enter', function*() {\n  // Get the parent form's context and API with full type safety\n  const parentForm = getParentContext\u003cHTMLFormElement, { \n    submitForm: () =\u003e void; \n    isValid: () =\u003e boolean \n  }\u003e();\n\n  yield on('keydown', (e) =\u003e {\n    if (e.key === 'Enter' \u0026\u0026 parentForm) {\n      e.preventDefault();\n      if (parentForm.api.isValid()) {\n        parentForm.api.submitForm(); // Call the parent's API method\n      }\n    }\n  });\n});\n```\n\n### Real-World Example: Interactive Counter Dashboard\n\n```typescript\n// Child counter component\nfunction* counterWidget() {\n  let count = 0;\n  const startTime = Date.now();\n  \n  yield addClass('counter-widget');\n  yield text(`Count: ${count}`);\n  \n  yield click(() =\u003e {\n    count++;\n    yield text(`Count: ${count}`);\n    yield addClass('updated');\n    setTimeout(() =\u003e yield removeClass('updated'), 200);\n  });\n  \n  // Public API for parent interaction\n  return {\n    getCount: () =\u003e count,\n    getRate: () =\u003e count / ((Date.now() - startTime) / 1000),\n    reset: () =\u003e {\n      count = 0;\n      yield text(`Count: ${count}`);\n    },\n    setCount: (newCount: number) =\u003e {\n      count = newCount;\n      yield text(`Count: ${count}`);\n    }\n  };\n}\n\n// Parent dashboard component\nfunction* counterDashboard() {\n  const counters = child('.counter', counterWidget);\n  \n  // Dashboard controls\n  yield click('.reset-all', () =\u003e {\n    counters.forEach(api =\u003e api.reset());\n  });\n  \n  yield click('.show-stats', () =\u003e {\n    const stats = Array.from(counters.values()).map(api =\u003e ({\n      count: api.getCount(),\n      rate: api.getRate()\n    }));\n    console.log('Dashboard stats:', stats);\n  });\n  \n  yield click('.distribute-evenly', () =\u003e {\n    const total = Array.from(counters.values()).reduce((sum, api) =\u003e sum + api.getCount(), 0);\n    const perCounter = Math.floor(total / counters.size);\n    counters.forEach(api =\u003e api.setCount(perCounter));\n  });\n  \n  // Parent API\n  return {\n    getTotalCount: () =\u003e Array.from(counters.values()).reduce((sum, api) =\u003e sum + api.getCount(), 0),\n    getCounterCount: () =\u003e counters.size,\n    resetAll: () =\u003e counters.forEach(api =\u003e api.reset())\n  };\n}\n\n// Usage\nwatch('.dashboard', counterDashboard);\n```\n\n\u003cbr\u003e\n\n### Building Higher-Level Abstractions\n\nWatch's primitive functions are designed to be composable building blocks for more sophisticated abstractions. You can integrate templating engines, routing libraries, state management solutions, and domain-specific tools while maintaining Watch's ergonomic patterns.\n\n### Writing Custom Abstractions\n\nThe key to building great abstractions with Watch is following the established patterns:\n\n#### 1. Dual API Pattern\n\nMake your functions work both directly and in generators:\n\n```typescript\n// Custom templating integration\nexport function template(templateOrElement: string | HTMLElement, data?: any): any {\n  // Direct usage\n  if (arguments.length === 2 \u0026\u0026 (typeof templateOrElement === 'string' || templateOrElement instanceof HTMLElement)) {\n    const element = resolveElement(templateOrElement);\n    if (element) {\n      element.innerHTML = renderTemplate(templateOrElement as string, data);\n    }\n    return;\n  }\n  \n  // Generator usage\n  if (arguments.length === 1) {\n    const templateStr = templateOrElement as string;\n    return ((element: HTMLElement) =\u003e {\n      element.innerHTML = renderTemplate(templateStr, data || {});\n    }) as ElementFn\u003cHTMLElement\u003e;\n  }\n  \n  // Selector + data usage\n  const [templateStr, templateData] = arguments;\n  return ((element: HTMLElement) =\u003e {\n    element.innerHTML = renderTemplate(templateStr, templateData);\n  }) as ElementFn\u003cHTMLElement\u003e;\n}\n\n// Usage examples\nconst element = document.querySelector('.content');\ntemplate(element, '\u003ch1\u003e{{title}}\u003c/h1\u003e', { title: 'Hello' });\n\n// Or in generators\nwatch('.dynamic-content', function* () {\n  yield template('\u003cdiv\u003e{{message}}\u003c/div\u003e', { message: 'Dynamic!' });\n});\n```\n\n#### 2. Context-Aware Functions\n\nCreate functions that understand the current element context:\n\n```typescript\n// Custom router integration\nexport function route(pattern: string, handler: () =\u003e void): ElementFn\u003cHTMLElement\u003e {\n  return (element: HTMLElement) =\u003e {\n    const currentPath = window.location.pathname;\n    const matches = matchRoute(pattern, currentPath);\n    \n    if (matches) {\n      // Store route params in element context\n      if (!element.dataset.routeParams) {\n        element.dataset.routeParams = JSON.stringify(matches.params);\n      }\n      handler();\n    }\n  };\n}\n\n// Route parameters helper\nexport function routeParams\u003cT = Record\u003cstring, string\u003e\u003e(): T {\n  const element = self();\n  const params = element.dataset.routeParams;\n  return params ? JSON.parse(params) : {};\n}\n\n// Usage\nwatch('[data-route]', function* () {\n  yield route('/users/:id', () =\u003e {\n    const { id } = routeParams\u003c{ id: string }\u003e();\n    yield template('\u003cdiv\u003eUser ID: {{id}}\u003c/div\u003e', { id });\n  });\n});\n```\n\n#### 3. State Integration\n\nBuild abstractions that work with Watch's state system:\n\n```typescript\n// Custom form validation abstraction\nexport function validateForm(schema: ValidationSchema): ElementFn\u003cHTMLFormElement\u003e {\n  return (form: HTMLFormElement) =\u003e {\n    const errors = createState('validation-errors', {});\n    const isValid = createComputed(() =\u003e Object.keys(errors.get()).length === 0, ['validation-errors']);\n    \n    // Validate on input changes\n    const inputs = form.querySelectorAll('input, select, textarea');\n    inputs.forEach(input =\u003e {\n      input.addEventListener('blur', () =\u003e {\n        const fieldErrors = validateField(input.name, input.value, schema);\n        errors.update(current =\u003e ({\n          ...current,\n          [input.name]: fieldErrors\n        }));\n      });\n    });\n    \n    // Expose validation state\n    form.dataset.valid = isValid().toString();\n  };\n}\n\n// Usage\nwatch('form.needs-validation', function* () {\n  yield validateForm({\n    email: { required: true, email: true },\n    password: { required: true, minLength: 8 }\n  });\n  \n  yield submit((e) =\u003e {\n    const isValid = getState('validation-errors');\n    if (Object.keys(isValid).length \u003e 0) {\n      e.preventDefault();\n    }\n  });\n});\n```\n\n### Templating Engine Integration\n\nHere's how to integrate popular templating engines:\n\n#### Handlebars Integration\n\n```typescript\nimport Handlebars from 'handlebars';\n\n// Create a templating abstraction\nexport function handlebars(templateSource: string, data?: any): ElementFn\u003cHTMLElement\u003e;\nexport function handlebars(element: HTMLElement, templateSource: string, data: any): void;\nexport function handlebars(...args: any[]): any {\n  if (args.length === 3) {\n    // Direct usage: handlebars(element, template, data)\n    const [element, templateSource, data] = args;\n    const template = Handlebars.compile(templateSource);\n    element.innerHTML = template(data);\n    return;\n  }\n  \n  if (args.length === 2) {\n    // Generator usage: yield handlebars(template, data)\n    const [templateSource, data] = args;\n    return (element: HTMLElement) =\u003e {\n      const template = Handlebars.compile(templateSource);\n      element.innerHTML = template(data);\n    };\n  }\n  \n  // Template only - data from state\n  const [templateSource] = args;\n  return (element: HTMLElement) =\u003e {\n    const template = Handlebars.compile(templateSource);\n    const data = getAllState(); // Get all state as template context\n    element.innerHTML = template(data);\n  };\n}\n\n// Helper for reactive templates\nexport function reactiveTemplate(templateSource: string, dependencies: string[]): ElementFn\u003cHTMLElement\u003e {\n  return (element: HTMLElement) =\u003e {\n    const template = Handlebars.compile(templateSource);\n    \n    const render = () =\u003e {\n      const data = getAllState();\n      element.innerHTML = template(data);\n    };\n    \n    // Re-render when dependencies change\n    dependencies.forEach(dep =\u003e {\n      watchState(dep, render);\n    });\n    \n    // Initial render\n    render();\n  };\n}\n\n// Usage\nwatch('.user-profile', function* () {\n  const user = createState('user', { name: 'John', email: 'john@example.com' });\n  \n  // Template updates automatically when user state changes\n  yield reactiveTemplate(`\n    \u003ch2\u003e{{user.name}}\u003c/h2\u003e\n    \u003cp\u003e{{user.email}}\u003c/p\u003e\n  `, ['user']);\n  \n  yield click('.edit-btn', () =\u003e {\n    user.update(u =\u003e ({ ...u, name: 'Jane' }));\n  });\n});\n```\n\n#### Lit-html Integration\n\n```typescript\nimport { html, render } from 'lit-html';\n\nexport function litTemplate(template: TemplateResult): ElementFn\u003cHTMLElement\u003e;\nexport function litTemplate(element: HTMLElement, template: TemplateResult): void;\nexport function litTemplate(...args: any[]): any {\n  if (args.length === 2) {\n    const [element, template] = args;\n    render(template, element);\n    return;\n  }\n  \n  const [template] = args;\n  return (element: HTMLElement) =\u003e {\n    render(template, element);\n  };\n}\n\n// Usage with reactive updates\nwatch('.todo-list', function* () {\n  const todos = createState('todos', [\n    { id: 1, text: 'Learn Watch', done: false },\n    { id: 2, text: 'Build something awesome', done: false }\n  ]);\n  \n  // Template function that uses current state\n  const todoTemplate = () =\u003e html`\n    \u003cul\u003e\n      ${todos.get().map(todo =\u003e html`\n        \u003cli class=\"${todo.done ? 'done' : ''}\"\u003e\n          \u003cinput type=\"checkbox\" .checked=${todo.done} \n                 @change=${() =\u003e toggleTodo(todo.id)}\u003e\n          ${todo.text}\n        \u003c/li\u003e\n      `)}\n    \u003c/ul\u003e\n  `;\n  \n  // Re-render when todos change\n  watchState('todos', () =\u003e {\n    yield litTemplate(todoTemplate());\n  });\n  \n  // Initial render\n  yield litTemplate(todoTemplate());\n});\n```\n\n### Router Integration\n\nCreate routing abstractions that work seamlessly with Watch:\n\n```typescript\n// Simple router abstraction\nclass WatchRouter {\n  private routes = new Map\u003cstring, RouteHandler\u003e();\n  \n  route(pattern: string, handler: RouteHandler): ElementFn\u003cHTMLElement\u003e {\n    this.routes.set(pattern, handler);\n    \n    return (element: HTMLElement) =\u003e {\n      const checkRoute = () =\u003e {\n        const path = window.location.pathname;\n        const match = this.matchRoute(pattern, path);\n        \n        if (match) {\n          // Store route context\n          element.dataset.routeParams = JSON.stringify(match.params);\n          element.dataset.routeQuery = JSON.stringify(match.query);\n          \n          // Execute handler with route context\n          handler(match);\n        }\n      };\n      \n      // Check on load and route changes\n      checkRoute();\n      window.addEventListener('popstate', checkRoute);\n      \n      // Cleanup\n      cleanup(() =\u003e {\n        window.removeEventListener('popstate', checkRoute);\n      });\n    };\n  }\n  \n  private matchRoute(pattern: string, path: string) {\n    // Route matching logic...\n    return { params: {}, query: {} };\n  }\n}\n\nconst router = new WatchRouter();\n\n// Route-aware helper functions\nexport function routeParams\u003cT = Record\u003cstring, any\u003e\u003e(): T {\n  const element = self();\n  const params = element.dataset.routeParams;\n  return params ? JSON.parse(params) : {};\n}\n\nexport function routeQuery\u003cT = Record\u003cstring, any\u003e\u003e(): T {\n  const element = self();\n  const query = element.dataset.routeQuery;\n  return query ? JSON.parse(query) : {};\n}\n\nexport const route = router.route.bind(router);\n\n// Usage\nwatch('[data-route=\"/users/:id\"]', function* () {\n  yield route('/users/:id', ({ params }) =\u003e {\n    const userId = params.id;\n    \n    // Load user data\n    const user = createState('user', null);\n    \n    fetch(`/api/users/${userId}`)\n      .then(r =\u003e r.json())\n      .then(userData =\u003e user.set(userData));\n    \n    // Reactive template\n    watchState('user', (userData) =\u003e {\n      if (userData) {\n        yield handlebars(`\n          \u003cdiv class=\"user-profile\"\u003e\n            \u003ch1\u003e{{name}}\u003c/h1\u003e\n            \u003cp\u003e{{email}}\u003c/p\u003e\n          \u003c/div\u003e\n        `, userData);\n      }\n    });\n  });\n});\n```\n\n### State Management Integration\n\nIntegrate with external state management libraries:\n\n```typescript\n// Redux integration\nimport { Store } from 'redux';\n\nexport function connectRedux\u003cT\u003e(\n  store: Store\u003cT\u003e, \n  selector: (state: T) =\u003e any,\n  mapDispatchToProps?: any\n): ElementFn\u003cHTMLElement\u003e {\n  return (element: HTMLElement) =\u003e {\n    let currentValue = selector(store.getState());\n    \n    const handleChange = () =\u003e {\n      const newValue = selector(store.getState());\n      if (newValue !== currentValue) {\n        currentValue = newValue;\n        // Update element state\n        setState('redux-state', newValue);\n      }\n    };\n    \n    const unsubscribe = store.subscribe(handleChange);\n    \n    // Initial state\n    setState('redux-state', currentValue);\n    \n    // Provide dispatch function\n    if (mapDispatchToProps) {\n      const dispatchers = mapDispatchToProps(store.dispatch);\n      setState('redux-dispatch', dispatchers);\n    }\n    \n    cleanup(() =\u003e unsubscribe());\n  };\n}\n\n// Usage\nwatch('.connected-component', function* () {\n  yield connectRedux(\n    store,\n    state =\u003e state.user,\n    dispatch =\u003e ({\n      updateUser: (user) =\u003e dispatch({ type: 'UPDATE_USER', user })\n    })\n  );\n  \n  // Use Redux state in templates\n  watchState('redux-state', (user) =\u003e {\n    yield template('\u003cdiv\u003eHello {{name}}\u003c/div\u003e', user);\n  });\n  \n  yield click('.update-btn', () =\u003e {\n    const { updateUser } = getState('redux-dispatch');\n    updateUser({ name: 'New Name' });\n  });\n});\n```\n\n### Domain-Specific Abstractions\n\nCreate specialized tools for specific use cases:\n\n```typescript\n// E-commerce specific abstractions\nexport function cart(): ElementFn\u003cHTMLElement\u003e {\n  return (element: HTMLElement) =\u003e {\n    const items = createState('cart-items', []);\n    const total = createComputed(\n      () =\u003e items.get().reduce((sum, item) =\u003e sum + item.price * item.quantity, 0),\n      ['cart-items']\n    );\n    \n    // Expose cart API globally\n    window.cart = {\n      add: (item) =\u003e items.update(current =\u003e [...current, item]),\n      remove: (id) =\u003e items.update(current =\u003e current.filter(i =\u003e i.id !== id)),\n      getTotal: () =\u003e total()\n    };\n  };\n}\n\nexport function addToCart(productId: string, price: number): ElementFn\u003cHTMLButtonElement\u003e {\n  return (button: HTMLButtonElement) =\u003e {\n    button.addEventListener('click', () =\u003e {\n      window.cart.add({ id: productId, price, quantity: 1 });\n      \n      // Visual feedback\n      addClass(button, 'added');\n      setTimeout(() =\u003e removeClass(button, 'added'), 1000);\n    });\n  };\n}\n\n// Data fetching abstraction\nexport function fetchData\u003cT\u003e(\n  url: string, \n  options?: RequestInit\n): ElementFn\u003cHTMLElement\u003e {\n  return (element: HTMLElement) =\u003e {\n    const data = createState\u003cT | null\u003e('fetch-data', null);\n    const loading = createState('fetch-loading', true);\n    const error = createState\u003cError | null\u003e('fetch-error', null);\n    \n    fetch(url, options)\n      .then(response =\u003e response.json())\n      .then(result =\u003e {\n        data.set(result);\n        loading.set(false);\n      })\n      .catch(err =\u003e {\n        error.set(err);\n        loading.set(false);\n      });\n  };\n}\n\n// Usage of domain abstractions\nwatch('.product-page', function* () {\n  // Initialize cart\n  yield cart();\n  \n  // Fetch product data\n  yield fetchData('/api/products/123');\n  \n  // Reactive content based on loading state\n  watchState('fetch-loading', (isLoading) =\u003e {\n    if (isLoading) {\n      yield template('\u003cdiv class=\"loading\"\u003eLoading...\u003c/div\u003e');\n    }\n  });\n  \n  // Reactive content based on data\n  watchState('fetch-data', (product) =\u003e {\n    if (product) {\n      yield template(`\n        \u003cdiv class=\"product\"\u003e\n          \u003ch1\u003e{{name}}\u003c/h1\u003e\n          \u003cp\u003e{{description}}\u003c/p\u003e\n          \u003cspan class=\"price\"\u003e${{price}}\u003c/span\u003e\n          \u003cbutton class=\"add-to-cart\"\u003eAdd to Cart\u003c/button\u003e\n        \u003c/div\u003e\n      `, product);\n    }\n  });\n  \n  // Add to cart functionality\n  yield click('.add-to-cart', () =\u003e {\n    const product = getState('fetch-data');\n    yield addToCart(product.id, product.price);\n  });\n});\n```\n\n### Creating Reusable Component Libraries\n\nBuild component libraries that follow Watch's patterns:\n\n```typescript\n// UI Component library built on Watch\nexport const UI = {\n  // Modal component\n  modal(options: { title?: string, closable?: boolean } = {}): ElementFn\u003cHTMLElement\u003e {\n    return (element: HTMLElement) =\u003e {\n      const isOpen = createState('modal-open', false);\n      \n      // Setup modal structure\n      yield template(`\n        \u003cdiv class=\"modal-backdrop\" style=\"display: none;\"\u003e\n          \u003cdiv class=\"modal-content\"\u003e\n            ${options.title ? `\u003ch2\u003e${options.title}\u003c/h2\u003e` : ''}\n            \u003cdiv class=\"modal-body\"\u003e\u003c/div\u003e\n            ${options.closable ? '\u003cbutton class=\"modal-close\"\u003e×\u003c/button\u003e' : ''}\n          \u003c/div\u003e\n        \u003c/div\u003e\n      `);\n      \n      // Show/hide logic\n      watchState('modal-open', (open) =\u003e {\n        const backdrop = el('.modal-backdrop');\n        if (backdrop) {\n          backdrop.style.display = open ? 'flex' : 'none';\n        }\n      });\n      \n      if (options.closable) {\n        yield click('.modal-close', () =\u003e {\n          isOpen.set(false);\n        });\n      }\n      \n      // Expose modal API\n      return {\n        open: () =\u003e isOpen.set(true),\n        close: () =\u003e isOpen.set(false),\n        toggle: () =\u003e isOpen.update(current =\u003e !current)\n      };\n    };\n  },\n  \n  // Tabs component\n  tabs(): ElementFn\u003cHTMLElement\u003e {\n    return (element: HTMLElement) =\u003e {\n      const activeTab = createState('active-tab', 0);\n      \n      // Setup tab navigation\n      const tabButtons = all('.tab-button');\n      const tabPanels = all('.tab-panel');\n      \n      tabButtons.forEach((button, index) =\u003e {\n        button.addEventListener('click', () =\u003e {\n          activeTab.set(index);\n        });\n      });\n      \n      // Show/hide panels based on active tab\n      watchState('active-tab', (active) =\u003e {\n        tabPanels.forEach((panel, index) =\u003e {\n          panel.style.display = index === active ? 'block' : 'none';\n        });\n        \n        tabButtons.forEach((button, index) =\u003e {\n          button.classList.toggle('active', index === active);\n        });\n      });\n    };\n  }\n};\n\n// Usage\nwatch('.my-modal', function* () {\n  const modalApi = yield UI.modal({ title: 'Settings', closable: true });\n  \n  yield click('.open-modal', () =\u003e {\n    modalApi.open();\n  });\n});\n\nwatch('.tab-container', function* () {\n  yield UI.tabs();\n});\n```\n\n### Best Practices for Abstractions\n\n1. **Follow the Dual API Pattern**: Make functions work both directly and in generators\n2. **Use Element-Scoped State**: Keep component state isolated per element instance\n3. **Provide Type Safety**: Use TypeScript generics and proper typing\n4. **Compose with Existing Functions**: Build on Watch's primitive functions\n5. **Handle Cleanup**: Always clean up external resources\n6. **Maintain Context**: Use `self()`, `el()`, and context functions appropriately\n7. **Return APIs**: Let components expose public interfaces through return values\n\nThis approach lets you build powerful, domain-specific libraries while maintaining Watch's ergonomic patterns and type safety guarantees.\n\n### Generator Abstractions: When to Wrap the Generator Itself\n\nSometimes you need to wrap or transform the generator pattern itself, not just individual functions. This is useful for cross-cutting concerns, meta-functionality, and standardizing behaviors across components.\n\n#### When to Use Generator Abstractions vs Function Abstractions\n\n**Use Function Abstractions When:**\n- Adding specific functionality (templating, validation, etc.)\n- Building domain-specific operations\n- Creating reusable behaviors\n- Extending the dual API pattern\n\n**Use Generator Abstractions When:**\n- Adding cross-cutting concerns (logging, performance, error handling)\n- Standardizing component patterns across teams\n- Injecting behavior into ALL components\n- Creating meta-frameworks or higher-level patterns\n- Managing component lifecycles uniformly\n\n#### Performance Monitoring Generator\n\n```typescript\n// Wraps any generator to add performance monitoring\nexport function withPerformanceMonitoring\u003cT extends HTMLElement\u003e(\n  name: string,\n  generator: () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e\n): () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e {\n  return function* () {\n    const startTime = performance.now();\n    console.log(`🚀 Component \"${name}\" starting...`);\n    \n    try {\n      // Execute the original generator\n      const originalGen = generator();\n      let result = originalGen.next();\n      \n      while (!result.done) {\n        // Time each yielded operation\n        const opStart = performance.now();\n        yield result.value;\n        const opEnd = performance.now();\n        \n        // Log slow operations\n        if (opEnd - opStart \u003e 10) {\n          console.warn(`⚠️ Slow operation in \"${name}\": ${opEnd - opStart}ms`);\n        }\n        \n        result = originalGen.next();\n      }\n      \n      const endTime = performance.now();\n      console.log(`✅ Component \"${name}\" initialized in ${endTime - startTime}ms`);\n      \n      return result.value; // Return the original generator's return value\n    } catch (error) {\n      const endTime = performance.now();\n      console.error(`❌ Component \"${name}\" failed after ${endTime - startTime}ms:`, error);\n      throw error;\n    }\n  };\n}\n\n// Usage\nconst monitoredButton = withPerformanceMonitoring('InteractiveButton', function* () {\n  yield addClass('interactive');\n  yield click(() =\u003e console.log('Clicked!'));\n  \n  return {\n    disable: () =\u003e yield addClass('disabled')\n  };\n});\n\nwatch('button', monitoredButton);\n```\n\n#### Error Boundary Generator\n\n```typescript\n// Wraps generators with error handling and fallback UI\nexport function withErrorBoundary\u003cT extends HTMLElement\u003e(\n  generator: () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e,\n  fallbackContent?: string,\n  onError?: (error: Error, element: T) =\u003e void\n): () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e {\n  return function* () {\n    try {\n      yield* generator();\n    } catch (error) {\n      console.error('Component error:', error);\n      \n      // Show fallback UI\n      if (fallbackContent) {\n        yield text(fallbackContent);\n        yield addClass('error-state');\n      }\n      \n      // Call custom error handler\n      if (onError) {\n        const element = self() as T;\n        onError(error as Error, element);\n      }\n      \n      // Return safe fallback API\n      return {\n        hasError: true,\n        retry: () =\u003e {\n          // Could implement retry logic here\n          window.location.reload();\n        }\n      };\n    }\n  };\n}\n\n// Usage\nconst safeComponent = withErrorBoundary(\n  function* () {\n    // This might throw an error\n    const data = JSON.parse(self().dataset.config || '');\n    yield template('\u003cdiv\u003e{{message}}\u003c/div\u003e', data);\n    \n    throw new Error('Something went wrong!'); // Simulated error\n  },\n  'Something went wrong. Please try again.',\n  (error, element) =\u003e {\n    // Send error to logging service\n    console.error('Logging error for element:', element.id, error);\n  }\n);\n\nwatch('.risky-component', safeComponent);\n```\n\n#### Feature Flag Generator\n\n```typescript\n// Wraps generators with feature flag checks\nexport function withFeatureFlag\u003cT extends HTMLElement\u003e(\n  flagName: string,\n  generator: () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e,\n  fallbackGenerator?: () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e\n): () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e {\n  return function* () {\n    const isEnabled = await checkFeatureFlag(flagName);\n    \n    if (isEnabled) {\n      console.log(`🎯 Feature \"${flagName}\" enabled`);\n      yield* generator();\n    } else if (fallbackGenerator) {\n      console.log(`🚫 Feature \"${flagName}\" disabled, using fallback`);\n      yield* fallbackGenerator();\n    } else {\n      console.log(`🚫 Feature \"${flagName}\" disabled, no fallback`);\n      // Component does nothing\n    }\n  };\n}\n\n// Usage\nconst newButtonBehavior = withFeatureFlag(\n  'enhanced-buttons',\n  function* () {\n    // New enhanced behavior\n    yield addClass('enhanced');\n    yield style({ \n      background: 'linear-gradient(45deg, #007bff, #0056b3)',\n      transition: 'all 0.3s ease'\n    });\n    yield click(() =\u003e {\n      yield addClass('clicked');\n      setTimeout(() =\u003e yield removeClass('clicked'), 300);\n    });\n  },\n  function* () {\n    // Fallback to old behavior\n    yield addClass('basic');\n    yield click(() =\u003e console.log('Basic click'));\n  }\n);\n\nwatch('button.enhanced', newButtonBehavior);\n```\n\n#### Lifecycle Management Generator\n\n```typescript\n// Adds standardized lifecycle hooks to any generator\nexport function withLifecycle\u003cT extends HTMLElement, R = any\u003e(\n  generator: () =\u003e Generator\u003cElementFn\u003cT\u003e, R, unknown\u003e,\n  options: {\n    onMount?: (element: T) =\u003e void;\n    onUnmount?: (element: T) =\u003e void;\n    onUpdate?: (element: T) =\u003e void;\n    enableDebug?: boolean;\n  } = {}\n): () =\u003e Generator\u003cElementFn\u003cT\u003e, R, unknown\u003e {\n  return function* () {\n    const element = self() as T;\n    const componentName = element.className || element.tagName.toLowerCase();\n    \n    // Mount lifecycle\n    if (options.onMount) {\n      options.onMount(element);\n    }\n    \n    if (options.enableDebug) {\n      console.log(`🔧 Mounting component: ${componentName}`);\n    }\n    \n    // Setup unmount cleanup\n    if (options.onUnmount) {\n      cleanup(() =\u003e {\n        if (options.enableDebug) {\n          console.log(`🗑️ Unmounting component: ${componentName}`);\n        }\n        options.onUnmount!(element);\n      });\n    }\n    \n    // Track updates if enabled\n    if (options.onUpdate) {\n      const observer = new MutationObserver(() =\u003e {\n        options.onUpdate!(element);\n      });\n      \n      observer.observe(element, {\n        attributes: true,\n        childList: true,\n        subtree: true\n      });\n      \n      cleanup(() =\u003e observer.disconnect());\n    }\n    \n    // Execute the wrapped generator\n    const result = yield* generator();\n    \n    if (options.enableDebug) {\n      console.log(`✅ Component initialized: ${componentName}`);\n    }\n    \n    return result;\n  };\n}\n\n// Usage\nconst lifecycleComponent = withLifecycle(\n  function* () {\n    const clickCount = createState('clicks', 0);\n    \n    yield click(() =\u003e {\n      clickCount.update(c =\u003e c + 1);\n      yield text(`Clicked ${clickCount.get()} times`);\n    });\n    \n    return {\n      getClicks: () =\u003e clickCount.get()\n    };\n  },\n  {\n    onMount: (el) =\u003e console.log(`Component mounted on:`, el),\n    onUnmount: (el) =\u003e console.log(`Component unmounted from:`, el),\n    onUpdate: (el) =\u003e console.log(`Component updated:`, el),\n    enableDebug: true\n  }\n);\n\nwatch('.lifecycle-component', lifecycleComponent);\n```\n\n#### A/B Testing Generator\n\n```typescript\n// Enables A/B testing at the component level\nexport function withABTest\u003cT extends HTMLElement\u003e(\n  testName: string,\n  variants: Record\u003cstring, () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e\u003e,\n  options: {\n    userIdGetter?: () =\u003e string;\n    onVariantShown?: (variant: string, userId: string) =\u003e void;\n  } = {}\n): () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e {\n  return function* () {\n    const userId = options.userIdGetter?.() || 'anonymous';\n    const variant = selectVariant(testName, userId, Object.keys(variants));\n    \n    // Track which variant was shown\n    if (options.onVariantShown) {\n      options.onVariantShown(variant, userId);\n    }\n    \n    // Store variant info on element for debugging\n    const element = self() as T;\n    element.dataset.abTest = testName;\n    element.dataset.abVariant = variant;\n    \n    console.log(`🧪 A/B Test \"${testName}\": showing variant \"${variant}\" to user ${userId}`);\n    \n    // Execute the selected variant\n    const selectedGenerator = variants[variant];\n    if (selectedGenerator) {\n      yield* selectedGenerator();\n    } else {\n      console.warn(`⚠️ A/B Test \"${testName}\": variant \"${variant}\" not found`);\n    }\n  };\n}\n\n// Usage\nconst abTestButton = withABTest(\n  'button-style-test',\n  {\n    control: function* () {\n      yield addClass('btn-primary');\n      yield text('Click Me');\n      yield click(() =\u003e console.log('Control clicked'));\n    },\n    \n    variant_a: function* () {\n      yield addClass('btn-success');\n      yield text('Take Action!');\n      yield style({ fontSize: '18px', fontWeight: 'bold' });\n      yield click(() =\u003e console.log('Variant A clicked'));\n    },\n    \n    variant_b: function* () {\n      yield addClass('btn-warning');\n      yield text('Get Started');\n      yield style({ borderRadius: '25px' });\n      yield click(() =\u003e console.log('Variant B clicked'));\n    }\n  },\n  {\n    userIdGetter: () =\u003e getCurrentUserId(),\n    onVariantShown: (variant, userId) =\u003e {\n      analytics.track('ab_test_variant_shown', {\n        test: 'button-style-test',\n        variant,\n        userId\n      });\n    }\n  }\n);\n\nwatch('.ab-test-button', abTestButton);\n```\n\n#### Permission-Based Generator\n\n```typescript\n// Wraps generators with permission checks\nexport function withPermissions\u003cT extends HTMLElement\u003e(\n  requiredPermissions: string[],\n  generator: () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e,\n  unauthorizedGenerator?: () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e\n): () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e {\n  return function* () {\n    const hasPermission = await checkPermissions(requiredPermissions);\n    \n    if (hasPermission) {\n      yield* generator();\n    } else {\n      console.log(`🔒 Access denied. Required permissions: ${requiredPermissions.join(', ')}`);\n      \n      if (unauthorizedGenerator) {\n        yield* unauthorizedGenerator();\n      } else {\n        // Default unauthorized behavior\n        yield addClass('unauthorized');\n        yield text('Access Denied');\n        yield click(() =\u003e {\n          alert('You do not have permission to use this feature.');\n        });\n      }\n    }\n  };\n}\n\n// Usage\nconst adminButton = withPermissions(\n  ['admin', 'user_management'],\n  function* () {\n    yield text('Delete User');\n    yield addClass('btn-danger');\n    yield click(() =\u003e {\n      if (confirm('Are you sure?')) {\n        deleteUser();\n      }\n    });\n  },\n  function* () {\n    yield text('Contact Admin');\n    yield addClass('btn-secondary');\n    yield click(() =\u003e {\n      window.location.href = 'mailto:admin@company.com';\n    });\n  }\n);\n\nwatch('.admin-action', adminButton);\n```\n\n#### Higher-Order Generator Composition\n\n```typescript\n// Combine multiple generator wrappers\nexport function compose\u003cT extends HTMLElement\u003e(\n  ...wrappers: Array\u003c(gen: () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e) =\u003e () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e\u003e\n) {\n  return (generator: () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e) =\u003e {\n    return wrappers.reduceRight((acc, wrapper) =\u003e wrapper(acc), generator);\n  };\n}\n\n// Usage - apply multiple concerns to a component\nconst enhancedComponent = compose(\n  // Applied in reverse order (inside-out)\n  gen =\u003e withPerformanceMonitoring('MyComponent', gen),\n  gen =\u003e withErrorBoundary(gen, 'Component failed to load'),\n  gen =\u003e withLifecycle(gen, { enableDebug: true }),\n  gen =\u003e withFeatureFlag('new-ui', gen, () =\u003e function* () {\n    yield text('Feature disabled');\n  })\n)(function* () {\n  // The actual component logic\n  const count = createState('count', 0);\n  \n  yield click(() =\u003e {\n    count.update(c =\u003e c + 1);\n    yield text(`Count: ${count.get()}`);\n  });\n  \n  return {\n    getCount: () =\u003e count.get()\n  };\n});\n\nwatch('.enhanced-component', enhancedComponent);\n```\n\n#### Component Factory Generator\n\n```typescript\n// Creates standardized component patterns\nexport function createComponent\u003cT extends HTMLElement\u003e(\n  name: string,\n  config: {\n    template?: string;\n    styles?: Record\u003cstring, string\u003e;\n    state?: Record\u003cstring, any\u003e;\n    methods?: Record\u003cstring, (...args: any[]) =\u003e any\u003e;\n    lifecycle?: {\n      onMount?: (element: T) =\u003e void;\n      onUnmount?: (element: T) =\u003e void;\n    };\n  }\n): () =\u003e Generator\u003cElementFn\u003cT\u003e, any, unknown\u003e {\n  return function* () {\n    const element = self() as T;\n    \n    // Apply template\n    if (config.template) {\n      yield html(config.template);\n    }\n    \n    // Apply styles\n    if (config.styles) {\n      yield style(config.styles);\n    }\n    \n    // Initialize state\n    const componentState: Record\u003cstring, any\u003e = {};\n    if (config.state) {\n      Object.entries(config.state).forEach(([key, initialValue]) =\u003e {\n        componentState[key] = createState(key, initialValue);\n      });\n    }\n    \n    // Lifecycle hooks\n    if (config.lifecycle?.onMount) {\n      config.lifecycle.onMount(element);\n    }\n    \n    if (config.lifecycle?.onUnmount) {\n      cleanup(() =\u003e config.lifecycle!.onUnmount!(element));\n    }\n    \n    // Create public API\n    const api: Record\u003cstring, any\u003e = {};\n    if (config.methods) {\n      Object.entries(config.methods).forEach(([methodName, method]) =\u003e {\n        api[methodName] = (...args: any[]) =\u003e {\n          return method.call({ element, state: componentState }, ...args);\n        };\n      });\n    }\n    \n    // Add state getters\n    Object.keys(componentState).forEach(key =\u003e {\n      api[`get${key.charAt(0).toUpperCase() + key.slice(1)}`] = () =\u003e {\n        return componentState[key].get();\n      };\n    });\n    \n    console.log(`🏗️ Component \"${name}\" created with API:`, Object.keys(api));\n    \n    return api;\n  };\n}\n\n// Usage - declarative component creation\nconst counterComponent = createComponent('Counter', {\n  template: '\u003cdiv class=\"counter\"\u003eCount: 0\u003c/div\u003e',\n  styles: {\n    padding: '10px',\n    border: '1px solid #ccc',\n    borderRadius: '4px'\n  },\n  state: {\n    count: 0\n  },\n  methods: {\n    increment() {\n      this.state.count.update(c =\u003e c + 1);\n      this.element.textContent = `Count: ${this.state.count.get()}`;\n    },\n    \n    reset() {\n      this.state.count.set(0);\n      this.element.textContent = 'Count: 0';\n    }\n  },\n  lifecycle: {\n    onMount: (el) =\u003e {\n      el.addEventListener('click', () =\u003e {\n        // Access the component API through return value\n      });\n    }\n  }\n});\n\nwatch('.auto-counter', counterComponent);\n```\n\n### When NOT to Use Generator Abstractions\n\n**Avoid generator wrapping when:**\n\n1. **Simple functionality** - Use function abstractions instead\n2. **One-off behaviors** - Don't abstract what you won't reuse\n3. **Performance critical** - Each wrapper adds overhead\n4. **Team confusion** - If it makes code harder to understand\n5. **Over-engineering** - Start simple, abstract when patterns emerge\n\n**Rule of thumb:** If you find yourself copying the same generator patterns across multiple components, consider a generator abstraction. If you're just adding functionality to elements, use function abstractions.\n\n\u003cbr\u003e\n\n### Scoped Watch: Isolated DOM Observation\n\nWhen you need precise control over DOM observation scope, **scoped watch** creates isolated observers for specific parent elements without event delegation.\n\n### **Key Benefits**\n\n- **🔒 Isolated Observation**: Each watcher has its own MutationObserver scoped to a specific parent\n- **🚫 No Event Delegation**: Direct DOM observation without event bubbling overhead  \n- **⚡ Better Performance**: Only watches the specific subtree you care about\n- **🧹 Automatic Cleanup**: Automatically disconnects when parent is removed from DOM\n- **🎛️ Granular Control**: Fine-tune what changes to observe (attributes, character data, etc.)\n\n### **Basic Usage**\n\n```typescript\nimport { scopedWatch, addClass, text } from 'watch-selector';\n\n// Watch for buttons only within a specific container\nconst container = document.querySelector('#my-container');\nconst watcher = scopedWatch(container, 'button', function* () {\n  yield addClass('scoped-button');\n  yield text('I was found by scoped watch!');\n});\n\n// Later cleanup\nwatcher.disconnect();\n```\n\n### **Advanced Options**\n\n```typescript\n// Watch attributes and character data within a form\nconst form = document.querySelector('form');\nconst formWatcher = scopedWatch(form, 'input', function* () {\n  yield addClass('monitored-input');\n  yield* setValue(''); // Clear on detection\n}, {\n  attributes: true,\n  attributeFilter: ['value', 'disabled'],\n  characterData: true,\n  subtree: true // Watch descendants (default: true)\n});\n```\n\n### **Batch Scoped Watching**\n\n```typescript\n// Watch multiple selectors within the same parent\nconst dashboard = document.querySelector('#dashboard');\nconst watchers = scopedWatchBatch(dashboard, [\n  {\n    selector: '.chart',\n    generator: function* () {\n      yield addClass('chart-initialized');\n      yield* setupChart();\n    }\n  },\n  {\n    selector: '.widget',\n    generator: function* () {\n      yield addClass('widget-ready');\n      yield* setupWidget();\n    },\n    options: { attributes: true }\n  }\n]);\n\n// Later cleanup all watchers\nwatchers.forEach(watcher =\u003e watcher.disconnect());\n```\n\n### **One-Time and Timeout Watchers**\n\n```typescript\n// Process only the first 3 matching elements\nconst firstThreeWatcher = scopedWatchOnce(list, '.item', function* () {\n  yield addClass('first-batch');\n  yield* setupSpecialBehavior();\n}, 3);\n\n// Auto-disconnect after 5 seconds\nconst tempWatcher = scopedWatchTimeout(container, '.temp-element', function* () {\n  yield addClass('temporary-highlight');\n  yield* animateIn();\n}, 5000);\n```\n\n### **Matcher Functions**\n\n```typescript\n// Use custom logic instead of CSS selectors\nconst submitButtonMatcher = (el: HTMLElement): el is HTMLButtonElement =\u003e {\n  return el.tagName === 'BUTTON' \u0026\u0026 \n         el.getAttribute('type') === 'submit' \u0026\u0026 \n         el.dataset.important === 'true';\n};\n\nconst watcher = scopedWatch(container, submitButtonMatcher, function* () {\n  yield addClass('important-submit');\n  yield style({ backgroundColor: 'red', color: 'white' });\n});\n```\n\n### **Full Context Integration**\n\nScoped watchers work seamlessly with all Watch primitives:\n\n```typescript\nconst watcher = scopedWatch(container, 'li', function* () {\n  // Context primitives work perfectly\n  const element = yield* self();\n  const siblings = yield* all('li');\n  const parentContext = yield* ctx();\n  \n  // State management\n  yield* createState('itemIndex', siblings.indexOf(element));\n  \n  // Event handling\n  yield onClick(function* () {\n    const index = yield* getState('itemIndex');\n    yield text(`Item ${index} clicked`);\n  });\n  \n  // Execution helpers\n  yield onClick(debounce(function* () {\n    yield addClass('debounced-click');\n  }, 300));\n});\n```\n\n### **When to Use Scoped Watch**\n\n**Use scoped watch when:**\n- You need to observe a specific container or component\n- Performance is critical (avoid global observer overhead)\n- You want isolated behavior that doesn't affect other parts of the page\n- You need fine-grained control over what changes to observe\n\n**Use regular watch when:**\n- You want to observe elements across the entire document\n- You need event delegation for dynamic content\n- You want the simplest possible setup\n\n### **Utility Functions**\n\n```typescript\n// Get all active watchers for a parent\nconst activeWatchers = getScopedWatchers(parent);\n\n// Disconnect all watchers for a parent\ndisconnectScopedWatchers(parent);\n\n// Check watcher status\nconsole.log('Active:', watcher.isActive());\nconsole.log('Parent:', watcher.getParent());\nconsole.log('Selector:', watcher.getSelector());\n```\n\n\u003cbr\u003e\n\n## Frequently Asked Questions\n\n### Why doesn't Watch include templating?\n\n**Short answer:** We believe in \"bring your own templating\" for maximum flexibility.\n\n**Long answer:** Watch is designed to integrate into existing pages where you don't control the DOM structure. This is common in:\n\n- **Server-driven websites** (Rails, Django, PHP applications)\n- **E-commerce platforms** with fixed templates\n- **CMS systems** like WordPress or Drupal\n- **Legacy applications** being modernized incrementally\n- **Browser extensions** working with arbitrary websites\n\nBy not including templating, Watch can focus on what it does best: reactive DOM observation and element lifecycle management. You can use any templating solution you prefer - Handlebars, Mustache, lit-html, or even just string concatenation.\n\nThat said, we may add an opinionated templating module in the future that integrates seamlessly with watch-selector's patterns, but it would be optional and composable with existing solutions.\n\n### Isn't this just jQuery `.live()` but more confusing?\n\n**Yes!** But with significant improvements:\n\n```javascript\n// jQuery .live() (deprecated)\n$(document).on('click', '.button', function() {\n  var clickCount = 0; // This doesn't work - shared across all buttons!\n  clickCount++;\n  $(this).text('Clicked ' + clickCount + ' times');\n});\n\n// Watch equivalent\nwatch('button', function* () {\n  let clickCount = 0; // This works - scoped per button instance\n  \n  yield click(() =\u003e {\n    clickCount++; // Each button has its own counter\n    yield text(`Clicked ${clickCount} times`);\n  });\n});\n```\n\n**Key improvements over jQuery:**\n\n1. **Type Safety**: Full TypeScript support with element type inference\n2. **Element-Scoped State**: Each element gets its own isolated state\n3. **Composable Behavior**: Generators can be mixed, matched, and reused  \n4. **Automatic Cleanup**: No memory leaks from forgotten event handlers\n5. **Modern JavaScript**: Uses generators, async/await, and ES modules\n6. **Performance**: Single global observer vs multiple event delegations\n\n### Why not React/Vue/Svelte/Alpine/htmx/Mithril?\n\n**I respect all those libraries!** They're excellent for their intended use cases. But they have different assumptions:\n\n**React/Vue/Svelte:**\n- Want complete control of rendering and state\n- Assume you're building a Single Page Application\n- Require build tools and complex toolchains\n- Don't play well with server-rendered markup you can't control\n\n**Alpine.js:**\n- Great library! Very similar philosophy to Watch\n- Less type-safe, more limited state management\n- Watch provides more sophisticated component composition\n\n**htmx:**\n- Excellent for server-driven interactions\n- Requires server-side coordination\n- Watch works purely client-side with any backend\n\n**Mithril:**\n- Lightweight and fast\n- Still assumes control over rendering\n- Not designed for enhancing existing markup\n\n**Watch is designed for different scenarios:**\n- Enhancing existing server-rendered pages\n- Adding interactivity without controlling the entire page\n- Working with legacy systems or third-party markup\n- Building browser extensions or user scripts\n- Gradual modernization of existing applications\n\n### Why not just use Web Components?\n\nWeb Components are great, but they have limitations for Watch's use cases:\n\n**Composition Challenges:**\n```javascript\n// Web Components - hard to compose behaviors\nclass MyButton extends HTMLElement {\n  connectedCallback() {\n    // How do you mix in other behaviors?\n    // How do you share this logic with other components?\n  }\n}\n\n// Watch - easy composition\nfunction* clickBehavior() { yield click(() =\u003e console.log('Clicked')); }\nfunction* hoverBehavior() { yield on('hover', () =\u003e console.log('Hovered')); }\n\nwatch('button', function* () {\n  yield* clickBehavior();\n  yield* hoverBehavior();\n  // Easy to mix and match behaviors\n});\n```\n\n**Pre-existing Markup:**\n```html\n\u003c!-- You can't easily turn this into a web component --\u003e\n\u003cdiv class=\"legacy-widget\" data-product-id=\"123\"\u003e\n  \u003cspan class=\"price\"\u003e$29.99\u003c/span\u003e\n  \u003cbutton class=\"add-to-cart\"\u003eAdd to Cart\u003c/button\u003e\n\u003c/div\u003e\n\n\u003c!-- But you can easily enhance it with Watch --\u003e\n\u003cscript\u003e\nwatch('.legacy-widget', function* () {\n  const productId = self().dataset.productId;\n  yield click('.add-to-cart', () =\u003e addToCart(productId));\n});\n\u003c/script\u003e\n```\n\n**Other Web Component limitations:**\n- Require defining custom elements upfront\n- Don't work well with server-rendered content\n- Limited cross-component communication\n- Heavyweight for simple enhancements\n- Browser compatibility considerations\n\n### How does this compare to arrive.js or mount-observer?\n\nYou're right - the core observation pattern is very similar! Watch builds on that foundation:\n\n**arrive.js:**\n```javascript\n// arrive.js\ndocument.arrive('.button', function() {\n  var element = this;\n  element.addEventListener('click', function() {\n    // No built-in state management\n    // No automatic cleanup\n    // No composition patterns\n  });\n});\n```\n\n**mount-observer:**\n```javascript\n// mount-observer  \nmountObserver.observe('.button', {\n  mount(element) {\n    // Similar observation pattern\n    // But no state, no composition, no generators\n  }\n});\n```\n\n**Watch adds:**\n\n1. **Lifecycle Context**: Persistent generator scope for each element\n2. **State Management**: Built-in element-scoped state with reactivity\n3. **Pseudo-Components**: Components with APIs, parent-child relationships\n4. **Type Safety**: Full TypeScript integration with element type inference\n5. **Composition**: Generators can be mixed, matched, and reused\n6. **Automatic Cleanup**: Memory leak prevention\n7. **Performance**: Optimized observation and delegation patterns\n\n**Think of it as:** arrive.js + state management + component composition + type safety + modern JavaScript patterns.\n\n### When should I NOT use Watch?\n\nWatch isn't the right choice for every project:\n\n**Don't use Watch when:**\n\n- **Building a new SPA from scratch** - Use React, Vue, or Svelte\n- **You control the entire page** - Modern frameworks might be better\n- **Server-side rendering is critical** - Use Next.js, Nuxt, or SvelteKit  \n- **You need complex routing** - Use a full framework with routing\n- **Team prefers component-based architecture** - Stick with what works\n- **Performance is absolutely critical** - Vanilla JS might be better\n- **You don't need reactivity** - Simple event listeners might suffice\n\n**DO use Watch when:**\n\n- Enhancing existing server-rendered pages\n- Building browser extensions or user scripts\n- Adding interactivity to CMS or e-commerce sites\n- Modernizing legacy applications gradually\n- Working with third-party markup you can't control\n- Building reusable behaviors for multiple projects\n- You want type safety without build complexity\n\n### Is Watch suitable for large applications?\n\n**Yes, with the right patterns:**\n\n**For large applications, use:**\n- Component composition with parent-child APIs\n- Generator abstractions for cross-cutting concerns\n- Custom higher-level abstractions for your domain\n- Performance optimization patterns (scoped observers, delegation)\n- TypeScript for type safety at scale\n\n**Watch scales well because:**\n- Each component is isolated and independently testable\n- Behaviors can be composed and reused across teams\n- Performance stays consistent regardless of component count\n- TypeScript catches integration issues early\n- No global state management complexity\n\nMany teams use Watch successfully in production applications with hundreds of components.\n\n### Does Watch support async generators and yield*?\n\n**Yes!** Watch has full support for advanced generator patterns:\n\n```typescript\n// ✅ Async generators\nwatch('.data-component', async function* () {\n  yield text('Loading...');\n  \n  const data = await fetch('/api/data').then(r =\u003e r.json());\n  yield template('\u003cdiv\u003e{{message}}\u003c/div\u003e', data);\n});\n\n// ✅ Generator delegation with yield*\nfunction* reusableBehavior() {\n  yield addClass('interactive');\n  yield click(() =\u003e console.log('Reusable click!'));\n}\n\nwatch('button', function* () {\n  yield* reusableBehavior();  // Delegate to another generator\n  yield text('Enhanced Button');\n});\n\n// ✅ Promise yields\nwatch('.promise-component', function* () {\n  yield new Promise(resolve =\u003e {\n    setTimeout(() =\u003e {\n      yield text('Delayed content');\n      resolve();\n    }, 1000);\n  });\n});\n\n// ✅ Nested composition\nfunction* withErrorHandling(innerGen) {\n  try {\n    yield* innerGen();\n  } catch (error) {\n    yield text('Error occurred');\n    yield addClass('error');\n  }\n}\n```\n\n**Supported patterns:**\n- **Async generators** with `async function*`\n- **Generator delegation** with `yield*`\n- **Promise yields** - automatically awaited\n- **Nested generators** - full recursion support\n- **Mixed sync/async** - seamless composition\n\n\u003cbr\u003e\n\n## Complete API Reference\n\n\u003e **Context Parameter Support**: Most generator context functions accept an optional `ctx?` parameter. This allows you to explicitly pass context instead of relying on `getCurrentContext()`. Both patterns work: `self()` uses global context, `self(ctx)` uses passed context.\n\n### Core Functions\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `watch` | `(target, generator) =\u003e WatchController` | Watch for elements and run generators (generators can accept optional context parameter) |\n| `run` | `(selector, generator) =\u003e void` | Run generator on existing elements (generators can accept optional context parameter) |\n| `runOn` | `(element, generator) =\u003e void` | Run generator on specific element (generators can accept optional context parameter) |\n| `layer` | `(controller, generator) =\u003e void` | Add behavior layer to controller |\n| `getInstances` | `(controller) =\u003e ReadonlyMap\u003cElement, ManagedInstance\u003e` | Get controller's managed instances |\n| `destroy` | `(controller) =\u003e void` | Destroy controller and all layers |\n\n### DOM Manipulation\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `text` | `(el?, content?) =\u003e void \\| string \\| ElementFn` | Get/set text content |\n| `html` | `(el?, content?) =\u003e void \\| string \\| ElementFn` | Get/set inner HTML |\n| `addClass` | `(el?, ...classes) =\u003e void \\| ElementFn` | Add CSS classes |\n| `removeClass` | `(el?, ...classes) =\u003e void \\| ElementFn` | Remove CSS classes |\n| `toggleClass` | `(el?, class, force?) =\u003e boolean \\| ElementFn` | Toggle CSS class |\n| `hasClass` | `(el?, class) =\u003e boolean \\| ElementFn` | Check if has CSS class |\n| `style` | `(el?, prop\\|styles, val?) =\u003e void \\| ElementFn` | Get/set styles |\n| `attr` | `(el?, name, val?) =\u003e void \\| string \\| ElementFn` | Get/set attributes |\n| `removeAttr` | `(el?, name) =\u003e void \\| ElementFn` | Remove attribute |\n| `hasAttr` | `(el?, name) =\u003e boolean \\| ElementFn` | Check if has attribute |\n| `prop` | `(el?, prop, val?) =\u003e void \\| any \\| ElementFn` | Get/set properties |\n| `data` | `(el?, key, val?) =\u003e void \\| string \\| ElementFn` | Get/set data attributes |\n| `value` | `(el?, val?) =\u003e void \\| string \\| ElementFn` | Get/set form values |\n| `checked` | `(el?, checked?) =\u003e void \\| boolean \\| ElementFn` | Get/set checkbox state |\n| `focus` | `(el?) =\u003e void \\| ElementFn` | Focus element |\n| `blur` | `(el?) =\u003e void \\| ElementFn` | Blur element |\n| `show` | `(el?) =\u003e void \\| ElementFn` | Show element |\n| `hide` | `(el?) =\u003e void \\| ElementFn` | Hide element |\n\n### DOM Traversal\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `query` / `el` | `(el?, selector) =\u003e Element \\| ElementFn` | Query single element |\n| `queryAll` / `all` | `(el?, selector) =\u003e Element[] \\| ElementFn` | Query all elements |\n| `parent` | `(selector?) =\u003e ElementFn` | Get parent element |\n| `children` | `() =\u003e ElementFn` | Get child elements |\n| `siblings` | `(selector?) =\u003e ElementFn` | Get sibling elements |\n\n### Event Handling\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `on` | `(el?, event\\|CustomEvent, handler, options?) =\u003e CleanupFn \\| ElementFn` | Advanced event listener with generators, queuing, delegation, debounce/throttle, AbortSignal |\n| `emit` | `(el?, event, detail?, options?) =\u003e void \\| ElementFn` | Dispatch custom event with full API support |\n| `createCustomEvent` | `(type, detail, options?) =\u003e CustomEvent\u003cT\u003e` | Create typed CustomEvent with type inference |\n| `click` | `(el?, handler, options?) =\u003e CleanupFn \\| ElementFn` | Click event with generator support and advanced options |\n| `change` | `(el?, handler, options?) =\u003e CleanupFn \\| ElementFn` | Change event with generator support and advanced options |\n| `input` | `(el?, handler, options?) =\u003e CleanupFn \\| ElementFn` | Input event with generator support and advanced options |\n| `submit` | `(el?, handler, options?) =\u003e CleanupFn \\| ElementFn` | Submit event with generator support and advanced options |\n| `createEventBehavior` | `(eventType, behavior, options?) =\u003e () =\u003e Generator` | Create typed reusable event behavior |\n| `composeEventHandlers` | `(...handlers) =\u003e EventHandler` | Compose multiple event handlers with async support |\n| `delegate` | `(selector, eventType, handler, options?) =\u003e ElementFn` | Create delegated event handler with capture/bubble support |\n\n### Observer Events\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `onAttr` | `(el?, filter, handler) =\u003e CleanupFn \\| ElementFn` | Watch attribute changes |\n| `onText` | `(el?, handler) =\u003e CleanupFn \\| ElementFn` | Watch text changes |\n| `onVisible` | `(el?, handler, options?) =\u003e CleanupFn \\| ElementFn` | Watch visibility changes |\n| `onResize` | `(el?, handler) =\u003e CleanupFn \\| ElementFn` | Watch resize changes |\n| `onMount` | `(el?, handler) =\u003e CleanupFn \\| ElementFn` | Element mount event |\n| `onUnmount` | `(el?, handler) =\u003e CleanupFn \\| ElementFn` | Element unmount event |\n\n### Context Functions (Generator Only)\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `self` | `(ctx?) =\u003e Element` | Get current element (optionally pass context) |\n| `el` | `(selector, ctx?) =\u003e Element \\| null` | Query within current element (optionally pass context) |\n| `all` | `(selector, ctx?) =\u003e Element[]` | Query all within current element (optionally pass context) |\n| `cleanup` | `(fn, ctx?) =\u003e void` | Register cleanup function (optionally pass context) |\n| `ctx` | `(passedCtx?) =\u003e WatchContext` | Get full context object (optionally pass context) |\n| `getParentContext` | `(ctx?) =\u003e ParentContext \\| null` | Get parent component context (optionally pass context) |\n| `getCurrentElement` | `() =\u003e Element \\| null` | Get current element (low-level) |\n| `getCurrentContext` | `(ctx?) =\u003e WatchContext \\| null` | Get current context (low-level, optionally pass context) |\n\n### State Management\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `createState` | `(key, initial) =\u003e TypedState` | Create element-scoped state |\n| `createTypedState` | `(key, initial) =\u003e TypedState` | Create typed element-scoped state |\n| `createComputed` | `(fn, deps) =\u003e () =\u003e T` | Create computed value |\n| `getState` | `(key, defaultValue?, ctx?) =\u003e T` | Get state value (optionally pass context) |\n| `setState` | `(key, val, ctx?) =\u003e void` | Set state value (optionally pass context) |\n| `updateState` | `(key, fn, ctx?) =\u003e void` | Update state value (optionally pass context) |\n| `hasState` | `(key, ctx?) =\u003e boolean` | Check if state exists (optionally pass context) |\n| `deleteState` | `(key, ctx?) =\u003e void` | Delete state value (optionally pass context) |\n| `watchState` | `(key, handler) =\u003e CleanupFn` | Watch state changes |\n| `setStateReactive` | `(key, val) =\u003e void` | Set state with automatic reactivity |\n| `batchStateUpdates` | `(fn) =\u003e void` | Batch multiple state updates |\n| `createPersistedState` | `(key, initial, storageKey?) =\u003e TypedState` | Create localStorage-backed state |\n| `clearAllState` | `() =\u003e void` | Clear all state for element |\n| `debugState` | `() =\u003e void` | Debug state for current element |\n| `logState` | `() =\u003e void` | Log state for current element |\n\n### Execution Helpers\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `once` | `(fn) =\u003e ElementFn` | Execute only once per element |\n| `delay` | `(ms, fn) =\u003e ElementFn` | Delay execution |\n| `throttle` | `(ms, fn) =\u003e ElementFn` | Throttle execution |\n| `debounce` | `(ms, fn) =\u003e ElementFn` | Debounce execution |\n| `when` | `(condition, then, else?) =\u003e ElementFn` | Conditional execution |\n| `safely` | `(fn, fallback?, onError?) =\u003e ElementFn` | Safe execution with error handling |\n| `batch` | `(...fns) =\u003e ElementFn` | Batch multiple operations |\n| `retry` | `(fn, attempts?, backoff?) =\u003e ElementFn` | Retry with exponential backoff |\n| `memoize` | `(fn, keyFn?) =\u003e ElementFn` | Memoize function results |\n| `rateLimit` | `(fn, windowMs, maxCalls) =\u003e ElementFn` | Rate limit function calls |\n| `timeout` | `(ms, fn) =\u003e ElementFn` | Execute with timeout |\n| `compose` | `(...fns) =\u003e ElementFn` | Compose multiple functions |\n| `unless` | `(condition, fn) =\u003e ElementFn` | Execute unless condition is true |\n| `async` | `(fn) =\u003e ElementFn` | Execute async function |\n\n### Context Factories\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `context` | `(selector, options?) =\u003e PreDefinedWatchContext` | Create watch context |\n| `contextFor` | `(selector, options?) =\u003e PreDefinedWatchContext` | Create context for specific selector |\n| `button` | `(selector, options?) =\u003e PreDefinedWatchContext` | Button context |\n| `input` | `(selector, options?) =\u003e PreDefinedWatchContext` | Input context |\n| `form` | `(selector, options?) =\u003e PreDefinedWatchContext` | Form context |\n| `div` | `(selector, options?) =\u003e PreDefinedWatchContext` | Div context |\n| `span` | `(selector, options?) =\u003e PreDefinedWatchContext` | Span context |\n\n### Context Combinators\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `withData` | `(ctx, data) =\u003e PreDefinedWatchContext` | Add custom data |\n| `withDebounce` | `(ctx, ms) =\u003e PreDefinedWatchContext` | Add debouncing |\n| `withThrottle` | `(ctx, ms) =\u003e PreDefinedWatchContext` | Add throttling |\n| `once` | `(ctx) =\u003e PreDefinedWatchContext` | Execute only once |\n| `withFilter` | `(ctx, filterFn) =\u003e PreDefinedWatchContext` | Add element filter |\n\n### Generator Utilities\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `createGenerator` | `(fn) =\u003e GeneratorFn` | Create typed generator |\n| `gen` | `(fn) =\u003e GeneratorFn` | Generator alias |\n| `watchGenerator` | `(selector, fn) =\u003e GeneratorFn` | Create selector-specific generator |\n| `debounceGenerator` | `(ms, gen) =\u003e GeneratorFn` | Create debounced generator |\n| `throttleGenerator` | `(ms, gen) =\u003e GeneratorFn` | Create throttled generator |\n| `onceGenerator` | `(gen) =\u003e GeneratorFn` | Create once-only generator |\n| `delayGenerator` | `(ms, gen) =\u003e GeneratorFn` | Create delayed generator |\n| `batchGenerator` | `(...gens) =\u003e GeneratorFn` | Create batched generator |\n\n### Utilities\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `isElement` | `(value) =\u003e boolean` | Check if value is HTMLElement |\n| `isElementLike` | `(value) =\u003e boolean` | Check if value is element or selector |\n| `resolveElement` | `(elementLike) =\u003e Element \\| null` | Resolve element from selector |\n| `batchAll` | `(elements, ...fns) =\u003e void` | Apply functions to multiple elements |\n| `elDOM` | `(selector) =\u003e Element \\| null` | Alias for DOM query |\n| `allDOM` | `(selector) =\u003e Element[]` | Alias for DOM queryAll |\n| `$` | `(selector) =\u003e Element \\| null` | Convenience alias for `el` |\n\n### Component Composition\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `createChildWatcher` | `(selector, generator, ctx?) =\u003e Map\u003cChildEl, ChildApi\u003e` | Create a reactive collection of child component APIs (optionally pass context) |\n| `child` | `(selector, generator, ctx?) =\u003e Map\u003cChildEl, ChildApi\u003e` | Alias for `createChildWatcher` - shorter and more intuitive (optionally pass context) |\n| `getParentContext` | `(ctx?) =\u003e { element: ParentEl, api: ParentApi } \\| null` | Get the context of the parent watcher (optionally pass context) |\n\n### Declarative Rendering\n| Function | Type | Description |\n|----------|------|-------------|\n| `For` | `(data, keyFn, renderFn) =\u003e Workflow` | Efficiently renders a keyed list of data. |\n| `Show` | `(condition, renderFn) =\u003e Workflow` | Conditionally renders content in the DOM. |\n| `Switch` | `(expression, ...cases) =\u003e Workflow` | Renders the first matching `Case` or a `Default`. |\n| `Case` | `(match, renderFn) =\u003e CaseDescriptor` | Defines a case for the `Switch` primitive. |\n| `Default`| `(renderFn) =\u003e DefaultDescriptor` | Defines the default case for the `Switch` primitive. |\n| `Async` | `(promise, templates) =\u003e Workflow` | Renders UI based on a promise's pending, success, and error states. |\n| `render` | `(componentGen, deps) =\u003e Workflow` | Creates a reactive component that re-renders on state changes. |\n| `tag` | `(name, ...args) =\u003e HTMLElement` | Creates an element programmatically (hyperscript-style). |\n| `tag` | `(name, builder) =\u003e Workflow` | Creates an element using a declarative, reactive generator builder. |\n| `reactive` | `(state) =\u003e ReactiveBinding` | Binds a `TypedState` object to a property in the `tag` builder. |\n\n### WatchController Interface\n\n```typescript\ninterface WatchController\u003cEl extends HTMLElement = HTMLElement\u003e {\n  readonly subject: WatchTarget\u003cEl\u003e;\n  getInstances(): ReadonlyMap\u003cEl, ManagedInstance\u003e;\n  layer(generator: () =\u003e Generator\u003cElementFn\u003cEl, any\u003e, any, unknown\u003e): void;\n  destroy(): void;\n}\n\ninterface ManagedInstance {\n  readonly element: HTMLElement;\n  getState(): Readonly\u003cRecord\u003cstring, any\u003e\u003e;\n}\n```\n\n### Scoped Controller Functions\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `scopedWatch` | `(parent, selector, generator, options?) =\u003e ScopedWatcher` | Create scoped watcher |\n| `scopedWatchWithController` | `(parent, selector, generator, options?) =\u003e ScopedWatcher \u0026 { controller: WatchController }` | Create scoped watcher with controller |\n| `scopedWatchBatch` | `(parent, watchers[]) =\u003e ScopedWatcher[]` | Create multiple scoped watchers |\n| `scopedWatchBatchWithController` | `(parent, watchers[]) =\u003e ScopedWatcher[]` | Create multiple scoped watchers with controllers |\n| `scopedWatchTimeout` | `(parent, selector, generator, timeout, options?) =\u003e ScopedWatcher` | Create scoped watcher with timeout |\n| `scopedWatchOnce` | `(parent, selector, generator, options?) =\u003e ScopedWatcher` | Create scoped watcher that runs once |\n| `createScopedWatcher` | `(parent, options?) =\u003e ScopedWatcher` | Create scoped watcher instance |\n| `disconnectScopedWatchers` | `(parent) =\u003e void` | Disconnect all scoped watchers for parent |\n| `getScopedWatchers` | `(parent) =\u003e ScopedWatcher[]` | Get all scoped watchers for parent |\n\n### Observer Utilities\n\n| Function | Type | Description |\n|----------|------|-------------|\n| `register` | `(element, generator) =\u003e void` | Register element with observer |\n| `getObserverStatus` | `() =\u003e ObserverStatus` | Get current observer status |\n| `cleanupObserver` | `() =\u003e void` | Cleanup observer resources |\n\n### Enhanced Event Options\n\n```typescript\ninterface WatchEventListenerOptions extends AddEventListenerOptions {\n  /** Enable delegation - listen on parent and match against selector */\n  delegate?: string;\n  /** Debounce the event handler (milliseconds) */\n  debounce?: number;\n  /** Throttle the event handler (milliseconds) */\n  throttle?: number;\n  /** Only handle events from specific elements */\n  filter?: (event: Event, element: HTMLElement) =\u003e boolean;\n  /** All standard AddEventListenerOptions (passive, once, capture, signal) */\n}\n```\n\n\u003cbr\u003e\n\n## Examples\n\n### WatchController \u0026 Behavior Layering\n```typescript\n// Create a controller for product cards\nconst productController = watch('.product-card', function* () {\n  const inCart = createState('inCart', false);\n  yield on('click', '.add-btn', () =\u003e inCart.set(true));\n});\n\n// Layer analytics behavior\nproductController.layer(function* () {\n  yield onVisible(() =\u003e {\n    analytics.track('product_viewed', { \n      id: self().dataset.productId \n    });\n  });\n});\n\n// Layer animation behavior conditionally\nif (enableAnimations) {\n  productController.layer(function* () {\n    yield watchState('inCart', (inCart) =\u003e {\n      if (inCart) yield addClass('animate-bounce');\n    });\n  });\n}\n\n// Inspect managed instances\nconst instances = productController.getInstances();\nconsole.log(`Managing ${instances.size} product cards`);\n```\n\n### Enhanced Event Handling\n```typescript\nimport { watch, on, createCustomEvent } from 'watch-selector';\n\n// 1. CustomEvent with type safety\nconst userEvent = createCustomEvent('user:login', { \n  userId: 123, \n  username: 'john_doe' \n});\n\nwatch('.dashboard', function* () {\n  // TypeScript infers the detail type automatically\n  yield on(userEvent, (event, element) =\u003e {\n    console.log(event.detail.userId); // ✅ Type-safe access\n    console.log(event.detail.username); // ✅ Type-safe access\n  });\n});\n\n// 2. Event delegation\nwatch('.list-container', function* () {\n  yield on('click', (event, delegatedElement) =\u003e {\n    // delegatedElement is the matched .list-item, not .list-container\n    console.log('Item clicked:', delegatedElement.textContent);\n  }, {\n    delegate: '.list-item' // Only handle clicks on list items\n  });\n});\n\n// 3. Debounced input handling\nwatch('input[type=\"search\"]', function* () {\n  yield on('input', (event, input) =\u003e {\n    performSearch(input.value);\n  }, {\n    debounce: 300 // Wait 300ms after user stops typing\n  });\n});\n\n// 4. AbortSignal support\nconst controller = new AbortController();\n\nwatch('.temporary-element', function* () {\n  yield on('click', handler, {\n    signal: controller.signal // Automatically cleanup when aborted\n  });\n});\n\n// Later: controller.abort(); // Removes all listeners\n\n// 5. Throttled scroll events\nwatch('.scroll-container', function* () {\n  yield on('scroll', (event, container) =\u003e {\n    updateScrollIndicator(container.scrollTop);\n  }, {\n    throttle: 16 // 60fps throttling\n  });\n});\n\n// 6. Event filtering\nwatch('.interactive-area', function* () {\n  yield on('click', handler, {\n    filter: (event, element) =\u003e {\n      // Only handle clicks with Ctrl+Click\n      return event.ctrlKey;\n    }\n  });\n});\n\n// 7. Multiple options combined\nwatch('.advanced-button', function* () {\n  yield on('click', handler, {\n    delegate: '.clickable',\n    debounce: 100,\n    filter: (event) =\u003e !event.defaultPrevented,\n    once: true,\n    passive: true\n  });\n});\n```\n\n### Advanced Event Handling with Generators\n\nThe event handling system provides a powerful, generator-first approach while maintaining full backward compatibility with traditional event listeners:\n\n```typescript\nimport { watch, on, click } from 'watch-selector';\n\n// 1. Generator Event Handlers with Full Context Access\nwatch('.interactive-button', function* () {\n  yield click(function* (event) {\n    // Full access to Watch context!\n    const button = self();\n    const allButtons = all('.interactive-button');\n    \n    // Can yield other Watch functions seamlessly\n    yield addClass('clicked');\n    yield style({ transform: 'scale(0.95)' });\n    \n    // Async operations with yield\n    yield delay(150);\n    \n    // State management works naturally\n    const clickCount = getState('clicks') || 0;\n    setState('clicks', clickCount + 1);\n    \n    yield removeClass('clicked');\n    yield style({ transform: 'scale(1)' });\n    yield text(`Clicked ${clickCount + 1} times`);\n    \n    // Can register cleanup for this specific click\n    cleanup(() =\u003e {\n      console.log('Click handler cleaned up');\n    });\n  });\n  \n  // Traditional handlers also work and have context access\n  yield click((event) =\u003e {\n    const button = self(); // Works! Context is provided automatically\n    const clickCount = getState('traditionalClicks') || 0;\n    setState('traditionalClicks', clickCount + 1);\n    text(button, `Traditional: ${clickCount + 1}`);\n  });\n});\n\n// 2. Async Operations Made Simple\nwatch('.data-loader', function* () {\n  yield click(async function* (event) {\n    const button = self();\n    \n    yield addClass('loading');\n    yield text('Loading...');\n    \n    try {\n      // Async operation\n      const response = await fetch('/api/data');\n      const data = await response.json();\n      \n      yield removeClass('loading');\n      yield addClass('success');\n      yield text(`Loaded: ${data.name}`);\n      \n      // Auto-reset after delay\n      yield delay(2000);\n      yield removeClass('success');\n      yield text('Load Data');\n      \n    } catch (error) {\n      yield removeClass('loading');\n      yield addClass('error');\n      yield text('Failed to load');\n      \n      yield delay(2000);\n      yield removeClass('error');\n      yield text('Load Data');\n    }\n  });\n});\n\n// 3. Complex Interactive Components\nwatch('.hover-card', function* () {\n  // Mouse enter with nested event handling\n  yield on('mouseenter', function* (event) {\n    yield addClass('hovered');\n    yield style({\n      transform: 'translateY(-5px)',\n      boxShadow: '0 10px 25px rgba(0,0,0,0.1)'\n    });\n    \n    // Set up temporary mouse leave handler\n    yield on('mouseleave', function* () {\n      yield removeClass('hovered');\n      yield style({\n        transform: 'translateY(0)',\n        boxShadow: 'none'\n      });\n    }, { once: true });\n  });\n});\n\n// 4. Form Handling with Real-time Validation\nwatch('.smart-form', function* () {\n  // Real-time validation with advanced debouncing\n  yield on('input', function* (event) {\n    const input = event.target as HTMLInputElement;\n    const value = input.value;\n    \n    // Validation logic\n    const isValid = value.length \u003e= 3;\n    const errorMsg = all('.error-message')[0];\n    \n    if (isValid) {\n      yield removeClass('invalid');\n      yield addClass('valid');\n      if (errorMsg) yield text(errorMsg, '');\n    } else {\n      yield removeClass('valid');\n      yield addClass('invalid');\n      if (errorMsg) yield text(errorMsg, 'Minimum 3 characters required');\n    }\n    \n    // Update form state\n    setState('formValid', isValid);\n  }, { \n    // Advanced debouncing with leading/trailing edge control\n    debounce: { wait: 300, leading: false, trailing: true },\n    delegate: 'input[required]',\n    delegatePhase: 'bubble', // or 'capture' for capture phase\n    queue: 'latest' // Only process the latest input change\n  });\n});\n\n// 5. Event Composition and Reusability\nconst clickRippleEffect = createEventBehavior('click', function* (event) {\n  const clickX = (event as MouseEvent).clientX;\n  const clickY = (event as MouseEvent).clientY;\n  const rect = self().getBoundingClientRect();\n  \n  // Create ripple element\n  const ripple = document.createElement('div');\n  ripple.className = 'ripple';\n  ripple.style.left = `${clickX - rect.left}px`;\n  ripple.style.top = `${clickY - rect.top}px`;\n  \n  self().appendChild(ripple);\n  \n  yield delay(600);\n  ripple.remove();\n});\n\n// Apply composed behavior\nwatch('.material-button', function* () {\n  yield click(clickRippleEffect);\n});\n```\n\n#### Advanced Options Example:\n\n```typescript\nwatch('.advanced-component', function* () {\n  yield on('click', function* (event) {\n    // Complex interaction logic\n    yield addClass('processing');\n    yield delay(100);\n    yield removeClass('processing');\n  }, {\n    // Advanced debouncing with leading edge\n    debounce: { wait: 300, leading: true, trailing: false },\n    \n    // Event delegation with capture phase\n    delegate: '.clickable-child',\n    delegatePhase: 'capture',\n    \n    // Queue management for concurrent executions\n    queue: 'latest', // 'all' | 'latest' | 'none'\n    \n    // Event filtering\n    filter: (event, element) =\u003e !element.disabled,\n    \n    // Standard addEventListener options\n    once: false,\n    passive: false,\n    signal: abortController.signal\n  });\n});\n```\n\n#### Key Features:\n\n- **Generator-First**: Event handlers can be generators that yield Watch functions\n- **Full Context Access**: Access to `self()`, `all()`, `getState()`, `setState()`, etc.\n- **Async Support**: Native support for async operations with yield\n- **Composable**: Create reusable event behaviors\n- **Type-Safe**: Full TypeScript support with proper inference\n- **Performance**: Efficient debouncing, throttling, and delegation\n- **Queue Control**: Manage concurrent async generator execution\n- **Capture/Bubble**: Support for both event phases in delegation\n\n### Interactive Components\n```typescript\n// Make all buttons interactive\nconst buttonController = watch('button', function* () {\n  yield addClass('interactive');\n  yield style('cursor', 'pointer');\n  \n  yield click((e, el) =\u003e {\n    addClass(el, 'clicked');\n    setTimeout(() =\u003e removeClass(el, 'clicked'), 200);\n  });\n});\n```\n\n### Form Validation\n```typescript\nwatch('input[type=\"email\"]', function* () {\n  const isValid = createState('valid', true);\n  \n  yield on('blur', (e, el) =\u003e {\n    const valid = el.value.includes('@');\n    isValid.set(valid);\n    \n    if (valid) {\n      removeClass(el, 'error');\n    } else {\n      addClass(el, 'error');\n    }\n  });\n});\n```\n\n### Advanced Context Usage\n```typescript\nconst searchInput = withDebounce(\n  withData(\n    input('.search'),\n    { searchHistory: [] }\n  ),\n  300\n);\n\nwatch(searchInput, function* () {\n  const history = createState('history', []);\n  \n  yield on('input', (e, el) =\u003e {\n    const query = el.value;\n    if (query.length \u003e 2) {\n      history.update(h =\u003e [...h, query].slice(-10));\n      // Perform search...\n    }\n  });\n});\n```\n\n### Component Hierarchies\n```typescript\n// Child counter component with API\nfunction* smartCounter() {\n  let count = 0;\n  yield text(`Smart Counter: ${count}`);\n  \n  yield click(() =\u003e {\n    count++;\n    yield text(`Smart Counter: ${count}`);\n  });\n  \n  // Return API for parent to use\n  return {\n    getCount: () =\u003e count,\n    reset: () =\u003e {\n      count = 0;\n      yield text(`Smart Counter: ${count}`);\n    }\n  };\n}\n\n// Parent dashboard managing multiple counters\nwatch('.counter-dashboard', function* () {\n  const counters = child('.smart-counter', smartCounter);\n  \n  yield click('.reset-all', () =\u003e {\n    // Reset all child counters\n    for (const api of counters.values()) {\n      api.reset();\n    }\n  });\n  \n  yield click('.show-total', () =\u003e {\n    const total = Array.from(counters.values())\n      .reduce((sum, api) =\u003e sum + api.getCount(), 0);\n    alert(`Total: ${total}`);\n  });\n});\n```\n\n### State Management\n```typescript\nwatch('.counter', function* () {\n  const count = createState('count', 0);\n  const doubled = createComputed(() =\u003e count.get() * 2, ['count']);\n  \n  // Update display when count changes\n  watchState('count', (newCount) =\u003e {\n    text(self(), `Count: ${newCount} (×2: ${doubled()})`);\n  });\n  \n  yield click((e, el) =\u003e {\n    count.update(c =\u003e c + 1);\n  });\n});\n```\n\n\u003cbr\u003e\n\n## Performance \u0026 Browser Support\n\n### Browser Support\nWatch supports all modern browsers with:\n- MutationObserver\n- IntersectionObserver  \n- ResizeObserver\n- Proxy\n- WeakMap/WeakSet\n\n### Performance\n\nWatch is designed for maximum performance with several key optimizations:\n\n- **Single global observer**: One MutationObserver handles all DOM changes\n- **Efficient batching**: DOM changes processed in batches to minimize layout thrashing\n- **Memory safe**: Automatic cleanup with WeakMaps prevents memory leaks\n- **Type-safe**: No runtime type checking needed - all validation at compile time\n- **Event delegation**: Built-in support for efficient event handling on dynamic content\n- **Scoped observation**: Parent-based watching reduces unnecessary element checks\n\n### How the Observer System Works\n\nWatch uses a sophisticated observation strategy that balances performance with functionality:\n\n#### Global MutationObserver\n```typescript\n// All these watchers share a single global observer\nwatch('button', buttonHandler);\nwatch('.dropdown', dropdownHandler); \nwatch('input[type=\"email\"]', emailHandler);\n\n// The observer checks each added element against ALL active selectors\n// This can be expensive with many watchers or frequent DOM changes\n```\n\n#### Event Delegation for Performance\n```typescript\n// ❌ Creates individual watchers for each button (expensive)\nwatch('button', function* () {\n  yield click(() =\u003e console.log('Clicked!'));\n});\n\n// ✅ Uses event delegation - single event listener (fast)\nwatch(document.body, 'button', function* () {\n  yield click(() =\u003e console.log('Clicked!'));\n});\n```\n\n#### Scoped Observation\n```typescript\n// ❌ Observes entire document - all DOM changes trigger checks\nwatch('.todo-item', todoHandler);\n\n// ✅ Scoped to specific container - only changes in .todo-list trigger checks\nwatch(document.querySelector('.todo-list'), '.todo-item', todoHandler);\n```\n\n#### Component Composition Optimization\n```typescript\n// child() uses scoped MutationObserver for optimal performance\nwatch('.parent', function* () {\n  // This creates a scoped observer that only watches within this parent\n  const children = child('.child', childHandler);\n  \n  // Adding children elsewhere doesn't trigger this observer\n  // Only changes within this .parent element are monitored\n});\n```\n\u003cbr /\u003e \n\n## Declarative Rendering Primitives\n\nWhile Watch excels at enhancing existing HTML, it also provides a powerful suite of declarative, high-performance primitives for when you need to render dynamic content from scratch. These tools, inspired by modern frameworks, integrate seamlessly into the generator-based workflow.\n\n### Efficient List Rendering with `For`\n\nThe `For` primitive renders a list of items with efficient, keyed reconciliation. It a","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoeixd%2Fwatch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdoeixd%2Fwatch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoeixd%2Fwatch/lists"}