@ds-mo/ui 14.4.0 → 14.4.1

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.
Files changed (54) hide show
  1. package/dist/.package-ready.json +1 -1
  2. package/dist/agent-patterns.json +1 -1
  3. package/dist/agent.json +6 -5
  4. package/dist/components/ds-bar-nav.js +1 -1
  5. package/dist/components/ds-bar-title.js +1 -1
  6. package/dist/components/ds-bar-workflow.js +1 -1
  7. package/dist/components/ds-menu.js +1 -1
  8. package/dist/components/ds-mobile-header.js +1 -1
  9. package/dist/components/ds-mobile-section-switcher.js +1 -1
  10. package/dist/components/ds-pagination.js +1 -1
  11. package/dist/components/ds-panel-nav.js +1 -1
  12. package/dist/components/ds-panel-tool-header.js +1 -1
  13. package/dist/components/ds-panel-tool-search.js +1 -1
  14. package/dist/components/ds-panel-tools.js +1 -1
  15. package/dist/components/ds-shell-app.js +1 -1
  16. package/dist/components/ds-shell-page.js +1 -1
  17. package/dist/components/ds-shell-tools.js +1 -1
  18. package/dist/components/ds-swatch-picker.js +1 -1
  19. package/dist/components/ds-table.js +1 -1
  20. package/dist/components/ds-table.js.map +1 -1
  21. package/dist/components/ds-toast.js +1 -1
  22. package/dist/components/ds-typing-indicator.js +1 -1
  23. package/dist/components/{p-DhDWmMxD.js → p-B2hqUkg-.js} +2 -2
  24. package/dist/components/{p-CbqdWBAC.js → p-BGGm1NB1.js} +2 -2
  25. package/dist/components/{p-b-Dlg3y_.js → p-BXNLaPbw.js} +2 -2
  26. package/dist/components/{p-CW7ML-3h.js → p-Ba1l3Yot.js} +2 -2
  27. package/dist/components/{p-DF52Grw1.js → p-BiIdszv1.js} +2 -2
  28. package/dist/components/{p-RLiackRN.js → p-CXqVVdW2.js} +2 -2
  29. package/dist/components/{p-CKfdvOZ2.js → p-Ccq1RmOb.js} +2 -2
  30. package/dist/components/p-CvpsV-jC.js +2 -0
  31. package/dist/components/p-CvpsV-jC.js.map +1 -0
  32. package/dist/components/{p-CDF_nP74.js → p-DgWGfscR.js} +2 -2
  33. package/dist/components/{p-O3OuOaj3.js → p-DjqZWBnM.js} +2 -2
  34. package/dist/components/{p-ChWnbdFS.js → p-o1v7Choa.js} +2 -2
  35. package/dist/components/{p-BDXMXH_i.js → p-w64TmQd-.js} +2 -2
  36. package/dist/docs/components.json +8 -1
  37. package/dist/mcp-data/registry/pagination.json +5 -4
  38. package/dist/mcp-data/registry/registry.json +5 -4
  39. package/dist/mcp-data/registry/table.json +4 -4
  40. package/dist/types/components/Table/Table.d.ts +1 -0
  41. package/package.json +1 -1
  42. package/dist/components/p-Cc5xgR17.js +0 -2
  43. package/dist/components/p-Cc5xgR17.js.map +0 -1
  44. /package/dist/components/{p-DhDWmMxD.js.map → p-B2hqUkg-.js.map} +0 -0
  45. /package/dist/components/{p-CbqdWBAC.js.map → p-BGGm1NB1.js.map} +0 -0
  46. /package/dist/components/{p-b-Dlg3y_.js.map → p-BXNLaPbw.js.map} +0 -0
  47. /package/dist/components/{p-CW7ML-3h.js.map → p-Ba1l3Yot.js.map} +0 -0
  48. /package/dist/components/{p-DF52Grw1.js.map → p-BiIdszv1.js.map} +0 -0
  49. /package/dist/components/{p-RLiackRN.js.map → p-CXqVVdW2.js.map} +0 -0
  50. /package/dist/components/{p-CKfdvOZ2.js.map → p-Ccq1RmOb.js.map} +0 -0
  51. /package/dist/components/{p-CDF_nP74.js.map → p-DgWGfscR.js.map} +0 -0
  52. /package/dist/components/{p-O3OuOaj3.js.map → p-DjqZWBnM.js.map} +0 -0
  53. /package/dist/components/{p-ChWnbdFS.js.map → p-o1v7Choa.js.map} +0 -0
  54. /package/dist/components/{p-BDXMXH_i.js.map → p-w64TmQd-.js.map} +0 -0
@@ -5961,7 +5961,7 @@
5961
5961
  ],
5962
5962
  "accessibility": [
5963
5963
  "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.",
5964
- "The visible range and page position use compact slash notation with tabular numerals. A polite atomic status update retains complete ‘of’ and ‘Page’ phrasing with the localized item noun.",
5964
+ "The visible page-size cluster states the localized noun, selected size, and total, for example Rows: 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.",
5965
5965
  "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.",
5966
5966
  "Inactive boundary controls and every control during loading remain non-interactive while retaining their accessible names."
5967
5967
  ],
@@ -5973,7 +5973,7 @@
5973
5973
  "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. Pagination does not measure layouts itself."
5974
5974
  ],
5975
5975
  "responsiveBehavior": [
5976
- "The compact controls remain on one line with noun, range, and page slash notation. When inline space is still insufficient, the pagination surface scrolls horizontally rather than wrapping labels away from their controls."
5976
+ "The compact controls remain on one line with noun, selected size, total, a vertical divider, and page position. When inline space is still insufficient, the pagination surface scrolls horizontally rather than wrapping labels away from their controls."
5977
5977
  ],
5978
5978
  "frameworkCaveats": {
5979
5979
  "customElements": [
@@ -6070,6 +6070,7 @@
6070
6070
  ],
6071
6071
  "registryDependencies": [
6072
6072
  "button-unfilled",
6073
+ "divider",
6073
6074
  "select",
6074
6075
  "text"
6075
6076
  ]
@@ -8856,14 +8857,14 @@
8856
8857
  "Interactive rows are focusable as native rows, activate with Enter or Space, and do not steal activation from their nested selection or action controls.",
8857
8858
  "Scrollable table regions become keyboard focusable and named only when overflow exists.",
8858
8859
  "Incremental loading, newly appended row counts, failures, and end-of-results transitions are announced through a polite status region without replacing visible state. In grouped tables, announcements name the independently loading group.",
8859
- "Static and infinite tables may show an optional result summary footer when displayedCount and totalCount are finite. Pagination mode instead renders a named Pagination control with compact slash notation, whose range changes are announced independently with complete phrasing. The footer slot can replace summary copy, while footer-leading and footer-trailing remain available in the same compact-bar anatomy."
8860
+ "Static and infinite tables may show an optional result summary footer when displayedCount and totalCount are finite. Pagination mode instead renders a named Pagination control with a compact selected-size total, vertical divider, and page position; range changes are announced independently with complete phrasing. While focus is anywhere inside a paginated table, unmodified Left and Right Arrow request the adjacent page; editable fields, choice controls, sliders, and modified key combinations retain their own keyboard behavior. The footer slot can replace summary copy, while footer-leading and footer-trailing remain available in the same compact-bar anatomy."
8860
8861
  ],
8861
8862
  "states": [
8862
8863
  "Columns, rows, groups, grouping, sorting, selected row IDs, collapsed group IDs, dataMode, pagination, displayedCount, totalCount, and incremental-loading state are controlled inputs. Events communicate intent and never mutate those inputs. The optional header is one full-width composable table-local surface: the component owns its 48px chrome, 8px inset on every side, and sticky placement while the application owns all internal layout, controls, labels, filtering behavior, and action handling. The footer retains its compact-bar composition: application-owned leading status sits left while a controlled result summary or Pagination aligns right, before any trailing action. Content-sized tables remain natural for sparse or fixed-count results. height bounds the complete header, frame, and footer composition. fitViewport reserves the compact-state composition height in the nearest vertical scrollport while growing the visible table into that reservation as surrounding page chrome collapses.",
8863
8864
  "Grouping supports one level. The application supplies groups in a fixed product-defined order and supplies ordered member rows independently. Group section headers show an emphasized text-body-medium label followed by a middle dot and plain secondary loaded progress, for example Critical · 50 of 166. The loaded value comes from the supplied member rows and the total comes from totalCount. A localized countLabel total phrase remains available for the complete assistive progress announcement. Groups may declare an optional intent (brand, neutral, negative, warning, caution, positive) that paints an LTR gradient from the faint intent surface at the label edge into faint neutral at the trailing edge, plus a bold intent title color. Neutral groups remain solid faint neutral. Section separators are painted inside that viewport-width surface so translucent border tokens composite with each intent background. Internal sections keep their separators; a collapsed or empty section header yields its separator only when it is the table's final rendered item. A trailing visually neutral icon-only ButtonUnfilled uses the 24px md double-inset action recipe and toggles controlled collapsedGroupIds, showing ChevronUp while expanded and ChevronDown while collapsed. While any group is expanded, a ChevronDownUp action uses the xs sort-control recipe widened to a table-owned 24px lane and collapses every group. It appears at the inline-end edge of the trailing column declared with kind action when one exists; otherwise a button-hugging 24px by 16px medium-elevation surface floats at the visible scrollport's header edge so the action remains reachable while member columns scroll beneath it. With stickyHeader enabled, each contained section uses its one real section row as a native sticky header inside its real row-group boundary. The browser therefore owns pinning, push-off, scrolling, and resize layout in one pass without a duplicated lane, active-section state, transforms, or scroll-time geometry. Shared grid tracks retain exact header and member-cell alignment. The section surface also stays pinned to the visible horizontal scrollport and spans the smaller of the visible viewport or table width.",
8864
8865
  "An active member sort toggles only between ascending and descending; activating a different sortable label moves the one sorting state to that key. Applications may still clear the controlled sort state explicitly. Grouping does not introduce another interactive direction.",
8865
8866
  "Compound headers can expose multiple related labels with independent sort keys. Each label owns only its content-width interaction target while the active direction remains in the column's alignment-aware sort lane.",
8866
- "Initial loading defaults to ten row-shaped skeletons and preserves row dividers, column dividers, selection lanes, and sticky boundaries. Each column may declare representative text, multiline text, image, Tag, icon, action, or intentionally blank loading geometry; the table applies the matching resolved-cell padding and track anatomy so the placeholder row has the same height and rhythm as its data row. Columns without a declaration use one text line, while action columns infer an icon action placeholder. Empty, initial error, load-more, retry, loading-more, and terminal bands remain fixed to the visible table width while data columns scroll horizontally beneath them. Incremental loading retains existing rows and supports automatic sentinel, manual load-more, retry, and terminal states. When grouping is active, each expanded group owns its load band at the end of its member rows; internal group load bands keep their bottom divider, while a terminal group load band yields it to the table edge or footer. The global bottom-of-table loader is not rendered.",
8867
+ "Initial loading defaults to ten row-shaped skeletons and preserves row dividers, column dividers, selection lanes, and sticky boundaries. A selectable table renders a checkbox-sized control skeleton in its selection lane so loading geometry matches the real checkbox instead of a generic icon. Each column may declare representative text, multiline text, image, Tag, icon, action, or intentionally blank loading geometry; the table applies the matching resolved-cell padding and track anatomy so the placeholder row has the same height and rhythm as its data row. Columns without a declaration use one text line, while action columns infer an icon action placeholder. Empty, initial error, load-more, retry, loading-more, and terminal bands remain fixed to the visible table width while data columns scroll horizontally beneath them. Incremental loading retains existing rows and supports automatic sentinel, manual load-more, retry, and terminal states. When grouping is active, each expanded group owns its load band at the end of its member rows; internal group load bands keep their bottom divider, while a terminal group load band yields it to the table edge or footer. The global bottom-of-table loader is not rendered.",
8867
8868
  "dataMode selects the top-level window: infinite appends ungrouped rows through dsLoadMore when hasMore is true, and pagination renders the controlled page supplied by the application and emits dsPaginationChange. Infinite mode also covers fixed result sets by leaving hasMore false. In a grouped paginated table, pagination counts and windows only parent groups; each expanded parent can still append its own members through dsGroupLoadMore. The application owns requests, cancellation, cursors, deduplication, slicing, and query identity.",
8868
8869
  "When pagination enables Fit to page on a bounded table, the table proposes the number of complete top-level rows that fit below its column header. Grouped tables count group headers only; member rows never affect capacity. Selecting Fit captures that numeric snapshot. A changed fitIdentity requests one fresh snapshot for a new query shape, while viewport resizing alone does not change the active controlled page size.",
8869
8870
  "Rows opt into complete-row activation with interactive and emit dsRowActivate without owning navigation. Selection paints through the shared selected ::before layer; interactive hover and press paint through the shared ::after layer.",
@@ -42,14 +42,14 @@
42
42
  "Interactive rows are focusable as native rows, activate with Enter or Space, and do not steal activation from their nested selection or action controls.",
43
43
  "Scrollable table regions become keyboard focusable and named only when overflow exists.",
44
44
  "Incremental loading, newly appended row counts, failures, and end-of-results transitions are announced through a polite status region without replacing visible state. In grouped tables, announcements name the independently loading group.",
45
- "Static and infinite tables may show an optional result summary footer when displayedCount and totalCount are finite. Pagination mode instead renders a named Pagination control with compact slash notation, whose range changes are announced independently with complete phrasing. The footer slot can replace summary copy, while footer-leading and footer-trailing remain available in the same compact-bar anatomy."
45
+ "Static and infinite tables may show an optional result summary footer when displayedCount and totalCount are finite. Pagination mode instead renders a named Pagination control with a compact selected-size total, vertical divider, and page position; range changes are announced independently with complete phrasing. While focus is anywhere inside a paginated table, unmodified Left and Right Arrow request the adjacent page; editable fields, choice controls, sliders, and modified key combinations retain their own keyboard behavior. The footer slot can replace summary copy, while footer-leading and footer-trailing remain available in the same compact-bar anatomy."
46
46
  ],
47
47
  "states": [
48
48
  "Columns, rows, groups, grouping, sorting, selected row IDs, collapsed group IDs, dataMode, pagination, displayedCount, totalCount, and incremental-loading state are controlled inputs. Events communicate intent and never mutate those inputs. The optional header is one full-width composable table-local surface: the component owns its 48px chrome, 8px inset on every side, and sticky placement while the application owns all internal layout, controls, labels, filtering behavior, and action handling. The footer retains its compact-bar composition: application-owned leading status sits left while a controlled result summary or Pagination aligns right, before any trailing action. Content-sized tables remain natural for sparse or fixed-count results. height bounds the complete header, frame, and footer composition. fitViewport reserves the compact-state composition height in the nearest vertical scrollport while growing the visible table into that reservation as surrounding page chrome collapses.",
49
49
  "Grouping supports one level. The application supplies groups in a fixed product-defined order and supplies ordered member rows independently. Group section headers show an emphasized text-body-medium label followed by a middle dot and plain secondary loaded progress, for example Critical · 50 of 166. The loaded value comes from the supplied member rows and the total comes from totalCount. A localized countLabel total phrase remains available for the complete assistive progress announcement. Groups may declare an optional intent (brand, neutral, negative, warning, caution, positive) that paints an LTR gradient from the faint intent surface at the label edge into faint neutral at the trailing edge, plus a bold intent title color. Neutral groups remain solid faint neutral. Section separators are painted inside that viewport-width surface so translucent border tokens composite with each intent background. Internal sections keep their separators; a collapsed or empty section header yields its separator only when it is the table's final rendered item. A trailing visually neutral icon-only ButtonUnfilled uses the 24px md double-inset action recipe and toggles controlled collapsedGroupIds, showing ChevronUp while expanded and ChevronDown while collapsed. While any group is expanded, a ChevronDownUp action uses the xs sort-control recipe widened to a table-owned 24px lane and collapses every group. It appears at the inline-end edge of the trailing column declared with kind action when one exists; otherwise a button-hugging 24px by 16px medium-elevation surface floats at the visible scrollport's header edge so the action remains reachable while member columns scroll beneath it. With stickyHeader enabled, each contained section uses its one real section row as a native sticky header inside its real row-group boundary. The browser therefore owns pinning, push-off, scrolling, and resize layout in one pass without a duplicated lane, active-section state, transforms, or scroll-time geometry. Shared grid tracks retain exact header and member-cell alignment. The section surface also stays pinned to the visible horizontal scrollport and spans the smaller of the visible viewport or table width.",
50
50
  "An active member sort toggles only between ascending and descending; activating a different sortable label moves the one sorting state to that key. Applications may still clear the controlled sort state explicitly. Grouping does not introduce another interactive direction.",
51
51
  "Compound headers can expose multiple related labels with independent sort keys. Each label owns only its content-width interaction target while the active direction remains in the column's alignment-aware sort lane.",
52
- "Initial loading defaults to ten row-shaped skeletons and preserves row dividers, column dividers, selection lanes, and sticky boundaries. Each column may declare representative text, multiline text, image, Tag, icon, action, or intentionally blank loading geometry; the table applies the matching resolved-cell padding and track anatomy so the placeholder row has the same height and rhythm as its data row. Columns without a declaration use one text line, while action columns infer an icon action placeholder. Empty, initial error, load-more, retry, loading-more, and terminal bands remain fixed to the visible table width while data columns scroll horizontally beneath them. Incremental loading retains existing rows and supports automatic sentinel, manual load-more, retry, and terminal states. When grouping is active, each expanded group owns its load band at the end of its member rows; internal group load bands keep their bottom divider, while a terminal group load band yields it to the table edge or footer. The global bottom-of-table loader is not rendered.",
52
+ "Initial loading defaults to ten row-shaped skeletons and preserves row dividers, column dividers, selection lanes, and sticky boundaries. A selectable table renders a checkbox-sized control skeleton in its selection lane so loading geometry matches the real checkbox instead of a generic icon. Each column may declare representative text, multiline text, image, Tag, icon, action, or intentionally blank loading geometry; the table applies the matching resolved-cell padding and track anatomy so the placeholder row has the same height and rhythm as its data row. Columns without a declaration use one text line, while action columns infer an icon action placeholder. Empty, initial error, load-more, retry, loading-more, and terminal bands remain fixed to the visible table width while data columns scroll horizontally beneath them. Incremental loading retains existing rows and supports automatic sentinel, manual load-more, retry, and terminal states. When grouping is active, each expanded group owns its load band at the end of its member rows; internal group load bands keep their bottom divider, while a terminal group load band yields it to the table edge or footer. The global bottom-of-table loader is not rendered.",
53
53
  "dataMode selects the top-level window: infinite appends ungrouped rows through dsLoadMore when hasMore is true, and pagination renders the controlled page supplied by the application and emits dsPaginationChange. Infinite mode also covers fixed result sets by leaving hasMore false. In a grouped paginated table, pagination counts and windows only parent groups; each expanded parent can still append its own members through dsGroupLoadMore. The application owns requests, cancellation, cursors, deduplication, slicing, and query identity.",
54
54
  "When pagination enables Fit to page on a bounded table, the table proposes the number of complete top-level rows that fit below its column header. Grouped tables count group headers only; member rows never affect capacity. Selecting Fit captures that numeric snapshot. A changed fitIdentity requests one fresh snapshot for a new query shape, while viewport resizing alone does not change the active controlled page size.",
55
55
  "Rows opt into complete-row activation with interactive and emit dsRowActivate without owning navigation. Selection paints through the shared selected ::before layer; interactive hover and press paint through the shared ::after layer.",
@@ -1049,12 +1049,12 @@
1049
1049
  "files": [
1050
1050
  {
1051
1051
  "path": "src/wc/components/Table/Table.css",
1052
- "content": "@import '../../styles/table.css';\n@import '../../styles/control-elevation.css';\n@import '../../utils/chrome-header.css';\n@import '../../utils/control-density.css';\n\n:host {\n display: block;\n box-sizing: border-box;\n min-width: 0;\n max-width: 100%;\n}\n\n:host(.table-host--bounded) {\n min-block-size: 0;\n}\n\n:host(.table-host--fixed-height) {\n block-size: var(--_table-fixed-block-size);\n}\n\n:host(.table-host--viewport-fit) {\n block-size: var(--_table-viewport-fit-reserved-block-size, auto);\n min-block-size: var(--_table-viewport-fit-reserved-block-size, auto);\n}\n\n/*\n * The public table recipe stays at zero specificity for product-owned native\n * markup. Inside the component, these classes sit on ds-text hosts whose reset\n * intentionally owns display, padding, and border with higher specificity.\n * Reassert only those collided properties so component geometry cannot depend\n * on whether ds-text or ds-table happens to hydrate first.\n */\nds-text.ds-table__header-label-box {\n padding-inline: var(--dimension-space-025);\n}\n\nds-text.ds-table__cell-track {\n display: flex;\n}\n\nds-text.ds-table__cell-track--text {\n padding-inline: var(--_table-cell-track-label-inset);\n}\n\nds-text.ds-table__cell-secondary {\n padding-block: var(--dimension-space-025);\n}\n\n.ds-table__cell--primary-text ds-text.ds-table__cell-secondary {\n padding-block: 0;\n}\n\n.ds-table__cell-tag-stack--tag-with-text ds-text.ds-table__cell-tag-text {\n padding-inline: var(--_table-cell-track-padding-inline);\n padding-block: var(--dimension-space-025);\n}\n\n.ds-table__cell-tag-stack--text-with-tag ds-text.ds-table__cell-tag-text {\n padding-inline: var(--_table-cell-track-label-inset);\n}\n\nds-text.ds-table__load-content {\n display: inline-flex;\n}\n\nds-text.ds-table__bar-text {\n display: inline-flex;\n width: auto;\n max-width: 100%;\n padding-inline: var(--ds-control-label-inset, var(--dimension-space-025));\n}\n\nds-text.ds-table__footer-summary {\n margin-inline-start: auto;\n}\n\n.ds-table__bar-trailing ds-pagination {\n max-inline-size: 100%;\n}\n\n.ds-table__bar-status slot::slotted(*) {\n width: auto;\n max-width: 100%;\n min-width: 0;\n}\n\nds-skeleton.ds-table__skeleton-image-shape {\n display: flex;\n width: 100%;\n height: 100%;\n --ds-control-height: 100%;\n}\n",
1052
+ "content": "@import '../../styles/table.css';\n@import '../../styles/control-elevation.css';\n@import '../../utils/chrome-header.css';\n@import '../../utils/control-density.css';\n\n:host {\n display: block;\n box-sizing: border-box;\n min-width: 0;\n max-width: 100%;\n}\n\n:host(.table-host--bounded) {\n min-block-size: 0;\n}\n\n:host(.table-host--fixed-height) {\n block-size: var(--_table-fixed-block-size);\n}\n\n:host(.table-host--viewport-fit) {\n block-size: var(--_table-viewport-fit-reserved-block-size, auto);\n min-block-size: var(--_table-viewport-fit-reserved-block-size, auto);\n}\n\n/*\n * The public table recipe stays at zero specificity for product-owned native\n * markup. Inside the component, these classes sit on ds-text hosts whose reset\n * intentionally owns display, padding, and border with higher specificity.\n * Reassert only those collided properties so component geometry cannot depend\n * on whether ds-text or ds-table happens to hydrate first.\n */\nds-text.ds-table__header-label-box {\n padding-inline: var(--dimension-space-025);\n}\n\nds-text.ds-table__cell-track {\n display: flex;\n}\n\nds-text.ds-table__cell-track--text {\n padding-inline: var(--_table-cell-track-label-inset);\n}\n\nds-text.ds-table__cell-secondary {\n padding-block: var(--dimension-space-025);\n}\n\n.ds-table__cell--primary-text ds-text.ds-table__cell-secondary {\n padding-block: 0;\n}\n\n.ds-table__cell-tag-stack--tag-with-text ds-text.ds-table__cell-tag-text {\n padding-inline: var(--_table-cell-track-padding-inline);\n padding-block: var(--dimension-space-025);\n}\n\n.ds-table__cell-tag-stack--text-with-tag ds-text.ds-table__cell-tag-text {\n padding-inline: var(--_table-cell-track-label-inset);\n}\n\nds-text.ds-table__load-content {\n display: inline-flex;\n}\n\nds-text.ds-table__bar-text {\n display: inline-flex;\n width: auto;\n max-width: 100%;\n padding-inline: var(--ds-control-label-inset, var(--dimension-space-025));\n}\n\nds-text.ds-table__footer-summary {\n margin-inline-start: auto;\n}\n\n.ds-table__bar-trailing ds-pagination {\n max-inline-size: 100%;\n}\n\n.ds-table__bar-status slot::slotted(*) {\n width: 100%;\n max-width: 100%;\n min-width: 0;\n}\n\nds-skeleton.ds-table__skeleton-image-shape {\n display: flex;\n width: 100%;\n height: 100%;\n --ds-control-height: 100%;\n}\n\nds-skeleton.ds-table__skeleton-checkbox {\n --ds-control-height: var(--dimension-iconography-sm);\n}\n\n.ds-table__skeleton-checkbox-canvas {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n inline-size: var(--dimension-iconography-md);\n block-size: var(--dimension-iconography-md);\n}\n",
1053
1053
  "type": "registry:ui"
1054
1054
  },
1055
1055
  {
1056
1056
  "path": "src/wc/components/Table/Table.tsx",
1057
- "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport {\n deriveTableSelectionState,\n formatTableResultSummary,\n nextTableSortState,\n tableColumnSize,\n tableModelIssues,\n tableRows,\n tableRowSelectionLabel,\n toggleAllLoadedTableRows,\n toggleTableGroupCollapsed,\n toggleTableGroupSelection,\n nextTableGroupsCollapsed,\n toggleTableRowSelection,\n} from './table-model';\nimport {\n resolveTableCellPresentation,\n type TableCellPresentation,\n} from './table-cell-model';\nimport {\n createTableRenderModel,\n type TableRenderModel,\n} from './table-render-model';\nimport { TableLayoutController } from './table-layout-controller';\nimport { TableLoadController } from './table-load-controller';\nimport { TableGroupLoadController } from './table-group-load-controller';\nimport type { PaginationChangeDetail } from '../Pagination/pagination-types';\nimport { resolveCssLengthPx } from '../../utils/resolve-css-length-px';\nimport { resolveTableFitPageSize } from './table-pagination-fit';\nimport {\n TableViewportFitController,\n type TableViewportFitMetrics,\n} from './table-viewport-fit-controller';\nimport type {\n TableCaptionVisibility,\n TableCellActionDetail,\n TableCellSkeleton,\n TableColumn,\n TableDataMode,\n TableGroup,\n TableGroupCollapseChangeDetail,\n TableGroupLoadMoreDetail,\n TableGroupingState,\n TableLoadMoreDetail,\n TableLoadMoreMode,\n TablePaginationState,\n TableRow,\n TableRowActivateDetail,\n TableSelectionChangeDetail,\n TableSelectionMode,\n TableSortChangeDetail,\n TableSortState,\n} from './table-types';\n\n@Component({\n tag: 'ds-table',\n styleUrls: [\n '../../utils/focus-ring.css',\n '../../utils/interaction-fill.css',\n 'Table.css',\n ],\n scoped: true,\n})\nexport class Table {\n @Element() el!: HTMLElement;\n /** Stable column definitions. Assign through JavaScript. */\n @Prop() columns: TableColumn[] = [];\n /** Ungrouped row data. Ignored while grouping is active. Assign through JavaScript. */\n @Prop() rows: TableRow[] = [];\n /** One level of application-owned grouped data. Assign through JavaScript. */\n @Prop() groups: TableGroup[] = [];\n /** Controlled grouping column. Applications supply groups in their final fixed order. */\n @Prop() grouping: TableGroupingState | null = null;\n /** Controlled member-row sort state. */\n @Prop() sort: TableSortState | null = null;\n /** Controlled collapsed group identities. Groups not listed remain expanded. */\n @Prop() collapsedGroupIds: string[] = [];\n\n /** Required accessible table name, retained as a native caption. */\n @Prop() caption!: string;\n /** Shows a matching presentational title bar above the native table frame. */\n @Prop() captionVisibility: TableCaptionVisibility = 'hidden';\n /**\n * Optional result summary footer. When both `displayedCount` and `totalCount`\n * are finite numbers, the table shows “Displaying {displayed} of {total}”.\n */\n @Prop() displayedCount: number | undefined;\n @Prop() totalCount: number | undefined;\n /** Supports {displayed} and {total} placeholders. */\n @Prop() resultSummaryLabel: string = 'Displaying {displayed} of {total}';\n @Prop() stickyHeader: boolean = false;\n /** Maximum scroll-region height. Numbers resolve to CSS pixels. */\n @Prop() maxHeight: string | number | undefined;\n /** Fixed height for the complete header, table frame, and footer composition. */\n @Prop() height: string | number | undefined;\n /** Fit the complete table composition to its nearest vertical scrollport. */\n @Prop() fitViewport: boolean = false;\n /** Reserved space above a viewport-fitted table once surrounding chrome is compact. */\n @Prop() viewportInsetBlockStart: string | number = 0;\n /** Reserved space below a viewport-fitted table. */\n @Prop() viewportInsetBlockEnd: string | number = 0;\n /** Optional explicit label for the horizontal/vertical scroll region. */\n @Prop() scrollLabel: string | undefined;\n\n @Prop() selectionMode: TableSelectionMode = 'none';\n /** Controlled selected row identities. IDs outside the loaded rows are preserved. */\n @Prop() selectedRowIds: string[] = [];\n\n /** Initial loading state. Existing rows stay visible; incremental loading uses loadingMore. */\n @Prop() loading: boolean = false;\n /** Initial-loading rows. Defaults to ten so bounded tables retain a useful filled viewport. */\n @Prop() skeletonRows: number = 10;\n @Prop() emptyHeading: string = 'No results';\n @Prop() emptyBody: string = 'No data is available.';\n /** Initial error state. Existing rows stay visible; incremental failures use loadMoreError. */\n @Prop() error: boolean = false;\n @Prop() errorHeading: string = 'Unable to load data';\n @Prop() errorBody: string = 'The data could not be loaded.';\n @Prop() emptyCellLabel: string = 'Not available';\n\n /** Top-level data-window strategy. Group member loading remains group-owned. */\n @Prop() dataMode: TableDataMode = 'infinite';\n /** Controlled top-level pagination state. Required when dataMode is pagination. */\n @Prop() pagination: TablePaginationState | null = null;\n @Prop() loadMoreMode: TableLoadMoreMode = 'auto';\n @Prop() hasMore: boolean = false;\n @Prop() loadingMore: boolean = false;\n @Prop() loadMoreError: string | undefined;\n /** Reset key for a new query/group/sort dataset. */\n @Prop() loadIdentity: string | number = 'default';\n /** IntersectionObserver root margin in CSS pixels. */\n @Prop() loadMoreThreshold: number = 0;\n @Prop() loadMoreLabel: string = 'Load more';\n @Prop() retryLabel: string = 'Retry';\n @Prop() loadingMoreLabel: string = 'Loading more items';\n @Prop() endOfResultsLabel: string = 'All results loaded';\n /** Supports {count} and {total} placeholders. */\n @Prop() rowsLoadedLabel: string = '{count} more rows loaded. {total} rows loaded.';\n @Prop() groupLoadMoreLabel: string = 'Load more';\n /** Supports the {group} placeholder. */\n @Prop() groupLoadMoreAriaLabel: string = 'Load more {group} results';\n /** Supports the {group} placeholder. */\n @Prop() groupRetryLabel: string = 'Retry loading {group} results';\n /** Supports the {group} placeholder. */\n @Prop() groupLoadingMoreLabel: string = 'Loading more items';\n /** Supports the {group} placeholder. */\n @Prop() groupEndOfResultsLabel: string = 'All {group} results loaded';\n /** Supports {group}, {count}, {loaded}, and {total} placeholders. */\n @Prop() groupRowsLoadedLabel: string = '{count} more rows loaded in {group}. {loaded} of {total} rows loaded.';\n\n @Event() dsSortChange!: EventEmitter<TableSortChangeDetail>;\n @Event() dsGroupCollapseChange!: EventEmitter<TableGroupCollapseChangeDetail>;\n @Event() dsSelectionChange!: EventEmitter<TableSelectionChangeDetail>;\n @Event() dsLoadMore!: EventEmitter<TableLoadMoreDetail>;\n @Event() dsGroupLoadMore!: EventEmitter<TableGroupLoadMoreDetail>;\n @Event() dsPaginationChange!: EventEmitter<PaginationChangeDetail>;\n @Event() dsCellAction!: EventEmitter<TableCellActionDetail>;\n @Event() dsRowActivate!: EventEmitter<TableRowActivateDetail>;\n\n @State() private overflowStart = false;\n @State() private overflowEnd = false;\n @State() private scrollable = false;\n @State() private announcement = '';\n @State() private activeStickyGroupId: string | null = null;\n @State() private viewportFitSettled = false;\n @State() private headerPresent = false;\n @State() private fitPageSize: number | undefined;\n\n private rootEl: HTMLElement | null = null;\n private viewportEl: HTMLElement | null = null;\n private frameEl: HTMLElement | null = null;\n private interactiveHeadEl: HTMLTableSectionElement | null = null;\n private collapseAllOverlayEl: HTMLElement | null = null;\n private stickyHeaderTableEl: HTMLTableElement | null = null;\n private tableEl: HTMLTableElement | null = null;\n private sentinelEl: HTMLElement | null = null;\n private incrementalWindowActive = false;\n private readonly groupSentinelEls = new Map<string, HTMLElement>();\n private previousModelWarning = '';\n private hasLoaded = false;\n private renderedModel: TableRenderModel | null = null;\n private stickyGroupConnected = false;\n private headerSlotObserver: MutationObserver | null = null;\n private fitResizeObserver: ResizeObserver | null = null;\n private fitObservedViewport: HTMLElement | null = null;\n private fitObservedTable: HTMLTableElement | null = null;\n private fitMeasurementPending = false;\n private readonly layoutController = new TableLayoutController({\n elements: () => ({\n viewport: this.viewportEl,\n stickyHeaderTable: this.stickyHeaderTableEl,\n collapseAllOverlay: this.collapseAllOverlayEl,\n frame: this.frameEl,\n interactiveHead: this.interactiveHeadEl,\n }),\n mode: () => ({\n documentStickyHeader: this.documentStickyHeader,\n floatingCollapseAll: this.renderedModel?.collapseAllHost?.mode === 'floating',\n clampVerticalOverscroll: this.fitViewport && this.viewportFitSettled,\n }),\n verticalEdgeWheel: deltaY => this.viewportFitController.scrollOuterBy(deltaY),\n overflowChanged: state => {\n if (state.start !== this.overflowStart) this.overflowStart = state.start;\n if (state.end !== this.overflowEnd) this.overflowEnd = state.end;\n if (state.scrollable !== this.scrollable) this.scrollable = state.scrollable;\n },\n });\n private readonly loadController = new TableLoadController({\n state: () => ({\n lazyLoading: this.dataMode === 'infinite' && !this.grouped,\n loadMoreMode: this.loadMoreMode,\n hasMore: this.hasMore,\n loadingMore: this.loadingMore,\n loadMoreError: this.loadMoreError,\n loadIdentity: this.loadIdentity,\n loadMoreThreshold: this.loadMoreThreshold,\n containedScroll: this.containedScroll,\n loadingMoreLabel: this.loadingMoreLabel,\n endOfResultsLabel: this.endOfResultsLabel,\n rowsLoadedLabel: this.rowsLoadedLabel,\n loadedRowCount: this.currentLoadedRowCount(),\n viewport: this.viewportEl,\n sentinel: this.sentinelEl,\n }),\n announce: message => {\n this.announcement = message;\n },\n request: detail => {\n this.dsLoadMore.emit(detail);\n },\n });\n private readonly groupLoadController = new TableGroupLoadController({\n state: () => ({\n enabled: this.grouped,\n loadMoreMode: this.loadMoreMode,\n loadMoreThreshold: this.loadMoreThreshold,\n containedScroll: this.containedScroll,\n groups: this.groups,\n viewport: this.viewportEl,\n sentinels: this.groupSentinelEls,\n loadingMoreLabel: this.groupLoadingMoreLabel,\n endOfResultsLabel: this.groupEndOfResultsLabel,\n rowsLoadedLabel: this.groupRowsLoadedLabel,\n }),\n announce: message => {\n this.announcement = message;\n },\n request: detail => {\n this.dsGroupLoadMore.emit(detail);\n },\n });\n private readonly viewportFitController = new TableViewportFitController({\n enabled: () => this.fitViewport,\n elements: () => ({ host: this.el, surface: this.rootEl }),\n insets: () => ({\n blockStart: this.viewportInsetBlockStart,\n blockEnd: this.viewportInsetBlockEnd,\n }),\n fitChanged: (metrics: TableViewportFitMetrics | null) => {\n const settled = metrics?.settled ?? false;\n if (settled !== this.viewportFitSettled) this.viewportFitSettled = settled;\n },\n });\n\n componentWillLoad(): void {\n this.incrementalWindowActive = this.hasIncrementalState;\n this.syncHeaderSlotPresence();\n this.loadController.initialize();\n this.groupLoadController.initialize();\n this.warnModelIssues();\n }\n\n componentDidLoad(): void {\n this.hasLoaded = true;\n this.layoutController.connect();\n this.loadController.connect();\n this.groupLoadController.connect();\n this.viewportFitController.connect();\n this.syncStickyGroupConnection();\n this.connectHeaderSlotObserver();\n this.connectFitObserver();\n this.syncFitPageSize();\n }\n\n componentDidRender(): void {\n this.layoutController.refresh();\n this.loadController.refresh();\n this.groupLoadController.refresh();\n this.viewportFitController.refresh();\n this.syncStickyGroupConnection();\n this.connectFitObserver();\n this.syncFitPageSize();\n if (this.stickyGroupConnected) this.updateStickyGroup();\n }\n\n connectedCallback(): void {\n if (!this.hasLoaded) return;\n this.layoutController.connect();\n this.loadController.connect();\n this.groupLoadController.connect();\n this.viewportFitController.connect();\n this.syncStickyGroupConnection();\n this.connectHeaderSlotObserver();\n this.connectFitObserver();\n }\n\n disconnectedCallback(): void {\n this.layoutController.disconnect();\n this.loadController.disconnect();\n this.groupLoadController.disconnect();\n this.viewportFitController.disconnect();\n this.disconnectStickyGroup();\n this.headerSlotObserver?.disconnect();\n this.headerSlotObserver = null;\n this.disconnectFitObserver();\n }\n\n private syncHeaderSlotPresence = () => {\n this.headerPresent = !!this.el.querySelector('[slot=\"header\"]');\n };\n\n private connectHeaderSlotObserver(): void {\n if (this.headerSlotObserver || typeof MutationObserver === 'undefined') return;\n this.headerSlotObserver = new MutationObserver(this.syncHeaderSlotPresence);\n this.headerSlotObserver.observe(this.el, { childList: true });\n this.syncHeaderSlotPresence();\n }\n\n private connectStickyGroup(): void {\n if (this.stickyGroupConnected || typeof window === 'undefined') return;\n this.stickyGroupConnected = true;\n document.addEventListener('scroll', this.updateStickyGroup, {\n capture: true,\n passive: true,\n });\n window.addEventListener('resize', this.updateStickyGroup, { passive: true });\n this.updateStickyGroup();\n }\n\n private syncStickyGroupConnection(): void {\n if (this.documentStickyHeader && this.grouped) {\n this.connectStickyGroup();\n return;\n }\n\n this.disconnectStickyGroup();\n this.frameEl?.style.removeProperty('--_table-sticky-group-top');\n if (this.activeStickyGroupId !== null) {\n this.activeStickyGroupId = null;\n }\n }\n\n private disconnectStickyGroup(): void {\n if (!this.stickyGroupConnected || typeof window === 'undefined') return;\n this.stickyGroupConnected = false;\n document.removeEventListener('scroll', this.updateStickyGroup, true);\n window.removeEventListener('resize', this.updateStickyGroup);\n }\n\n private readonly updateStickyGroup = (): void => {\n if (!this.documentStickyHeader || !this.grouped || !this.frameEl || !this.tableEl) {\n if (this.activeStickyGroupId !== null) this.activeStickyGroupId = null;\n return;\n }\n\n const stickyHeader = this.frameEl.querySelector<HTMLElement>(\n '.ds-table__document-sticky-header',\n );\n if (!stickyHeader) return;\n const threshold = stickyHeader.getBoundingClientRect().bottom;\n let activeGroupId: string | null = null;\n let activeGroupTop = threshold;\n for (const body of this.tableEl.querySelectorAll<HTMLElement>('tbody[data-group-id]')) {\n const source = body.querySelector<HTMLElement>('.ds-table__group-cell');\n if (!source) continue;\n const sourceRect = source.getBoundingClientRect();\n const bodyRect = body.getBoundingClientRect();\n if (sourceRect.top <= threshold && bodyRect.bottom > threshold) {\n activeGroupId = body.dataset.groupId ?? null;\n // A sticky section is bounded by its own row group. As that boundary\n // approaches the sticky lane, move the outgoing section bar with it so\n // the incoming source bar pushes it away instead of rendering beneath it.\n activeGroupTop = Math.min(threshold, bodyRect.bottom - sourceRect.height);\n }\n }\n\n const frameRect = this.frameEl.getBoundingClientRect();\n this.frameEl.style.setProperty(\n '--_table-sticky-group-top',\n `${activeGroupTop - frameRect.top}px`,\n );\n\n if (activeGroupId !== this.activeStickyGroupId) {\n this.activeStickyGroupId = activeGroupId;\n return;\n }\n };\n\n @Watch('columns')\n @Watch('grouping')\n handleStructureChange(): void {\n this.warnModelIssues();\n this.loadController.structureChanged();\n this.groupLoadController.structureChanged();\n }\n\n @Watch('rows')\n @Watch('groups')\n handleDataChange(): void {\n if (this.grouped) this.groupLoadController.dataChanged();\n else this.loadController.dataChanged();\n this.warnModelIssues();\n }\n\n @Watch('loadIdentity')\n handleLoadIdentityChange(): void {\n this.incrementalWindowActive = this.hasIncrementalState;\n this.loadController.identityChanged();\n }\n\n @Watch('loadMoreMode')\n @Watch('loadMoreThreshold')\n handleLazyConfigurationChange(): void {\n this.loadController.configurationChanged();\n this.groupLoadController.configurationChanged();\n }\n\n @Watch('dataMode')\n handleDataModeChange(): void {\n this.incrementalWindowActive = this.hasIncrementalState;\n this.handleLazyConfigurationChange();\n }\n\n @Watch('pagination')\n handlePaginationChange(\n pagination: TablePaginationState | null,\n previous: TablePaginationState | null,\n ): void {\n if (\n pagination?.pageSizeMode === 'fit' &&\n pagination.fitIdentity !== previous?.fitIdentity\n ) {\n this.fitMeasurementPending = true;\n }\n }\n\n @Watch('loadingMore')\n handleLoadingMoreChange(loading: boolean): void {\n if (loading) this.incrementalWindowActive = true;\n if (!this.grouped) this.loadController.loadingChanged(loading);\n }\n\n @Watch('loadMoreError')\n handleLoadMoreErrorChange(error: string | undefined): void {\n if (error?.trim()) this.incrementalWindowActive = true;\n if (!this.grouped) this.loadController.errorChanged(error);\n }\n\n @Watch('hasMore')\n handleHasMoreChange(hasMore: boolean, hadMore: boolean): void {\n if (hasMore) this.incrementalWindowActive = true;\n if (!this.grouped) this.loadController.hasMoreChanged(hasMore, hadMore);\n }\n\n private get grouped(): boolean {\n return this.grouping !== null;\n }\n\n private get hasIncrementalState(): boolean {\n return this.dataMode === 'infinite' &&\n (this.hasMore || this.loadingMore || !!this.loadMoreError?.trim());\n }\n\n private get selectable(): boolean {\n return this.selectionMode === 'multiple';\n }\n\n private get documentStickyHeader(): boolean {\n return this.stickyHeader && !this.containedScroll;\n }\n\n private get fixedHeight(): boolean {\n return !this.fitViewport && this.resolvedHeight !== undefined;\n }\n\n private get boundedComposition(): boolean {\n return this.fitViewport || this.fixedHeight;\n }\n\n private get containedScroll(): boolean {\n return this.boundedComposition || this.resolvedMaxHeight !== undefined;\n }\n\n private connectFitObserver(): void {\n if (typeof ResizeObserver === 'undefined') return;\n if (this.fitObservedViewport === this.viewportEl && this.fitObservedTable === this.tableEl) {\n return;\n }\n this.disconnectFitObserver();\n this.fitObservedViewport = this.viewportEl;\n this.fitObservedTable = this.tableEl;\n this.fitResizeObserver = new ResizeObserver(this.syncFitPageSize);\n if (this.fitObservedViewport) this.fitResizeObserver.observe(this.fitObservedViewport);\n if (this.fitObservedTable) this.fitResizeObserver.observe(this.fitObservedTable);\n }\n\n private disconnectFitObserver(): void {\n this.fitResizeObserver?.disconnect();\n this.fitResizeObserver = null;\n this.fitObservedViewport = null;\n this.fitObservedTable = null;\n }\n\n private readonly syncFitPageSize = (): void => {\n const pagination = this.dataMode === 'pagination' ? this.pagination : null;\n if (!pagination?.fitToPage || !this.containedScroll || !this.viewportEl || !this.tableEl) {\n if (this.fitPageSize !== undefined) this.fitPageSize = undefined;\n this.fitMeasurementPending = false;\n return;\n }\n const header = this.tableEl.querySelector<HTMLElement>(\n '.ds-table__head .ds-table__header-row',\n );\n const item = this.tableEl.querySelector<HTMLElement>(\n this.grouped ? '.ds-table__group-row' : '.ds-table__body .ds-table__row',\n );\n const itemBlockSize = item?.getBoundingClientRect().height || resolveCssLengthPx(\n 'var(--ds-table-row-min-block-size, var(--dimension-size-500))',\n 0,\n this.rootEl ?? this.el,\n );\n const next = resolveTableFitPageSize({\n viewportBlockSize: this.viewportEl.clientHeight,\n headerBlockSize: header?.getBoundingClientRect().height ?? 0,\n itemBlockSize,\n });\n if (next !== this.fitPageSize) this.fitPageSize = next;\n if (!this.fitMeasurementPending || pagination.pageSizeMode !== 'fit' || next === undefined) {\n return;\n }\n this.fitMeasurementPending = false;\n if (next === pagination.pageSize && pagination.pageIndex === 0) return;\n this.dsPaginationChange.emit({\n pageIndex: 0,\n pageSize: next,\n pageSizeMode: 'fit',\n totalItems: pagination.totalItems,\n pageSizeOptions: pagination.pageSizeOptions ?? [25, 50, 100, 200],\n fitToPage: true,\n fitPageSize: next,\n fitPageSizeLabel: pagination.fitPageSizeLabel ?? 'Fit to page',\n fitPageSizeTriggerLabel: pagination.fitPageSizeTriggerLabel ?? 'Fit',\n itemLabel: pagination.itemLabel ?? 'items',\n pageSizeLabel: pagination.pageSizeLabel ?? 'Items',\n ariaLabel: pagination.ariaLabel ?? `${this.caption} pagination`,\n previousPageIndex: pagination.pageIndex,\n previousPageSize: pagination.pageSize,\n previousPageSizeMode: 'fit',\n reason: 'fit',\n });\n };\n\n private currentLoadedRows(): TableRow[] {\n return tableRows(this.rows, this.groups, this.grouped);\n }\n\n private currentLoadedRowCount(): number {\n return this.grouped\n ? this.groups.reduce((count, group) => count + group.rows.length, 0)\n : this.rows.length;\n }\n\n private createRenderModel(): TableRenderModel {\n return createTableRenderModel({\n columns: this.columns,\n rows: this.rows,\n groups: this.groups,\n grouped: this.grouped,\n selectionMode: this.selectionMode,\n selectedRowIds: this.selectedRowIds,\n collapsedGroupIds: this.collapsedGroupIds,\n });\n }\n\n private get resolvedMaxHeight(): string | undefined {\n if (this.maxHeight == null || this.maxHeight === '') return undefined;\n return typeof this.maxHeight === 'number' ? `${Math.max(0, this.maxHeight)}px` : this.maxHeight;\n }\n\n private get resolvedHeight(): string | undefined {\n if (this.height == null || this.height === '') return undefined;\n return typeof this.height === 'number' ? `${Math.max(0, this.height)}px` : this.height;\n }\n\n private warnModelIssues(): void {\n const issues = tableModelIssues(this.columns, this.rows, this.groups, this.grouped);\n if (!this.caption?.trim()) issues.unshift('A non-empty caption is required.');\n if (this.grouping && !this.columns.some(column => column.id === this.grouping!.columnId)) {\n issues.push(`Grouping references unknown column id: ${this.grouping.columnId}`);\n }\n if (\n this.sort &&\n !this.columns.some(column =>\n column.id === this.sort!.columnId ||\n column.headerSegments?.some(segment => segment.sortKey === this.sort!.columnId),\n )\n ) {\n issues.push(`Sorting references unknown column id: ${this.sort.columnId}`);\n }\n const stickyStart = this.columns.filter(column => column.sticky === 'start');\n const stickyEnd = this.columns.filter(column => column.sticky === 'end');\n if (stickyStart.length > 1 || (this.selectable && stickyStart.length > 0)) {\n issues.push('Only one sticky start column is supported, and row selection already owns that lane.');\n }\n if (stickyEnd.length > 1) issues.push('Only one sticky end column is supported.');\n for (const column of [...stickyStart, ...stickyEnd]) {\n if (!tableColumnSize(column)) issues.push(`Sticky column ${column.id} requires an explicit size.`);\n }\n const message = issues.join(' ');\n if (!message || message === this.previousModelWarning) return;\n this.previousModelWarning = message;\n console.warn(`[ds-table] ${message}`);\n }\n\n private emitSort(column: TableColumn, sortKey = column.id): void {\n if (!column.sortable) return;\n this.dsSortChange.emit({ sort: nextTableSortState(this.sort, sortKey) });\n }\n\n private sortButtonLabel(column: TableColumn, sortKey = column.id, label = column.header): string {\n if (this.sort?.columnId !== sortKey) return `Sort ${label} ascending`;\n if (this.sort.direction === 'asc') return `Sort ${label} descending. Currently ascending.`;\n return `Sort ${label} ascending. Currently descending.`;\n }\n\n private emitRowSelection(row: TableRow): void {\n const next = toggleTableRowSelection(this.selectedRowIds, row);\n const selected = next.includes(row.id);\n this.dsSelectionChange.emit({\n selectedRowIds: next,\n scope: 'row',\n changedRowId: row.id,\n selected,\n });\n }\n\n private emitAllSelection(): void {\n const loadedRows = this.currentLoadedRows();\n const selection = deriveTableSelectionState(loadedRows, this.selectedRowIds);\n this.dsSelectionChange.emit({\n selectedRowIds: toggleAllLoadedTableRows(this.selectedRowIds, loadedRows),\n scope: 'all-loaded',\n selected: !selection.allSelected,\n });\n }\n\n private emitGroupSelection(group: TableGroup): void {\n const state = deriveTableSelectionState(group.rows, this.selectedRowIds);\n this.dsSelectionChange.emit({\n selectedRowIds: toggleTableGroupSelection(this.selectedRowIds, group.rows),\n scope: 'group',\n groupId: group.id,\n selected: !state.allSelected,\n });\n }\n\n private rowEventOwnsActivation(event: Event): boolean {\n const currentTarget = event.currentTarget;\n return !event.composedPath().some(target => {\n if (!(target instanceof HTMLElement) || target === currentTarget) return false;\n return target.matches(\n 'button, a, input, select, textarea, [role=\"button\"], [role=\"checkbox\"], ds-button-unfilled',\n );\n });\n }\n\n private emitRowActivation(row: TableRow, event: Event): void {\n if (!row.interactive || row.disabled || !this.rowEventOwnsActivation(event)) return;\n this.dsRowActivate.emit({ rowId: row.id });\n }\n\n private handleRowKeydown(row: TableRow, event: KeyboardEvent): void {\n if (event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' ')) return;\n event.preventDefault();\n this.emitRowActivation(row, event);\n }\n\n private renderSelectionControl(\n label: string,\n checked: boolean,\n indeterminate: boolean,\n disabled: boolean,\n onActivate: () => void,\n ) {\n return (\n <button\n class=\"ds-table__selection-control ds-focus-ring ds-interaction-fill__content\"\n type=\"button\"\n role=\"checkbox\"\n aria-label={label}\n aria-checked={indeterminate ? 'mixed' : String(checked)}\n disabled={disabled}\n onClick={onActivate}\n >\n <ds-checkbox\n label=\"\"\n size=\"md\"\n checked={checked}\n indeterminate={indeterminate}\n presentation={true}\n />\n </button>\n );\n }\n\n private renderStickyEdge(sticky: TableColumn['sticky']) {\n if (!sticky) return null;\n return (\n <span\n class={`ds-table__sticky-edge ds-table__sticky-edge--${sticky}`}\n aria-hidden=\"true\"\n />\n );\n }\n\n private renderCollapseAllButton() {\n return (\n <ds-button-unfilled\n class=\"ds-table__collapse-all\"\n variant=\"icon\"\n icon=\"ChevronDownUp\"\n size=\"xs\"\n style={{ width: 'var(--dimension-size-300)' }}\n aria-label=\"Collapse all groups\"\n hasBorder={false}\n activeFill={false}\n pressScale={false}\n onDsClick={event => {\n event.stopPropagation();\n this.emitAllGroupsCollapse();\n }}\n />\n );\n }\n\n private renderFloatingCollapseAll(model: TableRenderModel) {\n if (model.collapseAllHost?.mode !== 'floating') return null;\n return (\n <span\n class=\"ds-table__collapse-all-overlay\"\n ref={element => {\n this.collapseAllOverlayEl = element ?? null;\n }}\n >\n <span class=\"ds-table__collapse-all-surface ds-control-elevation ds-control-elevation--md\">\n {this.renderCollapseAllButton()}\n </span>\n </span>\n );\n }\n\n private renderColumnHeader(\n column: TableColumn,\n model: TableRenderModel,\n interactive = true,\n presentational = false,\n ) {\n const groupedColumn = this.grouping?.columnId === column.id;\n const headerSegments = column.headerSegments?.length\n ? column.headerSegments\n : [{ label: column.header, sortKey: column.id }];\n const activeMemberSegment = headerSegments.find(\n segment => segment.sortKey === this.sort?.columnId,\n );\n const activeMemberSort = !!activeMemberSegment;\n const activeSort = activeMemberSort;\n const align = column.align ?? 'start';\n const direction = activeMemberSort ? this.sort!.direction : undefined;\n const memberAriaSort = activeMemberSort\n ? (this.sort!.direction === 'asc' ? 'ascending' : 'descending')\n : undefined;\n\n const labelControl = (\n <span class=\"ds-table__header-labels\">\n {headerSegments.map((segment, index) => {\n const segmentActive = activeMemberSegment?.sortKey === segment.sortKey;\n const segmentInteractive = interactive && !!column.sortable;\n const label = (\n <ds-text\n class=\"ds-table__header-label-box ds-control-label-box\"\n as=\"span\"\n variant=\"text-caption\"\n emphasis={segmentActive}\n color=\"inherit\"\n lineTruncation={1}\n >\n {segment.label}\n </ds-text>\n );\n const control = segmentInteractive ? (\n <button\n class=\"ds-table__header-label ds-table__header-label--interactive ds-focus-ring\"\n type=\"button\"\n aria-label={this.sortButtonLabel(column, segment.sortKey, segment.label)}\n data-sort-control=\"label\"\n data-sort-key={segment.sortKey}\n data-sort-active={segmentActive ? 'true' : undefined}\n onClick={() => this.emitSort(column, segment.sortKey)}\n >\n {label}\n </button>\n ) : (\n <span class=\"ds-table__header-label ds-table__header-static\">{label}</span>\n );\n\n return (\n <span class=\"ds-table__header-segment\" key={segment.sortKey}>\n {control}\n {index < headerSegments.length - 1 && (\n <ds-text\n class=\"ds-table__header-separator\"\n as=\"span\"\n variant=\"text-caption\"\n color=\"tertiary\"\n aria-hidden=\"true\"\n >\n {segment.separator ?? '/'}\n </ds-text>\n )}\n </span>\n );\n })}\n </span>\n );\n const sortControl = interactive ? (\n <span class=\"ds-table__sort-slot\">\n {activeSort && (\n <ds-button-unfilled\n class=\"ds-table__sort-direction\"\n variant=\"icon\"\n icon={direction === 'asc' ? 'ArrowUp' : 'ArrowDown'}\n size=\"xs\"\n aria-label={this.sortButtonLabel(\n column,\n activeMemberSegment?.sortKey ?? column.id,\n activeMemberSegment?.label ?? column.header,\n )}\n hasBorder={false}\n activeFill={false}\n isActive={true}\n pressScale={false}\n data-sort-control=\"direction\"\n onDsClick={() => this.emitSort(column, activeMemberSegment?.sortKey ?? column.id)}\n />\n )}\n </span>\n ) : null;\n const collapseHost = model.collapseAllHost;\n const actionCollapseHost = collapseHost?.mode === 'action' && collapseHost.columnId === column.id;\n const blankActionCollapseHost = actionCollapseHost &&\n !column.header.trim() &&\n !column.headerSegments?.length;\n const collapseControl =\n interactive && actionCollapseHost ? (\n <span class=\"ds-table__collapse-slot\">\n {this.renderCollapseAllButton()}\n </span>\n ) : null;\n\n return (\n <th\n key={column.id}\n class={{\n 'ds-table__header-cell': true,\n [`ds-table__cell--align-${align}`]: true,\n 'ds-table__cell--sticky-start': column.sticky === 'start',\n 'ds-table__cell--sticky-end': column.sticky === 'end',\n 'ds-table__header-cell--collapse-all': actionCollapseHost,\n }}\n scope={presentational ? undefined : 'col'}\n aria-sort={presentational ? undefined : memberAriaSort}\n data-column-id={column.id}\n data-grouped={groupedColumn ? 'true' : undefined}\n data-sort-active={activeSort ? 'true' : undefined}\n >\n {!column.header.trim() && column.headerLabel?.trim() && (\n <span class=\"ds-visually-hidden\">{column.headerLabel}</span>\n )}\n {blankActionCollapseHost ? (\n <span class=\"ds-table__header-content ds-table__header-content--collapse-all\">\n {collapseControl}\n </span>\n ) : (\n <span class=\"ds-table__header-content\">\n {align === 'end' && sortControl}\n {align === 'center' && (\n <span\n class=\"ds-table__sort-slot ds-table__sort-slot--balance\"\n data-sort-balance=\"true\"\n aria-hidden=\"true\"\n />\n )}\n {labelControl}\n {align !== 'end' && sortControl}\n {collapseControl}\n </span>\n )}\n {this.renderStickyEdge(column.sticky)}\n </th>\n );\n }\n\n private renderColgroup(model: TableRenderModel) {\n return (\n <colgroup>\n {model.selectable && <col class=\"ds-table__selection-column\" />}\n {this.columns.map(column => {\n const width = tableColumnSize(column);\n const flexible = column.id === model.flexibleColumnId;\n return (\n <col\n key={column.id}\n class={{\n 'ds-table__action-column': column.kind === 'action',\n 'ds-table__flexible-column': flexible,\n }}\n style={width && !flexible ? { width } : undefined}\n />\n );\n })}\n </colgroup>\n );\n }\n\n private renderHeader(\n model: TableRenderModel,\n interactive = true,\n presentational = false,\n ) {\n const selection = model.selection;\n return (\n <thead class={{\n 'ds-table__head': true,\n 'ds-table__head--semantic-copy': !interactive,\n }} ref={element => {\n if (interactive) this.interactiveHeadEl = element ?? null;\n }}>\n <tr class=\"ds-table__header-row\">\n {model.selectable && (\n <th\n class=\"ds-table__header-cell ds-table__selection-cell ds-table__cell--sticky-start\"\n scope={presentational ? undefined : 'col'}\n >\n {interactive ? this.renderSelectionControl(\n selection.allSelected ? 'Deselect all loaded rows' : 'Select all loaded rows',\n selection.allSelected,\n selection.indeterminate,\n selection.selectableRowIds.length === 0,\n () => this.emitAllSelection(),\n ) : (\n <span class=\"ds-visually-hidden\">Select rows</span>\n )}\n {this.renderStickyEdge('start')}\n </th>\n )}\n {this.columns.map(column => (\n this.renderColumnHeader(column, model, interactive, presentational)\n ))}\n </tr>\n </thead>\n );\n }\n\n private renderCellValue(\n cell: TableCellPresentation,\n column: TableColumn,\n row: TableRow,\n ) {\n if (cell.kind === 'blank') return null;\n\n if (cell.kind === 'empty') {\n return (\n <ds-text\n class=\"ds-table__cell-track ds-table__cell-track--text\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"tertiary\"\n >\n <span aria-hidden=\"true\">—</span>\n <span class=\"ds-visually-hidden\">{this.emptyCellLabel}</span>\n </ds-text>\n );\n }\n\n if (cell.kind === 'icon') {\n const value = cell.value;\n return (\n <ds-icon\n name={value.icon}\n size=\"md\"\n color={value.color ?? 'secondary'}\n label={value.label}\n />\n );\n }\n\n if (cell.kind === 'image') {\n const value = cell.value;\n return (\n <span class=\"ds-table__cell-image\">\n {value.src ? (\n <img class=\"ds-table__cell-image-content\" src={value.src} alt={value.alt} loading=\"lazy\" />\n ) : (\n <span\n class=\"ds-table__cell-image-placeholder\"\n role=\"img\"\n aria-label={value.alt}\n />\n )}\n </span>\n );\n }\n\n if (cell.kind === 'action') {\n const value = cell.value;\n return (\n <ds-button-unfilled\n variant={value.variant ?? 'label'}\n size=\"md\"\n isInset={true}\n insetDepth=\"double\"\n label={value.label ?? ''}\n icon={value.icon ?? ''}\n aria-label={value.ariaLabel ?? null}\n hasBorder={value.hasBorder ?? false}\n isInactive={value.isInactive ?? false}\n isLoading={value.isLoading ?? false}\n onDsClick={event => {\n event.stopPropagation();\n this.dsCellAction.emit({\n actionId: value.actionId,\n rowId: row.id,\n columnId: column.id,\n });\n }}\n />\n );\n }\n\n if (cell.kind === 'tag') {\n const value = cell.value;\n const variant = cell.variant;\n const tag = (\n <ds-tag\n label={value.label}\n intent={value.intent ?? 'neutral'}\n contrast={value.contrast ?? 'faint'}\n size={variant === 'text-with-tag' ? 'sm' : 'md'}\n icon={value.icon ?? ''}\n rounded={value.rounded ?? false}\n isInset\n insetDepth=\"double\"\n />\n );\n\n if (variant === 'tag-only') return tag;\n\n return (\n <span class={`ds-table__cell-tag-stack ds-table__cell-tag-stack--${variant}`}>\n {variant === 'tag-with-text' && tag}\n <ds-text\n class=\"ds-table__cell-tag-text ds-table__cell-track\"\n as=\"span\"\n variant={variant === 'tag-with-text' ? 'text-body-small' : 'text-body-medium'}\n color=\"secondary\"\n lineTruncation={1}\n >\n {value.text}\n </ds-text>\n {variant === 'text-with-tag' && (\n <span class=\"ds-table__cell-tag-control-track\">{tag}</span>\n )}\n </span>\n );\n }\n\n const text = cell.value;\n const wraps = cell.wraps;\n\n return (\n <span class={{ 'ds-table__cell-copy': true, 'ds-table__cell-copy--wrap': wraps }}>\n <ds-text\n class=\"ds-table__cell-primary ds-table__cell-track ds-table__cell-track--text\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"primary\"\n lineTruncation={wraps ? 'none' : 1}\n wrap={wraps ? 'wrap' : 'nowrap'}\n fontFeature={text.fontFeature ?? 'normal'}\n >\n {text.primary}\n </ds-text>\n {text.secondary !== undefined && text.secondary !== '' && (\n <ds-text\n class=\"ds-table__cell-secondary ds-table__cell-track ds-table__cell-track--text\"\n as=\"span\"\n variant={cell.primaryText ? 'text-body-medium' : 'text-body-small'}\n color={text.secondaryColor ?? (cell.primaryText ? 'primary' : 'secondary')}\n lineTruncation={wraps ? 'none' : 1}\n wrap={wraps ? 'wrap' : 'nowrap'}\n >\n {text.secondary}\n </ds-text>\n )}\n </span>\n );\n }\n\n private renderRow(row: TableRow, model: TableRenderModel) {\n const selected = model.selectedRowIds.has(row.id);\n const rowSelectable = row.selectable !== false && !row.disabled;\n return (\n <tr\n key={row.id}\n class={{\n 'ds-table__row': true,\n 'ds-table__row--selected': selected,\n 'ds-table__row--disabled': !!row.disabled,\n 'ds-table__row--interactive': !!row.interactive && !row.disabled,\n 'ds-focus-ring': !!row.interactive && !row.disabled,\n }}\n data-row-id={row.id}\n data-selected={selected ? 'true' : undefined}\n aria-disabled={row.disabled ? 'true' : undefined}\n tabIndex={row.interactive && !row.disabled ? 0 : undefined}\n onClick={event => this.emitRowActivation(row, event)}\n onKeyDown={event => this.handleRowKeydown(row, event)}\n >\n {model.selectable && (\n <td class={{\n 'ds-table__cell': true,\n 'ds-table__selection-cell': true,\n 'ds-table__cell--sticky-start': true,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--grouped': true,\n 'ds-interaction-fill--selected': selected,\n }}>\n {this.renderSelectionControl(\n `${selected ? 'Deselect' : 'Select'} ${tableRowSelectionLabel(row, this.columns)}`,\n selected,\n false,\n !rowSelectable,\n () => this.emitRowSelection(row),\n )}\n {this.renderStickyEdge('start')}\n </td>\n )}\n {this.columns.map(column => {\n const align = column.align ?? 'start';\n const cell = resolveTableCellPresentation(row.cells[column.id], column);\n const tagCell = cell.kind === 'tag';\n const iconCell = cell.kind === 'icon';\n const imageCell = cell.kind === 'image';\n const actionCell = cell.kind === 'action';\n const textCell = cell.kind === 'text';\n const primaryTextCell = textCell && cell.primaryText;\n const singleTextCell = textCell && cell.singleLine;\n const emptyCell = cell.kind === 'empty';\n const blankCell = cell.kind === 'blank';\n const tagVariant = tagCell ? cell.variant : undefined;\n const textVariant = textCell ? cell.variant : undefined;\n return (\n <td\n key={`${row.id}:${column.id}`}\n class={{\n 'ds-table__cell': true,\n [`ds-table__cell--align-${align}`]: true,\n 'ds-table__cell--tag': tagCell,\n [`ds-table__cell--tag-${tagVariant}`]: tagCell,\n 'ds-table__cell--icon': iconCell,\n 'ds-table__cell--image': imageCell,\n 'ds-table__cell--action': actionCell,\n 'ds-table__cell--primary-text': primaryTextCell,\n 'ds-table__cell--text-single': singleTextCell,\n 'ds-table__cell--text-multi': textCell && !singleTextCell,\n 'ds-table__cell--empty': emptyCell,\n 'ds-table__cell--blank': blankCell,\n 'ds-table__cell--sticky-start': column.sticky === 'start',\n 'ds-table__cell--sticky-end': column.sticky === 'end',\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--grouped': true,\n 'ds-interaction-fill--selected': selected,\n }}\n data-column-id={column.id}\n data-cell-type={cell.cellType}\n data-cell-variant={tagVariant ?? textVariant}\n >\n <span class=\"ds-table__cell-content ds-interaction-fill__content\">\n {this.renderCellValue(cell, column, row)}\n </span>\n {this.renderStickyEdge(column.sticky)}\n </td>\n );\n })}\n </tr>\n );\n }\n\n private emitGroupCollapse(group: TableGroup) {\n const collapsedGroupIds = toggleTableGroupCollapsed(this.collapsedGroupIds, group.id);\n this.dsGroupCollapseChange.emit({\n scope: 'group',\n groupId: group.id,\n collapsed: collapsedGroupIds.includes(group.id),\n collapsedGroupIds,\n });\n }\n\n private emitAllGroupsCollapse() {\n const groupIds = this.groups.map(group => group.id);\n const collapsedGroupIds = nextTableGroupsCollapsed(this.collapsedGroupIds, groupIds);\n this.dsGroupCollapseChange.emit({\n scope: 'all',\n collapsed: collapsedGroupIds.length > 0,\n collapsedGroupIds,\n });\n }\n\n private renderGroupContent(groupModel: TableRenderModel['groups'][number]) {\n const {\n group,\n count,\n loadedCount,\n countLabel,\n collapsed: isCollapsed,\n labelColor,\n selection: groupSelection,\n } = groupModel;\n return (\n <span class=\"ds-table__group-content\">\n {groupSelection && (\n <span class=\"ds-table__group-selection\">\n {this.renderSelectionControl(\n groupSelection.allSelected\n ? `Deselect loaded rows in ${group.label} group`\n : `Select loaded rows in ${group.label} group`,\n groupSelection.allSelected,\n groupSelection.indeterminate,\n groupSelection.selectableRowIds.length === 0,\n () => this.emitGroupSelection(group),\n )}\n </span>\n )}\n <span class=\"ds-table__group-copy\">\n <ds-text\n class=\"ds-table__group-label\"\n as=\"span\"\n variant=\"text-body-medium\"\n emphasis={true}\n color={labelColor}\n >\n {group.label}\n </ds-text>\n <ds-text\n class=\"ds-table__group-separator\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n aria-hidden=\"true\"\n >\n ·\n </ds-text>\n <ds-text\n class=\"ds-table__group-count\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n aria-hidden=\"true\"\n >\n {loadedCount} of {count}\n </ds-text>\n <span class=\"ds-visually-hidden\">{countLabel}</span>\n </span>\n <ds-button-unfilled\n class=\"ds-table__group-toggle\"\n variant=\"icon\"\n size=\"md\"\n isInset={true}\n insetDepth=\"double\"\n icon={isCollapsed ? 'ChevronDown' : 'ChevronUp'}\n expanded={!isCollapsed}\n aria-label={\n isCollapsed\n ? `Expand ${group.label} group`\n : `Collapse ${group.label} group`\n }\n hasBorder={false}\n onDsClick={event => {\n event.stopPropagation();\n this.emitGroupCollapse(group);\n }}\n />\n </span>\n );\n }\n\n private formatGroupLoadLabel(template: string, group: TableGroup): string {\n return template.split('{group}').join(group.label);\n }\n\n private renderGroupLoadRow(group: TableGroup, totalColumns: number) {\n const error = group.loadMoreError?.trim();\n if (!error && !group.loadingMore && !group.hasMore) return null;\n const manualFallback = this.loadMoreMode === 'manual' ||\n !this.groupLoadController.intersectionSupported;\n\n return (\n <tr\n class=\"ds-table__load-row ds-table__group-load-row\"\n data-group-id={group.id}\n ref={element => {\n if (element) this.groupSentinelEls.set(group.id, element);\n else this.groupSentinelEls.delete(group.id);\n }}\n >\n <td class=\"ds-table__load-cell\" colSpan={totalColumns}>\n <div class=\"ds-table__viewport-band ds-table__load-band\">\n {error ? (\n <span class=\"ds-table__load-content ds-table__load-content--error\">\n <span class=\"ds-table__load-copy\">\n <ds-icon name=\"ErrorTriangle\" size=\"md\" color=\"secondary\" aria-hidden=\"true\" />\n <ds-text as=\"span\" variant=\"text-body-medium\" color=\"secondary\">{error}</ds-text>\n </span>\n <ds-button-unfilled\n label={this.formatGroupLoadLabel(this.groupRetryLabel, group)}\n size=\"md\"\n onDsClick={() => this.groupLoadController.request(group.id, 'retry')}\n />\n </span>\n ) : group.loadingMore ? (\n <span class=\"ds-table__load-content\">\n <ds-loader size=\"md\" color=\"secondary\" />\n <ds-text as=\"span\" variant=\"text-body-medium\" color=\"secondary\">\n {this.formatGroupLoadLabel(this.groupLoadingMoreLabel, group)}\n </ds-text>\n </span>\n ) : group.hasMore && manualFallback ? (\n <span class=\"ds-table__load-content\">\n <ds-button-unfilled\n label={this.groupLoadMoreLabel}\n aria-label={this.formatGroupLoadLabel(this.groupLoadMoreAriaLabel, group)}\n size=\"md\"\n onDsClick={() => this.groupLoadController.request(group.id, 'manual')}\n />\n </span>\n ) : (\n <span class=\"ds-table__auto-sentinel\" aria-hidden=\"true\" />\n )}\n </div>\n </td>\n </tr>\n );\n }\n\n private renderStickyGroup(model: TableRenderModel) {\n if (!this.documentStickyHeader || !this.activeStickyGroupId) return null;\n const groupModel = model.groups.find(item => item.group.id === this.activeStickyGroupId);\n if (!groupModel) return null;\n return (\n <div\n class={{\n 'ds-table__sticky-group': true,\n 'ds-table__sticky-group--selectable': model.selectable,\n [groupModel.intentClass ?? '']: !!groupModel.intentClass,\n }}\n data-group-id={groupModel.group.id}\n data-group-intent={groupModel.intent}\n >\n {this.renderGroupContent(groupModel)}\n </div>\n );\n }\n\n private renderDataBodies(model: TableRenderModel) {\n if (!model.grouped) {\n return (\n <tbody class=\"ds-table__body\">\n {this.rows.map(row => this.renderRow(row, model))}\n </tbody>\n );\n }\n\n return model.groups.map(groupModel => {\n const { group, collapsed: isCollapsed, intent, intentClass } = groupModel;\n const stickySourceHidden = this.documentStickyHeader && this.activeStickyGroupId === group.id;\n return (\n <tbody\n class=\"ds-table__body ds-table__group\"\n role=\"rowgroup\"\n data-group-id={group.id}\n data-group-intent={intent}\n data-collapsed={isCollapsed ? 'true' : undefined}\n aria-busy={!isCollapsed && group.loadingMore ? 'true' : undefined}\n key={group.id}\n >\n <tr\n role=\"row\"\n aria-hidden={stickySourceHidden ? 'true' : undefined}\n class={{\n 'ds-table__group-row': true,\n 'ds-table__group-row--native-sticky': this.stickyHeader && !this.documentStickyHeader,\n }}\n >\n <th\n class={{\n 'ds-table__group-cell': true,\n 'ds-table__group-cell--sticky-source-hidden': stickySourceHidden,\n [intentClass ?? '']: !!intentClass,\n }}\n role=\"rowheader\"\n scope=\"rowgroup\"\n colSpan={model.totalColumns}\n >\n {this.renderGroupContent(groupModel)}\n </th>\n </tr>\n {!isCollapsed && group.rows.map(row => this.renderRow(row, model))}\n {!isCollapsed && this.renderGroupLoadRow(group, model.totalColumns)}\n </tbody>\n );\n });\n }\n\n private renderSkeletonBody(model: TableRenderModel) {\n const count = Math.min(20, Math.max(1, Math.round(this.skeletonRows) || 1));\n return (\n <tbody class=\"ds-table__body ds-table__skeleton-body\">\n {Array.from({ length: count }, (_, index) => (\n <tr class=\"ds-table__row ds-table__skeleton-row\" key={`skeleton-${index}`}>\n {model.selectable && (\n <td class=\"ds-table__cell ds-table__selection-cell ds-table__cell--sticky-start ds-table__skeleton-cell ds-interaction-fill ds-interaction-fill--grouped\">\n <span class=\"ds-interaction-fill__content\">\n <ds-skeleton variant=\"icon\" iconSize=\"md\" />\n </span>\n {this.renderStickyEdge('start')}\n </td>\n )}\n {this.columns.map(column => this.renderSkeletonCell(column, index))}\n </tr>\n ))}\n </tbody>\n );\n }\n\n private renderSkeletonCell(column: TableColumn, rowIndex: number) {\n const skeleton = column.skeleton ?? (\n column.kind === 'action'\n ? { kind: 'action', variant: 'icon' }\n : { kind: 'text', lines: 1 }\n ) satisfies TableCellSkeleton;\n const align = column.align ?? 'start';\n const text = skeleton.kind === 'text';\n const lines = text ? skeleton.lines ?? 1 : 1;\n const tag = skeleton.kind === 'tag';\n const icon = skeleton.kind === 'icon';\n const image = skeleton.kind === 'image';\n const action = skeleton.kind === 'action';\n const blank = skeleton.kind === 'blank';\n\n return (\n <td\n class={{\n 'ds-table__cell': true,\n [`ds-table__cell--align-${align}`]: true,\n 'ds-table__skeleton-cell': true,\n 'ds-table__cell--text-single': text && lines === 1,\n 'ds-table__cell--text-multi': text && lines === 2,\n 'ds-table__cell--tag': tag,\n 'ds-table__cell--tag-tag-only': tag,\n 'ds-table__cell--icon': icon,\n 'ds-table__cell--image': image,\n 'ds-table__cell--action': action,\n 'ds-table__cell--blank': blank,\n 'ds-table__cell--sticky-start': column.sticky === 'start',\n 'ds-table__cell--sticky-end': column.sticky === 'end',\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--grouped': true,\n }}\n data-column-id={column.id}\n data-skeleton-kind={skeleton.kind}\n key={`skeleton-${rowIndex}:${column.id}`}\n >\n <span class=\"ds-table__cell-content ds-interaction-fill__content\">\n {this.renderSkeletonCellContent(skeleton)}\n </span>\n {this.renderStickyEdge(column.sticky)}\n </td>\n );\n }\n\n private renderSkeletonCellContent(skeleton: TableCellSkeleton) {\n if (skeleton.kind === 'blank') return null;\n\n if (skeleton.kind === 'image') {\n return (\n <span class=\"ds-table__cell-image ds-table__skeleton-image\">\n <ds-skeleton\n class=\"ds-table__skeleton-image-shape\"\n variant=\"control\"\n controlSize=\"md\"\n width=\"100%\"\n />\n </span>\n );\n }\n\n if (skeleton.kind === 'icon') {\n return <ds-skeleton variant=\"icon\" iconSize=\"md\" rounded={skeleton.rounded ?? false} />;\n }\n\n if (skeleton.kind === 'tag') {\n return (\n <ds-skeleton\n variant=\"control\"\n controlSize=\"sm\"\n width={skeleton.width ?? '64%'}\n />\n );\n }\n\n if (skeleton.kind === 'action') {\n const iconOnly = (skeleton.variant ?? 'icon') === 'icon';\n return (\n <ds-skeleton\n variant=\"control\"\n controlSize=\"sm\"\n width={skeleton.width ?? (iconOnly ? '24px' : '72%')}\n />\n );\n }\n\n const lines = skeleton.lines ?? 1;\n return (\n <span class=\"ds-table__cell-copy\">\n <span class=\"ds-table__cell-track ds-table__cell-track--text\">\n <ds-skeleton\n variant=\"text\"\n textVariant=\"text-body-medium\"\n width={skeleton.primaryWidth ?? '100%'}\n />\n </span>\n {lines === 2 && (\n <span class=\"ds-table__cell-secondary ds-table__cell-track ds-table__cell-track--text\">\n <ds-skeleton\n variant=\"text\"\n textVariant=\"text-body-small\"\n width={skeleton.secondaryWidth ?? '72%'}\n />\n </span>\n )}\n </span>\n );\n }\n\n private renderStateBody(kind: 'empty' | 'error', totalColumns: number) {\n const error = kind === 'error';\n return (\n <tbody class=\"ds-table__body ds-table__state-body\">\n <tr class=\"ds-table__state-row\">\n <td class=\"ds-table__state-cell\" colSpan={totalColumns}>\n <div class=\"ds-table__viewport-band ds-table__state-band\">\n <ds-empty-state\n icon={error ? 'ErrorTriangle' : 'Inbox'}\n heading={error ? this.errorHeading : this.emptyHeading}\n body={error ? this.errorBody : this.emptyBody}\n />\n </div>\n </td>\n </tr>\n </tbody>\n );\n }\n\n private renderLazyBody(model: TableRenderModel) {\n if (\n this.dataMode !== 'infinite' ||\n !this.incrementalWindowActive ||\n !model.hasData ||\n model.grouped\n ) return null;\n const error = this.loadMoreError?.trim();\n const manualFallback = this.loadMoreMode === 'manual' ||\n !this.loadController.intersectionSupported;\n\n return (\n <tbody class=\"ds-table__body ds-table__load-body\">\n <tr\n class=\"ds-table__load-row\"\n ref={element => {\n this.sentinelEl = element ?? null;\n }}\n >\n <td class=\"ds-table__load-cell\" colSpan={model.totalColumns}>\n <div class=\"ds-table__viewport-band ds-table__load-band\">\n {error ? (\n <span class=\"ds-table__load-content ds-table__load-content--error\">\n <span class=\"ds-table__load-copy\">\n <ds-icon name=\"ErrorTriangle\" size=\"md\" color=\"secondary\" aria-hidden=\"true\" />\n <ds-text as=\"span\" variant=\"text-body-medium\" color=\"secondary\">{error}</ds-text>\n </span>\n <ds-button-unfilled\n label={this.retryLabel}\n size=\"md\"\n onDsClick={() => this.loadController.request('retry')}\n />\n </span>\n ) : this.loadingMore ? (\n <span class=\"ds-table__load-content\">\n <ds-loader size=\"md\" color=\"secondary\" />\n <ds-text as=\"span\" variant=\"text-body-medium\" color=\"secondary\">\n {this.loadingMoreLabel}\n </ds-text>\n </span>\n ) : this.hasMore && manualFallback ? (\n <span class=\"ds-table__load-content\">\n <ds-button-unfilled\n label={this.loadMoreLabel}\n size=\"md\"\n onDsClick={() => this.loadController.request('manual')}\n />\n </span>\n ) : this.hasMore ? (\n <span class=\"ds-table__auto-sentinel\" aria-hidden=\"true\" />\n ) : (\n <ds-text\n class=\"ds-table__load-content\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n >\n {this.endOfResultsLabel}\n </ds-text>\n )}\n </div>\n </td>\n </tr>\n </tbody>\n );\n }\n\n private renderDocumentStickyHeader(model: TableRenderModel) {\n if (!this.documentStickyHeader) return null;\n return (\n <div\n class=\"ds-table__document-sticky-header\"\n >\n <table\n class={{\n 'ds-table__table': true,\n 'ds-table__sticky-header-table': true,\n 'ds-table__table--selectable': model.selectable,\n 'ds-table__table--grouped': model.grouped,\n }}\n style={model.tableStyle}\n role=\"presentation\"\n ref={element => {\n this.stickyHeaderTableEl = element ?? null;\n }}\n >\n {this.renderColgroup(model)}\n {this.renderHeader(model, true, true)}\n </table>\n {this.renderFloatingCollapseAll(model)}\n </div>\n );\n }\n\n private get resultSummary(): string | null {\n if (this.dataMode === 'pagination') return null;\n return formatTableResultSummary(\n this.displayedCount,\n this.totalCount,\n this.resultSummaryLabel,\n );\n }\n\n private get hasResultFooter(): boolean {\n return (this.dataMode === 'pagination' && !!this.pagination) ||\n !!this.resultSummary || !!this.el.querySelector(\n '[slot=\"footer-leading\"], [slot=\"footer\"], [slot=\"footer-trailing\"]',\n );\n }\n\n private renderResultFooter() {\n const summary = this.resultSummary;\n const hasLeading = !!this.el.querySelector('[slot=\"footer-leading\"]');\n const hasCopy = !!this.el.querySelector('[slot=\"footer\"]');\n const hasTrailing = !!this.el.querySelector('[slot=\"footer-trailing\"]');\n const pagination = this.dataMode === 'pagination' ? this.pagination : null;\n if (!this.hasResultFooter) return null;\n return (\n <div class=\"ds-table__footer ds-table__bar ds-chrome-header ds-control--md\">\n <div class=\"ds-table__bar-copy ds-chrome-header__copy ds-control--md\">\n {hasLeading && (\n <div class=\"ds-table__bar-status ds-chrome-header__heading\">\n <slot name=\"footer-leading\" />\n </div>\n )}\n {hasCopy ? (\n <slot name=\"footer\" />\n ) : summary ? (\n <ds-text\n class=\"ds-table__footer-summary ds-table__bar-text\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n >\n {summary}\n </ds-text>\n ) : null}\n </div>\n {(pagination || hasTrailing) && (\n <div class=\"ds-table__bar-trailing ds-chrome-header__trailing\">\n {pagination && (\n <ds-pagination\n pageIndex={pagination.pageIndex}\n pageSize={pagination.pageSize}\n pageSizeMode={pagination.pageSizeMode ?? 'fixed'}\n totalItems={pagination.totalItems}\n pageSizeOptions={pagination.pageSizeOptions ?? [25, 50, 100, 200]}\n fitToPage={pagination.fitToPage ?? false}\n fitPageSize={this.fitPageSize ?? pagination.fitPageSize}\n fitPageSizeLabel={pagination.fitPageSizeLabel ?? 'Fit to page'}\n fitPageSizeTriggerLabel={pagination.fitPageSizeTriggerLabel ?? 'Fit'}\n itemLabel={pagination.itemLabel ?? 'items'}\n pageSizeLabel={pagination.pageSizeLabel ?? 'Items'}\n label={pagination.ariaLabel ?? `${this.caption} pagination`}\n loading={this.loading}\n onDsChange={(event: CustomEvent<PaginationChangeDetail>) =>\n this.dsPaginationChange.emit(event.detail)}\n />\n )}\n <slot name=\"footer-trailing\" />\n </div>\n )}\n </div>\n );\n }\n\n private renderCaptionBar() {\n if (this.captionVisibility !== 'visible') return null;\n return (\n <div class=\"ds-table__caption-bar ds-table__bar ds-control--md\">\n <div class=\"ds-table__caption-content\">\n <slot\n name=\"header\"\n onSlotchange={this.syncHeaderSlotPresence}\n />\n {!this.headerPresent ? (\n <ds-text\n class=\"ds-table__caption-title ds-table__bar-text\"\n as=\"div\"\n variant=\"text-title-small\"\n emphasis={true}\n color=\"primary\"\n aria-hidden=\"true\"\n >\n {this.caption}\n </ds-text>\n ) : null}\n </div>\n </div>\n );\n }\n\n render() {\n this.groupSentinelEls.clear();\n const model = this.createRenderModel();\n this.renderedModel = model;\n const regionLabel = this.scrollLabel?.trim() || `${this.caption} scroll area`;\n const initialLoading = this.loading && !model.hasData;\n const initialError = !model.hasData && this.error;\n const hasGroupedStructure = model.grouped && model.groups.length > 0;\n const groupLoadingMore = hasGroupedStructure && this.groups.some(group => group.loadingMore);\n const viewportStyle = !this.boundedComposition && this.resolvedMaxHeight\n ? { '--ds-table-max-block-size': this.resolvedMaxHeight }\n : undefined;\n const hostStyle = this.fixedHeight\n ? { '--_table-fixed-block-size': this.resolvedHeight }\n : undefined;\n\n return (\n <Host\n class={{\n 'table-host': true,\n 'table-host--bounded': this.boundedComposition,\n 'table-host--fixed-height': this.fixedHeight,\n 'table-host--viewport-fit': this.fitViewport,\n }}\n style={hostStyle}\n >\n <div class={{\n 'ds-table': true,\n 'ds-table--bounded': this.boundedComposition,\n 'ds-table--contained-scroll': this.containedScroll,\n 'ds-table--fixed-height': this.fixedHeight,\n 'ds-table--viewport-fit': this.fitViewport,\n 'ds-table--viewport-fit-pending': this.fitViewport && !this.viewportFitSettled,\n 'ds-table--viewport-fit-settled': this.fitViewport && this.viewportFitSettled,\n 'ds-table--sticky-header': this.stickyHeader,\n 'ds-table--document-sticky-header': this.documentStickyHeader,\n 'ds-table--contained-sticky-header': this.stickyHeader && !this.documentStickyHeader,\n 'ds-table--caption-visible': this.captionVisibility === 'visible',\n 'ds-table--footer-visible': this.hasResultFooter,\n }} ref={element => {\n this.rootEl = element ?? null;\n }}>\n {this.renderCaptionBar()}\n <div\n class={{\n 'ds-table__frame': true,\n 'ds-table__frame--overflow-start': this.overflowStart,\n 'ds-table__frame--overflow-end': this.overflowEnd,\n }}\n ref={element => {\n this.frameEl = element ?? null;\n }}\n >\n {this.renderDocumentStickyHeader(model)}\n {this.renderStickyGroup(model)}\n {!this.documentStickyHeader && this.renderFloatingCollapseAll(model)}\n <div\n class={{\n 'ds-table__viewport': true,\n 'ds-focus-ring': this.scrollable,\n }}\n style={viewportStyle}\n ref={element => {\n this.viewportEl = element ?? null;\n }}\n role={this.scrollable ? 'region' : undefined}\n aria-label={this.scrollable ? regionLabel : undefined}\n tabIndex={this.scrollable ? 0 : undefined}\n >\n <table\n class={{\n 'ds-table__table': true,\n 'ds-table__table--selectable': model.selectable,\n 'ds-table__table--grouped': model.grouped,\n 'ds-table__table--native-group-sticky': model.grouped &&\n this.stickyHeader && !this.documentStickyHeader,\n }}\n style={model.tableStyle}\n aria-busy={initialLoading || this.loadingMore || groupLoadingMore ? 'true' : undefined}\n ref={element => {\n this.tableEl = element ?? null;\n }}\n >\n <caption class=\"ds-table__caption ds-visually-hidden\">\n <ds-text as=\"span\" variant=\"text-title-small\" emphasis={true} color=\"primary\">\n {this.caption}\n </ds-text>\n </caption>\n {this.renderColgroup(model)}\n {this.renderHeader(model, !this.documentStickyHeader)}\n {initialLoading\n ? this.renderSkeletonBody(model)\n : initialError\n ? this.renderStateBody('error', model.totalColumns)\n : model.hasData || hasGroupedStructure\n ? this.renderDataBodies(model)\n : this.renderStateBody('empty', model.totalColumns)}\n {!initialLoading && !initialError && this.renderLazyBody(model)}\n </table>\n </div>\n </div>\n {this.renderResultFooter()}\n <div class=\"ds-visually-hidden\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">\n {this.announcement}\n </div>\n </div>\n </Host>\n );\n }\n}\n",
1057
+ "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport {\n deriveTableSelectionState,\n formatTableResultSummary,\n nextTableSortState,\n tableColumnSize,\n tableModelIssues,\n tableRows,\n tableRowSelectionLabel,\n toggleAllLoadedTableRows,\n toggleTableGroupCollapsed,\n toggleTableGroupSelection,\n nextTableGroupsCollapsed,\n toggleTableRowSelection,\n} from './table-model';\nimport {\n resolveTableCellPresentation,\n type TableCellPresentation,\n} from './table-cell-model';\nimport {\n createTableRenderModel,\n type TableRenderModel,\n} from './table-render-model';\nimport { TableLayoutController } from './table-layout-controller';\nimport { TableLoadController } from './table-load-controller';\nimport { TableGroupLoadController } from './table-group-load-controller';\nimport type { PaginationChangeDetail } from '../Pagination/pagination-types';\nimport { resolvePaginationState } from '../Pagination/pagination-model';\nimport { resolveCssLengthPx } from '../../utils/resolve-css-length-px';\nimport { resolveTableFitPageSize } from './table-pagination-fit';\nimport {\n TableViewportFitController,\n type TableViewportFitMetrics,\n} from './table-viewport-fit-controller';\nimport type {\n TableCaptionVisibility,\n TableCellActionDetail,\n TableCellSkeleton,\n TableColumn,\n TableDataMode,\n TableGroup,\n TableGroupCollapseChangeDetail,\n TableGroupLoadMoreDetail,\n TableGroupingState,\n TableLoadMoreDetail,\n TableLoadMoreMode,\n TablePaginationState,\n TableRow,\n TableRowActivateDetail,\n TableSelectionChangeDetail,\n TableSelectionMode,\n TableSortChangeDetail,\n TableSortState,\n} from './table-types';\n\n@Component({\n tag: 'ds-table',\n styleUrls: [\n '../../utils/focus-ring.css',\n '../../utils/interaction-fill.css',\n 'Table.css',\n ],\n scoped: true,\n})\nexport class Table {\n @Element() el!: HTMLElement;\n /** Stable column definitions. Assign through JavaScript. */\n @Prop() columns: TableColumn[] = [];\n /** Ungrouped row data. Ignored while grouping is active. Assign through JavaScript. */\n @Prop() rows: TableRow[] = [];\n /** One level of application-owned grouped data. Assign through JavaScript. */\n @Prop() groups: TableGroup[] = [];\n /** Controlled grouping column. Applications supply groups in their final fixed order. */\n @Prop() grouping: TableGroupingState | null = null;\n /** Controlled member-row sort state. */\n @Prop() sort: TableSortState | null = null;\n /** Controlled collapsed group identities. Groups not listed remain expanded. */\n @Prop() collapsedGroupIds: string[] = [];\n\n /** Required accessible table name, retained as a native caption. */\n @Prop() caption!: string;\n /** Shows a matching presentational title bar above the native table frame. */\n @Prop() captionVisibility: TableCaptionVisibility = 'hidden';\n /**\n * Optional result summary footer. When both `displayedCount` and `totalCount`\n * are finite numbers, the table shows “Displaying {displayed} of {total}”.\n */\n @Prop() displayedCount: number | undefined;\n @Prop() totalCount: number | undefined;\n /** Supports {displayed} and {total} placeholders. */\n @Prop() resultSummaryLabel: string = 'Displaying {displayed} of {total}';\n @Prop() stickyHeader: boolean = false;\n /** Maximum scroll-region height. Numbers resolve to CSS pixels. */\n @Prop() maxHeight: string | number | undefined;\n /** Fixed height for the complete header, table frame, and footer composition. */\n @Prop() height: string | number | undefined;\n /** Fit the complete table composition to its nearest vertical scrollport. */\n @Prop() fitViewport: boolean = false;\n /** Reserved space above a viewport-fitted table once surrounding chrome is compact. */\n @Prop() viewportInsetBlockStart: string | number = 0;\n /** Reserved space below a viewport-fitted table. */\n @Prop() viewportInsetBlockEnd: string | number = 0;\n /** Optional explicit label for the horizontal/vertical scroll region. */\n @Prop() scrollLabel: string | undefined;\n\n @Prop() selectionMode: TableSelectionMode = 'none';\n /** Controlled selected row identities. IDs outside the loaded rows are preserved. */\n @Prop() selectedRowIds: string[] = [];\n\n /** Initial loading state. Existing rows stay visible; incremental loading uses loadingMore. */\n @Prop() loading: boolean = false;\n /** Initial-loading rows. Defaults to ten so bounded tables retain a useful filled viewport. */\n @Prop() skeletonRows: number = 10;\n @Prop() emptyHeading: string = 'No results';\n @Prop() emptyBody: string = 'No data is available.';\n /** Initial error state. Existing rows stay visible; incremental failures use loadMoreError. */\n @Prop() error: boolean = false;\n @Prop() errorHeading: string = 'Unable to load data';\n @Prop() errorBody: string = 'The data could not be loaded.';\n @Prop() emptyCellLabel: string = 'Not available';\n\n /** Top-level data-window strategy. Group member loading remains group-owned. */\n @Prop() dataMode: TableDataMode = 'infinite';\n /** Controlled top-level pagination state. Required when dataMode is pagination. */\n @Prop() pagination: TablePaginationState | null = null;\n @Prop() loadMoreMode: TableLoadMoreMode = 'auto';\n @Prop() hasMore: boolean = false;\n @Prop() loadingMore: boolean = false;\n @Prop() loadMoreError: string | undefined;\n /** Reset key for a new query/group/sort dataset. */\n @Prop() loadIdentity: string | number = 'default';\n /** IntersectionObserver root margin in CSS pixels. */\n @Prop() loadMoreThreshold: number = 0;\n @Prop() loadMoreLabel: string = 'Load more';\n @Prop() retryLabel: string = 'Retry';\n @Prop() loadingMoreLabel: string = 'Loading more items';\n @Prop() endOfResultsLabel: string = 'All results loaded';\n /** Supports {count} and {total} placeholders. */\n @Prop() rowsLoadedLabel: string = '{count} more rows loaded. {total} rows loaded.';\n @Prop() groupLoadMoreLabel: string = 'Load more';\n /** Supports the {group} placeholder. */\n @Prop() groupLoadMoreAriaLabel: string = 'Load more {group} results';\n /** Supports the {group} placeholder. */\n @Prop() groupRetryLabel: string = 'Retry loading {group} results';\n /** Supports the {group} placeholder. */\n @Prop() groupLoadingMoreLabel: string = 'Loading more items';\n /** Supports the {group} placeholder. */\n @Prop() groupEndOfResultsLabel: string = 'All {group} results loaded';\n /** Supports {group}, {count}, {loaded}, and {total} placeholders. */\n @Prop() groupRowsLoadedLabel: string = '{count} more rows loaded in {group}. {loaded} of {total} rows loaded.';\n\n @Event() dsSortChange!: EventEmitter<TableSortChangeDetail>;\n @Event() dsGroupCollapseChange!: EventEmitter<TableGroupCollapseChangeDetail>;\n @Event() dsSelectionChange!: EventEmitter<TableSelectionChangeDetail>;\n @Event() dsLoadMore!: EventEmitter<TableLoadMoreDetail>;\n @Event() dsGroupLoadMore!: EventEmitter<TableGroupLoadMoreDetail>;\n @Event() dsPaginationChange!: EventEmitter<PaginationChangeDetail>;\n @Event() dsCellAction!: EventEmitter<TableCellActionDetail>;\n @Event() dsRowActivate!: EventEmitter<TableRowActivateDetail>;\n\n @State() private overflowStart = false;\n @State() private overflowEnd = false;\n @State() private scrollable = false;\n @State() private announcement = '';\n @State() private activeStickyGroupId: string | null = null;\n @State() private viewportFitSettled = false;\n @State() private headerPresent = false;\n @State() private fitPageSize: number | undefined;\n\n private rootEl: HTMLElement | null = null;\n private viewportEl: HTMLElement | null = null;\n private frameEl: HTMLElement | null = null;\n private interactiveHeadEl: HTMLTableSectionElement | null = null;\n private collapseAllOverlayEl: HTMLElement | null = null;\n private stickyHeaderTableEl: HTMLTableElement | null = null;\n private tableEl: HTMLTableElement | null = null;\n private sentinelEl: HTMLElement | null = null;\n private incrementalWindowActive = false;\n private readonly groupSentinelEls = new Map<string, HTMLElement>();\n private previousModelWarning = '';\n private hasLoaded = false;\n private renderedModel: TableRenderModel | null = null;\n private stickyGroupConnected = false;\n private headerSlotObserver: MutationObserver | null = null;\n private fitResizeObserver: ResizeObserver | null = null;\n private fitObservedViewport: HTMLElement | null = null;\n private fitObservedTable: HTMLTableElement | null = null;\n private fitMeasurementPending = false;\n private readonly layoutController = new TableLayoutController({\n elements: () => ({\n viewport: this.viewportEl,\n stickyHeaderTable: this.stickyHeaderTableEl,\n collapseAllOverlay: this.collapseAllOverlayEl,\n frame: this.frameEl,\n interactiveHead: this.interactiveHeadEl,\n }),\n mode: () => ({\n documentStickyHeader: this.documentStickyHeader,\n floatingCollapseAll: this.renderedModel?.collapseAllHost?.mode === 'floating',\n clampVerticalOverscroll: this.fitViewport && this.viewportFitSettled,\n }),\n verticalEdgeWheel: deltaY => this.viewportFitController.scrollOuterBy(deltaY),\n overflowChanged: state => {\n if (state.start !== this.overflowStart) this.overflowStart = state.start;\n if (state.end !== this.overflowEnd) this.overflowEnd = state.end;\n if (state.scrollable !== this.scrollable) this.scrollable = state.scrollable;\n },\n });\n private readonly loadController = new TableLoadController({\n state: () => ({\n lazyLoading: this.dataMode === 'infinite' && !this.grouped,\n loadMoreMode: this.loadMoreMode,\n hasMore: this.hasMore,\n loadingMore: this.loadingMore,\n loadMoreError: this.loadMoreError,\n loadIdentity: this.loadIdentity,\n loadMoreThreshold: this.loadMoreThreshold,\n containedScroll: this.containedScroll,\n loadingMoreLabel: this.loadingMoreLabel,\n endOfResultsLabel: this.endOfResultsLabel,\n rowsLoadedLabel: this.rowsLoadedLabel,\n loadedRowCount: this.currentLoadedRowCount(),\n viewport: this.viewportEl,\n sentinel: this.sentinelEl,\n }),\n announce: message => {\n this.announcement = message;\n },\n request: detail => {\n this.dsLoadMore.emit(detail);\n },\n });\n private readonly groupLoadController = new TableGroupLoadController({\n state: () => ({\n enabled: this.grouped,\n loadMoreMode: this.loadMoreMode,\n loadMoreThreshold: this.loadMoreThreshold,\n containedScroll: this.containedScroll,\n groups: this.groups,\n viewport: this.viewportEl,\n sentinels: this.groupSentinelEls,\n loadingMoreLabel: this.groupLoadingMoreLabel,\n endOfResultsLabel: this.groupEndOfResultsLabel,\n rowsLoadedLabel: this.groupRowsLoadedLabel,\n }),\n announce: message => {\n this.announcement = message;\n },\n request: detail => {\n this.dsGroupLoadMore.emit(detail);\n },\n });\n private readonly viewportFitController = new TableViewportFitController({\n enabled: () => this.fitViewport,\n elements: () => ({ host: this.el, surface: this.rootEl }),\n insets: () => ({\n blockStart: this.viewportInsetBlockStart,\n blockEnd: this.viewportInsetBlockEnd,\n }),\n fitChanged: (metrics: TableViewportFitMetrics | null) => {\n const settled = metrics?.settled ?? false;\n if (settled !== this.viewportFitSettled) this.viewportFitSettled = settled;\n },\n });\n\n componentWillLoad(): void {\n this.incrementalWindowActive = this.hasIncrementalState;\n this.syncHeaderSlotPresence();\n this.loadController.initialize();\n this.groupLoadController.initialize();\n this.warnModelIssues();\n }\n\n componentDidLoad(): void {\n this.hasLoaded = true;\n this.layoutController.connect();\n this.loadController.connect();\n this.groupLoadController.connect();\n this.viewportFitController.connect();\n this.syncStickyGroupConnection();\n this.connectHeaderSlotObserver();\n this.connectFitObserver();\n this.syncFitPageSize();\n }\n\n componentDidRender(): void {\n this.layoutController.refresh();\n this.loadController.refresh();\n this.groupLoadController.refresh();\n this.viewportFitController.refresh();\n this.syncStickyGroupConnection();\n this.connectFitObserver();\n this.syncFitPageSize();\n if (this.stickyGroupConnected) this.updateStickyGroup();\n }\n\n connectedCallback(): void {\n if (!this.hasLoaded) return;\n this.layoutController.connect();\n this.loadController.connect();\n this.groupLoadController.connect();\n this.viewportFitController.connect();\n this.syncStickyGroupConnection();\n this.connectHeaderSlotObserver();\n this.connectFitObserver();\n }\n\n disconnectedCallback(): void {\n this.layoutController.disconnect();\n this.loadController.disconnect();\n this.groupLoadController.disconnect();\n this.viewportFitController.disconnect();\n this.disconnectStickyGroup();\n this.headerSlotObserver?.disconnect();\n this.headerSlotObserver = null;\n this.disconnectFitObserver();\n }\n\n private syncHeaderSlotPresence = () => {\n this.headerPresent = !!this.el.querySelector('[slot=\"header\"]');\n };\n\n private connectHeaderSlotObserver(): void {\n if (this.headerSlotObserver || typeof MutationObserver === 'undefined') return;\n this.headerSlotObserver = new MutationObserver(this.syncHeaderSlotPresence);\n this.headerSlotObserver.observe(this.el, { childList: true });\n this.syncHeaderSlotPresence();\n }\n\n private connectStickyGroup(): void {\n if (this.stickyGroupConnected || typeof window === 'undefined') return;\n this.stickyGroupConnected = true;\n document.addEventListener('scroll', this.updateStickyGroup, {\n capture: true,\n passive: true,\n });\n window.addEventListener('resize', this.updateStickyGroup, { passive: true });\n this.updateStickyGroup();\n }\n\n private syncStickyGroupConnection(): void {\n if (this.documentStickyHeader && this.grouped) {\n this.connectStickyGroup();\n return;\n }\n\n this.disconnectStickyGroup();\n this.frameEl?.style.removeProperty('--_table-sticky-group-top');\n if (this.activeStickyGroupId !== null) {\n this.activeStickyGroupId = null;\n }\n }\n\n private disconnectStickyGroup(): void {\n if (!this.stickyGroupConnected || typeof window === 'undefined') return;\n this.stickyGroupConnected = false;\n document.removeEventListener('scroll', this.updateStickyGroup, true);\n window.removeEventListener('resize', this.updateStickyGroup);\n }\n\n private readonly updateStickyGroup = (): void => {\n if (!this.documentStickyHeader || !this.grouped || !this.frameEl || !this.tableEl) {\n if (this.activeStickyGroupId !== null) this.activeStickyGroupId = null;\n return;\n }\n\n const stickyHeader = this.frameEl.querySelector<HTMLElement>(\n '.ds-table__document-sticky-header',\n );\n if (!stickyHeader) return;\n const threshold = stickyHeader.getBoundingClientRect().bottom;\n let activeGroupId: string | null = null;\n let activeGroupTop = threshold;\n for (const body of this.tableEl.querySelectorAll<HTMLElement>('tbody[data-group-id]')) {\n const source = body.querySelector<HTMLElement>('.ds-table__group-cell');\n if (!source) continue;\n const sourceRect = source.getBoundingClientRect();\n const bodyRect = body.getBoundingClientRect();\n if (sourceRect.top <= threshold && bodyRect.bottom > threshold) {\n activeGroupId = body.dataset.groupId ?? null;\n // A sticky section is bounded by its own row group. As that boundary\n // approaches the sticky lane, move the outgoing section bar with it so\n // the incoming source bar pushes it away instead of rendering beneath it.\n activeGroupTop = Math.min(threshold, bodyRect.bottom - sourceRect.height);\n }\n }\n\n const frameRect = this.frameEl.getBoundingClientRect();\n this.frameEl.style.setProperty(\n '--_table-sticky-group-top',\n `${activeGroupTop - frameRect.top}px`,\n );\n\n if (activeGroupId !== this.activeStickyGroupId) {\n this.activeStickyGroupId = activeGroupId;\n return;\n }\n };\n\n @Watch('columns')\n @Watch('grouping')\n handleStructureChange(): void {\n this.warnModelIssues();\n this.loadController.structureChanged();\n this.groupLoadController.structureChanged();\n }\n\n @Watch('rows')\n @Watch('groups')\n handleDataChange(): void {\n if (this.grouped) this.groupLoadController.dataChanged();\n else this.loadController.dataChanged();\n this.warnModelIssues();\n }\n\n @Watch('loadIdentity')\n handleLoadIdentityChange(): void {\n this.incrementalWindowActive = this.hasIncrementalState;\n this.loadController.identityChanged();\n }\n\n @Watch('loadMoreMode')\n @Watch('loadMoreThreshold')\n handleLazyConfigurationChange(): void {\n this.loadController.configurationChanged();\n this.groupLoadController.configurationChanged();\n }\n\n @Watch('dataMode')\n handleDataModeChange(): void {\n this.incrementalWindowActive = this.hasIncrementalState;\n this.handleLazyConfigurationChange();\n }\n\n @Watch('pagination')\n handlePaginationChange(\n pagination: TablePaginationState | null,\n previous: TablePaginationState | null,\n ): void {\n if (\n pagination?.pageSizeMode === 'fit' &&\n pagination.fitIdentity !== previous?.fitIdentity\n ) {\n this.fitMeasurementPending = true;\n }\n }\n\n @Watch('loadingMore')\n handleLoadingMoreChange(loading: boolean): void {\n if (loading) this.incrementalWindowActive = true;\n if (!this.grouped) this.loadController.loadingChanged(loading);\n }\n\n @Watch('loadMoreError')\n handleLoadMoreErrorChange(error: string | undefined): void {\n if (error?.trim()) this.incrementalWindowActive = true;\n if (!this.grouped) this.loadController.errorChanged(error);\n }\n\n @Watch('hasMore')\n handleHasMoreChange(hasMore: boolean, hadMore: boolean): void {\n if (hasMore) this.incrementalWindowActive = true;\n if (!this.grouped) this.loadController.hasMoreChanged(hasMore, hadMore);\n }\n\n private get grouped(): boolean {\n return this.grouping !== null;\n }\n\n private get hasIncrementalState(): boolean {\n return this.dataMode === 'infinite' &&\n (this.hasMore || this.loadingMore || !!this.loadMoreError?.trim());\n }\n\n private get selectable(): boolean {\n return this.selectionMode === 'multiple';\n }\n\n private get documentStickyHeader(): boolean {\n return this.stickyHeader && !this.containedScroll;\n }\n\n private get fixedHeight(): boolean {\n return !this.fitViewport && this.resolvedHeight !== undefined;\n }\n\n private get boundedComposition(): boolean {\n return this.fitViewport || this.fixedHeight;\n }\n\n private get containedScroll(): boolean {\n return this.boundedComposition || this.resolvedMaxHeight !== undefined;\n }\n\n private connectFitObserver(): void {\n if (typeof ResizeObserver === 'undefined') return;\n if (this.fitObservedViewport === this.viewportEl && this.fitObservedTable === this.tableEl) {\n return;\n }\n this.disconnectFitObserver();\n this.fitObservedViewport = this.viewportEl;\n this.fitObservedTable = this.tableEl;\n this.fitResizeObserver = new ResizeObserver(this.syncFitPageSize);\n if (this.fitObservedViewport) this.fitResizeObserver.observe(this.fitObservedViewport);\n if (this.fitObservedTable) this.fitResizeObserver.observe(this.fitObservedTable);\n }\n\n private disconnectFitObserver(): void {\n this.fitResizeObserver?.disconnect();\n this.fitResizeObserver = null;\n this.fitObservedViewport = null;\n this.fitObservedTable = null;\n }\n\n private readonly syncFitPageSize = (): void => {\n const pagination = this.dataMode === 'pagination' ? this.pagination : null;\n if (!pagination?.fitToPage || !this.containedScroll || !this.viewportEl || !this.tableEl) {\n if (this.fitPageSize !== undefined) this.fitPageSize = undefined;\n this.fitMeasurementPending = false;\n return;\n }\n const header = this.tableEl.querySelector<HTMLElement>(\n '.ds-table__head .ds-table__header-row',\n );\n const item = this.tableEl.querySelector<HTMLElement>(\n this.grouped ? '.ds-table__group-row' : '.ds-table__body .ds-table__row',\n );\n const itemBlockSize = item?.getBoundingClientRect().height || resolveCssLengthPx(\n 'var(--ds-table-row-min-block-size, var(--dimension-size-500))',\n 0,\n this.rootEl ?? this.el,\n );\n const next = resolveTableFitPageSize({\n viewportBlockSize: this.viewportEl.clientHeight,\n headerBlockSize: header?.getBoundingClientRect().height ?? 0,\n itemBlockSize,\n });\n if (next !== this.fitPageSize) this.fitPageSize = next;\n if (!this.fitMeasurementPending || pagination.pageSizeMode !== 'fit' || next === undefined) {\n return;\n }\n this.fitMeasurementPending = false;\n if (next === pagination.pageSize && pagination.pageIndex === 0) return;\n this.dsPaginationChange.emit({\n pageIndex: 0,\n pageSize: next,\n pageSizeMode: 'fit',\n totalItems: pagination.totalItems,\n pageSizeOptions: pagination.pageSizeOptions ?? [25, 50, 100, 200],\n fitToPage: true,\n fitPageSize: next,\n fitPageSizeLabel: pagination.fitPageSizeLabel ?? 'Fit to page',\n fitPageSizeTriggerLabel: pagination.fitPageSizeTriggerLabel ?? 'Fit',\n itemLabel: pagination.itemLabel ?? 'items',\n pageSizeLabel: pagination.pageSizeLabel ?? 'Items',\n ariaLabel: pagination.ariaLabel ?? `${this.caption} pagination`,\n previousPageIndex: pagination.pageIndex,\n previousPageSize: pagination.pageSize,\n previousPageSizeMode: 'fit',\n reason: 'fit',\n });\n };\n\n private handlePaginationKeyDown(event: KeyboardEvent): void {\n const pagination = this.dataMode === 'pagination' ? this.pagination : null;\n if (\n !pagination ||\n this.loading ||\n event.defaultPrevented ||\n event.altKey ||\n event.ctrlKey ||\n event.metaKey ||\n event.shiftKey ||\n (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')\n ) {\n return;\n }\n\n const fromDirectionalControl = event.composedPath().some(node => {\n if (!(node instanceof HTMLElement)) return false;\n return node.tagName === 'DS-SELECT' ||\n ['INPUT', 'SELECT', 'TEXTAREA'].includes(node.tagName) ||\n node.isContentEditable ||\n node.getAttribute('role') === 'slider';\n });\n if (fromDirectionalControl) return;\n\n const state = resolvePaginationState(pagination);\n const pageIndex = event.key === 'ArrowLeft' ? state.pageIndex - 1 : state.pageIndex + 1;\n if (pageIndex < 0 || pageIndex >= state.totalPages) return;\n\n event.preventDefault();\n this.dsPaginationChange.emit({\n pageIndex,\n pageSize: state.pageSize,\n pageSizeMode: state.pageSizeMode,\n totalItems: state.totalItems,\n pageSizeOptions: state.pageSizeOptions,\n fitToPage: pagination.fitToPage ?? false,\n fitPageSize: this.fitPageSize ?? pagination.fitPageSize,\n fitPageSizeLabel: pagination.fitPageSizeLabel ?? 'Fit to page',\n fitPageSizeTriggerLabel: pagination.fitPageSizeTriggerLabel ?? 'Fit',\n itemLabel: pagination.itemLabel ?? 'items',\n pageSizeLabel: pagination.pageSizeLabel ?? 'Items',\n ariaLabel: pagination.ariaLabel ?? `${this.caption} pagination`,\n previousPageIndex: state.pageIndex,\n previousPageSize: state.pageSize,\n previousPageSizeMode: state.pageSizeMode,\n reason: 'page',\n });\n }\n\n private currentLoadedRows(): TableRow[] {\n return tableRows(this.rows, this.groups, this.grouped);\n }\n\n private currentLoadedRowCount(): number {\n return this.grouped\n ? this.groups.reduce((count, group) => count + group.rows.length, 0)\n : this.rows.length;\n }\n\n private createRenderModel(): TableRenderModel {\n return createTableRenderModel({\n columns: this.columns,\n rows: this.rows,\n groups: this.groups,\n grouped: this.grouped,\n selectionMode: this.selectionMode,\n selectedRowIds: this.selectedRowIds,\n collapsedGroupIds: this.collapsedGroupIds,\n });\n }\n\n private get resolvedMaxHeight(): string | undefined {\n if (this.maxHeight == null || this.maxHeight === '') return undefined;\n return typeof this.maxHeight === 'number' ? `${Math.max(0, this.maxHeight)}px` : this.maxHeight;\n }\n\n private get resolvedHeight(): string | undefined {\n if (this.height == null || this.height === '') return undefined;\n return typeof this.height === 'number' ? `${Math.max(0, this.height)}px` : this.height;\n }\n\n private warnModelIssues(): void {\n const issues = tableModelIssues(this.columns, this.rows, this.groups, this.grouped);\n if (!this.caption?.trim()) issues.unshift('A non-empty caption is required.');\n if (this.grouping && !this.columns.some(column => column.id === this.grouping!.columnId)) {\n issues.push(`Grouping references unknown column id: ${this.grouping.columnId}`);\n }\n if (\n this.sort &&\n !this.columns.some(column =>\n column.id === this.sort!.columnId ||\n column.headerSegments?.some(segment => segment.sortKey === this.sort!.columnId),\n )\n ) {\n issues.push(`Sorting references unknown column id: ${this.sort.columnId}`);\n }\n const stickyStart = this.columns.filter(column => column.sticky === 'start');\n const stickyEnd = this.columns.filter(column => column.sticky === 'end');\n if (stickyStart.length > 1 || (this.selectable && stickyStart.length > 0)) {\n issues.push('Only one sticky start column is supported, and row selection already owns that lane.');\n }\n if (stickyEnd.length > 1) issues.push('Only one sticky end column is supported.');\n for (const column of [...stickyStart, ...stickyEnd]) {\n if (!tableColumnSize(column)) issues.push(`Sticky column ${column.id} requires an explicit size.`);\n }\n const message = issues.join(' ');\n if (!message || message === this.previousModelWarning) return;\n this.previousModelWarning = message;\n console.warn(`[ds-table] ${message}`);\n }\n\n private emitSort(column: TableColumn, sortKey = column.id): void {\n if (!column.sortable) return;\n this.dsSortChange.emit({ sort: nextTableSortState(this.sort, sortKey) });\n }\n\n private sortButtonLabel(column: TableColumn, sortKey = column.id, label = column.header): string {\n if (this.sort?.columnId !== sortKey) return `Sort ${label} ascending`;\n if (this.sort.direction === 'asc') return `Sort ${label} descending. Currently ascending.`;\n return `Sort ${label} ascending. Currently descending.`;\n }\n\n private emitRowSelection(row: TableRow): void {\n const next = toggleTableRowSelection(this.selectedRowIds, row);\n const selected = next.includes(row.id);\n this.dsSelectionChange.emit({\n selectedRowIds: next,\n scope: 'row',\n changedRowId: row.id,\n selected,\n });\n }\n\n private emitAllSelection(): void {\n const loadedRows = this.currentLoadedRows();\n const selection = deriveTableSelectionState(loadedRows, this.selectedRowIds);\n this.dsSelectionChange.emit({\n selectedRowIds: toggleAllLoadedTableRows(this.selectedRowIds, loadedRows),\n scope: 'all-loaded',\n selected: !selection.allSelected,\n });\n }\n\n private emitGroupSelection(group: TableGroup): void {\n const state = deriveTableSelectionState(group.rows, this.selectedRowIds);\n this.dsSelectionChange.emit({\n selectedRowIds: toggleTableGroupSelection(this.selectedRowIds, group.rows),\n scope: 'group',\n groupId: group.id,\n selected: !state.allSelected,\n });\n }\n\n private rowEventOwnsActivation(event: Event): boolean {\n const currentTarget = event.currentTarget;\n return !event.composedPath().some(target => {\n if (!(target instanceof HTMLElement) || target === currentTarget) return false;\n return target.matches(\n 'button, a, input, select, textarea, [role=\"button\"], [role=\"checkbox\"], ds-button-unfilled',\n );\n });\n }\n\n private emitRowActivation(row: TableRow, event: Event): void {\n if (!row.interactive || row.disabled || !this.rowEventOwnsActivation(event)) return;\n this.dsRowActivate.emit({ rowId: row.id });\n }\n\n private handleRowKeydown(row: TableRow, event: KeyboardEvent): void {\n if (event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' ')) return;\n event.preventDefault();\n this.emitRowActivation(row, event);\n }\n\n private renderSelectionControl(\n label: string,\n checked: boolean,\n indeterminate: boolean,\n disabled: boolean,\n onActivate: () => void,\n ) {\n return (\n <button\n class=\"ds-table__selection-control ds-focus-ring ds-interaction-fill__content\"\n type=\"button\"\n role=\"checkbox\"\n aria-label={label}\n aria-checked={indeterminate ? 'mixed' : String(checked)}\n disabled={disabled}\n onClick={onActivate}\n >\n <ds-checkbox\n label=\"\"\n size=\"md\"\n checked={checked}\n indeterminate={indeterminate}\n presentation={true}\n />\n </button>\n );\n }\n\n private renderStickyEdge(sticky: TableColumn['sticky']) {\n if (!sticky) return null;\n return (\n <span\n class={`ds-table__sticky-edge ds-table__sticky-edge--${sticky}`}\n aria-hidden=\"true\"\n />\n );\n }\n\n private renderCollapseAllButton() {\n return (\n <ds-button-unfilled\n class=\"ds-table__collapse-all\"\n variant=\"icon\"\n icon=\"ChevronDownUp\"\n size=\"xs\"\n style={{ width: 'var(--dimension-size-300)' }}\n aria-label=\"Collapse all groups\"\n hasBorder={false}\n activeFill={false}\n pressScale={false}\n onDsClick={event => {\n event.stopPropagation();\n this.emitAllGroupsCollapse();\n }}\n />\n );\n }\n\n private renderFloatingCollapseAll(model: TableRenderModel) {\n if (model.collapseAllHost?.mode !== 'floating') return null;\n return (\n <span\n class=\"ds-table__collapse-all-overlay\"\n ref={element => {\n this.collapseAllOverlayEl = element ?? null;\n }}\n >\n <span class=\"ds-table__collapse-all-surface ds-control-elevation ds-control-elevation--md\">\n {this.renderCollapseAllButton()}\n </span>\n </span>\n );\n }\n\n private renderColumnHeader(\n column: TableColumn,\n model: TableRenderModel,\n interactive = true,\n presentational = false,\n ) {\n const groupedColumn = this.grouping?.columnId === column.id;\n const headerSegments = column.headerSegments?.length\n ? column.headerSegments\n : [{ label: column.header, sortKey: column.id }];\n const activeMemberSegment = headerSegments.find(\n segment => segment.sortKey === this.sort?.columnId,\n );\n const activeMemberSort = !!activeMemberSegment;\n const activeSort = activeMemberSort;\n const align = column.align ?? 'start';\n const direction = activeMemberSort ? this.sort!.direction : undefined;\n const memberAriaSort = activeMemberSort\n ? (this.sort!.direction === 'asc' ? 'ascending' : 'descending')\n : undefined;\n\n const labelControl = (\n <span class=\"ds-table__header-labels\">\n {headerSegments.map((segment, index) => {\n const segmentActive = activeMemberSegment?.sortKey === segment.sortKey;\n const segmentInteractive = interactive && !!column.sortable;\n const label = (\n <ds-text\n class=\"ds-table__header-label-box ds-control-label-box\"\n as=\"span\"\n variant=\"text-caption\"\n emphasis={segmentActive}\n color=\"inherit\"\n lineTruncation={1}\n >\n {segment.label}\n </ds-text>\n );\n const control = segmentInteractive ? (\n <button\n class=\"ds-table__header-label ds-table__header-label--interactive ds-focus-ring\"\n type=\"button\"\n aria-label={this.sortButtonLabel(column, segment.sortKey, segment.label)}\n data-sort-control=\"label\"\n data-sort-key={segment.sortKey}\n data-sort-active={segmentActive ? 'true' : undefined}\n onClick={() => this.emitSort(column, segment.sortKey)}\n >\n {label}\n </button>\n ) : (\n <span class=\"ds-table__header-label ds-table__header-static\">{label}</span>\n );\n\n return (\n <span class=\"ds-table__header-segment\" key={segment.sortKey}>\n {control}\n {index < headerSegments.length - 1 && (\n <ds-text\n class=\"ds-table__header-separator\"\n as=\"span\"\n variant=\"text-caption\"\n color=\"tertiary\"\n aria-hidden=\"true\"\n >\n {segment.separator ?? '/'}\n </ds-text>\n )}\n </span>\n );\n })}\n </span>\n );\n const sortControl = interactive ? (\n <span class=\"ds-table__sort-slot\">\n {activeSort && (\n <ds-button-unfilled\n class=\"ds-table__sort-direction\"\n variant=\"icon\"\n icon={direction === 'asc' ? 'ArrowUp' : 'ArrowDown'}\n size=\"xs\"\n aria-label={this.sortButtonLabel(\n column,\n activeMemberSegment?.sortKey ?? column.id,\n activeMemberSegment?.label ?? column.header,\n )}\n hasBorder={false}\n activeFill={false}\n isActive={true}\n pressScale={false}\n data-sort-control=\"direction\"\n onDsClick={() => this.emitSort(column, activeMemberSegment?.sortKey ?? column.id)}\n />\n )}\n </span>\n ) : null;\n const collapseHost = model.collapseAllHost;\n const actionCollapseHost = collapseHost?.mode === 'action' && collapseHost.columnId === column.id;\n const blankActionCollapseHost = actionCollapseHost &&\n !column.header.trim() &&\n !column.headerSegments?.length;\n const collapseControl =\n interactive && actionCollapseHost ? (\n <span class=\"ds-table__collapse-slot\">\n {this.renderCollapseAllButton()}\n </span>\n ) : null;\n\n return (\n <th\n key={column.id}\n class={{\n 'ds-table__header-cell': true,\n [`ds-table__cell--align-${align}`]: true,\n 'ds-table__cell--sticky-start': column.sticky === 'start',\n 'ds-table__cell--sticky-end': column.sticky === 'end',\n 'ds-table__header-cell--collapse-all': actionCollapseHost,\n }}\n scope={presentational ? undefined : 'col'}\n aria-sort={presentational ? undefined : memberAriaSort}\n data-column-id={column.id}\n data-grouped={groupedColumn ? 'true' : undefined}\n data-sort-active={activeSort ? 'true' : undefined}\n >\n {!column.header.trim() && column.headerLabel?.trim() && (\n <span class=\"ds-visually-hidden\">{column.headerLabel}</span>\n )}\n {blankActionCollapseHost ? (\n <span class=\"ds-table__header-content ds-table__header-content--collapse-all\">\n {collapseControl}\n </span>\n ) : (\n <span class=\"ds-table__header-content\">\n {align === 'end' && sortControl}\n {align === 'center' && (\n <span\n class=\"ds-table__sort-slot ds-table__sort-slot--balance\"\n data-sort-balance=\"true\"\n aria-hidden=\"true\"\n />\n )}\n {labelControl}\n {align !== 'end' && sortControl}\n {collapseControl}\n </span>\n )}\n {this.renderStickyEdge(column.sticky)}\n </th>\n );\n }\n\n private renderColgroup(model: TableRenderModel) {\n return (\n <colgroup>\n {model.selectable && <col class=\"ds-table__selection-column\" />}\n {this.columns.map(column => {\n const width = tableColumnSize(column);\n const flexible = column.id === model.flexibleColumnId;\n return (\n <col\n key={column.id}\n class={{\n 'ds-table__action-column': column.kind === 'action',\n 'ds-table__flexible-column': flexible,\n }}\n style={width && !flexible ? { width } : undefined}\n />\n );\n })}\n </colgroup>\n );\n }\n\n private renderHeader(\n model: TableRenderModel,\n interactive = true,\n presentational = false,\n ) {\n const selection = model.selection;\n return (\n <thead class={{\n 'ds-table__head': true,\n 'ds-table__head--semantic-copy': !interactive,\n }} ref={element => {\n if (interactive) this.interactiveHeadEl = element ?? null;\n }}>\n <tr class=\"ds-table__header-row\">\n {model.selectable && (\n <th\n class=\"ds-table__header-cell ds-table__selection-cell ds-table__cell--sticky-start\"\n scope={presentational ? undefined : 'col'}\n >\n {interactive ? this.renderSelectionControl(\n selection.allSelected ? 'Deselect all loaded rows' : 'Select all loaded rows',\n selection.allSelected,\n selection.indeterminate,\n selection.selectableRowIds.length === 0,\n () => this.emitAllSelection(),\n ) : (\n <span class=\"ds-visually-hidden\">Select rows</span>\n )}\n {this.renderStickyEdge('start')}\n </th>\n )}\n {this.columns.map(column => (\n this.renderColumnHeader(column, model, interactive, presentational)\n ))}\n </tr>\n </thead>\n );\n }\n\n private renderCellValue(\n cell: TableCellPresentation,\n column: TableColumn,\n row: TableRow,\n ) {\n if (cell.kind === 'blank') return null;\n\n if (cell.kind === 'empty') {\n return (\n <ds-text\n class=\"ds-table__cell-track ds-table__cell-track--text\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"tertiary\"\n >\n <span aria-hidden=\"true\">—</span>\n <span class=\"ds-visually-hidden\">{this.emptyCellLabel}</span>\n </ds-text>\n );\n }\n\n if (cell.kind === 'icon') {\n const value = cell.value;\n return (\n <ds-icon\n name={value.icon}\n size=\"md\"\n color={value.color ?? 'secondary'}\n label={value.label}\n />\n );\n }\n\n if (cell.kind === 'image') {\n const value = cell.value;\n return (\n <span class=\"ds-table__cell-image\">\n {value.src ? (\n <img class=\"ds-table__cell-image-content\" src={value.src} alt={value.alt} loading=\"lazy\" />\n ) : (\n <span\n class=\"ds-table__cell-image-placeholder\"\n role=\"img\"\n aria-label={value.alt}\n />\n )}\n </span>\n );\n }\n\n if (cell.kind === 'action') {\n const value = cell.value;\n return (\n <ds-button-unfilled\n variant={value.variant ?? 'label'}\n size=\"md\"\n isInset={true}\n insetDepth=\"double\"\n label={value.label ?? ''}\n icon={value.icon ?? ''}\n aria-label={value.ariaLabel ?? null}\n hasBorder={value.hasBorder ?? false}\n isInactive={value.isInactive ?? false}\n isLoading={value.isLoading ?? false}\n onDsClick={event => {\n event.stopPropagation();\n this.dsCellAction.emit({\n actionId: value.actionId,\n rowId: row.id,\n columnId: column.id,\n });\n }}\n />\n );\n }\n\n if (cell.kind === 'tag') {\n const value = cell.value;\n const variant = cell.variant;\n const tag = (\n <ds-tag\n label={value.label}\n intent={value.intent ?? 'neutral'}\n contrast={value.contrast ?? 'faint'}\n size={variant === 'text-with-tag' ? 'sm' : 'md'}\n icon={value.icon ?? ''}\n rounded={value.rounded ?? false}\n isInset\n insetDepth=\"double\"\n />\n );\n\n if (variant === 'tag-only') return tag;\n\n return (\n <span class={`ds-table__cell-tag-stack ds-table__cell-tag-stack--${variant}`}>\n {variant === 'tag-with-text' && tag}\n <ds-text\n class=\"ds-table__cell-tag-text ds-table__cell-track\"\n as=\"span\"\n variant={variant === 'tag-with-text' ? 'text-body-small' : 'text-body-medium'}\n color=\"secondary\"\n lineTruncation={1}\n >\n {value.text}\n </ds-text>\n {variant === 'text-with-tag' && (\n <span class=\"ds-table__cell-tag-control-track\">{tag}</span>\n )}\n </span>\n );\n }\n\n const text = cell.value;\n const wraps = cell.wraps;\n\n return (\n <span class={{ 'ds-table__cell-copy': true, 'ds-table__cell-copy--wrap': wraps }}>\n <ds-text\n class=\"ds-table__cell-primary ds-table__cell-track ds-table__cell-track--text\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"primary\"\n lineTruncation={wraps ? 'none' : 1}\n wrap={wraps ? 'wrap' : 'nowrap'}\n fontFeature={text.fontFeature ?? 'normal'}\n >\n {text.primary}\n </ds-text>\n {text.secondary !== undefined && text.secondary !== '' && (\n <ds-text\n class=\"ds-table__cell-secondary ds-table__cell-track ds-table__cell-track--text\"\n as=\"span\"\n variant={cell.primaryText ? 'text-body-medium' : 'text-body-small'}\n color={text.secondaryColor ?? (cell.primaryText ? 'primary' : 'secondary')}\n lineTruncation={wraps ? 'none' : 1}\n wrap={wraps ? 'wrap' : 'nowrap'}\n >\n {text.secondary}\n </ds-text>\n )}\n </span>\n );\n }\n\n private renderRow(row: TableRow, model: TableRenderModel) {\n const selected = model.selectedRowIds.has(row.id);\n const rowSelectable = row.selectable !== false && !row.disabled;\n return (\n <tr\n key={row.id}\n class={{\n 'ds-table__row': true,\n 'ds-table__row--selected': selected,\n 'ds-table__row--disabled': !!row.disabled,\n 'ds-table__row--interactive': !!row.interactive && !row.disabled,\n 'ds-focus-ring': !!row.interactive && !row.disabled,\n }}\n data-row-id={row.id}\n data-selected={selected ? 'true' : undefined}\n aria-disabled={row.disabled ? 'true' : undefined}\n tabIndex={row.interactive && !row.disabled ? 0 : undefined}\n onClick={event => this.emitRowActivation(row, event)}\n onKeyDown={event => this.handleRowKeydown(row, event)}\n >\n {model.selectable && (\n <td class={{\n 'ds-table__cell': true,\n 'ds-table__selection-cell': true,\n 'ds-table__cell--sticky-start': true,\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--grouped': true,\n 'ds-interaction-fill--selected': selected,\n }}>\n {this.renderSelectionControl(\n `${selected ? 'Deselect' : 'Select'} ${tableRowSelectionLabel(row, this.columns)}`,\n selected,\n false,\n !rowSelectable,\n () => this.emitRowSelection(row),\n )}\n {this.renderStickyEdge('start')}\n </td>\n )}\n {this.columns.map(column => {\n const align = column.align ?? 'start';\n const cell = resolveTableCellPresentation(row.cells[column.id], column);\n const tagCell = cell.kind === 'tag';\n const iconCell = cell.kind === 'icon';\n const imageCell = cell.kind === 'image';\n const actionCell = cell.kind === 'action';\n const textCell = cell.kind === 'text';\n const primaryTextCell = textCell && cell.primaryText;\n const singleTextCell = textCell && cell.singleLine;\n const emptyCell = cell.kind === 'empty';\n const blankCell = cell.kind === 'blank';\n const tagVariant = tagCell ? cell.variant : undefined;\n const textVariant = textCell ? cell.variant : undefined;\n return (\n <td\n key={`${row.id}:${column.id}`}\n class={{\n 'ds-table__cell': true,\n [`ds-table__cell--align-${align}`]: true,\n 'ds-table__cell--tag': tagCell,\n [`ds-table__cell--tag-${tagVariant}`]: tagCell,\n 'ds-table__cell--icon': iconCell,\n 'ds-table__cell--image': imageCell,\n 'ds-table__cell--action': actionCell,\n 'ds-table__cell--primary-text': primaryTextCell,\n 'ds-table__cell--text-single': singleTextCell,\n 'ds-table__cell--text-multi': textCell && !singleTextCell,\n 'ds-table__cell--empty': emptyCell,\n 'ds-table__cell--blank': blankCell,\n 'ds-table__cell--sticky-start': column.sticky === 'start',\n 'ds-table__cell--sticky-end': column.sticky === 'end',\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--grouped': true,\n 'ds-interaction-fill--selected': selected,\n }}\n data-column-id={column.id}\n data-cell-type={cell.cellType}\n data-cell-variant={tagVariant ?? textVariant}\n >\n <span class=\"ds-table__cell-content ds-interaction-fill__content\">\n {this.renderCellValue(cell, column, row)}\n </span>\n {this.renderStickyEdge(column.sticky)}\n </td>\n );\n })}\n </tr>\n );\n }\n\n private emitGroupCollapse(group: TableGroup) {\n const collapsedGroupIds = toggleTableGroupCollapsed(this.collapsedGroupIds, group.id);\n this.dsGroupCollapseChange.emit({\n scope: 'group',\n groupId: group.id,\n collapsed: collapsedGroupIds.includes(group.id),\n collapsedGroupIds,\n });\n }\n\n private emitAllGroupsCollapse() {\n const groupIds = this.groups.map(group => group.id);\n const collapsedGroupIds = nextTableGroupsCollapsed(this.collapsedGroupIds, groupIds);\n this.dsGroupCollapseChange.emit({\n scope: 'all',\n collapsed: collapsedGroupIds.length > 0,\n collapsedGroupIds,\n });\n }\n\n private renderGroupContent(groupModel: TableRenderModel['groups'][number]) {\n const {\n group,\n count,\n loadedCount,\n countLabel,\n collapsed: isCollapsed,\n labelColor,\n selection: groupSelection,\n } = groupModel;\n return (\n <span class=\"ds-table__group-content\">\n {groupSelection && (\n <span class=\"ds-table__group-selection\">\n {this.renderSelectionControl(\n groupSelection.allSelected\n ? `Deselect loaded rows in ${group.label} group`\n : `Select loaded rows in ${group.label} group`,\n groupSelection.allSelected,\n groupSelection.indeterminate,\n groupSelection.selectableRowIds.length === 0,\n () => this.emitGroupSelection(group),\n )}\n </span>\n )}\n <span class=\"ds-table__group-copy\">\n <ds-text\n class=\"ds-table__group-label\"\n as=\"span\"\n variant=\"text-body-medium\"\n emphasis={true}\n color={labelColor}\n >\n {group.label}\n </ds-text>\n <ds-text\n class=\"ds-table__group-separator\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n aria-hidden=\"true\"\n >\n ·\n </ds-text>\n <ds-text\n class=\"ds-table__group-count\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n aria-hidden=\"true\"\n >\n {loadedCount} of {count}\n </ds-text>\n <span class=\"ds-visually-hidden\">{countLabel}</span>\n </span>\n <ds-button-unfilled\n class=\"ds-table__group-toggle\"\n variant=\"icon\"\n size=\"md\"\n isInset={true}\n insetDepth=\"double\"\n icon={isCollapsed ? 'ChevronDown' : 'ChevronUp'}\n expanded={!isCollapsed}\n aria-label={\n isCollapsed\n ? `Expand ${group.label} group`\n : `Collapse ${group.label} group`\n }\n hasBorder={false}\n onDsClick={event => {\n event.stopPropagation();\n this.emitGroupCollapse(group);\n }}\n />\n </span>\n );\n }\n\n private formatGroupLoadLabel(template: string, group: TableGroup): string {\n return template.split('{group}').join(group.label);\n }\n\n private renderGroupLoadRow(group: TableGroup, totalColumns: number) {\n const error = group.loadMoreError?.trim();\n if (!error && !group.loadingMore && !group.hasMore) return null;\n const manualFallback = this.loadMoreMode === 'manual' ||\n !this.groupLoadController.intersectionSupported;\n\n return (\n <tr\n class=\"ds-table__load-row ds-table__group-load-row\"\n data-group-id={group.id}\n ref={element => {\n if (element) this.groupSentinelEls.set(group.id, element);\n else this.groupSentinelEls.delete(group.id);\n }}\n >\n <td class=\"ds-table__load-cell\" colSpan={totalColumns}>\n <div class=\"ds-table__viewport-band ds-table__load-band\">\n {error ? (\n <span class=\"ds-table__load-content ds-table__load-content--error\">\n <span class=\"ds-table__load-copy\">\n <ds-icon name=\"ErrorTriangle\" size=\"md\" color=\"secondary\" aria-hidden=\"true\" />\n <ds-text as=\"span\" variant=\"text-body-medium\" color=\"secondary\">{error}</ds-text>\n </span>\n <ds-button-unfilled\n label={this.formatGroupLoadLabel(this.groupRetryLabel, group)}\n size=\"md\"\n onDsClick={() => this.groupLoadController.request(group.id, 'retry')}\n />\n </span>\n ) : group.loadingMore ? (\n <span class=\"ds-table__load-content\">\n <ds-loader size=\"md\" color=\"secondary\" />\n <ds-text as=\"span\" variant=\"text-body-medium\" color=\"secondary\">\n {this.formatGroupLoadLabel(this.groupLoadingMoreLabel, group)}\n </ds-text>\n </span>\n ) : group.hasMore && manualFallback ? (\n <span class=\"ds-table__load-content\">\n <ds-button-unfilled\n label={this.groupLoadMoreLabel}\n aria-label={this.formatGroupLoadLabel(this.groupLoadMoreAriaLabel, group)}\n size=\"md\"\n onDsClick={() => this.groupLoadController.request(group.id, 'manual')}\n />\n </span>\n ) : (\n <span class=\"ds-table__auto-sentinel\" aria-hidden=\"true\" />\n )}\n </div>\n </td>\n </tr>\n );\n }\n\n private renderStickyGroup(model: TableRenderModel) {\n if (!this.documentStickyHeader || !this.activeStickyGroupId) return null;\n const groupModel = model.groups.find(item => item.group.id === this.activeStickyGroupId);\n if (!groupModel) return null;\n return (\n <div\n class={{\n 'ds-table__sticky-group': true,\n 'ds-table__sticky-group--selectable': model.selectable,\n [groupModel.intentClass ?? '']: !!groupModel.intentClass,\n }}\n data-group-id={groupModel.group.id}\n data-group-intent={groupModel.intent}\n >\n {this.renderGroupContent(groupModel)}\n </div>\n );\n }\n\n private renderDataBodies(model: TableRenderModel) {\n if (!model.grouped) {\n return (\n <tbody class=\"ds-table__body\">\n {this.rows.map(row => this.renderRow(row, model))}\n </tbody>\n );\n }\n\n return model.groups.map(groupModel => {\n const { group, collapsed: isCollapsed, intent, intentClass } = groupModel;\n const stickySourceHidden = this.documentStickyHeader && this.activeStickyGroupId === group.id;\n return (\n <tbody\n class=\"ds-table__body ds-table__group\"\n role=\"rowgroup\"\n data-group-id={group.id}\n data-group-intent={intent}\n data-collapsed={isCollapsed ? 'true' : undefined}\n aria-busy={!isCollapsed && group.loadingMore ? 'true' : undefined}\n key={group.id}\n >\n <tr\n role=\"row\"\n aria-hidden={stickySourceHidden ? 'true' : undefined}\n class={{\n 'ds-table__group-row': true,\n 'ds-table__group-row--native-sticky': this.stickyHeader && !this.documentStickyHeader,\n }}\n >\n <th\n class={{\n 'ds-table__group-cell': true,\n 'ds-table__group-cell--sticky-source-hidden': stickySourceHidden,\n [intentClass ?? '']: !!intentClass,\n }}\n role=\"rowheader\"\n scope=\"rowgroup\"\n colSpan={model.totalColumns}\n >\n {this.renderGroupContent(groupModel)}\n </th>\n </tr>\n {!isCollapsed && group.rows.map(row => this.renderRow(row, model))}\n {!isCollapsed && this.renderGroupLoadRow(group, model.totalColumns)}\n </tbody>\n );\n });\n }\n\n private renderSkeletonBody(model: TableRenderModel) {\n const count = Math.min(20, Math.max(1, Math.round(this.skeletonRows) || 1));\n return (\n <tbody class=\"ds-table__body ds-table__skeleton-body\">\n {Array.from({ length: count }, (_, index) => (\n <tr class=\"ds-table__row ds-table__skeleton-row\" key={`skeleton-${index}`}>\n {model.selectable && (\n <td\n class=\"ds-table__cell ds-table__selection-cell ds-table__cell--sticky-start ds-table__skeleton-cell ds-interaction-fill ds-interaction-fill--grouped\"\n data-skeleton-kind=\"checkbox\"\n >\n <span class=\"ds-table__skeleton-checkbox-canvas ds-interaction-fill__content\">\n <ds-skeleton\n class=\"ds-table__skeleton-checkbox\"\n variant=\"control\"\n controlSize=\"xs\"\n width=\"var(--dimension-iconography-sm)\"\n />\n </span>\n {this.renderStickyEdge('start')}\n </td>\n )}\n {this.columns.map(column => this.renderSkeletonCell(column, index))}\n </tr>\n ))}\n </tbody>\n );\n }\n\n private renderSkeletonCell(column: TableColumn, rowIndex: number) {\n const skeleton = column.skeleton ?? (\n column.kind === 'action'\n ? { kind: 'action', variant: 'icon' }\n : { kind: 'text', lines: 1 }\n ) satisfies TableCellSkeleton;\n const align = column.align ?? 'start';\n const text = skeleton.kind === 'text';\n const lines = text ? skeleton.lines ?? 1 : 1;\n const tag = skeleton.kind === 'tag';\n const icon = skeleton.kind === 'icon';\n const image = skeleton.kind === 'image';\n const action = skeleton.kind === 'action';\n const blank = skeleton.kind === 'blank';\n\n return (\n <td\n class={{\n 'ds-table__cell': true,\n [`ds-table__cell--align-${align}`]: true,\n 'ds-table__skeleton-cell': true,\n 'ds-table__cell--text-single': text && lines === 1,\n 'ds-table__cell--text-multi': text && lines === 2,\n 'ds-table__cell--tag': tag,\n 'ds-table__cell--tag-tag-only': tag,\n 'ds-table__cell--icon': icon,\n 'ds-table__cell--image': image,\n 'ds-table__cell--action': action,\n 'ds-table__cell--blank': blank,\n 'ds-table__cell--sticky-start': column.sticky === 'start',\n 'ds-table__cell--sticky-end': column.sticky === 'end',\n 'ds-interaction-fill': true,\n 'ds-interaction-fill--grouped': true,\n }}\n data-column-id={column.id}\n data-skeleton-kind={skeleton.kind}\n key={`skeleton-${rowIndex}:${column.id}`}\n >\n <span class=\"ds-table__cell-content ds-interaction-fill__content\">\n {this.renderSkeletonCellContent(skeleton)}\n </span>\n {this.renderStickyEdge(column.sticky)}\n </td>\n );\n }\n\n private renderSkeletonCellContent(skeleton: TableCellSkeleton) {\n if (skeleton.kind === 'blank') return null;\n\n if (skeleton.kind === 'image') {\n return (\n <span class=\"ds-table__cell-image ds-table__skeleton-image\">\n <ds-skeleton\n class=\"ds-table__skeleton-image-shape\"\n variant=\"control\"\n controlSize=\"md\"\n width=\"100%\"\n />\n </span>\n );\n }\n\n if (skeleton.kind === 'icon') {\n return <ds-skeleton variant=\"icon\" iconSize=\"md\" rounded={skeleton.rounded ?? false} />;\n }\n\n if (skeleton.kind === 'tag') {\n return (\n <ds-skeleton\n variant=\"control\"\n controlSize=\"sm\"\n width={skeleton.width ?? '64%'}\n />\n );\n }\n\n if (skeleton.kind === 'action') {\n const iconOnly = (skeleton.variant ?? 'icon') === 'icon';\n return (\n <ds-skeleton\n variant=\"control\"\n controlSize=\"sm\"\n width={skeleton.width ?? (iconOnly ? '24px' : '72%')}\n />\n );\n }\n\n const lines = skeleton.lines ?? 1;\n return (\n <span class=\"ds-table__cell-copy\">\n <span class=\"ds-table__cell-track ds-table__cell-track--text\">\n <ds-skeleton\n variant=\"text\"\n textVariant=\"text-body-medium\"\n width={skeleton.primaryWidth ?? '100%'}\n />\n </span>\n {lines === 2 && (\n <span class=\"ds-table__cell-secondary ds-table__cell-track ds-table__cell-track--text\">\n <ds-skeleton\n variant=\"text\"\n textVariant=\"text-body-small\"\n width={skeleton.secondaryWidth ?? '72%'}\n />\n </span>\n )}\n </span>\n );\n }\n\n private renderStateBody(kind: 'empty' | 'error', totalColumns: number) {\n const error = kind === 'error';\n return (\n <tbody class=\"ds-table__body ds-table__state-body\">\n <tr class=\"ds-table__state-row\">\n <td class=\"ds-table__state-cell\" colSpan={totalColumns}>\n <div class=\"ds-table__viewport-band ds-table__state-band\">\n <ds-empty-state\n icon={error ? 'ErrorTriangle' : 'Inbox'}\n heading={error ? this.errorHeading : this.emptyHeading}\n body={error ? this.errorBody : this.emptyBody}\n />\n </div>\n </td>\n </tr>\n </tbody>\n );\n }\n\n private renderLazyBody(model: TableRenderModel) {\n if (\n this.dataMode !== 'infinite' ||\n !this.incrementalWindowActive ||\n !model.hasData ||\n model.grouped\n ) return null;\n const error = this.loadMoreError?.trim();\n const manualFallback = this.loadMoreMode === 'manual' ||\n !this.loadController.intersectionSupported;\n\n return (\n <tbody class=\"ds-table__body ds-table__load-body\">\n <tr\n class=\"ds-table__load-row\"\n ref={element => {\n this.sentinelEl = element ?? null;\n }}\n >\n <td class=\"ds-table__load-cell\" colSpan={model.totalColumns}>\n <div class=\"ds-table__viewport-band ds-table__load-band\">\n {error ? (\n <span class=\"ds-table__load-content ds-table__load-content--error\">\n <span class=\"ds-table__load-copy\">\n <ds-icon name=\"ErrorTriangle\" size=\"md\" color=\"secondary\" aria-hidden=\"true\" />\n <ds-text as=\"span\" variant=\"text-body-medium\" color=\"secondary\">{error}</ds-text>\n </span>\n <ds-button-unfilled\n label={this.retryLabel}\n size=\"md\"\n onDsClick={() => this.loadController.request('retry')}\n />\n </span>\n ) : this.loadingMore ? (\n <span class=\"ds-table__load-content\">\n <ds-loader size=\"md\" color=\"secondary\" />\n <ds-text as=\"span\" variant=\"text-body-medium\" color=\"secondary\">\n {this.loadingMoreLabel}\n </ds-text>\n </span>\n ) : this.hasMore && manualFallback ? (\n <span class=\"ds-table__load-content\">\n <ds-button-unfilled\n label={this.loadMoreLabel}\n size=\"md\"\n onDsClick={() => this.loadController.request('manual')}\n />\n </span>\n ) : this.hasMore ? (\n <span class=\"ds-table__auto-sentinel\" aria-hidden=\"true\" />\n ) : (\n <ds-text\n class=\"ds-table__load-content\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n >\n {this.endOfResultsLabel}\n </ds-text>\n )}\n </div>\n </td>\n </tr>\n </tbody>\n );\n }\n\n private renderDocumentStickyHeader(model: TableRenderModel) {\n if (!this.documentStickyHeader) return null;\n return (\n <div\n class=\"ds-table__document-sticky-header\"\n >\n <table\n class={{\n 'ds-table__table': true,\n 'ds-table__sticky-header-table': true,\n 'ds-table__table--selectable': model.selectable,\n 'ds-table__table--grouped': model.grouped,\n }}\n style={model.tableStyle}\n role=\"presentation\"\n ref={element => {\n this.stickyHeaderTableEl = element ?? null;\n }}\n >\n {this.renderColgroup(model)}\n {this.renderHeader(model, true, true)}\n </table>\n {this.renderFloatingCollapseAll(model)}\n </div>\n );\n }\n\n private get resultSummary(): string | null {\n if (this.dataMode === 'pagination') return null;\n return formatTableResultSummary(\n this.displayedCount,\n this.totalCount,\n this.resultSummaryLabel,\n );\n }\n\n private get hasResultFooter(): boolean {\n return (this.dataMode === 'pagination' && !!this.pagination) ||\n !!this.resultSummary || !!this.el.querySelector(\n '[slot=\"footer-leading\"], [slot=\"footer\"], [slot=\"footer-trailing\"]',\n );\n }\n\n private renderResultFooter() {\n const summary = this.resultSummary;\n const hasLeading = !!this.el.querySelector('[slot=\"footer-leading\"]');\n const hasCopy = !!this.el.querySelector('[slot=\"footer\"]');\n const hasTrailing = !!this.el.querySelector('[slot=\"footer-trailing\"]');\n const pagination = this.dataMode === 'pagination' ? this.pagination : null;\n if (!this.hasResultFooter) return null;\n return (\n <div class=\"ds-table__footer ds-table__bar ds-chrome-header ds-control--md\">\n <div class=\"ds-table__bar-copy ds-chrome-header__copy ds-control--md\">\n {hasLeading && (\n <div class=\"ds-table__bar-status ds-chrome-header__heading\">\n <slot name=\"footer-leading\" />\n </div>\n )}\n {hasCopy ? (\n <slot name=\"footer\" />\n ) : summary ? (\n <ds-text\n class=\"ds-table__footer-summary ds-table__bar-text\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n >\n {summary}\n </ds-text>\n ) : null}\n </div>\n {(pagination || hasTrailing) && (\n <div class=\"ds-table__bar-trailing ds-chrome-header__trailing\">\n {pagination && (\n <ds-pagination\n pageIndex={pagination.pageIndex}\n pageSize={pagination.pageSize}\n pageSizeMode={pagination.pageSizeMode ?? 'fixed'}\n totalItems={pagination.totalItems}\n pageSizeOptions={pagination.pageSizeOptions ?? [25, 50, 100, 200]}\n fitToPage={pagination.fitToPage ?? false}\n fitPageSize={this.fitPageSize ?? pagination.fitPageSize}\n fitPageSizeLabel={pagination.fitPageSizeLabel ?? 'Fit to page'}\n fitPageSizeTriggerLabel={pagination.fitPageSizeTriggerLabel ?? 'Fit'}\n itemLabel={pagination.itemLabel ?? 'items'}\n pageSizeLabel={pagination.pageSizeLabel ?? 'Items'}\n label={pagination.ariaLabel ?? `${this.caption} pagination`}\n loading={this.loading}\n onDsChange={(event: CustomEvent<PaginationChangeDetail>) =>\n this.dsPaginationChange.emit(event.detail)}\n />\n )}\n <slot name=\"footer-trailing\" />\n </div>\n )}\n </div>\n );\n }\n\n private renderCaptionBar() {\n if (this.captionVisibility !== 'visible') return null;\n return (\n <div class=\"ds-table__caption-bar ds-table__bar ds-control--md\">\n <div class=\"ds-table__caption-content\">\n <slot\n name=\"header\"\n onSlotchange={this.syncHeaderSlotPresence}\n />\n {!this.headerPresent ? (\n <ds-text\n class=\"ds-table__caption-title ds-table__bar-text\"\n as=\"div\"\n variant=\"text-title-small\"\n emphasis={true}\n color=\"primary\"\n aria-hidden=\"true\"\n >\n {this.caption}\n </ds-text>\n ) : null}\n </div>\n </div>\n );\n }\n\n render() {\n this.groupSentinelEls.clear();\n const model = this.createRenderModel();\n this.renderedModel = model;\n const regionLabel = this.scrollLabel?.trim() || `${this.caption} scroll area`;\n const initialLoading = this.loading && !model.hasData;\n const initialError = !model.hasData && this.error;\n const hasGroupedStructure = model.grouped && model.groups.length > 0;\n const groupLoadingMore = hasGroupedStructure && this.groups.some(group => group.loadingMore);\n const viewportStyle = !this.boundedComposition && this.resolvedMaxHeight\n ? { '--ds-table-max-block-size': this.resolvedMaxHeight }\n : undefined;\n const hostStyle = this.fixedHeight\n ? { '--_table-fixed-block-size': this.resolvedHeight }\n : undefined;\n\n return (\n <Host\n class={{\n 'table-host': true,\n 'table-host--bounded': this.boundedComposition,\n 'table-host--fixed-height': this.fixedHeight,\n 'table-host--viewport-fit': this.fitViewport,\n }}\n style={hostStyle}\n onKeyDown={(event: KeyboardEvent) => this.handlePaginationKeyDown(event)}\n >\n <div class={{\n 'ds-table': true,\n 'ds-table--bounded': this.boundedComposition,\n 'ds-table--contained-scroll': this.containedScroll,\n 'ds-table--fixed-height': this.fixedHeight,\n 'ds-table--viewport-fit': this.fitViewport,\n 'ds-table--viewport-fit-pending': this.fitViewport && !this.viewportFitSettled,\n 'ds-table--viewport-fit-settled': this.fitViewport && this.viewportFitSettled,\n 'ds-table--sticky-header': this.stickyHeader,\n 'ds-table--document-sticky-header': this.documentStickyHeader,\n 'ds-table--contained-sticky-header': this.stickyHeader && !this.documentStickyHeader,\n 'ds-table--caption-visible': this.captionVisibility === 'visible',\n 'ds-table--footer-visible': this.hasResultFooter,\n }} ref={element => {\n this.rootEl = element ?? null;\n }}>\n {this.renderCaptionBar()}\n <div\n class={{\n 'ds-table__frame': true,\n 'ds-table__frame--overflow-start': this.overflowStart,\n 'ds-table__frame--overflow-end': this.overflowEnd,\n }}\n ref={element => {\n this.frameEl = element ?? null;\n }}\n >\n {this.renderDocumentStickyHeader(model)}\n {this.renderStickyGroup(model)}\n {!this.documentStickyHeader && this.renderFloatingCollapseAll(model)}\n <div\n class={{\n 'ds-table__viewport': true,\n 'ds-focus-ring': this.scrollable,\n }}\n style={viewportStyle}\n ref={element => {\n this.viewportEl = element ?? null;\n }}\n role={this.scrollable ? 'region' : undefined}\n aria-label={this.scrollable ? regionLabel : undefined}\n tabIndex={this.scrollable ? 0 : undefined}\n >\n <table\n class={{\n 'ds-table__table': true,\n 'ds-table__table--selectable': model.selectable,\n 'ds-table__table--grouped': model.grouped,\n 'ds-table__table--native-group-sticky': model.grouped &&\n this.stickyHeader && !this.documentStickyHeader,\n }}\n style={model.tableStyle}\n aria-busy={initialLoading || this.loadingMore || groupLoadingMore ? 'true' : undefined}\n ref={element => {\n this.tableEl = element ?? null;\n }}\n >\n <caption class=\"ds-table__caption ds-visually-hidden\">\n <ds-text as=\"span\" variant=\"text-title-small\" emphasis={true} color=\"primary\">\n {this.caption}\n </ds-text>\n </caption>\n {this.renderColgroup(model)}\n {this.renderHeader(model, !this.documentStickyHeader)}\n {initialLoading\n ? this.renderSkeletonBody(model)\n : initialError\n ? this.renderStateBody('error', model.totalColumns)\n : model.hasData || hasGroupedStructure\n ? this.renderDataBodies(model)\n : this.renderStateBody('empty', model.totalColumns)}\n {!initialLoading && !initialError && this.renderLazyBody(model)}\n </table>\n </div>\n </div>\n {this.renderResultFooter()}\n <div class=\"ds-visually-hidden\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">\n {this.announcement}\n </div>\n </div>\n </Host>\n );\n }\n}\n",
1058
1058
  "type": "registry:ui"
1059
1059
  },
1060
1060
  {
@@ -152,6 +152,7 @@ export declare class Table {
152
152
  private connectFitObserver;
153
153
  private disconnectFitObserver;
154
154
  private readonly syncFitPageSize;
155
+ private handlePaginationKeyDown;
155
156
  private currentLoadedRows;
156
157
  private currentLoadedRowCount;
157
158
  private createRenderModel;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ds-mo/ui",
3
- "version": "14.4.0",
3
+ "version": "14.4.1",
4
4
  "description": "CompoMo — composable web components styled with TokoMo design tokens",
5
5
  "keywords": [
6
6
  "web-components",
@@ -1,2 +0,0 @@
1
- import{p as e,H as i,c as a,h as t,a as s,t as n}from"./index.js";import{d as o}from"./p-D8nnhQNJ.js";import{d}from"./p-CTTDiW33.js";import{d as r}from"./p-C5TrNd31.js";import{d as c}from"./p-DLkNcCY0.js";import{d as l}from"./p-BFfLvMZd.js";import{d as p}from"./p-Ca-I2UnB.js";import{d as g}from"./p-CRLRjtsc.js";import{d as f}from"./p-DZFilC--.js";import{d as h}from"./p-CtOK5oZ1.js";const b=[25,50,100,200];function m(e){return Number.isFinite(e)?Math.max(0,Math.trunc(e)):0}function u(e,i){return Number.isFinite(e)&&e>0?Math.trunc(e):i}function z(e){const i=u(e.pageSize,b[0]);const a=e.pageSizeMode==="fit"?"fit":"fixed";const t=m(e.totalItems);const s=Math.max(1,Math.ceil(t/i));const n=Math.min(m(e.pageIndex),s-1);const o=(e.pageSizeOptions?.length?e.pageSizeOptions:b).map((e=>u(e,0))).filter((e=>e>0));const d=[...new Set(a==="fixed"?[...o,i]:o)].sort(((e,i)=>e-i));const r=t===0?0:n*i+1;const c=t===0?0:Math.min(t,r+i-1);return{pageIndex:n,pageSize:i,pageSizeMode:a,totalItems:t,totalPages:s,firstItem:r,lastItem:c,pageSizeOptions:d}}const v=()=>`.ds-visually-hidden.sc-ds-pagination-h,.ds-visually-hidden.sc-ds-pagination{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}.sc-ds-pagination-h{display:block;box-sizing:border-box;min-width:0;max-width:100%;overflow-x:auto;scrollbar-width:none}.sc-ds-pagination-h::-webkit-scrollbar{display:none}.pagination.sc-ds-pagination,.pagination__page-size.sc-ds-pagination,.pagination__navigation.sc-ds-pagination{display:flex;align-items:center;box-sizing:border-box}.pagination.sc-ds-pagination{width:max-content;min-width:100%;gap:var(--dimension-space-200)}.pagination__page-size.sc-ds-pagination,.pagination__navigation.sc-ds-pagination{flex:0 0 auto}.pagination__page-size.sc-ds-pagination{gap:var(--dimension-space-100)}.pagination__navigation.sc-ds-pagination{gap:var(--dimension-space-050)}.pagination__range.sc-ds-pagination,.pagination__page.sc-ds-pagination,.pagination__label.sc-ds-pagination{display:inline-flex;align-items:center;flex:0 0 auto;white-space:nowrap}.pagination__page.sc-ds-pagination{justify-content:center;min-inline-size:calc(var(--dimension-size-800) + var(--dimension-space-200));padding-inline:var(--dimension-space-050)}`;let S=0;const x=e(class e extends i{constructor(e){super();if(e!==false){this.__registerHost()}this.dsChange=a(this,"dsChange");this.pageIndex=0;this.pageSize=25;this.pageSizeMode="fixed";this.totalItems=0;this.pageSizeOptions=[25,50,100,200];this.fitToPage=false;this.fitPageSizeLabel="Fit to page";this.fitPageSizeTriggerLabel="Fit";this.itemLabel="items";this.pageSizeLabel="Items";this.label="Pagination";this.loading=false;this.pageSizeControlId=`ds-pagination-${++S}-page-size`}requestPage(e){const i=this.resolvedState;if(this.loading||e===i.pageIndex||e<0||e>=i.totalPages){return}this.dsChange.emit({pageIndex:e,pageSize:i.pageSize,pageSizeMode:i.pageSizeMode,totalItems:i.totalItems,pageSizeOptions:i.pageSizeOptions,fitToPage:this.fitToPage,fitPageSize:this.resolvedFitPageSize,fitPageSizeLabel:this.fitPageSizeLabel,fitPageSizeTriggerLabel:this.fitPageSizeTriggerLabel,itemLabel:this.itemLabel,pageSizeLabel:this.pageSizeLabel,ariaLabel:this.label,previousPageIndex:i.pageIndex,previousPageSize:i.pageSize,previousPageSizeMode:i.pageSizeMode,reason:"page"})}requestPageSize(e){if(this.loading||typeof e!=="string")return;const i=this.resolvedState;const a=e==="fit"?"fit":"fixed";const t=a==="fit"?this.resolvedFitPageSize:Number(e);if(!t||t===i.pageSize&&a===i.pageSizeMode)return;this.dsChange.emit({pageIndex:0,pageSize:Math.trunc(t),pageSizeMode:a,totalItems:i.totalItems,pageSizeOptions:i.pageSizeOptions,fitToPage:this.fitToPage,fitPageSize:this.resolvedFitPageSize,fitPageSizeLabel:this.fitPageSizeLabel,fitPageSizeTriggerLabel:this.fitPageSizeTriggerLabel,itemLabel:this.itemLabel,pageSizeLabel:this.pageSizeLabel,ariaLabel:this.label,previousPageIndex:i.pageIndex,previousPageSize:i.pageSize,previousPageSizeMode:i.pageSizeMode,reason:"page-size"})}handleKeyDown(e){if(e.defaultPrevented||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey){return}const a=e.composedPath().some((e=>e instanceof i&&(e.tagName==="DS-SELECT"||["INPUT","SELECT","TEXTAREA"].includes(e.tagName))));if(a||e.key!=="ArrowLeft"&&e.key!=="ArrowRight")return;const t=this.resolvedState;const s=e.key==="ArrowLeft"?t.pageIndex-1:t.pageIndex+1;if(this.loading||s<0||s>=t.totalPages)return;e.preventDefault();this.requestPage(s)}get resolvedFitPageSize(){return Number.isFinite(this.fitPageSize)&&Number(this.fitPageSize)>0?Math.trunc(Number(this.fitPageSize)):undefined}get resolvedState(){return z({pageIndex:this.pageIndex,pageSize:this.pageSize,pageSizeMode:this.pageSizeMode,totalItems:this.totalItems,pageSizeOptions:this.pageSizeOptions})}render(){const e=this.resolvedState;const i=e.pageIndex===0;const a=e.pageIndex===e.totalPages-1;const n=`${e.firstItem}–${e.lastItem} / ${e.totalItems}`;const o=`${e.pageIndex+1} / ${e.totalPages}`;const d=`${e.firstItem}–${e.lastItem} of ${e.totalItems}`;const r=`Page ${e.pageIndex+1} of ${e.totalPages}`;const c=this.pageSizeAriaLabel??`${this.pageSizeLabel} per page`;const l=e.pageSizeOptions.map((e=>({label:String(e),value:String(e)})));if(this.fitToPage){l.push({label:this.fitPageSizeLabel,value:"fit",isInactive:this.resolvedFitPageSize===undefined})}return t(s,{key:"7f5d9665efe630d9b36e44a56080142fda479b14"},t("nav",{key:"caeecd225b1b6729cbf78e41b2c2062f7f4106d2",class:"pagination","aria-label":this.label,"aria-busy":this.loading?"true":undefined,onKeyDown:e=>this.handleKeyDown(e)},t("div",{key:"860290191577ce319ade473239fb763eaf58a04d",class:"pagination__page-size"},t("ds-text",{key:"940064381fec85322519bdc8a71868953693d929",class:"pagination__label",as:"span",variant:"text-body-medium",color:"secondary","aria-hidden":"true"},this.pageSizeLabel,":"),t("span",{key:"8b1e18e3a7d544803bac8bf98aa9351146b66850",id:`${this.pageSizeControlId}-label`,class:"ds-visually-hidden"},c),t("ds-select",{key:"50d09f7813fbb536f255ffb634cf56c0d9d97160",size:"md",inputId:this.pageSizeControlId,ariaLabelledby:`${this.pageSizeControlId}-label`,options:l,value:e.pageSizeMode==="fit"?"fit":String(e.pageSize),triggerLabel:e.pageSizeMode==="fit"?this.fitPageSizeTriggerLabel:undefined,indicator:"up-down",allowClear:false,activeFill:false,isInactive:this.loading,onDsChange:e=>{e.stopPropagation();this.requestPageSize(e.detail)}})),t("ds-text",{key:"031a672e5e83759218d15d5a4c7c65bd634e8dac",class:"pagination__range",as:"span",variant:"text-body-medium",color:"secondary",fontFeature:"tabular-nums"},n),t("div",{key:"9b7cd979fa2c4a7dc24a30bdf99565edd21209fd",class:"pagination__navigation"},t("ds-button-unfilled",{key:"be8c10a5fab1225fb481f7a18409f1a6353126d1",variant:"icon",size:"md",icon:"ChevronLeftDouble",ariaLabel:"First page",hasBorder:false,isInactive:this.loading||i,onDsClick:()=>this.requestPage(0)}),t("ds-button-unfilled",{key:"dc68bea73685900ab83fa378b5476f6284369d3a",variant:"icon",size:"md",icon:"ChevronLeft",ariaLabel:"Previous page",hasBorder:false,isInactive:this.loading||i,onDsClick:()=>this.requestPage(e.pageIndex-1)}),t("ds-text",{key:"c307dffabe2e9160d1f31c618317513470d9a119",class:"pagination__page",as:"span",variant:"text-body-medium",color:"secondary",fontFeature:"tabular-nums"},o),t("ds-button-unfilled",{key:"44cc58dc027aed3c2b237f8c2dc9e4e13d517d42",variant:"icon",size:"md",icon:"ChevronRight",ariaLabel:"Next page",hasBorder:false,isInactive:this.loading||a,onDsClick:()=>this.requestPage(e.pageIndex+1)}),t("ds-button-unfilled",{key:"93a6803c86c8ef1e9b8274fe13002684946c68c0",variant:"icon",size:"md",icon:"ChevronRightDouble",ariaLabel:"Last page",hasBorder:false,isInactive:this.loading||a,onDsClick:()=>this.requestPage(e.totalPages-1)})),t("span",{key:"f194bd14a1452d354fb3bcfab8bf27718a0ac48a",class:"ds-visually-hidden",role:"status","aria-live":"polite","aria-atomic":"true"},d," ",this.itemLabel,". ",r,".")))}static get style(){return v()}},[2,"ds-pagination",{pageIndex:[2,"page-index"],pageSize:[2,"page-size"],pageSizeMode:[1,"page-size-mode"],totalItems:[2,"total-items"],pageSizeOptions:[16],fitToPage:[4,"fit-to-page"],fitPageSize:[2,"fit-page-size"],fitPageSizeLabel:[1,"fit-page-size-label"],fitPageSizeTriggerLabel:[1,"fit-page-size-trigger-label"],itemLabel:[1,"item-label"],pageSizeLabel:[1,"page-size-label"],pageSizeAriaLabel:[1,"page-size-aria-label"],label:[1],loading:[4]}]);function y(){if(typeof customElements==="undefined"){return}const e=["ds-pagination","ds-badge","ds-button-unfilled","ds-checkbox","ds-empty-state","ds-icon","ds-loader","ds-select","ds-text","ds-tooltip"];e.forEach((e=>{switch(e){case"ds-pagination":if(!customElements.get(n(e))){customElements.define(n(e),x)}break;case"ds-badge":if(!customElements.get(n(e))){o()}break;case"ds-button-unfilled":if(!customElements.get(n(e))){d()}break;case"ds-checkbox":if(!customElements.get(n(e))){r()}break;case"ds-empty-state":if(!customElements.get(n(e))){c()}break;case"ds-icon":if(!customElements.get(n(e))){l()}break;case"ds-loader":if(!customElements.get(n(e))){p()}break;case"ds-select":if(!customElements.get(n(e))){g()}break;case"ds-text":if(!customElements.get(n(e))){f()}break;case"ds-tooltip":if(!customElements.get(n(e))){h()}break}}))}y();export{x as P,y as d};
2
- //# sourceMappingURL=p-Cc5xgR17.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["DEFAULT_PAGE_SIZE_OPTIONS","nonNegativeInteger","value","Number","isFinite","Math","max","trunc","positiveInteger","fallback","resolvePaginationState","state","pageSize","pageSizeMode","totalItems","totalPages","ceil","pageIndex","min","options","pageSizeOptions","length","map","filter","Set","sort","left","right","firstItem","lastItem","paginationCss","paginationId","Pagination","__stencil_proxyCustomElement","HTMLElement","constructor","registerHost","this","fitToPage","fitPageSizeLabel","fitPageSizeTriggerLabel","itemLabel","pageSizeLabel","label","loading","pageSizeControlId","requestPage","resolvedState","dsChange","emit","fitPageSize","resolvedFitPageSize","ariaLabel","previousPageIndex","previousPageSize","previousPageSizeMode","reason","requestPageSize","nextMode","handleKeyDown","event","defaultPrevented","altKey","ctrlKey","metaKey","shiftKey","fromChoiceControl","composedPath","some","node","tagName","includes","key","preventDefault","undefined","render","atStart","atEnd","range","page","announcedRange","announcedPage","pageSizeAriaLabel","String","push","isInactive","h","Host","class","onKeyDown","as","variant","color","id","size","inputId","ariaLabelledby","triggerLabel","indicator","allowClear","activeFill","onDsChange","stopPropagation","detail","fontFeature","icon","hasBorder","onDsClick","role"],"sources":["src/wc/components/Pagination/pagination-model.ts","src/wc/components/Pagination/Pagination.css?tag=ds-pagination&encapsulation=scoped","src/wc/components/Pagination/Pagination.tsx"],"sourcesContent":["import type { PaginationPageSizeMode, PaginationState } from './pagination-types';\n\nexport const DEFAULT_PAGE_SIZE_OPTIONS = [25, 50, 100, 200] as const;\n\nexport interface ResolvedPaginationState {\n pageIndex: number;\n pageSize: number;\n pageSizeMode: PaginationPageSizeMode;\n totalItems: number;\n totalPages: number;\n firstItem: number;\n lastItem: number;\n pageSizeOptions: number[];\n}\n\nfunction nonNegativeInteger(value: number): number {\n return Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;\n}\n\nfunction positiveInteger(value: number, fallback: number): number {\n return Number.isFinite(value) && value > 0 ? Math.trunc(value) : fallback;\n}\n\nexport function resolvePaginationState(\n state: Pick<\n PaginationState,\n 'pageIndex' | 'pageSize' | 'pageSizeMode' | 'totalItems' | 'pageSizeOptions'\n >,\n): ResolvedPaginationState {\n const pageSize = positiveInteger(state.pageSize, DEFAULT_PAGE_SIZE_OPTIONS[0]);\n const pageSizeMode = state.pageSizeMode === 'fit' ? 'fit' : 'fixed';\n const totalItems = nonNegativeInteger(state.totalItems);\n const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));\n const pageIndex = Math.min(nonNegativeInteger(state.pageIndex), totalPages - 1);\n const options = (state.pageSizeOptions?.length\n ? state.pageSizeOptions\n : DEFAULT_PAGE_SIZE_OPTIONS\n )\n .map(value => positiveInteger(value, 0))\n .filter(value => value > 0);\n const pageSizeOptions = [\n ...new Set(pageSizeMode === 'fixed' ? [...options, pageSize] : options),\n ].sort((left, right) => left - right);\n const firstItem = totalItems === 0 ? 0 : pageIndex * pageSize + 1;\n const lastItem = totalItems === 0 ? 0 : Math.min(totalItems, firstItem + pageSize - 1);\n\n return {\n pageIndex,\n pageSize,\n pageSizeMode,\n totalItems,\n totalPages,\n firstItem,\n lastItem,\n pageSizeOptions,\n };\n}\n","@import '../../utils/visually-hidden.css';\n\n:host {\n display: block;\n box-sizing: border-box;\n min-width: 0;\n max-width: 100%;\n overflow-x: auto;\n scrollbar-width: none;\n}\n\n:host::-webkit-scrollbar {\n display: none;\n}\n\n.pagination,\n.pagination__page-size,\n.pagination__navigation {\n display: flex;\n align-items: center;\n box-sizing: border-box;\n}\n\n.pagination {\n width: max-content;\n min-width: 100%;\n gap: var(--dimension-space-200);\n}\n\n.pagination__page-size,\n.pagination__navigation {\n flex: 0 0 auto;\n}\n\n.pagination__page-size {\n gap: var(--dimension-space-100);\n}\n\n.pagination__navigation {\n gap: var(--dimension-space-050);\n}\n\n.pagination__range,\n.pagination__page,\n.pagination__label {\n display: inline-flex;\n align-items: center;\n flex: 0 0 auto;\n white-space: nowrap;\n}\n\n.pagination__page {\n justify-content: center;\n min-inline-size: calc(var(--dimension-size-800) + var(--dimension-space-200));\n padding-inline: var(--dimension-space-050);\n}\n","import { Component, Event, EventEmitter, h, Host, Prop } from '@stencil/core';\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 /** 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 /** 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 private readonly pageSizeControlId = `ds-pagination-${++paginationId}-page-size`;\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 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 (this.loading || typeof value !== 'string') 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 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 range = `${state.firstItem}–${state.lastItem} / ${state.totalItems}`;\n const page = `${state.pageIndex + 1} / ${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.resolvedFitPageSize === undefined,\n });\n }\n\n return (\n <Host>\n <nav\n class=\"pagination\"\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 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 isInactive={this.loading}\n onDsChange={event => {\n event.stopPropagation();\n this.requestPageSize(event.detail);\n }}\n />\n </div>\n <ds-text\n class=\"pagination__range\"\n as=\"span\"\n variant=\"text-body-medium\"\n color=\"secondary\"\n fontFeature=\"tabular-nums\"\n >\n {range}\n </ds-text>\n <div class=\"pagination__navigation\">\n <ds-button-unfilled\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 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"],"mappings":"iYAEO,MAAMA,EAA4B,CAAC,GAAI,GAAI,IAAK,KAavD,SAASC,EAAmBC,GAC1B,OAAOC,OAAOC,SAASF,GAASG,KAAKC,IAAI,EAAGD,KAAKE,MAAML,IAAU,CACnE,CAEA,SAASM,EAAgBN,EAAeO,GACtC,OAAON,OAAOC,SAASF,IAAUA,EAAQ,EAAIG,KAAKE,MAAML,GAASO,CACnE,CAEM,SAAUC,EACdC,GAKA,MAAMC,EAAWJ,EAAgBG,EAAMC,SAAUZ,EAA0B,IAC3E,MAAMa,EAAeF,EAAME,eAAiB,MAAQ,MAAQ,QAC5D,MAAMC,EAAab,EAAmBU,EAAMG,YAC5C,MAAMC,EAAaV,KAAKC,IAAI,EAAGD,KAAKW,KAAKF,EAAaF,IACtD,MAAMK,EAAYZ,KAAKa,IAAIjB,EAAmBU,EAAMM,WAAYF,EAAa,GAC7E,MAAMI,GAAWR,EAAMS,iBAAiBC,OACpCV,EAAMS,gBACNpB,GAEDsB,KAAIpB,GAASM,EAAgBN,EAAO,KACpCqB,QAAOrB,GAASA,EAAQ,IAC3B,MAAMkB,EAAkB,IACnB,IAAII,IAAIX,IAAiB,QAAU,IAAIM,EAASP,GAAYO,IAC/DM,MAAK,CAACC,EAAMC,IAAUD,EAAOC,IAC/B,MAAMC,EAAYd,IAAe,EAAI,EAAIG,EAAYL,EAAW,EAChE,MAAMiB,EAAWf,IAAe,EAAI,EAAIT,KAAKa,IAAIJ,EAAYc,EAAYhB,EAAW,GAEpF,MAAO,CACLK,YACAL,WACAC,eACAC,aACAC,aACAa,YACAC,WACAT,kBAEJ,CCxDA,MAAMU,EAAgB,IAAM,6sCCO5B,IAAIC,EAAe,E,MAONC,EAAUC,EAAA,MAAAD,UAAAE,EALvB,WAAAC,CAAAC,G,6EAOUC,KAAApB,UAAoB,EAEpBoB,KAAAzB,SAAmB,GAEnByB,KAAAxB,aAAuC,QAEvCwB,KAAAvB,WAAqB,EAErBuB,KAAAjB,gBAA4B,CAAC,GAAI,GAAI,IAAK,KAE1CiB,KAAAC,UAAqB,MAIrBD,KAAAE,iBAA2B,cAE3BF,KAAAG,wBAAkC,MAElCH,KAAAI,UAAoB,QAEpBJ,KAAAK,cAAwB,QAIxBL,KAAAM,MAAgB,aAEhBN,KAAAO,QAAmB,MAKVP,KAAAQ,kBAAoB,mBAAmBd,aA6MzD,CA3MS,WAAAe,CAAY7B,GAClB,MAAMN,EAAQ0B,KAAKU,cACnB,GAAIV,KAAKO,SAAW3B,IAAcN,EAAMM,WAAaA,EAAY,GAAKA,GAAaN,EAAMI,WAAY,CACnG,M,CAEFsB,KAAKW,SAASC,KAAK,CACjBhC,YACAL,SAAUD,EAAMC,SAChBC,aAAcF,EAAME,aACpBC,WAAYH,EAAMG,WAClBM,gBAAiBT,EAAMS,gBACvBkB,UAAWD,KAAKC,UAChBY,YAAab,KAAKc,oBAClBZ,iBAAkBF,KAAKE,iBACvBC,wBAAyBH,KAAKG,wBAC9BC,UAAWJ,KAAKI,UAChBC,cAAeL,KAAKK,cACpBU,UAAWf,KAAKM,MAChBU,kBAAmB1C,EAAMM,UACzBqC,iBAAkB3C,EAAMC,SACxB2C,qBAAsB5C,EAAME,aAC5B2C,OAAQ,Q,CAIJ,eAAAC,CAAgBvD,GACtB,GAAImC,KAAKO,gBAAkB1C,IAAU,SAAU,OAC/C,MAAMS,EAAQ0B,KAAKU,cACnB,MAAMW,EAAmCxD,IAAU,MAAQ,MAAQ,QACnE,MAAMU,EAAW8C,IAAa,MAAQrB,KAAKc,oBAAsBhD,OAAOD,GACxE,IAAKU,GAAaA,IAAaD,EAAMC,UAAY8C,IAAa/C,EAAME,aAAe,OACnFwB,KAAKW,SAASC,KAAK,CACjBhC,UAAW,EACXL,SAAUP,KAAKE,MAAMK,GACrBC,aAAc6C,EACd5C,WAAYH,EAAMG,WAClBM,gBAAiBT,EAAMS,gBACvBkB,UAAWD,KAAKC,UAChBY,YAAab,KAAKc,oBAClBZ,iBAAkBF,KAAKE,iBACvBC,wBAAyBH,KAAKG,wBAC9BC,UAAWJ,KAAKI,UAChBC,cAAeL,KAAKK,cACpBU,UAAWf,KAAKM,MAChBU,kBAAmB1C,EAAMM,UACzBqC,iBAAkB3C,EAAMC,SACxB2C,qBAAsB5C,EAAME,aAC5B2C,OAAQ,a,CAIJ,aAAAG,CAAcC,GACpB,GAAIA,EAAMC,kBAAoBD,EAAME,QAAUF,EAAMG,SAAWH,EAAMI,SAAWJ,EAAMK,SAAU,CAC9F,M,CAEF,MAAMC,EAAoBN,EAAMO,eAAeC,MAAKC,GAClDA,aAAgBnC,IACfmC,EAAKC,UAAY,aAAe,CAAC,QAAS,SAAU,YAAYC,SAASF,EAAKC,YAEjF,GAAIJ,GAAsBN,EAAMY,MAAQ,aAAeZ,EAAMY,MAAQ,aAAe,OACpF,MAAM7D,EAAQ0B,KAAKU,cACnB,MAAM9B,EAAY2C,EAAMY,MAAQ,YAAc7D,EAAMM,UAAY,EAAIN,EAAMM,UAAY,EACtF,GAAIoB,KAAKO,SAAW3B,EAAY,GAAKA,GAAaN,EAAMI,WAAY,OACpE6C,EAAMa,iBACNpC,KAAKS,YAAY7B,E,CAGnB,uBAAYkC,GACV,OAAOhD,OAAOC,SAASiC,KAAKa,cAAgB/C,OAAOkC,KAAKa,aAAe,EACnE7C,KAAKE,MAAMJ,OAAOkC,KAAKa,cACvBwB,S,CAGN,iBAAY3B,GACV,OAAOrC,EAAuB,CAC5BO,UAAWoB,KAAKpB,UAChBL,SAAUyB,KAAKzB,SACfC,aAAcwB,KAAKxB,aACnBC,WAAYuB,KAAKvB,WACjBM,gBAAiBiB,KAAKjB,iB,CAI1B,MAAAuD,GACE,MAAMhE,EAAQ0B,KAAKU,cACnB,MAAM6B,EAAUjE,EAAMM,YAAc,EACpC,MAAM4D,EAAQlE,EAAMM,YAAcN,EAAMI,WAAa,EACrD,MAAM+D,EAAQ,GAAGnE,EAAMiB,aAAajB,EAAMkB,cAAclB,EAAMG,aAC9D,MAAMiE,EAAO,GAAGpE,EAAMM,UAAY,OAAON,EAAMI,aAC/C,MAAMiE,EAAiB,GAAGrE,EAAMiB,aAAajB,EAAMkB,eAAelB,EAAMG,aACxE,MAAMmE,EAAgB,QAAQtE,EAAMM,UAAY,QAAQN,EAAMI,aAC9D,MAAMmE,EAAoB7C,KAAK6C,mBAAqB,GAAG7C,KAAKK,yBAC5D,MAAMvB,EACJR,EAAMS,gBAAgBE,KAAIpB,IAAK,CAAOyC,MAAOwC,OAAOjF,GAAQA,MAAOiF,OAAOjF,OAC5E,GAAImC,KAAKC,UAAW,CAClBnB,EAAQiE,KAAK,CACXzC,MAAON,KAAKE,iBACZrC,MAAO,MACPmF,WAAYhD,KAAKc,sBAAwBuB,W,CAI7C,OACEY,EAACC,EAAI,CAAAf,IAAA,4CACHc,EAAA,OAAAd,IAAA,2CACEgB,MAAM,aAAY,aACNnD,KAAKM,MAAK,YACXN,KAAKO,QAAU,OAAS8B,UACnCe,UAAW7B,GAASvB,KAAKsB,cAAcC,IAEvC0B,EAAA,OAAAd,IAAA,2CAAKgB,MAAM,yBACTF,EAAA,WAAAd,IAAA,2CACEgB,MAAM,oBACNE,GAAG,OACHC,QAAQ,mBACRC,MAAM,YAAW,cACL,QAEXvD,KAAKK,cACE,KACV4C,EAAA,QAAAd,IAAA,2CAAMqB,GAAI,GAAGxD,KAAKQ,0BAA2B2C,MAAM,sBAChDN,GAEHI,EAAA,aAAAd,IAAA,2CACEsB,KAAK,KACLC,QAAS1D,KAAKQ,kBACdmD,eAAgB,GAAG3D,KAAKQ,0BACxB1B,QAASA,EACTjB,MAAOS,EAAME,eAAiB,MAAQ,MAAQsE,OAAOxE,EAAMC,UAC3DqF,aAActF,EAAME,eAAiB,MAAQwB,KAAKG,wBAA0BkC,UAC5EwB,UAAU,UACVC,WAAY,MACZC,WAAY,MACZf,WAAYhD,KAAKO,QACjByD,WAAYzC,IACVA,EAAM0C,kBACNjE,KAAKoB,gBAAgBG,EAAM2C,OAAO,KAIxCjB,EAAA,WAAAd,IAAA,2CACEgB,MAAM,oBACNE,GAAG,OACHC,QAAQ,mBACRC,MAAM,YACNY,YAAY,gBAEX1B,GAEHQ,EAAA,OAAAd,IAAA,2CAAKgB,MAAM,0BACTF,EAAA,sBAAAd,IAAA,2CACEmB,QAAQ,OACRG,KAAK,KACLW,KAAK,oBACLrD,UAAU,aACVsD,UAAW,MACXrB,WAAYhD,KAAKO,SAAWgC,EAC5B+B,UAAW,IAAMtE,KAAKS,YAAY,KAEpCwC,EAAA,sBAAAd,IAAA,2CACEmB,QAAQ,OACRG,KAAK,KACLW,KAAK,cACLrD,UAAU,gBACVsD,UAAW,MACXrB,WAAYhD,KAAKO,SAAWgC,EAC5B+B,UAAW,IAAMtE,KAAKS,YAAYnC,EAAMM,UAAY,KAEtDqE,EAAA,WAAAd,IAAA,2CACEgB,MAAM,mBACNE,GAAG,OACHC,QAAQ,mBACRC,MAAM,YACNY,YAAY,gBAEXzB,GAEHO,EAAA,sBAAAd,IAAA,2CACEmB,QAAQ,OACRG,KAAK,KACLW,KAAK,eACLrD,UAAU,YACVsD,UAAW,MACXrB,WAAYhD,KAAKO,SAAWiC,EAC5B8B,UAAW,IAAMtE,KAAKS,YAAYnC,EAAMM,UAAY,KAEtDqE,EAAA,sBAAAd,IAAA,2CACEmB,QAAQ,OACRG,KAAK,KACLW,KAAK,qBACLrD,UAAU,YACVsD,UAAW,MACXrB,WAAYhD,KAAKO,SAAWiC,EAC5B8B,UAAW,IAAMtE,KAAKS,YAAYnC,EAAMI,WAAa,MAGzDuE,EAAA,QAAAd,IAAA,2CAAMgB,MAAM,qBAAqBoB,KAAK,SAAQ,YAAW,SAAQ,cAAa,QAC3E5B,EAAc,IAAG3C,KAAKI,UAAS,KAAIwC,EAAa,M","ignoreList":[]}