@qodalis/react-cli 2.0.0-beta.1 → 2.0.0-beta.11

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