@alwatr/action 9.14.0 → 9.16.0

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.
package/src/main.ts CHANGED
@@ -1,17 +1,29 @@
1
1
  /**
2
2
  * @alwatr/action — Declarative DOM action-dispatch for Unidirectional Data Flow.
3
3
  *
4
- * ## Activating `on-action` attributes
4
+ * ## Activating `on-<eventType>` attributes
5
5
  *
6
6
  * Call `setupActionDelegation()` once at bootstrap. A single capture-phase
7
- * listener on `document.body` handles every `on-action` element — including
8
- * elements added dynamically after bootstrap — with O(1) initialization cost.
7
+ * listener on `document.body` handles every `on-click`, `on-submit`, etc. element —
8
+ * including elements added dynamically after bootstrap — with O(1) initialization cost.
9
9
  *
10
10
  * ```ts
11
11
  * import {setupActionDelegation, onAction} from '@alwatr/action';
12
12
  *
13
13
  * setupActionDelegation();
14
- * onAction('open-drawer', (panel) => openDrawer(panel));
14
+ * onAction('open_drawer', (panel) => openDrawer(panel));
15
+ * ```
16
+ *
17
+ * ## Attribute syntax
18
+ *
19
+ * ```
20
+ * on-<eventType>="actionId[:payload][; modifier1,modifier2,…]"
21
+ * ```
22
+ *
23
+ * ```html
24
+ * <button on-click="open_drawer:main">Open</button>
25
+ * <input on-input="search_query:$value" />
26
+ * <form on-submit="submit_form:$formdata; prevent,validate" novalidate>…</form>
15
27
  * ```
16
28
  *
17
29
  * ## Programmatic dispatch
@@ -30,8 +42,8 @@
30
42
  * // src/action-record.ts
31
43
  * declare module '@alwatr/action' {
32
44
  * interface ActionRecord {
33
- * 'open-drawer': string;
34
- * 'add-to-cart': {productId: number; qty: number};
45
+ * 'open_drawer': string;
46
+ * 'add_to_cart': {productId: number; qty: number};
35
47
  * 'logout': void;
36
48
  * }
37
49
  * }
package/src/method.ts CHANGED
@@ -17,8 +17,8 @@ export type {ModifierHandler, PayloadResolver};
17
17
  * generic annotation needed:
18
18
  *
19
19
  * ```ts
20
- * // ActionRecord declares: 'add-to-cart': {productId: number; qty: number}
21
- * onAction('add-to-cart', (item) => {
20
+ * // ActionRecord declares: 'add_to_cart': {productId: number; qty: number}
21
+ * onAction('add_to_cart', (item) => {
22
22
  * cartService.add(item.productId, item.qty); // fully typed, no `!` needed
23
23
  * });
24
24
  * ```
@@ -30,7 +30,7 @@ export type {ModifierHandler, PayloadResolver};
30
30
  * // src/action-record.ts
31
31
  * declare module '@alwatr/action' {
32
32
  * interface ActionRecord {
33
- * 'open-drawer': string;
33
+ * 'open_drawer': string;
34
34
  * }
35
35
  * }
36
36
  * ```
@@ -46,7 +46,7 @@ export type {ModifierHandler, PayloadResolver};
46
46
  * ```ts
47
47
  * import {onAction} from '@alwatr/action';
48
48
  *
49
- * const sub = onAction('page-ready', (pageId) => {
49
+ * const sub = onAction('page_ready', (pageId) => {
50
50
  * router.setPage(pageId); // pageId: string — inferred from ActionRecord
51
51
  * });
52
52
  *
@@ -68,10 +68,10 @@ export function onAction<K extends keyof ActionRecord>(
68
68
  * automatically typed — passing the wrong type is a **compile error**:
69
69
  *
70
70
  * ```ts
71
- * // ActionRecord declares: 'add-to-cart': {productId: number; qty: number}
72
- * dispatchAction('add-to-cart', {productId: 42, qty: 1}); // ✅
73
- * dispatchAction('add-to-cart', 'wrong'); // ❌ compile error
74
- * dispatchAction('unknown-action', 'x'); // ❌ compile error
71
+ * // ActionRecord declares: 'add_to_cart': {productId: number; qty: number}
72
+ * dispatchAction('add_to_cart', {productId: 42, qty: 1}); // ✅
73
+ * dispatchAction('add_to_cart', 'wrong'); // ❌ compile error
74
+ * dispatchAction('unknown_action', 'x'); // ❌ compile error
75
75
  * ```
76
76
  *
77
77
  * Register new actions by extending `ActionRecord` via declaration merging:
@@ -88,7 +88,7 @@ export function onAction<K extends keyof ActionRecord>(
88
88
  *
89
89
  * Use `dispatchAction` when triggering an action from code — e.g. after an
90
90
  * async operation, from a service layer, or in tests. For DOM-driven actions,
91
- * use the `on-action` HTML attribute with `setupActionDelegation`.
91
+ * use the `on-<eventType>` HTML attribute with `setupActionDelegation`.
92
92
  *
93
93
  * @param actionId - A key of `ActionRecord`.
94
94
  * @param actionPayload - The payload; type is enforced by `ActionRecord`.
@@ -97,7 +97,7 @@ export function onAction<K extends keyof ActionRecord>(
97
97
  * ```ts
98
98
  * import {dispatchAction} from '@alwatr/action';
99
99
  *
100
- * dispatchAction('page-ready', 'home');
100
+ * dispatchAction('page_ready', 'home');
101
101
  * dispatchAction('navigate', '/dashboard');
102
102
  * ```
103
103
  *
@@ -117,10 +117,10 @@ export function dispatchAction<K extends keyof ActionRecord>(
117
117
  // ─── Extension API ────────────────────────────────────────────────────────────
118
118
 
119
119
  /**
120
- * Registers a custom modifier that can be used in `on-action` attribute syntax.
120
+ * Registers a custom modifier that can be used in `on-<eventType>` attribute syntax.
121
121
  *
122
- * A modifier is a dot-chained token placed after the event type
123
- * (e.g. `click.mymod->action-id`). Its handler runs before the payload is
122
+ * A modifier is a comma-separated token placed after the `;` separator
123
+ * (e.g. `on-click="action-id; mymod"`). Its handler runs before the payload is
124
124
  * resolved and the action is dispatched. Returning `false` cancels the dispatch.
125
125
  *
126
126
  * Built-in modifiers (`prevent`, `stop`, `validate`, `once`) are always
@@ -129,7 +129,7 @@ export function dispatchAction<K extends keyof ActionRecord>(
129
129
  * Registering the same name twice logs an accident and overwrites the previous
130
130
  * handler — avoid duplicate registrations in production code.
131
131
  *
132
- * @param name - The modifier token (lowercase, no dots or arrows).
132
+ * @param name - The modifier token (lowercase, no special characters).
133
133
  * @param handler - A `ModifierHandler` receiving `(event, element)`.
134
134
  *
135
135
  * @example — a `confirm` modifier that shows a browser dialog
@@ -139,7 +139,7 @@ export function dispatchAction<K extends keyof ActionRecord>(
139
139
  * registerModifier('confirm', () => window.confirm('Are you sure?'));
140
140
  * ```
141
141
  * ```html
142
- * <button on-action="click.confirm->delete-item:42">Delete</button>
142
+ * <button on-click="delete_item:42; confirm">Delete</button>
143
143
  * ```
144
144
  */
145
145
  export function registerModifier(name: string, handler: ModifierHandler): void {
@@ -151,10 +151,10 @@ export function registerModifier(name: string, handler: ModifierHandler): void {
151
151
  }
152
152
 
153
153
  /**
154
- * Registers a custom payload resolver that can be used in `on-action` attribute syntax.
154
+ * Registers a custom payload resolver that can be used in `on-<eventType>` attribute syntax.
155
155
  *
156
- * A payload resolver is a colon-suffixed token in the attribute value
157
- * (e.g. `click->action-id:$mytoken`). Its function is called at dispatch time
156
+ * A payload resolver is a colon-prefixed token in the attribute value
157
+ * (e.g. `on-click="action-id:$mytoken"`). Its function is called at dispatch time
158
158
  * with an `ActionContext` as `this` and the DOM event as the argument.
159
159
  * The return value becomes the `actionPayload` passed to `onAction` subscribers.
160
160
  *
@@ -176,7 +176,7 @@ export function registerModifier(name: string, handler: ModifierHandler): void {
176
176
  * });
177
177
  * ```
178
178
  * ```html
179
- * <input type="checkbox" on-action="change->toggle-feature:$checked" />
179
+ * <input type="checkbox" on-change="toggle_feature:$checked" />
180
180
  * ```
181
181
  */
182
182
  export function registerPayloadResolver(name: string, resolver: PayloadResolver): void {
package/src/registry.ts CHANGED
@@ -45,8 +45,8 @@ export type PayloadResolver = (event: Event, element: HTMLElement) => unknown;
45
45
  /**
46
46
  * Registry of all named modifier handlers.
47
47
  *
48
- * Keys are modifier names used in the `on-action` attribute syntax
49
- * (e.g. `click.prevent->action-id`). Values are `ModifierHandler` functions.
48
+ * Keys are modifier names used in the `on-<eventType>` attribute syntax
49
+ * (e.g. `on-click="action-id; prevent"`). Values are `ModifierHandler` functions.
50
50
  * Populated at module load with built-in modifiers; extended at runtime via
51
51
  * `registerModifier`.
52
52
  *
@@ -57,8 +57,8 @@ export const modifierRegistry = new Map<string, ModifierHandler>();
57
57
  /**
58
58
  * Registry of all named payload resolvers.
59
59
  *
60
- * Keys are resolver tokens used in the `on-action` attribute syntax
61
- * (e.g. `click->action-id:$value`). Values are `PayloadResolver` functions.
60
+ * Keys are resolver tokens used in the `on-<eventType>` attribute syntax
61
+ * (e.g. `on-input="search_query:$value"`). Values are `PayloadResolver` functions.
62
62
  * Populated at module load with built-in resolvers; extended at runtime via
63
63
  * `registerPayloadResolver`.
64
64
  *
@@ -74,7 +74,7 @@ export const payloadRegistry = new Map<string, PayloadResolver>();
74
74
  * Use it to suppress the browser's default behaviour (e.g. form submission,
75
75
  * link navigation, context menu).
76
76
  *
77
- * @example `<form on-action="submit.prevent->submit-form">`
77
+ * @example `<form on-submit="submit-form; prevent">`
78
78
  */
79
79
  modifierRegistry.set('prevent', (event) => {
80
80
  event.preventDefault();
@@ -91,7 +91,7 @@ modifierRegistry.set('prevent', (event) => {
91
91
  *
92
92
  * Pair with `.prevent` on `submit` events to avoid page reloads:
93
93
  *
94
- * @example `<form on-action="submit.prevent.validate->submit-form:$formdata" novalidate>`
94
+ * @example `<form on-submit="submit_form:$formdata; prevent,validate" novalidate>`
95
95
  */
96
96
  modifierRegistry.set('validate', (_event, element) => {
97
97
  const form = element instanceof HTMLFormElement ? element : element.closest('form');
@@ -107,7 +107,7 @@ modifierRegistry.set('validate', (_event, element) => {
107
107
  * Works with any element that exposes a `value` property: `<input>`,
108
108
  * `<textarea>`, `<select>`. Returns `null` for elements without `.value`.
109
109
  *
110
- * @example `<input on-action="input->search-query:$value" />`
110
+ * @example `<input on-input="search_query:$value" />`
111
111
  */
112
112
  payloadRegistry.set('$value', (_event, element) => {
113
113
  return 'value' in element ? (element as {value: unknown}).value : null;
@@ -120,14 +120,31 @@ payloadRegistry.set('$value', (_event, element) => {
120
120
  * Looks for a `<form>` ancestor (or the element itself). Returns `null` when no
121
121
  * form is found.
122
122
  *
123
- * @example `<form on-action="submit.prevent.validate->submit-form:$formdata">`
123
+ * @example `<form on-submit="submit_form:$formdata; prevent,validate">`
124
124
  * ```ts
125
- * onAction('submit-form', (data) => {
125
+ * onAction('submit_form', (data) => {
126
126
  * console.log(data); // {username: 'ali', password: '…'}
127
127
  * });
128
128
  * ```
129
129
  */
130
130
  payloadRegistry.set('$formdata', (_event, element) => {
131
131
  const form = element instanceof HTMLFormElement ? element : element.closest('form');
132
- return form ? Object.fromEntries(new FormData(form).entries()) : null;
132
+ return form ? Object.fromEntries(new FormData(form)) : null;
133
+ });
134
+
135
+ /**
136
+ * `$checked` — resolves to the `.checked` boolean property of a checkbox or radio input.
137
+ *
138
+ * Works with `<input type="checkbox">` and `<input type="radio">`.
139
+ * Returns `null` for elements that do not have a `checked` property.
140
+ *
141
+ * @example `<input type="checkbox" on-change="toggle_feature:$checked" />`
142
+ * ```ts
143
+ * onAction('toggle_feature', (isChecked) => {
144
+ * featureSignal.set(isChecked as boolean);
145
+ * });
146
+ * ```
147
+ */
148
+ payloadRegistry.set('$checked', (_event, element) => {
149
+ return 'checked' in element ? (element as HTMLInputElement).checked : null;
133
150
  });