regular-layout 0.0.1 → 0.1.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.
@@ -1,5 +1,4 @@
1
- import type { LayoutPath, Layout } from "./common/layout_config.ts";
2
- export type OverlayMode = "grid" | "absolute" | "interactive";
1
+ import type { LayoutPath, Layout, TabLayout, OverlayMode } from "./common/layout_config.ts";
3
2
  /**
4
3
  * A Web Component that provides a resizable panel layout system.
5
4
  * Panels are arranged using CSS Grid and can be resized by dragging dividers.
@@ -49,44 +48,61 @@ export declare class RegularLayout extends HTMLElement {
49
48
  *
50
49
  * @param x - X coordinate in screen pixels.
51
50
  * @param y - Y coordinate in screen pixels.
52
- * @param layoutPath - Layout path containing the slot identifier.
53
- * @param mode - Overlay rendering mode: "grid" highlights the target,
54
- * "absolute" positions the panel absolutely, "interactive" updates the
55
- * actual layout in real-time. Defaults to "absolute".
51
+ * @param dragTarget - A `LayoutPath` (presumably from `calculateIntersect`)
52
+ * which points to the drag element in the current layout.
53
+ * @param className - The CSS class name to use for the overlay panel
54
+ * (defaults to "overlay").
55
+ * @param mode - Overlay rendering mode: "grid" uses CSS grid to position
56
+ * the target, "absolute" positions the panel absolutely. Defaults to
57
+ * "absolute".
56
58
  */
57
- setOverlayState<T>(x: number, y: number, { slot }: LayoutPath<T>, mode?: "grid" | "absolute" | "interactive"): void;
59
+ setOverlayState: (x: number, y: number, { slot }: LayoutPath<unknown>, className?: string, mode?: OverlayMode) => void;
58
60
  /**
59
61
  * Clears the overlay state and commits the panel placement.
60
62
  *
61
63
  * @param x - X coordinate in screen pixels.
62
64
  * @param y - Y coordinate in screen pixels.
63
- * @param layout_path - Layout path containing the slot identifier.
65
+ * @param dragTarget - A `LayoutPath` (presumably from `calculateIntersect`)
66
+ * which points to the drag element in the current layout.
67
+ * @param className - The CSS class name to use for the overlay panel
68
+ * (defaults to "overlay").
64
69
  * @param mode - Overlay rendering mode that was used, must match the mode
65
- * passed to `setOverlayState`. Defaults to "absolute".
70
+ * passed to `setOverlayState`. Defaults to "absolute".
66
71
  */
67
- clearOverlayState<T>(x: number, y: number, drag_target: LayoutPath<T>, mode?: "grid" | "absolute" | "interactive"): void;
72
+ clearOverlayState: (x: number, y: number, drag_target: LayoutPath<unknown>, className?: string, mode?: OverlayMode) => void;
68
73
  /**
69
74
  * Inserts a new panel into the layout at a specified path.
70
75
  *
71
76
  * @param name - Unique identifier for the new panel.
72
77
  * @param path - Index path defining where to insert.
73
78
  */
74
- insertPanel(name: string, path?: number[]): void;
79
+ insertPanel: (name: string, path?: number[]) => void;
75
80
  /**
76
81
  * Removes a panel from the layout by name.
77
82
  *
78
83
  * @param name - Name of the panel to remove
79
84
  */
80
- removePanel(name: string): void;
85
+ removePanel: (name: string) => void;
86
+ /**
87
+ * Retrieves a panel by name from the layout tree.
88
+ *
89
+ * @param name - Name of the panel to find.
90
+ * @param layout - Optional layout tree to search in (defaults to current layout).
91
+ * @returns The TabLayout containing the panel if found, null otherwise.
92
+ */
93
+ getPanel: (name: string, layout?: Layout) => TabLayout | null;
81
94
  /**
82
95
  * Determines which panel is at a given screen coordinate.
83
- * Useful for drag-and-drop operations or custom interactions.
84
96
  *
85
97
  * @param column - X coordinate in screen pixels.
86
98
  * @param row - Y coordinate in screen pixels.
87
99
  * @returns Panel information if a panel is at that position, null otherwise.
88
100
  */
89
- calculateIntersect(x: number, y: number, check_dividers?: boolean): LayoutPath<DOMRect> | null;
101
+ calculateIntersect: (x: number, y: number, check_dividers?: boolean) => LayoutPath<DOMRect> | null;
102
+ /**
103
+ * Clears the entire layout, unslotting all panels.
104
+ */
105
+ clear: () => void;
90
106
  /**
91
107
  * Restores the layout from a saved state.
92
108
  *
@@ -99,7 +115,7 @@ export declare class RegularLayout extends HTMLElement {
99
115
  * layout.restore(savedState);
100
116
  * ```
101
117
  */
102
- restore(layout: Layout, _is_flattened?: boolean): void;
118
+ restore: (layout: Layout, _is_flattened?: boolean) => void;
103
119
  /**
104
120
  * Serializes the current layout state, which can be restored via `restore`.
105
121
  *
@@ -112,8 +128,22 @@ export declare class RegularLayout extends HTMLElement {
112
128
  * localStorage.setItem('layout', JSON.stringify(state));
113
129
  * ```
114
130
  */
115
- save(): Layout;
116
- private relativeCoordinates;
131
+ save: () => Layout;
132
+ /**
133
+ * Converts screen coordinates to relative layout coordinates.
134
+ *
135
+ * Transforms absolute pixel positions into normalized coordinates (0-1 range)
136
+ * relative to the layout's bounding box.
137
+ *
138
+ * @param clientX - X coordinate in screen pixels (client space).
139
+ * @param clientY - Y coordinate in screen pixels (client space).
140
+ * @returns A tuple containing:
141
+ * - col: Normalized X coordinate (0 = left edge, 1 = right edge)
142
+ * - row: Normalized Y coordinate (0 = top edge, 1 = bottom edge)
143
+ * - box: The layout element's bounding rectangle
144
+ */
145
+ relativeCoordinates: (clientX: number, clientY: number) => [number, number, DOMRect];
146
+ private updateSlots;
117
147
  private onPointerDown;
118
148
  private onPointerMove;
119
149
  private onPointerUp;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "regular-layout",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "A regular CSS `grid` container",
5
5
  "keywords": [],
6
6
  "license": "Apache-2.0",
@@ -20,6 +20,7 @@
20
20
  "clean": "rm -rf dist",
21
21
  "test": "playwright test",
22
22
  "example": "npx http-server . -p 8000",
23
+ "deploy": "node deploy.mjs",
23
24
  "lint": "biome lint src tests",
24
25
  "format": "biome format --write src tests",
25
26
  "check": "biome check --write src tests"
@@ -0,0 +1,104 @@
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
+ import { calculate_intersection } from "./calculate_intersect";
13
+ import { SPLIT_EDGE_TOLERANCE } from "./constants";
14
+ import { insert_child } from "./insert_child";
15
+ import type { Layout, LayoutPath, Orientation } from "./layout_config";
16
+
17
+ /**
18
+ * Calculates an insertion point (which may involve splitting a single
19
+ * `"child-panel"` into a new `"split-panel"`), based on the cursor position.
20
+ * *
21
+ * @param col - The cursor column.
22
+ * @param row - The cursor row.
23
+ * @param panel - The `Layout` to insert into.
24
+ * @param slot - The slot identifier where the insert should occur
25
+ * @param drop_target - The `LayoutPath` (from `calculateIntersect`) of the
26
+ * panel to either insert next to, or split by.
27
+ * @returns A new `LayoutPath` reflecting the updated (maybe) `"split-panel"`,
28
+ * which is enough to draw the overlay.
29
+ */
30
+ export function calculate_edge(
31
+ col: number,
32
+ row: number,
33
+ panel: Layout,
34
+ slot: string,
35
+ drop_target: LayoutPath,
36
+ ): LayoutPath {
37
+ const is_column_edge =
38
+ drop_target.column_offset < SPLIT_EDGE_TOLERANCE ||
39
+ drop_target.column_offset > 1 - SPLIT_EDGE_TOLERANCE;
40
+
41
+ const is_row_edge =
42
+ drop_target.row_offset < SPLIT_EDGE_TOLERANCE ||
43
+ drop_target.row_offset > 1 - SPLIT_EDGE_TOLERANCE;
44
+
45
+ if (is_column_edge) {
46
+ return handle_axis(
47
+ col,
48
+ row,
49
+ panel,
50
+ slot,
51
+ drop_target,
52
+ drop_target.column_offset,
53
+ "horizontal",
54
+ );
55
+ } else if (is_row_edge) {
56
+ return handle_axis(
57
+ col,
58
+ row,
59
+ panel,
60
+ slot,
61
+ drop_target,
62
+ drop_target.row_offset,
63
+ "vertical",
64
+ );
65
+ }
66
+
67
+ return drop_target;
68
+ }
69
+
70
+ function handle_axis(
71
+ col: number,
72
+ row: number,
73
+ panel: Layout,
74
+ slot: string,
75
+ drop_target: LayoutPath,
76
+ axis_offset: number,
77
+ axis_orientation: Orientation,
78
+ ): LayoutPath {
79
+ const is_before = axis_offset < SPLIT_EDGE_TOLERANCE;
80
+ if (drop_target.orientation === axis_orientation) {
81
+ if (drop_target.path.length === 0) {
82
+ const insert_index = is_before ? 0 : 1;
83
+ const new_panel = insert_child(panel, slot, [insert_index]);
84
+ drop_target = calculate_intersection(col, row, new_panel, false);
85
+ } else {
86
+ const path_without_last = drop_target.path.slice(0, -1);
87
+ const last_index = drop_target.path[drop_target.path.length - 1];
88
+ const insert_index = is_before ? last_index : last_index + 1;
89
+ const new_panel = insert_child(panel, slot, [
90
+ ...path_without_last,
91
+ insert_index,
92
+ ]);
93
+
94
+ drop_target = calculate_intersection(col, row, new_panel, false);
95
+ }
96
+ } else {
97
+ const path = [...drop_target.path, is_before ? 0 : 1];
98
+ const new_panel = insert_child(panel, slot, path, axis_orientation);
99
+ drop_target = calculate_intersection(col, row, new_panel, false);
100
+ }
101
+
102
+ drop_target.is_edge = true;
103
+ return drop_target;
104
+ }
@@ -72,25 +72,38 @@ function calculate_intersection_recursive(
72
72
  row: number,
73
73
  panel: Layout,
74
74
  check_dividers: boolean,
75
- parent_orientation: "horizontal" | "vertical" = "horizontal",
75
+ parent_orientation: "horizontal" | "vertical" | null = null,
76
76
  view_window: ViewWindow = structuredClone(VIEW_WINDOW),
77
77
  path: number[] = [],
78
78
  ): LayoutPath | null | LayoutDivider {
79
+ if (column < 0 || row < 0 || column > 1 || row > 1) {
80
+ return null;
81
+ }
82
+
79
83
  // Base case: if this is a child panel, return its name
80
84
  if (panel.type === "child-panel") {
85
+ const selected = panel.selected ?? 0;
86
+ const column_offset =
87
+ (column - view_window.col_start) /
88
+ (view_window.col_end - view_window.col_start);
89
+
90
+ const row_offset =
91
+ (row - view_window.row_start) /
92
+ (view_window.row_end - view_window.row_start);
93
+
81
94
  return {
82
95
  type: "layout-path",
83
96
  box: undefined,
84
- slot: panel.child,
97
+ slot: panel.child[selected],
98
+ panel: structuredClone(panel),
85
99
  path: path,
86
100
  view_window: view_window,
87
- column_offset:
88
- (column - view_window.col_start) /
89
- (view_window.col_end - view_window.col_start),
90
- row_offset:
91
- (row - view_window.row_start) /
92
- (view_window.row_end - view_window.row_start),
93
- orientation: parent_orientation,
101
+ is_edge: false,
102
+ column,
103
+ row,
104
+ column_offset,
105
+ row_offset,
106
+ orientation: parent_orientation || "horizontal",
94
107
  };
95
108
  }
96
109
 
@@ -0,0 +1,46 @@
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
+ import type { OverlayMode } from "./layout_config";
13
+
14
+ /**
15
+ * The minimum number of pixels the mouse must move to be considered a drag.
16
+ */
17
+ export const MIN_DRAG_DISTANCE = 10;
18
+
19
+ /**
20
+ * Class name to use for child elements in overlay position (dragging).
21
+ */
22
+ export const OVERLAY_CLASSNAME = "overlay";
23
+
24
+ /**
25
+ * The percentage of the maximum resize distance that will be clamped.
26
+ *
27
+ */
28
+ export const MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD = 0.15;
29
+
30
+ /**
31
+ * Threshold from panel edge that is considered a split vs drop action.
32
+ */
33
+ export const SPLIT_EDGE_TOLERANCE = 0.25;
34
+
35
+ /**
36
+ * Tolerance threshold for considering two grid track positions as identical.
37
+ *
38
+ * When collecting and deduplicating track positions, any positions closer than
39
+ * this value are treated as the same position to avoid redundant grid tracks.
40
+ */
41
+ export const GRID_TRACK_COLLAPSE_TOLERANCE = 0.001;
42
+
43
+ /**
44
+ * The overlay default behavior.
45
+ */
46
+ export const OVERLAY_DEFAULT: OverlayMode = "absolute";
@@ -24,6 +24,7 @@ import type { Layout } from "./layout_config.ts";
24
24
  */
25
25
  export function flatten(layout: Layout): Layout {
26
26
  if (layout.type === "child-panel") {
27
+ layout.selected = layout.selected || 0;
27
28
  return layout;
28
29
  }
29
30
 
@@ -48,6 +49,10 @@ export function flatten(layout: Layout): Layout {
48
49
  }
49
50
  }
50
51
 
52
+ if (flattenedChildren.length === 1) {
53
+ return flattenedChildren[0];
54
+ }
55
+
51
56
  return {
52
57
  type: "split-panel",
53
58
  orientation: layout.orientation,
@@ -9,7 +9,8 @@
9
9
  // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
- import { GRID_TRACK_COLLAPSE_TOLERANCE, type Layout } from "./layout_config.ts";
12
+ import { GRID_TRACK_COLLAPSE_TOLERANCE } from "./constants.ts";
13
+ import type { Layout } from "./layout_config.ts";
13
14
  import { remove_child } from "./remove_child.ts";
14
15
 
15
16
  interface GridCell {
@@ -85,7 +86,7 @@ function collectTrackPositions(
85
86
 
86
87
  function findTrackIndex(positions: number[], value: number): number {
87
88
  for (let i = 0; i < positions.length; i++) {
88
- if (Math.abs(positions[i] - value) < 0.0001) {
89
+ if (Math.abs(positions[i] - value) < GRID_TRACK_COLLAPSE_TOLERANCE) {
89
90
  return i;
90
91
  }
91
92
  }
@@ -103,9 +104,10 @@ function buildCells(
103
104
  rowEnd: number,
104
105
  ): GridCell[] {
105
106
  if (panel.type === "child-panel") {
107
+ const selected = panel.selected ?? 0;
106
108
  return [
107
109
  {
108
- child: panel.child,
110
+ child: panel.child[selected],
109
111
  colStart: findTrackIndex(colPositions, colStart),
110
112
  colEnd: findTrackIndex(colPositions, colEnd),
111
113
  rowStart: findTrackIndex(rowPositions, rowStart),
@@ -203,7 +205,8 @@ export function create_css_grid_layout(
203
205
  }
204
206
 
205
207
  if (layout.type === "child-panel") {
206
- return `${host_template("100%", "100%")}\n${child_template(layout.child, "1", "1")}`;
208
+ const selected = layout.selected ?? 0;
209
+ return `${host_template("100%", "100%")}\n${child_template(layout.child[selected], "1", "1")}`;
207
210
  }
208
211
 
209
212
  const colPositions = collectTrackPositions(layout, "horizontal", 0, 1);
@@ -11,15 +11,18 @@
11
11
 
12
12
  import type { LayoutPath } from "./layout_config";
13
13
 
14
- export function updateOverlaySheet({
15
- view_window: { row_start, row_end, col_start, col_end },
16
- box,
17
- }: LayoutPath<DOMRect>) {
14
+ export function updateOverlaySheet(
15
+ slot: string,
16
+ {
17
+ view_window: { row_start, row_end, col_start, col_end },
18
+ box,
19
+ }: LayoutPath<DOMRect>,
20
+ ) {
18
21
  const margin = 0;
19
22
  const top = row_start * box.height + margin / 2;
20
23
  const left = col_start * box.width + margin / 2;
21
24
  const height = (row_end - row_start) * box.height - margin;
22
25
  const width = (col_end - col_start) * box.width - margin;
23
26
  const css = `position:absolute!important;z-index:1;top:${top}px;left:${left}px;height:${height}px;width:${width}px;`;
24
- return `::slotted(:not([slot])){${css}}`;
27
+ return `::slotted([slot="${slot}"]){${css}}`;
25
28
  }
@@ -29,22 +29,15 @@ export function insert_child(
29
29
  child: string,
30
30
  path: number[],
31
31
  orientation: "horizontal" | "vertical" = "horizontal",
32
+ is_edge?: boolean,
32
33
  ): Layout {
33
34
  if (path.length === 0) {
34
35
  // Insert at root level
35
36
  if (panel.type === "child-panel") {
36
- // Convert single child-panel to split-panel with two children
37
+ // Add to existing child-panel as a tab
37
38
  return {
38
- type: "split-panel",
39
- orientation,
40
- children: [
41
- panel,
42
- {
43
- type: "child-panel",
44
- child,
45
- },
46
- ],
47
- sizes: [0.5, 0.5],
39
+ type: "child-panel",
40
+ child: [child, ...panel.child],
48
41
  };
49
42
  } else {
50
43
  // Append to existing split-panel
@@ -52,7 +45,7 @@ export function insert_child(
52
45
  ...panel.children,
53
46
  {
54
47
  type: "child-panel",
55
- child,
48
+ child: [child],
56
49
  } as Layout,
57
50
  ];
58
51
 
@@ -78,15 +71,21 @@ export function insert_child(
78
71
  sizes: [1],
79
72
  };
80
73
 
81
- return insert_child(newPanel, child, path, orientation);
74
+ return insert_child(newPanel, child, path, orientation, is_edge);
82
75
  }
83
76
 
84
77
  if (restPath.length === 0 || index === panel.children.length) {
78
+ if (is_edge && panel.children[index]?.type === "child-panel") {
79
+ panel.children[index].child.unshift(child);
80
+ panel.children[index].selected = 0;
81
+ return panel;
82
+ }
83
+
85
84
  // Insert at this level at the specified index
86
85
  const newChildren = [...panel.children];
87
86
  newChildren.splice(index, 0, {
88
87
  type: "child-panel",
89
- child,
88
+ child: [child],
90
89
  });
91
90
 
92
91
  const numChildren = newChildren.length;
@@ -109,6 +108,7 @@ export function insert_child(
109
108
  child,
110
109
  restPath,
111
110
  oppositeOrientation,
111
+ is_edge,
112
112
  );
113
113
 
114
114
  const newChildren = [...panel.children];
@@ -119,7 +119,14 @@ export function insert_child(
119
119
  };
120
120
  }
121
121
 
122
- const updatedChild = insert_child(targetChild, child, restPath, orientation);
122
+ const updatedChild = insert_child(
123
+ targetChild,
124
+ child,
125
+ restPath,
126
+ orientation,
127
+ is_edge,
128
+ );
129
+
123
130
  const newChildren = [...panel.children];
124
131
  newChildren[index] = updatedChild;
125
132
  return {
@@ -10,23 +10,9 @@
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
12
  /**
13
- * The percentage of the maximum resize distance that will be clamped.
14
- *
15
- */
16
- export const MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD = 0.15;
17
-
18
- /**
19
- * Threshold from panel edge that is considered a split vs drop action.
20
- */
21
- export const SPLIT_EDGE_TOLERANCE = 0.15;
22
-
23
- /**
24
- * Tolerance threshold for considering two grid track positions as identical.
25
- *
26
- * When collecting and deduplicating track positions, any positions closer than
27
- * this value are treated as the same position to avoid redundant grid tracks.
13
+ * The overlay behavior type.
28
14
  */
29
- export const GRID_TRACK_COLLAPSE_TOLERANCE = 0.0001;
15
+ export type OverlayMode = "grid" | "absolute";
30
16
 
31
17
  /**
32
18
  * The representation of a CSS grid, in JSON form.
@@ -67,7 +53,8 @@ export interface SplitLayout {
67
53
  */
68
54
  export interface TabLayout {
69
55
  type: "child-panel";
70
- child: string;
56
+ child: string[];
57
+ selected?: number;
71
58
  }
72
59
 
73
60
  /**
@@ -91,11 +78,15 @@ export interface LayoutDivider {
91
78
  export interface LayoutPath<T = undefined> {
92
79
  type: "layout-path";
93
80
  slot: string;
81
+ panel: TabLayout;
94
82
  path: number[];
95
83
  view_window: ViewWindow;
84
+ column: number;
85
+ row: number;
96
86
  column_offset: number;
97
87
  row_offset: number;
98
88
  orientation: Orientation;
89
+ is_edge: boolean;
99
90
  box: T;
100
91
  }
101
92
 
@@ -112,7 +103,7 @@ export function* iter_panel_children(panel: Layout): Generator<string> {
112
103
  yield* iter_panel_children(child);
113
104
  }
114
105
  } else {
115
- yield panel.child;
106
+ yield panel.child[panel.selected || 0];
116
107
  }
117
108
  }
118
109
 
@@ -9,10 +9,8 @@
9
9
  // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
- import {
13
- MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD,
14
- type Layout,
15
- } from "./layout_config.ts";
12
+ import { MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD } from "./constants.ts";
13
+ import type { Layout } from "./layout_config.ts";
16
14
 
17
15
  /**
18
16
  * Adjusts panel sizes during a drag operation on a divider.
@@ -54,7 +52,11 @@ export function redistribute_panel_sizes(
54
52
  if (current.type === "split-panel") {
55
53
  const delta = deltas[current.orientation];
56
54
  const index = path[path.length - 1];
57
- current.sizes = add_and_redistribute(current.sizes, index, delta);
55
+
56
+ // It would be fun to remove this condition.
57
+ if (index < current.sizes.length - 1) {
58
+ current.sizes = add_and_redistribute(current.sizes, index, delta);
59
+ }
58
60
  }
59
61
 
60
62
  return result;