@adecore/ui 0.39.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.
- package/dist/AccentSwatches.js +2 -3
- package/dist/Banner.js +1 -6
- package/dist/ChoiceCards.js +0 -5
- package/dist/ColorSwatch.js +1 -4
- package/dist/ColorWheelPicker.js +3 -6
- package/dist/DockShell.js +3 -5
- package/dist/EmptyState.js +0 -1
- package/dist/ErrorBoundary.js +1 -5
- package/dist/Field.js +2 -6
- package/dist/FileIcon.js +2 -4
- package/dist/Icon.js +1 -2
- package/dist/IconButton.js +1 -1
- package/dist/IconPicker.js +3 -7
- package/dist/Input.js +1 -2
- package/dist/Kbd.js +1 -3
- package/dist/LazyBoundary.js +2 -3
- package/dist/Meter.js +0 -1
- package/dist/MiddleTruncate.js +2 -6
- package/dist/PanelEmpty.js +0 -1
- package/dist/PanelHeader.js +1 -4
- package/dist/Pill.js +1 -6
- package/dist/ProjectSwitcher.js +1 -1
- package/dist/PromptDialog.js +5 -8
- package/dist/RelativeTime.js +1 -1
- package/dist/SegmentBar.js +1 -2
- package/dist/Segmented.js +2 -5
- package/dist/Select.js +1 -4
- package/dist/ShortcutHints.js +2 -3
- package/dist/Slider.js +0 -1
- package/dist/SlidingColumn.js +2 -4
- package/dist/SplitButton.js +1 -5
- package/dist/TextMenu.js +1 -5
- package/dist/Tile.js +0 -4
- package/dist/Toasts.js +1 -9
- package/dist/Tooltip.js +1 -1
- package/dist/UIProvider.js +0 -5
- package/dist/Waveform.js +4 -2
- package/dist/Wipe.js +1 -6
- package/dist/ZoomControls.js +0 -1
- package/dist/clipboard.js +2 -5
- package/dist/combobox/parts.js +3 -9
- package/dist/command-palette/parts.js +5 -14
- package/dist/dialog/parts.js +5 -10
- package/dist/dialog-layer.js +2 -3
- package/dist/error-boundary.js +4 -20
- package/dist/file-icon.js +4 -9
- package/dist/file-tree/empty-lines.js +2 -8
- package/dist/file-tree/use-empty-lines.js +1 -5
- package/dist/file-tree/use-slots.js +6 -10
- package/dist/format/datetime.js +32 -29
- package/dist/format/duration.js +2 -10
- package/dist/format/locale.js +19 -39
- package/dist/format/number.js +9 -22
- package/dist/format/regions.js +4 -7
- package/dist/icon-picker.js +5 -14
- package/dist/key-value-list/parts.js +1 -5
- package/dist/lazy.js +4 -19
- package/dist/locales.js +2 -6
- package/dist/menu/parts.js +61 -133
- package/dist/middle-truncate.js +2 -3
- package/dist/placement.js +0 -1
- package/dist/popover/parts.js +1 -5
- package/dist/popup-layer.js +2 -4
- package/dist/prefetch.js +2 -5
- package/dist/selection.js +1 -5
- package/dist/settings/MasterDetail.js +1 -7
- package/dist/settings/SettingsDialog.js +1 -2
- package/dist/settings/SettingsRow.js +2 -5
- package/dist/settings/SettingsSection.js +1 -1
- package/dist/shortcut-hints.js +1 -5
- package/dist/shortcut.js +1 -2
- package/dist/tabs/overflow.js +1 -6
- package/dist/tabs/parts.js +69 -95
- package/dist/testing/dedupe.js +5 -7
- package/dist/tint.js +3 -5
- package/dist/toast-store.js +2 -10
- package/dist/tree/use-tree-shift.js +2 -6
- package/dist/useColumnResize.js +9 -10
- package/dist/useNow.js +2 -9
- package/dist/workspace/DropPreview.js +1 -5
- package/dist/workspace/preview.js +4 -9
- package/package.json +9 -9
- package/src/AccentSwatches.tsx +9 -3
- package/src/Banner.tsx +5 -11
- package/src/Button.tsx +4 -0
- package/src/Checkbox.tsx +3 -0
- package/src/ChoiceCards.tsx +12 -11
- package/src/CloseButton.tsx +1 -0
- package/src/ColorSwatch.tsx +7 -8
- package/src/ColorWheelPicker.tsx +28 -12
- package/src/ColumnResizeHandle.tsx +1 -0
- package/src/CopyButton.tsx +6 -0
- package/src/DisabledReason.tsx +1 -0
- package/src/DockShell.tsx +11 -10
- package/src/EmptyState.tsx +10 -6
- package/src/ErrorBoundary.tsx +13 -10
- package/src/Field.tsx +11 -13
- package/src/FileIcon.tsx +4 -4
- package/src/Icon.tsx +2 -4
- package/src/IconButton.tsx +14 -5
- package/src/IconPicker.tsx +30 -15
- package/src/Input.tsx +11 -9
- package/src/Kbd.tsx +8 -7
- package/src/LazyBoundary.tsx +3 -3
- package/src/Meter.tsx +7 -2
- package/src/MiddleTruncate.tsx +15 -6
- package/src/PanelEmpty.tsx +9 -7
- package/src/PanelHeader.tsx +2 -5
- package/src/Pill.tsx +7 -8
- package/src/ProjectSwitcher.tsx +16 -6
- package/src/PromptDialog.tsx +31 -26
- package/src/RelativeTime.tsx +6 -1
- package/src/SegmentBar.tsx +22 -11
- package/src/Segmented.tsx +16 -11
- package/src/Select.tsx +7 -4
- package/src/SelectSearch.tsx +3 -0
- package/src/ShortcutHints.tsx +24 -3
- package/src/Skeleton.tsx +1 -0
- package/src/Slider.tsx +4 -1
- package/src/SlidingColumn.tsx +18 -14
- package/src/Spinner.tsx +1 -0
- package/src/SplitButton.tsx +8 -8
- package/src/Stepper.tsx +6 -0
- package/src/Switch.tsx +2 -0
- package/src/TextMenu.tsx +2 -5
- package/src/Tile.tsx +2 -6
- package/src/Toasts.tsx +8 -12
- package/src/Tooltip.tsx +10 -5
- package/src/UIProvider.tsx +7 -7
- package/src/Waveform.tsx +41 -12
- package/src/Wipe.tsx +5 -6
- package/src/ZoomControls.tsx +5 -2
- package/src/clipboard.ts +4 -5
- package/src/combobox/parts.tsx +18 -14
- package/src/command-palette/context.ts +2 -0
- package/src/command-palette/index.parts.ts +1 -0
- package/src/command-palette/parts.tsx +20 -14
- package/src/command-palette/virtual.tsx +21 -0
- package/src/dialog/parts.tsx +10 -13
- package/src/dialog-layer.ts +5 -3
- package/src/error-boundary.ts +7 -20
- package/src/file-icon.ts +8 -9
- package/src/file-tree/empty-lines.ts +24 -8
- package/src/file-tree/helpers.ts +34 -0
- package/src/file-tree/highlight.ts +19 -0
- package/src/file-tree/index.parts.ts +1 -0
- package/src/file-tree/parts.tsx +41 -5
- package/src/file-tree/use-empty-lines.ts +22 -5
- package/src/file-tree/use-file-tree-shift.tsx +10 -0
- package/src/file-tree/use-file-tree.ts +2 -0
- package/src/file-tree/use-slots.ts +77 -12
- package/src/format/datetime.ts +42 -17
- package/src/format/duration.ts +28 -10
- package/src/format/index.ts +3 -0
- package/src/format/locale.ts +33 -42
- package/src/format/number.ts +21 -27
- package/src/format/regions.ts +4 -7
- package/src/icon-picker.ts +23 -16
- package/src/index.ts +8 -0
- package/src/key-value-list/parts.tsx +1 -5
- package/src/lazy-landings.ts +2 -0
- package/src/lazy.tsx +23 -19
- package/src/locales.ts +2 -6
- package/src/match.ts +14 -0
- package/src/menu/parts.tsx +30 -55
- package/src/middle-truncate.ts +12 -3
- package/src/modality.ts +4 -0
- package/src/placement.ts +2 -1
- package/src/platform.ts +2 -0
- package/src/popover/parts.tsx +3 -5
- package/src/popup-layer.ts +6 -4
- package/src/prefetch.ts +18 -5
- package/src/project-switcher-row.tsx +2 -0
- package/src/segmented-keys.ts +6 -0
- package/src/select-shared.ts +2 -0
- package/src/selection.ts +5 -5
- package/src/settings/ConfirmDialog.tsx +3 -0
- package/src/settings/MasterDetail.tsx +3 -9
- package/src/settings/SettingsDialog.tsx +27 -9
- package/src/settings/SettingsRow.tsx +16 -10
- package/src/settings/SettingsSection.tsx +10 -5
- package/src/settings/index.ts +3 -0
- package/src/settings/target.ts +1 -0
- package/src/shortcut-hints.ts +10 -5
- package/src/shortcut.ts +26 -6
- package/src/tabs/overflow.ts +8 -6
- package/src/tabs/parts.tsx +50 -26
- package/src/testing/dedupe.ts +27 -7
- package/src/testing/fake-source.ts +1 -0
- package/src/tint.ts +3 -5
- package/src/toast-store.ts +20 -14
- package/src/tree/parts.tsx +8 -0
- package/src/tree/shift.ts +6 -0
- package/src/tree/use-tree-shift.tsx +23 -7
- package/src/useAsyncAction.ts +8 -0
- package/src/useColumnResize.ts +24 -15
- package/src/useContentSize.ts +5 -0
- package/src/useMeasuredWidth.ts +1 -0
- package/src/useNow.ts +6 -11
- package/src/workspace/DropPreview.tsx +3 -5
- package/src/workspace/SplitView.tsx +109 -0
- package/src/workspace/TabStrip.tsx +42 -0
- package/src/workspace/Workspace.tsx +5 -0
- package/src/workspace/context.ts +1 -0
- package/src/workspace/geometry.ts +49 -0
- package/src/workspace/model.ts +72 -0
- 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}
|