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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# `<regular-layout>`

[![Build Status](https://img.shields.io/github/actions/workflow/status/texodus/regular-layout/build.yaml?event=push&style=for-the-badge)](https://github.com/texodus/regular-layout/actions/workflows/build.yaml)
[![npm](https://img.shields.io/npm/v/regular-layout.svg?style=for-the-badge)](https://www.npmjs.com/package/regular-layout)
[![bundlephobia](https://img.shields.io/bundlephobia/minzip/regular-layout?style=for-the-badge)](https://bundlephobia.com/package/regular-layout)
[![Build Status](https://img.shields.io/github/actions/workflow/status/texodus/regular-layout/build.yaml?event=push&style=for-the-badge)](https://github.com/texodus/regular-layout/actions/workflows/build.yaml)

A library for resizable & repositionable panel layouts, using
[CSS `grid`](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout).
Expand Down
11 changes: 11 additions & 0 deletions build.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
// ░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░█░░░█▀█░█░█░█▀█░█░█░▀█▀░▀▄░░░░░░░░
// ░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░█░░░█▀█░░█░░█░█░█░█░░█░░░▄▀░░░░░░░
// ░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░░▀░░▀░░░░░░░░░
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ * 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 * as esbuild from "esbuild";
import { execSync } from "node:child_process";

Expand Down
73 changes: 73 additions & 0 deletions deploy.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
// ░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░█░░░█▀█░█░█░█▀█░█░█░▀█▀░▀▄░░░░░░░░
// ░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░█░░░█▀█░░█░░█░█░█░█░░█░░░▄▀░░░░░░░
// ░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░░▀░░▀░░░░░░░░░
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ * 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 { execSync } from "node:child_process";
import { cpSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

function exec(command, options = {}) {
console.log(`> ${command}`);
return execSync(command, { stdio: "inherit", ...options });
}

function execOutput(command) {
return execSync(command, { encoding: "utf-8" }).trim();
}

try {
console.log("Checking git status...");
const gitStatus = execOutput("git status --porcelain");
if (gitStatus) {
console.error("Error: Git staging area is not clean. Please commit or stash your changes.");
console.error(gitStatus);
process.exit(1);
}

console.log("Building project...");
exec("pnpm run build");

console.log("Preparing deployment files...");
const currentBranch = execOutput("git rev-parse --abbrev-ref HEAD");
const currentCommit = execOutput("git rev-parse --short HEAD");
const tempDir = mkdtempSync(join(tmpdir(), "gh-pages-"));
cpSync("dist", join(tempDir, "dist"), { recursive: true });
cpSync("examples", tempDir, { recursive: true });

console.log("Switching to gh-pages branch...");
let ghPagesExists = false;
try {
execSync("git show-ref --verify --quiet refs/heads/gh-pages");
ghPagesExists = true;
} catch (e) {
throw new Error("No gh-pages branch found");
}

if (ghPagesExists) {
exec("git checkout gh-pages");
} else {
throw new Error("No gh-pages branch found");
}

console.log("Copying build artifacts...");
cpSync(tempDir, ".", { recursive: true });
console.log("Committing changes...");
exec("git add -A");
exec(`git commit -m "Deploy from ${currentBranch} @ ${currentCommit}"`);

console.log(`Returning to ${currentBranch}...`);
exec(`git checkout ${currentBranch}`);
rmSync(tempDir, { recursive: true, force: true });
console.log("Deployment complete! gh-pages branch updated locally.");
} catch (error) {
console.error("Deployment failed:", error.message);
process.exit(1);
}
3 changes: 2 additions & 1 deletion examples/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ regular-layout-frame::part(tab) {
display: flex;
flex: 1 1 150px;
align-items: center;
padding: 0 12px;
font-size: 10px;
padding: 0 8px;
cursor: pointer;
max-width: 150px;
text-overflow: ellipsis;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"clean": "rm -rf dist",
"test": "playwright test",
"example": "npx http-server . -p 8000",
"deploy": "node deploy.mjs",
"lint": "biome lint src tests",
"format": "biome format --write src tests",
"check": "biome check --write src tests"
Expand Down
1 change: 1 addition & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default defineConfig({
},
],
webServer: {
reuseExistingServer: true,
command: "npx http-server . -p 8081",
url: "http://127.0.0.1:8081",
},
Expand Down
240 changes: 115 additions & 125 deletions src/common/calculate_split.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,155 +30,145 @@ import {
* @returns A new `LayoutPath` reflecting the updated (maybe) `"split-panel"`,
* which is enough to draw the overlay.
*/
function handle_matching_orientation(
col: number,
row: number,
panel: Layout,
slot: string,
drop_target: LayoutPath,
is_before: boolean,
): LayoutPath {
if (drop_target.path.length === 0) {
const insert_index = is_before ? 0 : 1;
const new_panel = insert_child(panel, slot, [insert_index]);
if (is_before) {
return calculate_intersection(col, row, new_panel, false);
} else {
const new_drop_target = calculate_intersection(
col,
row,
new_panel,
false,
);
return {
...new_drop_target,
path: [0],
};
}
} else {
const path_without_last = drop_target.path.slice(0, -1);
const last_index = drop_target.path[drop_target.path.length - 1];
const insert_index = is_before ? last_index : last_index + 1;
const new_panel = insert_child(panel, slot, [
...path_without_last,
insert_index,
]);

if (is_before) {
return calculate_intersection(col, row, new_panel, false);
} else {
const new_drop_target = calculate_intersection(
col,
row,
new_panel,
false,
);
return {
...new_drop_target,
path: [...path_without_last, last_index],
};
}
}
}

function handle_cross_orientation(
col: number,
row: number,
panel: Layout,
slot: string,
drop_target: LayoutPath,
insert_index: number,
new_orientation: "horizontal" | "vertical",
): LayoutPath {
const original_path = drop_target.path;
const new_panel = insert_child(
panel,
slot,
[...original_path, insert_index],
new_orientation,
);
const new_drop_target = calculate_intersection(col, row, new_panel, false);
return {
...new_drop_target,
slot,
path: [...original_path, insert_index],
};
}

export function calculate_split(
col: number,
row: number,
panel: Layout,
slot: string,
drop_target: LayoutPath,
): LayoutPath {
if (
const is_column_edge =
drop_target.column_offset < SPLIT_EDGE_TOLERANCE ||
drop_target.column_offset > 1 - SPLIT_EDGE_TOLERANCE
) {
drop_target.column_offset > 1 - SPLIT_EDGE_TOLERANCE;
const is_row_edge =
drop_target.row_offset < SPLIT_EDGE_TOLERANCE ||
drop_target.row_offset > 1 - SPLIT_EDGE_TOLERANCE;

if (is_column_edge) {
const is_before = drop_target.column_offset < SPLIT_EDGE_TOLERANCE;
if (drop_target.orientation === "horizontal") {
const is_before = drop_target.column_offset < SPLIT_EDGE_TOLERANCE;
if (drop_target.path.length === 0) {
const insert_index = is_before ? 0 : 1;
const new_panel = insert_child(panel, slot, [insert_index]);
// When inserting before, point to new panel; when after, keep original
if (is_before) {
drop_target = calculate_intersection(col, row, new_panel, false);
} else {
const new_drop_target = calculate_intersection(
col,
row,
new_panel,
false,
);
drop_target = {
...new_drop_target,
path: [0],
};
}
} else {
const path_without_last = drop_target.path.slice(0, -1);
const last_index = drop_target.path[drop_target.path.length - 1];
const insert_index = is_before ? last_index : last_index + 1;
const new_panel = insert_child(panel, slot, [
...path_without_last,
insert_index,
]);
// When inserting before, point to new panel; when after, keep original
if (is_before) {
drop_target = calculate_intersection(col, row, new_panel, false);
} else {
// Keep the original panel but update view_window from new layout
const new_drop_target = calculate_intersection(
col,
row,
new_panel,
false,
);
drop_target = {
...new_drop_target,
path: [...path_without_last, last_index],
};
}
}
drop_target = handle_matching_orientation(
col,
row,
panel,
slot,
drop_target,
is_before,
);
} else {
const insert_index =
drop_target.column_offset < SPLIT_EDGE_TOLERANCE ? 0 : 1;
const original_path = drop_target.path;
const new_panel = insert_child(
const insert_index = is_before ? 0 : 1;
drop_target = handle_cross_orientation(
col,
row,
panel,
slot,
[...original_path, insert_index],
drop_target,
insert_index,
"horizontal",
);
drop_target = calculate_intersection(col, row, new_panel, false);
// Override to point to the newly inserted panel
drop_target = {
...drop_target,
slot,
path: [...original_path, insert_index],
};
}

if (drop_target) {
drop_target.is_edge = true;
}
} else if (
drop_target.row_offset < SPLIT_EDGE_TOLERANCE ||
drop_target.row_offset > 1 - SPLIT_EDGE_TOLERANCE
) {
drop_target.is_edge = true;
} else if (is_row_edge) {
const is_before = drop_target.row_offset < SPLIT_EDGE_TOLERANCE;
if (drop_target.orientation === "vertical") {
const is_before = drop_target.row_offset < SPLIT_EDGE_TOLERANCE;
if (drop_target.path.length === 0) {
const insert_index = is_before ? 0 : 1;
const new_panel = insert_child(panel, slot, [insert_index]);
// When inserting before, point to new panel; when after, keep original
if (is_before) {
drop_target = calculate_intersection(col, row, new_panel, false);
} else {
const new_drop_target = calculate_intersection(
col,
row,
new_panel,
false,
);
drop_target = {
...new_drop_target,
path: [0],
};
}
} else {
const path_without_last = drop_target.path.slice(0, -1);
const last_index = drop_target.path[drop_target.path.length - 1];
const insert_index = is_before ? last_index : last_index + 1;
const new_panel = insert_child(panel, slot, [
...path_without_last,
insert_index,
]);
// When inserting before, point to new panel; when after, keep original
if (is_before) {
drop_target = calculate_intersection(col, row, new_panel, false);
} else {
// Keep the original panel but update view_window from new layout
const new_drop_target = calculate_intersection(
col,
row,
new_panel,
false,
);
drop_target = {
...new_drop_target,
path: [...path_without_last, last_index],
};
}
}
drop_target = handle_matching_orientation(
col,
row,
panel,
slot,
drop_target,
is_before,
);
} else {
const insert_index =
drop_target.row_offset < SPLIT_EDGE_TOLERANCE ? 0 : 1;
const original_path = drop_target.path;
const new_panel = insert_child(
const insert_index = is_before ? 0 : 1;
drop_target = handle_cross_orientation(
col,
row,
panel,
slot,
[...original_path, insert_index],
drop_target,
insert_index,
"vertical",
);
drop_target = calculate_intersection(col, row, new_panel, false);
// Override to point to the newly inserted panel
drop_target = {
...drop_target,
slot,
path: [...original_path, insert_index],
};
}

if (drop_target) {
drop_target.is_edge = true;
}
drop_target.is_edge = true;
}

return drop_target;
Expand Down
Loading