@alwatr/action 9.12.0 → 9.13.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/dist/method.d.ts CHANGED
@@ -1,74 +1,98 @@
1
1
  import type { SubscribeResult } from '@alwatr/signal';
2
2
  import { type ModifierHandler, type PayloadResolver } from './registry.js';
3
+ import type { ActionRecord } from './action-record.js';
3
4
  export type { ModifierHandler, PayloadResolver };
4
5
  /**
5
6
  * Subscribes to a named action dispatched anywhere in the application.
6
7
  *
7
- * The handler is invoked every time `dispatchAction(actionId, payload)` is
8
- * called whether from an `on-action` directive or from code and the
9
- * `actionId` matches. Multiple subscribers for the same `actionId` are all
10
- * notified in subscription order.
8
+ * `actionId` must be a key of `ActionRecord`. The handler's `payload` parameter
9
+ * is automatically typed to the corresponding `ActionRecord` valueno manual
10
+ * generic annotation needed:
11
11
  *
12
- * The generic parameter `T` narrows the type of the received payload.
13
- * Defaults to `string`, which covers the common case of attribute-driven
14
- * literal payloads.
12
+ * ```ts
13
+ * // ActionRecord declares: 'add-to-cart': {productId: number; qty: number}
14
+ * onAction('add-to-cart', (item) => {
15
+ * cartService.add(item.productId, item.qty); // fully typed, no `!` needed
16
+ * });
17
+ * ```
15
18
  *
16
- * @param actionId - The action identifier to listen for (e.g. `'open-drawer'`).
17
- * @param handler - Callback invoked with the resolved payload each time the
18
- * action is dispatched. `payload` is `undefined` when the
19
- * action was dispatched without a value.
20
- * @returns A `SubscribeResult` with an `unsubscribe()` method for cleanup.
19
+ * Passing an action name not declared in `ActionRecord` is a **compile error**.
20
+ * Register new actions by extending `ActionRecord` via declaration merging:
21
21
  *
22
- * @example — basic string payload
23
22
  * ```ts
24
- * import {onAction} from '@alwatr/action';
23
+ * // src/action-record.ts
24
+ * declare module '@alwatr/action' {
25
+ * interface ActionRecord {
26
+ * 'open-drawer': string;
27
+ * }
28
+ * }
29
+ * ```
25
30
  *
26
- * const sub = onAction('open-drawer', (panel) => {
27
- * openDrawer(panel); // panel === 'settings'
28
- * });
31
+ * Internally delegates to `ChannelSignal.on()` for **O(1) routing** — dispatching
32
+ * action `'A'` never invokes handlers registered for action `'B'`.
29
33
  *
30
- * // Stop listening when the component is destroyed
31
- * sub.unsubscribe();
32
- * ```
34
+ * @param actionId - A key of `ActionRecord`.
35
+ * @param handler - Callback invoked with the typed payload on each dispatch.
36
+ * @returns A `SubscribeResult` with an `unsubscribe()` method for cleanup.
33
37
  *
34
- * @example — typed object payload
38
+ * @example
35
39
  * ```ts
36
40
  * import {onAction} from '@alwatr/action';
37
41
  *
38
- * onAction<{productId: number; qty: number}>('add-to-cart', (item) => {
39
- * cartService.add(item!.productId, item!.qty);
42
+ * const sub = onAction('page-ready', (pageId) => {
43
+ * router.setPage(pageId); // pageId: string — inferred from ActionRecord
40
44
  * });
45
+ *
46
+ * sub.unsubscribe(); // stop listening when no longer needed
41
47
  * ```
42
48
  */
43
- export declare function onAction<T = string>(actionId: string, handler: (payload?: T) => void): SubscribeResult;
49
+ export declare function onAction<K extends keyof ActionRecord>(actionId: K, handler: (payload: ActionRecord[K]) => void): SubscribeResult;
44
50
  /**
45
51
  * Dispatches a named action to all `onAction` subscribers with a matching `actionId`.
46
52
  *
47
- * This is the programmatic counterpart to the `on-action` HTML attribute.
48
- * Use it when you need to trigger an action from code rather than from a DOM
49
- * event (e.g. after an async operation completes, or from a service layer).
53
+ * `actionId` must be a key of `ActionRecord`. The `payload` parameter is
54
+ * automatically typed passing the wrong type is a **compile error**:
50
55
  *
51
- * The generic parameter `T` types the payload. Omit it to default to `string`.
56
+ * ```ts
57
+ * // ActionRecord declares: 'add-to-cart': {productId: number; qty: number}
58
+ * dispatchAction('add-to-cart', {productId: 42, qty: 1}); // ✅
59
+ * dispatchAction('add-to-cart', 'wrong'); // ❌ compile error
60
+ * dispatchAction('unknown-action', 'x'); // ❌ compile error
61
+ * ```
52
62
  *
53
- * @param actionId - The action identifier (e.g. `'navigate'`).
54
- * @param actionPayload - Optional value passed to every matching subscriber.
63
+ * Register new actions by extending `ActionRecord` via declaration merging:
55
64
  *
56
- * @example — dispatch without payload
57
65
  * ```ts
58
- * import {dispatchAction} from '@alwatr/action';
59
- *
60
- * dispatchAction('logout');
66
+ * // src/action-record.ts
67
+ * declare module '@alwatr/action' {
68
+ * interface ActionRecord {
69
+ * 'navigate': string;
70
+ * 'logout': void;
71
+ * }
72
+ * }
61
73
  * ```
62
74
  *
63
- * @example dispatch with a typed payload
75
+ * Use `dispatchAction` when triggering an action from code — e.g. after an
76
+ * async operation, from a service layer, or in tests. For DOM-driven actions,
77
+ * use the `on-action` HTML attribute with `setupActionDelegation`.
78
+ *
79
+ * @param actionId - A key of `ActionRecord`.
80
+ * @param actionPayload - The payload; type is enforced by `ActionRecord`.
81
+ *
82
+ * @example — with payload
64
83
  * ```ts
65
84
  * import {dispatchAction} from '@alwatr/action';
66
85
  *
86
+ * dispatchAction('page-ready', 'home');
67
87
  * dispatchAction('navigate', '/dashboard');
68
- * dispatchAction<{code: number}>('show-error', {code: 404});
88
+ * ```
89
+ *
90
+ * @example — void payload (no second argument)
91
+ * ```ts
92
+ * dispatchAction('logout');
69
93
  * ```
70
94
  */
71
- export declare function dispatchAction<T = string>(actionId: string, actionPayload?: T): void;
95
+ export declare function dispatchAction<K extends keyof ActionRecord>(...args: ActionRecord[K] extends void | undefined ? [actionId: K] : [actionId: K, actionPayload: ActionRecord[K]]): void;
72
96
  /**
73
97
  * Registers a custom modifier that can be used in `on-action` attribute syntax.
74
98
  *
@@ -76,14 +100,14 @@ export declare function dispatchAction<T = string>(actionId: string, actionPaylo
76
100
  * (e.g. `click.mymod->action-id`). Its handler runs before the payload is
77
101
  * resolved and the action is dispatched. Returning `false` cancels the dispatch.
78
102
  *
79
- * Built-in modifiers (`prevent`, `stop`, `validate`, `once`, `passive`) are
80
- * always available. This function lets you add domain-specific ones.
103
+ * Built-in modifiers (`prevent`, `stop`, `validate`, `once`) are always
104
+ * available. This function lets you add domain-specific ones.
81
105
  *
82
106
  * Registering the same name twice logs an accident and overwrites the previous
83
107
  * handler — avoid duplicate registrations in production code.
84
108
  *
85
109
  * @param name - The modifier token (lowercase, no dots or arrows).
86
- * @param handler - The `ModifierHandler` function bound to the directive instance.
110
+ * @param handler - The `ModifierHandler` function bound to an `ActionContext`.
87
111
  *
88
112
  * @example — a `confirm` modifier that shows a browser dialog
89
113
  * ```ts
@@ -103,7 +127,7 @@ export declare function registerModifier(name: string, handler: ModifierHandler)
103
127
  *
104
128
  * A payload resolver is a colon-suffixed token in the attribute value
105
129
  * (e.g. `click->action-id:$mytoken`). Its function is called at dispatch time
106
- * with the directive instance as `this` and the DOM event as the argument.
130
+ * with an `ActionContext` as `this` and the DOM event as the argument.
107
131
  * The return value becomes the `actionPayload` passed to `onAction` subscribers.
108
132
  *
109
133
  * Built-in resolvers (`$value`, `$formdata`) are always available. This function
@@ -113,29 +137,19 @@ export declare function registerModifier(name: string, handler: ModifierHandler)
113
137
  * resolver — avoid duplicate registrations in production code.
114
138
  *
115
139
  * @param name - The resolver token (should start with `$` by convention).
116
- * @param resolver - The `PayloadResolver` function bound to the directive instance.
140
+ * @param resolver - The `PayloadResolver` function bound to an `ActionContext`.
117
141
  *
118
142
  * @example — a `$checked` resolver for checkbox state
119
143
  * ```ts
120
144
  * import {registerPayloadResolver} from '@alwatr/action';
121
145
  *
122
146
  * registerPayloadResolver('$checked', function () {
123
- * return (this.element_ as HTMLInputElement).checked;
147
+ * return (this.element as HTMLInputElement).checked;
124
148
  * });
125
149
  * ```
126
150
  * ```html
127
151
  * <input type="checkbox" on-action="change->toggle-feature:$checked" />
128
152
  * ```
129
- *
130
- * @example — a `$dataset-id` resolver for data attributes
131
- * ```ts
132
- * registerPayloadResolver('$dataset-id', function () {
133
- * return (this.element_ as HTMLElement).dataset.id ?? null;
134
- * });
135
- * ```
136
- * ```html
137
- * <li on-action="click->select-item:$dataset-id" data-id="42">Item</li>
138
- * ```
139
153
  */
140
154
  export declare function registerPayloadResolver(name: string, resolver: PayloadResolver): void;
141
155
  //# sourceMappingURL=method.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"method.d.ts","sourceRoot":"","sources":["../src/method.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAoC,KAAK,eAAe,EAAE,KAAK,eAAe,EAAC,MAAM,eAAe,CAAC;AAG5G,YAAY,EAAC,eAAe,EAAE,eAAe,EAAC,CAAC;AAI/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,QAAQ,CAAC,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,IAAI,GAAG,eAAe,CAQtG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,cAAc,CAAC,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,CAAC,GAAG,IAAI,CAGpF;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAM7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAMrF"}
1
+ {"version":3,"file":"method.d.ts","sourceRoot":"","sources":["../src/method.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAoC,KAAK,eAAe,EAAE,KAAK,eAAe,EAAC,MAAM,eAAe,CAAC;AAC5G,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAGrD,YAAY,EAAC,eAAe,EAAE,eAAe,EAAC,CAAC;AAI/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,YAAY,EACnD,QAAQ,EAAE,CAAC,EACX,OAAO,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,GAC1C,eAAe,CAKjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,YAAY,EACzD,GAAG,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,SAAS,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,GAChH,IAAI,CAAC;AASR;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAM7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAMrF"}
@@ -0,0 +1,3 @@
1
+ export declare function onPageReady<T extends string>(pageId: T, handler: () => void): void;
2
+ export declare function dispatchPageReady(): void;
3
+ //# sourceMappingURL=page-ready.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-ready.d.ts","sourceRoot":"","sources":["../src/page-ready.ts"],"names":[],"mappings":"AASA,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,IAAI,QAG3E;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAgBxC"}
@@ -1,36 +1,35 @@
1
- import type { ActionDirective } from './directive.js';
2
1
  /**
3
- * A modifier handler attached to an `on-action` directive.
2
+ * A modifier handler used in `on-action` attribute syntax.
4
3
  *
5
- * Called with the directive instance as `this` and the triggering DOM `event`.
6
- * Return `true` to allow the action to proceed, or `false` to cancel it.
7
- * Returning `false` is the only way a modifier can veto a dispatch.
4
+ * Called with an `ActionContext` as `this` and the triggering DOM `event`.
5
+ * Return `true` (or any truthy value) to allow the action to proceed,
6
+ * or `false` to cancel the dispatch.
8
7
  *
9
8
  * @example
10
9
  * ```ts
11
10
  * // A modifier that only allows the action when the element is not disabled
12
11
  * const notDisabledHandler: ModifierHandler = function () {
13
- * return !(this.element_ as HTMLButtonElement).disabled;
12
+ * return !(this.element as HTMLButtonElement).disabled;
14
13
  * };
15
14
  * ```
16
15
  */
17
- export type ModifierHandler = (this: ActionDirective, event: Event) => boolean;
16
+ export type ModifierHandler = (event: Event, element: HTMLElement) => boolean;
18
17
  /**
19
- * A payload resolver attached to an `on-action` directive.
18
+ * A payload resolver used in `on-action` attribute syntax.
20
19
  *
21
- * Called with the directive instance as `this` and the triggering DOM `event`
22
- * at dispatch time. The return value becomes the `actionPayload` of the
23
- * dispatched action. Use this to compute dynamic payloads from the DOM state.
20
+ * Called with an `ActionContext` as `this` and the triggering DOM `event`
21
+ * at dispatch time. The return value becomes the `actionPayload` passed to
22
+ * `onAction` subscribers. Use this to compute dynamic payloads from DOM state.
24
23
  *
25
24
  * @example
26
25
  * ```ts
27
26
  * // A resolver that returns the element's dataset id
28
27
  * const dataIdResolver: PayloadResolver = function () {
29
- * return (this.element_ as HTMLElement).dataset.id ?? null;
28
+ * return (this.element as HTMLElement).dataset.id ?? null;
30
29
  * };
31
30
  * ```
32
31
  */
33
- export type PayloadResolver = (this: ActionDirective, event: Event) => unknown;
32
+ export type PayloadResolver = (event: Event, element: HTMLElement) => unknown;
34
33
  /**
35
34
  * Registry of all named modifier handlers.
36
35
  *
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAC;AAIpD;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;AAE/E;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;AAI/E;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,8BAAqC,CAAC;AAEnE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,8BAAqC,CAAC"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC;AAE9E;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC;AAI9E;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,8BAAqC,CAAC;AAEnE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,8BAAqC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alwatr/action",
3
- "version": "9.12.0",
3
+ "version": "9.13.0",
4
4
  "description": "Declarative DOM action-dispatch — bridge HTML attributes to typed signal handlers.",
5
5
  "license": "MPL-2.0",
6
6
  "author": "S. Ali Mihandoost <ali.mihandoost@gmail.com> (https://ali.mihandoost.com)",
@@ -21,15 +21,13 @@
21
21
  },
22
22
  "sideEffects": false,
23
23
  "dependencies": {
24
- "@alwatr/directive": "9.11.2",
25
24
  "@alwatr/logger": "9.11.2",
26
- "@alwatr/signal": "9.12.0"
25
+ "@alwatr/signal": "9.13.0"
27
26
  },
28
27
  "devDependencies": {
29
28
  "@alwatr/nano-build": "9.10.1",
30
29
  "@alwatr/standard": "9.11.2",
31
30
  "@alwatr/type-helper": "9.11.2",
32
- "@happy-dom/global-registrator": "^20.9.0",
33
31
  "typescript": "^6.0.3"
34
32
  },
35
33
  "scripts": {
@@ -81,5 +79,5 @@
81
79
  "vanilla-js",
82
80
  "web-development"
83
81
  ],
84
- "gitHead": "b4ca873ebd15cd2e25b34273d76febfd75267b62"
82
+ "gitHead": "da284d23e3173d589fa69376e51a098c5e89649d"
85
83
  }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @file action-record.ts
3
+ *
4
+ * Global action type registry via TypeScript declaration merging.
5
+ *
6
+ * ## How it works
7
+ *
8
+ * `ActionRecord` is an open interface — any package in the monorepo (or any
9
+ * consumer application) can extend it with their own action names and payload
10
+ * types using declaration merging, without modifying this file:
11
+ *
12
+ * ```ts
13
+ * // In your package: src/action-record.ts
14
+ * declare module '@alwatr/action' {
15
+ * interface ActionRecord {
16
+ * 'open-drawer': string;
17
+ * 'add-to-cart': {productId: number; qty: number};
18
+ * 'logout': void;
19
+ * }
20
+ * }
21
+ * ```
22
+ *
23
+ * Once declared, `onAction` and `dispatchAction` become fully type-safe for
24
+ * those action names — the compiler enforces the correct payload type at every
25
+ * call site and provides autocomplete for action identifiers.
26
+ *
27
+ * Only actions declared in `ActionRecord` are accepted. Passing an unknown
28
+ * action name is a **compile error** — there is no string fallback.
29
+ */
30
+
31
+ /**
32
+ * Global registry mapping action identifiers to their payload types.
33
+ *
34
+ * Extend this interface via declaration merging to register your application's
35
+ * actions and gain full type safety in `onAction` and `dispatchAction`.
36
+ *
37
+ * Built-in system actions are declared here. Application-level actions should
38
+ * be declared in a dedicated `action-record.ts` file within each feature package.
39
+ *
40
+ * @example — registering actions in a feature package
41
+ * ```ts
42
+ * // pkg/my-feature/src/action-record.ts
43
+ * declare module '@alwatr/action' {
44
+ * interface ActionRecord {
45
+ * 'open-drawer': string;
46
+ * 'add-to-cart': {productId: number; qty: number};
47
+ * 'logout': void;
48
+ * }
49
+ * }
50
+ * ```
51
+ */
52
+ export interface ActionRecord {
53
+ /**
54
+ * Dispatched by `dispatchPageId()` when the page identity is read from the
55
+ * `page-id` HTML attribute. Payload is the page identifier string.
56
+ *
57
+ * @example
58
+ * ```html
59
+ * <body page-id="home">…</body>
60
+ * ```
61
+ * ```ts
62
+ * onAction('page-ready', (pageId) => router.setPage(pageId));
63
+ * ```
64
+ */
65
+ 'page-ready': string;
66
+ }