Add terminal and addon refs to all windows

This commit is contained in:
Daniel Imms
2025-12-26 04:24:07 -08:00
parent fa87ecdc4e
commit dc0d7c81d9
9 changed files with 82 additions and 42 deletions
+6 -6
View File
@@ -206,21 +206,21 @@ if (document.location.pathname === '/test') {
const typedTerm = createTerminal(); const typedTerm = createTerminal();
controlBar = new ControlBar(document.getElementById('sidebar'), document.querySelector('.banner-tabs'), []); controlBar = new ControlBar(document.getElementById('sidebar'), document.querySelector('.banner-tabs'), []);
addonsWindow = new AddonsWindow(); addonsWindow = new AddonsWindow(typedTerm, addons);
controlBar.registerWindow(addonsWindow); controlBar.registerWindow(addonsWindow);
actionElements = { actionElements = {
findNext: addonsWindow.findNextInput, findNext: addonsWindow.findNextInput,
findPrevious: addonsWindow.findPreviousInput, findPrevious: addonsWindow.findPreviousInput,
findResults: addonsWindow.findResultsSpan findResults: addonsWindow.findResultsSpan
}; };
gpuWindow = new GpuWindow(); gpuWindow = new GpuWindow(typedTerm, addons);
controlBar.registerWindow(gpuWindow, { afterId: 'addons', hidden: true, smallTab: true }); controlBar.registerWindow(gpuWindow, { afterId: 'addons', hidden: true, smallTab: true });
optionsWindow = new OptionsWindow(updateTerminalSize, updateTerminalContainerBackground); optionsWindow = new OptionsWindow(typedTerm, addons, { updateTerminalSize, updateTerminalContainerBackground });
const styleWindow = controlBar.registerWindow(new StyleWindow()); const styleWindow = controlBar.registerWindow(new StyleWindow(typedTerm, addons));
paddingElement = styleWindow.paddingElement; paddingElement = styleWindow.paddingElement;
controlBar.registerWindow(optionsWindow); controlBar.registerWindow(optionsWindow);
controlBar.registerWindow(new TestWindow(typedTerm, addons, { disposeRecreateButtonHandler, createNewWindowButtonHandler })); controlBar.registerWindow(new TestWindow(typedTerm, addons, { disposeRecreateButtonHandler, createNewWindowButtonHandler }));
vtWindow = new VtWindow(); vtWindow = new VtWindow(typedTerm, addons);
controlBar.registerWindow(vtWindow); controlBar.registerWindow(vtWindow);
vtWindow.initTerminal(term); vtWindow.initTerminal(term);
controlBar.activateDefaultTab(); controlBar.activateDefaultTab();
@@ -351,7 +351,7 @@ function createTerminal(): Terminal {
// fit is called within a setTimeout, cols and rows need this. // fit is called within a setTimeout, cols and rows need this.
setTimeout(async () => { setTimeout(async () => {
optionsWindow.initOptions(term, addDomListener); optionsWindow.initOptions(addDomListener);
// Set terminal size again to set the specific dimensions on the demo // Set terminal size again to set the specific dimensions on the demo
updateTerminalSize(); updateTerminalSize();
+6
View File
@@ -3,6 +3,11 @@
* @license MIT * @license MIT
*/ */
/// <reference path="../../typings/xterm.d.ts"/>
import type { Terminal } from '@xterm/xterm';
import type { AddonCollection } from 'types';
export interface ITabConfig { export interface ITabConfig {
id: string; id: string;
label: string; label: string;
@@ -12,6 +17,7 @@ export interface IControlWindow {
readonly id: string; readonly id: string;
readonly label: string; readonly label: string;
build(container: HTMLElement): void; build(container: HTMLElement): void;
setTerminal(terminal: Terminal): void;
} }
export class ControlBar { export class ControlBar {
+2 -2
View File
@@ -3,10 +3,10 @@
* @license MIT * @license MIT
*/ */
import type { Terminal } from '@xterm/xterm'; import { BaseWindow } from './baseWindow';
import type { IControlWindow } from '../controlBar'; import type { IControlWindow } from '../controlBar';
export class AddonsWindow implements IControlWindow { export class AddonsWindow extends BaseWindow implements IControlWindow {
public readonly id = 'addons'; public readonly id = 'addons';
public readonly label = 'Addons'; public readonly label = 'Addons';
+29
View File
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2025 The xterm.js authors. All rights reserved.
* @license MIT
*/
/// <reference path="../../../typings/xterm.d.ts"/>
import type { AddonCollection } from '../../types';
import type { IControlWindow } from '../controlBar';
import type { Terminal } from '@xterm/xterm';
export abstract class BaseWindow implements IControlWindow {
protected get _terminal(): Terminal { return this.__terminal; }
constructor(
private __terminal: Terminal,
protected readonly _addons: AddonCollection,
) {
}
setTerminal(terminal: Terminal): void {
this.__terminal = terminal;
}
abstract id: string;
abstract label: string;
abstract build(container: HTMLElement): void;
}
+2 -1
View File
@@ -3,9 +3,10 @@
* @license MIT * @license MIT
*/ */
import { BaseWindow } from './baseWindow';
import type { IControlWindow } from '../controlBar'; import type { IControlWindow } from '../controlBar';
export class GpuWindow implements IControlWindow { export class GpuWindow extends BaseWindow implements IControlWindow {
public readonly id = 'gpu'; public readonly id = 'gpu';
public readonly label = 'GPU'; public readonly label = 'GPU';
+28 -28
View File
@@ -3,8 +3,10 @@
* @license MIT * @license MIT
*/ */
import { BaseWindow } from './baseWindow';
import type { Terminal, ITheme } from '@xterm/xterm'; import type { Terminal, ITheme } from '@xterm/xterm';
import type { IControlWindow } from '../controlBar'; import type { IControlWindow } from '../controlBar';
import type { AddonCollection } from 'types';
const xtermjsTheme: ITheme = { const xtermjsTheme: ITheme = {
foreground: '#F8F8F8', foreground: '#F8F8F8',
@@ -29,23 +31,23 @@ const xtermjsTheme: ITheme = {
brightWhite: '#FFFFFF' brightWhite: '#FFFFFF'
}; };
export class OptionsWindow implements IControlWindow { export class OptionsWindow extends BaseWindow implements IControlWindow {
public readonly id = 'options'; public readonly id = 'options';
public readonly label = 'Options'; public readonly label = 'Options';
private _container: HTMLElement; private _container: HTMLElement;
private _optionsContainer: HTMLElement; private _optionsContainer: HTMLElement;
private _term: Terminal;
private _autoResize: boolean = true; private _autoResize: boolean = true;
private _updateTerminalSize: () => void;
private _updateTerminalContainerBackground: () => void;
constructor( constructor(
updateTerminalSize: () => void, terminal: Terminal,
updateTerminalContainerBackground: () => void addons: AddonCollection,
private readonly _handlers: {
updateTerminalSize: () => void,
updateTerminalContainerBackground: () => void
},
) { ) {
this._updateTerminalSize = updateTerminalSize; super(terminal, addons)
this._updateTerminalContainerBackground = updateTerminalContainerBackground;
} }
public build(container: HTMLElement): void { public build(container: HTMLElement): void {
@@ -64,9 +66,7 @@ export class OptionsWindow implements IControlWindow {
container.appendChild(this._optionsContainer); container.appendChild(this._optionsContainer);
} }
public initOptions(term: Terminal, addDomListener: (el: HTMLElement, type: string, handler: (...args: any[]) => any) => void): void { public initOptions(addDomListener: (el: HTMLElement, type: string, handler: (...args: any[]) => any) => void): void {
this._term = term;
const blacklistedOptions = [ const blacklistedOptions = [
'cancelEvents', 'cancelEvents',
'convertEol', 'convertEol',
@@ -91,11 +91,11 @@ export class OptionsWindow implements IControlWindow {
wordSeparator: null, wordSeparator: null,
colsRows: null colsRows: null
}; };
const options = Object.getOwnPropertyNames(term.options); const options = Object.getOwnPropertyNames(this._terminal.options);
const booleanOptions: string[] = []; const booleanOptions: string[] = [];
const numberOptions: string[] = []; const numberOptions: string[] = [];
options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => { options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => {
switch (typeof term.options[o]) { switch (typeof this._terminal.options[o]) {
case 'boolean': case 'boolean':
booleanOptions.push(o); booleanOptions.push(o);
break; break;
@@ -112,21 +112,21 @@ export class OptionsWindow implements IControlWindow {
let html = ''; let html = '';
html += '<div class="option-group">'; html += '<div class="option-group">';
booleanOptions.forEach(o => { booleanOptions.forEach(o => {
html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${term.options[o] ? 'checked' : ''}/> ${o}</label></div>`; html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${this._terminal.options[o] ? 'checked' : ''}/> ${o}</label></div>`;
}); });
html += '</div><div class="option-group">'; html += '</div><div class="option-group">';
numberOptions.forEach(o => { numberOptions.forEach(o => {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.options[o] ?? ''}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`; html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${this._terminal.options[o] ?? ''}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`;
}); });
html += '</div><div class="option-group">'; html += '</div><div class="option-group">';
Object.keys(stringOptions).forEach(o => { Object.keys(stringOptions).forEach(o => {
if (o === 'colsRows') { if (o === 'colsRows') {
html += `<div class="option"><label>size (<var>cols</var><code>x</code><var>rows</var> or <code>auto</code>) <input id="opt-${o}" type="text" value="auto"/></label></div>`; html += `<div class="option"><label>size (<var>cols</var><code>x</code><var>rows</var> or <code>auto</code>) <input id="opt-${o}" type="text" value="auto"/></label></div>`;
} else if (stringOptions[o]) { } else if (stringOptions[o]) {
const selectedOption = o === 'theme' ? 'xtermjs' : term.options[o]; const selectedOption = o === 'theme' ? 'xtermjs' : this._terminal.options[o];
html += `<div class="option"><label>${o} <select id="opt-${o}">${stringOptions[o]!.map(v => `<option ${v === selectedOption ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`; html += `<div class="option"><label>${o} <select id="opt-${o}">${stringOptions[o]!.map(v => `<option ${v === selectedOption ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`;
} else { } else {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${term.options[o]}"/></label></div>`; html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${this._terminal.options[o]}"/></label></div>`;
} }
}); });
html += '</div>'; html += '</div>';
@@ -138,7 +138,7 @@ export class OptionsWindow implements IControlWindow {
const input = document.getElementById(`opt-${o}`) as HTMLInputElement; const input = document.getElementById(`opt-${o}`) as HTMLInputElement;
addDomListener(input, 'change', () => { addDomListener(input, 'change', () => {
console.log('change', o, input.checked); console.log('change', o, input.checked);
term.options[o] = input.checked; this._terminal.options[o] = input.checked;
}); });
}); });
numberOptions.forEach(o => { numberOptions.forEach(o => {
@@ -146,16 +146,16 @@ export class OptionsWindow implements IControlWindow {
addDomListener(input, 'change', () => { addDomListener(input, 'change', () => {
console.log('change', o, input.value); console.log('change', o, input.value);
if (o === 'lineHeight') { if (o === 'lineHeight') {
term.options.lineHeight = parseFloat(input.value); this._terminal.options.lineHeight = parseFloat(input.value);
} else if (o === 'scrollSensitivity') { } else if (o === 'scrollSensitivity') {
term.options.scrollSensitivity = parseFloat(input.value); this._terminal.options.scrollSensitivity = parseFloat(input.value);
} else if (o === 'scrollback') { } else if (o === 'scrollback') {
term.options.scrollback = parseInt(input.value); this._terminal.options.scrollback = parseInt(input.value);
setTimeout(() => this._updateTerminalSize(), 5); setTimeout(() => this._handlers.updateTerminalSize(), 5);
} else { } else {
term.options[o] = parseInt(input.value); this._terminal.options[o] = parseInt(input.value);
} }
this._updateTerminalSize(); this._handlers.updateTerminalSize();
}); });
}); });
Object.keys(stringOptions).forEach(o => { Object.keys(stringOptions).forEach(o => {
@@ -167,11 +167,11 @@ export class OptionsWindow implements IControlWindow {
const m = input.value.match(/^([0-9]+)x([0-9]+)$/); const m = input.value.match(/^([0-9]+)x([0-9]+)$/);
if (m) { if (m) {
this._autoResize = false; this._autoResize = false;
term.resize(parseInt(m[1]), parseInt(m[2])); this._terminal.resize(parseInt(m[1]), parseInt(m[2]));
} else { } else {
this._autoResize = true; this._autoResize = true;
input.value = 'auto'; input.value = 'auto';
this._updateTerminalSize(); this._handlers.updateTerminalSize();
} }
} else if (o === 'theme') { } else if (o === 'theme') {
switch (input.value) { switch (input.value) {
@@ -232,9 +232,9 @@ export class OptionsWindow implements IControlWindow {
break; break;
} }
} }
term.options[o] = value; this._terminal.options[o] = value;
if (o === 'theme') { if (o === 'theme') {
this._updateTerminalContainerBackground(); this._handlers.updateTerminalContainerBackground();
} }
}); });
}); });
+2 -1
View File
@@ -3,9 +3,10 @@
* @license MIT * @license MIT
*/ */
import { BaseWindow } from './baseWindow';
import type { IControlWindow } from '../controlBar'; import type { IControlWindow } from '../controlBar';
export class StyleWindow implements IControlWindow { export class StyleWindow extends BaseWindow implements IControlWindow {
public readonly id = 'style'; public readonly id = 'style';
public readonly label = 'Style'; public readonly label = 'Style';
+5 -3
View File
@@ -7,21 +7,23 @@
import { writeUnicodeTable } from 'unicodeTable'; import { writeUnicodeTable } from 'unicodeTable';
import type { IControlWindow } from '../controlBar'; import type { IControlWindow } from '../controlBar';
import { BaseWindow } from './baseWindow';
import type { IDisposable, Terminal } from '@xterm/xterm'; import type { IDisposable, Terminal } from '@xterm/xterm';
import type { AddonCollection } from 'types'; import type { AddonCollection } from 'types';
export class TestWindow implements IControlWindow { export class TestWindow extends BaseWindow implements IControlWindow {
public readonly id = 'test'; public readonly id = 'test';
public readonly label = 'Test'; public readonly label = 'Test';
constructor( constructor(
private readonly _terminal: Terminal, terminal: Terminal,
private readonly _addons: AddonCollection, addons: AddonCollection,
private readonly _handlers: { private readonly _handlers: {
disposeRecreateButtonHandler: () => void, disposeRecreateButtonHandler: () => void,
createNewWindowButtonHandler: () => void, createNewWindowButtonHandler: () => void,
}, },
) { ) {
super(terminal, addons);
} }
public build(container: HTMLElement): void { public build(container: HTMLElement): void {
+2 -1
View File
@@ -3,10 +3,11 @@
* @license MIT * @license MIT
*/ */
import { BaseWindow } from './baseWindow';
import type { IControlWindow } from '../controlBar'; import type { IControlWindow } from '../controlBar';
import type { Terminal } from '@xterm/xterm'; import type { Terminal } from '@xterm/xterm';
export class VtWindow implements IControlWindow { export class VtWindow extends BaseWindow implements IControlWindow {
public readonly id = 'vt'; public readonly id = 'vt';
public readonly label = 'VT'; public readonly label = 'VT';