regular-layout 0.2.2 → 0.4.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 (47) hide show
  1. package/README.md +110 -22
  2. package/dist/{layout → core}/types.d.ts +14 -4
  3. package/dist/extensions.d.ts +6 -1
  4. package/dist/index.d.ts +2 -1
  5. package/dist/index.js +7 -7
  6. package/dist/index.js.map +4 -4
  7. package/dist/layout/calculate_edge.d.ts +4 -4
  8. package/dist/layout/calculate_intersect.d.ts +1 -1
  9. package/dist/layout/calculate_path.d.ts +12 -0
  10. package/dist/layout/calculate_presize_paths.d.ts +10 -0
  11. package/dist/layout/flatten.d.ts +1 -1
  12. package/dist/layout/generate_grid.d.ts +5 -5
  13. package/dist/layout/generate_overlay.d.ts +18 -2
  14. package/dist/layout/insert_child.d.ts +2 -2
  15. package/dist/layout/redistribute_panel_sizes.d.ts +2 -2
  16. package/dist/layout/remove_child.d.ts +1 -1
  17. package/dist/model/overlay_controller.d.ts +34 -0
  18. package/dist/model/presize_queue.d.ts +17 -0
  19. package/dist/regular-layout-tab.d.ts +1 -1
  20. package/dist/regular-layout.d.ts +44 -9
  21. package/package.json +5 -4
  22. package/src/{layout → core}/constants.ts +2 -2
  23. package/src/{layout → core}/types.ts +17 -5
  24. package/src/extensions.ts +13 -1
  25. package/src/index.ts +3 -1
  26. package/src/layout/calculate_edge.ts +17 -8
  27. package/src/layout/calculate_intersect.ts +13 -4
  28. package/src/layout/calculate_path.ts +53 -0
  29. package/src/layout/calculate_presize_paths.ts +93 -0
  30. package/src/layout/flatten.ts +4 -4
  31. package/src/layout/generate_grid.ts +8 -8
  32. package/src/layout/generate_overlay.ts +48 -16
  33. package/src/layout/insert_child.ts +16 -16
  34. package/src/layout/redistribute_panel_sizes.ts +5 -5
  35. package/src/layout/remove_child.ts +6 -6
  36. package/src/model/overlay_controller.ts +162 -0
  37. package/src/model/presize_queue.ts +79 -0
  38. package/src/regular-layout-frame.ts +3 -3
  39. package/src/regular-layout-tab.ts +1 -1
  40. package/src/regular-layout.ts +180 -133
  41. package/themes/borland.css +103 -0
  42. package/themes/chicago.css +55 -49
  43. package/themes/fluxbox.css +64 -60
  44. package/themes/gibson.css +174 -164
  45. package/themes/hotdog.css +53 -47
  46. package/themes/lorax.css +82 -75
  47. /package/dist/{layout → core}/constants.d.ts +0 -0
@@ -1,8 +1,8 @@
1
- import { type Physics } from "./constants";
2
- import type { Layout, LayoutPath } from "./types";
1
+ import { type Physics } from "../core/constants";
2
+ import type { Layout, LayoutPath } from "../core/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;
@@ -1,4 +1,4 @@
1
- import type { LayoutPath, LayoutDivider, Layout } from "./types.ts";
1
+ import type { LayoutPath, LayoutDivider, Layout } from "../core/types.ts";
2
2
  /**
3
3
  * Determines which panel or divider is located at a given position in the
4
4
  * layout.
@@ -0,0 +1,12 @@
1
+ import type { Layout } from "../core/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;
@@ -0,0 +1,10 @@
1
+ import type { Layout, LayoutPath } from "../core/types.ts";
2
+ /**
3
+ * Walks a layout tree and returns a {@link LayoutPath} for every visible
4
+ * panel, keyed by panel name. Each path's `column`/`row` are set to the
5
+ * center of the panel's view window, with offsets of 0.5.
6
+ *
7
+ * @param layout - The layout tree to walk.
8
+ * @returns A record mapping panel names to their layout paths.
9
+ */
10
+ export declare function calculate_presize_paths(layout: Layout): Record<string, LayoutPath>;
@@ -1,4 +1,4 @@
1
- import type { Layout } from "./types.ts";
1
+ import type { Layout } from "../core/types.ts";
2
2
  /**
3
3
  * Flattens the layout tree by merging parent and child split panels that have
4
4
  * the same orientation and type.
@@ -1,5 +1,5 @@
1
- import { type Physics } from "./constants.ts";
2
- import type { Layout } from "./types.ts";
1
+ import { type Physics } from "../core/constants.ts";
2
+ import type { Layout } from "../core/types.ts";
3
3
  /**
4
4
  * Generates CSS Grid styles to render a layout tree.
5
5
  * Creates grid-template-rows, grid-template-columns, and positioning rules for
@@ -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,2 +1,18 @@
1
- import type { LayoutPath } from "./types";
2
- export declare function updateOverlaySheet(slot: string, box: DOMRect, style: CSSStyleDeclaration, drag_target: LayoutPath | null, physics?: import("./constants").Physics): string;
1
+ import type { LayoutPath, ViewWindow } from "../core/types";
2
+ /**
3
+ * Converts a {@link ViewWindow} to element-relative pixel coordinates,
4
+ * accounting for padding and optionally CSS `gap` and child `margin`.
5
+ *
6
+ * @param window - The view window in normalized 0–1 coordinates.
7
+ * @param box - The element's bounding client rect.
8
+ * @param style - The element's computed style (for padding).
9
+ * @param margin - Optional child element's computed style (for margin inset).
10
+ * @returns Pixel coordinates relative to the element's border-box origin.
11
+ */
12
+ export declare function viewWindowToLocalRect(window: ViewWindow, box: DOMRect, style: CSSStyleDeclaration, margin?: CSSStyleDeclaration): {
13
+ x: number;
14
+ y: number;
15
+ width: number;
16
+ height: number;
17
+ };
18
+ export declare function updateOverlaySheet(slot: string, box: DOMRect, style: CSSStyleDeclaration, drag_target: LayoutPath | null, physics?: import("../core/constants").Physics, margin?: CSSStyleDeclaration): string;
@@ -1,4 +1,4 @@
1
- import type { Layout } from "./types.ts";
1
+ import type { Layout, LayoutPathTraversal } from "../core/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 "../core/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("../core/constants.ts").Physics): Layout;
@@ -1,4 +1,4 @@
1
- import type { Layout } from "./types.ts";
1
+ import type { Layout } from "../core/types.ts";
2
2
  /**
3
3
  * Removes a child panel from the layout tree by its name.
4
4
  *
@@ -0,0 +1,34 @@
1
+ import type { Layout, LayoutPath, OverlayMode } from "../core/types.ts";
2
+ import type { Physics } from "../core/constants.ts";
3
+ import type { PresizeQueue } from "./presize_queue.ts";
4
+ export interface OverlayHost {
5
+ readonly panel: Layout;
6
+ readonly physics: Physics;
7
+ readonly stylesheet: CSSStyleSheet;
8
+ readonly presizeQueue: PresizeQueue;
9
+ relativeCoordinates(event: {
10
+ clientX: number;
11
+ clientY: number;
12
+ }, recalculate: boolean): [number, number, DOMRect, CSSStyleDeclaration];
13
+ restore(layout: Layout, isFlattened?: boolean): Promise<void>;
14
+ querySelector(selectors: string): Element | null;
15
+ dispatchEvent(event: Event): boolean;
16
+ }
17
+ /**
18
+ * Manages overlay state during drag-and-drop panel rearrangement.
19
+ *
20
+ * Handles rendering a preview of where a dragged panel would land,
21
+ * and committing or cancelling the placement when the drag ends.
22
+ */
23
+ export declare class OverlayController {
24
+ private _host;
25
+ constructor(_host: OverlayHost);
26
+ set(event: {
27
+ clientX: number;
28
+ clientY: number;
29
+ }, { slot }: LayoutPath, className?: string, mode?: OverlayMode): Promise<void>;
30
+ clear(event: {
31
+ clientX: number;
32
+ clientY: number;
33
+ } | null, { slot, layout }: LayoutPath, className?: string): Promise<void>;
34
+ }
@@ -0,0 +1,17 @@
1
+ import type { Layout } from "../core/types.ts";
2
+ /**
3
+ * Manages cancelable pre-resize gating for layout updates.
4
+ *
5
+ * Before each layout change, a cancelable `resize-before` event is dispatched
6
+ * on the target. If the event is cancelled via `preventDefault()`, the update
7
+ * is suspended until {@link resume} is called. Concurrent updates are queued
8
+ * and processed sequentially.
9
+ */
10
+ export declare class PresizeQueue {
11
+ #private;
12
+ private _target;
13
+ private _eventName;
14
+ constructor(_target: EventTarget, _eventName: string);
15
+ run(layout: Layout, fn: () => void): Promise<void>;
16
+ resume(): void;
17
+ }
@@ -1,4 +1,4 @@
1
- import type { TabLayout } from "./layout/types.ts";
1
+ import type { TabLayout } from "./core/types.ts";
2
2
  import type { RegularLayout } from "./regular-layout.ts";
3
3
  /**
4
4
  * A custom HTML element representing an individual tab in a tab panel.
@@ -1,5 +1,5 @@
1
- import type { LayoutPath, Layout, TabLayout, OverlayMode, Orientation } from "./layout/types.ts";
2
- import { type PhysicsUpdate, type Physics } from "./layout/constants.ts";
1
+ import type { LayoutPath, Layout, TabLayout, OverlayMode, Orientation, LayoutPathTraversal, ViewWindow } from "./core/types.ts";
2
+ import { type PhysicsUpdate, type Physics } from "./core/constants.ts";
3
3
  /**
4
4
  * An interface which models the fields of `PointerEvent` that
5
5
  * `<regular-layour>` actually uses, making it easier to sub out with an
@@ -67,6 +67,8 @@ export declare class RegularLayout extends HTMLElement {
67
67
  private _cursor_override;
68
68
  private _dimensions?;
69
69
  private _physics;
70
+ private _presizeQueue;
71
+ private _overlayController;
70
72
  constructor();
71
73
  connectedCallback(): void;
72
74
  disconnectedCallback(): void;
@@ -78,6 +80,13 @@ export declare class RegularLayout extends HTMLElement {
78
80
  * @returns Panel information if a panel is at that position, null otherwise.
79
81
  */
80
82
  calculateIntersect: (coordinates: PointerEventCoordinates) => LayoutPath | null;
83
+ /**
84
+ * Calculates the index path for a panel with the given name.
85
+ *
86
+ * @param name - The name of the panel to find.
87
+ * @returns The panel's index path if found, `null` otherwise.
88
+ */
89
+ calculatePath: (name: string) => number[] | null;
81
90
  /**
82
91
  * Sets the visual overlay state during drag-and-drop operations.
83
92
  * Displays a preview of where a panel would be placed at the given coordinates.
@@ -92,7 +101,7 @@ export declare class RegularLayout extends HTMLElement {
92
101
  * the target, "absolute" positions the panel absolutely. Defaults to
93
102
  * "absolute".
94
103
  */
95
- setOverlayState: (event: PointerEventCoordinates, { slot }: LayoutPath, className?: string, mode?: OverlayMode) => void;
104
+ setOverlayState: (event: PointerEventCoordinates, target: LayoutPath, className?: string, mode?: OverlayMode) => Promise<void>;
96
105
  /**
97
106
  * Clears the overlay state and commits the panel placement.
98
107
  *
@@ -105,7 +114,7 @@ export declare class RegularLayout extends HTMLElement {
105
114
  * @param mode - Overlay rendering mode that was used, must match the mode
106
115
  * passed to `setOverlayState`. Defaults to "absolute".
107
116
  */
108
- clearOverlayState: (event: PointerEventCoordinates | null, { slot, layout }: LayoutPath, className?: string) => void;
117
+ clearOverlayState: (event: PointerEventCoordinates | null, target: LayoutPath, className?: string) => Promise<void>;
109
118
  /**
110
119
  * Inserts a new panel into the layout at a specified path.
111
120
  *
@@ -117,13 +126,13 @@ export declare class RegularLayout extends HTMLElement {
117
126
  * the new `SplitPanel` _if_ there is an option of orientation (e.g. if
118
127
  * the layout had no pre-existing `SplitPanel`)
119
128
  */
120
- insertPanel: (name: string, path?: number[], split?: boolean | Orientation) => void;
129
+ insertPanel: (name: string, path?: LayoutPathTraversal, split?: boolean | Orientation) => Promise<void>;
121
130
  /**
122
131
  * Removes a panel from the layout by name.
123
132
  *
124
133
  * @param name - Name of the panel to remove
125
134
  */
126
- removePanel: (name: string) => void;
135
+ removePanel: (name: string) => Promise<void>;
127
136
  /**
128
137
  * Retrieves a panel by name from the layout tree.
129
138
  *
@@ -135,20 +144,36 @@ export declare class RegularLayout extends HTMLElement {
135
144
  /**
136
145
  * Clears the entire layout, unslotting all panels.
137
146
  */
138
- clear: () => void;
147
+ clear: () => Promise<void>;
148
+ /**
149
+ * Restores the layout from a saved state synchronously, without
150
+ * dispatching the `regular-layout-before-resize` event.
151
+ *
152
+ * @param layout - The layout tree to restore
153
+ */
154
+ restoreSync: (layout: Layout, _is_flattened?: boolean) => void;
139
155
  /**
140
156
  * Restores the layout from a saved state.
141
157
  *
158
+ * Before applying, dispatches a cancelable `regular-layout-before-resize`
159
+ * event. If the event is cancelled via `preventDefault()`, the layout
160
+ * update is suspended until {@link resumeResize} is called.
161
+ *
142
162
  * @param layout - The layout tree to restore
143
163
  *
144
164
  * @example
145
165
  * ```typescript
146
166
  * const layout = document.querySelector('regular-layout');
147
167
  * const savedState = JSON.parse(localStorage.getItem('layout'));
148
- * layout.restore(savedState);
168
+ * await layout.restore(savedState);
149
169
  * ```
150
170
  */
151
- restore: (layout: Layout, _is_flattened?: boolean) => void;
171
+ restore: (layout: Layout, _is_flattened?: boolean) => Promise<void>;
172
+ /**
173
+ * Resumes a layout update that was suspended by cancelling the
174
+ * `regular-layout-before-resize` event.
175
+ */
176
+ resumeResize: () => void;
152
177
  /**
153
178
  * Serializes the current layout state, which can be restored via `restore`.
154
179
  *
@@ -188,6 +213,15 @@ export declare class RegularLayout extends HTMLElement {
188
213
  * - box: The layout element's bounding rectangle
189
214
  */
190
215
  relativeCoordinates: (event: PointerEventCoordinates, recalculate_bounds?: boolean) => [number, number, DOMRect, CSSStyleDeclaration];
216
+ /**
217
+ * Converts a {@link ViewWindow} (normalized 0–1 coordinates) to a
218
+ * `DOMRect` in screen pixels, accounting for padding and optionally
219
+ * CSS `gap` and child `margin`.
220
+ *
221
+ * @param window - The view window to convert.
222
+ * @returns A `DOMRect` representing the window in screen coordinates.
223
+ */
224
+ realCoordinates: (window: ViewWindow, child?: HTMLElement) => DOMRect;
191
225
  /**
192
226
  * Calculates the Euclidean distance in pixels between the current pointer
193
227
  * coordinates and a drag target's position within the layout.
@@ -199,6 +233,7 @@ export declare class RegularLayout extends HTMLElement {
199
233
  * target.
200
234
  */
201
235
  diffCoordinates: (event: PointerEventCoordinates, drag_target: LayoutPath) => number;
236
+ private create_overlay_host;
202
237
  private onDblClick;
203
238
  private onPointerDown;
204
239
  private onPointerMove;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "regular-layout",
3
- "version": "0.2.2",
3
+ "version": "0.4.0",
4
4
  "description": "A regular CSS `grid` container",
5
5
  "keywords": [],
6
6
  "license": "Apache-2.0",
@@ -19,10 +19,10 @@
19
19
  ],
20
20
  "scripts": {
21
21
  "build": "tsx build.ts",
22
- "build:watch": "tsx build.ts --watch",
22
+ "watch": "tsx build.ts --watch",
23
23
  "clean": "rm -rf dist",
24
- "test": "playwright test tests/integration tests/unit",
25
- "test:perf": "playwright test --workers=1 ./benchmarks",
24
+ "test": "tsx test.ts",
25
+ "bench": "playwright test --workers=1 ./benchmarks",
26
26
  "example": "tsx serve.ts",
27
27
  "deploy": "tsx deploy.ts",
28
28
  "lint": "biome lint src tests",
@@ -34,6 +34,7 @@
34
34
  "@playwright/test": "^1.57.0",
35
35
  "@types/node": "^22.10.5",
36
36
  "esbuild": "^0.27.2",
37
+ "monocart-coverage-reports": "^2.12.2",
37
38
  "tsx": "^4.21.0",
38
39
  "typescript": "^5.9.3"
39
40
  }
@@ -110,8 +110,8 @@ export const DEFAULT_PHYSICS: Physics = Object.freeze({
110
110
  SHOULD_ROUND: false,
111
111
  OVERLAY_CLASSNAME: "overlay",
112
112
  MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD: 0.15,
113
- SPLIT_EDGE_TOLERANCE: 0.25,
114
- SPLIT_ROOT_EDGE_TOLERANCE: 0.01,
113
+ SPLIT_EDGE_TOLERANCE: 0.33,
114
+ SPLIT_ROOT_EDGE_TOLERANCE: 0.03,
115
115
  GRID_TRACK_COLLAPSE_TOLERANCE: 0.001,
116
116
  OVERLAY_DEFAULT: "absolute",
117
117
  GRID_DIVIDER_SIZE: 6,
@@ -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;
@@ -90,11 +95,18 @@ export interface LayoutPath {
90
95
  layout: Layout;
91
96
  }
92
97
 
98
+ /**
99
+ * The detail payload of the `regular-layout-before-resize` event.
100
+ */
101
+ export interface PresizeDetail {
102
+ calculatePresizePaths(): Record<string, LayoutPath>;
103
+ }
104
+
93
105
  /**
94
106
  * An empty `Layout` with no panels.
95
107
  */
96
108
  export const EMPTY_PANEL: Layout = {
97
- type: "split-panel",
109
+ type: "split-layout",
98
110
  orientation: "horizontal",
99
111
  sizes: [],
100
112
  children: [],
package/src/extensions.ts CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { RegularLayout } from "./regular-layout.ts";
13
13
  import { RegularLayoutFrame } from "./regular-layout-frame.ts";
14
- import type { Layout } from "./layout/types.ts";
14
+ import type { Layout, PresizeDetail } from "./core/types.ts";
15
15
  import { RegularLayoutTab } from "./regular-layout-tab.ts";
16
16
 
17
17
  customElements.define("regular-layout", RegularLayout);
@@ -59,6 +59,12 @@ declare global {
59
59
  options?: { signal: AbortSignal },
60
60
  ): void;
61
61
 
62
+ addEventListener(
63
+ name: "regular-layout-before-resize",
64
+ cb: (e: RegularLayoutPresizeEvent) => void,
65
+ options?: { signal: AbortSignal },
66
+ ): void;
67
+
62
68
  removeEventListener(
63
69
  name: "regular-layout-update",
64
70
  cb: (e: RegularLayoutEvent) => void,
@@ -68,7 +74,13 @@ declare global {
68
74
  name: "regular-layout-before-update",
69
75
  cb: (e: RegularLayoutEvent) => void,
70
76
  ): void;
77
+
78
+ removeEventListener(
79
+ name: "regular-layout-before-resize",
80
+ cb: (e: RegularLayoutPresizeEvent) => void,
81
+ ): void;
71
82
  }
72
83
  }
73
84
 
74
85
  export type RegularLayoutEvent = CustomEvent<Layout>;
86
+ export type RegularLayoutPresizeEvent = CustomEvent<PresizeDetail>;
package/src/index.ts CHANGED
@@ -59,10 +59,12 @@
59
59
  * @packageDocumentation
60
60
  */
61
61
 
62
- export type * from "./layout/types.ts";
62
+ export type * from "./core/types.ts";
63
63
 
64
64
  export { RegularLayout } from "./regular-layout.ts";
65
65
  export { RegularLayoutFrame } from "./regular-layout-frame.ts";
66
66
 
67
+ export type * from "./extensions.ts";
68
+
67
69
  // Side effects
68
70
  import "./extensions.ts";
@@ -9,13 +9,19 @@
9
9
  // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
- import { DEFAULT_PHYSICS, type Physics } from "./constants";
12
+ import { DEFAULT_PHYSICS, type Physics } from "../core/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 "../core/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 "../core/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;
@@ -124,7 +130,10 @@ function calculate_intersection_recursive(
124
130
  }
125
131
 
126
132
  // Check if position falls within this child's bounds
127
- if (position >= current_pos && position < next_pos) {
133
+ if (
134
+ position >= current_pos &&
135
+ (position < next_pos || i === panel.children.length - 1)
136
+ ) {
128
137
  return calculate_intersection_recursive(
129
138
  column,
130
139
  row,
@@ -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 "../core/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
+ }