regular-layout 0.1.0 → 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.
Files changed (42) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +6 -6
  3. package/dist/extensions.d.ts +5 -4
  4. package/dist/index.d.ts +3 -3
  5. package/dist/index.js +15 -10
  6. package/dist/index.js.map +4 -4
  7. package/dist/{common → layout}/calculate_edge.d.ts +2 -2
  8. package/dist/{common → layout}/calculate_intersect.d.ts +7 -9
  9. package/dist/{common → layout}/constants.d.ts +15 -1
  10. package/dist/{common → layout}/flatten.d.ts +1 -1
  11. package/dist/{common → layout}/generate_grid.d.ts +3 -3
  12. package/dist/layout/generate_overlay.d.ts +2 -0
  13. package/dist/{common → layout}/insert_child.d.ts +3 -2
  14. package/dist/{common → layout}/redistribute_panel_sizes.d.ts +1 -1
  15. package/dist/{common → layout}/remove_child.d.ts +1 -1
  16. package/dist/{common/layout_config.d.ts → layout/types.d.ts} +6 -10
  17. package/dist/regular-layout-frame.d.ts +1 -4
  18. package/dist/regular-layout-tab.d.ts +26 -0
  19. package/dist/regular-layout.d.ts +23 -18
  20. package/package.json +9 -7
  21. package/src/extensions.ts +10 -4
  22. package/src/index.ts +3 -7
  23. package/src/layout/calculate_edge.ts +209 -0
  24. package/src/{common → layout}/calculate_intersect.ts +59 -101
  25. package/src/{common → layout}/constants.ts +18 -1
  26. package/src/{common → layout}/flatten.ts +1 -1
  27. package/src/{common → layout}/generate_grid.ts +76 -106
  28. package/src/{common → layout}/generate_overlay.ts +24 -12
  29. package/src/{common → layout}/insert_child.ts +105 -51
  30. package/src/{common → layout}/redistribute_panel_sizes.ts +1 -1
  31. package/src/{common → layout}/remove_child.ts +2 -2
  32. package/src/{common/layout_config.ts → layout/types.ts} +6 -19
  33. package/src/regular-layout-frame.ts +34 -71
  34. package/src/regular-layout-tab.ts +103 -0
  35. package/src/regular-layout.ts +190 -141
  36. package/themes/chicago.css +89 -0
  37. package/themes/fluxbox.css +110 -0
  38. package/themes/gibson.css +264 -0
  39. package/themes/hotdog.css +88 -0
  40. package/themes/lorax.css +129 -0
  41. package/dist/common/generate_overlay.d.ts +0 -2
  42. package/src/common/calculate_edge.ts +0 -104
@@ -16,23 +16,28 @@
16
16
  * @packageDocumentation
17
17
  */
18
18
 
19
- import { EMPTY_PANEL, iter_panel_children } from "./common/layout_config.ts";
20
- 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";
21
21
  import type {
22
22
  LayoutPath,
23
23
  Layout,
24
24
  LayoutDivider,
25
25
  TabLayout,
26
26
  OverlayMode,
27
- } from "./common/layout_config.ts";
28
- import { calculate_intersection } from "./common/calculate_intersect.ts";
29
- import { remove_child } from "./common/remove_child.ts";
30
- import { insert_child } from "./common/insert_child.ts";
31
- import { redistribute_panel_sizes } from "./common/redistribute_panel_sizes.ts";
32
- import { updateOverlaySheet } from "./common/generate_overlay.ts";
33
- import { calculate_edge } from "./common/calculate_edge.ts";
34
- import { flatten } from "./common/flatten.ts";
35
- import { OVERLAY_CLASSNAME, OVERLAY_DEFAULT } from "./common/constants.ts";
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";
36
41
 
37
42
  /**
38
43
  * A Web Component that provides a resizable panel layout system.
@@ -43,8 +48,8 @@ import { OVERLAY_CLASSNAME, OVERLAY_DEFAULT } from "./common/constants.ts";
43
48
  * @example
44
49
  * ```html
45
50
  * <regular-layout>
46
- * <div slot="sidebar">Sidebar content</div>
47
- * <div slot="main">Main content</div>
51
+ * <div name="sidebar">Sidebar content</div>
52
+ * <div name="main">Main content</div>
48
53
  * </regular-layout>
49
54
  * ```
50
55
  *
@@ -71,19 +76,34 @@ export class RegularLayout extends HTMLElement {
71
76
  private _shadowRoot: ShadowRoot;
72
77
  private _panel: Layout;
73
78
  private _stylesheet: CSSStyleSheet;
74
- private _dragPath?: [LayoutDivider, number, number];
75
- private _slots: Map<string, HTMLSlotElement>;
76
- private _unslotted_slot: HTMLSlotElement;
79
+ private _cursor_stylesheet: CSSStyleSheet;
80
+ private _drag_target?: [LayoutDivider, number, number];
81
+ private _cursor_override: boolean;
82
+ private _dimensions?: { box: DOMRect; style: CSSStyleDeclaration };
77
83
 
78
84
  constructor() {
79
85
  super();
80
86
  this._panel = structuredClone(EMPTY_PANEL);
81
- this._stylesheet = new CSSStyleSheet();
82
- this._unslotted_slot = document.createElement("slot");
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
83
98
  this._shadowRoot = this.attachShadow({ mode: "open" });
84
- this._shadowRoot.adoptedStyleSheets = [this._stylesheet];
85
- this._shadowRoot.appendChild(this._unslotted_slot);
86
- this._slots = new Map();
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
+ ];
87
107
  }
88
108
 
89
109
  connectedCallback() {
@@ -98,6 +118,33 @@ export class RegularLayout extends HTMLElement {
98
118
  this.removeEventListener("pointermove", this.onPointerMove);
99
119
  }
100
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
+
101
148
  /**
102
149
  * Sets the visual overlay state during drag-and-drop operations.
103
150
  * Displays a preview of where a panel would be placed at the given coordinates.
@@ -119,35 +166,29 @@ export class RegularLayout extends HTMLElement {
119
166
  className: string = OVERLAY_CLASSNAME,
120
167
  mode: OverlayMode = OVERLAY_DEFAULT,
121
168
  ) => {
122
- let panel = this._panel;
123
- if (mode === "absolute") {
124
- panel = remove_child(panel, slot);
125
- this.updateSlots(panel, slot);
126
- this._slots.get(slot)?.assignedElements()[0]?.classList.add(className);
127
- }
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, false);
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 = calculate_edge(col, row, panel, slot, drop_target);
133
- if (mode === "grid") {
134
- const path: [string, string] = [slot, drop_target.slot];
135
- const css = create_css_grid_layout(this._panel, false, path);
136
- this._stylesheet.replaceSync(css);
137
- } else if (mode === "absolute") {
138
- const grid_css = create_css_grid_layout(panel);
139
- const overlay_css = updateOverlaySheet(slot, { ...drop_target, box });
140
- this._stylesheet.replaceSync([grid_css, overlay_css].join("\n"));
141
- }
142
- } else {
143
- const css = `${create_css_grid_layout(panel)}}`;
144
- this._stylesheet.replaceSync(css);
177
+ drop_target = calculate_edge(col, row, panel, slot, drop_target, box);
145
178
  }
146
179
 
147
- const event = new CustomEvent("regular-layout-before-update", {
148
- detail: panel,
149
- });
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);
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"));
188
+ }
150
189
 
190
+ const event_name = `${CUSTOM_EVENT_NAME_PREFIX}-before-update`;
191
+ const event = new CustomEvent<Layout>(event_name, { detail: panel });
151
192
  this.dispatchEvent(event);
152
193
  };
153
194
 
@@ -166,21 +207,17 @@ export class RegularLayout extends HTMLElement {
166
207
  clearOverlayState = (
167
208
  x: number,
168
209
  y: number,
169
- drag_target: LayoutPath<unknown>,
210
+ drag_target: LayoutPath<Layout>,
170
211
  className: string = OVERLAY_CLASSNAME,
171
- mode: OverlayMode = OVERLAY_DEFAULT,
172
212
  ) => {
173
213
  let panel = this._panel;
174
- if (mode === "absolute") {
175
- panel = remove_child(panel, drag_target.slot);
176
- this._slots
177
- .get(drag_target.slot)
178
- ?.assignedElements()[0]
179
- ?.classList.remove(className);
180
- }
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);
181
218
 
182
- const [col, row, _] = this.relativeCoordinates(x, y);
183
- let drop_target = calculate_intersection(col, row, panel, false);
219
+ const [col, row, box] = this.relativeCoordinates(x, y, false);
220
+ let drop_target = calculate_intersection(col, row, panel);
184
221
  if (drop_target) {
185
222
  drop_target = calculate_edge(
186
223
  col,
@@ -188,19 +225,21 @@ export class RegularLayout extends HTMLElement {
188
225
  panel,
189
226
  drag_target.slot,
190
227
  drop_target,
228
+ box,
191
229
  );
192
230
  }
193
231
 
194
232
  const { path, orientation } = drop_target ? drop_target : drag_target;
195
- this.restore(
196
- insert_child(
197
- panel,
198
- drag_target.slot,
199
- path,
200
- orientation,
201
- !drop_target?.is_edge,
202
- ),
203
- );
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;
241
+
242
+ this.restore(new_layout);
204
243
  };
205
244
 
206
245
  /**
@@ -208,9 +247,25 @@ export class RegularLayout extends HTMLElement {
208
247
  *
209
248
  * @param name - Unique identifier for the new panel.
210
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`)
211
255
  */
212
- insertPanel = (name: string, path: number[] = []) => {
213
- this.restore(insert_child(this._panel, name, path));
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));
214
269
  };
215
270
 
216
271
  /**
@@ -247,27 +302,6 @@ export class RegularLayout extends HTMLElement {
247
302
  return null;
248
303
  };
249
304
 
250
- /**
251
- * Determines which panel is at a given screen coordinate.
252
- *
253
- * @param column - X coordinate in screen pixels.
254
- * @param row - Y coordinate in screen pixels.
255
- * @returns Panel information if a panel is at that position, null otherwise.
256
- */
257
- calculateIntersect = (
258
- x: number,
259
- y: number,
260
- check_dividers: boolean = false,
261
- ): LayoutPath<DOMRect> | null => {
262
- const [col, row, box] = this.relativeCoordinates(x, y);
263
- const panel = calculate_intersection(col, row, this._panel, check_dividers);
264
- if (panel?.type === "layout-path") {
265
- return { ...panel, box };
266
- }
267
-
268
- return null;
269
- };
270
-
271
305
  /**
272
306
  * Clears the entire layout, unslotting all panels.
273
307
  */
@@ -291,11 +325,8 @@ export class RegularLayout extends HTMLElement {
291
325
  this._panel = !_is_flattened ? flatten(layout) : layout;
292
326
  const css = create_css_grid_layout(this._panel);
293
327
  this._stylesheet.replaceSync(css);
294
- this.updateSlots(this._panel);
295
- const event = new CustomEvent("regular-layout-update", {
296
- detail: this._panel,
297
- });
298
-
328
+ const event_name = `${CUSTOM_EVENT_NAME_PREFIX}-update`;
329
+ const event = new CustomEvent<Layout>(event_name, { detail: this._panel });
299
330
  this.dispatchEvent(event);
300
331
  };
301
332
 
@@ -331,44 +362,41 @@ export class RegularLayout extends HTMLElement {
331
362
  relativeCoordinates = (
332
363
  clientX: number,
333
364
  clientY: number,
334
- ): [number, number, DOMRect] => {
335
- const box = this.getBoundingClientRect();
336
- const col = (clientX - box.left) / (box.right - box.left);
337
- const row = (clientY - box.top) / (box.bottom - box.top);
338
- return [col, row, box];
339
- };
340
-
341
- private updateSlots = (layout: Layout, overlay?: string) => {
342
- const old = new Set(this._slots.keys());
343
- if (overlay) {
344
- old.delete(overlay);
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
+ };
345
372
  }
346
373
 
347
- for (const name of iter_panel_children(layout)) {
348
- old.delete(name);
349
- if (!this._slots.has(name)) {
350
- const slot = document.createElement("slot");
351
- slot.setAttribute("name", name);
352
- this._shadowRoot.appendChild(slot);
353
- this._slots.set(name, slot);
354
- }
355
- }
356
-
357
- for (const key of old) {
358
- const child = this._slots.get(key);
359
- if (child) {
360
- this._shadowRoot.removeChild(child);
361
- this._slots.delete(key);
362
- }
363
- }
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];
364
388
  };
365
389
 
366
390
  private onPointerDown = (event: PointerEvent) => {
367
391
  if (event.target === this) {
368
- const [col, row] = this.relativeCoordinates(event.clientX, event.clientY);
369
- const hit = calculate_intersection(col, row, this._panel);
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);
370
398
  if (hit && hit.type !== "layout-path") {
371
- this._dragPath = [hit, col, row];
399
+ this._drag_target = [hit, col, row];
372
400
  this.setPointerCapture(event.pointerId);
373
401
  event.preventDefault();
374
402
  }
@@ -376,31 +404,52 @@ export class RegularLayout extends HTMLElement {
376
404
  };
377
405
 
378
406
  private onPointerMove = (event: PointerEvent) => {
379
- if (this._dragPath) {
380
- const [col, row] = this.relativeCoordinates(event.clientX, event.clientY);
381
- const old_panel = this._panel;
382
- const [{ path, type }, old_col, old_row] = this._dragPath;
407
+ if (this._drag_target) {
408
+ const [col, row] = this.relativeCoordinates(
409
+ event.clientX,
410
+ event.clientY,
411
+ false,
412
+ );
413
+
414
+ const [{ path, type }, old_col, old_row] = this._drag_target;
383
415
  const offset = type === "horizontal" ? old_col - col : old_row - row;
384
- const panel = redistribute_panel_sizes(old_panel, path, offset);
416
+ const panel = redistribute_panel_sizes(this._panel, path, offset);
385
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("");
386
436
  }
387
437
  };
388
438
 
389
439
  private onPointerUp = (event: PointerEvent) => {
390
- if (this._dragPath) {
440
+ if (this._drag_target) {
391
441
  this.releasePointerCapture(event.pointerId);
392
- const [col, row] = this.relativeCoordinates(event.clientX, event.clientY);
393
- const old_panel = this._panel;
394
- const [{ path }, old_col, old_row] = this._dragPath;
395
- if (this._dragPath[0].type === "horizontal") {
396
- const panel = redistribute_panel_sizes(old_panel, path, old_col - col);
397
- this.restore(panel, true);
398
- } else {
399
- const panel = redistribute_panel_sizes(old_panel, path, old_row - row);
400
- this.restore(panel, true);
401
- }
442
+ const [col, row] = this.relativeCoordinates(
443
+ event.clientX,
444
+ event.clientY,
445
+ false,
446
+ );
402
447
 
403
- this._dragPath = undefined;
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;
404
453
  }
405
454
  };
406
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
+ }
@@ -0,0 +1,110 @@
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.fluxbox {
14
+ background-color: #DBDFE6;
15
+ font-family: "ui-sans-serif", "Helvetica", "Arial", sans-serif;
16
+ padding: 16px;
17
+ }
18
+
19
+ /* Frame */
20
+ regular-layout.fluxbox regular-layout-frame {
21
+ position: relative;
22
+ box-sizing: border-box;
23
+ margin: 8px;
24
+ background: #FFFFFF;
25
+ border: 1px solid #9DACBE;
26
+ box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.15);
27
+ }
28
+
29
+ regular-layout.fluxbox regular-layout-frame::part(close) {
30
+ border: 1px solid #8A96A3;
31
+ height: 14px;
32
+ background: linear-gradient(to bottom, #E8ECEF 0%, #CDD5DD 100%);
33
+ align-self: center;
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ padding: 0px;
38
+ width: 14px;
39
+ margin-right: 2px;
40
+ }
41
+
42
+ regular-layout.fluxbox regular-layout-frame::part(close):hover {
43
+ background: linear-gradient(to bottom, #F0F3F5 0%, #D8DFE6 100%);
44
+ }
45
+
46
+ regular-layout.fluxbox regular-layout-frame::part(close):active {
47
+ background: linear-gradient(to bottom, #C5CFD9 0%, #B3BEC9 100%);
48
+ }
49
+
50
+ regular-layout.fluxbox regular-layout-frame::part(close):before {
51
+ content: "×";
52
+ font-size: 14px;
53
+ font-weight: normal;
54
+ color: #444444;
55
+ line-height: 1;
56
+ }
57
+
58
+ regular-layout.fluxbox regular-layout-frame::part(titlebar) {
59
+ display: flex;
60
+ align-items: stretch;
61
+ padding-right: 0px;
62
+ height: 22px;
63
+ }
64
+
65
+ regular-layout.fluxbox regular-layout-frame::part(tab) {
66
+ display: flex;
67
+ flex: 1 1 150px;
68
+ align-items: center;
69
+ padding: 0 8px;
70
+ cursor: pointer;
71
+ text-overflow: ellipsis;
72
+ background: linear-gradient(to bottom, #C7D1DB 0%, #B3BEC9 100%);
73
+ color: #4A4A4A;
74
+ font-size: 11px;
75
+ font-weight: normal;
76
+ border-right: 1px solid #9DACBE;
77
+ opacity: 0.85;
78
+ }
79
+
80
+ regular-layout.fluxbox regular-layout-frame::part(tab):hover {
81
+ background: linear-gradient(to bottom, #D2DBE4 0%, #BEC9D4 100%);
82
+ }
83
+
84
+ regular-layout.fluxbox regular-layout-frame::part(active-tab) {
85
+ background: linear-gradient(to bottom, #E0E7EF 0%, #D1DAE3 100%);
86
+ color: #1A1A1A;
87
+ opacity: 1;
88
+ font-weight: 500;
89
+ }
90
+
91
+ regular-layout.fluxbox:has(.overlay)>* {
92
+ opacity: 0.7;
93
+ }
94
+
95
+ regular-layout.fluxbox:has(.overlay)>.overlay {
96
+ opacity: 1;
97
+ }
98
+
99
+ /* Frame in Overlay Mode */
100
+ regular-layout.fluxbox regular-layout-frame.overlay {
101
+ margin: 0;
102
+ background-color: rgba(155, 172, 190, 0.25);
103
+ border: 1px solid #6B7C8F;
104
+ box-shadow: none;
105
+ transition:
106
+ top 0.1s ease-in-out,
107
+ height 0.1s ease-in-out,
108
+ width 0.1s ease-in-out,
109
+ left 0.1s ease-in-out;
110
+ }