@weasel-js/forge 1.9.4 → 1.10.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.
- package/README.md +13 -7
- package/dist/{FrameController-BQjKhdwk.d.ts → FrameController-UeTY7ubF.d.ts} +25 -1
- package/dist/{chunk-B3PPHJTS.js → chunk-AP3DRQLI.js} +16 -16
- package/dist/chunk-AP3DRQLI.js.map +1 -0
- package/dist/{chunk-MQSFPRF6.js → chunk-BIAEGWGJ.js} +37 -19
- package/dist/chunk-BIAEGWGJ.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-E2FHBDNQ.js} +16 -25
- package/dist/chunk-E2FHBDNQ.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-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 +41 -5
- package/dist/shell/index.css.map +1 -1
- package/dist/shell/index.d.ts +7 -6
- package/dist/shell/index.js +537 -334
- 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 +28 -1
- package/dist/vite/index.js +413 -79
- 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,27 @@
|
|
|
1
|
-
import { parseRoute, formatRoute, paramsToKnobs, knobPaths, sameKnob, knobsToParams, PLAYHEAD_PARAM, reservedParams } from '../chunk-
|
|
1
|
+
import { parseRoute, formatRoute, paramsToKnobs, knobPaths, sameKnob, knobsToParams, PLAYHEAD_PARAM, reservedParams } from '../chunk-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-BIAEGWGJ.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-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';
|
|
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
|
-
import { TOKEN_MANIFEST, THEME_SOURCES, isByAxis } from '@weasel-js/theme';
|
|
21
|
+
import { themeAxes, weaselTheme, TOKEN_MANIFEST, THEME_SOURCES, resolveTheme, isColorModePreference, 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';
|
|
24
|
+
import { useResolvedColorMode } from '@weasel-js/theme/react';
|
|
23
25
|
|
|
24
26
|
function Finding({ finding }) {
|
|
25
27
|
return /* @__PURE__ */ jsxs("li", { className: "fg-a11y__finding", children: [
|
|
@@ -100,19 +102,9 @@ var A11Y_SECTION = {
|
|
|
100
102
|
region: "aside",
|
|
101
103
|
item: { title: "Accessibility", body: /* @__PURE__ */ jsx(FocusedA11y, {}) }
|
|
102
104
|
};
|
|
103
|
-
|
|
104
|
-
// src/shell/storyChanges.ts
|
|
105
105
|
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
|
-
};
|
|
106
|
+
const changes = createEmitter();
|
|
107
|
+
return { subscribe: changes.subscribe, emit: changes.emit };
|
|
116
108
|
}
|
|
117
109
|
var DependenciesContext = createContext(null);
|
|
118
110
|
function readRoute() {
|
|
@@ -233,17 +225,12 @@ function crumbs(title, name) {
|
|
|
233
225
|
function breadcrumb(title, name) {
|
|
234
226
|
return crumbs(title, name).map((step) => step.label).join(" > ");
|
|
235
227
|
}
|
|
236
|
-
|
|
237
|
-
// src/shell/treeReveal.ts
|
|
238
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
228
|
+
var reveals = createEmitter();
|
|
239
229
|
function revealInTree(path) {
|
|
240
|
-
|
|
230
|
+
reveals.emit(path);
|
|
241
231
|
}
|
|
242
232
|
function onRevealInTree(listener) {
|
|
243
|
-
|
|
244
|
-
return () => {
|
|
245
|
-
listeners.delete(listener);
|
|
246
|
-
};
|
|
233
|
+
return reveals.subscribe(listener);
|
|
247
234
|
}
|
|
248
235
|
function Crumbs({ entry }) {
|
|
249
236
|
const steps = crumbs(entry.title, entry.name);
|
|
@@ -352,6 +339,73 @@ function missingInstrument(id, globals = {}) {
|
|
|
352
339
|
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
340
|
};
|
|
354
341
|
}
|
|
342
|
+
var ICONS = {
|
|
343
|
+
color: "drop",
|
|
344
|
+
type: "type",
|
|
345
|
+
size: "measure",
|
|
346
|
+
motion: "curveEaseInOut",
|
|
347
|
+
depth: "layers",
|
|
348
|
+
other: "more"
|
|
349
|
+
};
|
|
350
|
+
var PANEL = ".fg-css-vars";
|
|
351
|
+
var HOST = ".lk-sidebar-section, .lk-panel-tile";
|
|
352
|
+
var MANUAL = ["wheel", "touchmove", "keydown"];
|
|
353
|
+
var LIST = ".fg-css-vars__list";
|
|
354
|
+
function readLine(panel) {
|
|
355
|
+
return (panel.querySelector(LIST) ?? panel).getBoundingClientRect().top;
|
|
356
|
+
}
|
|
357
|
+
function sectionsOf(panel) {
|
|
358
|
+
return [...panel.querySelectorAll("[data-token-category]")];
|
|
359
|
+
}
|
|
360
|
+
function SectionJump() {
|
|
361
|
+
const ref = useRef(null);
|
|
362
|
+
const [present, setPresent] = useState([]);
|
|
363
|
+
const [current, setCurrent] = useState(null);
|
|
364
|
+
const held = useRef(false);
|
|
365
|
+
useEffect(() => {
|
|
366
|
+
const root = ref.current?.closest(HOST) ?? document.body;
|
|
367
|
+
const read = () => {
|
|
368
|
+
const panel = root.querySelector(PANEL);
|
|
369
|
+
const sections = panel ? sectionsOf(panel) : [];
|
|
370
|
+
setPresent(sections.map((s) => s.dataset.tokenCategory));
|
|
371
|
+
if (!panel || held.current) return;
|
|
372
|
+
const line = readLine(panel) + 1;
|
|
373
|
+
let top = sections[0]?.dataset.tokenCategory;
|
|
374
|
+
for (const s of sections) if (s.getBoundingClientRect().top <= line) top = s.dataset.tokenCategory;
|
|
375
|
+
setCurrent(top ?? null);
|
|
376
|
+
};
|
|
377
|
+
const release = () => {
|
|
378
|
+
held.current = false;
|
|
379
|
+
};
|
|
380
|
+
read();
|
|
381
|
+
const observer = new MutationObserver(read);
|
|
382
|
+
observer.observe(root, { childList: true, subtree: true });
|
|
383
|
+
document.addEventListener("scroll", read, { capture: true, passive: true });
|
|
384
|
+
for (const type of MANUAL) document.addEventListener(type, release, { capture: true, passive: true });
|
|
385
|
+
return () => {
|
|
386
|
+
observer.disconnect();
|
|
387
|
+
document.removeEventListener("scroll", read, { capture: true });
|
|
388
|
+
for (const type of MANUAL) document.removeEventListener(type, release, { capture: true });
|
|
389
|
+
};
|
|
390
|
+
}, []);
|
|
391
|
+
const items = TOKEN_CATEGORIES.map(({ id, title }) => ({
|
|
392
|
+
value: id,
|
|
393
|
+
label: /* @__PURE__ */ jsx(Icon, { name: ICONS[id], size: 14 }),
|
|
394
|
+
ariaLabel: title,
|
|
395
|
+
tooltip: title,
|
|
396
|
+
disabled: !present.includes(id)
|
|
397
|
+
}));
|
|
398
|
+
const jump = (next) => {
|
|
399
|
+
const panel = ref.current?.closest(HOST)?.querySelector(PANEL);
|
|
400
|
+
const target = next && panel?.querySelector(`[data-token-category="${next}"]`);
|
|
401
|
+
const scroller = panel?.querySelector(LIST);
|
|
402
|
+
if (!panel || !target || !scroller) return;
|
|
403
|
+
held.current = true;
|
|
404
|
+
setCurrent(next);
|
|
405
|
+
scroller.scrollTop += target.getBoundingClientRect().top - readLine(panel);
|
|
406
|
+
};
|
|
407
|
+
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 }) });
|
|
408
|
+
}
|
|
355
409
|
var SEED_REF = /^\{seeds\.([^}]+)\}$/;
|
|
356
410
|
var RULE_KEYS = { factors: "factors", ratio: "ratio", step: "step" };
|
|
357
411
|
function selectionFor(axes, globals) {
|
|
@@ -365,24 +419,24 @@ function selectionFor(axes, globals) {
|
|
|
365
419
|
}
|
|
366
420
|
var EditError = class extends Error {
|
|
367
421
|
};
|
|
368
|
-
function
|
|
369
|
-
|
|
370
|
-
let
|
|
371
|
-
let k = key;
|
|
372
|
-
let w = where;
|
|
373
|
-
let value = h[k];
|
|
374
|
-
while (isByAxis(value)) {
|
|
422
|
+
function branchOf(holder, key, where, selection) {
|
|
423
|
+
let slot = { holder, key, where };
|
|
424
|
+
for (let value = holder[key]; isByAxis(value); value = slot.holder[slot.key]) {
|
|
375
425
|
const axis = value.by;
|
|
376
426
|
const picked = selection[axis];
|
|
377
427
|
if (picked === void 0 || picked === "by" || !(picked in value)) {
|
|
378
|
-
throw new EditError(`${
|
|
428
|
+
throw new EditError(`${slot.where} differs by ${axis}, and which ${axis} this trial shows is not known`);
|
|
379
429
|
}
|
|
380
|
-
|
|
381
|
-
k = picked;
|
|
382
|
-
w = `${w}.${picked}`;
|
|
383
|
-
value = h[k];
|
|
430
|
+
slot = { holder: value, key: picked, where: `${slot.where}.${picked}` };
|
|
384
431
|
}
|
|
385
|
-
|
|
432
|
+
return slot;
|
|
433
|
+
}
|
|
434
|
+
function slotOf(root, holder, key, where, selection, depth = 0) {
|
|
435
|
+
if (depth > 16) throw new EditError(`${where} references itself`);
|
|
436
|
+
const slot = branchOf(holder, key, where, selection);
|
|
437
|
+
const { holder: h, key: k, where: w } = slot;
|
|
438
|
+
const value = h[k];
|
|
439
|
+
if (typeof value === "number") return slot;
|
|
386
440
|
if (typeof value === "string") {
|
|
387
441
|
const ref = SEED_REF.exec(value);
|
|
388
442
|
if (!ref) throw new EditError(`${w} is "${value}", which is not a number or a seed`);
|
|
@@ -394,18 +448,10 @@ function slotOf(root, holder, key, where, selection, depth = 0) {
|
|
|
394
448
|
throw new EditError(`${w} is not a number`);
|
|
395
449
|
}
|
|
396
450
|
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 };
|
|
451
|
+
const slot = branchOf(holder, "factors", `${where}.factors`, selection);
|
|
452
|
+
const value = slot.holder[slot.key];
|
|
453
|
+
if (!Array.isArray(value)) throw new EditError(`${slot.where} is not a list`);
|
|
454
|
+
return { list: value, where: slot.where };
|
|
409
455
|
}
|
|
410
456
|
var kindOf = (scale) => scale.factors !== void 0 ? "factors" : scale.ratio !== void 0 ? "ratio" : scale.step !== void 0 ? "step" : null;
|
|
411
457
|
var plainLiteral = (value) => typeof value === "number" || Array.isArray(value) && value.every((v) => typeof v === "number");
|
|
@@ -461,90 +507,219 @@ function applyScaleEdits(definition, edits, selection) {
|
|
|
461
507
|
for (const [holder, byKey] of writes) for (const [key, value] of byKey) holder[key] = value;
|
|
462
508
|
return { ok: true, definition: root };
|
|
463
509
|
}
|
|
464
|
-
var
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const
|
|
478
|
-
|
|
510
|
+
var REFERENCE = /\{[^}]+\}/;
|
|
511
|
+
var LAYERS = ["pins", "semantics", "components"];
|
|
512
|
+
function derivation(entry) {
|
|
513
|
+
const value = isPlainObject(entry) ? entry.value : entry;
|
|
514
|
+
if (isPlainObject(entry) && value === void 0) return "follows a rule";
|
|
515
|
+
if (isPlainObject(entry) && entry.alpha !== void 0) return "is drawn at an alpha";
|
|
516
|
+
if (typeof value === "string" && REFERENCE.test(value)) return `follows ${REFERENCE.exec(value)?.[0]}`;
|
|
517
|
+
return typeof value === "string" || typeof value === "number" ? null : "is a list";
|
|
518
|
+
}
|
|
519
|
+
function literalSlot(definition, token, selection) {
|
|
520
|
+
for (const layer of LAYERS) {
|
|
521
|
+
const entries = definition[layer];
|
|
522
|
+
if (!entries || !(token in entries)) continue;
|
|
523
|
+
const slot = branchOf(entries, token, `${layer}.${token}`, selection);
|
|
524
|
+
const why = derivation(slot.holder[slot.key]);
|
|
525
|
+
if (why) throw new EditError(`${slot.where} ${why}, and only a value written out in the theme can be saved`);
|
|
526
|
+
return slot;
|
|
479
527
|
}
|
|
480
|
-
|
|
528
|
+
for (const layer of ["ramps", "scales"]) {
|
|
529
|
+
const from = Object.keys(definition[layer] ?? {}).find((name) => token.startsWith(`${name}-`));
|
|
530
|
+
if (from) throw new EditError(`${token} is a step ${layer}.${from} generates, and the theme pins no value over it`);
|
|
531
|
+
}
|
|
532
|
+
throw new EditError(`the theme defines no token "${token}"`);
|
|
481
533
|
}
|
|
482
|
-
function
|
|
483
|
-
|
|
534
|
+
function writtenOut(definition, token, selection) {
|
|
535
|
+
try {
|
|
536
|
+
literalSlot(definition, token, selection);
|
|
537
|
+
return true;
|
|
538
|
+
} catch (e) {
|
|
539
|
+
if (e instanceof EditError) return false;
|
|
540
|
+
throw e;
|
|
541
|
+
}
|
|
484
542
|
}
|
|
485
|
-
function
|
|
486
|
-
|
|
543
|
+
function applyTokenEdits(definition, edits, selection) {
|
|
544
|
+
const root = structuredClone(definition);
|
|
545
|
+
try {
|
|
546
|
+
for (const [token, value] of Object.entries(edits)) {
|
|
547
|
+
const { holder, key } = literalSlot(root, token, selection);
|
|
548
|
+
const entry = holder[key];
|
|
549
|
+
holder[key] = isPlainObject(entry) ? { ...entry, value } : value;
|
|
550
|
+
}
|
|
551
|
+
} catch (e) {
|
|
552
|
+
if (e instanceof EditError) return { ok: false, message: `Cannot save: ${e.message}.` };
|
|
553
|
+
throw e;
|
|
554
|
+
}
|
|
555
|
+
return { ok: true, definition: root };
|
|
487
556
|
}
|
|
488
|
-
function
|
|
489
|
-
const
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
557
|
+
function useTrialSelection(axes, globals) {
|
|
558
|
+
const { mode } = globals;
|
|
559
|
+
const osMode = useResolvedColorMode(isColorModePreference(mode) ? mode : "auto");
|
|
560
|
+
return useMemo(() => {
|
|
561
|
+
const selection = selectionFor(axes, globals);
|
|
562
|
+
return axes.mode && osMode in axes.mode.values ? { mode: osMode, ...selection } : selection;
|
|
563
|
+
}, [axes, globals, osMode]);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// src/shell/cssVars/useThemeSave.ts
|
|
567
|
+
var THEME_NAME = "weasel";
|
|
568
|
+
var TOKEN_PREFIX = "--wzl-";
|
|
569
|
+
var NO_AXES = {};
|
|
570
|
+
function useThemeSave({ trialId, globals, themeApi, scales, overrides, history: history2 }) {
|
|
571
|
+
const definition = THEME_SOURCES[THEME_NAME];
|
|
572
|
+
const selection = useTrialSelection(definition?.axes ?? NO_AXES, globals);
|
|
573
|
+
const tokens = useMemo(() => {
|
|
574
|
+
if (!definition) return {};
|
|
575
|
+
const generated = new Set(scales.flatMap((scale) => scale.tokens));
|
|
576
|
+
return Object.fromEntries(
|
|
577
|
+
Object.entries(overrides).flatMap(([name, value]) => {
|
|
578
|
+
if (!name.startsWith(TOKEN_PREFIX) || generated.has(name)) return [];
|
|
579
|
+
const token = name.slice(TOKEN_PREFIX.length);
|
|
580
|
+
return writtenOut(definition, token, selection) ? [[token, value]] : [];
|
|
581
|
+
})
|
|
582
|
+
);
|
|
583
|
+
}, [definition, selection, scales, overrides]);
|
|
584
|
+
const [saving, setSaving] = useState(false);
|
|
585
|
+
const [held, setHeld] = useState(null);
|
|
586
|
+
const setNotice = (next) => setHeld(next && { ...next, trialId });
|
|
587
|
+
const save = async () => {
|
|
588
|
+
setSaving(true);
|
|
589
|
+
setNotice(null);
|
|
590
|
+
try {
|
|
591
|
+
const api = themeApi ?? httpThemeApi();
|
|
592
|
+
const stored = await api.get(THEME_NAME);
|
|
593
|
+
const file = `themes/${THEME_NAME}.json`;
|
|
594
|
+
if (JSON.stringify(stored.definition) !== JSON.stringify(THEME_SOURCES[THEME_NAME])) {
|
|
595
|
+
setNotice({ tone: "error", text: `${file} has changed since this page loaded. Reload to edit against it.` });
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const scaled = applyScaleEdits(stored.definition, Object.fromEntries(scales.map((s) => [s.name, s.edit])), selection);
|
|
599
|
+
const next = scaled.ok ? applyTokenEdits(scaled.definition, tokens, selection) : scaled;
|
|
600
|
+
if (!next.ok) {
|
|
601
|
+
setNotice({ tone: "error", text: next.message });
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const result = await api.put(THEME_NAME, next.definition, stored.hash);
|
|
605
|
+
if (result.status === "conflict") {
|
|
606
|
+
setNotice({ tone: "error", text: `${file} changed on disk while saving. Nothing was written; reload to edit against it.` });
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (result.status === "invalid") {
|
|
610
|
+
setNotice({ tone: "error", text: result.message });
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const saved = /* @__PURE__ */ new Set([
|
|
614
|
+
...scales.flatMap((scale) => scale.tokens),
|
|
615
|
+
...Object.keys(tokens).map((token) => `${TOKEN_PREFIX}${token}`)
|
|
616
|
+
]);
|
|
617
|
+
const groups = new Set(scales.map((scale) => scale.group));
|
|
618
|
+
const now = history2.current();
|
|
619
|
+
history2.reset({
|
|
620
|
+
overrides: Object.fromEntries(Object.entries(now.overrides).filter(([name]) => !saved.has(name))),
|
|
621
|
+
scales: Object.fromEntries(Object.entries(now.scales).filter(([group]) => !groups.has(group)))
|
|
622
|
+
});
|
|
623
|
+
setNotice(
|
|
624
|
+
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("; ")}` }
|
|
625
|
+
);
|
|
626
|
+
} catch (e) {
|
|
627
|
+
setNotice({ tone: "error", text: `Saving needs forge's dev server, which did not answer: ${e.message}` });
|
|
628
|
+
} finally {
|
|
629
|
+
setSaving(false);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
return {
|
|
633
|
+
pending: scales.length > 0 || Object.keys(tokens).length > 0,
|
|
634
|
+
saving,
|
|
635
|
+
notice: held?.trialId === trialId ? held : null,
|
|
636
|
+
save
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
var AXES = themeAxes(weaselTheme);
|
|
640
|
+
function useThemeTokens(globals, vars) {
|
|
641
|
+
const selection = useTrialSelection(AXES, globals);
|
|
642
|
+
const themed = useMemo(() => resolveTheme(weaselTheme, selection), [selection]);
|
|
643
|
+
return useMemo(() => {
|
|
644
|
+
const reported = new Map(vars.map((v) => [v.name, v.value]));
|
|
645
|
+
return TOKEN_MANIFEST.map((token) => ({
|
|
646
|
+
name: token.name,
|
|
647
|
+
type: token.type,
|
|
648
|
+
group: token.group,
|
|
649
|
+
value: reported.get(token.name) ?? themed[token.name] ?? token.defaultValue,
|
|
650
|
+
description: token.description
|
|
651
|
+
}));
|
|
652
|
+
}, [vars, themed]);
|
|
653
|
+
}
|
|
654
|
+
var SWAP = "fg.vars.swap";
|
|
655
|
+
var COALESCE_MS = 800;
|
|
656
|
+
var LIMIT = 100;
|
|
657
|
+
function useVarsHistory(state, apply) {
|
|
658
|
+
const trialId = useTrialId();
|
|
659
|
+
const [stored, setStored] = usePersistedState("fg-css-vars-history", null, { scope: "trial" });
|
|
660
|
+
const rendered = useLatest(state);
|
|
661
|
+
const ahead = useRef(null);
|
|
493
662
|
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
|
-
|
|
663
|
+
ahead.current = null;
|
|
664
|
+
}, [state]);
|
|
665
|
+
const current = () => ahead.current ?? rendered.current;
|
|
666
|
+
const latestApply = useLatest(apply);
|
|
667
|
+
const emit = (to) => {
|
|
668
|
+
const from = current();
|
|
669
|
+
ahead.current = to;
|
|
670
|
+
latestApply.current(from, to);
|
|
671
|
+
};
|
|
672
|
+
const swap = (before, after, coalesceKey) => swapOp({ name: SWAP, label: "edit vars", before, after, emit, ...coalesceKey !== void 0 ? { coalesceKey } : {} });
|
|
673
|
+
const history2 = useMemo(() => {
|
|
674
|
+
const opened = createHistory(null, {
|
|
675
|
+
coalesceWindowMs: COALESCE_MS,
|
|
676
|
+
historyLimit: LIMIT,
|
|
677
|
+
rebuildOp: (name, args) => {
|
|
678
|
+
const { before, after } = args;
|
|
679
|
+
return name === SWAP ? swap(before, after) : null;
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
if (stored) opened.restore(stored);
|
|
683
|
+
return opened;
|
|
684
|
+
}, [trialId]);
|
|
685
|
+
useSyncExternalStore(history2.subscribe, history2.getVersion);
|
|
686
|
+
const keep = () => setStored(history2.canUndo() || history2.canRedo() ? history2.serialize() : null);
|
|
687
|
+
const tick = useRef(null);
|
|
688
|
+
return {
|
|
689
|
+
current,
|
|
690
|
+
commit(next, key) {
|
|
691
|
+
if (tick.current === null) {
|
|
692
|
+
tick.current = key;
|
|
693
|
+
queueMicrotask(() => {
|
|
694
|
+
tick.current = null;
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
history2.applyOps([swap(current(), next, tick.current)], "edit vars");
|
|
698
|
+
keep();
|
|
699
|
+
},
|
|
700
|
+
step(to) {
|
|
701
|
+
history2[to]();
|
|
702
|
+
keep();
|
|
703
|
+
},
|
|
704
|
+
canUndo: history2.canUndo(),
|
|
705
|
+
canRedo: history2.canRedo(),
|
|
706
|
+
reset(next) {
|
|
707
|
+
emit(next);
|
|
708
|
+
history2.clear();
|
|
709
|
+
setStored(null);
|
|
710
|
+
}
|
|
534
711
|
};
|
|
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
712
|
}
|
|
537
713
|
var TABS = [
|
|
538
714
|
{ value: "theme", label: "Theme" },
|
|
539
715
|
{ value: "story", label: "Story" }
|
|
540
716
|
];
|
|
541
717
|
var MANIFEST = new Map(TOKEN_MANIFEST.map((token) => [token.name, token]));
|
|
542
|
-
var THEME_NAME = "weasel";
|
|
543
718
|
var literal = (v) => typeof v === "number";
|
|
544
719
|
var SCALE_SOURCES = Object.entries(THEME_SOURCES[THEME_NAME]?.scales ?? {}).flatMap(([name, def]) => {
|
|
545
720
|
if (!Array.isArray(def.steps)) return [];
|
|
546
721
|
const steps = def.steps;
|
|
547
|
-
const tokens = steps.map((step) =>
|
|
722
|
+
const tokens = steps.map((step) => `${TOKEN_PREFIX}${name}-${step}`);
|
|
548
723
|
const group = MANIFEST.get(tokens[0] ?? "")?.group;
|
|
549
724
|
if (!group) return [];
|
|
550
725
|
const factors = Array.isArray(def.factors) && def.factors.every(literal) ? def.factors : null;
|
|
@@ -589,25 +764,17 @@ function CssVarsPanel({ globals = NO_GLOBALS, themeApi } = {}) {
|
|
|
589
764
|
{},
|
|
590
765
|
{ scope: "lab" }
|
|
591
766
|
);
|
|
592
|
-
const [edited, setEdited] = usePersistedState("fg-css-vars-scales", {}, { scope: "
|
|
767
|
+
const [edited, setEdited] = usePersistedState("fg-css-vars-scales", {}, { scope: "trial" });
|
|
593
768
|
const [tab, setTab] = useState("theme");
|
|
594
769
|
const [filter, setFilter] = useState("");
|
|
770
|
+
const themeTokens = useThemeTokens(globals, frame.vars);
|
|
595
771
|
const tokens = useMemo(() => {
|
|
596
|
-
if (tab === "
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const reported = new Map(frame.vars.map((v) => [v.name, v.value]));
|
|
603
|
-
return TOKEN_MANIFEST.map((token) => ({
|
|
604
|
-
name: token.name,
|
|
605
|
-
type: token.type,
|
|
606
|
-
group: token.group,
|
|
607
|
-
value: reported.get(token.name) ?? token.defaultValue,
|
|
608
|
-
description: token.description
|
|
609
|
-
}));
|
|
610
|
-
}, [tab, frame.vars]);
|
|
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]);
|
|
611
778
|
const query = filter.trim().toLowerCase();
|
|
612
779
|
const shown = tokens.map((token) => {
|
|
613
780
|
const override = overrides[token.name];
|
|
@@ -626,135 +793,115 @@ function CssVarsPanel({ globals = NO_GLOBALS, themeApi } = {}) {
|
|
|
626
793
|
return out;
|
|
627
794
|
}, [tab, shown, overrides, edited]);
|
|
628
795
|
const send = frame.send;
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
(
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
796
|
+
const state = useMemo(() => ({ overrides, scales: edited }), [overrides, edited]);
|
|
797
|
+
const history2 = useVarsHistory(state, (from, to) => {
|
|
798
|
+
setOverrides(to.overrides);
|
|
799
|
+
setEdited(to.scales);
|
|
800
|
+
for (const name of /* @__PURE__ */ new Set([...Object.keys(from.overrides), ...Object.keys(to.overrides)])) {
|
|
801
|
+
if (from.overrides[name] !== to.overrides[name]) send?.({ type: "vars.set", name, value: to.overrides[name] ?? null });
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
const write = (name, value) => {
|
|
805
|
+
const now = history2.current();
|
|
806
|
+
const { [name]: _dropped, ...rest } = now.overrides;
|
|
807
|
+
history2.commit({ ...now, overrides: value === null ? rest : { ...rest, [name]: value } }, `token:${name}`);
|
|
808
|
+
};
|
|
809
|
+
const onScaleChange = (group, next) => {
|
|
810
|
+
const source = SCALE_SOURCES.find((sc) => sc.group === group);
|
|
811
|
+
if (!source) return;
|
|
812
|
+
const now = history2.current();
|
|
813
|
+
history2.commit(
|
|
814
|
+
{ overrides: { ...now.overrides, ...Object.fromEntries(scaleValues(source, next)) }, scales: { ...now.scales, [group]: next } },
|
|
815
|
+
`scale:${group}`
|
|
816
|
+
);
|
|
817
|
+
};
|
|
648
818
|
const unsaved = useMemo(
|
|
649
819
|
() => SCALE_SOURCES.flatMap((source) => {
|
|
650
820
|
const edit = edited[source.group];
|
|
651
|
-
if (!edit || !source.tokens.some((
|
|
652
|
-
const
|
|
653
|
-
|
|
821
|
+
if (!edit || !source.tokens.some((name2) => overrides[name2] !== void 0)) return [];
|
|
822
|
+
const { name, group, tokens: tokens2 } = source;
|
|
823
|
+
const clean = scaleValues(source, edit).every(([token, value]) => overrides[token] === value);
|
|
824
|
+
return clean ? [{ name, group, tokens: tokens2, edit: { base: edit.base, rule: edit.rule } }] : [];
|
|
654
825
|
}),
|
|
655
826
|
[edited, overrides]
|
|
656
827
|
);
|
|
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
|
-
};
|
|
828
|
+
const { pending, saving, notice, save } = useThemeSave({ trialId, globals, themeApi, scales: unsaved, overrides, history: history2 });
|
|
701
829
|
const onCollapsedChange = useCallback(
|
|
702
830
|
(category, collapsed) => setFolds((prev) => ({ ...prev, [category]: collapsed })),
|
|
703
831
|
[setFolds]
|
|
704
832
|
);
|
|
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,
|
|
833
|
+
return (
|
|
834
|
+
// Capture: React Aria's fields stop a keydown from bubbling past them.
|
|
835
|
+
/* @__PURE__ */ jsxs(
|
|
836
|
+
"div",
|
|
747
837
|
{
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
838
|
+
className: "fg-css-vars",
|
|
839
|
+
onKeyDownCapture: (e) => {
|
|
840
|
+
const to = e.target.closest(".fg-css-vars__filter") ? null : historyKey(e);
|
|
841
|
+
if (!to) return;
|
|
842
|
+
e.preventDefault();
|
|
843
|
+
history2.step(to);
|
|
844
|
+
},
|
|
845
|
+
children: [
|
|
846
|
+
/* @__PURE__ */ jsxs("div", { className: "fg-css-vars__controls", children: [
|
|
847
|
+
/* @__PURE__ */ jsxs("div", { className: "fg-css-vars__bar", children: [
|
|
848
|
+
/* @__PURE__ */ jsx(
|
|
849
|
+
ToggleBar$1,
|
|
850
|
+
{
|
|
851
|
+
ariaLabel: "Variables",
|
|
852
|
+
variant: "flat",
|
|
853
|
+
size: "sm",
|
|
854
|
+
items: TABS,
|
|
855
|
+
value: tab,
|
|
856
|
+
onChange: (next) => {
|
|
857
|
+
if (next) setTab(next);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
),
|
|
861
|
+
/* @__PURE__ */ jsx(
|
|
862
|
+
Input,
|
|
863
|
+
{
|
|
864
|
+
className: "fg-css-vars__filter",
|
|
865
|
+
"aria-label": "Filter",
|
|
866
|
+
placeholder: "Filter",
|
|
867
|
+
value: filter,
|
|
868
|
+
onChange: setFilter,
|
|
869
|
+
leadingAdornment: /* @__PURE__ */ jsx(SearchIcon, { size: 12 })
|
|
870
|
+
}
|
|
871
|
+
),
|
|
872
|
+
/* @__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 }) })
|
|
875
|
+
] })
|
|
876
|
+
] }),
|
|
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
|
|
888
|
+
] }),
|
|
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(
|
|
890
|
+
TokenPanel,
|
|
891
|
+
{
|
|
892
|
+
tokens: shown,
|
|
893
|
+
namePrefix: TOKEN_PREFIX,
|
|
894
|
+
onChange: write,
|
|
895
|
+
scales,
|
|
896
|
+
onScaleChange,
|
|
897
|
+
collapsed: folds,
|
|
898
|
+
onCollapsedChange
|
|
899
|
+
}
|
|
900
|
+
) })
|
|
901
|
+
]
|
|
755
902
|
}
|
|
756
903
|
)
|
|
757
|
-
|
|
904
|
+
);
|
|
758
905
|
}
|
|
759
906
|
function FocusedCssVars() {
|
|
760
907
|
const { focusedTrialId, trials, instruments } = useLabContext();
|
|
@@ -771,7 +918,7 @@ function FocusedCssVars() {
|
|
|
771
918
|
var CSS_VARS_SECTION = {
|
|
772
919
|
id: "fg-css-vars",
|
|
773
920
|
region: "aside",
|
|
774
|
-
item: { title: "CSS Vars", actions: /* @__PURE__ */ jsx(SectionJump, {}), body: /* @__PURE__ */ jsx(FocusedCssVars, {}) }
|
|
921
|
+
item: { title: "CSS Vars", actions: /* @__PURE__ */ jsx(SectionJump, {}), fill: true, body: /* @__PURE__ */ jsx(FocusedCssVars, {}) }
|
|
775
922
|
};
|
|
776
923
|
var GLOBALS = "$globals";
|
|
777
924
|
function leaves(shape, prefix = "") {
|
|
@@ -905,10 +1052,10 @@ var whenIdle = (fn) => {
|
|
|
905
1052
|
return () => clearTimeout(handle);
|
|
906
1053
|
};
|
|
907
1054
|
function createFramePool(frameUrl, size = 2) {
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
document.body.append(
|
|
1055
|
+
const shelf2 = document.createElement("div");
|
|
1056
|
+
shelf2.className = "fg-frame-pool";
|
|
1057
|
+
shelf2.setAttribute("aria-hidden", "true");
|
|
1058
|
+
document.body.append(shelf2);
|
|
912
1059
|
const warm = [];
|
|
913
1060
|
const waits = /* @__PURE__ */ new Set();
|
|
914
1061
|
let cancelRefill = null;
|
|
@@ -935,7 +1082,7 @@ function createFramePool(frameUrl, size = 2) {
|
|
|
935
1082
|
}, HELLO_TIMEOUT_MS);
|
|
936
1083
|
waits.add(stop);
|
|
937
1084
|
window.addEventListener("message", onMessage);
|
|
938
|
-
|
|
1085
|
+
shelf2.append(frame);
|
|
939
1086
|
};
|
|
940
1087
|
const refill = () => {
|
|
941
1088
|
if (disposed || cancelRefill) return;
|
|
@@ -956,7 +1103,7 @@ function createFramePool(frameUrl, size = 2) {
|
|
|
956
1103
|
disposed = true;
|
|
957
1104
|
cancelRefill?.();
|
|
958
1105
|
for (const stop of [...waits]) stop();
|
|
959
|
-
|
|
1106
|
+
shelf2.remove();
|
|
960
1107
|
}
|
|
961
1108
|
};
|
|
962
1109
|
}
|
|
@@ -966,7 +1113,7 @@ var FramePoolContext = createContext(null);
|
|
|
966
1113
|
function routedTrial(trials, focusedTrialId, route) {
|
|
967
1114
|
return trials.find((t) => t.id === focusedTrialId && t.instrumentName === route) ?? trials.find((t) => t.instrumentName === route);
|
|
968
1115
|
}
|
|
969
|
-
function PlayheadSync({ trialId, route,
|
|
1116
|
+
function PlayheadSync({ trialId, route, appliedRef }) {
|
|
970
1117
|
const clock = useTrialClock(trialId);
|
|
971
1118
|
useEffect(() => {
|
|
972
1119
|
if (!clock) return;
|
|
@@ -984,8 +1131,8 @@ function PlayheadSync({ trialId, route, applied }) {
|
|
|
984
1131
|
held = next;
|
|
985
1132
|
writePlayheadParam(next);
|
|
986
1133
|
};
|
|
987
|
-
if (
|
|
988
|
-
|
|
1134
|
+
if (appliedRef.current !== route) {
|
|
1135
|
+
appliedRef.current = route;
|
|
989
1136
|
fromUrl();
|
|
990
1137
|
}
|
|
991
1138
|
toUrl();
|
|
@@ -1001,7 +1148,7 @@ function PlayheadSync({ trialId, route, applied }) {
|
|
|
1001
1148
|
off();
|
|
1002
1149
|
window.removeEventListener("hashchange", onHashChange);
|
|
1003
1150
|
};
|
|
1004
|
-
}, [clock, route,
|
|
1151
|
+
}, [clock, route, appliedRef]);
|
|
1005
1152
|
return null;
|
|
1006
1153
|
}
|
|
1007
1154
|
function RoutePlayhead() {
|
|
@@ -1010,7 +1157,7 @@ function RoutePlayhead() {
|
|
|
1010
1157
|
const applied = useRef(null);
|
|
1011
1158
|
const trial = route === null ? void 0 : routedTrial(lab.trials, lab.focusedTrialId, route);
|
|
1012
1159
|
if (!trial || route === null) return null;
|
|
1013
|
-
return /* @__PURE__ */ jsx(PlayheadSync, { trialId: trial.id, route, applied }, `${trial.id} ${route}`);
|
|
1160
|
+
return /* @__PURE__ */ jsx(PlayheadSync, { trialId: trial.id, route, appliedRef: applied }, `${trial.id} ${route}`);
|
|
1014
1161
|
}
|
|
1015
1162
|
var RECORD = "fg-globals";
|
|
1016
1163
|
function useLabGlobals(declarations) {
|
|
@@ -1350,13 +1497,13 @@ function StoryTree({ ctx, index }) {
|
|
|
1350
1497
|
}, [index]);
|
|
1351
1498
|
const enabled = (value) => views.some((item) => item.value === value && !item.disabled);
|
|
1352
1499
|
const view = [chosen, "components", "gallery"].find(enabled) ?? chosen;
|
|
1353
|
-
const
|
|
1500
|
+
const kept2 = useMemo(() => index.filter((entry) => !hidden[table.of(entry)]), [index, hidden, table]);
|
|
1354
1501
|
const [galleryEntries, rest] = useMemo(() => {
|
|
1355
1502
|
const yes = [];
|
|
1356
1503
|
const no = [];
|
|
1357
|
-
for (const entry of
|
|
1504
|
+
for (const entry of kept2) (isGallery(entry) ? yes : no).push(entry);
|
|
1358
1505
|
return [yes, no];
|
|
1359
|
-
}, [
|
|
1506
|
+
}, [kept2]);
|
|
1360
1507
|
const tree = useMemo(() => buildTree(rest), [rest]);
|
|
1361
1508
|
const components = useMemo(() => buildComponents(rest, table), [rest, table]);
|
|
1362
1509
|
const galleries = useMemo(() => buildComponents(galleryEntries, table), [galleryEntries, table]);
|
|
@@ -1364,7 +1511,7 @@ function StoryTree({ ctx, index }) {
|
|
|
1364
1511
|
() => view === "tree" ? filterTree(tree, query) : componentNodes(filterComponents(view === "gallery" ? galleries : components, query)),
|
|
1365
1512
|
[view, components, galleries, tree, query]
|
|
1366
1513
|
);
|
|
1367
|
-
const routable = useMemo(() => [...
|
|
1514
|
+
const routable = useMemo(() => [...kept2, ...indexEntries(kept2)], [kept2]);
|
|
1368
1515
|
const routed = routable.find((entry) => entry.id === route);
|
|
1369
1516
|
const routeAncestors = useMemo(() => ancestorsOf(routed), [routed]);
|
|
1370
1517
|
const filtering = query.trim() !== "";
|
|
@@ -1615,13 +1762,11 @@ function StoryTree({ ctx, index }) {
|
|
|
1615
1762
|
/* @__PURE__ */ jsx("div", { role: "tree", "aria-labelledby": headingId, className: "fg-tree__items", onKeyDown, children: renderNodes(shown, 1) })
|
|
1616
1763
|
] });
|
|
1617
1764
|
}
|
|
1618
|
-
|
|
1619
|
-
// src/shell/answers.ts
|
|
1620
1765
|
var KEPT_CONFIGS = 50;
|
|
1621
1766
|
var UNANSWERED = stableStringify({ hidden: [], errors: {} });
|
|
1622
1767
|
function createAnswerBook() {
|
|
1623
1768
|
const byConfig = /* @__PURE__ */ new Map();
|
|
1624
|
-
const
|
|
1769
|
+
const answered = createEmitter();
|
|
1625
1770
|
const held = /* @__PURE__ */ new Map();
|
|
1626
1771
|
const keyOf2 = (config) => stableStringify(storyConfig(config));
|
|
1627
1772
|
const answerFor = (config) => byConfig.get(keyOf2(config));
|
|
@@ -1635,7 +1780,7 @@ function createAnswerBook() {
|
|
|
1635
1780
|
const oldest = byConfig.keys().next().value;
|
|
1636
1781
|
if (oldest !== void 0) byConfig.delete(oldest);
|
|
1637
1782
|
}
|
|
1638
|
-
if (changed && held.has(answers.configKey))
|
|
1783
|
+
if (changed && held.has(answers.configKey)) answered.emit();
|
|
1639
1784
|
},
|
|
1640
1785
|
hold(config) {
|
|
1641
1786
|
const key = keyOf2(config);
|
|
@@ -1651,17 +1796,60 @@ function createAnswerBook() {
|
|
|
1651
1796
|
return hidden?.some((h) => path === h || path.startsWith(`${h}.`)) ?? false;
|
|
1652
1797
|
},
|
|
1653
1798
|
errors: (path, config) => answerFor(config)?.errors[path] ?? [],
|
|
1654
|
-
subscribe
|
|
1655
|
-
listeners2.add(fn);
|
|
1656
|
-
return () => {
|
|
1657
|
-
listeners2.delete(fn);
|
|
1658
|
-
};
|
|
1659
|
-
}
|
|
1799
|
+
subscribe: answered.subscribe
|
|
1660
1800
|
};
|
|
1661
1801
|
}
|
|
1662
1802
|
|
|
1663
1803
|
// src/shell/readyKey.ts
|
|
1664
1804
|
var readyKey = (ready) => stableStringify({ schema: ready.schema, layout: ready.layout, viewport: ready.viewport });
|
|
1805
|
+
var kept = /* @__PURE__ */ new Map();
|
|
1806
|
+
var shelf = null;
|
|
1807
|
+
function shelfElement() {
|
|
1808
|
+
if (!shelf) {
|
|
1809
|
+
shelf = document.createElement("div");
|
|
1810
|
+
shelf.className = "fg-frame-pool";
|
|
1811
|
+
shelf.setAttribute("aria-hidden", "true");
|
|
1812
|
+
}
|
|
1813
|
+
if (!shelf.isConnected) document.body.append(shelf);
|
|
1814
|
+
return shelf;
|
|
1815
|
+
}
|
|
1816
|
+
function adopt({ key, slot, frame, link, onAdopt }) {
|
|
1817
|
+
const waiting = key === null ? void 0 : kept.get(key);
|
|
1818
|
+
if (key === null || !waiting || !slot.current) return;
|
|
1819
|
+
kept.delete(key);
|
|
1820
|
+
moveFrame(slot.current, waiting.frame);
|
|
1821
|
+
frame.current = waiting.frame;
|
|
1822
|
+
link.current = waiting.link;
|
|
1823
|
+
onAdopt(waiting.link);
|
|
1824
|
+
}
|
|
1825
|
+
function shelve(options) {
|
|
1826
|
+
const { key, frame, link, onDrop } = options.current;
|
|
1827
|
+
const element = frame.current;
|
|
1828
|
+
const current = link.current;
|
|
1829
|
+
if (key === null || !element?.isConnected || !current || !canMoveFrames()) return false;
|
|
1830
|
+
if (current.awaiting === null && current.sent === null) return false;
|
|
1831
|
+
const entry = { frame: element, link: current };
|
|
1832
|
+
moveFrame(shelfElement(), element);
|
|
1833
|
+
kept.set(key, entry);
|
|
1834
|
+
queueMicrotask(() => {
|
|
1835
|
+
if (kept.get(key) !== entry) return;
|
|
1836
|
+
kept.delete(key);
|
|
1837
|
+
onDrop();
|
|
1838
|
+
element.remove();
|
|
1839
|
+
});
|
|
1840
|
+
return true;
|
|
1841
|
+
}
|
|
1842
|
+
function useKeptFrame(options) {
|
|
1843
|
+
const handedOn = useRef(false);
|
|
1844
|
+
const latest = useLatest(options);
|
|
1845
|
+
useLayoutEffect(() => {
|
|
1846
|
+
adopt(latest.current);
|
|
1847
|
+
return () => {
|
|
1848
|
+
if (shelve(latest)) handedOn.current = true;
|
|
1849
|
+
};
|
|
1850
|
+
}, [latest]);
|
|
1851
|
+
return useCallback(() => handedOn.current, []);
|
|
1852
|
+
}
|
|
1665
1853
|
var START_TIMEOUT_MS = 1e4;
|
|
1666
1854
|
var requests = 0;
|
|
1667
1855
|
function closeLink(link) {
|
|
@@ -1676,6 +1864,27 @@ function closeLink(link) {
|
|
|
1676
1864
|
function mismatchMessage(mismatch) {
|
|
1677
1865
|
return mismatch.reason === "version" ? `Frame speaks protocol ${String(mismatch.version)}; this workshop speaks ${PROTOCOL_VERSION}` : "Frame sent a message that is not a forge envelope";
|
|
1678
1866
|
}
|
|
1867
|
+
function sendInputs(current, sent, next) {
|
|
1868
|
+
const before = current.inputs;
|
|
1869
|
+
const { state, globals } = next;
|
|
1870
|
+
const config = storyConfig(next.config, sent.config);
|
|
1871
|
+
if (config !== sent.config) {
|
|
1872
|
+
sent.config = config;
|
|
1873
|
+
current.channel.send({ type: "config", config });
|
|
1874
|
+
current.inputs += 1;
|
|
1875
|
+
}
|
|
1876
|
+
if (state !== sent.state) {
|
|
1877
|
+
sent.state = state;
|
|
1878
|
+
current.channel.send({ type: "state", state });
|
|
1879
|
+
current.inputs += 1;
|
|
1880
|
+
}
|
|
1881
|
+
if (globals !== sent.globals && stableStringify(globals) !== stableStringify(sent.globals)) {
|
|
1882
|
+
sent.globals = globals;
|
|
1883
|
+
current.channel.send({ type: "globals", globals });
|
|
1884
|
+
current.inputs += 1;
|
|
1885
|
+
}
|
|
1886
|
+
return current.inputs !== before;
|
|
1887
|
+
}
|
|
1679
1888
|
function FrameView(props) {
|
|
1680
1889
|
const { entry, frameUrl, answers, descriptionKey, ctx } = props;
|
|
1681
1890
|
const labGlobals2 = useContext(StoryGlobalsContext);
|
|
@@ -1693,29 +1902,29 @@ function FrameView(props) {
|
|
|
1693
1902
|
const slotRef = useRef(null);
|
|
1694
1903
|
const inView = useNearViewport(hostRef, IN_VIEW_MARGIN);
|
|
1695
1904
|
const link = useRef(null);
|
|
1696
|
-
const
|
|
1905
|
+
const latestRef = useLatest({ ...props, globals, overrides, pool, lab, title });
|
|
1697
1906
|
const [fault, setFault] = useState(null);
|
|
1698
1907
|
const [pending, setPending] = useState(true);
|
|
1699
1908
|
const init = useCallback((current) => {
|
|
1700
|
-
const { ctx: live, globals: liveGlobals } =
|
|
1909
|
+
const { ctx: live, globals: liveGlobals } = latestRef.current;
|
|
1701
1910
|
current.awaiting = null;
|
|
1702
1911
|
current.sent = { config: storyConfig(live.config), state: live.state, globals: liveGlobals };
|
|
1703
1912
|
current.inputs += 1;
|
|
1704
1913
|
current.channel.send({ type: "init", ...current.sent });
|
|
1705
|
-
}, [
|
|
1914
|
+
}, [latestRef]);
|
|
1706
1915
|
const receive = (current, msg) => {
|
|
1707
|
-
const { ctx: live, answers: answers2, onReady } =
|
|
1916
|
+
const { ctx: live, answers: answers2, onReady } = latestRef.current;
|
|
1708
1917
|
switch (msg.type) {
|
|
1709
1918
|
case "ready": {
|
|
1710
1919
|
clearTimeout(current.timer);
|
|
1711
1920
|
setFault(null);
|
|
1712
|
-
for (const [name, value] of Object.entries(
|
|
1921
|
+
for (const [name, value] of Object.entries(latestRef.current.overrides)) {
|
|
1713
1922
|
current.channel.send({ type: "vars.set", name, value });
|
|
1714
1923
|
}
|
|
1715
1924
|
const key = readyKey(msg);
|
|
1716
1925
|
current.awaiting = key;
|
|
1717
|
-
onReady(
|
|
1718
|
-
if (current.awaiting === key && key ===
|
|
1926
|
+
onReady(latestRef.current.entry, msg);
|
|
1927
|
+
if (current.awaiting === key && key === latestRef.current.descriptionKey) init(current);
|
|
1719
1928
|
break;
|
|
1720
1929
|
}
|
|
1721
1930
|
case "rendered":
|
|
@@ -1744,7 +1953,7 @@ function FrameView(props) {
|
|
|
1744
1953
|
break;
|
|
1745
1954
|
}
|
|
1746
1955
|
case "open": {
|
|
1747
|
-
const { lab: liveLab } =
|
|
1956
|
+
const { lab: liveLab } = latestRef.current;
|
|
1748
1957
|
if (!trialId || !liveLab.instruments.some((instrument) => instrument.name === msg.id)) break;
|
|
1749
1958
|
liveLab.swapTrial(trialId, msg.id);
|
|
1750
1959
|
setRoute(msg.id, { inPlace: true });
|
|
@@ -1782,6 +1991,7 @@ function FrameView(props) {
|
|
|
1782
1991
|
};
|
|
1783
1992
|
const current = {
|
|
1784
1993
|
channel,
|
|
1994
|
+
deliver: (msg) => receive(current, msg),
|
|
1785
1995
|
timer,
|
|
1786
1996
|
awaiting: null,
|
|
1787
1997
|
sent: null,
|
|
@@ -1792,23 +2002,35 @@ function FrameView(props) {
|
|
|
1792
2002
|
queued: []
|
|
1793
2003
|
};
|
|
1794
2004
|
link.current = current;
|
|
1795
|
-
channel.on((msg) =>
|
|
1796
|
-
target.postMessage({ type: PORT_HANDOFF, id:
|
|
2005
|
+
channel.on((msg) => current.deliver(msg));
|
|
2006
|
+
target.postMessage({ type: PORT_HANDOFF, id: latestRef.current.entry.id }, location.origin, [
|
|
1797
2007
|
port2
|
|
1798
2008
|
]);
|
|
1799
2009
|
};
|
|
1800
2010
|
const connectRef = useLatest(connect);
|
|
2011
|
+
const handedOn = useKeptFrame({
|
|
2012
|
+
key: trialId ? `${trialId} ${src}` : null,
|
|
2013
|
+
slot: slotRef,
|
|
2014
|
+
frame: iframeRef,
|
|
2015
|
+
link,
|
|
2016
|
+
onAdopt: (adopted) => {
|
|
2017
|
+
adopted.deliver = (msg) => receive(adopted, msg);
|
|
2018
|
+
setPending(!adopted.hasRendered);
|
|
2019
|
+
},
|
|
2020
|
+
onDrop: () => closeLink(link)
|
|
2021
|
+
});
|
|
1801
2022
|
useEffect(() => {
|
|
1802
2023
|
const slot = slotRef.current;
|
|
1803
2024
|
if (!inView || !slot) return;
|
|
1804
|
-
const
|
|
1805
|
-
const
|
|
2025
|
+
const adopted = iframeRef.current;
|
|
2026
|
+
const claimed = adopted ? null : latestRef.current.pool?.claim() ?? null;
|
|
2027
|
+
const frame = adopted ?? claimed ?? document.createElement("iframe");
|
|
1806
2028
|
frame.className = "fg-frame-view";
|
|
1807
|
-
frame.title =
|
|
2029
|
+
frame.title = latestRef.current.title;
|
|
1808
2030
|
frame.removeAttribute("tabindex");
|
|
1809
2031
|
frame.toggleAttribute("data-pending", true);
|
|
1810
2032
|
iframeRef.current = frame;
|
|
1811
|
-
const unheard = claimed ? void 0 : setTimeout(() => setFault({ phase: null, message: `Frame did not start: ${src}` }), HELLO_TIMEOUT_MS);
|
|
2033
|
+
const unheard = adopted || claimed ? void 0 : setTimeout(() => setFault({ phase: null, message: `Frame did not start: ${src}` }), HELLO_TIMEOUT_MS);
|
|
1812
2034
|
const onMessage = (event) => {
|
|
1813
2035
|
if (event.source !== frame.contentWindow || event.origin !== location.origin) return;
|
|
1814
2036
|
if (event.data?.type !== FRAME_HELLO) return;
|
|
@@ -1819,20 +2041,21 @@ function FrameView(props) {
|
|
|
1819
2041
|
if (claimed) {
|
|
1820
2042
|
moveFrame(slot, claimed);
|
|
1821
2043
|
connectRef.current(claimed);
|
|
1822
|
-
} else {
|
|
2044
|
+
} else if (!adopted) {
|
|
1823
2045
|
frame.src = src;
|
|
1824
2046
|
slot.append(frame);
|
|
1825
2047
|
}
|
|
1826
2048
|
return () => {
|
|
1827
2049
|
window.removeEventListener("message", onMessage);
|
|
1828
2050
|
clearTimeout(unheard);
|
|
2051
|
+
if (handedOn()) return;
|
|
1829
2052
|
closeLink(link);
|
|
1830
2053
|
frame.remove();
|
|
1831
2054
|
iframeRef.current = null;
|
|
1832
2055
|
setPending(true);
|
|
1833
2056
|
setFault(null);
|
|
1834
2057
|
};
|
|
1835
|
-
}, [inView, src, connectRef,
|
|
2058
|
+
}, [inView, src, connectRef, latestRef, handedOn]);
|
|
1836
2059
|
useEffect(() => {
|
|
1837
2060
|
iframeRef.current?.toggleAttribute("data-pending", pending && !fault);
|
|
1838
2061
|
}, [pending, fault, inView, src]);
|
|
@@ -1847,29 +2070,9 @@ function FrameView(props) {
|
|
|
1847
2070
|
if (current.awaiting === descriptionKey) init(current);
|
|
1848
2071
|
return;
|
|
1849
2072
|
}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
let input = false;
|
|
1853
|
-
const config = storyConfig(ctx.config, sent.config);
|
|
1854
|
-
if (config !== sent.config) {
|
|
1855
|
-
sent.config = config;
|
|
1856
|
-
current.channel.send({ type: "config", config });
|
|
1857
|
-
current.inputs += 1;
|
|
1858
|
-
input = true;
|
|
1859
|
-
}
|
|
1860
|
-
if (ctx.state !== sent.state) {
|
|
1861
|
-
sent.state = ctx.state;
|
|
1862
|
-
current.channel.send({ type: "state", state: ctx.state });
|
|
1863
|
-
current.inputs += 1;
|
|
1864
|
-
input = true;
|
|
1865
|
-
}
|
|
1866
|
-
if (globals !== sent.globals && stableStringify(globals) !== stableStringify(sent.globals)) {
|
|
1867
|
-
sent.globals = globals;
|
|
1868
|
-
current.channel.send({ type: "globals", globals });
|
|
1869
|
-
current.inputs += 1;
|
|
1870
|
-
input = true;
|
|
2073
|
+
if (current.sent && sendInputs(current, current.sent, { config: ctx.config, state: ctx.state, globals })) {
|
|
2074
|
+
setFault((shown) => shown?.phase === "render" ? null : shown);
|
|
1871
2075
|
}
|
|
1872
|
-
if (input) setFault((shown) => shown?.phase === "render" ? null : shown);
|
|
1873
2076
|
}, [ctx.config, ctx.state, globals, descriptionKey, init]);
|
|
1874
2077
|
return /* @__PURE__ */ jsxs("div", { ref: hostRef, className: "fg-frame-host", children: [
|
|
1875
2078
|
/* @__PURE__ */ jsx("div", { ref: slotRef, className: "fg-frame-slot" }),
|
|
@@ -1939,21 +2142,21 @@ function useStoryRegistry(index, options) {
|
|
|
1939
2142
|
setRevisions((prev) => pruned(prev, ids));
|
|
1940
2143
|
}
|
|
1941
2144
|
const imports = useRef(/* @__PURE__ */ new Map());
|
|
1942
|
-
const
|
|
2145
|
+
const liveRef = useLatest({ index, importers, setup, load, libraries });
|
|
1943
2146
|
const missing = useRef(/* @__PURE__ */ new Set());
|
|
1944
2147
|
const importOf = useCallback((file) => {
|
|
1945
2148
|
let loading = imports.current.get(file);
|
|
1946
2149
|
if (!loading) {
|
|
1947
|
-
const importer =
|
|
2150
|
+
const importer = liveRef.current.importers?.[file];
|
|
1948
2151
|
if (!importer) missing.current.add(file);
|
|
1949
2152
|
loading = importer ? importer() : Promise.reject(new Error(`No importer for ${file}`));
|
|
1950
2153
|
imports.current.set(file, loading);
|
|
1951
2154
|
}
|
|
1952
2155
|
return loading;
|
|
1953
|
-
}, [
|
|
2156
|
+
}, [liveRef]);
|
|
1954
2157
|
const request = useCallback(
|
|
1955
2158
|
(entry) => {
|
|
1956
|
-
const { setup: current, load: loadModule } =
|
|
2159
|
+
const { setup: current, load: loadModule } = liveRef.current;
|
|
1957
2160
|
const settle = (ids2, result) => {
|
|
1958
2161
|
setLoaded((prev) => {
|
|
1959
2162
|
const next = new Map(prev);
|
|
@@ -1971,7 +2174,7 @@ function useStoryRegistry(index, options) {
|
|
|
1971
2174
|
});
|
|
1972
2175
|
};
|
|
1973
2176
|
if (isIndexId(entry.id)) {
|
|
1974
|
-
const entries =
|
|
2177
|
+
const entries = liveRef.current.index.filter((e) => !isIndexId(e.id) && indexId(e.title) === entry.id);
|
|
1975
2178
|
const files = [...new Set(entries.map((e) => e.file))];
|
|
1976
2179
|
void Promise.all(files.map(importOf)).then(async (mods) => {
|
|
1977
2180
|
const byFile = new Map(files.map((file, i) => [file, loadModule(mods[i] ?? {}, entry.title, current.parameters)]));
|
|
@@ -1984,7 +2187,7 @@ function useStoryRegistry(index, options) {
|
|
|
1984
2187
|
kind: "index",
|
|
1985
2188
|
bundle: {
|
|
1986
2189
|
title: entry.title,
|
|
1987
|
-
library:
|
|
2190
|
+
library: liveRef.current.libraries.of(entry),
|
|
1988
2191
|
...entry.componentDescription === void 0 ? {} : { description: entry.componentDescription },
|
|
1989
2192
|
...entry.tags === void 0 ? {} : { tags: entry.tags },
|
|
1990
2193
|
galleries: entries.filter(isGallery).map((e) => e.id),
|
|
@@ -1997,7 +2200,7 @@ function useStoryRegistry(index, options) {
|
|
|
1997
2200
|
}).catch((error) => fail([entry.id], error));
|
|
1998
2201
|
return;
|
|
1999
2202
|
}
|
|
2000
|
-
const siblings =
|
|
2203
|
+
const siblings = liveRef.current.index.filter((e) => e.file === entry.file && !isIndexId(e.id));
|
|
2001
2204
|
const ids = siblings.map((e) => e.id);
|
|
2002
2205
|
void importOf(entry.file).then(async (mod) => {
|
|
2003
2206
|
const stories = loadModule(mod, entry.title, current.parameters);
|
|
@@ -2012,18 +2215,18 @@ function useStoryRegistry(index, options) {
|
|
|
2012
2215
|
settle(ids, result);
|
|
2013
2216
|
}).catch((error) => fail(ids, error));
|
|
2014
2217
|
},
|
|
2015
|
-
[importOf,
|
|
2218
|
+
[importOf, liveRef]
|
|
2016
2219
|
);
|
|
2017
2220
|
const reload = useCallback(
|
|
2018
2221
|
(file) => {
|
|
2019
2222
|
imports.current.delete(file);
|
|
2020
|
-
const affected =
|
|
2223
|
+
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
2224
|
const ids = new Set(affected.map((e) => e.id));
|
|
2022
2225
|
setLoaded((prev) => pruned(prev, new Set([...prev.keys()].filter((id) => !ids.has(id)))));
|
|
2023
2226
|
setFaults((prev) => pruned(prev, new Set([...prev.keys()].filter((id) => !ids.has(id)))));
|
|
2024
2227
|
setGenerations((prev) => new Map(prev).set(file, (prev.get(file) ?? 0) + 1));
|
|
2025
2228
|
},
|
|
2026
|
-
[
|
|
2229
|
+
[liveRef]
|
|
2027
2230
|
);
|
|
2028
2231
|
useEffect(() => {
|
|
2029
2232
|
for (const file of [...missing.current]) {
|
|
@@ -2048,7 +2251,7 @@ function useStoryRegistry(index, options) {
|
|
|
2048
2251
|
const answers = previous.books.get(entry.id) ?? createAnswerBook();
|
|
2049
2252
|
books.set(entry.id, answers);
|
|
2050
2253
|
const held = previous.built.get(entry.id);
|
|
2051
|
-
const
|
|
2254
|
+
const kept2 = held && held.entryKey === entryKey && held.ready === ready && held.loaded === had && held.fault === fault && held.frameUrl === frameUrl && held.globals === globals && held.setup === setup && held.inDocument === inDocument && held.generation === generation && held.revision === revision ? held : {
|
|
2052
2255
|
entryKey,
|
|
2053
2256
|
ready,
|
|
2054
2257
|
loaded: had,
|
|
@@ -2061,8 +2264,8 @@ function useStoryRegistry(index, options) {
|
|
|
2061
2264
|
revision,
|
|
2062
2265
|
instrument: !inDocument ? storyInstrument({ entry, ready, answers, frameUrl, onReady, globals }) : had ? documentInstrument({ entry, loaded: had, setup, globals }) : pendingInstrument({ entry, globals, load: request, generation, ...fault ? { fault } : {} })
|
|
2063
2266
|
};
|
|
2064
|
-
built.set(entry.id,
|
|
2065
|
-
return
|
|
2267
|
+
built.set(entry.id, kept2);
|
|
2268
|
+
return kept2.instrument;
|
|
2066
2269
|
});
|
|
2067
2270
|
const unchanged = list.length === previous.list.length && list.every((i, n) => i === previous.list[n]);
|
|
2068
2271
|
return { built, books, list: unchanged ? previous.list : list };
|
|
@@ -2167,10 +2370,10 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
|
|
|
2167
2370
|
const labChrome = useMemo(
|
|
2168
2371
|
() => [
|
|
2169
2372
|
{ id: "fg-stories", region: "sidebar", render: (ctx) => /* @__PURE__ */ jsx(StoryTree, { ctx, index }) },
|
|
2170
|
-
{ id: "fg-info", region: "palette", item: infoCommand },
|
|
2171
2373
|
...config?.cssVars === false ? [] : [CSS_VARS_SECTION],
|
|
2172
2374
|
A11Y_SECTION,
|
|
2173
2375
|
...Object.keys(declarations).length > 0 ? [{ id: "fg-globals", region: "header", render: () => /* @__PURE__ */ jsx(GlobalsToolbar, { declarations }) }] : [],
|
|
2376
|
+
{ id: "fg-info", region: "header", group: "story", item: infoCommand },
|
|
2174
2377
|
...config?.labChrome ?? []
|
|
2175
2378
|
],
|
|
2176
2379
|
[index, infoCommand, declarations, config?.cssVars, config?.labChrome]
|