regular-layout 0.5.0 → 0.6.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/dist/core/types.d.ts +14 -0
- package/dist/index.js +19 -17
- package/dist/index.js.map +3 -3
- package/dist/model/presize_queue.d.ts +12 -3
- package/dist/regular-layout-frame.d.ts +12 -10
- package/dist/regular-layout.d.ts +14 -2
- package/package.json +1 -1
- package/src/core/types.ts +12 -0
- package/src/layout/generate_grid.ts +39 -13
- package/src/layout/generate_overlay.ts +1 -1
- package/src/model/overlay_controller.ts +42 -29
- package/src/model/presize_queue.ts +33 -8
- package/src/regular-layout-frame.ts +58 -56
- package/src/regular-layout.ts +45 -11
- package/themes/borland.css +6 -4
- package/themes/chicago.css +12 -4
- package/themes/fluxbox.css +6 -3
- package/themes/gibson.css +8 -22
- package/themes/hotdog.css +13 -4
- package/themes/lorax.css +40 -43
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import type { Layout } from "../core/types.ts";
|
|
1
|
+
import type { Layout, LayoutPath } from "../core/types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The dragged panel's preview geometry during a drag transition - see
|
|
4
|
+
* {@link PresizeDetail.overlay}.
|
|
5
|
+
*/
|
|
6
|
+
export type PresizeOverlay = {
|
|
7
|
+
slot: string;
|
|
8
|
+
path: LayoutPath;
|
|
9
|
+
} | null;
|
|
2
10
|
/**
|
|
3
11
|
* Manages cancelable pre-resize gating for layout updates.
|
|
4
12
|
*
|
|
@@ -11,7 +19,8 @@ export declare class PresizeQueue {
|
|
|
11
19
|
#private;
|
|
12
20
|
private _target;
|
|
13
21
|
private _eventName;
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
private _onDispatch?;
|
|
23
|
+
constructor(_target: EventTarget, _eventName: string, _onDispatch?: (() => void) | undefined);
|
|
24
|
+
run(layout: Layout, fn: () => void, overlay?: PresizeOverlay): Promise<void>;
|
|
16
25
|
resume(): void;
|
|
17
26
|
}
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
export declare class RegularLayoutFrame extends HTMLElement {
|
|
36
36
|
private _shadowRoot;
|
|
37
37
|
private _container_sheet;
|
|
38
|
-
private
|
|
38
|
+
private _tab_sheet;
|
|
39
39
|
private _layout;
|
|
40
40
|
private _header;
|
|
41
|
+
private _default_tab;
|
|
41
42
|
private _drag;
|
|
42
|
-
private _tab_to_index_map;
|
|
43
43
|
/**
|
|
44
44
|
* Initializes this elements. Override this method and
|
|
45
45
|
* `disconnectedCallback` to modify how this subclass renders the Shadow
|
|
@@ -57,14 +57,16 @@ export declare class RegularLayoutFrame extends HTMLElement {
|
|
|
57
57
|
private onPointerLost;
|
|
58
58
|
private drawTabs;
|
|
59
59
|
/**
|
|
60
|
-
* Regenerates this frame's
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
60
|
+
* Regenerates this frame's tab stylesheet: the shared titlebar grid template
|
|
61
|
+
* (so every frame in the stack aligns), the column this frame's tab occupies,
|
|
62
|
+
* and the tab label. The label renders via the title's `::before` `content`,
|
|
63
|
+
* reading the slot's `--regular-layout-<slot>--title` override variable with
|
|
64
|
+
* the slot name as the fallback - so a consumer can relabel a tab purely in
|
|
65
|
+
* CSS and the change applies reactively.
|
|
65
66
|
*
|
|
66
|
-
* @param
|
|
67
|
-
* @param
|
|
67
|
+
* @param slot - This frame's panel name.
|
|
68
|
+
* @param index - This frame's column (zero-based) within the stack.
|
|
69
|
+
* @param count - The number of tabs in the stack.
|
|
68
70
|
*/
|
|
69
|
-
private
|
|
71
|
+
private drawTab;
|
|
70
72
|
}
|
package/dist/regular-layout.d.ts
CHANGED
|
@@ -78,6 +78,7 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
78
78
|
private _presizeQueue;
|
|
79
79
|
private _overlayController;
|
|
80
80
|
private _maximized?;
|
|
81
|
+
private _resize_observer?;
|
|
81
82
|
constructor();
|
|
82
83
|
connectedCallback(): void;
|
|
83
84
|
disconnectedCallback(): void;
|
|
@@ -174,16 +175,27 @@ export declare class RegularLayout extends HTMLElement {
|
|
|
174
175
|
* {@link save}, and any subsequent {@link restore} resets the layout to the
|
|
175
176
|
* minimized (normal, multi-panel) view.
|
|
176
177
|
*
|
|
178
|
+
* Before applying, dispatches a cancelable `regular-layout-before-resize`
|
|
179
|
+
* event (as {@link restore} does) whose paths describe the post-maximize
|
|
180
|
+
* geometry - a single full-window entry for `name`. If the event is
|
|
181
|
+
* cancelled via `preventDefault()`, the update is suspended until
|
|
182
|
+
* {@link resumeResize} is called.
|
|
183
|
+
*
|
|
177
184
|
* Has no effect if `name` is not present in the current layout.
|
|
178
185
|
*
|
|
179
186
|
* @param name - The name of the panel to maximize.
|
|
180
187
|
*/
|
|
181
|
-
maximize: (name: string) => void
|
|
188
|
+
maximize: (name: string) => Promise<void>;
|
|
182
189
|
/**
|
|
183
190
|
* Restores the normal multi-panel view after a {@link maximize}, without
|
|
184
191
|
* altering the layout tree. No-op if no panel is currently maximized.
|
|
192
|
+
*
|
|
193
|
+
* Before applying, dispatches a cancelable `regular-layout-before-resize`
|
|
194
|
+
* event (as {@link restore} does) with paths for the restored multi-panel
|
|
195
|
+
* geometry. If the event is cancelled via `preventDefault()`, the update
|
|
196
|
+
* is suspended until {@link resumeResize} is called.
|
|
185
197
|
*/
|
|
186
|
-
minimize: () => void
|
|
198
|
+
minimize: () => Promise<void>;
|
|
187
199
|
/**
|
|
188
200
|
* Restores the layout from a saved state synchronously, without
|
|
189
201
|
* dispatching the `regular-layout-before-resize` event.
|
package/package.json
CHANGED
package/src/core/types.ts
CHANGED
|
@@ -100,6 +100,18 @@ export interface LayoutPath {
|
|
|
100
100
|
*/
|
|
101
101
|
export interface PresizeDetail {
|
|
102
102
|
calculatePresizePaths(): Record<string, LayoutPath>;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* When the transition is a drag preview, the dragged panel's name and the
|
|
106
|
+
* {@link LayoutPath} of its preview box. The dragged panel is removed from
|
|
107
|
+
* the layout tree during a drag (so it is absent from
|
|
108
|
+
* {@link PresizeDetail.calculatePresizePaths}), but `path.view_window` here
|
|
109
|
+
* is exactly where the overlay renders it, letting consumers presize the
|
|
110
|
+
* dragged panel like any other. `null` when the pointer is outside every
|
|
111
|
+
* drop target (the dragged panel is hidden), `undefined` for non-drag
|
|
112
|
+
* transitions.
|
|
113
|
+
*/
|
|
114
|
+
overlay?: { slot: string; path: LayoutPath } | null;
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
/**
|
|
@@ -18,6 +18,7 @@ interface GridCell {
|
|
|
18
18
|
colEnd: number;
|
|
19
19
|
rowStart: number;
|
|
20
20
|
rowEnd: number;
|
|
21
|
+
zIndex?: number;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
function dedupe_sort(physics: Physics, result: number[], pos: number) {
|
|
@@ -102,15 +103,26 @@ function build_cells(
|
|
|
102
103
|
): GridCell[] {
|
|
103
104
|
if (panel.type === "tab-layout") {
|
|
104
105
|
const selected = panel.selected ?? 0;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
106
|
+
const col_start = find_track_index(physics, colPositions, colStart);
|
|
107
|
+
const col_end = find_track_index(physics, colPositions, colEnd);
|
|
108
|
+
const row_start = find_track_index(physics, rowPositions, rowStart);
|
|
109
|
+
const row_end = find_track_index(physics, rowPositions, rowEnd);
|
|
110
|
+
|
|
111
|
+
// Stacked tabs all occupy the same cell, overlapping. Only the selected
|
|
112
|
+
// frame is lifted (`z-index:1`) so its content sits on top; the rest
|
|
113
|
+
// self-hide their content and just contribute their tab. Keeping the
|
|
114
|
+
// max frame `z-index` at 1 lets the drag overlay sit above any stack
|
|
115
|
+
// with a small constant (2). A single-tab stack
|
|
116
|
+
// emits one placement with no `z-index`, so non-stacked output is
|
|
117
|
+
// unchanged.
|
|
118
|
+
return panel.tabs.map((child, i) => ({
|
|
119
|
+
child,
|
|
120
|
+
colStart: col_start,
|
|
121
|
+
colEnd: col_end,
|
|
122
|
+
rowStart: row_start,
|
|
123
|
+
rowEnd: row_end,
|
|
124
|
+
zIndex: panel.tabs.length > 1 && i === selected ? 1 : undefined,
|
|
125
|
+
}));
|
|
114
126
|
}
|
|
115
127
|
|
|
116
128
|
const { children, sizes, orientation } = panel;
|
|
@@ -162,8 +174,11 @@ const child_template = (
|
|
|
162
174
|
slot: string,
|
|
163
175
|
rowPart: string,
|
|
164
176
|
colPart: string,
|
|
177
|
+
zIndex?: number,
|
|
165
178
|
) =>
|
|
166
|
-
`:host ::slotted([${physics.CHILD_ATTRIBUTE_NAME}="${slot}"]){display:flex;grid-column:${colPart};grid-row:${rowPart}
|
|
179
|
+
`:host ::slotted([${physics.CHILD_ATTRIBUTE_NAME}="${slot}"]){display:flex;grid-column:${colPart};grid-row:${rowPart}${
|
|
180
|
+
zIndex === undefined ? "" : `;z-index:${zIndex}`
|
|
181
|
+
}}`;
|
|
167
182
|
|
|
168
183
|
/**
|
|
169
184
|
* Generates CSS Grid styles to render a layout tree.
|
|
@@ -202,9 +217,18 @@ export function create_css_grid_layout(
|
|
|
202
217
|
): string {
|
|
203
218
|
if (layout.type === "tab-layout") {
|
|
204
219
|
const selected = layout.selected ?? 0;
|
|
220
|
+
const stacked = layout.tabs.length > 1;
|
|
205
221
|
return [
|
|
206
222
|
host_template("100%", "100%"),
|
|
207
|
-
|
|
223
|
+
...layout.tabs.map((tab, i) =>
|
|
224
|
+
child_template(
|
|
225
|
+
physics,
|
|
226
|
+
tab,
|
|
227
|
+
"1",
|
|
228
|
+
"1",
|
|
229
|
+
stacked && i === selected ? 1 : undefined,
|
|
230
|
+
),
|
|
231
|
+
),
|
|
208
232
|
].join("\n");
|
|
209
233
|
}
|
|
210
234
|
|
|
@@ -253,11 +277,13 @@ export function create_css_grid_layout(
|
|
|
253
277
|
for (const cell of cells) {
|
|
254
278
|
const colPart = formatGridLine(cell.colStart, cell.colEnd);
|
|
255
279
|
const rowPart = formatGridLine(cell.rowStart, cell.rowEnd);
|
|
256
|
-
css.push(
|
|
280
|
+
css.push(
|
|
281
|
+
child_template(physics, cell.child, rowPart, colPart, cell.zIndex),
|
|
282
|
+
);
|
|
257
283
|
if (cell.child === overlay?.[1]) {
|
|
258
284
|
css.push(child_template(physics, overlay[0], rowPart, colPart));
|
|
259
285
|
css.push(
|
|
260
|
-
`:host ::slotted([${physics.CHILD_ATTRIBUTE_NAME}=${overlay[0]}]){z-index:
|
|
286
|
+
`:host ::slotted([${physics.CHILD_ATTRIBUTE_NAME}=${overlay[0]}]){z-index:2}`,
|
|
261
287
|
);
|
|
262
288
|
}
|
|
263
289
|
}
|
|
@@ -69,6 +69,6 @@ export function updateOverlaySheet(
|
|
|
69
69
|
margin,
|
|
70
70
|
);
|
|
71
71
|
|
|
72
|
-
const css = `display:flex;position:absolute!important;z-index:
|
|
72
|
+
const css = `display:flex;position:absolute!important;z-index:2;top:${local.y}px;left:${local.x}px;height:${local.height}px;width:${local.width}px;`;
|
|
73
73
|
return `::slotted([${physics.CHILD_ATTRIBUTE_NAME}="${slot}"]){${css}}`;
|
|
74
74
|
}
|
|
@@ -70,36 +70,49 @@ export class OverlayController {
|
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
73
|
+
// The dragged panel is removed from `panel` above, so it is absent from
|
|
74
|
+
// the presize paths - `drop_target` carries its preview box instead
|
|
75
|
+
// (null when the pointer is outside every drop target and the dragged
|
|
76
|
+
// panel is hidden).
|
|
77
|
+
const overlay = drop_target ? { slot, path: drop_target } : null;
|
|
78
|
+
await host.presizeQueue.run(
|
|
79
|
+
panel,
|
|
80
|
+
() => {
|
|
81
|
+
if (mode === "grid" && drop_target) {
|
|
82
|
+
const path: [string, string] = [slot, drop_target?.slot];
|
|
83
|
+
const css = create_css_grid_layout(panel, path, host.physics);
|
|
84
|
+
host.stylesheet.replaceSync(css);
|
|
85
|
+
} else if (mode === "absolute") {
|
|
86
|
+
const grid_css = create_css_grid_layout(
|
|
87
|
+
panel,
|
|
88
|
+
undefined,
|
|
89
|
+
host.physics,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
while (drag_element?.tagName === "SLOT" && drag_element) {
|
|
93
|
+
drag_element = (
|
|
94
|
+
drag_element as HTMLSlotElement
|
|
95
|
+
).assignedElements()[0];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const margin = drag_element
|
|
99
|
+
? getComputedStyle(drag_element)
|
|
100
|
+
: undefined;
|
|
101
|
+
|
|
102
|
+
const overlay_css = updateOverlaySheet(
|
|
103
|
+
slot,
|
|
104
|
+
box,
|
|
105
|
+
style,
|
|
106
|
+
drop_target,
|
|
107
|
+
host.physics,
|
|
108
|
+
margin,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
host.stylesheet.replaceSync([grid_css, overlay_css].join("\n"));
|
|
85
112
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
: undefined;
|
|
90
|
-
|
|
91
|
-
const overlay_css = updateOverlaySheet(
|
|
92
|
-
slot,
|
|
93
|
-
box,
|
|
94
|
-
style,
|
|
95
|
-
drop_target,
|
|
96
|
-
host.physics,
|
|
97
|
-
margin,
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
host.stylesheet.replaceSync([grid_css, overlay_css].join("\n"));
|
|
101
|
-
}
|
|
102
|
-
});
|
|
113
|
+
},
|
|
114
|
+
overlay,
|
|
115
|
+
);
|
|
103
116
|
|
|
104
117
|
const event_name = `${host.physics.CUSTOM_EVENT_NAME_PREFIX}-before-update`;
|
|
105
118
|
const custom_event = new CustomEvent<Layout>(event_name, {
|
|
@@ -9,9 +9,15 @@
|
|
|
9
9
|
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
|
|
10
10
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
11
11
|
|
|
12
|
-
import type { Layout, PresizeDetail } from "../core/types.ts";
|
|
12
|
+
import type { Layout, LayoutPath, PresizeDetail } from "../core/types.ts";
|
|
13
13
|
import { calculate_presize_paths } from "../layout/calculate_presize_paths.ts";
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* The dragged panel's preview geometry during a drag transition - see
|
|
17
|
+
* {@link PresizeDetail.overlay}.
|
|
18
|
+
*/
|
|
19
|
+
export type PresizeOverlay = { slot: string; path: LayoutPath } | null;
|
|
20
|
+
|
|
15
21
|
/**
|
|
16
22
|
* Manages cancelable pre-resize gating for layout updates.
|
|
17
23
|
*
|
|
@@ -22,27 +28,40 @@ import { calculate_presize_paths } from "../layout/calculate_presize_paths.ts";
|
|
|
22
28
|
*/
|
|
23
29
|
export class PresizeQueue {
|
|
24
30
|
#resizing = false;
|
|
25
|
-
#queued: {
|
|
31
|
+
#queued: {
|
|
32
|
+
layout: Layout;
|
|
33
|
+
fn: () => void;
|
|
34
|
+
overlay?: PresizeOverlay;
|
|
35
|
+
} | null = null;
|
|
26
36
|
#pending: (() => void) | null = null;
|
|
27
37
|
|
|
28
38
|
constructor(
|
|
29
39
|
private _target: EventTarget,
|
|
30
40
|
private _eventName: string,
|
|
41
|
+
private _onDispatch?: () => void,
|
|
31
42
|
) {}
|
|
32
43
|
|
|
33
|
-
async run(
|
|
44
|
+
async run(
|
|
45
|
+
layout: Layout,
|
|
46
|
+
fn: () => void,
|
|
47
|
+
overlay?: PresizeOverlay,
|
|
48
|
+
): Promise<void> {
|
|
34
49
|
if (this.#resizing) {
|
|
35
|
-
this.#queued = { layout, fn };
|
|
50
|
+
this.#queued = { layout, fn, overlay };
|
|
36
51
|
return;
|
|
37
52
|
}
|
|
38
53
|
|
|
39
54
|
this.#resizing = true;
|
|
40
55
|
try {
|
|
41
|
-
await this.#dispatchAndMaybeWait(layout, fn);
|
|
56
|
+
await this.#dispatchAndMaybeWait(layout, fn, overlay);
|
|
42
57
|
while (this.#queued) {
|
|
43
|
-
const {
|
|
58
|
+
const {
|
|
59
|
+
layout: nextLayout,
|
|
60
|
+
fn: nextFn,
|
|
61
|
+
overlay: nextOverlay,
|
|
62
|
+
} = this.#queued;
|
|
44
63
|
this.#queued = null;
|
|
45
|
-
await this.#dispatchAndMaybeWait(nextLayout, nextFn);
|
|
64
|
+
await this.#dispatchAndMaybeWait(nextLayout, nextFn, nextOverlay);
|
|
46
65
|
}
|
|
47
66
|
} finally {
|
|
48
67
|
this.#resizing = false;
|
|
@@ -57,9 +76,15 @@ export class PresizeQueue {
|
|
|
57
76
|
}
|
|
58
77
|
}
|
|
59
78
|
|
|
60
|
-
async #dispatchAndMaybeWait(
|
|
79
|
+
async #dispatchAndMaybeWait(
|
|
80
|
+
layout: Layout,
|
|
81
|
+
fn: () => void,
|
|
82
|
+
overlay?: PresizeOverlay,
|
|
83
|
+
): Promise<void> {
|
|
84
|
+
this._onDispatch?.();
|
|
61
85
|
const detail: PresizeDetail = {
|
|
62
86
|
calculatePresizePaths: () => calculate_presize_paths(layout),
|
|
87
|
+
overlay,
|
|
63
88
|
};
|
|
64
89
|
|
|
65
90
|
const event = new CustomEvent(this._eventName, {
|
|
@@ -15,17 +15,19 @@ import type { RegularLayout } from "./regular-layout.ts";
|
|
|
15
15
|
import type { RegularLayoutTab } from "./regular-layout-tab.ts";
|
|
16
16
|
|
|
17
17
|
const CSS = `
|
|
18
|
-
:host{box-sizing:border-box;flex-direction:column}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
:host
|
|
24
|
-
:
|
|
18
|
+
:host{box-sizing:border-box;flex-direction:column;pointer-events:none}
|
|
19
|
+
[part~="titlebar"]{height:24px;user-select:none;overflow:hidden}
|
|
20
|
+
[part~="container"]{flex:1 1 auto;pointer-events:auto}
|
|
21
|
+
[part~="title"]{flex:1 1 auto;pointer-events:none}
|
|
22
|
+
[part~="close"]{align-self:stretch}
|
|
23
|
+
:host([inactive]) [part~="container"]{display:none}
|
|
24
|
+
.tabs{display:grid;width:100%;height:100%}
|
|
25
|
+
.tabs slot[name="tab"]{display:grid;grid-row:1;pointer-events:auto}
|
|
26
|
+
.tabs regular-layout-tab{display:flex;overflow:hidden}
|
|
25
27
|
`;
|
|
26
28
|
|
|
27
29
|
const HTML_TEMPLATE = `
|
|
28
|
-
<div part="titlebar"></div>
|
|
30
|
+
<div part="titlebar"><div class="tabs" part="titlebar-track"><slot name="tab"><regular-layout-tab></regular-layout-tab></slot></div></div>
|
|
29
31
|
<div part="container"><slot></slot></div>
|
|
30
32
|
`;
|
|
31
33
|
|
|
@@ -82,11 +84,11 @@ const title_variable = (slot: string): string =>
|
|
|
82
84
|
export class RegularLayoutFrame extends HTMLElement {
|
|
83
85
|
private _shadowRoot!: ShadowRoot;
|
|
84
86
|
private _container_sheet!: CSSStyleSheet;
|
|
85
|
-
private
|
|
87
|
+
private _tab_sheet!: CSSStyleSheet;
|
|
86
88
|
private _layout!: RegularLayout;
|
|
87
89
|
private _header!: HTMLElement;
|
|
90
|
+
private _default_tab!: RegularLayoutTab;
|
|
88
91
|
private _drag: DragState | null = null;
|
|
89
|
-
private _tab_to_index_map: WeakMap<RegularLayoutTab, number> = new WeakMap();
|
|
90
92
|
|
|
91
93
|
/**
|
|
92
94
|
* Initializes this elements. Override this method and
|
|
@@ -96,15 +98,20 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
96
98
|
connectedCallback() {
|
|
97
99
|
this._container_sheet ??= new CSSStyleSheet();
|
|
98
100
|
this._container_sheet.replaceSync(CSS);
|
|
99
|
-
this.
|
|
101
|
+
this._tab_sheet ??= new CSSStyleSheet();
|
|
100
102
|
this._shadowRoot ??= this.attachShadow({ mode: "open" });
|
|
101
103
|
this._shadowRoot.adoptedStyleSheets = [
|
|
102
104
|
this._container_sheet,
|
|
103
|
-
this.
|
|
105
|
+
this._tab_sheet,
|
|
104
106
|
];
|
|
105
107
|
this._shadowRoot.innerHTML = HTML_TEMPLATE;
|
|
106
108
|
this._layout = this.parentElement as RegularLayout;
|
|
107
109
|
this._header = this._shadowRoot.children[0] as HTMLElement;
|
|
110
|
+
// The built-in tab is the `slot="tab"` fallback; a consumer-supplied
|
|
111
|
+
// `slot="tab"` child replaces it.
|
|
112
|
+
this._default_tab = this._header.querySelector(
|
|
113
|
+
"regular-layout-tab",
|
|
114
|
+
) as RegularLayoutTab;
|
|
108
115
|
this._header.addEventListener("pointerdown", this.onPointerDown);
|
|
109
116
|
this.addEventListener("pointermove", this.onPointerMove);
|
|
110
117
|
this.addEventListener("pointerup", this.onPointerUp);
|
|
@@ -140,9 +147,17 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
140
147
|
|
|
141
148
|
const elem = event.target as RegularLayoutTab;
|
|
142
149
|
if (elem.part.contains("tab")) {
|
|
150
|
+
const slot = this.getAttribute(
|
|
151
|
+
this._layout.savePhysics().CHILD_ATTRIBUTE_NAME,
|
|
152
|
+
);
|
|
153
|
+
|
|
143
154
|
const path = this._layout.calculateIntersect(event);
|
|
144
|
-
if (path) {
|
|
145
|
-
this
|
|
155
|
+
if (path && slot) {
|
|
156
|
+
// Drag *this frame's* panel, not whichever panel is front-most at
|
|
157
|
+
// the pointer (which is what `calculateIntersect` resolves to in a
|
|
158
|
+
// stack). The overlapping stack shares geometry, so only the slot
|
|
159
|
+
// needs overriding.
|
|
160
|
+
this._drag = { path: { ...path, slot } };
|
|
146
161
|
this.setPointerCapture(event.pointerId);
|
|
147
162
|
event.preventDefault();
|
|
148
163
|
} else {
|
|
@@ -190,54 +205,41 @@ export class RegularLayoutFrame extends HTMLElement {
|
|
|
190
205
|
return;
|
|
191
206
|
}
|
|
192
207
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
new_tab_panel = {
|
|
197
|
-
type: "tab-layout",
|
|
198
|
-
tabs: [slot],
|
|
199
|
-
selected: 0,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
for (let i = 0; i < new_tab_panel.tabs.length; i++) {
|
|
204
|
-
if (i >= this._header.children.length) {
|
|
205
|
-
const new_tab = document.createElement("regular-layout-tab");
|
|
206
|
-
new_tab.populate(this._layout, new_tab_panel, i);
|
|
207
|
-
this._header.appendChild(new_tab);
|
|
208
|
-
this._tab_to_index_map.set(new_tab, i);
|
|
209
|
-
} else {
|
|
210
|
-
const tab = this._header.children[i] as RegularLayoutTab;
|
|
211
|
-
tab.populate(this._layout, new_tab_panel, i);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const last_index = new_tab_panel.tabs.length;
|
|
216
|
-
for (let j = this._header.children.length - 1; j >= last_index; j--) {
|
|
217
|
-
this._header.removeChild(this._header.children[j]);
|
|
208
|
+
let tab_panel = this._layout.getPanel(slot, event.detail);
|
|
209
|
+
if (!tab_panel) {
|
|
210
|
+
tab_panel = { type: "tab-layout", tabs: [slot], selected: 0 };
|
|
218
211
|
}
|
|
219
212
|
|
|
220
|
-
|
|
213
|
+
// Each frame renders only its *own* tab. Frames in a stack overlap (see
|
|
214
|
+
// `create_css_grid_layout`); the tabs tile because every frame derives
|
|
215
|
+
// the same column template and places its tab in its own column. Only
|
|
216
|
+
// the selected frame shows its content; the rest keep just their tab.
|
|
217
|
+
const index = tab_panel.tabs.indexOf(slot);
|
|
218
|
+
const selected = (tab_panel.selected ?? 0) === index;
|
|
219
|
+
this.toggleAttribute("inactive", !selected);
|
|
220
|
+
this._default_tab.populate(this._layout, tab_panel, index);
|
|
221
|
+
this.drawTab(slot, index, tab_panel.tabs.length);
|
|
221
222
|
};
|
|
222
223
|
|
|
223
224
|
/**
|
|
224
|
-
* Regenerates this frame's
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
225
|
+
* Regenerates this frame's tab stylesheet: the shared titlebar grid template
|
|
226
|
+
* (so every frame in the stack aligns), the column this frame's tab occupies,
|
|
227
|
+
* and the tab label. The label renders via the title's `::before` `content`,
|
|
228
|
+
* reading the slot's `--regular-layout-<slot>--title` override variable with
|
|
229
|
+
* the slot name as the fallback - so a consumer can relabel a tab purely in
|
|
230
|
+
* CSS and the change applies reactively.
|
|
229
231
|
*
|
|
230
|
-
* @param
|
|
231
|
-
* @param
|
|
232
|
+
* @param slot - This frame's panel name.
|
|
233
|
+
* @param index - This frame's column (zero-based) within the stack.
|
|
234
|
+
* @param count - The number of tabs in the stack.
|
|
232
235
|
*/
|
|
233
|
-
private
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
.join("\n")
|
|
240
|
-
|
|
241
|
-
this._title_sheet.replaceSync(css);
|
|
236
|
+
private drawTab = (slot: string, index: number, count: number) => {
|
|
237
|
+
this._tab_sheet.replaceSync(
|
|
238
|
+
[
|
|
239
|
+
`.tabs{grid-template-columns:repeat(${count},var(--rl-tab-width,1fr))}`,
|
|
240
|
+
`.tabs slot[name="tab"]{grid-column:${index + 1}}`,
|
|
241
|
+
`[part~="title"]::before{content:var(${title_variable(slot)}, ${css_string(slot)})}`,
|
|
242
|
+
].join("\n"),
|
|
243
|
+
);
|
|
242
244
|
};
|
|
243
245
|
}
|