@ds-mo/ui 14.18.1 → 14.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.package-ready.json +1 -1
- package/dist/agent-patterns.json +1 -1
- package/dist/agent.json +16 -9
- package/dist/angular/ds-pagination.d.ts +1 -1
- package/dist/angular/ds-pagination.js +3 -3
- package/dist/components/ds-bar-nav.js +1 -1
- package/dist/components/ds-bar-title.js +1 -1
- package/dist/components/ds-bar-workflow.js +1 -1
- package/dist/components/ds-menu.js +1 -1
- package/dist/components/ds-mobile-header.js +1 -1
- package/dist/components/ds-mobile-section-switcher.js +1 -1
- package/dist/components/ds-pagination.js +1 -1
- package/dist/components/ds-panel-nav.js +1 -1
- package/dist/components/ds-panel-tool-header.js +1 -1
- package/dist/components/ds-panel-tool-search.js +1 -1
- package/dist/components/ds-panel-tools.js +1 -1
- package/dist/components/ds-paper-texture.js +1 -1
- package/dist/components/ds-shell-app.js +1 -1
- package/dist/components/ds-shell-page.js +1 -1
- package/dist/components/ds-shell-tools.js +1 -1
- package/dist/components/ds-swatch-picker.js +1 -1
- package/dist/components/ds-table-filter.js +1 -1
- package/dist/components/ds-table-saved-views.js +1 -1
- package/dist/components/ds-table-sort.js +1 -1
- package/dist/components/ds-table-toolbar.js +1 -1
- package/dist/components/ds-table.js +1 -1
- package/dist/components/ds-table.js.map +1 -1
- package/dist/components/ds-toast.js +1 -1
- package/dist/components/ds-typing-indicator.js +1 -1
- package/dist/components/p--CRAu9fJ.js +2 -0
- package/dist/components/{p-CE2IKOsy.js.map → p--CRAu9fJ.js.map} +1 -1
- package/dist/components/p-BNiuAMeh.js +2 -0
- package/dist/components/p-BNiuAMeh.js.map +1 -0
- package/dist/components/{p-CKVHrOEp.js → p-BS0AbMNK.js} +2 -2
- package/dist/components/{p-LK-jpxwq.js → p-BZVWmzKf.js} +2 -2
- package/dist/components/{p-1Hu9jlju.js → p-Bf7PZJPT.js} +2 -2
- package/dist/components/{p-B00PEE6t.js → p-BxGQ3cRJ.js} +2 -2
- package/dist/components/{p-D123Jvp-.js → p-BysVBVQv.js} +2 -2
- package/dist/components/{p-BXN-e7vF.js → p-DOUDRJbE.js} +2 -2
- package/dist/components/{p-CqQFowZd.js → p-DaAXY-XS.js} +2 -2
- package/dist/components/{p-D7haeL5y.js → p-DlYw4UGQ.js} +2 -2
- package/dist/components/{p-H5T258T-.js → p-ZpQBAwZC.js} +2 -2
- package/dist/components/{p-Bkh7scMQ.js → p-fg9EKoZp.js} +2 -2
- package/dist/components/{p-CLVxgTme.js → p-r8PMqlqm.js} +2 -2
- package/dist/docs/components.json +30 -1
- package/dist/mcp-data/registry/pagination.json +31 -10
- package/dist/mcp-data/registry/registry.json +15 -8
- package/dist/mcp-data/registry/table.json +1 -1
- package/dist/types/components/Pagination/Pagination.d.ts +2 -0
- package/dist/types/components/Pagination/pagination-types.d.ts +2 -0
- package/dist/types/components.d.ts +11 -0
- package/dist/vue/ds-pagination.js +1 -0
- package/package.json +1 -1
- package/dist/components/p-Bece5KH3.js +0 -2
- package/dist/components/p-Bece5KH3.js.map +0 -1
- package/dist/components/p-CE2IKOsy.js +0 -2
- /package/dist/components/{p-CKVHrOEp.js.map → p-BS0AbMNK.js.map} +0 -0
- /package/dist/components/{p-LK-jpxwq.js.map → p-BZVWmzKf.js.map} +0 -0
- /package/dist/components/{p-1Hu9jlju.js.map → p-Bf7PZJPT.js.map} +0 -0
- /package/dist/components/{p-B00PEE6t.js.map → p-BxGQ3cRJ.js.map} +0 -0
- /package/dist/components/{p-D123Jvp-.js.map → p-BysVBVQv.js.map} +0 -0
- /package/dist/components/{p-BXN-e7vF.js.map → p-DOUDRJbE.js.map} +0 -0
- /package/dist/components/{p-CqQFowZd.js.map → p-DaAXY-XS.js.map} +0 -0
- /package/dist/components/{p-D7haeL5y.js.map → p-DlYw4UGQ.js.map} +0 -0
- /package/dist/components/{p-H5T258T-.js.map → p-ZpQBAwZC.js.map} +0 -0
- /package/dist/components/{p-Bkh7scMQ.js.map → p-fg9EKoZp.js.map} +0 -0
- /package/dist/components/{p-CLVxgTme.js.map → p-r8PMqlqm.js.map} +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
3
|
"name": "pagination",
|
|
4
4
|
"title": "Pagination",
|
|
5
|
-
"description": "Controlled fixed or fitted page-size, compact range, and first/
|
|
5
|
+
"description": "Controlled fixed or fitted page-size, compact range, adjacent-page navigation, and optional first/last controls for application-owned data windows.",
|
|
6
6
|
"type": "registry:ui",
|
|
7
7
|
"meta": {
|
|
8
8
|
"library": "@ds-mo/ui",
|
|
@@ -13,14 +13,15 @@
|
|
|
13
13
|
"intent": {
|
|
14
14
|
"audience": "general",
|
|
15
15
|
"status": "stable",
|
|
16
|
-
"summary": "Controlled fixed or fitted page-size, compact range, and first/
|
|
16
|
+
"summary": "Controlled fixed or fitted page-size, compact range, adjacent-page navigation, and optional first/last controls for application-owned data windows.",
|
|
17
17
|
"useWhen": [
|
|
18
18
|
"A result set is divided into discrete pages and the application owns fetching or slicing each page.",
|
|
19
|
-
"A table or list needs a compact page-size selector, result range, and boundary-aware navigation."
|
|
19
|
+
"A table or list needs a compact page-size selector, result range, and boundary-aware navigation.",
|
|
20
|
+
"Direct first-page and last-page navigation is useful enough to opt into alongside the default Previous and Next controls."
|
|
20
21
|
],
|
|
21
22
|
"avoidWhen": [
|
|
22
23
|
"More results append to one continuous list; use the owning component's incremental-loading behavior instead.",
|
|
23
|
-
"
|
|
24
|
+
"The total result count is unknown; use an incremental-loading pattern instead."
|
|
24
25
|
],
|
|
25
26
|
"commonlyComposedWith": [
|
|
26
27
|
"component:ds-table",
|
|
@@ -31,18 +32,20 @@
|
|
|
31
32
|
"The component renders a named navigation region, gives its compact page-size noun a complete accessible label, and gives every icon-only boundary control an explicit page action name.",
|
|
32
33
|
"The visible page-size cluster states the localized noun, selected size, and current-page count of total, for example Rows: [25] 25 of 500. The page position uses compact ‘of’ phrasing, for example 1 of 20. A polite atomic status update retains the complete current range and ‘Page’ phrasing with the localized item noun.",
|
|
33
34
|
"Left and Right Arrow request the adjacent page while focus is within pagination navigation controls. The page-size combobox retains its own arrow-key behavior.",
|
|
34
|
-
"Inactive boundary controls and every control during loading remain non-interactive while retaining their accessible names."
|
|
35
|
+
"Inactive boundary controls and every control during loading remain non-interactive while retaining their accessible names.",
|
|
36
|
+
"showFirstLastButtons adds explicitly named First page and Last page controls; Previous and Next remain present in either mode."
|
|
35
37
|
],
|
|
36
38
|
"states": [
|
|
37
39
|
"pageIndex, numeric effective pageSize, pageSizeMode, totalItems, and pageSizeOptions are controlled inputs. dsChange reports the complete next pagination state, previous index, size, and mode, and whether the request changed the page, page size, or fitted capacity; it never mutates controlled inputs.",
|
|
38
40
|
"Page-size changes request the first page. Out-of-range page indexes and invalid numeric inputs normalize only for presentation; the owner remains authoritative.",
|
|
39
41
|
"Zero results display 0–0 of 0 and Page 1 of 1. A partial final page caps the visible range at totalItems.",
|
|
40
42
|
"loading preserves the full pagination surface, applies aria-busy to the navigation region, and prevents page or page-size requests.",
|
|
41
|
-
"fitToPage appends a Fit to page choice. fitPageSize supplies its effective whole-item capacity; the closed trigger says Fit while the range continues to expose the resulting count. fitToPageInactive keeps that choice visible but unavailable when fitting is not meaningful for the current data shape. Pagination does not measure layouts itself."
|
|
43
|
+
"fitToPage appends a Fit to page choice. fitPageSize supplies its effective whole-item capacity; the closed trigger says Fit while the range continues to expose the resulting count. fitToPageInactive keeps that choice visible but unavailable when fitting is not meaningful for the current data shape. Pagination does not measure layouts itself.",
|
|
44
|
+
"showFirstLastButtons defaults to false. Enable it when direct jumps to both result boundaries materially help the workflow."
|
|
42
45
|
],
|
|
43
46
|
"responsiveBehavior": [
|
|
44
|
-
"The full-width controls remain on one line with noun, selected size, current-page count of total, a vertical divider, page position, and
|
|
45
|
-
"Inside a ds-table container narrower than 900px, pagination hides the visible current-page count and first/last
|
|
47
|
+
"The full-width controls remain on one line with noun, selected size, current-page count of total, a vertical divider, page position, Previous and Next, plus optional First and Last navigation.",
|
|
48
|
+
"Inside a ds-table container narrower than 900px, pagination hides the visible current-page count and any opted-in first/last controls while retaining the page-size noun and selector plus Previous, page position, and Next. The complete current range remains available to assistive technology."
|
|
46
49
|
],
|
|
47
50
|
"frameworkCaveats": {
|
|
48
51
|
"customElements": [
|
|
@@ -187,6 +190,15 @@
|
|
|
187
190
|
"mutable": false,
|
|
188
191
|
"description": "Available page sizes. Assign arrays through JavaScript."
|
|
189
192
|
},
|
|
193
|
+
"showFirstLastButtons": {
|
|
194
|
+
"type": "boolean",
|
|
195
|
+
"resolvedType": "boolean",
|
|
196
|
+
"attribute": "show-first-last-buttons",
|
|
197
|
+
"default": "false",
|
|
198
|
+
"required": false,
|
|
199
|
+
"mutable": false,
|
|
200
|
+
"description": "Include direct first-page and last-page controls."
|
|
201
|
+
},
|
|
190
202
|
"totalItems": {
|
|
191
203
|
"type": "number",
|
|
192
204
|
"resolvedType": "number",
|
|
@@ -334,6 +346,15 @@
|
|
|
334
346
|
"mutable": false,
|
|
335
347
|
"description": "Available page sizes. Assign arrays through JavaScript."
|
|
336
348
|
},
|
|
349
|
+
"showFirstLastButtons": {
|
|
350
|
+
"type": "boolean",
|
|
351
|
+
"resolvedType": "boolean",
|
|
352
|
+
"attribute": "show-first-last-buttons",
|
|
353
|
+
"default": "false",
|
|
354
|
+
"required": false,
|
|
355
|
+
"mutable": false,
|
|
356
|
+
"description": "Include direct first-page and last-page controls."
|
|
357
|
+
},
|
|
337
358
|
"totalItems": {
|
|
338
359
|
"type": "number",
|
|
339
360
|
"resolvedType": "number",
|
|
@@ -409,7 +430,7 @@
|
|
|
409
430
|
},
|
|
410
431
|
{
|
|
411
432
|
"path": "src/wc/components/Pagination/Pagination.tsx",
|
|
412
|
-
"content": "import { Component, Element, Event, EventEmitter, h, Host, Prop, State } from '@stencil/core';\nimport { observeTableCaptionCompact } from '../../utils/table-caption-compact';\nimport { resolvePaginationState } from './pagination-model';\nimport type {\n PaginationChangeDetail,\n PaginationPageSizeMode,\n} from './pagination-types';\n\nlet paginationId = 0;\n\n@Component({\n tag: 'ds-pagination',\n styleUrl: 'Pagination.css',\n scoped: true,\n})\nexport class Pagination {\n @Element() el!: HTMLElement;\n\n /** Controlled zero-based page index. */\n @Prop() pageIndex: number = 0;\n /** Controlled number of top-level items on a full page. */\n @Prop() pageSize: number = 25;\n /** Whether pageSize is a fixed choice or a measured Fit snapshot. */\n @Prop() pageSizeMode: PaginationPageSizeMode = 'fixed';\n /** Controlled total number of top-level items across every page. */\n @Prop() totalItems: number = 0;\n /** Available page sizes. Assign arrays through JavaScript. */\n @Prop() pageSizeOptions: number[] = [25, 50, 100, 200];\n /** Include the Fit to page choice. */\n @Prop() fitToPage: boolean = false;\n /** Keep the Fit to page choice visible but unavailable. */\n @Prop() fitToPageInactive: boolean = false;\n /** Effective whole-item capacity to request when Fit is selected. */\n @Prop() fitPageSize: number | undefined;\n /** Full choice-list label for Fit. */\n @Prop() fitPageSizeLabel: string = 'Fit to page';\n /** Compact closed-trigger label while Fit is selected. */\n @Prop() fitPageSizeTriggerLabel: string = 'Fit';\n /** Localized plural noun used by assistive range announcements. */\n @Prop() itemLabel: string = 'items';\n /** Visible page-size control label. */\n @Prop() pageSizeLabel: string = 'Items';\n /** Accessible page-size control label. Defaults to “{pageSizeLabel} per page”. */\n @Prop() pageSizeAriaLabel: string | undefined;\n /** Accessible name for the pagination navigation region. */\n @Prop() label: string = 'Pagination';\n /** Prevent interaction while the owner replaces the current data page. */\n @Prop() loading: boolean = false;\n\n /** Emits the complete next controlled state after a page or page-size request. */\n @Event() dsChange!: EventEmitter<PaginationChangeDetail>;\n\n @State() private tableCompact = false;\n\n private readonly pageSizeControlId = `ds-pagination-${++paginationId}-page-size`;\n private tableCompactDisconnect: (() => void) | undefined;\n private hasLoaded = false;\n\n componentDidLoad(): void {\n this.hasLoaded = true;\n this.syncTableCompactObserver();\n }\n\n connectedCallback(): void {\n if (this.hasLoaded) this.syncTableCompactObserver();\n }\n\n disconnectedCallback(): void {\n this.tableCompactDisconnect?.();\n this.tableCompactDisconnect = undefined;\n }\n\n private syncTableCompactObserver(): void {\n this.tableCompactDisconnect?.();\n this.tableCompactDisconnect = observeTableCaptionCompact(this.el, compact => {\n if (this.tableCompact !== compact) this.tableCompact = compact;\n });\n }\n\n private requestPage(pageIndex: number): void {\n const state = this.resolvedState;\n if (this.loading || pageIndex === state.pageIndex || pageIndex < 0 || pageIndex >= state.totalPages) {\n return;\n }\n this.dsChange.emit({\n pageIndex,\n pageSize: state.pageSize,\n pageSizeMode: state.pageSizeMode,\n totalItems: state.totalItems,\n pageSizeOptions: state.pageSizeOptions,\n fitToPage: this.fitToPage,\n fitToPageInactive: this.fitToPageInactive,\n fitPageSize: this.resolvedFitPageSize,\n fitPageSizeLabel: this.fitPageSizeLabel,\n fitPageSizeTriggerLabel: this.fitPageSizeTriggerLabel,\n itemLabel: this.itemLabel,\n pageSizeLabel: this.pageSizeLabel,\n ariaLabel: this.label,\n previousPageIndex: state.pageIndex,\n previousPageSize: state.pageSize,\n previousPageSizeMode: state.pageSizeMode,\n reason: 'page',\n });\n }\n\n private requestPageSize(value: string | string[]): void {\n if (\n this.loading ||\n typeof value !== 'string' ||\n (value === 'fit' && this.fitToPageInactive)\n ) return;\n const state = this.resolvedState;\n const nextMode: PaginationPageSizeMode = value === 'fit' ? 'fit' : 'fixed';\n const pageSize = nextMode === 'fit' ? this.resolvedFitPageSize : Number(value);\n if (!pageSize || (pageSize === state.pageSize && nextMode === state.pageSizeMode)) return;\n this.dsChange.emit({\n pageIndex: 0,\n pageSize: Math.trunc(pageSize),\n pageSizeMode: nextMode,\n totalItems: state.totalItems,\n pageSizeOptions: state.pageSizeOptions,\n fitToPage: this.fitToPage,\n fitToPageInactive: this.fitToPageInactive,\n fitPageSize: this.resolvedFitPageSize,\n fitPageSizeLabel: this.fitPageSizeLabel,\n fitPageSizeTriggerLabel: this.fitPageSizeTriggerLabel,\n itemLabel: this.itemLabel,\n pageSizeLabel: this.pageSizeLabel,\n ariaLabel: this.label,\n previousPageIndex: state.pageIndex,\n previousPageSize: state.pageSize,\n previousPageSizeMode: state.pageSizeMode,\n reason: 'page-size',\n });\n }\n\n private handleKeyDown(event: KeyboardEvent): void {\n if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {\n return;\n }\n const fromChoiceControl = event.composedPath().some(node =>\n node instanceof HTMLElement &&\n (node.tagName === 'DS-SELECT' || ['INPUT', 'SELECT', 'TEXTAREA'].includes(node.tagName)),\n );\n if (fromChoiceControl || (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')) return;\n const state = this.resolvedState;\n const pageIndex = event.key === 'ArrowLeft' ? state.pageIndex - 1 : state.pageIndex + 1;\n if (this.loading || pageIndex < 0 || pageIndex >= state.totalPages) return;\n event.preventDefault();\n this.requestPage(pageIndex);\n }\n\n private get resolvedFitPageSize(): number | undefined {\n return Number.isFinite(this.fitPageSize) && Number(this.fitPageSize) > 0\n ? Math.trunc(Number(this.fitPageSize))\n : undefined;\n }\n\n private get resolvedState() {\n return resolvePaginationState({\n pageIndex: this.pageIndex,\n pageSize: this.pageSize,\n pageSizeMode: this.pageSizeMode,\n totalItems: this.totalItems,\n pageSizeOptions: this.pageSizeOptions,\n });\n }\n\n render() {\n const state = this.resolvedState;\n const atStart = state.pageIndex === 0;\n const atEnd = state.pageIndex === state.totalPages - 1;\n const visibleItems = state.totalItems === 0 ? 0 : state.lastItem - state.firstItem + 1;\n const total = `${visibleItems} of ${state.totalItems}`;\n const page = `${state.pageIndex + 1} of ${state.totalPages}`;\n const announcedRange = `${state.firstItem}–${state.lastItem} of ${state.totalItems}`;\n const announcedPage = `Page ${state.pageIndex + 1} of ${state.totalPages}`;\n const pageSizeAriaLabel = this.pageSizeAriaLabel ?? `${this.pageSizeLabel} per page`;\n const options: Array<{ label: string; value: string; isInactive?: boolean }> =\n state.pageSizeOptions.map(value => ({ label: String(value), value: String(value) }));\n if (this.fitToPage) {\n options.push({\n label: this.fitPageSizeLabel,\n value: 'fit',\n isInactive: this.fitToPageInactive || this.resolvedFitPageSize === undefined,\n });\n }\n\n return (\n <Host>\n <nav\n class={{\n pagination: true,\n 'pagination--table-compact': this.tableCompact,\n }}\n aria-label={this.label}\n aria-busy={this.loading ? 'true' : undefined}\n onKeyDown={event => this.handleKeyDown(event)}\n >\n <div class=\"pagination__page-size\">\n <ds-text\n class=\"pagination__label\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n aria-hidden=\"true\"\n >\n {this.pageSizeLabel}:\n </ds-text>\n <span id={`${this.pageSizeControlId}-label`} class=\"ds-visually-hidden\">\n {pageSizeAriaLabel}\n </span>\n <ds-select\n class=\"pagination__page-size-select\"\n size=\"md\"\n inputId={this.pageSizeControlId}\n ariaLabelledby={`${this.pageSizeControlId}-label`}\n options={options}\n value={state.pageSizeMode === 'fit' ? 'fit' : String(state.pageSize)}\n triggerLabel={state.pageSizeMode === 'fit' ? this.fitPageSizeTriggerLabel : undefined}\n indicator=\"up-down\"\n allowClear={false}\n activeFill={false}\n hasBorder={false}\n isInactive={this.loading}\n onDsChange={event => {\n event.stopPropagation();\n this.requestPageSize(event.detail);\n }}\n />\n <ds-text\n class=\"pagination__total\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n fontFeature=\"tabular-nums\"\n >\n {total}\n </ds-text>\n </div>\n <ds-divider\n class=\"pagination__divider\"\n orientation=\"vertical\"\n length=\"var(--dimension-size-250)\"\n />\n <div class=\"pagination__navigation\">\n <ds-button-unfilled\n class=\"pagination__boundary\"\n variant=\"icon\"\n size=\"md\"\n icon=\"ChevronLeftDouble\"\n ariaLabel=\"First page\"\n hasBorder={false}\n isInactive={this.loading || atStart}\n onDsClick={() => this.requestPage(0)}\n />\n <ds-button-unfilled\n variant=\"icon\"\n size=\"md\"\n icon=\"ChevronLeft\"\n ariaLabel=\"Previous page\"\n hasBorder={false}\n isInactive={this.loading || atStart}\n onDsClick={() => this.requestPage(state.pageIndex - 1)}\n />\n <ds-text\n class=\"pagination__page\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n fontFeature=\"tabular-nums\"\n >\n {page}\n </ds-text>\n <ds-button-unfilled\n variant=\"icon\"\n size=\"md\"\n icon=\"ChevronRight\"\n ariaLabel=\"Next page\"\n hasBorder={false}\n isInactive={this.loading || atEnd}\n onDsClick={() => this.requestPage(state.pageIndex + 1)}\n />\n <ds-button-unfilled\n class=\"pagination__boundary\"\n variant=\"icon\"\n size=\"md\"\n icon=\"ChevronRightDouble\"\n ariaLabel=\"Last page\"\n hasBorder={false}\n isInactive={this.loading || atEnd}\n onDsClick={() => this.requestPage(state.totalPages - 1)}\n />\n </div>\n <span class=\"ds-visually-hidden\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">\n {announcedRange} {this.itemLabel}. {announcedPage}.\n </span>\n </nav>\n </Host>\n );\n }\n}\n",
|
|
433
|
+
"content": "import { Component, Element, Event, EventEmitter, h, Host, Prop, State } from '@stencil/core';\nimport { observeTableCaptionCompact } from '../../utils/table-caption-compact';\nimport { resolvePaginationState } from './pagination-model';\nimport type {\n PaginationChangeDetail,\n PaginationPageSizeMode,\n} from './pagination-types';\n\nlet paginationId = 0;\n\n@Component({\n tag: 'ds-pagination',\n styleUrl: 'Pagination.css',\n scoped: true,\n})\nexport class Pagination {\n @Element() el!: HTMLElement;\n\n /** Controlled zero-based page index. */\n @Prop() pageIndex: number = 0;\n /** Controlled number of top-level items on a full page. */\n @Prop() pageSize: number = 25;\n /** Whether pageSize is a fixed choice or a measured Fit snapshot. */\n @Prop() pageSizeMode: PaginationPageSizeMode = 'fixed';\n /** Controlled total number of top-level items across every page. */\n @Prop() totalItems: number = 0;\n /** Available page sizes. Assign arrays through JavaScript. */\n @Prop() pageSizeOptions: number[] = [25, 50, 100, 200];\n /** Include the Fit to page choice. */\n @Prop() fitToPage: boolean = false;\n /** Keep the Fit to page choice visible but unavailable. */\n @Prop() fitToPageInactive: boolean = false;\n /** Effective whole-item capacity to request when Fit is selected. */\n @Prop() fitPageSize: number | undefined;\n /** Full choice-list label for Fit. */\n @Prop() fitPageSizeLabel: string = 'Fit to page';\n /** Compact closed-trigger label while Fit is selected. */\n @Prop() fitPageSizeTriggerLabel: string = 'Fit';\n /** Localized plural noun used by assistive range announcements. */\n @Prop() itemLabel: string = 'items';\n /** Visible page-size control label. */\n @Prop() pageSizeLabel: string = 'Items';\n /** Accessible page-size control label. Defaults to “{pageSizeLabel} per page”. */\n @Prop() pageSizeAriaLabel: string | undefined;\n /** Accessible name for the pagination navigation region. */\n @Prop() label: string = 'Pagination';\n /** Include direct first-page and last-page controls. */\n @Prop() showFirstLastButtons: boolean = false;\n /** Prevent interaction while the owner replaces the current data page. */\n @Prop() loading: boolean = false;\n\n /** Emits the complete next controlled state after a page or page-size request. */\n @Event() dsChange!: EventEmitter<PaginationChangeDetail>;\n\n @State() private tableCompact = false;\n\n private readonly pageSizeControlId = `ds-pagination-${++paginationId}-page-size`;\n private tableCompactDisconnect: (() => void) | undefined;\n private hasLoaded = false;\n\n componentDidLoad(): void {\n this.hasLoaded = true;\n this.syncTableCompactObserver();\n }\n\n connectedCallback(): void {\n if (this.hasLoaded) this.syncTableCompactObserver();\n }\n\n disconnectedCallback(): void {\n this.tableCompactDisconnect?.();\n this.tableCompactDisconnect = undefined;\n }\n\n private syncTableCompactObserver(): void {\n this.tableCompactDisconnect?.();\n this.tableCompactDisconnect = observeTableCaptionCompact(this.el, compact => {\n if (this.tableCompact !== compact) this.tableCompact = compact;\n });\n }\n\n private requestPage(pageIndex: number): void {\n const state = this.resolvedState;\n if (this.loading || pageIndex === state.pageIndex || pageIndex < 0 || pageIndex >= state.totalPages) {\n return;\n }\n this.dsChange.emit({\n pageIndex,\n pageSize: state.pageSize,\n pageSizeMode: state.pageSizeMode,\n totalItems: state.totalItems,\n pageSizeOptions: state.pageSizeOptions,\n fitToPage: this.fitToPage,\n fitToPageInactive: this.fitToPageInactive,\n fitPageSize: this.resolvedFitPageSize,\n fitPageSizeLabel: this.fitPageSizeLabel,\n fitPageSizeTriggerLabel: this.fitPageSizeTriggerLabel,\n itemLabel: this.itemLabel,\n pageSizeLabel: this.pageSizeLabel,\n ariaLabel: this.label,\n showFirstLastButtons: this.showFirstLastButtons,\n previousPageIndex: state.pageIndex,\n previousPageSize: state.pageSize,\n previousPageSizeMode: state.pageSizeMode,\n reason: 'page',\n });\n }\n\n private requestPageSize(value: string | string[]): void {\n if (\n this.loading ||\n typeof value !== 'string' ||\n (value === 'fit' && this.fitToPageInactive)\n ) return;\n const state = this.resolvedState;\n const nextMode: PaginationPageSizeMode = value === 'fit' ? 'fit' : 'fixed';\n const pageSize = nextMode === 'fit' ? this.resolvedFitPageSize : Number(value);\n if (!pageSize || (pageSize === state.pageSize && nextMode === state.pageSizeMode)) return;\n this.dsChange.emit({\n pageIndex: 0,\n pageSize: Math.trunc(pageSize),\n pageSizeMode: nextMode,\n totalItems: state.totalItems,\n pageSizeOptions: state.pageSizeOptions,\n fitToPage: this.fitToPage,\n fitToPageInactive: this.fitToPageInactive,\n fitPageSize: this.resolvedFitPageSize,\n fitPageSizeLabel: this.fitPageSizeLabel,\n fitPageSizeTriggerLabel: this.fitPageSizeTriggerLabel,\n itemLabel: this.itemLabel,\n pageSizeLabel: this.pageSizeLabel,\n ariaLabel: this.label,\n showFirstLastButtons: this.showFirstLastButtons,\n previousPageIndex: state.pageIndex,\n previousPageSize: state.pageSize,\n previousPageSizeMode: state.pageSizeMode,\n reason: 'page-size',\n });\n }\n\n private handleKeyDown(event: KeyboardEvent): void {\n if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {\n return;\n }\n const fromChoiceControl = event.composedPath().some(node =>\n node instanceof HTMLElement &&\n (node.tagName === 'DS-SELECT' || ['INPUT', 'SELECT', 'TEXTAREA'].includes(node.tagName)),\n );\n if (fromChoiceControl || (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')) return;\n const state = this.resolvedState;\n const pageIndex = event.key === 'ArrowLeft' ? state.pageIndex - 1 : state.pageIndex + 1;\n if (this.loading || pageIndex < 0 || pageIndex >= state.totalPages) return;\n event.preventDefault();\n this.requestPage(pageIndex);\n }\n\n private get resolvedFitPageSize(): number | undefined {\n return Number.isFinite(this.fitPageSize) && Number(this.fitPageSize) > 0\n ? Math.trunc(Number(this.fitPageSize))\n : undefined;\n }\n\n private get resolvedState() {\n return resolvePaginationState({\n pageIndex: this.pageIndex,\n pageSize: this.pageSize,\n pageSizeMode: this.pageSizeMode,\n totalItems: this.totalItems,\n pageSizeOptions: this.pageSizeOptions,\n });\n }\n\n render() {\n const state = this.resolvedState;\n const atStart = state.pageIndex === 0;\n const atEnd = state.pageIndex === state.totalPages - 1;\n const visibleItems = state.totalItems === 0 ? 0 : state.lastItem - state.firstItem + 1;\n const total = `${visibleItems} of ${state.totalItems}`;\n const page = `${state.pageIndex + 1} of ${state.totalPages}`;\n const announcedRange = `${state.firstItem}–${state.lastItem} of ${state.totalItems}`;\n const announcedPage = `Page ${state.pageIndex + 1} of ${state.totalPages}`;\n const pageSizeAriaLabel = this.pageSizeAriaLabel ?? `${this.pageSizeLabel} per page`;\n const options: Array<{ label: string; value: string; isInactive?: boolean }> =\n state.pageSizeOptions.map(value => ({ label: String(value), value: String(value) }));\n if (this.fitToPage) {\n options.push({\n label: this.fitPageSizeLabel,\n value: 'fit',\n isInactive: this.fitToPageInactive || this.resolvedFitPageSize === undefined,\n });\n }\n\n return (\n <Host>\n <nav\n class={{\n pagination: true,\n 'pagination--table-compact': this.tableCompact,\n }}\n aria-label={this.label}\n aria-busy={this.loading ? 'true' : undefined}\n onKeyDown={event => this.handleKeyDown(event)}\n >\n <div class=\"pagination__page-size\">\n <ds-text\n class=\"pagination__label\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n aria-hidden=\"true\"\n >\n {this.pageSizeLabel}:\n </ds-text>\n <span id={`${this.pageSizeControlId}-label`} class=\"ds-visually-hidden\">\n {pageSizeAriaLabel}\n </span>\n <ds-select\n class=\"pagination__page-size-select\"\n size=\"md\"\n inputId={this.pageSizeControlId}\n ariaLabelledby={`${this.pageSizeControlId}-label`}\n options={options}\n value={state.pageSizeMode === 'fit' ? 'fit' : String(state.pageSize)}\n triggerLabel={state.pageSizeMode === 'fit' ? this.fitPageSizeTriggerLabel : undefined}\n indicator=\"up-down\"\n allowClear={false}\n activeFill={false}\n hasBorder={false}\n isInactive={this.loading}\n onDsChange={event => {\n event.stopPropagation();\n this.requestPageSize(event.detail);\n }}\n />\n <ds-text\n class=\"pagination__total\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n fontFeature=\"tabular-nums\"\n >\n {total}\n </ds-text>\n </div>\n <ds-divider\n class=\"pagination__divider\"\n orientation=\"vertical\"\n length=\"var(--dimension-size-250)\"\n />\n <div class=\"pagination__navigation\">\n {this.showFirstLastButtons ? (\n <ds-button-unfilled\n class=\"pagination__boundary\"\n variant=\"icon\"\n size=\"md\"\n icon=\"ChevronLeftDouble\"\n ariaLabel=\"First page\"\n hasBorder={false}\n isInactive={this.loading || atStart}\n onDsClick={() => this.requestPage(0)}\n />\n ) : null}\n <ds-button-unfilled\n variant=\"icon\"\n size=\"md\"\n icon=\"ChevronLeft\"\n ariaLabel=\"Previous page\"\n hasBorder={false}\n isInactive={this.loading || atStart}\n onDsClick={() => this.requestPage(state.pageIndex - 1)}\n />\n <ds-text\n class=\"pagination__page\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n fontFeature=\"tabular-nums\"\n >\n {page}\n </ds-text>\n <ds-button-unfilled\n variant=\"icon\"\n size=\"md\"\n icon=\"ChevronRight\"\n ariaLabel=\"Next page\"\n hasBorder={false}\n isInactive={this.loading || atEnd}\n onDsClick={() => this.requestPage(state.pageIndex + 1)}\n />\n {this.showFirstLastButtons ? (\n <ds-button-unfilled\n class=\"pagination__boundary\"\n variant=\"icon\"\n size=\"md\"\n icon=\"ChevronRightDouble\"\n ariaLabel=\"Last page\"\n hasBorder={false}\n isInactive={this.loading || atEnd}\n onDsClick={() => this.requestPage(state.totalPages - 1)}\n />\n ) : null}\n </div>\n <span class=\"ds-visually-hidden\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">\n {announcedRange} {this.itemLabel}. {announcedPage}.\n </span>\n </nav>\n </Host>\n );\n }\n}\n",
|
|
413
434
|
"type": "registry:ui"
|
|
414
435
|
},
|
|
415
436
|
{
|
|
@@ -419,7 +440,7 @@
|
|
|
419
440
|
},
|
|
420
441
|
{
|
|
421
442
|
"path": "src/wc/components/Pagination/pagination-types.ts",
|
|
422
|
-
"content": "export type PaginationChangeReason = 'page' | 'page-size' | 'fit';\nexport type PaginationPageSizeMode = 'fixed' | 'fit';\n\n/** Controlled pagination state shared by Pagination and pagination-aware consumers. */\nexport interface PaginationState {\n /** Zero-based page index. */\n pageIndex: number;\n /** Number of top-level items on a full page. */\n pageSize: number;\n /** Whether pageSize is a chosen fixed value or a measured Fit snapshot. */\n pageSizeMode?: PaginationPageSizeMode;\n /** Total number of top-level items across every page. */\n totalItems: number;\n /** Available page sizes. Defaults to 25, 50, 100, and 200. */\n pageSizeOptions?: number[];\n /** Include a Fit to page option. The owner supplies the effective fitted size. */\n fitToPage?: boolean;\n /** Keep the Fit to page option visible but unavailable for the current data shape. */\n fitToPageInactive?: boolean;\n /** Effective page size proposed when Fit to page is selected. */\n fitPageSize?: number;\n /** Full option label for Fit in the open choice list. */\n fitPageSizeLabel?: string;\n /** Compact trigger label while Fit is selected. */\n fitPageSizeTriggerLabel?: string;\n /** Localized plural noun used by assistive range announcements. */\n itemLabel?: string;\n /** Visible label for the page-size selector. */\n pageSizeLabel?: string;\n /** Accessible name for the pagination navigation region. */\n ariaLabel?: string;\n}\n\n/** Complete next controlled state emitted for a page or page-size request. */\nexport interface PaginationChangeDetail extends PaginationState {\n previousPageIndex: number;\n previousPageSize: number;\n previousPageSizeMode: PaginationPageSizeMode;\n reason: PaginationChangeReason;\n}\n",
|
|
443
|
+
"content": "export type PaginationChangeReason = 'page' | 'page-size' | 'fit';\nexport type PaginationPageSizeMode = 'fixed' | 'fit';\n\n/** Controlled pagination state shared by Pagination and pagination-aware consumers. */\nexport interface PaginationState {\n /** Zero-based page index. */\n pageIndex: number;\n /** Number of top-level items on a full page. */\n pageSize: number;\n /** Whether pageSize is a chosen fixed value or a measured Fit snapshot. */\n pageSizeMode?: PaginationPageSizeMode;\n /** Total number of top-level items across every page. */\n totalItems: number;\n /** Available page sizes. Defaults to 25, 50, 100, and 200. */\n pageSizeOptions?: number[];\n /** Include a Fit to page option. The owner supplies the effective fitted size. */\n fitToPage?: boolean;\n /** Keep the Fit to page option visible but unavailable for the current data shape. */\n fitToPageInactive?: boolean;\n /** Effective page size proposed when Fit to page is selected. */\n fitPageSize?: number;\n /** Full option label for Fit in the open choice list. */\n fitPageSizeLabel?: string;\n /** Compact trigger label while Fit is selected. */\n fitPageSizeTriggerLabel?: string;\n /** Localized plural noun used by assistive range announcements. */\n itemLabel?: string;\n /** Visible label for the page-size selector. */\n pageSizeLabel?: string;\n /** Accessible name for the pagination navigation region. */\n ariaLabel?: string;\n /** Include direct first-page and last-page controls around Previous and Next. */\n showFirstLastButtons?: boolean;\n}\n\n/** Complete next controlled state emitted for a page or page-size request. */\nexport interface PaginationChangeDetail extends PaginationState {\n previousPageIndex: number;\n previousPageSize: number;\n previousPageSizeMode: PaginationPageSizeMode;\n reason: PaginationChangeReason;\n}\n",
|
|
423
444
|
"type": "registry:ui"
|
|
424
445
|
}
|
|
425
446
|
]
|
|
@@ -6066,7 +6066,7 @@
|
|
|
6066
6066
|
{
|
|
6067
6067
|
"name": "pagination",
|
|
6068
6068
|
"title": "Pagination",
|
|
6069
|
-
"description": "Controlled fixed or fitted page-size, compact range, and first/
|
|
6069
|
+
"description": "Controlled fixed or fitted page-size, compact range, adjacent-page navigation, and optional first/last controls for application-owned data windows.",
|
|
6070
6070
|
"type": "registry:ui",
|
|
6071
6071
|
"meta": {
|
|
6072
6072
|
"library": "@ds-mo/ui",
|
|
@@ -6077,14 +6077,15 @@
|
|
|
6077
6077
|
"intent": {
|
|
6078
6078
|
"audience": "general",
|
|
6079
6079
|
"status": "stable",
|
|
6080
|
-
"summary": "Controlled fixed or fitted page-size, compact range, and first/
|
|
6080
|
+
"summary": "Controlled fixed or fitted page-size, compact range, adjacent-page navigation, and optional first/last controls for application-owned data windows.",
|
|
6081
6081
|
"useWhen": [
|
|
6082
6082
|
"A result set is divided into discrete pages and the application owns fetching or slicing each page.",
|
|
6083
|
-
"A table or list needs a compact page-size selector, result range, and boundary-aware navigation."
|
|
6083
|
+
"A table or list needs a compact page-size selector, result range, and boundary-aware navigation.",
|
|
6084
|
+
"Direct first-page and last-page navigation is useful enough to opt into alongside the default Previous and Next controls."
|
|
6084
6085
|
],
|
|
6085
6086
|
"avoidWhen": [
|
|
6086
6087
|
"More results append to one continuous list; use the owning component's incremental-loading behavior instead.",
|
|
6087
|
-
"
|
|
6088
|
+
"The total result count is unknown; use an incremental-loading pattern instead."
|
|
6088
6089
|
],
|
|
6089
6090
|
"commonlyComposedWith": [
|
|
6090
6091
|
"component:ds-table",
|
|
@@ -6095,18 +6096,20 @@
|
|
|
6095
6096
|
"The component renders a named navigation region, gives its compact page-size noun a complete accessible label, and gives every icon-only boundary control an explicit page action name.",
|
|
6096
6097
|
"The visible page-size cluster states the localized noun, selected size, and current-page count of total, for example Rows: [25] 25 of 500. The page position uses compact ‘of’ phrasing, for example 1 of 20. A polite atomic status update retains the complete current range and ‘Page’ phrasing with the localized item noun.",
|
|
6097
6098
|
"Left and Right Arrow request the adjacent page while focus is within pagination navigation controls. The page-size combobox retains its own arrow-key behavior.",
|
|
6098
|
-
"Inactive boundary controls and every control during loading remain non-interactive while retaining their accessible names."
|
|
6099
|
+
"Inactive boundary controls and every control during loading remain non-interactive while retaining their accessible names.",
|
|
6100
|
+
"showFirstLastButtons adds explicitly named First page and Last page controls; Previous and Next remain present in either mode."
|
|
6099
6101
|
],
|
|
6100
6102
|
"states": [
|
|
6101
6103
|
"pageIndex, numeric effective pageSize, pageSizeMode, totalItems, and pageSizeOptions are controlled inputs. dsChange reports the complete next pagination state, previous index, size, and mode, and whether the request changed the page, page size, or fitted capacity; it never mutates controlled inputs.",
|
|
6102
6104
|
"Page-size changes request the first page. Out-of-range page indexes and invalid numeric inputs normalize only for presentation; the owner remains authoritative.",
|
|
6103
6105
|
"Zero results display 0–0 of 0 and Page 1 of 1. A partial final page caps the visible range at totalItems.",
|
|
6104
6106
|
"loading preserves the full pagination surface, applies aria-busy to the navigation region, and prevents page or page-size requests.",
|
|
6105
|
-
"fitToPage appends a Fit to page choice. fitPageSize supplies its effective whole-item capacity; the closed trigger says Fit while the range continues to expose the resulting count. fitToPageInactive keeps that choice visible but unavailable when fitting is not meaningful for the current data shape. Pagination does not measure layouts itself."
|
|
6107
|
+
"fitToPage appends a Fit to page choice. fitPageSize supplies its effective whole-item capacity; the closed trigger says Fit while the range continues to expose the resulting count. fitToPageInactive keeps that choice visible but unavailable when fitting is not meaningful for the current data shape. Pagination does not measure layouts itself.",
|
|
6108
|
+
"showFirstLastButtons defaults to false. Enable it when direct jumps to both result boundaries materially help the workflow."
|
|
6106
6109
|
],
|
|
6107
6110
|
"responsiveBehavior": [
|
|
6108
|
-
"The full-width controls remain on one line with noun, selected size, current-page count of total, a vertical divider, page position, and
|
|
6109
|
-
"Inside a ds-table container narrower than 900px, pagination hides the visible current-page count and first/last
|
|
6111
|
+
"The full-width controls remain on one line with noun, selected size, current-page count of total, a vertical divider, page position, Previous and Next, plus optional First and Last navigation.",
|
|
6112
|
+
"Inside a ds-table container narrower than 900px, pagination hides the visible current-page count and any opted-in first/last controls while retaining the page-size noun and selector plus Previous, page position, and Next. The complete current range remains available to assistive technology."
|
|
6110
6113
|
],
|
|
6111
6114
|
"frameworkCaveats": {
|
|
6112
6115
|
"customElements": [
|
|
@@ -6183,6 +6186,10 @@
|
|
|
6183
6186
|
"type": "number[]",
|
|
6184
6187
|
"required": false
|
|
6185
6188
|
},
|
|
6189
|
+
"showFirstLastButtons": {
|
|
6190
|
+
"type": "boolean",
|
|
6191
|
+
"required": false
|
|
6192
|
+
},
|
|
6186
6193
|
"totalItems": {
|
|
6187
6194
|
"type": "number",
|
|
6188
6195
|
"required": false
|