UNPKG

invokers

Version:

A powerful, platform-first JavaScript library for creating modern user interfaces with declarative HTML. Features universal command chaining, conditional execution, and declarative workflow orchestration via `data-and-then` attributes and `<and-then>` ele

1,063 lines (881 loc) • 96.3 kB
# ✨ Invokers: _Write Interactive HTML Without Writing JavaScript_ [![npm version](https://badge.fury.io/js/invokers.svg)](https://www.npmjs.com/package/invokers) [![Build Status](https://github.com/doeixd/invokers/workflows/CI/badge.svg)](https://github.com/doeixd/invokers/actions) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) **Invokers lets you write future-proof HTML interactions without custom JavaScript.** It's a polyfill for the upcoming HTML Invoker Commands API and Interest Invokers (hover cards, tooltips), with a comprehensive set of extended commands automatically included for real-world needs like toggling, fetching, media controls, and complex workflow chaining. Think of it as **HTMX-lite**, but fully aligned with web standards. ## šŸ“‹ Table of Contents - [šŸš€ Quick Demo](#-quick-demo) - [šŸ¤” How Does This Compare?](#-how-does-this-compare) - [šŸŽÆ Why Invokers?](#-why-invokers) - [šŸš€ Modular Architecture](#-modular-architecture) - [šŸ“¦ Installation & Basic Usage](#-installation--basic-usage) - [šŸŽ›ļø Command Packs](#ļø-command-packs) - [šŸ“‹ Command Cheatsheet](#-command-cheatsheet) - [šŸ”§ Command Syntax Guide](#-command-syntax-guide) - [šŸŽÆ Comprehensive Demo](#-comprehensive-demo) - [šŸƒā€ā™‚ļø Quick Start Examples](#ļø-quick-start-examples) - [šŸ“š Progressive Learning Guide](#-progressive-learning-guide) - [šŸ”Œ Plugin System](#-plugin-system) - [🧰 Extended Commands](#-extended-commands) - [šŸŽÆ Advanced `commandfor` Selectors](#-advanced-commandfor-selectors) - [šŸ”„ Migration Guide](#-migration-guide) - [šŸ“– Documentation](#-documentation) - [⚔ Performance](#-performance) - [šŸ› ļø Development](#ļø-development) - [šŸŽÆ Browser Support](#-browser-support) - [šŸ¤ Contributing](#-contributing) - [šŸ“„ License](#-license) - āœ… **Standards-First:** Built on the W3C/WHATWG `command` attribute and Interest Invokers proposals. Learn future-proof skills, not framework-specific APIs. - 🧩 **Polyfill & Superset:** Provides the standard APIs in all modern browsers and extends them with a rich set of custom commands. - āœļø **Declarative & Readable:** Describe *what* you want to happen in your HTML, not *how* in JavaScript. Create UIs that are self-documenting. - šŸ”— **Universal Command Chaining:** Chain any command with any other using `data-and-then` attributes or declarative `<and-then>` elements for complex workflows. - šŸŽÆ **Conditional Execution:** Execute different command sequences based on success/error states with built-in conditional logic. - šŸ”„ **Lifecycle Management:** Control command execution with states like `once`, `disabled`, and `completed` for sophisticated interaction patterns. - ♿ **Accessible by Design:** Automatically manages `aria-*` attributes and focus behavior, guiding you to build inclusive interfaces. - 🌐 **Server-Interactive:** Fetch content and update the DOM without a page reload using simple, declarative HTML attributes. - šŸ’” **Interest Invokers:** Create hover cards, tooltips, and rich hints that work across mouse, keyboard, and touch with the `interestfor` attribute. - šŸš€ **Zero Dependencies & Tiny:** A featherlight addition to any project, framework-agnostic, and ready to use in seconds. - šŸŽØ **View Transitions:** Built-in, automatic support for the [View Transition API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) for beautiful, animated UI changes with zero JS configuration. - šŸ”§ **Singleton Architecture:** Optimized internal architecture ensures consistent behavior and prevents duplicate registrations. ## 🌐 Platform Proposals & Standards Alignment Invokers is built on emerging web platform proposals from the OpenUI Community Group and WHATWG, providing a polyfill today for features that will become native browser APIs tomorrow. This section explains the underlying standards and how Invokers extends them. ### HTML Invoker Commands API The [Invoker Commands API](https://open-ui.org/components/invokers.explainer/) is a W3C/WHATWG proposal that introduces the `command` and `commandfor` attributes to HTML `<button>` elements. This allows buttons to declaratively trigger actions on other elements without JavaScript. #### Core Proposal Features - **`command` attribute**: Specifies the action to perform (e.g., `show-modal`, `toggle-popover`) - **`commandfor` attribute**: References the target element by ID - **`CommandEvent`**: Dispatched on the target element when the button is activated - **Built-in commands**: Native browser behaviors for dialogs and popovers #### Example from the Specification ```html <button command="show-modal" commandfor="my-dialog">Open Dialog</button> <dialog id="my-dialog">Hello World</dialog> ``` #### How Invokers Extends This Invokers provides a complete polyfill for the Invoker Commands API while adding extensive enhancements: - **Extended Command Set**: Adds 50+ custom commands (`--toggle`, `--fetch:get`, `--media:play`, etc.) beyond the spec's basic commands - **Advanced Event Triggers**: Adds `command-on` attribute for any DOM event (click, input, submit, etc.) - **Expression Engine**: Adds `{{...}}` syntax for dynamic command parameters - **Command Chaining**: Adds `<and-then>` elements and `data-and-then` attributes for workflow orchestration - **Conditional Logic**: Adds success/error state handling with `data-after-success`/`data-after-error` - **Lifecycle States**: Adds `once`, `disabled`, `completed` states for sophisticated interactions ### Interest Invokers (Hover Cards & Tooltips) The [Interest Invokers](https://open-ui.org/components/interest-invokers.explainer/) proposal introduces the `interestfor` attribute for creating accessible hover cards, tooltips, and preview popovers that work across all input modalities. #### Core Proposal Features - **`interestfor` attribute**: Connects interactive elements to hovercard/popover content - **Multi-modal Support**: Works with mouse hover, keyboard focus, and touchscreen long-press - **Automatic Accessibility**: Manages ARIA attributes and focus behavior - **Delay Controls**: CSS properties for customizing show/hide timing - **Pseudo-classes**: `:interest-source` and `:interest-target` for styling #### Example from the Specification ```html <a href="/profile" interestfor="user-card">@username</a> <div id="user-card" popover="hint">User details...</div> ``` #### How Invokers Extends This Invokers includes a complete polyfill for Interest Invokers with additional enhancements: - **Extended Element Support**: Works on all `HTMLElement` types (spec currently limits to specific elements) - **Touchscreen Context Menu Integration**: Adds "Show Details" item to existing long-press menus - **Advanced Delay Controls**: Full support for `interest-delay-start`/`interest-delay-end` CSS properties - **Pseudo-class Support**: Implements `:interest-source` and `:interest-target` pseudo-classes - **Combined Usage**: Works seamlessly with Invoker Commands on the same elements ### Popover API Integration Invokers has deep integration with the [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API), automatically handling popover lifecycle and accessibility when using `popover` attributes. #### Automatic Behaviors - **Popover Commands**: `toggle-popover`, `show-popover`, `hide-popover` work natively - **ARIA Management**: Automatic `aria-expanded` and `aria-details` attributes - **Focus Management**: Proper focus restoration when popovers close - **Top Layer Integration**: Works with the browser's top layer stacking context ### Standards Compliance & Future-Proofing #### Current Browser Support - **Chrome/Edge**: Full Invoker Commands support (v120+) - **Firefox**: Partial support, actively developing - **Safari**: Under consideration - **Polyfill Coverage**: Invokers provides complete fallback for all browsers #### Standards Timeline - **Invoker Commands**: Graduated from OpenUI, in WHATWG HTML specification - **Interest Invokers**: Active proposal, expected to graduate soon - **Popover API**: Already shipping in major browsers #### Migration Path As browsers implement these features natively: 1. Invokers will automatically detect native support 2. Polyfill behaviors will gracefully disable 3. Your HTML markup remains unchanged 4. Enhanced features (chaining, expressions) continue to work #### Why Invokers vs. Native-Only While waiting for universal browser support, Invokers provides: - **Immediate Availability**: Use these features today in any browser - **Enhanced Functionality**: Command chaining, expressions, and advanced workflows - **Backward Compatibility**: Works alongside native implementations - **Progressive Enhancement**: Adds features without breaking existing code This standards-first approach ensures your code is future-proof while providing powerful enhancements that complement the core platform proposals. ## šŸš€ Quick Demo (30 seconds) See Invokers in action with this copy-paste example: ```html <!DOCTYPE html> <html> <head> <!-- Add Invokers via CDN (includes all commands) --> <script type="module" src="https://esm.sh/invokers/compatible"></script> </head> <body> <!-- Toggle a navigation menu with zero JavaScript --> <button type="button" command="--toggle" commandfor="nav-menu" aria-expanded="false"> Menu </button> <nav id="nav-menu" hidden> <a href="/home">Home</a> <a href="/about">About</a> <!-- Dismiss button that hides itself --> <button type="button" command="--hide" commandfor="nav-menu">āœ•</button> </nav> <!-- Hover cards work automatically with Interest Invokers --> <a href="/profile" interestfor="profile-hint">@username</a> <div id="profile-hint" popover="hint"> <strong>John Doe</strong><br> Software Developer<br> šŸ“ San Francisco </div> </body> </html> ``` That's it! No event listeners, no DOM queries, no state management. The HTML describes the behavior, and Invokers makes it work. ## šŸ¤” How Does This Compare? | Feature | Vanilla JS | HTMX | Alpine.js | **Invokers** | | --------------------- | ---------- | ------- | --------- | ------------ | | Declarative in HTML | āŒ | āœ… | āœ… | āœ… | | Standards-aligned | āŒ | āŒ | āŒ | āœ… | | Extended commands | āŒ | āŒ | āŒ | āœ… (Auto) | | Workflow chaining | āŒ | Limited | Limited | āœ… | | Accessible by default | āŒ | āŒ | āŒ | āœ… | | Future-proof | āŒ | āŒ | āŒ | āœ… | <br /> ## šŸ†š vs HTMX **Invokers embraces JavaScript** while keeping simple interactions simple. Unlike HTMX's hypermedia-first approach, Invokers focuses on rich client-side interactions with clean separation between HTML structure and JavaScript logic. ### HTMX: Hypermedia with Server State ```html <!-- HTMX: Server handles everything, HTML becomes the API --> <div hx-get="/api/todos" hx-trigger="load" hx-target="#todo-list"> <button hx-post="/api/todos" hx-include="[name='task']" hx-target="#todo-list"> Add Todo </button> <input name="task" placeholder="New task..."> </div> <ul id="todo-list"></ul> ``` ### Invokers: Client-Side Interactions with JS Control ```html <!-- Invokers: HTML declares intent, JavaScript handles logic --> <div id="todo-app"> <form command-on="submit.prevent" command="--add-todo"> <input name="task" placeholder="New task..." command-on="input" command="--update-preview"> <button type="submit">Add Todo</button> </form> <div id="preview">Preview will appear here...</div> <ul id="todo-list"></ul> </div> <script> // Clean separation: JavaScript manages data and business logic const todos = []; const app = document.getElementById('todo-app'); app.addEventListener('command', (e) => { if (e.command === '--add-todo') { const task = e.target.elements.task.value; if (task.trim()) { todos.push({ id: Date.now(), text: task, completed: false }); renderTodos(); e.target.reset(); } } }); function renderTodos() { const list = document.getElementById('todo-list'); list.innerHTML = todos.map(todo => ` <li> <input type="checkbox" ${todo.completed ? 'checked' : ''} command="--toggle-todo" data-todo-id="${todo.id}"> <span ${todo.completed ? 'style="text-decoration: line-through"' : ''}> ${todo.text} </span> <button command="--delete-todo" data-todo-id="${todo.id}">Ɨ</button> </li> `).join(''); } </script> ``` **Key Differences:** - **JavaScript Embrace**: Invokers doesn't hide JavaScript - it enhances it with declarative HTML - **Client-Side Focus**: Perfect for SPAs and rich interactions without server round-trips - **Clean Separation**: HTML declares *what* happens, JavaScript handles *how* - **Not Hypermedia**: No assumption about server APIs or HTML-over-the-wire ## šŸ†š vs Alpine.js **Invokers keeps control flow and data out of the DOM by default**, while Alpine.js embeds JavaScript expressions directly in HTML attributes. Invokers embraces JavaScript but maintains clean separation between structure and logic. ### Alpine.js: Logic in HTML Attributes ```html <!-- Alpine.js: JavaScript expressions in HTML attributes --> <div x-data="{ todos: [], newTodo: '', addTodo() { if (this.newTodo.trim()) { this.todos.push({ id: Date.now(), text: this.newTodo, completed: false }); this.newTodo = ''; } }, toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } }"> <form @submit.prevent="addTodo()"> <input x-model="newTodo" placeholder="New task..."> <button type="submit">Add Todo</button> </form> <ul> <template x-for="todo in todos" :key="todo.id"> <li> <input type="checkbox" :checked="todo.completed" @change="toggleTodo(todo.id)"> <span :class="{ 'line-through': todo.completed }" x-text="todo.text"></span> <button @click="todos = todos.filter(t => t.id !== todo.id)">Ɨ</button> </li> </template> </ul> </div> ``` ### Invokers: Declarative HTML, Clean JavaScript ```html <!-- Invokers: HTML declares intent, JavaScript manages state --> <div id="todo-app"> <form command-on="submit.prevent" command="--add-todo"> <input name="task" placeholder="New task..."> <button type="submit">Add Todo</button> </form> <ul id="todo-list"> <!-- Todos rendered here by JavaScript --> </ul> </div> <script> // Clean separation: All logic in JavaScript, not scattered in HTML class TodoApp { constructor() { this.todos = []; this.app = document.getElementById('todo-app'); this.bindEvents(); this.render(); } bindEvents() { this.app.addEventListener('command', (e) => { switch (e.command) { case '--add-todo': this.addTodo(e.target.elements.task.value); break; case '--toggle-todo': this.toggleTodo(parseInt(e.target.dataset.todoId)); break; case '--delete-todo': this.deleteTodo(parseInt(e.target.dataset.todoId)); break; } }); } addTodo(text) { if (text.trim()) { this.todos.push({ id: Date.now(), text: text.trim(), completed: false }); this.render(); this.app.querySelector('input[name="task"]').value = ''; } } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) { todo.completed = !todo.completed; this.render(); } } deleteTodo(id) { this.todos = this.todos.filter(t => t.id !== id); this.render(); } render() { const list = document.getElementById('todo-list'); list.innerHTML = this.todos.map(todo => ` <li class="${todo.completed ? 'completed' : ''}"> <input type="checkbox" ${todo.completed ? 'checked' : ''} command="--toggle-todo" data-todo-id="${todo.id}"> <span>${todo.text}</span> <button command="--delete-todo" data-todo-id="${todo.id}">Ɨ</button> </li> `).join(''); } } // Initialize the app new TodoApp(); </script> ``` **Key Differences:** - **DOM Separation**: Control flow and data logic stay in JavaScript, not embedded in HTML - **JavaScript Embrace**: Full power of JavaScript without hiding it behind HTML attributes - **Maintainable**: Logic is organized in proper JavaScript classes/functions, not scattered across HTML - **Simple Things Stay Simple**: Basic interactions use simple HTML attributes, complex logic uses JavaScript - **Standards-Based**: Uses web platform APIs instead of custom attribute syntax ## šŸŽÆ Why Invokers? **Write interactive UIs without JavaScript.** Invokers transforms static HTML into dynamic, interactive interfaces using declarative attributes. Perfect for progressive enhancement, component libraries, and reducing JavaScript complexity. ```html <!-- Toggle a menu --> <button command="--toggle" commandfor="menu">Menu</button> <nav id="menu" hidden>...</nav> <!-- Form with dynamic feedback --> <form command-on="submit.prevent" command="--fetch:send" commandfor="#result"> <input name="query" placeholder="Search..."> <button type="submit">Search</button> </form> <div id="result"></div> ``` ## šŸš€ Modular Architecture **Choose exactly what you need.** Invokers now features a hyper-modular architecture with four tiers: - **šŸ—ļø Tier 0**: Core polyfill (25.8 kB) - Standards-compliant foundation - **⚔ Tier 1**: Essential commands (~30 kB) - Basic UI interactions - **šŸ”§ Tier 2**: Specialized packs (25-47 kB each) - Advanced functionality - **🌟 Tier 3**: Reactive engine (26-42 kB) - Dynamic templating & events ## šŸ“¦ Installation & Basic Usage ### Core Installation (25.8 kB) For developers who want just the standards polyfill: ```bash npm install invokers ``` ```javascript import 'invokers'; // That's it! Now command/commandfor attributes work ``` ```html <!-- Native/polyfilled commands work immediately --> <button command="toggle-popover" commandfor="menu">Menu</button> <div id="menu" popover>Menu content</div> ``` ### Essential UI Commands (+30 kB) Add the most common interactive commands: ```javascript import invokers from 'invokers'; import { registerBaseCommands } from 'invokers/commands/base'; import { registerFormCommands } from 'invokers/commands/form'; registerBaseCommands(invokers); registerFormCommands(invokers); ``` ```html <!-- Now you can use essential commands --> <button command="--toggle" commandfor="sidebar">Toggle Sidebar</button> <button command="--class:toggle:dark-mode" commandfor="body">Dark Mode</button> <button command="--text:set:Hello World!" commandfor="output">Set Text</button> ``` ## šŸŽ›ļø Command Packs ### Tier 1: Essential Commands #### Base Commands (`invokers/commands/base`) - 29.2 kB Essential UI state management without DOM manipulation. ```javascript import { registerBaseCommands } from 'invokers/commands/base'; registerBaseCommands(invokers); ``` **Commands:** `--toggle`, `--show`, `--hide`, `--class:*`, `--attr:*` ```html <button command="--toggle" commandfor="menu">Menu</button> <button command="--class:add:active" commandfor="tab1">Activate Tab</button> <button command="--attr:set:aria-expanded:true" commandfor="dropdown">Expand</button> ``` #### Form Commands (`invokers/commands/form`) - 30.5 kB Form interactions and content manipulation. ```javascript import { registerFormCommands } from 'invokers/commands/form'; registerFormCommands(invokers); ``` **Commands:** `--text:*`, `--value:*`, `--focus`, `--disabled:*`, `--form:*`, `--input:step` ```html <button command="--text:set:Form submitted!" commandfor="status">Submit</button> <button command="--value:set:admin@example.com" commandfor="email">Use Admin Email</button> <button command="--input:step:5" commandfor="quantity">+5</button> ``` ### Tier 2: Specialized Commands #### DOM Manipulation (`invokers/commands/dom`) - 47.1 kB Dynamic content insertion and templating. ```javascript import { registerDomCommands } from 'invokers/commands/dom'; registerDomCommands(invokers); ``` **Commands:** `--dom:*`, `--template:*` ```html <button command="--dom:append" commandfor="list" data-template-id="item-tpl">Add Item</button> <button command="--template:render:user-card" commandfor="output" data-name="John" data-email="john@example.com">Render User</button> ``` #### Flow Control (`invokers/commands/flow`) - 45.3 kB Async operations, navigation, and data binding. ```javascript import { registerFlowCommands } from 'invokers/commands/flow'; registerFlowCommands(invokers); ``` **Commands:** `--fetch:*`, `--navigate:*`, `--emit:*`, `--command:*`, `--bind:*` ```html <button command="--fetch:get" data-url="/api/users" commandfor="user-list">Load Users</button> <button command="--navigate:to:/dashboard">Go to Dashboard</button> <input command-on="input" command="--bind:value" data-bind-to="#output" data-bind-as="text"> ``` #### Media & Animation (`invokers/commands/media`) - 27.7 kB Rich media controls and interactions. ```javascript import { registerMediaCommands } from 'invokers/commands/media'; registerMediaCommands(invokers); ``` **Commands:** `--media:*`, `--carousel:*`, `--scroll:*`, `--clipboard:*` ```html <button command="--media:toggle" commandfor="video">Play/Pause</button> <button command="--carousel:nav:next" commandfor="slideshow">Next</button> <button command="--scroll:to" commandfor="section2">Scroll to Section</button> <button command="--clipboard:copy" commandfor="code">Copy Code</button> ``` #### Browser APIs (`invokers/commands/browser`) - 25.3 kB Cookie management and browser integration. ```javascript import { registerBrowserCommands } from 'invokers/commands/browser'; registerBrowserCommands(invokers); ``` **Commands:** `--cookie:*` ```html <button command="--cookie:set:theme:dark" data-cookie-expires="365">Set Dark Theme</button> <button command="--cookie:get:theme" commandfor="current-theme">Show Theme</button> ``` #### Data Management (`invokers/commands/data`) - 45.2 kB Complex data operations and array manipulation. ```javascript import { registerDataCommands } from 'invokers/commands/data'; registerDataCommands(invokers); ``` **Commands:** `--data:*`, array operations, reactive data binding ```html <button command="--data:set:userId:123" commandfor="profile">Set User ID</button> <button command="--data:set:array:push:todos" data-value='{"title":"New Task"}' commandfor="app">Add Todo</button> ``` #### Device APIs (`invokers/commands/device`) - 28.1 kB ```javascript import { registerDeviceCommands } from 'invokers/commands/device'; registerDeviceCommands(invokers); ``` **Commands:** `--device:vibrate`, `--device:share`, `--device:geolocation:get`, `--device:battery:get`, `--device:clipboard:*`, `--device:wake-lock*` ```html <button command="--device:vibrate:200:100:200">Vibrate</button> <button command="--device:share:title:Check this out:text:Amazing content:url:https://example.com">Share</button> <button command="--device:geolocation:get" commandfor="location-display">Get Location</button> ``` #### Accessibility Helpers (`invokers/commands/accessibility`) - 26.8 kB ```javascript import { registerAccessibilityCommands } from 'invokers/commands/accessibility'; registerAccessibilityCommands(invokers); ``` **Commands:** `--a11y:announce`, `--a11y:focus`, `--a11y:skip-to`, `--a11y:focus-trap`, `--a11y:aria:*`, `--a11y:heading-level` ```html <button command="--a11y:announce:Item saved successfully">Save</button> <button command="--a11y:focus" commandfor="search-input">Focus Search</button> <button command="--a11y:focus-trap:enable" commandfor="modal">Open Modal</button> ``` ## šŸ“‹ Command Cheatsheet ### Core Commands (Available Now) | Command | Purpose | Example | | ---------------------- | --------------------------------- | -------------------------------------------- | | `--toggle` | Show/hide + update ARIA | `command="--toggle" commandfor="menu"` | | `--show` | Show one, hide siblings | `command="--show" commandfor="panel-1"` | | `--hide` | Hide element | `command="--hide" commandfor="alert"` | | `--class:add:name` | Add CSS class | `command="--class:add:active"` | | `--class:remove:name` | Remove CSS class | `command="--class:remove:loading"` | | `--class:toggle:name` | Toggle CSS class | `command="--class:toggle:dark-mode"` | | `--text:set:message` | Replace text content | `command="--text:set:Hello World"` | | `--text:append:text` | Append to text content | `command="--text:append: more text"` | | `--text:prepend:text` | Prepend to text content | `command="--text:prepend:Prefix "` | | `--text:clear` | Clear text content | `command="--text:clear"` | | `--attr:set:name:val` | Set attribute | `command="--attr:set:disabled:true"` | | `--attr:remove:name` | Remove attribute | `command="--attr:remove:disabled"` | | `--attr:toggle:name` | Toggle attribute presence | `command="--attr:toggle:hidden"` | | `--value:set` | Set input value | `command="--value:set:new-value"` | | `--focus` | Focus element | `command="--focus" commandfor="input"` | | `--disabled:toggle` | Toggle disabled state | `command="--disabled:toggle"` | | `--scroll:into-view` | Scroll element into view | `command="--scroll:into-view"` | | `--scroll:top` | Scroll to top of element | `command="--scroll:top"` | | `--scroll:bottom` | Scroll to bottom of element | `command="--scroll:bottom"` | | `--scroll:center` | Scroll to center of element | `command="--scroll:center"` | ### Storage Commands | Command | Purpose | Example | | ---------------------- | --------------------------------- | -------------------------------------------- | | `--storage:local:set:key:val` | Store in localStorage | `command="--storage:local:set:user:name"` | | `--storage:session:set:key:val` | Store in sessionStorage | `command="--storage:session:set:temp:data"` | | `--storage:local:get:key` | Get from localStorage | `command="--storage:local:get:user"` | | `--storage:local:remove:key` | Remove from localStorage | `command="--storage:local:remove:user"` | | `--storage:local:clear` | Clear all localStorage | `command="--storage:local:clear"` | | `--storage:local:keys` | Get all localStorage keys | `command="--storage:local:keys"` | | `--storage:local:has:key` | Check if key exists | `command="--storage:local:has:user"` | | `--storage:local:size` | Get localStorage size in bytes | `command="--storage:local:size"` | ### Animation Commands | Command | Purpose | Example | | ---------------------- | --------------------------------- | -------------------------------------------- | | `--animate:fade-in` | Fade element in | `command="--animate:fade-in"` | | `--animate:fade-out` | Fade element out | `command="--animate:fade-out"` | | `--animate:slide-up` | Slide up animation | `command="--animate:slide-up"` | | `--animate:slide-down` | Slide down animation | `command="--animate:slide-down"` | | `--animate:bounce` | Bounce animation | `command="--animate:bounce"` | | `--animate:spin` | Spin animation | `command="--animate:spin"` | ### Event Commands | Command | Purpose | Example | | ---------------------- | --------------------------------- | -------------------------------------------- | | `--emit:event-name` | Dispatch custom event | `command="--emit:my-event:detail-data"` | ### URL Manipulation Commands | Command | Purpose | Example | | ---------------------- | --------------------------------- | -------------------------------------------- | | `--url:params-get:key` | Get URL parameter | `command="--url:params-get:id"` | | `--url:params-set:key:val` | Set URL parameter | `command="--url:params-set:page:2"` | | `--url:params-set:key` | Set URL parameter from input | `command="--url:params-set:query" commandfor="search-input"` | | `--url:params-delete:key` | Delete URL parameter | `command="--url:params-delete:id"` | | `--url:params-clear` | Clear all URL parameters | `command="--url:params-clear"` | | `--url:params-all` | Get all URL parameters as JSON | `command="--url:params-all"` | | `--url:hash-get` | Get URL hash | `command="--url:hash-get"` | | `--url:hash-set:value` | Set URL hash | `command="--url:hash-set:section-1"` | | `--url:hash-set` | Set URL hash from input | `command="--url:hash-set" commandfor="hash-input"` | | `--url:hash-clear` | Clear URL hash | `command="--url:hash-clear"` | | `--url:pathname-get` | Get current pathname | `command="--url:pathname-get"` | | `--url:pathname-set:path` | Set pathname | `command="--url:pathname-set:/new-page"` | | `--url:pathname-set` | Set pathname from input | `command="--url:pathname-set" commandfor="path-input"` | | `--url:reload` | Reload the page | `command="--url:reload"` | | `--url:replace:url` | Replace current URL | `command="--url:replace:/new-page"` | | `--url:navigate:url` | Navigate to URL | `command="--url:navigate:/new-page"` | | `--url:base` | Get base URL (protocol+host) | `command="--url:base"` | | `--url:full` | Get full current URL | `command="--url:full"` | **Input/Textarea Integration:** URL commands like `params-set`, `hash-set`, and `pathname-set` can get their values from `<input>` or `<textarea>` elements by using `commandfor` to target the input element. If no value is provided in the command string, the value will be taken from the target element's `value` property. ### History Commands | Command | Purpose | Example | | ---------------------- | --------------------------------- | -------------------------------------------- | | `--history:push:url` | Push new history entry | `command="--history:push:/new-page"` | | `--history:replace:url`| Replace current history entry | `command="--history:replace:/new-page"` | | `--history:back` | Go back in history | `command="--history:back"` | | `--history:forward` | Go forward in history | `command="--history:forward"` | | `--history:go:delta` | Go to specific history position | `command="--history:go:-2"` | | `--history:state:get` | Get current history state | `command="--history:state:get"` | | `--history:state:set:data` | Set history state | `command="--history:state:set:json-data"` | | `--history:length` | Get history length | `command="--history:length"` | | `--history:clear` | Clear history state | `command="--history:clear"` | ### Device API Commands | Command | Purpose | Example | | ---------------------- | --------------------------------- | -------------------------------------------- | | `--device:vibrate:pattern` | Vibrate device | `command="--device:vibrate:200:100:200"` | | `--device:share:url:text:title` | Share content | `command="--device:share:url:http://ex.com"` | | `--device:geolocation:get` | Get device location | `command="--device:geolocation:get"` | | `--device:battery:get` | Get battery status | `command="--device:battery:get"` | | `--device:clipboard:read` | Read from clipboard | `command="--device:clipboard:read"` | | `--device:clipboard:write:text` | Write to clipboard | `command="--device:clipboard:write:hello"` | | `--device:wake-lock` | Request wake lock | `command="--device:wake-lock"` | | `--device:wake-lock:release` | Release wake lock | `command="--device:wake-lock:release"` | ### Accessibility Commands | Command | Purpose | Example | | ---------------------- | --------------------------------- | -------------------------------------------- | | `--a11y:announce:text` | Announce to screen readers | `command="--a11y:announce:Item saved"` | | `--a11y:focus` | Focus element with announcement | `command="--a11y:focus" commandfor="input"` | | `--a11y:skip-to:id` | Skip navigation to element | `command="--a11y:skip-to:main-content"` | | `--a11y:focus-trap:enable` | Enable focus trap | `command="--a11y:focus-trap:enable"` | | `--a11y:aria:set:attr:val` | Set ARIA attribute | `command="--a11y:aria:set:label:Save button"`| | `--a11y:aria:remove:attr` | Remove ARIA attribute | `command="--a11y:aria:remove:label"` | | `--a11y:heading-level:level` | Set heading level | `command="--a11y:heading-level:2"` | ### Extended Commands (Auto-Included) | Command | Purpose | Example | | ---------------------- | --------------------------------- | -------------------------------------------- | | `--media:toggle` | Toggle play/pause | `command="--media:toggle" commandfor="video"`| | `--media:seek:seconds` | Seek media by seconds | `command="--media:seek:10"` | | `--media:mute` | Toggle media mute | `command="--media:mute"` | | `--carousel:nav:next` | Navigate carousel | `command="--carousel:nav:next"` | | `--clipboard:copy` | Copy text to clipboard | `command="--clipboard:copy"` | | `--form:reset` | Reset form | `command="--form:reset"` | | `--form:submit` | Submit form | `command="--form:submit"` | | `--input:step:amount` | Step number input | `command="--input:step:1"` | | `--dom:remove` | Remove element from DOM | `command="--dom:remove"` | | `--dom:replace[:inner\:outer]` | Replace with template content (inner/outer) | `command="--dom:replace:outer" data-template-id="tpl"` | | `--dom:swap[:inner\:outer]` | Swap content with template (inner/outer) | `command="--dom:swap:outer" data-template-id="tpl"`| | `--dom:append[:inner\:outer]` | Append template content (inner/outer) | `command="--dom:append:outer" data-template-id="tpl"` | | `--dom:prepend[:inner\:outer]` | Prepend template content (inner/outer) | `command="--dom:prepend:outer" data-template-id="tpl"` | | `--dom:wrap[:tag]` | Wrap element with template/tag | `command="--dom:wrap:div" data-wrapper-class="card"` | | `--dom:unwrap` | Unwrap element from parent | `command="--dom:unwrap"` | | `--dom:toggle-empty-class:class` | Toggle class based on children | `command="--dom:toggle-empty-class:empty"` | | `--data:set:key:val` | Set data attribute on element | `command="--data:set:userId:123"` | | `--data:copy:key` | Copy data attribute between elements | `command="--data:copy:userId" data-copy-from="#src"` | | `--cookie:set:key:val` | Set browser cookie | `command="--cookie:set:theme:dark"` | | `--cookie:get:key` | Get cookie value | `command="--cookie:get:theme"` | | `--cookie:remove:key` | Remove browser cookie | `command="--cookie:remove:theme"` | | `--command:trigger:event` | Trigger event on element | `command="--command:trigger:click" commandfor="#btn"` | | `--command:delay:ms` | Wait for milliseconds | `command="--command:delay:2000"` | | `--on:interval:ms` | Execute command at intervals | `command-on="load" command="--on:interval:5000" data-interval-command="--fetch:get"` | | `--bind:prop` | Bind data between elements | `command="--bind:value" data-bind-to="#output"` | | `--text:copy` | Copy text between elements | `command="--text:copy" data-copy-from="#source"` | | `--fetch:get` | Fetch HTML and update element | `command="--fetch:get" data-url="/api/data"` | | `--fetch:send` | Send form data via fetch | `command="--fetch:send"` | | `--navigate:to:url` | Navigate using History API | `command="--navigate:to:/new-page"` | ### Native/Polyfilled Commands (No -- Prefix) | Command | Target Element | Purpose | | ---------------------- | -------------------- | -------------------------------------------- | | `show-modal` | `<dialog>` | Open dialog modally | | `close` | `<dialog>`, `<popover>` | Close dialog/popover | | `request-close` | `<dialog>` | Request dialog close (allows cancel) | | `toggle-popover` | `<popover>` | Toggle popover visibility | | `show-popover` | `<popover>` | Show popover | | `hide-popover` | `<popover>` | Hide popover | | `toggle` | `<details>` | Toggle details open/closed | | `open` | `<details>` | Open details element | | `show-picker` | `<input>`, `<select>`| Show native picker (date, file, etc.) | | `play-pause` | `<video>`, `<audio>` | Toggle media playback | | `play` | `<video>`, `<audio>` | Start media playback | | `pause` | `<video>`, `<audio>` | Pause media playback | | `toggle-muted` | `<video>`, `<audio>` | Toggle media mute state | | `toggle-fullscreen` | Any element | Toggle fullscreen mode | | `request-fullscreen` | Any element | Enter fullscreen mode | | `exit-fullscreen` | Any element | Exit fullscreen mode | | `copy-text` | Any element | Copy element's text to clipboard | | `share` | Any element | Share element's text or URL | | `step-up` | `<input type=number>`| Increment number input | | `step-down` | `<input type=number>`| Decrement number input | | `toggle-openable` | Openable elements | Toggle open/close state | | `open-openable` | Openable elements | Open element | | `close-openable` | Openable elements | Close element | ### Pipeline Commands | Command | Purpose | Example | | ---------------------- | -------------------------------------------- | -------------------------------------------- | | `--pipeline:execute:id`| Execute template-based command pipeline | `command="--pipeline:execute:user-flow"` | šŸ’” **Tip:** Commands starting with `--` are Invokers extensions. Commands without prefixes are native/future browser commands. ## šŸ”§ Command Syntax Guide ### Parameter Syntax Commands use colon (`:`) separated parameters: ```html <!-- Basic parameter --> <button command="--text:set:Hello World">Set Text</button> <!-- Multiple parameters --> <button command="--storage:local:set:user:John">Save User</button> <!-- Numeric parameters --> <button command="--media:seek:30">Skip 30s</button> ``` ### Data Attributes for Enhanced Control Many commands support additional configuration via `data-*` attributes: #### Fetch Commands ```html <button type="button" command="--fetch:get" data-url="/api/data" data-loading-template="spinner" data-error-template="error-msg" data-response-target="#result" commandfor="content-area"> Load Data </button> ``` #### Animation Commands ```html <button type="button" command="--animate:fade-in" data-animate-duration="2s" data-animate-delay="500ms" data-animate-easing="ease-out" data-animate-iterations="3"> Custom Fade In </button> ``` #### Storage Commands ```html <button type="button" command="--storage:local:set:settings" data-storage-json="true" data-storage-expires="3600"> Save Settings (JSON, expires in 1 hour) </button> ``` #### Device Commands ```html <button type="button" command="--device:geolocation:get" data-geo-high-accuracy="true" data-geo-timeout="10000" data-geo-max-age="300000"> Get Location </button> ``` ### Error Handling Commands include comprehensive error handling with helpful messages: - **Validation errors** for incorrect parameters - **Permission errors** for device APIs requiring user consent - **Network errors** for fetch operations - **Browser support warnings** for unsupported features Errors are logged to console with recovery suggestions. Use browser dev tools to see detailed error information. ### Command States Control command execution behavior with `data-state`: ```html <!-- Execute once, then disable --> <button command="--show" commandfor="welcome" data-state="once">Show Welcome</button> <!-- Currently disabled --> <button command="--toggle" commandfor="menu" data-state="disabled">Menu</button> <!-- Completed, won't execute again --> <button command="--fetch:get" data-url="/api" data-state="completed">Load Data</button> ``` ### Tier 3: Advanced Features #### Event Triggers (`invokers/advanced/events`) - 42.3 kB Advanced event binding with `command-on` attribute. ```javascript import { enableEventTriggers } from 'invokers/advanced/events'; enableEventTriggers(); ``` ```html <!-- Respond to any DOM event --> <form command-on="submit.prevent" command="--fetch:send">...</form> <input command-on="input:debounce:300" command="--text:set:{{this.value}}" commandfor="preview"> ``` #### Expression Engine (`invokers/advanced/expressions`) - 26.2 kB Dynamic templating with `{{expression}}` syntax. ```javascript import { enableExpressionEngine } from 'invokers/advanced/expressions'; enableExpressionEngine(); ``` ```html <!-- Dynamic command parameters --> <button command="--text:set:Hello {{user.name}}!" commandfor="greeting">Greet</button> <button command="--class:toggle:{{this.dataset.theme}}-mode" commandfor="body">Theme</button> ``` #### Complete Advanced Features (`invokers/advanced`) - 42.4 kB Both event triggers and expressions in one import. ```javascript import { enableAdvancedEvents } from 'invokers/advanced'; enableAdvancedEvents(); ``` ```html <!-- Fully dynamic interactions --> <form command-on="submit.prevent" command="--text:set:Submitted {{this.elements.name.value}}" commandfor="status"> <input name="name" placeholder="Your name"> <button type="submit">Submit</button> </form> ``` ## šŸŽÆ Comprehensive Demo For a deeper dive into Invokers' capabilities, check out our [comprehensive demo](examples/comprehensive-demo.html) that showcases: - **Advanced Event Handling** with `command-on` and dynamic interpolation - **Async Operations** with promises, error handling, and loading states - **Component Communication** through data binding and custom events - **Library Integration** with Chart.js and external APIs - **Advanced Templating** with loops, conditionals, and data injection - **Programmatic Triggering** using the JavaScript API - **Error Handling & Debugging** with detailed logging and recovery - **Command Queuing** for sequential execution The demo uses demo-specific commands that are separate from the core library. To use them in your own projects: ```javascript import 'invokers'; // Core library import { registerDemoCommands } from 'invokers/demo-commands'; registerDemoCommands(); // Only for demos/testing ``` Open `examples/comprehensive-demo.html` in your browser to explore all features interactively. ## šŸƒā€ā™‚ļø Quick Start Examples ### Simple Tab System ```javascript import invokers from 'invokers'; import { registerBaseCommands } from 'invokers/commands/base'; registerBaseCommands(invokers); ``` ```html <div role="tablist"> <button command="--show" commandfor="tab1" role="tab">Tab 1</button> <button command="--show" commandfor="tab2" role="tab">Tab 2</button> </div> <div id="tab1" role="tabpanel">Content 1</div> <div id="tab2" role="tabpanel" hidden>Content 2</div> ``` ### Dynamic Form with Validation ```javascript import invokers from 'invokers'; import { registerFormCommands } from 'invokers/commands/form'; import { registerFlowCommands } from 'invokers/commands/flow'; import { enableAdvancedEvents } from 'invokers/advanced'; registerFormCommands(invokers); registerFlowCommands(invokers); enableAdvancedEvents(); ``` ```html <form command-on="submit.prevent" command="--fetch:send" data-url="/api/contact"> <input name="email" command-on="blur" command="--text:set:Email: {{this.value}}" commandfor="email-preview" required> <div id="email-preview"></div> <button type="submit">Submit</button> </form> ``` ### Rich Media Experience ```javascript import invokers from 'invokers'; import { registerBaseCommands } from 'invokers/commands/base'; import { registerMediaCommands } from 'invokers/commands/media'; registerBaseCommands(invokers); registerMediaCommands(invokers); ``` ```html <video id="player" src="video.mp4"></video> <div class="controls"> <button command="--media:toggle" commandfor="player" data-play-text="Pause" data-pause-text="Play">Play</button> <button command="--media:seek:-10" commandfor="player">-10s</button> <button command="--media:seek:10" commandfor="player">+10s</button> <button command="--media:mute" commandfor="player">Mute</button> </div> ``` ## šŸ“š Progressive Learning Guide Learn Invokers step by step, from basic interactions to complex workflows. ### šŸ“š Level 1: Basic Interactions Start with these essential patterns you'll use every day. #### Interest Invokers (Hover Cards & Tooltips) Create accessible hover cards and tooltips that work across all input methods: ```html <!-- Simple tooltip on hover/focus --> <button type="button" interestfor="help-tip">Help</button> <div id="help-tip" popover="hint">Click to toggle the sidebar</div> <!-- Rich hover card with interactive content --> <a href="/users/johndoe" interestfor="user-card">@johndoe</a> <div id="user-card" popover="auto"> <img src="/avatars/johndoe.jpg" alt="John's avatar"> <h3>John Doe</h3> <p>Full-stack developer</p> <button type="button">Follow</button> </div> <!-- Link preview --> <a href="https://example.com/article" interestfor="article-preview"> Read the full article </a> <div id="article-preview" popover="hint">Quick reference guide...</div> <!-- Adjustable delay timing --> <style> .slow-hint { --interest-delay: 1s 500ms; } </style> <button type="button" interestfor="slow-tip" class="slow-hint">Slow hint</button> <div id="slow-tip" popover="hint">Takes longer to appear</div> ``` #### Native Commands (Work in All Browsers) ```html <!-- Open/close modal dialogs --> <button type="button" command="show-modal" commandfor="my-dialog"> Open Dialog </button> <dialog id="my-dialog"> <p>Hello from a modal dialog!</p> <button type="button" command="close" commandfor="my-dialog">Close</button> </dialog> ``` #### Future Native Commands (Polyfilled) ```html <!-- Details/summary expansion --> <button type="button" command="toggle" commandfor="faq-1">Toggle FAQ</button> <details id="faq-1"> <summary>What is Invokers?</summary> <p>Invokers is a library for declarative HTML interactions...</p> </details> <!-- Media controls --> <button type="button" command="play-pause" commandfor="my-video">āÆļø</button> <button type="button" command="toggle-muted" commandfor="my-video">šŸ”Š</button> <