@termaxjs/web-ui 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 (42) hide show
  1. package/LICENSE +201 -0
  2. package/dist/Terminal.d.ts +128 -0
  3. package/dist/Terminal.d.ts.map +1 -0
  4. package/dist/Terminal.js +1070 -0
  5. package/dist/Terminal.js.map +1 -0
  6. package/dist/adapters/tauri.d.ts +16 -0
  7. package/dist/adapters/tauri.d.ts.map +1 -0
  8. package/dist/adapters/tauri.js +30 -0
  9. package/dist/adapters/tauri.js.map +1 -0
  10. package/dist/adapters/web.d.ts +18 -0
  11. package/dist/adapters/web.d.ts.map +1 -0
  12. package/dist/adapters/web.js +38 -0
  13. package/dist/adapters/web.js.map +1 -0
  14. package/dist/adapters/worker.d.ts +26 -0
  15. package/dist/adapters/worker.d.ts.map +1 -0
  16. package/dist/adapters/worker.js +62 -0
  17. package/dist/adapters/worker.js.map +1 -0
  18. package/dist/index.d.ts +8 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +8 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/renderer/BoxDrawingRenderer.d.ts +7 -0
  23. package/dist/renderer/BoxDrawingRenderer.d.ts.map +1 -0
  24. package/dist/renderer/BoxDrawingRenderer.js +138 -0
  25. package/dist/renderer/BoxDrawingRenderer.js.map +1 -0
  26. package/dist/renderer/CanvasRenderer.d.ts +51 -0
  27. package/dist/renderer/CanvasRenderer.d.ts.map +1 -0
  28. package/dist/renderer/CanvasRenderer.js +537 -0
  29. package/dist/renderer/CanvasRenderer.js.map +1 -0
  30. package/dist/renderer/DomRenderer.d.ts +15 -0
  31. package/dist/renderer/DomRenderer.d.ts.map +1 -0
  32. package/dist/renderer/DomRenderer.js +112 -0
  33. package/dist/renderer/DomRenderer.js.map +1 -0
  34. package/dist/renderer/GlyphAtlas.d.ts +26 -0
  35. package/dist/renderer/GlyphAtlas.d.ts.map +1 -0
  36. package/dist/renderer/GlyphAtlas.js +86 -0
  37. package/dist/renderer/GlyphAtlas.js.map +1 -0
  38. package/dist/renderer/WebGpuRenderer.d.ts +31 -0
  39. package/dist/renderer/WebGpuRenderer.d.ts.map +1 -0
  40. package/dist/renderer/WebGpuRenderer.js +134 -0
  41. package/dist/renderer/WebGpuRenderer.js.map +1 -0
  42. package/package.json +37 -0
@@ -0,0 +1,1070 @@
1
+ import { VtEngine } from '@termaxjs/web-core';
2
+ import { CanvasRenderer } from './renderer/CanvasRenderer.js';
3
+ class RealBufferCell {
4
+ cell;
5
+ constructor(cell) {
6
+ this.cell = cell;
7
+ }
8
+ getChar() { return this.cell.char; }
9
+ getChars() { return this.cell.char; }
10
+ getCode() { return this.cell.char.codePointAt(0) || 32; }
11
+ getWidth() { return this.cell.width; }
12
+ getFgColor() { return 0; }
13
+ getBgColor() { return 0; }
14
+ }
15
+ class RealBufferLine {
16
+ isWrapped = false;
17
+ length;
18
+ cells;
19
+ constructor(cells) {
20
+ this.cells = cells;
21
+ this.length = cells.length;
22
+ }
23
+ getCell(x) {
24
+ const c = this.cells[x];
25
+ return c ? new RealBufferCell(c) : undefined;
26
+ }
27
+ translateToString(trimRight, startCol, endCol) {
28
+ let res = this.cells.map((c) => c.char).join('');
29
+ if (startCol !== undefined || endCol !== undefined) {
30
+ const s = startCol ?? 0;
31
+ const e = endCol ?? res.length;
32
+ res = res.slice(s, e);
33
+ }
34
+ return trimRight ? res.trimEnd() : res;
35
+ }
36
+ }
37
+ class TerminalMarker {
38
+ id;
39
+ isDisposed = false;
40
+ line;
41
+ disposeListeners = [];
42
+ constructor(id, line) {
43
+ this.id = id;
44
+ this.line = line;
45
+ }
46
+ onDispose(callback) {
47
+ this.disposeListeners.push(callback);
48
+ return {
49
+ dispose: () => {
50
+ this.disposeListeners = this.disposeListeners.filter((l) => l !== callback);
51
+ },
52
+ };
53
+ }
54
+ dispose() {
55
+ if (this.isDisposed)
56
+ return;
57
+ this.isDisposed = true;
58
+ for (const listener of this.disposeListeners) {
59
+ listener();
60
+ }
61
+ this.disposeListeners = [];
62
+ }
63
+ }
64
+ class TerminalDecoration {
65
+ marker;
66
+ element;
67
+ isDisposed = false;
68
+ renderListeners = [];
69
+ disposeListeners = [];
70
+ constructor(options) {
71
+ this.marker = options.marker;
72
+ if (typeof document !== 'undefined') {
73
+ this.element = document.createElement('div');
74
+ this.element.className = 'termaxui-decoration';
75
+ }
76
+ }
77
+ onRender(callback) {
78
+ this.renderListeners.push(callback);
79
+ if (this.element)
80
+ callback(this.element);
81
+ return {
82
+ dispose: () => {
83
+ this.renderListeners = this.renderListeners.filter((l) => l !== callback);
84
+ },
85
+ };
86
+ }
87
+ onDispose(callback) {
88
+ this.disposeListeners.push(callback);
89
+ return {
90
+ dispose: () => {
91
+ this.disposeListeners = this.disposeListeners.filter((l) => l !== callback);
92
+ },
93
+ };
94
+ }
95
+ dispose() {
96
+ if (this.isDisposed)
97
+ return;
98
+ this.isDisposed = true;
99
+ this.element?.remove();
100
+ for (const listener of this.disposeListeners) {
101
+ listener();
102
+ }
103
+ this.disposeListeners = [];
104
+ this.renderListeners = [];
105
+ }
106
+ }
107
+ export class Terminal {
108
+ options;
109
+ element;
110
+ textarea;
111
+ get modes() {
112
+ return {
113
+ mouseTrackingMode: this.vt.mouseMode.sgr ? 'sgr' : this.vt.mouseMode.x10 ? 'x10' : 'none',
114
+ bracketedPasteMode: this.vt.bracketedPasteMode,
115
+ synchronizedOutput: this.vt.synchronizedOutput,
116
+ focusReporting: this.vt.focusReporting,
117
+ altBuffer: this.vt.isAltBuffer,
118
+ };
119
+ }
120
+ vt;
121
+ renderer;
122
+ transport;
123
+ addons = [];
124
+ nextMarkerId = 1;
125
+ isMouseDown = false;
126
+ selection = null;
127
+ lastClickTime = 0;
128
+ clickCount = 0;
129
+ hoveredLink = null;
130
+ currentHoveredILink = null;
131
+ activeLinks = [];
132
+ dataListeners = [];
133
+ resizeListeners = [];
134
+ titleListeners = [];
135
+ bellListeners = [];
136
+ lineFeedListeners = [];
137
+ scrollListeners = [];
138
+ selectionChangeListeners = [];
139
+ writeParsedListeners = [];
140
+ renderListeners = [];
141
+ cursorMoveListeners = [];
142
+ keyListeners = [];
143
+ customKeyEventHandler;
144
+ oscHandlers = new Map();
145
+ linkProviders = [];
146
+ parser = {
147
+ registerOscHandler: (ident, handler) => {
148
+ let set = this.oscHandlers.get(ident);
149
+ if (!set) {
150
+ set = new Set();
151
+ this.oscHandlers.set(ident, set);
152
+ }
153
+ set.add(handler);
154
+ return {
155
+ dispose: () => {
156
+ const s = this.oscHandlers.get(ident);
157
+ if (s) {
158
+ s.delete(handler);
159
+ if (s.size === 0)
160
+ this.oscHandlers.delete(ident);
161
+ }
162
+ },
163
+ };
164
+ },
165
+ };
166
+ unicode = {
167
+ activeVersion: '11',
168
+ };
169
+ constructor(options = {}) {
170
+ const rawOptions = {
171
+ cols: options.cols || 80,
172
+ rows: options.rows || 24,
173
+ fontFamily: options.fontFamily || 'monospace',
174
+ fontSize: options.fontSize || 14,
175
+ ...options,
176
+ };
177
+ const self = this;
178
+ let settingOption = false;
179
+ this.options = new Proxy(rawOptions, {
180
+ set(target, prop, value) {
181
+ const record = target;
182
+ const oldVal = record[prop];
183
+ record[prop] = value;
184
+ if (oldVal === value || settingOption)
185
+ return true;
186
+ settingOption = true;
187
+ try {
188
+ if (prop === 'theme') {
189
+ self.renderer?.updateTheme(value);
190
+ }
191
+ else if (prop === 'cursorBlink') {
192
+ self.renderer?.setCursorBlink(Boolean(value));
193
+ }
194
+ else if (prop === 'cursorStyle') {
195
+ self.setCursorStyle(value);
196
+ }
197
+ }
198
+ finally {
199
+ settingOption = false;
200
+ }
201
+ return true;
202
+ },
203
+ });
204
+ this.vt = new VtEngine(this.options.cols || 80, this.options.rows || 24, {
205
+ onTitle: (title) => {
206
+ for (const l of this.titleListeners)
207
+ l(title);
208
+ },
209
+ onBell: () => {
210
+ for (const l of this.bellListeners)
211
+ l();
212
+ },
213
+ onClipboard: (text) => {
214
+ if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
215
+ navigator.clipboard.writeText(text).catch(() => { });
216
+ }
217
+ },
218
+ onOsc: (ident, data) => {
219
+ const handlers = this.oscHandlers.get(ident);
220
+ if (handlers) {
221
+ for (const handler of handlers) {
222
+ try {
223
+ handler(data);
224
+ }
225
+ catch (err) {
226
+ console.warn('[Terminal] OSC handler error:', err);
227
+ }
228
+ }
229
+ }
230
+ },
231
+ });
232
+ this.renderer = new CanvasRenderer(this.options);
233
+ if (this.options.cursorStyle) {
234
+ this.vt.cursorShape = this.options.cursorStyle;
235
+ this.vt.cursor.shape = this.options.cursorStyle;
236
+ if (this.renderer.cursor) {
237
+ this.renderer.cursor.shape = this.options.cursorStyle;
238
+ }
239
+ }
240
+ if (typeof document !== 'undefined') {
241
+ this.textarea = document.createElement('textarea');
242
+ this.textarea.className = 'xterm-helper-textarea';
243
+ this.textarea.setAttribute('aria-label', 'Terminal input');
244
+ this.textarea.setAttribute('autocapitalize', 'off');
245
+ this.textarea.setAttribute('autocomplete', 'off');
246
+ this.textarea.setAttribute('autocorrect', 'off');
247
+ this.textarea.setAttribute('spellcheck', 'false');
248
+ this.textarea.style.position = 'absolute';
249
+ this.textarea.style.opacity = '0';
250
+ this.textarea.style.left = '-9999px';
251
+ this.textarea.style.top = '-9999px';
252
+ this.textarea.style.width = '0';
253
+ this.textarea.style.height = '0';
254
+ this.textarea.style.zIndex = '-10';
255
+ this.attachTextareaHandlers(this.textarea);
256
+ }
257
+ }
258
+ get cols() {
259
+ return this.vt.cols;
260
+ }
261
+ get rows() {
262
+ return this.vt.rows;
263
+ }
264
+ get buffer() {
265
+ const totalLines = this.vt.scrollback.length + this.vt.lines.length;
266
+ const getLineAt = (y) => {
267
+ if (y < this.vt.scrollback.length) {
268
+ return new RealBufferLine(this.vt.scrollback[y]);
269
+ }
270
+ const gridIdx = y - this.vt.scrollback.length;
271
+ if (gridIdx < this.vt.lines.length) {
272
+ return new RealBufferLine(this.vt.lines[gridIdx]);
273
+ }
274
+ return undefined;
275
+ };
276
+ const activeInterface = {
277
+ type: this.vt.isAltBuffer ? 'alternate' : 'normal',
278
+ cursorX: this.vt.cursor.col,
279
+ cursorY: this.vt.cursor.row,
280
+ baseY: this.vt.scrollback.length,
281
+ viewportY: this.vt.viewportY,
282
+ length: totalLines,
283
+ getLine: getLineAt,
284
+ getNullCell: () => new RealBufferCell({
285
+ char: ' ',
286
+ width: 1,
287
+ flags: 0,
288
+ fg: { type: 'default' },
289
+ bg: { type: 'default' },
290
+ }),
291
+ };
292
+ return {
293
+ active: activeInterface,
294
+ normal: activeInterface,
295
+ alternate: { ...activeInterface, type: 'alternate' },
296
+ };
297
+ }
298
+ setTransport(transport) {
299
+ this.transport = transport;
300
+ this.transport.init((diff) => {
301
+ this.renderer.applyDiff(diff);
302
+ for (const listener of this.renderListeners) {
303
+ listener({ start: 0, end: this.rows });
304
+ }
305
+ for (const listener of this.writeParsedListeners) {
306
+ listener();
307
+ }
308
+ });
309
+ }
310
+ loadAddon(addon) {
311
+ this.addons.push(addon);
312
+ addon.activate(this);
313
+ }
314
+ open(container) {
315
+ this.element = container;
316
+ container.style.position = 'relative';
317
+ container.tabIndex = 0;
318
+ container.style.outline = 'none';
319
+ if (this.textarea)
320
+ container.appendChild(this.textarea);
321
+ this.renderer.attach(container);
322
+ this.attachInputHandlers(container);
323
+ this.attachMouseSelectionHandlers(container);
324
+ this.attachDragAndDropHandlers(container);
325
+ this.attachWheelScrollHandlers(container);
326
+ this.attachFocusHandlers(container);
327
+ for (const addon of this.addons) {
328
+ addon.activate(this);
329
+ }
330
+ this.refresh(0, this.rows - 1);
331
+ }
332
+ pendingWrites = [];
333
+ pendingCallbacks = [];
334
+ rafId = null;
335
+ textDecoder = typeof TextDecoder !== 'undefined' ? new TextDecoder() : null;
336
+ write(data, callback) {
337
+ const text = typeof data === 'string'
338
+ ? data
339
+ : (this.textDecoder?.decode(data, { stream: true }) ?? String(data));
340
+ this.pendingWrites.push(text);
341
+ if (callback)
342
+ this.pendingCallbacks.push(callback);
343
+ if (this.rafId === null && typeof requestAnimationFrame !== 'undefined') {
344
+ this.rafId = requestAnimationFrame(() => this.flushWrites());
345
+ }
346
+ else if (typeof requestAnimationFrame === 'undefined') {
347
+ this.flushWrites();
348
+ }
349
+ }
350
+ flush() {
351
+ if (this.rafId !== null && typeof cancelAnimationFrame !== 'undefined') {
352
+ cancelAnimationFrame(this.rafId);
353
+ this.rafId = null;
354
+ }
355
+ if (this.pendingWrites.length > 0) {
356
+ this.flushWrites();
357
+ }
358
+ }
359
+ flushWrites() {
360
+ this.rafId = null;
361
+ if (this.pendingWrites.length === 0)
362
+ return;
363
+ const batch = this.pendingWrites.join('');
364
+ this.pendingWrites = [];
365
+ const callbacks = this.pendingCallbacks;
366
+ this.pendingCallbacks = [];
367
+ const prevCol = this.vt.cursor.col;
368
+ const prevRow = this.vt.cursor.row;
369
+ const diff = this.vt.feed(batch);
370
+ this.renderer.applyDiff(diff);
371
+ if (prevCol !== this.vt.cursor.col || prevRow !== this.vt.cursor.row) {
372
+ for (const listener of this.cursorMoveListeners) {
373
+ listener();
374
+ }
375
+ }
376
+ for (const listener of this.renderListeners) {
377
+ listener({ start: 0, end: this.rows });
378
+ }
379
+ for (const listener of this.writeParsedListeners) {
380
+ listener();
381
+ }
382
+ for (const cb of callbacks) {
383
+ cb();
384
+ }
385
+ }
386
+ writeln(data, callback) {
387
+ if (typeof data === 'string') {
388
+ this.write(`${data}\r\n`, callback);
389
+ }
390
+ else {
391
+ this.write(data, callback);
392
+ }
393
+ }
394
+ paste(data) {
395
+ let payload = data;
396
+ if (this.modes.bracketedPasteMode) {
397
+ payload = `\x1b[200~${data}\x1b[201~`;
398
+ }
399
+ for (const listener of this.dataListeners) {
400
+ listener(payload);
401
+ }
402
+ }
403
+ clear() {
404
+ this.vt.reset();
405
+ this.clearSelection();
406
+ this.renderer.renderAll();
407
+ }
408
+ reset() {
409
+ this.clear();
410
+ }
411
+ refresh(_start, _end) {
412
+ this.renderer.measureFont();
413
+ this.renderer.resizeCanvas();
414
+ this.renderer.renderAll();
415
+ }
416
+ resize(cols, rows) {
417
+ this.options.cols = cols;
418
+ this.options.rows = rows;
419
+ const diff = this.vt.resize(cols, rows);
420
+ this.renderer.resize(cols, rows);
421
+ this.renderer.applyDiff(diff);
422
+ this.renderer.renderAll();
423
+ this.transport?.resize(cols, rows);
424
+ for (const listener of this.resizeListeners) {
425
+ listener({ cols, rows });
426
+ }
427
+ }
428
+ updateTheme(theme) {
429
+ this.options.theme = theme;
430
+ this.renderer?.updateTheme(theme);
431
+ }
432
+ setCursorBlink(blink) {
433
+ this.options.cursorBlink = blink;
434
+ this.renderer?.setCursorBlink(blink);
435
+ }
436
+ setCursorStyle(style) {
437
+ this.options.cursorStyle = style;
438
+ if (this.vt) {
439
+ this.vt.cursorShape = style;
440
+ if (this.vt.cursor) {
441
+ this.vt.cursor.shape = style;
442
+ }
443
+ }
444
+ if (this.renderer) {
445
+ this.renderer.options.cursorStyle = style;
446
+ if (this.renderer.cursor) {
447
+ this.renderer.cursor.shape = style;
448
+ }
449
+ this.renderer.renderAll();
450
+ }
451
+ }
452
+ focus() {
453
+ if (this.options.disableStdin)
454
+ return;
455
+ this.textarea?.focus({ preventScroll: true });
456
+ this.element?.focus({ preventScroll: true });
457
+ }
458
+ blur() {
459
+ this.textarea?.blur();
460
+ this.element?.blur();
461
+ }
462
+ // Scrollback Navigation APIs
463
+ scrollLines(amount) {
464
+ this.vt.scrollLines(amount);
465
+ this.renderer.updateScroll(this.vt.viewportY, this.buffer.active.length);
466
+ const diff = this.vt.feed('');
467
+ this.renderer.applyDiff(diff);
468
+ for (const listener of this.scrollListeners) {
469
+ listener(this.vt.viewportY);
470
+ }
471
+ }
472
+ scrollToBottom() {
473
+ this.vt.scrollToBottom();
474
+ this.renderer.updateScroll(this.vt.viewportY, this.buffer.active.length);
475
+ const diff = this.vt.feed('');
476
+ this.renderer.applyDiff(diff);
477
+ for (const listener of this.scrollListeners) {
478
+ listener(this.vt.viewportY);
479
+ }
480
+ }
481
+ scrollToTop() {
482
+ this.vt.scrollToTop();
483
+ this.renderer.updateScroll(this.vt.viewportY, this.buffer.active.length);
484
+ const diff = this.vt.feed('');
485
+ this.renderer.applyDiff(diff);
486
+ for (const listener of this.scrollListeners) {
487
+ listener(this.vt.viewportY);
488
+ }
489
+ }
490
+ scrollToLine(line) {
491
+ this.vt.scrollToLine(line);
492
+ this.renderer.updateScroll(this.vt.viewportY, this.buffer.active.length);
493
+ const diff = this.vt.feed('');
494
+ this.renderer.applyDiff(diff);
495
+ for (const listener of this.scrollListeners) {
496
+ listener(this.vt.viewportY);
497
+ }
498
+ }
499
+ // Selection & Copy APIs
500
+ select(column, row, length) {
501
+ this.selection = {
502
+ start: { col: column, row },
503
+ end: { col: column + length - 1, row },
504
+ };
505
+ this.renderer.setSelection(this.selection);
506
+ for (const l of this.selectionChangeListeners)
507
+ l();
508
+ }
509
+ selectLines(start, end) {
510
+ this.selection = {
511
+ start: { col: 0, row: Math.max(0, start) },
512
+ end: { col: this.cols - 1, row: Math.min(this.rows - 1, end) },
513
+ };
514
+ this.renderer.setSelection(this.selection);
515
+ for (const l of this.selectionChangeListeners)
516
+ l();
517
+ }
518
+ selectAll() {
519
+ this.selectLines(0, this.rows - 1);
520
+ }
521
+ getSelection() {
522
+ if (!this.selection)
523
+ return '';
524
+ let sRow = this.selection.start.row;
525
+ let sCol = this.selection.start.col;
526
+ let eRow = this.selection.end.row;
527
+ let eCol = this.selection.end.col;
528
+ if (sRow > eRow || (sRow === eRow && sCol > eCol)) {
529
+ const tR = sRow;
530
+ sRow = eRow;
531
+ eRow = tR;
532
+ const tC = sCol;
533
+ sCol = eCol;
534
+ eCol = tC;
535
+ }
536
+ const lines = [];
537
+ for (let r = sRow; r <= eRow; r++) {
538
+ if (r < this.vt.lines.length) {
539
+ const rowCells = this.vt.lines[r];
540
+ const startC = r === sRow ? sCol : 0;
541
+ const endC = r === eRow ? eCol : this.cols - 1;
542
+ const lineText = rowCells
543
+ .slice(Math.max(0, startC), Math.min(this.cols, endC + 1))
544
+ .map((c) => c.char)
545
+ .join('');
546
+ lines.push(lineText.trimEnd());
547
+ }
548
+ }
549
+ return lines.join('\n');
550
+ }
551
+ getSelectionPosition() {
552
+ if (!this.selection)
553
+ return undefined;
554
+ return {
555
+ start: { x: this.selection.start.col, y: this.selection.start.row },
556
+ end: { x: this.selection.end.col, y: this.selection.end.row },
557
+ };
558
+ }
559
+ clearSelection() {
560
+ if (this.selection) {
561
+ this.selection = null;
562
+ this.renderer.setSelection(null);
563
+ for (const l of this.selectionChangeListeners)
564
+ l();
565
+ }
566
+ }
567
+ hasSelection() {
568
+ return this.selection !== null;
569
+ }
570
+ registerMarker(cursorYOffset) {
571
+ const marker = new TerminalMarker(this.nextMarkerId++, (cursorYOffset ?? 0));
572
+ return marker;
573
+ }
574
+ registerDecoration(options) {
575
+ return new TerminalDecoration(options);
576
+ }
577
+ registerLinkProvider(provider) {
578
+ this.linkProviders.push(provider);
579
+ return {
580
+ dispose: () => {
581
+ this.linkProviders = this.linkProviders.filter((p) => p !== provider);
582
+ },
583
+ };
584
+ }
585
+ attachCustomKeyEventHandler(handler) {
586
+ this.customKeyEventHandler = handler;
587
+ }
588
+ onData(callback) {
589
+ this.dataListeners.push(callback);
590
+ return {
591
+ dispose: () => {
592
+ this.dataListeners = this.dataListeners.filter((l) => l !== callback);
593
+ },
594
+ };
595
+ }
596
+ onResize(callback) {
597
+ this.resizeListeners.push(callback);
598
+ return {
599
+ dispose: () => {
600
+ this.resizeListeners = this.resizeListeners.filter((l) => l !== callback);
601
+ },
602
+ };
603
+ }
604
+ onTitleChange(callback) {
605
+ this.titleListeners.push(callback);
606
+ return {
607
+ dispose: () => {
608
+ this.titleListeners = this.titleListeners.filter((l) => l !== callback);
609
+ },
610
+ };
611
+ }
612
+ onBell(callback) {
613
+ this.bellListeners.push(callback);
614
+ return {
615
+ dispose: () => {
616
+ this.bellListeners = this.bellListeners.filter((l) => l !== callback);
617
+ },
618
+ };
619
+ }
620
+ onLineFeed(callback) {
621
+ this.lineFeedListeners.push(callback);
622
+ return {
623
+ dispose: () => {
624
+ this.lineFeedListeners = this.lineFeedListeners.filter((l) => l !== callback);
625
+ },
626
+ };
627
+ }
628
+ onScroll(callback) {
629
+ this.scrollListeners.push(callback);
630
+ return {
631
+ dispose: () => {
632
+ this.scrollListeners = this.scrollListeners.filter((l) => l !== callback);
633
+ },
634
+ };
635
+ }
636
+ onSelectionChange(callback) {
637
+ this.selectionChangeListeners.push(callback);
638
+ return {
639
+ dispose: () => {
640
+ this.selectionChangeListeners = this.selectionChangeListeners.filter((l) => l !== callback);
641
+ },
642
+ };
643
+ }
644
+ onKey(callback) {
645
+ this.keyListeners.push(callback);
646
+ return {
647
+ dispose: () => {
648
+ this.keyListeners = this.keyListeners.filter((l) => l !== callback);
649
+ },
650
+ };
651
+ }
652
+ onWriteParsed(callback) {
653
+ this.writeParsedListeners.push(callback);
654
+ return {
655
+ dispose: () => {
656
+ this.writeParsedListeners = this.writeParsedListeners.filter((l) => l !== callback);
657
+ },
658
+ };
659
+ }
660
+ onRender(callback) {
661
+ this.renderListeners.push(callback);
662
+ return {
663
+ dispose: () => {
664
+ this.renderListeners = this.renderListeners.filter((l) => l !== callback);
665
+ },
666
+ };
667
+ }
668
+ onCursorMove(callback) {
669
+ this.cursorMoveListeners.push(callback);
670
+ return {
671
+ dispose: () => {
672
+ this.cursorMoveListeners = this.cursorMoveListeners.filter((l) => l !== callback);
673
+ },
674
+ };
675
+ }
676
+ handleKeyEvent(e) {
677
+ if (this.options.disableStdin)
678
+ return;
679
+ if (this.customKeyEventHandler && !this.customKeyEventHandler(e)) {
680
+ return;
681
+ }
682
+ for (const listener of this.keyListeners) {
683
+ listener({ key: e.key, domEvent: e });
684
+ }
685
+ const isMac = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.userAgent);
686
+ const cmdOrCtrl = isMac ? e.metaKey : e.ctrlKey;
687
+ // Auto-scroll to bottom on typing
688
+ if (this.vt.viewportY !== this.vt.scrollback.length) {
689
+ this.scrollToBottom();
690
+ }
691
+ // Clipboard Copy: Cmd+C / Ctrl+C
692
+ if (cmdOrCtrl && (e.key === 'c' || e.key === 'C')) {
693
+ if (this.hasSelection()) {
694
+ const text = this.getSelection();
695
+ if (text && typeof navigator !== 'undefined' && navigator.clipboard) {
696
+ navigator.clipboard.writeText(text).catch(() => { });
697
+ return;
698
+ }
699
+ }
700
+ }
701
+ // Clipboard Paste: Cmd+V / Ctrl+V
702
+ if (cmdOrCtrl && (e.key === 'v' || e.key === 'V')) {
703
+ if (typeof navigator !== 'undefined' && navigator.clipboard) {
704
+ navigator.clipboard.readText().then((text) => {
705
+ if (text)
706
+ this.paste(text);
707
+ }).catch(() => { });
708
+ return;
709
+ }
710
+ }
711
+ // Select All: Cmd+A / Ctrl+A
712
+ if (cmdOrCtrl && (e.key === 'a' || e.key === 'A')) {
713
+ e.preventDefault();
714
+ this.selectAll();
715
+ return;
716
+ }
717
+ let payload = '';
718
+ if (e.key === 'Enter')
719
+ payload = '\r';
720
+ else if (e.key === 'Backspace')
721
+ payload = '\x7f';
722
+ else if (e.key === 'Tab') {
723
+ e.preventDefault();
724
+ payload = '\t';
725
+ }
726
+ else if (e.key === 'ArrowUp') {
727
+ e.preventDefault();
728
+ payload = '\x1b[A';
729
+ }
730
+ else if (e.key === 'ArrowDown') {
731
+ e.preventDefault();
732
+ payload = '\x1b[B';
733
+ }
734
+ else if (e.key === 'ArrowRight') {
735
+ e.preventDefault();
736
+ payload = '\x1b[C';
737
+ }
738
+ else if (e.key === 'ArrowLeft') {
739
+ e.preventDefault();
740
+ payload = '\x1b[D';
741
+ }
742
+ else if (e.key === 'Escape') {
743
+ payload = '\x1b';
744
+ }
745
+ else if (e.ctrlKey && e.key.length === 1) {
746
+ const code = e.key.toLowerCase().charCodeAt(0) - 96;
747
+ if (code >= 1 && code <= 26) {
748
+ payload = String.fromCharCode(code);
749
+ }
750
+ }
751
+ else if (e.key.length === 1 && !e.metaKey && !e.altKey) {
752
+ payload = e.key;
753
+ }
754
+ if (payload) {
755
+ this.clearSelection();
756
+ for (const listener of this.dataListeners) {
757
+ listener(payload);
758
+ }
759
+ }
760
+ }
761
+ attachTextareaHandlers(textarea) {
762
+ textarea.addEventListener('keydown', (e) => {
763
+ e.stopPropagation();
764
+ this.handleKeyEvent(e);
765
+ });
766
+ textarea.addEventListener('compositionend', () => {
767
+ if (textarea.value) {
768
+ const val = textarea.value;
769
+ textarea.value = '';
770
+ if (!this.options.disableStdin) {
771
+ this.clearSelection();
772
+ for (const listener of this.dataListeners) {
773
+ listener(val);
774
+ }
775
+ }
776
+ }
777
+ });
778
+ textarea.addEventListener('paste', (e) => {
779
+ e.stopPropagation();
780
+ const text = e.clipboardData?.getData('text/plain');
781
+ if (text) {
782
+ e.preventDefault();
783
+ this.paste(text);
784
+ }
785
+ });
786
+ }
787
+ getGridCoordinates(e) {
788
+ if (!this.element)
789
+ return { col: 0, row: 0 };
790
+ const rect = this.element.getBoundingClientRect();
791
+ const x = Math.max(0, e.clientX - rect.left);
792
+ const y = Math.max(0, e.clientY - rect.top);
793
+ const charW = this.renderer.charWidth || 9;
794
+ const charH = this.renderer.charHeight || 18;
795
+ const col = Math.max(0, Math.min(this.cols - 1, Math.floor(x / charW)));
796
+ const row = Math.max(0, Math.min(this.rows - 1, Math.floor(y / charH)));
797
+ return { col, row };
798
+ }
799
+ attachWheelScrollHandlers(container) {
800
+ container.addEventListener('wheel', (e) => {
801
+ e.preventDefault();
802
+ const isMouseMode = this.vt.mouseMode.x10 ||
803
+ this.vt.mouseMode.button ||
804
+ this.vt.mouseMode.any;
805
+ if (isMouseMode && !e.shiftKey) {
806
+ const pos = this.getGridCoordinates(e);
807
+ const col = pos.col + 1;
808
+ const row = pos.row + 1;
809
+ const btn = e.deltaY < 0 ? 64 : 65;
810
+ this.sendData(`\x1b[<${btn};${col};${row}M`);
811
+ return;
812
+ }
813
+ const delta = Math.sign(e.deltaY) * Math.max(1, Math.abs(Math.round(e.deltaY / 24)));
814
+ this.scrollLines(delta);
815
+ }, { passive: false });
816
+ }
817
+ attachFocusHandlers(container) {
818
+ container.addEventListener('focus', () => {
819
+ if (this.vt.focusReporting) {
820
+ this.sendData('\x1b[I');
821
+ }
822
+ });
823
+ container.addEventListener('blur', () => {
824
+ if (this.vt.focusReporting) {
825
+ this.sendData('\x1b[O');
826
+ }
827
+ });
828
+ }
829
+ sendData(data) {
830
+ for (const listener of this.dataListeners) {
831
+ listener(data);
832
+ }
833
+ }
834
+ scanLinksAtRow(row1Based) {
835
+ this.activeLinks = [];
836
+ for (const provider of this.linkProviders) {
837
+ provider.provideLinks(row1Based, (links) => {
838
+ if (links)
839
+ this.activeLinks.push(...links);
840
+ });
841
+ }
842
+ }
843
+ attachMouseSelectionHandlers(container) {
844
+ container.addEventListener('mousedown', (e) => {
845
+ this.focus();
846
+ const isMouseMode = this.vt.mouseMode.x10 ||
847
+ this.vt.mouseMode.button ||
848
+ this.vt.mouseMode.any;
849
+ if (isMouseMode && !e.shiftKey) {
850
+ const pos = this.getGridCoordinates(e);
851
+ const col = pos.col + 1;
852
+ const row = pos.row + 1;
853
+ const btn = e.button;
854
+ this.sendData(`\x1b[<${btn};${col};${row}M`);
855
+ return;
856
+ }
857
+ if (e.button !== 0)
858
+ return;
859
+ const pos = this.getGridCoordinates(e);
860
+ const visible = this.vt.getVisibleLines();
861
+ const cell = visible[pos.row]?.[pos.col];
862
+ if (cell?.hyperlink && (e.metaKey || e.ctrlKey)) {
863
+ if (typeof window !== 'undefined') {
864
+ window.open(cell.hyperlink, '_blank');
865
+ }
866
+ return;
867
+ }
868
+ // Check for link click
869
+ if (this.currentHoveredILink && (e.metaKey || e.ctrlKey)) {
870
+ this.currentHoveredILink.activate(e, this.currentHoveredILink.text);
871
+ return;
872
+ }
873
+ const now = performance.now();
874
+ if (now - this.lastClickTime < 350) {
875
+ this.clickCount++;
876
+ }
877
+ else {
878
+ this.clickCount = 1;
879
+ }
880
+ this.lastClickTime = now;
881
+ if (this.clickCount === 2) {
882
+ // Double click: Select Word
883
+ const r = pos.row;
884
+ const visible = this.vt.getVisibleLines();
885
+ if (r < visible.length) {
886
+ const cells = visible[r];
887
+ let start = pos.col;
888
+ let end = pos.col;
889
+ while (start > 0 && cells[start - 1]?.char && cells[start - 1].char !== ' ')
890
+ start--;
891
+ while (end < this.cols - 1 && cells[end + 1]?.char && cells[end + 1].char !== ' ')
892
+ end++;
893
+ this.selection = {
894
+ start: { col: start, row: r },
895
+ end: { col: end, row: r },
896
+ };
897
+ this.renderer.setSelection(this.selection);
898
+ for (const l of this.selectionChangeListeners)
899
+ l();
900
+ }
901
+ return;
902
+ }
903
+ if (this.clickCount >= 3) {
904
+ // Triple click: Select Line
905
+ this.selectLines(pos.row, pos.row);
906
+ return;
907
+ }
908
+ this.isMouseDown = true;
909
+ this.clearSelection();
910
+ this.selection = { start: pos, end: pos };
911
+ });
912
+ window.addEventListener('mousemove', (e) => {
913
+ const isMouseMode = this.vt.mouseMode.any ||
914
+ (this.vt.mouseMode.button && this.isMouseDown);
915
+ if (isMouseMode && !e.shiftKey && this.element && this.element.contains(e.target)) {
916
+ const pos = this.getGridCoordinates(e);
917
+ const col = pos.col + 1;
918
+ const row = pos.row + 1;
919
+ this.sendData(`\x1b[<32;${col};${row}M`);
920
+ return;
921
+ }
922
+ if (this.isMouseDown && this.selection) {
923
+ const pos = this.getGridCoordinates(e);
924
+ this.selection.end = pos;
925
+ this.renderer.setSelection(this.selection);
926
+ for (const l of this.selectionChangeListeners)
927
+ l();
928
+ return;
929
+ }
930
+ if (this.element && this.element.contains(e.target)) {
931
+ const pos = this.getGridCoordinates(e);
932
+ const col1Based = pos.col + 1;
933
+ const row1Based = this.vt.viewportY + pos.row + 1;
934
+ this.scanLinksAtRow(row1Based);
935
+ const link = this.activeLinks.find((l) => l.range.start.y === row1Based &&
936
+ col1Based >= l.range.start.x &&
937
+ col1Based <= l.range.end.x);
938
+ if (link) {
939
+ if (this.currentHoveredILink !== link) {
940
+ this.currentHoveredILink?.leave?.(e, this.currentHoveredILink.text);
941
+ this.currentHoveredILink = link;
942
+ link.hover?.(e, link.text);
943
+ }
944
+ this.hoveredLink = {
945
+ startCol: link.range.start.x - 1,
946
+ endCol: link.range.end.x - 1,
947
+ row: pos.row,
948
+ uri: link.text,
949
+ };
950
+ this.renderer.setHoveredLink(this.hoveredLink);
951
+ if (e.metaKey || e.ctrlKey) {
952
+ container.style.cursor = 'pointer';
953
+ }
954
+ else {
955
+ container.style.cursor = 'text';
956
+ }
957
+ }
958
+ else {
959
+ if (this.currentHoveredILink) {
960
+ this.currentHoveredILink.leave?.(e, this.currentHoveredILink.text);
961
+ this.currentHoveredILink = null;
962
+ }
963
+ if (this.hoveredLink) {
964
+ this.hoveredLink = null;
965
+ this.renderer.setHoveredLink(null);
966
+ container.style.cursor = 'text';
967
+ }
968
+ }
969
+ }
970
+ else {
971
+ if (this.currentHoveredILink) {
972
+ this.currentHoveredILink.leave?.(e, this.currentHoveredILink.text);
973
+ this.currentHoveredILink = null;
974
+ }
975
+ if (this.hoveredLink) {
976
+ this.hoveredLink = null;
977
+ this.renderer.setHoveredLink(null);
978
+ }
979
+ }
980
+ });
981
+ window.addEventListener('mouseup', (e) => {
982
+ const isMouseMode = this.vt.mouseMode.x10 ||
983
+ this.vt.mouseMode.button ||
984
+ this.vt.mouseMode.any;
985
+ if (isMouseMode && !e.shiftKey && this.element && this.element.contains(e.target)) {
986
+ const pos = this.getGridCoordinates(e);
987
+ const col = pos.col + 1;
988
+ const row = pos.row + 1;
989
+ const btn = e.button;
990
+ this.sendData(`\x1b[<${btn};${col};${row}m`);
991
+ return;
992
+ }
993
+ if (this.isMouseDown) {
994
+ this.isMouseDown = false;
995
+ if (this.selection &&
996
+ this.selection.start.col === this.selection.end.col &&
997
+ this.selection.start.row === this.selection.end.row &&
998
+ this.clickCount === 1) {
999
+ this.clearSelection();
1000
+ }
1001
+ }
1002
+ });
1003
+ }
1004
+ attachDragAndDropHandlers(container) {
1005
+ let dragCounter = 0;
1006
+ container.addEventListener('dragenter', (e) => {
1007
+ e.preventDefault();
1008
+ dragCounter++;
1009
+ container.classList.add('termax-drop-active');
1010
+ });
1011
+ container.addEventListener('dragover', (e) => {
1012
+ e.preventDefault();
1013
+ if (e.dataTransfer)
1014
+ e.dataTransfer.dropEffect = 'copy';
1015
+ });
1016
+ container.addEventListener('dragleave', () => {
1017
+ dragCounter--;
1018
+ if (dragCounter <= 0) {
1019
+ dragCounter = 0;
1020
+ container.classList.remove('termax-drop-active');
1021
+ }
1022
+ });
1023
+ container.addEventListener('drop', (e) => {
1024
+ e.preventDefault();
1025
+ dragCounter = 0;
1026
+ container.classList.remove('termax-drop-active');
1027
+ if (!e.dataTransfer)
1028
+ return;
1029
+ const files = Array.from(e.dataTransfer.files);
1030
+ if (files.length > 0) {
1031
+ const paths = files.map((f) => {
1032
+ const path = f.path || f.name;
1033
+ return /[\s"'\\]/.test(path) ? `'${path.replace(/'/g, `'\\''`)}'` : path;
1034
+ });
1035
+ this.paste(`${paths.join(' ')} `);
1036
+ }
1037
+ else {
1038
+ const text = e.dataTransfer.getData('text/plain');
1039
+ if (text) {
1040
+ this.paste(text);
1041
+ }
1042
+ }
1043
+ });
1044
+ }
1045
+ attachInputHandlers(container) {
1046
+ container.addEventListener('keydown', (e) => {
1047
+ this.handleKeyEvent(e);
1048
+ });
1049
+ }
1050
+ dispose() {
1051
+ for (const addon of this.addons) {
1052
+ addon.dispose();
1053
+ }
1054
+ this.addons = [];
1055
+ this.transport?.dispose();
1056
+ this.element?.replaceChildren();
1057
+ this.dataListeners = [];
1058
+ this.resizeListeners = [];
1059
+ this.titleListeners = [];
1060
+ this.bellListeners = [];
1061
+ this.lineFeedListeners = [];
1062
+ this.scrollListeners = [];
1063
+ this.selectionChangeListeners = [];
1064
+ this.writeParsedListeners = [];
1065
+ this.renderListeners = [];
1066
+ this.cursorMoveListeners = [];
1067
+ this.keyListeners = [];
1068
+ }
1069
+ }
1070
+ //# sourceMappingURL=Terminal.js.map