Skip to content

Commit 386778b

Browse files
committed
Improved REACME.md
1 parent 9cba07e commit 386778b

13 files changed

Lines changed: 751 additions & 422 deletions

README.md

Lines changed: 110 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,58 +17,146 @@
1717

1818

1919

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

2322
- Zero depedencies, pure TypeScript, tiny.
2423
- Implemented as a [Web Component](https://developer.mozilla.org/en-US/docs/Web/API/Web_components),
25-
interoperable with any framework and fully customizable.
24+
interoperable with any framework.
25+
- Zero DOM mutation at runtime, implemented entirely by generating using
26+
[CSS `grid`](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout) rules.
27+
- Supports arbitrary theming via [CSS Variabled](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties) and [`::part`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/::part).
28+
- Supports async-aware container rendering for smooth animations even when rendering ovvurs over an event loop boundary.
2629
- Covered in bees.
2730

31+
## Demo
32+
33+
<a href="https://texodus.github.io/regular-layout/"><img src="./examples/PREVIEW.png" /></a>
34+
2835
## Installation
2936

3037
```bash
3138
npm install regular-layout
3239
```
3340

34-
## Usage
41+
## Quick Start
3542

36-
Add the `<regular-layout>` custom element to your HTML:
43+
Import the library and add `<regular-layout>` to your HTML. Children are
44+
matched to layout slots by their `name` attribute.
3745

3846
```html
47+
<script type="module" src="regular-layout/dist/index.js"></script>
48+
3949
<regular-layout>
4050
<div name="main">Main content</div>
4151
<div name="sidebar">Sidebar content</div>
4252
</regular-layout>
4353
```
4454

45-
Create and manipulate layouts programmatically:
55+
For draggable, tabbed panels, use `<regular-layout-frame>`:
56+
57+
```html
58+
<regular-layout>
59+
<regular-layout-frame name="main">
60+
Main content
61+
</regular-layout-frame>
62+
<regular-layout-frame name="sidebar">
63+
Sidebar content
64+
</regular-layout-frame>
65+
</regular-layout>
66+
```
67+
68+
Panels must be added and remove programmatically (e.g they are not
69+
auto-registered):
4670

4771
```javascript
48-
import "regular-layout/dist/index.js";
72+
const layout = document.querySelector("regular-layout");
4973

50-
const layout = document.querySelector('regular-layout');
74+
// This adds the panel definition to the layout (and makes it visible via CSS),
75+
// but does not mutat the DOM.
76+
layout.insertPanel("main");
77+
layout.insertPanel("sidebar");
5178

52-
// Add panels
53-
layout.insertPanel('main');
54-
layout.insertPanel('sidebar');
79+
// This removes the panel from the layout (and hides it via CSS) but does not
80+
// mutate the DOM.
81+
layout.removePanel("sidebar");
82+
```
5583

56-
// Save layout state
84+
## Save/Restore
85+
86+
Layout state serializes to a JSON tree of splits and tabs, which can be
87+
persisted and restored:
88+
89+
```javascript
5790
const state = layout.save();
91+
localStorage.setItem("layout", JSON.stringify(state));
92+
93+
// Later...
94+
layout.restore(JSON.parse(localStorage.getItem("layout")));
95+
```
5896

59-
// Remove panels (this does not change the DOM, the element is unslotted).
60-
layout.removePanel('sidebar');
97+
`restore()` dispatches a cancelable `regular-layout-resize-before` event before
98+
applying the new state. Call `preventDefault()` to suspend the update, then
99+
`layout.resumeResize()` when ready:
61100

62-
// Restore saved state
63-
layout.restore(state);
101+
```javascript
102+
layout.addEventListener("regular-layout-resize-before", (event) => {
103+
event.preventDefault();
104+
// ... prepare for resize ...
105+
layout.resumeResize();
106+
});
64107
```
65108

66-
Create repositionable panels using `<regular-layout-frame>`:
109+
The `restore()` API can also be used as an alternative to
110+
`insertPanel`/`removePanel` for initializing a `<regular-layout>`.
111+
112+
## Theming
113+
114+
Themes are plain CSS files that style the layout and its `::part()` selectors,
115+
scoped by a class on `<regular-layout>`. Apply a theme by adding its stylesheet
116+
and setting the class:
67117

68118
```html
69-
<regular-layout>
70-
<regular-layout-frame name="main">
71-
Main content
72-
</regular-layout-frame>
119+
<link rel="stylesheet" href="regular-layout/themes/chicago.css">
120+
121+
<regular-layout class="chicago">
122+
...
73123
</regular-layout>
74-
```
124+
```
125+
126+
`<regular-layout-frame>` exposes these CSS parts:
127+
128+
| Part | Description |
129+
|------|-------------|
130+
| `titlebar` | Tab bar container |
131+
| `tab` | Individual tab |
132+
| `active-tab` | Currently selected tab |
133+
| `close` | Close button |
134+
| `active-close` | Close button on the active tab |
135+
| `container` | Content area |
136+
137+
```css
138+
regular-layout.mytheme regular-layout-frame::part(titlebar) {
139+
background: #333;
140+
}
141+
142+
regular-layout.mytheme regular-layout-frame::part(active-tab) {
143+
background: #fff;
144+
color: #000;
145+
}
146+
```
147+
148+
See [the example `themes/`](./themes) directory for examples of how to write a
149+
complete theme for `<regular-layout>` and `regular-layout-frame>`.
150+
151+
## Events
152+
153+
| Event | Detail | Cancelable | Description |
154+
|-------|--------|------------|-------------|
155+
| `regular-layout-resize-before` | `{ calculatePresizePaths() }` | Yes | Fired before any layout change. Cancel to suspend until `resumeResize()`. |
156+
| `regular-layout-update` | `Layout` | No | Fired after layout state is updated. |
157+
158+
```javascript
159+
layout.addEventListener("regular-layout-update", (event) => {
160+
console.log("New layout:", event.detail);
161+
});
162+
```

examples/PREVIEW.png

136 KB
Loading

examples/index.html

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<link rel="stylesheet" href="../themes/gibson.css">
1212
<link rel="stylesheet" href="../themes/fluxbox.css">
1313
<link rel="stylesheet" href="../themes/hotdog.css">
14+
<link rel="stylesheet" href="../themes/borland.css">
1415
<script type="module" src="/.esbuild-serve/index.js"></script>
1516
</head>
1617
<body>
@@ -25,17 +26,18 @@
2526
<option value="chicago">Chicago</option>
2627
<option value="fluxbox">Fluxbox</option>
2728
<option value="hotdog">Hot Dog</option>
29+
<option value="borland">Borland</option>
2830
</select>
2931
</header>
3032
<regular-layout class="lorax">
31-
<regular-layout-frame name="AAA"> </regular-layout-frame>
32-
<regular-layout-frame name="BBB"></regular-layout-frame>
33-
<regular-layout-frame name="CCC"></regular-layout-frame>
34-
<regular-layout-frame name="DDD"></regular-layout-frame>
35-
<regular-layout-frame name="EEE"></regular-layout-frame>
36-
<regular-layout-frame name="FFF"></regular-layout-frame>
37-
<regular-layout-frame name="GGG"></regular-layout-frame>
38-
<regular-layout-frame name="HHH"></regular-layout-frame>
33+
<regular-layout-frame name="AAA">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</regular-layout-frame>
34+
<regular-layout-frame name="BBB">Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</regular-layout-frame>
35+
<regular-layout-frame name="CCC">Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</regular-layout-frame>
36+
<regular-layout-frame name="DDD">Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</regular-layout-frame>
37+
<regular-layout-frame name="EEE">Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, turpis et commodo pharetra.</regular-layout-frame>
38+
<regular-layout-frame name="FFF">Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</regular-layout-frame>
39+
<regular-layout-frame name="GGG">Maecenas faucibus mollis interdum. Donec sed odio dui. Cras justo odio, dapibus ut facilisis in, egestas eget quam.</regular-layout-frame>
40+
<regular-layout-frame name="HHH">Vestibulum id ligula porta felis euismod semper. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis.</regular-layout-frame>
3941
</regular-layout>
4042
</body>
4143

examples/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ add.addEventListener("click", () => {
3838
layout.insertPanel(name, []);
3939
});
4040

41+
const urlTheme = new URLSearchParams(window.location.search).get("theme");
42+
if (urlTheme) {
43+
themes.value = urlTheme;
44+
layout.className = urlTheme;
45+
}
46+
4147
themes.addEventListener("change", (_event) => {
4248
layout.className = themes.value;
4349
});

montage.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { chromium } from "@playwright/test";
2+
import * as fs from "node:fs";
3+
import * as path from "node:path";
4+
5+
const themes = ["lorax", "gibson", "chicago", "fluxbox", "hotdog", "borland"];
6+
const dir = "screenshots";
7+
const cols = 3;
8+
const rows = 2;
9+
const tileW = 300;
10+
11+
const images = themes.map((t) =>
12+
fs.readFileSync(path.join(dir, `${t}.png`)).toString("base64"),
13+
);
14+
15+
const browser = await chromium.launch();
16+
const page = await browser.newPage();
17+
await page.setContent("<html><body></body></html>");
18+
19+
const pngData = await page.evaluate(
20+
async ({ images, cols, rows, tileW }) => {
21+
const loaded = await Promise.all(
22+
images.map((b64) => {
23+
return new Promise<HTMLImageElement>((resolve) => {
24+
const img = new Image();
25+
img.onload = () => resolve(img);
26+
img.src = `data:image/png;base64,${b64}`;
27+
});
28+
}),
29+
);
30+
31+
const aspect = loaded[0].naturalHeight / loaded[0].naturalWidth;
32+
const tileH = Math.round(tileW * aspect);
33+
const canvas = document.createElement("canvas");
34+
canvas.width = cols * tileW;
35+
canvas.height = rows * tileH;
36+
const ctx = canvas.getContext("2d")!;
37+
38+
loaded.forEach((img, i) => {
39+
const col = i % cols;
40+
const row = Math.floor(i / cols);
41+
ctx.drawImage(img, col * tileW, row * tileH, tileW, tileH);
42+
});
43+
44+
return canvas.toDataURL("image/png").split(",")[1];
45+
},
46+
{ images, cols, rows, tileW },
47+
);
48+
49+
fs.writeFileSync(
50+
path.join(dir, "montage.png"),
51+
Buffer.from(pngData, "base64"),
52+
);
53+
console.log("Created montage.png");
54+
await browser.close();

screenshots.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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 { chromium } from "@playwright/test";
13+
import * as fs from "node:fs";
14+
15+
const THEMES = ["lorax", "gibson", "chicago", "fluxbox", "hotdog", "borland"];
16+
const BASE_URL = "http://127.0.0.1:8081";
17+
const OUT_DIR = "screenshots";
18+
19+
async function main() {
20+
fs.mkdirSync(OUT_DIR, { recursive: true });
21+
const browser = await chromium.launch();
22+
const page = await browser.newPage();
23+
24+
for (const theme of THEMES) {
25+
await page.goto(
26+
`${BASE_URL}/examples/index.html?theme=${theme}`,
27+
);
28+
await page.waitForSelector("regular-layout");
29+
await page.evaluate(() => {
30+
const header = document.querySelector("header");
31+
if (header) header.style.display = "none";
32+
});
33+
34+
const layout = page.locator("regular-layout");
35+
await layout.screenshot({
36+
path: `${OUT_DIR}/${theme}.png`,
37+
});
38+
39+
console.log(`Captured ${theme}.png`);
40+
}
41+
42+
await browser.close();
43+
}
44+
45+
main();

src/regular-layout-frame.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const CSS = `
2626

2727
const HTML_TEMPLATE = `
2828
<div part="titlebar"></div>
29-
<slot part="container"></slot>
29+
<div part="container"><slot></slot></div>
3030
`;
3131

3232
type DragState = { moved?: boolean; path: LayoutPath };

0 commit comments

Comments
 (0)