(items, bpCols)
| 47 | } |
| 48 | |
| 49 | function normalizeAndCompact(items, bpCols) { |
| 50 | const normalized = cloneItems(items).map((it) => clampItemToGrid(it, bpCols)); |
| 51 | // Stable order by y, then x, then i to make deterministic |
| 52 | normalized.sort((a, b) => (a.y - b.y) || (a.x - b.x) || `${a.i}`.localeCompare(`${b.i}`)); |
| 53 | |
| 54 | const placed = []; |
| 55 | normalized.forEach((it) => { |
| 56 | const candidate = placeGreedy(it, placed, bpCols); |
| 57 | placed.push(candidate); |
| 58 | }); |
| 59 | |
| 60 | // Final stable sort |
| 61 | placed.sort((a, b) => (a.y - b.y) || (a.x - b.x) || `${a.i}`.localeCompare(`${b.i}`)); |
| 62 | |
| 63 | // Stretch trailing gaps on each row so the last item fills row end |
| 64 | const stretched = stretchTrailingGaps(placed, bpCols); |
| 65 | return stretched; |
| 66 | } |
| 67 | |
| 68 | function placeGreedy(item, placed, bpCols) { |
| 69 | const clamped = clampItemToGrid(item, bpCols); |
no test coverage detected