@qodalis/react-cli 2.0.0-beta.3 → 2.0.0-beta.5

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/index.js ADDED
@@ -0,0 +1,846 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.tsx
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ Cli: () => Cli,
34
+ CliConfigProvider: () => CliConfigProvider,
35
+ CliContext: () => CliContext,
36
+ CliEngine: () => import_cli2.CliEngine,
37
+ CliPanel: () => CliPanel,
38
+ CliProvider: () => CliProvider,
39
+ useCli: () => useCli,
40
+ useCliConfig: () => useCliConfig,
41
+ useCliEngine: () => useCliEngine
42
+ });
43
+ module.exports = __toCommonJS(index_exports);
44
+
45
+ // src/Cli.tsx
46
+ var import_react4 = require("react");
47
+
48
+ // src/CliContext.tsx
49
+ var import_react = require("react");
50
+ var CliContext = (0, import_react.createContext)({ engine: null });
51
+ function useCli() {
52
+ return (0, import_react.useContext)(CliContext);
53
+ }
54
+
55
+ // src/CliConfigContext.tsx
56
+ var import_react2 = require("react");
57
+ var import_jsx_runtime = require("react/jsx-runtime");
58
+ var CliConfigCtx = (0, import_react2.createContext)({});
59
+ function useCliConfig() {
60
+ return (0, import_react2.useContext)(CliConfigCtx);
61
+ }
62
+ function CliConfigProvider({
63
+ modules,
64
+ processors,
65
+ options,
66
+ services,
67
+ children
68
+ }) {
69
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CliConfigCtx.Provider, { value: { modules, processors, options, services }, children });
70
+ }
71
+
72
+ // src/useCliEngine.ts
73
+ var import_react3 = require("react");
74
+ var import_cli = require("@qodalis/cli");
75
+ function useCliEngine(containerRef, config) {
76
+ const [engine, setEngine] = (0, import_react3.useState)(null);
77
+ const engineRef = (0, import_react3.useRef)(null);
78
+ const initTimerRef = (0, import_react3.useRef)(null);
79
+ (0, import_react3.useEffect)(() => {
80
+ if (config?.disabled || !containerRef.current) return;
81
+ const container = containerRef.current;
82
+ initTimerRef.current = setTimeout(() => {
83
+ initTimerRef.current = null;
84
+ if (!container) return;
85
+ const e = new import_cli.CliEngine(container, config?.options);
86
+ e.registerService("cli-framework", "React");
87
+ if (config?.services) {
88
+ for (const [token, value] of Object.entries(config.services)) {
89
+ e.registerService(token, value);
90
+ }
91
+ }
92
+ if (config?.modules) {
93
+ e.registerModules(config.modules);
94
+ }
95
+ if (config?.processors) {
96
+ e.registerProcessors(config.processors);
97
+ }
98
+ engineRef.current = e;
99
+ e.start().then(() => setEngine(e));
100
+ }, 0);
101
+ return () => {
102
+ if (initTimerRef.current !== null) {
103
+ clearTimeout(initTimerRef.current);
104
+ initTimerRef.current = null;
105
+ return;
106
+ }
107
+ if (engineRef.current) {
108
+ engineRef.current.destroy();
109
+ engineRef.current = null;
110
+ setEngine(null);
111
+ }
112
+ };
113
+ }, []);
114
+ return engine;
115
+ }
116
+
117
+ // src/Cli.tsx
118
+ var import_jsx_runtime2 = require("react/jsx-runtime");
119
+ function Cli({
120
+ modules,
121
+ processors,
122
+ options,
123
+ services,
124
+ snapshot,
125
+ onReady,
126
+ style,
127
+ className
128
+ }) {
129
+ const ctx = useCli();
130
+ const config = useCliConfig();
131
+ const containerRef = (0, import_react4.useRef)(null);
132
+ const hasProvider = !!ctx.engine;
133
+ const mergedModules = modules ?? config.modules;
134
+ const mergedProcessors = processors ?? config.processors;
135
+ const mergedOptions = options ?? config.options;
136
+ const mergedServices = services ?? config.services;
137
+ const optionsWithSnapshot = snapshot ? { ...mergedOptions, snapshot } : mergedOptions;
138
+ const standaloneEngine = useCliEngine(containerRef, {
139
+ modules: mergedModules,
140
+ processors: mergedProcessors,
141
+ options: optionsWithSnapshot,
142
+ services: mergedServices,
143
+ disabled: hasProvider
144
+ });
145
+ const engine = ctx.engine ?? standaloneEngine;
146
+ (0, import_react4.useEffect)(() => {
147
+ if (engine && onReady) {
148
+ onReady(engine);
149
+ }
150
+ }, [engine, onReady]);
151
+ if (hasProvider) {
152
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, {});
153
+ }
154
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
155
+ "div",
156
+ {
157
+ ref: containerRef,
158
+ style: { height: "100%", ...style },
159
+ className
160
+ }
161
+ );
162
+ }
163
+
164
+ // src/CliPanel.tsx
165
+ var import_react5 = __toESM(require("react"));
166
+ var import_cli_core = require("@qodalis/cli-core");
167
+ var import_jsx_runtime3 = require("react/jsx-runtime");
168
+ var MIN_PANE_WIDTH_PERCENT = 10;
169
+ var TerminalIcon = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
170
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "4 17 10 11 4 5" }),
171
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", y1: "19", x2: "20", y2: "19" })
172
+ ] });
173
+ var MaximizeIcon = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [
174
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "15 3 21 3 21 9" }),
175
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "9 21 3 21 3 15" }),
176
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
177
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
178
+ ] });
179
+ var RestoreIcon = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [
180
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "4 14 10 14 10 20" }),
181
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "20 10 14 10 14 4" }),
182
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "14", y1: "10", x2: "21", y2: "3" }),
183
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
184
+ ] });
185
+ var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [
186
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
187
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
188
+ ] });
189
+ var HideIcon = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [
190
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "5", y1: "18", x2: "19", y2: "18" }),
191
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "9 14 12 17 15 14" })
192
+ ] });
193
+ function CollapseChevron({ position, isCollapsed }) {
194
+ let points;
195
+ switch (position) {
196
+ case "top":
197
+ points = isCollapsed ? "6 9 12 15 18 9" : "18 15 12 9 6 15";
198
+ break;
199
+ case "left":
200
+ points = isCollapsed ? "9 18 15 12 9 6" : "15 6 9 12 15 18";
201
+ break;
202
+ case "right":
203
+ points = isCollapsed ? "15 6 9 12 15 18" : "9 18 15 12 9 6";
204
+ break;
205
+ default:
206
+ points = isCollapsed ? "18 15 12 9 6 15" : "6 9 12 15 18 9";
207
+ break;
208
+ }
209
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points }) });
210
+ }
211
+ function HideTabChevron({ position }) {
212
+ let points;
213
+ switch (position) {
214
+ case "top":
215
+ points = "6 9 12 15 18 9";
216
+ break;
217
+ case "left":
218
+ points = "9 18 15 12 9 6";
219
+ break;
220
+ case "right":
221
+ points = "15 6 9 12 15 18";
222
+ break;
223
+ default:
224
+ points = "18 15 12 9 6 15";
225
+ break;
226
+ }
227
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { className: "cli-panel-hide-tab-arrow", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points }) });
228
+ }
229
+ function PositionIcon({ position, size = 20 }) {
230
+ let fillRect;
231
+ switch (position) {
232
+ case "top":
233
+ fillRect = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "4", y: "4", width: "16", height: "5", rx: "1", fill: "currentColor", stroke: "none", opacity: "0.5" });
234
+ break;
235
+ case "left":
236
+ fillRect = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "4", y: "4", width: "5", height: "16", rx: "1", fill: "currentColor", stroke: "none", opacity: "0.5" });
237
+ break;
238
+ case "right":
239
+ fillRect = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "15", y: "4", width: "5", height: "16", rx: "1", fill: "currentColor", stroke: "none", opacity: "0.5" });
240
+ break;
241
+ default:
242
+ fillRect = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "4", y: "15", width: "16", height: "5", rx: "1", fill: "currentColor", stroke: "none", opacity: "0.5" });
243
+ break;
244
+ }
245
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [
246
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
247
+ fillRect
248
+ ] });
249
+ }
250
+ function CliPanel({ options: optionsProp, modules: modulesProp, processors: processorsProp, services: servicesProp, onClose, style, className }) {
251
+ const config = useCliConfig();
252
+ const options = optionsProp ?? config.options;
253
+ const modules = modulesProp ?? config.modules;
254
+ const processors = processorsProp ?? config.processors;
255
+ const services = servicesProp ?? config.services;
256
+ const [position, setPosition] = (0, import_react5.useState)(
257
+ () => (0, import_cli_core.loadPanelPosition)() ?? options?.position ?? "bottom"
258
+ );
259
+ const closable = options?.closable ?? true;
260
+ const resizable = options?.resizable ?? true;
261
+ const isHorizontal = position === "left" || position === "right";
262
+ const hideable = options?.hideable ?? true;
263
+ const hideAlignment = options?.hideAlignment ?? "center";
264
+ const syncTheme = options?.syncTheme ?? false;
265
+ const [visible, setVisible] = (0, import_react5.useState)(true);
266
+ const [collapsed, setCollapsed] = (0, import_react5.useState)(options?.isCollapsed ?? true);
267
+ const [maximized, setMaximized] = (0, import_react5.useState)(false);
268
+ const [panelHeight, setPanelHeight] = (0, import_react5.useState)(600);
269
+ const [panelWidth, setPanelWidth] = (0, import_react5.useState)(400);
270
+ const [initialized, setInitialized] = (0, import_react5.useState)(false);
271
+ const [hidden, setHidden] = (0, import_react5.useState)(options?.isHidden ?? false);
272
+ const preHideCollapsedRef = (0, import_react5.useRef)(true);
273
+ const [themeStyles, setThemeStyles] = (0, import_react5.useState)({});
274
+ const wrapperRef = (0, import_react5.useRef)(null);
275
+ const [tabs, setTabs] = (0, import_react5.useState)([]);
276
+ const [activeTabId, setActiveTabId] = (0, import_react5.useState)(0);
277
+ const [activePaneId, setActivePaneId] = (0, import_react5.useState)(0);
278
+ const nextIdRef = (0, import_react5.useRef)({ tab: 1, pane: 1 });
279
+ const [contextMenu, setContextMenu] = (0, import_react5.useState)({ visible: false, x: 0, y: 0, tabId: 0 });
280
+ const engineMapRef = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
281
+ const [paneResizing, setPaneResizing] = (0, import_react5.useState)(false);
282
+ const paneResizeRef = (0, import_react5.useRef)({ tabId: 0, dividerIndex: 0, startX: 0, startWidths: [], containerWidth: 0 });
283
+ const [panelResizing, setPanelResizing] = (0, import_react5.useState)(false);
284
+ const panelResizeRef = (0, import_react5.useRef)({ startY: 0, startHeight: 0, startX: 0, startWidth: 0 });
285
+ const prevHeightRef = (0, import_react5.useRef)(600);
286
+ const prevWidthRef = (0, import_react5.useRef)(400);
287
+ const addTab = (0, import_react5.useCallback)(() => {
288
+ const paneId = nextIdRef.current.pane++;
289
+ const tabId = nextIdRef.current.tab++;
290
+ const pane = { id: paneId, widthPercent: 100 };
291
+ const tab = { id: tabId, title: `Terminal ${tabId}`, isEditing: false, panes: [pane] };
292
+ setTabs((prev) => [...prev, tab]);
293
+ setActiveTabId(tabId);
294
+ setActivePaneId(paneId);
295
+ }, []);
296
+ const toggle = (0, import_react5.useCallback)(() => {
297
+ setCollapsed((prev) => {
298
+ const next = !prev;
299
+ if (!next && !initialized) {
300
+ setInitialized(true);
301
+ const paneId = nextIdRef.current.pane++;
302
+ const tabId = nextIdRef.current.tab++;
303
+ const pane = { id: paneId, widthPercent: 100 };
304
+ const tab = { id: tabId, title: `Terminal ${tabId}`, isEditing: false, panes: [pane] };
305
+ setTabs([tab]);
306
+ setActiveTabId(tabId);
307
+ setActivePaneId(paneId);
308
+ }
309
+ return next;
310
+ });
311
+ }, [initialized]);
312
+ const closeTab = (0, import_react5.useCallback)((id) => {
313
+ setTabs((prev) => {
314
+ const tab = prev.find((t) => t.id === id);
315
+ if (tab) {
316
+ tab.panes.forEach((p) => engineMapRef.current.delete(p.id));
317
+ }
318
+ const next = prev.filter((t) => t.id !== id);
319
+ if (next.length === 0) {
320
+ setInitialized(false);
321
+ setCollapsed(true);
322
+ return [];
323
+ }
324
+ setActiveTabId((current) => {
325
+ if (current === id) {
326
+ const oldIdx = prev.findIndex((t) => t.id === id);
327
+ const newIdx = Math.min(oldIdx, next.length - 1);
328
+ return next[newIdx].id;
329
+ }
330
+ return current;
331
+ });
332
+ return next;
333
+ });
334
+ }, []);
335
+ const selectTab = (0, import_react5.useCallback)((id) => {
336
+ setActiveTabId(id);
337
+ setTabs((prev) => {
338
+ const tab = prev.find((t) => t.id === id);
339
+ if (tab && tab.panes.length > 0) {
340
+ setActivePaneId(tab.panes[0].id);
341
+ }
342
+ return prev;
343
+ });
344
+ }, []);
345
+ const normalizePanes = (panes) => {
346
+ const total = panes.reduce((s, p) => s + p.widthPercent, 0);
347
+ if (total === 0) return;
348
+ const scale = 100 / total;
349
+ panes.forEach((p) => {
350
+ p.widthPercent *= scale;
351
+ });
352
+ };
353
+ const splitRight = (0, import_react5.useCallback)((tabId, afterPaneId) => {
354
+ setTabs((prev) => {
355
+ const next = prev.map((t) => ({ ...t, panes: t.panes.map((p) => ({ ...p })) }));
356
+ const tab = next.find((t) => t.id === (tabId ?? activeTabId));
357
+ if (!tab) return prev;
358
+ const targetPaneId = afterPaneId ?? activePaneId;
359
+ const idx = tab.panes.findIndex((p) => p.id === targetPaneId);
360
+ const insertIdx = idx === -1 ? tab.panes.length : idx + 1;
361
+ const newPaneId = nextIdRef.current.pane++;
362
+ const newPane = { id: newPaneId, widthPercent: 0 };
363
+ tab.panes.splice(insertIdx, 0, newPane);
364
+ const evenWidth = 100 / tab.panes.length;
365
+ tab.panes.forEach((p) => {
366
+ p.widthPercent = evenWidth;
367
+ });
368
+ normalizePanes(tab.panes);
369
+ setActivePaneId(newPaneId);
370
+ return next;
371
+ });
372
+ }, [activeTabId, activePaneId]);
373
+ const closePane = (0, import_react5.useCallback)((tabId, paneId) => {
374
+ setTabs((prev) => {
375
+ const tab = prev.find((t) => t.id === tabId);
376
+ if (!tab) return prev;
377
+ if (tab.panes.length <= 1) {
378
+ const next2 = prev.filter((t) => t.id !== tabId);
379
+ if (next2.length === 0) {
380
+ setInitialized(false);
381
+ setCollapsed(true);
382
+ return [];
383
+ }
384
+ return next2;
385
+ }
386
+ const next = prev.map((t) => ({ ...t, panes: t.panes.map((p) => ({ ...p })) }));
387
+ const updatedTab = next.find((t) => t.id === tabId);
388
+ const idx = updatedTab.panes.findIndex((p) => p.id === paneId);
389
+ if (idx === -1) return prev;
390
+ updatedTab.panes.splice(idx, 1);
391
+ const totalRemaining = updatedTab.panes.reduce((s, p) => s + p.widthPercent, 0);
392
+ if (totalRemaining > 0) {
393
+ updatedTab.panes.forEach((p) => {
394
+ p.widthPercent = p.widthPercent / totalRemaining * 100;
395
+ });
396
+ }
397
+ normalizePanes(updatedTab.panes);
398
+ setActivePaneId((curr) => {
399
+ if (curr === paneId) {
400
+ const newIdx = Math.min(idx, updatedTab.panes.length - 1);
401
+ return updatedTab.panes[newIdx].id;
402
+ }
403
+ return curr;
404
+ });
405
+ return next;
406
+ });
407
+ }, []);
408
+ const startRename = (0, import_react5.useCallback)((tabId) => {
409
+ setTabs((prev) => prev.map((t) => ({ ...t, isEditing: t.id === tabId })));
410
+ }, []);
411
+ const commitRename = (0, import_react5.useCallback)((tabId, value) => {
412
+ const trimmed = value.trim();
413
+ setTabs((prev) => prev.map(
414
+ (t) => t.id === tabId ? { ...t, title: trimmed || t.title, isEditing: false } : t
415
+ ));
416
+ }, []);
417
+ const closeContextMenu = (0, import_react5.useCallback)(() => {
418
+ setContextMenu((prev) => ({ ...prev, visible: false }));
419
+ }, []);
420
+ (0, import_react5.useEffect)(() => {
421
+ if (!contextMenu.visible) return;
422
+ const handler = () => closeContextMenu();
423
+ document.addEventListener("click", handler);
424
+ return () => document.removeEventListener("click", handler);
425
+ }, [contextMenu.visible, closeContextMenu]);
426
+ const onPanelResizeStart = (0, import_react5.useCallback)((e) => {
427
+ if (!resizable) return;
428
+ e.preventDefault();
429
+ if (collapsed) {
430
+ toggle();
431
+ }
432
+ setPanelResizing(true);
433
+ panelResizeRef.current = {
434
+ startY: e.clientY,
435
+ startHeight: panelHeight,
436
+ startX: e.clientX,
437
+ startWidth: panelWidth
438
+ };
439
+ }, [collapsed, toggle, panelHeight, panelWidth, resizable]);
440
+ (0, import_react5.useEffect)(() => {
441
+ if (!panelResizing) return;
442
+ const onMove = (e) => {
443
+ if (isHorizontal) {
444
+ const deltaX = position === "left" ? e.clientX - panelResizeRef.current.startX : panelResizeRef.current.startX - e.clientX;
445
+ let next = Math.max(100, panelResizeRef.current.startWidth + deltaX);
446
+ if (next > window.innerWidth) next = window.innerWidth;
447
+ setPanelWidth(next);
448
+ } else {
449
+ const deltaY = position === "top" ? e.clientY - panelResizeRef.current.startY : panelResizeRef.current.startY - e.clientY;
450
+ let next = Math.max(100, panelResizeRef.current.startHeight + deltaY);
451
+ if (next > window.innerHeight) next = window.innerHeight;
452
+ setPanelHeight(next);
453
+ }
454
+ };
455
+ const onUp = () => setPanelResizing(false);
456
+ document.addEventListener("mousemove", onMove);
457
+ document.addEventListener("mouseup", onUp);
458
+ return () => {
459
+ document.removeEventListener("mousemove", onMove);
460
+ document.removeEventListener("mouseup", onUp);
461
+ };
462
+ }, [panelResizing, isHorizontal, position]);
463
+ const onPaneResizeStart = (0, import_react5.useCallback)((e, tabId, dividerIndex) => {
464
+ e.preventDefault();
465
+ const tab = tabs.find((t) => t.id === tabId);
466
+ if (!tab) return;
467
+ setPaneResizing(true);
468
+ const container = e.target.closest(".cli-panel-panes-container");
469
+ paneResizeRef.current = {
470
+ tabId,
471
+ dividerIndex,
472
+ startX: e.clientX,
473
+ startWidths: tab.panes.map((p) => p.widthPercent),
474
+ containerWidth: container ? container.clientWidth : 1
475
+ };
476
+ }, [tabs]);
477
+ (0, import_react5.useEffect)(() => {
478
+ if (!paneResizing) return;
479
+ const onMove = (e) => {
480
+ const ref = paneResizeRef.current;
481
+ const deltaX = e.clientX - ref.startX;
482
+ const deltaPct = deltaX / ref.containerWidth * 100;
483
+ const i = ref.dividerIndex;
484
+ let leftWidth = ref.startWidths[i] + deltaPct;
485
+ let rightWidth = ref.startWidths[i + 1] - deltaPct;
486
+ if (leftWidth < MIN_PANE_WIDTH_PERCENT) {
487
+ leftWidth = MIN_PANE_WIDTH_PERCENT;
488
+ rightWidth = ref.startWidths[i] + ref.startWidths[i + 1] - MIN_PANE_WIDTH_PERCENT;
489
+ }
490
+ if (rightWidth < MIN_PANE_WIDTH_PERCENT) {
491
+ rightWidth = MIN_PANE_WIDTH_PERCENT;
492
+ leftWidth = ref.startWidths[i] + ref.startWidths[i + 1] - MIN_PANE_WIDTH_PERCENT;
493
+ }
494
+ setTabs((prev) => {
495
+ const next = prev.map((t) => ({ ...t, panes: t.panes.map((p) => ({ ...p })) }));
496
+ const tab = next.find((t) => t.id === ref.tabId);
497
+ if (tab) {
498
+ tab.panes[i].widthPercent = leftWidth;
499
+ tab.panes[i + 1].widthPercent = rightWidth;
500
+ }
501
+ return next;
502
+ });
503
+ };
504
+ const onUp = () => {
505
+ setPaneResizing(false);
506
+ document.body.classList.remove("cli-pane-resizing");
507
+ };
508
+ document.body.classList.add("cli-pane-resizing");
509
+ document.addEventListener("mousemove", onMove);
510
+ document.addEventListener("mouseup", onUp);
511
+ return () => {
512
+ document.removeEventListener("mousemove", onMove);
513
+ document.removeEventListener("mouseup", onUp);
514
+ };
515
+ }, [paneResizing]);
516
+ (0, import_react5.useEffect)(() => {
517
+ if (!syncTheme || !initialized) return;
518
+ const syncFromEngine = () => {
519
+ const engine = engineMapRef.current.values().next().value;
520
+ if (!engine) return;
521
+ const theme = engine.getTerminal().options.theme;
522
+ if (theme) setThemeStyles((0, import_cli_core.derivePanelThemeStyles)(theme));
523
+ };
524
+ const timer = setTimeout(() => {
525
+ syncFromEngine();
526
+ const container = wrapperRef.current?.querySelector(".terminal-container");
527
+ if (!container) return;
528
+ const observer = new MutationObserver(syncFromEngine);
529
+ observer.observe(container, { attributes: true, attributeFilter: ["style"] });
530
+ return () => observer.disconnect();
531
+ }, 200);
532
+ return () => clearTimeout(timer);
533
+ }, [syncTheme, initialized]);
534
+ const toggleMaximize = (0, import_react5.useCallback)(() => {
535
+ setMaximized((prev) => {
536
+ if (!prev) {
537
+ if (isHorizontal) {
538
+ prevWidthRef.current = panelWidth;
539
+ setPanelWidth(window.innerWidth);
540
+ } else {
541
+ prevHeightRef.current = panelHeight;
542
+ setPanelHeight(window.innerHeight);
543
+ }
544
+ } else {
545
+ if (isHorizontal) {
546
+ setPanelWidth(prevWidthRef.current);
547
+ } else {
548
+ setPanelHeight(prevHeightRef.current);
549
+ }
550
+ }
551
+ return !prev;
552
+ });
553
+ }, [panelHeight, panelWidth, isHorizontal]);
554
+ const handleClose = (0, import_react5.useCallback)(() => {
555
+ setVisible(false);
556
+ onClose?.();
557
+ }, [onClose]);
558
+ const handleHide = (0, import_react5.useCallback)(() => {
559
+ preHideCollapsedRef.current = collapsed;
560
+ setHidden(true);
561
+ }, [collapsed]);
562
+ const handleUnhide = (0, import_react5.useCallback)(() => {
563
+ setHidden(false);
564
+ setCollapsed(preHideCollapsedRef.current);
565
+ }, []);
566
+ const [positionDropdownOpen, setPositionDropdownOpen] = (0, import_react5.useState)(false);
567
+ const closePositionDropdown = (0, import_react5.useCallback)(() => setPositionDropdownOpen(false), []);
568
+ (0, import_react5.useEffect)(() => {
569
+ if (!positionDropdownOpen) return;
570
+ document.addEventListener("click", closePositionDropdown);
571
+ return () => document.removeEventListener("click", closePositionDropdown);
572
+ }, [positionDropdownOpen, closePositionDropdown]);
573
+ const handlePositionButtonClick = (0, import_react5.useCallback)((e) => {
574
+ e.stopPropagation();
575
+ setPositionDropdownOpen((prev) => !prev);
576
+ }, []);
577
+ const selectPosition = (0, import_react5.useCallback)((pos) => {
578
+ setPositionDropdownOpen(false);
579
+ setPosition(pos);
580
+ (0, import_cli_core.savePanelPosition)(pos);
581
+ }, []);
582
+ if (!visible) return null;
583
+ const wrapperStyle = {
584
+ ...isHorizontal ? { width: `${panelWidth}px` } : { height: `${panelHeight}px` },
585
+ ...hidden ? { display: "none" } : {},
586
+ ...themeStyles,
587
+ ...style
588
+ };
589
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
590
+ hidden && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
591
+ "button",
592
+ {
593
+ className: "cli-panel-hide-tab",
594
+ "data-position": position,
595
+ "data-hide-align": hideAlignment,
596
+ style: themeStyles,
597
+ title: "Show CLI",
598
+ onClick: handleUnhide,
599
+ children: [
600
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { className: "cli-panel-hide-tab-icon", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
601
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("polyline", { points: "4 17 10 11 4 5" }),
602
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "12", y1: "19", x2: "20", y2: "19" })
603
+ ] }),
604
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "cli-panel-hide-tab-label", children: "CLI" }),
605
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HideTabChevron, { position })
606
+ ]
607
+ }
608
+ ),
609
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
610
+ "div",
611
+ {
612
+ ref: wrapperRef,
613
+ className: `cli-panel-wrapper ${collapsed ? "collapsed" : ""} ${maximized ? "maximized" : ""} ${panelResizing ? "resizing" : ""} ${className ?? ""}`,
614
+ style: wrapperStyle,
615
+ "data-position": position,
616
+ "data-resizable": String(resizable),
617
+ "data-closable": String(closable),
618
+ "data-hideable": String(hideable),
619
+ children: [
620
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "cli-panel-header", children: [
621
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "cli-panel-resize-bar", onMouseDown: onPanelResizeStart, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "cli-panel-resize-grip" }) }),
622
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "cli-panel-header-content", children: [
623
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "cli-panel-title", children: [
624
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "cli-panel-title-icon", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TerminalIcon, {}) }),
625
+ "CLI"
626
+ ] }),
627
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "cli-panel-action-buttons", children: [
628
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "cli-panel-btn-position-wrapper", onClick: (e) => e.stopPropagation(), children: [
629
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "cli-panel-btn cli-panel-btn-position", title: "Move panel", onClick: handlePositionButtonClick, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PositionIcon, { position }) }),
630
+ positionDropdownOpen && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "cli-panel-position-dropdown", children: ["bottom", "top", "left", "right"].map((pos) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
631
+ "button",
632
+ {
633
+ className: `cli-panel-position-dropdown-item${position === pos ? " active" : ""}`,
634
+ onClick: () => selectPosition(pos),
635
+ children: [
636
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PositionIcon, { position: pos, size: 16 }),
637
+ pos.charAt(0).toUpperCase() + pos.slice(1)
638
+ ]
639
+ },
640
+ pos
641
+ )) })
642
+ ] }),
643
+ hideable && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "cli-panel-btn cli-panel-btn-hide", title: "Hide", onClick: handleHide, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HideIcon, {}) }),
644
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "cli-panel-btn", title: maximized ? "Restore" : "Maximize", disabled: collapsed, onClick: toggleMaximize, children: maximized ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RestoreIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MaximizeIcon, {}) }),
645
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "cli-panel-btn", title: collapsed ? "Expand" : "Collapse", onClick: toggle, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CollapseChevron, { position, isCollapsed: collapsed }) }),
646
+ closable && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "cli-panel-btn cli-panel-btn-close", title: "Close", onClick: handleClose, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, {}) })
647
+ ] })
648
+ ] })
649
+ ] }),
650
+ initialized && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "cli-panel-content", style: collapsed ? { display: "none" } : void 0, children: [
651
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "cli-panel-tabs", children: [
652
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className: "cli-panel-tab-list", children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
653
+ "li",
654
+ {
655
+ className: `cli-panel-tab ${tab.id === activeTabId ? "active" : ""}`,
656
+ onClick: () => selectTab(tab.id),
657
+ onDoubleClick: () => startRename(tab.id),
658
+ onContextMenu: (e) => {
659
+ e.preventDefault();
660
+ e.stopPropagation();
661
+ setContextMenu({ visible: true, x: e.clientX, y: e.clientY, tabId: tab.id });
662
+ },
663
+ children: tab.isEditing ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
664
+ "input",
665
+ {
666
+ className: "cli-panel-tab-rename-input",
667
+ type: "text",
668
+ defaultValue: tab.title,
669
+ autoFocus: true,
670
+ onKeyDown: (e) => {
671
+ if (e.key === "Enter") commitRename(tab.id, e.target.value);
672
+ else if (e.key === "Escape") setTabs((prev) => prev.map((t) => ({ ...t, isEditing: false })));
673
+ },
674
+ onBlur: (e) => commitRename(tab.id, e.target.value),
675
+ onClick: (e) => e.stopPropagation(),
676
+ onDoubleClick: (e) => e.stopPropagation()
677
+ }
678
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
679
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "cli-panel-tab-title", children: tab.title }),
680
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
681
+ "button",
682
+ {
683
+ className: "cli-panel-tab-close-btn",
684
+ title: "Close tab",
685
+ onClick: (e) => {
686
+ e.stopPropagation();
687
+ closeTab(tab.id);
688
+ },
689
+ children: "\xD7"
690
+ }
691
+ )
692
+ ] })
693
+ },
694
+ tab.id
695
+ )) }),
696
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "cli-panel-add-tab", title: "New terminal", onClick: addTab, children: "+" })
697
+ ] }),
698
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "cli-panel-instances", children: tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "cli-panel-instance", style: { display: tab.id === activeTabId ? void 0 : "none" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `cli-panel-panes-container ${paneResizing ? "resizing" : ""}`, children: tab.panes.map((pane, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react5.default.Fragment, { children: [
699
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "cli-panel-pane-divider", onMouseDown: (e) => onPaneResizeStart(e, tab.id, i - 1), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "cli-panel-pane-divider-grip" }) }),
700
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
701
+ "div",
702
+ {
703
+ className: `cli-panel-pane ${pane.id === activePaneId && tab.id === activeTabId ? "active-pane" : ""}`,
704
+ style: { flex: `${pane.widthPercent} 1 0` },
705
+ onClick: () => {
706
+ setActivePaneId(pane.id);
707
+ setActiveTabId(tab.id);
708
+ },
709
+ children: [
710
+ tab.panes.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
711
+ "button",
712
+ {
713
+ className: "cli-panel-pane-close-btn",
714
+ onClick: (e) => {
715
+ e.stopPropagation();
716
+ closePane(tab.id, pane.id);
717
+ },
718
+ title: "Close pane",
719
+ children: "\xD7"
720
+ }
721
+ ),
722
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CliContext.Provider, { value: { engine: null }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
723
+ Cli,
724
+ {
725
+ options,
726
+ modules,
727
+ processors,
728
+ services,
729
+ snapshot: pane.snapshot,
730
+ onReady: (engine) => engineMapRef.current.set(pane.id, engine)
731
+ }
732
+ ) })
733
+ ]
734
+ }
735
+ )
736
+ ] }, pane.id)) }) }, tab.id)) })
737
+ ] })
738
+ ]
739
+ }
740
+ ),
741
+ contextMenu.visible && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
742
+ "div",
743
+ {
744
+ className: "cli-panel-context-menu",
745
+ style: { left: contextMenu.x, top: contextMenu.y },
746
+ onClick: (e) => e.stopPropagation(),
747
+ children: [
748
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "cli-panel-context-menu-item", onClick: () => {
749
+ closeContextMenu();
750
+ startRename(contextMenu.tabId);
751
+ }, children: "Rename" }),
752
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "cli-panel-context-menu-item", onClick: () => {
753
+ closeContextMenu();
754
+ const tab = tabs.find((t) => t.id === contextMenu.tabId);
755
+ if (!tab) return;
756
+ const sourceEngine = engineMapRef.current.get(tab.panes[0]?.id);
757
+ const snapshot = sourceEngine?.snapshot();
758
+ const paneId = nextIdRef.current.pane++;
759
+ const tabId = nextIdRef.current.tab++;
760
+ const pane = { id: paneId, widthPercent: 100, snapshot };
761
+ const newTab = { id: tabId, title: `${tab.title} (copy)`, isEditing: false, panes: [pane] };
762
+ const idx = tabs.indexOf(tab);
763
+ setTabs((prev) => {
764
+ const next = [...prev];
765
+ next.splice(idx + 1, 0, newTab);
766
+ return next;
767
+ });
768
+ setActiveTabId(tabId);
769
+ setActivePaneId(paneId);
770
+ }, children: "Duplicate" }),
771
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "cli-panel-context-menu-item", onClick: () => {
772
+ closeContextMenu();
773
+ splitRight(contextMenu.tabId);
774
+ }, children: "Split Right" }),
775
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "cli-panel-context-menu-separator" }),
776
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "cli-panel-context-menu-item", onClick: () => {
777
+ closeContextMenu();
778
+ closeTab(contextMenu.tabId);
779
+ }, children: "Close" }),
780
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "cli-panel-context-menu-item", disabled: tabs.length <= 1, onClick: () => {
781
+ closeContextMenu();
782
+ setTabs((prev) => prev.filter((t) => t.id === contextMenu.tabId));
783
+ setActiveTabId(contextMenu.tabId);
784
+ }, children: "Close Others" }),
785
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "cli-panel-context-menu-item", onClick: () => {
786
+ const id = contextMenu.tabId;
787
+ closeContextMenu();
788
+ setTabs((prev) => {
789
+ const idx = prev.findIndex((t) => t.id === id);
790
+ if (idx === -1) return prev;
791
+ return prev.slice(0, idx + 1);
792
+ });
793
+ }, children: "Close to the Right" }),
794
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "cli-panel-context-menu-separator" }),
795
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "cli-panel-context-menu-item destructive", onClick: () => {
796
+ closeContextMenu();
797
+ setTabs([]);
798
+ setInitialized(false);
799
+ setCollapsed(true);
800
+ }, children: "Close All" })
801
+ ]
802
+ }
803
+ )
804
+ ] });
805
+ }
806
+
807
+ // src/CliProvider.tsx
808
+ var import_react6 = require("react");
809
+ var import_jsx_runtime4 = require("react/jsx-runtime");
810
+ function CliProvider({
811
+ modules,
812
+ processors,
813
+ options,
814
+ services,
815
+ children,
816
+ style
817
+ }) {
818
+ const config = useCliConfig();
819
+ const containerRef = (0, import_react6.useRef)(null);
820
+ const engine = useCliEngine(containerRef, {
821
+ modules: modules ?? config.modules,
822
+ processors: processors ?? config.processors,
823
+ options: options ?? config.options,
824
+ services: services ?? config.services
825
+ });
826
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(CliContext.Provider, { value: { engine }, children: [
827
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref: containerRef, style: { height: "100%", ...style } }),
828
+ children
829
+ ] });
830
+ }
831
+
832
+ // src/index.tsx
833
+ var import_cli2 = require("@qodalis/cli");
834
+ // Annotate the CommonJS export names for ESM import in node:
835
+ 0 && (module.exports = {
836
+ Cli,
837
+ CliConfigProvider,
838
+ CliContext,
839
+ CliEngine,
840
+ CliPanel,
841
+ CliProvider,
842
+ useCli,
843
+ useCliConfig,
844
+ useCliEngine
845
+ });
846
+ //# sourceMappingURL=index.js.map