diff --git a/web/src/lib/components/ui/Pagination.stories.svelte b/web/src/lib/components/ui/Pagination.stories.svelte
--- a/web/src/lib/components/ui/Pagination.stories.svelte
+++ b/web/src/lib/components/ui/Pagination.stories.svelte
@@ -9,8 +9,6 @@
argTypes: {
page: { control: { type: "number" } },
total: { control: { type: "number" } },
- siblings: { control: { type: "number" } },
- boundaries: { control: { type: "number" } },
labels: { control: { type: "boolean" } },
disabled: { control: { type: "boolean" } }
},
@@ -33,5 +31,3 @@
-
-
diff --git a/web/src/lib/components/ui/Pagination.svelte b/web/src/lib/components/ui/Pagination.svelte
--- a/web/src/lib/components/ui/Pagination.svelte
+++ b/web/src/lib/components/ui/Pagination.svelte
@@ -25,6 +25,26 @@
});
export type PaginationVariants = VariantProps;
+
+ export type PaginationItem = number | "gap-start" | "gap-end";
+
+ const range = (from: number, to: number): number[] =>
+ Array.from({ length: Math.max(to - from + 1, 0) }, (_, i) => from + i);
+
+ // both ends, the current page and its two neighbours, and the two gaps
+ const SLOTS = 7;
+
+ // the row is always SLOTS long once a gap is needed, so both gaps are permanent
+ // residents. that is what lets the plate be placed from an index rather than
+ // measured, and why paging the middle of a long range moves the numbers, not the row
+ export function paginate(page: number, count: number): PaginationItem[] {
+ if (count <= SLOTS) return range(1, count);
+ // near either end a gap would hide fewer pages than the slots it costs, so the
+ // window opens flush against that end and only the far gap is drawn
+ if (page < 4) return [...range(1, 5), "gap-end", count];
+ if (page > count - 3) return [1, "gap-start", ...range(count - 4, count)];
+ return [1, "gap-start", ...range(page - 1, page + 1), "gap-end", count];
+ }