regular-layout 0.0.2 → 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 +17 -3
- package/dist/common/{calculate_split.d.ts → calculate_edge.d.ts} +2 -2
- 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/layout_config.d.ts +3 -21
- package/dist/common/redistribute_panel_sizes.d.ts +1 -1
- package/dist/extensions.d.ts +6 -8
- package/dist/index.js +9 -9
- package/dist/index.js.map +4 -4
- package/dist/regular-layout-frame.d.ts +4 -1
- package/dist/regular-layout.d.ts +47 -18
- package/package.json +2 -1
- package/src/common/calculate_edge.ts +104 -0
- package/src/common/calculate_intersect.ts +3 -0
- package/src/common/constants.ts +46 -0
- package/src/common/flatten.ts +1 -0
- package/src/common/generate_grid.ts +2 -1
- package/src/common/generate_overlay.ts +8 -5
- package/src/common/insert_child.ts +1 -0
- package/src/common/layout_config.ts +4 -26
- package/src/common/redistribute_panel_sizes.ts +2 -4
- package/src/common/remove_child.ts +1 -0
- package/src/extensions.ts +16 -12
- package/src/regular-layout-frame.ts +111 -59
- package/src/regular-layout.ts +122 -89
- package/src/common/calculate_split.ts +0 -185
|
@@ -9,16 +9,17 @@
|
|
|
9
9
|
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
|
|
10
10
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
11
11
|
|
|
12
|
-
import
|
|
12
|
+
import { MIN_DRAG_DISTANCE, OVERLAY_CLASSNAME } from "./common/constants.ts";
|
|
13
|
+
import type { LayoutPath, TabLayout } from "./common/layout_config.ts";
|
|
13
14
|
import type { RegularLayoutEvent } from "./extensions.ts";
|
|
14
15
|
import type { RegularLayout } from "./regular-layout.ts";
|
|
15
16
|
|
|
16
|
-
const CSS = `
|
|
17
|
+
const CSS = (className: string) => `
|
|
17
18
|
:host{--titlebar--height:24px;box-sizing:border-box}
|
|
18
|
-
:host(
|
|
19
|
-
:host(
|
|
20
|
-
:host(
|
|
21
|
-
:host(
|
|
19
|
+
:host(:not(.${className})){margin-top:calc(var(--titlebar--height) + 3px)!important;}
|
|
20
|
+
:host(:not(.${className}))::part(container){position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;background-color:inherit;border-radius:inherit}
|
|
21
|
+
:host(:not(.${className}))::part(titlebar){height:var(--titlebar--height);margin-top:calc(0px - var(--titlebar--height));user-select: none;}
|
|
22
|
+
:host(:not(.${className}))::part(body){flex:1 1 auto;}
|
|
22
23
|
`;
|
|
23
24
|
|
|
24
25
|
const HTML_TEMPLATE = `<slot part="container"><slot part="titlebar"></slot><slot part="body"><slot></slot></slot></slot>`;
|
|
@@ -56,17 +57,13 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
56
57
|
private _drag_state: LayoutPath<DOMRect> | null = null;
|
|
57
58
|
private _drag_moved: boolean = false;
|
|
58
59
|
private _tab_to_index_map: WeakMap<HTMLDivElement, number> = new WeakMap();
|
|
60
|
+
private _tab_panel_state: TabLayout | null = null;
|
|
59
61
|
constructor() {
|
|
60
62
|
super();
|
|
61
63
|
this._container_sheet = new CSSStyleSheet();
|
|
62
|
-
this._container_sheet.replaceSync(CSS);
|
|
64
|
+
this._container_sheet.replaceSync(CSS(OVERLAY_CLASSNAME));
|
|
63
65
|
this._shadowRoot = this.attachShadow({ mode: "open" });
|
|
64
66
|
this._shadowRoot.adoptedStyleSheets = [this._container_sheet];
|
|
65
|
-
this.drawTabs = this.drawTabs.bind(this);
|
|
66
|
-
this.onPointerDown = this.onPointerDown.bind(this);
|
|
67
|
-
this.onPointerMove = this.onPointerMove.bind(this);
|
|
68
|
-
this.onPointerUp = this.onPointerUp.bind(this);
|
|
69
|
-
this.onPointerLost = this.onPointerLost.bind(this);
|
|
70
67
|
}
|
|
71
68
|
|
|
72
69
|
connectedCallback() {
|
|
@@ -78,6 +75,10 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
78
75
|
this._header.addEventListener("pointerup", this.onPointerUp);
|
|
79
76
|
this._header.addEventListener("lostpointercapture", this.onPointerLost);
|
|
80
77
|
this._layout.addEventListener("regular-layout-update", this.drawTabs);
|
|
78
|
+
this._layout.addEventListener(
|
|
79
|
+
"regular-layout-before-update",
|
|
80
|
+
this.drawTabs,
|
|
81
|
+
);
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
disconnectedCallback() {
|
|
@@ -86,73 +87,52 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
86
87
|
this._header.removeEventListener("pointerup", this.onPointerUp);
|
|
87
88
|
this._header.removeEventListener("lostpointercapture", this.onPointerLost);
|
|
88
89
|
this._layout.removeEventListener("regular-layout-update", this.drawTabs);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (slot) {
|
|
94
|
-
const result = this._layout.getPanel(slot, event.detail);
|
|
95
|
-
this._header.textContent = "";
|
|
96
|
-
if (!result) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
for (let e = 0; e < (result?.child?.length || 0); e++) {
|
|
101
|
-
const elem = result?.child[e];
|
|
102
|
-
const div = document.createElement("div");
|
|
103
|
-
this._tab_to_index_map.set(div, e);
|
|
104
|
-
// div.dataset.index = `${e}`;
|
|
105
|
-
div.textContent = elem || "";
|
|
106
|
-
div.setAttribute(
|
|
107
|
-
"part",
|
|
108
|
-
e === (result?.selected || 0) ? "tab active-tab" : "tab",
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
const x = e;
|
|
112
|
-
if (e !== (result?.selected || 0)) {
|
|
113
|
-
div.addEventListener("pointerdown", (pointerEvent: PointerEvent) => {
|
|
114
|
-
result.selected = x;
|
|
115
|
-
this._layout.restore(event.detail);
|
|
116
|
-
pointerEvent.preventDefault();
|
|
117
|
-
pointerEvent.stopImmediatePropagation();
|
|
118
|
-
pointerEvent.stopPropagation();
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
this._header.appendChild(div);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
90
|
+
this._layout.removeEventListener(
|
|
91
|
+
"regular-layout-before-update",
|
|
92
|
+
this.drawTabs,
|
|
93
|
+
);
|
|
125
94
|
}
|
|
126
95
|
|
|
127
96
|
private onPointerDown = (event: PointerEvent): void => {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
97
|
+
const elem = event.target as HTMLDivElement;
|
|
98
|
+
if (elem.part.contains("tab")) {
|
|
99
|
+
this._drag_state = this._layout.calculateIntersect(
|
|
100
|
+
event.clientX,
|
|
101
|
+
event.clientY,
|
|
102
|
+
);
|
|
132
103
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
104
|
+
if (this._drag_state) {
|
|
105
|
+
this._header.setPointerCapture(event.pointerId);
|
|
106
|
+
event.preventDefault();
|
|
136
107
|
const last_index = this._drag_state.path.length - 1;
|
|
137
108
|
const selected = this._tab_to_index_map.get(elem);
|
|
138
109
|
if (selected) {
|
|
139
110
|
this._drag_state.path[last_index] = selected;
|
|
140
111
|
}
|
|
141
112
|
}
|
|
142
|
-
|
|
143
|
-
this._header.setPointerCapture(event.pointerId);
|
|
144
|
-
// event.preventDefault();
|
|
145
|
-
// event.stopImmediatePropagation();
|
|
146
113
|
}
|
|
147
114
|
};
|
|
148
115
|
|
|
149
116
|
private onPointerMove = (event: PointerEvent): void => {
|
|
150
117
|
if (this._drag_state) {
|
|
118
|
+
// Only initiate a drag if the cursor has moved sufficiently.
|
|
119
|
+
if (!this._drag_moved) {
|
|
120
|
+
const [current_col, current_row, box] =
|
|
121
|
+
this._layout.relativeCoordinates(event.clientX, event.clientY);
|
|
122
|
+
|
|
123
|
+
const dx = (current_col - this._drag_state.column) * box.width;
|
|
124
|
+
const dy = (current_row - this._drag_state.row) * box.height;
|
|
125
|
+
if (Math.sqrt(dx * dx + dy * dy) <= MIN_DRAG_DISTANCE) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
151
130
|
this._drag_moved = true;
|
|
152
131
|
this._layout.setOverlayState(
|
|
153
132
|
event.clientX,
|
|
154
133
|
event.clientY,
|
|
155
134
|
this._drag_state,
|
|
135
|
+
OVERLAY_CLASSNAME,
|
|
156
136
|
);
|
|
157
137
|
}
|
|
158
138
|
};
|
|
@@ -163,6 +143,7 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
163
143
|
event.clientX,
|
|
164
144
|
event.clientY,
|
|
165
145
|
this._drag_state,
|
|
146
|
+
OVERLAY_CLASSNAME,
|
|
166
147
|
);
|
|
167
148
|
}
|
|
168
149
|
|
|
@@ -182,4 +163,75 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
182
163
|
this._drag_state = null;
|
|
183
164
|
this._drag_moved = false;
|
|
184
165
|
};
|
|
166
|
+
|
|
167
|
+
private drawTabs = (event: RegularLayoutEvent) => {
|
|
168
|
+
const slot = this.assignedSlot;
|
|
169
|
+
if (!slot) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const new_panel = event.detail;
|
|
174
|
+
const new_tab_panel = this._layout.getPanel(slot.name, new_panel);
|
|
175
|
+
if (!new_tab_panel) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
for (let i = 0; i < new_tab_panel.child.length; i++) {
|
|
180
|
+
if (i >= this._header.children.length) {
|
|
181
|
+
const new_tab = this.createTab(new_tab_panel, i);
|
|
182
|
+
this._header.appendChild(new_tab);
|
|
183
|
+
} else {
|
|
184
|
+
const tab_changed =
|
|
185
|
+
(i === new_tab_panel.selected) !==
|
|
186
|
+
(i === this._tab_panel_state?.selected);
|
|
187
|
+
|
|
188
|
+
const tab = this._header.children[i] as HTMLDivElement;
|
|
189
|
+
const index_changed =
|
|
190
|
+
tab_changed ||
|
|
191
|
+
this._tab_panel_state?.child[i] !== new_tab_panel.child[i];
|
|
192
|
+
|
|
193
|
+
if (index_changed) {
|
|
194
|
+
const new_tab = this.createTab(new_tab_panel, i);
|
|
195
|
+
this._header.replaceChild(new_tab, tab);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const last_index = new_tab_panel.child.length;
|
|
201
|
+
for (let j = this._header.children.length - 1; j >= last_index; j--) {
|
|
202
|
+
this._header.removeChild(this._header.children[j]);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this._tab_panel_state = new_tab_panel;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
private createTab = (tab_panel: TabLayout, index: number): HTMLDivElement => {
|
|
209
|
+
const selected = tab_panel.selected || 0;
|
|
210
|
+
const tab = document.createElement("div");
|
|
211
|
+
this._tab_to_index_map.set(tab, index);
|
|
212
|
+
tab.textContent = tab_panel.child[index] || "";
|
|
213
|
+
if (index === selected) {
|
|
214
|
+
tab.setAttribute("part", "tab active-tab");
|
|
215
|
+
} else {
|
|
216
|
+
tab.setAttribute("part", "tab");
|
|
217
|
+
tab.addEventListener("pointerdown", (_) =>
|
|
218
|
+
this.onTabClick(tab_panel, index),
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return tab;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
private onTabClick = (tab_panel: TabLayout, index: number) => {
|
|
226
|
+
const new_layout = this._layout.save();
|
|
227
|
+
const new_tab_panel = this._layout.getPanel(
|
|
228
|
+
tab_panel.child[index],
|
|
229
|
+
new_layout,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
if (new_tab_panel) {
|
|
233
|
+
new_tab_panel.selected = index;
|
|
234
|
+
this._layout.restore(new_layout);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
185
237
|
}
|
package/src/regular-layout.ts
CHANGED
|
@@ -16,25 +16,23 @@
|
|
|
16
16
|
* @packageDocumentation
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
20
|
-
EMPTY_PANEL,
|
|
21
|
-
iter_panel_children,
|
|
22
|
-
OVERLAY_DEFAULT,
|
|
23
|
-
} from "./common/layout_config.ts";
|
|
19
|
+
import { EMPTY_PANEL, iter_panel_children } from "./common/layout_config.ts";
|
|
24
20
|
import { create_css_grid_layout } from "./common/generate_grid.ts";
|
|
25
21
|
import type {
|
|
26
22
|
LayoutPath,
|
|
27
23
|
Layout,
|
|
28
24
|
LayoutDivider,
|
|
29
25
|
TabLayout,
|
|
26
|
+
OverlayMode,
|
|
30
27
|
} from "./common/layout_config.ts";
|
|
31
28
|
import { calculate_intersection } from "./common/calculate_intersect.ts";
|
|
32
29
|
import { remove_child } from "./common/remove_child.ts";
|
|
33
30
|
import { insert_child } from "./common/insert_child.ts";
|
|
34
31
|
import { redistribute_panel_sizes } from "./common/redistribute_panel_sizes.ts";
|
|
35
32
|
import { updateOverlaySheet } from "./common/generate_overlay.ts";
|
|
36
|
-
import {
|
|
33
|
+
import { calculate_edge } from "./common/calculate_edge.ts";
|
|
37
34
|
import { flatten } from "./common/flatten.ts";
|
|
35
|
+
import { OVERLAY_CLASSNAME, OVERLAY_DEFAULT } from "./common/constants.ts";
|
|
38
36
|
|
|
39
37
|
/**
|
|
40
38
|
* A Web Component that provides a resizable panel layout system.
|
|
@@ -86,9 +84,6 @@ export class RegularLayout extends HTMLElement {
|
|
|
86
84
|
this._shadowRoot.adoptedStyleSheets = [this._stylesheet];
|
|
87
85
|
this._shadowRoot.appendChild(this._unslotted_slot);
|
|
88
86
|
this._slots = new Map();
|
|
89
|
-
this.onPointerDown = this.onPointerDown.bind(this);
|
|
90
|
-
this.onPointerMove = this.onPointerMove.bind(this);
|
|
91
|
-
this.onPointerUp = this.onPointerUp.bind(this);
|
|
92
87
|
}
|
|
93
88
|
|
|
94
89
|
connectedCallback() {
|
|
@@ -109,77 +104,85 @@ export class RegularLayout extends HTMLElement {
|
|
|
109
104
|
*
|
|
110
105
|
* @param x - X coordinate in screen pixels.
|
|
111
106
|
* @param y - Y coordinate in screen pixels.
|
|
112
|
-
* @param
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
107
|
+
* @param dragTarget - A `LayoutPath` (presumably from `calculateIntersect`)
|
|
108
|
+
* which points to the drag element in the current layout.
|
|
109
|
+
* @param className - The CSS class name to use for the overlay panel
|
|
110
|
+
* (defaults to "overlay").
|
|
111
|
+
* @param mode - Overlay rendering mode: "grid" uses CSS grid to position
|
|
112
|
+
* the target, "absolute" positions the panel absolutely. Defaults to
|
|
113
|
+
* "absolute".
|
|
116
114
|
*/
|
|
117
|
-
setOverlayState
|
|
115
|
+
setOverlayState = (
|
|
118
116
|
x: number,
|
|
119
117
|
y: number,
|
|
120
|
-
{ slot }: LayoutPath<
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
{ slot }: LayoutPath<unknown>,
|
|
119
|
+
className: string = OVERLAY_CLASSNAME,
|
|
120
|
+
mode: OverlayMode = OVERLAY_DEFAULT,
|
|
121
|
+
) => {
|
|
123
122
|
let panel = this._panel;
|
|
124
123
|
if (mode === "absolute") {
|
|
125
124
|
panel = remove_child(panel, slot);
|
|
126
|
-
this.
|
|
125
|
+
this.updateSlots(panel, slot);
|
|
126
|
+
this._slots.get(slot)?.assignedElements()[0]?.classList.add(className);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
const [col, row, box] = this.relativeCoordinates(x, y);
|
|
130
130
|
let drop_target = calculate_intersection(col, row, panel, false);
|
|
131
131
|
if (drop_target) {
|
|
132
|
-
drop_target =
|
|
133
|
-
if (mode === "
|
|
134
|
-
let new_panel = remove_child(this._panel, slot);
|
|
135
|
-
new_panel = flatten(insert_child(new_panel, slot, drop_target.path));
|
|
136
|
-
const css = create_css_grid_layout(new_panel);
|
|
137
|
-
this._stylesheet.replaceSync(css);
|
|
138
|
-
} else if (mode === "grid") {
|
|
132
|
+
drop_target = calculate_edge(col, row, panel, slot, drop_target);
|
|
133
|
+
if (mode === "grid") {
|
|
139
134
|
const path: [string, string] = [slot, drop_target.slot];
|
|
140
135
|
const css = create_css_grid_layout(this._panel, false, path);
|
|
141
136
|
this._stylesheet.replaceSync(css);
|
|
142
137
|
} else if (mode === "absolute") {
|
|
143
|
-
const
|
|
144
|
-
|
|
138
|
+
const grid_css = create_css_grid_layout(panel);
|
|
139
|
+
const overlay_css = updateOverlaySheet(slot, { ...drop_target, box });
|
|
140
|
+
this._stylesheet.replaceSync([grid_css, overlay_css].join("\n"));
|
|
145
141
|
}
|
|
146
142
|
} else {
|
|
147
143
|
const css = `${create_css_grid_layout(panel)}}`;
|
|
148
144
|
this._stylesheet.replaceSync(css);
|
|
149
145
|
}
|
|
150
146
|
|
|
151
|
-
const event = new CustomEvent("regular-layout-update", {
|
|
147
|
+
const event = new CustomEvent("regular-layout-before-update", {
|
|
148
|
+
detail: panel,
|
|
149
|
+
});
|
|
150
|
+
|
|
152
151
|
this.dispatchEvent(event);
|
|
153
|
-
}
|
|
152
|
+
};
|
|
154
153
|
|
|
155
154
|
/**
|
|
156
155
|
* Clears the overlay state and commits the panel placement.
|
|
157
156
|
*
|
|
158
157
|
* @param x - X coordinate in screen pixels.
|
|
159
158
|
* @param y - Y coordinate in screen pixels.
|
|
160
|
-
* @param
|
|
159
|
+
* @param dragTarget - A `LayoutPath` (presumably from `calculateIntersect`)
|
|
160
|
+
* which points to the drag element in the current layout.
|
|
161
|
+
* @param className - The CSS class name to use for the overlay panel
|
|
162
|
+
* (defaults to "overlay").
|
|
161
163
|
* @param mode - Overlay rendering mode that was used, must match the mode
|
|
162
|
-
*
|
|
164
|
+
* passed to `setOverlayState`. Defaults to "absolute".
|
|
163
165
|
*/
|
|
164
|
-
clearOverlayState
|
|
166
|
+
clearOverlayState = (
|
|
165
167
|
x: number,
|
|
166
168
|
y: number,
|
|
167
|
-
drag_target: LayoutPath<
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
drag_target: LayoutPath<unknown>,
|
|
170
|
+
className: string = OVERLAY_CLASSNAME,
|
|
171
|
+
mode: OverlayMode = OVERLAY_DEFAULT,
|
|
172
|
+
) => {
|
|
170
173
|
let panel = this._panel;
|
|
171
174
|
if (mode === "absolute") {
|
|
172
175
|
panel = remove_child(panel, drag_target.slot);
|
|
173
|
-
this.
|
|
174
|
-
.
|
|
175
|
-
?.
|
|
176
|
+
this._slots
|
|
177
|
+
.get(drag_target.slot)
|
|
178
|
+
?.assignedElements()[0]
|
|
179
|
+
?.classList.remove(className);
|
|
176
180
|
}
|
|
177
181
|
|
|
178
182
|
const [col, row, _] = this.relativeCoordinates(x, y);
|
|
179
183
|
let drop_target = calculate_intersection(col, row, panel, false);
|
|
180
184
|
if (drop_target) {
|
|
181
|
-
|
|
182
|
-
drop_target = calculate_split(
|
|
185
|
+
drop_target = calculate_edge(
|
|
183
186
|
col,
|
|
184
187
|
row,
|
|
185
188
|
panel,
|
|
@@ -189,7 +192,6 @@ export class RegularLayout extends HTMLElement {
|
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
const { path, orientation } = drop_target ? drop_target : drag_target;
|
|
192
|
-
|
|
193
195
|
this.restore(
|
|
194
196
|
insert_child(
|
|
195
197
|
panel,
|
|
@@ -199,7 +201,7 @@ export class RegularLayout extends HTMLElement {
|
|
|
199
201
|
!drop_target?.is_edge,
|
|
200
202
|
),
|
|
201
203
|
);
|
|
202
|
-
}
|
|
204
|
+
};
|
|
203
205
|
|
|
204
206
|
/**
|
|
205
207
|
* Inserts a new panel into the layout at a specified path.
|
|
@@ -207,20 +209,27 @@ export class RegularLayout extends HTMLElement {
|
|
|
207
209
|
* @param name - Unique identifier for the new panel.
|
|
208
210
|
* @param path - Index path defining where to insert.
|
|
209
211
|
*/
|
|
210
|
-
insertPanel(name: string, path: number[] = []) {
|
|
212
|
+
insertPanel = (name: string, path: number[] = []) => {
|
|
211
213
|
this.restore(insert_child(this._panel, name, path));
|
|
212
|
-
}
|
|
214
|
+
};
|
|
213
215
|
|
|
214
216
|
/**
|
|
215
217
|
* Removes a panel from the layout by name.
|
|
216
218
|
*
|
|
217
219
|
* @param name - Name of the panel to remove
|
|
218
220
|
*/
|
|
219
|
-
removePanel(name: string) {
|
|
221
|
+
removePanel = (name: string) => {
|
|
220
222
|
this.restore(remove_child(this._panel, name));
|
|
221
|
-
}
|
|
223
|
+
};
|
|
222
224
|
|
|
223
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Retrieves a panel by name from the layout tree.
|
|
227
|
+
*
|
|
228
|
+
* @param name - Name of the panel to find.
|
|
229
|
+
* @param layout - Optional layout tree to search in (defaults to current layout).
|
|
230
|
+
* @returns The TabLayout containing the panel if found, null otherwise.
|
|
231
|
+
*/
|
|
232
|
+
getPanel = (name: string, layout: Layout = this._panel): TabLayout | null => {
|
|
224
233
|
if (layout.type === "child-panel") {
|
|
225
234
|
if (layout.child.includes(name)) {
|
|
226
235
|
return layout;
|
|
@@ -236,21 +245,20 @@ export class RegularLayout extends HTMLElement {
|
|
|
236
245
|
}
|
|
237
246
|
|
|
238
247
|
return null;
|
|
239
|
-
}
|
|
248
|
+
};
|
|
240
249
|
|
|
241
250
|
/**
|
|
242
251
|
* Determines which panel is at a given screen coordinate.
|
|
243
|
-
* Useful for drag-and-drop operations or custom interactions.
|
|
244
252
|
*
|
|
245
253
|
* @param column - X coordinate in screen pixels.
|
|
246
254
|
* @param row - Y coordinate in screen pixels.
|
|
247
255
|
* @returns Panel information if a panel is at that position, null otherwise.
|
|
248
256
|
*/
|
|
249
|
-
calculateIntersect(
|
|
257
|
+
calculateIntersect = (
|
|
250
258
|
x: number,
|
|
251
259
|
y: number,
|
|
252
260
|
check_dividers: boolean = false,
|
|
253
|
-
): LayoutPath<DOMRect> | null {
|
|
261
|
+
): LayoutPath<DOMRect> | null => {
|
|
254
262
|
const [col, row, box] = this.relativeCoordinates(x, y);
|
|
255
263
|
const panel = calculate_intersection(col, row, this._panel, check_dividers);
|
|
256
264
|
if (panel?.type === "layout-path") {
|
|
@@ -258,11 +266,14 @@ export class RegularLayout extends HTMLElement {
|
|
|
258
266
|
}
|
|
259
267
|
|
|
260
268
|
return null;
|
|
261
|
-
}
|
|
269
|
+
};
|
|
262
270
|
|
|
263
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Clears the entire layout, unslotting all panels.
|
|
273
|
+
*/
|
|
274
|
+
clear = () => {
|
|
264
275
|
this.restore(EMPTY_PANEL);
|
|
265
|
-
}
|
|
276
|
+
};
|
|
266
277
|
|
|
267
278
|
/**
|
|
268
279
|
* Restores the layout from a saved state.
|
|
@@ -276,32 +287,17 @@ export class RegularLayout extends HTMLElement {
|
|
|
276
287
|
* layout.restore(savedState);
|
|
277
288
|
* ```
|
|
278
289
|
*/
|
|
279
|
-
restore(layout: Layout, _is_flattened: boolean = false) {
|
|
290
|
+
restore = (layout: Layout, _is_flattened: boolean = false) => {
|
|
280
291
|
this._panel = !_is_flattened ? flatten(layout) : layout;
|
|
281
|
-
const css = create_css_grid_layout(
|
|
292
|
+
const css = create_css_grid_layout(this._panel);
|
|
282
293
|
this._stylesheet.replaceSync(css);
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const slot = document.createElement("slot");
|
|
288
|
-
slot.setAttribute("name", name);
|
|
289
|
-
this._shadowRoot.appendChild(slot);
|
|
290
|
-
this._slots.set(name, slot);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
for (const key of old) {
|
|
295
|
-
const child = this._slots.get(key);
|
|
296
|
-
if (child) {
|
|
297
|
-
this._shadowRoot.removeChild(child);
|
|
298
|
-
this._slots.delete(key);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
294
|
+
this.updateSlots(this._panel);
|
|
295
|
+
const event = new CustomEvent("regular-layout-update", {
|
|
296
|
+
detail: this._panel,
|
|
297
|
+
});
|
|
301
298
|
|
|
302
|
-
const event = new CustomEvent("regular-layout-update", { detail: layout });
|
|
303
299
|
this.dispatchEvent(event);
|
|
304
|
-
}
|
|
300
|
+
};
|
|
305
301
|
|
|
306
302
|
/**
|
|
307
303
|
* Serializes the current layout state, which can be restored via `restore`.
|
|
@@ -315,34 +311,71 @@ export class RegularLayout extends HTMLElement {
|
|
|
315
311
|
* localStorage.setItem('layout', JSON.stringify(state));
|
|
316
312
|
* ```
|
|
317
313
|
*/
|
|
318
|
-
save(): Layout {
|
|
314
|
+
save = (): Layout => {
|
|
319
315
|
return structuredClone(this._panel);
|
|
320
|
-
}
|
|
316
|
+
};
|
|
321
317
|
|
|
322
|
-
|
|
318
|
+
/**
|
|
319
|
+
* Converts screen coordinates to relative layout coordinates.
|
|
320
|
+
*
|
|
321
|
+
* Transforms absolute pixel positions into normalized coordinates (0-1 range)
|
|
322
|
+
* relative to the layout's bounding box.
|
|
323
|
+
*
|
|
324
|
+
* @param clientX - X coordinate in screen pixels (client space).
|
|
325
|
+
* @param clientY - Y coordinate in screen pixels (client space).
|
|
326
|
+
* @returns A tuple containing:
|
|
327
|
+
* - col: Normalized X coordinate (0 = left edge, 1 = right edge)
|
|
328
|
+
* - row: Normalized Y coordinate (0 = top edge, 1 = bottom edge)
|
|
329
|
+
* - box: The layout element's bounding rectangle
|
|
330
|
+
*/
|
|
331
|
+
relativeCoordinates = (
|
|
323
332
|
clientX: number,
|
|
324
333
|
clientY: number,
|
|
325
|
-
): [number, number, DOMRect] {
|
|
334
|
+
): [number, number, DOMRect] => {
|
|
326
335
|
const box = this.getBoundingClientRect();
|
|
327
336
|
const col = (clientX - box.left) / (box.right - box.left);
|
|
328
337
|
const row = (clientY - box.top) / (box.bottom - box.top);
|
|
329
338
|
return [col, row, box];
|
|
330
|
-
}
|
|
339
|
+
};
|
|
331
340
|
|
|
332
|
-
private
|
|
341
|
+
private updateSlots = (layout: Layout, overlay?: string) => {
|
|
342
|
+
const old = new Set(this._slots.keys());
|
|
343
|
+
if (overlay) {
|
|
344
|
+
old.delete(overlay);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
for (const name of iter_panel_children(layout)) {
|
|
348
|
+
old.delete(name);
|
|
349
|
+
if (!this._slots.has(name)) {
|
|
350
|
+
const slot = document.createElement("slot");
|
|
351
|
+
slot.setAttribute("name", name);
|
|
352
|
+
this._shadowRoot.appendChild(slot);
|
|
353
|
+
this._slots.set(name, slot);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
for (const key of old) {
|
|
358
|
+
const child = this._slots.get(key);
|
|
359
|
+
if (child) {
|
|
360
|
+
this._shadowRoot.removeChild(child);
|
|
361
|
+
this._slots.delete(key);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
private onPointerDown = (event: PointerEvent) => {
|
|
333
367
|
if (event.target === this) {
|
|
334
368
|
const [col, row] = this.relativeCoordinates(event.clientX, event.clientY);
|
|
335
369
|
const hit = calculate_intersection(col, row, this._panel);
|
|
336
370
|
if (hit && hit.type !== "layout-path") {
|
|
337
371
|
this._dragPath = [hit, col, row];
|
|
338
372
|
this.setPointerCapture(event.pointerId);
|
|
339
|
-
|
|
340
|
-
// event.stopImmediatePropagation();
|
|
373
|
+
event.preventDefault();
|
|
341
374
|
}
|
|
342
375
|
}
|
|
343
|
-
}
|
|
376
|
+
};
|
|
344
377
|
|
|
345
|
-
private onPointerMove(event: PointerEvent) {
|
|
378
|
+
private onPointerMove = (event: PointerEvent) => {
|
|
346
379
|
if (this._dragPath) {
|
|
347
380
|
const [col, row] = this.relativeCoordinates(event.clientX, event.clientY);
|
|
348
381
|
const old_panel = this._panel;
|
|
@@ -351,9 +384,9 @@ export class RegularLayout extends HTMLElement {
|
|
|
351
384
|
const panel = redistribute_panel_sizes(old_panel, path, offset);
|
|
352
385
|
this._stylesheet.replaceSync(create_css_grid_layout(panel));
|
|
353
386
|
}
|
|
354
|
-
}
|
|
387
|
+
};
|
|
355
388
|
|
|
356
|
-
private onPointerUp(event: PointerEvent) {
|
|
389
|
+
private onPointerUp = (event: PointerEvent) => {
|
|
357
390
|
if (this._dragPath) {
|
|
358
391
|
this.releasePointerCapture(event.pointerId);
|
|
359
392
|
const [col, row] = this.relativeCoordinates(event.clientX, event.clientY);
|
|
@@ -369,5 +402,5 @@ export class RegularLayout extends HTMLElement {
|
|
|
369
402
|
|
|
370
403
|
this._dragPath = undefined;
|
|
371
404
|
}
|
|
372
|
-
}
|
|
405
|
+
};
|
|
373
406
|
}
|