Skip to content

Commit ec0cd88

Browse files
authored
feat: version 1.0.0 (#5)
1 parent fdd75f3 commit ec0cd88

File tree

6 files changed

+91
-41
lines changed

6 files changed

+91
-41
lines changed

README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Rectpackr Layout
22

3+
[![NPM Version](https://img.shields.io/npm/v/rectpackr-layout)](https://www.npmjs.com/package/rectpackr-layout)
4+
[![Coverage Status](https://img.shields.io/coverallsCoverage/github/styiannis/rectpackr-layout)](https://coveralls.io/github/styiannis/rectpackr-layout?branch=main)
5+
[![CodePen Demos](https://img.shields.io/badge/CodePen-Demos-blue)](https://codepen.io/collection/dGpeLa)
6+
37
A web component that creates layouts by treating your HTML elements as rectangles and packing them using a best-fit 2D strip-packing algorithm.
48

59
## ⚙️ Why a Packing Algorithm for Web Layouts?
@@ -68,6 +72,39 @@ Or directly in your HTML:
6872
</script>
6973
```
7074

75+
### Using a CDN (No Build Step Needed)
76+
77+
Include it directly in your HTML via CDN:
78+
79+
#### unpkg
80+
81+
```html
82+
<script type="module" src="https://unpkg.com/rectpackr-layout"></script>
83+
```
84+
85+
#### jsDelivr
86+
87+
```html
88+
<script
89+
type="module"
90+
src="https://cdn.jsdelivr.net/npm/rectpackr-layout"
91+
></script>
92+
```
93+
94+
#### esm.sh
95+
96+
```html
97+
<script type="module" src="https://esm.sh/rectpackr-layout"></script>
98+
```
99+
100+
### Once installed, use the web component anywhere in your HTML:
101+
102+
```html
103+
<rectpackr-layout>
104+
<div>Your content here</div>
105+
</rectpackr-layout>
106+
```
107+
71108
## 📖 API Reference
72109

73110
### Attributes
@@ -151,6 +188,26 @@ The `x-direction` and `y-direction` attributes control visual placement, which m
151188
</script>
152189
```
153190

191+
## 🎯 Live Demos
192+
193+
### Consistent Width Gallery
194+
195+
See predictable masonry-style layouts with equal-width elements
196+
197+
[View on CodePen](https://codepen.io/styiannis/pen/ogbzBXg)
198+
199+
### Mixed Dimension Gallery
200+
201+
Explore optimal packing of variably-sized elements and aspect ratios
202+
203+
[View on CodePen](https://codepen.io/styiannis/pen/XJXjayR)
204+
205+
### Interactive Playground
206+
207+
Experiment with real-time controls and dynamic content manipulation
208+
209+
[View on CodePen](https://codepen.io/styiannis/pen/qEbqMBZ)
210+
154211
## ✅ Browser Support
155212

156213
Modern browsers with Web Components support.

package.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rectpackr-layout",
3-
"version": "0.9.0",
3+
"version": "1.0.0",
44
"description": "A web component that creates layouts by packing HTML elements as rectangles using a best-fit 2D strip-packing algorithm. Automatically measures dimensions and handles mixed content.",
55
"keywords": [
66
"best-fit",
@@ -12,7 +12,7 @@
1212
"masonry-layout",
1313
"responsive",
1414
"responsive-layout",
15-
"rectangle-packer",
15+
"rectangle-packing",
1616
"strip-packing",
1717
"web-component",
1818
"web-components"
@@ -63,22 +63,22 @@
6363
"validate-exports": "node ./scripts/validate-exports.js"
6464
},
6565
"dependencies": {
66-
"best-fit-strip-pack": "^1.0.1"
66+
"best-fit-strip-pack": "^1.0.2"
6767
},
6868
"devDependencies": {
69-
"@rollup/plugin-node-resolve": "^16.0.1",
69+
"@rollup/plugin-node-resolve": "^16.0.2",
7070
"@rollup/plugin-terser": "^0.4.4",
7171
"@rollup/plugin-typescript": "^12.1.4",
7272
"@tsconfig/node22": "^22.0.2",
7373
"@types/jest": "^30.0.0",
74-
"@types/node": "^24.5.2",
75-
"jest": "^30.1.3",
76-
"jest-environment-jsdom": "^30.1.2",
77-
"rollup": "^4.52.2",
74+
"@types/node": "^24.7.0",
75+
"jest": "^30.2.0",
76+
"jest-environment-jsdom": "^30.2.0",
77+
"rollup": "^4.52.4",
7878
"rollup-plugin-dts": "^6.2.3",
7979
"ts-jest": "^29.4.4",
8080
"tslib": "^2.8.1",
81-
"typescript": "^5.9.2"
81+
"typescript": "^5.9.3"
8282
},
8383
"exports": {
8484
".": {

src/core/rectpackr.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ export function create<R extends IRectpackr>(
3333
container,
3434
children: [] as R['children'],
3535
childrenContainer,
36+
isPendingStartObservingChildren: false,
3637
loadingImages: new Map(),
3738
observers: { childrenContainerMutation, childrenResize, containerResize },
38-
pendingStartObservingChildren: false,
3939
stripPack,
4040
} as R;
4141

@@ -47,5 +47,6 @@ export function create<R extends IRectpackr>(
4747
export function clear<R extends IRectpackr>(instance: R) {
4848
stopObserving(instance);
4949
resetStyle(instance);
50+
instance.children.length = 0;
5051
instance.stripPack.reset();
5152
}

src/core/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ export interface IRectpackr {
1717
width: number;
1818
}[];
1919
childrenContainer: HTMLElement;
20+
isPendingStartObservingChildren: boolean;
2021
loadingImages: Map<HTMLElement, (this: HTMLImageElement) => void>;
2122
observers: {
2223
childrenContainerMutation: MutationObserver;
2324
childrenResize: ResizeObserver;
2425
containerResize: ResizeObserver;
2526
};
26-
pendingStartObservingChildren: boolean;
2727
stripPack: BestFitStripPack;
2828
}

src/core/util.ts

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ function render(instance: IRectpackr) {
1313
}
1414

1515
function restartObservingChildren(instance: IRectpackr) {
16-
if (instance.pendingStartObservingChildren) {
16+
if (instance.isPendingStartObservingChildren) {
1717
return;
1818
}
1919

20-
instance.pendingStartObservingChildren = true;
20+
instance.isPendingStartObservingChildren = true;
2121
stopObservingChildren(instance);
2222

2323
requestAnimationFrame(() => {
24-
instance.pendingStartObservingChildren = false;
24+
instance.isPendingStartObservingChildren = false;
2525
startObservingChildren(instance);
2626
});
2727
}
@@ -53,15 +53,15 @@ function startObservingContainer(instance: IRectpackr) {
5353
}
5454

5555
function startObservingImages(instance: IRectpackr) {
56-
function callback(this: HTMLImageElement) {
56+
function onImgLoad(this: HTMLImageElement) {
5757
instance.loadingImages.delete(this);
5858
restartObservingChildren(instance);
5959
}
6060

6161
for (const img of instance.childrenContainer.querySelectorAll('img')) {
6262
if (!img.complete && !instance.loadingImages.get(img)) {
63-
instance.loadingImages.set(img, callback);
64-
img.addEventListener('load', callback, { once: true, passive: true });
63+
instance.loadingImages.set(img, onImgLoad);
64+
img.addEventListener('load', onImgLoad, { once: true, passive: true });
6565
}
6666
}
6767
}
@@ -80,8 +80,8 @@ function stopObservingContainer(instance: IRectpackr) {
8080
}
8181

8282
function stopObservingImages(instance: IRectpackr) {
83-
for (const [img, callback] of instance.loadingImages) {
84-
img.removeEventListener('load', callback);
83+
for (const [img, onImgLoad] of instance.loadingImages) {
84+
img.removeEventListener('load', onImgLoad);
8585
instance.loadingImages.delete(img);
8686
}
8787
}
@@ -112,9 +112,7 @@ function updateStyle(
112112
instance: IRectpackr,
113113
children: { element: IRectpackrChildElement; point: [number, number] }[]
114114
) {
115-
/*
116-
* Update children style.
117-
*/
115+
// Update children style
118116
for (const { element, point } of children) {
119117
const xVal =
120118
point[0] *
@@ -143,9 +141,7 @@ function updateStyle(
143141
}
144142
}
145143

146-
/*
147-
* Update container style.
148-
*/
144+
// Update container style
149145
instance.container.style.height = `${instance.stripPack.packedHeight}px`;
150146
}
151147

@@ -154,6 +150,10 @@ function updateStyle(
154150
/* ------------------------------------------------------------------------- */
155151

156152
export function onChildrenContainerMutation(instance: IRectpackr) {
153+
if (instance.childrenContainer.children.length === 0) {
154+
onChildResize(instance, []);
155+
}
156+
157157
restartObservingChildren(instance);
158158
restartObservingImages(instance);
159159
}
@@ -196,9 +196,7 @@ export function onContainerResize(instance: IRectpackr) {
196196
}
197197

198198
export function resetStyle(instance: IRectpackr) {
199-
/*
200-
* Reset children style.
201-
*/
199+
// Reset children style
202200
for (const { element } of instance.children) {
203201
if (instance.config.positioning === 'offset') {
204202
element.style.inset = '';
@@ -207,9 +205,7 @@ export function resetStyle(instance: IRectpackr) {
207205
}
208206
}
209207

210-
/*
211-
* Reset container style.
212-
*/
208+
// Reset container style
213209
instance.container.style.height = '';
214210
}
215211

tests/rectpackr-layout-attributes.test.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,8 @@ import {
66
} from './util/types';
77

88
describe('Change attributes', () => {
9-
const shadowRootOffsetChildrenStyle = Object.freeze({
10-
ltr: '::slotted(:not([slot])){ position: absolute !important }',
11-
rtl: '::slotted(:not([slot])){ position: absolute !important }',
12-
ttb: '::slotted(:not([slot])){ position: absolute !important }',
13-
btt: '::slotted(:not([slot])){ position: absolute !important }',
14-
});
9+
const shadowRootOffsetChildrenStyle =
10+
'::slotted(:not([slot])){ position: absolute !important }';
1511

1612
const shadowRootTransformChildrenStyle = Object.freeze({
1713
ltr: '::slotted(:not([slot])){ position: absolute !important; inset: 0 auto auto 0 !important }',
@@ -102,7 +98,7 @@ describe('Change attributes', () => {
10298

10399
changeAttributeAndValidate(
104100
{ positioning: 'offset' },
105-
shadowRootOffsetChildrenStyle.rtl,
101+
shadowRootOffsetChildrenStyle,
106102
[
107103
{ element: children[0]!, inset: '0 0 auto auto' },
108104
{ element: children[1]!, inset: `0 ${1 * childWidth}px auto auto` },
@@ -113,7 +109,7 @@ describe('Change attributes', () => {
113109

114110
changeAttributeAndValidate(
115111
{ 'x-direction': 'ltr' },
116-
shadowRootOffsetChildrenStyle.ltr,
112+
shadowRootOffsetChildrenStyle,
117113
[
118114
{ element: children[0]!, inset: '0 auto auto 0' },
119115
{ element: children[1]!, inset: `0 auto auto ${1 * childWidth}px` },
@@ -223,7 +219,7 @@ describe('Change attributes', () => {
223219

224220
changeAttributeAndValidate(
225221
{ positioning: 'offset' },
226-
shadowRootOffsetChildrenStyle.btt,
222+
shadowRootOffsetChildrenStyle,
227223
[
228224
{ element: children[0]!, inset: 'auto auto 0 0' },
229225
{ element: children[1]!, inset: `auto auto ${1 * childHeight}px 0` },
@@ -234,7 +230,7 @@ describe('Change attributes', () => {
234230

235231
changeAttributeAndValidate(
236232
{ 'y-direction': 'ttb' },
237-
shadowRootOffsetChildrenStyle.ttb,
233+
shadowRootOffsetChildrenStyle,
238234
[
239235
{ element: children[0]!, inset: '0 auto auto 0' },
240236
{ element: children[1]!, inset: `${1 * childHeight}px auto auto 0` },

0 commit comments

Comments
 (0)