@xgspz/cli-ui 0.0.0-stage → 0.6.14

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.
Files changed (169) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/README.md +512 -2
  3. package/dist/cli-ui-embed-D-3DvyTN.js +50279 -0
  4. package/dist/cli-ui-index-C34qFb4u.js +11209 -0
  5. package/dist/cli-ui-jszip.min-BObcE2nn.js +26 -0
  6. package/dist/cli-ui-jszip.min-DojbZPiE.js +2324 -0
  7. package/dist/cli-ui-xlsx-JVLtx0qR.js +93 -0
  8. package/dist/cli-ui.css +32 -0
  9. package/dist/cli-ui.js +17 -0
  10. package/package.json +60 -4
  11. package/src/board/BrowserPane.tsx +969 -0
  12. package/src/board/ChangesPane.tsx +185 -0
  13. package/src/board/CliBoard.tsx +484 -0
  14. package/src/board/FileViewer.tsx +721 -0
  15. package/src/board/FilesPane.tsx +262 -0
  16. package/src/board/HtmlReadingPreview.tsx +52 -0
  17. package/src/board/MarkdownPreview.tsx +134 -0
  18. package/src/board/PanelControls.tsx +40 -0
  19. package/src/board/blocks.tsx +97 -0
  20. package/src/board/context.ts +65 -0
  21. package/src/board/diffLines.ts +46 -0
  22. package/src/board/fileKind.ts +250 -0
  23. package/src/board/fileOps.ts +85 -0
  24. package/src/board/fileScope.ts +32 -0
  25. package/src/board/fileSync.ts +351 -0
  26. package/src/board/fileSyncCache.ts +250 -0
  27. package/src/board/fileSyncProcessor.ts +72 -0
  28. package/src/board/fileSyncQueue.ts +56 -0
  29. package/src/board/fileSyncTransport.ts +85 -0
  30. package/src/board/fileWatch.ts +211 -0
  31. package/src/board/folderDownload.ts +211 -0
  32. package/src/board/frameQuote.ts +52 -0
  33. package/src/board/htmlReading.ts +148 -0
  34. package/src/board/panels.ts +33 -0
  35. package/src/board/textLoad.ts +76 -0
  36. package/src/board/useBoardUpload.tsx +51 -0
  37. package/src/board/useBoardVisibility.ts +35 -0
  38. package/src/board/workspace.ts +200 -0
  39. package/src/board/zip.ts +85 -0
  40. package/src/chat/CliChat.tsx +932 -0
  41. package/src/chat/Composer.tsx +239 -0
  42. package/src/chat/GroupChat.tsx +113 -0
  43. package/src/chat/RunControls.tsx +196 -0
  44. package/src/chat/configuration.ts +15 -0
  45. package/src/chat/draft.ts +49 -0
  46. package/src/chat/export.ts +34 -0
  47. package/src/chat/queueEdit.ts +27 -0
  48. package/src/chat/useExecutionConfig.ts +93 -0
  49. package/src/components.tsx +13 -0
  50. package/src/core/blocks.ts +205 -0
  51. package/src/core/callbacks.ts +8 -0
  52. package/src/core/client.ts +822 -0
  53. package/src/core/config.ts +252 -0
  54. package/src/core/conversation.ts +577 -0
  55. package/src/core/documentUpload.ts +196 -0
  56. package/src/core/execution.ts +24 -0
  57. package/src/core/id.ts +15 -0
  58. package/src/core/identity.ts +21 -0
  59. package/src/core/provider.tsx +141 -0
  60. package/src/core/sha256.ts +21 -0
  61. package/src/core/sse.ts +51 -0
  62. package/src/core/store.ts +1103 -0
  63. package/src/core/subagents.ts +78 -0
  64. package/src/core/textDecode.ts +24 -0
  65. package/src/core/types.ts +505 -0
  66. package/src/core/xlsx.ts +142 -0
  67. package/src/elements.tsx +376 -0
  68. package/src/embed.ts +56 -0
  69. package/src/index.ts +56 -0
  70. package/src/layout/CliSplit.tsx +53 -0
  71. package/src/layout/split.ts +251 -0
  72. package/src/msglist/CliMsgList.tsx +422 -0
  73. package/src/operator/branchFlow.ts +35 -0
  74. package/src/operator/index.ts +9 -0
  75. package/src/operator/liveStream.ts +1 -0
  76. package/src/operator/node/AgentConnect.tsx +619 -0
  77. package/src/operator/node/AppearancePanel.tsx +35 -0
  78. package/src/operator/node/BranchHistory.tsx +67 -0
  79. package/src/operator/node/BranchNavigator.tsx +36 -0
  80. package/src/operator/node/BrowserPane.tsx +128 -0
  81. package/src/operator/node/BrowserPopup.tsx +22 -0
  82. package/src/operator/node/Chat.tsx +538 -0
  83. package/src/operator/node/CommandPalette.tsx +213 -0
  84. package/src/operator/node/Composer.tsx +316 -0
  85. package/src/operator/node/InsightsView.tsx +831 -0
  86. package/src/operator/node/Layout.tsx +192 -0
  87. package/src/operator/node/Markdown.tsx +61 -0
  88. package/src/operator/node/Modal.tsx +179 -0
  89. package/src/operator/node/NeedsYouMenu.tsx +54 -0
  90. package/src/operator/node/OnboardingWizard.tsx +248 -0
  91. package/src/operator/node/Orchestrator.tsx +644 -0
  92. package/src/operator/node/ProjectLanding.tsx +86 -0
  93. package/src/operator/node/ProjectsColumn.tsx +137 -0
  94. package/src/operator/node/ProviderAuth.tsx +559 -0
  95. package/src/operator/node/RunControls.tsx +2 -0
  96. package/src/operator/node/RunSwitch.tsx +113 -0
  97. package/src/operator/node/Services.tsx +245 -0
  98. package/src/operator/node/SessionRail.tsx +299 -0
  99. package/src/operator/node/SessionView.tsx +292 -0
  100. package/src/operator/node/SettingsView.tsx +690 -0
  101. package/src/operator/node/TaskBoard.tsx +352 -0
  102. package/src/operator/node/TaskChanges.tsx +526 -0
  103. package/src/operator/node/TaskHero.tsx +50 -0
  104. package/src/operator/node/TasksColumn.tsx +200 -0
  105. package/src/operator/node/Terminal.tsx +143 -0
  106. package/src/operator/node/Transcript.tsx +485 -0
  107. package/src/operator/node/ViewFile.tsx +79 -0
  108. package/src/operator/node/Welcome.tsx +101 -0
  109. package/src/operator/node/WorktreePrune.tsx +333 -0
  110. package/src/operator/node/agents.ts +58 -0
  111. package/src/operator/node/api.ts +57 -0
  112. package/src/operator/node/boardBackend.ts +63 -0
  113. package/src/operator/node/boardEvents.ts +40 -0
  114. package/src/operator/node/boardInteraction.ts +15 -0
  115. package/src/operator/node/boardState.ts +74 -0
  116. package/src/operator/node/boardWorkspace.ts +15 -0
  117. package/src/operator/node/boundedPoll.ts +47 -0
  118. package/src/operator/node/branchFlow.ts +1 -0
  119. package/src/operator/node/composerDraft.ts +78 -0
  120. package/src/operator/node/fileLinks.ts +81 -0
  121. package/src/operator/node/format.ts +292 -0
  122. package/src/operator/node/github.tsx +232 -0
  123. package/src/operator/node/icons.tsx +88 -0
  124. package/src/operator/node/liveStream.ts +323 -0
  125. package/src/operator/node/modals.tsx +766 -0
  126. package/src/operator/node/navHistory.ts +70 -0
  127. package/src/operator/node/persist.ts +26 -0
  128. package/src/operator/node/protocol/agents/optIn.ts +9 -0
  129. package/src/operator/node/protocol/agents/types.ts +394 -0
  130. package/src/operator/node/protocol/approvalFailure.ts +77 -0
  131. package/src/operator/node/protocol/authFailure.ts +66 -0
  132. package/src/operator/node/protocol/customModels.ts +23 -0
  133. package/src/operator/node/protocol/events.ts +13 -0
  134. package/src/operator/node/protocol/features.ts +70 -0
  135. package/src/operator/node/protocol/internalUsage.ts +1 -0
  136. package/src/operator/node/protocol/promptLimits.ts +52 -0
  137. package/src/operator/node/protocol/storageRetention.ts +13 -0
  138. package/src/operator/node/protocol/types.ts +335 -0
  139. package/src/operator/node/protocol/usageLimit.ts +72 -0
  140. package/src/operator/node/shared.tsx +89 -0
  141. package/src/operator/node/transcriptModel.ts +129 -0
  142. package/src/operator/node/types.ts +305 -0
  143. package/src/operator/node/useGlobalEvents.ts +97 -0
  144. package/src/operator/node/useOrchestrator.ts +697 -0
  145. package/src/operator/node/usePrefs.ts +114 -0
  146. package/src/operator/node/useRecaps.ts +102 -0
  147. package/src/operator/node/useTaskStream.ts +286 -0
  148. package/src/operator.base.css +1949 -0
  149. package/src/operator.css +1949 -0
  150. package/src/styles.css +579 -0
  151. package/src/ui/ErrorBoundary.tsx +38 -0
  152. package/src/ui/Markdown.tsx +60 -0
  153. package/src/ui/Popover.tsx +95 -0
  154. package/src/ui/QueuedMessages.tsx +110 -0
  155. package/src/ui/ReferenceCard.tsx +28 -0
  156. package/src/ui/SubagentChat.tsx +233 -0
  157. package/src/ui/TextBlock.tsx +51 -0
  158. package/src/ui/Transcript.tsx +435 -0
  159. package/src/ui/UploadProgress.tsx +46 -0
  160. package/src/ui/WorkspaceDrawer.tsx +64 -0
  161. package/src/ui/WorkspaceTabs.tsx +60 -0
  162. package/src/ui/fileLinks.ts +52 -0
  163. package/src/ui/format.ts +103 -0
  164. package/src/ui/icons.tsx +92 -0
  165. package/src/ui/reference.ts +40 -0
  166. package/src/ui/shared.tsx +38 -0
  167. package/src/ui/transcriptModel.ts +151 -0
  168. package/src/ui/useChatScrollbar.ts +29 -0
  169. package/src/ui/useComposerAutosize.ts +54 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * 可拖动分栏(React 版)。每个子元素是一栏,栏之间可以左右拖动改宽度(规则见 split.ts)。
3
+ *
4
+ * <CliCenterProvider baseUrl="/api/center" namespace="strategy">
5
+ * <CliSplit storageKey="strategy" panes={[{ size: 300, min: 220, max: 480 }, { min: 360 }, { size: "42%", min: 320 }]}>
6
+ * <CliMsgList />
7
+ * <CliChat />
8
+ * <CliBoard />
9
+ * </CliSplit>
10
+ * </CliCenterProvider>
11
+ *
12
+ * 三个组件在同一个 Provider 里照常联动(列表选中 → 对话和看板跟着切;对话里点文件 → 看板打开),分栏只管宽度。
13
+ */
14
+ import { Children, useEffect, useRef, type CSSProperties, type ReactNode } from "react";
15
+ import { attachSplit, type SplitHandle, type SplitPane } from "./split";
16
+
17
+ export type CliSplitProps = {
18
+ children: ReactNode;
19
+ /** 每栏的初始宽度 / 最小 / 最大。写了 size 的是定宽栏,没写的占剩余宽度。缺省:第一栏 300px、最后一栏 42%、中间占剩余。 */
20
+ panes?: SplitPane[];
21
+ /** 记住拖好的宽度(localStorage)。 */
22
+ storageKey?: string;
23
+ onResize?: (sizes: (number | null)[]) => void;
24
+ className?: string;
25
+ style?: CSSProperties;
26
+ };
27
+
28
+ const defaultPanes = (n: number): SplitPane[] =>
29
+ n <= 1 ? [{}] : n === 2 ? [{ size: 300, min: 220, max: 520 }, { min: 360 }]
30
+ : Array.from({ length: n }, (_, i) => (i === 0 ? { size: 300, min: 220, max: 520 } : i === n - 1 ? { size: "42%", min: 320 } : { min: 360 }));
31
+
32
+ export function CliSplit({ children, panes, storageKey, onResize, className, style }: CliSplitProps) {
33
+ const ref = useRef<HTMLDivElement>(null);
34
+ const handle = useRef<SplitHandle | null>(null);
35
+ const items = Children.toArray(children).filter(Boolean);
36
+ const onResizeRef = useRef(onResize);
37
+ onResizeRef.current = onResize;
38
+ const options = { panes: panes ?? defaultPanes(items.length), storageKey, onResize: (s: (number | null)[]) => onResizeRef.current?.(s) };
39
+ const sig = JSON.stringify([options.panes, storageKey, items.length]);
40
+
41
+ useEffect(() => {
42
+ if (!ref.current) return;
43
+ handle.current = attachSplit(ref.current, options);
44
+ return () => { handle.current?.destroy(); handle.current = null; };
45
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
46
+ useEffect(() => { handle.current?.update(options); }, [sig]); // eslint-disable-line react-hooks/exhaustive-deps
47
+
48
+ return (
49
+ <div ref={ref} className={`cli-split ${className ?? ""}`} style={style}>
50
+ {items.map((child, i) => <div key={(child as { key?: string | null }).key ?? i} className="cli-split-pane">{child}</div>)}
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1,251 @@
1
+ /**
2
+ * 可拖动分栏:把一个容器里横向排的几栏(列表 / 对话 / 看板,或宿主自己的栏)之间加上拖动条,左右拖改宽度。
3
+ * 不依赖 React:React 版 CliSplit 和 Web Component 版 <cli-split> 都用它。
4
+ *
5
+ * attachSplit(container, { panes: [{ size: 300, min: 220, max: 480 }, { min: 360 }, { size: "42%", min: 320 }], storageKey: "strategy" })
6
+ *
7
+ * 规则:
8
+ * · 写了 size 的栏是定宽(px 或百分比,未手动调整的百分比随容器宽度变化);没写 size 的栏占剩下的宽度(只能有一个,没有就是最后一栏)。
9
+ * · 拖动条改它旁边定宽的那一栏;任何一栏都不会小于自己的 min,占剩余宽度的那栏也一样。
10
+ * · 双击拖动条:那一栏回到初始宽度。键盘:拖动条能聚焦,←/→ 每次 16px(按住 Shift 64px)。
11
+ * · 子元素根节点带 collapsed 类(cli-board 收起时):那一栏变成 40px 窄条(--cli-split-collapsed),旁边的拖动条隐藏;展开后回到原来的宽度。
12
+ * · 写了 storageKey:拖好的宽度记在 localStorage(cli-ui:split:<storageKey>),下次打开还是这个宽度。
13
+ * · 窗口变窄时定宽栏按超过 min 的空间比例让出宽度;各栏 min 之和能放下时,保证占剩余宽度的那栏不小于它的 min。
14
+ */
15
+ export type SplitPane = { size?: number | `${number}%`; min?: number; max?: number };
16
+ export type SplitOptions = {
17
+ panes?: SplitPane[];
18
+ storageKey?: string;
19
+ /** 拖完(松手 / 键盘 / 双击)后的各栏宽度,占剩余宽度的那栏是 null。 */
20
+ onResize?: (sizes: (number | null)[]) => void;
21
+ };
22
+ export type SplitHandle = { update: (options: SplitOptions) => void; reset: () => void; destroy: () => void };
23
+
24
+ const DEFAULT_MIN = 160;
25
+ const STEP = 16;
26
+
27
+ export function attachSplit(container: HTMLElement, initial: SplitOptions = {}): SplitHandle {
28
+ let options = initial;
29
+ // Only user overrides are pixels. null keeps the configured responsive default.
30
+ // Old persisted numeric arrays remain valid user preferences.
31
+ let sizes: (number | null)[] = [];
32
+ let gutters: HTMLElement[] = [];
33
+ let panes: HTMLElement[] = [];
34
+ let dragging = false;
35
+
36
+ container.classList.add("cli-split");
37
+
38
+ const key = () => (options.storageKey ? `cli-ui:split:${options.storageKey}` : null);
39
+ const load = (): (number | null)[] | null => {
40
+ const k = key();
41
+ if (!k) return null;
42
+ try { const v = JSON.parse(localStorage.getItem(k) ?? "null"); return Array.isArray(v) ? v : null; } catch { return null; }
43
+ };
44
+ const save = () => {
45
+ const k = key();
46
+ if (k) try { localStorage.setItem(k, JSON.stringify(sizes)); } catch { /* 存不下就不记 */ }
47
+ };
48
+
49
+ const spec = (i: number): SplitPane => options.panes?.[i] ?? {};
50
+ const preferredFlexIndex = () => {
51
+ const i = panes.findIndex((_, n) => spec(n).size === undefined);
52
+ return i >= 0 ? i : panes.length - 1;
53
+ };
54
+ const minOf = (i: number) => Math.max(0, spec(i).min ?? DEFAULT_MIN);
55
+ const clamp = (i: number, w: number) => Math.round(Math.min(Math.max(minOf(i), spec(i).max ?? Infinity), Math.max(minOf(i), w)));
56
+ const contentWidth = () => {
57
+ const style = getComputedStyle(container);
58
+ return Math.max(0, container.clientWidth - (parseFloat(style.paddingLeft) || 0) - (parseFloat(style.paddingRight) || 0));
59
+ };
60
+ const initialSize = (i: number): number | null => {
61
+ if (i === preferredFlexIndex()) return null;
62
+ const s = spec(i).size;
63
+ if (typeof s === "string") return clamp(i, (parseFloat(s) / 100) * (contentWidth() || 1200));
64
+ return clamp(i, s ?? 320);
65
+ };
66
+ const isCollapsed = (pane: HTMLElement) => !!pane.firstElementChild?.classList.contains("collapsed");
67
+ const isVisible = (pane: HTMLElement) => !pane.hidden && getComputedStyle(pane).display !== "none";
68
+ const flexIndex = () => {
69
+ const preferred = preferredFlexIndex();
70
+ if (panes[preferred] && isVisible(panes[preferred]) && !isCollapsed(panes[preferred])) return preferred;
71
+ // A host may hide whole panes on mobile. A remaining visible pane fills the
72
+ // space without turning its default width into a permanent pixel override.
73
+ for (let i = panes.length - 1; i >= 0; i--) if (isVisible(panes[i]) && !isCollapsed(panes[i])) return i;
74
+ return -1;
75
+ };
76
+ const desiredSizes = () => panes.map((_, i) => i === preferredFlexIndex() ? null : clamp(i, sizes[i] ?? initialSize(i)!));
77
+
78
+ /** 定宽栏放进容器:总宽超了就按比例收,给占剩余宽度的那栏留出它的 min。 */
79
+ function fitted(flex: number): (number | null)[] {
80
+ const shown = desiredSizes();
81
+ const fixed = panes.flatMap((p, i) => i !== flex && isVisible(p) && !isCollapsed(p) ? [i] : []);
82
+ const collapsedW = panes.reduce((sum, p) => sum + (isVisible(p) && isCollapsed(p) ? p.getBoundingClientRect().width : 0), 0);
83
+ const gutterW = gutters.reduce((sum, g) => {
84
+ if (g.hidden) return sum;
85
+ const style = getComputedStyle(g);
86
+ const width = g.getBoundingClientRect().width || parseFloat(style.flexBasis) || 0;
87
+ return sum + Math.max(0, width + (parseFloat(style.marginLeft) || 0) + (parseFloat(style.marginRight) || 0));
88
+ }, 0);
89
+ const room = contentWidth() - (flex < 0 ? 0 : minOf(flex)) - collapsedW - gutterW;
90
+ const sum = fixed.reduce((total, i) => total + shown[i]!, 0);
91
+ if (!container.clientWidth || sum <= room) return shown;
92
+ const minimum = fixed.reduce((total, i) => total + minOf(i), 0);
93
+ // Scaling whole widths can hit one pane's minimum and still overflow the
94
+ // flexible pane. Allocate only the space above each minimum instead.
95
+ const ratio = sum > minimum ? Math.max(0, room - minimum) / (sum - minimum) : 0;
96
+ for (const i of fixed) shown[i] = minOf(i) + Math.floor((shown[i]! - minOf(i)) * ratio);
97
+ return shown;
98
+ }
99
+
100
+ function apply() {
101
+ const flex = flexIndex();
102
+ // Set narrow strips before measuring: their previous expanded width must
103
+ // not be subtracted from the available space on the first collapse frame.
104
+ for (const pane of panes) {
105
+ if (!isCollapsed(pane)) continue;
106
+ pane.style.flex = "0 0 var(--cli-split-collapsed, 40px)";
107
+ pane.style.width = "";
108
+ pane.style.minWidth = "";
109
+ }
110
+ gutters.forEach((g, i) => { g.hidden = !isVisible(panes[i]) || !isVisible(panes[i + 1]) || isCollapsed(panes[i]) || isCollapsed(panes[i + 1]); });
111
+ const shown = fitted(flex);
112
+ panes.forEach((pane, i) => {
113
+ pane.classList.add("cli-split-pane");
114
+ if (isCollapsed(pane)) return;
115
+ if (i === flex) { pane.style.flex = "1 1 0"; pane.style.width = ""; pane.style.minWidth = `${minOf(i)}px`; return; }
116
+ // Hidden preferred flex panes keep their flex role for when they return.
117
+ if (shown[i] === null) { pane.style.flex = "1 1 0"; pane.style.width = ""; pane.style.minWidth = `${minOf(i)}px`; return; }
118
+ pane.style.flex = `0 0 ${shown[i]}px`;
119
+ pane.style.width = `${shown[i]}px`;
120
+ pane.style.minWidth = "0";
121
+ });
122
+ gutters.forEach((g, i) => {
123
+ g.setAttribute("aria-valuenow", String(Math.round(panes[sideOf(i)]?.getBoundingClientRect().width ?? 0)));
124
+ });
125
+ }
126
+
127
+ /** 拖动条 i(在第 i 栏和第 i+1 栏之间)改的是哪一栏:左边定宽改左边,否则改右边。 */
128
+ const sideOf = (gutter: number) => (gutter === flexIndex() ? gutter + 1 : gutter);
129
+
130
+ function resize(gutter: number, dx: number) {
131
+ if (gutters[gutter]?.hidden) return;
132
+ const target = sideOf(gutter);
133
+ const sign = target === gutter ? 1 : -1;
134
+ const flex = flexIndex();
135
+ if (flex < 0) return;
136
+ const current = panes[target].getBoundingClientRect().width;
137
+ // 占剩余宽度的那栏不能被挤到 min 以下。
138
+ const flexW = panes[flex].getBoundingClientRect().width;
139
+ const grow = Math.min(sign * dx, Math.max(0, flexW - minOf(flex)));
140
+ sizes[target] = clamp(target, current + grow);
141
+ apply();
142
+ }
143
+
144
+ function finish() { save(); options.onResize?.(desiredSizes()); }
145
+
146
+ function makeGutter(index: number) {
147
+ const g = document.createElement("div");
148
+ g.className = "cli-split-gutter";
149
+ g.tabIndex = 0;
150
+ g.setAttribute("role", "separator");
151
+ g.setAttribute("aria-orientation", "vertical");
152
+ g.setAttribute("aria-label", "拖动调整宽度");
153
+ g.title = "拖动调整宽度,双击恢复";
154
+ g.addEventListener("pointerdown", (e) => {
155
+ if (e.button !== 0) return;
156
+ e.preventDefault();
157
+ g.setPointerCapture(e.pointerId);
158
+ dragging = true;
159
+ container.classList.add("cli-split-dragging");
160
+ let last = e.clientX;
161
+ const move = (ev: PointerEvent) => { const dx = ev.clientX - last; if (!dx) return; last = ev.clientX; resize(index, dx); };
162
+ const up = () => {
163
+ dragging = false;
164
+ container.classList.remove("cli-split-dragging");
165
+ g.removeEventListener("pointermove", move);
166
+ g.removeEventListener("pointerup", up);
167
+ g.removeEventListener("pointercancel", up);
168
+ finish();
169
+ };
170
+ g.addEventListener("pointermove", move);
171
+ g.addEventListener("pointerup", up);
172
+ g.addEventListener("pointercancel", up);
173
+ });
174
+ g.addEventListener("dblclick", () => {
175
+ const target = sideOf(index);
176
+ sizes[target] = null;
177
+ apply();
178
+ finish();
179
+ });
180
+ g.addEventListener("keydown", (e) => {
181
+ if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
182
+ e.preventDefault();
183
+ resize(index, (e.key === "ArrowLeft" ? -1 : 1) * (e.shiftKey ? STEP * 4 : STEP));
184
+ finish();
185
+ });
186
+ return g;
187
+ }
188
+
189
+ /** 子元素变了(宿主增删栏):重新找栏、重新插拖动条。 */
190
+ function rebuild() {
191
+ for (const p of panes) ro?.unobserve?.(p);
192
+ for (const g of gutters) g.remove();
193
+ panes = [...container.children].filter((c): c is HTMLElement => c instanceof HTMLElement && !c.classList.contains("cli-split-gutter"));
194
+ const saved = load();
195
+ const flex = preferredFlexIndex();
196
+ sizes = panes.map((_, i) => {
197
+ if (i === flex) return null;
198
+ const keep = sizes[i];
199
+ if (typeof keep === "number" && Number.isFinite(keep)) return clamp(i, keep);
200
+ const s = saved && saved.length === panes.length ? saved[i] : null;
201
+ return typeof s === "number" && Number.isFinite(s) ? clamp(i, s) : null;
202
+ });
203
+ gutters = panes.slice(0, -1).map((_, i) => makeGutter(i));
204
+ gutters.forEach((g, i) => panes[i].after(g));
205
+ apply();
206
+ for (const p of panes) ro?.observe(p);
207
+ }
208
+
209
+ // 只看栏的增删、栏的根节点换了、根节点的 class 变了(收起 / 展开);栏里面的内容(流式输出)不看,不然每个字都要重排。
210
+ const observer = new MutationObserver((records) => {
211
+ if (dragging) return;
212
+ const structural = records.some((r) => r.type === "childList" && r.target === container
213
+ && [...r.addedNodes, ...r.removedNodes].some((n) => !(n instanceof HTMLElement && n.classList.contains("cli-split-gutter"))));
214
+ observer.disconnect();
215
+ if (structural) rebuild(); else apply();
216
+ observe();
217
+ });
218
+ const observe = () => {
219
+ observer.observe(container, { childList: true });
220
+ for (const p of panes) {
221
+ observer.observe(p, { childList: true });
222
+ if (p.firstElementChild) observer.observe(p.firstElementChild, { attributes: true, attributeFilter: ["class"] });
223
+ }
224
+ };
225
+ const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => { if (!dragging) apply(); }) : null;
226
+
227
+ rebuild();
228
+ observe();
229
+ ro?.observe(container);
230
+
231
+ return {
232
+ update(next) {
233
+ const keyChanged = next.storageKey !== options.storageKey;
234
+ options = next;
235
+ if (keyChanged) sizes = [];
236
+ observer.disconnect(); rebuild(); observe();
237
+ },
238
+ reset() {
239
+ sizes = panes.map(() => null);
240
+ apply();
241
+ finish();
242
+ },
243
+ destroy() {
244
+ observer.disconnect();
245
+ ro?.disconnect();
246
+ for (const g of gutters) g.remove();
247
+ for (const p of panes) { p.style.flex = ""; p.style.width = ""; p.style.minWidth = ""; p.classList.remove("cli-split-pane"); }
248
+ container.classList.remove("cli-split", "cli-split-dragging");
249
+ },
250
+ };
251
+ }