@simon_he/vue-tui 1.1.1 → 1.1.2

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 (66) hide show
  1. package/README.md +68 -28
  2. package/dist/{TMermaidText-CXm_L969.js → TMermaidText-frVr8Ysb.js} +1 -1
  3. package/dist/{TSelect-CiyMvwl-.js → TSelect-tJ87rKPM.js} +6 -168
  4. package/dist/{TTree-DKGiuIcM.js → TTree-CGdE-CYu.js} +12 -13
  5. package/dist/agent.cjs +9 -5
  6. package/dist/agent.js +15 -1197
  7. package/dist/{block-source-5CQp4tF8.js → block-source-CrlmaBM5.js} +7 -601
  8. package/dist/cli/input.d.ts +8 -0
  9. package/dist/cli/video/ffmpeg-frame-source.d.ts +10 -0
  10. package/dist/cli/video/png-frame-parser.d.ts +4 -0
  11. package/dist/cli/video/raw-frame-parser.d.ts +1 -0
  12. package/dist/cli/video/yt-dlp-frame-source.d.ts +11 -0
  13. package/dist/cli.cjs +142 -33
  14. package/dist/cli.d.cts +1 -1
  15. package/dist/cli.d.ts +1 -1
  16. package/dist/cli.js +167 -37
  17. package/dist/core.js +5 -6
  18. package/dist/create-terminal-app.d.ts +24 -1
  19. package/dist/{debug-logger-ChY_PXVk.js → debug-logger-CmsGI4of.js} +1 -1
  20. package/dist/{dom-renderer-DNzmg2As.js → dom-renderer-DXx8fWzD.js} +2 -3
  21. package/dist/experimental/3d/bun/contributor-avatar-atlas.d.ts +12 -0
  22. package/dist/experimental/3d/bun/png.d.ts +1 -0
  23. package/dist/experimental/3d/bun/terminal-badge-scene.d.ts +11 -0
  24. package/dist/experimental/3d/bun/terminal-badge.wgsl.d.ts +1 -0
  25. package/dist/experimental/3d/bun/webgpu-renderer.d.ts +26 -0
  26. package/dist/experimental/3d/bun.cjs +7271 -0
  27. package/dist/experimental/3d/bun.d.cts +4 -0
  28. package/dist/experimental/3d/bun.d.ts +4 -0
  29. package/dist/experimental/3d/bun.js +7390 -0
  30. package/dist/experimental/video/node.cjs +497 -0
  31. package/dist/experimental/video/node.d.cts +4 -0
  32. package/dist/experimental/video/node.d.ts +4 -0
  33. package/dist/experimental/video/node.js +610 -0
  34. package/dist/experimental.cjs +3064 -567
  35. package/dist/experimental.d.cts +5 -0
  36. package/dist/experimental.d.ts +5 -0
  37. package/dist/experimental.js +1524 -9
  38. package/dist/{create-terminal-DXuZ2fci.js → hyperlink-_1FAj7Wq.js} +403 -2
  39. package/dist/index.js +15 -16
  40. package/dist/{linkify-CuuquMQT.js → linkify-D68T7rdW.js} +164 -3
  41. package/dist/markdown.js +8 -8
  42. package/dist/{mermaid-CWto52Wk.js → mermaid-BNhM1jAZ.js} +1 -1
  43. package/dist/mermaid.js +3 -3
  44. package/dist/observability.js +2 -2
  45. package/dist/{path-suggest-fPzRsgPv.js → path-suggest-Dndu4dTi.js} +1 -1
  46. package/dist/renderer/terminal-graphics.d.ts +1 -0
  47. package/dist/renderer-dom.js +3 -4
  48. package/dist/runtime.js +2 -2
  49. package/dist/terminal-graphics-Bbe83wSr.js +597 -0
  50. package/dist/{tlog-theme-CopRt9SC.js → tlog-theme-Bul2Na3u.js} +1206 -12
  51. package/dist/{trace-CYHDKM1m.js → trace-Bh8WnoMo.js} +1 -1
  52. package/dist/vue/components/T3DViewport.d.ts +473 -0
  53. package/dist/vue/components/TAgentTerminalGraphic.d.ts +13 -0
  54. package/dist/vue/components/TVideo.d.ts +291 -0
  55. package/dist/vue/video/types.d.ts +39 -0
  56. package/dist/vue.js +17 -18
  57. package/package.json +38 -1
  58. package/dist/hyperlink-B35-O-cW.js +0 -403
  59. /package/dist/{ansi-palette-CNXqFciC.js → ansi-palette-CH6jc2Z7.js} +0 -0
  60. /package/dist/{env-DNCzvX8b.js → env-Cl8qmNFN.js} +0 -0
  61. /package/dist/{frame-perf-BBhE-iuS.js → frame-perf-CHpWhbC4.js} +0 -0
  62. /package/dist/{path-BI9W15Zt.js → path-mnSgEpCm.js} +0 -0
  63. /package/dist/{rect-Ng-3eKzF.js → rect-DUpeDfBg.js} +0 -0
  64. /package/dist/{runtime-D77C03RM.js → runtime-Byf8XXOT.js} +0 -0
  65. /package/dist/{terminal-selection-CQYr--26.js → terminal-selection-4W1SJj4c.js} +0 -0
  66. /package/dist/{wheel-scroll-CQ22Hcyj.js → wheel-scroll-DASkEZr-.js} +0 -0
@@ -1,13 +1,1528 @@
1
+ import { sanitizeTerminalHref } from "./hyperlink-_1FAj7Wq.js";
1
2
  import "./width-GqllnV8C.js";
2
- import { sanitizeTerminalHref } from "./hyperlink-B35-O-cW.js";
3
- import { EventZIndexContextKey, forEachTextCellSegment, intersectRect, sanitizeInlineText, sliceByCells, spaces, textCellWidth$1 as textCellWidth, translateRect, useLayout, useRenderNode, useTerminal, useTerminalNode, useVisibility } from "./rect-Ng-3eKzF.js";
4
- import { mergeStyle } from "./linkify-CuuquMQT.js";
5
- import "./frame-perf-BBhE-iuS.js";
6
- import "./terminal-selection-CQYr--26.js";
7
- import "./wheel-scroll-CQ22Hcyj.js";
8
- import { TLogLinksPanel, TLogMinimap, TLogScrollbar, TLogSearchBar, TLogSearchPager, TLogSearchResults, TLogView, TLogVirtualLinksPanel, TLogVirtualSearchResults, TTranscriptView, TVirtualList, createAppendOnlyLogStore, createTLogDensityBucketsFromMarkers, createTLogLevelPlugin, createTLogLineMatcherPlugin, createTLogLinkActionPlugin, createTLogOsc8LinkPlugin, createTLogUrlPlugin, detectTLogUrls, dispatchTLogPluginLinkAction, getTLogPluginMetadata, handleTLogKeymapEvent, matchesTLogKeyBinding, parseTLogAnnotatedText, resolveTLogLinksPanelTheme, resolveTLogMinimapTheme, resolveTLogScrollbarTheme, resolveTLogSearchBarTheme, resolveTLogSearchPagerTheme, resolveTLogSearchResultsTheme, resolveTLogViewTheme, stripTLogAnsiText, tlogDarkPreset, tlogDarkTheme, tlogDefaultKeymap, tlogDefaultPreset, tlogDefaultTheme, tlogHighContrastKeymap, tlogHighContrastPreset, tlogHighContrastTheme, toTLogExternalLinkFromVisibleLink, useTLogLinkController, useTLogRetainedIndex, useTLogSearchController, useTLogSearchResultsPage, useTLogVirtualSearchResults } from "./tlog-theme-CopRt9SC.js";
9
- import { computed, defineComponent, h, inject, ref } from "vue";
3
+ import { EventZIndexContextKey, TerminalGraphicsActivityKey, forEachTextCellSegment, intersectRect, sanitizeInlineText, sliceByCells, spaces, textCellWidth$1 as textCellWidth, translateRect, useLayout, useRenderNode, useTerminal, useTerminalNode, useVisibility } from "./rect-DUpeDfBg.js";
4
+ import { TText, TView, createFrameMailbox, mergeStyle } from "./linkify-D68T7rdW.js";
5
+ import "./frame-perf-CHpWhbC4.js";
6
+ import "./terminal-selection-4W1SJj4c.js";
7
+ import "./wheel-scroll-DASkEZr-.js";
8
+ import { createIterm2InlineImageSequence, createKittyDeleteGraphicsSequence, createKittyGraphicsSequence, createKittyPlacementSequence, getTerminalGraphicsOutput, getTerminalGraphicsOutputVersion, subscribeTerminalGraphicsOutput } from "./terminal-graphics-Bbe83wSr.js";
9
+ import { TAgentTerminalGraphic, TLogLinksPanel, TLogMinimap, TLogScrollbar, TLogSearchBar, TLogSearchPager, TLogSearchResults, TLogView, TLogVirtualLinksPanel, TLogVirtualSearchResults, TTranscriptView, TVirtualList, createAppendOnlyLogStore, createTLogDensityBucketsFromMarkers, createTLogLevelPlugin, createTLogLineMatcherPlugin, createTLogLinkActionPlugin, createTLogOsc8LinkPlugin, createTLogUrlPlugin, detectTLogUrls, dispatchTLogPluginLinkAction, getTLogPluginMetadata, handleTLogKeymapEvent, matchesTLogKeyBinding, parseTLogAnnotatedText, resolveTLogLinksPanelTheme, resolveTLogMinimapTheme, resolveTLogScrollbarTheme, resolveTLogSearchBarTheme, resolveTLogSearchPagerTheme, resolveTLogSearchResultsTheme, resolveTLogViewTheme, stripTLogAnsiText, tlogDarkPreset, tlogDarkTheme, tlogDefaultKeymap, tlogDefaultPreset, tlogDefaultTheme, tlogHighContrastKeymap, tlogHighContrastPreset, tlogHighContrastTheme, toTLogExternalLinkFromVisibleLink, useTLogLinkController, useTLogRetainedIndex, useTLogSearchController, useTLogSearchResultsPage, useTLogVirtualSearchResults } from "./tlog-theme-Bul2Na3u.js";
10
+ import { computed, defineComponent, getCurrentInstance, h, inject, markRaw, onBeforeUnmount, onUnmounted, ref, shallowRef, watch } from "vue";
10
11
 
12
+ //#region src/vue/components/TVideo.ts
13
+ const DEFAULT_MAX_FPS$1 = 12;
14
+ const DEFAULT_MAX_PIXEL_WIDTH = 640;
15
+ const DEFAULT_MAX_PIXEL_HEIGHT = 360;
16
+ const MAX_PIXEL_DIMENSION = 4096;
17
+ const MAX_FRAME_BYTES = 8 * 1024 * 1024;
18
+ const MAX_GRAY8_FPS = 10;
19
+ const MAX_GRAY8_WIDTH = 512;
20
+ const MAX_GRAY8_HEIGHT = 256;
21
+ const ASCII_RAMP = " .:-=+*#%@";
22
+ const CONTROL_REFRESH_MS = 250;
23
+ const PLAYBACK_RATES = [
24
+ 1,
25
+ 2,
26
+ 3
27
+ ];
28
+ function positiveInt(value, fallback, max) {
29
+ const parsed = Math.floor(Number(value));
30
+ if (!Number.isFinite(parsed) || parsed <= 0) return Math.min(max, fallback);
31
+ return Math.min(max, parsed);
32
+ }
33
+ function positiveNumber(value, fallback, max) {
34
+ const parsed = Number(value);
35
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
36
+ return Math.min(max, parsed);
37
+ }
38
+ function playbackRate(value) {
39
+ return value === 2 || value === 3 ? value : 1;
40
+ }
41
+ function optionalDuration(value) {
42
+ const parsed = Number(value);
43
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
44
+ }
45
+ function formatPlaybackTime(valueMs, hourDigits = 0) {
46
+ if (valueMs == null) return "--:--";
47
+ const seconds = Math.max(0, Math.floor(valueMs / 1e3));
48
+ const hours = Math.floor(seconds / 3600);
49
+ const minutes = Math.floor(seconds % 3600 / 60);
50
+ const remainder = seconds % 60;
51
+ if (hours > 0 || hourDigits > 0) return `${String(hours).padStart(Math.max(1, hourDigits), "0")}:${String(minutes).padStart(2, "0")}:${String(remainder).padStart(2, "0")}`;
52
+ return `${String(minutes).padStart(2, "0")}:${String(remainder).padStart(2, "0")}`;
53
+ }
54
+ function readUint32(bytes, offset) {
55
+ return bytes[offset] * 16777216 + (bytes[offset + 1] << 16) + (bytes[offset + 2] << 8) + bytes[offset + 3] >>> 0;
56
+ }
57
+ function normalizeFrame(frame) {
58
+ if (frame.format === "gray8") {
59
+ const pixelWidth$1 = Math.floor(Number(frame.pixelWidth));
60
+ const pixelHeight$1 = Math.floor(Number(frame.pixelHeight));
61
+ const pixels = frame.pixels;
62
+ if (!Number.isFinite(pixelWidth$1) || !Number.isFinite(pixelHeight$1) || pixelWidth$1 <= 0 || pixelHeight$1 <= 0 || pixelWidth$1 > MAX_GRAY8_WIDTH || pixelHeight$1 > MAX_GRAY8_HEIGHT || !(pixels instanceof Uint8Array) || pixels.length !== pixelWidth$1 * pixelHeight$1 || pixels.length > MAX_FRAME_BYTES) throw new Error("TVideo gray8 frame must match its bounded pixel dimensions");
63
+ return {
64
+ format: "gray8",
65
+ pixels,
66
+ timestampMs: Math.max(0, Number(frame.timestampMs) || 0),
67
+ pixelWidth: pixelWidth$1,
68
+ pixelHeight: pixelHeight$1,
69
+ fingerprint: frame.fingerprint,
70
+ durationMs: optionalDuration(frame.durationMs)
71
+ };
72
+ }
73
+ const png = frame.png;
74
+ if (!(png instanceof Uint8Array) || png.length < 24 || png.length > MAX_FRAME_BYTES) throw new Error("TVideo frame must contain a bounded PNG Uint8Array");
75
+ if (png[0] !== 137 || png[1] !== 80 || png[2] !== 78 || png[3] !== 71 || png[4] !== 13 || png[5] !== 10 || png[6] !== 26 || png[7] !== 10) throw new Error("TVideo frame source emitted invalid PNG data");
76
+ const pixelWidth = readUint32(png, 16);
77
+ const pixelHeight = readUint32(png, 20);
78
+ if (pixelWidth <= 0 || pixelHeight <= 0 || pixelWidth > MAX_PIXEL_DIMENSION || pixelHeight > MAX_PIXEL_DIMENSION) throw new Error(`TVideo frame has an unsupported size: ${pixelWidth}x${pixelHeight}`);
79
+ return {
80
+ format: "png",
81
+ png,
82
+ timestampMs: Math.max(0, Number(frame.timestampMs) || 0),
83
+ pixelWidth,
84
+ pixelHeight,
85
+ fingerprint: frame.fingerprint,
86
+ durationMs: optionalDuration(frame.durationMs)
87
+ };
88
+ }
89
+ function frameBytes(frame) {
90
+ return frame.format === "gray8" ? frame.pixels : frame.png;
91
+ }
92
+ function gray8ToAscii(frame, cellWidth) {
93
+ const lines = [];
94
+ const width = Math.max(1, Math.floor(cellWidth));
95
+ for (let y = 0; y < frame.pixelHeight; y++) {
96
+ let line = "";
97
+ const rowStart = y * frame.pixelWidth;
98
+ for (let x = 0; x < frame.pixelWidth; x++) {
99
+ const value = frame.pixels[rowStart + x];
100
+ const glyph = ASCII_RAMP[Math.min(ASCII_RAMP.length - 1, value * ASCII_RAMP.length >> 8)];
101
+ line += `${glyph}${glyph}`;
102
+ }
103
+ lines.push(line.slice(0, width));
104
+ }
105
+ return lines.join("\n");
106
+ }
107
+ function sameBytes(left, right) {
108
+ if (left.length !== right.length) return false;
109
+ for (let index = 0; index < left.length; index++) if (left[index] !== right[index]) return false;
110
+ return true;
111
+ }
112
+ function bytesToBase64(bytes) {
113
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
114
+ const chunks = [];
115
+ let chunk = "";
116
+ for (let index = 0; index < bytes.length; index += 3) {
117
+ const a = bytes[index];
118
+ const hasB = index + 1 < bytes.length;
119
+ const hasC = index + 2 < bytes.length;
120
+ const b = hasB ? bytes[index + 1] : 0;
121
+ const c = hasC ? bytes[index + 2] : 0;
122
+ chunk += alphabet[a >> 2] + alphabet[(a & 3) << 4 | b >> 4] + (hasB ? alphabet[(b & 15) << 2 | c >> 6] : "=") + (hasC ? alphabet[c & 63] : "=");
123
+ if (chunk.length >= 16384) {
124
+ chunks.push(chunk);
125
+ chunk = "";
126
+ }
127
+ }
128
+ if (chunk) chunks.push(chunk);
129
+ return chunks.join("");
130
+ }
131
+ function resolveKittyPlacement(frame, context) {
132
+ const rect = context.viewport.rect;
133
+ const full = context.viewport.fullRect;
134
+ const columns = Math.max(1, Math.floor(rect.w));
135
+ const rows = Math.max(1, Math.floor(rect.h));
136
+ if (full.w <= 0 || full.h <= 0) return {
137
+ columns,
138
+ rows
139
+ };
140
+ const offsetX = Math.max(0, rect.x - full.x);
141
+ const offsetY = Math.max(0, rect.y - full.y);
142
+ const visibleW = Math.max(0, Math.min(rect.w, full.w - offsetX));
143
+ const visibleH = Math.max(0, Math.min(rect.h, full.h - offsetY));
144
+ if (offsetX <= 0 && offsetY <= 0 && visibleW >= full.w && visibleH >= full.h) return {
145
+ columns,
146
+ rows
147
+ };
148
+ const sourceX = Math.max(0, Math.min(frame.pixelWidth - 1, Math.floor(offsetX * frame.pixelWidth / full.w)));
149
+ const sourceY = Math.max(0, Math.min(frame.pixelHeight - 1, Math.floor(offsetY * frame.pixelHeight / full.h)));
150
+ const sourceRight = Math.max(sourceX + 1, Math.min(frame.pixelWidth, Math.ceil((offsetX + visibleW) * frame.pixelWidth / full.w)));
151
+ const sourceBottom = Math.max(sourceY + 1, Math.min(frame.pixelHeight, Math.ceil((offsetY + visibleH) * frame.pixelHeight / full.h)));
152
+ return {
153
+ columns,
154
+ rows,
155
+ sourceX,
156
+ sourceY,
157
+ sourceWidth: sourceRight - sourceX,
158
+ sourceHeight: sourceBottom - sourceY
159
+ };
160
+ }
161
+ function createFrameRenderer(frame, cellWidth) {
162
+ if (frame.format === "gray8") {
163
+ const text = gray8ToAscii(frame, cellWidth);
164
+ return () => ({
165
+ type: "text",
166
+ text
167
+ });
168
+ }
169
+ let base64;
170
+ return (_content, context) => {
171
+ if (!context.visible || !context.rawVisible) return null;
172
+ base64 ?? (base64 = bytesToBase64(frame.png));
173
+ if (context.protocol === "kitty") {
174
+ const placement = resolveKittyPlacement(frame, context);
175
+ return {
176
+ type: "sequence",
177
+ protocol: "kitty",
178
+ sequence: createKittyGraphicsSequence(base64, {
179
+ imageId: context.imageId,
180
+ placementId: context.placementId,
181
+ ...placement
182
+ }),
183
+ resizeSequence: createKittyPlacementSequence({
184
+ imageId: context.imageId,
185
+ placementId: context.placementId,
186
+ ...placement
187
+ }),
188
+ clearSequence: createKittyDeleteGraphicsSequence({
189
+ imageId: context.imageId,
190
+ placementId: context.placementId,
191
+ freeImageData: true
192
+ }),
193
+ cols: context.width,
194
+ rows: context.height,
195
+ sourceWidth: frame.pixelWidth,
196
+ sourceHeight: frame.pixelHeight
197
+ };
198
+ }
199
+ if (context.protocol === "iterm2") return {
200
+ type: "sequence",
201
+ protocol: "iterm2",
202
+ sequence: createIterm2InlineImageSequence(base64, {
203
+ width: context.width,
204
+ height: context.height,
205
+ preserveAspectRatio: true,
206
+ doNotMoveCursor: true
207
+ }),
208
+ cols: context.width,
209
+ rows: context.height,
210
+ sourceWidth: frame.pixelWidth,
211
+ sourceHeight: frame.pixelHeight
212
+ };
213
+ return null;
214
+ };
215
+ }
216
+ function errorMessage(error) {
217
+ return error instanceof Error ? error.message : String(error);
218
+ }
219
+ const tVideoProps = {
220
+ x: {
221
+ type: Number,
222
+ required: true
223
+ },
224
+ y: {
225
+ type: Number,
226
+ required: true
227
+ },
228
+ w: {
229
+ type: Number,
230
+ required: true
231
+ },
232
+ h: {
233
+ type: Number,
234
+ required: true
235
+ },
236
+ zIndex: {
237
+ type: Number,
238
+ default: 0
239
+ },
240
+ src: {
241
+ type: String,
242
+ required: true
243
+ },
244
+ frameSource: {
245
+ type: Function,
246
+ required: true
247
+ },
248
+ paused: {
249
+ type: Boolean,
250
+ default: void 0
251
+ },
252
+ playbackRate: {
253
+ type: Number,
254
+ default: void 0
255
+ },
256
+ controls: {
257
+ type: Boolean,
258
+ default: false
259
+ },
260
+ controlsLayout: {
261
+ type: String,
262
+ default: "compact"
263
+ },
264
+ durationMs: {
265
+ type: Number,
266
+ default: void 0
267
+ },
268
+ loop: {
269
+ type: Boolean,
270
+ default: false
271
+ },
272
+ maxFps: {
273
+ type: Number,
274
+ default: DEFAULT_MAX_FPS$1
275
+ },
276
+ pixelWidth: {
277
+ type: Number,
278
+ default: void 0
279
+ },
280
+ pixelHeight: {
281
+ type: Number,
282
+ default: void 0
283
+ },
284
+ fallback: {
285
+ type: String,
286
+ default: "[video]"
287
+ },
288
+ style: {
289
+ type: Object,
290
+ default: void 0
291
+ },
292
+ clear: {
293
+ type: Boolean,
294
+ default: true
295
+ }
296
+ };
297
+ const TVideo = defineComponent({
298
+ name: "TVideo",
299
+ props: tVideoProps,
300
+ emits: {
301
+ frame: (_event) => true,
302
+ ended: () => true,
303
+ error: (_error) => true,
304
+ seek: (_event) => true,
305
+ "update:paused": (_paused) => true,
306
+ "update:playbackRate": (_rate) => true
307
+ },
308
+ setup(props, { emit }) {
309
+ const instance = getCurrentInstance();
310
+ const { terminal, scheduler } = useTerminal();
311
+ const layout = useLayout();
312
+ const { visible, rootProps } = useVisibility({ provide: true });
313
+ const graphicsActivity = inject(TerminalGraphicsActivityKey, null);
314
+ const frameRenderer = shallowRef();
315
+ const playbackError = shallowRef("");
316
+ const internalPaused = shallowRef(props.paused ?? false);
317
+ const internalPlaybackRate = shallowRef(playbackRate(props.playbackRate));
318
+ const discoveredDurationMs = shallowRef();
319
+ const displayedTimestampMs = shallowRef(0);
320
+ const seekPreviewMs = shallowRef();
321
+ const seeking = shallowRef(false);
322
+ const terminalSizeVersion = shallowRef(0);
323
+ const graphicsOutputVersion = shallowRef(getTerminalGraphicsOutputVersion(terminal));
324
+ const placementKey = `TVideo:${instance?.uid ?? "unknown"}`;
325
+ const mailboxId = `${placementKey}:frame`;
326
+ let alive = true;
327
+ let generation = 0;
328
+ let activeAbort = null;
329
+ let lastSourceBytes = null;
330
+ let lastQueuedBytes = null;
331
+ let lastQueuedFormat = null;
332
+ let lastQueuedFingerprint;
333
+ let lastTimestampMs = 0;
334
+ let droppedFrames = 0;
335
+ let pendingEndedGeneration = null;
336
+ let suppressNextVideoClick = false;
337
+ let controlPointerCapture = null;
338
+ const unsubscribeTerminalResize = terminal.on("resize", () => {
339
+ terminalSizeVersion.value++;
340
+ });
341
+ const unsubscribeGraphicsOutput = subscribeTerminalGraphicsOutput(terminal, () => {
342
+ graphicsOutputVersion.value = getTerminalGraphicsOutputVersion(terminal);
343
+ });
344
+ const showControls = computed(() => props.controls && Math.floor(props.w) >= 14 && Math.floor(props.h) >= 2);
345
+ const resolvedControlsLayout = computed(() => props.controlsLayout === "cinema" && Math.floor(props.h) >= 3 ? "cinema" : "compact");
346
+ const controlRows = computed(() => showControls.value ? resolvedControlsLayout.value === "cinema" ? 2 : 1 : 0);
347
+ const videoHeight = computed(() => Math.max(1, Math.floor(props.h) - controlRows.value));
348
+ const resolvedMaxFps = computed(() => positiveNumber(props.maxFps, DEFAULT_MAX_FPS$1, 60));
349
+ const resolvedPixelSize = computed(() => {
350
+ const desiredWidth = Math.max(2, Math.floor(props.w) * 8);
351
+ const desiredHeight = Math.max(2, videoHeight.value * 16);
352
+ if (props.pixelWidth == null && props.pixelHeight == null) {
353
+ const scale = Math.min(1, DEFAULT_MAX_PIXEL_WIDTH / desiredWidth, DEFAULT_MAX_PIXEL_HEIGHT / desiredHeight);
354
+ return {
355
+ width: Math.max(2, Math.floor(desiredWidth * scale)),
356
+ height: Math.max(2, Math.floor(desiredHeight * scale))
357
+ };
358
+ }
359
+ if (props.pixelWidth != null && props.pixelHeight == null) {
360
+ const width = positiveInt(props.pixelWidth, desiredWidth, MAX_PIXEL_DIMENSION);
361
+ const inferredHeight = width * desiredHeight / desiredWidth;
362
+ const scale = Math.min(1, MAX_PIXEL_DIMENSION / inferredHeight);
363
+ return {
364
+ width: Math.max(2, Math.floor(width * scale)),
365
+ height: Math.max(2, Math.floor(inferredHeight * scale))
366
+ };
367
+ }
368
+ if (props.pixelWidth == null && props.pixelHeight != null) {
369
+ const height = positiveInt(props.pixelHeight, desiredHeight, MAX_PIXEL_DIMENSION);
370
+ const inferredWidth = height * desiredWidth / desiredHeight;
371
+ const scale = Math.min(1, MAX_PIXEL_DIMENSION / inferredWidth);
372
+ return {
373
+ width: Math.max(2, Math.floor(inferredWidth * scale)),
374
+ height: Math.max(2, Math.floor(height * scale))
375
+ };
376
+ }
377
+ return {
378
+ width: positiveInt(props.pixelWidth, desiredWidth, MAX_PIXEL_DIMENSION),
379
+ height: positiveInt(props.pixelHeight, desiredHeight, MAX_PIXEL_DIMENSION)
380
+ };
381
+ });
382
+ const resolvedPixelWidth = computed(() => resolvedPixelSize.value.width);
383
+ const resolvedPixelHeight = computed(() => resolvedPixelSize.value.height);
384
+ const paintable = computed(() => {
385
+ terminalSizeVersion.value;
386
+ if (!visible.value) return false;
387
+ const size = terminal.size();
388
+ const terminalRect = {
389
+ x: 0,
390
+ y: 0,
391
+ w: Math.max(0, Math.floor(size.cols)),
392
+ h: Math.max(0, Math.floor(size.rows))
393
+ };
394
+ const clip = layout.clipRect ? intersectRect(layout.clipRect, terminalRect) : terminalRect;
395
+ if (!clip) return false;
396
+ const rect = translateRect({
397
+ x: props.x,
398
+ y: props.y,
399
+ w: props.w,
400
+ h: videoHeight.value
401
+ }, layout.originX, layout.originY);
402
+ const visibleRect = intersectRect(rect, clip);
403
+ return Boolean(visibleRect && visibleRect.w > 0 && visibleRect.h > 0);
404
+ });
405
+ const graphicsProtocol = computed(() => {
406
+ graphicsOutputVersion.value;
407
+ return getTerminalGraphicsOutput(terminal)?.capabilities.preferredProtocol ?? null;
408
+ });
409
+ const preferredFormat = computed(() => graphicsProtocol.value === "kitty" || graphicsProtocol.value === "iterm2" ? "png" : "gray8");
410
+ const resolvedDecodeFps = computed(() => preferredFormat.value === "gray8" ? Math.min(MAX_GRAY8_FPS, resolvedMaxFps.value) : resolvedMaxFps.value);
411
+ const resolvedDecodeWidth = computed(() => preferredFormat.value === "gray8" ? positiveInt(Math.ceil(props.w / 2), 1, MAX_GRAY8_WIDTH) : resolvedPixelWidth.value);
412
+ const resolvedDecodeHeight = computed(() => preferredFormat.value === "gray8" ? positiveInt(videoHeight.value, 1, MAX_GRAY8_HEIGHT) : resolvedPixelHeight.value);
413
+ const resolvedDurationMs = computed(() => optionalDuration(props.durationMs) ?? discoveredDurationMs.value);
414
+ const effectivePaused = computed(() => props.paused ?? internalPaused.value);
415
+ const effectivePlaybackRate = computed(() => props.playbackRate == null ? internalPlaybackRate.value : playbackRate(props.playbackRate));
416
+ const controlTimestampMs = computed(() => seekPreviewMs.value ?? displayedTimestampMs.value);
417
+ const timeHourDigits = computed(() => {
418
+ const duration = resolvedDurationMs.value;
419
+ if (!duration || duration < 36e5) return 0;
420
+ return String(Math.floor(duration / 36e5)).length;
421
+ });
422
+ const currentTimeText = computed(() => formatPlaybackTime(controlTimestampMs.value, timeHourDigits.value));
423
+ const durationTimeText = computed(() => formatPlaybackTime(resolvedDurationMs.value, timeHourDigits.value));
424
+ const controlLayout = computed(() => {
425
+ const width = Math.max(1, Math.floor(props.w));
426
+ const progressY = videoHeight.value;
427
+ const current = currentTimeText.value;
428
+ const duration = durationTimeText.value;
429
+ const timeWidth = Math.max(textCellWidth(current), textCellWidth(duration));
430
+ const ratesWidth = 8;
431
+ if (resolvedControlsLayout.value === "cinema") {
432
+ const ratesX$1 = Math.max(3, width - ratesWidth);
433
+ const timeText = `${current} / ${duration}`;
434
+ return {
435
+ width,
436
+ kind: "cinema",
437
+ progressX: 0,
438
+ progressY,
439
+ progressWidth: width,
440
+ actionY: progressY + 1,
441
+ ratesX: ratesX$1,
442
+ showAllRates: true,
443
+ showTimes: 3 + textCellWidth(timeText) < ratesX$1,
444
+ current,
445
+ duration,
446
+ timeText,
447
+ timeWidth,
448
+ currentX: 3,
449
+ totalX: 0
450
+ };
451
+ }
452
+ const ratesX = width - ratesWidth;
453
+ const progressX = timeWidth + 4;
454
+ const totalX = ratesX - timeWidth - 1;
455
+ const progressWidth = totalX - progressX - 1;
456
+ if (progressWidth >= 4) return {
457
+ width,
458
+ kind: "compact",
459
+ progressX,
460
+ progressY,
461
+ progressWidth,
462
+ actionY: progressY,
463
+ ratesX,
464
+ showAllRates: true,
465
+ showTimes: true,
466
+ current,
467
+ duration,
468
+ timeText: "",
469
+ timeWidth,
470
+ currentX: 3,
471
+ totalX
472
+ };
473
+ const compactRatesX = Math.max(4, width - 2);
474
+ return {
475
+ width,
476
+ kind: "compact",
477
+ progressX: 3,
478
+ progressY,
479
+ progressWidth: Math.max(1, compactRatesX - 4),
480
+ actionY: progressY,
481
+ ratesX: compactRatesX,
482
+ showAllRates: false,
483
+ showTimes: false,
484
+ current,
485
+ duration,
486
+ timeText: "",
487
+ timeWidth,
488
+ currentX: 0,
489
+ totalX: 0
490
+ };
491
+ });
492
+ const rateStyles = computed(() => PLAYBACK_RATES.map((rate) => rate === effectivePlaybackRate.value ? {
493
+ ...props.style,
494
+ inverse: true
495
+ } : props.style));
496
+ const remainingProgressStyle = computed(() => ({
497
+ ...props.style,
498
+ dim: true
499
+ }));
500
+ const progressThumbStyle = computed(() => ({
501
+ ...props.style,
502
+ inverse: true
503
+ }));
504
+ const progressSegments = computed(() => {
505
+ const { progressWidth } = controlLayout.value;
506
+ const duration = resolvedDurationMs.value;
507
+ if (!duration) return {
508
+ playedWidth: 0,
509
+ thumbWidth: 0,
510
+ remainingWidth: progressWidth
511
+ };
512
+ const ratio = Math.max(0, Math.min(1, controlTimestampMs.value / duration));
513
+ const marker = Math.round(ratio * Math.max(0, progressWidth - 1));
514
+ return {
515
+ playedWidth: marker,
516
+ thumbWidth: 1,
517
+ remainingWidth: progressWidth - marker - 1
518
+ };
519
+ });
520
+ const controlFill = computed(() => " ".repeat(controlLayout.value.width));
521
+ const progressGlyphs = computed(() => ({
522
+ played: "━".repeat(progressSegments.value.playedWidth),
523
+ remaining: "─".repeat(progressSegments.value.remainingWidth)
524
+ }));
525
+ const playbackEnabled = computed(() => alive && !effectivePaused.value && !graphicsActivity?.scrolling.value && props.src.trim().length > 0 && paintable.value);
526
+ const shouldPlay = computed(() => playbackEnabled.value && !seeking.value);
527
+ const visibleFallback = computed(() => playbackError.value ? `${props.fallback}: ${playbackError.value}` : props.fallback);
528
+ function timelineTimestamp(timestampMs) {
529
+ const duration = resolvedDurationMs.value;
530
+ if (!duration) return Math.max(0, timestampMs);
531
+ if (props.loop) return Math.max(0, timestampMs) % duration;
532
+ return Math.max(0, Math.min(duration, timestampMs));
533
+ }
534
+ function seekTimestamp(timestampMs) {
535
+ const duration = resolvedDurationMs.value;
536
+ if (!duration) return Math.max(0, timestampMs);
537
+ const end = props.loop ? Math.max(0, duration - 1) : duration;
538
+ return Math.max(0, Math.min(end, timestampMs));
539
+ }
540
+ function updateTimestamp(timestampMs, force = false) {
541
+ const next = timelineTimestamp(timestampMs);
542
+ lastTimestampMs = next;
543
+ if (!showControls.value) return;
544
+ const threshold = CONTROL_REFRESH_MS * effectivePlaybackRate.value;
545
+ if (force || next < displayedTimestampMs.value || Math.abs(next - displayedTimestampMs.value) >= threshold) displayedTimestampMs.value = next;
546
+ }
547
+ function commitFrame(frame, dropped) {
548
+ if (!alive) return;
549
+ droppedFrames += dropped;
550
+ frameRenderer.value = markRaw(createFrameRenderer(frame, props.w));
551
+ updateTimestamp(frame.timestampMs);
552
+ const durationMs = resolvedDurationMs.value;
553
+ emit("frame", {
554
+ timestampMs: frame.timestampMs,
555
+ pixelWidth: frame.pixelWidth,
556
+ pixelHeight: frame.pixelHeight,
557
+ droppedFrames,
558
+ ...durationMs ? { durationMs } : {},
559
+ playbackRate: effectivePlaybackRate.value
560
+ });
561
+ }
562
+ const mailbox = createFrameMailbox({
563
+ scheduler,
564
+ id: mailboxId,
565
+ reason: "stream",
566
+ priority: "low",
567
+ apply(frame, _context, meta) {
568
+ commitFrame(frame, meta.dropped);
569
+ if (pendingEndedGeneration === generation) {
570
+ pendingEndedGeneration = null;
571
+ emit("ended");
572
+ }
573
+ }
574
+ });
575
+ function stopPlayback() {
576
+ generation++;
577
+ pendingEndedGeneration = null;
578
+ const discardedPendingFrame = mailbox.hasPending();
579
+ mailbox.cancel();
580
+ if (discardedPendingFrame) {
581
+ lastQueuedBytes = null;
582
+ lastQueuedFormat = null;
583
+ lastQueuedFingerprint = void 0;
584
+ }
585
+ activeAbort?.abort();
586
+ activeAbort = null;
587
+ }
588
+ function resetQueuedFrameIdentity() {
589
+ lastSourceBytes = null;
590
+ lastQueuedBytes = null;
591
+ lastQueuedFormat = null;
592
+ lastQueuedFingerprint = void 0;
593
+ }
594
+ function setPaused(paused) {
595
+ if (effectivePaused.value === paused) return;
596
+ if (props.paused == null) internalPaused.value = paused;
597
+ emit("update:paused", paused);
598
+ }
599
+ function setPlaybackRate(rate) {
600
+ const next = playbackRate(rate);
601
+ if (effectivePlaybackRate.value === next) return;
602
+ if (props.playbackRate == null) internalPlaybackRate.value = next;
603
+ emit("update:playbackRate", next);
604
+ }
605
+ function applySeek(timestampMs, restart) {
606
+ const parsed = Number(timestampMs);
607
+ if (!Number.isFinite(parsed)) return;
608
+ stopPlayback();
609
+ resetQueuedFrameIdentity();
610
+ const next = seekTimestamp(parsed);
611
+ updateTimestamp(next, true);
612
+ seekPreviewMs.value = void 0;
613
+ const durationMs = resolvedDurationMs.value;
614
+ emit("seek", {
615
+ timestampMs: next,
616
+ ...durationMs ? { durationMs } : {}
617
+ });
618
+ if (restart) startPlayback(effectivePaused.value);
619
+ }
620
+ function controlCell(event) {
621
+ return {
622
+ x: Math.floor(event.cellX - layout.originX - props.x),
623
+ y: Math.floor(event.cellY - layout.originY - props.y)
624
+ };
625
+ }
626
+ function previewSeekAt(cellX) {
627
+ const duration = resolvedDurationMs.value;
628
+ if (!duration) return;
629
+ const { progressX, progressWidth } = controlLayout.value;
630
+ const offset = Math.max(0, Math.min(progressWidth - 1, cellX - progressX));
631
+ seekPreviewMs.value = progressWidth <= 1 ? 0 : offset / Math.max(1, progressWidth - 1) * duration;
632
+ }
633
+ function captureControlPointer(event) {
634
+ const native = event.nativeEvent;
635
+ const pointerId = native?.pointerId;
636
+ const target = native?.target;
637
+ if (pointerId == null || !target?.setPointerCapture) return;
638
+ target.setPointerCapture(pointerId);
639
+ controlPointerCapture = {
640
+ pointerId,
641
+ target
642
+ };
643
+ }
644
+ function releaseControlPointer() {
645
+ if (!controlPointerCapture) return;
646
+ controlPointerCapture.target.releasePointerCapture?.(controlPointerCapture.pointerId);
647
+ controlPointerCapture = null;
648
+ }
649
+ function rateAt(cellX) {
650
+ const { ratesX, showAllRates } = controlLayout.value;
651
+ const offset = cellX - ratesX;
652
+ if (!showAllRates) {
653
+ if (offset < 0 || offset >= 2) return void 0;
654
+ const index = PLAYBACK_RATES.indexOf(effectivePlaybackRate.value);
655
+ return PLAYBACK_RATES[(index + 1) % PLAYBACK_RATES.length];
656
+ }
657
+ if (offset < 0 || offset % 3 >= 2) return void 0;
658
+ return PLAYBACK_RATES[Math.floor(offset / 3)];
659
+ }
660
+ function onInteractivePointerDown(event) {
661
+ if (event.button != null && event.button !== 0) return;
662
+ const { x: cellX, y: cellY } = controlCell(event);
663
+ if (cellY < videoHeight.value) {
664
+ suppressNextVideoClick = false;
665
+ return;
666
+ }
667
+ if (!showControls.value) return;
668
+ const { actionY, progressY, progressX, progressWidth } = controlLayout.value;
669
+ const rate = rateAt(cellX);
670
+ if (cellY === actionY && cellX <= 1) {
671
+ event.preventDefault();
672
+ event.stopPropagation();
673
+ suppressNextVideoClick = true;
674
+ setPaused(!effectivePaused.value);
675
+ return;
676
+ }
677
+ if (cellY === actionY && rate) {
678
+ event.preventDefault();
679
+ event.stopPropagation();
680
+ suppressNextVideoClick = true;
681
+ setPlaybackRate(rate);
682
+ return;
683
+ }
684
+ if (cellY !== progressY || !resolvedDurationMs.value || cellX < progressX || cellX >= progressX + progressWidth) return;
685
+ event.preventDefault();
686
+ event.stopPropagation();
687
+ suppressNextVideoClick = true;
688
+ seeking.value = true;
689
+ stopPlayback();
690
+ previewSeekAt(cellX);
691
+ captureControlPointer(event);
692
+ }
693
+ function onInteractivePointerMove(event) {
694
+ if (!seeking.value) return;
695
+ if (event.buttons === 0) {
696
+ onInteractivePointerUp(event);
697
+ return;
698
+ }
699
+ event.preventDefault();
700
+ event.stopPropagation();
701
+ previewSeekAt(controlCell(event).x);
702
+ }
703
+ function onInteractivePointerUp(event) {
704
+ if (!seeking.value) return;
705
+ event.preventDefault();
706
+ event.stopPropagation();
707
+ previewSeekAt(controlCell(event).x);
708
+ const timestampMs = seekPreviewMs.value ?? lastTimestampMs;
709
+ applySeek(timestampMs, false);
710
+ seeking.value = false;
711
+ releaseControlPointer();
712
+ startPlayback(effectivePaused.value);
713
+ }
714
+ function onVideoClick(event) {
715
+ if (event.button != null && event.button !== 0) return;
716
+ if (suppressNextVideoClick) {
717
+ suppressNextVideoClick = false;
718
+ event.preventDefault();
719
+ event.stopPropagation();
720
+ return;
721
+ }
722
+ if (controlCell(event).y >= videoHeight.value) return;
723
+ event.preventDefault();
724
+ event.stopPropagation();
725
+ setPaused(!effectivePaused.value);
726
+ }
727
+ function finishControlSeek() {
728
+ if (!seeking.value) return;
729
+ const timestampMs = seekPreviewMs.value ?? lastTimestampMs;
730
+ applySeek(timestampMs, false);
731
+ seeking.value = false;
732
+ releaseControlPointer();
733
+ startPlayback(effectivePaused.value);
734
+ }
735
+ function onControlKeydown(event) {
736
+ if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
737
+ event.preventDefault();
738
+ event.stopPropagation();
739
+ setPaused(!effectivePaused.value);
740
+ return;
741
+ }
742
+ if (event.key === "ArrowUp" || event.key === "ArrowDown") {
743
+ event.preventDefault();
744
+ event.stopPropagation();
745
+ const index = PLAYBACK_RATES.indexOf(effectivePlaybackRate.value);
746
+ const offset = event.key === "ArrowUp" ? 1 : -1;
747
+ setPlaybackRate(PLAYBACK_RATES[Math.max(0, Math.min(2, index + offset))]);
748
+ return;
749
+ }
750
+ if (!event.repeat && (event.key === "Home" || event.key === "End") && resolvedDurationMs.value) {
751
+ event.preventDefault();
752
+ event.stopPropagation();
753
+ applySeek(event.key === "Home" ? 0 : resolvedDurationMs.value, true);
754
+ return;
755
+ }
756
+ if (event.key === "1" || event.key === "2" || event.key === "3") {
757
+ event.preventDefault();
758
+ event.stopPropagation();
759
+ setPlaybackRate(Number(event.key));
760
+ }
761
+ }
762
+ function queueFrame(frame, expectedFormat) {
763
+ let normalized = normalizeFrame(frame);
764
+ if (normalized.format !== expectedFormat) throw new Error(`TVideo frame source did not provide requested ${expectedFormat} frames`);
765
+ if (normalized.durationMs != null && normalized.durationMs !== discoveredDurationMs.value) discoveredDurationMs.value = normalized.durationMs;
766
+ normalized = {
767
+ ...normalized,
768
+ timestampMs: timelineTimestamp(normalized.timestampMs)
769
+ };
770
+ const sourceBytes = frameBytes(normalized);
771
+ const reusesSourceBuffer = sourceBytes === lastSourceBytes;
772
+ lastSourceBytes = sourceBytes;
773
+ const stableFrame = reusesSourceBuffer ? normalized.format === "gray8" ? {
774
+ ...normalized,
775
+ pixels: new Uint8Array(normalized.pixels)
776
+ } : {
777
+ ...normalized,
778
+ png: new Uint8Array(normalized.png)
779
+ } : normalized;
780
+ const bytes = frameBytes(stableFrame);
781
+ const sameFingerprint = normalized.format === lastQueuedFormat && normalized.fingerprint != null && lastQueuedFingerprint != null && normalized.fingerprint === lastQueuedFingerprint;
782
+ const canCompareBytes = normalized.fingerprint == null && lastQueuedFingerprint == null && !reusesSourceBuffer;
783
+ if (sameFingerprint || canCompareBytes && normalized.format === lastQueuedFormat && lastQueuedBytes && sameBytes(lastQueuedBytes, sourceBytes)) {
784
+ lastQueuedBytes = bytes;
785
+ lastQueuedFormat = normalized.format;
786
+ lastQueuedFingerprint = normalized.fingerprint;
787
+ updateTimestamp(normalized.timestampMs);
788
+ if (mailbox.hasPending()) mailbox.replacePending(stableFrame);
789
+ return;
790
+ }
791
+ lastQueuedBytes = bytes;
792
+ lastQueuedFormat = normalized.format;
793
+ lastQueuedFingerprint = normalized.fingerprint;
794
+ if (!mailbox.queue(stableFrame)) {
795
+ commitFrame(stableFrame, 0);
796
+ scheduler.invalidate({
797
+ priority: "low",
798
+ reason: "stream"
799
+ });
800
+ }
801
+ }
802
+ function startPlayback(oneFrame = false) {
803
+ stopPlayback();
804
+ if (!shouldPlay.value && !(oneFrame && alive && props.src.trim().length > 0 && paintable.value && !graphicsActivity?.scrolling.value)) return;
805
+ const currentGeneration = generation;
806
+ const abort = new AbortController();
807
+ activeAbort = abort;
808
+ playbackError.value = "";
809
+ const source = props.frameSource;
810
+ const format = preferredFormat.value;
811
+ const context = {
812
+ src: props.src,
813
+ signal: abort.signal,
814
+ maxFps: resolvedDecodeFps.value,
815
+ pixelWidth: resolvedDecodeWidth.value,
816
+ pixelHeight: resolvedDecodeHeight.value,
817
+ startAtMs: lastTimestampMs,
818
+ preferredFormat: format,
819
+ playbackRate: effectivePlaybackRate.value,
820
+ loop: props.loop
821
+ };
822
+ (async () => {
823
+ try {
824
+ const frames = await source(context);
825
+ for await (const frame of frames) {
826
+ if (!alive || abort.signal.aborted || currentGeneration !== generation) return;
827
+ queueFrame(frame, format);
828
+ if (oneFrame) return;
829
+ }
830
+ if (!alive || abort.signal.aborted || currentGeneration !== generation) return;
831
+ if (mailbox.hasPending()) pendingEndedGeneration = currentGeneration;
832
+ else emit("ended");
833
+ } catch (error) {
834
+ if (!alive || abort.signal.aborted || currentGeneration !== generation) return;
835
+ playbackError.value = errorMessage(error);
836
+ emit("error", error);
837
+ scheduler.invalidate({
838
+ priority: "low",
839
+ reason: "data"
840
+ });
841
+ } finally {
842
+ if (activeAbort === abort) activeAbort = null;
843
+ }
844
+ })();
845
+ }
846
+ watch(() => props.paused, (paused) => {
847
+ if (paused != null) internalPaused.value = paused;
848
+ });
849
+ watch(() => props.playbackRate, (rate) => {
850
+ if (rate != null) internalPlaybackRate.value = playbackRate(rate);
851
+ });
852
+ watch([
853
+ () => props.src,
854
+ () => props.frameSource,
855
+ playbackEnabled,
856
+ resolvedDecodeFps,
857
+ resolvedDecodeWidth,
858
+ resolvedDecodeHeight,
859
+ preferredFormat,
860
+ effectivePlaybackRate,
861
+ () => props.loop
862
+ ], (next, previous) => {
863
+ const sourceChanged = !previous || next[0] !== previous[0] || next[1] !== previous[1];
864
+ const formatChanged = !previous || next[6] !== previous[6];
865
+ if (sourceChanged || formatChanged) {
866
+ resetQueuedFrameIdentity();
867
+ if (sourceChanged) {
868
+ lastTimestampMs = 0;
869
+ displayedTimestampMs.value = 0;
870
+ discoveredDurationMs.value = void 0;
871
+ seekPreviewMs.value = void 0;
872
+ seeking.value = false;
873
+ suppressNextVideoClick = false;
874
+ releaseControlPointer();
875
+ droppedFrames = 0;
876
+ }
877
+ playbackError.value = "";
878
+ frameRenderer.value = void 0;
879
+ }
880
+ startPlayback();
881
+ }, {
882
+ immediate: true,
883
+ flush: "post"
884
+ });
885
+ onBeforeUnmount(() => {
886
+ alive = false;
887
+ stopPlayback();
888
+ releaseControlPointer();
889
+ mailbox.dispose();
890
+ unsubscribeTerminalResize();
891
+ unsubscribeGraphicsOutput();
892
+ });
893
+ return () => {
894
+ const children = [h(TAgentTerminalGraphic, {
895
+ x: props.x,
896
+ y: props.y,
897
+ w: props.w,
898
+ h: videoHeight.value,
899
+ zIndex: props.zIndex,
900
+ content: props.src.trim() ? props.src : "video",
901
+ fallback: visibleFallback.value,
902
+ style: props.style,
903
+ clear: props.clear,
904
+ final: false,
905
+ streaming: true,
906
+ renderer: frameRenderer.value,
907
+ placementKey,
908
+ preserveRawWhileRendering: true,
909
+ ignoreSamePlaneRawCoverage: true,
910
+ suspended: !paintable.value
911
+ })];
912
+ if (props.controls) children.push(h(TView, {
913
+ x: props.x,
914
+ y: props.y,
915
+ w: Math.max(0, Math.floor(props.w)),
916
+ h: Math.max(0, Math.floor(props.h)),
917
+ zIndex: props.zIndex,
918
+ focusable: true,
919
+ selectable: false,
920
+ onClick: onVideoClick,
921
+ onPointerdown: onInteractivePointerDown,
922
+ onPointermove: onInteractivePointerMove,
923
+ onPointerup: onInteractivePointerUp,
924
+ onKeydown: onControlKeydown,
925
+ onBlur: finishControlSeek
926
+ }, () => {
927
+ if (!showControls.value) return [];
928
+ const layout$1 = controlLayout.value;
929
+ const segments = progressSegments.value;
930
+ const controls = Array.from({ length: controlRows.value }, (_, index) => h(TText, {
931
+ x: 0,
932
+ y: videoHeight.value + index,
933
+ w: layout$1.width,
934
+ value: controlFill.value,
935
+ style: props.style
936
+ }));
937
+ controls.push(h(TText, {
938
+ x: 0,
939
+ y: layout$1.actionY,
940
+ w: 2,
941
+ value: effectivePaused.value ? "> " : "||",
942
+ style: props.style
943
+ }));
944
+ if (layout$1.showTimes) if (layout$1.kind === "cinema") controls.push(h(TText, {
945
+ x: layout$1.currentX,
946
+ y: layout$1.actionY,
947
+ w: textCellWidth(layout$1.timeText),
948
+ value: layout$1.timeText,
949
+ style: props.style
950
+ }));
951
+ else controls.push(h(TText, {
952
+ x: layout$1.currentX,
953
+ y: layout$1.actionY,
954
+ w: layout$1.timeWidth,
955
+ value: layout$1.current,
956
+ style: props.style
957
+ }), h(TText, {
958
+ x: layout$1.totalX,
959
+ y: layout$1.actionY,
960
+ w: layout$1.timeWidth,
961
+ value: layout$1.duration,
962
+ style: props.style
963
+ }));
964
+ if (segments.playedWidth > 0) controls.push(h(TText, {
965
+ x: layout$1.progressX,
966
+ y: layout$1.progressY,
967
+ w: segments.playedWidth,
968
+ value: progressGlyphs.value.played,
969
+ style: props.style
970
+ }));
971
+ if (segments.thumbWidth > 0) controls.push(h(TText, {
972
+ x: layout$1.progressX + segments.playedWidth,
973
+ y: layout$1.progressY,
974
+ w: 1,
975
+ value: "╋",
976
+ style: progressThumbStyle.value
977
+ }));
978
+ if (segments.remainingWidth > 0) controls.push(h(TText, {
979
+ x: layout$1.progressX + segments.playedWidth + segments.thumbWidth,
980
+ y: layout$1.progressY,
981
+ w: segments.remainingWidth,
982
+ value: progressGlyphs.value.remaining,
983
+ style: remainingProgressStyle.value
984
+ }));
985
+ if (layout$1.showAllRates) controls.push(...PLAYBACK_RATES.map((rate, index) => h(TText, {
986
+ x: layout$1.ratesX + index * 3,
987
+ y: layout$1.actionY,
988
+ w: 2,
989
+ value: `${rate}x`,
990
+ style: rateStyles.value[index]
991
+ })));
992
+ else controls.push(h(TText, {
993
+ x: layout$1.ratesX,
994
+ y: layout$1.actionY,
995
+ w: 2,
996
+ value: `${effectivePlaybackRate.value}x`,
997
+ style: rateStyles.value[PLAYBACK_RATES.indexOf(effectivePlaybackRate.value)]
998
+ }));
999
+ return controls;
1000
+ }));
1001
+ return h("span", rootProps, children);
1002
+ };
1003
+ }
1004
+ });
1005
+
1006
+ //#endregion
1007
+ //#region src/vue/components/T3DViewport.ts
1008
+ const DEFAULT_MAX_FPS = 24;
1009
+ const DEFAULT_AUTO_ROTATE_SPEED = .35;
1010
+ const DEFAULT_POINTER_SENSITIVITY = .055;
1011
+ const VELOCITY_DAMPING_PER_SECOND = 5.5;
1012
+ const ZOOM_DAMPING_PER_SECOND = 7;
1013
+ const MAX_ZOOM_VELOCITY = 3;
1014
+ const DEFAULT_MIN_ZOOM = .65;
1015
+ const DEFAULT_MAX_ZOOM = 1.65;
1016
+ const DEFAULT_ZOOM_SENSITIVITY = .1;
1017
+ const MAX_PITCH = Math.PI * .42;
1018
+ const t3DViewportProps = {
1019
+ x: {
1020
+ type: Number,
1021
+ required: true
1022
+ },
1023
+ y: {
1024
+ type: Number,
1025
+ required: true
1026
+ },
1027
+ w: {
1028
+ type: Number,
1029
+ required: true
1030
+ },
1031
+ h: {
1032
+ type: Number,
1033
+ required: true
1034
+ },
1035
+ zIndex: {
1036
+ type: Number,
1037
+ default: 0
1038
+ },
1039
+ renderer: {
1040
+ type: Object,
1041
+ required: true
1042
+ },
1043
+ paused: {
1044
+ type: Boolean,
1045
+ default: false
1046
+ },
1047
+ maxFps: {
1048
+ type: Number,
1049
+ default: DEFAULT_MAX_FPS
1050
+ },
1051
+ pixelWidth: {
1052
+ type: Number,
1053
+ default: void 0
1054
+ },
1055
+ pixelHeight: {
1056
+ type: Number,
1057
+ default: void 0
1058
+ },
1059
+ fallback: {
1060
+ type: String,
1061
+ default: "[3D viewport]"
1062
+ },
1063
+ style: {
1064
+ type: Object,
1065
+ default: void 0
1066
+ },
1067
+ clear: {
1068
+ type: Boolean,
1069
+ default: true
1070
+ },
1071
+ interactive: {
1072
+ type: Boolean,
1073
+ default: true
1074
+ },
1075
+ initialYaw: {
1076
+ type: Number,
1077
+ default: 0
1078
+ },
1079
+ initialPitch: {
1080
+ type: Number,
1081
+ default: 0
1082
+ },
1083
+ autoRotate: {
1084
+ type: Boolean,
1085
+ default: true
1086
+ },
1087
+ autoRotateSpeed: {
1088
+ type: Number,
1089
+ default: DEFAULT_AUTO_ROTATE_SPEED
1090
+ },
1091
+ pointerSensitivity: {
1092
+ type: Number,
1093
+ default: DEFAULT_POINTER_SENSITIVITY
1094
+ },
1095
+ initialZoom: {
1096
+ type: Number,
1097
+ default: 1
1098
+ },
1099
+ minZoom: {
1100
+ type: Number,
1101
+ default: DEFAULT_MIN_ZOOM
1102
+ },
1103
+ maxZoom: {
1104
+ type: Number,
1105
+ default: DEFAULT_MAX_ZOOM
1106
+ },
1107
+ zoomSensitivity: {
1108
+ type: Number,
1109
+ default: DEFAULT_ZOOM_SENSITIVITY
1110
+ }
1111
+ };
1112
+ function finite(value, fallback) {
1113
+ return Number.isFinite(value) ? value : fallback;
1114
+ }
1115
+ function clampPitch(value) {
1116
+ return Math.max(-MAX_PITCH, Math.min(MAX_PITCH, value));
1117
+ }
1118
+ function zoomBounds(minZoom, maxZoom) {
1119
+ const first = Math.max(.01, finite(minZoom, DEFAULT_MIN_ZOOM));
1120
+ const second = Math.max(.01, finite(maxZoom, DEFAULT_MAX_ZOOM));
1121
+ return first <= second ? {
1122
+ min: first,
1123
+ max: second
1124
+ } : {
1125
+ min: second,
1126
+ max: first
1127
+ };
1128
+ }
1129
+ function clampZoom(value, minZoom, maxZoom) {
1130
+ const { min, max } = zoomBounds(minZoom, maxZoom);
1131
+ return Math.max(min, Math.min(max, finite(value, 1)));
1132
+ }
1133
+ function normalizedWheelDelta(event) {
1134
+ const deltaY = Number(event.deltaY ?? 0);
1135
+ if (!Number.isFinite(deltaY) || deltaY === 0) return 0;
1136
+ if (event.deltaMode === 1) return deltaY;
1137
+ if (event.deltaMode === 2) return Math.max(-3, Math.min(3, deltaY * 3));
1138
+ if (event.deltaMode === 0) return deltaY / 100;
1139
+ return Number.isInteger(deltaY) && Math.abs(deltaY) <= 3 ? deltaY : deltaY / 100;
1140
+ }
1141
+ function nowMs() {
1142
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
1143
+ }
1144
+ function abortError() {
1145
+ const error = new Error("T3D viewport render aborted");
1146
+ error.name = "AbortError";
1147
+ return error;
1148
+ }
1149
+ function warnDev(message) {
1150
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
1151
+ if (nodeEnv !== "production") console.warn(message);
1152
+ }
1153
+ function waitFor(ms, signal) {
1154
+ if (signal.aborted || ms <= 0) return Promise.resolve();
1155
+ return new Promise((resolve) => {
1156
+ const timer = setTimeout(done, ms);
1157
+ function done() {
1158
+ clearTimeout(timer);
1159
+ signal.removeEventListener("abort", done);
1160
+ resolve();
1161
+ }
1162
+ signal.addEventListener("abort", done, { once: true });
1163
+ });
1164
+ }
1165
+ /**
1166
+ * Browser-safe interactive 3D viewport. Frames are pulled from a renderer and
1167
+ * delegated directly to TVideo, which owns frame coalescing and terminal output.
1168
+ *
1169
+ * @event frame Emitted after TVideo commits a rendered frame.
1170
+ * @event error Emitted when the renderer or TVideo frame handling fails.
1171
+ */
1172
+ const T3DViewport = defineComponent({
1173
+ name: "T3DViewport",
1174
+ props: t3DViewportProps,
1175
+ emits: {
1176
+ frame: (_event) => true,
1177
+ error: (_error) => true,
1178
+ objecthover: (_hit) => true,
1179
+ objectselect: (_hit) => true
1180
+ },
1181
+ setup(props, { emit, expose }) {
1182
+ const layout = useLayout();
1183
+ const renderer = props.renderer;
1184
+ let alive = true;
1185
+ let yaw = finite(props.initialYaw, 0);
1186
+ let pitch = clampPitch(finite(props.initialPitch, 0));
1187
+ let yawVelocity = 0;
1188
+ let pitchVelocity = 0;
1189
+ let pointerX = 0;
1190
+ let pointerY = 0;
1191
+ let pointerSpeed = 0;
1192
+ let hovering = false;
1193
+ let zoom = clampZoom(props.initialZoom, props.minZoom, props.maxZoom);
1194
+ let zoomVelocity = 0;
1195
+ let hoveredObject = null;
1196
+ let selectedObject = null;
1197
+ let actualPixelWidth = Math.max(1, Math.floor(props.pixelWidth ?? props.w));
1198
+ let actualPixelHeight = Math.max(1, Math.floor(props.pixelHeight ?? props.h));
1199
+ let pointer = null;
1200
+ let drag = null;
1201
+ let renderTail = Promise.resolve();
1202
+ function motionSnapshot() {
1203
+ return {
1204
+ yaw,
1205
+ pitch,
1206
+ yawVelocity,
1207
+ pitchVelocity,
1208
+ pointerX,
1209
+ pointerY,
1210
+ pointerSpeed,
1211
+ hovering,
1212
+ zoom,
1213
+ zoomVelocity,
1214
+ hoveredObjectId: hoveredObject?.objectId ?? null,
1215
+ selectedObjectId: selectedObject?.objectId ?? null
1216
+ };
1217
+ }
1218
+ async function renderFrame(context) {
1219
+ const previous = renderTail;
1220
+ let release = () => {};
1221
+ renderTail = new Promise((resolve) => {
1222
+ release = resolve;
1223
+ });
1224
+ await previous;
1225
+ try {
1226
+ if (!alive || context.signal.aborted) throw abortError();
1227
+ return await renderer.render(context);
1228
+ } finally {
1229
+ release();
1230
+ }
1231
+ }
1232
+ function sameHit(left, right) {
1233
+ return left?.objectId === right?.objectId;
1234
+ }
1235
+ function setHoveredObject(hit) {
1236
+ if (sameHit(hoveredObject, hit)) return;
1237
+ hoveredObject = hit;
1238
+ emit("objecthover", hit);
1239
+ }
1240
+ function setSelectedObject(hit) {
1241
+ if (sameHit(selectedObject, hit)) return;
1242
+ selectedObject = hit;
1243
+ emit("objectselect", hit);
1244
+ }
1245
+ function resetMotion() {
1246
+ yaw = finite(props.initialYaw, 0);
1247
+ pitch = clampPitch(finite(props.initialPitch, 0));
1248
+ yawVelocity = 0;
1249
+ pitchVelocity = 0;
1250
+ pointerX = 0;
1251
+ pointerY = 0;
1252
+ pointerSpeed = 0;
1253
+ zoom = clampZoom(props.initialZoom, props.minZoom, props.maxZoom);
1254
+ zoomVelocity = 0;
1255
+ setHoveredObject(null);
1256
+ setSelectedObject(null);
1257
+ pointer = null;
1258
+ drag = null;
1259
+ }
1260
+ expose({ resetMotion });
1261
+ function localCell(event) {
1262
+ return {
1263
+ x: Math.floor(event.cellX - layout.originX - props.x),
1264
+ y: Math.floor(event.cellY - layout.originY - props.y)
1265
+ };
1266
+ }
1267
+ function eventTime(event) {
1268
+ return Number.isFinite(event.timeStamp) ? event.timeStamp : nowMs();
1269
+ }
1270
+ function updatePointerPosition(point) {
1271
+ const width = Math.max(1, Math.floor(props.w));
1272
+ const height = Math.max(1, Math.floor(props.h));
1273
+ pointerX = Math.max(-1, Math.min(1, (point.x + .5) / width * 2 - 1));
1274
+ pointerY = Math.max(-1, Math.min(1, (point.y + .5) / height * 2 - 1));
1275
+ }
1276
+ function hitTest() {
1277
+ if (!renderer.hitTest) return null;
1278
+ try {
1279
+ return renderer.hitTest({
1280
+ pointerX,
1281
+ pointerY,
1282
+ pixelWidth: actualPixelWidth,
1283
+ pixelHeight: actualPixelHeight,
1284
+ cellWidth: Math.max(1, Math.floor(props.w)),
1285
+ cellHeight: Math.max(1, Math.floor(props.h)),
1286
+ motion: motionSnapshot()
1287
+ });
1288
+ } catch (error) {
1289
+ emit("error", error);
1290
+ return null;
1291
+ }
1292
+ }
1293
+ function updateHoveredObject() {
1294
+ if (renderer.hitTest) setHoveredObject(hitTest());
1295
+ }
1296
+ function onPointerEnter(event) {
1297
+ if (!props.interactive) return;
1298
+ const point = localCell(event);
1299
+ hovering = true;
1300
+ updatePointerPosition(point);
1301
+ pointer = {
1302
+ ...point,
1303
+ timeMs: eventTime(event)
1304
+ };
1305
+ updateHoveredObject();
1306
+ }
1307
+ function onPointerDown(event) {
1308
+ if (!props.interactive || event.button != null && event.button !== 0) return;
1309
+ event.preventDefault();
1310
+ event.stopPropagation();
1311
+ const point = localCell(event);
1312
+ const timeMs = eventTime(event);
1313
+ hovering = true;
1314
+ updatePointerPosition(point);
1315
+ yawVelocity = 0;
1316
+ pitchVelocity = 0;
1317
+ pointerSpeed = 0;
1318
+ pointer = {
1319
+ ...point,
1320
+ timeMs
1321
+ };
1322
+ drag = {
1323
+ ...point,
1324
+ timeMs,
1325
+ moved: false
1326
+ };
1327
+ updateHoveredObject();
1328
+ }
1329
+ function onPointerMove(event) {
1330
+ if (!props.interactive) return;
1331
+ hovering = true;
1332
+ const point = localCell(event);
1333
+ updatePointerPosition(point);
1334
+ const timeMs = eventTime(event);
1335
+ const previous = drag ?? pointer;
1336
+ pointer = {
1337
+ ...point,
1338
+ timeMs
1339
+ };
1340
+ if (!previous) return;
1341
+ const dtMs = Math.max(1, timeMs - previous.timeMs);
1342
+ const dx = point.x - previous.x;
1343
+ const dy = point.y - previous.y;
1344
+ const sensitivity = finite(props.pointerSensitivity, DEFAULT_POINTER_SENSITIVITY);
1345
+ const deltaYaw = dx * sensitivity;
1346
+ const deltaPitch = dy * sensitivity;
1347
+ pointerSpeed = Math.hypot(dx, dy) * 1e3 / dtMs;
1348
+ if (!drag) {
1349
+ if (renderer.hitTest) {
1350
+ yawVelocity = 0;
1351
+ pitchVelocity = 0;
1352
+ updateHoveredObject();
1353
+ return;
1354
+ }
1355
+ yawVelocity = deltaYaw * 650 / dtMs;
1356
+ pitchVelocity = deltaPitch * 450 / dtMs;
1357
+ return;
1358
+ }
1359
+ if (event.buttons === 0) {
1360
+ drag = null;
1361
+ return;
1362
+ }
1363
+ event.preventDefault();
1364
+ event.stopPropagation();
1365
+ yaw += deltaYaw;
1366
+ pitch = clampPitch(pitch + deltaPitch);
1367
+ yawVelocity = deltaYaw * 1e3 / dtMs;
1368
+ pitchVelocity = deltaPitch * 1e3 / dtMs;
1369
+ setHoveredObject(null);
1370
+ drag = {
1371
+ ...point,
1372
+ timeMs,
1373
+ moved: drag.moved || dx !== 0 || dy !== 0
1374
+ };
1375
+ }
1376
+ function onPointerUp(event) {
1377
+ if (!drag) return;
1378
+ event.preventDefault();
1379
+ event.stopPropagation();
1380
+ const moved = drag.moved;
1381
+ drag = null;
1382
+ if (!moved && renderer.hitTest) {
1383
+ const hit = hitTest();
1384
+ setHoveredObject(hit);
1385
+ setSelectedObject(hit);
1386
+ } else updateHoveredObject();
1387
+ }
1388
+ function onPointerLeave(event) {
1389
+ if (drag) {
1390
+ event.preventDefault();
1391
+ event.stopPropagation();
1392
+ drag = null;
1393
+ }
1394
+ hovering = false;
1395
+ pointer = null;
1396
+ setHoveredObject(null);
1397
+ }
1398
+ function onWheel(event) {
1399
+ if (!props.interactive) return;
1400
+ const delta = normalizedWheelDelta(event);
1401
+ const sensitivity = Math.max(0, finite(props.zoomSensitivity, DEFAULT_ZOOM_SENSITIVITY));
1402
+ if (delta === 0 || sensitivity === 0) return;
1403
+ event.preventDefault();
1404
+ event.stopPropagation();
1405
+ zoomVelocity = Math.max(-MAX_ZOOM_VELOCITY, Math.min(MAX_ZOOM_VELOCITY, zoomVelocity - delta * sensitivity * 6));
1406
+ }
1407
+ function advanceZoom(deltaMs) {
1408
+ if (deltaMs <= 0 || zoomVelocity === 0) return;
1409
+ const dt = Math.min(deltaMs, 100) / 1e3;
1410
+ const next = zoom + zoomVelocity * dt;
1411
+ zoom = clampZoom(next, props.minZoom, props.maxZoom);
1412
+ if (zoom !== next) {
1413
+ zoomVelocity = 0;
1414
+ return;
1415
+ }
1416
+ zoomVelocity *= Math.exp(-ZOOM_DAMPING_PER_SECOND * dt);
1417
+ if (Math.abs(zoomVelocity) < .001) zoomVelocity = 0;
1418
+ }
1419
+ function advanceMotion(deltaMs) {
1420
+ if (deltaMs <= 0) return;
1421
+ advanceZoom(deltaMs);
1422
+ if (drag) return;
1423
+ const dt = Math.min(deltaMs, 100) / 1e3;
1424
+ if (hovering && !renderer.hitTest) {
1425
+ const targetPitch = clampPitch(-pointerY * .36);
1426
+ pitchVelocity += (targetPitch - pitch) * 4.5 * dt;
1427
+ } else if (!hovering) {
1428
+ pointerX *= Math.exp(-7 * dt);
1429
+ pointerY *= Math.exp(-7 * dt);
1430
+ }
1431
+ yaw += yawVelocity * dt;
1432
+ pitch = clampPitch(pitch + pitchVelocity * dt);
1433
+ const inspectingObject = Boolean(renderer.hitTest && (hoveredObject || selectedObject));
1434
+ if (props.autoRotate && !inspectingObject) {
1435
+ const baseSpeed = finite(props.autoRotateSpeed, DEFAULT_AUTO_ROTATE_SPEED);
1436
+ const steering = hovering ? .85 + pointerX * 1.75 : 1;
1437
+ yaw += baseSpeed * steering * dt;
1438
+ }
1439
+ const decay = Math.exp(-VELOCITY_DAMPING_PER_SECOND * dt);
1440
+ yawVelocity *= decay;
1441
+ pitchVelocity *= decay;
1442
+ pointerSpeed *= decay;
1443
+ }
1444
+ const frameSource = async function* (videoContext) {
1445
+ const startedAt = nowMs();
1446
+ let previousAt = startedAt;
1447
+ let frame = 0;
1448
+ const frameIntervalMs = 1e3 / Math.max(1, finite(videoContext.maxFps, DEFAULT_MAX_FPS));
1449
+ while (alive && !videoContext.signal.aborted) {
1450
+ const pulledAt = nowMs();
1451
+ const deltaMs = frame === 0 ? 0 : Math.max(0, pulledAt - previousAt);
1452
+ advanceMotion(deltaMs);
1453
+ actualPixelWidth = videoContext.pixelWidth;
1454
+ actualPixelHeight = videoContext.pixelHeight;
1455
+ const result = await renderFrame({
1456
+ timeMs: Math.max(0, pulledAt - startedAt),
1457
+ deltaMs,
1458
+ frame,
1459
+ pixelWidth: videoContext.pixelWidth,
1460
+ pixelHeight: videoContext.pixelHeight,
1461
+ format: videoContext.preferredFormat,
1462
+ signal: videoContext.signal,
1463
+ motion: motionSnapshot()
1464
+ });
1465
+ if (!alive || videoContext.signal.aborted) return;
1466
+ yield result;
1467
+ frame++;
1468
+ previousAt = pulledAt;
1469
+ await waitFor(frameIntervalMs - (nowMs() - pulledAt), videoContext.signal);
1470
+ }
1471
+ };
1472
+ watch(() => props.renderer, (next) => {
1473
+ if (next === renderer) return;
1474
+ warnDev("[vue-tui] T3DViewport renderer is init-only. Remount T3DViewport to replace it.");
1475
+ });
1476
+ watch(() => props.interactive, (interactive) => {
1477
+ if (!interactive) {
1478
+ hovering = false;
1479
+ pointer = null;
1480
+ drag = null;
1481
+ setHoveredObject(null);
1482
+ setSelectedObject(null);
1483
+ }
1484
+ });
1485
+ onBeforeUnmount(() => {
1486
+ alive = false;
1487
+ pointer = null;
1488
+ drag = null;
1489
+ });
1490
+ onUnmounted(() => renderer.dispose?.());
1491
+ return () => h("span", [h(TVideo, {
1492
+ x: props.x,
1493
+ y: props.y,
1494
+ w: props.w,
1495
+ h: props.h,
1496
+ zIndex: props.zIndex,
1497
+ src: "t3d://viewport",
1498
+ frameSource,
1499
+ paused: props.paused,
1500
+ maxFps: props.maxFps,
1501
+ pixelWidth: props.pixelWidth,
1502
+ pixelHeight: props.pixelHeight,
1503
+ fallback: props.fallback,
1504
+ style: props.style,
1505
+ clear: props.clear,
1506
+ onFrame: (event) => emit("frame", event),
1507
+ onError: (error) => emit("error", error)
1508
+ }), props.interactive ? h(TView, {
1509
+ x: props.x,
1510
+ y: props.y,
1511
+ w: Math.max(0, Math.floor(props.w)),
1512
+ h: Math.max(0, Math.floor(props.h)),
1513
+ zIndex: props.zIndex + 1,
1514
+ selectable: false,
1515
+ onPointerenter: onPointerEnter,
1516
+ onPointermove: onPointerMove,
1517
+ onPointerleave: onPointerLeave,
1518
+ onPointerdown: onPointerDown,
1519
+ onPointerup: onPointerUp,
1520
+ onWheel
1521
+ }) : null]);
1522
+ }
1523
+ });
1524
+
1525
+ //#endregion
11
1526
  //#region src/vue/components/TCharts.ts
12
1527
  const DEFAULT_HEATMAP_LEVEL_STYLES = Object.freeze([
13
1528
  Object.freeze({ fg: "#9be9a8" }),
@@ -1592,4 +3107,4 @@ function restoreTLogViewSessionState(bindings, state) {
1592
3107
  }
1593
3108
 
1594
3109
  //#endregion
1595
- export { TCandlestickChart, TContributionGraph, TLineChart, TLogLinksPanel, TLogMinimap, TLogScrollbar, TLogSearchBar, TLogSearchPager, TLogSearchResults, TLogView, TLogVirtualLinksPanel, TLogVirtualSearchResults, TPieChart, TTranscriptView, TVirtualList, captureTLogViewSessionState, createAppendOnlyLogStore, createTLogDensityBucketsFromMarkers, createTLogLevelPlugin, createTLogLineMatcherPlugin, createTLogLinkActionPlugin, createTLogOsc8LinkPlugin, createTLogUrlPlugin, createTLogViewSessionStore, deserializeTLogViewSessionState, detectTLogUrls, dispatchTLogPluginLinkAction, getTLogPluginMetadata, handleTLogKeymapEvent, matchesTLogKeyBinding, parseTLogAnnotatedText, resolveTLogLinksPanelTheme, resolveTLogMinimapTheme, resolveTLogScrollbarTheme, resolveTLogSearchBarTheme, resolveTLogSearchPagerTheme, resolveTLogSearchResultsTheme, resolveTLogViewTheme, restoreTLogViewSessionState, sanitizeTerminalHref, serializeTLogViewSessionState, stripTLogAnsiText, tlogDarkPreset, tlogDarkTheme, tlogDefaultKeymap, tlogDefaultPreset, tlogDefaultTheme, tlogHighContrastKeymap, tlogHighContrastPreset, tlogHighContrastTheme, toTLogExternalLinkFromVisibleLink, useTLogLinkController, useTLogRetainedIndex, useTLogSearchController, useTLogSearchResultsPage, useTLogVirtualSearchResults };
3110
+ export { T3DViewport, TCandlestickChart, TContributionGraph, TLineChart, TLogLinksPanel, TLogMinimap, TLogScrollbar, TLogSearchBar, TLogSearchPager, TLogSearchResults, TLogView, TLogVirtualLinksPanel, TLogVirtualSearchResults, TPieChart, TTranscriptView, TVideo, TVirtualList, captureTLogViewSessionState, createAppendOnlyLogStore, createTLogDensityBucketsFromMarkers, createTLogLevelPlugin, createTLogLineMatcherPlugin, createTLogLinkActionPlugin, createTLogOsc8LinkPlugin, createTLogUrlPlugin, createTLogViewSessionStore, deserializeTLogViewSessionState, detectTLogUrls, dispatchTLogPluginLinkAction, getTLogPluginMetadata, handleTLogKeymapEvent, matchesTLogKeyBinding, parseTLogAnnotatedText, resolveTLogLinksPanelTheme, resolveTLogMinimapTheme, resolveTLogScrollbarTheme, resolveTLogSearchBarTheme, resolveTLogSearchPagerTheme, resolveTLogSearchResultsTheme, resolveTLogViewTheme, restoreTLogViewSessionState, sanitizeTerminalHref, serializeTLogViewSessionState, stripTLogAnsiText, tlogDarkPreset, tlogDarkTheme, tlogDefaultKeymap, tlogDefaultPreset, tlogDefaultTheme, tlogHighContrastKeymap, tlogHighContrastPreset, tlogHighContrastTheme, toTLogExternalLinkFromVisibleLink, useTLogLinkController, useTLogRetainedIndex, useTLogSearchController, useTLogSearchResultsPage, useTLogVirtualSearchResults };