@weasel-js/forge 1.10.1 → 1.11.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.
Files changed (43) hide show
  1. package/README.md +2 -1
  2. package/dist/{FrameController-UeTY7ubF.d.ts → FrameController-Cj_i-Rdk.d.ts} +23 -10
  3. package/dist/{chunk-DFKCLZKS.js → chunk-7BQPHMA2.js} +2 -2
  4. package/dist/chunk-7BQPHMA2.js.map +1 -0
  5. package/dist/{chunk-BIAEGWGJ.js → chunk-BKPDKUDN.js} +26 -15
  6. package/dist/chunk-BKPDKUDN.js.map +1 -0
  7. package/dist/{chunk-E2FHBDNQ.js → chunk-FZNABWJC.js} +13 -16
  8. package/dist/chunk-FZNABWJC.js.map +1 -0
  9. package/dist/{chunk-FL2MUPUL.js → chunk-GH5RE5X5.js} +2 -2
  10. package/dist/chunk-GH5RE5X5.js.map +1 -0
  11. package/dist/{chunk-AP3DRQLI.js → chunk-JTCUIJPZ.js} +170 -10
  12. package/dist/chunk-JTCUIJPZ.js.map +1 -0
  13. package/dist/{chunk-GB6PRJET.js → chunk-KEQBGDUZ.js} +3 -3
  14. package/dist/{chunk-GB6PRJET.js.map → chunk-KEQBGDUZ.js.map} +1 -1
  15. package/dist/{chunk-DKWHGSBZ.js → chunk-LTTEILXY.js} +3 -3
  16. package/dist/{chunk-DKWHGSBZ.js.map → chunk-LTTEILXY.js.map} +1 -1
  17. package/dist/{config-Cz7ZyGF-.d.ts → config-DN6IaNf6.d.ts} +6 -5
  18. package/dist/csf/preview-api.js +2 -2
  19. package/dist/frame/index.css +1 -0
  20. package/dist/frame/index.css.map +1 -1
  21. package/dist/frame/index.d.ts +3 -2
  22. package/dist/frame/index.js +13 -15
  23. package/dist/frame/index.js.map +1 -1
  24. package/dist/index.d.ts +6 -5
  25. package/dist/index.js +3 -3
  26. package/dist/index.js.map +1 -1
  27. package/dist/shell/index.css +18 -6
  28. package/dist/shell/index.css.map +1 -1
  29. package/dist/shell/index.d.ts +6 -13
  30. package/dist/shell/index.js +235 -92
  31. package/dist/shell/index.js.map +1 -1
  32. package/dist/test/index.d.ts +3 -4
  33. package/dist/test/index.js +10 -12
  34. package/dist/test/index.js.map +1 -1
  35. package/dist/vite/index.d.ts +1 -1
  36. package/dist/vite/index.js +14 -5
  37. package/dist/vite/index.js.map +1 -1
  38. package/package.json +8 -6
  39. package/dist/chunk-AP3DRQLI.js.map +0 -1
  40. package/dist/chunk-BIAEGWGJ.js.map +0 -1
  41. package/dist/chunk-DFKCLZKS.js.map +0 -1
  42. package/dist/chunk-E2FHBDNQ.js.map +0 -1
  43. package/dist/chunk-FL2MUPUL.js.map +0 -1
@@ -1,38 +1,55 @@
1
- import { parseRoute, formatRoute, paramsToKnobs, knobPaths, sameKnob, knobsToParams, PLAYHEAD_PARAM, reservedParams } from '../chunk-DKWHGSBZ.js';
1
+ import { parseRoute, formatRoute, paramsToKnobs, knobPaths, sameKnob, knobsToParams, PLAYHEAD_PARAM, reservedParams } from '../chunk-LTTEILXY.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-BIAEGWGJ.js';
3
+ import { TITLE_LIBRARIES, isIndexId, indexId, indexEntries, librariesFrom, LibrariesContext, createKitRoot, schemaFromDescription, LibraryBadge, openChannel, IndexPage } from '../chunk-BKPDKUDN.js';
4
4
  import { isGallery } from '../chunk-WEQSLPKT.js';
5
- import { useTrialFrame, createTrialFrames, createTrialClocks, StoryGlobalsContext, TrialFramesContext, TrialClocksContext, StoryTrial, useCssOverrides, useTrialClock, TrialHost } from '../chunk-E2FHBDNQ.js';
6
- export { StoryGlobalsContext, StoryTrial, TrialFramesContext, TrialHost, useTrialFrame } from '../chunk-E2FHBDNQ.js';
7
- import { labGlobals, withGlobals, followApp, storyConfig, GLOBALS_KEY, effectiveGlobals, PLAYHEAD_KEY, labOptions, isGlobalsPath } from '../chunk-DFKCLZKS.js';
8
- import { loadStories, indexRenderOf, useNearViewport, IN_VIEW_MARGIN } from '../chunk-AP3DRQLI.js';
5
+ import { useTrialFrame, createTrialFrames, createTrialClocks, StoryGlobalsContext, TrialFramesContext, TrialClocksContext, StoryTrial, useCssOverrides, useTrialClock, TrialHost } from '../chunk-FZNABWJC.js';
6
+ export { StoryGlobalsContext, StoryTrial, TrialFramesContext, TrialHost, useTrialFrame } from '../chunk-FZNABWJC.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-JTCUIJPZ.js';
9
9
  import '../chunk-L5XM5VKB.js';
10
10
  import '../chunk-ZAWXMUGZ.js';
11
- import '../chunk-GB6PRJET.js';
12
- import { stableStringify, PORT_HANDOFF, FRAME_HELLO, PROTOCOL_VERSION } from '../chunk-FL2MUPUL.js';
13
- import { useTrialId, Button, Lab, useLabContext, TrialIdContext, usePersistedState, Select, useTrialState, inferTokenType, ToggleBar as ToggleBar$1, Input, TokenPanel, refitScale } from '@weasel-js/labkit';
11
+ import '../chunk-KEQBGDUZ.js';
12
+ import { stableStringify, PORT_HANDOFF, FRAME_HELLO, PROTOCOL_VERSION } from '../chunk-GH5RE5X5.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 { InfoIcon, ToggleBar, Checkbox, Dialog, TOKEN_CATEGORIES, Icon, DisclosureMark, Button as Button$1, Callout, Slider, Transport, SearchIcon } from '@weasel-js/ui';
21
- import { themeAxes, weaselTheme, TOKEN_MANIFEST, THEME_SOURCES, resolveTheme, isColorModePreference, isByAxis } from '@weasel-js/theme';
19
+ import { Slider } from '@weasel-js/ui/components/Slider';
20
+ import { Transport } from '@weasel-js/ui/components/Timeline';
21
+ import { TOKEN_MANIFEST, themeAxes, weaselTheme, THEME_SOURCES, resolveTheme, isColorModePreference, isByAxis } from '@weasel-js/theme';
22
22
  import { scale, httpThemeApi } from '@weasel-js/theme/engine';
23
23
  import { useLatest, historyKey, createHistory, swapOp, isPlainObject } from '@weasel-js/core';
24
+ import { Button as Button$1 } from '@weasel-js/ui/components/Button';
25
+ import { Checkbox } from '@weasel-js/ui/components/Checkbox';
26
+ import { Icon } from '@weasel-js/ui/components/Icon';
27
+ import { ToggleBar } from '@weasel-js/ui/components/ToggleBar';
28
+ import { TOKEN_CATEGORIES } from '@weasel-js/ui/components/TokenPanel';
24
29
  import { useResolvedColorMode } from '@weasel-js/theme/react';
30
+ import { formatShortcut } from '@weasel-js/ui/components/ToolPalette';
31
+ import { Dialog } from '@weasel-js/ui/components/Dialog';
32
+ import { Callout } from '@weasel-js/ui/components/Callout';
33
+ import { pref } from '@weasel-js/prefs';
34
+ import { DisclosureMark } from '@weasel-js/ui/components/Disclosure';
25
35
 
26
- function Finding({ finding }) {
27
- return /* @__PURE__ */ jsxs("li", { className: "fg-a11y__finding", children: [
36
+ function pointAt(point, nodes) {
37
+ const enter = (event) => {
38
+ event.stopPropagation();
39
+ point(nodes);
40
+ };
41
+ return { onMouseOver: enter, onFocus: enter, onMouseLeave: () => point(null), onBlur: () => point(null) };
42
+ }
43
+ function Finding({ finding, point }) {
44
+ return /* @__PURE__ */ jsxs("li", { className: "fg-a11y__finding", ...pointAt(point, finding.nodes), children: [
28
45
  /* @__PURE__ */ jsxs("p", { className: "fg-a11y__help", children: [
29
46
  /* @__PURE__ */ jsx("a", { href: finding.helpUrl, target: "_blank", rel: "noreferrer", children: finding.help }),
30
47
  finding.impact ? /* @__PURE__ */ jsx("span", { className: "fg-a11y__impact", "data-impact": finding.impact, children: finding.impact }) : null
31
48
  ] }),
32
- /* @__PURE__ */ jsx("ul", { className: "fg-a11y__nodes", children: finding.nodes.map((node) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: node.target.join(" ") }) }, `${finding.id}:${node.target.join(",")}`)) })
49
+ /* @__PURE__ */ jsx("ul", { className: "fg-a11y__nodes", children: finding.nodes.map((node) => /* @__PURE__ */ jsx("li", { ...pointAt(point, [node]), children: /* @__PURE__ */ jsx("code", { children: node.target.join(" ") }) }, `${finding.id}:${node.target.join(",")}`)) })
33
50
  ] });
34
51
  }
35
- function Findings({ title, findings }) {
52
+ function Findings({ title, findings, point }) {
36
53
  if (findings.length === 0) return null;
37
54
  return /* @__PURE__ */ jsxs("section", { className: "fg-a11y__group", children: [
38
55
  /* @__PURE__ */ jsxs("h3", { className: `fg-a11y__group-title ${num.numeric}`, children: [
@@ -41,7 +58,7 @@ function Findings({ title, findings }) {
41
58
  findings.length,
42
59
  ")"
43
60
  ] }),
44
- /* @__PURE__ */ jsx("ul", { className: "fg-a11y__findings", children: findings.map((finding) => /* @__PURE__ */ jsx(Finding, { finding }, finding.id)) })
61
+ /* @__PURE__ */ jsx("ul", { className: "fg-a11y__findings", children: findings.map((finding) => /* @__PURE__ */ jsx(Finding, { finding, point }, finding.id)) })
45
62
  ] });
46
63
  }
47
64
  function A11yPanel() {
@@ -60,6 +77,13 @@ function A11yPanel() {
60
77
  ran.current = audit;
61
78
  start(audit);
62
79
  }, [audit, start]);
80
+ const send = frame.send;
81
+ const [pointed, setPointed] = useState(null);
82
+ useEffect(() => {
83
+ if (!send || !pointed) return;
84
+ send({ type: "a11y.highlight", targets: pointed.map((node) => node.target) });
85
+ return () => send({ type: "a11y.highlight", targets: [] });
86
+ }, [send, pointed]);
63
87
  const rerun = () => {
64
88
  if (audit) start(audit);
65
89
  };
@@ -82,8 +106,8 @@ function A11yPanel() {
82
106
  outcome.report.passes,
83
107
  " passes"
84
108
  ] }),
85
- /* @__PURE__ */ jsx(Findings, { title: "Violations", findings: outcome.report.violations }),
86
- /* @__PURE__ */ jsx(Findings, { title: "Needs review", findings: outcome.report.incomplete })
109
+ /* @__PURE__ */ jsx(Findings, { title: "Violations", findings: outcome.report.violations, point: setPointed }),
110
+ /* @__PURE__ */ jsx(Findings, { title: "Needs review", findings: outcome.report.incomplete, point: setPointed })
87
111
  ] }) : null
88
112
  ] });
89
113
  }
@@ -340,12 +364,30 @@ function missingInstrument(id, globals = {}) {
340
364
  };
341
365
  }
342
366
  var ICONS = {
343
- color: "drop",
344
- type: "type",
345
- size: "measure",
346
- motion: "curveEaseInOut",
347
- depth: "layers",
348
- other: "more"
367
+ color: (
368
+ /* @icon */
369
+ "ui:drop"
370
+ ),
371
+ type: (
372
+ /* @icon */
373
+ "ui:type"
374
+ ),
375
+ size: (
376
+ /* @icon */
377
+ "core:measure"
378
+ ),
379
+ motion: (
380
+ /* @icon */
381
+ "ui:curveEaseInOut"
382
+ ),
383
+ depth: (
384
+ /* @icon */
385
+ "ui:layers"
386
+ ),
387
+ other: (
388
+ /* @icon */
389
+ "ui:more"
390
+ )
349
391
  };
350
392
  var PANEL = ".fg-css-vars";
351
393
  var HOST = ".lk-sidebar-section, .lk-panel-tile";
@@ -636,6 +678,24 @@ function useThemeSave({ trialId, globals, themeApi, scales, overrides, history:
636
678
  save
637
679
  };
638
680
  }
681
+ var MANIFEST = new Map(TOKEN_MANIFEST.map((token) => [token.name, token]));
682
+ function groupOf(name) {
683
+ const segments = name.replace(/^--/, "").split("-");
684
+ return segments.length > 1 ? segments.slice(0, -1).join("-") : name.replace(/^--/, "");
685
+ }
686
+ function useStoryTokens(vars) {
687
+ const [showUnused, setShowUnused] = usePersistedState("fg-css-vars-unused", false, { scope: "lab" });
688
+ const tokens = useMemo(
689
+ () => vars.flatMap(({ name, value, used }) => {
690
+ if (!used && !showUnused) return [];
691
+ const known = MANIFEST.get(name);
692
+ return [{ name, type: known?.type ?? inferTokenType(value), group: known?.group ?? groupOf(name), value }];
693
+ }),
694
+ [vars, showUnused]
695
+ );
696
+ const emptyText = vars.length > 0 && !showUnused ? "The story uses no variables." : "The story\u2019s frame has reported no variables.";
697
+ return { tokens, showUnused, setShowUnused, emptyText };
698
+ }
639
699
  var AXES = themeAxes(weaselTheme);
640
700
  function useThemeTokens(globals, vars) {
641
701
  const selection = useTrialSelection(AXES, globals);
@@ -714,13 +774,13 @@ var TABS = [
714
774
  { value: "theme", label: "Theme" },
715
775
  { value: "story", label: "Story" }
716
776
  ];
717
- var MANIFEST = new Map(TOKEN_MANIFEST.map((token) => [token.name, token]));
777
+ var MANIFEST2 = new Map(TOKEN_MANIFEST.map((token) => [token.name, token]));
718
778
  var literal = (v) => typeof v === "number";
719
779
  var SCALE_SOURCES = Object.entries(THEME_SOURCES[THEME_NAME]?.scales ?? {}).flatMap(([name, def]) => {
720
780
  if (!Array.isArray(def.steps)) return [];
721
781
  const steps = def.steps;
722
782
  const tokens = steps.map((step) => `${TOKEN_PREFIX}${name}-${step}`);
723
- const group = MANIFEST.get(tokens[0] ?? "")?.group;
783
+ const group = MANIFEST2.get(tokens[0] ?? "")?.group;
724
784
  if (!group) return [];
725
785
  const factors = Array.isArray(def.factors) && def.factors.every(literal) ? def.factors : null;
726
786
  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";
@@ -750,10 +810,6 @@ function standingScale(source, amounts) {
750
810
  const base = (amounts[at] ?? 0) / (rule.factors[at] ?? 1);
751
811
  return { ...seed, base: Math.round(base * 100) / 100, rule };
752
812
  }
753
- function groupOf(name) {
754
- const segments = name.replace(/^--/, "").split("-");
755
- return segments.length > 1 ? segments.slice(0, -1).join("-") : name.replace(/^--/, "");
756
- }
757
813
  var NO_GLOBALS = {};
758
814
  function CssVarsPanel({ globals = NO_GLOBALS, themeApi } = {}) {
759
815
  const trialId = useTrialId();
@@ -768,13 +824,8 @@ function CssVarsPanel({ globals = NO_GLOBALS, themeApi } = {}) {
768
824
  const [tab, setTab] = useState("theme");
769
825
  const [filter, setFilter] = useState("");
770
826
  const themeTokens = useThemeTokens(globals, frame.vars);
771
- const tokens = useMemo(() => {
772
- if (tab === "theme") return themeTokens;
773
- return frame.vars.map(({ name, value }) => {
774
- const known = MANIFEST.get(name);
775
- return { name, type: known?.type ?? inferTokenType(value), group: known?.group ?? groupOf(name), value };
776
- });
777
- }, [tab, frame.vars, themeTokens]);
827
+ const story = useStoryTokens(frame.vars);
828
+ const tokens = tab === "theme" ? themeTokens : story.tokens;
778
829
  const query = filter.trim().toLowerCase();
779
830
  const shown = tokens.map((token) => {
780
831
  const override = overrides[token.name];
@@ -866,27 +917,27 @@ function CssVarsPanel({ globals = NO_GLOBALS, themeApi } = {}) {
866
917
  placeholder: "Filter",
867
918
  value: filter,
868
919
  onChange: setFilter,
869
- leadingAdornment: /* @__PURE__ */ jsx(SearchIcon, { size: 12 })
920
+ leadingAdornment: /* @__PURE__ */ jsx(Icon, { name: (
921
+ /* @icon */
922
+ "ui:search"
923
+ ), size: 12 })
870
924
  }
871
925
  ),
872
926
  /* @__PURE__ */ jsxs("div", { className: "fg-css-vars__steps", role: "group", "aria-label": "History", children: [
873
- /* @__PURE__ */ jsx(Button$1, { iconOnly: true, ariaLabel: "Undo", variant: "ghost", size: "sm", disabled: !history2.canUndo, onClick: () => history2.step("undo"), children: /* @__PURE__ */ jsx(Icon, { name: "undo", size: 14 }) }),
874
- /* @__PURE__ */ jsx(Button$1, { iconOnly: true, ariaLabel: "Redo", variant: "ghost", size: "sm", disabled: !history2.canRedo, onClick: () => history2.step("redo"), children: /* @__PURE__ */ jsx(Icon, { name: "redo", size: 14 }) })
927
+ /* @__PURE__ */ jsx(Button$1, { iconOnly: true, ariaLabel: "Undo", variant: "ghost", size: "sm", disabled: !history2.canUndo, onClick: () => history2.step("undo"), children: /* @__PURE__ */ jsx(Icon, { name: (
928
+ /* @icon */
929
+ "core:undo"
930
+ ), size: 14 }) }),
931
+ /* @__PURE__ */ jsx(Button$1, { iconOnly: true, ariaLabel: "Redo", variant: "ghost", size: "sm", disabled: !history2.canRedo, onClick: () => history2.step("redo"), children: /* @__PURE__ */ jsx(Icon, { name: (
932
+ /* @icon */
933
+ "core:redo"
934
+ ), size: 14 }) })
875
935
  ] })
876
936
  ] }),
877
- tab === "theme" && (pending || notice) ? /* @__PURE__ */ jsxs("div", { className: "fg-css-vars__save", children: [
878
- pending ? /* @__PURE__ */ jsx(Button$1, { variant: "primary", size: "sm", loading: saving, disabled: saving, onClick: () => void save(), children: "Save to theme" }) : null,
879
- notice ? /* @__PURE__ */ jsx(
880
- "p",
881
- {
882
- className: `fg-css-vars__notice fg-css-vars__notice--${notice.tone}`,
883
- role: notice.tone === "error" ? "alert" : "status",
884
- children: notice.text
885
- }
886
- ) : null
887
- ] }) : null
937
+ 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,
938
+ tab === "theme" ? /* @__PURE__ */ jsx(SaveRow, { pending, saving, notice, onSave: () => void save() }) : null
888
939
  ] }),
889
- shown.length === 0 ? /* @__PURE__ */ jsx("p", { className: "fg-css-vars__empty", children: tokens.length === 0 ? "The story\u2019s frame has reported no variables." : "No variables match." }) : /* @__PURE__ */ jsx("div", { className: "fg-css-vars__list", children: /* @__PURE__ */ jsx(
940
+ 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(
890
941
  TokenPanel,
891
942
  {
892
943
  tokens: shown,
@@ -903,6 +954,13 @@ function CssVarsPanel({ globals = NO_GLOBALS, themeApi } = {}) {
903
954
  )
904
955
  );
905
956
  }
957
+ function SaveRow({ pending, saving, notice, onSave }) {
958
+ if (!pending && !notice) return null;
959
+ return /* @__PURE__ */ jsxs("div", { className: "fg-css-vars__save", children: [
960
+ pending ? /* @__PURE__ */ jsx(Button$1, { variant: "primary", size: "sm", loading: saving, disabled: saving, onClick: onSave, children: "Save to theme" }) : null,
961
+ 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
962
+ ] });
963
+ }
906
964
  function FocusedCssVars() {
907
965
  const { focusedTrialId, trials, instruments } = useLabContext();
908
966
  const labGlobals2 = useContext(StoryGlobalsContext);
@@ -920,6 +978,42 @@ var CSS_VARS_SECTION = {
920
978
  region: "aside",
921
979
  item: { title: "CSS Vars", actions: /* @__PURE__ */ jsx(SectionJump, {}), fill: true, body: /* @__PURE__ */ jsx(FocusedCssVars, {}) }
922
980
  };
981
+
982
+ // src/shell/cssVars/cssVarsChrome.ts
983
+ var RECORD = "fg-css-vars-shown";
984
+ function cssVarsChrome(shown, toggle) {
985
+ return [
986
+ {
987
+ id: "fg-css-vars-toggle",
988
+ region: "header",
989
+ group: "story",
990
+ item: { icon: (
991
+ /* @icon */
992
+ "ui:typeBraces"
993
+ ), label: "CSS Vars panel", pressed: shown, onActivate: toggle }
994
+ },
995
+ ...shown ? [CSS_VARS_SECTION] : []
996
+ ];
997
+ }
998
+ function CssVarsChrome({ onChrome }) {
999
+ const [shown, setShown] = usePersistedState(RECORD, false, { scope: "lab" });
1000
+ const chrome = useMemo(() => cssVarsChrome(shown, () => setShown((was) => !was)), [shown, setShown]);
1001
+ useEffect(() => onChrome(chrome), [chrome, onChrome]);
1002
+ return null;
1003
+ }
1004
+ var KEYS = { kind: "key", key: "i", mods: { mod: true } };
1005
+ function infoItem(open) {
1006
+ return {
1007
+ icon: (
1008
+ /* @icon */
1009
+ "ui:info"
1010
+ ),
1011
+ label: "Info",
1012
+ shortcut: formatShortcut({ key: KEYS.key, mod: KEYS.mods.mod }),
1013
+ keys: KEYS,
1014
+ onActivate: open
1015
+ };
1016
+ }
923
1017
  var GLOBALS = "$globals";
924
1018
  function leaves(shape, prefix = "") {
925
1019
  const out = [];
@@ -1159,9 +1253,9 @@ function RoutePlayhead() {
1159
1253
  if (!trial || route === null) return null;
1160
1254
  return /* @__PURE__ */ jsx(PlayheadSync, { trialId: trial.id, route, appliedRef: applied }, `${trial.id} ${route}`);
1161
1255
  }
1162
- var RECORD = "fg-globals";
1256
+ var RECORD2 = "fg-globals";
1163
1257
  function useLabGlobals(declarations) {
1164
- const [stored, setStored] = usePersistedState(RECORD, null, { scope: "lab" });
1258
+ const [stored, setStored] = usePersistedState(RECORD2, null, { scope: "lab" });
1165
1259
  const values = useMemo(() => labGlobals(declarations, stored), [declarations, stored]);
1166
1260
  const set = useCallback(
1167
1261
  (key, value) => setStored((prev) => ({ ...labGlobals(declarations, prev), [key]: value })),
@@ -1193,7 +1287,8 @@ function GlobalsToolbar({ declarations }) {
1193
1287
  NestedGlobals,
1194
1288
  {
1195
1289
  label: declaration.label,
1196
- icon: declaration.icon ?? "tune",
1290
+ icon: declaration.icon ?? /* @icon */
1291
+ "core:tune",
1197
1292
  nested,
1198
1293
  values,
1199
1294
  set
@@ -1251,6 +1346,41 @@ function LabGlobals({
1251
1346
  useEffect(() => onChange(resolved), [resolved, onChange]);
1252
1347
  return null;
1253
1348
  }
1349
+ var leafPath = (n) => `globals.d${n}.v`;
1350
+ function globalsMenu(declarations, values, set) {
1351
+ const entries = Object.entries(declarations);
1352
+ if (entries.length === 0) return void 0;
1353
+ const submenus = {};
1354
+ const current = {};
1355
+ const keys = /* @__PURE__ */ new Map();
1356
+ entries.forEach(([key, declaration], n) => {
1357
+ const options = labOptions(declaration, values);
1358
+ submenus[`d${n}`] = pref.group(
1359
+ declaration.label,
1360
+ { v: pref.enum(declaration.label, declaration.default, options) },
1361
+ { as: "menu" }
1362
+ );
1363
+ current[leafPath(n)] = values[key];
1364
+ keys.set(leafPath(n), key);
1365
+ });
1366
+ return {
1367
+ schema: pref.group("", { globals: pref.group("Globals", submenus, { as: "menu" }) }),
1368
+ values: current,
1369
+ onChange: (path, value) => {
1370
+ const key = keys.get(path);
1371
+ if (key !== void 0 && typeof value === "string") set(key, value);
1372
+ }
1373
+ };
1374
+ }
1375
+ function GlobalsMenus({
1376
+ declarations,
1377
+ onMenus
1378
+ }) {
1379
+ const [values, set] = useLabGlobals(declarations);
1380
+ const menus = useMemo(() => globalsMenu(declarations, values, set), [declarations, values, set]);
1381
+ useEffect(() => onMenus(menus), [menus, onMenus]);
1382
+ return null;
1383
+ }
1254
1384
  function KnobSync({ schema, config }) {
1255
1385
  const { setConfig } = useTrialState();
1256
1386
  const reading = useRef(true);
@@ -1427,14 +1557,27 @@ function filterComponents(rows, query) {
1427
1557
  }
1428
1558
  return out;
1429
1559
  }
1560
+ function parentOf(row, byPath) {
1561
+ const segments = row.path.split("/");
1562
+ for (let take = segments.length - 1; take > 0; take--) {
1563
+ const parent = byPath.get(segments.slice(0, take).join("/"));
1564
+ if (parent && parent.library === row.library) return parent;
1565
+ }
1566
+ return void 0;
1567
+ }
1430
1568
  function componentNodes(rows) {
1431
1569
  const root = [];
1432
1570
  const folders = /* @__PURE__ */ new Map();
1571
+ const byPath = new Map(rows.map((row) => [row.path, row]));
1572
+ const nodes = /* @__PURE__ */ new Map();
1573
+ const subs = /* @__PURE__ */ new Map();
1574
+ for (const row of rows) subs.set(row.path, []);
1433
1575
  for (const row of rows) {
1434
- const labels = row.label.split("/");
1576
+ const parent = parentOf(row, byPath);
1435
1577
  const title = row.path.split("/");
1436
- let siblings = root;
1437
- let tag = row.library;
1578
+ const labels = parent ? title.slice(parent.path.split("/").length) : row.label.split("/");
1579
+ let siblings = parent ? subs.get(parent.path) : root;
1580
+ let tag = parent ? void 0 : row.library;
1438
1581
  for (let i = 0; i < labels.length - 1; i++) {
1439
1582
  const path = title.slice(0, title.length - labels.length + i + 1).join("/");
1440
1583
  let folder = folders.get(path);
@@ -1446,14 +1589,22 @@ function componentNodes(rows) {
1446
1589
  siblings = folder.children;
1447
1590
  tag = void 0;
1448
1591
  }
1449
- siblings.push({
1592
+ const node = {
1450
1593
  kind: "folder",
1451
1594
  label: labels[labels.length - 1],
1452
1595
  path: row.path,
1453
1596
  tag,
1454
1597
  ...row.index ? { index: row.index } : {},
1455
- children: row.entries.map((entry) => ({ kind: "story", entry }))
1456
- });
1598
+ children: []
1599
+ };
1600
+ nodes.set(row.path, node);
1601
+ siblings.push(node);
1602
+ }
1603
+ for (const row of rows) {
1604
+ nodes.get(row.path).children = [
1605
+ ...subs.get(row.path),
1606
+ ...row.entries.map((entry) => ({ kind: "story", entry }))
1607
+ ];
1457
1608
  }
1458
1609
  return foldGalleries(root);
1459
1610
  }
@@ -2303,26 +2454,21 @@ function RouteOpener() {
2303
2454
  }, [route, lab]);
2304
2455
  return null;
2305
2456
  }
2306
- function useInfoShortcut(open) {
2457
+ function useDocumentTitle(title) {
2307
2458
  useEffect(() => {
2308
- const onKeyDown = (event) => {
2309
- if (event.key !== "i" && event.key !== "I") return;
2310
- if (!(event.metaKey || event.ctrlKey) || event.altKey || event.shiftKey) return;
2311
- const target = event.target;
2312
- if (target?.isContentEditable || /^(input|textarea|select)$/i.test(target?.tagName ?? "")) return;
2313
- event.preventDefault();
2314
- open(true);
2315
- };
2316
- window.addEventListener("keydown", onKeyDown);
2317
- return () => window.removeEventListener("keydown", onKeyDown);
2318
- }, [open]);
2459
+ document.title = title;
2460
+ }, [title]);
2461
+ }
2462
+ function NoStories({ stories }) {
2463
+ return /* @__PURE__ */ jsxs("div", { className: "fg-empty", children: [
2464
+ /* @__PURE__ */ jsx("p", { className: "fg-empty__title", children: "No stories found" }),
2465
+ stories.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "fg-empty__globs", "aria-label": "Story globs", children: stories.map((glob) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: glob }) }, glob)) }) : null
2466
+ ] });
2319
2467
  }
2320
2468
  function Workshop({ index, frameUrl, importers, setup, changes, config, stories = [], storageKey, storage, dependencies }) {
2321
2469
  const declarations = config?.globals ?? NO_DECLARATIONS;
2322
2470
  const title = config?.title ?? DEFAULT_TITLE;
2323
- useEffect(() => {
2324
- document.title = title;
2325
- }, [title]);
2471
+ useDocumentTitle(title);
2326
2472
  const [frames] = useState(createTrialFrames);
2327
2473
  const [clocks] = useState(createTrialClocks);
2328
2474
  const [pool, setPool] = useState(null);
@@ -2358,33 +2504,27 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
2358
2504
  const themeFor = config?.labTheme;
2359
2505
  const labTheme = useMemo(() => themeFor?.(labValues), [themeFor, labValues]);
2360
2506
  const [infoOpen, setInfoOpen] = useState(false);
2361
- useInfoShortcut(setInfoOpen);
2362
2507
  const reportLabValues = useCallback(
2363
2508
  (next) => setLabValues((prev) => stableStringify(prev) === stableStringify(next) ? prev : next),
2364
2509
  []
2365
2510
  );
2366
- const infoCommand = useMemo(
2367
- () => ({ icon: InfoIcon, label: "Info", shortcut: "\u2318I", onActivate: () => setInfoOpen(true) }),
2368
- []
2369
- );
2511
+ const infoCommand = useMemo(() => infoItem(() => setInfoOpen(true)), []);
2512
+ const showsCssVars = config?.cssVars !== false;
2513
+ const [cssVarsChrome2, setCssVarsChrome] = useState([]);
2370
2514
  const labChrome = useMemo(
2371
2515
  () => [
2372
2516
  { id: "fg-stories", region: "sidebar", render: (ctx) => /* @__PURE__ */ jsx(StoryTree, { ctx, index }) },
2373
- ...config?.cssVars === false ? [] : [CSS_VARS_SECTION],
2517
+ ...showsCssVars ? cssVarsChrome2 : [],
2374
2518
  A11Y_SECTION,
2375
2519
  ...Object.keys(declarations).length > 0 ? [{ id: "fg-globals", region: "header", render: () => /* @__PURE__ */ jsx(GlobalsToolbar, { declarations }) }] : [],
2376
2520
  { id: "fg-info", region: "header", group: "story", item: infoCommand },
2377
2521
  ...config?.labChrome ?? []
2378
2522
  ],
2379
- [index, infoCommand, declarations, config?.cssVars, config?.labChrome]
2523
+ [index, infoCommand, declarations, showsCssVars, cssVarsChrome2, config?.labChrome]
2380
2524
  );
2525
+ const [menus, setMenus] = useState();
2381
2526
  const first = index[0];
2382
- if (!first) {
2383
- return /* @__PURE__ */ jsxs("div", { className: "fg-empty", children: [
2384
- /* @__PURE__ */ jsx("p", { className: "fg-empty__title", children: "No stories found" }),
2385
- stories.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "fg-empty__globs", "aria-label": "Story globs", children: stories.map((glob) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: glob }) }, glob)) }) : null
2386
- ] });
2387
- }
2527
+ if (!first) return /* @__PURE__ */ jsx(NoStories, { stories });
2388
2528
  return /* @__PURE__ */ jsx(LibrariesContext.Provider, { value: libraries, children: /* @__PURE__ */ jsx(StoryGlobalsContext.Provider, { value: labValues, children: /* @__PURE__ */ jsx(DependenciesContext.Provider, { value: dependencies ?? null, children: /* @__PURE__ */ jsx(FramePoolContext.Provider, { value: pool, children: /* @__PURE__ */ jsx(TrialFramesContext.Provider, { value: frames, children: /* @__PURE__ */ jsx(TrialClocksContext.Provider, { value: clocks, children: /* @__PURE__ */ jsxs(
2389
2529
  Lab,
2390
2530
  {
@@ -2397,6 +2537,7 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
2397
2537
  ...storage ? { storage } : {},
2398
2538
  labChrome,
2399
2539
  addTrial: false,
2540
+ ...menus ? { menus } : {},
2400
2541
  ...config?.controls ? { controls: config.controls } : {},
2401
2542
  ...config?.pages ? { pages: config.pages } : {},
2402
2543
  ...config?.path !== void 0 ? { path: config.path } : {},
@@ -2413,7 +2554,9 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
2413
2554
  onOpenChange: setInfoOpen
2414
2555
  }
2415
2556
  ),
2416
- /* @__PURE__ */ jsx(LabGlobals, { declarations, onChange: reportLabValues })
2557
+ /* @__PURE__ */ jsx(LabGlobals, { declarations, onChange: reportLabValues }),
2558
+ /* @__PURE__ */ jsx(GlobalsMenus, { declarations, onMenus: setMenus }),
2559
+ showsCssVars ? /* @__PURE__ */ jsx(CssVarsChrome, { onChrome: setCssVarsChrome }) : null
2417
2560
  ]
2418
2561
  }
2419
2562
  ) }) }) }) }) }) });
@@ -2421,7 +2564,7 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
2421
2564
  function mountWorkshop({ container, ...props }) {
2422
2565
  const target = container ?? document.getElementById("root");
2423
2566
  if (!target) throw new Error("mountWorkshop: no container given and no #root element");
2424
- const root = createRoot(target);
2567
+ const root = createKitRoot(target, props.setup?.icons);
2425
2568
  const changes = createStoryChanges();
2426
2569
  let current = { ...props, changes };
2427
2570
  root.render(/* @__PURE__ */ jsx(Workshop, { ...current }));