@bitcobblers/wod-wiki-lang 0.1.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/dist/react.mjs ADDED
@@ -0,0 +1,741 @@
1
+ import { wallClockNow, formatDurationSmart, getRuntimeNowMs } from './chunk-F2TOS4AF.mjs';
2
+ export { createBrowserRuntimeNow, getRuntimeNowMs, wallClockNow as runtimeNow } from './chunk-F2TOS4AF.mjs';
3
+ import { createContext, useState, useRef, useCallback, useEffect, useMemo, useContext } from 'react';
4
+ import { MetricContainer } from '@bitcobblers/wod-wiki-core';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ // src/runtime/events/TickEvent.ts
8
+ var TickEvent = class _TickEvent {
9
+ constructor(data, now = wallClockNow) {
10
+ this.now = now;
11
+ const nowMs = this.now.nowMs();
12
+ _TickEvent._counter++;
13
+ this.timestamp = new Date(nowMs + _TickEvent._counter);
14
+ this.data = data;
15
+ }
16
+ now;
17
+ name = "tick";
18
+ timestamp;
19
+ data;
20
+ static _counter = 0;
21
+ };
22
+
23
+ // src/react/hooks/useRuntimeExecution.ts
24
+ var EXECUTION_TICK_RATE_MS = 20;
25
+ var useRuntimeExecution = (runtime) => {
26
+ const [status, setStatus] = useState("idle");
27
+ const [elapsedTime, setElapsedTime] = useState(0);
28
+ const [stepCount, setStepCount] = useState(0);
29
+ const [startTime, setStartTime] = useState(null);
30
+ const intervalRef = useRef(null);
31
+ const startTimeRef = useRef(null);
32
+ const executeStep = useCallback(() => {
33
+ if (!runtime) return;
34
+ try {
35
+ const tickEvent = new TickEvent(void 0, runtime.nowProvider);
36
+ runtime.handle(tickEvent);
37
+ setStepCount((prev) => prev + 1);
38
+ } catch (error) {
39
+ stop();
40
+ setStatus("error");
41
+ console.error("Runtime execution error:", error);
42
+ }
43
+ }, [runtime]);
44
+ useEffect(() => {
45
+ if (!runtime) return;
46
+ const unsubscribe = runtime.subscribeToStack((snapshot) => {
47
+ if (snapshot.blocks.length === 0 && (status === "running" || status === "paused")) {
48
+ if (intervalRef.current) {
49
+ clearInterval(intervalRef.current);
50
+ intervalRef.current = null;
51
+ }
52
+ setStatus("completed");
53
+ }
54
+ });
55
+ return unsubscribe;
56
+ }, [runtime, status]);
57
+ const start = useCallback(() => {
58
+ if (!runtime) {
59
+ console.warn("Cannot start execution: runtime is null");
60
+ return;
61
+ }
62
+ if (status === "running") {
63
+ console.warn("Cannot start execution: already running");
64
+ return;
65
+ }
66
+ if (status === "paused") {
67
+ runtime.handle({
68
+ name: "timer:resume",
69
+ timestamp: /* @__PURE__ */ new Date(),
70
+ data: {}
71
+ });
72
+ }
73
+ setStatus("running");
74
+ const now = runtime?.nowProvider?.nowMs() ?? wallClockNow.nowMs();
75
+ startTimeRef.current = now - elapsedTime;
76
+ if (elapsedTime === 0) {
77
+ setStartTime(now);
78
+ }
79
+ intervalRef.current = setInterval(executeStep, EXECUTION_TICK_RATE_MS);
80
+ }, [runtime, status, elapsedTime, executeStep]);
81
+ const pause = useCallback(() => {
82
+ if (intervalRef.current) {
83
+ clearInterval(intervalRef.current);
84
+ intervalRef.current = null;
85
+ }
86
+ if (runtime) {
87
+ runtime.handle({
88
+ name: "timer:pause",
89
+ timestamp: /* @__PURE__ */ new Date(),
90
+ data: {}
91
+ });
92
+ }
93
+ setStatus("paused");
94
+ }, [runtime]);
95
+ const stop = useCallback(() => {
96
+ if (intervalRef.current) {
97
+ clearInterval(intervalRef.current);
98
+ intervalRef.current = null;
99
+ }
100
+ setStatus("idle");
101
+ }, []);
102
+ const reset = useCallback(() => {
103
+ if (intervalRef.current) {
104
+ clearInterval(intervalRef.current);
105
+ intervalRef.current = null;
106
+ }
107
+ setElapsedTime(0);
108
+ setStepCount(0);
109
+ setStatus("idle");
110
+ setStartTime(null);
111
+ startTimeRef.current = null;
112
+ }, [runtime]);
113
+ const step = useCallback(() => {
114
+ if (!runtime) {
115
+ console.warn("Cannot step: runtime is null");
116
+ return;
117
+ }
118
+ if (status === "running") {
119
+ console.warn("Cannot step while running");
120
+ return;
121
+ }
122
+ setStatus("paused");
123
+ executeStep();
124
+ }, [runtime, status, executeStep]);
125
+ useEffect(() => {
126
+ if (status !== "running" || !startTimeRef.current) return;
127
+ const updateInterval = setInterval(() => {
128
+ setElapsedTime((runtime?.nowProvider?.nowMs() ?? wallClockNow.nowMs()) - startTimeRef.current);
129
+ }, 100);
130
+ return () => clearInterval(updateInterval);
131
+ }, [status]);
132
+ useEffect(() => {
133
+ return () => {
134
+ if (intervalRef.current) {
135
+ clearInterval(intervalRef.current);
136
+ }
137
+ };
138
+ }, []);
139
+ useEffect(() => {
140
+ reset();
141
+ }, [runtime, reset]);
142
+ return useMemo(() => ({
143
+ status,
144
+ elapsedTime,
145
+ stepCount,
146
+ startTime,
147
+ start,
148
+ pause,
149
+ stop,
150
+ reset,
151
+ step
152
+ }), [status, elapsedTime, stepCount, startTime, start, pause, stop, reset, step]);
153
+ };
154
+
155
+ // src/utils/timeUtils.ts
156
+ var calculateDuration = (spans, now) => {
157
+ if (!spans || !Array.isArray(spans)) return 0;
158
+ return spans.reduce((total, span) => {
159
+ if ("started" in span) {
160
+ const start2 = span.started;
161
+ const end = span.ended ?? now;
162
+ return total + Math.max(0, end - start2);
163
+ }
164
+ const start = span.start instanceof Date ? span.start.getTime() : span.start || 0;
165
+ const stop = span.stop ? span.stop instanceof Date ? span.stop.getTime() : span.stop : now;
166
+ return total + Math.max(0, stop - start);
167
+ }, 0);
168
+ };
169
+
170
+ // src/react/hooks/useBlockMemory.ts
171
+ function useBlockMemory(block, type) {
172
+ const tag = type;
173
+ const getVal = () => {
174
+ const loc = block?.getMemoryByTag(tag)[0];
175
+ if (!loc) return void 0;
176
+ if (tag === "round") return loc.metrics[0];
177
+ return loc.metrics[0]?.value;
178
+ };
179
+ const [value, setValue] = useState(getVal);
180
+ useEffect(() => {
181
+ if (!block) {
182
+ setValue(void 0);
183
+ return;
184
+ }
185
+ setValue(getVal());
186
+ const loc = block.getMemoryByTag(tag)[0];
187
+ if (loc) {
188
+ const unsubscribe = loc.subscribe((newFrags) => {
189
+ if (tag === "round") {
190
+ setValue(newFrags[0]);
191
+ } else {
192
+ setValue(newFrags[0]?.value);
193
+ }
194
+ });
195
+ return unsubscribe;
196
+ }
197
+ return void 0;
198
+ }, [block, type]);
199
+ return value;
200
+ }
201
+ function useTimerState(block) {
202
+ return useBlockMemory(block, "time");
203
+ }
204
+ function useRoundState(block) {
205
+ return useBlockMemory(block, "round");
206
+ }
207
+ function useDisplayState(block) {
208
+ return useBlockMemory(block, "display");
209
+ }
210
+ function useTimerDisplay(block) {
211
+ const timer = useTimerState(block);
212
+ const getNow = () => {
213
+ if (typeof window.__chromecast_senderClockTimeMs === "function") {
214
+ return window.__chromecast_senderClockTimeMs();
215
+ }
216
+ return Date.now();
217
+ };
218
+ const [now, setNow] = useState(getNow());
219
+ const isRunning = useMemo(() => {
220
+ if (!timer || timer.spans.length === 0) return false;
221
+ const lastSpan = timer.spans[timer.spans.length - 1];
222
+ return lastSpan.ended === void 0;
223
+ }, [timer]);
224
+ useEffect(() => {
225
+ if (!isRunning) return;
226
+ let animationFrameId;
227
+ let running = true;
228
+ const tick = () => {
229
+ if (running) {
230
+ setNow(getNow());
231
+ animationFrameId = requestAnimationFrame(tick);
232
+ }
233
+ };
234
+ animationFrameId = requestAnimationFrame(tick);
235
+ return () => {
236
+ running = false;
237
+ cancelAnimationFrame(animationFrameId);
238
+ };
239
+ }, [isRunning]);
240
+ return useMemo(() => {
241
+ if (!timer) return null;
242
+ const elapsed = calculateDuration(timer.spans, now);
243
+ const remaining = timer.durationMs !== void 0 ? Math.max(0, timer.durationMs - elapsed) : void 0;
244
+ const isComplete = timer.durationMs !== void 0 && elapsed >= timer.durationMs;
245
+ const displayMs = timer.direction === "down" && remaining !== void 0 ? remaining : elapsed;
246
+ return {
247
+ elapsed,
248
+ remaining,
249
+ isRunning,
250
+ direction: timer.direction,
251
+ isComplete,
252
+ formatted: formatDurationSmart(displayMs)
253
+ };
254
+ }, [timer, now, isRunning]);
255
+ }
256
+ function useRoundDisplay(block) {
257
+ const round = useRoundState(block);
258
+ const display = useDisplayState(block);
259
+ return useMemo(() => {
260
+ if (!round) return null;
261
+ const label = display?.roundDisplay ?? (round.total !== void 0 ? `Round ${round.current} of ${round.total}` : `Round ${round.current}`);
262
+ const progress = round.total !== void 0 ? Math.min(1, (round.current - 1) / round.total) : void 0;
263
+ return {
264
+ current: round.current,
265
+ total: round.total,
266
+ label,
267
+ progress
268
+ };
269
+ }, [round, display?.roundDisplay]);
270
+ }
271
+ function useFragmentSource(block) {
272
+ const [version, setVersion] = useState(0);
273
+ useEffect(() => {
274
+ if (!block) return;
275
+ const loc = block.getMemoryByTag("metric:display")[0];
276
+ if (!loc) return;
277
+ return loc.subscribe(() => setVersion((v) => v + 1));
278
+ }, [block]);
279
+ return useMemo(() => {
280
+ if (!block) return void 0;
281
+ const locs = block.getMemoryByTag("metric:display");
282
+ if (locs.length === 0) return void 0;
283
+ const allFragments = MetricContainer.empty(block.key.toString());
284
+ for (const loc of locs) allFragments.merge(loc.metrics);
285
+ return allFragments;
286
+ }, [block, version]);
287
+ }
288
+ var ScriptRuntimeContext = createContext(void 0);
289
+ var ScriptRuntimeProvider = ({
290
+ runtime,
291
+ children
292
+ }) => {
293
+ return /* @__PURE__ */ jsx(ScriptRuntimeContext.Provider, { value: runtime, children });
294
+ };
295
+ function useScriptRuntime() {
296
+ const runtime = useContext(ScriptRuntimeContext);
297
+ if (!runtime) {
298
+ throw new Error("useScriptRuntime must be used within ScriptRuntimeProvider");
299
+ }
300
+ return runtime;
301
+ }
302
+
303
+ // src/react/hooks/useOutputStatements.ts
304
+ function useOutputStatements(runtime) {
305
+ const contextRuntime = useScriptRuntime();
306
+ const resolvedRuntime = runtime !== void 0 ? runtime : contextRuntime;
307
+ const [outputs, setOutputs] = useState(() => {
308
+ return resolvedRuntime?.getOutputStatements?.() ?? [];
309
+ });
310
+ useEffect(() => {
311
+ if (!resolvedRuntime) {
312
+ setOutputs([]);
313
+ return;
314
+ }
315
+ setOutputs(resolvedRuntime.getOutputStatements());
316
+ const unsubscribe = resolvedRuntime.subscribeToOutput?.(() => {
317
+ setOutputs(resolvedRuntime.getOutputStatements());
318
+ });
319
+ return unsubscribe;
320
+ }, [resolvedRuntime]);
321
+ const data = useMemo(() => {
322
+ const byId = /* @__PURE__ */ new Map();
323
+ const byBlockKey = /* @__PURE__ */ new Map();
324
+ const segments = [];
325
+ const metrics = [];
326
+ for (const output of outputs) {
327
+ byId.set(output.id, output);
328
+ const blockOutputs = byBlockKey.get(output.sourceBlockKey) ?? [];
329
+ blockOutputs.push(output);
330
+ byBlockKey.set(output.sourceBlockKey, blockOutputs);
331
+ switch (output.outputType) {
332
+ case "segment":
333
+ segments.push(output);
334
+ break;
335
+ case "metric":
336
+ metrics.push(output);
337
+ break;
338
+ }
339
+ }
340
+ const completions = segments.filter((s) => s.completionReason !== void 0);
341
+ return { outputs, byId, byBlockKey, segments, completions, metrics };
342
+ }, [outputs]);
343
+ return data;
344
+ }
345
+ function useLiveAnalytics(runtime) {
346
+ const contextRuntime = useScriptRuntime();
347
+ const resolvedRuntime = runtime !== void 0 ? runtime : contextRuntime;
348
+ const [analytics, setAnalytics] = useState(
349
+ () => resolvedRuntime?.getLiveAnalytics?.() ?? []
350
+ );
351
+ useEffect(() => {
352
+ if (!resolvedRuntime) {
353
+ setAnalytics([]);
354
+ return;
355
+ }
356
+ setAnalytics(resolvedRuntime.getLiveAnalytics?.() ?? []);
357
+ const unsubscribe = resolvedRuntime.subscribeToOutput?.(() => {
358
+ setAnalytics(resolvedRuntime.getLiveAnalytics?.() ?? []);
359
+ });
360
+ return unsubscribe;
361
+ }, [resolvedRuntime]);
362
+ return analytics;
363
+ }
364
+ function useOutputStatement(runtime, id) {
365
+ const { byId } = useOutputStatements(runtime);
366
+ if (id === null) return void 0;
367
+ return byId.get(id);
368
+ }
369
+ function useBlockOutputs(runtime, blockKey) {
370
+ const { byBlockKey } = useOutputStatements(runtime);
371
+ if (!blockKey) return [];
372
+ return byBlockKey.get(blockKey) ?? [];
373
+ }
374
+ function useOutputHierarchy(runtime) {
375
+ const { outputs } = useOutputStatements(runtime);
376
+ return useMemo(() => {
377
+ const depthMap = /* @__PURE__ */ new Map();
378
+ for (const output of outputs) {
379
+ depthMap.set(output.id, output.stackLevel);
380
+ }
381
+ return depthMap;
382
+ }, [outputs]);
383
+ }
384
+ function useMemorySubscription(memoryRef) {
385
+ const [value, setValue] = useState(memoryRef?.get());
386
+ useEffect(() => {
387
+ if (!memoryRef) {
388
+ setValue(void 0);
389
+ return;
390
+ }
391
+ setValue(memoryRef.get());
392
+ const unsubscribe = memoryRef.subscribe((newValue) => {
393
+ setValue(newValue);
394
+ });
395
+ return unsubscribe;
396
+ }, [memoryRef]);
397
+ return value;
398
+ }
399
+ function useStackSnapshot() {
400
+ const runtime = useScriptRuntime();
401
+ const [snapshot, setSnapshot] = useState({
402
+ type: "initial",
403
+ blocks: runtime.stack.blocks,
404
+ depth: runtime.stack.count,
405
+ clockTime: runtime.clock.currentDate
406
+ });
407
+ useEffect(() => {
408
+ const unsubscribe = runtime.subscribeToStack((s) => {
409
+ setSnapshot(s);
410
+ });
411
+ return unsubscribe;
412
+ }, [runtime]);
413
+ return snapshot;
414
+ }
415
+ function useSnapshotBlocks() {
416
+ const snapshot = useStackSnapshot();
417
+ return snapshot.blocks;
418
+ }
419
+ function useSnapshotCurrentBlock() {
420
+ const snapshot = useStackSnapshot();
421
+ return snapshot.blocks.length > 0 ? snapshot.blocks[snapshot.blocks.length - 1] : void 0;
422
+ }
423
+ function useNextPreview() {
424
+ const blocks = useSnapshotBlocks();
425
+ const [version, setVersion] = useState(0);
426
+ useEffect(() => {
427
+ const unsubscribes = [];
428
+ for (const block of blocks) {
429
+ const nextLocs = block.getMemoryByTag("metric:next");
430
+ for (const loc of nextLocs) {
431
+ const unsub = loc.subscribe(() => {
432
+ setVersion((v) => v + 1);
433
+ });
434
+ unsubscribes.push(unsub);
435
+ }
436
+ }
437
+ return () => {
438
+ unsubscribes.forEach((fn) => fn());
439
+ };
440
+ }, [blocks]);
441
+ return useMemo(() => {
442
+ for (const block of blocks) {
443
+ const nextLocs = block.getMemoryByTag("metric:next");
444
+ if (nextLocs.length === 0) continue;
445
+ const metrics = MetricContainer.empty(block.key.toString());
446
+ for (const loc of nextLocs) metrics.merge(loc.metrics);
447
+ if (metrics.length > 0) {
448
+ return { metrics, block };
449
+ }
450
+ }
451
+ return null;
452
+ }, [blocks, version]);
453
+ }
454
+ function useStackTimers() {
455
+ const blocks = useSnapshotBlocks();
456
+ const [version, setVersion] = useState(0);
457
+ useEffect(() => {
458
+ const unsubscribes = [];
459
+ for (const block of blocks) {
460
+ const timerLoc = block.getMemoryByTag("time")[0];
461
+ if (timerLoc) {
462
+ const unsub = timerLoc.subscribe(() => {
463
+ setVersion((v) => v + 1);
464
+ });
465
+ unsubscribes.push(unsub);
466
+ }
467
+ }
468
+ return () => {
469
+ unsubscribes.forEach((fn) => fn());
470
+ };
471
+ }, [blocks]);
472
+ return useMemo(() => {
473
+ const entries = [];
474
+ for (const block of blocks) {
475
+ const timerLoc = block.getMemoryByTag("time")[0];
476
+ if (!timerLoc) continue;
477
+ const timer = timerLoc.metrics[0]?.value;
478
+ if (!timer) continue;
479
+ entries.push({
480
+ block,
481
+ timer,
482
+ isPinned: timer.role === "primary"
483
+ });
484
+ }
485
+ return entries;
486
+ }, [blocks, version]);
487
+ }
488
+ function usePrimaryTimer() {
489
+ const timers = useStackTimers();
490
+ return useMemo(() => {
491
+ if (timers.length === 0) return void 0;
492
+ const leaf = timers[0];
493
+ if (leaf.block.blockType === "Rest") return leaf;
494
+ const pinned = [...timers].reverse().find((t) => t.isPinned);
495
+ if (pinned) return pinned;
496
+ return leaf;
497
+ }, [timers]);
498
+ }
499
+ function useSecondaryTimers() {
500
+ const timers = useStackTimers();
501
+ const primary = usePrimaryTimer();
502
+ return useMemo(() => {
503
+ if (!primary) return [];
504
+ return timers.filter((t) => t.block !== primary.block);
505
+ }, [timers, primary]);
506
+ }
507
+ function useActiveControls() {
508
+ const blocks = useSnapshotBlocks();
509
+ const [version, setVersion] = useState(0);
510
+ useEffect(() => {
511
+ const unsubscribes = [];
512
+ for (const block of blocks) {
513
+ const controlsLocs = block.getMemoryByTag("controls");
514
+ for (const loc of controlsLocs) {
515
+ const unsub = loc.subscribe(() => {
516
+ setVersion((v) => v + 1);
517
+ });
518
+ unsubscribes.push(unsub);
519
+ }
520
+ }
521
+ return () => {
522
+ unsubscribes.forEach((fn) => fn());
523
+ };
524
+ }, [blocks]);
525
+ return useMemo(() => {
526
+ const pinned = [];
527
+ const topButtons = [];
528
+ for (let i = 0; i < blocks.length; i++) {
529
+ const controlsLocs = blocks[i].getMemoryByTag("controls");
530
+ if (controlsLocs.length === 0) continue;
531
+ const buttons = controlsLocs[0].metrics.map((f) => f.value).filter(Boolean);
532
+ const isTop = i === blocks.length - 1;
533
+ for (const btn of buttons) {
534
+ if (btn.isPinned) {
535
+ pinned.push(btn);
536
+ } else if (isTop) {
537
+ topButtons.push(btn);
538
+ }
539
+ }
540
+ }
541
+ const seen = /* @__PURE__ */ new Set();
542
+ const result = [];
543
+ for (const btn of pinned) {
544
+ if (!seen.has(btn.id)) {
545
+ seen.add(btn.id);
546
+ result.push(btn);
547
+ }
548
+ }
549
+ for (const btn of topButtons) {
550
+ if (!seen.has(btn.id)) {
551
+ seen.add(btn.id);
552
+ result.push(btn);
553
+ }
554
+ }
555
+ return result;
556
+ }, [blocks, version]);
557
+ }
558
+ function useStackFragmentSources() {
559
+ const blocks = useSnapshotBlocks();
560
+ const [version, setVersion] = useState(0);
561
+ useEffect(() => {
562
+ const unsubscribes = [];
563
+ for (const block of blocks) {
564
+ const displayLocs = block.getMemoryByTag("metric:display");
565
+ for (const loc of displayLocs) {
566
+ const unsub = loc.subscribe(() => {
567
+ setVersion((v) => v + 1);
568
+ });
569
+ unsubscribes.push(unsub);
570
+ }
571
+ }
572
+ return () => {
573
+ unsubscribes.forEach((fn) => fn());
574
+ };
575
+ }, [blocks]);
576
+ return useMemo(() => {
577
+ if (blocks.length === 0) return void 0;
578
+ const entries = [];
579
+ const orderedBlocks = [...blocks].reverse();
580
+ orderedBlocks.forEach((block, index) => {
581
+ if (block.blockType === "Root" && !block.label) return;
582
+ const displayLocs = block.getMemoryByTag("metric:display");
583
+ let source;
584
+ if (displayLocs.length === 0) {
585
+ if (!block.label) return;
586
+ source = MetricContainer.empty(block.key.toString());
587
+ } else {
588
+ const mergedMetrics = MetricContainer.empty(block.key.toString());
589
+ for (const loc of displayLocs) mergedMetrics.merge(loc.metrics);
590
+ source = mergedMetrics;
591
+ }
592
+ const isLeaf = index === orderedBlocks.length - 1;
593
+ const metricGroups = displayLocs.length > 1 ? displayLocs.map((loc) => loc.metrics) : void 0;
594
+ entries.push({
595
+ source,
596
+ block,
597
+ depth: index,
598
+ isLeaf,
599
+ label: block.label,
600
+ metricGroups
601
+ });
602
+ });
603
+ return entries.length > 0 ? entries : void 0;
604
+ }, [blocks, version]);
605
+ }
606
+ function useStackDisplayRows() {
607
+ const blocks = useSnapshotBlocks();
608
+ const [version, setVersion] = useState(0);
609
+ useEffect(() => {
610
+ const unsubscribes = [];
611
+ for (const block of blocks) {
612
+ const displayLocs = block.getMemoryByTag("metric:display");
613
+ for (const loc of displayLocs) {
614
+ const unsub = loc.subscribe(() => {
615
+ setVersion((v) => v + 1);
616
+ });
617
+ unsubscribes.push(unsub);
618
+ }
619
+ }
620
+ return () => {
621
+ unsubscribes.forEach((fn) => fn());
622
+ };
623
+ }, [blocks]);
624
+ return useMemo(() => {
625
+ if (blocks.length === 0) return void 0;
626
+ const entries = [];
627
+ const orderedBlocks = [...blocks].reverse();
628
+ orderedBlocks.forEach((block, index) => {
629
+ if (block.blockType === "Root" && !block.label) return;
630
+ const displayLocs = block.getMemoryByTag("metric:display");
631
+ const displayRows = displayLocs.map((loc) => loc.metrics);
632
+ if (displayRows.length === 0 && !block.label) return;
633
+ const isLeaf = index === orderedBlocks.length - 1;
634
+ const metricGroups = displayLocs.length > 1 ? displayLocs.map((loc) => loc.metrics) : void 0;
635
+ entries.push({
636
+ block,
637
+ displayRows,
638
+ label: block.label,
639
+ depth: index,
640
+ isLeaf,
641
+ metricGroups
642
+ });
643
+ });
644
+ return entries.length > 0 ? entries : void 0;
645
+ }, [blocks, version]);
646
+ }
647
+ function useTimerElapsed(blockKey) {
648
+ const runtime = useScriptRuntime();
649
+ const block = useMemo(() => {
650
+ return runtime.stack.blocks.find((b) => b.key.toString() === blockKey);
651
+ }, [runtime, blockKey]);
652
+ const [timerState, setTimerState] = useState(() => {
653
+ return block?.getMemoryByTag("time")[0]?.metrics[0]?.value;
654
+ });
655
+ useEffect(() => {
656
+ const unsubscribe = runtime.stack.subscribe(() => {
657
+ const foundBlock = runtime.stack.blocks.find((b) => b.key.toString() === blockKey);
658
+ if (foundBlock) {
659
+ const timerLoc = foundBlock.getMemoryByTag("time")[0];
660
+ setTimerState(timerLoc?.metrics[0]?.value);
661
+ } else {
662
+ setTimerState(void 0);
663
+ }
664
+ });
665
+ return unsubscribe;
666
+ }, [runtime, blockKey]);
667
+ useEffect(() => {
668
+ if (!block) {
669
+ setTimerState(void 0);
670
+ return;
671
+ }
672
+ const timerLoc = block.getMemoryByTag("time")[0];
673
+ setTimerState(timerLoc?.metrics[0]?.value);
674
+ if (timerLoc) {
675
+ const unsubscribe = timerLoc.subscribe((newFrags) => {
676
+ setTimerState(newFrags[0]?.value);
677
+ });
678
+ return unsubscribe;
679
+ }
680
+ }, [block]);
681
+ const timeSpans = timerState?.spans || [];
682
+ const isRunning = timeSpans.length > 0 && timeSpans[timeSpans.length - 1].ended === void 0;
683
+ const [now, setNow] = useState(getRuntimeNowMs());
684
+ const elapsed = useMemo(() => {
685
+ if (timeSpans.length === 0) return 0;
686
+ return calculateDuration(timeSpans, now);
687
+ }, [timeSpans, now]);
688
+ useEffect(() => {
689
+ if (!isRunning) return;
690
+ let animationFrameId;
691
+ const update = () => {
692
+ setNow(getRuntimeNowMs());
693
+ animationFrameId = requestAnimationFrame(update);
694
+ };
695
+ update();
696
+ return () => cancelAnimationFrame(animationFrameId);
697
+ }, [isRunning]);
698
+ return {
699
+ elapsed,
700
+ isRunning,
701
+ timeSpans
702
+ };
703
+ }
704
+ function cn(...classes) {
705
+ return classes.filter(Boolean).join(" ");
706
+ }
707
+ var BlockTimerDisplay = ({
708
+ block,
709
+ className,
710
+ showRounds = true,
711
+ compact = false
712
+ }) => {
713
+ const timer = useTimerDisplay(block);
714
+ const round = useRoundDisplay(block);
715
+ if (!timer) {
716
+ return /* @__PURE__ */ jsx("div", { className: cn("flex items-center justify-center p-4 text-muted-foreground", className), children: "No timer active" });
717
+ }
718
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center", className), children: [
719
+ /* @__PURE__ */ jsx("div", { className: cn(
720
+ "font-mono font-bold tabular-nums",
721
+ compact ? "text-3xl" : "text-5xl md:text-6xl",
722
+ timer.isRunning ? "text-primary" : "text-foreground"
723
+ ), children: timer.formatted }),
724
+ /* @__PURE__ */ jsx("div", { className: cn(
725
+ "text-xs uppercase tracking-wider mt-2",
726
+ timer.isRunning ? "text-success" : "text-muted-foreground"
727
+ ), children: timer.isComplete ? "Complete" : timer.isRunning ? "Running" : "Paused" }),
728
+ timer.remaining !== void 0 && timer.remaining > 0 && /* @__PURE__ */ jsxs("div", { className: "text-sm text-muted-foreground mt-1", children: [
729
+ Math.ceil(timer.remaining / 1e3),
730
+ "s remaining"
731
+ ] }),
732
+ showRounds && round && /* @__PURE__ */ jsx("div", { className: cn(
733
+ "mt-3 px-3 py-1 rounded-full text-sm",
734
+ "bg-secondary text-secondary-foreground"
735
+ ), children: round.label })
736
+ ] });
737
+ };
738
+
739
+ export { BlockTimerDisplay, ScriptRuntimeProvider, useActiveControls, useBlockMemory, useBlockOutputs, useDisplayState, useFragmentSource, useLiveAnalytics, useMemorySubscription, useNextPreview, useOutputHierarchy, useOutputStatement, useOutputStatements, usePrimaryTimer, useRoundDisplay, useRoundState, useRuntimeExecution, useScriptRuntime, useSecondaryTimers, useSnapshotBlocks, useSnapshotCurrentBlock, useStackDisplayRows, useStackFragmentSources, useStackSnapshot, useStackTimers, useTimerDisplay, useTimerElapsed, useTimerState };
740
+ //# sourceMappingURL=react.mjs.map
741
+ //# sourceMappingURL=react.mjs.map