@alwatr/action 9.13.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 +190 -176
- package/dist/action-record.d.ts +7 -19
- package/dist/action-record.d.ts.map +1 -1
- package/dist/delegate.d.ts +8 -8
- package/dist/delegate.d.ts.map +1 -1
- package/dist/lib.d.ts +1 -7
- package/dist/lib.d.ts.map +1 -1
- package/dist/main.d.ts +27 -16
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +3 -3
- package/dist/main.js.map +7 -8
- package/dist/method.d.ts +26 -27
- package/dist/method.d.ts.map +1 -1
- package/dist/registry.d.ts +20 -14
- package/dist/registry.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/action-record.ts +9 -21
- package/src/delegate.ts +67 -73
- package/src/lib.ts +1 -7
- package/src/main.ts +27 -16
- package/src/method.ts +30 -35
- package/src/registry.ts +46 -23
- package/dist/page-ready.d.ts +0 -3
- package/dist/page-ready.d.ts.map +0 -1
- package/src/page-ready.ts +0 -31
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Declarative DOM action-dispatch — the Action layer for Unidirectional Data Flow.**
|
|
4
4
|
|
|
5
|
-
`@alwatr/action` bridges HTML `on
|
|
5
|
+
`@alwatr/action` bridges HTML `on-<eventType>` attributes to typed signal handlers using **global event delegation**. One listener on `document.body` covers every element on the page — including elements added dynamically after bootstrap — with O(1) initialization cost regardless of how many elements exist.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -19,26 +19,27 @@
|
|
|
19
19
|
|
|
20
20
|
## How It Works
|
|
21
21
|
|
|
22
|
+
### Action Bus
|
|
23
|
+
|
|
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
|
+
|
|
22
26
|
### Global Event Delegation
|
|
23
27
|
|
|
24
|
-
|
|
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 action.
|
|
25
29
|
|
|
26
30
|
```
|
|
27
31
|
User clicks a button
|
|
28
32
|
│
|
|
29
33
|
▼
|
|
30
|
-
document.body capture listener
|
|
34
|
+
document.body capture listener (1 listener per event type)
|
|
31
35
|
│
|
|
32
|
-
└─ closest('[on-
|
|
33
|
-
parse attribute → '
|
|
36
|
+
└─ closest('[on-click]') → finds element
|
|
37
|
+
parse attribute → 'add_to_cart:42'
|
|
34
38
|
run modifiers → none
|
|
35
39
|
resolve payload → '42'
|
|
36
|
-
|
|
37
|
-
│
|
|
38
|
-
▼
|
|
39
|
-
ChannelSignal.dispatch('add-to-cart', '42') [O(1)]
|
|
40
|
+
internalChannel_.dispatch('add_to_cart', '42')
|
|
40
41
|
│
|
|
41
|
-
└─ Map.get('
|
|
42
|
+
└─ Map.get('add_to_cart') → O(1) → invoke only matching handlers
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
### Complexity
|
|
@@ -48,10 +49,11 @@ document.body capture listener (1 listener total)
|
|
|
48
49
|
| Boot time | O(N elements) | **O(1)** |
|
|
49
50
|
| Memory | O(N listeners) | **O(1)** |
|
|
50
51
|
| Dynamic content | Requires re-bootstrap | **Works out-of-box** |
|
|
52
|
+
| `once` modifier | Native option | Remove attribute |
|
|
51
53
|
|
|
52
|
-
###
|
|
54
|
+
### `once` modifier
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
In delegation mode, `once` is implemented by removing the `on-<eventType>` attribute from the element after the first fire. This is simpler than a `WeakSet` cache and naturally handles element reuse — if the element is re-rendered with the attribute, it fires again.
|
|
55
57
|
|
|
56
58
|
---
|
|
57
59
|
|
|
@@ -69,55 +71,54 @@ npm i @alwatr/action
|
|
|
69
71
|
|
|
70
72
|
### 1. Register your action types
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
Extend `ActionRecord` via declaration merging. This gives you full type safety and IDE autocomplete — passing an undeclared action name is a **compile error**.
|
|
73
75
|
|
|
74
76
|
```ts
|
|
75
77
|
// src/action-record.ts
|
|
76
78
|
declare module '@alwatr/action' {
|
|
77
79
|
interface ActionRecord {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
open_drawer: string;
|
|
81
|
+
search_query: string;
|
|
82
|
+
add_to_cart: {productId: number; qty: number};
|
|
83
|
+
logout: void;
|
|
82
84
|
}
|
|
83
85
|
}
|
|
84
86
|
```
|
|
85
87
|
|
|
86
|
-
Passing an action name not declared in `ActionRecord` is a **compile error** — there is no string fallback.
|
|
87
|
-
|
|
88
88
|
### 2. Bootstrap delegation
|
|
89
89
|
|
|
90
90
|
```ts
|
|
91
91
|
import {setupActionDelegation, onAction} from '@alwatr/action';
|
|
92
92
|
import './action-record.js'; // ensure the declaration is loaded
|
|
93
93
|
|
|
94
|
-
// One call — the entire page is covered, including future dynamic content.
|
|
95
94
|
setupActionDelegation();
|
|
96
95
|
|
|
97
|
-
// Payload types are inferred
|
|
98
|
-
onAction('
|
|
99
|
-
onAction('
|
|
100
|
-
|
|
101
|
-
cartService.add(item.productId, item.qty); // fully typed, no `!`
|
|
96
|
+
// Payload types are inferred from ActionRecord — no generics needed.
|
|
97
|
+
onAction('open_drawer', (panel) => openDrawer(panel)); // panel: string
|
|
98
|
+
onAction('add_to_cart', (item) => {
|
|
99
|
+
cartService.add(item.productId, item.qty); // fully typed
|
|
102
100
|
});
|
|
103
101
|
```
|
|
104
102
|
|
|
105
103
|
### 3. Add attributes to HTML
|
|
106
104
|
|
|
107
105
|
```html
|
|
108
|
-
<!-- Dispatches '
|
|
109
|
-
<button on-
|
|
106
|
+
<!-- Dispatches 'close_drawer' on click — no payload -->
|
|
107
|
+
<button on-click="close_drawer">Close</button>
|
|
108
|
+
|
|
109
|
+
<!-- Dispatches 'open_drawer' with payload 'main' on click -->
|
|
110
|
+
<button on-click="open_drawer:main">Open Drawer</button>
|
|
110
111
|
|
|
111
|
-
<!-- Dispatches '
|
|
112
|
+
<!-- Dispatches 'search_query' with the input's live value -->
|
|
112
113
|
<input
|
|
113
114
|
type="search"
|
|
114
|
-
on-
|
|
115
|
+
on-input="search_query:$value"
|
|
115
116
|
placeholder="Search…"
|
|
116
117
|
/>
|
|
117
118
|
|
|
118
119
|
<!-- Prevents default, validates, then dispatches all field values -->
|
|
119
120
|
<form
|
|
120
|
-
on-
|
|
121
|
+
on-submit="submit_form:$formdata; prevent,validate"
|
|
121
122
|
novalidate
|
|
122
123
|
>
|
|
123
124
|
<input
|
|
@@ -126,19 +127,20 @@ onAction('add-to-cart', (item) => {
|
|
|
126
127
|
/>
|
|
127
128
|
<button type="submit">Save</button>
|
|
128
129
|
</form>
|
|
130
|
+
|
|
131
|
+
<!-- Fires only once — attribute is removed after first click -->
|
|
132
|
+
<button on-click="welcome_dismissed; once">Got it</button>
|
|
129
133
|
```
|
|
130
134
|
|
|
131
|
-
###
|
|
135
|
+
### 4. Programmatic dispatch
|
|
132
136
|
|
|
133
137
|
```ts
|
|
134
138
|
import {dispatchAction} from '@alwatr/action';
|
|
135
139
|
|
|
136
|
-
// Trigger actions from code — after async ops, from service layers, etc.
|
|
137
140
|
await uploadFile(file);
|
|
138
|
-
dispatchAction('
|
|
141
|
+
dispatchAction('upload_complete', fileId);
|
|
139
142
|
|
|
140
143
|
dispatchAction('navigate', '/dashboard');
|
|
141
|
-
dispatchAction<{code: number}>('show-error', {code: 404});
|
|
142
144
|
```
|
|
143
145
|
|
|
144
146
|
---
|
|
@@ -146,27 +148,23 @@ dispatchAction<{code: number}>('show-error', {code: 404});
|
|
|
146
148
|
## Attribute Syntax
|
|
147
149
|
|
|
148
150
|
```
|
|
149
|
-
on
|
|
151
|
+
on-<eventType>="actionId[:payload][; modifier1,modifier2,…]"
|
|
150
152
|
```
|
|
151
153
|
|
|
152
|
-
| Segment
|
|
153
|
-
|
|
|
154
|
-
| `eventType`
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
157
|
-
|
|
|
154
|
+
| Segment | Description | Example |
|
|
155
|
+
| ------------- | ----------------------------------------------------------- | ----------------------------- |
|
|
156
|
+
| `eventType` | Any standard DOM event name — encoded in the attribute name | `on-click`, `on-submit` |
|
|
157
|
+
| `actionId` | Identifier your handler subscribes to | `open_drawer`, `search_query` |
|
|
158
|
+
| `:payload` | Optional literal string, or a `$`-prefixed resolver token | `:main`, `:$value` |
|
|
159
|
+
| `; modifiers` | Optional comma-separated modifier list after a semicolon | `; prevent,validate` |
|
|
158
160
|
|
|
159
161
|
### Built-in modifiers
|
|
160
162
|
|
|
161
|
-
| Modifier
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
| `.validate` | Cancels dispatch if the nearest `<form>` fails `checkValidity()` |
|
|
167
|
-
|
|
168
|
-
> **Note:** `.passive` is not supported in delegation mode because all delegated
|
|
169
|
-
> listeners must be non-passive to allow `.prevent` to work.
|
|
163
|
+
| Modifier | Behavior |
|
|
164
|
+
| ---------- | ------------------------------------------------------------------------------------- |
|
|
165
|
+
| `prevent` | Calls `event.preventDefault()` |
|
|
166
|
+
| `once` | Removes the `on-<eventType>` attribute after first fire — action dispatches only once |
|
|
167
|
+
| `validate` | Cancels dispatch if the nearest `<form>` fails `checkValidity()` |
|
|
170
168
|
|
|
171
169
|
### Built-in payload resolvers
|
|
172
170
|
|
|
@@ -181,31 +179,22 @@ on-action="eventType[.modifier…]->actionId[:payload]"
|
|
|
181
179
|
|
|
182
180
|
### `ActionRecord` (interface)
|
|
183
181
|
|
|
184
|
-
The global action type registry. Extend
|
|
182
|
+
The global action type registry. Extend via declaration merging to register typed actions.
|
|
185
183
|
|
|
186
184
|
```ts
|
|
187
|
-
// src/action-record.ts
|
|
188
185
|
declare module '@alwatr/action' {
|
|
189
186
|
interface ActionRecord {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
'logout': void;
|
|
187
|
+
open_drawer: string;
|
|
188
|
+
logout: void;
|
|
193
189
|
}
|
|
194
190
|
}
|
|
195
191
|
```
|
|
196
192
|
|
|
197
|
-
Once declared:
|
|
198
|
-
|
|
199
|
-
- `onAction('open-drawer', (panel) => …)` — `panel` is inferred as `string`
|
|
200
|
-
- `dispatchAction('add-to-cart', {productId: 42, qty: 1})` — payload type enforced
|
|
201
|
-
- `dispatchAction('unknown-action', …)` — **compile error**
|
|
202
|
-
|
|
203
193
|
---
|
|
204
194
|
|
|
205
195
|
### `setupActionDelegation(eventTypes?)`
|
|
206
196
|
|
|
207
|
-
Registers global event delegation on `document.body`. Call once at bootstrap.
|
|
208
|
-
Subsequent calls with the same event types are no-ops (idempotent).
|
|
197
|
+
Registers global event delegation on `document.body`. Call once at bootstrap. Idempotent.
|
|
209
198
|
|
|
210
199
|
```ts
|
|
211
200
|
function setupActionDelegation(eventTypes?: readonly string[]): void;
|
|
@@ -216,18 +205,14 @@ Defaults to `DEFAULT_DELEGATED_EVENTS`: `['click', 'submit', 'input', 'change']`
|
|
|
216
205
|
```ts
|
|
217
206
|
import {setupActionDelegation, DEFAULT_DELEGATED_EVENTS} from '@alwatr/action';
|
|
218
207
|
|
|
219
|
-
|
|
220
|
-
setupActionDelegation();
|
|
221
|
-
|
|
222
|
-
// Add extra event types
|
|
223
|
-
setupActionDelegation([...DEFAULT_DELEGATED_EVENTS, 'keydown', 'pointerup']);
|
|
208
|
+
setupActionDelegation([...DEFAULT_DELEGATED_EVENTS, 'keydown']);
|
|
224
209
|
```
|
|
225
210
|
|
|
226
211
|
---
|
|
227
212
|
|
|
228
213
|
### `teardownActionDelegation()`
|
|
229
214
|
|
|
230
|
-
Removes all delegation listeners. Useful in tests or micro-frontend teardown.
|
|
215
|
+
Removes all delegation listeners and clears the descriptor cache. Useful in tests or micro-frontend teardown.
|
|
231
216
|
|
|
232
217
|
```ts
|
|
233
218
|
function teardownActionDelegation(): void;
|
|
@@ -237,54 +222,32 @@ function teardownActionDelegation(): void;
|
|
|
237
222
|
|
|
238
223
|
### `onAction(actionId, handler)`
|
|
239
224
|
|
|
240
|
-
Subscribes to a named action.
|
|
225
|
+
Subscribes to a named action. O(1) routing via `ChannelSignal`.
|
|
241
226
|
|
|
242
227
|
```ts
|
|
243
|
-
function onAction<
|
|
228
|
+
function onAction<K extends keyof ActionRecord>(
|
|
229
|
+
actionId: K,
|
|
230
|
+
handler: (payload: ActionRecord[K]) => void,
|
|
231
|
+
): SubscribeResult;
|
|
244
232
|
```
|
|
245
233
|
|
|
246
234
|
```ts
|
|
247
|
-
const sub = onAction('
|
|
248
|
-
|
|
249
|
-
// Unsubscribe when no longer needed (prevents memory leaks)
|
|
250
|
-
sub.unsubscribe();
|
|
235
|
+
const sub = onAction('open_drawer', (panel) => openDrawer(panel));
|
|
236
|
+
sub.unsubscribe(); // prevent memory leaks
|
|
251
237
|
```
|
|
252
238
|
|
|
253
239
|
---
|
|
254
240
|
|
|
255
241
|
### `dispatchAction(actionId, payload?)`
|
|
256
242
|
|
|
257
|
-
Dispatches a named action
|
|
243
|
+
Dispatches a named action. Payload type is enforced by `ActionRecord`.
|
|
258
244
|
|
|
259
245
|
```ts
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
|
-
### `dispatchPageId(element?)`
|
|
246
|
+
// With payload
|
|
247
|
+
dispatchAction('open_drawer', 'settings');
|
|
266
248
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
```ts
|
|
271
|
-
function dispatchPageId(element?: HTMLElement): void;
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
```html
|
|
275
|
-
<body page-id="home">
|
|
276
|
-
…
|
|
277
|
-
</body>
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
```ts
|
|
281
|
-
import {dispatchPageId, onAction} from '@alwatr/action';
|
|
282
|
-
|
|
283
|
-
dispatchPageId(); // → dispatchAction('page-ready', 'home')
|
|
284
|
-
|
|
285
|
-
onAction('page-ready', (pageId) => {
|
|
286
|
-
console.log('navigated to:', pageId); // 'home'
|
|
287
|
-
});
|
|
249
|
+
// Void payload — no second argument
|
|
250
|
+
dispatchAction('logout');
|
|
288
251
|
```
|
|
289
252
|
|
|
290
253
|
---
|
|
@@ -292,48 +255,57 @@ onAction('page-ready', (pageId) => {
|
|
|
292
255
|
### `registerModifier(name, handler)`
|
|
293
256
|
|
|
294
257
|
Registers a custom modifier. Return `false` to cancel the dispatch.
|
|
295
|
-
|
|
258
|
+
|
|
259
|
+
Handler signature: `(event: Event, element: HTMLElement) => boolean`
|
|
296
260
|
|
|
297
261
|
```ts
|
|
298
262
|
import {registerModifier} from '@alwatr/action';
|
|
299
263
|
|
|
300
|
-
registerModifier('
|
|
301
|
-
return
|
|
264
|
+
registerModifier('not_disabled', (_event, element) => {
|
|
265
|
+
return !(element as HTMLButtonElement).disabled;
|
|
302
266
|
});
|
|
303
267
|
```
|
|
304
268
|
|
|
305
269
|
```html
|
|
306
|
-
<button
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
interface ActionContext {
|
|
313
|
-
readonly element: HTMLElement; // the element with the on-action attribute
|
|
314
|
-
}
|
|
270
|
+
<button
|
|
271
|
+
on-click="select_item:$data_id; not_disabled"
|
|
272
|
+
data-id="42"
|
|
273
|
+
>
|
|
274
|
+
Select
|
|
275
|
+
</button>
|
|
315
276
|
```
|
|
316
277
|
|
|
317
278
|
---
|
|
318
279
|
|
|
319
280
|
### `registerPayloadResolver(name, resolver)`
|
|
320
281
|
|
|
321
|
-
Registers a custom payload resolver.
|
|
322
|
-
|
|
282
|
+
Registers a custom payload resolver.
|
|
283
|
+
|
|
284
|
+
Handler signature: `(event: Event, element: HTMLElement) => unknown`
|
|
323
285
|
|
|
324
286
|
```ts
|
|
325
287
|
import {registerPayloadResolver} from '@alwatr/action';
|
|
326
288
|
|
|
327
|
-
registerPayloadResolver('$checked',
|
|
328
|
-
return (
|
|
289
|
+
registerPayloadResolver('$checked', (_event, element) => {
|
|
290
|
+
return (element as HTMLInputElement).checked;
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
registerPayloadResolver('$data_id', (_event, element) => {
|
|
294
|
+
return (element as HTMLElement).dataset.id ?? null;
|
|
329
295
|
});
|
|
330
296
|
```
|
|
331
297
|
|
|
332
298
|
```html
|
|
333
299
|
<input
|
|
334
300
|
type="checkbox"
|
|
335
|
-
on-
|
|
301
|
+
on-change="toggle_feature:$checked"
|
|
336
302
|
/>
|
|
303
|
+
<li
|
|
304
|
+
on-click="select_item:$data_id"
|
|
305
|
+
data-id="42"
|
|
306
|
+
>
|
|
307
|
+
Item
|
|
308
|
+
</li>
|
|
337
309
|
```
|
|
338
310
|
|
|
339
311
|
---
|
|
@@ -341,93 +313,135 @@ registerPayloadResolver('$checked', function () {
|
|
|
341
313
|
## Unidirectional Data Flow
|
|
342
314
|
|
|
343
315
|
```
|
|
344
|
-
|
|
345
|
-
│
|
|
346
|
-
│ <button on-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
│
|
|
352
|
-
│ document.body capture listener (1
|
|
353
|
-
│ → closest('[on-
|
|
354
|
-
│ →
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
│
|
|
360
|
-
│ onAction('
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
│
|
|
366
|
-
│ cartSignal.set(newCartState)
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
316
|
+
┌──────────────────────────────────────────────────────────┐
|
|
317
|
+
│ UI Layer │
|
|
318
|
+
│ <button on-click="add_to_cart:42">Add</button> │
|
|
319
|
+
└─────────────────────────┬────────────────────────────────┘
|
|
320
|
+
│ DOM event bubbles to body
|
|
321
|
+
▼
|
|
322
|
+
┌──────────────────────────────────────────────────────────┐
|
|
323
|
+
│ Action Layer (@alwatr/action) │
|
|
324
|
+
│ document.body capture listener (1 per event type) │
|
|
325
|
+
│ → closest('[on-click]') → parse → modifiers │
|
|
326
|
+
│ → internalChannel_.dispatch('add_to_cart', '42') [O(1)] │
|
|
327
|
+
└─────────────────────────┬────────────────────────────────┘
|
|
328
|
+
│ O(1) routing via ChannelSignal
|
|
329
|
+
▼
|
|
330
|
+
┌──────────────────────────────────────────────────────────┐
|
|
331
|
+
│ Business Logic Layer │
|
|
332
|
+
│ onAction('add_to_cart', (id) => cartService.add(id)) │
|
|
333
|
+
└─────────────────────────┬────────────────────────────────┘
|
|
334
|
+
│ state update
|
|
335
|
+
▼
|
|
336
|
+
┌──────────────────────────────────────────────────────────┐
|
|
337
|
+
│ State Layer (@alwatr/signal) │
|
|
338
|
+
│ cartSignal.set(newCartState) │
|
|
339
|
+
└─────────────────────────┬────────────────────────────────┘
|
|
340
|
+
│ state flows down to UI
|
|
341
|
+
▼
|
|
342
|
+
UI re-renders
|
|
371
343
|
```
|
|
372
344
|
|
|
373
345
|
---
|
|
374
346
|
|
|
347
|
+
## Page Identity
|
|
348
|
+
|
|
349
|
+
For page-ready signals in SSG/SSR apps (reading `page-id` attribute and notifying
|
|
350
|
+
page-specific handlers), use [`@alwatr/page-ready`](../page-ready/README.md) instead.
|
|
351
|
+
It is intentionally separate from the action bus — page identity is a routing/lifecycle
|
|
352
|
+
concern, not a user-interaction action.
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
375
356
|
## Migration from Previous Versions
|
|
376
357
|
|
|
377
|
-
###
|
|
358
|
+
### Attribute syntax changed
|
|
378
359
|
|
|
379
|
-
The
|
|
360
|
+
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.
|
|
380
361
|
|
|
381
362
|
**Before:**
|
|
382
363
|
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
364
|
+
```html
|
|
365
|
+
<button on-action="click->open_drawer:main">Open</button>
|
|
366
|
+
<form
|
|
367
|
+
on-action="submit.prevent.validate->submit_form:$formdata"
|
|
368
|
+
novalidate
|
|
369
|
+
>
|
|
370
|
+
…
|
|
371
|
+
</form>
|
|
372
|
+
<button on-action="click.once->welcome_dismissed">Got it</button>
|
|
390
373
|
```
|
|
391
374
|
|
|
392
375
|
**After:**
|
|
393
376
|
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
377
|
+
```html
|
|
378
|
+
<button on-click="open_drawer:main">Open</button>
|
|
379
|
+
<form
|
|
380
|
+
on-submit="submit_form:$formdata; prevent,validate"
|
|
381
|
+
novalidate
|
|
382
|
+
>
|
|
383
|
+
…
|
|
384
|
+
</form>
|
|
385
|
+
<button on-click="welcome_dismissed; once">Got it</button>
|
|
399
386
|
```
|
|
400
387
|
|
|
401
|
-
### `
|
|
402
|
-
|
|
403
|
-
These classes are no longer exported. Use `setupActionDelegation()` and
|
|
404
|
-
`dispatchPageId()` instead.
|
|
405
|
-
|
|
406
|
-
### `ModifierHandler` / `PayloadResolver` context changed
|
|
388
|
+
### `ActionContext` removed
|
|
407
389
|
|
|
408
|
-
The `this` context in
|
|
409
|
-
`ActionDirective` to `ActionContext`:
|
|
390
|
+
The `this` context in modifier and resolver handlers changed to explicit parameters:
|
|
410
391
|
|
|
411
392
|
**Before:**
|
|
412
393
|
|
|
413
394
|
```ts
|
|
414
|
-
registerModifier('
|
|
415
|
-
return !(this.
|
|
395
|
+
registerModifier('not_disabled', function () {
|
|
396
|
+
return !(this.element as HTMLButtonElement).disabled;
|
|
416
397
|
});
|
|
417
398
|
```
|
|
418
399
|
|
|
419
400
|
**After:**
|
|
420
401
|
|
|
421
402
|
```ts
|
|
422
|
-
registerModifier('
|
|
423
|
-
return !(
|
|
403
|
+
registerModifier('not_disabled', (_event, element) => {
|
|
404
|
+
return !(element as HTMLButtonElement).disabled;
|
|
424
405
|
});
|
|
425
406
|
```
|
|
426
407
|
|
|
427
|
-
### `
|
|
408
|
+
### `page-ready` moved to `@alwatr/page-ready`
|
|
409
|
+
|
|
410
|
+
`dispatchPageId` / `onPageReady` are no longer part of this package.
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## 🌊 Part of Alwatr Flux
|
|
417
|
+
|
|
418
|
+
`@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
|
+
|
|
420
|
+
```
|
|
421
|
+
View (HTML on-<event> attributes)
|
|
422
|
+
↓
|
|
423
|
+
Action Layer (@alwatr/action) — global delegation, O(1) routing
|
|
424
|
+
↓
|
|
425
|
+
Controller (business logic via onAction)
|
|
426
|
+
↓
|
|
427
|
+
State Layer (@alwatr/signal) — fine-grained reactivity
|
|
428
|
+
↓
|
|
429
|
+
View (re-render only affected nodes)
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
`@alwatr/action` is the bridge between the **View** and **Controller** layers. It captures user intent from HTML attributes and routes it to the right handler — without any coupling between the UI and business logic.
|
|
433
|
+
|
|
434
|
+
**The full Flux bundle** (`@alwatr/flux`) includes actions, signals, directives, page-ready, and storage — everything you need to build a complete reactive application from a single import.
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
// Use @alwatr/flux for the complete architecture
|
|
438
|
+
import {setupActionDelegation, onAction, createStateSignal} from '@alwatr/flux';
|
|
439
|
+
|
|
440
|
+
// Or use @alwatr/action standalone for just the action bus
|
|
441
|
+
import {setupActionDelegation, onAction, dispatchAction} from '@alwatr/action';
|
|
442
|
+
```
|
|
428
443
|
|
|
429
|
-
|
|
430
|
-
is no longer needed. Use `onAction` and `dispatchAction` directly.
|
|
444
|
+
→ [View the complete Flux documentation](https://github.com/Alwatr/alwatr/tree/next/pkg/flux)
|
|
431
445
|
|
|
432
446
|
---
|
|
433
447
|
|
package/dist/action-record.d.ts
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* // In your package: src/action-record.ts
|
|
14
14
|
* declare module '@alwatr/action' {
|
|
15
15
|
* interface ActionRecord {
|
|
16
|
-
* '
|
|
17
|
-
* '
|
|
16
|
+
* 'open_drawer': string;
|
|
17
|
+
* 'add_to_cart': {productId: number; qty: number};
|
|
18
18
|
* 'logout': void;
|
|
19
19
|
* }
|
|
20
20
|
* }
|
|
@@ -33,34 +33,22 @@
|
|
|
33
33
|
* Extend this interface via declaration merging to register your application's
|
|
34
34
|
* actions and gain full type safety in `onAction` and `dispatchAction`.
|
|
35
35
|
*
|
|
36
|
-
*
|
|
37
|
-
* be declared in a dedicated `action-record.ts`
|
|
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.
|
|
38
39
|
*
|
|
39
40
|
* @example — registering actions in a feature package
|
|
40
41
|
* ```ts
|
|
41
42
|
* // pkg/my-feature/src/action-record.ts
|
|
42
43
|
* declare module '@alwatr/action' {
|
|
43
44
|
* interface ActionRecord {
|
|
44
|
-
* '
|
|
45
|
-
* '
|
|
45
|
+
* 'open_drawer': string;
|
|
46
|
+
* 'add_to_cart': {productId: number; qty: number};
|
|
46
47
|
* 'logout': void;
|
|
47
48
|
* }
|
|
48
49
|
* }
|
|
49
50
|
* ```
|
|
50
51
|
*/
|
|
51
52
|
export interface ActionRecord {
|
|
52
|
-
/**
|
|
53
|
-
* Dispatched by `dispatchPageId()` when the page identity is read from the
|
|
54
|
-
* `page-id` HTML attribute. Payload is the page identifier string.
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* ```html
|
|
58
|
-
* <body page-id="home">…</body>
|
|
59
|
-
* ```
|
|
60
|
-
* ```ts
|
|
61
|
-
* onAction('page-ready', (pageId) => router.setPage(pageId));
|
|
62
|
-
* ```
|
|
63
|
-
*/
|
|
64
|
-
'page-ready': string;
|
|
65
53
|
}
|
|
66
54
|
//# sourceMappingURL=action-record.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-record.d.ts","sourceRoot":"","sources":["../src/action-record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH
|
|
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"}
|