regular-layout 0.2.2 → 0.4.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 +110 -22
- package/dist/{layout → core}/types.d.ts +14 -4
- package/dist/extensions.d.ts +6 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +7 -7
- package/dist/index.js.map +4 -4
- package/dist/layout/calculate_edge.d.ts +4 -4
- package/dist/layout/calculate_intersect.d.ts +1 -1
- package/dist/layout/calculate_path.d.ts +12 -0
- package/dist/layout/calculate_presize_paths.d.ts +10 -0
- package/dist/layout/flatten.d.ts +1 -1
- package/dist/layout/generate_grid.d.ts +5 -5
- package/dist/layout/generate_overlay.d.ts +18 -2
- package/dist/layout/insert_child.d.ts +2 -2
- package/dist/layout/redistribute_panel_sizes.d.ts +2 -2
- package/dist/layout/remove_child.d.ts +1 -1
- package/dist/model/overlay_controller.d.ts +34 -0
- package/dist/model/presize_queue.d.ts +17 -0
- package/dist/regular-layout-tab.d.ts +1 -1
- package/dist/regular-layout.d.ts +44 -9
- package/package.json +5 -4
- package/src/{layout → core}/constants.ts +2 -2
- package/src/{layout → core}/types.ts +17 -5
- package/src/extensions.ts +13 -1
- package/src/index.ts +3 -1
- package/src/layout/calculate_edge.ts +17 -8
- package/src/layout/calculate_intersect.ts +13 -4
- package/src/layout/calculate_path.ts +53 -0
- package/src/layout/calculate_presize_paths.ts +93 -0
- package/src/layout/flatten.ts +4 -4
- package/src/layout/generate_grid.ts +8 -8
- package/src/layout/generate_overlay.ts +48 -16
- package/src/layout/insert_child.ts +16 -16
- package/src/layout/redistribute_panel_sizes.ts +5 -5
- package/src/layout/remove_child.ts +6 -6
- package/src/model/overlay_controller.ts +162 -0
- package/src/model/presize_queue.ts +79 -0
- package/src/regular-layout-frame.ts +3 -3
- package/src/regular-layout-tab.ts +1 -1
- package/src/regular-layout.ts +180 -133
- package/themes/borland.css +103 -0
- package/themes/chicago.css +55 -49
- package/themes/fluxbox.css +64 -60
- package/themes/gibson.css +174 -164
- package/themes/hotdog.css +53 -47
- package/themes/lorax.css +82 -75
- /package/dist/{layout → core}/constants.d.ts +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
2
|
+
// ░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░█░░░█▀█░█░█░█▀█░█░█░▀█▀░▀▄░░░░░░░░
|
|
3
|
+
// ░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░█░░░█▀█░░█░░█░█░█░█░░█░░░▄▀░░░░░░░
|
|
4
|
+
// ░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░░▀░░▀░░░░░░░░░
|
|
5
|
+
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
6
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
7
|
+
// ┃ * Copyright (c) 2026, the Regular Layout Authors. This file is part * ┃
|
|
8
|
+
// ┃ * of the Regular Layout library, distributed under the terms of the * ┃
|
|
9
|
+
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
|
|
10
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
11
|
+
|
|
12
|
+
import type { Layout, PresizeDetail } from "../core/types.ts";
|
|
13
|
+
import { calculate_presize_paths } from "../layout/calculate_presize_paths.ts";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Manages cancelable pre-resize gating for layout updates.
|
|
17
|
+
*
|
|
18
|
+
* Before each layout change, a cancelable `resize-before` event is dispatched
|
|
19
|
+
* on the target. If the event is cancelled via `preventDefault()`, the update
|
|
20
|
+
* is suspended until {@link resume} is called. Concurrent updates are queued
|
|
21
|
+
* and processed sequentially.
|
|
22
|
+
*/
|
|
23
|
+
export class PresizeQueue {
|
|
24
|
+
#resizing = false;
|
|
25
|
+
#queued: { layout: Layout; fn: () => void } | null = null;
|
|
26
|
+
#pending: (() => void) | null = null;
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
private _target: EventTarget,
|
|
30
|
+
private _eventName: string,
|
|
31
|
+
) {}
|
|
32
|
+
|
|
33
|
+
async run(layout: Layout, fn: () => void): Promise<void> {
|
|
34
|
+
if (this.#resizing) {
|
|
35
|
+
this.#queued = { layout, fn };
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.#resizing = true;
|
|
40
|
+
try {
|
|
41
|
+
await this.#dispatchAndMaybeWait(layout, fn);
|
|
42
|
+
while (this.#queued) {
|
|
43
|
+
const { layout: nextLayout, fn: nextFn } = this.#queued;
|
|
44
|
+
this.#queued = null;
|
|
45
|
+
await this.#dispatchAndMaybeWait(nextLayout, nextFn);
|
|
46
|
+
}
|
|
47
|
+
} finally {
|
|
48
|
+
this.#resizing = false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
resume(): void {
|
|
53
|
+
if (this.#pending) {
|
|
54
|
+
const resolve = this.#pending;
|
|
55
|
+
this.#pending = null;
|
|
56
|
+
resolve();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async #dispatchAndMaybeWait(layout: Layout, fn: () => void): Promise<void> {
|
|
61
|
+
const detail: PresizeDetail = {
|
|
62
|
+
calculatePresizePaths: () => calculate_presize_paths(layout),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const event = new CustomEvent(this._eventName, {
|
|
66
|
+
cancelable: true,
|
|
67
|
+
detail,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const proceed = this._target.dispatchEvent(event);
|
|
71
|
+
if (!proceed) {
|
|
72
|
+
await new Promise<void>((resolve) => {
|
|
73
|
+
this.#pending = resolve;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fn();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -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 { LayoutPath } from "./
|
|
12
|
+
import type { LayoutPath } from "./core/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";
|
|
@@ -26,7 +26,7 @@ const CSS = `
|
|
|
26
26
|
|
|
27
27
|
const HTML_TEMPLATE = `
|
|
28
28
|
<div part="titlebar"></div>
|
|
29
|
-
<
|
|
29
|
+
<div part="container"><slot></slot></div>
|
|
30
30
|
`;
|
|
31
31
|
|
|
32
32
|
type DragState = { moved?: boolean; path: LayoutPath };
|
|
@@ -162,7 +162,7 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
162
162
|
let new_tab_panel = this._layout.getPanel(slot, new_panel);
|
|
163
163
|
if (!new_tab_panel) {
|
|
164
164
|
new_tab_panel = {
|
|
165
|
-
type: "
|
|
165
|
+
type: "tab-layout",
|
|
166
166
|
tabs: [slot],
|
|
167
167
|
selected: 0,
|
|
168
168
|
};
|
|
@@ -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 { TabLayout } from "./
|
|
12
|
+
import type { TabLayout } from "./core/types.ts";
|
|
13
13
|
import type { RegularLayout } from "./regular-layout.ts";
|
|
14
14
|
|
|
15
15
|
/**
|
package/src/regular-layout.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* @packageDocumentation
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { EMPTY_PANEL } from "./
|
|
19
|
+
import { EMPTY_PANEL } from "./core/types.ts";
|
|
20
20
|
import { create_css_grid_layout } from "./layout/generate_grid.ts";
|
|
21
21
|
import type {
|
|
22
22
|
LayoutPath,
|
|
@@ -25,19 +25,28 @@ import type {
|
|
|
25
25
|
TabLayout,
|
|
26
26
|
OverlayMode,
|
|
27
27
|
Orientation,
|
|
28
|
-
|
|
28
|
+
LayoutPathTraversal,
|
|
29
|
+
ViewWindow,
|
|
30
|
+
} from "./core/types.ts";
|
|
31
|
+
|
|
29
32
|
import { calculate_intersection } from "./layout/calculate_intersect.ts";
|
|
30
33
|
import { remove_child } from "./layout/remove_child.ts";
|
|
31
34
|
import { insert_child } from "./layout/insert_child.ts";
|
|
32
35
|
import { redistribute_panel_sizes } from "./layout/redistribute_panel_sizes.ts";
|
|
33
|
-
import {
|
|
34
|
-
import { calculate_edge } from "./layout/calculate_edge.ts";
|
|
36
|
+
import { viewWindowToLocalRect } from "./layout/generate_overlay.ts";
|
|
35
37
|
import { flatten } from "./layout/flatten.ts";
|
|
38
|
+
import { calculate_path } from "./layout/calculate_path.ts";
|
|
39
|
+
import { PresizeQueue } from "./model/presize_queue.ts";
|
|
40
|
+
import {
|
|
41
|
+
OverlayController,
|
|
42
|
+
type OverlayHost,
|
|
43
|
+
} from "./model/overlay_controller.ts";
|
|
44
|
+
|
|
36
45
|
import {
|
|
37
46
|
DEFAULT_PHYSICS,
|
|
38
47
|
type PhysicsUpdate,
|
|
39
48
|
type Physics,
|
|
40
|
-
} from "./
|
|
49
|
+
} from "./core/constants.ts";
|
|
41
50
|
|
|
42
51
|
/**
|
|
43
52
|
* An interface which models the fields of `PointerEvent` that
|
|
@@ -107,6 +116,8 @@ export class RegularLayout extends HTMLElement {
|
|
|
107
116
|
private _cursor_override: boolean;
|
|
108
117
|
private _dimensions?: { box: DOMRect; style: CSSStyleDeclaration };
|
|
109
118
|
private _physics: Physics;
|
|
119
|
+
private _presizeQueue: PresizeQueue;
|
|
120
|
+
private _overlayController: OverlayController;
|
|
110
121
|
|
|
111
122
|
constructor() {
|
|
112
123
|
super();
|
|
@@ -117,6 +128,9 @@ export class RegularLayout extends HTMLElement {
|
|
|
117
128
|
this._stylesheet = new CSSStyleSheet();
|
|
118
129
|
this._cursor_stylesheet = new CSSStyleSheet();
|
|
119
130
|
this._cursor_override = false;
|
|
131
|
+
const event_name = `${this._physics.CUSTOM_EVENT_NAME_PREFIX}-before-resize`;
|
|
132
|
+
this._presizeQueue = new PresizeQueue(this, event_name);
|
|
133
|
+
this._overlayController = new OverlayController(this.create_overlay_host());
|
|
120
134
|
this._shadowRoot.adoptedStyleSheets = [
|
|
121
135
|
this._stylesheet,
|
|
122
136
|
this._cursor_stylesheet,
|
|
@@ -151,6 +165,16 @@ export class RegularLayout extends HTMLElement {
|
|
|
151
165
|
return calculate_intersection(col, row, this._panel);
|
|
152
166
|
};
|
|
153
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Calculates the index path for a panel with the given name.
|
|
170
|
+
*
|
|
171
|
+
* @param name - The name of the panel to find.
|
|
172
|
+
* @returns The panel's index path if found, `null` otherwise.
|
|
173
|
+
*/
|
|
174
|
+
calculatePath = (name: string): number[] | null => {
|
|
175
|
+
return calculate_path(name, this._panel);
|
|
176
|
+
};
|
|
177
|
+
|
|
154
178
|
/**
|
|
155
179
|
* Sets the visual overlay state during drag-and-drop operations.
|
|
156
180
|
* Displays a preview of where a panel would be placed at the given coordinates.
|
|
@@ -165,54 +189,13 @@ export class RegularLayout extends HTMLElement {
|
|
|
165
189
|
* the target, "absolute" positions the panel absolutely. Defaults to
|
|
166
190
|
* "absolute".
|
|
167
191
|
*/
|
|
168
|
-
setOverlayState = (
|
|
192
|
+
setOverlayState = async (
|
|
169
193
|
event: PointerEventCoordinates,
|
|
170
|
-
|
|
171
|
-
className
|
|
172
|
-
mode
|
|
194
|
+
target: LayoutPath,
|
|
195
|
+
className?: string,
|
|
196
|
+
mode?: OverlayMode,
|
|
173
197
|
) => {
|
|
174
|
-
|
|
175
|
-
const query = `:scope > [${this._physics.CHILD_ATTRIBUTE_NAME}="${slot}"]`;
|
|
176
|
-
const drag_element = this.querySelector(query);
|
|
177
|
-
if (drag_element) {
|
|
178
|
-
drag_element.classList.add(className);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// TODO: Don't recalculate box (but this currently protects against resize).
|
|
182
|
-
const [col, row, box, style] = this.relativeCoordinates(event, true);
|
|
183
|
-
let drop_target = calculate_intersection(col, row, panel);
|
|
184
|
-
if (drop_target) {
|
|
185
|
-
drop_target = calculate_edge(
|
|
186
|
-
col,
|
|
187
|
-
row,
|
|
188
|
-
panel,
|
|
189
|
-
slot,
|
|
190
|
-
drop_target,
|
|
191
|
-
box,
|
|
192
|
-
this._physics,
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (mode === "grid" && drop_target) {
|
|
197
|
-
const path: [string, string] = [slot, drop_target?.slot];
|
|
198
|
-
const css = create_css_grid_layout(panel, path, this._physics);
|
|
199
|
-
this._stylesheet.replaceSync(css);
|
|
200
|
-
} else if (mode === "absolute") {
|
|
201
|
-
const grid_css = create_css_grid_layout(panel, undefined, this._physics);
|
|
202
|
-
const overlay_css = updateOverlaySheet(
|
|
203
|
-
slot,
|
|
204
|
-
box,
|
|
205
|
-
style,
|
|
206
|
-
drop_target,
|
|
207
|
-
this._physics,
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
this._stylesheet.replaceSync([grid_css, overlay_css].join("\n"));
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const event_name = `${this._physics.CUSTOM_EVENT_NAME_PREFIX}-before-update`;
|
|
214
|
-
const custom_event = new CustomEvent<Layout>(event_name, { detail: panel });
|
|
215
|
-
this.dispatchEvent(custom_event);
|
|
198
|
+
await this._overlayController.set(event, target, className, mode);
|
|
216
199
|
};
|
|
217
200
|
|
|
218
201
|
/**
|
|
@@ -227,54 +210,12 @@ export class RegularLayout extends HTMLElement {
|
|
|
227
210
|
* @param mode - Overlay rendering mode that was used, must match the mode
|
|
228
211
|
* passed to `setOverlayState`. Defaults to "absolute".
|
|
229
212
|
*/
|
|
230
|
-
clearOverlayState = (
|
|
213
|
+
clearOverlayState = async (
|
|
231
214
|
event: PointerEventCoordinates | null,
|
|
232
|
-
|
|
233
|
-
className
|
|
215
|
+
target: LayoutPath,
|
|
216
|
+
className?: string,
|
|
234
217
|
) => {
|
|
235
|
-
|
|
236
|
-
panel = remove_child(panel, slot);
|
|
237
|
-
const query = `:scope > [${this._physics.CHILD_ATTRIBUTE_NAME}="${slot}"]`;
|
|
238
|
-
const drag_element = this.querySelector(query);
|
|
239
|
-
if (drag_element) {
|
|
240
|
-
drag_element.classList.remove(className);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (event === null) {
|
|
244
|
-
this.restore(layout);
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const [col, row, box] = this.relativeCoordinates(event, false);
|
|
249
|
-
let drop_target = calculate_intersection(col, row, panel);
|
|
250
|
-
if (drop_target) {
|
|
251
|
-
drop_target = calculate_edge(
|
|
252
|
-
col,
|
|
253
|
-
row,
|
|
254
|
-
panel,
|
|
255
|
-
slot,
|
|
256
|
-
drop_target,
|
|
257
|
-
box,
|
|
258
|
-
this._physics,
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (drop_target) {
|
|
263
|
-
const orientation = drop_target?.is_edge
|
|
264
|
-
? drop_target.orientation
|
|
265
|
-
: undefined;
|
|
266
|
-
|
|
267
|
-
const new_layout = insert_child(
|
|
268
|
-
panel,
|
|
269
|
-
slot,
|
|
270
|
-
drop_target.path,
|
|
271
|
-
orientation,
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
this.restore(new_layout);
|
|
275
|
-
} else {
|
|
276
|
-
this.restore(layout);
|
|
277
|
-
}
|
|
218
|
+
await this._overlayController.clear(event, target, className);
|
|
278
219
|
};
|
|
279
220
|
|
|
280
221
|
/**
|
|
@@ -288,9 +229,9 @@ export class RegularLayout extends HTMLElement {
|
|
|
288
229
|
* the new `SplitPanel` _if_ there is an option of orientation (e.g. if
|
|
289
230
|
* the layout had no pre-existing `SplitPanel`)
|
|
290
231
|
*/
|
|
291
|
-
insertPanel = (
|
|
232
|
+
insertPanel = async (
|
|
292
233
|
name: string,
|
|
293
|
-
path:
|
|
234
|
+
path: LayoutPathTraversal = [],
|
|
294
235
|
split?: boolean | Orientation,
|
|
295
236
|
) => {
|
|
296
237
|
let orientation: Orientation | undefined;
|
|
@@ -300,7 +241,7 @@ export class RegularLayout extends HTMLElement {
|
|
|
300
241
|
orientation = split;
|
|
301
242
|
}
|
|
302
243
|
|
|
303
|
-
this.restore(insert_child(this._panel, name, path, orientation));
|
|
244
|
+
await this.restore(insert_child(this._panel, name, path, orientation));
|
|
304
245
|
};
|
|
305
246
|
|
|
306
247
|
/**
|
|
@@ -308,8 +249,8 @@ export class RegularLayout extends HTMLElement {
|
|
|
308
249
|
*
|
|
309
250
|
* @param name - Name of the panel to remove
|
|
310
251
|
*/
|
|
311
|
-
removePanel = (name: string) => {
|
|
312
|
-
this.restore(remove_child(this._panel, name));
|
|
252
|
+
removePanel = async (name: string) => {
|
|
253
|
+
await this.restore(remove_child(this._panel, name));
|
|
313
254
|
};
|
|
314
255
|
|
|
315
256
|
/**
|
|
@@ -320,7 +261,7 @@ export class RegularLayout extends HTMLElement {
|
|
|
320
261
|
* @returns The TabLayout containing the panel if found, null otherwise.
|
|
321
262
|
*/
|
|
322
263
|
getPanel = (name: string, layout: Layout = this._panel): TabLayout | null => {
|
|
323
|
-
if (layout.type === "
|
|
264
|
+
if (layout.type === "tab-layout") {
|
|
324
265
|
if (layout.tabs.includes(name)) {
|
|
325
266
|
return layout;
|
|
326
267
|
}
|
|
@@ -341,29 +282,61 @@ export class RegularLayout extends HTMLElement {
|
|
|
341
282
|
/**
|
|
342
283
|
* Clears the entire layout, unslotting all panels.
|
|
343
284
|
*/
|
|
344
|
-
clear = () => {
|
|
345
|
-
this.restore(EMPTY_PANEL);
|
|
285
|
+
clear = async () => {
|
|
286
|
+
await this.restore(EMPTY_PANEL);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Restores the layout from a saved state synchronously, without
|
|
291
|
+
* dispatching the `regular-layout-before-resize` event.
|
|
292
|
+
*
|
|
293
|
+
* @param layout - The layout tree to restore
|
|
294
|
+
*/
|
|
295
|
+
restoreSync = (layout: Layout, _is_flattened: boolean = false) => {
|
|
296
|
+
this._panel = !_is_flattened ? flatten(layout) : layout;
|
|
297
|
+
const css = create_css_grid_layout(this._panel, undefined, this._physics);
|
|
298
|
+
this._stylesheet.replaceSync(css);
|
|
299
|
+
const event_name = `${this._physics.CUSTOM_EVENT_NAME_PREFIX}-update`;
|
|
300
|
+
const event = new CustomEvent<Layout>(event_name, { detail: this._panel });
|
|
301
|
+
this.dispatchEvent(event);
|
|
346
302
|
};
|
|
347
303
|
|
|
348
304
|
/**
|
|
349
305
|
* Restores the layout from a saved state.
|
|
350
306
|
*
|
|
307
|
+
* Before applying, dispatches a cancelable `regular-layout-before-resize`
|
|
308
|
+
* event. If the event is cancelled via `preventDefault()`, the layout
|
|
309
|
+
* update is suspended until {@link resumeResize} is called.
|
|
310
|
+
*
|
|
351
311
|
* @param layout - The layout tree to restore
|
|
352
312
|
*
|
|
353
313
|
* @example
|
|
354
314
|
* ```typescript
|
|
355
315
|
* const layout = document.querySelector('regular-layout');
|
|
356
316
|
* const savedState = JSON.parse(localStorage.getItem('layout'));
|
|
357
|
-
* layout.restore(savedState);
|
|
317
|
+
* await layout.restore(savedState);
|
|
358
318
|
* ```
|
|
359
319
|
*/
|
|
360
|
-
restore = (layout: Layout, _is_flattened: boolean = false) => {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
320
|
+
restore = async (layout: Layout, _is_flattened: boolean = false) => {
|
|
321
|
+
const panel = !_is_flattened ? flatten(layout) : layout;
|
|
322
|
+
await this._presizeQueue.run(panel, () => {
|
|
323
|
+
this._panel = panel;
|
|
324
|
+
const css = create_css_grid_layout(this._panel, undefined, this._physics);
|
|
325
|
+
this._stylesheet.replaceSync(css);
|
|
326
|
+
const event_name = `${this._physics.CUSTOM_EVENT_NAME_PREFIX}-update`;
|
|
327
|
+
const event = new CustomEvent<Layout>(event_name, {
|
|
328
|
+
detail: this._panel,
|
|
329
|
+
});
|
|
330
|
+
this.dispatchEvent(event);
|
|
331
|
+
});
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Resumes a layout update that was suspended by cancelling the
|
|
336
|
+
* `regular-layout-before-resize` event.
|
|
337
|
+
*/
|
|
338
|
+
resumeResize = () => {
|
|
339
|
+
this._presizeQueue.resume();
|
|
367
340
|
};
|
|
368
341
|
|
|
369
342
|
/**
|
|
@@ -429,21 +402,53 @@ export class RegularLayout extends HTMLElement {
|
|
|
429
402
|
|
|
430
403
|
const box = this._dimensions.box;
|
|
431
404
|
const style = this._dimensions.style;
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
405
|
+
const paddingLeft = parseFloat(style.paddingLeft);
|
|
406
|
+
const paddingTop = parseFloat(style.paddingTop);
|
|
407
|
+
const contentWidth =
|
|
408
|
+
box.width - paddingLeft - parseFloat(style.paddingRight);
|
|
409
|
+
|
|
410
|
+
const contentHeight =
|
|
411
|
+
box.height - paddingTop - parseFloat(style.paddingBottom);
|
|
412
|
+
|
|
413
|
+
const localX = event.clientX - box.left - paddingLeft;
|
|
414
|
+
const localY = event.clientY - box.top - paddingTop;
|
|
415
|
+
const col = Math.max(0, Math.min(1, localX / contentWidth));
|
|
416
|
+
const row = Math.max(0, Math.min(1, localY / contentHeight));
|
|
444
417
|
return [col, row, box, style];
|
|
445
418
|
};
|
|
446
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Converts a {@link ViewWindow} (normalized 0–1 coordinates) to a
|
|
422
|
+
* `DOMRect` in screen pixels, accounting for padding and optionally
|
|
423
|
+
* CSS `gap` and child `margin`.
|
|
424
|
+
*
|
|
425
|
+
* @param window - The view window to convert.
|
|
426
|
+
* @returns A `DOMRect` representing the window in screen coordinates.
|
|
427
|
+
*/
|
|
428
|
+
realCoordinates = (window: ViewWindow, child?: HTMLElement): DOMRect => {
|
|
429
|
+
if (!this._dimensions) {
|
|
430
|
+
this._dimensions = {
|
|
431
|
+
box: this.getBoundingClientRect(),
|
|
432
|
+
style: getComputedStyle(this),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const box = this._dimensions.box;
|
|
437
|
+
const style = this._dimensions.style;
|
|
438
|
+
let childStyle: CSSStyleDeclaration | undefined;
|
|
439
|
+
if (child) {
|
|
440
|
+
childStyle = getComputedStyle(child);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const local = viewWindowToLocalRect(window, box, style, childStyle);
|
|
444
|
+
return new DOMRect(
|
|
445
|
+
box.left + local.x,
|
|
446
|
+
box.top + local.y,
|
|
447
|
+
local.width,
|
|
448
|
+
local.height,
|
|
449
|
+
);
|
|
450
|
+
};
|
|
451
|
+
|
|
447
452
|
/**
|
|
448
453
|
* Calculates the Euclidean distance in pixels between the current pointer
|
|
449
454
|
* coordinates and a drag target's position within the layout.
|
|
@@ -464,7 +469,47 @@ export class RegularLayout extends HTMLElement {
|
|
|
464
469
|
return Math.sqrt(dx ** 2 + dy ** 2);
|
|
465
470
|
};
|
|
466
471
|
|
|
467
|
-
|
|
472
|
+
// ▇█████████████████■■■■ȺȺȺȺȺ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ȺȺȺȺȺ■■■■■█████████████████
|
|
473
|
+
// ███████▛▀▀█▃▄▄▅▆▆▆▇▇▇██████████████████████████████▇▇▇▆▆▆▅▅▄▃█▀▀▜███████
|
|
474
|
+
// ████▛▚▅▇███▍ ▗▄▃¯"▜██▘ ▜██▍ ▀█▋ ▐█▀▔▂▄▃ ▔▜█ ▗▃▃▃▄▊ ▄▄▃ ▔▜██▇▅▃▀████
|
|
475
|
+
// ███▋╺██████▍ ▐██▋ █▘ ◨Ƚ "██▍ ▙▁ ▀ ▐▋ █▛▀▀▀▜█ ´▂▂Ŋ█▊ °"▔ ▃██████▍▐███
|
|
476
|
+
// ████▄▝▜████▍ ▝▀▀ ▂▟▘ ▄▄▄▄ "█▍ ██▄ ▐█▃ ▝▀▀ ▐█ ▝▀▀▀▀▊ ██▖ ▝████▛▀▄████
|
|
477
|
+
// ██████▇▆▄▃█▀▀Ⱥ▼■███▇▇████▇▇█▇▇████▇████▇▇▇████▇▇▇▇▇▇▇█▇■◘▀▀▀▀▂▃▄▅▇██████
|
|
478
|
+
// ███████████████▇▇▆▆▆▅▅▅▅▅▅▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▅▅▅▅▅▅▆▆▆▇▇███████████████
|
|
479
|
+
//
|
|
480
|
+
//
|
|
481
|
+
// ┏▅▅▖ ▗▅▅▶ ▅▅▅▅▅▅▅ ▗▅▅▅▅▅▅▖ ▅▅▅▅▅▄▄▂ ▃▅▆▆▆▄▁ ┏▅▅▖ ▅▅▖▅▅▅▅▅▅▅▅▅
|
|
482
|
+
// ┣██▌▗██▛ ██▊▔▔▔▔ ▐██▋▔▔▔` ███▍"▜██▙ ▟██▘▔███▖ ▐██▌ ██▌^▔▔███^▔^
|
|
483
|
+
// ┣██▙██▋ ██▊▂▂▂ ▐██▋▂▂▂ ███▍ ▐██▋ ▐███ ▐██▋ ▐██▌ ██▌ ███▎
|
|
484
|
+
// ┣██████▌ ███▀▀▀ ▐███▀▀▘ ██████▛▀ ▐███ ▐██▋ ▐██▌ ██▌ ███▎
|
|
485
|
+
// ┣██▛ ▜██▖ ██▊ ▐██▋ ███▍ ▝███ ▟██▌ ▐██▌ ██▌ ███▎
|
|
486
|
+
// ┣██▌ ███ ███▆▆▆▆▎▐███▆▆▆▅ ███▍ ▀██▙▅██▛ ▝███▅▆██▘ ███▎
|
|
487
|
+
// ▔▔▔` ´▔▔` ▔▔▔▔▔▔▔ ´▔▔▔▔▔▔▔ ▔▔▔ ▔""▔¯ ▔""^▔ ▔▔▔
|
|
488
|
+
|
|
489
|
+
private create_overlay_host(): OverlayHost {
|
|
490
|
+
const self = this;
|
|
491
|
+
return {
|
|
492
|
+
get panel() {
|
|
493
|
+
return self._panel;
|
|
494
|
+
},
|
|
495
|
+
get physics() {
|
|
496
|
+
return self._physics;
|
|
497
|
+
},
|
|
498
|
+
get stylesheet() {
|
|
499
|
+
return self._stylesheet;
|
|
500
|
+
},
|
|
501
|
+
get presizeQueue() {
|
|
502
|
+
return self._presizeQueue;
|
|
503
|
+
},
|
|
504
|
+
relativeCoordinates: (event, recalculate) =>
|
|
505
|
+
self.relativeCoordinates(event, recalculate),
|
|
506
|
+
restore: (layout, isFlattened) => self.restore(layout, isFlattened),
|
|
507
|
+
querySelector: (selectors) => self.querySelector(selectors),
|
|
508
|
+
dispatchEvent: (event) => self.dispatchEvent(event),
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
private onDblClick = async (event: MouseEvent) => {
|
|
468
513
|
const [col, row, rect] = this.relativeCoordinates(event, false);
|
|
469
514
|
const divider = calculate_intersection(col, row, this._panel, {
|
|
470
515
|
rect,
|
|
@@ -478,7 +523,7 @@ export class RegularLayout extends HTMLElement {
|
|
|
478
523
|
undefined,
|
|
479
524
|
);
|
|
480
525
|
|
|
481
|
-
this.restore(panel, true);
|
|
526
|
+
await this.restore(panel, true);
|
|
482
527
|
}
|
|
483
528
|
};
|
|
484
529
|
|
|
@@ -495,15 +540,17 @@ export class RegularLayout extends HTMLElement {
|
|
|
495
540
|
}
|
|
496
541
|
};
|
|
497
542
|
|
|
498
|
-
private onPointerMove = (event: PointerEvent) => {
|
|
543
|
+
private onPointerMove = async (event: PointerEvent) => {
|
|
499
544
|
if (this._drag_target) {
|
|
500
545
|
const [col, row] = this.relativeCoordinates(event, false);
|
|
501
546
|
const [{ path, type }, old_col, old_row] = this._drag_target;
|
|
502
547
|
const offset = type === "horizontal" ? old_col - col : old_row - row;
|
|
503
548
|
const panel = redistribute_panel_sizes(this._panel, path, offset);
|
|
504
|
-
this.
|
|
505
|
-
|
|
506
|
-
|
|
549
|
+
await this._presizeQueue.run(panel, () => {
|
|
550
|
+
this._stylesheet.replaceSync(
|
|
551
|
+
create_css_grid_layout(panel, undefined, this._physics),
|
|
552
|
+
);
|
|
553
|
+
});
|
|
507
554
|
}
|
|
508
555
|
|
|
509
556
|
if (this._physics.GRID_DIVIDER_CHECK_TARGET && event.target !== this) {
|
|
@@ -533,14 +580,14 @@ export class RegularLayout extends HTMLElement {
|
|
|
533
580
|
}
|
|
534
581
|
};
|
|
535
582
|
|
|
536
|
-
private onPointerUp = (event: PointerEvent) => {
|
|
583
|
+
private onPointerUp = async (event: PointerEvent) => {
|
|
537
584
|
if (this._drag_target) {
|
|
538
585
|
this.releasePointerCapture(event.pointerId);
|
|
539
586
|
const [col, row] = this.relativeCoordinates(event, false);
|
|
540
587
|
const [{ path, type }, old_col, old_row] = this._drag_target;
|
|
541
588
|
const offset = type === "horizontal" ? old_col - col : old_row - row;
|
|
542
589
|
const panel = redistribute_panel_sizes(this._panel, path, offset);
|
|
543
|
-
this.restore(panel, true);
|
|
590
|
+
await this.restore(panel, true);
|
|
544
591
|
this._drag_target = undefined;
|
|
545
592
|
}
|
|
546
593
|
};
|
|
@@ -0,0 +1,103 @@
|
|
|
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
|
+
|
|
13
|
+
regular-layout.borland {
|
|
14
|
+
background-color: #0000aa;
|
|
15
|
+
font-family:
|
|
16
|
+
"ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "Consolas",
|
|
17
|
+
"Liberation Mono", monospace;
|
|
18
|
+
padding: 16px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Frame */
|
|
22
|
+
regular-layout.borland regular-layout-frame {
|
|
23
|
+
position: relative;
|
|
24
|
+
box-sizing: border-box;
|
|
25
|
+
margin: 4px;
|
|
26
|
+
background: #0000aa;
|
|
27
|
+
border: 1px solid #00aaaa;
|
|
28
|
+
box-shadow: 1px 1px 0 #000000;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
regular-layout.borland regular-layout-frame::part(container) {
|
|
32
|
+
padding: 4px 6px;
|
|
33
|
+
color: #ffff55;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
regular-layout.borland regular-layout-frame::part(close) {
|
|
37
|
+
background: #00aa00;
|
|
38
|
+
height: 16px;
|
|
39
|
+
align-self: center;
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
justify-content: center;
|
|
43
|
+
padding: 0 3px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
regular-layout.borland regular-layout-frame::part(close):hover {
|
|
47
|
+
background: #00ff00;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
regular-layout.borland regular-layout-frame::part(close):before {
|
|
51
|
+
content: "[×]";
|
|
52
|
+
font-size: 10px;
|
|
53
|
+
font-weight: bold;
|
|
54
|
+
color: #000000;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
regular-layout.borland regular-layout-frame::part(titlebar) {
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: stretch;
|
|
60
|
+
padding-right: 0;
|
|
61
|
+
height: 22px;
|
|
62
|
+
background: #0000aa;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
regular-layout.borland regular-layout-frame::part(tab) {
|
|
66
|
+
display: flex;
|
|
67
|
+
flex: 1 1 150px;
|
|
68
|
+
align-items: center;
|
|
69
|
+
padding: 0 3px 0 8px;
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
text-overflow: ellipsis;
|
|
72
|
+
background: #0000aa;
|
|
73
|
+
color: #aaaaaa;
|
|
74
|
+
font-size: 11px;
|
|
75
|
+
font-weight: bold;
|
|
76
|
+
border-right: 1px solid #00aaaa;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
regular-layout.borland regular-layout-frame::part(active-tab) {
|
|
80
|
+
background: #00aaaa;
|
|
81
|
+
color: #000000;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
regular-layout.borland:has(.overlay) > * {
|
|
85
|
+
opacity: 0.6;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
regular-layout.borland:has(.overlay) > .overlay {
|
|
89
|
+
opacity: 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Frame in Overlay Mode */
|
|
93
|
+
regular-layout.borland regular-layout-frame.overlay {
|
|
94
|
+
background: rgba(0, 170, 170, 0.9);
|
|
95
|
+
border: 1px solid #ffff55;
|
|
96
|
+
box-shadow: none;
|
|
97
|
+
margin: 0;
|
|
98
|
+
transition:
|
|
99
|
+
top 0.05s linear,
|
|
100
|
+
height 0.05s linear,
|
|
101
|
+
width 0.05s linear,
|
|
102
|
+
left 0.05s linear;
|
|
103
|
+
}
|