regular-layout 0.0.2 → 0.2.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/LICENSE.md +1 -1
- package/README.md +20 -6
- package/dist/extensions.d.ts +10 -11
- package/dist/index.d.ts +3 -3
- package/dist/index.js +15 -10
- package/dist/index.js.map +4 -4
- package/dist/{common/calculate_split.d.ts → layout/calculate_edge.d.ts} +2 -2
- package/dist/{common → layout}/calculate_intersect.d.ts +7 -9
- package/dist/layout/constants.d.ts +43 -0
- package/dist/{common → layout}/flatten.d.ts +1 -1
- package/dist/{common → layout}/generate_grid.d.ts +3 -3
- package/dist/layout/generate_overlay.d.ts +2 -0
- package/dist/{common → layout}/insert_child.d.ts +3 -2
- package/dist/{common → layout}/redistribute_panel_sizes.d.ts +1 -1
- package/dist/{common → layout}/remove_child.d.ts +1 -1
- package/dist/{common/layout_config.d.ts → layout/types.d.ts} +9 -31
- package/dist/regular-layout-frame.d.ts +2 -2
- package/dist/regular-layout-tab.d.ts +26 -0
- package/dist/regular-layout.d.ts +61 -27
- package/package.json +9 -6
- package/src/extensions.ts +25 -15
- package/src/index.ts +3 -7
- package/src/layout/calculate_edge.ts +209 -0
- package/src/{common → layout}/calculate_intersect.ts +61 -100
- package/src/layout/constants.ts +63 -0
- package/src/{common → layout}/flatten.ts +2 -1
- package/src/{common → layout}/generate_grid.ts +77 -106
- package/src/{common → layout}/generate_overlay.ts +27 -12
- package/src/{common → layout}/insert_child.ts +105 -50
- package/src/{common → layout}/redistribute_panel_sizes.ts +2 -4
- package/src/{common → layout}/remove_child.ts +3 -2
- package/src/{common/layout_config.ts → layout/types.ts} +9 -44
- package/src/regular-layout-frame.ts +83 -68
- package/src/regular-layout-tab.ts +103 -0
- package/src/regular-layout.ts +257 -175
- package/themes/chicago.css +89 -0
- package/themes/fluxbox.css +110 -0
- package/themes/gibson.css +264 -0
- package/themes/hotdog.css +88 -0
- package/themes/lorax.css +129 -0
- package/dist/common/generate_overlay.d.ts +0 -2
- package/src/common/calculate_split.ts +0 -185
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
|
|
10
10
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
11
11
|
|
|
12
|
-
import { GRID_TRACK_COLLAPSE_TOLERANCE
|
|
13
|
-
import {
|
|
12
|
+
import { GRID_TRACK_COLLAPSE_TOLERANCE } from "./constants.ts";
|
|
13
|
+
import type { Layout } from "./types.ts";
|
|
14
14
|
|
|
15
15
|
interface GridCell {
|
|
16
16
|
child: string;
|
|
@@ -20,26 +20,23 @@ interface GridCell {
|
|
|
20
20
|
rowEnd: number;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const result = [sorted[0]];
|
|
30
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
31
|
-
if (
|
|
32
|
-
Math.abs(sorted[i] - result[result.length - 1]) >
|
|
33
|
-
GRID_TRACK_COLLAPSE_TOLERANCE
|
|
34
|
-
) {
|
|
35
|
-
result.push(sorted[i]);
|
|
36
|
-
}
|
|
23
|
+
function dedupe_sort(result: number[], pos: number) {
|
|
24
|
+
if (
|
|
25
|
+
result.length === 0 ||
|
|
26
|
+
Math.abs(pos - result[result.length - 1]) > GRID_TRACK_COLLAPSE_TOLERANCE
|
|
27
|
+
) {
|
|
28
|
+
result.push(pos);
|
|
37
29
|
}
|
|
38
30
|
|
|
39
31
|
return result;
|
|
40
32
|
}
|
|
41
33
|
|
|
42
|
-
function
|
|
34
|
+
function dedupe_positions(positions: number[]): number[] {
|
|
35
|
+
const sorted = positions.sort((a, b) => a - b);
|
|
36
|
+
return sorted.reduce(dedupe_sort, []);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function collect_track_positions(
|
|
43
40
|
panel: Layout,
|
|
44
41
|
orientation: "horizontal" | "vertical",
|
|
45
42
|
start: number,
|
|
@@ -49,51 +46,44 @@ function collectTrackPositions(
|
|
|
49
46
|
return [start, end];
|
|
50
47
|
}
|
|
51
48
|
|
|
49
|
+
const positions: number[] = [start, end];
|
|
52
50
|
if (panel.orientation === orientation) {
|
|
53
|
-
const positions: number[] = [start, end];
|
|
54
51
|
let current = start;
|
|
52
|
+
const range = end - start;
|
|
55
53
|
for (let i = 0; i < panel.children.length; i++) {
|
|
56
54
|
const size = panel.sizes[i];
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
const next = current + size * range;
|
|
56
|
+
positions.push(
|
|
57
|
+
...collect_track_positions(
|
|
58
|
+
panel.children[i],
|
|
59
|
+
orientation,
|
|
60
|
+
current,
|
|
61
|
+
next,
|
|
62
|
+
),
|
|
62
63
|
);
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
current = current + size * (end - start);
|
|
65
|
+
current = next;
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
return dedupePositions(positions);
|
|
69
67
|
} else {
|
|
70
|
-
const allPositions: number[] = [start, end];
|
|
71
68
|
for (const child of panel.children) {
|
|
72
|
-
|
|
73
|
-
child,
|
|
74
|
-
orientation,
|
|
75
|
-
start,
|
|
76
|
-
end,
|
|
69
|
+
positions.push(
|
|
70
|
+
...collect_track_positions(child, orientation, start, end),
|
|
77
71
|
);
|
|
78
|
-
|
|
79
|
-
allPositions.push(...childPositions);
|
|
80
72
|
}
|
|
81
|
-
|
|
82
|
-
return dedupePositions(allPositions);
|
|
83
73
|
}
|
|
74
|
+
|
|
75
|
+
return dedupe_positions(positions);
|
|
84
76
|
}
|
|
85
77
|
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
}
|
|
78
|
+
function find_track_index(positions: number[], value: number): number {
|
|
79
|
+
const index = positions.findIndex(
|
|
80
|
+
(pos) => Math.abs(pos - value) < GRID_TRACK_COLLAPSE_TOLERANCE,
|
|
81
|
+
);
|
|
92
82
|
|
|
93
|
-
|
|
83
|
+
return index === -1 ? 0 : index;
|
|
94
84
|
}
|
|
95
85
|
|
|
96
|
-
function
|
|
86
|
+
function build_cells(
|
|
97
87
|
panel: Layout,
|
|
98
88
|
colPositions: number[],
|
|
99
89
|
rowPositions: number[],
|
|
@@ -107,22 +97,24 @@ function buildCells(
|
|
|
107
97
|
return [
|
|
108
98
|
{
|
|
109
99
|
child: panel.child[selected],
|
|
110
|
-
colStart:
|
|
111
|
-
colEnd:
|
|
112
|
-
rowStart:
|
|
113
|
-
rowEnd:
|
|
100
|
+
colStart: find_track_index(colPositions, colStart),
|
|
101
|
+
colEnd: find_track_index(colPositions, colEnd),
|
|
102
|
+
rowStart: find_track_index(rowPositions, rowStart),
|
|
103
|
+
rowEnd: find_track_index(rowPositions, rowEnd),
|
|
114
104
|
},
|
|
115
105
|
];
|
|
116
106
|
}
|
|
117
107
|
|
|
118
|
-
const cells: GridCell[] = [];
|
|
119
108
|
const { children, sizes, orientation } = panel;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
109
|
+
const isHorizontal = orientation === "horizontal";
|
|
110
|
+
let current = isHorizontal ? colStart : rowStart;
|
|
111
|
+
const range = isHorizontal ? colEnd - colStart : rowEnd - rowStart;
|
|
112
|
+
const cells: GridCell[] = [];
|
|
113
|
+
for (let i = 0; i < children.length; i++) {
|
|
114
|
+
const next = current + sizes[i] * range;
|
|
115
|
+
if (isHorizontal) {
|
|
124
116
|
cells.push(
|
|
125
|
-
...
|
|
117
|
+
...build_cells(
|
|
126
118
|
children[i],
|
|
127
119
|
colPositions,
|
|
128
120
|
rowPositions,
|
|
@@ -132,15 +124,9 @@ function buildCells(
|
|
|
132
124
|
rowEnd,
|
|
133
125
|
),
|
|
134
126
|
);
|
|
135
|
-
|
|
136
|
-
current = next;
|
|
137
|
-
}
|
|
138
|
-
} else {
|
|
139
|
-
let current = rowStart;
|
|
140
|
-
for (let i = 0; i < children.length; i++) {
|
|
141
|
-
const next = current + sizes[i] * (rowEnd - rowStart);
|
|
127
|
+
} else {
|
|
142
128
|
cells.push(
|
|
143
|
-
...
|
|
129
|
+
...build_cells(
|
|
144
130
|
children[i],
|
|
145
131
|
colPositions,
|
|
146
132
|
rowPositions,
|
|
@@ -150,19 +136,19 @@ function buildCells(
|
|
|
150
136
|
next,
|
|
151
137
|
),
|
|
152
138
|
);
|
|
153
|
-
|
|
154
|
-
current = next;
|
|
155
139
|
}
|
|
140
|
+
|
|
141
|
+
current = next;
|
|
156
142
|
}
|
|
157
143
|
|
|
158
144
|
return cells;
|
|
159
145
|
}
|
|
160
146
|
|
|
161
147
|
const host_template = (rowTemplate: string, colTemplate: string) =>
|
|
162
|
-
`:host {
|
|
148
|
+
`:host ::slotted(*){display:none}:host{display:grid;grid-template-rows:${rowTemplate};grid-template-columns:${colTemplate}}`;
|
|
163
149
|
|
|
164
150
|
const child_template = (slot: string, rowPart: string, colPart: string) =>
|
|
165
|
-
`:host ::slotted([
|
|
151
|
+
`:host ::slotted([name="${slot}"]){display:flex;grid-column:${colPart};grid-row:${rowPart}}`;
|
|
166
152
|
|
|
167
153
|
/**
|
|
168
154
|
* Generates CSS Grid styles to render a layout tree.
|
|
@@ -190,8 +176,8 @@ const child_template = (slot: string, rowPart: string, colPart: string) =>
|
|
|
190
176
|
* const css = create_css_grid_layout(layout);
|
|
191
177
|
* // Returns CSS like:
|
|
192
178
|
* // :host { display: grid; grid-template-columns: 25% 75%; ... }
|
|
193
|
-
* // :host ::slotted([
|
|
194
|
-
* // :host ::slotted([
|
|
179
|
+
* // :host ::slotted([name=sidebar]) { grid-column: 1; grid-row: 1; }
|
|
180
|
+
* // :host ::slotted([name=main]) { grid-column: 2; grid-row: 1; }
|
|
195
181
|
* ```
|
|
196
182
|
*/
|
|
197
183
|
export function create_css_grid_layout(
|
|
@@ -199,51 +185,36 @@ export function create_css_grid_layout(
|
|
|
199
185
|
round: boolean = false,
|
|
200
186
|
overlay?: [string, string],
|
|
201
187
|
): string {
|
|
202
|
-
if (overlay) {
|
|
203
|
-
layout = remove_child(layout, overlay[0]);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
188
|
if (layout.type === "child-panel") {
|
|
207
189
|
const selected = layout.selected ?? 0;
|
|
208
190
|
return `${host_template("100%", "100%")}\n${child_template(layout.child[selected], "1", "1")}`;
|
|
209
191
|
}
|
|
210
192
|
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
.map((s) => `${round ? Math.round(s * 100) : s * 100}%`)
|
|
229
|
-
.join(" ");
|
|
230
|
-
|
|
231
|
-
const cells = buildCells(layout, colPositions, rowPositions, 0, 1, 0, 1);
|
|
193
|
+
const createTemplate = (positions: number[]) => {
|
|
194
|
+
const sizes = positions
|
|
195
|
+
.slice(0, -1)
|
|
196
|
+
.map((pos, i) => positions[i + 1] - pos);
|
|
197
|
+
return sizes
|
|
198
|
+
.map((s) => `${round ? Math.round(s * 100) : s * 100}fr`)
|
|
199
|
+
.join(" ");
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const colPositions = collect_track_positions(layout, "horizontal", 0, 1);
|
|
203
|
+
const colTemplate = createTemplate(colPositions);
|
|
204
|
+
const rowPositions = collect_track_positions(layout, "vertical", 0, 1);
|
|
205
|
+
const rowTemplate = createTemplate(rowPositions);
|
|
206
|
+
const formatGridLine = (start: number, end: number) =>
|
|
207
|
+
end - start === 1 ? `${start + 1}` : `${start + 1} / ${end + 1}`;
|
|
208
|
+
|
|
209
|
+
const cells = build_cells(layout, colPositions, rowPositions, 0, 1, 0, 1);
|
|
232
210
|
const css = [host_template(rowTemplate, colTemplate)];
|
|
233
211
|
for (const cell of cells) {
|
|
234
|
-
const colPart =
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
: `${cell.colStart + 1} / ${cell.colEnd + 1}`;
|
|
238
|
-
const rowPart =
|
|
239
|
-
cell.rowEnd - cell.rowStart === 1
|
|
240
|
-
? `${cell.rowStart + 1}`
|
|
241
|
-
: `${cell.rowStart + 1} / ${cell.rowEnd + 1}`;
|
|
242
|
-
|
|
243
|
-
css.push(`${child_template(cell.child, rowPart, colPart)}`);
|
|
212
|
+
const colPart = formatGridLine(cell.colStart, cell.colEnd);
|
|
213
|
+
const rowPart = formatGridLine(cell.rowStart, cell.rowEnd);
|
|
214
|
+
css.push(child_template(cell.child, rowPart, colPart));
|
|
244
215
|
if (cell.child === overlay?.[1]) {
|
|
245
|
-
css.push(
|
|
246
|
-
css.push(`:host ::slotted([
|
|
216
|
+
css.push(child_template(overlay[0], rowPart, colPart));
|
|
217
|
+
css.push(`:host ::slotted([name=${overlay[0]}]){z-index:1}`);
|
|
247
218
|
}
|
|
248
219
|
}
|
|
249
220
|
|
|
@@ -9,17 +9,32 @@
|
|
|
9
9
|
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
|
|
10
10
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
11
11
|
|
|
12
|
-
import type { LayoutPath } from "./
|
|
12
|
+
import type { LayoutPath } from "./types";
|
|
13
13
|
|
|
14
|
-
export function updateOverlaySheet(
|
|
15
|
-
|
|
16
|
-
box,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
export function updateOverlaySheet(
|
|
15
|
+
slot: string,
|
|
16
|
+
box: DOMRect,
|
|
17
|
+
style: CSSStyleDeclaration,
|
|
18
|
+
drag_target: LayoutPath<undefined> | null,
|
|
19
|
+
) {
|
|
20
|
+
if (!drag_target) {
|
|
21
|
+
return `:host ::slotted([name="${slot}"]){display:none;}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
view_window: { row_start, row_end, col_start, col_end },
|
|
26
|
+
} = drag_target;
|
|
27
|
+
|
|
28
|
+
const box_height =
|
|
29
|
+
box.height - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom);
|
|
30
|
+
|
|
31
|
+
const box_width =
|
|
32
|
+
box.width - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
|
|
33
|
+
|
|
34
|
+
const top = row_start * box_height + parseFloat(style.paddingTop);
|
|
35
|
+
const left = col_start * box_width + parseFloat(style.paddingLeft);
|
|
36
|
+
const height = (row_end - row_start) * box_height;
|
|
37
|
+
const width = (col_end - col_start) * box_width;
|
|
38
|
+
const css = `display:flex;position:absolute!important;z-index:1;top:${top}px;left:${left}px;height:${height}px;width:${width}px;`;
|
|
39
|
+
return `::slotted([name="${slot}"]){${css}}`;
|
|
25
40
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
|
|
10
10
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
11
11
|
|
|
12
|
-
import type { Layout } from "./
|
|
12
|
+
import type { Layout } from "./types.ts";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Inserts a new child panel into the layout tree at a specified location.
|
|
@@ -22,15 +22,20 @@ import type { Layout } from "./layout_config.ts";
|
|
|
22
22
|
* at root level.
|
|
23
23
|
* @param orientation - Orientation for newly created split panels. Defaults to
|
|
24
24
|
* "horizontal".
|
|
25
|
+
* @param is_edge - If true, create the split at the parent level.
|
|
25
26
|
* @returns A new layout tree with the child inserted (original is not mutated).
|
|
26
27
|
*/
|
|
27
28
|
export function insert_child(
|
|
28
29
|
panel: Layout,
|
|
29
30
|
child: string,
|
|
30
31
|
path: number[],
|
|
31
|
-
orientation
|
|
32
|
-
is_edge?: boolean,
|
|
32
|
+
orientation?: "horizontal" | "vertical",
|
|
33
33
|
): Layout {
|
|
34
|
+
const createChildPanel = (childId: string): Layout => ({
|
|
35
|
+
type: "child-panel",
|
|
36
|
+
child: [childId],
|
|
37
|
+
});
|
|
38
|
+
|
|
34
39
|
if (path.length === 0) {
|
|
35
40
|
// Insert at root level
|
|
36
41
|
if (panel.type === "child-panel") {
|
|
@@ -39,91 +44,136 @@ export function insert_child(
|
|
|
39
44
|
type: "child-panel",
|
|
40
45
|
child: [child, ...panel.child],
|
|
41
46
|
};
|
|
47
|
+
} else if (orientation) {
|
|
48
|
+
// When inserting at edge of root, wrap the entire panel in a new split
|
|
49
|
+
return {
|
|
50
|
+
type: "split-panel",
|
|
51
|
+
orientation: orientation,
|
|
52
|
+
children: [createChildPanel(child), panel],
|
|
53
|
+
sizes: [0.5, 0.5],
|
|
54
|
+
};
|
|
42
55
|
} else {
|
|
43
56
|
// Append to existing split-panel
|
|
44
|
-
const newChildren = [
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
type: "child-panel",
|
|
48
|
-
child: [child],
|
|
49
|
-
} as Layout,
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
const numChildren = newChildren.length;
|
|
53
|
-
const newSizes = Array(numChildren).fill(1 / numChildren);
|
|
57
|
+
const newChildren = [...panel.children, createChildPanel(child)];
|
|
58
|
+
const newSizes = [...panel.sizes, 1 / (newChildren.length - 1)];
|
|
54
59
|
return {
|
|
55
60
|
...panel,
|
|
56
61
|
children: newChildren,
|
|
57
|
-
sizes: newSizes,
|
|
62
|
+
sizes: redistribute(newSizes),
|
|
58
63
|
};
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
// Navigate down the path
|
|
63
68
|
const [index, ...restPath] = path;
|
|
69
|
+
|
|
70
|
+
// Special case: when orientation is provided and restPath is empty, handle edge insertion
|
|
71
|
+
if (orientation && restPath.length === 0) {
|
|
72
|
+
// If panel is a split-panel with the same orientation, insert into its children
|
|
73
|
+
if (panel.type === "split-panel" && panel.orientation === orientation) {
|
|
74
|
+
const newChildren = [...panel.children];
|
|
75
|
+
newChildren.splice(index, 0, createChildPanel(child));
|
|
76
|
+
const newSizes = [...panel.sizes];
|
|
77
|
+
newSizes.splice(index, 0, 1 / (newChildren.length - 1));
|
|
78
|
+
return {
|
|
79
|
+
...panel,
|
|
80
|
+
children: newChildren,
|
|
81
|
+
sizes: redistribute(newSizes),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Otherwise, wrap the entire panel in a new split at the edge
|
|
86
|
+
const children =
|
|
87
|
+
index === 0
|
|
88
|
+
? [createChildPanel(child), panel]
|
|
89
|
+
: [panel, createChildPanel(child)];
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
type: "split-panel",
|
|
93
|
+
orientation: orientation,
|
|
94
|
+
children,
|
|
95
|
+
sizes: [0.5, 0.5],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
64
99
|
if (panel.type === "child-panel") {
|
|
65
|
-
//
|
|
66
|
-
//
|
|
100
|
+
// Stack into child array only when ALL of these conditions are met:
|
|
101
|
+
// 1. Path has exactly one element (restPath is empty)
|
|
102
|
+
// 2. Orientation was NOT explicitly provided (orientation is undefined)
|
|
103
|
+
// 3. Index is within the valid stacking range [0, child.length]
|
|
104
|
+
if (
|
|
105
|
+
restPath.length === 0 &&
|
|
106
|
+
orientation === undefined &&
|
|
107
|
+
index >= 0 &&
|
|
108
|
+
index <= panel.child.length
|
|
109
|
+
) {
|
|
110
|
+
const newChild = [...panel.child];
|
|
111
|
+
newChild.splice(index, 0, child);
|
|
112
|
+
return {
|
|
113
|
+
...panel,
|
|
114
|
+
child: newChild,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Otherwise, wrap in a split panel and recurse
|
|
67
119
|
const newPanel: Layout = {
|
|
68
120
|
type: "split-panel",
|
|
69
|
-
orientation,
|
|
121
|
+
orientation: orientation || "horizontal",
|
|
70
122
|
children: [panel],
|
|
71
123
|
sizes: [1],
|
|
72
124
|
};
|
|
73
125
|
|
|
74
|
-
return insert_child(newPanel, child, path, orientation
|
|
126
|
+
return insert_child(newPanel, child, path, orientation);
|
|
75
127
|
}
|
|
76
128
|
|
|
77
129
|
if (restPath.length === 0 || index === panel.children.length) {
|
|
78
|
-
if (
|
|
79
|
-
panel
|
|
80
|
-
|
|
130
|
+
if (orientation && panel.children[index]) {
|
|
131
|
+
// When inserting at an edge, create a split panel with the new child and existing child
|
|
132
|
+
const newSplitPanel: Layout = {
|
|
133
|
+
type: "split-panel",
|
|
134
|
+
orientation: orientation,
|
|
135
|
+
children: [createChildPanel(child), panel.children[index]],
|
|
136
|
+
sizes: [0.5, 0.5],
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const newChildren = [...panel.children];
|
|
140
|
+
newChildren[index] = newSplitPanel;
|
|
141
|
+
return {
|
|
142
|
+
...panel,
|
|
143
|
+
children: newChildren,
|
|
144
|
+
sizes: redistribute(panel.sizes),
|
|
145
|
+
};
|
|
81
146
|
}
|
|
82
147
|
|
|
83
148
|
// Insert at this level at the specified index
|
|
84
149
|
const newChildren = [...panel.children];
|
|
85
|
-
newChildren.splice(index, 0,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const numChildren = newChildren.length;
|
|
91
|
-
const newSizes = Array(numChildren).fill(1 / numChildren);
|
|
150
|
+
newChildren.splice(index, 0, createChildPanel(child));
|
|
151
|
+
const newSizes = [...panel.sizes];
|
|
152
|
+
newSizes.splice(index, 0, 1 / (newChildren.length - 1));
|
|
92
153
|
return {
|
|
93
154
|
...panel,
|
|
94
155
|
children: newChildren,
|
|
95
|
-
sizes: newSizes,
|
|
156
|
+
sizes: redistribute(newSizes),
|
|
96
157
|
};
|
|
97
158
|
}
|
|
98
159
|
|
|
99
160
|
const targetChild = panel.children[index];
|
|
100
|
-
if (targetChild.type === "child-panel" && restPath.length > 0) {
|
|
101
|
-
// Need to split this child-panel
|
|
102
|
-
const oppositeOrientation =
|
|
103
|
-
panel.orientation === "horizontal" ? "vertical" : "horizontal";
|
|
104
|
-
|
|
105
|
-
const newSplitPanel = insert_child(
|
|
106
|
-
targetChild,
|
|
107
|
-
child,
|
|
108
|
-
restPath,
|
|
109
|
-
oppositeOrientation,
|
|
110
|
-
is_edge,
|
|
111
|
-
);
|
|
112
161
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
162
|
+
// Determine the orientation to pass down when navigating into a child-panel
|
|
163
|
+
const childOrientation =
|
|
164
|
+
targetChild.type === "child-panel" &&
|
|
165
|
+
restPath.length > 0 &&
|
|
166
|
+
orientation !== undefined
|
|
167
|
+
? panel.orientation === "horizontal"
|
|
168
|
+
? "vertical"
|
|
169
|
+
: "horizontal"
|
|
170
|
+
: orientation;
|
|
120
171
|
|
|
121
172
|
const updatedChild = insert_child(
|
|
122
173
|
targetChild,
|
|
123
174
|
child,
|
|
124
175
|
restPath,
|
|
125
|
-
|
|
126
|
-
is_edge,
|
|
176
|
+
childOrientation,
|
|
127
177
|
);
|
|
128
178
|
|
|
129
179
|
const newChildren = [...panel.children];
|
|
@@ -133,3 +183,8 @@ export function insert_child(
|
|
|
133
183
|
children: newChildren,
|
|
134
184
|
};
|
|
135
185
|
}
|
|
186
|
+
|
|
187
|
+
function redistribute(arr: number[]): number[] {
|
|
188
|
+
const total = arr.reduce((sum, val) => sum + val, 0);
|
|
189
|
+
return arr.map((val) => val / total);
|
|
190
|
+
}
|
|
@@ -9,10 +9,8 @@
|
|
|
9
9
|
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
|
|
10
10
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
type Layout,
|
|
15
|
-
} from "./layout_config.ts";
|
|
12
|
+
import { MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD } from "./constants.ts";
|
|
13
|
+
import type { Layout } from "./types.ts";
|
|
16
14
|
|
|
17
15
|
/**
|
|
18
16
|
* Adjusts panel sizes during a drag operation on a divider.
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
|
|
10
10
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
11
11
|
|
|
12
|
-
import type { Layout, TabLayout } from "./
|
|
13
|
-
import { EMPTY_PANEL } from "./
|
|
12
|
+
import type { Layout, TabLayout } from "./types.ts";
|
|
13
|
+
import { EMPTY_PANEL } from "./types.ts";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Removes a child panel from the layout tree by its name.
|
|
@@ -36,6 +36,7 @@ export function remove_child(panel: Layout, child: string): Layout {
|
|
|
36
36
|
child: newChild,
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
+
|
|
39
40
|
return structuredClone(panel);
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -9,34 +9,10 @@
|
|
|
9
9
|
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
|
|
10
10
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* The percentage of the maximum resize distance that will be clamped.
|
|
14
|
-
*
|
|
15
|
-
*/
|
|
16
|
-
export const MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD = 0.15;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Threshold from panel edge that is considered a split vs drop action.
|
|
20
|
-
*/
|
|
21
|
-
export const SPLIT_EDGE_TOLERANCE = 0.25;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Tolerance threshold for considering two grid track positions as identical.
|
|
25
|
-
*
|
|
26
|
-
* When collecting and deduplicating track positions, any positions closer than
|
|
27
|
-
* this value are treated as the same position to avoid redundant grid tracks.
|
|
28
|
-
*/
|
|
29
|
-
export const GRID_TRACK_COLLAPSE_TOLERANCE = 0.001;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* The overlay default behavior.
|
|
33
|
-
*/
|
|
34
|
-
export const OVERLAY_DEFAULT: OverlayMode = "absolute";
|
|
35
|
-
|
|
36
12
|
/**
|
|
37
13
|
* The overlay behavior type.
|
|
38
14
|
*/
|
|
39
|
-
export type OverlayMode = "grid" | "absolute"
|
|
15
|
+
export type OverlayMode = "grid" | "absolute";
|
|
40
16
|
|
|
41
17
|
/**
|
|
42
18
|
* The representation of a CSS grid, in JSON form.
|
|
@@ -64,6 +40,11 @@ export interface ViewWindow {
|
|
|
64
40
|
* Child panels are arranged either horizontally (side by side) or vertically
|
|
65
41
|
* (stacked), via the `orientation` property `"horizzontal"` and `"vertical"`
|
|
66
42
|
* (respectively).
|
|
43
|
+
*
|
|
44
|
+
* While the type structure of `SplitLayout` allows nesting levels with the same
|
|
45
|
+
* `orientation`, calling `RegularLayout.restore` with such a `Layout` will be
|
|
46
|
+
* flattened to the equivalent layout with every child guaranteed to have the
|
|
47
|
+
* opposite `orientation` as its parent.
|
|
67
48
|
*/
|
|
68
49
|
export interface SplitLayout {
|
|
69
50
|
type: "split-panel";
|
|
@@ -102,31 +83,15 @@ export interface LayoutDivider {
|
|
|
102
83
|
export interface LayoutPath<T = undefined> {
|
|
103
84
|
type: "layout-path";
|
|
104
85
|
slot: string;
|
|
105
|
-
panel: TabLayout;
|
|
106
86
|
path: number[];
|
|
107
87
|
view_window: ViewWindow;
|
|
88
|
+
column: number;
|
|
89
|
+
row: number;
|
|
108
90
|
column_offset: number;
|
|
109
91
|
row_offset: number;
|
|
110
92
|
orientation: Orientation;
|
|
111
93
|
is_edge: boolean;
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Recursively iterates over all child panel names in the layout tree, yielding
|
|
117
|
-
* panel names in depth-first order.
|
|
118
|
-
*
|
|
119
|
-
* @param panel - The layout tree to iterate over
|
|
120
|
-
* @returns Generator yielding child panel names
|
|
121
|
-
*/
|
|
122
|
-
export function* iter_panel_children(panel: Layout): Generator<string> {
|
|
123
|
-
if (panel.type === "split-panel") {
|
|
124
|
-
for (const child of panel.children) {
|
|
125
|
-
yield* iter_panel_children(child);
|
|
126
|
-
}
|
|
127
|
-
} else {
|
|
128
|
-
yield* panel.child;
|
|
129
|
-
}
|
|
94
|
+
layout: T;
|
|
130
95
|
}
|
|
131
96
|
|
|
132
97
|
/**
|