regular-layout 0.2.2 → 0.3.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/index.js +6 -6
- package/dist/index.js.map +4 -4
- package/dist/layout/calculate_edge.d.ts +2 -2
- package/dist/layout/calculate_path.d.ts +12 -0
- package/dist/layout/generate_grid.d.ts +3 -3
- package/dist/layout/insert_child.d.ts +2 -2
- package/dist/layout/redistribute_panel_sizes.d.ts +2 -2
- package/dist/layout/types.d.ts +8 -4
- package/dist/regular-layout.d.ts +9 -2
- package/package.json +1 -1
- package/src/layout/calculate_edge.ts +16 -7
- package/src/layout/calculate_intersect.ts +9 -3
- package/src/layout/calculate_path.ts +53 -0
- package/src/layout/flatten.ts +3 -3
- package/src/layout/generate_grid.ts +6 -6
- package/src/layout/insert_child.ts +16 -16
- package/src/layout/redistribute_panel_sizes.ts +4 -4
- package/src/layout/remove_child.ts +4 -4
- package/src/layout/types.ts +10 -5
- package/src/regular-layout-frame.ts +1 -1
- package/src/regular-layout.ts +14 -2
|
@@ -2,7 +2,7 @@ import { type Physics } from "./constants";
|
|
|
2
2
|
import type { Layout, LayoutPath } from "./types";
|
|
3
3
|
/**
|
|
4
4
|
* Calculates an insertion point (which may involve splitting a single
|
|
5
|
-
* `"
|
|
5
|
+
* `"tab-layout"` into a new `"split-layout"`), based on the cursor position.
|
|
6
6
|
* *
|
|
7
7
|
* @param col - The cursor column.
|
|
8
8
|
* @param row - The cursor row.
|
|
@@ -10,7 +10,7 @@ import type { Layout, LayoutPath } from "./types";
|
|
|
10
10
|
* @param slot - The slot identifier where the insert should occur
|
|
11
11
|
* @param drop_target - The `LayoutPath` (from `calculateIntersect`) of the
|
|
12
12
|
* panel to either insert next to, or split by.
|
|
13
|
-
* @returns A new `LayoutPath` reflecting the updated (maybe) `"split-
|
|
13
|
+
* @returns A new `LayoutPath` reflecting the updated (maybe) `"split-layout"`,
|
|
14
14
|
* which is enough to draw the overlay.
|
|
15
15
|
*/
|
|
16
16
|
export declare function calculate_edge(col: number, row: number, panel: Layout, slot: string, drop_target: LayoutPath, box?: DOMRect, physics?: Physics): LayoutPath;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Layout } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Calculates the index path for a panel with the given name.
|
|
4
|
+
*
|
|
5
|
+
* Traverses the layout tree to find the named panel and returns the
|
|
6
|
+
* index path describing its position in the tree.
|
|
7
|
+
*
|
|
8
|
+
* @param name - The name of the panel to find.
|
|
9
|
+
* @param layout - The layout tree to search.
|
|
10
|
+
* @returns The panel's index path if found, `null` otherwise.
|
|
11
|
+
*/
|
|
12
|
+
export declare function calculate_path(name: string, layout: Layout): number[] | null;
|
|
@@ -14,11 +14,11 @@ import type { Layout } from "./types.ts";
|
|
|
14
14
|
* @example
|
|
15
15
|
* ```typescript
|
|
16
16
|
* const layout = {
|
|
17
|
-
* type: "split-
|
|
17
|
+
* type: "split-layout",
|
|
18
18
|
* orientation: "horizontal",
|
|
19
19
|
* children: [
|
|
20
|
-
* { type: "
|
|
21
|
-
* { type: "
|
|
20
|
+
* { type: "tab-layout", tabs: "sidebar" },
|
|
21
|
+
* { type: "tab-layout", tabs: "main" }
|
|
22
22
|
* ],
|
|
23
23
|
* sizes: [0.25, 0.75]
|
|
24
24
|
* };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Layout } from "./types.ts";
|
|
1
|
+
import type { Layout, LayoutPathTraversal } from "./types.ts";
|
|
2
2
|
/**
|
|
3
3
|
* Inserts a new child panel into the layout tree at a specified location.
|
|
4
4
|
* Creates a split panel if necessary and redistributes sizes equally among all
|
|
@@ -13,4 +13,4 @@ import type { Layout } from "./types.ts";
|
|
|
13
13
|
* @param is_edge - If true, create the split at the parent level.
|
|
14
14
|
* @returns A new layout tree with the child inserted (original is not mutated).
|
|
15
15
|
*/
|
|
16
|
-
export declare function insert_child(panel: Layout, child: string, path:
|
|
16
|
+
export declare function insert_child(panel: Layout, child: string, path: LayoutPathTraversal, orientation?: "horizontal" | "vertical"): Layout;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Layout } from "./types.ts";
|
|
1
|
+
import type { Layout, LayoutPathTraversal } from "./types.ts";
|
|
2
2
|
/**
|
|
3
3
|
* Adjusts panel sizes during a drag operation on a divider.
|
|
4
4
|
*
|
|
@@ -16,4 +16,4 @@ import type { Layout } from "./types.ts";
|
|
|
16
16
|
* @returns A new layout tree with updated sizes (original is not mutated).
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
-
export declare function redistribute_panel_sizes(panel: Layout, path:
|
|
19
|
+
export declare function redistribute_panel_sizes(panel: Layout, path: LayoutPathTraversal, delta: number | undefined, physics?: import("./constants.ts").Physics): Layout;
|
package/dist/layout/types.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export interface ViewWindow {
|
|
|
32
32
|
* opposite `orientation` as its parent.
|
|
33
33
|
*/
|
|
34
34
|
export interface SplitLayout {
|
|
35
|
-
type: "split-
|
|
35
|
+
type: "split-layout";
|
|
36
36
|
children: Layout[];
|
|
37
37
|
sizes: number[];
|
|
38
38
|
orientation: Orientation;
|
|
@@ -41,17 +41,21 @@ export interface SplitLayout {
|
|
|
41
41
|
* A leaf panel node that contains a single named child element.
|
|
42
42
|
*/
|
|
43
43
|
export interface TabLayout {
|
|
44
|
-
type: "
|
|
44
|
+
type: "tab-layout";
|
|
45
45
|
tabs: string[];
|
|
46
46
|
selected?: number;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Represents a panel location relative to a `Layout` struct.
|
|
50
|
+
*/
|
|
51
|
+
export type LayoutPathTraversal = number[];
|
|
48
52
|
/**
|
|
49
53
|
* Represents a draggable divider between two panels in the layout.
|
|
50
54
|
*
|
|
51
55
|
* Used for hit detection.
|
|
52
56
|
*/
|
|
53
57
|
export interface LayoutDivider {
|
|
54
|
-
path:
|
|
58
|
+
path: LayoutPathTraversal;
|
|
55
59
|
view_window: ViewWindow;
|
|
56
60
|
type: Orientation;
|
|
57
61
|
}
|
|
@@ -61,7 +65,7 @@ export interface LayoutDivider {
|
|
|
61
65
|
export interface LayoutPath {
|
|
62
66
|
type: "layout-path";
|
|
63
67
|
slot: string;
|
|
64
|
-
path:
|
|
68
|
+
path: LayoutPathTraversal;
|
|
65
69
|
view_window: ViewWindow;
|
|
66
70
|
column: number;
|
|
67
71
|
row: number;
|
package/dist/regular-layout.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LayoutPath, Layout, TabLayout, OverlayMode, Orientation } from "./layout/types.ts";
|
|
1
|
+
import type { LayoutPath, Layout, TabLayout, OverlayMode, Orientation, LayoutPathTraversal } from "./layout/types.ts";
|
|
2
2
|
import { type PhysicsUpdate, type Physics } from "./layout/constants.ts";
|
|
3
3
|
/**
|
|
4
4
|
* An interface which models the fields of `PointerEvent` that
|
|
@@ -78,6 +78,13 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
78
78
|
* @returns Panel information if a panel is at that position, null otherwise.
|
|
79
79
|
*/
|
|
80
80
|
calculateIntersect: (coordinates: PointerEventCoordinates) => LayoutPath | null;
|
|
81
|
+
/**
|
|
82
|
+
* Calculates the index path for a panel with the given name.
|
|
83
|
+
*
|
|
84
|
+
* @param name - The name of the panel to find.
|
|
85
|
+
* @returns The panel's index path if found, `null` otherwise.
|
|
86
|
+
*/
|
|
87
|
+
calculatePath: (name: string) => number[] | null;
|
|
81
88
|
/**
|
|
82
89
|
* Sets the visual overlay state during drag-and-drop operations.
|
|
83
90
|
* Displays a preview of where a panel would be placed at the given coordinates.
|
|
@@ -117,7 +124,7 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
117
124
|
* the new `SplitPanel` _if_ there is an option of orientation (e.g. if
|
|
118
125
|
* the layout had no pre-existing `SplitPanel`)
|
|
119
126
|
*/
|
|
120
|
-
insertPanel: (name: string, path?:
|
|
127
|
+
insertPanel: (name: string, path?: LayoutPathTraversal, split?: boolean | Orientation) => void;
|
|
121
128
|
/**
|
|
122
129
|
* Removes a panel from the layout by name.
|
|
123
130
|
*
|
package/package.json
CHANGED
|
@@ -11,11 +11,17 @@
|
|
|
11
11
|
|
|
12
12
|
import { DEFAULT_PHYSICS, type Physics } from "./constants";
|
|
13
13
|
import { insert_child } from "./insert_child";
|
|
14
|
-
import type {
|
|
14
|
+
import type {
|
|
15
|
+
Layout,
|
|
16
|
+
LayoutPath,
|
|
17
|
+
LayoutPathTraversal,
|
|
18
|
+
Orientation,
|
|
19
|
+
ViewWindow,
|
|
20
|
+
} from "./types";
|
|
15
21
|
|
|
16
22
|
/**
|
|
17
23
|
* Calculates an insertion point (which may involve splitting a single
|
|
18
|
-
* `"
|
|
24
|
+
* `"tab-layout"` into a new `"split-layout"`), based on the cursor position.
|
|
19
25
|
* *
|
|
20
26
|
* @param col - The cursor column.
|
|
21
27
|
* @param row - The cursor row.
|
|
@@ -23,7 +29,7 @@ import type { Layout, LayoutPath, Orientation, ViewWindow } from "./types";
|
|
|
23
29
|
* @param slot - The slot identifier where the insert should occur
|
|
24
30
|
* @param drop_target - The `LayoutPath` (from `calculateIntersect`) of the
|
|
25
31
|
* panel to either insert next to, or split by.
|
|
26
|
-
* @returns A new `LayoutPath` reflecting the updated (maybe) `"split-
|
|
32
|
+
* @returns A new `LayoutPath` reflecting the updated (maybe) `"split-layout"`,
|
|
27
33
|
* which is enough to draw the overlay.
|
|
28
34
|
*/
|
|
29
35
|
export function calculate_edge(
|
|
@@ -133,7 +139,7 @@ function insert_root_edge(
|
|
|
133
139
|
panel: Layout,
|
|
134
140
|
slot: string,
|
|
135
141
|
drop_target: LayoutPath,
|
|
136
|
-
path:
|
|
142
|
+
path: LayoutPathTraversal,
|
|
137
143
|
is_before: boolean,
|
|
138
144
|
orientation: Orientation,
|
|
139
145
|
): LayoutPath {
|
|
@@ -153,7 +159,7 @@ function insert_axis(
|
|
|
153
159
|
is_before: boolean,
|
|
154
160
|
axis_orientation: Orientation,
|
|
155
161
|
): LayoutPath {
|
|
156
|
-
let result_path:
|
|
162
|
+
let result_path: LayoutPathTraversal;
|
|
157
163
|
|
|
158
164
|
if (drop_target.orientation === axis_orientation) {
|
|
159
165
|
// Same orientation - insert into existing split
|
|
@@ -183,7 +189,10 @@ function insert_axis(
|
|
|
183
189
|
};
|
|
184
190
|
}
|
|
185
191
|
|
|
186
|
-
function calculate_view_window(
|
|
192
|
+
function calculate_view_window(
|
|
193
|
+
panel: Layout,
|
|
194
|
+
path: LayoutPathTraversal,
|
|
195
|
+
): ViewWindow {
|
|
187
196
|
let view_window: ViewWindow = {
|
|
188
197
|
row_start: 0,
|
|
189
198
|
row_end: 1,
|
|
@@ -193,7 +202,7 @@ function calculate_view_window(panel: Layout, path: number[]): ViewWindow {
|
|
|
193
202
|
|
|
194
203
|
let current_panel = panel;
|
|
195
204
|
for (const step of path) {
|
|
196
|
-
if (current_panel.type === "
|
|
205
|
+
if (current_panel.type === "tab-layout") {
|
|
197
206
|
break;
|
|
198
207
|
}
|
|
199
208
|
|
|
@@ -9,7 +9,13 @@
|
|
|
9
9
|
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
|
|
10
10
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
11
11
|
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
LayoutPath,
|
|
14
|
+
LayoutDivider,
|
|
15
|
+
Layout,
|
|
16
|
+
ViewWindow,
|
|
17
|
+
LayoutPathTraversal,
|
|
18
|
+
} from "./types.ts";
|
|
13
19
|
|
|
14
20
|
const VIEW_WINDOW = {
|
|
15
21
|
row_start: 0,
|
|
@@ -67,14 +73,14 @@ function calculate_intersection_recursive(
|
|
|
67
73
|
check_dividers: { rect: DOMRect; size: number } | null,
|
|
68
74
|
parent_orientation: "horizontal" | "vertical" | null = null,
|
|
69
75
|
view_window: ViewWindow = structuredClone(VIEW_WINDOW),
|
|
70
|
-
path:
|
|
76
|
+
path: LayoutPathTraversal = [],
|
|
71
77
|
): LayoutPath | null | LayoutDivider {
|
|
72
78
|
if (column < 0 || row < 0 || column > 1 || row > 1) {
|
|
73
79
|
return null;
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
// Base case: if this is a child panel, return its name
|
|
77
|
-
if (panel.type === "
|
|
83
|
+
if (panel.type === "tab-layout") {
|
|
78
84
|
const selected = panel.selected ?? 0;
|
|
79
85
|
const col_width = view_window.col_end - view_window.col_start;
|
|
80
86
|
const row_height = view_window.row_end - view_window.row_start;
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
|
|
12
|
+
import type { Layout, LayoutPathTraversal } from "./types.ts";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Calculates the index path for a panel with the given name.
|
|
16
|
+
*
|
|
17
|
+
* Traverses the layout tree to find the named panel and returns the
|
|
18
|
+
* index path describing its position in the tree.
|
|
19
|
+
*
|
|
20
|
+
* @param name - The name of the panel to find.
|
|
21
|
+
* @param layout - The layout tree to search.
|
|
22
|
+
* @returns The panel's index path if found, `null` otherwise.
|
|
23
|
+
*/
|
|
24
|
+
export function calculate_path(name: string, layout: Layout): number[] | null {
|
|
25
|
+
return calculate_path_recursive(name, layout, []);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function calculate_path_recursive(
|
|
29
|
+
name: string,
|
|
30
|
+
panel: Layout,
|
|
31
|
+
path: LayoutPathTraversal,
|
|
32
|
+
): number[] | null {
|
|
33
|
+
if (panel.type === "tab-layout") {
|
|
34
|
+
if (!panel.tabs.includes(name)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return path;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < panel.children.length; i++) {
|
|
42
|
+
const result = calculate_path_recursive(name, panel.children[i], [
|
|
43
|
+
...path,
|
|
44
|
+
i,
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
if (result) {
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null;
|
|
53
|
+
}
|
package/src/layout/flatten.ts
CHANGED
|
@@ -23,7 +23,7 @@ import type { Layout } from "./types.ts";
|
|
|
23
23
|
* @returns A new flattened layout tree (original is not mutated).
|
|
24
24
|
*/
|
|
25
25
|
export function flatten(layout: Layout): Layout {
|
|
26
|
-
if (layout.type === "
|
|
26
|
+
if (layout.type === "tab-layout") {
|
|
27
27
|
layout.selected = layout.selected || 0;
|
|
28
28
|
return layout;
|
|
29
29
|
}
|
|
@@ -36,7 +36,7 @@ export function flatten(layout: Layout): Layout {
|
|
|
36
36
|
const flattenedChild = flatten(child);
|
|
37
37
|
|
|
38
38
|
if (
|
|
39
|
-
flattenedChild.type === "split-
|
|
39
|
+
flattenedChild.type === "split-layout" &&
|
|
40
40
|
flattenedChild.orientation === layout.orientation
|
|
41
41
|
) {
|
|
42
42
|
for (let j = 0; j < flattenedChild.children.length; j++) {
|
|
@@ -54,7 +54,7 @@ export function flatten(layout: Layout): Layout {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
return {
|
|
57
|
-
type: "split-
|
|
57
|
+
type: "split-layout",
|
|
58
58
|
orientation: layout.orientation,
|
|
59
59
|
children: flattenedChildren,
|
|
60
60
|
sizes: flattenedSizes,
|
|
@@ -44,7 +44,7 @@ function collect_track_positions(
|
|
|
44
44
|
end: number,
|
|
45
45
|
physics: Physics,
|
|
46
46
|
): number[] {
|
|
47
|
-
if (panel.type === "
|
|
47
|
+
if (panel.type === "tab-layout") {
|
|
48
48
|
return [start, end];
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -100,7 +100,7 @@ function build_cells(
|
|
|
100
100
|
rowEnd: number,
|
|
101
101
|
physics: Physics,
|
|
102
102
|
): GridCell[] {
|
|
103
|
-
if (panel.type === "
|
|
103
|
+
if (panel.type === "tab-layout") {
|
|
104
104
|
const selected = panel.selected ?? 0;
|
|
105
105
|
return [
|
|
106
106
|
{
|
|
@@ -179,11 +179,11 @@ const child_template = (
|
|
|
179
179
|
* @example
|
|
180
180
|
* ```typescript
|
|
181
181
|
* const layout = {
|
|
182
|
-
* type: "split-
|
|
182
|
+
* type: "split-layout",
|
|
183
183
|
* orientation: "horizontal",
|
|
184
184
|
* children: [
|
|
185
|
-
* { type: "
|
|
186
|
-
* { type: "
|
|
185
|
+
* { type: "tab-layout", tabs: "sidebar" },
|
|
186
|
+
* { type: "tab-layout", tabs: "main" }
|
|
187
187
|
* ],
|
|
188
188
|
* sizes: [0.25, 0.75]
|
|
189
189
|
* };
|
|
@@ -200,7 +200,7 @@ export function create_css_grid_layout(
|
|
|
200
200
|
overlay?: [string, string],
|
|
201
201
|
physics: Physics = DEFAULT_PHYSICS,
|
|
202
202
|
): string {
|
|
203
|
-
if (layout.type === "
|
|
203
|
+
if (layout.type === "tab-layout") {
|
|
204
204
|
const selected = layout.selected ?? 0;
|
|
205
205
|
return [
|
|
206
206
|
host_template("100%", "100%"),
|
|
@@ -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 "./types.ts";
|
|
12
|
+
import type { Layout, LayoutPathTraversal } from "./types.ts";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Inserts a new child panel into the layout tree at a specified location.
|
|
@@ -28,32 +28,32 @@ import type { Layout } from "./types.ts";
|
|
|
28
28
|
export function insert_child(
|
|
29
29
|
panel: Layout,
|
|
30
30
|
child: string,
|
|
31
|
-
path:
|
|
31
|
+
path: LayoutPathTraversal,
|
|
32
32
|
orientation?: "horizontal" | "vertical",
|
|
33
33
|
): Layout {
|
|
34
34
|
const createChildPanel = (childId: string): Layout => ({
|
|
35
|
-
type: "
|
|
35
|
+
type: "tab-layout",
|
|
36
36
|
tabs: [childId],
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
if (path.length === 0) {
|
|
40
40
|
// Insert at root level
|
|
41
|
-
if (panel.type === "
|
|
42
|
-
// Add to existing
|
|
41
|
+
if (panel.type === "tab-layout") {
|
|
42
|
+
// Add to existing tab-layout as a tab
|
|
43
43
|
return {
|
|
44
|
-
type: "
|
|
44
|
+
type: "tab-layout",
|
|
45
45
|
tabs: [child, ...panel.tabs],
|
|
46
46
|
};
|
|
47
47
|
} else if (orientation) {
|
|
48
48
|
// When inserting at edge of root, wrap the entire panel in a new split
|
|
49
49
|
return {
|
|
50
|
-
type: "split-
|
|
50
|
+
type: "split-layout",
|
|
51
51
|
orientation: orientation,
|
|
52
52
|
children: [createChildPanel(child), panel],
|
|
53
53
|
sizes: [0.5, 0.5],
|
|
54
54
|
};
|
|
55
55
|
} else {
|
|
56
|
-
// Append to existing split-
|
|
56
|
+
// Append to existing split-layout
|
|
57
57
|
const newChildren = [...panel.children, createChildPanel(child)];
|
|
58
58
|
const newSizes = [...panel.sizes, 1 / (newChildren.length - 1)];
|
|
59
59
|
return {
|
|
@@ -69,8 +69,8 @@ export function insert_child(
|
|
|
69
69
|
|
|
70
70
|
// Special case: when orientation is provided and restPath is empty, handle edge insertion
|
|
71
71
|
if (orientation && restPath.length === 0) {
|
|
72
|
-
// If panel is a split-
|
|
73
|
-
if (panel.type === "split-
|
|
72
|
+
// If panel is a split-layout with the same orientation, insert into its children
|
|
73
|
+
if (panel.type === "split-layout" && panel.orientation === orientation) {
|
|
74
74
|
const newChildren = [...panel.children];
|
|
75
75
|
newChildren.splice(index, 0, createChildPanel(child));
|
|
76
76
|
const newSizes = [...panel.sizes];
|
|
@@ -89,14 +89,14 @@ export function insert_child(
|
|
|
89
89
|
: [panel, createChildPanel(child)];
|
|
90
90
|
|
|
91
91
|
return {
|
|
92
|
-
type: "split-
|
|
92
|
+
type: "split-layout",
|
|
93
93
|
orientation: orientation,
|
|
94
94
|
children,
|
|
95
95
|
sizes: [0.5, 0.5],
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
if (panel.type === "
|
|
99
|
+
if (panel.type === "tab-layout") {
|
|
100
100
|
// Stack into child array only when ALL of these conditions are met:
|
|
101
101
|
// 1. Path has exactly one element (restPath is empty)
|
|
102
102
|
// 2. Orientation was NOT explicitly provided (orientation is undefined)
|
|
@@ -117,7 +117,7 @@ export function insert_child(
|
|
|
117
117
|
|
|
118
118
|
// Otherwise, wrap in a split panel and recurse
|
|
119
119
|
const newPanel: Layout = {
|
|
120
|
-
type: "split-
|
|
120
|
+
type: "split-layout",
|
|
121
121
|
orientation: orientation || "horizontal",
|
|
122
122
|
children: [panel],
|
|
123
123
|
sizes: [1],
|
|
@@ -130,7 +130,7 @@ export function insert_child(
|
|
|
130
130
|
if (orientation && panel.children[index]) {
|
|
131
131
|
// When inserting at an edge, create a split panel with the new child and existing child
|
|
132
132
|
const newSplitPanel: Layout = {
|
|
133
|
-
type: "split-
|
|
133
|
+
type: "split-layout",
|
|
134
134
|
orientation: orientation,
|
|
135
135
|
children: [createChildPanel(child), panel.children[index]],
|
|
136
136
|
sizes: [0.5, 0.5],
|
|
@@ -159,9 +159,9 @@ export function insert_child(
|
|
|
159
159
|
|
|
160
160
|
const targetChild = panel.children[index];
|
|
161
161
|
|
|
162
|
-
// Determine the orientation to pass down when navigating into a
|
|
162
|
+
// Determine the orientation to pass down when navigating into a tab-layout
|
|
163
163
|
const childOrientation =
|
|
164
|
-
targetChild.type === "
|
|
164
|
+
targetChild.type === "tab-layout" &&
|
|
165
165
|
restPath.length > 0 &&
|
|
166
166
|
orientation !== undefined
|
|
167
167
|
? panel.orientation === "horizontal"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
11
11
|
|
|
12
12
|
import { DEFAULT_PHYSICS } from "./constants.ts";
|
|
13
|
-
import type { Layout } from "./types.ts";
|
|
13
|
+
import type { Layout, LayoutPathTraversal } from "./types.ts";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Adjusts panel sizes during a drag operation on a divider.
|
|
@@ -31,7 +31,7 @@ import type { Layout } from "./types.ts";
|
|
|
31
31
|
*/
|
|
32
32
|
export function redistribute_panel_sizes(
|
|
33
33
|
panel: Layout,
|
|
34
|
-
path:
|
|
34
|
+
path: LayoutPathTraversal,
|
|
35
35
|
delta: number | undefined,
|
|
36
36
|
physics = DEFAULT_PHYSICS,
|
|
37
37
|
): Layout {
|
|
@@ -43,14 +43,14 @@ export function redistribute_panel_sizes(
|
|
|
43
43
|
let current: Layout = result;
|
|
44
44
|
const deltas = { horizontal: delta || 0, vertical: delta || 0 };
|
|
45
45
|
for (let i = 0; i < path.length - 1; i++) {
|
|
46
|
-
if (current.type === "split-
|
|
46
|
+
if (current.type === "split-layout") {
|
|
47
47
|
deltas[current.orientation] /= current.sizes[path[i]];
|
|
48
48
|
current = current.children[path[i]];
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// Apply the redistribution at the final path index
|
|
53
|
-
if (current.type === "split-
|
|
53
|
+
if (current.type === "split-layout") {
|
|
54
54
|
if (delta === undefined) {
|
|
55
55
|
current.sizes = current.sizes.map((_) => 1 / current.sizes.length);
|
|
56
56
|
} else {
|
|
@@ -25,14 +25,14 @@ import { EMPTY_PANEL } from "./types.ts";
|
|
|
25
25
|
*/
|
|
26
26
|
export function remove_child(panel: Layout, child: string): Layout {
|
|
27
27
|
// If this is a child panel, handle tab removal
|
|
28
|
-
if (panel.type === "
|
|
28
|
+
if (panel.type === "tab-layout") {
|
|
29
29
|
if (panel.tabs.includes(child)) {
|
|
30
30
|
const newChild = panel.tabs.filter((c) => c !== child);
|
|
31
31
|
if (newChild.length === 0) {
|
|
32
32
|
return structuredClone(EMPTY_PANEL);
|
|
33
33
|
}
|
|
34
34
|
return {
|
|
35
|
-
type: "
|
|
35
|
+
type: "tab-layout",
|
|
36
36
|
tabs: newChild,
|
|
37
37
|
};
|
|
38
38
|
}
|
|
@@ -45,7 +45,7 @@ export function remove_child(panel: Layout, child: string): Layout {
|
|
|
45
45
|
|
|
46
46
|
// Try to remove the child from this split panel's children
|
|
47
47
|
const index = result.children.findIndex((p) => {
|
|
48
|
-
if (p.type === "
|
|
48
|
+
if (p.type === "tab-layout") {
|
|
49
49
|
return p.tabs.includes(child);
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -82,7 +82,7 @@ export function remove_child(panel: Layout, child: string): Layout {
|
|
|
82
82
|
// Child not found at this level - recursively search children
|
|
83
83
|
let modified = false;
|
|
84
84
|
const newChildren = result.children.map((p) => {
|
|
85
|
-
if (p.type === "split-
|
|
85
|
+
if (p.type === "split-layout") {
|
|
86
86
|
const updated = remove_child(p, child);
|
|
87
87
|
if (updated !== p) {
|
|
88
88
|
modified = true;
|
package/src/layout/types.ts
CHANGED
|
@@ -47,7 +47,7 @@ export interface ViewWindow {
|
|
|
47
47
|
* opposite `orientation` as its parent.
|
|
48
48
|
*/
|
|
49
49
|
export interface SplitLayout {
|
|
50
|
-
type: "split-
|
|
50
|
+
type: "split-layout";
|
|
51
51
|
children: Layout[];
|
|
52
52
|
sizes: number[];
|
|
53
53
|
orientation: Orientation;
|
|
@@ -57,18 +57,23 @@ export interface SplitLayout {
|
|
|
57
57
|
* A leaf panel node that contains a single named child element.
|
|
58
58
|
*/
|
|
59
59
|
export interface TabLayout {
|
|
60
|
-
type: "
|
|
60
|
+
type: "tab-layout";
|
|
61
61
|
tabs: string[];
|
|
62
62
|
selected?: number;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Represents a panel location relative to a `Layout` struct.
|
|
67
|
+
*/
|
|
68
|
+
export type LayoutPathTraversal = number[];
|
|
69
|
+
|
|
65
70
|
/**
|
|
66
71
|
* Represents a draggable divider between two panels in the layout.
|
|
67
72
|
*
|
|
68
73
|
* Used for hit detection.
|
|
69
74
|
*/
|
|
70
75
|
export interface LayoutDivider {
|
|
71
|
-
path:
|
|
76
|
+
path: LayoutPathTraversal;
|
|
72
77
|
view_window: ViewWindow;
|
|
73
78
|
type: Orientation;
|
|
74
79
|
}
|
|
@@ -79,7 +84,7 @@ export interface LayoutDivider {
|
|
|
79
84
|
export interface LayoutPath {
|
|
80
85
|
type: "layout-path";
|
|
81
86
|
slot: string;
|
|
82
|
-
path:
|
|
87
|
+
path: LayoutPathTraversal;
|
|
83
88
|
view_window: ViewWindow;
|
|
84
89
|
column: number;
|
|
85
90
|
row: number;
|
|
@@ -94,7 +99,7 @@ export interface LayoutPath {
|
|
|
94
99
|
* An empty `Layout` with no panels.
|
|
95
100
|
*/
|
|
96
101
|
export const EMPTY_PANEL: Layout = {
|
|
97
|
-
type: "split-
|
|
102
|
+
type: "split-layout",
|
|
98
103
|
orientation: "horizontal",
|
|
99
104
|
sizes: [],
|
|
100
105
|
children: [],
|
|
@@ -162,7 +162,7 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
162
162
|
let new_tab_panel = this._layout.getPanel(slot, new_panel);
|
|
163
163
|
if (!new_tab_panel) {
|
|
164
164
|
new_tab_panel = {
|
|
165
|
-
type: "
|
|
165
|
+
type: "tab-layout",
|
|
166
166
|
tabs: [slot],
|
|
167
167
|
selected: 0,
|
|
168
168
|
};
|
package/src/regular-layout.ts
CHANGED
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
TabLayout,
|
|
26
26
|
OverlayMode,
|
|
27
27
|
Orientation,
|
|
28
|
+
LayoutPathTraversal,
|
|
28
29
|
} from "./layout/types.ts";
|
|
29
30
|
import { calculate_intersection } from "./layout/calculate_intersect.ts";
|
|
30
31
|
import { remove_child } from "./layout/remove_child.ts";
|
|
@@ -33,6 +34,7 @@ import { redistribute_panel_sizes } from "./layout/redistribute_panel_sizes.ts";
|
|
|
33
34
|
import { updateOverlaySheet } from "./layout/generate_overlay.ts";
|
|
34
35
|
import { calculate_edge } from "./layout/calculate_edge.ts";
|
|
35
36
|
import { flatten } from "./layout/flatten.ts";
|
|
37
|
+
import { calculate_path } from "./layout/calculate_path.ts";
|
|
36
38
|
import {
|
|
37
39
|
DEFAULT_PHYSICS,
|
|
38
40
|
type PhysicsUpdate,
|
|
@@ -151,6 +153,16 @@ export class RegularLayout extends HTMLElement {
|
|
|
151
153
|
return calculate_intersection(col, row, this._panel);
|
|
152
154
|
};
|
|
153
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Calculates the index path for a panel with the given name.
|
|
158
|
+
*
|
|
159
|
+
* @param name - The name of the panel to find.
|
|
160
|
+
* @returns The panel's index path if found, `null` otherwise.
|
|
161
|
+
*/
|
|
162
|
+
calculatePath = (name: string): number[] | null => {
|
|
163
|
+
return calculate_path(name, this._panel);
|
|
164
|
+
};
|
|
165
|
+
|
|
154
166
|
/**
|
|
155
167
|
* Sets the visual overlay state during drag-and-drop operations.
|
|
156
168
|
* Displays a preview of where a panel would be placed at the given coordinates.
|
|
@@ -290,7 +302,7 @@ export class RegularLayout extends HTMLElement {
|
|
|
290
302
|
*/
|
|
291
303
|
insertPanel = (
|
|
292
304
|
name: string,
|
|
293
|
-
path:
|
|
305
|
+
path: LayoutPathTraversal = [],
|
|
294
306
|
split?: boolean | Orientation,
|
|
295
307
|
) => {
|
|
296
308
|
let orientation: Orientation | undefined;
|
|
@@ -320,7 +332,7 @@ export class RegularLayout extends HTMLElement {
|
|
|
320
332
|
* @returns The TabLayout containing the panel if found, null otherwise.
|
|
321
333
|
*/
|
|
322
334
|
getPanel = (name: string, layout: Layout = this._panel): TabLayout | null => {
|
|
323
|
-
if (layout.type === "
|
|
335
|
+
if (layout.type === "tab-layout") {
|
|
324
336
|
if (layout.tabs.includes(name)) {
|
|
325
337
|
return layout;
|
|
326
338
|
}
|