jssm 5.134.0 → 5.136.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 +8 -8
- package/custom-elements.json +188 -0
- package/dist/cdn/instance.js +25361 -0
- package/dist/cdn/viz.js +19 -2
- package/dist/cli/fsl-render.cjs +1 -1
- package/dist/cli/fsl.cjs +1 -1
- package/dist/deno/README.md +8 -8
- 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.define.js +6 -0
- package/dist/wc/instance.js +288 -0
- package/dist/wc/viz.define.js +19 -0
- package/package.json +16 -2
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { css, LitElement, html } from 'lit';
|
|
2
|
+
import { sm } from 'jssm';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a `<jssm-instance>`'s FSL source from the three legal channels:
|
|
6
|
+
* the `fsl=""` attribute, a child `<script type="text/fsl">`, and the
|
|
7
|
+
* element's own text content (after stripping the script and any
|
|
8
|
+
* `<jssm-*>` companion tags). Exactly one channel may be used; using
|
|
9
|
+
* none or more than one is an error.
|
|
10
|
+
*
|
|
11
|
+
* Pulled out as a pure function so it's testable without spinning up a
|
|
12
|
+
* Lit element.
|
|
13
|
+
*
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const div = document.createElement('div');
|
|
16
|
+
* div.setAttribute('fsl', 'Off -> On;');
|
|
17
|
+
* resolve_fsl_source(div as HTMLElement, 'Off -> On;');
|
|
18
|
+
* // => { fsl: 'Off -> On;', provided_count: 1, error: undefined }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @param host - The `<jssm-instance>` element being resolved.
|
|
22
|
+
* @param fsl_attr - The current value of the host's `fsl` attribute (or property), or empty string.
|
|
23
|
+
* @returns A {@link JssmInstanceFslResolution} describing the outcome.
|
|
24
|
+
*/
|
|
25
|
+
function resolve_fsl_source(host, fsl_attr) {
|
|
26
|
+
const sources = [];
|
|
27
|
+
// Channel 1: fsl="" attribute / property.
|
|
28
|
+
if (typeof fsl_attr === 'string' && fsl_attr.trim().length > 0) {
|
|
29
|
+
sources.push({ kind: 'fsl-attribute', fsl: fsl_attr });
|
|
30
|
+
}
|
|
31
|
+
// Channel 2: <script type="text/fsl"> child.
|
|
32
|
+
const script_child = host.querySelector('script[type="text/fsl"]');
|
|
33
|
+
if (script_child !== null) {
|
|
34
|
+
const text = (script_child.textContent || '').trim();
|
|
35
|
+
if (text.length > 0) {
|
|
36
|
+
sources.push({ kind: 'fsl-script', fsl: text });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Channel 3: textContent of the host, EXCLUDING the script-child and any
|
|
40
|
+
// <jssm-*> companion tags. We clone the host and strip those nodes before
|
|
41
|
+
// reading textContent so the consumer's literal FSL is not contaminated
|
|
42
|
+
// by companion-tag markup.
|
|
43
|
+
const text_content_fsl = (function extract_text_fsl() {
|
|
44
|
+
const clone = host.cloneNode(true);
|
|
45
|
+
// Drop every script tag (any type — we only want raw text FSL here).
|
|
46
|
+
clone.querySelectorAll('script').forEach(n => n.remove());
|
|
47
|
+
// Drop every <jssm-*> companion tag (e.g. <jssm-hook>, <jssm-on>, etc.).
|
|
48
|
+
clone.querySelectorAll('*').forEach(n => {
|
|
49
|
+
if (n.tagName.toLowerCase().startsWith('jssm-')) {
|
|
50
|
+
n.remove();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
return (clone.textContent || '').trim();
|
|
54
|
+
})();
|
|
55
|
+
if (text_content_fsl.length > 0) {
|
|
56
|
+
sources.push({ kind: 'text-content', fsl: text_content_fsl });
|
|
57
|
+
}
|
|
58
|
+
if (sources.length === 0) {
|
|
59
|
+
return { fsl: undefined, provided_count: 0, error: 'no FSL source' };
|
|
60
|
+
}
|
|
61
|
+
if (sources.length > 1) {
|
|
62
|
+
return {
|
|
63
|
+
fsl: undefined,
|
|
64
|
+
provided_count: sources.length,
|
|
65
|
+
error: `use exactly one source (found ${sources.length}: ${sources.map(s => s.kind).join(', ')})`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// sources.length === 1, so the non-null assertion is sound; `noUncheckedIndexedAccess`
|
|
69
|
+
// widens the indexed type to include `undefined`, but the length check above
|
|
70
|
+
// narrows the runtime invariant.
|
|
71
|
+
const only = sources[0];
|
|
72
|
+
return { fsl: only.fsl, provided_count: 1, error: undefined };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Web component that owns a single `Machine<unknown>` constructed from an
|
|
76
|
+
* FSL source supplied via one of three mutually exclusive channels:
|
|
77
|
+
*
|
|
78
|
+
* 1. The `fsl=""` attribute,
|
|
79
|
+
* 2. A child `<script type="text/fsl">`,
|
|
80
|
+
* 3. The element's own text content (companion `<jssm-*>` children and
|
|
81
|
+
* any `<script type="text/fsl">` are excluded from this channel).
|
|
82
|
+
*
|
|
83
|
+
* Supplying zero or more than one channel is a thrown error.
|
|
84
|
+
*
|
|
85
|
+
* On every transition the component reflects machine state to its own
|
|
86
|
+
* attributes (`current-state`, `legal-actions`, `terminal`, `complete`)
|
|
87
|
+
* and sets a `--current-state` CSS custom property so consumer CSS can
|
|
88
|
+
* style by state without subclassing.
|
|
89
|
+
*
|
|
90
|
+
* @element jssm-instance
|
|
91
|
+
* @cssproperty [--current-state] - The machine's current state name as a CSS string token.
|
|
92
|
+
* @slot title - Heading area for the instance.
|
|
93
|
+
* @slot viz - Visualization slot; fallback is a placeholder string.
|
|
94
|
+
* @slot editor - Editor surface slot.
|
|
95
|
+
* @slot actions - Slot for action buttons / UI.
|
|
96
|
+
* @slot toolbar - Slot for toolbar UI.
|
|
97
|
+
* @slot info-panel - Slot for an info / status panel.
|
|
98
|
+
* @slot footer - Footer slot.
|
|
99
|
+
*/
|
|
100
|
+
class JssmInstance extends LitElement {
|
|
101
|
+
constructor() {
|
|
102
|
+
super(...arguments);
|
|
103
|
+
/**
|
|
104
|
+
* FSL source attribute. When non-empty, this is the sole channel
|
|
105
|
+
* supplying the machine's source. Setting both this and a child
|
|
106
|
+
* `<script type="text/fsl">` (or non-empty text content) is an error.
|
|
107
|
+
*/
|
|
108
|
+
this.fsl = '';
|
|
109
|
+
/**
|
|
110
|
+
* The underlying machine instance, constructed at `connectedCallback`.
|
|
111
|
+
* Exposed raw (not proxied) per the #639/#648 design decision so that
|
|
112
|
+
* consumers can use the full {@link Machine} API directly.
|
|
113
|
+
*
|
|
114
|
+
* Marked optional because Lit will instantiate the element before
|
|
115
|
+
* `connectedCallback` runs; the instance is guaranteed present after
|
|
116
|
+
* connection.
|
|
117
|
+
*/
|
|
118
|
+
this._machine = undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Raw machine accessor. Returns the owned {@link Machine} instance.
|
|
122
|
+
*
|
|
123
|
+
* @throws If accessed before the element has been connected.
|
|
124
|
+
*/
|
|
125
|
+
get machine() {
|
|
126
|
+
if (this._machine === undefined) {
|
|
127
|
+
throw new Error('jssm-instance: machine accessed before connection');
|
|
128
|
+
}
|
|
129
|
+
return this._machine;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Convenience wrapper for `machine.action(action, data)`.
|
|
133
|
+
* After the action, reflects updated state to host attributes and the
|
|
134
|
+
* `--current-state` CSS custom property, and requests a Lit update so
|
|
135
|
+
* the state-specific `<slot name="state-...">` can re-pick.
|
|
136
|
+
*
|
|
137
|
+
* @param action - The action name to dispatch.
|
|
138
|
+
* @param data - Optional data payload to pass to the action.
|
|
139
|
+
* @returns `true` if the action succeeded, `false` otherwise.
|
|
140
|
+
*/
|
|
141
|
+
do(action, data) {
|
|
142
|
+
const result = this.machine.action(action, data);
|
|
143
|
+
this._paint_state_reflection();
|
|
144
|
+
this.requestUpdate();
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Convenience wrapper for `machine.state()`. Returns the current
|
|
149
|
+
* state's name.
|
|
150
|
+
*/
|
|
151
|
+
state() {
|
|
152
|
+
return String(this.machine.state());
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Lifecycle hook. Resolves the FSL source, constructs the machine,
|
|
156
|
+
* paints initial reflection, then defers shadow-DOM rendering to Lit.
|
|
157
|
+
*
|
|
158
|
+
* Order is important: state reflection happens BEFORE the first render
|
|
159
|
+
* so that consumer CSS rules keyed off `[current-state="..."]` apply on
|
|
160
|
+
* first paint without a flash of unstyled content.
|
|
161
|
+
*
|
|
162
|
+
* @throws If no FSL source was provided, or if more than one channel
|
|
163
|
+
* supplied a source.
|
|
164
|
+
*/
|
|
165
|
+
connectedCallback() {
|
|
166
|
+
super.connectedCallback();
|
|
167
|
+
// Step 1: resolve FSL source.
|
|
168
|
+
const resolved = resolve_fsl_source(this, this.fsl);
|
|
169
|
+
if (resolved.error !== undefined) {
|
|
170
|
+
throw new Error(`jssm-instance: ${resolved.error}`);
|
|
171
|
+
}
|
|
172
|
+
// Step 2: construct the machine.
|
|
173
|
+
// (The resolver guarantees `fsl` is a non-empty string when error is undefined.)
|
|
174
|
+
const fsl_source = resolved.fsl;
|
|
175
|
+
this._machine = sm `${fsl_source}`;
|
|
176
|
+
// Step 3: paint initial host attributes + CSS custom properties.
|
|
177
|
+
this._paint_state_reflection();
|
|
178
|
+
// Step 4: shadow DOM render is automatic via Lit; requesting an update
|
|
179
|
+
// here ensures the first paint sees the freshly painted attributes.
|
|
180
|
+
this.requestUpdate();
|
|
181
|
+
// TODO #638: subscribe to machine.on('transition', ...) once available
|
|
182
|
+
// and dispatch DOM CustomEvents from this element.
|
|
183
|
+
// TODO #641: <jssm-hook> discovery happens here.
|
|
184
|
+
// TODO #643: <jssm-on> discovery happens here.
|
|
185
|
+
// TODO #640: <jssm-action> discovery happens here.
|
|
186
|
+
// TODO #645: <jssm-bind> discovery happens here.
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Lifecycle hook. Cleans up any installed subscriptions. Currently a
|
|
190
|
+
* no-op (no subscriptions are installed in the base scaffolding) — the
|
|
191
|
+
* empty body is intentional so the future tickets #638/#641/#643/#645
|
|
192
|
+
* have a single canonical hook to extend.
|
|
193
|
+
*/
|
|
194
|
+
disconnectedCallback() {
|
|
195
|
+
super.disconnectedCallback();
|
|
196
|
+
// TODO #638: unsubscribe from machine.on(...) handlers.
|
|
197
|
+
// TODO #641: remove installed hooks.
|
|
198
|
+
// TODO #643/#645: remove installed listeners / bindings.
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Reflect machine state onto host attributes and CSS custom properties.
|
|
202
|
+
* Called after every transition and once during `connectedCallback`.
|
|
203
|
+
*
|
|
204
|
+
* Mechanism 1 (#639): writes to host attributes.
|
|
205
|
+
* Mechanism 3 (#639): writes to host inline-style custom properties.
|
|
206
|
+
*/
|
|
207
|
+
_paint_state_reflection() {
|
|
208
|
+
// Invariant: only called after `connectedCallback` has set `_machine`.
|
|
209
|
+
const m = this._machine;
|
|
210
|
+
const current_state = String(m.state());
|
|
211
|
+
const legal_actions = m.list_exit_actions().map(a => String(a)).join(' ');
|
|
212
|
+
const is_terminal = m.is_terminal();
|
|
213
|
+
const is_complete = m.is_complete();
|
|
214
|
+
this.setAttribute('current-state', current_state);
|
|
215
|
+
this.setAttribute('legal-actions', legal_actions);
|
|
216
|
+
this.toggleAttribute('terminal', is_terminal);
|
|
217
|
+
this.toggleAttribute('complete', is_complete);
|
|
218
|
+
// CSS custom properties. v1 only sets --current-state; --current-state-color
|
|
219
|
+
// is left commented out because it requires the theme system integration
|
|
220
|
+
// tracked in a separate ticket.
|
|
221
|
+
this.style.setProperty('--current-state', current_state);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Lit render method. Produces the shadow-DOM template with named slots
|
|
225
|
+
* and a state-specific `<slot name="state-...">` that re-targets on each
|
|
226
|
+
* transition. Fallback content in each slot keeps a bare
|
|
227
|
+
* `<jssm-instance fsl="...">` from rendering as a blank box.
|
|
228
|
+
*
|
|
229
|
+
* @returns A Lit `TemplateResult` describing the shadow tree.
|
|
230
|
+
*/
|
|
231
|
+
render() {
|
|
232
|
+
const state_slot_name = this._machine === undefined
|
|
233
|
+
? 'state-unknown'
|
|
234
|
+
: `state-${String(this._machine.state())}`;
|
|
235
|
+
return html `
|
|
236
|
+
<div class="container">
|
|
237
|
+
<header>
|
|
238
|
+
<slot name="title"><span class="placeholder">jssm-instance</span></slot>
|
|
239
|
+
</header>
|
|
240
|
+
<section class="viz">
|
|
241
|
+
<slot name="viz"><span class="placeholder">no viz configured</span></slot>
|
|
242
|
+
</section>
|
|
243
|
+
<section class="editor">
|
|
244
|
+
<slot name="editor"></slot>
|
|
245
|
+
</section>
|
|
246
|
+
<section class="toolbar">
|
|
247
|
+
<slot name="toolbar"></slot>
|
|
248
|
+
</section>
|
|
249
|
+
<section class="actions">
|
|
250
|
+
<slot name="actions"></slot>
|
|
251
|
+
</section>
|
|
252
|
+
<section class="info-panel">
|
|
253
|
+
<slot name="info-panel"></slot>
|
|
254
|
+
</section>
|
|
255
|
+
<section class="state-section">
|
|
256
|
+
<slot name=${state_slot_name}></slot>
|
|
257
|
+
</section>
|
|
258
|
+
<footer>
|
|
259
|
+
<slot name="footer"></slot>
|
|
260
|
+
</footer>
|
|
261
|
+
</div>
|
|
262
|
+
`;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
JssmInstance.styles = css `
|
|
266
|
+
:host {
|
|
267
|
+
display: block;
|
|
268
|
+
}
|
|
269
|
+
.container {
|
|
270
|
+
width: 100%;
|
|
271
|
+
height: 100%;
|
|
272
|
+
}
|
|
273
|
+
.placeholder {
|
|
274
|
+
opacity: 0.6;
|
|
275
|
+
font-style: italic;
|
|
276
|
+
}
|
|
277
|
+
`;
|
|
278
|
+
/**
|
|
279
|
+
* Lit reactive properties declaration. We declare `fsl` here (rather
|
|
280
|
+
* than via a decorator) so the attribute observation stays explicit and
|
|
281
|
+
* survives the future companion-tag work without colliding with
|
|
282
|
+
* dynamically declared attributes.
|
|
283
|
+
*/
|
|
284
|
+
JssmInstance.properties = {
|
|
285
|
+
fsl: { type: String, reflect: false },
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export { JssmInstance, resolve_fsl_source };
|
package/dist/wc/viz.define.js
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { JssmViz } from './viz.js';
|
|
2
2
|
export { JssmViz } from './viz.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Empty subclass that allows the same class to be registered under a
|
|
6
|
+
* second tag name. `customElements.define` requires a distinct constructor
|
|
7
|
+
* per tag, so the only portable way to publish `<fsl-viz>` as a synonym
|
|
8
|
+
* for `<jssm-viz>` is to register a no-op subclass.
|
|
9
|
+
*
|
|
10
|
+
* Both tags render identically; `<fsl-viz>` is provided as an alternative
|
|
11
|
+
* spelling for users whose mental model is "FSL viz" rather than "jssm
|
|
12
|
+
* viz".
|
|
13
|
+
*
|
|
14
|
+
* TODO: parent-context binding from #647 Stage 2 lands once #648 exists.
|
|
15
|
+
*/
|
|
16
|
+
class FslViz extends JssmViz {
|
|
17
|
+
}
|
|
4
18
|
if (!customElements.get('jssm-viz')) {
|
|
5
19
|
customElements.define('jssm-viz', JssmViz);
|
|
6
20
|
}
|
|
21
|
+
if (!customElements.get('fsl-viz')) {
|
|
22
|
+
customElements.define('fsl-viz', FslViz);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { FslViz };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jssm",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.136.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=10.0.0"
|
|
6
6
|
},
|
|
@@ -57,7 +57,16 @@
|
|
|
57
57
|
"import": "./dist/wc/viz.define.js",
|
|
58
58
|
"default": "./dist/wc/viz.define.js"
|
|
59
59
|
},
|
|
60
|
+
"./wc/instance": {
|
|
61
|
+
"import": "./dist/wc/instance.js",
|
|
62
|
+
"default": "./dist/wc/instance.js"
|
|
63
|
+
},
|
|
64
|
+
"./wc/instance/define": {
|
|
65
|
+
"import": "./dist/wc/instance.define.js",
|
|
66
|
+
"default": "./dist/wc/instance.define.js"
|
|
67
|
+
},
|
|
60
68
|
"./cdn/viz": "./dist/cdn/viz.js",
|
|
69
|
+
"./cdn/instance": "./dist/cdn/instance.js",
|
|
61
70
|
"./custom-elements.json": "./custom-elements.json"
|
|
62
71
|
},
|
|
63
72
|
"autoupdate": {
|
|
@@ -86,7 +95,10 @@
|
|
|
86
95
|
"dist/jssm_viz.mjs",
|
|
87
96
|
"dist/wc/viz.js",
|
|
88
97
|
"dist/wc/viz.define.js",
|
|
98
|
+
"dist/wc/instance.js",
|
|
99
|
+
"dist/wc/instance.define.js",
|
|
89
100
|
"dist/cdn/viz.js",
|
|
101
|
+
"dist/cdn/instance.js",
|
|
90
102
|
"dist/deno/jssm.js",
|
|
91
103
|
"dist/deno/*.d.ts",
|
|
92
104
|
"dist/deno/README.md",
|
|
@@ -126,10 +138,12 @@
|
|
|
126
138
|
"make_viz": "rollup -c rollup.config.viz.js",
|
|
127
139
|
"make_wc_viz_es6": "rollup -c rollup.config.wc.viz.es6.js",
|
|
128
140
|
"make_wc_viz_cdn": "rollup -c rollup.config.wc.viz.cdn.js",
|
|
141
|
+
"make_wc_instance_es6": "rollup -c rollup.config.wc.instance.es6.js",
|
|
142
|
+
"make_wc_instance_cdn": "rollup -c rollup.config.wc.instance.cdn.js",
|
|
129
143
|
"typescript": "tsc --build tsconfig.json",
|
|
130
144
|
"makever": "node src/buildjs/makever.cjs",
|
|
131
145
|
"make_doctests": "node src/buildjs/extract_examples.cjs",
|
|
132
|
-
"make": "npm run clean && npm run makever && npm run peg && npm run build:cem && npm run typescript && npm run make_doctests && npm run make_core && npm run make_deno && npm run make_viz && npm run make_wc_viz_es6 && npm run make_wc_viz_cdn && npm run typecheck_cli && npm run make_cli && npm run minify && npm run min_iife && npm run min_es6 && npm run min_cjs && npm run min_deno && npm run min_viz_iife && npm run min_viz_es6 && npm run min_viz_cjs && npm run min_cli && rm ./dist/es6/*.nonmin.js",
|
|
146
|
+
"make": "npm run clean && npm run makever && npm run peg && npm run build:cem && npm run typescript && npm run make_doctests && npm run make_core && npm run make_deno && npm run make_viz && npm run make_wc_viz_es6 && npm run make_wc_viz_cdn && npm run make_wc_instance_es6 && npm run make_wc_instance_cdn && npm run typecheck_cli && npm run make_cli && npm run minify && npm run min_iife && npm run min_es6 && npm run min_cjs && npm run min_deno && npm run min_viz_iife && npm run min_viz_es6 && npm run min_viz_cjs && npm run min_cli && rm ./dist/es6/*.nonmin.js",
|
|
133
147
|
"eslint": "eslint --color src/ts/jssm.ts src/ts/jssm_types.ts src/ts/tests/*.ts",
|
|
134
148
|
"audit": "text_audit -r -t major MAJOR wasteful WASTEFUL any mixed fixme FIXME checkme CHECKME testme TESTME stochable STOCHABLE todo TODO comeback COMEBACK whargarbl WHARGARBL -g ./src/ts/**/*.{js,ts}",
|
|
135
149
|
"vet": "npm run eslint && npm run audit",
|