@mks2508/mks-ui 0.9.0 → 0.11.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/dist/react-ui/blocks/Terminal/ChromeShell/ChromeShell.styles.d.ts +55 -0
- package/dist/react-ui/blocks/Terminal/ChromeShell/ChromeShell.styles.d.ts.map +1 -0
- package/dist/react-ui/blocks/Terminal/ChromeShell/ChromeShell.styles.js +62 -0
- package/dist/react-ui/blocks/Terminal/ChromeShell/ChromeShell.types.d.ts +92 -0
- package/dist/react-ui/blocks/Terminal/ChromeShell/ChromeShell.types.d.ts.map +1 -0
- package/dist/react-ui/blocks/Terminal/ChromeShell/index.d.ts +71 -0
- package/dist/react-ui/blocks/Terminal/ChromeShell/index.d.ts.map +1 -0
- package/dist/react-ui/blocks/Terminal/ChromeShell/index.js +114 -0
- package/dist/react-ui/blocks/Terminal/chrome.d.ts +1 -0
- package/dist/react-ui/blocks/Terminal/chrome.d.ts.map +1 -1
- package/dist/react-ui/blocks/Terminal/chrome.js +3 -1
- package/dist/react-ui/blocks/Terminal/index.d.ts +1 -0
- package/dist/react-ui/blocks/Terminal/index.d.ts.map +1 -1
- package/dist/react-ui/blocks/Terminal/index.js +3 -1
- package/dist/react-ui/blocks/Terminal/panel/TerminalInteractivePanelWterm.d.ts.map +1 -1
- package/dist/react-ui/blocks/Terminal/panel/TerminalInteractivePanelWterm.js +20 -9
- package/dist/react-ui/blocks/Terminal/panel/TerminalInteractivePanelXterm.d.ts.map +1 -1
- package/dist/react-ui/blocks/Terminal/panel/TerminalInteractivePanelXterm.js +19 -6
- package/dist/react-ui/blocks/Terminal/panel/TerminalLogsPanel/index.d.ts.map +1 -1
- package/dist/react-ui/blocks/Terminal/panel/TerminalLogsPanel/index.js +31 -9
- package/dist/react-ui/blocks/Terminal/panel/TerminalPanelChrome.d.ts +31 -2
- package/dist/react-ui/blocks/Terminal/panel/TerminalPanelChrome.d.ts.map +1 -1
- package/dist/react-ui/blocks/Terminal/panel/TerminalPanelChrome.js +41 -5
- package/dist/react-ui/blocks/Terminal/panel/TerminalPanelChrome.types.d.ts +22 -0
- package/dist/react-ui/blocks/Terminal/panel/TerminalPanelChrome.types.d.ts.map +1 -1
- package/dist/react-ui/blocks/Terminal/panel/TerminalSettingsPopover/index.d.ts +8 -0
- package/dist/react-ui/blocks/Terminal/panel/TerminalSettingsPopover/index.d.ts.map +1 -1
- package/dist/react-ui/blocks/Terminal/panel/TerminalSettingsPopover/index.js +54 -12
- package/dist/react-ui/blocks/Terminal/renderers/ResttyRenderer.d.ts +52 -0
- package/dist/react-ui/blocks/Terminal/renderers/ResttyRenderer.d.ts.map +1 -0
- package/dist/react-ui/blocks/Terminal/renderers/ResttyRenderer.js +160 -0
- package/dist/react-ui/blocks/Terminal/renderers/WtermRenderer.d.ts +46 -0
- package/dist/react-ui/blocks/Terminal/renderers/WtermRenderer.d.ts.map +1 -0
- package/dist/react-ui/blocks/Terminal/renderers/WtermRenderer.js +195 -0
- package/dist/react-ui/blocks/Terminal/renderers/XTermRenderer.d.ts +39 -0
- package/dist/react-ui/blocks/Terminal/renderers/XTermRenderer.d.ts.map +1 -0
- package/dist/react-ui/blocks/Terminal/renderers/XTermRenderer.js +251 -0
- package/dist/react-ui/blocks/Terminal/renderers/index.d.ts +20 -0
- package/dist/react-ui/blocks/Terminal/renderers/index.d.ts.map +1 -0
- package/dist/react-ui/blocks/Terminal/renderers/types.d.ts +47 -0
- package/dist/react-ui/blocks/Terminal/renderers/types.d.ts.map +1 -0
- package/dist/react-ui/blocks/Terminal/restty.d.ts +2 -0
- package/dist/react-ui/blocks/Terminal/restty.d.ts.map +1 -1
- package/dist/react-ui/blocks/Terminal/restty.js +2 -1
- package/dist/react-ui/blocks/Terminal/wterm.d.ts +2 -0
- package/dist/react-ui/blocks/Terminal/wterm.d.ts.map +1 -1
- package/dist/react-ui/blocks/Terminal/wterm.js +2 -1
- package/dist/react-ui/blocks/Terminal/xterm.d.ts +2 -0
- package/dist/react-ui/blocks/Terminal/xterm.d.ts.map +1 -1
- package/dist/react-ui/blocks/Terminal/xterm.js +2 -1
- package/dist/react-ui/index.d.ts +2 -2
- package/dist/react-ui/index.d.ts.map +1 -1
- package/dist/react-ui/index.js +5 -3
- package/dist/react-ui/ui/MiddleTruncate/MiddleTruncate.styles.d.ts +30 -0
- package/dist/react-ui/ui/MiddleTruncate/MiddleTruncate.styles.d.ts.map +1 -0
- package/dist/react-ui/ui/MiddleTruncate/MiddleTruncate.styles.js +31 -0
- package/dist/react-ui/ui/MiddleTruncate/MiddleTruncate.types.d.ts +66 -0
- package/dist/react-ui/ui/MiddleTruncate/MiddleTruncate.types.d.ts.map +1 -0
- package/dist/react-ui/ui/MiddleTruncate/index.d.ts +79 -0
- package/dist/react-ui/ui/MiddleTruncate/index.d.ts.map +1 -0
- package/dist/react-ui/ui/MiddleTruncate/index.js +164 -0
- package/dist/react-ui/ui/MiddleTruncatePath/MiddleTruncatePath.styles.js +1 -1
- package/dist/react-ui/ui/MiddleTruncatePath/index.d.ts +7 -1
- package/dist/react-ui/ui/MiddleTruncatePath/index.d.ts.map +1 -1
- package/dist/react-ui/ui/MiddleTruncatePath/index.js +21 -17
- package/dist/react-ui/ui/index.d.ts +1 -0
- package/dist/react-ui/ui/index.d.ts.map +1 -1
- package/dist/react-ui/ui/index.js +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../../lib/utils.js";
|
|
4
|
+
import { useTerminalSettings } from "../hooks/useTerminalSettings.js";
|
|
5
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
|
|
8
|
+
//#region src/react-ui/blocks/Terminal/renderers/ResttyRenderer.tsx
|
|
9
|
+
/**
|
|
10
|
+
* ResttyRenderer Component.
|
|
11
|
+
*
|
|
12
|
+
* Chrome-less restty renderer for maximum composability.
|
|
13
|
+
* Wraps @mks2508/restty ResttyTerminal component without any chrome,
|
|
14
|
+
* toolbar, or status bar. Use this when you want to build your own
|
|
15
|
+
* terminal shell or embed in custom layouts.
|
|
16
|
+
*
|
|
17
|
+
* Lazy-loads @mks2508/restty on first render. Returns null if
|
|
18
|
+
* the peer dependency is not installed.
|
|
19
|
+
*
|
|
20
|
+
* @module components/devenv/terminal/renderers/restty
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Terminal container styles for restty renderer.
|
|
24
|
+
*/
|
|
25
|
+
const terminalContainerStyles = "relative bg-[hsl(var(--terminal-bg,var(--background)))] min-h-[400px] border-x border-border/10";
|
|
26
|
+
/** Lazy-loaded ResttyTerminal component. */
|
|
27
|
+
let ResttyTerminalComponent = null;
|
|
28
|
+
/** Whether we've attempted to load restty. */
|
|
29
|
+
let resttyLoadAttempted = false;
|
|
30
|
+
/**
|
|
31
|
+
* Lazy-load @mks2508/restty ResttyTerminal component.
|
|
32
|
+
*
|
|
33
|
+
* Returns null if @mks2508/restty is not installed.
|
|
34
|
+
*/
|
|
35
|
+
async function loadResttyTerminal() {
|
|
36
|
+
if (ResttyTerminalComponent !== null || resttyLoadAttempted) return ResttyTerminalComponent;
|
|
37
|
+
resttyLoadAttempted = true;
|
|
38
|
+
try {
|
|
39
|
+
ResttyTerminalComponent = (await import("@mks2508/restty")).ResttyTerminal;
|
|
40
|
+
return ResttyTerminalComponent;
|
|
41
|
+
} catch {
|
|
42
|
+
ResttyTerminalComponent = null;
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* ResttyRenderer Component.
|
|
48
|
+
*
|
|
49
|
+
* Chrome-less restty renderer. Provides:
|
|
50
|
+
* - @mks2508/restty ResttyTerminal with WebGPU/WebGL2 rendering
|
|
51
|
+
* - Auto-resize on container resize
|
|
52
|
+
* - Font size from useTerminalSettings context (override via props)
|
|
53
|
+
* - Ref forwarding with focus, write, clear, resize methods
|
|
54
|
+
* - Lazy-loading with graceful fallback if @mks2508/restty is missing
|
|
55
|
+
*
|
|
56
|
+
* restty features:
|
|
57
|
+
* - GPU-accelerated rendering (WebGPU/WebGL2)
|
|
58
|
+
* - libghostty-vt WASM core (full VT100/VT220/xterm parser)
|
|
59
|
+
* - Built-in theme catalog (Dracula, Synthwave, etc.)
|
|
60
|
+
* - Multi-pane splits
|
|
61
|
+
* - Scrollback history
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* const termRef = useRef<ITerminalRendererRef>(null);
|
|
66
|
+
*
|
|
67
|
+
* <ResttyRenderer
|
|
68
|
+
* ref={termRef}
|
|
69
|
+
* sessionId="my-session"
|
|
70
|
+
* fontSize={16}
|
|
71
|
+
* resttyThemeName="Dracula"
|
|
72
|
+
* onReady={() => console.log('Terminal ready')}
|
|
73
|
+
* className="h-[600px]"
|
|
74
|
+
* />
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
const ResttyRenderer = forwardRef(({ sessionId, fontSize: fontSizeProp, resttyThemeName: resttyThemeNameProp, gpuRenderer = "auto", onReady, className }, ref) => {
|
|
78
|
+
const containerRef = useRef(null);
|
|
79
|
+
const terminalReadyRef = useRef(false);
|
|
80
|
+
const { settings, setFontSize } = useTerminalSettings();
|
|
81
|
+
/**
|
|
82
|
+
* Font size reads from global terminal settings store, with prop
|
|
83
|
+
* override taking precedence. This matches the v0.11.0 pattern
|
|
84
|
+
* where all consumers sync to the single source of truth.
|
|
85
|
+
*/
|
|
86
|
+
const fontSize = fontSizeProp ?? settings.fontSize;
|
|
87
|
+
const resttyThemeName = resttyThemeNameProp ?? settings.resttyThemeName;
|
|
88
|
+
const [resttyAvailable, setResttyAvailable] = useState(null);
|
|
89
|
+
const [ResttyTerminal, setResttyTerminal] = useState(null);
|
|
90
|
+
/**
|
|
91
|
+
* Lazy-load restty on mount.
|
|
92
|
+
*/
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
loadResttyTerminal().then((term) => {
|
|
95
|
+
if (term) {
|
|
96
|
+
setResttyTerminal(() => term);
|
|
97
|
+
setResttyAvailable(true);
|
|
98
|
+
} else setResttyAvailable(false);
|
|
99
|
+
});
|
|
100
|
+
}, []);
|
|
101
|
+
/**
|
|
102
|
+
* Handle terminal ready callback.
|
|
103
|
+
*/
|
|
104
|
+
const handleResttyReady = useCallback(() => {
|
|
105
|
+
terminalReadyRef.current = true;
|
|
106
|
+
onReady?.();
|
|
107
|
+
}, [onReady]);
|
|
108
|
+
/**
|
|
109
|
+
* Font size update propagates to global settings if not overridden.
|
|
110
|
+
*/
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (!fontSizeProp) setFontSize(fontSize);
|
|
113
|
+
}, [
|
|
114
|
+
fontSize,
|
|
115
|
+
fontSizeProp,
|
|
116
|
+
setFontSize
|
|
117
|
+
]);
|
|
118
|
+
/**
|
|
119
|
+
* Expose imperative methods to parent.
|
|
120
|
+
*
|
|
121
|
+
* Note: write() is a no-op for restty since it manages its own PTY.
|
|
122
|
+
* The renderer focuses on display only.
|
|
123
|
+
*/
|
|
124
|
+
useImperativeHandle(ref, () => ({
|
|
125
|
+
focus: () => {},
|
|
126
|
+
write: (_data) => {},
|
|
127
|
+
clear: () => {},
|
|
128
|
+
resize: (_cols, _rows) => {}
|
|
129
|
+
}));
|
|
130
|
+
/**
|
|
131
|
+
* Render fallbacks for missing/unavailable restty.
|
|
132
|
+
*/
|
|
133
|
+
if (resttyAvailable === null) return /* @__PURE__ */ jsx("div", {
|
|
134
|
+
className: "flex items-center justify-center h-full text-muted-foreground text-sm min-h-[400px]",
|
|
135
|
+
children: "Loading restty..."
|
|
136
|
+
});
|
|
137
|
+
if (resttyAvailable === false) return /* @__PURE__ */ jsxs("div", {
|
|
138
|
+
className: "flex flex-col items-center justify-center h-full text-muted-foreground text-sm gap-2 min-h-[400px]",
|
|
139
|
+
children: [/* @__PURE__ */ jsx("span", { children: "restty is not installed" }), /* @__PURE__ */ jsx("code", {
|
|
140
|
+
className: "text-xs bg-muted px-2 py-1 rounded",
|
|
141
|
+
children: "bun add @mks2508/restty"
|
|
142
|
+
})]
|
|
143
|
+
});
|
|
144
|
+
return /* @__PURE__ */ jsx("div", {
|
|
145
|
+
ref: containerRef,
|
|
146
|
+
className: cn(terminalContainerStyles, className),
|
|
147
|
+
"data-session-id": sessionId,
|
|
148
|
+
children: ResttyTerminal && /* @__PURE__ */ jsx(ResttyTerminal, {
|
|
149
|
+
ref: void 0,
|
|
150
|
+
resttyThemeName,
|
|
151
|
+
gpuRenderer,
|
|
152
|
+
className: "w-full h-full min-h-[400px]",
|
|
153
|
+
onReady: handleResttyReady
|
|
154
|
+
})
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
ResttyRenderer.displayName = "ResttyRenderer";
|
|
158
|
+
|
|
159
|
+
//#endregion
|
|
160
|
+
export { ResttyRenderer };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WtermRenderer Component.
|
|
3
|
+
*
|
|
4
|
+
* Chrome-less wterm renderer for maximum composability.
|
|
5
|
+
* Wraps @wterm/react Terminal component without any chrome,
|
|
6
|
+
* toolbar, or status bar. Use this when you want to build
|
|
7
|
+
* your own terminal shell or embed in custom layouts.
|
|
8
|
+
*
|
|
9
|
+
* Lazy-loads @wterm/react on first render. Returns null if
|
|
10
|
+
* the peer dependency is not installed.
|
|
11
|
+
*
|
|
12
|
+
* @module components/devenv/terminal/renderers/wterm
|
|
13
|
+
*/
|
|
14
|
+
import type { ITerminalRendererProps, ITerminalRendererRef } from './types';
|
|
15
|
+
/**
|
|
16
|
+
* WtermRenderer Component.
|
|
17
|
+
*
|
|
18
|
+
* Chrome-less wterm renderer. Provides:
|
|
19
|
+
* - @wterm/react Terminal with Zig + WASM VT100/VT220/xterm parser
|
|
20
|
+
* - Auto-resize on container resize
|
|
21
|
+
* - Font size from useTerminalSettings context (override via props)
|
|
22
|
+
* - Ref forwarding with focus, write, clear, resize methods
|
|
23
|
+
* - Lazy-loading with graceful fallback if @wterm/react is missing
|
|
24
|
+
*
|
|
25
|
+
* wterm features:
|
|
26
|
+
* - ~12KB WASM binary (embedded base64 fallback)
|
|
27
|
+
* - DOM rendering with dirty-row tracking via requestAnimationFrame
|
|
28
|
+
* - Full 24-bit color (SGR) support
|
|
29
|
+
* - Alternate screen buffer (vim, less, htop work)
|
|
30
|
+
* - Scrollback history ring buffer
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* const termRef = useRef<ITerminalRendererRef>(null);
|
|
35
|
+
*
|
|
36
|
+
* <WtermRenderer
|
|
37
|
+
* ref={termRef}
|
|
38
|
+
* sessionId="my-session"
|
|
39
|
+
* fontSize={16}
|
|
40
|
+
* onReady={() => console.log('Terminal ready')}
|
|
41
|
+
* className="h-[600px]"
|
|
42
|
+
* />
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare const WtermRenderer: import("react").ForwardRefExoticComponent<ITerminalRendererProps & import("react").RefAttributes<ITerminalRendererRef>>;
|
|
46
|
+
//# sourceMappingURL=WtermRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WtermRenderer.d.ts","sourceRoot":"","sources":["../../../../../src/react-ui/blocks/Terminal/renderers/WtermRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAyD5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,aAAa,yHAoJzB,CAAC"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../../lib/utils.js";
|
|
4
|
+
import { useTerminalSettings } from "../hooks/useTerminalSettings.js";
|
|
5
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
|
|
8
|
+
//#region src/react-ui/blocks/Terminal/renderers/WtermRenderer.tsx
|
|
9
|
+
/**
|
|
10
|
+
* WtermRenderer Component.
|
|
11
|
+
*
|
|
12
|
+
* Chrome-less wterm renderer for maximum composability.
|
|
13
|
+
* Wraps @wterm/react Terminal component without any chrome,
|
|
14
|
+
* toolbar, or status bar. Use this when you want to build
|
|
15
|
+
* your own terminal shell or embed in custom layouts.
|
|
16
|
+
*
|
|
17
|
+
* Lazy-loads @wterm/react on first render. Returns null if
|
|
18
|
+
* the peer dependency is not installed.
|
|
19
|
+
*
|
|
20
|
+
* @module components/devenv/terminal/renderers/wterm
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Terminal container styles for wterm renderer.
|
|
24
|
+
*/
|
|
25
|
+
const terminalContainerStyles = "relative bg-[hsl(var(--terminal-bg,var(--background)))] min-h-[400px] border-x border-border/10 terminal-scroll wterm-wrapper";
|
|
26
|
+
/** Lazy-loaded wterm Terminal component. */
|
|
27
|
+
let WtermTerminalComponent = null;
|
|
28
|
+
/** Whether we've attempted to load wterm. */
|
|
29
|
+
let wtermLoadAttempted = false;
|
|
30
|
+
/**
|
|
31
|
+
* Lazy-load @wterm/react Terminal component.
|
|
32
|
+
*
|
|
33
|
+
* Returns null if @wterm/react is not installed.
|
|
34
|
+
*/
|
|
35
|
+
async function loadWtermTerminal() {
|
|
36
|
+
if (WtermTerminalComponent !== null || wtermLoadAttempted) return WtermTerminalComponent;
|
|
37
|
+
wtermLoadAttempted = true;
|
|
38
|
+
try {
|
|
39
|
+
WtermTerminalComponent = (await import("@wterm/react")).Terminal;
|
|
40
|
+
await import("@wterm/react/css");
|
|
41
|
+
return WtermTerminalComponent;
|
|
42
|
+
} catch {
|
|
43
|
+
WtermTerminalComponent = null;
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* WtermRenderer Component.
|
|
49
|
+
*
|
|
50
|
+
* Chrome-less wterm renderer. Provides:
|
|
51
|
+
* - @wterm/react Terminal with Zig + WASM VT100/VT220/xterm parser
|
|
52
|
+
* - Auto-resize on container resize
|
|
53
|
+
* - Font size from useTerminalSettings context (override via props)
|
|
54
|
+
* - Ref forwarding with focus, write, clear, resize methods
|
|
55
|
+
* - Lazy-loading with graceful fallback if @wterm/react is missing
|
|
56
|
+
*
|
|
57
|
+
* wterm features:
|
|
58
|
+
* - ~12KB WASM binary (embedded base64 fallback)
|
|
59
|
+
* - DOM rendering with dirty-row tracking via requestAnimationFrame
|
|
60
|
+
* - Full 24-bit color (SGR) support
|
|
61
|
+
* - Alternate screen buffer (vim, less, htop work)
|
|
62
|
+
* - Scrollback history ring buffer
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```tsx
|
|
66
|
+
* const termRef = useRef<ITerminalRendererRef>(null);
|
|
67
|
+
*
|
|
68
|
+
* <WtermRenderer
|
|
69
|
+
* ref={termRef}
|
|
70
|
+
* sessionId="my-session"
|
|
71
|
+
* fontSize={16}
|
|
72
|
+
* onReady={() => console.log('Terminal ready')}
|
|
73
|
+
* className="h-[600px]"
|
|
74
|
+
* />
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
const WtermRenderer = forwardRef(({ sessionId, fontSize: fontSizeProp, onReady, className }, ref) => {
|
|
78
|
+
/** wterm terminal instance handle */
|
|
79
|
+
const terminalRef = useRef(null);
|
|
80
|
+
const containerRef = useRef(null);
|
|
81
|
+
const terminalReadyRef = useRef(false);
|
|
82
|
+
const { settings, setFontSize } = useTerminalSettings();
|
|
83
|
+
/**
|
|
84
|
+
* Font size reads from global terminal settings store, with prop
|
|
85
|
+
* override taking precedence. This matches the v0.11.0 pattern
|
|
86
|
+
* where all consumers sync to the single source of truth.
|
|
87
|
+
*/
|
|
88
|
+
const fontSize = fontSizeProp ?? settings.fontSize;
|
|
89
|
+
const [dimensions, setDimensions] = useState({
|
|
90
|
+
cols: 80,
|
|
91
|
+
rows: 24
|
|
92
|
+
});
|
|
93
|
+
const [wtermAvailable, setWtermAvailable] = useState(null);
|
|
94
|
+
const [Terminal, setTerminal] = useState(null);
|
|
95
|
+
/**
|
|
96
|
+
* Lazy-load wterm on mount.
|
|
97
|
+
*/
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
loadWtermTerminal().then((term) => {
|
|
100
|
+
if (term) {
|
|
101
|
+
setTerminal(() => term);
|
|
102
|
+
setWtermAvailable(true);
|
|
103
|
+
} else setWtermAvailable(false);
|
|
104
|
+
});
|
|
105
|
+
}, []);
|
|
106
|
+
/**
|
|
107
|
+
* Handle terminal data input (no-op for chrome-less renderer).
|
|
108
|
+
* Consumer should handle I/O via ref methods.
|
|
109
|
+
*/
|
|
110
|
+
const handleData = useCallback((_data) => {}, []);
|
|
111
|
+
/**
|
|
112
|
+
* Handle terminal resize.
|
|
113
|
+
*/
|
|
114
|
+
const handleResize = useCallback((cols, rows) => {
|
|
115
|
+
setDimensions({
|
|
116
|
+
cols,
|
|
117
|
+
rows
|
|
118
|
+
});
|
|
119
|
+
}, []);
|
|
120
|
+
/**
|
|
121
|
+
* Handle terminal ready callback.
|
|
122
|
+
*/
|
|
123
|
+
const handleWtermReady = useCallback((core) => {
|
|
124
|
+
terminalReadyRef.current = true;
|
|
125
|
+
onReady?.();
|
|
126
|
+
}, [onReady]);
|
|
127
|
+
/**
|
|
128
|
+
* Handle font size changes via CSS custom property.
|
|
129
|
+
*/
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (containerRef.current) containerRef.current.style.setProperty("--wterm-font-size", `${fontSize}px`);
|
|
132
|
+
}, [fontSize]);
|
|
133
|
+
/**
|
|
134
|
+
* Font size update propagates to global settings if not overridden.
|
|
135
|
+
*/
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (!fontSizeProp) setFontSize(fontSize);
|
|
138
|
+
}, [
|
|
139
|
+
fontSize,
|
|
140
|
+
fontSizeProp,
|
|
141
|
+
setFontSize
|
|
142
|
+
]);
|
|
143
|
+
/**
|
|
144
|
+
* Expose imperative methods to parent.
|
|
145
|
+
*/
|
|
146
|
+
useImperativeHandle(ref, () => ({
|
|
147
|
+
focus: () => {
|
|
148
|
+
terminalRef.current?.focus();
|
|
149
|
+
},
|
|
150
|
+
write: (data) => {
|
|
151
|
+
terminalRef.current?.write(data);
|
|
152
|
+
},
|
|
153
|
+
clear: () => {
|
|
154
|
+
terminalRef.current?.write("\x1B[2J\x1B[H");
|
|
155
|
+
},
|
|
156
|
+
resize: (cols, rows) => {
|
|
157
|
+
terminalRef.current?.resize(cols, rows);
|
|
158
|
+
}
|
|
159
|
+
}));
|
|
160
|
+
/**
|
|
161
|
+
* Render fallbacks for missing/unavailable wterm.
|
|
162
|
+
*/
|
|
163
|
+
if (wtermAvailable === null) return /* @__PURE__ */ jsx("div", {
|
|
164
|
+
className: "flex items-center justify-center h-full text-muted-foreground text-sm min-h-[400px]",
|
|
165
|
+
children: "Loading wterm..."
|
|
166
|
+
});
|
|
167
|
+
if (wtermAvailable === false) return /* @__PURE__ */ jsxs("div", {
|
|
168
|
+
className: "flex flex-col items-center justify-center h-full text-muted-foreground text-sm gap-2 min-h-[400px]",
|
|
169
|
+
children: [/* @__PURE__ */ jsx("span", { children: "wterm is not installed" }), /* @__PURE__ */ jsx("code", {
|
|
170
|
+
className: "text-xs bg-muted px-2 py-1 rounded",
|
|
171
|
+
children: "bun add @wterm/react"
|
|
172
|
+
})]
|
|
173
|
+
});
|
|
174
|
+
return /* @__PURE__ */ jsx("div", {
|
|
175
|
+
ref: containerRef,
|
|
176
|
+
className: cn(terminalContainerStyles, className),
|
|
177
|
+
style: { "--wterm-font-size": `${fontSize}px` },
|
|
178
|
+
"data-session-id": sessionId,
|
|
179
|
+
children: Terminal && /* @__PURE__ */ jsx(Terminal, {
|
|
180
|
+
ref: terminalRef,
|
|
181
|
+
cols: dimensions.cols,
|
|
182
|
+
rows: dimensions.rows,
|
|
183
|
+
autoResize: false,
|
|
184
|
+
cursorBlink: true,
|
|
185
|
+
onData: handleData,
|
|
186
|
+
onResize: handleResize,
|
|
187
|
+
onReady: handleWtermReady,
|
|
188
|
+
className: "w-full h-full"
|
|
189
|
+
})
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
WtermRenderer.displayName = "WtermRenderer";
|
|
193
|
+
|
|
194
|
+
//#endregion
|
|
195
|
+
export { WtermRenderer };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XTermRenderer Component.
|
|
3
|
+
*
|
|
4
|
+
* Chrome-less xterm.js renderer for maximum composability.
|
|
5
|
+
* Wraps xterm.js Terminal + FitAddon without any chrome,
|
|
6
|
+
* toolbar, or status bar. Use this when you want to build
|
|
7
|
+
* your own terminal shell or embed in custom layouts.
|
|
8
|
+
*
|
|
9
|
+
* Lazy-loads @xterm/xterm and addon family on first render.
|
|
10
|
+
* Returns null if the peer dependency is not installed.
|
|
11
|
+
*
|
|
12
|
+
* @module components/devenv/terminal/renderers/xterm
|
|
13
|
+
*/
|
|
14
|
+
import type { ITerminalRendererProps, ITerminalRendererRef } from './types';
|
|
15
|
+
/**
|
|
16
|
+
* XTermRenderer Component.
|
|
17
|
+
*
|
|
18
|
+
* Chrome-less xterm.js renderer. Provides:
|
|
19
|
+
* - xterm.js Terminal with FitAddon, WebLinksAddon, SearchAddon
|
|
20
|
+
* - Auto-resize on container resize
|
|
21
|
+
* - Font size from useTerminalSettings context (override via props)
|
|
22
|
+
* - Ref forwarding with focus, write, clear, resize methods
|
|
23
|
+
* - Lazy-loading with graceful fallback if @xterm/xterm is missing
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* const termRef = useRef<ITerminalRendererRef>(null);
|
|
28
|
+
*
|
|
29
|
+
* <XTermRenderer
|
|
30
|
+
* ref={termRef}
|
|
31
|
+
* sessionId="my-session"
|
|
32
|
+
* fontSize={16}
|
|
33
|
+
* onReady={() => console.log('Terminal ready')}
|
|
34
|
+
* className="h-[600px]"
|
|
35
|
+
* />
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare const XTermRenderer: import("react").ForwardRefExoticComponent<ITerminalRendererProps & import("react").RefAttributes<ITerminalRendererRef>>;
|
|
39
|
+
//# sourceMappingURL=XTermRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"XTermRenderer.d.ts","sourceRoot":"","sources":["../../../../../src/react-ui/blocks/Terminal/renderers/XTermRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAcH,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAwF5E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,aAAa,yHA6LzB,CAAC"}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../../lib/utils.js";
|
|
4
|
+
import { useTerminalSettings } from "../hooks/useTerminalSettings.js";
|
|
5
|
+
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
|
|
8
|
+
//#region src/react-ui/blocks/Terminal/renderers/XTermRenderer.tsx
|
|
9
|
+
/**
|
|
10
|
+
* XTermRenderer Component.
|
|
11
|
+
*
|
|
12
|
+
* Chrome-less xterm.js renderer for maximum composability.
|
|
13
|
+
* Wraps xterm.js Terminal + FitAddon without any chrome,
|
|
14
|
+
* toolbar, or status bar. Use this when you want to build
|
|
15
|
+
* your own terminal shell or embed in custom layouts.
|
|
16
|
+
*
|
|
17
|
+
* Lazy-loads @xterm/xterm and addon family on first render.
|
|
18
|
+
* Returns null if the peer dependency is not installed.
|
|
19
|
+
*
|
|
20
|
+
* @module components/devenv/terminal/renderers/xterm
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Terminal container styles for xterm.js renderer.
|
|
24
|
+
*/
|
|
25
|
+
const terminalContainerStyles = "relative bg-[hsl(var(--terminal-bg,var(--background)))] min-h-[400px] border-x border-border/10 terminal-scroll";
|
|
26
|
+
/** Cached lazy-loaded xterm.js modules. */
|
|
27
|
+
let xtermModules = null;
|
|
28
|
+
/** Whether we've attempted to load xterm.js modules. */
|
|
29
|
+
let xtermLoadAttempted = false;
|
|
30
|
+
/**
|
|
31
|
+
* Lazy-load xterm.js and addon family.
|
|
32
|
+
*
|
|
33
|
+
* Returns null if @xterm/xterm is not installed.
|
|
34
|
+
*/
|
|
35
|
+
async function loadXtermModules() {
|
|
36
|
+
if (xtermModules !== null || xtermLoadAttempted) return xtermModules;
|
|
37
|
+
xtermLoadAttempted = true;
|
|
38
|
+
try {
|
|
39
|
+
const [xterm, fitAddon, webLinksAddon, searchAddon] = await Promise.all([
|
|
40
|
+
import("@xterm/xterm"),
|
|
41
|
+
import("@xterm/addon-fit"),
|
|
42
|
+
import("@xterm/addon-web-links"),
|
|
43
|
+
import("@xterm/addon-search")
|
|
44
|
+
]);
|
|
45
|
+
await import("@xterm/xterm/css/xterm.css");
|
|
46
|
+
xtermModules = {
|
|
47
|
+
Terminal: xterm.Terminal,
|
|
48
|
+
FitAddon: fitAddon.FitAddon,
|
|
49
|
+
WebLinksAddon: webLinksAddon.WebLinksAddon,
|
|
50
|
+
SearchAddon: searchAddon.SearchAddon
|
|
51
|
+
};
|
|
52
|
+
return xtermModules;
|
|
53
|
+
} catch {
|
|
54
|
+
xtermModules = null;
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get xterm.js theme from current theme context.
|
|
60
|
+
*/
|
|
61
|
+
function getXtermTheme() {
|
|
62
|
+
return {
|
|
63
|
+
background: "#241B2F",
|
|
64
|
+
foreground: "#e8e8ec",
|
|
65
|
+
cursor: "#D40C67",
|
|
66
|
+
black: "#000000",
|
|
67
|
+
red: "#cd3131",
|
|
68
|
+
green: "#0dbc79",
|
|
69
|
+
yellow: "#e5e510",
|
|
70
|
+
blue: "#2472c8",
|
|
71
|
+
magenta: "#bc3fbc",
|
|
72
|
+
cyan: "#11a8cd",
|
|
73
|
+
white: "#e5e5e5",
|
|
74
|
+
brightBlack: "#666666",
|
|
75
|
+
brightRed: "#f14c4c",
|
|
76
|
+
brightGreen: "#23d18b",
|
|
77
|
+
brightYellow: "#f5f543",
|
|
78
|
+
brightBlue: "#3b8eea",
|
|
79
|
+
brightMagenta: "#d670d6",
|
|
80
|
+
brightCyan: "#29b8db",
|
|
81
|
+
brightWhite: "#ffffff"
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* XTermRenderer Component.
|
|
86
|
+
*
|
|
87
|
+
* Chrome-less xterm.js renderer. Provides:
|
|
88
|
+
* - xterm.js Terminal with FitAddon, WebLinksAddon, SearchAddon
|
|
89
|
+
* - Auto-resize on container resize
|
|
90
|
+
* - Font size from useTerminalSettings context (override via props)
|
|
91
|
+
* - Ref forwarding with focus, write, clear, resize methods
|
|
92
|
+
* - Lazy-loading with graceful fallback if @xterm/xterm is missing
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```tsx
|
|
96
|
+
* const termRef = useRef<ITerminalRendererRef>(null);
|
|
97
|
+
*
|
|
98
|
+
* <XTermRenderer
|
|
99
|
+
* ref={termRef}
|
|
100
|
+
* sessionId="my-session"
|
|
101
|
+
* fontSize={16}
|
|
102
|
+
* onReady={() => console.log('Terminal ready')}
|
|
103
|
+
* className="h-[600px]"
|
|
104
|
+
* />
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
const XTermRenderer = forwardRef(({ sessionId, fontSize: fontSizeProp, fontFamily: fontFamilyProp, onReady, className }, ref) => {
|
|
108
|
+
const terminalRef = useRef(null);
|
|
109
|
+
const fitAddonRef = useRef(null);
|
|
110
|
+
const containerRef = useRef(null);
|
|
111
|
+
const terminalReadyRef = useRef(false);
|
|
112
|
+
const { settings, setFontSize } = useTerminalSettings();
|
|
113
|
+
/**
|
|
114
|
+
* Font size reads from global terminal settings store, with prop
|
|
115
|
+
* override taking precedence. This matches the v0.11.0 pattern
|
|
116
|
+
* where all consumers sync to the single source of truth.
|
|
117
|
+
*/
|
|
118
|
+
const fontSize = fontSizeProp ?? settings.fontSize;
|
|
119
|
+
const fontFamily = fontFamilyProp ?? "'JetBrains Mono', 'Fira Code', 'SF Mono', 'Cascadia Code', monospace";
|
|
120
|
+
const [xtermAvailable, setXtermAvailable] = useState(null);
|
|
121
|
+
const [XTerm, setXTerm] = useState(null);
|
|
122
|
+
/**
|
|
123
|
+
* Lazy-load xterm.js on mount.
|
|
124
|
+
*/
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
loadXtermModules().then((mods) => {
|
|
127
|
+
if (mods) {
|
|
128
|
+
setXTerm(() => mods.Terminal);
|
|
129
|
+
setXtermAvailable(true);
|
|
130
|
+
} else setXtermAvailable(false);
|
|
131
|
+
});
|
|
132
|
+
}, []);
|
|
133
|
+
/**
|
|
134
|
+
* Initialize xterm.js instance.
|
|
135
|
+
*/
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (!XTerm || !containerRef.current || !xtermAvailable) return;
|
|
138
|
+
if (!xtermModules) return;
|
|
139
|
+
const terminal = new XTerm({
|
|
140
|
+
cursorBlink: true,
|
|
141
|
+
cursorStyle: "bar",
|
|
142
|
+
scrollback: 1e4,
|
|
143
|
+
fontSize,
|
|
144
|
+
fontFamily,
|
|
145
|
+
theme: getXtermTheme(),
|
|
146
|
+
allowProposedApi: true,
|
|
147
|
+
disableStdin: false
|
|
148
|
+
});
|
|
149
|
+
const fitAddon = new xtermModules.FitAddon();
|
|
150
|
+
const webLinksAddon = new xtermModules.WebLinksAddon();
|
|
151
|
+
const searchAddon = new xtermModules.SearchAddon();
|
|
152
|
+
terminal.loadAddon(fitAddon);
|
|
153
|
+
terminal.loadAddon(webLinksAddon);
|
|
154
|
+
terminal.loadAddon(searchAddon);
|
|
155
|
+
terminal.open(containerRef.current);
|
|
156
|
+
const rafId = requestAnimationFrame(() => {
|
|
157
|
+
requestAnimationFrame(() => {
|
|
158
|
+
try {
|
|
159
|
+
fitAddon.fit();
|
|
160
|
+
} catch {}
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
terminalRef.current = terminal;
|
|
164
|
+
fitAddonRef.current = fitAddon;
|
|
165
|
+
terminalReadyRef.current = true;
|
|
166
|
+
onReady?.();
|
|
167
|
+
return () => {
|
|
168
|
+
cancelAnimationFrame(rafId);
|
|
169
|
+
terminalReadyRef.current = false;
|
|
170
|
+
terminal.dispose();
|
|
171
|
+
terminalRef.current = null;
|
|
172
|
+
fitAddonRef.current = null;
|
|
173
|
+
};
|
|
174
|
+
}, [XTerm, xtermAvailable]);
|
|
175
|
+
/**
|
|
176
|
+
* Update font size on existing terminal.
|
|
177
|
+
*/
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (!terminalRef.current || !terminalReadyRef.current) return;
|
|
180
|
+
terminalRef.current.options.fontSize = fontSize;
|
|
181
|
+
requestAnimationFrame(() => {
|
|
182
|
+
try {
|
|
183
|
+
fitAddonRef.current?.fit();
|
|
184
|
+
} catch {}
|
|
185
|
+
});
|
|
186
|
+
}, [fontSize]);
|
|
187
|
+
/**
|
|
188
|
+
* Handle container resize.
|
|
189
|
+
*/
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
const handleResize = () => {
|
|
192
|
+
requestAnimationFrame(() => {
|
|
193
|
+
if (fitAddonRef.current && terminalRef.current) try {
|
|
194
|
+
fitAddonRef.current.fit();
|
|
195
|
+
} catch {}
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
window.addEventListener("resize", handleResize);
|
|
199
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
200
|
+
}, []);
|
|
201
|
+
/**
|
|
202
|
+
* Font size update propagates to global settings if not overridden.
|
|
203
|
+
*/
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (!fontSizeProp) setFontSize(fontSize);
|
|
206
|
+
}, [
|
|
207
|
+
fontSize,
|
|
208
|
+
fontSizeProp,
|
|
209
|
+
setFontSize
|
|
210
|
+
]);
|
|
211
|
+
/**
|
|
212
|
+
* Expose imperative methods to parent.
|
|
213
|
+
*/
|
|
214
|
+
useImperativeHandle(ref, () => ({
|
|
215
|
+
focus: () => {
|
|
216
|
+
terminalRef.current?.focus();
|
|
217
|
+
},
|
|
218
|
+
write: (data) => {
|
|
219
|
+
terminalRef.current?.write(data);
|
|
220
|
+
},
|
|
221
|
+
clear: () => {
|
|
222
|
+
terminalRef.current?.clear();
|
|
223
|
+
},
|
|
224
|
+
resize: (cols, rows) => {
|
|
225
|
+
terminalRef.current?.resize(cols, rows);
|
|
226
|
+
}
|
|
227
|
+
}));
|
|
228
|
+
/**
|
|
229
|
+
* Render fallbacks for missing/unavailable xterm.js.
|
|
230
|
+
*/
|
|
231
|
+
if (xtermAvailable === null) return /* @__PURE__ */ jsx("div", {
|
|
232
|
+
className: "flex items-center justify-center h-full text-muted-foreground text-sm min-h-[400px]",
|
|
233
|
+
children: "Loading xterm.js..."
|
|
234
|
+
});
|
|
235
|
+
if (xtermAvailable === false) return /* @__PURE__ */ jsxs("div", {
|
|
236
|
+
className: "flex flex-col items-center justify-center h-full text-muted-foreground text-sm gap-2 min-h-[400px]",
|
|
237
|
+
children: [/* @__PURE__ */ jsx("span", { children: "xterm.js is not installed" }), /* @__PURE__ */ jsx("code", {
|
|
238
|
+
className: "text-xs bg-muted px-2 py-1 rounded",
|
|
239
|
+
children: "bun add @xterm/xterm"
|
|
240
|
+
})]
|
|
241
|
+
});
|
|
242
|
+
return /* @__PURE__ */ jsx("div", {
|
|
243
|
+
ref: containerRef,
|
|
244
|
+
className: cn(terminalContainerStyles, className),
|
|
245
|
+
"data-session-id": sessionId
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
XTermRenderer.displayName = "XTermRenderer";
|
|
249
|
+
|
|
250
|
+
//#endregion
|
|
251
|
+
export { XTermRenderer };
|