This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

web/components: improve Pagination

Signed-off-by: eti <eti@eti.tf>

+26 -12
-4
web/src/lib/components/ui/Pagination.stories.svelte
··· 9 9 argTypes: { 10 10 page: { control: { type: "number" } }, 11 11 total: { control: { type: "number" } }, 12 - siblings: { control: { type: "number" } }, 13 - boundaries: { control: { type: "number" } }, 14 12 labels: { control: { type: "boolean" } }, 15 13 disabled: { control: { type: "boolean" } } 16 14 }, ··· 33 31 <!-- slots are sized from `total`, so a four-digit range keeps one width across the 34 32 whole row instead of widening as the current page gains a digit --> 35 33 <Story name="FourDigitRange" args={{ page: 1234, total: 5000 }} /> 36 - <Story name="WideWindow" args={{ page: 20, total: 40, siblings: 2, boundaries: 2 }} /> 37 - <Story name="NarrowWindow" args={{ page: 20, total: 40, siblings: 0 }} />
+26 -8
web/src/lib/components/ui/Pagination.svelte
··· 25 25 }); 26 26 27 27 export type PaginationVariants = VariantProps<typeof pagination>; 28 + 29 + export type PaginationItem = number | "gap-start" | "gap-end"; 30 + 31 + const range = (from: number, to: number): number[] => 32 + Array.from({ length: Math.max(to - from + 1, 0) }, (_, i) => from + i); 33 + 34 + // both ends, the current page and its two neighbours, and the two gaps 35 + const SLOTS = 7; 36 + 37 + // the row is always SLOTS long once a gap is needed, so both gaps are permanent 38 + // residents. that is what lets the plate be placed from an index rather than 39 + // measured, and why paging the middle of a long range moves the numbers, not the row 40 + export function paginate(page: number, count: number): PaginationItem[] { 41 + if (count <= SLOTS) return range(1, count); 42 + // near either end a gap would hide fewer pages than the slots it costs, so the 43 + // window opens flush against that end and only the far gap is drawn 44 + if (page < 4) return [...range(1, 5), "gap-end", count]; 45 + if (page > count - 3) return [1, "gap-start", ...range(count - 4, count)]; 46 + return [1, "gap-start", ...range(page - 1, page + 1), "gap-end", count]; 47 + } 28 48 </script> 29 49 30 50 <script lang="ts"> ··· 33 53 import { untrack } from "svelte"; 34 54 import { Spring, prefersReducedMotion } from "svelte/motion"; 35 55 import Button from "./Button.svelte"; 36 - import { paginate } from "./paginate"; 37 56 38 57 interface Props { 39 58 page: number; 40 59 total: number; 41 - siblings?: number; 42 - boundaries?: number; 43 60 onchange?: (page: number) => void; 44 61 class?: string; 45 62 labels?: boolean; ··· 49 66 let { 50 67 page = $bindable(1), 51 68 total, 52 - siblings = 1, 53 - boundaries = 1, 54 69 onchange, 55 70 class: className, 56 71 labels, ··· 59 74 60 75 const count = $derived(Math.max(total, 1)); 61 76 const current = $derived(Math.min(Math.max(page, 1), count)); 62 - const items = $derived(paginate(current, count, siblings, boundaries)); 77 + const items = $derived(paginate(current, count)); 63 78 const index = $derived(items.indexOf(current)); 64 79 65 80 const digits = $derived(String(count).length); 66 81 67 - const plate = new Spring(index, { stiffness: 0.139, damping: 0.61 }); 82 + const plate = new Spring( 83 + untrack(() => index), 84 + { stiffness: 0.139, damping: 0.61 } 85 + ); 68 86 69 87 let direction = $state(1); 70 88 let rolled = $state(false); 71 - let previous = current; 89 + let previous = untrack(() => current); 72 90 $effect.pre(() => { 73 91 if (current !== previous) { 74 92 direction = current > previous ? 1 : -1;