regular-layout 0.2.1 → 0.2.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/dist/index.js +5 -5
- package/dist/index.js.map +3 -3
- package/dist/layout/generate_grid.d.ts +2 -2
- package/dist/layout/generate_overlay.d.ts +1 -1
- package/dist/layout/redistribute_panel_sizes.d.ts +1 -1
- package/dist/layout/types.d.ts +3 -7
- package/dist/regular-layout-frame.d.ts +10 -3
- package/dist/regular-layout.d.ts +49 -12
- package/package.json +4 -2
- package/src/layout/calculate_edge.ts +3 -1
- package/src/layout/calculate_intersect.ts +2 -2
- package/src/layout/generate_grid.ts +4 -4
- package/src/layout/generate_overlay.ts +1 -1
- package/src/layout/insert_child.ts +5 -5
- package/src/layout/redistribute_panel_sizes.ts +16 -12
- package/src/layout/remove_child.ts +7 -7
- package/src/layout/types.ts +3 -8
- package/src/regular-layout-frame.ts +52 -67
- package/src/regular-layout-tab.ts +5 -5
- package/src/regular-layout.ts +133 -104
|
@@ -17,8 +17,8 @@ import type { Layout } from "./types.ts";
|
|
|
17
17
|
* type: "split-panel",
|
|
18
18
|
* orientation: "horizontal",
|
|
19
19
|
* children: [
|
|
20
|
-
* { type: "child-panel",
|
|
21
|
-
* { type: "child-panel",
|
|
20
|
+
* { type: "child-panel", tabs: "sidebar" },
|
|
21
|
+
* { type: "child-panel", tabs: "main" }
|
|
22
22
|
* ],
|
|
23
23
|
* sizes: [0.25, 0.75]
|
|
24
24
|
* };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { LayoutPath } from "./types";
|
|
2
|
-
export declare function updateOverlaySheet(slot: string, box: DOMRect, style: CSSStyleDeclaration, drag_target: LayoutPath
|
|
2
|
+
export declare function updateOverlaySheet(slot: string, box: DOMRect, style: CSSStyleDeclaration, drag_target: LayoutPath | null, physics?: import("./constants").Physics): string;
|
|
@@ -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: number[], delta: number, physics?: import("./constants.ts").Physics): Layout;
|
|
19
|
+
export declare function redistribute_panel_sizes(panel: Layout, path: number[], delta: number | undefined, physics?: import("./constants.ts").Physics): Layout;
|
package/dist/layout/types.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export interface SplitLayout {
|
|
|
42
42
|
*/
|
|
43
43
|
export interface TabLayout {
|
|
44
44
|
type: "child-panel";
|
|
45
|
-
|
|
45
|
+
tabs: string[];
|
|
46
46
|
selected?: number;
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
@@ -57,12 +57,8 @@ export interface LayoutDivider {
|
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
59
|
* Represents a panel location result from hit detection.
|
|
60
|
-
*
|
|
61
|
-
* Contains both the panel identifier and its grid position in relative units.
|
|
62
|
-
* The generic parameter `T` allows DOM-only properties (e.g. `DOMRect`) to be
|
|
63
|
-
* shared in this cross-platform module.
|
|
64
60
|
*/
|
|
65
|
-
export interface LayoutPath
|
|
61
|
+
export interface LayoutPath {
|
|
66
62
|
type: "layout-path";
|
|
67
63
|
slot: string;
|
|
68
64
|
path: number[];
|
|
@@ -73,7 +69,7 @@ export interface LayoutPath<T = undefined> {
|
|
|
73
69
|
row_offset: number;
|
|
74
70
|
orientation: Orientation;
|
|
75
71
|
is_edge: boolean;
|
|
76
|
-
layout:
|
|
72
|
+
layout: Layout;
|
|
77
73
|
}
|
|
78
74
|
/**
|
|
79
75
|
* An empty `Layout` with no panels.
|
|
@@ -28,15 +28,22 @@ export declare class RegularLayoutFrame extends HTMLElement {
|
|
|
28
28
|
private _container_sheet;
|
|
29
29
|
private _layout;
|
|
30
30
|
private _header;
|
|
31
|
-
private
|
|
32
|
-
private _drag_moved;
|
|
31
|
+
private _drag;
|
|
33
32
|
private _tab_to_index_map;
|
|
34
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Initializes this elements. Override this method and
|
|
35
|
+
* `disconnectedCallback` to modify how this subclass renders the Shadow
|
|
36
|
+
* DOM and registers events.
|
|
37
|
+
*/
|
|
35
38
|
connectedCallback(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Destroys this element.
|
|
41
|
+
*/
|
|
36
42
|
disconnectedCallback(): void;
|
|
37
43
|
private onPointerDown;
|
|
38
44
|
private onPointerMove;
|
|
39
45
|
private onPointerUp;
|
|
46
|
+
private onPointerCancel;
|
|
40
47
|
private onPointerLost;
|
|
41
48
|
private drawTabs;
|
|
42
49
|
}
|
package/dist/regular-layout.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { LayoutPath, Layout, TabLayout, OverlayMode, Orientation } from "./layout/types.ts";
|
|
2
2
|
import { type PhysicsUpdate, type Physics } from "./layout/constants.ts";
|
|
3
|
+
/**
|
|
4
|
+
* An interface which models the fields of `PointerEvent` that
|
|
5
|
+
* `<regular-layour>` actually uses, making it easier to sub out with an
|
|
6
|
+
* JavaScript object lieral when you don't have a `PointerEvent` handy.
|
|
7
|
+
*/
|
|
8
|
+
export interface PointerEventCoordinates {
|
|
9
|
+
clientX: number;
|
|
10
|
+
clientY: number;
|
|
11
|
+
}
|
|
3
12
|
/**
|
|
4
13
|
* A Web Component that provides a resizable panel layout system.
|
|
5
14
|
* Panels are arranged using CSS Grid and can be resized by dragging dividers.
|
|
@@ -32,6 +41,22 @@ import { type PhysicsUpdate, type Physics } from "./layout/constants.ts";
|
|
|
32
41
|
* layout.restore(state);
|
|
33
42
|
* ```
|
|
34
43
|
*
|
|
44
|
+
* @remarks
|
|
45
|
+
*
|
|
46
|
+
* Why does this implementation use a `<slot>` at all? We must use
|
|
47
|
+
* `<slot>` and the Shadow DOM to scope the grid CSS rules to each
|
|
48
|
+
* instance of `<regular-layout>` (without e.g. giving them unique
|
|
49
|
+
* `"id"` and injecting into `document,head`), and we can only select
|
|
50
|
+
* `::slotted` light DOM children from `adoptedStyleSheets` on the
|
|
51
|
+
* `ShadowRoot`.
|
|
52
|
+
*
|
|
53
|
+
* Why does this implementation use a single `<slot>` and the child
|
|
54
|
+
* `"name"` attribute, as opposed to a named `<slot name="my_slot">`
|
|
55
|
+
* and the built-in `"slot"` child attribute? Children with a `"slot"`
|
|
56
|
+
* attribute don't fallback to the un-named `<slot>`, so using the
|
|
57
|
+
* latter implementation would require synchronizing the light DOM
|
|
58
|
+
* and shadow DOM slots/slotted children continuously.
|
|
59
|
+
*
|
|
35
60
|
*/
|
|
36
61
|
export declare class RegularLayout extends HTMLElement {
|
|
37
62
|
private _shadowRoot;
|
|
@@ -48,17 +73,17 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
48
73
|
/**
|
|
49
74
|
* Determines which panel is at a given screen coordinate.
|
|
50
75
|
*
|
|
51
|
-
* @param
|
|
52
|
-
*
|
|
76
|
+
* @param coordinates - `PointerEvent`, `MouseEvent`, or just X and Y
|
|
77
|
+
* coordinates in screen pixels.
|
|
53
78
|
* @returns Panel information if a panel is at that position, null otherwise.
|
|
54
79
|
*/
|
|
55
|
-
calculateIntersect: (
|
|
80
|
+
calculateIntersect: (coordinates: PointerEventCoordinates) => LayoutPath | null;
|
|
56
81
|
/**
|
|
57
82
|
* Sets the visual overlay state during drag-and-drop operations.
|
|
58
83
|
* Displays a preview of where a panel would be placed at the given coordinates.
|
|
59
84
|
*
|
|
60
|
-
* @param
|
|
61
|
-
*
|
|
85
|
+
* @param event - `PointerEvent`, `MouseEvent`, or just X and Y
|
|
86
|
+
* coordinates in screen pixels.
|
|
62
87
|
* @param dragTarget - A `LayoutPath` (presumably from `calculateIntersect`)
|
|
63
88
|
* which points to the drag element in the current layout.
|
|
64
89
|
* @param className - The CSS class name to use for the overlay panel
|
|
@@ -67,12 +92,12 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
67
92
|
* the target, "absolute" positions the panel absolutely. Defaults to
|
|
68
93
|
* "absolute".
|
|
69
94
|
*/
|
|
70
|
-
setOverlayState: (
|
|
95
|
+
setOverlayState: (event: PointerEventCoordinates, { slot }: LayoutPath, className?: string, mode?: OverlayMode) => void;
|
|
71
96
|
/**
|
|
72
97
|
* Clears the overlay state and commits the panel placement.
|
|
73
98
|
*
|
|
74
|
-
* @param
|
|
75
|
-
*
|
|
99
|
+
* @param event - `PointerEvent`, `MouseEvent`, or just X and Y
|
|
100
|
+
* coordinates in screen pixels.
|
|
76
101
|
* @param dragTarget - A `LayoutPath` (presumably from `calculateIntersect`)
|
|
77
102
|
* which points to the drag element in the current layout.
|
|
78
103
|
* @param className - The CSS class name to use for the overlay panel
|
|
@@ -80,7 +105,7 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
80
105
|
* @param mode - Overlay rendering mode that was used, must match the mode
|
|
81
106
|
* passed to `setOverlayState`. Defaults to "absolute".
|
|
82
107
|
*/
|
|
83
|
-
clearOverlayState: (
|
|
108
|
+
clearOverlayState: (event: PointerEventCoordinates | null, { slot, layout }: LayoutPath, className?: string) => void;
|
|
84
109
|
/**
|
|
85
110
|
* Inserts a new panel into the layout at a specified path.
|
|
86
111
|
*
|
|
@@ -155,14 +180,26 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
155
180
|
* Transforms absolute pixel positions into normalized coordinates (0-1 range)
|
|
156
181
|
* relative to the layout's bounding box.
|
|
157
182
|
*
|
|
158
|
-
* @param
|
|
159
|
-
*
|
|
183
|
+
* @param coordinates - `PointerEvent`, `MouseEvent`, or just X and Y
|
|
184
|
+
* coordinates in screen pixels.
|
|
160
185
|
* @returns A tuple containing:
|
|
161
186
|
* - col: Normalized X coordinate (0 = left edge, 1 = right edge)
|
|
162
187
|
* - row: Normalized Y coordinate (0 = top edge, 1 = bottom edge)
|
|
163
188
|
* - box: The layout element's bounding rectangle
|
|
164
189
|
*/
|
|
165
|
-
relativeCoordinates: (
|
|
190
|
+
relativeCoordinates: (event: PointerEventCoordinates, recalculate_bounds?: boolean) => [number, number, DOMRect, CSSStyleDeclaration];
|
|
191
|
+
/**
|
|
192
|
+
* Calculates the Euclidean distance in pixels between the current pointer
|
|
193
|
+
* coordinates and a drag target's position within the layout.
|
|
194
|
+
*
|
|
195
|
+
* @param coordinates - The current pointer event coordinates.
|
|
196
|
+
* @param drag_target - The layout path representing the drag target
|
|
197
|
+
* position.
|
|
198
|
+
* @returns The distance in pixels between the coordinates and the drag
|
|
199
|
+
* target.
|
|
200
|
+
*/
|
|
201
|
+
diffCoordinates: (event: PointerEventCoordinates, drag_target: LayoutPath) => number;
|
|
202
|
+
private onDblClick;
|
|
166
203
|
private onPointerDown;
|
|
167
204
|
private onPointerMove;
|
|
168
205
|
private onPointerUp;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "regular-layout",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "A regular CSS `grid` container",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"browser": "dist/index.js",
|
|
12
12
|
"main": "dist/index.js",
|
|
13
|
+
"types": "dist/index.d.ts",
|
|
13
14
|
"type": "module",
|
|
14
15
|
"files": [
|
|
15
16
|
"dist/**/*",
|
|
@@ -20,7 +21,8 @@
|
|
|
20
21
|
"build": "tsx build.ts",
|
|
21
22
|
"build:watch": "tsx build.ts --watch",
|
|
22
23
|
"clean": "rm -rf dist",
|
|
23
|
-
"test": "playwright test",
|
|
24
|
+
"test": "playwright test tests/integration tests/unit",
|
|
25
|
+
"test:perf": "playwright test --workers=1 ./benchmarks",
|
|
24
26
|
"example": "tsx serve.ts",
|
|
25
27
|
"deploy": "tsx deploy.ts",
|
|
26
28
|
"lint": "biome lint src tests",
|
|
@@ -87,7 +87,9 @@ export function calculate_edge(
|
|
|
87
87
|
(box?.height || 1) *
|
|
88
88
|
(drop_target.view_window.row_end - drop_target.view_window.row_start);
|
|
89
89
|
|
|
90
|
-
const use_column =
|
|
90
|
+
const use_column =
|
|
91
|
+
col_scale / 2 - col_distance * col_scale <
|
|
92
|
+
row_scale / 2 - row_distance * row_scale;
|
|
91
93
|
|
|
92
94
|
return insert_axis(
|
|
93
95
|
panel,
|
|
@@ -80,8 +80,8 @@ function calculate_intersection_recursive(
|
|
|
80
80
|
const row_height = view_window.row_end - view_window.row_start;
|
|
81
81
|
return {
|
|
82
82
|
type: "layout-path",
|
|
83
|
-
layout:
|
|
84
|
-
slot: panel.
|
|
83
|
+
layout: panel,
|
|
84
|
+
slot: panel.tabs[selected],
|
|
85
85
|
path,
|
|
86
86
|
view_window,
|
|
87
87
|
is_edge: false,
|
|
@@ -104,7 +104,7 @@ function build_cells(
|
|
|
104
104
|
const selected = panel.selected ?? 0;
|
|
105
105
|
return [
|
|
106
106
|
{
|
|
107
|
-
child: panel.
|
|
107
|
+
child: panel.tabs[selected],
|
|
108
108
|
colStart: find_track_index(physics, colPositions, colStart),
|
|
109
109
|
colEnd: find_track_index(physics, colPositions, colEnd),
|
|
110
110
|
rowStart: find_track_index(physics, rowPositions, rowStart),
|
|
@@ -182,8 +182,8 @@ const child_template = (
|
|
|
182
182
|
* type: "split-panel",
|
|
183
183
|
* orientation: "horizontal",
|
|
184
184
|
* children: [
|
|
185
|
-
* { type: "child-panel",
|
|
186
|
-
* { type: "child-panel",
|
|
185
|
+
* { type: "child-panel", tabs: "sidebar" },
|
|
186
|
+
* { type: "child-panel", tabs: "main" }
|
|
187
187
|
* ],
|
|
188
188
|
* sizes: [0.25, 0.75]
|
|
189
189
|
* };
|
|
@@ -204,7 +204,7 @@ export function create_css_grid_layout(
|
|
|
204
204
|
const selected = layout.selected ?? 0;
|
|
205
205
|
return [
|
|
206
206
|
host_template("100%", "100%"),
|
|
207
|
-
child_template(physics, layout.
|
|
207
|
+
child_template(physics, layout.tabs[selected], "1", "1"),
|
|
208
208
|
].join("\n");
|
|
209
209
|
}
|
|
210
210
|
|
|
@@ -33,7 +33,7 @@ export function insert_child(
|
|
|
33
33
|
): Layout {
|
|
34
34
|
const createChildPanel = (childId: string): Layout => ({
|
|
35
35
|
type: "child-panel",
|
|
36
|
-
|
|
36
|
+
tabs: [childId],
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
if (path.length === 0) {
|
|
@@ -42,7 +42,7 @@ export function insert_child(
|
|
|
42
42
|
// Add to existing child-panel as a tab
|
|
43
43
|
return {
|
|
44
44
|
type: "child-panel",
|
|
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
|
|
@@ -105,13 +105,13 @@ export function insert_child(
|
|
|
105
105
|
restPath.length === 0 &&
|
|
106
106
|
orientation === undefined &&
|
|
107
107
|
index >= 0 &&
|
|
108
|
-
index <= panel.
|
|
108
|
+
index <= panel.tabs.length
|
|
109
109
|
) {
|
|
110
|
-
const newChild = [...panel.
|
|
110
|
+
const newChild = [...panel.tabs];
|
|
111
111
|
newChild.splice(index, 0, child);
|
|
112
112
|
return {
|
|
113
113
|
...panel,
|
|
114
|
-
|
|
114
|
+
tabs: newChild,
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -32,7 +32,7 @@ import type { Layout } from "./types.ts";
|
|
|
32
32
|
export function redistribute_panel_sizes(
|
|
33
33
|
panel: Layout,
|
|
34
34
|
path: number[],
|
|
35
|
-
delta: number,
|
|
35
|
+
delta: number | undefined,
|
|
36
36
|
physics = DEFAULT_PHYSICS,
|
|
37
37
|
): Layout {
|
|
38
38
|
// Clone the entire panel structure
|
|
@@ -41,7 +41,7 @@ export function redistribute_panel_sizes(
|
|
|
41
41
|
// Find the orientation of the insertion panel,
|
|
42
42
|
// and scale the delta on the respective axis if aligned.
|
|
43
43
|
let current: Layout = result;
|
|
44
|
-
const deltas = { horizontal: delta, vertical: delta };
|
|
44
|
+
const deltas = { horizontal: delta || 0, vertical: delta || 0 };
|
|
45
45
|
for (let i = 0; i < path.length - 1; i++) {
|
|
46
46
|
if (current.type === "split-panel") {
|
|
47
47
|
deltas[current.orientation] /= current.sizes[path[i]];
|
|
@@ -51,17 +51,21 @@ export function redistribute_panel_sizes(
|
|
|
51
51
|
|
|
52
52
|
// Apply the redistribution at the final path index
|
|
53
53
|
if (current.type === "split-panel") {
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
if (delta === undefined) {
|
|
55
|
+
current.sizes = current.sizes.map((_) => 1 / current.sizes.length);
|
|
56
|
+
} else {
|
|
57
|
+
const delta = deltas[current.orientation];
|
|
58
|
+
const index = path[path.length - 1];
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
// It would be fun to remove this condition.
|
|
61
|
+
if (index < current.sizes.length - 1) {
|
|
62
|
+
current.sizes = add_and_redistribute(
|
|
63
|
+
physics,
|
|
64
|
+
current.sizes,
|
|
65
|
+
index,
|
|
66
|
+
delta,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
|
|
@@ -26,14 +26,14 @@ import { EMPTY_PANEL } from "./types.ts";
|
|
|
26
26
|
export function remove_child(panel: Layout, child: string): Layout {
|
|
27
27
|
// If this is a child panel, handle tab removal
|
|
28
28
|
if (panel.type === "child-panel") {
|
|
29
|
-
if (panel.
|
|
30
|
-
const newChild = panel.
|
|
29
|
+
if (panel.tabs.includes(child)) {
|
|
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
35
|
type: "child-panel",
|
|
36
|
-
|
|
36
|
+
tabs: newChild,
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -46,7 +46,7 @@ export function remove_child(panel: Layout, child: string): Layout {
|
|
|
46
46
|
// Try to remove the child from this split panel's children
|
|
47
47
|
const index = result.children.findIndex((p) => {
|
|
48
48
|
if (p.type === "child-panel") {
|
|
49
|
-
return p.
|
|
49
|
+
return p.tabs.includes(child);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
return false;
|
|
@@ -54,7 +54,7 @@ export function remove_child(panel: Layout, child: string): Layout {
|
|
|
54
54
|
|
|
55
55
|
if (index !== -1) {
|
|
56
56
|
const tab_layout = result.children[index] as TabLayout;
|
|
57
|
-
if (tab_layout.
|
|
57
|
+
if (tab_layout.tabs.length === 1) {
|
|
58
58
|
// Found the child at this level - remove it
|
|
59
59
|
const newChildren = result.children.filter((_, i) => i !== index);
|
|
60
60
|
const newSizes = remove_and_redistribute(result.sizes, index);
|
|
@@ -67,10 +67,10 @@ export function remove_child(panel: Layout, child: string): Layout {
|
|
|
67
67
|
result.children = newChildren;
|
|
68
68
|
result.sizes = newSizes;
|
|
69
69
|
} else {
|
|
70
|
-
tab_layout.
|
|
70
|
+
tab_layout.tabs.splice(tab_layout.tabs.indexOf(child), 1);
|
|
71
71
|
if (
|
|
72
72
|
tab_layout.selected &&
|
|
73
|
-
tab_layout.selected >= tab_layout.
|
|
73
|
+
tab_layout.selected >= tab_layout.tabs.length
|
|
74
74
|
) {
|
|
75
75
|
tab_layout.selected--;
|
|
76
76
|
}
|
package/src/layout/types.ts
CHANGED
|
@@ -34,7 +34,6 @@ export interface ViewWindow {
|
|
|
34
34
|
col_end: number;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
38
37
|
/**
|
|
39
38
|
* A split panel that divides space among multiple child layouts
|
|
40
39
|
* .
|
|
@@ -59,7 +58,7 @@ export interface SplitLayout {
|
|
|
59
58
|
*/
|
|
60
59
|
export interface TabLayout {
|
|
61
60
|
type: "child-panel";
|
|
62
|
-
|
|
61
|
+
tabs: string[];
|
|
63
62
|
selected?: number;
|
|
64
63
|
}
|
|
65
64
|
|
|
@@ -76,12 +75,8 @@ export interface LayoutDivider {
|
|
|
76
75
|
|
|
77
76
|
/**
|
|
78
77
|
* Represents a panel location result from hit detection.
|
|
79
|
-
*
|
|
80
|
-
* Contains both the panel identifier and its grid position in relative units.
|
|
81
|
-
* The generic parameter `T` allows DOM-only properties (e.g. `DOMRect`) to be
|
|
82
|
-
* shared in this cross-platform module.
|
|
83
78
|
*/
|
|
84
|
-
export interface LayoutPath
|
|
79
|
+
export interface LayoutPath {
|
|
85
80
|
type: "layout-path";
|
|
86
81
|
slot: string;
|
|
87
82
|
path: number[];
|
|
@@ -92,7 +87,7 @@ export interface LayoutPath<T = undefined> {
|
|
|
92
87
|
row_offset: number;
|
|
93
88
|
orientation: Orientation;
|
|
94
89
|
is_edge: boolean;
|
|
95
|
-
layout:
|
|
90
|
+
layout: Layout;
|
|
96
91
|
}
|
|
97
92
|
|
|
98
93
|
/**
|
|
@@ -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 {
|
|
12
|
+
import type { LayoutPath } from "./layout/types.ts";
|
|
13
13
|
import type { RegularLayoutEvent } from "./extensions.ts";
|
|
14
14
|
import type { RegularLayout } from "./regular-layout.ts";
|
|
15
15
|
import type { RegularLayoutTab } from "./regular-layout-tab.ts";
|
|
@@ -29,6 +29,8 @@ const HTML_TEMPLATE = `
|
|
|
29
29
|
<slot part="container"></slot>
|
|
30
30
|
`;
|
|
31
31
|
|
|
32
|
+
type DragState = { moved?: boolean; path: LayoutPath };
|
|
33
|
+
|
|
32
34
|
/**
|
|
33
35
|
* A custom element that represents a draggable panel within a
|
|
34
36
|
* `<regular-layout>`.
|
|
@@ -55,29 +57,31 @@ const HTML_TEMPLATE = `
|
|
|
55
57
|
* ```
|
|
56
58
|
*/
|
|
57
59
|
export class RegularLayoutFrame extends HTMLElement {
|
|
58
|
-
private _shadowRoot
|
|
59
|
-
private _container_sheet
|
|
60
|
+
private _shadowRoot!: ShadowRoot;
|
|
61
|
+
private _container_sheet!: CSSStyleSheet;
|
|
60
62
|
private _layout!: RegularLayout;
|
|
61
63
|
private _header!: HTMLElement;
|
|
62
|
-
private
|
|
63
|
-
private _drag_moved: boolean = false;
|
|
64
|
+
private _drag: DragState | null = null;
|
|
64
65
|
private _tab_to_index_map: WeakMap<RegularLayoutTab, number> = new WeakMap();
|
|
65
|
-
constructor() {
|
|
66
|
-
super();
|
|
67
|
-
this._container_sheet = new CSSStyleSheet();
|
|
68
|
-
this._container_sheet.replaceSync(CSS);
|
|
69
|
-
this._shadowRoot = this.attachShadow({ mode: "open" });
|
|
70
|
-
this._shadowRoot.adoptedStyleSheets = [this._container_sheet];
|
|
71
|
-
}
|
|
72
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Initializes this elements. Override this method and
|
|
69
|
+
* `disconnectedCallback` to modify how this subclass renders the Shadow
|
|
70
|
+
* DOM and registers events.
|
|
71
|
+
*/
|
|
73
72
|
connectedCallback() {
|
|
73
|
+
this._container_sheet ??= new CSSStyleSheet();
|
|
74
|
+
this._container_sheet.replaceSync(CSS);
|
|
75
|
+
this._shadowRoot ??= this.attachShadow({ mode: "open" });
|
|
76
|
+
this._shadowRoot.adoptedStyleSheets = [this._container_sheet];
|
|
74
77
|
this._shadowRoot.innerHTML = HTML_TEMPLATE;
|
|
75
78
|
this._layout = this.parentElement as RegularLayout;
|
|
76
79
|
this._header = this._shadowRoot.children[0] as HTMLElement;
|
|
77
80
|
this._header.addEventListener("pointerdown", this.onPointerDown);
|
|
78
|
-
this.
|
|
79
|
-
this.
|
|
80
|
-
this.
|
|
81
|
+
this.addEventListener("pointermove", this.onPointerMove);
|
|
82
|
+
this.addEventListener("pointerup", this.onPointerUp);
|
|
83
|
+
this.addEventListener("pointercancel", this.onPointerCancel);
|
|
84
|
+
this.addEventListener("lostpointercapture", this.onPointerLost);
|
|
81
85
|
this._layout.addEventListener("regular-layout-update", this.drawTabs);
|
|
82
86
|
this._layout.addEventListener(
|
|
83
87
|
"regular-layout-before-update",
|
|
@@ -85,11 +89,15 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
85
89
|
);
|
|
86
90
|
}
|
|
87
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Destroys this element.
|
|
94
|
+
*/
|
|
88
95
|
disconnectedCallback() {
|
|
89
96
|
this._header.removeEventListener("pointerdown", this.onPointerDown);
|
|
90
|
-
this.
|
|
91
|
-
this.
|
|
92
|
-
this.
|
|
97
|
+
this.removeEventListener("pointermove", this.onPointerMove);
|
|
98
|
+
this.removeEventListener("pointerup", this.onPointerUp);
|
|
99
|
+
this.removeEventListener("pointercancel", this.onPointerUp);
|
|
100
|
+
this.removeEventListener("lostpointercapture", this.onPointerLost);
|
|
93
101
|
this._layout.removeEventListener("regular-layout-update", this.drawTabs);
|
|
94
102
|
this._layout.removeEventListener(
|
|
95
103
|
"regular-layout-before-update",
|
|
@@ -100,75 +108,52 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
100
108
|
private onPointerDown = (event: PointerEvent): void => {
|
|
101
109
|
const elem = event.target as RegularLayoutTab;
|
|
102
110
|
if (elem.part.contains("tab")) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (this._drag_state) {
|
|
109
|
-
this._header.setPointerCapture(event.pointerId);
|
|
111
|
+
const path = this._layout.calculateIntersect(event);
|
|
112
|
+
if (path) {
|
|
113
|
+
this._drag = { path };
|
|
114
|
+
this.setPointerCapture(event.pointerId);
|
|
110
115
|
event.preventDefault();
|
|
116
|
+
} else {
|
|
117
|
+
this._drag = null;
|
|
111
118
|
}
|
|
112
119
|
}
|
|
113
120
|
};
|
|
114
121
|
|
|
115
122
|
private onPointerMove = (event: PointerEvent): void => {
|
|
116
|
-
if (this.
|
|
123
|
+
if (this._drag) {
|
|
117
124
|
const physics = this._layout.savePhysics();
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const [current_col, current_row, box] =
|
|
122
|
-
this._layout.relativeCoordinates(event.clientX, event.clientY);
|
|
123
|
-
|
|
124
|
-
const dx = (current_col - this._drag_state.column) * box.width;
|
|
125
|
-
const dy = (current_row - this._drag_state.row) * box.height;
|
|
126
|
-
if (Math.sqrt(dx * dx + dy * dy) <= physics.MIN_DRAG_DISTANCE) {
|
|
125
|
+
if (!this._drag.moved) {
|
|
126
|
+
const diff = this._layout.diffCoordinates(event, this._drag.path);
|
|
127
|
+
if (diff <= physics.MIN_DRAG_DISTANCE) {
|
|
127
128
|
return;
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
this.
|
|
132
|
-
this._layout.setOverlayState(
|
|
133
|
-
event.clientX,
|
|
134
|
-
event.clientY,
|
|
135
|
-
this._drag_state,
|
|
136
|
-
physics.OVERLAY_CLASSNAME,
|
|
137
|
-
);
|
|
132
|
+
this._drag.moved = true;
|
|
133
|
+
this._layout.setOverlayState(event, this._drag.path);
|
|
138
134
|
}
|
|
139
135
|
};
|
|
140
136
|
|
|
141
137
|
private onPointerUp = (event: PointerEvent): void => {
|
|
142
|
-
if (this.
|
|
143
|
-
this._layout.clearOverlayState(
|
|
144
|
-
event.clientX,
|
|
145
|
-
event.clientY,
|
|
146
|
-
this._drag_state,
|
|
147
|
-
);
|
|
138
|
+
if (this._drag?.moved) {
|
|
139
|
+
this._layout.clearOverlayState(event, this._drag.path);
|
|
148
140
|
}
|
|
149
|
-
|
|
150
|
-
// TODO This may be handled by `onPointerLost`, not sure if this is
|
|
151
|
-
// browser-specific behavior ...
|
|
152
|
-
this._header.releasePointerCapture(event.pointerId);
|
|
153
|
-
this._drag_state = null;
|
|
154
|
-
this._drag_moved = false;
|
|
155
141
|
};
|
|
156
142
|
|
|
157
|
-
private
|
|
158
|
-
if (this.
|
|
159
|
-
this._layout.clearOverlayState(
|
|
143
|
+
private onPointerCancel = (_: PointerEvent): void => {
|
|
144
|
+
if (this._drag?.moved) {
|
|
145
|
+
this._layout.clearOverlayState(null, this._drag.path);
|
|
160
146
|
}
|
|
147
|
+
};
|
|
161
148
|
|
|
162
|
-
|
|
163
|
-
this.
|
|
164
|
-
this.
|
|
149
|
+
private onPointerLost = (event: PointerEvent): void => {
|
|
150
|
+
this.releasePointerCapture(event.pointerId);
|
|
151
|
+
this._drag = null;
|
|
165
152
|
};
|
|
166
153
|
|
|
167
154
|
private drawTabs = (event: RegularLayoutEvent) => {
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
);
|
|
171
|
-
|
|
155
|
+
const attr = this._layout.savePhysics().CHILD_ATTRIBUTE_NAME;
|
|
156
|
+
const slot = this.getAttribute(attr);
|
|
172
157
|
if (!slot) {
|
|
173
158
|
return;
|
|
174
159
|
}
|
|
@@ -178,12 +163,12 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
178
163
|
if (!new_tab_panel) {
|
|
179
164
|
new_tab_panel = {
|
|
180
165
|
type: "child-panel",
|
|
181
|
-
|
|
166
|
+
tabs: [slot],
|
|
182
167
|
selected: 0,
|
|
183
168
|
};
|
|
184
169
|
}
|
|
185
170
|
|
|
186
|
-
for (let i = 0; i < new_tab_panel.
|
|
171
|
+
for (let i = 0; i < new_tab_panel.tabs.length; i++) {
|
|
187
172
|
if (i >= this._header.children.length) {
|
|
188
173
|
const new_tab = document.createElement("regular-layout-tab");
|
|
189
174
|
new_tab.populate(this._layout, new_tab_panel, i);
|
|
@@ -195,7 +180,7 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
195
180
|
}
|
|
196
181
|
}
|
|
197
182
|
|
|
198
|
-
const last_index = new_tab_panel.
|
|
183
|
+
const last_index = new_tab_panel.tabs.length;
|
|
199
184
|
for (let j = this._header.children.length - 1; j >= last_index; j--) {
|
|
200
185
|
this._header.removeChild(this._header.children[j]);
|
|
201
186
|
}
|