jssm 5.162.7 → 5.162.9
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 +24 -24
- package/dist/cdn/instance.js +5 -3
- package/dist/cdn/viz.js +1 -1
- package/dist/cli/fsl-export-system-prompt.cjs +27 -24
- package/dist/cli/fsl-render.cjs +1 -1
- package/dist/cli/fsl.cjs +1 -1
- package/dist/cli/lib.cjs +1 -1
- package/dist/cli/lib.mjs +1 -1
- package/dist/cm6/fsl_language.js +10 -14
- package/dist/deno/README.md +7 -7
- package/dist/deno/fence.d.ts +0 -1
- package/dist/deno/fsl_fence_highlight.d.ts +0 -4
- package/dist/deno/fsl_fence_render.d.ts +0 -8
- package/dist/deno/fsl_gif.d.ts +0 -8
- package/dist/deno/fsl_markdown_fence.d.ts +0 -5
- package/dist/deno/fsl_svg_patch.d.ts +0 -6
- package/dist/deno/fsl_walk.d.ts +0 -2
- package/dist/deno/jssm.d.ts +214 -503
- package/dist/deno/jssm.js +1 -1
- package/dist/deno/jssm_compiler.d.ts +1 -1
- package/dist/deno/jssm_constants.d.ts +0 -3
- package/dist/deno/jssm_intern.d.ts +0 -15
- package/dist/deno/jssm_theme.d.ts +2 -2
- package/dist/deno/jssm_types.d.ts +13 -34
- package/dist/deno/jssm_util.d.ts +20 -6
- package/dist/deno/jssm_viz.d.ts +5 -51
- package/dist/es6/cm6/fsl_language.d.ts +0 -2
- package/dist/fence/fence.js +2052 -1857
- 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/docs.define.js +0 -5
- package/dist/wc/docs.js +79 -43
- package/dist/wc/editor.define.js +0 -5
- package/dist/wc/editor.js +1 -7
- package/dist/wc/instance.define.js +0 -6
- package/dist/wc/instance.js +93 -115
- package/dist/wc/viz.define.js +0 -6
- package/dist/wc/viz.js +22 -36
- package/dist/wc/widgets.define.js +0 -5
- package/dist/wc/widgets.js +117 -95
- package/jssm.cli.d.cts +0 -8
- package/jssm.cli.d.ts +0 -8
- package/jssm.es5.d.cts +449 -808
- package/jssm.es6.d.ts +449 -808
- package/jssm.fence.d.ts +213 -508
- package/jssm_viz.es5.d.cts +216 -529
- package/jssm_viz.es6.d.ts +216 -529
- package/package.json +21 -11
package/dist/wc/instance.js
CHANGED
|
@@ -9,11 +9,9 @@ import { sm, from } from 'jssm';
|
|
|
9
9
|
/**
|
|
10
10
|
* Returns true when `tag_name` is exactly `fsl-<suffix>` or `jssm-<suffix>`
|
|
11
11
|
* (case-insensitive).
|
|
12
|
-
*
|
|
13
12
|
* @param tag_name - The element tag name to test (e.g. `"FSL-VIZ"`, `"jssm-viz"`).
|
|
14
13
|
* @param suffix - The suffix to match after the prefix (e.g. `"viz"`).
|
|
15
14
|
* @returns `true` when `tag_name` is `fsl-<suffix>` or `jssm-<suffix>`.
|
|
16
|
-
*
|
|
17
15
|
* @example
|
|
18
16
|
* wc_suffix_matches('FSL-VIZ', 'viz'); // true
|
|
19
17
|
* wc_suffix_matches('jssm-viz', 'viz'); // true
|
|
@@ -27,15 +25,12 @@ function wc_suffix_matches(tag_name, suffix) {
|
|
|
27
25
|
/**
|
|
28
26
|
* Returns the nearest ancestor of `el` (or `el` itself) whose tag is
|
|
29
27
|
* `fsl-<suffix>` or `jssm-<suffix>`, or `null` if none exists.
|
|
30
|
-
*
|
|
31
28
|
* @param el - The element to start the search from.
|
|
32
29
|
* @param suffix - The suffix to match (e.g. `"instance"`).
|
|
33
30
|
* @returns The closest matching ancestor element, or `null`.
|
|
34
|
-
*
|
|
35
31
|
* @example
|
|
36
32
|
* // <fsl-instance><div id="k"></div></fsl-instance>
|
|
37
33
|
* closest_wc(document.getElementById('k'), 'instance'); // <fsl-instance>
|
|
38
|
-
*
|
|
39
34
|
* @see wc_suffix_matches
|
|
40
35
|
*/
|
|
41
36
|
function closest_wc(el, suffix) {
|
|
@@ -56,7 +51,6 @@ function closest_wc(el, suffix) {
|
|
|
56
51
|
* walk_path({ a: null }, 'a.b'); // => undefined (null is not an object)
|
|
57
52
|
* walk_path({ a: 1 }, ''); // => { a: 1 } (empty path = identity)
|
|
58
53
|
* ```
|
|
59
|
-
*
|
|
60
54
|
* @param obj - The root value to traverse.
|
|
61
55
|
* @param path - Dotted path of property names, e.g. `"a.b.c"`.
|
|
62
56
|
* @returns The terminal value, or `undefined` if any step fails.
|
|
@@ -96,22 +90,30 @@ function walk_path(obj, path) {
|
|
|
96
90
|
* resolve_binding(m, 'data.username'); // typed-data subfield
|
|
97
91
|
* resolve_binding(m, 'wat'); // throws
|
|
98
92
|
* ```
|
|
99
|
-
*
|
|
100
93
|
* @param m - The machine whose state/data is being projected.
|
|
101
94
|
* @param expr - The binding expression text (raw attribute value).
|
|
102
95
|
* @returns The resolved value, typed `unknown` since each expression
|
|
103
96
|
* yields a different shape.
|
|
104
|
-
*
|
|
105
97
|
* @throws Error - When `expr` is not a recognized binding form.
|
|
106
98
|
*/
|
|
107
99
|
function resolve_binding(m, expr) {
|
|
108
100
|
switch (expr) {
|
|
109
|
-
case 'state':
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
case '
|
|
113
|
-
|
|
114
|
-
|
|
101
|
+
case 'state': {
|
|
102
|
+
return m.state();
|
|
103
|
+
}
|
|
104
|
+
case 'terminal': {
|
|
105
|
+
return m.is_terminal();
|
|
106
|
+
}
|
|
107
|
+
case 'complete': {
|
|
108
|
+
return m.is_complete();
|
|
109
|
+
}
|
|
110
|
+
case 'legal-actions': {
|
|
111
|
+
return m.list_exit_actions().map(String).join(' ');
|
|
112
|
+
}
|
|
113
|
+
case 'data': {
|
|
114
|
+
return m.data();
|
|
115
|
+
}
|
|
116
|
+
default: {
|
|
115
117
|
if (expr.startsWith('data.')) {
|
|
116
118
|
// walk the live reference (no whole-tree structuredClone per event),
|
|
117
119
|
// then clone only the extracted leaf so callers still receive a
|
|
@@ -121,6 +123,7 @@ function resolve_binding(m, expr) {
|
|
|
121
123
|
return ((typeof leaf === 'object') && (leaf !== null)) ? structuredClone(leaf) : leaf;
|
|
122
124
|
}
|
|
123
125
|
throw new Error(`<jssm-bind>: unknown binding expression "${expr}"`);
|
|
126
|
+
}
|
|
124
127
|
}
|
|
125
128
|
}
|
|
126
129
|
/**
|
|
@@ -141,16 +144,17 @@ function resolve_binding(m, expr) {
|
|
|
141
144
|
* set_on_element(button, 'disabled', true); // button.disabled = true
|
|
142
145
|
* set_on_element(div, 'data-current', 'red'); // setAttribute('data-current', 'red')
|
|
143
146
|
* ```
|
|
144
|
-
*
|
|
145
147
|
* @param el - The element to update.
|
|
146
148
|
* @param target - Target property name, possibly a `data-*` attribute.
|
|
147
149
|
* @param value - The resolved value to assign.
|
|
148
150
|
*/
|
|
149
151
|
function set_on_element(el, target, value) {
|
|
150
152
|
if (target.startsWith('data-')) {
|
|
153
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- documented #645 contract: attribute targets coerce with String(); a `data`-bound object rendering '[object Object]' is the shipped behavior
|
|
151
154
|
el.setAttribute(target, String(value));
|
|
152
155
|
}
|
|
153
156
|
else if (target === 'textContent') {
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- documented #645 contract: textContent coerces with String(); a `data`-bound object rendering '[object Object]' is the shipped behavior
|
|
154
158
|
el.textContent = String(value);
|
|
155
159
|
}
|
|
156
160
|
else {
|
|
@@ -185,12 +189,10 @@ function set_on_element(el, target, value) {
|
|
|
185
189
|
* const unsubs = install_bindings(this, this.machine);
|
|
186
190
|
* this._unsubs.push(...unsubs);
|
|
187
191
|
* ```
|
|
188
|
-
*
|
|
189
192
|
* @param host - The host element whose descendants carry the bindings.
|
|
190
193
|
* @param machine - The machine whose state/data is being projected.
|
|
191
194
|
* @returns A flat array of unsubscribe callbacks, one per installed
|
|
192
195
|
* subscription.
|
|
193
|
-
*
|
|
194
196
|
* @throws Error - When any binding expression is unrecognized
|
|
195
197
|
* (propagated from {@link resolve_binding}).
|
|
196
198
|
* @throws Error - When a `<jssm-bind>` tag is missing its `selector`
|
|
@@ -201,7 +203,7 @@ function install_bindings(host, machine) {
|
|
|
201
203
|
const unsubs = [];
|
|
202
204
|
// Form 1: inline `data-jssm-bind` on descendants.
|
|
203
205
|
const inline_nodes = host.querySelectorAll('[data-jssm-bind]');
|
|
204
|
-
for (const el of
|
|
206
|
+
for (const el of inline_nodes) {
|
|
205
207
|
const expr = el.dataset.jssmBind;
|
|
206
208
|
const target = (_a = el.dataset.jssmBindTo) !== null && _a !== void 0 ? _a : 'textContent';
|
|
207
209
|
const apply = () => {
|
|
@@ -215,19 +217,19 @@ function install_bindings(host, machine) {
|
|
|
215
217
|
// `<fsl-instance>` / `<jssm-instance>` children would have their own
|
|
216
218
|
// bindings handled by their own component.
|
|
217
219
|
const all_direct = host.querySelectorAll(':scope > *');
|
|
218
|
-
const config_tags =
|
|
220
|
+
const config_tags = [...all_direct].filter(el => wc_suffix_matches(el.tagName, 'bind'));
|
|
219
221
|
for (const tag of config_tags) {
|
|
220
222
|
const selector = tag.getAttribute('selector');
|
|
221
|
-
const expr = tag.getAttribute('source');
|
|
222
|
-
const target = (_b = tag.getAttribute('target')) !== null && _b !== void 0 ? _b : 'textContent';
|
|
223
223
|
if (selector === null || selector.length === 0) {
|
|
224
224
|
throw new Error('<jssm-bind>: missing required "selector" attribute');
|
|
225
225
|
}
|
|
226
|
+
const expr = tag.getAttribute('source');
|
|
226
227
|
if (expr === null || expr.length === 0) {
|
|
227
228
|
throw new Error('<jssm-bind>: missing required "source" attribute');
|
|
228
229
|
}
|
|
230
|
+
const target = (_b = tag.getAttribute('target')) !== null && _b !== void 0 ? _b : 'textContent';
|
|
229
231
|
const targets = host.querySelectorAll(selector);
|
|
230
|
-
for (const el of
|
|
232
|
+
for (const el of targets) {
|
|
231
233
|
const apply = () => {
|
|
232
234
|
set_on_element(el, target, resolve_binding(machine, expr));
|
|
233
235
|
};
|
|
@@ -247,7 +249,6 @@ function install_bindings(host, machine) {
|
|
|
247
249
|
* unknown tag) gives it a stable upgrade timing, a `display: none`
|
|
248
250
|
* default style, and a proper place in the custom-elements registry so
|
|
249
251
|
* `customElements.get('fsl-bind')` resolves.
|
|
250
|
-
*
|
|
251
252
|
* @element fsl-bind
|
|
252
253
|
* @attribute selector - CSS selector for the target element(s), scoped to the host.
|
|
253
254
|
* @attribute source - Binding expression (see {@link resolve_binding}).
|
|
@@ -285,7 +286,6 @@ const VALID_KINDS = new Set([
|
|
|
285
286
|
*
|
|
286
287
|
* The `machine` parameter is used only for `state()`, so unit tests can
|
|
287
288
|
* substitute any object with a `state(): unknown` method.
|
|
288
|
-
*
|
|
289
289
|
* @param ctx - Raw hook context passed by jssm.
|
|
290
290
|
* @param machine - The owning machine; used for the `state()` accessor.
|
|
291
291
|
* @returns A proxy object suitable for passing to a user handler.
|
|
@@ -308,6 +308,7 @@ function make_hook_proxy(ctx, machine) {
|
|
|
308
308
|
return ctx.action;
|
|
309
309
|
},
|
|
310
310
|
state() {
|
|
311
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- `machine` is deliberately duck-typed `state(): unknown` so tests can stub it (see docblock); real machines return string, and String() is the documented normalization
|
|
311
312
|
return String(machine.state());
|
|
312
313
|
},
|
|
313
314
|
};
|
|
@@ -322,7 +323,6 @@ function make_hook_proxy(ctx, machine) {
|
|
|
322
323
|
*
|
|
323
324
|
* Prepends a `//# sourceURL=` comment so devtools surface a meaningful name
|
|
324
325
|
* in stack traces instead of `anonymous`.
|
|
325
|
-
*
|
|
326
326
|
* @param body - Trimmed textContent of the `<jssm-hook>` element.
|
|
327
327
|
* @param debug_id - Identifier appended to the synthetic sourceURL.
|
|
328
328
|
* @returns The compiled handler.
|
|
@@ -336,7 +336,6 @@ function compile_inline_body$1(body, debug_id) {
|
|
|
336
336
|
* Resolve a `handler="name"` attribute to a callable by consulting first the
|
|
337
337
|
* optional in-WC registry, then `globalThis[name]`. Throws a clear error if
|
|
338
338
|
* neither resolves.
|
|
339
|
-
*
|
|
340
339
|
* @param name - The handler name from the `handler=""` attribute.
|
|
341
340
|
* @param registry - Optional in-WC registry to consult first.
|
|
342
341
|
* @returns The resolved handler.
|
|
@@ -359,13 +358,12 @@ function resolve_named_handler$1(name, registry) {
|
|
|
359
358
|
* Validate and normalize a `<jssm-hook kind="...">` value, defaulting to
|
|
360
359
|
* `"hook"` when the attribute is absent. Throws on unknown kinds rather
|
|
361
360
|
* than silently doing nothing later.
|
|
362
|
-
*
|
|
363
361
|
* @param raw - The raw attribute value, or null if not present.
|
|
364
362
|
* @returns The normalized {@link JssmHookKind}.
|
|
365
363
|
* @throws Error - On an unknown kind.
|
|
366
364
|
*/
|
|
367
365
|
function normalize_hook_kind(raw) {
|
|
368
|
-
if (raw
|
|
366
|
+
if (!raw) { // null, undefined, and '' — the only falsy values of this type
|
|
369
367
|
return 'hook';
|
|
370
368
|
}
|
|
371
369
|
if (!VALID_KINDS.has(raw)) {
|
|
@@ -383,7 +381,6 @@ function normalize_hook_kind(raw) {
|
|
|
383
381
|
* `from`/`to` for `kind="hook"`) are NOT validated here — `set_hook` will
|
|
384
382
|
* throw with its own clear errors on missing pieces, which keeps the
|
|
385
383
|
* error surface single-sourced.
|
|
386
|
-
*
|
|
387
384
|
* @param el - The `<jssm-hook>` element to parse.
|
|
388
385
|
* @param debug_id - Identifier used in the inline body's sourceURL.
|
|
389
386
|
* @param registry - Optional in-WC registry of named handlers.
|
|
@@ -401,9 +398,9 @@ function parse_hook_element(el, debug_id, registry) {
|
|
|
401
398
|
if (handler_attr === null && body_text.length === 0) {
|
|
402
399
|
throw new Error('<jssm-hook>: must specify either handler="name" attribute or an inline body');
|
|
403
400
|
}
|
|
404
|
-
const user_handler = handler_attr
|
|
405
|
-
?
|
|
406
|
-
:
|
|
401
|
+
const user_handler = handler_attr === null
|
|
402
|
+
? compile_inline_body$1(body_text, debug_id)
|
|
403
|
+
: resolve_named_handler$1(handler_attr, registry);
|
|
407
404
|
const kind = normalize_hook_kind(el.getAttribute('kind'));
|
|
408
405
|
// Convert null → undefined so downstream descriptors omit absent keys.
|
|
409
406
|
const from = (_a = el.getAttribute('from')) !== null && _a !== void 0 ? _a : undefined;
|
|
@@ -421,7 +418,6 @@ function parse_hook_element(el, debug_id, registry) {
|
|
|
421
418
|
* Any non-`false` return — including `undefined`, `true`, or an arbitrary
|
|
422
419
|
* object — allows the transition. This matches the contract spelled out
|
|
423
420
|
* in the issue (#641): "return false cancels; anything else allows".
|
|
424
|
-
*
|
|
425
421
|
* @param spec - The parsed install spec carrying the user handler.
|
|
426
422
|
* @param machine - The owning machine; used by the proxy's `state()`.
|
|
427
423
|
* @returns A wrapped handler suitable for `set_hook`.
|
|
@@ -450,7 +446,6 @@ function wrap_user_handler(spec, machine) {
|
|
|
450
446
|
* Return type is `unknown` because jssm's `HookDescription` is a
|
|
451
447
|
* discriminated union and our runtime-discriminator value can't be tracked
|
|
452
448
|
* by TypeScript across the build. The WC casts at the `set_hook` call site.
|
|
453
|
-
*
|
|
454
449
|
* @param spec - The parsed install spec.
|
|
455
450
|
* @param wrapped - The wrapped (friendly-proxy) handler from {@link wrap_user_handler}.
|
|
456
451
|
* @returns A descriptor object for `set_hook`/`remove_hook`.
|
|
@@ -478,14 +473,12 @@ function build_hook_descriptor(spec, wrapped) {
|
|
|
478
473
|
*/
|
|
479
474
|
/**
|
|
480
475
|
* Default fragment key for the single-machine case (back-compat with 5.150).
|
|
481
|
-
*
|
|
482
476
|
* @example
|
|
483
477
|
* DEFAULT_PERMALINK_KEY; // 'm'
|
|
484
478
|
*/
|
|
485
479
|
/**
|
|
486
480
|
* URL-safe base64 (RFC 4648 §5) of raw bytes: standard base64 with `+`→`-`,
|
|
487
481
|
* `/`→`_`, and trailing `=` padding stripped.
|
|
488
|
-
*
|
|
489
482
|
* @example
|
|
490
483
|
* bytes_to_base64url(new TextEncoder().encode("a")); // "YQ"
|
|
491
484
|
*/
|
|
@@ -498,7 +491,6 @@ function bytes_to_base64url(bytes) {
|
|
|
498
491
|
}
|
|
499
492
|
/**
|
|
500
493
|
* Inverse of {@link bytes_to_base64url}.
|
|
501
|
-
*
|
|
502
494
|
* @example
|
|
503
495
|
* new TextDecoder().decode(base64url_to_bytes("YQ")); // "a"
|
|
504
496
|
*/
|
|
@@ -514,7 +506,6 @@ function base64url_to_bytes(text) {
|
|
|
514
506
|
}
|
|
515
507
|
/**
|
|
516
508
|
* DEFLATE `bytes` (raw, headerless) via the platform `CompressionStream`.
|
|
517
|
-
*
|
|
518
509
|
* @example
|
|
519
510
|
* await deflate_raw(new TextEncoder().encode("aaaaaaaa")); // shorter Uint8Array of raw DEFLATE bytes
|
|
520
511
|
*/
|
|
@@ -540,9 +531,7 @@ const MAX_PERMALINK_INFLATE_BYTES = 5 * 1024 * 1024;
|
|
|
540
531
|
* Inverse of {@link deflate_raw}, reading the stream in chunks and aborting once
|
|
541
532
|
* the inflated output would exceed {@link MAX_PERMALINK_INFLATE_BYTES} (a
|
|
542
533
|
* decompression-bomb guard — see that constant).
|
|
543
|
-
*
|
|
544
534
|
* @throws RangeError when the inflated output exceeds the cap.
|
|
545
|
-
*
|
|
546
535
|
* @example
|
|
547
536
|
* new TextDecoder().decode(await inflate_raw(await deflate_raw(new TextEncoder().encode("hi")))); // "hi"
|
|
548
537
|
*/
|
|
@@ -582,7 +571,6 @@ async function inflate_raw(bytes) {
|
|
|
582
571
|
* Encode FSL to a `<scheme><payload>` segment value (the part after `key=`).
|
|
583
572
|
* DEFLATE is used (scheme `1`) only when it is strictly shorter than the raw
|
|
584
573
|
* bytes (scheme `0`).
|
|
585
|
-
*
|
|
586
574
|
* @example
|
|
587
575
|
* await encode_machine("a -> b;"); // "0YSAtPiBiOw"
|
|
588
576
|
*/
|
|
@@ -595,7 +583,6 @@ async function encode_machine(fsl) {
|
|
|
595
583
|
/**
|
|
596
584
|
* Inverse of {@link encode_machine}: decode a `<scheme><payload>` segment back
|
|
597
585
|
* to FSL. Async because inflate is async.
|
|
598
|
-
*
|
|
599
586
|
* @example
|
|
600
587
|
* await decode_machine("0YSAtPiBiOw"); // "a -> b;"
|
|
601
588
|
*/
|
|
@@ -605,8 +592,10 @@ async function decode_machine(segment) {
|
|
|
605
592
|
const plain = scheme === '1' ? await inflate_raw(bytes) : bytes;
|
|
606
593
|
return new TextDecoder().decode(plain);
|
|
607
594
|
}
|
|
608
|
-
/**
|
|
609
|
-
*
|
|
595
|
+
/**
|
|
596
|
+
* `decodeURIComponent` that returns its input untouched on a malformed escape,
|
|
597
|
+
* so a hand-mangled fragment never throws out of {@link read_fragment_param}.
|
|
598
|
+
*/
|
|
610
599
|
function safe_decode(text) {
|
|
611
600
|
try {
|
|
612
601
|
return decodeURIComponent(text);
|
|
@@ -631,9 +620,7 @@ function fragment_pairs(hash) {
|
|
|
631
620
|
}
|
|
632
621
|
/**
|
|
633
622
|
* Read one segment's value out of a `#a=…&b=…` fragment.
|
|
634
|
-
*
|
|
635
623
|
* @returns The value, or `null` if `key` is absent.
|
|
636
|
-
*
|
|
637
624
|
* @example
|
|
638
625
|
* read_fragment_param('#a=0AAA&b=1BBB', 'b'); // "1BBB"
|
|
639
626
|
*/
|
|
@@ -644,7 +631,6 @@ function read_fragment_param(hash, key) {
|
|
|
644
631
|
/**
|
|
645
632
|
* Return a new fragment body (no leading `#`) with `key`'s segment set to
|
|
646
633
|
* `value`, preserving every other segment and its order; appends if absent.
|
|
647
|
-
*
|
|
648
634
|
* @example
|
|
649
635
|
* set_fragment_param('#a=0AAA', 'b', '1BBB'); // "a=0AAA&b=1BBB"
|
|
650
636
|
*/
|
|
@@ -665,7 +651,6 @@ function set_fragment_param(hash, key, value) {
|
|
|
665
651
|
* The fragment key an element owns: its `uhash` attribute if set, else its
|
|
666
652
|
* `id`, else `null` (does not participate in URL sync). The single source of
|
|
667
653
|
* this rule, shared by the toolbar export and the sync controller.
|
|
668
|
-
*
|
|
669
654
|
* @example
|
|
670
655
|
* permalink_key_for(el); // "myId" (when <el id="myId">, no uhash)
|
|
671
656
|
*/
|
|
@@ -685,7 +670,6 @@ const PERMALINK_WRITE_DEBOUNCE_MS = 300;
|
|
|
685
670
|
*
|
|
686
671
|
* Echo guard: `_last` holds the segment most recently read or written, so a
|
|
687
672
|
* restore→rebuild→write cycle and a self-induced `hashchange` are both no-ops.
|
|
688
|
-
*
|
|
689
673
|
* @example
|
|
690
674
|
* // In an element's constructor:
|
|
691
675
|
* new FslPermalinkSync(this); // reads <el id="k">'s #k=… on connect, writes it on edit
|
|
@@ -706,14 +690,14 @@ class FslPermalinkSync {
|
|
|
706
690
|
}
|
|
707
691
|
void this._restore();
|
|
708
692
|
this.host.addEventListener('fsl-machine-rebuilt', this._onRebuilt);
|
|
709
|
-
|
|
693
|
+
addEventListener('hashchange', this._onHashChange);
|
|
710
694
|
}
|
|
711
695
|
hostDisconnected() {
|
|
712
696
|
if (this.key === null) {
|
|
713
697
|
return;
|
|
714
698
|
}
|
|
715
699
|
this.host.removeEventListener('fsl-machine-rebuilt', this._onRebuilt);
|
|
716
|
-
|
|
700
|
+
removeEventListener('hashchange', this._onHashChange);
|
|
717
701
|
if (this._timer !== undefined) {
|
|
718
702
|
clearTimeout(this._timer);
|
|
719
703
|
this._timer = undefined;
|
|
@@ -771,7 +755,6 @@ class FslPermalinkSync {
|
|
|
771
755
|
* with `system` following the OS `prefers-color-scheme`. The host resolves
|
|
772
756
|
* (theme name × mode) to a palette and writes it as `--fsl-color-*` custom
|
|
773
757
|
* properties, which cascade to every slotted widget.
|
|
774
|
-
*
|
|
775
758
|
* @see resolve_theme_mode
|
|
776
759
|
*/
|
|
777
760
|
/**
|
|
@@ -804,7 +787,6 @@ const BUILTIN_THEMES = {
|
|
|
804
787
|
/**
|
|
805
788
|
* Resolve a `(registry, theme name, variant)` triple to a concrete palette,
|
|
806
789
|
* falling back to the built-in `Default` theme when the name is unknown.
|
|
807
|
-
*
|
|
808
790
|
* @param themes - The registry to look in.
|
|
809
791
|
* @param name - The selected theme name.
|
|
810
792
|
* @param variant - `light` or `dark`.
|
|
@@ -817,11 +799,9 @@ function resolve_palette(themes, name, variant) {
|
|
|
817
799
|
/**
|
|
818
800
|
* Resolve a theme mode to a concrete `light`/`dark`. `system` consults the OS
|
|
819
801
|
* preference; `light`/`dark` are returned as-is.
|
|
820
|
-
*
|
|
821
802
|
* @param mode - The selected mode.
|
|
822
803
|
* @param prefers_dark - Whether the OS prefers a dark color scheme.
|
|
823
804
|
* @returns The concrete variant to apply.
|
|
824
|
-
*
|
|
825
805
|
* @example
|
|
826
806
|
* resolve_theme_mode('system', true); // 'dark'
|
|
827
807
|
* resolve_theme_mode('light', true); // 'light'
|
|
@@ -832,7 +812,6 @@ function resolve_theme_mode(mode, prefers_dark) {
|
|
|
832
812
|
/**
|
|
833
813
|
* Map a palette to its `--fsl-color-*` custom-property entries, ready to set on
|
|
834
814
|
* an element's style.
|
|
835
|
-
*
|
|
836
815
|
* @param palette - The resolved palette.
|
|
837
816
|
* @returns `[propertyName, value]` pairs, e.g. `['--fsl-color-surface', '#fff']`.
|
|
838
817
|
*/
|
|
@@ -843,7 +822,8 @@ function register_palette_properties() {
|
|
|
843
822
|
if (typeof CSS === 'undefined' || typeof CSS.registerProperty !== 'function') {
|
|
844
823
|
return;
|
|
845
824
|
}
|
|
846
|
-
|
|
825
|
+
const default_light_vars = palette_to_vars(BUILTIN_THEMES['Default'].light);
|
|
826
|
+
for (const [prop, value] of default_light_vars) {
|
|
847
827
|
try {
|
|
848
828
|
CSS.registerProperty({ name: prop, syntax: '<color>', inherits: true, initialValue: value });
|
|
849
829
|
}
|
|
@@ -900,7 +880,6 @@ const JSSM_ON_EVENT_NAMES = new Set([
|
|
|
900
880
|
* // => { event: 'entry', handler_name: 'onPaid', inline_body: undefined,
|
|
901
881
|
* // once: false, name: undefined, filter: { state: 'paid' } }
|
|
902
882
|
* ```
|
|
903
|
-
*
|
|
904
883
|
* @param el - The `<jssm-on>` element to parse.
|
|
905
884
|
* @returns A validated {@link ParsedJssmOn} record.
|
|
906
885
|
* @throws If `event` is missing, unknown, both handler forms are
|
|
@@ -985,7 +964,6 @@ const jssm_handler_registry = new Map();
|
|
|
985
964
|
* Resolve a named handler from the registry, then from `globalThis`.
|
|
986
965
|
* Throws if neither lookup finds a function — earlier failure here is
|
|
987
966
|
* better than a delayed "is not a function" at first event delivery.
|
|
988
|
-
*
|
|
989
967
|
* @param name - The handler name as supplied by `handler="..."`.
|
|
990
968
|
* @returns The resolved function.
|
|
991
969
|
* @throws If no function is registered under `name`.
|
|
@@ -1011,7 +989,6 @@ function resolve_named_handler(name) {
|
|
|
1011
989
|
* caveats apply (strict CSP without `'unsafe-eval'` blocks it). A
|
|
1012
990
|
* `//# sourceURL=jssm-on:N` pragma is appended so devtools stack traces
|
|
1013
991
|
* point at a meaningful name.
|
|
1014
|
-
*
|
|
1015
992
|
* @param body - The inline JS body (function body, not full function).
|
|
1016
993
|
* @param source_id - A short identifier for the sourceURL pragma.
|
|
1017
994
|
* @returns The compiled handler.
|
|
@@ -1021,7 +998,7 @@ function compile_inline_body(body, source_id) {
|
|
|
1021
998
|
// The Function constructor is intentional here — see the docblock above
|
|
1022
999
|
// for the rationale and the CSP caveat. Equivalent to how browsers wire
|
|
1023
1000
|
// up inline event handlers; the input is consumer-authored markup.
|
|
1024
|
-
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
1001
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
1025
1002
|
return new Function('e', wrapped); // skipcq: JS-0086
|
|
1026
1003
|
}
|
|
1027
1004
|
/**
|
|
@@ -1040,7 +1017,6 @@ function compile_inline_body(body, source_id) {
|
|
|
1040
1017
|
* resolve_fsl_source(div as HTMLElement, 'Off -> On;');
|
|
1041
1018
|
* // => { fsl: 'Off -> On;', provided_count: 1, error: undefined }
|
|
1042
1019
|
* ```
|
|
1043
|
-
*
|
|
1044
1020
|
* @param host - The `<jssm-instance>` element being resolved.
|
|
1045
1021
|
* @param fsl_attr - The current value of the host's `fsl` attribute (or property), or empty string.
|
|
1046
1022
|
* @returns A {@link JssmInstanceFslResolution} describing the outcome.
|
|
@@ -1103,7 +1079,6 @@ function resolve_fsl_source(host, fsl_attr) {
|
|
|
1103
1079
|
* Resolve `layout="auto"` to a concrete split direction from the viewport
|
|
1104
1080
|
* shape: side-by-side (`'lr'`) when at least as wide as tall, else stacked
|
|
1105
1081
|
* (`'tb'`). Pure, so it's testable without a laid-out DOM.
|
|
1106
|
-
*
|
|
1107
1082
|
* @example
|
|
1108
1083
|
* auto_mode(1200, 800); // => 'lr'
|
|
1109
1084
|
* auto_mode(600, 900); // => 'tb'
|
|
@@ -1116,7 +1091,6 @@ function auto_mode(width, height) {
|
|
|
1116
1091
|
* Pure so it's testable without a laid-out DOM: returns a neutral `50` when the
|
|
1117
1092
|
* container has no measured size (e.g. jsdom, where `getBoundingClientRect`
|
|
1118
1093
|
* yields zeros), and otherwise clamps to `[15, 85]` so neither pane collapses.
|
|
1119
|
-
*
|
|
1120
1094
|
* @example
|
|
1121
1095
|
* split_ratio(30, 0, 100); // => 30
|
|
1122
1096
|
* split_ratio(5, 0, 100); // => 15 (clamped low)
|
|
@@ -1144,7 +1118,6 @@ function split_ratio(coord, start, size) {
|
|
|
1144
1118
|
* attributes (`current-state`, `legal-actions`, `terminal`, `complete`)
|
|
1145
1119
|
* and sets a `--current-state` CSS custom property so consumer CSS can
|
|
1146
1120
|
* style by state without subclassing.
|
|
1147
|
-
*
|
|
1148
1121
|
* @element fsl-instance
|
|
1149
1122
|
* @cssproperty [--current-state] - The machine's current state name as a CSS string token.
|
|
1150
1123
|
* @slot title - Heading area for the instance.
|
|
@@ -1163,8 +1136,10 @@ function split_ratio(coord, start, size) {
|
|
|
1163
1136
|
* @slot footer - Footer slot.
|
|
1164
1137
|
*/
|
|
1165
1138
|
class FslInstance extends LitElement {
|
|
1166
|
-
/**
|
|
1167
|
-
*
|
|
1139
|
+
/**
|
|
1140
|
+
* Bind this instance to a URL-fragment segment keyed by its `uhash`/`id`
|
|
1141
|
+
* (inert if it has neither): restore on connect, write debounced on edit.
|
|
1142
|
+
*/
|
|
1168
1143
|
constructor() {
|
|
1169
1144
|
super();
|
|
1170
1145
|
/**
|
|
@@ -1215,19 +1190,27 @@ class FslInstance extends LitElement {
|
|
|
1215
1190
|
this._autoMode = 'lr';
|
|
1216
1191
|
/** Window-resize listener installed while `layout="auto"`, or null. */
|
|
1217
1192
|
this._autoListener = null;
|
|
1218
|
-
/**
|
|
1219
|
-
*
|
|
1193
|
+
/**
|
|
1194
|
+
* Per-panel runtime visibility overrides set by the user via the toolbar
|
|
1195
|
+
* toggles; a slot absent here falls back to its mode-resolved base.
|
|
1196
|
+
*/
|
|
1220
1197
|
this._overrides = new Map();
|
|
1221
|
-
/**
|
|
1198
|
+
/**
|
|
1199
|
+
* Control-level default {@link PanelMode}; {@link panelModes} overrides it
|
|
1222
1200
|
* per panel. `default` shows only the editor + renderer; every other panel
|
|
1223
|
-
* starts hidden and is opt-in.
|
|
1201
|
+
* starts hidden and is opt-in.
|
|
1202
|
+
*/
|
|
1224
1203
|
this.panelMode = 'default';
|
|
1225
|
-
/**
|
|
1226
|
-
*
|
|
1204
|
+
/**
|
|
1205
|
+
* Per-panel {@link PanelMode} overrides (slot → mode), each overriding the
|
|
1206
|
+
* control-level {@link panelMode}.
|
|
1207
|
+
*/
|
|
1227
1208
|
this.panelModes = {};
|
|
1228
|
-
/**
|
|
1209
|
+
/**
|
|
1210
|
+
* Panels the FSL "requests" — the embedder-set stand-in for the
|
|
1229
1211
|
* editor-defaults-in-FSL mechanism (fsl#1334). `request`-mode panels listed
|
|
1230
|
-
* here are shown; others fall back to the default.
|
|
1212
|
+
* here are shown; others fall back to the default.
|
|
1213
|
+
*/
|
|
1231
1214
|
this.requestedPanels = [];
|
|
1232
1215
|
/**
|
|
1233
1216
|
* The underlying machine instance, constructed at `connectedCallback`.
|
|
@@ -1301,7 +1284,7 @@ class FslInstance extends LitElement {
|
|
|
1301
1284
|
};
|
|
1302
1285
|
/** Document pointer-move during a drag: recompute the split ratio. */
|
|
1303
1286
|
this._onGutterMove = (e) => {
|
|
1304
|
-
const rect = this.renderRoot.querySelector('.workbench').getBoundingClientRect();
|
|
1287
|
+
const rect = (this.renderRoot.querySelector('.workbench')).getBoundingClientRect();
|
|
1305
1288
|
const mode = this._effectiveMode();
|
|
1306
1289
|
this._split = (mode === 'tb' || mode === 'bt')
|
|
1307
1290
|
? split_ratio(e.clientY, rect.top, rect.height)
|
|
@@ -1322,7 +1305,6 @@ class FslInstance extends LitElement {
|
|
|
1322
1305
|
}
|
|
1323
1306
|
/**
|
|
1324
1307
|
* Raw machine accessor. Returns the owned {@link Machine} instance.
|
|
1325
|
-
*
|
|
1326
1308
|
* @throws If accessed before the element has been connected.
|
|
1327
1309
|
*/
|
|
1328
1310
|
get machine() {
|
|
@@ -1336,7 +1318,6 @@ class FslInstance extends LitElement {
|
|
|
1336
1318
|
* After the action, reflects updated state to host attributes and the
|
|
1337
1319
|
* `--current-state` CSS custom property, and requests a Lit update so
|
|
1338
1320
|
* the state-specific `<slot name="state-...">` can re-pick.
|
|
1339
|
-
*
|
|
1340
1321
|
* @param action - The action name to dispatch.
|
|
1341
1322
|
* @param data - Optional data payload to pass to the action.
|
|
1342
1323
|
* @returns `true` if the action succeeded, `false` otherwise.
|
|
@@ -1351,7 +1332,6 @@ class FslInstance extends LitElement {
|
|
|
1351
1332
|
* Convenience wrapper for `machine.transition(state, data)` — moves directly
|
|
1352
1333
|
* to a state along a legal (non-forced) edge. Reflects the new state and
|
|
1353
1334
|
* requests an update, exactly as {@link FslInstance.do} does for actions.
|
|
1354
|
-
*
|
|
1355
1335
|
* @param state - The destination state.
|
|
1356
1336
|
* @param data - Optional data payload.
|
|
1357
1337
|
* @returns `true` if the transition succeeded (a legal edge existed).
|
|
@@ -1366,7 +1346,6 @@ class FslInstance extends LitElement {
|
|
|
1366
1346
|
* Convenience wrapper for `machine.force_transition(state, data)` — moves to a
|
|
1367
1347
|
* state along any edge, including forced-only ones. Reflects the new state and
|
|
1368
1348
|
* requests an update.
|
|
1369
|
-
*
|
|
1370
1349
|
* @param state - The destination state.
|
|
1371
1350
|
* @param data - Optional data payload.
|
|
1372
1351
|
* @returns `true` if the forced transition succeeded (any edge existed).
|
|
@@ -1382,12 +1361,12 @@ class FslInstance extends LitElement {
|
|
|
1382
1361
|
* state's name.
|
|
1383
1362
|
*/
|
|
1384
1363
|
state() {
|
|
1385
|
-
return
|
|
1364
|
+
return this.machine.state();
|
|
1386
1365
|
}
|
|
1387
1366
|
/** Whether the OS currently prefers a dark color scheme. */
|
|
1388
1367
|
_prefers_dark() {
|
|
1389
|
-
return typeof
|
|
1390
|
-
&&
|
|
1368
|
+
return typeof matchMedia === 'function'
|
|
1369
|
+
&& matchMedia('(prefers-color-scheme: dark)').matches;
|
|
1391
1370
|
}
|
|
1392
1371
|
/**
|
|
1393
1372
|
* Resolve `theme` (mode) × `themeName` to a palette and apply it: write the
|
|
@@ -1398,7 +1377,8 @@ class FslInstance extends LitElement {
|
|
|
1398
1377
|
_applyTheme() {
|
|
1399
1378
|
const variant = resolve_theme_mode(this.theme, this._prefers_dark());
|
|
1400
1379
|
this.setAttribute('resolved-theme', variant);
|
|
1401
|
-
|
|
1380
|
+
const vars = palette_to_vars(resolve_palette(this.themes, this.themeName, variant));
|
|
1381
|
+
for (const [prop, value] of vars) {
|
|
1402
1382
|
this.style.setProperty(prop, value);
|
|
1403
1383
|
}
|
|
1404
1384
|
for (const editor of this.querySelectorAll('[slot="editor"]')) {
|
|
@@ -1415,7 +1395,8 @@ class FslInstance extends LitElement {
|
|
|
1415
1395
|
<div class="tabbar" role="tablist" aria-label="Pane">
|
|
1416
1396
|
<button type="button" role="tab" aria-selected=${this._tab === 'viz'} @click=${() => this._setTab('viz')}>Graph</button>
|
|
1417
1397
|
<button type="button" role="tab" aria-selected=${this._tab === 'editor'} @click=${() => this._setTab('editor')}>Code</button>
|
|
1418
|
-
</div
|
|
1398
|
+
</div>
|
|
1399
|
+
`;
|
|
1419
1400
|
}
|
|
1420
1401
|
/** Switch the visible pane in the `tabs` layout. */
|
|
1421
1402
|
_setTab(tab) {
|
|
@@ -1431,10 +1412,8 @@ class FslInstance extends LitElement {
|
|
|
1431
1412
|
* {@link PanelMode} ({@link panelModes} for the slot, else {@link panelMode}):
|
|
1432
1413
|
* `hide`/`show` force the state; otherwise a user toggle wins, then a
|
|
1433
1414
|
* `request`ed panel shows, then the built-in default.
|
|
1434
|
-
*
|
|
1435
1415
|
* @param slot - A panel slot name (e.g. `"viz"`, `"editor"`, `"history"`).
|
|
1436
1416
|
* @returns `true` when the panel is hidden.
|
|
1437
|
-
*
|
|
1438
1417
|
* @example
|
|
1439
1418
|
* el.panelModes = { history: 'show' };
|
|
1440
1419
|
* el.isPanelHidden('history'); // false
|
|
@@ -1462,7 +1441,6 @@ class FslInstance extends LitElement {
|
|
|
1462
1441
|
* `viz` or `editor` collapses that workbench pane (the other fills); hiding
|
|
1463
1442
|
* an aux panel removes its section. `<fsl-toolbar>` drives this from its
|
|
1464
1443
|
* panel toggles.
|
|
1465
|
-
*
|
|
1466
1444
|
* @param slot - A panel slot name (e.g. `"viz"`, `"editor"`, `"history"`).
|
|
1467
1445
|
* @param hidden - `true` to hide, `false` to show.
|
|
1468
1446
|
*/
|
|
@@ -1473,7 +1451,6 @@ class FslInstance extends LitElement {
|
|
|
1473
1451
|
/**
|
|
1474
1452
|
* Toggle the visibility of the panel slotted under `slot`. A no-op when the
|
|
1475
1453
|
* panel's mode is `hide` or `show` — those lock the visibility.
|
|
1476
|
-
*
|
|
1477
1454
|
* @param slot - A panel slot name (e.g. `"viz"`, `"editor"`, `"history"`).
|
|
1478
1455
|
*/
|
|
1479
1456
|
togglePanel(slot) {
|
|
@@ -1499,6 +1476,7 @@ class FslInstance extends LitElement {
|
|
|
1499
1476
|
}
|
|
1500
1477
|
};
|
|
1501
1478
|
this._autoListener = recompute;
|
|
1479
|
+
// eslint-disable-next-line unicorn/prefer-observer-apis -- deliberately a window resize listener: it resolves layout from the VIEWPORT aspect (not an element box) and must work in jsdom, which lacks ResizeObserver
|
|
1502
1480
|
window.addEventListener('resize', recompute);
|
|
1503
1481
|
recompute();
|
|
1504
1482
|
}
|
|
@@ -1514,7 +1492,6 @@ class FslInstance extends LitElement {
|
|
|
1514
1492
|
* Order is important: state reflection happens BEFORE the first render
|
|
1515
1493
|
* so that consumer CSS rules keyed off `[current-state="..."]` apply on
|
|
1516
1494
|
* first paint without a flash of unstyled content.
|
|
1517
|
-
*
|
|
1518
1495
|
* @throws If no FSL source was provided, or if more than one channel
|
|
1519
1496
|
* supplied a source.
|
|
1520
1497
|
*/
|
|
@@ -1564,8 +1541,8 @@ class FslInstance extends LitElement {
|
|
|
1564
1541
|
// switches can ease), follow the OS while in `system` mode, then apply the
|
|
1565
1542
|
// resolved palette.
|
|
1566
1543
|
register_palette_properties();
|
|
1567
|
-
if (typeof
|
|
1568
|
-
this._mql =
|
|
1544
|
+
if (typeof matchMedia === 'function') {
|
|
1545
|
+
this._mql = matchMedia('(prefers-color-scheme: dark)');
|
|
1569
1546
|
this._mql.addEventListener('change', this._on_os_theme_change);
|
|
1570
1547
|
}
|
|
1571
1548
|
this._applyTheme();
|
|
@@ -1604,12 +1581,12 @@ class FslInstance extends LitElement {
|
|
|
1604
1581
|
const machine = this._machine;
|
|
1605
1582
|
const on_nodes = this.querySelectorAll(':scope > fsl-on, :scope > jssm-on');
|
|
1606
1583
|
let index = 0;
|
|
1607
|
-
for (const el of
|
|
1584
|
+
for (const el of on_nodes) {
|
|
1608
1585
|
index += 1;
|
|
1609
1586
|
const parsed = parse_jssm_on_element(el);
|
|
1610
|
-
const handler = parsed.handler_name
|
|
1611
|
-
?
|
|
1612
|
-
:
|
|
1587
|
+
const handler = parsed.handler_name === undefined
|
|
1588
|
+
? compile_inline_body(parsed.inline_body, String(index))
|
|
1589
|
+
: resolve_named_handler(parsed.handler_name);
|
|
1613
1590
|
// Argument shape: machine.on(name, handler) when no filter, or
|
|
1614
1591
|
// machine.on(name, filter, handler) when filtered. Same for once.
|
|
1615
1592
|
// `as any` collapses the per-event detail typing — the WC is a
|
|
@@ -1631,7 +1608,7 @@ class FslInstance extends LitElement {
|
|
|
1631
1608
|
_install_declarative_hooks() {
|
|
1632
1609
|
const machine = this._machine;
|
|
1633
1610
|
const hook_els = this.querySelectorAll(':scope > fsl-hook, :scope > jssm-hook');
|
|
1634
|
-
for (const el of
|
|
1611
|
+
for (const el of hook_els) {
|
|
1635
1612
|
const debug_id = `${this._hook_id_prefix()}${++this._hook_debug_counter}`;
|
|
1636
1613
|
const spec = parse_hook_element(el, debug_id, this.registry);
|
|
1637
1614
|
const wrapped = wrap_user_handler(spec, machine);
|
|
@@ -1684,7 +1661,6 @@ class FslInstance extends LitElement {
|
|
|
1684
1661
|
* host attributes (mechanism 1), CSS custom properties (mechanism 3), and
|
|
1685
1662
|
* the state-specific slot (mechanism 2) are all current by the time a
|
|
1686
1663
|
* `fsl-*` listener runs.
|
|
1687
|
-
*
|
|
1688
1664
|
* @param changed - Lit's changed-property map (forwarded to super).
|
|
1689
1665
|
*/
|
|
1690
1666
|
/**
|
|
@@ -1715,7 +1691,6 @@ class FslInstance extends LitElement {
|
|
|
1715
1691
|
*/
|
|
1716
1692
|
/**
|
|
1717
1693
|
* Build a machine from FSL source, seeding {@link data} when it is set.
|
|
1718
|
-
*
|
|
1719
1694
|
* @param fsl_source - The FSL string to compile.
|
|
1720
1695
|
* @returns The compiled machine.
|
|
1721
1696
|
*/
|
|
@@ -1724,10 +1699,12 @@ class FslInstance extends LitElement {
|
|
|
1724
1699
|
? sm `${fsl_source}`
|
|
1725
1700
|
: from(fsl_source, { data: this.data }));
|
|
1726
1701
|
}
|
|
1727
|
-
/**
|
|
1702
|
+
/**
|
|
1703
|
+
* Adopt the FSL's `editor: {}` panel request (fsl#1334): when the machine
|
|
1728
1704
|
* declares `panels`, drive {@link requestedPanels} from it so `request` panel
|
|
1729
1705
|
* mode honors the source. The embedder's value persists when the FSL is
|
|
1730
|
-
* silent. Called after each (re)build, with `_machine` freshly assigned.
|
|
1706
|
+
* silent. Called after each (re)build, with `_machine` freshly assigned.
|
|
1707
|
+
*/
|
|
1731
1708
|
_applyEditorConfig() {
|
|
1732
1709
|
var _a;
|
|
1733
1710
|
const panels = (_a = this._machine.editor_config()) === null || _a === void 0 ? void 0 : _a.panels;
|
|
@@ -1862,7 +1839,7 @@ class FslInstance extends LitElement {
|
|
|
1862
1839
|
*/
|
|
1863
1840
|
_discover_jssm_actions() {
|
|
1864
1841
|
var _a, _b, _c, _d;
|
|
1865
|
-
const inline_targets =
|
|
1842
|
+
const inline_targets = [...this.querySelectorAll('[data-jssm-action]')].filter(el => closest_wc(el, 'action') === null);
|
|
1866
1843
|
for (const el of inline_targets) {
|
|
1867
1844
|
this._install_action_listener({
|
|
1868
1845
|
source: el,
|
|
@@ -1875,7 +1852,7 @@ class FslInstance extends LitElement {
|
|
|
1875
1852
|
});
|
|
1876
1853
|
}
|
|
1877
1854
|
const tags = this.querySelectorAll(':scope > fsl-action, :scope > jssm-action');
|
|
1878
|
-
for (const tag of
|
|
1855
|
+
for (const tag of tags) {
|
|
1879
1856
|
const selector = tag.getAttribute('selector');
|
|
1880
1857
|
const action_name = tag.getAttribute('action');
|
|
1881
1858
|
if (selector === null || action_name === null) {
|
|
@@ -1887,7 +1864,7 @@ class FslInstance extends LitElement {
|
|
|
1887
1864
|
const prevent_default = tag.hasAttribute('prevent-default');
|
|
1888
1865
|
const stop_propagation = tag.hasAttribute('stop-propagation');
|
|
1889
1866
|
const sources = this.querySelectorAll(selector);
|
|
1890
|
-
for (const src of
|
|
1867
|
+
for (const src of sources) {
|
|
1891
1868
|
this._install_action_listener({
|
|
1892
1869
|
source: src,
|
|
1893
1870
|
event_name,
|
|
@@ -1921,9 +1898,9 @@ class FslInstance extends LitElement {
|
|
|
1921
1898
|
if (config.from_state !== undefined && this.state() !== config.from_state) {
|
|
1922
1899
|
return;
|
|
1923
1900
|
}
|
|
1924
|
-
const data = config.from_property
|
|
1925
|
-
?
|
|
1926
|
-
:
|
|
1901
|
+
const data = config.from_property === undefined
|
|
1902
|
+
? undefined
|
|
1903
|
+
: config.source[config.from_property];
|
|
1927
1904
|
this.do(config.action_name, data);
|
|
1928
1905
|
};
|
|
1929
1906
|
config.source.addEventListener(config.event_name, handler);
|
|
@@ -1943,8 +1920,8 @@ class FslInstance extends LitElement {
|
|
|
1943
1920
|
_paint_state_reflection() {
|
|
1944
1921
|
// Invariant: only called after `connectedCallback` has set `_machine`.
|
|
1945
1922
|
const m = this._machine;
|
|
1946
|
-
const current_state =
|
|
1947
|
-
const legal_actions = m.list_exit_actions().map(
|
|
1923
|
+
const current_state = m.state();
|
|
1924
|
+
const legal_actions = m.list_exit_actions().map(String).join(' ');
|
|
1948
1925
|
const is_terminal = m.is_terminal();
|
|
1949
1926
|
const is_complete = m.is_complete();
|
|
1950
1927
|
this.setAttribute('current-state', current_state);
|
|
@@ -1961,17 +1938,17 @@ class FslInstance extends LitElement {
|
|
|
1961
1938
|
* and a state-specific `<slot name="state-...">` that re-targets on each
|
|
1962
1939
|
* transition. Fallback content in each slot keeps a bare
|
|
1963
1940
|
* `<jssm-instance fsl="...">` from rendering as a blank box.
|
|
1964
|
-
*
|
|
1965
1941
|
* @returns A Lit `TemplateResult` describing the shadow tree.
|
|
1966
1942
|
*/
|
|
1967
1943
|
render() {
|
|
1968
1944
|
const state_slot_name = this._machine === undefined
|
|
1969
1945
|
? 'state-unknown'
|
|
1970
|
-
: `state-${
|
|
1946
|
+
: `state-${this._machine.state()}`;
|
|
1971
1947
|
const header = html `
|
|
1972
1948
|
<header>
|
|
1973
1949
|
<slot name="title"><span class="placeholder">fsl-instance</span></slot>
|
|
1974
|
-
</header
|
|
1950
|
+
</header>
|
|
1951
|
+
`;
|
|
1975
1952
|
const viz = html `<slot name="viz"><span class="placeholder">no viz configured</span></slot>`;
|
|
1976
1953
|
const editor = html `<slot name="editor"></slot>`;
|
|
1977
1954
|
const toolbar = html `<section class="toolbar"><slot name="toolbar"></slot></section>`;
|
|
@@ -2016,16 +1993,17 @@ class FslInstance extends LitElement {
|
|
|
2016
1993
|
</div>
|
|
2017
1994
|
`;
|
|
2018
1995
|
}
|
|
2019
|
-
/**
|
|
1996
|
+
/**
|
|
1997
|
+
* The stacked middle panels, shared by both layouts. The toolbar slot is
|
|
2020
1998
|
* rendered at the top of {@link render}. In split mode the `hook-log` (events)
|
|
2021
1999
|
* and `data-inspector` panels are lifted out into easing side docks, so
|
|
2022
2000
|
* `docked` is true there and they are skipped here to avoid duplicating
|
|
2023
2001
|
* their slots; `actions` instead lives here as a horizontal bar. The
|
|
2024
2002
|
* state-section + footer stay in {@link render} so the dynamic state-slot
|
|
2025
2003
|
* name binds at the top level.
|
|
2026
|
-
*
|
|
2027
2004
|
* @param docked - True when hook-log + data-inspector are rendered as side
|
|
2028
|
-
* docks (split layouts); they are then omitted from this stack.
|
|
2005
|
+
* docks (split layouts); they are then omitted from this stack.
|
|
2006
|
+
*/
|
|
2029
2007
|
_renderAuxPanels(docked) {
|
|
2030
2008
|
const h = (slot) => this.isPanelHidden(slot);
|
|
2031
2009
|
return html `
|