@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.
- 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 +10 -10
- 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
package/src/workspace/model.ts
CHANGED
|
@@ -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':
|
package/src/workspace/preview.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
/*
|
|
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
|
-
*
|
|
79
|
-
*
|
|
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
|
}
|