diff --git a/COMPONENT_INDEX.md b/COMPONENT_INDEX.md index f495d031..0d0c1429 100644 --- a/COMPONENT_INDEX.md +++ b/COMPONENT_INDEX.md @@ -2681,24 +2681,25 @@ None. ### Props -| Prop name | Required | Kind | Reactive | Type | Default value | Description | -| :-------------------- | :------- | :--------------- | :------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------ | -| pageSize | No | let | Yes | number | 10 | Specify the number of items to display in a page | -| page | No | let | Yes | number | 1 | Specify the current page index | -| totalItems | No | let | No | number | 0 | Specify the total number of items | -| disabled | No | let | No | boolean | false | Set to `true` to disable the pagination | -| forwardText | No | let | No | string | "Next page" | Specify the forward button text | -| backwardText | No | let | No | string | "Previous page" | Specify the backward button text | -| itemsPerPageText | No | let | No | string | "Items per page:" | Specify the items per page text | -| itemText | No | let | No | (min: number, max: number) => string | (min, max) => \`${min}–${max} item${max === 1 ? "" : "s"}\` | Override the item text | -| itemRangeText | No | let | No | (min: number, max: number, total: number) => string | (min, max, total) => \`${min}–${max} of ${total} item${max === 1 ? "" : "s"}\` | Override the item range text | -| pageInputDisabled | No | let | No | boolean | false | Set to `true` to disable the page input | -| pageSizeInputDisabled | No | let | No | boolean | false | Set to `true` to disable the page size input | -| pageSizes | No | let | No | ReadonlyArray | [10] | Specify the available page sizes | -| pagesUnknown | No | let | No | boolean | false | Set to `true` if the number of pages is unknown | -| pageText | No | let | No | (page: number) => string | (page) => \`page ${page}\` | Override the page text | -| pageRangeText | No | let | No | (current: number, total: number) => string | (current, total) => \`of ${total} page${total === 1 ? "" : "s"}\` | Override the page range text | -| id | No | let | No | string | "ccs-" + Math.random().toString(36) | Set an id for the top-level element | +| Prop name | Required | Kind | Reactive | Type | Default value | Description | +| :-------------------- | :------- | :--------------- | :------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| pageSize | No | let | Yes | number | 10 | Specify the number of items to display in a page | +| page | No | let | Yes | number | 1 | Specify the current page index | +| totalItems | No | let | No | number | 0 | Specify the total number of items | +| pageWindow | No | let | No | number | 1000 | If `totalItems` is a large number, it can affect the
rendering performance of this component since its value
is used to calculate the number of pages in the native
select dropdown. This value creates a small window of
pages rendered around the current page. By default,
a maximum of 1000 select items are rendered. | +| disabled | No | let | No | boolean | false | Set to `true` to disable the pagination | +| forwardText | No | let | No | string | "Next page" | Specify the forward button text | +| backwardText | No | let | No | string | "Previous page" | Specify the backward button text | +| itemsPerPageText | No | let | No | string | "Items per page:" | Specify the items per page text | +| itemText | No | let | No | (min: number, max: number) => string | (min, max) => \`${min}–${max} item${max === 1 ? "" : "s"}\` | Override the item text | +| itemRangeText | No | let | No | (min: number, max: number, total: number) => string | (min, max, total) => \`${min}–${max} of ${total} item${max === 1 ? "" : "s"}\` | Override the item range text | +| pageInputDisabled | No | let | No | boolean | false | Set to `true` to disable the page input | +| pageSizeInputDisabled | No | let | No | boolean | false | Set to `true` to disable the page size input | +| pageSizes | No | let | No | ReadonlyArray | [10] | Specify the available page sizes | +| pagesUnknown | No | let | No | boolean | false | Set to `true` if the number of pages is unknown | +| pageText | No | let | No | (page: number) => string | (page) => \`page ${page}\` | Override the page text | +| pageRangeText | No | let | No | (current: number, total: number) => string | (current, total) => \`of ${total} page${total === 1 ? "" : "s"}\` | Override the page range text | +| id | No | let | No | string | "ccs-" + Math.random().toString(36) | Set an id for the top-level element | ### Slots diff --git a/docs/src/COMPONENT_API.json b/docs/src/COMPONENT_API.json index ea4293b0..ac997ae7 100644 --- a/docs/src/COMPONENT_API.json +++ b/docs/src/COMPONENT_API.json @@ -10048,6 +10048,18 @@ "constant": false, "reactive": false }, + { + "name": "pageWindow", + "kind": "let", + "description": "If `totalItems` is a large number, it can affect the\nrendering performance of this component since its value\nis used to calculate the number of pages in the native\nselect dropdown. This value creates a small window of\npages rendered around the current page. By default,\na maximum of 1000 select items are rendered.", + "type": "number", + "value": "1000", + "isFunction": false, + "isFunctionDeclaration": false, + "isRequired": false, + "constant": false, + "reactive": false + }, { "name": "disabled", "kind": "let", diff --git a/docs/src/pages/components/Pagination.svx b/docs/src/pages/components/Pagination.svx index 0ff1b471..0e54246b 100644 --- a/docs/src/pages/components/Pagination.svx +++ b/docs/src/pages/components/Pagination.svx @@ -19,6 +19,19 @@ components: ["Pagination", "PaginationSkeleton"] +## Page window + +The number of native select items rendered is derived from the value of `totalItems`. + +If `totalItems` is a very large number, this can impact rendering performance since +thousands of elements may be rendered. By default, the window of rendered items is +capped at 1,000. For example, if `totalItems=100_000` and the `pageSize=10`, +1,000 select options are rendered. + +Use the `pageWindow` prop to increase this value. + + + ## Hidden page input diff --git a/src/Pagination/Pagination.svelte b/src/Pagination/Pagination.svelte index 1306e912..89b90b82 100644 --- a/src/Pagination/Pagination.svelte +++ b/src/Pagination/Pagination.svelte @@ -12,6 +12,16 @@ /** Specify the total number of items */ export let totalItems = 0; + /** + * If `totalItems` is a large number, it can affect the + * rendering performance of this component since its value + * is used to calculate the number of pages in the native + * select dropdown. This value creates a small window of + * pages rendered around the current page. By default, + * a maximum of 1000 select items are rendered. + */ + export let pageWindow = 1000; + /** Set to `true` to disable the pagination */ export let disabled = false; @@ -81,6 +91,21 @@ const dispatch = createEventDispatcher(); + /** + * Returns a subset of page numbers centered around the current page to prevent + * performance issues with large datasets. Creates a capped window of pages + * instead of potentially thousands, improving render speed and memory usage. + * @param {number} currentPage - The current page number + * @param {number} totalPages - Total number of pages + * @param {number} window - How many pages to show before/after current page + * @returns {number[]} Array of page numbers to display + */ + function getWindowedPages(currentPage, totalPages, window) { + const start = Math.max(1, currentPage - window); + const end = Math.min(totalPages, currentPage + window); + return Array.from({ length: end - start + 1 }, (_, i) => start + i); + } + afterUpdate(() => { if (page > totalPages) { page = totalPages; @@ -89,7 +114,7 @@ $: dispatch("update", { pageSize, page }); $: totalPages = Math.max(Math.ceil(totalItems / pageSize), 1); - $: selectItems = Array.from({ length: totalPages }, (_, i) => i); + $: selectItems = getWindowedPages(page, totalPages, pageWindow); $: backButtonDisabled = disabled || page === 1; $: forwardButtonDisabled = disabled || page === totalPages; @@ -146,7 +171,7 @@ bind:selected={page} > {#each selectItems as size, i (size)} - + {/each} diff --git a/tests/Pagination/Pagination.test.svelte b/tests/Pagination/Pagination.test.svelte index 63b4005e..64fdae79 100644 --- a/tests/Pagination/Pagination.test.svelte +++ b/tests/Pagination/Pagination.test.svelte @@ -11,6 +11,7 @@ export let pageSizeInputDisabled = false; export let pageSize = 10; export let pageSizes: ReadonlyArray = [10]; + export let pageWindow: undefined | number = undefined; export let pagesUnknown = false; @@ -23,6 +24,7 @@ {itemsPerPageText} {pageInputDisabled} {pageSizeInputDisabled} + {pageWindow} bind:pageSize {pageSizes} {pagesUnknown} diff --git a/tests/Pagination/Pagination.test.ts b/tests/Pagination/Pagination.test.ts index 027647d2..7345c214 100644 --- a/tests/Pagination/Pagination.test.ts +++ b/tests/Pagination/Pagination.test.ts @@ -196,4 +196,22 @@ describe("Pagination", () => { expect(screen.getByText("0–0 of 0 items")).toBeInTheDocument(); }); + + it("renders a cap of 1000 page numbers by default", () => { + render(Pagination, { + props: { totalItems: 100_000 }, + }); + + const pageNumbers = screen.getByLabelText(/Page number, of 10000 pages/); + expect(pageNumbers).toHaveLength(1_000 + 1); + }); + + it("renders a custom page window", () => { + render(Pagination, { + props: { totalItems: 100_000, pageWindow: 100 }, + }); + + const pageNumbers = screen.getByLabelText(/Page number, of 10000 pages/); + expect(pageNumbers).toHaveLength(100 + 1); + }); }); diff --git a/types/Pagination/Pagination.svelte.d.ts b/types/Pagination/Pagination.svelte.d.ts index 942aff98..8d0a9384 100644 --- a/types/Pagination/Pagination.svelte.d.ts +++ b/types/Pagination/Pagination.svelte.d.ts @@ -16,6 +16,17 @@ type $Props = { */ totalItems?: number; + /** + * If `totalItems` is a large number, it can affect the + * rendering performance of this component since its value + * is used to calculate the number of pages in the native + * select dropdown. This value creates a small window of + * pages rendered around the current page. By default, + * a maximum of 1000 select items are rendered. + * @default 1000 + */ + pageWindow?: number; + /** * Set to `true` to disable the pagination * @default false