@alwatr/action 9.14.0 → 9.17.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 +335 -95
- package/dist/action-record.d.ts +4 -4
- package/dist/action.d.ts +93 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/delegate.d.ts +33 -10
- package/dist/delegate.d.ts.map +1 -1
- package/dist/lib.d.ts +8 -6
- package/dist/lib.d.ts.map +1 -1
- package/dist/main.d.ts +47 -8
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +3 -3
- package/dist/main.js.map +6 -6
- package/dist/method.d.ts +74 -57
- package/dist/method.d.ts.map +1 -1
- package/dist/registry.d.ts +28 -16
- package/dist/registry.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/action-record.ts +4 -4
- package/src/action.ts +97 -0
- package/src/delegate.ts +131 -86
- package/src/lib.ts +9 -6
- package/src/main.ts +47 -8
- package/src/method.ts +84 -66
- package/src/registry.ts +55 -24
package/src/delegate.ts
CHANGED
|
@@ -10,14 +10,18 @@
|
|
|
10
10
|
* time — O(N) initialization cost, O(N) memory for listener references, and
|
|
11
11
|
* zero support for elements added after bootstrap.
|
|
12
12
|
*
|
|
13
|
-
* This module implements the
|
|
13
|
+
* This module implements the global delegation pattern:
|
|
14
14
|
* - A single listener per event type is attached to `document.body` with
|
|
15
15
|
* `capture: true` (so it fires even for non-bubbling events).
|
|
16
16
|
* - When an event fires, the handler walks up the DOM from `event.target`
|
|
17
|
-
* using `closest()` to find the nearest element with an `on
|
|
18
|
-
* attribute
|
|
19
|
-
* -
|
|
20
|
-
*
|
|
17
|
+
* using `closest()` to find the nearest element with an `on-<eventType>`
|
|
18
|
+
* attribute (e.g. `on-click`, `on-submit`).
|
|
19
|
+
* - The nearest `[action-context]` ancestor is also resolved and attached to
|
|
20
|
+
* the `Action` object as `context` — enabling the same action type to be
|
|
21
|
+
* scoped to different UI regions.
|
|
22
|
+
* - Modifiers run with access to the mutable `Action` object so they can
|
|
23
|
+
* enrich `meta` before the action reaches subscribers.
|
|
24
|
+
* - `dispatchAction` is called with the fully assembled `Action` object.
|
|
21
25
|
*
|
|
22
26
|
* ## Complexity
|
|
23
27
|
*
|
|
@@ -35,74 +39,86 @@
|
|
|
35
39
|
* - `stop` stops further bubbling but the delegation handler has already
|
|
36
40
|
* captured the event at `body` level — it does not prevent other delegation
|
|
37
41
|
* handlers from running on the same element.
|
|
38
|
-
* - `once` is emulated by
|
|
42
|
+
* - `once` is emulated by removing the attribute after first fire.
|
|
39
43
|
*/
|
|
40
44
|
|
|
41
45
|
import {internalChannel_, logger_} from './lib.js';
|
|
42
46
|
import {modifierRegistry, payloadRegistry} from './registry.js';
|
|
47
|
+
import type {Action} from './action.js';
|
|
48
|
+
import type {ActionRecord} from './action-record.js';
|
|
43
49
|
|
|
44
50
|
// ─── Syntax Parser ────────────────────────────────────────────────────────────
|
|
45
51
|
|
|
46
52
|
/**
|
|
47
|
-
* Parses the `on
|
|
53
|
+
* Parses the `on-<eventType>` attribute value into its segments.
|
|
48
54
|
*
|
|
49
|
-
*
|
|
55
|
+
* Syntax: `actionId[:payload][; modifier1,modifier2,…]`
|
|
50
56
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* |
|
|
57
|
+
* The event type is encoded in the **attribute name** itself (`on-click`,
|
|
58
|
+
* `on-submit`, etc.) rather than inside the value. This makes the HTML more
|
|
59
|
+
* readable and aligns with native event attribute conventions.
|
|
60
|
+
*
|
|
61
|
+
* | Capture group | Matches | Example |
|
|
62
|
+
* | ------------- | -------------------------------------- | ---------------------- |
|
|
63
|
+
* | 1 | Action identifier | `open_drawer` |
|
|
64
|
+
* | 2 | Optional payload token or literal | `main_menu` / `$value` |
|
|
65
|
+
* | 3 | Optional comma-separated modifier list | `prevent,validate` |
|
|
56
66
|
*
|
|
57
67
|
* @example
|
|
58
68
|
* ```
|
|
59
|
-
* '
|
|
60
|
-
*
|
|
61
|
-
*
|
|
69
|
+
* 'close_drawer'
|
|
70
|
+
* → actionId='close_drawer', payload=undefined, modifiers={}
|
|
71
|
+
*
|
|
72
|
+
* 'open_drawer:main_menu'
|
|
73
|
+
* → actionId='open_drawer', payload='main_menu', modifiers={}
|
|
74
|
+
*
|
|
75
|
+
* 'my_submit_handler:$formdata; prevent,validate'
|
|
76
|
+
* → actionId='my_submit_handler', payload='$formdata', modifiers={'prevent','validate'}
|
|
62
77
|
* ```
|
|
63
78
|
*/
|
|
64
|
-
const syntaxRegex = /^([a-z0-
|
|
79
|
+
const syntaxRegex = /^([a-z0-9_:-]+)(?::([^;]+))?(?:;\s*([a-z0-9_,-]+))?$/;
|
|
65
80
|
|
|
66
81
|
// ─── Parsed Action Descriptor ─────────────────────────────────────────────────
|
|
67
82
|
|
|
68
83
|
/**
|
|
69
|
-
* Parsed and cached representation of a single `on
|
|
84
|
+
* Parsed and cached representation of a single `on-<eventType>` attribute value.
|
|
70
85
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* the
|
|
86
|
+
* Does not store `eventType` — the caller always has it from `event.type`,
|
|
87
|
+
* and the attribute name already encodes it (e.g. `on-click`), so storing it
|
|
88
|
+
* here would be redundant. This also keeps the cache key simple: just the raw
|
|
89
|
+
* attribute value string, with no composite key needed.
|
|
74
90
|
*/
|
|
75
91
|
interface ActionDescriptor {
|
|
76
|
-
/** The DOM event type to listen for (e.g. `'click'`, `'input'`). */
|
|
77
|
-
readonly eventType: string;
|
|
78
92
|
/** Set of active modifier names (e.g. `{'prevent', 'once'}`). */
|
|
79
93
|
readonly modifiers: ReadonlySet<string>;
|
|
80
94
|
/** The action identifier dispatched to `onAction` subscribers. */
|
|
81
95
|
readonly actionId: string;
|
|
82
|
-
/** Raw payload token from the attribute (literal string or
|
|
96
|
+
/** Raw payload token from the attribute (literal string or $-resolver key). */
|
|
83
97
|
readonly payload: string | undefined;
|
|
84
98
|
}
|
|
85
99
|
|
|
86
100
|
/**
|
|
87
|
-
*
|
|
101
|
+
* Cache for parsed `on-<eventType>` attribute values.
|
|
88
102
|
*
|
|
89
103
|
* Attribute strings are typically repeated across many elements (e.g. every
|
|
90
|
-
* "add to cart" button shares the same `on-
|
|
104
|
+
* "add to cart" button shares the same `on-click` value). Caching the parsed
|
|
91
105
|
* descriptor avoids redundant regex work on every event.
|
|
92
106
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
107
|
+
* The cache key is the raw attribute value string. No composite key with
|
|
108
|
+
* event type is needed because the attribute name already encodes the event
|
|
109
|
+
* type — `on-click="open_drawer"` and `on-submit="open_drawer"` are two
|
|
110
|
+
* separate attributes with the same value string, but they are read from
|
|
111
|
+
* different attribute names and never collide in this cache.
|
|
96
112
|
*
|
|
97
113
|
* @internal
|
|
98
114
|
*/
|
|
99
115
|
const descriptorCache__ = new Map<string, ActionDescriptor | null>();
|
|
100
116
|
|
|
101
117
|
/**
|
|
102
|
-
* Parses an `on
|
|
118
|
+
* Parses an `on-<eventType>` attribute value into an `ActionDescriptor`.
|
|
103
119
|
*
|
|
104
120
|
* Returns `null` when the syntax is invalid. Results are cached by the raw
|
|
105
|
-
* attribute string so repeated calls for the same value are O(1).
|
|
121
|
+
* attribute value string so repeated calls for the same value are O(1).
|
|
106
122
|
*
|
|
107
123
|
* @internal
|
|
108
124
|
*/
|
|
@@ -120,23 +136,12 @@ function parseDescriptor__(attributeValue: string): ActionDescriptor | null {
|
|
|
120
136
|
return null;
|
|
121
137
|
}
|
|
122
138
|
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const modifiers = new Set(modifierList);
|
|
131
|
-
const actionId = match[2];
|
|
132
|
-
const payload: string | undefined = match[3];
|
|
133
|
-
|
|
134
|
-
const descriptor: ActionDescriptor = {
|
|
135
|
-
eventType,
|
|
136
|
-
modifiers,
|
|
137
|
-
actionId,
|
|
138
|
-
payload,
|
|
139
|
-
};
|
|
139
|
+
const actionId = match[1];
|
|
140
|
+
const payload: string | undefined = match[2];
|
|
141
|
+
// match[3] is the raw modifier list string, e.g. "prevent,validate"
|
|
142
|
+
const modifierString = match[3];
|
|
143
|
+
const modifiers: Set<string> = modifierString ? new Set(modifierString.split(',').filter(Boolean)) : new Set();
|
|
144
|
+
const descriptor: ActionDescriptor = {modifiers, actionId, payload};
|
|
140
145
|
|
|
141
146
|
descriptorCache__.set(attributeValue, descriptor);
|
|
142
147
|
return descriptor;
|
|
@@ -144,17 +149,19 @@ function parseDescriptor__(attributeValue: string): ActionDescriptor | null {
|
|
|
144
149
|
|
|
145
150
|
// ─── Core Delegation Handler ──────────────────────────────────────────────────
|
|
146
151
|
|
|
147
|
-
const onActionAttrib__ = 'on-action';
|
|
148
152
|
/**
|
|
149
153
|
* Central event handler attached to `document.body` for every delegated event type.
|
|
150
154
|
*
|
|
151
155
|
* Execution flow for each incoming event:
|
|
152
156
|
* 1. Walk up from `event.target` to find the nearest element with an
|
|
153
|
-
* `on
|
|
157
|
+
* `on-<eventType>` attribute (e.g. `on-click`, `on-submit`).
|
|
154
158
|
* 2. Parse (or retrieve from cache) the `ActionDescriptor` for that attribute.
|
|
155
|
-
* 3.
|
|
156
|
-
* 4.
|
|
157
|
-
* 5.
|
|
159
|
+
* 3. Resolve `context` from the nearest `[action-context]` ancestor.
|
|
160
|
+
* 4. Build a mutable `Action` object with `type`, `payload` (raw), and `context`.
|
|
161
|
+
* 5. Run each modifier in order with access to the mutable `Action`; if any
|
|
162
|
+
* returns `false`, abort.
|
|
163
|
+
* 6. Resolve the payload token (literal or $-resolver) and assign to `action.payload`.
|
|
164
|
+
* 7. Call `dispatchAction(action)` with the fully assembled object.
|
|
158
165
|
*
|
|
159
166
|
* @internal
|
|
160
167
|
*/
|
|
@@ -162,62 +169,81 @@ function handleDelegatedEvent__(event: Event): void {
|
|
|
162
169
|
const eventType = event.type;
|
|
163
170
|
logger_.logMethodArgs?.('handleDelegatedEvent__', {eventType});
|
|
164
171
|
|
|
165
|
-
// Walk up the DOM to find the closest element with a matching on-action attribute.
|
|
166
|
-
// We use `closest` on the composedPath target to support Shadow DOM correctly.
|
|
167
172
|
const target = event.target as Element | null;
|
|
168
173
|
if (!target) return;
|
|
169
174
|
|
|
170
|
-
//
|
|
171
|
-
const
|
|
175
|
+
// Attribute name encodes the event type: on-click, on-submit, etc.
|
|
176
|
+
const actionAttrib = `on-${eventType}`;
|
|
177
|
+
|
|
178
|
+
// Walk up the DOM to find the closest element with the matching on-<eventType> attribute.
|
|
179
|
+
const actionElement = target.closest?.(`[${actionAttrib}]`);
|
|
172
180
|
if (!actionElement) return;
|
|
173
181
|
|
|
174
|
-
const attributeValue = actionElement.getAttribute?.(
|
|
182
|
+
const attributeValue = actionElement.getAttribute?.(actionAttrib)?.trim();
|
|
175
183
|
if (!attributeValue) {
|
|
176
|
-
logger_.accident('handleDelegatedEvent__', 'empty_attribute', {eventType,
|
|
184
|
+
logger_.accident('handleDelegatedEvent__', 'empty_attribute', {eventType, actionElement});
|
|
177
185
|
return;
|
|
178
186
|
}
|
|
179
187
|
|
|
180
188
|
if (!(actionElement instanceof HTMLElement)) {
|
|
181
|
-
logger_.accident('handleDelegatedEvent__', 'target_not_html_element', {eventType,
|
|
189
|
+
logger_.accident('handleDelegatedEvent__', 'target_not_html_element', {eventType, actionElement});
|
|
182
190
|
return;
|
|
183
191
|
}
|
|
184
192
|
|
|
185
193
|
const descriptor = parseDescriptor__(attributeValue);
|
|
186
|
-
if (!descriptor)
|
|
187
|
-
logger_.accident('handleDelegatedEvent__', 'invalid_attribute', {eventType, attributeValue, actionElement});
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
194
|
+
if (!descriptor) return;
|
|
190
195
|
|
|
191
|
-
|
|
196
|
+
logger_.logMethodArgs?.('handleDelegatedEvent__.action', {eventType, descriptor});
|
|
192
197
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
// Step 1: handle once modifier
|
|
198
|
+
// Step 1: handle `once` modifier — remove attribute before running other modifiers
|
|
199
|
+
// so that even if a modifier aborts, the element will not fire again.
|
|
196
200
|
if (descriptor.modifiers.has('once')) {
|
|
197
|
-
actionElement.removeAttribute(
|
|
198
|
-
descriptorCache__.delete(attributeValue); // free memory for once
|
|
201
|
+
actionElement.removeAttribute(actionAttrib);
|
|
199
202
|
}
|
|
200
203
|
|
|
201
|
-
// Step 2:
|
|
204
|
+
// Step 2: resolve `context` from the nearest [action-context] ancestor.
|
|
205
|
+
// Walk up from the action element itself (inclusive) to find the context scope.
|
|
206
|
+
// This allows the action element itself to carry action-context if needed.
|
|
207
|
+
const actionContext = actionElement.closest('[action-context]')?.getAttribute('action-context') ?? undefined;
|
|
208
|
+
|
|
209
|
+
// Step 3: build the mutable Action object.
|
|
210
|
+
// `payload` starts as the raw token string; it will be resolved in step 5.
|
|
211
|
+
// Modifiers in step 4 may mutate `meta` to attach cross-cutting data.
|
|
212
|
+
const action: Action = {
|
|
213
|
+
type: descriptor.actionId as keyof ActionRecord,
|
|
214
|
+
context: actionContext,
|
|
215
|
+
// Payload is temporarily set to the raw token; resolved below after modifiers run.
|
|
216
|
+
payload: descriptor.payload as ActionRecord[keyof ActionRecord],
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Step 4: run modifiers — each receives the mutable action so it can enrich meta.
|
|
202
220
|
for (const modifier of descriptor.modifiers) {
|
|
203
|
-
if (modifier === 'once') continue; // handled
|
|
221
|
+
if (modifier === 'once') continue; // handled above
|
|
204
222
|
const handler = modifierRegistry.get(modifier);
|
|
205
223
|
if (!handler) {
|
|
206
|
-
logger_.accident('handleDelegatedEvent__', 'unknown_modifier', {modifier, attributeValue});
|
|
207
|
-
return; // unknown modifier — abort to avoid silent
|
|
224
|
+
logger_.accident('handleDelegatedEvent__', 'unknown_modifier', {eventType, modifier, attributeValue, descriptor});
|
|
225
|
+
return; // unknown modifier — abort to avoid silent misbehavior
|
|
208
226
|
}
|
|
209
|
-
if (handler(event, actionElement) === false) return;
|
|
227
|
+
if (handler(event, actionElement, action) === false) return;
|
|
210
228
|
}
|
|
211
229
|
|
|
212
|
-
// Step
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
230
|
+
// Step 5: resolve payload — replace raw token with the actual value.
|
|
231
|
+
// If the raw token starts with '$', look it up in the payload resolver registry.
|
|
232
|
+
// Otherwise treat it as a literal string payload.
|
|
233
|
+
if (descriptor.payload) {
|
|
234
|
+
const resolver = payloadRegistry.get(descriptor.payload);
|
|
235
|
+
if (resolver) {
|
|
236
|
+
// Cast needed: payload is typed as ActionRecord[K] but we're building generically.
|
|
237
|
+
(action as {payload: unknown}).payload = resolver(event, actionElement);
|
|
238
|
+
}
|
|
239
|
+
// else: keep the literal string already set on action.payload
|
|
240
|
+
} else {
|
|
241
|
+
// No payload token in the attribute — set to undefined.
|
|
242
|
+
(action as {payload: unknown}).payload = undefined;
|
|
217
243
|
}
|
|
218
244
|
|
|
219
|
-
// Step
|
|
220
|
-
internalChannel_.dispatch(
|
|
245
|
+
// Step 6: dispatch the fully assembled Action object.
|
|
246
|
+
internalChannel_.dispatch(action.type, action);
|
|
221
247
|
}
|
|
222
248
|
|
|
223
249
|
// ─── Setup ────────────────────────────────────────────────────────────────────
|
|
@@ -246,11 +272,12 @@ const delegatedEventTypes__ = new Set<string>();
|
|
|
246
272
|
export const DEFAULT_DELEGATED_EVENTS: readonly string[] = ['click', 'submit', 'input', 'change'];
|
|
247
273
|
|
|
248
274
|
/**
|
|
249
|
-
* Registers global event delegation for `on
|
|
275
|
+
* Registers global event delegation for `on-<eventType>` attributes.
|
|
250
276
|
*
|
|
251
277
|
* Attaches a single `capture`-phase listener on `document.body` for each
|
|
252
|
-
* event type in `eventTypes`. All
|
|
253
|
-
* payload resolution, and `dispatchAction` — happens inside that
|
|
278
|
+
* event type in `eventTypes`. All processing — context resolution, modifier
|
|
279
|
+
* execution, payload resolution, and `dispatchAction` — happens inside that
|
|
280
|
+
* one handler.
|
|
254
281
|
*
|
|
255
282
|
* **Call this once at application bootstrap**, before any user interaction.
|
|
256
283
|
* Subsequent calls with the same event types are no-ops (idempotent).
|
|
@@ -267,6 +294,24 @@ export const DEFAULT_DELEGATED_EVENTS: readonly string[] = ['click', 'submit', '
|
|
|
267
294
|
* after this call — via `innerHTML`, `lit-html`, a framework renderer, or
|
|
268
295
|
* server-sent HTML — is automatically covered. No re-bootstrap is needed.
|
|
269
296
|
*
|
|
297
|
+
* ### Context scoping
|
|
298
|
+
*
|
|
299
|
+
* Wrap a group of elements in a `[action-context]` container to scope their
|
|
300
|
+
* actions. The delegation handler automatically resolves the nearest ancestor
|
|
301
|
+
* and attaches its value to `action.context`:
|
|
302
|
+
*
|
|
303
|
+
* ```html
|
|
304
|
+
* <section action-context="product-list">
|
|
305
|
+
* <button on-click="add_to_cart:42">Add</button>
|
|
306
|
+
* </section>
|
|
307
|
+
* ```
|
|
308
|
+
*
|
|
309
|
+
* ```ts
|
|
310
|
+
* onAction('add_to_cart', (action) => {
|
|
311
|
+
* console.log(action.context); // 'product-list'
|
|
312
|
+
* });
|
|
313
|
+
* ```
|
|
314
|
+
*
|
|
270
315
|
* @param eventTypes - Event types to delegate. Defaults to `DEFAULT_DELEGATED_EVENTS`.
|
|
271
316
|
*
|
|
272
317
|
* @example — minimal bootstrap
|
|
@@ -276,7 +321,7 @@ export const DEFAULT_DELEGATED_EVENTS: readonly string[] = ['click', 'submit', '
|
|
|
276
321
|
* // One call activates the entire page.
|
|
277
322
|
* setupActionDelegation();
|
|
278
323
|
*
|
|
279
|
-
* onAction('
|
|
324
|
+
* onAction('open_drawer', (action) => openDrawer(action.payload));
|
|
280
325
|
* ```
|
|
281
326
|
*
|
|
282
327
|
* @example — with extra event types
|
package/src/lib.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {createLogger} from '@alwatr/logger';
|
|
2
2
|
import {createChannelSignal} from '@alwatr/signal';
|
|
3
3
|
|
|
4
|
+
import type {Action} from './action.js';
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Module-scoped logger for `@alwatr/action`.
|
|
6
8
|
* Scoped to `'alwatr-action'` so log lines are easy to filter in the console.
|
|
@@ -10,16 +12,17 @@ import {createChannelSignal} from '@alwatr/signal';
|
|
|
10
12
|
export const logger_ = createLogger('alwatr-action');
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
|
-
* The action channel — a `ChannelSignal`
|
|
15
|
+
* The internal action channel — a `ChannelSignal` keyed by action `type`.
|
|
14
16
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
17
|
+
* Each message on this channel is a full `Action` object (AFSA), not just a
|
|
18
|
+
* raw payload. Subscribers registered via `onAction('foo', handler)` receive
|
|
19
|
+
* the entire `Action<'foo'>` so they have access to `context`, `meta`, and
|
|
20
|
+
* `payload` in one place.
|
|
18
21
|
*
|
|
19
22
|
* Uses `ChannelSignal` for O(1) routing: dispatching action `'A'` performs a
|
|
20
23
|
* single `Map.get('A')` lookup and invokes only the handlers registered for
|
|
21
|
-
* that specific
|
|
24
|
+
* that specific type — never handlers for `'B'`, `'C'`, etc.
|
|
22
25
|
*
|
|
23
26
|
* @internal — not part of the public API; use `onAction` / `dispatchAction` instead.
|
|
24
27
|
*/
|
|
25
|
-
export const internalChannel_ = createChannelSignal<Record<string,
|
|
28
|
+
export const internalChannel_ = createChannelSignal<Record<string, Action>>({name: 'alwatr-action'});
|
package/src/main.ts
CHANGED
|
@@ -1,17 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @alwatr/action — Declarative DOM action-dispatch for Unidirectional Data Flow.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Implements the **Alwatr Flux Standard Action (AFSA)** pattern: every action
|
|
5
|
+
* flowing through the bus is a single, typed `Action<K>` object carrying
|
|
6
|
+
* `type`, `payload`, `context`, and optional `meta`. This replaces the previous
|
|
7
|
+
* two-argument `(id, payload)` API with a unified structure that is extensible
|
|
8
|
+
* without breaking existing call sites.
|
|
9
|
+
*
|
|
10
|
+
* ## Activating `on-<eventType>` attributes
|
|
5
11
|
*
|
|
6
12
|
* 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.
|
|
13
|
+
* listener on `document.body` handles every `on-click`, `on-submit`, etc. element —
|
|
14
|
+
* including elements added dynamically after bootstrap — with O(1) initialization cost.
|
|
9
15
|
*
|
|
10
16
|
* ```ts
|
|
11
17
|
* import {setupActionDelegation, onAction} from '@alwatr/action';
|
|
12
18
|
*
|
|
13
19
|
* setupActionDelegation();
|
|
14
|
-
* onAction('
|
|
20
|
+
* onAction('open_drawer', (action) => openDrawer(action.payload));
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* ## Attribute syntax
|
|
24
|
+
*
|
|
25
|
+
* ```
|
|
26
|
+
* on-<eventType>="actionId[:payload][; modifier1,modifier2,…]"
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* ```html
|
|
30
|
+
* <button on-click="open_drawer:main">Open</button>
|
|
31
|
+
* <input on-input="search_query:$value" />
|
|
32
|
+
* <form on-submit="submit_form:$formdata; prevent,validate" novalidate>…</form>
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* ## Context scoping
|
|
36
|
+
*
|
|
37
|
+
* Wrap elements in an `[action-context]` container to scope their actions.
|
|
38
|
+
* The delegation handler resolves the nearest ancestor and attaches its value
|
|
39
|
+
* to `action.context`:
|
|
40
|
+
*
|
|
41
|
+
* ```html
|
|
42
|
+
* <section action-context="product-list">
|
|
43
|
+
* <button on-click="add_to_cart:42">Add</button>
|
|
44
|
+
* </section>
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* onAction('add_to_cart', (action) => {
|
|
49
|
+
* console.log(action.context); // 'product-list'
|
|
50
|
+
* console.log(action.payload); // '42'
|
|
51
|
+
* });
|
|
15
52
|
* ```
|
|
16
53
|
*
|
|
17
54
|
* ## Programmatic dispatch
|
|
@@ -19,7 +56,7 @@
|
|
|
19
56
|
* ```ts
|
|
20
57
|
* import {dispatchAction} from '@alwatr/action';
|
|
21
58
|
*
|
|
22
|
-
* dispatchAction('navigate', '/dashboard');
|
|
59
|
+
* dispatchAction({type: 'navigate', payload: '/dashboard'});
|
|
23
60
|
* ```
|
|
24
61
|
*
|
|
25
62
|
* ## Registering typed actions
|
|
@@ -30,8 +67,8 @@
|
|
|
30
67
|
* // src/action-record.ts
|
|
31
68
|
* declare module '@alwatr/action' {
|
|
32
69
|
* interface ActionRecord {
|
|
33
|
-
* '
|
|
34
|
-
* '
|
|
70
|
+
* 'open_drawer': string;
|
|
71
|
+
* 'add_to_cart': {productId: number; qty: number};
|
|
35
72
|
* 'logout': void;
|
|
36
73
|
* }
|
|
37
74
|
* }
|
|
@@ -39,7 +76,8 @@
|
|
|
39
76
|
*
|
|
40
77
|
* ## Public API
|
|
41
78
|
*
|
|
42
|
-
* - `
|
|
79
|
+
* - `Action` — the AFSA object interface (`type`, `payload`, `context`, `meta`)
|
|
80
|
+
* - `ActionRecord` — extend this interface to register typed actions
|
|
43
81
|
* - `setupActionDelegation` / `teardownActionDelegation` — global delegation lifecycle
|
|
44
82
|
* - `DEFAULT_DELEGATED_EVENTS` — default event types covered by delegation
|
|
45
83
|
* - `onAction` / `dispatchAction` — subscribe to and dispatch named actions
|
|
@@ -50,5 +88,6 @@
|
|
|
50
88
|
* For page-ready signals in SSG/SSR apps, use `@alwatr/page-ready` instead.
|
|
51
89
|
*/
|
|
52
90
|
export type {ActionRecord} from './action-record.js';
|
|
91
|
+
export type {Action} from './action.js';
|
|
53
92
|
export * from './method.js';
|
|
54
93
|
export * from './delegate.js';
|