petersburg 0.0.2 → 0.0.3
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/dist/{index.mjs → index.cjs} +49 -26
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +21 -52
- package/dist/index.js.map +1 -1
- package/package.json +17 -6
- package/dist/index.mjs.map +0 -1
- /package/dist/{index.d.mts → index.d.cts} +0 -0
|
@@ -1,5 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
HermitageLayout: () => HermitageLayout
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
1
27
|
// src/HermitageLayout.tsx
|
|
2
|
-
|
|
28
|
+
var import_react = require("react");
|
|
3
29
|
|
|
4
30
|
// src/maxrects.ts
|
|
5
31
|
function calcMaxHeight(items, gap) {
|
|
@@ -250,7 +276,7 @@ function rectContains(outer, inner) {
|
|
|
250
276
|
}
|
|
251
277
|
|
|
252
278
|
// src/HermitageLayout.tsx
|
|
253
|
-
|
|
279
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
254
280
|
function HermitageLayout({
|
|
255
281
|
items,
|
|
256
282
|
containerWidth: fixedWidth,
|
|
@@ -258,19 +284,18 @@ function HermitageLayout({
|
|
|
258
284
|
sortStrategy = "none",
|
|
259
285
|
className
|
|
260
286
|
}) {
|
|
261
|
-
const containerRef = useRef(null);
|
|
262
|
-
const measureRef = useRef(null);
|
|
263
|
-
const [measuredWidth, setMeasuredWidth] = useState(0);
|
|
264
|
-
const [measuredHeights, setMeasuredHeights] = useState(
|
|
287
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
288
|
+
const measureRef = (0, import_react.useRef)(null);
|
|
289
|
+
const [measuredWidth, setMeasuredWidth] = (0, import_react.useState)(0);
|
|
290
|
+
const [measuredHeights, setMeasuredHeights] = (0, import_react.useState)(
|
|
265
291
|
/* @__PURE__ */ new Map()
|
|
266
292
|
);
|
|
267
|
-
const [isMeasuring, setIsMeasuring] = useState(false);
|
|
268
293
|
const containerWidth = fixedWidth ?? measuredWidth;
|
|
269
|
-
const itemsNeedingMeasure = useMemo(() => {
|
|
294
|
+
const itemsNeedingMeasure = (0, import_react.useMemo)(() => {
|
|
270
295
|
return items.filter((item) => item.height === void 0);
|
|
271
296
|
}, [items]);
|
|
272
297
|
const needsMeasurement = itemsNeedingMeasure.length > 0;
|
|
273
|
-
useEffect(() => {
|
|
298
|
+
(0, import_react.useEffect)(() => {
|
|
274
299
|
if (fixedWidth !== void 0) {
|
|
275
300
|
return;
|
|
276
301
|
}
|
|
@@ -286,16 +311,13 @@ function HermitageLayout({
|
|
|
286
311
|
setMeasuredWidth(element.getBoundingClientRect().width);
|
|
287
312
|
return () => observer.disconnect();
|
|
288
313
|
}, [fixedWidth]);
|
|
289
|
-
useEffect(() => {
|
|
314
|
+
(0, import_react.useEffect)(() => {
|
|
290
315
|
if (!needsMeasurement || containerWidth === 0) {
|
|
291
|
-
setIsMeasuring(false);
|
|
292
316
|
return;
|
|
293
317
|
}
|
|
294
|
-
setIsMeasuring(true);
|
|
295
318
|
requestAnimationFrame(() => {
|
|
296
319
|
const measureContainer = measureRef.current;
|
|
297
320
|
if (!measureContainer) {
|
|
298
|
-
setIsMeasuring(false);
|
|
299
321
|
return;
|
|
300
322
|
}
|
|
301
323
|
const newHeights = /* @__PURE__ */ new Map();
|
|
@@ -308,30 +330,29 @@ function HermitageLayout({
|
|
|
308
330
|
}
|
|
309
331
|
}
|
|
310
332
|
setMeasuredHeights(newHeights);
|
|
311
|
-
setIsMeasuring(false);
|
|
312
333
|
});
|
|
313
334
|
}, [itemsNeedingMeasure, needsMeasurement, containerWidth]);
|
|
314
|
-
const resolvedItems = useMemo(() => {
|
|
335
|
+
const resolvedItems = (0, import_react.useMemo)(() => {
|
|
315
336
|
return items.map((item) => ({
|
|
316
337
|
...item,
|
|
317
338
|
height: item.height ?? measuredHeights.get(item.id) ?? 0
|
|
318
339
|
}));
|
|
319
340
|
}, [items, measuredHeights]);
|
|
320
341
|
const allHeightsResolved = resolvedItems.every((item) => item.height > 0);
|
|
321
|
-
const { placements, totalHeight } = useMemo(() => {
|
|
342
|
+
const { placements, totalHeight } = (0, import_react.useMemo)(() => {
|
|
322
343
|
if (containerWidth === 0 || !allHeightsResolved) {
|
|
323
344
|
return { placements: [], totalHeight: 0 };
|
|
324
345
|
}
|
|
325
346
|
return pack(resolvedItems, containerWidth, gap, sortStrategy);
|
|
326
347
|
}, [resolvedItems, containerWidth, gap, sortStrategy, allHeightsResolved]);
|
|
327
|
-
const placementMap = useMemo(() => {
|
|
348
|
+
const placementMap = (0, import_react.useMemo)(() => {
|
|
328
349
|
const map = /* @__PURE__ */ new Map();
|
|
329
350
|
for (const p of placements) {
|
|
330
351
|
map.set(p.id, { x: p.x, y: p.y, height: p.height });
|
|
331
352
|
}
|
|
332
353
|
return map;
|
|
333
354
|
}, [placements]);
|
|
334
|
-
const sortedItems = useMemo(() => {
|
|
355
|
+
const sortedItems = (0, import_react.useMemo)(() => {
|
|
335
356
|
return [...items].sort((a, b) => {
|
|
336
357
|
const posA = placementMap.get(a.id);
|
|
337
358
|
const posB = placementMap.get(b.id);
|
|
@@ -341,7 +362,7 @@ function HermitageLayout({
|
|
|
341
362
|
});
|
|
342
363
|
}, [items, placementMap]);
|
|
343
364
|
const showLayout = allHeightsResolved && containerWidth > 0;
|
|
344
|
-
return /* @__PURE__ */ jsxs(
|
|
365
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
345
366
|
"div",
|
|
346
367
|
{
|
|
347
368
|
ref: containerRef,
|
|
@@ -349,10 +370,11 @@ function HermitageLayout({
|
|
|
349
370
|
style: {
|
|
350
371
|
position: "relative",
|
|
351
372
|
width: fixedWidth ?? "100%",
|
|
352
|
-
height: totalHeight || void 0
|
|
373
|
+
height: totalHeight || void 0,
|
|
374
|
+
overflow: "hidden"
|
|
353
375
|
},
|
|
354
376
|
children: [
|
|
355
|
-
needsMeasurement && containerWidth > 0 && /* @__PURE__ */ jsx(
|
|
377
|
+
needsMeasurement && containerWidth > 0 && !allHeightsResolved && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
356
378
|
"div",
|
|
357
379
|
{
|
|
358
380
|
ref: measureRef,
|
|
@@ -362,7 +384,7 @@ function HermitageLayout({
|
|
|
362
384
|
visibility: "hidden",
|
|
363
385
|
pointerEvents: "none"
|
|
364
386
|
},
|
|
365
|
-
children: itemsNeedingMeasure.map((item) => /* @__PURE__ */ jsx(
|
|
387
|
+
children: itemsNeedingMeasure.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
366
388
|
"div",
|
|
367
389
|
{
|
|
368
390
|
"data-measure-id": item.id,
|
|
@@ -376,7 +398,7 @@ function HermitageLayout({
|
|
|
376
398
|
showLayout && sortedItems.map((item) => {
|
|
377
399
|
const position = placementMap.get(item.id);
|
|
378
400
|
if (!position) return null;
|
|
379
|
-
return /* @__PURE__ */ jsx(
|
|
401
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
380
402
|
"div",
|
|
381
403
|
{
|
|
382
404
|
style: {
|
|
@@ -395,7 +417,8 @@ function HermitageLayout({
|
|
|
395
417
|
}
|
|
396
418
|
);
|
|
397
419
|
}
|
|
398
|
-
export
|
|
420
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
421
|
+
0 && (module.exports = {
|
|
399
422
|
HermitageLayout
|
|
400
|
-
};
|
|
401
|
-
//# sourceMappingURL=index.
|
|
423
|
+
});
|
|
424
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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 measureRef = useRef<HTMLDivElement>(null);\n const [measuredWidth, setMeasuredWidth] = useState<number>(0);\n const [measuredHeights, setMeasuredHeights] = useState<Map<string, number>>(\n new Map()\n );\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 return;\n }\n\n // Wait for next frame to ensure measurement elements are rendered\n requestAnimationFrame(() => {\n const measureContainer = measureRef.current;\n if (!measureContainer) {\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 });\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 overflow: \"hidden\",\n }}\n >\n {/* Hidden measurement container for items without explicit height */}\n {needsMeasurement && containerWidth > 0 && !allHeightsResolved && (\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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAqD;;;ACYrD,SAAS,cAAc,OAAoB,KAAqB;AAC9D,QAAM,cAAc,MAAM,OAAO,CAAC,KAAK,SAAS,MAAM,KAAK,SAAS,KAAK,CAAC;AAC1E,SAAO,KAAK,IAAI,aAAa,GAAI;AACnC;AAEA,SAAS,UAAU,OAAoB,UAAqC;AAC1E,MAAI,aAAa,UAAU,aAAa,WAAW;AAEjD,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,CAAC,GAAG,KAAK;AAExB,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,aAAO,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AACzC;AAAA,EACJ;AAEA,SAAO;AACT;AAMO,SAAS,KACd,OACA,gBACA,MAAc,GACd,eAA6B,QACjB;AACZ,MAAI,iBAAiB,WAAW;AAC9B,WAAO,YAAY,OAAO,gBAAgB,GAAG;AAAA,EAC/C;AAEA,QAAM,cAAc,UAAU,OAAO,YAAY;AAGjD,QAAM,YAAY,cAAc,aAAa,GAAG;AAChD,QAAM,YAAoB;AAAA,IACxB,EAAE,GAAG,GAAG,GAAG,GAAG,OAAO,gBAAgB,QAAQ,UAAU;AAAA,EACzD;AAEA,QAAM,aAA2B,CAAC;AAClC,MAAI,OAAO;AAEX,aAAW,QAAQ,aAAa;AAE9B,UAAM,cAAc,KAAK,QAAQ;AACjC,UAAM,eAAe,KAAK,SAAS;AAEnC,UAAM,WAAW,iBAAiB,aAAa,cAAc,SAAS;AAEtE,QAAI,UAAU;AACZ,YAAM,YAAwB;AAAA,QAC5B,IAAI,KAAK;AAAA,QACT,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,MACf;AACA,iBAAW,KAAK,SAAS;AAGzB,aAAO,KAAK,IAAI,MAAM,SAAS,IAAI,KAAK,MAAM;AAG9C,YAAM,aAAmB;AAAA,QACvB,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO;AAAA,QACP,QAAQ;AAAA,MACV;AACA,qBAAe,WAAW,UAAU;AACpC,qBAAe,SAAS;AAAA,IAC1B;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,aAAa;AAAA,EACf;AACF;AAQA,SAAS,YACP,OACA,gBACA,KACY;AAEZ,QAAM,YAAY,cAAc,OAAO,GAAG;AAC1C,QAAM,YAAoB;AAAA,IACxB,EAAE,GAAG,GAAG,GAAG,GAAG,OAAO,gBAAgB,QAAQ,UAAU;AAAA,EACzD;AAEA,QAAM,aAA2B,CAAC;AAClC,MAAI,OAAO;AACX,MAAI,YAAY;AAGhB,MAAI,YAAY;AAChB,QAAM,YAAY,eAAe,OAAO,gBAAgB,GAAG;AAE3D,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,cAAc,KAAK,QAAQ;AACjC,UAAM,eAAe,KAAK,SAAS;AAEnC,UAAM,YAAwB;AAAA,MAC5B,IAAI,KAAK;AAAA,MACT,GAAG;AAAA,MACH,GAAG;AAAA,MACH,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,IACf;AACA,eAAW,KAAK,SAAS;AAEzB,WAAO,KAAK,IAAI,MAAM,KAAK,MAAM;AAGjC,UAAM,aAAmB;AAAA,MACvB,GAAG;AAAA,MACH,GAAG;AAAA,MACH,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AACA,mBAAe,WAAW,UAAU;AACpC,mBAAe,SAAS;AAExB,iBAAa;AACb;AAAA,EACF;AAGA,QAAM,iBAAiB,MAAM,MAAM,SAAS;AAE5C,MAAI,eAAe,WAAW,GAAG;AAC/B,WAAO,EAAE,YAAY,aAAa,KAAK;AAAA,EACzC;AAIA,QAAM,qBAAqB,KAAK,IAAI,WAAW,eAAe,MAAM;AACpE,QAAM,iBAAiB,eAAe,MAAM,GAAG,kBAAkB;AACjE,QAAM,YAAY,eAAe,MAAM,kBAAkB;AAGzD,QAAM,aAAa,CAAC,GAAG,cAAc,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AAEzE,aAAW,QAAQ,YAAY;AAC7B,UAAM,cAAc,KAAK,QAAQ;AACjC,UAAM,eAAe,KAAK,SAAS;AAEnC,UAAM,WAAW,iBAAiB,aAAa,cAAc,SAAS;AAEtE,QAAI,UAAU;AACZ,YAAM,YAAwB;AAAA,QAC5B,IAAI,KAAK;AAAA,QACT,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,MACf;AACA,iBAAW,KAAK,SAAS;AAEzB,aAAO,KAAK,IAAI,MAAM,SAAS,IAAI,KAAK,MAAM;AAE9C,YAAM,aAAmB;AAAA,QACvB,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO;AAAA,QACP,QAAQ;AAAA,MACV;AACA,qBAAe,WAAW,UAAU;AACpC,qBAAe,SAAS;AAAA,IAC1B;AAAA,EACF;AAGA,QAAM,aAAa,CAAC,GAAG,SAAS,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AAEpE,aAAW,QAAQ,YAAY;AAC7B,UAAM,cAAc,KAAK,QAAQ;AACjC,UAAM,eAAe,KAAK,SAAS;AAEnC,UAAM,WAAW,iBAAiB,aAAa,cAAc,SAAS;AAEtE,QAAI,UAAU;AACZ,YAAM,YAAwB;AAAA,QAC5B,IAAI,KAAK;AAAA,QACT,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,MACf;AACA,iBAAW,KAAK,SAAS;AAEzB,aAAO,KAAK,IAAI,MAAM,SAAS,IAAI,KAAK,MAAM;AAE9C,YAAM,aAAmB;AAAA,QACvB,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO;AAAA,QACP,QAAQ;AAAA,MACV;AACA,qBAAe,WAAW,UAAU;AACpC,qBAAe,SAAS;AAAA,IAC1B;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,aAAa;AAAA,EACf;AACF;AAKA,SAAS,eACP,OACA,gBACA,KACQ;AACR,MAAI,IAAI;AACR,MAAI,QAAQ;AAEZ,aAAW,QAAQ,OAAO;AACxB,UAAM,cAAc,KAAK,QAAQ;AACjC,QAAI,IAAI,cAAc,iBAAiB,KAAK;AAC1C;AAAA,IACF;AACA,SAAK;AACL;AAAA,EACF;AAEA,SAAO;AACT;AAyCA,SAAS,iBACP,OACA,QACA,WACiC;AACjC,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,MAAI,eAAgD;AAEpD,aAAW,QAAQ,WAAW;AAE5B,QAAI,SAAS,KAAK,SAAS,UAAU,KAAK,QAAQ;AAEhD,YAAM,IAAI,KAAK;AACf,YAAM,IAAI,KAAK;AAGf,UAAI,IAAI,SAAU,MAAM,SAAS,IAAI,OAAQ;AAC3C,gBAAQ;AACR,gBAAQ;AACR,uBAAe,EAAE,GAAG,EAAE;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAMA,SAAS,eAAe,WAAmB,YAAwB;AACjE,QAAM,WAAmB,CAAC;AAE1B,WAAS,IAAI,UAAU,SAAS,GAAG,KAAK,GAAG,KAAK;AAC9C,UAAM,WAAW,UAAU,CAAC;AAG5B,QAAI,CAAC,aAAa,UAAU,UAAU,GAAG;AACvC;AAAA,IACF;AAGA,cAAU,OAAO,GAAG,CAAC;AAKrB,QAAI,WAAW,IAAI,SAAS,GAAG;AAC7B,eAAS,KAAK;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO,WAAW,IAAI,SAAS;AAAA,QAC/B,QAAQ,SAAS;AAAA,MACnB,CAAC;AAAA,IACH;AAGA,UAAM,cAAc,WAAW,IAAI,WAAW;AAC9C,UAAM,YAAY,SAAS,IAAI,SAAS;AACxC,QAAI,cAAc,WAAW;AAC3B,eAAS,KAAK;AAAA,QACZ,GAAG;AAAA,QACH,GAAG,SAAS;AAAA,QACZ,OAAO,YAAY;AAAA,QACnB,QAAQ,SAAS;AAAA,MACnB,CAAC;AAAA,IACH;AAGA,QAAI,WAAW,IAAI,SAAS,GAAG;AAC7B,eAAS,KAAK;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO,SAAS;AAAA,QAChB,QAAQ,WAAW,IAAI,SAAS;AAAA,MAClC,CAAC;AAAA,IACH;AAGA,UAAM,eAAe,WAAW,IAAI,WAAW;AAC/C,UAAM,aAAa,SAAS,IAAI,SAAS;AACzC,QAAI,eAAe,YAAY;AAC7B,eAAS,KAAK;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,GAAG;AAAA,QACH,OAAO,SAAS;AAAA,QAChB,QAAQ,aAAa;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF;AAGA,YAAU,KAAK,GAAG,QAAQ;AAC5B;AAKA,SAAS,eAAe,WAAyB;AAC/C,WAAS,IAAI,UAAU,SAAS,GAAG,KAAK,GAAG,KAAK;AAC9C,aAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,UAAI,MAAM,EAAG;AAEb,UAAI,aAAa,UAAU,CAAC,GAAG,UAAU,CAAC,CAAC,GAAG;AAC5C,kBAAU,OAAO,GAAG,CAAC;AACrB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAKA,SAAS,aAAa,GAAS,GAAkB;AAC/C,SACE,EAAE,IAAI,EAAE,IAAI,EAAE,SACd,EAAE,IAAI,EAAE,QAAQ,EAAE,KAClB,EAAE,IAAI,EAAE,IAAI,EAAE,UACd,EAAE,IAAI,EAAE,SAAS,EAAE;AAEvB;AAKA,SAAS,aAAa,OAAa,OAAsB;AACvD,SACE,MAAM,KAAK,MAAM,KACjB,MAAM,KAAK,MAAM,KACjB,MAAM,IAAI,MAAM,SAAS,MAAM,IAAI,MAAM,SACzC,MAAM,IAAI,MAAM,UAAU,MAAM,IAAI,MAAM;AAE9C;;;ADxTI;AApHG,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,gBAAgB;AAAA,EAChB,MAAM;AAAA,EACN,eAAe;AAAA,EACf;AACF,GAAyB;AACvB,QAAM,mBAAe,qBAAuB,IAAI;AAChD,QAAM,iBAAa,qBAAuB,IAAI;AAC9C,QAAM,CAAC,eAAe,gBAAgB,QAAI,uBAAiB,CAAC;AAC5D,QAAM,CAAC,iBAAiB,kBAAkB,QAAI;AAAA,IAC5C,oBAAI,IAAI;AAAA,EACV;AAGA,QAAM,iBAAiB,cAAc;AAGrC,QAAM,0BAAsB,sBAAQ,MAAM;AACxC,WAAO,MAAM,OAAO,CAAC,SAAS,KAAK,WAAW,MAAS;AAAA,EACzD,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,mBAAmB,oBAAoB,SAAS;AAGtD,8BAAU,MAAM;AACd,QAAI,eAAe,QAAW;AAC5B;AAAA,IACF;AAEA,UAAM,UAAU,aAAa;AAC7B,QAAI,CAAC,QAAS;AAEd,UAAM,WAAW,IAAI,eAAe,CAAC,YAAY;AAC/C,iBAAW,SAAS,SAAS;AAC3B,cAAM,QAAQ,MAAM,YAAY;AAChC,yBAAiB,KAAK;AAAA,MACxB;AAAA,IACF,CAAC;AAED,aAAS,QAAQ,OAAO;AACxB,qBAAiB,QAAQ,sBAAsB,EAAE,KAAK;AAEtD,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,UAAU,CAAC;AAGf,8BAAU,MAAM;AACd,QAAI,CAAC,oBAAoB,mBAAmB,GAAG;AAC7C;AAAA,IACF;AAGA,0BAAsB,MAAM;AAC1B,YAAM,mBAAmB,WAAW;AACpC,UAAI,CAAC,kBAAkB;AACrB;AAAA,MACF;AAEA,YAAM,aAAa,oBAAI,IAAoB;AAE3C,iBAAW,QAAQ,qBAAqB;AACtC,cAAM,UAAU,iBAAiB;AAAA,UAC/B,qBAAqB,KAAK,EAAE;AAAA,QAC9B;AACA,YAAI,SAAS;AACX,qBAAW,IAAI,KAAK,IAAI,QAAQ,sBAAsB,EAAE,MAAM;AAAA,QAChE;AAAA,MACF;AAEA,yBAAmB,UAAU;AAAA,IAC/B,CAAC;AAAA,EACH,GAAG,CAAC,qBAAqB,kBAAkB,cAAc,CAAC;AAG1D,QAAM,oBAAgB,sBAAQ,MAAM;AAClC,WAAO,MAAM,IAAI,CAAC,UAAU;AAAA,MAC1B,GAAG;AAAA,MACH,QAAQ,KAAK,UAAU,gBAAgB,IAAI,KAAK,EAAE,KAAK;AAAA,IACzD,EAAE;AAAA,EACJ,GAAG,CAAC,OAAO,eAAe,CAAC;AAG3B,QAAM,qBAAqB,cAAc,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC;AAExE,QAAM,EAAE,YAAY,YAAY,QAAI,sBAAQ,MAAM;AAChD,QAAI,mBAAmB,KAAK,CAAC,oBAAoB;AAC/C,aAAO,EAAE,YAAY,CAAC,GAAG,aAAa,EAAE;AAAA,IAC1C;AACA,WAAO,KAAK,eAAe,gBAAgB,KAAK,YAAY;AAAA,EAC9D,GAAG,CAAC,eAAe,gBAAgB,KAAK,cAAc,kBAAkB,CAAC;AAGzE,QAAM,mBAAe,sBAAQ,MAAM;AACjC,UAAM,MAAM,oBAAI,IAAsD;AACtE,eAAW,KAAK,YAAY;AAC1B,UAAI,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,QAAQ,EAAE,OAAO,CAAC;AAAA,IACpD;AACA,WAAO;AAAA,EACT,GAAG,CAAC,UAAU,CAAC;AAGf,QAAM,kBAAc,sBAAQ,MAAM;AAChC,WAAO,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM;AAC/B,YAAM,OAAO,aAAa,IAAI,EAAE,EAAE;AAClC,YAAM,OAAO,aAAa,IAAI,EAAE,EAAE;AAClC,UAAI,CAAC,QAAQ,CAAC,KAAM,QAAO;AAC3B,UAAI,KAAK,MAAM,KAAK,EAAG,QAAO,KAAK,IAAI,KAAK;AAC5C,aAAO,KAAK,IAAI,KAAK;AAAA,IACvB,CAAC;AAAA,EACH,GAAG,CAAC,OAAO,YAAY,CAAC;AAGxB,QAAM,aAAa,sBAAsB,iBAAiB;AAE1D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL;AAAA,MACA,OAAO;AAAA,QACL,UAAU;AAAA,QACV,OAAO,cAAc;AAAA,QACrB,QAAQ,eAAe;AAAA,QACvB,UAAU;AAAA,MACZ;AAAA,MAGC;AAAA,4BAAoB,iBAAiB,KAAK,CAAC,sBAC1C;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,eAAY;AAAA,YACZ,OAAO;AAAA,cACL,UAAU;AAAA,cACV,YAAY;AAAA,cACZ,eAAe;AAAA,YACjB;AAAA,YAEC,8BAAoB,IAAI,CAAC,SACxB;AAAA,cAAC;AAAA;AAAA,gBAEC,mBAAiB,KAAK;AAAA,gBACtB,OAAO,EAAE,OAAO,KAAK,MAAM;AAAA,gBAE1B,eAAK;AAAA;AAAA,cAJD,KAAK;AAAA,YAKZ,CACD;AAAA;AAAA,QACH;AAAA,QAID,cACC,YAAY,IAAI,CAAC,SAAS;AACxB,gBAAM,WAAW,aAAa,IAAI,KAAK,EAAE;AACzC,cAAI,CAAC,SAAU,QAAO;AAEtB,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC,OAAO;AAAA,gBACL,UAAU;AAAA,gBACV,MAAM,SAAS;AAAA,gBACf,KAAK,SAAS;AAAA,gBACd,OAAO,KAAK;AAAA,gBACZ,QAAQ,SAAS;AAAA,cACnB;AAAA,cAEC,eAAK;AAAA;AAAA,YATD,KAAK;AAAA,UAUZ;AAAA,QAEJ,CAAC;AAAA;AAAA;AAAA,EACL;AAEJ;","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,31 +1,5 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
HermitageLayout: () => HermitageLayout
|
|
24
|
-
});
|
|
25
|
-
module.exports = __toCommonJS(index_exports);
|
|
26
|
-
|
|
27
1
|
// src/HermitageLayout.tsx
|
|
28
|
-
|
|
2
|
+
import { useMemo, useRef, useState, useEffect } from "react";
|
|
29
3
|
|
|
30
4
|
// src/maxrects.ts
|
|
31
5
|
function calcMaxHeight(items, gap) {
|
|
@@ -276,7 +250,7 @@ function rectContains(outer, inner) {
|
|
|
276
250
|
}
|
|
277
251
|
|
|
278
252
|
// src/HermitageLayout.tsx
|
|
279
|
-
|
|
253
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
280
254
|
function HermitageLayout({
|
|
281
255
|
items,
|
|
282
256
|
containerWidth: fixedWidth,
|
|
@@ -284,19 +258,18 @@ function HermitageLayout({
|
|
|
284
258
|
sortStrategy = "none",
|
|
285
259
|
className
|
|
286
260
|
}) {
|
|
287
|
-
const containerRef =
|
|
288
|
-
const measureRef =
|
|
289
|
-
const [measuredWidth, setMeasuredWidth] =
|
|
290
|
-
const [measuredHeights, setMeasuredHeights] =
|
|
261
|
+
const containerRef = useRef(null);
|
|
262
|
+
const measureRef = useRef(null);
|
|
263
|
+
const [measuredWidth, setMeasuredWidth] = useState(0);
|
|
264
|
+
const [measuredHeights, setMeasuredHeights] = useState(
|
|
291
265
|
/* @__PURE__ */ new Map()
|
|
292
266
|
);
|
|
293
|
-
const [isMeasuring, setIsMeasuring] = (0, import_react.useState)(false);
|
|
294
267
|
const containerWidth = fixedWidth ?? measuredWidth;
|
|
295
|
-
const itemsNeedingMeasure =
|
|
268
|
+
const itemsNeedingMeasure = useMemo(() => {
|
|
296
269
|
return items.filter((item) => item.height === void 0);
|
|
297
270
|
}, [items]);
|
|
298
271
|
const needsMeasurement = itemsNeedingMeasure.length > 0;
|
|
299
|
-
|
|
272
|
+
useEffect(() => {
|
|
300
273
|
if (fixedWidth !== void 0) {
|
|
301
274
|
return;
|
|
302
275
|
}
|
|
@@ -312,16 +285,13 @@ function HermitageLayout({
|
|
|
312
285
|
setMeasuredWidth(element.getBoundingClientRect().width);
|
|
313
286
|
return () => observer.disconnect();
|
|
314
287
|
}, [fixedWidth]);
|
|
315
|
-
|
|
288
|
+
useEffect(() => {
|
|
316
289
|
if (!needsMeasurement || containerWidth === 0) {
|
|
317
|
-
setIsMeasuring(false);
|
|
318
290
|
return;
|
|
319
291
|
}
|
|
320
|
-
setIsMeasuring(true);
|
|
321
292
|
requestAnimationFrame(() => {
|
|
322
293
|
const measureContainer = measureRef.current;
|
|
323
294
|
if (!measureContainer) {
|
|
324
|
-
setIsMeasuring(false);
|
|
325
295
|
return;
|
|
326
296
|
}
|
|
327
297
|
const newHeights = /* @__PURE__ */ new Map();
|
|
@@ -334,30 +304,29 @@ function HermitageLayout({
|
|
|
334
304
|
}
|
|
335
305
|
}
|
|
336
306
|
setMeasuredHeights(newHeights);
|
|
337
|
-
setIsMeasuring(false);
|
|
338
307
|
});
|
|
339
308
|
}, [itemsNeedingMeasure, needsMeasurement, containerWidth]);
|
|
340
|
-
const resolvedItems =
|
|
309
|
+
const resolvedItems = useMemo(() => {
|
|
341
310
|
return items.map((item) => ({
|
|
342
311
|
...item,
|
|
343
312
|
height: item.height ?? measuredHeights.get(item.id) ?? 0
|
|
344
313
|
}));
|
|
345
314
|
}, [items, measuredHeights]);
|
|
346
315
|
const allHeightsResolved = resolvedItems.every((item) => item.height > 0);
|
|
347
|
-
const { placements, totalHeight } =
|
|
316
|
+
const { placements, totalHeight } = useMemo(() => {
|
|
348
317
|
if (containerWidth === 0 || !allHeightsResolved) {
|
|
349
318
|
return { placements: [], totalHeight: 0 };
|
|
350
319
|
}
|
|
351
320
|
return pack(resolvedItems, containerWidth, gap, sortStrategy);
|
|
352
321
|
}, [resolvedItems, containerWidth, gap, sortStrategy, allHeightsResolved]);
|
|
353
|
-
const placementMap =
|
|
322
|
+
const placementMap = useMemo(() => {
|
|
354
323
|
const map = /* @__PURE__ */ new Map();
|
|
355
324
|
for (const p of placements) {
|
|
356
325
|
map.set(p.id, { x: p.x, y: p.y, height: p.height });
|
|
357
326
|
}
|
|
358
327
|
return map;
|
|
359
328
|
}, [placements]);
|
|
360
|
-
const sortedItems =
|
|
329
|
+
const sortedItems = useMemo(() => {
|
|
361
330
|
return [...items].sort((a, b) => {
|
|
362
331
|
const posA = placementMap.get(a.id);
|
|
363
332
|
const posB = placementMap.get(b.id);
|
|
@@ -367,7 +336,7 @@ function HermitageLayout({
|
|
|
367
336
|
});
|
|
368
337
|
}, [items, placementMap]);
|
|
369
338
|
const showLayout = allHeightsResolved && containerWidth > 0;
|
|
370
|
-
return /* @__PURE__ */
|
|
339
|
+
return /* @__PURE__ */ jsxs(
|
|
371
340
|
"div",
|
|
372
341
|
{
|
|
373
342
|
ref: containerRef,
|
|
@@ -375,10 +344,11 @@ function HermitageLayout({
|
|
|
375
344
|
style: {
|
|
376
345
|
position: "relative",
|
|
377
346
|
width: fixedWidth ?? "100%",
|
|
378
|
-
height: totalHeight || void 0
|
|
347
|
+
height: totalHeight || void 0,
|
|
348
|
+
overflow: "hidden"
|
|
379
349
|
},
|
|
380
350
|
children: [
|
|
381
|
-
needsMeasurement && containerWidth > 0 && /* @__PURE__ */
|
|
351
|
+
needsMeasurement && containerWidth > 0 && !allHeightsResolved && /* @__PURE__ */ jsx(
|
|
382
352
|
"div",
|
|
383
353
|
{
|
|
384
354
|
ref: measureRef,
|
|
@@ -388,7 +358,7 @@ function HermitageLayout({
|
|
|
388
358
|
visibility: "hidden",
|
|
389
359
|
pointerEvents: "none"
|
|
390
360
|
},
|
|
391
|
-
children: itemsNeedingMeasure.map((item) => /* @__PURE__ */
|
|
361
|
+
children: itemsNeedingMeasure.map((item) => /* @__PURE__ */ jsx(
|
|
392
362
|
"div",
|
|
393
363
|
{
|
|
394
364
|
"data-measure-id": item.id,
|
|
@@ -402,7 +372,7 @@ function HermitageLayout({
|
|
|
402
372
|
showLayout && sortedItems.map((item) => {
|
|
403
373
|
const position = placementMap.get(item.id);
|
|
404
374
|
if (!position) return null;
|
|
405
|
-
return /* @__PURE__ */
|
|
375
|
+
return /* @__PURE__ */ jsx(
|
|
406
376
|
"div",
|
|
407
377
|
{
|
|
408
378
|
style: {
|
|
@@ -421,8 +391,7 @@ function HermitageLayout({
|
|
|
421
391
|
}
|
|
422
392
|
);
|
|
423
393
|
}
|
|
424
|
-
|
|
425
|
-
0 && (module.exports = {
|
|
394
|
+
export {
|
|
426
395
|
HermitageLayout
|
|
427
|
-
}
|
|
396
|
+
};
|
|
428
397
|
//# sourceMappingURL=index.js.map
|
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, 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":[]}
|
|
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 measureRef = useRef<HTMLDivElement>(null);\n const [measuredWidth, setMeasuredWidth] = useState<number>(0);\n const [measuredHeights, setMeasuredHeights] = useState<Map<string, number>>(\n new Map()\n );\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 return;\n }\n\n // Wait for next frame to ensure measurement elements are rendered\n requestAnimationFrame(() => {\n const measureContainer = measureRef.current;\n if (!measureContainer) {\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 });\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 overflow: \"hidden\",\n }}\n >\n {/* Hidden measurement container for items without explicit height */}\n {needsMeasurement && containerWidth > 0 && !allHeightsResolved && (\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
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "petersburg",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"description": "React layout library implementing Hermitage-style 2D bin-packing for picture arrangement",
|
|
5
|
-
"main": "./dist/index.
|
|
6
|
-
"module": "./dist/index.
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
7
8
|
"types": "./dist/index.d.ts",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
10
11
|
"types": "./dist/index.d.ts",
|
|
11
|
-
"import": "./dist/index.
|
|
12
|
-
"require": "./dist/index.
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
13
14
|
}
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
@@ -18,6 +19,8 @@
|
|
|
18
19
|
"scripts": {
|
|
19
20
|
"build": "tsup",
|
|
20
21
|
"dev": "tsup --watch",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
21
24
|
"lint": "eslint src",
|
|
22
25
|
"typecheck": "tsc --noEmit",
|
|
23
26
|
"prepublishOnly": "npm run build"
|
|
@@ -42,11 +45,19 @@
|
|
|
42
45
|
"react-dom": ">=17.0.0"
|
|
43
46
|
},
|
|
44
47
|
"devDependencies": {
|
|
48
|
+
"@eslint/js": "^9.39.2",
|
|
49
|
+
"@testing-library/dom": "^10.4.1",
|
|
50
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
51
|
+
"@testing-library/react": "^16.3.1",
|
|
45
52
|
"@types/react": "^18.2.0",
|
|
46
53
|
"@types/react-dom": "^18.2.0",
|
|
54
|
+
"eslint": "^9.39.2",
|
|
55
|
+
"jsdom": "^27.4.0",
|
|
47
56
|
"react": "^18.2.0",
|
|
48
57
|
"react-dom": "^18.2.0",
|
|
49
58
|
"tsup": "^8.0.0",
|
|
50
|
-
"typescript": "^5.3.0"
|
|
59
|
+
"typescript": "^5.3.0",
|
|
60
|
+
"typescript-eslint": "^8.52.0",
|
|
61
|
+
"vitest": "^4.0.16"
|
|
51
62
|
}
|
|
52
63
|
}
|
package/dist/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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":";AAAA,SAAS,SAAS,QAAQ,UAAU,iBAA8B;;;ACYlE,SAAS,cAAc,OAAoB,KAAqB;AAC9D,QAAM,cAAc,MAAM,OAAO,CAAC,KAAK,SAAS,MAAM,KAAK,SAAS,KAAK,CAAC;AAC1E,SAAO,KAAK,IAAI,aAAa,GAAI;AACnC;AAEA,SAAS,UAAU,OAAoB,UAAqC;AAC1E,MAAI,aAAa,UAAU,aAAa,WAAW;AAEjD,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,CAAC,GAAG,KAAK;AAExB,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,aAAO,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AACzC;AAAA,EACJ;AAEA,SAAO;AACT;AAMO,SAAS,KACd,OACA,gBACA,MAAc,GACd,eAA6B,QACjB;AACZ,MAAI,iBAAiB,WAAW;AAC9B,WAAO,YAAY,OAAO,gBAAgB,GAAG;AAAA,EAC/C;AAEA,QAAM,cAAc,UAAU,OAAO,YAAY;AAGjD,QAAM,YAAY,cAAc,aAAa,GAAG;AAChD,QAAM,YAAoB;AAAA,IACxB,EAAE,GAAG,GAAG,GAAG,GAAG,OAAO,gBAAgB,QAAQ,UAAU;AAAA,EACzD;AAEA,QAAM,aAA2B,CAAC;AAClC,MAAI,OAAO;AAEX,aAAW,QAAQ,aAAa;AAE9B,UAAM,cAAc,KAAK,QAAQ;AACjC,UAAM,eAAe,KAAK,SAAS;AAEnC,UAAM,WAAW,iBAAiB,aAAa,cAAc,SAAS;AAEtE,QAAI,UAAU;AACZ,YAAM,YAAwB;AAAA,QAC5B,IAAI,KAAK;AAAA,QACT,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,MACf;AACA,iBAAW,KAAK,SAAS;AAGzB,aAAO,KAAK,IAAI,MAAM,SAAS,IAAI,KAAK,MAAM;AAG9C,YAAM,aAAmB;AAAA,QACvB,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO;AAAA,QACP,QAAQ;AAAA,MACV;AACA,qBAAe,WAAW,UAAU;AACpC,qBAAe,SAAS;AAAA,IAC1B;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,aAAa;AAAA,EACf;AACF;AAQA,SAAS,YACP,OACA,gBACA,KACY;AAEZ,QAAM,YAAY,cAAc,OAAO,GAAG;AAC1C,QAAM,YAAoB;AAAA,IACxB,EAAE,GAAG,GAAG,GAAG,GAAG,OAAO,gBAAgB,QAAQ,UAAU;AAAA,EACzD;AAEA,QAAM,aAA2B,CAAC;AAClC,MAAI,OAAO;AACX,MAAI,YAAY;AAGhB,MAAI,YAAY;AAChB,QAAM,YAAY,eAAe,OAAO,gBAAgB,GAAG;AAE3D,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,cAAc,KAAK,QAAQ;AACjC,UAAM,eAAe,KAAK,SAAS;AAEnC,UAAM,YAAwB;AAAA,MAC5B,IAAI,KAAK;AAAA,MACT,GAAG;AAAA,MACH,GAAG;AAAA,MACH,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK;AAAA,IACf;AACA,eAAW,KAAK,SAAS;AAEzB,WAAO,KAAK,IAAI,MAAM,KAAK,MAAM;AAGjC,UAAM,aAAmB;AAAA,MACvB,GAAG;AAAA,MACH,GAAG;AAAA,MACH,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AACA,mBAAe,WAAW,UAAU;AACpC,mBAAe,SAAS;AAExB,iBAAa;AACb;AAAA,EACF;AAGA,QAAM,iBAAiB,MAAM,MAAM,SAAS;AAE5C,MAAI,eAAe,WAAW,GAAG;AAC/B,WAAO,EAAE,YAAY,aAAa,KAAK;AAAA,EACzC;AAIA,QAAM,qBAAqB,KAAK,IAAI,WAAW,eAAe,MAAM;AACpE,QAAM,iBAAiB,eAAe,MAAM,GAAG,kBAAkB;AACjE,QAAM,YAAY,eAAe,MAAM,kBAAkB;AAGzD,QAAM,aAAa,CAAC,GAAG,cAAc,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AAEzE,aAAW,QAAQ,YAAY;AAC7B,UAAM,cAAc,KAAK,QAAQ;AACjC,UAAM,eAAe,KAAK,SAAS;AAEnC,UAAM,WAAW,iBAAiB,aAAa,cAAc,SAAS;AAEtE,QAAI,UAAU;AACZ,YAAM,YAAwB;AAAA,QAC5B,IAAI,KAAK;AAAA,QACT,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,MACf;AACA,iBAAW,KAAK,SAAS;AAEzB,aAAO,KAAK,IAAI,MAAM,SAAS,IAAI,KAAK,MAAM;AAE9C,YAAM,aAAmB;AAAA,QACvB,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO;AAAA,QACP,QAAQ;AAAA,MACV;AACA,qBAAe,WAAW,UAAU;AACpC,qBAAe,SAAS;AAAA,IAC1B;AAAA,EACF;AAGA,QAAM,aAAa,CAAC,GAAG,SAAS,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AAEpE,aAAW,QAAQ,YAAY;AAC7B,UAAM,cAAc,KAAK,QAAQ;AACjC,UAAM,eAAe,KAAK,SAAS;AAEnC,UAAM,WAAW,iBAAiB,aAAa,cAAc,SAAS;AAEtE,QAAI,UAAU;AACZ,YAAM,YAAwB;AAAA,QAC5B,IAAI,KAAK;AAAA,QACT,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,MACf;AACA,iBAAW,KAAK,SAAS;AAEzB,aAAO,KAAK,IAAI,MAAM,SAAS,IAAI,KAAK,MAAM;AAE9C,YAAM,aAAmB;AAAA,QACvB,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO;AAAA,QACP,QAAQ;AAAA,MACV;AACA,qBAAe,WAAW,UAAU;AACpC,qBAAe,SAAS;AAAA,IAC1B;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,aAAa;AAAA,EACf;AACF;AAKA,SAAS,eACP,OACA,gBACA,KACQ;AACR,MAAI,IAAI;AACR,MAAI,QAAQ;AAEZ,aAAW,QAAQ,OAAO;AACxB,UAAM,cAAc,KAAK,QAAQ;AACjC,QAAI,IAAI,cAAc,iBAAiB,KAAK;AAC1C;AAAA,IACF;AACA,SAAK;AACL;AAAA,EACF;AAEA,SAAO;AACT;AAyCA,SAAS,iBACP,OACA,QACA,WACiC;AACjC,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,MAAI,eAAgD;AAEpD,aAAW,QAAQ,WAAW;AAE5B,QAAI,SAAS,KAAK,SAAS,UAAU,KAAK,QAAQ;AAEhD,YAAM,IAAI,KAAK;AACf,YAAM,IAAI,KAAK;AAGf,UAAI,IAAI,SAAU,MAAM,SAAS,IAAI,OAAQ;AAC3C,gBAAQ;AACR,gBAAQ;AACR,uBAAe,EAAE,GAAG,EAAE;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAMA,SAAS,eAAe,WAAmB,YAAwB;AACjE,QAAM,WAAmB,CAAC;AAE1B,WAAS,IAAI,UAAU,SAAS,GAAG,KAAK,GAAG,KAAK;AAC9C,UAAM,WAAW,UAAU,CAAC;AAG5B,QAAI,CAAC,aAAa,UAAU,UAAU,GAAG;AACvC;AAAA,IACF;AAGA,cAAU,OAAO,GAAG,CAAC;AAKrB,QAAI,WAAW,IAAI,SAAS,GAAG;AAC7B,eAAS,KAAK;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO,WAAW,IAAI,SAAS;AAAA,QAC/B,QAAQ,SAAS;AAAA,MACnB,CAAC;AAAA,IACH;AAGA,UAAM,cAAc,WAAW,IAAI,WAAW;AAC9C,UAAM,YAAY,SAAS,IAAI,SAAS;AACxC,QAAI,cAAc,WAAW;AAC3B,eAAS,KAAK;AAAA,QACZ,GAAG;AAAA,QACH,GAAG,SAAS;AAAA,QACZ,OAAO,YAAY;AAAA,QACnB,QAAQ,SAAS;AAAA,MACnB,CAAC;AAAA,IACH;AAGA,QAAI,WAAW,IAAI,SAAS,GAAG;AAC7B,eAAS,KAAK;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,OAAO,SAAS;AAAA,QAChB,QAAQ,WAAW,IAAI,SAAS;AAAA,MAClC,CAAC;AAAA,IACH;AAGA,UAAM,eAAe,WAAW,IAAI,WAAW;AAC/C,UAAM,aAAa,SAAS,IAAI,SAAS;AACzC,QAAI,eAAe,YAAY;AAC7B,eAAS,KAAK;AAAA,QACZ,GAAG,SAAS;AAAA,QACZ,GAAG;AAAA,QACH,OAAO,SAAS;AAAA,QAChB,QAAQ,aAAa;AAAA,MACvB,CAAC;AAAA,IACH;AAAA,EACF;AAGA,YAAU,KAAK,GAAG,QAAQ;AAC5B;AAKA,SAAS,eAAe,WAAyB;AAC/C,WAAS,IAAI,UAAU,SAAS,GAAG,KAAK,GAAG,KAAK;AAC9C,aAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,UAAI,MAAM,EAAG;AAEb,UAAI,aAAa,UAAU,CAAC,GAAG,UAAU,CAAC,CAAC,GAAG;AAC5C,kBAAU,OAAO,GAAG,CAAC;AACrB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAKA,SAAS,aAAa,GAAS,GAAkB;AAC/C,SACE,EAAE,IAAI,EAAE,IAAI,EAAE,SACd,EAAE,IAAI,EAAE,QAAQ,EAAE,KAClB,EAAE,IAAI,EAAE,IAAI,EAAE,UACd,EAAE,IAAI,EAAE,SAAS,EAAE;AAEvB;AAKA,SAAS,aAAa,OAAa,OAAsB;AACvD,SACE,MAAM,KAAK,MAAM,KACjB,MAAM,KAAK,MAAM,KACjB,MAAM,IAAI,MAAM,SAAS,MAAM,IAAI,MAAM,SACzC,MAAM,IAAI,MAAM,UAAU,MAAM,IAAI,MAAM;AAE9C;;;ADlTI,SAqBQ,KArBR;AA1HG,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,gBAAgB;AAAA,EAChB,MAAM;AAAA,EACN,eAAe;AAAA,EACf;AACF,GAAyB;AACvB,QAAM,eAAe,OAAuB,IAAI;AAChD,QAAM,aAAa,OAAuB,IAAI;AAC9C,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAiB,CAAC;AAC5D,QAAM,CAAC,iBAAiB,kBAAkB,IAAI;AAAA,IAC5C,oBAAI,IAAI;AAAA,EACV;AACA,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AAGpD,QAAM,iBAAiB,cAAc;AAGrC,QAAM,sBAAsB,QAAQ,MAAM;AACxC,WAAO,MAAM,OAAO,CAAC,SAAS,KAAK,WAAW,MAAS;AAAA,EACzD,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,mBAAmB,oBAAoB,SAAS;AAGtD,YAAU,MAAM;AACd,QAAI,eAAe,QAAW;AAC5B;AAAA,IACF;AAEA,UAAM,UAAU,aAAa;AAC7B,QAAI,CAAC,QAAS;AAEd,UAAM,WAAW,IAAI,eAAe,CAAC,YAAY;AAC/C,iBAAW,SAAS,SAAS;AAC3B,cAAM,QAAQ,MAAM,YAAY;AAChC,yBAAiB,KAAK;AAAA,MACxB;AAAA,IACF,CAAC;AAED,aAAS,QAAQ,OAAO;AACxB,qBAAiB,QAAQ,sBAAsB,EAAE,KAAK;AAEtD,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,UAAU,CAAC;AAGf,YAAU,MAAM;AACd,QAAI,CAAC,oBAAoB,mBAAmB,GAAG;AAC7C,qBAAe,KAAK;AACpB;AAAA,IACF;AAEA,mBAAe,IAAI;AAGnB,0BAAsB,MAAM;AAC1B,YAAM,mBAAmB,WAAW;AACpC,UAAI,CAAC,kBAAkB;AACrB,uBAAe,KAAK;AACpB;AAAA,MACF;AAEA,YAAM,aAAa,oBAAI,IAAoB;AAE3C,iBAAW,QAAQ,qBAAqB;AACtC,cAAM,UAAU,iBAAiB;AAAA,UAC/B,qBAAqB,KAAK,EAAE;AAAA,QAC9B;AACA,YAAI,SAAS;AACX,qBAAW,IAAI,KAAK,IAAI,QAAQ,sBAAsB,EAAE,MAAM;AAAA,QAChE;AAAA,MACF;AAEA,yBAAmB,UAAU;AAC7B,qBAAe,KAAK;AAAA,IACtB,CAAC;AAAA,EACH,GAAG,CAAC,qBAAqB,kBAAkB,cAAc,CAAC;AAG1D,QAAM,gBAAgB,QAAQ,MAAM;AAClC,WAAO,MAAM,IAAI,CAAC,UAAU;AAAA,MAC1B,GAAG;AAAA,MACH,QAAQ,KAAK,UAAU,gBAAgB,IAAI,KAAK,EAAE,KAAK;AAAA,IACzD,EAAE;AAAA,EACJ,GAAG,CAAC,OAAO,eAAe,CAAC;AAG3B,QAAM,qBAAqB,cAAc,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC;AAExE,QAAM,EAAE,YAAY,YAAY,IAAI,QAAQ,MAAM;AAChD,QAAI,mBAAmB,KAAK,CAAC,oBAAoB;AAC/C,aAAO,EAAE,YAAY,CAAC,GAAG,aAAa,EAAE;AAAA,IAC1C;AACA,WAAO,KAAK,eAAe,gBAAgB,KAAK,YAAY;AAAA,EAC9D,GAAG,CAAC,eAAe,gBAAgB,KAAK,cAAc,kBAAkB,CAAC;AAGzE,QAAM,eAAe,QAAQ,MAAM;AACjC,UAAM,MAAM,oBAAI,IAAsD;AACtE,eAAW,KAAK,YAAY;AAC1B,UAAI,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,QAAQ,EAAE,OAAO,CAAC;AAAA,IACpD;AACA,WAAO;AAAA,EACT,GAAG,CAAC,UAAU,CAAC;AAGf,QAAM,cAAc,QAAQ,MAAM;AAChC,WAAO,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM;AAC/B,YAAM,OAAO,aAAa,IAAI,EAAE,EAAE;AAClC,YAAM,OAAO,aAAa,IAAI,EAAE,EAAE;AAClC,UAAI,CAAC,QAAQ,CAAC,KAAM,QAAO;AAC3B,UAAI,KAAK,MAAM,KAAK,EAAG,QAAO,KAAK,IAAI,KAAK;AAC5C,aAAO,KAAK,IAAI,KAAK;AAAA,IACvB,CAAC;AAAA,EACH,GAAG,CAAC,OAAO,YAAY,CAAC;AAGxB,QAAM,aAAa,sBAAsB,iBAAiB;AAE1D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL;AAAA,MACA,OAAO;AAAA,QACL,UAAU;AAAA,QACV,OAAO,cAAc;AAAA,QACrB,QAAQ,eAAe;AAAA,MACzB;AAAA,MAGC;AAAA,4BAAoB,iBAAiB,KACpC;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,eAAY;AAAA,YACZ,OAAO;AAAA,cACL,UAAU;AAAA,cACV,YAAY;AAAA,cACZ,eAAe;AAAA,YACjB;AAAA,YAEC,8BAAoB,IAAI,CAAC,SACxB;AAAA,cAAC;AAAA;AAAA,gBAEC,mBAAiB,KAAK;AAAA,gBACtB,OAAO,EAAE,OAAO,KAAK,MAAM;AAAA,gBAE1B,eAAK;AAAA;AAAA,cAJD,KAAK;AAAA,YAKZ,CACD;AAAA;AAAA,QACH;AAAA,QAID,cACC,YAAY,IAAI,CAAC,SAAS;AACxB,gBAAM,WAAW,aAAa,IAAI,KAAK,EAAE;AACzC,cAAI,CAAC,SAAU,QAAO;AAEtB,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC,OAAO;AAAA,gBACL,UAAU;AAAA,gBACV,MAAM,SAAS;AAAA,gBACf,KAAK,SAAS;AAAA,gBACd,OAAO,KAAK;AAAA,gBACZ,QAAQ,SAAS;AAAA,cACnB;AAAA,cAEC,eAAK;AAAA;AAAA,YATD,KAAK;AAAA,UAUZ;AAAA,QAEJ,CAAC;AAAA;AAAA;AAAA,EACL;AAEJ;","names":[]}
|
|
File without changes
|