@ds-mo/ui 15.12.1 → 16.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (368) hide show
  1. package/dist/.package-ready.json +1 -1
  2. package/dist/agent-patterns.json +1 -1
  3. package/dist/agent.json +5 -2
  4. package/dist/angular/boolean-value-accessor.js +3 -3
  5. package/dist/angular/ds-agent-activity.js +3 -3
  6. package/dist/angular/ds-agent-questionnaire.js +3 -3
  7. package/dist/angular/ds-agent-response.js +3 -3
  8. package/dist/angular/ds-agent-source-list.js +3 -3
  9. package/dist/angular/ds-agent-tool-call.js +3 -3
  10. package/dist/angular/ds-attachment-list.js +3 -3
  11. package/dist/angular/ds-avatar.js +3 -3
  12. package/dist/angular/ds-badge.js +3 -3
  13. package/dist/angular/ds-banner.js +3 -3
  14. package/dist/angular/ds-bar-action.js +3 -3
  15. package/dist/angular/ds-bar-nav.js +3 -3
  16. package/dist/angular/ds-bar-page-title.js +3 -3
  17. package/dist/angular/ds-bar-title.js +3 -3
  18. package/dist/angular/ds-bar-workflow.js +3 -3
  19. package/dist/angular/ds-breadcrumb.js +3 -3
  20. package/dist/angular/ds-button-filled.js +3 -3
  21. package/dist/angular/ds-button-inverted.js +3 -3
  22. package/dist/angular/ds-button-unfilled.js +3 -3
  23. package/dist/angular/ds-calendar.js +3 -3
  24. package/dist/angular/ds-card-action-center.js +3 -3
  25. package/dist/angular/ds-card-chart.js +3 -3
  26. package/dist/angular/ds-card-navigation.js +3 -3
  27. package/dist/angular/ds-card-overview.js +3 -3
  28. package/dist/angular/ds-card-setting.js +3 -3
  29. package/dist/angular/ds-card-settings-scope.js +3 -3
  30. package/dist/angular/ds-chart-legend.js +3 -3
  31. package/dist/angular/ds-chart.js +3 -3
  32. package/dist/angular/ds-checkbox-group.js +3 -3
  33. package/dist/angular/ds-checkbox.js +3 -3
  34. package/dist/angular/ds-chip.js +3 -3
  35. package/dist/angular/ds-code-block.js +3 -3
  36. package/dist/angular/ds-conversation-list-item.js +3 -3
  37. package/dist/angular/ds-conversation-list-section.js +3 -3
  38. package/dist/angular/ds-conversation-list.js +3 -3
  39. package/dist/angular/ds-data-customize.js +3 -3
  40. package/dist/angular/ds-data-filter.js +3 -3
  41. package/dist/angular/ds-data-group.js +3 -3
  42. package/dist/angular/ds-data-preferences.js +3 -3
  43. package/dist/angular/ds-data-saved-views.js +3 -3
  44. package/dist/angular/ds-data-search.js +3 -3
  45. package/dist/angular/ds-data-sort.js +3 -3
  46. package/dist/angular/ds-data-toolbar.js +3 -3
  47. package/dist/angular/ds-divider.js +3 -3
  48. package/dist/angular/ds-empty-state.js +3 -3
  49. package/dist/angular/ds-field.js +3 -3
  50. package/dist/angular/ds-filter-menu.js +3 -3
  51. package/dist/angular/ds-icon.js +3 -3
  52. package/dist/angular/ds-inline-banner-settings.js +3 -3
  53. package/dist/angular/ds-input-date.js +3 -3
  54. package/dist/angular/ds-input-time.js +3 -3
  55. package/dist/angular/ds-input.js +3 -3
  56. package/dist/angular/ds-loader.js +3 -3
  57. package/dist/angular/ds-map-cluster-marker.js +3 -3
  58. package/dist/angular/ds-map-entity-marker.js +3 -3
  59. package/dist/angular/ds-map-marker.js +3 -3
  60. package/dist/angular/ds-markdown.js +3 -3
  61. package/dist/angular/ds-menu.js +3 -3
  62. package/dist/angular/ds-message-actions.js +3 -3
  63. package/dist/angular/ds-message-bubble.js +3 -3
  64. package/dist/angular/ds-message-composer.js +3 -3
  65. package/dist/angular/ds-message-scroller.js +3 -3
  66. package/dist/angular/ds-message.js +3 -3
  67. package/dist/angular/ds-mobile-bar-nav.js +3 -3
  68. package/dist/angular/ds-mobile-header.js +3 -3
  69. package/dist/angular/ds-mobile-section-switcher.js +3 -3
  70. package/dist/angular/ds-mobile-sheet-nav.js +3 -3
  71. package/dist/angular/ds-modal.js +3 -3
  72. package/dist/angular/ds-navigation-tab-group.js +3 -3
  73. package/dist/angular/ds-pagination.js +3 -3
  74. package/dist/angular/ds-panel-nav.js +3 -3
  75. package/dist/angular/ds-panel-sub-nav.js +3 -3
  76. package/dist/angular/ds-panel-tool-header.js +3 -3
  77. package/dist/angular/ds-panel-tool-search.js +3 -3
  78. package/dist/angular/ds-panel-tools.js +3 -3
  79. package/dist/angular/ds-paper-texture.js +3 -3
  80. package/dist/angular/ds-radio-tile.js +3 -3
  81. package/dist/angular/ds-radio.js +3 -3
  82. package/dist/angular/ds-score.js +3 -3
  83. package/dist/angular/ds-scroll-overlay.js +3 -3
  84. package/dist/angular/ds-select-toggle.js +3 -3
  85. package/dist/angular/ds-select.js +3 -3
  86. package/dist/angular/ds-setting-row-checkbox.js +3 -3
  87. package/dist/angular/ds-setting-row-radio.js +3 -3
  88. package/dist/angular/ds-setting-row-toggle.js +3 -3
  89. package/dist/angular/ds-shell-app.js +3 -3
  90. package/dist/angular/ds-shell-page.js +3 -3
  91. package/dist/angular/ds-shell-tools.js +3 -3
  92. package/dist/angular/ds-skeleton.js +3 -3
  93. package/dist/angular/ds-slider.js +3 -3
  94. package/dist/angular/ds-swatch-picker.js +3 -3
  95. package/dist/angular/ds-switch.js +3 -3
  96. package/dist/angular/ds-tab-group.js +3 -3
  97. package/dist/angular/ds-table.js +3 -3
  98. package/dist/angular/ds-tag.js +3 -3
  99. package/dist/angular/ds-text.js +3 -3
  100. package/dist/angular/ds-textarea.js +3 -3
  101. package/dist/angular/ds-time-picker.js +3 -3
  102. package/dist/angular/ds-toast.js +3 -3
  103. package/dist/angular/ds-tooltip-chart.js +3 -3
  104. package/dist/angular/ds-tooltip.js +3 -3
  105. package/dist/angular/ds-typing-indicator.js +3 -3
  106. package/dist/angular/number-value-accessor.js +3 -3
  107. package/dist/angular/radio-value-accessor.js +3 -3
  108. package/dist/angular/select-value-accessor.js +3 -3
  109. package/dist/angular/text-value-accessor.js +3 -3
  110. package/dist/angular/value-accessor.js +3 -3
  111. package/dist/components/THIRD-PARTY-NOTICES +1 -1
  112. package/dist/components/bundled-dependencies.json +1 -1
  113. package/dist/components/ds-agent-activity.js +1 -1
  114. package/dist/components/ds-agent-questionnaire.js +1 -1
  115. package/dist/components/ds-agent-response.js +1 -1
  116. package/dist/components/ds-agent-source-list.js +1 -1
  117. package/dist/components/ds-agent-tool-call.js +1 -1
  118. package/dist/components/ds-attachment-list.js +1 -1
  119. package/dist/components/ds-avatar.js +1 -1
  120. package/dist/components/ds-avatar.js.map +1 -1
  121. package/dist/components/ds-badge.js +1 -1
  122. package/dist/components/ds-banner.js +1 -1
  123. package/dist/components/ds-bar-nav.js +1 -1
  124. package/dist/components/ds-bar-page-title.js +1 -1
  125. package/dist/components/ds-bar-title.js +1 -1
  126. package/dist/components/ds-bar-workflow.js +1 -1
  127. package/dist/components/ds-button-filled.js +1 -1
  128. package/dist/components/ds-button-inverted.js +1 -1
  129. package/dist/components/ds-button-unfilled.js +1 -1
  130. package/dist/components/ds-calendar.js +1 -1
  131. package/dist/components/ds-card-action-center.js +1 -1
  132. package/dist/components/ds-card-chart.js +1 -1
  133. package/dist/components/ds-card-navigation.js +1 -1
  134. package/dist/components/ds-card-setting.js +1 -1
  135. package/dist/components/ds-chart.js +1 -1
  136. package/dist/components/ds-chip.js +1 -1
  137. package/dist/components/ds-code-block.js +1 -1
  138. package/dist/components/ds-conversation-list-item.js +1 -1
  139. package/dist/components/ds-data-customize.js +1 -1
  140. package/dist/components/ds-data-filter.js +1 -1
  141. package/dist/components/ds-data-group.js +1 -1
  142. package/dist/components/ds-data-preferences.js +1 -1
  143. package/dist/components/ds-data-saved-views.js +1 -1
  144. package/dist/components/ds-data-search.js +1 -1
  145. package/dist/components/ds-data-sort.js +1 -1
  146. package/dist/components/ds-empty-state.js +1 -1
  147. package/dist/components/ds-filter-menu.js +1 -1
  148. package/dist/components/ds-icon.js +1 -1
  149. package/dist/components/ds-input-date.js +1 -1
  150. package/dist/components/ds-input-time.js +1 -1
  151. package/dist/components/ds-input.js +1 -1
  152. package/dist/components/ds-map-entity-marker.js +1 -1
  153. package/dist/components/ds-map-marker.js +1 -1
  154. package/dist/components/ds-markdown.js +1 -1
  155. package/dist/components/ds-menu.js +1 -1
  156. package/dist/components/ds-message-actions.js +1 -1
  157. package/dist/components/ds-message-composer.js +1 -1
  158. package/dist/components/ds-message-scroller.js +1 -1
  159. package/dist/components/ds-mobile-bar-nav.js +1 -1
  160. package/dist/components/ds-mobile-header.js +1 -1
  161. package/dist/components/ds-mobile-section-switcher.js +1 -1
  162. package/dist/components/ds-mobile-sheet-nav.js +1 -1
  163. package/dist/components/ds-modal.js +1 -1
  164. package/dist/components/ds-navigation-tab-group.js +1 -1
  165. package/dist/components/ds-pagination.js +1 -1
  166. package/dist/components/ds-panel-nav.js +1 -1
  167. package/dist/components/ds-panel-tool-header.js +1 -1
  168. package/dist/components/ds-panel-tool-search.js +1 -1
  169. package/dist/components/ds-panel-tools.js +1 -1
  170. package/dist/components/ds-select-toggle.js +1 -1
  171. package/dist/components/ds-select.js +1 -1
  172. package/dist/components/ds-shell-app.js +1 -1
  173. package/dist/components/ds-shell-tools.js +1 -1
  174. package/dist/components/ds-tab-group.js +1 -1
  175. package/dist/components/ds-table.js +1 -1
  176. package/dist/components/ds-table.js.map +1 -1
  177. package/dist/components/ds-tag.js +1 -1
  178. package/dist/components/ds-time-picker.js +1 -1
  179. package/dist/components/ds-toast.js +1 -1
  180. package/dist/components/ds-tooltip-chart.js +1 -1
  181. package/dist/components/ds-typing-indicator.js +1 -1
  182. package/dist/components/{p-BZbJkJpG.js → p-02bODFpT.js} +2 -2
  183. package/dist/components/{p-BhbniDw4.js → p-8XJLNIKj.js} +2 -2
  184. package/dist/components/{p-3dG9v5Aw.js → p-B3-zImxn.js} +2 -2
  185. package/dist/components/{p-CeaIWw1Q.js → p-B6kIiHGc.js} +2 -2
  186. package/dist/components/{p-BV8sNEVP.js → p-BL98zGOI.js} +2 -2
  187. package/dist/components/{p-CL0FpMKo.js → p-BLPr2sCi.js} +2 -2
  188. package/dist/components/{p-BcYzHn1E.js → p-BLQgaSh-.js} +2 -2
  189. package/dist/components/{p-nakO4viw.js → p-BNimvRZy.js} +2 -2
  190. package/dist/components/{p-3Xp9hXbL.js → p-BO0Tb3LT.js} +2 -2
  191. package/dist/components/{p-DKZP3jCY.js → p-BiB2qjNS.js} +2 -2
  192. package/dist/components/{p-By_jYvHp.js → p-BufAjyCA.js} +2 -2
  193. package/dist/components/{p-CdMIA5hz.js → p-BxkhBlKJ.js} +2 -2
  194. package/dist/components/{p-r_e6_0nP.js → p-C9R-Yn0N.js} +2 -2
  195. package/dist/components/{p-9u2WDF0J.js → p-CbJx9swS.js} +2 -2
  196. package/dist/components/{p-Cfqvxxs2.js → p-CeaXdjwl.js} +2 -2
  197. package/dist/components/{p-8fddX3V3.js → p-ClOvX8cD.js} +2 -2
  198. package/dist/components/{p-8fddX3V3.js.map → p-ClOvX8cD.js.map} +1 -1
  199. package/dist/components/{p-DYruK9cb.js → p-Cm1FS7D-.js} +2 -2
  200. package/dist/components/{p-CbP9ASmI.js → p-CtBlyEwu.js} +2 -2
  201. package/dist/components/{p-B-ChwMFE.js → p-CwL5zodA.js} +2 -2
  202. package/dist/components/{p-IQdLCfJJ.js → p-CymFzY_N.js} +2 -2
  203. package/dist/components/{p-WEffoQQD.js → p-D1D_nwPJ.js} +2 -2
  204. package/dist/components/p-D1D_nwPJ.js.map +1 -0
  205. package/dist/components/{p-DimFbKZH.js → p-D4wFhnby.js} +2 -2
  206. package/dist/components/{p-BVUdtuog.js → p-DACIe-66.js} +2 -2
  207. package/dist/components/{p-CYmz92EF.js → p-DC_yjqdf.js} +2 -2
  208. package/dist/components/{p-CEHhx4VQ.js → p-DGznD-OD.js} +2 -2
  209. package/dist/components/{p-BfRAVR40.js → p-DMjOjrHi.js} +2 -2
  210. package/dist/components/{p-DFR9hqDU.js → p-DOazC0iw.js} +2 -2
  211. package/dist/components/{p-CpqLGzv4.js → p-DXAY6GB5.js} +2 -2
  212. package/dist/components/{p-CUG4EkKn.js → p-DbBRCHst.js} +2 -2
  213. package/dist/components/{p-DiUaIJ91.js → p-DfGJX_Eo.js} +2 -2
  214. package/dist/components/{p-DuhJ49iN.js → p-Dp90dvw8.js} +2 -2
  215. package/dist/components/{p-BIMW5_Z7.js → p-DwiHy7ws.js} +2 -2
  216. package/dist/components/p-FsosWHV6.js +2 -0
  217. package/dist/components/{p-C5HGwzy9.js.map → p-FsosWHV6.js.map} +1 -1
  218. package/dist/components/p-GP7Dz3qO.js +3 -0
  219. package/dist/components/p-GP7Dz3qO.js.map +1 -0
  220. package/dist/components/{p-BpPHCF63.js → p-S6vTfjOk.js} +2 -2
  221. package/dist/components/{p-CbPGO7vl.js → p-SuM1Dske.js} +2 -2
  222. package/dist/components/{p-5PReYMiY.js → p-a_-GSwE_.js} +2 -2
  223. package/dist/components/p-mSCnWLhL.js.map +1 -1
  224. package/dist/components/{p-BAIYOobl.js → p-xszHlNkG.js} +2 -2
  225. package/dist/components/p-xszHlNkG.js.map +1 -0
  226. package/dist/docs/components.json +13 -3
  227. package/dist/mcp-data/registry/agent-activity.json +2 -2
  228. package/dist/mcp-data/registry/agent-questionnaire.json +2 -2
  229. package/dist/mcp-data/registry/agent-response.json +2 -2
  230. package/dist/mcp-data/registry/agent-source-list.json +2 -2
  231. package/dist/mcp-data/registry/agent-tool-call.json +2 -2
  232. package/dist/mcp-data/registry/attachment-list.json +2 -2
  233. package/dist/mcp-data/registry/avatar.json +2 -2
  234. package/dist/mcp-data/registry/badge.json +3 -3
  235. package/dist/mcp-data/registry/banner.json +2 -2
  236. package/dist/mcp-data/registry/bar-action.json +2 -2
  237. package/dist/mcp-data/registry/bar-nav.json +2 -2
  238. package/dist/mcp-data/registry/bar-page-title.json +2 -2
  239. package/dist/mcp-data/registry/bar-title.json +2 -2
  240. package/dist/mcp-data/registry/bar-workflow.json +2 -2
  241. package/dist/mcp-data/registry/breadcrumb.json +2 -2
  242. package/dist/mcp-data/registry/button-filled.json +2 -2
  243. package/dist/mcp-data/registry/button-inverted.json +2 -2
  244. package/dist/mcp-data/registry/button-unfilled.json +3 -3
  245. package/dist/mcp-data/registry/calendar.json +2 -2
  246. package/dist/mcp-data/registry/card-action-center.json +2 -2
  247. package/dist/mcp-data/registry/card-chart.json +2 -2
  248. package/dist/mcp-data/registry/card-navigation.json +2 -2
  249. package/dist/mcp-data/registry/card-overview.json +2 -2
  250. package/dist/mcp-data/registry/card-setting.json +2 -2
  251. package/dist/mcp-data/registry/card-settings-scope.json +2 -2
  252. package/dist/mcp-data/registry/chart-legend.json +2 -2
  253. package/dist/mcp-data/registry/chart.json +2 -2
  254. package/dist/mcp-data/registry/checkbox-group.json +2 -2
  255. package/dist/mcp-data/registry/checkbox.json +2 -2
  256. package/dist/mcp-data/registry/chip.json +2 -2
  257. package/dist/mcp-data/registry/code-block.json +2 -2
  258. package/dist/mcp-data/registry/conversation-list-item.json +2 -2
  259. package/dist/mcp-data/registry/conversation-list-section.json +2 -2
  260. package/dist/mcp-data/registry/conversation-list.json +2 -2
  261. package/dist/mcp-data/registry/data-customize.json +2 -2
  262. package/dist/mcp-data/registry/data-filter.json +2 -2
  263. package/dist/mcp-data/registry/data-group.json +2 -2
  264. package/dist/mcp-data/registry/data-preferences.json +2 -2
  265. package/dist/mcp-data/registry/data-saved-views.json +2 -2
  266. package/dist/mcp-data/registry/data-search.json +2 -2
  267. package/dist/mcp-data/registry/data-sort.json +2 -2
  268. package/dist/mcp-data/registry/data-toolbar.json +2 -2
  269. package/dist/mcp-data/registry/divider.json +2 -2
  270. package/dist/mcp-data/registry/empty-state.json +2 -2
  271. package/dist/mcp-data/registry/field.json +2 -2
  272. package/dist/mcp-data/registry/filter-menu.json +2 -2
  273. package/dist/mcp-data/registry/icon.json +3 -3
  274. package/dist/mcp-data/registry/inline-banner-settings.json +2 -2
  275. package/dist/mcp-data/registry/input-date.json +2 -2
  276. package/dist/mcp-data/registry/input-time.json +2 -2
  277. package/dist/mcp-data/registry/input.json +2 -2
  278. package/dist/mcp-data/registry/loader.json +2 -2
  279. package/dist/mcp-data/registry/map-cluster-marker.json +2 -2
  280. package/dist/mcp-data/registry/map-entity-marker.json +2 -2
  281. package/dist/mcp-data/registry/map-marker.json +2 -2
  282. package/dist/mcp-data/registry/markdown.json +2 -2
  283. package/dist/mcp-data/registry/menu.json +2 -2
  284. package/dist/mcp-data/registry/message-actions.json +2 -2
  285. package/dist/mcp-data/registry/message-bubble.json +2 -2
  286. package/dist/mcp-data/registry/message-composer.json +2 -2
  287. package/dist/mcp-data/registry/message-scroller.json +2 -2
  288. package/dist/mcp-data/registry/message.json +2 -2
  289. package/dist/mcp-data/registry/mobile-bar-nav.json +2 -2
  290. package/dist/mcp-data/registry/mobile-header.json +2 -2
  291. package/dist/mcp-data/registry/mobile-section-switcher.json +2 -2
  292. package/dist/mcp-data/registry/mobile-sheet-nav.json +2 -2
  293. package/dist/mcp-data/registry/modal.json +2 -2
  294. package/dist/mcp-data/registry/navigation-tab-group.json +2 -2
  295. package/dist/mcp-data/registry/pagination.json +2 -2
  296. package/dist/mcp-data/registry/panel-nav.json +2 -2
  297. package/dist/mcp-data/registry/panel-sub-nav.json +2 -2
  298. package/dist/mcp-data/registry/panel-tool-header.json +2 -2
  299. package/dist/mcp-data/registry/panel-tool-search.json +2 -2
  300. package/dist/mcp-data/registry/panel-tools.json +2 -2
  301. package/dist/mcp-data/registry/paper-texture.json +2 -2
  302. package/dist/mcp-data/registry/radio-tile.json +2 -2
  303. package/dist/mcp-data/registry/radio.json +2 -2
  304. package/dist/mcp-data/registry/registry.json +6 -3
  305. package/dist/mcp-data/registry/score.json +2 -2
  306. package/dist/mcp-data/registry/scroll-overlay.json +2 -2
  307. package/dist/mcp-data/registry/select-toggle.json +2 -2
  308. package/dist/mcp-data/registry/select.json +2 -2
  309. package/dist/mcp-data/registry/setting-row-checkbox.json +2 -2
  310. package/dist/mcp-data/registry/setting-row-radio.json +2 -2
  311. package/dist/mcp-data/registry/setting-row-toggle.json +2 -2
  312. package/dist/mcp-data/registry/shell-app.json +2 -2
  313. package/dist/mcp-data/registry/shell-page.json +2 -2
  314. package/dist/mcp-data/registry/shell-tools.json +2 -2
  315. package/dist/mcp-data/registry/skeleton.json +2 -2
  316. package/dist/mcp-data/registry/slider.json +2 -2
  317. package/dist/mcp-data/registry/swatch-picker.json +2 -2
  318. package/dist/mcp-data/registry/switch.json +2 -2
  319. package/dist/mcp-data/registry/tab-group.json +2 -2
  320. package/dist/mcp-data/registry/table.json +17 -6
  321. package/dist/mcp-data/registry/tag.json +2 -2
  322. package/dist/mcp-data/registry/text.json +2 -2
  323. package/dist/mcp-data/registry/textarea.json +2 -2
  324. package/dist/mcp-data/registry/time-picker.json +2 -2
  325. package/dist/mcp-data/registry/toast.json +2 -2
  326. package/dist/mcp-data/registry/tooltip-chart.json +3 -3
  327. package/dist/mcp-data/registry/tooltip.json +2 -2
  328. package/dist/mcp-data/registry/typing-indicator.json +2 -2
  329. package/dist/types/components/Table/Table.d.ts +7 -2
  330. package/package.json +7 -7
  331. package/dist/components/p-BAIYOobl.js.map +0 -1
  332. package/dist/components/p-BVsVUnAw.js +0 -3
  333. package/dist/components/p-BVsVUnAw.js.map +0 -1
  334. package/dist/components/p-C5HGwzy9.js +0 -2
  335. package/dist/components/p-WEffoQQD.js.map +0 -1
  336. /package/dist/components/{p-BZbJkJpG.js.map → p-02bODFpT.js.map} +0 -0
  337. /package/dist/components/{p-BhbniDw4.js.map → p-8XJLNIKj.js.map} +0 -0
  338. /package/dist/components/{p-3dG9v5Aw.js.map → p-B3-zImxn.js.map} +0 -0
  339. /package/dist/components/{p-CeaIWw1Q.js.map → p-B6kIiHGc.js.map} +0 -0
  340. /package/dist/components/{p-BV8sNEVP.js.map → p-BL98zGOI.js.map} +0 -0
  341. /package/dist/components/{p-CL0FpMKo.js.map → p-BLPr2sCi.js.map} +0 -0
  342. /package/dist/components/{p-BcYzHn1E.js.map → p-BLQgaSh-.js.map} +0 -0
  343. /package/dist/components/{p-nakO4viw.js.map → p-BNimvRZy.js.map} +0 -0
  344. /package/dist/components/{p-3Xp9hXbL.js.map → p-BO0Tb3LT.js.map} +0 -0
  345. /package/dist/components/{p-DKZP3jCY.js.map → p-BiB2qjNS.js.map} +0 -0
  346. /package/dist/components/{p-By_jYvHp.js.map → p-BufAjyCA.js.map} +0 -0
  347. /package/dist/components/{p-CdMIA5hz.js.map → p-BxkhBlKJ.js.map} +0 -0
  348. /package/dist/components/{p-r_e6_0nP.js.map → p-C9R-Yn0N.js.map} +0 -0
  349. /package/dist/components/{p-9u2WDF0J.js.map → p-CbJx9swS.js.map} +0 -0
  350. /package/dist/components/{p-Cfqvxxs2.js.map → p-CeaXdjwl.js.map} +0 -0
  351. /package/dist/components/{p-DYruK9cb.js.map → p-Cm1FS7D-.js.map} +0 -0
  352. /package/dist/components/{p-CbP9ASmI.js.map → p-CtBlyEwu.js.map} +0 -0
  353. /package/dist/components/{p-B-ChwMFE.js.map → p-CwL5zodA.js.map} +0 -0
  354. /package/dist/components/{p-IQdLCfJJ.js.map → p-CymFzY_N.js.map} +0 -0
  355. /package/dist/components/{p-DimFbKZH.js.map → p-D4wFhnby.js.map} +0 -0
  356. /package/dist/components/{p-BVUdtuog.js.map → p-DACIe-66.js.map} +0 -0
  357. /package/dist/components/{p-CYmz92EF.js.map → p-DC_yjqdf.js.map} +0 -0
  358. /package/dist/components/{p-CEHhx4VQ.js.map → p-DGznD-OD.js.map} +0 -0
  359. /package/dist/components/{p-BfRAVR40.js.map → p-DMjOjrHi.js.map} +0 -0
  360. /package/dist/components/{p-DFR9hqDU.js.map → p-DOazC0iw.js.map} +0 -0
  361. /package/dist/components/{p-CpqLGzv4.js.map → p-DXAY6GB5.js.map} +0 -0
  362. /package/dist/components/{p-CUG4EkKn.js.map → p-DbBRCHst.js.map} +0 -0
  363. /package/dist/components/{p-DiUaIJ91.js.map → p-DfGJX_Eo.js.map} +0 -0
  364. /package/dist/components/{p-DuhJ49iN.js.map → p-Dp90dvw8.js.map} +0 -0
  365. /package/dist/components/{p-BIMW5_Z7.js.map → p-DwiHy7ws.js.map} +0 -0
  366. /package/dist/components/{p-BpPHCF63.js.map → p-S6vTfjOk.js.map} +0 -0
  367. /package/dist/components/{p-CbPGO7vl.js.map → p-SuM1Dske.js.map} +0 -0
  368. /package/dist/components/{p-5PReYMiY.js.map → p-a_-GSwE_.js.map} +0 -0
@@ -60,6 +60,7 @@
60
60
  "Static and infinite tables may show an optional result summary footer when displayedCount and totalCount are finite. Virtual mode shows a total-only summary and ignores displayedCount. Pagination mode instead renders a named Pagination control with a page-size picker, vertical divider, and page position; range changes are announced independently with complete phrasing. Unmodified Left and Right Arrow request the adjacent page while focus is inside a paginated table or on a containing page scroller that owns it; editable fields, choice controls, sliders, open menus, and modified key combinations retain their own keyboard behavior. A table-owned footer slot can replace summary copy, while footer-leading and footer-trailing remain available in the same compact-bar anatomy. Nested dialog footers are ignored."
61
61
  ],
62
62
  "states": [
63
+ "Initial-error recovery actions are application-owned controls displayed below the error message. The application owns retry behavior, loading, result updates and focus after recovery; controls are not shown for empty, populated, loading, or invalid-viewport states.",
63
64
  "Column super-groups describe adjacent visible fields. Hidden and reordered fields recompute the bands; unrelated fields, selection, elastic space, and pin boundaries split them. Compound sort controls remain in the leaf headers.",
64
65
  "Cell presentation moves the complete content stack for deliberate middle or bottom alignment; the default remains first-track alignment. Semantic highlight borders require an accessible explanation, and flags require labels. Presentation metadata never changes the cell value or the shared text, icon, tag and action track anatomy.",
65
66
  "Report merges name ordered row and column identities. Only a complete contiguous non-overlapping rectangle is accepted, and invalid definitions leave original values visible. Selection checkboxes are never merged. Merges cannot cross pin or elastic-spacer boundaries, and grouped or editable grids do not merge. Cross-row merges require non-windowed reports; column-only merges can use virtual rows. Applications recompute spans with their final data order and retain covered values.",
@@ -941,7 +942,12 @@
941
942
  "description": "Scroll the nearest vertical owner only enough to reveal a supplied row.\nResolves false when the row is absent from the supplied, expanded table model.\nThis method never moves focus."
942
943
  }
943
944
  ],
944
- "slots": []
945
+ "slots": [
946
+ {
947
+ "name": "error-actions",
948
+ "description": "Application-owned recovery controls below the initial error message."
949
+ }
950
+ ]
945
951
  },
946
952
  "props": {
947
953
  "alternateView": {
@@ -1645,7 +1651,12 @@
1645
1651
  "description": "Scroll the nearest vertical owner only enough to reveal a supplied row.\nResolves false when the row is absent from the supplied, expanded table model.\nThis method never moves focus."
1646
1652
  }
1647
1653
  ],
1648
- "slots": [],
1654
+ "slots": [
1655
+ {
1656
+ "name": "error-actions",
1657
+ "description": "Application-owned recovery controls below the initial error message."
1658
+ }
1659
+ ],
1649
1660
  "exports": {
1650
1661
  "customElement": "ds-table",
1651
1662
  "react": "DsTable",
@@ -1674,8 +1685,8 @@
1674
1685
  "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: cellRange, cellSpans, collapsedGroupIds, columns, customizeOptions, fieldOrder, grouping, groups, hiddenFieldIds, highlightFieldIds, highlightTerms, pagination, pinnedFieldIds, rows, selectedRowIds, sort.",
1675
1686
  "peerDependencies": {
1676
1687
  "required": [
1677
- "@ds-mo/tokens ^8.0.1",
1678
- "@ds-mo/icons ^8.1.2"
1688
+ "@ds-mo/tokens ^9.0.0",
1689
+ "@ds-mo/icons ^8.2.1"
1679
1690
  ],
1680
1691
  "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
1681
1692
  }
@@ -1709,12 +1720,12 @@
1709
1720
  "files": [
1710
1721
  {
1711
1722
  "path": "src/wc/components/Table/Table.css",
1712
- "content": "@import '../../styles/table.css';\n@import '../../styles/control-elevation.css';\n@import '../../utils/chrome-header.css';\n@import '../../utils/radius.css';\n@import '../../utils/control-density.css';\n@import '../../utils/forced-colors.css';\n@import '../../utils/text-decoration.css';\n\n:host {\n display: block;\n box-sizing: border-box;\n min-width: 0;\n max-width: 100%;\n}\n\n/* Keep size containment away from the recycled scrollport (WebKit). */\n.ds-table__caption-bar {\n container-type: inline-size;\n container-name: ds-table;\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::slotted([slot='header']) {\n inline-size: 100%;\n min-inline-size: 0;\n}\n\n::slotted([slot='caption-trailing']) {\n display: inline-flex;\n flex: 0 0 auto;\n align-items: center;\n}\n\n::slotted([slot='alternate-view']) {\n display: block;\n inline-size: 100%;\n block-size: 100%;\n min-block-size: 0;\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__card-label {\n display: none;\n padding-inline: var(--_table-cell-track-padding-inline);\n}\n\n.ds-table--responsive-cards .ds-table__frame[data-narrow='true'] ds-text.ds-table__card-label {\n display: block;\n}\n\nds-text.ds-table__cell-track {\n display: flex;\n}\n\n.ds-table__cell--text-wrap ds-text.ds-table__cell-track--text,\n.ds-table__cell--icon-text-wrap ds-text.ds-table__cell-track--text,\n.ds-table__cell--score-text-wrap ds-text.ds-table__cell-track--text {\n display: block;\n}\n\nds-text.ds-table__cell-track--text {\n padding-inline: var(--_table-cell-track-padding-inline);\n}\n\nds-text.ds-table__cell-primary,\n.ds-table__cell--empty ds-text.ds-table__cell-track {\n padding-block: var(--dimension-space-025);\n}\n\n/*\n * The public :where() wrap recipe has zero specificity, so ds-text's\n * variant line-height would win inside the component. Reassert on wrapping\n * cell layout classes so extra primary and secondary lines occupy later tracks.\n */\n.ds-table__cell--text-wrap.ds-table__cell--text-single .ds-table__cell-primary,\n.ds-table__cell--text-wrap.ds-table__cell--text-multi .ds-table__cell-primary,\n.ds-table__cell--icon-text-wrap.ds-table__cell--icon-text-single .ds-table__cell-primary,\n.ds-table__cell--icon-text-wrap.ds-table__cell--icon-text-multi .ds-table__cell-primary,\n.ds-table__cell--score-text-wrap.ds-table__cell--score-text-single .ds-table__cell-primary,\n.ds-table__cell--score-text-wrap.ds-table__cell--score-text-multi .ds-table__cell-primary {\n line-height: var(--_table-wrap-primary-line-height);\n padding-block: var(--_table-wrap-primary-padding-block);\n}\n\n.ds-table__cell--text-wrap.ds-table__cell--text-multi .ds-table__cell-copy,\n.ds-table__cell--icon-text-wrap.ds-table__cell--icon-text-multi .ds-table__cell-copy,\n.ds-table__cell--score-text-wrap.ds-table__cell--score-text-multi .ds-table__cell-copy {\n gap: 0;\n}\n\n.ds-table__cell--text-wrap.ds-table__cell--text-multi .ds-table__cell-secondary,\n.ds-table__cell--icon-text-wrap.ds-table__cell--icon-text-multi .ds-table__cell-secondary,\n.ds-table__cell--score-text-wrap.ds-table__cell--score-text-multi .ds-table__cell-secondary {\n line-height: var(--_table-wrap-secondary-line-height);\n padding-block: var(--_table-wrap-secondary-padding-block);\n}\n\nds-text.ds-table__cell-secondary,\nds-text.ds-table__cell-tertiary {\n padding-block: var(--dimension-space-025);\n}\n\n.ds-table__cell--text-multi ds-text.ds-table__cell-secondary,\n.ds-table__cell--text-triple ds-text.ds-table__cell-secondary,\n.ds-table__cell--text-triple ds-text.ds-table__cell-tertiary,\n.ds-table__cell--icon-text-multi ds-text.ds-table__cell-secondary,\n.ds-table__cell--icon-text-triple ds-text.ds-table__cell-secondary,\n.ds-table__cell--icon-text-triple ds-text.ds-table__cell-tertiary,\n.ds-table__cell--score-text-multi ds-text.ds-table__cell-secondary,\n.ds-table__cell--score-text-triple ds-text.ds-table__cell-secondary,\n.ds-table__cell--score-text-triple ds-text.ds-table__cell-tertiary,\n.ds-table__cell--primary-text ds-text.ds-table__cell-secondary {\n min-block-size: var(--_table-cell-track-min-block-size);\n}\n\nds-text.ds-table__cell-run,\nds-text.ds-table__cell-run-separator {\n display: flex;\n}\n\nds-text.ds-table__group-label,\nds-text.ds-table__group-count,\nds-text.ds-table__group-accessory,\nds-text.ds-table__group-accessory-separator {\n display: flex;\n align-items: center;\n}\n\nds-text.ds-table__group-count {\n min-block-size: var(--_table-cell-track-min-block-size);\n padding-inline-end: var(--dimension-space-050);\n}\n\n.ds-table__cell-tag-stack--tag-with-text ds-text.ds-table__cell-tag-text,\n.ds-table__cell-tag-stack--text-with-tag ds-text.ds-table__cell-tag-text {\n padding-inline: var(--_table-cell-track-padding-inline);\n padding-block: var(--dimension-space-025);\n min-block-size: var(--_table-cell-track-min-block-size);\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__footer-summary-skeleton {\n min-width: 0;\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-size-300);\n block-size: var(--dimension-size-300);\n}\n\n.ds-table__loading-label {\n display: inline-grid;\n min-inline-size: 0;\n align-items: center;\n}\n\n.ds-table__loading-label > * {\n grid-area: 1 / 1;\n}\n\n.ds-table__loading-label > ds-text {\n visibility: hidden;\n}\n\n.ds-table__caption-customizer {\n position: relative;\n display: flex;\n align-items: center;\n min-inline-size: 0;\n}\n\n.ds-table__caption-mode-switcher {\n display: flex;\n align-items: center;\n}\n\n.ds-table__caption-customizer--loading > ds-tooltip ds-button-unfilled {\n visibility: hidden;\n}\n\n.ds-table__caption-customizer > ds-skeleton {\n position: absolute;\n inset: 0;\n}\n\n.ds-table__caption-bar:has([slot='header-after']) {\n flex-direction: column;\n align-items: stretch;\n block-size: auto;\n padding: 0;\n gap: 0;\n}\n\n.ds-table__caption-bar:has([slot='header-after']) .ds-table__caption-content {\n flex: 0 0 auto;\n min-block-size: var(--dimension-size-600);\n padding: var(--dimension-space-100);\n}\n",
1723
+ "content": "@import '../../styles/table.css';\n@import '../../styles/control-elevation.css';\n@import '../../utils/chrome-header.css';\n@import '../../utils/radius.css';\n@import '../../utils/control-density.css';\n@import '../../utils/forced-colors.css';\n@import '../../utils/text-decoration.css';\n\n:host {\n display: block;\n box-sizing: border-box;\n min-width: 0;\n max-width: 100%;\n}\n\n/* Keep size containment away from the recycled scrollport (WebKit). */\n.ds-table__caption-bar {\n container-type: inline-size;\n container-name: ds-table;\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::slotted([slot='header']) {\n inline-size: 100%;\n min-inline-size: 0;\n}\n\n::slotted([slot='caption-trailing']) {\n display: inline-flex;\n flex: 0 0 auto;\n align-items: center;\n}\n\n::slotted([slot='alternate-view']) {\n display: block;\n inline-size: 100%;\n block-size: 100%;\n min-block-size: 0;\n}\n\n/* Retain consumer controls outside the active error view without exposing them. */\n.ds-table__parked-actions {\n display: none;\n}\n\n.ds-table__error-actions {\n display: contents;\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__card-label {\n display: none;\n padding-inline: var(--_table-cell-track-padding-inline);\n}\n\n.ds-table--responsive-cards .ds-table__frame[data-narrow='true'] ds-text.ds-table__card-label {\n display: block;\n}\n\nds-text.ds-table__cell-track {\n display: flex;\n}\n\n.ds-table__cell--text-wrap ds-text.ds-table__cell-track--text,\n.ds-table__cell--icon-text-wrap ds-text.ds-table__cell-track--text,\n.ds-table__cell--score-text-wrap ds-text.ds-table__cell-track--text {\n display: block;\n}\n\nds-text.ds-table__cell-track--text {\n padding-inline: var(--_table-cell-track-padding-inline);\n}\n\nds-text.ds-table__cell-primary,\n.ds-table__cell--empty ds-text.ds-table__cell-track {\n padding-block: var(--dimension-space-025);\n}\n\n/*\n * The public :where() wrap recipe has zero specificity, so ds-text's\n * variant line-height would win inside the component. Reassert on wrapping\n * cell layout classes so extra primary and secondary lines occupy later tracks.\n */\n.ds-table__cell--text-wrap.ds-table__cell--text-single .ds-table__cell-primary,\n.ds-table__cell--text-wrap.ds-table__cell--text-multi .ds-table__cell-primary,\n.ds-table__cell--icon-text-wrap.ds-table__cell--icon-text-single .ds-table__cell-primary,\n.ds-table__cell--icon-text-wrap.ds-table__cell--icon-text-multi .ds-table__cell-primary,\n.ds-table__cell--score-text-wrap.ds-table__cell--score-text-single .ds-table__cell-primary,\n.ds-table__cell--score-text-wrap.ds-table__cell--score-text-multi .ds-table__cell-primary {\n line-height: var(--_table-wrap-primary-line-height);\n padding-block: var(--_table-wrap-primary-padding-block);\n}\n\n.ds-table__cell--text-wrap.ds-table__cell--text-multi .ds-table__cell-copy,\n.ds-table__cell--icon-text-wrap.ds-table__cell--icon-text-multi .ds-table__cell-copy,\n.ds-table__cell--score-text-wrap.ds-table__cell--score-text-multi .ds-table__cell-copy {\n gap: 0;\n}\n\n.ds-table__cell--text-wrap.ds-table__cell--text-multi .ds-table__cell-secondary,\n.ds-table__cell--icon-text-wrap.ds-table__cell--icon-text-multi .ds-table__cell-secondary,\n.ds-table__cell--score-text-wrap.ds-table__cell--score-text-multi .ds-table__cell-secondary {\n line-height: var(--_table-wrap-secondary-line-height);\n padding-block: var(--_table-wrap-secondary-padding-block);\n}\n\nds-text.ds-table__cell-secondary,\nds-text.ds-table__cell-tertiary {\n padding-block: var(--dimension-space-025);\n}\n\n.ds-table__cell--text-multi ds-text.ds-table__cell-secondary,\n.ds-table__cell--text-triple ds-text.ds-table__cell-secondary,\n.ds-table__cell--text-triple ds-text.ds-table__cell-tertiary,\n.ds-table__cell--icon-text-multi ds-text.ds-table__cell-secondary,\n.ds-table__cell--icon-text-triple ds-text.ds-table__cell-secondary,\n.ds-table__cell--icon-text-triple ds-text.ds-table__cell-tertiary,\n.ds-table__cell--score-text-multi ds-text.ds-table__cell-secondary,\n.ds-table__cell--score-text-triple ds-text.ds-table__cell-secondary,\n.ds-table__cell--score-text-triple ds-text.ds-table__cell-tertiary,\n.ds-table__cell--primary-text ds-text.ds-table__cell-secondary {\n min-block-size: var(--_table-cell-track-min-block-size);\n}\n\nds-text.ds-table__cell-run,\nds-text.ds-table__cell-run-separator {\n display: flex;\n}\n\nds-text.ds-table__group-label,\nds-text.ds-table__group-count,\nds-text.ds-table__group-accessory,\nds-text.ds-table__group-accessory-separator {\n display: flex;\n align-items: center;\n}\n\nds-text.ds-table__group-count {\n min-block-size: var(--_table-cell-track-min-block-size);\n padding-inline-end: var(--dimension-space-050);\n}\n\n.ds-table__cell-tag-stack--tag-with-text ds-text.ds-table__cell-tag-text,\n.ds-table__cell-tag-stack--text-with-tag ds-text.ds-table__cell-tag-text {\n padding-inline: var(--_table-cell-track-padding-inline);\n padding-block: var(--dimension-space-025);\n min-block-size: var(--_table-cell-track-min-block-size);\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__footer-summary-skeleton {\n min-width: 0;\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-size-300);\n block-size: var(--dimension-size-300);\n}\n\n.ds-table__loading-label {\n display: inline-grid;\n min-inline-size: 0;\n align-items: center;\n}\n\n.ds-table__loading-label > * {\n grid-area: 1 / 1;\n}\n\n.ds-table__loading-label > ds-text {\n visibility: hidden;\n}\n\n.ds-table__caption-customizer {\n position: relative;\n display: flex;\n align-items: center;\n min-inline-size: 0;\n}\n\n.ds-table__caption-mode-switcher {\n display: flex;\n align-items: center;\n}\n\n.ds-table__caption-customizer--loading > ds-tooltip ds-button-unfilled {\n visibility: hidden;\n}\n\n.ds-table__caption-customizer > ds-skeleton {\n position: absolute;\n inset: 0;\n}\n\n.ds-table__caption-bar:has([slot='header-after']) {\n flex-direction: column;\n align-items: stretch;\n block-size: auto;\n padding: 0;\n gap: 0;\n}\n\n.ds-table__caption-bar:has([slot='header-after']) .ds-table__caption-content {\n flex: 0 0 auto;\n min-block-size: var(--dimension-size-600);\n padding: var(--dimension-space-100);\n}\n",
1713
1724
  "type": "registry:ui"
1714
1725
  },
1715
1726
  {
1716
1727
  "path": "src/wc/components/Table/Table.tsx",
1717
- "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Listen,\n Method,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport {\n deriveTableSelectionState,\n formatTableResultSummary,\n formatTableTotalSummary,\n hasOwnedTableFooterSlot,\n isTableCellAction,\n nextDataSortState,\n tableColumnSize,\n tableModelIssues,\n TABLE_GROUP_HERO_SCORE_PRESENTATION,\n tableRows,\n toggleAllLoadedTableRows,\n toggleTableGroupCollapsed,\n toggleTableGroupSelection,\n nextTableGroupsCollapsed,\n toggleTableRowSelection,\n} from './table-model';\nimport { createTableRenderModel, type TableRenderModel } from './table-render-model';\nimport {\n isRenderableTableActionMenu,\n nextTableActionMenuElementId,\n tableActionMenuSections,\n tableActionTriggerId,\n} from './table-action-menu';\nimport { resolveTableVisibleColumns } from './table-column-customizer';\nimport { createTableCaptionController, createTableCaptionState } from './table-caption-controller';\nimport { createTableEditingController, type TableEditingState } from './table-editing-controller';\nimport { resolveTableTruncateTrack, tableTruncateLabel } from './table-truncate';\nimport { renderTableRow as renderTableRowView } from './table-row-view';\nimport { TableBodyRenderer } from './table-body-renderer';\nimport { tableHeaderBands, tablePinnedColumns, tableSpanModel } from './table-structure';\nimport type { TableCellSpan, TableCellRange, TableCellsChangeDetail } from './table-types';\nimport { renderTableSkeletonBody } from './table-skeleton-view';\nimport { renderTableLoadContent } from './table-load-view';\nimport {\n isTableCardViewport,\n TABLE_CARD_VIEWPORT_BREAKPOINT,\n TableLayoutController,\n} from './table-layout-controller';\nimport { TableLoadController } from './table-load-controller';\nimport { TableGroupLoadController } from './table-group-load-controller';\nimport {\n estimateTableRowBlockSize,\n flattenTableVirtualItems,\n resolveTableVirtualPlan,\n TABLE_VIRTUAL_TOTAL_SUMMARY_LABEL,\n TABLE_VIRTUAL_VIEWPORT_REQUIRED_BODY,\n TABLE_VIRTUAL_VIEWPORT_REQUIRED_HEADING,\n type TableVirtualItem,\n type TableVirtualPlan,\n} from './table-virtual-model';\nimport { TableVirtualController } from './table-virtual-controller';\nimport type { PaginationChangeDetail } from '../Pagination/pagination-types';\nimport type { MenuItemData } from '../Menu/menu-types';\nimport { resolvePaginationState } from '../Pagination/pagination-model';\nimport { resolveCssLengthPx } from '../../utils/resolve-css-length-px';\nimport { isElementTruncated } from '../../utils/is-element-truncated';\nimport { observeTableCaptionCompact } from '../../utils/table-caption-compact';\nimport { resolveTableFitPageSize } from './table-pagination-fit';\nimport { createTableHighlightMatcher, type TableHighlightMatcher } from './table-highlight';\nimport {\n paginationShortcutBlockedByPath,\n shouldHandleContainingPagePaginationShortcut,\n} from './table-pagination-shortcut';\nimport {\n TableViewportFitController,\n type TableViewportFitMetrics,\n} from './table-viewport-fit-controller';\nimport type {\n TableCaptionVisibility,\n TableChromeLayout,\n TableCellActionDetail,\n TableColumn,\n DataFieldsConfigChangeDetail,\n TableDataMode,\n TableDataModeChangeDetail,\n DataGroup,\n TableGroupCollapseChangeDetail,\n TableGroupLoadMoreDetail,\n DataGroupingState,\n TableLoadMoreDetail,\n TableLoadMoreMode,\n TablePaginationState,\n TableRow,\n TableRowActivateDetail,\n TableSelectionChangeDetail,\n TableSelectionMode,\n DataSortChangeDetail,\n DataSortState,\n} from './table-types';\n\nconst TABLE_FOOTER_SLOT_LEADING = 1;\nconst TABLE_FOOTER_SLOT_COPY = 2;\nconst TABLE_FOOTER_SLOT_TRAILING = 4;\nconst TABLE_NON_VIRTUAL_WINDOW_THRESHOLD = 50;\n\n@Component({\n tag: 'ds-table',\n styleUrls: ['../../utils/focus-ring.css', '../../utils/interaction-fill.css', 'Table.css'],\n scoped: true,\n})\nexport class Table {\n @Element() el!: HTMLElement;\n /** Stable column definitions. Assign through JavaScript. */\n @Prop() columns: TableColumn[] = [];\n /** Opt-in native card presentation below the 768px browser viewport breakpoint. */\n @Prop() responsiveLayout: 'scroll' | 'cards' = 'scroll';\n /** Keep all table chrome contained, or let the caption and footer span edge to edge around an inset data frame. */\n @Prop() chromeLayout: TableChromeLayout = 'contained';\n /** Explicit report merges; invalid combinations leave the original cells visible. */\n @Prop() cellSpans: TableCellSpan[] = [];\n /** Table is read-only; edit uses per-cell pencil buttons; grid adds cell selection and keyboard navigation. Grouped tables remain read-only. */\n @Prop() interactionMode: 'table' | 'edit' | 'grid' = 'table';\n /** Controlled rectangular cell selection, independent of row checkbox selection. */\n @Prop() cellRange: TableCellRange | null = null;\n @Event() dsCellRangeChange!: EventEmitter<TableCellRange>;\n @Event() dsCellsChange!: EventEmitter<TableCellsChangeDetail>;\n /** Additional toggle options in the column customizer. */\n @Prop() customizeOptions: MenuItemData[] = [];\n @Event() dsCustomizeOptionChange!: EventEmitter<string>;\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: DataGroup[] = [];\n /** Controlled grouping column. Applications supply groups in their final fixed order. */\n @Prop() grouping: DataGroupingState | null = null;\n /** Controlled member-row sort state. */\n @Prop() sort: DataSortState | null = null;\n /** Controlled collapsed group identities. Groups not listed remain expanded. */\n @Prop() collapsedGroupIds: string[] = [];\n /** Literal terms to highlight in table-owned text cells. Applications still own filtering. */\n @Prop() highlightTerms: string[] = [];\n /** Optional DataSearch field identities that restrict which data-point tracks are highlighted. */\n @Prop() highlightFieldIds: 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 /** Replace only the data frame with application-owned content; caption and footer remain shared. */\n @Prop() alternateView: boolean = false;\n /** Fires after a table/alternate-view switch has patched the frame and footer. */\n @Event() dsAlternateViewRendered!: EventEmitter<{ alternateView: boolean }>;\n /**\n * Opt in to the table-owned column customizer. The `columns` prop remains the\n * catalog; hidden and ordered columns are controlled separately. The trigger\n * opens the shared Menu of live show/hide switch rows.\n */\n @Prop() columnCustomizer: boolean = false;\n /** Hide only the built-in Customize trigger when an external preferences surface owns it. */\n /** Remove borders from table-owned caption controls. */\n @Prop() captionControlsBorderless: boolean = false;\n @Prop() hideColumnCustomizerTrigger: boolean = false;\n /** Controlled hidden data-column identities. Action ids are ignored. */\n @Prop() hiddenFieldIds: string[] = [];\n /** Controlled data-column identities in display order. Omitted ids append in catalog order. */\n @Prop() fieldOrder: string[] = [];\n /** Controlled user-pinned data-column identities. Hidden columns retain their pin state. */\n @Prop() pinnedFieldIds: string[] = [];\n /**\n * Optional result summary footer. When both `displayedCount` and `totalCount`\n * are finite numbers, infinite mode shows “Displaying {displayed} of {total}”.\n * Virtual mode ignores `displayedCount` and derives its total from the\n * complete supplied rows; a mismatched `totalCount` emits a warning.\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 /** Supports the {total} placeholder. Used when dataMode is virtual. */\n @Prop() resultTotalSummaryLabel: string = TABLE_VIRTUAL_TOTAL_SUMMARY_LABEL;\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 /**\n * Identity of the interactive row whose associated surface is visible.\n * Holds its pressed wash independently of checkbox selection. Keep the ID\n * through exit motion and clear it after close; the application owns toggling\n * and switching the surface. Off-window identities survive virtualization.\n */\n @Prop() surfaceOpenRowId: string | undefined;\n\n /** Initial loading state. Existing rows stay visible; incremental loading uses loadingMore. */\n @Prop() loading: boolean = false;\n /** Replace column headers and table-owned caption controls with same-size visual skeletons. */\n @Prop() chromeLoading: 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. Virtual mode recycles row DOM only. */\n @Prop() dataMode: TableDataMode = 'infinite';\n /** Opt in to the table-owned control for choosing between supported data modes. */\n @Prop() dataModeSwitcher: boolean = false;\n @Prop() dataModeSwitcherLabel: string = 'Change table variation';\n @Prop() dataModeMenuLabel: string = 'Table variation';\n @Prop() infiniteModeLabel: string = 'Infinite scroll';\n @Prop() paginationModeLabel: string = 'Pagination + Infinite groups';\n @Prop() virtualModeLabel: string = 'Virtual scroll';\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 =\n '{count} more rows loaded in {group}. {loaded} of {total} rows loaded.';\n\n @Event() dsSortChange!: EventEmitter<DataSortChangeDetail>;\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 @Event() dsFieldsConfigChange!: EventEmitter<DataFieldsConfigChangeDetail>;\n @Event() dsDataModeChange!: EventEmitter<TableDataModeChangeDetail>;\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 headerUsesToolbar = false;\n @State() private captionTrailingPresent = false;\n @State() private footerSlotPresence = 0;\n @State() private fitPageSize: number | undefined;\n @State() private actionMenu: { rowId: string; columnId: string } | null = null;\n @State() private actionMenuInitialFocusVisible = false;\n @State() private actionMenuSurface: { rowId: string; columnId: string } | null = null;\n @State() private truncateTooltipLabel = '';\n @State() private captionCompact = false;\n @State() private narrowViewport = false;\n @State() private captionMenus = createTableCaptionState();\n @State() private virtualWindow: TableVirtualPlan | null = null;\n @State() private cellInteraction: TableEditingState = { activeCell: null, editingCell: null };\n\n private readonly actionMenuElementId = nextTableActionMenuElementId();\n private structureCache?: {\n columns: TableColumn[];\n rows: TableRow[];\n spans: TableCellSpan[];\n selectable: boolean;\n grouped: boolean;\n windowed: boolean;\n grid: boolean;\n pins: ReturnType<typeof tablePinnedColumns>;\n bands: ReturnType<typeof tableHeaderBands>;\n merges: ReturnType<typeof tableSpanModel>;\n };\n\n private get structure() {\n const columns = this.visibleColumns;\n const model = this.createRenderModel();\n const cached = this.structureCache;\n const windowed = this.rowWindowingEnabled;\n const grid = this.interactionMode !== 'table';\n if (\n cached &&\n cached.columns === columns &&\n cached.rows === this.rows &&\n cached.spans === this.cellSpans &&\n cached.selectable === model.selectable &&\n cached.grouped === model.grouped &&\n cached.windowed === windowed &&\n cached.grid === grid\n )\n return cached;\n return (this.structureCache = {\n columns,\n rows: this.rows,\n spans: this.cellSpans,\n selectable: model.selectable,\n grouped: model.grouped,\n windowed,\n grid,\n pins: tablePinnedColumns(columns, model.selectable),\n bands: tableHeaderBands(columns, model.selectable, model.elasticSpacerIndex),\n merges: tableSpanModel(this.cellSpans, this.rows, columns, {\n windowed,\n grouped: model.grouped,\n grid,\n selectable: model.selectable,\n spacer: model.elasticSpacerIndex,\n }),\n });\n }\n\n private headerId(columnId: string): string {\n return `${this.actionMenuElementId}-header-${encodeURIComponent(columnId)}`;\n }\n\n private columnBandId(columnId: string): string | undefined {\n const model = this.createRenderModel();\n const index = this.visibleColumns.findIndex(column => column.id === columnId);\n const lane =\n index +\n 1 +\n Number(model.selectable) +\n Number(model.elasticSpacerIndex != null && index >= model.elasticSpacerIndex);\n const band = this.structure.bands.find(\n band => band.label && lane >= band.start && lane < band.start + band.span\n );\n return band ? `${this.actionMenuElementId}-${band.key}` : undefined;\n }\n private truncateTooltipEl?: HTMLDsTooltipElement;\n private truncateAnchor: HTMLElement | null = null;\n private truncateTooltipBound = false;\n private focusedRowId: string | null = null;\n private virtualRenderFocus: HTMLElement | null = null;\n private renderedAlternateView: boolean | null = null;\n private virtualItems: TableVirtualItem[] = [];\n private visibleColumnsCache: {\n columns: TableColumn[];\n columnCustomizer: boolean;\n hiddenFieldIds: string[];\n fieldOrder: string[];\n pinnedFieldIds: string[];\n value: TableColumn[];\n } | null = null;\n private renderModelCache: {\n columns: TableColumn[];\n rows: TableRow[];\n groups: DataGroup[];\n grouped: boolean;\n selectionMode: TableSelectionMode;\n selectedRowIds: string[];\n collapsedGroupIds: string[];\n virtualCounts: boolean;\n value: TableRenderModel;\n } | null = null;\n private highlightMatcherCache: {\n terms: string[];\n value: TableHighlightMatcher;\n } | null = null;\n private virtualItemsCache: {\n columns: TableColumn[];\n rows: TableRow[];\n groups: DataGroup[];\n grouped: boolean;\n collapsedGroupIds: string[];\n } | null = null;\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 modelWarningQueued = false;\n private initialModelWarningFrame: number | undefined;\n private hasLoaded = false;\n private renderedModel: TableRenderModel | null = null;\n private stickyGroupConnected = false;\n private headerSlotObserver: MutationObserver | null = null;\n private footerSlotObserver: MutationObserver | null = null;\n private captionCompactDisconnect: (() => void) | undefined;\n private fitResizeObserver: ResizeObserver | null = null;\n private fitObservedViewport: HTMLElement | null = null;\n private fitObservedTable: HTMLTableElement | null = null;\n private fitMeasurementPending = false;\n private cardViewportQuery: MediaQueryList | null = null;\n private readonly editingController = createTableEditingController({\n host: () => this.el,\n state: () => ({\n rows: this.rows,\n columns: this.visibleColumns,\n mode: this.grouped ? 'table' : this.interactionMode,\n loading: this.loading,\n cellRange: this.cellRange,\n }),\n interaction: () => this.cellInteraction,\n change: patch => {\n this.cellInteraction = { ...this.cellInteraction, ...patch };\n },\n rangeChange: range => {\n this.dsCellRangeChange.emit(range);\n },\n cellsChange: detail => {\n this.dsCellsChange.emit(detail);\n },\n announce: message => {\n this.announcement = message;\n },\n focusRow: rowId => {\n this.focusedRowId = rowId;\n },\n revealRow: rowId => this.scrollRowIntoView(rowId),\n findRow: rowId => this.findRenderedRow(rowId),\n });\n private readonly captionController = createTableCaptionController({\n host: () => this.el,\n state: () => ({\n caption: this.caption,\n captionVisibility: this.captionVisibility,\n headerPresent: this.headerPresent,\n headerUsesToolbar: this.headerUsesToolbar,\n captionTrailingPresent: this.captionTrailingPresent,\n captionCompact: this.captionCompact,\n columnCustomizer: this.columnCustomizer,\n hideColumnCustomizerTrigger: this.hideColumnCustomizerTrigger,\n captionControlsBorderless: this.captionControlsBorderless,\n chromeLoading: this.chromeLoading,\n columns: this.columns,\n hiddenFieldIds: this.hiddenFieldIds,\n fieldOrder: this.fieldOrder,\n pinnedFieldIds: this.pinnedFieldIds,\n customizeOptions: this.customizeOptions,\n dataModeSwitcher: this.dataModeSwitcher,\n dataModeSwitcherLabel: this.dataModeSwitcherLabel,\n dataModeMenuLabel: this.dataModeMenuLabel,\n dataMode: this.dataMode,\n infiniteModeLabel: this.infiniteModeLabel,\n paginationModeLabel: this.paginationModeLabel,\n virtualModeLabel: this.virtualModeLabel,\n }),\n menus: () => this.captionMenus,\n change: patch => {\n this.captionMenus = { ...this.captionMenus, ...patch };\n },\n headerSlotChange: () => this.syncHeaderSlotPresence(),\n fieldsChange: detail => {\n this.dsFieldsConfigChange.emit(detail);\n },\n dataModeChange: detail => {\n this.dsDataModeChange.emit(detail);\n },\n customizeOptionChange: value => {\n this.dsCustomizeOptionChange.emit(value);\n },\n });\n private readonly bodyRenderer = new TableBodyRenderer();\n private readonly layoutController = new TableLayoutController({\n elements: () => ({\n viewport: this.viewportEl,\n contentTable: this.tableEl,\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 && this.dataMode !== 'virtual',\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 virtualController = new TableVirtualController({\n state: () => ({\n enabled: this.rowWindowingEnabled,\n items: this.virtualItems,\n pinnedRowIds: this.virtualPinnedRowIds(),\n viewport: this.viewportEl,\n viewportSize: this.estimateVirtualViewportSize(),\n }),\n windowChanged: plan => {\n this.virtualWindow = plan;\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.connectCardViewportQuery();\n // Reserve the final viewport-fit height before the first paint. The fitted\n // surface is connected after render, but the host can already resolve its\n // owning scrollport and prevent an initial auto-height frame.\n this.viewportFitController.connect();\n this.incrementalWindowActive = this.hasIncrementalState;\n this.syncHeaderSlotPresence();\n this.syncFooterSlotPresence();\n this.loadController.initialize();\n this.groupLoadController.initialize();\n }\n\n componentDidLoad(): void {\n this.hasLoaded = true;\n this.layoutController.connect();\n this.loadController.connect();\n this.groupLoadController.connect();\n this.virtualController.connect();\n this.viewportFitController.connect();\n this.syncStickyGroupConnection();\n this.connectHeaderSlotObserver();\n this.connectFooterSlotObserver();\n this.connectFitObserver();\n this.connectCaptionCompactObserver();\n this.syncFitPageSize();\n this.connectTruncateTooltip();\n this.scheduleInitialModelIssueWarning();\n }\n\n componentWillRender(): void {\n const root = this.el.getRootNode() as Document | ShadowRoot;\n const active = root.activeElement;\n this.virtualRenderFocus =\n this.rowWindowingEnabled &&\n active instanceof HTMLElement &&\n this.tableEl?.contains(active) &&\n active.closest('[data-row-id]')\n ? active\n : null;\n }\n\n componentDidRender(): void {\n // Moving a retained pooled row can clear native focus in Firefox. Restore\n // only the same connected node, without scrolling or stealing external focus.\n const retainedFocus = this.virtualRenderFocus;\n this.virtualRenderFocus = null;\n if (retainedFocus?.isConnected) {\n const root = retainedFocus.getRootNode() as Document | ShadowRoot;\n if (!root.activeElement || root.activeElement === this.el.ownerDocument.body) {\n // Resolve the moved rows' scroll geometry before restoring native focus.\n // WebKit otherwise may reveal the caret using the pre-patch layout even\n // with preventScroll. Preserve both axes within the resulting bounds.\n const viewport = this.viewportEl;\n const top = viewport?.scrollTop ?? 0;\n const left = viewport?.scrollLeft ?? 0;\n retainedFocus.focus({ preventScroll: true });\n if (viewport) {\n if (viewport.scrollTop !== top) viewport.scrollTop = top;\n if (viewport.scrollLeft !== left) viewport.scrollLeft = left;\n }\n }\n }\n this.layoutController.refresh(false);\n this.loadController.refresh();\n this.groupLoadController.refresh();\n this.virtualController.refresh();\n this.virtualController.collectMeasurements(this.tableEl);\n if (\n this.rowWindowingEnabled &&\n this.truncateAnchor &&\n this.tableEl &&\n !this.tableEl.contains(this.truncateAnchor)\n ) {\n this.dismissTruncateTooltip();\n }\n this.viewportFitController.refresh(false);\n this.syncStickyGroupConnection();\n this.connectFitObserver();\n this.connectCaptionCompactObserver();\n this.syncFitPageSize();\n if (this.stickyGroupConnected) this.updateStickyGroup();\n if (this.renderedAlternateView !== this.alternateView) {\n this.renderedAlternateView = this.alternateView;\n this.dsAlternateViewRendered.emit({ alternateView: this.alternateView });\n }\n }\n\n /**\n * Scroll the nearest vertical owner only enough to reveal a supplied row.\n * Resolves false when the row is absent from the supplied, expanded table model.\n * This method never moves focus.\n */\n @Method()\n async scrollRowIntoView(rowId: string): Promise<boolean> {\n if (!rowId) return false;\n\n if (this.rowWindowingEnabled) {\n const headerSize = this.tableEl?.tHead?.getBoundingClientRect().height ?? 0;\n const revealed = this.virtualController.scrollRowIntoView(\n rowId,\n headerSize,\n this.stickyHeader ? headerSize : 0\n );\n if (!revealed) return false;\n await this.nextAnimationFrame();\n }\n\n const row = this.findRenderedRow(rowId);\n if (!row) return false;\n this.revealRenderedRow(row);\n return true;\n }\n\n connectedCallback(): void {\n if (!this.hasLoaded) return;\n this.connectCardViewportQuery();\n this.layoutController.connect();\n this.loadController.connect();\n this.groupLoadController.connect();\n this.virtualController.connect();\n this.viewportFitController.connect();\n this.syncStickyGroupConnection();\n this.connectHeaderSlotObserver();\n this.connectFooterSlotObserver();\n this.connectFitObserver();\n this.connectCaptionCompactObserver();\n this.connectTruncateTooltip();\n }\n\n disconnectedCallback(): void {\n this.editingController.disconnect();\n this.captionController.disconnect();\n this.layoutController.disconnect();\n this.loadController.disconnect();\n this.groupLoadController.disconnect();\n this.virtualController.disconnect();\n this.viewportFitController.disconnect();\n this.disconnectStickyGroup();\n this.headerSlotObserver?.disconnect();\n this.headerSlotObserver = null;\n this.footerSlotObserver?.disconnect();\n this.footerSlotObserver = null;\n this.disconnectFitObserver();\n this.disconnectCaptionCompactObserver();\n this.disconnectTruncateTooltip();\n this.disconnectCardViewportQuery();\n if (this.initialModelWarningFrame !== undefined) {\n cancelAnimationFrame(this.initialModelWarningFrame);\n this.initialModelWarningFrame = undefined;\n }\n }\n\n private readonly handleCardViewportChange = (event: MediaQueryListEvent): void => {\n if (this.narrowViewport !== event.matches) this.narrowViewport = event.matches;\n };\n\n private connectCardViewportQuery(): void {\n if (\n this.cardViewportQuery ||\n typeof window === 'undefined' ||\n typeof window.matchMedia !== 'function'\n )\n return;\n const query = window.matchMedia(`(max-width: ${TABLE_CARD_VIEWPORT_BREAKPOINT - 1}px)`);\n this.cardViewportQuery = query;\n const narrow = isTableCardViewport(window.innerWidth);\n if (this.narrowViewport !== narrow) this.narrowViewport = narrow;\n query.addEventListener('change', this.handleCardViewportChange);\n }\n\n private disconnectCardViewportQuery(): void {\n this.cardViewportQuery?.removeEventListener('change', this.handleCardViewportChange);\n this.cardViewportQuery = null;\n }\n\n private syncHeaderSlotPresence = () => {\n const header = this.el.querySelector<HTMLElement>('[slot=\"header\"]');\n this.headerPresent = !!header;\n this.headerUsesToolbar = header?.tagName === 'DS-DATA-TOOLBAR';\n this.captionTrailingPresent = !!this.el.querySelector('[slot=\"caption-trailing\"]');\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 syncFooterSlotPresence = () => {\n let presence = 0;\n if (hasOwnedTableFooterSlot(this.el, 'footer-leading')) presence |= TABLE_FOOTER_SLOT_LEADING;\n if (hasOwnedTableFooterSlot(this.el, 'footer')) presence |= TABLE_FOOTER_SLOT_COPY;\n if (hasOwnedTableFooterSlot(this.el, 'footer-trailing')) presence |= TABLE_FOOTER_SLOT_TRAILING;\n if (presence !== this.footerSlotPresence) this.footerSlotPresence = presence;\n };\n\n private connectFooterSlotObserver(): void {\n if (this.footerSlotObserver || typeof MutationObserver === 'undefined') return;\n this.footerSlotObserver = new MutationObserver(this.syncFooterSlotPresence);\n this.footerSlotObserver.observe(this.el, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['slot'],\n });\n this.syncFooterSlotPresence();\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 @Watch('hiddenFieldIds')\n @Watch('fieldOrder')\n @Watch('pinnedFieldIds')\n @Watch('columnCustomizer')\n @Watch('cellSpans')\n @Watch('interactionMode')\n @Watch('responsiveLayout')\n handleStructureChange(): void {\n this.virtualItemsCache = null;\n this.scheduleModelIssueWarning();\n this.loadController.structureChanged();\n this.groupLoadController.structureChanged();\n this.virtualController.invalidateMeasures();\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.scheduleModelIssueWarning();\n this.syncActionMenu();\n this.virtualController.invalidateMeasures();\n }\n\n @Watch('totalCount')\n handleTotalCountChange(): void {\n this.scheduleModelIssueWarning();\n }\n\n @Watch('caption')\n handleCaptionChange(): void {\n this.scheduleModelIssueWarning();\n }\n\n @Watch('loadIdentity')\n handleLoadIdentityChange(): void {\n this.incrementalWindowActive = this.hasIncrementalState;\n this.loadController.identityChanged();\n this.virtualController.resetToTop();\n }\n\n @Watch('sort')\n @Watch('grouping')\n handleVirtualIndexReset(): void {\n this.virtualController.resetToTop();\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.scheduleModelIssueWarning();\n this.handleLazyConfigurationChange();\n this.virtualController.resetToTop();\n }\n\n @Watch('pagination')\n handlePaginationChange(\n pagination: TablePaginationState | null,\n previous: TablePaginationState | null\n ): void {\n if (pagination?.pageSizeMode === 'fit' && pagination.fitIdentity !== previous?.fitIdentity) {\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 @Watch('columnCustomizer')\n @Watch('captionVisibility')\n @Watch('dataModeSwitcher')\n @Watch('chromeLoading')\n handleCaptionAvailability(): void {\n this.captionController.reconcile();\n }\n\n private get grouped(): boolean {\n return this.grouping !== null;\n }\n\n private get hasIncrementalState(): boolean {\n return (\n this.dataMode === 'infinite' &&\n (this.hasMore || this.loadingMore || !!this.loadMoreError?.trim())\n );\n }\n\n private get selectable(): boolean {\n return this.selectionMode === 'multiple';\n }\n\n private get visibleColumns(): TableColumn[] {\n const cached = this.visibleColumnsCache;\n if (\n cached &&\n cached.columns === this.columns &&\n cached.columnCustomizer === this.columnCustomizer &&\n cached.hiddenFieldIds === this.hiddenFieldIds &&\n cached.fieldOrder === this.fieldOrder &&\n cached.pinnedFieldIds === this.pinnedFieldIds\n ) {\n return cached.value;\n }\n const value = resolveTableVisibleColumns(this.columns, {\n columnCustomizer: this.columnCustomizer,\n hiddenFieldIds: this.hiddenFieldIds,\n fieldOrder: this.fieldOrder,\n pinnedFieldIds: this.pinnedFieldIds,\n });\n this.visibleColumnsCache = {\n columns: this.columns,\n columnCustomizer: this.columnCustomizer,\n hiddenFieldIds: this.hiddenFieldIds,\n fieldOrder: this.fieldOrder,\n pinnedFieldIds: this.pinnedFieldIds,\n value,\n };\n return value;\n }\n\n private get documentStickyHeader(): boolean {\n return this.stickyHeader && !this.containedScroll && this.responsiveLayout !== 'cards';\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 connectCaptionCompactObserver(): void {\n if (this.captionCompactDisconnect) return;\n this.captionCompactDisconnect = observeTableCaptionCompact(this.el, compact => {\n if (this.captionCompact !== compact) this.captionCompact = compact;\n });\n }\n\n private disconnectCaptionCompactObserver(): void {\n this.captionCompactDisconnect?.();\n this.captionCompactDisconnect = undefined;\n }\n\n private readonly syncFitPageSize = (): void => {\n const pagination = this.dataMode === 'pagination' ? this.pagination : null;\n if (\n !pagination?.fitToPage ||\n pagination.fitToPageInactive ||\n !this.containedScroll ||\n !this.viewportEl ||\n !this.tableEl\n ) {\n if (this.fitPageSize !== undefined) this.fitPageSize = undefined;\n this.fitMeasurementPending = false;\n return;\n }\n const header = this.tableEl.querySelector<HTMLElement>('.ds-table__head');\n const item = this.tableEl.querySelector<HTMLElement>(\n this.grouped ? '.ds-table__group-row' : '.ds-table__body .ds-table__row'\n );\n const itemBlockSize =\n item?.getBoundingClientRect().height ||\n 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 fitToPageInactive: false,\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 showFirstLastButtons: pagination.showFirstLastButtons ?? false,\n previousPageIndex: pagination.pageIndex,\n previousPageSize: pagination.pageSize,\n previousPageSizeMode: 'fit',\n reason: 'fit',\n });\n };\n\n @Listen('keydown', { target: 'window' })\n handleWindowPaginationKeyDown(event: KeyboardEvent): void {\n this.handlePaginationKeyDown(event, true);\n }\n\n private handlePaginationKeyDown(event: KeyboardEvent, fromWindow = false): 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 if (paginationShortcutBlockedByPath(event.composedPath())) return;\n\n if (fromWindow) {\n const origin = event.composedPath()[0];\n const originNode = origin instanceof Text ? origin.parentNode : origin;\n if (\n !shouldHandleContainingPagePaginationShortcut({\n origin: originNode,\n table: this.el,\n eventPath: event.composedPath(),\n })\n ) {\n return;\n }\n }\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 fitToPageInactive: pagination.fitToPageInactive ?? 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 showFirstLastButtons: pagination.showFirstLastButtons ?? false,\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 const columns = this.visibleColumns;\n const grouped = this.grouped;\n const virtualCounts = this.dataMode === 'virtual';\n const cached = this.renderModelCache;\n if (\n cached &&\n cached.columns === columns &&\n cached.rows === this.rows &&\n cached.groups === this.groups &&\n cached.grouped === grouped &&\n cached.selectionMode === this.selectionMode &&\n cached.selectedRowIds === this.selectedRowIds &&\n cached.collapsedGroupIds === this.collapsedGroupIds &&\n cached.virtualCounts === virtualCounts\n ) {\n return cached.value;\n }\n const value = createTableRenderModel({\n columns,\n rows: this.rows,\n groups: this.groups,\n grouped,\n selectionMode: this.selectionMode,\n selectedRowIds: this.selectedRowIds,\n collapsedGroupIds: this.collapsedGroupIds,\n groupCountPresentation: virtualCounts ? 'total' : 'loaded-progress',\n });\n this.renderModelCache = {\n columns,\n rows: this.rows,\n groups: this.groups,\n grouped,\n selectionMode: this.selectionMode,\n selectedRowIds: this.selectedRowIds,\n collapsedGroupIds: this.collapsedGroupIds,\n virtualCounts,\n value,\n };\n return value;\n }\n\n private get highlightMatcher(): TableHighlightMatcher {\n if (this.highlightMatcherCache?.terms === this.highlightTerms) {\n return this.highlightMatcherCache.value;\n }\n const value = createTableHighlightMatcher(this.highlightTerms);\n this.highlightMatcherCache = { terms: this.highlightTerms, value };\n return value;\n }\n\n private get rowWindowingEnabled(): boolean {\n if (!this.hasVirtualViewportBounds) return false;\n if (this.dataMode === 'virtual') return true;\n return !this.grouped && this.currentLoadedRowCount() > TABLE_NON_VIRTUAL_WINDOW_THRESHOLD;\n }\n\n private get virtualViewportMissing(): boolean {\n return this.dataMode === 'virtual' && !this.hasVirtualViewportBounds;\n }\n\n private get hasVirtualViewportBounds(): boolean {\n return this.containedScroll && this.estimateVirtualViewportSize() > 0;\n }\n\n private estimateVirtualViewportSize(): number {\n const measured =\n this.virtualController.currentViewportSize() || this.viewportEl?.clientHeight || 0;\n const chrome =\n (this.captionVisibility === 'visible' ? 48 : 0) + (this.hasResultFooter ? 48 : 0);\n if (this.fixedHeight && this.resolvedHeight) {\n if (measured > 0) return measured;\n return Math.max(\n 0,\n resolveCssLengthPx(this.resolvedHeight, 0, this.rootEl ?? this.el) - chrome\n );\n }\n if (this.fitViewport) return Math.max(0, measured);\n if (this.resolvedMaxHeight) {\n if (this.resolvedMaxHeight.trim().toLowerCase() === 'none') return 0;\n return resolveCssLengthPx(this.resolvedMaxHeight, 0, this.rootEl ?? this.el);\n }\n return 0;\n }\n\n private virtualPinnedRowIds(): Set<string> {\n const pinned = new Set<string>();\n if (this.focusedRowId) pinned.add(this.focusedRowId);\n if (this.actionMenu?.rowId) pinned.add(this.actionMenu.rowId);\n return pinned;\n }\n\n private findRenderedRow(rowId: string): HTMLElement | null {\n return (\n [...this.el.querySelectorAll<HTMLElement>('[data-row-id]')].find(\n candidate => candidate.dataset.rowId === rowId\n ) ?? null\n );\n }\n\n private revealRenderedRow(row: HTMLElement): void {\n if (!this.containedScroll || !this.viewportEl) {\n row.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n return;\n }\n\n const viewportRect = this.viewportEl.getBoundingClientRect();\n const rowRect = row.getBoundingClientRect();\n let visibleStart = viewportRect.top;\n if (this.stickyHeader) {\n visibleStart = Math.max(\n visibleStart,\n this.tableEl?.tHead?.getBoundingClientRect().bottom ?? visibleStart\n );\n }\n const groupRow = row.closest('tbody')?.querySelector<HTMLElement>('.ds-table__group-row');\n if (groupRow && getComputedStyle(groupRow).position === 'sticky') {\n visibleStart = Math.max(visibleStart, groupRow.getBoundingClientRect().bottom);\n }\n\n if (rowRect.top < visibleStart) {\n this.viewportEl.scrollTop += rowRect.top - visibleStart;\n } else if (rowRect.bottom > viewportRect.bottom) {\n this.viewportEl.scrollTop += rowRect.bottom - viewportRect.bottom;\n }\n }\n\n private nextAnimationFrame(): Promise<void> {\n return new Promise(resolve => requestAnimationFrame(() => resolve()));\n }\n\n private syncVirtualItems(model: TableRenderModel): void {\n if (!this.rowWindowingEnabled) {\n this.virtualItems = [];\n this.virtualItemsCache = null;\n this.bodyRenderer.resetVirtualState();\n return;\n }\n const columns = this.visibleColumns;\n const cached = this.virtualItemsCache;\n if (\n cached &&\n cached.columns === columns &&\n cached.rows === this.rows &&\n cached.groups === this.groups &&\n cached.grouped === model.grouped &&\n cached.collapsedGroupIds === this.collapsedGroupIds\n ) {\n return;\n }\n this.virtualItems = flattenTableVirtualItems({\n grouped: model.grouped,\n rows: this.rows,\n groups: this.groups,\n collapsedGroupIds: this.collapsedGroupIds,\n columns,\n });\n if (this.responsiveLayout === 'cards') {\n this.virtualItems = this.virtualItems.map(item => ({ ...item, variableSize: true }));\n }\n this.virtualItemsCache = {\n columns,\n rows: this.rows,\n groups: this.groups,\n grouped: model.grouped,\n collapsedGroupIds: this.collapsedGroupIds,\n };\n }\n\n private shouldWindowRows(model: TableRenderModel): boolean {\n return (\n this.rowWindowingEnabled && (model.hasData || (model.grouped && model.groups.length > 0))\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 const visibleColumns = this.visibleColumns;\n if (!this.caption?.trim()) issues.unshift('A non-empty caption is required.');\n if (this.grouping) {\n const groupingColumn = this.columns.find(column => column.id === this.grouping!.fieldId);\n if (!groupingColumn) {\n issues.push(`Grouping references unknown column id: ${this.grouping.fieldId}`);\n } else if (!visibleColumns.some(column => column.id === groupingColumn.id)) {\n issues.push(`Grouping references hidden column id: ${this.grouping.fieldId}`);\n }\n }\n if (this.sort) {\n const sortColumn = this.columns.find(\n column =>\n column.id === this.sort!.fieldId ||\n column.segments?.some(segment => segment.sortKey === this.sort!.fieldId)\n );\n if (!sortColumn) {\n issues.push(`Sorting references unknown column id: ${this.sort.fieldId}`);\n } else if (!visibleColumns.some(column => column.id === sortColumn.id)) {\n issues.push(`Sorting references hidden column id: ${this.sort.fieldId}`);\n }\n }\n if (\n this.dataMode === 'virtual' &&\n Number.isFinite(this.totalCount) &&\n Math.max(0, Math.trunc(this.totalCount!)) !== this.currentLoadedRowCount()\n ) {\n issues.push('Virtual mode requires totalCount to match the complete supplied row count.');\n }\n const stickyStart = visibleColumns.filter(column => column.sticky === 'start');\n const stickyEnd = visibleColumns.filter(column => column.sticky === 'end');\n issues.push(...this.structure.merges.issues);\n if (this.interactionMode === 'grid' && this.grouped)\n issues.push('Grid interaction requires ungrouped rows.');\n for (const column of [...stickyStart, ...stickyEnd]) {\n if (!tableColumnSize(column))\n issues.push(`Sticky column ${column.id} requires an explicit size.`);\n }\n const message = issues.join(' ');\n if (!message) {\n this.previousModelWarning = '';\n return;\n }\n if (message === this.previousModelWarning) return;\n this.previousModelWarning = message;\n console.warn(`[ds-table] ${message}`);\n }\n\n private scheduleModelIssueWarning(): void {\n if (this.modelWarningQueued) return;\n this.modelWarningQueued = true;\n queueMicrotask(() => {\n this.modelWarningQueued = false;\n this.warnModelIssues();\n });\n }\n\n private scheduleInitialModelIssueWarning(): void {\n if (this.initialModelWarningFrame !== undefined) {\n cancelAnimationFrame(this.initialModelWarningFrame);\n }\n this.initialModelWarningFrame = requestAnimationFrame(() => {\n this.initialModelWarningFrame = undefined;\n this.warnModelIssues();\n });\n }\n\n private emitSort(column: TableColumn, sortKey = column.id): void {\n if (!column.sortable) return;\n this.dsSortChange.emit({ sort: nextDataSortState(this.sort, sortKey) });\n }\n\n private sortButtonLabel(column: TableColumn, sortKey = column.id, label = column.label): string {\n if (this.sort?.fieldId !== 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: DataGroup): 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\"], [popover], ds-button-unfilled, ds-menu'\n );\n });\n }\n\n private groupRowEventOwnsCollapse(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 'input, select, textarea, [role=\"checkbox\"], .ds-table__selection-control, .ds-table__group-selection, ds-tooltip'\n );\n });\n }\n\n private handleGroupRowClick(group: DataGroup, event: Event): void {\n if (!this.groupRowEventOwnsCollapse(event)) return;\n this.emitGroupCollapse(group);\n }\n\n private emitRowActivation(row: TableRow, event: Event): void {\n if (this.editingController.gridEnabled || row.disabled || !this.rowEventOwnsActivation(event))\n return;\n if (this.selectionMode === 'multiple' && this.selectedRowIds.length > 0) {\n if (row.selectable !== false) this.emitRowSelection(row);\n return;\n }\n if (row.interactive) this.dsRowActivate.emit({ rowId: row.id });\n }\n\n private syncActionMenu(): void {\n if (!this.actionMenu) return;\n const row = tableRows(this.rows, this.groups, this.grouped).find(\n candidate => candidate.id === this.actionMenu?.rowId\n );\n const value = row && !row.disabled ? row.cells[this.actionMenu.columnId] : undefined;\n if (!isTableCellAction(value) || !isRenderableTableActionMenu(value)) {\n this.actionMenu = null;\n }\n }\n\n private closeActionMenu(): void {\n this.actionMenu = null;\n }\n\n private handleActionMenuSelect(item: MenuItemData): void {\n const open = this.actionMenu;\n const actionId = item.value;\n if (!open || !actionId || item.isInactive) return;\n this.dsCellAction.emit({\n actionId,\n rowId: open.rowId,\n columnId: open.columnId,\n });\n const triggerId = tableActionTriggerId(this.actionMenuElementId, open.rowId, open.columnId);\n this.closeActionMenu();\n requestAnimationFrame(() => {\n const trigger = this.el.querySelector<HTMLElement & { setFocus?: () => void }>(\n `#${CSS.escape(triggerId)}`\n );\n trigger?.setFocus?.();\n });\n }\n\n private toggleActionMenu(row: TableRow, column: TableColumn, event: MouseEvent): void {\n if (row.disabled) return;\n const open = this.actionMenu?.rowId === row.id && this.actionMenu?.columnId === column.id;\n this.actionMenuInitialFocusVisible = event.detail === 0;\n this.actionMenu = open ? null : { rowId: row.id, columnId: column.id };\n if (this.actionMenu) this.actionMenuSurface = this.actionMenu;\n }\n\n private renderOverflowActionMenu() {\n const open = this.actionMenu ?? this.actionMenuSurface;\n const row = open\n ? tableRows(this.rows, this.groups, this.grouped).find(\n candidate => candidate.id === open.rowId\n )\n : undefined;\n const value = row && open ? row.cells[open.columnId] : undefined;\n const menu = isTableCellAction(value) && isRenderableTableActionMenu(value) ? value : undefined;\n const sections = menu ? tableActionMenuSections(menu.items) : [];\n const triggerId = open\n ? tableActionTriggerId(this.actionMenuElementId, open.rowId, open.columnId)\n : undefined;\n\n return (\n <ds-menu\n id={this.actionMenuElementId}\n open={Boolean(this.actionMenu && sections.length)}\n anchorId={triggerId}\n align=\"end\"\n side=\"bottom\"\n menuLabel={menu?.ariaLabel ?? 'More actions'}\n initialFocusVisible={this.actionMenuInitialFocusVisible}\n sections={sections}\n onDsClose={() => this.closeActionMenu()}\n onDsAfterClose={() => {\n if (!this.actionMenu) this.actionMenuSurface = null;\n }}\n onDsSelect={event => this.handleActionMenuSelect(event.detail)}\n />\n );\n }\n\n private connectTruncateTooltip(): void {\n if (this.truncateTooltipBound) return;\n this.el.addEventListener('pointerover', this.onTruncatePointerOver);\n this.el.addEventListener('pointerout', this.onTruncatePointerOut);\n this.el.addEventListener('focusin', this.onTruncateFocusIn);\n this.el.addEventListener('focusout', this.onTruncateFocusOut);\n this.truncateTooltipBound = true;\n }\n\n private disconnectTruncateTooltip(): void {\n if (!this.truncateTooltipBound) return;\n this.el.removeEventListener('pointerover', this.onTruncatePointerOver);\n this.el.removeEventListener('pointerout', this.onTruncatePointerOut);\n this.el.removeEventListener('focusin', this.onTruncateFocusIn);\n this.el.removeEventListener('focusout', this.onTruncateFocusOut);\n this.truncateTooltipBound = false;\n this.dismissTruncateTooltip();\n }\n\n private onTruncatePointerOver = (event: PointerEvent) => {\n if (event.pointerType === 'touch') return;\n const track = resolveTableTruncateTrack(event.target);\n if (!track) return;\n this.presentOrDismissTruncateTrack(track);\n };\n\n private onTruncatePointerOut = (event: PointerEvent) => {\n if (event.pointerType === 'touch') return;\n if (resolveTableTruncateTrack(event.relatedTarget)) return;\n const next = event.relatedTarget;\n const cell = this.truncateAnchor?.closest('.ds-table__cell');\n if (cell && next instanceof Node && cell.contains(next)) return;\n this.dismissTruncateTooltip();\n };\n\n private onTruncateFocusIn = (event: FocusEvent) => {\n const track = resolveTableTruncateTrack(event.target);\n if (!track) {\n this.dismissTruncateTooltip();\n return;\n }\n this.presentOrDismissTruncateTrack(track);\n };\n\n private onTruncateFocusOut = (event: FocusEvent) => {\n if (resolveTableTruncateTrack(event.relatedTarget)) return;\n this.dismissTruncateTooltip();\n };\n\n private presentOrDismissTruncateTrack(track: HTMLElement): void {\n if (!isElementTruncated(track)) {\n this.dismissTruncateTooltip();\n return;\n }\n const label = tableTruncateLabel(track);\n if (!label) {\n this.dismissTruncateTooltip();\n return;\n }\n this.truncateAnchor = track;\n this.truncateTooltipLabel = label;\n void this.truncateTooltipEl?.presentFrom(track, label);\n }\n\n private dismissTruncateTooltip(): void {\n this.truncateAnchor = null;\n if (!this.truncateTooltipLabel && !this.truncateTooltipEl) return;\n this.truncateTooltipLabel = '';\n void this.truncateTooltipEl?.dismiss();\n }\n\n private renderTruncateTooltip() {\n return (\n <ds-tooltip\n ref={element => {\n this.truncateTooltipEl = element;\n }}\n label={this.truncateTooltipLabel}\n describedBy={false}\n wrapLabel={true}\n size=\"sm\"\n delay={0}\n side=\"top\"\n />\n );\n }\n\n private handleRowKeydown(row: TableRow, event: KeyboardEvent): void {\n if (event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' '))\n return;\n event.preventDefault();\n this.emitRowActivation(row, event);\n }\n\n private onVirtualFocusIn = (event: FocusEvent): void => {\n const target = event.target;\n if (!(target instanceof HTMLElement)) return;\n const row = target.closest('[data-row-id]');\n const id = row?.getAttribute('data-row-id') ?? null;\n if (id === this.focusedRowId) return;\n this.focusedRowId = id;\n if (this.rowWindowingEnabled) this.virtualController.schedule();\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'], shadow = true) {\n if (!sticky) return null;\n return (\n <span\n class={{\n 'ds-table__sticky-edge': true,\n [`ds-table__sticky-edge--${sticky}`]: true,\n 'ds-table__sticky-edge--internal': !shadow,\n }}\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 headerLane?: { start: number; spanning: boolean }\n ) {\n interactive = interactive && !this.chromeLoading;\n const groupedColumn = this.grouping?.fieldId === column.id;\n const previousColumn = headerLane\n ? this.visibleColumns[this.visibleColumns.indexOf(column) - 1]\n : undefined;\n const segments = column.segments?.length\n ? column.segments\n : [{ label: column.label, sortKey: column.id }];\n const activeMemberSegment = segments.find(segment => segment.sortKey === this.sort?.fieldId);\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'\n ? 'ascending'\n : 'descending'\n : undefined;\n\n const help = column.help?.trim();\n const labels = (\n <span\n class=\"ds-table__header-labels\"\n tabIndex={interactive && help && !column.sortable ? 0 : undefined}\n data-header-help={help ? '' : undefined}\n >\n {segments.map((segment, index) => {\n const segmentActive = activeMemberSegment?.sortKey === segment.sortKey;\n const segmentInteractive = interactive && !!column.sortable;\n const text = (\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 decoration={help ? 'dotted-underline' : undefined}\n lineTruncation={1}\n >\n {segment.label}\n </ds-text>\n );\n const label =\n this.chromeLoading && segment.label ? (\n <span class=\"ds-table__loading-label\" aria-hidden=\"true\">\n {text}\n <ds-skeleton textVariant=\"text-caption\" width=\"100%\" />\n </span>\n ) : (\n 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(\n column,\n segment.sortKey,\n segment.dataLabel?.trim() || segment.label\n )}\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 < segments.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 labelControl =\n interactive && help ? (\n <ds-tooltip label={help} side=\"top\" size=\"sm\" wrapLabel={true}>\n {labels}\n </ds-tooltip>\n ) : (\n labels\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.label\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 =\n collapseHost?.mode === 'action' && collapseHost.columnId === column.id;\n const blankActionCollapseHost =\n actionCollapseHost && !column.label.trim() && !column.segments?.length;\n const collapseControl =\n interactive && actionCollapseHost ? (\n <span class=\"ds-table__collapse-slot\">{this.renderCollapseAllButton()}</span>\n ) : null;\n\n return (\n <th\n key={column.id}\n id={presentational ? undefined : this.headerId(column.id)}\n headers={presentational ? undefined : this.columnBandId(column.id)}\n rowSpan={headerLane?.spanning ? 2 : undefined}\n style={{\n ...this.structure.pins.get(column.id)?.style,\n ...(headerLane\n ? {\n gridColumn: String(headerLane.start),\n gridRow: headerLane.spanning ? '1 / span 2' : '2',\n }\n : {}),\n }}\n class={{\n 'ds-table__header-cell': true,\n 'ds-table__header-cell--rowspan': !!headerLane?.spanning,\n 'ds-table__header-cell--after-rowspan':\n !!headerLane &&\n !headerLane.spanning &&\n !!previousColumn &&\n !previousColumn.group &&\n !this.structure.pins.has(previousColumn.id),\n [`ds-table__cell--align-${align}`]: true,\n 'ds-table__cell--sticky-start':\n column.sticky === 'start' && this.structure.pins.has(column.id),\n 'ds-table__cell--sticky-end':\n column.sticky === 'end' && this.structure.pins.has(column.id),\n 'ds-table__header-cell--collapse-all': actionCollapseHost,\n }}\n scope={presentational ? undefined : 'col'}\n aria-label={\n !presentational && this.chromeLoading\n ? column.accessibleLabel?.trim() || segments.map(segment => segment.label).join(' / ')\n : undefined\n }\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.label.trim() && column.accessibleLabel?.trim() && (\n <span class=\"ds-visually-hidden\">{column.accessibleLabel}</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(\n this.structure.pins.has(column.id) ? column.sticky : undefined,\n !!this.structure.pins.get(column.id)?.edge\n )}\n </th>\n );\n }\n\n private renderColgroup(model: TableRenderModel) {\n const beforeSpacer =\n model.elasticSpacerIndex == null\n ? this.visibleColumns\n : this.visibleColumns.slice(0, model.elasticSpacerIndex);\n const afterSpacer =\n model.elasticSpacerIndex == null ? [] : this.visibleColumns.slice(model.elasticSpacerIndex);\n const renderColumn = (column: TableColumn) => {\n const width = tableColumnSize(column);\n return (\n <col\n key={column.id}\n class={{ 'ds-table__action-column': column.kind === 'action' }}\n style={width ? { width } : undefined}\n />\n );\n };\n return (\n <colgroup>\n {model.selectable && <col class=\"ds-table__selection-column\" />}\n {beforeSpacer.map(renderColumn)}\n {model.elasticSpacerIndex != null && <col class=\"ds-table__elastic-spacer-column\" />}\n {afterSpacer.map(renderColumn)}\n </colgroup>\n );\n }\n\n private renderHeader(\n model: TableRenderModel,\n interactive = true,\n presentational = false,\n ariaRowIndex?: number\n ) {\n const selection = model.selection;\n const bands = this.structure.bands;\n const multirow = bands.length > 0;\n const controls =\n interactive &&\n !(\n this.responsiveLayout === 'cards' &&\n this.narrowViewport &&\n this.interactionMode !== 'grid' &&\n !this.cellSpans.length\n );\n const selectionHeader = model.selectable && (\n <th\n class={{\n 'ds-table__header-cell': true,\n 'ds-table__selection-cell': true,\n 'ds-table__cell--sticky-start': true,\n 'ds-table__header-cell--rowspan': multirow,\n }}\n rowSpan={multirow ? 2 : undefined}\n style={multirow ? { gridColumn: '1', gridRow: '1 / span 2' } : undefined}\n scope={presentational ? undefined : 'col'}\n aria-label={this.chromeLoading && !presentational ? 'Select rows' : undefined}\n >\n {this.chromeLoading ? (\n <span class=\"ds-table__skeleton-checkbox-canvas\" aria-hidden=\"true\">\n <ds-skeleton\n class=\"ds-table__skeleton-checkbox\"\n variant=\"control\"\n width=\"var(--dimension-iconography-sm)\"\n />\n </span>\n ) : controls ? (\n 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 ) : (\n <span class=\"ds-visually-hidden\">Select rows</span>\n )}\n {this.renderStickyEdge(\n 'start',\n ![...this.structure.pins.values()].some(pin => pin.edge === 'start')\n )}\n </th>\n );\n const lanes: (TableColumn | undefined)[] = [...this.visibleColumns];\n if (model.elasticSpacerIndex != null) lanes.splice(model.elasticSpacerIndex, 0, undefined);\n const renderLane = (column: TableColumn | undefined, index: number) => {\n const start = index + 1 + Number(model.selectable);\n if (column)\n return this.renderColumnHeader(\n column,\n model,\n controls,\n presentational,\n multirow ? { start, spanning: !column.group } : undefined\n );\n return (\n <th\n class=\"ds-table__header-cell ds-table__elastic-spacer-cell\"\n rowSpan={multirow ? 2 : undefined}\n style={multirow ? { gridColumn: String(start), gridRow: '1 / span 2' } : undefined}\n aria-hidden=\"true\"\n role=\"presentation\"\n data-elastic-spacer=\"true\"\n />\n );\n };\n return (\n <thead\n class={{\n 'ds-table__head': true,\n 'ds-table__head--multirow': multirow,\n 'ds-table__head--semantic-copy': !interactive,\n }}\n ref={element => {\n if (interactive) this.interactiveHeadEl = element ?? null;\n }}\n >\n {multirow && (\n <tr class=\"ds-table__header-row ds-table__super-header-row\" aria-rowindex={ariaRowIndex}>\n {selectionHeader}\n {bands.map(band =>\n !band.label ? (\n lanes.map((column, index) => {\n const start = index + 1 + Number(model.selectable);\n return start >= band.start && start < band.start + band.span\n ? renderLane(column, index)\n : null;\n })\n ) : (\n <th\n key={band.key}\n id={presentational ? undefined : `${this.actionMenuElementId}-${band.key}`}\n class={{\n 'ds-table__header-cell': true,\n 'ds-table__super-header-cell': true,\n 'ds-table__cell--sticky-start': band.sticky === 'start',\n 'ds-table__cell--sticky-end': band.sticky === 'end',\n }}\n scope={presentational || !band.label ? undefined : 'colgroup'}\n aria-label={!presentational && this.chromeLoading ? band.label : undefined}\n colSpan={band.span}\n style={{\n gridColumn: `${band.start} / span ${band.span}`,\n gridRow: '1',\n ...this.structure.pins.get(\n (band.sticky === 'end' ? band.lastColumnId : band.firstColumnId) ?? ''\n )?.style,\n }}\n aria-hidden={!band.label ? 'true' : undefined}\n >\n <span class=\"ds-table__header-content\">\n <span class=\"ds-table__header-labels\">\n <span class=\"ds-table__header-segment\">\n <span class=\"ds-table__header-label ds-table__header-static\">\n <ds-skeleton\n preserveLayout={true}\n isLoading={this.chromeLoading}\n textVariant=\"text-caption\"\n >\n <ds-text\n class=\"ds-table__header-label-box ds-control-label-box\"\n as=\"span\"\n variant=\"text-caption\"\n color=\"inherit\"\n lineTruncation={1}\n >\n {band.label}\n </ds-text>\n </ds-skeleton>\n </span>\n </span>\n </span>\n </span>\n {this.renderStickyEdge(\n band.sticky,\n !!this.structure.pins.get(\n (band.sticky === 'end' ? band.firstColumnId : band.lastColumnId) ?? ''\n )?.edge\n )}\n </th>\n )\n )}\n </tr>\n )}\n <tr\n class=\"ds-table__header-row\"\n aria-rowindex={\n ariaRowIndex == null\n ? undefined\n : ariaRowIndex + Number(this.structure.bands.length > 0)\n }\n >\n {!multirow && selectionHeader}\n {lanes.map((column, index) => (!multirow || column?.group) && renderLane(column, index))}\n </tr>\n </thead>\n );\n }\n\n private renderRow(\n row: TableRow,\n model: TableRenderModel,\n ariaRowIndex?: number,\n variableVirtualSize = false,\n rowKey = row.id\n ) {\n return renderTableRowView({\n grid: this.editingController.gridEnabled,\n cellAttributes: this.editingController.enabled\n ? (targetRow, column) => this.editingController.cellAttributes(targetRow, column)\n : undefined,\n renderEditor: this.editingController.enabled\n ? (targetRow, column) => this.editingController.renderEditor(targetRow, column)\n : undefined,\n renderEditTrigger: this.editingController.enabled\n ? (targetRow, column) => this.editingController.renderEditTrigger(targetRow, column)\n : undefined,\n pins: this.structure.pins,\n spans: this.structure.merges.cells.get(row.id),\n headerId: id => this.headerId(id),\n responsiveCards: this.responsiveLayout === 'cards',\n row,\n model,\n visibleColumns: this.visibleColumns,\n emptyCellLabel: this.emptyCellLabel,\n actionMenuElementId: this.actionMenuElementId,\n actionMenu: this.actionMenu,\n actionMenuSurface: this.actionMenuSurface,\n surfaceOpenRowId: this.surfaceOpenRowId,\n highlightMatcher: this.highlightMatcher,\n highlightFieldIds: this.highlightFieldIds,\n ariaRowIndex:\n ariaRowIndex == null ? undefined : ariaRowIndex + Number(this.structure.bands.length > 0),\n variableVirtualSize,\n intrinsicBlockSize:\n this.dataMode !== 'virtual'\n ? estimateTableRowBlockSize(row, this.visibleColumns)\n : undefined,\n rowKey,\n renderSelectionControl: (label, checked, indeterminate, disabled, onActivate) =>\n this.renderSelectionControl(label, checked, indeterminate, disabled, onActivate),\n renderStickyEdge: (sticky, shadow) => this.renderStickyEdge(sticky, shadow),\n onRowActivate: (targetRow, event) => this.emitRowActivation(targetRow, event),\n onRowKeyDown: (targetRow, event) => this.handleRowKeydown(targetRow, event),\n onRowSelection: targetRow => this.emitRowSelection(targetRow),\n onCellAction: detail => this.dsCellAction.emit(detail),\n onActionMenuToggle: (targetRow, column, event) =>\n this.toggleActionMenu(targetRow, column, event),\n });\n }\n\n private emitGroupCollapse(group: DataGroup) {\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 renderGroupAccessory(\n item: TableRenderModel['groups'][number]['accessories'][number],\n index: number\n ) {\n const text = (\n <ds-text\n key={`acc-${index}`}\n class=\"ds-table__group-accessory\"\n as=\"span\"\n variant=\"text-body-small\"\n color=\"secondary\"\n decoration={item.help ? 'dotted-underline' : undefined}\n lineTruncation={1}\n >\n {item.text}\n </ds-text>\n );\n if (!item.help) return text;\n return (\n <ds-tooltip\n key={`acc-${index}`}\n label={item.help}\n side=\"bottom\"\n align=\"start\"\n size=\"sm\"\n wrapLabel={true}\n >\n <span class=\"ds-table__group-accessory-tip\" tabIndex={0}>\n {text}\n </span>\n </ds-tooltip>\n );\n }\n\n private renderGroupContent(groupModel: TableRenderModel['groups'][number]) {\n const {\n group,\n countLabel,\n collapsed: isCollapsed,\n labelColor,\n selection: groupSelection,\n accessories,\n hero,\n } = groupModel;\n const heroTracks = hero?.kind === 'score' && hero.tracks === 2 ? 2 : 1;\n const heroPresentation = TABLE_GROUP_HERO_SCORE_PRESENTATION[heroTracks];\n return (\n <span\n class={{\n 'ds-table__group-content': true,\n 'ds-table__group-content--multi': accessories.length > 0 || heroTracks === 2,\n 'ds-table__group-content--hero': Boolean(hero),\n 'ds-table__group-content--hero-two-track': Boolean(hero) && heroTracks === 2,\n }}\n onClick={event => this.handleGroupRowClick(group, event)}\n >\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 {hero?.kind === 'score' && (\n <span class=\"ds-table__group-hero\">\n <ds-score\n size={heroPresentation.size}\n variant={heroPresentation.variant}\n value={hero.value}\n level={hero.level}\n label={hero.label ?? 'Safety score'}\n isLoading={hero.isLoading === true}\n />\n </span>\n )}\n <span class=\"ds-table__group-copy\">\n <span class=\"ds-table__group-primary\">\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 <span class=\"ds-visually-hidden\">{countLabel}</span>\n </span>\n {accessories.length > 0 && (\n <span class=\"ds-table__group-accessories\">\n {accessories.flatMap((item, index) => [\n ...(index > 0\n ? [\n <ds-text\n key={`sep-${index}`}\n class=\"ds-table__group-accessory-separator\"\n as=\"span\"\n variant=\"text-body-small\"\n color=\"secondary\"\n aria-hidden=\"true\"\n >\n ·\n </ds-text>,\n ]\n : []),\n this.renderGroupAccessory(item, index),\n ])}\n </span>\n )}\n </span>\n <span class=\"ds-table__group-trailing\">\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 {groupModel.visibleCountText}\n </ds-text>\n <span class=\"ds-table__group-action\">\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 ? `Expand ${group.label} group` : `Collapse ${group.label} group`\n }\n hasBorder={false}\n pressScale={false}\n />\n </span>\n </span>\n </span>\n );\n }\n\n private formatGroupLoadLabel(template: string, group: DataGroup): string {\n return template.split('{group}').join(group.label);\n }\n\n private renderGroupLoadRow(group: DataGroup, totalColumns: number) {\n const error = group.loadMoreError?.trim();\n if (!error && !group.loadingMore && !group.hasMore) return null;\n const manualFallback =\n this.loadMoreMode === 'manual' || !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 {renderTableLoadContent({\n error,\n loading: !!group.loadingMore,\n hasMore: !!group.hasMore,\n manualFallback,\n retryLabel: this.formatGroupLoadLabel(this.groupRetryLabel, group),\n loadingLabel: this.formatGroupLoadLabel(this.groupLoadingMoreLabel, group),\n loadMoreLabel: this.groupLoadMoreLabel,\n loadMoreAriaLabel: this.formatGroupLoadLabel(this.groupLoadMoreAriaLabel, group),\n onRetry: () => this.groupLoadController.request(group.id, 'retry'),\n onLoadMore: () => this.groupLoadController.request(group.id, 'manual'),\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, plan: TableVirtualPlan | null) {\n return this.bodyRenderer.render({\n headerRowCount: 1 + Number(this.structure.bands.length > 0),\n model,\n plan,\n rows: this.rows,\n stickyHeader: this.stickyHeader,\n documentStickyHeader: this.documentStickyHeader,\n activeStickyGroupId: this.activeStickyGroupId,\n renderRow: (row, renderModel, ariaRowIndex, variableVirtualSize, rowKey) =>\n this.renderRow(row, renderModel, ariaRowIndex, variableVirtualSize, rowKey),\n renderGroupContent: groupModel => this.renderGroupContent(groupModel),\n renderGroupLoadRow: (group, totalColumns) => this.renderGroupLoadRow(group, totalColumns),\n });\n }\n\n private renderSkeletonBody(model: TableRenderModel) {\n return renderTableSkeletonBody({\n pins: this.structure.pins,\n model,\n visibleColumns: this.visibleColumns,\n skeletonRows: this.skeletonRows,\n renderStickyEdge: (sticky, shadow) => this.renderStickyEdge(sticky, shadow),\n });\n }\n\n private renderStateBody(kind: 'empty' | 'error' | 'virtual-viewport', totalColumns: number) {\n const error = kind !== 'empty';\n const heading =\n kind === 'virtual-viewport'\n ? TABLE_VIRTUAL_VIEWPORT_REQUIRED_HEADING\n : error\n ? this.errorHeading\n : this.emptyHeading;\n const body =\n kind === 'virtual-viewport'\n ? TABLE_VIRTUAL_VIEWPORT_REQUIRED_BODY\n : error\n ? this.errorBody\n : this.emptyBody;\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 ? 'ExclamationTriangle' : 'Inbox'}\n heading={heading}\n body={body}\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 )\n return null;\n const error = this.loadMoreError?.trim();\n const manualFallback =\n this.loadMoreMode === 'manual' || !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 {renderTableLoadContent({\n error,\n loading: this.loadingMore,\n hasMore: this.hasMore,\n manualFallback,\n retryLabel: this.retryLabel,\n loadingLabel: this.loadingMoreLabel,\n loadMoreLabel: this.loadMoreLabel,\n endOfResultsLabel: this.endOfResultsLabel,\n onRetry: () => this.loadController.request('retry'),\n onLoadMore: () => this.loadController.request('manual'),\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 class=\"ds-table__document-sticky-header\">\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 if (this.dataMode === 'virtual') {\n return formatTableTotalSummary(this.currentLoadedRowCount(), this.resultTotalSummaryLabel);\n }\n return formatTableResultSummary(this.displayedCount, this.totalCount, this.resultSummaryLabel);\n }\n\n private get hasResultFooter(): boolean {\n return (\n (this.dataMode === 'pagination' && !!this.pagination) ||\n !!this.resultSummary ||\n this.footerSlotPresence !== 0\n );\n }\n\n private renderResultFooter() {\n const summary = this.resultSummary;\n const hasLeading = (this.footerSlotPresence & TABLE_FOOTER_SLOT_LEADING) !== 0;\n const hasCopy = (this.footerSlotPresence & TABLE_FOOTER_SLOT_COPY) !== 0;\n const hasTrailing = (this.footerSlotPresence & TABLE_FOOTER_SLOT_TRAILING) !== 0;\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\" onSlotchange={this.syncFooterSlotPresence} />\n </div>\n )}\n {hasCopy ? (\n <slot name=\"footer\" onSlotchange={this.syncFooterSlotPresence} />\n ) : summary ? (\n <ds-skeleton\n class=\"ds-table__footer-summary-skeleton\"\n preserveLayout={true}\n isLoading={this.chromeLoading}\n >\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 lineTruncation={1}\n >\n {summary}\n </ds-text>\n </ds-skeleton>\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 {...{ fitToPageInactive: pagination.fitToPageInactive ?? 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 showFirstLastButtons={pagination.showFirstLastButtons ?? false}\n loading={this.loading}\n chromeLoading={this.chromeLoading}\n onDsChange={(event: CustomEvent<PaginationChangeDetail>) =>\n this.dsPaginationChange.emit(event.detail)\n }\n />\n )}\n <slot name=\"footer-trailing\" onSlotchange={this.syncFooterSlotPresence} />\n </div>\n )}\n </div>\n );\n }\n\n render() {\n this.groupSentinelEls.clear();\n const model = this.createRenderModel();\n this.syncVirtualItems(model);\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 windowRows = this.shouldWindowRows(model);\n const virtualViewportMissing =\n this.virtualViewportMissing && !initialLoading && (model.hasData || hasGroupedStructure);\n const virtualPlan = windowRows\n ? (this.virtualWindow ??\n resolveTableVirtualPlan({\n items: this.virtualItems,\n sizes: this.virtualItems.map(item => this.virtualController.sizeFor(item)),\n scrollOffset: this.viewportEl?.scrollTop ?? 0,\n viewportSize: this.estimateVirtualViewportSize(),\n pinnedRowIds: this.virtualPinnedRowIds(),\n }))\n : null;\n const groupLoadingMore = hasGroupedStructure && this.groups.some(group => group.loadingMore);\n const viewportStyle =\n !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) => {\n this.editingController.handleKeyDown(event);\n this.handlePaginationKeyDown(event);\n }}\n onCopy={(event: ClipboardEvent) => this.editingController.handleClipboard(event)}\n onPaste={(event: ClipboardEvent) => this.editingController.handleClipboard(event)}\n onFocusin={this.onVirtualFocusIn}\n >\n <div\n 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--chrome-edge-to-edge': this.chromeLayout === 'edge-to-edge',\n 'ds-table--super-headers': this.structure.bands.length > 0,\n 'ds-table--responsive-cards':\n this.responsiveLayout === 'cards' &&\n this.interactionMode !== 'grid' &&\n this.cellSpans.length === 0,\n 'ds-table--cards-active':\n this.narrowViewport &&\n this.responsiveLayout === 'cards' &&\n this.interactionMode !== 'grid' &&\n this.cellSpans.length === 0,\n 'ds-table--footer-visible': this.hasResultFooter,\n 'ds-table--state-fill':\n !initialLoading &&\n (initialError || virtualViewportMissing || !(model.hasData || hasGroupedStructure)),\n }}\n ref={element => {\n this.rootEl = element ?? null;\n }}\n >\n {this.captionController.renderBar()}\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 data-narrow={String(this.narrowViewport)}\n ref={element => {\n this.frameEl = element ?? null;\n }}\n >\n {this.alternateView ? <slot name=\"alternate-view\" /> : null}\n {!this.alternateView && this.renderDocumentStickyHeader(model)}\n {!this.alternateView && this.renderStickyGroup(model)}\n {!this.alternateView &&\n !this.documentStickyHeader &&\n this.renderFloatingCollapseAll(model)}\n {!this.alternateView && (\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--deferred-rows':\n this.dataMode !== 'virtual' && !this.structure.merges.hasRowSpans,\n 'ds-table__table--windowed': windowRows,\n 'ds-table__table--virtual': this.dataMode === 'virtual' && windowRows,\n 'ds-table__table--native-group-sticky':\n (model.grouped && this.stickyHeader && !this.documentStickyHeader) ||\n windowRows,\n }}\n style={model.tableStyle}\n role={this.editingController.gridEnabled ? 'grid' : 'table'}\n aria-multiselectable={this.editingController.gridEnabled ? 'true' : undefined}\n aria-rowcount={\n windowRows\n ? 1 + Number(this.structure.bands.length > 0) + this.virtualItems.length\n : undefined\n }\n aria-busy={\n initialLoading ||\n (this.dataMode === 'infinite' && (this.loadingMore || groupLoadingMore))\n ? 'true'\n : undefined\n }\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(\n model,\n !this.documentStickyHeader,\n false,\n windowRows ? 1 : undefined\n )}\n {initialLoading\n ? this.renderSkeletonBody(model)\n : initialError\n ? this.renderStateBody('error', model.totalColumns)\n : virtualViewportMissing\n ? this.renderStateBody('virtual-viewport', model.totalColumns)\n : model.hasData || hasGroupedStructure\n ? this.renderDataBodies(model, virtualPlan)\n : this.renderStateBody('empty', model.totalColumns)}\n {!initialLoading &&\n !initialError &&\n this.dataMode !== 'virtual' &&\n this.renderLazyBody(model)}\n </table>\n </div>\n )}\n </div>\n {this.renderResultFooter()}\n {this.renderOverflowActionMenu()}\n {this.captionController.renderMenus()}\n {this.renderTruncateTooltip()}\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",
1728
+ "content": "import {\n Component,\n Element,\n Event,\n EventEmitter,\n h,\n Host,\n Listen,\n Method,\n Prop,\n State,\n Watch,\n} from '@stencil/core';\nimport {\n deriveTableSelectionState,\n formatTableResultSummary,\n formatTableTotalSummary,\n hasOwnedTableFooterSlot,\n isTableCellAction,\n nextDataSortState,\n tableColumnSize,\n tableModelIssues,\n TABLE_GROUP_HERO_SCORE_PRESENTATION,\n tableRows,\n toggleAllLoadedTableRows,\n toggleTableGroupCollapsed,\n toggleTableGroupSelection,\n nextTableGroupsCollapsed,\n toggleTableRowSelection,\n} from './table-model';\nimport { createTableRenderModel, type TableRenderModel } from './table-render-model';\nimport {\n isRenderableTableActionMenu,\n nextTableActionMenuElementId,\n tableActionMenuSections,\n tableActionTriggerId,\n} from './table-action-menu';\nimport { resolveTableVisibleColumns } from './table-column-customizer';\nimport { createTableCaptionController, createTableCaptionState } from './table-caption-controller';\nimport { createTableEditingController, type TableEditingState } from './table-editing-controller';\nimport { resolveTableTruncateTrack, tableTruncateLabel } from './table-truncate';\nimport { renderTableRow as renderTableRowView } from './table-row-view';\nimport { TableBodyRenderer } from './table-body-renderer';\nimport { tableHeaderBands, tablePinnedColumns, tableSpanModel } from './table-structure';\nimport type { TableCellSpan, TableCellRange, TableCellsChangeDetail } from './table-types';\nimport { renderTableSkeletonBody } from './table-skeleton-view';\nimport { renderTableLoadContent } from './table-load-view';\nimport {\n isTableCardViewport,\n TABLE_CARD_VIEWPORT_BREAKPOINT,\n TableLayoutController,\n} from './table-layout-controller';\nimport { TableLoadController } from './table-load-controller';\nimport { TableGroupLoadController } from './table-group-load-controller';\nimport {\n estimateTableRowBlockSize,\n flattenTableVirtualItems,\n resolveTableVirtualPlan,\n TABLE_VIRTUAL_TOTAL_SUMMARY_LABEL,\n TABLE_VIRTUAL_VIEWPORT_REQUIRED_BODY,\n TABLE_VIRTUAL_VIEWPORT_REQUIRED_HEADING,\n type TableVirtualItem,\n type TableVirtualPlan,\n} from './table-virtual-model';\nimport { TableVirtualController } from './table-virtual-controller';\nimport type { PaginationChangeDetail } from '../Pagination/pagination-types';\nimport type { MenuItemData } from '../Menu/menu-types';\nimport { resolvePaginationState } from '../Pagination/pagination-model';\nimport { resolveCssLengthPx } from '../../utils/resolve-css-length-px';\nimport { isElementTruncated } from '../../utils/is-element-truncated';\nimport { observeTableCaptionCompact } from '../../utils/table-caption-compact';\nimport { resolveTableFitPageSize } from './table-pagination-fit';\nimport { createTableHighlightMatcher, type TableHighlightMatcher } from './table-highlight';\nimport {\n paginationShortcutBlockedByPath,\n shouldHandleContainingPagePaginationShortcut,\n} from './table-pagination-shortcut';\nimport {\n TableViewportFitController,\n type TableViewportFitMetrics,\n} from './table-viewport-fit-controller';\nimport type {\n TableCaptionVisibility,\n TableChromeLayout,\n TableCellActionDetail,\n TableColumn,\n DataFieldsConfigChangeDetail,\n TableDataMode,\n TableDataModeChangeDetail,\n DataGroup,\n TableGroupCollapseChangeDetail,\n TableGroupLoadMoreDetail,\n DataGroupingState,\n TableLoadMoreDetail,\n TableLoadMoreMode,\n TablePaginationState,\n TableRow,\n TableRowActivateDetail,\n TableSelectionChangeDetail,\n TableSelectionMode,\n DataSortChangeDetail,\n DataSortState,\n} from './table-types';\n\nconst TABLE_FOOTER_SLOT_LEADING = 1;\nconst TABLE_FOOTER_SLOT_COPY = 2;\nconst TABLE_FOOTER_SLOT_TRAILING = 4;\nconst TABLE_NON_VIRTUAL_WINDOW_THRESHOLD = 50;\n\n/**\n * @slot error-actions - Application-owned recovery controls below the initial error message.\n */\n@Component({\n tag: 'ds-table',\n styleUrls: ['../../utils/focus-ring.css', '../../utils/interaction-fill.css', 'Table.css'],\n scoped: true,\n})\nexport class Table {\n @Element() el!: HTMLElement;\n /** Stable column definitions. Assign through JavaScript. */\n @Prop() columns: TableColumn[] = [];\n /** Opt-in native card presentation below the 768px browser viewport breakpoint. */\n @Prop() responsiveLayout: 'scroll' | 'cards' = 'scroll';\n /** Keep all table chrome contained, or let the caption and footer span edge to edge around an inset data frame. */\n @Prop() chromeLayout: TableChromeLayout = 'contained';\n /** Explicit report merges; invalid combinations leave the original cells visible. */\n @Prop() cellSpans: TableCellSpan[] = [];\n /** Table is read-only; edit uses per-cell pencil buttons; grid adds cell selection and keyboard navigation. Grouped tables remain read-only. */\n @Prop() interactionMode: 'table' | 'edit' | 'grid' = 'table';\n /** Controlled rectangular cell selection, independent of row checkbox selection. */\n @Prop() cellRange: TableCellRange | null = null;\n @Event() dsCellRangeChange!: EventEmitter<TableCellRange>;\n @Event() dsCellsChange!: EventEmitter<TableCellsChangeDetail>;\n /** Additional toggle options in the column customizer. */\n @Prop() customizeOptions: MenuItemData[] = [];\n @Event() dsCustomizeOptionChange!: EventEmitter<string>;\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: DataGroup[] = [];\n /** Controlled grouping column. Applications supply groups in their final fixed order. */\n @Prop() grouping: DataGroupingState | null = null;\n /** Controlled member-row sort state. */\n @Prop() sort: DataSortState | null = null;\n /** Controlled collapsed group identities. Groups not listed remain expanded. */\n @Prop() collapsedGroupIds: string[] = [];\n /** Literal terms to highlight in table-owned text cells. Applications still own filtering. */\n @Prop() highlightTerms: string[] = [];\n /** Optional DataSearch field identities that restrict which data-point tracks are highlighted. */\n @Prop() highlightFieldIds: 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 /** Replace only the data frame with application-owned content; caption and footer remain shared. */\n @Prop() alternateView: boolean = false;\n /** Fires after a table/alternate-view switch has patched the frame and footer. */\n @Event() dsAlternateViewRendered!: EventEmitter<{ alternateView: boolean }>;\n /**\n * Opt in to the table-owned column customizer. The `columns` prop remains the\n * catalog; hidden and ordered columns are controlled separately. The trigger\n * opens the shared Menu of live show/hide switch rows.\n */\n @Prop() columnCustomizer: boolean = false;\n /** Hide only the built-in Customize trigger when an external preferences surface owns it. */\n /** Remove borders from table-owned caption controls. */\n @Prop() captionControlsBorderless: boolean = false;\n @Prop() hideColumnCustomizerTrigger: boolean = false;\n /** Controlled hidden data-column identities. Action ids are ignored. */\n @Prop() hiddenFieldIds: string[] = [];\n /** Controlled data-column identities in display order. Omitted ids append in catalog order. */\n @Prop() fieldOrder: string[] = [];\n /** Controlled user-pinned data-column identities. Hidden columns retain their pin state. */\n @Prop() pinnedFieldIds: string[] = [];\n /**\n * Optional result summary footer. When both `displayedCount` and `totalCount`\n * are finite numbers, infinite mode shows “Displaying {displayed} of {total}”.\n * Virtual mode ignores `displayedCount` and derives its total from the\n * complete supplied rows; a mismatched `totalCount` emits a warning.\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 /** Supports the {total} placeholder. Used when dataMode is virtual. */\n @Prop() resultTotalSummaryLabel: string = TABLE_VIRTUAL_TOTAL_SUMMARY_LABEL;\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 /**\n * Identity of the interactive row whose associated surface is visible.\n * Holds its pressed wash independently of checkbox selection. Keep the ID\n * through exit motion and clear it after close; the application owns toggling\n * and switching the surface. Off-window identities survive virtualization.\n */\n @Prop() surfaceOpenRowId: string | undefined;\n\n /** Initial loading state. Existing rows stay visible; incremental loading uses loadingMore. */\n @Prop() loading: boolean = false;\n /** Replace column headers and table-owned caption controls with same-size visual skeletons. */\n @Prop() chromeLoading: 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. Virtual mode recycles row DOM only. */\n @Prop() dataMode: TableDataMode = 'infinite';\n /** Opt in to the table-owned control for choosing between supported data modes. */\n @Prop() dataModeSwitcher: boolean = false;\n @Prop() dataModeSwitcherLabel: string = 'Change table variation';\n @Prop() dataModeMenuLabel: string = 'Table variation';\n @Prop() infiniteModeLabel: string = 'Infinite scroll';\n @Prop() paginationModeLabel: string = 'Pagination + Infinite groups';\n @Prop() virtualModeLabel: string = 'Virtual scroll';\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 =\n '{count} more rows loaded in {group}. {loaded} of {total} rows loaded.';\n\n @Event() dsSortChange!: EventEmitter<DataSortChangeDetail>;\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 @Event() dsFieldsConfigChange!: EventEmitter<DataFieldsConfigChangeDetail>;\n @Event() dsDataModeChange!: EventEmitter<TableDataModeChangeDetail>;\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 headerUsesToolbar = false;\n @State() private captionTrailingPresent = false;\n @State() private footerSlotPresence = 0;\n @State() private errorActionsPresent = false;\n @State() private fitPageSize: number | undefined;\n @State() private actionMenu: { rowId: string; columnId: string } | null = null;\n @State() private actionMenuInitialFocusVisible = false;\n @State() private actionMenuSurface: { rowId: string; columnId: string } | null = null;\n @State() private truncateTooltipLabel = '';\n @State() private captionCompact = false;\n @State() private narrowViewport = false;\n @State() private captionMenus = createTableCaptionState();\n @State() private virtualWindow: TableVirtualPlan | null = null;\n @State() private cellInteraction: TableEditingState = { activeCell: null, editingCell: null };\n\n private readonly actionMenuElementId = nextTableActionMenuElementId();\n private structureCache?: {\n columns: TableColumn[];\n rows: TableRow[];\n spans: TableCellSpan[];\n selectable: boolean;\n grouped: boolean;\n windowed: boolean;\n grid: boolean;\n pins: ReturnType<typeof tablePinnedColumns>;\n bands: ReturnType<typeof tableHeaderBands>;\n merges: ReturnType<typeof tableSpanModel>;\n };\n\n private get structure() {\n const columns = this.visibleColumns;\n const model = this.createRenderModel();\n const cached = this.structureCache;\n const windowed = this.rowWindowingEnabled;\n const grid = this.interactionMode !== 'table';\n if (\n cached &&\n cached.columns === columns &&\n cached.rows === this.rows &&\n cached.spans === this.cellSpans &&\n cached.selectable === model.selectable &&\n cached.grouped === model.grouped &&\n cached.windowed === windowed &&\n cached.grid === grid\n )\n return cached;\n return (this.structureCache = {\n columns,\n rows: this.rows,\n spans: this.cellSpans,\n selectable: model.selectable,\n grouped: model.grouped,\n windowed,\n grid,\n pins: tablePinnedColumns(columns, model.selectable),\n bands: tableHeaderBands(columns, model.selectable, model.elasticSpacerIndex),\n merges: tableSpanModel(this.cellSpans, this.rows, columns, {\n windowed,\n grouped: model.grouped,\n grid,\n selectable: model.selectable,\n spacer: model.elasticSpacerIndex,\n }),\n });\n }\n\n private headerId(columnId: string): string {\n return `${this.actionMenuElementId}-header-${encodeURIComponent(columnId)}`;\n }\n\n private columnBandId(columnId: string): string | undefined {\n const model = this.createRenderModel();\n const index = this.visibleColumns.findIndex(column => column.id === columnId);\n const lane =\n index +\n 1 +\n Number(model.selectable) +\n Number(model.elasticSpacerIndex != null && index >= model.elasticSpacerIndex);\n const band = this.structure.bands.find(\n band => band.label && lane >= band.start && lane < band.start + band.span\n );\n return band ? `${this.actionMenuElementId}-${band.key}` : undefined;\n }\n private truncateTooltipEl?: HTMLDsTooltipElement;\n private truncateAnchor: HTMLElement | null = null;\n private truncateTooltipBound = false;\n private focusedRowId: string | null = null;\n private virtualRenderFocus: HTMLElement | null = null;\n private renderedAlternateView: boolean | null = null;\n private virtualItems: TableVirtualItem[] = [];\n private visibleColumnsCache: {\n columns: TableColumn[];\n columnCustomizer: boolean;\n hiddenFieldIds: string[];\n fieldOrder: string[];\n pinnedFieldIds: string[];\n value: TableColumn[];\n } | null = null;\n private renderModelCache: {\n columns: TableColumn[];\n rows: TableRow[];\n groups: DataGroup[];\n grouped: boolean;\n selectionMode: TableSelectionMode;\n selectedRowIds: string[];\n collapsedGroupIds: string[];\n virtualCounts: boolean;\n value: TableRenderModel;\n } | null = null;\n private highlightMatcherCache: {\n terms: string[];\n value: TableHighlightMatcher;\n } | null = null;\n private virtualItemsCache: {\n columns: TableColumn[];\n rows: TableRow[];\n groups: DataGroup[];\n grouped: boolean;\n collapsedGroupIds: string[];\n } | null = null;\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 modelWarningQueued = false;\n private initialModelWarningFrame: number | undefined;\n private hasLoaded = false;\n private renderedModel: TableRenderModel | null = null;\n private stickyGroupConnected = false;\n private headerSlotObserver: MutationObserver | null = null;\n private contentSlotObserver: MutationObserver | null = null;\n private captionCompactDisconnect: (() => void) | undefined;\n private fitResizeObserver: ResizeObserver | null = null;\n private fitObservedViewport: HTMLElement | null = null;\n private fitObservedTable: HTMLTableElement | null = null;\n private fitMeasurementPending = false;\n private cardViewportQuery: MediaQueryList | null = null;\n private readonly editingController = createTableEditingController({\n host: () => this.el,\n state: () => ({\n rows: this.rows,\n columns: this.visibleColumns,\n mode: this.grouped ? 'table' : this.interactionMode,\n loading: this.loading,\n cellRange: this.cellRange,\n }),\n interaction: () => this.cellInteraction,\n change: patch => {\n this.cellInteraction = { ...this.cellInteraction, ...patch };\n },\n rangeChange: range => {\n this.dsCellRangeChange.emit(range);\n },\n cellsChange: detail => {\n this.dsCellsChange.emit(detail);\n },\n announce: message => {\n this.announcement = message;\n },\n focusRow: rowId => {\n this.focusedRowId = rowId;\n },\n revealRow: rowId => this.scrollRowIntoView(rowId),\n findRow: rowId => this.findRenderedRow(rowId),\n });\n private readonly captionController = createTableCaptionController({\n host: () => this.el,\n state: () => ({\n caption: this.caption,\n captionVisibility: this.captionVisibility,\n headerPresent: this.headerPresent,\n headerUsesToolbar: this.headerUsesToolbar,\n captionTrailingPresent: this.captionTrailingPresent,\n captionCompact: this.captionCompact,\n columnCustomizer: this.columnCustomizer,\n hideColumnCustomizerTrigger: this.hideColumnCustomizerTrigger,\n captionControlsBorderless: this.captionControlsBorderless,\n chromeLoading: this.chromeLoading,\n columns: this.columns,\n hiddenFieldIds: this.hiddenFieldIds,\n fieldOrder: this.fieldOrder,\n pinnedFieldIds: this.pinnedFieldIds,\n customizeOptions: this.customizeOptions,\n dataModeSwitcher: this.dataModeSwitcher,\n dataModeSwitcherLabel: this.dataModeSwitcherLabel,\n dataModeMenuLabel: this.dataModeMenuLabel,\n dataMode: this.dataMode,\n infiniteModeLabel: this.infiniteModeLabel,\n paginationModeLabel: this.paginationModeLabel,\n virtualModeLabel: this.virtualModeLabel,\n }),\n menus: () => this.captionMenus,\n change: patch => {\n this.captionMenus = { ...this.captionMenus, ...patch };\n },\n headerSlotChange: () => this.syncHeaderSlotPresence(),\n fieldsChange: detail => {\n this.dsFieldsConfigChange.emit(detail);\n },\n dataModeChange: detail => {\n this.dsDataModeChange.emit(detail);\n },\n customizeOptionChange: value => {\n this.dsCustomizeOptionChange.emit(value);\n },\n });\n private readonly bodyRenderer = new TableBodyRenderer();\n private readonly layoutController = new TableLayoutController({\n elements: () => ({\n viewport: this.viewportEl,\n contentTable: this.tableEl,\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 && this.dataMode !== 'virtual',\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 virtualController = new TableVirtualController({\n state: () => ({\n enabled: this.rowWindowingEnabled,\n items: this.virtualItems,\n pinnedRowIds: this.virtualPinnedRowIds(),\n viewport: this.viewportEl,\n viewportSize: this.estimateVirtualViewportSize(),\n }),\n windowChanged: plan => {\n this.virtualWindow = plan;\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.connectCardViewportQuery();\n // Reserve the final viewport-fit height before the first paint. The fitted\n // surface is connected after render, but the host can already resolve its\n // owning scrollport and prevent an initial auto-height frame.\n this.viewportFitController.connect();\n this.incrementalWindowActive = this.hasIncrementalState;\n this.syncHeaderSlotPresence();\n this.syncContentSlotPresence();\n this.loadController.initialize();\n this.groupLoadController.initialize();\n }\n\n componentDidLoad(): void {\n this.hasLoaded = true;\n this.layoutController.connect();\n this.loadController.connect();\n this.groupLoadController.connect();\n this.virtualController.connect();\n this.viewportFitController.connect();\n this.syncStickyGroupConnection();\n this.connectHeaderSlotObserver();\n this.connectContentSlotObserver();\n this.connectFitObserver();\n this.connectCaptionCompactObserver();\n this.syncFitPageSize();\n this.connectTruncateTooltip();\n this.scheduleInitialModelIssueWarning();\n }\n\n componentWillRender(): void {\n if (\n this.errorActionsPresent &&\n (this.alternateView || this.loading || !this.error || this.createRenderModel().hasData)\n ) {\n // EmptyState relocates this wrapper into its own rendered tree. Return\n // authored controls before removing the wrapper so scoped-slot teardown\n // cannot discard them during an error-to-loading or results transition.\n const actions = this.el.querySelector('.ds-table__error-actions');\n const parked = this.el.querySelector('.ds-table__parked-actions');\n if (parked && actions?.closest('ds-table') === this.el) {\n for (const action of actions.querySelectorAll(':scope > [slot=\"error-actions\"]')) {\n parked.appendChild(action);\n }\n }\n }\n const root = this.el.getRootNode() as Document | ShadowRoot;\n const active = root.activeElement;\n this.virtualRenderFocus =\n this.rowWindowingEnabled &&\n active instanceof HTMLElement &&\n this.tableEl?.contains(active) &&\n active.closest('[data-row-id]')\n ? active\n : null;\n }\n\n componentDidRender(): void {\n // Moving a retained pooled row can clear native focus in Firefox. Restore\n // only the same connected node, without scrolling or stealing external focus.\n const retainedFocus = this.virtualRenderFocus;\n this.virtualRenderFocus = null;\n if (retainedFocus?.isConnected) {\n const root = retainedFocus.getRootNode() as Document | ShadowRoot;\n if (!root.activeElement || root.activeElement === this.el.ownerDocument.body) {\n // Resolve the moved rows' scroll geometry before restoring native focus.\n // WebKit otherwise may reveal the caret using the pre-patch layout even\n // with preventScroll. Preserve both axes within the resulting bounds.\n const viewport = this.viewportEl;\n const top = viewport?.scrollTop ?? 0;\n const left = viewport?.scrollLeft ?? 0;\n retainedFocus.focus({ preventScroll: true });\n if (viewport) {\n if (viewport.scrollTop !== top) viewport.scrollTop = top;\n if (viewport.scrollLeft !== left) viewport.scrollLeft = left;\n }\n }\n }\n this.layoutController.refresh(false);\n this.loadController.refresh();\n this.groupLoadController.refresh();\n this.virtualController.refresh();\n this.virtualController.collectMeasurements(this.tableEl);\n if (\n this.rowWindowingEnabled &&\n this.truncateAnchor &&\n this.tableEl &&\n !this.tableEl.contains(this.truncateAnchor)\n ) {\n this.dismissTruncateTooltip();\n }\n this.viewportFitController.refresh(false);\n this.syncStickyGroupConnection();\n this.connectFitObserver();\n this.connectCaptionCompactObserver();\n this.syncFitPageSize();\n if (this.stickyGroupConnected) this.updateStickyGroup();\n if (this.renderedAlternateView !== this.alternateView) {\n this.renderedAlternateView = this.alternateView;\n this.dsAlternateViewRendered.emit({ alternateView: this.alternateView });\n }\n }\n\n /**\n * Scroll the nearest vertical owner only enough to reveal a supplied row.\n * Resolves false when the row is absent from the supplied, expanded table model.\n * This method never moves focus.\n */\n @Method()\n async scrollRowIntoView(rowId: string): Promise<boolean> {\n if (!rowId) return false;\n\n if (this.rowWindowingEnabled) {\n const headerSize = this.tableEl?.tHead?.getBoundingClientRect().height ?? 0;\n const revealed = this.virtualController.scrollRowIntoView(\n rowId,\n headerSize,\n this.stickyHeader ? headerSize : 0\n );\n if (!revealed) return false;\n await this.nextAnimationFrame();\n }\n\n const row = this.findRenderedRow(rowId);\n if (!row) return false;\n this.revealRenderedRow(row);\n return true;\n }\n\n connectedCallback(): void {\n if (!this.hasLoaded) return;\n this.connectCardViewportQuery();\n this.layoutController.connect();\n this.loadController.connect();\n this.groupLoadController.connect();\n this.virtualController.connect();\n this.viewportFitController.connect();\n this.syncStickyGroupConnection();\n this.connectHeaderSlotObserver();\n this.connectContentSlotObserver();\n this.connectFitObserver();\n this.connectCaptionCompactObserver();\n this.connectTruncateTooltip();\n }\n\n disconnectedCallback(): void {\n this.editingController.disconnect();\n this.captionController.disconnect();\n this.layoutController.disconnect();\n this.loadController.disconnect();\n this.groupLoadController.disconnect();\n this.virtualController.disconnect();\n this.viewportFitController.disconnect();\n this.disconnectStickyGroup();\n this.headerSlotObserver?.disconnect();\n this.headerSlotObserver = null;\n this.contentSlotObserver?.disconnect();\n this.contentSlotObserver = null;\n this.disconnectFitObserver();\n this.disconnectCaptionCompactObserver();\n this.disconnectTruncateTooltip();\n this.disconnectCardViewportQuery();\n if (this.initialModelWarningFrame !== undefined) {\n cancelAnimationFrame(this.initialModelWarningFrame);\n this.initialModelWarningFrame = undefined;\n }\n }\n\n private readonly handleCardViewportChange = (event: MediaQueryListEvent): void => {\n if (this.narrowViewport !== event.matches) this.narrowViewport = event.matches;\n };\n\n private connectCardViewportQuery(): void {\n if (\n this.cardViewportQuery ||\n typeof window === 'undefined' ||\n typeof window.matchMedia !== 'function'\n )\n return;\n const query = window.matchMedia(`(max-width: ${TABLE_CARD_VIEWPORT_BREAKPOINT - 1}px)`);\n this.cardViewportQuery = query;\n const narrow = isTableCardViewport(window.innerWidth);\n if (this.narrowViewport !== narrow) this.narrowViewport = narrow;\n query.addEventListener('change', this.handleCardViewportChange);\n }\n\n private disconnectCardViewportQuery(): void {\n this.cardViewportQuery?.removeEventListener('change', this.handleCardViewportChange);\n this.cardViewportQuery = null;\n }\n\n private syncHeaderSlotPresence = () => {\n const header = this.el.querySelector<HTMLElement>('[slot=\"header\"]');\n this.headerPresent = !!header;\n this.headerUsesToolbar = header?.tagName === 'DS-DATA-TOOLBAR';\n this.captionTrailingPresent = !!this.el.querySelector('[slot=\"caption-trailing\"]');\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 syncFooterSlotPresence = () => {\n let presence = 0;\n if (hasOwnedTableFooterSlot(this.el, 'footer-leading')) presence |= TABLE_FOOTER_SLOT_LEADING;\n if (hasOwnedTableFooterSlot(this.el, 'footer')) presence |= TABLE_FOOTER_SLOT_COPY;\n if (hasOwnedTableFooterSlot(this.el, 'footer-trailing')) presence |= TABLE_FOOTER_SLOT_TRAILING;\n if (presence !== this.footerSlotPresence) this.footerSlotPresence = presence;\n };\n\n private syncContentSlotPresence = () => {\n this.syncFooterSlotPresence();\n const actions = [...this.el.querySelectorAll('[slot=\"error-actions\"]')].filter(node => {\n const parent = node.parentElement;\n return (\n parent === this.el ||\n ((parent?.classList.contains('ds-table__error-actions') ||\n parent?.classList.contains('ds-table__parked-actions')) &&\n parent.closest('ds-table') === this.el)\n );\n });\n const present = actions.length > 0;\n if (present !== this.errorActionsPresent) this.errorActionsPresent = present;\n // Scoped slot relocation does not revisit nodes forwarded through a child\n // component after that outlet is removed. Reconcile only our authored\n // controls into the current outlet, retaining their identity and listeners.\n const outlet =\n this.el.querySelector('.ds-table__error-actions') ??\n this.el.querySelector('.ds-table__parked-actions');\n if (outlet?.closest('ds-table') === this.el) {\n for (const action of actions) {\n if (action.parentElement !== outlet) outlet.appendChild(action);\n }\n }\n };\n\n private connectContentSlotObserver(): void {\n if (this.contentSlotObserver || typeof MutationObserver === 'undefined') return;\n this.contentSlotObserver = new MutationObserver(this.syncContentSlotPresence);\n this.contentSlotObserver.observe(this.el, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['slot'],\n });\n this.syncContentSlotPresence();\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 @Watch('hiddenFieldIds')\n @Watch('fieldOrder')\n @Watch('pinnedFieldIds')\n @Watch('columnCustomizer')\n @Watch('cellSpans')\n @Watch('interactionMode')\n @Watch('responsiveLayout')\n handleStructureChange(): void {\n this.virtualItemsCache = null;\n this.scheduleModelIssueWarning();\n this.loadController.structureChanged();\n this.groupLoadController.structureChanged();\n this.virtualController.invalidateMeasures();\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.scheduleModelIssueWarning();\n this.syncActionMenu();\n this.virtualController.invalidateMeasures();\n }\n\n @Watch('totalCount')\n handleTotalCountChange(): void {\n this.scheduleModelIssueWarning();\n }\n\n @Watch('caption')\n handleCaptionChange(): void {\n this.scheduleModelIssueWarning();\n }\n\n @Watch('loadIdentity')\n handleLoadIdentityChange(): void {\n this.incrementalWindowActive = this.hasIncrementalState;\n this.loadController.identityChanged();\n this.virtualController.resetToTop();\n }\n\n @Watch('sort')\n @Watch('grouping')\n handleVirtualIndexReset(): void {\n this.virtualController.resetToTop();\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.scheduleModelIssueWarning();\n this.handleLazyConfigurationChange();\n this.virtualController.resetToTop();\n }\n\n @Watch('pagination')\n handlePaginationChange(\n pagination: TablePaginationState | null,\n previous: TablePaginationState | null\n ): void {\n if (pagination?.pageSizeMode === 'fit' && pagination.fitIdentity !== previous?.fitIdentity) {\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 @Watch('columnCustomizer')\n @Watch('captionVisibility')\n @Watch('dataModeSwitcher')\n @Watch('chromeLoading')\n handleCaptionAvailability(): void {\n this.captionController.reconcile();\n }\n\n private get grouped(): boolean {\n return this.grouping !== null;\n }\n\n private get hasIncrementalState(): boolean {\n return (\n this.dataMode === 'infinite' &&\n (this.hasMore || this.loadingMore || !!this.loadMoreError?.trim())\n );\n }\n\n private get selectable(): boolean {\n return this.selectionMode === 'multiple';\n }\n\n private get visibleColumns(): TableColumn[] {\n const cached = this.visibleColumnsCache;\n if (\n cached &&\n cached.columns === this.columns &&\n cached.columnCustomizer === this.columnCustomizer &&\n cached.hiddenFieldIds === this.hiddenFieldIds &&\n cached.fieldOrder === this.fieldOrder &&\n cached.pinnedFieldIds === this.pinnedFieldIds\n ) {\n return cached.value;\n }\n const value = resolveTableVisibleColumns(this.columns, {\n columnCustomizer: this.columnCustomizer,\n hiddenFieldIds: this.hiddenFieldIds,\n fieldOrder: this.fieldOrder,\n pinnedFieldIds: this.pinnedFieldIds,\n });\n this.visibleColumnsCache = {\n columns: this.columns,\n columnCustomizer: this.columnCustomizer,\n hiddenFieldIds: this.hiddenFieldIds,\n fieldOrder: this.fieldOrder,\n pinnedFieldIds: this.pinnedFieldIds,\n value,\n };\n return value;\n }\n\n private get documentStickyHeader(): boolean {\n return this.stickyHeader && !this.containedScroll && this.responsiveLayout !== 'cards';\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 connectCaptionCompactObserver(): void {\n if (this.captionCompactDisconnect) return;\n this.captionCompactDisconnect = observeTableCaptionCompact(this.el, compact => {\n if (this.captionCompact !== compact) this.captionCompact = compact;\n });\n }\n\n private disconnectCaptionCompactObserver(): void {\n this.captionCompactDisconnect?.();\n this.captionCompactDisconnect = undefined;\n }\n\n private readonly syncFitPageSize = (): void => {\n const pagination = this.dataMode === 'pagination' ? this.pagination : null;\n if (\n !pagination?.fitToPage ||\n pagination.fitToPageInactive ||\n !this.containedScroll ||\n !this.viewportEl ||\n !this.tableEl\n ) {\n if (this.fitPageSize !== undefined) this.fitPageSize = undefined;\n this.fitMeasurementPending = false;\n return;\n }\n const header = this.tableEl.querySelector<HTMLElement>('.ds-table__head');\n const item = this.tableEl.querySelector<HTMLElement>(\n this.grouped ? '.ds-table__group-row' : '.ds-table__body .ds-table__row'\n );\n const itemBlockSize =\n item?.getBoundingClientRect().height ||\n 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 fitToPageInactive: false,\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 showFirstLastButtons: pagination.showFirstLastButtons ?? false,\n previousPageIndex: pagination.pageIndex,\n previousPageSize: pagination.pageSize,\n previousPageSizeMode: 'fit',\n reason: 'fit',\n });\n };\n\n @Listen('keydown', { target: 'window' })\n handleWindowPaginationKeyDown(event: KeyboardEvent): void {\n this.handlePaginationKeyDown(event, true);\n }\n\n private handlePaginationKeyDown(event: KeyboardEvent, fromWindow = false): 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 if (paginationShortcutBlockedByPath(event.composedPath())) return;\n\n if (fromWindow) {\n const origin = event.composedPath()[0];\n const originNode = origin instanceof Text ? origin.parentNode : origin;\n if (\n !shouldHandleContainingPagePaginationShortcut({\n origin: originNode,\n table: this.el,\n eventPath: event.composedPath(),\n })\n ) {\n return;\n }\n }\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 fitToPageInactive: pagination.fitToPageInactive ?? 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 showFirstLastButtons: pagination.showFirstLastButtons ?? false,\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 const columns = this.visibleColumns;\n const grouped = this.grouped;\n const virtualCounts = this.dataMode === 'virtual';\n const cached = this.renderModelCache;\n if (\n cached &&\n cached.columns === columns &&\n cached.rows === this.rows &&\n cached.groups === this.groups &&\n cached.grouped === grouped &&\n cached.selectionMode === this.selectionMode &&\n cached.selectedRowIds === this.selectedRowIds &&\n cached.collapsedGroupIds === this.collapsedGroupIds &&\n cached.virtualCounts === virtualCounts\n ) {\n return cached.value;\n }\n const value = createTableRenderModel({\n columns,\n rows: this.rows,\n groups: this.groups,\n grouped,\n selectionMode: this.selectionMode,\n selectedRowIds: this.selectedRowIds,\n collapsedGroupIds: this.collapsedGroupIds,\n groupCountPresentation: virtualCounts ? 'total' : 'loaded-progress',\n });\n this.renderModelCache = {\n columns,\n rows: this.rows,\n groups: this.groups,\n grouped,\n selectionMode: this.selectionMode,\n selectedRowIds: this.selectedRowIds,\n collapsedGroupIds: this.collapsedGroupIds,\n virtualCounts,\n value,\n };\n return value;\n }\n\n private get highlightMatcher(): TableHighlightMatcher {\n if (this.highlightMatcherCache?.terms === this.highlightTerms) {\n return this.highlightMatcherCache.value;\n }\n const value = createTableHighlightMatcher(this.highlightTerms);\n this.highlightMatcherCache = { terms: this.highlightTerms, value };\n return value;\n }\n\n private get rowWindowingEnabled(): boolean {\n if (!this.hasVirtualViewportBounds) return false;\n if (this.dataMode === 'virtual') return true;\n return !this.grouped && this.currentLoadedRowCount() > TABLE_NON_VIRTUAL_WINDOW_THRESHOLD;\n }\n\n private get virtualViewportMissing(): boolean {\n return this.dataMode === 'virtual' && !this.hasVirtualViewportBounds;\n }\n\n private get hasVirtualViewportBounds(): boolean {\n return this.containedScroll && this.estimateVirtualViewportSize() > 0;\n }\n\n private estimateVirtualViewportSize(): number {\n const measured =\n this.virtualController.currentViewportSize() || this.viewportEl?.clientHeight || 0;\n const chrome =\n (this.captionVisibility === 'visible' ? 48 : 0) + (this.hasResultFooter ? 48 : 0);\n if (this.fixedHeight && this.resolvedHeight) {\n if (measured > 0) return measured;\n return Math.max(\n 0,\n resolveCssLengthPx(this.resolvedHeight, 0, this.rootEl ?? this.el) - chrome\n );\n }\n if (this.fitViewport) return Math.max(0, measured);\n if (this.resolvedMaxHeight) {\n if (this.resolvedMaxHeight.trim().toLowerCase() === 'none') return 0;\n return resolveCssLengthPx(this.resolvedMaxHeight, 0, this.rootEl ?? this.el);\n }\n return 0;\n }\n\n private virtualPinnedRowIds(): Set<string> {\n const pinned = new Set<string>();\n if (this.focusedRowId) pinned.add(this.focusedRowId);\n if (this.actionMenu?.rowId) pinned.add(this.actionMenu.rowId);\n return pinned;\n }\n\n private findRenderedRow(rowId: string): HTMLElement | null {\n return (\n [...this.el.querySelectorAll<HTMLElement>('[data-row-id]')].find(\n candidate => candidate.dataset.rowId === rowId\n ) ?? null\n );\n }\n\n private revealRenderedRow(row: HTMLElement): void {\n if (!this.containedScroll || !this.viewportEl) {\n row.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n return;\n }\n\n const viewportRect = this.viewportEl.getBoundingClientRect();\n const rowRect = row.getBoundingClientRect();\n let visibleStart = viewportRect.top;\n if (this.stickyHeader) {\n visibleStart = Math.max(\n visibleStart,\n this.tableEl?.tHead?.getBoundingClientRect().bottom ?? visibleStart\n );\n }\n const groupRow = row.closest('tbody')?.querySelector<HTMLElement>('.ds-table__group-row');\n if (groupRow && getComputedStyle(groupRow).position === 'sticky') {\n visibleStart = Math.max(visibleStart, groupRow.getBoundingClientRect().bottom);\n }\n\n if (rowRect.top < visibleStart) {\n this.viewportEl.scrollTop += rowRect.top - visibleStart;\n } else if (rowRect.bottom > viewportRect.bottom) {\n this.viewportEl.scrollTop += rowRect.bottom - viewportRect.bottom;\n }\n }\n\n private nextAnimationFrame(): Promise<void> {\n return new Promise(resolve => requestAnimationFrame(() => resolve()));\n }\n\n private syncVirtualItems(model: TableRenderModel): void {\n if (!this.rowWindowingEnabled) {\n this.virtualItems = [];\n this.virtualItemsCache = null;\n this.bodyRenderer.resetVirtualState();\n return;\n }\n const columns = this.visibleColumns;\n const cached = this.virtualItemsCache;\n if (\n cached &&\n cached.columns === columns &&\n cached.rows === this.rows &&\n cached.groups === this.groups &&\n cached.grouped === model.grouped &&\n cached.collapsedGroupIds === this.collapsedGroupIds\n ) {\n return;\n }\n this.virtualItems = flattenTableVirtualItems({\n grouped: model.grouped,\n rows: this.rows,\n groups: this.groups,\n collapsedGroupIds: this.collapsedGroupIds,\n columns,\n });\n if (this.responsiveLayout === 'cards') {\n this.virtualItems = this.virtualItems.map(item => ({ ...item, variableSize: true }));\n }\n this.virtualItemsCache = {\n columns,\n rows: this.rows,\n groups: this.groups,\n grouped: model.grouped,\n collapsedGroupIds: this.collapsedGroupIds,\n };\n }\n\n private shouldWindowRows(model: TableRenderModel): boolean {\n return (\n this.rowWindowingEnabled && (model.hasData || (model.grouped && model.groups.length > 0))\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 const visibleColumns = this.visibleColumns;\n if (!this.caption?.trim()) issues.unshift('A non-empty caption is required.');\n if (this.grouping) {\n const groupingColumn = this.columns.find(column => column.id === this.grouping!.fieldId);\n if (!groupingColumn) {\n issues.push(`Grouping references unknown column id: ${this.grouping.fieldId}`);\n } else if (!visibleColumns.some(column => column.id === groupingColumn.id)) {\n issues.push(`Grouping references hidden column id: ${this.grouping.fieldId}`);\n }\n }\n if (this.sort) {\n const sortColumn = this.columns.find(\n column =>\n column.id === this.sort!.fieldId ||\n column.segments?.some(segment => segment.sortKey === this.sort!.fieldId)\n );\n if (!sortColumn) {\n issues.push(`Sorting references unknown column id: ${this.sort.fieldId}`);\n } else if (!visibleColumns.some(column => column.id === sortColumn.id)) {\n issues.push(`Sorting references hidden column id: ${this.sort.fieldId}`);\n }\n }\n if (\n this.dataMode === 'virtual' &&\n Number.isFinite(this.totalCount) &&\n Math.max(0, Math.trunc(this.totalCount!)) !== this.currentLoadedRowCount()\n ) {\n issues.push('Virtual mode requires totalCount to match the complete supplied row count.');\n }\n const stickyStart = visibleColumns.filter(column => column.sticky === 'start');\n const stickyEnd = visibleColumns.filter(column => column.sticky === 'end');\n issues.push(...this.structure.merges.issues);\n if (this.interactionMode === 'grid' && this.grouped)\n issues.push('Grid interaction requires ungrouped rows.');\n for (const column of [...stickyStart, ...stickyEnd]) {\n if (!tableColumnSize(column))\n issues.push(`Sticky column ${column.id} requires an explicit size.`);\n }\n const message = issues.join(' ');\n if (!message) {\n this.previousModelWarning = '';\n return;\n }\n if (message === this.previousModelWarning) return;\n this.previousModelWarning = message;\n console.warn(`[ds-table] ${message}`);\n }\n\n private scheduleModelIssueWarning(): void {\n if (this.modelWarningQueued) return;\n this.modelWarningQueued = true;\n queueMicrotask(() => {\n this.modelWarningQueued = false;\n this.warnModelIssues();\n });\n }\n\n private scheduleInitialModelIssueWarning(): void {\n if (this.initialModelWarningFrame !== undefined) {\n cancelAnimationFrame(this.initialModelWarningFrame);\n }\n this.initialModelWarningFrame = requestAnimationFrame(() => {\n this.initialModelWarningFrame = undefined;\n this.warnModelIssues();\n });\n }\n\n private emitSort(column: TableColumn, sortKey = column.id): void {\n if (!column.sortable) return;\n this.dsSortChange.emit({ sort: nextDataSortState(this.sort, sortKey) });\n }\n\n private sortButtonLabel(column: TableColumn, sortKey = column.id, label = column.label): string {\n if (this.sort?.fieldId !== 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: DataGroup): 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\"], [popover], ds-button-unfilled, ds-menu'\n );\n });\n }\n\n private groupRowEventOwnsCollapse(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 'input, select, textarea, [role=\"checkbox\"], .ds-table__selection-control, .ds-table__group-selection, ds-tooltip'\n );\n });\n }\n\n private handleGroupRowClick(group: DataGroup, event: Event): void {\n if (!this.groupRowEventOwnsCollapse(event)) return;\n this.emitGroupCollapse(group);\n }\n\n private emitRowActivation(row: TableRow, event: Event): void {\n if (this.editingController.gridEnabled || row.disabled || !this.rowEventOwnsActivation(event))\n return;\n if (this.selectionMode === 'multiple' && this.selectedRowIds.length > 0) {\n if (row.selectable !== false) this.emitRowSelection(row);\n return;\n }\n if (row.interactive) this.dsRowActivate.emit({ rowId: row.id });\n }\n\n private syncActionMenu(): void {\n if (!this.actionMenu) return;\n const row = tableRows(this.rows, this.groups, this.grouped).find(\n candidate => candidate.id === this.actionMenu?.rowId\n );\n const value = row && !row.disabled ? row.cells[this.actionMenu.columnId] : undefined;\n if (!isTableCellAction(value) || !isRenderableTableActionMenu(value)) {\n this.actionMenu = null;\n }\n }\n\n private closeActionMenu(): void {\n this.actionMenu = null;\n }\n\n private handleActionMenuSelect(item: MenuItemData): void {\n const open = this.actionMenu;\n const actionId = item.value;\n if (!open || !actionId || item.isInactive) return;\n this.dsCellAction.emit({\n actionId,\n rowId: open.rowId,\n columnId: open.columnId,\n });\n const triggerId = tableActionTriggerId(this.actionMenuElementId, open.rowId, open.columnId);\n this.closeActionMenu();\n requestAnimationFrame(() => {\n const trigger = this.el.querySelector<HTMLElement & { setFocus?: () => void }>(\n `#${CSS.escape(triggerId)}`\n );\n trigger?.setFocus?.();\n });\n }\n\n private toggleActionMenu(row: TableRow, column: TableColumn, event: MouseEvent): void {\n if (row.disabled) return;\n const open = this.actionMenu?.rowId === row.id && this.actionMenu?.columnId === column.id;\n this.actionMenuInitialFocusVisible = event.detail === 0;\n this.actionMenu = open ? null : { rowId: row.id, columnId: column.id };\n if (this.actionMenu) this.actionMenuSurface = this.actionMenu;\n }\n\n private renderOverflowActionMenu() {\n const open = this.actionMenu ?? this.actionMenuSurface;\n const row = open\n ? tableRows(this.rows, this.groups, this.grouped).find(\n candidate => candidate.id === open.rowId\n )\n : undefined;\n const value = row && open ? row.cells[open.columnId] : undefined;\n const menu = isTableCellAction(value) && isRenderableTableActionMenu(value) ? value : undefined;\n const sections = menu ? tableActionMenuSections(menu.items) : [];\n const triggerId = open\n ? tableActionTriggerId(this.actionMenuElementId, open.rowId, open.columnId)\n : undefined;\n\n return (\n <ds-menu\n id={this.actionMenuElementId}\n open={Boolean(this.actionMenu && sections.length)}\n anchorId={triggerId}\n align=\"end\"\n side=\"bottom\"\n menuLabel={menu?.ariaLabel ?? 'More actions'}\n initialFocusVisible={this.actionMenuInitialFocusVisible}\n sections={sections}\n onDsClose={() => this.closeActionMenu()}\n onDsAfterClose={() => {\n if (!this.actionMenu) this.actionMenuSurface = null;\n }}\n onDsSelect={event => this.handleActionMenuSelect(event.detail)}\n />\n );\n }\n\n private connectTruncateTooltip(): void {\n if (this.truncateTooltipBound) return;\n this.el.addEventListener('pointerover', this.onTruncatePointerOver);\n this.el.addEventListener('pointerout', this.onTruncatePointerOut);\n this.el.addEventListener('focusin', this.onTruncateFocusIn);\n this.el.addEventListener('focusout', this.onTruncateFocusOut);\n this.truncateTooltipBound = true;\n }\n\n private disconnectTruncateTooltip(): void {\n if (!this.truncateTooltipBound) return;\n this.el.removeEventListener('pointerover', this.onTruncatePointerOver);\n this.el.removeEventListener('pointerout', this.onTruncatePointerOut);\n this.el.removeEventListener('focusin', this.onTruncateFocusIn);\n this.el.removeEventListener('focusout', this.onTruncateFocusOut);\n this.truncateTooltipBound = false;\n this.dismissTruncateTooltip();\n }\n\n private onTruncatePointerOver = (event: PointerEvent) => {\n if (event.pointerType === 'touch') return;\n const track = resolveTableTruncateTrack(event.target);\n if (!track) return;\n this.presentOrDismissTruncateTrack(track);\n };\n\n private onTruncatePointerOut = (event: PointerEvent) => {\n if (event.pointerType === 'touch') return;\n if (resolveTableTruncateTrack(event.relatedTarget)) return;\n const next = event.relatedTarget;\n const cell = this.truncateAnchor?.closest('.ds-table__cell');\n if (cell && next instanceof Node && cell.contains(next)) return;\n this.dismissTruncateTooltip();\n };\n\n private onTruncateFocusIn = (event: FocusEvent) => {\n const track = resolveTableTruncateTrack(event.target);\n if (!track) {\n this.dismissTruncateTooltip();\n return;\n }\n this.presentOrDismissTruncateTrack(track);\n };\n\n private onTruncateFocusOut = (event: FocusEvent) => {\n if (resolveTableTruncateTrack(event.relatedTarget)) return;\n this.dismissTruncateTooltip();\n };\n\n private presentOrDismissTruncateTrack(track: HTMLElement): void {\n if (!isElementTruncated(track)) {\n this.dismissTruncateTooltip();\n return;\n }\n const label = tableTruncateLabel(track);\n if (!label) {\n this.dismissTruncateTooltip();\n return;\n }\n this.truncateAnchor = track;\n this.truncateTooltipLabel = label;\n void this.truncateTooltipEl?.presentFrom(track, label);\n }\n\n private dismissTruncateTooltip(): void {\n this.truncateAnchor = null;\n if (!this.truncateTooltipLabel && !this.truncateTooltipEl) return;\n this.truncateTooltipLabel = '';\n void this.truncateTooltipEl?.dismiss();\n }\n\n private renderTruncateTooltip() {\n return (\n <ds-tooltip\n ref={element => {\n this.truncateTooltipEl = element;\n }}\n label={this.truncateTooltipLabel}\n describedBy={false}\n wrapLabel={true}\n size=\"sm\"\n delay={0}\n side=\"top\"\n />\n );\n }\n\n private handleRowKeydown(row: TableRow, event: KeyboardEvent): void {\n if (event.target !== event.currentTarget || (event.key !== 'Enter' && event.key !== ' '))\n return;\n event.preventDefault();\n this.emitRowActivation(row, event);\n }\n\n private onVirtualFocusIn = (event: FocusEvent): void => {\n const target = event.target;\n if (!(target instanceof HTMLElement)) return;\n const row = target.closest('[data-row-id]');\n const id = row?.getAttribute('data-row-id') ?? null;\n if (id === this.focusedRowId) return;\n this.focusedRowId = id;\n if (this.rowWindowingEnabled) this.virtualController.schedule();\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'], shadow = true) {\n if (!sticky) return null;\n return (\n <span\n class={{\n 'ds-table__sticky-edge': true,\n [`ds-table__sticky-edge--${sticky}`]: true,\n 'ds-table__sticky-edge--internal': !shadow,\n }}\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 headerLane?: { start: number; spanning: boolean }\n ) {\n interactive = interactive && !this.chromeLoading;\n const groupedColumn = this.grouping?.fieldId === column.id;\n const previousColumn = headerLane\n ? this.visibleColumns[this.visibleColumns.indexOf(column) - 1]\n : undefined;\n const segments = column.segments?.length\n ? column.segments\n : [{ label: column.label, sortKey: column.id }];\n const activeMemberSegment = segments.find(segment => segment.sortKey === this.sort?.fieldId);\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'\n ? 'ascending'\n : 'descending'\n : undefined;\n\n const help = column.help?.trim();\n const labels = (\n <span\n class=\"ds-table__header-labels\"\n tabIndex={interactive && help && !column.sortable ? 0 : undefined}\n data-header-help={help ? '' : undefined}\n >\n {segments.map((segment, index) => {\n const segmentActive = activeMemberSegment?.sortKey === segment.sortKey;\n const segmentInteractive = interactive && !!column.sortable;\n const text = (\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 decoration={help ? 'dotted-underline' : undefined}\n lineTruncation={1}\n >\n {segment.label}\n </ds-text>\n );\n const label =\n this.chromeLoading && segment.label ? (\n <span class=\"ds-table__loading-label\" aria-hidden=\"true\">\n {text}\n <ds-skeleton textVariant=\"text-caption\" width=\"100%\" />\n </span>\n ) : (\n 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(\n column,\n segment.sortKey,\n segment.dataLabel?.trim() || segment.label\n )}\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 < segments.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 labelControl =\n interactive && help ? (\n <ds-tooltip label={help} side=\"top\" size=\"sm\" wrapLabel={true}>\n {labels}\n </ds-tooltip>\n ) : (\n labels\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.label\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 =\n collapseHost?.mode === 'action' && collapseHost.columnId === column.id;\n const blankActionCollapseHost =\n actionCollapseHost && !column.label.trim() && !column.segments?.length;\n const collapseControl =\n interactive && actionCollapseHost ? (\n <span class=\"ds-table__collapse-slot\">{this.renderCollapseAllButton()}</span>\n ) : null;\n\n return (\n <th\n key={column.id}\n id={presentational ? undefined : this.headerId(column.id)}\n headers={presentational ? undefined : this.columnBandId(column.id)}\n rowSpan={headerLane?.spanning ? 2 : undefined}\n style={{\n ...this.structure.pins.get(column.id)?.style,\n ...(headerLane\n ? {\n gridColumn: String(headerLane.start),\n gridRow: headerLane.spanning ? '1 / span 2' : '2',\n }\n : {}),\n }}\n class={{\n 'ds-table__header-cell': true,\n 'ds-table__header-cell--rowspan': !!headerLane?.spanning,\n 'ds-table__header-cell--after-rowspan':\n !!headerLane &&\n !headerLane.spanning &&\n !!previousColumn &&\n !previousColumn.group &&\n !this.structure.pins.has(previousColumn.id),\n [`ds-table__cell--align-${align}`]: true,\n 'ds-table__cell--sticky-start':\n column.sticky === 'start' && this.structure.pins.has(column.id),\n 'ds-table__cell--sticky-end':\n column.sticky === 'end' && this.structure.pins.has(column.id),\n 'ds-table__header-cell--collapse-all': actionCollapseHost,\n }}\n scope={presentational ? undefined : 'col'}\n aria-label={\n !presentational && this.chromeLoading\n ? column.accessibleLabel?.trim() || segments.map(segment => segment.label).join(' / ')\n : undefined\n }\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.label.trim() && column.accessibleLabel?.trim() && (\n <span class=\"ds-visually-hidden\">{column.accessibleLabel}</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(\n this.structure.pins.has(column.id) ? column.sticky : undefined,\n !!this.structure.pins.get(column.id)?.edge\n )}\n </th>\n );\n }\n\n private renderColgroup(model: TableRenderModel) {\n const beforeSpacer =\n model.elasticSpacerIndex == null\n ? this.visibleColumns\n : this.visibleColumns.slice(0, model.elasticSpacerIndex);\n const afterSpacer =\n model.elasticSpacerIndex == null ? [] : this.visibleColumns.slice(model.elasticSpacerIndex);\n const renderColumn = (column: TableColumn) => {\n const width = tableColumnSize(column);\n return (\n <col\n key={column.id}\n class={{ 'ds-table__action-column': column.kind === 'action' }}\n style={width ? { width } : undefined}\n />\n );\n };\n return (\n <colgroup>\n {model.selectable && <col class=\"ds-table__selection-column\" />}\n {beforeSpacer.map(renderColumn)}\n {model.elasticSpacerIndex != null && <col class=\"ds-table__elastic-spacer-column\" />}\n {afterSpacer.map(renderColumn)}\n </colgroup>\n );\n }\n\n private renderHeader(\n model: TableRenderModel,\n interactive = true,\n presentational = false,\n ariaRowIndex?: number\n ) {\n const selection = model.selection;\n const bands = this.structure.bands;\n const multirow = bands.length > 0;\n const controls =\n interactive &&\n !(\n this.responsiveLayout === 'cards' &&\n this.narrowViewport &&\n this.interactionMode !== 'grid' &&\n !this.cellSpans.length\n );\n const selectionHeader = model.selectable && (\n <th\n class={{\n 'ds-table__header-cell': true,\n 'ds-table__selection-cell': true,\n 'ds-table__cell--sticky-start': true,\n 'ds-table__header-cell--rowspan': multirow,\n }}\n rowSpan={multirow ? 2 : undefined}\n style={multirow ? { gridColumn: '1', gridRow: '1 / span 2' } : undefined}\n scope={presentational ? undefined : 'col'}\n aria-label={this.chromeLoading && !presentational ? 'Select rows' : undefined}\n >\n {this.chromeLoading ? (\n <span class=\"ds-table__skeleton-checkbox-canvas\" aria-hidden=\"true\">\n <ds-skeleton\n class=\"ds-table__skeleton-checkbox\"\n variant=\"control\"\n width=\"var(--dimension-iconography-sm)\"\n />\n </span>\n ) : controls ? (\n 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 ) : (\n <span class=\"ds-visually-hidden\">Select rows</span>\n )}\n {this.renderStickyEdge(\n 'start',\n ![...this.structure.pins.values()].some(pin => pin.edge === 'start')\n )}\n </th>\n );\n const lanes: (TableColumn | undefined)[] = [...this.visibleColumns];\n if (model.elasticSpacerIndex != null) lanes.splice(model.elasticSpacerIndex, 0, undefined);\n const renderLane = (column: TableColumn | undefined, index: number) => {\n const start = index + 1 + Number(model.selectable);\n if (column)\n return this.renderColumnHeader(\n column,\n model,\n controls,\n presentational,\n multirow ? { start, spanning: !column.group } : undefined\n );\n return (\n <th\n class=\"ds-table__header-cell ds-table__elastic-spacer-cell\"\n rowSpan={multirow ? 2 : undefined}\n style={multirow ? { gridColumn: String(start), gridRow: '1 / span 2' } : undefined}\n aria-hidden=\"true\"\n role=\"presentation\"\n data-elastic-spacer=\"true\"\n />\n );\n };\n return (\n <thead\n class={{\n 'ds-table__head': true,\n 'ds-table__head--multirow': multirow,\n 'ds-table__head--semantic-copy': !interactive,\n }}\n ref={element => {\n if (interactive) this.interactiveHeadEl = element ?? null;\n }}\n >\n {multirow && (\n <tr class=\"ds-table__header-row ds-table__super-header-row\" aria-rowindex={ariaRowIndex}>\n {selectionHeader}\n {bands.map(band =>\n !band.label ? (\n lanes.map((column, index) => {\n const start = index + 1 + Number(model.selectable);\n return start >= band.start && start < band.start + band.span\n ? renderLane(column, index)\n : null;\n })\n ) : (\n <th\n key={band.key}\n id={presentational ? undefined : `${this.actionMenuElementId}-${band.key}`}\n class={{\n 'ds-table__header-cell': true,\n 'ds-table__super-header-cell': true,\n 'ds-table__cell--sticky-start': band.sticky === 'start',\n 'ds-table__cell--sticky-end': band.sticky === 'end',\n }}\n scope={presentational || !band.label ? undefined : 'colgroup'}\n aria-label={!presentational && this.chromeLoading ? band.label : undefined}\n colSpan={band.span}\n style={{\n gridColumn: `${band.start} / span ${band.span}`,\n gridRow: '1',\n ...this.structure.pins.get(\n (band.sticky === 'end' ? band.lastColumnId : band.firstColumnId) ?? ''\n )?.style,\n }}\n aria-hidden={!band.label ? 'true' : undefined}\n >\n <span class=\"ds-table__header-content\">\n <span class=\"ds-table__header-labels\">\n <span class=\"ds-table__header-segment\">\n <span class=\"ds-table__header-label ds-table__header-static\">\n <ds-skeleton\n preserveLayout={true}\n isLoading={this.chromeLoading}\n textVariant=\"text-caption\"\n >\n <ds-text\n class=\"ds-table__header-label-box ds-control-label-box\"\n as=\"span\"\n variant=\"text-caption\"\n color=\"inherit\"\n lineTruncation={1}\n >\n {band.label}\n </ds-text>\n </ds-skeleton>\n </span>\n </span>\n </span>\n </span>\n {this.renderStickyEdge(\n band.sticky,\n !!this.structure.pins.get(\n (band.sticky === 'end' ? band.firstColumnId : band.lastColumnId) ?? ''\n )?.edge\n )}\n </th>\n )\n )}\n </tr>\n )}\n <tr\n class=\"ds-table__header-row\"\n aria-rowindex={\n ariaRowIndex == null\n ? undefined\n : ariaRowIndex + Number(this.structure.bands.length > 0)\n }\n >\n {!multirow && selectionHeader}\n {lanes.map((column, index) => (!multirow || column?.group) && renderLane(column, index))}\n </tr>\n </thead>\n );\n }\n\n private renderRow(\n row: TableRow,\n model: TableRenderModel,\n ariaRowIndex?: number,\n variableVirtualSize = false,\n rowKey = row.id\n ) {\n return renderTableRowView({\n grid: this.editingController.gridEnabled,\n cellAttributes: this.editingController.enabled\n ? (targetRow, column) => this.editingController.cellAttributes(targetRow, column)\n : undefined,\n renderEditor: this.editingController.enabled\n ? (targetRow, column) => this.editingController.renderEditor(targetRow, column)\n : undefined,\n renderEditTrigger: this.editingController.enabled\n ? (targetRow, column) => this.editingController.renderEditTrigger(targetRow, column)\n : undefined,\n pins: this.structure.pins,\n spans: this.structure.merges.cells.get(row.id),\n headerId: id => this.headerId(id),\n responsiveCards: this.responsiveLayout === 'cards',\n row,\n model,\n visibleColumns: this.visibleColumns,\n emptyCellLabel: this.emptyCellLabel,\n actionMenuElementId: this.actionMenuElementId,\n actionMenu: this.actionMenu,\n actionMenuSurface: this.actionMenuSurface,\n surfaceOpenRowId: this.surfaceOpenRowId,\n highlightMatcher: this.highlightMatcher,\n highlightFieldIds: this.highlightFieldIds,\n ariaRowIndex:\n ariaRowIndex == null ? undefined : ariaRowIndex + Number(this.structure.bands.length > 0),\n variableVirtualSize,\n intrinsicBlockSize:\n this.dataMode !== 'virtual'\n ? estimateTableRowBlockSize(row, this.visibleColumns)\n : undefined,\n rowKey,\n renderSelectionControl: (label, checked, indeterminate, disabled, onActivate) =>\n this.renderSelectionControl(label, checked, indeterminate, disabled, onActivate),\n renderStickyEdge: (sticky, shadow) => this.renderStickyEdge(sticky, shadow),\n onRowActivate: (targetRow, event) => this.emitRowActivation(targetRow, event),\n onRowKeyDown: (targetRow, event) => this.handleRowKeydown(targetRow, event),\n onRowSelection: targetRow => this.emitRowSelection(targetRow),\n onCellAction: detail => this.dsCellAction.emit(detail),\n onActionMenuToggle: (targetRow, column, event) =>\n this.toggleActionMenu(targetRow, column, event),\n });\n }\n\n private emitGroupCollapse(group: DataGroup) {\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 renderGroupAccessory(\n item: TableRenderModel['groups'][number]['accessories'][number],\n index: number\n ) {\n const text = (\n <ds-text\n key={`acc-${index}`}\n class=\"ds-table__group-accessory\"\n as=\"span\"\n variant=\"text-body-small\"\n color=\"secondary\"\n decoration={item.help ? 'dotted-underline' : undefined}\n lineTruncation={1}\n >\n {item.text}\n </ds-text>\n );\n if (!item.help) return text;\n return (\n <ds-tooltip\n key={`acc-${index}`}\n label={item.help}\n side=\"bottom\"\n align=\"start\"\n size=\"sm\"\n wrapLabel={true}\n >\n <span class=\"ds-table__group-accessory-tip\" tabIndex={0}>\n {text}\n </span>\n </ds-tooltip>\n );\n }\n\n private renderGroupContent(groupModel: TableRenderModel['groups'][number]) {\n const {\n group,\n countLabel,\n collapsed: isCollapsed,\n labelColor,\n selection: groupSelection,\n accessories,\n hero,\n } = groupModel;\n const heroTracks = hero?.kind === 'score' && hero.tracks === 2 ? 2 : 1;\n const heroPresentation = TABLE_GROUP_HERO_SCORE_PRESENTATION[heroTracks];\n return (\n <span\n class={{\n 'ds-table__group-content': true,\n 'ds-table__group-content--multi': accessories.length > 0 || heroTracks === 2,\n 'ds-table__group-content--hero': Boolean(hero),\n 'ds-table__group-content--hero-two-track': Boolean(hero) && heroTracks === 2,\n }}\n onClick={event => this.handleGroupRowClick(group, event)}\n >\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 {hero?.kind === 'score' && (\n <span class=\"ds-table__group-hero\">\n <ds-score\n size={heroPresentation.size}\n variant={heroPresentation.variant}\n value={hero.value}\n level={hero.level}\n label={hero.label ?? 'Safety score'}\n isLoading={hero.isLoading === true}\n />\n </span>\n )}\n <span class=\"ds-table__group-copy\">\n <span class=\"ds-table__group-primary\">\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 <span class=\"ds-visually-hidden\">{countLabel}</span>\n </span>\n {accessories.length > 0 && (\n <span class=\"ds-table__group-accessories\">\n {accessories.flatMap((item, index) => [\n ...(index > 0\n ? [\n <ds-text\n key={`sep-${index}`}\n class=\"ds-table__group-accessory-separator\"\n as=\"span\"\n variant=\"text-body-small\"\n color=\"secondary\"\n aria-hidden=\"true\"\n >\n ·\n </ds-text>,\n ]\n : []),\n this.renderGroupAccessory(item, index),\n ])}\n </span>\n )}\n </span>\n <span class=\"ds-table__group-trailing\">\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 {groupModel.visibleCountText}\n </ds-text>\n <span class=\"ds-table__group-action\">\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 ? `Expand ${group.label} group` : `Collapse ${group.label} group`\n }\n hasBorder={false}\n pressScale={false}\n />\n </span>\n </span>\n </span>\n );\n }\n\n private formatGroupLoadLabel(template: string, group: DataGroup): string {\n return template.split('{group}').join(group.label);\n }\n\n private renderGroupLoadRow(group: DataGroup, totalColumns: number) {\n const error = group.loadMoreError?.trim();\n if (!error && !group.loadingMore && !group.hasMore) return null;\n const manualFallback =\n this.loadMoreMode === 'manual' || !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 {renderTableLoadContent({\n error,\n loading: !!group.loadingMore,\n hasMore: !!group.hasMore,\n manualFallback,\n retryLabel: this.formatGroupLoadLabel(this.groupRetryLabel, group),\n loadingLabel: this.formatGroupLoadLabel(this.groupLoadingMoreLabel, group),\n loadMoreLabel: this.groupLoadMoreLabel,\n loadMoreAriaLabel: this.formatGroupLoadLabel(this.groupLoadMoreAriaLabel, group),\n onRetry: () => this.groupLoadController.request(group.id, 'retry'),\n onLoadMore: () => this.groupLoadController.request(group.id, 'manual'),\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, plan: TableVirtualPlan | null) {\n return this.bodyRenderer.render({\n headerRowCount: 1 + Number(this.structure.bands.length > 0),\n model,\n plan,\n rows: this.rows,\n stickyHeader: this.stickyHeader,\n documentStickyHeader: this.documentStickyHeader,\n activeStickyGroupId: this.activeStickyGroupId,\n renderRow: (row, renderModel, ariaRowIndex, variableVirtualSize, rowKey) =>\n this.renderRow(row, renderModel, ariaRowIndex, variableVirtualSize, rowKey),\n renderGroupContent: groupModel => this.renderGroupContent(groupModel),\n renderGroupLoadRow: (group, totalColumns) => this.renderGroupLoadRow(group, totalColumns),\n });\n }\n\n private renderSkeletonBody(model: TableRenderModel) {\n return renderTableSkeletonBody({\n pins: this.structure.pins,\n model,\n visibleColumns: this.visibleColumns,\n skeletonRows: this.skeletonRows,\n renderStickyEdge: (sticky, shadow) => this.renderStickyEdge(sticky, shadow),\n });\n }\n\n private renderStateBody(kind: 'empty' | 'error' | 'virtual-viewport', totalColumns: number) {\n const error = kind !== 'empty';\n const heading =\n kind === 'virtual-viewport'\n ? TABLE_VIRTUAL_VIEWPORT_REQUIRED_HEADING\n : error\n ? this.errorHeading\n : this.emptyHeading;\n const body =\n kind === 'virtual-viewport'\n ? TABLE_VIRTUAL_VIEWPORT_REQUIRED_BODY\n : error\n ? this.errorBody\n : this.emptyBody;\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 ? 'ExclamationTriangle' : 'Inbox'}\n heading={heading}\n body={body}\n >\n {kind === 'error' && this.errorActionsPresent && (\n <div class=\"ds-table__error-actions\" slot=\"actions\">\n <slot name=\"error-actions\" />\n </div>\n )}\n </ds-empty-state>\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 )\n return null;\n const error = this.loadMoreError?.trim();\n const manualFallback =\n this.loadMoreMode === 'manual' || !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 {renderTableLoadContent({\n error,\n loading: this.loadingMore,\n hasMore: this.hasMore,\n manualFallback,\n retryLabel: this.retryLabel,\n loadingLabel: this.loadingMoreLabel,\n loadMoreLabel: this.loadMoreLabel,\n endOfResultsLabel: this.endOfResultsLabel,\n onRetry: () => this.loadController.request('retry'),\n onLoadMore: () => this.loadController.request('manual'),\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 class=\"ds-table__document-sticky-header\">\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 if (this.dataMode === 'virtual') {\n return formatTableTotalSummary(this.currentLoadedRowCount(), this.resultTotalSummaryLabel);\n }\n return formatTableResultSummary(this.displayedCount, this.totalCount, this.resultSummaryLabel);\n }\n\n private get hasResultFooter(): boolean {\n return (\n (this.dataMode === 'pagination' && !!this.pagination) ||\n !!this.resultSummary ||\n this.footerSlotPresence !== 0\n );\n }\n\n private renderResultFooter() {\n const summary = this.resultSummary;\n const hasLeading = (this.footerSlotPresence & TABLE_FOOTER_SLOT_LEADING) !== 0;\n const hasCopy = (this.footerSlotPresence & TABLE_FOOTER_SLOT_COPY) !== 0;\n const hasTrailing = (this.footerSlotPresence & TABLE_FOOTER_SLOT_TRAILING) !== 0;\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\" onSlotchange={this.syncFooterSlotPresence} />\n </div>\n )}\n {hasCopy ? (\n <slot name=\"footer\" onSlotchange={this.syncFooterSlotPresence} />\n ) : summary ? (\n <ds-skeleton\n class=\"ds-table__footer-summary-skeleton\"\n preserveLayout={true}\n isLoading={this.chromeLoading}\n >\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 lineTruncation={1}\n >\n {summary}\n </ds-text>\n </ds-skeleton>\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 {...{ fitToPageInactive: pagination.fitToPageInactive ?? 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 showFirstLastButtons={pagination.showFirstLastButtons ?? false}\n loading={this.loading}\n chromeLoading={this.chromeLoading}\n onDsChange={(event: CustomEvent<PaginationChangeDetail>) =>\n this.dsPaginationChange.emit(event.detail)\n }\n />\n )}\n <slot name=\"footer-trailing\" onSlotchange={this.syncFooterSlotPresence} />\n </div>\n )}\n </div>\n );\n }\n\n render() {\n this.groupSentinelEls.clear();\n const model = this.createRenderModel();\n this.syncVirtualItems(model);\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 windowRows = this.shouldWindowRows(model);\n const virtualViewportMissing =\n this.virtualViewportMissing && !initialLoading && (model.hasData || hasGroupedStructure);\n const virtualPlan = windowRows\n ? (this.virtualWindow ??\n resolveTableVirtualPlan({\n items: this.virtualItems,\n sizes: this.virtualItems.map(item => this.virtualController.sizeFor(item)),\n scrollOffset: this.viewportEl?.scrollTop ?? 0,\n viewportSize: this.estimateVirtualViewportSize(),\n pinnedRowIds: this.virtualPinnedRowIds(),\n }))\n : null;\n const groupLoadingMore = hasGroupedStructure && this.groups.some(group => group.loadingMore);\n const viewportStyle =\n !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) => {\n this.editingController.handleKeyDown(event);\n this.handlePaginationKeyDown(event);\n }}\n onCopy={(event: ClipboardEvent) => this.editingController.handleClipboard(event)}\n onPaste={(event: ClipboardEvent) => this.editingController.handleClipboard(event)}\n onFocusin={this.onVirtualFocusIn}\n >\n <div\n 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--chrome-edge-to-edge': this.chromeLayout === 'edge-to-edge',\n 'ds-table--super-headers': this.structure.bands.length > 0,\n 'ds-table--responsive-cards':\n this.responsiveLayout === 'cards' &&\n this.interactionMode !== 'grid' &&\n this.cellSpans.length === 0,\n 'ds-table--cards-active':\n this.narrowViewport &&\n this.responsiveLayout === 'cards' &&\n this.interactionMode !== 'grid' &&\n this.cellSpans.length === 0,\n 'ds-table--footer-visible': this.hasResultFooter,\n 'ds-table--state-fill':\n !initialLoading &&\n (initialError || virtualViewportMissing || !(model.hasData || hasGroupedStructure)),\n }}\n ref={element => {\n this.rootEl = element ?? null;\n }}\n >\n {this.captionController.renderBar()}\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 data-narrow={String(this.narrowViewport)}\n ref={element => {\n this.frameEl = element ?? null;\n }}\n >\n {this.alternateView ? <slot name=\"alternate-view\" /> : null}\n {!this.alternateView && this.renderDocumentStickyHeader(model)}\n {!this.alternateView && this.renderStickyGroup(model)}\n {!this.alternateView &&\n !this.documentStickyHeader &&\n this.renderFloatingCollapseAll(model)}\n {!this.alternateView && (\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--deferred-rows':\n this.dataMode !== 'virtual' && !this.structure.merges.hasRowSpans,\n 'ds-table__table--windowed': windowRows,\n 'ds-table__table--virtual': this.dataMode === 'virtual' && windowRows,\n 'ds-table__table--native-group-sticky':\n (model.grouped && this.stickyHeader && !this.documentStickyHeader) ||\n windowRows,\n }}\n style={model.tableStyle}\n role={this.editingController.gridEnabled ? 'grid' : 'table'}\n aria-multiselectable={this.editingController.gridEnabled ? 'true' : undefined}\n aria-rowcount={\n windowRows\n ? 1 + Number(this.structure.bands.length > 0) + this.virtualItems.length\n : undefined\n }\n aria-busy={\n initialLoading ||\n (this.dataMode === 'infinite' && (this.loadingMore || groupLoadingMore))\n ? 'true'\n : undefined\n }\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(\n model,\n !this.documentStickyHeader,\n false,\n windowRows ? 1 : undefined\n )}\n {initialLoading\n ? this.renderSkeletonBody(model)\n : initialError\n ? this.renderStateBody('error', model.totalColumns)\n : virtualViewportMissing\n ? this.renderStateBody('virtual-viewport', model.totalColumns)\n : model.hasData || hasGroupedStructure\n ? this.renderDataBodies(model, virtualPlan)\n : this.renderStateBody('empty', model.totalColumns)}\n {!initialLoading &&\n !initialError &&\n this.dataMode !== 'virtual' &&\n this.renderLazyBody(model)}\n </table>\n </div>\n )}\n </div>\n {this.renderResultFooter()}\n {this.renderOverflowActionMenu()}\n {this.captionController.renderMenus()}\n {this.renderTruncateTooltip()}\n <div class=\"ds-visually-hidden\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">\n {this.announcement}\n </div>\n </div>\n <div class=\"ds-table__parked-actions\" />\n </Host>\n );\n }\n}\n",
1718
1729
  "type": "registry:ui"
1719
1730
  },
1720
1731
  {