@adecore/ui 0.38.0 → 0.40.0

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 (210) 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/index.d.ts +1 -0
  57. package/dist/index.js +1 -0
  58. package/dist/key-value-list/parts.js +1 -5
  59. package/dist/lazy.js +4 -19
  60. package/dist/locales.js +2 -6
  61. package/dist/menu/parts.js +61 -133
  62. package/dist/middle-truncate.js +2 -3
  63. package/dist/placement.js +0 -1
  64. package/dist/popover/parts.js +1 -5
  65. package/dist/popup-layer.js +2 -4
  66. package/dist/prefetch.js +2 -5
  67. package/dist/selection.js +1 -5
  68. package/dist/settings/MasterDetail.js +1 -7
  69. package/dist/settings/SettingsDialog.js +1 -2
  70. package/dist/settings/SettingsRow.js +2 -5
  71. package/dist/settings/SettingsSection.js +1 -1
  72. package/dist/shortcut-hints.js +1 -5
  73. package/dist/shortcut.js +1 -2
  74. package/dist/tabs/overflow.js +1 -6
  75. package/dist/tabs/parts.js +69 -95
  76. package/dist/testing/dedupe.js +5 -7
  77. package/dist/tint.d.ts +2 -0
  78. package/dist/tint.js +11 -0
  79. package/dist/toast-store.js +2 -10
  80. package/dist/tree/use-tree-shift.js +2 -6
  81. package/dist/useColumnResize.js +9 -10
  82. package/dist/useNow.js +2 -9
  83. package/dist/workspace/DropPreview.js +1 -5
  84. package/dist/workspace/preview.js +4 -9
  85. package/package.json +9 -9
  86. package/src/AccentSwatches.tsx +9 -3
  87. package/src/Banner.tsx +5 -11
  88. package/src/Button.tsx +4 -0
  89. package/src/Checkbox.tsx +3 -0
  90. package/src/ChoiceCards.tsx +12 -11
  91. package/src/CloseButton.tsx +1 -0
  92. package/src/ColorSwatch.tsx +7 -8
  93. package/src/ColorWheelPicker.tsx +28 -12
  94. package/src/ColumnResizeHandle.tsx +1 -0
  95. package/src/CopyButton.tsx +6 -0
  96. package/src/DisabledReason.tsx +1 -0
  97. package/src/DockShell.tsx +11 -10
  98. package/src/EmptyState.tsx +10 -6
  99. package/src/ErrorBoundary.tsx +13 -10
  100. package/src/Field.tsx +11 -13
  101. package/src/FileIcon.tsx +4 -4
  102. package/src/Icon.tsx +2 -4
  103. package/src/IconButton.tsx +14 -5
  104. package/src/IconPicker.tsx +30 -15
  105. package/src/Input.tsx +11 -9
  106. package/src/Kbd.tsx +8 -7
  107. package/src/LazyBoundary.tsx +3 -3
  108. package/src/Meter.tsx +7 -2
  109. package/src/MiddleTruncate.tsx +15 -6
  110. package/src/PanelEmpty.tsx +9 -7
  111. package/src/PanelHeader.tsx +2 -5
  112. package/src/Pill.tsx +7 -8
  113. package/src/ProjectSwitcher.tsx +16 -6
  114. package/src/PromptDialog.tsx +31 -26
  115. package/src/RelativeTime.tsx +6 -1
  116. package/src/SegmentBar.tsx +22 -11
  117. package/src/Segmented.tsx +16 -11
  118. package/src/Select.tsx +7 -4
  119. package/src/SelectSearch.tsx +3 -0
  120. package/src/ShortcutHints.tsx +24 -3
  121. package/src/Skeleton.tsx +1 -0
  122. package/src/Slider.tsx +4 -1
  123. package/src/SlidingColumn.tsx +18 -14
  124. package/src/Spinner.tsx +1 -0
  125. package/src/SplitButton.tsx +8 -8
  126. package/src/Stepper.tsx +6 -0
  127. package/src/Switch.tsx +2 -0
  128. package/src/TextMenu.tsx +2 -5
  129. package/src/Tile.tsx +2 -6
  130. package/src/Toasts.tsx +8 -12
  131. package/src/Tooltip.tsx +10 -5
  132. package/src/UIProvider.tsx +7 -7
  133. package/src/Waveform.tsx +41 -12
  134. package/src/Wipe.tsx +5 -6
  135. package/src/ZoomControls.tsx +5 -2
  136. package/src/clipboard.ts +4 -5
  137. package/src/combobox/parts.tsx +18 -14
  138. package/src/command-palette/context.ts +2 -0
  139. package/src/command-palette/index.parts.ts +1 -0
  140. package/src/command-palette/parts.tsx +20 -14
  141. package/src/command-palette/virtual.tsx +21 -0
  142. package/src/dialog/parts.tsx +10 -13
  143. package/src/dialog-layer.ts +5 -3
  144. package/src/error-boundary.ts +7 -20
  145. package/src/file-icon.ts +8 -9
  146. package/src/file-tree/empty-lines.ts +24 -8
  147. package/src/file-tree/helpers.ts +34 -0
  148. package/src/file-tree/highlight.ts +19 -0
  149. package/src/file-tree/index.parts.ts +1 -0
  150. package/src/file-tree/parts.tsx +41 -5
  151. package/src/file-tree/use-empty-lines.ts +22 -5
  152. package/src/file-tree/use-file-tree-shift.tsx +10 -0
  153. package/src/file-tree/use-file-tree.ts +2 -0
  154. package/src/file-tree/use-slots.ts +77 -12
  155. package/src/format/datetime.ts +42 -17
  156. package/src/format/duration.ts +28 -10
  157. package/src/format/index.ts +3 -0
  158. package/src/format/locale.ts +33 -42
  159. package/src/format/number.ts +21 -27
  160. package/src/format/regions.ts +4 -7
  161. package/src/icon-picker.ts +23 -16
  162. package/src/index.ts +9 -0
  163. package/src/key-value-list/parts.tsx +1 -5
  164. package/src/lazy-landings.ts +2 -0
  165. package/src/lazy.tsx +23 -19
  166. package/src/locales.ts +2 -6
  167. package/src/match.ts +14 -0
  168. package/src/menu/parts.tsx +30 -55
  169. package/src/middle-truncate.ts +12 -3
  170. package/src/modality.ts +4 -0
  171. package/src/placement.ts +2 -1
  172. package/src/platform.ts +2 -0
  173. package/src/popover/parts.tsx +3 -5
  174. package/src/popup-layer.ts +6 -4
  175. package/src/prefetch.ts +18 -5
  176. package/src/project-switcher-row.tsx +2 -0
  177. package/src/segmented-keys.ts +6 -0
  178. package/src/select-shared.ts +2 -0
  179. package/src/selection.ts +5 -5
  180. package/src/settings/ConfirmDialog.tsx +3 -0
  181. package/src/settings/MasterDetail.tsx +3 -9
  182. package/src/settings/SettingsDialog.tsx +27 -9
  183. package/src/settings/SettingsRow.tsx +16 -10
  184. package/src/settings/SettingsSection.tsx +10 -5
  185. package/src/settings/index.ts +3 -0
  186. package/src/settings/target.ts +1 -0
  187. package/src/shortcut-hints.ts +10 -5
  188. package/src/shortcut.ts +26 -6
  189. package/src/tabs/overflow.ts +8 -6
  190. package/src/tabs/parts.tsx +50 -26
  191. package/src/testing/dedupe.ts +27 -7
  192. package/src/testing/fake-source.ts +1 -0
  193. package/src/tint.ts +13 -0
  194. package/src/toast-store.ts +20 -14
  195. package/src/tree/parts.tsx +8 -0
  196. package/src/tree/shift.ts +6 -0
  197. package/src/tree/use-tree-shift.tsx +23 -7
  198. package/src/useAsyncAction.ts +8 -0
  199. package/src/useColumnResize.ts +24 -15
  200. package/src/useContentSize.ts +5 -0
  201. package/src/useMeasuredWidth.ts +1 -0
  202. package/src/useNow.ts +6 -11
  203. package/src/workspace/DropPreview.tsx +3 -5
  204. package/src/workspace/SplitView.tsx +109 -0
  205. package/src/workspace/TabStrip.tsx +42 -0
  206. package/src/workspace/Workspace.tsx +5 -0
  207. package/src/workspace/context.ts +1 -0
  208. package/src/workspace/geometry.ts +49 -0
  209. package/src/workspace/model.ts +72 -0
  210. package/src/workspace/preview.ts +15 -15
@@ -41,15 +41,19 @@ export interface SplitViewInfo {
41
41
 
42
42
  export interface SplitViewBounds extends SplitViewInfo {
43
43
  viewId: string;
44
+
44
45
  /** Viewport coordinates, suitable for a native surface kept outside the layout's DOM. */
45
46
  rect: SplitRect;
47
+
46
48
  borderRadius: string;
47
49
  }
48
50
 
49
51
  export interface SplitDropTarget {
50
52
  paneId: string;
53
+
51
54
  /** A body drop names its zone; an omitted side denotes the tab strip. */
52
55
  side?: 'left' | 'right' | 'top' | 'bottom' | 'center';
56
+
53
57
  /** A gap in the original tab order, before the source is lifted out. */
54
58
  index?: number;
55
59
  }
@@ -89,12 +93,14 @@ interface Measurement {
89
93
  scaleX: number;
90
94
  scaleY: number;
91
95
  }
96
+
92
97
  const ZERO: Measurement = { width: 0, height: 0, left: 0, top: 0, scaleX: 1, scaleY: 1 };
93
98
  const MINIMUM: SplitMinimum = { width: 120, height: 80 };
94
99
 
95
100
  /* The pane's section or header under `container`, found by its data attribute rather than a selector, so no id needs escaping. */
96
101
  function paneElement(container: ParentNode | null | undefined, attribute: 'splitPane' | 'splitHeader', paneId: string): HTMLElement | undefined {
97
102
  const selector = attribute === 'splitPane' ? '[data-split-pane]' : '[data-split-header]';
103
+
98
104
  return Array.from(container?.querySelectorAll<HTMLElement>(selector) ?? []).find((element) => element.dataset[attribute] === paneId);
99
105
  }
100
106
 
@@ -116,10 +122,12 @@ function Divider({
116
122
  const before = divider.lengths.slice(0, divider.index + 1).reduce((sum, value) => sum + value, 0);
117
123
  const total = divider.lengths.reduce((sum, value) => sum + value, 0);
118
124
  const position = total ? Math.round((before / total) * 100) : 0;
125
+
119
126
  const limit = (delta: number): number =>
120
127
  resizeSplit(divider, delta, false, 0, minimumResizeShare)
121
128
  .slice(0, divider.index + 1)
122
129
  .reduce((sum, size) => sum + size, 0) * 100;
130
+
123
131
  return (
124
132
  <div
125
133
  role="separator"
@@ -135,6 +143,7 @@ function Divider({
135
143
  onKeyDown={(event) => {
136
144
  const negative = horizontal ? 'ArrowLeft' : 'ArrowUp';
137
145
  const positive = horizontal ? 'ArrowRight' : 'ArrowDown';
146
+
138
147
  if (event.key === 'Enter') {
139
148
  event.preventDefault();
140
149
  onEqualize(event.altKey);
@@ -148,6 +157,7 @@ function Divider({
148
157
  if (event.button !== 0) {
149
158
  return;
150
159
  }
160
+
151
161
  event.preventDefault();
152
162
  cleanup.current?.();
153
163
  const element = event.currentTarget;
@@ -162,52 +172,65 @@ function Divider({
162
172
  // A pointer reports more often than the screen draws; one resize per frame is all that shows.
163
173
  let frame = 0;
164
174
  let pending: PointerEvent | null = null;
175
+
165
176
  const apply = (): void => {
166
177
  frame = 0;
167
178
  const next = pending;
168
179
  pending = null;
180
+
169
181
  if (next) {
170
182
  const delta = ((horizontal ? next.clientX : next.clientY) - start) / (scale || 1);
171
183
  onResize(resizeSplit(divider, delta, next.altKey, 8 / (scale || 1), minimumResizeShare));
172
184
  }
173
185
  };
186
+
174
187
  const move = (next: PointerEvent): void => {
175
188
  if (next.pointerId !== pointerId) {
176
189
  return;
177
190
  }
191
+
178
192
  pending = next;
193
+
179
194
  if (frame !== 0) {
180
195
  return;
181
196
  }
197
+
182
198
  if (view) {
183
199
  frame = view.requestAnimationFrame(apply);
184
200
  } else {
185
201
  apply();
186
202
  }
187
203
  };
204
+
188
205
  const release = (): void => {
189
206
  if (frame !== 0) {
190
207
  view?.cancelAnimationFrame(frame);
191
208
  apply();
192
209
  }
210
+
193
211
  end();
194
212
  };
213
+
195
214
  const end = (): void => {
196
215
  if (frame !== 0) {
197
216
  view?.cancelAnimationFrame(frame);
198
217
  frame = 0;
199
218
  }
219
+
200
220
  element.removeEventListener('pointermove', move);
201
221
  element.removeEventListener('pointerup', release);
202
222
  element.removeEventListener('pointercancel', end);
203
223
  element.removeEventListener('lostpointercapture', end);
204
224
  element.ownerDocument.defaultView?.removeEventListener('blur', end);
205
225
  delete element.dataset.resizing;
226
+
206
227
  if (element.hasPointerCapture(pointerId)) {
207
228
  element.releasePointerCapture(pointerId);
208
229
  }
230
+
209
231
  cleanup.current = null;
210
232
  };
233
+
211
234
  cleanup.current = end;
212
235
  element.setPointerCapture(pointerId);
213
236
  element.addEventListener('pointermove', move);
@@ -256,10 +279,12 @@ export function SplitView({
256
279
  const prefix = useId();
257
280
  const serial = useRef(0);
258
281
  const lastFocus = useRef<HTMLElement | null>(null);
282
+
259
283
  useEffect(() => {
260
284
  const previous = lastFocus.current;
261
285
  const element = root.current;
262
286
  const active = element?.ownerDocument.activeElement;
287
+
263
288
  if (
264
289
  previous &&
265
290
  element &&
@@ -271,35 +296,45 @@ export function SplitView({
271
296
  target?.focus({ preventScroll: true });
272
297
  }
273
298
  });
299
+
274
300
  const [measurement, setMeasurement] = useState(ZERO);
275
301
  const [dragging, setDragging] = useState(false);
276
302
  const drag = useRef<(SplitDrag & { width: number }) | null>(null);
277
303
  const [preview, setPreview] = useState<DropPreviewShape | null>(null);
278
304
  // A dragover fires many times a second; only a different shape is worth a render.
279
305
  const shownPreview = useRef<DropPreviewShape | null>(null);
306
+
280
307
  const showPreview = useCallback((shape: DropPreviewShape | null): void => {
281
308
  if (!sameDropPreview(shownPreview.current, shape)) {
282
309
  shownPreview.current = shape;
283
310
  setPreview(shape);
284
311
  }
285
312
  }, []);
313
+
286
314
  const latest = useRef({ value, onValueChange });
315
+
287
316
  useLayoutEffect(() => {
288
317
  latest.current = { value, onValueChange };
289
318
  });
319
+
290
320
  const dispatch = (command: SplitCommand): void => {
291
321
  const state = latest.current;
292
322
  const next = updateSplitLayout(state.value, command);
323
+
293
324
  if (next !== state.value) {
294
325
  state.onValueChange(next, command);
295
326
  }
296
327
  };
328
+
297
329
  const measure = useCallback((): void => {
298
330
  const element = root.current;
331
+
299
332
  if (!element) {
300
333
  return;
301
334
  }
335
+
302
336
  const rect = element.getBoundingClientRect();
337
+
303
338
  const next = {
304
339
  width: element.clientWidth,
305
340
  height: element.clientHeight,
@@ -308,68 +343,89 @@ export function SplitView({
308
343
  scaleX: rect.width / (element.clientWidth || 1),
309
344
  scaleY: rect.height / (element.clientHeight || 1)
310
345
  };
346
+
311
347
  setMeasurement((current) => (Object.keys(next).every((key) => current[key as keyof Measurement] === next[key as keyof Measurement]) ? current : next));
312
348
  }, []);
349
+
313
350
  useLayoutEffect(measure);
351
+
314
352
  useEffect(() => {
315
353
  const element = root.current;
354
+
316
355
  if (!element) {
317
356
  return;
318
357
  }
358
+
319
359
  const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure);
320
360
  observer?.observe(element);
321
361
  const view = element.ownerDocument.defaultView;
322
362
  view?.addEventListener('resize', measure);
323
363
  view?.addEventListener('scroll', measure, true);
364
+
324
365
  return () => {
325
366
  observer?.disconnect();
326
367
  view?.removeEventListener('resize', measure);
327
368
  view?.removeEventListener('scroll', measure, true);
328
369
  };
329
370
  }, [measure]);
371
+
330
372
  const stopDrag = useCallback((): void => {
331
373
  drag.current = null;
332
374
  setDragging(false);
333
375
  showPreview(null);
334
376
  }, [showPreview]);
377
+
335
378
  useEffect(() => {
336
379
  if (!dragging) {
337
380
  return;
338
381
  }
382
+
339
383
  const document = root.current?.ownerDocument;
340
384
  const view = document?.defaultView;
341
385
  document?.addEventListener('dragend', stopDrag);
342
386
  document?.addEventListener('drop', stopDrag);
343
387
  view?.addEventListener('blur', stopDrag);
388
+
344
389
  return () => {
345
390
  document?.removeEventListener('dragend', stopDrag);
346
391
  document?.removeEventListener('drop', stopDrag);
347
392
  view?.removeEventListener('blur', stopDrag);
348
393
  };
349
394
  }, [dragging, stopDrag]);
395
+
350
396
  const gap = layout === 'roomy' ? 8 : 1;
351
397
  const barHeight = showTabs || renderPaneHeader ? Math.max(0, headerHeight) : 0;
398
+
352
399
  const geometry = splitGeometry(value, measurement.width, measurement.height, gap, (pane) =>
353
400
  typeof minimumSize === 'function' ? minimumSize(pane) : minimumSize
354
401
  );
402
+
355
403
  const panes = splitPanes(value.root);
404
+
356
405
  // A stable sibling order also avoids DOM moves, which reload embedded browsing contexts.
357
406
  const views = panes
358
407
  .flatMap((pane) => pane.views.map((viewId) => ({ pane, viewId })))
359
408
  .sort((left, right) => (left.viewId < right.viewId ? -1 : left.viewId > right.viewId ? 1 : 0));
409
+
360
410
  const visible = new Map(geometry.panes.map((pane) => [pane.pane.id, pane]));
411
+
361
412
  const radius = (rect: SplitRect): string => paneRadius(rect, measurement.width, measurement.height, edges, layout === 'roomy' ? 8 : 0);
413
+
362
414
  // A header squares off the top corners of the body under it.
363
415
  const bodyRadius = (rect: SplitRect, header: number): string => {
364
416
  const corners = radius(rect).split(' ');
417
+
365
418
  if (header > 0) {
366
419
  corners[0] = corners[1] = '0px';
367
420
  }
421
+
368
422
  return corners.join(' ');
369
423
  };
424
+
370
425
  const bounds: SplitViewBounds[] = panes.flatMap((pane) => {
371
426
  const rect = visible.get(pane.id);
372
427
  const header = Math.min(barHeight, rect?.height ?? 0);
428
+
373
429
  const body = rect
374
430
  ? {
375
431
  x: measurement.left + rect.x * measurement.scaleX,
@@ -378,40 +434,54 @@ export function SplitView({
378
434
  height: Math.max(0, rect.height - header) * measurement.scaleY
379
435
  }
380
436
  : { x: 0, y: 0, width: 0, height: 0 };
437
+
381
438
  const borderRadius = rect ? bodyRadius(rect, header) : '0px 0px 0px 0px';
439
+
382
440
  return pane.views.map((viewId) => ({ viewId, paneId: pane.id, active: !!rect && pane.active === viewId, rect: body, borderRadius }));
383
441
  });
442
+
384
443
  const boundsKey = JSON.stringify(bounds);
385
444
  const reportedBounds = useRef('');
386
445
  const boundsCallback = useRef(onViewBoundsChange);
446
+
387
447
  useLayoutEffect(() => {
388
448
  boundsCallback.current = onViewBoundsChange;
389
449
  });
450
+
390
451
  useEffect(() => () => boundsCallback.current?.([]), []);
452
+
391
453
  useLayoutEffect(() => {
392
454
  if (onViewBoundsChange && reportedBounds.current !== boundsKey) {
393
455
  reportedBounds.current = boundsKey;
394
456
  onViewBoundsChange(bounds);
395
457
  }
396
458
  });
459
+
397
460
  const id = (kind: string, viewId: string): string => `${prefix}-${kind}-${encodeURIComponent(viewId)}`;
461
+
398
462
  const startDrag = (pane: SplitPane, event: DragEvent<HTMLButtonElement>, viewId?: string): void => {
399
463
  drag.current = { sourceId: pane.id, viewId, width: event.currentTarget.closest('[data-document-tab]')?.getBoundingClientRect().width ?? 128 };
400
464
  event.dataTransfer.setData('application/x-adecore-view', JSON.stringify({ workspace: prefix, paneId: pane.id, viewId }));
401
465
  event.dataTransfer.effectAllowed = 'move';
466
+
402
467
  if (viewId) {
403
468
  onViewDragStart?.(viewId, event);
404
469
  }
470
+
405
471
  setDragging(true);
406
472
  };
473
+
407
474
  const commandFor = (target: SplitDropTarget): SplitCommand | null => {
408
475
  if (!drag.current) {
409
476
  return null;
410
477
  }
478
+
411
479
  const { sourceId, viewId } = drag.current;
480
+
412
481
  if (target.side === 'center') {
413
482
  return { type: 'swap', sourceId, targetId: target.paneId, viewId };
414
483
  }
484
+
415
485
  return {
416
486
  type: 'move',
417
487
  sourceId,
@@ -423,13 +493,16 @@ export function SplitView({
423
493
  splitId: `${prefix}-split-${serial.current}`
424
494
  };
425
495
  };
496
+
426
497
  const locateDrop = (event: DragEvent<HTMLDivElement>): { target: SplitDropTarget; shape: DropPreviewShape } | null => {
427
498
  const x = (event.clientX - measurement.left) / (measurement.scaleX || 1);
428
499
  const y = (event.clientY - measurement.top) / (measurement.scaleY || 1);
429
500
  const rect = geometry.panes.find((item) => x >= item.x && x <= item.x + item.width && y >= item.y && y <= item.y + item.height);
501
+
430
502
  if (!rect) {
431
503
  return null;
432
504
  }
505
+
433
506
  const header = Math.min(barHeight, rect.height);
434
507
  const inHeader = y < rect.y + header;
435
508
  const strip = inHeader ? paneElement(root.current, 'splitHeader', rect.pane.id) : undefined;
@@ -438,6 +511,7 @@ export function SplitView({
438
511
  const index = inHeader ? tabGapAt(tabRects, event.clientX) : undefined;
439
512
  const target: SplitDropTarget = { paneId: rect.pane.id, side, index };
440
513
  const command = commandFor(target);
514
+
441
515
  if (drag.current) {
442
516
  if (canDrop?.(drag.current, target) === false || !command || updateSplitLayout(value, command) === value) {
443
517
  return null;
@@ -445,13 +519,16 @@ export function SplitView({
445
519
  } else if (!onExternalDrop || !canDropExternal?.(event, target)) {
446
520
  return null;
447
521
  }
522
+
448
523
  if (side === 'center') {
449
524
  return { target, shape: rectDropPreview(rect) };
450
525
  }
526
+
451
527
  if (side) {
452
528
  const horizontal = side === 'left' || side === 'right';
453
529
  const width = horizontal ? Math.max(0, (rect.width - gap) / 2) : rect.width;
454
530
  const height = horizontal ? rect.height : Math.max(0, (rect.height - gap) / 2);
531
+
455
532
  return {
456
533
  target,
457
534
  shape: rectDropPreview({
@@ -462,13 +539,16 @@ export function SplitView({
462
539
  })
463
540
  };
464
541
  }
542
+
465
543
  if (header === 0) {
466
544
  return { target, shape: rectDropPreview(rect) };
467
545
  }
546
+
468
547
  const tabWidth = Math.min(rect.width, (drag.current?.width ?? 128) / (measurement.scaleX || 1));
469
548
  const next = tabRects[index ?? tabRects.length];
470
549
  const left = next?.left ?? tabRects.at(-1)?.right ?? measurement.left + rect.x * measurement.scaleX;
471
550
  const relative = Math.max(0, Math.min(rect.width - tabWidth, (left - measurement.left) / (measurement.scaleX || 1) - rect.x));
551
+
472
552
  return {
473
553
  target,
474
554
  shape: tabDropPreview(
@@ -477,32 +557,43 @@ export function SplitView({
477
557
  )
478
558
  };
479
559
  };
560
+
480
561
  const focusDirection = (key: string): void => {
481
562
  const source = visible.get(value.focused);
563
+
482
564
  if (!source) {
483
565
  return;
484
566
  }
567
+
485
568
  const horizontal = key === 'ArrowLeft' || key === 'ArrowRight';
486
569
  const positive = key === 'ArrowRight' || key === 'ArrowDown';
570
+
487
571
  const center = (rect: SplitRect): number => (horizontal ? rect.x + rect.width / 2 : rect.y + rect.height / 2);
572
+
488
573
  const cross = (rect: SplitRect): number => (horizontal ? rect.y + rect.height / 2 : rect.x + rect.width / 2);
574
+
489
575
  const options = geometry.panes.filter((rect) => rect !== source && (positive ? center(rect) > center(source) : center(rect) < center(source)));
576
+
490
577
  options.sort(
491
578
  (left, right) =>
492
579
  Math.abs(center(left) - center(source)) +
493
580
  2 * Math.abs(cross(left) - cross(source)) -
494
581
  (Math.abs(center(right) - center(source)) + 2 * Math.abs(cross(right) - cross(source)))
495
582
  );
583
+
496
584
  const next = options[0];
585
+
497
586
  if (next) {
498
587
  dispatch({ type: 'focus', paneId: next.pane.id });
499
588
  const element = paneElement(root.current, 'splitPane', next.pane.id);
500
589
  element?.querySelector<HTMLElement>('[role="tab"][aria-selected="true"]')?.focus();
590
+
501
591
  if (!showTabs) {
502
592
  element?.focus();
503
593
  }
504
594
  }
505
595
  };
596
+
506
597
  return useRender({
507
598
  render,
508
599
  ref: [root, ref ?? null],
@@ -520,24 +611,31 @@ export function SplitView({
520
611
  onKeyDown: (event: KeyboardEvent<HTMLDivElement>) => {
521
612
  props.onKeyDown?.(event);
522
613
  const target = event.target as HTMLElement;
614
+
523
615
  const navigation =
524
616
  target === event.currentTarget || target.matches('.ade-split-pane, .ade-split-content') || !!target.closest('.ade-split-header');
617
+
525
618
  if (!event.defaultPrevented && navigation && event.altKey && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.key)) {
526
619
  event.preventDefault();
527
620
  focusDirection(event.key);
528
621
  }
622
+
529
623
  if (event.key === 'Escape') {
530
624
  stopDrag();
531
625
  }
532
626
  },
533
627
  onDragOver: (event: DragEvent<HTMLDivElement>) => {
534
628
  props.onDragOver?.(event);
629
+
535
630
  if (event.defaultPrevented) {
536
631
  showPreview(null);
632
+
537
633
  return;
538
634
  }
635
+
539
636
  const drop = locateDrop(event);
540
637
  showPreview(drop?.shape ?? null);
638
+
541
639
  if (drop) {
542
640
  event.preventDefault();
543
641
  event.dataTransfer.dropEffect = drag.current ? 'move' : 'copy';
@@ -545,21 +643,27 @@ export function SplitView({
545
643
  },
546
644
  onDragLeave: (event: DragEvent<HTMLDivElement>) => {
547
645
  props.onDragLeave?.(event);
646
+
548
647
  if (!(event.relatedTarget instanceof Node) || !event.currentTarget.contains(event.relatedTarget)) {
549
648
  showPreview(null);
550
649
  }
551
650
  },
552
651
  onDrop: (event: DragEvent<HTMLDivElement>) => {
553
652
  props.onDrop?.(event);
653
+
554
654
  if (event.defaultPrevented) {
555
655
  stopDrag();
656
+
556
657
  return;
557
658
  }
659
+
558
660
  const drop = locateDrop(event);
661
+
559
662
  if (drop) {
560
663
  event.preventDefault();
561
664
  event.stopPropagation();
562
665
  const command = commandFor(drop.target);
666
+
563
667
  if (command) {
564
668
  serial.current++;
565
669
  dispatch(command);
@@ -567,6 +671,7 @@ export function SplitView({
567
671
  onExternalDrop?.(drop.target, event);
568
672
  }
569
673
  }
674
+
570
675
  stopDrag();
571
676
  },
572
677
  children: (
@@ -574,6 +679,7 @@ export function SplitView({
574
679
  {panes.map((pane) => {
575
680
  const rect = visible.get(pane.id);
576
681
  const header = Math.min(barHeight, rect?.height ?? 0);
682
+
577
683
  return (
578
684
  <section
579
685
  key={pane.id}
@@ -597,6 +703,7 @@ export function SplitView({
597
703
  <TabStrip
598
704
  items={pane.views.map((viewId) => {
599
705
  const metadata = getTab?.(viewId);
706
+
600
707
  return {
601
708
  ...metadata,
602
709
  id: viewId,
@@ -639,6 +746,7 @@ export function SplitView({
639
746
  const rect = visible.get(pane.id);
640
747
  const active = !!rect && pane.active === viewId;
641
748
  const header = Math.min(barHeight, rect?.height ?? 0);
749
+
642
750
  const position: CSSProperties = rect
643
751
  ? {
644
752
  left: rect.x,
@@ -648,6 +756,7 @@ export function SplitView({
648
756
  borderRadius: bodyRadius(rect, header)
649
757
  }
650
758
  : {};
759
+
651
760
  return (
652
761
  <div
653
762
  key={viewId}