( percentages: readonly number[], available: number, dividerSize: number = DEFAULT_DIVIDER_SIZE, minSizes?: readonly number[], maxSizes?: readonly number[], )
| 91 | * @returns Computed panel sizes and divider positions |
| 92 | */ |
| 93 | export function computePanelSizes( |
| 94 | percentages: readonly number[], |
| 95 | available: number, |
| 96 | dividerSize: number = DEFAULT_DIVIDER_SIZE, |
| 97 | minSizes?: readonly number[], |
| 98 | maxSizes?: readonly number[], |
| 99 | ): PanelSizes { |
| 100 | const panelCount = percentages.length; |
| 101 | if (panelCount === 0) { |
| 102 | return Object.freeze({ sizes: Object.freeze([]), dividerPositions: Object.freeze([]) }); |
| 103 | } |
| 104 | |
| 105 | const totalDividerSpace = Math.max(0, panelCount - 1) * dividerSize; |
| 106 | const availableForPanels = Math.max(0, available - totalDividerSpace); |
| 107 | |
| 108 | // First pass: compute ideal sizes with deterministic integer remainder handling. |
| 109 | const weights = percentages.map((p) => |
| 110 | Number.isFinite(p) && (p as number) > 0 ? (p as number) : 0, |
| 111 | ); |
| 112 | const sizes = distributeInteger(availableForPanels, weights); |
| 113 | |
| 114 | normalizePanelCellSizes(sizes, availableForPanels, minSizes, maxSizes); |
| 115 | |
| 116 | // Compute divider positions |
| 117 | const dividerPositions: number[] = []; |
| 118 | let position = 0; |
| 119 | for (let j = 0; j < sizes.length - 1; j++) { |
| 120 | position += sizes[j] ?? 0; |
| 121 | dividerPositions.push(position); |
| 122 | position += dividerSize; |
| 123 | } |
| 124 | |
| 125 | return Object.freeze({ |
| 126 | sizes: Object.freeze(sizes), |
| 127 | dividerPositions: Object.freeze(dividerPositions), |
| 128 | }); |
| 129 | } |
| 130 | |
| 131 | /** |
| 132 | * Compute panel sizes in cells for both percent and absolute sizing modes. |
no test coverage detected