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.
@@ -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
- import { useMemo, useRef, useState, useEffect } from "react";
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
- import { jsx, jsxs } from "react/jsx-runtime";
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.mjs.map
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":"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","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
- var import_react = require("react");
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
- var import_jsx_runtime = require("react/jsx-runtime");
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 = (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)(
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 = (0, import_react.useMemo)(() => {
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
- (0, import_react.useEffect)(() => {
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
- (0, import_react.useEffect)(() => {
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 = (0, import_react.useMemo)(() => {
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 } = (0, import_react.useMemo)(() => {
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 = (0, import_react.useMemo)(() => {
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 = (0, import_react.useMemo)(() => {
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__ */ (0, import_jsx_runtime.jsxs)(
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__ */ (0, import_jsx_runtime.jsx)(
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__ */ (0, import_jsx_runtime.jsx)(
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__ */ (0, import_jsx_runtime.jsx)(
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
- // Annotate the CommonJS export names for ESM import in node:
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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAkE;;;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;AA1HG,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;AACA,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAS,KAAK;AAGpD,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,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,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,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":[]}
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.2",
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.js",
6
- "module": "./dist/index.mjs",
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.mjs",
12
- "require": "./dist/index.js"
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
  }
@@ -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":"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","names":[]}
File without changes