import { start, stop, register, listen, scan, observe, run, preload, loaded, registered, use, fromGlob, defineAction, version } from 'cyclewire';The classic-script builds expose the same functions on window.CycleWire. Every function
is also available to actions as ctx.wire.
Attaches the delegated listeners, activates triggers and starts watching for content.
Returns the API. Calling it again only registers the actions you pass.
| Option | Default | |
|---|---|---|
actions |
– | Registry entries, see register |
prefix |
'cw-' |
Attribute prefix: cw-action. 'data-cw-' gives names HTML validators accept; '' means data- |
events |
– | Extra event types to delegate |
capture |
false |
Delegate every event in the capture phase, before your own handlers (useful when an island calls stopPropagation() on everything) |
rootMargin |
'120px' |
IntersectionObserver margin for visible |
idleTimeout |
2000 |
requestIdleCallback timeout for idle, in ms |
mutations |
true |
Watch for added and removed content with a MutationObserver |
shadow |
false |
Observe the open shadow roots found while scanning (declarative shadow DOM) |
preload |
'auto' |
What else fetches the modules of elements without cw-preload. 'auto': on screens that cannot hover ((hover: none)), the element nearing the viewport once the page is idle; 'visible': the same on every screen; 'intent': nothing but intent |
onError |
console.error |
(error, { action, element, event }) => void for failed runs |
plugins |
– | Plugins to install, e.g. [signals()] |
If another copy of CycleWire already started on the page (say a CDN copy next to a
bundled one), start() warns and defers to it instead of delegating every event twice.
Removes every listener and observer, aborts running actions and cancels scheduled
triggers. A later start() activates the page afresh.
register({
cart: () => import('./actions/cart.js'), // loader function: bundler code splitting
map: '/js/actions/map.js', // URL, resolved against document.baseURI
chart: 'app/chart', // bare specifier, resolved by the page's import map
datepicker: { // an object: the module, plus options for plugins
module: '/js/actions/datepicker.js',
css: ['/css/vendor/flatpickr.css'], // read by styles() from cyclewire/css
},
});An object entry names its module in module; its other fields are options for plugins.
With the styles() plugin of cyclewire/css, css lists stylesheets that are
preloaded with the module and applied before the handler runs.
Registering a name again replaces it and forgets its cached module. Triggers waiting for
a name run as soon as it is registered. Names may contain letters, digits, _, - and
..
A module that fails to load is forgotten, and the next interaction imports it again.
Browsers remember failed module URLs, so URL entries are retried with a cw-retry query
parameter.
listen(['dblclick', 'contextmenu']);
listen(['pointermove'], { passive: true });Delegates more event types on every observed root, at once. Listeners are passive for
high-frequency events that cannot be cancelled usefully (pointermove, touchmove,
wheel, scroll, …). Override with { capture, passive }.
Activates the triggers and scheduled preloads in root, including root itself, and
delegates the event types its bindings use. You only need it when you started with
mutations: false, or to use a binding set on an element already in the page before
any pointer or focus reaches it; otherwise added content is scanned automatically.
class CartWidget extends HTMLElement {
connectedCallback() {
const root = this.attachShadow({ mode: 'open' });
root.innerHTML = '<form cw-action="cart#checkout">…</form>';
this.release = observe(root);
}
disconnectedCallback() {
this.release();
}
}Delegates events inside a shadow root, including the ones that do not cross its boundary
(submit, change, toggle, command), and activates and watches its triggers.
Returns a function that undoes it. See shadow DOM.
const result = await run('cart#add', button);Runs an action programmatically, through the same once, debounce and concurrency rules
as a delegated event, so the default mode is drop. Resolves to the handler's return
value, or undefined when the run was dropped, superseded or cancelled. Rejects if the
action is not registered or the handler throws. element defaults to
document.documentElement.
Fetches an action's module ahead of use, even under Save-Data. Returns a promise that settles when the fetch has finished; it never rejects.
Names of the action modules imported so far. It is [] until the user reaches for
something, which makes it a handy assertion in tests and a quick check in the console.
Names of the registered action modules, imported or not, in the order they were registered. Tools use it to compare the registry with the markup.
Adds a plugin. A plugin is a plain object:
{
setup({ prefix, wire }) {}, // once, when added
context(ctx) {}, // add fields to every action context
scan(root) {}, // see every subtree CycleWire scans
preload(entry, name) {}, // an action is being preloaded: fetch what else it needs
load(entry, element, name) {}, // an action is about to run: return a promise the handler waits for
stop() {},
}See plugins.
start({ actions: fromGlob(import.meta.glob('./actions/**/*.js')) });
// ./actions/cart.js → "cart", ./actions/cart/add.js → "cart.add", ./actions/menu/index.js → "menu"Turns a path → loader map into action names by stripping base and the extension and
turning / into ..
Returns the handler it is given. It exists for types: with it, editors and TypeScript
check the handler's context, props included, and cyclewire types reads the props for
the markup's type checks.
import { defineAction } from 'cyclewire';
export const add = defineAction<{ sku: string }, HTMLFormElement>(async ({ props, element, signal }) => {
await fetch('/cart', { method: 'POST', body: new FormData(element), signal });
console.log(props.sku);
});The package version, e.g. "1.0.0".
Dispatched on the bound element. They bubble and cross shadow boundaries.
| Event | detail |
Cancelable |
|---|---|---|
cw:run |
{ action, event } |
Yes. Cancelling it makes CycleWire step aside, and the browser default happens |
cw:done |
{ action, result } |
No |
cw:error |
{ action, error } |
No |
document.addEventListener('cw:done', ({ detail }) => analytics.track(detail.action));An element removed from the page cannot bubble events to the document, so use onError
to catch every failure.
Types ship with the package:
import type { ActionMap, Context, Options, Plugin } from 'cyclewire';
export async function run({ element, props, signal }: Context<{ id: string }, HTMLButtonElement>) {
// …
}Context<Props, Element> and Action<Props, Element> take the props and the element's
type. ActionName is any string until a global CycleWireActions interface lists the
actions; run() and preload() then accept only those names, and PropsOf<'cart#add'>
is that handler's props:
// cyclewire-actions.d.ts
interface CycleWireActions {
'cart#add': typeof import('./actions/cart.js').add;
}cyclewire types and the Vite plugin write this file for you: see
TypeScript.