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
package/src/regular-layout.ts
CHANGED
|
@@ -16,25 +16,28 @@
|
|
|
16
16
|
* @packageDocumentation
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
iter_panel_children,
|
|
22
|
-
OVERLAY_DEFAULT,
|
|
23
|
-
} from "./common/layout_config.ts";
|
|
24
|
-
import { create_css_grid_layout } from "./common/generate_grid.ts";
|
|
19
|
+
import { EMPTY_PANEL } from "./layout/types.ts";
|
|
20
|
+
import { create_css_grid_layout } from "./layout/generate_grid.ts";
|
|
25
21
|
import type {
|
|
26
22
|
LayoutPath,
|
|
27
23
|
Layout,
|
|
28
24
|
LayoutDivider,
|
|
29
25
|
TabLayout,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
26
|
+
OverlayMode,
|
|
27
|
+
Orientation,
|
|
28
|
+
} from "./layout/types.ts";
|
|
29
|
+
import { calculate_intersection } from "./layout/calculate_intersect.ts";
|
|
30
|
+
import { remove_child } from "./layout/remove_child.ts";
|
|
31
|
+
import { insert_child } from "./layout/insert_child.ts";
|
|
32
|
+
import { redistribute_panel_sizes } from "./layout/redistribute_panel_sizes.ts";
|
|
33
|
+
import { updateOverlaySheet } from "./layout/generate_overlay.ts";
|
|
34
|
+
import { calculate_edge } from "./layout/calculate_edge.ts";
|
|
35
|
+
import { flatten } from "./layout/flatten.ts";
|
|
36
|
+
import {
|
|
37
|
+
CUSTOM_EVENT_NAME_PREFIX,
|
|
38
|
+
OVERLAY_CLASSNAME,
|
|
39
|
+
OVERLAY_DEFAULT,
|
|
40
|
+
} from "./layout/constants.ts";
|
|
38
41
|
|
|
39
42
|
/**
|
|
40
43
|
* A Web Component that provides a resizable panel layout system.
|
|
@@ -45,8 +48,8 @@ import { flatten } from "./common/flatten.ts";
|
|
|
45
48
|
* @example
|
|
46
49
|
* ```html
|
|
47
50
|
* <regular-layout>
|
|
48
|
-
* <div
|
|
49
|
-
* <div
|
|
51
|
+
* <div name="sidebar">Sidebar content</div>
|
|
52
|
+
* <div name="main">Main content</div>
|
|
50
53
|
* </regular-layout>
|
|
51
54
|
* ```
|
|
52
55
|
*
|
|
@@ -73,22 +76,34 @@ export class RegularLayout extends HTMLElement {
|
|
|
73
76
|
private _shadowRoot: ShadowRoot;
|
|
74
77
|
private _panel: Layout;
|
|
75
78
|
private _stylesheet: CSSStyleSheet;
|
|
76
|
-
private
|
|
77
|
-
private
|
|
78
|
-
private
|
|
79
|
+
private _cursor_stylesheet: CSSStyleSheet;
|
|
80
|
+
private _drag_target?: [LayoutDivider, number, number];
|
|
81
|
+
private _cursor_override: boolean;
|
|
82
|
+
private _dimensions?: { box: DOMRect; style: CSSStyleDeclaration };
|
|
79
83
|
|
|
80
84
|
constructor() {
|
|
81
85
|
super();
|
|
82
86
|
this._panel = structuredClone(EMPTY_PANEL);
|
|
83
|
-
|
|
84
|
-
this
|
|
87
|
+
|
|
88
|
+
// Why does this implementation use a `<slot>` at all? We must use
|
|
89
|
+
// `<slot>` and the Shadow DOM to scope the grid CSS rules to each
|
|
90
|
+
// instance of `<regular-layout>` (without e.g. giving them unique
|
|
91
|
+
// `"id"` and injecting into `document,head`), and we can only select
|
|
92
|
+
// `::slotted` light DOM children from `adoptedStyleSheets` on the
|
|
93
|
+
// `ShadowRoot`.
|
|
94
|
+
|
|
95
|
+
// In addition, this model uses a single un-named `<slot>` to host all
|
|
96
|
+
// light-DOM children, and the child's `"name"` attribute to identify
|
|
97
|
+
// its position in the `Layout`. Alternatively, using named
|
|
85
98
|
this._shadowRoot = this.attachShadow({ mode: "open" });
|
|
86
|
-
this._shadowRoot.
|
|
87
|
-
this.
|
|
88
|
-
this.
|
|
89
|
-
this.
|
|
90
|
-
this.
|
|
91
|
-
|
|
99
|
+
this._shadowRoot.innerHTML = `<slot></slot>`;
|
|
100
|
+
this._stylesheet = new CSSStyleSheet();
|
|
101
|
+
this._cursor_stylesheet = new CSSStyleSheet();
|
|
102
|
+
this._cursor_override = false;
|
|
103
|
+
this._shadowRoot.adoptedStyleSheets = [
|
|
104
|
+
this._stylesheet,
|
|
105
|
+
this._cursor_stylesheet,
|
|
106
|
+
];
|
|
92
107
|
}
|
|
93
108
|
|
|
94
109
|
connectedCallback() {
|
|
@@ -103,124 +118,173 @@ export class RegularLayout extends HTMLElement {
|
|
|
103
118
|
this.removeEventListener("pointermove", this.onPointerMove);
|
|
104
119
|
}
|
|
105
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Determines which panel is at a given screen coordinate.
|
|
123
|
+
*
|
|
124
|
+
* @param column - X coordinate in screen pixels.
|
|
125
|
+
* @param row - Y coordinate in screen pixels.
|
|
126
|
+
* @returns Panel information if a panel is at that position, null otherwise.
|
|
127
|
+
*/
|
|
128
|
+
calculateIntersect = (
|
|
129
|
+
x: number,
|
|
130
|
+
y: number,
|
|
131
|
+
check_dividers: boolean = false,
|
|
132
|
+
): LayoutPath<Layout> | null => {
|
|
133
|
+
const [col, row, box] = this.relativeCoordinates(x, y, false);
|
|
134
|
+
const panel = calculate_intersection(
|
|
135
|
+
col,
|
|
136
|
+
row,
|
|
137
|
+
this._panel,
|
|
138
|
+
check_dividers ? box : null,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (panel?.type === "layout-path") {
|
|
142
|
+
return { ...panel, layout: this.save() };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return null;
|
|
146
|
+
};
|
|
147
|
+
|
|
106
148
|
/**
|
|
107
149
|
* Sets the visual overlay state during drag-and-drop operations.
|
|
108
150
|
* Displays a preview of where a panel would be placed at the given coordinates.
|
|
109
151
|
*
|
|
110
152
|
* @param x - X coordinate in screen pixels.
|
|
111
153
|
* @param y - Y coordinate in screen pixels.
|
|
112
|
-
* @param
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
154
|
+
* @param dragTarget - A `LayoutPath` (presumably from `calculateIntersect`)
|
|
155
|
+
* which points to the drag element in the current layout.
|
|
156
|
+
* @param className - The CSS class name to use for the overlay panel
|
|
157
|
+
* (defaults to "overlay").
|
|
158
|
+
* @param mode - Overlay rendering mode: "grid" uses CSS grid to position
|
|
159
|
+
* the target, "absolute" positions the panel absolutely. Defaults to
|
|
160
|
+
* "absolute".
|
|
116
161
|
*/
|
|
117
|
-
setOverlayState
|
|
162
|
+
setOverlayState = (
|
|
118
163
|
x: number,
|
|
119
164
|
y: number,
|
|
120
|
-
{ slot }: LayoutPath<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
165
|
+
{ slot }: LayoutPath<unknown>,
|
|
166
|
+
className: string = OVERLAY_CLASSNAME,
|
|
167
|
+
mode: OverlayMode = OVERLAY_DEFAULT,
|
|
168
|
+
) => {
|
|
169
|
+
const panel = remove_child(this._panel, slot);
|
|
170
|
+
Array.from(this.children)
|
|
171
|
+
.find((x) => x.getAttribute("name") === slot)
|
|
172
|
+
?.classList.add(className);
|
|
128
173
|
|
|
129
|
-
const [col, row, box] = this.relativeCoordinates(x, y);
|
|
130
|
-
let drop_target = calculate_intersection(col, row, panel
|
|
174
|
+
const [col, row, box, style] = this.relativeCoordinates(x, y, false);
|
|
175
|
+
let drop_target = calculate_intersection(col, row, panel);
|
|
131
176
|
if (drop_target) {
|
|
132
|
-
drop_target =
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
} else if (mode === "grid") {
|
|
139
|
-
const path: [string, string] = [slot, drop_target.slot];
|
|
140
|
-
const css = create_css_grid_layout(this._panel, false, path);
|
|
141
|
-
this._stylesheet.replaceSync(css);
|
|
142
|
-
} else if (mode === "absolute") {
|
|
143
|
-
const css = `${create_css_grid_layout(panel)}\n${updateOverlaySheet({ ...drop_target, box })}`;
|
|
144
|
-
this._stylesheet.replaceSync(css);
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
const css = `${create_css_grid_layout(panel)}}`;
|
|
177
|
+
drop_target = calculate_edge(col, row, panel, slot, drop_target, box);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (mode === "grid" && drop_target) {
|
|
181
|
+
const path: [string, string] = [slot, drop_target?.slot];
|
|
182
|
+
const css = create_css_grid_layout(panel, false, path);
|
|
148
183
|
this._stylesheet.replaceSync(css);
|
|
184
|
+
} else if (mode === "absolute") {
|
|
185
|
+
const grid_css = create_css_grid_layout(panel);
|
|
186
|
+
const overlay_css = updateOverlaySheet(slot, box, style, drop_target);
|
|
187
|
+
this._stylesheet.replaceSync([grid_css, overlay_css].join("\n"));
|
|
149
188
|
}
|
|
150
189
|
|
|
151
|
-
const
|
|
190
|
+
const event_name = `${CUSTOM_EVENT_NAME_PREFIX}-before-update`;
|
|
191
|
+
const event = new CustomEvent<Layout>(event_name, { detail: panel });
|
|
152
192
|
this.dispatchEvent(event);
|
|
153
|
-
}
|
|
193
|
+
};
|
|
154
194
|
|
|
155
195
|
/**
|
|
156
196
|
* Clears the overlay state and commits the panel placement.
|
|
157
197
|
*
|
|
158
198
|
* @param x - X coordinate in screen pixels.
|
|
159
199
|
* @param y - Y coordinate in screen pixels.
|
|
160
|
-
* @param
|
|
200
|
+
* @param dragTarget - A `LayoutPath` (presumably from `calculateIntersect`)
|
|
201
|
+
* which points to the drag element in the current layout.
|
|
202
|
+
* @param className - The CSS class name to use for the overlay panel
|
|
203
|
+
* (defaults to "overlay").
|
|
161
204
|
* @param mode - Overlay rendering mode that was used, must match the mode
|
|
162
|
-
*
|
|
205
|
+
* passed to `setOverlayState`. Defaults to "absolute".
|
|
163
206
|
*/
|
|
164
|
-
clearOverlayState
|
|
207
|
+
clearOverlayState = (
|
|
165
208
|
x: number,
|
|
166
209
|
y: number,
|
|
167
|
-
drag_target: LayoutPath<
|
|
168
|
-
|
|
169
|
-
) {
|
|
210
|
+
drag_target: LayoutPath<Layout>,
|
|
211
|
+
className: string = OVERLAY_CLASSNAME,
|
|
212
|
+
) => {
|
|
170
213
|
let panel = this._panel;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
?.setAttribute("slot", drag_target.slot);
|
|
176
|
-
}
|
|
214
|
+
panel = remove_child(panel, drag_target.slot);
|
|
215
|
+
Array.from(this.children)
|
|
216
|
+
.find((x) => x.getAttribute("name") === drag_target.slot)
|
|
217
|
+
?.classList.remove(className);
|
|
177
218
|
|
|
178
|
-
const [col, row,
|
|
179
|
-
let drop_target = calculate_intersection(col, row, panel
|
|
219
|
+
const [col, row, box] = this.relativeCoordinates(x, y, false);
|
|
220
|
+
let drop_target = calculate_intersection(col, row, panel);
|
|
180
221
|
if (drop_target) {
|
|
181
|
-
|
|
182
|
-
drop_target = calculate_split(
|
|
222
|
+
drop_target = calculate_edge(
|
|
183
223
|
col,
|
|
184
224
|
row,
|
|
185
225
|
panel,
|
|
186
226
|
drag_target.slot,
|
|
187
227
|
drop_target,
|
|
228
|
+
box,
|
|
188
229
|
);
|
|
189
230
|
}
|
|
190
231
|
|
|
191
232
|
const { path, orientation } = drop_target ? drop_target : drag_target;
|
|
233
|
+
const new_layout = drop_target
|
|
234
|
+
? insert_child(
|
|
235
|
+
panel,
|
|
236
|
+
drag_target.slot,
|
|
237
|
+
path,
|
|
238
|
+
drop_target?.is_edge ? orientation : undefined,
|
|
239
|
+
)
|
|
240
|
+
: drag_target.layout;
|
|
192
241
|
|
|
193
|
-
this.restore(
|
|
194
|
-
|
|
195
|
-
panel,
|
|
196
|
-
drag_target.slot,
|
|
197
|
-
path,
|
|
198
|
-
orientation,
|
|
199
|
-
!drop_target?.is_edge,
|
|
200
|
-
),
|
|
201
|
-
);
|
|
202
|
-
}
|
|
242
|
+
this.restore(new_layout);
|
|
243
|
+
};
|
|
203
244
|
|
|
204
245
|
/**
|
|
205
246
|
* Inserts a new panel into the layout at a specified path.
|
|
206
247
|
*
|
|
207
248
|
* @param name - Unique identifier for the new panel.
|
|
208
249
|
* @param path - Index path defining where to insert.
|
|
250
|
+
* @param split - Force a split in the layout at the end of `path`
|
|
251
|
+
* regardless if there is a leaf at this position or not. Optionally,
|
|
252
|
+
*. `split` may be your preferred `Orientation`, which will be used by
|
|
253
|
+
* the new `SplitPanel` _if_ there is an option of orientation (e.g. if
|
|
254
|
+
* the layout had no pre-existing `SplitPanel`)
|
|
209
255
|
*/
|
|
210
|
-
insertPanel
|
|
211
|
-
|
|
212
|
-
|
|
256
|
+
insertPanel = (
|
|
257
|
+
name: string,
|
|
258
|
+
path: number[] = [],
|
|
259
|
+
split?: boolean | Orientation,
|
|
260
|
+
) => {
|
|
261
|
+
let orientation: Orientation | undefined;
|
|
262
|
+
if (typeof split === "boolean" && split) {
|
|
263
|
+
orientation = "horizontal";
|
|
264
|
+
} else if (typeof split === "string") {
|
|
265
|
+
orientation = split;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
this.restore(insert_child(this._panel, name, path, orientation));
|
|
269
|
+
};
|
|
213
270
|
|
|
214
271
|
/**
|
|
215
272
|
* Removes a panel from the layout by name.
|
|
216
273
|
*
|
|
217
274
|
* @param name - Name of the panel to remove
|
|
218
275
|
*/
|
|
219
|
-
removePanel(name: string) {
|
|
276
|
+
removePanel = (name: string) => {
|
|
220
277
|
this.restore(remove_child(this._panel, name));
|
|
221
|
-
}
|
|
278
|
+
};
|
|
222
279
|
|
|
223
|
-
|
|
280
|
+
/**
|
|
281
|
+
* Retrieves a panel by name from the layout tree.
|
|
282
|
+
*
|
|
283
|
+
* @param name - Name of the panel to find.
|
|
284
|
+
* @param layout - Optional layout tree to search in (defaults to current layout).
|
|
285
|
+
* @returns The TabLayout containing the panel if found, null otherwise.
|
|
286
|
+
*/
|
|
287
|
+
getPanel = (name: string, layout: Layout = this._panel): TabLayout | null => {
|
|
224
288
|
if (layout.type === "child-panel") {
|
|
225
289
|
if (layout.child.includes(name)) {
|
|
226
290
|
return layout;
|
|
@@ -236,33 +300,14 @@ export class RegularLayout extends HTMLElement {
|
|
|
236
300
|
}
|
|
237
301
|
|
|
238
302
|
return null;
|
|
239
|
-
}
|
|
303
|
+
};
|
|
240
304
|
|
|
241
305
|
/**
|
|
242
|
-
*
|
|
243
|
-
* Useful for drag-and-drop operations or custom interactions.
|
|
244
|
-
*
|
|
245
|
-
* @param column - X coordinate in screen pixels.
|
|
246
|
-
* @param row - Y coordinate in screen pixels.
|
|
247
|
-
* @returns Panel information if a panel is at that position, null otherwise.
|
|
306
|
+
* Clears the entire layout, unslotting all panels.
|
|
248
307
|
*/
|
|
249
|
-
|
|
250
|
-
x: number,
|
|
251
|
-
y: number,
|
|
252
|
-
check_dividers: boolean = false,
|
|
253
|
-
): LayoutPath<DOMRect> | null {
|
|
254
|
-
const [col, row, box] = this.relativeCoordinates(x, y);
|
|
255
|
-
const panel = calculate_intersection(col, row, this._panel, check_dividers);
|
|
256
|
-
if (panel?.type === "layout-path") {
|
|
257
|
-
return { ...panel, box };
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return null;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
clear() {
|
|
308
|
+
clear = () => {
|
|
264
309
|
this.restore(EMPTY_PANEL);
|
|
265
|
-
}
|
|
310
|
+
};
|
|
266
311
|
|
|
267
312
|
/**
|
|
268
313
|
* Restores the layout from a saved state.
|
|
@@ -276,32 +321,14 @@ export class RegularLayout extends HTMLElement {
|
|
|
276
321
|
* layout.restore(savedState);
|
|
277
322
|
* ```
|
|
278
323
|
*/
|
|
279
|
-
restore(layout: Layout, _is_flattened: boolean = false) {
|
|
324
|
+
restore = (layout: Layout, _is_flattened: boolean = false) => {
|
|
280
325
|
this._panel = !_is_flattened ? flatten(layout) : layout;
|
|
281
|
-
const css = create_css_grid_layout(
|
|
326
|
+
const css = create_css_grid_layout(this._panel);
|
|
282
327
|
this._stylesheet.replaceSync(css);
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
old.delete(name);
|
|
286
|
-
if (!this._slots.has(name)) {
|
|
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
|
-
}
|
|
301
|
-
|
|
302
|
-
const event = new CustomEvent("regular-layout-update", { detail: layout });
|
|
328
|
+
const event_name = `${CUSTOM_EVENT_NAME_PREFIX}-update`;
|
|
329
|
+
const event = new CustomEvent<Layout>(event_name, { detail: this._panel });
|
|
303
330
|
this.dispatchEvent(event);
|
|
304
|
-
}
|
|
331
|
+
};
|
|
305
332
|
|
|
306
333
|
/**
|
|
307
334
|
* Serializes the current layout state, which can be restored via `restore`.
|
|
@@ -315,59 +342,114 @@ export class RegularLayout extends HTMLElement {
|
|
|
315
342
|
* localStorage.setItem('layout', JSON.stringify(state));
|
|
316
343
|
* ```
|
|
317
344
|
*/
|
|
318
|
-
save(): Layout {
|
|
345
|
+
save = (): Layout => {
|
|
319
346
|
return structuredClone(this._panel);
|
|
320
|
-
}
|
|
347
|
+
};
|
|
321
348
|
|
|
322
|
-
|
|
349
|
+
/**
|
|
350
|
+
* Converts screen coordinates to relative layout coordinates.
|
|
351
|
+
*
|
|
352
|
+
* Transforms absolute pixel positions into normalized coordinates (0-1 range)
|
|
353
|
+
* relative to the layout's bounding box.
|
|
354
|
+
*
|
|
355
|
+
* @param clientX - X coordinate in screen pixels (client space).
|
|
356
|
+
* @param clientY - Y coordinate in screen pixels (client space).
|
|
357
|
+
* @returns A tuple containing:
|
|
358
|
+
* - col: Normalized X coordinate (0 = left edge, 1 = right edge)
|
|
359
|
+
* - row: Normalized Y coordinate (0 = top edge, 1 = bottom edge)
|
|
360
|
+
* - box: The layout element's bounding rectangle
|
|
361
|
+
*/
|
|
362
|
+
relativeCoordinates = (
|
|
323
363
|
clientX: number,
|
|
324
364
|
clientY: number,
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
365
|
+
recalculate_bounds: boolean = true,
|
|
366
|
+
): [number, number, DOMRect, CSSStyleDeclaration] => {
|
|
367
|
+
if (recalculate_bounds || !this._dimensions) {
|
|
368
|
+
this._dimensions = {
|
|
369
|
+
box: this.getBoundingClientRect(),
|
|
370
|
+
style: getComputedStyle(this),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const box = this._dimensions.box;
|
|
375
|
+
const style = this._dimensions.style;
|
|
376
|
+
const col =
|
|
377
|
+
(clientX - box.left - parseFloat(style.paddingLeft)) /
|
|
378
|
+
(box.width -
|
|
379
|
+
parseFloat(style.paddingLeft) -
|
|
380
|
+
parseFloat(style.paddingRight));
|
|
381
|
+
const row =
|
|
382
|
+
(clientY - box.top - parseFloat(style.paddingTop)) /
|
|
383
|
+
(box.height -
|
|
384
|
+
parseFloat(style.paddingTop) -
|
|
385
|
+
parseFloat(style.paddingBottom));
|
|
386
|
+
|
|
387
|
+
return [col, row, box, style];
|
|
388
|
+
};
|
|
331
389
|
|
|
332
|
-
private onPointerDown(event: PointerEvent) {
|
|
390
|
+
private onPointerDown = (event: PointerEvent) => {
|
|
333
391
|
if (event.target === this) {
|
|
334
|
-
const [col, row] = this.relativeCoordinates(
|
|
335
|
-
|
|
392
|
+
const [col, row, box] = this.relativeCoordinates(
|
|
393
|
+
event.clientX,
|
|
394
|
+
event.clientY,
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const hit = calculate_intersection(col, row, this._panel, box);
|
|
336
398
|
if (hit && hit.type !== "layout-path") {
|
|
337
|
-
this.
|
|
399
|
+
this._drag_target = [hit, col, row];
|
|
338
400
|
this.setPointerCapture(event.pointerId);
|
|
339
|
-
|
|
340
|
-
// event.stopImmediatePropagation();
|
|
401
|
+
event.preventDefault();
|
|
341
402
|
}
|
|
342
403
|
}
|
|
343
|
-
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
private onPointerMove = (event: PointerEvent) => {
|
|
407
|
+
if (this._drag_target) {
|
|
408
|
+
const [col, row] = this.relativeCoordinates(
|
|
409
|
+
event.clientX,
|
|
410
|
+
event.clientY,
|
|
411
|
+
false,
|
|
412
|
+
);
|
|
344
413
|
|
|
345
|
-
|
|
346
|
-
if (this._dragPath) {
|
|
347
|
-
const [col, row] = this.relativeCoordinates(event.clientX, event.clientY);
|
|
348
|
-
const old_panel = this._panel;
|
|
349
|
-
const [{ path, type }, old_col, old_row] = this._dragPath;
|
|
414
|
+
const [{ path, type }, old_col, old_row] = this._drag_target;
|
|
350
415
|
const offset = type === "horizontal" ? old_col - col : old_row - row;
|
|
351
|
-
const panel = redistribute_panel_sizes(
|
|
416
|
+
const panel = redistribute_panel_sizes(this._panel, path, offset);
|
|
352
417
|
this._stylesheet.replaceSync(create_css_grid_layout(panel));
|
|
418
|
+
} else if (event.target === this) {
|
|
419
|
+
const [col, row, box] = this.relativeCoordinates(
|
|
420
|
+
event.clientX,
|
|
421
|
+
event.clientY,
|
|
422
|
+
false,
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
const divider = calculate_intersection(col, row, this._panel, box);
|
|
426
|
+
if (divider?.type === "vertical") {
|
|
427
|
+
this._cursor_stylesheet.replaceSync(":host{cursor:row-resize");
|
|
428
|
+
this._cursor_override = true;
|
|
429
|
+
} else if (divider?.type === "horizontal") {
|
|
430
|
+
this._cursor_stylesheet.replaceSync(":host{cursor:col-resize");
|
|
431
|
+
this._cursor_override = true;
|
|
432
|
+
}
|
|
433
|
+
} else if (this._cursor_override) {
|
|
434
|
+
this._cursor_override = false;
|
|
435
|
+
this._cursor_stylesheet.replaceSync("");
|
|
353
436
|
}
|
|
354
|
-
}
|
|
437
|
+
};
|
|
355
438
|
|
|
356
|
-
private onPointerUp(event: PointerEvent) {
|
|
357
|
-
if (this.
|
|
439
|
+
private onPointerUp = (event: PointerEvent) => {
|
|
440
|
+
if (this._drag_target) {
|
|
358
441
|
this.releasePointerCapture(event.pointerId);
|
|
359
|
-
const [col, row] = this.relativeCoordinates(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
this.restore(panel, true);
|
|
365
|
-
} else {
|
|
366
|
-
const panel = redistribute_panel_sizes(old_panel, path, old_row - row);
|
|
367
|
-
this.restore(panel, true);
|
|
368
|
-
}
|
|
442
|
+
const [col, row] = this.relativeCoordinates(
|
|
443
|
+
event.clientX,
|
|
444
|
+
event.clientY,
|
|
445
|
+
false,
|
|
446
|
+
);
|
|
369
447
|
|
|
370
|
-
|
|
448
|
+
const [{ path, type }, old_col, old_row] = this._drag_target;
|
|
449
|
+
const offset = type === "horizontal" ? old_col - col : old_row - row;
|
|
450
|
+
const panel = redistribute_panel_sizes(this._panel, path, offset);
|
|
451
|
+
this.restore(panel, true);
|
|
452
|
+
this._drag_target = undefined;
|
|
371
453
|
}
|
|
372
|
-
}
|
|
454
|
+
};
|
|
373
455
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
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.chicago {
|
|
14
|
+
background-color: #008080;
|
|
15
|
+
font-family: "ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "Consolas", "Liberation Mono", monospace;
|
|
16
|
+
padding: 24px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Frame */
|
|
20
|
+
regular-layout.chicago regular-layout-frame {
|
|
21
|
+
position: relative;
|
|
22
|
+
box-sizing: border-box;
|
|
23
|
+
margin: 12px;
|
|
24
|
+
background: #C0C0C0;
|
|
25
|
+
border-width: 2px;
|
|
26
|
+
border-color: #FFFFFF #808080 #808080 #FFFFFF;
|
|
27
|
+
border-style: solid;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
regular-layout.chicago regular-layout-frame::part(close) {
|
|
31
|
+
border-width: 1px;
|
|
32
|
+
border-color: #FFFFFF #808080 #808080 #FFFFFF;
|
|
33
|
+
border-style: solid;
|
|
34
|
+
height: 16px;
|
|
35
|
+
background: #C0C0C0;
|
|
36
|
+
align-self: center;
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
padding: 0px 4px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
regular-layout.chicago regular-layout-frame::part(close):before {
|
|
43
|
+
content: "X";
|
|
44
|
+
font-size: 10px;
|
|
45
|
+
font-weight: bold;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
regular-layout.chicago regular-layout-frame::part(titlebar) {
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: stretch;
|
|
51
|
+
padding-right: 0px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
regular-layout.chicago regular-layout-frame::part(tab) {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex: 1 1 150px;
|
|
57
|
+
align-items: center;
|
|
58
|
+
padding: 0 3px 0 8px;
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
text-overflow: ellipsis;
|
|
61
|
+
background: #808080;
|
|
62
|
+
color: #FFF;
|
|
63
|
+
font-family: "Tahoma", "Arial", sans-serif;
|
|
64
|
+
font-weight: bold;
|
|
65
|
+
font-size: 11px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
regular-layout.chicago regular-layout-frame::part(active-tab) {
|
|
69
|
+
background: #000080;
|
|
70
|
+
opacity: 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
regular-layout.chicago:has(.overlay)>* {
|
|
74
|
+
opacity: 0.8;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
regular-layout.chicago:has(.overlay)>.overlay {
|
|
78
|
+
opacity: 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Frame in Overlay Mode */
|
|
82
|
+
regular-layout.chicago regular-layout-frame.overlay {
|
|
83
|
+
margin: 0;
|
|
84
|
+
transition:
|
|
85
|
+
top 0.1s ease-in-out,
|
|
86
|
+
height 0.1s ease-in-out,
|
|
87
|
+
width 0.1s ease-in-out,
|
|
88
|
+
left 0.1s ease-in-out;
|
|
89
|
+
}
|