@weasel-js/forge 1.9.1 → 1.9.3

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.
@@ -1,9 +1,9 @@
1
1
  import { parseRoute, paramsToKnobs, knobPaths, sameKnob, knobsToParams, formatRoute, PLAYHEAD_PARAM, reservedParams } from '../chunk-CJX3W5HY.js';
2
2
  import { isIndexId, indexId, libraryOf, indexEntries, schemaFromDescription, LibraryBadge, openChannel, IndexPage } from '../chunk-JVRHBI3V.js';
3
3
  import { isGallery } from '../chunk-WEQSLPKT.js';
4
- import { useTrialFrame, createTrialFrames, createTrialClocks, StoryGlobalsContext, TrialFramesContext, TrialClocksContext, StoryTrial, useCssOverrides, useNearViewport, IN_VIEW_MARGIN, useTrialClock, TrialHost } from '../chunk-QN3HJQXZ.js';
5
- export { StoryGlobalsContext, StoryTrial, TrialFramesContext, TrialHost, useTrialFrame } from '../chunk-QN3HJQXZ.js';
6
- import { labGlobals, withGlobals, followApp, storyConfig, GLOBALS_KEY, effectiveGlobals, PLAYHEAD_KEY, labOptions, isGlobalsPath } from '../chunk-NI5FYQHW.js';
4
+ import { useTrialFrame, createTrialFrames, createTrialClocks, StoryGlobalsContext, TrialFramesContext, TrialClocksContext, StoryTrial, useCssOverrides, useNearViewport, IN_VIEW_MARGIN, useTrialClock, TrialHost } from '../chunk-BGOHALHO.js';
5
+ export { StoryGlobalsContext, StoryTrial, TrialFramesContext, TrialHost, useTrialFrame } from '../chunk-BGOHALHO.js';
6
+ import { labGlobals, withGlobals, followApp, storyConfig, GLOBALS_KEY, effectiveGlobals, PLAYHEAD_KEY, labOptions, isGlobalsPath } from '../chunk-GZPQMGXJ.js';
7
7
  import { loadStories, indexRenderOf } from '../chunk-Q2EZXJFE.js';
8
8
  import '../chunk-L5XM5VKB.js';
9
9
  import '../chunk-HRGPFQBW.js';
@@ -11,10 +11,10 @@ import '../chunk-MGQWCPYE.js';
11
11
  import { stableStringify, PORT_HANDOFF, FRAME_HELLO, PROTOCOL_VERSION } from '../chunk-KJGXJD3U.js';
12
12
  import { useTrialId, Button, Lab, useLabContext, TrialIdContext, usePersistedState, Select, useTrialState, inferTokenType, ToggleBar as ToggleBar$1, Input, TokenPanel, refitScale } from '@weasel-js/labkit';
13
13
  import num from '@weasel-js/theme/numeric.module.css';
14
- import { createContext, useState, useRef, useEffect, useCallback, useMemo, useLayoutEffect, useId, useContext, useSyncExternalStore } from 'react';
14
+ import { createContext, useState, useRef, useEffect, useCallback, useMemo, useLayoutEffect, useId, useContext, Fragment as Fragment$1, useSyncExternalStore } from 'react';
15
15
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
16
16
  import { createRoot } from 'react-dom/client';
17
- import { InfoIcon, ToggleBar, Checkbox, Dialog, DisclosureMark, Button as Button$1, TuneIcon, Callout, Slider, Transport } from '@weasel-js/ui';
17
+ import { InfoIcon, ToggleBar, Checkbox, Dialog, TOKEN_CATEGORIES, Icon, DisclosureMark, Button as Button$1, Callout, Slider, Transport, SearchIcon } from '@weasel-js/ui';
18
18
  import { TOKEN_MANIFEST, THEME_SOURCES, isByAxis } from '@weasel-js/theme';
19
19
  import { scale, httpThemeApi } from '@weasel-js/theme/engine';
20
20
  import { f, valueAtPath, isConfigBranch } from '@weasel-js/labkit/config';
@@ -100,14 +100,14 @@ var A11Y_SECTION = {
100
100
 
101
101
  // src/shell/storyChanges.ts
102
102
  function createStoryChanges() {
103
- const listeners = /* @__PURE__ */ new Set();
103
+ const listeners2 = /* @__PURE__ */ new Set();
104
104
  return {
105
105
  subscribe(listener) {
106
- listeners.add(listener);
107
- return () => listeners.delete(listener);
106
+ listeners2.add(listener);
107
+ return () => listeners2.delete(listener);
108
108
  },
109
109
  emit(file) {
110
- for (const listener of [...listeners]) listener(file);
110
+ for (const listener of [...listeners2]) listener(file);
111
111
  }
112
112
  };
113
113
  }
@@ -221,6 +221,79 @@ function applyScaleEdits(definition, edits, selection) {
221
221
  for (const [holder, byKey] of writes) for (const [key, value] of byKey) holder[key] = value;
222
222
  return { ok: true, definition: root };
223
223
  }
224
+ var ICONS = {
225
+ color: "drop",
226
+ type: "type",
227
+ size: "measure",
228
+ motion: "curveEaseInOut",
229
+ depth: "layers",
230
+ other: "more"
231
+ };
232
+ var PANEL = ".fg-css-vars";
233
+ var HOST = ".lk-sidebar-section, .lk-panel-tile";
234
+ var MANUAL = ["wheel", "touchmove", "keydown"];
235
+ function scrollerOf(el) {
236
+ for (let at = el.parentElement; at; at = at.parentElement) {
237
+ const { overflowY } = getComputedStyle(at);
238
+ if ((overflowY === "auto" || overflowY === "scroll") && at.scrollHeight > at.clientHeight) return at;
239
+ }
240
+ return null;
241
+ }
242
+ function readLine(panel) {
243
+ return panel.querySelector(".fg-css-vars__controls")?.getBoundingClientRect().bottom ?? panel.getBoundingClientRect().top;
244
+ }
245
+ function sectionsOf(panel) {
246
+ return [...panel.querySelectorAll("[data-token-category]")];
247
+ }
248
+ function SectionJump() {
249
+ const ref = useRef(null);
250
+ const [present, setPresent] = useState([]);
251
+ const [current, setCurrent] = useState(null);
252
+ const held = useRef(false);
253
+ useEffect(() => {
254
+ const root = ref.current?.closest(HOST) ?? document.body;
255
+ const read = () => {
256
+ const panel = root.querySelector(PANEL);
257
+ const sections = panel ? sectionsOf(panel) : [];
258
+ setPresent(sections.map((s) => s.dataset.tokenCategory));
259
+ if (!panel || held.current) return;
260
+ const line = readLine(panel) + 1;
261
+ let top = sections[0]?.dataset.tokenCategory;
262
+ for (const s of sections) if (s.getBoundingClientRect().top <= line) top = s.dataset.tokenCategory;
263
+ setCurrent(top ?? null);
264
+ };
265
+ const release = () => {
266
+ held.current = false;
267
+ };
268
+ read();
269
+ const observer = new MutationObserver(read);
270
+ observer.observe(root, { childList: true, subtree: true });
271
+ document.addEventListener("scroll", read, { capture: true, passive: true });
272
+ for (const type of MANUAL) document.addEventListener(type, release, { capture: true, passive: true });
273
+ return () => {
274
+ observer.disconnect();
275
+ document.removeEventListener("scroll", read, { capture: true });
276
+ for (const type of MANUAL) document.removeEventListener(type, release, { capture: true });
277
+ };
278
+ }, []);
279
+ const items = TOKEN_CATEGORIES.map(({ id, title }) => ({
280
+ value: id,
281
+ label: /* @__PURE__ */ jsx(Icon, { name: ICONS[id], size: 14 }),
282
+ ariaLabel: title,
283
+ tooltip: title,
284
+ disabled: !present.includes(id)
285
+ }));
286
+ const jump = (next) => {
287
+ const panel = ref.current?.closest(HOST)?.querySelector(PANEL);
288
+ const target = next && panel?.querySelector(`[data-token-category="${next}"]`);
289
+ const scroller = panel && scrollerOf(panel);
290
+ if (!panel || !target || !scroller) return;
291
+ held.current = true;
292
+ setCurrent(next);
293
+ scroller.scrollTop += target.getBoundingClientRect().top - readLine(panel);
294
+ };
295
+ return /* @__PURE__ */ jsx("div", { ref, className: "fg-css-vars__jump", children: /* @__PURE__ */ jsx(ToggleBar, { ariaLabel: "Jump to section", variant: "flat", size: "sm", items, value: current, onChange: jump }) });
296
+ }
224
297
  var TABS = [
225
298
  { value: "theme", label: "Theme" },
226
299
  { value: "story", label: "Story" }
@@ -391,19 +464,32 @@ function CssVarsPanel({ globals = NO_GLOBALS, themeApi } = {}) {
391
464
  );
392
465
  return /* @__PURE__ */ jsxs("div", { className: "fg-css-vars", children: [
393
466
  /* @__PURE__ */ jsxs("div", { className: "fg-css-vars__controls", children: [
394
- /* @__PURE__ */ jsx(
395
- ToggleBar$1,
396
- {
397
- ariaLabel: "Variables",
398
- variant: "flat",
399
- items: TABS,
400
- value: tab,
401
- onChange: (next) => {
402
- if (next) setTab(next);
467
+ /* @__PURE__ */ jsxs("div", { className: "fg-css-vars__bar", children: [
468
+ /* @__PURE__ */ jsx(
469
+ ToggleBar$1,
470
+ {
471
+ ariaLabel: "Variables",
472
+ variant: "flat",
473
+ size: "sm",
474
+ items: TABS,
475
+ value: tab,
476
+ onChange: (next) => {
477
+ if (next) setTab(next);
478
+ }
403
479
  }
404
- }
405
- ),
406
- /* @__PURE__ */ jsx(Input, { "aria-label": "Filter", placeholder: "Filter by name or value", value: filter, onChange: setFilter }),
480
+ ),
481
+ /* @__PURE__ */ jsx(
482
+ Input,
483
+ {
484
+ className: "fg-css-vars__filter",
485
+ "aria-label": "Filter",
486
+ placeholder: "Filter",
487
+ value: filter,
488
+ onChange: setFilter,
489
+ leadingAdornment: /* @__PURE__ */ jsx(SearchIcon, { size: 12 })
490
+ }
491
+ )
492
+ ] }),
407
493
  tab === "theme" && (unsaved.length > 0 || notice) ? /* @__PURE__ */ jsxs("div", { className: "fg-css-vars__save", children: [
408
494
  unsaved.length > 0 ? /* @__PURE__ */ jsx(Button$1, { variant: "primary", size: "sm", loading: saving, disabled: saving, onClick: () => void save(), children: "Save scales to theme" }) : null,
409
495
  notice ? /* @__PURE__ */ jsx(
@@ -420,6 +506,7 @@ function CssVarsPanel({ globals = NO_GLOBALS, themeApi } = {}) {
420
506
  TokenPanel,
421
507
  {
422
508
  tokens: shown,
509
+ namePrefix: "--wzl-",
423
510
  onChange: write,
424
511
  scales,
425
512
  onScaleChange,
@@ -444,13 +531,18 @@ function FocusedCssVars() {
444
531
  var CSS_VARS_SECTION = {
445
532
  id: "fg-css-vars",
446
533
  region: "aside",
447
- item: { title: "CSS Vars", body: /* @__PURE__ */ jsx(FocusedCssVars, {}) }
534
+ item: { title: "CSS Vars", actions: /* @__PURE__ */ jsx(SectionJump, {}), body: /* @__PURE__ */ jsx(FocusedCssVars, {}) }
448
535
  };
449
536
 
450
537
  // src/shell/breadcrumb.ts
538
+ function crumbs(title, name) {
539
+ const segments = title.split("/");
540
+ const steps = segments.map((label, i) => ({ label, path: segments.slice(0, i + 1).join("/") }));
541
+ steps.push({ label: name, path: null });
542
+ return steps.filter((step, i) => i === 0 || step.label.toLowerCase() !== "index");
543
+ }
451
544
  function breadcrumb(title, name) {
452
- const segments = [...title.split("/"), name].filter((segment, i) => i === 0 || segment.toLowerCase() !== "index");
453
- return segments.join(" > ");
545
+ return crumbs(title, name).map((step) => step.label).join(" > ");
454
546
  }
455
547
  var GLOBALS = "$globals";
456
548
  function leaves(shape, prefix = "") {
@@ -771,12 +863,23 @@ function GlobalsToolbar({ declarations }) {
771
863
  top.map(([key, declaration]) => /* @__PURE__ */ jsx("div", { className: "fg-globals__item", children: /* @__PURE__ */ jsx(GlobalSelect, { name: key, declaration, values, set }) }, key)),
772
864
  top.map(([key, declaration]) => {
773
865
  const nested = entries.filter(([, other]) => other.under === key);
774
- return nested.length > 0 ? /* @__PURE__ */ jsx(NestedGlobals, { label: declaration.label, nested, values, set }, key) : null;
866
+ return nested.length > 0 ? /* @__PURE__ */ jsx(
867
+ NestedGlobals,
868
+ {
869
+ label: declaration.label,
870
+ icon: declaration.icon ?? "tune",
871
+ nested,
872
+ values,
873
+ set
874
+ },
875
+ key
876
+ ) : null;
775
877
  })
776
878
  ] });
777
879
  }
778
880
  function NestedGlobals({
779
881
  label,
882
+ icon,
780
883
  nested,
781
884
  values,
782
885
  set
@@ -794,7 +897,7 @@ function NestedGlobals({
794
897
  ariaLabel: `More ${label} settings`,
795
898
  pressed: open,
796
899
  onClick: () => setOpen((was) => !was),
797
- children: /* @__PURE__ */ jsx(TuneIcon, { size: 16 })
900
+ children: /* @__PURE__ */ jsx(Icon, { name: icon, size: 16 })
798
901
  }
799
902
  ),
800
903
  /* @__PURE__ */ jsx(
@@ -884,6 +987,18 @@ function revealTrial(trialId, root = document) {
884
987
  return true;
885
988
  }
886
989
 
990
+ // src/shell/treeReveal.ts
991
+ var listeners = /* @__PURE__ */ new Set();
992
+ function revealInTree(path) {
993
+ for (const listener of listeners) listener(path);
994
+ }
995
+ function onRevealInTree(listener) {
996
+ listeners.add(listener);
997
+ return () => {
998
+ listeners.delete(listener);
999
+ };
1000
+ }
1001
+
887
1002
  // src/shell/tree/buildTree.ts
888
1003
  function sortFolder(nodes) {
889
1004
  const folders = nodes.filter((n) => n.kind === "folder");
@@ -1124,6 +1239,43 @@ function StoryTree({ ctx, index }) {
1124
1239
  }
1125
1240
  activate(node.index, another);
1126
1241
  };
1242
+ const scrollTo = useRef(null);
1243
+ const reveal = (path) => {
1244
+ const find = (nodes, at) => {
1245
+ for (const node2 of nodes) {
1246
+ if (node2.kind !== "folder") continue;
1247
+ if (node2.path === at) return node2;
1248
+ const inner = find(node2.children, at);
1249
+ if (inner) return inner;
1250
+ }
1251
+ return void 0;
1252
+ };
1253
+ const nodesOf = (v) => v === "tree" ? tree : componentNodes(v === "gallery" ? galleries : components);
1254
+ const order = [view, ...VIEWS.map((item) => item.value).filter((v) => v !== view && enabled(v))];
1255
+ let node;
1256
+ let where = view;
1257
+ for (let at = path; at && !node; at = at.includes("/") ? at.slice(0, at.lastIndexOf("/")) : "") {
1258
+ for (const v of order) {
1259
+ node = find(nodesOf(v), at);
1260
+ if (node) {
1261
+ where = v;
1262
+ break;
1263
+ }
1264
+ }
1265
+ }
1266
+ const found = node;
1267
+ if (!found) return;
1268
+ if (where !== view) setView(where);
1269
+ setQuery("");
1270
+ const segments = found.path.split("/");
1271
+ const above = segments.slice(0, -1).map((_, i) => segments.slice(0, i + 1).join("/"));
1272
+ setFolds((prev) => ({ ...prev, ...Object.fromEntries(above.map((p) => [p, true])) }));
1273
+ setFocusKey(keyOf(found));
1274
+ scrollTo.current = keyOf(found);
1275
+ if (found.index) openFolder(found, false);
1276
+ else setFolds((prev) => ({ ...prev, [found.path]: true }));
1277
+ };
1278
+ useEffect(() => onRevealInTree(reveal));
1127
1279
  const onKeyDown = (event) => {
1128
1280
  const at = rows.findIndex((row2) => row2.key === active);
1129
1281
  const row = rows[at];
@@ -1170,6 +1322,10 @@ function StoryTree({ ctx, index }) {
1170
1322
  ref: (el) => {
1171
1323
  if (el) items.current.set(key, el);
1172
1324
  else items.current.delete(key);
1325
+ if (el && scrollTo.current === key) {
1326
+ scrollTo.current = null;
1327
+ el.scrollIntoView({ block: "nearest" });
1328
+ }
1173
1329
  },
1174
1330
  // A folder item contains its children, so their focus bubbles through it.
1175
1331
  onFocus: (event) => {
@@ -1254,6 +1410,7 @@ function StoryTree({ ctx, index }) {
1254
1410
  {
1255
1411
  ariaLabel: "Sidebar view",
1256
1412
  variant: "flat",
1413
+ size: "sm",
1257
1414
  items: views,
1258
1415
  value: view,
1259
1416
  onChange: (next) => {
@@ -1268,7 +1425,7 @@ function StoryTree({ ctx, index }) {
1268
1425
  {
1269
1426
  isSelected: !hidden[library],
1270
1427
  onChange: (on) => setHidden((prev) => ({ ...prev, [library]: !on })),
1271
- children: /* @__PURE__ */ jsx(LibraryBadge2, { library })
1428
+ children: /* @__PURE__ */ jsx("span", { className: "fg-tree__library", children: /* @__PURE__ */ jsx(LibraryBadge2, { library }) })
1272
1429
  },
1273
1430
  library
1274
1431
  ))
@@ -1292,7 +1449,7 @@ var KEPT_CONFIGS = 50;
1292
1449
  var UNANSWERED = stableStringify({ hidden: [], errors: {} });
1293
1450
  function createAnswerBook() {
1294
1451
  const byConfig = /* @__PURE__ */ new Map();
1295
- const listeners = /* @__PURE__ */ new Set();
1452
+ const listeners2 = /* @__PURE__ */ new Set();
1296
1453
  const held = /* @__PURE__ */ new Map();
1297
1454
  const keyOf2 = (config) => stableStringify(storyConfig(config));
1298
1455
  const answerFor = (config) => byConfig.get(keyOf2(config));
@@ -1306,7 +1463,7 @@ function createAnswerBook() {
1306
1463
  const oldest = byConfig.keys().next().value;
1307
1464
  if (oldest !== void 0) byConfig.delete(oldest);
1308
1465
  }
1309
- if (changed && held.has(answers.configKey)) for (const fn of listeners) fn();
1466
+ if (changed && held.has(answers.configKey)) for (const fn of listeners2) fn();
1310
1467
  },
1311
1468
  hold(config) {
1312
1469
  const key = keyOf2(config);
@@ -1323,9 +1480,9 @@ function createAnswerBook() {
1323
1480
  },
1324
1481
  errors: (path, config) => answerFor(config)?.errors[path] ?? [],
1325
1482
  subscribe(fn) {
1326
- listeners.add(fn);
1483
+ listeners2.add(fn);
1327
1484
  return () => {
1328
- listeners.delete(fn);
1485
+ listeners2.delete(fn);
1329
1486
  };
1330
1487
  }
1331
1488
  };
@@ -1368,6 +1525,27 @@ function TrialTransport({ trialId }) {
1368
1525
  const clock = useTrialClock(trialId);
1369
1526
  return clock ? /* @__PURE__ */ jsx(TransportBar, { clock }) : null;
1370
1527
  }
1528
+ function Crumbs({ entry }) {
1529
+ const steps = crumbs(entry.title, entry.name);
1530
+ return /* @__PURE__ */ jsx("span", { className: "fg-crumbs", children: steps.map((step, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
1531
+ i > 0 ? /* @__PURE__ */ jsx("span", { className: "fg-crumbs__sep", "aria-hidden": "true", children: "\u203A" }) : null,
1532
+ step.path === null ? /* @__PURE__ */ jsx("span", { className: "fg-crumbs__here", children: step.label }) : /* @__PURE__ */ jsx(
1533
+ "button",
1534
+ {
1535
+ type: "button",
1536
+ className: "fg-crumbs__step",
1537
+ title: `Show ${step.label} in the story tree`,
1538
+ onPointerDown: (event) => event.stopPropagation(),
1539
+ onClick: () => revealInTree(step.path),
1540
+ children: step.label
1541
+ }
1542
+ )
1543
+ ] }, step.path ?? `${step.label}:${i}`)) });
1544
+ }
1545
+ function crumbTitle(entry) {
1546
+ const title = breadcrumb(entry.title, entry.name);
1547
+ return { title, renderTitle: (shown) => shown === title ? /* @__PURE__ */ jsx(Crumbs, { entry }) : shown };
1548
+ }
1371
1549
  function IndexTrial({ bundle, setup, config }) {
1372
1550
  const lab = useLabContext();
1373
1551
  const trialId = useContext(TrialIdContext);
@@ -1410,7 +1588,7 @@ function documentInstrument(options) {
1410
1588
  const initialState = loaded.kind === "story" ? loaded.story.initialState : null;
1411
1589
  return {
1412
1590
  name: entry.id,
1413
- title: breadcrumb(entry.title, entry.name),
1591
+ ...crumbTitle(entry),
1414
1592
  config,
1415
1593
  defaultConfig: () => config.defaults(),
1416
1594
  initialState: (c) => initialState?.(c) ?? null,
@@ -1433,7 +1611,7 @@ function pendingInstrument(options) {
1433
1611
  const config = withGlobals(f.schema({}), globals);
1434
1612
  return {
1435
1613
  name: entry.id,
1436
- title: breadcrumb(entry.title, entry.name),
1614
+ ...crumbTitle(entry),
1437
1615
  config,
1438
1616
  defaultConfig: () => config.defaults(),
1439
1617
  initialState: () => null,
@@ -1711,7 +1889,7 @@ function storyInstrument(options) {
1711
1889
  const viewport = ready ? ready.viewport : entry.viewport;
1712
1890
  return {
1713
1891
  name: entry.id,
1714
- title: breadcrumb(entry.title, entry.name),
1892
+ ...crumbTitle(entry),
1715
1893
  config,
1716
1894
  defaultConfig: () => config.defaults(),
1717
1895
  initialState: () => null,