jssm 5.139.0 → 5.141.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 +232 -6
- package/dist/cdn/instance.js +235 -33
- package/dist/cdn/viz.js +145 -5
- 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 +234 -32
- package/dist/wc/viz.define.js +3 -3
- package/dist/wc/viz.js +132 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,10 +18,10 @@ Please edit the file it's derived from, instead: `./src/md/readme_base.md`
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
* Generated for version 5.
|
|
21
|
+
* Generated for version 5.141.0 at 5/28/2026, 8:18:17 AM
|
|
22
22
|
|
|
23
23
|
-->
|
|
24
|
-
# jssm 5.
|
|
24
|
+
# jssm 5.141.0
|
|
25
25
|
|
|
26
26
|
[**Try the live editor**](https://stonecypher.github.io/jssm-viz-demo/graph_explorer.html) ·
|
|
27
27
|
[Documentation](https://stonecypher.github.io/jssm/docs/) ·
|
|
@@ -281,7 +281,7 @@ That decision shows up everywhere downstream:
|
|
|
281
281
|
or run `npm run benny` against your own machine.
|
|
282
282
|
|
|
283
283
|
- **More thoroughly tested than any other JavaScript state-machine
|
|
284
|
-
library.** 6,
|
|
284
|
+
library.** 6,450 tests at 100.0% line coverage
|
|
285
285
|
([report](https://coveralls.io/github/StoneCypher/jssm)), plus
|
|
286
286
|
fuzz testing via `fast-check`, with parser test data across ten natural
|
|
287
287
|
languages and Emoji.
|
|
@@ -414,11 +414,11 @@ If your contribution is missing here, please open an issue.
|
|
|
414
414
|
|
|
415
415
|
<br/>
|
|
416
416
|
|
|
417
|
-
***6,
|
|
417
|
+
***6,450 tests***, run 57,237 times.
|
|
418
418
|
|
|
419
|
-
- 5,
|
|
420
|
-
- 513 fuzz tests with 3.
|
|
421
|
-
- 5,
|
|
419
|
+
- 5,937 specs with 100.0% coverage
|
|
420
|
+
- 513 fuzz tests with 3.4% coverage
|
|
421
|
+
- 5,555 TypeScript lines - 1.2 tests per line, 10.3 generated tests per line
|
|
422
422
|
|
|
423
423
|
[](https://github.com/StoneCypher/jssm/actions)
|
|
424
424
|
[](https://www.npmjs.com/package/jssm)
|
package/custom-elements.json
CHANGED
|
@@ -2,6 +2,191 @@
|
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
3
|
"readme": "",
|
|
4
4
|
"modules": [
|
|
5
|
+
{
|
|
6
|
+
"kind": "javascript-module",
|
|
7
|
+
"path": "src/ts/wc/jssm_bind_wc.ts",
|
|
8
|
+
"declarations": [
|
|
9
|
+
{
|
|
10
|
+
"kind": "function",
|
|
11
|
+
"name": "walk_path",
|
|
12
|
+
"return": {
|
|
13
|
+
"type": {
|
|
14
|
+
"text": ""
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"parameters": [
|
|
18
|
+
{
|
|
19
|
+
"name": "obj",
|
|
20
|
+
"type": {
|
|
21
|
+
"text": "unknown"
|
|
22
|
+
},
|
|
23
|
+
"description": "The root value to traverse."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "path",
|
|
27
|
+
"type": {
|
|
28
|
+
"text": "string"
|
|
29
|
+
},
|
|
30
|
+
"description": "Dotted path of property names, e.g. `\"a.b.c\"`."
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"description": "Walk a dotted path into a value. Used by the `data.path.to.field`\r\nvariant of resolve_binding. Returns `undefined` whenever the\r\ntraversal would dereference a non-object, missing field, or `null` —\r\nmatching the natural \"missing data\" semantics rather than throwing.\r\n\r\n```typescript\r\nwalk_path({ a: { b: 7 } }, 'a.b'); // => 7\r\nwalk_path({ a: { b: 7 } }, 'a.c'); // => undefined\r\nwalk_path({ a: { b: 7 } }, 'a.b.c'); // => undefined (7 is not an object)\r\nwalk_path(undefined, 'a'); // => undefined\r\nwalk_path({ a: null }, 'a.b'); // => undefined (null is not an object)\r\nwalk_path({ a: 1 }, ''); // => { a: 1 } (empty path = identity)\r\n```"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"kind": "function",
|
|
37
|
+
"name": "resolve_binding",
|
|
38
|
+
"return": {
|
|
39
|
+
"type": {
|
|
40
|
+
"text": ""
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"parameters": [
|
|
44
|
+
{
|
|
45
|
+
"name": "m",
|
|
46
|
+
"type": {
|
|
47
|
+
"text": "Machine<unknown>"
|
|
48
|
+
},
|
|
49
|
+
"description": "The machine whose state/data is being projected."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "expr",
|
|
53
|
+
"type": {
|
|
54
|
+
"text": "string"
|
|
55
|
+
},
|
|
56
|
+
"description": "The binding expression text (raw attribute value)."
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"description": "Resolve a `<jssm-bind>` / `data-jssm-bind` expression against a live\r\nmachine. Throws on any unknown expression — bindings fail fast at\r\ninstall time rather than silently producing `undefined` strings in the\r\nDOM.\r\n\r\nRecognized expressions:\r\n\r\n| Expression | Resolves to |\r\n| ---------------- | --------------------------------------------- |\r\n| `data` | `machine.data()` |\r\n| `data.a.b.c` | dotted-path traversal into `machine.data()` |\r\n| `state` | `machine.state()` |\r\n| `terminal` | `machine.is_terminal()` |\r\n| `complete` | `machine.is_complete()` |\r\n| `legal-actions` | `machine.list_exit_actions().join(' ')` |\r\n\r\n```typescript\r\nresolve_binding(m, 'state'); // current state name\r\nresolve_binding(m, 'data.username'); // typed-data subfield\r\nresolve_binding(m, 'wat'); // throws\r\n```"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"kind": "function",
|
|
63
|
+
"name": "set_on_element",
|
|
64
|
+
"return": {
|
|
65
|
+
"type": {
|
|
66
|
+
"text": "void"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"parameters": [
|
|
70
|
+
{
|
|
71
|
+
"name": "el",
|
|
72
|
+
"type": {
|
|
73
|
+
"text": "HTMLElement"
|
|
74
|
+
},
|
|
75
|
+
"description": "The element to update."
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "target",
|
|
79
|
+
"type": {
|
|
80
|
+
"text": "string"
|
|
81
|
+
},
|
|
82
|
+
"description": "Target property name, possibly a `data-*` attribute."
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "value",
|
|
86
|
+
"type": {
|
|
87
|
+
"text": "unknown"
|
|
88
|
+
},
|
|
89
|
+
"description": "The resolved value to assign."
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"description": "Apply a resolved binding value to an element's target property. The\r\n`target` selector follows the rules documented in #645:\r\n\r\n- `textContent` (or omitted) sets `el.textContent` to the value coerced\r\n with `String()`.\r\n- Any string starting with `data-` is treated as an attribute name and\r\n set via `setAttribute`, value coerced with `String()`.\r\n- Any other string is assigned directly as a property of the element\r\n (no coercion) — supports `value`, `disabled`, `hidden`, `checked`,\r\n and the documented power-user escape hatch.\r\n\r\n```typescript\r\nset_on_element(span, 'textContent', 7); // span.textContent = '7'\r\nset_on_element(input, 'value', 'hi'); // input.value = 'hi'\r\nset_on_element(button, 'disabled', true); // button.disabled = true\r\nset_on_element(div, 'data-current', 'red'); // setAttribute('data-current', 'red')\r\n```"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"kind": "function",
|
|
96
|
+
"name": "install_bindings",
|
|
97
|
+
"return": {
|
|
98
|
+
"type": {
|
|
99
|
+
"text": ""
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"parameters": [
|
|
103
|
+
{
|
|
104
|
+
"name": "host",
|
|
105
|
+
"type": {
|
|
106
|
+
"text": "HTMLElement"
|
|
107
|
+
},
|
|
108
|
+
"description": "The host element whose descendants carry the bindings."
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "machine",
|
|
112
|
+
"type": {
|
|
113
|
+
"text": "Machine<unknown>"
|
|
114
|
+
},
|
|
115
|
+
"description": "The machine whose state/data is being projected."
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"description": "Discover every binding declaration under `host` and install live\r\nsubscriptions that refresh them on every machine transition. Returns\r\na list of unsubscribe callbacks so the host's `disconnectedCallback`\r\ncan tear them all down.\r\n\r\nTwo surface forms are recognized:\r\n\r\n1. Inline attribute — any descendant with `data-jssm-bind=\"<expr>\"`.\r\n Optional `data-jssm-bind-to=\"<target>\"` chooses the target property\r\n (defaults to `textContent`).\r\n\r\n2. Dedicated tag — direct-child `<jssm-bind>` configuration tags with\r\n `selector=\"<css>\"` and `source=\"<expr>\"` attributes, plus an\r\n optional `target=\"<target>\"` (also defaulting to `textContent`).\r\n The `selector` is scoped to `host`'s descendants.\r\n\r\nEach binding is painted once immediately (using the machine's current\r\nstate) and then re-painted on every `transition` event.\r\n\r\n```typescript\r\n// typical install during <jssm-instance>.connectedCallback:\r\nconst unsubs = install_bindings(this, this.machine);\r\nthis._unsubs.push(...unsubs);\r\n```"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"kind": "class",
|
|
122
|
+
"description": "`<jssm-bind>` configuration tag. The element itself is invisible —\r\nit carries `selector`, `source`, and optional `target` attributes\r\nthat the parent `<jssm-instance>` reads during its connection\r\nlifecycle to wire up a machine-to-DOM binding.\r\n\r\nRegistering it as a `LitElement` (rather than leaving it as a generic\r\nunknown tag) gives it a stable upgrade timing, a `display: none`\r\ndefault style, and a proper place in the custom-elements registry so\r\n`customElements.get('jssm-bind')` resolves.",
|
|
123
|
+
"name": "JssmBind",
|
|
124
|
+
"members": [],
|
|
125
|
+
"attributes": [
|
|
126
|
+
{
|
|
127
|
+
"description": "CSS selector for the target element(s), scoped to the host.",
|
|
128
|
+
"name": "selector"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"description": "Binding expression (see {@link resolve_binding}).",
|
|
132
|
+
"name": "source"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"description": "Target property name; defaults to `textContent`.",
|
|
136
|
+
"name": "target"
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
"superclass": {
|
|
140
|
+
"name": "LitElement",
|
|
141
|
+
"package": "lit"
|
|
142
|
+
},
|
|
143
|
+
"tagName": "jssm-bind",
|
|
144
|
+
"customElement": true
|
|
145
|
+
}
|
|
146
|
+
],
|
|
147
|
+
"exports": [
|
|
148
|
+
{
|
|
149
|
+
"kind": "js",
|
|
150
|
+
"name": "walk_path",
|
|
151
|
+
"declaration": {
|
|
152
|
+
"name": "walk_path",
|
|
153
|
+
"module": "src/ts/wc/jssm_bind_wc.ts"
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"kind": "js",
|
|
158
|
+
"name": "resolve_binding",
|
|
159
|
+
"declaration": {
|
|
160
|
+
"name": "resolve_binding",
|
|
161
|
+
"module": "src/ts/wc/jssm_bind_wc.ts"
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"kind": "js",
|
|
166
|
+
"name": "set_on_element",
|
|
167
|
+
"declaration": {
|
|
168
|
+
"name": "set_on_element",
|
|
169
|
+
"module": "src/ts/wc/jssm_bind_wc.ts"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"kind": "js",
|
|
174
|
+
"name": "install_bindings",
|
|
175
|
+
"declaration": {
|
|
176
|
+
"name": "install_bindings",
|
|
177
|
+
"module": "src/ts/wc/jssm_bind_wc.ts"
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"kind": "js",
|
|
182
|
+
"name": "JssmBind",
|
|
183
|
+
"declaration": {
|
|
184
|
+
"name": "JssmBind",
|
|
185
|
+
"module": "src/ts/wc/jssm_bind_wc.ts"
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
},
|
|
5
190
|
{
|
|
6
191
|
"kind": "javascript-module",
|
|
7
192
|
"path": "src/ts/wc/jssm_hook_wc.ts",
|
|
@@ -421,6 +606,16 @@
|
|
|
421
606
|
"default": "undefined",
|
|
422
607
|
"description": "The underlying machine instance, constructed at `connectedCallback`.\r\nExposed raw (not proxied) per the #639/#648 design decision so that\r\nconsumers can use the full Machine API directly.\r\n\r\nMarked optional because Lit will instantiate the element before\r\n`connectedCallback` runs; the instance is guaranteed present after\r\nconnection."
|
|
423
608
|
},
|
|
609
|
+
{
|
|
610
|
+
"kind": "field",
|
|
611
|
+
"name": "_unsubs",
|
|
612
|
+
"type": {
|
|
613
|
+
"text": "JssmBindUnsub[]"
|
|
614
|
+
},
|
|
615
|
+
"privacy": "private",
|
|
616
|
+
"default": "[]",
|
|
617
|
+
"description": "Live unsubscribe callbacks for #645 `<jssm-bind>` / `data-jssm-bind`\r\nprojections. Every entry must be invoked exactly once during\r\ndisconnectedCallback."
|
|
618
|
+
},
|
|
424
619
|
{
|
|
425
620
|
"kind": "field",
|
|
426
621
|
"name": "_on_unsubscribes",
|
|
@@ -429,7 +624,7 @@
|
|
|
429
624
|
},
|
|
430
625
|
"privacy": "private",
|
|
431
626
|
"default": "[]",
|
|
432
|
-
"description": "Unsubscribe callbacks for every `machine.on(...)` / `machine.once(...)`\r\nsubscription installed from a `<jssm-on>` child during\r\n`connectedCallback`. Walked in `disconnectedCallback
|
|
627
|
+
"description": "Unsubscribe callbacks for every `machine.on(...)` / `machine.once(...)`\r\nsubscription installed from a `<jssm-on>` child during\r\n`connectedCallback`. Walked in `disconnectedCallback`."
|
|
433
628
|
},
|
|
434
629
|
{
|
|
435
630
|
"kind": "field",
|
|
@@ -439,7 +634,7 @@
|
|
|
439
634
|
},
|
|
440
635
|
"readonly": true,
|
|
441
636
|
"default": "new Map()",
|
|
442
|
-
"description": "Per-instance registry of named hook handlers consulted before\r\n`globalThis` when resolving `<jssm-hook handler=\"name\"
|
|
637
|
+
"description": "Per-instance registry of named hook handlers consulted before\r\n`globalThis` when resolving `<jssm-hook handler=\"name\">`."
|
|
443
638
|
},
|
|
444
639
|
{
|
|
445
640
|
"kind": "field",
|
|
@@ -449,7 +644,7 @@
|
|
|
449
644
|
},
|
|
450
645
|
"privacy": "private",
|
|
451
646
|
"default": "[]",
|
|
452
|
-
"description": "Descriptors for hooks this WC installed at connect time
|
|
647
|
+
"description": "Descriptors for hooks this WC installed at connect time."
|
|
453
648
|
},
|
|
454
649
|
{
|
|
455
650
|
"kind": "field",
|
|
@@ -459,7 +654,7 @@
|
|
|
459
654
|
},
|
|
460
655
|
"privacy": "private",
|
|
461
656
|
"default": "0",
|
|
462
|
-
"description": "Counter
|
|
657
|
+
"description": "Counter for compiled inline-body hook debug ids."
|
|
463
658
|
},
|
|
464
659
|
{
|
|
465
660
|
"kind": "field",
|
|
@@ -469,7 +664,7 @@
|
|
|
469
664
|
},
|
|
470
665
|
"privacy": "private",
|
|
471
666
|
"default": "[]",
|
|
472
|
-
"description": "
|
|
667
|
+
"description": "DOM listeners installed by `<jssm-action>` / `data-jssm-action` discovery."
|
|
473
668
|
},
|
|
474
669
|
{
|
|
475
670
|
"kind": "field",
|
|
@@ -695,7 +890,7 @@
|
|
|
695
890
|
},
|
|
696
891
|
{
|
|
697
892
|
"kind": "class",
|
|
698
|
-
"description": "Web component that renders a jssm machine as inline SVG.",
|
|
893
|
+
"description": "Web component that renders a jssm machine as inline SVG.\r\n\r\nTwo operating modes:\r\n\r\n 1. **Standalone** (no parent `<jssm-instance>` ancestor): render from\r\n the element's own `fsl=\"\"` attribute / property. Re-renders on\r\n attribute change.\r\n 2. **Nested** (inside a `<jssm-instance>` ancestor, found via\r\n `closest('jssm-instance')` at `connectedCallback`): bind to the\r\n parent's machine and re-render on every `transition` event. The\r\n element's own `fsl` attribute is ignored in this mode; supplying it\r\n emits a `console.warn` for developer feedback.",
|
|
699
894
|
"name": "JssmViz",
|
|
700
895
|
"cssProperties": [
|
|
701
896
|
{
|
|
@@ -734,6 +929,37 @@
|
|
|
734
929
|
"privacy": "private",
|
|
735
930
|
"default": "''"
|
|
736
931
|
},
|
|
932
|
+
{
|
|
933
|
+
"kind": "field",
|
|
934
|
+
"name": "_parent_host",
|
|
935
|
+
"type": {
|
|
936
|
+
"text": "JssmInstanceHost | null"
|
|
937
|
+
},
|
|
938
|
+
"privacy": "private",
|
|
939
|
+
"default": "null",
|
|
940
|
+
"description": "Parent `<jssm-instance>` host reference, set in `connectedCallback`\r\nwhen a parent is found. When non-null the viz is in nested mode and\r\nrenders the parent's machine instead of its own `fsl` attribute."
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
"kind": "field",
|
|
944
|
+
"name": "_parent_sub",
|
|
945
|
+
"type": {
|
|
946
|
+
"text": "(() => void) | null"
|
|
947
|
+
},
|
|
948
|
+
"privacy": "private",
|
|
949
|
+
"default": "null",
|
|
950
|
+
"description": "Unsubscribe callback returned from `host.machine.on('transition', ...)`.\r\nHeld so `disconnectedCallback` can release the subscription."
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
"kind": "method",
|
|
954
|
+
"name": "_rerenderFromHostMachine",
|
|
955
|
+
"privacy": "private",
|
|
956
|
+
"return": {
|
|
957
|
+
"type": {
|
|
958
|
+
"text": ""
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
"description": "Nested-mode render path. Renders the bound parent's machine via the\r\nmachine_to_svg_string pipeline and commits the result to\r\n`_svg`. On failure emits a `viz-error` `CustomEvent` and clears the\r\nSVG."
|
|
962
|
+
},
|
|
737
963
|
{
|
|
738
964
|
"kind": "method",
|
|
739
965
|
"name": "_renderSvg",
|
package/dist/cdn/instance.js
CHANGED
|
@@ -21327,7 +21327,7 @@ var constants = /*#__PURE__*/Object.freeze({
|
|
|
21327
21327
|
* Useful for runtime diagnostics and for embedding in serialized machine
|
|
21328
21328
|
* snapshots so that deserializers can detect version-skew.
|
|
21329
21329
|
*/
|
|
21330
|
-
const version = "5.
|
|
21330
|
+
const version = "5.141.0";
|
|
21331
21331
|
|
|
21332
21332
|
// whargarbl lots of these return arrays could/should be sets
|
|
21333
21333
|
const { state_name_chars, state_name_first_chars, action_label_chars } = constants;
|
|
@@ -25070,6 +25070,222 @@ function abstract_everything_hook_step(maybe_hook, hook_args) {
|
|
|
25070
25070
|
}
|
|
25071
25071
|
}
|
|
25072
25072
|
|
|
25073
|
+
/**
|
|
25074
|
+
* Walk a dotted path into a value. Used by the `data.path.to.field`
|
|
25075
|
+
* variant of {@link resolve_binding}. Returns `undefined` whenever the
|
|
25076
|
+
* traversal would dereference a non-object, missing field, or `null` —
|
|
25077
|
+
* matching the natural "missing data" semantics rather than throwing.
|
|
25078
|
+
*
|
|
25079
|
+
* ```typescript
|
|
25080
|
+
* walk_path({ a: { b: 7 } }, 'a.b'); // => 7
|
|
25081
|
+
* walk_path({ a: { b: 7 } }, 'a.c'); // => undefined
|
|
25082
|
+
* walk_path({ a: { b: 7 } }, 'a.b.c'); // => undefined (7 is not an object)
|
|
25083
|
+
* walk_path(undefined, 'a'); // => undefined
|
|
25084
|
+
* walk_path({ a: null }, 'a.b'); // => undefined (null is not an object)
|
|
25085
|
+
* walk_path({ a: 1 }, ''); // => { a: 1 } (empty path = identity)
|
|
25086
|
+
* ```
|
|
25087
|
+
*
|
|
25088
|
+
* @param obj - The root value to traverse.
|
|
25089
|
+
* @param path - Dotted path of property names, e.g. `"a.b.c"`.
|
|
25090
|
+
* @returns The terminal value, or `undefined` if any step fails.
|
|
25091
|
+
*/
|
|
25092
|
+
function walk_path(obj, path) {
|
|
25093
|
+
if (path.length === 0) {
|
|
25094
|
+
return obj;
|
|
25095
|
+
}
|
|
25096
|
+
let cur = obj;
|
|
25097
|
+
for (const part of path.split('.')) {
|
|
25098
|
+
if (cur === null || typeof cur !== 'object') {
|
|
25099
|
+
return undefined;
|
|
25100
|
+
}
|
|
25101
|
+
cur = cur[part];
|
|
25102
|
+
}
|
|
25103
|
+
return cur;
|
|
25104
|
+
}
|
|
25105
|
+
/**
|
|
25106
|
+
* Resolve a `<jssm-bind>` / `data-jssm-bind` expression against a live
|
|
25107
|
+
* machine. Throws on any unknown expression — bindings fail fast at
|
|
25108
|
+
* install time rather than silently producing `undefined` strings in the
|
|
25109
|
+
* DOM.
|
|
25110
|
+
*
|
|
25111
|
+
* Recognized expressions:
|
|
25112
|
+
*
|
|
25113
|
+
* | Expression | Resolves to |
|
|
25114
|
+
* | ---------------- | --------------------------------------------- |
|
|
25115
|
+
* | `data` | `machine.data()` |
|
|
25116
|
+
* | `data.a.b.c` | dotted-path traversal into `machine.data()` |
|
|
25117
|
+
* | `state` | `machine.state()` |
|
|
25118
|
+
* | `terminal` | `machine.is_terminal()` |
|
|
25119
|
+
* | `complete` | `machine.is_complete()` |
|
|
25120
|
+
* | `legal-actions` | `machine.list_exit_actions().join(' ')` |
|
|
25121
|
+
*
|
|
25122
|
+
* ```typescript
|
|
25123
|
+
* resolve_binding(m, 'state'); // current state name
|
|
25124
|
+
* resolve_binding(m, 'data.username'); // typed-data subfield
|
|
25125
|
+
* resolve_binding(m, 'wat'); // throws
|
|
25126
|
+
* ```
|
|
25127
|
+
*
|
|
25128
|
+
* @param m - The machine whose state/data is being projected.
|
|
25129
|
+
* @param expr - The binding expression text (raw attribute value).
|
|
25130
|
+
* @returns The resolved value, typed `unknown` since each expression
|
|
25131
|
+
* yields a different shape.
|
|
25132
|
+
*
|
|
25133
|
+
* @throws Error - When `expr` is not a recognized binding form.
|
|
25134
|
+
*/
|
|
25135
|
+
function resolve_binding(m, expr) {
|
|
25136
|
+
switch (expr) {
|
|
25137
|
+
case 'state': return m.state();
|
|
25138
|
+
case 'terminal': return m.is_terminal();
|
|
25139
|
+
case 'complete': return m.is_complete();
|
|
25140
|
+
case 'legal-actions': return m.list_exit_actions().map(a => String(a)).join(' ');
|
|
25141
|
+
case 'data': return m.data();
|
|
25142
|
+
default:
|
|
25143
|
+
if (expr.startsWith('data.')) {
|
|
25144
|
+
return walk_path(m.data(), expr.slice(5));
|
|
25145
|
+
}
|
|
25146
|
+
throw new Error(`<jssm-bind>: unknown binding expression "${expr}"`);
|
|
25147
|
+
}
|
|
25148
|
+
}
|
|
25149
|
+
/**
|
|
25150
|
+
* Apply a resolved binding value to an element's target property. The
|
|
25151
|
+
* `target` selector follows the rules documented in #645:
|
|
25152
|
+
*
|
|
25153
|
+
* - `textContent` (or omitted) sets `el.textContent` to the value coerced
|
|
25154
|
+
* with `String()`.
|
|
25155
|
+
* - Any string starting with `data-` is treated as an attribute name and
|
|
25156
|
+
* set via `setAttribute`, value coerced with `String()`.
|
|
25157
|
+
* - Any other string is assigned directly as a property of the element
|
|
25158
|
+
* (no coercion) — supports `value`, `disabled`, `hidden`, `checked`,
|
|
25159
|
+
* and the documented power-user escape hatch.
|
|
25160
|
+
*
|
|
25161
|
+
* ```typescript
|
|
25162
|
+
* set_on_element(span, 'textContent', 7); // span.textContent = '7'
|
|
25163
|
+
* set_on_element(input, 'value', 'hi'); // input.value = 'hi'
|
|
25164
|
+
* set_on_element(button, 'disabled', true); // button.disabled = true
|
|
25165
|
+
* set_on_element(div, 'data-current', 'red'); // setAttribute('data-current', 'red')
|
|
25166
|
+
* ```
|
|
25167
|
+
*
|
|
25168
|
+
* @param el - The element to update.
|
|
25169
|
+
* @param target - Target property name, possibly a `data-*` attribute.
|
|
25170
|
+
* @param value - The resolved value to assign.
|
|
25171
|
+
*/
|
|
25172
|
+
function set_on_element(el, target, value) {
|
|
25173
|
+
if (target.startsWith('data-')) {
|
|
25174
|
+
el.setAttribute(target, String(value));
|
|
25175
|
+
}
|
|
25176
|
+
else if (target === 'textContent') {
|
|
25177
|
+
el.textContent = String(value);
|
|
25178
|
+
}
|
|
25179
|
+
else {
|
|
25180
|
+
// Power-user escape hatch — assigns value as-is so booleans hit
|
|
25181
|
+
// properties like `disabled`/`hidden`/`checked` with the correct
|
|
25182
|
+
// semantics rather than being coerced to a string.
|
|
25183
|
+
el[target] = value;
|
|
25184
|
+
}
|
|
25185
|
+
}
|
|
25186
|
+
/**
|
|
25187
|
+
* Discover every binding declaration under `host` and install live
|
|
25188
|
+
* subscriptions that refresh them on every machine transition. Returns
|
|
25189
|
+
* a list of unsubscribe callbacks so the host's `disconnectedCallback`
|
|
25190
|
+
* can tear them all down.
|
|
25191
|
+
*
|
|
25192
|
+
* Two surface forms are recognized:
|
|
25193
|
+
*
|
|
25194
|
+
* 1. Inline attribute — any descendant with `data-jssm-bind="<expr>"`.
|
|
25195
|
+
* Optional `data-jssm-bind-to="<target>"` chooses the target property
|
|
25196
|
+
* (defaults to `textContent`).
|
|
25197
|
+
*
|
|
25198
|
+
* 2. Dedicated tag — direct-child `<jssm-bind>` configuration tags with
|
|
25199
|
+
* `selector="<css>"` and `source="<expr>"` attributes, plus an
|
|
25200
|
+
* optional `target="<target>"` (also defaulting to `textContent`).
|
|
25201
|
+
* The `selector` is scoped to `host`'s descendants.
|
|
25202
|
+
*
|
|
25203
|
+
* Each binding is painted once immediately (using the machine's current
|
|
25204
|
+
* state) and then re-painted on every `transition` event.
|
|
25205
|
+
*
|
|
25206
|
+
* ```typescript
|
|
25207
|
+
* // typical install during <jssm-instance>.connectedCallback:
|
|
25208
|
+
* const unsubs = install_bindings(this, this.machine);
|
|
25209
|
+
* this._unsubs.push(...unsubs);
|
|
25210
|
+
* ```
|
|
25211
|
+
*
|
|
25212
|
+
* @param host - The host element whose descendants carry the bindings.
|
|
25213
|
+
* @param machine - The machine whose state/data is being projected.
|
|
25214
|
+
* @returns A flat array of unsubscribe callbacks, one per installed
|
|
25215
|
+
* subscription.
|
|
25216
|
+
*
|
|
25217
|
+
* @throws Error - When any binding expression is unrecognized
|
|
25218
|
+
* (propagated from {@link resolve_binding}).
|
|
25219
|
+
* @throws Error - When a `<jssm-bind>` tag is missing its `selector`
|
|
25220
|
+
* or `source` attribute.
|
|
25221
|
+
*/
|
|
25222
|
+
function install_bindings(host, machine) {
|
|
25223
|
+
var _a, _b;
|
|
25224
|
+
const unsubs = [];
|
|
25225
|
+
// Form 1: inline `data-jssm-bind` on descendants.
|
|
25226
|
+
const inline_nodes = host.querySelectorAll('[data-jssm-bind]');
|
|
25227
|
+
for (const el of Array.from(inline_nodes)) {
|
|
25228
|
+
const expr = el.dataset.jssmBind;
|
|
25229
|
+
const target = (_a = el.dataset.jssmBindTo) !== null && _a !== void 0 ? _a : 'textContent';
|
|
25230
|
+
const apply = () => {
|
|
25231
|
+
set_on_element(el, target, resolve_binding(machine, expr));
|
|
25232
|
+
};
|
|
25233
|
+
apply();
|
|
25234
|
+
unsubs.push(machine.on('transition', apply));
|
|
25235
|
+
}
|
|
25236
|
+
// Form 2: dedicated `<jssm-bind>` configuration tags. Only direct
|
|
25237
|
+
// children are considered configuration tags for THIS host — nested
|
|
25238
|
+
// `<jssm-instance>` children would have their own bindings handled by
|
|
25239
|
+
// their own component.
|
|
25240
|
+
const config_tags = host.querySelectorAll(':scope > jssm-bind');
|
|
25241
|
+
for (const tag of Array.from(config_tags)) {
|
|
25242
|
+
const selector = tag.getAttribute('selector');
|
|
25243
|
+
const expr = tag.getAttribute('source');
|
|
25244
|
+
const target = (_b = tag.getAttribute('target')) !== null && _b !== void 0 ? _b : 'textContent';
|
|
25245
|
+
if (selector === null || selector.length === 0) {
|
|
25246
|
+
throw new Error('<jssm-bind>: missing required "selector" attribute');
|
|
25247
|
+
}
|
|
25248
|
+
if (expr === null || expr.length === 0) {
|
|
25249
|
+
throw new Error('<jssm-bind>: missing required "source" attribute');
|
|
25250
|
+
}
|
|
25251
|
+
const targets = host.querySelectorAll(selector);
|
|
25252
|
+
for (const el of Array.from(targets)) {
|
|
25253
|
+
const apply = () => {
|
|
25254
|
+
set_on_element(el, target, resolve_binding(machine, expr));
|
|
25255
|
+
};
|
|
25256
|
+
apply();
|
|
25257
|
+
unsubs.push(machine.on('transition', apply));
|
|
25258
|
+
}
|
|
25259
|
+
}
|
|
25260
|
+
return unsubs;
|
|
25261
|
+
}
|
|
25262
|
+
/**
|
|
25263
|
+
* `<jssm-bind>` configuration tag. The element itself is invisible —
|
|
25264
|
+
* it carries `selector`, `source`, and optional `target` attributes
|
|
25265
|
+
* that the parent `<jssm-instance>` reads during its connection
|
|
25266
|
+
* lifecycle to wire up a machine-to-DOM binding.
|
|
25267
|
+
*
|
|
25268
|
+
* Registering it as a `LitElement` (rather than leaving it as a generic
|
|
25269
|
+
* unknown tag) gives it a stable upgrade timing, a `display: none`
|
|
25270
|
+
* default style, and a proper place in the custom-elements registry so
|
|
25271
|
+
* `customElements.get('jssm-bind')` resolves.
|
|
25272
|
+
*
|
|
25273
|
+
* @element jssm-bind
|
|
25274
|
+
* @attribute selector - CSS selector for the target element(s), scoped to the host.
|
|
25275
|
+
* @attribute source - Binding expression (see {@link resolve_binding}).
|
|
25276
|
+
* @attribute target - Target property name; defaults to `textContent`.
|
|
25277
|
+
*/
|
|
25278
|
+
class JssmBind extends i {
|
|
25279
|
+
/**
|
|
25280
|
+
* No-op render. The tag's purpose is purely declarative
|
|
25281
|
+
* configuration; it must not contribute any DOM to the page.
|
|
25282
|
+
*/
|
|
25283
|
+
render() {
|
|
25284
|
+
return null;
|
|
25285
|
+
}
|
|
25286
|
+
}
|
|
25287
|
+
JssmBind.styles = i$3 `:host { display: none; }`;
|
|
25288
|
+
|
|
25073
25289
|
const VALID_KINDS = new Set([
|
|
25074
25290
|
'hook',
|
|
25075
25291
|
'named',
|
|
@@ -25558,53 +25774,33 @@ class JssmInstance extends i {
|
|
|
25558
25774
|
* connection.
|
|
25559
25775
|
*/
|
|
25560
25776
|
this._machine = undefined;
|
|
25777
|
+
/**
|
|
25778
|
+
* Live unsubscribe callbacks for #645 `<jssm-bind>` / `data-jssm-bind`
|
|
25779
|
+
* projections. Every entry must be invoked exactly once during
|
|
25780
|
+
* {@link disconnectedCallback}.
|
|
25781
|
+
*/
|
|
25782
|
+
this._unsubs = [];
|
|
25561
25783
|
/**
|
|
25562
25784
|
* Unsubscribe callbacks for every `machine.on(...)` / `machine.once(...)`
|
|
25563
25785
|
* subscription installed from a `<jssm-on>` child during
|
|
25564
|
-
* `connectedCallback`. Walked in `disconnectedCallback
|
|
25565
|
-
* `<jssm-instance>` doesn't leave dangling handlers on its (now-orphan)
|
|
25566
|
-
* machine. Array (insertion order) rather than Set so cleanup order is
|
|
25567
|
-
* deterministic and easy to reason about.
|
|
25786
|
+
* `connectedCallback`. Walked in `disconnectedCallback`.
|
|
25568
25787
|
*/
|
|
25569
25788
|
this._on_unsubscribes = [];
|
|
25570
25789
|
/**
|
|
25571
25790
|
* Per-instance registry of named hook handlers consulted before
|
|
25572
25791
|
* `globalThis` when resolving `<jssm-hook handler="name">`.
|
|
25573
|
-
*
|
|
25574
|
-
* Initialized to an empty `Map`; consumers may populate it before the
|
|
25575
|
-
* element connects to provide handlers without polluting global scope —
|
|
25576
|
-
* useful for module-scoped SPAs where strict CSP blocks inline-body hooks.
|
|
25577
|
-
*
|
|
25578
|
-
* @see {@link parse_hook_element}
|
|
25579
25792
|
*/
|
|
25580
25793
|
this.registry = new Map();
|
|
25581
25794
|
/**
|
|
25582
|
-
* Descriptors for hooks this WC installed at connect time
|
|
25583
|
-
* `disconnectedCallback` to call `remove_hook` for each so the underlying
|
|
25584
|
-
* machine doesn't leak handlers when the element is detached.
|
|
25585
|
-
*
|
|
25586
|
-
* Captured at install time because `remove_hook` matches by descriptor
|
|
25587
|
-
* shape (not handler identity), and we need to record the wrapped handler
|
|
25588
|
-
* we passed to `set_hook` to undo the registration cleanly. Stored as
|
|
25589
|
-
* `unknown[]` and cast at the call site because jssm's `HookDescription`
|
|
25590
|
-
* is a discriminated union whose discriminator is only known at runtime.
|
|
25795
|
+
* Descriptors for hooks this WC installed at connect time.
|
|
25591
25796
|
*/
|
|
25592
25797
|
this._installed_hooks = [];
|
|
25593
25798
|
/**
|
|
25594
|
-
* Counter
|
|
25595
|
-
* for its `//# sourceURL=jssm-hook:N` annotation. Per-instance so that
|
|
25596
|
-
* multiple `<jssm-instance>` elements on a page don't share numbering.
|
|
25799
|
+
* Counter for compiled inline-body hook debug ids.
|
|
25597
25800
|
*/
|
|
25598
25801
|
this._hook_debug_counter = 0;
|
|
25599
25802
|
/**
|
|
25600
|
-
*
|
|
25601
|
-
* discovery so {@link disconnectedCallback} can remove each one with the
|
|
25602
|
-
* same handler reference originally passed to `addEventListener`.
|
|
25603
|
-
*
|
|
25604
|
-
* Listeners installed via the dedicated `<jssm-action>` tag form may target
|
|
25605
|
-
* elements outside the host (its `selector` is resolved against the host,
|
|
25606
|
-
* but matching elements live in the document tree), so cleanup must be
|
|
25607
|
-
* explicit — relying on the host's GC is not sufficient.
|
|
25803
|
+
* DOM listeners installed by `<jssm-action>` / `data-jssm-action` discovery.
|
|
25608
25804
|
*/
|
|
25609
25805
|
this._action_listeners = [];
|
|
25610
25806
|
}
|
|
@@ -25675,7 +25871,9 @@ class JssmInstance extends i {
|
|
|
25675
25871
|
this._install_declarative_hooks();
|
|
25676
25872
|
// #643: <jssm-on> declarative event observation.
|
|
25677
25873
|
this._install_jssm_on_children();
|
|
25678
|
-
//
|
|
25874
|
+
// #645: discover <jssm-bind> tags and `data-jssm-bind` descendants,
|
|
25875
|
+
// install live machine-to-DOM projections.
|
|
25876
|
+
this._unsubs.push(...install_bindings(this, this._machine));
|
|
25679
25877
|
// #640: <jssm-action> DOM event → machine action wiring.
|
|
25680
25878
|
this._discover_jssm_actions();
|
|
25681
25879
|
}
|
|
@@ -25771,7 +25969,11 @@ class JssmInstance extends i {
|
|
|
25771
25969
|
catch ( /* swallow — cleanup must not throw past us */_a) { /* swallow — cleanup must not throw past us */ }
|
|
25772
25970
|
}
|
|
25773
25971
|
this._on_unsubscribes = [];
|
|
25774
|
-
//
|
|
25972
|
+
// #645: tear down every live binding.
|
|
25973
|
+
for (const off of this._unsubs) {
|
|
25974
|
+
off();
|
|
25975
|
+
}
|
|
25976
|
+
this._unsubs = [];
|
|
25775
25977
|
// #640: remove DOM listeners installed via <jssm-action> / data-jssm-action.
|
|
25776
25978
|
for (const entry of this._action_listeners) {
|
|
25777
25979
|
entry.target.removeEventListener(entry.event, entry.handler);
|