@particle-academy/fancy-term 0.2.2 → 0.4.0
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/README.md +102 -1
- package/dist/index.cjs +474 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +283 -3
- package/dist/index.d.ts +283 -3
- package/dist/index.js +462 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { forwardRef, useRef, useState, useCallback, useImperativeHandle, useEffect, useId } from 'react';
|
|
1
|
+
import { forwardRef, useRef, useState, useCallback, useImperativeHandle, useEffect, useLayoutEffect, useId } from 'react';
|
|
2
2
|
import { Terminal as Terminal$1 } from '@xterm/xterm';
|
|
3
3
|
import { FitAddon } from '@xterm/addon-fit';
|
|
4
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
6
|
// src/components/Terminal.tsx
|
|
7
7
|
|
|
@@ -50,8 +50,181 @@ function useTerminalFit(ref, fit, enabled = true) {
|
|
|
50
50
|
}, [ref, enabled]);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// src/clipboard.ts
|
|
54
|
+
function isImageFile(f) {
|
|
55
|
+
return typeof f.type === "string" && f.type.startsWith("image/");
|
|
56
|
+
}
|
|
57
|
+
function readDataTransfer(dt) {
|
|
58
|
+
const files = [];
|
|
59
|
+
let text = "";
|
|
60
|
+
if (dt) {
|
|
61
|
+
try {
|
|
62
|
+
text = dt.getData("text/plain") ?? "";
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
const items = dt.items;
|
|
66
|
+
if (items) {
|
|
67
|
+
for (let i = 0; i < items.length; i++) {
|
|
68
|
+
const it = items[i];
|
|
69
|
+
if (it && it.kind === "file") {
|
|
70
|
+
const f = it.getAsFile();
|
|
71
|
+
if (f) files.push(f);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (files.length === 0 && dt.files) {
|
|
76
|
+
for (let i = 0; i < dt.files.length; i++) files.push(dt.files[i]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { text, files, images: files.filter(isImageFile) };
|
|
80
|
+
}
|
|
81
|
+
async function writeClipboardText(text) {
|
|
82
|
+
try {
|
|
83
|
+
const nav = typeof navigator !== "undefined" ? navigator : void 0;
|
|
84
|
+
if (nav?.clipboard?.writeText) {
|
|
85
|
+
await nav.clipboard.writeText(text);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
if (typeof document === "undefined") return false;
|
|
92
|
+
const ta = document.createElement("textarea");
|
|
93
|
+
ta.value = text;
|
|
94
|
+
ta.style.position = "fixed";
|
|
95
|
+
ta.style.left = "-9999px";
|
|
96
|
+
ta.style.opacity = "0";
|
|
97
|
+
document.body.appendChild(ta);
|
|
98
|
+
ta.select();
|
|
99
|
+
const ok = document.execCommand("copy");
|
|
100
|
+
document.body.removeChild(ta);
|
|
101
|
+
return ok;
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function readClipboardText() {
|
|
107
|
+
try {
|
|
108
|
+
const nav = typeof navigator !== "undefined" ? navigator : void 0;
|
|
109
|
+
if (nav?.clipboard?.readText) return await nav.clipboard.readText();
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
return "";
|
|
113
|
+
}
|
|
114
|
+
var navigatorClipboard = {
|
|
115
|
+
writeText: (text) => writeClipboardText(text),
|
|
116
|
+
readText: () => readClipboardText()
|
|
117
|
+
};
|
|
118
|
+
function resolveClipboard(clipboard) {
|
|
119
|
+
if (clipboard === false) return { enabled: false, provider: navigatorClipboard };
|
|
120
|
+
if (clipboard && typeof clipboard === "object") return { enabled: true, provider: clipboard };
|
|
121
|
+
return { enabled: true, provider: navigatorClipboard };
|
|
122
|
+
}
|
|
123
|
+
async function providerWrite(provider, text) {
|
|
124
|
+
try {
|
|
125
|
+
await provider.writeText(text);
|
|
126
|
+
return true;
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function providerRead(provider) {
|
|
132
|
+
try {
|
|
133
|
+
return await provider.readText() ?? "";
|
|
134
|
+
} catch {
|
|
135
|
+
return "";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/copy-paste-mode.ts
|
|
140
|
+
function copyPasteBehavior(mode) {
|
|
141
|
+
switch (mode) {
|
|
142
|
+
case "linux":
|
|
143
|
+
return { selectToCopy: true, middleClickPaste: true, keyCopy: false, keyPaste: false };
|
|
144
|
+
case "winmac":
|
|
145
|
+
return { selectToCopy: false, middleClickPaste: false, keyCopy: true, keyPaste: true };
|
|
146
|
+
case "contextmenu":
|
|
147
|
+
default:
|
|
148
|
+
return { selectToCopy: false, middleClickPaste: false, keyCopy: false, keyPaste: false };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function resolveKeyAction(e, hasSelection, behavior) {
|
|
152
|
+
if (e.type !== "keydown") return null;
|
|
153
|
+
const key = e.key.toLowerCase();
|
|
154
|
+
if (key === "c") {
|
|
155
|
+
if (e.ctrlKey && e.shiftKey && !e.metaKey) return "copy";
|
|
156
|
+
if (e.metaKey && !e.ctrlKey && hasSelection) return "copy";
|
|
157
|
+
if (behavior.keyCopy && (e.ctrlKey || e.metaKey) && !e.shiftKey && hasSelection) return "copy";
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
if (key === "v" && behavior.keyPaste) {
|
|
161
|
+
if ((e.ctrlKey || e.metaKey) && !e.shiftKey) return "paste";
|
|
162
|
+
if (e.ctrlKey && e.shiftKey) return "paste";
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/osc52.ts
|
|
169
|
+
function encodeBase64(text) {
|
|
170
|
+
const bytes = new TextEncoder().encode(text);
|
|
171
|
+
let binary = "";
|
|
172
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
173
|
+
return typeof btoa === "function" ? btoa(binary) : binary;
|
|
174
|
+
}
|
|
175
|
+
function decodeBase64(b64) {
|
|
176
|
+
try {
|
|
177
|
+
const binary = typeof atob === "function" ? atob(b64) : b64;
|
|
178
|
+
const bytes = new Uint8Array(binary.length);
|
|
179
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
180
|
+
return new TextDecoder().decode(bytes);
|
|
181
|
+
} catch {
|
|
182
|
+
return "";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function parseOsc52(payload) {
|
|
186
|
+
const semi = payload.indexOf(";");
|
|
187
|
+
if (semi < 0) return null;
|
|
188
|
+
const selection = payload.slice(0, semi);
|
|
189
|
+
const pd = payload.slice(semi + 1);
|
|
190
|
+
if (pd === "?") return { selection, kind: "read", data: "" };
|
|
191
|
+
return { selection, kind: "write", data: decodeBase64(pd) };
|
|
192
|
+
}
|
|
193
|
+
function osc52Response(selection, text) {
|
|
194
|
+
return `\x1B]52;${selection};${encodeBase64(text)}\x07`;
|
|
195
|
+
}
|
|
196
|
+
function registerOsc52(term, provider, mode) {
|
|
197
|
+
if (mode() === false) return null;
|
|
198
|
+
return term.parser.registerOscHandler(52, (payload) => {
|
|
199
|
+
const m = mode();
|
|
200
|
+
if (m === false) return true;
|
|
201
|
+
const req = parseOsc52(payload);
|
|
202
|
+
if (!req) return false;
|
|
203
|
+
if (req.kind === "write") {
|
|
204
|
+
if (m === "copy" || m === "both") {
|
|
205
|
+
void Promise.resolve(provider().writeText(req.data)).catch(() => {
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
if (m === "read" || m === "both") {
|
|
211
|
+
void providerRead(provider()).then((text) => {
|
|
212
|
+
term.write(osc52Response(req.selection, text));
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
53
219
|
// src/hooks/use-terminal.ts
|
|
54
220
|
var DEFAULT_FONT = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace';
|
|
221
|
+
function makeDeferred() {
|
|
222
|
+
let resolve;
|
|
223
|
+
const promise = new Promise((r) => {
|
|
224
|
+
resolve = r;
|
|
225
|
+
});
|
|
226
|
+
return { promise, resolve };
|
|
227
|
+
}
|
|
55
228
|
function readBuffer(term) {
|
|
56
229
|
if (!term) return "";
|
|
57
230
|
const buf = term.buffer.active;
|
|
@@ -67,12 +240,16 @@ function useTerminal(containerRef, options = {}) {
|
|
|
67
240
|
const fitRef = useRef(null);
|
|
68
241
|
const optsRef = useRef(options);
|
|
69
242
|
optsRef.current = options;
|
|
243
|
+
const readyRef = useRef(makeDeferred());
|
|
70
244
|
const handleRef = useRef(null);
|
|
71
245
|
if (handleRef.current === null) {
|
|
72
246
|
handleRef.current = {
|
|
73
247
|
get xterm() {
|
|
74
248
|
return xtermRef.current;
|
|
75
249
|
},
|
|
250
|
+
get ready() {
|
|
251
|
+
return readyRef.current.promise;
|
|
252
|
+
},
|
|
76
253
|
write: (d) => xtermRef.current?.write(d),
|
|
77
254
|
writeln: (d) => xtermRef.current?.writeln(d),
|
|
78
255
|
clear: () => xtermRef.current?.clear(),
|
|
@@ -92,6 +269,17 @@ function useTerminal(containerRef, options = {}) {
|
|
|
92
269
|
focus: () => xtermRef.current?.focus(),
|
|
93
270
|
getBuffer: () => readBuffer(xtermRef.current),
|
|
94
271
|
getSelection: () => xtermRef.current?.getSelection() ?? "",
|
|
272
|
+
copySelection: async () => {
|
|
273
|
+
const sel = xtermRef.current?.getSelection() ?? "";
|
|
274
|
+
if (!sel) return false;
|
|
275
|
+
return providerWrite(resolveClipboard(optsRef.current.clipboard).provider, sel);
|
|
276
|
+
},
|
|
277
|
+
paste: async (text) => {
|
|
278
|
+
const data = text ?? await providerRead(resolveClipboard(optsRef.current.clipboard).provider);
|
|
279
|
+
if (data) xtermRef.current?.paste(data);
|
|
280
|
+
},
|
|
281
|
+
selectAll: () => xtermRef.current?.selectAll(),
|
|
282
|
+
clearSelection: () => xtermRef.current?.clearSelection(),
|
|
95
283
|
// Shell selection is owned by the <Terminal> component layer (it needs the
|
|
96
284
|
// `shells` list + onShellChange). The headless engine is shell-agnostic, so
|
|
97
285
|
// these are no-ops here and get overridden when <Terminal> composes them.
|
|
@@ -129,14 +317,77 @@ function useTerminal(containerRef, options = {}) {
|
|
|
129
317
|
const resizeSub = term.onResize(
|
|
130
318
|
({ cols, rows }) => optsRef.current.onResize?.({ cols, rows })
|
|
131
319
|
);
|
|
320
|
+
const pasteFromClipboard = async () => {
|
|
321
|
+
const oo = optsRef.current;
|
|
322
|
+
if (oo.readOnly) return;
|
|
323
|
+
const text = await providerRead(resolveClipboard(oo.clipboard).provider);
|
|
324
|
+
if (text) xtermRef.current?.paste(text);
|
|
325
|
+
};
|
|
326
|
+
term.attachCustomKeyEventHandler((e) => {
|
|
327
|
+
const oo = optsRef.current;
|
|
328
|
+
const clip = resolveClipboard(oo.clipboard);
|
|
329
|
+
if (!clip.enabled) return true;
|
|
330
|
+
const action = resolveKeyAction(e, term.hasSelection(), copyPasteBehavior(oo.copyPaste));
|
|
331
|
+
if (action === "copy") {
|
|
332
|
+
void providerWrite(clip.provider, term.getSelection());
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
if (action === "paste") {
|
|
336
|
+
void pasteFromClipboard();
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
return true;
|
|
340
|
+
});
|
|
341
|
+
const oscDisposable = registerOsc52(
|
|
342
|
+
term,
|
|
343
|
+
() => resolveClipboard(optsRef.current.clipboard).provider,
|
|
344
|
+
() => {
|
|
345
|
+
const oo = optsRef.current;
|
|
346
|
+
return resolveClipboard(oo.clipboard).enabled ? oo.osc52 ?? "copy" : false;
|
|
347
|
+
}
|
|
348
|
+
);
|
|
349
|
+
const selectionSub = term.onSelectionChange(() => {
|
|
350
|
+
const oo = optsRef.current;
|
|
351
|
+
const clip = resolveClipboard(oo.clipboard);
|
|
352
|
+
if (clip.enabled && copyPasteBehavior(oo.copyPaste).selectToCopy && term.hasSelection()) {
|
|
353
|
+
void providerWrite(clip.provider, term.getSelection());
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
const onMouseDown = (ev) => {
|
|
357
|
+
const oo = optsRef.current;
|
|
358
|
+
if (ev.button !== 1) return;
|
|
359
|
+
if (!resolveClipboard(oo.clipboard).enabled) return;
|
|
360
|
+
if (!copyPasteBehavior(oo.copyPaste).middleClickPaste) return;
|
|
361
|
+
ev.preventDefault();
|
|
362
|
+
void pasteFromClipboard();
|
|
363
|
+
};
|
|
364
|
+
el.addEventListener("mousedown", onMouseDown);
|
|
365
|
+
const onPasteEvent = (e) => {
|
|
366
|
+
const o2 = optsRef.current;
|
|
367
|
+
if (resolveClipboard(o2.clipboard).enabled === false) return;
|
|
368
|
+
if (o2.readOnly) {
|
|
369
|
+
e.preventDefault();
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const payload = readDataTransfer(e.clipboardData);
|
|
373
|
+
if (o2.onPaste?.(payload) === false) e.preventDefault();
|
|
374
|
+
};
|
|
375
|
+
el.addEventListener("paste", onPasteEvent, true);
|
|
132
376
|
if (o.initialOutput) term.write(o.initialOutput);
|
|
133
377
|
if (o.fit ?? true) handle.fit();
|
|
378
|
+
optsRef.current.onReady?.(term);
|
|
379
|
+
readyRef.current.resolve(term);
|
|
134
380
|
return () => {
|
|
381
|
+
el.removeEventListener("paste", onPasteEvent, true);
|
|
382
|
+
el.removeEventListener("mousedown", onMouseDown);
|
|
383
|
+
oscDisposable?.dispose();
|
|
384
|
+
selectionSub.dispose();
|
|
135
385
|
dataSub.dispose();
|
|
136
386
|
resizeSub.dispose();
|
|
137
387
|
term.dispose();
|
|
138
388
|
xtermRef.current = null;
|
|
139
389
|
fitRef.current = null;
|
|
390
|
+
readyRef.current = makeDeferred();
|
|
140
391
|
};
|
|
141
392
|
}, [containerRef]);
|
|
142
393
|
useEffect(() => {
|
|
@@ -362,6 +613,122 @@ function ShellSwitcher({
|
|
|
362
613
|
);
|
|
363
614
|
}
|
|
364
615
|
|
|
616
|
+
// src/menu-position.ts
|
|
617
|
+
function clampMenuPosition(at, menu, viewport, margin = 8) {
|
|
618
|
+
const maxX = Math.max(margin, viewport.width - menu.width - margin);
|
|
619
|
+
const maxY = Math.max(margin, viewport.height - menu.height - margin);
|
|
620
|
+
return {
|
|
621
|
+
x: Math.min(Math.max(margin, at.x), maxX),
|
|
622
|
+
y: Math.min(Math.max(margin, at.y), maxY)
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function TerminalContextMenu({
|
|
626
|
+
at,
|
|
627
|
+
items,
|
|
628
|
+
ctx,
|
|
629
|
+
onClose
|
|
630
|
+
}) {
|
|
631
|
+
const ref = useRef(null);
|
|
632
|
+
const [pos, setPos] = useState(at);
|
|
633
|
+
useLayoutEffect(() => {
|
|
634
|
+
const el = ref.current;
|
|
635
|
+
if (!el || typeof window === "undefined") return;
|
|
636
|
+
const r = el.getBoundingClientRect();
|
|
637
|
+
setPos(
|
|
638
|
+
clampMenuPosition(
|
|
639
|
+
at,
|
|
640
|
+
{ width: r.width, height: r.height },
|
|
641
|
+
{ width: window.innerWidth, height: window.innerHeight }
|
|
642
|
+
)
|
|
643
|
+
);
|
|
644
|
+
}, [at]);
|
|
645
|
+
useEffect(() => {
|
|
646
|
+
const onDown = (e) => {
|
|
647
|
+
if (ref.current && !ref.current.contains(e.target)) onClose();
|
|
648
|
+
};
|
|
649
|
+
const onKey = (e) => {
|
|
650
|
+
if (e.key === "Escape") onClose();
|
|
651
|
+
};
|
|
652
|
+
document.addEventListener("mousedown", onDown, true);
|
|
653
|
+
document.addEventListener("keydown", onKey, true);
|
|
654
|
+
return () => {
|
|
655
|
+
document.removeEventListener("mousedown", onDown, true);
|
|
656
|
+
document.removeEventListener("keydown", onKey, true);
|
|
657
|
+
};
|
|
658
|
+
}, [onClose]);
|
|
659
|
+
return /* @__PURE__ */ jsx(
|
|
660
|
+
"div",
|
|
661
|
+
{
|
|
662
|
+
ref,
|
|
663
|
+
"data-fancy-terminal-menu": "",
|
|
664
|
+
role: "menu",
|
|
665
|
+
style: {
|
|
666
|
+
position: "fixed",
|
|
667
|
+
left: pos.x,
|
|
668
|
+
top: pos.y,
|
|
669
|
+
zIndex: 2147483e3,
|
|
670
|
+
minWidth: 176,
|
|
671
|
+
padding: 4,
|
|
672
|
+
background: "#18181b",
|
|
673
|
+
border: "1px solid #3f3f46",
|
|
674
|
+
borderRadius: 8,
|
|
675
|
+
boxShadow: "0 10px 32px -8px rgba(0,0,0,.6)",
|
|
676
|
+
color: "#e4e4e7",
|
|
677
|
+
font: '13px/1.4 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
678
|
+
userSelect: "none"
|
|
679
|
+
},
|
|
680
|
+
children: items.map(
|
|
681
|
+
(it) => it.separator ? /* @__PURE__ */ jsx(
|
|
682
|
+
"div",
|
|
683
|
+
{
|
|
684
|
+
role: "separator",
|
|
685
|
+
style: { height: 1, margin: "4px 6px", background: "#3f3f46" }
|
|
686
|
+
},
|
|
687
|
+
it.id
|
|
688
|
+
) : /* @__PURE__ */ jsxs(
|
|
689
|
+
"button",
|
|
690
|
+
{
|
|
691
|
+
type: "button",
|
|
692
|
+
role: "menuitem",
|
|
693
|
+
"data-menu-item": it.id,
|
|
694
|
+
disabled: it.disabled,
|
|
695
|
+
onClick: () => {
|
|
696
|
+
if (it.disabled) return;
|
|
697
|
+
it.onSelect?.(ctx);
|
|
698
|
+
onClose();
|
|
699
|
+
},
|
|
700
|
+
onMouseEnter: (e) => {
|
|
701
|
+
if (!it.disabled) e.currentTarget.style.background = "#27272a";
|
|
702
|
+
},
|
|
703
|
+
onMouseLeave: (e) => {
|
|
704
|
+
e.currentTarget.style.background = "transparent";
|
|
705
|
+
},
|
|
706
|
+
style: {
|
|
707
|
+
display: "flex",
|
|
708
|
+
alignItems: "center",
|
|
709
|
+
gap: 9,
|
|
710
|
+
width: "100%",
|
|
711
|
+
textAlign: "left",
|
|
712
|
+
padding: "6px 9px",
|
|
713
|
+
border: "none",
|
|
714
|
+
borderRadius: 5,
|
|
715
|
+
background: "transparent",
|
|
716
|
+
color: it.disabled ? "#52525b" : "inherit",
|
|
717
|
+
cursor: it.disabled ? "default" : "pointer",
|
|
718
|
+
font: "inherit"
|
|
719
|
+
},
|
|
720
|
+
children: [
|
|
721
|
+
/* @__PURE__ */ jsx("span", { style: { width: 16, textAlign: "center", opacity: 0.8 }, children: it.icon ?? "" }),
|
|
722
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: it.label })
|
|
723
|
+
]
|
|
724
|
+
},
|
|
725
|
+
it.id
|
|
726
|
+
)
|
|
727
|
+
)
|
|
728
|
+
}
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
365
732
|
// src/output-diff.ts
|
|
366
733
|
function diffOutput(written, next) {
|
|
367
734
|
if (next === written) return null;
|
|
@@ -378,6 +745,28 @@ function decideShellSelect(shells, current, id) {
|
|
|
378
745
|
function decideShellSwitch(current, next) {
|
|
379
746
|
return { changed: current !== next };
|
|
380
747
|
}
|
|
748
|
+
|
|
749
|
+
// src/context-menu.ts
|
|
750
|
+
function defaultMenuItems(ctx, actions) {
|
|
751
|
+
const items = [
|
|
752
|
+
{ id: "copy", label: "Copy", icon: "\u29C9", disabled: !ctx.hasSelection, onSelect: actions.copy }
|
|
753
|
+
];
|
|
754
|
+
if (!ctx.readOnly) {
|
|
755
|
+
items.push({ id: "paste", label: "Paste", icon: "\u2398", onSelect: actions.paste });
|
|
756
|
+
}
|
|
757
|
+
items.push({ id: "select-all", label: "Select all", onSelect: actions.selectAll });
|
|
758
|
+
if (!ctx.readOnly) {
|
|
759
|
+
items.push({ id: "sep-default", separator: true });
|
|
760
|
+
items.push({ id: "clear", label: "Clear", onSelect: actions.clear });
|
|
761
|
+
}
|
|
762
|
+
return items;
|
|
763
|
+
}
|
|
764
|
+
function resolveMenuItems(config, ctx, actions) {
|
|
765
|
+
if (config === false) return null;
|
|
766
|
+
const defaults = defaultMenuItems(ctx, actions);
|
|
767
|
+
const items = config === void 0 || config === true ? defaults : Array.isArray(config) ? config : config(ctx, defaults);
|
|
768
|
+
return items.length > 0 ? items : null;
|
|
769
|
+
}
|
|
381
770
|
var Terminal = forwardRef(function Terminal2({
|
|
382
771
|
output,
|
|
383
772
|
theme,
|
|
@@ -397,6 +786,12 @@ var Terminal = forwardRef(function Terminal2({
|
|
|
397
786
|
activeShell,
|
|
398
787
|
onShellChange,
|
|
399
788
|
showShellBar = false,
|
|
789
|
+
clipboard,
|
|
790
|
+
osc52,
|
|
791
|
+
copyPaste,
|
|
792
|
+
onReady,
|
|
793
|
+
onPaste,
|
|
794
|
+
contextMenu,
|
|
400
795
|
className,
|
|
401
796
|
style,
|
|
402
797
|
...rest
|
|
@@ -429,12 +824,34 @@ var Terminal = forwardRef(function Terminal2({
|
|
|
429
824
|
// diffing effect below owns the buffer, so don't double-write.
|
|
430
825
|
initialOutput: output === void 0 ? initialOutput : void 0,
|
|
431
826
|
onData,
|
|
432
|
-
onResize
|
|
827
|
+
onResize,
|
|
828
|
+
clipboard,
|
|
829
|
+
osc52,
|
|
830
|
+
copyPaste,
|
|
831
|
+
onReady,
|
|
832
|
+
onPaste
|
|
433
833
|
});
|
|
434
834
|
useImperativeHandle(
|
|
435
835
|
ref,
|
|
436
836
|
() => ({
|
|
437
|
-
|
|
837
|
+
get xterm() {
|
|
838
|
+
return handle.xterm;
|
|
839
|
+
},
|
|
840
|
+
get ready() {
|
|
841
|
+
return handle.ready;
|
|
842
|
+
},
|
|
843
|
+
write: handle.write,
|
|
844
|
+
writeln: handle.writeln,
|
|
845
|
+
clear: handle.clear,
|
|
846
|
+
reset: handle.reset,
|
|
847
|
+
fit: handle.fit,
|
|
848
|
+
focus: handle.focus,
|
|
849
|
+
getBuffer: handle.getBuffer,
|
|
850
|
+
getSelection: handle.getSelection,
|
|
851
|
+
copySelection: handle.copySelection,
|
|
852
|
+
paste: handle.paste,
|
|
853
|
+
selectAll: handle.selectAll,
|
|
854
|
+
clearSelection: handle.clearSelection,
|
|
438
855
|
setShell: selectShell,
|
|
439
856
|
getShell: () => shellStateRef.current.shellId
|
|
440
857
|
}),
|
|
@@ -449,18 +866,46 @@ var Terminal = forwardRef(function Terminal2({
|
|
|
449
866
|
handle.write(change.write);
|
|
450
867
|
written.current = output;
|
|
451
868
|
}, [output, handle]);
|
|
869
|
+
const [menu, setMenu] = useState(null);
|
|
870
|
+
const openMenu = useCallback(
|
|
871
|
+
(e) => {
|
|
872
|
+
if (contextMenu === false) return;
|
|
873
|
+
e.preventDefault();
|
|
874
|
+
const selection = handle.getSelection();
|
|
875
|
+
const ctx = {
|
|
876
|
+
hasSelection: selection.length > 0,
|
|
877
|
+
selection,
|
|
878
|
+
readOnly: !!readOnly
|
|
879
|
+
};
|
|
880
|
+
const items = resolveMenuItems(contextMenu, ctx, {
|
|
881
|
+
copy: () => void handle.copySelection(),
|
|
882
|
+
paste: () => void handle.paste(),
|
|
883
|
+
selectAll: () => handle.selectAll(),
|
|
884
|
+
clear: () => handle.clear()
|
|
885
|
+
});
|
|
886
|
+
if (items) setMenu({ at: { x: e.clientX, y: e.clientY }, items, ctx });
|
|
887
|
+
},
|
|
888
|
+
[contextMenu, handle, readOnly]
|
|
889
|
+
);
|
|
890
|
+
const menuNode = menu ? /* @__PURE__ */ jsx(TerminalContextMenu, { at: menu.at, items: menu.items, ctx: menu.ctx, onClose: () => setMenu(null) }) : null;
|
|
452
891
|
const surface = /* @__PURE__ */ jsx(
|
|
453
892
|
"div",
|
|
454
893
|
{
|
|
455
894
|
ref: containerRef,
|
|
456
895
|
"data-fancy-terminal": "",
|
|
896
|
+
onContextMenu: openMenu,
|
|
457
897
|
"data-readonly": readOnly ? "" : void 0,
|
|
458
898
|
"data-fancy-terminal-shell": shellId ?? void 0,
|
|
459
899
|
style: showShellBar && shells ? { width: "100%", flex: 1, minHeight: 0 } : { width: "100%", height: "100%", ...style },
|
|
460
900
|
...showShellBar && shells ? {} : rest
|
|
461
901
|
}
|
|
462
902
|
);
|
|
463
|
-
if (!showShellBar || !shells)
|
|
903
|
+
if (!showShellBar || !shells) {
|
|
904
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
905
|
+
surface,
|
|
906
|
+
menuNode
|
|
907
|
+
] });
|
|
908
|
+
}
|
|
464
909
|
return /* @__PURE__ */ jsxs(
|
|
465
910
|
"div",
|
|
466
911
|
{
|
|
@@ -491,7 +936,8 @@ var Terminal = forwardRef(function Terminal2({
|
|
|
491
936
|
)
|
|
492
937
|
}
|
|
493
938
|
),
|
|
494
|
-
surface
|
|
939
|
+
surface,
|
|
940
|
+
menuNode
|
|
495
941
|
]
|
|
496
942
|
}
|
|
497
943
|
);
|
|
@@ -540,6 +986,15 @@ function useTerminalSession(options) {
|
|
|
540
986
|
return { output, sendData, append, clear, shell, switchShell };
|
|
541
987
|
}
|
|
542
988
|
|
|
543
|
-
|
|
989
|
+
// src/copy-keybinding.ts
|
|
990
|
+
function shouldCopyEvent(e, hasSelection) {
|
|
991
|
+
if (e.type !== "keydown") return false;
|
|
992
|
+
if (e.key !== "c" && e.key !== "C") return false;
|
|
993
|
+
if (e.ctrlKey && e.shiftKey && !e.metaKey) return true;
|
|
994
|
+
if (e.metaKey && !e.ctrlKey && hasSelection) return true;
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
export { BUILTIN_SHELLS, ShellSwitcher, Terminal, TerminalContextMenu, clampMenuPosition, copyPasteBehavior, decodeBase64, defaultMenuItems, encodeBase64, fancyDarkTheme, isImageFile, navigatorClipboard, osc52Response, parseOsc52, registerOsc52, resolveClipboard, resolveKeyAction, resolveMenuItems, resolveShell, shouldCopyEvent, useTerminal, useTerminalFit, useTerminalSession };
|
|
544
999
|
//# sourceMappingURL=index.js.map
|
|
545
1000
|
//# sourceMappingURL=index.js.map
|