diff --git a/web/src/lib/components/HorizontalScrollRow.svelte b/web/src/lib/components/HorizontalScrollRow.svelte index a6c5ec4a..865089af 100644 --- a/web/src/lib/components/HorizontalScrollRow.svelte +++ b/web/src/lib/components/HorizontalScrollRow.svelte @@ -3,14 +3,24 @@ import type { Snippet } from 'svelte'; let { - items, + rows, item, title, ariaLabel }: { - items: T[]; + /** One or more visual rows of items rendered inside a single shared + horizontal scroller. Pass [items] for a single-row section. */ + rows: T[][]; + /** Render snippet for a single item. The second arg is the global + index across the flattened sequence (rows[0] then rows[1] etc.) — + useful when the snippet needs section-wide indexing, e.g. a + compact track card whose play action takes its position in the + whole section's track list. */ item?: Snippet<[T, number]>; + /** Section heading shown to the left of the arrows. Omit for an + unlabeled scroller. */ title?: string; + /** Accessible label for the section as a whole. */ ariaLabel?: string; } = $props(); @@ -22,6 +32,16 @@ const atStart = $derived(scrollLeft <= 1); const atEnd = $derived(scrollLeft + clientWidth >= scrollWidth - 1); + // Per-row offsets so the snippet's second arg can be the global index. + // [0, len(rows[0]), len(rows[0])+len(rows[1]), ...] + const rowOffsets = $derived.by(() => { + const out: number[] = [0]; + for (let i = 0; i < rows.length - 1; i++) { + out.push(out[i] + rows[i].length); + } + return out; + }); + function refreshMetrics() { if (!scroller) return; scrollLeft = scroller.scrollLeft; @@ -39,8 +59,8 @@ $effect(() => { refreshMetrics(); }); -