@floegence/floeterm-terminal-web 0.5.21 → 0.5.23

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 (43) hide show
  1. package/README.md +17 -2
  2. package/dist/core/PagedTerminalOutputCoordinator.d.ts +6 -0
  3. package/dist/core/PagedTerminalOutputCoordinator.d.ts.map +1 -1
  4. package/dist/core/PagedTerminalOutputCoordinator.js +30 -0
  5. package/dist/core/PagedTerminalOutputCoordinator.js.map +1 -1
  6. package/dist/core/TerminalCore.d.ts +4 -3
  7. package/dist/core/TerminalCore.d.ts.map +1 -1
  8. package/dist/core/TerminalCore.js +49 -95
  9. package/dist/core/TerminalCore.js.map +1 -1
  10. package/dist/core/TerminalInputBridge.js +1 -1
  11. package/dist/core/TerminalInputBridge.js.map +1 -1
  12. package/dist/entries/history.d.ts +4 -0
  13. package/dist/entries/history.d.ts.map +1 -0
  14. package/dist/entries/history.js +2 -0
  15. package/dist/entries/history.js.map +1 -0
  16. package/dist/entries/preload.d.ts +3 -0
  17. package/dist/entries/preload.d.ts.map +1 -0
  18. package/dist/entries/preload.js +2 -0
  19. package/dist/entries/preload.js.map +1 -0
  20. package/dist/entries/sessions.d.ts +4 -0
  21. package/dist/entries/sessions.d.ts.map +1 -0
  22. package/dist/entries/sessions.js +2 -0
  23. package/dist/entries/sessions.js.map +1 -0
  24. package/dist/fabric/BeamtermFabricRenderer.d.ts +3 -3
  25. package/dist/fabric/BeamtermFabricRenderer.d.ts.map +1 -1
  26. package/dist/fabric/BeamtermFabricRenderer.js +2 -13
  27. package/dist/fabric/BeamtermFabricRenderer.js.map +1 -1
  28. package/dist/index.d.ts +3 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +2 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/internal/BeamtermResourceLoader.d.ts +3 -0
  33. package/dist/internal/BeamtermResourceLoader.d.ts.map +1 -0
  34. package/dist/internal/BeamtermResourceLoader.js +14 -0
  35. package/dist/internal/BeamtermResourceLoader.js.map +1 -0
  36. package/dist/internal/TerminalResourceLoader.d.ts +7 -0
  37. package/dist/internal/TerminalResourceLoader.d.ts.map +1 -0
  38. package/dist/internal/TerminalResourceLoader.js +61 -0
  39. package/dist/internal/TerminalResourceLoader.js.map +1 -0
  40. package/dist/sessions/TerminalSessionsCoordinator.d.ts.map +1 -1
  41. package/dist/sessions/TerminalSessionsCoordinator.js +5 -1
  42. package/dist/sessions/TerminalSessionsCoordinator.js.map +1 -1
  43. package/package.json +20 -2
@@ -4,7 +4,7 @@ import { cursorToFabricCursor, mapGhosttyRowToFabricCells, renderReasonFromForce
4
4
  import { terminalFabricCoordinator } from '../fabric/TerminalFabricCoordinator.js';
5
5
  import { TerminalState, } from '../types.js';
6
6
  import { terminalInitializationScheduler, } from '../internal/TerminalInitializationScheduler.js';
7
- import { loadBeamtermModule } from '../fabric/BeamtermFabricRenderer.js';
7
+ import { getGhosttyFitAddonConstructor, getGhosttyTerminalConstructor, loadGhosttyModules, waitWithAbort, } from '../internal/TerminalResourceLoader.js';
8
8
  import { resolveTerminalInputElement, TerminalInputBridge } from './TerminalInputBridge.js';
9
9
  import { terminalRenderScheduler } from './TerminalRenderScheduler.js';
10
10
  import { scheduleUiTurn } from '../internal/scheduleUiTurn.js';
@@ -113,64 +113,7 @@ const getPerfProbe = () => {
113
113
  }
114
114
  return window.__floetermPerfProbe;
115
115
  };
116
- // Dynamic imports avoid SSR issues and keep the bundle flexible.
117
- let TerminalCtor = null;
118
- let FitAddonCtor = null;
119
- let ghosttyResourcesPromise = null;
120
- const abortError = () => {
121
- if (typeof DOMException !== 'undefined')
122
- return new DOMException('Operation aborted', 'AbortError');
123
- const error = new Error('Operation aborted');
124
- error.name = 'AbortError';
125
- return error;
126
- };
127
- const waitWithAbort = (promise, signal) => {
128
- if (!signal)
129
- return promise;
130
- if (signal.aborted)
131
- return Promise.reject(abortError());
132
- return new Promise((resolve, reject) => {
133
- const onAbort = () => reject(abortError());
134
- signal.addEventListener('abort', onAbort, { once: true });
135
- promise.then(value => {
136
- signal.removeEventListener('abort', onAbort);
137
- resolve(value);
138
- }, error => {
139
- signal.removeEventListener('abort', onAbort);
140
- reject(error);
141
- });
142
- });
143
- };
144
- const loadGhosttyModules = async (logger, signal) => {
145
- if (typeof window === 'undefined') {
146
- throw new Error('ghostty-web 只能在浏览器环境中加载');
147
- }
148
- if (TerminalCtor && FitAddonCtor) {
149
- return;
150
- }
151
- if (!ghosttyResourcesPromise) {
152
- logger.debug('[TerminalCore] Initializing ghostty-web WASM');
153
- ghosttyResourcesPromise = (async () => {
154
- const { Terminal, FitAddon, init } = await import('ghostty-web');
155
- await init();
156
- TerminalCtor = Terminal;
157
- FitAddonCtor = FitAddon;
158
- })().catch((error) => {
159
- TerminalCtor = null;
160
- FitAddonCtor = null;
161
- ghosttyResourcesPromise = null;
162
- throw error;
163
- });
164
- }
165
- await waitWithAbort(ghosttyResourcesPromise, signal);
166
- };
167
- export const preloadTerminalResources = async (options = {}) => {
168
- const resources = Promise.all([
169
- loadGhosttyModules(options.logger ?? noopLogger),
170
- loadBeamtermModule(),
171
- ]).then(() => undefined);
172
- await waitWithAbort(resources, options.signal);
173
- };
116
+ export { preloadTerminalResources } from '../internal/TerminalResourceLoader.js';
174
117
  const mapThemeToGhostty = (theme) => {
175
118
  if (!theme) {
176
119
  return {};
@@ -404,6 +347,7 @@ export class TerminalCore {
404
347
  this.fabricAttachSeq = 0;
405
348
  this.cancelFabricAttachSchedule = null;
406
349
  this.cancelInputRefocusSchedule = null;
350
+ this.inputElement = null;
407
351
  this.viewportHost = null;
408
352
  this.renderHost = null;
409
353
  this.resizeObserver = null;
@@ -588,12 +532,15 @@ export class TerminalCore {
588
532
  this.fabricView = null;
589
533
  this.terminal?.dispose();
590
534
  this.terminal = null;
535
+ this.inputElement?.remove();
536
+ this.inputElement = null;
591
537
  this.fitAddon = null;
592
538
  this.viewportHost?.remove();
593
539
  this.viewportHost = null;
594
540
  this.renderHost = null;
595
541
  }
596
542
  async createTerminalInstance(signal) {
543
+ const TerminalCtor = getGhosttyTerminalConstructor();
597
544
  if (!TerminalCtor) {
598
545
  throw new Error('ghostty-web module not loaded');
599
546
  }
@@ -644,6 +591,7 @@ export class TerminalCore {
644
591
  if (!this.terminal) {
645
592
  throw new Error('Terminal instance not created');
646
593
  }
594
+ const FitAddonCtor = getGhosttyFitAddonConstructor();
647
595
  if (!FitAddonCtor) {
648
596
  throw new Error('Required ghostty-web addons not loaded');
649
597
  }
@@ -661,6 +609,7 @@ export class TerminalCore {
661
609
  this.applyPresentationScaleStyles();
662
610
  this.installDemandRenderPatchBeforeOpen();
663
611
  this.terminal.open(this.renderHost ?? this.container);
612
+ this.mountInputElement();
664
613
  this.stopGhosttyRenderLoop();
665
614
  this.patchDemandRenderTriggersAfterOpen();
666
615
  this.patchSelectionManagerClipboardBehavior();
@@ -669,6 +618,18 @@ export class TerminalCore {
669
618
  this.applyRegisteredLinkProviders();
670
619
  void this.refreshFontMetricsAfterLoad('open');
671
620
  }
621
+ mountInputElement() {
622
+ const input = resolveTerminalInputElement(this.renderHost ?? this.container);
623
+ if (!input) {
624
+ throw new Error('ghostty-web terminal input element was not created');
625
+ }
626
+ const body = this.container.ownerDocument.body;
627
+ if (!body) {
628
+ throw new Error('Terminal input requires a document body');
629
+ }
630
+ body.appendChild(input);
631
+ this.inputElement = input;
632
+ }
672
633
  installFitAddonGeometryPatch() {
673
634
  const fitAddon = this.fitAddon;
674
635
  if (!fitAddon || fitAddon.__floetermGeometryPatchApplied) {
@@ -791,7 +752,7 @@ export class TerminalCore {
791
752
  this.syncImeInputAnchor();
792
753
  }
793
754
  syncImeInputAnchor() {
794
- const input = resolveTerminalInputElement(this.container);
755
+ const input = this.inputElement;
795
756
  if (!input || this.shouldPreserveTransientInputPlacement(input)) {
796
757
  return;
797
758
  }
@@ -801,17 +762,34 @@ export class TerminalCore {
801
762
  return;
802
763
  }
803
764
  const canvasRect = geometry.canvas.getBoundingClientRect();
804
- const left = Number(canvasRect.left) + cursor.x * geometry.cellWidth;
805
- const top = Number(canvasRect.top) + cursor.y * geometry.cellHeight;
806
- if (!Number.isFinite(left) || !Number.isFinite(top)) {
765
+ const canvasClientWidth = Number(geometry.canvas.clientWidth);
766
+ const canvasClientHeight = Number(geometry.canvas.clientHeight);
767
+ if (!Number.isFinite(canvasClientWidth)
768
+ || !Number.isFinite(canvasClientHeight)
769
+ || canvasClientWidth <= 0
770
+ || canvasClientHeight <= 0) {
771
+ return;
772
+ }
773
+ const visibleScaleX = Number(canvasRect.width) / canvasClientWidth;
774
+ const visibleScaleY = Number(canvasRect.height) / canvasClientHeight;
775
+ const cellWidth = geometry.cellWidth * visibleScaleX;
776
+ const cellHeight = geometry.cellHeight * visibleScaleY;
777
+ const left = Number(canvasRect.left) + cursor.x * cellWidth;
778
+ const top = Number(canvasRect.top) + cursor.y * cellHeight;
779
+ if (!Number.isFinite(left)
780
+ || !Number.isFinite(top)
781
+ || !Number.isFinite(cellWidth)
782
+ || !Number.isFinite(cellHeight)
783
+ || cellWidth <= 0
784
+ || cellHeight <= 0) {
807
785
  return;
808
786
  }
809
787
  input.style.position = 'fixed';
810
788
  input.style.left = formatCssPixelValue(left);
811
789
  input.style.top = formatCssPixelValue(top);
812
- input.style.width = formatCssPixelValue(geometry.cellWidth);
813
- input.style.height = formatCssPixelValue(geometry.cellHeight);
814
- input.style.lineHeight = formatCssPixelValue(geometry.cellHeight);
790
+ input.style.width = formatCssPixelValue(cellWidth);
791
+ input.style.height = formatCssPixelValue(cellHeight);
792
+ input.style.lineHeight = formatCssPixelValue(cellHeight);
815
793
  input.style.padding = '0';
816
794
  input.style.border = 'none';
817
795
  input.style.margin = '0';
@@ -822,35 +800,10 @@ export class TerminalCore {
822
800
  input.style.clipPath = 'none';
823
801
  input.style.pointerEvents = 'none';
824
802
  input.style.zIndex = '0';
825
- this.calibrateImeInputAnchorRect(input, left, top);
826
803
  }
827
804
  shouldPreserveTransientInputPlacement(input) {
828
805
  return input.style.pointerEvents === 'auto' || input.style.zIndex === '1000';
829
806
  }
830
- calibrateImeInputAnchorRect(input, targetLeft, targetTop) {
831
- const rect = input.getBoundingClientRect();
832
- if (rect.width <= 0
833
- || rect.height <= 0
834
- || !Number.isFinite(rect.left)
835
- || !Number.isFinite(rect.top)) {
836
- return;
837
- }
838
- const deltaLeft = targetLeft - Number(rect.left);
839
- const deltaTop = targetTop - Number(rect.top);
840
- if (Math.abs(deltaLeft) < 0.5 && Math.abs(deltaTop) < 0.5) {
841
- return;
842
- }
843
- const currentLeft = Number.parseFloat(input.style.left);
844
- const currentTop = Number.parseFloat(input.style.top);
845
- const nextLeft = (Number.isFinite(currentLeft) ? currentLeft : targetLeft) + deltaLeft;
846
- const nextTop = (Number.isFinite(currentTop) ? currentTop : targetTop) + deltaTop;
847
- if (Number.isFinite(nextLeft)) {
848
- input.style.left = formatCssPixelValue(nextLeft);
849
- }
850
- if (Number.isFinite(nextTop)) {
851
- input.style.top = formatCssPixelValue(nextTop);
852
- }
853
- }
854
807
  resolveImeInputAnchorGeometry() {
855
808
  const terminalAny = this.terminal;
856
809
  const renderer = terminalAny?.renderer ?? null;
@@ -873,9 +826,8 @@ export class TerminalCore {
873
826
  const metrics = renderer?.getMetrics?.() ?? renderer?.metrics ?? {};
874
827
  const cols = Math.floor(Number(terminalAny?.cols));
875
828
  const rows = Math.floor(Number(terminalAny?.rows));
876
- const canvasRect = canvas.getBoundingClientRect();
877
- const fallbackCellWidth = cols > 0 ? Number(canvasRect.width) / cols : Number.NaN;
878
- const fallbackCellHeight = rows > 0 ? Number(canvasRect.height) / rows : Number.NaN;
829
+ const fallbackCellWidth = cols > 0 ? Number(canvas.clientWidth) / cols : Number.NaN;
830
+ const fallbackCellHeight = rows > 0 ? Number(canvas.clientHeight) / rows : Number.NaN;
879
831
  const cellWidth = Number(metrics.width ?? renderer?.charWidth ?? fallbackCellWidth);
880
832
  const cellHeight = Number(metrics.height ?? renderer?.charHeight ?? fallbackCellHeight);
881
833
  if (!this.isUsableAnchorGeometry(cellWidth, cellHeight, cols, rows)) {
@@ -1309,7 +1261,7 @@ export class TerminalCore {
1309
1261
  }
1310
1262
  setupInputBridge() {
1311
1263
  this.disposeInputBridge();
1312
- const input = resolveTerminalInputElement(this.container);
1264
+ const input = this.inputElement;
1313
1265
  if (!input) {
1314
1266
  this.logger.debug('[TerminalCore] No terminal input host found for input bridge');
1315
1267
  return;
@@ -2727,6 +2679,8 @@ export class TerminalCore {
2727
2679
  this.fabricView = null;
2728
2680
  this.terminal?.dispose();
2729
2681
  this.terminal = null;
2682
+ this.inputElement?.remove();
2683
+ this.inputElement = null;
2730
2684
  this.viewportHost = null;
2731
2685
  this.renderHost = null;
2732
2686
  this.registeredLinkProviders.clear();