regular-layout 0.0.1 → 0.1.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/README.md +26 -7
- package/dist/common/calculate_edge.d.ts +15 -0
- package/dist/common/constants.d.ts +29 -0
- package/dist/common/generate_grid.d.ts +1 -1
- package/dist/common/generate_overlay.d.ts +1 -1
- package/dist/common/insert_child.d.ts +1 -1
- package/dist/common/layout_config.d.ts +8 -15
- package/dist/common/redistribute_panel_sizes.d.ts +1 -1
- package/dist/extensions.d.ts +14 -0
- package/dist/index.js +9 -9
- package/dist/index.js.map +4 -4
- package/dist/regular-layout-frame.d.ts +7 -0
- package/dist/regular-layout.d.ts +47 -17
- package/package.json +2 -1
- package/src/common/calculate_edge.ts +104 -0
- package/src/common/calculate_intersect.ts +22 -9
- package/src/common/constants.ts +46 -0
- package/src/common/flatten.ts +5 -0
- package/src/common/generate_grid.ts +7 -4
- package/src/common/generate_overlay.ts +8 -5
- package/src/common/insert_child.ts +22 -15
- package/src/common/layout_config.ts +9 -18
- package/src/common/redistribute_panel_sizes.ts +7 -5
- package/src/common/remove_child.ts +38 -13
- package/src/extensions.ts +30 -2
- package/src/regular-layout-frame.ts +146 -23
- package/src/regular-layout.ts +163 -91
- package/dist/common/calculate_split.d.ts +0 -2
- package/src/common/calculate_split.ts +0 -53
package/dist/regular-layout.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { LayoutPath, Layout } from "./common/layout_config.ts";
|
|
2
|
-
export type OverlayMode = "grid" | "absolute" | "interactive";
|
|
1
|
+
import type { LayoutPath, Layout, TabLayout, OverlayMode } from "./common/layout_config.ts";
|
|
3
2
|
/**
|
|
4
3
|
* A Web Component that provides a resizable panel layout system.
|
|
5
4
|
* Panels are arranged using CSS Grid and can be resized by dragging dividers.
|
|
@@ -49,44 +48,61 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
49
48
|
*
|
|
50
49
|
* @param x - X coordinate in screen pixels.
|
|
51
50
|
* @param y - Y coordinate in screen pixels.
|
|
52
|
-
* @param
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
51
|
+
* @param dragTarget - A `LayoutPath` (presumably from `calculateIntersect`)
|
|
52
|
+
* which points to the drag element in the current layout.
|
|
53
|
+
* @param className - The CSS class name to use for the overlay panel
|
|
54
|
+
* (defaults to "overlay").
|
|
55
|
+
* @param mode - Overlay rendering mode: "grid" uses CSS grid to position
|
|
56
|
+
* the target, "absolute" positions the panel absolutely. Defaults to
|
|
57
|
+
* "absolute".
|
|
56
58
|
*/
|
|
57
|
-
setOverlayState
|
|
59
|
+
setOverlayState: (x: number, y: number, { slot }: LayoutPath<unknown>, className?: string, mode?: OverlayMode) => void;
|
|
58
60
|
/**
|
|
59
61
|
* Clears the overlay state and commits the panel placement.
|
|
60
62
|
*
|
|
61
63
|
* @param x - X coordinate in screen pixels.
|
|
62
64
|
* @param y - Y coordinate in screen pixels.
|
|
63
|
-
* @param
|
|
65
|
+
* @param dragTarget - A `LayoutPath` (presumably from `calculateIntersect`)
|
|
66
|
+
* which points to the drag element in the current layout.
|
|
67
|
+
* @param className - The CSS class name to use for the overlay panel
|
|
68
|
+
* (defaults to "overlay").
|
|
64
69
|
* @param mode - Overlay rendering mode that was used, must match the mode
|
|
65
|
-
*
|
|
70
|
+
* passed to `setOverlayState`. Defaults to "absolute".
|
|
66
71
|
*/
|
|
67
|
-
clearOverlayState
|
|
72
|
+
clearOverlayState: (x: number, y: number, drag_target: LayoutPath<unknown>, className?: string, mode?: OverlayMode) => void;
|
|
68
73
|
/**
|
|
69
74
|
* Inserts a new panel into the layout at a specified path.
|
|
70
75
|
*
|
|
71
76
|
* @param name - Unique identifier for the new panel.
|
|
72
77
|
* @param path - Index path defining where to insert.
|
|
73
78
|
*/
|
|
74
|
-
insertPanel(name: string, path?: number[])
|
|
79
|
+
insertPanel: (name: string, path?: number[]) => void;
|
|
75
80
|
/**
|
|
76
81
|
* Removes a panel from the layout by name.
|
|
77
82
|
*
|
|
78
83
|
* @param name - Name of the panel to remove
|
|
79
84
|
*/
|
|
80
|
-
removePanel(name: string)
|
|
85
|
+
removePanel: (name: string) => void;
|
|
86
|
+
/**
|
|
87
|
+
* Retrieves a panel by name from the layout tree.
|
|
88
|
+
*
|
|
89
|
+
* @param name - Name of the panel to find.
|
|
90
|
+
* @param layout - Optional layout tree to search in (defaults to current layout).
|
|
91
|
+
* @returns The TabLayout containing the panel if found, null otherwise.
|
|
92
|
+
*/
|
|
93
|
+
getPanel: (name: string, layout?: Layout) => TabLayout | null;
|
|
81
94
|
/**
|
|
82
95
|
* Determines which panel is at a given screen coordinate.
|
|
83
|
-
* Useful for drag-and-drop operations or custom interactions.
|
|
84
96
|
*
|
|
85
97
|
* @param column - X coordinate in screen pixels.
|
|
86
98
|
* @param row - Y coordinate in screen pixels.
|
|
87
99
|
* @returns Panel information if a panel is at that position, null otherwise.
|
|
88
100
|
*/
|
|
89
|
-
calculateIntersect(x: number, y: number, check_dividers?: boolean)
|
|
101
|
+
calculateIntersect: (x: number, y: number, check_dividers?: boolean) => LayoutPath<DOMRect> | null;
|
|
102
|
+
/**
|
|
103
|
+
* Clears the entire layout, unslotting all panels.
|
|
104
|
+
*/
|
|
105
|
+
clear: () => void;
|
|
90
106
|
/**
|
|
91
107
|
* Restores the layout from a saved state.
|
|
92
108
|
*
|
|
@@ -99,7 +115,7 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
99
115
|
* layout.restore(savedState);
|
|
100
116
|
* ```
|
|
101
117
|
*/
|
|
102
|
-
restore(layout: Layout, _is_flattened?: boolean)
|
|
118
|
+
restore: (layout: Layout, _is_flattened?: boolean) => void;
|
|
103
119
|
/**
|
|
104
120
|
* Serializes the current layout state, which can be restored via `restore`.
|
|
105
121
|
*
|
|
@@ -112,8 +128,22 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
112
128
|
* localStorage.setItem('layout', JSON.stringify(state));
|
|
113
129
|
* ```
|
|
114
130
|
*/
|
|
115
|
-
save()
|
|
116
|
-
|
|
131
|
+
save: () => Layout;
|
|
132
|
+
/**
|
|
133
|
+
* Converts screen coordinates to relative layout coordinates.
|
|
134
|
+
*
|
|
135
|
+
* Transforms absolute pixel positions into normalized coordinates (0-1 range)
|
|
136
|
+
* relative to the layout's bounding box.
|
|
137
|
+
*
|
|
138
|
+
* @param clientX - X coordinate in screen pixels (client space).
|
|
139
|
+
* @param clientY - Y coordinate in screen pixels (client space).
|
|
140
|
+
* @returns A tuple containing:
|
|
141
|
+
* - col: Normalized X coordinate (0 = left edge, 1 = right edge)
|
|
142
|
+
* - row: Normalized Y coordinate (0 = top edge, 1 = bottom edge)
|
|
143
|
+
* - box: The layout element's bounding rectangle
|
|
144
|
+
*/
|
|
145
|
+
relativeCoordinates: (clientX: number, clientY: number) => [number, number, DOMRect];
|
|
146
|
+
private updateSlots;
|
|
117
147
|
private onPointerDown;
|
|
118
148
|
private onPointerMove;
|
|
119
149
|
private onPointerUp;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "regular-layout",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "A regular CSS `grid` container",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"clean": "rm -rf dist",
|
|
21
21
|
"test": "playwright test",
|
|
22
22
|
"example": "npx http-server . -p 8000",
|
|
23
|
+
"deploy": "node deploy.mjs",
|
|
23
24
|
"lint": "biome lint src tests",
|
|
24
25
|
"format": "biome format --write src tests",
|
|
25
26
|
"check": "biome check --write src tests"
|
|
@@ -0,0 +1,104 @@
|
|
|
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 { calculate_intersection } from "./calculate_intersect";
|
|
13
|
+
import { SPLIT_EDGE_TOLERANCE } from "./constants";
|
|
14
|
+
import { insert_child } from "./insert_child";
|
|
15
|
+
import type { Layout, LayoutPath, Orientation } from "./layout_config";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Calculates an insertion point (which may involve splitting a single
|
|
19
|
+
* `"child-panel"` into a new `"split-panel"`), based on the cursor position.
|
|
20
|
+
* *
|
|
21
|
+
* @param col - The cursor column.
|
|
22
|
+
* @param row - The cursor row.
|
|
23
|
+
* @param panel - The `Layout` to insert into.
|
|
24
|
+
* @param slot - The slot identifier where the insert should occur
|
|
25
|
+
* @param drop_target - The `LayoutPath` (from `calculateIntersect`) of the
|
|
26
|
+
* panel to either insert next to, or split by.
|
|
27
|
+
* @returns A new `LayoutPath` reflecting the updated (maybe) `"split-panel"`,
|
|
28
|
+
* which is enough to draw the overlay.
|
|
29
|
+
*/
|
|
30
|
+
export function calculate_edge(
|
|
31
|
+
col: number,
|
|
32
|
+
row: number,
|
|
33
|
+
panel: Layout,
|
|
34
|
+
slot: string,
|
|
35
|
+
drop_target: LayoutPath,
|
|
36
|
+
): LayoutPath {
|
|
37
|
+
const is_column_edge =
|
|
38
|
+
drop_target.column_offset < SPLIT_EDGE_TOLERANCE ||
|
|
39
|
+
drop_target.column_offset > 1 - SPLIT_EDGE_TOLERANCE;
|
|
40
|
+
|
|
41
|
+
const is_row_edge =
|
|
42
|
+
drop_target.row_offset < SPLIT_EDGE_TOLERANCE ||
|
|
43
|
+
drop_target.row_offset > 1 - SPLIT_EDGE_TOLERANCE;
|
|
44
|
+
|
|
45
|
+
if (is_column_edge) {
|
|
46
|
+
return handle_axis(
|
|
47
|
+
col,
|
|
48
|
+
row,
|
|
49
|
+
panel,
|
|
50
|
+
slot,
|
|
51
|
+
drop_target,
|
|
52
|
+
drop_target.column_offset,
|
|
53
|
+
"horizontal",
|
|
54
|
+
);
|
|
55
|
+
} else if (is_row_edge) {
|
|
56
|
+
return handle_axis(
|
|
57
|
+
col,
|
|
58
|
+
row,
|
|
59
|
+
panel,
|
|
60
|
+
slot,
|
|
61
|
+
drop_target,
|
|
62
|
+
drop_target.row_offset,
|
|
63
|
+
"vertical",
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return drop_target;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function handle_axis(
|
|
71
|
+
col: number,
|
|
72
|
+
row: number,
|
|
73
|
+
panel: Layout,
|
|
74
|
+
slot: string,
|
|
75
|
+
drop_target: LayoutPath,
|
|
76
|
+
axis_offset: number,
|
|
77
|
+
axis_orientation: Orientation,
|
|
78
|
+
): LayoutPath {
|
|
79
|
+
const is_before = axis_offset < SPLIT_EDGE_TOLERANCE;
|
|
80
|
+
if (drop_target.orientation === axis_orientation) {
|
|
81
|
+
if (drop_target.path.length === 0) {
|
|
82
|
+
const insert_index = is_before ? 0 : 1;
|
|
83
|
+
const new_panel = insert_child(panel, slot, [insert_index]);
|
|
84
|
+
drop_target = calculate_intersection(col, row, new_panel, false);
|
|
85
|
+
} else {
|
|
86
|
+
const path_without_last = drop_target.path.slice(0, -1);
|
|
87
|
+
const last_index = drop_target.path[drop_target.path.length - 1];
|
|
88
|
+
const insert_index = is_before ? last_index : last_index + 1;
|
|
89
|
+
const new_panel = insert_child(panel, slot, [
|
|
90
|
+
...path_without_last,
|
|
91
|
+
insert_index,
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
drop_target = calculate_intersection(col, row, new_panel, false);
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
const path = [...drop_target.path, is_before ? 0 : 1];
|
|
98
|
+
const new_panel = insert_child(panel, slot, path, axis_orientation);
|
|
99
|
+
drop_target = calculate_intersection(col, row, new_panel, false);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
drop_target.is_edge = true;
|
|
103
|
+
return drop_target;
|
|
104
|
+
}
|
|
@@ -72,25 +72,38 @@ 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
|
+
|
|
90
|
+
const row_offset =
|
|
91
|
+
(row - view_window.row_start) /
|
|
92
|
+
(view_window.row_end - view_window.row_start);
|
|
93
|
+
|
|
81
94
|
return {
|
|
82
95
|
type: "layout-path",
|
|
83
96
|
box: undefined,
|
|
84
|
-
slot: panel.child,
|
|
97
|
+
slot: panel.child[selected],
|
|
98
|
+
panel: structuredClone(panel),
|
|
85
99
|
path: path,
|
|
86
100
|
view_window: view_window,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
orientation: parent_orientation,
|
|
101
|
+
is_edge: false,
|
|
102
|
+
column,
|
|
103
|
+
row,
|
|
104
|
+
column_offset,
|
|
105
|
+
row_offset,
|
|
106
|
+
orientation: parent_orientation || "horizontal",
|
|
94
107
|
};
|
|
95
108
|
}
|
|
96
109
|
|
|
@@ -0,0 +1,46 @@
|
|
|
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 { OverlayMode } from "./layout_config";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The minimum number of pixels the mouse must move to be considered a drag.
|
|
16
|
+
*/
|
|
17
|
+
export const MIN_DRAG_DISTANCE = 10;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Class name to use for child elements in overlay position (dragging).
|
|
21
|
+
*/
|
|
22
|
+
export const OVERLAY_CLASSNAME = "overlay";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The percentage of the maximum resize distance that will be clamped.
|
|
26
|
+
*
|
|
27
|
+
*/
|
|
28
|
+
export const MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD = 0.15;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Threshold from panel edge that is considered a split vs drop action.
|
|
32
|
+
*/
|
|
33
|
+
export const SPLIT_EDGE_TOLERANCE = 0.25;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Tolerance threshold for considering two grid track positions as identical.
|
|
37
|
+
*
|
|
38
|
+
* When collecting and deduplicating track positions, any positions closer than
|
|
39
|
+
* this value are treated as the same position to avoid redundant grid tracks.
|
|
40
|
+
*/
|
|
41
|
+
export const GRID_TRACK_COLLAPSE_TOLERANCE = 0.001;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The overlay default behavior.
|
|
45
|
+
*/
|
|
46
|
+
export const OVERLAY_DEFAULT: OverlayMode = "absolute";
|
package/src/common/flatten.ts
CHANGED
|
@@ -24,6 +24,7 @@ import type { Layout } from "./layout_config.ts";
|
|
|
24
24
|
*/
|
|
25
25
|
export function flatten(layout: Layout): Layout {
|
|
26
26
|
if (layout.type === "child-panel") {
|
|
27
|
+
layout.selected = layout.selected || 0;
|
|
27
28
|
return layout;
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -48,6 +49,10 @@ export function flatten(layout: Layout): Layout {
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
if (flattenedChildren.length === 1) {
|
|
53
|
+
return flattenedChildren[0];
|
|
54
|
+
}
|
|
55
|
+
|
|
51
56
|
return {
|
|
52
57
|
type: "split-panel",
|
|
53
58
|
orientation: layout.orientation,
|
|
@@ -9,7 +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
|
|
12
|
+
import { GRID_TRACK_COLLAPSE_TOLERANCE } from "./constants.ts";
|
|
13
|
+
import type { Layout } from "./layout_config.ts";
|
|
13
14
|
import { remove_child } from "./remove_child.ts";
|
|
14
15
|
|
|
15
16
|
interface GridCell {
|
|
@@ -85,7 +86,7 @@ function collectTrackPositions(
|
|
|
85
86
|
|
|
86
87
|
function findTrackIndex(positions: number[], value: number): number {
|
|
87
88
|
for (let i = 0; i < positions.length; i++) {
|
|
88
|
-
if (Math.abs(positions[i] - value) <
|
|
89
|
+
if (Math.abs(positions[i] - value) < GRID_TRACK_COLLAPSE_TOLERANCE) {
|
|
89
90
|
return i;
|
|
90
91
|
}
|
|
91
92
|
}
|
|
@@ -103,9 +104,10 @@ function buildCells(
|
|
|
103
104
|
rowEnd: number,
|
|
104
105
|
): GridCell[] {
|
|
105
106
|
if (panel.type === "child-panel") {
|
|
107
|
+
const selected = panel.selected ?? 0;
|
|
106
108
|
return [
|
|
107
109
|
{
|
|
108
|
-
child: panel.child,
|
|
110
|
+
child: panel.child[selected],
|
|
109
111
|
colStart: findTrackIndex(colPositions, colStart),
|
|
110
112
|
colEnd: findTrackIndex(colPositions, colEnd),
|
|
111
113
|
rowStart: findTrackIndex(rowPositions, rowStart),
|
|
@@ -203,7 +205,8 @@ export function create_css_grid_layout(
|
|
|
203
205
|
}
|
|
204
206
|
|
|
205
207
|
if (layout.type === "child-panel") {
|
|
206
|
-
|
|
208
|
+
const selected = layout.selected ?? 0;
|
|
209
|
+
return `${host_template("100%", "100%")}\n${child_template(layout.child[selected], "1", "1")}`;
|
|
207
210
|
}
|
|
208
211
|
|
|
209
212
|
const colPositions = collectTrackPositions(layout, "horizontal", 0, 1);
|
|
@@ -11,15 +11,18 @@
|
|
|
11
11
|
|
|
12
12
|
import type { LayoutPath } from "./layout_config";
|
|
13
13
|
|
|
14
|
-
export function updateOverlaySheet(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
export function updateOverlaySheet(
|
|
15
|
+
slot: string,
|
|
16
|
+
{
|
|
17
|
+
view_window: { row_start, row_end, col_start, col_end },
|
|
18
|
+
box,
|
|
19
|
+
}: LayoutPath<DOMRect>,
|
|
20
|
+
) {
|
|
18
21
|
const margin = 0;
|
|
19
22
|
const top = row_start * box.height + margin / 2;
|
|
20
23
|
const left = col_start * box.width + margin / 2;
|
|
21
24
|
const height = (row_end - row_start) * box.height - margin;
|
|
22
25
|
const width = (col_end - col_start) * box.width - margin;
|
|
23
26
|
const css = `position:absolute!important;z-index:1;top:${top}px;left:${left}px;height:${height}px;width:${width}px;`;
|
|
24
|
-
return `::slotted(
|
|
27
|
+
return `::slotted([slot="${slot}"]){${css}}`;
|
|
25
28
|
}
|
|
@@ -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,21 @@ 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
|
+
panel.children[index].selected = 0;
|
|
81
|
+
return panel;
|
|
82
|
+
}
|
|
83
|
+
|
|
85
84
|
// Insert at this level at the specified index
|
|
86
85
|
const newChildren = [...panel.children];
|
|
87
86
|
newChildren.splice(index, 0, {
|
|
88
87
|
type: "child-panel",
|
|
89
|
-
child,
|
|
88
|
+
child: [child],
|
|
90
89
|
});
|
|
91
90
|
|
|
92
91
|
const numChildren = newChildren.length;
|
|
@@ -109,6 +108,7 @@ export function insert_child(
|
|
|
109
108
|
child,
|
|
110
109
|
restPath,
|
|
111
110
|
oppositeOrientation,
|
|
111
|
+
is_edge,
|
|
112
112
|
);
|
|
113
113
|
|
|
114
114
|
const newChildren = [...panel.children];
|
|
@@ -119,7 +119,14 @@ export function insert_child(
|
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
const updatedChild = insert_child(
|
|
122
|
+
const updatedChild = insert_child(
|
|
123
|
+
targetChild,
|
|
124
|
+
child,
|
|
125
|
+
restPath,
|
|
126
|
+
orientation,
|
|
127
|
+
is_edge,
|
|
128
|
+
);
|
|
129
|
+
|
|
123
130
|
const newChildren = [...panel.children];
|
|
124
131
|
newChildren[index] = updatedChild;
|
|
125
132
|
return {
|
|
@@ -10,23 +10,9 @@
|
|
|
10
10
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* The
|
|
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.15;
|
|
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.
|
|
13
|
+
* The overlay behavior type.
|
|
28
14
|
*/
|
|
29
|
-
export
|
|
15
|
+
export type OverlayMode = "grid" | "absolute";
|
|
30
16
|
|
|
31
17
|
/**
|
|
32
18
|
* The representation of a CSS grid, in JSON form.
|
|
@@ -67,7 +53,8 @@ export interface SplitLayout {
|
|
|
67
53
|
*/
|
|
68
54
|
export interface TabLayout {
|
|
69
55
|
type: "child-panel";
|
|
70
|
-
child: string;
|
|
56
|
+
child: string[];
|
|
57
|
+
selected?: number;
|
|
71
58
|
}
|
|
72
59
|
|
|
73
60
|
/**
|
|
@@ -91,11 +78,15 @@ export interface LayoutDivider {
|
|
|
91
78
|
export interface LayoutPath<T = undefined> {
|
|
92
79
|
type: "layout-path";
|
|
93
80
|
slot: string;
|
|
81
|
+
panel: TabLayout;
|
|
94
82
|
path: number[];
|
|
95
83
|
view_window: ViewWindow;
|
|
84
|
+
column: number;
|
|
85
|
+
row: number;
|
|
96
86
|
column_offset: number;
|
|
97
87
|
row_offset: number;
|
|
98
88
|
orientation: Orientation;
|
|
89
|
+
is_edge: boolean;
|
|
99
90
|
box: T;
|
|
100
91
|
}
|
|
101
92
|
|
|
@@ -112,7 +103,7 @@ export function* iter_panel_children(panel: Layout): Generator<string> {
|
|
|
112
103
|
yield* iter_panel_children(child);
|
|
113
104
|
}
|
|
114
105
|
} else {
|
|
115
|
-
yield panel.child;
|
|
106
|
+
yield panel.child[panel.selected || 0];
|
|
116
107
|
}
|
|
117
108
|
}
|
|
118
109
|
|
|
@@ -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 "./layout_config.ts";
|
|
16
14
|
|
|
17
15
|
/**
|
|
18
16
|
* Adjusts panel sizes during a drag operation on a divider.
|
|
@@ -54,7 +52,11 @@ export function redistribute_panel_sizes(
|
|
|
54
52
|
if (current.type === "split-panel") {
|
|
55
53
|
const delta = deltas[current.orientation];
|
|
56
54
|
const index = path[path.length - 1];
|
|
57
|
-
|
|
55
|
+
|
|
56
|
+
// It would be fun to remove this condition.
|
|
57
|
+
if (index < current.sizes.length - 1) {
|
|
58
|
+
current.sizes = add_and_redistribute(current.sizes, index, delta);
|
|
59
|
+
}
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
return result;
|