regular-layout 0.2.2 → 0.3.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.
@@ -2,7 +2,7 @@ import { type Physics } from "./constants";
2
2
  import type { Layout, LayoutPath } from "./types";
3
3
  /**
4
4
  * Calculates an insertion point (which may involve splitting a single
5
- * `"child-panel"` into a new `"split-panel"`), based on the cursor position.
5
+ * `"tab-layout"` into a new `"split-layout"`), based on the cursor position.
6
6
  * *
7
7
  * @param col - The cursor column.
8
8
  * @param row - The cursor row.
@@ -10,7 +10,7 @@ import type { Layout, LayoutPath } from "./types";
10
10
  * @param slot - The slot identifier where the insert should occur
11
11
  * @param drop_target - The `LayoutPath` (from `calculateIntersect`) of the
12
12
  * panel to either insert next to, or split by.
13
- * @returns A new `LayoutPath` reflecting the updated (maybe) `"split-panel"`,
13
+ * @returns A new `LayoutPath` reflecting the updated (maybe) `"split-layout"`,
14
14
  * which is enough to draw the overlay.
15
15
  */
16
16
  export declare function calculate_edge(col: number, row: number, panel: Layout, slot: string, drop_target: LayoutPath, box?: DOMRect, physics?: Physics): LayoutPath;
@@ -0,0 +1,12 @@
1
+ import type { Layout } from "./types.ts";
2
+ /**
3
+ * Calculates the index path for a panel with the given name.
4
+ *
5
+ * Traverses the layout tree to find the named panel and returns the
6
+ * index path describing its position in the tree.
7
+ *
8
+ * @param name - The name of the panel to find.
9
+ * @param layout - The layout tree to search.
10
+ * @returns The panel's index path if found, `null` otherwise.
11
+ */
12
+ export declare function calculate_path(name: string, layout: Layout): number[] | null;
@@ -14,11 +14,11 @@ import type { Layout } from "./types.ts";
14
14
  * @example
15
15
  * ```typescript
16
16
  * const layout = {
17
- * type: "split-panel",
17
+ * type: "split-layout",
18
18
  * orientation: "horizontal",
19
19
  * children: [
20
- * { type: "child-panel", tabs: "sidebar" },
21
- * { type: "child-panel", tabs: "main" }
20
+ * { type: "tab-layout", tabs: "sidebar" },
21
+ * { type: "tab-layout", tabs: "main" }
22
22
  * ],
23
23
  * sizes: [0.25, 0.75]
24
24
  * };
@@ -1,4 +1,4 @@
1
- import type { Layout } from "./types.ts";
1
+ import type { Layout, LayoutPathTraversal } from "./types.ts";
2
2
  /**
3
3
  * Inserts a new child panel into the layout tree at a specified location.
4
4
  * Creates a split panel if necessary and redistributes sizes equally among all
@@ -13,4 +13,4 @@ import type { Layout } from "./types.ts";
13
13
  * @param is_edge - If true, create the split at the parent level.
14
14
  * @returns A new layout tree with the child inserted (original is not mutated).
15
15
  */
16
- export declare function insert_child(panel: Layout, child: string, path: number[], orientation?: "horizontal" | "vertical"): Layout;
16
+ export declare function insert_child(panel: Layout, child: string, path: LayoutPathTraversal, orientation?: "horizontal" | "vertical"): Layout;
@@ -1,4 +1,4 @@
1
- import type { Layout } from "./types.ts";
1
+ import type { Layout, LayoutPathTraversal } from "./types.ts";
2
2
  /**
3
3
  * Adjusts panel sizes during a drag operation on a divider.
4
4
  *
@@ -16,4 +16,4 @@ import type { Layout } from "./types.ts";
16
16
  * @returns A new layout tree with updated sizes (original is not mutated).
17
17
  * ```
18
18
  */
19
- export declare function redistribute_panel_sizes(panel: Layout, path: number[], delta: number | undefined, physics?: import("./constants.ts").Physics): Layout;
19
+ export declare function redistribute_panel_sizes(panel: Layout, path: LayoutPathTraversal, delta: number | undefined, physics?: import("./constants.ts").Physics): Layout;
@@ -32,7 +32,7 @@ export interface ViewWindow {
32
32
  * opposite `orientation` as its parent.
33
33
  */
34
34
  export interface SplitLayout {
35
- type: "split-panel";
35
+ type: "split-layout";
36
36
  children: Layout[];
37
37
  sizes: number[];
38
38
  orientation: Orientation;
@@ -41,17 +41,21 @@ export interface SplitLayout {
41
41
  * A leaf panel node that contains a single named child element.
42
42
  */
43
43
  export interface TabLayout {
44
- type: "child-panel";
44
+ type: "tab-layout";
45
45
  tabs: string[];
46
46
  selected?: number;
47
47
  }
48
+ /**
49
+ * Represents a panel location relative to a `Layout` struct.
50
+ */
51
+ export type LayoutPathTraversal = number[];
48
52
  /**
49
53
  * Represents a draggable divider between two panels in the layout.
50
54
  *
51
55
  * Used for hit detection.
52
56
  */
53
57
  export interface LayoutDivider {
54
- path: number[];
58
+ path: LayoutPathTraversal;
55
59
  view_window: ViewWindow;
56
60
  type: Orientation;
57
61
  }
@@ -61,7 +65,7 @@ export interface LayoutDivider {
61
65
  export interface LayoutPath {
62
66
  type: "layout-path";
63
67
  slot: string;
64
- path: number[];
68
+ path: LayoutPathTraversal;
65
69
  view_window: ViewWindow;
66
70
  column: number;
67
71
  row: number;
@@ -1,4 +1,4 @@
1
- import type { LayoutPath, Layout, TabLayout, OverlayMode, Orientation } from "./layout/types.ts";
1
+ import type { LayoutPath, Layout, TabLayout, OverlayMode, Orientation, LayoutPathTraversal } from "./layout/types.ts";
2
2
  import { type PhysicsUpdate, type Physics } from "./layout/constants.ts";
3
3
  /**
4
4
  * An interface which models the fields of `PointerEvent` that
@@ -78,6 +78,13 @@ export declare class RegularLayout extends HTMLElement {
78
78
  * @returns Panel information if a panel is at that position, null otherwise.
79
79
  */
80
80
  calculateIntersect: (coordinates: PointerEventCoordinates) => LayoutPath | null;
81
+ /**
82
+ * Calculates the index path for a panel with the given name.
83
+ *
84
+ * @param name - The name of the panel to find.
85
+ * @returns The panel's index path if found, `null` otherwise.
86
+ */
87
+ calculatePath: (name: string) => number[] | null;
81
88
  /**
82
89
  * Sets the visual overlay state during drag-and-drop operations.
83
90
  * Displays a preview of where a panel would be placed at the given coordinates.
@@ -117,7 +124,7 @@ export declare class RegularLayout extends HTMLElement {
117
124
  * the new `SplitPanel` _if_ there is an option of orientation (e.g. if
118
125
  * the layout had no pre-existing `SplitPanel`)
119
126
  */
120
- insertPanel: (name: string, path?: number[], split?: boolean | Orientation) => void;
127
+ insertPanel: (name: string, path?: LayoutPathTraversal, split?: boolean | Orientation) => void;
121
128
  /**
122
129
  * Removes a panel from the layout by name.
123
130
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "regular-layout",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "A regular CSS `grid` container",
5
5
  "keywords": [],
6
6
  "license": "Apache-2.0",
@@ -11,11 +11,17 @@
11
11
 
12
12
  import { DEFAULT_PHYSICS, type Physics } from "./constants";
13
13
  import { insert_child } from "./insert_child";
14
- import type { Layout, LayoutPath, Orientation, ViewWindow } from "./types";
14
+ import type {
15
+ Layout,
16
+ LayoutPath,
17
+ LayoutPathTraversal,
18
+ Orientation,
19
+ ViewWindow,
20
+ } from "./types";
15
21
 
16
22
  /**
17
23
  * Calculates an insertion point (which may involve splitting a single
18
- * `"child-panel"` into a new `"split-panel"`), based on the cursor position.
24
+ * `"tab-layout"` into a new `"split-layout"`), based on the cursor position.
19
25
  * *
20
26
  * @param col - The cursor column.
21
27
  * @param row - The cursor row.
@@ -23,7 +29,7 @@ import type { Layout, LayoutPath, Orientation, ViewWindow } from "./types";
23
29
  * @param slot - The slot identifier where the insert should occur
24
30
  * @param drop_target - The `LayoutPath` (from `calculateIntersect`) of the
25
31
  * panel to either insert next to, or split by.
26
- * @returns A new `LayoutPath` reflecting the updated (maybe) `"split-panel"`,
32
+ * @returns A new `LayoutPath` reflecting the updated (maybe) `"split-layout"`,
27
33
  * which is enough to draw the overlay.
28
34
  */
29
35
  export function calculate_edge(
@@ -133,7 +139,7 @@ function insert_root_edge(
133
139
  panel: Layout,
134
140
  slot: string,
135
141
  drop_target: LayoutPath,
136
- path: number[],
142
+ path: LayoutPathTraversal,
137
143
  is_before: boolean,
138
144
  orientation: Orientation,
139
145
  ): LayoutPath {
@@ -153,7 +159,7 @@ function insert_axis(
153
159
  is_before: boolean,
154
160
  axis_orientation: Orientation,
155
161
  ): LayoutPath {
156
- let result_path: number[];
162
+ let result_path: LayoutPathTraversal;
157
163
 
158
164
  if (drop_target.orientation === axis_orientation) {
159
165
  // Same orientation - insert into existing split
@@ -183,7 +189,10 @@ function insert_axis(
183
189
  };
184
190
  }
185
191
 
186
- function calculate_view_window(panel: Layout, path: number[]): ViewWindow {
192
+ function calculate_view_window(
193
+ panel: Layout,
194
+ path: LayoutPathTraversal,
195
+ ): ViewWindow {
187
196
  let view_window: ViewWindow = {
188
197
  row_start: 0,
189
198
  row_end: 1,
@@ -193,7 +202,7 @@ function calculate_view_window(panel: Layout, path: number[]): ViewWindow {
193
202
 
194
203
  let current_panel = panel;
195
204
  for (const step of path) {
196
- if (current_panel.type === "child-panel") {
205
+ if (current_panel.type === "tab-layout") {
197
206
  break;
198
207
  }
199
208
 
@@ -9,7 +9,13 @@
9
9
  // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
- import type { LayoutPath, LayoutDivider, Layout, ViewWindow } from "./types.ts";
12
+ import type {
13
+ LayoutPath,
14
+ LayoutDivider,
15
+ Layout,
16
+ ViewWindow,
17
+ LayoutPathTraversal,
18
+ } from "./types.ts";
13
19
 
14
20
  const VIEW_WINDOW = {
15
21
  row_start: 0,
@@ -67,14 +73,14 @@ function calculate_intersection_recursive(
67
73
  check_dividers: { rect: DOMRect; size: number } | null,
68
74
  parent_orientation: "horizontal" | "vertical" | null = null,
69
75
  view_window: ViewWindow = structuredClone(VIEW_WINDOW),
70
- path: number[] = [],
76
+ path: LayoutPathTraversal = [],
71
77
  ): LayoutPath | null | LayoutDivider {
72
78
  if (column < 0 || row < 0 || column > 1 || row > 1) {
73
79
  return null;
74
80
  }
75
81
 
76
82
  // Base case: if this is a child panel, return its name
77
- if (panel.type === "child-panel") {
83
+ if (panel.type === "tab-layout") {
78
84
  const selected = panel.selected ?? 0;
79
85
  const col_width = view_window.col_end - view_window.col_start;
80
86
  const row_height = view_window.row_end - view_window.row_start;
@@ -0,0 +1,53 @@
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 { Layout, LayoutPathTraversal } from "./types.ts";
13
+
14
+ /**
15
+ * Calculates the index path for a panel with the given name.
16
+ *
17
+ * Traverses the layout tree to find the named panel and returns the
18
+ * index path describing its position in the tree.
19
+ *
20
+ * @param name - The name of the panel to find.
21
+ * @param layout - The layout tree to search.
22
+ * @returns The panel's index path if found, `null` otherwise.
23
+ */
24
+ export function calculate_path(name: string, layout: Layout): number[] | null {
25
+ return calculate_path_recursive(name, layout, []);
26
+ }
27
+
28
+ function calculate_path_recursive(
29
+ name: string,
30
+ panel: Layout,
31
+ path: LayoutPathTraversal,
32
+ ): number[] | null {
33
+ if (panel.type === "tab-layout") {
34
+ if (!panel.tabs.includes(name)) {
35
+ return null;
36
+ }
37
+
38
+ return path;
39
+ }
40
+
41
+ for (let i = 0; i < panel.children.length; i++) {
42
+ const result = calculate_path_recursive(name, panel.children[i], [
43
+ ...path,
44
+ i,
45
+ ]);
46
+
47
+ if (result) {
48
+ return result;
49
+ }
50
+ }
51
+
52
+ return null;
53
+ }
@@ -23,7 +23,7 @@ import type { Layout } from "./types.ts";
23
23
  * @returns A new flattened layout tree (original is not mutated).
24
24
  */
25
25
  export function flatten(layout: Layout): Layout {
26
- if (layout.type === "child-panel") {
26
+ if (layout.type === "tab-layout") {
27
27
  layout.selected = layout.selected || 0;
28
28
  return layout;
29
29
  }
@@ -36,7 +36,7 @@ export function flatten(layout: Layout): Layout {
36
36
  const flattenedChild = flatten(child);
37
37
 
38
38
  if (
39
- flattenedChild.type === "split-panel" &&
39
+ flattenedChild.type === "split-layout" &&
40
40
  flattenedChild.orientation === layout.orientation
41
41
  ) {
42
42
  for (let j = 0; j < flattenedChild.children.length; j++) {
@@ -54,7 +54,7 @@ export function flatten(layout: Layout): Layout {
54
54
  }
55
55
 
56
56
  return {
57
- type: "split-panel",
57
+ type: "split-layout",
58
58
  orientation: layout.orientation,
59
59
  children: flattenedChildren,
60
60
  sizes: flattenedSizes,
@@ -44,7 +44,7 @@ function collect_track_positions(
44
44
  end: number,
45
45
  physics: Physics,
46
46
  ): number[] {
47
- if (panel.type === "child-panel") {
47
+ if (panel.type === "tab-layout") {
48
48
  return [start, end];
49
49
  }
50
50
 
@@ -100,7 +100,7 @@ function build_cells(
100
100
  rowEnd: number,
101
101
  physics: Physics,
102
102
  ): GridCell[] {
103
- if (panel.type === "child-panel") {
103
+ if (panel.type === "tab-layout") {
104
104
  const selected = panel.selected ?? 0;
105
105
  return [
106
106
  {
@@ -179,11 +179,11 @@ const child_template = (
179
179
  * @example
180
180
  * ```typescript
181
181
  * const layout = {
182
- * type: "split-panel",
182
+ * type: "split-layout",
183
183
  * orientation: "horizontal",
184
184
  * children: [
185
- * { type: "child-panel", tabs: "sidebar" },
186
- * { type: "child-panel", tabs: "main" }
185
+ * { type: "tab-layout", tabs: "sidebar" },
186
+ * { type: "tab-layout", tabs: "main" }
187
187
  * ],
188
188
  * sizes: [0.25, 0.75]
189
189
  * };
@@ -200,7 +200,7 @@ export function create_css_grid_layout(
200
200
  overlay?: [string, string],
201
201
  physics: Physics = DEFAULT_PHYSICS,
202
202
  ): string {
203
- if (layout.type === "child-panel") {
203
+ if (layout.type === "tab-layout") {
204
204
  const selected = layout.selected ?? 0;
205
205
  return [
206
206
  host_template("100%", "100%"),
@@ -9,7 +9,7 @@
9
9
  // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
- import type { Layout } from "./types.ts";
12
+ import type { Layout, LayoutPathTraversal } from "./types.ts";
13
13
 
14
14
  /**
15
15
  * Inserts a new child panel into the layout tree at a specified location.
@@ -28,32 +28,32 @@ import type { Layout } from "./types.ts";
28
28
  export function insert_child(
29
29
  panel: Layout,
30
30
  child: string,
31
- path: number[],
31
+ path: LayoutPathTraversal,
32
32
  orientation?: "horizontal" | "vertical",
33
33
  ): Layout {
34
34
  const createChildPanel = (childId: string): Layout => ({
35
- type: "child-panel",
35
+ type: "tab-layout",
36
36
  tabs: [childId],
37
37
  });
38
38
 
39
39
  if (path.length === 0) {
40
40
  // Insert at root level
41
- if (panel.type === "child-panel") {
42
- // Add to existing child-panel as a tab
41
+ if (panel.type === "tab-layout") {
42
+ // Add to existing tab-layout as a tab
43
43
  return {
44
- type: "child-panel",
44
+ type: "tab-layout",
45
45
  tabs: [child, ...panel.tabs],
46
46
  };
47
47
  } else if (orientation) {
48
48
  // When inserting at edge of root, wrap the entire panel in a new split
49
49
  return {
50
- type: "split-panel",
50
+ type: "split-layout",
51
51
  orientation: orientation,
52
52
  children: [createChildPanel(child), panel],
53
53
  sizes: [0.5, 0.5],
54
54
  };
55
55
  } else {
56
- // Append to existing split-panel
56
+ // Append to existing split-layout
57
57
  const newChildren = [...panel.children, createChildPanel(child)];
58
58
  const newSizes = [...panel.sizes, 1 / (newChildren.length - 1)];
59
59
  return {
@@ -69,8 +69,8 @@ export function insert_child(
69
69
 
70
70
  // Special case: when orientation is provided and restPath is empty, handle edge insertion
71
71
  if (orientation && restPath.length === 0) {
72
- // If panel is a split-panel with the same orientation, insert into its children
73
- if (panel.type === "split-panel" && panel.orientation === orientation) {
72
+ // If panel is a split-layout with the same orientation, insert into its children
73
+ if (panel.type === "split-layout" && panel.orientation === orientation) {
74
74
  const newChildren = [...panel.children];
75
75
  newChildren.splice(index, 0, createChildPanel(child));
76
76
  const newSizes = [...panel.sizes];
@@ -89,14 +89,14 @@ export function insert_child(
89
89
  : [panel, createChildPanel(child)];
90
90
 
91
91
  return {
92
- type: "split-panel",
92
+ type: "split-layout",
93
93
  orientation: orientation,
94
94
  children,
95
95
  sizes: [0.5, 0.5],
96
96
  };
97
97
  }
98
98
 
99
- if (panel.type === "child-panel") {
99
+ if (panel.type === "tab-layout") {
100
100
  // Stack into child array only when ALL of these conditions are met:
101
101
  // 1. Path has exactly one element (restPath is empty)
102
102
  // 2. Orientation was NOT explicitly provided (orientation is undefined)
@@ -117,7 +117,7 @@ export function insert_child(
117
117
 
118
118
  // Otherwise, wrap in a split panel and recurse
119
119
  const newPanel: Layout = {
120
- type: "split-panel",
120
+ type: "split-layout",
121
121
  orientation: orientation || "horizontal",
122
122
  children: [panel],
123
123
  sizes: [1],
@@ -130,7 +130,7 @@ export function insert_child(
130
130
  if (orientation && panel.children[index]) {
131
131
  // When inserting at an edge, create a split panel with the new child and existing child
132
132
  const newSplitPanel: Layout = {
133
- type: "split-panel",
133
+ type: "split-layout",
134
134
  orientation: orientation,
135
135
  children: [createChildPanel(child), panel.children[index]],
136
136
  sizes: [0.5, 0.5],
@@ -159,9 +159,9 @@ export function insert_child(
159
159
 
160
160
  const targetChild = panel.children[index];
161
161
 
162
- // Determine the orientation to pass down when navigating into a child-panel
162
+ // Determine the orientation to pass down when navigating into a tab-layout
163
163
  const childOrientation =
164
- targetChild.type === "child-panel" &&
164
+ targetChild.type === "tab-layout" &&
165
165
  restPath.length > 0 &&
166
166
  orientation !== undefined
167
167
  ? panel.orientation === "horizontal"
@@ -10,7 +10,7 @@
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
12
  import { DEFAULT_PHYSICS } from "./constants.ts";
13
- import type { Layout } from "./types.ts";
13
+ import type { Layout, LayoutPathTraversal } from "./types.ts";
14
14
 
15
15
  /**
16
16
  * Adjusts panel sizes during a drag operation on a divider.
@@ -31,7 +31,7 @@ import type { Layout } from "./types.ts";
31
31
  */
32
32
  export function redistribute_panel_sizes(
33
33
  panel: Layout,
34
- path: number[],
34
+ path: LayoutPathTraversal,
35
35
  delta: number | undefined,
36
36
  physics = DEFAULT_PHYSICS,
37
37
  ): Layout {
@@ -43,14 +43,14 @@ export function redistribute_panel_sizes(
43
43
  let current: Layout = result;
44
44
  const deltas = { horizontal: delta || 0, vertical: delta || 0 };
45
45
  for (let i = 0; i < path.length - 1; i++) {
46
- if (current.type === "split-panel") {
46
+ if (current.type === "split-layout") {
47
47
  deltas[current.orientation] /= current.sizes[path[i]];
48
48
  current = current.children[path[i]];
49
49
  }
50
50
  }
51
51
 
52
52
  // Apply the redistribution at the final path index
53
- if (current.type === "split-panel") {
53
+ if (current.type === "split-layout") {
54
54
  if (delta === undefined) {
55
55
  current.sizes = current.sizes.map((_) => 1 / current.sizes.length);
56
56
  } else {
@@ -25,14 +25,14 @@ import { EMPTY_PANEL } from "./types.ts";
25
25
  */
26
26
  export function remove_child(panel: Layout, child: string): Layout {
27
27
  // If this is a child panel, handle tab removal
28
- if (panel.type === "child-panel") {
28
+ if (panel.type === "tab-layout") {
29
29
  if (panel.tabs.includes(child)) {
30
30
  const newChild = panel.tabs.filter((c) => c !== child);
31
31
  if (newChild.length === 0) {
32
32
  return structuredClone(EMPTY_PANEL);
33
33
  }
34
34
  return {
35
- type: "child-panel",
35
+ type: "tab-layout",
36
36
  tabs: newChild,
37
37
  };
38
38
  }
@@ -45,7 +45,7 @@ export function remove_child(panel: Layout, child: string): Layout {
45
45
 
46
46
  // Try to remove the child from this split panel's children
47
47
  const index = result.children.findIndex((p) => {
48
- if (p.type === "child-panel") {
48
+ if (p.type === "tab-layout") {
49
49
  return p.tabs.includes(child);
50
50
  }
51
51
 
@@ -82,7 +82,7 @@ export function remove_child(panel: Layout, child: string): Layout {
82
82
  // Child not found at this level - recursively search children
83
83
  let modified = false;
84
84
  const newChildren = result.children.map((p) => {
85
- if (p.type === "split-panel") {
85
+ if (p.type === "split-layout") {
86
86
  const updated = remove_child(p, child);
87
87
  if (updated !== p) {
88
88
  modified = true;
@@ -47,7 +47,7 @@ export interface ViewWindow {
47
47
  * opposite `orientation` as its parent.
48
48
  */
49
49
  export interface SplitLayout {
50
- type: "split-panel";
50
+ type: "split-layout";
51
51
  children: Layout[];
52
52
  sizes: number[];
53
53
  orientation: Orientation;
@@ -57,18 +57,23 @@ export interface SplitLayout {
57
57
  * A leaf panel node that contains a single named child element.
58
58
  */
59
59
  export interface TabLayout {
60
- type: "child-panel";
60
+ type: "tab-layout";
61
61
  tabs: string[];
62
62
  selected?: number;
63
63
  }
64
64
 
65
+ /**
66
+ * Represents a panel location relative to a `Layout` struct.
67
+ */
68
+ export type LayoutPathTraversal = number[];
69
+
65
70
  /**
66
71
  * Represents a draggable divider between two panels in the layout.
67
72
  *
68
73
  * Used for hit detection.
69
74
  */
70
75
  export interface LayoutDivider {
71
- path: number[];
76
+ path: LayoutPathTraversal;
72
77
  view_window: ViewWindow;
73
78
  type: Orientation;
74
79
  }
@@ -79,7 +84,7 @@ export interface LayoutDivider {
79
84
  export interface LayoutPath {
80
85
  type: "layout-path";
81
86
  slot: string;
82
- path: number[];
87
+ path: LayoutPathTraversal;
83
88
  view_window: ViewWindow;
84
89
  column: number;
85
90
  row: number;
@@ -94,7 +99,7 @@ export interface LayoutPath {
94
99
  * An empty `Layout` with no panels.
95
100
  */
96
101
  export const EMPTY_PANEL: Layout = {
97
- type: "split-panel",
102
+ type: "split-layout",
98
103
  orientation: "horizontal",
99
104
  sizes: [],
100
105
  children: [],
@@ -162,7 +162,7 @@ export class RegularLayoutFrame extends HTMLElement {
162
162
  let new_tab_panel = this._layout.getPanel(slot, new_panel);
163
163
  if (!new_tab_panel) {
164
164
  new_tab_panel = {
165
- type: "child-panel",
165
+ type: "tab-layout",
166
166
  tabs: [slot],
167
167
  selected: 0,
168
168
  };
@@ -25,6 +25,7 @@ import type {
25
25
  TabLayout,
26
26
  OverlayMode,
27
27
  Orientation,
28
+ LayoutPathTraversal,
28
29
  } from "./layout/types.ts";
29
30
  import { calculate_intersection } from "./layout/calculate_intersect.ts";
30
31
  import { remove_child } from "./layout/remove_child.ts";
@@ -33,6 +34,7 @@ import { redistribute_panel_sizes } from "./layout/redistribute_panel_sizes.ts";
33
34
  import { updateOverlaySheet } from "./layout/generate_overlay.ts";
34
35
  import { calculate_edge } from "./layout/calculate_edge.ts";
35
36
  import { flatten } from "./layout/flatten.ts";
37
+ import { calculate_path } from "./layout/calculate_path.ts";
36
38
  import {
37
39
  DEFAULT_PHYSICS,
38
40
  type PhysicsUpdate,
@@ -151,6 +153,16 @@ export class RegularLayout extends HTMLElement {
151
153
  return calculate_intersection(col, row, this._panel);
152
154
  };
153
155
 
156
+ /**
157
+ * Calculates the index path for a panel with the given name.
158
+ *
159
+ * @param name - The name of the panel to find.
160
+ * @returns The panel's index path if found, `null` otherwise.
161
+ */
162
+ calculatePath = (name: string): number[] | null => {
163
+ return calculate_path(name, this._panel);
164
+ };
165
+
154
166
  /**
155
167
  * Sets the visual overlay state during drag-and-drop operations.
156
168
  * Displays a preview of where a panel would be placed at the given coordinates.
@@ -290,7 +302,7 @@ export class RegularLayout extends HTMLElement {
290
302
  */
291
303
  insertPanel = (
292
304
  name: string,
293
- path: number[] = [],
305
+ path: LayoutPathTraversal = [],
294
306
  split?: boolean | Orientation,
295
307
  ) => {
296
308
  let orientation: Orientation | undefined;
@@ -320,7 +332,7 @@ export class RegularLayout extends HTMLElement {
320
332
  * @returns The TabLayout containing the panel if found, null otherwise.
321
333
  */
322
334
  getPanel = (name: string, layout: Layout = this._panel): TabLayout | null => {
323
- if (layout.type === "child-panel") {
335
+ if (layout.type === "tab-layout") {
324
336
  if (layout.tabs.includes(name)) {
325
337
  return layout;
326
338
  }