@excom/listenable-element 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.
- package/.rush/temp/chunked-rush-logs/listenable-element.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/listenable-element.build_docs.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/listenable-element.build_package-metas.chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/all.log +1 -0
- package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/state.json +3 -0
- package/.rush/temp/operation/build_docs/all.log +1 -0
- package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_docs/state.json +3 -0
- package/.rush/temp/operation/build_package-metas/all.log +1 -0
- package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_package-metas/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +4 -0
- package/config/rig.json +6 -0
- package/index.ts +282 -0
- package/package.json +45 -0
- package/rush-logs/listenable-element.apply-exports.cache.log +1 -0
- package/rush-logs/listenable-element.apply-exports.log +1 -0
- package/rush-logs/listenable-element.build_docs.cache.log +1 -0
- package/rush-logs/listenable-element.build_docs.log +1 -0
- package/rush-logs/listenable-element.build_package-metas.cache.log +1 -0
- package/rush-logs/listenable-element.build_package-metas.log +1 -0
- package/src/index.css +12 -0
- package/support/custom-elements.json +332 -0
- package/support/dist-docs/listenable-element.md +122 -0
- package/support/docs/README.md +82 -0
- package/support/package-meta.json +148 -0
- package/support/tests/listenable-element.matrix.test.ts +300 -0
- package/support/tests/listenable-element.test.ts +455 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0.0",
|
|
3
|
+
"modules": [
|
|
4
|
+
{
|
|
5
|
+
"kind": "javascript-module",
|
|
6
|
+
"path": "index.ts",
|
|
7
|
+
"declarations": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "mixin",
|
|
10
|
+
"name": "ListenableElement",
|
|
11
|
+
"summary": "Declarative event / lifecycle listening for Neutron elements.",
|
|
12
|
+
"description": "Composition base that wires event and lifecycle listening. Subclasses implement `actionHandler`. Used by `<spa-a>`, `<event-handler>`, and similar interactive elements.",
|
|
13
|
+
"attributes": [
|
|
14
|
+
{
|
|
15
|
+
"name": "host-ref",
|
|
16
|
+
"type": {
|
|
17
|
+
"text": "string"
|
|
18
|
+
},
|
|
19
|
+
"description": "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.",
|
|
20
|
+
"fieldName": "hostRef",
|
|
21
|
+
"values": [
|
|
22
|
+
"<CSS Selector>",
|
|
23
|
+
"window",
|
|
24
|
+
"document",
|
|
25
|
+
"html",
|
|
26
|
+
"body",
|
|
27
|
+
"head"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "listen-for",
|
|
32
|
+
"type": {
|
|
33
|
+
"text": "tokenlist"
|
|
34
|
+
},
|
|
35
|
+
"description": "Space-separated event names to listen for. Defaults to `click` when unset (and no lifecycle list is set).",
|
|
36
|
+
"fieldName": "listenFor",
|
|
37
|
+
"values": [
|
|
38
|
+
"<EventName>…"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "listen-for-lifecycle",
|
|
43
|
+
"type": {
|
|
44
|
+
"text": "tokenlist"
|
|
45
|
+
},
|
|
46
|
+
"description": "Space-separated element lifecycles to handle.",
|
|
47
|
+
"fieldName": "listenForLifecycle",
|
|
48
|
+
"values": [
|
|
49
|
+
"connected",
|
|
50
|
+
"disconnected",
|
|
51
|
+
"adopted"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "listen-once",
|
|
56
|
+
"type": {
|
|
57
|
+
"text": "boolean"
|
|
58
|
+
},
|
|
59
|
+
"description": "Handle each distinct event name / lifecycle at most once.",
|
|
60
|
+
"fieldName": "listenOnce"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "selector-filter",
|
|
64
|
+
"type": {
|
|
65
|
+
"text": "string"
|
|
66
|
+
},
|
|
67
|
+
"description": "Only handle events whose `event.target` matches this CSS selector. Does not support `:scope` in the selector.",
|
|
68
|
+
"fieldName": "selectorFilter",
|
|
69
|
+
"values": [
|
|
70
|
+
"<CSS Selector>"
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "keycode-filter",
|
|
75
|
+
"type": {
|
|
76
|
+
"text": "tokenlist"
|
|
77
|
+
},
|
|
78
|
+
"description": "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.",
|
|
79
|
+
"fieldName": "keycodeFilter",
|
|
80
|
+
"values": [
|
|
81
|
+
"<key",
|
|
82
|
+
"mod+key>…"
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "pathname-filter",
|
|
87
|
+
"type": {
|
|
88
|
+
"text": "tokenlist"
|
|
89
|
+
},
|
|
90
|
+
"description": "Only handle when `location.pathname` is one of these values — route-aware behaviors without a separate router element.",
|
|
91
|
+
"fieldName": "pathnameFilter",
|
|
92
|
+
"values": [
|
|
93
|
+
"<pathname>…"
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"name": "prevent-default",
|
|
98
|
+
"type": {
|
|
99
|
+
"text": "boolean"
|
|
100
|
+
},
|
|
101
|
+
"description": "Call `preventDefault()` on matched events (ignored for lifecycles).",
|
|
102
|
+
"fieldName": "preventDefault"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "stop-propagation",
|
|
106
|
+
"type": {
|
|
107
|
+
"text": "boolean"
|
|
108
|
+
},
|
|
109
|
+
"description": "Call `stopPropagation()` on matched events (ignored for lifecycles).",
|
|
110
|
+
"fieldName": "stopPropagation"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "stop-immediate-propagation",
|
|
114
|
+
"type": {
|
|
115
|
+
"text": "boolean"
|
|
116
|
+
},
|
|
117
|
+
"description": "Call `stopImmediatePropagation()` on matched events (ignored for lifecycles).",
|
|
118
|
+
"fieldName": "stopImmediatePropagation"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "vibrate-ms",
|
|
122
|
+
"type": {
|
|
123
|
+
"text": "number"
|
|
124
|
+
},
|
|
125
|
+
"description": "Vibrate on handle (`navigator.vibrate`). Empty / `0` uses a 20ms pulse.",
|
|
126
|
+
"fieldName": "vibrateMs",
|
|
127
|
+
"default": "20 (when attribute is present with no value)"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"name": "delay-ms",
|
|
131
|
+
"type": {
|
|
132
|
+
"text": "number"
|
|
133
|
+
},
|
|
134
|
+
"description": "Delay handling by this many milliseconds.",
|
|
135
|
+
"fieldName": "delayMs"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"name": "is-debounced",
|
|
139
|
+
"type": {
|
|
140
|
+
"text": "boolean"
|
|
141
|
+
},
|
|
142
|
+
"description": "With `delay-ms`, coalesce bursts into one trailing call (debounce).",
|
|
143
|
+
"fieldName": "isDebounced"
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
"members": [
|
|
147
|
+
{
|
|
148
|
+
"kind": "field",
|
|
149
|
+
"name": "hostRef",
|
|
150
|
+
"type": {
|
|
151
|
+
"text": "string"
|
|
152
|
+
},
|
|
153
|
+
"privacy": "public",
|
|
154
|
+
"readonly": false,
|
|
155
|
+
"description": "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.",
|
|
156
|
+
"_neutron": {
|
|
157
|
+
"surface": "option"
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"kind": "field",
|
|
162
|
+
"name": "listenFor",
|
|
163
|
+
"type": {
|
|
164
|
+
"text": "tokenlist"
|
|
165
|
+
},
|
|
166
|
+
"privacy": "public",
|
|
167
|
+
"readonly": false,
|
|
168
|
+
"description": "Space-separated event names to listen for. Defaults to `click` when unset (and no lifecycle list is set).",
|
|
169
|
+
"_neutron": {
|
|
170
|
+
"surface": "option"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"kind": "field",
|
|
175
|
+
"name": "listenForLifecycle",
|
|
176
|
+
"type": {
|
|
177
|
+
"text": "tokenlist"
|
|
178
|
+
},
|
|
179
|
+
"privacy": "public",
|
|
180
|
+
"readonly": false,
|
|
181
|
+
"description": "Space-separated element lifecycles to handle.",
|
|
182
|
+
"_neutron": {
|
|
183
|
+
"surface": "option"
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"kind": "field",
|
|
188
|
+
"name": "listenOnce",
|
|
189
|
+
"type": {
|
|
190
|
+
"text": "boolean"
|
|
191
|
+
},
|
|
192
|
+
"privacy": "public",
|
|
193
|
+
"readonly": false,
|
|
194
|
+
"description": "Handle each distinct event name / lifecycle at most once.",
|
|
195
|
+
"_neutron": {
|
|
196
|
+
"surface": "option"
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"kind": "field",
|
|
201
|
+
"name": "selectorFilter",
|
|
202
|
+
"type": {
|
|
203
|
+
"text": "string"
|
|
204
|
+
},
|
|
205
|
+
"privacy": "public",
|
|
206
|
+
"readonly": false,
|
|
207
|
+
"description": "Only handle events whose `event.target` matches this CSS selector. Does not support `:scope` in the selector.",
|
|
208
|
+
"_neutron": {
|
|
209
|
+
"surface": "option"
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"kind": "field",
|
|
214
|
+
"name": "keycodeFilter",
|
|
215
|
+
"type": {
|
|
216
|
+
"text": "tokenlist"
|
|
217
|
+
},
|
|
218
|
+
"privacy": "public",
|
|
219
|
+
"readonly": false,
|
|
220
|
+
"description": "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.",
|
|
221
|
+
"_neutron": {
|
|
222
|
+
"surface": "option"
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"kind": "field",
|
|
227
|
+
"name": "pathnameFilter",
|
|
228
|
+
"type": {
|
|
229
|
+
"text": "tokenlist"
|
|
230
|
+
},
|
|
231
|
+
"privacy": "public",
|
|
232
|
+
"readonly": false,
|
|
233
|
+
"description": "Only handle when `location.pathname` is one of these values — route-aware behaviors without a separate router element.",
|
|
234
|
+
"_neutron": {
|
|
235
|
+
"surface": "option"
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"kind": "field",
|
|
240
|
+
"name": "preventDefault",
|
|
241
|
+
"type": {
|
|
242
|
+
"text": "boolean"
|
|
243
|
+
},
|
|
244
|
+
"privacy": "public",
|
|
245
|
+
"readonly": false,
|
|
246
|
+
"description": "Call `preventDefault()` on matched events (ignored for lifecycles).",
|
|
247
|
+
"_neutron": {
|
|
248
|
+
"surface": "option"
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"kind": "field",
|
|
253
|
+
"name": "stopPropagation",
|
|
254
|
+
"type": {
|
|
255
|
+
"text": "boolean"
|
|
256
|
+
},
|
|
257
|
+
"privacy": "public",
|
|
258
|
+
"readonly": false,
|
|
259
|
+
"description": "Call `stopPropagation()` on matched events (ignored for lifecycles).",
|
|
260
|
+
"_neutron": {
|
|
261
|
+
"surface": "option"
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"kind": "field",
|
|
266
|
+
"name": "stopImmediatePropagation",
|
|
267
|
+
"type": {
|
|
268
|
+
"text": "boolean"
|
|
269
|
+
},
|
|
270
|
+
"privacy": "public",
|
|
271
|
+
"readonly": false,
|
|
272
|
+
"description": "Call `stopImmediatePropagation()` on matched events (ignored for lifecycles).",
|
|
273
|
+
"_neutron": {
|
|
274
|
+
"surface": "option"
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"kind": "field",
|
|
279
|
+
"name": "vibrateMs",
|
|
280
|
+
"type": {
|
|
281
|
+
"text": "number"
|
|
282
|
+
},
|
|
283
|
+
"privacy": "public",
|
|
284
|
+
"readonly": false,
|
|
285
|
+
"description": "Vibrate on handle (`navigator.vibrate`). Empty / `0` uses a 20ms pulse.",
|
|
286
|
+
"default": "20 (when attribute is present with no value)",
|
|
287
|
+
"_neutron": {
|
|
288
|
+
"surface": "option"
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"kind": "field",
|
|
293
|
+
"name": "delayMs",
|
|
294
|
+
"type": {
|
|
295
|
+
"text": "number"
|
|
296
|
+
},
|
|
297
|
+
"privacy": "public",
|
|
298
|
+
"readonly": false,
|
|
299
|
+
"description": "Delay handling by this many milliseconds.",
|
|
300
|
+
"_neutron": {
|
|
301
|
+
"surface": "option"
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"kind": "field",
|
|
306
|
+
"name": "isDebounced",
|
|
307
|
+
"type": {
|
|
308
|
+
"text": "boolean"
|
|
309
|
+
},
|
|
310
|
+
"privacy": "public",
|
|
311
|
+
"readonly": false,
|
|
312
|
+
"description": "With `delay-ms`, coalesce bursts into one trailing call (debounce).",
|
|
313
|
+
"_neutron": {
|
|
314
|
+
"surface": "option"
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
],
|
|
320
|
+
"exports": [
|
|
321
|
+
{
|
|
322
|
+
"kind": "js",
|
|
323
|
+
"name": "ListenableElement",
|
|
324
|
+
"declaration": {
|
|
325
|
+
"name": "ListenableElement",
|
|
326
|
+
"module": "index.ts"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
]
|
|
330
|
+
}
|
|
331
|
+
]
|
|
332
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# listenable-element
|
|
2
|
+
|
|
3
|
+
Declarative event and lifecycle listening for Neutron elements —
|
|
4
|
+
filter, debounce, vibrate, and hand off to your `actionHandler`.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- **Event / lifecycle hooks** Listen for DOM events or `connected` /
|
|
9
|
+
`disconnected` / `adopted`
|
|
10
|
+
- **Host retarget** `host-ref="window"` / `document` / any selector —
|
|
11
|
+
Escape to dismiss, shortcuts outside the bubble path
|
|
12
|
+
- **Target filters** Selector, keycode (`shift+k` chords), and pathname gates
|
|
13
|
+
- **Debounce / delay** Coalesce noisy input
|
|
14
|
+
- **Event hygiene** `prevent-default` / `stop-propagation` /
|
|
15
|
+
`stop-immediate-propagation`
|
|
16
|
+
- **Haptic pulse** Optional `vibrate-ms` on handle
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
`@excom/listenable-element` v0.1.0
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add @excom/listenable-element
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @excom/listenable-element
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
yarn add @excom/listenable-element
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Import
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { /* … */ } from "@excom/listenable-element";
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
Compose `ListenableElement` and implement `actionHandler`. Concrete
|
|
46
|
+
consumers include `<spa-a>` and `<event-handler>`.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { Neutron } from "@excom/neutron";
|
|
50
|
+
import { ListenableElement } from "@excom/listenable-element";
|
|
51
|
+
|
|
52
|
+
export const TapLog = Neutron.compose([
|
|
53
|
+
ListenableElement,
|
|
54
|
+
Neutron({ tag: "tap-log" }),
|
|
55
|
+
])
|
|
56
|
+
.defineMethods({
|
|
57
|
+
actionHandler: (_el, e) => {
|
|
58
|
+
console.log("handled", e.type);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
TapLog.define();
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
<tap-log listen-for="click keydown" keycode-filter="enter">
|
|
67
|
+
Tap or Enter
|
|
68
|
+
</tap-log>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`host-ref` moves listening off `:scope` — e.g. `host-ref="window"` for
|
|
72
|
+
global keydown. See `<event-handler>` for Escape-to-dismiss examples.
|
|
73
|
+
|
|
74
|
+
### API Reference
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
#### Attributes
|
|
78
|
+
|
|
79
|
+
| Name | Surface | Type | Default | Values | Description |
|
|
80
|
+
| --- | --- | --- | --- | --- | --- |
|
|
81
|
+
| `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. |
|
|
82
|
+
| `listen-for` | option | `tokenlist` | | `<EventName>…` | Space-separated event names to listen for. Defaults to `click` when unset (and no lifecycle list is set). |
|
|
83
|
+
| `listen-for-lifecycle` | option | `tokenlist` | | `"connected"` \| `"disconnected"` \| `"adopted"` | Space-separated element lifecycles to handle. |
|
|
84
|
+
| `listen-once` | option | `boolean` | | | Handle each distinct event name / lifecycle at most once. |
|
|
85
|
+
| `selector-filter` | option | `string` | | `<CSS Selector>` | Only handle events whose `event.target` matches this CSS selector. Does not support `:scope` in the selector. |
|
|
86
|
+
| `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. |
|
|
87
|
+
| `pathname-filter` | option | `tokenlist` | | `<pathname>…` | Only handle when `location.pathname` is one of these values — route-aware behaviors without a separate router element. |
|
|
88
|
+
| `prevent-default` | option | `boolean` | | | Call `preventDefault()` on matched events (ignored for lifecycles). |
|
|
89
|
+
| `stop-propagation` | option | `boolean` | | | Call `stopPropagation()` on matched events (ignored for lifecycles). |
|
|
90
|
+
| `stop-immediate-propagation` | option | `boolean` | | | Call `stopImmediatePropagation()` on matched events (ignored for lifecycles). |
|
|
91
|
+
| `vibrate-ms` | option | `number` | `"20 (when attribute is present with no value)"` | | Vibrate on handle (`navigator.vibrate`). Empty / `0` uses a 20ms pulse. |
|
|
92
|
+
| `delay-ms` | option | `number` | | | Delay handling by this many milliseconds. |
|
|
93
|
+
| `is-debounced` | option | `boolean` | | | With `delay-ms`, coalesce bursts into one trailing call (debounce). |
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
### Examples
|
|
98
|
+
|
|
99
|
+
#### Default click → navigate
|
|
100
|
+
|
|
101
|
+
`<spa-a>` inherits this base and defaults to click when `listen-for`
|
|
102
|
+
is unset:
|
|
103
|
+
|
|
104
|
+
```html
|
|
105
|
+
<spa-a route-href="/pricing">Pricing</spa-a>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### Filter & debounce
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
<event-handler
|
|
112
|
+
listen-for="input"
|
|
113
|
+
delay-ms="200"
|
|
114
|
+
is-debounced
|
|
115
|
+
fire-event="search-query"
|
|
116
|
+
>
|
|
117
|
+
<input name="q" />
|
|
118
|
+
</event-handler>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
See `<event-handler>` and `<spa-a>` package docs for more examples
|
|
122
|
+
built on this mixin.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# listenable-element
|
|
2
|
+
|
|
3
|
+
Declarative event and lifecycle listening for Neutron elements —
|
|
4
|
+
filter, debounce, vibrate, and hand off to your `actionHandler`.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- **Event / lifecycle hooks** Listen for DOM events or `connected` /
|
|
9
|
+
`disconnected` / `adopted`
|
|
10
|
+
- **Host retarget** `host-ref="window"` / `document` / any selector —
|
|
11
|
+
Escape to dismiss, shortcuts outside the bubble path
|
|
12
|
+
- **Target filters** Selector, keycode (`shift+k` chords), and pathname gates
|
|
13
|
+
- **Debounce / delay** Coalesce noisy input
|
|
14
|
+
- **Event hygiene** `prevent-default` / `stop-propagation` /
|
|
15
|
+
`stop-immediate-propagation`
|
|
16
|
+
- **Haptic pulse** Optional `vibrate-ms` on handle
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
<include-content is-active template-ref="/views/install-section/install-section.html"></include-content>
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Compose `ListenableElement` and implement `actionHandler`. Concrete
|
|
25
|
+
consumers include `<spa-a>` and `<event-handler>`.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { Neutron } from "@excom/neutron";
|
|
29
|
+
import { ListenableElement } from "@excom/listenable-element";
|
|
30
|
+
|
|
31
|
+
export const TapLog = Neutron.compose([
|
|
32
|
+
ListenableElement,
|
|
33
|
+
Neutron({ tag: "tap-log" }),
|
|
34
|
+
])
|
|
35
|
+
.defineMethods({
|
|
36
|
+
actionHandler: (_el, e) => {
|
|
37
|
+
console.log("handled", e.type);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
TapLog.define();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```html
|
|
45
|
+
<tap-log listen-for="click keydown" keycode-filter="enter">
|
|
46
|
+
Tap or Enter
|
|
47
|
+
</tap-log>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`host-ref` moves listening off `:scope` — e.g. `host-ref="window"` for
|
|
51
|
+
global keydown. See `<event-handler>` for Escape-to-dismiss examples.
|
|
52
|
+
|
|
53
|
+
### API Reference
|
|
54
|
+
|
|
55
|
+
<include-content is-active template-ref="/views/api-reference/api-reference.html"></include-content>
|
|
56
|
+
|
|
57
|
+
### Examples
|
|
58
|
+
|
|
59
|
+
#### Default click → navigate
|
|
60
|
+
|
|
61
|
+
`<spa-a>` inherits this base and defaults to click when `listen-for`
|
|
62
|
+
is unset:
|
|
63
|
+
|
|
64
|
+
```html
|
|
65
|
+
<spa-a route-href="/pricing">Pricing</spa-a>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### Filter & debounce
|
|
69
|
+
|
|
70
|
+
```html
|
|
71
|
+
<event-handler
|
|
72
|
+
listen-for="input"
|
|
73
|
+
delay-ms="200"
|
|
74
|
+
is-debounced
|
|
75
|
+
fire-event="search-query"
|
|
76
|
+
>
|
|
77
|
+
<input name="q" />
|
|
78
|
+
</event-handler>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
See `<event-handler>` and `<spa-a>` package docs for more examples
|
|
82
|
+
built on this mixin.
|