( itemWidths: number[], availableWidth: number, arrowWidth: number, selectedIdx: number, firstItemHasSeparator = true, )
| 19 | * @returns Visible window bounds and whether to show scroll arrows |
| 20 | */ |
| 21 | export function calculateHorizontalScrollWindow( |
| 22 | itemWidths: number[], |
| 23 | availableWidth: number, |
| 24 | arrowWidth: number, |
| 25 | selectedIdx: number, |
| 26 | firstItemHasSeparator = true, |
| 27 | ): HorizontalScrollWindow { |
| 28 | const totalItems = itemWidths.length |
| 29 | |
| 30 | if (totalItems === 0) { |
| 31 | return { |
| 32 | startIndex: 0, |
| 33 | endIndex: 0, |
| 34 | showLeftArrow: false, |
| 35 | showRightArrow: false, |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | // Clamp selectedIdx to valid range |
| 40 | const clampedSelected = Math.max(0, Math.min(selectedIdx, totalItems - 1)) |
| 41 | |
| 42 | // If all items fit, show them all |
| 43 | const totalWidth = itemWidths.reduce((sum, w) => sum + w, 0) |
| 44 | if (totalWidth <= availableWidth) { |
| 45 | return { |
| 46 | startIndex: 0, |
| 47 | endIndex: totalItems, |
| 48 | showLeftArrow: false, |
| 49 | showRightArrow: false, |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | // Calculate cumulative widths for efficient range calculations |
| 54 | const cumulativeWidths: number[] = [0] |
| 55 | for (let i = 0; i < totalItems; i++) { |
| 56 | cumulativeWidths.push(cumulativeWidths[i]! + itemWidths[i]!) |
| 57 | } |
| 58 | |
| 59 | // Helper to get width of range [start, end) |
| 60 | function rangeWidth(start: number, end: number): number { |
| 61 | const baseWidth = cumulativeWidths[end]! - cumulativeWidths[start]! |
| 62 | // When starting after index 0 and first item has separator baked in, |
| 63 | // subtract 1 because we don't render leading separator on first visible item |
| 64 | if (firstItemHasSeparator && start > 0) { |
| 65 | return baseWidth - 1 |
| 66 | } |
| 67 | return baseWidth |
| 68 | } |
| 69 | |
| 70 | // Calculate effective available width based on whether we'll show arrows |
| 71 | function getEffectiveWidth(start: number, end: number): number { |
| 72 | let width = availableWidth |
| 73 | if (start > 0) width -= arrowWidth // left arrow |
| 74 | if (end < totalItems) width -= arrowWidth // right arrow |
| 75 | return width |
| 76 | } |
| 77 | |
| 78 | // Edge-based scrolling: Start from the beginning and only scroll when necessary |
no test coverage detected