regular-layout 0.0.1
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 +194 -0
- package/README.md +55 -0
- package/dist/common/calculate_intersect.d.ts +18 -0
- package/dist/common/calculate_split.d.ts +2 -0
- package/dist/common/flatten.d.ts +13 -0
- package/dist/common/generate_grid.d.ts +32 -0
- package/dist/common/generate_overlay.d.ts +2 -0
- package/dist/common/insert_child.d.ts +15 -0
- package/dist/common/layout_config.d.ts +92 -0
- package/dist/common/redistribute_panel_sizes.d.ts +19 -0
- package/dist/common/remove_child.d.ts +13 -0
- package/dist/extensions.d.ts +15 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +7 -0
- package/dist/regular-layout-frame.d.ts +38 -0
- package/dist/regular-layout.d.ts +120 -0
- package/package.json +35 -0
- package/src/common/calculate_intersect.ts +176 -0
- package/src/common/calculate_split.ts +53 -0
- package/src/common/flatten.ts +57 -0
- package/src/common/generate_grid.ts +249 -0
- package/src/common/generate_overlay.ts +25 -0
- package/src/common/insert_child.ts +129 -0
- package/src/common/layout_config.ts +127 -0
- package/src/common/redistribute_panel_sizes.ts +100 -0
- package/src/common/remove_child.ts +102 -0
- package/src/extensions.ts +40 -0
- package/src/index.ts +72 -0
- package/src/regular-layout-frame.ts +114 -0
- package/src/regular-layout.ts +334 -0
|
@@ -0,0 +1,100 @@
|
|
|
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 {
|
|
13
|
+
MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD,
|
|
14
|
+
type Layout,
|
|
15
|
+
} from "./layout_config.ts";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Adjusts panel sizes during a drag operation on a divider.
|
|
19
|
+
*
|
|
20
|
+
* The `delta` is distributed proportionally among affected panels, maintaining
|
|
21
|
+
* the sum:
|
|
22
|
+
*
|
|
23
|
+
* - Panels before and including the path index shrink by delta.
|
|
24
|
+
* - Panels after the path index grow by delta.
|
|
25
|
+
*
|
|
26
|
+
* @param panel - The root layout tree to modify.
|
|
27
|
+
* @param path - Path to the divider being dragged (identifies which split panel
|
|
28
|
+
* to resize).
|
|
29
|
+
* @param delta - Amount to resize, as a fraction (0-1). Positive values grow
|
|
30
|
+
* panels before the divider, negative values shrink them.
|
|
31
|
+
* @returns A new layout tree with updated sizes (original is not mutated).
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function redistribute_panel_sizes(
|
|
35
|
+
panel: Layout,
|
|
36
|
+
path: number[],
|
|
37
|
+
delta: number,
|
|
38
|
+
): Layout {
|
|
39
|
+
// Clone the entire panel structure
|
|
40
|
+
const result = structuredClone(panel);
|
|
41
|
+
|
|
42
|
+
// Find the orientation of the insertion panel,
|
|
43
|
+
// and scale the delta on the respective axis if aligned.
|
|
44
|
+
let current: Layout = result;
|
|
45
|
+
const deltas = { horizontal: delta, vertical: delta };
|
|
46
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
47
|
+
if (current.type === "split-panel") {
|
|
48
|
+
deltas[current.orientation] /= current.sizes[path[i]];
|
|
49
|
+
current = current.children[path[i]];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Apply the redistribution at the final path index
|
|
54
|
+
if (current.type === "split-panel") {
|
|
55
|
+
const delta = deltas[current.orientation];
|
|
56
|
+
const index = path[path.length - 1];
|
|
57
|
+
current.sizes = add_and_redistribute(current.sizes, index, delta);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function add_and_redistribute(
|
|
64
|
+
arr: number[],
|
|
65
|
+
index: number,
|
|
66
|
+
delta: number,
|
|
67
|
+
): number[] {
|
|
68
|
+
const result = [...arr];
|
|
69
|
+
let before_total = 0;
|
|
70
|
+
for (let i = 0; i <= index; i++) {
|
|
71
|
+
before_total += arr[i];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let after_total = 0;
|
|
75
|
+
for (let i = index + 1; i < arr.length; i++) {
|
|
76
|
+
after_total += arr[i];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Clamp `delta` to prevent redistributing either side to 0.
|
|
80
|
+
delta =
|
|
81
|
+
Math.sign(delta) *
|
|
82
|
+
Math.min(
|
|
83
|
+
Math.abs(delta),
|
|
84
|
+
(1 - MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD) *
|
|
85
|
+
(delta > 0 ? before_total : after_total),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Redistribute elements
|
|
89
|
+
for (let i = 0; i <= index; i++) {
|
|
90
|
+
const proportion = arr[i] / before_total;
|
|
91
|
+
result[i] = arr[i] - delta * proportion;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (let i = index + 1; i < arr.length; i++) {
|
|
95
|
+
const proportion = arr[i] / after_total;
|
|
96
|
+
result[i] = arr[i] + delta * proportion;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
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 } from "./layout_config.ts";
|
|
13
|
+
import { EMPTY_PANEL } from "./layout_config.ts";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Removes a child panel from the layout tree by its name.
|
|
17
|
+
*
|
|
18
|
+
* Redistributes the removed panel's space proportionally among remaining
|
|
19
|
+
* siblings. Automatically collapses split panels when only one child remains.
|
|
20
|
+
*
|
|
21
|
+
* @param panel - The root layout tree to remove from.
|
|
22
|
+
* @param child - Name of the child panel to remove.
|
|
23
|
+
* @returns A new layout tree with the child removed (original is not mutated).
|
|
24
|
+
* Returns `EMPTY_PANEL` if the last panel is removed.
|
|
25
|
+
*/
|
|
26
|
+
export function remove_child(panel: Layout, child: string): Layout {
|
|
27
|
+
// If this is a child panel and it matches, we can't remove ourselves
|
|
28
|
+
// The caller should handle this case
|
|
29
|
+
if (panel.type === "child-panel") {
|
|
30
|
+
return structuredClone(EMPTY_PANEL);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Clone the panel structure
|
|
34
|
+
const result = structuredClone(panel);
|
|
35
|
+
|
|
36
|
+
// Try to remove the child from this split panel's children
|
|
37
|
+
const index = result.children.findIndex((p) => {
|
|
38
|
+
if (p.type === "child-panel") {
|
|
39
|
+
return p.child === child;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (index !== -1) {
|
|
45
|
+
// Found the child at this level - remove it
|
|
46
|
+
const newChildren = result.children.filter((_, i) => i !== index);
|
|
47
|
+
const newSizes = remove_and_redistribute(result.sizes, index);
|
|
48
|
+
|
|
49
|
+
// If only one child remains, collapse the split panel
|
|
50
|
+
if (newChildren.length === 1) {
|
|
51
|
+
return newChildren[0];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
result.children = newChildren;
|
|
55
|
+
result.sizes = newSizes;
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Child not found at this level - recursively search children
|
|
60
|
+
let modified = false;
|
|
61
|
+
const newChildren = result.children.map((p) => {
|
|
62
|
+
if (p.type === "split-panel") {
|
|
63
|
+
const updated = remove_child(p, child);
|
|
64
|
+
if (updated !== p) {
|
|
65
|
+
modified = true;
|
|
66
|
+
}
|
|
67
|
+
return updated;
|
|
68
|
+
}
|
|
69
|
+
return p;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (modified) {
|
|
73
|
+
result.children = newChildren;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function remove_and_redistribute(arr: number[], index: number): number[] {
|
|
80
|
+
const result = [];
|
|
81
|
+
|
|
82
|
+
// Get the size of the element being removed
|
|
83
|
+
const removed_size = arr[index];
|
|
84
|
+
|
|
85
|
+
// Calculate the total of remaining elements
|
|
86
|
+
let remaining_total = 0;
|
|
87
|
+
for (let i = 0; i < arr.length; i++) {
|
|
88
|
+
if (i !== index) {
|
|
89
|
+
remaining_total += arr[i];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Distribute the removed size proportionally to remaining elements
|
|
94
|
+
for (let i = 0; i < arr.length; i++) {
|
|
95
|
+
if (i !== index) {
|
|
96
|
+
const proportion = arr[i] / remaining_total;
|
|
97
|
+
result.push(arr[i] + removed_size * proportion);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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 { RegularLayout } from "./regular-layout.ts";
|
|
13
|
+
import { RegularLayoutFrame } from "./regular-layout-frame.ts";
|
|
14
|
+
|
|
15
|
+
customElements.define("regular-layout-frame", RegularLayoutFrame);
|
|
16
|
+
|
|
17
|
+
customElements.define("regular-layout", RegularLayout);
|
|
18
|
+
|
|
19
|
+
declare global {
|
|
20
|
+
interface Document {
|
|
21
|
+
createElement(
|
|
22
|
+
tagName: "regular-layout",
|
|
23
|
+
options?: ElementCreationOptions,
|
|
24
|
+
): RegularLayout;
|
|
25
|
+
|
|
26
|
+
createElement(
|
|
27
|
+
tagName: "regular-layout-frame",
|
|
28
|
+
options?: ElementCreationOptions,
|
|
29
|
+
): RegularLayoutFrame;
|
|
30
|
+
|
|
31
|
+
querySelector<E extends Element = Element>(selectors: string): E | null;
|
|
32
|
+
querySelector(selectors: "regular-layout"): RegularLayout | null;
|
|
33
|
+
querySelector(selectors: "regular-layout-frame"): RegularLayoutFrame | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface CustomElementRegistry {
|
|
37
|
+
get(tagName: "regular-layout"): typeof RegularLayout;
|
|
38
|
+
get(tagName: "regular-layout-frame"): typeof RegularLayoutFrame;
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
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 - Resizable Panel Layout System
|
|
14
|
+
*
|
|
15
|
+
* A Web Component library for creating resizable, re-arrangeable layouts using
|
|
16
|
+
* CSS `grid`.
|
|
17
|
+
*
|
|
18
|
+
* ## Basic Usage
|
|
19
|
+
*
|
|
20
|
+
* ```html
|
|
21
|
+
* <regular-layout>
|
|
22
|
+
* <regular-layout-frame slot="sidebar">Sidebar content</regular-layout-frame>
|
|
23
|
+
* <regular-layout-frame slot="main">Main content</regular-layout-frame>
|
|
24
|
+
* </regular-layout>
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { RegularLayout } from 'regular-layout';
|
|
29
|
+
*
|
|
30
|
+
* const layout = document.querySelector('regular-layout');
|
|
31
|
+
*
|
|
32
|
+
* // Insert panels into the grid layout.
|
|
33
|
+
* layout.insertPanel('sidebar', [0]);
|
|
34
|
+
* layout.insertPanel('main', [1]);
|
|
35
|
+
*
|
|
36
|
+
* // Remove a panel (DOM child remains connected, but not slotted).
|
|
37
|
+
* layout.removePanel('sidebar');
|
|
38
|
+
*
|
|
39
|
+
* // Save current layout state.
|
|
40
|
+
* const state = layout.save();
|
|
41
|
+
* localStorage.setItem('layout', JSON.stringify(state));
|
|
42
|
+
*
|
|
43
|
+
* // Restore layout later.
|
|
44
|
+
* const savedState = JSON.parse(localStorage.getItem('layout'));
|
|
45
|
+
* layout.restore(savedState);
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* ## Core Components
|
|
49
|
+
*
|
|
50
|
+
* - {@link RegularLayout}: The main `<regular-layout>` custom element.
|
|
51
|
+
* - {@link RegularLayoutFrame}: Optional frame component with titlebar support.
|
|
52
|
+
*
|
|
53
|
+
* ## Type Definitions
|
|
54
|
+
*
|
|
55
|
+
* - {@link Layout}: The layout tree structure for panel configuration.
|
|
56
|
+
* - {@link LayoutPath}: Information about a panel's position and viewport.
|
|
57
|
+
* - {@link LayoutDivider}: Information about a resizable divider.
|
|
58
|
+
*
|
|
59
|
+
* @packageDocumentation
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
export type {
|
|
63
|
+
LayoutPath,
|
|
64
|
+
Layout,
|
|
65
|
+
LayoutDivider,
|
|
66
|
+
} from "./common/layout_config.ts";
|
|
67
|
+
|
|
68
|
+
export { RegularLayout } from "./regular-layout.ts";
|
|
69
|
+
export { RegularLayoutFrame } from "./regular-layout-frame.ts";
|
|
70
|
+
|
|
71
|
+
// Side effects
|
|
72
|
+
import "./extensions.ts";
|
|
@@ -0,0 +1,114 @@
|
|
|
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 { LayoutPath } from "./common/layout_config.ts";
|
|
13
|
+
import type { RegularLayout } from "./regular-layout.ts";
|
|
14
|
+
|
|
15
|
+
const CSS = `
|
|
16
|
+
:host{--titlebar--height:24px;box-sizing:border-box}
|
|
17
|
+
:host([slot]){margin-top:calc(var(--titlebar--height) + 3px)!important;}
|
|
18
|
+
:host([slot])::part(container){position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;background-color:inherit;border-radius:inherit}
|
|
19
|
+
:host([slot])::part(titlebar){height:var(--titlebar--height);margin-top:calc(-2px - var(--titlebar--height));user-select: none;}
|
|
20
|
+
:host([slot])::part(body){flex:1 1 auto;}
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A custom element that represents a draggable panel within a
|
|
25
|
+
* `<regular-layout>`.
|
|
26
|
+
*
|
|
27
|
+
* `<regular-layout-frame>` is optional - you may also use a `<regular-layout>`
|
|
28
|
+
* with just plain `<div>` children (for example), but panels will not be
|
|
29
|
+
* moveable within the layout unless you manually call `setOverlayState` and
|
|
30
|
+
* `clearOverlayState` (or otherwise impement panel moving via the
|
|
31
|
+
* `<regular-layout>` API).
|
|
32
|
+
*
|
|
33
|
+
* `<regular-layout-frame>` simple and highly customizable implementations
|
|
34
|
+
* based on [CSS `part`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/::part)
|
|
35
|
+
* for custom styling, and symmetric
|
|
36
|
+
* [named `slot`s](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots)
|
|
37
|
+
* for wholesale replacement of the underlying Shadow DOM.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```html
|
|
41
|
+
* <regular-layout>
|
|
42
|
+
* <regular-layout-frame slot="panel-1">
|
|
43
|
+
* <!-- Panel content here -->
|
|
44
|
+
* </regular-layout-frame>
|
|
45
|
+
* </regular-layout>
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export class RegularLayoutFrame extends HTMLElement {
|
|
49
|
+
private _shadowRoot: ShadowRoot;
|
|
50
|
+
private _container_sheet: CSSStyleSheet;
|
|
51
|
+
private _layout!: RegularLayout;
|
|
52
|
+
private _header!: HTMLElement;
|
|
53
|
+
private _drag_state: LayoutPath<DOMRect> | null = null;
|
|
54
|
+
constructor() {
|
|
55
|
+
super();
|
|
56
|
+
this._container_sheet = new CSSStyleSheet();
|
|
57
|
+
this._container_sheet.replaceSync(CSS);
|
|
58
|
+
this._shadowRoot = this.attachShadow({ mode: "open" });
|
|
59
|
+
this._shadowRoot.adoptedStyleSheets = [this._container_sheet];
|
|
60
|
+
this._shadowRoot.innerHTML = `<slot part="container"><slot part="titlebar">header</slot><slot part="body"><slot></slot></slot></slot>`;
|
|
61
|
+
this._layout = this.parentElement as RegularLayout;
|
|
62
|
+
this._header = this._shadowRoot.children[0].children[0] as HTMLElement;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
connectedCallback() {
|
|
66
|
+
this._header.addEventListener("pointerdown", this.onPointerDown);
|
|
67
|
+
this._header.addEventListener("pointermove", this.onPointerMove);
|
|
68
|
+
this._header.addEventListener("pointerup", this.onPointerUp);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
disconnectedCallback() {
|
|
72
|
+
this._header.removeEventListener("pointerdown", this.onPointerDown);
|
|
73
|
+
this._header.removeEventListener("pointermove", this.onPointerMove);
|
|
74
|
+
this._header.removeEventListener("pointerup", this.onPointerUp);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private onPointerDown = (event: PointerEvent): void => {
|
|
78
|
+
this._drag_state = this._layout.calculateIntersect(
|
|
79
|
+
event.clientX,
|
|
80
|
+
event.clientY,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
if (!this._drag_state) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this._header.setPointerCapture(event.pointerId);
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
event.stopImmediatePropagation();
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
private onPointerMove = (event: PointerEvent): void => {
|
|
93
|
+
if (this._drag_state) {
|
|
94
|
+
this._layout.setOverlayState(
|
|
95
|
+
event.clientX,
|
|
96
|
+
event.clientY,
|
|
97
|
+
this._drag_state,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
private onPointerUp = (event: PointerEvent): void => {
|
|
103
|
+
if (this._drag_state) {
|
|
104
|
+
this._layout.clearOverlayState(
|
|
105
|
+
event.clientX,
|
|
106
|
+
event.clientY,
|
|
107
|
+
this._drag_state,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
this._header.releasePointerCapture(event.pointerId);
|
|
111
|
+
this._drag_state = null;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|