Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/layout/calculate_path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
// ░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░█░░░█▀█░█░█░█▀█░█░█░▀█▀░▀▄░░░░░░░░
// ░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░█░░░█▀█░░█░░█░█░█░█░░█░░░▄▀░░░░░░░
// ░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░░▀░░▀░░░░░░░░░
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ * Copyright (c) 2026, the Regular Layout Authors. This file is part * ┃
// ┃ * of the Regular Layout library, distributed under the terms of the * ┃
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import type { Layout } from "./types.ts";

/**
* Calculates the index path for a panel with the given name.
*
* Traverses the layout tree to find the named panel and returns the
* index path describing its position in the tree.
*
* @param name - The name of the panel to find.
* @param layout - The layout tree to search.
* @returns The panel's index path if found, `null` otherwise.
*/
export function calculate_path(name: string, layout: Layout): number[] | null {
return calculate_path_recursive(name, layout, []);
}

function calculate_path_recursive(
name: string,
panel: Layout,
path: number[],
): number[] | null {
if (panel.type === "child-panel") {
if (!panel.tabs.includes(name)) {
return null;
}

return path;
}

for (let i = 0; i < panel.children.length; i++) {
const result = calculate_path_recursive(name, panel.children[i], [
...path,
i,
]);

if (result) {
return result;
}
}

return null;
}
11 changes: 11 additions & 0 deletions src/regular-layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { redistribute_panel_sizes } from "./layout/redistribute_panel_sizes.ts";
import { updateOverlaySheet } from "./layout/generate_overlay.ts";
import { calculate_edge } from "./layout/calculate_edge.ts";
import { flatten } from "./layout/flatten.ts";
import { calculate_path } from "./layout/calculate_path.ts";
import {
DEFAULT_PHYSICS,
type PhysicsUpdate,
Expand Down Expand Up @@ -151,6 +152,16 @@ export class RegularLayout extends HTMLElement {
return calculate_intersection(col, row, this._panel);
};

/**
* Calculates the index path for a panel with the given name.
*
* @param name - The name of the panel to find.
* @returns The panel's index path if found, `null` otherwise.
*/
calculatePath = (name: string): number[] | null => {
return calculate_path(name, this._panel);
};

/**
* Sets the visual overlay state during drag-and-drop operations.
* Displays a preview of where a panel would be placed at the given coordinates.
Expand Down
69 changes: 69 additions & 0 deletions tests/unit/calculate_path.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
// ░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░█░░░█▀█░█░█░█▀█░█░█░▀█▀░▀▄░░░░░░░░
// ░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░█░░░█▀█░░█░░█░█░█░█░░█░░░▄▀░░░░░░░
// ░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░░▀░░▀░░░░░░░░░
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ * Copyright (c) 2026, the Regular Layout Authors. This file is part * ┃
// ┃ * of the Regular Layout library, distributed under the terms of the * ┃
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import { expect, test } from "@playwright/test";
import { calculate_path } from "../../src/layout/calculate_path.ts";
import { LAYOUTS } from "../helpers/fixtures.ts";

test("returns null for a name not in the layout", () => {
const result = calculate_path("ZZZ", LAYOUTS.SINGLE_AAA);
expect(result).toBeNull();
});

test("returns null for an empty layout", () => {
const result = calculate_path("AAA", {
type: "split-panel",
orientation: "horizontal",
sizes: [],
children: [],
});
expect(result).toBeNull();
});

test("finds a single panel", () => {
const result = calculate_path("AAA", LAYOUTS.SINGLE_AAA);
expect(result).toStrictEqual([]);
});

test("finds left panel in horizontal split", () => {
const result = calculate_path("AAA", LAYOUTS.TWO_HORIZONTAL);
expect(result).toStrictEqual([0]);
});

test("finds right panel in horizontal split", () => {
const result = calculate_path("BBB", LAYOUTS.TWO_HORIZONTAL);
expect(result).toStrictEqual([1]);
});

test("finds top panel in vertical split", () => {
const result = calculate_path("AAA", LAYOUTS.TWO_VERTICAL);
expect(result).toStrictEqual([0]);
});

test("finds panel in nested layout", () => {
const result = calculate_path("AAA", LAYOUTS.NESTED_BASIC);
expect(result).toStrictEqual([0, 0]);
});

test("finds deeply nested panel", () => {
const result = calculate_path("BBB", LAYOUTS.DEEPLY_NESTED);
expect(result).toStrictEqual([0, 1]);
});

test("finds non-selected tab by name", () => {
const result = calculate_path("BBB", LAYOUTS.SINGLE_TABS);
expect(result).toStrictEqual([]);
});

test("finds leaf panel in deeply nested alt layout", () => {
const result = calculate_path("DDD", LAYOUTS.DEEPLY_NESTED_ALT);
expect(result).toStrictEqual([1]);
});
Loading