jssm 5.162.12 → 5.162.13

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
@@ -1,7 +1,7 @@
1
1
  import { css, LitElement, html } from 'lit';
2
2
  import { property, state } from 'lit/decorators.js';
3
3
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
4
- import { machine_to_svg_string, fsl_to_svg_string } from 'jssm/viz';
4
+ import { machine_to_svg_string, fsl_to_svg_string, slug_for } from 'jssm/viz';
5
5
 
6
6
  /**
7
7
  * Shared helpers for the dual-prefix (`fsl-` canonical, `jssm-` synonym)
@@ -137,6 +137,7 @@ function normalize_viz_error(e) {
137
137
  * supplying it emits a `console.warn` for developer feedback.
138
138
  * @element fsl-viz
139
139
  * @cssproperty [--jssm-viz-min-height=100px] - Minimum height of the rendered SVG container.
140
+ * @cssproperty [--jssm-viz-max-height=none] - Maximum height of the control; the rendered SVG stays bounded (aspect preserved, letterboxed) within it. Equivalent to setting `max-height` on the host from outside, without shadow surgery.
140
141
  * @fires {CustomEvent<{ message: string; location?: unknown }>} viz-error - Fires when the FSL source fails to parse or render.
141
142
  */
142
143
  class FslViz extends LitElement {
@@ -377,15 +378,27 @@ class FslViz extends LitElement {
377
378
  * Programmatically highlights one execution trace (a path of state names)
378
379
  * through the rendered graph, optionally fading everything off the path.
379
380
  *
380
- * Matches nodes by their Graphviz `<title>` (the state name) and edges by
381
- * the `from->to` title Graphviz emits, applying inline style overrides so
382
- * the highlight composes over — and is reversible against — the default
383
- * rendering (see {@link clearHighlights}, which this calls first). No-ops
384
- * when detached, before the first render, or given an empty trace.
381
+ * Matches nodes by their Graphviz `<title>` and edges by the `from->to`
382
+ * title Graphviz emits, applying inline style overrides so the highlight
383
+ * composes over — and is reversible against — the default rendering (see
384
+ * {@link clearHighlights}, which this calls first). No-ops when detached,
385
+ * before the first render, or given an empty trace.
386
+ *
387
+ * Because dot generation slugs state names into node identifiers (fsl#1935
388
+ * — `'Wrong Pin'` renders with `<title>wrong-pin</title>`), each trace name
389
+ * is matched in **both** its raw form and its slugged form, using the same
390
+ * `slug_for` the dot generator uses. Display names (`'Red'`, `'Wrong Pin'`,
391
+ * `'Röd'`) and already-slug-form names (`'red'`, `'wrong-pin'`) therefore
392
+ * both work. Names whose slug is empty (e.g. `'!!!'`, which renders under
393
+ * an indexed `node-N` title) are only matchable by passing that literal
394
+ * `node-N` title.
385
395
  *
386
396
  * ```typescript
387
397
  * // Highlight a -> b -> c in green, without dimming the rest:
388
398
  * viz.highlightTrace(['a', 'b', 'c'], { color: '#2e7d32', fadeOthers: false });
399
+ *
400
+ * // Display-form names match their slugged titles:
401
+ * viz.highlightTrace(['Wrong Pin', 'Alarm']); // titles wrong-pin, alarm
389
402
  * ```
390
403
  * @param trace Ordered state names describing the path (e.g.
391
404
  * `['A', 'B', 'C']`). Consecutive pairs select the edges
@@ -408,10 +421,26 @@ class FslViz extends LitElement {
408
421
  const fadeOthers = options.fadeOthers !== false; // default: true — undefined must fade
409
422
  const targetNodes = new Set();
410
423
  const targetEdges = new Set();
411
- for (let i = 0; i < trace.length; i++) {
412
- targetNodes.add(trace[i]);
424
+ // fsl#1935: dot generation slugs state names for node identity (see
425
+ // slug_for in jssm_viz.ts), so rendered <title>s carry 'wrong-pin', not
426
+ // 'Wrong Pin'. Match each trace name in both raw and slugged forms; for
427
+ // names already in slug form the two coincide, preserving old behavior.
428
+ const title_forms_of = (name) => {
429
+ const slug = slug_for(name);
430
+ return (slug !== '' && slug !== name) ? [name, slug] : [name];
431
+ };
432
+ for (const [i, name] of trace.entries()) {
433
+ const from_forms = title_forms_of(name);
434
+ for (const form of from_forms) {
435
+ targetNodes.add(form);
436
+ }
413
437
  if (i < trace.length - 1) {
414
- targetEdges.add(`${trace[i]}->${trace[i + 1]}`);
438
+ const to_forms = title_forms_of(trace[i + 1]);
439
+ for (const from_form of from_forms) {
440
+ for (const to_form of to_forms) {
441
+ targetEdges.add(`${from_form}->${to_form}`);
442
+ }
443
+ }
415
444
  }
416
445
  }
417
446
  const allNodes = container.querySelectorAll('.node');
@@ -461,10 +490,18 @@ FslViz.styles = css `
461
490
  :host {
462
491
  display: block;
463
492
  min-height: var(--jssm-viz-min-height, 100px);
493
+ /* #1934: embedder sizing seam — cap the control via the custom property
494
+ (or plain external max-height on the host) without shadow surgery. */
495
+ max-height: var(--jssm-viz-max-height, none);
464
496
  }
465
497
  .container {
466
498
  width: 100%;
467
499
  height: 100%;
500
+ /* #1934: when the host's height is auto (content-driven under an
501
+ external max-height cap), the percentage heights below collapse to
502
+ auto and stop constraining anything. Inheriting the host's computed
503
+ max-height re-threads the cap down to the svg. */
504
+ max-height: inherit;
468
505
  display: flex;
469
506
  align-items: center;
470
507
  justify-content: center;
@@ -475,6 +512,14 @@ FslViz.styles = css `
475
512
  the 90% box centered in the flex container yields the 5% inset. */
476
513
  width: 90%;
477
514
  height: 90%;
515
+ /* #1934: with an auto-height host the 90% height collapses and the svg
516
+ falls back to ratio-derived (or intrinsic pt) sizing, which can run
517
+ far past an external max-height on the host. The inherited cap keeps
518
+ that fallback bounded; Graphviz SVGs carry viewBox + the default
519
+ preserveAspectRatio, so the capped viewport letterboxes cleanly
520
+ instead of distorting. */
521
+ max-width: 100%;
522
+ max-height: inherit;
478
523
  }
479
524
 
480
525
  /* Smoothly animate the inline style overrides applied by highlightTrace()
@@ -1,4 +1,4 @@
1
- import { FslToolbar, FslActions, FslFooter, FslHelp, FslHistory, FslDataInspector, FslHookLog, FslSimulation, FslExport, FslStochastic } from './widgets.js';
1
+ import { FslToolbar, FslActions, FslFooter, FslHelp, FslHistory, FslDataInspector, FslHookLog, FslSimulation, FslExport, FslStochastic, FslInfoPanel } from './widgets.js';
2
2
  export * from './widgets.js';
3
3
 
4
4
  /**
@@ -45,7 +45,8 @@ function define_canonical(canonical_tag, CanonicalClass) {
45
45
 
46
46
  /**
47
47
  * Bundle entry: registers the entire light fsl-* widget suite (toolbar, actions,
48
- * footer, help, history, data-inspector, hook-log, simulation, export, stochastic) in one import.
48
+ * footer, help, history, data-inspector, hook-log, simulation, export, stochastic,
49
+ * info-panel) in one import.
49
50
  * Canonical `fsl-*` tags only — no deprecated `jssm-*` synonyms. Registration
50
51
  * is idempotent, so this composes safely with the per-widget `*.define` modules.
51
52
  */
@@ -59,3 +60,4 @@ define_canonical('fsl-hook-log', FslHookLog);
59
60
  define_canonical('fsl-simulation', FslSimulation);
60
61
  define_canonical('fsl-export', FslExport);
61
62
  define_canonical('fsl-stochastic', FslStochastic);
63
+ define_canonical('fsl-info-panel', FslInfoPanel);
@@ -201,7 +201,7 @@ async function permalink_for(fsl, key = DEFAULT_PERMALINK_KEY, href = location.h
201
201
  return `${href.split('#', 1)[0]}#${fragment}`;
202
202
  }
203
203
 
204
- var __decorate$8 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
204
+ var __decorate$9 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
205
205
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
206
206
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
207
207
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -535,23 +535,23 @@ FslToolbar.styles = css `
535
535
  .spacer { flex: 1; }
536
536
  ${fslTokens}
537
537
  `;
538
- __decorate$8([
538
+ __decorate$9([
539
539
  state()
540
540
  ], FslToolbar.prototype, "_openMenu", void 0);
541
- __decorate$8([
541
+ __decorate$9([
542
542
  state()
543
543
  ], FslToolbar.prototype, "_dest", void 0);
544
- __decorate$8([
544
+ __decorate$9([
545
545
  property({ attribute: false })
546
546
  ], FslToolbar.prototype, "lastDirectory", void 0);
547
- __decorate$8([
547
+ __decorate$9([
548
548
  property({ type: Boolean, attribute: 'no-validate' })
549
549
  ], FslToolbar.prototype, "noValidate", void 0);
550
- __decorate$8([
550
+ __decorate$9([
551
551
  property({ type: Boolean, attribute: 'no-lint' })
552
552
  ], FslToolbar.prototype, "noLint", void 0);
553
553
 
554
- var __decorate$7 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
554
+ var __decorate$8 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
555
555
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
556
556
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
557
557
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -704,20 +704,20 @@ FslActions.styles = css `
704
704
  .empty { color: var(--_fsl-muted); font-style: italic; }
705
705
  ${fslTokens}
706
706
  `;
707
- __decorate$7([
707
+ __decorate$8([
708
708
  state()
709
709
  ], FslActions.prototype, "_actions", void 0);
710
- __decorate$7([
710
+ __decorate$8([
711
711
  state()
712
712
  ], FslActions.prototype, "_main", void 0);
713
- __decorate$7([
713
+ __decorate$8([
714
714
  state()
715
715
  ], FslActions.prototype, "_regular", void 0);
716
- __decorate$7([
716
+ __decorate$8([
717
717
  state()
718
718
  ], FslActions.prototype, "_forced", void 0);
719
719
 
720
- var __decorate$6 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
720
+ var __decorate$7 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
721
721
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
722
722
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
723
723
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -835,32 +835,32 @@ FslFooter.styles = css `
835
835
  .spacer { flex: 1; }
836
836
  ${fslTokens}
837
837
  `;
838
- __decorate$6([
838
+ __decorate$7([
839
839
  state()
840
840
  ], FslFooter.prototype, "_state", void 0);
841
- __decorate$6([
841
+ __decorate$7([
842
842
  state()
843
843
  ], FslFooter.prototype, "_actions", void 0);
844
- __decorate$6([
844
+ __decorate$7([
845
845
  state()
846
846
  ], FslFooter.prototype, "_transitions", void 0);
847
- __decorate$6([
847
+ __decorate$7([
848
848
  state()
849
849
  ], FslFooter.prototype, "_terminal", void 0);
850
- __decorate$6([
850
+ __decorate$7([
851
851
  state()
852
852
  ], FslFooter.prototype, "_complete", void 0);
853
- __decorate$6([
853
+ __decorate$7([
854
854
  state()
855
855
  ], FslFooter.prototype, "_gActions", void 0);
856
- __decorate$6([
856
+ __decorate$7([
857
857
  state()
858
858
  ], FslFooter.prototype, "_gStarts", void 0);
859
- __decorate$6([
859
+ __decorate$7([
860
860
  state()
861
861
  ], FslFooter.prototype, "_gTransitions", void 0);
862
862
 
863
- var __decorate$5 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
863
+ var __decorate$6 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
864
864
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
865
865
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
866
866
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -929,14 +929,14 @@ FslHelp.styles = css `
929
929
  .body { overflow-y: auto; padding: 0.5rem 0.9rem; font-size: 0.86rem; line-height: 1.5; }
930
930
  ${fslTokens}
931
931
  `;
932
- __decorate$5([
932
+ __decorate$6([
933
933
  property({ type: Boolean, reflect: true })
934
934
  ], FslHelp.prototype, "open", void 0);
935
- __decorate$5([
935
+ __decorate$6([
936
936
  property({ type: String })
937
937
  ], FslHelp.prototype, "heading", void 0);
938
938
 
939
- var __decorate$4 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
939
+ var __decorate$5 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
940
940
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
941
941
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
942
942
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -1009,11 +1009,11 @@ FslHistory.styles = css `
1009
1009
  .empty { color: var(--_fsl-muted); font-style: italic; padding: 0.5rem 0.7rem; }
1010
1010
  ${fslTokens}
1011
1011
  `;
1012
- __decorate$4([
1012
+ __decorate$5([
1013
1013
  state()
1014
1014
  ], FslHistory.prototype, "_history", void 0);
1015
1015
 
1016
- var __decorate$3 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1016
+ var __decorate$4 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1017
1017
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1018
1018
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1019
1019
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -1131,11 +1131,11 @@ FslDataInspector.styles = css `
1131
1131
  .tok-bool, .tok-null { color: var(--fsl-color-json-atom, #c2185b); }
1132
1132
  ${fslTokens}
1133
1133
  `;
1134
- __decorate$3([
1134
+ __decorate$4([
1135
1135
  state()
1136
1136
  ], FslDataInspector.prototype, "_data", void 0);
1137
1137
 
1138
- var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1138
+ var __decorate$3 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1139
1139
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1140
1140
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1141
1141
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -1205,11 +1205,11 @@ FslHookLog.styles = css `
1205
1205
  .empty { color: var(--_fsl-muted); font-style: italic; }
1206
1206
  ${fslTokens}
1207
1207
  `;
1208
- __decorate$2([
1208
+ __decorate$3([
1209
1209
  state()
1210
1210
  ], FslHookLog.prototype, "_log", void 0);
1211
1211
 
1212
- var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1212
+ var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1213
1213
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1214
1214
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1215
1215
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -1303,13 +1303,13 @@ FslSimulation.styles = css `
1303
1303
  .count.idle { font-style: italic; }
1304
1304
  ${fslTokens}
1305
1305
  `;
1306
- __decorate$1([
1306
+ __decorate$2([
1307
1307
  property({ type: Number })
1308
1308
  ], FslSimulation.prototype, "interval", void 0);
1309
- __decorate$1([
1309
+ __decorate$2([
1310
1310
  state()
1311
1311
  ], FslSimulation.prototype, "_running", void 0);
1312
- __decorate$1([
1312
+ __decorate$2([
1313
1313
  state()
1314
1314
  ], FslSimulation.prototype, "_steps", void 0);
1315
1315
 
@@ -1376,7 +1376,7 @@ FslExport.styles = css `
1376
1376
  ${fslTokens}
1377
1377
  `;
1378
1378
 
1379
- var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1379
+ var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1380
1380
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1381
1381
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1382
1382
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
@@ -1647,29 +1647,182 @@ FslStochastic.styles = css `
1647
1647
  .error { color: var(--_fsl-danger, #c0392b); padding: 0.4rem 0.6rem; }
1648
1648
  ${fslTokens}
1649
1649
  `;
1650
- __decorate([
1650
+ __decorate$1([
1651
1651
  property({ type: Number })
1652
1652
  ], FslStochastic.prototype, "runs", void 0);
1653
- __decorate([
1653
+ __decorate$1([
1654
1654
  property({ type: Number })
1655
1655
  ], FslStochastic.prototype, "maxSteps", void 0);
1656
- __decorate([
1656
+ __decorate$1([
1657
1657
  property({ type: String })
1658
1658
  ], FslStochastic.prototype, "seed", void 0);
1659
- __decorate([
1659
+ __decorate$1([
1660
1660
  property({ type: String })
1661
1661
  ], FslStochastic.prototype, "mode", void 0);
1662
- __decorate([
1662
+ __decorate$1([
1663
1663
  state()
1664
1664
  ], FslStochastic.prototype, "_summary", void 0);
1665
- __decorate([
1665
+ __decorate$1([
1666
1666
  state()
1667
1667
  ], FslStochastic.prototype, "_error", void 0);
1668
- __decorate([
1668
+ __decorate$1([
1669
1669
  state()
1670
1670
  ], FslStochastic.prototype, "_host", void 0);
1671
- __decorate([
1671
+ __decorate$1([
1672
1672
  state()
1673
1673
  ], FslStochastic.prototype, "_playing", void 0);
1674
1674
 
1675
- export { FslActions, FslDataInspector, FslExport, FslFooter, FslHelp, FslHistory, FslHookLog, FslSimulation, FslStochastic, FslToolbar };
1675
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1676
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1677
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1678
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1679
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1680
+ };
1681
+ /**
1682
+ * Read-only state-inspector web component for a parent `<fsl-instance>`.
1683
+ *
1684
+ * Slotted into the host's `info-panel` slot (or nested anywhere inside it), it
1685
+ * displays the machine's current state, the most recent transition
1686
+ * (`from → to via action`), the currently-legal exit actions, and the
1687
+ * terminal / complete flags. Every field refreshes on each `transition`
1688
+ * event.
1689
+ *
1690
+ * Display-only: it never drives the machine. It binds by walking up to the
1691
+ * host via {@link closest_wc} (which matches both the canonical `fsl-instance`
1692
+ * and the deprecated `jssm-instance` host tags), so it works under either.
1693
+ * @element fsl-info-panel
1694
+ * @cssproperty [--fsl-info-panel-gap=0.25rem] - Vertical gap between rows.
1695
+ */
1696
+ class FslInfoPanel extends LitElement {
1697
+ constructor() {
1698
+ super(...arguments);
1699
+ /**
1700
+ * Parent host reference, set in `connectedCallback` when one is found.
1701
+ * Cleared on disconnect so a stale deferred subscription cannot fire.
1702
+ */
1703
+ this._host = null;
1704
+ /** Unsubscribe callback from the host machine's `transition` subscription. */
1705
+ this._sub = null;
1706
+ /** Current state name; `null` until the panel has bound to a host machine. */
1707
+ this._current = null;
1708
+ /** Space-separated legal exit actions for the current state. */
1709
+ this._actions = '';
1710
+ /** Whether the current state has no exits. */
1711
+ this._terminal = false;
1712
+ /** Whether the current state is a `complete` state. */
1713
+ this._complete = false;
1714
+ /** Most recent transition, or `null` before the first one. */
1715
+ this._last = null;
1716
+ }
1717
+ /**
1718
+ * Web Components lifecycle hook. Finds the parent host via {@link closest_wc}
1719
+ * and, once `<fsl-instance>` has upgraded, subscribes to its machine's
1720
+ * `transition` events and paints the initial snapshot. With no host, it
1721
+ * leaves `_current` null so {@link render} shows the placeholder.
1722
+ */
1723
+ connectedCallback() {
1724
+ super.connectedCallback();
1725
+ const host = closest_wc(this, 'instance');
1726
+ if (host === null) {
1727
+ return; // no host: render() shows the placeholder
1728
+ }
1729
+ this._host = host;
1730
+ void customElements.whenDefined('fsl-instance').then(() => {
1731
+ // Disconnection between scheduling and resolution is legal.
1732
+ if (this._host !== host) {
1733
+ return;
1734
+ }
1735
+ this._sub = host.machine.on('transition', (detail) => {
1736
+ // The transition event detail carries `StateType = string` endpoints;
1737
+ // `action` is undefined for pure transitions, rendered as 'undefined'
1738
+ // by String() (longstanding display behavior, preserved).
1739
+ const d = detail;
1740
+ this._last = {
1741
+ from: d.from,
1742
+ to: d.to,
1743
+ // eslint-disable-next-line unicorn/no-useless-coercion -- action is `string | undefined`, not string: String() renders an actionless transition as 'undefined', the shipped display behavior
1744
+ action: String(d.action),
1745
+ };
1746
+ this._refresh(host);
1747
+ });
1748
+ this._refresh(host); // initial snapshot
1749
+ return;
1750
+ });
1751
+ }
1752
+ /**
1753
+ * Web Components lifecycle hook. Releases the machine subscription and clears
1754
+ * the host reference. The last-painted snapshot is intentionally retained so
1755
+ * a detached panel keeps showing its final state rather than blanking.
1756
+ */
1757
+ disconnectedCallback() {
1758
+ super.disconnectedCallback();
1759
+ if (this._sub !== null) {
1760
+ this._sub();
1761
+ this._sub = null;
1762
+ }
1763
+ this._host = null;
1764
+ }
1765
+ /**
1766
+ * Read the current machine snapshot into the reactive fields, triggering a
1767
+ * re-render. Called once on bind and again on every transition. The bound
1768
+ * host is passed in by the caller (which already holds a non-null reference),
1769
+ * so no re-null-check is needed here.
1770
+ * @param host - The bound parent host whose machine to snapshot.
1771
+ */
1772
+ _refresh(host) {
1773
+ const m = host.machine;
1774
+ this._current = m.state();
1775
+ this._actions = m.list_exit_actions().map(String).join(' ');
1776
+ this._terminal = m.is_terminal();
1777
+ this._complete = m.is_complete();
1778
+ }
1779
+ /**
1780
+ * Lit render method. Shows a placeholder until the panel has bound to a host
1781
+ * machine; thereafter a labeled grid of the live snapshot.
1782
+ * @returns A Lit `TemplateResult` for the panel.
1783
+ */
1784
+ render() {
1785
+ if (this._current === null) {
1786
+ return html `<div class="placeholder">no fsl-instance host</div>`;
1787
+ }
1788
+ const last = this._last;
1789
+ return html `
1790
+ <div class="info">
1791
+ <div class="row"><span class="label">state</span><span class="state">${this._current}</span></div>
1792
+ <div class="row"><span class="label">actions</span><span class="actions">${this._actions === '' ? 'none' : this._actions}</span></div>
1793
+ <div class="row"><span class="label">last</span><span class="last">${last === null ? '—' : `${last.from} → ${last.to} via ${last.action}`}</span></div>
1794
+ <div class="row"><span class="label">terminal</span><span class="terminal">${String(this._terminal)}</span></div>
1795
+ <div class="row"><span class="label">complete</span><span class="complete">${String(this._complete)}</span></div>
1796
+ </div>
1797
+ `;
1798
+ }
1799
+ }
1800
+ FslInfoPanel.styles = css `
1801
+ :host { display: block; }
1802
+ .info, .placeholder {
1803
+ padding: 0.5rem 0.7rem; font: 0.8rem var(--_fsl-font-mono);
1804
+ color: var(--_fsl-text); background: var(--_fsl-surface);
1805
+ }
1806
+ .info { display: grid; gap: var(--fsl-info-panel-gap, 0.25rem); }
1807
+ .row { display: flex; gap: 0.5rem; }
1808
+ .label { font-weight: 600; color: var(--_fsl-muted); }
1809
+ .placeholder { color: var(--_fsl-muted); font-style: italic; }
1810
+ ${fslTokens}
1811
+ `;
1812
+ __decorate([
1813
+ state()
1814
+ ], FslInfoPanel.prototype, "_current", void 0);
1815
+ __decorate([
1816
+ state()
1817
+ ], FslInfoPanel.prototype, "_actions", void 0);
1818
+ __decorate([
1819
+ state()
1820
+ ], FslInfoPanel.prototype, "_terminal", void 0);
1821
+ __decorate([
1822
+ state()
1823
+ ], FslInfoPanel.prototype, "_complete", void 0);
1824
+ __decorate([
1825
+ state()
1826
+ ], FslInfoPanel.prototype, "_last", void 0);
1827
+
1828
+ export { FslActions, FslDataInspector, FslExport, FslFooter, FslHelp, FslHistory, FslHookLog, FslInfoPanel, FslSimulation, FslStochastic, FslToolbar };
package/jssm.es5.d.cts CHANGED
@@ -308,6 +308,32 @@ type FslSourceLocation = {
308
308
  start: FslSourcePoint;
309
309
  end: FslSourcePoint;
310
310
  };
311
+ /**
312
+ * Options accepted by the FSL parser and its {@link wrap_parse} wrapper
313
+ * (exported from the package as `parse`). Exists so the two-argument parse
314
+ * call is typed against what the parser actually reads, instead of a bare
315
+ * `object`.
316
+ *
317
+ * - `locations` — when `true`, the grammar attaches a `loc` field of type
318
+ * {@link FslSourceLocation} (plus curated `*_loc` token sub-spans) to every
319
+ * AST node. When absent or `false`, the tree is byte-for-byte identical to
320
+ * the historical location-free output.
321
+ *
322
+ * - `startRule` — honored by the generated PEG.js boilerplate, which throws
323
+ * on any rule name it doesn't expose. This grammar exposes only its
324
+ * default rule, `Document`, so the field is only useful for explicitness.
325
+ *
326
+ * ```typescript
327
+ * const [t] = parse('a -> b;', { locations: true });
328
+ * // t.loc === { start: { offset: 0, line: 1, column: 1 },
329
+ * // end: { offset: 7, line: 1, column: 8 } }
330
+ * ```
331
+ * @see FslSourceLocation
332
+ */
333
+ type JssmParseOptions = {
334
+ locations?: boolean;
335
+ startRule?: 'Document';
336
+ };
311
337
  /**
312
338
  * A single key/value pair from an FSL `state X: { ... };` block, in the
313
339
  * raw form produced by the parser before being condensed into a
@@ -1357,13 +1383,25 @@ interface FenceDimension {
1357
1383
  value: number;
1358
1384
  unit: FenceDimensionUnit;
1359
1385
  }
1360
- /** The fully-parsed, validated description of one FSL Markdown fence block. */
1386
+ /**
1387
+ * The fully-parsed, validated description of one FSL Markdown fence block.
1388
+ *
1389
+ * Sizing semantics: `width`/`height` (from `width=`/`height=` tokens) are
1390
+ * *exact* dimensions — the host renders the block at that size.
1391
+ * `max_width`/`max_height` (from `max-width=`/`max-height=` tokens) are
1392
+ * *upper bounds* on natural sizing — the block renders at its natural size
1393
+ * but is capped on that axis. When both an exact and a max token are given
1394
+ * for the same axis, the exact dimension wins and the cap is moot. All four
1395
+ * are `null` when their token is absent.
1396
+ */
1361
1397
  interface FenceDescriptor {
1362
1398
  parts: FencePart[];
1363
1399
  ide: boolean;
1364
1400
  format: FenceImageFormat;
1365
1401
  width: FenceDimension | null;
1366
1402
  height: FenceDimension | null;
1403
+ max_width: FenceDimension | null;
1404
+ max_height: FenceDimension | null;
1367
1405
  interactive: boolean;
1368
1406
  notes: string[];
1369
1407
  }
@@ -1381,13 +1419,18 @@ declare function fsl_fence_lang(info: string): 'fsl' | 'jssm' | null;
1381
1419
  /**
1382
1420
  * Parse a fence info string into a {@link FenceDescriptor}. The first token is
1383
1421
  * the (already-validated) language and is ignored; remaining tokens are
1384
- * classified as parts, image formats, the `ide` macro, or `width`/`height`
1385
- * options. Unrecognized or conflicting tokens are dropped and recorded in
1422
+ * classified as parts, image formats, the `ide` macro, or the dimension
1423
+ * options `width`/`height` (exact size) and `max-width`/`max-height`
1424
+ * (upper bounds on natural size — see {@link FenceDescriptor} for the
1425
+ * precedence rule when both appear on one axis). All four dimension tokens
1426
+ * share one value syntax: a bare number (pixels), `<n>px`, or `<n>%`.
1427
+ * Unrecognized or conflicting tokens are dropped and recorded in
1386
1428
  * `notes` rather than throwing, so a host can render forward-compatibly.
1387
1429
  * @param info The full fence info string, e.g. `'fsl image code width=300'`.
1388
1430
  * @returns The validated descriptor; `notes` lists anything ignored or overridden.
1389
1431
  * @example parse_fence_info('fsl').parts // => ['image', 'code']
1390
1432
  * @example parse_fence_info('fsl code image').parts // => ['code', 'image']
1433
+ * @example parse_fence_info('fsl image max-width=300 max-height=50%').max_width // => { value: 300, unit: 'px' }
1391
1434
  */
1392
1435
  declare function parse_fence_info(info: string): FenceDescriptor;
1393
1436
 
@@ -1523,14 +1566,31 @@ declare function arrow_right_kind(arrow: JssmArrow): JssmArrowKind;
1523
1566
  * `wrap_parse` itself is an internal convenience method for alting out an
1524
1567
  * object as the options call. Not generally meant for external use.
1525
1568
  *
1569
+ * @typeParam StateType The type of state names in the resulting tree; the
1570
+ * grammar itself always produces `string`s, so only
1571
+ * override this when threading a caller's own state
1572
+ * naming through to {@link compile}.
1573
+ * @typeParam mDT The type of the machine data member; usually omitted.
1574
+ *
1526
1575
  * @param input The FSL code to be evaluated
1527
1576
  *
1528
- * @param options Things to control about the instance. Pass
1577
+ * @param options Things to control about the parse. Pass
1529
1578
  * `{ locations: true }` to enable opt-in source location
1530
- * tracking on every AST node.
1579
+ * tracking on every AST node. When omitted, an empty options
1580
+ * object is passed through to the parser.
1581
+ *
1582
+ * @returns The machine's intermediate representation: a flat
1583
+ * {@link JssmParseTree} with one node per top-level FSL statement.
1584
+ *
1585
+ * @throws {SyntaxError} The generated PEG.js parser's `SyntaxError` when
1586
+ * `input` is not valid FSL.
1587
+ *
1588
+ * @see {@link compile}
1589
+ * @see {@link make}
1590
+ * @see {@link JssmParseOptions}
1531
1591
  *
1532
1592
  */
1533
- declare function wrap_parse(input: string, options?: object): any;
1593
+ declare function wrap_parse<StateType = string, mDT = unknown>(input: string, options?: JssmParseOptions): JssmParseTree<StateType, mDT>;
1534
1594
  /*********
1535
1595
  *
1536
1596
  * Compile a machine's JSON intermediate representation to a config object. If
@@ -4737,4 +4797,4 @@ declare function compareVersions(v1: string, v2: string): number;
4737
4797
  declare function deserialize<mDT>(machine_string: string, ser: JssmSerialization<mDT>): Machine<mDT>;
4738
4798
 
4739
4799
  export { FslDirections, Machine, STOCHASTIC_DEFAULT_MAX_STEPS, STOCHASTIC_DEFAULT_RUNS, abstract_everything_hook_step, abstract_hook_step, action_label_chars, arrow_direction, arrow_left_kind, arrow_right_kind, build_time, compareVersions, compile, jssm_constants_d as constants, deserialize, find_repeated, from, fslCompletions, fslDiagnostics, fslSemanticSpans, fsl_fence_lang, gen_splitmix32, gviz_shapes, histograph, is_hook_complex_result, is_hook_rejection, make, named_colors, wrap_parse as parse, parse_fence_info, seq, shapes, sleep, sm, state_name_chars, state_name_first_chars, state_style_condense, transfer_state_properties, unique, version, weighted_histo_key, weighted_rand_select, weighted_sample_select };
4740
- export type { FenceDescriptor, FenceDimension, FenceDimensionUnit, FenceImageFormat, FencePart };
4800
+ export type { FenceDescriptor, FenceDimension, FenceDimensionUnit, FenceImageFormat, FencePart, JssmParseOptions };