regular-layout 0.1.0 → 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 +6 -6
- package/dist/extensions.d.ts +5 -4
- package/dist/index.d.ts +3 -3
- package/dist/index.js +15 -10
- package/dist/index.js.map +4 -4
- package/dist/{common → layout}/calculate_edge.d.ts +2 -2
- package/dist/{common → layout}/calculate_intersect.d.ts +7 -9
- package/dist/{common → layout}/constants.d.ts +15 -1
- 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} +6 -10
- package/dist/regular-layout-frame.d.ts +1 -4
- package/dist/regular-layout-tab.d.ts +26 -0
- package/dist/regular-layout.d.ts +23 -18
- package/package.json +9 -7
- package/src/extensions.ts +10 -4
- package/src/index.ts +3 -7
- package/src/layout/calculate_edge.ts +209 -0
- package/src/{common → layout}/calculate_intersect.ts +59 -101
- package/src/{common → layout}/constants.ts +18 -1
- package/src/{common → layout}/flatten.ts +1 -1
- package/src/{common → layout}/generate_grid.ts +76 -106
- package/src/{common → layout}/generate_overlay.ts +24 -12
- package/src/{common → layout}/insert_child.ts +105 -51
- package/src/{common → layout}/redistribute_panel_sizes.ts +1 -1
- package/src/{common → layout}/remove_child.ts +2 -2
- package/src/{common/layout_config.ts → layout/types.ts} +6 -19
- package/src/regular-layout-frame.ts +34 -71
- package/src/regular-layout-tab.ts +103 -0
- package/src/regular-layout.ts +190 -141
- 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_edge.ts +0 -104
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Layout, LayoutPath } from "./
|
|
1
|
+
import type { Layout, LayoutPath } from "./types";
|
|
2
2
|
/**
|
|
3
3
|
* Calculates an insertion point (which may involve splitting a single
|
|
4
4
|
* `"child-panel"` into a new `"split-panel"`), based on the cursor position.
|
|
@@ -12,4 +12,4 @@ import type { Layout, LayoutPath } from "./layout_config";
|
|
|
12
12
|
* @returns A new `LayoutPath` reflecting the updated (maybe) `"split-panel"`,
|
|
13
13
|
* which is enough to draw the overlay.
|
|
14
14
|
*/
|
|
15
|
-
export declare function calculate_edge(col: number, row: number, panel: Layout, slot: string, drop_target: LayoutPath): LayoutPath;
|
|
15
|
+
export declare function calculate_edge(col: number, row: number, panel: Layout, slot: string, drop_target: LayoutPath, box?: DOMRect): LayoutPath;
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import type { LayoutPath, LayoutDivider, Layout } from "./
|
|
1
|
+
import type { LayoutPath, LayoutDivider, Layout } from "./types.ts";
|
|
2
2
|
/**
|
|
3
3
|
* Determines which panel or divider is located at a given position in the
|
|
4
4
|
* layout.
|
|
5
5
|
*
|
|
6
|
-
* @param
|
|
7
|
-
* height
|
|
8
|
-
* @param column - Horizontal position as a fraction (0-1) of the
|
|
9
|
-
* container width
|
|
6
|
+
* @param column - Horizontal position as a fraction (0-1) of the container width
|
|
7
|
+
* @param row - Vertical position as a fraction (0-1) of the container height
|
|
10
8
|
* @param layout - The layout tree to search
|
|
11
9
|
* @param check_dividers - Whether `LayoutDivider` intersection should be
|
|
12
|
-
* checked, which
|
|
10
|
+
* checked, which you may not want for e.g. `drop` actions.
|
|
13
11
|
* @returns The panel path if over a panel, a divider if over a resizable
|
|
14
12
|
* boundary, or null if outside all panels
|
|
15
13
|
*/
|
|
16
|
-
export declare function calculate_intersection(column: number, row: number, layout: Layout, check_dividers
|
|
17
|
-
export declare function calculate_intersection(column: number, row: number, layout: Layout, check_dividers
|
|
18
|
-
export declare function calculate_intersection(column: number, row: number, layout: Layout, check_dividers?:
|
|
14
|
+
export declare function calculate_intersection(column: number, row: number, layout: Layout, check_dividers?: null): LayoutPath | null;
|
|
15
|
+
export declare function calculate_intersection(column: number, row: number, layout: Layout, check_dividers?: DOMRect): LayoutPath | null | LayoutDivider;
|
|
16
|
+
export declare function calculate_intersection(column: number, row: number, layout: Layout, check_dividers?: DOMRect | null): LayoutPath | null | LayoutDivider;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type { OverlayMode } from "./
|
|
1
|
+
import type { OverlayMode } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* The prefix to use for `CustomEvent`s generated by `regular-layout`, e.g.
|
|
4
|
+
* `"regular-layout-before-update"`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const CUSTOM_EVENT_NAME_PREFIX = "regular-layout";
|
|
2
7
|
/**
|
|
3
8
|
* The minimum number of pixels the mouse must move to be considered a drag.
|
|
4
9
|
*/
|
|
@@ -16,6 +21,11 @@ export declare const MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD = 0.15;
|
|
|
16
21
|
* Threshold from panel edge that is considered a split vs drop action.
|
|
17
22
|
*/
|
|
18
23
|
export declare const SPLIT_EDGE_TOLERANCE = 0.25;
|
|
24
|
+
/**
|
|
25
|
+
* Threshold from _container_ edge that is considered a split action on the root
|
|
26
|
+
* node.
|
|
27
|
+
*/
|
|
28
|
+
export declare const SPLIT_ROOT_EDGE_TOLERANCE = 0.01;
|
|
19
29
|
/**
|
|
20
30
|
* Tolerance threshold for considering two grid track positions as identical.
|
|
21
31
|
*
|
|
@@ -27,3 +37,7 @@ export declare const GRID_TRACK_COLLAPSE_TOLERANCE = 0.001;
|
|
|
27
37
|
* The overlay default behavior.
|
|
28
38
|
*/
|
|
29
39
|
export declare const OVERLAY_DEFAULT: OverlayMode;
|
|
40
|
+
/**
|
|
41
|
+
* Width of split panel dividers in pixels (for hit-test purposes).
|
|
42
|
+
*/
|
|
43
|
+
export declare const GRID_DIVIDER_SIZE = 6;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Layout } from "./
|
|
1
|
+
import type { Layout } from "./types.ts";
|
|
2
2
|
/**
|
|
3
3
|
* Generates CSS Grid styles to render a layout tree.
|
|
4
4
|
* Creates grid-template-rows, grid-template-columns, and positioning rules for
|
|
@@ -25,8 +25,8 @@ import type { Layout } from "./layout_config.ts";
|
|
|
25
25
|
* const css = create_css_grid_layout(layout);
|
|
26
26
|
* // Returns CSS like:
|
|
27
27
|
* // :host { display: grid; grid-template-columns: 25% 75%; ... }
|
|
28
|
-
* // :host ::slotted([
|
|
29
|
-
* // :host ::slotted([
|
|
28
|
+
* // :host ::slotted([name=sidebar]) { grid-column: 1; grid-row: 1; }
|
|
29
|
+
* // :host ::slotted([name=main]) { grid-column: 2; grid-row: 1; }
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
32
|
export declare function create_css_grid_layout(layout: Layout, round?: boolean, overlay?: [string, string]): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Layout } from "./
|
|
1
|
+
import type { Layout } 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
|
|
@@ -10,6 +10,7 @@ import type { Layout } from "./layout_config.ts";
|
|
|
10
10
|
* at root level.
|
|
11
11
|
* @param orientation - Orientation for newly created split panels. Defaults to
|
|
12
12
|
* "horizontal".
|
|
13
|
+
* @param is_edge - If true, create the split at the parent level.
|
|
13
14
|
* @returns A new layout tree with the child inserted (original is not mutated).
|
|
14
15
|
*/
|
|
15
|
-
export declare function insert_child(panel: Layout, child: string, path: number[], orientation?: "horizontal" | "vertical"
|
|
16
|
+
export declare function insert_child(panel: Layout, child: string, path: number[], orientation?: "horizontal" | "vertical"): Layout;
|
|
@@ -25,6 +25,11 @@ export interface ViewWindow {
|
|
|
25
25
|
* Child panels are arranged either horizontally (side by side) or vertically
|
|
26
26
|
* (stacked), via the `orientation` property `"horizzontal"` and `"vertical"`
|
|
27
27
|
* (respectively).
|
|
28
|
+
*
|
|
29
|
+
* While the type structure of `SplitLayout` allows nesting levels with the same
|
|
30
|
+
* `orientation`, calling `RegularLayout.restore` with such a `Layout` will be
|
|
31
|
+
* flattened to the equivalent layout with every child guaranteed to have the
|
|
32
|
+
* opposite `orientation` as its parent.
|
|
28
33
|
*/
|
|
29
34
|
export interface SplitLayout {
|
|
30
35
|
type: "split-panel";
|
|
@@ -60,7 +65,6 @@ export interface LayoutDivider {
|
|
|
60
65
|
export interface LayoutPath<T = undefined> {
|
|
61
66
|
type: "layout-path";
|
|
62
67
|
slot: string;
|
|
63
|
-
panel: TabLayout;
|
|
64
68
|
path: number[];
|
|
65
69
|
view_window: ViewWindow;
|
|
66
70
|
column: number;
|
|
@@ -69,16 +73,8 @@ export interface LayoutPath<T = undefined> {
|
|
|
69
73
|
row_offset: number;
|
|
70
74
|
orientation: Orientation;
|
|
71
75
|
is_edge: boolean;
|
|
72
|
-
|
|
76
|
+
layout: T;
|
|
73
77
|
}
|
|
74
|
-
/**
|
|
75
|
-
* Recursively iterates over all child panel names in the layout tree, yielding
|
|
76
|
-
* panel names in depth-first order.
|
|
77
|
-
*
|
|
78
|
-
* @param panel - The layout tree to iterate over
|
|
79
|
-
* @returns Generator yielding child panel names
|
|
80
|
-
*/
|
|
81
|
-
export declare function iter_panel_children(panel: Layout): Generator<string>;
|
|
82
78
|
/**
|
|
83
79
|
* An empty `Layout` with no panels.
|
|
84
80
|
*/
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* @example
|
|
18
18
|
* ```html
|
|
19
19
|
* <regular-layout>
|
|
20
|
-
* <regular-layout-frame
|
|
20
|
+
* <regular-layout-frame name="panel-1">
|
|
21
21
|
* <!-- Panel content here -->
|
|
22
22
|
* </regular-layout-frame>
|
|
23
23
|
* </regular-layout>
|
|
@@ -31,7 +31,6 @@ export declare class RegularLayoutFrame extends HTMLElement {
|
|
|
31
31
|
private _drag_state;
|
|
32
32
|
private _drag_moved;
|
|
33
33
|
private _tab_to_index_map;
|
|
34
|
-
private _tab_panel_state;
|
|
35
34
|
constructor();
|
|
36
35
|
connectedCallback(): void;
|
|
37
36
|
disconnectedCallback(): void;
|
|
@@ -40,6 +39,4 @@ export declare class RegularLayoutFrame extends HTMLElement {
|
|
|
40
39
|
private onPointerUp;
|
|
41
40
|
private onPointerLost;
|
|
42
41
|
private drawTabs;
|
|
43
|
-
private createTab;
|
|
44
|
-
private onTabClick;
|
|
45
42
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { TabLayout } from "./layout/types.ts";
|
|
2
|
+
import type { RegularLayout } from "./regular-layout.ts";
|
|
3
|
+
/**
|
|
4
|
+
* A custom HTML element representing an individual tab in a tab panel.
|
|
5
|
+
*
|
|
6
|
+
* This element manages the visual representation and interactions for a single tab,
|
|
7
|
+
* including selection state, close functionality, and content display.
|
|
8
|
+
*/
|
|
9
|
+
export declare class RegularLayoutTab extends HTMLElement {
|
|
10
|
+
private _layout?;
|
|
11
|
+
private _tab_panel?;
|
|
12
|
+
private _index?;
|
|
13
|
+
/**
|
|
14
|
+
* Populates or updates the tab with layout information.
|
|
15
|
+
*
|
|
16
|
+
* This method initializes the tab's content and event listeners on first call,
|
|
17
|
+
* and efficiently updates only the changed properties on subsequent calls.
|
|
18
|
+
*
|
|
19
|
+
* @param layout - The parent RegularLayout instance managing this tab.
|
|
20
|
+
* @param tab_panel - The tab panel layout containing this tab.
|
|
21
|
+
* @param index - The index of this tab within the tab panel.
|
|
22
|
+
*/
|
|
23
|
+
populate: (layout: RegularLayout, tab_panel: TabLayout, index: number) => void;
|
|
24
|
+
private onTabClose;
|
|
25
|
+
private onTabClick;
|
|
26
|
+
}
|
package/dist/regular-layout.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LayoutPath, Layout, TabLayout, OverlayMode } from "./
|
|
1
|
+
import type { LayoutPath, Layout, TabLayout, OverlayMode, Orientation } from "./layout/types.ts";
|
|
2
2
|
/**
|
|
3
3
|
* A Web Component that provides a resizable panel layout system.
|
|
4
4
|
* Panels are arranged using CSS Grid and can be resized by dragging dividers.
|
|
@@ -8,8 +8,8 @@ import type { LayoutPath, Layout, TabLayout, OverlayMode } from "./common/layout
|
|
|
8
8
|
* @example
|
|
9
9
|
* ```html
|
|
10
10
|
* <regular-layout>
|
|
11
|
-
* <div
|
|
12
|
-
* <div
|
|
11
|
+
* <div name="sidebar">Sidebar content</div>
|
|
12
|
+
* <div name="main">Main content</div>
|
|
13
13
|
* </regular-layout>
|
|
14
14
|
* ```
|
|
15
15
|
*
|
|
@@ -36,12 +36,21 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
36
36
|
private _shadowRoot;
|
|
37
37
|
private _panel;
|
|
38
38
|
private _stylesheet;
|
|
39
|
-
private
|
|
40
|
-
private
|
|
41
|
-
private
|
|
39
|
+
private _cursor_stylesheet;
|
|
40
|
+
private _drag_target?;
|
|
41
|
+
private _cursor_override;
|
|
42
|
+
private _dimensions?;
|
|
42
43
|
constructor();
|
|
43
44
|
connectedCallback(): void;
|
|
44
45
|
disconnectedCallback(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Determines which panel is at a given screen coordinate.
|
|
48
|
+
*
|
|
49
|
+
* @param column - X coordinate in screen pixels.
|
|
50
|
+
* @param row - Y coordinate in screen pixels.
|
|
51
|
+
* @returns Panel information if a panel is at that position, null otherwise.
|
|
52
|
+
*/
|
|
53
|
+
calculateIntersect: (x: number, y: number, check_dividers?: boolean) => LayoutPath<Layout> | null;
|
|
45
54
|
/**
|
|
46
55
|
* Sets the visual overlay state during drag-and-drop operations.
|
|
47
56
|
* Displays a preview of where a panel would be placed at the given coordinates.
|
|
@@ -69,14 +78,19 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
69
78
|
* @param mode - Overlay rendering mode that was used, must match the mode
|
|
70
79
|
* passed to `setOverlayState`. Defaults to "absolute".
|
|
71
80
|
*/
|
|
72
|
-
clearOverlayState: (x: number, y: number, drag_target: LayoutPath<
|
|
81
|
+
clearOverlayState: (x: number, y: number, drag_target: LayoutPath<Layout>, className?: string) => void;
|
|
73
82
|
/**
|
|
74
83
|
* Inserts a new panel into the layout at a specified path.
|
|
75
84
|
*
|
|
76
85
|
* @param name - Unique identifier for the new panel.
|
|
77
86
|
* @param path - Index path defining where to insert.
|
|
87
|
+
* @param split - Force a split in the layout at the end of `path`
|
|
88
|
+
* regardless if there is a leaf at this position or not. Optionally,
|
|
89
|
+
*. `split` may be your preferred `Orientation`, which will be used by
|
|
90
|
+
* the new `SplitPanel` _if_ there is an option of orientation (e.g. if
|
|
91
|
+
* the layout had no pre-existing `SplitPanel`)
|
|
78
92
|
*/
|
|
79
|
-
insertPanel: (name: string, path?: number[]) => void;
|
|
93
|
+
insertPanel: (name: string, path?: number[], split?: boolean | Orientation) => void;
|
|
80
94
|
/**
|
|
81
95
|
* Removes a panel from the layout by name.
|
|
82
96
|
*
|
|
@@ -91,14 +105,6 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
91
105
|
* @returns The TabLayout containing the panel if found, null otherwise.
|
|
92
106
|
*/
|
|
93
107
|
getPanel: (name: string, layout?: Layout) => TabLayout | null;
|
|
94
|
-
/**
|
|
95
|
-
* Determines which panel is at a given screen coordinate.
|
|
96
|
-
*
|
|
97
|
-
* @param column - X coordinate in screen pixels.
|
|
98
|
-
* @param row - Y coordinate in screen pixels.
|
|
99
|
-
* @returns Panel information if a panel is at that position, null otherwise.
|
|
100
|
-
*/
|
|
101
|
-
calculateIntersect: (x: number, y: number, check_dividers?: boolean) => LayoutPath<DOMRect> | null;
|
|
102
108
|
/**
|
|
103
109
|
* Clears the entire layout, unslotting all panels.
|
|
104
110
|
*/
|
|
@@ -142,8 +148,7 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
142
148
|
* - row: Normalized Y coordinate (0 = top edge, 1 = bottom edge)
|
|
143
149
|
* - box: The layout element's bounding rectangle
|
|
144
150
|
*/
|
|
145
|
-
relativeCoordinates: (clientX: number, clientY: number) => [number, number, DOMRect];
|
|
146
|
-
private updateSlots;
|
|
151
|
+
relativeCoordinates: (clientX: number, clientY: number, recalculate_bounds?: boolean) => [number, number, DOMRect, CSSStyleDeclaration];
|
|
147
152
|
private onPointerDown;
|
|
148
153
|
private onPointerMove;
|
|
149
154
|
private onPointerUp;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "regular-layout",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A regular CSS `grid` container",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -9,18 +9,20 @@
|
|
|
9
9
|
"url": "https://github.com/texodus/regular-layout"
|
|
10
10
|
},
|
|
11
11
|
"browser": "dist/index.js",
|
|
12
|
+
"main": "dist/index.js",
|
|
12
13
|
"type": "module",
|
|
13
14
|
"files": [
|
|
14
15
|
"dist/**/*",
|
|
15
|
-
"src/**/*"
|
|
16
|
+
"src/**/*",
|
|
17
|
+
"themes/**/*"
|
|
16
18
|
],
|
|
17
19
|
"scripts": {
|
|
18
|
-
"build": "
|
|
19
|
-
"build:watch": "
|
|
20
|
+
"build": "tsx build.ts",
|
|
21
|
+
"build:watch": "tsx build.ts --watch",
|
|
20
22
|
"clean": "rm -rf dist",
|
|
21
23
|
"test": "playwright test",
|
|
22
|
-
"example": "
|
|
23
|
-
"deploy": "
|
|
24
|
+
"example": "tsx serve.ts",
|
|
25
|
+
"deploy": "tsx deploy.ts",
|
|
24
26
|
"lint": "biome lint src tests",
|
|
25
27
|
"format": "biome format --write src tests",
|
|
26
28
|
"check": "biome check --write src tests"
|
|
@@ -30,7 +32,7 @@
|
|
|
30
32
|
"@playwright/test": "^1.57.0",
|
|
31
33
|
"@types/node": "^22.10.5",
|
|
32
34
|
"esbuild": "^0.27.2",
|
|
33
|
-
"
|
|
35
|
+
"tsx": "^4.21.0",
|
|
34
36
|
"typescript": "^5.9.3"
|
|
35
37
|
}
|
|
36
38
|
}
|
package/src/extensions.ts
CHANGED
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
|
|
12
12
|
import { RegularLayout } from "./regular-layout.ts";
|
|
13
13
|
import { RegularLayoutFrame } from "./regular-layout-frame.ts";
|
|
14
|
-
import type { Layout } from "./
|
|
14
|
+
import type { Layout } from "./layout/types.ts";
|
|
15
|
+
import { RegularLayoutTab } from "./regular-layout-tab.ts";
|
|
15
16
|
|
|
16
17
|
customElements.define("regular-layout", RegularLayout);
|
|
17
18
|
customElements.define("regular-layout-frame", RegularLayoutFrame);
|
|
19
|
+
customElements.define("regular-layout-tab", RegularLayoutTab);
|
|
18
20
|
|
|
19
21
|
declare global {
|
|
20
22
|
interface Document {
|
|
@@ -28,9 +30,15 @@ declare global {
|
|
|
28
30
|
options?: ElementCreationOptions,
|
|
29
31
|
): RegularLayoutFrame;
|
|
30
32
|
|
|
33
|
+
createElement(
|
|
34
|
+
tagName: "regular-layout-tab",
|
|
35
|
+
options?: ElementCreationOptions,
|
|
36
|
+
): RegularLayoutTab;
|
|
37
|
+
|
|
31
38
|
querySelector<E extends Element = Element>(selectors: string): E | null;
|
|
32
39
|
querySelector(selectors: "regular-layout"): RegularLayout | null;
|
|
33
40
|
querySelector(selectors: "regular-layout-frame"): RegularLayoutFrame | null;
|
|
41
|
+
querySelector(selectors: "regular-layout-tab"): RegularLayoutTab | null;
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
interface CustomElementRegistry {
|
|
@@ -63,6 +71,4 @@ declare global {
|
|
|
63
71
|
}
|
|
64
72
|
}
|
|
65
73
|
|
|
66
|
-
export
|
|
67
|
-
detail: Layout;
|
|
68
|
-
}
|
|
74
|
+
export type RegularLayoutEvent = CustomEvent<Layout>;
|
package/src/index.ts
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
*
|
|
20
20
|
* ```html
|
|
21
21
|
* <regular-layout>
|
|
22
|
-
* <regular-layout-frame
|
|
23
|
-
* <regular-layout-frame
|
|
22
|
+
* <regular-layout-frame name="sidebar">Sidebar content</regular-layout-frame>
|
|
23
|
+
* <regular-layout-frame name="main">Main content</regular-layout-frame>
|
|
24
24
|
* </regular-layout>
|
|
25
25
|
* ```
|
|
26
26
|
*
|
|
@@ -59,11 +59,7 @@
|
|
|
59
59
|
* @packageDocumentation
|
|
60
60
|
*/
|
|
61
61
|
|
|
62
|
-
export type
|
|
63
|
-
LayoutPath,
|
|
64
|
-
Layout,
|
|
65
|
-
LayoutDivider,
|
|
66
|
-
} from "./common/layout_config.ts";
|
|
62
|
+
export type * from "./layout/types.ts";
|
|
67
63
|
|
|
68
64
|
export { RegularLayout } from "./regular-layout.ts";
|
|
69
65
|
export { RegularLayoutFrame } from "./regular-layout-frame.ts";
|
|
@@ -0,0 +1,209 @@
|
|
|
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 { SPLIT_EDGE_TOLERANCE, SPLIT_ROOT_EDGE_TOLERANCE } from "./constants";
|
|
13
|
+
import { insert_child } from "./insert_child";
|
|
14
|
+
import type { Layout, LayoutPath, Orientation, ViewWindow } from "./types";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Calculates an insertion point (which may involve splitting a single
|
|
18
|
+
* `"child-panel"` into a new `"split-panel"`), based on the cursor position.
|
|
19
|
+
* *
|
|
20
|
+
* @param col - The cursor column.
|
|
21
|
+
* @param row - The cursor row.
|
|
22
|
+
* @param panel - The `Layout` to insert into.
|
|
23
|
+
* @param slot - The slot identifier where the insert should occur
|
|
24
|
+
* @param drop_target - The `LayoutPath` (from `calculateIntersect`) of the
|
|
25
|
+
* panel to either insert next to, or split by.
|
|
26
|
+
* @returns A new `LayoutPath` reflecting the updated (maybe) `"split-panel"`,
|
|
27
|
+
* which is enough to draw the overlay.
|
|
28
|
+
*/
|
|
29
|
+
export function calculate_edge(
|
|
30
|
+
col: number,
|
|
31
|
+
row: number,
|
|
32
|
+
panel: Layout,
|
|
33
|
+
slot: string,
|
|
34
|
+
drop_target: LayoutPath,
|
|
35
|
+
box?: DOMRect,
|
|
36
|
+
): LayoutPath {
|
|
37
|
+
// Check root edges first
|
|
38
|
+
if (col < SPLIT_ROOT_EDGE_TOLERANCE) {
|
|
39
|
+
return insert_root_edge(panel, slot, drop_target, [0], true, "horizontal");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (col > 1 - SPLIT_ROOT_EDGE_TOLERANCE) {
|
|
43
|
+
return insert_root_edge(
|
|
44
|
+
panel,
|
|
45
|
+
slot,
|
|
46
|
+
drop_target,
|
|
47
|
+
drop_target.path.length > 0 ? drop_target.path : [],
|
|
48
|
+
false,
|
|
49
|
+
"horizontal",
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (row < SPLIT_ROOT_EDGE_TOLERANCE) {
|
|
54
|
+
return insert_root_edge(panel, slot, drop_target, [0], true, "vertical");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (row > 1 - SPLIT_ROOT_EDGE_TOLERANCE) {
|
|
58
|
+
return insert_root_edge(panel, slot, drop_target, [], false, "vertical");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check panel edges
|
|
62
|
+
const is_column_edge =
|
|
63
|
+
drop_target.column_offset < SPLIT_EDGE_TOLERANCE ||
|
|
64
|
+
drop_target.column_offset > 1 - SPLIT_EDGE_TOLERANCE;
|
|
65
|
+
|
|
66
|
+
const is_row_edge =
|
|
67
|
+
drop_target.row_offset < SPLIT_EDGE_TOLERANCE ||
|
|
68
|
+
drop_target.row_offset > 1 - SPLIT_EDGE_TOLERANCE;
|
|
69
|
+
|
|
70
|
+
// If both edges triggered, choose closer axis
|
|
71
|
+
if (is_column_edge && is_row_edge) {
|
|
72
|
+
const col_distance = Math.abs(drop_target.column_offset - 0.5);
|
|
73
|
+
const row_distance = Math.abs(drop_target.row_offset - 0.5);
|
|
74
|
+
const col_scale =
|
|
75
|
+
(box?.width || 1) *
|
|
76
|
+
(drop_target.view_window.col_end - drop_target.view_window.col_start);
|
|
77
|
+
|
|
78
|
+
const row_scale =
|
|
79
|
+
(box?.height || 1) *
|
|
80
|
+
(drop_target.view_window.row_end - drop_target.view_window.row_start);
|
|
81
|
+
|
|
82
|
+
const use_column = col_distance * col_scale > row_distance * row_scale;
|
|
83
|
+
|
|
84
|
+
return insert_axis(
|
|
85
|
+
panel,
|
|
86
|
+
slot,
|
|
87
|
+
drop_target,
|
|
88
|
+
use_column
|
|
89
|
+
? drop_target.column_offset < SPLIT_EDGE_TOLERANCE
|
|
90
|
+
: drop_target.row_offset < SPLIT_EDGE_TOLERANCE,
|
|
91
|
+
use_column ? "horizontal" : "vertical",
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (is_column_edge) {
|
|
96
|
+
return insert_axis(
|
|
97
|
+
panel,
|
|
98
|
+
slot,
|
|
99
|
+
drop_target,
|
|
100
|
+
drop_target.column_offset < SPLIT_EDGE_TOLERANCE,
|
|
101
|
+
"horizontal",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (is_row_edge) {
|
|
106
|
+
return insert_axis(
|
|
107
|
+
panel,
|
|
108
|
+
slot,
|
|
109
|
+
drop_target,
|
|
110
|
+
drop_target.row_offset < SPLIT_EDGE_TOLERANCE,
|
|
111
|
+
"vertical",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Not at an edge - insert as a tab
|
|
116
|
+
return {
|
|
117
|
+
...drop_target,
|
|
118
|
+
path: [...drop_target.path, 0],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function insert_root_edge(
|
|
123
|
+
panel: Layout,
|
|
124
|
+
slot: string,
|
|
125
|
+
drop_target: LayoutPath,
|
|
126
|
+
path: number[],
|
|
127
|
+
is_before: boolean,
|
|
128
|
+
orientation: Orientation,
|
|
129
|
+
): LayoutPath {
|
|
130
|
+
return insert_axis(
|
|
131
|
+
panel,
|
|
132
|
+
slot,
|
|
133
|
+
{ ...drop_target, path, orientation },
|
|
134
|
+
is_before,
|
|
135
|
+
orientation,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function insert_axis(
|
|
140
|
+
panel: Layout,
|
|
141
|
+
slot: string,
|
|
142
|
+
drop_target: LayoutPath,
|
|
143
|
+
is_before: boolean,
|
|
144
|
+
axis_orientation: Orientation,
|
|
145
|
+
): LayoutPath {
|
|
146
|
+
let result_path: number[];
|
|
147
|
+
|
|
148
|
+
if (drop_target.orientation === axis_orientation) {
|
|
149
|
+
// Same orientation - insert into existing split
|
|
150
|
+
if (drop_target.path.length === 0) {
|
|
151
|
+
result_path = [is_before ? 0 : 1];
|
|
152
|
+
} else {
|
|
153
|
+
const last_index = drop_target.path[drop_target.path.length - 1];
|
|
154
|
+
result_path = [
|
|
155
|
+
...drop_target.path.slice(0, -1),
|
|
156
|
+
is_before ? last_index : last_index + 1,
|
|
157
|
+
];
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
// Different orientation - split the child panel
|
|
161
|
+
result_path = [...drop_target.path, is_before ? 0 : 1];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const new_panel = insert_child(panel, slot, result_path, axis_orientation);
|
|
165
|
+
const view_window = calculate_view_window(new_panel, result_path);
|
|
166
|
+
return {
|
|
167
|
+
...drop_target,
|
|
168
|
+
path: result_path,
|
|
169
|
+
slot: drop_target.slot,
|
|
170
|
+
is_edge: true,
|
|
171
|
+
orientation: axis_orientation,
|
|
172
|
+
view_window,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function calculate_view_window(panel: Layout, path: number[]): ViewWindow {
|
|
177
|
+
let view_window: ViewWindow = {
|
|
178
|
+
row_start: 0,
|
|
179
|
+
row_end: 1,
|
|
180
|
+
col_start: 0,
|
|
181
|
+
col_end: 1,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
let current_panel = panel;
|
|
185
|
+
for (const step of path) {
|
|
186
|
+
if (current_panel.type === "child-panel") {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const index = Math.min(step, current_panel.children.length - 1);
|
|
191
|
+
const is_vertical = current_panel.orientation === "vertical";
|
|
192
|
+
const start_key = is_vertical ? "row_start" : "col_start";
|
|
193
|
+
const end_key = is_vertical ? "row_end" : "col_end";
|
|
194
|
+
const total_size = view_window[end_key] - view_window[start_key];
|
|
195
|
+
const offset = current_panel.sizes
|
|
196
|
+
.slice(0, index)
|
|
197
|
+
.reduce((sum, size) => sum + size * total_size, view_window[start_key]);
|
|
198
|
+
|
|
199
|
+
view_window = {
|
|
200
|
+
...view_window,
|
|
201
|
+
[start_key]: offset,
|
|
202
|
+
[end_key]: offset + total_size * current_panel.sizes[index],
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
current_panel = current_panel.children[index];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return view_window;
|
|
209
|
+
}
|