regular-layout 0.4.0 → 0.6.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.
@@ -17,7 +17,10 @@
17
17
  */
18
18
 
19
19
  import { EMPTY_PANEL } from "./core/types.ts";
20
- import { create_css_grid_layout } from "./layout/generate_grid.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,
@@ -106,6 +109,14 @@ export interface PointerEventCoordinates {
106
109
  * latter implementation would require synchronizing the light DOM
107
110
  * and shadow DOM slots/slotted children continuously.
108
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
+ *
109
120
  */
110
121
  export class RegularLayout extends HTMLElement {
111
122
  private _shadowRoot: ShadowRoot;
@@ -118,6 +129,7 @@ export class RegularLayout extends HTMLElement {
118
129
  private _physics: Physics;
119
130
  private _presizeQueue: PresizeQueue;
120
131
  private _overlayController: OverlayController;
132
+ private _maximized?: string;
121
133
 
122
134
  constructor() {
123
135
  super();
@@ -253,6 +265,36 @@ export class RegularLayout extends HTMLElement {
253
265
  await this.restore(remove_child(this._panel, name));
254
266
  };
255
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 } }));
296
+ };
297
+
256
298
  /**
257
299
  * Retrieves a panel by name from the layout tree.
258
300
  *
@@ -286,6 +328,42 @@ export class RegularLayout extends HTMLElement {
286
328
  await this.restore(EMPTY_PANEL);
287
329
  };
288
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
+
289
367
  /**
290
368
  * Restores the layout from a saved state synchronously, without
291
369
  * dispatching the `regular-layout-before-resize` event.
@@ -293,6 +371,7 @@ export class RegularLayout extends HTMLElement {
293
371
  * @param layout - The layout tree to restore
294
372
  */
295
373
  restoreSync = (layout: Layout, _is_flattened: boolean = false) => {
374
+ this._maximized = undefined;
296
375
  this._panel = !_is_flattened ? flatten(layout) : layout;
297
376
  const css = create_css_grid_layout(this._panel, undefined, this._physics);
298
377
  this._stylesheet.replaceSync(css);
@@ -320,6 +399,7 @@ export class RegularLayout extends HTMLElement {
320
399
  restore = async (layout: Layout, _is_flattened: boolean = false) => {
321
400
  const panel = !_is_flattened ? flatten(layout) : layout;
322
401
  await this._presizeQueue.run(panel, () => {
402
+ this._maximized = undefined;
323
403
  this._panel = panel;
324
404
  const css = create_css_grid_layout(this._panel, undefined, this._physics);
325
405
  this._stylesheet.replaceSync(css);
@@ -327,6 +407,7 @@ export class RegularLayout extends HTMLElement {
327
407
  const event = new CustomEvent<Layout>(event_name, {
328
408
  detail: this._panel,
329
409
  });
410
+
330
411
  this.dispatchEvent(event);
331
412
  });
332
413
  };
@@ -528,6 +609,10 @@ export class RegularLayout extends HTMLElement {
528
609
  };
529
610
 
530
611
  private onPointerDown = (event: PointerEvent) => {
612
+ if (event.button !== 0) {
613
+ return;
614
+ }
615
+
531
616
  if (!this._physics.GRID_DIVIDER_CHECK_TARGET || event.target === this) {
532
617
  const [col, row, rect] = this.relativeCoordinates(event);
533
618
  const size = this._physics.GRID_DIVIDER_SIZE;
@@ -23,12 +23,15 @@ regular-layout.borland regular-layout-frame {
23
23
  position: relative;
24
24
  box-sizing: border-box;
25
25
  margin: 4px;
26
- background: #0000aa;
27
- border: 1px solid #00aaaa;
28
- box-shadow: 1px 1px 0 #000000;
29
26
  }
30
27
 
28
+ /* Panel chrome lives on the content (only the selected frame shows it), so the
29
+ overlapping frame box stays transparent and every stacked tab is visible. */
31
30
  regular-layout.borland regular-layout-frame::part(container) {
31
+ background: #0000aa;
32
+ border: 1px solid #00aaaa;
33
+ box-shadow: 1px 1px 0 #000000;
34
+ box-sizing: border-box;
32
35
  padding: 4px 6px;
33
36
  color: #ffff55;
34
37
  }
@@ -59,7 +62,6 @@ regular-layout.borland regular-layout-frame::part(titlebar) {
59
62
  align-items: stretch;
60
63
  padding-right: 0;
61
64
  height: 22px;
62
- background: #0000aa;
63
65
  }
64
66
 
65
67
  regular-layout.borland regular-layout-frame::part(tab) {
@@ -23,13 +23,16 @@ regular-layout.chicago regular-layout-frame {
23
23
  position: relative;
24
24
  box-sizing: border-box;
25
25
  margin: 12px;
26
+ }
27
+
28
+ /* Panel chrome lives on the content (only the selected frame shows it), so the
29
+ overlapping frame box stays transparent and every stacked tab is visible. */
30
+ regular-layout.chicago regular-layout-frame::part(container) {
26
31
  background: #c0c0c0;
27
32
  border-width: 2px;
28
33
  border-color: #ffffff #808080 #808080 #ffffff;
29
34
  border-style: solid;
30
- }
31
-
32
- regular-layout.chicago regular-layout-frame::part(container) {
35
+ box-sizing: border-box;
33
36
  padding: 6px;
34
37
  }
35
38
 
@@ -58,8 +61,8 @@ regular-layout.chicago regular-layout-frame::part(titlebar) {
58
61
  }
59
62
 
60
63
  regular-layout.chicago regular-layout-frame::part(tab) {
64
+ box-sizing: border-box;
61
65
  display: flex;
62
- flex: 1 1 150px;
63
66
  align-items: center;
64
67
  padding: 0 3px 0 8px;
65
68
  cursor: pointer;
@@ -69,6 +72,11 @@ regular-layout.chicago regular-layout-frame::part(tab) {
69
72
  font-family: "Tahoma", "Arial", sans-serif;
70
73
  font-weight: bold;
71
74
  font-size: 11px;
75
+ /* Raised bevel - the frame box no longer carries the border in the
76
+ overlap model, so each tab frames itself. */
77
+ border-width: 2px;
78
+ border-color: #ffffff #808080 #808080 #ffffff;
79
+ border-style: solid;
72
80
  }
73
81
 
74
82
  regular-layout.chicago regular-layout-frame::part(active-tab) {
@@ -21,13 +21,16 @@ regular-layout.fluxbox regular-layout-frame {
21
21
  position: relative;
22
22
  box-sizing: border-box;
23
23
  margin: 8px;
24
- background: #ffffff;
25
- border: 1px solid #9dacbe;
26
- box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.15);
27
24
  }
28
25
 
26
+ /* Panel chrome lives on the content (only the selected frame shows it), so the
27
+ overlapping frame box stays transparent and every stacked tab is visible. */
29
28
  regular-layout.fluxbox regular-layout-frame::part(container) {
29
+ box-sizing: border-box;
30
30
  padding: 6px;
31
+ background: #ffffff;
32
+ border: 1px solid #9dacbe;
33
+ box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.15);
31
34
  }
32
35
 
33
36
  regular-layout.fluxbox regular-layout-frame::part(close) {
package/themes/gibson.css CHANGED
@@ -20,12 +20,9 @@ regular-layout.gibson {
20
20
 
21
21
  /* Frame */
22
22
  regular-layout.gibson regular-layout-frame {
23
- --titlebar-height: 28px;
24
23
  position: relative;
25
24
  box-sizing: border-box;
26
25
  margin: 8px;
27
- background: rgba(10, 10, 26, 0.85);
28
- backdrop-filter: blur(4px);
29
26
  color: white;
30
27
  text-shadow:
31
28
  0 0 5px #fff,
@@ -33,8 +30,13 @@ regular-layout.gibson regular-layout-frame {
33
30
  0 0 20px #fff;
34
31
  }
35
32
 
33
+ /* Panel chrome lives on the content (only the selected frame shows it), so the
34
+ overlapping frame box stays transparent and every stacked tab is visible. */
36
35
  regular-layout.gibson regular-layout-frame::part(container) {
37
36
  padding: 6px;
37
+ background: rgba(10, 10, 26, 0.85);
38
+ backdrop-filter: blur(4px);
39
+ box-sizing: border-box;
38
40
  }
39
41
 
40
42
  regular-layout.gibson regular-layout-frame::part(active-close) {
@@ -70,19 +72,11 @@ regular-layout.gibson regular-layout-frame::part(close) {
70
72
  }
71
73
 
72
74
  regular-layout.gibson regular-layout-frame::part(titlebar) {
73
- display: flex;
74
- justify-content: flex-end;
75
- align-items: stretch;
76
- padding-left: 12px;
77
75
  height: 32px;
78
-
79
- /* border-bottom: 1px solid rgba(0, 255, 255, 0.3); */
80
76
  }
81
77
 
82
78
  regular-layout.gibson regular-layout-frame::part(tab) {
83
79
  display: flex;
84
- flex: 1 1 150px;
85
- max-width: 200px;
86
80
  align-items: center;
87
81
  padding: 0 5px 0 12px;
88
82
  cursor: pointer;
@@ -131,6 +125,7 @@ regular-layout.gibson regular-layout-frame.overlay {
131
125
  inset 0 0 20px rgba(255, 0, 255, 0.2);
132
126
  margin: 0;
133
127
  transition:
128
+ margin 0.075s ease-out,
134
129
  top 0.075s ease-out,
135
130
  height 0.075s ease-out,
136
131
  width 0.075s ease-out,
@@ -138,18 +133,9 @@ regular-layout.gibson regular-layout-frame.overlay {
138
133
  }
139
134
 
140
135
  regular-layout.gibson regular-layout-frame::part(container) {
141
- display: none;
142
136
  border: 1px solid #00ffff;
143
- border-radius: 12px 0 12px 0;
144
- }
145
-
146
- regular-layout.gibson regular-layout-frame::part(titlebar) {
147
- display: none;
148
- }
149
-
150
- regular-layout.gibson regular-layout-frame:not(.overlay)::part(container),
151
- regular-layout.gibson regular-layout-frame:not(.overlay)::part(titlebar) {
152
- display: flex;
137
+ /* Square top so the tab strip sits flush; keep the asymmetric bottom. */
138
+ border-radius: 0 0 12px 0;
153
139
  }
154
140
 
155
141
  /* Cyberpunk color variations */
package/themes/hotdog.css CHANGED
@@ -21,13 +21,16 @@ regular-layout.hotdog {
21
21
  /* Frame */
22
22
  regular-layout.hotdog regular-layout-frame {
23
23
  margin: 12px;
24
+ }
25
+
26
+ /* Panel chrome lives on the content (only the selected frame shows it), so the
27
+ overlapping frame box stays transparent and every stacked tab is visible. */
28
+ regular-layout.hotdog regular-layout-frame::part(container) {
24
29
  background: #ffff00;
25
30
  border-width: 2px;
26
31
  border-color: #ff6b6b #8b0000 #8b0000 #ff6b6b;
27
32
  border-style: solid;
28
- }
29
-
30
- regular-layout.hotdog regular-layout-frame::part(container) {
33
+ box-sizing: border-box;
31
34
  padding: 6px;
32
35
  }
33
36
 
@@ -57,8 +60,8 @@ regular-layout.hotdog regular-layout-frame::part(titlebar) {
57
60
  }
58
61
 
59
62
  regular-layout.hotdog regular-layout-frame::part(tab) {
63
+ box-sizing: border-box;
60
64
  display: flex;
61
- flex: 1 1 150px;
62
65
  align-items: center;
63
66
  padding: 0 3px 0 8px;
64
67
  cursor: pointer;
@@ -68,6 +71,11 @@ regular-layout.hotdog regular-layout-frame::part(tab) {
68
71
  font-family: "Tahoma", "Arial", sans-serif;
69
72
  font-weight: bold;
70
73
  font-size: 11px;
74
+ /* Raised bevel - the frame box no longer carries the border in the
75
+ overlap model, so each tab frames itself. */
76
+ border-width: 2px;
77
+ border-color: #ff6b6b #8b0000 #8b0000 #ff6b6b;
78
+ border-style: solid;
71
79
  }
72
80
 
73
81
  regular-layout.hotdog regular-layout-frame::part(active-tab) {
@@ -87,6 +95,7 @@ regular-layout.hotdog:has(.overlay) > .overlay {
87
95
  regular-layout.hotdog regular-layout-frame.overlay {
88
96
  margin: 0;
89
97
  transition:
98
+ margin 0.1 ease-in-out,
90
99
  top 0.1s ease-in-out,
91
100
  height 0.1s ease-in-out,
92
101
  width 0.1s ease-in-out,
package/themes/lorax.css CHANGED
@@ -10,50 +10,62 @@
10
10
  * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
  */
12
12
 
13
+ /*
14
+ * In the own-tab model, the frames of a stack overlap in one cell: each frame
15
+ * renders only its own tab (the engine tiles them into the shared titlebar) and
16
+ * only the selected frame shows its content. So this theme styles appearance,
17
+ * not layout - panel chrome lives on `::part(container)` (shown only for the
18
+ * selected frame) rather than the overlapping frame box, and the titlebar strip
19
+ * is transparent so every stacked frame's tab shows through.
20
+ */
21
+
13
22
  regular-layout.lorax {
14
23
  font-family:
15
24
  "ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "Consolas",
16
25
  "Liberation Mono", monospace;
17
26
  }
18
27
 
19
- /* Frame */
20
28
  regular-layout.lorax regular-layout-frame {
21
- margin: 3px;
22
- margin-top: 27px;
23
- border-radius: 0 6px 6px 6px;
24
- border: 1px solid #666;
25
- box-shadow: 0px 6px 6px -4px rgba(150, 150, 180);
29
+ --rl-tab-width: 200px;
26
30
  }
27
31
 
32
+ /* Panel content chrome (only the selected frame's container is rendered). */
28
33
  regular-layout.lorax regular-layout-frame::part(container) {
34
+ box-sizing: border-box;
35
+ margin: 0 3px 3px 3px;
29
36
  padding: 6px;
37
+ /* Square top so the tab strip sits flush; rounded bottom. */
38
+ border-radius: 0 6px 6px 6px;
39
+ border: 1px solid #666;
40
+ box-shadow: 0px 6px 6px -4px rgba(150, 150, 180);
30
41
  }
31
42
 
43
+ /* Transparent strip; the engine tiles the per-frame tabs across it. */
32
44
  regular-layout.lorax regular-layout-frame::part(titlebar) {
33
- display: flex;
34
- align-items: stretch;
35
- margin-left: -1px;
36
- margin-right: -1px;
37
- margin-bottom: 0px;
38
- margin-top: -24px;
39
- padding-right: 6px;
45
+ margin: 3px 6px 0 3px;
46
+ align-items: end;
40
47
  }
41
48
 
42
49
  regular-layout.lorax regular-layout-frame::part(tab) {
43
- display: flex;
44
- flex: 1 1 150px;
50
+ box-sizing: border-box;
45
51
  align-items: center;
46
- text-align: center;
52
+ justify-content: center;
47
53
  font-size: 10px;
48
54
  padding: 0 6px;
55
+ margin-right: 2px;
49
56
  cursor: pointer;
50
- max-width: 150px;
51
- text-overflow: ellipsis;
52
57
  border: 1px solid #666;
58
+ border-bottom: none;
53
59
  border-radius: 6px 6px 0 0;
54
60
  opacity: 0.5;
55
61
  }
56
62
 
63
+ regular-layout.lorax regular-layout-frame::part(title) {
64
+ overflow: hidden;
65
+ text-overflow: ellipsis;
66
+ white-space: nowrap;
67
+ }
68
+
57
69
  regular-layout.lorax regular-layout-frame::part(active-tab) {
58
70
  opacity: 1;
59
71
  }
@@ -71,67 +83,52 @@ regular-layout.lorax regular-layout-frame::part(close):hover {
71
83
  background-color: rgba(255, 0, 0, 0.2);
72
84
  }
73
85
 
74
- /* Frame in Overlay Mode */
75
86
  regular-layout.lorax regular-layout-frame.overlay {
76
- background-color: rgba(0, 0, 0, 0.2);
77
- border: 1px dashed rgb(0, 0, 0);
78
- border-radius: 6px;
79
- margin: 3px;
80
- box-shadow: none;
81
87
  transition:
88
+ maring 0.1s ease-in-out,
82
89
  top 0.1s ease-in-out,
83
90
  height 0.1s ease-in-out,
84
91
  width 0.1s ease-in-out,
85
92
  left 0.1s ease-in-out;
86
93
  }
87
94
 
88
- regular-layout.lorax regular-layout-frame::part(container),
89
- regular-layout.lorax regular-layout-frame::part(titlebar) {
90
- display: none;
91
- }
92
-
93
- regular-layout.lorax regular-layout-frame:not(.overlay)::part(container),
94
- regular-layout.lorax regular-layout-frame:not(.overlay)::part(titlebar) {
95
- display: flex;
96
- }
97
-
98
- /* Colors */
99
- regular-layout.lorax :nth-child(8n + 1),
95
+ /* Colors (per panel position) - the content area and its tab share a hue. */
96
+ regular-layout.lorax :nth-child(8n + 1)::part(container),
100
97
  regular-layout.lorax :nth-child(8n + 1)::part(tab) {
101
98
  background-color: #ffadadff;
102
99
  }
103
100
 
104
- regular-layout.lorax :nth-child(8n + 2),
101
+ regular-layout.lorax :nth-child(8n + 2)::part(container),
105
102
  regular-layout.lorax :nth-child(8n + 2)::part(tab) {
106
103
  background-color: #ffd6a5ff;
107
104
  }
108
105
 
109
- regular-layout.lorax :nth-child(8n + 3),
106
+ regular-layout.lorax :nth-child(8n + 3)::part(container),
110
107
  regular-layout.lorax :nth-child(8n + 3)::part(tab) {
111
108
  background-color: #fdffb6ff;
112
109
  }
113
110
 
114
- regular-layout.lorax :nth-child(8n + 4),
111
+ regular-layout.lorax :nth-child(8n + 4)::part(container),
115
112
  regular-layout.lorax :nth-child(8n + 4)::part(tab) {
116
113
  background-color: #caffbfff;
117
114
  }
118
115
 
119
- regular-layout.lorax :nth-child(8n + 5),
116
+ regular-layout.lorax :nth-child(8n + 5)::part(container),
120
117
  regular-layout.lorax :nth-child(8n + 5)::part(tab) {
121
118
  background-color: #9bf6ffff;
122
119
  }
123
120
 
124
- regular-layout.lorax :nth-child(8n + 6),
121
+ regular-layout.lorax :nth-child(8n + 6)::part(container),
125
122
  regular-layout.lorax :nth-child(8n + 6)::part(tab) {
126
123
  background-color: #a0c4ffff;
127
124
  }
128
125
 
129
- regular-layout.lorax :nth-child(8n + 7),
126
+ regular-layout.lorax :nth-child(8n + 7)::part(container),
130
127
  regular-layout.lorax :nth-child(8n + 7)::part(tab) {
131
128
  background-color: #bdb2ffff;
132
129
  }
133
130
 
134
- regular-layout.lorax :nth-child(8n + 8),
131
+ regular-layout.lorax :nth-child(8n + 8)::part(container),
135
132
  regular-layout.lorax :nth-child(8n + 8)::part(tab) {
136
133
  background-color: #ffc6ffff;
137
134
  }