@weasel-js/forge 1.13.0-rc.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{FrameController-Tv3jrHyT.d.ts → FrameController-BM6xuEfY.d.ts} +18 -10
- package/dist/{chunk-KEQBGDUZ.js → chunk-B26DWEYT.js} +3 -3
- package/dist/{chunk-KEQBGDUZ.js.map → chunk-B26DWEYT.js.map} +1 -1
- package/dist/{chunk-3BAM6M26.js → chunk-FPF2JSWB.js} +3 -3
- package/dist/chunk-FPF2JSWB.js.map +1 -0
- package/dist/{chunk-ORMMZ5PV.js → chunk-MRYXJTDO.js} +7 -3
- package/dist/chunk-MRYXJTDO.js.map +1 -0
- package/dist/{chunk-35DOPXHZ.js → chunk-PGLOYXDP.js} +193 -190
- package/dist/chunk-PGLOYXDP.js.map +1 -0
- package/dist/{chunk-BUXUWAI4.js → chunk-PPB7VP6R.js} +62 -8
- package/dist/chunk-PPB7VP6R.js.map +1 -0
- package/dist/{chunk-STF6NQ5N.js → chunk-RVJHUY7W.js} +2 -2
- package/dist/chunk-RVJHUY7W.js.map +1 -0
- package/dist/{chunk-LTTEILXY.js → chunk-SFKDHXVE.js} +26 -4
- package/dist/chunk-SFKDHXVE.js.map +1 -0
- package/dist/chunk-STK2AZH2.js +11 -0
- package/dist/chunk-STK2AZH2.js.map +1 -0
- package/dist/chunk-TT4ZOYB6.js +143 -0
- package/dist/chunk-TT4ZOYB6.js.map +1 -0
- package/dist/{chunk-RKQ277P5.js → chunk-UDEUT7AG.js} +125 -80
- package/dist/chunk-UDEUT7AG.js.map +1 -0
- package/dist/{config-Bd6FVlh7.d.ts → config-DOjWyIui.d.ts} +3 -21
- package/dist/csf/preview-api.js +2 -2
- package/dist/frame/index.css +0 -2
- package/dist/frame/index.css.map +1 -1
- package/dist/frame/index.d.ts +14 -8
- package/dist/frame/index.js +114 -79
- package/dist/frame/index.js.map +1 -1
- package/dist/index.d.ts +12 -5
- package/dist/index.js +4 -4
- package/dist/library-CdTj-hFp.d.ts +21 -0
- package/dist/{mountFrame-FdJDb3TB.d.ts → mountFrame-CRPAjg1M.d.ts} +1 -1
- package/dist/shell/index.css +166 -150
- package/dist/shell/index.css.map +1 -1
- package/dist/shell/index.d.ts +24 -5
- package/dist/shell/index.js +594 -402
- package/dist/shell/index.js.map +1 -1
- package/dist/test/index.css +2 -3
- package/dist/test/index.css.map +1 -1
- package/dist/test/index.d.ts +1 -1
- package/dist/test/index.js +8 -10
- package/dist/test/index.js.map +1 -1
- package/dist/vite/index.js +127 -32
- package/dist/vite/index.js.map +1 -1
- package/package.json +11 -9
- package/dist/chunk-35DOPXHZ.js.map +0 -1
- package/dist/chunk-3BAM6M26.js.map +0 -1
- package/dist/chunk-BUXUWAI4.js.map +0 -1
- package/dist/chunk-GH5RE5X5.js +0 -10
- package/dist/chunk-GH5RE5X5.js.map +0 -1
- package/dist/chunk-L5XM5VKB.js +0 -47
- package/dist/chunk-L5XM5VKB.js.map +0 -1
- package/dist/chunk-LTTEILXY.js.map +0 -1
- package/dist/chunk-ORMMZ5PV.js.map +0 -1
- package/dist/chunk-RKQ277P5.js.map +0 -1
- package/dist/chunk-STF6NQ5N.js.map +0 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { stableStringify } from '@weasel-js/core';
|
|
2
|
+
|
|
3
|
+
// src/protocol/messages.ts
|
|
4
|
+
var PROTOCOL_VERSION = 1;
|
|
5
|
+
var DEFAULT_LAYOUT = "padded";
|
|
6
|
+
var PORT_HANDOFF = "weaselforge:port";
|
|
7
|
+
var FRAME_HELLO = "weaselforge:hello";
|
|
8
|
+
|
|
9
|
+
export { DEFAULT_LAYOUT, FRAME_HELLO, PORT_HANDOFF, PROTOCOL_VERSION };
|
|
10
|
+
//# sourceMappingURL=chunk-STK2AZH2.js.map
|
|
11
|
+
//# sourceMappingURL=chunk-STK2AZH2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/protocol/messages.ts"],"names":[],"mappings":";;;AAEO,IAAM,gBAAA,GAAmB;AAOzB,IAAM,cAAA,GAAyB;AAgG/B,IAAM,YAAA,GAAe;AASrB,IAAM,WAAA,GAAc","file":"chunk-STK2AZH2.js","sourcesContent":["import type { SchemaDescription } from './schema.ts';\n\nexport const PROTOCOL_VERSION = 1;\n\n/** The workshop's toolbar globals as a story reads them, by name. */\nexport type Globals = Readonly<Record<string, unknown>>;\n/** How a story sits in its frame. */\nexport type Layout = 'centered' | 'padded' | 'fullscreen';\n/** The layout a story takes when it names none, in either story format. */\nexport const DEFAULT_LAYOUT: Layout = 'padded';\n/** A frame size a story asks for, in CSS pixels. */\nexport interface Viewport {\n width: number;\n height: number;\n}\n/** Where a frame's fault happened: loading the story, rendering it, or reading a message. */\nexport type FaultPhase = 'import' | 'render' | 'protocol';\n\n/** One custom property as the frame found it. */\nexport interface CssVarReport {\n name: string;\n value: string;\n /** Whether a `vars.set` override is in force on it. */\n overridden: boolean;\n /** Whether the story reads it, directly or through another property it reads. */\n used: boolean;\n}\n\n/** Visibility and validation answers, keyed by the config they were computed for. */\nexport interface ConfigAnswers {\n /** `stableStringify(config)` of the config the frame evaluated. */\n configKey: string;\n hidden: string[];\n errors: Record<string, string[]>;\n}\n\n/** One element axe faulted on, inside the frame's own document. */\nexport interface A11yNode {\n /** The CSS selectors, outermost first, that reach the element in the frame. */\n target: readonly string[];\n html: string;\n failureSummary?: string;\n /** On a node axe could not decide: why, as axe's message key (`bgOverlap`, `pseudoContent`, …) and its own sentence. */\n reason?: { key?: string; message?: string };\n}\n\n/** One axe rule's outcome on the story, with the elements it names. */\nexport interface A11yFinding {\n id: string;\n impact: 'minor' | 'moderate' | 'serious' | 'critical' | null;\n help: string;\n helpUrl: string;\n description: string;\n nodes: readonly A11yNode[];\n}\n\n/** What axe found on one story: the rules it failed, and the ones it could not decide. */\nexport interface A11yReport {\n violations: readonly A11yFinding[];\n incomplete: readonly A11yFinding[];\n /** Rules that passed, and rules no element on the page could be judged by. */\n passes: number;\n inapplicable: number;\n}\n\n/** What an axe run in the frame came back with. */\nexport type A11yOutcome = { ok: true; report: A11yReport } | { ok: false; message: string };\n\n/** A message the workshop sends a story's frame. */\nexport type ToFrame =\n | { type: 'init'; config: unknown; state: unknown; globals: Globals }\n | { type: 'config'; config: unknown }\n | { type: 'state'; state: unknown }\n | { type: 'globals'; globals: Globals }\n | { type: 'vars.set'; name: string; value: string | null }\n /** Run axe over the story's own subtree. Answered by `a11y` carrying the same `id`. */\n | { type: 'a11y.run'; id: string }\n /** Outlines the elements each `A11yNode.target` reaches, in place of the last ones; an empty list clears. */\n | { type: 'a11y.highlight'; targets: readonly (readonly string[])[] }\n /** Scrolls the frame document to `left`, `top` in CSS px: a lens's copy following its trial's frame. */\n | { type: 'scroll'; left: number; top: number };\n\n/** A message a story's frame sends the workshop. */\nexport type FromFrame =\n | { type: 'ready'; schema: SchemaDescription; layout: Layout; viewport: Viewport | null }\n /** Sent once, when the story's first render after `init` has committed. */\n | { type: 'rendered' }\n | { type: 'answers'; answers: ConfigAnswers }\n | { type: 'setConfig'; path: string; value: unknown }\n | { type: 'setState'; state: unknown }\n | { type: 'vars'; vars: CssVarReport[] }\n /** Asks the shell to show story `id` in this frame's trial in place of what it shows now. */\n | { type: 'open'; id: string }\n | ({ type: 'a11y'; id: string } & A11yOutcome)\n /** Where the frame document stands, in CSS px: sent once after `ready`, then whenever it scrolls. */\n | { type: 'scroll'; left: number; top: number }\n /** `seq`, on a render fault only: how many `init`/`config`/`state`/`globals` messages the frame had received. */\n | { type: 'fault'; phase: FaultPhase; message: string; stack?: string; seq?: number };\n\nexport interface Envelope<M> {\n v: number;\n msg: M;\n}\n\n/** Posted on the frame's window with the port transferred alongside it, as a `PortHandoff`. */\nexport const PORT_HANDOFF = 'weaselforge:port';\n\n/** What the shell posts with the port: `id` names what the frame shows, a story or an index. */\nexport interface PortHandoff {\n type: typeof PORT_HANDOFF;\n id: string;\n}\n\n/** Posted by a frame document to its parent as soon as its entry runs, asking for a port. */\nexport const FRAME_HELLO = 'weaselforge:hello';\n\nexport { stableStringify } from '@weasel-js/core';\n"]}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { useLatest } from '@weasel-js/core';
|
|
2
|
+
import { useTrialClockHandle, useTransportControl } from '@weasel-js/labkit';
|
|
3
|
+
import { createContext, useSyncExternalStore, useMemo, useContext, useCallback } from 'react';
|
|
4
|
+
import { createEmitter } from '@weasel-js/registry';
|
|
5
|
+
import { shownInPass } from '@weasel-js/clock';
|
|
6
|
+
|
|
7
|
+
// src/story/define.ts
|
|
8
|
+
var NATIVE = /* @__PURE__ */ Symbol.for("weaselforge.native");
|
|
9
|
+
function meta(spec) {
|
|
10
|
+
return Object.assign({}, spec, { [NATIVE]: "meta" });
|
|
11
|
+
}
|
|
12
|
+
function story(spec) {
|
|
13
|
+
return Object.assign({}, spec, { [NATIVE]: "story" });
|
|
14
|
+
}
|
|
15
|
+
function isNative(value, kind) {
|
|
16
|
+
return !!value && typeof value === "object" && value[NATIVE] === kind;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/timeline/clockSpec.ts
|
|
20
|
+
var positive = (n) => typeof n === "number" && Number.isFinite(n) && n > 0;
|
|
21
|
+
var lengthOf = (duration) => positive(duration) ? duration : 1;
|
|
22
|
+
var startOf = (start) => typeof start === "number" && Number.isFinite(start) ? start : 0;
|
|
23
|
+
function clockSpecOf(spec) {
|
|
24
|
+
return {
|
|
25
|
+
duration: lengthOf(spec.duration),
|
|
26
|
+
origin: startOf(spec.start),
|
|
27
|
+
loop: spec.loop ?? true,
|
|
28
|
+
speed: positive(spec.rate) ? spec.rate : 1
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function spanOverrideOf(span) {
|
|
32
|
+
return { duration: lengthOf(span.duration), origin: startOf(span.start) };
|
|
33
|
+
}
|
|
34
|
+
function setLoop(clock, loop) {
|
|
35
|
+
clock.loop = loop;
|
|
36
|
+
}
|
|
37
|
+
var speeds = /* @__PURE__ */ new WeakMap();
|
|
38
|
+
var speedChanged = createEmitter();
|
|
39
|
+
function subscribeSpeed(clock, listener) {
|
|
40
|
+
return speedChanged.subscribe((changed) => {
|
|
41
|
+
if (changed === clock) listener();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function ownMoves(clock) {
|
|
45
|
+
const speed = () => speeds.get(clock) ?? (Math.abs(clock.rate) || clock.speed);
|
|
46
|
+
return {
|
|
47
|
+
speed,
|
|
48
|
+
play() {
|
|
49
|
+
if (clock.rate !== 0) return;
|
|
50
|
+
if (clock.ended) clock.seek(0);
|
|
51
|
+
clock.rate = speed();
|
|
52
|
+
},
|
|
53
|
+
pause() {
|
|
54
|
+
clock.rate = 0;
|
|
55
|
+
},
|
|
56
|
+
setSpeed(next) {
|
|
57
|
+
speeds.set(clock, next);
|
|
58
|
+
if (clock.rate !== 0) clock.rate = next;
|
|
59
|
+
else speedChanged.emit(clock);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function shownTime(clock) {
|
|
64
|
+
return shownInPass(clock.origin, clock.elapsed, clock.duration, clock.loop);
|
|
65
|
+
}
|
|
66
|
+
function seekShown(clock, t) {
|
|
67
|
+
const within = Number.isNaN(t) ? 0 : Math.min(Math.max(t - clock.origin, 0), clock.duration);
|
|
68
|
+
clock.seek(within, within >= clock.duration ? "end" : "start");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/timeline/context.ts
|
|
72
|
+
var ClockContext = createContext(null);
|
|
73
|
+
function useClock(hook) {
|
|
74
|
+
const handle = useContext(ClockContext);
|
|
75
|
+
if (!handle) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`${hook}: this story declares no timeline. Give it \`timeline: { duration }\`, or \`parameters.forge.timeline\` in CSF.`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return handle;
|
|
81
|
+
}
|
|
82
|
+
function useClockChanges(handle) {
|
|
83
|
+
return useCallback(
|
|
84
|
+
(listener) => {
|
|
85
|
+
const offChange = handle.clock.subscribe(listener);
|
|
86
|
+
const offFrame = handle.onFrame(listener);
|
|
87
|
+
const offSpeed = subscribeSpeed(handle.clock, listener);
|
|
88
|
+
return () => {
|
|
89
|
+
offChange();
|
|
90
|
+
offFrame();
|
|
91
|
+
offSpeed();
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
[handle]
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
function usePlayhead() {
|
|
98
|
+
const handle = useClock("usePlayhead");
|
|
99
|
+
return useSyncExternalStore(useClockChanges(handle), () => shownTime(handle.clock));
|
|
100
|
+
}
|
|
101
|
+
var validRate = (rate) => Number.isFinite(rate) && rate > 0;
|
|
102
|
+
function usePlayMoves(handle) {
|
|
103
|
+
const resolved = useTrialClockHandle();
|
|
104
|
+
const control = useTransportControl(resolved === handle ? void 0 : null);
|
|
105
|
+
if (control === null) return ownMoves(handle.clock);
|
|
106
|
+
return { speed: () => control.speed, play: control.play, pause: control.pause, setSpeed: control.setSpeed };
|
|
107
|
+
}
|
|
108
|
+
function useTimeline() {
|
|
109
|
+
const handle = useClock("useTimeline");
|
|
110
|
+
const { clock } = handle;
|
|
111
|
+
const moves = usePlayMoves(handle);
|
|
112
|
+
const latest = useLatest({ moves });
|
|
113
|
+
useSyncExternalStore(
|
|
114
|
+
useClockChanges(handle),
|
|
115
|
+
() => `${shownTime(clock)}|${clock.rate}|${String(clock.loop)}|${clock.origin}|${clock.duration}|${moves.speed()}`
|
|
116
|
+
);
|
|
117
|
+
const time = shownTime(clock);
|
|
118
|
+
const playing = clock.rate !== 0;
|
|
119
|
+
const loop = clock.loop !== false;
|
|
120
|
+
const rate = moves.speed();
|
|
121
|
+
const { origin, duration } = clock;
|
|
122
|
+
const actions = useMemo(
|
|
123
|
+
() => ({
|
|
124
|
+
play: () => latest.current.moves.play(),
|
|
125
|
+
pause: () => latest.current.moves.pause(),
|
|
126
|
+
seek: (t) => seekShown(handle.clock, t),
|
|
127
|
+
setLoop: (next) => setLoop(handle.clock, next),
|
|
128
|
+
setRate: (next) => {
|
|
129
|
+
if (validRate(next)) latest.current.moves.setSpeed(next);
|
|
130
|
+
},
|
|
131
|
+
setSpan: (span) => handle.override(span ? spanOverrideOf(span) : null)
|
|
132
|
+
}),
|
|
133
|
+
[handle, latest]
|
|
134
|
+
);
|
|
135
|
+
return useMemo(
|
|
136
|
+
() => ({ time, playing, loop, rate, span: { start: origin, end: origin + duration }, ...actions }),
|
|
137
|
+
[time, playing, loop, rate, origin, duration, actions]
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { ClockContext, clockSpecOf, isNative, meta, shownTime, story, usePlayhead, useTimeline };
|
|
142
|
+
//# sourceMappingURL=chunk-TT4ZOYB6.js.map
|
|
143
|
+
//# sourceMappingURL=chunk-TT4ZOYB6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/story/define.ts","../src/timeline/clockSpec.ts","../src/timeline/moves.ts","../src/timeline/shown.ts","../src/timeline/context.ts"],"names":[],"mappings":"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","file":"chunk-TT4ZOYB6.js","sourcesContent":["import type { MetaSpec, StorySpec } from './types';\n\nexport const NATIVE = Symbol.for('weaselforge.native');\n\n/** Declares a native story file's meta; default-export the result. */\nexport function meta(spec: MetaSpec): MetaSpec {\n return Object.assign({}, spec, { [NATIVE]: 'meta' as const });\n}\n\n/** Declares one native story; export the result under the story's name. `C` and `S` come from `config` and `state`. */\nexport function story<C = Record<string, never>, S = undefined>(spec: StorySpec<C, S>): StorySpec<C, S> {\n return Object.assign({}, spec, { [NATIVE]: 'story' as const });\n}\n\nexport function isNative(value: unknown, kind: 'meta' | 'story'): boolean {\n return !!value && typeof value === 'object' && (value as Record<symbol, unknown>)[NATIVE] === kind;\n}\n","import type { ClockSpec } from '@weasel-js/clock';\nimport type { SpanOverride, TimelineSpec } from './types';\n\nconst positive = (n: number | undefined): n is number => typeof n === 'number' && Number.isFinite(n) && n > 0;\n\n/** A span's length, in ms: anything not a positive finite number stands as 1 ms, so a timeline can never throw. */\nconst lengthOf = (duration: number): number => (positive(duration) ? duration : 1);\n\nconst startOf = (start: number | undefined): number => (typeof start === 'number' && Number.isFinite(start) ? start : 0);\n\n/** The clock a story's timeline declares. It opens paused at the span's start, and loops unless told not to. */\nexport function clockSpecOf(spec: TimelineSpec): ClockSpec {\n return {\n duration: lengthOf(spec.duration),\n origin: startOf(spec.start),\n loop: spec.loop ?? true,\n speed: positive(spec.rate) ? spec.rate : 1,\n };\n}\n\n/** The clock override a story's `setSpan` asks for. */\nexport function spanOverrideOf(span: SpanOverride): { duration: number; origin: number } {\n return { duration: lengthOf(span.duration), origin: startOf(span.start) };\n}\n","import type { Clock } from '@weasel-js/clock';\nimport { createEmitter } from '@weasel-js/registry';\n\n/** Loops the clock without end, or plays it once. */\nexport function setLoop(clock: Clock, loop: boolean): void {\n clock.loop = loop;\n}\n\n/** Play, pause and speed, as `useTimeline` asks for them. */\nexport interface PlayMoves {\n /** Unsigned: what a pause comes back to. */\n speed(): number;\n play(): void;\n pause(): void;\n setSpeed(speed: number): void;\n}\n\nconst speeds = new WeakMap<Clock, number>();\nconst speedChanged = createEmitter<[Clock]>();\n\n/** Calls `listener` when the speed a pause of `clock` returns to changes outside a transport. */\nexport function subscribeSpeed(clock: Clock, listener: () => void): () => void {\n return speedChanged.subscribe((changed) => {\n if (changed === clock) listener();\n });\n}\n\n/** The moves on a clock no transport works, as outside a lab: the speed a pause returns to is kept here. */\nexport function ownMoves(clock: Clock): PlayMoves {\n const speed = (): number => speeds.get(clock) ?? (Math.abs(clock.rate) || clock.speed);\n return {\n speed,\n play() {\n if (clock.rate !== 0) return;\n // Playing from the end starts over, as a player's play button does.\n if (clock.ended) clock.seek(0);\n clock.rate = speed();\n },\n pause() {\n clock.rate = 0;\n },\n setSpeed(next) {\n speeds.set(clock, next);\n if (clock.rate !== 0) clock.rate = next;\n else speedChanged.emit(clock);\n },\n };\n}\n","import { type Clock, shownInPass } from '@weasel-js/clock';\n\n/** The time a story's playhead shows: the clock's origin plus how far it is into the current pass; see `shownInPass`. */\nexport function shownTime(clock: Clock): number {\n return shownInPass(clock.origin, clock.elapsed, clock.duration, clock.loop);\n}\n\n/** Moves the playhead to shown time `t`, clamped to the span; the span's end stands as the end of the first pass. */\nexport function seekShown(clock: Clock, t: number): void {\n const within = Number.isNaN(t) ? 0 : Math.min(Math.max(t - clock.origin, 0), clock.duration);\n clock.seek(within, within >= clock.duration ? 'end' : 'start');\n}\n","import type { ClockHandle } from '@weasel-js/clock';\nimport { useLatest } from '@weasel-js/core';\nimport { useTransportControl, useTrialClockHandle } from '@weasel-js/labkit';\nimport { createContext, useCallback, useContext, useMemo, useSyncExternalStore } from 'react';\nimport { spanOverrideOf } from './clockSpec';\nimport { ownMoves, type PlayMoves, setLoop, subscribeSpeed } from './moves';\nimport { seekShown, shownTime } from './shown';\nimport type { Span, SpanOverride } from './types';\n\n/** The clock of the story rendering below it; null where the story declares no timeline. */\nexport const ClockContext = createContext<ClockHandle | null>(null);\n\n/** What `useTimeline` hands a story: where its playhead stands, and the moves the transport makes. Times in ms. */\nexport interface StoryTimeline {\n /** The playhead, in ms, always inside `span`. */\n time: number;\n playing: boolean;\n loop: boolean;\n rate: number;\n span: Span;\n play(): void;\n pause(): void;\n seek(time: number): void;\n setLoop(loop: boolean): void;\n setRate(rate: number): void;\n /** Replaces the declared span, for one the story only learns once it runs; null goes back to the declared one. */\n setSpan(span: SpanOverride | null): void;\n}\n\nfunction useClock(hook: string): ClockHandle {\n const handle = useContext(ClockContext);\n if (!handle) {\n throw new Error(\n `${hook}: this story declares no timeline. Give it \\`timeline: { duration }\\`, or \\`parameters.forge.timeline\\` in CSF.`,\n );\n }\n return handle;\n}\n\n/** Subscribes to every change of `handle`'s clock: rate, seek, loop and declare, each frame it moves, and a paused\n * speed change. */\nfunction useClockChanges(handle: ClockHandle): (listener: () => void) => () => void {\n return useCallback(\n (listener) => {\n const offChange = handle.clock.subscribe(listener);\n const offFrame = handle.onFrame(listener);\n const offSpeed = subscribeSpeed(handle.clock, listener);\n return () => {\n offChange();\n offFrame();\n offSpeed();\n };\n },\n [handle],\n );\n}\n\n/** The story's playhead, in ms. Re-renders only the component calling it, on every move. Throws outside a story that\n * declares a timeline. */\nexport function usePlayhead(): number {\n const handle = useClock('usePlayhead');\n return useSyncExternalStore(useClockChanges(handle), () => shownTime(handle.clock));\n}\n\nconst validRate = (rate: number): boolean => Number.isFinite(rate) && rate > 0;\n\n/** Play, pause and speed through the trial's transport, so the story and the transport share one speed, or on the\n * clock itself where no transport works it. A transport over another clock goes unread, so its frames re-render\n * nothing here. */\nfunction usePlayMoves(handle: ClockHandle): PlayMoves {\n const resolved = useTrialClockHandle();\n const control = useTransportControl(resolved === handle ? undefined : null);\n if (control === null) return ownMoves(handle.clock);\n return { speed: () => control.speed, play: control.play, pause: control.pause, setSpeed: control.setSpeed };\n}\n\n/** The story's whole clock: playhead, span, play state, loop and rate, and the moves on them. Throws outside a story\n * that declares a timeline. */\nexport function useTimeline(): StoryTimeline {\n const handle = useClock('useTimeline');\n const { clock } = handle;\n const moves = usePlayMoves(handle);\n const latest = useLatest({ moves });\n useSyncExternalStore(\n useClockChanges(handle),\n () =>\n `${shownTime(clock)}|${clock.rate}|${String(clock.loop)}|${clock.origin}|${clock.duration}|${moves.speed()}`,\n );\n const time = shownTime(clock);\n const playing = clock.rate !== 0;\n const loop = clock.loop !== false;\n const rate = moves.speed();\n const { origin, duration } = clock;\n\n const actions = useMemo(\n () => ({\n play: () => latest.current.moves.play(),\n pause: () => latest.current.moves.pause(),\n seek: (t: number) => seekShown(handle.clock, t),\n setLoop: (next: boolean) => setLoop(handle.clock, next),\n setRate: (next: number) => {\n if (validRate(next)) latest.current.moves.setSpeed(next);\n },\n setSpan: (span: SpanOverride | null) => handle.override(span ? spanOverrideOf(span) : null),\n }),\n [handle, latest],\n );\n return useMemo(\n () => ({ time, playing, loop, rate, span: { start: origin, end: origin + duration }, ...actions }),\n [time, playing, loop, rate, origin, duration, actions],\n );\n}\n"]}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { GLOBALS_KEY, effectiveGlobals, storyConfig, isForgePath,
|
|
2
|
-
import { StoryHost,
|
|
3
|
-
import { ClockContext } from './chunk-
|
|
1
|
+
import { GLOBALS_KEY, effectiveGlobals, storyConfig, isForgePath, FOLLOW_APP } from './chunk-FPF2JSWB.js';
|
|
2
|
+
import { StoryHost, useHostConditions, createA11yHighlighter, scanCssVars, auditElement } from './chunk-PGLOYXDP.js';
|
|
3
|
+
import { ClockContext } from './chunk-TT4ZOYB6.js';
|
|
4
4
|
import { createEmitter } from '@weasel-js/registry';
|
|
5
5
|
import { createContext, memo, useContext, useCallback, useSyncExternalStore, useMemo, useId, useState, useRef, useLayoutEffect, useEffect } from 'react';
|
|
6
6
|
import { useLatest } from '@weasel-js/core';
|
|
7
|
-
import { usePersistedState, TrialIdContext, useTrialTitle, LabBoundary } from '@weasel-js/labkit';
|
|
7
|
+
import { usePersistedState, TrialIdContext, useTrialClockHandle, useTrialTitle, LabBoundary } from '@weasel-js/labkit';
|
|
8
8
|
import { OverlayPortalProvider } from '@weasel-js/ui/portal-host';
|
|
9
|
+
import { useNearViewport } from '@weasel-js/react';
|
|
9
10
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
10
11
|
import { createPortal } from 'react-dom';
|
|
11
12
|
import { withValueAtPath } from '@weasel-js/labkit/config';
|
|
@@ -46,6 +47,9 @@ function useTrialFrame(trialId) {
|
|
|
46
47
|
return useSyncExternalStore(subscribe, read, read);
|
|
47
48
|
}
|
|
48
49
|
var StoryGlobalsContext = createContext({});
|
|
50
|
+
|
|
51
|
+
// src/shell/inViewMargin.ts
|
|
52
|
+
var IN_VIEW_MARGIN = "50%";
|
|
49
53
|
var NONE = {};
|
|
50
54
|
function useCssOverrides() {
|
|
51
55
|
return usePersistedState("fg-css-overrides", NONE, { scope: "trial" });
|
|
@@ -90,6 +94,95 @@ function StoryScroller({ children }) {
|
|
|
90
94
|
}
|
|
91
95
|
);
|
|
92
96
|
}
|
|
97
|
+
function createStoryHosts() {
|
|
98
|
+
const refs = /* @__PURE__ */ new Map();
|
|
99
|
+
const shows = /* @__PURE__ */ new Map();
|
|
100
|
+
const ref = (trialId) => {
|
|
101
|
+
let held = refs.get(trialId);
|
|
102
|
+
if (!held) {
|
|
103
|
+
held = { current: null };
|
|
104
|
+
refs.set(trialId, held);
|
|
105
|
+
}
|
|
106
|
+
return held;
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
ref,
|
|
110
|
+
attach(trialId, host, show) {
|
|
111
|
+
const held = ref(trialId);
|
|
112
|
+
held.current = host;
|
|
113
|
+
if (show) shows.set(trialId, { host, show });
|
|
114
|
+
return () => {
|
|
115
|
+
if (held.current === host) held.current = null;
|
|
116
|
+
if (shows.get(trialId)?.host === host) shows.delete(trialId);
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
show(trialId) {
|
|
120
|
+
return shows.get(trialId)?.show() ?? Promise.resolve(() => {
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
var StoryHostsContext = createContext(null);
|
|
126
|
+
function useStoryHost(trialId, host, show) {
|
|
127
|
+
const hosts = useContext(StoryHostsContext);
|
|
128
|
+
useLayoutEffect(
|
|
129
|
+
() => host && hosts && trialId ? hosts.attach(trialId, host, show) : void 0,
|
|
130
|
+
[host, hosts, trialId, show]
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
function afterPaint(host, done) {
|
|
134
|
+
let canceled = false;
|
|
135
|
+
const finish = () => {
|
|
136
|
+
if (!canceled) done();
|
|
137
|
+
};
|
|
138
|
+
const { fonts } = host.ownerDocument;
|
|
139
|
+
if (!fonts) finish();
|
|
140
|
+
else {
|
|
141
|
+
const frame = () => requestAnimationFrame(finish);
|
|
142
|
+
void fonts.ready.then(frame, frame);
|
|
143
|
+
}
|
|
144
|
+
return () => {
|
|
145
|
+
canceled = true;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function useStoryShow(host, inView) {
|
|
149
|
+
const [held, setHeld] = useState(0);
|
|
150
|
+
const [waiters] = useState(() => []);
|
|
151
|
+
const live = inView || held > 0;
|
|
152
|
+
const show = useCallback(
|
|
153
|
+
() => new Promise((resolve) => {
|
|
154
|
+
let released = false;
|
|
155
|
+
const release = () => {
|
|
156
|
+
if (released) return;
|
|
157
|
+
released = true;
|
|
158
|
+
setHeld((n) => n - 1);
|
|
159
|
+
};
|
|
160
|
+
waiters.push(() => resolve(release));
|
|
161
|
+
setHeld((n) => n + 1);
|
|
162
|
+
}),
|
|
163
|
+
[waiters]
|
|
164
|
+
);
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (!host || !live || waiters.length === 0) return;
|
|
167
|
+
const waiting = waiters.splice(0);
|
|
168
|
+
let settled = false;
|
|
169
|
+
const cancel = afterPaint(host, () => {
|
|
170
|
+
settled = true;
|
|
171
|
+
for (const resolve of waiting) resolve();
|
|
172
|
+
});
|
|
173
|
+
return () => {
|
|
174
|
+
cancel();
|
|
175
|
+
if (!settled) waiters.unshift(...waiting);
|
|
176
|
+
};
|
|
177
|
+
}, [host, live, held, waiters]);
|
|
178
|
+
useEffect(
|
|
179
|
+
() => () => {
|
|
180
|
+
for (const resolve of waiters.splice(0)) resolve();
|
|
181
|
+
},
|
|
182
|
+
[waiters]
|
|
183
|
+
);
|
|
184
|
+
return { live, show };
|
|
185
|
+
}
|
|
93
186
|
var VARS_SETTLE_MS = 100;
|
|
94
187
|
function StoryContent({ host, children }) {
|
|
95
188
|
return /* @__PURE__ */ jsx(LabBoundary, { children: /* @__PURE__ */ jsx(OverlayPortalProvider, { container: host, contained: true, children }) });
|
|
@@ -107,7 +200,15 @@ function TrialHost({ layout, setup, config, children, hostRef, onRendered, copy
|
|
|
107
200
|
const [host, setHost] = useState(null);
|
|
108
201
|
const elementRef = useRef(null);
|
|
109
202
|
const inView = useNearViewport(elementRef, IN_VIEW_MARGIN);
|
|
203
|
+
const { live, show } = useStoryShow(host, inView);
|
|
110
204
|
const [pending, setPending] = useState(true);
|
|
205
|
+
const [rendered] = useState(() => {
|
|
206
|
+
let resolve;
|
|
207
|
+
const promise = new Promise((r) => {
|
|
208
|
+
resolve = r;
|
|
209
|
+
});
|
|
210
|
+
return { promise, resolve };
|
|
211
|
+
});
|
|
111
212
|
const latest = useLatest({ onRendered: copy ? void 0 : onRendered });
|
|
112
213
|
const overridesRef = useHostConditions(host, { hostId, globals, overrides: persisted, setup });
|
|
113
214
|
useLayoutEffect(() => {
|
|
@@ -140,6 +241,7 @@ function TrialHost({ layout, setup, config, children, hostRef, onRendered, copy
|
|
|
140
241
|
if (lastVars?.some((v) => v.name === msg.name)) scan();
|
|
141
242
|
};
|
|
142
243
|
const audit = async () => {
|
|
244
|
+
await rendered.promise;
|
|
143
245
|
const outcome = await auditElement(host, { page: false });
|
|
144
246
|
if (trialId) frames?.reportA11y(trialId, outcome);
|
|
145
247
|
return outcome;
|
|
@@ -152,28 +254,18 @@ function TrialHost({ layout, setup, config, children, hostRef, onRendered, copy
|
|
|
152
254
|
mutations?.disconnect();
|
|
153
255
|
clearTimeout(settleTimer);
|
|
154
256
|
};
|
|
155
|
-
}, [host, hostId, trialId, frames, copy, overridesRef]);
|
|
257
|
+
}, [host, hostId, trialId, frames, copy, overridesRef, rendered]);
|
|
258
|
+
useStoryHost(trialId, copy ? null : host, show);
|
|
156
259
|
const announced = useRef(false);
|
|
157
260
|
useEffect(() => {
|
|
158
|
-
if (!host || !
|
|
159
|
-
|
|
160
|
-
const done = () => {
|
|
161
|
-
if (canceled) return;
|
|
261
|
+
if (!host || !live || announced.current) return;
|
|
262
|
+
return afterPaint(host, () => {
|
|
162
263
|
announced.current = true;
|
|
264
|
+
rendered.resolve();
|
|
163
265
|
setPending(false);
|
|
164
266
|
latest.current.onRendered?.();
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
if (!fonts) {
|
|
168
|
-
done();
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
const afterPaint = () => requestAnimationFrame(done);
|
|
172
|
-
void fonts.ready.then(afterPaint, afterPaint);
|
|
173
|
-
return () => {
|
|
174
|
-
canceled = true;
|
|
175
|
-
};
|
|
176
|
-
}, [host, inView, latest]);
|
|
267
|
+
});
|
|
268
|
+
}, [host, live, latest, rendered]);
|
|
177
269
|
return /* @__PURE__ */ jsx(
|
|
178
270
|
"div",
|
|
179
271
|
{
|
|
@@ -187,54 +279,10 @@ function TrialHost({ layout, setup, config, children, hostRef, onRendered, copy
|
|
|
187
279
|
"data-fg-copy": copy ? "" : void 0,
|
|
188
280
|
"data-fg-layout": layout,
|
|
189
281
|
"data-pending": pending || void 0,
|
|
190
|
-
children: /* @__PURE__ */ jsx(StoryScroller, { children: host &&
|
|
282
|
+
children: /* @__PURE__ */ jsx(StoryScroller, { children: host && live ? /* @__PURE__ */ jsx(StoryContent, { host, children: children(globals) }) : null })
|
|
191
283
|
}
|
|
192
284
|
);
|
|
193
285
|
}
|
|
194
|
-
function createTrialClocks() {
|
|
195
|
-
const clocks = /* @__PURE__ */ new Map();
|
|
196
|
-
const changed = createEmitter();
|
|
197
|
-
const notify = changed.emit;
|
|
198
|
-
return {
|
|
199
|
-
publish(trialId, clock) {
|
|
200
|
-
clocks.set(trialId, clock);
|
|
201
|
-
notify();
|
|
202
|
-
return () => {
|
|
203
|
-
if (clocks.get(trialId) !== clock) return;
|
|
204
|
-
clocks.delete(trialId);
|
|
205
|
-
notify();
|
|
206
|
-
};
|
|
207
|
-
},
|
|
208
|
-
get: (trialId) => clocks.get(trialId) ?? null,
|
|
209
|
-
subscribe: changed.subscribe
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
var TrialClocksContext = createContext(null);
|
|
213
|
-
var NEVER = () => () => {
|
|
214
|
-
};
|
|
215
|
-
var NONE2 = () => null;
|
|
216
|
-
function useTrialClock(trialId) {
|
|
217
|
-
const clocks = useContext(TrialClocksContext);
|
|
218
|
-
return useSyncExternalStore(clocks?.subscribe ?? NEVER, clocks ? () => clocks.get(trialId) : NONE2);
|
|
219
|
-
}
|
|
220
|
-
function usePersistedPlayhead(clock, persisted, write) {
|
|
221
|
-
const written = useRef(persisted);
|
|
222
|
-
const latestWrite = useLatest(write);
|
|
223
|
-
useEffect(() => {
|
|
224
|
-
if (!clock || persisted === written.current) return;
|
|
225
|
-
written.current = persisted;
|
|
226
|
-
clock.request(persisted);
|
|
227
|
-
}, [clock, persisted]);
|
|
228
|
-
useEffect(() => {
|
|
229
|
-
if (!clock) return;
|
|
230
|
-
return clock.subscribe(() => {
|
|
231
|
-
const { playing, time } = clock.get();
|
|
232
|
-
if (playing || clock.pending() !== null || time === written.current) return;
|
|
233
|
-
written.current = time;
|
|
234
|
-
latestWrite.current(time);
|
|
235
|
-
});
|
|
236
|
-
}, [clock, latestWrite]);
|
|
237
|
-
}
|
|
238
286
|
var StoryBody = memo(function StoryBody2({ story, config, setConfig, state, setState, globals, decorators, resetKey, onError }) {
|
|
239
287
|
const storyCtx = { config, setConfig, state, setState, globals, title: story.title, name: story.name };
|
|
240
288
|
return /* @__PURE__ */ jsx(StoryHost, { story, ctx: storyCtx, decorators, resetKey, onError });
|
|
@@ -288,7 +336,7 @@ function resolveContextOf(lab) {
|
|
|
288
336
|
const prefersDark = globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
|
|
289
337
|
return { declarations: lab.declarations, modeGlobal: lab.modeGlobal, prefersDark, labMode: lab.mode };
|
|
290
338
|
}
|
|
291
|
-
var
|
|
339
|
+
var NONE2 = [];
|
|
292
340
|
function createLiveLenses() {
|
|
293
341
|
const requests = /* @__PURE__ */ new Map();
|
|
294
342
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -305,7 +353,7 @@ function createLiveLenses() {
|
|
|
305
353
|
let found = byTrial.get(trialId);
|
|
306
354
|
if (!found) {
|
|
307
355
|
const mine = [...requests.values()].filter((r) => r.hit.trialId === trialId);
|
|
308
|
-
found = mine.length === 0 ?
|
|
356
|
+
found = mine.length === 0 ? NONE2 : mine;
|
|
309
357
|
byTrial.set(trialId, found);
|
|
310
358
|
}
|
|
311
359
|
return found;
|
|
@@ -421,8 +469,9 @@ function LiveCopies(props) {
|
|
|
421
469
|
function FrameLensCopy({ request, trialId, trialConfig, children }) {
|
|
422
470
|
const globals = useCopyGlobals(request, trialConfig);
|
|
423
471
|
const id = copyId(trialId, request);
|
|
424
|
-
const { args } = request;
|
|
425
|
-
const
|
|
472
|
+
const { args, factor } = request;
|
|
473
|
+
const main = request.hit.host;
|
|
474
|
+
const copy = useMemo(() => ({ id, main, globals, args, factor }), [id, main, globals, args, factor]);
|
|
426
475
|
return /* @__PURE__ */ jsx(LensPortal, { request, children: children(copy) });
|
|
427
476
|
}
|
|
428
477
|
function FrameCopies({ lenses, trialId, ...props }) {
|
|
@@ -457,15 +506,11 @@ function StoryTrial({ story, setup, ctx, hostRef, onRendered, onError }) {
|
|
|
457
506
|
}, [latestRef]);
|
|
458
507
|
const setState = useCallback((next) => latestRef.current.setState(next), [latestRef]);
|
|
459
508
|
const trialId = useContext(TrialIdContext);
|
|
460
|
-
const clocks = useContext(TrialClocksContext);
|
|
461
509
|
const ownHost = useRef(null);
|
|
462
510
|
const host = hostRef ?? ownHost;
|
|
463
|
-
const
|
|
464
|
-
const
|
|
465
|
-
const clock =
|
|
466
|
-
useClockLoop(clock, host);
|
|
467
|
-
usePersistedPlayhead(clock, persisted, (time) => latestRef.current.setConfig(PLAYHEAD_KEY, time));
|
|
468
|
-
useEffect(() => clock && clocks && trialId ? clocks.publish(trialId, clock) : void 0, [clock, clocks, trialId]);
|
|
511
|
+
const spec = story.timeline?.(config) ?? null;
|
|
512
|
+
const trialClock = useTrialClockHandle();
|
|
513
|
+
const clock = spec ? trialClock : null;
|
|
469
514
|
const decorators = setup.decorators ?? NO_DECORATORS;
|
|
470
515
|
const latestError = useLatest(onError);
|
|
471
516
|
const reportError = useCallback((error) => latestError.current?.(error), [latestError]);
|
|
@@ -496,6 +541,6 @@ function StoryTrial({ story, setup, ctx, hostRef, onRendered, onError }) {
|
|
|
496
541
|
] });
|
|
497
542
|
}
|
|
498
543
|
|
|
499
|
-
export { LiveCopies, LiveFrameCopies, LiveLensesContext, StoryGlobalsContext,
|
|
500
|
-
//# sourceMappingURL=chunk-
|
|
501
|
-
//# sourceMappingURL=chunk-
|
|
544
|
+
export { IN_VIEW_MARGIN, LiveCopies, LiveFrameCopies, LiveLensesContext, StoryGlobalsContext, StoryHostsContext, StoryTrial, TrialFramesContext, TrialHost, createLiveLenses, createStoryHosts, createTrialFrames, describeLens, resolveComparison, resolveContextOf, scrollerOf, trialAt, useCopyConfig, useCssOverrides, useTrialFrame, withArgs };
|
|
545
|
+
//# sourceMappingURL=chunk-UDEUT7AG.js.map
|
|
546
|
+
//# sourceMappingURL=chunk-UDEUT7AG.js.map
|