@ai-matrx/terminal 0.0.0-stage → 0.1.2
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/CHANGELOG.md +37 -0
- package/LICENSE +21 -0
- package/README.md +52 -2
- package/dist/index.cjs +567 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +260 -0
- package/dist/index.d.ts +260 -0
- package/dist/index.js +544 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +849 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +95 -0
- package/dist/react.d.ts +95 -0
- package/dist/react.js +817 -0
- package/dist/react.js.map +1 -0
- package/dist/styles.css +148 -0
- package/dist/tokens.css +25 -0
- package/package.json +97 -4
package/dist/react.js
ADDED
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/react/Terminal.tsx
|
|
4
|
+
import "@xterm/xterm/css/xterm.css";
|
|
5
|
+
import "@ai-matrx/terminal/styles.css";
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
|
+
|
|
8
|
+
// src/core/keys.ts
|
|
9
|
+
var ACCESSORY_KEYS = [
|
|
10
|
+
{ id: "esc", label: "Esc", aria: "Escape", kind: "send", group: 0 },
|
|
11
|
+
{ id: "tab", label: "Tab", aria: "Tab", kind: "send", group: 0 },
|
|
12
|
+
{ id: "ctrl", label: "Ctrl", aria: "Control", kind: "modifier", group: 0 },
|
|
13
|
+
{ id: "alt", label: "Alt", aria: "Alt", kind: "modifier", group: 0 },
|
|
14
|
+
{ id: "left", label: "\u2190", aria: "Left arrow", kind: "send", group: 1 },
|
|
15
|
+
{ id: "down", label: "\u2193", aria: "Down arrow", kind: "send", group: 1 },
|
|
16
|
+
{ id: "up", label: "\u2191", aria: "Up arrow", kind: "send", group: 1 },
|
|
17
|
+
{ id: "right", label: "\u2192", aria: "Right arrow", kind: "send", group: 1 },
|
|
18
|
+
{ id: "pipe", label: "|", aria: "Pipe", kind: "send", group: 2 },
|
|
19
|
+
{ id: "slash", label: "/", aria: "Slash", kind: "send", group: 2 },
|
|
20
|
+
{ id: "tilde", label: "~", aria: "Tilde", kind: "send", group: 2 },
|
|
21
|
+
{ id: "dash", label: "-", aria: "Dash", kind: "send", group: 2 },
|
|
22
|
+
{ id: "home", label: "Home", aria: "Home", kind: "send", group: 3 },
|
|
23
|
+
{ id: "end", label: "End", aria: "End", kind: "send", group: 3 },
|
|
24
|
+
{ id: "pgup", label: "PgUp", aria: "Page up", kind: "send", group: 3 },
|
|
25
|
+
{ id: "pgdn", label: "PgDn", aria: "Page down", kind: "send", group: 3 },
|
|
26
|
+
{ id: "paste", label: "Paste", aria: "Paste", kind: "action", group: 4 },
|
|
27
|
+
{ id: "hide", label: "Hide", aria: "Hide keyboard", kind: "action", group: 4 }
|
|
28
|
+
];
|
|
29
|
+
var ESC = "\x1B";
|
|
30
|
+
var CURSOR = { up: "A", down: "B", right: "C", left: "D", home: "H", end: "F" };
|
|
31
|
+
var TILDE = { pgup: "5", pgdn: "6" };
|
|
32
|
+
var LITERAL = { pipe: "|", slash: "/", tilde: "~", dash: "-", tab: " ", esc: ESC };
|
|
33
|
+
function modParam(mods) {
|
|
34
|
+
return 1 + (mods.alt ? 2 : 0) + (mods.ctrl ? 4 : 0);
|
|
35
|
+
}
|
|
36
|
+
function keySequence(id, mods, modes) {
|
|
37
|
+
const cursor = CURSOR[id];
|
|
38
|
+
if (cursor !== void 0) {
|
|
39
|
+
const m = modParam(mods);
|
|
40
|
+
if (m > 1) return `${ESC}[1;${m}${cursor}`;
|
|
41
|
+
return modes.applicationCursor ? `${ESC}O${cursor}` : `${ESC}[${cursor}`;
|
|
42
|
+
}
|
|
43
|
+
const tilde = TILDE[id];
|
|
44
|
+
if (tilde !== void 0) {
|
|
45
|
+
const m = modParam(mods);
|
|
46
|
+
return m > 1 ? `${ESC}[${tilde};${m}~` : `${ESC}[${tilde}~`;
|
|
47
|
+
}
|
|
48
|
+
const literal = LITERAL[id];
|
|
49
|
+
if (literal !== void 0) {
|
|
50
|
+
if (id === "tab" && mods.ctrl === false && mods.alt === false) return literal;
|
|
51
|
+
if (id === "esc") return mods.alt ? ESC + ESC : ESC;
|
|
52
|
+
return applyModifiers(literal, mods).data;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function controlCode(char) {
|
|
57
|
+
if (char.length !== 1) return null;
|
|
58
|
+
const code = char.charCodeAt(0);
|
|
59
|
+
if (code >= 97 && code <= 122) return String.fromCharCode(code - 96);
|
|
60
|
+
if (code >= 64 && code <= 95) return String.fromCharCode(code - 64);
|
|
61
|
+
switch (char) {
|
|
62
|
+
case " ":
|
|
63
|
+
case "2":
|
|
64
|
+
return "\0";
|
|
65
|
+
case "3":
|
|
66
|
+
return "\x1B";
|
|
67
|
+
case "4":
|
|
68
|
+
return "";
|
|
69
|
+
case "5":
|
|
70
|
+
return "";
|
|
71
|
+
case "6":
|
|
72
|
+
return "";
|
|
73
|
+
case "7":
|
|
74
|
+
case "/":
|
|
75
|
+
case "-":
|
|
76
|
+
return "";
|
|
77
|
+
case "8":
|
|
78
|
+
case "?":
|
|
79
|
+
return "\x7F";
|
|
80
|
+
default:
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function applyModifiers(data, mods) {
|
|
85
|
+
if (!mods.ctrl && !mods.alt) return { data, consumed: false };
|
|
86
|
+
const chars = Array.from(data);
|
|
87
|
+
if (chars.length !== 1) {
|
|
88
|
+
const m = /^\x1b(?:\[|O)([ABCDHF])$/.exec(data);
|
|
89
|
+
if (m) return { data: `${ESC}[1;${modParam(mods)}${m[1]}`, consumed: true };
|
|
90
|
+
return { data, consumed: false };
|
|
91
|
+
}
|
|
92
|
+
let out = data;
|
|
93
|
+
if (mods.ctrl) {
|
|
94
|
+
const ctl = controlCode(data);
|
|
95
|
+
if (ctl !== null) out = ctl;
|
|
96
|
+
}
|
|
97
|
+
if (mods.alt) out = ESC + out;
|
|
98
|
+
return { data: out, consumed: true };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/core/modifiers.ts
|
|
102
|
+
var DOUBLE_TAP_MS = 350;
|
|
103
|
+
var MODIFIER_OFF = { state: "off", tappedAt: null };
|
|
104
|
+
function tapModifier(latch, now) {
|
|
105
|
+
switch (latch.state) {
|
|
106
|
+
case "off":
|
|
107
|
+
return { state: "once", tappedAt: now };
|
|
108
|
+
case "once":
|
|
109
|
+
return latch.tappedAt !== null && now - latch.tappedAt <= DOUBLE_TAP_MS ? { state: "locked", tappedAt: now } : { state: "off", tappedAt: now };
|
|
110
|
+
case "locked":
|
|
111
|
+
return { state: "off", tappedAt: now };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function afterKey(latch) {
|
|
115
|
+
return latch.state === "once" ? { state: "off", tappedAt: latch.tappedAt } : latch;
|
|
116
|
+
}
|
|
117
|
+
function isActive(latch) {
|
|
118
|
+
return latch.state !== "off";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/core/gesture.ts
|
|
122
|
+
var LONG_PRESS_MS = 450;
|
|
123
|
+
var FLING_FRICTION = 0.95;
|
|
124
|
+
var FLING_STOP = 2e-3;
|
|
125
|
+
var FLING_MIN = 0.01;
|
|
126
|
+
function createGestureRecognizer(options) {
|
|
127
|
+
const slop = options.slopPx ?? 8;
|
|
128
|
+
const lineHeight = Math.max(1, options.lineHeight);
|
|
129
|
+
let phase = "idle";
|
|
130
|
+
let start = { x: 0, y: 0, t: 0 };
|
|
131
|
+
let last = start;
|
|
132
|
+
let carryPx = 0;
|
|
133
|
+
let trail = [];
|
|
134
|
+
function scrollBy(fromY, toY) {
|
|
135
|
+
carryPx += fromY - toY;
|
|
136
|
+
const lines = carryPx > 0 ? Math.floor(carryPx / lineHeight) : Math.ceil(carryPx / lineHeight);
|
|
137
|
+
if (lines === 0) return [];
|
|
138
|
+
carryPx -= lines * lineHeight;
|
|
139
|
+
return [{ kind: "scroll", lines }];
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
get phase() {
|
|
143
|
+
return phase;
|
|
144
|
+
},
|
|
145
|
+
down(s) {
|
|
146
|
+
phase = "pressed";
|
|
147
|
+
start = s;
|
|
148
|
+
last = s;
|
|
149
|
+
carryPx = 0;
|
|
150
|
+
trail = [s];
|
|
151
|
+
return [];
|
|
152
|
+
},
|
|
153
|
+
move(s) {
|
|
154
|
+
if (phase === "idle") return [];
|
|
155
|
+
const out = [];
|
|
156
|
+
if (phase === "pressed") {
|
|
157
|
+
if (Math.hypot(s.x - start.x, s.y - start.y) <= slop) return [];
|
|
158
|
+
phase = "scrolling";
|
|
159
|
+
}
|
|
160
|
+
if (phase === "scrolling") {
|
|
161
|
+
out.push(...scrollBy(last.y, s.y));
|
|
162
|
+
trail.push(s);
|
|
163
|
+
while (trail.length > 2 && s.t - trail[0].t > 100) trail.shift();
|
|
164
|
+
} else if (phase === "selecting") {
|
|
165
|
+
out.push({ kind: "select-extend", x: s.x, y: s.y });
|
|
166
|
+
}
|
|
167
|
+
last = s;
|
|
168
|
+
return out;
|
|
169
|
+
},
|
|
170
|
+
up(s) {
|
|
171
|
+
const was = phase;
|
|
172
|
+
phase = "idle";
|
|
173
|
+
if (was === "pressed") return [{ kind: "tap", x: start.x, y: start.y }];
|
|
174
|
+
if (was === "selecting") return [{ kind: "select-end" }];
|
|
175
|
+
if (was === "scrolling") {
|
|
176
|
+
const out = scrollBy(last.y, s.y);
|
|
177
|
+
trail.push(s);
|
|
178
|
+
const first = trail[0];
|
|
179
|
+
const dt = s.t - first.t;
|
|
180
|
+
const velocity = dt > 0 ? (first.y - s.y) / dt / lineHeight : 0;
|
|
181
|
+
if (Math.abs(velocity) >= FLING_MIN) out.push({ kind: "fling", velocity });
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
return [];
|
|
185
|
+
},
|
|
186
|
+
cancel() {
|
|
187
|
+
const was = phase;
|
|
188
|
+
phase = "idle";
|
|
189
|
+
return was === "selecting" ? [{ kind: "select-end" }] : [];
|
|
190
|
+
},
|
|
191
|
+
longPress() {
|
|
192
|
+
if (phase !== "pressed") return [];
|
|
193
|
+
phase = "selecting";
|
|
194
|
+
return [{ kind: "select-start", x: start.x, y: start.y }];
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function flingStep(velocity, dtMs) {
|
|
199
|
+
const lines = velocity * dtMs;
|
|
200
|
+
const next = velocity * Math.pow(FLING_FRICTION, dtMs / 16);
|
|
201
|
+
return { lines, velocity: Math.abs(next) < FLING_STOP ? 0 : next };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/core/selection.ts
|
|
205
|
+
var WORD = /[A-Za-z0-9_\-./~:@%+=,?&#]/;
|
|
206
|
+
function wordBounds(line, col) {
|
|
207
|
+
const chars = Array.from(line);
|
|
208
|
+
if (col < 0 || col >= chars.length || !WORD.test(chars[col] ?? " ")) return { start: col, end: col + 1 };
|
|
209
|
+
let start = col;
|
|
210
|
+
let end = col + 1;
|
|
211
|
+
while (start > 0 && WORD.test(chars[start - 1])) start--;
|
|
212
|
+
while (end < chars.length && WORD.test(chars[end])) end++;
|
|
213
|
+
return { start, end };
|
|
214
|
+
}
|
|
215
|
+
function spanBetween(anchor, focus, cols) {
|
|
216
|
+
const a = anchor.row * cols + anchor.col;
|
|
217
|
+
const b = focus.row * cols + focus.col;
|
|
218
|
+
const from = Math.min(a, b);
|
|
219
|
+
const to = Math.max(a, b);
|
|
220
|
+
return { col: from % cols, row: Math.floor(from / cols), length: to - from + 1 };
|
|
221
|
+
}
|
|
222
|
+
function cellAt(point, box, grid) {
|
|
223
|
+
const cw = box.width / Math.max(1, grid.cols);
|
|
224
|
+
const ch = box.height / Math.max(1, grid.rows);
|
|
225
|
+
const col = Math.min(grid.cols - 1, Math.max(0, Math.floor((point.x - box.left) / cw)));
|
|
226
|
+
const screenRow = Math.min(grid.rows - 1, Math.max(0, Math.floor((point.y - box.top) / ch)));
|
|
227
|
+
return { col, row: grid.viewportY + screenRow };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/core/theme.ts
|
|
231
|
+
var TERMINAL_FONT_FAMILY = 'ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace';
|
|
232
|
+
var TERMINAL_DEFAULTS = {
|
|
233
|
+
fontSize: 13,
|
|
234
|
+
lineHeight: 1.2,
|
|
235
|
+
scrollback: 5e3,
|
|
236
|
+
/** Side padding inside the terminal box, px. */
|
|
237
|
+
padding: 8
|
|
238
|
+
};
|
|
239
|
+
var FONT_SIZE_MIN = 9;
|
|
240
|
+
var FONT_SIZE_MAX = 24;
|
|
241
|
+
function stepFontSize(current, delta) {
|
|
242
|
+
return Math.min(FONT_SIZE_MAX, Math.max(FONT_SIZE_MIN, Math.round(current) + delta));
|
|
243
|
+
}
|
|
244
|
+
function pinchFontSize(startSize, startDistance, distance) {
|
|
245
|
+
if (startDistance <= 0) return startSize;
|
|
246
|
+
return Math.min(FONT_SIZE_MAX, Math.max(FONT_SIZE_MIN, Math.round(startSize * distance / startDistance)));
|
|
247
|
+
}
|
|
248
|
+
function terminalTheme(dark) {
|
|
249
|
+
return dark ? {
|
|
250
|
+
background: "#1e1e1e",
|
|
251
|
+
foreground: "#e4e4e7",
|
|
252
|
+
cursor: "#e4e4e7",
|
|
253
|
+
cursorAccent: "#1e1e1e",
|
|
254
|
+
black: "#1e1e1e",
|
|
255
|
+
red: "#f87171",
|
|
256
|
+
green: "#4ade80",
|
|
257
|
+
yellow: "#facc15",
|
|
258
|
+
blue: "#60a5fa",
|
|
259
|
+
magenta: "#c084fc",
|
|
260
|
+
cyan: "#22d3ee",
|
|
261
|
+
white: "#e5e7eb",
|
|
262
|
+
brightBlack: "#71717a",
|
|
263
|
+
brightRed: "#fca5a5",
|
|
264
|
+
brightGreen: "#86efac",
|
|
265
|
+
brightYellow: "#fde047",
|
|
266
|
+
brightBlue: "#93c5fd",
|
|
267
|
+
brightMagenta: "#d8b4fe",
|
|
268
|
+
brightCyan: "#67e8f9",
|
|
269
|
+
brightWhite: "#f4f4f5",
|
|
270
|
+
selectionBackground: "#3b82f680"
|
|
271
|
+
} : {
|
|
272
|
+
background: "#ffffff",
|
|
273
|
+
foreground: "#18181b",
|
|
274
|
+
cursor: "#18181b",
|
|
275
|
+
cursorAccent: "#ffffff",
|
|
276
|
+
black: "#18181b",
|
|
277
|
+
red: "#b91c1c",
|
|
278
|
+
green: "#15803d",
|
|
279
|
+
yellow: "#b45309",
|
|
280
|
+
blue: "#1d4ed8",
|
|
281
|
+
magenta: "#7c3aed",
|
|
282
|
+
cyan: "#0e7490",
|
|
283
|
+
white: "#d4d4d8",
|
|
284
|
+
brightBlack: "#71717a",
|
|
285
|
+
brightRed: "#dc2626",
|
|
286
|
+
brightGreen: "#16a34a",
|
|
287
|
+
brightYellow: "#d97706",
|
|
288
|
+
brightBlue: "#2563eb",
|
|
289
|
+
brightMagenta: "#9333ea",
|
|
290
|
+
brightCyan: "#0891b2",
|
|
291
|
+
brightWhite: "#a1a1aa",
|
|
292
|
+
selectionBackground: "#93c5fd99"
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/core/viewport.ts
|
|
297
|
+
function visibleHeightBelow(top, vv, layoutHeight) {
|
|
298
|
+
const bottom = vv ? vv.offsetTop + vv.height : layoutHeight;
|
|
299
|
+
return Math.max(0, Math.floor(bottom - top));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/react/AccessoryBar.tsx
|
|
303
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
304
|
+
function PasteIcon() {
|
|
305
|
+
return /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
306
|
+
/* @__PURE__ */ jsx("rect", { x: "8", y: "2", width: "8", height: "4", rx: "1" }),
|
|
307
|
+
/* @__PURE__ */ jsx("path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" })
|
|
308
|
+
] });
|
|
309
|
+
}
|
|
310
|
+
function HideKeyboardIcon() {
|
|
311
|
+
return /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
312
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "3", width: "20", height: "12", rx: "2" }),
|
|
313
|
+
/* @__PURE__ */ jsx("path", { d: "M6 7h.01M10 7h.01M14 7h.01M18 7h.01M7 11h10" }),
|
|
314
|
+
/* @__PURE__ */ jsx("path", { d: "m8 19 4 3 4-3" })
|
|
315
|
+
] });
|
|
316
|
+
}
|
|
317
|
+
var ICONS = { paste: PasteIcon, hide: HideKeyboardIcon };
|
|
318
|
+
function AccessoryBar({ ctrl, alt, onKey }) {
|
|
319
|
+
const groups = [];
|
|
320
|
+
for (const key of ACCESSORY_KEYS) (groups[key.group] ??= []).push(key);
|
|
321
|
+
return /* @__PURE__ */ jsx("div", { className: "mxt-bar", role: "toolbar", "aria-label": "Terminal keys", children: groups.map((group, gi) => /* @__PURE__ */ jsxs("div", { className: "mxt-bar-group", children: [
|
|
322
|
+
gi > 0 ? /* @__PURE__ */ jsx("span", { className: "mxt-bar-divider", "aria-hidden": "true" }) : null,
|
|
323
|
+
group.map((key) => {
|
|
324
|
+
const state = key.id === "ctrl" ? ctrl : key.id === "alt" ? alt : null;
|
|
325
|
+
const Icon = ICONS[key.id];
|
|
326
|
+
return /* @__PURE__ */ jsx(
|
|
327
|
+
"button",
|
|
328
|
+
{
|
|
329
|
+
type: "button",
|
|
330
|
+
className: "mxt-key",
|
|
331
|
+
"data-state": state ?? void 0,
|
|
332
|
+
"aria-label": key.aria,
|
|
333
|
+
"aria-pressed": state === null ? void 0 : state !== "off",
|
|
334
|
+
onPointerDown: (e) => e.preventDefault(),
|
|
335
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
336
|
+
onClick: () => onKey(key.id),
|
|
337
|
+
children: Icon ? /* @__PURE__ */ jsx(Icon, {}) : key.label
|
|
338
|
+
},
|
|
339
|
+
key.id
|
|
340
|
+
);
|
|
341
|
+
})
|
|
342
|
+
] }, gi)) });
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/react/Terminal.tsx
|
|
346
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
347
|
+
function prefersDark() {
|
|
348
|
+
return typeof document !== "undefined" && document.documentElement.classList.contains("dark");
|
|
349
|
+
}
|
|
350
|
+
function coarsePointer() {
|
|
351
|
+
return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(pointer: coarse)").matches;
|
|
352
|
+
}
|
|
353
|
+
function Terminal(props) {
|
|
354
|
+
const {
|
|
355
|
+
theme = "auto",
|
|
356
|
+
fontSize = TERMINAL_DEFAULTS.fontSize,
|
|
357
|
+
lineHeight = TERMINAL_DEFAULTS.lineHeight,
|
|
358
|
+
scrollback = TERMINAL_DEFAULTS.scrollback,
|
|
359
|
+
padding = TERMINAL_DEFAULTS.padding,
|
|
360
|
+
convertEol = false,
|
|
361
|
+
cursorBlink = true,
|
|
362
|
+
disableStdin = false,
|
|
363
|
+
links = true,
|
|
364
|
+
touch = "auto",
|
|
365
|
+
accessory = "auto",
|
|
366
|
+
fit = "container",
|
|
367
|
+
autoFocus = false
|
|
368
|
+
} = props;
|
|
369
|
+
const rootRef = useRef(null);
|
|
370
|
+
const hostRef = useRef(null);
|
|
371
|
+
const termRef = useRef(null);
|
|
372
|
+
const fitRef = useRef(null);
|
|
373
|
+
const handleRef = useRef(null);
|
|
374
|
+
const latchRef = useRef({ ctrl: MODIFIER_OFF, alt: MODIFIER_OFF });
|
|
375
|
+
const callbacksRef = useRef({
|
|
376
|
+
onData: props.onData,
|
|
377
|
+
onResize: props.onResize,
|
|
378
|
+
onReady: props.onReady,
|
|
379
|
+
onInterrupt: props.onInterrupt,
|
|
380
|
+
onFontSizeChange: props.onFontSizeChange
|
|
381
|
+
});
|
|
382
|
+
const fontSizeRef = useRef(fontSize);
|
|
383
|
+
const [latches, setLatches] = useState({ ctrl: MODIFIER_OFF, alt: MODIFIER_OFF });
|
|
384
|
+
const [coarse, setCoarse] = useState(false);
|
|
385
|
+
const [dark, setDark] = useState(theme === "dark");
|
|
386
|
+
const [copyVisible, setCopyVisible] = useState(false);
|
|
387
|
+
const [viewportHeight, setViewportHeight] = useState(null);
|
|
388
|
+
const [booted, setBooted] = useState(false);
|
|
389
|
+
useEffect(() => {
|
|
390
|
+
callbacksRef.current = {
|
|
391
|
+
onData: props.onData,
|
|
392
|
+
onResize: props.onResize,
|
|
393
|
+
onReady: props.onReady,
|
|
394
|
+
onInterrupt: props.onInterrupt,
|
|
395
|
+
onFontSizeChange: props.onFontSizeChange
|
|
396
|
+
};
|
|
397
|
+
fontSizeRef.current = fontSize;
|
|
398
|
+
});
|
|
399
|
+
useEffect(() => {
|
|
400
|
+
setCoarse(coarsePointer());
|
|
401
|
+
const mq = typeof window.matchMedia === "function" ? window.matchMedia("(pointer: coarse)") : null;
|
|
402
|
+
const onChange = () => setCoarse(coarsePointer());
|
|
403
|
+
mq?.addEventListener("change", onChange);
|
|
404
|
+
return () => mq?.removeEventListener("change", onChange);
|
|
405
|
+
}, []);
|
|
406
|
+
useEffect(() => {
|
|
407
|
+
if (theme !== "auto") {
|
|
408
|
+
setDark(theme === "dark");
|
|
409
|
+
return void 0;
|
|
410
|
+
}
|
|
411
|
+
setDark(prefersDark());
|
|
412
|
+
if (typeof MutationObserver === "undefined") return void 0;
|
|
413
|
+
const observer = new MutationObserver(() => setDark(prefersDark()));
|
|
414
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
|
|
415
|
+
return () => observer.disconnect();
|
|
416
|
+
}, [theme]);
|
|
417
|
+
const touchOn = touch === "on" || touch === "auto" && coarse;
|
|
418
|
+
const barOn = accessory === "on" || accessory === "auto" && coarse;
|
|
419
|
+
function setLatchState(next) {
|
|
420
|
+
latchRef.current = next;
|
|
421
|
+
setLatches(next);
|
|
422
|
+
}
|
|
423
|
+
function emit(data) {
|
|
424
|
+
const current = latchRef.current;
|
|
425
|
+
const mods = { ctrl: isActive(current.ctrl), alt: isActive(current.alt) };
|
|
426
|
+
const { data: out, consumed } = applyModifiers(data, mods);
|
|
427
|
+
if (consumed) setLatchState({ ctrl: afterKey(current.ctrl), alt: afterKey(current.alt) });
|
|
428
|
+
callbacksRef.current.onData?.(out);
|
|
429
|
+
}
|
|
430
|
+
useEffect(() => {
|
|
431
|
+
let cancelled = false;
|
|
432
|
+
let disposeAll = null;
|
|
433
|
+
void (async () => {
|
|
434
|
+
const [{ Terminal: XTermCtor }, { FitAddon: FitCtor }, linksModule] = await Promise.all([
|
|
435
|
+
import("@xterm/xterm"),
|
|
436
|
+
import("@xterm/addon-fit"),
|
|
437
|
+
import("@xterm/addon-web-links")
|
|
438
|
+
]);
|
|
439
|
+
const host = hostRef.current;
|
|
440
|
+
if (cancelled || !host) return;
|
|
441
|
+
const options = {
|
|
442
|
+
fontFamily: TERMINAL_FONT_FAMILY,
|
|
443
|
+
fontSize,
|
|
444
|
+
lineHeight,
|
|
445
|
+
scrollback,
|
|
446
|
+
convertEol,
|
|
447
|
+
cursorBlink,
|
|
448
|
+
cursorStyle: "bar",
|
|
449
|
+
disableStdin,
|
|
450
|
+
allowProposedApi: false,
|
|
451
|
+
theme: terminalTheme(theme === "auto" ? prefersDark() : theme === "dark")
|
|
452
|
+
};
|
|
453
|
+
const term = new XTermCtor(options);
|
|
454
|
+
const fitAddon = new FitCtor();
|
|
455
|
+
term.loadAddon(fitAddon);
|
|
456
|
+
if (links) term.loadAddon(new linksModule.WebLinksAddon());
|
|
457
|
+
term.open(host);
|
|
458
|
+
const textarea = term.textarea;
|
|
459
|
+
if (textarea) {
|
|
460
|
+
textarea.setAttribute("autocorrect", "off");
|
|
461
|
+
textarea.setAttribute("autocapitalize", "off");
|
|
462
|
+
textarea.setAttribute("autocomplete", "off");
|
|
463
|
+
textarea.setAttribute("spellcheck", "false");
|
|
464
|
+
textarea.setAttribute("aria-label", props["aria-label"] ?? "Terminal input");
|
|
465
|
+
}
|
|
466
|
+
termRef.current = term;
|
|
467
|
+
fitRef.current = fitAddon;
|
|
468
|
+
const dataSub = term.onData((data) => emit(data));
|
|
469
|
+
const resizeSub = term.onResize(({ cols, rows }) => callbacksRef.current.onResize?.({ cols, rows }));
|
|
470
|
+
term.attachCustomKeyEventHandler((event) => {
|
|
471
|
+
const zoom = callbacksRef.current.onFontSizeChange;
|
|
472
|
+
if (zoom && event.type === "keydown" && event.metaKey && !event.altKey && !event.ctrlKey) {
|
|
473
|
+
const delta = event.key === "=" || event.key === "+" ? 1 : event.key === "-" ? -1 : event.key === "0" ? 0 : null;
|
|
474
|
+
if (delta !== null) {
|
|
475
|
+
event.preventDefault();
|
|
476
|
+
zoom(delta === 0 ? TERMINAL_DEFAULTS.fontSize : stepFontSize(fontSizeRef.current, delta));
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (event.type === "keydown" && event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey && event.key.toLowerCase() === "c" && !term.hasSelection()) {
|
|
481
|
+
const consumed = callbacksRef.current.onInterrupt?.() ?? false;
|
|
482
|
+
if (consumed) return false;
|
|
483
|
+
}
|
|
484
|
+
return true;
|
|
485
|
+
});
|
|
486
|
+
const selectionSub = term.onSelectionChange(() => setCopyVisible(term.hasSelection()));
|
|
487
|
+
const handle = {
|
|
488
|
+
write: (data) => new Promise((resolve) => term.write(data, resolve)),
|
|
489
|
+
reset: () => term.reset(),
|
|
490
|
+
clear: () => term.clear(),
|
|
491
|
+
focus: () => term.focus(),
|
|
492
|
+
blur: () => term.blur(),
|
|
493
|
+
getText: () => {
|
|
494
|
+
const buffer = term.buffer.active;
|
|
495
|
+
const lines = [];
|
|
496
|
+
for (let y = 0; y < buffer.length; y++) lines.push(buffer.getLine(y)?.translateToString(true) ?? "");
|
|
497
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
498
|
+
return lines.join("\n");
|
|
499
|
+
},
|
|
500
|
+
get cols() {
|
|
501
|
+
return term.cols;
|
|
502
|
+
},
|
|
503
|
+
get rows() {
|
|
504
|
+
return term.rows;
|
|
505
|
+
},
|
|
506
|
+
xterm: term
|
|
507
|
+
};
|
|
508
|
+
handleRef.current = handle;
|
|
509
|
+
try {
|
|
510
|
+
fitAddon.fit();
|
|
511
|
+
} catch {
|
|
512
|
+
}
|
|
513
|
+
setBooted(true);
|
|
514
|
+
callbacksRef.current.onResize?.({ cols: term.cols, rows: term.rows });
|
|
515
|
+
callbacksRef.current.onReady?.(handle);
|
|
516
|
+
if (autoFocus) term.focus();
|
|
517
|
+
disposeAll = () => {
|
|
518
|
+
dataSub.dispose();
|
|
519
|
+
resizeSub.dispose();
|
|
520
|
+
selectionSub.dispose();
|
|
521
|
+
term.dispose();
|
|
522
|
+
};
|
|
523
|
+
})();
|
|
524
|
+
return () => {
|
|
525
|
+
cancelled = true;
|
|
526
|
+
disposeAll?.();
|
|
527
|
+
termRef.current = null;
|
|
528
|
+
fitRef.current = null;
|
|
529
|
+
handleRef.current = null;
|
|
530
|
+
};
|
|
531
|
+
}, []);
|
|
532
|
+
useEffect(() => {
|
|
533
|
+
const term = termRef.current;
|
|
534
|
+
if (!term) return;
|
|
535
|
+
term.options.theme = terminalTheme(dark);
|
|
536
|
+
}, [dark, booted]);
|
|
537
|
+
useEffect(() => {
|
|
538
|
+
const term = termRef.current;
|
|
539
|
+
if (!term) return;
|
|
540
|
+
term.options.disableStdin = disableStdin;
|
|
541
|
+
term.options.fontSize = fontSize;
|
|
542
|
+
term.options.lineHeight = lineHeight;
|
|
543
|
+
try {
|
|
544
|
+
fitRef.current?.fit();
|
|
545
|
+
} catch {
|
|
546
|
+
}
|
|
547
|
+
}, [disableStdin, fontSize, lineHeight, booted]);
|
|
548
|
+
useEffect(() => {
|
|
549
|
+
const host = hostRef.current;
|
|
550
|
+
if (!host) return void 0;
|
|
551
|
+
const refit = () => {
|
|
552
|
+
const term = termRef.current;
|
|
553
|
+
if (!term || host.clientWidth <= 0 || host.clientHeight <= 0) return;
|
|
554
|
+
try {
|
|
555
|
+
fitRef.current?.fit();
|
|
556
|
+
term.refresh(0, term.rows - 1);
|
|
557
|
+
} catch {
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
refit();
|
|
561
|
+
if (typeof ResizeObserver === "undefined") return void 0;
|
|
562
|
+
const ro = new ResizeObserver(refit);
|
|
563
|
+
ro.observe(host);
|
|
564
|
+
return () => ro.disconnect();
|
|
565
|
+
}, [booted]);
|
|
566
|
+
useEffect(() => {
|
|
567
|
+
if (fit !== "viewport") {
|
|
568
|
+
setViewportHeight(null);
|
|
569
|
+
return void 0;
|
|
570
|
+
}
|
|
571
|
+
const root = rootRef.current;
|
|
572
|
+
if (!root) return void 0;
|
|
573
|
+
const vv = window.visualViewport;
|
|
574
|
+
const measure = () => {
|
|
575
|
+
if (window.scrollY !== 0) window.scrollTo(0, 0);
|
|
576
|
+
const top = root.getBoundingClientRect().top + window.scrollY;
|
|
577
|
+
setViewportHeight(visibleHeightBelow(top, vv ? { height: vv.height, offsetTop: vv.offsetTop } : null, window.innerHeight));
|
|
578
|
+
};
|
|
579
|
+
measure();
|
|
580
|
+
vv?.addEventListener("resize", measure);
|
|
581
|
+
vv?.addEventListener("scroll", measure);
|
|
582
|
+
window.addEventListener("resize", measure);
|
|
583
|
+
window.addEventListener("orientationchange", measure);
|
|
584
|
+
return () => {
|
|
585
|
+
vv?.removeEventListener("resize", measure);
|
|
586
|
+
vv?.removeEventListener("scroll", measure);
|
|
587
|
+
window.removeEventListener("resize", measure);
|
|
588
|
+
window.removeEventListener("orientationchange", measure);
|
|
589
|
+
};
|
|
590
|
+
}, [fit]);
|
|
591
|
+
useEffect(() => {
|
|
592
|
+
const host = hostRef.current;
|
|
593
|
+
const term = termRef.current;
|
|
594
|
+
if (!touchOn || !host || !term) return void 0;
|
|
595
|
+
let recognizer = createGestureRecognizer({ lineHeight: fontSizeRef.current * lineHeight });
|
|
596
|
+
let holdTimer = null;
|
|
597
|
+
let flingFrame = null;
|
|
598
|
+
let flingCarry = 0;
|
|
599
|
+
let anchor = null;
|
|
600
|
+
const screen = () => term.element?.querySelector(".xterm-screen") ?? host;
|
|
601
|
+
const cellFor = (x, y) => {
|
|
602
|
+
const r = screen().getBoundingClientRect();
|
|
603
|
+
return cellAt({ x, y }, { left: r.left, top: r.top, width: r.width, height: r.height }, { cols: term.cols, rows: term.rows, viewportY: term.buffer.active.viewportY });
|
|
604
|
+
};
|
|
605
|
+
const scrollLines = (lines) => {
|
|
606
|
+
if (lines === 0) return;
|
|
607
|
+
if (term.buffer.active.type === "alternate") {
|
|
608
|
+
const key = lines > 0 ? "down" : "up";
|
|
609
|
+
const seq = keySequence(key, { ctrl: false, alt: false }, { applicationCursor: term.modes.applicationCursorKeysMode });
|
|
610
|
+
if (seq) callbacksRef.current.onData?.(seq.repeat(Math.min(Math.abs(lines), 50)));
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
term.scrollLines(lines);
|
|
614
|
+
};
|
|
615
|
+
const stopFling = () => {
|
|
616
|
+
if (flingFrame !== null) cancelAnimationFrame(flingFrame);
|
|
617
|
+
flingFrame = null;
|
|
618
|
+
flingCarry = 0;
|
|
619
|
+
};
|
|
620
|
+
const startFling = (velocity) => {
|
|
621
|
+
stopFling();
|
|
622
|
+
let v = velocity;
|
|
623
|
+
let last = performance.now();
|
|
624
|
+
const step = (now) => {
|
|
625
|
+
const res = flingStep(v, now - last);
|
|
626
|
+
last = now;
|
|
627
|
+
v = res.velocity;
|
|
628
|
+
flingCarry += res.lines;
|
|
629
|
+
const whole = flingCarry > 0 ? Math.floor(flingCarry) : Math.ceil(flingCarry);
|
|
630
|
+
if (whole !== 0) {
|
|
631
|
+
flingCarry -= whole;
|
|
632
|
+
scrollLines(whole);
|
|
633
|
+
}
|
|
634
|
+
flingFrame = v === 0 ? null : requestAnimationFrame(step);
|
|
635
|
+
};
|
|
636
|
+
flingFrame = requestAnimationFrame(step);
|
|
637
|
+
};
|
|
638
|
+
const act = (actions) => {
|
|
639
|
+
for (const a of actions) {
|
|
640
|
+
switch (a.kind) {
|
|
641
|
+
case "tap":
|
|
642
|
+
term.clearSelection();
|
|
643
|
+
term.focus();
|
|
644
|
+
break;
|
|
645
|
+
case "scroll":
|
|
646
|
+
scrollLines(a.lines);
|
|
647
|
+
break;
|
|
648
|
+
case "fling":
|
|
649
|
+
startFling(a.velocity);
|
|
650
|
+
break;
|
|
651
|
+
case "select-start": {
|
|
652
|
+
const cell = cellFor(a.x, a.y);
|
|
653
|
+
const line = term.buffer.active.getLine(cell.row)?.translateToString(false) ?? "";
|
|
654
|
+
const word = wordBounds(line, cell.col);
|
|
655
|
+
anchor = { col: word.start, row: cell.row };
|
|
656
|
+
term.select(word.start, cell.row, Math.max(1, word.end - word.start));
|
|
657
|
+
navigator.vibrate?.(10);
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
case "select-extend": {
|
|
661
|
+
if (!anchor) break;
|
|
662
|
+
const span = spanBetween(anchor, cellFor(a.x, a.y), term.cols);
|
|
663
|
+
term.select(span.col, span.row, span.length);
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
case "select-end":
|
|
667
|
+
anchor = null;
|
|
668
|
+
setCopyVisible(term.hasSelection());
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
const clearHold = () => {
|
|
674
|
+
if (holdTimer !== null) clearTimeout(holdTimer);
|
|
675
|
+
holdTimer = null;
|
|
676
|
+
};
|
|
677
|
+
let pinch = null;
|
|
678
|
+
const fingerDistance = (e) => {
|
|
679
|
+
const a = e.touches[0];
|
|
680
|
+
const b = e.touches[1];
|
|
681
|
+
return Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY);
|
|
682
|
+
};
|
|
683
|
+
const onStart = (e) => {
|
|
684
|
+
if (e.touches.length === 2 && callbacksRef.current.onFontSizeChange) {
|
|
685
|
+
e.preventDefault();
|
|
686
|
+
clearHold();
|
|
687
|
+
stopFling();
|
|
688
|
+
act(recognizer.cancel());
|
|
689
|
+
pinch = { startDistance: fingerDistance(e), startSize: fontSizeRef.current };
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (e.touches.length !== 1) {
|
|
693
|
+
clearHold();
|
|
694
|
+
act(recognizer.cancel());
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const t = e.touches[0];
|
|
698
|
+
e.preventDefault();
|
|
699
|
+
stopFling();
|
|
700
|
+
recognizer = createGestureRecognizer({ lineHeight: fontSizeRef.current * lineHeight });
|
|
701
|
+
act(recognizer.down({ x: t.clientX, y: t.clientY, t: e.timeStamp }));
|
|
702
|
+
clearHold();
|
|
703
|
+
holdTimer = setTimeout(() => act(recognizer.longPress()), LONG_PRESS_MS);
|
|
704
|
+
};
|
|
705
|
+
const onMove = (e) => {
|
|
706
|
+
if (pinch && e.touches.length === 2) {
|
|
707
|
+
e.preventDefault();
|
|
708
|
+
const size = pinchFontSize(pinch.startSize, pinch.startDistance, fingerDistance(e));
|
|
709
|
+
if (size !== fontSizeRef.current) {
|
|
710
|
+
fontSizeRef.current = size;
|
|
711
|
+
callbacksRef.current.onFontSizeChange?.(size);
|
|
712
|
+
}
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (e.touches.length !== 1 || recognizer.phase === "idle") return;
|
|
716
|
+
const t = e.touches[0];
|
|
717
|
+
e.preventDefault();
|
|
718
|
+
act(recognizer.move({ x: t.clientX, y: t.clientY, t: e.timeStamp }));
|
|
719
|
+
if (recognizer.phase !== "pressed") clearHold();
|
|
720
|
+
};
|
|
721
|
+
const onEnd = (e) => {
|
|
722
|
+
if (pinch) {
|
|
723
|
+
if (e.touches.length < 2) pinch = null;
|
|
724
|
+
e.preventDefault();
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
clearHold();
|
|
728
|
+
if (recognizer.phase === "idle") return;
|
|
729
|
+
const t = e.changedTouches[0];
|
|
730
|
+
e.preventDefault();
|
|
731
|
+
act(recognizer.up({ x: t?.clientX ?? 0, y: t?.clientY ?? 0, t: e.timeStamp }));
|
|
732
|
+
};
|
|
733
|
+
const onCancel = () => {
|
|
734
|
+
clearHold();
|
|
735
|
+
act(recognizer.cancel());
|
|
736
|
+
};
|
|
737
|
+
host.addEventListener("touchstart", onStart, { passive: false });
|
|
738
|
+
host.addEventListener("touchmove", onMove, { passive: false });
|
|
739
|
+
host.addEventListener("touchend", onEnd, { passive: false });
|
|
740
|
+
host.addEventListener("touchcancel", onCancel);
|
|
741
|
+
return () => {
|
|
742
|
+
clearHold();
|
|
743
|
+
stopFling();
|
|
744
|
+
host.removeEventListener("touchstart", onStart);
|
|
745
|
+
host.removeEventListener("touchmove", onMove);
|
|
746
|
+
host.removeEventListener("touchend", onEnd);
|
|
747
|
+
host.removeEventListener("touchcancel", onCancel);
|
|
748
|
+
};
|
|
749
|
+
}, [touchOn, booted, lineHeight]);
|
|
750
|
+
function onAccessoryKey(id) {
|
|
751
|
+
const term = termRef.current;
|
|
752
|
+
if (!term) return;
|
|
753
|
+
const def = ACCESSORY_KEYS.find((k) => k.id === id);
|
|
754
|
+
if (!def) return;
|
|
755
|
+
if (def.kind === "modifier") {
|
|
756
|
+
const now = performance.now();
|
|
757
|
+
const current2 = latchRef.current;
|
|
758
|
+
setLatchState(id === "ctrl" ? { ...current2, ctrl: tapModifier(current2.ctrl, now) } : { ...current2, alt: tapModifier(current2.alt, now) });
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (id === "hide") {
|
|
762
|
+
term.blur();
|
|
763
|
+
term.textarea?.blur();
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
if (id === "paste") {
|
|
767
|
+
if (!navigator.clipboard?.readText) return;
|
|
768
|
+
navigator.clipboard.readText().then(
|
|
769
|
+
(text) => {
|
|
770
|
+
if (text) term.paste(text);
|
|
771
|
+
},
|
|
772
|
+
() => void 0
|
|
773
|
+
// permission refused: the system paste menu still works on the input
|
|
774
|
+
);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
const current = latchRef.current;
|
|
778
|
+
const mods = { ctrl: isActive(current.ctrl), alt: isActive(current.alt) };
|
|
779
|
+
const seq = keySequence(id, mods, { applicationCursor: term.modes.applicationCursorKeysMode });
|
|
780
|
+
if (seq === null) return;
|
|
781
|
+
if (mods.ctrl || mods.alt) setLatchState({ ctrl: afterKey(current.ctrl), alt: afterKey(current.alt) });
|
|
782
|
+
callbacksRef.current.onData?.(seq);
|
|
783
|
+
}
|
|
784
|
+
function copySelection() {
|
|
785
|
+
const term = termRef.current;
|
|
786
|
+
if (!term) return;
|
|
787
|
+
const text = term.getSelection();
|
|
788
|
+
if (text && navigator.clipboard?.writeText) void navigator.clipboard.writeText(text);
|
|
789
|
+
term.clearSelection();
|
|
790
|
+
setCopyVisible(false);
|
|
791
|
+
}
|
|
792
|
+
const rootStyle = { ...props.style };
|
|
793
|
+
if (viewportHeight !== null) rootStyle.height = viewportHeight;
|
|
794
|
+
return /* @__PURE__ */ jsxs2(
|
|
795
|
+
"div",
|
|
796
|
+
{
|
|
797
|
+
ref: rootRef,
|
|
798
|
+
className: ["mxt-root", props.className].filter(Boolean).join(" "),
|
|
799
|
+
style: rootStyle,
|
|
800
|
+
"data-touch": touchOn ? "on" : "off",
|
|
801
|
+
"data-theme": dark ? "dark" : "light",
|
|
802
|
+
children: [
|
|
803
|
+
/* @__PURE__ */ jsxs2("div", { className: "mxt-frame", style: { background: terminalTheme(dark).background }, children: [
|
|
804
|
+
/* @__PURE__ */ jsx2("div", { ref: hostRef, className: "mxt-host", style: { paddingLeft: padding, paddingRight: padding } }),
|
|
805
|
+
copyVisible ? /* @__PURE__ */ jsx2("button", { type: "button", className: "mxt-copy", onPointerDown: (e) => e.preventDefault(), onClick: copySelection, children: "Copy" }) : null,
|
|
806
|
+
props.children
|
|
807
|
+
] }),
|
|
808
|
+
barOn ? /* @__PURE__ */ jsx2(AccessoryBar, { ctrl: latches.ctrl.state, alt: latches.alt.state, onKey: onAccessoryKey }) : null
|
|
809
|
+
]
|
|
810
|
+
}
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
export {
|
|
814
|
+
AccessoryBar,
|
|
815
|
+
Terminal
|
|
816
|
+
};
|
|
817
|
+
//# sourceMappingURL=react.js.map
|