@alwatr/action 9.16.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.
- package/README.md +249 -70
- package/dist/delegate.d.ts +28 -5
- package/dist/delegate.d.ts.map +1 -1
- package/dist/{lib.d.ts → lib_.d.ts} +9 -7
- package/dist/lib_.d.ts.map +1 -0
- package/dist/main.d.ts +31 -5
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +3 -3
- package/dist/main.js.map +7 -7
- package/dist/method.d.ts +63 -48
- package/dist/method.d.ts.map +1 -1
- package/dist/registry_.d.ts +24 -0
- package/dist/registry_.d.ts.map +1 -0
- package/dist/type.d.ts +160 -0
- package/dist/type.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/delegate.ts +71 -21
- package/src/{lib.ts → lib_.ts} +8 -6
- package/src/main.ts +31 -5
- package/src/method.ts +73 -58
- package/src/{registry.ts → registry_.ts} +7 -48
- package/src/type.ts +165 -0
- package/dist/action-record.d.ts +0 -54
- package/dist/action-record.d.ts.map +0 -1
- package/dist/lib.d.ts.map +0 -1
- package/dist/registry.d.ts +0 -61
- package/dist/registry.d.ts.map +0 -1
- package/src/action-record.ts +0 -54
package/README.md
CHANGED
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
|
|
24
24
|
The action bus is powered by a [`ChannelSignal`](../signal/README.md) from `@alwatr/signal`. Dispatching action `'A'` performs a single `Map.get('A')` lookup and invokes only the handlers registered for that specific action — **O(1) per dispatch**, regardless of how many other actions are subscribed.
|
|
25
25
|
|
|
26
|
+
Every message on the bus is a full **`Action<K>`** object (Alwatr Flux Standard Action — AFSA) rather than a bare payload. This means every handler receives `type`, `payload`, `context`, and `meta` in one unified structure.
|
|
27
|
+
|
|
26
28
|
### Global Event Delegation
|
|
27
29
|
|
|
28
|
-
A single capture-phase listener on `document.body` handles all `on-<eventType>` elements. When an event fires, the handler walks up from `event.target` using `closest('[on-click]')` (or the matching attribute), parses the attribute value, runs modifiers, resolves the payload, and dispatches the
|
|
30
|
+
A single capture-phase listener on `document.body` handles all `on-<eventType>` elements. When an event fires, the handler walks up from `event.target` using `closest('[on-click]')` (or the matching attribute), resolves the nearest `[action-context]` ancestor, parses the attribute value, runs modifiers, resolves the payload, and dispatches the full `Action` object.
|
|
29
31
|
|
|
30
32
|
```
|
|
31
33
|
User clicks a button
|
|
@@ -34,10 +36,15 @@ User clicks a button
|
|
|
34
36
|
document.body capture listener (1 listener per event type)
|
|
35
37
|
│
|
|
36
38
|
└─ closest('[on-click]') → finds element
|
|
39
|
+
closest('[action-context]') → resolves context (e.g. 'product-list')
|
|
37
40
|
parse attribute → 'add_to_cart:42'
|
|
38
41
|
run modifiers → none
|
|
39
42
|
resolve payload → '42'
|
|
40
|
-
internalChannel_.dispatch('add_to_cart',
|
|
43
|
+
internalChannel_.dispatch('add_to_cart', {
|
|
44
|
+
type: 'add_to_cart',
|
|
45
|
+
payload: '42',
|
|
46
|
+
context: 'product-list',
|
|
47
|
+
})
|
|
41
48
|
│
|
|
42
49
|
└─ Map.get('add_to_cart') → O(1) → invoke only matching handlers
|
|
43
50
|
```
|
|
@@ -93,10 +100,11 @@ import './action-record.js'; // ensure the declaration is loaded
|
|
|
93
100
|
|
|
94
101
|
setupActionDelegation();
|
|
95
102
|
|
|
96
|
-
//
|
|
97
|
-
onAction('open_drawer', (
|
|
98
|
-
onAction('add_to_cart', (
|
|
99
|
-
cartService.add(
|
|
103
|
+
// The handler receives the full Action<K> object — payload, context, and meta in one place.
|
|
104
|
+
onAction('open_drawer', (action) => openDrawer(action.payload)); // action.payload: string
|
|
105
|
+
onAction('add_to_cart', (action) => {
|
|
106
|
+
cartService.add(action.payload.productId, action.payload.qty); // fully typed
|
|
107
|
+
console.log(action.context); // e.g. 'product-list' — from nearest [action-context] ancestor
|
|
100
108
|
});
|
|
101
109
|
```
|
|
102
110
|
|
|
@@ -132,15 +140,54 @@ onAction('add_to_cart', (item) => {
|
|
|
132
140
|
<button on-click="welcome_dismissed; once">Got it</button>
|
|
133
141
|
```
|
|
134
142
|
|
|
135
|
-
### 4.
|
|
143
|
+
### 4. Context scoping with `action-context`
|
|
144
|
+
|
|
145
|
+
Wrap a group of elements in an `[action-context]` container to scope their actions. The delegation handler automatically resolves the nearest ancestor and attaches its value to `action.context`. This lets the same action type serve multiple independent UI regions without creating separate action names.
|
|
146
|
+
|
|
147
|
+
```html
|
|
148
|
+
<!-- Two sliders on the same page, both dispatching 'slider:change' -->
|
|
149
|
+
<section action-context="volume">
|
|
150
|
+
<input
|
|
151
|
+
type="range"
|
|
152
|
+
on-input="slider:change:$value"
|
|
153
|
+
/>
|
|
154
|
+
</section>
|
|
155
|
+
|
|
156
|
+
<section action-context="brightness">
|
|
157
|
+
<input
|
|
158
|
+
type="range"
|
|
159
|
+
on-input="slider:change:$value"
|
|
160
|
+
/>
|
|
161
|
+
</section>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
onAction('slider:change', (action) => {
|
|
166
|
+
if (action.context === 'volume') audioService.setVolume(Number(action.payload));
|
|
167
|
+
if (action.context === 'brightness') displayService.setBrightness(Number(action.payload));
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Context is `undefined` when no `[action-context]` ancestor exists — programmatic dispatches also have no context by default.
|
|
172
|
+
|
|
173
|
+
### 5. Programmatic dispatch
|
|
136
174
|
|
|
137
175
|
```ts
|
|
138
176
|
import {dispatchAction} from '@alwatr/action';
|
|
139
177
|
|
|
178
|
+
// dispatchAction takes a full Action object
|
|
140
179
|
await uploadFile(file);
|
|
141
|
-
dispatchAction('upload_complete', fileId);
|
|
180
|
+
dispatchAction({type: 'upload_complete', payload: fileId});
|
|
181
|
+
|
|
182
|
+
dispatchAction({type: 'navigate', payload: '/dashboard'});
|
|
142
183
|
|
|
143
|
-
|
|
184
|
+
// With explicit context and meta
|
|
185
|
+
dispatchAction({
|
|
186
|
+
type: 'slider:change',
|
|
187
|
+
payload: 75,
|
|
188
|
+
context: 'volume',
|
|
189
|
+
meta: {source: 'keyboard'},
|
|
190
|
+
});
|
|
144
191
|
```
|
|
145
192
|
|
|
146
193
|
---
|
|
@@ -172,11 +219,81 @@ on-<eventType>="actionId[:payload][; modifier1,modifier2,…]"
|
|
|
172
219
|
| ------------ | -------------------------------------------------------------- |
|
|
173
220
|
| `:$value` | `element.value` (for `<input>`, `<select>`, `<textarea>`) |
|
|
174
221
|
| `:$formdata` | `Object.fromEntries(new FormData(form))` from nearest `<form>` |
|
|
222
|
+
| `:$checked` | `(element as HTMLInputElement).checked` for checkboxes/radios |
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## The Action Object (AFSA)
|
|
227
|
+
|
|
228
|
+
Every action flowing through the bus — whether triggered from HTML attributes or dispatched programmatically — is a single **`Action<K>`** object:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
interface Action<K extends keyof ActionRecord> {
|
|
232
|
+
/** Action identifier — must be a key of ActionRecord. */
|
|
233
|
+
type: K;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* DOM context from the nearest [action-context] ancestor.
|
|
237
|
+
* undefined for programmatic dispatches or when no ancestor exists.
|
|
238
|
+
*/
|
|
239
|
+
context?: string;
|
|
240
|
+
|
|
241
|
+
/** Business payload — type is inferred from ActionRecord[K]. */
|
|
242
|
+
payload: ActionRecord[K];
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Open-ended metadata bag for cross-cutting concerns.
|
|
246
|
+
* Modifiers may write to this before the action reaches subscribers.
|
|
247
|
+
*/
|
|
248
|
+
meta?: Record<string, unknown>;
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Modifiers in the delegation pipeline receive the mutable `action` object and can enrich `meta` before the action reaches subscribers:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import {registerModifier} from '@alwatr/action';
|
|
256
|
+
|
|
257
|
+
// A modifier that stamps a trace ID into meta
|
|
258
|
+
registerModifier('trace', (_event, _element, action) => {
|
|
259
|
+
action.meta ??= {};
|
|
260
|
+
action.meta['traceId'] = crypto.randomUUID();
|
|
261
|
+
return true;
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
```html
|
|
266
|
+
<button on-click="submit_order:42; trace">Place Order</button>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
onAction('submit_order', (action) => {
|
|
271
|
+
console.log(action.meta?.['traceId']); // e.g. 'a1b2-c3d4-…'
|
|
272
|
+
});
|
|
273
|
+
```
|
|
175
274
|
|
|
176
275
|
---
|
|
177
276
|
|
|
178
277
|
## API Reference
|
|
179
278
|
|
|
279
|
+
### `Action<K>` (interface)
|
|
280
|
+
|
|
281
|
+
The Alwatr Flux Standard Action object. Every dispatch and every handler callback uses this structure.
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
import type {Action} from '@alwatr/action';
|
|
285
|
+
|
|
286
|
+
// Reading fields in a handler
|
|
287
|
+
onAction('add_to_cart', (action: Action<'add_to_cart'>) => {
|
|
288
|
+
console.log(action.type); // 'add_to_cart'
|
|
289
|
+
console.log(action.payload); // {productId: number; qty: number}
|
|
290
|
+
console.log(action.context); // string | undefined
|
|
291
|
+
console.log(action.meta); // Record<string, unknown> | undefined
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
180
297
|
### `ActionRecord` (interface)
|
|
181
298
|
|
|
182
299
|
The global action type registry. Extend via declaration merging to register typed actions.
|
|
@@ -220,34 +337,46 @@ function teardownActionDelegation(): void;
|
|
|
220
337
|
|
|
221
338
|
---
|
|
222
339
|
|
|
223
|
-
### `onAction(
|
|
340
|
+
### `onAction(type, handler)`
|
|
224
341
|
|
|
225
|
-
Subscribes to a named action. O(1) routing via `ChannelSignal`.
|
|
342
|
+
Subscribes to a named action. O(1) routing via `ChannelSignal`. The handler receives the full `Action<K>` object.
|
|
226
343
|
|
|
227
344
|
```ts
|
|
228
|
-
function onAction<K extends keyof ActionRecord>(
|
|
229
|
-
actionId: K,
|
|
230
|
-
handler: (payload: ActionRecord[K]) => void,
|
|
231
|
-
): SubscribeResult;
|
|
345
|
+
function onAction<K extends keyof ActionRecord>(type: K, handler: (action: Action<K>) => void): SubscribeResult;
|
|
232
346
|
```
|
|
233
347
|
|
|
234
348
|
```ts
|
|
235
|
-
const sub = onAction('open_drawer', (
|
|
349
|
+
const sub = onAction('open_drawer', (action) => {
|
|
350
|
+
openDrawer(action.payload); // payload: string
|
|
351
|
+
console.log(action.context); // e.g. 'sidebar' or undefined
|
|
352
|
+
});
|
|
236
353
|
sub.unsubscribe(); // prevent memory leaks
|
|
237
354
|
```
|
|
238
355
|
|
|
239
356
|
---
|
|
240
357
|
|
|
241
|
-
### `dispatchAction(
|
|
358
|
+
### `dispatchAction(action)`
|
|
242
359
|
|
|
243
360
|
Dispatches a named action. Payload type is enforced by `ActionRecord`.
|
|
244
361
|
|
|
362
|
+
```ts
|
|
363
|
+
function dispatchAction<K extends keyof ActionRecord>(action: Action<K>): void;
|
|
364
|
+
```
|
|
365
|
+
|
|
245
366
|
```ts
|
|
246
367
|
// With payload
|
|
247
|
-
dispatchAction('open_drawer', 'settings');
|
|
368
|
+
dispatchAction({type: 'open_drawer', payload: 'settings'});
|
|
248
369
|
|
|
249
|
-
// Void payload
|
|
250
|
-
dispatchAction('logout');
|
|
370
|
+
// Void payload
|
|
371
|
+
dispatchAction({type: 'logout', payload: undefined});
|
|
372
|
+
|
|
373
|
+
// With context and meta
|
|
374
|
+
dispatchAction({
|
|
375
|
+
type: 'open_drawer',
|
|
376
|
+
payload: 'settings',
|
|
377
|
+
context: 'header',
|
|
378
|
+
meta: {triggeredBy: 'keyboard'},
|
|
379
|
+
});
|
|
251
380
|
```
|
|
252
381
|
|
|
253
382
|
---
|
|
@@ -256,7 +385,9 @@ dispatchAction('logout');
|
|
|
256
385
|
|
|
257
386
|
Registers a custom modifier. Return `false` to cancel the dispatch.
|
|
258
387
|
|
|
259
|
-
Handler signature: `(event: Event, element: HTMLElement) => boolean`
|
|
388
|
+
Handler signature: `(event: Event, element: HTMLElement, action: Action) => boolean`
|
|
389
|
+
|
|
390
|
+
The handler receives the mutable `action` object and may write to `action.meta`.
|
|
260
391
|
|
|
261
392
|
```ts
|
|
262
393
|
import {registerModifier} from '@alwatr/action';
|
|
@@ -264,11 +395,18 @@ import {registerModifier} from '@alwatr/action';
|
|
|
264
395
|
registerModifier('not_disabled', (_event, element) => {
|
|
265
396
|
return !(element as HTMLButtonElement).disabled;
|
|
266
397
|
});
|
|
398
|
+
|
|
399
|
+
// A modifier that enriches meta before dispatch
|
|
400
|
+
registerModifier('timestamp', (_event, _element, action) => {
|
|
401
|
+
action.meta ??= {};
|
|
402
|
+
action.meta['ts'] = Date.now();
|
|
403
|
+
return true;
|
|
404
|
+
});
|
|
267
405
|
```
|
|
268
406
|
|
|
269
407
|
```html
|
|
270
408
|
<button
|
|
271
|
-
on-click="select_item:$data_id; not_disabled"
|
|
409
|
+
on-click="select_item:$data_id; not_disabled,timestamp"
|
|
272
410
|
data-id="42"
|
|
273
411
|
>
|
|
274
412
|
Select
|
|
@@ -286,10 +424,6 @@ Handler signature: `(event: Event, element: HTMLElement) => unknown`
|
|
|
286
424
|
```ts
|
|
287
425
|
import {registerPayloadResolver} from '@alwatr/action';
|
|
288
426
|
|
|
289
|
-
registerPayloadResolver('$checked', (_event, element) => {
|
|
290
|
-
return (element as HTMLInputElement).checked;
|
|
291
|
-
});
|
|
292
|
-
|
|
293
427
|
registerPayloadResolver('$data_id', (_event, element) => {
|
|
294
428
|
return (element as HTMLElement).dataset.id ?? null;
|
|
295
429
|
});
|
|
@@ -313,33 +447,41 @@ registerPayloadResolver('$data_id', (_event, element) => {
|
|
|
313
447
|
## Unidirectional Data Flow
|
|
314
448
|
|
|
315
449
|
```
|
|
316
|
-
|
|
317
|
-
│
|
|
318
|
-
│ <
|
|
319
|
-
|
|
450
|
+
┌────────────────────────────────────────────────────────────┐
|
|
451
|
+
│ UI Layer │
|
|
452
|
+
│ <section action-context="cart"> │
|
|
453
|
+
│ <button on-click="add_to_cart:42">Add</button> │
|
|
454
|
+
│ </section> │
|
|
455
|
+
└─────────────────────────┬──────────────────────────────────┘
|
|
320
456
|
│ DOM event bubbles to body
|
|
321
457
|
▼
|
|
322
|
-
|
|
323
|
-
│
|
|
324
|
-
│ document.body capture listener (1 per event type)
|
|
325
|
-
│ → closest('[on-click]') → parse
|
|
326
|
-
│ →
|
|
327
|
-
|
|
458
|
+
┌────────────────────────────────────────────────────────────┐
|
|
459
|
+
│ Action Layer (@alwatr/action) │
|
|
460
|
+
│ document.body capture listener (1 per event type) │
|
|
461
|
+
│ → closest('[on-click]') → parse attribute │
|
|
462
|
+
│ → closest('[action-context]') → context = 'cart' │
|
|
463
|
+
│ → run modifiers (may enrich action.meta) │
|
|
464
|
+
│ → resolve payload → '42' │
|
|
465
|
+
│ → dispatch Action {type, payload, context, meta} [O(1)] │
|
|
466
|
+
└─────────────────────────┬──────────────────────────────────┘
|
|
328
467
|
│ O(1) routing via ChannelSignal
|
|
329
468
|
▼
|
|
330
|
-
|
|
331
|
-
│
|
|
332
|
-
│ onAction('add_to_cart', (
|
|
333
|
-
|
|
469
|
+
┌────────────────────────────────────────────────────────────┐
|
|
470
|
+
│ Business Logic Layer │
|
|
471
|
+
│ onAction('add_to_cart', (action) => { │
|
|
472
|
+
│ cartService.add(action.payload); │
|
|
473
|
+
│ // action.context === 'cart' │
|
|
474
|
+
│ }) │
|
|
475
|
+
└─────────────────────────┬──────────────────────────────────┘
|
|
334
476
|
│ state update
|
|
335
477
|
▼
|
|
336
|
-
|
|
337
|
-
│
|
|
338
|
-
│ cartSignal.set(newCartState)
|
|
339
|
-
|
|
478
|
+
┌────────────────────────────────────────────────────────────┐
|
|
479
|
+
│ State Layer (@alwatr/signal) │
|
|
480
|
+
│ cartSignal.set(newCartState) │
|
|
481
|
+
└─────────────────────────┬──────────────────────────────────┘
|
|
340
482
|
│ state flows down to UI
|
|
341
483
|
▼
|
|
342
|
-
|
|
484
|
+
UI re-renders
|
|
343
485
|
```
|
|
344
486
|
|
|
345
487
|
---
|
|
@@ -355,6 +497,65 @@ concern, not a user-interaction action.
|
|
|
355
497
|
|
|
356
498
|
## Migration from Previous Versions
|
|
357
499
|
|
|
500
|
+
### `dispatchAction` API changed
|
|
501
|
+
|
|
502
|
+
`dispatchAction` now takes a single `Action` object instead of two positional arguments.
|
|
503
|
+
|
|
504
|
+
**Before:**
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
dispatchAction('open_drawer', 'settings');
|
|
508
|
+
dispatchAction('logout');
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**After:**
|
|
512
|
+
|
|
513
|
+
```ts
|
|
514
|
+
dispatchAction({type: 'open_drawer', payload: 'settings'});
|
|
515
|
+
dispatchAction({type: 'logout', payload: undefined});
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### `onAction` handler signature changed
|
|
519
|
+
|
|
520
|
+
Handlers now receive the full `Action<K>` object instead of just the payload.
|
|
521
|
+
|
|
522
|
+
**Before:**
|
|
523
|
+
|
|
524
|
+
```ts
|
|
525
|
+
onAction('add_to_cart', (item) => {
|
|
526
|
+
cartService.add(item.productId, item.qty);
|
|
527
|
+
});
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**After:**
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
onAction('add_to_cart', (action) => {
|
|
534
|
+
cartService.add(action.payload.productId, action.payload.qty);
|
|
535
|
+
// action.context is now also available
|
|
536
|
+
});
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### `registerModifier` handler signature changed
|
|
540
|
+
|
|
541
|
+
Modifier handlers now receive a third `action` argument for `meta` enrichment.
|
|
542
|
+
|
|
543
|
+
**Before:**
|
|
544
|
+
|
|
545
|
+
```ts
|
|
546
|
+
registerModifier('not_disabled', (_event, element) => {
|
|
547
|
+
return !(element as HTMLButtonElement).disabled;
|
|
548
|
+
});
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**After (backward-compatible — third arg is optional to use):**
|
|
552
|
+
|
|
553
|
+
```ts
|
|
554
|
+
registerModifier('not_disabled', (_event, element, _action) => {
|
|
555
|
+
return !(element as HTMLButtonElement).disabled;
|
|
556
|
+
});
|
|
557
|
+
```
|
|
558
|
+
|
|
358
559
|
### Attribute syntax changed
|
|
359
560
|
|
|
360
561
|
The event type is now encoded in the **attribute name** instead of the value, and modifiers are listed after a semicolon instead of dot-chained before the arrow.
|
|
@@ -385,44 +586,22 @@ The event type is now encoded in the **attribute name** instead of the value, an
|
|
|
385
586
|
<button on-click="welcome_dismissed; once">Got it</button>
|
|
386
587
|
```
|
|
387
588
|
|
|
388
|
-
### `ActionContext` removed
|
|
389
|
-
|
|
390
|
-
The `this` context in modifier and resolver handlers changed to explicit parameters:
|
|
391
|
-
|
|
392
|
-
**Before:**
|
|
393
|
-
|
|
394
|
-
```ts
|
|
395
|
-
registerModifier('not_disabled', function () {
|
|
396
|
-
return !(this.element as HTMLButtonElement).disabled;
|
|
397
|
-
});
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
**After:**
|
|
401
|
-
|
|
402
|
-
```ts
|
|
403
|
-
registerModifier('not_disabled', (_event, element) => {
|
|
404
|
-
return !(element as HTMLButtonElement).disabled;
|
|
405
|
-
});
|
|
406
|
-
```
|
|
407
|
-
|
|
408
589
|
### `page-ready` moved to `@alwatr/page-ready`
|
|
409
590
|
|
|
410
591
|
`dispatchPageId` / `onPageReady` are no longer part of this package.
|
|
411
592
|
|
|
412
593
|
---
|
|
413
594
|
|
|
414
|
-
---
|
|
415
|
-
|
|
416
595
|
## 🌊 Part of Alwatr Flux
|
|
417
596
|
|
|
418
597
|
`@alwatr/action` is the **Action Layer** of the [Alwatr Flux](https://github.com/Alwatr/alwatr/tree/next/pkg/flux) architecture — a complete Unidirectional Data Flow system for building scalable Progressive Web Applications.
|
|
419
598
|
|
|
420
599
|
```
|
|
421
|
-
View (HTML on-<event> attributes)
|
|
600
|
+
View (HTML on-<event> attributes + action-context)
|
|
422
601
|
↓
|
|
423
|
-
Action Layer (@alwatr/action) — global delegation, O(1) routing
|
|
602
|
+
Action Layer (@alwatr/action) — global delegation, O(1) routing, AFSA objects
|
|
424
603
|
↓
|
|
425
|
-
Controller (business logic via onAction)
|
|
604
|
+
Controller (business logic via onAction — receives full Action object)
|
|
426
605
|
↓
|
|
427
606
|
State Layer (@alwatr/signal) — fine-grained reactivity
|
|
428
607
|
↓
|
package/dist/delegate.d.ts
CHANGED
|
@@ -16,8 +16,12 @@
|
|
|
16
16
|
* - When an event fires, the handler walks up the DOM from `event.target`
|
|
17
17
|
* using `closest()` to find the nearest element with an `on-<eventType>`
|
|
18
18
|
* attribute (e.g. `on-click`, `on-submit`).
|
|
19
|
-
* -
|
|
20
|
-
*
|
|
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
|
*
|
|
@@ -53,8 +57,9 @@ export declare const DEFAULT_DELEGATED_EVENTS: readonly string[];
|
|
|
53
57
|
* Registers global event delegation for `on-<eventType>` attributes.
|
|
54
58
|
*
|
|
55
59
|
* Attaches a single `capture`-phase listener on `document.body` for each
|
|
56
|
-
* event type in `eventTypes`. All processing —
|
|
57
|
-
* resolution, and `dispatchAction` — happens inside that
|
|
60
|
+
* event type in `eventTypes`. All processing — context resolution, modifier
|
|
61
|
+
* execution, payload resolution, and `dispatchAction` — happens inside that
|
|
62
|
+
* one handler.
|
|
58
63
|
*
|
|
59
64
|
* **Call this once at application bootstrap**, before any user interaction.
|
|
60
65
|
* Subsequent calls with the same event types are no-ops (idempotent).
|
|
@@ -71,6 +76,24 @@ export declare const DEFAULT_DELEGATED_EVENTS: readonly string[];
|
|
|
71
76
|
* after this call — via `innerHTML`, `lit-html`, a framework renderer, or
|
|
72
77
|
* server-sent HTML — is automatically covered. No re-bootstrap is needed.
|
|
73
78
|
*
|
|
79
|
+
* ### Context scoping
|
|
80
|
+
*
|
|
81
|
+
* Wrap a group of elements in a `[action-context]` container to scope their
|
|
82
|
+
* actions. The delegation handler automatically resolves the nearest ancestor
|
|
83
|
+
* and attaches its value to `action.context`:
|
|
84
|
+
*
|
|
85
|
+
* ```html
|
|
86
|
+
* <section action-context="product-list">
|
|
87
|
+
* <button on-click="add_to_cart:42">Add</button>
|
|
88
|
+
* </section>
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* ```ts
|
|
92
|
+
* onAction('add_to_cart', (action) => {
|
|
93
|
+
* console.log(action.context); // 'product-list'
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
74
97
|
* @param eventTypes - Event types to delegate. Defaults to `DEFAULT_DELEGATED_EVENTS`.
|
|
75
98
|
*
|
|
76
99
|
* @example — minimal bootstrap
|
|
@@ -80,7 +103,7 @@ export declare const DEFAULT_DELEGATED_EVENTS: readonly string[];
|
|
|
80
103
|
* // One call activates the entire page.
|
|
81
104
|
* setupActionDelegation();
|
|
82
105
|
*
|
|
83
|
-
* onAction('open_drawer', (
|
|
106
|
+
* onAction('open_drawer', (action) => openDrawer(action.payload));
|
|
84
107
|
* ```
|
|
85
108
|
*
|
|
86
109
|
* @example — with extra event types
|
package/dist/delegate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delegate.d.ts","sourceRoot":"","sources":["../src/delegate.ts"],"names":[],"mappings":"AAAA
|
|
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,3 +1,4 @@
|
|
|
1
|
+
import type { Action } from './type.js';
|
|
1
2
|
/**
|
|
2
3
|
* Module-scoped logger for `@alwatr/action`.
|
|
3
4
|
* Scoped to `'alwatr-action'` so log lines are easy to filter in the console.
|
|
@@ -6,17 +7,18 @@
|
|
|
6
7
|
*/
|
|
7
8
|
export declare const logger_: import("@alwatr/logger").AlwatrLogger;
|
|
8
9
|
/**
|
|
9
|
-
* The action channel — a `ChannelSignal`
|
|
10
|
+
* The internal action channel — a `ChannelSignal` keyed by action `type`.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Each message on this channel is a full `Action` object (AFSA), not just a
|
|
13
|
+
* raw payload. Subscribers registered via `onAction('foo', handler)` receive
|
|
14
|
+
* the entire `Action<'foo'>` so they have access to `context`, `meta`, and
|
|
15
|
+
* `payload` in one place.
|
|
14
16
|
*
|
|
15
17
|
* Uses `ChannelSignal` for O(1) routing: dispatching action `'A'` performs a
|
|
16
18
|
* single `Map.get('A')` lookup and invokes only the handlers registered for
|
|
17
|
-
* that specific
|
|
19
|
+
* that specific type — never handlers for `'B'`, `'C'`, etc.
|
|
18
20
|
*
|
|
19
21
|
* @internal — not part of the public API; use `onAction` / `dispatchAction` instead.
|
|
20
22
|
*/
|
|
21
|
-
export declare const internalChannel_: import("@alwatr/signal").ChannelSignal<Record<string,
|
|
22
|
-
//# sourceMappingURL=
|
|
23
|
+
export declare const internalChannel_: import("@alwatr/signal").ChannelSignal<Record<string, Action<never>>>;
|
|
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
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @alwatr/action — Declarative DOM action-dispatch for Unidirectional Data Flow.
|
|
3
3
|
*
|
|
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
|
+
*
|
|
4
10
|
* ## Activating `on-<eventType>` attributes
|
|
5
11
|
*
|
|
6
12
|
* Call `setupActionDelegation()` once at bootstrap. A single capture-phase
|
|
@@ -11,7 +17,7 @@
|
|
|
11
17
|
* import {setupActionDelegation, onAction} from '@alwatr/action';
|
|
12
18
|
*
|
|
13
19
|
* setupActionDelegation();
|
|
14
|
-
* onAction('open_drawer', (
|
|
20
|
+
* onAction('open_drawer', (action) => openDrawer(action.payload));
|
|
15
21
|
* ```
|
|
16
22
|
*
|
|
17
23
|
* ## Attribute syntax
|
|
@@ -26,12 +32,31 @@
|
|
|
26
32
|
* <form on-submit="submit_form:$formdata; prevent,validate" novalidate>…</form>
|
|
27
33
|
* ```
|
|
28
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
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
29
54
|
* ## Programmatic dispatch
|
|
30
55
|
*
|
|
31
56
|
* ```ts
|
|
32
57
|
* import {dispatchAction} from '@alwatr/action';
|
|
33
58
|
*
|
|
34
|
-
* dispatchAction('navigate', '/dashboard');
|
|
59
|
+
* dispatchAction({type: 'navigate', payload: '/dashboard'});
|
|
35
60
|
* ```
|
|
36
61
|
*
|
|
37
62
|
* ## Registering typed actions
|
|
@@ -51,7 +76,8 @@
|
|
|
51
76
|
*
|
|
52
77
|
* ## Public API
|
|
53
78
|
*
|
|
54
|
-
* - `
|
|
79
|
+
* - `Action` — the AFSA object interface (`type`, `payload`, `context`, `meta`)
|
|
80
|
+
* - `ActionRecord` — extend this interface to register typed actions
|
|
55
81
|
* - `setupActionDelegation` / `teardownActionDelegation` — global delegation lifecycle
|
|
56
82
|
* - `DEFAULT_DELEGATED_EVENTS` — default event types covered by delegation
|
|
57
83
|
* - `onAction` / `dispatchAction` — subscribe to and dispatch named actions
|
|
@@ -61,7 +87,7 @@
|
|
|
61
87
|
*
|
|
62
88
|
* For page-ready signals in SSG/SSR apps, use `@alwatr/page-ready` instead.
|
|
63
89
|
*/
|
|
64
|
-
export type { ActionRecord } from './action-record.js';
|
|
65
|
-
export * from './method.js';
|
|
66
90
|
export * from './delegate.js';
|
|
91
|
+
export * from './method.js';
|
|
92
|
+
export type * from './type.js';
|
|
67
93
|
//# sourceMappingURL=main.d.ts.map
|
package/dist/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA
|
|
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"}
|