regular-layout 0.0.1 → 0.0.2
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/README.md +9 -4
- package/dist/common/calculate_split.d.ts +14 -1
- package/dist/common/insert_child.d.ts +1 -1
- package/dist/common/layout_config.d.ts +14 -3
- package/dist/extensions.d.ts +16 -0
- package/dist/index.js +6 -6
- package/dist/index.js.map +3 -3
- package/dist/regular-layout-frame.d.ts +4 -0
- package/dist/regular-layout.d.ts +3 -2
- package/package.json +1 -1
- package/src/common/calculate_intersect.ts +19 -9
- package/src/common/calculate_split.ts +160 -28
- package/src/common/flatten.ts +4 -0
- package/src/common/generate_grid.ts +5 -3
- package/src/common/insert_child.ts +21 -15
- package/src/common/layout_config.ts +17 -4
- package/src/common/redistribute_panel_sizes.ts +5 -1
- package/src/common/remove_child.ts +37 -13
- package/src/extensions.ts +26 -2
- package/src/regular-layout-frame.ts +84 -13
- package/src/regular-layout.ts +57 -18
|
@@ -72,25 +72,35 @@ function calculate_intersection_recursive(
|
|
|
72
72
|
row: number,
|
|
73
73
|
panel: Layout,
|
|
74
74
|
check_dividers: boolean,
|
|
75
|
-
parent_orientation: "horizontal" | "vertical" =
|
|
75
|
+
parent_orientation: "horizontal" | "vertical" | null = null,
|
|
76
76
|
view_window: ViewWindow = structuredClone(VIEW_WINDOW),
|
|
77
77
|
path: number[] = [],
|
|
78
78
|
): LayoutPath | null | LayoutDivider {
|
|
79
|
+
if (column < 0 || row < 0 || column > 1 || row > 1) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
79
83
|
// Base case: if this is a child panel, return its name
|
|
80
84
|
if (panel.type === "child-panel") {
|
|
85
|
+
const selected = panel.selected ?? 0;
|
|
86
|
+
const column_offset =
|
|
87
|
+
(column - view_window.col_start) /
|
|
88
|
+
(view_window.col_end - view_window.col_start);
|
|
89
|
+
const row_offset =
|
|
90
|
+
(row - view_window.row_start) /
|
|
91
|
+
(view_window.row_end - view_window.row_start);
|
|
92
|
+
|
|
81
93
|
return {
|
|
82
94
|
type: "layout-path",
|
|
83
95
|
box: undefined,
|
|
84
|
-
slot: panel.child,
|
|
96
|
+
slot: panel.child[selected],
|
|
97
|
+
panel: structuredClone(panel),
|
|
85
98
|
path: path,
|
|
86
99
|
view_window: view_window,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
(row - view_window.row_start) /
|
|
92
|
-
(view_window.row_end - view_window.row_start),
|
|
93
|
-
orientation: parent_orientation,
|
|
100
|
+
is_edge: false,
|
|
101
|
+
column_offset,
|
|
102
|
+
row_offset,
|
|
103
|
+
orientation: parent_orientation || "horizontal",
|
|
94
104
|
};
|
|
95
105
|
}
|
|
96
106
|
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
2
|
+
// ░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░█░░░█▀█░█░█░█▀█░█░█░▀█▀░▀▄░░░░░░░░
|
|
3
|
+
// ░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░█░░░█▀█░░█░░█░█░█░█░░█░░░▄▀░░░░░░░
|
|
4
|
+
// ░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░░▀░░▀░░░░░░░░░
|
|
5
|
+
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
6
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
7
|
+
// ┃ * Copyright (c) 2026, the Regular Layout Authors. This file is part * ┃
|
|
8
|
+
// ┃ * of the Regular Layout library, distributed under the terms of the * ┃
|
|
9
|
+
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
|
|
10
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
11
|
+
|
|
1
12
|
import { calculate_intersection } from "./calculate_intersect";
|
|
2
13
|
import { insert_child } from "./insert_child";
|
|
3
14
|
import {
|
|
@@ -6,48 +17,169 @@ import {
|
|
|
6
17
|
type LayoutPath,
|
|
7
18
|
} from "./layout_config";
|
|
8
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Calculates an insertion point (which may involve splitting a single
|
|
22
|
+
* `"child-panel"` into a new `"split-panel"`), based on the cursor position.
|
|
23
|
+
* *
|
|
24
|
+
* @param col - The cursor column.
|
|
25
|
+
* @param row - The cursor row.
|
|
26
|
+
* @param panel - The `Layout` to insert into.
|
|
27
|
+
* @param slot - The slot identifier where the insert should occur
|
|
28
|
+
* @param drop_target - The `LayoutPath` (from `calculateIntersect`) of the
|
|
29
|
+
* panel to either insert next to, or split by.
|
|
30
|
+
* @returns A new `LayoutPath` reflecting the updated (maybe) `"split-panel"`,
|
|
31
|
+
* which is enough to draw the overlay.
|
|
32
|
+
*/
|
|
9
33
|
export function calculate_split(
|
|
10
34
|
col: number,
|
|
11
35
|
row: number,
|
|
12
36
|
panel: Layout,
|
|
13
37
|
slot: string,
|
|
14
|
-
|
|
38
|
+
drop_target: LayoutPath,
|
|
15
39
|
): LayoutPath {
|
|
16
40
|
if (
|
|
17
|
-
|
|
18
|
-
|
|
41
|
+
drop_target.column_offset < SPLIT_EDGE_TOLERANCE ||
|
|
42
|
+
drop_target.column_offset > 1 - SPLIT_EDGE_TOLERANCE
|
|
19
43
|
) {
|
|
20
|
-
if (
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
if (drop_target.orientation === "horizontal") {
|
|
45
|
+
const is_before = drop_target.column_offset < SPLIT_EDGE_TOLERANCE;
|
|
46
|
+
if (drop_target.path.length === 0) {
|
|
47
|
+
const insert_index = is_before ? 0 : 1;
|
|
48
|
+
const new_panel = insert_child(panel, slot, [insert_index]);
|
|
49
|
+
// When inserting before, point to new panel; when after, keep original
|
|
50
|
+
if (is_before) {
|
|
51
|
+
drop_target = calculate_intersection(col, row, new_panel, false);
|
|
52
|
+
} else {
|
|
53
|
+
const new_drop_target = calculate_intersection(
|
|
54
|
+
col,
|
|
55
|
+
row,
|
|
56
|
+
new_panel,
|
|
57
|
+
false,
|
|
58
|
+
);
|
|
59
|
+
drop_target = {
|
|
60
|
+
...new_drop_target,
|
|
61
|
+
path: [0],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
const path_without_last = drop_target.path.slice(0, -1);
|
|
66
|
+
const last_index = drop_target.path[drop_target.path.length - 1];
|
|
67
|
+
const insert_index = is_before ? last_index : last_index + 1;
|
|
68
|
+
const new_panel = insert_child(panel, slot, [
|
|
69
|
+
...path_without_last,
|
|
70
|
+
insert_index,
|
|
71
|
+
]);
|
|
72
|
+
// When inserting before, point to new panel; when after, keep original
|
|
73
|
+
if (is_before) {
|
|
74
|
+
drop_target = calculate_intersection(col, row, new_panel, false);
|
|
75
|
+
} else {
|
|
76
|
+
// Keep the original panel but update view_window from new layout
|
|
77
|
+
const new_drop_target = calculate_intersection(
|
|
78
|
+
col,
|
|
79
|
+
row,
|
|
80
|
+
new_panel,
|
|
81
|
+
false,
|
|
82
|
+
);
|
|
83
|
+
drop_target = {
|
|
84
|
+
...new_drop_target,
|
|
85
|
+
path: [...path_without_last, last_index],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
27
89
|
} else {
|
|
28
|
-
const
|
|
29
|
-
|
|
90
|
+
const insert_index =
|
|
91
|
+
drop_target.column_offset < SPLIT_EDGE_TOLERANCE ? 0 : 1;
|
|
92
|
+
const original_path = drop_target.path;
|
|
93
|
+
const new_panel = insert_child(
|
|
94
|
+
panel,
|
|
95
|
+
slot,
|
|
96
|
+
[...original_path, insert_index],
|
|
97
|
+
"horizontal",
|
|
98
|
+
);
|
|
99
|
+
drop_target = calculate_intersection(col, row, new_panel, false);
|
|
100
|
+
// Override to point to the newly inserted panel
|
|
101
|
+
drop_target = {
|
|
102
|
+
...drop_target,
|
|
103
|
+
slot,
|
|
104
|
+
path: [...original_path, insert_index],
|
|
105
|
+
};
|
|
30
106
|
}
|
|
31
|
-
}
|
|
32
107
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
108
|
+
if (drop_target) {
|
|
109
|
+
drop_target.is_edge = true;
|
|
110
|
+
}
|
|
111
|
+
} else if (
|
|
112
|
+
drop_target.row_offset < SPLIT_EDGE_TOLERANCE ||
|
|
113
|
+
drop_target.row_offset > 1 - SPLIT_EDGE_TOLERANCE
|
|
38
114
|
) {
|
|
39
|
-
if (
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
115
|
+
if (drop_target.orientation === "vertical") {
|
|
116
|
+
const is_before = drop_target.row_offset < SPLIT_EDGE_TOLERANCE;
|
|
117
|
+
if (drop_target.path.length === 0) {
|
|
118
|
+
const insert_index = is_before ? 0 : 1;
|
|
119
|
+
const new_panel = insert_child(panel, slot, [insert_index]);
|
|
120
|
+
// When inserting before, point to new panel; when after, keep original
|
|
121
|
+
if (is_before) {
|
|
122
|
+
drop_target = calculate_intersection(col, row, new_panel, false);
|
|
123
|
+
} else {
|
|
124
|
+
const new_drop_target = calculate_intersection(
|
|
125
|
+
col,
|
|
126
|
+
row,
|
|
127
|
+
new_panel,
|
|
128
|
+
false,
|
|
129
|
+
);
|
|
130
|
+
drop_target = {
|
|
131
|
+
...new_drop_target,
|
|
132
|
+
path: [0],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
const path_without_last = drop_target.path.slice(0, -1);
|
|
137
|
+
const last_index = drop_target.path[drop_target.path.length - 1];
|
|
138
|
+
const insert_index = is_before ? last_index : last_index + 1;
|
|
139
|
+
const new_panel = insert_child(panel, slot, [
|
|
140
|
+
...path_without_last,
|
|
141
|
+
insert_index,
|
|
142
|
+
]);
|
|
143
|
+
// When inserting before, point to new panel; when after, keep original
|
|
144
|
+
if (is_before) {
|
|
145
|
+
drop_target = calculate_intersection(col, row, new_panel, false);
|
|
146
|
+
} else {
|
|
147
|
+
// Keep the original panel but update view_window from new layout
|
|
148
|
+
const new_drop_target = calculate_intersection(
|
|
149
|
+
col,
|
|
150
|
+
row,
|
|
151
|
+
new_panel,
|
|
152
|
+
false,
|
|
153
|
+
);
|
|
154
|
+
drop_target = {
|
|
155
|
+
...new_drop_target,
|
|
156
|
+
path: [...path_without_last, last_index],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
46
160
|
} else {
|
|
47
|
-
const
|
|
48
|
-
|
|
161
|
+
const insert_index =
|
|
162
|
+
drop_target.row_offset < SPLIT_EDGE_TOLERANCE ? 0 : 1;
|
|
163
|
+
const original_path = drop_target.path;
|
|
164
|
+
const new_panel = insert_child(
|
|
165
|
+
panel,
|
|
166
|
+
slot,
|
|
167
|
+
[...original_path, insert_index],
|
|
168
|
+
"vertical",
|
|
169
|
+
);
|
|
170
|
+
drop_target = calculate_intersection(col, row, new_panel, false);
|
|
171
|
+
// Override to point to the newly inserted panel
|
|
172
|
+
drop_target = {
|
|
173
|
+
...drop_target,
|
|
174
|
+
slot,
|
|
175
|
+
path: [...original_path, insert_index],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (drop_target) {
|
|
180
|
+
drop_target.is_edge = true;
|
|
49
181
|
}
|
|
50
182
|
}
|
|
51
183
|
|
|
52
|
-
return
|
|
184
|
+
return drop_target;
|
|
53
185
|
}
|
package/src/common/flatten.ts
CHANGED
|
@@ -85,7 +85,7 @@ function collectTrackPositions(
|
|
|
85
85
|
|
|
86
86
|
function findTrackIndex(positions: number[], value: number): number {
|
|
87
87
|
for (let i = 0; i < positions.length; i++) {
|
|
88
|
-
if (Math.abs(positions[i] - value) <
|
|
88
|
+
if (Math.abs(positions[i] - value) < GRID_TRACK_COLLAPSE_TOLERANCE) {
|
|
89
89
|
return i;
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -103,9 +103,10 @@ function buildCells(
|
|
|
103
103
|
rowEnd: number,
|
|
104
104
|
): GridCell[] {
|
|
105
105
|
if (panel.type === "child-panel") {
|
|
106
|
+
const selected = panel.selected ?? 0;
|
|
106
107
|
return [
|
|
107
108
|
{
|
|
108
|
-
child: panel.child,
|
|
109
|
+
child: panel.child[selected],
|
|
109
110
|
colStart: findTrackIndex(colPositions, colStart),
|
|
110
111
|
colEnd: findTrackIndex(colPositions, colEnd),
|
|
111
112
|
rowStart: findTrackIndex(rowPositions, rowStart),
|
|
@@ -203,7 +204,8 @@ export function create_css_grid_layout(
|
|
|
203
204
|
}
|
|
204
205
|
|
|
205
206
|
if (layout.type === "child-panel") {
|
|
206
|
-
|
|
207
|
+
const selected = layout.selected ?? 0;
|
|
208
|
+
return `${host_template("100%", "100%")}\n${child_template(layout.child[selected], "1", "1")}`;
|
|
207
209
|
}
|
|
208
210
|
|
|
209
211
|
const colPositions = collectTrackPositions(layout, "horizontal", 0, 1);
|
|
@@ -29,22 +29,15 @@ export function insert_child(
|
|
|
29
29
|
child: string,
|
|
30
30
|
path: number[],
|
|
31
31
|
orientation: "horizontal" | "vertical" = "horizontal",
|
|
32
|
+
is_edge?: boolean,
|
|
32
33
|
): Layout {
|
|
33
34
|
if (path.length === 0) {
|
|
34
35
|
// Insert at root level
|
|
35
36
|
if (panel.type === "child-panel") {
|
|
36
|
-
//
|
|
37
|
+
// Add to existing child-panel as a tab
|
|
37
38
|
return {
|
|
38
|
-
type: "
|
|
39
|
-
|
|
40
|
-
children: [
|
|
41
|
-
panel,
|
|
42
|
-
{
|
|
43
|
-
type: "child-panel",
|
|
44
|
-
child,
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
sizes: [0.5, 0.5],
|
|
39
|
+
type: "child-panel",
|
|
40
|
+
child: [child, ...panel.child],
|
|
48
41
|
};
|
|
49
42
|
} else {
|
|
50
43
|
// Append to existing split-panel
|
|
@@ -52,7 +45,7 @@ export function insert_child(
|
|
|
52
45
|
...panel.children,
|
|
53
46
|
{
|
|
54
47
|
type: "child-panel",
|
|
55
|
-
child,
|
|
48
|
+
child: [child],
|
|
56
49
|
} as Layout,
|
|
57
50
|
];
|
|
58
51
|
|
|
@@ -78,15 +71,20 @@ export function insert_child(
|
|
|
78
71
|
sizes: [1],
|
|
79
72
|
};
|
|
80
73
|
|
|
81
|
-
return insert_child(newPanel, child, path, orientation);
|
|
74
|
+
return insert_child(newPanel, child, path, orientation, is_edge);
|
|
82
75
|
}
|
|
83
76
|
|
|
84
77
|
if (restPath.length === 0 || index === panel.children.length) {
|
|
78
|
+
if (is_edge && panel.children[index]?.type === "child-panel") {
|
|
79
|
+
panel.children[index].child.unshift(child);
|
|
80
|
+
return panel;
|
|
81
|
+
}
|
|
82
|
+
|
|
85
83
|
// Insert at this level at the specified index
|
|
86
84
|
const newChildren = [...panel.children];
|
|
87
85
|
newChildren.splice(index, 0, {
|
|
88
86
|
type: "child-panel",
|
|
89
|
-
child,
|
|
87
|
+
child: [child],
|
|
90
88
|
});
|
|
91
89
|
|
|
92
90
|
const numChildren = newChildren.length;
|
|
@@ -109,6 +107,7 @@ export function insert_child(
|
|
|
109
107
|
child,
|
|
110
108
|
restPath,
|
|
111
109
|
oppositeOrientation,
|
|
110
|
+
is_edge,
|
|
112
111
|
);
|
|
113
112
|
|
|
114
113
|
const newChildren = [...panel.children];
|
|
@@ -119,7 +118,14 @@ export function insert_child(
|
|
|
119
118
|
};
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
const updatedChild = insert_child(
|
|
121
|
+
const updatedChild = insert_child(
|
|
122
|
+
targetChild,
|
|
123
|
+
child,
|
|
124
|
+
restPath,
|
|
125
|
+
orientation,
|
|
126
|
+
is_edge,
|
|
127
|
+
);
|
|
128
|
+
|
|
123
129
|
const newChildren = [...panel.children];
|
|
124
130
|
newChildren[index] = updatedChild;
|
|
125
131
|
return {
|
|
@@ -18,7 +18,7 @@ export const MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD = 0.15;
|
|
|
18
18
|
/**
|
|
19
19
|
* Threshold from panel edge that is considered a split vs drop action.
|
|
20
20
|
*/
|
|
21
|
-
export const SPLIT_EDGE_TOLERANCE = 0.
|
|
21
|
+
export const SPLIT_EDGE_TOLERANCE = 0.25;
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Tolerance threshold for considering two grid track positions as identical.
|
|
@@ -26,7 +26,17 @@ export const SPLIT_EDGE_TOLERANCE = 0.15;
|
|
|
26
26
|
* When collecting and deduplicating track positions, any positions closer than
|
|
27
27
|
* this value are treated as the same position to avoid redundant grid tracks.
|
|
28
28
|
*/
|
|
29
|
-
export const GRID_TRACK_COLLAPSE_TOLERANCE = 0.
|
|
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
|
+
/**
|
|
37
|
+
* The overlay behavior type.
|
|
38
|
+
*/
|
|
39
|
+
export type OverlayMode = "grid" | "absolute" | "interactive";
|
|
30
40
|
|
|
31
41
|
/**
|
|
32
42
|
* The representation of a CSS grid, in JSON form.
|
|
@@ -67,7 +77,8 @@ export interface SplitLayout {
|
|
|
67
77
|
*/
|
|
68
78
|
export interface TabLayout {
|
|
69
79
|
type: "child-panel";
|
|
70
|
-
child: string;
|
|
80
|
+
child: string[];
|
|
81
|
+
selected?: number;
|
|
71
82
|
}
|
|
72
83
|
|
|
73
84
|
/**
|
|
@@ -91,11 +102,13 @@ export interface LayoutDivider {
|
|
|
91
102
|
export interface LayoutPath<T = undefined> {
|
|
92
103
|
type: "layout-path";
|
|
93
104
|
slot: string;
|
|
105
|
+
panel: TabLayout;
|
|
94
106
|
path: number[];
|
|
95
107
|
view_window: ViewWindow;
|
|
96
108
|
column_offset: number;
|
|
97
109
|
row_offset: number;
|
|
98
110
|
orientation: Orientation;
|
|
111
|
+
is_edge: boolean;
|
|
99
112
|
box: T;
|
|
100
113
|
}
|
|
101
114
|
|
|
@@ -112,7 +125,7 @@ export function* iter_panel_children(panel: Layout): Generator<string> {
|
|
|
112
125
|
yield* iter_panel_children(child);
|
|
113
126
|
}
|
|
114
127
|
} else {
|
|
115
|
-
yield panel.child;
|
|
128
|
+
yield* panel.child;
|
|
116
129
|
}
|
|
117
130
|
}
|
|
118
131
|
|
|
@@ -54,7 +54,11 @@ export function redistribute_panel_sizes(
|
|
|
54
54
|
if (current.type === "split-panel") {
|
|
55
55
|
const delta = deltas[current.orientation];
|
|
56
56
|
const index = path[path.length - 1];
|
|
57
|
-
|
|
57
|
+
|
|
58
|
+
// It would be fun to remove this condition.
|
|
59
|
+
if (index < current.sizes.length - 1) {
|
|
60
|
+
current.sizes = add_and_redistribute(current.sizes, index, delta);
|
|
61
|
+
}
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
return result;
|
|
@@ -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 "./layout_config.ts";
|
|
12
|
+
import type { Layout, TabLayout } from "./layout_config.ts";
|
|
13
13
|
import { EMPTY_PANEL } from "./layout_config.ts";
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -24,10 +24,19 @@ import { EMPTY_PANEL } from "./layout_config.ts";
|
|
|
24
24
|
* Returns `EMPTY_PANEL` if the last panel is removed.
|
|
25
25
|
*/
|
|
26
26
|
export function remove_child(panel: Layout, child: string): Layout {
|
|
27
|
-
// If this is a child panel
|
|
28
|
-
// The caller should handle this case
|
|
27
|
+
// If this is a child panel, handle tab removal
|
|
29
28
|
if (panel.type === "child-panel") {
|
|
30
|
-
|
|
29
|
+
if (panel.child.includes(child)) {
|
|
30
|
+
const newChild = panel.child.filter((c) => c !== child);
|
|
31
|
+
if (newChild.length === 0) {
|
|
32
|
+
return structuredClone(EMPTY_PANEL);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
type: "child-panel",
|
|
36
|
+
child: newChild,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return structuredClone(panel);
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
// Clone the panel structure
|
|
@@ -36,23 +45,36 @@ export function remove_child(panel: Layout, child: string): Layout {
|
|
|
36
45
|
// Try to remove the child from this split panel's children
|
|
37
46
|
const index = result.children.findIndex((p) => {
|
|
38
47
|
if (p.type === "child-panel") {
|
|
39
|
-
return p.child
|
|
48
|
+
return p.child.includes(child);
|
|
40
49
|
}
|
|
50
|
+
|
|
41
51
|
return false;
|
|
42
52
|
});
|
|
43
53
|
|
|
44
54
|
if (index !== -1) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
const tab_layout = result.children[index] as TabLayout;
|
|
56
|
+
if (tab_layout.child.length === 1) {
|
|
57
|
+
// Found the child at this level - remove it
|
|
58
|
+
const newChildren = result.children.filter((_, i) => i !== index);
|
|
59
|
+
const newSizes = remove_and_redistribute(result.sizes, index);
|
|
60
|
+
|
|
61
|
+
// If only one child remains, collapse the split panel
|
|
62
|
+
if (newChildren.length === 1) {
|
|
63
|
+
return newChildren[0];
|
|
64
|
+
}
|
|
48
65
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
result.children = newChildren;
|
|
67
|
+
result.sizes = newSizes;
|
|
68
|
+
} else {
|
|
69
|
+
tab_layout.child.splice(tab_layout.child.indexOf(child), 1);
|
|
70
|
+
if (
|
|
71
|
+
tab_layout.selected &&
|
|
72
|
+
tab_layout.selected >= tab_layout.child.length
|
|
73
|
+
) {
|
|
74
|
+
tab_layout.selected--;
|
|
75
|
+
}
|
|
52
76
|
}
|
|
53
77
|
|
|
54
|
-
result.children = newChildren;
|
|
55
|
-
result.sizes = newSizes;
|
|
56
78
|
return result;
|
|
57
79
|
}
|
|
58
80
|
|
|
@@ -64,8 +86,10 @@ export function remove_child(panel: Layout, child: string): Layout {
|
|
|
64
86
|
if (updated !== p) {
|
|
65
87
|
modified = true;
|
|
66
88
|
}
|
|
89
|
+
|
|
67
90
|
return updated;
|
|
68
91
|
}
|
|
92
|
+
|
|
69
93
|
return p;
|
|
70
94
|
});
|
|
71
95
|
|
package/src/extensions.ts
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
|
|
12
12
|
import { RegularLayout } from "./regular-layout.ts";
|
|
13
13
|
import { RegularLayoutFrame } from "./regular-layout-frame.ts";
|
|
14
|
-
|
|
15
|
-
customElements.define("regular-layout-frame", RegularLayoutFrame);
|
|
14
|
+
import { Layout } from "./common/layout_config.ts";
|
|
16
15
|
|
|
17
16
|
customElements.define("regular-layout", RegularLayout);
|
|
17
|
+
customElements.define("regular-layout-frame", RegularLayoutFrame);
|
|
18
18
|
|
|
19
19
|
declare global {
|
|
20
20
|
interface Document {
|
|
@@ -37,4 +37,28 @@ declare global {
|
|
|
37
37
|
get(tagName: "regular-layout"): typeof RegularLayout;
|
|
38
38
|
get(tagName: "regular-layout-frame"): typeof RegularLayoutFrame;
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
interface HTMLElement {
|
|
42
|
+
addEventListener(
|
|
43
|
+
name: "regular-layout-update",
|
|
44
|
+
cb: (e: RegularLayoutEvent) => void,
|
|
45
|
+
options?: { signal: AbortSignal },
|
|
46
|
+
): void;
|
|
47
|
+
|
|
48
|
+
removeEventListener(name: "regular-layout-update", cb: any): void;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface RegularLayoutEvent extends CustomEvent {
|
|
53
|
+
detail: Layout;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface PerspectiveViewerElementExt {
|
|
57
|
+
addEventListener(
|
|
58
|
+
name: "regular-layout-update",
|
|
59
|
+
cb: (e: RegularLayoutEvent) => void,
|
|
60
|
+
options?: { signal: AbortSignal },
|
|
61
|
+
): void;
|
|
62
|
+
|
|
63
|
+
removeEventListener(name: "regular-layout-update", cb: any): void;
|
|
40
64
|
}
|