@weasel-js/forge 1.9.4 → 1.10.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 +12 -4
- package/dist/{FrameController-BQjKhdwk.d.ts → FrameController-UeTY7ubF.d.ts} +25 -1
- package/dist/{chunk-B3PPHJTS.js → chunk-4NM3RTYK.js} +14 -14
- package/dist/chunk-4NM3RTYK.js.map +1 -0
- package/dist/{chunk-JHL4ERDA.js → chunk-DFKCLZKS.js} +3 -3
- package/dist/chunk-DFKCLZKS.js.map +1 -0
- package/dist/{chunk-FRUA5WJF.js → chunk-DKWHGSBZ.js} +3 -3
- package/dist/{chunk-FRUA5WJF.js.map → chunk-DKWHGSBZ.js.map} +1 -1
- package/dist/{chunk-ZZCCIXEJ.js → chunk-ENXU34PI.js} +16 -25
- package/dist/chunk-ENXU34PI.js.map +1 -0
- package/dist/{chunk-TXLWQFF6.js → chunk-FL2MUPUL.js} +2 -2
- package/dist/chunk-FL2MUPUL.js.map +1 -0
- package/dist/{chunk-5KI5YSQ5.js → chunk-GB6PRJET.js} +3 -3
- package/dist/{chunk-5KI5YSQ5.js.map → chunk-GB6PRJET.js.map} +1 -1
- package/dist/{chunk-MQSFPRF6.js → chunk-V4RI7GWD.js} +37 -19
- package/dist/chunk-V4RI7GWD.js.map +1 -0
- package/dist/{chunk-HRGPFQBW.js → chunk-ZAWXMUGZ.js} +6 -3
- package/dist/chunk-ZAWXMUGZ.js.map +1 -0
- package/dist/{config-Dfu-NKxd.d.ts → config-Cz7ZyGF-.d.ts} +5 -5
- package/dist/csf/preview-api.d.ts +5 -2
- package/dist/csf/preview-api.js +2 -2
- package/dist/csf/preview-api.js.map +1 -1
- package/dist/frame/index.css +19 -2
- package/dist/frame/index.css.map +1 -1
- package/dist/frame/index.d.ts +2 -2
- package/dist/frame/index.js +5 -5
- package/dist/frame/index.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/shell/index.css +36 -5
- package/dist/shell/index.css.map +1 -1
- package/dist/shell/index.d.ts +7 -6
- package/dist/shell/index.js +398 -274
- package/dist/shell/index.js.map +1 -1
- package/dist/test/index.d.ts +1 -1
- package/dist/test/index.js +6 -6
- package/dist/test/play.d.ts +3 -5
- package/dist/test/play.js.map +1 -1
- package/dist/vite/index.d.ts +16 -1
- package/dist/vite/index.js +403 -78
- package/dist/vite/index.js.map +1 -1
- package/package.json +10 -5
- package/dist/chunk-B3PPHJTS.js.map +0 -1
- package/dist/chunk-HRGPFQBW.js.map +0 -1
- package/dist/chunk-JHL4ERDA.js.map +0 -1
- package/dist/chunk-MQSFPRF6.js.map +0 -1
- package/dist/chunk-TXLWQFF6.js.map +0 -1
- package/dist/chunk-ZZCCIXEJ.js.map +0 -1
package/dist/shell/index.js
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
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-DKWHGSBZ.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, schemaFromDescription, LibraryBadge, openChannel, IndexPage } from '../chunk-V4RI7GWD.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-ENXU34PI.js';
|
|
6
|
+
export { StoryGlobalsContext, StoryTrial, TrialFramesContext, TrialHost, useTrialFrame } from '../chunk-ENXU34PI.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-4NM3RTYK.js';
|
|
9
9
|
import '../chunk-L5XM5VKB.js';
|
|
10
|
-
import '../chunk-
|
|
11
|
-
import '../chunk-
|
|
12
|
-
import { stableStringify, PORT_HANDOFF, FRAME_HELLO, PROTOCOL_VERSION } from '../chunk-
|
|
10
|
+
import '../chunk-ZAWXMUGZ.js';
|
|
11
|
+
import '../chunk-GB6PRJET.js';
|
|
12
|
+
import { stableStringify, PORT_HANDOFF, FRAME_HELLO, PROTOCOL_VERSION } from '../chunk-FL2MUPUL.js';
|
|
13
13
|
import { useTrialId, Button, Lab, useLabContext, TrialIdContext, usePersistedState, Select, useTrialState, inferTokenType, ToggleBar as ToggleBar$1, Input, TokenPanel, 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
17
|
import { createRoot } from 'react-dom/client';
|
|
18
|
+
import { createEmitter } from '@weasel-js/registry';
|
|
18
19
|
import { f, valueAtPath, isConfigBranch } from '@weasel-js/labkit/config';
|
|
19
20
|
import { InfoIcon, ToggleBar, Checkbox, Dialog, TOKEN_CATEGORIES, Icon, DisclosureMark, Button as Button$1, Callout, Slider, Transport, SearchIcon } from '@weasel-js/ui';
|
|
20
21
|
import { TOKEN_MANIFEST, THEME_SOURCES, isByAxis } from '@weasel-js/theme';
|
|
21
22
|
import { scale, httpThemeApi } from '@weasel-js/theme/engine';
|
|
22
|
-
import { useLatest } from '@weasel-js/core';
|
|
23
|
+
import { useLatest, historyKey, createHistory, swapOp, isPlainObject } from '@weasel-js/core';
|
|
23
24
|
|
|
24
25
|
function Finding({ finding }) {
|
|
25
26
|
return /* @__PURE__ */ jsxs("li", { className: "fg-a11y__finding", children: [
|
|
@@ -100,19 +101,9 @@ var A11Y_SECTION = {
|
|
|
100
101
|
region: "aside",
|
|
101
102
|
item: { title: "Accessibility", body: /* @__PURE__ */ jsx(FocusedA11y, {}) }
|
|
102
103
|
};
|
|
103
|
-
|
|
104
|
-
// src/shell/storyChanges.ts
|
|
105
104
|
function createStoryChanges() {
|
|
106
|
-
const
|
|
107
|
-
return {
|
|
108
|
-
subscribe(listener) {
|
|
109
|
-
listeners2.add(listener);
|
|
110
|
-
return () => listeners2.delete(listener);
|
|
111
|
-
},
|
|
112
|
-
emit(file) {
|
|
113
|
-
for (const listener of [...listeners2]) listener(file);
|
|
114
|
-
}
|
|
115
|
-
};
|
|
105
|
+
const changes = createEmitter();
|
|
106
|
+
return { subscribe: changes.subscribe, emit: changes.emit };
|
|
116
107
|
}
|
|
117
108
|
var DependenciesContext = createContext(null);
|
|
118
109
|
function readRoute() {
|
|
@@ -233,17 +224,12 @@ function crumbs(title, name) {
|
|
|
233
224
|
function breadcrumb(title, name) {
|
|
234
225
|
return crumbs(title, name).map((step) => step.label).join(" > ");
|
|
235
226
|
}
|
|
236
|
-
|
|
237
|
-
// src/shell/treeReveal.ts
|
|
238
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
227
|
+
var reveals = createEmitter();
|
|
239
228
|
function revealInTree(path) {
|
|
240
|
-
|
|
229
|
+
reveals.emit(path);
|
|
241
230
|
}
|
|
242
231
|
function onRevealInTree(listener) {
|
|
243
|
-
|
|
244
|
-
return () => {
|
|
245
|
-
listeners.delete(listener);
|
|
246
|
-
};
|
|
232
|
+
return reveals.subscribe(listener);
|
|
247
233
|
}
|
|
248
234
|
function Crumbs({ entry }) {
|
|
249
235
|
const steps = crumbs(entry.title, entry.name);
|
|
@@ -352,6 +338,73 @@ function missingInstrument(id, globals = {}) {
|
|
|
352
338
|
render: () => /* @__PURE__ */ jsx("div", { className: "fg-story fg-story--loading", "data-fg-layout": "fullscreen", children: /* @__PURE__ */ jsx(Fault, { phase: "route", message: `No story ${id}` }) })
|
|
353
339
|
};
|
|
354
340
|
}
|
|
341
|
+
var ICONS = {
|
|
342
|
+
color: "drop",
|
|
343
|
+
type: "type",
|
|
344
|
+
size: "measure",
|
|
345
|
+
motion: "curveEaseInOut",
|
|
346
|
+
depth: "layers",
|
|
347
|
+
other: "more"
|
|
348
|
+
};
|
|
349
|
+
var PANEL = ".fg-css-vars";
|
|
350
|
+
var HOST = ".lk-sidebar-section, .lk-panel-tile";
|
|
351
|
+
var MANUAL = ["wheel", "touchmove", "keydown"];
|
|
352
|
+
var LIST = ".fg-css-vars__list";
|
|
353
|
+
function readLine(panel) {
|
|
354
|
+
return (panel.querySelector(LIST) ?? panel).getBoundingClientRect().top;
|
|
355
|
+
}
|
|
356
|
+
function sectionsOf(panel) {
|
|
357
|
+
return [...panel.querySelectorAll("[data-token-category]")];
|
|
358
|
+
}
|
|
359
|
+
function SectionJump() {
|
|
360
|
+
const ref = useRef(null);
|
|
361
|
+
const [present, setPresent] = useState([]);
|
|
362
|
+
const [current, setCurrent] = useState(null);
|
|
363
|
+
const held = useRef(false);
|
|
364
|
+
useEffect(() => {
|
|
365
|
+
const root = ref.current?.closest(HOST) ?? document.body;
|
|
366
|
+
const read = () => {
|
|
367
|
+
const panel = root.querySelector(PANEL);
|
|
368
|
+
const sections = panel ? sectionsOf(panel) : [];
|
|
369
|
+
setPresent(sections.map((s) => s.dataset.tokenCategory));
|
|
370
|
+
if (!panel || held.current) return;
|
|
371
|
+
const line = readLine(panel) + 1;
|
|
372
|
+
let top = sections[0]?.dataset.tokenCategory;
|
|
373
|
+
for (const s of sections) if (s.getBoundingClientRect().top <= line) top = s.dataset.tokenCategory;
|
|
374
|
+
setCurrent(top ?? null);
|
|
375
|
+
};
|
|
376
|
+
const release = () => {
|
|
377
|
+
held.current = false;
|
|
378
|
+
};
|
|
379
|
+
read();
|
|
380
|
+
const observer = new MutationObserver(read);
|
|
381
|
+
observer.observe(root, { childList: true, subtree: true });
|
|
382
|
+
document.addEventListener("scroll", read, { capture: true, passive: true });
|
|
383
|
+
for (const type of MANUAL) document.addEventListener(type, release, { capture: true, passive: true });
|
|
384
|
+
return () => {
|
|
385
|
+
observer.disconnect();
|
|
386
|
+
document.removeEventListener("scroll", read, { capture: true });
|
|
387
|
+
for (const type of MANUAL) document.removeEventListener(type, release, { capture: true });
|
|
388
|
+
};
|
|
389
|
+
}, []);
|
|
390
|
+
const items = TOKEN_CATEGORIES.map(({ id, title }) => ({
|
|
391
|
+
value: id,
|
|
392
|
+
label: /* @__PURE__ */ jsx(Icon, { name: ICONS[id], size: 14 }),
|
|
393
|
+
ariaLabel: title,
|
|
394
|
+
tooltip: title,
|
|
395
|
+
disabled: !present.includes(id)
|
|
396
|
+
}));
|
|
397
|
+
const jump = (next) => {
|
|
398
|
+
const panel = ref.current?.closest(HOST)?.querySelector(PANEL);
|
|
399
|
+
const target = next && panel?.querySelector(`[data-token-category="${next}"]`);
|
|
400
|
+
const scroller = panel?.querySelector(LIST);
|
|
401
|
+
if (!panel || !target || !scroller) return;
|
|
402
|
+
held.current = true;
|
|
403
|
+
setCurrent(next);
|
|
404
|
+
scroller.scrollTop += target.getBoundingClientRect().top - readLine(panel);
|
|
405
|
+
};
|
|
406
|
+
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 }) });
|
|
407
|
+
}
|
|
355
408
|
var SEED_REF = /^\{seeds\.([^}]+)\}$/;
|
|
356
409
|
var RULE_KEYS = { factors: "factors", ratio: "ratio", step: "step" };
|
|
357
410
|
function selectionFor(axes, globals) {
|
|
@@ -365,24 +418,24 @@ function selectionFor(axes, globals) {
|
|
|
365
418
|
}
|
|
366
419
|
var EditError = class extends Error {
|
|
367
420
|
};
|
|
368
|
-
function
|
|
369
|
-
|
|
370
|
-
let
|
|
371
|
-
let k = key;
|
|
372
|
-
let w = where;
|
|
373
|
-
let value = h[k];
|
|
374
|
-
while (isByAxis(value)) {
|
|
421
|
+
function branchOf(holder, key, where, selection) {
|
|
422
|
+
let slot = { holder, key, where };
|
|
423
|
+
for (let value = holder[key]; isByAxis(value); value = slot.holder[slot.key]) {
|
|
375
424
|
const axis = value.by;
|
|
376
425
|
const picked = selection[axis];
|
|
377
426
|
if (picked === void 0 || picked === "by" || !(picked in value)) {
|
|
378
|
-
throw new EditError(`${
|
|
427
|
+
throw new EditError(`${slot.where} differs by ${axis}, and which ${axis} this trial shows is not known`);
|
|
379
428
|
}
|
|
380
|
-
|
|
381
|
-
k = picked;
|
|
382
|
-
w = `${w}.${picked}`;
|
|
383
|
-
value = h[k];
|
|
429
|
+
slot = { holder: value, key: picked, where: `${slot.where}.${picked}` };
|
|
384
430
|
}
|
|
385
|
-
|
|
431
|
+
return slot;
|
|
432
|
+
}
|
|
433
|
+
function slotOf(root, holder, key, where, selection, depth = 0) {
|
|
434
|
+
if (depth > 16) throw new EditError(`${where} references itself`);
|
|
435
|
+
const slot = branchOf(holder, key, where, selection);
|
|
436
|
+
const { holder: h, key: k, where: w } = slot;
|
|
437
|
+
const value = h[k];
|
|
438
|
+
if (typeof value === "number") return slot;
|
|
386
439
|
if (typeof value === "string") {
|
|
387
440
|
const ref = SEED_REF.exec(value);
|
|
388
441
|
if (!ref) throw new EditError(`${w} is "${value}", which is not a number or a seed`);
|
|
@@ -394,18 +447,10 @@ function slotOf(root, holder, key, where, selection, depth = 0) {
|
|
|
394
447
|
throw new EditError(`${w} is not a number`);
|
|
395
448
|
}
|
|
396
449
|
function factorsOf(holder, where, selection) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (picked === void 0 || !(picked in value)) {
|
|
402
|
-
throw new EditError(`${w} differs by ${value.by}, and which ${value.by} this trial shows is not known`);
|
|
403
|
-
}
|
|
404
|
-
w = `${w}.${picked}`;
|
|
405
|
-
value = value[picked];
|
|
406
|
-
}
|
|
407
|
-
if (!Array.isArray(value)) throw new EditError(`${w} is not a list`);
|
|
408
|
-
return { list: value, where: w };
|
|
450
|
+
const slot = branchOf(holder, "factors", `${where}.factors`, selection);
|
|
451
|
+
const value = slot.holder[slot.key];
|
|
452
|
+
if (!Array.isArray(value)) throw new EditError(`${slot.where} is not a list`);
|
|
453
|
+
return { list: value, where: slot.where };
|
|
409
454
|
}
|
|
410
455
|
var kindOf = (scale) => scale.factors !== void 0 ? "factors" : scale.ratio !== void 0 ? "ratio" : scale.step !== void 0 ? "step" : null;
|
|
411
456
|
var plainLiteral = (value) => typeof value === "number" || Array.isArray(value) && value.every((v) => typeof v === "number");
|
|
@@ -461,90 +506,196 @@ function applyScaleEdits(definition, edits, selection) {
|
|
|
461
506
|
for (const [holder, byKey] of writes) for (const [key, value] of byKey) holder[key] = value;
|
|
462
507
|
return { ok: true, definition: root };
|
|
463
508
|
}
|
|
464
|
-
var
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const
|
|
478
|
-
|
|
509
|
+
var REFERENCE = /\{[^}]+\}/;
|
|
510
|
+
var LAYERS = ["pins", "semantics", "components"];
|
|
511
|
+
function derivation(entry) {
|
|
512
|
+
const value = isPlainObject(entry) ? entry.value : entry;
|
|
513
|
+
if (isPlainObject(entry) && value === void 0) return "follows a rule";
|
|
514
|
+
if (isPlainObject(entry) && entry.alpha !== void 0) return "is drawn at an alpha";
|
|
515
|
+
if (typeof value === "string" && REFERENCE.test(value)) return `follows ${REFERENCE.exec(value)?.[0]}`;
|
|
516
|
+
return typeof value === "string" || typeof value === "number" ? null : "is a list";
|
|
517
|
+
}
|
|
518
|
+
function literalSlot(definition, token, selection) {
|
|
519
|
+
for (const layer of LAYERS) {
|
|
520
|
+
const entries = definition[layer];
|
|
521
|
+
if (!entries || !(token in entries)) continue;
|
|
522
|
+
const slot = branchOf(entries, token, `${layer}.${token}`, selection);
|
|
523
|
+
const why = derivation(slot.holder[slot.key]);
|
|
524
|
+
if (why) throw new EditError(`${slot.where} ${why}, and only a value written out in the theme can be saved`);
|
|
525
|
+
return slot;
|
|
479
526
|
}
|
|
480
|
-
|
|
527
|
+
for (const layer of ["ramps", "scales"]) {
|
|
528
|
+
const from = Object.keys(definition[layer] ?? {}).find((name) => token.startsWith(`${name}-`));
|
|
529
|
+
if (from) throw new EditError(`${token} is a step ${layer}.${from} generates, and the theme pins no value over it`);
|
|
530
|
+
}
|
|
531
|
+
throw new EditError(`the theme defines no token "${token}"`);
|
|
481
532
|
}
|
|
482
|
-
function
|
|
483
|
-
|
|
533
|
+
function writtenOut(definition, token, selection) {
|
|
534
|
+
try {
|
|
535
|
+
literalSlot(definition, token, selection);
|
|
536
|
+
return true;
|
|
537
|
+
} catch (e) {
|
|
538
|
+
if (e instanceof EditError) return false;
|
|
539
|
+
throw e;
|
|
540
|
+
}
|
|
484
541
|
}
|
|
485
|
-
function
|
|
486
|
-
|
|
542
|
+
function applyTokenEdits(definition, edits, selection) {
|
|
543
|
+
const root = structuredClone(definition);
|
|
544
|
+
try {
|
|
545
|
+
for (const [token, value] of Object.entries(edits)) {
|
|
546
|
+
const { holder, key } = literalSlot(root, token, selection);
|
|
547
|
+
const entry = holder[key];
|
|
548
|
+
holder[key] = isPlainObject(entry) ? { ...entry, value } : value;
|
|
549
|
+
}
|
|
550
|
+
} catch (e) {
|
|
551
|
+
if (e instanceof EditError) return { ok: false, message: `Cannot save: ${e.message}.` };
|
|
552
|
+
throw e;
|
|
553
|
+
}
|
|
554
|
+
return { ok: true, definition: root };
|
|
487
555
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
556
|
+
|
|
557
|
+
// src/shell/cssVars/useThemeSave.ts
|
|
558
|
+
var THEME_NAME = "weasel";
|
|
559
|
+
var TOKEN_PREFIX = "--wzl-";
|
|
560
|
+
function useThemeSave({ trialId, globals, themeApi, scales, overrides, history: history2 }) {
|
|
561
|
+
const tokens = useMemo(() => {
|
|
562
|
+
const definition = THEME_SOURCES[THEME_NAME];
|
|
563
|
+
if (!definition) return {};
|
|
564
|
+
const selection = selectionFor(definition.axes ?? {}, globals);
|
|
565
|
+
const generated = new Set(scales.flatMap((scale) => scale.tokens));
|
|
566
|
+
return Object.fromEntries(
|
|
567
|
+
Object.entries(overrides).flatMap(([name, value]) => {
|
|
568
|
+
if (!name.startsWith(TOKEN_PREFIX) || generated.has(name)) return [];
|
|
569
|
+
const token = name.slice(TOKEN_PREFIX.length);
|
|
570
|
+
return writtenOut(definition, token, selection) ? [[token, value]] : [];
|
|
571
|
+
})
|
|
572
|
+
);
|
|
573
|
+
}, [globals, scales, overrides]);
|
|
574
|
+
const [saving, setSaving] = useState(false);
|
|
575
|
+
const [held, setHeld] = useState(null);
|
|
576
|
+
const setNotice = (next) => setHeld(next && { ...next, trialId });
|
|
577
|
+
const save = async () => {
|
|
578
|
+
setSaving(true);
|
|
579
|
+
setNotice(null);
|
|
580
|
+
try {
|
|
581
|
+
const api = themeApi ?? httpThemeApi();
|
|
582
|
+
const stored = await api.get(THEME_NAME);
|
|
583
|
+
const file = `themes/${THEME_NAME}.json`;
|
|
584
|
+
if (JSON.stringify(stored.definition) !== JSON.stringify(THEME_SOURCES[THEME_NAME])) {
|
|
585
|
+
setNotice({ tone: "error", text: `${file} has changed since this page loaded. Reload to edit against it.` });
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const selection = selectionFor(stored.definition.axes ?? {}, globals);
|
|
589
|
+
const scaled = applyScaleEdits(stored.definition, Object.fromEntries(scales.map((s) => [s.name, s.edit])), selection);
|
|
590
|
+
const next = scaled.ok ? applyTokenEdits(scaled.definition, tokens, selection) : scaled;
|
|
591
|
+
if (!next.ok) {
|
|
592
|
+
setNotice({ tone: "error", text: next.message });
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
const result = await api.put(THEME_NAME, next.definition, stored.hash);
|
|
596
|
+
if (result.status === "conflict") {
|
|
597
|
+
setNotice({ tone: "error", text: `${file} changed on disk while saving. Nothing was written; reload to edit against it.` });
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
if (result.status === "invalid") {
|
|
601
|
+
setNotice({ tone: "error", text: result.message });
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const saved = /* @__PURE__ */ new Set([
|
|
605
|
+
...scales.flatMap((scale) => scale.tokens),
|
|
606
|
+
...Object.keys(tokens).map((token) => `${TOKEN_PREFIX}${token}`)
|
|
607
|
+
]);
|
|
608
|
+
const groups = new Set(scales.map((scale) => scale.group));
|
|
609
|
+
const now = history2.current();
|
|
610
|
+
history2.reset({
|
|
611
|
+
overrides: Object.fromEntries(Object.entries(now.overrides).filter(([name]) => !saved.has(name))),
|
|
612
|
+
scales: Object.fromEntries(Object.entries(now.scales).filter(([group]) => !groups.has(group)))
|
|
613
|
+
});
|
|
614
|
+
setNotice(
|
|
615
|
+
result.regenerated || result.problems.length === 0 ? { tone: "saved", text: `Saved to ${file}.` } : { tone: "error", text: `Saved to ${file}, but the tokens were not regenerated: ${result.problems.join("; ")}` }
|
|
616
|
+
);
|
|
617
|
+
} catch (e) {
|
|
618
|
+
setNotice({ tone: "error", text: `Saving needs forge's dev server, which did not answer: ${e.message}` });
|
|
619
|
+
} finally {
|
|
620
|
+
setSaving(false);
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
return {
|
|
624
|
+
pending: scales.length > 0 || Object.keys(tokens).length > 0,
|
|
625
|
+
saving,
|
|
626
|
+
notice: held?.trialId === trialId ? held : null,
|
|
627
|
+
save
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
var SWAP = "fg.vars.swap";
|
|
631
|
+
var COALESCE_MS = 800;
|
|
632
|
+
var LIMIT = 100;
|
|
633
|
+
function useVarsHistory(state, apply) {
|
|
634
|
+
const trialId = useTrialId();
|
|
635
|
+
const [stored, setStored] = usePersistedState("fg-css-vars-history", null, { scope: "trial" });
|
|
636
|
+
const rendered = useLatest(state);
|
|
637
|
+
const ahead = useRef(null);
|
|
493
638
|
useEffect(() => {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
639
|
+
ahead.current = null;
|
|
640
|
+
}, [state]);
|
|
641
|
+
const current = () => ahead.current ?? rendered.current;
|
|
642
|
+
const latestApply = useLatest(apply);
|
|
643
|
+
const emit = (to) => {
|
|
644
|
+
const from = current();
|
|
645
|
+
ahead.current = to;
|
|
646
|
+
latestApply.current(from, to);
|
|
647
|
+
};
|
|
648
|
+
const swap = (before, after, coalesceKey) => swapOp({ name: SWAP, label: "edit vars", before, after, emit, ...coalesceKey !== void 0 ? { coalesceKey } : {} });
|
|
649
|
+
const history2 = useMemo(() => {
|
|
650
|
+
const opened = createHistory(null, {
|
|
651
|
+
coalesceWindowMs: COALESCE_MS,
|
|
652
|
+
historyLimit: LIMIT,
|
|
653
|
+
rebuildOp: (name, args) => {
|
|
654
|
+
const { before, after } = args;
|
|
655
|
+
return name === SWAP ? swap(before, after) : null;
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
if (stored) opened.restore(stored);
|
|
659
|
+
return opened;
|
|
660
|
+
}, [trialId]);
|
|
661
|
+
useSyncExternalStore(history2.subscribe, history2.getVersion);
|
|
662
|
+
const keep = () => setStored(history2.canUndo() || history2.canRedo() ? history2.serialize() : null);
|
|
663
|
+
const tick = useRef(null);
|
|
664
|
+
return {
|
|
665
|
+
current,
|
|
666
|
+
commit(next, key) {
|
|
667
|
+
if (tick.current === null) {
|
|
668
|
+
tick.current = key;
|
|
669
|
+
queueMicrotask(() => {
|
|
670
|
+
tick.current = null;
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
history2.applyOps([swap(current(), next, tick.current)], "edit vars");
|
|
674
|
+
keep();
|
|
675
|
+
},
|
|
676
|
+
step(to) {
|
|
677
|
+
history2[to]();
|
|
678
|
+
keep();
|
|
679
|
+
},
|
|
680
|
+
canUndo: history2.canUndo(),
|
|
681
|
+
canRedo: history2.canRedo(),
|
|
682
|
+
reset(next) {
|
|
683
|
+
emit(next);
|
|
684
|
+
history2.clear();
|
|
685
|
+
setStored(null);
|
|
686
|
+
}
|
|
534
687
|
};
|
|
535
|
-
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 }) });
|
|
536
688
|
}
|
|
537
689
|
var TABS = [
|
|
538
690
|
{ value: "theme", label: "Theme" },
|
|
539
691
|
{ value: "story", label: "Story" }
|
|
540
692
|
];
|
|
541
693
|
var MANIFEST = new Map(TOKEN_MANIFEST.map((token) => [token.name, token]));
|
|
542
|
-
var THEME_NAME = "weasel";
|
|
543
694
|
var literal = (v) => typeof v === "number";
|
|
544
695
|
var SCALE_SOURCES = Object.entries(THEME_SOURCES[THEME_NAME]?.scales ?? {}).flatMap(([name, def]) => {
|
|
545
696
|
if (!Array.isArray(def.steps)) return [];
|
|
546
697
|
const steps = def.steps;
|
|
547
|
-
const tokens = steps.map((step) =>
|
|
698
|
+
const tokens = steps.map((step) => `${TOKEN_PREFIX}${name}-${step}`);
|
|
548
699
|
const group = MANIFEST.get(tokens[0] ?? "")?.group;
|
|
549
700
|
if (!group) return [];
|
|
550
701
|
const factors = Array.isArray(def.factors) && def.factors.every(literal) ? def.factors : null;
|
|
@@ -589,7 +740,7 @@ function CssVarsPanel({ globals = NO_GLOBALS, themeApi } = {}) {
|
|
|
589
740
|
{},
|
|
590
741
|
{ scope: "lab" }
|
|
591
742
|
);
|
|
592
|
-
const [edited, setEdited] = usePersistedState("fg-css-vars-scales", {}, { scope: "
|
|
743
|
+
const [edited, setEdited] = usePersistedState("fg-css-vars-scales", {}, { scope: "trial" });
|
|
593
744
|
const [tab, setTab] = useState("theme");
|
|
594
745
|
const [filter, setFilter] = useState("");
|
|
595
746
|
const tokens = useMemo(() => {
|
|
@@ -626,135 +777,115 @@ function CssVarsPanel({ globals = NO_GLOBALS, themeApi } = {}) {
|
|
|
626
777
|
return out;
|
|
627
778
|
}, [tab, shown, overrides, edited]);
|
|
628
779
|
const send = frame.send;
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
(
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
780
|
+
const state = useMemo(() => ({ overrides, scales: edited }), [overrides, edited]);
|
|
781
|
+
const history2 = useVarsHistory(state, (from, to) => {
|
|
782
|
+
setOverrides(to.overrides);
|
|
783
|
+
setEdited(to.scales);
|
|
784
|
+
for (const name of /* @__PURE__ */ new Set([...Object.keys(from.overrides), ...Object.keys(to.overrides)])) {
|
|
785
|
+
if (from.overrides[name] !== to.overrides[name]) send?.({ type: "vars.set", name, value: to.overrides[name] ?? null });
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
const write = (name, value) => {
|
|
789
|
+
const now = history2.current();
|
|
790
|
+
const { [name]: _dropped, ...rest } = now.overrides;
|
|
791
|
+
history2.commit({ ...now, overrides: value === null ? rest : { ...rest, [name]: value } }, `token:${name}`);
|
|
792
|
+
};
|
|
793
|
+
const onScaleChange = (group, next) => {
|
|
794
|
+
const source = SCALE_SOURCES.find((sc) => sc.group === group);
|
|
795
|
+
if (!source) return;
|
|
796
|
+
const now = history2.current();
|
|
797
|
+
history2.commit(
|
|
798
|
+
{ overrides: { ...now.overrides, ...Object.fromEntries(scaleValues(source, next)) }, scales: { ...now.scales, [group]: next } },
|
|
799
|
+
`scale:${group}`
|
|
800
|
+
);
|
|
801
|
+
};
|
|
648
802
|
const unsaved = useMemo(
|
|
649
803
|
() => SCALE_SOURCES.flatMap((source) => {
|
|
650
804
|
const edit = edited[source.group];
|
|
651
|
-
if (!edit || !source.tokens.some((
|
|
652
|
-
const
|
|
653
|
-
|
|
805
|
+
if (!edit || !source.tokens.some((name2) => overrides[name2] !== void 0)) return [];
|
|
806
|
+
const { name, group, tokens: tokens2 } = source;
|
|
807
|
+
const clean = scaleValues(source, edit).every(([token, value]) => overrides[token] === value);
|
|
808
|
+
return clean ? [{ name, group, tokens: tokens2, edit: { base: edit.base, rule: edit.rule } }] : [];
|
|
654
809
|
}),
|
|
655
810
|
[edited, overrides]
|
|
656
811
|
);
|
|
657
|
-
const
|
|
658
|
-
const [held, setHeld] = useState(null);
|
|
659
|
-
const notice = held?.trialId === trialId ? held : null;
|
|
660
|
-
const setNotice = (next) => setHeld(next && { ...next, trialId });
|
|
661
|
-
const save = async () => {
|
|
662
|
-
setSaving(true);
|
|
663
|
-
setNotice(null);
|
|
664
|
-
try {
|
|
665
|
-
const api = themeApi ?? httpThemeApi();
|
|
666
|
-
const stored = await api.get(THEME_NAME);
|
|
667
|
-
const file = `themes/${THEME_NAME}.json`;
|
|
668
|
-
if (JSON.stringify(stored.definition) !== JSON.stringify(THEME_SOURCES[THEME_NAME])) {
|
|
669
|
-
setNotice({ tone: "error", text: `${file} has changed since this page loaded. Reload to edit against it.` });
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
const edits = Object.fromEntries(
|
|
673
|
-
unsaved.map(({ source, edit }) => [source.name, { base: edit.base, rule: edit.rule }])
|
|
674
|
-
);
|
|
675
|
-
const next = applyScaleEdits(stored.definition, edits, selectionFor(stored.definition.axes ?? {}, globals));
|
|
676
|
-
if (!next.ok) {
|
|
677
|
-
setNotice({ tone: "error", text: next.message });
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
const result = await api.put(THEME_NAME, next.definition, stored.hash);
|
|
681
|
-
if (result.status === "conflict") {
|
|
682
|
-
setNotice({ tone: "error", text: `${file} changed on disk while saving. Nothing was written; reload to edit against it.` });
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
if (result.status === "invalid") {
|
|
686
|
-
setNotice({ tone: "error", text: result.message });
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
for (const { source } of unsaved) for (const name of source.tokens) write(name, null);
|
|
690
|
-
const groups = new Set(unsaved.map(({ source }) => source.group));
|
|
691
|
-
setEdited((prev) => Object.fromEntries(Object.entries(prev).filter(([group]) => !groups.has(group))));
|
|
692
|
-
setNotice(
|
|
693
|
-
result.regenerated || result.problems.length === 0 ? { tone: "saved", text: `Saved to ${file}.` } : { tone: "error", text: `Saved to ${file}, but the tokens were not regenerated: ${result.problems.join("; ")}` }
|
|
694
|
-
);
|
|
695
|
-
} catch (e) {
|
|
696
|
-
setNotice({ tone: "error", text: `Saving needs forge's dev server, which did not answer: ${e.message}` });
|
|
697
|
-
} finally {
|
|
698
|
-
setSaving(false);
|
|
699
|
-
}
|
|
700
|
-
};
|
|
812
|
+
const { pending, saving, notice, save } = useThemeSave({ trialId, globals, themeApi, scales: unsaved, overrides, history: history2 });
|
|
701
813
|
const onCollapsedChange = useCallback(
|
|
702
814
|
(category, collapsed) => setFolds((prev) => ({ ...prev, [category]: collapsed })),
|
|
703
815
|
[setFolds]
|
|
704
816
|
);
|
|
705
|
-
return
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
ToggleBar$1,
|
|
710
|
-
{
|
|
711
|
-
ariaLabel: "Variables",
|
|
712
|
-
variant: "flat",
|
|
713
|
-
size: "sm",
|
|
714
|
-
items: TABS,
|
|
715
|
-
value: tab,
|
|
716
|
-
onChange: (next) => {
|
|
717
|
-
if (next) setTab(next);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
),
|
|
721
|
-
/* @__PURE__ */ jsx(
|
|
722
|
-
Input,
|
|
723
|
-
{
|
|
724
|
-
className: "fg-css-vars__filter",
|
|
725
|
-
"aria-label": "Filter",
|
|
726
|
-
placeholder: "Filter",
|
|
727
|
-
value: filter,
|
|
728
|
-
onChange: setFilter,
|
|
729
|
-
leadingAdornment: /* @__PURE__ */ jsx(SearchIcon, { size: 12 })
|
|
730
|
-
}
|
|
731
|
-
)
|
|
732
|
-
] }),
|
|
733
|
-
tab === "theme" && (unsaved.length > 0 || notice) ? /* @__PURE__ */ jsxs("div", { className: "fg-css-vars__save", children: [
|
|
734
|
-
unsaved.length > 0 ? /* @__PURE__ */ jsx(Button$1, { variant: "primary", size: "sm", loading: saving, disabled: saving, onClick: () => void save(), children: "Save scales to theme" }) : null,
|
|
735
|
-
notice ? /* @__PURE__ */ jsx(
|
|
736
|
-
"p",
|
|
737
|
-
{
|
|
738
|
-
className: `fg-css-vars__notice fg-css-vars__notice--${notice.tone}`,
|
|
739
|
-
role: notice.tone === "error" ? "alert" : "status",
|
|
740
|
-
children: notice.text
|
|
741
|
-
}
|
|
742
|
-
) : null
|
|
743
|
-
] }) : null
|
|
744
|
-
] }),
|
|
745
|
-
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(
|
|
746
|
-
TokenPanel,
|
|
817
|
+
return (
|
|
818
|
+
// Capture: React Aria's fields stop a keydown from bubbling past them.
|
|
819
|
+
/* @__PURE__ */ jsxs(
|
|
820
|
+
"div",
|
|
747
821
|
{
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
822
|
+
className: "fg-css-vars",
|
|
823
|
+
onKeyDownCapture: (e) => {
|
|
824
|
+
const to = e.target.closest(".fg-css-vars__filter") ? null : historyKey(e);
|
|
825
|
+
if (!to) return;
|
|
826
|
+
e.preventDefault();
|
|
827
|
+
history2.step(to);
|
|
828
|
+
},
|
|
829
|
+
children: [
|
|
830
|
+
/* @__PURE__ */ jsxs("div", { className: "fg-css-vars__controls", children: [
|
|
831
|
+
/* @__PURE__ */ jsxs("div", { className: "fg-css-vars__bar", children: [
|
|
832
|
+
/* @__PURE__ */ jsx(
|
|
833
|
+
ToggleBar$1,
|
|
834
|
+
{
|
|
835
|
+
ariaLabel: "Variables",
|
|
836
|
+
variant: "flat",
|
|
837
|
+
size: "sm",
|
|
838
|
+
items: TABS,
|
|
839
|
+
value: tab,
|
|
840
|
+
onChange: (next) => {
|
|
841
|
+
if (next) setTab(next);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
),
|
|
845
|
+
/* @__PURE__ */ jsx(
|
|
846
|
+
Input,
|
|
847
|
+
{
|
|
848
|
+
className: "fg-css-vars__filter",
|
|
849
|
+
"aria-label": "Filter",
|
|
850
|
+
placeholder: "Filter",
|
|
851
|
+
value: filter,
|
|
852
|
+
onChange: setFilter,
|
|
853
|
+
leadingAdornment: /* @__PURE__ */ jsx(SearchIcon, { size: 12 })
|
|
854
|
+
}
|
|
855
|
+
),
|
|
856
|
+
/* @__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: "undo", size: 14 }) }),
|
|
858
|
+
/* @__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 }) })
|
|
859
|
+
] })
|
|
860
|
+
] }),
|
|
861
|
+
tab === "theme" && (pending || notice) ? /* @__PURE__ */ jsxs("div", { className: "fg-css-vars__save", children: [
|
|
862
|
+
pending ? /* @__PURE__ */ jsx(Button$1, { variant: "primary", size: "sm", loading: saving, disabled: saving, onClick: () => void save(), children: "Save to theme" }) : null,
|
|
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
|
|
872
|
+
] }),
|
|
873
|
+
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(
|
|
874
|
+
TokenPanel,
|
|
875
|
+
{
|
|
876
|
+
tokens: shown,
|
|
877
|
+
namePrefix: TOKEN_PREFIX,
|
|
878
|
+
onChange: write,
|
|
879
|
+
scales,
|
|
880
|
+
onScaleChange,
|
|
881
|
+
collapsed: folds,
|
|
882
|
+
onCollapsedChange
|
|
883
|
+
}
|
|
884
|
+
) })
|
|
885
|
+
]
|
|
755
886
|
}
|
|
756
887
|
)
|
|
757
|
-
|
|
888
|
+
);
|
|
758
889
|
}
|
|
759
890
|
function FocusedCssVars() {
|
|
760
891
|
const { focusedTrialId, trials, instruments } = useLabContext();
|
|
@@ -771,7 +902,7 @@ function FocusedCssVars() {
|
|
|
771
902
|
var CSS_VARS_SECTION = {
|
|
772
903
|
id: "fg-css-vars",
|
|
773
904
|
region: "aside",
|
|
774
|
-
item: { title: "CSS Vars", actions: /* @__PURE__ */ jsx(SectionJump, {}), body: /* @__PURE__ */ jsx(FocusedCssVars, {}) }
|
|
905
|
+
item: { title: "CSS Vars", actions: /* @__PURE__ */ jsx(SectionJump, {}), fill: true, body: /* @__PURE__ */ jsx(FocusedCssVars, {}) }
|
|
775
906
|
};
|
|
776
907
|
var GLOBALS = "$globals";
|
|
777
908
|
function leaves(shape, prefix = "") {
|
|
@@ -1615,13 +1746,11 @@ function StoryTree({ ctx, index }) {
|
|
|
1615
1746
|
/* @__PURE__ */ jsx("div", { role: "tree", "aria-labelledby": headingId, className: "fg-tree__items", onKeyDown, children: renderNodes(shown, 1) })
|
|
1616
1747
|
] });
|
|
1617
1748
|
}
|
|
1618
|
-
|
|
1619
|
-
// src/shell/answers.ts
|
|
1620
1749
|
var KEPT_CONFIGS = 50;
|
|
1621
1750
|
var UNANSWERED = stableStringify({ hidden: [], errors: {} });
|
|
1622
1751
|
function createAnswerBook() {
|
|
1623
1752
|
const byConfig = /* @__PURE__ */ new Map();
|
|
1624
|
-
const
|
|
1753
|
+
const answered = createEmitter();
|
|
1625
1754
|
const held = /* @__PURE__ */ new Map();
|
|
1626
1755
|
const keyOf2 = (config) => stableStringify(storyConfig(config));
|
|
1627
1756
|
const answerFor = (config) => byConfig.get(keyOf2(config));
|
|
@@ -1635,7 +1764,7 @@ function createAnswerBook() {
|
|
|
1635
1764
|
const oldest = byConfig.keys().next().value;
|
|
1636
1765
|
if (oldest !== void 0) byConfig.delete(oldest);
|
|
1637
1766
|
}
|
|
1638
|
-
if (changed && held.has(answers.configKey))
|
|
1767
|
+
if (changed && held.has(answers.configKey)) answered.emit();
|
|
1639
1768
|
},
|
|
1640
1769
|
hold(config) {
|
|
1641
1770
|
const key = keyOf2(config);
|
|
@@ -1651,12 +1780,7 @@ function createAnswerBook() {
|
|
|
1651
1780
|
return hidden?.some((h) => path === h || path.startsWith(`${h}.`)) ?? false;
|
|
1652
1781
|
},
|
|
1653
1782
|
errors: (path, config) => answerFor(config)?.errors[path] ?? [],
|
|
1654
|
-
subscribe
|
|
1655
|
-
listeners2.add(fn);
|
|
1656
|
-
return () => {
|
|
1657
|
-
listeners2.delete(fn);
|
|
1658
|
-
};
|
|
1659
|
-
}
|
|
1783
|
+
subscribe: answered.subscribe
|
|
1660
1784
|
};
|
|
1661
1785
|
}
|
|
1662
1786
|
|
|
@@ -1693,29 +1817,29 @@ function FrameView(props) {
|
|
|
1693
1817
|
const slotRef = useRef(null);
|
|
1694
1818
|
const inView = useNearViewport(hostRef, IN_VIEW_MARGIN);
|
|
1695
1819
|
const link = useRef(null);
|
|
1696
|
-
const
|
|
1820
|
+
const latestRef = useLatest({ ...props, globals, overrides, pool, lab, title });
|
|
1697
1821
|
const [fault, setFault] = useState(null);
|
|
1698
1822
|
const [pending, setPending] = useState(true);
|
|
1699
1823
|
const init = useCallback((current) => {
|
|
1700
|
-
const { ctx: live, globals: liveGlobals } =
|
|
1824
|
+
const { ctx: live, globals: liveGlobals } = latestRef.current;
|
|
1701
1825
|
current.awaiting = null;
|
|
1702
1826
|
current.sent = { config: storyConfig(live.config), state: live.state, globals: liveGlobals };
|
|
1703
1827
|
current.inputs += 1;
|
|
1704
1828
|
current.channel.send({ type: "init", ...current.sent });
|
|
1705
|
-
}, [
|
|
1829
|
+
}, [latestRef]);
|
|
1706
1830
|
const receive = (current, msg) => {
|
|
1707
|
-
const { ctx: live, answers: answers2, onReady } =
|
|
1831
|
+
const { ctx: live, answers: answers2, onReady } = latestRef.current;
|
|
1708
1832
|
switch (msg.type) {
|
|
1709
1833
|
case "ready": {
|
|
1710
1834
|
clearTimeout(current.timer);
|
|
1711
1835
|
setFault(null);
|
|
1712
|
-
for (const [name, value] of Object.entries(
|
|
1836
|
+
for (const [name, value] of Object.entries(latestRef.current.overrides)) {
|
|
1713
1837
|
current.channel.send({ type: "vars.set", name, value });
|
|
1714
1838
|
}
|
|
1715
1839
|
const key = readyKey(msg);
|
|
1716
1840
|
current.awaiting = key;
|
|
1717
|
-
onReady(
|
|
1718
|
-
if (current.awaiting === key && key ===
|
|
1841
|
+
onReady(latestRef.current.entry, msg);
|
|
1842
|
+
if (current.awaiting === key && key === latestRef.current.descriptionKey) init(current);
|
|
1719
1843
|
break;
|
|
1720
1844
|
}
|
|
1721
1845
|
case "rendered":
|
|
@@ -1744,7 +1868,7 @@ function FrameView(props) {
|
|
|
1744
1868
|
break;
|
|
1745
1869
|
}
|
|
1746
1870
|
case "open": {
|
|
1747
|
-
const { lab: liveLab } =
|
|
1871
|
+
const { lab: liveLab } = latestRef.current;
|
|
1748
1872
|
if (!trialId || !liveLab.instruments.some((instrument) => instrument.name === msg.id)) break;
|
|
1749
1873
|
liveLab.swapTrial(trialId, msg.id);
|
|
1750
1874
|
setRoute(msg.id, { inPlace: true });
|
|
@@ -1793,7 +1917,7 @@ function FrameView(props) {
|
|
|
1793
1917
|
};
|
|
1794
1918
|
link.current = current;
|
|
1795
1919
|
channel.on((msg) => receive(current, msg));
|
|
1796
|
-
target.postMessage({ type: PORT_HANDOFF, id:
|
|
1920
|
+
target.postMessage({ type: PORT_HANDOFF, id: latestRef.current.entry.id }, location.origin, [
|
|
1797
1921
|
port2
|
|
1798
1922
|
]);
|
|
1799
1923
|
};
|
|
@@ -1801,10 +1925,10 @@ function FrameView(props) {
|
|
|
1801
1925
|
useEffect(() => {
|
|
1802
1926
|
const slot = slotRef.current;
|
|
1803
1927
|
if (!inView || !slot) return;
|
|
1804
|
-
const claimed =
|
|
1928
|
+
const claimed = latestRef.current.pool?.claim() ?? null;
|
|
1805
1929
|
const frame = claimed ?? document.createElement("iframe");
|
|
1806
1930
|
frame.className = "fg-frame-view";
|
|
1807
|
-
frame.title =
|
|
1931
|
+
frame.title = latestRef.current.title;
|
|
1808
1932
|
frame.removeAttribute("tabindex");
|
|
1809
1933
|
frame.toggleAttribute("data-pending", true);
|
|
1810
1934
|
iframeRef.current = frame;
|
|
@@ -1832,7 +1956,7 @@ function FrameView(props) {
|
|
|
1832
1956
|
setPending(true);
|
|
1833
1957
|
setFault(null);
|
|
1834
1958
|
};
|
|
1835
|
-
}, [inView, src, connectRef,
|
|
1959
|
+
}, [inView, src, connectRef, latestRef]);
|
|
1836
1960
|
useEffect(() => {
|
|
1837
1961
|
iframeRef.current?.toggleAttribute("data-pending", pending && !fault);
|
|
1838
1962
|
}, [pending, fault, inView, src]);
|
|
@@ -1939,21 +2063,21 @@ function useStoryRegistry(index, options) {
|
|
|
1939
2063
|
setRevisions((prev) => pruned(prev, ids));
|
|
1940
2064
|
}
|
|
1941
2065
|
const imports = useRef(/* @__PURE__ */ new Map());
|
|
1942
|
-
const
|
|
2066
|
+
const liveRef = useLatest({ index, importers, setup, load, libraries });
|
|
1943
2067
|
const missing = useRef(/* @__PURE__ */ new Set());
|
|
1944
2068
|
const importOf = useCallback((file) => {
|
|
1945
2069
|
let loading = imports.current.get(file);
|
|
1946
2070
|
if (!loading) {
|
|
1947
|
-
const importer =
|
|
2071
|
+
const importer = liveRef.current.importers?.[file];
|
|
1948
2072
|
if (!importer) missing.current.add(file);
|
|
1949
2073
|
loading = importer ? importer() : Promise.reject(new Error(`No importer for ${file}`));
|
|
1950
2074
|
imports.current.set(file, loading);
|
|
1951
2075
|
}
|
|
1952
2076
|
return loading;
|
|
1953
|
-
}, [
|
|
2077
|
+
}, [liveRef]);
|
|
1954
2078
|
const request = useCallback(
|
|
1955
2079
|
(entry) => {
|
|
1956
|
-
const { setup: current, load: loadModule } =
|
|
2080
|
+
const { setup: current, load: loadModule } = liveRef.current;
|
|
1957
2081
|
const settle = (ids2, result) => {
|
|
1958
2082
|
setLoaded((prev) => {
|
|
1959
2083
|
const next = new Map(prev);
|
|
@@ -1971,7 +2095,7 @@ function useStoryRegistry(index, options) {
|
|
|
1971
2095
|
});
|
|
1972
2096
|
};
|
|
1973
2097
|
if (isIndexId(entry.id)) {
|
|
1974
|
-
const entries =
|
|
2098
|
+
const entries = liveRef.current.index.filter((e) => !isIndexId(e.id) && indexId(e.title) === entry.id);
|
|
1975
2099
|
const files = [...new Set(entries.map((e) => e.file))];
|
|
1976
2100
|
void Promise.all(files.map(importOf)).then(async (mods) => {
|
|
1977
2101
|
const byFile = new Map(files.map((file, i) => [file, loadModule(mods[i] ?? {}, entry.title, current.parameters)]));
|
|
@@ -1984,7 +2108,7 @@ function useStoryRegistry(index, options) {
|
|
|
1984
2108
|
kind: "index",
|
|
1985
2109
|
bundle: {
|
|
1986
2110
|
title: entry.title,
|
|
1987
|
-
library:
|
|
2111
|
+
library: liveRef.current.libraries.of(entry),
|
|
1988
2112
|
...entry.componentDescription === void 0 ? {} : { description: entry.componentDescription },
|
|
1989
2113
|
...entry.tags === void 0 ? {} : { tags: entry.tags },
|
|
1990
2114
|
galleries: entries.filter(isGallery).map((e) => e.id),
|
|
@@ -1997,7 +2121,7 @@ function useStoryRegistry(index, options) {
|
|
|
1997
2121
|
}).catch((error) => fail([entry.id], error));
|
|
1998
2122
|
return;
|
|
1999
2123
|
}
|
|
2000
|
-
const siblings =
|
|
2124
|
+
const siblings = liveRef.current.index.filter((e) => e.file === entry.file && !isIndexId(e.id));
|
|
2001
2125
|
const ids = siblings.map((e) => e.id);
|
|
2002
2126
|
void importOf(entry.file).then(async (mod) => {
|
|
2003
2127
|
const stories = loadModule(mod, entry.title, current.parameters);
|
|
@@ -2012,18 +2136,18 @@ function useStoryRegistry(index, options) {
|
|
|
2012
2136
|
settle(ids, result);
|
|
2013
2137
|
}).catch((error) => fail(ids, error));
|
|
2014
2138
|
},
|
|
2015
|
-
[importOf,
|
|
2139
|
+
[importOf, liveRef]
|
|
2016
2140
|
);
|
|
2017
2141
|
const reload = useCallback(
|
|
2018
2142
|
(file) => {
|
|
2019
2143
|
imports.current.delete(file);
|
|
2020
|
-
const affected =
|
|
2144
|
+
const affected = liveRef.current.index.filter((e) => e.file === file || isIndexId(e.id) && liveRef.current.index.some((s) => s.file === file && indexId(s.title) === e.id));
|
|
2021
2145
|
const ids = new Set(affected.map((e) => e.id));
|
|
2022
2146
|
setLoaded((prev) => pruned(prev, new Set([...prev.keys()].filter((id) => !ids.has(id)))));
|
|
2023
2147
|
setFaults((prev) => pruned(prev, new Set([...prev.keys()].filter((id) => !ids.has(id)))));
|
|
2024
2148
|
setGenerations((prev) => new Map(prev).set(file, (prev.get(file) ?? 0) + 1));
|
|
2025
2149
|
},
|
|
2026
|
-
[
|
|
2150
|
+
[liveRef]
|
|
2027
2151
|
);
|
|
2028
2152
|
useEffect(() => {
|
|
2029
2153
|
for (const file of [...missing.current]) {
|
|
@@ -2167,10 +2291,10 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
|
|
|
2167
2291
|
const labChrome = useMemo(
|
|
2168
2292
|
() => [
|
|
2169
2293
|
{ id: "fg-stories", region: "sidebar", render: (ctx) => /* @__PURE__ */ jsx(StoryTree, { ctx, index }) },
|
|
2170
|
-
{ id: "fg-info", region: "palette", item: infoCommand },
|
|
2171
2294
|
...config?.cssVars === false ? [] : [CSS_VARS_SECTION],
|
|
2172
2295
|
A11Y_SECTION,
|
|
2173
2296
|
...Object.keys(declarations).length > 0 ? [{ id: "fg-globals", region: "header", render: () => /* @__PURE__ */ jsx(GlobalsToolbar, { declarations }) }] : [],
|
|
2297
|
+
{ id: "fg-info", region: "header", group: "story", item: infoCommand },
|
|
2174
2298
|
...config?.labChrome ?? []
|
|
2175
2299
|
],
|
|
2176
2300
|
[index, infoCommand, declarations, config?.cssVars, config?.labChrome]
|