@excom/event-handler 0.1.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.
Files changed (52) hide show
  1. package/.rush/temp/chunked-rush-logs/event-handler.apply-exports.chunks.jsonl +1 -0
  2. package/.rush/temp/chunked-rush-logs/event-handler.build_docs.chunks.jsonl +1 -0
  3. package/.rush/temp/chunked-rush-logs/event-handler.build_package-metas.chunks.jsonl +1 -0
  4. package/.rush/temp/operation/apply-exports/all.log +1 -0
  5. package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
  6. package/.rush/temp/operation/apply-exports/state.json +3 -0
  7. package/.rush/temp/operation/build_docs/all.log +1 -0
  8. package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
  9. package/.rush/temp/operation/build_docs/state.json +3 -0
  10. package/.rush/temp/operation/build_package-metas/all.log +1 -0
  11. package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
  12. package/.rush/temp/operation/build_package-metas/state.json +3 -0
  13. package/.rush/temp/shrinkwrap-deps.json +3 -0
  14. package/config/rig.json +5 -0
  15. package/event-handler.ts +178 -0
  16. package/index.css +5 -0
  17. package/index.ts +17 -0
  18. package/package.json +44 -0
  19. package/rush-logs/event-handler.apply-exports.cache.log +1 -0
  20. package/rush-logs/event-handler.apply-exports.log +1 -0
  21. package/rush-logs/event-handler.build_docs.cache.log +1 -0
  22. package/rush-logs/event-handler.build_docs.log +1 -0
  23. package/rush-logs/event-handler.build_package-metas.cache.log +1 -0
  24. package/rush-logs/event-handler.build_package-metas.log +1 -0
  25. package/src/event-handler.css +19 -0
  26. package/support/custom-elements.json +245 -0
  27. package/support/demos/command-dialog.html +9 -0
  28. package/support/demos/debounce.html +21 -0
  29. package/support/demos/enter-close.html +13 -0
  30. package/support/demos/escape-close.html +16 -0
  31. package/support/demos/fire-event.html +13 -0
  32. package/support/demos/prevent-default.html +3 -0
  33. package/support/demos/retarget.html +14 -0
  34. package/support/demos/with-as.html +8 -0
  35. package/support/dist-docs/event-handler.md +277 -0
  36. package/support/docs/INTERNAL.md +1 -0
  37. package/support/docs/README.md +97 -0
  38. package/support/package-meta.json +249 -0
  39. package/support/tests/__snapshots__/debounce.view.test.ts.snap +23 -0
  40. package/support/tests/__snapshots__/fire-event.view.test.ts.snap +23 -0
  41. package/support/tests/__snapshots__/retarget.view.test.ts.snap +23 -0
  42. package/support/tests/command-dialog.view.test.ts +34 -0
  43. package/support/tests/debounce.view.test.ts +43 -0
  44. package/support/tests/enter-close.view.test.ts +35 -0
  45. package/support/tests/escape-close.view.test.ts +44 -0
  46. package/support/tests/event-handler.form-fallback.test.ts +49 -0
  47. package/support/tests/event-handler.test.ts +524 -0
  48. package/support/tests/fire-event.view.test.ts +38 -0
  49. package/support/tests/prevent-default.view.test.ts +21 -0
  50. package/support/tests/retarget.view.test.ts +38 -0
  51. package/support/tests/with-as.view.test.ts +31 -0
  52. package/tsconfig.json +5 -0
@@ -0,0 +1,13 @@
1
+ <div>
2
+ <section>
3
+ <event-handler fire-event="cart-add" detail-sku="sku-1">
4
+ Add to cart
5
+ </event-handler>
6
+ <output>Waiting…</output>
7
+ </section>
8
+ <quark-sheet>
9
+ @use "/demo-utils" as *;
10
+
11
+ section { @on cart-add (handle: setOutputFromDetail); }
12
+ </quark-sheet>
13
+ </div>
@@ -0,0 +1,3 @@
1
+ <event-handler prevent-default>
2
+ <a href="https://example.com">This native link does nothing</a>
3
+ </event-handler>
@@ -0,0 +1,14 @@
1
+ <div>
2
+ <output>Waiting…</output>
3
+ <event-handler target-ref="output:has(+ :scope)" fire-event="demo-ping"
4
+ detail-label="pong">
5
+ Ping previous output
6
+ </event-handler>
7
+ <quark-sheet>
8
+ @use "/demo-utils" as *;
9
+
10
+ output {
11
+ @on demo-ping (handle: setOutputFromDetail);
12
+ }
13
+ </quark-sheet>
14
+ </div>
@@ -0,0 +1,8 @@
1
+ <section>
2
+ <event-handler target-ref=":scope + content-drawer" command-name="--toggle">
3
+ Toggle drawer
4
+ </event-handler>
5
+ <content-drawer>
6
+ <h2>Open!</h2>
7
+ </content-drawer>
8
+ </section>
@@ -0,0 +1,277 @@
1
+ # event-handler
2
+
3
+ Stitch behaviors of your app together, primarily through events.
4
+
5
+
6
+ ```html
7
+ <section>
8
+ <event-handler target-ref=":scope + content-drawer" command-name="--toggle">
9
+ Toggle drawer
10
+ </event-handler>
11
+ <content-drawer>
12
+ <h2>Open!</h2>
13
+ </content-drawer>
14
+ </section>
15
+ ```
16
+
17
+
18
+ ## Features
19
+
20
+ - **Fire custom events** Map any input event, such as clicks, or lifecycles to custom output events
21
+ - **Commands** `command-name` invokes the HTML Command API — built-in verbs (`show-modal`) and the `--verb` commands elements accept (`--submit`, `--close`)
22
+ - **Retarget** Aim events/commands at any selector (`target-ref`) — where a `<button commandfor>` needs an id
23
+ - **Global / host listening** Listen to events globally or on any element. Helpful for: escape to dismiss, global shortcuts, etc.
24
+ - **Keycode filter** Escape to dismiss, Shift+K shortcuts — keys / modifier chords
25
+ - **Listen filters** Debounce, selector, and pathname gates
26
+ - **Custom Event Payloads** `detail-*` attributes and forms convert to `event.detail` JSON
27
+
28
+ > In a view that already has a `<quark-sheet>`, the same wiring is a rule: `@on click (target: "[data-add]") { @dispatch cart-add (detail: (sku: attr("data-sku"))); }` — `@on` options cover `selector-filter` / `keycode-filter` / `is-debounced` / `host-ref`, and `@dispatch` / `@command` cover `fire-event` / `target-ref` / `form-ref` / `command-name`. Keep `<event-handler>` for markup without a sheet.
29
+
30
+ ## Installation
31
+
32
+
33
+ `@excom/event-handler` v0.1.0
34
+
35
+ ```bash
36
+ pnpm add @excom/event-handler
37
+ ```
38
+
39
+ ```bash
40
+ npm install @excom/event-handler
41
+ ```
42
+
43
+ ```bash
44
+ yarn add @excom/event-handler
45
+ ```
46
+
47
+ ### Import
48
+
49
+ ```ts
50
+ import "@excom/event-handler";
51
+ ```
52
+
53
+
54
+
55
+ ## Usage
56
+
57
+ Listen (default `click`), then either `fire-event` or `command-name`.
58
+
59
+ ```html
60
+ <event-handler fire-event="cart-add">
61
+ Add to cart
62
+ </event-handler>
63
+ ```
64
+
65
+ Or, for example: a successful form submit re-fetches the related data by invoking the provider's `--fetch` command.
66
+ ```html
67
+ <event-handler listen-for="super-form-success" target-ref="#fetch-todos" command-name="--fetch">
68
+ <super-form>
69
+ <form>
70
+ <!-- form to create a new todo -->
71
+ </form>
72
+ </super-form>
73
+ </event-handler>
74
+ ```
75
+
76
+ In a Nucleus Stack application, this will be one of the most heavily used elements. It is the primary method of linking a functional cause and effect.
77
+
78
+ ### API Reference
79
+
80
+
81
+ #### Attributes
82
+
83
+ | Name | Surface | Type | Default | Values | Description | Inherited from |
84
+ | --- | --- | --- | --- | --- | --- | --- |
85
+ | `target-ref` | option | `string` | | `<CSS Selector>` | Where outgoing events / mutations / commands apply. Unset = this element. Supports `:scope` for relative targeting (e.g. `:scope ~ dialog`). | |
86
+ | `fire-event` | option | `tokenlist` | | `<EventName>…` | Space-separated event names to dispatch on the target. Each name gets the same merged `detail`. Ignored when `mutate-target` is set. Pair with `detail-*` attributes for static detail fields. | |
87
+ | `command-name` | option | `tokenlist` | | `<command>…` | Space-separated commands to invoke on the target: custom `--verb` commands (`--submit`, `--close`) dispatch a `command` event, as a `<button command commandfor>` would; built-in verbs (`show-modal`, `close`, `toggle-popover`) run through the platform. | |
88
+ | `not-bubbles` | option | `boolean` | | | Outgoing events use `bubbles: false` (default: bubble). | |
89
+ | `not-cancelable` | option | `boolean` | | | Outgoing events use `cancelable: false` (default: cancelable). | |
90
+ | `not-composed` | option | `boolean` | | | Outgoing events use `composed: false` (default: composed / cross shadow roots). | |
91
+ | `form-ref` | option | `string` | | `<CSS Selector>` | `<form>` whose fields merge into event `detail`, or become attributes when `mutate-target` is set. | |
92
+ | `mutate-target` | option | `boolean` | | | @deprecated Write attributes on the target instead of firing events (sources: `attr-*` on this element plus `form-ref` fields). Use a Quark `@on <event> { … }` block instead — it writes State from the event without one element mutating another. Kept for compatibility; will be removed in a future major. | |
93
+ | `host-ref` | option | `string` | | `<CSS Selector>` \| `"window"` \| `"document"` \| `"html"` \| `"body"` \| `"head"` | Listen on another element / `window` / `document` — e.g. Escape to dismiss a dialog from a global `keydown`. Defaults to `:scope`. Used with `listen-for`. Not compatible with `listen-for-lifecycle`. The selector MUST resolve when `host-ref` is set — it will not wait for a match to appear. | `@excom/listenable-element` |
94
+ | `listen-for` | option | `tokenlist` | | `<EventName>…` | Space-separated event names to listen for. Defaults to `click` when unset (and no lifecycle list is set). | `@excom/listenable-element` |
95
+ | `listen-for-lifecycle` | option | `tokenlist` | | `"connected"` \| `"disconnected"` \| `"adopted"` | Space-separated element lifecycles to handle. | `@excom/listenable-element` |
96
+ | `listen-once` | option | `boolean` | | | Handle each distinct event name / lifecycle at most once. | `@excom/listenable-element` |
97
+ | `selector-filter` | option | `string` | | `<CSS Selector>` | Only handle events whose `event.target` matches this CSS selector. Does not support `:scope` in the selector. | `@excom/listenable-element` |
98
+ | `keycode-filter` | option | `tokenlist` | | `<key` \| `mod+key>…` | Space-separated key filters (OR). Join modifiers with `+` (AND, any order): `shift+k tab` → Shift+K or Tab. Modifiers: `shift`, `alt`, `ctrl`/`control`, `meta`. Case-insensitive. | `@excom/listenable-element` |
99
+ | `pathname-filter` | option | `tokenlist` | | `<pathname>…` | Only handle when `location.pathname` is one of these values — route-aware behaviors without a separate router element. | `@excom/listenable-element` |
100
+ | `prevent-default` | option | `boolean` | | | Call `preventDefault()` on matched events (ignored for lifecycles). | `@excom/listenable-element` |
101
+ | `stop-propagation` | option | `boolean` | | | Call `stopPropagation()` on matched events (ignored for lifecycles). | `@excom/listenable-element` |
102
+ | `stop-immediate-propagation` | option | `boolean` | | | Call `stopImmediatePropagation()` on matched events (ignored for lifecycles). | `@excom/listenable-element` |
103
+ | `vibrate-ms` | option | `number` | `"20 (when attribute is present with no value)"` | | Vibrate on handle (`navigator.vibrate`). Empty / `0` uses a 20ms pulse. | `@excom/listenable-element` |
104
+ | `delay-ms` | option | `number` | | | Delay handling by this many milliseconds. | `@excom/listenable-element` |
105
+ | `is-debounced` | option | `boolean` | | | With `delay-ms`, coalesce bursts into one trailing call (debounce). | `@excom/listenable-element` |
106
+
107
+ #### CSS Classes
108
+
109
+ | Name | Description |
110
+ | --- | --- |
111
+ | `.unstyled` | Skip the pointer cursor from the listenable mixin. |
112
+
113
+ #### CSS Aliases
114
+
115
+ | Alias | Kind | Matches | Description |
116
+ | --- | --- | --- | --- |
117
+ | `:--event-handler` | element | `event-handler`, `.tag-event-handler` | |
118
+
119
+
120
+
121
+ ### Examples
122
+
123
+ #### Fire a custom event
124
+
125
+ Click dispatches `cart-add` event with `{ sku: "sku-1" }` in `event.detail`.
126
+
127
+
128
+ ```html
129
+ <div>
130
+ <section>
131
+ <event-handler fire-event="cart-add" detail-sku="sku-1">
132
+ Add to cart
133
+ </event-handler>
134
+ <output>Waiting…</output>
135
+ </section>
136
+ <quark-sheet>
137
+ @use "/demo-utils" as *;
138
+
139
+ section { @on cart-add (handle: setOutputFromDetail); }
140
+ </quark-sheet>
141
+ </div>
142
+ ```
143
+
144
+
145
+ #### Open a dialog
146
+
147
+ `command-name` invokes the HTML Command API — here the built-in `show-modal` opens the sibling `<dialog>`. Custom commands (`command-name="--close"`) reach any element that handles them, such as `<content-drawer>`, through a relative `target-ref`.
148
+
149
+
150
+ ```html
151
+ <section>
152
+ <event-handler target-ref=":scope + dialog" command-name="show-modal">
153
+ Open dialog
154
+ </event-handler>
155
+ <dialog>
156
+ Hello!
157
+ <form method="dialog"><button>Close</button></form>
158
+ </dialog>
159
+ </section>
160
+ ```
161
+
162
+
163
+ #### Close on Enter
164
+
165
+ `keycode-filter` gates keyboard handling — here Enter, focused in the
166
+ `input`, invokes the drawer's `--close` command (local / bubbling events only).
167
+
168
+
169
+ ```html
170
+ <section>
171
+ <event-handler target-ref=":scope + content-drawer" command-name="--open">
172
+ Open drawer
173
+ </event-handler>
174
+ <content-drawer id="demo-enter-close-drawer" is-open>
175
+ <event-handler listen-for="keydown" keycode-filter="enter" target-ref="#demo-enter-close-drawer" command-name="--close">
176
+ <label>
177
+ Favorite color
178
+ <input type="text" placeholder="Type something, then press Enter to close drawer" autofocus />
179
+ </label>
180
+ </event-handler>
181
+ </content-drawer>
182
+ </section>
183
+ ```
184
+
185
+
186
+ #### Escape to dismiss (global)
187
+
188
+ `host-ref="window"` listens for `keydown` on the window — Escape closes
189
+ the dialog even when focus is outside it.
190
+
191
+
192
+ ```html
193
+ <section>
194
+ <event-handler target-ref=":scope ~ dialog" command-name="show-modal">
195
+ Open dialog
196
+ </event-handler>
197
+ <event-handler
198
+ host-ref="window"
199
+ listen-for="keydown"
200
+ keycode-filter="escape"
201
+ target-ref=":scope ~ dialog"
202
+ command-name="close"
203
+ ></event-handler>
204
+ <dialog>
205
+ Press Escape anywhere to close.
206
+ <form method="dialog"><button>Close</button></form>
207
+ </dialog>
208
+ </section>
209
+ ```
210
+
211
+
212
+ #### Cancel a link click
213
+
214
+ `prevent-default` cancels the native action — here the wrapped `<a>` never navigates.
215
+ Use `stop-propagation` / `stop-immediate-propagation` the same way when you need to stop bubbling.
216
+
217
+
218
+ ```html
219
+ <event-handler prevent-default>
220
+ <a href="https://example.com">This native link does nothing</a>
221
+ </event-handler>
222
+ ```
223
+
224
+
225
+ #### Retarget
226
+
227
+ `target-ref` aims the outgoing event at another element — useful when the target sits outside of the bubble path.
228
+
229
+
230
+ ```html
231
+ <div>
232
+ <output>Waiting…</output>
233
+ <event-handler target-ref="output:has(+ :scope)" fire-event="demo-ping"
234
+ detail-label="pong">
235
+ Ping previous output
236
+ </event-handler>
237
+ <quark-sheet>
238
+ @use "/demo-utils" as *;
239
+
240
+ output {
241
+ @on demo-ping (handle: setOutputFromDetail);
242
+ }
243
+ </quark-sheet>
244
+ </div>
245
+ ```
246
+
247
+
248
+ #### Debounced input / form data
249
+
250
+ Inherited `delay-ms` + `is-debounced` coalesce noisy `input` into one `search-query` event. Demo below is debounced every 200ms.
251
+
252
+ Also demonstrated is form conversion into JSON: `text` -> `string`, `checkbox` -> `boolean`, etc. Including the data structure: `detail.strict` -> `{detail: {strict}}`.
253
+
254
+
255
+ ```html
256
+ <div>
257
+ <section>
258
+ <event-handler listen-for="input" delay-ms="200" is-debounced fire-event="search-query" form-ref=":scope form">
259
+ <form>
260
+ <label>
261
+ <input name="detail.q" type="search" placeholder="Type to search…" />
262
+ </label>
263
+ <label>
264
+ Strict search
265
+ <input name="detail.strict" type="checkbox" role="switch" />
266
+ </label>
267
+ </form>
268
+ </event-handler>
269
+ <output>Waiting…</output>
270
+ </section>
271
+ <quark-sheet>
272
+ @use "/demo-utils" as *;
273
+
274
+ section { @on search-query (handle: setOutputFromDetail); }
275
+ </quark-sheet>
276
+ </div>
277
+ ```
@@ -0,0 +1 @@
1
+ - `mutate-target` is deprecated (2026-09-11): Quark's `@on <event> { … }` block covers event → attribute writes without one element mutating another. Still functional and tested; every in-repo usage was migrated. Remove in the next major.
@@ -0,0 +1,97 @@
1
+ # event-handler
2
+
3
+ Stitch behaviors of your app together, primarily through events.
4
+
5
+ <include-content data-demo="with-as"></include-content>
6
+
7
+ ## Features
8
+
9
+ - **Fire custom events** Map any input event, such as clicks, or lifecycles to custom output events
10
+ - **Commands** `command-name` invokes the HTML Command API — built-in verbs (`show-modal`) and the `--verb` commands elements accept (`--submit`, `--close`)
11
+ - **Retarget** Aim events/commands at any selector (`target-ref`) — where a `<button commandfor>` needs an id
12
+ - **Global / host listening** Listen to events globally or on any element. Helpful for: escape to dismiss, global shortcuts, etc.
13
+ - **Keycode filter** Escape to dismiss, Shift+K shortcuts — keys / modifier chords
14
+ - **Listen filters** Debounce, selector, and pathname gates
15
+ - **Custom Event Payloads** `detail-*` attributes and forms convert to `event.detail` JSON
16
+
17
+ > In a view that already has a `<quark-sheet>`, the same wiring is a rule: `@on click (target: "[data-add]") { @dispatch cart-add (detail: (sku: attr("data-sku"))); }` — `@on` options cover `selector-filter` / `keycode-filter` / `is-debounced` / `host-ref`, and `@dispatch` / `@command` cover `fire-event` / `target-ref` / `form-ref` / `command-name`. Keep `<event-handler>` for markup without a sheet.
18
+
19
+ ## Installation
20
+
21
+ <include-content is-active template-ref="/views/install-section/install-section.html"></include-content>
22
+
23
+ ## Usage
24
+
25
+ Listen (default `click`), then either `fire-event` or `command-name`.
26
+
27
+ ```html
28
+ <event-handler fire-event="cart-add">
29
+ Add to cart
30
+ </event-handler>
31
+ ```
32
+
33
+ Or, for example: a successful form submit re-fetches the related data by invoking the provider's `--fetch` command.
34
+ ```html
35
+ <event-handler listen-for="super-form-success" target-ref="#fetch-todos" command-name="--fetch">
36
+ <super-form>
37
+ <form>
38
+ <!-- form to create a new todo -->
39
+ </form>
40
+ </super-form>
41
+ </event-handler>
42
+ ```
43
+
44
+ In a Nucleus Stack application, this will be one of the most heavily used elements. It is the primary method of linking a functional cause and effect.
45
+
46
+ ### API Reference
47
+
48
+ <include-content is-active template-ref="/views/api-reference/api-reference.html"></include-content>
49
+
50
+ ### Examples
51
+
52
+ #### Fire a custom event
53
+
54
+ Click dispatches `cart-add` event with `{ sku: "sku-1" }` in `event.detail`.
55
+
56
+ <include-content data-demo="fire-event"></include-content>
57
+
58
+ #### Open a dialog
59
+
60
+ `command-name` invokes the HTML Command API — here the built-in `show-modal` opens the sibling `<dialog>`. Custom commands (`command-name="--close"`) reach any element that handles them, such as `<content-drawer>`, through a relative `target-ref`.
61
+
62
+ <include-content data-demo="command-dialog"></include-content>
63
+
64
+ #### Close on Enter
65
+
66
+ `keycode-filter` gates keyboard handling — here Enter, focused in the
67
+ `input`, invokes the drawer's `--close` command (local / bubbling events only).
68
+
69
+ <include-content data-demo="enter-close"></include-content>
70
+
71
+ #### Escape to dismiss (global)
72
+
73
+ `host-ref="window"` listens for `keydown` on the window — Escape closes
74
+ the dialog even when focus is outside it.
75
+
76
+ <include-content data-demo="escape-close"></include-content>
77
+
78
+ #### Cancel a link click
79
+
80
+ `prevent-default` cancels the native action — here the wrapped `<a>` never navigates.
81
+ Use `stop-propagation` / `stop-immediate-propagation` the same way when you need to stop bubbling.
82
+
83
+ <include-content data-demo="prevent-default"></include-content>
84
+
85
+ #### Retarget
86
+
87
+ `target-ref` aims the outgoing event at another element — useful when the target sits outside of the bubble path.
88
+
89
+ <include-content data-demo="retarget"></include-content>
90
+
91
+ #### Debounced input / form data
92
+
93
+ Inherited `delay-ms` + `is-debounced` coalesce noisy `input` into one `search-query` event. Demo below is debounced every 200ms.
94
+
95
+ Also demonstrated is form conversion into JSON: `text` -> `string`, `checkbox` -> `boolean`, etc. Including the data structure: `detail.strict` -> `{detail: {strict}}`.
96
+
97
+ <include-content data-demo="debounce"></include-content>