@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/README.md +137 -76
- package/dist/action-record.d.ts +4 -4
- package/dist/delegate.d.ts +8 -8
- package/dist/delegate.d.ts.map +1 -1
- package/dist/main.d.ts +18 -6
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +3 -3
- package/dist/main.js.map +5 -5
- package/dist/method.d.ts +19 -19
- package/dist/registry.d.ts +4 -4
- package/package.json +5 -5
- package/src/action-record.ts +4 -4
- package/src/delegate.ts +67 -73
- package/src/main.ts +18 -6
- package/src/method.ts +19 -19
- package/src/registry.ts +27 -10
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
|
|
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-
|
|
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('
|
|
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
|
-
* '
|
|
34
|
-
* '
|
|
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: '
|
|
21
|
-
* onAction('
|
|
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
|
-
* '
|
|
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('
|
|
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: '
|
|
72
|
-
* dispatchAction('
|
|
73
|
-
* dispatchAction('
|
|
74
|
-
* dispatchAction('
|
|
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
|
|
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('
|
|
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
|
|
120
|
+
* Registers a custom modifier that can be used in `on-<eventType>` attribute syntax.
|
|
121
121
|
*
|
|
122
|
-
* A modifier is a
|
|
123
|
-
* (e.g. `click
|
|
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
|
|
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-
|
|
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
|
|
154
|
+
* Registers a custom payload resolver that can be used in `on-<eventType>` attribute syntax.
|
|
155
155
|
*
|
|
156
|
-
* A payload resolver is a colon-
|
|
157
|
-
* (e.g. `click
|
|
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-
|
|
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
|
|
49
|
-
* (e.g. `click
|
|
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
|
|
61
|
-
* (e.g. `
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
123
|
+
* @example `<form on-submit="submit_form:$formdata; prevent,validate">`
|
|
124
124
|
* ```ts
|
|
125
|
-
* onAction('
|
|
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)
|
|
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
|
});
|