Skip to content

Commit fdd75f3

Browse files
authored
feat: images support & unit tests (#4)
1 parent 8792b71 commit fdd75f3

28 files changed

+1742
-75
lines changed

README.md

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
A web component that creates layouts by treating your HTML elements as rectangles and packing them using a best-fit 2D strip-packing algorithm.
44

5-
## Why a Packing Algorithm for Web Layouts?
5+
## ⚙️ Why a Packing Algorithm for Web Layouts?
66

77
Web browsers naturally manage elements as rectangles. `rectpackr-layout` leverages this by applying a best-fit strip-packing algorithm — the same approach used in industrial optimization problems — to web layout creation.
88

9-
## Intelligent Layouts Through Automated Measurement
9+
## 🤖 Layouts Through Automated Measurement
1010

1111
The algorithm intelligently works with whatever dimensional information is available:
1212

@@ -31,7 +31,7 @@ The algorithm intelligently works with whatever dimensional information is avail
3131
- **Production-ready precision** when you need exact control
3232
- **Best of both worlds** — automation when you want it, control when you need it
3333

34-
## Installation
34+
## 📥 Installation
3535

3636
### Install the package via your preferred package manager:
3737

@@ -68,7 +68,7 @@ Or directly in your HTML:
6868
</script>
6969
```
7070

71-
## API Reference
71+
## 📖 API Reference
7272

7373
### Attributes
7474

@@ -77,9 +77,9 @@ Or directly in your HTML:
7777
Defines the CSS method used to position items.
7878

7979
- `transform` (_Default_): Uses `transform: translate(x, y)`
80-
- `offset`: Uses CSS offset properties (`top`/`bottom` and `left`/`right`)
80+
- `offset`: Uses CSS `inset` property for precise positioning
8181

82-
> **Performance Note:** The default `transform` value typically offers better performance through hardware acceleration. Use `offset` only when child elements already use `transform` for other purposes (animation etc.).
82+
> 💡 **Performance Note:** The default `transform` value typically offers better performance through hardware acceleration. Use `offset` only when child elements already use `transform` for other purposes (animation etc.).
8383
8484
**`x-direction`**
8585

@@ -97,21 +97,68 @@ Controls the vertical packing direction.
9797

9898
### A Note on Visual Order & Accessibility
9999

100-
The `x-direction` and `y-direction` attributes control visual placement, which may differ from DOM order.
100+
The `x-direction` and `y-direction` attributes control visual placement, which may create a difference between the visual arrangement and the underlying DOM order.
101101

102-
- **DOM Order is Preserved:** The library never changes the underlying HTML structure, ensuring correct tab order and screen reader navigation
103-
- **Visual Order is Optimized:** The algorithm places items for spatial efficiency, which may not match linear DOM order
102+
- **DOM Order is Preserved:** The underlying HTML structure remains unchanged
103+
- **Visual Order is Algorithm-Determined**: Item placement follows the packing logic and your direction settings
104104

105-
**Best Practice:** Ensure your HTML source reflects the logical reading order.
105+
## 🚀 Usage Examples
106106

107-
## Browser Support
107+
### Fluid, Responsive Layout
108+
109+
```html
110+
<rectpackr-layout>
111+
<div>Card 1</div>
112+
<div>Card 2</div>
113+
<div>Card 3</div>
114+
</rectpackr-layout>
115+
116+
<style>
117+
rectpackr-layout {
118+
container-type: inline-size;
119+
display: block;
120+
}
121+
122+
rectpackr-layout > * {
123+
/* Fluid width based on container queries */
124+
width: 100%;
125+
}
126+
127+
@container (min-width: 400px) {
128+
rectpackr-layout > * {
129+
width: 50%;
130+
}
131+
}
132+
133+
@container (min-width: 800px) {
134+
rectpackr-layout > * {
135+
width: 33.33%;
136+
}
137+
}
138+
</style>
139+
```
140+
141+
### Dynamic Content Handling
142+
143+
```html
144+
<rectpackr-layout id="dynamic-layout">
145+
<!-- Content can be added/removed dynamically -->
146+
</rectpackr-layout>
147+
148+
<script>
149+
// The layout automatically adjusts to content changes
150+
document.getElementById('dynamic-layout').appendChild(newElement);
151+
</script>
152+
```
153+
154+
## ✅ Browser Support
108155

109156
Modern browsers with Web Components support.
110157

111-
## Issues and Support
158+
## 🔧 Issues and Support
112159

113160
If you encounter any issues or have questions, please [open an issue](https://github.com/styiannis/rectpackr-layout/issues).
114161

115-
## License
162+
## 📄 License
116163

117164
This project is licensed under the [MIT License](https://github.com/styiannis/rectpackr-layout?tab=MIT-1-ov-file#readme).

jest.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
module.exports = {
33
preset: 'ts-jest/presets/js-with-ts',
44
testEnvironment: 'jsdom',
5+
setupFilesAfterEnv: ['<rootDir>/tests/mocks/index.ts'],
56
modulePathIgnorePatterns: [
67
'<rootDir>/build/',
8+
'<rootDir>/code_documentation/',
79
'<rootDir>/coverage_report/',
810
'<rootDir>/dist/',
911
],

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rectpackr-layout",
3-
"version": "0.8.0",
3+
"version": "0.9.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",
@@ -29,8 +29,8 @@
2929
"source": "src/index.ts",
3030
"main": "dist/cjs/index.js",
3131
"module": "dist/es/index.js",
32-
"unpkg": "./dist/umd/index.js",
33-
"jsdelivr": "./dist/umd/index.js",
32+
"unpkg": "dist/umd/index.js",
33+
"jsdelivr": "dist/umd/index.js",
3434
"types": "dist/@types/index.d.ts",
3535
"files": [
3636
"CHANGELOG.md",
@@ -84,7 +84,8 @@
8484
".": {
8585
"import": "./dist/es/index.js",
8686
"require": "./dist/cjs/index.js",
87-
"default": "./dist/umd/index.js"
87+
"types": "./dist/@types/index.d.ts",
88+
"default": "./dist/es/index.js"
8889
}
8990
}
9091
}

scripts/validate-exports.js

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,32 @@ const { readFileSync, existsSync } = require('node:fs');
22

33
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'));
44

5+
const invalidPaths = [];
6+
7+
['source', 'main', 'module', 'unpkg', 'jsdelivr', 'types'].forEach((k) => {
8+
if (pkg[k] && !existsSync(pkg[k])) invalidPaths.push(pkg[k]);
9+
});
10+
511
if (pkg.exports) {
6-
const invalidExports = Object.keys(pkg.exports).reduce((acc, curr) => {
12+
Object.keys(pkg.exports).reduce((acc, curr) => {
13+
const def = pkg.exports[curr].default;
714
const imp = pkg.exports[curr].import;
815
const req = pkg.exports[curr].require;
16+
const typ = pkg.exports[curr].types;
917

10-
if (imp) {
11-
if (imp.types && !existsSync(imp.types)) acc.push(imp.types);
12-
if (imp.default && !existsSync(imp.default)) acc.push(imp.default);
13-
}
14-
15-
if (req) {
16-
if (req.types && !existsSync(req.types)) acc.push(req.types);
17-
if (req.default && !existsSync(req.default)) acc.push(req.default);
18-
}
18+
if (def && !existsSync(def)) acc.push(def);
19+
if (imp && !existsSync(imp)) acc.push(imp);
20+
if (req && !existsSync(req)) acc.push(req);
21+
if (typ && !existsSync(typ)) acc.push(typ);
1922

2023
return acc;
21-
}, []);
24+
}, invalidPaths);
25+
}
2226

23-
if (0 < invalidExports.length) {
24-
throw Error(
25-
`Found invalid export paths\n\n${invalidExports
26-
.map((path, i) => `[${i}] ${path}`)
27-
.join('\n')}\n`
28-
);
29-
}
27+
if (invalidPaths.length > 0) {
28+
throw Error(
29+
`Found missing exported files\n\n${invalidPaths
30+
.map((path, i) => `[${i}] ${path}`)
31+
.join('\n')}\n`
32+
);
3033
}

src/RectpackrLayout.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,20 @@ const parseConfig = (config: {
1818
});
1919

2020
const getStyleTextContent = (config: IRectpackrConfig) => {
21-
const insetValue = {
22-
ltr: { ttb: '0 auto auto 0', btt: 'auto auto 0 0' },
23-
rtl: { ttb: '0 0 auto auto', btt: 'auto 0 0 auto' },
24-
}[config['x-direction']][config['y-direction']];
21+
let ret = `slot{ box-sizing: border-box; position: relative; display: block; width: 100% }`;
2522

26-
return `
27-
slot{ box-sizing: border-box; position:relative; display:block; width:100% }
28-
::slotted(:not([slot])){ position:absolute; inset:${insetValue} }`;
23+
if (config.positioning === 'transform') {
24+
const insetValue = {
25+
ltr: { ttb: '0 auto auto 0', btt: 'auto auto 0 0' },
26+
rtl: { ttb: '0 0 auto auto', btt: 'auto 0 0 auto' },
27+
}[config['x-direction']][config['y-direction']];
28+
29+
ret = `${ret} ::slotted(:not([slot])){ position: absolute !important; inset: ${insetValue} !important }`;
30+
} else {
31+
ret = `${ret} ::slotted(:not([slot])){ position: absolute !important }`;
32+
}
33+
34+
return ret;
2935
};
3036

3137
/* ------------------------------------------------------------------------- */
@@ -70,11 +76,10 @@ export class RectpackrLayout extends HTMLElement {
7076
}
7177

7278
attributeChangedCallback() {
73-
if (!this.shadowRoot) {
74-
return;
79+
if (this.shadowRoot) {
80+
this.#clear();
81+
this.#render();
7582
}
76-
this.#clear();
77-
this.#render();
7883
}
7984

8085
connectedCallback() {

src/core/rectpackr.ts

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

src/core/types.ts

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

0 commit comments

Comments
 (0)