regular-layout 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +1 -1
- package/README.md +20 -6
- package/dist/extensions.d.ts +10 -11
- package/dist/index.d.ts +3 -3
- package/dist/index.js +15 -10
- package/dist/index.js.map +4 -4
- package/dist/{common/calculate_split.d.ts → layout/calculate_edge.d.ts} +2 -2
- package/dist/{common → layout}/calculate_intersect.d.ts +7 -9
- package/dist/layout/constants.d.ts +43 -0
- package/dist/{common → layout}/flatten.d.ts +1 -1
- package/dist/{common → layout}/generate_grid.d.ts +3 -3
- package/dist/layout/generate_overlay.d.ts +2 -0
- package/dist/{common → layout}/insert_child.d.ts +3 -2
- package/dist/{common → layout}/redistribute_panel_sizes.d.ts +1 -1
- package/dist/{common → layout}/remove_child.d.ts +1 -1
- package/dist/{common/layout_config.d.ts → layout/types.d.ts} +9 -31
- package/dist/regular-layout-frame.d.ts +2 -2
- package/dist/regular-layout-tab.d.ts +26 -0
- package/dist/regular-layout.d.ts +61 -27
- package/package.json +9 -6
- package/src/extensions.ts +25 -15
- package/src/index.ts +3 -7
- package/src/layout/calculate_edge.ts +209 -0
- package/src/{common → layout}/calculate_intersect.ts +61 -100
- package/src/layout/constants.ts +63 -0
- package/src/{common → layout}/flatten.ts +2 -1
- package/src/{common → layout}/generate_grid.ts +77 -106
- package/src/{common → layout}/generate_overlay.ts +27 -12
- package/src/{common → layout}/insert_child.ts +105 -50
- package/src/{common → layout}/redistribute_panel_sizes.ts +2 -4
- package/src/{common → layout}/remove_child.ts +3 -2
- package/src/{common/layout_config.ts → layout/types.ts} +9 -44
- package/src/regular-layout-frame.ts +83 -68
- package/src/regular-layout-tab.ts +103 -0
- package/src/regular-layout.ts +257 -175
- package/themes/chicago.css +89 -0
- package/themes/fluxbox.css +110 -0
- package/themes/gibson.css +264 -0
- package/themes/hotdog.css +88 -0
- package/themes/lorax.css +129 -0
- package/dist/common/generate_overlay.d.ts +0 -2
- package/src/common/calculate_split.ts +0 -185
|
@@ -9,19 +9,26 @@
|
|
|
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 "./layout/constants.ts";
|
|
13
|
+
import type { Layout, LayoutPath } from "./layout/types.ts";
|
|
13
14
|
import type { RegularLayoutEvent } from "./extensions.ts";
|
|
14
15
|
import type { RegularLayout } from "./regular-layout.ts";
|
|
16
|
+
import type { RegularLayoutTab } from "./regular-layout-tab.ts";
|
|
15
17
|
|
|
16
18
|
const CSS = `
|
|
17
|
-
:host{
|
|
18
|
-
:host(
|
|
19
|
-
:host
|
|
20
|
-
:host
|
|
21
|
-
:host
|
|
19
|
+
:host{box-sizing:border-box;flex-direction:column}
|
|
20
|
+
:host::part(titlebar){display:flex;height:24px;user-select:none;overflow:hidden}
|
|
21
|
+
:host::part(container){flex:1 1 auto}
|
|
22
|
+
:host::part(title){flex:1 1 auto;pointer-events:none}
|
|
23
|
+
:host::part(close){align-self:stretch}
|
|
24
|
+
:host::slotted{flex:1 1 auto;}
|
|
25
|
+
:host regular-layout-tab{width:0px;}
|
|
22
26
|
`;
|
|
23
27
|
|
|
24
|
-
const HTML_TEMPLATE =
|
|
28
|
+
const HTML_TEMPLATE = `
|
|
29
|
+
<div part="titlebar"></div>
|
|
30
|
+
<slot part="container"></slot>
|
|
31
|
+
`;
|
|
25
32
|
|
|
26
33
|
/**
|
|
27
34
|
* A custom element that represents a draggable panel within a
|
|
@@ -42,7 +49,7 @@ const HTML_TEMPLATE = `<slot part="container"><slot part="titlebar"></slot><slot
|
|
|
42
49
|
* @example
|
|
43
50
|
* ```html
|
|
44
51
|
* <regular-layout>
|
|
45
|
-
* <regular-layout-frame
|
|
52
|
+
* <regular-layout-frame name="panel-1">
|
|
46
53
|
* <!-- Panel content here -->
|
|
47
54
|
* </regular-layout-frame>
|
|
48
55
|
* </regular-layout>
|
|
@@ -53,31 +60,30 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
53
60
|
private _container_sheet: CSSStyleSheet;
|
|
54
61
|
private _layout!: RegularLayout;
|
|
55
62
|
private _header!: HTMLElement;
|
|
56
|
-
private _drag_state: LayoutPath<
|
|
63
|
+
private _drag_state: LayoutPath<Layout> | null = null;
|
|
57
64
|
private _drag_moved: boolean = false;
|
|
58
|
-
private _tab_to_index_map: WeakMap<
|
|
65
|
+
private _tab_to_index_map: WeakMap<RegularLayoutTab, number> = new WeakMap();
|
|
59
66
|
constructor() {
|
|
60
67
|
super();
|
|
61
68
|
this._container_sheet = new CSSStyleSheet();
|
|
62
69
|
this._container_sheet.replaceSync(CSS);
|
|
63
70
|
this._shadowRoot = this.attachShadow({ mode: "open" });
|
|
64
71
|
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
72
|
}
|
|
71
73
|
|
|
72
74
|
connectedCallback() {
|
|
73
75
|
this._shadowRoot.innerHTML = HTML_TEMPLATE;
|
|
74
76
|
this._layout = this.parentElement as RegularLayout;
|
|
75
|
-
this._header = this._shadowRoot.children[0]
|
|
77
|
+
this._header = this._shadowRoot.children[0] as HTMLElement;
|
|
76
78
|
this._header.addEventListener("pointerdown", this.onPointerDown);
|
|
77
79
|
this._header.addEventListener("pointermove", this.onPointerMove);
|
|
78
80
|
this._header.addEventListener("pointerup", this.onPointerUp);
|
|
79
81
|
this._header.addEventListener("lostpointercapture", this.onPointerLost);
|
|
80
82
|
this._layout.addEventListener("regular-layout-update", this.drawTabs);
|
|
83
|
+
this._layout.addEventListener(
|
|
84
|
+
"regular-layout-before-update",
|
|
85
|
+
this.drawTabs,
|
|
86
|
+
);
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
disconnectedCallback() {
|
|
@@ -86,73 +92,47 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
86
92
|
this._header.removeEventListener("pointerup", this.onPointerUp);
|
|
87
93
|
this._header.removeEventListener("lostpointercapture", this.onPointerLost);
|
|
88
94
|
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
|
-
}
|
|
95
|
+
this._layout.removeEventListener(
|
|
96
|
+
"regular-layout-before-update",
|
|
97
|
+
this.drawTabs,
|
|
98
|
+
);
|
|
125
99
|
}
|
|
126
100
|
|
|
127
101
|
private onPointerDown = (event: PointerEvent): void => {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
102
|
+
const elem = event.target as RegularLayoutTab;
|
|
103
|
+
if (elem.part.contains("tab")) {
|
|
104
|
+
this._drag_state = this._layout.calculateIntersect(
|
|
105
|
+
event.clientX,
|
|
106
|
+
event.clientY,
|
|
107
|
+
);
|
|
132
108
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const last_index = this._drag_state.path.length - 1;
|
|
137
|
-
const selected = this._tab_to_index_map.get(elem);
|
|
138
|
-
if (selected) {
|
|
139
|
-
this._drag_state.path[last_index] = selected;
|
|
140
|
-
}
|
|
109
|
+
if (this._drag_state) {
|
|
110
|
+
this._header.setPointerCapture(event.pointerId);
|
|
111
|
+
event.preventDefault();
|
|
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,38 @@ 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.getAttribute("name");
|
|
169
|
+
if (!slot) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const new_panel = event.detail;
|
|
174
|
+
let new_tab_panel = this._layout.getPanel(slot, new_panel);
|
|
175
|
+
if (!new_tab_panel) {
|
|
176
|
+
new_tab_panel = {
|
|
177
|
+
type: "child-panel",
|
|
178
|
+
child: [slot],
|
|
179
|
+
selected: 0,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (let i = 0; i < new_tab_panel.child.length; i++) {
|
|
184
|
+
if (i >= this._header.children.length) {
|
|
185
|
+
const new_tab = document.createElement("regular-layout-tab");
|
|
186
|
+
new_tab.populate(this._layout, new_tab_panel, i);
|
|
187
|
+
this._header.appendChild(new_tab);
|
|
188
|
+
this._tab_to_index_map.set(new_tab, i);
|
|
189
|
+
} else {
|
|
190
|
+
const tab = this._header.children[i] as RegularLayoutTab;
|
|
191
|
+
tab.populate(this._layout, new_tab_panel, i);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const last_index = new_tab_panel.child.length;
|
|
196
|
+
for (let j = this._header.children.length - 1; j >= last_index; j--) {
|
|
197
|
+
this._header.removeChild(this._header.children[j]);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
185
200
|
}
|
|
@@ -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
|
+
import type { TabLayout } from "./layout/types.ts";
|
|
13
|
+
import type { RegularLayout } from "./regular-layout.ts";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A custom HTML element representing an individual tab in a tab panel.
|
|
17
|
+
*
|
|
18
|
+
* This element manages the visual representation and interactions for a single tab,
|
|
19
|
+
* including selection state, close functionality, and content display.
|
|
20
|
+
*/
|
|
21
|
+
export class RegularLayoutTab extends HTMLElement {
|
|
22
|
+
private _layout?: RegularLayout;
|
|
23
|
+
private _tab_panel?: TabLayout;
|
|
24
|
+
private _index?: number;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Populates or updates the tab with layout information.
|
|
28
|
+
*
|
|
29
|
+
* This method initializes the tab's content and event listeners on first call,
|
|
30
|
+
* and efficiently updates only the changed properties on subsequent calls.
|
|
31
|
+
*
|
|
32
|
+
* @param layout - The parent RegularLayout instance managing this tab.
|
|
33
|
+
* @param tab_panel - The tab panel layout containing this tab.
|
|
34
|
+
* @param index - The index of this tab within the tab panel.
|
|
35
|
+
*/
|
|
36
|
+
populate = (layout: RegularLayout, tab_panel: TabLayout, index: number) => {
|
|
37
|
+
if (this._tab_panel) {
|
|
38
|
+
const tab_changed =
|
|
39
|
+
(index === tab_panel.selected) !==
|
|
40
|
+
(index === this._tab_panel?.selected);
|
|
41
|
+
|
|
42
|
+
const index_changed =
|
|
43
|
+
tab_changed || this._tab_panel?.child[index] !== tab_panel.child[index];
|
|
44
|
+
|
|
45
|
+
if (index_changed) {
|
|
46
|
+
const selected = tab_panel.selected === index;
|
|
47
|
+
const slot = tab_panel.child[index];
|
|
48
|
+
this.children[0].textContent = slot;
|
|
49
|
+
|
|
50
|
+
if (selected) {
|
|
51
|
+
this.children[1].part.add("active-close");
|
|
52
|
+
this.part.add("active-tab");
|
|
53
|
+
} else {
|
|
54
|
+
this.children[1].part.remove("active-close");
|
|
55
|
+
this.part.remove("active-tab");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
const slot = tab_panel.child[index];
|
|
60
|
+
const selected = tab_panel.selected === index;
|
|
61
|
+
const parts = selected ? "active-close close" : "close";
|
|
62
|
+
this.innerHTML = `<div part="title"></div><button part="${parts}"></button>`;
|
|
63
|
+
if (selected) {
|
|
64
|
+
this.part.add("tab", "active-tab");
|
|
65
|
+
} else {
|
|
66
|
+
this.part.add("tab");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.addEventListener("pointerdown", this.onTabClick);
|
|
70
|
+
this.children[0].textContent = slot;
|
|
71
|
+
this.children[1].addEventListener("pointerdown", this.onTabClose);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this._tab_panel = tab_panel;
|
|
75
|
+
this._layout = layout;
|
|
76
|
+
this._index = index;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
private onTabClose = (_: Event) => {
|
|
80
|
+
if (this._tab_panel !== undefined && this._index !== undefined) {
|
|
81
|
+
this._layout?.removePanel(this._tab_panel.child[this._index]);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
private onTabClick = (_: PointerEvent) => {
|
|
86
|
+
if (
|
|
87
|
+
this._tab_panel !== undefined &&
|
|
88
|
+
this._index !== undefined &&
|
|
89
|
+
this._index !== this._tab_panel.selected
|
|
90
|
+
) {
|
|
91
|
+
const new_layout = this._layout?.save();
|
|
92
|
+
const new_tab_panel = this._layout?.getPanel(
|
|
93
|
+
this._tab_panel.child[this._index],
|
|
94
|
+
new_layout,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (new_tab_panel && new_layout) {
|
|
98
|
+
new_tab_panel.selected = this._index;
|
|
99
|
+
this._layout?.restore(new_layout);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|