@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/src/registry.ts CHANGED
@@ -3,15 +3,18 @@
3
3
  /**
4
4
  * A modifier handler used in `on-action` attribute syntax.
5
5
  *
6
- * Called with an `ActionContext` as `this` and the triggering DOM `event`.
7
- * Return `true` (or any truthy value) to allow the action to proceed,
8
- * or `false` to cancel the dispatch.
6
+ * Receives the triggering DOM `event` and the `element` that owns the
7
+ * `on-action` attribute. Return `true` (or any truthy value) to allow the
8
+ * action to proceed, or `false` to cancel the dispatch.
9
+ *
10
+ * Using explicit parameters instead of `this` binding makes handlers
11
+ * compatible with arrow functions and easier to test in isolation.
9
12
  *
10
13
  * @example
11
14
  * ```ts
12
15
  * // A modifier that only allows the action when the element is not disabled
13
- * const notDisabledHandler: ModifierHandler = function () {
14
- * return !(this.element as HTMLButtonElement).disabled;
16
+ * const notDisabledHandler: ModifierHandler = (_event, element) => {
17
+ * return !(element as HTMLButtonElement).disabled;
15
18
  * };
16
19
  * ```
17
20
  */
@@ -20,15 +23,18 @@ export type ModifierHandler = (event: Event, element: HTMLElement) => boolean;
20
23
  /**
21
24
  * A payload resolver used in `on-action` attribute syntax.
22
25
  *
23
- * Called with an `ActionContext` as `this` and the triggering DOM `event`
24
- * at dispatch time. The return value becomes the `actionPayload` passed to
25
- * `onAction` subscribers. Use this to compute dynamic payloads from DOM state.
26
+ * Receives the triggering DOM `event` and the `element` that owns the
27
+ * `on-action` attribute. The return value becomes the `actionPayload` passed
28
+ * to `onAction` subscribers. Use this to compute dynamic payloads from DOM state.
29
+ *
30
+ * Using explicit parameters instead of `this` binding makes resolvers
31
+ * compatible with arrow functions and easier to test in isolation.
26
32
  *
27
33
  * @example
28
34
  * ```ts
29
35
  * // A resolver that returns the element's dataset id
30
- * const dataIdResolver: PayloadResolver = function () {
31
- * return (this.element as HTMLElement).dataset.id ?? null;
36
+ * const dataIdResolver: PayloadResolver = (_event, element) => {
37
+ * return (element as HTMLElement).dataset.id ?? null;
32
38
  * };
33
39
  * ```
34
40
  */
@@ -39,8 +45,8 @@ export type PayloadResolver = (event: Event, element: HTMLElement) => unknown;
39
45
  /**
40
46
  * Registry of all named modifier handlers.
41
47
  *
42
- * Keys are modifier names used in the `on-action` attribute syntax
43
- * (e.g. `click.prevent->action-id`). Values are `ModifierHandler` functions.
48
+ * Keys are modifier names used in the `on-<eventType>` attribute syntax
49
+ * (e.g. `on-click="action-id; prevent"`). Values are `ModifierHandler` functions.
44
50
  * Populated at module load with built-in modifiers; extended at runtime via
45
51
  * `registerModifier`.
46
52
  *
@@ -51,8 +57,8 @@ export const modifierRegistry = new Map<string, ModifierHandler>();
51
57
  /**
52
58
  * Registry of all named payload resolvers.
53
59
  *
54
- * Keys are resolver tokens used in the `on-action` attribute syntax
55
- * (e.g. `click->action-id:$value`). Values are `PayloadResolver` functions.
60
+ * Keys are resolver tokens used in the `on-<eventType>` attribute syntax
61
+ * (e.g. `on-input="search_query:$value"`). Values are `PayloadResolver` functions.
56
62
  * Populated at module load with built-in resolvers; extended at runtime via
57
63
  * `registerPayloadResolver`.
58
64
  *
@@ -68,7 +74,7 @@ export const payloadRegistry = new Map<string, PayloadResolver>();
68
74
  * Use it to suppress the browser's default behaviour (e.g. form submission,
69
75
  * link navigation, context menu).
70
76
  *
71
- * @example `<form on-action="submit.prevent->submit-form">`
77
+ * @example `<form on-submit="submit-form; prevent">`
72
78
  */
73
79
  modifierRegistry.set('prevent', (event) => {
74
80
  event.preventDefault();
@@ -85,9 +91,9 @@ modifierRegistry.set('prevent', (event) => {
85
91
  *
86
92
  * Pair with `.prevent` on `submit` events to avoid page reloads:
87
93
  *
88
- * @example `<form on-action="submit.prevent.validate->submit-form:$formdata" novalidate>`
94
+ * @example `<form on-submit="submit_form:$formdata; prevent,validate" novalidate>`
89
95
  */
90
- modifierRegistry.set('validate', function (_, element) {
96
+ modifierRegistry.set('validate', (_event, element) => {
91
97
  const form = element instanceof HTMLFormElement ? element : element.closest('form');
92
98
  if (!form) return false;
93
99
  return form.checkValidity();
@@ -101,9 +107,9 @@ modifierRegistry.set('validate', function (_, element) {
101
107
  * Works with any element that exposes a `value` property: `<input>`,
102
108
  * `<textarea>`, `<select>`. Returns `null` for elements without `.value`.
103
109
  *
104
- * @example `<input on-action="input->search-query:$value" />`
110
+ * @example `<input on-input="search_query:$value" />`
105
111
  */
106
- payloadRegistry.set('$value', function (_, element) {
112
+ payloadRegistry.set('$value', (_event, element) => {
107
113
  return 'value' in element ? (element as {value: unknown}).value : null;
108
114
  });
109
115
 
@@ -114,14 +120,31 @@ payloadRegistry.set('$value', function (_, element) {
114
120
  * Looks for a `<form>` ancestor (or the element itself). Returns `null` when no
115
121
  * form is found.
116
122
  *
117
- * @example `<form on-action="submit.prevent.validate->submit-form:$formdata">`
123
+ * @example `<form on-submit="submit_form:$formdata; prevent,validate">`
118
124
  * ```ts
119
- * onAction<Record<string, FormDataEntryValue>>('submit-form', (data) => {
125
+ * onAction('submit_form', (data) => {
120
126
  * console.log(data); // {username: 'ali', password: '…'}
121
127
  * });
122
128
  * ```
123
129
  */
124
- payloadRegistry.set('$formdata', function (_, element) {
130
+ payloadRegistry.set('$formdata', (_event, element) => {
125
131
  const form = element instanceof HTMLFormElement ? element : element.closest('form');
126
- return form ? Object.fromEntries(new FormData(form).entries()) : null;
132
+ return form ? Object.fromEntries(new FormData(form)) : null;
133
+ });
134
+
135
+ /**
136
+ * `$checked` — resolves to the `.checked` boolean property of a checkbox or radio input.
137
+ *
138
+ * Works with `<input type="checkbox">` and `<input type="radio">`.
139
+ * Returns `null` for elements that do not have a `checked` property.
140
+ *
141
+ * @example `<input type="checkbox" on-change="toggle_feature:$checked" />`
142
+ * ```ts
143
+ * onAction('toggle_feature', (isChecked) => {
144
+ * featureSignal.set(isChecked as boolean);
145
+ * });
146
+ * ```
147
+ */
148
+ payloadRegistry.set('$checked', (_event, element) => {
149
+ return 'checked' in element ? (element as HTMLInputElement).checked : null;
127
150
  });
@@ -1,3 +0,0 @@
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
@@ -1 +0,0 @@
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"}
package/src/page-ready.ts DELETED
@@ -1,31 +0,0 @@
1
- import {createLogger} from '@alwatr/logger';
2
- import {createChannelSignal} from '@alwatr/signal';
3
-
4
- const logger = createLogger('page-ready');
5
-
6
- const pageReadyChannel_ = createChannelSignal<Record<string, undefined>>({
7
- name: 'page-ready',
8
- });
9
-
10
- export function onPageReady<T extends string>(pageId: T, handler: () => void) {
11
- logger.logMethodArgs?.('onPageReady', {pageId});
12
- pageReadyChannel_.on(pageId, handler);
13
- }
14
-
15
- export function dispatchPageReady(): void {
16
- logger.logMethod?.('dispatchPageReady');
17
- const element = document.querySelector('[page-id]');
18
- if (!element) {
19
- logger.incident?.('dispatchPageReady', 'element_not_found');
20
- return;
21
- }
22
-
23
- const pageId = element.getAttribute('page-id')?.trim();
24
-
25
- if (!pageId) {
26
- logger.accident('dispatchPageReady', 'empty_page_id', {element});
27
- return;
28
- }
29
-
30
- pageReadyChannel_.dispatch(pageId);
31
- }