@smoothstream/vue 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/index.js ADDED
@@ -0,0 +1,706 @@
1
+ // src/index.ts
2
+ import "@smoothstream/styles/base.css";
3
+ import "@smoothstream/styles/styles.css";
4
+
5
+ // src/Smoothstream.ts
6
+ import {
7
+ codeBlockRequestKey,
8
+ resolveCodeHighlight,
9
+ StreamingSession,
10
+ unitsReadyForCodeHighlights
11
+ } from "@smoothstream/core";
12
+ import {
13
+ createWebPresentation,
14
+ createWebPresentationCache
15
+ } from "@smoothstream/core/web";
16
+ import {
17
+ computed,
18
+ defineComponent as defineComponent2,
19
+ Fragment,
20
+ h as h2,
21
+ mergeProps,
22
+ nextTick,
23
+ onBeforeUnmount as onBeforeUnmount2,
24
+ onMounted,
25
+ onUpdated,
26
+ shallowRef,
27
+ Teleport,
28
+ ref as ref2,
29
+ watch as watch2
30
+ } from "vue";
31
+
32
+ // src/render-web.ts
33
+ import { find, html, svg } from "property-information";
34
+ import {
35
+ defineComponent,
36
+ createTextVNode,
37
+ h,
38
+ onBeforeUnmount,
39
+ ref,
40
+ watch
41
+ } from "vue";
42
+ var htmlPropertyInfoByName = /* @__PURE__ */ new Map();
43
+ var svgPropertyInfoByName = /* @__PURE__ */ new Map();
44
+ var tableContainers = /* @__PURE__ */ new Set(["table", "tbody", "tfoot", "thead", "tr"]);
45
+ var vuePropertyInfo = (property, namespace) => {
46
+ const cache = namespace === "svg" ? svgPropertyInfoByName : htmlPropertyInfoByName;
47
+ const cached = cache.get(property);
48
+ if (cached) return cached;
49
+ const information = find(namespace === "svg" ? svg : html, property);
50
+ const result = {
51
+ commaSeparated: information.commaSeparated,
52
+ name: information.attribute
53
+ };
54
+ cache.set(property, result);
55
+ return result;
56
+ };
57
+ var vueElementProperties = (node) => {
58
+ const result = {};
59
+ for (const [property, originalValue] of Object.entries(node.properties)) {
60
+ if (property === "children" || originalValue === null || originalValue === void 0 || typeof originalValue === "number" && Number.isNaN(originalValue)) {
61
+ continue;
62
+ }
63
+ const information = vuePropertyInfo(property, node.namespace);
64
+ result[information.name] = Array.isArray(originalValue) ? originalValue.map(String).join(information.commaSeparated ? ", " : " ").trim() : originalValue;
65
+ }
66
+ return result;
67
+ };
68
+ var textContent = (node) => node.type === "text" ? node.value : node.children.map(textContent).join("");
69
+ var codeBlockValue = (node) => {
70
+ const code = node.children.find(
71
+ (child) => child.type === "element" && child.tagName === "code"
72
+ );
73
+ const value = code ? textContent(code) : "";
74
+ return value.endsWith("\n") ? value.slice(0, -1) : value;
75
+ };
76
+ var writeClipboardText = async (button, value) => {
77
+ const document = button.ownerDocument;
78
+ const view = document.defaultView;
79
+ if (!view) throw new Error("Clipboard copy requires a browser window.");
80
+ if (view.navigator.clipboard?.writeText) {
81
+ await view.navigator.clipboard.writeText(value);
82
+ return;
83
+ }
84
+ const textarea = document.createElement("textarea");
85
+ textarea.value = value;
86
+ textarea.setAttribute("readonly", "");
87
+ textarea.style.position = "fixed";
88
+ textarea.style.opacity = "0";
89
+ document.body.append(textarea);
90
+ textarea.select();
91
+ const copied = document.execCommand?.("copy") === true;
92
+ textarea.remove();
93
+ if (!copied) throw new Error("Clipboard copy is unavailable.");
94
+ };
95
+ var webChildrenToVue = (children, filterTableWhitespace, copyContext) => children.flatMap((child) => {
96
+ if (filterTableWhitespace && child.type === "text" && /^\s*$/u.test(child.value)) {
97
+ return [];
98
+ }
99
+ return [webNodeToVue(child, copyContext)];
100
+ });
101
+ var webElementToVue = (node, copyContext) => {
102
+ const properties = vueElementProperties(node);
103
+ if (copyContext) {
104
+ if (node.properties["data-smoothstream-code-copy"] === true) {
105
+ properties.onClick = copyContext.copy;
106
+ if (copyContext.copied) properties["aria-label"] = "Code copied";
107
+ }
108
+ if (node.properties["data-smoothstream-code-icon-swap"] === true) {
109
+ properties["data-state"] = copyContext.copied ? "check" : "copy";
110
+ }
111
+ }
112
+ return h(
113
+ node.tagName,
114
+ { ...properties, key: node.key },
115
+ webChildrenToVue(
116
+ node.children,
117
+ tableContainers.has(node.tagName),
118
+ copyContext
119
+ )
120
+ );
121
+ };
122
+ var WebCodeBlock = defineComponent({
123
+ name: "SmoothstreamCodeBlock",
124
+ props: {
125
+ node: {
126
+ required: true,
127
+ type: Object
128
+ }
129
+ },
130
+ setup(props) {
131
+ const copied = ref(false);
132
+ let resetTimer;
133
+ const clearResetTimer = () => {
134
+ if (resetTimer === void 0) return;
135
+ const view = typeof window === "undefined" ? void 0 : window;
136
+ view?.clearTimeout(resetTimer);
137
+ resetTimer = void 0;
138
+ };
139
+ watch(
140
+ () => codeBlockValue(props.node),
141
+ () => {
142
+ copied.value = false;
143
+ clearResetTimer();
144
+ }
145
+ );
146
+ onBeforeUnmount(clearResetTimer);
147
+ const copy = (event) => {
148
+ if (props.node.properties["data-smoothstream-code-copy-ready"] !== true) {
149
+ return;
150
+ }
151
+ const button = event.currentTarget;
152
+ if (!button || button.localName !== "button") return;
153
+ const code = codeBlockValue(props.node);
154
+ void writeClipboardText(button, code).then(
155
+ () => {
156
+ copied.value = true;
157
+ clearResetTimer();
158
+ const view = button.ownerDocument.defaultView;
159
+ if (!view) return;
160
+ resetTimer = view.setTimeout(() => {
161
+ copied.value = false;
162
+ resetTimer = void 0;
163
+ }, 2e3);
164
+ },
165
+ () => {
166
+ copied.value = false;
167
+ }
168
+ );
169
+ };
170
+ return () => webElementToVue(props.node, {
171
+ copied: copied.value,
172
+ copy
173
+ });
174
+ }
175
+ });
176
+ var webNodeToVue = (node, copyContext) => {
177
+ if (node.type === "text") {
178
+ const text = createTextVNode(node.value);
179
+ text.key = node.key;
180
+ return text;
181
+ }
182
+ if (!copyContext && node.tagName === "pre" && node.properties["data-smoothstream-code-block"] === true) {
183
+ return h(WebCodeBlock, { key: node.key, node });
184
+ }
185
+ return webElementToVue(node, copyContext);
186
+ };
187
+ var webNodesToVue = (nodes) => nodes.map((node) => webNodeToVue(node));
188
+
189
+ // src/Smoothstream.ts
190
+ var COMPLETION_ANNOUNCEMENT = "Content ready.";
191
+ var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
192
+ var PHASE_SELECTOR = "[data-smoothstream-animation-start]";
193
+ var PHASE_PROPERTY = "--smoothstream-animation-delay";
194
+ var screenReaderOnlyStyle = {
195
+ border: 0,
196
+ clip: "rect(0 0 0 0)",
197
+ clipPath: "inset(50%)",
198
+ height: "1px",
199
+ margin: "-1px",
200
+ overflow: "hidden",
201
+ padding: 0,
202
+ position: "absolute",
203
+ whiteSpace: "nowrap",
204
+ width: "1px"
205
+ };
206
+ var browserClock = {
207
+ now: () => typeof window === "undefined" ? 0 : window.performance?.now() ?? Date.now()
208
+ };
209
+ var emptyPlayback = () => ({
210
+ animationComplete: true,
211
+ immediate: false,
212
+ now: 0,
213
+ schedules: /* @__PURE__ */ new Map(),
214
+ visibleUnitCount: 0
215
+ });
216
+ var Smoothstream = defineComponent2({
217
+ name: "Smoothstream",
218
+ inheritAttrs: false,
219
+ props: {
220
+ codeHighlighter: Object,
221
+ duration: {
222
+ default: 1e3,
223
+ type: Number
224
+ },
225
+ interval: {
226
+ default: 3,
227
+ type: Number
228
+ },
229
+ markdown: {
230
+ default: "",
231
+ type: String
232
+ },
233
+ mode: {
234
+ default: "streaming",
235
+ type: String
236
+ },
237
+ receiving: {
238
+ default: false,
239
+ type: Boolean
240
+ },
241
+ reducedMotion: {
242
+ default: "system",
243
+ type: String
244
+ },
245
+ reveal: {
246
+ default: "character",
247
+ type: String
248
+ },
249
+ unstyled: {
250
+ default: false,
251
+ type: Boolean
252
+ }
253
+ },
254
+ setup(props, { attrs }) {
255
+ const root = ref2(null);
256
+ const mounted = ref2(false);
257
+ const motionDisabled = ref2(props.reducedMotion === "always");
258
+ const currentInput = shallowRef();
259
+ const currentPlayback = shallowRef(
260
+ emptyPlayback()
261
+ );
262
+ const codeHighlights = shallowRef(
263
+ /* @__PURE__ */ new Map()
264
+ );
265
+ const imageReadiness = shallowRef(
266
+ /* @__PURE__ */ new Map()
267
+ );
268
+ const resourceNow = ref2(0);
269
+ const completionAnnouncement = ref2("");
270
+ let session = new StreamingSession(browserClock, {
271
+ duration: props.duration,
272
+ interval: props.interval
273
+ });
274
+ let presentationCache = createWebPresentationCache();
275
+ let lastPresentation;
276
+ let requestedSource = props.markdown;
277
+ let pendingInput = {
278
+ receiving: props.receiving,
279
+ source: props.markdown
280
+ };
281
+ let inputFrame;
282
+ let playbackFrame;
283
+ let mediaQuery;
284
+ let destroyed = false;
285
+ let announcedSource;
286
+ let completionRendered = false;
287
+ const codeRequestsByBlock = /* @__PURE__ */ new Map();
288
+ const codeSessionsByBlock = /* @__PURE__ */ new Map();
289
+ const imageLoaders = /* @__PURE__ */ new Map();
290
+ const resourceTimers = /* @__PURE__ */ new Set();
291
+ const synchronizedPhases = /* @__PURE__ */ new WeakMap();
292
+ let phasedElements = /* @__PURE__ */ new Set();
293
+ const resolvedMotion = computed(() => {
294
+ if (props.reducedMotion === "always") return "none";
295
+ if (props.reducedMotion === "never") return "animate";
296
+ if (!mounted.value) return "pending";
297
+ return motionDisabled.value ? "none" : "animate";
298
+ });
299
+ const presentationImmediate = () => props.mode === "static" || resolvedMotion.value === "none";
300
+ const requestFrame = (callback) => typeof window.requestAnimationFrame === "function" ? window.requestAnimationFrame(callback) : window.setTimeout(() => callback(browserClock.now()), 16);
301
+ const cancelFrame = (frame) => {
302
+ if (typeof window.cancelAnimationFrame === "function") {
303
+ window.cancelAnimationFrame(frame);
304
+ } else {
305
+ window.clearTimeout(frame);
306
+ }
307
+ };
308
+ const playbackUnits = (input) => presentationImmediate() ? input.plan.units : unitsReadyForCodeHighlights(
309
+ input.plan.units,
310
+ props.codeHighlighter,
311
+ codeHighlights.value
312
+ );
313
+ const cancelPlaybackFrame = () => {
314
+ if (playbackFrame === void 0 || typeof window === "undefined") return;
315
+ cancelFrame(playbackFrame);
316
+ playbackFrame = void 0;
317
+ };
318
+ const ensurePlaybackFrame = () => {
319
+ if (destroyed || typeof window === "undefined" || presentationImmediate() || currentPlayback.value.animationComplete || playbackFrame !== void 0) {
320
+ return;
321
+ }
322
+ playbackFrame = requestFrame(() => {
323
+ playbackFrame = void 0;
324
+ const previous = currentPlayback.value;
325
+ const next = session.advance();
326
+ if (next.visibleUnitCount !== previous.visibleUnitCount || next.animationComplete && !completionRendered) {
327
+ completionRendered = next.animationComplete;
328
+ currentPlayback.value = next;
329
+ }
330
+ ensurePlaybackFrame();
331
+ });
332
+ };
333
+ const refreshPlayback = (input = currentInput.value) => {
334
+ if (!input) return;
335
+ cancelPlaybackFrame();
336
+ const units = playbackUnits(input);
337
+ if (presentationImmediate()) {
338
+ currentPlayback.value = session.immediate(input, units);
339
+ } else if (mounted.value && resolvedMotion.value === "animate") {
340
+ currentPlayback.value = session.schedule(input, units);
341
+ } else {
342
+ currentPlayback.value = emptyPlayback();
343
+ }
344
+ completionRendered = currentPlayback.value.animationComplete;
345
+ ensurePlaybackFrame();
346
+ };
347
+ const publishResourceChange = () => {
348
+ resourceNow.value = browserClock.now();
349
+ };
350
+ const ensureImages = (descriptors) => {
351
+ if (typeof window === "undefined" || presentationImmediate()) {
352
+ return;
353
+ }
354
+ for (const descriptor of descriptors) {
355
+ if (imageLoaders.has(descriptor.id) || imageReadiness.value.has(descriptor.id)) {
356
+ continue;
357
+ }
358
+ let finished = false;
359
+ const finish = (status, image2) => {
360
+ if (finished || destroyed) return;
361
+ finished = true;
362
+ imageLoaders.delete(descriptor.id);
363
+ const readyAt = browserClock.now();
364
+ const width = image2?.naturalWidth ?? 0;
365
+ const height = image2?.naturalHeight ?? 0;
366
+ const next = new Map(imageReadiness.value);
367
+ next.set(
368
+ descriptor.id,
369
+ status === "ready" && width > 0 && height > 0 ? { height, readyAt, status, width } : { readyAt, status }
370
+ );
371
+ imageReadiness.value = next;
372
+ publishResourceChange();
373
+ const timer = window.setTimeout(() => {
374
+ resourceTimers.delete(timer);
375
+ publishResourceChange();
376
+ }, props.duration + 20);
377
+ resourceTimers.add(timer);
378
+ };
379
+ if (descriptor.src.length === 0) {
380
+ finish("error");
381
+ continue;
382
+ }
383
+ const image = new window.Image();
384
+ imageLoaders.set(descriptor.id, image);
385
+ image.decoding = "async";
386
+ image.onerror = () => finish("error");
387
+ let decodeStarted = false;
388
+ const handleLoad = () => {
389
+ if (decodeStarted || finished) return;
390
+ decodeStarted = true;
391
+ const decoded = typeof image.decode === "function" ? image.decode().catch(() => void 0) : Promise.resolve();
392
+ void decoded.then(() => finish("ready", image));
393
+ };
394
+ image.onload = handleLoad;
395
+ image.src = descriptor.src;
396
+ if (image.complete && image.naturalWidth > 0) handleLoad();
397
+ }
398
+ };
399
+ const commitCodeHighlight = (highlighter, request, key, result) => {
400
+ if (destroyed || props.codeHighlighter !== highlighter || codeRequestsByBlock.get(request.blockStart) !== key) {
401
+ return;
402
+ }
403
+ const next = new Map(codeHighlights.value);
404
+ next.set(
405
+ request.blockStart,
406
+ resolveCodeHighlight(
407
+ request,
408
+ result,
409
+ codeHighlights.value.get(request.blockStart)
410
+ )
411
+ );
412
+ codeHighlights.value = next;
413
+ refreshPlayback();
414
+ };
415
+ const ensureCodeHighlights = (requests) => {
416
+ const highlighter = props.codeHighlighter;
417
+ if (!mounted.value || !highlighter) return;
418
+ for (const request of requests) {
419
+ const key = codeBlockRequestKey(request);
420
+ const current = codeHighlights.value.get(request.blockStart);
421
+ if (current?.language === request.language && current.code === request.code || codeRequestsByBlock.get(request.blockStart) === key) {
422
+ continue;
423
+ }
424
+ codeRequestsByBlock.set(request.blockStart, key);
425
+ let privateSession = codeSessionsByBlock.get(request.blockStart);
426
+ if (!privateSession) {
427
+ privateSession = {};
428
+ codeSessionsByBlock.set(request.blockStart, privateSession);
429
+ }
430
+ void Promise.resolve(highlighter.highlight({
431
+ code: request.code,
432
+ language: request.language,
433
+ session: privateSession
434
+ })).then(
435
+ (result) => commitCodeHighlight(highlighter, request, key, result),
436
+ () => commitCodeHighlight(highlighter, request, key, void 0)
437
+ );
438
+ }
439
+ };
440
+ const flushInput = () => {
441
+ const pending = pendingInput;
442
+ pendingInput = void 0;
443
+ if (!pending || destroyed) return;
444
+ const input = session.prepareInput(
445
+ pending.source,
446
+ pending.receiving,
447
+ props.reveal
448
+ );
449
+ session.commitInput(input);
450
+ currentInput.value = input;
451
+ if (mounted.value) {
452
+ ensureCodeHighlights(input.codeBlocks);
453
+ ensureImages(input.images);
454
+ }
455
+ refreshPlayback(input);
456
+ };
457
+ const scheduleInputFlush = () => {
458
+ if (!mounted.value || typeof window === "undefined") {
459
+ flushInput();
460
+ return;
461
+ }
462
+ if (inputFrame !== void 0) return;
463
+ inputFrame = requestFrame(() => {
464
+ inputFrame = void 0;
465
+ flushInput();
466
+ });
467
+ };
468
+ const clearResourceWork = () => {
469
+ for (const image of imageLoaders.values()) {
470
+ image.onload = null;
471
+ image.onerror = null;
472
+ }
473
+ imageLoaders.clear();
474
+ if (typeof window !== "undefined") {
475
+ for (const timer of resourceTimers) window.clearTimeout(timer);
476
+ }
477
+ resourceTimers.clear();
478
+ };
479
+ const resetSession = () => {
480
+ if (typeof window !== "undefined") {
481
+ if (inputFrame !== void 0) cancelFrame(inputFrame);
482
+ inputFrame = void 0;
483
+ cancelPlaybackFrame();
484
+ }
485
+ clearResourceWork();
486
+ codeRequestsByBlock.clear();
487
+ codeSessionsByBlock.clear();
488
+ codeHighlights.value = /* @__PURE__ */ new Map();
489
+ imageReadiness.value = /* @__PURE__ */ new Map();
490
+ resourceNow.value = 0;
491
+ presentationCache = createWebPresentationCache();
492
+ session = new StreamingSession(browserClock, {
493
+ duration: props.duration,
494
+ interval: props.interval
495
+ });
496
+ requestedSource = props.markdown;
497
+ pendingInput = {
498
+ receiving: props.receiving,
499
+ source: props.markdown
500
+ };
501
+ announcedSource = void 0;
502
+ completionAnnouncement.value = "";
503
+ flushInput();
504
+ };
505
+ function handleMotionChange(event) {
506
+ if (!event.matches || motionDisabled.value) return;
507
+ motionDisabled.value = true;
508
+ refreshPlayback();
509
+ }
510
+ const removeMediaListener = () => {
511
+ if (!mediaQuery) return;
512
+ if (typeof mediaQuery.removeEventListener === "function") {
513
+ mediaQuery.removeEventListener("change", handleMotionChange);
514
+ } else {
515
+ mediaQuery.removeListener?.(handleMotionChange);
516
+ }
517
+ mediaQuery = void 0;
518
+ };
519
+ const configureMotion = () => {
520
+ removeMediaListener();
521
+ if (!mounted.value || typeof window === "undefined" || props.reducedMotion !== "system" || typeof window.matchMedia !== "function") {
522
+ return;
523
+ }
524
+ mediaQuery = window.matchMedia(REDUCED_MOTION_QUERY);
525
+ if (mediaQuery.matches) motionDisabled.value = true;
526
+ if (typeof mediaQuery.addEventListener === "function") {
527
+ mediaQuery.addEventListener("change", handleMotionChange);
528
+ } else {
529
+ mediaQuery.addListener?.(handleMotionChange);
530
+ }
531
+ };
532
+ const synchronizeAnimationPhases = () => {
533
+ const element = root.value;
534
+ if (!element) return;
535
+ const now = browserClock.now();
536
+ const nextElements = /* @__PURE__ */ new Set();
537
+ for (const animated of element.querySelectorAll(PHASE_SELECTOR)) {
538
+ nextElements.add(animated);
539
+ const startAt = Number(animated.dataset.smoothstreamAnimationStart);
540
+ const duration = Number(animated.dataset.smoothstreamAnimationDuration);
541
+ if (!Number.isFinite(startAt) || !Number.isFinite(duration)) continue;
542
+ const synchronized = synchronizedPhases.get(animated);
543
+ if (synchronized?.startAt === startAt && synchronized.duration === duration) {
544
+ animated.style.setProperty(PHASE_PROPERTY, synchronized.delay);
545
+ continue;
546
+ }
547
+ const currentTime = Math.max(0, Math.min(duration, now - startAt));
548
+ const delay = String(-currentTime) + "ms";
549
+ animated.style.setProperty(PHASE_PROPERTY, delay);
550
+ synchronizedPhases.set(animated, { delay, duration, startAt });
551
+ }
552
+ for (const previous of phasedElements) {
553
+ if (!nextElements.has(previous)) {
554
+ previous.style.removeProperty(PHASE_PROPERTY);
555
+ if (previous.style.length === 0) previous.removeAttribute("style");
556
+ }
557
+ }
558
+ phasedElements = nextElements;
559
+ };
560
+ const publishCompletionIfReady = () => {
561
+ const input = currentInput.value;
562
+ const presentation = lastPresentation;
563
+ const element = root.value;
564
+ if (props.mode !== "streaming" || !mounted.value || !input || !presentation || !element) {
565
+ return;
566
+ }
567
+ if (input.inputOpen) completionAnnouncement.value = "";
568
+ if (input.inputOpen || input.plan.tree.children.length === 0 || !presentation.allPlannedUnitsCompacted || element.querySelector("[data-smoothstream-unit]") !== null || announcedSource === input.source) {
569
+ return;
570
+ }
571
+ announcedSource = input.source;
572
+ completionAnnouncement.value = COMPLETION_ANNOUNCEMENT;
573
+ };
574
+ const afterRender = () => {
575
+ synchronizeAnimationPhases();
576
+ publishCompletionIfReady();
577
+ };
578
+ watch2(
579
+ () => [props.markdown, props.receiving],
580
+ ([source, receiving]) => {
581
+ if (!source.startsWith(requestedSource)) {
582
+ throw new Error(
583
+ "Smoothstream Markdown must be append-only. Give a different response a new Vue key."
584
+ );
585
+ }
586
+ requestedSource = source;
587
+ pendingInput = { receiving, source };
588
+ if (receiving || announcedSource !== source) {
589
+ announcedSource = void 0;
590
+ completionAnnouncement.value = "";
591
+ }
592
+ scheduleInputFlush();
593
+ },
594
+ { flush: "sync" }
595
+ );
596
+ watch2(
597
+ () => [props.duration, props.interval, props.mode, props.reveal],
598
+ resetSession,
599
+ { flush: "sync" }
600
+ );
601
+ watch2(
602
+ () => props.codeHighlighter,
603
+ () => {
604
+ codeRequestsByBlock.clear();
605
+ codeSessionsByBlock.clear();
606
+ codeHighlights.value = /* @__PURE__ */ new Map();
607
+ const input = currentInput.value;
608
+ if (input) ensureCodeHighlights(input.codeBlocks);
609
+ refreshPlayback(input);
610
+ },
611
+ { flush: "sync" }
612
+ );
613
+ watch2(
614
+ () => props.reducedMotion,
615
+ () => {
616
+ configureMotion();
617
+ const input = currentInput.value;
618
+ if (input && !presentationImmediate()) ensureImages(input.images);
619
+ refreshPlayback(input);
620
+ },
621
+ { flush: "sync" }
622
+ );
623
+ flushInput();
624
+ onMounted(() => {
625
+ mounted.value = true;
626
+ configureMotion();
627
+ const input = currentInput.value;
628
+ if (input) {
629
+ ensureCodeHighlights(input.codeBlocks);
630
+ ensureImages(input.images);
631
+ refreshPlayback(input);
632
+ }
633
+ void nextTick(afterRender);
634
+ });
635
+ onUpdated(afterRender);
636
+ onBeforeUnmount2(() => {
637
+ destroyed = true;
638
+ removeMediaListener();
639
+ if (typeof window !== "undefined") {
640
+ if (inputFrame !== void 0) cancelFrame(inputFrame);
641
+ cancelPlaybackFrame();
642
+ }
643
+ clearResourceWork();
644
+ codeRequestsByBlock.clear();
645
+ codeSessionsByBlock.clear();
646
+ });
647
+ const renderContent = () => {
648
+ const input = currentInput.value;
649
+ if (!input || props.mode === "streaming" && resolvedMotion.value === "pending") {
650
+ lastPresentation = void 0;
651
+ return [];
652
+ }
653
+ const playback = currentPlayback.value;
654
+ const now = Math.max(playback.now, resourceNow.value);
655
+ const presentation = session.present(input, playback, { now });
656
+ lastPresentation = presentation;
657
+ return webNodesToVue(createWebPresentation({
658
+ cache: presentationCache,
659
+ codeHighlighterEnabled: props.codeHighlighter !== void 0,
660
+ codeHighlights: codeHighlights.value,
661
+ compactedBlockIds: presentation.compactedBlockIds,
662
+ compactedUnitIds: presentation.compactedUnitIds,
663
+ images: imageReadiness.value,
664
+ immediate: presentationImmediate(),
665
+ now,
666
+ reveal: input.reveal,
667
+ schedules: presentation.schedules,
668
+ showLanguageLabels: props.codeHighlighter?.showLanguageLabels !== false,
669
+ tree: input.plan.tree,
670
+ units: input.plan.units
671
+ }));
672
+ };
673
+ return () => {
674
+ const motion = props.mode === "streaming" && resolvedMotion.value === "pending" ? "pending" : resolvedMotion.value === "none" ? "none" : "animate";
675
+ const rootNode = h2(
676
+ "div",
677
+ mergeProps(attrs, {
678
+ "data-smoothstream": true,
679
+ "data-smoothstream-mode": props.mode,
680
+ "data-smoothstream-motion": motion,
681
+ "data-smoothstream-reduced-motion": props.reducedMotion,
682
+ "data-smoothstream-reveal": props.reveal,
683
+ "data-smoothstream-theme": props.unstyled ? void 0 : "default",
684
+ ref: root,
685
+ style: {
686
+ "--smoothstream-duration": String(resolvedMotion.value === "none" ? 0 : props.duration) + "ms",
687
+ "--smoothstream-interval": String(resolvedMotion.value === "none" ? 0 : props.interval) + "ms"
688
+ }
689
+ }),
690
+ renderContent()
691
+ );
692
+ const announcer = props.mode === "streaming" && mounted.value ? h2(Teleport, { to: "body" }, h2("div", {
693
+ "aria-atomic": true,
694
+ "aria-live": "polite",
695
+ "data-smoothstream-announcer": true,
696
+ role: "status",
697
+ style: screenReaderOnlyStyle
698
+ }, completionAnnouncement.value)) : null;
699
+ return h2(Fragment, null, [rootNode, announcer]);
700
+ };
701
+ }
702
+ });
703
+ export {
704
+ Smoothstream
705
+ };
706
+ //# sourceMappingURL=index.js.map