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

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,801 @@
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
+ function CliPanel({ options: optionsProp, modules: modulesProp, processors: processorsProp, services: servicesProp, onClose, style, className }) {
207
+ const config = useCliConfig();
208
+ const options = optionsProp ?? config.options;
209
+ const modules = modulesProp ?? config.modules;
210
+ const processors = processorsProp ?? config.processors;
211
+ const services = servicesProp ?? config.services;
212
+ const [position, setPosition] = useState2(
213
+ () => loadPanelPosition() ?? options?.position ?? "bottom"
214
+ );
215
+ const closable = options?.closable ?? true;
216
+ const resizable = options?.resizable ?? true;
217
+ const isHorizontal = position === "left" || position === "right";
218
+ const hideable = options?.hideable ?? true;
219
+ const hideAlignment = options?.hideAlignment ?? "center";
220
+ const syncTheme = options?.syncTheme ?? false;
221
+ const [visible, setVisible] = useState2(true);
222
+ const [collapsed, setCollapsed] = useState2(options?.isCollapsed ?? true);
223
+ const [maximized, setMaximized] = useState2(false);
224
+ const [panelHeight, setPanelHeight] = useState2(600);
225
+ const [panelWidth, setPanelWidth] = useState2(400);
226
+ const [initialized, setInitialized] = useState2(false);
227
+ const [hidden, setHidden] = useState2(options?.isHidden ?? false);
228
+ const preHideCollapsedRef = useRef3(true);
229
+ const [themeStyles, setThemeStyles] = useState2({});
230
+ const wrapperRef = useRef3(null);
231
+ const [tabs, setTabs] = useState2([]);
232
+ const [activeTabId, setActiveTabId] = useState2(0);
233
+ const [activePaneId, setActivePaneId] = useState2(0);
234
+ const nextIdRef = useRef3({ tab: 1, pane: 1 });
235
+ const [contextMenu, setContextMenu] = useState2({ visible: false, x: 0, y: 0, tabId: 0 });
236
+ const engineMapRef = useRef3(/* @__PURE__ */ new Map());
237
+ const [paneResizing, setPaneResizing] = useState2(false);
238
+ const paneResizeRef = useRef3({ tabId: 0, dividerIndex: 0, startX: 0, startWidths: [], containerWidth: 0 });
239
+ const [panelResizing, setPanelResizing] = useState2(false);
240
+ const panelResizeRef = useRef3({ startY: 0, startHeight: 0, startX: 0, startWidth: 0 });
241
+ const prevHeightRef = useRef3(600);
242
+ const prevWidthRef = useRef3(400);
243
+ const addTab = useCallback(() => {
244
+ const paneId = nextIdRef.current.pane++;
245
+ const tabId = nextIdRef.current.tab++;
246
+ const pane = { id: paneId, widthPercent: 100 };
247
+ const tab = { id: tabId, title: `Terminal ${tabId}`, isEditing: false, panes: [pane] };
248
+ setTabs((prev) => [...prev, tab]);
249
+ setActiveTabId(tabId);
250
+ setActivePaneId(paneId);
251
+ }, []);
252
+ const toggle = useCallback(() => {
253
+ setCollapsed((prev) => {
254
+ const next = !prev;
255
+ if (!next && !initialized) {
256
+ setInitialized(true);
257
+ const paneId = nextIdRef.current.pane++;
258
+ const tabId = nextIdRef.current.tab++;
259
+ const pane = { id: paneId, widthPercent: 100 };
260
+ const tab = { id: tabId, title: `Terminal ${tabId}`, isEditing: false, panes: [pane] };
261
+ setTabs([tab]);
262
+ setActiveTabId(tabId);
263
+ setActivePaneId(paneId);
264
+ }
265
+ return next;
266
+ });
267
+ }, [initialized]);
268
+ const closeTab = useCallback((id) => {
269
+ setTabs((prev) => {
270
+ const tab = prev.find((t) => t.id === id);
271
+ if (tab) {
272
+ tab.panes.forEach((p) => engineMapRef.current.delete(p.id));
273
+ }
274
+ const next = prev.filter((t) => t.id !== id);
275
+ if (next.length === 0) {
276
+ setInitialized(false);
277
+ setCollapsed(true);
278
+ return [];
279
+ }
280
+ setActiveTabId((current) => {
281
+ if (current === id) {
282
+ const oldIdx = prev.findIndex((t) => t.id === id);
283
+ const newIdx = Math.min(oldIdx, next.length - 1);
284
+ return next[newIdx].id;
285
+ }
286
+ return current;
287
+ });
288
+ return next;
289
+ });
290
+ }, []);
291
+ const selectTab = useCallback((id) => {
292
+ setActiveTabId(id);
293
+ setTabs((prev) => {
294
+ const tab = prev.find((t) => t.id === id);
295
+ if (tab && tab.panes.length > 0) {
296
+ setActivePaneId(tab.panes[0].id);
297
+ }
298
+ return prev;
299
+ });
300
+ }, []);
301
+ const normalizePanes = (panes) => {
302
+ const total = panes.reduce((s, p) => s + p.widthPercent, 0);
303
+ if (total === 0) return;
304
+ const scale = 100 / total;
305
+ panes.forEach((p) => {
306
+ p.widthPercent *= scale;
307
+ });
308
+ };
309
+ const splitRight = useCallback((tabId, afterPaneId) => {
310
+ setTabs((prev) => {
311
+ const next = prev.map((t) => ({ ...t, panes: t.panes.map((p) => ({ ...p })) }));
312
+ const tab = next.find((t) => t.id === (tabId ?? activeTabId));
313
+ if (!tab) return prev;
314
+ const targetPaneId = afterPaneId ?? activePaneId;
315
+ const idx = tab.panes.findIndex((p) => p.id === targetPaneId);
316
+ const insertIdx = idx === -1 ? tab.panes.length : idx + 1;
317
+ const newPaneId = nextIdRef.current.pane++;
318
+ const newPane = { id: newPaneId, widthPercent: 0 };
319
+ tab.panes.splice(insertIdx, 0, newPane);
320
+ const evenWidth = 100 / tab.panes.length;
321
+ tab.panes.forEach((p) => {
322
+ p.widthPercent = evenWidth;
323
+ });
324
+ normalizePanes(tab.panes);
325
+ setActivePaneId(newPaneId);
326
+ return next;
327
+ });
328
+ }, [activeTabId, activePaneId]);
329
+ const closePane = useCallback((tabId, paneId) => {
330
+ setTabs((prev) => {
331
+ const tab = prev.find((t) => t.id === tabId);
332
+ if (!tab) return prev;
333
+ if (tab.panes.length <= 1) {
334
+ const next2 = prev.filter((t) => t.id !== tabId);
335
+ if (next2.length === 0) {
336
+ setInitialized(false);
337
+ setCollapsed(true);
338
+ return [];
339
+ }
340
+ return next2;
341
+ }
342
+ const next = prev.map((t) => ({ ...t, panes: t.panes.map((p) => ({ ...p })) }));
343
+ const updatedTab = next.find((t) => t.id === tabId);
344
+ const idx = updatedTab.panes.findIndex((p) => p.id === paneId);
345
+ if (idx === -1) return prev;
346
+ updatedTab.panes.splice(idx, 1);
347
+ const totalRemaining = updatedTab.panes.reduce((s, p) => s + p.widthPercent, 0);
348
+ if (totalRemaining > 0) {
349
+ updatedTab.panes.forEach((p) => {
350
+ p.widthPercent = p.widthPercent / totalRemaining * 100;
351
+ });
352
+ }
353
+ normalizePanes(updatedTab.panes);
354
+ setActivePaneId((curr) => {
355
+ if (curr === paneId) {
356
+ const newIdx = Math.min(idx, updatedTab.panes.length - 1);
357
+ return updatedTab.panes[newIdx].id;
358
+ }
359
+ return curr;
360
+ });
361
+ return next;
362
+ });
363
+ }, []);
364
+ const startRename = useCallback((tabId) => {
365
+ setTabs((prev) => prev.map((t) => ({ ...t, isEditing: t.id === tabId })));
366
+ }, []);
367
+ const commitRename = useCallback((tabId, value) => {
368
+ const trimmed = value.trim();
369
+ setTabs((prev) => prev.map(
370
+ (t) => t.id === tabId ? { ...t, title: trimmed || t.title, isEditing: false } : t
371
+ ));
372
+ }, []);
373
+ const closeContextMenu = useCallback(() => {
374
+ setContextMenu((prev) => ({ ...prev, visible: false }));
375
+ }, []);
376
+ useEffect3(() => {
377
+ if (!contextMenu.visible) return;
378
+ const handler = () => closeContextMenu();
379
+ document.addEventListener("click", handler);
380
+ return () => document.removeEventListener("click", handler);
381
+ }, [contextMenu.visible, closeContextMenu]);
382
+ const onPanelResizeStart = useCallback((e) => {
383
+ if (!resizable) return;
384
+ e.preventDefault();
385
+ if (collapsed) {
386
+ toggle();
387
+ }
388
+ setPanelResizing(true);
389
+ panelResizeRef.current = {
390
+ startY: e.clientY,
391
+ startHeight: panelHeight,
392
+ startX: e.clientX,
393
+ startWidth: panelWidth
394
+ };
395
+ }, [collapsed, toggle, panelHeight, panelWidth, resizable]);
396
+ useEffect3(() => {
397
+ if (!panelResizing) return;
398
+ const onMove = (e) => {
399
+ if (isHorizontal) {
400
+ const deltaX = position === "left" ? e.clientX - panelResizeRef.current.startX : panelResizeRef.current.startX - e.clientX;
401
+ let next = Math.max(100, panelResizeRef.current.startWidth + deltaX);
402
+ if (next > window.innerWidth) next = window.innerWidth;
403
+ setPanelWidth(next);
404
+ } else {
405
+ const deltaY = position === "top" ? e.clientY - panelResizeRef.current.startY : panelResizeRef.current.startY - e.clientY;
406
+ let next = Math.max(100, panelResizeRef.current.startHeight + deltaY);
407
+ if (next > window.innerHeight) next = window.innerHeight;
408
+ setPanelHeight(next);
409
+ }
410
+ };
411
+ const onUp = () => setPanelResizing(false);
412
+ document.addEventListener("mousemove", onMove);
413
+ document.addEventListener("mouseup", onUp);
414
+ return () => {
415
+ document.removeEventListener("mousemove", onMove);
416
+ document.removeEventListener("mouseup", onUp);
417
+ };
418
+ }, [panelResizing, isHorizontal, position]);
419
+ const onPaneResizeStart = useCallback((e, tabId, dividerIndex) => {
420
+ e.preventDefault();
421
+ const tab = tabs.find((t) => t.id === tabId);
422
+ if (!tab) return;
423
+ setPaneResizing(true);
424
+ const container = e.target.closest(".cli-panel-panes-container");
425
+ paneResizeRef.current = {
426
+ tabId,
427
+ dividerIndex,
428
+ startX: e.clientX,
429
+ startWidths: tab.panes.map((p) => p.widthPercent),
430
+ containerWidth: container ? container.clientWidth : 1
431
+ };
432
+ }, [tabs]);
433
+ useEffect3(() => {
434
+ if (!paneResizing) return;
435
+ const onMove = (e) => {
436
+ const ref = paneResizeRef.current;
437
+ const deltaX = e.clientX - ref.startX;
438
+ const deltaPct = deltaX / ref.containerWidth * 100;
439
+ const i = ref.dividerIndex;
440
+ let leftWidth = ref.startWidths[i] + deltaPct;
441
+ let rightWidth = ref.startWidths[i + 1] - deltaPct;
442
+ if (leftWidth < MIN_PANE_WIDTH_PERCENT) {
443
+ leftWidth = MIN_PANE_WIDTH_PERCENT;
444
+ rightWidth = ref.startWidths[i] + ref.startWidths[i + 1] - MIN_PANE_WIDTH_PERCENT;
445
+ }
446
+ if (rightWidth < MIN_PANE_WIDTH_PERCENT) {
447
+ rightWidth = MIN_PANE_WIDTH_PERCENT;
448
+ leftWidth = ref.startWidths[i] + ref.startWidths[i + 1] - MIN_PANE_WIDTH_PERCENT;
449
+ }
450
+ setTabs((prev) => {
451
+ const next = prev.map((t) => ({ ...t, panes: t.panes.map((p) => ({ ...p })) }));
452
+ const tab = next.find((t) => t.id === ref.tabId);
453
+ if (tab) {
454
+ tab.panes[i].widthPercent = leftWidth;
455
+ tab.panes[i + 1].widthPercent = rightWidth;
456
+ }
457
+ return next;
458
+ });
459
+ };
460
+ const onUp = () => {
461
+ setPaneResizing(false);
462
+ document.body.classList.remove("cli-pane-resizing");
463
+ };
464
+ document.body.classList.add("cli-pane-resizing");
465
+ document.addEventListener("mousemove", onMove);
466
+ document.addEventListener("mouseup", onUp);
467
+ return () => {
468
+ document.removeEventListener("mousemove", onMove);
469
+ document.removeEventListener("mouseup", onUp);
470
+ };
471
+ }, [paneResizing]);
472
+ useEffect3(() => {
473
+ if (!syncTheme || !initialized) return;
474
+ const syncFromEngine = () => {
475
+ const engine = engineMapRef.current.values().next().value;
476
+ if (!engine) return;
477
+ const theme = engine.getTerminal().options.theme;
478
+ if (theme) setThemeStyles(derivePanelThemeStyles(theme));
479
+ };
480
+ const timer = setTimeout(() => {
481
+ syncFromEngine();
482
+ const container = wrapperRef.current?.querySelector(".terminal-container");
483
+ if (!container) return;
484
+ const observer = new MutationObserver(syncFromEngine);
485
+ observer.observe(container, { attributes: true, attributeFilter: ["style"] });
486
+ return () => observer.disconnect();
487
+ }, 200);
488
+ return () => clearTimeout(timer);
489
+ }, [syncTheme, initialized]);
490
+ const toggleMaximize = useCallback(() => {
491
+ setMaximized((prev) => {
492
+ if (!prev) {
493
+ if (isHorizontal) {
494
+ prevWidthRef.current = panelWidth;
495
+ setPanelWidth(window.innerWidth);
496
+ } else {
497
+ prevHeightRef.current = panelHeight;
498
+ setPanelHeight(window.innerHeight);
499
+ }
500
+ } else {
501
+ if (isHorizontal) {
502
+ setPanelWidth(prevWidthRef.current);
503
+ } else {
504
+ setPanelHeight(prevHeightRef.current);
505
+ }
506
+ }
507
+ return !prev;
508
+ });
509
+ }, [panelHeight, panelWidth, isHorizontal]);
510
+ const handleClose = useCallback(() => {
511
+ setVisible(false);
512
+ onClose?.();
513
+ }, [onClose]);
514
+ const handleHide = useCallback(() => {
515
+ preHideCollapsedRef.current = collapsed;
516
+ setHidden(true);
517
+ }, [collapsed]);
518
+ const handleUnhide = useCallback(() => {
519
+ setHidden(false);
520
+ setCollapsed(preHideCollapsedRef.current);
521
+ }, []);
522
+ const [positionDropdownOpen, setPositionDropdownOpen] = useState2(false);
523
+ const closePositionDropdown = useCallback(() => setPositionDropdownOpen(false), []);
524
+ useEffect3(() => {
525
+ if (!positionDropdownOpen) return;
526
+ document.addEventListener("click", closePositionDropdown);
527
+ return () => document.removeEventListener("click", closePositionDropdown);
528
+ }, [positionDropdownOpen, closePositionDropdown]);
529
+ const handlePositionButtonClick = useCallback((e) => {
530
+ e.stopPropagation();
531
+ setPositionDropdownOpen((prev) => !prev);
532
+ }, []);
533
+ const selectPosition = useCallback((pos) => {
534
+ setPositionDropdownOpen(false);
535
+ setPosition(pos);
536
+ savePanelPosition(pos);
537
+ }, []);
538
+ if (!visible) return null;
539
+ const wrapperStyle = {
540
+ ...isHorizontal ? { width: `${panelWidth}px` } : { height: `${panelHeight}px` },
541
+ ...hidden ? { display: "none" } : {},
542
+ ...themeStyles,
543
+ ...style
544
+ };
545
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
546
+ hidden && /* @__PURE__ */ jsxs(
547
+ "button",
548
+ {
549
+ className: "cli-panel-hide-tab",
550
+ "data-position": position,
551
+ "data-hide-align": hideAlignment,
552
+ style: themeStyles,
553
+ title: "Show CLI",
554
+ onClick: handleUnhide,
555
+ children: [
556
+ /* @__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: [
557
+ /* @__PURE__ */ jsx3("polyline", { points: "4 17 10 11 4 5" }),
558
+ /* @__PURE__ */ jsx3("line", { x1: "12", y1: "19", x2: "20", y2: "19" })
559
+ ] }),
560
+ /* @__PURE__ */ jsx3("span", { className: "cli-panel-hide-tab-label", children: "CLI" }),
561
+ /* @__PURE__ */ jsx3(HideTabChevron, { position })
562
+ ]
563
+ }
564
+ ),
565
+ /* @__PURE__ */ jsxs(
566
+ "div",
567
+ {
568
+ ref: wrapperRef,
569
+ className: `cli-panel-wrapper ${collapsed ? "collapsed" : ""} ${maximized ? "maximized" : ""} ${panelResizing ? "resizing" : ""} ${className ?? ""}`,
570
+ style: wrapperStyle,
571
+ "data-position": position,
572
+ "data-resizable": String(resizable),
573
+ "data-closable": String(closable),
574
+ "data-hideable": String(hideable),
575
+ children: [
576
+ /* @__PURE__ */ jsxs("div", { className: "cli-panel-header", children: [
577
+ /* @__PURE__ */ jsx3("div", { className: "cli-panel-resize-bar", onMouseDown: onPanelResizeStart, children: /* @__PURE__ */ jsx3("div", { className: "cli-panel-resize-grip" }) }),
578
+ /* @__PURE__ */ jsxs("div", { className: "cli-panel-header-content", children: [
579
+ /* @__PURE__ */ jsxs("p", { className: "cli-panel-title", children: [
580
+ /* @__PURE__ */ jsx3("span", { className: "cli-panel-title-icon", children: /* @__PURE__ */ jsx3(TerminalIcon, {}) }),
581
+ "CLI"
582
+ ] }),
583
+ /* @__PURE__ */ jsxs("div", { className: "cli-panel-action-buttons", children: [
584
+ /* @__PURE__ */ jsxs("div", { className: "cli-panel-btn-position-wrapper", onClick: (e) => e.stopPropagation(), children: [
585
+ /* @__PURE__ */ jsx3("button", { className: "cli-panel-btn cli-panel-btn-position", title: "Move panel", onClick: handlePositionButtonClick, children: /* @__PURE__ */ jsx3(PositionIcon, { position }) }),
586
+ positionDropdownOpen && /* @__PURE__ */ jsx3("div", { className: "cli-panel-position-dropdown", children: ["bottom", "top", "left", "right"].map((pos) => /* @__PURE__ */ jsxs(
587
+ "button",
588
+ {
589
+ className: `cli-panel-position-dropdown-item${position === pos ? " active" : ""}`,
590
+ onClick: () => selectPosition(pos),
591
+ children: [
592
+ /* @__PURE__ */ jsx3(PositionIcon, { position: pos, size: 16 }),
593
+ pos.charAt(0).toUpperCase() + pos.slice(1)
594
+ ]
595
+ },
596
+ pos
597
+ )) })
598
+ ] }),
599
+ hideable && /* @__PURE__ */ jsx3("button", { className: "cli-panel-btn cli-panel-btn-hide", title: "Hide", onClick: handleHide, children: /* @__PURE__ */ jsx3(HideIcon, {}) }),
600
+ /* @__PURE__ */ jsx3("button", { className: "cli-panel-btn", title: maximized ? "Restore" : "Maximize", disabled: collapsed, onClick: toggleMaximize, children: maximized ? /* @__PURE__ */ jsx3(RestoreIcon, {}) : /* @__PURE__ */ jsx3(MaximizeIcon, {}) }),
601
+ /* @__PURE__ */ jsx3("button", { className: "cli-panel-btn", title: collapsed ? "Expand" : "Collapse", onClick: toggle, children: /* @__PURE__ */ jsx3(CollapseChevron, { position, isCollapsed: collapsed }) }),
602
+ closable && /* @__PURE__ */ jsx3("button", { className: "cli-panel-btn cli-panel-btn-close", title: "Close", onClick: handleClose, children: /* @__PURE__ */ jsx3(CloseIcon, {}) })
603
+ ] })
604
+ ] })
605
+ ] }),
606
+ initialized && /* @__PURE__ */ jsxs("div", { className: "cli-panel-content", style: collapsed ? { display: "none" } : void 0, children: [
607
+ /* @__PURE__ */ jsxs("div", { className: "cli-panel-tabs", children: [
608
+ /* @__PURE__ */ jsx3("ul", { className: "cli-panel-tab-list", children: tabs.map((tab) => /* @__PURE__ */ jsx3(
609
+ "li",
610
+ {
611
+ className: `cli-panel-tab ${tab.id === activeTabId ? "active" : ""}`,
612
+ onClick: () => selectTab(tab.id),
613
+ onDoubleClick: () => startRename(tab.id),
614
+ onContextMenu: (e) => {
615
+ e.preventDefault();
616
+ e.stopPropagation();
617
+ setContextMenu({ visible: true, x: e.clientX, y: e.clientY, tabId: tab.id });
618
+ },
619
+ children: tab.isEditing ? /* @__PURE__ */ jsx3(
620
+ "input",
621
+ {
622
+ className: "cli-panel-tab-rename-input",
623
+ type: "text",
624
+ defaultValue: tab.title,
625
+ autoFocus: true,
626
+ onKeyDown: (e) => {
627
+ if (e.key === "Enter") commitRename(tab.id, e.target.value);
628
+ else if (e.key === "Escape") setTabs((prev) => prev.map((t) => ({ ...t, isEditing: false })));
629
+ },
630
+ onBlur: (e) => commitRename(tab.id, e.target.value),
631
+ onClick: (e) => e.stopPropagation(),
632
+ onDoubleClick: (e) => e.stopPropagation()
633
+ }
634
+ ) : /* @__PURE__ */ jsxs(Fragment2, { children: [
635
+ /* @__PURE__ */ jsx3("span", { className: "cli-panel-tab-title", children: tab.title }),
636
+ /* @__PURE__ */ jsx3(
637
+ "button",
638
+ {
639
+ className: "cli-panel-tab-close-btn",
640
+ title: "Close tab",
641
+ onClick: (e) => {
642
+ e.stopPropagation();
643
+ closeTab(tab.id);
644
+ },
645
+ children: "\xD7"
646
+ }
647
+ )
648
+ ] })
649
+ },
650
+ tab.id
651
+ )) }),
652
+ /* @__PURE__ */ jsx3("button", { className: "cli-panel-add-tab", title: "New terminal", onClick: addTab, children: "+" })
653
+ ] }),
654
+ /* @__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: [
655
+ 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" }) }),
656
+ /* @__PURE__ */ jsxs(
657
+ "div",
658
+ {
659
+ className: `cli-panel-pane ${pane.id === activePaneId && tab.id === activeTabId ? "active-pane" : ""}`,
660
+ style: { flex: `${pane.widthPercent} 1 0` },
661
+ onClick: () => {
662
+ setActivePaneId(pane.id);
663
+ setActiveTabId(tab.id);
664
+ },
665
+ children: [
666
+ tab.panes.length > 1 && /* @__PURE__ */ jsx3(
667
+ "button",
668
+ {
669
+ className: "cli-panel-pane-close-btn",
670
+ onClick: (e) => {
671
+ e.stopPropagation();
672
+ closePane(tab.id, pane.id);
673
+ },
674
+ title: "Close pane",
675
+ children: "\xD7"
676
+ }
677
+ ),
678
+ /* @__PURE__ */ jsx3(CliContext.Provider, { value: { engine: null }, children: /* @__PURE__ */ jsx3(
679
+ Cli,
680
+ {
681
+ options,
682
+ modules,
683
+ processors,
684
+ services,
685
+ snapshot: pane.snapshot,
686
+ onReady: (engine) => engineMapRef.current.set(pane.id, engine)
687
+ }
688
+ ) })
689
+ ]
690
+ }
691
+ )
692
+ ] }, pane.id)) }) }, tab.id)) })
693
+ ] })
694
+ ]
695
+ }
696
+ ),
697
+ contextMenu.visible && /* @__PURE__ */ jsxs(
698
+ "div",
699
+ {
700
+ className: "cli-panel-context-menu",
701
+ style: { left: contextMenu.x, top: contextMenu.y },
702
+ onClick: (e) => e.stopPropagation(),
703
+ children: [
704
+ /* @__PURE__ */ jsx3("button", { className: "cli-panel-context-menu-item", onClick: () => {
705
+ closeContextMenu();
706
+ startRename(contextMenu.tabId);
707
+ }, children: "Rename" }),
708
+ /* @__PURE__ */ jsx3("button", { className: "cli-panel-context-menu-item", onClick: () => {
709
+ closeContextMenu();
710
+ const tab = tabs.find((t) => t.id === contextMenu.tabId);
711
+ if (!tab) return;
712
+ const sourceEngine = engineMapRef.current.get(tab.panes[0]?.id);
713
+ const snapshot = sourceEngine?.snapshot();
714
+ const paneId = nextIdRef.current.pane++;
715
+ const tabId = nextIdRef.current.tab++;
716
+ const pane = { id: paneId, widthPercent: 100, snapshot };
717
+ const newTab = { id: tabId, title: `${tab.title} (copy)`, isEditing: false, panes: [pane] };
718
+ const idx = tabs.indexOf(tab);
719
+ setTabs((prev) => {
720
+ const next = [...prev];
721
+ next.splice(idx + 1, 0, newTab);
722
+ return next;
723
+ });
724
+ setActiveTabId(tabId);
725
+ setActivePaneId(paneId);
726
+ }, children: "Duplicate" }),
727
+ /* @__PURE__ */ jsx3("button", { className: "cli-panel-context-menu-item", onClick: () => {
728
+ closeContextMenu();
729
+ splitRight(contextMenu.tabId);
730
+ }, children: "Split Right" }),
731
+ /* @__PURE__ */ jsx3("div", { className: "cli-panel-context-menu-separator" }),
732
+ /* @__PURE__ */ jsx3("button", { className: "cli-panel-context-menu-item", onClick: () => {
733
+ closeContextMenu();
734
+ closeTab(contextMenu.tabId);
735
+ }, children: "Close" }),
736
+ /* @__PURE__ */ jsx3("button", { className: "cli-panel-context-menu-item", disabled: tabs.length <= 1, onClick: () => {
737
+ closeContextMenu();
738
+ setTabs((prev) => prev.filter((t) => t.id === contextMenu.tabId));
739
+ setActiveTabId(contextMenu.tabId);
740
+ }, children: "Close Others" }),
741
+ /* @__PURE__ */ jsx3("button", { className: "cli-panel-context-menu-item", onClick: () => {
742
+ const id = contextMenu.tabId;
743
+ closeContextMenu();
744
+ setTabs((prev) => {
745
+ const idx = prev.findIndex((t) => t.id === id);
746
+ if (idx === -1) return prev;
747
+ return prev.slice(0, idx + 1);
748
+ });
749
+ }, children: "Close to the Right" }),
750
+ /* @__PURE__ */ jsx3("div", { className: "cli-panel-context-menu-separator" }),
751
+ /* @__PURE__ */ jsx3("button", { className: "cli-panel-context-menu-item destructive", onClick: () => {
752
+ closeContextMenu();
753
+ setTabs([]);
754
+ setInitialized(false);
755
+ setCollapsed(true);
756
+ }, children: "Close All" })
757
+ ]
758
+ }
759
+ )
760
+ ] });
761
+ }
762
+
763
+ // src/CliProvider.tsx
764
+ import { useRef as useRef4 } from "react";
765
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
766
+ function CliProvider({
767
+ modules,
768
+ processors,
769
+ options,
770
+ services,
771
+ children,
772
+ style
773
+ }) {
774
+ const config = useCliConfig();
775
+ const containerRef = useRef4(null);
776
+ const engine = useCliEngine(containerRef, {
777
+ modules: modules ?? config.modules,
778
+ processors: processors ?? config.processors,
779
+ options: options ?? config.options,
780
+ services: services ?? config.services
781
+ });
782
+ return /* @__PURE__ */ jsxs2(CliContext.Provider, { value: { engine }, children: [
783
+ /* @__PURE__ */ jsx4("div", { ref: containerRef, style: { height: "100%", ...style } }),
784
+ children
785
+ ] });
786
+ }
787
+
788
+ // src/index.tsx
789
+ import { CliEngine as CliEngine2 } from "@qodalis/cli";
790
+ export {
791
+ Cli,
792
+ CliConfigProvider,
793
+ CliContext,
794
+ CliEngine2 as CliEngine,
795
+ CliPanel,
796
+ CliProvider,
797
+ useCli,
798
+ useCliConfig,
799
+ useCliEngine
800
+ };
801
+ //# sourceMappingURL=index.mjs.map