fix: scroller to work on mobile

and some other bug fix
This commit is contained in:
lda
2026-07-15 10:17:56 +07:00 Verified
parent 7ac482431a
commit 1276de79d3
4 changed files with 60 additions and 47 deletions
+1
View File
@@ -15,6 +15,7 @@
"@base-ui/react": "^1.4.0",
"@fontsource-variable/inter": "^5.2.8",
"@tailwindcss/vite": "^4.2.2",
"@xterm/addon-clipboard": "^0.2.0",
"@xterm/addon-fit": "^0.11.0",
"@xterm/xterm": "^6.0.0",
"class-variance-authority": "^0.7.1",
+21
View File
@@ -16,6 +16,9 @@ importers:
"@tailwindcss/vite":
specifier: ^4.2.2
version: 4.2.2([email protected](@types/[email protected])([email protected]))
"@xterm/addon-clipboard":
specifier: ^0.2.0
version: 0.2.0
"@xterm/addon-fit":
specifier: ^0.11.0
version: 0.11.0
@@ -1248,6 +1251,12 @@ packages:
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
"@xterm/[email protected]":
resolution:
{
integrity: sha512-Dl31BCtBhLaUEECUbEiVcCLvLBbaeGYdT7NofB8OJkGTD3MWgBsaLjXvfGAD4tQNHhm6mbKyYkR7XD8kiZsdNg==,
}
"@xterm/[email protected]":
resolution:
{
@@ -2286,6 +2295,12 @@ packages:
integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==,
}
[email protected]:
resolution:
{
integrity: sha512-5xVjhUZlHHeuO2W7w2rDFj/Kl1xLX+HjZxdOQwCsUOifl6UaoH1o1wsbsTMz+r0aeC7gCijvru02j6TfKZWzKg==,
}
[email protected]:
resolution:
{
@@ -4221,6 +4236,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
"@xterm/[email protected]":
dependencies:
js-base64: 3.8.1
"@xterm/[email protected]": {}
"@xterm/[email protected]": {}
@@ -4767,6 +4786,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]: {}
[email protected]:
+33 -44
View File
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { ClipboardAddon } from "@xterm/addon-clipboard";
import { FitAddon } from "@xterm/addon-fit";
import { Terminal as XTerm } from "@xterm/xterm";
import { Eraser, PlugZap, RotateCcw, Square, Terminal } from "lucide-react";
@@ -165,65 +166,51 @@ export function TerminalPage({
fontFamily: '"SFMono-Regular", Consolas, "Liberation Mono", monospace',
fontSize: 14,
lineHeight: 1.1,
// xterm uses this width for its internal scrollbar as well as the
// overview ruler. Reserve a larger touch target on coarse pointers.
overviewRuler: {
width: window.matchMedia("(pointer: coarse)").matches ? 22 : 16,
},
scrollback: 5000,
theme: {
background: "#0b1117",
foreground: "#e5e7eb",
cursor: "#6ee7a8",
scrollbarSliderBackground: "#52617099",
scrollbarSliderHoverBackground: "#6b7c8dcc",
scrollbarSliderActiveBackground: "#8294a6",
},
});
const fit = new FitAddon();
terminal.loadAddon(new ClipboardAddon());
terminal.loadAddon(fit);
terminal.open(hostRef.current);
terminal.attachCustomKeyEventHandler((event) => {
if (event.type !== "keydown") return true;
const copiesTerminalSelection =
event.type === "keydown" &&
event.ctrlKey &&
event.shiftKey &&
event.code === "KeyC";
if (!copiesTerminalSelection) return true;
const copy =
(event.ctrlKey && event.shiftKey && event.code === "KeyC") ||
(event.metaKey && event.code === "KeyC") ||
(event.ctrlKey && event.code === "Insert");
if (copy) {
event.preventDefault();
event.stopPropagation();
if (terminal.hasSelection()) {
if (!navigator.clipboard) {
toast.error("Clipboard access requires HTTPS or localhost");
} else {
void navigator.clipboard
.writeText(terminal.getSelection())
.catch((error) =>
toast.error("Clipboard access was denied", {
description: String(error),
}),
);
}
}
return false;
}
const paste =
(event.ctrlKey && event.shiftKey && event.code === "KeyV") ||
(event.metaKey && event.code === "KeyV") ||
(event.shiftKey && event.code === "Insert");
if (paste) {
event.preventDefault();
event.stopPropagation();
// Chromium reserves Ctrl+Shift+C for DevTools. Other clipboard shortcuts
// fall through to xterm's native copy/paste event handlers.
event.preventDefault();
event.stopPropagation();
if (terminal.hasSelection()) {
if (!navigator.clipboard) {
toast.error("Clipboard access requires HTTPS or localhost");
return false;
} else {
void navigator.clipboard
.writeText(terminal.getSelection())
.catch((error) =>
toast.error("Clipboard access was denied", {
description: String(error),
}),
);
}
void navigator.clipboard
.readText()
.then((text) => terminal.paste(text))
.catch((error) =>
toast.error("Clipboard access was denied", {
description: String(error),
}),
);
return false;
}
return true;
return false;
});
fit.fit();
terminalRef.current = terminal;
@@ -351,7 +338,9 @@ export function TerminalPage({
} finally {
setSession(null);
setConnection("idle");
terminalRef.current?.clear();
// xterm.clear() deliberately preserves the active cursor line. Closing
// a session should discard its complete screen and terminal modes.
terminalRef.current?.reset();
}
}
+5 -3
View File
@@ -465,11 +465,13 @@
fitted canvas, so keep it on the same surface as the terminal theme. */
background-color: #0b1117;
overscroll-behavior: contain;
scrollbar-width: none;
}
.terminal-surface .xterm-viewport::-webkit-scrollbar {
display: none;
.terminal-surface .xterm-scrollable-element > .scrollbar,
.terminal-surface .xterm-scrollable-element > .scrollbar > .slider {
/* Without this, mobile browsers claim a thumb drag as page panning before
xterm's pointer-capture scrollbar can process it. */
touch-action: none;
}
@media (max-width: 640px) {