jssm 5.136.0 → 5.138.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 +7 -7
- package/custom-elements.json +377 -8
- package/dist/cdn/instance.js +419 -8
- package/dist/cdn/viz.js +1 -1
- package/dist/cli/fsl-render.cjs +1 -1
- package/dist/cli/fsl.cjs +1 -1
- package/dist/deno/README.md +7 -7
- package/dist/deno/jssm.js +1 -1
- package/dist/jssm.es5.cjs +1 -1
- package/dist/jssm.es5.iife.js +1 -1
- package/dist/jssm.es6.mjs +1 -1
- package/dist/jssm_viz.cjs +1 -1
- package/dist/jssm_viz.iife.cjs +1 -1
- package/dist/jssm_viz.mjs +1 -1
- package/dist/wc/instance.js +418 -7
- package/package.json +1 -1
package/dist/wc/instance.js
CHANGED
|
@@ -1,6 +1,208 @@
|
|
|
1
1
|
import { css, LitElement, html } from 'lit';
|
|
2
2
|
import { sm } from 'jssm';
|
|
3
3
|
|
|
4
|
+
const VALID_KINDS = new Set([
|
|
5
|
+
'hook',
|
|
6
|
+
'named',
|
|
7
|
+
'any transition',
|
|
8
|
+
'standard transition',
|
|
9
|
+
'main transition',
|
|
10
|
+
'forced transition',
|
|
11
|
+
'entry',
|
|
12
|
+
'exit',
|
|
13
|
+
'any action',
|
|
14
|
+
'global action',
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* Build a {@link JssmHookProxy} that wraps an arbitrary hook context object.
|
|
18
|
+
*
|
|
19
|
+
* The context shape varies by hook kind (`from`/`to`/`action` may be absent
|
|
20
|
+
* for transition-kind hooks), so this normalizes the shape via optional
|
|
21
|
+
* fields and exposes mutable `data` while keeping the rest read-only.
|
|
22
|
+
*
|
|
23
|
+
* The `machine` parameter is used only for `state()`, so unit tests can
|
|
24
|
+
* substitute any object with a `state(): unknown` method.
|
|
25
|
+
*
|
|
26
|
+
* @param ctx - Raw hook context passed by jssm.
|
|
27
|
+
* @param machine - The owning machine; used for the `state()` accessor.
|
|
28
|
+
* @returns A proxy object suitable for passing to a user handler.
|
|
29
|
+
*/
|
|
30
|
+
function make_hook_proxy(ctx, machine) {
|
|
31
|
+
return {
|
|
32
|
+
get data() {
|
|
33
|
+
return ctx.data;
|
|
34
|
+
},
|
|
35
|
+
set data(next) {
|
|
36
|
+
ctx.data = next;
|
|
37
|
+
},
|
|
38
|
+
get from() {
|
|
39
|
+
return ctx.from;
|
|
40
|
+
},
|
|
41
|
+
get to() {
|
|
42
|
+
return ctx.to;
|
|
43
|
+
},
|
|
44
|
+
get action() {
|
|
45
|
+
return ctx.action;
|
|
46
|
+
},
|
|
47
|
+
state() {
|
|
48
|
+
return String(machine.state());
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Compile a textContent body into a callable user handler.
|
|
54
|
+
*
|
|
55
|
+
* Uses dynamic function construction — the same primitive browsers use
|
|
56
|
+
* internally for `<a onclick="...">` and `setTimeout(stringBody, ms)`.
|
|
57
|
+
* Strict CSP without `'unsafe-eval'` blocks this and the call will throw;
|
|
58
|
+
* consumers should fall back to the `handler="name"` form there.
|
|
59
|
+
*
|
|
60
|
+
* Prepends a `//# sourceURL=` comment so devtools surface a meaningful name
|
|
61
|
+
* in stack traces instead of `anonymous`.
|
|
62
|
+
*
|
|
63
|
+
* @param body - Trimmed textContent of the `<jssm-hook>` element.
|
|
64
|
+
* @param debug_id - Identifier appended to the synthetic sourceURL.
|
|
65
|
+
* @returns The compiled handler.
|
|
66
|
+
*/
|
|
67
|
+
function compile_inline_body(body, debug_id) {
|
|
68
|
+
const annotated = `//# sourceURL=jssm-hook:${debug_id}\n${body}`;
|
|
69
|
+
const ctor = Function;
|
|
70
|
+
return new ctor('m', annotated);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolve a `handler="name"` attribute to a callable by consulting first the
|
|
74
|
+
* optional in-WC registry, then `globalThis[name]`. Throws a clear error if
|
|
75
|
+
* neither resolves.
|
|
76
|
+
*
|
|
77
|
+
* @param name - The handler name from the `handler=""` attribute.
|
|
78
|
+
* @param registry - Optional in-WC registry to consult first.
|
|
79
|
+
* @returns The resolved handler.
|
|
80
|
+
* @throws Error - If no callable of that name is found in either location.
|
|
81
|
+
*/
|
|
82
|
+
function resolve_named_handler(name, registry) {
|
|
83
|
+
if (registry !== undefined) {
|
|
84
|
+
const registered = registry.get(name);
|
|
85
|
+
if (registered !== undefined) {
|
|
86
|
+
return registered;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const global = globalThis[name];
|
|
90
|
+
if (typeof global === 'function') {
|
|
91
|
+
return global;
|
|
92
|
+
}
|
|
93
|
+
throw new Error(`<jssm-hook handler="${name}">: handler not found in registry or globalThis`);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Validate and normalize a `<jssm-hook kind="...">` value, defaulting to
|
|
97
|
+
* `"hook"` when the attribute is absent. Throws on unknown kinds rather
|
|
98
|
+
* than silently doing nothing later.
|
|
99
|
+
*
|
|
100
|
+
* @param raw - The raw attribute value, or null if not present.
|
|
101
|
+
* @returns The normalized {@link JssmHookKind}.
|
|
102
|
+
* @throws Error - On an unknown kind.
|
|
103
|
+
*/
|
|
104
|
+
function normalize_hook_kind(raw) {
|
|
105
|
+
if (raw === null || raw === undefined || raw === '') {
|
|
106
|
+
return 'hook';
|
|
107
|
+
}
|
|
108
|
+
if (!VALID_KINDS.has(raw)) {
|
|
109
|
+
throw new Error(`<jssm-hook kind="${raw}">: unknown hook kind (expected one of: ${[...VALID_KINDS].join(', ')})`);
|
|
110
|
+
}
|
|
111
|
+
return raw;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Parse a single `<jssm-hook>` element into a {@link JssmHookInstallSpec}.
|
|
115
|
+
*
|
|
116
|
+
* Validates the mutual-exclusion rule between `handler="name"` and inline
|
|
117
|
+
* body, defaults `kind` to `"hook"`, resolves named handlers against the
|
|
118
|
+
* optional registry then `globalThis`, and compiles inline bodies via
|
|
119
|
+
* dynamic function construction. Conditional-required attributes (e.g.
|
|
120
|
+
* `from`/`to` for `kind="hook"`) are NOT validated here — `set_hook` will
|
|
121
|
+
* throw with its own clear errors on missing pieces, which keeps the
|
|
122
|
+
* error surface single-sourced.
|
|
123
|
+
*
|
|
124
|
+
* @param el - The `<jssm-hook>` element to parse.
|
|
125
|
+
* @param debug_id - Identifier used in the inline body's sourceURL.
|
|
126
|
+
* @param registry - Optional in-WC registry of named handlers.
|
|
127
|
+
* @returns A {@link JssmHookInstallSpec} describing what to install.
|
|
128
|
+
* @throws Error - On mutual-exclusion violation, unknown kind, or unresolved name.
|
|
129
|
+
*/
|
|
130
|
+
function parse_hook_element(el, debug_id, registry) {
|
|
131
|
+
var _a, _b, _c, _d;
|
|
132
|
+
const handler_attr = el.getAttribute('handler');
|
|
133
|
+
const raw_text = el.textContent;
|
|
134
|
+
const body_text = (raw_text === null ? '' : raw_text).trim();
|
|
135
|
+
if (handler_attr !== null && body_text.length > 0) {
|
|
136
|
+
throw new Error('<jssm-hook>: specify handler="name" OR inline body, not both');
|
|
137
|
+
}
|
|
138
|
+
if (handler_attr === null && body_text.length === 0) {
|
|
139
|
+
throw new Error('<jssm-hook>: must specify either handler="name" attribute or an inline body');
|
|
140
|
+
}
|
|
141
|
+
const user_handler = handler_attr !== null
|
|
142
|
+
? resolve_named_handler(handler_attr, registry)
|
|
143
|
+
: compile_inline_body(body_text, debug_id);
|
|
144
|
+
const kind = normalize_hook_kind(el.getAttribute('kind'));
|
|
145
|
+
// Convert null → undefined so downstream descriptors omit absent keys.
|
|
146
|
+
const from = (_a = el.getAttribute('from')) !== null && _a !== void 0 ? _a : undefined;
|
|
147
|
+
const to = (_b = el.getAttribute('to')) !== null && _b !== void 0 ? _b : undefined;
|
|
148
|
+
const action = (_c = el.getAttribute('action')) !== null && _c !== void 0 ? _c : undefined;
|
|
149
|
+
const name = (_d = el.getAttribute('name')) !== null && _d !== void 0 ? _d : undefined;
|
|
150
|
+
return { kind, name, from, to, action, user_handler };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Wrap a {@link JssmHookUserHandler} so that jssm's native hook contract is
|
|
154
|
+
* satisfied: the user gets a friendly proxy, the proxy's mutated `data`
|
|
155
|
+
* becomes the `HookComplexResult.data`, and an explicit `false` return
|
|
156
|
+
* cancels the transition.
|
|
157
|
+
*
|
|
158
|
+
* Any non-`false` return — including `undefined`, `true`, or an arbitrary
|
|
159
|
+
* object — allows the transition. This matches the contract spelled out
|
|
160
|
+
* in the issue (#641): "return false cancels; anything else allows".
|
|
161
|
+
*
|
|
162
|
+
* @param spec - The parsed install spec carrying the user handler.
|
|
163
|
+
* @param machine - The owning machine; used by the proxy's `state()`.
|
|
164
|
+
* @returns A wrapped handler suitable for `set_hook`.
|
|
165
|
+
*/
|
|
166
|
+
function wrap_user_handler(spec, machine) {
|
|
167
|
+
const user = spec.user_handler;
|
|
168
|
+
return (ctx) => {
|
|
169
|
+
const proxy = make_hook_proxy(ctx, machine);
|
|
170
|
+
const result = user(proxy);
|
|
171
|
+
if (result === false) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
return { pass: true, data: proxy.data };
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Build the typed descriptor object passed to `machine.set_hook` (and later
|
|
179
|
+
* to `machine.remove_hook` for cleanup) from a parsed {@link JssmHookInstallSpec}
|
|
180
|
+
* and the wrapped handler.
|
|
181
|
+
*
|
|
182
|
+
* For kinds that need `from`/`to`/`action`, the descriptor includes those.
|
|
183
|
+
* Missing required keys produce `undefined` here; jssm's `set_hook` will
|
|
184
|
+
* surface the error with its own clear message so we don't duplicate
|
|
185
|
+
* validation.
|
|
186
|
+
*
|
|
187
|
+
* Return type is `unknown` because jssm's `HookDescription` is a
|
|
188
|
+
* discriminated union and our runtime-discriminator value can't be tracked
|
|
189
|
+
* by TypeScript across the build. The WC casts at the `set_hook` call site.
|
|
190
|
+
*
|
|
191
|
+
* @param spec - The parsed install spec.
|
|
192
|
+
* @param wrapped - The wrapped (friendly-proxy) handler from {@link wrap_user_handler}.
|
|
193
|
+
* @returns A descriptor object for `set_hook`/`remove_hook`.
|
|
194
|
+
*/
|
|
195
|
+
function build_hook_descriptor(spec, wrapped) {
|
|
196
|
+
const base = { kind: spec.kind, handler: wrapped };
|
|
197
|
+
if (spec.from !== undefined)
|
|
198
|
+
base.from = spec.from;
|
|
199
|
+
if (spec.to !== undefined)
|
|
200
|
+
base.to = spec.to;
|
|
201
|
+
if (spec.action !== undefined)
|
|
202
|
+
base.action = spec.action;
|
|
203
|
+
return base;
|
|
204
|
+
}
|
|
205
|
+
|
|
4
206
|
/**
|
|
5
207
|
* Resolve a `<jssm-instance>`'s FSL source from the three legal channels:
|
|
6
208
|
* the `fsl=""` attribute, a child `<script type="text/fsl">`, and the
|
|
@@ -116,6 +318,46 @@ class JssmInstance extends LitElement {
|
|
|
116
318
|
* connection.
|
|
117
319
|
*/
|
|
118
320
|
this._machine = undefined;
|
|
321
|
+
/**
|
|
322
|
+
* Per-instance registry of named hook handlers consulted before
|
|
323
|
+
* `globalThis` when resolving `<jssm-hook handler="name">`.
|
|
324
|
+
*
|
|
325
|
+
* Initialized to an empty `Map`; consumers may populate it before the
|
|
326
|
+
* element connects to provide handlers without polluting global scope —
|
|
327
|
+
* useful for module-scoped SPAs where strict CSP blocks inline-body hooks.
|
|
328
|
+
*
|
|
329
|
+
* @see {@link parse_hook_element}
|
|
330
|
+
*/
|
|
331
|
+
this.registry = new Map();
|
|
332
|
+
/**
|
|
333
|
+
* Descriptors for hooks this WC installed at connect time, used in
|
|
334
|
+
* `disconnectedCallback` to call `remove_hook` for each so the underlying
|
|
335
|
+
* machine doesn't leak handlers when the element is detached.
|
|
336
|
+
*
|
|
337
|
+
* Captured at install time because `remove_hook` matches by descriptor
|
|
338
|
+
* shape (not handler identity), and we need to record the wrapped handler
|
|
339
|
+
* we passed to `set_hook` to undo the registration cleanly. Stored as
|
|
340
|
+
* `unknown[]` and cast at the call site because jssm's `HookDescription`
|
|
341
|
+
* is a discriminated union whose discriminator is only known at runtime.
|
|
342
|
+
*/
|
|
343
|
+
this._installed_hooks = [];
|
|
344
|
+
/**
|
|
345
|
+
* Counter used to give each compiled inline-body hook a unique debug id
|
|
346
|
+
* for its `//# sourceURL=jssm-hook:N` annotation. Per-instance so that
|
|
347
|
+
* multiple `<jssm-instance>` elements on a page don't share numbering.
|
|
348
|
+
*/
|
|
349
|
+
this._hook_debug_counter = 0;
|
|
350
|
+
/**
|
|
351
|
+
* Records every DOM listener installed by `<jssm-action>` / `data-jssm-action`
|
|
352
|
+
* discovery so {@link disconnectedCallback} can remove each one with the
|
|
353
|
+
* same handler reference originally passed to `addEventListener`.
|
|
354
|
+
*
|
|
355
|
+
* Listeners installed via the dedicated `<jssm-action>` tag form may target
|
|
356
|
+
* elements outside the host (its `selector` is resolved against the host,
|
|
357
|
+
* but matching elements live in the document tree), so cleanup must be
|
|
358
|
+
* explicit — relying on the host's GC is not sufficient.
|
|
359
|
+
*/
|
|
360
|
+
this._action_listeners = [];
|
|
119
361
|
}
|
|
120
362
|
/**
|
|
121
363
|
* Raw machine accessor. Returns the owned {@link Machine} instance.
|
|
@@ -180,22 +422,191 @@ class JssmInstance extends LitElement {
|
|
|
180
422
|
this.requestUpdate();
|
|
181
423
|
// TODO #638: subscribe to machine.on('transition', ...) once available
|
|
182
424
|
// and dispatch DOM CustomEvents from this element.
|
|
183
|
-
//
|
|
425
|
+
// #641: <jssm-hook> declarative discovery.
|
|
426
|
+
this._install_declarative_hooks();
|
|
184
427
|
// TODO #643: <jssm-on> discovery happens here.
|
|
185
|
-
|
|
428
|
+
this._discover_jssm_actions();
|
|
186
429
|
// TODO #645: <jssm-bind> discovery happens here.
|
|
187
430
|
}
|
|
188
431
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
432
|
+
* Discover every direct-child `<jssm-hook>` element and install each
|
|
433
|
+
* against the owned machine. Handlers are wrapped with the friendly-proxy
|
|
434
|
+
* adapter that lets user code write `m.data = ...` and return `false` to
|
|
435
|
+
* cancel — see {@link make_hook_proxy} and the issue (#641) doc-comment
|
|
436
|
+
* for the full contract.
|
|
437
|
+
*
|
|
438
|
+
* Direct children only (the `:scope > jssm-hook` selector) so that nested
|
|
439
|
+
* `<jssm-instance>` elements don't have their child hooks installed on
|
|
440
|
+
* the outer machine.
|
|
441
|
+
*
|
|
442
|
+
* Tracks every installed descriptor in `_installed_hooks` so that
|
|
443
|
+
* `disconnectedCallback` can remove them on detach.
|
|
444
|
+
*
|
|
445
|
+
* @throws Error - On a malformed `<jssm-hook>` (mutual-exclusion violation,
|
|
446
|
+
* unknown kind, unresolved name, or jssm's own missing-key
|
|
447
|
+
* errors from `set_hook`).
|
|
448
|
+
*/
|
|
449
|
+
_install_declarative_hooks() {
|
|
450
|
+
const machine = this._machine;
|
|
451
|
+
const hook_els = this.querySelectorAll(':scope > jssm-hook');
|
|
452
|
+
for (const el of Array.from(hook_els)) {
|
|
453
|
+
const debug_id = `${this._hook_id_prefix()}${++this._hook_debug_counter}`;
|
|
454
|
+
const spec = parse_hook_element(el, debug_id, this.registry);
|
|
455
|
+
const wrapped = wrap_user_handler(spec, machine);
|
|
456
|
+
const desc = build_hook_descriptor(spec, wrapped);
|
|
457
|
+
// `desc` is shaped from runtime kind discrimination; jssm's typed
|
|
458
|
+
// `HookDescription` is a static discriminated union that TS can't
|
|
459
|
+
// unify with our runtime-built object, hence the cast.
|
|
460
|
+
machine.set_hook(desc);
|
|
461
|
+
this._installed_hooks.push(desc);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Prefix used in synthetic `//# sourceURL=jssm-hook:<prefix><n>` annotations
|
|
466
|
+
* for inline-body hooks compiled by this element. Includes the element's
|
|
467
|
+
* `id` when present so multi-instance pages can tell sources apart in
|
|
468
|
+
* devtools.
|
|
469
|
+
*/
|
|
470
|
+
_hook_id_prefix() {
|
|
471
|
+
const host_id = this.getAttribute('id');
|
|
472
|
+
return host_id !== null && host_id.length > 0 ? `${host_id}-` : '';
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Lifecycle hook. Removes every hook this WC installed via
|
|
476
|
+
* `<jssm-hook>` discovery so the underlying machine doesn't leak handlers
|
|
477
|
+
* when the element detaches. Called automatically by the browser; the
|
|
478
|
+
* machine itself is not destroyed (consumers can reuse it).
|
|
479
|
+
*
|
|
480
|
+
* Future tickets #638/#643/#645 will extend this to drop other
|
|
481
|
+
* subscriptions / listeners installed by their respective tags.
|
|
193
482
|
*/
|
|
194
483
|
disconnectedCallback() {
|
|
195
484
|
super.disconnectedCallback();
|
|
196
485
|
// TODO #638: unsubscribe from machine.on(...) handlers.
|
|
197
|
-
//
|
|
486
|
+
// #641: remove installed hooks.
|
|
487
|
+
if (this._machine !== undefined) {
|
|
488
|
+
const machine = this._machine;
|
|
489
|
+
for (const desc of this._installed_hooks) {
|
|
490
|
+
machine.remove_hook(desc);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
this._installed_hooks = [];
|
|
198
494
|
// TODO #643/#645: remove installed listeners / bindings.
|
|
495
|
+
// Remove every listener installed during `<jssm-action>` / `data-jssm-action`
|
|
496
|
+
// discovery. Using the original handler reference ensures `removeEventListener`
|
|
497
|
+
// actually unbinds — anonymous re-creation here would silently leak.
|
|
498
|
+
for (const entry of this._action_listeners) {
|
|
499
|
+
entry.target.removeEventListener(entry.event, entry.handler);
|
|
500
|
+
}
|
|
501
|
+
this._action_listeners = [];
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Wire DOM events to machine actions, using the two declarative forms from
|
|
505
|
+
* issue #640:
|
|
506
|
+
*
|
|
507
|
+
* 1. Inline attribute form: every descendant of the host carrying a
|
|
508
|
+
* `data-jssm-action="<name>"` attribute receives a listener on the
|
|
509
|
+
* event named by `data-jssm-event` (default `click`).
|
|
510
|
+
* 2. Dedicated tag form: each direct `<jssm-action>` child of the host
|
|
511
|
+
* supplies a CSS `selector` (scoped to the host), an `action`, and an
|
|
512
|
+
* optional `event` (default `click`); every matching descendant
|
|
513
|
+
* receives a listener configured by the tag's attributes.
|
|
514
|
+
*
|
|
515
|
+
* Both forms support optional `from-state` guards (dispatch only when the
|
|
516
|
+
* machine's current state matches), `from-property` data extraction (pass
|
|
517
|
+
* the source element's named property as the action's data argument), and
|
|
518
|
+
* `prevent-default` / `stop-propagation` modifiers.
|
|
519
|
+
*
|
|
520
|
+
* Every installed listener is recorded in {@link _action_listeners} so
|
|
521
|
+
* {@link disconnectedCallback} can detach them cleanly.
|
|
522
|
+
*/
|
|
523
|
+
_discover_jssm_actions() {
|
|
524
|
+
var _a, _b, _c, _d;
|
|
525
|
+
// Inline attribute form: `[data-jssm-action]` descendants. Per the
|
|
526
|
+
// ticket, we scan the host's light DOM (not the shadow tree, which is
|
|
527
|
+
// owned by us) and skip any element living inside a `<jssm-action>` tag
|
|
528
|
+
// — those tags are pure data markup, never the source of an event.
|
|
529
|
+
const inline_targets = Array.from(this.querySelectorAll('[data-jssm-action]')).filter(el => el.closest('jssm-action') === null);
|
|
530
|
+
for (const el of inline_targets) {
|
|
531
|
+
this._install_action_listener({
|
|
532
|
+
source: el,
|
|
533
|
+
event_name: (_a = el.dataset['jssmEvent']) !== null && _a !== void 0 ? _a : 'click',
|
|
534
|
+
action_name: el.dataset['jssmAction'],
|
|
535
|
+
from_state: el.dataset['jssmFromState'],
|
|
536
|
+
from_property: el.dataset['jssmFromProperty'],
|
|
537
|
+
prevent_default: 'jssmPreventDefault' in el.dataset,
|
|
538
|
+
stop_propagation: 'jssmStopPropagation' in el.dataset,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
// Dedicated tag form: direct `<jssm-action>` children of the host.
|
|
542
|
+
// `:scope >` keeps a nested `<jssm-instance>`'s actions from being
|
|
543
|
+
// claimed by an outer host.
|
|
544
|
+
const tags = this.querySelectorAll(':scope > jssm-action');
|
|
545
|
+
for (const tag of Array.from(tags)) {
|
|
546
|
+
const selector = tag.getAttribute('selector');
|
|
547
|
+
const action_name = tag.getAttribute('action');
|
|
548
|
+
if (selector === null || action_name === null) {
|
|
549
|
+
// Required attrs missing — skip, but don't throw: a malformed tag
|
|
550
|
+
// shouldn't break the rest of the host's wiring.
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
const event_name = (_b = tag.getAttribute('event')) !== null && _b !== void 0 ? _b : 'click';
|
|
554
|
+
const from_state = (_c = tag.getAttribute('from-state')) !== null && _c !== void 0 ? _c : undefined;
|
|
555
|
+
const from_property = (_d = tag.getAttribute('from-property')) !== null && _d !== void 0 ? _d : undefined;
|
|
556
|
+
const prevent_default = tag.hasAttribute('prevent-default');
|
|
557
|
+
const stop_propagation = tag.hasAttribute('stop-propagation');
|
|
558
|
+
const sources = this.querySelectorAll(selector);
|
|
559
|
+
for (const src of Array.from(sources)) {
|
|
560
|
+
this._install_action_listener({
|
|
561
|
+
source: src,
|
|
562
|
+
event_name,
|
|
563
|
+
action_name,
|
|
564
|
+
from_state,
|
|
565
|
+
from_property,
|
|
566
|
+
prevent_default,
|
|
567
|
+
stop_propagation,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Attach one DOM listener that translates a DOM event into a
|
|
574
|
+
* `machine.action(...)` call, honoring the configured modifiers. The
|
|
575
|
+
* listener is recorded in {@link _action_listeners} so it can be removed
|
|
576
|
+
* on disconnect.
|
|
577
|
+
*
|
|
578
|
+
* @param config - Listener configuration.
|
|
579
|
+
* @param config.source - Element to attach the listener to.
|
|
580
|
+
* @param config.event_name - DOM event to listen for.
|
|
581
|
+
* @param config.action_name - Action to dispatch on the machine.
|
|
582
|
+
* @param config.from_state - If set, only fire when `machine.state() === from_state`.
|
|
583
|
+
* @param config.from_property - If set, pass `source[from_property]` as the action's data argument.
|
|
584
|
+
* @param config.prevent_default - If true, call `e.preventDefault()` before checking the guard.
|
|
585
|
+
* @param config.stop_propagation - If true, call `e.stopPropagation()` before checking the guard.
|
|
586
|
+
*/
|
|
587
|
+
_install_action_listener(config) {
|
|
588
|
+
const handler = (e) => {
|
|
589
|
+
if (config.prevent_default) {
|
|
590
|
+
e.preventDefault();
|
|
591
|
+
}
|
|
592
|
+
if (config.stop_propagation) {
|
|
593
|
+
e.stopPropagation();
|
|
594
|
+
}
|
|
595
|
+
// Guard: skip dispatch when the machine isn't in the required state.
|
|
596
|
+
if (config.from_state !== undefined && this.state() !== config.from_state) {
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
const data = config.from_property !== undefined
|
|
600
|
+
? config.source[config.from_property]
|
|
601
|
+
: undefined;
|
|
602
|
+
this.do(config.action_name, data);
|
|
603
|
+
};
|
|
604
|
+
config.source.addEventListener(config.event_name, handler);
|
|
605
|
+
this._action_listeners.push({
|
|
606
|
+
target: config.source,
|
|
607
|
+
event: config.event_name,
|
|
608
|
+
handler,
|
|
609
|
+
});
|
|
199
610
|
}
|
|
200
611
|
/**
|
|
201
612
|
* Reflect machine state onto host attributes and CSS custom properties.
|