@xxmachina/components 19.33.0 → 19.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,7 +12,7 @@ class WeeklyHeaderMolecule {
12
12
  <span>{{ day }}</span>
13
13
  </div>
14
14
  }
15
- `, isInline: true, styles: [":host{display:block;--primary-color: #404040;--primary-color-SCOPED-IN-Vrr9lEwB: var(--primary-color);--secondary-color: #B0B0B0;--secondary-color-SCOPED-IN-Vrr9lEwB: var(--secondary-color)}:host{display:grid;width:100%;grid-template-columns:repeat(7,1fr);background-color:var(--primary-color-SCOPED-IN-Vrr9lEwB)}:host .day-container{display:flex;justify-content:center;align-items:center;color:#fff;font-weight:700;width:calc(hvar(--width) / 7)}\n"] });
15
+ `, isInline: true, styles: [":host{display:block;--primary-color: #404040;--primary-color-SCOPED-IN-eYJMXurT: var(--primary-color);--secondary-color: #B0B0B0;--secondary-color-SCOPED-IN-eYJMXurT: var(--secondary-color)}:host{display:grid;width:100%;grid-template-columns:repeat(7,1fr);background-color:var(--primary-color-SCOPED-IN-eYJMXurT)}:host .day-container{display:flex;justify-content:center;align-items:center;color:#fff;font-weight:700;width:calc(hvar(--width) / 7)}\n"] });
16
16
  }
17
17
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: WeeklyHeaderMolecule, decorators: [{
18
18
  type: Component,
@@ -22,7 +22,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
22
22
  <span>{{ day }}</span>
23
23
  </div>
24
24
  }
25
- `, styles: [":host{display:block;--primary-color: #404040;--primary-color-SCOPED-IN-Vrr9lEwB: var(--primary-color);--secondary-color: #B0B0B0;--secondary-color-SCOPED-IN-Vrr9lEwB: var(--secondary-color)}:host{display:grid;width:100%;grid-template-columns:repeat(7,1fr);background-color:var(--primary-color-SCOPED-IN-Vrr9lEwB)}:host .day-container{display:flex;justify-content:center;align-items:center;color:#fff;font-weight:700;width:calc(hvar(--width) / 7)}\n"] }]
25
+ `, styles: [":host{display:block;--primary-color: #404040;--primary-color-SCOPED-IN-eYJMXurT: var(--primary-color);--secondary-color: #B0B0B0;--secondary-color-SCOPED-IN-eYJMXurT: var(--secondary-color)}:host{display:grid;width:100%;grid-template-columns:repeat(7,1fr);background-color:var(--primary-color-SCOPED-IN-eYJMXurT)}:host .day-container{display:flex;justify-content:center;align-items:center;color:#fff;font-weight:700;width:calc(hvar(--width) / 7)}\n"] }]
26
26
  }], ctorParameters: () => [] });
27
27
 
28
28
  /**
@@ -56,7 +56,7 @@ class CalendarSectionOrganism {
56
56
  }
57
57
  }
58
58
  </div>
59
- `, isInline: true, styles: [":host{display:block;--row: 80px;--row-SCOPED-IN-g44Ra67X: var(--row);--row-num: 0;--row-num-SCOPED-IN-g44Ra67X: var(--row-num);--border-color: #9E9E9E;--border-color-SCOPED-IN-g44Ra67X: var(--border-color);--primary-color: #404040;--primary-color-SCOPED-IN-g44Ra67X: var(--primary-color);--secondary-color: #B0B0B0;--secondary-color-SCOPED-IN-g44Ra67X: var(--secondary-color)}:host{width:100%;height:auto}:host .calendar.header molecules-weekly-header{--height: 32px}:host .calendar.contents{display:grid;width:var(--width);border:1px solid var(--border-color-SCOPED-IN-g44Ra67X);grid-gap:1px;grid-template-columns:repeat(7,1fr);grid-template-rows:repeat(var(--row-num-SCOPED-IN-g44Ra67X),var(--row-SCOPED-IN-g44Ra67X));box-sizing:border-box;background-color:var(--border-color-SCOPED-IN-g44Ra67X)}:host .calendar.contents molecules-daily-cell{--width: calc((var(--width) - 8px) / 7);--height: var(--row-SCOPED-IN-g44Ra67X);background-color:#fff}:host .calendar.description{display:flex;justify-content:flex-end;width:var(--width);height:32px;padding-right:16px}:host .calendar.description .description.container{display:flex;flex-direction:row;justify-content:flex-end;align-items:center;font-size:12px}:host .calendar.description .description.container .circle{display:flex;width:20px;height:20px;justify-content:center;align-items:center;border-radius:50%;color:#fff;background:#00f}:host .calendar.description .description.container label{padding-left:4px;padding-right:8px}\n"], dependencies: [{ kind: "component", type: DailyCellMolecule, selector: "molecules-daily-cell", inputs: ["date", "schedules", "displayDate", "noSchedulesText", "toolTipText", "marked", "markingColor"] }, { kind: "component", type: WeeklyHeaderMolecule, selector: "molecules-weekly-header" }, { kind: "ngmodule", type: MatRippleModule }, { kind: "directive", type: i1.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
59
+ `, isInline: true, styles: [":host{display:block;--row: 80px;--row-SCOPED-IN-wti5W38b: var(--row);--row-num: 0;--row-num-SCOPED-IN-wti5W38b: var(--row-num);--border-color: #9E9E9E;--border-color-SCOPED-IN-wti5W38b: var(--border-color);--primary-color: #404040;--primary-color-SCOPED-IN-wti5W38b: var(--primary-color);--secondary-color: #B0B0B0;--secondary-color-SCOPED-IN-wti5W38b: var(--secondary-color)}:host{width:100%;height:auto}:host .calendar.header molecules-weekly-header{--height: 32px}:host .calendar.contents{display:grid;width:var(--width);border:1px solid var(--border-color-SCOPED-IN-wti5W38b);grid-gap:1px;grid-template-columns:repeat(7,1fr);grid-template-rows:repeat(var(--row-num-SCOPED-IN-wti5W38b),var(--row-SCOPED-IN-wti5W38b));box-sizing:border-box;background-color:var(--border-color-SCOPED-IN-wti5W38b)}:host .calendar.contents molecules-daily-cell{--width: calc((var(--width) - 8px) / 7);--height: var(--row-SCOPED-IN-wti5W38b);background-color:#fff}:host .calendar.description{display:flex;justify-content:flex-end;width:var(--width);height:32px;padding-right:16px}:host .calendar.description .description.container{display:flex;flex-direction:row;justify-content:flex-end;align-items:center;font-size:12px}:host .calendar.description .description.container .circle{display:flex;width:20px;height:20px;justify-content:center;align-items:center;border-radius:50%;color:#fff;background:#00f}:host .calendar.description .description.container label{padding-left:4px;padding-right:8px}\n"], dependencies: [{ kind: "component", type: DailyCellMolecule, selector: "molecules-daily-cell", inputs: ["date", "schedules", "displayDate", "noSchedulesText", "toolTipText", "marked", "markingColor"] }, { kind: "component", type: WeeklyHeaderMolecule, selector: "molecules-weekly-header" }, { kind: "ngmodule", type: MatRippleModule }, { kind: "directive", type: i1.MatRipple, selector: "[mat-ripple], [matRipple]", inputs: ["matRippleColor", "matRippleUnbounded", "matRippleCentered", "matRippleRadius", "matRippleAnimation", "matRippleDisabled", "matRippleTrigger"], exportAs: ["matRipple"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
60
60
  }
61
61
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CalendarSectionOrganism, decorators: [{
62
62
  type: Component,
@@ -90,7 +90,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImpor
90
90
  }
91
91
  }
92
92
  </div>
93
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;--row: 80px;--row-SCOPED-IN-g44Ra67X: var(--row);--row-num: 0;--row-num-SCOPED-IN-g44Ra67X: var(--row-num);--border-color: #9E9E9E;--border-color-SCOPED-IN-g44Ra67X: var(--border-color);--primary-color: #404040;--primary-color-SCOPED-IN-g44Ra67X: var(--primary-color);--secondary-color: #B0B0B0;--secondary-color-SCOPED-IN-g44Ra67X: var(--secondary-color)}:host{width:100%;height:auto}:host .calendar.header molecules-weekly-header{--height: 32px}:host .calendar.contents{display:grid;width:var(--width);border:1px solid var(--border-color-SCOPED-IN-g44Ra67X);grid-gap:1px;grid-template-columns:repeat(7,1fr);grid-template-rows:repeat(var(--row-num-SCOPED-IN-g44Ra67X),var(--row-SCOPED-IN-g44Ra67X));box-sizing:border-box;background-color:var(--border-color-SCOPED-IN-g44Ra67X)}:host .calendar.contents molecules-daily-cell{--width: calc((var(--width) - 8px) / 7);--height: var(--row-SCOPED-IN-g44Ra67X);background-color:#fff}:host .calendar.description{display:flex;justify-content:flex-end;width:var(--width);height:32px;padding-right:16px}:host .calendar.description .description.container{display:flex;flex-direction:row;justify-content:flex-end;align-items:center;font-size:12px}:host .calendar.description .description.container .circle{display:flex;width:20px;height:20px;justify-content:center;align-items:center;border-radius:50%;color:#fff;background:#00f}:host .calendar.description .description.container label{padding-left:4px;padding-right:8px}\n"] }]
93
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;--row: 80px;--row-SCOPED-IN-wti5W38b: var(--row);--row-num: 0;--row-num-SCOPED-IN-wti5W38b: var(--row-num);--border-color: #9E9E9E;--border-color-SCOPED-IN-wti5W38b: var(--border-color);--primary-color: #404040;--primary-color-SCOPED-IN-wti5W38b: var(--primary-color);--secondary-color: #B0B0B0;--secondary-color-SCOPED-IN-wti5W38b: var(--secondary-color)}:host{width:100%;height:auto}:host .calendar.header molecules-weekly-header{--height: 32px}:host .calendar.contents{display:grid;width:var(--width);border:1px solid var(--border-color-SCOPED-IN-wti5W38b);grid-gap:1px;grid-template-columns:repeat(7,1fr);grid-template-rows:repeat(var(--row-num-SCOPED-IN-wti5W38b),var(--row-SCOPED-IN-wti5W38b));box-sizing:border-box;background-color:var(--border-color-SCOPED-IN-wti5W38b)}:host .calendar.contents molecules-daily-cell{--width: calc((var(--width) - 8px) / 7);--height: var(--row-SCOPED-IN-wti5W38b);background-color:#fff}:host .calendar.description{display:flex;justify-content:flex-end;width:var(--width);height:32px;padding-right:16px}:host .calendar.description .description.container{display:flex;flex-direction:row;justify-content:flex-end;align-items:center;font-size:12px}:host .calendar.description .description.container .circle{display:flex;width:20px;height:20px;justify-content:center;align-items:center;border-radius:50%;color:#fff;background:#00f}:host .calendar.description .description.container label{padding-left:4px;padding-right:8px}\n"] }]
94
94
  }], propDecorators: { label: [{
95
95
  type: Input
96
96
  }], displayDays: [{
@@ -1,20 +1,16 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, Directive, viewChild, inject, ElementRef, output, signal, effect, ChangeDetectionStrategy, Component } from '@angular/core';
2
+ import { InjectionToken, input, inject, Directive, viewChild, ElementRef, output, signal, effect, ChangeDetectionStrategy, Component } from '@angular/core';
3
3
  import { Terminal } from '@xterm/xterm';
4
4
  import { FitAddon } from '@xterm/addon-fit';
5
5
  import { InjectableComponent, NgAtomicComponent } from '@ng-atomic/core';
6
6
  import { makeDI } from '@ng-atomic/common/services/ui';
7
7
 
8
- const URL_REGEX = /https?:\/\/[^\s<>'")\]]+/g;
8
+ const URL_REGEX = /https?:\/\/[^\s<>'")\[\]]+/g;
9
+ const LOCALHOST_REGEX = /^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d+)?(?:\/|$)/;
9
10
  const MAX_SCAN_LINES = 30;
10
11
  /**
11
12
  * Link provider that detects URLs spanning multiple terminal lines.
12
- *
13
- * Handles two wrapping scenarios:
14
- * 1. Terminal wrapping (isWrapped=true) - xterm splits long output
15
- * 2. Application wrapping (isWrapped=false) - e.g. Claude Code/Ink explicitly
16
- * breaks lines at some width. Detected by heuristic: previous line ends
17
- * without whitespace and current line starts without whitespace.
13
+ * localhost/127.0.0.1 URLs are skipped (handled by ProxyLinksAddon).
18
14
  */
19
15
  class WebLinkProvider {
20
16
  _terminal;
@@ -27,14 +23,12 @@ class WebLinkProvider {
27
23
  }
28
24
  provideLinks(bufferLineNumber, callback) {
29
25
  const buffer = this._terminal.buffer.active;
30
- // Find the start of this line group (0-indexed)
31
26
  let startY = bufferLineNumber - 1;
32
27
  while (startY > 0
33
28
  && (bufferLineNumber - 1 - startY) < MAX_SCAN_LINES
34
29
  && this._isLineContinuation(startY)) {
35
30
  startY--;
36
31
  }
37
- // Collect text from all lines in the group
38
32
  const lineTexts = [];
39
33
  let y = startY;
40
34
  do {
@@ -51,6 +45,8 @@ class WebLinkProvider {
51
45
  let match;
52
46
  const links = [];
53
47
  while ((match = URL_REGEX.exec(fullText)) !== null) {
48
+ if (LOCALHOST_REGEX.test(match[0]))
49
+ continue;
54
50
  const startPos = this._offsetToPos(match.index, startY, lineTexts);
55
51
  const endPos = this._offsetToPos(match.index + match[0].length - 1, startY, lineTexts);
56
52
  if (!startPos || !endPos)
@@ -66,13 +62,6 @@ class WebLinkProvider {
66
62
  }
67
63
  callback(links.length > 0 ? links : undefined);
68
64
  }
69
- /**
70
- * Determine if a line is a continuation of the previous line.
71
- * True when either:
72
- * - xterm marks it as wrapped (isWrapped=true), OR
73
- * - no whitespace at line boundary (previous line ends without space,
74
- * current line starts without space) suggesting mid-token line break
75
- */
76
65
  _isLineContinuation(lineIdx) {
77
66
  const buffer = this._terminal.buffer.active;
78
67
  const line = buffer.getLine(lineIdx);
@@ -92,7 +81,6 @@ class WebLinkProvider {
92
81
  && currentText.length > 0
93
82
  && !/^\s/.test(currentText));
94
83
  }
95
- /** Convert a character offset in the concatenated text to a buffer position. */
96
84
  _offsetToPos(offset, startY, lineTexts) {
97
85
  let remaining = offset;
98
86
  for (let i = 0; i < lineTexts.length; i++) {
@@ -109,6 +97,7 @@ class WebLinkProvider {
109
97
  /**
110
98
  * Drop-in replacement for @xterm/addon-web-links that supports
111
99
  * URLs spanning wrapped terminal lines.
100
+ * localhost/127.0.0.1 URLs are skipped (handled by ProxyLinksAddon).
112
101
  */
113
102
  class WebLinksAddon {
114
103
  _handler;
@@ -358,6 +347,98 @@ class FileLinksAddon {
358
347
  }
359
348
  }
360
349
 
350
+ /**
351
+ * localhost/127.0.0.1 URLをmachina web proxy URLに変換するリンクプロバイダ。
352
+ *
353
+ * 変換ルール:
354
+ * http://localhost:4200/path → https://{serverUUID}--4200.proxy.machina.at/__content__/path
355
+ * http://127.0.0.1:3000/api → https://{serverUUID}--3000.proxy.machina.at/__content__/api
356
+ */
357
+ const LOCALHOST_URL_REGEX = /https?:\/\/(?:localhost|127\.0\.0\.1)(?::(\d+))?(\/[^\s<>'")\]]*)?/g;
358
+ class ProxyLinkProvider {
359
+ _terminal;
360
+ _getServerUUID;
361
+ _handler;
362
+ constructor(_terminal, _getServerUUID, _handler = (_, url) => {
363
+ window.open(url, '_blank', 'noopener');
364
+ }) {
365
+ this._terminal = _terminal;
366
+ this._getServerUUID = _getServerUUID;
367
+ this._handler = _handler;
368
+ }
369
+ provideLinks(bufferLineNumber, callback) {
370
+ const lineIndex = bufferLineNumber - 1;
371
+ const line = this._terminal.buffer.active.getLine(lineIndex);
372
+ if (!line) {
373
+ callback(undefined);
374
+ return;
375
+ }
376
+ const text = line.translateToString(true);
377
+ const links = [];
378
+ console.log('[proxy-link] provideLinks line', bufferLineNumber, 'text:', text.substring(0, 80));
379
+ LOCALHOST_URL_REGEX.lastIndex = 0;
380
+ let match;
381
+ while ((match = LOCALHOST_URL_REGEX.exec(text)) !== null) {
382
+ const matchedUrl = match[0];
383
+ const port = match[1] || '80';
384
+ const path = match[2] || '/';
385
+ const startX = match.index + 1;
386
+ const endX = match.index + matchedUrl.length;
387
+ links.push({
388
+ text: matchedUrl,
389
+ range: {
390
+ start: { x: startX, y: bufferLineNumber },
391
+ end: { x: endX, y: bufferLineNumber },
392
+ },
393
+ // serverUUIDはクリック時にlazy評価(セッション接続後に値が入る)
394
+ activate: (event) => {
395
+ const uuid = this._getServerUUID();
396
+ const proxyUrl = uuid
397
+ ? `https://${uuid}--${port}.proxy.machina.at/__content__${path}`
398
+ : null;
399
+ const targetUrl = proxyUrl || matchedUrl;
400
+ console.log('[proxy-link] activate:', { matchedUrl, uuid, proxyUrl, targetUrl });
401
+ this._handler(event, targetUrl);
402
+ },
403
+ });
404
+ }
405
+ callback(links.length > 0 ? links : undefined);
406
+ }
407
+ }
408
+ function toProxyUrl(serverUUID, localhostUrl) {
409
+ const match = localhostUrl.match(/^https?:\/\/(?:localhost|127\.0\.0\.1)(?::(\d+))?(\/.*)?$/);
410
+ if (!match)
411
+ return null;
412
+ const port = match[1] || '80';
413
+ const path = match[2] || '/';
414
+ return `https://${serverUUID}--${port}.proxy.machina.at/__content__${path}`;
415
+ }
416
+
417
+ /**
418
+ * xterm.js addon: localhost URLをmachina web proxyにリダイレクトするリンクを表示。
419
+ *
420
+ * Usage:
421
+ * ```typescript
422
+ * const addon = new ProxyLinksAddon(() => currentServerUUID);
423
+ * terminal.loadAddon(addon);
424
+ * ```
425
+ */
426
+ class ProxyLinksAddon {
427
+ _getServerUUID;
428
+ _handler;
429
+ _linkProvider;
430
+ constructor(_getServerUUID, _handler) {
431
+ this._getServerUUID = _getServerUUID;
432
+ this._handler = _handler;
433
+ }
434
+ activate(terminal) {
435
+ this._linkProvider = terminal.registerLinkProvider(new ProxyLinkProvider(terminal, this._getServerUUID, this._handler));
436
+ }
437
+ dispose() {
438
+ this._linkProvider?.dispose();
439
+ }
440
+ }
441
+
361
442
  var XtermActionId;
362
443
  (function (XtermActionId) {
363
444
  XtermActionId["DATA_INPUT"] = "xterm:data-input";
@@ -366,6 +447,8 @@ var XtermActionId;
366
447
  XtermActionId["FILE_LINK_CLICK"] = "xterm:file-link-click";
367
448
  XtermActionId["FOCUS_INPUT_REQUEST"] = "xterm:focus-input-request";
368
449
  })(XtermActionId || (XtermActionId = {}));
450
+ /** DI token for providing serverUUID getter to ProxyLinksAddon */
451
+ const XTERM_SERVER_UUID = new InjectionToken('XTERM_SERVER_UUID');
369
452
  class XtermOrganismStore extends InjectableComponent {
370
453
  static DI = makeDI(XtermOrganismStore, () => () => ({
371
454
  data: '',
@@ -374,15 +457,13 @@ class XtermOrganismStore extends InjectableComponent {
374
457
  useInteractiveTheme: false,
375
458
  }), ['components', 'organisms', 'xterm']);
376
459
  config = XtermOrganismStore.DI.injectConfig();
377
- // Note: Using simple default values instead of _computed() to ensure
378
- // input bindings from templates work correctly with Angular signal effects
379
460
  data = input(undefined, ...(ngDevMode ? [{ debugName: "data" }] : []));
380
461
  interactive = input(false, ...(ngDevMode ? [{ debugName: "interactive" }] : []));
381
462
  queryResult = input('', ...(ngDevMode ? [{ debugName: "queryResult" }] : []));
382
- /** Use interactive theme even when not interactive (for display consistency) */
383
463
  useInteractiveTheme = input(false, ...(ngDevMode ? [{ debugName: "useInteractiveTheme" }] : []));
384
- /** Total bytes written to the buffer (for accurate diff detection when buffer overflows) */
385
464
  totalWritten = input(0, ...(ngDevMode ? [{ debugName: "totalWritten" }] : []));
465
+ /** Injected serverUUID getter for proxy links (optional) */
466
+ getServerUUID = inject(XTERM_SERVER_UUID, { optional: true });
386
467
  constructor() {
387
468
  super();
388
469
  XtermOrganismStore.DI.initialize(this);
@@ -456,6 +537,12 @@ class XtermOrganism extends NgAtomicComponent {
456
537
  terminalInitialized = signal(false, ...(ngDevMode ? [{ debugName: "terminalInitialized" }] : []));
457
538
  /** Tracks if initialization was skipped due to container being too small */
458
539
  initSkippedDueToSize = false;
540
+ /** IDisposable refs for explicit cleanup (prevents memory leaks on mobile) */
541
+ disposables = [];
542
+ /** Timer refs for cleanup in ngOnDestroy */
543
+ stableTimer = null;
544
+ resizeTimer = null;
545
+ fallbackTimer = null;
459
546
  constructor() {
460
547
  super();
461
548
  // Watch for data input changes (with totalWritten support for buffer overflow handling)
@@ -607,35 +694,36 @@ class XtermOrganism extends NgAtomicComponent {
607
694
  }
608
695
  });
609
696
  this.terminal.loadAddon(this.fileLinksAddon);
697
+ // ProxyLinksAddon: localhost → proxy.machina.at redirect (always loaded)
698
+ this.terminal.loadAddon(new ProxyLinksAddon(this.store.getServerUUID ?? (() => undefined)));
610
699
  this.terminal.open(containerElement);
611
700
  // Setup event handlers for interactive mode
612
701
  if (isInteractive) {
613
- this.terminal.onData((data) => {
702
+ this.disposables.push(this.terminal.onData((data) => {
614
703
  this.dispatch({ id: XtermActionId.DATA_INPUT, payload: data });
615
- });
616
- this.terminal.onResize(({ cols, rows }) => this.dispatch({ id: XtermActionId.RESIZED, payload: { cols, rows } }));
704
+ }), this.terminal.onResize(({ cols, rows }) => this.dispatch({ id: XtermActionId.RESIZED, payload: { cols, rows } })));
617
705
  }
618
706
  // Use ResizeObserver with debounce to wait for container size to stabilize
619
707
  // (CSS transitions take 300-500ms, so fixed setTimeout(100ms) is too early)
620
- let stableTimer = null;
621
- let resizeTimer = null;
622
708
  this.resizeObserver = new ResizeObserver(() => {
623
709
  try {
624
710
  if (!this.terminalInitialized()) {
625
711
  this.fitAddon.fit();
626
712
  // Debounce: wait for container to stop changing before initializing
627
- if (stableTimer)
628
- clearTimeout(stableTimer);
629
- stableTimer = setTimeout(() => {
713
+ if (this.stableTimer)
714
+ clearTimeout(this.stableTimer);
715
+ this.stableTimer = setTimeout(() => {
716
+ this.stableTimer = null;
630
717
  this.fitAddon.fit();
631
718
  this.markInitialized(isInteractive);
632
719
  }, 150);
633
720
  }
634
721
  else if (isInteractive) {
635
722
  // Debounce resize to wait for CSS transitions to complete (300ms)
636
- if (resizeTimer)
637
- clearTimeout(resizeTimer);
638
- resizeTimer = setTimeout(() => {
723
+ if (this.resizeTimer)
724
+ clearTimeout(this.resizeTimer);
725
+ this.resizeTimer = setTimeout(() => {
726
+ this.resizeTimer = null;
639
727
  this.fitAddon.fit();
640
728
  this.dispatch({ id: XtermActionId.RESIZED, payload: { cols: this.cols, rows: this.rows } });
641
729
  }, 300);
@@ -647,7 +735,8 @@ class XtermOrganism extends NgAtomicComponent {
647
735
  });
648
736
  this.resizeObserver.observe(containerElement);
649
737
  // Fallback: initialize after 500ms if ResizeObserver hasn't triggered stable state
650
- setTimeout(() => {
738
+ this.fallbackTimer = setTimeout(() => {
739
+ this.fallbackTimer = null;
651
740
  if (!this.terminalInitialized()) {
652
741
  this.fitAddon.fit();
653
742
  this.markInitialized(isInteractive);
@@ -655,7 +744,18 @@ class XtermOrganism extends NgAtomicComponent {
655
744
  }, 500);
656
745
  }
657
746
  ngOnDestroy() {
747
+ // Clear pending timers to prevent post-destroy callbacks
748
+ if (this.stableTimer)
749
+ clearTimeout(this.stableTimer);
750
+ if (this.resizeTimer)
751
+ clearTimeout(this.resizeTimer);
752
+ if (this.fallbackTimer)
753
+ clearTimeout(this.fallbackTimer);
658
754
  this.resizeObserver?.disconnect();
755
+ // Dispose xterm event handlers (onData, onResize) to prevent memory leaks
756
+ for (const d of this.disposables)
757
+ d.dispose();
758
+ this.disposables.length = 0;
659
759
  // Remove terminal instance from window for E2E testing cleanup
660
760
  if (typeof window !== 'undefined' && window.__XTERM_INSTANCES__) {
661
761
  const instances = window.__XTERM_INSTANCES__;
@@ -1003,5 +1103,5 @@ async function getMachinaAsciiArtSixel(options = {}) {
1003
1103
  * Generated bundle index. Do not edit.
1004
1104
  */
1005
1105
 
1006
- export { GitHubLinkProvider, GitHubLinksAddon, WebLinksAddon, XtermActionId, XtermOrganism, XtermOrganismStore, asciiArtToSvg, generateAsciiArt, getMachinaAsciiArtSixel, svgToSixel };
1106
+ export { GitHubLinkProvider, GitHubLinksAddon, ProxyLinkProvider, ProxyLinksAddon, WebLinksAddon, XTERM_SERVER_UUID, XtermActionId, XtermOrganism, XtermOrganismStore, asciiArtToSvg, generateAsciiArt, getMachinaAsciiArtSixel, svgToSixel, toProxyUrl };
1007
1107
  //# sourceMappingURL=xxmachina-components-organisms-xterm.mjs.map