@alwatr/action 9.17.0 → 9.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"delegate.d.ts","sourceRoot":"","sources":["../src/delegate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AA0NH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAA2C,CAAC;AAElG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,GAAE,SAAS,MAAM,EAA6B,GAAG,IAAI,CASpG;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAO/C"}
1
+ {"version":3,"file":"delegate.d.ts","sourceRoot":"","sources":["../src/delegate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAyNH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAA2C,CAAC;AAElG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,GAAE,SAAS,MAAM,EAA6B,GAAG,IAAI,CASpG;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAO/C"}
@@ -1,4 +1,4 @@
1
- import type { Action } from './action.js';
1
+ import type { Action } from './type.js';
2
2
  /**
3
3
  * Module-scoped logger for `@alwatr/action`.
4
4
  * Scoped to `'alwatr-action'` so log lines are easy to filter in the console.
@@ -21,4 +21,4 @@ export declare const logger_: import("@alwatr/logger").AlwatrLogger;
21
21
  * @internal — not part of the public API; use `onAction` / `dispatchAction` instead.
22
22
  */
23
23
  export declare const internalChannel_: import("@alwatr/signal").ChannelSignal<Record<string, Action<never>>>;
24
- //# sourceMappingURL=lib.d.ts.map
24
+ //# sourceMappingURL=lib_.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib_.d.ts","sourceRoot":"","sources":["../src/lib_.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,WAAW,CAAC;AAEtC;;;;;GAKG;AACH,eAAO,MAAM,OAAO,uCAAgC,CAAC;AAErD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB,uEAAuE,CAAC"}
package/dist/main.d.ts CHANGED
@@ -87,8 +87,7 @@
87
87
  *
88
88
  * For page-ready signals in SSG/SSR apps, use `@alwatr/page-ready` instead.
89
89
  */
90
- export type { ActionRecord } from './action-record.js';
91
- export type { Action } from './action.js';
92
- export * from './method.js';
93
90
  export * from './delegate.js';
91
+ export * from './method.js';
92
+ export type * from './type.js';
94
93
  //# sourceMappingURL=main.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AACH,YAAY,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,YAAY,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,mBAAmB,WAAW,CAAC"}
package/dist/main.js CHANGED
@@ -1,5 +1,5 @@
1
- /* 📦 @alwatr/action v9.17.0 */
2
- import{createLogger as Z}from"@alwatr/logger";import{createChannelSignal as H}from"@alwatr/signal";var w=Z("alwatr-action"),J=H({name:"alwatr-action"});var P=new Map,I=new Map;P.set("prevent",(A)=>{return A.preventDefault(),!0});P.set("validate",(A,x)=>{let F=x instanceof HTMLFormElement?x:x.closest("form");if(!F)return!1;return F.checkValidity()});I.set("$value",(A,x)=>{return"value"in x?x.value:null});I.set("$formdata",(A,x)=>{let F=x instanceof HTMLFormElement?x:x.closest("form");return F?Object.fromEntries(new FormData(F)):null});I.set("$checked",(A,x)=>{return"checked"in x?x.checked:null});function k(A,x){return w.logMethodArgs?.("onAction",{type:A}),J.on(A,x)}function V(A){w.logMethodArgs?.("dispatchAction",A),J.dispatch(A.type,A)}function f(A,x){if(w.logMethodArgs?.("registerModifier",{name:A}),P.has(A))w.accident("registerModifier","modifier_already_registered",{name:A});P.set(A,x)}function E(A,x){if(w.logMethodArgs?.("registerPayloadResolver",{name:A}),I.has(A))w.accident("registerPayloadResolver","payload_resolver_already_registered",{name:A});I.set(A,x)}var M=/^([a-z0-9_:-]+)(?::([^;]+))?(?:;\s*([a-z0-9_,-]+))?$/,Q=new Map;function U(A){w.logMethodArgs?.("parseDescriptor__",{attributeValue:A});let x=Q.get(A);if(x!==void 0)return x;let F=A.match(M);if(!F)return w.accident("parseDescriptor__","invalid_syntax",{attributeValue:A}),Q.set(A,null),null;let K=F[1],D=F[2],z=F[3],N={modifiers:z?new Set(z.split(",").filter(Boolean)):new Set,actionId:K,payload:D};return Q.set(A,N),N}function Y(A){let x=A.type;w.logMethodArgs?.("handleDelegatedEvent__",{eventType:x});let F=A.target;if(!F)return;let K=`on-${x}`,D=F.closest?.(`[${K}]`);if(!D)return;let z=D.getAttribute?.(K)?.trim();if(!z){w.accident("handleDelegatedEvent__","empty_attribute",{eventType:x,actionElement:D});return}if(!(D instanceof HTMLElement)){w.accident("handleDelegatedEvent__","target_not_html_element",{eventType:x,actionElement:D});return}let q=U(z);if(!q)return;if(w.logMethodArgs?.("handleDelegatedEvent__.action",{eventType:x,descriptor:q}),q.modifiers.has("once"))D.removeAttribute(K);let N=D.closest("[action-context]")?.getAttribute("action-context")??void 0,O={type:q.actionId,context:N,payload:q.payload};for(let G of q.modifiers){if(G==="once")continue;let X=P.get(G);if(!X){w.accident("handleDelegatedEvent__","unknown_modifier",{eventType:x,modifier:G,attributeValue:z,descriptor:q});return}if(X(A,D,O)===!1)return}if(q.payload){let G=I.get(q.payload);if(G)O.payload=G(A,D)}else O.payload=void 0;J.dispatch(O.type,O)}var W=new Set,B=["click","submit","input","change"];function p(A=B){w.logMethodArgs?.("setupActionDelegation",{eventTypes:A});for(let x of A){if(W.has(x))continue;W.add(x),document.body.addEventListener(x,Y,{capture:!0})}}function u(){w.logMethod?.("teardownActionDelegation");for(let A of W)document.body.removeEventListener(A,Y,{capture:!0});W.clear(),Q.clear()}export{u as teardownActionDelegation,p as setupActionDelegation,E as registerPayloadResolver,f as registerModifier,k as onAction,V as dispatchAction,B as DEFAULT_DELEGATED_EVENTS};
1
+ /* 📦 @alwatr/action v9.18.1 */
2
+ import{createLogger as Z}from"@alwatr/logger";import{createChannelSignal as H}from"@alwatr/signal";var w=Z("alwatr-action"),J=H({name:"alwatr-action"});var P=new Map,I=new Map;P.set("prevent",(A)=>{return A.preventDefault(),!0});P.set("validate",(A,x)=>{let F=x instanceof HTMLFormElement?x:x.closest("form");if(!F)return!1;return F.checkValidity()});I.set("$value",(A,x)=>{return"value"in x?x.value:null});I.set("$formdata",(A,x)=>{let F=x instanceof HTMLFormElement?x:x.closest("form");return F?Object.fromEntries(new FormData(F)):null});I.set("$checked",(A,x)=>{return"checked"in x?x.checked:null});var M=/^([a-z0-9_:-]+)(?::([^;]+))?(?:;\s*([a-z0-9_,-]+))?$/,Q=new Map;function U(A){w.logMethodArgs?.("parseDescriptor__",{attributeValue:A});let x=Q.get(A);if(x!==void 0)return x;let F=A.match(M);if(!F)return w.accident("parseDescriptor__","invalid_syntax",{attributeValue:A}),Q.set(A,null),null;let K=F[1],D=F[2],z=F[3],N={modifiers:z?new Set(z.split(",").filter(Boolean)):new Set,actionId:K,payload:D};return Q.set(A,N),N}function Y(A){let x=A.type;w.logMethodArgs?.("handleDelegatedEvent__",{eventType:x});let F=A.target;if(!F)return;let K=`on-${x}`,D=F.closest?.(`[${K}]`);if(!D)return;let z=D.getAttribute?.(K)?.trim();if(!z){w.accident("handleDelegatedEvent__","empty_attribute",{eventType:x,actionElement:D});return}if(!(D instanceof HTMLElement)){w.accident("handleDelegatedEvent__","target_not_html_element",{eventType:x,actionElement:D});return}let q=U(z);if(!q)return;if(w.logMethodArgs?.("handleDelegatedEvent__.action",{eventType:x,descriptor:q}),q.modifiers.has("once"))D.removeAttribute(K);let N=D.closest("[action-context]")?.getAttribute("action-context")??void 0,O={type:q.actionId,context:N,payload:q.payload};for(let G of q.modifiers){if(G==="once")continue;let X=P.get(G);if(!X){w.accident("handleDelegatedEvent__","unknown_modifier",{eventType:x,modifier:G,attributeValue:z,descriptor:q});return}if(X(A,D,O)===!1)return}if(q.payload){let G=I.get(q.payload);if(G)O.payload=G(A,D)}else O.payload=void 0;J.dispatch(O.type,O)}var W=new Set,B=["click","submit","input","change"];function R(A=B){w.logMethodArgs?.("setupActionDelegation",{eventTypes:A});for(let x of A){if(W.has(x))continue;W.add(x),document.body.addEventListener(x,Y,{capture:!0})}}function V(){w.logMethod?.("teardownActionDelegation");for(let A of W)document.body.removeEventListener(A,Y,{capture:!0});W.clear(),Q.clear()}function T(A,x){return w.logMethodArgs?.("onAction",{type:A}),J.on(A,x)}function p(A){w.logMethodArgs?.("dispatchAction",A),J.dispatch(A.type,A)}function u(A,x){if(w.logMethodArgs?.("registerModifier",{name:A}),P.has(A))w.accident("registerModifier","modifier_already_registered",{name:A});P.set(A,x)}function c(A,x){if(w.logMethodArgs?.("registerPayloadResolver",{name:A}),I.has(A))w.accident("registerPayloadResolver","payload_resolver_already_registered",{name:A});I.set(A,x)}export{V as teardownActionDelegation,R as setupActionDelegation,c as registerPayloadResolver,u as registerModifier,T as onAction,p as dispatchAction,B as DEFAULT_DELEGATED_EVENTS};
3
3
 
4
- //# debugId=F2B64ABE6411370464756E2164756E21
4
+ //# debugId=727670A7DA05103E64756E2164756E21
5
5
  //# sourceMappingURL=main.js.map
package/dist/main.js.map CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/lib.ts", "../src/registry.ts", "../src/method.ts", "../src/delegate.ts"],
3
+ "sources": ["../src/lib_.ts", "../src/registry_.ts", "../src/delegate.ts", "../src/method.ts"],
4
4
  "sourcesContent": [
5
- "import {createLogger} from '@alwatr/logger';\nimport {createChannelSignal} from '@alwatr/signal';\n\nimport type {Action} from './action.js';\n\n/**\n * Module-scoped logger for `@alwatr/action`.\n * Scoped to `'alwatr-action'` so log lines are easy to filter in the console.\n *\n * @internal\n */\nexport const logger_ = createLogger('alwatr-action');\n\n/**\n * The internal action channel — a `ChannelSignal` keyed by action `type`.\n *\n * Each message on this channel is a full `Action` object (AFSA), not just a\n * raw payload. Subscribers registered via `onAction('foo', handler)` receive\n * the entire `Action<'foo'>` so they have access to `context`, `meta`, and\n * `payload` in one place.\n *\n * Uses `ChannelSignal` for O(1) routing: dispatching action `'A'` performs a\n * single `Map.get('A')` lookup and invokes only the handlers registered for\n * that specific type — never handlers for `'B'`, `'C'`, etc.\n *\n * @internal — not part of the public API; use `onAction` / `dispatchAction` instead.\n */\nexport const internalChannel_ = createChannelSignal<Record<string, Action>>({name: 'alwatr-action'});\n",
6
- "import type {Action} from './action.js';\n\n// ─── Type Definitions ────────────────────────────────────────────────────────\n\n/**\n * A modifier handler used in `on-<eventType>` attribute syntax.\n *\n * Receives the triggering DOM `event`, the `element` that owns the\n * `on-<eventType>` attribute, and the **mutable** `action` object being built.\n * The handler may mutate `action.meta` to attach cross-cutting data (e.g. a\n * trace ID, a timestamp, or an A/B flag) before the action reaches subscribers.\n *\n * Return `true` (or any truthy value) to allow the action to proceed, or\n * `false` to cancel the dispatch entirely.\n *\n * Using explicit parameters instead of `this` binding makes handlers\n * compatible with arrow functions and easier to test in isolation.\n *\n * @example — a modifier that stamps a timestamp into meta\n * ```ts\n * const timestampHandler: ModifierHandler = (_event, _element, action) => {\n * action.meta ??= {};\n * action.meta['timestamp'] = Date.now();\n * return true;\n * };\n * ```\n *\n * @example — a modifier that cancels dispatch when the element is disabled\n * ```ts\n * const notDisabledHandler: ModifierHandler = (_event, element) => {\n * return !(element as HTMLButtonElement).disabled;\n * };\n * ```\n */\nexport type ModifierHandler = (event: Event, element: HTMLElement, action: Action) => boolean;\n\n/**\n * A payload resolver used in `on-<eventType>` attribute syntax.\n *\n * Receives the triggering DOM `event` and the `element` that owns the\n * `on-<eventType>` attribute. The return value becomes the `payload` field of\n * the `Action` object passed to `onAction` subscribers.\n *\n * Using explicit parameters instead of `this` binding makes resolvers\n * compatible with arrow functions and easier to test in isolation.\n *\n * @example — a resolver that returns the element's dataset id\n * ```ts\n * const dataIdResolver: PayloadResolver = (_event, element) => {\n * return (element as HTMLElement).dataset.id ?? null;\n * };\n * ```\n */\nexport type PayloadResolver = (event: Event, element: HTMLElement) => unknown;\n\n// ─── Registries ──────────────────────────────────────────────────────────────\n\n/**\n * Registry of all named modifier handlers.\n *\n * Keys are modifier names used in the `on-<eventType>` attribute syntax\n * (e.g. `on-click=\"action-id; prevent\"`). Values are `ModifierHandler` functions.\n * Populated at module load with built-in modifiers; extended at runtime via\n * `registerModifier`.\n *\n * @internal\n */\nexport const modifierRegistry = new Map<string, ModifierHandler>();\n\n/**\n * Registry of all named payload resolvers.\n *\n * Keys are resolver tokens used in the `on-<eventType>` attribute syntax\n * (e.g. `on-input=\"search_query:$value\"`). Values are `PayloadResolver` functions.\n * Populated at module load with built-in resolvers; extended at runtime via\n * `registerPayloadResolver`.\n *\n * @internal\n */\nexport const payloadRegistry = new Map<string, PayloadResolver>();\n\n// ─── Built-in Modifiers ───────────────────────────────────────────────────────\n\n/**\n * `prevent` — calls `event.preventDefault()` before dispatching.\n *\n * Use it to suppress the browser's default behaviour (e.g. form submission,\n * link navigation, context menu).\n *\n * @example `<form on-submit=\"submit-form; prevent\">`\n */\nmodifierRegistry.set('prevent', (event) => {\n event.preventDefault();\n return true;\n});\n\n/**\n * `validate` — cancels the dispatch if the nearest `<form>` fails validation.\n *\n * Looks for a `<form>` ancestor (or the element itself if it is a form) and\n * calls `checkValidity()`. If the form is invalid the action is not dispatched,\n * allowing native constraint-validation UI to surface errors. If no form is\n * found the dispatch is also cancelled.\n *\n * Pair with `prevent` on `submit` events to avoid page reloads:\n *\n * @example `<form on-submit=\"submit_form:$formdata; prevent,validate\" novalidate>`\n */\nmodifierRegistry.set('validate', (_event, element) => {\n const form = element instanceof HTMLFormElement ? element : element.closest('form');\n if (!form) return false;\n return form.checkValidity();\n});\n\n// ─── Built-in Payload Resolvers ───────────────────────────────────────────────\n\n/**\n * `$value` — resolves to the element's `.value` property at dispatch time.\n *\n * Works with any element that exposes a `value` property: `<input>`,\n * `<textarea>`, `<select>`. Returns `null` for elements without `.value`.\n *\n * @example `<input on-input=\"search_query:$value\" />`\n */\npayloadRegistry.set('$value', (_event, element) => {\n return 'value' in element ? (element as {value: unknown}).value : null;\n});\n\n/**\n * `$formdata` — resolves to a plain object of all fields in the nearest `<form>`.\n *\n * Collects entries via `FormData` and converts them to a `Record<string, FormDataEntryValue>`.\n * Looks for a `<form>` ancestor (or the element itself). Returns `null` when no\n * form is found.\n *\n * @example `<form on-submit=\"submit_form:$formdata; prevent,validate\">`\n * ```ts\n * onAction('submit_form', (action) => {\n * console.log(action.payload); // {username: 'ali', password: '…'}\n * });\n * ```\n */\npayloadRegistry.set('$formdata', (_event, element) => {\n const form = element instanceof HTMLFormElement ? element : element.closest('form');\n return form ? Object.fromEntries(new FormData(form)) : null;\n});\n\n/**\n * `$checked` — resolves to the `.checked` boolean property of a checkbox or radio input.\n *\n * Works with `<input type=\"checkbox\">` and `<input type=\"radio\">`.\n * Returns `null` for elements that do not have a `checked` property.\n *\n * @example `<input type=\"checkbox\" on-change=\"toggle_feature:$checked\" />`\n * ```ts\n * onAction('toggle_feature', (action) => {\n * console.log(action.payload); // true or false\n * featureSignal.set(action.payload);\n * });\n * ```\n */\npayloadRegistry.set('$checked', (_event, element) => {\n return 'checked' in element ? (element as HTMLInputElement).checked : null;\n});\n",
7
- "import type {Awaitable} from '@alwatr/type-helper';\nimport type {SubscribeResult} from '@alwatr/signal';\n\nimport {internalChannel_, logger_} from './lib.js';\nimport {modifierRegistry, payloadRegistry, type ModifierHandler, type PayloadResolver} from './registry.js';\nimport type {ActionRecord} from './action-record.js';\nimport type {Action} from './action.js';\n\n// Re-export extension types so consumers can import them from the package root.\nexport type {ModifierHandler, PayloadResolver};\nexport type {Action};\n\n// ─── Core Action API ──────────────────────────────────────────────────────────\n\n/**\n * Subscribes to a named action dispatched anywhere in the application.\n *\n * `type` must be a key of `ActionRecord`. The handler receives the full\n * `Action<K>` object — giving access to `payload`, `context`, and `meta`\n * in one place. No manual generic annotation is needed; the compiler infers\n * the correct `payload` type from `ActionRecord`:\n *\n * ```ts\n * // ActionRecord declares: 'add_to_cart': {productId: number; qty: number}\n * onAction('add_to_cart', (action) => {\n * cartService.add(action.payload.productId, action.payload.qty); // fully typed\n * console.log(action.context); // e.g. 'product-list' (from DOM) or undefined\n * });\n * ```\n *\n * Passing an action name not declared in `ActionRecord` is a **compile error**.\n * Register new actions by extending `ActionRecord` via declaration merging:\n *\n * ```ts\n * // src/action-record.ts\n * declare module '@alwatr/action' {\n * interface ActionRecord {\n * 'open_drawer': string;\n * }\n * }\n * ```\n *\n * Internally delegates to `ChannelSignal.on()` for **O(1) routing** — dispatching\n * action `'A'` never invokes handlers registered for action `'B'`.\n *\n * @param type - A key of `ActionRecord`.\n * @param handler - Callback invoked with the full `Action<K>` on each dispatch.\n * @returns A `SubscribeResult` with an `unsubscribe()` method for cleanup.\n *\n * @example\n * ```ts\n * import {onAction} from '@alwatr/action';\n *\n * const sub = onAction('page_ready', (action) => {\n * router.setPage(action.payload); // payload: string — inferred from ActionRecord\n * });\n *\n * sub.unsubscribe(); // stop listening when no longer needed\n * ```\n */\nexport function onAction<K extends keyof ActionRecord>(\n type: K,\n handler: (action: Action<K>) => Awaitable<void>,\n): SubscribeResult {\n logger_.logMethodArgs?.('onAction', {type});\n // The internal channel stores Action<any>; we cast to Action<K> here because\n // the channel key guarantees the type matches — only Action<K> objects are\n // ever dispatched under key K.\n return internalChannel_.on(type, handler as (action: Action) => Awaitable<void>);\n}\n\n/**\n * Dispatches an action to all `onAction` subscribers with a matching `type`.\n *\n * Accepts a full `Action<K>` object. The `payload` field is automatically\n * typed from `ActionRecord[K]` — passing the wrong shape is a **compile error**:\n *\n * ```ts\n * // ActionRecord declares: 'add_to_cart': {productId: number; qty: number}\n * dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}}); // ✅\n * dispatchAction({type: 'add_to_cart', payload: 'wrong'}); // ❌ compile error\n * dispatchAction({type: 'unknown_action', payload: 'x'}); // ❌ compile error\n * ```\n *\n * The `context` and `meta` fields are optional. When dispatching from code\n * (not from the DOM), omit `context` — it is only meaningful for DOM-originated\n * actions where an `[action-context]` ancestor exists.\n *\n * Use `dispatchAction` when triggering an action from code — e.g. after an\n * async operation, from a service layer, or in tests. For DOM-driven actions,\n * use the `on-<eventType>` HTML attribute with `setupActionDelegation`.\n *\n * @param action - A full `Action<K>` object with at minimum `type` and `payload`.\n *\n * @example — with payload\n * ```ts\n * import {dispatchAction} from '@alwatr/action';\n *\n * dispatchAction({type: 'navigate', payload: '/dashboard'});\n * dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}});\n * ```\n *\n * @example — void payload\n * ```ts\n * dispatchAction({type: 'logout', payload: undefined});\n * ```\n *\n * @example — with context and meta\n * ```ts\n * dispatchAction({\n * type: 'slider_change',\n * payload: 75,\n * context: 'volume_slider',\n * meta: {traceId: 'abc-123'},\n * });\n * ```\n */\nexport function dispatchAction<K extends keyof ActionRecord>(action: Action<K>): void {\n logger_.logMethodArgs?.('dispatchAction', action);\n internalChannel_.dispatch(action.type, action);\n}\n\n// ─── Extension API ────────────────────────────────────────────────────────────\n\n/**\n * Registers a custom modifier that can be used in `on-<eventType>` attribute syntax.\n *\n * A modifier is a comma-separated token placed after the `;` separator\n * (e.g. `on-click=\"action-id; mymod\"`). Its handler runs before the payload is\n * resolved and the action is dispatched. Returning `false` cancels the dispatch.\n *\n * The handler also receives the **mutable** `action` object being built, so it\n * can attach data to `action.meta` before the action reaches subscribers.\n *\n * Built-in modifiers (`prevent`, `validate`, `once`) are always available.\n * This function lets you add domain-specific ones.\n *\n * Registering the same name twice logs an accident and overwrites the previous\n * handler — avoid duplicate registrations in production code.\n *\n * @param name - The modifier token (lowercase, no special characters).\n * @param handler - A `ModifierHandler` receiving `(event, element, action)`.\n *\n * @example — a `confirm` modifier that shows a browser dialog\n * ```ts\n * import {registerModifier} from '@alwatr/action';\n *\n * registerModifier('confirm', () => window.confirm('Are you sure?'));\n * ```\n * ```html\n * <button on-click=\"delete_item:42; confirm\">Delete</button>\n * ```\n *\n * @example — a `trace` modifier that stamps a trace ID into meta\n * ```ts\n * registerModifier('trace', (_event, _element, action) => {\n * action.meta ??= {};\n * action.meta['traceId'] = crypto.randomUUID();\n * return true;\n * });\n * ```\n */\nexport function registerModifier(name: string, handler: ModifierHandler): void {\n logger_.logMethodArgs?.('registerModifier', {name});\n if (modifierRegistry.has(name)) {\n logger_.accident('registerModifier', 'modifier_already_registered', {name});\n }\n modifierRegistry.set(name, handler);\n}\n\n/**\n * Registers a custom payload resolver that can be used in `on-<eventType>` attribute syntax.\n *\n * A payload resolver is a colon-prefixed token in the attribute value\n * (e.g. `on-click=\"action-id:$mytoken\"`). Its function is called at dispatch time\n * with the DOM event and the element. The return value becomes the `payload`\n * field of the `Action` object passed to `onAction` subscribers.\n *\n * Built-in resolvers (`$value`, `$formdata`, `$checked`) are always available.\n * This function lets you add domain-specific ones.\n *\n * Registering the same name twice logs an accident and overwrites the previous\n * resolver — avoid duplicate registrations in production code.\n *\n * @param name - The resolver token (should start with `$` by convention).\n * @param resolver - A `PayloadResolver` receiving `(event, element)`.\n *\n * @example — a `$data-id` resolver that reads a data attribute\n * ```ts\n * import {registerPayloadResolver} from '@alwatr/action';\n *\n * registerPayloadResolver('$data-id', (_event, element) => {\n * return (element as HTMLElement).dataset.id ?? null;\n * });\n * ```\n * ```html\n * <button on-click=\"select_item:$data-id\" data-id=\"42\">Select</button>\n * ```\n */\nexport function registerPayloadResolver(name: string, resolver: PayloadResolver): void {\n logger_.logMethodArgs?.('registerPayloadResolver', {name});\n if (payloadRegistry.has(name)) {\n logger_.accident('registerPayloadResolver', 'payload_resolver_already_registered', {name});\n }\n payloadRegistry.set(name, resolver);\n}\n",
8
- "/**\n * @file delegate.ts\n *\n * Global Event Delegation engine for `@alwatr/action`.\n *\n * ## Why delegation instead of per-element listeners?\n *\n * The classic directive approach attaches one `addEventListener` per element.\n * With 100 buttons on a page, that means 100 listener registrations at boot\n * time — O(N) initialization cost, O(N) memory for listener references, and\n * zero support for elements added after bootstrap.\n *\n * This module implements the global delegation pattern:\n * - A single listener per event type is attached to `document.body` with\n * `capture: true` (so it fires even for non-bubbling events).\n * - When an event fires, the handler walks up the DOM from `event.target`\n * using `closest()` to find the nearest element with an `on-<eventType>`\n * attribute (e.g. `on-click`, `on-submit`).\n * - The nearest `[action-context]` ancestor is also resolved and attached to\n * the `Action` object as `context` — enabling the same action type to be\n * scoped to different UI regions.\n * - Modifiers run with access to the mutable `Action` object so they can\n * enrich `meta` before the action reaches subscribers.\n * - `dispatchAction` is called with the fully assembled `Action` object.\n *\n * ## Complexity\n *\n * | Metric | Per-element listeners | Global delegation |\n * | --------------- | --------------------- | ----------------- |\n * | Boot time | O(N elements) | O(1) — 1 loop |\n * | Memory | O(N listeners) | O(1) — 1 handler |\n * | Dynamic content | Requires re-bootstrap | Works out-of-box |\n * | `once` modifier | Native option | Manual tracking |\n *\n * ## Trade-offs vs. the directive approach\n *\n * - `passive` is not supported as a per-element option (all delegated listeners\n * are non-passive so that `prevent` can call `preventDefault()`).\n * - `stop` stops further bubbling but the delegation handler has already\n * captured the event at `body` level — it does not prevent other delegation\n * handlers from running on the same element.\n * - `once` is emulated by removing the attribute after first fire.\n */\n\nimport {internalChannel_, logger_} from './lib.js';\nimport {modifierRegistry, payloadRegistry} from './registry.js';\nimport type {Action} from './action.js';\nimport type {ActionRecord} from './action-record.js';\n\n// ─── Syntax Parser ────────────────────────────────────────────────────────────\n\n/**\n * Parses the `on-<eventType>` attribute value into its segments.\n *\n * Syntax: `actionId[:payload][; modifier1,modifier2,…]`\n *\n * The event type is encoded in the **attribute name** itself (`on-click`,\n * `on-submit`, etc.) rather than inside the value. This makes the HTML more\n * readable and aligns with native event attribute conventions.\n *\n * | Capture group | Matches | Example |\n * | ------------- | -------------------------------------- | ---------------------- |\n * | 1 | Action identifier | `open_drawer` |\n * | 2 | Optional payload token or literal | `main_menu` / `$value` |\n * | 3 | Optional comma-separated modifier list | `prevent,validate` |\n *\n * @example\n * ```\n * 'close_drawer'\n * → actionId='close_drawer', payload=undefined, modifiers={}\n *\n * 'open_drawer:main_menu'\n * → actionId='open_drawer', payload='main_menu', modifiers={}\n *\n * 'my_submit_handler:$formdata; prevent,validate'\n * → actionId='my_submit_handler', payload='$formdata', modifiers={'prevent','validate'}\n * ```\n */\nconst syntaxRegex = /^([a-z0-9_:-]+)(?::([^;]+))?(?:;\\s*([a-z0-9_,-]+))?$/;\n\n// ─── Parsed Action Descriptor ─────────────────────────────────────────────────\n\n/**\n * Parsed and cached representation of a single `on-<eventType>` attribute value.\n *\n * Does not store `eventType` — the caller always has it from `event.type`,\n * and the attribute name already encodes it (e.g. `on-click`), so storing it\n * here would be redundant. This also keeps the cache key simple: just the raw\n * attribute value string, with no composite key needed.\n */\ninterface ActionDescriptor {\n /** Set of active modifier names (e.g. `{'prevent', 'once'}`). */\n readonly modifiers: ReadonlySet<string>;\n /** The action identifier dispatched to `onAction` subscribers. */\n readonly actionId: string;\n /** Raw payload token from the attribute (literal string or $-resolver key). */\n readonly payload: string | undefined;\n}\n\n/**\n * Cache for parsed `on-<eventType>` attribute values.\n *\n * Attribute strings are typically repeated across many elements (e.g. every\n * \"add to cart\" button shares the same `on-click` value). Caching the parsed\n * descriptor avoids redundant regex work on every event.\n *\n * The cache key is the raw attribute value string. No composite key with\n * event type is needed because the attribute name already encodes the event\n * type — `on-click=\"open_drawer\"` and `on-submit=\"open_drawer\"` are two\n * separate attributes with the same value string, but they are read from\n * different attribute names and never collide in this cache.\n *\n * @internal\n */\nconst descriptorCache__ = new Map<string, ActionDescriptor | null>();\n\n/**\n * Parses an `on-<eventType>` attribute value into an `ActionDescriptor`.\n *\n * Returns `null` when the syntax is invalid. Results are cached by the raw\n * attribute value string so repeated calls for the same value are O(1).\n *\n * @internal\n */\nfunction parseDescriptor__(attributeValue: string): ActionDescriptor | null {\n logger_.logMethodArgs?.('parseDescriptor__', {attributeValue});\n\n const cached = descriptorCache__.get(attributeValue);\n // Explicit `undefined` check: `null` means \"already parsed and invalid\".\n if (cached !== undefined) return cached;\n\n const match = attributeValue.match(syntaxRegex);\n if (!match) {\n logger_.accident('parseDescriptor__', 'invalid_syntax', {attributeValue});\n descriptorCache__.set(attributeValue, null);\n return null;\n }\n\n const actionId = match[1];\n const payload: string | undefined = match[2];\n // match[3] is the raw modifier list string, e.g. \"prevent,validate\"\n const modifierString = match[3];\n const modifiers: Set<string> = modifierString ? new Set(modifierString.split(',').filter(Boolean)) : new Set();\n const descriptor: ActionDescriptor = {modifiers, actionId, payload};\n\n descriptorCache__.set(attributeValue, descriptor);\n return descriptor;\n}\n\n// ─── Core Delegation Handler ──────────────────────────────────────────────────\n\n/**\n * Central event handler attached to `document.body` for every delegated event type.\n *\n * Execution flow for each incoming event:\n * 1. Walk up from `event.target` to find the nearest element with an\n * `on-<eventType>` attribute (e.g. `on-click`, `on-submit`).\n * 2. Parse (or retrieve from cache) the `ActionDescriptor` for that attribute.\n * 3. Resolve `context` from the nearest `[action-context]` ancestor.\n * 4. Build a mutable `Action` object with `type`, `payload` (raw), and `context`.\n * 5. Run each modifier in order with access to the mutable `Action`; if any\n * returns `false`, abort.\n * 6. Resolve the payload token (literal or $-resolver) and assign to `action.payload`.\n * 7. Call `dispatchAction(action)` with the fully assembled object.\n *\n * @internal\n */\nfunction handleDelegatedEvent__(event: Event): void {\n const eventType = event.type;\n logger_.logMethodArgs?.('handleDelegatedEvent__', {eventType});\n\n const target = event.target as Element | null;\n if (!target) return;\n\n // Attribute name encodes the event type: on-click, on-submit, etc.\n const actionAttrib = `on-${eventType}`;\n\n // Walk up the DOM to find the closest element with the matching on-<eventType> attribute.\n const actionElement = target.closest?.(`[${actionAttrib}]`);\n if (!actionElement) return;\n\n const attributeValue = actionElement.getAttribute?.(actionAttrib)?.trim();\n if (!attributeValue) {\n logger_.accident('handleDelegatedEvent__', 'empty_attribute', {eventType, actionElement});\n return;\n }\n\n if (!(actionElement instanceof HTMLElement)) {\n logger_.accident('handleDelegatedEvent__', 'target_not_html_element', {eventType, actionElement});\n return;\n }\n\n const descriptor = parseDescriptor__(attributeValue);\n if (!descriptor) return;\n\n logger_.logMethodArgs?.('handleDelegatedEvent__.action', {eventType, descriptor});\n\n // Step 1: handle `once` modifier — remove attribute before running other modifiers\n // so that even if a modifier aborts, the element will not fire again.\n if (descriptor.modifiers.has('once')) {\n actionElement.removeAttribute(actionAttrib);\n }\n\n // Step 2: resolve `context` from the nearest [action-context] ancestor.\n // Walk up from the action element itself (inclusive) to find the context scope.\n // This allows the action element itself to carry action-context if needed.\n const actionContext = actionElement.closest('[action-context]')?.getAttribute('action-context') ?? undefined;\n\n // Step 3: build the mutable Action object.\n // `payload` starts as the raw token string; it will be resolved in step 5.\n // Modifiers in step 4 may mutate `meta` to attach cross-cutting data.\n const action: Action = {\n type: descriptor.actionId as keyof ActionRecord,\n context: actionContext,\n // Payload is temporarily set to the raw token; resolved below after modifiers run.\n payload: descriptor.payload as ActionRecord[keyof ActionRecord],\n };\n\n // Step 4: run modifiers — each receives the mutable action so it can enrich meta.\n for (const modifier of descriptor.modifiers) {\n if (modifier === 'once') continue; // handled above\n const handler = modifierRegistry.get(modifier);\n if (!handler) {\n logger_.accident('handleDelegatedEvent__', 'unknown_modifier', {eventType, modifier, attributeValue, descriptor});\n return; // unknown modifier — abort to avoid silent misbehavior\n }\n if (handler(event, actionElement, action) === false) return;\n }\n\n // Step 5: resolve payload — replace raw token with the actual value.\n // If the raw token starts with '$', look it up in the payload resolver registry.\n // Otherwise treat it as a literal string payload.\n if (descriptor.payload) {\n const resolver = payloadRegistry.get(descriptor.payload);\n if (resolver) {\n // Cast needed: payload is typed as ActionRecord[K] but we're building generically.\n (action as {payload: unknown}).payload = resolver(event, actionElement);\n }\n // else: keep the literal string already set on action.payload\n } else {\n // No payload token in the attribute — set to undefined.\n (action as {payload: unknown}).payload = undefined;\n }\n\n // Step 6: dispatch the fully assembled Action object.\n internalChannel_.dispatch(action.type, action);\n}\n\n// ─── Setup ────────────────────────────────────────────────────────────────────\n\n/**\n * The set of event types currently delegated to `document.body`.\n *\n * Tracked so that `setupActionDelegation` is idempotent — calling it multiple\n * times with the same event types does not register duplicate listeners.\n *\n * @internal\n */\nconst delegatedEventTypes__ = new Set<string>();\n\n/**\n * Default DOM event types that cover the vast majority of interactive elements.\n *\n * - `click` — buttons, links, checkboxes, custom interactive elements\n * - `submit` — form submission\n * - `input` — live text input, range sliders\n * - `change` — select boxes, checkboxes, radio buttons (fires on commit)\n *\n * Pass additional types to `setupActionDelegation` when your app uses other\n * events (e.g. `'keydown'`, `'pointerup'`).\n */\nexport const DEFAULT_DELEGATED_EVENTS: readonly string[] = ['click', 'submit', 'input', 'change'];\n\n/**\n * Registers global event delegation for `on-<eventType>` attributes.\n *\n * Attaches a single `capture`-phase listener on `document.body` for each\n * event type in `eventTypes`. All processing — context resolution, modifier\n * execution, payload resolution, and `dispatchAction` — happens inside that\n * one handler.\n *\n * **Call this once at application bootstrap**, before any user interaction.\n * Subsequent calls with the same event types are no-ops (idempotent).\n *\n * ### Why `capture: true`?\n *\n * Capture-phase listeners fire before bubble-phase listeners and also catch\n * events that do not bubble (e.g. `focus`, `blur`). This ensures the delegation\n * handler always runs, even when a child element calls `stopPropagation()`.\n *\n * ### Dynamic content\n *\n * Because the listener lives on `document.body`, any element added to the DOM\n * after this call — via `innerHTML`, `lit-html`, a framework renderer, or\n * server-sent HTML — is automatically covered. No re-bootstrap is needed.\n *\n * ### Context scoping\n *\n * Wrap a group of elements in a `[action-context]` container to scope their\n * actions. The delegation handler automatically resolves the nearest ancestor\n * and attaches its value to `action.context`:\n *\n * ```html\n * <section action-context=\"product-list\">\n * <button on-click=\"add_to_cart:42\">Add</button>\n * </section>\n * ```\n *\n * ```ts\n * onAction('add_to_cart', (action) => {\n * console.log(action.context); // 'product-list'\n * });\n * ```\n *\n * @param eventTypes - Event types to delegate. Defaults to `DEFAULT_DELEGATED_EVENTS`.\n *\n * @example — minimal bootstrap\n * ```ts\n * import {setupActionDelegation, onAction} from '@alwatr/action';\n *\n * // One call activates the entire page.\n * setupActionDelegation();\n *\n * onAction('open_drawer', (action) => openDrawer(action.payload));\n * ```\n *\n * @example — with extra event types\n * ```ts\n * import {setupActionDelegation, DEFAULT_DELEGATED_EVENTS} from '@alwatr/action';\n *\n * setupActionDelegation([...DEFAULT_DELEGATED_EVENTS, 'keydown', 'pointerup']);\n * ```\n */\nexport function setupActionDelegation(eventTypes: readonly string[] = DEFAULT_DELEGATED_EVENTS): void {\n logger_.logMethodArgs?.('setupActionDelegation', {eventTypes});\n\n for (const eventType of eventTypes) {\n if (delegatedEventTypes__.has(eventType)) continue; // already registered — skip\n delegatedEventTypes__.add(eventType);\n // capture: true — fires before bubble-phase listeners and catches non-bubbling events.\n document.body.addEventListener(eventType, handleDelegatedEvent__, {capture: true});\n }\n}\n\n/**\n * Removes all global delegation listeners registered by `setupActionDelegation`.\n *\n * Useful in test environments where each test needs a clean slate, or in\n * micro-frontend setups where a sub-app is unmounted.\n *\n * After calling this, `setupActionDelegation` can be called again to re-register.\n */\nexport function teardownActionDelegation(): void {\n logger_.logMethod?.('teardownActionDelegation');\n for (const eventType of delegatedEventTypes__) {\n document.body.removeEventListener(eventType, handleDelegatedEvent__, {capture: true});\n }\n delegatedEventTypes__.clear();\n descriptorCache__.clear();\n}\n"
5
+ "import {createLogger} from '@alwatr/logger';\nimport {createChannelSignal} from '@alwatr/signal';\nimport type {Action} from './type.js';\n\n/**\n * Module-scoped logger for `@alwatr/action`.\n * Scoped to `'alwatr-action'` so log lines are easy to filter in the console.\n *\n * @internal\n */\nexport const logger_ = createLogger('alwatr-action');\n\n/**\n * The internal action channel — a `ChannelSignal` keyed by action `type`.\n *\n * Each message on this channel is a full `Action` object (AFSA), not just a\n * raw payload. Subscribers registered via `onAction('foo', handler)` receive\n * the entire `Action<'foo'>` so they have access to `context`, `meta`, and\n * `payload` in one place.\n *\n * Uses `ChannelSignal` for O(1) routing: dispatching action `'A'` performs a\n * single `Map.get('A')` lookup and invokes only the handlers registered for\n * that specific type — never handlers for `'B'`, `'C'`, etc.\n *\n * @internal — not part of the public API; use `onAction` / `dispatchAction` instead.\n */\nexport const internalChannel_ = createChannelSignal<Record<string, Action>>({name: 'alwatr-action'});\n",
6
+ "import type {ModifierHandler, PayloadResolver} from './type.js';\n\n/**\n * Registry of all named modifier handlers.\n *\n * Keys are modifier names used in the `on-<eventType>` attribute syntax\n * (e.g. `on-click=\"action-id; prevent\"`). Values are `ModifierHandler` functions.\n * Populated at module load with built-in modifiers; extended at runtime via\n * `registerModifier`.\n *\n * @internal\n */\nexport const modifierRegistry = new Map<string, ModifierHandler>();\n\n/**\n * Registry of all named payload resolvers.\n *\n * Keys are resolver tokens used in the `on-<eventType>` attribute syntax\n * (e.g. `on-input=\"search_query:$value\"`). Values are `PayloadResolver` functions.\n * Populated at module load with built-in resolvers; extended at runtime via\n * `registerPayloadResolver`.\n *\n * @internal\n */\nexport const payloadRegistry = new Map<string, PayloadResolver>();\n\n// ─── Built-in Modifiers ───────────────────────────────────────────────────────\n\n/**\n * `prevent` — calls `event.preventDefault()` before dispatching.\n *\n * Use it to suppress the browser's default behaviour (e.g. form submission,\n * link navigation, context menu).\n *\n * @example `<form on-submit=\"submit-form; prevent\">`\n */\nmodifierRegistry.set('prevent', (event) => {\n event.preventDefault();\n return true;\n});\n\n/**\n * `validate` — cancels the dispatch if the nearest `<form>` fails validation.\n *\n * Looks for a `<form>` ancestor (or the element itself if it is a form) and\n * calls `checkValidity()`. If the form is invalid the action is not dispatched,\n * allowing native constraint-validation UI to surface errors. If no form is\n * found the dispatch is also cancelled.\n *\n * Pair with `prevent` on `submit` events to avoid page reloads:\n *\n * @example `<form on-submit=\"submit_form:$formdata; prevent,validate\" novalidate>`\n */\nmodifierRegistry.set('validate', (_event, element) => {\n const form = element instanceof HTMLFormElement ? element : element.closest('form');\n if (!form) return false;\n return form.checkValidity();\n});\n\n// ─── Built-in Payload Resolvers ───────────────────────────────────────────────\n\n/**\n * `$value` — resolves to the element's `.value` property at dispatch time.\n *\n * Works with any element that exposes a `value` property: `<input>`,\n * `<textarea>`, `<select>`. Returns `null` for elements without `.value`.\n *\n * @example `<input on-input=\"search_query:$value\" />`\n */\npayloadRegistry.set('$value', (_event, element) => {\n return 'value' in element ? (element as {value: unknown}).value : null;\n});\n\n/**\n * `$formdata` — resolves to a plain object of all fields in the nearest `<form>`.\n *\n * Collects entries via `FormData` and converts them to a `Record<string, FormDataEntryValue>`.\n * Looks for a `<form>` ancestor (or the element itself). Returns `null` when no\n * form is found.\n *\n * @example `<form on-submit=\"submit_form:$formdata; prevent,validate\">`\n * ```ts\n * onAction('submit_form', (action) => {\n * console.log(action.payload); // {username: 'ali', password: '…'}\n * });\n * ```\n */\npayloadRegistry.set('$formdata', (_event, element) => {\n const form = element instanceof HTMLFormElement ? element : element.closest('form');\n return form ? Object.fromEntries(new FormData(form)) : null;\n});\n\n/**\n * `$checked` — resolves to the `.checked` boolean property of a checkbox or radio input.\n *\n * Works with `<input type=\"checkbox\">` and `<input type=\"radio\">`.\n * Returns `null` for elements that do not have a `checked` property.\n *\n * @example `<input type=\"checkbox\" on-change=\"toggle_feature:$checked\" />`\n * ```ts\n * onAction('toggle_feature', (action) => {\n * console.log(action.payload); // true or false\n * featureSignal.set(action.payload);\n * });\n * ```\n */\npayloadRegistry.set('$checked', (_event, element) => {\n return 'checked' in element ? (element as HTMLInputElement).checked : null;\n});\n",
7
+ "/**\n * @file delegate.ts\n *\n * Global Event Delegation engine for `@alwatr/action`.\n *\n * ## Why delegation instead of per-element listeners?\n *\n * The classic directive approach attaches one `addEventListener` per element.\n * With 100 buttons on a page, that means 100 listener registrations at boot\n * time — O(N) initialization cost, O(N) memory for listener references, and\n * zero support for elements added after bootstrap.\n *\n * This module implements the global delegation pattern:\n * - A single listener per event type is attached to `document.body` with\n * `capture: true` (so it fires even for non-bubbling events).\n * - When an event fires, the handler walks up the DOM from `event.target`\n * using `closest()` to find the nearest element with an `on-<eventType>`\n * attribute (e.g. `on-click`, `on-submit`).\n * - The nearest `[action-context]` ancestor is also resolved and attached to\n * the `Action` object as `context` — enabling the same action type to be\n * scoped to different UI regions.\n * - Modifiers run with access to the mutable `Action` object so they can\n * enrich `meta` before the action reaches subscribers.\n * - `dispatchAction` is called with the fully assembled `Action` object.\n *\n * ## Complexity\n *\n * | Metric | Per-element listeners | Global delegation |\n * | --------------- | --------------------- | ----------------- |\n * | Boot time | O(N elements) | O(1) — 1 loop |\n * | Memory | O(N listeners) | O(1) — 1 handler |\n * | Dynamic content | Requires re-bootstrap | Works out-of-box |\n * | `once` modifier | Native option | Manual tracking |\n *\n * ## Trade-offs vs. the directive approach\n *\n * - `passive` is not supported as a per-element option (all delegated listeners\n * are non-passive so that `prevent` can call `preventDefault()`).\n * - `stop` stops further bubbling but the delegation handler has already\n * captured the event at `body` level — it does not prevent other delegation\n * handlers from running on the same element.\n * - `once` is emulated by removing the attribute after first fire.\n */\n\nimport {internalChannel_, logger_} from './lib_.js';\nimport {modifierRegistry, payloadRegistry} from './registry_.js';\nimport type {Action, ActionRecord} from './type.js';\n\n// ─── Syntax Parser ────────────────────────────────────────────────────────────\n\n/**\n * Parses the `on-<eventType>` attribute value into its segments.\n *\n * Syntax: `actionId[:payload][; modifier1,modifier2,…]`\n *\n * The event type is encoded in the **attribute name** itself (`on-click`,\n * `on-submit`, etc.) rather than inside the value. This makes the HTML more\n * readable and aligns with native event attribute conventions.\n *\n * | Capture group | Matches | Example |\n * | ------------- | -------------------------------------- | ---------------------- |\n * | 1 | Action identifier | `open_drawer` |\n * | 2 | Optional payload token or literal | `main_menu` / `$value` |\n * | 3 | Optional comma-separated modifier list | `prevent,validate` |\n *\n * @example\n * ```\n * 'close_drawer'\n * → actionId='close_drawer', payload=undefined, modifiers={}\n *\n * 'open_drawer:main_menu'\n * → actionId='open_drawer', payload='main_menu', modifiers={}\n *\n * 'my_submit_handler:$formdata; prevent,validate'\n * → actionId='my_submit_handler', payload='$formdata', modifiers={'prevent','validate'}\n * ```\n */\nconst syntaxRegex = /^([a-z0-9_:-]+)(?::([^;]+))?(?:;\\s*([a-z0-9_,-]+))?$/;\n\n// ─── Parsed Action Descriptor ─────────────────────────────────────────────────\n\n/**\n * Parsed and cached representation of a single `on-<eventType>` attribute value.\n *\n * Does not store `eventType` — the caller always has it from `event.type`,\n * and the attribute name already encodes it (e.g. `on-click`), so storing it\n * here would be redundant. This also keeps the cache key simple: just the raw\n * attribute value string, with no composite key needed.\n */\ninterface ActionDescriptor {\n /** Set of active modifier names (e.g. `{'prevent', 'once'}`). */\n readonly modifiers: ReadonlySet<string>;\n /** The action identifier dispatched to `onAction` subscribers. */\n readonly actionId: string;\n /** Raw payload token from the attribute (literal string or $-resolver key). */\n readonly payload: string | undefined;\n}\n\n/**\n * Cache for parsed `on-<eventType>` attribute values.\n *\n * Attribute strings are typically repeated across many elements (e.g. every\n * \"add to cart\" button shares the same `on-click` value). Caching the parsed\n * descriptor avoids redundant regex work on every event.\n *\n * The cache key is the raw attribute value string. No composite key with\n * event type is needed because the attribute name already encodes the event\n * type — `on-click=\"open_drawer\"` and `on-submit=\"open_drawer\"` are two\n * separate attributes with the same value string, but they are read from\n * different attribute names and never collide in this cache.\n *\n * @internal\n */\nconst descriptorCache__ = new Map<string, ActionDescriptor | null>();\n\n/**\n * Parses an `on-<eventType>` attribute value into an `ActionDescriptor`.\n *\n * Returns `null` when the syntax is invalid. Results are cached by the raw\n * attribute value string so repeated calls for the same value are O(1).\n *\n * @internal\n */\nfunction parseDescriptor__(attributeValue: string): ActionDescriptor | null {\n logger_.logMethodArgs?.('parseDescriptor__', {attributeValue});\n\n const cached = descriptorCache__.get(attributeValue);\n // Explicit `undefined` check: `null` means \"already parsed and invalid\".\n if (cached !== undefined) return cached;\n\n const match = attributeValue.match(syntaxRegex);\n if (!match) {\n logger_.accident('parseDescriptor__', 'invalid_syntax', {attributeValue});\n descriptorCache__.set(attributeValue, null);\n return null;\n }\n\n const actionId = match[1];\n const payload: string | undefined = match[2];\n // match[3] is the raw modifier list string, e.g. \"prevent,validate\"\n const modifierString = match[3];\n const modifiers: Set<string> = modifierString ? new Set(modifierString.split(',').filter(Boolean)) : new Set();\n const descriptor: ActionDescriptor = {modifiers, actionId, payload};\n\n descriptorCache__.set(attributeValue, descriptor);\n return descriptor;\n}\n\n// ─── Core Delegation Handler ──────────────────────────────────────────────────\n\n/**\n * Central event handler attached to `document.body` for every delegated event type.\n *\n * Execution flow for each incoming event:\n * 1. Walk up from `event.target` to find the nearest element with an\n * `on-<eventType>` attribute (e.g. `on-click`, `on-submit`).\n * 2. Parse (or retrieve from cache) the `ActionDescriptor` for that attribute.\n * 3. Resolve `context` from the nearest `[action-context]` ancestor.\n * 4. Build a mutable `Action` object with `type`, `payload` (raw), and `context`.\n * 5. Run each modifier in order with access to the mutable `Action`; if any\n * returns `false`, abort.\n * 6. Resolve the payload token (literal or $-resolver) and assign to `action.payload`.\n * 7. Call `dispatchAction(action)` with the fully assembled object.\n *\n * @internal\n */\nfunction handleDelegatedEvent__(event: Event): void {\n const eventType = event.type;\n logger_.logMethodArgs?.('handleDelegatedEvent__', {eventType});\n\n const target = event.target as Element | null;\n if (!target) return;\n\n // Attribute name encodes the event type: on-click, on-submit, etc.\n const actionAttrib = `on-${eventType}`;\n\n // Walk up the DOM to find the closest element with the matching on-<eventType> attribute.\n const actionElement = target.closest?.(`[${actionAttrib}]`);\n if (!actionElement) return;\n\n const attributeValue = actionElement.getAttribute?.(actionAttrib)?.trim();\n if (!attributeValue) {\n logger_.accident('handleDelegatedEvent__', 'empty_attribute', {eventType, actionElement});\n return;\n }\n\n if (!(actionElement instanceof HTMLElement)) {\n logger_.accident('handleDelegatedEvent__', 'target_not_html_element', {eventType, actionElement});\n return;\n }\n\n const descriptor = parseDescriptor__(attributeValue);\n if (!descriptor) return;\n\n logger_.logMethodArgs?.('handleDelegatedEvent__.action', {eventType, descriptor});\n\n // Step 1: handle `once` modifier — remove attribute before running other modifiers\n // so that even if a modifier aborts, the element will not fire again.\n if (descriptor.modifiers.has('once')) {\n actionElement.removeAttribute(actionAttrib);\n }\n\n // Step 2: resolve `context` from the nearest [action-context] ancestor.\n // Walk up from the action element itself (inclusive) to find the context scope.\n // This allows the action element itself to carry action-context if needed.\n const actionContext = actionElement.closest('[action-context]')?.getAttribute('action-context') ?? undefined;\n\n // Step 3: build the mutable Action object.\n // `payload` starts as the raw token string; it will be resolved in step 5.\n // Modifiers in step 4 may mutate `meta` to attach cross-cutting data.\n const action: Action = {\n type: descriptor.actionId as keyof ActionRecord,\n context: actionContext,\n // Payload is temporarily set to the raw token; resolved below after modifiers run.\n payload: descriptor.payload as ActionRecord[keyof ActionRecord],\n };\n\n // Step 4: run modifiers — each receives the mutable action so it can enrich meta.\n for (const modifier of descriptor.modifiers) {\n if (modifier === 'once') continue; // handled above\n const handler = modifierRegistry.get(modifier);\n if (!handler) {\n logger_.accident('handleDelegatedEvent__', 'unknown_modifier', {eventType, modifier, attributeValue, descriptor});\n return; // unknown modifier — abort to avoid silent misbehavior\n }\n if (handler(event, actionElement, action) === false) return;\n }\n\n // Step 5: resolve payload — replace raw token with the actual value.\n // If the raw token starts with '$', look it up in the payload resolver registry.\n // Otherwise treat it as a literal string payload.\n if (descriptor.payload) {\n const resolver = payloadRegistry.get(descriptor.payload);\n if (resolver) {\n // Cast needed: payload is typed as ActionRecord[K] but we're building generically.\n (action as {payload: unknown}).payload = resolver(event, actionElement);\n }\n // else: keep the literal string already set on action.payload\n } else {\n // No payload token in the attribute — set to undefined.\n (action as {payload: unknown}).payload = undefined;\n }\n\n // Step 6: dispatch the fully assembled Action object.\n internalChannel_.dispatch(action.type, action);\n}\n\n// ─── Setup ────────────────────────────────────────────────────────────────────\n\n/**\n * The set of event types currently delegated to `document.body`.\n *\n * Tracked so that `setupActionDelegation` is idempotent — calling it multiple\n * times with the same event types does not register duplicate listeners.\n *\n * @internal\n */\nconst delegatedEventTypes__ = new Set<string>();\n\n/**\n * Default DOM event types that cover the vast majority of interactive elements.\n *\n * - `click` — buttons, links, checkboxes, custom interactive elements\n * - `submit` — form submission\n * - `input` — live text input, range sliders\n * - `change` — select boxes, checkboxes, radio buttons (fires on commit)\n *\n * Pass additional types to `setupActionDelegation` when your app uses other\n * events (e.g. `'keydown'`, `'pointerup'`).\n */\nexport const DEFAULT_DELEGATED_EVENTS: readonly string[] = ['click', 'submit', 'input', 'change'];\n\n/**\n * Registers global event delegation for `on-<eventType>` attributes.\n *\n * Attaches a single `capture`-phase listener on `document.body` for each\n * event type in `eventTypes`. All processing — context resolution, modifier\n * execution, payload resolution, and `dispatchAction` — happens inside that\n * one handler.\n *\n * **Call this once at application bootstrap**, before any user interaction.\n * Subsequent calls with the same event types are no-ops (idempotent).\n *\n * ### Why `capture: true`?\n *\n * Capture-phase listeners fire before bubble-phase listeners and also catch\n * events that do not bubble (e.g. `focus`, `blur`). This ensures the delegation\n * handler always runs, even when a child element calls `stopPropagation()`.\n *\n * ### Dynamic content\n *\n * Because the listener lives on `document.body`, any element added to the DOM\n * after this call — via `innerHTML`, `lit-html`, a framework renderer, or\n * server-sent HTML — is automatically covered. No re-bootstrap is needed.\n *\n * ### Context scoping\n *\n * Wrap a group of elements in a `[action-context]` container to scope their\n * actions. The delegation handler automatically resolves the nearest ancestor\n * and attaches its value to `action.context`:\n *\n * ```html\n * <section action-context=\"product-list\">\n * <button on-click=\"add_to_cart:42\">Add</button>\n * </section>\n * ```\n *\n * ```ts\n * onAction('add_to_cart', (action) => {\n * console.log(action.context); // 'product-list'\n * });\n * ```\n *\n * @param eventTypes - Event types to delegate. Defaults to `DEFAULT_DELEGATED_EVENTS`.\n *\n * @example — minimal bootstrap\n * ```ts\n * import {setupActionDelegation, onAction} from '@alwatr/action';\n *\n * // One call activates the entire page.\n * setupActionDelegation();\n *\n * onAction('open_drawer', (action) => openDrawer(action.payload));\n * ```\n *\n * @example — with extra event types\n * ```ts\n * import {setupActionDelegation, DEFAULT_DELEGATED_EVENTS} from '@alwatr/action';\n *\n * setupActionDelegation([...DEFAULT_DELEGATED_EVENTS, 'keydown', 'pointerup']);\n * ```\n */\nexport function setupActionDelegation(eventTypes: readonly string[] = DEFAULT_DELEGATED_EVENTS): void {\n logger_.logMethodArgs?.('setupActionDelegation', {eventTypes});\n\n for (const eventType of eventTypes) {\n if (delegatedEventTypes__.has(eventType)) continue; // already registered — skip\n delegatedEventTypes__.add(eventType);\n // capture: true — fires before bubble-phase listeners and catches non-bubbling events.\n document.body.addEventListener(eventType, handleDelegatedEvent__, {capture: true});\n }\n}\n\n/**\n * Removes all global delegation listeners registered by `setupActionDelegation`.\n *\n * Useful in test environments where each test needs a clean slate, or in\n * micro-frontend setups where a sub-app is unmounted.\n *\n * After calling this, `setupActionDelegation` can be called again to re-register.\n */\nexport function teardownActionDelegation(): void {\n logger_.logMethod?.('teardownActionDelegation');\n for (const eventType of delegatedEventTypes__) {\n document.body.removeEventListener(eventType, handleDelegatedEvent__, {capture: true});\n }\n delegatedEventTypes__.clear();\n descriptorCache__.clear();\n}\n",
8
+ "import type {Awaitable} from '@alwatr/type-helper';\nimport type {SubscribeResult} from '@alwatr/signal';\n\nimport {internalChannel_, logger_} from './lib_.js';\nimport {modifierRegistry, payloadRegistry} from './registry_.js';\nimport type {Action, ActionRecord, DispatchParam, ModifierHandler, PayloadResolver} from './type.js';\n\n// ─── Core Action API ──────────────────────────────────────────────────────────\n\n/**\n * Subscribes to a named action dispatched anywhere in the application.\n *\n * `type` must be a key of `ActionRecord`. The handler receives the full\n * `Action<K>` object — giving access to `payload`, `context`, and `meta`\n * in one place. No manual generic annotation is needed; the compiler infers\n * the correct `payload` type from `ActionRecord`:\n *\n * ```ts\n * // ActionRecord declares: 'add_to_cart': {productId: number; qty: number}\n * onAction('add_to_cart', (action) => {\n * cartService.add(action.payload.productId, action.payload.qty); // fully typed\n * console.log(action.context); // e.g. 'product-list' (from DOM) or undefined\n * });\n * ```\n *\n * Passing an action name not declared in `ActionRecord` is a **compile error**.\n * Register new actions by extending `ActionRecord` via declaration merging:\n *\n * ```ts\n * // src/action-record.ts\n * declare module '@alwatr/action' {\n * interface ActionRecord {\n * 'open_drawer': string;\n * }\n * }\n * ```\n *\n * Internally delegates to `ChannelSignal.on()` for **O(1) routing** — dispatching\n * action `'A'` never invokes handlers registered for action `'B'`.\n *\n * @param type - A key of `ActionRecord`.\n * @param handler - Callback invoked with the full `Action<K>` on each dispatch.\n * @returns A `SubscribeResult` with an `unsubscribe()` method for cleanup.\n *\n * @example\n * ```ts\n * import {onAction} from '@alwatr/action';\n *\n * const sub = onAction('page_ready', (action) => {\n * router.setPage(action.payload); // payload: string — inferred from ActionRecord\n * });\n *\n * sub.unsubscribe(); // stop listening when no longer needed\n * ```\n */\nexport function onAction<K extends keyof ActionRecord>(\n type: K,\n handler: (action: Action<K>) => Awaitable<void>,\n): SubscribeResult {\n logger_.logMethodArgs?.('onAction', {type});\n // The internal channel stores Action<any>; we cast to Action<K> here because\n // the channel key guarantees the type matches — only Action<K> objects are\n // ever dispatched under key K.\n return internalChannel_.on(type, handler as (action: Action) => Awaitable<void>);\n}\n\n/**\n * Dispatches an action to all `onAction` subscribers with a matching `type`.\n *\n * Accepts a full `Action<K>` object. The `payload` field is automatically\n * typed from `ActionRecord[K]` — passing the wrong shape is a **compile error**:\n *\n * ```ts\n * // ActionRecord declares: 'add_to_cart': {productId: number; qty: number}\n * dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}}); // ✅\n * dispatchAction({type: 'add_to_cart', payload: 'wrong'}); // ❌ compile error\n * dispatchAction({type: 'unknown_action', payload: 'x'}); // ❌ compile error\n * ```\n *\n * The `context` and `meta` fields are optional. When dispatching from code\n * (not from the DOM), omit `context` — it is only meaningful for DOM-originated\n * actions where an `[action-context]` ancestor exists.\n *\n * Use `dispatchAction` when triggering an action from code — e.g. after an\n * async operation, from a service layer, or in tests. For DOM-driven actions,\n * use the `on-<eventType>` HTML attribute with `setupActionDelegation`.\n *\n * @param action - A full `Action<K>` object with at minimum `type` and `payload`.\n *\n * @example — with payload\n * ```ts\n * import {dispatchAction} from '@alwatr/action';\n *\n * dispatchAction({type: 'navigate', payload: '/dashboard'});\n * dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}});\n * ```\n *\n * @example — void payload (payload field is optional and can be omitted entirely)\n * ```ts\n * dispatchAction({type: 'logout'});\n * // or explicitly:\n * dispatchAction({type: 'logout', payload: undefined});\n * ```\n *\n * @example — with context and meta\n * ```ts\n * dispatchAction({\n * type: 'slider_change',\n * payload: 75,\n * context: 'volume_slider',\n * meta: {traceId: 'abc-123'},\n * });\n * ```\n */\nexport function dispatchAction<K extends keyof ActionRecord>(action: DispatchParam<K>): void {\n logger_.logMethodArgs?.('dispatchAction', action);\n internalChannel_.dispatch(action.type, action as Action<K>);\n}\n\n// ─── Extension API ────────────────────────────────────────────────────────────\n\n/**\n * Registers a custom modifier that can be used in `on-<eventType>` attribute syntax.\n *\n * A modifier is a comma-separated token placed after the `;` separator\n * (e.g. `on-click=\"action-id; mymod\"`). Its handler runs before the payload is\n * resolved and the action is dispatched. Returning `false` cancels the dispatch.\n *\n * The handler also receives the **mutable** `action` object being built, so it\n * can attach data to `action.meta` before the action reaches subscribers.\n *\n * Built-in modifiers (`prevent`, `validate`, `once`) are always available.\n * This function lets you add domain-specific ones.\n *\n * Registering the same name twice logs an accident and overwrites the previous\n * handler — avoid duplicate registrations in production code.\n *\n * @param name - The modifier token (lowercase, no special characters).\n * @param handler - A `ModifierHandler` receiving `(event, element, action)`.\n *\n * @example — a `confirm` modifier that shows a browser dialog\n * ```ts\n * import {registerModifier} from '@alwatr/action';\n *\n * registerModifier('confirm', () => window.confirm('Are you sure?'));\n * ```\n * ```html\n * <button on-click=\"delete_item:42; confirm\">Delete</button>\n * ```\n *\n * @example — a `trace` modifier that stamps a trace ID into meta\n * ```ts\n * registerModifier('trace', (_event, _element, action) => {\n * action.meta ??= {};\n * action.meta['traceId'] = crypto.randomUUID();\n * return true;\n * });\n * ```\n */\nexport function registerModifier(name: string, handler: ModifierHandler): void {\n logger_.logMethodArgs?.('registerModifier', {name});\n if (modifierRegistry.has(name)) {\n logger_.accident('registerModifier', 'modifier_already_registered', {name});\n }\n modifierRegistry.set(name, handler);\n}\n\n/**\n * Registers a custom payload resolver that can be used in `on-<eventType>` attribute syntax.\n *\n * A payload resolver is a colon-prefixed token in the attribute value\n * (e.g. `on-click=\"action-id:$mytoken\"`). Its function is called at dispatch time\n * with the DOM event and the element. The return value becomes the `payload`\n * field of the `Action` object passed to `onAction` subscribers.\n *\n * Built-in resolvers (`$value`, `$formdata`, `$checked`) are always available.\n * This function lets you add domain-specific ones.\n *\n * Registering the same name twice logs an accident and overwrites the previous\n * resolver — avoid duplicate registrations in production code.\n *\n * @param name - The resolver token (should start with `$` by convention).\n * @param resolver - A `PayloadResolver` receiving `(event, element)`.\n *\n * @example — a `$data-id` resolver that reads a data attribute\n * ```ts\n * import {registerPayloadResolver} from '@alwatr/action';\n *\n * registerPayloadResolver('$data-id', (_event, element) => {\n * return (element as HTMLElement).dataset.id ?? null;\n * });\n * ```\n * ```html\n * <button on-click=\"select_item:$data-id\" data-id=\"42\">Select</button>\n * ```\n */\nexport function registerPayloadResolver(name: string, resolver: PayloadResolver): void {\n logger_.logMethodArgs?.('registerPayloadResolver', {name});\n if (payloadRegistry.has(name)) {\n logger_.accident('registerPayloadResolver', 'payload_resolver_already_registered', {name});\n }\n payloadRegistry.set(name, resolver);\n}\n"
9
9
  ],
10
- "mappings": "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",
11
- "debugId": "F2B64ABE6411370464756E2164756E21",
10
+ "mappings": "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",
11
+ "debugId": "727670A7DA05103E64756E2164756E21",
12
12
  "names": []
13
13
  }
package/dist/method.d.ts CHANGED
@@ -1,10 +1,6 @@
1
1
  import type { Awaitable } from '@alwatr/type-helper';
2
2
  import type { SubscribeResult } from '@alwatr/signal';
3
- import { type ModifierHandler, type PayloadResolver } from './registry.js';
4
- import type { ActionRecord } from './action-record.js';
5
- import type { Action } from './action.js';
6
- export type { ModifierHandler, PayloadResolver };
7
- export type { Action };
3
+ import type { Action, ActionRecord, DispatchParam, ModifierHandler, PayloadResolver } from './type.js';
8
4
  /**
9
5
  * Subscribes to a named action dispatched anywhere in the application.
10
6
  *
@@ -83,8 +79,10 @@ export declare function onAction<K extends keyof ActionRecord>(type: K, handler:
83
79
  * dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}});
84
80
  * ```
85
81
  *
86
- * @example — void payload
82
+ * @example — void payload (payload field is optional and can be omitted entirely)
87
83
  * ```ts
84
+ * dispatchAction({type: 'logout'});
85
+ * // or explicitly:
88
86
  * dispatchAction({type: 'logout', payload: undefined});
89
87
  * ```
90
88
  *
@@ -98,7 +96,7 @@ export declare function onAction<K extends keyof ActionRecord>(type: K, handler:
98
96
  * });
99
97
  * ```
100
98
  */
101
- export declare function dispatchAction<K extends keyof ActionRecord>(action: Action<K>): void;
99
+ export declare function dispatchAction<K extends keyof ActionRecord>(action: DispatchParam<K>): void;
102
100
  /**
103
101
  * Registers a custom modifier that can be used in `on-<eventType>` attribute syntax.
104
102
  *
@@ -1 +1 @@
1
- {"version":3,"file":"method.d.ts","sourceRoot":"","sources":["../src/method.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAGpD,OAAO,EAAoC,KAAK,eAAe,EAAE,KAAK,eAAe,EAAC,MAAM,eAAe,CAAC;AAC5G,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAGxC,YAAY,EAAC,eAAe,EAAE,eAAe,EAAC,CAAC;AAC/C,YAAY,EAAC,MAAM,EAAC,CAAC;AAIrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,YAAY,EACnD,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,GAC9C,eAAe,CAMjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAGpF;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAM7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAMrF"}
1
+ {"version":3,"file":"method.d.ts","sourceRoot":"","sources":["../src/method.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAIpD,OAAO,KAAK,EAAC,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAC,MAAM,WAAW,CAAC;AAIrG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,YAAY,EACnD,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,GAC9C,eAAe,CAMjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,YAAY,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAG3F;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAM7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAMrF"}
@@ -0,0 +1,24 @@
1
+ import type { ModifierHandler, PayloadResolver } from './type.js';
2
+ /**
3
+ * Registry of all named modifier handlers.
4
+ *
5
+ * Keys are modifier names used in the `on-<eventType>` attribute syntax
6
+ * (e.g. `on-click="action-id; prevent"`). Values are `ModifierHandler` functions.
7
+ * Populated at module load with built-in modifiers; extended at runtime via
8
+ * `registerModifier`.
9
+ *
10
+ * @internal
11
+ */
12
+ export declare const modifierRegistry: Map<string, ModifierHandler>;
13
+ /**
14
+ * Registry of all named payload resolvers.
15
+ *
16
+ * Keys are resolver tokens used in the `on-<eventType>` attribute syntax
17
+ * (e.g. `on-input="search_query:$value"`). Values are `PayloadResolver` functions.
18
+ * Populated at module load with built-in resolvers; extended at runtime via
19
+ * `registerPayloadResolver`.
20
+ *
21
+ * @internal
22
+ */
23
+ export declare const payloadRegistry: Map<string, PayloadResolver>;
24
+ //# sourceMappingURL=registry_.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry_.d.ts","sourceRoot":"","sources":["../src/registry_.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,eAAe,EAAE,eAAe,EAAC,MAAM,WAAW,CAAC;AAEhE;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,8BAAqC,CAAC;AAEnE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,8BAAqC,CAAC"}
package/dist/type.d.ts ADDED
@@ -0,0 +1,160 @@
1
+ import type { DictionaryOpt } from '@alwatr/type-helper';
2
+ /**
3
+ * Global registry mapping action identifiers to their payload types.
4
+ *
5
+ * Extend this interface via declaration merging to register your application's
6
+ * actions and gain full type safety in `onAction` and `dispatchAction`.
7
+ *
8
+ * This interface is intentionally empty in the base package — all actions are
9
+ * application-specific and should be declared in a dedicated `action-record.ts`
10
+ * file within each feature package.
11
+ *
12
+ * @example — registering actions in a feature package
13
+ * ```ts
14
+ * // pkg/my-feature/src/action-record.ts
15
+ * declare module '@alwatr/action' {
16
+ * interface ActionRecord {
17
+ * 'open_drawer': string;
18
+ * 'add_to_cart': {productId: number; qty: number};
19
+ * 'logout': void;
20
+ * }
21
+ * }
22
+ * ```
23
+ */
24
+ export interface ActionRecord {
25
+ }
26
+ /**
27
+ * Builds the parameter type for `dispatchAction`.
28
+ *
29
+ * When `ActionRecord[K]` is `void`, the `payload` field becomes optional so
30
+ * callers can omit it entirely. For all other payload types the field remains
31
+ * required, preserving full type safety.
32
+ *
33
+ * @example — void action (payload omitted)
34
+ * ```ts
35
+ * dispatchAction({type: 'logout'});
36
+ * ```
37
+ *
38
+ * @example — typed action (payload required)
39
+ * ```ts
40
+ * dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}});
41
+ * ```
42
+ */
43
+ export type DispatchParam<K extends keyof ActionRecord> = ActionRecord[K] extends void ? Omit<Action<K>, 'payload'> & {
44
+ payload?: void;
45
+ } : Action<K>;
46
+ /**
47
+ * Alwatr Flux Standard Action (AFSA).
48
+ *
49
+ * The single, canonical object passed to every `dispatchAction` call and
50
+ * received by every `onAction` handler. Keeping all action data in one
51
+ * structure makes the bus extensible without breaking existing call sites.
52
+ *
53
+ * @template K - A key of `ActionRecord`; constrains `type` and `payload` together.
54
+ *
55
+ * @example — dispatching
56
+ * ```ts
57
+ * dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}});
58
+ * ```
59
+ *
60
+ * @example — subscribing
61
+ * ```ts
62
+ * onAction('add_to_cart', (action) => {
63
+ * console.log(action.type); // 'add_to_cart'
64
+ * console.log(action.payload); // {productId: 42, qty: 1}
65
+ * console.log(action.context); // e.g. 'product-list' (from DOM) or undefined
66
+ * });
67
+ * ```
68
+ */
69
+ export interface Action<K extends keyof ActionRecord = keyof ActionRecord> {
70
+ /**
71
+ * Unique action identifier — must be a key of `ActionRecord`.
72
+ *
73
+ * @example 'cart:add-item', 'open_drawer', 'logout'
74
+ */
75
+ readonly type: K;
76
+ /**
77
+ * The DOM context in which the action was triggered.
78
+ *
79
+ * Extracted at delegation time from the nearest ancestor element that carries
80
+ * an `action-context` attribute. Useful for scoping the same action type to
81
+ * different UI regions (e.g. two sliders on the same page both dispatching
82
+ * `'slider:change'` but with different context values).
83
+ *
84
+ * `undefined` when the action is dispatched programmatically (no DOM involved)
85
+ * or when no `[action-context]` ancestor exists.
86
+ *
87
+ * @example 'slider-123', 'product-list', 'checkout-form'
88
+ */
89
+ readonly context?: string;
90
+ /**
91
+ * The pure business payload carried by this action.
92
+ *
93
+ * Type is inferred from `ActionRecord[K]` — the compiler enforces the correct
94
+ * shape at every call site. No manual generic annotation is needed.
95
+ */
96
+ readonly payload: ActionRecord[K];
97
+ /**
98
+ * Open-ended metadata bag for cross-cutting concerns.
99
+ *
100
+ * Intentionally untyped so that future infrastructure layers (tracing,
101
+ * analytics, A/B testing) can attach data without touching the typed API.
102
+ * Modifiers in the delegation pipeline may also write to `meta` before the
103
+ * action reaches subscribers.
104
+ *
105
+ * Treat values here as `unknown` and validate before use.
106
+ *
107
+ * @example {traceId: 'abc-123', timestamp: Date.now()}
108
+ */
109
+ meta?: DictionaryOpt<unknown>;
110
+ }
111
+ /**
112
+ * A modifier handler used in `on-<eventType>` attribute syntax.
113
+ *
114
+ * Receives the triggering DOM `event`, the `element` that owns the
115
+ * `on-<eventType>` attribute, and the **mutable** `action` object being built.
116
+ * The handler may mutate `action.meta` to attach cross-cutting data (e.g. a
117
+ * trace ID, a timestamp, or an A/B flag) before the action reaches subscribers.
118
+ *
119
+ * Return `true` (or any truthy value) to allow the action to proceed, or
120
+ * `false` to cancel the dispatch entirely.
121
+ *
122
+ * Using explicit parameters instead of `this` binding makes handlers
123
+ * compatible with arrow functions and easier to test in isolation.
124
+ *
125
+ * @example — a modifier that stamps a timestamp into meta
126
+ * ```ts
127
+ * const timestampHandler: ModifierHandler = (_event, _element, action) => {
128
+ * action.meta ??= {};
129
+ * action.meta['timestamp'] = Date.now();
130
+ * return true;
131
+ * };
132
+ * ```
133
+ *
134
+ * @example — a modifier that cancels dispatch when the element is disabled
135
+ * ```ts
136
+ * const notDisabledHandler: ModifierHandler = (_event, element) => {
137
+ * return !(element as HTMLButtonElement).disabled;
138
+ * };
139
+ * ```
140
+ */
141
+ export type ModifierHandler = (event: Event, element: HTMLElement, action: Action) => boolean;
142
+ /**
143
+ * A payload resolver used in `on-<eventType>` attribute syntax.
144
+ *
145
+ * Receives the triggering DOM `event` and the `element` that owns the
146
+ * `on-<eventType>` attribute. The return value becomes the `payload` field of
147
+ * the `Action` object passed to `onAction` subscribers.
148
+ *
149
+ * Using explicit parameters instead of `this` binding makes resolvers
150
+ * compatible with arrow functions and easier to test in isolation.
151
+ *
152
+ * @example — a resolver that returns the element's dataset id
153
+ * ```ts
154
+ * const dataIdResolver: PayloadResolver = (_event, element) => {
155
+ * return (element as HTMLElement).dataset.id ?? null;
156
+ * };
157
+ * ```
158
+ */
159
+ export type PayloadResolver = (event: Event, element: HTMLElement) => unknown;
160
+ //# sourceMappingURL=type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../src/type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,YAAY;CAAG;AAEhC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,MAAM,YAAY,IACpD,YAAY,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG;IAAC,OAAO,CAAC,EAAE,IAAI,CAAA;CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;AAE3F;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,MAAM,CAAC,CAAC,SAAS,MAAM,YAAY,GAAG,MAAM,YAAY;IACvE;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAEjB;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAE1B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAElC;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;AAE9F;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alwatr/action",
3
- "version": "9.17.0",
3
+ "version": "9.18.1",
4
4
  "description": "Declarative DOM action-dispatch — bridge HTML attributes to typed signal handlers.",
5
5
  "license": "MPL-2.0",
6
6
  "author": "S. Ali Mihandoost <ali.mihandoost@gmail.com> (https://ali.mihandoost.com)",
@@ -79,5 +79,5 @@
79
79
  "vanilla-js",
80
80
  "web-development"
81
81
  ],
82
- "gitHead": "782563375f55c55d29719cbcfebaca251d69ddcd"
82
+ "gitHead": "33191a8579a5f910b1c1da19f8d20ba88852a83c"
83
83
  }
package/src/delegate.ts CHANGED
@@ -42,10 +42,9 @@
42
42
  * - `once` is emulated by removing the attribute after first fire.
43
43
  */
44
44
 
45
- import {internalChannel_, logger_} from './lib.js';
46
- import {modifierRegistry, payloadRegistry} from './registry.js';
47
- import type {Action} from './action.js';
48
- import type {ActionRecord} from './action-record.js';
45
+ import {internalChannel_, logger_} from './lib_.js';
46
+ import {modifierRegistry, payloadRegistry} from './registry_.js';
47
+ import type {Action, ActionRecord} from './type.js';
49
48
 
50
49
  // ─── Syntax Parser ────────────────────────────────────────────────────────────
51
50
 
@@ -1,7 +1,6 @@
1
1
  import {createLogger} from '@alwatr/logger';
2
2
  import {createChannelSignal} from '@alwatr/signal';
3
-
4
- import type {Action} from './action.js';
3
+ import type {Action} from './type.js';
5
4
 
6
5
  /**
7
6
  * Module-scoped logger for `@alwatr/action`.
package/src/main.ts CHANGED
@@ -87,7 +87,6 @@
87
87
  *
88
88
  * For page-ready signals in SSG/SSR apps, use `@alwatr/page-ready` instead.
89
89
  */
90
- export type {ActionRecord} from './action-record.js';
91
- export type {Action} from './action.js';
92
- export * from './method.js';
93
90
  export * from './delegate.js';
91
+ export * from './method.js';
92
+ export type * from './type.js';
package/src/method.ts CHANGED
@@ -1,14 +1,9 @@
1
1
  import type {Awaitable} from '@alwatr/type-helper';
2
2
  import type {SubscribeResult} from '@alwatr/signal';
3
3
 
4
- import {internalChannel_, logger_} from './lib.js';
5
- import {modifierRegistry, payloadRegistry, type ModifierHandler, type PayloadResolver} from './registry.js';
6
- import type {ActionRecord} from './action-record.js';
7
- import type {Action} from './action.js';
8
-
9
- // Re-export extension types so consumers can import them from the package root.
10
- export type {ModifierHandler, PayloadResolver};
11
- export type {Action};
4
+ import {internalChannel_, logger_} from './lib_.js';
5
+ import {modifierRegistry, payloadRegistry} from './registry_.js';
6
+ import type {Action, ActionRecord, DispatchParam, ModifierHandler, PayloadResolver} from './type.js';
12
7
 
13
8
  // ─── Core Action API ──────────────────────────────────────────────────────────
14
9
 
@@ -100,8 +95,10 @@ export function onAction<K extends keyof ActionRecord>(
100
95
  * dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}});
101
96
  * ```
102
97
  *
103
- * @example — void payload
98
+ * @example — void payload (payload field is optional and can be omitted entirely)
104
99
  * ```ts
100
+ * dispatchAction({type: 'logout'});
101
+ * // or explicitly:
105
102
  * dispatchAction({type: 'logout', payload: undefined});
106
103
  * ```
107
104
  *
@@ -115,9 +112,9 @@ export function onAction<K extends keyof ActionRecord>(
115
112
  * });
116
113
  * ```
117
114
  */
118
- export function dispatchAction<K extends keyof ActionRecord>(action: Action<K>): void {
115
+ export function dispatchAction<K extends keyof ActionRecord>(action: DispatchParam<K>): void {
119
116
  logger_.logMethodArgs?.('dispatchAction', action);
120
- internalChannel_.dispatch(action.type, action);
117
+ internalChannel_.dispatch(action.type, action as Action<K>);
121
118
  }
122
119
 
123
120
  // ─── Extension API ────────────────────────────────────────────────────────────
@@ -1,59 +1,4 @@
1
- import type {Action} from './action.js';
2
-
3
- // ─── Type Definitions ────────────────────────────────────────────────────────
4
-
5
- /**
6
- * A modifier handler used in `on-<eventType>` attribute syntax.
7
- *
8
- * Receives the triggering DOM `event`, the `element` that owns the
9
- * `on-<eventType>` attribute, and the **mutable** `action` object being built.
10
- * The handler may mutate `action.meta` to attach cross-cutting data (e.g. a
11
- * trace ID, a timestamp, or an A/B flag) before the action reaches subscribers.
12
- *
13
- * Return `true` (or any truthy value) to allow the action to proceed, or
14
- * `false` to cancel the dispatch entirely.
15
- *
16
- * Using explicit parameters instead of `this` binding makes handlers
17
- * compatible with arrow functions and easier to test in isolation.
18
- *
19
- * @example — a modifier that stamps a timestamp into meta
20
- * ```ts
21
- * const timestampHandler: ModifierHandler = (_event, _element, action) => {
22
- * action.meta ??= {};
23
- * action.meta['timestamp'] = Date.now();
24
- * return true;
25
- * };
26
- * ```
27
- *
28
- * @example — a modifier that cancels dispatch when the element is disabled
29
- * ```ts
30
- * const notDisabledHandler: ModifierHandler = (_event, element) => {
31
- * return !(element as HTMLButtonElement).disabled;
32
- * };
33
- * ```
34
- */
35
- export type ModifierHandler = (event: Event, element: HTMLElement, action: Action) => boolean;
36
-
37
- /**
38
- * A payload resolver used in `on-<eventType>` attribute syntax.
39
- *
40
- * Receives the triggering DOM `event` and the `element` that owns the
41
- * `on-<eventType>` attribute. The return value becomes the `payload` field of
42
- * the `Action` object passed to `onAction` subscribers.
43
- *
44
- * Using explicit parameters instead of `this` binding makes resolvers
45
- * compatible with arrow functions and easier to test in isolation.
46
- *
47
- * @example — a resolver that returns the element's dataset id
48
- * ```ts
49
- * const dataIdResolver: PayloadResolver = (_event, element) => {
50
- * return (element as HTMLElement).dataset.id ?? null;
51
- * };
52
- * ```
53
- */
54
- export type PayloadResolver = (event: Event, element: HTMLElement) => unknown;
55
-
56
- // ─── Registries ──────────────────────────────────────────────────────────────
1
+ import type {ModifierHandler, PayloadResolver} from './type.js';
57
2
 
58
3
  /**
59
4
  * Registry of all named modifier handlers.
package/src/type.ts ADDED
@@ -0,0 +1,165 @@
1
+ import type {DictionaryOpt} from '@alwatr/type-helper';
2
+
3
+ /**
4
+ * Global registry mapping action identifiers to their payload types.
5
+ *
6
+ * Extend this interface via declaration merging to register your application's
7
+ * actions and gain full type safety in `onAction` and `dispatchAction`.
8
+ *
9
+ * This interface is intentionally empty in the base package — all actions are
10
+ * application-specific and should be declared in a dedicated `action-record.ts`
11
+ * file within each feature package.
12
+ *
13
+ * @example — registering actions in a feature package
14
+ * ```ts
15
+ * // pkg/my-feature/src/action-record.ts
16
+ * declare module '@alwatr/action' {
17
+ * interface ActionRecord {
18
+ * 'open_drawer': string;
19
+ * 'add_to_cart': {productId: number; qty: number};
20
+ * 'logout': void;
21
+ * }
22
+ * }
23
+ * ```
24
+ */
25
+ export interface ActionRecord {}
26
+
27
+ /**
28
+ * Builds the parameter type for `dispatchAction`.
29
+ *
30
+ * When `ActionRecord[K]` is `void`, the `payload` field becomes optional so
31
+ * callers can omit it entirely. For all other payload types the field remains
32
+ * required, preserving full type safety.
33
+ *
34
+ * @example — void action (payload omitted)
35
+ * ```ts
36
+ * dispatchAction({type: 'logout'});
37
+ * ```
38
+ *
39
+ * @example — typed action (payload required)
40
+ * ```ts
41
+ * dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}});
42
+ * ```
43
+ */
44
+ export type DispatchParam<K extends keyof ActionRecord> =
45
+ ActionRecord[K] extends void ? Omit<Action<K>, 'payload'> & {payload?: void} : Action<K>;
46
+
47
+ /**
48
+ * Alwatr Flux Standard Action (AFSA).
49
+ *
50
+ * The single, canonical object passed to every `dispatchAction` call and
51
+ * received by every `onAction` handler. Keeping all action data in one
52
+ * structure makes the bus extensible without breaking existing call sites.
53
+ *
54
+ * @template K - A key of `ActionRecord`; constrains `type` and `payload` together.
55
+ *
56
+ * @example — dispatching
57
+ * ```ts
58
+ * dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}});
59
+ * ```
60
+ *
61
+ * @example — subscribing
62
+ * ```ts
63
+ * onAction('add_to_cart', (action) => {
64
+ * console.log(action.type); // 'add_to_cart'
65
+ * console.log(action.payload); // {productId: 42, qty: 1}
66
+ * console.log(action.context); // e.g. 'product-list' (from DOM) or undefined
67
+ * });
68
+ * ```
69
+ */
70
+ export interface Action<K extends keyof ActionRecord = keyof ActionRecord> {
71
+ /**
72
+ * Unique action identifier — must be a key of `ActionRecord`.
73
+ *
74
+ * @example 'cart:add-item', 'open_drawer', 'logout'
75
+ */
76
+ readonly type: K;
77
+
78
+ /**
79
+ * The DOM context in which the action was triggered.
80
+ *
81
+ * Extracted at delegation time from the nearest ancestor element that carries
82
+ * an `action-context` attribute. Useful for scoping the same action type to
83
+ * different UI regions (e.g. two sliders on the same page both dispatching
84
+ * `'slider:change'` but with different context values).
85
+ *
86
+ * `undefined` when the action is dispatched programmatically (no DOM involved)
87
+ * or when no `[action-context]` ancestor exists.
88
+ *
89
+ * @example 'slider-123', 'product-list', 'checkout-form'
90
+ */
91
+ readonly context?: string;
92
+
93
+ /**
94
+ * The pure business payload carried by this action.
95
+ *
96
+ * Type is inferred from `ActionRecord[K]` — the compiler enforces the correct
97
+ * shape at every call site. No manual generic annotation is needed.
98
+ */
99
+ readonly payload: ActionRecord[K];
100
+
101
+ /**
102
+ * Open-ended metadata bag for cross-cutting concerns.
103
+ *
104
+ * Intentionally untyped so that future infrastructure layers (tracing,
105
+ * analytics, A/B testing) can attach data without touching the typed API.
106
+ * Modifiers in the delegation pipeline may also write to `meta` before the
107
+ * action reaches subscribers.
108
+ *
109
+ * Treat values here as `unknown` and validate before use.
110
+ *
111
+ * @example {traceId: 'abc-123', timestamp: Date.now()}
112
+ */
113
+ meta?: DictionaryOpt<unknown>;
114
+ }
115
+
116
+ /**
117
+ * A modifier handler used in `on-<eventType>` attribute syntax.
118
+ *
119
+ * Receives the triggering DOM `event`, the `element` that owns the
120
+ * `on-<eventType>` attribute, and the **mutable** `action` object being built.
121
+ * The handler may mutate `action.meta` to attach cross-cutting data (e.g. a
122
+ * trace ID, a timestamp, or an A/B flag) before the action reaches subscribers.
123
+ *
124
+ * Return `true` (or any truthy value) to allow the action to proceed, or
125
+ * `false` to cancel the dispatch entirely.
126
+ *
127
+ * Using explicit parameters instead of `this` binding makes handlers
128
+ * compatible with arrow functions and easier to test in isolation.
129
+ *
130
+ * @example — a modifier that stamps a timestamp into meta
131
+ * ```ts
132
+ * const timestampHandler: ModifierHandler = (_event, _element, action) => {
133
+ * action.meta ??= {};
134
+ * action.meta['timestamp'] = Date.now();
135
+ * return true;
136
+ * };
137
+ * ```
138
+ *
139
+ * @example — a modifier that cancels dispatch when the element is disabled
140
+ * ```ts
141
+ * const notDisabledHandler: ModifierHandler = (_event, element) => {
142
+ * return !(element as HTMLButtonElement).disabled;
143
+ * };
144
+ * ```
145
+ */
146
+ export type ModifierHandler = (event: Event, element: HTMLElement, action: Action) => boolean;
147
+
148
+ /**
149
+ * A payload resolver used in `on-<eventType>` attribute syntax.
150
+ *
151
+ * Receives the triggering DOM `event` and the `element` that owns the
152
+ * `on-<eventType>` attribute. The return value becomes the `payload` field of
153
+ * the `Action` object passed to `onAction` subscribers.
154
+ *
155
+ * Using explicit parameters instead of `this` binding makes resolvers
156
+ * compatible with arrow functions and easier to test in isolation.
157
+ *
158
+ * @example — a resolver that returns the element's dataset id
159
+ * ```ts
160
+ * const dataIdResolver: PayloadResolver = (_event, element) => {
161
+ * return (element as HTMLElement).dataset.id ?? null;
162
+ * };
163
+ * ```
164
+ */
165
+ export type PayloadResolver = (event: Event, element: HTMLElement) => unknown;
@@ -1,54 +0,0 @@
1
- /**
2
- * @file action-record.ts
3
- *
4
- * Global action type registry via TypeScript declaration merging.
5
- *
6
- * ## How it works
7
- *
8
- * `ActionRecord` is an open interface — any package in the monorepo (or any
9
- * consumer application) can extend it with their own action names and payload
10
- * types using declaration merging, without modifying this file:
11
- *
12
- * ```ts
13
- * // In your package: src/action-record.ts
14
- * declare module '@alwatr/action' {
15
- * interface ActionRecord {
16
- * 'open_drawer': string;
17
- * 'add_to_cart': {productId: number; qty: number};
18
- * 'logout': void;
19
- * }
20
- * }
21
- * ```
22
- *
23
- * Once declared, `onAction` and `dispatchAction` become fully type-safe for
24
- * those action names — the compiler enforces the correct payload type at every
25
- * call site and provides autocomplete for action identifiers.
26
- *
27
- * Only actions declared in `ActionRecord` are accepted. Passing an unknown
28
- * action name is a **compile error** — there is no string fallback.
29
- */
30
- /**
31
- * Global registry mapping action identifiers to their payload types.
32
- *
33
- * Extend this interface via declaration merging to register your application's
34
- * actions and gain full type safety in `onAction` and `dispatchAction`.
35
- *
36
- * This interface is intentionally empty in the base package — all actions are
37
- * application-specific and should be declared in a dedicated `action-record.ts`
38
- * file within each feature package.
39
- *
40
- * @example — registering actions in a feature package
41
- * ```ts
42
- * // pkg/my-feature/src/action-record.ts
43
- * declare module '@alwatr/action' {
44
- * interface ActionRecord {
45
- * 'open_drawer': string;
46
- * 'add_to_cart': {productId: number; qty: number};
47
- * 'logout': void;
48
- * }
49
- * }
50
- * ```
51
- */
52
- export interface ActionRecord {
53
- }
54
- //# sourceMappingURL=action-record.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"action-record.d.ts","sourceRoot":"","sources":["../src/action-record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,YAAY;CAAG"}
package/dist/action.d.ts DELETED
@@ -1,93 +0,0 @@
1
- /**
2
- * @file action.ts
3
- *
4
- * Defines the Alwatr Flux Standard Action (AFSA) — the single, unified data
5
- * structure that flows through the entire action bus for both dispatch and
6
- * subscription.
7
- *
8
- * ## Why a single Action object?
9
- *
10
- * Previously, `dispatchAction(id, payload)` and `onAction(id, handler(payload))`
11
- * treated the action as two separate concerns: an identifier and a raw payload.
12
- * This made it impossible to carry cross-cutting metadata (context, trace IDs,
13
- * timestamps) without breaking every call site.
14
- *
15
- * AFSA wraps everything into one object:
16
- * - `type` — the action identifier (replaces the first positional argument)
17
- * - `payload` — the business data (replaces the second positional argument)
18
- * - `context` — the DOM context extracted from the nearest `[action-context]`
19
- * ancestor at delegation time; `undefined` for programmatic dispatches
20
- * - `meta` — open-ended bag for future cross-cutting concerns (trace IDs,
21
- * timestamps, A/B flags, etc.) without breaking the typed API
22
- *
23
- * Modifiers in the delegation pipeline can also mutate `meta` before the action
24
- * reaches subscribers — e.g. a `trace` modifier could stamp a request ID.
25
- */
26
- import type { DictionaryOpt } from '@alwatr/type-helper';
27
- import type { ActionRecord } from './action-record.js';
28
- /**
29
- * Alwatr Flux Standard Action (AFSA).
30
- *
31
- * The single, canonical object passed to every `dispatchAction` call and
32
- * received by every `onAction` handler. Keeping all action data in one
33
- * structure makes the bus extensible without breaking existing call sites.
34
- *
35
- * @template K - A key of `ActionRecord`; constrains `type` and `payload` together.
36
- *
37
- * @example — dispatching
38
- * ```ts
39
- * dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}});
40
- * ```
41
- *
42
- * @example — subscribing
43
- * ```ts
44
- * onAction('add_to_cart', (action) => {
45
- * console.log(action.type); // 'add_to_cart'
46
- * console.log(action.payload); // {productId: 42, qty: 1}
47
- * console.log(action.context); // e.g. 'product-list' (from DOM) or undefined
48
- * });
49
- * ```
50
- */
51
- export interface Action<K extends keyof ActionRecord = keyof ActionRecord> {
52
- /**
53
- * Unique action identifier — must be a key of `ActionRecord`.
54
- *
55
- * @example 'cart:add-item', 'open_drawer', 'logout'
56
- */
57
- readonly type: K;
58
- /**
59
- * The DOM context in which the action was triggered.
60
- *
61
- * Extracted at delegation time from the nearest ancestor element that carries
62
- * an `action-context` attribute. Useful for scoping the same action type to
63
- * different UI regions (e.g. two sliders on the same page both dispatching
64
- * `'slider:change'` but with different context values).
65
- *
66
- * `undefined` when the action is dispatched programmatically (no DOM involved)
67
- * or when no `[action-context]` ancestor exists.
68
- *
69
- * @example 'slider-123', 'product-list', 'checkout-form'
70
- */
71
- readonly context?: string;
72
- /**
73
- * The pure business payload carried by this action.
74
- *
75
- * Type is inferred from `ActionRecord[K]` — the compiler enforces the correct
76
- * shape at every call site. No manual generic annotation is needed.
77
- */
78
- readonly payload: ActionRecord[K];
79
- /**
80
- * Open-ended metadata bag for cross-cutting concerns.
81
- *
82
- * Intentionally untyped so that future infrastructure layers (tracing,
83
- * analytics, A/B testing) can attach data without touching the typed API.
84
- * Modifiers in the delegation pipeline may also write to `meta` before the
85
- * action reaches subscribers.
86
- *
87
- * Treat values here as `unknown` and validate before use.
88
- *
89
- * @example {traceId: 'abc-123', timestamp: Date.now()}
90
- */
91
- meta?: DictionaryOpt<unknown>;
92
- }
93
- //# sourceMappingURL=action.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../src/action.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,MAAM,CAAC,CAAC,SAAS,MAAM,YAAY,GAAG,MAAM,YAAY;IACvE;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAEjB;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAE1B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAElC;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;CAC/B"}
package/dist/lib.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC;;;;;GAKG;AACH,eAAO,MAAM,OAAO,uCAAgC,CAAC;AAErD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB,uEAAuE,CAAC"}
@@ -1,73 +0,0 @@
1
- import type { Action } from './action.js';
2
- /**
3
- * A modifier handler used in `on-<eventType>` attribute syntax.
4
- *
5
- * Receives the triggering DOM `event`, the `element` that owns the
6
- * `on-<eventType>` attribute, and the **mutable** `action` object being built.
7
- * The handler may mutate `action.meta` to attach cross-cutting data (e.g. a
8
- * trace ID, a timestamp, or an A/B flag) before the action reaches subscribers.
9
- *
10
- * Return `true` (or any truthy value) to allow the action to proceed, or
11
- * `false` to cancel the dispatch entirely.
12
- *
13
- * Using explicit parameters instead of `this` binding makes handlers
14
- * compatible with arrow functions and easier to test in isolation.
15
- *
16
- * @example — a modifier that stamps a timestamp into meta
17
- * ```ts
18
- * const timestampHandler: ModifierHandler = (_event, _element, action) => {
19
- * action.meta ??= {};
20
- * action.meta['timestamp'] = Date.now();
21
- * return true;
22
- * };
23
- * ```
24
- *
25
- * @example — a modifier that cancels dispatch when the element is disabled
26
- * ```ts
27
- * const notDisabledHandler: ModifierHandler = (_event, element) => {
28
- * return !(element as HTMLButtonElement).disabled;
29
- * };
30
- * ```
31
- */
32
- export type ModifierHandler = (event: Event, element: HTMLElement, action: Action) => boolean;
33
- /**
34
- * A payload resolver used in `on-<eventType>` attribute syntax.
35
- *
36
- * Receives the triggering DOM `event` and the `element` that owns the
37
- * `on-<eventType>` attribute. The return value becomes the `payload` field of
38
- * the `Action` object passed to `onAction` subscribers.
39
- *
40
- * Using explicit parameters instead of `this` binding makes resolvers
41
- * compatible with arrow functions and easier to test in isolation.
42
- *
43
- * @example — a resolver that returns the element's dataset id
44
- * ```ts
45
- * const dataIdResolver: PayloadResolver = (_event, element) => {
46
- * return (element as HTMLElement).dataset.id ?? null;
47
- * };
48
- * ```
49
- */
50
- export type PayloadResolver = (event: Event, element: HTMLElement) => unknown;
51
- /**
52
- * Registry of all named modifier handlers.
53
- *
54
- * Keys are modifier names used in the `on-<eventType>` attribute syntax
55
- * (e.g. `on-click="action-id; prevent"`). Values are `ModifierHandler` functions.
56
- * Populated at module load with built-in modifiers; extended at runtime via
57
- * `registerModifier`.
58
- *
59
- * @internal
60
- */
61
- export declare const modifierRegistry: Map<string, ModifierHandler>;
62
- /**
63
- * Registry of all named payload resolvers.
64
- *
65
- * Keys are resolver tokens used in the `on-<eventType>` attribute syntax
66
- * (e.g. `on-input="search_query:$value"`). Values are `PayloadResolver` functions.
67
- * Populated at module load with built-in resolvers; extended at runtime via
68
- * `registerPayloadResolver`.
69
- *
70
- * @internal
71
- */
72
- export declare const payloadRegistry: Map<string, PayloadResolver>;
73
- //# sourceMappingURL=registry.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAIxC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;AAE9F;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC;AAI9E;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,8BAAqC,CAAC;AAEnE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,8BAAqC,CAAC"}
@@ -1,54 +0,0 @@
1
- /**
2
- * @file action-record.ts
3
- *
4
- * Global action type registry via TypeScript declaration merging.
5
- *
6
- * ## How it works
7
- *
8
- * `ActionRecord` is an open interface — any package in the monorepo (or any
9
- * consumer application) can extend it with their own action names and payload
10
- * types using declaration merging, without modifying this file:
11
- *
12
- * ```ts
13
- * // In your package: src/action-record.ts
14
- * declare module '@alwatr/action' {
15
- * interface ActionRecord {
16
- * 'open_drawer': string;
17
- * 'add_to_cart': {productId: number; qty: number};
18
- * 'logout': void;
19
- * }
20
- * }
21
- * ```
22
- *
23
- * Once declared, `onAction` and `dispatchAction` become fully type-safe for
24
- * those action names — the compiler enforces the correct payload type at every
25
- * call site and provides autocomplete for action identifiers.
26
- *
27
- * Only actions declared in `ActionRecord` are accepted. Passing an unknown
28
- * action name is a **compile error** — there is no string fallback.
29
- */
30
-
31
- /**
32
- * Global registry mapping action identifiers to their payload types.
33
- *
34
- * Extend this interface via declaration merging to register your application's
35
- * actions and gain full type safety in `onAction` and `dispatchAction`.
36
- *
37
- * This interface is intentionally empty in the base package — all actions are
38
- * application-specific and should be declared in a dedicated `action-record.ts`
39
- * file within each feature package.
40
- *
41
- * @example — registering actions in a feature package
42
- * ```ts
43
- * // pkg/my-feature/src/action-record.ts
44
- * declare module '@alwatr/action' {
45
- * interface ActionRecord {
46
- * 'open_drawer': string;
47
- * 'add_to_cart': {productId: number; qty: number};
48
- * 'logout': void;
49
- * }
50
- * }
51
- * ```
52
- */
53
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
54
- export interface ActionRecord {}
package/src/action.ts DELETED
@@ -1,97 +0,0 @@
1
- /**
2
- * @file action.ts
3
- *
4
- * Defines the Alwatr Flux Standard Action (AFSA) — the single, unified data
5
- * structure that flows through the entire action bus for both dispatch and
6
- * subscription.
7
- *
8
- * ## Why a single Action object?
9
- *
10
- * Previously, `dispatchAction(id, payload)` and `onAction(id, handler(payload))`
11
- * treated the action as two separate concerns: an identifier and a raw payload.
12
- * This made it impossible to carry cross-cutting metadata (context, trace IDs,
13
- * timestamps) without breaking every call site.
14
- *
15
- * AFSA wraps everything into one object:
16
- * - `type` — the action identifier (replaces the first positional argument)
17
- * - `payload` — the business data (replaces the second positional argument)
18
- * - `context` — the DOM context extracted from the nearest `[action-context]`
19
- * ancestor at delegation time; `undefined` for programmatic dispatches
20
- * - `meta` — open-ended bag for future cross-cutting concerns (trace IDs,
21
- * timestamps, A/B flags, etc.) without breaking the typed API
22
- *
23
- * Modifiers in the delegation pipeline can also mutate `meta` before the action
24
- * reaches subscribers — e.g. a `trace` modifier could stamp a request ID.
25
- */
26
-
27
- import type {DictionaryOpt} from '@alwatr/type-helper';
28
- import type {ActionRecord} from './action-record.js';
29
-
30
- /**
31
- * Alwatr Flux Standard Action (AFSA).
32
- *
33
- * The single, canonical object passed to every `dispatchAction` call and
34
- * received by every `onAction` handler. Keeping all action data in one
35
- * structure makes the bus extensible without breaking existing call sites.
36
- *
37
- * @template K - A key of `ActionRecord`; constrains `type` and `payload` together.
38
- *
39
- * @example — dispatching
40
- * ```ts
41
- * dispatchAction({type: 'add_to_cart', payload: {productId: 42, qty: 1}});
42
- * ```
43
- *
44
- * @example — subscribing
45
- * ```ts
46
- * onAction('add_to_cart', (action) => {
47
- * console.log(action.type); // 'add_to_cart'
48
- * console.log(action.payload); // {productId: 42, qty: 1}
49
- * console.log(action.context); // e.g. 'product-list' (from DOM) or undefined
50
- * });
51
- * ```
52
- */
53
- export interface Action<K extends keyof ActionRecord = keyof ActionRecord> {
54
- /**
55
- * Unique action identifier — must be a key of `ActionRecord`.
56
- *
57
- * @example 'cart:add-item', 'open_drawer', 'logout'
58
- */
59
- readonly type: K;
60
-
61
- /**
62
- * The DOM context in which the action was triggered.
63
- *
64
- * Extracted at delegation time from the nearest ancestor element that carries
65
- * an `action-context` attribute. Useful for scoping the same action type to
66
- * different UI regions (e.g. two sliders on the same page both dispatching
67
- * `'slider:change'` but with different context values).
68
- *
69
- * `undefined` when the action is dispatched programmatically (no DOM involved)
70
- * or when no `[action-context]` ancestor exists.
71
- *
72
- * @example 'slider-123', 'product-list', 'checkout-form'
73
- */
74
- readonly context?: string;
75
-
76
- /**
77
- * The pure business payload carried by this action.
78
- *
79
- * Type is inferred from `ActionRecord[K]` — the compiler enforces the correct
80
- * shape at every call site. No manual generic annotation is needed.
81
- */
82
- readonly payload: ActionRecord[K];
83
-
84
- /**
85
- * Open-ended metadata bag for cross-cutting concerns.
86
- *
87
- * Intentionally untyped so that future infrastructure layers (tracing,
88
- * analytics, A/B testing) can attach data without touching the typed API.
89
- * Modifiers in the delegation pipeline may also write to `meta` before the
90
- * action reaches subscribers.
91
- *
92
- * Treat values here as `unknown` and validate before use.
93
- *
94
- * @example {traceId: 'abc-123', timestamp: Date.now()}
95
- */
96
- meta?: DictionaryOpt<unknown>;
97
- }