regular-layout 0.3.0 → 0.5.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 +6 -0
- package/dist/extensions.d.ts +13 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +10 -7
- package/dist/index.js.map +4 -4
- package/dist/layout/calculate_edge.d.ts +2 -2
- package/dist/layout/calculate_intersect.d.ts +1 -1
- package/dist/layout/calculate_path.d.ts +1 -1
- 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 +11 -2
- package/dist/layout/generate_overlay.d.ts +18 -2
- package/dist/layout/insert_child.d.ts +1 -1
- 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-frame.d.ts +21 -0
- package/dist/regular-layout-tab.d.ts +1 -1
- package/dist/regular-layout.d.ts +76 -9
- package/package.json +5 -4
- package/src/{layout → core}/constants.ts +2 -2
- package/src/{layout → core}/types.ts +7 -0
- package/src/extensions.ts +25 -1
- package/src/index.ts +3 -1
- package/src/layout/calculate_edge.ts +2 -2
- package/src/layout/calculate_intersect.ts +5 -2
- package/src/layout/calculate_path.ts +1 -1
- package/src/layout/calculate_presize_paths.ts +93 -0
- package/src/layout/flatten.ts +1 -1
- package/src/layout/generate_grid.ts +20 -2
- package/src/layout/generate_overlay.ts +48 -16
- package/src/layout/insert_child.ts +1 -1
- package/src/layout/redistribute_panel_sizes.ts +2 -2
- package/src/layout/remove_child.ts +2 -2
- package/src/model/overlay_controller.ts +161 -0
- package/src/model/presize_queue.ts +79 -0
- package/src/regular-layout-frame.ts +58 -3
- package/src/regular-layout-tab.ts +20 -22
- package/src/regular-layout.ts +252 -132
- 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
package/src/regular-layout.ts
CHANGED
|
@@ -16,8 +16,11 @@
|
|
|
16
16
|
* @packageDocumentation
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { EMPTY_PANEL } from "./
|
|
20
|
-
import {
|
|
19
|
+
import { EMPTY_PANEL } from "./core/types.ts";
|
|
20
|
+
import {
|
|
21
|
+
create_css_grid_layout,
|
|
22
|
+
create_css_maximize_layout,
|
|
23
|
+
} from "./layout/generate_grid.ts";
|
|
21
24
|
import type {
|
|
22
25
|
LayoutPath,
|
|
23
26
|
Layout,
|
|
@@ -26,20 +29,27 @@ import type {
|
|
|
26
29
|
OverlayMode,
|
|
27
30
|
Orientation,
|
|
28
31
|
LayoutPathTraversal,
|
|
29
|
-
|
|
32
|
+
ViewWindow,
|
|
33
|
+
} from "./core/types.ts";
|
|
34
|
+
|
|
30
35
|
import { calculate_intersection } from "./layout/calculate_intersect.ts";
|
|
31
36
|
import { remove_child } from "./layout/remove_child.ts";
|
|
32
37
|
import { insert_child } from "./layout/insert_child.ts";
|
|
33
38
|
import { redistribute_panel_sizes } from "./layout/redistribute_panel_sizes.ts";
|
|
34
|
-
import {
|
|
35
|
-
import { calculate_edge } from "./layout/calculate_edge.ts";
|
|
39
|
+
import { viewWindowToLocalRect } from "./layout/generate_overlay.ts";
|
|
36
40
|
import { flatten } from "./layout/flatten.ts";
|
|
37
41
|
import { calculate_path } from "./layout/calculate_path.ts";
|
|
42
|
+
import { PresizeQueue } from "./model/presize_queue.ts";
|
|
43
|
+
import {
|
|
44
|
+
OverlayController,
|
|
45
|
+
type OverlayHost,
|
|
46
|
+
} from "./model/overlay_controller.ts";
|
|
47
|
+
|
|
38
48
|
import {
|
|
39
49
|
DEFAULT_PHYSICS,
|
|
40
50
|
type PhysicsUpdate,
|
|
41
51
|
type Physics,
|
|
42
|
-
} from "./
|
|
52
|
+
} from "./core/constants.ts";
|
|
43
53
|
|
|
44
54
|
/**
|
|
45
55
|
* An interface which models the fields of `PointerEvent` that
|
|
@@ -99,6 +109,14 @@ export interface PointerEventCoordinates {
|
|
|
99
109
|
* latter implementation would require synchronizing the light DOM
|
|
100
110
|
* and shadow DOM slots/slotted children continuously.
|
|
101
111
|
*
|
|
112
|
+
* Note that `::slotted()` only matches *directly* slotted nodes - it
|
|
113
|
+
* does not pierce a slotted `<slot>`. A consumer that wants to forward
|
|
114
|
+
* its own light-DOM content through `<regular-layout>` therefore cannot
|
|
115
|
+
* place a bare `<slot name="X">` as a layout child and expect it to be
|
|
116
|
+
* grid-positioned; instead, wrap the forwarding slot in a concrete
|
|
117
|
+
* `<regular-layout-frame name="X">` (which *is* directly slotted and so
|
|
118
|
+
* matched by the generated `::slotted([name="X"])` rules).
|
|
119
|
+
*
|
|
102
120
|
*/
|
|
103
121
|
export class RegularLayout extends HTMLElement {
|
|
104
122
|
private _shadowRoot: ShadowRoot;
|
|
@@ -109,6 +127,9 @@ export class RegularLayout extends HTMLElement {
|
|
|
109
127
|
private _cursor_override: boolean;
|
|
110
128
|
private _dimensions?: { box: DOMRect; style: CSSStyleDeclaration };
|
|
111
129
|
private _physics: Physics;
|
|
130
|
+
private _presizeQueue: PresizeQueue;
|
|
131
|
+
private _overlayController: OverlayController;
|
|
132
|
+
private _maximized?: string;
|
|
112
133
|
|
|
113
134
|
constructor() {
|
|
114
135
|
super();
|
|
@@ -119,6 +140,9 @@ export class RegularLayout extends HTMLElement {
|
|
|
119
140
|
this._stylesheet = new CSSStyleSheet();
|
|
120
141
|
this._cursor_stylesheet = new CSSStyleSheet();
|
|
121
142
|
this._cursor_override = false;
|
|
143
|
+
const event_name = `${this._physics.CUSTOM_EVENT_NAME_PREFIX}-before-resize`;
|
|
144
|
+
this._presizeQueue = new PresizeQueue(this, event_name);
|
|
145
|
+
this._overlayController = new OverlayController(this.create_overlay_host());
|
|
122
146
|
this._shadowRoot.adoptedStyleSheets = [
|
|
123
147
|
this._stylesheet,
|
|
124
148
|
this._cursor_stylesheet,
|
|
@@ -177,54 +201,13 @@ export class RegularLayout extends HTMLElement {
|
|
|
177
201
|
* the target, "absolute" positions the panel absolutely. Defaults to
|
|
178
202
|
* "absolute".
|
|
179
203
|
*/
|
|
180
|
-
setOverlayState = (
|
|
204
|
+
setOverlayState = async (
|
|
181
205
|
event: PointerEventCoordinates,
|
|
182
|
-
|
|
183
|
-
className
|
|
184
|
-
mode
|
|
206
|
+
target: LayoutPath,
|
|
207
|
+
className?: string,
|
|
208
|
+
mode?: OverlayMode,
|
|
185
209
|
) => {
|
|
186
|
-
|
|
187
|
-
const query = `:scope > [${this._physics.CHILD_ATTRIBUTE_NAME}="${slot}"]`;
|
|
188
|
-
const drag_element = this.querySelector(query);
|
|
189
|
-
if (drag_element) {
|
|
190
|
-
drag_element.classList.add(className);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// TODO: Don't recalculate box (but this currently protects against resize).
|
|
194
|
-
const [col, row, box, style] = this.relativeCoordinates(event, true);
|
|
195
|
-
let drop_target = calculate_intersection(col, row, panel);
|
|
196
|
-
if (drop_target) {
|
|
197
|
-
drop_target = calculate_edge(
|
|
198
|
-
col,
|
|
199
|
-
row,
|
|
200
|
-
panel,
|
|
201
|
-
slot,
|
|
202
|
-
drop_target,
|
|
203
|
-
box,
|
|
204
|
-
this._physics,
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (mode === "grid" && drop_target) {
|
|
209
|
-
const path: [string, string] = [slot, drop_target?.slot];
|
|
210
|
-
const css = create_css_grid_layout(panel, path, this._physics);
|
|
211
|
-
this._stylesheet.replaceSync(css);
|
|
212
|
-
} else if (mode === "absolute") {
|
|
213
|
-
const grid_css = create_css_grid_layout(panel, undefined, this._physics);
|
|
214
|
-
const overlay_css = updateOverlaySheet(
|
|
215
|
-
slot,
|
|
216
|
-
box,
|
|
217
|
-
style,
|
|
218
|
-
drop_target,
|
|
219
|
-
this._physics,
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
this._stylesheet.replaceSync([grid_css, overlay_css].join("\n"));
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const event_name = `${this._physics.CUSTOM_EVENT_NAME_PREFIX}-before-update`;
|
|
226
|
-
const custom_event = new CustomEvent<Layout>(event_name, { detail: panel });
|
|
227
|
-
this.dispatchEvent(custom_event);
|
|
210
|
+
await this._overlayController.set(event, target, className, mode);
|
|
228
211
|
};
|
|
229
212
|
|
|
230
213
|
/**
|
|
@@ -239,54 +222,12 @@ export class RegularLayout extends HTMLElement {
|
|
|
239
222
|
* @param mode - Overlay rendering mode that was used, must match the mode
|
|
240
223
|
* passed to `setOverlayState`. Defaults to "absolute".
|
|
241
224
|
*/
|
|
242
|
-
clearOverlayState = (
|
|
225
|
+
clearOverlayState = async (
|
|
243
226
|
event: PointerEventCoordinates | null,
|
|
244
|
-
|
|
245
|
-
className
|
|
227
|
+
target: LayoutPath,
|
|
228
|
+
className?: string,
|
|
246
229
|
) => {
|
|
247
|
-
|
|
248
|
-
panel = remove_child(panel, slot);
|
|
249
|
-
const query = `:scope > [${this._physics.CHILD_ATTRIBUTE_NAME}="${slot}"]`;
|
|
250
|
-
const drag_element = this.querySelector(query);
|
|
251
|
-
if (drag_element) {
|
|
252
|
-
drag_element.classList.remove(className);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (event === null) {
|
|
256
|
-
this.restore(layout);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const [col, row, box] = this.relativeCoordinates(event, false);
|
|
261
|
-
let drop_target = calculate_intersection(col, row, panel);
|
|
262
|
-
if (drop_target) {
|
|
263
|
-
drop_target = calculate_edge(
|
|
264
|
-
col,
|
|
265
|
-
row,
|
|
266
|
-
panel,
|
|
267
|
-
slot,
|
|
268
|
-
drop_target,
|
|
269
|
-
box,
|
|
270
|
-
this._physics,
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (drop_target) {
|
|
275
|
-
const orientation = drop_target?.is_edge
|
|
276
|
-
? drop_target.orientation
|
|
277
|
-
: undefined;
|
|
278
|
-
|
|
279
|
-
const new_layout = insert_child(
|
|
280
|
-
panel,
|
|
281
|
-
slot,
|
|
282
|
-
drop_target.path,
|
|
283
|
-
orientation,
|
|
284
|
-
);
|
|
285
|
-
|
|
286
|
-
this.restore(new_layout);
|
|
287
|
-
} else {
|
|
288
|
-
this.restore(layout);
|
|
289
|
-
}
|
|
230
|
+
await this._overlayController.clear(event, target, className);
|
|
290
231
|
};
|
|
291
232
|
|
|
292
233
|
/**
|
|
@@ -300,7 +241,7 @@ export class RegularLayout extends HTMLElement {
|
|
|
300
241
|
* the new `SplitPanel` _if_ there is an option of orientation (e.g. if
|
|
301
242
|
* the layout had no pre-existing `SplitPanel`)
|
|
302
243
|
*/
|
|
303
|
-
insertPanel = (
|
|
244
|
+
insertPanel = async (
|
|
304
245
|
name: string,
|
|
305
246
|
path: LayoutPathTraversal = [],
|
|
306
247
|
split?: boolean | Orientation,
|
|
@@ -312,7 +253,7 @@ export class RegularLayout extends HTMLElement {
|
|
|
312
253
|
orientation = split;
|
|
313
254
|
}
|
|
314
255
|
|
|
315
|
-
this.restore(insert_child(this._panel, name, path, orientation));
|
|
256
|
+
await this.restore(insert_child(this._panel, name, path, orientation));
|
|
316
257
|
};
|
|
317
258
|
|
|
318
259
|
/**
|
|
@@ -320,8 +261,38 @@ export class RegularLayout extends HTMLElement {
|
|
|
320
261
|
*
|
|
321
262
|
* @param name - Name of the panel to remove
|
|
322
263
|
*/
|
|
323
|
-
removePanel = (name: string) => {
|
|
324
|
-
this.restore(remove_child(this._panel, name));
|
|
264
|
+
removePanel = async (name: string) => {
|
|
265
|
+
await this.restore(remove_child(this._panel, name));
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Selects a panel by `name`, making it the front-most tab within its
|
|
270
|
+
* containing stack, then dispatches a `<prefix>-select` event with
|
|
271
|
+
* `detail: { name }`.
|
|
272
|
+
*
|
|
273
|
+
* The event fires whenever a tab is selected - including re-selecting the
|
|
274
|
+
* already-active tab, or selecting the sole tab of a single-element stack -
|
|
275
|
+
* so consumers can always map a tab interaction to an "active panel". When
|
|
276
|
+
* the selection actually changes, a `<prefix>-update` event is dispatched
|
|
277
|
+
* first (via {@link restore}).
|
|
278
|
+
*
|
|
279
|
+
* @param name - The name of the panel to select.
|
|
280
|
+
*/
|
|
281
|
+
select = async (name: string) => {
|
|
282
|
+
const layout = this.save();
|
|
283
|
+
const tab_panel = this.getPanel(name, layout);
|
|
284
|
+
if (!tab_panel) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const index = tab_panel.tabs.indexOf(name);
|
|
289
|
+
if (index !== -1 && tab_panel.selected !== index) {
|
|
290
|
+
tab_panel.selected = index;
|
|
291
|
+
await this.restore(layout);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const event_name = `${this._physics.CUSTOM_EVENT_NAME_PREFIX}-select`;
|
|
295
|
+
this.dispatchEvent(new CustomEvent(event_name, { detail: { name } }));
|
|
325
296
|
};
|
|
326
297
|
|
|
327
298
|
/**
|
|
@@ -353,29 +324,100 @@ export class RegularLayout extends HTMLElement {
|
|
|
353
324
|
/**
|
|
354
325
|
* Clears the entire layout, unslotting all panels.
|
|
355
326
|
*/
|
|
356
|
-
clear = () => {
|
|
357
|
-
this.restore(EMPTY_PANEL);
|
|
327
|
+
clear = async () => {
|
|
328
|
+
await this.restore(EMPTY_PANEL);
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Maximizes a panel by `name`, rendering it full-size and hiding every
|
|
333
|
+
* other panel. This is transient display state - it is **not** persisted by
|
|
334
|
+
* {@link save}, and any subsequent {@link restore} resets the layout to the
|
|
335
|
+
* minimized (normal, multi-panel) view.
|
|
336
|
+
*
|
|
337
|
+
* Has no effect if `name` is not present in the current layout.
|
|
338
|
+
*
|
|
339
|
+
* @param name - The name of the panel to maximize.
|
|
340
|
+
*/
|
|
341
|
+
maximize = (name: string) => {
|
|
342
|
+
if (!this.getPanel(name)) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
this._maximized = name;
|
|
347
|
+
this._stylesheet.replaceSync(
|
|
348
|
+
create_css_maximize_layout(name, this._physics),
|
|
349
|
+
);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Restores the normal multi-panel view after a {@link maximize}, without
|
|
354
|
+
* altering the layout tree. No-op if no panel is currently maximized.
|
|
355
|
+
*/
|
|
356
|
+
minimize = () => {
|
|
357
|
+
if (this._maximized === undefined) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
this._maximized = undefined;
|
|
362
|
+
this._stylesheet.replaceSync(
|
|
363
|
+
create_css_grid_layout(this._panel, undefined, this._physics),
|
|
364
|
+
);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Restores the layout from a saved state synchronously, without
|
|
369
|
+
* dispatching the `regular-layout-before-resize` event.
|
|
370
|
+
*
|
|
371
|
+
* @param layout - The layout tree to restore
|
|
372
|
+
*/
|
|
373
|
+
restoreSync = (layout: Layout, _is_flattened: boolean = false) => {
|
|
374
|
+
this._maximized = undefined;
|
|
375
|
+
this._panel = !_is_flattened ? flatten(layout) : layout;
|
|
376
|
+
const css = create_css_grid_layout(this._panel, undefined, this._physics);
|
|
377
|
+
this._stylesheet.replaceSync(css);
|
|
378
|
+
const event_name = `${this._physics.CUSTOM_EVENT_NAME_PREFIX}-update`;
|
|
379
|
+
const event = new CustomEvent<Layout>(event_name, { detail: this._panel });
|
|
380
|
+
this.dispatchEvent(event);
|
|
358
381
|
};
|
|
359
382
|
|
|
360
383
|
/**
|
|
361
384
|
* Restores the layout from a saved state.
|
|
362
385
|
*
|
|
386
|
+
* Before applying, dispatches a cancelable `regular-layout-before-resize`
|
|
387
|
+
* event. If the event is cancelled via `preventDefault()`, the layout
|
|
388
|
+
* update is suspended until {@link resumeResize} is called.
|
|
389
|
+
*
|
|
363
390
|
* @param layout - The layout tree to restore
|
|
364
391
|
*
|
|
365
392
|
* @example
|
|
366
393
|
* ```typescript
|
|
367
394
|
* const layout = document.querySelector('regular-layout');
|
|
368
395
|
* const savedState = JSON.parse(localStorage.getItem('layout'));
|
|
369
|
-
* layout.restore(savedState);
|
|
396
|
+
* await layout.restore(savedState);
|
|
370
397
|
* ```
|
|
371
398
|
*/
|
|
372
|
-
restore = (layout: Layout, _is_flattened: boolean = false) => {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
399
|
+
restore = async (layout: Layout, _is_flattened: boolean = false) => {
|
|
400
|
+
const panel = !_is_flattened ? flatten(layout) : layout;
|
|
401
|
+
await this._presizeQueue.run(panel, () => {
|
|
402
|
+
this._maximized = undefined;
|
|
403
|
+
this._panel = panel;
|
|
404
|
+
const css = create_css_grid_layout(this._panel, undefined, this._physics);
|
|
405
|
+
this._stylesheet.replaceSync(css);
|
|
406
|
+
const event_name = `${this._physics.CUSTOM_EVENT_NAME_PREFIX}-update`;
|
|
407
|
+
const event = new CustomEvent<Layout>(event_name, {
|
|
408
|
+
detail: this._panel,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
this.dispatchEvent(event);
|
|
412
|
+
});
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Resumes a layout update that was suspended by cancelling the
|
|
417
|
+
* `regular-layout-before-resize` event.
|
|
418
|
+
*/
|
|
419
|
+
resumeResize = () => {
|
|
420
|
+
this._presizeQueue.resume();
|
|
379
421
|
};
|
|
380
422
|
|
|
381
423
|
/**
|
|
@@ -441,21 +483,53 @@ export class RegularLayout extends HTMLElement {
|
|
|
441
483
|
|
|
442
484
|
const box = this._dimensions.box;
|
|
443
485
|
const style = this._dimensions.style;
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
486
|
+
const paddingLeft = parseFloat(style.paddingLeft);
|
|
487
|
+
const paddingTop = parseFloat(style.paddingTop);
|
|
488
|
+
const contentWidth =
|
|
489
|
+
box.width - paddingLeft - parseFloat(style.paddingRight);
|
|
490
|
+
|
|
491
|
+
const contentHeight =
|
|
492
|
+
box.height - paddingTop - parseFloat(style.paddingBottom);
|
|
493
|
+
|
|
494
|
+
const localX = event.clientX - box.left - paddingLeft;
|
|
495
|
+
const localY = event.clientY - box.top - paddingTop;
|
|
496
|
+
const col = Math.max(0, Math.min(1, localX / contentWidth));
|
|
497
|
+
const row = Math.max(0, Math.min(1, localY / contentHeight));
|
|
456
498
|
return [col, row, box, style];
|
|
457
499
|
};
|
|
458
500
|
|
|
501
|
+
/**
|
|
502
|
+
* Converts a {@link ViewWindow} (normalized 0–1 coordinates) to a
|
|
503
|
+
* `DOMRect` in screen pixels, accounting for padding and optionally
|
|
504
|
+
* CSS `gap` and child `margin`.
|
|
505
|
+
*
|
|
506
|
+
* @param window - The view window to convert.
|
|
507
|
+
* @returns A `DOMRect` representing the window in screen coordinates.
|
|
508
|
+
*/
|
|
509
|
+
realCoordinates = (window: ViewWindow, child?: HTMLElement): DOMRect => {
|
|
510
|
+
if (!this._dimensions) {
|
|
511
|
+
this._dimensions = {
|
|
512
|
+
box: this.getBoundingClientRect(),
|
|
513
|
+
style: getComputedStyle(this),
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const box = this._dimensions.box;
|
|
518
|
+
const style = this._dimensions.style;
|
|
519
|
+
let childStyle: CSSStyleDeclaration | undefined;
|
|
520
|
+
if (child) {
|
|
521
|
+
childStyle = getComputedStyle(child);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const local = viewWindowToLocalRect(window, box, style, childStyle);
|
|
525
|
+
return new DOMRect(
|
|
526
|
+
box.left + local.x,
|
|
527
|
+
box.top + local.y,
|
|
528
|
+
local.width,
|
|
529
|
+
local.height,
|
|
530
|
+
);
|
|
531
|
+
};
|
|
532
|
+
|
|
459
533
|
/**
|
|
460
534
|
* Calculates the Euclidean distance in pixels between the current pointer
|
|
461
535
|
* coordinates and a drag target's position within the layout.
|
|
@@ -476,7 +550,47 @@ export class RegularLayout extends HTMLElement {
|
|
|
476
550
|
return Math.sqrt(dx ** 2 + dy ** 2);
|
|
477
551
|
};
|
|
478
552
|
|
|
479
|
-
|
|
553
|
+
// ▇█████████████████■■■■ȺȺȺȺȺ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ȺȺȺȺȺ■■■■■█████████████████
|
|
554
|
+
// ███████▛▀▀█▃▄▄▅▆▆▆▇▇▇██████████████████████████████▇▇▇▆▆▆▅▅▄▃█▀▀▜███████
|
|
555
|
+
// ████▛▚▅▇███▍ ▗▄▃¯"▜██▘ ▜██▍ ▀█▋ ▐█▀▔▂▄▃ ▔▜█ ▗▃▃▃▄▊ ▄▄▃ ▔▜██▇▅▃▀████
|
|
556
|
+
// ███▋╺██████▍ ▐██▋ █▘ ◨Ƚ "██▍ ▙▁ ▀ ▐▋ █▛▀▀▀▜█ ´▂▂Ŋ█▊ °"▔ ▃██████▍▐███
|
|
557
|
+
// ████▄▝▜████▍ ▝▀▀ ▂▟▘ ▄▄▄▄ "█▍ ██▄ ▐█▃ ▝▀▀ ▐█ ▝▀▀▀▀▊ ██▖ ▝████▛▀▄████
|
|
558
|
+
// ██████▇▆▄▃█▀▀Ⱥ▼■███▇▇████▇▇█▇▇████▇████▇▇▇████▇▇▇▇▇▇▇█▇■◘▀▀▀▀▂▃▄▅▇██████
|
|
559
|
+
// ███████████████▇▇▆▆▆▅▅▅▅▅▅▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▅▅▅▅▅▅▆▆▆▇▇███████████████
|
|
560
|
+
//
|
|
561
|
+
//
|
|
562
|
+
// ┏▅▅▖ ▗▅▅▶ ▅▅▅▅▅▅▅ ▗▅▅▅▅▅▅▖ ▅▅▅▅▅▄▄▂ ▃▅▆▆▆▄▁ ┏▅▅▖ ▅▅▖▅▅▅▅▅▅▅▅▅
|
|
563
|
+
// ┣██▌▗██▛ ██▊▔▔▔▔ ▐██▋▔▔▔` ███▍"▜██▙ ▟██▘▔███▖ ▐██▌ ██▌^▔▔███^▔^
|
|
564
|
+
// ┣██▙██▋ ██▊▂▂▂ ▐██▋▂▂▂ ███▍ ▐██▋ ▐███ ▐██▋ ▐██▌ ██▌ ███▎
|
|
565
|
+
// ┣██████▌ ███▀▀▀ ▐███▀▀▘ ██████▛▀ ▐███ ▐██▋ ▐██▌ ██▌ ███▎
|
|
566
|
+
// ┣██▛ ▜██▖ ██▊ ▐██▋ ███▍ ▝███ ▟██▌ ▐██▌ ██▌ ███▎
|
|
567
|
+
// ┣██▌ ███ ███▆▆▆▆▎▐███▆▆▆▅ ███▍ ▀██▙▅██▛ ▝███▅▆██▘ ███▎
|
|
568
|
+
// ▔▔▔` ´▔▔` ▔▔▔▔▔▔▔ ´▔▔▔▔▔▔▔ ▔▔▔ ▔""▔¯ ▔""^▔ ▔▔▔
|
|
569
|
+
|
|
570
|
+
private create_overlay_host(): OverlayHost {
|
|
571
|
+
const self = this;
|
|
572
|
+
return {
|
|
573
|
+
get panel() {
|
|
574
|
+
return self._panel;
|
|
575
|
+
},
|
|
576
|
+
get physics() {
|
|
577
|
+
return self._physics;
|
|
578
|
+
},
|
|
579
|
+
get stylesheet() {
|
|
580
|
+
return self._stylesheet;
|
|
581
|
+
},
|
|
582
|
+
get presizeQueue() {
|
|
583
|
+
return self._presizeQueue;
|
|
584
|
+
},
|
|
585
|
+
relativeCoordinates: (event, recalculate) =>
|
|
586
|
+
self.relativeCoordinates(event, recalculate),
|
|
587
|
+
restore: (layout, isFlattened) => self.restore(layout, isFlattened),
|
|
588
|
+
querySelector: (selectors) => self.querySelector(selectors),
|
|
589
|
+
dispatchEvent: (event) => self.dispatchEvent(event),
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
private onDblClick = async (event: MouseEvent) => {
|
|
480
594
|
const [col, row, rect] = this.relativeCoordinates(event, false);
|
|
481
595
|
const divider = calculate_intersection(col, row, this._panel, {
|
|
482
596
|
rect,
|
|
@@ -490,11 +604,15 @@ export class RegularLayout extends HTMLElement {
|
|
|
490
604
|
undefined,
|
|
491
605
|
);
|
|
492
606
|
|
|
493
|
-
this.restore(panel, true);
|
|
607
|
+
await this.restore(panel, true);
|
|
494
608
|
}
|
|
495
609
|
};
|
|
496
610
|
|
|
497
611
|
private onPointerDown = (event: PointerEvent) => {
|
|
612
|
+
if (event.button !== 0) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
498
616
|
if (!this._physics.GRID_DIVIDER_CHECK_TARGET || event.target === this) {
|
|
499
617
|
const [col, row, rect] = this.relativeCoordinates(event);
|
|
500
618
|
const size = this._physics.GRID_DIVIDER_SIZE;
|
|
@@ -507,15 +625,17 @@ export class RegularLayout extends HTMLElement {
|
|
|
507
625
|
}
|
|
508
626
|
};
|
|
509
627
|
|
|
510
|
-
private onPointerMove = (event: PointerEvent) => {
|
|
628
|
+
private onPointerMove = async (event: PointerEvent) => {
|
|
511
629
|
if (this._drag_target) {
|
|
512
630
|
const [col, row] = this.relativeCoordinates(event, false);
|
|
513
631
|
const [{ path, type }, old_col, old_row] = this._drag_target;
|
|
514
632
|
const offset = type === "horizontal" ? old_col - col : old_row - row;
|
|
515
633
|
const panel = redistribute_panel_sizes(this._panel, path, offset);
|
|
516
|
-
this.
|
|
517
|
-
|
|
518
|
-
|
|
634
|
+
await this._presizeQueue.run(panel, () => {
|
|
635
|
+
this._stylesheet.replaceSync(
|
|
636
|
+
create_css_grid_layout(panel, undefined, this._physics),
|
|
637
|
+
);
|
|
638
|
+
});
|
|
519
639
|
}
|
|
520
640
|
|
|
521
641
|
if (this._physics.GRID_DIVIDER_CHECK_TARGET && event.target !== this) {
|
|
@@ -545,14 +665,14 @@ export class RegularLayout extends HTMLElement {
|
|
|
545
665
|
}
|
|
546
666
|
};
|
|
547
667
|
|
|
548
|
-
private onPointerUp = (event: PointerEvent) => {
|
|
668
|
+
private onPointerUp = async (event: PointerEvent) => {
|
|
549
669
|
if (this._drag_target) {
|
|
550
670
|
this.releasePointerCapture(event.pointerId);
|
|
551
671
|
const [col, row] = this.relativeCoordinates(event, false);
|
|
552
672
|
const [{ path, type }, old_col, old_row] = this._drag_target;
|
|
553
673
|
const offset = type === "horizontal" ? old_col - col : old_row - row;
|
|
554
674
|
const panel = redistribute_panel_sizes(this._panel, path, offset);
|
|
555
|
-
this.restore(panel, true);
|
|
675
|
+
await this.restore(panel, true);
|
|
556
676
|
this._drag_target = undefined;
|
|
557
677
|
}
|
|
558
678
|
};
|
|
@@ -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
|
+
}
|