From 6bde923f07da5da01a098f5f04331918089722bb Mon Sep 17 00:00:00 2001
From: Daniel Imms <2193314+Tyriar@users.noreply.github.com>
Date: Wed, 24 Dec 2025 17:09:20 -0800
Subject: [PATCH] Register the windows
---
demo/client.ts | 309 +++---------------------
demo/components/controlBar.ts | 189 ++++++++++++---
demo/components/window/gpuWindow.ts | 52 ++++
demo/components/window/optionsWindow.ts | 250 +++++++++++++++++++
demo/components/window/styleWindow.ts | 39 +++
demo/components/window/testWindow.ts | 177 ++++++++++++++
demo/components/window/vtWindow.ts | 95 ++++++++
demo/index.html | 166 +------------
8 files changed, 813 insertions(+), 464 deletions(-)
create mode 100644 demo/components/window/gpuWindow.ts
create mode 100644 demo/components/window/optionsWindow.ts
create mode 100644 demo/components/window/styleWindow.ts
create mode 100644 demo/components/window/testWindow.ts
create mode 100644 demo/components/window/vtWindow.ts
diff --git a/demo/client.ts b/demo/client.ts
index e26a0e7d..8605be81 100644
--- a/demo/client.ts
+++ b/demo/client.ts
@@ -19,7 +19,12 @@ if ('WebAssembly' in window) {
import { Terminal, ITerminalOptions, type IDisposable, type ITheme } from '@xterm/xterm';
import { AttachAddon } from '@xterm/addon-attach';
-import { initControlBar } from './components/controlBar';
+import { ControlBar } from './components/controlBar';
+import { GpuWindow } from './components/window/gpuWindow';
+import { OptionsWindow } from './components/window/optionsWindow';
+import { StyleWindow } from './components/window/styleWindow';
+import { TestWindow } from './components/window/testWindow';
+import { VtWindow } from './components/window/vtWindow';
import { ClipboardAddon } from '@xterm/addon-clipboard';
import { FitAddon } from '@xterm/addon-fit';
import { LigaturesAddon } from '@xterm/addon-ligatures';
@@ -56,7 +61,11 @@ let protocol;
let socketURL;
let socket;
let pid;
-let autoResize: boolean = true;
+let gpuWindow: GpuWindow;
+let optionsWindow: OptionsWindow;
+let styleWindow: StyleWindow;
+let testWindow: TestWindow;
+let vtWindow: VtWindow;
type AddonType = 'attach' | 'clipboard' | 'fit' | 'image' | 'progress' | 'search' | 'serialize' | 'unicode11' | 'unicodeGraphemes' | 'webLinks' | 'webgl' | 'ligatures';
@@ -117,7 +126,7 @@ const actionElements = {
findPrevious: document.querySelector('#find-previous') as HTMLInputElement,
findResults: document.querySelector('#find-results')
};
-const paddingElement = document.getElementById('padding') as HTMLInputElement;
+let paddingElement: HTMLInputElement;
const xtermjsTheme = {
foreground: '#F8F8F8',
@@ -231,8 +240,21 @@ if (document.location.pathname === '/test') {
window.WebLinksAddon = WebLinksAddon;
window.WebglAddon = WebglAddon;
} else {
+ const controlBar = new ControlBar(document.getElementById('sidebar'), document.querySelector('.banner-tabs'), [
+ { id: 'addons', label: 'Addons' }
+ ]);
+ optionsWindow = new OptionsWindow(updateTerminalSize, updateTerminalContainerBackground);
+ controlBar.registerWindow(optionsWindow);
+ gpuWindow = new GpuWindow();
+ controlBar.registerWindow(gpuWindow);
+ styleWindow = new StyleWindow();
+ controlBar.registerWindow(styleWindow);
+ paddingElement = styleWindow.paddingElement;
+ testWindow = new TestWindow();
+ controlBar.registerWindow(testWindow);
+ vtWindow = new VtWindow();
+ controlBar.registerWindow(vtWindow);
createTerminal();
- initControlBar();
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
document.getElementById('create-new-window').addEventListener('click', createNewWindowButtonHandler);
document.getElementById('serialize').addEventListener('click', serializeButtonHandler);
@@ -255,7 +277,6 @@ if (document.location.pathname === '/test') {
document.getElementById('ligatures-test').addEventListener('click', ligaturesTest);
document.getElementById('weblinks-test').addEventListener('click', testWeblinks);
document.getElementById('bce').addEventListener('click', coloredErase);
- addVtButtons();
initImageAddonExposed();
testEvents();
progressButtons();
@@ -323,10 +344,10 @@ function createTerminal(): void {
try {
typedTerm.loadAddon(addons.webgl.instance);
term.open(terminalContainer);
- setTextureAtlas(addons.webgl.instance.textureAtlas);
- addons.webgl.instance.onChangeTextureAtlas(e => setTextureAtlas(e));
- addons.webgl.instance.onAddTextureAtlasCanvas(e => appendTextureAtlas(e));
- addons.webgl.instance.onRemoveTextureAtlasCanvas(e => removeTextureAtlas(e));
+ gpuWindow.setTextureAtlas(addons.webgl.instance.textureAtlas);
+ addons.webgl.instance.onChangeTextureAtlas(e => gpuWindow.setTextureAtlas(e));
+ addons.webgl.instance.onAddTextureAtlasCanvas(e => gpuWindow.appendTextureAtlas(e));
+ addons.webgl.instance.onRemoveTextureAtlasCanvas(e => gpuWindow.removeTextureAtlas(e));
} catch (e) {
console.warn('error during loading webgl addon:', e);
addons.webgl.instance.dispose();
@@ -340,9 +361,10 @@ function createTerminal(): void {
term.focus();
updateTerminalContainerBackground();
+ vtWindow?.initTerminal(term);
const resizeObserver = new ResizeObserver(entries => {
- if (autoResize) {
+ if (optionsWindow.autoResize) {
addons.fit.instance.fit();
}
});
@@ -377,7 +399,7 @@ function createTerminal(): void {
// fit is called within a setTimeout, cols and rows need this.
setTimeout(async () => {
- initOptions(term);
+ optionsWindow.initOptions(term, addDomListener);
paddingElement.value = '0';
// Set terminal size again to set the specific dimensions on the demo
@@ -441,186 +463,6 @@ function runFakeTerminal(): void {
});
}
-function initOptions(term: Terminal): void {
- const blacklistedOptions = [
- // Internal only options
- 'cancelEvents',
- 'convertEol',
- 'termName',
- 'cols', 'rows', // subsumed by "size" (colsRows) option
- // Complex option
- 'documentOverride',
- 'linkHandler',
- 'logger',
- 'overviewRuler',
- 'theme',
- 'windowOptions',
- 'windowsPty',
- ];
- const stringOptions = {
- cursorStyle: ['block', 'underline', 'bar'],
- cursorInactiveStyle: ['outline', 'block', 'bar', 'underline', 'none'],
- fontFamily: null,
- fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
- fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
- logLevel: ['trace', 'debug', 'info', 'warn', 'error', 'off'],
- theme: ['default', 'xtermjs', 'sapphire', 'light'],
- wordSeparator: null,
- colsRows: null
- };
- const options = Object.getOwnPropertyNames(term.options);
- const booleanOptions = [];
- const numberOptions = [];
- options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => {
- switch (typeof term.options[o]) {
- case 'boolean':
- booleanOptions.push(o);
- break;
- case 'number':
- numberOptions.push(o);
- break;
- default:
- if (Object.keys(stringOptions).indexOf(o) === -1 && numberOptions.indexOf(o) === -1 && booleanOptions.indexOf(o) === -1) {
- console.warn(`Unrecognized option: "${o}"`);
- }
- }
- });
-
- let html = '';
- html += '
';
- booleanOptions.forEach(o => {
- html += `
`;
- });
- html += '
';
- numberOptions.forEach(o => {
- html += `
`;
- });
- html += '
';
- Object.keys(stringOptions).forEach(o => {
- if (o === 'colsRows') {
- html += `
`;
- } else if (stringOptions[o]) {
- const selectedOption = o === 'theme' ? 'xtermjs' : term.options[o];
- html += `
`;
- } else {
- html += `
`;
- }
- });
- html += '
';
-
- const container = document.getElementById('options-container');
- container.innerHTML = html;
-
- // Attach listeners
- booleanOptions.forEach(o => {
- const input = document.getElementById(`opt-${o}`) as HTMLInputElement;
- addDomListener(input, 'change', () => {
- console.log('change', o, input.checked);
- term.options[o] = input.checked;
- });
- });
- numberOptions.forEach(o => {
- const input = document.getElementById(`opt-${o}`) as HTMLInputElement;
- addDomListener(input, 'change', () => {
- console.log('change', o, input.value);
- if (o === 'lineHeight') {
- term.options.lineHeight = parseFloat(input.value);
- } else if (o === 'scrollSensitivity') {
- term.options.scrollSensitivity = parseFloat(input.value);
- } else if (o === 'scrollback') {
- term.options.scrollback = parseInt(input.value);
- setTimeout(() => updateTerminalSize(), 5);
- } else {
- term.options[o] = parseInt(input.value);
- }
- // Always update terminal size in case the option changes the dimensions
- updateTerminalSize();
- });
- });
- Object.keys(stringOptions).forEach(o => {
- const input = document.getElementById(`opt-${o}`) as HTMLInputElement;
- addDomListener(input, 'change', () => {
- console.log('change', o, input.value);
- let value: any = input.value;
- if (o === 'colsRows') {
- const m = input.value.match(/^([0-9]+)x([0-9]+)$/);
- if (m) {
- autoResize = false;
- term.resize(parseInt(m[1]), parseInt(m[2]));
- } else {
- autoResize = true;
- input.value = 'auto';
- updateTerminalSize();
- }
- } else if (o === 'theme') {
- switch (input.value) {
- case 'default':
- value = undefined;
- break;
- case 'xtermjs':
- // Custom theme to match style of xterm.js logo
- value = xtermjsTheme;
- case 'sapphire':
- // Color source: https://github.com/Tyriar/vscode-theme-sapphire
- value = {
- background: '#1c2431',
- foreground: '#cccccc',
- selectionBackground: '#399ef440',
- black: '#666666',
- blue: '#399ef4',
- brightBlack: '#666666',
- brightBlue: '#399ef4',
- brightCyan: '#21c5c7',
- brightGreen: '#4eb071',
- brightMagenta: '#b168df',
- brightRed: '#da6771',
- brightWhite: '#efefef',
- brightYellow: '#fff099',
- cyan: '#21c5c7',
- green: '#4eb071',
- magenta: '#b168df',
- red: '#da6771',
- white: '#efefef',
- yellow: '#fff099'
- };
- break;
- case 'light':
- // Color source: https://github.com/microsoft/vscode/blob/main/extensions/theme-defaults/themes/light_plus.json
- value = {
- background: '#ffffff',
- foreground: '#333333',
- cursor: '#333333',
- cursorAccent: '#ffffff',
- selectionBackground: '#add6ff',
- overviewRulerBorder: '#aaaaaa',
- black: '#000000',
- blue: '#0451a5',
- brightBlack: '#666666',
- brightBlue: '#0451a5',
- brightCyan: '#0598bc',
- brightGreen: '#14ce14',
- brightMagenta: '#bc05bc',
- brightRed: '#cd3131',
- brightWhite: '#a5a5a5',
- brightYellow: '#b5ba00',
- cyan: '#0598bc',
- green: '#00bc00',
- magenta: '#bc05bc',
- red: '#cd3131',
- white: '#555555',
- yellow: '#949800'
- };
- break;
- }
- }
- term.options[o] = value;
- if (o === 'theme') {
- updateTerminalContainerBackground();
- }
- });
- });
-}
-
function updateTerminalContainerBackground(): void {
const bg = term.options.theme?.background ?? '#000000';
terminalContainer.style.backgroundColor = bg;
@@ -631,9 +473,9 @@ function initAddons(term: Terminal): void {
function postInitWebgl(): void {
setTimeout(() => {
- setTextureAtlas(addons.webgl.instance.textureAtlas);
- addons.webgl.instance.onChangeTextureAtlas(e => setTextureAtlas(e));
- addons.webgl.instance.onAddTextureAtlasCanvas(e => appendTextureAtlas(e));
+ gpuWindow.setTextureAtlas(addons.webgl.instance.textureAtlas);
+ addons.webgl.instance.onChangeTextureAtlas(e => gpuWindow.setTextureAtlas(e));
+ addons.webgl.instance.onAddTextureAtlasCanvas(e => gpuWindow.appendTextureAtlas(e));
}, 500);
}
function preDisposeWebgl(): void {
@@ -773,9 +615,9 @@ function addDomListener(element: HTMLElement, type: string, handler: (...args: a
}
function updateTerminalSize(): void {
- const width = autoResize ? '100%'
+ const width = optionsWindow.autoResize ? '100%'
: (term._core._renderService.dimensions.css.canvas.width + term._core.viewport.scrollBarWidth).toString() + 'px';
- const height = autoResize ? '100%'
+ const height = optionsWindow.autoResize ? '100%'
: (term._core._renderService.dimensions.css.canvas.height).toString() + 'px';
terminalContainer.style.width = width;
terminalContainer.style.height = height;
@@ -808,21 +650,7 @@ function htmlSerializeButtonHandler(): void {
document.getElementById('htmlserialize-output-result').innerText = 'Copied to clipboard';
}
-function setTextureAtlas(e: HTMLCanvasElement): void {
- styleAtlasPage(e);
- document.querySelector('#texture-atlas').replaceChildren(e);
-}
-function appendTextureAtlas(e: HTMLCanvasElement): void {
- styleAtlasPage(e);
- document.querySelector('#texture-atlas').appendChild(e);
-}
-function removeTextureAtlas(e: HTMLCanvasElement): void {
- e.remove();
-}
-function styleAtlasPage(e: HTMLCanvasElement): void {
- e.style.width = `${e.width / window.devicePixelRatio}px`;
- e.style.height = `${e.height / window.devicePixelRatio}px`;
-}
+
function customGlyphAlignmentHandler(): void {
term.write('\n\r');
@@ -1456,65 +1284,6 @@ function decorationStressTest(): void {
console.groupEnd();
};
-function addVtButtons(): void {
- function csi(e: string): string {
- return `\x1b[${e}`;
- }
-
- function createButton(name: string, description: string, writeCsi: string, paramCount: number = 1): HTMLElement {
- const inputs: HTMLInputElement[] = [];
- for (let i = 0; i < paramCount; i++) {
- const input = document.createElement('input');
- input.type = 'number';
- input.title = `Input #${i + 1}`;
- inputs.push(input);
- }
-
- const element = document.createElement('button');
- element.textContent = name;
- const writeCsiSplit = writeCsi.split('|');
- const prefix = writeCsiSplit.length === 2 ? writeCsiSplit[0] : '';
- const suffix = writeCsiSplit[writeCsiSplit.length - 1];
- element.addEventListener(`click`, () => term.write(csi(`${prefix}${inputs.map(e => e.value).join(';')}${suffix}`)));
-
- const desc = document.createElement('span');
- desc.textContent = description;
-
- const container = document.createElement('div');
- container.classList.add('vt-button');
- container.append(element, ...inputs, desc);
- return container;
- }
- const vtFragment = document.createDocumentFragment();
- const buttonSpecs: { [key: string]: { label: string, description: string, paramCount?: number }} = {
- A: { label: 'CUU ↑', description: 'Cursor Up Ps Times' },
- B: { label: 'CUD ↓', description: 'Cursor Down Ps Times' },
- C: { label: 'CUF →', description: 'Cursor Forward Ps Times' },
- D: { label: 'CUB ←', description: 'Cursor Backward Ps Times' },
- E: { label: 'CNL', description: 'Cursor Next Line Ps Times' },
- F: { label: 'CPL', description: 'Cursor Preceding Line Ps Times' },
- G: { label: 'CHA', description: 'Cursor Character Absolute' },
- H: { label: 'CUP', description: 'Cursor Position [row;column]', paramCount: 2 },
- I: { label: 'CHT', description: 'Cursor Forward Tabulation Ps tab stops' },
- J: { label: 'ED', description: 'Erase in Display' },
- '?|J': { label: 'DECSED', description: 'Erase in Display' },
- K: { label: 'EL', description: 'Erase in Line' },
- '?|K': { label: 'DECSEL', description: 'Erase in Line' },
- L: { label: 'IL', description: 'Insert Ps Line(s)' },
- M: { label: 'DL', description: 'Delete Ps Line(s)' },
- P: { label: 'DCH', description: 'Delete Ps Character(s)' },
- ' q': { label: 'DECSCUSR', description: 'Set Cursor Style' },
- '?2026h': { label: 'BSU', description: 'Begin synchronized update', paramCount: 0 },
- '?2026l': { label: 'ESU', description: 'End synchronized update', paramCount: 0 }
- };
- for (const s of Object.keys(buttonSpecs)) {
- const spec = buttonSpecs[s];
- vtFragment.appendChild(createButton(spec.label, spec.description, s, spec.paramCount));
- }
-
- document.querySelector('#vt-container').appendChild(vtFragment);
-}
-
function ligaturesTest(): void {
term.write([
'',
diff --git a/demo/components/controlBar.ts b/demo/components/controlBar.ts
index fb4d08e7..ab55891e 100644
--- a/demo/components/controlBar.ts
+++ b/demo/components/controlBar.ts
@@ -3,41 +3,172 @@
* @license MIT
*/
-export function initControlBar(): void {
- // Sidebar resize handling
- const sidebar = document.getElementById('sidebar');
- const resizeHandleH = document.getElementById('sidebar-resize-handle-horizontal');
- const resizeHandleV = document.getElementById('sidebar-resize-handle-vertical');
- const resizeHandleCorner = document.getElementById('sidebar-resize-handle-corner');
- let resizeMode: 'none' | 'horizontal' | 'vertical' | 'corner' = 'none';
+export interface ITabConfig {
+ id: string;
+ label: string;
+}
- function startResize(mode: 'horizontal' | 'vertical' | 'corner', e: MouseEvent): void {
- resizeMode = mode;
+export interface IControlWindow {
+ readonly id: string;
+ readonly label: string;
+ build(container: HTMLElement): void;
+}
+
+export class ControlBar {
+ private readonly _sidebar: HTMLElement;
+ private readonly _resizeHandleH: HTMLElement;
+ private readonly _resizeHandleV: HTMLElement;
+ private readonly _resizeHandleCorner: HTMLElement;
+ private _resizeMode: 'none' | 'horizontal' | 'vertical' | 'corner' = 'none';
+
+ private readonly _tabContainer: HTMLElement;
+ private readonly _tabs: Map = new Map();
+ private _activeTabId: string | null = null;
+
+ constructor(sidebar: HTMLElement, tabContainer: HTMLElement, tabs: ITabConfig[]) {
+ this._sidebar = sidebar;
+ this._tabContainer = tabContainer;
+
+ // Create resize handles
+ this._resizeHandleH = document.createElement('div');
+ this._resizeHandleH.id = 'sidebar-resize-handle-horizontal';
+
+ this._resizeHandleV = document.createElement('div');
+ this._resizeHandleV.id = 'sidebar-resize-handle-vertical';
+
+ this._resizeHandleCorner = document.createElement('div');
+ this._resizeHandleCorner.id = 'sidebar-resize-handle-corner';
+
+ // Insert handles at the beginning of the sidebar
+ this._sidebar.prepend(this._resizeHandleCorner);
+ this._sidebar.prepend(this._resizeHandleV);
+ this._sidebar.prepend(this._resizeHandleH);
+
+ this._initResizeListeners();
+ this._initTabs(tabs);
+ }
+
+ private _initResizeListeners(): void {
+ this._resizeHandleH.addEventListener('mousedown', (e: MouseEvent) => this._startResize('horizontal', e));
+ this._resizeHandleV.addEventListener('mousedown', (e: MouseEvent) => this._startResize('vertical', e));
+ this._resizeHandleCorner.addEventListener('mousedown', (e: MouseEvent) => this._startResize('corner', e));
+
+ document.addEventListener('mousemove', (e: MouseEvent) => {
+ if (this._resizeMode === 'none') return;
+ if (this._resizeMode === 'horizontal' || this._resizeMode === 'corner') {
+ const newWidth = window.innerWidth - e.clientX - 10;
+ this._sidebar.style.width = `${Math.max(200, newWidth)}px`;
+ }
+ if (this._resizeMode === 'vertical' || this._resizeMode === 'corner') {
+ const rect = this._sidebar.getBoundingClientRect();
+ const newHeight = e.clientY - rect.top;
+ this._sidebar.style.height = `${Math.max(100, newHeight)}px`;
+ }
+ });
+
+ document.addEventListener('mouseup', () => {
+ this._resizeMode = 'none';
+ document.body.style.cursor = '';
+ document.body.style.userSelect = '';
+ });
+ }
+
+ private _startResize(mode: 'horizontal' | 'vertical' | 'corner', e: MouseEvent): void {
+ this._resizeMode = mode;
document.body.style.cursor = mode === 'horizontal' ? 'ew-resize' : mode === 'vertical' ? 'ns-resize' : 'nesw-resize';
document.body.style.userSelect = 'none';
e.preventDefault();
}
- resizeHandleH.addEventListener('mousedown', (e: MouseEvent) => startResize('horizontal', e));
- resizeHandleV.addEventListener('mousedown', (e: MouseEvent) => startResize('vertical', e));
- resizeHandleCorner.addEventListener('mousedown', (e: MouseEvent) => startResize('corner', e));
+ private _initTabs(tabs: ITabConfig[]): void {
+ // Clear existing buttons
+ this._tabContainer.innerHTML = '';
- document.addEventListener('mousemove', (e: MouseEvent) => {
- if (resizeMode === 'none') return;
- if (resizeMode === 'horizontal' || resizeMode === 'corner') {
- const newWidth = window.innerWidth - e.clientX - 10;
- sidebar.style.width = `${Math.max(200, newWidth)}px`;
- }
- if (resizeMode === 'vertical' || resizeMode === 'corner') {
- const rect = sidebar.getBoundingClientRect();
- const newHeight = e.clientY - rect.top;
- sidebar.style.height = `${Math.max(100, newHeight)}px`;
- }
- });
+ // Create tab buttons
+ for (const tab of tabs) {
+ const content = document.getElementById(tab.id);
+ if (!content) {
+ console.warn(`Tab content element not found: ${tab.id}`);
+ continue;
+ }
- document.addEventListener('mouseup', () => {
- resizeMode = 'none';
- document.body.style.cursor = '';
- document.body.style.userSelect = '';
- });
+ const button = document.createElement('button');
+ button.id = `${tab.id}button`;
+ button.className = 'tabLinks';
+ button.textContent = tab.label;
+ button.addEventListener('click', (e) => this._openSection(e, tab.id));
+
+ this._tabContainer.appendChild(button);
+ this._tabs.set(tab.id, { button, content });
+
+ // Hide content initially
+ content.style.display = 'none';
+ }
+
+ // Restore saved tab or default to first
+ const savedTab = localStorage.getItem('tab');
+ const tabId = savedTab && this._tabs.has(savedTab) ? savedTab : tabs[0]?.id;
+ if (tabId) {
+ this._activateTab(tabId);
+ }
+ }
+
+ private _openSection(event: MouseEvent, tabId: string): void {
+ const tab = this._tabs.get(tabId);
+ if (!tab) return;
+
+ // If clicking active tab, toggle sidebar visibility
+ if (tab.button.classList.contains('active')) {
+ this._sidebar.classList.toggle('sidebar-hidden');
+ return;
+ }
+
+ // Show sidebar if hidden
+ this._sidebar.classList.remove('sidebar-hidden');
+
+ this._activateTab(tabId);
+ }
+
+ private _activateTab(tabId: string): void {
+ // Deactivate all tabs
+ for (const [, { button, content }] of this._tabs) {
+ button.classList.remove('active');
+ content.style.display = 'none';
+ }
+
+ // Activate the selected tab
+ const tab = this._tabs.get(tabId);
+ if (tab) {
+ tab.button.classList.add('active');
+ tab.content.style.display = 'block';
+ this._activeTabId = tabId;
+ localStorage.setItem('tab', tabId);
+ }
+ }
+
+ public registerWindow(window: IControlWindow): void {
+ // Create button
+ const button = document.createElement('button');
+ button.id = `${window.id}button`;
+ button.className = 'tabLinks';
+ button.textContent = window.label;
+ button.addEventListener('click', (e) => this._openSection(e, window.id));
+ this._tabContainer.appendChild(button);
+
+ // Create content container
+ const content = document.createElement('div');
+ content.id = window.id;
+ content.className = 'tabContent';
+ content.style.display = 'none';
+ this._sidebar.appendChild(content);
+
+ // Let the window build its content
+ window.build(content);
+
+ this._tabs.set(window.id, { button, content });
+ }
+
+ public get activeTabId(): string | null {
+ return this._activeTabId;
+ }
}
diff --git a/demo/components/window/gpuWindow.ts b/demo/components/window/gpuWindow.ts
new file mode 100644
index 00000000..4fa74ff8
--- /dev/null
+++ b/demo/components/window/gpuWindow.ts
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import type { IControlWindow } from '../controlBar';
+
+export class GpuWindow implements IControlWindow {
+ public readonly id = 'gpu';
+ public readonly label = 'GPU';
+
+ private _textureAtlasContainer: HTMLElement;
+
+ public build(container: HTMLElement): void {
+ const heading = document.createElement('h3');
+ heading.textContent = 'GPU';
+ container.appendChild(heading);
+
+ const zoomCheckbox = document.createElement('input');
+ zoomCheckbox.type = 'checkbox';
+ zoomCheckbox.id = 'texture-atlas-zoom';
+ container.appendChild(zoomCheckbox);
+
+ const zoomLabel = document.createElement('label');
+ zoomLabel.htmlFor = 'texture-atlas-zoom';
+ zoomLabel.textContent = 'Zoom texture atlas';
+ container.appendChild(zoomLabel);
+
+ this._textureAtlasContainer = document.createElement('div');
+ this._textureAtlasContainer.id = 'texture-atlas';
+ container.appendChild(this._textureAtlasContainer);
+ }
+
+ public setTextureAtlas(canvas: HTMLCanvasElement): void {
+ this._styleAtlasPage(canvas);
+ this._textureAtlasContainer.replaceChildren(canvas);
+ }
+
+ public appendTextureAtlas(canvas: HTMLCanvasElement): void {
+ this._styleAtlasPage(canvas);
+ this._textureAtlasContainer.appendChild(canvas);
+ }
+
+ public removeTextureAtlas(canvas: HTMLCanvasElement): void {
+ canvas.remove();
+ }
+
+ private _styleAtlasPage(canvas: HTMLCanvasElement): void {
+ canvas.style.width = `${canvas.width / window.devicePixelRatio}px`;
+ canvas.style.height = `${canvas.height / window.devicePixelRatio}px`;
+ }
+}
diff --git a/demo/components/window/optionsWindow.ts b/demo/components/window/optionsWindow.ts
new file mode 100644
index 00000000..7a5a24fc
--- /dev/null
+++ b/demo/components/window/optionsWindow.ts
@@ -0,0 +1,250 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import type { Terminal, ITheme } from '@xterm/xterm';
+import type { IControlWindow } from '../controlBar';
+
+const xtermjsTheme: ITheme = {
+ foreground: '#F8F8F8',
+ background: '#2D2E2C',
+ selectionBackground: '#5DA5D533',
+ selectionInactiveBackground: '#555555AA',
+ black: '#1E1E1D',
+ brightBlack: '#262625',
+ red: '#CE5C5C',
+ brightRed: '#FF7272',
+ green: '#5BCC5B',
+ brightGreen: '#72FF72',
+ yellow: '#CCCC5B',
+ brightYellow: '#FFFF72',
+ blue: '#5D5DD3',
+ brightBlue: '#7279FF',
+ magenta: '#BC5ED1',
+ brightMagenta: '#E572FF',
+ cyan: '#5DA5D5',
+ brightCyan: '#72F0FF',
+ white: '#F8F8F8',
+ brightWhite: '#FFFFFF'
+};
+
+export class OptionsWindow implements IControlWindow {
+ public readonly id = 'options';
+ public readonly label = 'Options';
+
+ private _container: HTMLElement;
+ private _optionsContainer: HTMLElement;
+ private _term: Terminal;
+ private _autoResize: boolean = true;
+ private _updateTerminalSize: () => void;
+ private _updateTerminalContainerBackground: () => void;
+
+ constructor(
+ updateTerminalSize: () => void,
+ updateTerminalContainerBackground: () => void
+ ) {
+ this._updateTerminalSize = updateTerminalSize;
+ this._updateTerminalContainerBackground = updateTerminalContainerBackground;
+ }
+
+ public build(container: HTMLElement): void {
+ this._container = container;
+
+ const heading = document.createElement('h3');
+ heading.textContent = 'Options';
+ container.appendChild(heading);
+
+ const description = document.createElement('p');
+ description.innerHTML = 'These options can be set in the Terminal constructor or by using the Terminal.options property.';
+ container.appendChild(description);
+
+ this._optionsContainer = document.createElement('div');
+ this._optionsContainer.id = 'options-container';
+ container.appendChild(this._optionsContainer);
+ }
+
+ public initOptions(term: Terminal, addDomListener: (el: HTMLElement, type: string, handler: (...args: any[]) => any) => void): void {
+ this._term = term;
+
+ const blacklistedOptions = [
+ 'cancelEvents',
+ 'convertEol',
+ 'termName',
+ 'cols', 'rows',
+ 'documentOverride',
+ 'linkHandler',
+ 'logger',
+ 'overviewRuler',
+ 'theme',
+ 'windowOptions',
+ 'windowsPty',
+ ];
+ const stringOptions: { [key: string]: string[] | null } = {
+ cursorStyle: ['block', 'underline', 'bar'],
+ cursorInactiveStyle: ['outline', 'block', 'bar', 'underline', 'none'],
+ fontFamily: null,
+ fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
+ fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
+ logLevel: ['trace', 'debug', 'info', 'warn', 'error', 'off'],
+ theme: ['default', 'xtermjs', 'sapphire', 'light'],
+ wordSeparator: null,
+ colsRows: null
+ };
+ const options = Object.getOwnPropertyNames(term.options);
+ const booleanOptions: string[] = [];
+ const numberOptions: string[] = [];
+ options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => {
+ switch (typeof term.options[o]) {
+ case 'boolean':
+ booleanOptions.push(o);
+ break;
+ case 'number':
+ numberOptions.push(o);
+ break;
+ default:
+ if (Object.keys(stringOptions).indexOf(o) === -1 && numberOptions.indexOf(o) === -1 && booleanOptions.indexOf(o) === -1) {
+ console.warn(`Unrecognized option: "${o}"`);
+ }
+ }
+ });
+
+ let html = '';
+ html += '';
+ booleanOptions.forEach(o => {
+ html += `
`;
+ });
+ html += '
';
+ numberOptions.forEach(o => {
+ html += `
`;
+ });
+ html += '
';
+ Object.keys(stringOptions).forEach(o => {
+ if (o === 'colsRows') {
+ html += `
`;
+ } else if (stringOptions[o]) {
+ const selectedOption = o === 'theme' ? 'xtermjs' : term.options[o];
+ html += `
`;
+ } else {
+ html += `
`;
+ }
+ });
+ html += '
';
+
+ this._optionsContainer.innerHTML = html;
+
+ // Attach listeners
+ booleanOptions.forEach(o => {
+ const input = document.getElementById(`opt-${o}`) as HTMLInputElement;
+ addDomListener(input, 'change', () => {
+ console.log('change', o, input.checked);
+ term.options[o] = input.checked;
+ });
+ });
+ numberOptions.forEach(o => {
+ const input = document.getElementById(`opt-${o}`) as HTMLInputElement;
+ addDomListener(input, 'change', () => {
+ console.log('change', o, input.value);
+ if (o === 'lineHeight') {
+ term.options.lineHeight = parseFloat(input.value);
+ } else if (o === 'scrollSensitivity') {
+ term.options.scrollSensitivity = parseFloat(input.value);
+ } else if (o === 'scrollback') {
+ term.options.scrollback = parseInt(input.value);
+ setTimeout(() => this._updateTerminalSize(), 5);
+ } else {
+ term.options[o] = parseInt(input.value);
+ }
+ this._updateTerminalSize();
+ });
+ });
+ Object.keys(stringOptions).forEach(o => {
+ const input = document.getElementById(`opt-${o}`) as HTMLInputElement;
+ addDomListener(input, 'change', () => {
+ console.log('change', o, input.value);
+ let value: any = input.value;
+ if (o === 'colsRows') {
+ const m = input.value.match(/^([0-9]+)x([0-9]+)$/);
+ if (m) {
+ this._autoResize = false;
+ term.resize(parseInt(m[1]), parseInt(m[2]));
+ } else {
+ this._autoResize = true;
+ input.value = 'auto';
+ this._updateTerminalSize();
+ }
+ } else if (o === 'theme') {
+ switch (input.value) {
+ case 'default':
+ value = undefined;
+ break;
+ case 'xtermjs':
+ value = xtermjsTheme;
+ break;
+ case 'sapphire':
+ value = {
+ background: '#1c2431',
+ foreground: '#cccccc',
+ selectionBackground: '#399ef440',
+ black: '#666666',
+ blue: '#399ef4',
+ brightBlack: '#666666',
+ brightBlue: '#399ef4',
+ brightCyan: '#21c5c7',
+ brightGreen: '#4eb071',
+ brightMagenta: '#b168df',
+ brightRed: '#da6771',
+ brightWhite: '#efefef',
+ brightYellow: '#fff099',
+ cyan: '#21c5c7',
+ green: '#4eb071',
+ magenta: '#b168df',
+ red: '#da6771',
+ white: '#efefef',
+ yellow: '#fff099'
+ };
+ break;
+ case 'light':
+ value = {
+ background: '#ffffff',
+ foreground: '#333333',
+ cursor: '#333333',
+ cursorAccent: '#ffffff',
+ selectionBackground: '#add6ff',
+ overviewRulerBorder: '#aaaaaa',
+ black: '#000000',
+ blue: '#0451a5',
+ brightBlack: '#666666',
+ brightBlue: '#0451a5',
+ brightCyan: '#0598bc',
+ brightGreen: '#14ce14',
+ brightMagenta: '#bc05bc',
+ brightRed: '#cd3131',
+ brightWhite: '#a5a5a5',
+ brightYellow: '#b5ba00',
+ cyan: '#0598bc',
+ green: '#00bc00',
+ magenta: '#bc05bc',
+ red: '#cd3131',
+ white: '#555555',
+ yellow: '#949800'
+ };
+ break;
+ }
+ }
+ term.options[o] = value;
+ if (o === 'theme') {
+ this._updateTerminalContainerBackground();
+ }
+ });
+ });
+ }
+
+ public get autoResize(): boolean {
+ return this._autoResize;
+ }
+
+ public set autoResize(value: boolean) {
+ this._autoResize = value;
+ }
+}
diff --git a/demo/components/window/styleWindow.ts b/demo/components/window/styleWindow.ts
new file mode 100644
index 00000000..7f9811b9
--- /dev/null
+++ b/demo/components/window/styleWindow.ts
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import type { IControlWindow } from '../controlBar';
+
+export class StyleWindow implements IControlWindow {
+ public readonly id = 'style';
+ public readonly label = 'Style';
+
+ private _paddingElement: HTMLInputElement;
+
+ public build(container: HTMLElement): void {
+ const heading = document.createElement('h3');
+ heading.textContent = 'Style';
+ container.appendChild(heading);
+
+ const wrapper = document.createElement('div');
+ wrapper.style.display = 'inline-block';
+ wrapper.style.marginRight = '16px';
+
+ const label = document.createElement('label');
+ label.htmlFor = 'padding';
+ label.textContent = 'Padding';
+ wrapper.appendChild(label);
+
+ this._paddingElement = document.createElement('input');
+ this._paddingElement.type = 'number';
+ this._paddingElement.id = 'padding';
+ wrapper.appendChild(this._paddingElement);
+
+ container.appendChild(wrapper);
+ }
+
+ public get paddingElement(): HTMLInputElement {
+ return this._paddingElement;
+ }
+}
diff --git a/demo/components/window/testWindow.ts b/demo/components/window/testWindow.ts
new file mode 100644
index 00000000..5626751c
--- /dev/null
+++ b/demo/components/window/testWindow.ts
@@ -0,0 +1,177 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import type { IControlWindow } from '../controlBar';
+
+export class TestWindow implements IControlWindow {
+ public readonly id = 'test';
+ public readonly label = 'Test';
+
+ public build(container: HTMLElement): void {
+ const heading = document.createElement('h3');
+ heading.textContent = 'Test';
+ container.appendChild(heading);
+
+ const wrapper = document.createElement('div');
+ wrapper.style.display = 'inline-block';
+ wrapper.style.marginRight = '16px';
+
+ const dl = document.createElement('dl');
+
+ // Lifecycle section
+ this._addDt(dl, 'Lifecycle');
+ this._addDdWithCheckbox(dl, 'use-real-terminal', 'Use real terminal', 'This is used to real vs fake terminals', true);
+ this._addDdWithButton(dl, 'dispose', 'Dispose terminal', 'This is used to testing memory leaks');
+ this._addDdWithButton(dl, 'create-new-window', 'Create terminal in new window', 'This is used to test rendering in other windows');
+
+ // Performance section
+ this._addDt(dl, 'Performance');
+ this._addDdWithButton(dl, 'load-test', 'Load test', 'Write several MB of data to simulate a lot of data coming from the process');
+ this._addDdWithButton(dl, 'load-test-long-lines', 'Load test (long lines)', 'Write several MB of data with long lines to simulate a lot of data coming from the process');
+ this._addDdWithButton(dl, 'print-cjk', 'CJK Unified Ideographs', 'Prints the 20977 characters from the CJK Unified Ideographs unicode block');
+ this._addDdWithButton(dl, 'print-cjk-sgr', 'CJK Unified Ideographs (random SGR)', 'Prints the 20977 characters from the CJK Unified Ideographs unicode block with randomized SGR attributes');
+
+ // Styles section
+ this._addDt(dl, 'Styles');
+ this._addDdWithButton(dl, 'custom-glyph-alignment', 'Custom glyph alignment test', 'Write custom glyph alignment tests to the terminal');
+ this._addDdWithButton(dl, 'custom-glyph-ranges', 'Custom glyph ranges', 'Write custom glyph unicode range to the terminal');
+ this._addDdWithButton(dl, 'powerline-symbol-test', 'Powerline symbol test', 'Write powerline symbol characters to the terminal (\\ue0a0+)');
+ this._addDdWithButton(dl, 'underline-test', 'Underline test', 'Write text with Kitty\'s extended underline sequences');
+ this._addDdWithButton(dl, 'sgr-test', 'SGR test', 'Write text with SGR attribute');
+ this._addDdWithButton(dl, 'ansi-colors', 'Ansi colors test', 'Write a wide range of ansi colors');
+ this._addDdWithButton(dl, 'osc-hyperlinks', 'Ansi hyperlinks test', 'Write some OSC 8 hyperlinks');
+ this._addDdWithButton(dl, 'bce', 'Colored Erase (BCE)', 'Test colored erase');
+ this._addDdWithButton(dl, 'add-grapheme-clusters', 'Grapheme clusters', 'Write grapheme cluster test strings');
+
+ // Decorations section
+ this._addDt(dl, 'Decorations');
+ this._addDdWithButton(dl, 'add-decoration', 'Decoration', 'Add a decoration to the terminal');
+ this._addDdWithButton(dl, 'add-overview-ruler', 'Add Overview Ruler', 'Add an overview ruler to the terminal');
+ this._addDdWithButton(dl, 'decoration-stress-test', 'Stress Test', 'Toggle between adding and removing a decoration to each line');
+
+ // Ligatures Addon section
+ this._addDt(dl, 'Ligatures Addon');
+ this._addDdWithButton(dl, 'ligatures-test', 'Common ligatures', 'Write common ligatures sequences');
+
+ // Weblinks Addon section
+ this._addDt(dl, 'Weblinks Addon');
+ this._addDdWithButton(dl, 'weblinks-test', 'Test URLs', 'Various url conditions from demo data, hover&click to test');
+
+ // Image Test section
+ this._addDt(dl, 'Image Test');
+ this._addDdWithButton(dl, 'image-demo1', 'snake (sixel)', '');
+ this._addDdWithButton(dl, 'image-demo2', 'oranges (sixel)', '');
+ this._addDdWithButton(dl, 'image-demo3', 'palette (iip)', '');
+
+ // Events Test section
+ this._addDt(dl, 'Events Test');
+ this._addDdWithButton(dl, 'event-focus', 'focus', '');
+ this._addDdWithButton(dl, 'event-blur', 'blur', '');
+
+ // Progress Addon section
+ this._addDt(dl, 'Progress Addon');
+ this._addDdWithButton(dl, 'progress-run', 'full set run', '');
+ this._addDdWithButton(dl, 'progress-0', 'state 0: remove', '');
+ this._addDdWithButton(dl, 'progress-1', 'state 1: set 20%', '');
+ this._addDdWithButton(dl, 'progress-2', 'state 2: error', '');
+ this._addDdWithButton(dl, 'progress-3', 'state 3: indeterminate', '');
+ this._addDdWithButton(dl, 'progress-4', 'state 4: pause', '');
+
+ // Progress bar
+ const progressDd = document.createElement('dd');
+ const progressDiv = document.createElement('div');
+ progressDiv.id = 'progress-progress';
+ const progressPercent = document.createElement('div');
+ progressPercent.id = 'progress-percent';
+ const progressIndeterminate = document.createElement('div');
+ progressIndeterminate.id = 'progress-indeterminate';
+ progressDiv.appendChild(progressPercent);
+ progressDiv.appendChild(progressIndeterminate);
+ progressDd.appendChild(progressDiv);
+ dl.appendChild(progressDd);
+
+ // Progress state
+ const stateDd = document.createElement('dd');
+ const stateDiv = document.createElement('div');
+ stateDiv.id = 'progress-state';
+ stateDiv.textContent = 'State:';
+ stateDd.appendChild(stateDiv);
+ dl.appendChild(stateDd);
+
+ wrapper.appendChild(dl);
+ container.appendChild(wrapper);
+
+ // Add progress bar styles
+ this._addProgressStyles(container);
+ }
+
+ private _addDt(dl: HTMLElement, text: string): void {
+ const dt = document.createElement('dt');
+ dt.textContent = text;
+ dl.appendChild(dt);
+ }
+
+ private _addDdWithButton(dl: HTMLElement, id: string, label: string, title: string): void {
+ const dd = document.createElement('dd');
+ const button = document.createElement('button');
+ button.id = id;
+ button.textContent = label;
+ if (title) {
+ button.title = title;
+ }
+ dd.appendChild(button);
+ dl.appendChild(dd);
+ }
+
+ private _addDdWithCheckbox(dl: HTMLElement, id: string, label: string, title: string, checked: boolean): void {
+ const dd = document.createElement('dd');
+ const labelElement = document.createElement('label');
+ labelElement.htmlFor = id;
+ const checkbox = document.createElement('input');
+ checkbox.type = 'checkbox';
+ checkbox.id = id;
+ checkbox.checked = checked;
+ if (title) {
+ checkbox.title = title;
+ }
+ labelElement.appendChild(checkbox);
+ labelElement.appendChild(document.createTextNode(label));
+ dd.appendChild(labelElement);
+ dl.appendChild(dd);
+ }
+
+ private _addProgressStyles(container: HTMLElement): void {
+ const style = document.createElement('style');
+ style.textContent = `
+ #progress-progress {
+ border: 1px solid black;
+ height: 10px;
+ }
+ #progress-percent {
+ height: 100%;
+ }
+ #progress-indeterminate {
+ display: none;
+ position: relative;
+ height: 100%;
+ }
+ #progress-indeterminate:before {
+ content: '';
+ position: absolute;
+ left: 0;
+ bottom: 0px;
+ width: 50px;
+ height: 10px;
+ background: blue;
+ animation: ballbns 1s ease-in-out infinite alternate;
+ }
+ @keyframes ballbns {
+ 0% { left: 0; transform: translateX(0%); }
+ 100% { left: 100%; transform: translateX(-100%); }
+ }
+ `;
+ container.appendChild(style);
+ }
+}
diff --git a/demo/components/window/vtWindow.ts b/demo/components/window/vtWindow.ts
new file mode 100644
index 00000000..18614e55
--- /dev/null
+++ b/demo/components/window/vtWindow.ts
@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import type { IControlWindow } from '../controlBar';
+import type { Terminal } from '@xterm/xterm';
+
+export class VtWindow implements IControlWindow {
+ public readonly id = 'vt';
+ public readonly label = 'VT';
+
+ private _container: HTMLElement;
+ private _term: Terminal | undefined;
+
+ public build(container: HTMLElement): void {
+ this._container = container;
+
+ const heading = document.createElement('h3');
+ heading.textContent = 'VT';
+ container.appendChild(heading);
+
+ const vtContainer = document.createElement('div');
+ vtContainer.id = 'vt-container';
+ container.appendChild(vtContainer);
+ }
+
+ public initTerminal(term: Terminal): void {
+ this._term = term;
+ this._addVtButtons();
+ }
+
+ private _addVtButtons(): void {
+ const vtContainer = this._container.querySelector('#vt-container');
+ if (!vtContainer) return;
+
+ const vtFragment = document.createDocumentFragment();
+ const buttonSpecs: { [key: string]: { label: string; description: string; paramCount?: number } } = {
+ 'A': { label: 'CUU ↑', description: 'Cursor Up Ps Times' },
+ 'B': { label: 'CUD ↓', description: 'Cursor Down Ps Times' },
+ 'C': { label: 'CUF →', description: 'Cursor Forward Ps Times' },
+ 'D': { label: 'CUB ←', description: 'Cursor Backward Ps Times' },
+ 'E': { label: 'CNL', description: 'Cursor Next Line Ps Times' },
+ 'F': { label: 'CPL', description: 'Cursor Preceding Line Ps Times' },
+ 'G': { label: 'CHA', description: 'Cursor Character Absolute' },
+ 'H': { label: 'CUP', description: 'Cursor Position [row;column]', paramCount: 2 },
+ 'I': { label: 'CHT', description: 'Cursor Forward Tabulation Ps tab stops' },
+ 'J': { label: 'ED', description: 'Erase in Display' },
+ '?|J': { label: 'DECSED', description: 'Erase in Display' },
+ 'K': { label: 'EL', description: 'Erase in Line' },
+ '?|K': { label: 'DECSEL', description: 'Erase in Line' },
+ 'L': { label: 'IL', description: 'Insert Ps Line(s)' },
+ 'M': { label: 'DL', description: 'Delete Ps Line(s)' },
+ 'P': { label: 'DCH', description: 'Delete Ps Character(s)' },
+ ' q': { label: 'DECSCUSR', description: 'Set Cursor Style' },
+ '?2026h': { label: 'BSU', description: 'Begin synchronized update', paramCount: 0 },
+ '?2026l': { label: 'ESU', description: 'End synchronized update', paramCount: 0 }
+ };
+ for (const s of Object.keys(buttonSpecs)) {
+ const spec = buttonSpecs[s];
+ vtFragment.appendChild(this._createButton(spec.label, spec.description, s, spec.paramCount));
+ }
+
+ vtContainer.appendChild(vtFragment);
+ }
+
+ private _createButton(name: string, description: string, writeCsi: string, paramCount: number = 1): HTMLElement {
+ const inputs: HTMLInputElement[] = [];
+ for (let i = 0; i < paramCount; i++) {
+ const input = document.createElement('input');
+ input.type = 'number';
+ input.title = `Input #${i + 1}`;
+ inputs.push(input);
+ }
+
+ const element = document.createElement('button');
+ element.textContent = name;
+ const writeCsiSplit = writeCsi.split('|');
+ const prefix = writeCsiSplit.length === 2 ? writeCsiSplit[0] : '';
+ const suffix = writeCsiSplit[writeCsiSplit.length - 1];
+ element.addEventListener('click', () => this._term?.write(this._csi(`${prefix}${inputs.map(e => e.value).join(';')}${suffix}`)));
+
+ const desc = document.createElement('span');
+ desc.textContent = description;
+
+ const container = document.createElement('div');
+ container.classList.add('vt-button');
+ container.append(element, ...inputs, desc);
+ return container;
+ }
+
+ private _csi(e: string): string {
+ return `\x1b[${e}`;
+ }
+}
diff --git a/demo/index.html b/demo/index.html
index b943a7ae..389b85e6 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -14,28 +14,13 @@
xterm.js
-
-
-
-
-
-
-
-
+
-
-
Style
-
-
-
-
-
-
-
Test
-
-
- - Lifecycle
-
-
-
-
- - Performance
-
-
-
-
-
- - Styles
-
-
-
-
-
-
-
-
-
-
- - Decorations
-
-
-
-
- - Ligatures Addon
-
-
- - Weblinks Addon
-
-
- - Image Test
-
-
-
-
- - Events Test
-
-
-
- - Progress Addon
-
-
-
-
-
-
-
-
- State:
-
-
-
-
-
-
GPU
-
-
-
-
-