jssm 5.147.10 → 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/README.md +7 -7
- package/custom-elements.json +2618 -617
- package/dist/cdn/instance.js +1526 -921
- package/dist/cdn/viz.js +990 -872
- package/dist/cli/fsl-export-system-prompt.cjs +1 -1
- package/dist/cli/fsl-render.cjs +1 -1
- package/dist/cli/fsl.cjs +1 -1
- package/dist/cli/lib.cjs +1 -1
- package/dist/cli/lib.mjs +1 -1
- package/dist/cm6/fsl_language.js +15 -2
- package/dist/deno/README.md +7 -7
- package/dist/deno/jssm.d.ts +1 -0
- 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/editor.define.js +52 -0
- package/dist/wc/editor.js +397 -0
- package/dist/wc/instance.js +589 -56
- package/dist/wc/viz.js +87 -5
- package/dist/wc/widgets.define.js +65 -0
- package/dist/wc/widgets.js +1113 -0
- package/jssm.es5.d.cts +90 -1
- package/jssm.es6.d.ts +90 -1
- package/package.json +30 -4
package/dist/wc/instance.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { css, LitElement, html } from 'lit';
|
|
2
|
-
import { sm } from 'jssm';
|
|
2
|
+
import { sm, from } from 'jssm';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Shared helpers for the dual-prefix (`fsl-` canonical, `jssm-` synonym)
|
|
@@ -461,6 +461,94 @@ function build_hook_descriptor(spec, wrapped) {
|
|
|
461
461
|
return base;
|
|
462
462
|
}
|
|
463
463
|
|
|
464
|
+
/**
|
|
465
|
+
* Theme registry for the fsl-* suite. A **theme** is a named pair of light/dark
|
|
466
|
+
* palettes; a **mode** (`system` | `light` | `dark`) picks which variant applies,
|
|
467
|
+
* with `system` following the OS `prefers-color-scheme`. The host resolves
|
|
468
|
+
* (theme name × mode) to a palette and writes it as `--fsl-color-*` custom
|
|
469
|
+
* properties, which cascade to every slotted widget.
|
|
470
|
+
*
|
|
471
|
+
* @see resolve_theme_mode
|
|
472
|
+
*/
|
|
473
|
+
/**
|
|
474
|
+
* The built-in themes: `Default` (the suite's house palette) and `Solarized`
|
|
475
|
+
* (Ethan Schoonover's palette). Consumers can extend the host's `themes`
|
|
476
|
+
* registry with their own; every entry shows up in the toolbar's theme list.
|
|
477
|
+
*/
|
|
478
|
+
const BUILTIN_THEMES = {
|
|
479
|
+
Default: {
|
|
480
|
+
light: {
|
|
481
|
+
surface: '#ffffff', text: '#222222', accent: '#5b9dff', border: '#e5e5e5', muted: '#9aa0a6',
|
|
482
|
+
'json-key': '#5b3da8', 'json-string': '#2e7d32', 'json-number': '#b8860b', 'json-atom': '#c2185b',
|
|
483
|
+
},
|
|
484
|
+
dark: {
|
|
485
|
+
surface: '#1e1e22', text: '#d6d6d6', accent: '#82aaff', border: '#2a2a2e', muted: '#5a5f66',
|
|
486
|
+
'json-key': '#82aaff', 'json-string': '#c3e88d', 'json-number': '#f78c6c', 'json-atom': '#c792ea',
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
Solarized: {
|
|
490
|
+
light: {
|
|
491
|
+
surface: '#fdf6e3', text: '#657b83', accent: '#268bd2', border: '#eee8d5', muted: '#93a1a1',
|
|
492
|
+
'json-key': '#6c71c4', 'json-string': '#859900', 'json-number': '#b58900', 'json-atom': '#d33682',
|
|
493
|
+
},
|
|
494
|
+
dark: {
|
|
495
|
+
surface: '#002b36', text: '#839496', accent: '#268bd2', border: '#073642', muted: '#586e75',
|
|
496
|
+
'json-key': '#6c71c4', 'json-string': '#859900', 'json-number': '#b58900', 'json-atom': '#d33682',
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
/**
|
|
501
|
+
* Resolve a `(registry, theme name, variant)` triple to a concrete palette,
|
|
502
|
+
* falling back to the built-in `Default` theme when the name is unknown.
|
|
503
|
+
*
|
|
504
|
+
* @param themes - The registry to look in.
|
|
505
|
+
* @param name - The selected theme name.
|
|
506
|
+
* @param variant - `light` or `dark`.
|
|
507
|
+
* @returns The palette to apply.
|
|
508
|
+
*/
|
|
509
|
+
function resolve_palette(themes, name, variant) {
|
|
510
|
+
var _a;
|
|
511
|
+
return ((_a = themes[name]) !== null && _a !== void 0 ? _a : BUILTIN_THEMES['Default'])[variant];
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Resolve a theme mode to a concrete `light`/`dark`. `system` consults the OS
|
|
515
|
+
* preference; `light`/`dark` are returned as-is.
|
|
516
|
+
*
|
|
517
|
+
* @param mode - The selected mode.
|
|
518
|
+
* @param prefers_dark - Whether the OS prefers a dark color scheme.
|
|
519
|
+
* @returns The concrete variant to apply.
|
|
520
|
+
*
|
|
521
|
+
* @example
|
|
522
|
+
* resolve_theme_mode('system', true); // 'dark'
|
|
523
|
+
* resolve_theme_mode('light', true); // 'light'
|
|
524
|
+
*/
|
|
525
|
+
function resolve_theme_mode(mode, prefers_dark) {
|
|
526
|
+
return mode === 'system' ? (prefers_dark ? 'dark' : 'light') : mode;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Map a palette to its `--fsl-color-*` custom-property entries, ready to set on
|
|
530
|
+
* an element's style.
|
|
531
|
+
*
|
|
532
|
+
* @param palette - The resolved palette.
|
|
533
|
+
* @returns `[propertyName, value]` pairs, e.g. `['--fsl-color-surface', '#fff']`.
|
|
534
|
+
*/
|
|
535
|
+
function palette_to_vars(palette) {
|
|
536
|
+
return Object.entries(palette).map(([k, v]) => [`--fsl-color-${k}`, v]);
|
|
537
|
+
}
|
|
538
|
+
function register_palette_properties() {
|
|
539
|
+
if (typeof CSS === 'undefined' || typeof CSS.registerProperty !== 'function') {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
for (const [prop, value] of palette_to_vars(BUILTIN_THEMES['Default'].light)) {
|
|
543
|
+
try {
|
|
544
|
+
CSS.registerProperty({ name: prop, syntax: '<color>', inherits: true, initialValue: value });
|
|
545
|
+
}
|
|
546
|
+
catch (_a) {
|
|
547
|
+
// already registered by another host or a prior call
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
464
552
|
/**
|
|
465
553
|
* Allow-list of event names accepted by `<jssm-on event="...">`. Must stay
|
|
466
554
|
* in sync with the `JssmEventName` union in `jssm_types.ts` (the library's
|
|
@@ -682,6 +770,10 @@ function resolve_fsl_source(host, fsl_attr) {
|
|
|
682
770
|
n.remove();
|
|
683
771
|
}
|
|
684
772
|
});
|
|
773
|
+
// Drop every element assigned to a named slot — slotted UI (toolbar, panels,
|
|
774
|
+
// action buttons) is projected into the host, not FSL source. Only the
|
|
775
|
+
// default-slot text (the literal FSL) should remain.
|
|
776
|
+
clone.querySelectorAll('[slot]').forEach(n => n.remove());
|
|
685
777
|
return (clone.textContent || '').trim();
|
|
686
778
|
})();
|
|
687
779
|
if (text_content_fsl.length > 0) {
|
|
@@ -703,6 +795,36 @@ function resolve_fsl_source(host, fsl_attr) {
|
|
|
703
795
|
const only = sources[0];
|
|
704
796
|
return { fsl: only.fsl, provided_count: 1, error: undefined };
|
|
705
797
|
}
|
|
798
|
+
/**
|
|
799
|
+
* Resolve `layout="auto"` to a concrete split direction from the viewport
|
|
800
|
+
* shape: side-by-side (`'lr'`) when at least as wide as tall, else stacked
|
|
801
|
+
* (`'tb'`). Pure, so it's testable without a laid-out DOM.
|
|
802
|
+
*
|
|
803
|
+
* @example
|
|
804
|
+
* auto_mode(1200, 800); // => 'lr'
|
|
805
|
+
* auto_mode(600, 900); // => 'tb'
|
|
806
|
+
*/
|
|
807
|
+
function auto_mode(width, height) {
|
|
808
|
+
return width >= height ? 'lr' : 'tb';
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Clamp a gutter-drag coordinate to a split ratio (percent of the first pane).
|
|
812
|
+
* Pure so it's testable without a laid-out DOM: returns a neutral `50` when the
|
|
813
|
+
* container has no measured size (e.g. jsdom, where `getBoundingClientRect`
|
|
814
|
+
* yields zeros), and otherwise clamps to `[15, 85]` so neither pane collapses.
|
|
815
|
+
*
|
|
816
|
+
* @example
|
|
817
|
+
* split_ratio(30, 0, 100); // => 30
|
|
818
|
+
* split_ratio(5, 0, 100); // => 15 (clamped low)
|
|
819
|
+
* split_ratio(0, 0, 0); // => 50 (no layout)
|
|
820
|
+
*/
|
|
821
|
+
function split_ratio(coord, start, size) {
|
|
822
|
+
if (size <= 0) {
|
|
823
|
+
return 50;
|
|
824
|
+
}
|
|
825
|
+
const pct = ((coord - start) / size) * 100;
|
|
826
|
+
return Math.min(85, Math.max(15, pct));
|
|
827
|
+
}
|
|
706
828
|
/**
|
|
707
829
|
* Web component that owns a single `Machine<unknown>` constructed from an
|
|
708
830
|
* FSL source supplied via one of three mutually exclusive channels:
|
|
@@ -744,6 +866,50 @@ class FslInstance extends LitElement {
|
|
|
744
866
|
* `<script type="text/fsl">` (or non-empty text content) is an error.
|
|
745
867
|
*/
|
|
746
868
|
this.fsl = '';
|
|
869
|
+
/**
|
|
870
|
+
* Panel arrangement of the viz + editor panes (see {@link FslLayout}). `''`
|
|
871
|
+
* (default) renders the stacked sections; `'lr'`/`'rl'` lay them side-by-side
|
|
872
|
+
* with a draggable vertical gutter (editor left / right); `'tb'`/`'bt'` stack
|
|
873
|
+
* them with a horizontal gutter; `'editor'`/`'viewer'` show a single pane;
|
|
874
|
+
* `'tabs'` shows one pane at a time behind a tab strip; `'auto'` follows the
|
|
875
|
+
* viewport aspect. Other panels (toolbar, info-panel, …) render below.
|
|
876
|
+
*/
|
|
877
|
+
this.layout = '';
|
|
878
|
+
/**
|
|
879
|
+
* Theme **mode**, reflected to the `theme` attribute: `system` (follow the OS
|
|
880
|
+
* `prefers-color-scheme`), `light`, or `dark`. Combined with {@link themeName}
|
|
881
|
+
* it selects a palette from {@link themes}, applied as inline `--fsl-color-*`
|
|
882
|
+
* by {@link _applyTheme}. `<fsl-toolbar>` sets this from its theme pulldown.
|
|
883
|
+
*/
|
|
884
|
+
this.theme = 'light';
|
|
885
|
+
/**
|
|
886
|
+
* Selected theme name, reflected to the `theme-name` attribute. A key of
|
|
887
|
+
* {@link themes}; an unknown name falls back to the built-in `Default`.
|
|
888
|
+
*/
|
|
889
|
+
this.themeName = 'Default';
|
|
890
|
+
/**
|
|
891
|
+
* The theme registry — named light/dark palette pairs. Defaults to the
|
|
892
|
+
* built-in `Default` + `Solarized`; a consumer can replace or extend it, and
|
|
893
|
+
* every entry appears in the toolbar's theme list.
|
|
894
|
+
*/
|
|
895
|
+
this.themes = BUILTIN_THEMES;
|
|
896
|
+
/**
|
|
897
|
+
* Initial extended-state data seeded into the machine at build time. When set
|
|
898
|
+
* (to anything other than `undefined`), the machine is built via `from(fsl,
|
|
899
|
+
* { data })` so `<fsl-data-inspector>` has something to show before any
|
|
900
|
+
* transition; the default keeps the lighter `sm`-tag build path.
|
|
901
|
+
*/
|
|
902
|
+
this.data = undefined;
|
|
903
|
+
/** Split ratio (percent of the first pane), updated by the gutter drag. */
|
|
904
|
+
this._split = 50;
|
|
905
|
+
/** Which pane the tabbed layout shows. */
|
|
906
|
+
this._tab = 'viz';
|
|
907
|
+
/** Concrete direction that `layout="auto"` currently resolves to. */
|
|
908
|
+
this._autoMode = 'lr';
|
|
909
|
+
/** Window-resize listener installed while `layout="auto"`, or null. */
|
|
910
|
+
this._autoListener = null;
|
|
911
|
+
/** Slot names of panels currently hidden (driven by `<fsl-toolbar>`). */
|
|
912
|
+
this._hiddenPanels = new Set();
|
|
747
913
|
/**
|
|
748
914
|
* The underlying machine instance, constructed at `connectedCallback`.
|
|
749
915
|
* Exposed raw (not proxied) per the #639/#648 design decision so that
|
|
@@ -798,6 +964,41 @@ class FslInstance extends LitElement {
|
|
|
798
964
|
* DOM listeners installed by `<jssm-action>` / `data-jssm-action` discovery.
|
|
799
965
|
*/
|
|
800
966
|
this._action_listeners = [];
|
|
967
|
+
/** Tracks the OS color scheme; null when `matchMedia` is unavailable. */
|
|
968
|
+
this._mql = null;
|
|
969
|
+
/** Re-apply on an OS color-scheme change — only relevant while in `system` mode. */
|
|
970
|
+
this._on_os_theme_change = () => { if (this.theme === 'system') {
|
|
971
|
+
this._applyTheme();
|
|
972
|
+
} };
|
|
973
|
+
/**
|
|
974
|
+
* Gutter pointer-down: begin a drag. Move/up are attached to the document so
|
|
975
|
+
* the drag survives the pointer leaving the thin gutter (no pointer-capture,
|
|
976
|
+
* which keeps the handlers testable in jsdom).
|
|
977
|
+
*/
|
|
978
|
+
this._onGutterDown = (e) => {
|
|
979
|
+
e.preventDefault();
|
|
980
|
+
document.addEventListener('pointermove', this._onGutterMove);
|
|
981
|
+
document.addEventListener('pointerup', this._onGutterUp);
|
|
982
|
+
};
|
|
983
|
+
/** Document pointer-move during a drag: recompute the split ratio. */
|
|
984
|
+
this._onGutterMove = (e) => {
|
|
985
|
+
const rect = this.renderRoot.querySelector('.workbench').getBoundingClientRect();
|
|
986
|
+
const mode = this._effectiveMode();
|
|
987
|
+
this._split = (mode === 'tb' || mode === 'bt')
|
|
988
|
+
? split_ratio(e.clientY, rect.top, rect.height)
|
|
989
|
+
: split_ratio(e.clientX, rect.left, rect.width);
|
|
990
|
+
this.requestUpdate();
|
|
991
|
+
};
|
|
992
|
+
/** Document pointer-up: end the drag and detach the document listeners. */
|
|
993
|
+
this._onGutterUp = () => {
|
|
994
|
+
document.removeEventListener('pointermove', this._onGutterMove);
|
|
995
|
+
document.removeEventListener('pointerup', this._onGutterUp);
|
|
996
|
+
};
|
|
997
|
+
/** Gutter double-click: reset the split to 50/50. */
|
|
998
|
+
this._onGutterReset = () => {
|
|
999
|
+
this._split = 50;
|
|
1000
|
+
this.requestUpdate();
|
|
1001
|
+
};
|
|
801
1002
|
}
|
|
802
1003
|
/**
|
|
803
1004
|
* Raw machine accessor. Returns the owned {@link Machine} instance.
|
|
@@ -826,6 +1027,36 @@ class FslInstance extends LitElement {
|
|
|
826
1027
|
this.requestUpdate();
|
|
827
1028
|
return result;
|
|
828
1029
|
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Convenience wrapper for `machine.transition(state, data)` — moves directly
|
|
1032
|
+
* to a state along a legal (non-forced) edge. Reflects the new state and
|
|
1033
|
+
* requests an update, exactly as {@link FslInstance.do} does for actions.
|
|
1034
|
+
*
|
|
1035
|
+
* @param state - The destination state.
|
|
1036
|
+
* @param data - Optional data payload.
|
|
1037
|
+
* @returns `true` if the transition succeeded (a legal edge existed).
|
|
1038
|
+
*/
|
|
1039
|
+
transition(state, data) {
|
|
1040
|
+
const result = this.machine.transition(state, data);
|
|
1041
|
+
this._paint_state_reflection();
|
|
1042
|
+
this.requestUpdate();
|
|
1043
|
+
return result;
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Convenience wrapper for `machine.force_transition(state, data)` — moves to a
|
|
1047
|
+
* state along any edge, including forced-only ones. Reflects the new state and
|
|
1048
|
+
* requests an update.
|
|
1049
|
+
*
|
|
1050
|
+
* @param state - The destination state.
|
|
1051
|
+
* @param data - Optional data payload.
|
|
1052
|
+
* @returns `true` if the forced transition succeeded (any edge existed).
|
|
1053
|
+
*/
|
|
1054
|
+
force_transition(state, data) {
|
|
1055
|
+
const result = this.machine.force_transition(state, data);
|
|
1056
|
+
this._paint_state_reflection();
|
|
1057
|
+
this.requestUpdate();
|
|
1058
|
+
return result;
|
|
1059
|
+
}
|
|
829
1060
|
/**
|
|
830
1061
|
* Convenience wrapper for `machine.state()`. Returns the current
|
|
831
1062
|
* state's name.
|
|
@@ -833,6 +1064,101 @@ class FslInstance extends LitElement {
|
|
|
833
1064
|
state() {
|
|
834
1065
|
return String(this.machine.state());
|
|
835
1066
|
}
|
|
1067
|
+
/** Whether the OS currently prefers a dark color scheme. */
|
|
1068
|
+
_prefers_dark() {
|
|
1069
|
+
return typeof window.matchMedia === 'function'
|
|
1070
|
+
&& window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Resolve `theme` (mode) × `themeName` to a palette and apply it: write the
|
|
1074
|
+
* `--fsl-color-*` tokens inline (overriding the CSS fallback and cascading to
|
|
1075
|
+
* every slotted widget), reflect the chosen variant to `resolved-theme`, and
|
|
1076
|
+
* drive each slotted editor's light/dark CodeMirror theme.
|
|
1077
|
+
*/
|
|
1078
|
+
_applyTheme() {
|
|
1079
|
+
const variant = resolve_theme_mode(this.theme, this._prefers_dark());
|
|
1080
|
+
this.setAttribute('resolved-theme', variant);
|
|
1081
|
+
for (const [prop, value] of palette_to_vars(resolve_palette(this.themes, this.themeName, variant))) {
|
|
1082
|
+
this.style.setProperty(prop, value);
|
|
1083
|
+
}
|
|
1084
|
+
for (const editor of this.querySelectorAll('[slot="editor"]')) {
|
|
1085
|
+
editor.theme = variant;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
/** Resolve the active layout to a concrete mode (`'auto'` → a direction). */
|
|
1089
|
+
_effectiveMode() {
|
|
1090
|
+
return this.layout === 'auto' ? this._autoMode : this.layout;
|
|
1091
|
+
}
|
|
1092
|
+
/** Tab strip for the `tabs` layout. */
|
|
1093
|
+
_renderTabbar() {
|
|
1094
|
+
return html `
|
|
1095
|
+
<div class="tabbar" role="tablist" aria-label="Pane">
|
|
1096
|
+
<button type="button" role="tab" aria-selected=${this._tab === 'viz'} @click=${() => this._setTab('viz')}>Graph</button>
|
|
1097
|
+
<button type="button" role="tab" aria-selected=${this._tab === 'editor'} @click=${() => this._setTab('editor')}>Code</button>
|
|
1098
|
+
</div>`;
|
|
1099
|
+
}
|
|
1100
|
+
/** Switch the visible pane in the `tabs` layout. */
|
|
1101
|
+
_setTab(tab) {
|
|
1102
|
+
this._tab = tab;
|
|
1103
|
+
this.requestUpdate();
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Whether the panel slotted under `slot` is currently hidden.
|
|
1107
|
+
*
|
|
1108
|
+
* @param slot - A panel slot name (e.g. `"viz"`, `"editor"`, `"history"`).
|
|
1109
|
+
* @returns `true` when the panel is hidden.
|
|
1110
|
+
*/
|
|
1111
|
+
isPanelHidden(slot) {
|
|
1112
|
+
return this._hiddenPanels.has(slot);
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
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.
|
|
1118
|
+
*
|
|
1119
|
+
* @param slot - A panel slot name (e.g. `"viz"`, `"editor"`, `"history"`).
|
|
1120
|
+
* @param hidden - `true` to hide, `false` to show.
|
|
1121
|
+
*/
|
|
1122
|
+
setPanelHidden(slot, hidden) {
|
|
1123
|
+
if (hidden) {
|
|
1124
|
+
this._hiddenPanels.add(slot);
|
|
1125
|
+
}
|
|
1126
|
+
else {
|
|
1127
|
+
this._hiddenPanels.delete(slot);
|
|
1128
|
+
}
|
|
1129
|
+
this.requestUpdate();
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Toggle the visibility of the panel slotted under `slot`.
|
|
1133
|
+
*
|
|
1134
|
+
* @param slot - A panel slot name (e.g. `"viz"`, `"editor"`, `"history"`).
|
|
1135
|
+
*/
|
|
1136
|
+
togglePanel(slot) {
|
|
1137
|
+
this.setPanelHidden(slot, !this._hiddenPanels.has(slot));
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Install or remove the window-resize listener that resolves `layout="auto"`
|
|
1141
|
+
* to a concrete direction. Called from {@link updated}; uses the viewport
|
|
1142
|
+
* aspect so it needs no layout measurement (works in jsdom).
|
|
1143
|
+
*/
|
|
1144
|
+
_syncAutoListener() {
|
|
1145
|
+
if (this.layout === 'auto' && this._autoListener === null) {
|
|
1146
|
+
const recompute = () => {
|
|
1147
|
+
const next = auto_mode(window.innerWidth, window.innerHeight);
|
|
1148
|
+
if (next !== this._autoMode) {
|
|
1149
|
+
this._autoMode = next;
|
|
1150
|
+
this.requestUpdate();
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
this._autoListener = recompute;
|
|
1154
|
+
window.addEventListener('resize', recompute);
|
|
1155
|
+
recompute();
|
|
1156
|
+
}
|
|
1157
|
+
else if (this.layout !== 'auto' && this._autoListener !== null) {
|
|
1158
|
+
window.removeEventListener('resize', this._autoListener);
|
|
1159
|
+
this._autoListener = null;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
836
1162
|
/**
|
|
837
1163
|
* Lifecycle hook. Resolves the FSL source, constructs the machine,
|
|
838
1164
|
* paints initial reflection, then defers shadow-DOM rendering to Lit.
|
|
@@ -854,7 +1180,7 @@ class FslInstance extends LitElement {
|
|
|
854
1180
|
// Step 2: construct the machine.
|
|
855
1181
|
// (The resolver guarantees `fsl` is a non-empty string when error is undefined.)
|
|
856
1182
|
const fsl_source = resolved.fsl;
|
|
857
|
-
this._machine =
|
|
1183
|
+
this._machine = this._build_machine(fsl_source);
|
|
858
1184
|
// Step 3: paint initial host attributes + CSS custom properties.
|
|
859
1185
|
this._paint_state_reflection();
|
|
860
1186
|
// Step 4: shadow DOM render is automatic via Lit; requesting an update
|
|
@@ -872,6 +1198,15 @@ class FslInstance extends LitElement {
|
|
|
872
1198
|
this._unsubs.push(...install_bindings(this, this._machine));
|
|
873
1199
|
// #640: <jssm-action> DOM event → machine action wiring.
|
|
874
1200
|
this._discover_jssm_actions();
|
|
1201
|
+
// Theme: register the palette tokens as animatable colors (once, globally, so
|
|
1202
|
+
// switches can ease), follow the OS while in `system` mode, then apply the
|
|
1203
|
+
// resolved palette.
|
|
1204
|
+
register_palette_properties();
|
|
1205
|
+
if (typeof window.matchMedia === 'function') {
|
|
1206
|
+
this._mql = window.matchMedia('(prefers-color-scheme: dark)');
|
|
1207
|
+
this._mql.addEventListener('change', this._on_os_theme_change);
|
|
1208
|
+
}
|
|
1209
|
+
this._applyTheme();
|
|
875
1210
|
}
|
|
876
1211
|
/**
|
|
877
1212
|
* Discover direct-child `<jssm-on>` elements and install their
|
|
@@ -980,9 +1315,75 @@ class FslInstance extends LitElement {
|
|
|
980
1315
|
*
|
|
981
1316
|
* @param changed - Lit's changed-property map (forwarded to super).
|
|
982
1317
|
*/
|
|
1318
|
+
/**
|
|
1319
|
+
* Lit lifecycle. After the initial connect, a change to the `fsl` property
|
|
1320
|
+
* (e.g. written back by a bound `<fsl-editor>`, #1387) rebuilds the machine.
|
|
1321
|
+
* The first update is skipped via `hasUpdated` — `connectedCallback` already
|
|
1322
|
+
* built the initial machine.
|
|
1323
|
+
*/
|
|
1324
|
+
willUpdate(changed) {
|
|
1325
|
+
if (this.hasUpdated && changed.has('fsl')) {
|
|
1326
|
+
this._rebuild_machine();
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Rebuild the owned machine from the current `fsl` property and re-install
|
|
1331
|
+
* every machine-scoped subscription against the new machine (#1387).
|
|
1332
|
+
*
|
|
1333
|
+
* Semantics:
|
|
1334
|
+
* - **Keep-last-good:** if the new FSL is empty or fails to parse/compile,
|
|
1335
|
+
* the current machine is left untouched (a bound editor's lint surfaces
|
|
1336
|
+
* the error); we never blank or throw on mid-edit invalid source.
|
|
1337
|
+
* - **Reset-to-start:** a successful rebuild is a fresh machine at its
|
|
1338
|
+
* start state — the structure changed, so preserving position is unsafe.
|
|
1339
|
+
* - **Re-subscribe:** mechanism-4 re-emission, `<fsl-hook>`, `<fsl-on>`,
|
|
1340
|
+
* and `<fsl-bind>` projections are torn down from the old machine and
|
|
1341
|
+
* re-installed on the new one. DOM action listeners (`<fsl-action>`)
|
|
1342
|
+
* persist untouched — they read `this.machine` live.
|
|
1343
|
+
*/
|
|
1344
|
+
/**
|
|
1345
|
+
* Build a machine from FSL source, seeding {@link data} when it is set.
|
|
1346
|
+
*
|
|
1347
|
+
* @param fsl_source - The FSL string to compile.
|
|
1348
|
+
* @returns The compiled machine.
|
|
1349
|
+
*/
|
|
1350
|
+
_build_machine(fsl_source) {
|
|
1351
|
+
return (this.data === undefined
|
|
1352
|
+
? sm `${fsl_source}`
|
|
1353
|
+
: from(fsl_source, { data: this.data }));
|
|
1354
|
+
}
|
|
1355
|
+
_rebuild_machine() {
|
|
1356
|
+
if (typeof this.fsl !== 'string' || this.fsl.trim().length === 0) {
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
let next;
|
|
1360
|
+
try {
|
|
1361
|
+
next = this._build_machine(this.fsl);
|
|
1362
|
+
}
|
|
1363
|
+
catch (_a) {
|
|
1364
|
+
return; // keep-last-good
|
|
1365
|
+
}
|
|
1366
|
+
// Tear down the OLD machine's subscriptions (shared with disconnect).
|
|
1367
|
+
this._unbind_machine_subscriptions();
|
|
1368
|
+
// Swap to the new machine and re-bind everything machine-scoped.
|
|
1369
|
+
this._machine = next;
|
|
1370
|
+
this._paint_state_reflection();
|
|
1371
|
+
this._install_event_reemission();
|
|
1372
|
+
this._install_declarative_hooks();
|
|
1373
|
+
this._install_jssm_on_children();
|
|
1374
|
+
this._unsubs.push(...install_bindings(this, next));
|
|
1375
|
+
this.requestUpdate();
|
|
1376
|
+
// Notify bound children (e.g. <fsl-viz>) that the machine object was
|
|
1377
|
+
// replaced, so they can re-subscribe and re-render against the new one.
|
|
1378
|
+
this.dispatchEvent(new CustomEvent('fsl-machine-rebuilt', { bubbles: true, composed: true }));
|
|
1379
|
+
}
|
|
983
1380
|
updated(changed) {
|
|
984
1381
|
super.updated(changed);
|
|
985
1382
|
this._flush_pending_dom_events();
|
|
1383
|
+
this._syncAutoListener();
|
|
1384
|
+
if (['theme', 'themeName', 'themes'].some(p => changed.has(p))) {
|
|
1385
|
+
this._applyTheme();
|
|
1386
|
+
}
|
|
986
1387
|
}
|
|
987
1388
|
/**
|
|
988
1389
|
* Dispatch and clear the queue of pending DOM events. The queue is
|
|
@@ -1008,21 +1409,20 @@ class FslInstance extends LitElement {
|
|
|
1008
1409
|
}
|
|
1009
1410
|
}
|
|
1010
1411
|
/**
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
1412
|
+
* Release every machine-scoped subscription installed against the current
|
|
1413
|
+
* machine: #639 mechanism-4 re-emission, #641 `<fsl-hook>` registrations,
|
|
1414
|
+
* #643 `<fsl-on>` subscriptions, and #645 `<fsl-bind>` projections. Shared by
|
|
1415
|
+
* {@link disconnectedCallback} (full teardown) and the live-rebuild path
|
|
1416
|
+
* ({@link _rebuild_machine}, #1387), which re-installs against the new machine.
|
|
1417
|
+
* DOM action listeners (`<fsl-action>`) are NOT touched here — they read
|
|
1418
|
+
* `this.machine` live and survive a rebuild.
|
|
1015
1419
|
*/
|
|
1016
|
-
|
|
1017
|
-
super.disconnectedCallback();
|
|
1018
|
-
// #639 mechanism 4: release host-level re-emission subscriptions and drop
|
|
1019
|
-
// any events that were queued but not yet flushed.
|
|
1420
|
+
_unbind_machine_subscriptions() {
|
|
1020
1421
|
for (const off of this._reemit_unsubscribes) {
|
|
1021
1422
|
off();
|
|
1022
1423
|
}
|
|
1023
1424
|
this._reemit_unsubscribes = [];
|
|
1024
1425
|
this._pending_dom_events = [];
|
|
1025
|
-
// #641: remove installed hooks.
|
|
1026
1426
|
if (this._machine !== undefined) {
|
|
1027
1427
|
const machine = this._machine;
|
|
1028
1428
|
for (const desc of this._installed_hooks) {
|
|
@@ -1030,7 +1430,6 @@ class FslInstance extends LitElement {
|
|
|
1030
1430
|
}
|
|
1031
1431
|
}
|
|
1032
1432
|
this._installed_hooks = [];
|
|
1033
|
-
// #643: release every subscription installed from a <jssm-on> child.
|
|
1034
1433
|
for (const off of this._on_unsubscribes) {
|
|
1035
1434
|
try {
|
|
1036
1435
|
off();
|
|
@@ -1038,16 +1437,38 @@ class FslInstance extends LitElement {
|
|
|
1038
1437
|
catch ( /* swallow — cleanup must not throw past us */_a) { /* swallow — cleanup must not throw past us */ }
|
|
1039
1438
|
}
|
|
1040
1439
|
this._on_unsubscribes = [];
|
|
1041
|
-
// #645: tear down every live binding.
|
|
1042
1440
|
for (const off of this._unsubs) {
|
|
1043
1441
|
off();
|
|
1044
1442
|
}
|
|
1045
1443
|
this._unsubs = [];
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Lifecycle hook. Cleans up everything the WC installed at connect: hook
|
|
1447
|
+
* registrations from `<jssm-hook>`, event subscriptions from `<jssm-on>`,
|
|
1448
|
+
* mechanism-4 re-emission subscriptions, and DOM listeners from
|
|
1449
|
+
* `<jssm-action>` / `data-jssm-action`.
|
|
1450
|
+
*/
|
|
1451
|
+
disconnectedCallback() {
|
|
1452
|
+
super.disconnectedCallback();
|
|
1453
|
+
// Release every machine-scoped subscription (#639 re-emission, #641 hooks,
|
|
1454
|
+
// #643 <fsl-on>, #645 <fsl-bind>). Shared with the live-rebuild path (#1387).
|
|
1455
|
+
this._unbind_machine_subscriptions();
|
|
1046
1456
|
// #640: remove DOM listeners installed via <jssm-action> / data-jssm-action.
|
|
1047
1457
|
for (const entry of this._action_listeners) {
|
|
1048
1458
|
entry.target.removeEventListener(entry.event, entry.handler);
|
|
1049
1459
|
}
|
|
1050
1460
|
this._action_listeners = [];
|
|
1461
|
+
// Detach any in-flight gutter-drag document listeners.
|
|
1462
|
+
this._onGutterUp();
|
|
1463
|
+
if (this._autoListener !== null) {
|
|
1464
|
+
window.removeEventListener('resize', this._autoListener);
|
|
1465
|
+
this._autoListener = null;
|
|
1466
|
+
}
|
|
1467
|
+
// Stop following the OS color scheme.
|
|
1468
|
+
if (this._mql !== null) {
|
|
1469
|
+
this._mql.removeEventListener('change', this._on_os_theme_change);
|
|
1470
|
+
this._mql = null;
|
|
1471
|
+
}
|
|
1051
1472
|
}
|
|
1052
1473
|
/**
|
|
1053
1474
|
* Wire DOM events to machine actions, using the two declarative forms from
|
|
@@ -1157,58 +1578,99 @@ class FslInstance extends LitElement {
|
|
|
1157
1578
|
const state_slot_name = this._machine === undefined
|
|
1158
1579
|
? 'state-unknown'
|
|
1159
1580
|
: `state-${String(this._machine.state())}`;
|
|
1581
|
+
const header = html `
|
|
1582
|
+
<header>
|
|
1583
|
+
<slot name="title"><span class="placeholder">fsl-instance</span></slot>
|
|
1584
|
+
</header>`;
|
|
1585
|
+
const viz = html `<slot name="viz"><span class="placeholder">no viz configured</span></slot>`;
|
|
1586
|
+
const editor = html `<slot name="editor"></slot>`;
|
|
1587
|
+
const toolbar = html `<section class="toolbar"><slot name="toolbar"></slot></section>`;
|
|
1588
|
+
if (this.layout !== '') {
|
|
1589
|
+
const mode = this._effectiveMode();
|
|
1590
|
+
return html `
|
|
1591
|
+
<div class="container is-split">
|
|
1592
|
+
${header}
|
|
1593
|
+
${toolbar}
|
|
1594
|
+
<div class="middle">
|
|
1595
|
+
<section class="dock actions-dock${this._hiddenPanels.has('actions') ? '' : ' open'}" part="actions-dock">
|
|
1596
|
+
<slot name="actions"></slot>
|
|
1597
|
+
</section>
|
|
1598
|
+
<div class="workbench${this._hiddenPanels.has('viz') ? ' hide-viz' : ''}${this._hiddenPanels.has('editor') ? ' hide-editor' : ''}"
|
|
1599
|
+
data-mode=${mode} style="--fsl-split:${this._split}%">
|
|
1600
|
+
${mode === 'tabs' ? this._renderTabbar() : ''}
|
|
1601
|
+
<section class="pane viz" ?hidden=${mode === 'tabs' && this._tab !== 'viz'}>${viz}</section>
|
|
1602
|
+
<div class="gutter"
|
|
1603
|
+
@pointerdown=${this._onGutterDown}
|
|
1604
|
+
@dblclick=${this._onGutterReset}></div>
|
|
1605
|
+
<section class="pane editor" ?hidden=${mode === 'tabs' && this._tab !== 'editor'}>${editor}</section>
|
|
1606
|
+
</div>
|
|
1607
|
+
<section class="dock data-dock${this._hiddenPanels.has('data-inspector') ? '' : ' open'}" part="data-dock">
|
|
1608
|
+
<slot name="data-inspector"></slot>
|
|
1609
|
+
</section>
|
|
1610
|
+
</div>
|
|
1611
|
+
${this._renderAuxPanels(true)}
|
|
1612
|
+
<section class="state-section"><slot name=${state_slot_name}></slot></section>
|
|
1613
|
+
<footer><slot name="footer"></slot></footer>
|
|
1614
|
+
</div>
|
|
1615
|
+
`;
|
|
1616
|
+
}
|
|
1160
1617
|
return html `
|
|
1161
1618
|
<div class="container">
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
</
|
|
1165
|
-
<section class="
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
<
|
|
1169
|
-
<slot name="editor"></slot>
|
|
1170
|
-
</section>
|
|
1171
|
-
<section class="toolbar">
|
|
1172
|
-
<slot name="toolbar"></slot>
|
|
1173
|
-
</section>
|
|
1174
|
-
<section class="actions">
|
|
1175
|
-
<slot name="actions"></slot>
|
|
1176
|
-
</section>
|
|
1177
|
-
<section class="info-panel">
|
|
1178
|
-
<slot name="info-panel"></slot>
|
|
1179
|
-
</section>
|
|
1180
|
-
<section class="history">
|
|
1181
|
-
<slot name="history"></slot>
|
|
1182
|
-
</section>
|
|
1183
|
-
<section class="data-inspector">
|
|
1184
|
-
<slot name="data-inspector"></slot>
|
|
1185
|
-
</section>
|
|
1186
|
-
<section class="hook-log">
|
|
1187
|
-
<slot name="hook-log"></slot>
|
|
1188
|
-
</section>
|
|
1189
|
-
<section class="effective-properties">
|
|
1190
|
-
<slot name="effective-properties"></slot>
|
|
1191
|
-
</section>
|
|
1192
|
-
<section class="simulation">
|
|
1193
|
-
<slot name="simulation"></slot>
|
|
1194
|
-
</section>
|
|
1195
|
-
<section class="export">
|
|
1196
|
-
<slot name="export"></slot>
|
|
1197
|
-
</section>
|
|
1198
|
-
<section class="state-section">
|
|
1199
|
-
<slot name=${state_slot_name}></slot>
|
|
1200
|
-
</section>
|
|
1201
|
-
<footer>
|
|
1202
|
-
<slot name="footer"></slot>
|
|
1203
|
-
</footer>
|
|
1619
|
+
${header}
|
|
1620
|
+
${toolbar}
|
|
1621
|
+
<section class="viz" ?hidden=${this._hiddenPanels.has('viz')}>${viz}</section>
|
|
1622
|
+
<section class="editor" ?hidden=${this._hiddenPanels.has('editor')}>${editor}</section>
|
|
1623
|
+
${this._renderAuxPanels(false)}
|
|
1624
|
+
<section class="state-section"><slot name=${state_slot_name}></slot></section>
|
|
1625
|
+
<footer><slot name="footer"></slot></footer>
|
|
1204
1626
|
</div>
|
|
1205
1627
|
`;
|
|
1206
1628
|
}
|
|
1629
|
+
/** 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
|
|
1632
|
+
* `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.
|
|
1635
|
+
*
|
|
1636
|
+
* @param docked - True when actions + data-inspector are rendered as side
|
|
1637
|
+
* docks (split layouts); they are then omitted from this stack. */
|
|
1638
|
+
_renderAuxPanels(docked) {
|
|
1639
|
+
const h = (slot) => this._hiddenPanels.has(slot);
|
|
1640
|
+
return html `
|
|
1641
|
+
${docked ? '' : html `<section class="actions" ?hidden=${h('actions')}><slot name="actions"></slot></section>`}
|
|
1642
|
+
<section class="info-panel" ?hidden=${h('info-panel')}><slot name="info-panel"></slot></section>
|
|
1643
|
+
<section class="history" ?hidden=${h('history')}><slot name="history"></slot></section>
|
|
1644
|
+
${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>
|
|
1646
|
+
<section class="effective-properties" ?hidden=${h('effective-properties')}><slot name="effective-properties"></slot></section>
|
|
1647
|
+
<section class="simulation" ?hidden=${h('simulation')}><slot name="simulation"></slot></section>
|
|
1648
|
+
<section class="export" ?hidden=${h('export')}><slot name="export"></slot></section>
|
|
1649
|
+
`;
|
|
1650
|
+
}
|
|
1207
1651
|
}
|
|
1208
1652
|
FslInstance.styles = css `
|
|
1209
1653
|
:host {
|
|
1210
1654
|
display: block;
|
|
1655
|
+
/* Ease every palette token on a theme switch. The tokens are registered as
|
|
1656
|
+
animatable <color>s in JS (register_palette_properties) — @property in a
|
|
1657
|
+
shadow stylesheet does not register globally. Because the host transitions
|
|
1658
|
+
its own --fsl-color-*, every widget's var(--fsl-color-*) re-resolves to the
|
|
1659
|
+
animating value each frame, so the whole suite cross-fades. */
|
|
1660
|
+
transition:
|
|
1661
|
+
--fsl-color-surface 0.28s ease, --fsl-color-text 0.28s ease, --fsl-color-accent 0.28s ease,
|
|
1662
|
+
--fsl-color-border 0.28s ease, --fsl-color-muted 0.28s ease, --fsl-color-json-key 0.28s ease,
|
|
1663
|
+
--fsl-color-json-string 0.28s ease, --fsl-color-json-number 0.28s ease, --fsl-color-json-atom 0.28s ease;
|
|
1664
|
+
/* Pre-JS fallback palette (the Default theme's light variant). At runtime
|
|
1665
|
+
the host writes the resolved theme's --fsl-color-* tokens as inline
|
|
1666
|
+
style (see _applyTheme), which override these and cascade to every
|
|
1667
|
+
slotted widget. A consumer's own --fsl-color-* still wins over both. */
|
|
1668
|
+
--fsl-color-surface: #ffffff; --fsl-color-text: #222222; --fsl-color-accent: #5b9dff;
|
|
1669
|
+
--fsl-color-border: #e5e5e5; --fsl-color-muted: #9aa0a6;
|
|
1670
|
+
--fsl-color-json-key: #5b3da8; --fsl-color-json-string: #2e7d32;
|
|
1671
|
+
--fsl-color-json-number: #b8860b; --fsl-color-json-atom: #c2185b;
|
|
1211
1672
|
}
|
|
1673
|
+
@media (prefers-reduced-motion: reduce) { :host { transition: none; } }
|
|
1212
1674
|
.container {
|
|
1213
1675
|
width: 100%;
|
|
1214
1676
|
height: 100%;
|
|
@@ -1217,6 +1679,72 @@ FslInstance.styles = css `
|
|
|
1217
1679
|
opacity: 0.6;
|
|
1218
1680
|
font-style: italic;
|
|
1219
1681
|
}
|
|
1682
|
+
|
|
1683
|
+
/* layout modes: lr/rl (row) · tb/bt (column) · editor/viewer (single) · tabs. */
|
|
1684
|
+
.container.is-split { display: flex; flex-direction: column; }
|
|
1685
|
+
/* the middle band: actions dock | workbench | data dock */
|
|
1686
|
+
.middle { display: flex; flex: 1 1 auto; min-height: 0; }
|
|
1687
|
+
.workbench { display: flex; flex: 1 1 auto; min-height: 0; min-width: 0; }
|
|
1688
|
+
/* side docks ease their width like the help drawer; closed = 0, open = a
|
|
1689
|
+
fixed basis. overflow:hidden clips the content during the slide. */
|
|
1690
|
+
.dock {
|
|
1691
|
+
flex: 0 0 0; min-width: 0; overflow: hidden; box-sizing: border-box;
|
|
1692
|
+
background: var(--fsl-color-surface, #fff);
|
|
1693
|
+
transition: flex-basis 0.28s ease;
|
|
1694
|
+
}
|
|
1695
|
+
.dock.open { flex-basis: var(--fsl-dock-width, 17em); }
|
|
1696
|
+
.actions-dock.open { border-right: 1px solid var(--fsl-color-border, #e5e5e5); }
|
|
1697
|
+
.data-dock.open { border-left: 1px solid var(--fsl-color-border, #e5e5e5); }
|
|
1698
|
+
/* a docked panel fills the band's height; the data inspector then scrolls
|
|
1699
|
+
to the dock instead of its own 16em cap. */
|
|
1700
|
+
.dock ::slotted(*) { height: 100%; box-sizing: border-box; }
|
|
1701
|
+
.data-dock { --fsl-data-inspector-max-height: 100%; }
|
|
1702
|
+
@media (prefers-reduced-motion: reduce) { .dock { transition: none; } }
|
|
1703
|
+
.workbench[data-mode="tb"], .workbench[data-mode="bt"], .workbench[data-mode="tabs"] { flex-direction: column; }
|
|
1704
|
+
.workbench .pane { display: flex; min-width: 0; min-height: 0; overflow: hidden; }
|
|
1705
|
+
.workbench .pane ::slotted(*) { flex: 1; min-width: 0; min-height: 0; }
|
|
1706
|
+
|
|
1707
|
+
/* pane order (which pane comes first along the main axis) */
|
|
1708
|
+
.workbench[data-mode="lr"] .editor { order: 0; } .workbench[data-mode="lr"] .gutter { order: 1; } .workbench[data-mode="lr"] .viz { order: 2; }
|
|
1709
|
+
.workbench[data-mode="rl"] .viz { order: 0; } .workbench[data-mode="rl"] .gutter { order: 1; } .workbench[data-mode="rl"] .editor { order: 2; }
|
|
1710
|
+
.workbench[data-mode="tb"] .editor { order: 0; } .workbench[data-mode="tb"] .gutter { order: 1; } .workbench[data-mode="tb"] .viz { order: 2; }
|
|
1711
|
+
.workbench[data-mode="bt"] .viz { order: 0; } .workbench[data-mode="bt"] .gutter { order: 1; } .workbench[data-mode="bt"] .editor { order: 2; }
|
|
1712
|
+
|
|
1713
|
+
/* the first pane carries the split ratio; the other fills */
|
|
1714
|
+
.workbench[data-mode="lr"] .editor, .workbench[data-mode="tb"] .editor,
|
|
1715
|
+
.workbench[data-mode="rl"] .viz, .workbench[data-mode="bt"] .viz { flex: 0 0 var(--fsl-split, 50%); }
|
|
1716
|
+
.workbench[data-mode="lr"] .viz, .workbench[data-mode="tb"] .viz,
|
|
1717
|
+
.workbench[data-mode="rl"] .editor, .workbench[data-mode="bt"] .editor { flex: 1 1 0; }
|
|
1718
|
+
|
|
1719
|
+
/* gutter */
|
|
1720
|
+
.gutter {
|
|
1721
|
+
flex: 0 0 6px; align-self: stretch; cursor: col-resize;
|
|
1722
|
+
background: rgba(0, 0, 0, 0.18); touch-action: none;
|
|
1723
|
+
}
|
|
1724
|
+
.workbench[data-mode="tb"] .gutter, .workbench[data-mode="bt"] .gutter { cursor: row-resize; }
|
|
1725
|
+
|
|
1726
|
+
/* single-pane modes hide the gutter and the other pane */
|
|
1727
|
+
.workbench[data-mode="editor"] .gutter, .workbench[data-mode="editor"] .viz { display: none; }
|
|
1728
|
+
.workbench[data-mode="viewer"] .gutter, .workbench[data-mode="viewer"] .editor { display: none; }
|
|
1729
|
+
.workbench[data-mode="editor"] .editor, .workbench[data-mode="viewer"] .viz { flex: 1 1 0; }
|
|
1730
|
+
|
|
1731
|
+
/* panel-hidden workbench panes (driven by <fsl-toolbar> toggles): drop the
|
|
1732
|
+
hidden pane + the gutter and let the surviving pane fill. */
|
|
1733
|
+
.workbench.hide-viz .viz, .workbench.hide-viz .gutter,
|
|
1734
|
+
.workbench.hide-editor .editor, .workbench.hide-editor .gutter { display: none; }
|
|
1735
|
+
.workbench.hide-viz .editor, .workbench.hide-editor .viz { flex: 1 1 0; }
|
|
1736
|
+
|
|
1737
|
+
/* tabbed: a tab strip + one pane at a time */
|
|
1738
|
+
.workbench[data-mode="tabs"] .gutter { display: none; }
|
|
1739
|
+
.workbench[data-mode="tabs"] .pane { flex: 1 1 0; }
|
|
1740
|
+
.workbench[data-mode="tabs"] .pane[hidden] { display: none; }
|
|
1741
|
+
.tabbar { display: flex; gap: 3px; padding: 5px 6px 0; flex: 0 0 auto; border-bottom: 1px solid rgba(127,127,127,0.25); }
|
|
1742
|
+
.tabbar button {
|
|
1743
|
+
font: inherit; font-size: 12px; padding: 5px 14px; cursor: pointer; color: inherit;
|
|
1744
|
+
background: rgba(127,127,127,0.12); border: 1px solid rgba(127,127,127,0.25);
|
|
1745
|
+
border-bottom: none; border-radius: 6px 6px 0 0;
|
|
1746
|
+
}
|
|
1747
|
+
.tabbar button[aria-selected="true"] { background: var(--_fsl-surface, #fff); font-weight: 600; }
|
|
1220
1748
|
`;
|
|
1221
1749
|
/**
|
|
1222
1750
|
* Library event names this WC re-emits as DOM `CustomEvent`s, fulfilling
|
|
@@ -1241,6 +1769,11 @@ FslInstance.REEMITTED_EVENTS = [
|
|
|
1241
1769
|
*/
|
|
1242
1770
|
FslInstance.properties = {
|
|
1243
1771
|
fsl: { type: String, reflect: false },
|
|
1772
|
+
layout: { type: String, reflect: true },
|
|
1773
|
+
theme: { type: String, reflect: true },
|
|
1774
|
+
themeName: { type: String, attribute: 'theme-name', reflect: true },
|
|
1775
|
+
themes: { type: Object, reflect: false },
|
|
1776
|
+
data: { type: Object, reflect: false },
|
|
1244
1777
|
};
|
|
1245
1778
|
|
|
1246
|
-
export { FslInstance, JSSM_ON_EVENT_NAMES, compile_inline_body, jssm_handler_registry, parse_jssm_on_element, resolve_fsl_source, resolve_named_handler };
|
|
1779
|
+
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 };
|