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
Markdown
# ⨠Invokers: _Write Interactive HTML Without Writing JavaScript_
[](https://www.npmjs.com/package/invokers)
[](https://github.com/doeixd/invokers/actions)
[](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>
<