(existingLayout, widget, bp)
| 134 | } |
| 135 | |
| 136 | export function placeNewWidget(existingLayout, widget, bp) { |
| 137 | const bpCols = cols[bp] || 12; |
| 138 | const items = Array.isArray(existingLayout) ? existingLayout : []; |
| 139 | const w = (bp === "xs" || bp === "xxs") ? bpCols : Math.max(1, Math.min(widget.w || 2, bpCols)); |
| 140 | const h = Math.max(1, widget.h || 2); |
| 141 | |
| 142 | if (items.length === 0) { |
| 143 | return { x: 0, y: 0, w, h }; |
| 144 | } |
| 145 | |
| 146 | const placed = cloneItems(items); |
| 147 | |
| 148 | // Scan rows from 0 to a safe upper bound |
| 149 | const maxY = placed.reduce((acc, it) => Math.max(acc, it.y + it.h), 0); |
| 150 | const upper = maxY + 50; // generous bound |
| 151 | if (bp === "xs" || bp === "xxs") { |
| 152 | // Always stack to bottom full width |
| 153 | return { x: 0, y: maxY, w, h }; |
| 154 | } |
| 155 | for (let y = 0; y <= upper; y += 1) { |
| 156 | for (let x = 0; x <= bpCols - w; x += 1) { |
| 157 | const candidate = { x, y, w, h }; |
| 158 | const collision = placed.some((it) => rectanglesOverlap(candidate, it)); |
| 159 | if (!collision) { |
| 160 | return candidate; |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | // Fallback at bottom |
| 166 | const bottom = placed.reduce((acc, it) => Math.max(acc, it.y + it.h), 0); |
| 167 | return { x: 0, y: bottom, w, h }; |
| 168 | } |
| 169 | |
| 170 | export default { |
| 171 | tidyLayout, |
no test coverage detected