xterm-input-panel 1.1.0 → 1.2.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.
- package/.storybook/vitest.setup.ts +2 -2
- package/CHANGELOG.md +14 -0
- package/LICENSE +21 -0
- package/package.json +3 -3
- package/src/input-method-tab.stories.ts +12 -8
- package/src/input-method-tab.ts +20 -8
- package/src/input-panel.stories.ts +14 -0
- package/src/input-panel.ts +3 -12
- package/src/pixi-theme.test.ts +2 -2
- package/src/platform.ts +3 -3
- package/src/shortcut-pages.ts +431 -61
- package/src/shortcut-tab.ts +45 -24
- package/src/virtual-keyboard-tab.stories.ts +10 -8
- package/src/virtual-trackpad-tab.stories.ts +51 -40
- package/src/xterm-addon.stories.ts +195 -6
- package/src/xterm-addon.ts +246 -19
- package/vitest.storybook.config.ts +1 -1
package/src/xterm-addon.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { ITerminalAddon, Terminal } from '@xterm/xterm'
|
|
2
2
|
import { iconKeyboard, iconMousePointer2 } from './icons.js'
|
|
3
|
-
import type { InputPanelLayout } from './input-panel.js'
|
|
3
|
+
import type { InputPanelLayout, InputPanelTab } from './input-panel.js'
|
|
4
4
|
import type { HostPlatform } from './platform.js'
|
|
5
5
|
|
|
6
6
|
const SENSITIVITY = 1.5
|
|
7
7
|
const EDGE_SCROLL_ZONE = 30
|
|
8
8
|
const EDGE_SCROLL_INTERVAL = 50
|
|
9
9
|
const EDGE_SCROLL_OVERSHOOT = 15
|
|
10
|
+
const STATE_STORAGE_KEY = 'xtermInputPanelState'
|
|
10
11
|
|
|
11
12
|
function isTouchDevice(): boolean {
|
|
12
13
|
return 'ontouchstart' in window || navigator.maxTouchPoints > 0
|
|
@@ -25,6 +26,84 @@ export interface InputPanelSettingsPayload {
|
|
|
25
26
|
historyLimit: number
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
interface InputPanelSessionState {
|
|
30
|
+
activeTab: InputPanelTab
|
|
31
|
+
inputDraft: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface InputMethodTabElement extends HTMLElement {
|
|
35
|
+
value: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface InputPanelElement extends HTMLElement {
|
|
39
|
+
activeTab: InputPanelTab
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isInputPanelTab(value: unknown): value is InputPanelTab {
|
|
43
|
+
return (
|
|
44
|
+
value === 'input' ||
|
|
45
|
+
value === 'keys' ||
|
|
46
|
+
value === 'shortcuts' ||
|
|
47
|
+
value === 'trackpad' ||
|
|
48
|
+
value === 'settings'
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface InputPanelStateStore {
|
|
53
|
+
lastActiveTab?: InputPanelTab
|
|
54
|
+
sessions?: Record<string, Partial<InputPanelSessionState>>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
58
|
+
return typeof value === 'object' && value !== null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function loadPanelStateStore(): InputPanelStateStore {
|
|
62
|
+
try {
|
|
63
|
+
const raw = localStorage.getItem(STATE_STORAGE_KEY)
|
|
64
|
+
if (!raw) return {}
|
|
65
|
+
const parsed = JSON.parse(raw)
|
|
66
|
+
return isRecord(parsed) ? (parsed as InputPanelStateStore) : {}
|
|
67
|
+
} catch {
|
|
68
|
+
return {}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function savePanelStateStore(store: InputPanelStateStore): void {
|
|
73
|
+
try {
|
|
74
|
+
localStorage.setItem(STATE_STORAGE_KEY, JSON.stringify(store))
|
|
75
|
+
} catch {
|
|
76
|
+
/* ignore */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function loadPanelSessionState(sessionKey: string): InputPanelSessionState | null {
|
|
81
|
+
const store = loadPanelStateStore()
|
|
82
|
+
const sessions = store.sessions
|
|
83
|
+
if (!sessions) return null
|
|
84
|
+
const rawState = sessions[sessionKey]
|
|
85
|
+
if (!isRecord(rawState)) return null
|
|
86
|
+
|
|
87
|
+
const state: InputPanelSessionState = {
|
|
88
|
+
activeTab: 'input',
|
|
89
|
+
inputDraft: '',
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (isInputPanelTab(rawState.activeTab)) {
|
|
93
|
+
state.activeTab = rawState.activeTab
|
|
94
|
+
}
|
|
95
|
+
if (typeof rawState.inputDraft === 'string') {
|
|
96
|
+
state.inputDraft = rawState.inputDraft
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return state
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function loadLastActiveTab(): InputPanelTab {
|
|
103
|
+
const store = loadPanelStateStore()
|
|
104
|
+
return isInputPanelTab(store.lastActiveTab) ? store.lastActiveTab : 'input'
|
|
105
|
+
}
|
|
106
|
+
|
|
28
107
|
/**
|
|
29
108
|
* xterm.js addon that provides full InputPanel integration.
|
|
30
109
|
*
|
|
@@ -56,6 +135,7 @@ export interface InputPanelSettingsPayload {
|
|
|
56
135
|
* ```
|
|
57
136
|
*/
|
|
58
137
|
export class InputPanelAddon implements ITerminalAddon {
|
|
138
|
+
private static _lastActiveTab: InputPanelTab = 'input'
|
|
59
139
|
// ── Singleton state ──
|
|
60
140
|
|
|
61
141
|
/** The currently active (open) addon instance, or null. */
|
|
@@ -108,6 +188,7 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
108
188
|
// ── Native FAB (static singleton) ──
|
|
109
189
|
|
|
110
190
|
private static _fabEl: HTMLButtonElement | null = null
|
|
191
|
+
private static _fabSubscriberCount = 0
|
|
111
192
|
|
|
112
193
|
/**
|
|
113
194
|
* Create the native FAB button and mount it into the given container.
|
|
@@ -273,7 +354,8 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
273
354
|
|
|
274
355
|
private static _setFabVisible(visible: boolean): void {
|
|
275
356
|
if (InputPanelAddon._fabEl) {
|
|
276
|
-
InputPanelAddon._fabEl.style.display =
|
|
357
|
+
InputPanelAddon._fabEl.style.display =
|
|
358
|
+
visible && InputPanelAddon._fabSubscriberCount > 0 ? 'flex' : 'none'
|
|
277
359
|
}
|
|
278
360
|
}
|
|
279
361
|
|
|
@@ -301,6 +383,11 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
301
383
|
private _onSettingsChange: ((settings: InputPanelSettingsPayload) => Promise<void> | void) | null
|
|
302
384
|
private _platform: HostPlatform
|
|
303
385
|
private _defaultLayout: InputPanelLayout
|
|
386
|
+
private _showFab: boolean
|
|
387
|
+
private _fabSubscribed: boolean
|
|
388
|
+
private _panelSessionState: InputPanelSessionState
|
|
389
|
+
private _stateKey: string
|
|
390
|
+
private _hasOwnPersistedState: boolean
|
|
304
391
|
|
|
305
392
|
constructor(opts?: {
|
|
306
393
|
onInput?: (data: string) => void
|
|
@@ -312,6 +399,8 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
312
399
|
onSettingsChange?: (settings: InputPanelSettingsPayload) => Promise<void> | void
|
|
313
400
|
platform?: HostPlatform
|
|
314
401
|
defaultLayout?: InputPanelLayout
|
|
402
|
+
showFab?: boolean
|
|
403
|
+
stateKey?: string
|
|
315
404
|
}) {
|
|
316
405
|
this._onInput = opts?.onInput ?? (() => {})
|
|
317
406
|
this._onOpenCb = opts?.onOpen ?? null
|
|
@@ -322,6 +411,21 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
322
411
|
this._onSettingsChange = opts?.onSettingsChange ?? null
|
|
323
412
|
this._platform = opts?.platform ?? 'common'
|
|
324
413
|
this._defaultLayout = opts?.defaultLayout ?? 'floating'
|
|
414
|
+
this._showFab = opts?.showFab ?? true
|
|
415
|
+
this._fabSubscribed = false
|
|
416
|
+
this._stateKey = opts?.stateKey?.trim() ? opts.stateKey : 'default'
|
|
417
|
+
this._hasOwnPersistedState = false
|
|
418
|
+
this._panelSessionState = {
|
|
419
|
+
activeTab: 'input',
|
|
420
|
+
inputDraft: '',
|
|
421
|
+
}
|
|
422
|
+
const persistedState = loadPanelSessionState(this._stateKey)
|
|
423
|
+
if (persistedState) {
|
|
424
|
+
this._panelSessionState = persistedState
|
|
425
|
+
this._hasOwnPersistedState = true
|
|
426
|
+
} else {
|
|
427
|
+
this._panelSessionState.activeTab = InputPanelAddon._lastActiveTab || loadLastActiveTab()
|
|
428
|
+
}
|
|
325
429
|
}
|
|
326
430
|
|
|
327
431
|
get isOpen(): boolean {
|
|
@@ -348,12 +452,54 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
348
452
|
this._defaultLayout = layout
|
|
349
453
|
}
|
|
350
454
|
|
|
455
|
+
private _persistPanelState(): void {
|
|
456
|
+
InputPanelAddon._lastActiveTab = this._panelSessionState.activeTab
|
|
457
|
+
const store = loadPanelStateStore()
|
|
458
|
+
const nextSessions = {
|
|
459
|
+
...store.sessions,
|
|
460
|
+
[this._stateKey]: {
|
|
461
|
+
activeTab: this._panelSessionState.activeTab,
|
|
462
|
+
inputDraft: this._panelSessionState.inputDraft,
|
|
463
|
+
},
|
|
464
|
+
}
|
|
465
|
+
savePanelStateStore({
|
|
466
|
+
...store,
|
|
467
|
+
lastActiveTab: this._panelSessionState.activeTab,
|
|
468
|
+
sessions: nextSessions,
|
|
469
|
+
})
|
|
470
|
+
}
|
|
471
|
+
|
|
351
472
|
/**
|
|
352
473
|
* Resolve the mount target for this addon instance.
|
|
353
474
|
* Priority: static mountTarget > terminal container > document.body
|
|
354
475
|
*/
|
|
355
476
|
private _getMountTarget(): HTMLElement {
|
|
356
|
-
return InputPanelAddon._mountTarget ?? this.
|
|
477
|
+
return InputPanelAddon._mountTarget ?? this._getTerminalHostElement() ?? document.body
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Resolve the visual host element used for overlay UI (cursor/panel).
|
|
482
|
+
*
|
|
483
|
+
* xterm renderer usually exposes `.xterm` as `terminal.element`, while
|
|
484
|
+
* ghostty may expose the mount container itself.
|
|
485
|
+
*/
|
|
486
|
+
private _getTerminalHostElement(): HTMLElement | null {
|
|
487
|
+
const termElement = this._terminal?.element
|
|
488
|
+
if (!(termElement instanceof HTMLElement)) return null
|
|
489
|
+
if (termElement.classList.contains('xterm')) {
|
|
490
|
+
return termElement.parentElement instanceof HTMLElement
|
|
491
|
+
? termElement.parentElement
|
|
492
|
+
: termElement
|
|
493
|
+
}
|
|
494
|
+
return termElement
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private _detectTerminalEngine(): 'xterm' | 'non-xterm' {
|
|
498
|
+
const termElement = this._terminal?.element
|
|
499
|
+
if (termElement instanceof HTMLElement && termElement.classList.contains('xterm')) {
|
|
500
|
+
return 'xterm'
|
|
501
|
+
}
|
|
502
|
+
return 'non-xterm'
|
|
357
503
|
}
|
|
358
504
|
|
|
359
505
|
activate(terminal: Terminal): void {
|
|
@@ -366,6 +512,11 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
366
512
|
for (const fn of this._persistentCleanups) fn()
|
|
367
513
|
this._persistentCleanups = []
|
|
368
514
|
this._listenersAttached = false
|
|
515
|
+
if (this._fabSubscribed) {
|
|
516
|
+
InputPanelAddon._fabSubscriberCount = Math.max(0, InputPanelAddon._fabSubscriberCount - 1)
|
|
517
|
+
this._fabSubscribed = false
|
|
518
|
+
InputPanelAddon._setFabVisible(InputPanelAddon._active === null)
|
|
519
|
+
}
|
|
369
520
|
InputPanelAddon._instances.delete(this)
|
|
370
521
|
if (InputPanelAddon._lastFocused === this) {
|
|
371
522
|
InputPanelAddon._lastFocused = null
|
|
@@ -392,7 +543,19 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
392
543
|
this._listenersAttached = true
|
|
393
544
|
|
|
394
545
|
// Ensure native FAB exists in the correct mount target
|
|
395
|
-
|
|
546
|
+
if (this._showFab) {
|
|
547
|
+
InputPanelAddon._ensureFab(this._getMountTarget())
|
|
548
|
+
if (!this._fabSubscribed) {
|
|
549
|
+
InputPanelAddon._fabSubscriberCount += 1
|
|
550
|
+
this._fabSubscribed = true
|
|
551
|
+
}
|
|
552
|
+
InputPanelAddon._setFabVisible(true)
|
|
553
|
+
} else {
|
|
554
|
+
// Hide legacy/stale FAB when current runtime has no FAB subscribers.
|
|
555
|
+
if (InputPanelAddon._fabSubscriberCount === 0) {
|
|
556
|
+
InputPanelAddon._setFabVisible(false)
|
|
557
|
+
}
|
|
558
|
+
}
|
|
396
559
|
|
|
397
560
|
// Default FAB target to the first terminal that attaches listeners
|
|
398
561
|
if (!InputPanelAddon._lastFocused) {
|
|
@@ -415,7 +578,13 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
415
578
|
}
|
|
416
579
|
|
|
417
580
|
open(): void {
|
|
418
|
-
if (
|
|
581
|
+
if (!this._terminal) return
|
|
582
|
+
if (this._isOpen) {
|
|
583
|
+
// Recover from host unmount/remount: panel DOM can be removed while addon
|
|
584
|
+
// still thinks it is open. In that case, close stale state and re-open.
|
|
585
|
+
if (this._panel?.isConnected) return
|
|
586
|
+
this.close()
|
|
587
|
+
}
|
|
419
588
|
|
|
420
589
|
// Singleton: close any other active instance (migration)
|
|
421
590
|
if (InputPanelAddon._active && InputPanelAddon._active !== this) {
|
|
@@ -427,17 +596,27 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
427
596
|
InputPanelAddon._lastFocused = this
|
|
428
597
|
|
|
429
598
|
// Hide FAB while panel is open
|
|
430
|
-
|
|
599
|
+
if (this._showFab) {
|
|
600
|
+
InputPanelAddon._setFabVisible(false)
|
|
601
|
+
}
|
|
431
602
|
|
|
432
603
|
this._suppressKeyboard()
|
|
433
604
|
|
|
605
|
+
if (!this._hasOwnPersistedState) {
|
|
606
|
+
const fallbackActiveTab = loadLastActiveTab()
|
|
607
|
+
this._panelSessionState.activeTab = fallbackActiveTab
|
|
608
|
+
InputPanelAddon._lastActiveTab = fallbackActiveTab
|
|
609
|
+
}
|
|
610
|
+
|
|
434
611
|
// Build the element tree
|
|
435
|
-
const panel = document.createElement('input-panel')
|
|
612
|
+
const panel = document.createElement('input-panel') as InputPanelElement
|
|
436
613
|
panel.setAttribute('layout', this._defaultLayout)
|
|
437
614
|
this._applyPanelThemeBindings(panel)
|
|
615
|
+
panel.activeTab = this._panelSessionState.activeTab
|
|
438
616
|
|
|
439
|
-
const inputTab = document.createElement('input-method-tab')
|
|
617
|
+
const inputTab = document.createElement('input-method-tab') as InputMethodTabElement
|
|
440
618
|
inputTab.setAttribute('slot', 'input')
|
|
619
|
+
inputTab.value = this._panelSessionState.inputDraft
|
|
441
620
|
panel.appendChild(inputTab)
|
|
442
621
|
|
|
443
622
|
const keysTab = document.createElement('virtual-keyboard-tab')
|
|
@@ -498,8 +677,21 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
498
677
|
}
|
|
499
678
|
}
|
|
500
679
|
})
|
|
680
|
+
this._on(inputTab, 'input-panel:input-change', (e) => {
|
|
681
|
+
const value = (e as CustomEvent).detail?.value
|
|
682
|
+
if (typeof value === 'string') {
|
|
683
|
+
this._panelSessionState.inputDraft = value
|
|
684
|
+
this._hasOwnPersistedState = true
|
|
685
|
+
this._persistPanelState()
|
|
686
|
+
}
|
|
687
|
+
})
|
|
501
688
|
this._on(panel, 'input-panel:tab-change', (e) => {
|
|
502
689
|
const tab = (e as CustomEvent).detail?.tab
|
|
690
|
+
if (isInputPanelTab(tab)) {
|
|
691
|
+
this._panelSessionState.activeTab = tab
|
|
692
|
+
this._hasOwnPersistedState = true
|
|
693
|
+
this._persistPanelState()
|
|
694
|
+
}
|
|
503
695
|
if (tab === 'trackpad') this._showCursor()
|
|
504
696
|
else this._hideCursor()
|
|
505
697
|
})
|
|
@@ -590,7 +782,7 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
590
782
|
}
|
|
591
783
|
|
|
592
784
|
private _applyPanelThemeBindings(panel: HTMLElement): void {
|
|
593
|
-
const scope = this.
|
|
785
|
+
const scope = this._getTerminalHostElement() ?? this._getMountTarget()
|
|
594
786
|
const style = getComputedStyle(scope)
|
|
595
787
|
|
|
596
788
|
const background = this._readThemeVar(
|
|
@@ -657,14 +849,46 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
657
849
|
}
|
|
658
850
|
|
|
659
851
|
// Show FAB again
|
|
660
|
-
|
|
852
|
+
if (this._showFab) {
|
|
853
|
+
InputPanelAddon._setFabVisible(true)
|
|
854
|
+
}
|
|
661
855
|
|
|
662
856
|
this._onCloseCb?.()
|
|
663
857
|
InputPanelAddon._onActiveChangeFn?.(null)
|
|
664
858
|
}
|
|
665
859
|
|
|
666
860
|
toggle(): void {
|
|
667
|
-
this._isOpen
|
|
861
|
+
if (this._isOpen) {
|
|
862
|
+
this.close()
|
|
863
|
+
return
|
|
864
|
+
}
|
|
865
|
+
this.open()
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Sync addon singleton state when host marks this terminal as active.
|
|
870
|
+
*
|
|
871
|
+
* Lifecycle:
|
|
872
|
+
* 1) Always refresh `_lastFocused` so FAB targets the current terminal.
|
|
873
|
+
* 2) If another terminal owns an open panel, migrate panel ownership here.
|
|
874
|
+
* 3) If this panel is already open, keep terminal focus in sync.
|
|
875
|
+
*/
|
|
876
|
+
syncFocusLifecycle(): void {
|
|
877
|
+
InputPanelAddon._lastFocused = this
|
|
878
|
+
|
|
879
|
+
if (this._isOpen && !this._panel?.isConnected) {
|
|
880
|
+
this.open()
|
|
881
|
+
return
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (InputPanelAddon._active && InputPanelAddon._active !== this) {
|
|
885
|
+
this.open()
|
|
886
|
+
return
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
if (this._isOpen) {
|
|
890
|
+
this._focusTerminal()
|
|
891
|
+
}
|
|
668
892
|
}
|
|
669
893
|
|
|
670
894
|
// ── Terminal focus ──
|
|
@@ -681,10 +905,13 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
681
905
|
// ── Cursor overlay ──
|
|
682
906
|
|
|
683
907
|
private _createCursor(): void {
|
|
684
|
-
const container = this.
|
|
908
|
+
const container = this._getTerminalHostElement()
|
|
685
909
|
if (!container) return
|
|
686
910
|
|
|
687
911
|
const el = document.createElement('div')
|
|
912
|
+
el.setAttribute('data-input-panel-cursor', 'virtual-mouse')
|
|
913
|
+
el.setAttribute('data-terminal-engine', this._detectTerminalEngine())
|
|
914
|
+
el.setAttribute('aria-hidden', 'true')
|
|
688
915
|
el.style.cssText =
|
|
689
916
|
'position:absolute;z-index:10;pointer-events:none;opacity:0;transition:opacity 0.15s;color:#fff;'
|
|
690
917
|
const pointer = iconMousePointer2(20)
|
|
@@ -707,7 +934,7 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
707
934
|
private _showCursor(): void {
|
|
708
935
|
if (!this._cursorEl) this._createCursor()
|
|
709
936
|
if (this._cursorEl) {
|
|
710
|
-
const container = this.
|
|
937
|
+
const container = this._getTerminalHostElement()
|
|
711
938
|
if (container) {
|
|
712
939
|
const rect = container.getBoundingClientRect()
|
|
713
940
|
this._cursorPos = { x: rect.width / 2, y: rect.height / 2 }
|
|
@@ -722,7 +949,7 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
722
949
|
}
|
|
723
950
|
|
|
724
951
|
private _moveCursor(dx: number, dy: number): void {
|
|
725
|
-
const container = this.
|
|
952
|
+
const container = this._getTerminalHostElement()
|
|
726
953
|
if (!container) return
|
|
727
954
|
const rect = container.getBoundingClientRect()
|
|
728
955
|
this._cursorPos.x = Math.max(0, Math.min(rect.width, this._cursorPos.x + dx * SENSITIVITY))
|
|
@@ -733,7 +960,7 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
733
960
|
// ── Mouse event dispatch ──
|
|
734
961
|
|
|
735
962
|
private _getClientCoords(): { clientX: number; clientY: number } | null {
|
|
736
|
-
const container = this.
|
|
963
|
+
const container = this._getTerminalHostElement()
|
|
737
964
|
if (!container) return null
|
|
738
965
|
const rect = container.getBoundingClientRect()
|
|
739
966
|
return {
|
|
@@ -743,7 +970,7 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
743
970
|
}
|
|
744
971
|
|
|
745
972
|
private _resolveTarget(clientX: number, clientY: number): Element {
|
|
746
|
-
const container = this.
|
|
973
|
+
const container = this._getTerminalHostElement()
|
|
747
974
|
if (!container) return document.body
|
|
748
975
|
const el = document.elementFromPoint(clientX, clientY)
|
|
749
976
|
if (el && container.contains(el)) return el
|
|
@@ -814,7 +1041,7 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
814
1041
|
// ── Edge scroll (during drag selection) ──
|
|
815
1042
|
|
|
816
1043
|
private _updateEdgeScroll(): void {
|
|
817
|
-
const container = this.
|
|
1044
|
+
const container = this._getTerminalHostElement()
|
|
818
1045
|
if (!container || !this._isDragging) {
|
|
819
1046
|
this._stopEdgeScroll()
|
|
820
1047
|
return
|
|
@@ -829,7 +1056,7 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
829
1056
|
|
|
830
1057
|
this._stopEdgeScroll()
|
|
831
1058
|
this._edgeScrollTimer = setInterval(() => {
|
|
832
|
-
const container = this.
|
|
1059
|
+
const container = this._getTerminalHostElement()
|
|
833
1060
|
if (!container || !this._isDragging) {
|
|
834
1061
|
this._stopEdgeScroll()
|
|
835
1062
|
return
|
|
@@ -914,7 +1141,7 @@ export class InputPanelAddon implements ITerminalAddon {
|
|
|
914
1141
|
button.style.border = '1px solid transparent'
|
|
915
1142
|
button.style.borderRadius = '4px'
|
|
916
1143
|
button.style.background = 'transparent'
|
|
917
|
-
button.style.color = 'var(--
|
|
1144
|
+
button.style.color = 'var(--foreground, #fff)'
|
|
918
1145
|
button.style.fontFamily = 'inherit'
|
|
919
1146
|
button.style.fontSize = '12px'
|
|
920
1147
|
button.style.textAlign = 'left'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'
|
|
2
2
|
import { playwright } from '@vitest/browser-playwright'
|
|
3
|
-
import { defineConfig } from 'vitest/config'
|
|
4
3
|
import { resolve } from 'path'
|
|
4
|
+
import { defineConfig } from 'vitest/config'
|
|
5
5
|
|
|
6
6
|
export default defineConfig({
|
|
7
7
|
plugins: [
|