regular-layout 0.1.0 → 0.2.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.
Files changed (44) 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 +3 -2
  8. package/dist/{common → layout}/calculate_intersect.d.ts +13 -9
  9. package/dist/layout/constants.d.ts +81 -0
  10. package/dist/{common → layout}/flatten.d.ts +1 -1
  11. package/dist/{common → layout}/generate_grid.d.ts +5 -4
  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 +2 -2
  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 +37 -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 +217 -0
  24. package/src/{common → layout}/calculate_intersect.ts +61 -101
  25. package/src/layout/constants.ts +119 -0
  26. package/src/{common → layout}/flatten.ts +1 -1
  27. package/src/{common → layout}/generate_grid.ts +120 -106
  28. package/src/{common → layout}/generate_overlay.ts +26 -12
  29. package/src/{common → layout}/insert_child.ts +105 -51
  30. package/src/{common → layout}/redistribute_panel_sizes.ts +11 -4
  31. package/src/{common → layout}/remove_child.ts +2 -2
  32. package/src/{common/layout_config.ts → layout/types.ts} +7 -19
  33. package/src/regular-layout-frame.ts +40 -74
  34. package/src/regular-layout-tab.ts +103 -0
  35. package/src/regular-layout.ts +260 -148
  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 +130 -0
  41. package/dist/common/constants.d.ts +0 -29
  42. package/dist/common/generate_overlay.d.ts +0 -2
  43. package/src/common/calculate_edge.ts +0 -104
  44. package/src/common/constants.ts +0 -46
@@ -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
+ DEFAULT_PHYSICS,
38
+ type PhysicsUpdate,
39
+ type Physics,
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,36 @@ 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 };
83
+ private _physics: Physics;
77
84
 
78
85
  constructor() {
79
86
  super();
87
+ this._physics = DEFAULT_PHYSICS;
80
88
  this._panel = structuredClone(EMPTY_PANEL);
81
- this._stylesheet = new CSSStyleSheet();
82
- this._unslotted_slot = document.createElement("slot");
89
+
90
+ // Why does this implementation use a `<slot>` at all? We must use
91
+ // `<slot>` and the Shadow DOM to scope the grid CSS rules to each
92
+ // instance of `<regular-layout>` (without e.g. giving them unique
93
+ // `"id"` and injecting into `document,head`), and we can only select
94
+ // `::slotted` light DOM children from `adoptedStyleSheets` on the
95
+ // `ShadowRoot`.
96
+
97
+ // In addition, this model uses a single un-named `<slot>` to host all
98
+ // light-DOM children, and the child's `"name"` attribute to identify
99
+ // its position in the `Layout`. Alternatively, using named
83
100
  this._shadowRoot = this.attachShadow({ mode: "open" });
84
- this._shadowRoot.adoptedStyleSheets = [this._stylesheet];
85
- this._shadowRoot.appendChild(this._unslotted_slot);
86
- this._slots = new Map();
101
+ this._shadowRoot.innerHTML = `<slot></slot>`;
102
+ this._stylesheet = new CSSStyleSheet();
103
+ this._cursor_stylesheet = new CSSStyleSheet();
104
+ this._cursor_override = false;
105
+ this._shadowRoot.adoptedStyleSheets = [
106
+ this._stylesheet,
107
+ this._cursor_stylesheet,
108
+ ];
87
109
  }
88
110
 
89
111
  connectedCallback() {
@@ -98,6 +120,33 @@ export class RegularLayout extends HTMLElement {
98
120
  this.removeEventListener("pointermove", this.onPointerMove);
99
121
  }
100
122
 
123
+ /**
124
+ * Determines which panel is at a given screen coordinate.
125
+ *
126
+ * @param column - X coordinate in screen pixels.
127
+ * @param row - Y coordinate in screen pixels.
128
+ * @returns Panel information if a panel is at that position, null otherwise.
129
+ */
130
+ calculateIntersect = (
131
+ x: number,
132
+ y: number,
133
+ check_dividers: boolean = false,
134
+ ): LayoutPath<Layout> | null => {
135
+ const [col, row, rect] = this.relativeCoordinates(x, y, false);
136
+ const panel = calculate_intersection(
137
+ col,
138
+ row,
139
+ this._panel,
140
+ check_dividers ? { rect, size: this._physics.GRID_DIVIDER_SIZE } : null,
141
+ );
142
+
143
+ if (panel?.type === "layout-path") {
144
+ return { ...panel, layout: this.save() };
145
+ }
146
+
147
+ return null;
148
+ };
149
+
101
150
  /**
102
151
  * Sets the visual overlay state during drag-and-drop operations.
103
152
  * Displays a preview of where a panel would be placed at the given coordinates.
@@ -116,38 +165,48 @@ export class RegularLayout extends HTMLElement {
116
165
  x: number,
117
166
  y: number,
118
167
  { slot }: LayoutPath<unknown>,
119
- className: string = OVERLAY_CLASSNAME,
120
- mode: OverlayMode = OVERLAY_DEFAULT,
168
+ className: string = this._physics.OVERLAY_CLASSNAME,
169
+ mode: OverlayMode = this._physics.OVERLAY_DEFAULT,
121
170
  ) => {
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
- }
171
+ const panel = remove_child(this._panel, slot);
172
+ Array.from(this.children)
173
+ .find((x) => x.getAttribute(this._physics.CHILD_ATTRIBUTE_NAME) === slot)
174
+ ?.classList.add(className);
128
175
 
129
- const [col, row, box] = this.relativeCoordinates(x, y);
130
- let drop_target = calculate_intersection(col, row, panel, false);
176
+ const [col, row, box, style] = this.relativeCoordinates(x, y, false);
177
+ let drop_target = calculate_intersection(col, row, panel);
131
178
  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);
179
+ drop_target = calculate_edge(
180
+ col,
181
+ row,
182
+ panel,
183
+ slot,
184
+ drop_target,
185
+ box,
186
+ this._physics,
187
+ );
145
188
  }
146
189
 
147
- const event = new CustomEvent("regular-layout-before-update", {
148
- detail: panel,
149
- });
190
+ if (mode === "grid" && drop_target) {
191
+ const path: [string, string] = [slot, drop_target?.slot];
192
+ const css = create_css_grid_layout(panel, path, this._physics);
193
+ this._stylesheet.replaceSync(css);
194
+ } else if (mode === "absolute") {
195
+ const grid_css = create_css_grid_layout(panel, undefined, this._physics);
196
+
197
+ const overlay_css = updateOverlaySheet(
198
+ slot,
199
+ box,
200
+ style,
201
+ drop_target,
202
+ this._physics,
203
+ );
204
+
205
+ this._stylesheet.replaceSync([grid_css, overlay_css].join("\n"));
206
+ }
150
207
 
208
+ const event_name = `${this._physics.CUSTOM_EVENT_NAME_PREFIX}-before-update`;
209
+ const event = new CustomEvent<Layout>(event_name, { detail: panel });
151
210
  this.dispatchEvent(event);
152
211
  };
153
212
 
@@ -166,21 +225,21 @@ export class RegularLayout extends HTMLElement {
166
225
  clearOverlayState = (
167
226
  x: number,
168
227
  y: number,
169
- drag_target: LayoutPath<unknown>,
170
- className: string = OVERLAY_CLASSNAME,
171
- mode: OverlayMode = OVERLAY_DEFAULT,
228
+ drag_target: LayoutPath<Layout>,
229
+ className: string = this._physics.OVERLAY_CLASSNAME,
172
230
  ) => {
173
231
  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
- }
181
-
182
- const [col, row, _] = this.relativeCoordinates(x, y);
183
- let drop_target = calculate_intersection(col, row, panel, false);
232
+ panel = remove_child(panel, drag_target.slot);
233
+ Array.from(this.children)
234
+ .find(
235
+ (x) =>
236
+ x.getAttribute(this._physics.CHILD_ATTRIBUTE_NAME) ===
237
+ drag_target.slot,
238
+ )
239
+ ?.classList.remove(className);
240
+
241
+ const [col, row, box] = this.relativeCoordinates(x, y, false);
242
+ let drop_target = calculate_intersection(col, row, panel);
184
243
  if (drop_target) {
185
244
  drop_target = calculate_edge(
186
245
  col,
@@ -188,19 +247,22 @@ export class RegularLayout extends HTMLElement {
188
247
  panel,
189
248
  drag_target.slot,
190
249
  drop_target,
250
+ box,
251
+ this._physics,
191
252
  );
192
253
  }
193
254
 
194
255
  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
- );
256
+ const new_layout = drop_target
257
+ ? insert_child(
258
+ panel,
259
+ drag_target.slot,
260
+ path,
261
+ drop_target?.is_edge ? orientation : undefined,
262
+ )
263
+ : drag_target.layout;
264
+
265
+ this.restore(new_layout);
204
266
  };
205
267
 
206
268
  /**
@@ -208,9 +270,25 @@ export class RegularLayout extends HTMLElement {
208
270
  *
209
271
  * @param name - Unique identifier for the new panel.
210
272
  * @param path - Index path defining where to insert.
273
+ * @param split - Force a split in the layout at the end of `path`
274
+ * regardless if there is a leaf at this position or not. Optionally,
275
+ *. `split` may be your preferred `Orientation`, which will be used by
276
+ * the new `SplitPanel` _if_ there is an option of orientation (e.g. if
277
+ * the layout had no pre-existing `SplitPanel`)
211
278
  */
212
- insertPanel = (name: string, path: number[] = []) => {
213
- this.restore(insert_child(this._panel, name, path));
279
+ insertPanel = (
280
+ name: string,
281
+ path: number[] = [],
282
+ split?: boolean | Orientation,
283
+ ) => {
284
+ let orientation: Orientation | undefined;
285
+ if (typeof split === "boolean" && split) {
286
+ orientation = "horizontal";
287
+ } else if (typeof split === "string") {
288
+ orientation = split;
289
+ }
290
+
291
+ this.restore(insert_child(this._panel, name, path, orientation));
214
292
  };
215
293
 
216
294
  /**
@@ -247,27 +325,6 @@ export class RegularLayout extends HTMLElement {
247
325
  return null;
248
326
  };
249
327
 
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
328
  /**
272
329
  * Clears the entire layout, unslotting all panels.
273
330
  */
@@ -289,13 +346,11 @@ export class RegularLayout extends HTMLElement {
289
346
  */
290
347
  restore = (layout: Layout, _is_flattened: boolean = false) => {
291
348
  this._panel = !_is_flattened ? flatten(layout) : layout;
292
- const css = create_css_grid_layout(this._panel);
293
- this._stylesheet.replaceSync(css);
294
- this.updateSlots(this._panel);
295
- const event = new CustomEvent("regular-layout-update", {
296
- detail: this._panel,
297
- });
349
+ const css = create_css_grid_layout(this._panel, undefined, this._physics);
298
350
 
351
+ this._stylesheet.replaceSync(css);
352
+ const event_name = `${this._physics.CUSTOM_EVENT_NAME_PREFIX}-update`;
353
+ const event = new CustomEvent<Layout>(event_name, { detail: this._panel });
299
354
  this.dispatchEvent(event);
300
355
  };
301
356
 
@@ -315,6 +370,27 @@ export class RegularLayout extends HTMLElement {
315
370
  return structuredClone(this._panel);
316
371
  };
317
372
 
373
+ /**
374
+ * Override this instance's global constants.
375
+ *
376
+ * @param physics
377
+ */
378
+ restorePhysics(physics: PhysicsUpdate) {
379
+ this._physics = Object.freeze({
380
+ ...this._physics,
381
+ ...physics,
382
+ });
383
+ }
384
+
385
+ /**
386
+ * Get this instance's constants.
387
+ *
388
+ * @returns The current constants
389
+ */
390
+ savePhysics(): Physics {
391
+ return this._physics;
392
+ }
393
+
318
394
  /**
319
395
  * Converts screen coordinates to relative layout coordinates.
320
396
  *
@@ -331,44 +407,44 @@ export class RegularLayout extends HTMLElement {
331
407
  relativeCoordinates = (
332
408
  clientX: number,
333
409
  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);
410
+ recalculate_bounds: boolean = true,
411
+ ): [number, number, DOMRect, CSSStyleDeclaration] => {
412
+ if (recalculate_bounds || !this._dimensions) {
413
+ this._dimensions = {
414
+ box: this.getBoundingClientRect(),
415
+ style: getComputedStyle(this),
416
+ };
345
417
  }
346
418
 
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
- }
419
+ const box = this._dimensions.box;
420
+ const style = this._dimensions.style;
421
+ const col =
422
+ (clientX - box.left - parseFloat(style.paddingLeft)) /
423
+ (box.width -
424
+ parseFloat(style.paddingLeft) -
425
+ parseFloat(style.paddingRight));
426
+ const row =
427
+ (clientY - box.top - parseFloat(style.paddingTop)) /
428
+ (box.height -
429
+ parseFloat(style.paddingTop) -
430
+ parseFloat(style.paddingBottom));
431
+
432
+ return [col, row, box, style];
364
433
  };
365
434
 
366
435
  private onPointerDown = (event: PointerEvent) => {
367
- if (event.target === this) {
368
- const [col, row] = this.relativeCoordinates(event.clientX, event.clientY);
369
- const hit = calculate_intersection(col, row, this._panel);
436
+ if (!this._physics.GRID_DIVIDER_CHECK_TARGET || event.target === this) {
437
+ const [col, row, rect] = this.relativeCoordinates(
438
+ event.clientX,
439
+ event.clientY,
440
+ );
441
+
442
+ const hit = calculate_intersection(col, row, this._panel, {
443
+ rect,
444
+ size: this._physics.GRID_DIVIDER_SIZE,
445
+ });
370
446
  if (hit && hit.type !== "layout-path") {
371
- this._dragPath = [hit, col, row];
447
+ this._drag_target = [hit, col, row];
372
448
  this.setPointerCapture(event.pointerId);
373
449
  event.preventDefault();
374
450
  }
@@ -376,31 +452,67 @@ export class RegularLayout extends HTMLElement {
376
452
  };
377
453
 
378
454
  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;
455
+ if (this._drag_target) {
456
+ const [col, row] = this.relativeCoordinates(
457
+ event.clientX,
458
+ event.clientY,
459
+ false,
460
+ );
461
+
462
+ const [{ path, type }, old_col, old_row] = this._drag_target;
383
463
  const offset = type === "horizontal" ? old_col - col : old_row - row;
384
- const panel = redistribute_panel_sizes(old_panel, path, offset);
385
- this._stylesheet.replaceSync(create_css_grid_layout(panel));
464
+ const panel = redistribute_panel_sizes(this._panel, path, offset);
465
+ this._stylesheet.replaceSync(
466
+ create_css_grid_layout(panel, undefined, this._physics),
467
+ );
468
+ }
469
+
470
+ if (this._physics.GRID_DIVIDER_CHECK_TARGET && event.target !== this) {
471
+ if (this._cursor_override) {
472
+ this._cursor_override = false;
473
+ this._cursor_stylesheet.replaceSync("");
474
+ }
475
+
476
+ return;
477
+ }
478
+
479
+ const [col, row, rect] = this.relativeCoordinates(
480
+ event.clientX,
481
+ event.clientY,
482
+ false,
483
+ );
484
+
485
+ const divider = calculate_intersection(col, row, this._panel, {
486
+ rect,
487
+ size: this._physics.GRID_DIVIDER_SIZE,
488
+ });
489
+
490
+ if (divider?.type === "vertical") {
491
+ this._cursor_stylesheet.replaceSync(":host{cursor:row-resize");
492
+ this._cursor_override = true;
493
+ } else if (divider?.type === "horizontal") {
494
+ this._cursor_stylesheet.replaceSync(":host{cursor:col-resize");
495
+ this._cursor_override = true;
496
+ } else if (this._cursor_override) {
497
+ this._cursor_override = false;
498
+ this._cursor_stylesheet.replaceSync("");
386
499
  }
387
500
  };
388
501
 
389
502
  private onPointerUp = (event: PointerEvent) => {
390
- if (this._dragPath) {
503
+ if (this._drag_target) {
391
504
  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
- }
505
+ const [col, row] = this.relativeCoordinates(
506
+ event.clientX,
507
+ event.clientY,
508
+ false,
509
+ );
402
510
 
403
- this._dragPath = undefined;
511
+ const [{ path, type }, old_col, old_row] = this._drag_target;
512
+ const offset = type === "horizontal" ? old_col - col : old_row - row;
513
+ const panel = redistribute_panel_sizes(this._panel, path, offset);
514
+ this.restore(panel, true);
515
+ this._drag_target = undefined;
404
516
  }
405
517
  };
406
518
  }
@@ -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
+ }