@termaxjs/web-react 0.1.1

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 (134) hide show
  1. package/LICENSE +201 -0
  2. package/dist/TermaxTerminal.d.ts +16 -0
  3. package/dist/TermaxTerminal.d.ts.map +1 -0
  4. package/dist/TermaxTerminal.js +11 -0
  5. package/dist/TermaxTerminal.js.map +1 -0
  6. package/dist/TerminalContext.d.ts +22 -0
  7. package/dist/TerminalContext.d.ts.map +1 -0
  8. package/dist/TerminalContext.js +10 -0
  9. package/dist/TerminalContext.js.map +1 -0
  10. package/dist/TerminalContextMenu.d.ts +15 -0
  11. package/dist/TerminalContextMenu.d.ts.map +1 -0
  12. package/dist/TerminalContextMenu.js +138 -0
  13. package/dist/TerminalContextMenu.js.map +1 -0
  14. package/dist/TerminalDropZone.d.ts +10 -0
  15. package/dist/TerminalDropZone.d.ts.map +1 -0
  16. package/dist/TerminalDropZone.js +40 -0
  17. package/dist/TerminalDropZone.js.map +1 -0
  18. package/dist/TerminalRoot.d.ts +10 -0
  19. package/dist/TerminalRoot.d.ts.map +1 -0
  20. package/dist/TerminalRoot.js +38 -0
  21. package/dist/TerminalRoot.js.map +1 -0
  22. package/dist/TerminalScreen.d.ts +7 -0
  23. package/dist/TerminalScreen.d.ts.map +1 -0
  24. package/dist/TerminalScreen.js +30 -0
  25. package/dist/TerminalScreen.js.map +1 -0
  26. package/dist/TerminalSearch.d.ts +8 -0
  27. package/dist/TerminalSearch.d.ts.map +1 -0
  28. package/dist/TerminalSearch.js +122 -0
  29. package/dist/TerminalSearch.js.map +1 -0
  30. package/dist/TerminalToolbar.d.ts +10 -0
  31. package/dist/TerminalToolbar.d.ts.map +1 -0
  32. package/dist/TerminalToolbar.js +34 -0
  33. package/dist/TerminalToolbar.js.map +1 -0
  34. package/dist/TerminalView.d.ts +28 -0
  35. package/dist/TerminalView.d.ts.map +1 -0
  36. package/dist/TerminalView.js +148 -0
  37. package/dist/TerminalView.js.map +1 -0
  38. package/dist/components/FileTree.d.ts +8 -0
  39. package/dist/components/FileTree.d.ts.map +1 -0
  40. package/dist/components/FileTree.js +19 -0
  41. package/dist/components/FileTree.js.map +1 -0
  42. package/dist/components/GitCommitGraph.d.ts +16 -0
  43. package/dist/components/GitCommitGraph.d.ts.map +1 -0
  44. package/dist/components/GitCommitGraph.js +11 -0
  45. package/dist/components/GitCommitGraph.js.map +1 -0
  46. package/dist/components/SpacesBar.d.ts +8 -0
  47. package/dist/components/SpacesBar.d.ts.map +1 -0
  48. package/dist/components/SpacesBar.js +12 -0
  49. package/dist/components/SpacesBar.js.map +1 -0
  50. package/dist/explorer/HeadlessFileTree.d.ts +28 -0
  51. package/dist/explorer/HeadlessFileTree.d.ts.map +1 -0
  52. package/dist/explorer/HeadlessFileTree.js +50 -0
  53. package/dist/explorer/HeadlessFileTree.js.map +1 -0
  54. package/dist/git/HeadlessGitGraph.d.ts +22 -0
  55. package/dist/git/HeadlessGitGraph.d.ts.map +1 -0
  56. package/dist/git/HeadlessGitGraph.js +13 -0
  57. package/dist/git/HeadlessGitGraph.js.map +1 -0
  58. package/dist/hooks/useTerminal.d.ts +27 -0
  59. package/dist/hooks/useTerminal.d.ts.map +1 -0
  60. package/dist/hooks/useTerminal.js +97 -0
  61. package/dist/hooks/useTerminal.js.map +1 -0
  62. package/dist/hooks/useTerminalDrop.d.ts +9 -0
  63. package/dist/hooks/useTerminalDrop.d.ts.map +1 -0
  64. package/dist/hooks/useTerminalDrop.js +59 -0
  65. package/dist/hooks/useTerminalDrop.js.map +1 -0
  66. package/dist/hooks/useTerminalSearch.d.ts +22 -0
  67. package/dist/hooks/useTerminalSearch.d.ts.map +1 -0
  68. package/dist/hooks/useTerminalSearch.js +89 -0
  69. package/dist/hooks/useTerminalSearch.js.map +1 -0
  70. package/dist/hooks/useTerminalSelection.d.ts +9 -0
  71. package/dist/hooks/useTerminalSelection.d.ts.map +1 -0
  72. package/dist/hooks/useTerminalSelection.js +46 -0
  73. package/dist/hooks/useTerminalSelection.js.map +1 -0
  74. package/dist/index.d.ts +29 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +28 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/layout/HeadlessFloatingPane.d.ts +18 -0
  79. package/dist/layout/HeadlessFloatingPane.d.ts.map +1 -0
  80. package/dist/layout/HeadlessFloatingPane.js +29 -0
  81. package/dist/layout/HeadlessFloatingPane.js.map +1 -0
  82. package/dist/palette/TerminalCommandPalette.d.ts +23 -0
  83. package/dist/palette/TerminalCommandPalette.d.ts.map +1 -0
  84. package/dist/palette/TerminalCommandPalette.js +105 -0
  85. package/dist/palette/TerminalCommandPalette.js.map +1 -0
  86. package/dist/panels/PanelContext.d.ts +25 -0
  87. package/dist/panels/PanelContext.d.ts.map +1 -0
  88. package/dist/panels/PanelContext.js +10 -0
  89. package/dist/panels/PanelContext.js.map +1 -0
  90. package/dist/panels/TerminalPanel.d.ts +17 -0
  91. package/dist/panels/TerminalPanel.d.ts.map +1 -0
  92. package/dist/panels/TerminalPanel.js +47 -0
  93. package/dist/panels/TerminalPanel.js.map +1 -0
  94. package/dist/panels/TerminalPanelGroup.d.ts +13 -0
  95. package/dist/panels/TerminalPanelGroup.d.ts.map +1 -0
  96. package/dist/panels/TerminalPanelGroup.js +167 -0
  97. package/dist/panels/TerminalPanelGroup.js.map +1 -0
  98. package/dist/panels/TerminalResizeHandle.d.ts +12 -0
  99. package/dist/panels/TerminalResizeHandle.d.ts.map +1 -0
  100. package/dist/panels/TerminalResizeHandle.js +56 -0
  101. package/dist/panels/TerminalResizeHandle.js.map +1 -0
  102. package/dist/pool/index.d.ts +2 -0
  103. package/dist/pool/index.d.ts.map +1 -0
  104. package/dist/pool/index.js +2 -0
  105. package/dist/pool/index.js.map +1 -0
  106. package/dist/pool/rendererPool.d.ts +118 -0
  107. package/dist/pool/rendererPool.d.ts.map +1 -0
  108. package/dist/pool/rendererPool.js +1281 -0
  109. package/dist/pool/rendererPool.js.map +1 -0
  110. package/dist/spaces/HeadlessSpacesBar.d.ts +21 -0
  111. package/dist/spaces/HeadlessSpacesBar.d.ts.map +1 -0
  112. package/dist/spaces/HeadlessSpacesBar.js +41 -0
  113. package/dist/spaces/HeadlessSpacesBar.js.map +1 -0
  114. package/dist/status/TerminalStatusBar.d.ts +21 -0
  115. package/dist/status/TerminalStatusBar.d.ts.map +1 -0
  116. package/dist/status/TerminalStatusBar.js +28 -0
  117. package/dist/status/TerminalStatusBar.js.map +1 -0
  118. package/dist/tabs/TerminalTab.d.ts +15 -0
  119. package/dist/tabs/TerminalTab.d.ts.map +1 -0
  120. package/dist/tabs/TerminalTab.js +57 -0
  121. package/dist/tabs/TerminalTab.js.map +1 -0
  122. package/dist/tabs/TerminalTabBar.d.ts +21 -0
  123. package/dist/tabs/TerminalTabBar.d.ts.map +1 -0
  124. package/dist/tabs/TerminalTabBar.js +57 -0
  125. package/dist/tabs/TerminalTabBar.js.map +1 -0
  126. package/dist/themes.d.ts +17 -0
  127. package/dist/themes.d.ts.map +1 -0
  128. package/dist/themes.js +153 -0
  129. package/dist/themes.js.map +1 -0
  130. package/dist/tree/TerminalTree.d.ts +19 -0
  131. package/dist/tree/TerminalTree.d.ts.map +1 -0
  132. package/dist/tree/TerminalTree.js +68 -0
  133. package/dist/tree/TerminalTree.js.map +1 -0
  134. package/package.json +53 -0
@@ -0,0 +1,1281 @@
1
+ function detectMonoFontFamily(...args) {
2
+ return args[0] || "JetBrains Mono, Menlo, Monaco, monospace";
3
+ }
4
+ function scheduleOnce(fn) {
5
+ const raf = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame(fn) : setTimeout(fn, 16);
6
+ return raf;
7
+ }
8
+ function cancelFrame(raf) {
9
+ if (typeof cancelAnimationFrame !== "undefined")
10
+ cancelAnimationFrame(raf);
11
+ else
12
+ clearTimeout(raf);
13
+ }
14
+ let activeConfig = {
15
+ getPreferences: () => ({}),
16
+ getTheme: () => ({}),
17
+ openFile: () => { },
18
+ openUrl: async (url) => {
19
+ if (typeof window !== "undefined")
20
+ window.open(url, "_blank");
21
+ },
22
+ };
23
+ export function initRendererPool(config) {
24
+ activeConfig = {
25
+ ...activeConfig,
26
+ ...config,
27
+ };
28
+ }
29
+ export function setRendererPoolPreferencesGetter(fn) {
30
+ activeConfig.getPreferences = fn;
31
+ }
32
+ const usePreferencesStore = {
33
+ getState: () => activeConfig.getPreferences ? activeConfig.getPreferences() : {},
34
+ };
35
+ export function setRendererPoolOpenFileHandler(fn) {
36
+ activeConfig.openFile = fn;
37
+ }
38
+ const openFileTab = (...args) => {
39
+ if (activeConfig.openFile)
40
+ activeConfig.openFile(...args);
41
+ };
42
+ export function setRendererPoolThemeGetter(fn) {
43
+ activeConfig.getTheme = fn;
44
+ }
45
+ function buildTerminalTheme(...args) {
46
+ if (activeConfig.getTheme)
47
+ return activeConfig.getTheme();
48
+ return args[0] || {};
49
+ }
50
+ export function setRendererPoolOpenUrlHandler(fn) {
51
+ activeConfig.openUrl = fn;
52
+ }
53
+ const openUrl = (url) => {
54
+ if (activeConfig.openUrl)
55
+ return activeConfig.openUrl(url);
56
+ return Promise.resolve();
57
+ };
58
+ import { AsciinemaAddon, CopyModeAddon, FitAddon, ImageAddon, LigaturesAddon, Osc133Addon, PathLinkProvider, SearchAddon, SerializeAddon, WebglAddon, WebLinksAddon, } from "@termaxjs/web-addons";
59
+ import { shouldCursorBlink, Terminal, terminalDeleteSequence, terminalLineNavigationSequence, terminalScrollAction, terminalWordNavigationSequence, } from "@termaxjs/web-ui";
60
+ export const POOL_MAX_SIZE = 5;
61
+ const FIT_DEBOUNCE_MS = 8;
62
+ const PTY_RESIZE_DEBOUNCE_MS = 16;
63
+ const SNAPSHOT_SCROLLBACK_CAP = 5_000;
64
+ const slots = [];
65
+ let recyclerEl = null;
66
+ let adapter = null;
67
+ let windowActive = typeof document === "undefined" || (!document.hidden && document.hasFocus());
68
+ let windowActivityBound = false;
69
+ let cursorBlinkEnabled = false;
70
+ function bindWindowActivityListeners() {
71
+ if (windowActivityBound || typeof window === "undefined")
72
+ return;
73
+ windowActivityBound = true;
74
+ const sync = () => setWindowActive(!document.hidden && document.hasFocus());
75
+ window.addEventListener("focus", sync);
76
+ window.addEventListener("blur", sync);
77
+ document.addEventListener("visibilitychange", sync);
78
+ }
79
+ function setWindowActive(active) {
80
+ if (windowActive === active)
81
+ return;
82
+ windowActive = active;
83
+ for (const slot of slots) {
84
+ if (slot.currentLeafId === null)
85
+ continue;
86
+ applyCursorBlinkOnSlot(slot, adapter?.isLeafFocused(slot.currentLeafId) ?? false);
87
+ }
88
+ }
89
+ export function configureRendererPool(a) {
90
+ adapter = a;
91
+ bindWindowActivityListeners();
92
+ }
93
+ export function forEachSlot(fn) {
94
+ for (const s of slots)
95
+ fn(s);
96
+ }
97
+ export function poolSize() {
98
+ return slots.length;
99
+ }
100
+ export function poolSlotStats() {
101
+ return slots.map((s) => ({
102
+ id: s.id,
103
+ leafId: s.currentLeafId,
104
+ cols: s.term.cols,
105
+ rows: s.term.rows,
106
+ bufferLines: s.term.buffer.active.length,
107
+ webgl: !!s.webglAddon,
108
+ canvases: s.webglCanvases.length,
109
+ }));
110
+ }
111
+ // Bracketed paste via xterm, so an app that enabled it (Claude Code) treats a
112
+ // dropped path as a real paste while a plain shell gets the literal text.
113
+ export function pasteIntoLeaf(leafId, text) {
114
+ const slot = slots.find((s) => s.currentLeafId === leafId);
115
+ if (!slot)
116
+ return false;
117
+ slot.term.paste(text);
118
+ return true;
119
+ }
120
+ export function toggleCopyModeForLeaf(leafId) {
121
+ const slot = slots.find((s) => s.currentLeafId === leafId);
122
+ if (!slot)
123
+ return false;
124
+ slot.copyModeAddon.toggle();
125
+ return true;
126
+ }
127
+ const termKeyHandlers = new WeakMap();
128
+ export function registerCustomKeyHandler(term, handler) {
129
+ let list = termKeyHandlers.get(term);
130
+ if (!list) {
131
+ list = [];
132
+ termKeyHandlers.set(term, list);
133
+ }
134
+ list.push(handler);
135
+ return () => {
136
+ const l = termKeyHandlers.get(term);
137
+ if (l) {
138
+ const idx = l.indexOf(handler);
139
+ if (idx !== -1)
140
+ l.splice(idx, 1);
141
+ }
142
+ };
143
+ }
144
+ function getRecycler() {
145
+ if (recyclerEl?.isConnected)
146
+ return recyclerEl;
147
+ const el = document.createElement("div");
148
+ el.setAttribute("data-termax-recycler", "");
149
+ el.style.cssText =
150
+ "position:fixed;left:-99999px;top:-99999px;width:1024px;height:768px;overflow:hidden;pointer-events:none;contain:strict;";
151
+ document.body.appendChild(el);
152
+ recyclerEl = el;
153
+ return el;
154
+ }
155
+ const MCR_BG_ACTIVE = 4.5;
156
+ const MCR_BG_INACTIVE = 1;
157
+ function bgActive(prefs) {
158
+ return prefs.backgroundKind === "image" && !!prefs.backgroundImageId;
159
+ }
160
+ function termOptions() {
161
+ const prefs = usePreferencesStore.getState();
162
+ const theme = buildTerminalTheme();
163
+ if (prefs.terminalCursorAnimation) {
164
+ theme.cursor = "transparent";
165
+ theme.cursorAccent = "transparent";
166
+ }
167
+ const cursorStyle = prefs.terminalCursorStyle ?? "bar";
168
+ const cursorInactiveStyle = cursorStyle === "block" ? "outline" : cursorStyle;
169
+ return {
170
+ fontFamily: prefs.terminalFontFamily || detectMonoFontFamily(),
171
+ letterSpacing: prefs.terminalLetterSpacing,
172
+ fontSize: Math.max(4, Math.round(prefs.terminalFontSize * prefs.zoomLevel)),
173
+ theme,
174
+ cursorBlink: false,
175
+ cursorStyle,
176
+ cursorInactiveStyle,
177
+ scrollback: prefs.terminalScrollback,
178
+ smoothScrollDuration: prefs.terminalSmoothScrolling ? 150 : 0,
179
+ allowProposedApi: true,
180
+ minimumContrastRatio: bgActive(prefs) ? MCR_BG_ACTIVE : MCR_BG_INACTIVE,
181
+ wordSeparator: " ()[]{}'\"`",
182
+ };
183
+ }
184
+ function applyHostPadding(slot) {
185
+ const prefs = usePreferencesStore.getState();
186
+ const padTop = prefs.terminalPaddingTop ?? prefs.terminalPadding ?? 0;
187
+ const padRight = prefs.terminalPaddingRight ?? prefs.terminalPadding ?? 0;
188
+ const padBottom = prefs.terminalPaddingBottom ?? prefs.terminalPadding ?? 0;
189
+ const padLeft = prefs.terminalPaddingLeft ?? prefs.terminalPadding ?? 0;
190
+ slot.host.style.paddingTop = `${padTop}px`;
191
+ slot.host.style.paddingRight = `${padRight}px`;
192
+ slot.host.style.paddingBottom = `${padBottom}px`;
193
+ slot.host.style.paddingLeft = `${padLeft}px`;
194
+ slot.host.style.boxSizing = "border-box";
195
+ slot.host.style.backgroundColor = "inherit";
196
+ }
197
+ export function applyBackgroundActive(active) {
198
+ const value = active ? MCR_BG_ACTIVE : MCR_BG_INACTIVE;
199
+ for (const slot of slots) {
200
+ if (slot.term.options.minimumContrastRatio === value)
201
+ continue;
202
+ slot.term.options.minimumContrastRatio = value;
203
+ }
204
+ }
205
+ function createSlot() {
206
+ const prefs = usePreferencesStore.getState();
207
+ const term = new Terminal(termOptions());
208
+ const initialStyle = prefs.terminalCursorStyle ?? "bar";
209
+ if (term.vt?.cursor) {
210
+ term.vt.cursor.shape = initialStyle;
211
+ }
212
+ const fitAddon = new FitAddon();
213
+ const searchAddon = new SearchAddon();
214
+ const serializeAddon = new SerializeAddon();
215
+ const imageAddon = new ImageAddon();
216
+ const copyModeAddon = new CopyModeAddon();
217
+ const osc133Addon = new Osc133Addon();
218
+ const asciinemaAddon = new AsciinemaAddon();
219
+ term.loadAddon(fitAddon);
220
+ term.loadAddon(searchAddon);
221
+ term.loadAddon(serializeAddon);
222
+ term.loadAddon(imageAddon);
223
+ term.loadAddon(copyModeAddon);
224
+ term.loadAddon(osc133Addon);
225
+ term.loadAddon(asciinemaAddon);
226
+ let linkHoverTooltip = null;
227
+ const showTooltip = (e, _text) => {
228
+ if (!linkHoverTooltip) {
229
+ linkHoverTooltip = document.createElement("div");
230
+ linkHoverTooltip.style.position = "fixed";
231
+ linkHoverTooltip.style.zIndex = "999999";
232
+ linkHoverTooltip.style.background = "hsl(var(--card, #18181b))";
233
+ linkHoverTooltip.style.color = "hsl(var(--foreground, #fafafa))";
234
+ linkHoverTooltip.style.padding = "4px 8px";
235
+ linkHoverTooltip.style.borderRadius = "6px";
236
+ linkHoverTooltip.style.fontSize = "11px";
237
+ linkHoverTooltip.style.fontWeight = "500";
238
+ linkHoverTooltip.style.fontFamily = "var(--font-sans)";
239
+ linkHoverTooltip.style.boxShadow = "0 4px 16px rgba(0,0,0,0.35)";
240
+ linkHoverTooltip.style.border =
241
+ "1px solid hsl(var(--border, #27272a) / 0.8)";
242
+ linkHoverTooltip.style.pointerEvents = "none";
243
+ linkHoverTooltip.style.whiteSpace = "nowrap";
244
+ linkHoverTooltip.style.backdropFilter = "blur(8px)";
245
+ document.body.appendChild(linkHoverTooltip);
246
+ }
247
+ const cmdKey = navigator.userAgent.includes("Mac") ? "Cmd" : "Ctrl";
248
+ linkHoverTooltip.innerHTML = `<span style="opacity: 0.85">${cmdKey} + click to follow link</span>`;
249
+ const x = Math.max(10, Math.min(e.clientX + 8, window.innerWidth - 220));
250
+ const y = e.clientY + 24 > window.innerHeight - 40
251
+ ? e.clientY - 28
252
+ : e.clientY + 18;
253
+ linkHoverTooltip.style.left = `${x}px`;
254
+ linkHoverTooltip.style.top = `${y}px`;
255
+ linkHoverTooltip.style.display = "block";
256
+ };
257
+ const hideTooltip = () => {
258
+ if (linkHoverTooltip)
259
+ linkHoverTooltip.style.display = "none";
260
+ };
261
+ term.loadAddon(new WebLinksAddon((_e, uri) => openUrl(uri).catch(console.error), {
262
+ hover: (e, text) => showTooltip(e, text),
263
+ leave: () => hideTooltip(),
264
+ }));
265
+ term.registerLinkProvider(new PathLinkProvider(term, {
266
+ hover: (e, text) => showTooltip(e, text),
267
+ leave: () => hideTooltip(),
268
+ }, (path, line) => openFileTab(path, true, line)));
269
+ const host = document.createElement("div");
270
+ host.style.cssText =
271
+ "width:100%;height:100%;position:relative;box-sizing:border-box;";
272
+ host.setAttribute("data-termax-slot", String(slots.length));
273
+ const cursorNode = document.createElement("div");
274
+ cursorNode.className = "termax-custom-cursor";
275
+ cursorNode.style.position = "absolute";
276
+ cursorNode.style.top = "0";
277
+ cursorNode.style.left = "0";
278
+ cursorNode.style.pointerEvents = "none";
279
+ cursorNode.style.zIndex = "10";
280
+ cursorNode.style.borderRadius = "1px";
281
+ cursorNode.style.opacity = "0";
282
+ cursorNode.style.transitionProperty = "transform, opacity, width, height";
283
+ cursorNode.style.transitionDuration = "80ms";
284
+ cursorNode.style.transitionTimingFunction = "cubic-bezier(0.2, 0.9, 0.4, 1)";
285
+ cursorNode.style.background = "var(--terminal-cursor, var(--tx-text, #fff))";
286
+ getRecycler().appendChild(host);
287
+ term.open(host);
288
+ host.appendChild(cursorNode);
289
+ const slot = {
290
+ id: slots.length,
291
+ term,
292
+ fitAddon,
293
+ searchAddon,
294
+ serializeAddon,
295
+ imageAddon,
296
+ copyModeAddon,
297
+ osc133Addon,
298
+ asciinemaAddon,
299
+ host,
300
+ cursorNode,
301
+ webglAddon: null,
302
+ ligaturesAddon: null,
303
+ webglCanvases: [],
304
+ currentLeafId: null,
305
+ oscDisposers: [],
306
+ observer: null,
307
+ fitTimer: null,
308
+ ptyTimer: null,
309
+ webglReapTimer: null,
310
+ slotReapTimer: null,
311
+ unhideRaf: null,
312
+ lastCols: term.cols,
313
+ lastRows: term.rows,
314
+ lastW: 0,
315
+ lastH: 0,
316
+ lastUsedAt: 0,
317
+ };
318
+ let cursorIdleTimer = null;
319
+ let cursorBlinkInterval = null;
320
+ let cursorRaf = null;
321
+ const stopAnimatedCursorBlink = () => {
322
+ if (cursorIdleTimer) {
323
+ clearTimeout(cursorIdleTimer);
324
+ cursorIdleTimer = null;
325
+ }
326
+ if (cursorBlinkInterval) {
327
+ clearInterval(cursorBlinkInterval);
328
+ cursorBlinkInterval = null;
329
+ }
330
+ };
331
+ const updateCursor = () => {
332
+ const prefs = usePreferencesStore.getState();
333
+ if (!prefs.terminalCursorAnimation || !slot.lastCols || !slot.lastRows) {
334
+ stopAnimatedCursorBlink();
335
+ cursorNode.style.opacity = "0";
336
+ return;
337
+ }
338
+ // Only show custom cursor if window is active and this slot is focused
339
+ const isFocused = slot.currentLeafId !== null &&
340
+ (adapter ? adapter.isLeafFocused(slot.currentLeafId) : true);
341
+ if (!windowActive || !isFocused) {
342
+ stopAnimatedCursorBlink();
343
+ cursorNode.style.opacity = "0";
344
+ return;
345
+ }
346
+ stopAnimatedCursorBlink();
347
+ cursorNode.style.opacity = "1";
348
+ const padTop = prefs.terminalPaddingTop ?? prefs.terminalPadding ?? 0;
349
+ const padRight = prefs.terminalPaddingRight ?? prefs.terminalPadding ?? 0;
350
+ const padBottom = prefs.terminalPaddingBottom ?? prefs.terminalPadding ?? 0;
351
+ const padLeft = prefs.terminalPaddingLeft ?? prefs.terminalPadding ?? 0;
352
+ const rendererCharW = slot.term.renderer?.charWidth || 9;
353
+ const rendererCharH = slot.term.renderer?.charHeight || 18;
354
+ const cw = slot.lastW > 0
355
+ ? (slot.lastW - padLeft - padRight) / slot.lastCols
356
+ : rendererCharW;
357
+ const ch = slot.lastH > 0
358
+ ? (slot.lastH - padTop - padBottom) / slot.lastRows
359
+ : rendererCharH;
360
+ const cx = padLeft + term.buffer.active.cursorX * cw;
361
+ const cy = padTop + term.buffer.active.cursorY * ch;
362
+ const style = prefs.terminalCursorStyle ?? "bar";
363
+ if (style === "underline") {
364
+ cursorNode.style.transform = `translate(${cx}px, ${cy + ch - 2}px)`;
365
+ cursorNode.style.width = `${cw}px`;
366
+ cursorNode.style.height = "2px";
367
+ cursorNode.style.mixBlendMode = "normal";
368
+ }
369
+ else if (style === "bar") {
370
+ cursorNode.style.transform = `translate(${cx}px, ${cy}px)`;
371
+ cursorNode.style.width = "2px";
372
+ cursorNode.style.height = `${ch}px`;
373
+ cursorNode.style.mixBlendMode = "normal";
374
+ }
375
+ else {
376
+ cursorNode.style.transform = `translate(${cx}px, ${cy}px)`;
377
+ cursorNode.style.width = `${cw}px`;
378
+ cursorNode.style.height = `${ch}px`;
379
+ cursorNode.style.mixBlendMode = "difference";
380
+ }
381
+ if (prefs.terminalCursorBlink) {
382
+ cursorIdleTimer = setTimeout(() => {
383
+ let visible = true;
384
+ cursorBlinkInterval = setInterval(() => {
385
+ visible = !visible;
386
+ cursorNode.style.opacity = visible ? "1" : "0";
387
+ }, 530);
388
+ }, 500);
389
+ }
390
+ };
391
+ const scheduleCursorUpdate = () => {
392
+ if (cursorRaf !== null)
393
+ return;
394
+ cursorRaf = scheduleOnce(() => {
395
+ cursorRaf = null;
396
+ updateCursor();
397
+ });
398
+ };
399
+ slot.updateCursor = updateCursor;
400
+ term.onCursorMove(scheduleCursorUpdate);
401
+ term.onRender(scheduleCursorUpdate);
402
+ term.onWriteParsed(scheduleCursorUpdate);
403
+ term.onData(updateCursor);
404
+ term.onKey(updateCursor);
405
+ host.addEventListener("focusin", updateCursor);
406
+ host.addEventListener("focusout", updateCursor);
407
+ term.attachCustomKeyEventHandler((event) => {
408
+ // During IME composition the browser is assembling a multi-keystroke
409
+ // character (Chinese pinyin → hanzi, Korean jamo → syllable, etc.).
410
+ // Raw keydown events — including the Enter that commits a candidate —
411
+ // must NOT be forwarded to the PTY; xterm will receive the final
412
+ // composed string through its own compositionend handler instead.
413
+ // keyCode 229 ("Process") is what Chromium reports for every key
414
+ // pressed inside an active IME session when isComposing is not yet set.
415
+ if (event.isComposing || event.keyCode === 229)
416
+ return false;
417
+ const extraHandlers = termKeyHandlers.get(term);
418
+ if (extraHandlers) {
419
+ for (const h of extraHandlers) {
420
+ const res = h(event);
421
+ if (res !== undefined)
422
+ return res;
423
+ }
424
+ }
425
+ if (slot.copyModeAddon.isActive) {
426
+ return slot.copyModeAddon.handleKey(event);
427
+ }
428
+ const leafId = slot.currentLeafId;
429
+ if (leafId === null)
430
+ return false;
431
+ const bridge = adapter?.resolveLeaf(leafId);
432
+ if (!bridge)
433
+ return true;
434
+ const lineNavigation = terminalLineNavigationSequence(event, {
435
+ isMac: IS_MAC,
436
+ });
437
+ if (lineNavigation) {
438
+ event.preventDefault();
439
+ if (event.type === "keydown")
440
+ bridge.writeToPty(lineNavigation);
441
+ return false;
442
+ }
443
+ const wordNavigation = terminalWordNavigationSequence(event, {
444
+ isMac: IS_MAC,
445
+ });
446
+ if (wordNavigation) {
447
+ event.preventDefault();
448
+ if (event.type === "keydown")
449
+ bridge.writeToPty(wordNavigation);
450
+ return false;
451
+ }
452
+ const deleteSeq = terminalDeleteSequence(event, { isMac: IS_MAC });
453
+ if (deleteSeq) {
454
+ event.preventDefault();
455
+ if (event.type === "keydown")
456
+ bridge.writeToPty(deleteSeq);
457
+ return false;
458
+ }
459
+ if (isShiftEnter(event)) {
460
+ event.preventDefault();
461
+ if (event.type === "keydown")
462
+ bridge.writeToPty("\x1b\r");
463
+ return false;
464
+ }
465
+ if (isTerminalCopy(event)) {
466
+ if (event.type === "keydown" && slot.term.hasSelection()) {
467
+ const sel = slot.term.getSelection();
468
+ if (sel)
469
+ void navigator.clipboard.writeText(sel).catch(() => { });
470
+ }
471
+ event.preventDefault();
472
+ return false;
473
+ }
474
+ if (isTerminalPaste(event)) {
475
+ if (event.type === "keydown") {
476
+ void navigator.clipboard
477
+ .readText()
478
+ .then((text) => {
479
+ if (text)
480
+ slot.term.paste(text);
481
+ })
482
+ .catch(() => { });
483
+ }
484
+ event.preventDefault();
485
+ return false;
486
+ }
487
+ const scrollAction = terminalScrollAction(event, { isMac: IS_MAC });
488
+ if (scrollAction) {
489
+ event.preventDefault();
490
+ if (event.type === "keydown") {
491
+ if (scrollAction === "top")
492
+ slot.term.scrollToTop();
493
+ else
494
+ slot.term.scrollToBottom();
495
+ }
496
+ return false;
497
+ }
498
+ // Default: allow event through
499
+ return true;
500
+ });
501
+ term.onData((data) => {
502
+ const leafId = slot.currentLeafId;
503
+ if (leafId === null)
504
+ return;
505
+ adapter?.resolveLeaf(leafId)?.writeToPty(data);
506
+ });
507
+ slots.push(slot);
508
+ return slot;
509
+ }
510
+ function isAltScreen(s) {
511
+ try {
512
+ return s.term.buffer.active.type === "alternate";
513
+ }
514
+ catch {
515
+ return false;
516
+ }
517
+ }
518
+ function pickSlotFor(leafId) {
519
+ const free = slots.find((s) => s.currentLeafId === null);
520
+ if (free)
521
+ return { slot: free, previousLeafId: null };
522
+ if (slots.length < POOL_MAX_SIZE)
523
+ return { slot: createSlot(), previousLeafId: null };
524
+ let best = null;
525
+ let bestScore = Number.POSITIVE_INFINITY;
526
+ for (const s of slots) {
527
+ if (s.currentLeafId === leafId)
528
+ return { slot: s, previousLeafId: null };
529
+ const focused = s.currentLeafId !== null &&
530
+ (adapter?.isLeafFocused(s.currentLeafId) ?? false);
531
+ const blocks = s.currentLeafId !== null &&
532
+ (adapter?.isLeafBlocks(s.currentLeafId) ?? false);
533
+ const score = (isAltScreen(s) ? 100 : 0) +
534
+ (blocks ? 50 : 0) +
535
+ (focused ? 10 : 0) +
536
+ s.lastUsedAt / 1e12;
537
+ if (score < bestScore) {
538
+ bestScore = score;
539
+ best = s;
540
+ }
541
+ }
542
+ const chosen = best;
543
+ return { slot: chosen, previousLeafId: chosen.currentLeafId };
544
+ }
545
+ export function acquireSlot(params) {
546
+ const existing = slots.find((s) => s.currentLeafId === params.leafId);
547
+ if (existing) {
548
+ rewireSlot(existing, params);
549
+ return existing;
550
+ }
551
+ const pick = pickSlotFor(params.leafId);
552
+ if (pick.previousLeafId !== null) {
553
+ adapter?.evictLeaf(pick.previousLeafId);
554
+ }
555
+ if (pick.slot.currentLeafId !== null &&
556
+ pick.slot.currentLeafId !== params.leafId) {
557
+ detachSlotFromLeaf(pick.slot);
558
+ }
559
+ bindSlot(pick.slot, params);
560
+ return pick.slot;
561
+ }
562
+ function bindSlot(slot, p) {
563
+ const stale = !slot.webglAddon || performance.now() - slot.lastUsedAt > SLOT_STALE_MS;
564
+ slot.currentLeafId = p.leafId;
565
+ slot.lastUsedAt = performance.now();
566
+ cancelPendingUnhide(slot);
567
+ cancelWebglReap(slot);
568
+ cancelSlotReap(slot);
569
+ slot.host.style.visibility = "hidden";
570
+ if (slot.host.parentNode !== p.container) {
571
+ p.container.appendChild(slot.host);
572
+ }
573
+ applyHostPadding(slot);
574
+ setupResizeObserver(slot, p);
575
+ slot.fitAddon.fit();
576
+ slot.lastCols = slot.term.cols;
577
+ slot.lastRows = slot.term.rows;
578
+ slot.lastW = p.container.clientWidth;
579
+ slot.lastH = p.container.clientHeight;
580
+ slot.term.options.disableStdin = p.shellExited;
581
+ slot.term.clear();
582
+ slot.term.reset();
583
+ if (p.snapshot) {
584
+ try {
585
+ slot.term.write(p.snapshot);
586
+ }
587
+ catch (e) {
588
+ console.warn("[termax] snapshot replay failed:", e);
589
+ }
590
+ }
591
+ if (p.altScreen) {
592
+ p.drainRing(() => { });
593
+ }
594
+ else {
595
+ p.drainRing((bytes) => slot.term.write(bytes));
596
+ }
597
+ try {
598
+ slot.term.write("\x1b[?25h");
599
+ }
600
+ catch (e) {
601
+ console.warn("[rendererPool]", e);
602
+ }
603
+ for (const d of slot.oscDisposers) {
604
+ try {
605
+ d();
606
+ }
607
+ catch (e) {
608
+ console.warn("[rendererPool]", e);
609
+ }
610
+ }
611
+ const d1 = slot.term.onTitleChange((title) => p.onTitle?.(title));
612
+ const d2 = slot.term.onBell(() => p.onBell?.());
613
+ slot.oscDisposers = [
614
+ ...p.registerOsc(slot.term),
615
+ () => d1.dispose(),
616
+ () => d2.dispose(),
617
+ ];
618
+ if (slot.lastCols !== p.cols || slot.lastRows !== p.rows) {
619
+ if (slot.lastCols > 0 && slot.lastRows > 0) {
620
+ adapter?.resolveLeaf(p.leafId)?.resizePty(slot.lastCols, slot.lastRows);
621
+ }
622
+ }
623
+ if (p.searchQuery) {
624
+ try {
625
+ slot.searchAddon.findNext(p.searchQuery);
626
+ }
627
+ catch (e) {
628
+ console.warn("[rendererPool]", e);
629
+ }
630
+ }
631
+ applyCursorBlinkOnSlot(slot, adapter?.isLeafFocused(p.leafId) ?? false);
632
+ const containerReady = p.container.clientWidth > 0 && p.container.clientHeight > 0;
633
+ if (p.altScreen && !p.shellExited) {
634
+ if (slot.term.cols > 0 && slot.term.rows > 0 && containerReady) {
635
+ adapter?.resolveLeaf(p.leafId)?.kickPty(slot.term.cols, slot.term.rows);
636
+ }
637
+ }
638
+ scheduleUnhide(slot, stale, p.altScreen && !p.shellExited && !containerReady);
639
+ p.onSearchReady(slot.searchAddon);
640
+ }
641
+ function scheduleUnhide(slot, stale, needsDeferredKick = false) {
642
+ slot.unhideRaf = scheduleOnce(() => {
643
+ slot.unhideRaf = null;
644
+ slot.host.style.visibility = "";
645
+ try {
646
+ slot.fitAddon.fit();
647
+ slot.term.scrollToBottom();
648
+ slot.updateCursor?.();
649
+ }
650
+ catch { }
651
+ if (stale) {
652
+ if (!slot.webglAddon)
653
+ attachWebgl(slot);
654
+ try {
655
+ slot.term.refresh(0, slot.term.rows - 1);
656
+ }
657
+ catch (e) {
658
+ console.warn("[rendererPool]", e);
659
+ }
660
+ }
661
+ const leafId = slot.currentLeafId;
662
+ if (needsDeferredKick &&
663
+ leafId !== null &&
664
+ slot.term.cols > 0 &&
665
+ slot.term.rows > 0) {
666
+ adapter?.resolveLeaf(leafId)?.kickPty(slot.term.cols, slot.term.rows);
667
+ }
668
+ if (leafId !== null && adapter?.isLeafFocused(leafId)) {
669
+ slot.term.focus();
670
+ }
671
+ });
672
+ }
673
+ function cancelPendingUnhide(slot) {
674
+ if (slot.unhideRaf !== null) {
675
+ cancelFrame(slot.unhideRaf);
676
+ slot.unhideRaf = null;
677
+ }
678
+ }
679
+ function rewireSlot(slot, p) {
680
+ slot.lastUsedAt = performance.now();
681
+ if (slot.host.parentNode !== p.container) {
682
+ p.container.appendChild(slot.host);
683
+ }
684
+ applyHostPadding(slot);
685
+ setupResizeObserver(slot, p);
686
+ slot.fitAddon.fit();
687
+ if (p.container.clientWidth > 0 && p.container.clientHeight > 0) {
688
+ slot.lastW = p.container.clientWidth;
689
+ slot.lastH = p.container.clientHeight;
690
+ }
691
+ if (slot.term.cols > 0 &&
692
+ slot.term.rows > 0 &&
693
+ (slot.term.cols !== p.cols || slot.term.rows !== p.rows)) {
694
+ adapter?.resolveLeaf(p.leafId)?.resizePty(slot.term.cols, slot.term.rows);
695
+ }
696
+ slot.lastCols = slot.term.cols;
697
+ slot.lastRows = slot.term.rows;
698
+ slot.updateCursor?.();
699
+ p.onSearchReady(slot.searchAddon);
700
+ }
701
+ function setupResizeObserver(slot, p) {
702
+ slot.observer?.disconnect();
703
+ if (slot.fitTimer)
704
+ clearTimeout(slot.fitTimer);
705
+ if (slot.ptyTimer)
706
+ clearTimeout(slot.ptyTimer);
707
+ slot.fitTimer = null;
708
+ slot.ptyTimer = null;
709
+ const container = p.container;
710
+ const flushPty = () => {
711
+ slot.ptyTimer = null;
712
+ if (slot.currentLeafId !== p.leafId)
713
+ return;
714
+ if (slot.term.cols === slot.lastCols && slot.term.rows === slot.lastRows)
715
+ return;
716
+ if (slot.term.cols <= 0 || slot.term.rows <= 0)
717
+ return;
718
+ slot.lastCols = slot.term.cols;
719
+ slot.lastRows = slot.term.rows;
720
+ adapter?.resolveLeaf(p.leafId)?.resizePty(slot.lastCols, slot.lastRows);
721
+ };
722
+ slot.observer = new ResizeObserver(() => {
723
+ if (slot.fitTimer)
724
+ clearTimeout(slot.fitTimer);
725
+ slot.fitTimer = setTimeout(() => {
726
+ slot.fitTimer = null;
727
+ if (slot.currentLeafId !== p.leafId)
728
+ return;
729
+ const w = container.clientWidth;
730
+ const h = container.clientHeight;
731
+ if (w <= 0 || h <= 0)
732
+ return;
733
+ if (w === slot.lastW && h === slot.lastH)
734
+ return;
735
+ slot.lastW = w;
736
+ slot.lastH = h;
737
+ slot.fitAddon.fit();
738
+ slot.term.scrollToBottom();
739
+ slot.updateCursor?.();
740
+ if (slot.ptyTimer)
741
+ clearTimeout(slot.ptyTimer);
742
+ slot.ptyTimer = setTimeout(flushPty, PTY_RESIZE_DEBOUNCE_MS);
743
+ }, FIT_DEBOUNCE_MS);
744
+ });
745
+ slot.observer.observe(container);
746
+ }
747
+ export function releaseSlot(leafId) {
748
+ const slot = slots.find((s) => s.currentLeafId === leafId);
749
+ if (!slot)
750
+ return null;
751
+ const out = serializeSlot(slot);
752
+ detachSlotFromLeaf(slot);
753
+ return out;
754
+ }
755
+ function serializeSlot(slot) {
756
+ let snapshot = null;
757
+ try {
758
+ const cap = Math.min(SNAPSHOT_SCROLLBACK_CAP, usePreferencesStore.getState().terminalScrollback);
759
+ snapshot = slot.serializeAddon.serialize({ scrollback: cap });
760
+ }
761
+ catch (e) {
762
+ console.warn("[termax] serialize failed:", e);
763
+ }
764
+ return {
765
+ snapshot,
766
+ cols: slot.term.cols,
767
+ rows: slot.term.rows,
768
+ altScreen: isAltScreen(slot),
769
+ };
770
+ }
771
+ function detachSlotFromLeaf(slot) {
772
+ for (const d of slot.oscDisposers) {
773
+ try {
774
+ d();
775
+ }
776
+ catch (e) {
777
+ console.warn("[rendererPool]", e);
778
+ }
779
+ }
780
+ slot.oscDisposers = [];
781
+ slot.observer?.disconnect();
782
+ slot.observer = null;
783
+ if (slot.fitTimer)
784
+ clearTimeout(slot.fitTimer);
785
+ if (slot.ptyTimer)
786
+ clearTimeout(slot.ptyTimer);
787
+ slot.fitTimer = null;
788
+ slot.ptyTimer = null;
789
+ cancelPendingUnhide(slot);
790
+ slot.host.style.visibility = "";
791
+ if (slot.host.parentNode !== getRecycler()) {
792
+ getRecycler().appendChild(slot.host);
793
+ }
794
+ slot.currentLeafId = null;
795
+ slot.lastUsedAt = performance.now();
796
+ scheduleWebglReap(slot);
797
+ scheduleSlotReap(slot);
798
+ }
799
+ function scheduleWebglReap(slot) {
800
+ cancelWebglReap(slot);
801
+ if (!slot.webglAddon)
802
+ return;
803
+ slot.webglReapTimer = setTimeout(() => {
804
+ slot.webglReapTimer = null;
805
+ if (slot.currentLeafId === null)
806
+ disposeSlotWebgl(slot);
807
+ }, WEBGL_REAP_GRACE_MS);
808
+ }
809
+ function cancelWebglReap(slot) {
810
+ if (slot.webglReapTimer !== null) {
811
+ clearTimeout(slot.webglReapTimer);
812
+ slot.webglReapTimer = null;
813
+ }
814
+ }
815
+ function scheduleSlotReap(slot) {
816
+ cancelSlotReap(slot);
817
+ slot.slotReapTimer = setTimeout(() => {
818
+ slot.slotReapTimer = null;
819
+ reapIdleSlot(slot);
820
+ }, SLOT_REAP_GRACE_MS);
821
+ }
822
+ function cancelSlotReap(slot) {
823
+ if (slot.slotReapTimer !== null) {
824
+ clearTimeout(slot.slotReapTimer);
825
+ slot.slotReapTimer = null;
826
+ }
827
+ }
828
+ function reapIdleSlot(slot) {
829
+ if (slot.currentLeafId !== null)
830
+ return;
831
+ const idle = slots.filter((s) => s.currentLeafId === null);
832
+ if (idle.length <= IDLE_SLOTS_KEEP_WARM)
833
+ return;
834
+ idle.sort((a, b) => a.lastUsedAt - b.lastUsedAt);
835
+ const surplus = idle.slice(0, idle.length - IDLE_SLOTS_KEEP_WARM);
836
+ if (surplus.includes(slot))
837
+ disposeSlot(slot);
838
+ }
839
+ function disposeSlot(slot) {
840
+ cancelSlotReap(slot);
841
+ cancelWebglReap(slot);
842
+ cancelPendingUnhide(slot);
843
+ if (slot.fitTimer)
844
+ clearTimeout(slot.fitTimer);
845
+ if (slot.ptyTimer)
846
+ clearTimeout(slot.ptyTimer);
847
+ slot.fitTimer = null;
848
+ slot.ptyTimer = null;
849
+ slot.observer?.disconnect();
850
+ slot.observer = null;
851
+ for (const d of slot.oscDisposers) {
852
+ try {
853
+ d();
854
+ }
855
+ catch (e) {
856
+ console.warn("[rendererPool]", e);
857
+ }
858
+ }
859
+ slot.oscDisposers = [];
860
+ disposeSlotWebgl(slot);
861
+ try {
862
+ slot.term.dispose();
863
+ }
864
+ catch (e) {
865
+ console.warn("[termax] slot dispose failed:", e);
866
+ }
867
+ slot.host.remove();
868
+ const i = slots.indexOf(slot);
869
+ if (i >= 0)
870
+ slots.splice(i, 1);
871
+ }
872
+ const WEBGL_RECOVERY_DELAY_MS = 250;
873
+ // Below this a re-shown slot is fresh enough to trust; above it, repaint on
874
+ // unhide to defeat silent GPU/context staleness.
875
+ const SLOT_STALE_MS = 10_000;
876
+ const WEBGL_REAP_GRACE_MS = 30_000;
877
+ const SLOT_REAP_GRACE_MS = 45_000;
878
+ const IDLE_SLOTS_KEEP_WARM = 1;
879
+ function attachWebgl(slot) {
880
+ if (slot.webglAddon || !slot.term.element)
881
+ return;
882
+ if (!usePreferencesStore.getState().terminalWebglEnabled)
883
+ return;
884
+ const elem = slot.term.element;
885
+ const before = new Set(elem.querySelectorAll("canvas"));
886
+ try {
887
+ const webgl = new WebglAddon();
888
+ webgl.onContextLoss(() => {
889
+ const cur = slot.webglAddon;
890
+ if (cur === webgl) {
891
+ slot.webglAddon = null;
892
+ slot.webglCanvases = [];
893
+ }
894
+ try {
895
+ webgl.dispose();
896
+ }
897
+ catch (e) {
898
+ console.warn("[rendererPool]", e);
899
+ }
900
+ // Recovery: WebKit may transiently lose contexts on sleep/wake or GPU
901
+ // reset; without re-attach the slot would silently fall back to DOM
902
+ // forever. Defer past WebKit's reset window before retrying.
903
+ setTimeout(() => {
904
+ if (slot.webglAddon || slot.currentLeafId === null)
905
+ return;
906
+ if (!usePreferencesStore.getState().terminalWebglEnabled)
907
+ return;
908
+ attachWebgl(slot);
909
+ if (slot.webglAddon) {
910
+ try {
911
+ slot.term.refresh(0, slot.term.rows - 1);
912
+ }
913
+ catch (e) {
914
+ console.warn("[rendererPool]", e);
915
+ }
916
+ }
917
+ }, WEBGL_RECOVERY_DELAY_MS);
918
+ });
919
+ slot.term.loadAddon(webgl);
920
+ const after = elem.querySelectorAll("canvas");
921
+ const added = [];
922
+ for (const c of after)
923
+ if (!before.has(c))
924
+ added.push(c);
925
+ slot.webglAddon = webgl;
926
+ slot.webglCanvases = added;
927
+ }
928
+ catch (e) {
929
+ console.warn("[termax-webgl] unavailable:", e);
930
+ }
931
+ }
932
+ function disposeSlotWebgl(slot) {
933
+ if (!slot.webglAddon)
934
+ return;
935
+ const addon = slot.webglAddon;
936
+ for (const canvas of slot.webglCanvases)
937
+ releaseCanvasContext(canvas);
938
+ slot.webglCanvases = [];
939
+ try {
940
+ addon.dispose();
941
+ }
942
+ catch (e) {
943
+ console.warn("[termax-webgl] dispose failed:", e);
944
+ }
945
+ try {
946
+ const r = addon._renderer;
947
+ if (r) {
948
+ r._canvas = null;
949
+ r._gl = null;
950
+ r._charAtlas = null;
951
+ r._atlas = null;
952
+ }
953
+ addon._renderer = null;
954
+ addon._renderService = null;
955
+ }
956
+ catch (e) {
957
+ console.warn("[rendererPool]", e);
958
+ }
959
+ slot.webglAddon = null;
960
+ }
961
+ function releaseCanvasContext(canvas) {
962
+ let gl = null;
963
+ try {
964
+ gl = canvas.getContext("webgl2");
965
+ }
966
+ catch (e) {
967
+ console.warn("[rendererPool]", e);
968
+ }
969
+ if (!gl) {
970
+ try {
971
+ gl = canvas.getContext("webgl");
972
+ }
973
+ catch (e) {
974
+ console.warn("[rendererPool]", e);
975
+ }
976
+ }
977
+ if (gl) {
978
+ try {
979
+ const ext = gl.getExtension("WEBGL_lose_context");
980
+ if (ext && !gl.isContextLost())
981
+ ext.loseContext();
982
+ }
983
+ catch (e) {
984
+ console.warn("[rendererPool]", e);
985
+ }
986
+ }
987
+ try {
988
+ canvas.width = 0;
989
+ canvas.height = 0;
990
+ }
991
+ catch (e) {
992
+ console.warn("[rendererPool]", e);
993
+ }
994
+ }
995
+ export function applyWebglPreference(enabled) {
996
+ for (const slot of slots) {
997
+ if (enabled) {
998
+ if (slot.currentLeafId !== null && !slot.webglAddon) {
999
+ attachWebgl(slot);
1000
+ if (slot.webglAddon) {
1001
+ try {
1002
+ slot.term.refresh(0, slot.term.rows - 1);
1003
+ }
1004
+ catch (e) {
1005
+ console.warn("[rendererPool]", e);
1006
+ }
1007
+ }
1008
+ }
1009
+ }
1010
+ else if (slot.webglAddon) {
1011
+ cancelWebglReap(slot);
1012
+ disposeSlotWebgl(slot);
1013
+ }
1014
+ }
1015
+ }
1016
+ export function applyFontSize(size) {
1017
+ for (const slot of slots) {
1018
+ if (slot.term.options.fontSize === size)
1019
+ continue;
1020
+ slot.term.options.fontSize = size;
1021
+ slot.fitAddon.fit();
1022
+ if (slot.currentLeafId !== null) {
1023
+ slot.lastCols = slot.term.cols;
1024
+ slot.lastRows = slot.term.rows;
1025
+ const bridge = adapter?.resolveLeaf(slot.currentLeafId);
1026
+ bridge?.resizePty(slot.term.cols, slot.term.rows);
1027
+ }
1028
+ }
1029
+ }
1030
+ export function applyLetterSpacing(spacing) {
1031
+ for (const slot of slots) {
1032
+ if (slot.term.options.letterSpacing === spacing)
1033
+ continue;
1034
+ slot.term.options.letterSpacing = spacing;
1035
+ slot.fitAddon.fit();
1036
+ }
1037
+ }
1038
+ export function applyFontFamily(family) {
1039
+ const resolved = family || detectMonoFontFamily();
1040
+ for (const slot of slots) {
1041
+ if (slot.term.options.fontFamily === resolved)
1042
+ continue;
1043
+ slot.term.options.fontFamily = resolved;
1044
+ slot.fitAddon.fit();
1045
+ if (slot.currentLeafId !== null) {
1046
+ slot.lastCols = slot.term.cols;
1047
+ slot.lastRows = slot.term.rows;
1048
+ const bridge = adapter?.resolveLeaf(slot.currentLeafId);
1049
+ bridge?.resizePty(slot.term.cols, slot.term.rows);
1050
+ }
1051
+ }
1052
+ }
1053
+ export function applyScrollback(value) {
1054
+ for (const slot of slots) {
1055
+ if (slot.term.options.scrollback === value)
1056
+ continue;
1057
+ slot.term.options.scrollback = value;
1058
+ }
1059
+ }
1060
+ function applyLigaturesOnSlot(slot, enabled) {
1061
+ if (enabled && !slot.ligaturesAddon) {
1062
+ slot.ligaturesAddon = new LigaturesAddon();
1063
+ slot.term.loadAddon(slot.ligaturesAddon);
1064
+ }
1065
+ else if (!enabled && slot.ligaturesAddon) {
1066
+ slot.ligaturesAddon.dispose();
1067
+ slot.ligaturesAddon = null;
1068
+ }
1069
+ }
1070
+ export function restoreWindowActivity() {
1071
+ const theme = buildTerminalTheme();
1072
+ const prefs = usePreferencesStore.getState();
1073
+ if (prefs.terminalCursorAnimation) {
1074
+ theme.cursor = "transparent";
1075
+ theme.cursorAccent = "transparent";
1076
+ }
1077
+ for (const slot of slots) {
1078
+ slot.term.updateTheme(theme);
1079
+ }
1080
+ }
1081
+ export function applyTheme() {
1082
+ const theme = buildTerminalTheme();
1083
+ const prefs = usePreferencesStore.getState();
1084
+ const rawCursor = theme.cursor;
1085
+ if (prefs.terminalCursorAnimation) {
1086
+ theme.cursor = "transparent";
1087
+ theme.cursorAccent = "transparent";
1088
+ }
1089
+ for (const slot of slots) {
1090
+ slot.term.updateTheme(theme);
1091
+ if (rawCursor && rawCursor !== "transparent") {
1092
+ slot.cursorNode.style.background = rawCursor;
1093
+ }
1094
+ else {
1095
+ slot.cursorNode.style.background =
1096
+ "var(--terminal-cursor, var(--tx-text, #fff))";
1097
+ }
1098
+ applyHostPadding(slot);
1099
+ slot.updateCursor?.();
1100
+ try {
1101
+ slot.term.refresh(0, slot.term.rows - 1);
1102
+ }
1103
+ catch { }
1104
+ }
1105
+ }
1106
+ export function applyCursorAnimation(_enabled) {
1107
+ applyTheme();
1108
+ for (const slot of slots) {
1109
+ slot.updateCursor?.();
1110
+ }
1111
+ }
1112
+ export function applyThemeToLeaf(leafId, theme) {
1113
+ const slot = slots.find((s) => s.currentLeafId === leafId);
1114
+ if (slot && theme) {
1115
+ const prefs = usePreferencesStore.getState();
1116
+ const clonedTheme = { ...theme };
1117
+ const rawCursor = clonedTheme.cursor;
1118
+ if (prefs.terminalCursorAnimation) {
1119
+ clonedTheme.cursor = "transparent";
1120
+ clonedTheme.cursorAccent = "transparent";
1121
+ }
1122
+ slot.term.updateTheme(clonedTheme);
1123
+ if (rawCursor && rawCursor !== "transparent") {
1124
+ slot.cursorNode.style.background = rawCursor;
1125
+ }
1126
+ else {
1127
+ slot.cursorNode.style.background =
1128
+ "var(--terminal-cursor, var(--tx-text, #fff))";
1129
+ }
1130
+ slot.updateCursor?.();
1131
+ try {
1132
+ slot.term.refresh(0, slot.term.rows - 1);
1133
+ }
1134
+ catch { }
1135
+ }
1136
+ }
1137
+ export function focusSlot(leafId) {
1138
+ const slot = slots.find((s) => s.currentLeafId === leafId);
1139
+ if (!slot)
1140
+ return;
1141
+ slot.term.focus();
1142
+ slot.updateCursor?.();
1143
+ }
1144
+ export function setSlotFocused(leafId, focused) {
1145
+ const slot = slots.find((s) => s.currentLeafId === leafId);
1146
+ if (!slot)
1147
+ return;
1148
+ applyCursorBlinkOnSlot(slot, focused);
1149
+ if (focused) {
1150
+ slot.term.focus();
1151
+ }
1152
+ slot.updateCursor?.();
1153
+ }
1154
+ export function applyTerminalPadding() {
1155
+ for (const slot of slots) {
1156
+ applyHostPadding(slot);
1157
+ try {
1158
+ slot.fitAddon.fit();
1159
+ if (slot.currentLeafId !== null) {
1160
+ slot.lastCols = slot.term.cols;
1161
+ slot.lastRows = slot.term.rows;
1162
+ const bridge = adapter?.resolveLeaf(slot.currentLeafId);
1163
+ bridge?.resizePty(slot.term.cols, slot.term.rows);
1164
+ }
1165
+ slot.updateCursor?.();
1166
+ slot.term.refresh(0, slot.term.rows - 1);
1167
+ }
1168
+ catch { }
1169
+ }
1170
+ }
1171
+ export function applyLigatures(enabled) {
1172
+ for (const slot of slots) {
1173
+ applyLigaturesOnSlot(slot, enabled);
1174
+ }
1175
+ }
1176
+ export function applyCursorBlink(enabled) {
1177
+ cursorBlinkEnabled = enabled;
1178
+ for (const slot of slots) {
1179
+ if (slot.currentLeafId === null)
1180
+ continue;
1181
+ applyCursorBlinkOnSlot(slot, adapter?.isLeafFocused(slot.currentLeafId) ?? false);
1182
+ }
1183
+ }
1184
+ function applyCursorBlinkOnSlot(slot, focused) {
1185
+ const desired = shouldCursorBlink(cursorBlinkEnabled, windowActive, focused);
1186
+ if (slot.term.options.cursorBlink === desired)
1187
+ return;
1188
+ slot.term.options.cursorBlink = desired;
1189
+ if (typeof slot.term.setCursorBlink === "function") {
1190
+ slot.term.setCursorBlink(desired);
1191
+ }
1192
+ }
1193
+ export function applyCursorStyle(style) {
1194
+ for (const slot of slots) {
1195
+ slot.term.options.cursorStyle = style;
1196
+ slot.term.options.cursorInactiveStyle =
1197
+ style === "block" ? "outline" : style;
1198
+ if (typeof slot.term.setCursorStyle === "function") {
1199
+ slot.term.setCursorStyle(style);
1200
+ }
1201
+ else {
1202
+ if (slot.term.vt?.cursor) {
1203
+ slot.term.vt.cursor.shape = style;
1204
+ }
1205
+ if (slot.term.renderer?.options) {
1206
+ slot.term.renderer.options.cursorStyle = style;
1207
+ }
1208
+ }
1209
+ slot.updateCursor?.();
1210
+ try {
1211
+ slot.term.refresh(0, slot.term.rows - 1);
1212
+ }
1213
+ catch { }
1214
+ }
1215
+ }
1216
+ export function getSlotForLeaf(leafId) {
1217
+ return slots.find((s) => s.currentLeafId === leafId) ?? null;
1218
+ }
1219
+ export function isLeafAltScreen(leafId) {
1220
+ const slot = slots.find((s) => s.currentLeafId === leafId);
1221
+ return slot ? isAltScreen(slot) : false;
1222
+ }
1223
+ export function parkLeafSlot(leafId) {
1224
+ const slot = slots.find((s) => s.currentLeafId === leafId);
1225
+ if (slot)
1226
+ disposeSlotWebgl(slot);
1227
+ }
1228
+ export function refreshLeafSlot(leafId) {
1229
+ const slot = slots.find((s) => s.currentLeafId === leafId);
1230
+ if (!slot)
1231
+ return;
1232
+ if (usePreferencesStore.getState().terminalWebglEnabled && !slot.webglAddon) {
1233
+ attachWebgl(slot);
1234
+ }
1235
+ try {
1236
+ slot.fitAddon.fit();
1237
+ slot.term.scrollToBottom();
1238
+ slot.lastCols = slot.term.cols;
1239
+ slot.lastRows = slot.term.rows;
1240
+ adapter?.resolveLeaf(leafId)?.resizePty(slot.lastCols, slot.lastRows);
1241
+ slot.term.refresh(0, slot.term.rows - 1);
1242
+ }
1243
+ catch (e) {
1244
+ console.warn("[rendererPool]", e);
1245
+ }
1246
+ }
1247
+ export function disposeLeafSlot(leafId) {
1248
+ const slot = slots.find((s) => s.currentLeafId === leafId);
1249
+ if (slot)
1250
+ disposeSlot(slot);
1251
+ }
1252
+ const IS_MAC = typeof navigator !== "undefined" &&
1253
+ /Mac|iPhone|iPad/.test(navigator.userAgent);
1254
+ function isTerminalCopy(e) {
1255
+ return (!IS_MAC &&
1256
+ e.ctrlKey &&
1257
+ e.shiftKey &&
1258
+ !e.altKey &&
1259
+ !e.metaKey &&
1260
+ (e.code === "KeyC" || e.key === "c" || e.key === "C"));
1261
+ }
1262
+ function isTerminalPaste(e) {
1263
+ return (!IS_MAC &&
1264
+ e.ctrlKey &&
1265
+ e.shiftKey &&
1266
+ !e.altKey &&
1267
+ !e.metaKey &&
1268
+ (e.code === "KeyV" || e.key === "v" || e.key === "V"));
1269
+ }
1270
+ function isShiftEnter(e) {
1271
+ return (e.key === "Enter" && e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey);
1272
+ }
1273
+ export function getAsciinemaForLeaf(leafId) {
1274
+ const slot = slots.find((s) => s.currentLeafId === leafId);
1275
+ return slot?.asciinemaAddon ?? null;
1276
+ }
1277
+ export function getOsc133ForLeaf(leafId) {
1278
+ const slot = slots.find((s) => s.currentLeafId === leafId);
1279
+ return slot?.osc133Addon ?? null;
1280
+ }
1281
+ //# sourceMappingURL=rendererPool.js.map