jssm 5.147.9 → 5.148.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/dist/wc/viz.js CHANGED
@@ -40,6 +40,57 @@ function closest_wc(el, suffix) {
40
40
  return el.closest(`fsl-${suffix}, jssm-${suffix}`);
41
41
  }
42
42
 
43
+ /**
44
+ * Reorder a graphviz-rendered SVG's paint stack so action labels can't be
45
+ * painted over by edges. Graphviz interleaves nodes and edges and keeps each
46
+ * edge's label `<text>` inside the edge group, so a later edge can draw over an
47
+ * earlier edge's label. This lifts the layers into a clean back-to-front order:
48
+ *
49
+ * background → edges → nodes → action (edge) labels
50
+ *
51
+ * Each edge's label text is hoisted out of its edge group to the very top.
52
+ * Markup that isn't graphviz output (no `g.graph`) is returned untouched.
53
+ *
54
+ * @param svg - SVG markup from the viz pipeline (`machine_to_svg_string`, etc.).
55
+ * @returns The reordered SVG markup; identical input for non-graphviz SVG.
56
+ *
57
+ * @example
58
+ * // <g class="graph"><polygon/>…<g class="node"/><g class="edge"><path/><text/></g></g>
59
+ * // becomes: <g class="graph"><polygon/><g class="edge"><path/></g><g class="node"/><text/></g>
60
+ */
61
+ function reorder_svg_layers(svg) {
62
+ const doc = new DOMParser().parseFromString(svg, 'image/svg+xml');
63
+ const graph = doc.querySelector('g.graph');
64
+ if (graph === null) {
65
+ return svg;
66
+ }
67
+ const background = graph.querySelector(':scope > polygon');
68
+ const edges = [...graph.querySelectorAll(':scope > g.edge')];
69
+ const nodes = [...graph.querySelectorAll(':scope > g.node')];
70
+ // Hoist every edge's label out of its group so it lands on the top layer.
71
+ const labels = [];
72
+ for (const edge of edges) {
73
+ for (const text of [...edge.querySelectorAll(':scope > text')]) {
74
+ labels.push(text);
75
+ }
76
+ }
77
+ // appendChild moves a node to the end (top of the stack), so appending in
78
+ // back-to-front order yields: background, edges, nodes, labels.
79
+ if (background !== null) {
80
+ graph.appendChild(background);
81
+ }
82
+ for (const edge of edges) {
83
+ graph.appendChild(edge);
84
+ }
85
+ for (const node of nodes) {
86
+ graph.appendChild(node);
87
+ }
88
+ for (const label of labels) {
89
+ graph.appendChild(label);
90
+ }
91
+ return new XMLSerializer().serializeToString(doc);
92
+ }
93
+
43
94
  var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
44
95
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
45
96
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -115,6 +166,13 @@ class FslViz extends LitElement {
115
166
  * Held so `disconnectedCallback` can release the subscription.
116
167
  */
117
168
  this._parent_sub = null;
169
+ /**
170
+ * Detaches the host's `fsl-machine-rebuilt` listener (which re-subscribes the
171
+ * viz to the host's new machine after a live rebuild, #1387). Captures the
172
+ * exact host the listener was attached to, so teardown is correct even if
173
+ * `_parent_host` was cleared or replaced. Null when standalone / before binding.
174
+ */
175
+ this._host_unbind = null;
118
176
  }
119
177
  /**
120
178
  * Lit lifecycle hook. Triggers an async SVG render whenever `fsl` or
@@ -172,10 +230,9 @@ class FslViz extends LitElement {
172
230
  if (this._parent_host !== host) {
173
231
  return;
174
232
  }
233
+ let sub;
175
234
  try {
176
- this._parent_sub = host.machine.on('transition', () => {
177
- this._rerenderFromHostMachine();
178
- });
235
+ sub = host.machine.on('transition', () => { this._rerenderFromHostMachine(); });
179
236
  }
180
237
  catch (e) {
181
238
  // The parent existed but its machine wasn't ready / threw. Emit
@@ -188,6 +245,17 @@ class FslViz extends LitElement {
188
245
  }));
189
246
  return;
190
247
  }
248
+ this._parent_sub = sub;
249
+ // Re-subscribe + re-render when the host swaps its machine (#1387 live
250
+ // rebuild): the old subscription is on a dead machine object.
251
+ const on_rebuild = () => {
252
+ sub();
253
+ sub = host.machine.on('transition', () => { this._rerenderFromHostMachine(); });
254
+ this._parent_sub = sub;
255
+ this._rerenderFromHostMachine();
256
+ };
257
+ host.addEventListener('fsl-machine-rebuilt', on_rebuild);
258
+ this._host_unbind = () => { host.removeEventListener('fsl-machine-rebuilt', on_rebuild); };
191
259
  this._rerenderFromHostMachine();
192
260
  });
193
261
  }
@@ -203,6 +271,10 @@ class FslViz extends LitElement {
203
271
  this._parent_sub();
204
272
  this._parent_sub = null;
205
273
  }
274
+ if (this._host_unbind !== null) {
275
+ this._host_unbind();
276
+ this._host_unbind = null;
277
+ }
206
278
  this._parent_host = null;
207
279
  }
208
280
  /**
@@ -223,7 +295,7 @@ class FslViz extends LitElement {
223
295
  const result = await machine_to_svg_string(m, this.engine ? { engine: this.engine } : undefined);
224
296
  // Guard against the parent disappearing mid-render.
225
297
  if (this._parent_host === host) {
226
- this._svg = result;
298
+ this._svg = reorder_svg_layers(result);
227
299
  }
228
300
  }
229
301
  catch (e) {
@@ -253,7 +325,7 @@ class FslViz extends LitElement {
253
325
  const result = await fsl_to_svg_string(source, this.engine ? { engine: this.engine } : undefined);
254
326
  // Guard against stale results: only commit if fsl has not changed since this render started.
255
327
  if (this.fsl === source) {
256
- this._svg = result;
328
+ this._svg = reorder_svg_layers(result);
257
329
  }
258
330
  }
259
331
  catch (e) {
@@ -288,6 +360,16 @@ FslViz.styles = css `
288
360
  .container {
289
361
  width: 100%;
290
362
  height: 100%;
363
+ display: flex;
364
+ align-items: center;
365
+ justify-content: center;
366
+ }
367
+ .container svg {
368
+ /* Zoom the graph to fill the container (aspect maintained via the SVG's
369
+ own preserveAspectRatio), leaving 5% padding on the constraining axis —
370
+ the 90% box centered in the flex container yields the 5% inset. */
371
+ width: 90%;
372
+ height: 90%;
291
373
  }
292
374
  `;
293
375
  __decorate([
@@ -0,0 +1,65 @@
1
+ import { FslToolbar, FslActions, FslFooter, FslHelp, FslHistory, FslDataInspector, FslHookLog, FslSimulation, FslExport } from './widgets.js';
2
+ export * from './widgets.js';
3
+
4
+ /**
5
+ * Shared helpers for the dual-prefix (`fsl-` canonical, `jssm-` synonym)
6
+ * web-component naming convention. Centralizes the "match either prefix"
7
+ * rule so it lives in exactly one place.
8
+ */
9
+ /**
10
+ * Returns true when `tag_name` is exactly `fsl-<suffix>` or `jssm-<suffix>`
11
+ * (case-insensitive).
12
+ *
13
+ * @param tag_name - The element tag name to test (e.g. `"FSL-VIZ"`, `"jssm-viz"`).
14
+ * @param suffix - The suffix to match after the prefix (e.g. `"viz"`).
15
+ * @returns `true` when `tag_name` is `fsl-<suffix>` or `jssm-<suffix>`.
16
+ *
17
+ * @example
18
+ * wc_suffix_matches('FSL-VIZ', 'viz'); // true
19
+ * wc_suffix_matches('jssm-viz', 'viz'); // true
20
+ * wc_suffix_matches('div', 'viz'); // false
21
+ * wc_suffix_matches('fsl-vizard', 'viz'); // false — suffix must match exactly
22
+ */
23
+ /**
24
+ * Registers a single canonical `fsl-*` custom-element tag, with no `jssm-*`
25
+ * synonym.
26
+ *
27
+ * This is the registration path for **new** web components. The `jssm-*`
28
+ * prefix is a deprecated backward-compatibility alias retained only for the
29
+ * components that shipped under that name (`<jssm-viz>`, `<jssm-instance>`,
30
+ * `<jssm-bind>`); new components are `fsl-*`-only for fsl.tools brand
31
+ * alignment, and the legacy synonyms are slated for removal in v6. Use
32
+ * {@link define_with_synonym} only when maintaining one of those pre-existing
33
+ * dual-named components.
34
+ *
35
+ * Idempotent: skips the `define` call when the tag is already registered.
36
+ *
37
+ * @param canonical_tag - The `fsl-*` tag name (e.g. `"fsl-info-panel"`).
38
+ * @param CanonicalClass - Constructor to register under `canonical_tag`.
39
+ *
40
+ * @example
41
+ * class FslInfoPanel extends HTMLElement {}
42
+ * define_canonical('fsl-info-panel', FslInfoPanel);
43
+ *
44
+ * @see define_with_synonym
45
+ */
46
+ function define_canonical(canonical_tag, CanonicalClass) {
47
+ if (!customElements.get(canonical_tag))
48
+ customElements.define(canonical_tag, CanonicalClass);
49
+ }
50
+
51
+ /**
52
+ * Bundle entry: registers the entire light fsl-* widget suite (toolbar, actions,
53
+ * footer, help, history, data-inspector, hook-log, simulation, export) in one import.
54
+ * Canonical `fsl-*` tags only — no deprecated `jssm-*` synonyms. Registration
55
+ * is idempotent, so this composes safely with the per-widget `*.define` modules.
56
+ */
57
+ define_canonical('fsl-toolbar', FslToolbar);
58
+ define_canonical('fsl-actions', FslActions);
59
+ define_canonical('fsl-footer', FslFooter);
60
+ define_canonical('fsl-help', FslHelp);
61
+ define_canonical('fsl-history', FslHistory);
62
+ define_canonical('fsl-data-inspector', FslDataInspector);
63
+ define_canonical('fsl-hook-log', FslHookLog);
64
+ define_canonical('fsl-simulation', FslSimulation);
65
+ define_canonical('fsl-export', FslExport);