xtermjs: fix inital load term size

This commit is contained in:
Will Owens 2025-10-25 11:35:34 -05:00
parent 53e31a964e
commit 71b9db14e2
No known key found for this signature in database
GPG key ID: 8C8384B16B623DA6
6 changed files with 179 additions and 110 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

9
js/dist/waitFor.d.ts vendored Normal file
View file

@ -0,0 +1,9 @@
/**
* Waits for a DOM element matching the selector to exist in the document.
* Resolves immediately if it already exists.
*
* @param selector CSS selector for the element to wait for
* @param timeout Optional timeout in milliseconds (default: no timeout)
* @returns Promise that resolves with the found element
*/
export declare function waitForElement<T extends Element>(selector: string, timeout?: number): Promise<T>;

38
js/src/waitFor.ts Normal file
View file

@ -0,0 +1,38 @@
/**
* Waits for a DOM element matching the selector to exist in the document.
* Resolves immediately if it already exists.
*
* @param selector CSS selector for the element to wait for
* @param timeout Optional timeout in milliseconds (default: no timeout)
* @returns Promise that resolves with the found element
*/
export function waitForElement<T extends Element>(
selector: string,
timeout?: number,
): Promise<T> {
return new Promise((resolve, reject) => {
// If it already exists, resolve immediately
const existing = document.querySelector<T>(selector);
if (existing) {
resolve(existing);
return;
}
const observer = new MutationObserver(() => {
const el = document.querySelector<T>(selector);
if (el) {
observer.disconnect();
resolve(el);
}
});
observer.observe(document.body, { childList: true, subtree: true });
if (timeout) {
setTimeout(() => {
observer.disconnect();
reject(new Error(`Timeout waiting for element: ${selector}`));
}, timeout);
}
});
}

View file

@ -4,118 +4,140 @@ import { IDisposable, Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit";
import { WebglAddon } from "xterm-addon-webgl";
import { waitForElement } from "./waitFor";
export class Xterm {
elem: HTMLElement;
term: Terminal;
resizeListener: () => void;
decoder: lib.UTF8Decoder;
elem: HTMLElement;
term: Terminal;
resizeListener: () => void;
decoder: lib.UTF8Decoder;
message: HTMLElement;
messageTimeout: number;
messageTimer: NodeJS.Timer;
message: HTMLElement;
messageTimeout: number;
messageTimer: NodeJS.Timer;
fitAddon: FitAddon;
disposables: IDisposable[] = [];
fitAddon: FitAddon;
disposables: IDisposable[] = [];
constructor(elem: HTMLElement) {
this.elem = elem;
const isWindows =
["Windows", "Win16", "Win32", "WinCE"].indexOf(navigator.platform) >= 0;
this.term = new Terminal({
cursorStyle: "block",
cursorBlink: true,
windowsMode: isWindows,
fontFamily:
"DejaVu Sans Mono, Everson Mono, FreeMono, Menlo, Terminal, monospace, Apple Symbols",
fontSize: 12,
});
constructor(elem: HTMLElement) {
this.elem = elem;
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
this.term = new Terminal({
cursorStyle: "block",
cursorBlink: true,
windowsMode: isWindows,
fontFamily: "DejaVu Sans Mono, Everson Mono, FreeMono, Menlo, Terminal, monospace, Apple Symbols",
fontSize: 12,
this.fitAddon = new FitAddon();
this.term.loadAddon(this.fitAddon);
this.message = elem.ownerDocument.createElement("div");
this.message.className = "xterm-overlay";
this.messageTimeout = 2000;
this.resizeListener = () => {
this.fitAddon.fit();
this.term.scrollToBottom();
this.showMessage(
String(this.term.cols) + "x" + String(this.term.rows),
this.messageTimeout,
);
};
this.term.open(elem);
this.term.focus();
window.addEventListener("resize", () => {
this.resizeListener();
});
waitForElement<HTMLElement>(".xterm-screen > canvas", 5000)
.then(() => {
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
this.resizeListener();
});
});
})
.catch((err) => {
console.error(err);
});
this.fitAddon = new FitAddon();
this.term.loadAddon(this.fitAddon);
this.decoder = new lib.UTF8Decoder();
}
this.message = elem.ownerDocument.createElement("div");
this.message.className = "xterm-overlay";
this.messageTimeout = 2000;
info(): { columns: number; rows: number } {
return { columns: this.term.cols, rows: this.term.rows };
}
this.resizeListener = () => {
this.fitAddon.fit();
this.term.scrollToBottom();
this.showMessage(String(this.term.cols) + "x" + String(this.term.rows), this.messageTimeout);
};
output(data: string) {
this.term.write(this.decoder.decode(data));
}
this.term.open(elem);
showMessage(message: string, timeout: number) {
this.message.textContent = message;
this.elem.appendChild(this.message);
this.term.focus()
this.resizeListener();
window.addEventListener("resize", () => { this.resizeListener(); });
this.decoder = new lib.UTF8Decoder()
};
info(): { columns: number, rows: number } {
return { columns: this.term.cols, rows: this.term.rows };
};
output(data: string) {
this.term.write(this.decoder.decode(data));
};
showMessage(message: string, timeout: number) {
this.message.textContent = message;
this.elem.appendChild(this.message);
if (this.messageTimer) {
clearTimeout(this.messageTimer);
}
if (timeout > 0) {
this.messageTimer = setTimeout(() => {
this.elem.removeChild(this.message);
}, timeout);
}
};
removeMessage(): void {
if (this.message.parentNode == this.elem) {
this.elem.removeChild(this.message);
}
if (this.messageTimer) {
clearTimeout(this.messageTimer);
}
setWindowTitle(title: string) {
document.title = title;
};
setPreferences(value: object) {
Object.keys(value).forEach((key) => {
if (key && key == "enable-webgl") {
this.term.loadAddon(new WebglAddon());
}
});
};
onInput(callback: (input: string) => void) {
this.disposables.push(this.term.onData((data) => {
callback(data);
}));
};
onResize(callback: (colmuns: number, rows: number) => void) {
this.disposables.push(this.term.onResize((data) => {
callback(data.cols, data.rows);
}));
};
deactivate(): void {
this.disposables.forEach(d => d.dispose())
this.term.blur();
if (timeout > 0) {
this.messageTimer = setTimeout(() => {
this.elem.removeChild(this.message);
}, timeout);
}
}
reset(): void {
this.removeMessage();
this.term.clear();
removeMessage(): void {
if (this.message.parentNode == this.elem) {
this.elem.removeChild(this.message);
}
}
close(): void {
window.removeEventListener("resize", this.resizeListener);
this.term.dispose();
}
setWindowTitle(title: string) {
document.title = title;
}
setPreferences(value: object) {
Object.keys(value).forEach((key) => {
if (key && key == "enable-webgl") {
this.term.loadAddon(new WebglAddon());
}
});
}
onInput(callback: (input: string) => void) {
this.disposables.push(
this.term.onData((data) => {
callback(data);
}),
);
}
onResize(callback: (colmuns: number, rows: number) => void) {
this.disposables.push(
this.term.onResize((data) => {
callback(data.cols, data.rows);
}),
);
}
deactivate(): void {
this.disposables.forEach((d) => d.dispose());
this.term.blur();
}
reset(): void {
this.removeMessage();
this.term.clear();
}
close(): void {
window.removeEventListener("resize", this.resizeListener);
this.term.dispose();
}
}

File diff suppressed because one or more lines are too long