@weasel-js/forge 1.10.0 → 1.11.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 +1 -3
- package/dist/{FrameController-UeTY7ubF.d.ts → FrameController-BRhShqut.d.ts} +5 -0
- package/dist/{chunk-FL2MUPUL.js → chunk-2OJAV522.js} +2 -2
- package/dist/chunk-2OJAV522.js.map +1 -0
- package/dist/{chunk-DFKCLZKS.js → chunk-7BQPHMA2.js} +2 -2
- package/dist/chunk-7BQPHMA2.js.map +1 -0
- package/dist/{chunk-DKWHGSBZ.js → chunk-DPTECNYC.js} +3 -3
- package/dist/{chunk-DKWHGSBZ.js.map → chunk-DPTECNYC.js.map} +1 -1
- package/dist/{chunk-ENXU34PI.js → chunk-J3GYSNCK.js} +4 -4
- package/dist/{chunk-ENXU34PI.js.map → chunk-J3GYSNCK.js.map} +1 -1
- package/dist/{chunk-4NM3RTYK.js → chunk-NVNTQ4AT.js} +89 -11
- package/dist/chunk-NVNTQ4AT.js.map +1 -0
- package/dist/{chunk-V4RI7GWD.js → chunk-R5F44ECM.js} +17 -7
- package/dist/chunk-R5F44ECM.js.map +1 -0
- package/dist/{chunk-GB6PRJET.js → chunk-SQ4KCIEH.js} +3 -3
- package/dist/{chunk-GB6PRJET.js.map → chunk-SQ4KCIEH.js.map} +1 -1
- package/dist/{config-Cz7ZyGF-.d.ts → config-BwScQukB.d.ts} +5 -4
- package/dist/csf/preview-api.js +2 -2
- package/dist/frame/index.d.ts +3 -2
- package/dist/frame/index.js +7 -8
- package/dist/frame/index.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3 -3
- package/dist/shell/index.css +8 -0
- package/dist/shell/index.css.map +1 -1
- package/dist/shell/index.d.ts +4 -3
- package/dist/shell/index.js +278 -128
- package/dist/shell/index.js.map +1 -1
- package/dist/test/index.d.ts +2 -1
- package/dist/test/index.js +5 -5
- package/dist/vite/index.d.ts +13 -1
- package/dist/vite/index.js +11 -2
- package/dist/vite/index.js.map +1 -1
- package/package.json +8 -6
- package/dist/chunk-4NM3RTYK.js.map +0 -1
- package/dist/chunk-DFKCLZKS.js.map +0 -1
- package/dist/chunk-FL2MUPUL.js.map +0 -1
- package/dist/chunk-V4RI7GWD.js.map +0 -1
package/dist/shell/index.js
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
import { parseRoute, formatRoute, paramsToKnobs, knobPaths, sameKnob, knobsToParams, PLAYHEAD_PARAM, reservedParams } from '../chunk-
|
|
1
|
+
import { parseRoute, formatRoute, paramsToKnobs, knobPaths, sameKnob, knobsToParams, PLAYHEAD_PARAM, reservedParams } from '../chunk-DPTECNYC.js';
|
|
2
2
|
import { DEFAULT_TITLE } from '../chunk-5DXUNLV2.js';
|
|
3
|
-
import { TITLE_LIBRARIES, isIndexId, indexId, indexEntries, librariesFrom, LibrariesContext, schemaFromDescription, LibraryBadge, openChannel, IndexPage } from '../chunk-
|
|
3
|
+
import { TITLE_LIBRARIES, isIndexId, indexId, indexEntries, librariesFrom, LibrariesContext, createKitRoot, schemaFromDescription, LibraryBadge, openChannel, IndexPage } from '../chunk-R5F44ECM.js';
|
|
4
4
|
import { isGallery } from '../chunk-WEQSLPKT.js';
|
|
5
|
-
import { useTrialFrame, createTrialFrames, createTrialClocks, StoryGlobalsContext, TrialFramesContext, TrialClocksContext, StoryTrial, useCssOverrides, useTrialClock, TrialHost } from '../chunk-
|
|
6
|
-
export { StoryGlobalsContext, StoryTrial, TrialFramesContext, TrialHost, useTrialFrame } from '../chunk-
|
|
7
|
-
import { labGlobals, withGlobals, followApp, storyConfig, GLOBALS_KEY, effectiveGlobals, PLAYHEAD_KEY, labOptions, isGlobalsPath } from '../chunk-
|
|
8
|
-
import { loadStories, indexRenderOf, useNearViewport, IN_VIEW_MARGIN } from '../chunk-
|
|
5
|
+
import { useTrialFrame, createTrialFrames, createTrialClocks, StoryGlobalsContext, TrialFramesContext, TrialClocksContext, StoryTrial, useCssOverrides, useTrialClock, TrialHost } from '../chunk-J3GYSNCK.js';
|
|
6
|
+
export { StoryGlobalsContext, StoryTrial, TrialFramesContext, TrialHost, useTrialFrame } from '../chunk-J3GYSNCK.js';
|
|
7
|
+
import { labGlobals, withGlobals, followApp, storyConfig, GLOBALS_KEY, effectiveGlobals, PLAYHEAD_KEY, labOptions, isGlobalsPath } from '../chunk-7BQPHMA2.js';
|
|
8
|
+
import { loadStories, indexRenderOf, useNearViewport, IN_VIEW_MARGIN } from '../chunk-NVNTQ4AT.js';
|
|
9
9
|
import '../chunk-L5XM5VKB.js';
|
|
10
10
|
import '../chunk-ZAWXMUGZ.js';
|
|
11
|
-
import '../chunk-
|
|
12
|
-
import { stableStringify, PORT_HANDOFF, FRAME_HELLO, PROTOCOL_VERSION } from '../chunk-
|
|
13
|
-
import { useTrialId, Button, Lab, useLabContext, TrialIdContext, usePersistedState, Select, useTrialState,
|
|
11
|
+
import '../chunk-SQ4KCIEH.js';
|
|
12
|
+
import { stableStringify, PORT_HANDOFF, FRAME_HELLO, PROTOCOL_VERSION } from '../chunk-2OJAV522.js';
|
|
13
|
+
import { useTrialId, Button, Lab, useLabContext, TrialIdContext, usePersistedState, Select, useTrialState, ToggleBar as ToggleBar$1, Input, TokenPanel, inferTokenType, refitScale } from '@weasel-js/labkit';
|
|
14
14
|
import num from '@weasel-js/theme/numeric.module.css';
|
|
15
15
|
import { createContext, useState, useRef, useCallback, useEffect, useMemo, useLayoutEffect, useId, useContext, Fragment as Fragment$1, useSyncExternalStore } from 'react';
|
|
16
16
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
17
|
-
import { createRoot } from 'react-dom/client';
|
|
18
17
|
import { createEmitter } from '@weasel-js/registry';
|
|
19
18
|
import { f, valueAtPath, isConfigBranch } from '@weasel-js/labkit/config';
|
|
20
|
-
import {
|
|
21
|
-
import { TOKEN_MANIFEST, THEME_SOURCES, isByAxis } from '@weasel-js/theme';
|
|
19
|
+
import { formatShortcut, ToggleBar, Checkbox, Dialog, TOKEN_CATEGORIES, Icon, DisclosureMark, Button as Button$1, Callout, Slider, Transport } from '@weasel-js/ui';
|
|
20
|
+
import { TOKEN_MANIFEST, themeAxes, weaselTheme, THEME_SOURCES, resolveTheme, isColorModePreference, isByAxis } from '@weasel-js/theme';
|
|
22
21
|
import { scale, httpThemeApi } from '@weasel-js/theme/engine';
|
|
23
22
|
import { useLatest, historyKey, createHistory, swapOp, isPlainObject } from '@weasel-js/core';
|
|
23
|
+
import { useResolvedColorMode } from '@weasel-js/theme/react';
|
|
24
|
+
import { pref } from '@weasel-js/prefs';
|
|
24
25
|
|
|
25
26
|
function Finding({ finding }) {
|
|
26
27
|
return /* @__PURE__ */ jsxs("li", { className: "fg-a11y__finding", children: [
|
|
@@ -339,12 +340,30 @@ function missingInstrument(id, globals = {}) {
|
|
|
339
340
|
};
|
|
340
341
|
}
|
|
341
342
|
var ICONS = {
|
|
342
|
-
color:
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
343
|
+
color: (
|
|
344
|
+
/* @icon */
|
|
345
|
+
"ui:drop"
|
|
346
|
+
),
|
|
347
|
+
type: (
|
|
348
|
+
/* @icon */
|
|
349
|
+
"ui:type"
|
|
350
|
+
),
|
|
351
|
+
size: (
|
|
352
|
+
/* @icon */
|
|
353
|
+
"core:measure"
|
|
354
|
+
),
|
|
355
|
+
motion: (
|
|
356
|
+
/* @icon */
|
|
357
|
+
"ui:curveEaseInOut"
|
|
358
|
+
),
|
|
359
|
+
depth: (
|
|
360
|
+
/* @icon */
|
|
361
|
+
"ui:layers"
|
|
362
|
+
),
|
|
363
|
+
other: (
|
|
364
|
+
/* @icon */
|
|
365
|
+
"ui:more"
|
|
366
|
+
)
|
|
348
367
|
};
|
|
349
368
|
var PANEL = ".fg-css-vars";
|
|
350
369
|
var HOST = ".lk-sidebar-section, .lk-panel-tile";
|
|
@@ -553,15 +572,24 @@ function applyTokenEdits(definition, edits, selection) {
|
|
|
553
572
|
}
|
|
554
573
|
return { ok: true, definition: root };
|
|
555
574
|
}
|
|
575
|
+
function useTrialSelection(axes, globals) {
|
|
576
|
+
const { mode } = globals;
|
|
577
|
+
const osMode = useResolvedColorMode(isColorModePreference(mode) ? mode : "auto");
|
|
578
|
+
return useMemo(() => {
|
|
579
|
+
const selection = selectionFor(axes, globals);
|
|
580
|
+
return axes.mode && osMode in axes.mode.values ? { mode: osMode, ...selection } : selection;
|
|
581
|
+
}, [axes, globals, osMode]);
|
|
582
|
+
}
|
|
556
583
|
|
|
557
584
|
// src/shell/cssVars/useThemeSave.ts
|
|
558
585
|
var THEME_NAME = "weasel";
|
|
559
586
|
var TOKEN_PREFIX = "--wzl-";
|
|
587
|
+
var NO_AXES = {};
|
|
560
588
|
function useThemeSave({ trialId, globals, themeApi, scales, overrides, history: history2 }) {
|
|
589
|
+
const definition = THEME_SOURCES[THEME_NAME];
|
|
590
|
+
const selection = useTrialSelection(definition?.axes ?? NO_AXES, globals);
|
|
561
591
|
const tokens = useMemo(() => {
|
|
562
|
-
const definition = THEME_SOURCES[THEME_NAME];
|
|
563
592
|
if (!definition) return {};
|
|
564
|
-
const selection = selectionFor(definition.axes ?? {}, globals);
|
|
565
593
|
const generated = new Set(scales.flatMap((scale) => scale.tokens));
|
|
566
594
|
return Object.fromEntries(
|
|
567
595
|
Object.entries(overrides).flatMap(([name, value]) => {
|
|
@@ -570,7 +598,7 @@ function useThemeSave({ trialId, globals, themeApi, scales, overrides, history:
|
|
|
570
598
|
return writtenOut(definition, token, selection) ? [[token, value]] : [];
|
|
571
599
|
})
|
|
572
600
|
);
|
|
573
|
-
}, [
|
|
601
|
+
}, [definition, selection, scales, overrides]);
|
|
574
602
|
const [saving, setSaving] = useState(false);
|
|
575
603
|
const [held, setHeld] = useState(null);
|
|
576
604
|
const setNotice = (next) => setHeld(next && { ...next, trialId });
|
|
@@ -585,7 +613,6 @@ function useThemeSave({ trialId, globals, themeApi, scales, overrides, history:
|
|
|
585
613
|
setNotice({ tone: "error", text: `${file} has changed since this page loaded. Reload to edit against it.` });
|
|
586
614
|
return;
|
|
587
615
|
}
|
|
588
|
-
const selection = selectionFor(stored.definition.axes ?? {}, globals);
|
|
589
616
|
const scaled = applyScaleEdits(stored.definition, Object.fromEntries(scales.map((s) => [s.name, s.edit])), selection);
|
|
590
617
|
const next = scaled.ok ? applyTokenEdits(scaled.definition, tokens, selection) : scaled;
|
|
591
618
|
if (!next.ok) {
|
|
@@ -627,6 +654,39 @@ function useThemeSave({ trialId, globals, themeApi, scales, overrides, history:
|
|
|
627
654
|
save
|
|
628
655
|
};
|
|
629
656
|
}
|
|
657
|
+
var MANIFEST = new Map(TOKEN_MANIFEST.map((token) => [token.name, token]));
|
|
658
|
+
function groupOf(name) {
|
|
659
|
+
const segments = name.replace(/^--/, "").split("-");
|
|
660
|
+
return segments.length > 1 ? segments.slice(0, -1).join("-") : name.replace(/^--/, "");
|
|
661
|
+
}
|
|
662
|
+
function useStoryTokens(vars) {
|
|
663
|
+
const [showUnused, setShowUnused] = usePersistedState("fg-css-vars-unused", false, { scope: "lab" });
|
|
664
|
+
const tokens = useMemo(
|
|
665
|
+
() => vars.flatMap(({ name, value, used }) => {
|
|
666
|
+
if (!used && !showUnused) return [];
|
|
667
|
+
const known = MANIFEST.get(name);
|
|
668
|
+
return [{ name, type: known?.type ?? inferTokenType(value), group: known?.group ?? groupOf(name), value }];
|
|
669
|
+
}),
|
|
670
|
+
[vars, showUnused]
|
|
671
|
+
);
|
|
672
|
+
const emptyText = vars.length > 0 && !showUnused ? "The story uses no variables." : "The story\u2019s frame has reported no variables.";
|
|
673
|
+
return { tokens, showUnused, setShowUnused, emptyText };
|
|
674
|
+
}
|
|
675
|
+
var AXES = themeAxes(weaselTheme);
|
|
676
|
+
function useThemeTokens(globals, vars) {
|
|
677
|
+
const selection = useTrialSelection(AXES, globals);
|
|
678
|
+
const themed = useMemo(() => resolveTheme(weaselTheme, selection), [selection]);
|
|
679
|
+
return useMemo(() => {
|
|
680
|
+
const reported = new Map(vars.map((v) => [v.name, v.value]));
|
|
681
|
+
return TOKEN_MANIFEST.map((token) => ({
|
|
682
|
+
name: token.name,
|
|
683
|
+
type: token.type,
|
|
684
|
+
group: token.group,
|
|
685
|
+
value: reported.get(token.name) ?? themed[token.name] ?? token.defaultValue,
|
|
686
|
+
description: token.description
|
|
687
|
+
}));
|
|
688
|
+
}, [vars, themed]);
|
|
689
|
+
}
|
|
630
690
|
var SWAP = "fg.vars.swap";
|
|
631
691
|
var COALESCE_MS = 800;
|
|
632
692
|
var LIMIT = 100;
|
|
@@ -690,13 +750,13 @@ var TABS = [
|
|
|
690
750
|
{ value: "theme", label: "Theme" },
|
|
691
751
|
{ value: "story", label: "Story" }
|
|
692
752
|
];
|
|
693
|
-
var
|
|
753
|
+
var MANIFEST2 = new Map(TOKEN_MANIFEST.map((token) => [token.name, token]));
|
|
694
754
|
var literal = (v) => typeof v === "number";
|
|
695
755
|
var SCALE_SOURCES = Object.entries(THEME_SOURCES[THEME_NAME]?.scales ?? {}).flatMap(([name, def]) => {
|
|
696
756
|
if (!Array.isArray(def.steps)) return [];
|
|
697
757
|
const steps = def.steps;
|
|
698
758
|
const tokens = steps.map((step) => `${TOKEN_PREFIX}${name}-${step}`);
|
|
699
|
-
const group =
|
|
759
|
+
const group = MANIFEST2.get(tokens[0] ?? "")?.group;
|
|
700
760
|
if (!group) return [];
|
|
701
761
|
const factors = Array.isArray(def.factors) && def.factors.every(literal) ? def.factors : null;
|
|
702
762
|
const rule = def.factors !== void 0 ? factors ? { kind: "factors", factors } : "factors" : def.ratio !== void 0 ? literal(def.ratio) ? { kind: "ratio", ratio: def.ratio } : "ratio" : literal(def.step) ? { kind: "step", step: def.step } : "step";
|
|
@@ -726,10 +786,6 @@ function standingScale(source, amounts) {
|
|
|
726
786
|
const base = (amounts[at] ?? 0) / (rule.factors[at] ?? 1);
|
|
727
787
|
return { ...seed, base: Math.round(base * 100) / 100, rule };
|
|
728
788
|
}
|
|
729
|
-
function groupOf(name) {
|
|
730
|
-
const segments = name.replace(/^--/, "").split("-");
|
|
731
|
-
return segments.length > 1 ? segments.slice(0, -1).join("-") : name.replace(/^--/, "");
|
|
732
|
-
}
|
|
733
789
|
var NO_GLOBALS = {};
|
|
734
790
|
function CssVarsPanel({ globals = NO_GLOBALS, themeApi } = {}) {
|
|
735
791
|
const trialId = useTrialId();
|
|
@@ -743,22 +799,9 @@ function CssVarsPanel({ globals = NO_GLOBALS, themeApi } = {}) {
|
|
|
743
799
|
const [edited, setEdited] = usePersistedState("fg-css-vars-scales", {}, { scope: "trial" });
|
|
744
800
|
const [tab, setTab] = useState("theme");
|
|
745
801
|
const [filter, setFilter] = useState("");
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
const known = MANIFEST.get(name);
|
|
750
|
-
return { name, type: known?.type ?? inferTokenType(value), group: known?.group ?? groupOf(name), value };
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
const reported = new Map(frame.vars.map((v) => [v.name, v.value]));
|
|
754
|
-
return TOKEN_MANIFEST.map((token) => ({
|
|
755
|
-
name: token.name,
|
|
756
|
-
type: token.type,
|
|
757
|
-
group: token.group,
|
|
758
|
-
value: reported.get(token.name) ?? token.defaultValue,
|
|
759
|
-
description: token.description
|
|
760
|
-
}));
|
|
761
|
-
}, [tab, frame.vars]);
|
|
802
|
+
const themeTokens = useThemeTokens(globals, frame.vars);
|
|
803
|
+
const story = useStoryTokens(frame.vars);
|
|
804
|
+
const tokens = tab === "theme" ? themeTokens : story.tokens;
|
|
762
805
|
const query = filter.trim().toLowerCase();
|
|
763
806
|
const shown = tokens.map((token) => {
|
|
764
807
|
const override = overrides[token.name];
|
|
@@ -850,27 +893,27 @@ function CssVarsPanel({ globals = NO_GLOBALS, themeApi } = {}) {
|
|
|
850
893
|
placeholder: "Filter",
|
|
851
894
|
value: filter,
|
|
852
895
|
onChange: setFilter,
|
|
853
|
-
leadingAdornment: /* @__PURE__ */ jsx(
|
|
896
|
+
leadingAdornment: /* @__PURE__ */ jsx(Icon, { name: (
|
|
897
|
+
/* @icon */
|
|
898
|
+
"ui:search"
|
|
899
|
+
), size: 12 })
|
|
854
900
|
}
|
|
855
901
|
),
|
|
856
902
|
/* @__PURE__ */ jsxs("div", { className: "fg-css-vars__steps", role: "group", "aria-label": "History", children: [
|
|
857
|
-
/* @__PURE__ */ jsx(Button$1, { iconOnly: true, ariaLabel: "Undo", variant: "ghost", size: "sm", disabled: !history2.canUndo, onClick: () => history2.step("undo"), children: /* @__PURE__ */ jsx(Icon, { name:
|
|
858
|
-
|
|
903
|
+
/* @__PURE__ */ jsx(Button$1, { iconOnly: true, ariaLabel: "Undo", variant: "ghost", size: "sm", disabled: !history2.canUndo, onClick: () => history2.step("undo"), children: /* @__PURE__ */ jsx(Icon, { name: (
|
|
904
|
+
/* @icon */
|
|
905
|
+
"core:undo"
|
|
906
|
+
), size: 14 }) }),
|
|
907
|
+
/* @__PURE__ */ jsx(Button$1, { iconOnly: true, ariaLabel: "Redo", variant: "ghost", size: "sm", disabled: !history2.canRedo, onClick: () => history2.step("redo"), children: /* @__PURE__ */ jsx(Icon, { name: (
|
|
908
|
+
/* @icon */
|
|
909
|
+
"core:redo"
|
|
910
|
+
), size: 14 }) })
|
|
859
911
|
] })
|
|
860
912
|
] }),
|
|
861
|
-
tab === "
|
|
862
|
-
|
|
863
|
-
notice ? /* @__PURE__ */ jsx(
|
|
864
|
-
"p",
|
|
865
|
-
{
|
|
866
|
-
className: `fg-css-vars__notice fg-css-vars__notice--${notice.tone}`,
|
|
867
|
-
role: notice.tone === "error" ? "alert" : "status",
|
|
868
|
-
children: notice.text
|
|
869
|
-
}
|
|
870
|
-
) : null
|
|
871
|
-
] }) : null
|
|
913
|
+
tab === "story" ? /* @__PURE__ */ jsx(Checkbox, { className: "fg-css-vars__unused", isSelected: story.showUnused, onChange: story.setShowUnused, children: "Show variables the story doesn\u2019t use" }) : null,
|
|
914
|
+
tab === "theme" ? /* @__PURE__ */ jsx(SaveRow, { pending, saving, notice, onSave: () => void save() }) : null
|
|
872
915
|
] }),
|
|
873
|
-
shown.length === 0 ? /* @__PURE__ */ jsx("p", { className: "fg-css-vars__empty", children: tokens.length
|
|
916
|
+
shown.length === 0 ? /* @__PURE__ */ jsx("p", { className: "fg-css-vars__empty", children: tokens.length > 0 ? "No variables match." : tab === "story" ? story.emptyText : "The story\u2019s frame has reported no variables." }) : /* @__PURE__ */ jsx("div", { className: "fg-css-vars__list", children: /* @__PURE__ */ jsx(
|
|
874
917
|
TokenPanel,
|
|
875
918
|
{
|
|
876
919
|
tokens: shown,
|
|
@@ -887,6 +930,13 @@ function CssVarsPanel({ globals = NO_GLOBALS, themeApi } = {}) {
|
|
|
887
930
|
)
|
|
888
931
|
);
|
|
889
932
|
}
|
|
933
|
+
function SaveRow({ pending, saving, notice, onSave }) {
|
|
934
|
+
if (!pending && !notice) return null;
|
|
935
|
+
return /* @__PURE__ */ jsxs("div", { className: "fg-css-vars__save", children: [
|
|
936
|
+
pending ? /* @__PURE__ */ jsx(Button$1, { variant: "primary", size: "sm", loading: saving, disabled: saving, onClick: onSave, children: "Save to theme" }) : null,
|
|
937
|
+
notice ? /* @__PURE__ */ jsx("p", { className: `fg-css-vars__notice fg-css-vars__notice--${notice.tone}`, role: notice.tone === "error" ? "alert" : "status", children: notice.text }) : null
|
|
938
|
+
] });
|
|
939
|
+
}
|
|
890
940
|
function FocusedCssVars() {
|
|
891
941
|
const { focusedTrialId, trials, instruments } = useLabContext();
|
|
892
942
|
const labGlobals2 = useContext(StoryGlobalsContext);
|
|
@@ -904,6 +954,19 @@ var CSS_VARS_SECTION = {
|
|
|
904
954
|
region: "aside",
|
|
905
955
|
item: { title: "CSS Vars", actions: /* @__PURE__ */ jsx(SectionJump, {}), fill: true, body: /* @__PURE__ */ jsx(FocusedCssVars, {}) }
|
|
906
956
|
};
|
|
957
|
+
var KEYS = { kind: "key", key: "i", mods: { mod: true } };
|
|
958
|
+
function infoItem(open) {
|
|
959
|
+
return {
|
|
960
|
+
icon: (
|
|
961
|
+
/* @icon */
|
|
962
|
+
"ui:info"
|
|
963
|
+
),
|
|
964
|
+
label: "Info",
|
|
965
|
+
shortcut: formatShortcut({ key: KEYS.key, mod: KEYS.mods.mod }),
|
|
966
|
+
keys: KEYS,
|
|
967
|
+
onActivate: open
|
|
968
|
+
};
|
|
969
|
+
}
|
|
907
970
|
var GLOBALS = "$globals";
|
|
908
971
|
function leaves(shape, prefix = "") {
|
|
909
972
|
const out = [];
|
|
@@ -1036,10 +1099,10 @@ var whenIdle = (fn) => {
|
|
|
1036
1099
|
return () => clearTimeout(handle);
|
|
1037
1100
|
};
|
|
1038
1101
|
function createFramePool(frameUrl, size = 2) {
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
document.body.append(
|
|
1102
|
+
const shelf2 = document.createElement("div");
|
|
1103
|
+
shelf2.className = "fg-frame-pool";
|
|
1104
|
+
shelf2.setAttribute("aria-hidden", "true");
|
|
1105
|
+
document.body.append(shelf2);
|
|
1043
1106
|
const warm = [];
|
|
1044
1107
|
const waits = /* @__PURE__ */ new Set();
|
|
1045
1108
|
let cancelRefill = null;
|
|
@@ -1066,7 +1129,7 @@ function createFramePool(frameUrl, size = 2) {
|
|
|
1066
1129
|
}, HELLO_TIMEOUT_MS);
|
|
1067
1130
|
waits.add(stop);
|
|
1068
1131
|
window.addEventListener("message", onMessage);
|
|
1069
|
-
|
|
1132
|
+
shelf2.append(frame);
|
|
1070
1133
|
};
|
|
1071
1134
|
const refill = () => {
|
|
1072
1135
|
if (disposed || cancelRefill) return;
|
|
@@ -1087,7 +1150,7 @@ function createFramePool(frameUrl, size = 2) {
|
|
|
1087
1150
|
disposed = true;
|
|
1088
1151
|
cancelRefill?.();
|
|
1089
1152
|
for (const stop of [...waits]) stop();
|
|
1090
|
-
|
|
1153
|
+
shelf2.remove();
|
|
1091
1154
|
}
|
|
1092
1155
|
};
|
|
1093
1156
|
}
|
|
@@ -1097,7 +1160,7 @@ var FramePoolContext = createContext(null);
|
|
|
1097
1160
|
function routedTrial(trials, focusedTrialId, route) {
|
|
1098
1161
|
return trials.find((t) => t.id === focusedTrialId && t.instrumentName === route) ?? trials.find((t) => t.instrumentName === route);
|
|
1099
1162
|
}
|
|
1100
|
-
function PlayheadSync({ trialId, route,
|
|
1163
|
+
function PlayheadSync({ trialId, route, appliedRef }) {
|
|
1101
1164
|
const clock = useTrialClock(trialId);
|
|
1102
1165
|
useEffect(() => {
|
|
1103
1166
|
if (!clock) return;
|
|
@@ -1115,8 +1178,8 @@ function PlayheadSync({ trialId, route, applied }) {
|
|
|
1115
1178
|
held = next;
|
|
1116
1179
|
writePlayheadParam(next);
|
|
1117
1180
|
};
|
|
1118
|
-
if (
|
|
1119
|
-
|
|
1181
|
+
if (appliedRef.current !== route) {
|
|
1182
|
+
appliedRef.current = route;
|
|
1120
1183
|
fromUrl();
|
|
1121
1184
|
}
|
|
1122
1185
|
toUrl();
|
|
@@ -1132,7 +1195,7 @@ function PlayheadSync({ trialId, route, applied }) {
|
|
|
1132
1195
|
off();
|
|
1133
1196
|
window.removeEventListener("hashchange", onHashChange);
|
|
1134
1197
|
};
|
|
1135
|
-
}, [clock, route,
|
|
1198
|
+
}, [clock, route, appliedRef]);
|
|
1136
1199
|
return null;
|
|
1137
1200
|
}
|
|
1138
1201
|
function RoutePlayhead() {
|
|
@@ -1141,7 +1204,7 @@ function RoutePlayhead() {
|
|
|
1141
1204
|
const applied = useRef(null);
|
|
1142
1205
|
const trial = route === null ? void 0 : routedTrial(lab.trials, lab.focusedTrialId, route);
|
|
1143
1206
|
if (!trial || route === null) return null;
|
|
1144
|
-
return /* @__PURE__ */ jsx(PlayheadSync, { trialId: trial.id, route, applied }, `${trial.id} ${route}`);
|
|
1207
|
+
return /* @__PURE__ */ jsx(PlayheadSync, { trialId: trial.id, route, appliedRef: applied }, `${trial.id} ${route}`);
|
|
1145
1208
|
}
|
|
1146
1209
|
var RECORD = "fg-globals";
|
|
1147
1210
|
function useLabGlobals(declarations) {
|
|
@@ -1177,7 +1240,8 @@ function GlobalsToolbar({ declarations }) {
|
|
|
1177
1240
|
NestedGlobals,
|
|
1178
1241
|
{
|
|
1179
1242
|
label: declaration.label,
|
|
1180
|
-
icon: declaration.icon ??
|
|
1243
|
+
icon: declaration.icon ?? /* @icon */
|
|
1244
|
+
"core:tune",
|
|
1181
1245
|
nested,
|
|
1182
1246
|
values,
|
|
1183
1247
|
set
|
|
@@ -1235,6 +1299,41 @@ function LabGlobals({
|
|
|
1235
1299
|
useEffect(() => onChange(resolved), [resolved, onChange]);
|
|
1236
1300
|
return null;
|
|
1237
1301
|
}
|
|
1302
|
+
var leafPath = (n) => `globals.d${n}.v`;
|
|
1303
|
+
function globalsMenu(declarations, values, set) {
|
|
1304
|
+
const entries = Object.entries(declarations);
|
|
1305
|
+
if (entries.length === 0) return void 0;
|
|
1306
|
+
const submenus = {};
|
|
1307
|
+
const current = {};
|
|
1308
|
+
const keys = /* @__PURE__ */ new Map();
|
|
1309
|
+
entries.forEach(([key, declaration], n) => {
|
|
1310
|
+
const options = labOptions(declaration, values);
|
|
1311
|
+
submenus[`d${n}`] = pref.group(
|
|
1312
|
+
declaration.label,
|
|
1313
|
+
{ v: pref.enum(declaration.label, declaration.default, options) },
|
|
1314
|
+
{ as: "menu" }
|
|
1315
|
+
);
|
|
1316
|
+
current[leafPath(n)] = values[key];
|
|
1317
|
+
keys.set(leafPath(n), key);
|
|
1318
|
+
});
|
|
1319
|
+
return {
|
|
1320
|
+
schema: pref.group("", { globals: pref.group("Globals", submenus, { as: "menu" }) }),
|
|
1321
|
+
values: current,
|
|
1322
|
+
onChange: (path, value) => {
|
|
1323
|
+
const key = keys.get(path);
|
|
1324
|
+
if (key !== void 0 && typeof value === "string") set(key, value);
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
function GlobalsMenus({
|
|
1329
|
+
declarations,
|
|
1330
|
+
onMenus
|
|
1331
|
+
}) {
|
|
1332
|
+
const [values, set] = useLabGlobals(declarations);
|
|
1333
|
+
const menus = useMemo(() => globalsMenu(declarations, values, set), [declarations, values, set]);
|
|
1334
|
+
useEffect(() => onMenus(menus), [menus, onMenus]);
|
|
1335
|
+
return null;
|
|
1336
|
+
}
|
|
1238
1337
|
function KnobSync({ schema, config }) {
|
|
1239
1338
|
const { setConfig } = useTrialState();
|
|
1240
1339
|
const reading = useRef(true);
|
|
@@ -1481,13 +1580,13 @@ function StoryTree({ ctx, index }) {
|
|
|
1481
1580
|
}, [index]);
|
|
1482
1581
|
const enabled = (value) => views.some((item) => item.value === value && !item.disabled);
|
|
1483
1582
|
const view = [chosen, "components", "gallery"].find(enabled) ?? chosen;
|
|
1484
|
-
const
|
|
1583
|
+
const kept2 = useMemo(() => index.filter((entry) => !hidden[table.of(entry)]), [index, hidden, table]);
|
|
1485
1584
|
const [galleryEntries, rest] = useMemo(() => {
|
|
1486
1585
|
const yes = [];
|
|
1487
1586
|
const no = [];
|
|
1488
|
-
for (const entry of
|
|
1587
|
+
for (const entry of kept2) (isGallery(entry) ? yes : no).push(entry);
|
|
1489
1588
|
return [yes, no];
|
|
1490
|
-
}, [
|
|
1589
|
+
}, [kept2]);
|
|
1491
1590
|
const tree = useMemo(() => buildTree(rest), [rest]);
|
|
1492
1591
|
const components = useMemo(() => buildComponents(rest, table), [rest, table]);
|
|
1493
1592
|
const galleries = useMemo(() => buildComponents(galleryEntries, table), [galleryEntries, table]);
|
|
@@ -1495,7 +1594,7 @@ function StoryTree({ ctx, index }) {
|
|
|
1495
1594
|
() => view === "tree" ? filterTree(tree, query) : componentNodes(filterComponents(view === "gallery" ? galleries : components, query)),
|
|
1496
1595
|
[view, components, galleries, tree, query]
|
|
1497
1596
|
);
|
|
1498
|
-
const routable = useMemo(() => [...
|
|
1597
|
+
const routable = useMemo(() => [...kept2, ...indexEntries(kept2)], [kept2]);
|
|
1499
1598
|
const routed = routable.find((entry) => entry.id === route);
|
|
1500
1599
|
const routeAncestors = useMemo(() => ancestorsOf(routed), [routed]);
|
|
1501
1600
|
const filtering = query.trim() !== "";
|
|
@@ -1786,6 +1885,54 @@ function createAnswerBook() {
|
|
|
1786
1885
|
|
|
1787
1886
|
// src/shell/readyKey.ts
|
|
1788
1887
|
var readyKey = (ready) => stableStringify({ schema: ready.schema, layout: ready.layout, viewport: ready.viewport });
|
|
1888
|
+
var kept = /* @__PURE__ */ new Map();
|
|
1889
|
+
var shelf = null;
|
|
1890
|
+
function shelfElement() {
|
|
1891
|
+
if (!shelf) {
|
|
1892
|
+
shelf = document.createElement("div");
|
|
1893
|
+
shelf.className = "fg-frame-pool";
|
|
1894
|
+
shelf.setAttribute("aria-hidden", "true");
|
|
1895
|
+
}
|
|
1896
|
+
if (!shelf.isConnected) document.body.append(shelf);
|
|
1897
|
+
return shelf;
|
|
1898
|
+
}
|
|
1899
|
+
function adopt({ key, slot, frame, link, onAdopt }) {
|
|
1900
|
+
const waiting = key === null ? void 0 : kept.get(key);
|
|
1901
|
+
if (key === null || !waiting || !slot.current) return;
|
|
1902
|
+
kept.delete(key);
|
|
1903
|
+
moveFrame(slot.current, waiting.frame);
|
|
1904
|
+
frame.current = waiting.frame;
|
|
1905
|
+
link.current = waiting.link;
|
|
1906
|
+
onAdopt(waiting.link);
|
|
1907
|
+
}
|
|
1908
|
+
function shelve(options) {
|
|
1909
|
+
const { key, frame, link, onDrop } = options.current;
|
|
1910
|
+
const element = frame.current;
|
|
1911
|
+
const current = link.current;
|
|
1912
|
+
if (key === null || !element?.isConnected || !current || !canMoveFrames()) return false;
|
|
1913
|
+
if (current.awaiting === null && current.sent === null) return false;
|
|
1914
|
+
const entry = { frame: element, link: current };
|
|
1915
|
+
moveFrame(shelfElement(), element);
|
|
1916
|
+
kept.set(key, entry);
|
|
1917
|
+
queueMicrotask(() => {
|
|
1918
|
+
if (kept.get(key) !== entry) return;
|
|
1919
|
+
kept.delete(key);
|
|
1920
|
+
onDrop();
|
|
1921
|
+
element.remove();
|
|
1922
|
+
});
|
|
1923
|
+
return true;
|
|
1924
|
+
}
|
|
1925
|
+
function useKeptFrame(options) {
|
|
1926
|
+
const handedOn = useRef(false);
|
|
1927
|
+
const latest = useLatest(options);
|
|
1928
|
+
useLayoutEffect(() => {
|
|
1929
|
+
adopt(latest.current);
|
|
1930
|
+
return () => {
|
|
1931
|
+
if (shelve(latest)) handedOn.current = true;
|
|
1932
|
+
};
|
|
1933
|
+
}, [latest]);
|
|
1934
|
+
return useCallback(() => handedOn.current, []);
|
|
1935
|
+
}
|
|
1789
1936
|
var START_TIMEOUT_MS = 1e4;
|
|
1790
1937
|
var requests = 0;
|
|
1791
1938
|
function closeLink(link) {
|
|
@@ -1800,6 +1947,27 @@ function closeLink(link) {
|
|
|
1800
1947
|
function mismatchMessage(mismatch) {
|
|
1801
1948
|
return mismatch.reason === "version" ? `Frame speaks protocol ${String(mismatch.version)}; this workshop speaks ${PROTOCOL_VERSION}` : "Frame sent a message that is not a forge envelope";
|
|
1802
1949
|
}
|
|
1950
|
+
function sendInputs(current, sent, next) {
|
|
1951
|
+
const before = current.inputs;
|
|
1952
|
+
const { state, globals } = next;
|
|
1953
|
+
const config = storyConfig(next.config, sent.config);
|
|
1954
|
+
if (config !== sent.config) {
|
|
1955
|
+
sent.config = config;
|
|
1956
|
+
current.channel.send({ type: "config", config });
|
|
1957
|
+
current.inputs += 1;
|
|
1958
|
+
}
|
|
1959
|
+
if (state !== sent.state) {
|
|
1960
|
+
sent.state = state;
|
|
1961
|
+
current.channel.send({ type: "state", state });
|
|
1962
|
+
current.inputs += 1;
|
|
1963
|
+
}
|
|
1964
|
+
if (globals !== sent.globals && stableStringify(globals) !== stableStringify(sent.globals)) {
|
|
1965
|
+
sent.globals = globals;
|
|
1966
|
+
current.channel.send({ type: "globals", globals });
|
|
1967
|
+
current.inputs += 1;
|
|
1968
|
+
}
|
|
1969
|
+
return current.inputs !== before;
|
|
1970
|
+
}
|
|
1803
1971
|
function FrameView(props) {
|
|
1804
1972
|
const { entry, frameUrl, answers, descriptionKey, ctx } = props;
|
|
1805
1973
|
const labGlobals2 = useContext(StoryGlobalsContext);
|
|
@@ -1906,6 +2074,7 @@ function FrameView(props) {
|
|
|
1906
2074
|
};
|
|
1907
2075
|
const current = {
|
|
1908
2076
|
channel,
|
|
2077
|
+
deliver: (msg) => receive(current, msg),
|
|
1909
2078
|
timer,
|
|
1910
2079
|
awaiting: null,
|
|
1911
2080
|
sent: null,
|
|
@@ -1916,23 +2085,35 @@ function FrameView(props) {
|
|
|
1916
2085
|
queued: []
|
|
1917
2086
|
};
|
|
1918
2087
|
link.current = current;
|
|
1919
|
-
channel.on((msg) =>
|
|
2088
|
+
channel.on((msg) => current.deliver(msg));
|
|
1920
2089
|
target.postMessage({ type: PORT_HANDOFF, id: latestRef.current.entry.id }, location.origin, [
|
|
1921
2090
|
port2
|
|
1922
2091
|
]);
|
|
1923
2092
|
};
|
|
1924
2093
|
const connectRef = useLatest(connect);
|
|
2094
|
+
const handedOn = useKeptFrame({
|
|
2095
|
+
key: trialId ? `${trialId} ${src}` : null,
|
|
2096
|
+
slot: slotRef,
|
|
2097
|
+
frame: iframeRef,
|
|
2098
|
+
link,
|
|
2099
|
+
onAdopt: (adopted) => {
|
|
2100
|
+
adopted.deliver = (msg) => receive(adopted, msg);
|
|
2101
|
+
setPending(!adopted.hasRendered);
|
|
2102
|
+
},
|
|
2103
|
+
onDrop: () => closeLink(link)
|
|
2104
|
+
});
|
|
1925
2105
|
useEffect(() => {
|
|
1926
2106
|
const slot = slotRef.current;
|
|
1927
2107
|
if (!inView || !slot) return;
|
|
1928
|
-
const
|
|
1929
|
-
const
|
|
2108
|
+
const adopted = iframeRef.current;
|
|
2109
|
+
const claimed = adopted ? null : latestRef.current.pool?.claim() ?? null;
|
|
2110
|
+
const frame = adopted ?? claimed ?? document.createElement("iframe");
|
|
1930
2111
|
frame.className = "fg-frame-view";
|
|
1931
2112
|
frame.title = latestRef.current.title;
|
|
1932
2113
|
frame.removeAttribute("tabindex");
|
|
1933
2114
|
frame.toggleAttribute("data-pending", true);
|
|
1934
2115
|
iframeRef.current = frame;
|
|
1935
|
-
const unheard = claimed ? void 0 : setTimeout(() => setFault({ phase: null, message: `Frame did not start: ${src}` }), HELLO_TIMEOUT_MS);
|
|
2116
|
+
const unheard = adopted || claimed ? void 0 : setTimeout(() => setFault({ phase: null, message: `Frame did not start: ${src}` }), HELLO_TIMEOUT_MS);
|
|
1936
2117
|
const onMessage = (event) => {
|
|
1937
2118
|
if (event.source !== frame.contentWindow || event.origin !== location.origin) return;
|
|
1938
2119
|
if (event.data?.type !== FRAME_HELLO) return;
|
|
@@ -1943,20 +2124,21 @@ function FrameView(props) {
|
|
|
1943
2124
|
if (claimed) {
|
|
1944
2125
|
moveFrame(slot, claimed);
|
|
1945
2126
|
connectRef.current(claimed);
|
|
1946
|
-
} else {
|
|
2127
|
+
} else if (!adopted) {
|
|
1947
2128
|
frame.src = src;
|
|
1948
2129
|
slot.append(frame);
|
|
1949
2130
|
}
|
|
1950
2131
|
return () => {
|
|
1951
2132
|
window.removeEventListener("message", onMessage);
|
|
1952
2133
|
clearTimeout(unheard);
|
|
2134
|
+
if (handedOn()) return;
|
|
1953
2135
|
closeLink(link);
|
|
1954
2136
|
frame.remove();
|
|
1955
2137
|
iframeRef.current = null;
|
|
1956
2138
|
setPending(true);
|
|
1957
2139
|
setFault(null);
|
|
1958
2140
|
};
|
|
1959
|
-
}, [inView, src, connectRef, latestRef]);
|
|
2141
|
+
}, [inView, src, connectRef, latestRef, handedOn]);
|
|
1960
2142
|
useEffect(() => {
|
|
1961
2143
|
iframeRef.current?.toggleAttribute("data-pending", pending && !fault);
|
|
1962
2144
|
}, [pending, fault, inView, src]);
|
|
@@ -1971,29 +2153,9 @@ function FrameView(props) {
|
|
|
1971
2153
|
if (current.awaiting === descriptionKey) init(current);
|
|
1972
2154
|
return;
|
|
1973
2155
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
let input = false;
|
|
1977
|
-
const config = storyConfig(ctx.config, sent.config);
|
|
1978
|
-
if (config !== sent.config) {
|
|
1979
|
-
sent.config = config;
|
|
1980
|
-
current.channel.send({ type: "config", config });
|
|
1981
|
-
current.inputs += 1;
|
|
1982
|
-
input = true;
|
|
1983
|
-
}
|
|
1984
|
-
if (ctx.state !== sent.state) {
|
|
1985
|
-
sent.state = ctx.state;
|
|
1986
|
-
current.channel.send({ type: "state", state: ctx.state });
|
|
1987
|
-
current.inputs += 1;
|
|
1988
|
-
input = true;
|
|
2156
|
+
if (current.sent && sendInputs(current, current.sent, { config: ctx.config, state: ctx.state, globals })) {
|
|
2157
|
+
setFault((shown) => shown?.phase === "render" ? null : shown);
|
|
1989
2158
|
}
|
|
1990
|
-
if (globals !== sent.globals && stableStringify(globals) !== stableStringify(sent.globals)) {
|
|
1991
|
-
sent.globals = globals;
|
|
1992
|
-
current.channel.send({ type: "globals", globals });
|
|
1993
|
-
current.inputs += 1;
|
|
1994
|
-
input = true;
|
|
1995
|
-
}
|
|
1996
|
-
if (input) setFault((shown) => shown?.phase === "render" ? null : shown);
|
|
1997
2159
|
}, [ctx.config, ctx.state, globals, descriptionKey, init]);
|
|
1998
2160
|
return /* @__PURE__ */ jsxs("div", { ref: hostRef, className: "fg-frame-host", children: [
|
|
1999
2161
|
/* @__PURE__ */ jsx("div", { ref: slotRef, className: "fg-frame-slot" }),
|
|
@@ -2172,7 +2334,7 @@ function useStoryRegistry(index, options) {
|
|
|
2172
2334
|
const answers = previous.books.get(entry.id) ?? createAnswerBook();
|
|
2173
2335
|
books.set(entry.id, answers);
|
|
2174
2336
|
const held = previous.built.get(entry.id);
|
|
2175
|
-
const
|
|
2337
|
+
const kept2 = held && held.entryKey === entryKey && held.ready === ready && held.loaded === had && held.fault === fault && held.frameUrl === frameUrl && held.globals === globals && held.setup === setup && held.inDocument === inDocument && held.generation === generation && held.revision === revision ? held : {
|
|
2176
2338
|
entryKey,
|
|
2177
2339
|
ready,
|
|
2178
2340
|
loaded: had,
|
|
@@ -2185,8 +2347,8 @@ function useStoryRegistry(index, options) {
|
|
|
2185
2347
|
revision,
|
|
2186
2348
|
instrument: !inDocument ? storyInstrument({ entry, ready, answers, frameUrl, onReady, globals }) : had ? documentInstrument({ entry, loaded: had, setup, globals }) : pendingInstrument({ entry, globals, load: request, generation, ...fault ? { fault } : {} })
|
|
2187
2349
|
};
|
|
2188
|
-
built.set(entry.id,
|
|
2189
|
-
return
|
|
2350
|
+
built.set(entry.id, kept2);
|
|
2351
|
+
return kept2.instrument;
|
|
2190
2352
|
});
|
|
2191
2353
|
const unchanged = list.length === previous.list.length && list.every((i, n) => i === previous.list[n]);
|
|
2192
2354
|
return { built, books, list: unchanged ? previous.list : list };
|
|
@@ -2224,26 +2386,15 @@ function RouteOpener() {
|
|
|
2224
2386
|
}, [route, lab]);
|
|
2225
2387
|
return null;
|
|
2226
2388
|
}
|
|
2227
|
-
function
|
|
2389
|
+
function useDocumentTitle(title) {
|
|
2228
2390
|
useEffect(() => {
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
if (!(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey) return;
|
|
2232
|
-
const target = event.target;
|
|
2233
|
-
if (target?.isContentEditable || /^(input|textarea|select)$/i.test(target?.tagName ?? "")) return;
|
|
2234
|
-
event.preventDefault();
|
|
2235
|
-
open(true);
|
|
2236
|
-
};
|
|
2237
|
-
window.addEventListener("keydown", onKeyDown);
|
|
2238
|
-
return () => window.removeEventListener("keydown", onKeyDown);
|
|
2239
|
-
}, [open]);
|
|
2391
|
+
document.title = title;
|
|
2392
|
+
}, [title]);
|
|
2240
2393
|
}
|
|
2241
2394
|
function Workshop({ index, frameUrl, importers, setup, changes, config, stories = [], storageKey, storage, dependencies }) {
|
|
2242
2395
|
const declarations = config?.globals ?? NO_DECLARATIONS;
|
|
2243
2396
|
const title = config?.title ?? DEFAULT_TITLE;
|
|
2244
|
-
|
|
2245
|
-
document.title = title;
|
|
2246
|
-
}, [title]);
|
|
2397
|
+
useDocumentTitle(title);
|
|
2247
2398
|
const [frames] = useState(createTrialFrames);
|
|
2248
2399
|
const [clocks] = useState(createTrialClocks);
|
|
2249
2400
|
const [pool, setPool] = useState(null);
|
|
@@ -2279,15 +2430,11 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
|
|
|
2279
2430
|
const themeFor = config?.labTheme;
|
|
2280
2431
|
const labTheme = useMemo(() => themeFor?.(labValues), [themeFor, labValues]);
|
|
2281
2432
|
const [infoOpen, setInfoOpen] = useState(false);
|
|
2282
|
-
useInfoShortcut(setInfoOpen);
|
|
2283
2433
|
const reportLabValues = useCallback(
|
|
2284
2434
|
(next) => setLabValues((prev) => stableStringify(prev) === stableStringify(next) ? prev : next),
|
|
2285
2435
|
[]
|
|
2286
2436
|
);
|
|
2287
|
-
const infoCommand = useMemo(
|
|
2288
|
-
() => ({ icon: InfoIcon, label: "Info", shortcut: "\u2318I", onActivate: () => setInfoOpen(true) }),
|
|
2289
|
-
[]
|
|
2290
|
-
);
|
|
2437
|
+
const infoCommand = useMemo(() => infoItem(() => setInfoOpen(true)), []);
|
|
2291
2438
|
const labChrome = useMemo(
|
|
2292
2439
|
() => [
|
|
2293
2440
|
{ id: "fg-stories", region: "sidebar", render: (ctx) => /* @__PURE__ */ jsx(StoryTree, { ctx, index }) },
|
|
@@ -2299,6 +2446,7 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
|
|
|
2299
2446
|
],
|
|
2300
2447
|
[index, infoCommand, declarations, config?.cssVars, config?.labChrome]
|
|
2301
2448
|
);
|
|
2449
|
+
const [menus, setMenus] = useState();
|
|
2302
2450
|
const first = index[0];
|
|
2303
2451
|
if (!first) {
|
|
2304
2452
|
return /* @__PURE__ */ jsxs("div", { className: "fg-empty", children: [
|
|
@@ -2318,6 +2466,7 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
|
|
|
2318
2466
|
...storage ? { storage } : {},
|
|
2319
2467
|
labChrome,
|
|
2320
2468
|
addTrial: false,
|
|
2469
|
+
...menus ? { menus } : {},
|
|
2321
2470
|
...config?.controls ? { controls: config.controls } : {},
|
|
2322
2471
|
...config?.pages ? { pages: config.pages } : {},
|
|
2323
2472
|
...config?.path !== void 0 ? { path: config.path } : {},
|
|
@@ -2334,7 +2483,8 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
|
|
|
2334
2483
|
onOpenChange: setInfoOpen
|
|
2335
2484
|
}
|
|
2336
2485
|
),
|
|
2337
|
-
/* @__PURE__ */ jsx(LabGlobals, { declarations, onChange: reportLabValues })
|
|
2486
|
+
/* @__PURE__ */ jsx(LabGlobals, { declarations, onChange: reportLabValues }),
|
|
2487
|
+
/* @__PURE__ */ jsx(GlobalsMenus, { declarations, onMenus: setMenus })
|
|
2338
2488
|
]
|
|
2339
2489
|
}
|
|
2340
2490
|
) }) }) }) }) }) });
|
|
@@ -2342,7 +2492,7 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
|
|
|
2342
2492
|
function mountWorkshop({ container, ...props }) {
|
|
2343
2493
|
const target = container ?? document.getElementById("root");
|
|
2344
2494
|
if (!target) throw new Error("mountWorkshop: no container given and no #root element");
|
|
2345
|
-
const root =
|
|
2495
|
+
const root = createKitRoot(target, props.setup?.icons);
|
|
2346
2496
|
const changes = createStoryChanges();
|
|
2347
2497
|
let current = { ...props, changes };
|
|
2348
2498
|
root.render(/* @__PURE__ */ jsx(Workshop, { ...current }));
|