jssm 5.149.2 → 5.150.1

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.
@@ -908,8 +908,20 @@ class FslInstance extends LitElement {
908
908
  this._autoMode = 'lr';
909
909
  /** Window-resize listener installed while `layout="auto"`, or null. */
910
910
  this._autoListener = null;
911
- /** Slot names of panels currently hidden (driven by `<fsl-toolbar>`). */
912
- this._hiddenPanels = new Set();
911
+ /** Per-panel runtime visibility overrides set by the user via the toolbar
912
+ * toggles; a slot absent here falls back to its mode-resolved base. */
913
+ this._overrides = new Map();
914
+ /** Control-level default {@link PanelMode}; {@link panelModes} overrides it
915
+ * per panel. `default` shows only the editor + renderer; every other panel
916
+ * starts hidden and is opt-in. */
917
+ this.panelMode = 'default';
918
+ /** Per-panel {@link PanelMode} overrides (slot → mode), each overriding the
919
+ * control-level {@link panelMode}. */
920
+ this.panelModes = {};
921
+ /** Panels the FSL "requests" — the embedder-set stand-in for the
922
+ * editor-defaults-in-FSL mechanism (fsl#1334). `request`-mode panels listed
923
+ * here are shown; others fall back to the default. */
924
+ this.requestedPanels = [];
913
925
  /**
914
926
  * The underlying machine instance, constructed at `connectedCallback`.
915
927
  * Exposed raw (not proxied) per the #639/#648 design decision so that
@@ -1102,39 +1114,67 @@ class FslInstance extends LitElement {
1102
1114
  this._tab = tab;
1103
1115
  this.requestUpdate();
1104
1116
  }
1117
+ /** The built-in default hidden state: only the editor + renderer (viz) show. */
1118
+ _defaultHidden(slot) {
1119
+ return slot !== 'viz' && slot !== 'editor';
1120
+ }
1105
1121
  /**
1106
- * Whether the panel slotted under `slot` is currently hidden.
1122
+ * Whether the panel slotted under `slot` is currently hidden, resolving its
1123
+ * {@link PanelMode} ({@link panelModes} for the slot, else {@link panelMode}):
1124
+ * `hide`/`show` force the state; otherwise a user toggle wins, then a
1125
+ * `request`ed panel shows, then the built-in default.
1107
1126
  *
1108
1127
  * @param slot - A panel slot name (e.g. `"viz"`, `"editor"`, `"history"`).
1109
1128
  * @returns `true` when the panel is hidden.
1129
+ *
1130
+ * @example
1131
+ * el.panelModes = { history: 'show' };
1132
+ * el.isPanelHidden('history'); // false
1110
1133
  */
1111
1134
  isPanelHidden(slot) {
1112
- return this._hiddenPanels.has(slot);
1135
+ var _a;
1136
+ const mode = (_a = this.panelModes[slot]) !== null && _a !== void 0 ? _a : this.panelMode;
1137
+ if (mode === 'hide') {
1138
+ return true;
1139
+ }
1140
+ if (mode === 'show') {
1141
+ return false;
1142
+ }
1143
+ const override = this._overrides.get(slot);
1144
+ if (override !== undefined) {
1145
+ return override;
1146
+ }
1147
+ if (mode === 'request' && this.requestedPanels.includes(slot)) {
1148
+ return false;
1149
+ }
1150
+ return this._defaultHidden(slot);
1113
1151
  }
1114
1152
  /**
1115
- * Show or hide the panel slotted under `slot`. Hiding `viz` or `editor`
1116
- * collapses that workbench pane (the other fills); hiding an aux panel
1117
- * removes its section. `<fsl-toolbar>` drives this from its panel toggles.
1153
+ * Show or hide the panel slotted under `slot` (a runtime override). Hiding
1154
+ * `viz` or `editor` collapses that workbench pane (the other fills); hiding
1155
+ * an aux panel removes its section. `<fsl-toolbar>` drives this from its
1156
+ * panel toggles.
1118
1157
  *
1119
1158
  * @param slot - A panel slot name (e.g. `"viz"`, `"editor"`, `"history"`).
1120
1159
  * @param hidden - `true` to hide, `false` to show.
1121
1160
  */
1122
1161
  setPanelHidden(slot, hidden) {
1123
- if (hidden) {
1124
- this._hiddenPanels.add(slot);
1125
- }
1126
- else {
1127
- this._hiddenPanels.delete(slot);
1128
- }
1162
+ this._overrides.set(slot, hidden);
1129
1163
  this.requestUpdate();
1130
1164
  }
1131
1165
  /**
1132
- * Toggle the visibility of the panel slotted under `slot`.
1166
+ * Toggle the visibility of the panel slotted under `slot`. A no-op when the
1167
+ * panel's mode is `hide` or `show` — those lock the visibility.
1133
1168
  *
1134
1169
  * @param slot - A panel slot name (e.g. `"viz"`, `"editor"`, `"history"`).
1135
1170
  */
1136
1171
  togglePanel(slot) {
1137
- this.setPanelHidden(slot, !this._hiddenPanels.has(slot));
1172
+ var _a;
1173
+ const mode = (_a = this.panelModes[slot]) !== null && _a !== void 0 ? _a : this.panelMode;
1174
+ if (mode === 'hide' || mode === 'show') {
1175
+ return;
1176
+ }
1177
+ this.setPanelHidden(slot, !this.isPanelHidden(slot));
1138
1178
  }
1139
1179
  /**
1140
1180
  * Install or remove the window-resize listener that resolves `layout="auto"`
@@ -1181,6 +1221,7 @@ class FslInstance extends LitElement {
1181
1221
  // (The resolver guarantees `fsl` is a non-empty string when error is undefined.)
1182
1222
  const fsl_source = resolved.fsl;
1183
1223
  this._machine = this._build_machine(fsl_source);
1224
+ this._applyEditorConfig();
1184
1225
  // Step 3: paint initial host attributes + CSS custom properties.
1185
1226
  this._paint_state_reflection();
1186
1227
  // Step 4: shadow DOM render is automatic via Lit; requesting an update
@@ -1352,6 +1393,17 @@ class FslInstance extends LitElement {
1352
1393
  ? sm `${fsl_source}`
1353
1394
  : from(fsl_source, { data: this.data }));
1354
1395
  }
1396
+ /** Adopt the FSL's `editor: {}` panel request (fsl#1334): when the machine
1397
+ * declares `panels`, drive {@link requestedPanels} from it so `request` panel
1398
+ * mode honors the source. The embedder's value persists when the FSL is
1399
+ * silent. Called after each (re)build, with `_machine` freshly assigned. */
1400
+ _applyEditorConfig() {
1401
+ var _a;
1402
+ const panels = (_a = this._machine.editor_config()) === null || _a === void 0 ? void 0 : _a.panels;
1403
+ if (panels !== undefined) {
1404
+ this.requestedPanels = panels;
1405
+ }
1406
+ }
1355
1407
  _rebuild_machine() {
1356
1408
  if (typeof this.fsl !== 'string' || this.fsl.trim().length === 0) {
1357
1409
  return;
@@ -1367,6 +1419,7 @@ class FslInstance extends LitElement {
1367
1419
  this._unbind_machine_subscriptions();
1368
1420
  // Swap to the new machine and re-bind everything machine-scoped.
1369
1421
  this._machine = next;
1422
+ this._applyEditorConfig();
1370
1423
  this._paint_state_reflection();
1371
1424
  this._install_event_reemission();
1372
1425
  this._install_declarative_hooks();
@@ -1592,10 +1645,10 @@ class FslInstance extends LitElement {
1592
1645
  ${header}
1593
1646
  ${toolbar}
1594
1647
  <div class="middle">
1595
- <section class="dock actions-dock${this._hiddenPanels.has('actions') ? '' : ' open'}" part="actions-dock">
1596
- <slot name="actions"></slot>
1648
+ <section class="dock events-dock${this.isPanelHidden('hook-log') ? '' : ' open'}" part="events-dock">
1649
+ <slot name="hook-log"></slot>
1597
1650
  </section>
1598
- <div class="workbench${this._hiddenPanels.has('viz') ? ' hide-viz' : ''}${this._hiddenPanels.has('editor') ? ' hide-editor' : ''}"
1651
+ <div class="workbench${this.isPanelHidden('viz') ? ' hide-viz' : ''}${this.isPanelHidden('editor') ? ' hide-editor' : ''}"
1599
1652
  data-mode=${mode} style="--fsl-split:${this._split}%">
1600
1653
  ${mode === 'tabs' ? this._renderTabbar() : ''}
1601
1654
  <section class="pane viz" ?hidden=${mode === 'tabs' && this._tab !== 'viz'}>${viz}</section>
@@ -1604,7 +1657,7 @@ class FslInstance extends LitElement {
1604
1657
  @dblclick=${this._onGutterReset}></div>
1605
1658
  <section class="pane editor" ?hidden=${mode === 'tabs' && this._tab !== 'editor'}>${editor}</section>
1606
1659
  </div>
1607
- <section class="dock data-dock${this._hiddenPanels.has('data-inspector') ? '' : ' open'}" part="data-dock">
1660
+ <section class="dock data-dock${this.isPanelHidden('data-inspector') ? '' : ' open'}" part="data-dock">
1608
1661
  <slot name="data-inspector"></slot>
1609
1662
  </section>
1610
1663
  </div>
@@ -1618,8 +1671,8 @@ class FslInstance extends LitElement {
1618
1671
  <div class="container">
1619
1672
  ${header}
1620
1673
  ${toolbar}
1621
- <section class="viz" ?hidden=${this._hiddenPanels.has('viz')}>${viz}</section>
1622
- <section class="editor" ?hidden=${this._hiddenPanels.has('editor')}>${editor}</section>
1674
+ <section class="viz" ?hidden=${this.isPanelHidden('viz')}>${viz}</section>
1675
+ <section class="editor" ?hidden=${this.isPanelHidden('editor')}>${editor}</section>
1623
1676
  ${this._renderAuxPanels(false)}
1624
1677
  <section class="state-section"><slot name=${state_slot_name}></slot></section>
1625
1678
  <footer><slot name="footer"></slot></footer>
@@ -1627,22 +1680,23 @@ class FslInstance extends LitElement {
1627
1680
  `;
1628
1681
  }
1629
1682
  /** The stacked middle panels, shared by both layouts. The toolbar slot is
1630
- * rendered at the top of {@link render}. In split mode the `actions` and
1631
- * `data-inspector` panels are lifted out into easing side docks, so
1683
+ * rendered at the top of {@link render}. In split mode the `hook-log` (events)
1684
+ * and `data-inspector` panels are lifted out into easing side docks, so
1632
1685
  * `docked` is true there and they are skipped here to avoid duplicating
1633
- * their slots. The state-section + footer stay in {@link render} so the
1634
- * dynamic state-slot name binds at the top level.
1686
+ * their slots; `actions` instead lives here as a horizontal bar. The
1687
+ * state-section + footer stay in {@link render} so the dynamic state-slot
1688
+ * name binds at the top level.
1635
1689
  *
1636
- * @param docked - True when actions + data-inspector are rendered as side
1690
+ * @param docked - True when hook-log + data-inspector are rendered as side
1637
1691
  * docks (split layouts); they are then omitted from this stack. */
1638
1692
  _renderAuxPanels(docked) {
1639
- const h = (slot) => this._hiddenPanels.has(slot);
1693
+ const h = (slot) => this.isPanelHidden(slot);
1640
1694
  return html `
1641
- ${docked ? '' : html `<section class="actions" ?hidden=${h('actions')}><slot name="actions"></slot></section>`}
1695
+ <section class="actions" ?hidden=${h('actions')}><slot name="actions"></slot></section>
1642
1696
  <section class="info-panel" ?hidden=${h('info-panel')}><slot name="info-panel"></slot></section>
1643
1697
  <section class="history" ?hidden=${h('history')}><slot name="history"></slot></section>
1644
1698
  ${docked ? '' : html `<section class="data-inspector" ?hidden=${h('data-inspector')}><slot name="data-inspector"></slot></section>`}
1645
- <section class="hook-log" ?hidden=${h('hook-log')}><slot name="hook-log"></slot></section>
1699
+ ${docked ? '' : html `<section class="hook-log" ?hidden=${h('hook-log')}><slot name="hook-log"></slot></section>`}
1646
1700
  <section class="effective-properties" ?hidden=${h('effective-properties')}><slot name="effective-properties"></slot></section>
1647
1701
  <section class="simulation" ?hidden=${h('simulation')}><slot name="simulation"></slot></section>
1648
1702
  <section class="export" ?hidden=${h('export')}><slot name="export"></slot></section>
@@ -1682,7 +1736,7 @@ FslInstance.styles = css `
1682
1736
 
1683
1737
  /* layout modes: lr/rl (row) · tb/bt (column) · editor/viewer (single) · tabs. */
1684
1738
  .container.is-split { display: flex; flex-direction: column; }
1685
- /* the middle band: actions dock | workbench | data dock */
1739
+ /* the middle band: events dock | workbench | data dock */
1686
1740
  .middle { display: flex; flex: 1 1 auto; min-height: 0; }
1687
1741
  .workbench { display: flex; flex: 1 1 auto; min-height: 0; min-width: 0; }
1688
1742
  /* side docks ease their width like the help drawer; closed = 0, open = a
@@ -1693,7 +1747,7 @@ FslInstance.styles = css `
1693
1747
  transition: flex-basis 0.28s ease;
1694
1748
  }
1695
1749
  .dock.open { flex-basis: var(--fsl-dock-width, 17em); }
1696
- .actions-dock.open { border-right: 1px solid var(--fsl-color-border, #e5e5e5); }
1750
+ .events-dock.open { border-right: 1px solid var(--fsl-color-border, #e5e5e5); }
1697
1751
  .data-dock.open { border-left: 1px solid var(--fsl-color-border, #e5e5e5); }
1698
1752
  /* a docked panel fills the band's height; the data inspector then scrolls
1699
1753
  to the dock instead of its own 16em cap. */
@@ -1774,6 +1828,9 @@ FslInstance.properties = {
1774
1828
  themeName: { type: String, attribute: 'theme-name', reflect: true },
1775
1829
  themes: { type: Object, reflect: false },
1776
1830
  data: { type: Object, reflect: false },
1831
+ panelMode: { type: String, attribute: 'panel-mode', reflect: true },
1832
+ panelModes: { type: Object, attribute: false },
1833
+ requestedPanels: { type: Array, attribute: false },
1777
1834
  };
1778
1835
 
1779
1836
  export { FslInstance, JSSM_ON_EVENT_NAMES, auto_mode, compile_inline_body, jssm_handler_registry, parse_jssm_on_element, resolve_fsl_source, resolve_named_handler, split_ratio };
@@ -95,7 +95,42 @@ const EXPORT_FORMATS = [
95
95
  { value: 'json', label: 'JSON (serialized)' },
96
96
  { value: 'fsl', label: 'FSL source' },
97
97
  { value: 'svg', label: 'SVG' },
98
+ { value: 'permalink', label: 'Permalink (URL)' },
99
+ { value: 'embed', label: 'Embed snippet' },
98
100
  ];
101
+ /**
102
+ * A shareable URL for the given FSL: the current page URL with the source
103
+ * encoded in the hash (`#fsl=...`). A page that reads the hash on load can
104
+ * restore the machine. Browser-only (uses `location`), like the rest of the
105
+ * toolbar.
106
+ *
107
+ * @example
108
+ * permalink_for("a -> b;"); // "https://host/path#fsl=a%20-%3E%20b%3B"
109
+ */
110
+ function permalink_for(fsl) {
111
+ return `${location.href.split('#')[0]}#fsl=${encodeURIComponent(fsl)}`;
112
+ }
113
+ /**
114
+ * A paste-able HTML snippet that renders the given FSL from the CDN builds: an
115
+ * `<fsl-instance>` reading its source from a `<script type="text/fsl">` child,
116
+ * with a slotted `<fsl-viz>` for the graph.
117
+ *
118
+ * @example
119
+ * embed_snippet_for("a -> b;"); // "<script …instance.js …><fsl-instance>…</fsl-instance>"
120
+ */
121
+ function embed_snippet_for(fsl) {
122
+ const CDN = 'https://cdn.jsdelivr.net/npm/jssm/dist/cdn';
123
+ return [
124
+ `<script type="module" src="${CDN}/instance.js"></script>`,
125
+ `<script type="module" src="${CDN}/viz.js"></script>`,
126
+ '<fsl-instance>',
127
+ ' <fsl-viz slot="viz"></fsl-viz>',
128
+ ' <script type="text/fsl">',
129
+ fsl,
130
+ ' </script>',
131
+ '</fsl-instance>',
132
+ ].join('\n');
133
+ }
99
134
  /* Panel icons — Solar (CC BY 4.0) bold-duotone. Layout icons — hand-drawn
100
135
  duotone split-rects. All use currentColor (+ baked opacity on the secondary
101
136
  tone), so they theme with the button's text color and pressed state. Each is
@@ -121,6 +156,9 @@ const ICON_AUTO = html `<svg class="ico" viewBox="0 0 24 24" aria-hidden="true">
121
156
  const ICON_ACTIONS = html `<svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" fill-rule="evenodd" d="M8.732 5.771L5.67 9.914c-1.285 1.739-1.928 2.608-1.574 3.291l.018.034c.375.673 1.485.673 3.704.673c1.233 0 1.85 0 2.236.363l.02.02l3.872-4.57l-.02-.02c-.379-.371-.379-.963-.379-2.148v-.31c0-3.285 0-4.927-.923-5.21s-1.913 1.056-3.892 3.734" clip-rule="evenodd"/><path fill="currentColor" d="M10.453 16.443v.31c0 3.284 0 4.927.923 5.21s1.913-1.056 3.893-3.734l3.062-4.143c1.284-1.739 1.927-2.608 1.573-3.291l-.018-.034c-.375-.673-1.485-.673-3.704-.673c-1.233 0-1.85 0-2.236-.363l-3.872 4.57c.379.371.379.963.379 2.148" opacity=".5"/></svg>`;
122
157
  /* Solar palette-round-bold-duotone — the theme pulldown. */
123
158
  const ICON_THEME = html `<svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M7 18a1 1 0 1 1-2 0a1 1 0 0 1 2 0"/><path fill="currentColor" d="M10 6v12a4 4 0 0 1-8 0V6a4 4 0 1 1 8 0" opacity=".4"/><path fill="currentColor" d="m9.248 20.336l3.974-3.975l5.838-6.09a4.042 4.042 0 0 0-5.776-5.655L10 7.9V18c0 .872-.279 1.679-.752 2.336" opacity=".7"/><path fill="currentColor" d="m13.222 16.362l-3.974 3.974A4 4 0 0 1 6 22h11.9a4 4 0 1 0 0-8h-2.414z"/></svg>`;
159
+ /* Validate — a duotone check-circle. Lint — a duotone document with rule lines. */
160
+ const ICON_VALIDATE = html `<svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2s10 4.477 10 10" opacity=".4"/><path fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" d="m8.5 12.5l2.5 2.5l4.5-5.5"/></svg>`;
161
+ const ICON_LINT = html `<svg class="ico" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M3 10c0-3.771 0-5.657 1.172-6.828S7.229 2 11 2h2c3.771 0 5.657 0 6.828 1.172S21 6.229 21 10v4c0 3.771 0 5.657-1.172 6.828S16.771 22 13 22h-2c-3.771 0-5.657 0-6.828-1.172S3 17.771 3 14z" opacity=".4"/><path fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" d="M7.5 8.5h9M7.5 12h6M7.5 15.5h3"/></svg>`;
124
162
  const PANELS = [
125
163
  { slot: 'actions', label: 'Actions', icon: ICON_ACTIONS },
126
164
  { slot: 'viz', label: 'Renderer', icon: ICON_VIZ },
@@ -144,11 +182,13 @@ const LAYOUTS = [
144
182
  { value: 'tabs', label: 'Tabbed', icon: ICON_TABS },
145
183
  ];
146
184
  /**
147
- * `<fsl-toolbar>` — a control bar for a parent `<fsl-instance>`. A light/dark
148
- * theme toggle on the left; on the right, an icon toggle to show/hide each
149
- * panel present in the host (renderer, code, history, …) plus a View menu of
150
- * the layout set (its button shows the current layout's icon). Standalone (no
151
- * host) the panel toggles disappear. A trailing slot carries extra buttons.
185
+ * `<fsl-toolbar>` — a control bar for a parent `<fsl-instance>`. Validate + Lint
186
+ * action buttons (fired as `fsl-validate` / `fsl-lint` events for the embedder
187
+ * to fulfill, each suppressible via `no-validate` / `no-lint`); an icon toggle
188
+ * to show/hide each panel present in the host (renderer, code, history, …); and
189
+ * Layout / Export / Theme pulldowns (the Layout button shows the current
190
+ * layout's icon). Standalone (no host) the host-dependent controls disappear.
191
+ * A trailing slot carries extra buttons.
152
192
  *
153
193
  * @element fsl-toolbar
154
194
  * @csspart toolbar - The bar container.
@@ -166,6 +206,10 @@ class FslToolbar extends LitElement {
166
206
  * The embedder sets this after fulfilling a `pick` export.
167
207
  */
168
208
  this.lastDirectory = '';
209
+ /** Hide the Validate button (e.g. when the consumer validates inline). */
210
+ this.noValidate = false;
211
+ /** Hide the Lint button. */
212
+ this.noLint = false;
169
213
  this._host = null;
170
214
  /** Panels actually present in the host — one toggle each. */
171
215
  this._present = [];
@@ -218,11 +262,24 @@ class FslToolbar extends LitElement {
218
262
  else if (format === 'svg') {
219
263
  content = await machine_to_svg_string(host.machine);
220
264
  }
265
+ else if (format === 'permalink') {
266
+ content = permalink_for(host.fsl);
267
+ }
268
+ else if (format === 'embed') {
269
+ content = embed_snippet_for(host.fsl);
270
+ }
221
271
  else {
222
272
  content = host.fsl;
223
273
  }
224
274
  this.dispatchEvent(new CustomEvent('fsl-export', { detail: { format, content, destination }, bubbles: true, composed: true }));
225
275
  }
276
+ /** Fire a workspace-action intent (validate / lint) for the consumer to
277
+ * fulfill — the toolbar presents the action; the embedder runs it. The
278
+ * current machine source rides along in the detail as a convenience. The
279
+ * buttons only render with a host, so `_host` is non-null here. */
280
+ _fireAction(type) {
281
+ this.dispatchEvent(new CustomEvent(type, { detail: { fsl: this._host.fsl }, bubbles: true, composed: true }));
282
+ }
226
283
  _toggleMenu(which) { this._openMenu = this._openMenu === which ? '' : which; }
227
284
  render() {
228
285
  var _a, _b, _c, _d, _e;
@@ -235,6 +292,13 @@ class FslToolbar extends LitElement {
235
292
  return html `
236
293
  <div class="toolbar" part="toolbar" role="toolbar" aria-label="Workbench controls">
237
294
  <span class="spacer"></span>
295
+ ${host ? html `
296
+ <div class="grp">
297
+ ${this.noValidate ? '' : html `
298
+ <button class="tb icon" aria-label="Validate" title="Validate" @click=${() => this._fireAction('fsl-validate')}>${ICON_VALIDATE}</button>`}
299
+ ${this.noLint ? '' : html `
300
+ <button class="tb icon" aria-label="Lint" title="Lint" @click=${() => this._fireAction('fsl-lint')}>${ICON_LINT}</button>`}
301
+ </div>` : ''}
238
302
  <div class="grp">
239
303
  ${host
240
304
  ? this._present.map(p => html `
@@ -336,6 +400,12 @@ __decorate$7([
336
400
  __decorate$7([
337
401
  property({ attribute: false })
338
402
  ], FslToolbar.prototype, "lastDirectory", void 0);
403
+ __decorate$7([
404
+ property({ type: Boolean, attribute: 'no-validate' })
405
+ ], FslToolbar.prototype, "noValidate", void 0);
406
+ __decorate$7([
407
+ property({ type: Boolean, attribute: 'no-lint' })
408
+ ], FslToolbar.prototype, "noLint", void 0);
339
409
 
340
410
  var __decorate$6 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
341
411
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -465,18 +535,21 @@ class FslActions extends LitElement {
465
535
  }
466
536
  FslActions.styles = css `
467
537
  :host { display: block; }
538
+ /* Horizontal bar: groups flow left-to-right and wrap; within a group the
539
+ label sits inline before its buttons. Suits the panel's home below the
540
+ workbench rather than a tall side dock. */
468
541
  .actions {
469
- display: flex; flex-direction: column; gap: 0.7rem;
542
+ display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 0.4rem 1.1rem;
470
543
  padding: 0.55rem 0.65rem; background: var(--_fsl-surface);
471
544
  color: var(--_fsl-text); font: 0.8rem var(--_fsl-font);
472
545
  }
473
- .group { display: flex; flex-direction: column; gap: 0.3rem; }
546
+ .group { display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 0.3rem; }
474
547
  .label {
475
548
  font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.05em;
476
- color: var(--_fsl-muted);
549
+ color: var(--_fsl-muted); white-space: nowrap;
477
550
  }
478
551
  button {
479
- text-align: left; padding: 0.4rem 0.6rem; cursor: pointer; border-radius: 4px;
552
+ text-align: center; padding: 0.4rem 0.6rem; cursor: pointer; border-radius: 4px;
480
553
  border: 1px solid var(--_fsl-border); background: var(--_fsl-surface);
481
554
  color: var(--_fsl-text); font: 600 0.8rem var(--_fsl-font);
482
555
  }
@@ -503,15 +576,23 @@ var __decorate$5 = (undefined && undefined.__decorate) || function (decorators,
503
576
  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;
504
577
  return c > 3 && r && Object.defineProperty(target, key, r), r;
505
578
  };
579
+ /** `<n> word`, with a regular plural `s` unless `n === 1`. */
580
+ function plural(n, word) {
581
+ return `${n} ${word}${n === 1 ? '' : 's'}`;
582
+ }
506
583
  /**
507
584
  * `<fsl-footer>` — a status bar for a parent `<fsl-instance>`.
508
585
  *
509
- * Reflects the host's current state, legal-action count, and
510
- * terminal/complete status by observing the host's reflected attributes
511
- * (`current-state`, `legal-actions`, `terminal`, `complete`) so it tracks
512
- * transitions *and* survives a live machine rebuild (#1387) without a machine
513
- * subscription. A default slot carries embedder status (line/column, parse
514
- * state, …). Standalone (no `<fsl-instance>` ancestor) it renders just the slot.
586
+ * Shows the current state with both **local** counts (actions firable from
587
+ * here + transitions out of here) and **global** machine counts: distinct
588
+ * action *names*, **action-starts** (action-bearing edgeseach place an
589
+ * action fires from), and total transitions. Plus terminal/complete badges.
590
+ *
591
+ * Local counts track transitions by observing the host's reflected
592
+ * `current-state` / `legal-actions` attributes; the global counts refresh on
593
+ * `fsl-machine-rebuilt`, so the footer survives a live rebuild (#1387). A
594
+ * default slot carries embedder status. Standalone (no `<fsl-instance>`
595
+ * ancestor) it renders just the slot.
515
596
  *
516
597
  * @element fsl-footer
517
598
  * @csspart bar - The footer bar container.
@@ -521,14 +602,21 @@ class FslFooter extends LitElement {
521
602
  constructor() {
522
603
  super(...arguments);
523
604
  this._state = '';
524
- this._actions = 0;
605
+ this._actions = 0; // local: actions firable from the current state
606
+ this._transitions = 0; // local: transitions out of the current state
525
607
  this._terminal = false;
526
608
  this._complete = false;
609
+ this._gActions = 0; // global: distinct action names
610
+ this._gStarts = 0; // global: action-starts (action-bearing edges)
611
+ this._gTransitions = 0; // global: total transitions (all edges)
527
612
  this._observer = null;
613
+ this._host = null;
614
+ this._onRebuilt = () => { this._syncMachine(); };
528
615
  }
529
616
  connectedCallback() {
530
617
  super.connectedCallback();
531
618
  const host = closest_wc(this, 'instance');
619
+ this._host = host;
532
620
  if (host === null) {
533
621
  return;
534
622
  }
@@ -539,12 +627,14 @@ class FslFooter extends LitElement {
539
627
  this._actions = la === '' ? 0 : la.split(/\s+/).length;
540
628
  this._terminal = host.hasAttribute('terminal');
541
629
  this._complete = host.hasAttribute('complete');
630
+ this._syncMachine();
542
631
  };
543
632
  this._observer = new MutationObserver(sync);
544
633
  this._observer.observe(host, {
545
634
  attributes: true,
546
635
  attributeFilter: ['current-state', 'legal-actions', 'terminal', 'complete'],
547
636
  });
637
+ host.addEventListener('fsl-machine-rebuilt', this._onRebuilt);
548
638
  sync();
549
639
  }
550
640
  disconnectedCallback() {
@@ -553,12 +643,29 @@ class FslFooter extends LitElement {
553
643
  this._observer.disconnect();
554
644
  this._observer = null;
555
645
  }
646
+ if (this._host !== null) {
647
+ this._host.removeEventListener('fsl-machine-rebuilt', this._onRebuilt);
648
+ this._host = null;
649
+ }
650
+ }
651
+ /** Recompute the machine-derived counts: local transitions out of the current
652
+ * state, and the global action / action-start / transition totals. Only
653
+ * called while a host is attached, so `_host` is non-null here. */
654
+ _syncMachine() {
655
+ var _a;
656
+ const host = this._host;
657
+ const current = (_a = host.getAttribute('current-state')) !== null && _a !== void 0 ? _a : '';
658
+ const edges = host.machine.list_edges();
659
+ this._transitions = edges.filter(e => String(e.from) === current).length;
660
+ const actionEdges = edges.filter(e => typeof e.action === 'string');
661
+ this._gStarts = actionEdges.length;
662
+ this._gActions = new Set(actionEdges.map(e => e.action)).size;
663
+ this._gTransitions = edges.length;
556
664
  }
557
665
  render() {
558
666
  return html `
559
667
  <div class="bar" part="bar">
560
- ${this._state ? html `<span class="state">${this._state}</span>` : ''}
561
- <span>${this._actions} action${this._actions === 1 ? '' : 's'}</span>
668
+ <span>${this._state ? html `<span class="state">${this._state}</span>, ` : ''}${plural(this._actions, 'action')}, ${plural(this._transitions, 'transition')}; globally ${plural(this._gActions, 'action')}, ${plural(this._gStarts, 'start')}, ${plural(this._gTransitions, 'transition')}</span>
562
669
  ${this._terminal ? html `<span class="badge">terminal</span>` : ''}
563
670
  ${this._complete ? html `<span class="badge">complete</span>` : ''}
564
671
  <span class="spacer"></span>
@@ -585,12 +692,24 @@ __decorate$5([
585
692
  __decorate$5([
586
693
  state()
587
694
  ], FslFooter.prototype, "_actions", void 0);
695
+ __decorate$5([
696
+ state()
697
+ ], FslFooter.prototype, "_transitions", void 0);
588
698
  __decorate$5([
589
699
  state()
590
700
  ], FslFooter.prototype, "_terminal", void 0);
591
701
  __decorate$5([
592
702
  state()
593
703
  ], FslFooter.prototype, "_complete", void 0);
704
+ __decorate$5([
705
+ state()
706
+ ], FslFooter.prototype, "_gActions", void 0);
707
+ __decorate$5([
708
+ state()
709
+ ], FslFooter.prototype, "_gStarts", void 0);
710
+ __decorate$5([
711
+ state()
712
+ ], FslFooter.prototype, "_gTransitions", void 0);
594
713
 
595
714
  var __decorate$4 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
596
715
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
package/jssm.es5.d.cts CHANGED
@@ -515,6 +515,15 @@ declare type JssmBaseTheme = {
515
515
  * @typeParam StateType - The state-name type (usually `string`).
516
516
  * @typeParam DataType - The user-supplied data payload type (`mDT`).
517
517
  */
518
+ /**
519
+ * Editor/panel defaults an FSL machine declares in an `editor: {}` block
520
+ * (fsl#1334), read by the all-widgets web control: a stochastic run-count
521
+ * and the panels the machine requests under `request` panel mode.
522
+ */
523
+ declare type JssmEditorConfig = {
524
+ stochastic_run_count?: number;
525
+ panels?: Array<string>;
526
+ };
518
527
  declare type JssmGenericConfig<StateType, DataType> = {
519
528
  graph_layout?: JssmLayout;
520
529
  complete?: Array<StateType>;
@@ -544,6 +553,7 @@ declare type JssmGenericConfig<StateType, DataType> = {
544
553
  min_exits?: number;
545
554
  max_exits?: number;
546
555
  allow_islands?: JssmAllowIslands;
556
+ editor_config?: JssmEditorConfig;
547
557
  allow_force?: false;
548
558
  actions?: JssmPermittedOpt;
549
559
  simplify_bidi?: boolean;
@@ -2044,6 +2054,7 @@ declare class Machine<mDT> {
2044
2054
  _code_allows_override: JssmAllowsOverride;
2045
2055
  _config_allows_override: JssmAllowsOverride;
2046
2056
  _allow_islands: JssmAllowIslands;
2057
+ _editor_config?: JssmEditorConfig;
2047
2058
  _post_hooks: Map<number, HookHandler<mDT>>;
2048
2059
  _post_named_hooks: Map<number, Map<number, HookHandler<mDT>>>;
2049
2060
  _post_entry_hooks: Map<number, HookHandler<mDT>>;
@@ -2093,7 +2104,7 @@ declare class Machine<mDT> {
2093
2104
  _firing_error: boolean;
2094
2105
  _boundary_depth: number;
2095
2106
  _boundary_depth_limit: number;
2096
- constructor({ start_states, end_states, failed_outputs, initial_state, start_states_no_enforce, complete, transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, npm_name, default_size, state_declaration, property_definition, state_property, fsl_version, dot_preamble, arrange_declaration, arrange_start_declaration, arrange_end_declaration, theme, flow, graph_layout, instance_name, history, boundary_depth_limit, data, default_state_config, default_active_state_config, default_hooked_state_config, default_terminal_state_config, default_start_state_config, default_end_state_config, default_transition_config, default_graph_config, group_registry, group_metadata, group_hooks, state_hooks, allows_override, config_allows_override, allow_islands, rng_seed, time_source, timeout_source, clear_timeout_source }: JssmGenericConfig<StateType, mDT>);
2107
+ constructor({ start_states, end_states, failed_outputs, initial_state, start_states_no_enforce, complete, transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, npm_name, default_size, state_declaration, property_definition, state_property, fsl_version, dot_preamble, arrange_declaration, arrange_start_declaration, arrange_end_declaration, theme, flow, graph_layout, instance_name, history, boundary_depth_limit, data, default_state_config, default_active_state_config, default_hooked_state_config, default_terminal_state_config, default_start_state_config, default_end_state_config, default_transition_config, default_graph_config, group_registry, group_metadata, group_hooks, state_hooks, allows_override, config_allows_override, allow_islands, editor_config, rng_seed, time_source, timeout_source, clear_timeout_source }: JssmGenericConfig<StateType, mDT>);
2097
2108
  /********
2098
2109
  *
2099
2110
  * Internal method for fabricating states. Not meant for external use.
@@ -2521,6 +2532,17 @@ declare class Machine<mDT> {
2521
2532
  * @returns The machine name string.
2522
2533
  */
2523
2534
  machine_name(): string;
2535
+ /** The editor/panel defaults declared in the FSL `editor: {}` block, or
2536
+ * `undefined` when none was given. Read by the all-widgets web control
2537
+ * (fsl#1334) — `panels` drives `request` panel mode.
2538
+ *
2539
+ * @returns `{ stochastic_run_count?, panels? }`, or `undefined`.
2540
+ *
2541
+ * @example
2542
+ * const m = sm`editor: { panels: [history]; }; a -> b;`;
2543
+ * m.editor_config(); // => { panels: ['history'] }
2544
+ */
2545
+ editor_config(): JssmEditorConfig | undefined;
2524
2546
  /** Get the npm package name associated with the machine. Set via the FSL `npm_name` directive.
2525
2547
  * Returns `undefined` when not present.
2526
2548
  * @returns The npm package name string, or `undefined`.
package/jssm.es6.d.ts CHANGED
@@ -515,6 +515,15 @@ declare type JssmBaseTheme = {
515
515
  * @typeParam StateType - The state-name type (usually `string`).
516
516
  * @typeParam DataType - The user-supplied data payload type (`mDT`).
517
517
  */
518
+ /**
519
+ * Editor/panel defaults an FSL machine declares in an `editor: {}` block
520
+ * (fsl#1334), read by the all-widgets web control: a stochastic run-count
521
+ * and the panels the machine requests under `request` panel mode.
522
+ */
523
+ declare type JssmEditorConfig = {
524
+ stochastic_run_count?: number;
525
+ panels?: Array<string>;
526
+ };
518
527
  declare type JssmGenericConfig<StateType, DataType> = {
519
528
  graph_layout?: JssmLayout;
520
529
  complete?: Array<StateType>;
@@ -544,6 +553,7 @@ declare type JssmGenericConfig<StateType, DataType> = {
544
553
  min_exits?: number;
545
554
  max_exits?: number;
546
555
  allow_islands?: JssmAllowIslands;
556
+ editor_config?: JssmEditorConfig;
547
557
  allow_force?: false;
548
558
  actions?: JssmPermittedOpt;
549
559
  simplify_bidi?: boolean;
@@ -2044,6 +2054,7 @@ declare class Machine<mDT> {
2044
2054
  _code_allows_override: JssmAllowsOverride;
2045
2055
  _config_allows_override: JssmAllowsOverride;
2046
2056
  _allow_islands: JssmAllowIslands;
2057
+ _editor_config?: JssmEditorConfig;
2047
2058
  _post_hooks: Map<number, HookHandler<mDT>>;
2048
2059
  _post_named_hooks: Map<number, Map<number, HookHandler<mDT>>>;
2049
2060
  _post_entry_hooks: Map<number, HookHandler<mDT>>;
@@ -2093,7 +2104,7 @@ declare class Machine<mDT> {
2093
2104
  _firing_error: boolean;
2094
2105
  _boundary_depth: number;
2095
2106
  _boundary_depth_limit: number;
2096
- constructor({ start_states, end_states, failed_outputs, initial_state, start_states_no_enforce, complete, transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, npm_name, default_size, state_declaration, property_definition, state_property, fsl_version, dot_preamble, arrange_declaration, arrange_start_declaration, arrange_end_declaration, theme, flow, graph_layout, instance_name, history, boundary_depth_limit, data, default_state_config, default_active_state_config, default_hooked_state_config, default_terminal_state_config, default_start_state_config, default_end_state_config, default_transition_config, default_graph_config, group_registry, group_metadata, group_hooks, state_hooks, allows_override, config_allows_override, allow_islands, rng_seed, time_source, timeout_source, clear_timeout_source }: JssmGenericConfig<StateType, mDT>);
2107
+ constructor({ start_states, end_states, failed_outputs, initial_state, start_states_no_enforce, complete, transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, npm_name, default_size, state_declaration, property_definition, state_property, fsl_version, dot_preamble, arrange_declaration, arrange_start_declaration, arrange_end_declaration, theme, flow, graph_layout, instance_name, history, boundary_depth_limit, data, default_state_config, default_active_state_config, default_hooked_state_config, default_terminal_state_config, default_start_state_config, default_end_state_config, default_transition_config, default_graph_config, group_registry, group_metadata, group_hooks, state_hooks, allows_override, config_allows_override, allow_islands, editor_config, rng_seed, time_source, timeout_source, clear_timeout_source }: JssmGenericConfig<StateType, mDT>);
2097
2108
  /********
2098
2109
  *
2099
2110
  * Internal method for fabricating states. Not meant for external use.
@@ -2521,6 +2532,17 @@ declare class Machine<mDT> {
2521
2532
  * @returns The machine name string.
2522
2533
  */
2523
2534
  machine_name(): string;
2535
+ /** The editor/panel defaults declared in the FSL `editor: {}` block, or
2536
+ * `undefined` when none was given. Read by the all-widgets web control
2537
+ * (fsl#1334) — `panels` drives `request` panel mode.
2538
+ *
2539
+ * @returns `{ stochastic_run_count?, panels? }`, or `undefined`.
2540
+ *
2541
+ * @example
2542
+ * const m = sm`editor: { panels: [history]; }; a -> b;`;
2543
+ * m.editor_config(); // => { panels: ['history'] }
2544
+ */
2545
+ editor_config(): JssmEditorConfig | undefined;
2524
2546
  /** Get the npm package name associated with the machine. Set via the FSL `npm_name` directive.
2525
2547
  * Returns `undefined` when not present.
2526
2548
  * @returns The npm package name string, or `undefined`.