@weasel-js/forge 1.9.2 → 1.9.4
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 +22 -0
- package/dist/{FrameController-CpcY0DNB.d.ts → FrameController-BQjKhdwk.d.ts} +2 -36
- package/dist/chunk-5DXUNLV2.js +6 -0
- package/dist/chunk-5DXUNLV2.js.map +1 -0
- package/dist/{chunk-MGQWCPYE.js → chunk-5KI5YSQ5.js} +4 -5
- package/dist/chunk-5KI5YSQ5.js.map +1 -0
- package/dist/{chunk-Q2EZXJFE.js → chunk-B3PPHJTS.js} +59 -45
- package/dist/chunk-B3PPHJTS.js.map +1 -0
- package/dist/{chunk-CJX3W5HY.js → chunk-FRUA5WJF.js} +4 -3
- package/dist/chunk-FRUA5WJF.js.map +1 -0
- package/dist/{chunk-GZPQMGXJ.js → chunk-JHL4ERDA.js} +4 -3
- package/dist/chunk-JHL4ERDA.js.map +1 -0
- package/dist/{chunk-JVRHBI3V.js → chunk-MQSFPRF6.js} +20 -44
- package/dist/chunk-MQSFPRF6.js.map +1 -0
- package/dist/chunk-TXLWQFF6.js +10 -0
- package/dist/chunk-TXLWQFF6.js.map +1 -0
- package/dist/{chunk-BGOHALHO.js → chunk-ZZCCIXEJ.js} +11 -90
- package/dist/chunk-ZZCCIXEJ.js.map +1 -0
- package/dist/{config-BfsYw5lg.d.ts → config-Dfu-NKxd.d.ts} +27 -4
- package/dist/csf/preview-api.js +2 -2
- package/dist/frame/index.d.ts +2 -2
- package/dist/frame/index.js +6 -40
- package/dist/frame/index.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/shell/index.css +61 -61
- package/dist/shell/index.css.map +1 -1
- package/dist/shell/index.d.ts +5 -13
- package/dist/shell/index.js +345 -332
- package/dist/shell/index.js.map +1 -1
- package/dist/test/index.d.ts +4 -2
- package/dist/test/index.js +7 -7
- package/dist/test/index.js.map +1 -1
- package/dist/test/play.d.ts +3 -3
- package/dist/test/play.js.map +1 -1
- package/dist/vite/index.d.ts +3 -1
- package/dist/vite/index.js +365 -101
- package/dist/vite/index.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-BGOHALHO.js.map +0 -1
- package/dist/chunk-CJX3W5HY.js.map +0 -1
- package/dist/chunk-GZPQMGXJ.js.map +0 -1
- package/dist/chunk-JVRHBI3V.js.map +0 -1
- package/dist/chunk-KJGXJD3U.js +0 -15
- package/dist/chunk-KJGXJD3U.js.map +0 -1
- package/dist/chunk-MGQWCPYE.js.map +0 -1
- package/dist/chunk-Q2EZXJFE.js.map +0 -1
package/dist/shell/index.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import { parseRoute, paramsToKnobs, knobPaths, sameKnob, knobsToParams,
|
|
2
|
-
import {
|
|
1
|
+
import { parseRoute, formatRoute, paramsToKnobs, knobPaths, sameKnob, knobsToParams, PLAYHEAD_PARAM, reservedParams } from '../chunk-FRUA5WJF.js';
|
|
2
|
+
import { DEFAULT_TITLE } from '../chunk-5DXUNLV2.js';
|
|
3
|
+
import { TITLE_LIBRARIES, isIndexId, indexId, indexEntries, librariesFrom, LibrariesContext, schemaFromDescription, LibraryBadge, openChannel, IndexPage } from '../chunk-MQSFPRF6.js';
|
|
3
4
|
import { isGallery } from '../chunk-WEQSLPKT.js';
|
|
4
|
-
import { useTrialFrame, createTrialFrames, createTrialClocks, StoryGlobalsContext, TrialFramesContext, TrialClocksContext, StoryTrial, useCssOverrides,
|
|
5
|
-
export { StoryGlobalsContext, StoryTrial, TrialFramesContext, TrialHost, useTrialFrame } from '../chunk-
|
|
6
|
-
import { labGlobals, withGlobals, followApp, storyConfig, GLOBALS_KEY, effectiveGlobals, PLAYHEAD_KEY, labOptions, isGlobalsPath } from '../chunk-
|
|
7
|
-
import { loadStories, indexRenderOf } from '../chunk-
|
|
5
|
+
import { useTrialFrame, createTrialFrames, createTrialClocks, StoryGlobalsContext, TrialFramesContext, TrialClocksContext, StoryTrial, useCssOverrides, useTrialClock, TrialHost } from '../chunk-ZZCCIXEJ.js';
|
|
6
|
+
export { StoryGlobalsContext, StoryTrial, TrialFramesContext, TrialHost, useTrialFrame } from '../chunk-ZZCCIXEJ.js';
|
|
7
|
+
import { labGlobals, withGlobals, followApp, storyConfig, GLOBALS_KEY, effectiveGlobals, PLAYHEAD_KEY, labOptions, isGlobalsPath } from '../chunk-JHL4ERDA.js';
|
|
8
|
+
import { loadStories, indexRenderOf, useNearViewport, IN_VIEW_MARGIN } from '../chunk-B3PPHJTS.js';
|
|
8
9
|
import '../chunk-L5XM5VKB.js';
|
|
9
10
|
import '../chunk-HRGPFQBW.js';
|
|
10
|
-
import '../chunk-
|
|
11
|
-
import { stableStringify, PORT_HANDOFF, FRAME_HELLO, PROTOCOL_VERSION } from '../chunk-
|
|
11
|
+
import '../chunk-5KI5YSQ5.js';
|
|
12
|
+
import { stableStringify, PORT_HANDOFF, FRAME_HELLO, PROTOCOL_VERSION } from '../chunk-TXLWQFF6.js';
|
|
12
13
|
import { useTrialId, Button, Lab, useLabContext, TrialIdContext, usePersistedState, Select, useTrialState, inferTokenType, ToggleBar as ToggleBar$1, Input, TokenPanel, refitScale } from '@weasel-js/labkit';
|
|
13
14
|
import num from '@weasel-js/theme/numeric.module.css';
|
|
14
|
-
import { createContext, useState, useRef,
|
|
15
|
+
import { createContext, useState, useRef, useCallback, useEffect, useMemo, useLayoutEffect, useId, useContext, Fragment as Fragment$1, useSyncExternalStore } from 'react';
|
|
15
16
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
16
17
|
import { createRoot } from 'react-dom/client';
|
|
18
|
+
import { f, valueAtPath, isConfigBranch } from '@weasel-js/labkit/config';
|
|
17
19
|
import { InfoIcon, ToggleBar, Checkbox, Dialog, TOKEN_CATEGORIES, Icon, DisclosureMark, Button as Button$1, Callout, Slider, Transport, SearchIcon } from '@weasel-js/ui';
|
|
18
20
|
import { TOKEN_MANIFEST, THEME_SOURCES, isByAxis } from '@weasel-js/theme';
|
|
19
21
|
import { scale, httpThemeApi } from '@weasel-js/theme/engine';
|
|
20
|
-
import { f, valueAtPath, isConfigBranch } from '@weasel-js/labkit/config';
|
|
21
22
|
import { useLatest } from '@weasel-js/core';
|
|
22
23
|
|
|
23
24
|
function Finding({ finding }) {
|
|
@@ -44,19 +45,21 @@ function Findings({ title, findings }) {
|
|
|
44
45
|
function A11yPanel() {
|
|
45
46
|
const trialId = useTrialId();
|
|
46
47
|
const frame = useTrialFrame(trialId);
|
|
47
|
-
const [
|
|
48
|
+
const [inFlight, setInFlight] = useState(0);
|
|
49
|
+
const running = inFlight > 0;
|
|
48
50
|
const audit = frame.audit;
|
|
49
51
|
const ran = useRef(null);
|
|
52
|
+
const start = useCallback((run) => {
|
|
53
|
+
setInFlight((n) => n + 1);
|
|
54
|
+
void run().finally(() => setInFlight((n) => n - 1));
|
|
55
|
+
}, []);
|
|
50
56
|
useEffect(() => {
|
|
51
57
|
if (!audit || ran.current === audit) return;
|
|
52
58
|
ran.current = audit;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}, [audit]);
|
|
59
|
+
start(audit);
|
|
60
|
+
}, [audit, start]);
|
|
56
61
|
const rerun = () => {
|
|
57
|
-
if (
|
|
58
|
-
setRunning(true);
|
|
59
|
-
void audit().finally(() => setRunning(false));
|
|
62
|
+
if (audit) start(audit);
|
|
60
63
|
};
|
|
61
64
|
const outcome = frame.a11y;
|
|
62
65
|
return /* @__PURE__ */ jsxs("div", { className: "fg-a11y", children: [
|
|
@@ -112,6 +115,243 @@ function createStoryChanges() {
|
|
|
112
115
|
};
|
|
113
116
|
}
|
|
114
117
|
var DependenciesContext = createContext(null);
|
|
118
|
+
function readRoute() {
|
|
119
|
+
return parseRoute(location.hash).story;
|
|
120
|
+
}
|
|
121
|
+
function readRouteParams() {
|
|
122
|
+
return { ...parseRoute(location.hash).params };
|
|
123
|
+
}
|
|
124
|
+
function replaceRouteParams(update) {
|
|
125
|
+
const { story, params } = parseRoute(location.hash);
|
|
126
|
+
if (story === null) return;
|
|
127
|
+
const next = formatRoute({ story, params: update({ ...params }) });
|
|
128
|
+
if (next !== location.hash) history.replaceState(history.state, "", next);
|
|
129
|
+
}
|
|
130
|
+
function readRouteEntry() {
|
|
131
|
+
const state = history.state;
|
|
132
|
+
const entry = state?.forgeRoute;
|
|
133
|
+
return typeof entry?.step === "number" ? entry : null;
|
|
134
|
+
}
|
|
135
|
+
function crossesInPlace(from, to) {
|
|
136
|
+
if (!from || !to || from.step === to.step) return false;
|
|
137
|
+
return to.step < from.step ? from.inPlace : to.inPlace;
|
|
138
|
+
}
|
|
139
|
+
function stamp(entry, hash) {
|
|
140
|
+
history.replaceState({ ...history.state ?? {}, forgeRoute: entry }, "", hash);
|
|
141
|
+
}
|
|
142
|
+
function setRoute(id, options = {}) {
|
|
143
|
+
if (readRoute() === id) {
|
|
144
|
+
history.replaceState(history.state, "", formatRoute({ story: id, params: readRouteParams() }));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const hash = formatRoute({ story: id, params: {} });
|
|
148
|
+
const from = readRouteEntry() ?? { step: 0, inPlace: false };
|
|
149
|
+
stamp(from, location.hash);
|
|
150
|
+
location.hash = hash;
|
|
151
|
+
stamp({ step: from.step + 1, inPlace: options.inPlace ?? false, ...options.fromTrial ? { fromTrial: true } : {} }, hash);
|
|
152
|
+
}
|
|
153
|
+
function takeRouteFromTrial() {
|
|
154
|
+
const entry = readRouteEntry();
|
|
155
|
+
if (!entry?.fromTrial) return false;
|
|
156
|
+
stamp({ step: entry.step, inPlace: entry.inPlace }, location.hash);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
function useRoute() {
|
|
160
|
+
const [route, setCurrent] = useState(readRoute);
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
const sync = () => setCurrent(readRoute());
|
|
163
|
+
window.addEventListener("hashchange", sync);
|
|
164
|
+
sync();
|
|
165
|
+
return () => window.removeEventListener("hashchange", sync);
|
|
166
|
+
}, []);
|
|
167
|
+
return [route, setRoute];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/timeline/playheadUrl.ts
|
|
171
|
+
function formatSeconds(ms) {
|
|
172
|
+
const rounded = Math.round(ms) / 1e3;
|
|
173
|
+
return String(rounded === 0 ? 0 : rounded);
|
|
174
|
+
}
|
|
175
|
+
function parseSeconds(text) {
|
|
176
|
+
if (text == null || !/^\s*-?(\d+\.?\d*|\.\d+)\s*$/.test(text)) return null;
|
|
177
|
+
return Math.round(Number(text) * 1e3);
|
|
178
|
+
}
|
|
179
|
+
function readPlayheadParam() {
|
|
180
|
+
return parseSeconds(readRouteParams()[PLAYHEAD_PARAM]);
|
|
181
|
+
}
|
|
182
|
+
function writePlayheadParam(ms) {
|
|
183
|
+
replaceRouteParams(
|
|
184
|
+
({ [PLAYHEAD_PARAM]: _held, ...rest }) => ms === null ? rest : { ...rest, [PLAYHEAD_PARAM]: formatSeconds(ms) }
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
function TransportBar({ clock }) {
|
|
188
|
+
const { time, playing, loop, rate, span } = useSyncExternalStore(clock.subscribe, clock.get);
|
|
189
|
+
return /* @__PURE__ */ jsxs("div", { className: "fg-transport", children: [
|
|
190
|
+
/* @__PURE__ */ jsx(
|
|
191
|
+
Slider,
|
|
192
|
+
{
|
|
193
|
+
className: "fg-transport__scrub",
|
|
194
|
+
ariaLabel: "Scrub",
|
|
195
|
+
density: "slim",
|
|
196
|
+
readoutPlacement: "none",
|
|
197
|
+
min: span.start,
|
|
198
|
+
max: span.end > span.start ? span.end : span.start + 1,
|
|
199
|
+
thumbs: [{ value: time, valueText: `${formatSeconds(time)} seconds` }],
|
|
200
|
+
onInput: ([thumb]) => {
|
|
201
|
+
if (thumb) clock.seek(thumb.value);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
),
|
|
205
|
+
/* @__PURE__ */ jsx(
|
|
206
|
+
Transport,
|
|
207
|
+
{
|
|
208
|
+
paused: !playing,
|
|
209
|
+
loop,
|
|
210
|
+
rate,
|
|
211
|
+
playhead: time,
|
|
212
|
+
duration: span.end,
|
|
213
|
+
onPlay: clock.play,
|
|
214
|
+
onPause: clock.pause,
|
|
215
|
+
onLoopChange: (next) => clock.setLoop(next !== false && next !== 0),
|
|
216
|
+
onRateChange: clock.setRate
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
] });
|
|
220
|
+
}
|
|
221
|
+
function TrialTransport({ trialId }) {
|
|
222
|
+
const clock = useTrialClock(trialId);
|
|
223
|
+
return clock ? /* @__PURE__ */ jsx(TransportBar, { clock }) : null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// src/shell/breadcrumb.ts
|
|
227
|
+
function crumbs(title, name) {
|
|
228
|
+
const segments = title.split("/");
|
|
229
|
+
const steps = segments.map((label, i) => ({ label, path: segments.slice(0, i + 1).join("/") }));
|
|
230
|
+
steps.push({ label: name, path: null });
|
|
231
|
+
return steps.filter((step, i) => i === 0 || step.label.toLowerCase() !== "index");
|
|
232
|
+
}
|
|
233
|
+
function breadcrumb(title, name) {
|
|
234
|
+
return crumbs(title, name).map((step) => step.label).join(" > ");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/shell/treeReveal.ts
|
|
238
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
239
|
+
function revealInTree(path) {
|
|
240
|
+
for (const listener of listeners) listener(path);
|
|
241
|
+
}
|
|
242
|
+
function onRevealInTree(listener) {
|
|
243
|
+
listeners.add(listener);
|
|
244
|
+
return () => {
|
|
245
|
+
listeners.delete(listener);
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function Crumbs({ entry }) {
|
|
249
|
+
const steps = crumbs(entry.title, entry.name);
|
|
250
|
+
return /* @__PURE__ */ jsx("span", { className: "fg-crumbs", children: steps.map((step, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
251
|
+
i > 0 ? /* @__PURE__ */ jsx("span", { className: "fg-crumbs__sep", "aria-hidden": "true", children: "\u203A" }) : null,
|
|
252
|
+
step.path === null ? /* @__PURE__ */ jsx("span", { className: "fg-crumbs__here", children: step.label }) : /* @__PURE__ */ jsx(
|
|
253
|
+
"button",
|
|
254
|
+
{
|
|
255
|
+
type: "button",
|
|
256
|
+
className: "fg-crumbs__step",
|
|
257
|
+
title: `Show ${step.label} in the story tree`,
|
|
258
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
259
|
+
onClick: () => revealInTree(step.path),
|
|
260
|
+
children: step.label
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
] }, step.path ?? `${step.label}:${i}`)) });
|
|
264
|
+
}
|
|
265
|
+
function crumbTitle(entry) {
|
|
266
|
+
const title = breadcrumb(entry.title, entry.name);
|
|
267
|
+
return { title, renderTitle: (shown) => shown === title ? /* @__PURE__ */ jsx(Crumbs, { entry }) : shown };
|
|
268
|
+
}
|
|
269
|
+
function IndexTrial({ bundle, setup, config }) {
|
|
270
|
+
const lab = useLabContext();
|
|
271
|
+
const trialId = useContext(TrialIdContext);
|
|
272
|
+
const graph = useContext(DependenciesContext);
|
|
273
|
+
const gallery = isGallery(bundle);
|
|
274
|
+
const dependencies = gallery ? void 0 : graph?.[bundle.title];
|
|
275
|
+
const open = (id) => {
|
|
276
|
+
if (!trialId || !lab.instruments.some((instrument) => instrument.name === id)) return;
|
|
277
|
+
lab.swapTrial(trialId, id);
|
|
278
|
+
setRoute(id, { inPlace: true });
|
|
279
|
+
};
|
|
280
|
+
const render = (globals) => {
|
|
281
|
+
const env = {
|
|
282
|
+
title: bundle.title,
|
|
283
|
+
library: bundle.library,
|
|
284
|
+
...bundle.description === void 0 ? {} : { description: bundle.description },
|
|
285
|
+
stories: bundle.stories,
|
|
286
|
+
descriptions: bundle.descriptions,
|
|
287
|
+
gallery,
|
|
288
|
+
galleries: bundle.galleries,
|
|
289
|
+
...dependencies ? { dependencies } : {},
|
|
290
|
+
globals,
|
|
291
|
+
decorators: setup.decorators ?? [],
|
|
292
|
+
open,
|
|
293
|
+
// A story that throws shows its error in its own cell; the rest of the page is still worth reading.
|
|
294
|
+
onError: (error) => console.error(error)
|
|
295
|
+
};
|
|
296
|
+
return /* @__PURE__ */ jsx(IndexPage, { env, render: bundle.render });
|
|
297
|
+
};
|
|
298
|
+
return /* @__PURE__ */ jsx(TrialHost, { layout: "fullscreen", setup, config, children: render });
|
|
299
|
+
}
|
|
300
|
+
function withPlayhead(schema) {
|
|
301
|
+
return f.schema({ ...schema.nodes, [PLAYHEAD_KEY]: f.custom("forge-playhead", null).hidden() });
|
|
302
|
+
}
|
|
303
|
+
function documentInstrument(options) {
|
|
304
|
+
const { entry, loaded, setup, globals = {} } = options;
|
|
305
|
+
const schema = loaded.kind === "story" ? loaded.story.config : f.schema({});
|
|
306
|
+
const timed = loaded.kind === "story" && loaded.story.timeline !== null;
|
|
307
|
+
const config = withGlobals(timed ? withPlayhead(schema) : schema, globals);
|
|
308
|
+
const initialState = loaded.kind === "story" ? loaded.story.initialState : null;
|
|
309
|
+
return {
|
|
310
|
+
name: entry.id,
|
|
311
|
+
...crumbTitle(entry),
|
|
312
|
+
config,
|
|
313
|
+
defaultConfig: () => config.defaults(),
|
|
314
|
+
initialState: (c) => initialState?.(c) ?? null,
|
|
315
|
+
render: (ctx) => loaded.kind === "story" ? /* @__PURE__ */ jsx(StoryTrial, { story: loaded.story, setup, ctx }, loaded.story.id) : /* @__PURE__ */ jsx(IndexTrial, { bundle: loaded.bundle, setup, config: ctx.config }),
|
|
316
|
+
...loaded.kind === "story" && loaded.story.viewport ? { stage: { size: { width: loaded.story.viewport.width, height: loaded.story.viewport.height } } } : {},
|
|
317
|
+
...timed ? { chrome: [{ id: "fg-transport", region: "status", render: (c) => /* @__PURE__ */ jsx(TrialTransport, { trialId: c.trialId }) }] } : {}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function Fault({ phase, message }) {
|
|
321
|
+
return /* @__PURE__ */ jsxs("div", { className: "fg-fault", role: "alert", children: [
|
|
322
|
+
/* @__PURE__ */ jsx("span", { className: "fg-fault__phase", children: phase }),
|
|
323
|
+
/* @__PURE__ */ jsx("p", { className: "fg-fault__message", children: message })
|
|
324
|
+
] });
|
|
325
|
+
}
|
|
326
|
+
function StoryLoad({ entry, load, fault }) {
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
if (!fault) load(entry);
|
|
329
|
+
}, [entry, load, fault]);
|
|
330
|
+
return /* @__PURE__ */ jsx("div", { className: "fg-story fg-story--loading", "data-fg-layout": "fullscreen", children: fault ? /* @__PURE__ */ jsx(Fault, { phase: "import", message: fault.message }) : null });
|
|
331
|
+
}
|
|
332
|
+
function pendingInstrument(options) {
|
|
333
|
+
const { entry, globals = {} } = options;
|
|
334
|
+
const config = withGlobals(f.schema({}), globals);
|
|
335
|
+
return {
|
|
336
|
+
name: entry.id,
|
|
337
|
+
...crumbTitle(entry),
|
|
338
|
+
config,
|
|
339
|
+
defaultConfig: () => config.defaults(),
|
|
340
|
+
initialState: () => null,
|
|
341
|
+
render: () => /* @__PURE__ */ jsx(StoryLoad, { ...options }, options.generation ?? 0)
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function missingInstrument(id, globals = {}) {
|
|
345
|
+
const config = withGlobals(f.schema({}), globals);
|
|
346
|
+
return {
|
|
347
|
+
name: id,
|
|
348
|
+
title: `No story ${id}`,
|
|
349
|
+
config,
|
|
350
|
+
defaultConfig: () => config.defaults(),
|
|
351
|
+
initialState: () => null,
|
|
352
|
+
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
|
+
};
|
|
354
|
+
}
|
|
115
355
|
var SEED_REF = /^\{seeds\.([^}]+)\}$/;
|
|
116
356
|
var RULE_KEYS = { factors: "factors", ratio: "ratio", step: "step" };
|
|
117
357
|
function selectionFor(axes, globals) {
|
|
@@ -533,17 +773,6 @@ var CSS_VARS_SECTION = {
|
|
|
533
773
|
region: "aside",
|
|
534
774
|
item: { title: "CSS Vars", actions: /* @__PURE__ */ jsx(SectionJump, {}), body: /* @__PURE__ */ jsx(FocusedCssVars, {}) }
|
|
535
775
|
};
|
|
536
|
-
|
|
537
|
-
// src/shell/breadcrumb.ts
|
|
538
|
-
function crumbs(title, name) {
|
|
539
|
-
const segments = title.split("/");
|
|
540
|
-
const steps = segments.map((label, i) => ({ label, path: segments.slice(0, i + 1).join("/") }));
|
|
541
|
-
steps.push({ label: name, path: null });
|
|
542
|
-
return steps.filter((step, i) => i === 0 || step.label.toLowerCase() !== "index");
|
|
543
|
-
}
|
|
544
|
-
function breadcrumb(title, name) {
|
|
545
|
-
return crumbs(title, name).map((step) => step.label).join(" > ");
|
|
546
|
-
}
|
|
547
776
|
var GLOBALS = "$globals";
|
|
548
777
|
function leaves(shape, prefix = "") {
|
|
549
778
|
const out = [];
|
|
@@ -556,7 +785,7 @@ function leaves(shape, prefix = "") {
|
|
|
556
785
|
return out;
|
|
557
786
|
}
|
|
558
787
|
function storyDossier(input) {
|
|
559
|
-
const { entry, instrument, config, ready = true } = input;
|
|
788
|
+
const { entry, libraries = TITLE_LIBRARIES, instrument, config, ready = true } = input;
|
|
560
789
|
const schema = ready ? instrument?.config : void 0;
|
|
561
790
|
const args = schema ? leaves(schema.nodes).map(({ path, node }) => {
|
|
562
791
|
const value = valueAtPath(config, path);
|
|
@@ -575,7 +804,7 @@ function storyDossier(input) {
|
|
|
575
804
|
name: entry.name,
|
|
576
805
|
exportName: entry.exportName,
|
|
577
806
|
file: entry.file,
|
|
578
|
-
library:
|
|
807
|
+
library: libraries.of(entry),
|
|
579
808
|
...entry.componentName === void 0 ? {} : { componentName: entry.componentName },
|
|
580
809
|
...entry.description === void 0 ? {} : { description: entry.description },
|
|
581
810
|
...entry.componentDescription === void 0 ? {} : { componentDescription: entry.componentDescription },
|
|
@@ -638,11 +867,13 @@ function StoryDossier({ dossier }) {
|
|
|
638
867
|
}
|
|
639
868
|
function StoryInfoDialog({ index, isReady, isOpen, onOpenChange }) {
|
|
640
869
|
const { focusedTrialId, trials, instruments } = useLabContext();
|
|
870
|
+
const libraries = useContext(LibrariesContext);
|
|
641
871
|
const trial = trials.find((record) => record.id === focusedTrialId);
|
|
642
872
|
const entry = trial ? index.find((candidate) => candidate.id === trial.instrumentName) : void 0;
|
|
643
873
|
if (!entry) return null;
|
|
644
874
|
const dossier = storyDossier({
|
|
645
875
|
entry,
|
|
876
|
+
libraries,
|
|
646
877
|
instrument: instruments.find((candidate) => candidate.name === entry.id),
|
|
647
878
|
config: trial?.config,
|
|
648
879
|
ready: isReady(entry.id)
|
|
@@ -658,6 +889,7 @@ function StoryInfoDialog({ index, isReady, isOpen, onOpenChange }) {
|
|
|
658
889
|
}
|
|
659
890
|
);
|
|
660
891
|
}
|
|
892
|
+
var HELLO_TIMEOUT_MS = 3e4;
|
|
661
893
|
function canMoveFrames() {
|
|
662
894
|
return typeof Element !== "undefined" && "moveBefore" in Element.prototype;
|
|
663
895
|
}
|
|
@@ -678,21 +910,30 @@ function createFramePool(frameUrl, size = 2) {
|
|
|
678
910
|
shelf.setAttribute("aria-hidden", "true");
|
|
679
911
|
document.body.append(shelf);
|
|
680
912
|
const warm = [];
|
|
681
|
-
|
|
913
|
+
const waits = /* @__PURE__ */ new Set();
|
|
682
914
|
let cancelRefill = null;
|
|
683
915
|
let disposed = false;
|
|
684
916
|
const load = () => {
|
|
685
|
-
loading += 1;
|
|
686
917
|
const frame = document.createElement("iframe");
|
|
687
918
|
frame.tabIndex = -1;
|
|
688
919
|
frame.src = frameUrl;
|
|
920
|
+
const stop = () => {
|
|
921
|
+
window.removeEventListener("message", onMessage);
|
|
922
|
+
clearTimeout(timer);
|
|
923
|
+
waits.delete(stop);
|
|
924
|
+
};
|
|
689
925
|
const onMessage = (event) => {
|
|
690
926
|
if (event.source !== frame.contentWindow || event.origin !== location.origin) return;
|
|
691
927
|
if (event.data?.type !== FRAME_HELLO) return;
|
|
692
|
-
|
|
693
|
-
loading -= 1;
|
|
928
|
+
stop();
|
|
694
929
|
warm.push(frame);
|
|
695
930
|
};
|
|
931
|
+
const timer = setTimeout(() => {
|
|
932
|
+
stop();
|
|
933
|
+
frame.remove();
|
|
934
|
+
refill();
|
|
935
|
+
}, HELLO_TIMEOUT_MS);
|
|
936
|
+
waits.add(stop);
|
|
696
937
|
window.addEventListener("message", onMessage);
|
|
697
938
|
shelf.append(frame);
|
|
698
939
|
};
|
|
@@ -700,7 +941,7 @@ function createFramePool(frameUrl, size = 2) {
|
|
|
700
941
|
if (disposed || cancelRefill) return;
|
|
701
942
|
cancelRefill = whenIdle(() => {
|
|
702
943
|
cancelRefill = null;
|
|
703
|
-
while (!disposed && warm.length +
|
|
944
|
+
while (!disposed && warm.length + waits.size < size) load();
|
|
704
945
|
});
|
|
705
946
|
};
|
|
706
947
|
if (canMoveFrames()) refill();
|
|
@@ -714,6 +955,7 @@ function createFramePool(frameUrl, size = 2) {
|
|
|
714
955
|
dispose() {
|
|
715
956
|
disposed = true;
|
|
716
957
|
cancelRefill?.();
|
|
958
|
+
for (const stop of [...waits]) stop();
|
|
717
959
|
shelf.remove();
|
|
718
960
|
}
|
|
719
961
|
};
|
|
@@ -724,69 +966,6 @@ var FramePoolContext = createContext(null);
|
|
|
724
966
|
function routedTrial(trials, focusedTrialId, route) {
|
|
725
967
|
return trials.find((t) => t.id === focusedTrialId && t.instrumentName === route) ?? trials.find((t) => t.instrumentName === route);
|
|
726
968
|
}
|
|
727
|
-
function readRoute() {
|
|
728
|
-
return parseRoute(location.hash).story;
|
|
729
|
-
}
|
|
730
|
-
function readRouteParams() {
|
|
731
|
-
return { ...parseRoute(location.hash).params };
|
|
732
|
-
}
|
|
733
|
-
function replaceRouteParams(update) {
|
|
734
|
-
const { story, params } = parseRoute(location.hash);
|
|
735
|
-
if (story === null) return;
|
|
736
|
-
const next = formatRoute({ story, params: update({ ...params }) });
|
|
737
|
-
if (next !== location.hash) history.replaceState(history.state, "", next);
|
|
738
|
-
}
|
|
739
|
-
function readRouteEntry() {
|
|
740
|
-
const state = history.state;
|
|
741
|
-
const entry = state?.forgeRoute;
|
|
742
|
-
return typeof entry?.step === "number" ? entry : null;
|
|
743
|
-
}
|
|
744
|
-
function crossesInPlace(from, to) {
|
|
745
|
-
if (!from || !to || from.step === to.step) return false;
|
|
746
|
-
return to.step < from.step ? from.inPlace : to.inPlace;
|
|
747
|
-
}
|
|
748
|
-
function stamp(entry, hash) {
|
|
749
|
-
history.replaceState({ ...history.state ?? {}, forgeRoute: entry }, "", hash);
|
|
750
|
-
}
|
|
751
|
-
function setRoute(id, options = {}) {
|
|
752
|
-
if (readRoute() === id) {
|
|
753
|
-
history.replaceState(history.state, "", formatRoute({ story: id, params: readRouteParams() }));
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
const hash = formatRoute({ story: id, params: {} });
|
|
757
|
-
const from = readRouteEntry() ?? { step: 0, inPlace: false };
|
|
758
|
-
stamp(from, location.hash);
|
|
759
|
-
location.hash = hash;
|
|
760
|
-
stamp({ step: from.step + 1, inPlace: options.inPlace ?? false }, hash);
|
|
761
|
-
}
|
|
762
|
-
function useRoute() {
|
|
763
|
-
const [route, setCurrent] = useState(readRoute);
|
|
764
|
-
useEffect(() => {
|
|
765
|
-
const sync = () => setCurrent(readRoute());
|
|
766
|
-
window.addEventListener("hashchange", sync);
|
|
767
|
-
sync();
|
|
768
|
-
return () => window.removeEventListener("hashchange", sync);
|
|
769
|
-
}, []);
|
|
770
|
-
return [route, setRoute];
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
// src/timeline/playheadUrl.ts
|
|
774
|
-
function formatSeconds(ms) {
|
|
775
|
-
const rounded = Math.round(ms) / 1e3;
|
|
776
|
-
return String(rounded === 0 ? 0 : rounded);
|
|
777
|
-
}
|
|
778
|
-
function parseSeconds(text) {
|
|
779
|
-
if (text == null || !/^\s*-?(\d+\.?\d*|\.\d+)\s*$/.test(text)) return null;
|
|
780
|
-
return Math.round(Number(text) * 1e3);
|
|
781
|
-
}
|
|
782
|
-
function readPlayheadParam() {
|
|
783
|
-
return parseSeconds(readRouteParams()[PLAYHEAD_PARAM]);
|
|
784
|
-
}
|
|
785
|
-
function writePlayheadParam(ms) {
|
|
786
|
-
replaceRouteParams(
|
|
787
|
-
({ [PLAYHEAD_PARAM]: _held, ...rest }) => ms === null ? rest : { ...rest, [PLAYHEAD_PARAM]: formatSeconds(ms) }
|
|
788
|
-
);
|
|
789
|
-
}
|
|
790
969
|
function PlayheadSync({ trialId, route, applied }) {
|
|
791
970
|
const clock = useTrialClock(trialId);
|
|
792
971
|
useEffect(() => {
|
|
@@ -938,6 +1117,7 @@ function KnobSync({ schema, config }) {
|
|
|
938
1117
|
return () => window.removeEventListener("hashchange", onHashChange);
|
|
939
1118
|
}, []);
|
|
940
1119
|
useEffect(() => {
|
|
1120
|
+
if (reading.current && takeRouteFromTrial()) reading.current = false;
|
|
941
1121
|
if (reading.current) {
|
|
942
1122
|
reading.current = false;
|
|
943
1123
|
const knobs2 = paramsToKnobs(schema, readRouteParams());
|
|
@@ -987,18 +1167,6 @@ function revealTrial(trialId, root = document) {
|
|
|
987
1167
|
return true;
|
|
988
1168
|
}
|
|
989
1169
|
|
|
990
|
-
// src/shell/treeReveal.ts
|
|
991
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
992
|
-
function revealInTree(path) {
|
|
993
|
-
for (const listener of listeners) listener(path);
|
|
994
|
-
}
|
|
995
|
-
function onRevealInTree(listener) {
|
|
996
|
-
listeners.add(listener);
|
|
997
|
-
return () => {
|
|
998
|
-
listeners.delete(listener);
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
1170
|
// src/shell/tree/buildTree.ts
|
|
1003
1171
|
function sortFolder(nodes) {
|
|
1004
1172
|
const folders = nodes.filter((n) => n.kind === "folder");
|
|
@@ -1055,7 +1223,7 @@ function filterTree(nodes, query) {
|
|
|
1055
1223
|
}
|
|
1056
1224
|
|
|
1057
1225
|
// src/shell/tree/buildComponents.ts
|
|
1058
|
-
function buildComponents(index) {
|
|
1226
|
+
function buildComponents(index, libraries) {
|
|
1059
1227
|
const byTitle = /* @__PURE__ */ new Map();
|
|
1060
1228
|
const pages = new Map(indexEntries(index).map((page) => [page.title, page]));
|
|
1061
1229
|
for (const entry of index) {
|
|
@@ -1065,7 +1233,7 @@ function buildComponents(index) {
|
|
|
1065
1233
|
row = {
|
|
1066
1234
|
path: entry.title,
|
|
1067
1235
|
label: segments[segments.length - 1],
|
|
1068
|
-
library:
|
|
1236
|
+
library: libraries.of(entry),
|
|
1069
1237
|
index: pages.get(entry.title),
|
|
1070
1238
|
entries: []
|
|
1071
1239
|
};
|
|
@@ -1142,8 +1310,8 @@ function componentNodes(rows) {
|
|
|
1142
1310
|
}
|
|
1143
1311
|
return foldGalleries(root);
|
|
1144
1312
|
}
|
|
1145
|
-
function librariesIn(index) {
|
|
1146
|
-
return [...new Set(index.map(
|
|
1313
|
+
function librariesIn(index, libraries) {
|
|
1314
|
+
return [...new Set(index.map(libraries.of))].sort((a, b) => a.localeCompare(b));
|
|
1147
1315
|
}
|
|
1148
1316
|
var VIEWS = [
|
|
1149
1317
|
{ value: "tree", label: "Tree" },
|
|
@@ -1173,7 +1341,8 @@ function StoryTree({ ctx, index }) {
|
|
|
1173
1341
|
const [hidden, setHidden] = usePersistedState("fg-tree-libraries", {}, { scope: "lab" });
|
|
1174
1342
|
const [focusKey, setFocusKey] = useState(null);
|
|
1175
1343
|
const items = useRef(/* @__PURE__ */ new Map());
|
|
1176
|
-
const
|
|
1344
|
+
const table = useContext(LibrariesContext);
|
|
1345
|
+
const libraries = useMemo(() => librariesIn(index, table), [index, table]);
|
|
1177
1346
|
const views = useMemo(() => {
|
|
1178
1347
|
const hasGallery = index.some(isGallery);
|
|
1179
1348
|
const hasOther = index.some((entry) => !isGallery(entry));
|
|
@@ -1181,7 +1350,7 @@ function StoryTree({ ctx, index }) {
|
|
|
1181
1350
|
}, [index]);
|
|
1182
1351
|
const enabled = (value) => views.some((item) => item.value === value && !item.disabled);
|
|
1183
1352
|
const view = [chosen, "components", "gallery"].find(enabled) ?? chosen;
|
|
1184
|
-
const kept = useMemo(() => index.filter((entry) => !hidden[
|
|
1353
|
+
const kept = useMemo(() => index.filter((entry) => !hidden[table.of(entry)]), [index, hidden, table]);
|
|
1185
1354
|
const [galleryEntries, rest] = useMemo(() => {
|
|
1186
1355
|
const yes = [];
|
|
1187
1356
|
const no = [];
|
|
@@ -1189,8 +1358,8 @@ function StoryTree({ ctx, index }) {
|
|
|
1189
1358
|
return [yes, no];
|
|
1190
1359
|
}, [kept]);
|
|
1191
1360
|
const tree = useMemo(() => buildTree(rest), [rest]);
|
|
1192
|
-
const components = useMemo(() => buildComponents(rest), [rest]);
|
|
1193
|
-
const galleries = useMemo(() => buildComponents(galleryEntries), [galleryEntries]);
|
|
1361
|
+
const components = useMemo(() => buildComponents(rest, table), [rest, table]);
|
|
1362
|
+
const galleries = useMemo(() => buildComponents(galleryEntries, table), [galleryEntries, table]);
|
|
1194
1363
|
const shown = useMemo(
|
|
1195
1364
|
() => view === "tree" ? filterTree(tree, query) : componentNodes(filterComponents(view === "gallery" ? galleries : components, query)),
|
|
1196
1365
|
[view, components, galleries, tree, query]
|
|
@@ -1221,8 +1390,11 @@ function StoryTree({ ctx, index }) {
|
|
|
1221
1390
|
const activate = (entry, another) => {
|
|
1222
1391
|
const focused = ctx.trials.find((trial) => trial.id === ctx.focusedTrialId);
|
|
1223
1392
|
if (another || !focused) ctx.addTrial(entry.id);
|
|
1224
|
-
else if (focused.instrumentName === entry.id)
|
|
1225
|
-
|
|
1393
|
+
else if (focused.instrumentName === entry.id) {
|
|
1394
|
+
revealTrial(focused.id);
|
|
1395
|
+
setRoute2(entry.id, { fromTrial: true });
|
|
1396
|
+
return;
|
|
1397
|
+
} else {
|
|
1226
1398
|
ctx.swapTrial(focused.id, entry.id);
|
|
1227
1399
|
setRoute2(entry.id, { inPlace: true });
|
|
1228
1400
|
return;
|
|
@@ -1487,156 +1659,20 @@ function createAnswerBook() {
|
|
|
1487
1659
|
}
|
|
1488
1660
|
};
|
|
1489
1661
|
}
|
|
1490
|
-
function TransportBar({ clock }) {
|
|
1491
|
-
const { time, playing, loop, rate, span } = useSyncExternalStore(clock.subscribe, clock.get);
|
|
1492
|
-
return /* @__PURE__ */ jsxs("div", { className: "fg-transport", children: [
|
|
1493
|
-
/* @__PURE__ */ jsx(
|
|
1494
|
-
Slider,
|
|
1495
|
-
{
|
|
1496
|
-
className: "fg-transport__scrub",
|
|
1497
|
-
ariaLabel: "Scrub",
|
|
1498
|
-
density: "slim",
|
|
1499
|
-
readoutPlacement: "none",
|
|
1500
|
-
min: span.start,
|
|
1501
|
-
max: span.end > span.start ? span.end : span.start + 1,
|
|
1502
|
-
thumbs: [{ value: time, valueText: `${formatSeconds(time)} seconds` }],
|
|
1503
|
-
onInput: ([thumb]) => {
|
|
1504
|
-
if (thumb) clock.seek(thumb.value);
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
),
|
|
1508
|
-
/* @__PURE__ */ jsx(
|
|
1509
|
-
Transport,
|
|
1510
|
-
{
|
|
1511
|
-
paused: !playing,
|
|
1512
|
-
loop,
|
|
1513
|
-
rate,
|
|
1514
|
-
playhead: time,
|
|
1515
|
-
duration: span.end,
|
|
1516
|
-
onPlay: clock.play,
|
|
1517
|
-
onPause: clock.pause,
|
|
1518
|
-
onLoopChange: (next) => clock.setLoop(next !== false && next !== 0),
|
|
1519
|
-
onRateChange: clock.setRate
|
|
1520
|
-
}
|
|
1521
|
-
)
|
|
1522
|
-
] });
|
|
1523
|
-
}
|
|
1524
|
-
function TrialTransport({ trialId }) {
|
|
1525
|
-
const clock = useTrialClock(trialId);
|
|
1526
|
-
return clock ? /* @__PURE__ */ jsx(TransportBar, { clock }) : null;
|
|
1527
|
-
}
|
|
1528
|
-
function Crumbs({ entry }) {
|
|
1529
|
-
const steps = crumbs(entry.title, entry.name);
|
|
1530
|
-
return /* @__PURE__ */ jsx("span", { className: "fg-crumbs", children: steps.map((step, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1531
|
-
i > 0 ? /* @__PURE__ */ jsx("span", { className: "fg-crumbs__sep", "aria-hidden": "true", children: "\u203A" }) : null,
|
|
1532
|
-
step.path === null ? /* @__PURE__ */ jsx("span", { className: "fg-crumbs__here", children: step.label }) : /* @__PURE__ */ jsx(
|
|
1533
|
-
"button",
|
|
1534
|
-
{
|
|
1535
|
-
type: "button",
|
|
1536
|
-
className: "fg-crumbs__step",
|
|
1537
|
-
title: `Show ${step.label} in the story tree`,
|
|
1538
|
-
onPointerDown: (event) => event.stopPropagation(),
|
|
1539
|
-
onClick: () => revealInTree(step.path),
|
|
1540
|
-
children: step.label
|
|
1541
|
-
}
|
|
1542
|
-
)
|
|
1543
|
-
] }, step.path ?? `${step.label}:${i}`)) });
|
|
1544
|
-
}
|
|
1545
|
-
function crumbTitle(entry) {
|
|
1546
|
-
const title = breadcrumb(entry.title, entry.name);
|
|
1547
|
-
return { title, renderTitle: (shown) => shown === title ? /* @__PURE__ */ jsx(Crumbs, { entry }) : shown };
|
|
1548
|
-
}
|
|
1549
|
-
function IndexTrial({ bundle, setup, config }) {
|
|
1550
|
-
const lab = useLabContext();
|
|
1551
|
-
const trialId = useContext(TrialIdContext);
|
|
1552
|
-
const graph = useContext(DependenciesContext);
|
|
1553
|
-
const gallery = isGallery(bundle);
|
|
1554
|
-
const dependencies = gallery ? void 0 : graph?.[bundle.title];
|
|
1555
|
-
const open = (id) => {
|
|
1556
|
-
if (!trialId || !lab.instruments.some((instrument) => instrument.name === id)) return;
|
|
1557
|
-
lab.swapTrial(trialId, id);
|
|
1558
|
-
setRoute(id, { inPlace: true });
|
|
1559
|
-
};
|
|
1560
|
-
const render = (globals) => {
|
|
1561
|
-
const env = {
|
|
1562
|
-
title: bundle.title,
|
|
1563
|
-
library: bundle.library,
|
|
1564
|
-
...bundle.description === void 0 ? {} : { description: bundle.description },
|
|
1565
|
-
stories: bundle.stories,
|
|
1566
|
-
descriptions: bundle.descriptions,
|
|
1567
|
-
gallery,
|
|
1568
|
-
galleries: bundle.galleries,
|
|
1569
|
-
...dependencies ? { dependencies } : {},
|
|
1570
|
-
globals,
|
|
1571
|
-
decorators: setup.decorators ?? [],
|
|
1572
|
-
open,
|
|
1573
|
-
// A story that throws shows its error in its own cell; the rest of the page is still worth reading.
|
|
1574
|
-
onError: (error) => console.error(error)
|
|
1575
|
-
};
|
|
1576
|
-
return /* @__PURE__ */ jsx(IndexPage, { env, render: bundle.render });
|
|
1577
|
-
};
|
|
1578
|
-
return /* @__PURE__ */ jsx(TrialHost, { layout: "fullscreen", setup, config, children: render });
|
|
1579
|
-
}
|
|
1580
|
-
function withPlayhead(schema) {
|
|
1581
|
-
return f.schema({ ...schema.nodes, [PLAYHEAD_KEY]: f.custom("forge-playhead", null).hidden() });
|
|
1582
|
-
}
|
|
1583
|
-
function documentInstrument(options) {
|
|
1584
|
-
const { entry, loaded, setup, globals = {} } = options;
|
|
1585
|
-
const schema = loaded.kind === "story" ? loaded.story.config : f.schema({});
|
|
1586
|
-
const timed = loaded.kind === "story" && loaded.story.timeline !== null;
|
|
1587
|
-
const config = withGlobals(timed ? withPlayhead(schema) : schema, globals);
|
|
1588
|
-
const initialState = loaded.kind === "story" ? loaded.story.initialState : null;
|
|
1589
|
-
return {
|
|
1590
|
-
name: entry.id,
|
|
1591
|
-
...crumbTitle(entry),
|
|
1592
|
-
config,
|
|
1593
|
-
defaultConfig: () => config.defaults(),
|
|
1594
|
-
initialState: (c) => initialState?.(c) ?? null,
|
|
1595
|
-
render: (ctx) => loaded.kind === "story" ? /* @__PURE__ */ jsx(StoryTrial, { story: loaded.story, setup, ctx }, loaded.story.id) : /* @__PURE__ */ jsx(IndexTrial, { bundle: loaded.bundle, setup, config: ctx.config }),
|
|
1596
|
-
...loaded.kind === "story" && loaded.story.viewport ? { stage: { size: { width: loaded.story.viewport.width, height: loaded.story.viewport.height } } } : {},
|
|
1597
|
-
...timed ? { chrome: [{ id: "fg-transport", region: "status", render: (c) => /* @__PURE__ */ jsx(TrialTransport, { trialId: c.trialId }) }] } : {}
|
|
1598
|
-
};
|
|
1599
|
-
}
|
|
1600
|
-
function StoryLoad({ entry, load, fault }) {
|
|
1601
|
-
useEffect(() => {
|
|
1602
|
-
if (!fault) load(entry);
|
|
1603
|
-
}, [entry, load, fault]);
|
|
1604
|
-
return /* @__PURE__ */ jsx("div", { className: "fg-story fg-story--loading", "data-fg-layout": "fullscreen", children: fault ? /* @__PURE__ */ jsxs("div", { className: "fg-fault", role: "alert", children: [
|
|
1605
|
-
/* @__PURE__ */ jsx("span", { className: "fg-fault__phase", children: "import" }),
|
|
1606
|
-
/* @__PURE__ */ jsx("p", { className: "fg-fault__message", children: fault.message })
|
|
1607
|
-
] }) : null });
|
|
1608
|
-
}
|
|
1609
|
-
function pendingInstrument(options) {
|
|
1610
|
-
const { entry, globals = {} } = options;
|
|
1611
|
-
const config = withGlobals(f.schema({}), globals);
|
|
1612
|
-
return {
|
|
1613
|
-
name: entry.id,
|
|
1614
|
-
...crumbTitle(entry),
|
|
1615
|
-
config,
|
|
1616
|
-
defaultConfig: () => config.defaults(),
|
|
1617
|
-
initialState: () => null,
|
|
1618
|
-
render: () => /* @__PURE__ */ jsx(StoryLoad, { ...options }, options.generation ?? 0)
|
|
1619
|
-
};
|
|
1620
|
-
}
|
|
1621
1662
|
|
|
1622
1663
|
// src/shell/readyKey.ts
|
|
1623
1664
|
var readyKey = (ready) => stableStringify({ schema: ready.schema, layout: ready.layout, viewport: ready.viewport });
|
|
1624
1665
|
var START_TIMEOUT_MS = 1e4;
|
|
1625
|
-
var HELLO_TIMEOUT_MS = 3e4;
|
|
1626
1666
|
var requests = 0;
|
|
1627
1667
|
function closeLink(link) {
|
|
1628
1668
|
if (!link.current) return;
|
|
1629
1669
|
clearTimeout(link.current.timer);
|
|
1630
|
-
for (const
|
|
1670
|
+
for (const settle of link.current.pending.values()) settle({ ok: false, message: "The frame went away" });
|
|
1631
1671
|
link.current.pending.clear();
|
|
1632
1672
|
link.current.disconnect();
|
|
1633
1673
|
link.current.channel.close();
|
|
1634
1674
|
link.current = null;
|
|
1635
1675
|
}
|
|
1636
|
-
function settle(link, id, value) {
|
|
1637
|
-
link.pending.get(id)?.resolve?.(value);
|
|
1638
|
-
link.pending.delete(id);
|
|
1639
|
-
}
|
|
1640
1676
|
function mismatchMessage(mismatch) {
|
|
1641
1677
|
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";
|
|
1642
1678
|
}
|
|
@@ -1700,20 +1736,13 @@ function FrameView(props) {
|
|
|
1700
1736
|
case "vars":
|
|
1701
1737
|
if (trialId) frames?.report(trialId, msg.vars);
|
|
1702
1738
|
break;
|
|
1703
|
-
case "size":
|
|
1704
|
-
if (trialId) frames?.reportSize(trialId, { width: msg.width, height: msg.height });
|
|
1705
|
-
break;
|
|
1706
1739
|
case "a11y": {
|
|
1707
1740
|
const outcome = msg.ok ? { ok: true, report: msg.report } : { ok: false, message: msg.message };
|
|
1708
1741
|
if (trialId) frames?.reportA11y(trialId, outcome);
|
|
1709
|
-
|
|
1710
|
-
break;
|
|
1711
|
-
}
|
|
1712
|
-
case "capture":
|
|
1713
|
-
if (msg.ok) settle(current, msg.id, msg.picture);
|
|
1714
|
-
else current.pending.get(msg.id)?.fail(new Error(msg.message));
|
|
1742
|
+
current.pending.get(msg.id)?.(outcome);
|
|
1715
1743
|
current.pending.delete(msg.id);
|
|
1716
1744
|
break;
|
|
1745
|
+
}
|
|
1717
1746
|
case "open": {
|
|
1718
1747
|
const { lab: liveLab } = latest.current;
|
|
1719
1748
|
if (!trialId || !liveLab.instruments.some((instrument) => instrument.name === msg.id)) break;
|
|
@@ -1742,33 +1771,14 @@ function FrameView(props) {
|
|
|
1742
1771
|
}
|
|
1743
1772
|
});
|
|
1744
1773
|
const waiting = /* @__PURE__ */ new Map();
|
|
1745
|
-
const
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
gone: () => gone(resolve, reject)
|
|
1750
|
-
});
|
|
1751
|
-
const ask = () => channel.send(msg);
|
|
1774
|
+
const audit = () => new Promise((resolve) => {
|
|
1775
|
+
const id = `a11y-${requests += 1}`;
|
|
1776
|
+
waiting.set(id, resolve);
|
|
1777
|
+
const ask = () => channel.send({ type: "a11y.run", id });
|
|
1752
1778
|
if (current.hasRendered) ask();
|
|
1753
1779
|
else current.queued.push(ask);
|
|
1754
1780
|
});
|
|
1755
|
-
const
|
|
1756
|
-
const id = `a11y-${requests += 1}`;
|
|
1757
|
-
return request(
|
|
1758
|
-
id,
|
|
1759
|
-
{ type: "a11y.run", id },
|
|
1760
|
-
(resolve) => resolve({ ok: false, message: "The frame went away" })
|
|
1761
|
-
);
|
|
1762
|
-
};
|
|
1763
|
-
const capture = () => {
|
|
1764
|
-
const id = `capture-${requests += 1}`;
|
|
1765
|
-
return request(
|
|
1766
|
-
id,
|
|
1767
|
-
{ type: "capture.run", id },
|
|
1768
|
-
(_resolve, fail) => fail(new Error("The frame could not draw itself"))
|
|
1769
|
-
);
|
|
1770
|
-
};
|
|
1771
|
-
const disconnect = frames && trialId ? frames.connect(trialId, { send: channel.send, audit, capture }) : () => {
|
|
1781
|
+
const disconnect = frames && trialId ? frames.connect(trialId, { send: channel.send, audit }) : () => {
|
|
1772
1782
|
};
|
|
1773
1783
|
const current = {
|
|
1774
1784
|
channel,
|
|
@@ -1861,23 +1871,13 @@ function FrameView(props) {
|
|
|
1861
1871
|
}
|
|
1862
1872
|
if (input) setFault((shown) => shown?.phase === "render" ? null : shown);
|
|
1863
1873
|
}, [ctx.config, ctx.state, globals, descriptionKey, init]);
|
|
1864
|
-
return /* @__PURE__ */ jsxs(
|
|
1865
|
-
"div",
|
|
1866
|
-
{
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
className: "fg-frame-host",
|
|
1872
|
-
children: [
|
|
1873
|
-
/* @__PURE__ */ jsx("div", { ref: slotRef, className: "fg-frame-slot" }),
|
|
1874
|
-
fault ? /* @__PURE__ */ jsxs("div", { className: "fg-fault", role: "alert", children: [
|
|
1875
|
-
fault.phase ? /* @__PURE__ */ jsx("span", { className: "fg-fault__phase", children: fault.phase }) : null,
|
|
1876
|
-
/* @__PURE__ */ jsx("p", { className: "fg-fault__message", children: fault.message })
|
|
1877
|
-
] }) : null
|
|
1878
|
-
]
|
|
1879
|
-
}
|
|
1880
|
-
);
|
|
1874
|
+
return /* @__PURE__ */ jsxs("div", { ref: hostRef, className: "fg-frame-host", children: [
|
|
1875
|
+
/* @__PURE__ */ jsx("div", { ref: slotRef, className: "fg-frame-slot" }),
|
|
1876
|
+
fault ? /* @__PURE__ */ jsxs("div", { className: "fg-fault", role: "alert", children: [
|
|
1877
|
+
fault.phase ? /* @__PURE__ */ jsx("span", { className: "fg-fault__phase", children: fault.phase }) : null,
|
|
1878
|
+
/* @__PURE__ */ jsx("p", { className: "fg-fault__message", children: fault.message })
|
|
1879
|
+
] }) : null
|
|
1880
|
+
] });
|
|
1881
1881
|
}
|
|
1882
1882
|
function storyInstrument(options) {
|
|
1883
1883
|
const { entry, ready, answers, frameUrl, onReady, globals = {} } = options;
|
|
@@ -1915,7 +1915,7 @@ var NO_SETUP = {};
|
|
|
1915
1915
|
var pruned = (map, ids) => [...map.keys()].every((id) => ids.has(id)) ? map : new Map([...map].filter(([id]) => ids.has(id)));
|
|
1916
1916
|
var toError = (error) => error instanceof Error ? error : new Error(String(error));
|
|
1917
1917
|
function useStoryRegistry(index, options) {
|
|
1918
|
-
const { frameUrl, globals = NO_GLOBALS2, importers, setup = NO_SETUP, load = loadStories } = options;
|
|
1918
|
+
const { frameUrl, globals = NO_GLOBALS2, importers, setup = NO_SETUP, libraries = TITLE_LIBRARIES, load = loadStories } = options;
|
|
1919
1919
|
const [readies, setReadies] = useState(() => /* @__PURE__ */ new Map());
|
|
1920
1920
|
const [loaded, setLoaded] = useState(() => /* @__PURE__ */ new Map());
|
|
1921
1921
|
const [faults, setFaults] = useState(() => /* @__PURE__ */ new Map());
|
|
@@ -1939,11 +1939,13 @@ function useStoryRegistry(index, options) {
|
|
|
1939
1939
|
setRevisions((prev) => pruned(prev, ids));
|
|
1940
1940
|
}
|
|
1941
1941
|
const imports = useRef(/* @__PURE__ */ new Map());
|
|
1942
|
-
const live = useLatest({ index, importers, setup, load });
|
|
1942
|
+
const live = useLatest({ index, importers, setup, load, libraries });
|
|
1943
|
+
const missing = useRef(/* @__PURE__ */ new Set());
|
|
1943
1944
|
const importOf = useCallback((file) => {
|
|
1944
1945
|
let loading = imports.current.get(file);
|
|
1945
1946
|
if (!loading) {
|
|
1946
1947
|
const importer = live.current.importers?.[file];
|
|
1948
|
+
if (!importer) missing.current.add(file);
|
|
1947
1949
|
loading = importer ? importer() : Promise.reject(new Error(`No importer for ${file}`));
|
|
1948
1950
|
imports.current.set(file, loading);
|
|
1949
1951
|
}
|
|
@@ -1952,7 +1954,7 @@ function useStoryRegistry(index, options) {
|
|
|
1952
1954
|
const request = useCallback(
|
|
1953
1955
|
(entry) => {
|
|
1954
1956
|
const { setup: current, load: loadModule } = live.current;
|
|
1955
|
-
const
|
|
1957
|
+
const settle = (ids2, result) => {
|
|
1956
1958
|
setLoaded((prev) => {
|
|
1957
1959
|
const next = new Map(prev);
|
|
1958
1960
|
for (const [id, value] of result) next.set(id, value);
|
|
@@ -1982,7 +1984,7 @@ function useStoryRegistry(index, options) {
|
|
|
1982
1984
|
kind: "index",
|
|
1983
1985
|
bundle: {
|
|
1984
1986
|
title: entry.title,
|
|
1985
|
-
library:
|
|
1987
|
+
library: live.current.libraries.of(entry),
|
|
1986
1988
|
...entry.componentDescription === void 0 ? {} : { description: entry.componentDescription },
|
|
1987
1989
|
...entry.tags === void 0 ? {} : { tags: entry.tags },
|
|
1988
1990
|
galleries: entries.filter(isGallery).map((e) => e.id),
|
|
@@ -1991,7 +1993,7 @@ function useStoryRegistry(index, options) {
|
|
|
1991
1993
|
render: mods.map(indexRenderOf).find((render) => render !== null) ?? null
|
|
1992
1994
|
}
|
|
1993
1995
|
});
|
|
1994
|
-
|
|
1996
|
+
settle([entry.id], result);
|
|
1995
1997
|
}).catch((error) => fail([entry.id], error));
|
|
1996
1998
|
return;
|
|
1997
1999
|
}
|
|
@@ -2007,7 +2009,7 @@ function useStoryRegistry(index, options) {
|
|
|
2007
2009
|
result.set(sibling.id, { kind: "story", story });
|
|
2008
2010
|
}
|
|
2009
2011
|
if (!result.has(entry.id)) throw new Error(`${entry.file} has no story export "${entry.exportName}"`);
|
|
2010
|
-
|
|
2012
|
+
settle(ids, result);
|
|
2011
2013
|
}).catch((error) => fail(ids, error));
|
|
2012
2014
|
},
|
|
2013
2015
|
[importOf, live]
|
|
@@ -2023,6 +2025,13 @@ function useStoryRegistry(index, options) {
|
|
|
2023
2025
|
},
|
|
2024
2026
|
[live]
|
|
2025
2027
|
);
|
|
2028
|
+
useEffect(() => {
|
|
2029
|
+
for (const file of [...missing.current]) {
|
|
2030
|
+
if (!importers?.[file]) continue;
|
|
2031
|
+
missing.current.delete(file);
|
|
2032
|
+
reload(file);
|
|
2033
|
+
}
|
|
2034
|
+
}, [importers, reload]);
|
|
2026
2035
|
const committed = useRef({ built: /* @__PURE__ */ new Map(), books: /* @__PURE__ */ new Map(), list: [] });
|
|
2027
2036
|
const cache = useMemo(() => {
|
|
2028
2037
|
const previous = committed.current;
|
|
@@ -2073,12 +2082,7 @@ function useStoryRegistry(index, options) {
|
|
|
2073
2082
|
return { instruments: cache.list, isReady, onReady, reload };
|
|
2074
2083
|
}
|
|
2075
2084
|
var NO_DECLARATIONS = {};
|
|
2076
|
-
|
|
2077
|
-
function initialStory(entries, fallback) {
|
|
2078
|
-
const route = readRoute();
|
|
2079
|
-
return route !== null && entries.some((entry) => entry.id === route) ? route : fallback;
|
|
2080
|
-
}
|
|
2081
|
-
function RouteOpener({ index }) {
|
|
2085
|
+
function RouteOpener() {
|
|
2082
2086
|
const lab = useLabContext();
|
|
2083
2087
|
const [route] = useRoute();
|
|
2084
2088
|
const handled = useRef(null);
|
|
@@ -2086,14 +2090,14 @@ function RouteOpener({ index }) {
|
|
|
2086
2090
|
useEffect(() => {
|
|
2087
2091
|
const from = last.current;
|
|
2088
2092
|
if (route !== from.route) last.current = { route, entry: readRouteEntry() };
|
|
2089
|
-
if (route === null || handled.current === route || !
|
|
2093
|
+
if (route === null || handled.current === route || !lab.instruments.some((i) => i.name === route)) return;
|
|
2090
2094
|
handled.current = route;
|
|
2091
2095
|
if (lab.trials.some((trial) => trial.instrumentName === route)) return;
|
|
2092
2096
|
const previous = lab.trials.find((trial) => trial.instrumentName === from.route);
|
|
2093
2097
|
const target = (previous && crossesInPlace(from.entry, last.current.entry) ? previous : void 0) ?? lab.trials.find((trial) => trial.id === lab.focusedTrialId) ?? lab.trials[0];
|
|
2094
2098
|
if (target) lab.swapTrial(target.id, route);
|
|
2095
2099
|
else lab.addTrial(route);
|
|
2096
|
-
}, [route,
|
|
2100
|
+
}, [route, lab]);
|
|
2097
2101
|
return null;
|
|
2098
2102
|
}
|
|
2099
2103
|
function useInfoShortcut(open) {
|
|
@@ -2130,12 +2134,21 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
|
|
|
2130
2134
|
};
|
|
2131
2135
|
}, [frameUrl, framed]);
|
|
2132
2136
|
const entries = useMemo(() => [...index, ...indexEntries(index)], [index]);
|
|
2137
|
+
const table = config?.libraries;
|
|
2138
|
+
const libraries = useMemo(() => librariesFrom(table), [table]);
|
|
2133
2139
|
const registry = useStoryRegistry(entries, {
|
|
2134
2140
|
frameUrl,
|
|
2135
2141
|
globals: declarations,
|
|
2142
|
+
libraries,
|
|
2136
2143
|
...importers ? { importers } : {},
|
|
2137
2144
|
...setup ? { setup } : {}
|
|
2138
2145
|
});
|
|
2146
|
+
const [route] = useRoute();
|
|
2147
|
+
const missing = route !== null && !entries.some((entry) => entry.id === route) ? route : null;
|
|
2148
|
+
const instruments = useMemo(
|
|
2149
|
+
() => missing === null ? registry.instruments : [...registry.instruments, missingInstrument(missing, declarations)],
|
|
2150
|
+
[registry.instruments, missing, declarations]
|
|
2151
|
+
);
|
|
2139
2152
|
const reload = registry.reload;
|
|
2140
2153
|
useEffect(() => changes?.subscribe(reload), [changes, reload]);
|
|
2141
2154
|
const [labValues, setLabValues] = useState(() => labGlobals(declarations, void 0));
|
|
@@ -2147,20 +2160,20 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
|
|
|
2147
2160
|
(next) => setLabValues((prev) => stableStringify(prev) === stableStringify(next) ? prev : next),
|
|
2148
2161
|
[]
|
|
2149
2162
|
);
|
|
2163
|
+
const infoCommand = useMemo(
|
|
2164
|
+
() => ({ icon: InfoIcon, label: "Info", shortcut: "\u2318I", onActivate: () => setInfoOpen(true) }),
|
|
2165
|
+
[]
|
|
2166
|
+
);
|
|
2150
2167
|
const labChrome = useMemo(
|
|
2151
2168
|
() => [
|
|
2152
2169
|
{ id: "fg-stories", region: "sidebar", render: (ctx) => /* @__PURE__ */ jsx(StoryTree, { ctx, index }) },
|
|
2153
|
-
{
|
|
2154
|
-
id: "fg-info",
|
|
2155
|
-
region: "palette",
|
|
2156
|
-
item: { icon: InfoIcon, label: "Info", shortcut: "\u2318I", onActivate: () => setInfoOpen(true) }
|
|
2157
|
-
},
|
|
2170
|
+
{ id: "fg-info", region: "palette", item: infoCommand },
|
|
2158
2171
|
...config?.cssVars === false ? [] : [CSS_VARS_SECTION],
|
|
2159
2172
|
A11Y_SECTION,
|
|
2160
2173
|
...Object.keys(declarations).length > 0 ? [{ id: "fg-globals", region: "header", render: () => /* @__PURE__ */ jsx(GlobalsToolbar, { declarations }) }] : [],
|
|
2161
2174
|
...config?.labChrome ?? []
|
|
2162
2175
|
],
|
|
2163
|
-
[index, declarations, config?.cssVars, config?.labChrome]
|
|
2176
|
+
[index, infoCommand, declarations, config?.cssVars, config?.labChrome]
|
|
2164
2177
|
);
|
|
2165
2178
|
const first = index[0];
|
|
2166
2179
|
if (!first) {
|
|
@@ -2169,14 +2182,14 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
|
|
|
2169
2182
|
stories.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "fg-empty__globs", "aria-label": "Story globs", children: stories.map((glob) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("code", { children: glob }) }, glob)) }) : null
|
|
2170
2183
|
] });
|
|
2171
2184
|
}
|
|
2172
|
-
return /* @__PURE__ */ jsx(StoryGlobalsContext.Provider, { value: labValues, children: /* @__PURE__ */ jsx(DependenciesContext.Provider, { value: dependencies ?? null, children: /* @__PURE__ */ jsx(FramePoolContext.Provider, { value: pool, children: /* @__PURE__ */ jsx(TrialFramesContext.Provider, { value: frames, children: /* @__PURE__ */ jsx(TrialClocksContext.Provider, { value: clocks, children: /* @__PURE__ */ jsxs(
|
|
2185
|
+
return /* @__PURE__ */ jsx(LibrariesContext.Provider, { value: libraries, children: /* @__PURE__ */ jsx(StoryGlobalsContext.Provider, { value: labValues, children: /* @__PURE__ */ jsx(DependenciesContext.Provider, { value: dependencies ?? null, children: /* @__PURE__ */ jsx(FramePoolContext.Provider, { value: pool, children: /* @__PURE__ */ jsx(TrialFramesContext.Provider, { value: frames, children: /* @__PURE__ */ jsx(TrialClocksContext.Provider, { value: clocks, children: /* @__PURE__ */ jsxs(
|
|
2173
2186
|
Lab,
|
|
2174
2187
|
{
|
|
2175
2188
|
title,
|
|
2176
2189
|
density: "roomy",
|
|
2177
2190
|
...labTheme ? { theme: labTheme } : {},
|
|
2178
|
-
instruments
|
|
2179
|
-
defaultInstrument:
|
|
2191
|
+
instruments,
|
|
2192
|
+
defaultInstrument: readRoute() ?? first.id,
|
|
2180
2193
|
storageKey: storageKey ?? "weaselforge",
|
|
2181
2194
|
...storage ? { storage } : {},
|
|
2182
2195
|
labChrome,
|
|
@@ -2185,7 +2198,7 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
|
|
|
2185
2198
|
...config?.pages ? { pages: config.pages } : {},
|
|
2186
2199
|
...config?.path !== void 0 ? { path: config.path } : {},
|
|
2187
2200
|
children: [
|
|
2188
|
-
/* @__PURE__ */ jsx(RouteOpener, {
|
|
2201
|
+
/* @__PURE__ */ jsx(RouteOpener, {}),
|
|
2189
2202
|
/* @__PURE__ */ jsx(RouteKnobs, { instruments: registry.instruments, isReady: registry.isReady }),
|
|
2190
2203
|
/* @__PURE__ */ jsx(RoutePlayhead, {}),
|
|
2191
2204
|
/* @__PURE__ */ jsx(
|
|
@@ -2200,7 +2213,7 @@ function Workshop({ index, frameUrl, importers, setup, changes, config, stories
|
|
|
2200
2213
|
/* @__PURE__ */ jsx(LabGlobals, { declarations, onChange: reportLabValues })
|
|
2201
2214
|
]
|
|
2202
2215
|
}
|
|
2203
|
-
) }) }) }) }) });
|
|
2216
|
+
) }) }) }) }) }) });
|
|
2204
2217
|
}
|
|
2205
2218
|
function mountWorkshop({ container, ...props }) {
|
|
2206
2219
|
const target = container ?? document.getElementById("root");
|