@qodalis/react-cli 2.0.0-beta.10 → 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 CHANGED
@@ -203,562 +203,690 @@ function PositionIcon({ position, size = 20 }) {
203
203
  fillRect
204
204
  ] });
205
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;
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;
287
291
  });
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);
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);
297
313
  }
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) {
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) {
336
326
  setInitialized(false);
337
- setCollapsed(true);
327
+ updateCollapsed(true);
338
328
  return [];
339
329
  }
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;
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);
358
334
  }
359
- return curr;
335
+ return next;
360
336
  });
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
- }
337
+ props.onTabClosed?.({ tabId: id });
338
+ }, [updateCollapsed, updateActiveTabId, props.onTabClosed]);
339
+ const selectTab = useCallback((id) => {
340
+ updateActiveTabId(id);
450
341
  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;
342
+ const tab = prev.find((t) => t.id === id);
343
+ if (tab && tab.panes.length > 0) {
344
+ setActivePaneId(tab.panes[0].id);
456
345
  }
457
- return next;
346
+ return prev;
458
347
  });
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) {
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) {
493
519
  if (isHorizontal) {
494
520
  prevWidthRef.current = panelWidth;
495
- setPanelWidth(window.innerWidth);
521
+ updateWidth(window.innerWidth);
496
522
  } else {
497
523
  prevHeightRef.current = panelHeight;
498
- setPanelHeight(window.innerHeight);
524
+ updateHeight(window.innerHeight);
499
525
  }
500
526
  } else {
501
527
  if (isHorizontal) {
502
- setPanelWidth(prevWidthRef.current);
528
+ updateWidth(prevWidthRef.current);
503
529
  } else {
504
- setPanelHeight(prevHeightRef.current);
530
+ updateHeight(prevHeightRef.current);
505
531
  }
506
532
  }
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
- )) })
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"
598
708
  ] }),
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(
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(
637
713
  "button",
638
714
  {
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: "+" })
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
+ ] })
653
731
  ] }),
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",
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",
668
747
  {
669
- className: "cli-panel-pane-close-btn",
670
- onClick: (e) => {
671
- e.stopPropagation();
672
- closePane(tab.id, pane.id);
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 })));
673
755
  },
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)
756
+ onBlur: (e) => commitRename(tab.id, e.target.value),
757
+ onClick: (e) => e.stopPropagation(),
758
+ onDoubleClick: (e) => e.stopPropagation()
687
759
  }
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
- }
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
+ );
762
890
 
763
891
  // src/CliProvider.tsx
764
892
  import { useRef as useRef4 } from "react";