petersburg 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +83 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +84 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ Named after the Hermitage Museum in St. Petersburg, where curators arrange paint
|
|
|
8
8
|
|
|
9
9
|
- **MaxRects bin-packing algorithm** — efficient 2D rectangle packing
|
|
10
10
|
- **Multiple sort strategies** — optimize for packing efficiency or preserve input order
|
|
11
|
+
- **Auto-height measurement** — items can omit height; Petersburg measures rendered content
|
|
11
12
|
- **Responsive** — auto-measures container and recalculates on resize
|
|
12
13
|
- **Accessible** — DOM order matches visual flow for proper tab navigation
|
|
13
14
|
- **Lightweight** — no dependencies beyond React
|
|
@@ -56,7 +57,7 @@ function Gallery() {
|
|
|
56
57
|
interface LayoutItem {
|
|
57
58
|
id: string;
|
|
58
59
|
width: number;
|
|
59
|
-
height
|
|
60
|
+
height?: number; // Optional — if omitted, auto-measured from content
|
|
60
61
|
content: ReactNode;
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -82,6 +83,27 @@ A hybrid approach for galleries where order matters at the top but efficiency ma
|
|
|
82
83
|
|
|
83
84
|
This is ideal for "newest items at top" layouts where the first row should show items 1, 2, 3... in order, but lower rows can be optimized.
|
|
84
85
|
|
|
86
|
+
## Auto-Height Mode
|
|
87
|
+
|
|
88
|
+
For items with dynamic content (text cards, variable-length descriptions), omit the `height` property:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
const items = [
|
|
92
|
+
{ id: '1', width: 200, content: <Card title="Short" /> },
|
|
93
|
+
{ id: '2', width: 200, content: <Card title="Much Longer Title Here" /> },
|
|
94
|
+
{ id: '3', width: 150, content: <Card title="Medium" description="With extra text" /> },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
<HermitageLayout items={items} gap={8} />
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Petersburg will:
|
|
101
|
+
1. Render items invisibly to measure their natural height
|
|
102
|
+
2. Pack using the measured dimensions
|
|
103
|
+
3. Display the final layout
|
|
104
|
+
|
|
105
|
+
This adds a brief measurement phase but avoids content overflow or fixed-height constraints.
|
|
106
|
+
|
|
85
107
|
## Responsive Layouts
|
|
86
108
|
|
|
87
109
|
Omit `containerWidth` to enable responsive mode:
|
package/dist/index.d.mts
CHANGED
|
@@ -4,7 +4,8 @@ import { ReactNode } from 'react';
|
|
|
4
4
|
interface LayoutItem {
|
|
5
5
|
id: string;
|
|
6
6
|
width: number;
|
|
7
|
-
height
|
|
7
|
+
/** If omitted, height is auto-measured from rendered content */
|
|
8
|
+
height?: number;
|
|
8
9
|
content: ReactNode;
|
|
9
10
|
}
|
|
10
11
|
type SortStrategy = "none" | "height-desc" | "ordered";
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { ReactNode } from 'react';
|
|
|
4
4
|
interface LayoutItem {
|
|
5
5
|
id: string;
|
|
6
6
|
width: number;
|
|
7
|
-
height
|
|
7
|
+
/** If omitted, height is auto-measured from rendered content */
|
|
8
|
+
height?: number;
|
|
8
9
|
content: ReactNode;
|
|
9
10
|
}
|
|
10
11
|
type SortStrategy = "none" | "height-desc" | "ordered";
|
package/dist/index.js
CHANGED
|
@@ -285,8 +285,17 @@ function HermitageLayout({
|
|
|
285
285
|
className
|
|
286
286
|
}) {
|
|
287
287
|
const containerRef = (0, import_react.useRef)(null);
|
|
288
|
+
const measureRef = (0, import_react.useRef)(null);
|
|
288
289
|
const [measuredWidth, setMeasuredWidth] = (0, import_react.useState)(0);
|
|
290
|
+
const [measuredHeights, setMeasuredHeights] = (0, import_react.useState)(
|
|
291
|
+
/* @__PURE__ */ new Map()
|
|
292
|
+
);
|
|
293
|
+
const [isMeasuring, setIsMeasuring] = (0, import_react.useState)(false);
|
|
289
294
|
const containerWidth = fixedWidth ?? measuredWidth;
|
|
295
|
+
const itemsNeedingMeasure = (0, import_react.useMemo)(() => {
|
|
296
|
+
return items.filter((item) => item.height === void 0);
|
|
297
|
+
}, [items]);
|
|
298
|
+
const needsMeasurement = itemsNeedingMeasure.length > 0;
|
|
290
299
|
(0, import_react.useEffect)(() => {
|
|
291
300
|
if (fixedWidth !== void 0) {
|
|
292
301
|
return;
|
|
@@ -303,16 +312,48 @@ function HermitageLayout({
|
|
|
303
312
|
setMeasuredWidth(element.getBoundingClientRect().width);
|
|
304
313
|
return () => observer.disconnect();
|
|
305
314
|
}, [fixedWidth]);
|
|
315
|
+
(0, import_react.useEffect)(() => {
|
|
316
|
+
if (!needsMeasurement || containerWidth === 0) {
|
|
317
|
+
setIsMeasuring(false);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
setIsMeasuring(true);
|
|
321
|
+
requestAnimationFrame(() => {
|
|
322
|
+
const measureContainer = measureRef.current;
|
|
323
|
+
if (!measureContainer) {
|
|
324
|
+
setIsMeasuring(false);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const newHeights = /* @__PURE__ */ new Map();
|
|
328
|
+
for (const item of itemsNeedingMeasure) {
|
|
329
|
+
const element = measureContainer.querySelector(
|
|
330
|
+
`[data-measure-id="${item.id}"]`
|
|
331
|
+
);
|
|
332
|
+
if (element) {
|
|
333
|
+
newHeights.set(item.id, element.getBoundingClientRect().height);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
setMeasuredHeights(newHeights);
|
|
337
|
+
setIsMeasuring(false);
|
|
338
|
+
});
|
|
339
|
+
}, [itemsNeedingMeasure, needsMeasurement, containerWidth]);
|
|
340
|
+
const resolvedItems = (0, import_react.useMemo)(() => {
|
|
341
|
+
return items.map((item) => ({
|
|
342
|
+
...item,
|
|
343
|
+
height: item.height ?? measuredHeights.get(item.id) ?? 0
|
|
344
|
+
}));
|
|
345
|
+
}, [items, measuredHeights]);
|
|
346
|
+
const allHeightsResolved = resolvedItems.every((item) => item.height > 0);
|
|
306
347
|
const { placements, totalHeight } = (0, import_react.useMemo)(() => {
|
|
307
|
-
if (containerWidth === 0) {
|
|
348
|
+
if (containerWidth === 0 || !allHeightsResolved) {
|
|
308
349
|
return { placements: [], totalHeight: 0 };
|
|
309
350
|
}
|
|
310
|
-
return pack(
|
|
311
|
-
}, [
|
|
351
|
+
return pack(resolvedItems, containerWidth, gap, sortStrategy);
|
|
352
|
+
}, [resolvedItems, containerWidth, gap, sortStrategy, allHeightsResolved]);
|
|
312
353
|
const placementMap = (0, import_react.useMemo)(() => {
|
|
313
354
|
const map = /* @__PURE__ */ new Map();
|
|
314
355
|
for (const p of placements) {
|
|
315
|
-
map.set(p.id, { x: p.x, y: p.y });
|
|
356
|
+
map.set(p.id, { x: p.x, y: p.y, height: p.height });
|
|
316
357
|
}
|
|
317
358
|
return map;
|
|
318
359
|
}, [placements]);
|
|
@@ -325,7 +366,8 @@ function HermitageLayout({
|
|
|
325
366
|
return posA.x - posB.x;
|
|
326
367
|
});
|
|
327
368
|
}, [items, placementMap]);
|
|
328
|
-
|
|
369
|
+
const showLayout = allHeightsResolved && containerWidth > 0;
|
|
370
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
329
371
|
"div",
|
|
330
372
|
{
|
|
331
373
|
ref: containerRef,
|
|
@@ -335,24 +377,47 @@ function HermitageLayout({
|
|
|
335
377
|
width: fixedWidth ?? "100%",
|
|
336
378
|
height: totalHeight || void 0
|
|
337
379
|
},
|
|
338
|
-
children:
|
|
339
|
-
|
|
340
|
-
if (!position) return null;
|
|
341
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
380
|
+
children: [
|
|
381
|
+
needsMeasurement && containerWidth > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
342
382
|
"div",
|
|
343
383
|
{
|
|
384
|
+
ref: measureRef,
|
|
385
|
+
"aria-hidden": "true",
|
|
344
386
|
style: {
|
|
345
387
|
position: "absolute",
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
width: item.width,
|
|
349
|
-
height: item.height
|
|
388
|
+
visibility: "hidden",
|
|
389
|
+
pointerEvents: "none"
|
|
350
390
|
},
|
|
351
|
-
children: item.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
391
|
+
children: itemsNeedingMeasure.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
392
|
+
"div",
|
|
393
|
+
{
|
|
394
|
+
"data-measure-id": item.id,
|
|
395
|
+
style: { width: item.width },
|
|
396
|
+
children: item.content
|
|
397
|
+
},
|
|
398
|
+
item.id
|
|
399
|
+
))
|
|
400
|
+
}
|
|
401
|
+
),
|
|
402
|
+
showLayout && sortedItems.map((item) => {
|
|
403
|
+
const position = placementMap.get(item.id);
|
|
404
|
+
if (!position) return null;
|
|
405
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
406
|
+
"div",
|
|
407
|
+
{
|
|
408
|
+
style: {
|
|
409
|
+
position: "absolute",
|
|
410
|
+
left: position.x,
|
|
411
|
+
top: position.y,
|
|
412
|
+
width: item.width,
|
|
413
|
+
height: position.height
|
|
414
|
+
},
|
|
415
|
+
children: item.content
|
|
416
|
+
},
|
|
417
|
+
item.id
|
|
418
|
+
);
|
|
419
|
+
})
|
|
420
|
+
]
|
|
356
421
|
}
|
|
357
422
|
);
|
|
358
423
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/HermitageLayout.tsx","../src/maxrects.ts"],"sourcesContent":["export { HermitageLayout } from \"./HermitageLayout\";\nexport type { HermitageLayoutProps, LayoutItem, SortStrategy } from \"./types\";\n","import { useMemo, useRef, useState, useEffect } from \"react\";\nimport { HermitageLayoutProps } from \"./types\";\nimport { pack } from \"./maxrects\";\n\nexport function HermitageLayout({\n items,\n containerWidth: fixedWidth,\n gap = 8,\n sortStrategy = \"none\",\n className,\n}: HermitageLayoutProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [measuredWidth, setMeasuredWidth] = useState<number>(0);\n\n // Use fixed width if provided, otherwise use measured width\n const containerWidth = fixedWidth ?? measuredWidth;\n\n // Measure container width using ResizeObserver\n useEffect(() => {\n if (fixedWidth !== undefined) {\n // Fixed width provided, no need to measure\n return;\n }\n\n const element = containerRef.current;\n if (!element) return;\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const width = entry.contentRect.width;\n setMeasuredWidth(width);\n }\n });\n\n observer.observe(element);\n\n // Initial measurement\n setMeasuredWidth(element.getBoundingClientRect().width);\n\n return () => observer.disconnect();\n }, [fixedWidth]);\n\n const { placements, totalHeight } = useMemo(() => {\n if (containerWidth === 0) {\n // Not yet measured, return empty layout\n return { placements: [], totalHeight: 0 };\n }\n return pack(items, containerWidth, gap, sortStrategy);\n }, [items, containerWidth, gap, sortStrategy]);\n\n // Create a map for quick lookup of placements by id\n const placementMap = useMemo(() => {\n const map = new Map<string, { x: number; y: number }>();\n for (const p of placements) {\n map.set(p.id, { x: p.x, y: p.y });\n }\n return map;\n }, [placements]);\n\n // Sort items by visual position (y, then x) for proper tab order\n const sortedItems = useMemo(() => {\n return [...items].sort((a, b) => {\n const posA = placementMap.get(a.id);\n const posB = placementMap.get(b.id);\n if (!posA || !posB) return 0;\n if (posA.y !== posB.y) return posA.y - posB.y;\n return posA.x - posB.x;\n });\n }, [items, placementMap]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{\n position: \"relative\",\n width: fixedWidth ?? \"100%\",\n height: totalHeight || undefined,\n }}\n >\n {sortedItems.map((item) => {\n const position = placementMap.get(item.id);\n if (!position) return null;\n\n return (\n <div\n key={item.id}\n style={{\n position: \"absolute\",\n left: position.x,\n top: position.y,\n width: item.width,\n height: item.height,\n }}\n >\n {item.content}\n </div>\n );\n })}\n </div>\n );\n}\n","import { Rect, PlacedItem, PackResult, SortStrategy } from \"./types\";\n\ninterface PackInput {\n id: string;\n width: number;\n height: number;\n}\n\n/**\n * Calculate a safe maximum height for the packing area.\n * Uses sum of all item heights as worst-case (vertical stack).\n */\nfunction calcMaxHeight(items: PackInput[], gap: number): number {\n const totalHeight = items.reduce((sum, item) => sum + item.height + gap, 0);\n return Math.max(totalHeight, 1000); // At least 1000px\n}\n\nfunction sortItems(items: PackInput[], strategy: SortStrategy): PackInput[] {\n if (strategy === \"none\" || strategy === \"ordered\") {\n // \"ordered\" uses a different placement approach, not pre-sorting\n return items;\n }\n\n const sorted = [...items];\n\n switch (strategy) {\n case \"height-desc\":\n sorted.sort((a, b) => b.height - a.height);\n break;\n }\n\n return sorted;\n}\n\n/**\n * MaxRects bin-packing algorithm.\n * Places rectangles in a container, minimizing wasted space.\n */\nexport function pack(\n items: PackInput[],\n containerWidth: number,\n gap: number = 0,\n sortStrategy: SortStrategy = \"none\"\n): PackResult {\n if (sortStrategy === \"ordered\") {\n return packOrdered(items, containerWidth, gap);\n }\n\n const sortedItems = sortItems(items, sortStrategy);\n\n // Start with one large free rectangle\n const maxHeight = calcMaxHeight(sortedItems, gap);\n const freeRects: Rect[] = [\n { x: 0, y: 0, width: containerWidth, height: maxHeight },\n ];\n\n const placements: PlacedItem[] = [];\n let maxY = 0;\n\n for (const item of sortedItems) {\n // Account for gap in item dimensions during placement\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const position = findBestPosition(paddedWidth, paddedHeight, freeRects);\n\n if (position) {\n const placement: PlacedItem = {\n id: item.id,\n x: position.x,\n y: position.y,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n // Track the maximum Y extent\n maxY = Math.max(maxY, position.y + item.height);\n\n // Split free rects around the placed item (using padded dimensions)\n const placedRect: Rect = {\n x: position.x,\n y: position.y,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n }\n }\n\n return {\n placements,\n totalHeight: maxY,\n };\n}\n\n/**\n * Ordered packing strategy:\n * - Row 1 (y=0): Strict input order, left-to-right, no gap filling\n * - Row 2: Next batch of items (in input order), can be reordered for better fit\n * - Row 3+: Full algorithmic freedom (height-desc sorting)\n */\nfunction packOrdered(\n items: PackInput[],\n containerWidth: number,\n gap: number\n): PackResult {\n // Start with full container as free space\n const maxHeight = calcMaxHeight(items, gap);\n const freeRects: Rect[] = [\n { x: 0, y: 0, width: containerWidth, height: maxHeight },\n ];\n\n const placements: PlacedItem[] = [];\n let maxY = 0;\n let itemIndex = 0;\n\n // --- Row 1: Strict left-to-right order at y=0 ---\n let row1NextX = 0;\n const row1Count = countRow1Items(items, containerWidth, gap);\n\n for (let i = 0; i < row1Count; i++) {\n const item = items[itemIndex];\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const placement: PlacedItem = {\n id: item.id,\n x: row1NextX,\n y: 0,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n maxY = Math.max(maxY, item.height);\n\n // Update freeRects to account for this placement\n const placedRect: Rect = {\n x: row1NextX,\n y: 0,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n\n row1NextX += paddedWidth;\n itemIndex++;\n }\n\n // Remaining items after row 1\n const remainingItems = items.slice(itemIndex);\n\n if (remainingItems.length === 0) {\n return { placements, totalHeight: maxY };\n }\n\n // --- Row 2: Next batch of items, reorderable for better fit ---\n // Take roughly the same count as row 1 (or remaining, whichever is smaller)\n const row2CandidateCount = Math.min(row1Count, remainingItems.length);\n const row2Candidates = remainingItems.slice(0, row2CandidateCount);\n const row3Items = remainingItems.slice(row2CandidateCount);\n\n // Sort row 2 candidates by height-desc for better gap filling\n const row2Sorted = [...row2Candidates].sort((a, b) => b.height - a.height);\n\n for (const item of row2Sorted) {\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const position = findBestPosition(paddedWidth, paddedHeight, freeRects);\n\n if (position) {\n const placement: PlacedItem = {\n id: item.id,\n x: position.x,\n y: position.y,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n maxY = Math.max(maxY, position.y + item.height);\n\n const placedRect: Rect = {\n x: position.x,\n y: position.y,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n }\n }\n\n // --- Row 3+: Full freedom, height-desc sorting ---\n const row3Sorted = [...row3Items].sort((a, b) => b.height - a.height);\n\n for (const item of row3Sorted) {\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const position = findBestPosition(paddedWidth, paddedHeight, freeRects);\n\n if (position) {\n const placement: PlacedItem = {\n id: item.id,\n x: position.x,\n y: position.y,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n maxY = Math.max(maxY, position.y + item.height);\n\n const placedRect: Rect = {\n x: position.x,\n y: position.y,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n }\n }\n\n return {\n placements,\n totalHeight: maxY,\n };\n}\n\n/**\n * Count how many items fit in row 1 (strict left-to-right at y=0).\n */\nfunction countRow1Items(\n items: PackInput[],\n containerWidth: number,\n gap: number\n): number {\n let x = 0;\n let count = 0;\n\n for (const item of items) {\n const paddedWidth = item.width + gap;\n if (x + paddedWidth > containerWidth + gap) {\n break;\n }\n x += paddedWidth;\n count++;\n }\n\n return count;\n}\n\n/**\n * Find a position for an item, but only accept positions at a specific y.\n */\nfunction findPositionAtY(\n width: number,\n height: number,\n freeRects: Rect[],\n targetY: number\n): { x: number; y: number } | null {\n let bestX = Infinity;\n let bestPosition: { x: number; y: number } | null = null;\n\n for (const rect of freeRects) {\n // Only consider rects that start at targetY\n if (rect.y !== targetY) continue;\n\n if (width <= rect.width && height <= rect.height) {\n const x = rect.x;\n if (x < bestX) {\n bestX = x;\n bestPosition = { x, y: targetY };\n }\n }\n }\n\n return bestPosition;\n}\n\n/**\n * Get the maximum height among a list of items.\n */\nfunction getMaxHeight(items: PackInput[]): number {\n return items.reduce((max, item) => Math.max(max, item.height), 0);\n}\n\n/**\n * Find the best position for an item using \"Best Y then Best X\" heuristic.\n * Prefers positions higher up (smaller Y), then leftward (smaller X).\n */\nfunction findBestPosition(\n width: number,\n height: number,\n freeRects: Rect[]\n): { x: number; y: number } | null {\n let bestY = Infinity;\n let bestX = Infinity;\n let bestPosition: { x: number; y: number } | null = null;\n\n for (const rect of freeRects) {\n // Check if item fits in this free rect\n if (width <= rect.width && height <= rect.height) {\n // Position at top-left corner of free rect\n const x = rect.x;\n const y = rect.y;\n\n // Prefer smaller Y, then smaller X\n if (y < bestY || (y === bestY && x < bestX)) {\n bestY = y;\n bestX = x;\n bestPosition = { x, y };\n }\n }\n }\n\n return bestPosition;\n}\n\n/**\n * Split free rectangles that overlap with the placed rectangle.\n * Generates up to 4 new rectangles around the placed item.\n */\nfunction splitFreeRects(freeRects: Rect[], placedRect: Rect): void {\n const newRects: Rect[] = [];\n\n for (let i = freeRects.length - 1; i >= 0; i--) {\n const freeRect = freeRects[i];\n\n // Check if this free rect overlaps with placed rect\n if (!rectsOverlap(freeRect, placedRect)) {\n continue;\n }\n\n // Remove the overlapping free rect\n freeRects.splice(i, 1);\n\n // Generate new free rects from the non-overlapping portions\n\n // Left portion\n if (placedRect.x > freeRect.x) {\n newRects.push({\n x: freeRect.x,\n y: freeRect.y,\n width: placedRect.x - freeRect.x,\n height: freeRect.height,\n });\n }\n\n // Right portion\n const placedRight = placedRect.x + placedRect.width;\n const freeRight = freeRect.x + freeRect.width;\n if (placedRight < freeRight) {\n newRects.push({\n x: placedRight,\n y: freeRect.y,\n width: freeRight - placedRight,\n height: freeRect.height,\n });\n }\n\n // Top portion\n if (placedRect.y > freeRect.y) {\n newRects.push({\n x: freeRect.x,\n y: freeRect.y,\n width: freeRect.width,\n height: placedRect.y - freeRect.y,\n });\n }\n\n // Bottom portion\n const placedBottom = placedRect.y + placedRect.height;\n const freeBottom = freeRect.y + freeRect.height;\n if (placedBottom < freeBottom) {\n newRects.push({\n x: freeRect.x,\n y: placedBottom,\n width: freeRect.width,\n height: freeBottom - placedBottom,\n });\n }\n }\n\n // Add all new rects\n freeRects.push(...newRects);\n}\n\n/**\n * Remove free rectangles that are fully contained within other free rectangles.\n */\nfunction pruneFreeRects(freeRects: Rect[]): void {\n for (let i = freeRects.length - 1; i >= 0; i--) {\n for (let j = 0; j < freeRects.length; j++) {\n if (i === j) continue;\n\n if (rectContains(freeRects[j], freeRects[i])) {\n freeRects.splice(i, 1);\n break;\n }\n }\n }\n}\n\n/**\n * Check if two rectangles overlap.\n */\nfunction rectsOverlap(a: Rect, b: Rect): boolean {\n return (\n a.x < b.x + b.width &&\n a.x + a.width > b.x &&\n a.y < b.y + b.height &&\n a.y + a.height > b.y\n );\n}\n\n/**\n * Check if rectangle `outer` fully contains rectangle `inner`.\n */\nfunction rectContains(outer: Rect, inner: Rect): boolean {\n return (\n inner.x >= outer.x &&\n inner.y >= outer.y &&\n inner.x + inner.width <= outer.x + outer.width &&\n inner.y + inner.height <= outer.y + outer.height\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/HermitageLayout.tsx","../src/maxrects.ts"],"sourcesContent":["export { HermitageLayout } from \"./HermitageLayout\";\nexport type { HermitageLayoutProps, LayoutItem, SortStrategy } from \"./types\";\n","import { useMemo, useRef, useState, useEffect, useCallback } from \"react\";\nimport { HermitageLayoutProps, LayoutItem } from \"./types\";\nimport { pack } from \"./maxrects\";\n\nexport function HermitageLayout({\n items,\n containerWidth: fixedWidth,\n gap = 8,\n sortStrategy = \"none\",\n className,\n}: HermitageLayoutProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const measureRef = useRef<HTMLDivElement>(null);\n const [measuredWidth, setMeasuredWidth] = useState<number>(0);\n const [measuredHeights, setMeasuredHeights] = useState<Map<string, number>>(\n new Map()\n );\n const [isMeasuring, setIsMeasuring] = useState(false);\n\n // Use fixed width if provided, otherwise use measured width\n const containerWidth = fixedWidth ?? measuredWidth;\n\n // Check which items need height measurement\n const itemsNeedingMeasure = useMemo(() => {\n return items.filter((item) => item.height === undefined);\n }, [items]);\n\n const needsMeasurement = itemsNeedingMeasure.length > 0;\n\n // Measure container width using ResizeObserver\n useEffect(() => {\n if (fixedWidth !== undefined) {\n return;\n }\n\n const element = containerRef.current;\n if (!element) return;\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const width = entry.contentRect.width;\n setMeasuredWidth(width);\n }\n });\n\n observer.observe(element);\n setMeasuredWidth(element.getBoundingClientRect().width);\n\n return () => observer.disconnect();\n }, [fixedWidth]);\n\n // Measure item heights when needed\n useEffect(() => {\n if (!needsMeasurement || containerWidth === 0) {\n setIsMeasuring(false);\n return;\n }\n\n setIsMeasuring(true);\n\n // Wait for next frame to ensure measurement elements are rendered\n requestAnimationFrame(() => {\n const measureContainer = measureRef.current;\n if (!measureContainer) {\n setIsMeasuring(false);\n return;\n }\n\n const newHeights = new Map<string, number>();\n\n for (const item of itemsNeedingMeasure) {\n const element = measureContainer.querySelector(\n `[data-measure-id=\"${item.id}\"]`\n );\n if (element) {\n newHeights.set(item.id, element.getBoundingClientRect().height);\n }\n }\n\n setMeasuredHeights(newHeights);\n setIsMeasuring(false);\n });\n }, [itemsNeedingMeasure, needsMeasurement, containerWidth]);\n\n // Build items with resolved heights\n const resolvedItems = useMemo(() => {\n return items.map((item) => ({\n ...item,\n height: item.height ?? measuredHeights.get(item.id) ?? 0,\n }));\n }, [items, measuredHeights]);\n\n // Check if we have all heights resolved\n const allHeightsResolved = resolvedItems.every((item) => item.height > 0);\n\n const { placements, totalHeight } = useMemo(() => {\n if (containerWidth === 0 || !allHeightsResolved) {\n return { placements: [], totalHeight: 0 };\n }\n return pack(resolvedItems, containerWidth, gap, sortStrategy);\n }, [resolvedItems, containerWidth, gap, sortStrategy, allHeightsResolved]);\n\n // Create a map for quick lookup of placements by id\n const placementMap = useMemo(() => {\n const map = new Map<string, { x: number; y: number; height: number }>();\n for (const p of placements) {\n map.set(p.id, { x: p.x, y: p.y, height: p.height });\n }\n return map;\n }, [placements]);\n\n // Sort items by visual position (y, then x) for proper tab order\n const sortedItems = useMemo(() => {\n return [...items].sort((a, b) => {\n const posA = placementMap.get(a.id);\n const posB = placementMap.get(b.id);\n if (!posA || !posB) return 0;\n if (posA.y !== posB.y) return posA.y - posB.y;\n return posA.x - posB.x;\n });\n }, [items, placementMap]);\n\n // Show layout only when all measurements are complete\n const showLayout = allHeightsResolved && containerWidth > 0;\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{\n position: \"relative\",\n width: fixedWidth ?? \"100%\",\n height: totalHeight || undefined,\n }}\n >\n {/* Hidden measurement container for items without explicit height */}\n {needsMeasurement && containerWidth > 0 && (\n <div\n ref={measureRef}\n aria-hidden=\"true\"\n style={{\n position: \"absolute\",\n visibility: \"hidden\",\n pointerEvents: \"none\",\n }}\n >\n {itemsNeedingMeasure.map((item) => (\n <div\n key={item.id}\n data-measure-id={item.id}\n style={{ width: item.width }}\n >\n {item.content}\n </div>\n ))}\n </div>\n )}\n\n {/* Main layout */}\n {showLayout &&\n sortedItems.map((item) => {\n const position = placementMap.get(item.id);\n if (!position) return null;\n\n return (\n <div\n key={item.id}\n style={{\n position: \"absolute\",\n left: position.x,\n top: position.y,\n width: item.width,\n height: position.height,\n }}\n >\n {item.content}\n </div>\n );\n })}\n </div>\n );\n}\n","import { Rect, PlacedItem, PackResult, SortStrategy } from \"./types\";\n\ninterface PackInput {\n id: string;\n width: number;\n height: number;\n}\n\n/**\n * Calculate a safe maximum height for the packing area.\n * Uses sum of all item heights as worst-case (vertical stack).\n */\nfunction calcMaxHeight(items: PackInput[], gap: number): number {\n const totalHeight = items.reduce((sum, item) => sum + item.height + gap, 0);\n return Math.max(totalHeight, 1000); // At least 1000px\n}\n\nfunction sortItems(items: PackInput[], strategy: SortStrategy): PackInput[] {\n if (strategy === \"none\" || strategy === \"ordered\") {\n // \"ordered\" uses a different placement approach, not pre-sorting\n return items;\n }\n\n const sorted = [...items];\n\n switch (strategy) {\n case \"height-desc\":\n sorted.sort((a, b) => b.height - a.height);\n break;\n }\n\n return sorted;\n}\n\n/**\n * MaxRects bin-packing algorithm.\n * Places rectangles in a container, minimizing wasted space.\n */\nexport function pack(\n items: PackInput[],\n containerWidth: number,\n gap: number = 0,\n sortStrategy: SortStrategy = \"none\"\n): PackResult {\n if (sortStrategy === \"ordered\") {\n return packOrdered(items, containerWidth, gap);\n }\n\n const sortedItems = sortItems(items, sortStrategy);\n\n // Start with one large free rectangle\n const maxHeight = calcMaxHeight(sortedItems, gap);\n const freeRects: Rect[] = [\n { x: 0, y: 0, width: containerWidth, height: maxHeight },\n ];\n\n const placements: PlacedItem[] = [];\n let maxY = 0;\n\n for (const item of sortedItems) {\n // Account for gap in item dimensions during placement\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const position = findBestPosition(paddedWidth, paddedHeight, freeRects);\n\n if (position) {\n const placement: PlacedItem = {\n id: item.id,\n x: position.x,\n y: position.y,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n // Track the maximum Y extent\n maxY = Math.max(maxY, position.y + item.height);\n\n // Split free rects around the placed item (using padded dimensions)\n const placedRect: Rect = {\n x: position.x,\n y: position.y,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n }\n }\n\n return {\n placements,\n totalHeight: maxY,\n };\n}\n\n/**\n * Ordered packing strategy:\n * - Row 1 (y=0): Strict input order, left-to-right, no gap filling\n * - Row 2: Next batch of items (in input order), can be reordered for better fit\n * - Row 3+: Full algorithmic freedom (height-desc sorting)\n */\nfunction packOrdered(\n items: PackInput[],\n containerWidth: number,\n gap: number\n): PackResult {\n // Start with full container as free space\n const maxHeight = calcMaxHeight(items, gap);\n const freeRects: Rect[] = [\n { x: 0, y: 0, width: containerWidth, height: maxHeight },\n ];\n\n const placements: PlacedItem[] = [];\n let maxY = 0;\n let itemIndex = 0;\n\n // --- Row 1: Strict left-to-right order at y=0 ---\n let row1NextX = 0;\n const row1Count = countRow1Items(items, containerWidth, gap);\n\n for (let i = 0; i < row1Count; i++) {\n const item = items[itemIndex];\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const placement: PlacedItem = {\n id: item.id,\n x: row1NextX,\n y: 0,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n maxY = Math.max(maxY, item.height);\n\n // Update freeRects to account for this placement\n const placedRect: Rect = {\n x: row1NextX,\n y: 0,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n\n row1NextX += paddedWidth;\n itemIndex++;\n }\n\n // Remaining items after row 1\n const remainingItems = items.slice(itemIndex);\n\n if (remainingItems.length === 0) {\n return { placements, totalHeight: maxY };\n }\n\n // --- Row 2: Next batch of items, reorderable for better fit ---\n // Take roughly the same count as row 1 (or remaining, whichever is smaller)\n const row2CandidateCount = Math.min(row1Count, remainingItems.length);\n const row2Candidates = remainingItems.slice(0, row2CandidateCount);\n const row3Items = remainingItems.slice(row2CandidateCount);\n\n // Sort row 2 candidates by height-desc for better gap filling\n const row2Sorted = [...row2Candidates].sort((a, b) => b.height - a.height);\n\n for (const item of row2Sorted) {\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const position = findBestPosition(paddedWidth, paddedHeight, freeRects);\n\n if (position) {\n const placement: PlacedItem = {\n id: item.id,\n x: position.x,\n y: position.y,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n maxY = Math.max(maxY, position.y + item.height);\n\n const placedRect: Rect = {\n x: position.x,\n y: position.y,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n }\n }\n\n // --- Row 3+: Full freedom, height-desc sorting ---\n const row3Sorted = [...row3Items].sort((a, b) => b.height - a.height);\n\n for (const item of row3Sorted) {\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const position = findBestPosition(paddedWidth, paddedHeight, freeRects);\n\n if (position) {\n const placement: PlacedItem = {\n id: item.id,\n x: position.x,\n y: position.y,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n maxY = Math.max(maxY, position.y + item.height);\n\n const placedRect: Rect = {\n x: position.x,\n y: position.y,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n }\n }\n\n return {\n placements,\n totalHeight: maxY,\n };\n}\n\n/**\n * Count how many items fit in row 1 (strict left-to-right at y=0).\n */\nfunction countRow1Items(\n items: PackInput[],\n containerWidth: number,\n gap: number\n): number {\n let x = 0;\n let count = 0;\n\n for (const item of items) {\n const paddedWidth = item.width + gap;\n if (x + paddedWidth > containerWidth + gap) {\n break;\n }\n x += paddedWidth;\n count++;\n }\n\n return count;\n}\n\n/**\n * Find a position for an item, but only accept positions at a specific y.\n */\nfunction findPositionAtY(\n width: number,\n height: number,\n freeRects: Rect[],\n targetY: number\n): { x: number; y: number } | null {\n let bestX = Infinity;\n let bestPosition: { x: number; y: number } | null = null;\n\n for (const rect of freeRects) {\n // Only consider rects that start at targetY\n if (rect.y !== targetY) continue;\n\n if (width <= rect.width && height <= rect.height) {\n const x = rect.x;\n if (x < bestX) {\n bestX = x;\n bestPosition = { x, y: targetY };\n }\n }\n }\n\n return bestPosition;\n}\n\n/**\n * Get the maximum height among a list of items.\n */\nfunction getMaxHeight(items: PackInput[]): number {\n return items.reduce((max, item) => Math.max(max, item.height), 0);\n}\n\n/**\n * Find the best position for an item using \"Best Y then Best X\" heuristic.\n * Prefers positions higher up (smaller Y), then leftward (smaller X).\n */\nfunction findBestPosition(\n width: number,\n height: number,\n freeRects: Rect[]\n): { x: number; y: number } | null {\n let bestY = Infinity;\n let bestX = Infinity;\n let bestPosition: { x: number; y: number } | null = null;\n\n for (const rect of freeRects) {\n // Check if item fits in this free rect\n if (width <= rect.width && height <= rect.height) {\n // Position at top-left corner of free rect\n const x = rect.x;\n const y = rect.y;\n\n // Prefer smaller Y, then smaller X\n if (y < bestY || (y === bestY && x < bestX)) {\n bestY = y;\n bestX = x;\n bestPosition = { x, y };\n }\n }\n }\n\n return bestPosition;\n}\n\n/**\n * Split free rectangles that overlap with the placed rectangle.\n * Generates up to 4 new rectangles around the placed item.\n */\nfunction splitFreeRects(freeRects: Rect[], placedRect: Rect): void {\n const newRects: Rect[] = [];\n\n for (let i = freeRects.length - 1; i >= 0; i--) {\n const freeRect = freeRects[i];\n\n // Check if this free rect overlaps with placed rect\n if (!rectsOverlap(freeRect, placedRect)) {\n continue;\n }\n\n // Remove the overlapping free rect\n freeRects.splice(i, 1);\n\n // Generate new free rects from the non-overlapping portions\n\n // Left portion\n if (placedRect.x > freeRect.x) {\n newRects.push({\n x: freeRect.x,\n y: freeRect.y,\n width: placedRect.x - freeRect.x,\n height: freeRect.height,\n });\n }\n\n // Right portion\n const placedRight = placedRect.x + placedRect.width;\n const freeRight = freeRect.x + freeRect.width;\n if (placedRight < freeRight) {\n newRects.push({\n x: placedRight,\n y: freeRect.y,\n width: freeRight - placedRight,\n height: freeRect.height,\n });\n }\n\n // Top portion\n if (placedRect.y > freeRect.y) {\n newRects.push({\n x: freeRect.x,\n y: freeRect.y,\n width: freeRect.width,\n height: placedRect.y - freeRect.y,\n });\n }\n\n // Bottom portion\n const placedBottom = placedRect.y + placedRect.height;\n const freeBottom = freeRect.y + freeRect.height;\n if (placedBottom < freeBottom) {\n newRects.push({\n x: freeRect.x,\n y: placedBottom,\n width: freeRect.width,\n height: freeBottom - placedBottom,\n });\n }\n }\n\n // Add all new rects\n freeRects.push(...newRects);\n}\n\n/**\n * Remove free rectangles that are fully contained within other free rectangles.\n */\nfunction pruneFreeRects(freeRects: Rect[]): void {\n for (let i = freeRects.length - 1; i >= 0; i--) {\n for (let j = 0; j < freeRects.length; j++) {\n if (i === j) continue;\n\n if (rectContains(freeRects[j], freeRects[i])) {\n freeRects.splice(i, 1);\n break;\n }\n }\n }\n}\n\n/**\n * Check if two rectangles overlap.\n */\nfunction rectsOverlap(a: Rect, b: Rect): boolean {\n return (\n a.x < b.x + b.width &&\n a.x + a.width > b.x &&\n a.y < b.y + b.height &&\n a.y + a.height > b.y\n );\n}\n\n/**\n * Check if rectangle `outer` fully contains rectangle `inner`.\n */\nfunction rectContains(outer: Rect, inner: Rect): boolean {\n return (\n inner.x >= outer.x &&\n inner.y >= outer.y &&\n inner.x + inner.width <= outer.x + outer.width &&\n inner.y + inner.height <= outer.y + outer.height\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -250,7 +250,7 @@ function rectContains(outer, inner) {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
// src/HermitageLayout.tsx
|
|
253
|
-
import { jsx } from "react/jsx-runtime";
|
|
253
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
254
254
|
function HermitageLayout({
|
|
255
255
|
items,
|
|
256
256
|
containerWidth: fixedWidth,
|
|
@@ -259,8 +259,17 @@ function HermitageLayout({
|
|
|
259
259
|
className
|
|
260
260
|
}) {
|
|
261
261
|
const containerRef = useRef(null);
|
|
262
|
+
const measureRef = useRef(null);
|
|
262
263
|
const [measuredWidth, setMeasuredWidth] = useState(0);
|
|
264
|
+
const [measuredHeights, setMeasuredHeights] = useState(
|
|
265
|
+
/* @__PURE__ */ new Map()
|
|
266
|
+
);
|
|
267
|
+
const [isMeasuring, setIsMeasuring] = useState(false);
|
|
263
268
|
const containerWidth = fixedWidth ?? measuredWidth;
|
|
269
|
+
const itemsNeedingMeasure = useMemo(() => {
|
|
270
|
+
return items.filter((item) => item.height === void 0);
|
|
271
|
+
}, [items]);
|
|
272
|
+
const needsMeasurement = itemsNeedingMeasure.length > 0;
|
|
264
273
|
useEffect(() => {
|
|
265
274
|
if (fixedWidth !== void 0) {
|
|
266
275
|
return;
|
|
@@ -277,16 +286,48 @@ function HermitageLayout({
|
|
|
277
286
|
setMeasuredWidth(element.getBoundingClientRect().width);
|
|
278
287
|
return () => observer.disconnect();
|
|
279
288
|
}, [fixedWidth]);
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
if (!needsMeasurement || containerWidth === 0) {
|
|
291
|
+
setIsMeasuring(false);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
setIsMeasuring(true);
|
|
295
|
+
requestAnimationFrame(() => {
|
|
296
|
+
const measureContainer = measureRef.current;
|
|
297
|
+
if (!measureContainer) {
|
|
298
|
+
setIsMeasuring(false);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const newHeights = /* @__PURE__ */ new Map();
|
|
302
|
+
for (const item of itemsNeedingMeasure) {
|
|
303
|
+
const element = measureContainer.querySelector(
|
|
304
|
+
`[data-measure-id="${item.id}"]`
|
|
305
|
+
);
|
|
306
|
+
if (element) {
|
|
307
|
+
newHeights.set(item.id, element.getBoundingClientRect().height);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
setMeasuredHeights(newHeights);
|
|
311
|
+
setIsMeasuring(false);
|
|
312
|
+
});
|
|
313
|
+
}, [itemsNeedingMeasure, needsMeasurement, containerWidth]);
|
|
314
|
+
const resolvedItems = useMemo(() => {
|
|
315
|
+
return items.map((item) => ({
|
|
316
|
+
...item,
|
|
317
|
+
height: item.height ?? measuredHeights.get(item.id) ?? 0
|
|
318
|
+
}));
|
|
319
|
+
}, [items, measuredHeights]);
|
|
320
|
+
const allHeightsResolved = resolvedItems.every((item) => item.height > 0);
|
|
280
321
|
const { placements, totalHeight } = useMemo(() => {
|
|
281
|
-
if (containerWidth === 0) {
|
|
322
|
+
if (containerWidth === 0 || !allHeightsResolved) {
|
|
282
323
|
return { placements: [], totalHeight: 0 };
|
|
283
324
|
}
|
|
284
|
-
return pack(
|
|
285
|
-
}, [
|
|
325
|
+
return pack(resolvedItems, containerWidth, gap, sortStrategy);
|
|
326
|
+
}, [resolvedItems, containerWidth, gap, sortStrategy, allHeightsResolved]);
|
|
286
327
|
const placementMap = useMemo(() => {
|
|
287
328
|
const map = /* @__PURE__ */ new Map();
|
|
288
329
|
for (const p of placements) {
|
|
289
|
-
map.set(p.id, { x: p.x, y: p.y });
|
|
330
|
+
map.set(p.id, { x: p.x, y: p.y, height: p.height });
|
|
290
331
|
}
|
|
291
332
|
return map;
|
|
292
333
|
}, [placements]);
|
|
@@ -299,7 +340,8 @@ function HermitageLayout({
|
|
|
299
340
|
return posA.x - posB.x;
|
|
300
341
|
});
|
|
301
342
|
}, [items, placementMap]);
|
|
302
|
-
|
|
343
|
+
const showLayout = allHeightsResolved && containerWidth > 0;
|
|
344
|
+
return /* @__PURE__ */ jsxs(
|
|
303
345
|
"div",
|
|
304
346
|
{
|
|
305
347
|
ref: containerRef,
|
|
@@ -309,24 +351,47 @@ function HermitageLayout({
|
|
|
309
351
|
width: fixedWidth ?? "100%",
|
|
310
352
|
height: totalHeight || void 0
|
|
311
353
|
},
|
|
312
|
-
children:
|
|
313
|
-
|
|
314
|
-
if (!position) return null;
|
|
315
|
-
return /* @__PURE__ */ jsx(
|
|
354
|
+
children: [
|
|
355
|
+
needsMeasurement && containerWidth > 0 && /* @__PURE__ */ jsx(
|
|
316
356
|
"div",
|
|
317
357
|
{
|
|
358
|
+
ref: measureRef,
|
|
359
|
+
"aria-hidden": "true",
|
|
318
360
|
style: {
|
|
319
361
|
position: "absolute",
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
width: item.width,
|
|
323
|
-
height: item.height
|
|
362
|
+
visibility: "hidden",
|
|
363
|
+
pointerEvents: "none"
|
|
324
364
|
},
|
|
325
|
-
children: item
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
365
|
+
children: itemsNeedingMeasure.map((item) => /* @__PURE__ */ jsx(
|
|
366
|
+
"div",
|
|
367
|
+
{
|
|
368
|
+
"data-measure-id": item.id,
|
|
369
|
+
style: { width: item.width },
|
|
370
|
+
children: item.content
|
|
371
|
+
},
|
|
372
|
+
item.id
|
|
373
|
+
))
|
|
374
|
+
}
|
|
375
|
+
),
|
|
376
|
+
showLayout && sortedItems.map((item) => {
|
|
377
|
+
const position = placementMap.get(item.id);
|
|
378
|
+
if (!position) return null;
|
|
379
|
+
return /* @__PURE__ */ jsx(
|
|
380
|
+
"div",
|
|
381
|
+
{
|
|
382
|
+
style: {
|
|
383
|
+
position: "absolute",
|
|
384
|
+
left: position.x,
|
|
385
|
+
top: position.y,
|
|
386
|
+
width: item.width,
|
|
387
|
+
height: position.height
|
|
388
|
+
},
|
|
389
|
+
children: item.content
|
|
390
|
+
},
|
|
391
|
+
item.id
|
|
392
|
+
);
|
|
393
|
+
})
|
|
394
|
+
]
|
|
330
395
|
}
|
|
331
396
|
);
|
|
332
397
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/HermitageLayout.tsx","../src/maxrects.ts"],"sourcesContent":["import { useMemo, useRef, useState, useEffect } from \"react\";\nimport { HermitageLayoutProps } from \"./types\";\nimport { pack } from \"./maxrects\";\n\nexport function HermitageLayout({\n items,\n containerWidth: fixedWidth,\n gap = 8,\n sortStrategy = \"none\",\n className,\n}: HermitageLayoutProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [measuredWidth, setMeasuredWidth] = useState<number>(0);\n\n // Use fixed width if provided, otherwise use measured width\n const containerWidth = fixedWidth ?? measuredWidth;\n\n // Measure container width using ResizeObserver\n useEffect(() => {\n if (fixedWidth !== undefined) {\n // Fixed width provided, no need to measure\n return;\n }\n\n const element = containerRef.current;\n if (!element) return;\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const width = entry.contentRect.width;\n setMeasuredWidth(width);\n }\n });\n\n observer.observe(element);\n\n // Initial measurement\n setMeasuredWidth(element.getBoundingClientRect().width);\n\n return () => observer.disconnect();\n }, [fixedWidth]);\n\n const { placements, totalHeight } = useMemo(() => {\n if (containerWidth === 0) {\n // Not yet measured, return empty layout\n return { placements: [], totalHeight: 0 };\n }\n return pack(items, containerWidth, gap, sortStrategy);\n }, [items, containerWidth, gap, sortStrategy]);\n\n // Create a map for quick lookup of placements by id\n const placementMap = useMemo(() => {\n const map = new Map<string, { x: number; y: number }>();\n for (const p of placements) {\n map.set(p.id, { x: p.x, y: p.y });\n }\n return map;\n }, [placements]);\n\n // Sort items by visual position (y, then x) for proper tab order\n const sortedItems = useMemo(() => {\n return [...items].sort((a, b) => {\n const posA = placementMap.get(a.id);\n const posB = placementMap.get(b.id);\n if (!posA || !posB) return 0;\n if (posA.y !== posB.y) return posA.y - posB.y;\n return posA.x - posB.x;\n });\n }, [items, placementMap]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{\n position: \"relative\",\n width: fixedWidth ?? \"100%\",\n height: totalHeight || undefined,\n }}\n >\n {sortedItems.map((item) => {\n const position = placementMap.get(item.id);\n if (!position) return null;\n\n return (\n <div\n key={item.id}\n style={{\n position: \"absolute\",\n left: position.x,\n top: position.y,\n width: item.width,\n height: item.height,\n }}\n >\n {item.content}\n </div>\n );\n })}\n </div>\n );\n}\n","import { Rect, PlacedItem, PackResult, SortStrategy } from \"./types\";\n\ninterface PackInput {\n id: string;\n width: number;\n height: number;\n}\n\n/**\n * Calculate a safe maximum height for the packing area.\n * Uses sum of all item heights as worst-case (vertical stack).\n */\nfunction calcMaxHeight(items: PackInput[], gap: number): number {\n const totalHeight = items.reduce((sum, item) => sum + item.height + gap, 0);\n return Math.max(totalHeight, 1000); // At least 1000px\n}\n\nfunction sortItems(items: PackInput[], strategy: SortStrategy): PackInput[] {\n if (strategy === \"none\" || strategy === \"ordered\") {\n // \"ordered\" uses a different placement approach, not pre-sorting\n return items;\n }\n\n const sorted = [...items];\n\n switch (strategy) {\n case \"height-desc\":\n sorted.sort((a, b) => b.height - a.height);\n break;\n }\n\n return sorted;\n}\n\n/**\n * MaxRects bin-packing algorithm.\n * Places rectangles in a container, minimizing wasted space.\n */\nexport function pack(\n items: PackInput[],\n containerWidth: number,\n gap: number = 0,\n sortStrategy: SortStrategy = \"none\"\n): PackResult {\n if (sortStrategy === \"ordered\") {\n return packOrdered(items, containerWidth, gap);\n }\n\n const sortedItems = sortItems(items, sortStrategy);\n\n // Start with one large free rectangle\n const maxHeight = calcMaxHeight(sortedItems, gap);\n const freeRects: Rect[] = [\n { x: 0, y: 0, width: containerWidth, height: maxHeight },\n ];\n\n const placements: PlacedItem[] = [];\n let maxY = 0;\n\n for (const item of sortedItems) {\n // Account for gap in item dimensions during placement\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const position = findBestPosition(paddedWidth, paddedHeight, freeRects);\n\n if (position) {\n const placement: PlacedItem = {\n id: item.id,\n x: position.x,\n y: position.y,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n // Track the maximum Y extent\n maxY = Math.max(maxY, position.y + item.height);\n\n // Split free rects around the placed item (using padded dimensions)\n const placedRect: Rect = {\n x: position.x,\n y: position.y,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n }\n }\n\n return {\n placements,\n totalHeight: maxY,\n };\n}\n\n/**\n * Ordered packing strategy:\n * - Row 1 (y=0): Strict input order, left-to-right, no gap filling\n * - Row 2: Next batch of items (in input order), can be reordered for better fit\n * - Row 3+: Full algorithmic freedom (height-desc sorting)\n */\nfunction packOrdered(\n items: PackInput[],\n containerWidth: number,\n gap: number\n): PackResult {\n // Start with full container as free space\n const maxHeight = calcMaxHeight(items, gap);\n const freeRects: Rect[] = [\n { x: 0, y: 0, width: containerWidth, height: maxHeight },\n ];\n\n const placements: PlacedItem[] = [];\n let maxY = 0;\n let itemIndex = 0;\n\n // --- Row 1: Strict left-to-right order at y=0 ---\n let row1NextX = 0;\n const row1Count = countRow1Items(items, containerWidth, gap);\n\n for (let i = 0; i < row1Count; i++) {\n const item = items[itemIndex];\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const placement: PlacedItem = {\n id: item.id,\n x: row1NextX,\n y: 0,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n maxY = Math.max(maxY, item.height);\n\n // Update freeRects to account for this placement\n const placedRect: Rect = {\n x: row1NextX,\n y: 0,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n\n row1NextX += paddedWidth;\n itemIndex++;\n }\n\n // Remaining items after row 1\n const remainingItems = items.slice(itemIndex);\n\n if (remainingItems.length === 0) {\n return { placements, totalHeight: maxY };\n }\n\n // --- Row 2: Next batch of items, reorderable for better fit ---\n // Take roughly the same count as row 1 (or remaining, whichever is smaller)\n const row2CandidateCount = Math.min(row1Count, remainingItems.length);\n const row2Candidates = remainingItems.slice(0, row2CandidateCount);\n const row3Items = remainingItems.slice(row2CandidateCount);\n\n // Sort row 2 candidates by height-desc for better gap filling\n const row2Sorted = [...row2Candidates].sort((a, b) => b.height - a.height);\n\n for (const item of row2Sorted) {\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const position = findBestPosition(paddedWidth, paddedHeight, freeRects);\n\n if (position) {\n const placement: PlacedItem = {\n id: item.id,\n x: position.x,\n y: position.y,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n maxY = Math.max(maxY, position.y + item.height);\n\n const placedRect: Rect = {\n x: position.x,\n y: position.y,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n }\n }\n\n // --- Row 3+: Full freedom, height-desc sorting ---\n const row3Sorted = [...row3Items].sort((a, b) => b.height - a.height);\n\n for (const item of row3Sorted) {\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const position = findBestPosition(paddedWidth, paddedHeight, freeRects);\n\n if (position) {\n const placement: PlacedItem = {\n id: item.id,\n x: position.x,\n y: position.y,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n maxY = Math.max(maxY, position.y + item.height);\n\n const placedRect: Rect = {\n x: position.x,\n y: position.y,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n }\n }\n\n return {\n placements,\n totalHeight: maxY,\n };\n}\n\n/**\n * Count how many items fit in row 1 (strict left-to-right at y=0).\n */\nfunction countRow1Items(\n items: PackInput[],\n containerWidth: number,\n gap: number\n): number {\n let x = 0;\n let count = 0;\n\n for (const item of items) {\n const paddedWidth = item.width + gap;\n if (x + paddedWidth > containerWidth + gap) {\n break;\n }\n x += paddedWidth;\n count++;\n }\n\n return count;\n}\n\n/**\n * Find a position for an item, but only accept positions at a specific y.\n */\nfunction findPositionAtY(\n width: number,\n height: number,\n freeRects: Rect[],\n targetY: number\n): { x: number; y: number } | null {\n let bestX = Infinity;\n let bestPosition: { x: number; y: number } | null = null;\n\n for (const rect of freeRects) {\n // Only consider rects that start at targetY\n if (rect.y !== targetY) continue;\n\n if (width <= rect.width && height <= rect.height) {\n const x = rect.x;\n if (x < bestX) {\n bestX = x;\n bestPosition = { x, y: targetY };\n }\n }\n }\n\n return bestPosition;\n}\n\n/**\n * Get the maximum height among a list of items.\n */\nfunction getMaxHeight(items: PackInput[]): number {\n return items.reduce((max, item) => Math.max(max, item.height), 0);\n}\n\n/**\n * Find the best position for an item using \"Best Y then Best X\" heuristic.\n * Prefers positions higher up (smaller Y), then leftward (smaller X).\n */\nfunction findBestPosition(\n width: number,\n height: number,\n freeRects: Rect[]\n): { x: number; y: number } | null {\n let bestY = Infinity;\n let bestX = Infinity;\n let bestPosition: { x: number; y: number } | null = null;\n\n for (const rect of freeRects) {\n // Check if item fits in this free rect\n if (width <= rect.width && height <= rect.height) {\n // Position at top-left corner of free rect\n const x = rect.x;\n const y = rect.y;\n\n // Prefer smaller Y, then smaller X\n if (y < bestY || (y === bestY && x < bestX)) {\n bestY = y;\n bestX = x;\n bestPosition = { x, y };\n }\n }\n }\n\n return bestPosition;\n}\n\n/**\n * Split free rectangles that overlap with the placed rectangle.\n * Generates up to 4 new rectangles around the placed item.\n */\nfunction splitFreeRects(freeRects: Rect[], placedRect: Rect): void {\n const newRects: Rect[] = [];\n\n for (let i = freeRects.length - 1; i >= 0; i--) {\n const freeRect = freeRects[i];\n\n // Check if this free rect overlaps with placed rect\n if (!rectsOverlap(freeRect, placedRect)) {\n continue;\n }\n\n // Remove the overlapping free rect\n freeRects.splice(i, 1);\n\n // Generate new free rects from the non-overlapping portions\n\n // Left portion\n if (placedRect.x > freeRect.x) {\n newRects.push({\n x: freeRect.x,\n y: freeRect.y,\n width: placedRect.x - freeRect.x,\n height: freeRect.height,\n });\n }\n\n // Right portion\n const placedRight = placedRect.x + placedRect.width;\n const freeRight = freeRect.x + freeRect.width;\n if (placedRight < freeRight) {\n newRects.push({\n x: placedRight,\n y: freeRect.y,\n width: freeRight - placedRight,\n height: freeRect.height,\n });\n }\n\n // Top portion\n if (placedRect.y > freeRect.y) {\n newRects.push({\n x: freeRect.x,\n y: freeRect.y,\n width: freeRect.width,\n height: placedRect.y - freeRect.y,\n });\n }\n\n // Bottom portion\n const placedBottom = placedRect.y + placedRect.height;\n const freeBottom = freeRect.y + freeRect.height;\n if (placedBottom < freeBottom) {\n newRects.push({\n x: freeRect.x,\n y: placedBottom,\n width: freeRect.width,\n height: freeBottom - placedBottom,\n });\n }\n }\n\n // Add all new rects\n freeRects.push(...newRects);\n}\n\n/**\n * Remove free rectangles that are fully contained within other free rectangles.\n */\nfunction pruneFreeRects(freeRects: Rect[]): void {\n for (let i = freeRects.length - 1; i >= 0; i--) {\n for (let j = 0; j < freeRects.length; j++) {\n if (i === j) continue;\n\n if (rectContains(freeRects[j], freeRects[i])) {\n freeRects.splice(i, 1);\n break;\n }\n }\n }\n}\n\n/**\n * Check if two rectangles overlap.\n */\nfunction rectsOverlap(a: Rect, b: Rect): boolean {\n return (\n a.x < b.x + b.width &&\n a.x + a.width > b.x &&\n a.y < b.y + b.height &&\n a.y + a.height > b.y\n );\n}\n\n/**\n * Check if rectangle `outer` fully contains rectangle `inner`.\n */\nfunction rectContains(outer: Rect, inner: Rect): boolean {\n return (\n inner.x >= outer.x &&\n inner.y >= outer.y &&\n inner.x + inner.width <= outer.x + outer.width &&\n inner.y + inner.height <= outer.y + outer.height\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/HermitageLayout.tsx","../src/maxrects.ts"],"sourcesContent":["import { useMemo, useRef, useState, useEffect, useCallback } from \"react\";\nimport { HermitageLayoutProps, LayoutItem } from \"./types\";\nimport { pack } from \"./maxrects\";\n\nexport function HermitageLayout({\n items,\n containerWidth: fixedWidth,\n gap = 8,\n sortStrategy = \"none\",\n className,\n}: HermitageLayoutProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const measureRef = useRef<HTMLDivElement>(null);\n const [measuredWidth, setMeasuredWidth] = useState<number>(0);\n const [measuredHeights, setMeasuredHeights] = useState<Map<string, number>>(\n new Map()\n );\n const [isMeasuring, setIsMeasuring] = useState(false);\n\n // Use fixed width if provided, otherwise use measured width\n const containerWidth = fixedWidth ?? measuredWidth;\n\n // Check which items need height measurement\n const itemsNeedingMeasure = useMemo(() => {\n return items.filter((item) => item.height === undefined);\n }, [items]);\n\n const needsMeasurement = itemsNeedingMeasure.length > 0;\n\n // Measure container width using ResizeObserver\n useEffect(() => {\n if (fixedWidth !== undefined) {\n return;\n }\n\n const element = containerRef.current;\n if (!element) return;\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const width = entry.contentRect.width;\n setMeasuredWidth(width);\n }\n });\n\n observer.observe(element);\n setMeasuredWidth(element.getBoundingClientRect().width);\n\n return () => observer.disconnect();\n }, [fixedWidth]);\n\n // Measure item heights when needed\n useEffect(() => {\n if (!needsMeasurement || containerWidth === 0) {\n setIsMeasuring(false);\n return;\n }\n\n setIsMeasuring(true);\n\n // Wait for next frame to ensure measurement elements are rendered\n requestAnimationFrame(() => {\n const measureContainer = measureRef.current;\n if (!measureContainer) {\n setIsMeasuring(false);\n return;\n }\n\n const newHeights = new Map<string, number>();\n\n for (const item of itemsNeedingMeasure) {\n const element = measureContainer.querySelector(\n `[data-measure-id=\"${item.id}\"]`\n );\n if (element) {\n newHeights.set(item.id, element.getBoundingClientRect().height);\n }\n }\n\n setMeasuredHeights(newHeights);\n setIsMeasuring(false);\n });\n }, [itemsNeedingMeasure, needsMeasurement, containerWidth]);\n\n // Build items with resolved heights\n const resolvedItems = useMemo(() => {\n return items.map((item) => ({\n ...item,\n height: item.height ?? measuredHeights.get(item.id) ?? 0,\n }));\n }, [items, measuredHeights]);\n\n // Check if we have all heights resolved\n const allHeightsResolved = resolvedItems.every((item) => item.height > 0);\n\n const { placements, totalHeight } = useMemo(() => {\n if (containerWidth === 0 || !allHeightsResolved) {\n return { placements: [], totalHeight: 0 };\n }\n return pack(resolvedItems, containerWidth, gap, sortStrategy);\n }, [resolvedItems, containerWidth, gap, sortStrategy, allHeightsResolved]);\n\n // Create a map for quick lookup of placements by id\n const placementMap = useMemo(() => {\n const map = new Map<string, { x: number; y: number; height: number }>();\n for (const p of placements) {\n map.set(p.id, { x: p.x, y: p.y, height: p.height });\n }\n return map;\n }, [placements]);\n\n // Sort items by visual position (y, then x) for proper tab order\n const sortedItems = useMemo(() => {\n return [...items].sort((a, b) => {\n const posA = placementMap.get(a.id);\n const posB = placementMap.get(b.id);\n if (!posA || !posB) return 0;\n if (posA.y !== posB.y) return posA.y - posB.y;\n return posA.x - posB.x;\n });\n }, [items, placementMap]);\n\n // Show layout only when all measurements are complete\n const showLayout = allHeightsResolved && containerWidth > 0;\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{\n position: \"relative\",\n width: fixedWidth ?? \"100%\",\n height: totalHeight || undefined,\n }}\n >\n {/* Hidden measurement container for items without explicit height */}\n {needsMeasurement && containerWidth > 0 && (\n <div\n ref={measureRef}\n aria-hidden=\"true\"\n style={{\n position: \"absolute\",\n visibility: \"hidden\",\n pointerEvents: \"none\",\n }}\n >\n {itemsNeedingMeasure.map((item) => (\n <div\n key={item.id}\n data-measure-id={item.id}\n style={{ width: item.width }}\n >\n {item.content}\n </div>\n ))}\n </div>\n )}\n\n {/* Main layout */}\n {showLayout &&\n sortedItems.map((item) => {\n const position = placementMap.get(item.id);\n if (!position) return null;\n\n return (\n <div\n key={item.id}\n style={{\n position: \"absolute\",\n left: position.x,\n top: position.y,\n width: item.width,\n height: position.height,\n }}\n >\n {item.content}\n </div>\n );\n })}\n </div>\n );\n}\n","import { Rect, PlacedItem, PackResult, SortStrategy } from \"./types\";\n\ninterface PackInput {\n id: string;\n width: number;\n height: number;\n}\n\n/**\n * Calculate a safe maximum height for the packing area.\n * Uses sum of all item heights as worst-case (vertical stack).\n */\nfunction calcMaxHeight(items: PackInput[], gap: number): number {\n const totalHeight = items.reduce((sum, item) => sum + item.height + gap, 0);\n return Math.max(totalHeight, 1000); // At least 1000px\n}\n\nfunction sortItems(items: PackInput[], strategy: SortStrategy): PackInput[] {\n if (strategy === \"none\" || strategy === \"ordered\") {\n // \"ordered\" uses a different placement approach, not pre-sorting\n return items;\n }\n\n const sorted = [...items];\n\n switch (strategy) {\n case \"height-desc\":\n sorted.sort((a, b) => b.height - a.height);\n break;\n }\n\n return sorted;\n}\n\n/**\n * MaxRects bin-packing algorithm.\n * Places rectangles in a container, minimizing wasted space.\n */\nexport function pack(\n items: PackInput[],\n containerWidth: number,\n gap: number = 0,\n sortStrategy: SortStrategy = \"none\"\n): PackResult {\n if (sortStrategy === \"ordered\") {\n return packOrdered(items, containerWidth, gap);\n }\n\n const sortedItems = sortItems(items, sortStrategy);\n\n // Start with one large free rectangle\n const maxHeight = calcMaxHeight(sortedItems, gap);\n const freeRects: Rect[] = [\n { x: 0, y: 0, width: containerWidth, height: maxHeight },\n ];\n\n const placements: PlacedItem[] = [];\n let maxY = 0;\n\n for (const item of sortedItems) {\n // Account for gap in item dimensions during placement\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const position = findBestPosition(paddedWidth, paddedHeight, freeRects);\n\n if (position) {\n const placement: PlacedItem = {\n id: item.id,\n x: position.x,\n y: position.y,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n // Track the maximum Y extent\n maxY = Math.max(maxY, position.y + item.height);\n\n // Split free rects around the placed item (using padded dimensions)\n const placedRect: Rect = {\n x: position.x,\n y: position.y,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n }\n }\n\n return {\n placements,\n totalHeight: maxY,\n };\n}\n\n/**\n * Ordered packing strategy:\n * - Row 1 (y=0): Strict input order, left-to-right, no gap filling\n * - Row 2: Next batch of items (in input order), can be reordered for better fit\n * - Row 3+: Full algorithmic freedom (height-desc sorting)\n */\nfunction packOrdered(\n items: PackInput[],\n containerWidth: number,\n gap: number\n): PackResult {\n // Start with full container as free space\n const maxHeight = calcMaxHeight(items, gap);\n const freeRects: Rect[] = [\n { x: 0, y: 0, width: containerWidth, height: maxHeight },\n ];\n\n const placements: PlacedItem[] = [];\n let maxY = 0;\n let itemIndex = 0;\n\n // --- Row 1: Strict left-to-right order at y=0 ---\n let row1NextX = 0;\n const row1Count = countRow1Items(items, containerWidth, gap);\n\n for (let i = 0; i < row1Count; i++) {\n const item = items[itemIndex];\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const placement: PlacedItem = {\n id: item.id,\n x: row1NextX,\n y: 0,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n maxY = Math.max(maxY, item.height);\n\n // Update freeRects to account for this placement\n const placedRect: Rect = {\n x: row1NextX,\n y: 0,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n\n row1NextX += paddedWidth;\n itemIndex++;\n }\n\n // Remaining items after row 1\n const remainingItems = items.slice(itemIndex);\n\n if (remainingItems.length === 0) {\n return { placements, totalHeight: maxY };\n }\n\n // --- Row 2: Next batch of items, reorderable for better fit ---\n // Take roughly the same count as row 1 (or remaining, whichever is smaller)\n const row2CandidateCount = Math.min(row1Count, remainingItems.length);\n const row2Candidates = remainingItems.slice(0, row2CandidateCount);\n const row3Items = remainingItems.slice(row2CandidateCount);\n\n // Sort row 2 candidates by height-desc for better gap filling\n const row2Sorted = [...row2Candidates].sort((a, b) => b.height - a.height);\n\n for (const item of row2Sorted) {\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const position = findBestPosition(paddedWidth, paddedHeight, freeRects);\n\n if (position) {\n const placement: PlacedItem = {\n id: item.id,\n x: position.x,\n y: position.y,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n maxY = Math.max(maxY, position.y + item.height);\n\n const placedRect: Rect = {\n x: position.x,\n y: position.y,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n }\n }\n\n // --- Row 3+: Full freedom, height-desc sorting ---\n const row3Sorted = [...row3Items].sort((a, b) => b.height - a.height);\n\n for (const item of row3Sorted) {\n const paddedWidth = item.width + gap;\n const paddedHeight = item.height + gap;\n\n const position = findBestPosition(paddedWidth, paddedHeight, freeRects);\n\n if (position) {\n const placement: PlacedItem = {\n id: item.id,\n x: position.x,\n y: position.y,\n width: item.width,\n height: item.height,\n };\n placements.push(placement);\n\n maxY = Math.max(maxY, position.y + item.height);\n\n const placedRect: Rect = {\n x: position.x,\n y: position.y,\n width: paddedWidth,\n height: paddedHeight,\n };\n splitFreeRects(freeRects, placedRect);\n pruneFreeRects(freeRects);\n }\n }\n\n return {\n placements,\n totalHeight: maxY,\n };\n}\n\n/**\n * Count how many items fit in row 1 (strict left-to-right at y=0).\n */\nfunction countRow1Items(\n items: PackInput[],\n containerWidth: number,\n gap: number\n): number {\n let x = 0;\n let count = 0;\n\n for (const item of items) {\n const paddedWidth = item.width + gap;\n if (x + paddedWidth > containerWidth + gap) {\n break;\n }\n x += paddedWidth;\n count++;\n }\n\n return count;\n}\n\n/**\n * Find a position for an item, but only accept positions at a specific y.\n */\nfunction findPositionAtY(\n width: number,\n height: number,\n freeRects: Rect[],\n targetY: number\n): { x: number; y: number } | null {\n let bestX = Infinity;\n let bestPosition: { x: number; y: number } | null = null;\n\n for (const rect of freeRects) {\n // Only consider rects that start at targetY\n if (rect.y !== targetY) continue;\n\n if (width <= rect.width && height <= rect.height) {\n const x = rect.x;\n if (x < bestX) {\n bestX = x;\n bestPosition = { x, y: targetY };\n }\n }\n }\n\n return bestPosition;\n}\n\n/**\n * Get the maximum height among a list of items.\n */\nfunction getMaxHeight(items: PackInput[]): number {\n return items.reduce((max, item) => Math.max(max, item.height), 0);\n}\n\n/**\n * Find the best position for an item using \"Best Y then Best X\" heuristic.\n * Prefers positions higher up (smaller Y), then leftward (smaller X).\n */\nfunction findBestPosition(\n width: number,\n height: number,\n freeRects: Rect[]\n): { x: number; y: number } | null {\n let bestY = Infinity;\n let bestX = Infinity;\n let bestPosition: { x: number; y: number } | null = null;\n\n for (const rect of freeRects) {\n // Check if item fits in this free rect\n if (width <= rect.width && height <= rect.height) {\n // Position at top-left corner of free rect\n const x = rect.x;\n const y = rect.y;\n\n // Prefer smaller Y, then smaller X\n if (y < bestY || (y === bestY && x < bestX)) {\n bestY = y;\n bestX = x;\n bestPosition = { x, y };\n }\n }\n }\n\n return bestPosition;\n}\n\n/**\n * Split free rectangles that overlap with the placed rectangle.\n * Generates up to 4 new rectangles around the placed item.\n */\nfunction splitFreeRects(freeRects: Rect[], placedRect: Rect): void {\n const newRects: Rect[] = [];\n\n for (let i = freeRects.length - 1; i >= 0; i--) {\n const freeRect = freeRects[i];\n\n // Check if this free rect overlaps with placed rect\n if (!rectsOverlap(freeRect, placedRect)) {\n continue;\n }\n\n // Remove the overlapping free rect\n freeRects.splice(i, 1);\n\n // Generate new free rects from the non-overlapping portions\n\n // Left portion\n if (placedRect.x > freeRect.x) {\n newRects.push({\n x: freeRect.x,\n y: freeRect.y,\n width: placedRect.x - freeRect.x,\n height: freeRect.height,\n });\n }\n\n // Right portion\n const placedRight = placedRect.x + placedRect.width;\n const freeRight = freeRect.x + freeRect.width;\n if (placedRight < freeRight) {\n newRects.push({\n x: placedRight,\n y: freeRect.y,\n width: freeRight - placedRight,\n height: freeRect.height,\n });\n }\n\n // Top portion\n if (placedRect.y > freeRect.y) {\n newRects.push({\n x: freeRect.x,\n y: freeRect.y,\n width: freeRect.width,\n height: placedRect.y - freeRect.y,\n });\n }\n\n // Bottom portion\n const placedBottom = placedRect.y + placedRect.height;\n const freeBottom = freeRect.y + freeRect.height;\n if (placedBottom < freeBottom) {\n newRects.push({\n x: freeRect.x,\n y: placedBottom,\n width: freeRect.width,\n height: freeBottom - placedBottom,\n });\n }\n }\n\n // Add all new rects\n freeRects.push(...newRects);\n}\n\n/**\n * Remove free rectangles that are fully contained within other free rectangles.\n */\nfunction pruneFreeRects(freeRects: Rect[]): void {\n for (let i = freeRects.length - 1; i >= 0; i--) {\n for (let j = 0; j < freeRects.length; j++) {\n if (i === j) continue;\n\n if (rectContains(freeRects[j], freeRects[i])) {\n freeRects.splice(i, 1);\n break;\n }\n }\n }\n}\n\n/**\n * Check if two rectangles overlap.\n */\nfunction rectsOverlap(a: Rect, b: Rect): boolean {\n return (\n a.x < b.x + b.width &&\n a.x + a.width > b.x &&\n a.y < b.y + b.height &&\n a.y + a.height > b.y\n );\n}\n\n/**\n * Check if rectangle `outer` fully contains rectangle `inner`.\n */\nfunction rectContains(outer: Rect, inner: Rect): boolean {\n return (\n inner.x >= outer.x &&\n inner.y >= outer.y &&\n inner.x + inner.width <= outer.x + outer.width &&\n inner.y + inner.height <= outer.y + outer.height\n );\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED