@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.
Files changed (57) hide show
  1. package/README.md +1 -1
  2. package/dist/{FrameController-Tv3jrHyT.d.ts → FrameController-BM6xuEfY.d.ts} +18 -10
  3. package/dist/{chunk-KEQBGDUZ.js → chunk-B26DWEYT.js} +3 -3
  4. package/dist/{chunk-KEQBGDUZ.js.map → chunk-B26DWEYT.js.map} +1 -1
  5. package/dist/{chunk-3BAM6M26.js → chunk-FPF2JSWB.js} +3 -3
  6. package/dist/chunk-FPF2JSWB.js.map +1 -0
  7. package/dist/{chunk-ORMMZ5PV.js → chunk-MRYXJTDO.js} +7 -3
  8. package/dist/chunk-MRYXJTDO.js.map +1 -0
  9. package/dist/{chunk-35DOPXHZ.js → chunk-PGLOYXDP.js} +193 -190
  10. package/dist/chunk-PGLOYXDP.js.map +1 -0
  11. package/dist/{chunk-BUXUWAI4.js → chunk-PPB7VP6R.js} +62 -8
  12. package/dist/chunk-PPB7VP6R.js.map +1 -0
  13. package/dist/{chunk-STF6NQ5N.js → chunk-RVJHUY7W.js} +2 -2
  14. package/dist/chunk-RVJHUY7W.js.map +1 -0
  15. package/dist/{chunk-LTTEILXY.js → chunk-SFKDHXVE.js} +26 -4
  16. package/dist/chunk-SFKDHXVE.js.map +1 -0
  17. package/dist/chunk-STK2AZH2.js +11 -0
  18. package/dist/chunk-STK2AZH2.js.map +1 -0
  19. package/dist/chunk-TT4ZOYB6.js +143 -0
  20. package/dist/chunk-TT4ZOYB6.js.map +1 -0
  21. package/dist/{chunk-RKQ277P5.js → chunk-UDEUT7AG.js} +125 -80
  22. package/dist/chunk-UDEUT7AG.js.map +1 -0
  23. package/dist/{config-Bd6FVlh7.d.ts → config-DOjWyIui.d.ts} +3 -21
  24. package/dist/csf/preview-api.js +2 -2
  25. package/dist/frame/index.css +0 -2
  26. package/dist/frame/index.css.map +1 -1
  27. package/dist/frame/index.d.ts +14 -8
  28. package/dist/frame/index.js +114 -79
  29. package/dist/frame/index.js.map +1 -1
  30. package/dist/index.d.ts +12 -5
  31. package/dist/index.js +4 -4
  32. package/dist/library-CdTj-hFp.d.ts +21 -0
  33. package/dist/{mountFrame-FdJDb3TB.d.ts → mountFrame-CRPAjg1M.d.ts} +1 -1
  34. package/dist/shell/index.css +166 -150
  35. package/dist/shell/index.css.map +1 -1
  36. package/dist/shell/index.d.ts +24 -5
  37. package/dist/shell/index.js +594 -402
  38. package/dist/shell/index.js.map +1 -1
  39. package/dist/test/index.css +2 -3
  40. package/dist/test/index.css.map +1 -1
  41. package/dist/test/index.d.ts +1 -1
  42. package/dist/test/index.js +8 -10
  43. package/dist/test/index.js.map +1 -1
  44. package/dist/vite/index.js +127 -32
  45. package/dist/vite/index.js.map +1 -1
  46. package/package.json +11 -9
  47. package/dist/chunk-35DOPXHZ.js.map +0 -1
  48. package/dist/chunk-3BAM6M26.js.map +0 -1
  49. package/dist/chunk-BUXUWAI4.js.map +0 -1
  50. package/dist/chunk-GH5RE5X5.js +0 -10
  51. package/dist/chunk-GH5RE5X5.js.map +0 -1
  52. package/dist/chunk-L5XM5VKB.js +0 -47
  53. package/dist/chunk-L5XM5VKB.js.map +0 -1
  54. package/dist/chunk-LTTEILXY.js.map +0 -1
  55. package/dist/chunk-ORMMZ5PV.js.map +0 -1
  56. package/dist/chunk-RKQ277P5.js.map +0 -1
  57. 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, PLAYHEAD_KEY, FOLLOW_APP } from './chunk-3BAM6M26.js';
2
- import { StoryHost, useNearViewport, IN_VIEW_MARGIN, useHostConditions, createA11yHighlighter, useStoryClock, useClockLoop, scanCssVars, auditElement } from './chunk-35DOPXHZ.js';
3
- import { ClockContext } from './chunk-L5XM5VKB.js';
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 || !inView || announced.current) return;
159
- let canceled = false;
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
- const { fonts } = host.ownerDocument;
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 && inView ? /* @__PURE__ */ jsx(StoryContent, { host, children: children(globals) }) : null })
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 NONE3 = [];
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 ? NONE3 : mine;
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 copy = useMemo(() => ({ id, globals, args }), [id, globals, args]);
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 kept = ctx.config?.[PLAYHEAD_KEY];
464
- const persisted = typeof kept === "number" && Number.isFinite(kept) ? kept : null;
465
- const clock = useStoryClock(story.timeline?.(config) ?? null, () => persisted);
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, StoryTrial, TrialClocksContext, TrialFramesContext, TrialHost, createLiveLenses, createTrialClocks, createTrialFrames, describeLens, resolveComparison, resolveContextOf, scrollerOf, trialAt, useCopyConfig, useCssOverrides, useTrialClock, useTrialFrame, withArgs };
500
- //# sourceMappingURL=chunk-RKQ277P5.js.map
501
- //# sourceMappingURL=chunk-RKQ277P5.js.map
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