@adecore/ui 0.39.0 → 0.40.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.
Files changed (207) hide show
  1. package/dist/AccentSwatches.js +2 -3
  2. package/dist/Banner.js +1 -6
  3. package/dist/ChoiceCards.js +0 -5
  4. package/dist/ColorSwatch.js +1 -4
  5. package/dist/ColorWheelPicker.js +3 -6
  6. package/dist/DockShell.js +3 -5
  7. package/dist/EmptyState.js +0 -1
  8. package/dist/ErrorBoundary.js +1 -5
  9. package/dist/Field.js +2 -6
  10. package/dist/FileIcon.js +2 -4
  11. package/dist/Icon.js +1 -2
  12. package/dist/IconButton.js +1 -1
  13. package/dist/IconPicker.js +3 -7
  14. package/dist/Input.js +1 -2
  15. package/dist/Kbd.js +1 -3
  16. package/dist/LazyBoundary.js +2 -3
  17. package/dist/Meter.js +0 -1
  18. package/dist/MiddleTruncate.js +2 -6
  19. package/dist/PanelEmpty.js +0 -1
  20. package/dist/PanelHeader.js +1 -4
  21. package/dist/Pill.js +1 -6
  22. package/dist/ProjectSwitcher.js +1 -1
  23. package/dist/PromptDialog.js +5 -8
  24. package/dist/RelativeTime.js +1 -1
  25. package/dist/SegmentBar.js +1 -2
  26. package/dist/Segmented.js +2 -5
  27. package/dist/Select.js +1 -4
  28. package/dist/ShortcutHints.js +2 -3
  29. package/dist/Slider.js +0 -1
  30. package/dist/SlidingColumn.js +2 -4
  31. package/dist/SplitButton.js +1 -5
  32. package/dist/TextMenu.js +1 -5
  33. package/dist/Tile.js +0 -4
  34. package/dist/Toasts.js +1 -9
  35. package/dist/Tooltip.js +1 -1
  36. package/dist/UIProvider.js +0 -5
  37. package/dist/Waveform.js +4 -2
  38. package/dist/Wipe.js +1 -6
  39. package/dist/ZoomControls.js +0 -1
  40. package/dist/clipboard.js +2 -5
  41. package/dist/combobox/parts.js +3 -9
  42. package/dist/command-palette/parts.js +5 -14
  43. package/dist/dialog/parts.js +5 -10
  44. package/dist/dialog-layer.js +2 -3
  45. package/dist/error-boundary.js +4 -20
  46. package/dist/file-icon.js +4 -9
  47. package/dist/file-tree/empty-lines.js +2 -8
  48. package/dist/file-tree/use-empty-lines.js +1 -5
  49. package/dist/file-tree/use-slots.js +6 -10
  50. package/dist/format/datetime.js +32 -29
  51. package/dist/format/duration.js +2 -10
  52. package/dist/format/locale.js +19 -39
  53. package/dist/format/number.js +9 -22
  54. package/dist/format/regions.js +4 -7
  55. package/dist/icon-picker.js +5 -14
  56. package/dist/key-value-list/parts.js +1 -5
  57. package/dist/lazy.js +4 -19
  58. package/dist/locales.js +2 -6
  59. package/dist/menu/parts.js +61 -133
  60. package/dist/middle-truncate.js +2 -3
  61. package/dist/placement.js +0 -1
  62. package/dist/popover/parts.js +1 -5
  63. package/dist/popup-layer.js +2 -4
  64. package/dist/prefetch.js +2 -5
  65. package/dist/selection.js +1 -5
  66. package/dist/settings/MasterDetail.js +1 -7
  67. package/dist/settings/SettingsDialog.js +1 -2
  68. package/dist/settings/SettingsRow.js +2 -5
  69. package/dist/settings/SettingsSection.js +1 -1
  70. package/dist/shortcut-hints.js +1 -5
  71. package/dist/shortcut.js +1 -2
  72. package/dist/tabs/overflow.js +1 -6
  73. package/dist/tabs/parts.js +69 -95
  74. package/dist/testing/dedupe.js +5 -7
  75. package/dist/tint.js +3 -5
  76. package/dist/toast-store.js +2 -10
  77. package/dist/tree/use-tree-shift.js +2 -6
  78. package/dist/useColumnResize.js +9 -10
  79. package/dist/useNow.js +2 -9
  80. package/dist/workspace/DropPreview.js +1 -5
  81. package/dist/workspace/preview.js +4 -9
  82. package/package.json +10 -10
  83. package/src/AccentSwatches.tsx +9 -3
  84. package/src/Banner.tsx +5 -11
  85. package/src/Button.tsx +4 -0
  86. package/src/Checkbox.tsx +3 -0
  87. package/src/ChoiceCards.tsx +12 -11
  88. package/src/CloseButton.tsx +1 -0
  89. package/src/ColorSwatch.tsx +7 -8
  90. package/src/ColorWheelPicker.tsx +28 -12
  91. package/src/ColumnResizeHandle.tsx +1 -0
  92. package/src/CopyButton.tsx +6 -0
  93. package/src/DisabledReason.tsx +1 -0
  94. package/src/DockShell.tsx +11 -10
  95. package/src/EmptyState.tsx +10 -6
  96. package/src/ErrorBoundary.tsx +13 -10
  97. package/src/Field.tsx +11 -13
  98. package/src/FileIcon.tsx +4 -4
  99. package/src/Icon.tsx +2 -4
  100. package/src/IconButton.tsx +14 -5
  101. package/src/IconPicker.tsx +30 -15
  102. package/src/Input.tsx +11 -9
  103. package/src/Kbd.tsx +8 -7
  104. package/src/LazyBoundary.tsx +3 -3
  105. package/src/Meter.tsx +7 -2
  106. package/src/MiddleTruncate.tsx +15 -6
  107. package/src/PanelEmpty.tsx +9 -7
  108. package/src/PanelHeader.tsx +2 -5
  109. package/src/Pill.tsx +7 -8
  110. package/src/ProjectSwitcher.tsx +16 -6
  111. package/src/PromptDialog.tsx +31 -26
  112. package/src/RelativeTime.tsx +6 -1
  113. package/src/SegmentBar.tsx +22 -11
  114. package/src/Segmented.tsx +16 -11
  115. package/src/Select.tsx +7 -4
  116. package/src/SelectSearch.tsx +3 -0
  117. package/src/ShortcutHints.tsx +24 -3
  118. package/src/Skeleton.tsx +1 -0
  119. package/src/Slider.tsx +4 -1
  120. package/src/SlidingColumn.tsx +18 -14
  121. package/src/Spinner.tsx +1 -0
  122. package/src/SplitButton.tsx +8 -8
  123. package/src/Stepper.tsx +6 -0
  124. package/src/Switch.tsx +2 -0
  125. package/src/TextMenu.tsx +2 -5
  126. package/src/Tile.tsx +2 -6
  127. package/src/Toasts.tsx +8 -12
  128. package/src/Tooltip.tsx +10 -5
  129. package/src/UIProvider.tsx +7 -7
  130. package/src/Waveform.tsx +41 -12
  131. package/src/Wipe.tsx +5 -6
  132. package/src/ZoomControls.tsx +5 -2
  133. package/src/clipboard.ts +4 -5
  134. package/src/combobox/parts.tsx +18 -14
  135. package/src/command-palette/context.ts +2 -0
  136. package/src/command-palette/index.parts.ts +1 -0
  137. package/src/command-palette/parts.tsx +20 -14
  138. package/src/command-palette/virtual.tsx +21 -0
  139. package/src/dialog/parts.tsx +10 -13
  140. package/src/dialog-layer.ts +5 -3
  141. package/src/error-boundary.ts +7 -20
  142. package/src/file-icon.ts +8 -9
  143. package/src/file-tree/empty-lines.ts +24 -8
  144. package/src/file-tree/helpers.ts +34 -0
  145. package/src/file-tree/highlight.ts +19 -0
  146. package/src/file-tree/index.parts.ts +1 -0
  147. package/src/file-tree/parts.tsx +41 -5
  148. package/src/file-tree/use-empty-lines.ts +22 -5
  149. package/src/file-tree/use-file-tree-shift.tsx +10 -0
  150. package/src/file-tree/use-file-tree.ts +2 -0
  151. package/src/file-tree/use-slots.ts +77 -12
  152. package/src/format/datetime.ts +42 -17
  153. package/src/format/duration.ts +28 -10
  154. package/src/format/index.ts +3 -0
  155. package/src/format/locale.ts +33 -42
  156. package/src/format/number.ts +21 -27
  157. package/src/format/regions.ts +4 -7
  158. package/src/icon-picker.ts +23 -16
  159. package/src/index.ts +8 -0
  160. package/src/key-value-list/parts.tsx +1 -5
  161. package/src/lazy-landings.ts +2 -0
  162. package/src/lazy.tsx +23 -19
  163. package/src/locales.ts +2 -6
  164. package/src/match.ts +14 -0
  165. package/src/menu/parts.tsx +30 -55
  166. package/src/middle-truncate.ts +12 -3
  167. package/src/modality.ts +4 -0
  168. package/src/placement.ts +2 -1
  169. package/src/platform.ts +2 -0
  170. package/src/popover/parts.tsx +3 -5
  171. package/src/popup-layer.ts +6 -4
  172. package/src/prefetch.ts +18 -5
  173. package/src/project-switcher-row.tsx +2 -0
  174. package/src/segmented-keys.ts +6 -0
  175. package/src/select-shared.ts +2 -0
  176. package/src/selection.ts +5 -5
  177. package/src/settings/ConfirmDialog.tsx +3 -0
  178. package/src/settings/MasterDetail.tsx +3 -9
  179. package/src/settings/SettingsDialog.tsx +27 -9
  180. package/src/settings/SettingsRow.tsx +16 -10
  181. package/src/settings/SettingsSection.tsx +10 -5
  182. package/src/settings/index.ts +3 -0
  183. package/src/settings/target.ts +1 -0
  184. package/src/shortcut-hints.ts +10 -5
  185. package/src/shortcut.ts +26 -6
  186. package/src/tabs/overflow.ts +8 -6
  187. package/src/tabs/parts.tsx +50 -26
  188. package/src/testing/dedupe.ts +27 -7
  189. package/src/testing/fake-source.ts +1 -0
  190. package/src/tint.ts +3 -5
  191. package/src/toast-store.ts +20 -14
  192. package/src/tree/parts.tsx +8 -0
  193. package/src/tree/shift.ts +6 -0
  194. package/src/tree/use-tree-shift.tsx +23 -7
  195. package/src/useAsyncAction.ts +8 -0
  196. package/src/useColumnResize.ts +24 -15
  197. package/src/useContentSize.ts +5 -0
  198. package/src/useMeasuredWidth.ts +1 -0
  199. package/src/useNow.ts +6 -11
  200. package/src/workspace/DropPreview.tsx +3 -5
  201. package/src/workspace/SplitView.tsx +109 -0
  202. package/src/workspace/TabStrip.tsx +42 -0
  203. package/src/workspace/Workspace.tsx +5 -0
  204. package/src/workspace/context.ts +1 -0
  205. package/src/workspace/geometry.ts +49 -0
  206. package/src/workspace/model.ts +72 -0
  207. package/src/workspace/preview.ts +15 -15
@@ -40,6 +40,7 @@ export type SplitCommand =
40
40
 
41
41
  export function createSplitLayout(views: string[] = [], paneId = 'main'): SplitLayout {
42
42
  const unique = [...new Set(views)];
43
+
43
44
  return { root: { type: 'pane', id: paneId, views: unique, active: unique[0] ?? null }, focused: paneId, maximized: null };
44
45
  }
45
46
 
@@ -51,6 +52,7 @@ export function findSplitNode(node: SplitNode, id: string): SplitNode | undefine
51
52
  if (node.id === id) {
52
53
  return node;
53
54
  }
55
+
54
56
  return node.type === 'split' ? node.children.map((child) => findSplitNode(child, id)).find(Boolean) : undefined;
55
57
  }
56
58
 
@@ -58,6 +60,7 @@ function shares(values: number[], length: number): number[] {
58
60
  const safe = Array.from({ length }, (_, i) => (Number.isFinite(values[i]) && values[i]! > 0 ? values[i]! : 1));
59
61
  const largest = Math.max(...safe, 1);
60
62
  const total = safe.reduce((sum, value) => sum + value / largest, 0);
63
+
61
64
  return safe.map((value) => value / largest / total);
62
65
  }
63
66
 
@@ -65,10 +68,13 @@ function flattenSplits(node: SplitNode): SplitNode {
65
68
  if (node.type === 'pane') {
66
69
  return node;
67
70
  }
71
+
68
72
  const children: SplitNode[] = [];
69
73
  const sizes: number[] = [];
74
+
70
75
  node.children.forEach((child, i) => {
71
76
  const next = flattenSplits(child);
77
+
72
78
  // A divider balances neighboring tracks, not a pane against a hidden group on the same axis.
73
79
  if (next.type === 'split' && next.axis === node.axis) {
74
80
  children.push(...next.children);
@@ -78,9 +84,11 @@ function flattenSplits(node: SplitNode): SplitNode {
78
84
  sizes.push(node.sizes[i]!);
79
85
  }
80
86
  });
87
+
81
88
  if (children.length === node.children.length && children.every((child, i) => child === node.children[i])) {
82
89
  return node;
83
90
  }
91
+
84
92
  return { ...node, children, sizes: shares(sizes, children.length) };
85
93
  }
86
94
 
@@ -88,30 +96,38 @@ function replace(node: SplitNode, id: string, transform: (node: SplitNode) => Sp
88
96
  if (node.id === id) {
89
97
  return transform(node);
90
98
  }
99
+
91
100
  if (node.type === 'pane') {
92
101
  return node;
93
102
  }
103
+
94
104
  const children: SplitNode[] = [];
95
105
  const sizes: number[] = [];
106
+
96
107
  node.children.forEach((child, i) => {
97
108
  const next = replace(child, id, transform);
109
+
98
110
  if (next) {
99
111
  children.push(next);
100
112
  sizes.push(node.sizes[i]!);
101
113
  }
102
114
  });
115
+
103
116
  if (children.every((child, i) => child === node.children[i]) && children.length === node.children.length) {
104
117
  return node;
105
118
  }
119
+
106
120
  if (children.length < 2) {
107
121
  return children[0] ?? null;
108
122
  }
123
+
109
124
  return { ...node, children, sizes: shares(sizes, children.length) };
110
125
  }
111
126
 
112
127
  function finish(layout: SplitLayout, root: SplitNode | null, focus = layout.focused): SplitLayout {
113
128
  const next = flattenSplits(root ?? createSplitLayout([], layout.focused).root);
114
129
  const panes = splitPanes(next);
130
+
115
131
  return {
116
132
  root: next,
117
133
  focused: panes.some((pane) => pane.id === focus) ? focus : panes[0]!.id,
@@ -122,6 +138,7 @@ function finish(layout: SplitLayout, root: SplitNode | null, focus = layout.focu
122
138
  function without(pane: SplitPane, views: string[]): SplitPane {
123
139
  const remaining = pane.views.filter((view) => !views.includes(view));
124
140
  const position = Math.max(0, pane.views.indexOf(pane.active ?? ''));
141
+
125
142
  return {
126
143
  ...pane,
127
144
  views: remaining,
@@ -141,30 +158,40 @@ function beside(target: SplitPane, pane: SplitPane, side: SplitSide, id: string)
141
158
 
142
159
  function equalizeAxis(root: SplitNode, axis: SplitAxis, length: number, gap: number): SplitNode {
143
160
  const tracks = new Map<string, number>();
161
+
144
162
  const measure = (node: SplitNode): number => {
145
163
  const children = node.type === 'split' ? node.children.map(measure) : [];
146
164
  const count = node.type === 'pane' ? 1 : node.axis === axis ? children.reduce((sum, value) => sum + value, 0) : Math.max(...children);
147
165
  tracks.set(node.id, count);
166
+
148
167
  return count;
149
168
  };
169
+
150
170
  measure(root);
171
+
151
172
  const distribute = (node: SplitNode, extent: number): SplitNode => {
152
173
  if (node.type === 'pane') {
153
174
  return node;
154
175
  }
176
+
155
177
  if (node.axis !== axis) {
156
178
  return { ...node, children: node.children.map((child) => distribute(child, extent)) };
157
179
  }
180
+
158
181
  const count = tracks.get(node.id)!;
159
182
  const spacing = Math.min(gap, extent / (count - 1));
160
183
  const unit = Math.max(0, extent - spacing * (count - 1)) / count;
184
+
161
185
  // Spanning panels also occupy the gaps between their tracks.
162
186
  const lengths = node.children.map((child) => {
163
187
  const span = tracks.get(child.id)!;
188
+
164
189
  return Math.max(Number.EPSILON, unit * span + spacing * (span - 1));
165
190
  });
191
+
166
192
  return { ...node, sizes: shares(lengths, lengths.length), children: node.children.map((child, i) => distribute(child, lengths[i]!)) };
167
193
  };
194
+
168
195
  return distribute(root, length);
169
196
  }
170
197
 
@@ -174,14 +201,18 @@ function addPane(layout: SplitLayout, panes: SplitPane[], pane: SplitPane | unde
174
201
  if (!pane || findSplitNode(layout.root, command.newPane.id) || findSplitNode(layout.root, command.splitId) || command.newPane.id === command.splitId) {
175
202
  return layout;
176
203
  }
204
+
177
205
  const allViews = new Set(panes.flatMap((item) => item.views));
206
+
178
207
  if (command.newPane.views.some((view) => allViews.has(view)) || new Set(command.newPane.views).size !== command.newPane.views.length) {
179
208
  return layout;
180
209
  }
210
+
181
211
  const added = {
182
212
  ...command.newPane,
183
213
  active: command.newPane.views.includes(command.newPane.active ?? '') ? command.newPane.active : (command.newPane.views[0] ?? null)
184
214
  };
215
+
185
216
  return finish(
186
217
  { ...layout, maximized: null },
187
218
  replace(layout.root, pane.id, () => beside(pane, added, command.side, command.splitId)),
@@ -192,23 +223,30 @@ function addPane(layout: SplitLayout, panes: SplitPane[], pane: SplitPane | unde
192
223
  function swapPanes(layout: SplitLayout, panes: SplitPane[], command: CommandOf<'swap'>): SplitLayout {
193
224
  const source = panes.find((item) => item.id === command.sourceId);
194
225
  const target = panes.find((item) => item.id === command.targetId);
226
+
195
227
  if (!source || !target || source === target || source.views.length === 0 || (command.viewId !== undefined && !source.views.includes(command.viewId))) {
196
228
  return layout;
197
229
  }
230
+
198
231
  if (command.viewId === undefined || source.views.length === 1) {
199
232
  const exchange = (node: SplitNode): SplitNode => {
200
233
  if (node.id === source.id) {
201
234
  return target;
202
235
  }
236
+
203
237
  if (node.id === target.id) {
204
238
  return source;
205
239
  }
240
+
206
241
  return node.type === 'pane' ? node : { ...node, children: node.children.map(exchange) };
207
242
  };
243
+
208
244
  return finish({ ...layout, maximized: null }, exchange(layout.root), source.id);
209
245
  }
246
+
210
247
  const moving = command.viewId;
211
248
  const displaced = target.active;
249
+
212
250
  const sourceNext =
213
251
  displaced === null
214
252
  ? without(source, [moving])
@@ -217,12 +255,15 @@ function swapPanes(layout: SplitLayout, panes: SplitPane[], command: CommandOf<'
217
255
  views: source.views.map((view) => (view === moving ? displaced : view)),
218
256
  active: source.active === moving ? displaced : source.active
219
257
  };
258
+
220
259
  const targetNext = {
221
260
  ...target,
222
261
  views: displaced === null ? [moving] : target.views.map((view) => (view === displaced ? moving : view)),
223
262
  active: moving
224
263
  };
264
+
225
265
  const root = replace(layout.root, source.id, () => sourceNext)!;
266
+
226
267
  return finish(
227
268
  { ...layout, maximized: null },
228
269
  replace(root, target.id, () => targetNext),
@@ -233,16 +274,22 @@ function swapPanes(layout: SplitLayout, panes: SplitPane[], command: CommandOf<'
233
274
  function moveViews(layout: SplitLayout, panes: SplitPane[], command: CommandOf<'move'>): SplitLayout {
234
275
  const source = panes.find((item) => item.id === command.sourceId);
235
276
  const target = panes.find((item) => item.id === command.targetId);
277
+
236
278
  if (!source || !target || (command.viewId !== undefined && !source.views.includes(command.viewId))) {
237
279
  return layout;
238
280
  }
281
+
239
282
  const moving = command.viewId === undefined ? source.views : [command.viewId];
283
+
240
284
  if (!moving.length || (source === target && (command.viewId === undefined || (command.side !== undefined && source.views.length === 1)))) {
241
285
  return layout;
242
286
  }
287
+
243
288
  let added: SplitPane | undefined;
289
+
244
290
  if (command.side) {
245
291
  const id = command.viewId === undefined ? source.id : command.newPaneId;
292
+
246
293
  if (
247
294
  !id ||
248
295
  !command.splitId ||
@@ -252,49 +299,64 @@ function moveViews(layout: SplitLayout, panes: SplitPane[], command: CommandOf<'
252
299
  ) {
253
300
  return layout;
254
301
  }
302
+
255
303
  added = { type: 'pane', id, views: moving, active: command.viewId ?? source.active };
256
304
  }
305
+
257
306
  const remaining = without(source, moving);
258
307
  const root = replace(layout.root, source.id, () => (remaining.views.length || source === target ? remaining : null));
308
+
259
309
  if (!root) {
260
310
  return layout;
261
311
  }
312
+
262
313
  const next = replace(root, target.id, (node) => {
263
314
  if (node.type !== 'pane') {
264
315
  return node;
265
316
  }
317
+
266
318
  if (added && command.side && command.splitId) {
267
319
  return beside(node, added, command.side, command.splitId);
268
320
  }
321
+
269
322
  const gap = Math.max(0, Math.min(target.views.length, command.index ?? target.views.length));
270
323
  const lifted = source === target ? target.views.slice(0, gap).filter((view) => moving.includes(view)).length : 0;
271
324
  const views = [...node.views];
272
325
  views.splice(gap - lifted, 0, ...moving);
326
+
273
327
  return { ...node, views, active: command.viewId ?? source.active ?? views[0] ?? null };
274
328
  });
329
+
275
330
  return finish({ ...layout, maximized: null }, next, added?.id ?? target.id);
276
331
  }
277
332
 
278
333
  function resizeBranch(layout: SplitLayout, command: CommandOf<'resize' | 'equalize'>): SplitLayout {
279
334
  const node = findSplitNode(layout.root, command.splitId);
335
+
280
336
  if (!node || node.type !== 'split') {
281
337
  return layout;
282
338
  }
339
+
283
340
  let sizes = [...node.sizes];
341
+
284
342
  if (command.type === 'resize') {
285
343
  if (command.sizes.length !== node.children.length || command.sizes.some((size) => !Number.isFinite(size) || size <= 0)) {
286
344
  return layout;
287
345
  }
346
+
288
347
  sizes = shares(command.sizes, node.children.length);
289
348
  } else if (command.index === undefined) {
290
349
  sizes = node.children.map(() => 1 / node.children.length);
291
350
  } else {
292
351
  const index = command.index;
352
+
293
353
  if (index < 0 || index >= sizes.length - 1) {
294
354
  return layout;
295
355
  }
356
+
296
357
  sizes[index] = sizes[index + 1] = (sizes[index]! + sizes[index + 1]!) / 2;
297
358
  }
359
+
298
360
  return finish(
299
361
  layout,
300
362
  replace(layout.root, node.id, () => ({ ...node, sizes }))
@@ -305,6 +367,7 @@ function resizeBranch(layout: SplitLayout, command: CommandOf<'resize' | 'equali
305
367
  export function updateSplitLayout(layout: SplitLayout, command: SplitCommand): SplitLayout {
306
368
  const panes = splitPanes(layout.root);
307
369
  const pane = 'paneId' in command ? panes.find((item) => item.id === command.paneId) : undefined;
370
+
308
371
  switch (command.type) {
309
372
  case 'focus':
310
373
  return pane && layout.focused !== pane.id ? { ...layout, focused: pane.id } : layout;
@@ -320,10 +383,13 @@ export function updateSplitLayout(layout: SplitLayout, command: SplitCommand): S
320
383
  : layout;
321
384
  case 'close': {
322
385
  const source = panes.find((item) => item.views.includes(command.viewId));
386
+
323
387
  if (!source) {
324
388
  return layout;
325
389
  }
390
+
326
391
  const next = without(source, [command.viewId]);
392
+
327
393
  return finish(
328
394
  layout,
329
395
  replace(layout.root, source.id, () => (next.views.length || panes.length === 1 ? next : null))
@@ -340,12 +406,16 @@ export function updateSplitLayout(layout: SplitLayout, command: SplitCommand): S
340
406
  if (!pane) {
341
407
  return layout;
342
408
  }
409
+
343
410
  const source = panes.find((item) => item.views.includes(command.viewId));
411
+
344
412
  if (source) {
345
413
  return updateSplitLayout(layout, { type: 'move', sourceId: source.id, targetId: pane.id, viewId: command.viewId, index: command.index });
346
414
  }
415
+
347
416
  const views = [...pane.views];
348
417
  views.splice(Math.max(0, Math.min(views.length, command.index ?? views.length)), 0, command.viewId);
418
+
349
419
  return finish(
350
420
  layout,
351
421
  replace(layout.root, pane.id, () => ({ ...pane, views, active: command.viewId })),
@@ -361,9 +431,11 @@ export function updateSplitLayout(layout: SplitLayout, command: SplitCommand): S
361
431
  case 'equalizeAxis': {
362
432
  const length = command.length ?? 1;
363
433
  const gap = command.gap ?? 0;
434
+
364
435
  if (layout.root.type === 'pane' || !Number.isFinite(length) || length <= 0 || !Number.isFinite(gap) || gap < 0) {
365
436
  return layout;
366
437
  }
438
+
367
439
  return finish(layout, equalizeAxis(layout.root, command.axis, length, gap));
368
440
  }
369
441
  case 'resize':
@@ -5,24 +5,22 @@ export const TAB_TOP_INSET = 4;
5
5
 
6
6
  /* The corner of the indicator's tab, and of its body, which is `rounded-sm` like the other drop indicator. */
7
7
  export const TAB_RADIUS = 6;
8
+
8
9
  export const BODY_RADIUS = 4;
9
10
 
10
11
  /* The stroke is centered on the path, so the path runs half of it inside the box to keep the outer edge on it. */
11
12
  export const DROP_STROKE = 2;
12
13
 
13
- /*
14
- * What the drop preview draws, in the grid's coordinates. A tab and a plain rectangle are the same
15
- * shape: a tab standing on a body, where the rectangle's tab has no height. `x` and `y` are the
16
- * top left of the outline's box, which for a tab is the cell with its bar.
17
- */
14
+ /* In the grid's coordinates. A rectangle is a tab with no height standing on a body; for a tab, the box is the cell with its bar. */
18
15
  export interface DropPreviewShape {
19
16
  x: number;
20
17
  y: number;
21
18
  width: number;
22
19
  height: number;
20
+
23
21
  /* The part of the box above the body that the tab stands in; 0 for a rectangle. */
24
22
  barHeight: number;
25
- /* The tab's left edge and the top of its box, in the same coordinates as `x` and `y`. */
23
+
26
24
  tabLeft: number;
27
25
  tabWidth: number;
28
26
  tabTop: number;
@@ -33,15 +31,12 @@ function coordinate(value: number): string {
33
31
  return String(Math.round(value * 100) / 100 + 0);
34
32
  }
35
33
 
36
- /*
37
- * The preview for a split or a trade: the rectangle as a tab with no height, parked just inside its
38
- * top left corner so the corner keeps its radius. It is where a tab target grows out of the rectangle.
39
- */
34
+ /* The tab with no height parks just inside the top left corner, so the corner keeps its radius and a tab target grows out of it. */
40
35
  export function rectDropPreview({ x, y, width, height }: SplitRect): DropPreviewShape {
41
36
  return { x, y, width, height, barHeight: 0, tabLeft: x + BODY_RADIUS, tabWidth: 0, tabTop: y, radius: TAB_RADIUS };
42
37
  }
43
38
 
44
- /* The preview for a tab: the tab and the body of the cell it lands in, `origin` being the cell's top left in the grid. */
39
+ /* `origin` is the cell's top left in the grid. */
45
40
  export function tabDropPreview(drop: TabDrop, origin: { x: number; y: number }): DropPreviewShape {
46
41
  return {
47
42
  x: origin.x,
@@ -61,6 +56,7 @@ export function sameDropPreview(left: DropPreviewShape | null, right: DropPrevie
61
56
  if (left === null || right === null) {
62
57
  return left === right;
63
58
  }
59
+
64
60
  return (
65
61
  left.x === right.x &&
66
62
  left.y === right.y &&
@@ -75,10 +71,8 @@ export function sameDropPreview(left: DropPreviewShape | null, right: DropPrevie
75
71
  }
76
72
 
77
73
  /*
78
- * The outline of a tab grown into the body under it, as one closed path so no line runs between the
79
- * two. The commands are the same whatever the numbers are (an arc of radius 0 is a straight line), so
80
- * the browser can move one outline into another with a transition on `d`. `inset` is how far the
81
- * path runs inside the box, which is half the stroke, so the outer edge of the line is on the box.
74
+ * One closed path, so no line runs between tab and body. The commands are the same whatever the
75
+ * numbers (an arc of radius 0 is a straight line), so a transition on `d` can move one outline into another.
82
76
  */
83
77
  export function dropPreviewPath({ x, y, width, height, barHeight, tabLeft, tabWidth, tabTop, radius }: DropPreviewShape, inset = DROP_STROKE / 2): string {
84
78
  const left = inset;
@@ -94,8 +88,11 @@ export function dropPreviewPath({ x, y, width, height, barHeight, tabLeft, tabWi
94
88
  const cornerLeft = Math.min(bodyCorner, tabStart - left);
95
89
  const cornerRight = Math.min(bodyCorner, right - tabEnd);
96
90
  const cornerBottom = Math.max(0, Math.min(bodyCorner, (right - left) / 2, (bottom - top) / 2));
91
+
97
92
  const at = (px: number, py: number): string => `${coordinate(x + px)},${coordinate(y + py)}`;
93
+
98
94
  const arc = (corner: number, px: number, py: number): string => `A${coordinate(corner)},${coordinate(corner)} 0 0 1 ${at(px, py)}`;
95
+
99
96
  return [
100
97
  `M${at(left + cornerLeft, top)}`,
101
98
  `H${coordinate(x + tabStart)}`,
@@ -120,10 +117,13 @@ export function dropPreviewPath({ x, y, width, height, barHeight, tabLeft, tabWi
120
117
  export interface TabDrop {
121
118
  /* The gap in the strip it lands in, counted in tabs from the left. */
122
119
  gap: number;
120
+
123
121
  left: number;
124
122
  tabWidth: number;
123
+
125
124
  /* The cell as a whole, bar included. */
126
125
  width: number;
126
+
127
127
  height: number;
128
128
  barHeight: number;
129
129
  }