Skip to content

Commit 2342dc9

Browse files
authored
Merge pull request #2 from texodus/wat
Refactoring and dev tools
2 parents 4100acc + 3f2b85d commit 2342dc9

24 files changed

+2705
-2781
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# `<regular-layout>`
22

3-
[![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)
43
[![npm](https://img.shields.io/npm/v/regular-layout.svg?style=for-the-badge)](https://www.npmjs.com/package/regular-layout)
4+
[![bundlephobia](https://img.shields.io/bundlephobia/minzip/regular-layout?style=for-the-badge)](https://bundlephobia.com/package/regular-layout)
5+
[![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)
56

67
A library for resizable & repositionable panel layouts, using
78
[CSS `grid`](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout).

build.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
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+
112
import * as esbuild from "esbuild";
213
import { execSync } from "node:child_process";
314

deploy.mjs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 { execSync } from "node:child_process";
13+
import { cpSync, mkdtempSync, rmSync } from "node:fs";
14+
import { tmpdir } from "node:os";
15+
import { join } from "node:path";
16+
17+
function exec(command, options = {}) {
18+
console.log(`> ${command}`);
19+
return execSync(command, { stdio: "inherit", ...options });
20+
}
21+
22+
function execOutput(command) {
23+
return execSync(command, { encoding: "utf-8" }).trim();
24+
}
25+
26+
try {
27+
console.log("Checking git status...");
28+
const gitStatus = execOutput("git status --porcelain");
29+
if (gitStatus) {
30+
console.error("Error: Git staging area is not clean. Please commit or stash your changes.");
31+
console.error(gitStatus);
32+
process.exit(1);
33+
}
34+
35+
console.log("Building project...");
36+
exec("pnpm run build");
37+
38+
console.log("Preparing deployment files...");
39+
const currentBranch = execOutput("git rev-parse --abbrev-ref HEAD");
40+
const currentCommit = execOutput("git rev-parse --short HEAD");
41+
const tempDir = mkdtempSync(join(tmpdir(), "gh-pages-"));
42+
cpSync("dist", join(tempDir, "dist"), { recursive: true });
43+
cpSync("examples", tempDir, { recursive: true });
44+
45+
console.log("Switching to gh-pages branch...");
46+
let ghPagesExists = false;
47+
try {
48+
execSync("git show-ref --verify --quiet refs/heads/gh-pages");
49+
ghPagesExists = true;
50+
} catch (e) {
51+
throw new Error("No gh-pages branch found");
52+
}
53+
54+
if (ghPagesExists) {
55+
exec("git checkout gh-pages");
56+
} else {
57+
throw new Error("No gh-pages branch found");
58+
}
59+
60+
console.log("Copying build artifacts...");
61+
cpSync(tempDir, ".", { recursive: true });
62+
console.log("Committing changes...");
63+
exec("git add -A");
64+
exec(`git commit -m "Deploy from ${currentBranch} @ ${currentCommit}"`);
65+
66+
console.log(`Returning to ${currentBranch}...`);
67+
exec(`git checkout ${currentBranch}`);
68+
rmSync(tempDir, { recursive: true, force: true });
69+
console.log("Deployment complete! gh-pages branch updated locally.");
70+
} catch (error) {
71+
console.error("Deployment failed:", error.message);
72+
process.exit(1);
73+
}

examples/index.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ regular-layout-frame::part(tab) {
3535
display: flex;
3636
flex: 1 1 150px;
3737
align-items: center;
38-
padding: 0 12px;
38+
font-size: 10px;
39+
padding: 0 8px;
3940
cursor: pointer;
4041
max-width: 150px;
4142
text-overflow: ellipsis;

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"clean": "rm -rf dist",
2121
"test": "playwright test",
2222
"example": "npx http-server . -p 8000",
23+
"deploy": "node deploy.mjs",
2324
"lint": "biome lint src tests",
2425
"format": "biome format --write src tests",
2526
"check": "biome check --write src tests"

playwright.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default defineConfig({
1818
},
1919
],
2020
webServer: {
21+
reuseExistingServer: true,
2122
command: "npx http-server . -p 8081",
2223
url: "http://127.0.0.1:8081",
2324
},

src/common/calculate_split.ts

Lines changed: 115 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -30,155 +30,145 @@ import {
3030
* @returns A new `LayoutPath` reflecting the updated (maybe) `"split-panel"`,
3131
* which is enough to draw the overlay.
3232
*/
33+
function handle_matching_orientation(
34+
col: number,
35+
row: number,
36+
panel: Layout,
37+
slot: string,
38+
drop_target: LayoutPath,
39+
is_before: boolean,
40+
): LayoutPath {
41+
if (drop_target.path.length === 0) {
42+
const insert_index = is_before ? 0 : 1;
43+
const new_panel = insert_child(panel, slot, [insert_index]);
44+
if (is_before) {
45+
return calculate_intersection(col, row, new_panel, false);
46+
} else {
47+
const new_drop_target = calculate_intersection(
48+
col,
49+
row,
50+
new_panel,
51+
false,
52+
);
53+
return {
54+
...new_drop_target,
55+
path: [0],
56+
};
57+
}
58+
} else {
59+
const path_without_last = drop_target.path.slice(0, -1);
60+
const last_index = drop_target.path[drop_target.path.length - 1];
61+
const insert_index = is_before ? last_index : last_index + 1;
62+
const new_panel = insert_child(panel, slot, [
63+
...path_without_last,
64+
insert_index,
65+
]);
66+
67+
if (is_before) {
68+
return calculate_intersection(col, row, new_panel, false);
69+
} else {
70+
const new_drop_target = calculate_intersection(
71+
col,
72+
row,
73+
new_panel,
74+
false,
75+
);
76+
return {
77+
...new_drop_target,
78+
path: [...path_without_last, last_index],
79+
};
80+
}
81+
}
82+
}
83+
84+
function handle_cross_orientation(
85+
col: number,
86+
row: number,
87+
panel: Layout,
88+
slot: string,
89+
drop_target: LayoutPath,
90+
insert_index: number,
91+
new_orientation: "horizontal" | "vertical",
92+
): LayoutPath {
93+
const original_path = drop_target.path;
94+
const new_panel = insert_child(
95+
panel,
96+
slot,
97+
[...original_path, insert_index],
98+
new_orientation,
99+
);
100+
const new_drop_target = calculate_intersection(col, row, new_panel, false);
101+
return {
102+
...new_drop_target,
103+
slot,
104+
path: [...original_path, insert_index],
105+
};
106+
}
107+
33108
export function calculate_split(
34109
col: number,
35110
row: number,
36111
panel: Layout,
37112
slot: string,
38113
drop_target: LayoutPath,
39114
): LayoutPath {
40-
if (
115+
const is_column_edge =
41116
drop_target.column_offset < SPLIT_EDGE_TOLERANCE ||
42-
drop_target.column_offset > 1 - SPLIT_EDGE_TOLERANCE
43-
) {
117+
drop_target.column_offset > 1 - SPLIT_EDGE_TOLERANCE;
118+
const is_row_edge =
119+
drop_target.row_offset < SPLIT_EDGE_TOLERANCE ||
120+
drop_target.row_offset > 1 - SPLIT_EDGE_TOLERANCE;
121+
122+
if (is_column_edge) {
123+
const is_before = drop_target.column_offset < SPLIT_EDGE_TOLERANCE;
44124
if (drop_target.orientation === "horizontal") {
45-
const is_before = drop_target.column_offset < SPLIT_EDGE_TOLERANCE;
46-
if (drop_target.path.length === 0) {
47-
const insert_index = is_before ? 0 : 1;
48-
const new_panel = insert_child(panel, slot, [insert_index]);
49-
// When inserting before, point to new panel; when after, keep original
50-
if (is_before) {
51-
drop_target = calculate_intersection(col, row, new_panel, false);
52-
} else {
53-
const new_drop_target = calculate_intersection(
54-
col,
55-
row,
56-
new_panel,
57-
false,
58-
);
59-
drop_target = {
60-
...new_drop_target,
61-
path: [0],
62-
};
63-
}
64-
} else {
65-
const path_without_last = drop_target.path.slice(0, -1);
66-
const last_index = drop_target.path[drop_target.path.length - 1];
67-
const insert_index = is_before ? last_index : last_index + 1;
68-
const new_panel = insert_child(panel, slot, [
69-
...path_without_last,
70-
insert_index,
71-
]);
72-
// When inserting before, point to new panel; when after, keep original
73-
if (is_before) {
74-
drop_target = calculate_intersection(col, row, new_panel, false);
75-
} else {
76-
// Keep the original panel but update view_window from new layout
77-
const new_drop_target = calculate_intersection(
78-
col,
79-
row,
80-
new_panel,
81-
false,
82-
);
83-
drop_target = {
84-
...new_drop_target,
85-
path: [...path_without_last, last_index],
86-
};
87-
}
88-
}
125+
drop_target = handle_matching_orientation(
126+
col,
127+
row,
128+
panel,
129+
slot,
130+
drop_target,
131+
is_before,
132+
);
89133
} else {
90-
const insert_index =
91-
drop_target.column_offset < SPLIT_EDGE_TOLERANCE ? 0 : 1;
92-
const original_path = drop_target.path;
93-
const new_panel = insert_child(
134+
const insert_index = is_before ? 0 : 1;
135+
drop_target = handle_cross_orientation(
136+
col,
137+
row,
94138
panel,
95139
slot,
96-
[...original_path, insert_index],
140+
drop_target,
141+
insert_index,
97142
"horizontal",
98143
);
99-
drop_target = calculate_intersection(col, row, new_panel, false);
100-
// Override to point to the newly inserted panel
101-
drop_target = {
102-
...drop_target,
103-
slot,
104-
path: [...original_path, insert_index],
105-
};
106144
}
107145

108-
if (drop_target) {
109-
drop_target.is_edge = true;
110-
}
111-
} else if (
112-
drop_target.row_offset < SPLIT_EDGE_TOLERANCE ||
113-
drop_target.row_offset > 1 - SPLIT_EDGE_TOLERANCE
114-
) {
146+
drop_target.is_edge = true;
147+
} else if (is_row_edge) {
148+
const is_before = drop_target.row_offset < SPLIT_EDGE_TOLERANCE;
115149
if (drop_target.orientation === "vertical") {
116-
const is_before = drop_target.row_offset < SPLIT_EDGE_TOLERANCE;
117-
if (drop_target.path.length === 0) {
118-
const insert_index = is_before ? 0 : 1;
119-
const new_panel = insert_child(panel, slot, [insert_index]);
120-
// When inserting before, point to new panel; when after, keep original
121-
if (is_before) {
122-
drop_target = calculate_intersection(col, row, new_panel, false);
123-
} else {
124-
const new_drop_target = calculate_intersection(
125-
col,
126-
row,
127-
new_panel,
128-
false,
129-
);
130-
drop_target = {
131-
...new_drop_target,
132-
path: [0],
133-
};
134-
}
135-
} else {
136-
const path_without_last = drop_target.path.slice(0, -1);
137-
const last_index = drop_target.path[drop_target.path.length - 1];
138-
const insert_index = is_before ? last_index : last_index + 1;
139-
const new_panel = insert_child(panel, slot, [
140-
...path_without_last,
141-
insert_index,
142-
]);
143-
// When inserting before, point to new panel; when after, keep original
144-
if (is_before) {
145-
drop_target = calculate_intersection(col, row, new_panel, false);
146-
} else {
147-
// Keep the original panel but update view_window from new layout
148-
const new_drop_target = calculate_intersection(
149-
col,
150-
row,
151-
new_panel,
152-
false,
153-
);
154-
drop_target = {
155-
...new_drop_target,
156-
path: [...path_without_last, last_index],
157-
};
158-
}
159-
}
150+
drop_target = handle_matching_orientation(
151+
col,
152+
row,
153+
panel,
154+
slot,
155+
drop_target,
156+
is_before,
157+
);
160158
} else {
161-
const insert_index =
162-
drop_target.row_offset < SPLIT_EDGE_TOLERANCE ? 0 : 1;
163-
const original_path = drop_target.path;
164-
const new_panel = insert_child(
159+
const insert_index = is_before ? 0 : 1;
160+
drop_target = handle_cross_orientation(
161+
col,
162+
row,
165163
panel,
166164
slot,
167-
[...original_path, insert_index],
165+
drop_target,
166+
insert_index,
168167
"vertical",
169168
);
170-
drop_target = calculate_intersection(col, row, new_panel, false);
171-
// Override to point to the newly inserted panel
172-
drop_target = {
173-
...drop_target,
174-
slot,
175-
path: [...original_path, insert_index],
176-
};
177169
}
178170

179-
if (drop_target) {
180-
drop_target.is_edge = true;
181-
}
171+
drop_target.is_edge = true;
182172
}
183173

184174
return drop_target;

0 commit comments

Comments
 (0)