@qodalis/react-cli 2.0.0-beta.8 → 2.0.0-rc.1

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 CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/Cli.tsx
2
2
  import { useRef as useRef2, useEffect as useEffect2 } from "react";
3
+ import { resolveCliHostConfig } from "@qodalis/cli";
3
4
 
4
5
  // src/CliContext.tsx
5
6
  import { createContext, useContext } from "react";
@@ -17,17 +18,16 @@ function useCliConfig() {
17
18
  }
18
19
  function CliConfigProvider({
19
20
  modules,
20
- processors,
21
21
  options,
22
22
  services,
23
23
  children
24
24
  }) {
25
- return /* @__PURE__ */ jsx(CliConfigCtx.Provider, { value: { modules, processors, options, services }, children });
25
+ return /* @__PURE__ */ jsx(CliConfigCtx.Provider, { value: { modules, options, services }, children });
26
26
  }
27
27
 
28
28
  // src/useCliEngine.ts
29
29
  import { useRef, useEffect, useState } from "react";
30
- import { CliEngine } from "@qodalis/cli";
30
+ import { createCliEngine } from "@qodalis/cli";
31
31
  function useCliEngine(containerRef, config) {
32
32
  const [engine, setEngine] = useState(null);
33
33
  const engineRef = useRef(null);
@@ -38,19 +38,7 @@ function useCliEngine(containerRef, config) {
38
38
  initTimerRef.current = setTimeout(() => {
39
39
  initTimerRef.current = null;
40
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
- }
41
+ const e = createCliEngine(container, { ...config, framework: "React" });
54
42
  engineRef.current = e;
55
43
  e.start().then(() => setEngine(e));
56
44
  }, 0);
@@ -74,7 +62,6 @@ function useCliEngine(containerRef, config) {
74
62
  import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
75
63
  function Cli({
76
64
  modules,
77
- processors,
78
65
  options,
79
66
  services,
80
67
  snapshot,
@@ -86,16 +73,9 @@ function Cli({
86
73
  const config = useCliConfig();
87
74
  const containerRef = useRef2(null);
88
75
  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
76
  const standaloneEngine = useCliEngine(containerRef, {
95
- modules: mergedModules,
96
- processors: mergedProcessors,
97
- options: optionsWithSnapshot,
98
- services: mergedServices,
77
+ ...resolveCliHostConfig({ modules, options, services }, config),
78
+ snapshot,
99
79
  disabled: hasProvider
100
80
  });
101
81
  const engine = ctx.engine ?? standaloneEngine;
@@ -118,503 +98,150 @@ function Cli({
118
98
  }
119
99
 
120
100
  // 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";
101
+ import React3, { useEffect as useEffect3, useLayoutEffect, useState as useState2, useSyncExternalStore } from "react";
102
+ import {
103
+ CliPanelController,
104
+ CLOSE_GLYPH,
105
+ CLOSE_ICON,
106
+ HIDE_ICON,
107
+ HIDE_TAB_TERMINAL_ICON,
108
+ MAXIMIZE_ICON,
109
+ PANEL_LABELS,
110
+ RESTORE_ICON,
111
+ TERMINAL_ICON,
112
+ collapseChevronIcon,
113
+ contextMenuItems,
114
+ contextMenuStyle,
115
+ glowLineClass,
116
+ handleRenameKey,
117
+ hideTabChevronIcon,
118
+ joinClasses,
119
+ mergeCliOptions,
120
+ openStatusDropdown,
121
+ paneClass,
122
+ paneStyle,
123
+ panelTabClass,
124
+ panelTabDotClass,
125
+ panelWrapperClass,
126
+ panelWrapperStyle,
127
+ panesContainerClass,
128
+ panesContainerWidth,
129
+ positionIcon,
130
+ positionOptions,
131
+ resolveCliHostConfig as resolveCliHostConfig2,
132
+ runContextMenuAction,
133
+ statusIndicators,
134
+ svgAttrs
135
+ } from "@qodalis/cli";
123
136
  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;
137
+ function reactSvgProps(attrs) {
138
+ const props = {};
139
+ for (const [key, value] of Object.entries(attrs)) {
140
+ props[key.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = value;
164
141
  }
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 }) });
142
+ return props;
166
143
  }
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 }) });
144
+ function Icon({ icon, className }) {
145
+ return /* @__PURE__ */ jsx3("svg", { className, ...reactSvgProps(svgAttrs(icon)), children: icon.shapes.map((shape, i) => React3.createElement(shape.tag, { key: i, ...reactSvgProps(shape.attrs) })) });
184
146
  }
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;
147
+ var focusAndSelect = (input) => {
148
+ if (input && document.activeElement !== input) {
149
+ input.focus();
150
+ input.select();
200
151
  }
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 }) {
152
+ };
153
+ var asStyle = (style) => style;
154
+ var CliPanel = React3.forwardRef(function CliPanel2(props, ref) {
207
155
  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);
156
+ const options = mergeCliOptions(props.options, config.options);
157
+ const { modules, services } = resolveCliHostConfig2(props, config);
158
+ const [controller] = useState2(
159
+ () => new CliPanelController({
160
+ config: options,
161
+ controlled: {
162
+ collapsed: props.collapsed,
163
+ hidden: props.hidden,
164
+ maximized: props.maximized,
165
+ activeTabId: props.activeTabId,
166
+ position: props.position,
167
+ height: props.height,
168
+ width: props.width
264
169
  }
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;
170
+ })
171
+ );
172
+ const s = useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
173
+ const [rootElement, setRootElement] = useState2(null);
174
+ useLayoutEffect(() => {
175
+ controller.setCallbacks({
176
+ onCollapsedChange: props.onCollapsedChange,
177
+ onHiddenChange: props.onHiddenChange,
178
+ onMaximizedChange: props.onMaximizedChange,
179
+ onActiveTabIdChange: props.onActiveTabIdChange,
180
+ onPositionChange: props.onPositionChange,
181
+ onHeightChange: props.onHeightChange,
182
+ onWidthChange: props.onWidthChange,
183
+ onClose: props.onClose,
184
+ onTabAdded: props.onTabAdded,
185
+ onTabClosed: props.onTabClosed,
186
+ onPaneSplit: props.onPaneSplit,
187
+ onPaneClosed: props.onPaneClosed
307
188
  });
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;
189
+ controller.setControlled({
190
+ collapsed: props.collapsed,
191
+ hidden: props.hidden,
192
+ maximized: props.maximized,
193
+ activeTabId: props.activeTabId,
194
+ position: props.position,
195
+ height: props.height,
196
+ width: props.width
327
197
  });
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
- };
198
+ controller.setOptions(options);
199
+ });
200
+ useEffect3(() => controller.setRootElement(rootElement), [controller, rootElement]);
201
+ useEffect3(() => () => controller.destroy(), [controller]);
202
+ React3.useImperativeHandle(ref, () => controller, [controller]);
203
+ if (!s.visible) return null;
545
204
  return /* @__PURE__ */ jsxs(Fragment2, { children: [
546
- hidden && /* @__PURE__ */ jsxs(
205
+ s.hidden && /* @__PURE__ */ jsxs(
547
206
  "button",
548
207
  {
549
208
  className: "cli-panel-hide-tab",
550
- "data-position": position,
551
- "data-hide-align": hideAlignment,
552
- style: themeStyles,
553
- title: "Show CLI",
554
- onClick: handleUnhide,
209
+ "data-position": s.position,
210
+ "data-hide-align": s.hideAlignment,
211
+ style: asStyle(s.themeStyles),
212
+ title: PANEL_LABELS.showCli,
213
+ onClick: () => controller.unhide(),
555
214
  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 })
215
+ /* @__PURE__ */ jsx3(Icon, { icon: HIDE_TAB_TERMINAL_ICON, className: "cli-panel-hide-tab-icon" }),
216
+ /* @__PURE__ */ jsx3("span", { className: "cli-panel-hide-tab-label", children: PANEL_LABELS.title }),
217
+ /* @__PURE__ */ jsx3(Icon, { icon: hideTabChevronIcon(s.position), className: "cli-panel-hide-tab-arrow" })
562
218
  ]
563
219
  }
564
220
  ),
565
221
  /* @__PURE__ */ jsxs(
566
222
  "div",
567
223
  {
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),
224
+ ref: setRootElement,
225
+ className: panelWrapperClass(s, props.className),
226
+ style: { ...asStyle(panelWrapperStyle(s)), ...props.style },
227
+ "data-position": s.position,
228
+ "data-resizable": String(s.resizable),
229
+ "data-closable": String(s.closable),
230
+ "data-hideable": String(s.hideable),
575
231
  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: [
232
+ /* @__PURE__ */ jsx3(PanelHeader, { controller, s }),
233
+ s.initialized && /* @__PURE__ */ jsxs("div", { className: "cli-panel-content", style: s.collapsed ? { display: "none" } : void 0, children: [
607
234
  /* @__PURE__ */ jsxs("div", { className: "cli-panel-tabs", children: [
608
- /* @__PURE__ */ jsx3("ul", { className: "cli-panel-tab-list", children: tabs.map((tab) => /* @__PURE__ */ jsx3(
235
+ /* @__PURE__ */ jsx3("ul", { className: "cli-panel-tab-list", children: s.tabs.map((tab) => /* @__PURE__ */ jsx3(
609
236
  "li",
610
237
  {
611
- className: `cli-panel-tab ${tab.id === activeTabId ? "active" : ""}`,
612
- onClick: () => selectTab(tab.id),
613
- onDoubleClick: () => startRename(tab.id),
238
+ className: panelTabClass(s, tab),
239
+ onClick: () => controller.selectTab(tab.id),
240
+ onDoubleClick: () => controller.startRename(tab.id),
614
241
  onContextMenu: (e) => {
615
242
  e.preventDefault();
616
243
  e.stopPropagation();
617
- setContextMenu({ visible: true, x: e.clientX, y: e.clientY, tabId: tab.id });
244
+ controller.openContextMenu(tab.id, e.clientX, e.clientY);
618
245
  },
619
246
  children: tab.isEditing ? /* @__PURE__ */ jsx3(
620
247
  "input",
@@ -622,57 +249,62 @@ function CliPanel({ options: optionsProp, modules: modulesProp, processors: proc
622
249
  className: "cli-panel-tab-rename-input",
623
250
  type: "text",
624
251
  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),
252
+ ref: focusAndSelect,
253
+ onKeyDown: (e) => handleRenameKey(controller, tab.id, e.key, e.currentTarget.value),
254
+ onBlur: (e) => controller.commitRename(tab.id, e.target.value),
631
255
  onClick: (e) => e.stopPropagation(),
632
256
  onDoubleClick: (e) => e.stopPropagation()
633
257
  }
634
258
  ) : /* @__PURE__ */ jsxs(Fragment2, { children: [
259
+ /* @__PURE__ */ jsx3("span", { className: panelTabDotClass(s, tab) }),
635
260
  /* @__PURE__ */ jsx3("span", { className: "cli-panel-tab-title", children: tab.title }),
636
261
  /* @__PURE__ */ jsx3(
637
262
  "button",
638
263
  {
639
264
  className: "cli-panel-tab-close-btn",
640
- title: "Close tab",
265
+ title: PANEL_LABELS.closeTab,
641
266
  onClick: (e) => {
642
267
  e.stopPropagation();
643
- closeTab(tab.id);
268
+ controller.closeTab(tab.id);
644
269
  },
645
- children: "\xD7"
270
+ children: CLOSE_GLYPH
646
271
  }
647
272
  )
648
273
  ] })
649
274
  },
650
275
  tab.id
651
276
  )) }),
652
- /* @__PURE__ */ jsx3("button", { className: "cli-panel-add-tab", title: "New terminal", onClick: addTab, children: "+" })
277
+ /* @__PURE__ */ jsx3("button", { className: "cli-panel-add-tab", title: PANEL_LABELS.newTerminal, onClick: () => controller.addTab(), children: "+" })
653
278
  ] }),
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(
279
+ /* @__PURE__ */ jsx3("div", { className: "cli-panel-instances", children: s.tabs.map((tab) => /* @__PURE__ */ jsx3("div", { className: "cli-panel-instance", style: { display: tab.id === s.activeTabId ? void 0 : "none" }, children: /* @__PURE__ */ jsx3("div", { className: panesContainerClass(s), children: tab.panes.map((pane, i) => /* @__PURE__ */ jsxs(React3.Fragment, { children: [
280
+ i > 0 && /* @__PURE__ */ jsx3(
657
281
  "div",
658
282
  {
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);
283
+ className: "cli-panel-pane-divider",
284
+ onMouseDown: (e) => {
285
+ e.preventDefault();
286
+ controller.startPaneResize(tab.id, i - 1, e.clientX, panesContainerWidth(e.target));
664
287
  },
288
+ children: /* @__PURE__ */ jsx3("div", { className: "cli-panel-pane-divider-grip" })
289
+ }
290
+ ),
291
+ /* @__PURE__ */ jsxs(
292
+ "div",
293
+ {
294
+ className: paneClass(s, tab.id, pane.id),
295
+ style: paneStyle(pane.widthPercent),
296
+ onClick: () => controller.activatePane(tab.id, pane.id),
665
297
  children: [
666
298
  tab.panes.length > 1 && /* @__PURE__ */ jsx3(
667
299
  "button",
668
300
  {
669
301
  className: "cli-panel-pane-close-btn",
302
+ title: PANEL_LABELS.closePane,
670
303
  onClick: (e) => {
671
304
  e.stopPropagation();
672
- closePane(tab.id, pane.id);
305
+ controller.closePane(pane.id);
673
306
  },
674
- title: "Close pane",
675
- children: "\xD7"
307
+ children: CLOSE_GLYPH
676
308
  }
677
309
  ),
678
310
  /* @__PURE__ */ jsx3(CliContext.Provider, { value: { engine: null }, children: /* @__PURE__ */ jsx3(
@@ -680,10 +312,9 @@ function CliPanel({ options: optionsProp, modules: modulesProp, processors: proc
680
312
  {
681
313
  options,
682
314
  modules,
683
- processors,
684
315
  services,
685
316
  snapshot: pane.snapshot,
686
- onReady: (engine) => engineMapRef.current.set(pane.id, engine)
317
+ onReady: (engine) => controller.registerEngine(tab.id, pane.id, engine)
687
318
  }
688
319
  ) })
689
320
  ]
@@ -694,91 +325,134 @@ function CliPanel({ options: optionsProp, modules: modulesProp, processors: proc
694
325
  ]
695
326
  }
696
327
  ),
697
- contextMenu.visible && /* @__PURE__ */ jsxs(
328
+ s.contextMenu.visible && /* @__PURE__ */ jsx3("div", { className: "cli-panel-context-menu", style: asStyle(contextMenuStyle(s)), onClick: (e) => e.stopPropagation(), children: contextMenuItems(s).map((item) => /* @__PURE__ */ jsxs(React3.Fragment, { children: [
329
+ item.separatorBefore && /* @__PURE__ */ jsx3("div", { className: "cli-panel-context-menu-separator" }),
330
+ /* @__PURE__ */ jsx3(
331
+ "button",
332
+ {
333
+ className: joinClasses("cli-panel-context-menu-item", item.destructive && "destructive"),
334
+ disabled: item.disabled,
335
+ onClick: () => runContextMenuAction(controller, item.action),
336
+ children: item.label
337
+ }
338
+ )
339
+ ] }, item.action)) }),
340
+ /* @__PURE__ */ jsx3(StatusDropdown, { s })
341
+ ] });
342
+ });
343
+ function StatusDropdown({ s }) {
344
+ const dropdown = openStatusDropdown(s);
345
+ if (!dropdown) return null;
346
+ return /* @__PURE__ */ jsxs("div", { className: "cli-panel-services-dropdown", style: asStyle(dropdown.style), onClick: (e) => e.stopPropagation(), children: [
347
+ /* @__PURE__ */ jsx3("div", { className: "cli-panel-services-dropdown-header", children: dropdown.header }),
348
+ /* @__PURE__ */ jsx3("div", { className: "cli-panel-services-dropdown-list", children: dropdown.rows.map((row) => /* @__PURE__ */ jsxs("div", { className: "cli-panel-services-dropdown-item", children: [
349
+ /* @__PURE__ */ jsx3("span", { className: row.dotClass }),
350
+ /* @__PURE__ */ jsxs("div", { className: "cli-panel-svc-info", children: [
351
+ /* @__PURE__ */ jsx3("span", { className: "cli-panel-svc-name", children: row.name }),
352
+ row.description && /* @__PURE__ */ jsx3("span", { className: "cli-panel-svc-desc", children: row.description })
353
+ ] }),
354
+ dropdown.kind === "servers" ? /* @__PURE__ */ jsxs("div", { className: "cli-panel-srv-meta", children: [
355
+ /* @__PURE__ */ jsx3("span", { className: "cli-panel-svc-status", children: row.status }),
356
+ row.meta.map((m) => /* @__PURE__ */ jsx3("span", { className: "cli-panel-svc-desc", children: m }, m))
357
+ ] }) : /* @__PURE__ */ jsx3("span", { className: "cli-panel-svc-status", children: row.status })
358
+ ] }, row.key)) }),
359
+ dropdown.rows.length === 0 && /* @__PURE__ */ jsx3("div", { className: "cli-panel-services-dropdown-empty", children: dropdown.emptyText })
360
+ ] });
361
+ }
362
+ function PanelHeader({ controller, s }) {
363
+ const indicators = statusIndicators(s);
364
+ const openDropdown = (e, dropdown) => {
365
+ if (!dropdown) return;
366
+ e.stopPropagation();
367
+ if (dropdown === "services") controller.toggleServicesDropdown(e.currentTarget);
368
+ else controller.toggleServersDropdown(e.currentTarget);
369
+ };
370
+ return /* @__PURE__ */ jsxs("div", { className: "cli-panel-header", children: [
371
+ /* @__PURE__ */ jsx3(
698
372
  "div",
699
373
  {
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
- ]
374
+ className: "cli-panel-resize-bar",
375
+ onMouseDown: (e) => {
376
+ if (!s.resizable) return;
377
+ e.preventDefault();
378
+ controller.startPanelResize(e.clientX, e.clientY);
379
+ },
380
+ children: /* @__PURE__ */ jsx3("div", { className: "cli-panel-resize-grip" })
758
381
  }
759
- )
382
+ ),
383
+ /* @__PURE__ */ jsxs("div", { className: "cli-panel-header-content", children: [
384
+ /* @__PURE__ */ jsxs("p", { className: "cli-panel-title", children: [
385
+ /* @__PURE__ */ jsx3("span", { className: "cli-panel-title-icon", children: /* @__PURE__ */ jsx3(Icon, { icon: TERMINAL_ICON }) }),
386
+ PANEL_LABELS.title
387
+ ] }),
388
+ s.showStatusIndicators && /* @__PURE__ */ jsx3("div", { className: "cli-panel-status-indicators", children: indicators.map((item) => /* @__PURE__ */ jsxs(
389
+ "span",
390
+ {
391
+ className: joinClasses("cli-panel-status-item", item.itemClass),
392
+ onClick: item.dropdown ? (e) => openDropdown(e, item.dropdown) : void 0,
393
+ children: [
394
+ item.dotClass && /* @__PURE__ */ jsx3("span", { className: `cli-panel-status-dot ${item.dotClass}` }),
395
+ item.icon && /* @__PURE__ */ jsx3("span", { className: joinClasses("cli-panel-status-icon", item.iconClass), children: item.icon }),
396
+ /* @__PURE__ */ jsx3("span", { className: "cli-panel-status-label", children: item.label })
397
+ ]
398
+ },
399
+ item.key
400
+ )) }),
401
+ /* @__PURE__ */ jsx3("div", { className: "cli-panel-status-indicators-compact", children: indicators.map((item) => /* @__PURE__ */ jsxs(
402
+ "span",
403
+ {
404
+ className: joinClasses("cli-panel-status-compact-item", item.compactClass),
405
+ title: item.title,
406
+ onClick: item.dropdown ? (e) => openDropdown(e, item.dropdown) : void 0,
407
+ children: [
408
+ item.compactDotClass && /* @__PURE__ */ jsx3("span", { className: item.compactDotClass }),
409
+ item.dotClass && /* @__PURE__ */ jsx3("span", { className: `cli-panel-status-dot ${item.dotClass}` }),
410
+ item.icon && /* @__PURE__ */ jsx3("span", { className: joinClasses("cli-panel-status-icon", item.iconClass), children: item.icon })
411
+ ]
412
+ },
413
+ item.key
414
+ )) }),
415
+ /* @__PURE__ */ jsxs("div", { className: "cli-panel-action-buttons", children: [
416
+ /* @__PURE__ */ jsxs("div", { className: "cli-panel-btn-position-wrapper", onClick: (e) => e.stopPropagation(), children: [
417
+ /* @__PURE__ */ jsx3("button", { className: "cli-panel-btn cli-panel-btn-position", title: PANEL_LABELS.movePanel, onClick: () => controller.togglePositionDropdown(), children: /* @__PURE__ */ jsx3(Icon, { icon: positionIcon(s.position) }) }),
418
+ s.positionDropdownOpen && /* @__PURE__ */ jsx3("div", { className: "cli-panel-position-dropdown", children: positionOptions(s).map((opt) => /* @__PURE__ */ jsxs("button", { className: opt.className, onClick: () => controller.setPosition(opt.position), children: [
419
+ /* @__PURE__ */ jsx3(Icon, { icon: positionIcon(opt.position, 16) }),
420
+ opt.label
421
+ ] }, opt.position)) })
422
+ ] }),
423
+ s.hideable && /* @__PURE__ */ jsx3("button", { className: "cli-panel-btn cli-panel-btn-hide", title: PANEL_LABELS.hide, onClick: () => controller.hide(), children: /* @__PURE__ */ jsx3(Icon, { icon: HIDE_ICON }) }),
424
+ /* @__PURE__ */ jsx3(
425
+ "button",
426
+ {
427
+ className: "cli-panel-btn",
428
+ title: s.maximized ? PANEL_LABELS.restore : PANEL_LABELS.maximize,
429
+ disabled: s.collapsed,
430
+ onClick: () => controller.toggleMaximize(),
431
+ children: /* @__PURE__ */ jsx3(Icon, { icon: s.maximized ? RESTORE_ICON : MAXIMIZE_ICON })
432
+ }
433
+ ),
434
+ /* @__PURE__ */ jsx3("button", { className: "cli-panel-btn", title: s.collapsed ? PANEL_LABELS.expand : PANEL_LABELS.collapse, onClick: () => controller.toggle(), children: /* @__PURE__ */ jsx3(Icon, { icon: collapseChevronIcon(s.position, s.collapsed) }) }),
435
+ s.closable && /* @__PURE__ */ jsx3("button", { className: "cli-panel-btn cli-panel-btn-close", title: PANEL_LABELS.close, onClick: () => controller.close(), children: /* @__PURE__ */ jsx3(Icon, { icon: CLOSE_ICON }) })
436
+ ] })
437
+ ] }),
438
+ /* @__PURE__ */ jsx3("div", { className: glowLineClass(s) })
760
439
  ] });
761
440
  }
762
441
 
763
442
  // src/CliProvider.tsx
764
- import { useRef as useRef4 } from "react";
443
+ import { useRef as useRef3 } from "react";
444
+ import { resolveCliHostConfig as resolveCliHostConfig3 } from "@qodalis/cli";
765
445
  import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
766
446
  function CliProvider({
767
447
  modules,
768
- processors,
769
448
  options,
770
449
  services,
771
450
  children,
772
451
  style
773
452
  }) {
774
453
  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
- });
454
+ const containerRef = useRef3(null);
455
+ const engine = useCliEngine(containerRef, resolveCliHostConfig3({ modules, options, services }, config));
782
456
  return /* @__PURE__ */ jsxs2(CliContext.Provider, { value: { engine }, children: [
783
457
  /* @__PURE__ */ jsx4("div", { ref: containerRef, style: { height: "100%", ...style } }),
784
458
  children
@@ -786,12 +460,12 @@ function CliProvider({
786
460
  }
787
461
 
788
462
  // src/index.tsx
789
- import { CliEngine as CliEngine2 } from "@qodalis/cli";
463
+ import { CliEngine as CliEngine4 } from "@qodalis/cli";
790
464
  export {
791
465
  Cli,
792
466
  CliConfigProvider,
793
467
  CliContext,
794
- CliEngine2 as CliEngine,
468
+ CliEngine4 as CliEngine,
795
469
  CliPanel,
796
470
  CliProvider,
797
471
  useCli,