@eunomia/elements 1.0.0-beta.4 → 1.0.0-beta.6

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 (496) hide show
  1. package/README.md +1 -1
  2. package/dist/breakpoints.d.ts +37 -10
  3. package/dist/breakpoints.js +38 -10
  4. package/dist/cdn/components/chunks/{chunk-MMLIPCOY.js → chunk-22D3SRFR.js} +1 -1
  5. package/dist/cdn/components/chunks/{chunk-4MXL6PZC.js → chunk-2Y2EU2CI.js} +28 -28
  6. package/dist/cdn/components/chunks/chunk-3MTUVMM5.js +1 -0
  7. package/dist/cdn/components/chunks/chunk-4PI6RDUS.js +1 -0
  8. package/dist/cdn/components/chunks/chunk-5GKM5DIB.js +1 -0
  9. package/dist/cdn/components/chunks/chunk-624VJYNA.js +1 -0
  10. package/dist/cdn/components/chunks/{chunk-5XJLIHAU.js → chunk-7KKWYPHY.js} +4 -2
  11. package/dist/cdn/components/chunks/chunk-A3X4KD2X.js +1 -0
  12. package/dist/cdn/components/chunks/chunk-BKZGTCMF.js +1 -0
  13. package/dist/cdn/components/chunks/{chunk-GW3VP2L7.js → chunk-BO3JOEWM.js} +1 -1
  14. package/dist/cdn/components/chunks/{chunk-NHFWATVN.js → chunk-BQFJR2UP.js} +5 -5
  15. package/dist/cdn/components/chunks/chunk-EUWUDCBZ.js +1 -0
  16. package/dist/cdn/components/chunks/{chunk-P32B7BFD.js → chunk-FF3I7C3C.js} +15 -15
  17. package/dist/cdn/components/chunks/{chunk-74AOAB4X.js → chunk-FOILAXLL.js} +6 -5
  18. package/dist/cdn/components/chunks/{chunk-E4IS25H7.js → chunk-G5SBDQQ3.js} +1 -1
  19. package/dist/cdn/components/chunks/{chunk-LGLQ36QB.js → chunk-GDBPWZN6.js} +1 -1
  20. package/dist/cdn/components/chunks/{chunk-S2UCEWNG.js → chunk-JYIIWN2P.js} +13 -11
  21. package/dist/cdn/components/chunks/{chunk-EOWBYZV7.js → chunk-KDN4O5W2.js} +26 -13
  22. package/dist/cdn/components/chunks/chunk-KSZRQRN4.js +1 -0
  23. package/dist/cdn/components/chunks/{chunk-PED5ATYN.js → chunk-L2ADCEN7.js} +1 -1
  24. package/dist/cdn/components/chunks/chunk-LMVW522V.js +1 -0
  25. package/dist/cdn/components/chunks/{chunk-5XJGYXW7.js → chunk-MOMYII3P.js} +1 -1
  26. package/dist/cdn/components/chunks/{chunk-SIHZFOTK.js → chunk-MQQ4BGVJ.js} +5 -5
  27. package/dist/cdn/components/chunks/{chunk-A4EQSU36.js → chunk-O7PHBLH2.js} +1 -1
  28. package/dist/cdn/components/chunks/chunk-PD62MBOJ.js +1 -0
  29. package/dist/cdn/components/chunks/{chunk-KBH5PCUO.js → chunk-PE4DAXHQ.js} +1 -1
  30. package/dist/cdn/components/chunks/{chunk-GSJEJLFA.js → chunk-PMPSJG5V.js} +7 -7
  31. package/dist/cdn/components/chunks/{chunk-5EVP7YNE.js → chunk-PPCHQG7P.js} +1 -1
  32. package/dist/cdn/components/chunks/{chunk-EJIX2D4Z.js → chunk-QZ5SHR3I.js} +15 -11
  33. package/dist/cdn/components/chunks/chunk-RS7TWEMQ.js +1 -0
  34. package/dist/cdn/components/chunks/chunk-SMTIDNGW.js +1 -0
  35. package/dist/cdn/components/chunks/{chunk-YGUJJ25C.js → chunk-SZYEBMG6.js} +11 -10
  36. package/dist/cdn/components/chunks/chunk-UDVB4LAE.js +56 -0
  37. package/dist/cdn/components/chunks/{chunk-UAGCQF23.js → chunk-UYZR4JXZ.js} +17 -12
  38. package/dist/cdn/components/chunks/chunk-VG6NAAIV.js +1 -0
  39. package/dist/cdn/components/chunks/chunk-VJCNHMQZ.js +10 -0
  40. package/dist/cdn/components/chunks/chunk-VOTRXBDS.js +38 -0
  41. package/dist/cdn/components/chunks/{chunk-BMQOI7HX.js → chunk-VYFR364B.js} +128 -74
  42. package/dist/cdn/components/chunks/{chunk-IJ5WFMK4.js → chunk-XDPRWUKN.js} +7 -6
  43. package/dist/cdn/components/chunks/{chunk-XH6NREVZ.js → chunk-YITWLCD3.js} +1 -1
  44. package/dist/cdn/components/chunks/{chunk-BQHYZB2W.js → chunk-ZBN4HTWP.js} +1 -1
  45. package/dist/cdn/components/chunks/{chunk-RJCJXB7Y.js → chunk-ZKZV2YG5.js} +18 -18
  46. package/dist/cdn/components/eun-alert.js +24 -16
  47. package/dist/cdn/components/eun-animate.js +12 -6
  48. package/dist/cdn/components/eun-badge.js +7 -7
  49. package/dist/cdn/components/eun-breadcrumb.js +25 -41
  50. package/dist/cdn/components/eun-button.js +1 -1
  51. package/dist/cdn/components/eun-calendar.js +68 -66
  52. package/dist/cdn/components/eun-card.js +67 -13
  53. package/dist/cdn/components/eun-carousel.js +66 -35
  54. package/dist/cdn/components/eun-check-card.js +13 -6
  55. package/dist/cdn/components/eun-checkbox-list.js +4 -4
  56. package/dist/cdn/components/eun-checkbox.js +1 -1
  57. package/dist/cdn/components/eun-chip.js +1 -1
  58. package/dist/cdn/components/eun-collapse.js +87 -39
  59. package/dist/cdn/components/eun-color-picker.js +54 -53
  60. package/dist/cdn/components/eun-counter.js +14 -13
  61. package/dist/cdn/components/eun-date-picker.js +66 -63
  62. package/dist/cdn/components/eun-description-list.js +152 -0
  63. package/dist/cdn/components/eun-divider.js +1 -1
  64. package/dist/cdn/components/eun-drawer.js +26 -12
  65. package/dist/cdn/components/eun-dropdown-button.js +1 -1
  66. package/dist/cdn/components/eun-dropdown-item.js +6 -5
  67. package/dist/cdn/components/eun-fab.js +20 -19
  68. package/dist/cdn/components/eun-icon.js +1 -1
  69. package/dist/cdn/components/eun-image-card.js +79 -22
  70. package/dist/cdn/components/eun-image-carousel.js +92 -47
  71. package/dist/cdn/components/eun-infinite-list.js +23 -11
  72. package/dist/cdn/components/eun-input-file.js +14 -13
  73. package/dist/cdn/components/eun-input-phone.js +26 -26
  74. package/dist/cdn/components/eun-input.js +1 -1
  75. package/dist/cdn/components/eun-label.js +4 -4
  76. package/dist/cdn/components/eun-link.js +174 -27
  77. package/dist/cdn/components/eun-loader.js +1 -1
  78. package/dist/cdn/components/eun-modal.js +29 -22
  79. package/dist/cdn/components/eun-pagination.js +17 -12
  80. package/dist/cdn/components/eun-password-strength.js +6 -2
  81. package/dist/cdn/components/eun-progress-bar.js +13 -5
  82. package/dist/cdn/components/eun-radio-card.js +16 -9
  83. package/dist/cdn/components/eun-radio-list.js +4 -4
  84. package/dist/cdn/components/eun-radio.js +1 -1
  85. package/dist/cdn/components/eun-scheduler-event-editor.js +8 -8
  86. package/dist/cdn/components/eun-scheduler-event.js +1 -1
  87. package/dist/cdn/components/eun-scheduler.js +42 -42
  88. package/dist/cdn/components/eun-search.js +21 -20
  89. package/dist/cdn/components/eun-select.js +12 -11
  90. package/dist/cdn/components/eun-sidebar.js +34 -20
  91. package/dist/cdn/components/eun-skeleton.js +1 -1
  92. package/dist/cdn/components/eun-slider.js +15 -14
  93. package/dist/cdn/components/eun-sliding-card.js +8 -4
  94. package/dist/cdn/components/eun-step.js +17 -6
  95. package/dist/cdn/components/eun-stepper.js +17 -6
  96. package/dist/cdn/components/eun-switch.js +10 -8
  97. package/dist/cdn/components/eun-tab-group.js +22 -17
  98. package/dist/cdn/components/eun-tab-panel.js +11 -3
  99. package/dist/cdn/components/eun-tab.js +1 -1
  100. package/dist/cdn/components/eun-table-action-bar.js +1 -1
  101. package/dist/cdn/components/eun-table.js +572 -31
  102. package/dist/cdn/components/eun-tag.js +13 -7
  103. package/dist/cdn/components/eun-textarea.js +24 -24
  104. package/dist/cdn/components/eun-time-picker.js +21 -19
  105. package/dist/cdn/components/eun-toast.js +36 -16
  106. package/dist/cdn/components/eun-toggle-group.js +64 -37
  107. package/dist/cdn/components/eun-toggle-item.js +24 -21
  108. package/dist/cdn/components/eun-toggle.js +12 -10
  109. package/dist/cdn/components/eun-toolbar.js +62 -52
  110. package/dist/cdn/components/eun-tooltip.js +30 -14
  111. package/dist/cdn/styles/breakpoints.scss +8 -0
  112. package/dist/cdn/styles/eunomia-grid.scss +6 -0
  113. package/dist/cdn/styles/eunomia-themes.css +1 -1
  114. package/dist/cdn/styles/themes/eunomia-theme.css +1 -1
  115. package/dist/cdn/styles/themes/eunomia-theme.scss +4 -3
  116. package/dist/cdn/styles/themes/mixins/eunomia-a11y-color.mixin.scss +34 -1
  117. package/dist/cdn/styles/themes/mixins/eunomia-neutrals.mixin.scss +4 -0
  118. package/dist/cdn/styles/themes/mixins/eunomia-states.mixin.scss +36 -12
  119. package/dist/cdn/styles/themes/prebuilt-themes/eunomia/eunomia-dark.mixin.scss +10 -10
  120. package/dist/cdn/styles/themes/prebuilt-themes/eunomia/eunomia.mixin.scss +10 -10
  121. package/dist/cdn/styles/themes/prebuilt-themes/eunomia/eunomia.theme.css +1 -1
  122. package/dist/cdn/styles/themes/prebuilt-themes/eunomia/eunomia.theme.scss +3 -2
  123. package/dist/cdn/styles/themes/prebuilt-themes/fire/eunomia-fire-dark.mixin.scss +10 -10
  124. package/dist/cdn/styles/themes/prebuilt-themes/fire/eunomia-fire.mixin.scss +10 -10
  125. package/dist/cdn/styles/themes/prebuilt-themes/fire/eunomia-fire.theme.css +1 -1
  126. package/dist/cdn/styles/themes/prebuilt-themes/fire/eunomia-fire.theme.scss +3 -2
  127. package/dist/cdn/styles/themes/prebuilt-themes/forest/eunomia-forest-dark.mixin.scss +10 -10
  128. package/dist/cdn/styles/themes/prebuilt-themes/forest/eunomia-forest.mixin.scss +10 -10
  129. package/dist/cdn/styles/themes/prebuilt-themes/forest/eunomia-forest.theme.css +1 -1
  130. package/dist/cdn/styles/themes/prebuilt-themes/forest/eunomia-forest.theme.scss +3 -2
  131. package/dist/cdn/styles/themes/prebuilt-themes/gold/eunomia-gold-dark.mixin.scss +10 -10
  132. package/dist/cdn/styles/themes/prebuilt-themes/gold/eunomia-gold.mixin.scss +10 -10
  133. package/dist/cdn/styles/themes/prebuilt-themes/gold/eunomia-gold.theme.css +1 -1
  134. package/dist/cdn/styles/themes/prebuilt-themes/gold/eunomia-gold.theme.scss +3 -2
  135. package/dist/cdn/styles/themes/prebuilt-themes/ocean/eunomia-ocean-dark.mixin.scss +10 -10
  136. package/dist/cdn/styles/themes/prebuilt-themes/ocean/eunomia-ocean.mixin.scss +10 -10
  137. package/dist/cdn/styles/themes/prebuilt-themes/ocean/eunomia-ocean.theme.css +1 -1
  138. package/dist/cdn/styles/themes/prebuilt-themes/ocean/eunomia-ocean.theme.scss +3 -2
  139. package/dist/cdn/styles/themes/prebuilt-themes/salmon/eunomia-salmon-dark.mixin.scss +10 -10
  140. package/dist/cdn/styles/themes/prebuilt-themes/salmon/eunomia-salmon.mixin.scss +10 -10
  141. package/dist/cdn/styles/themes/prebuilt-themes/salmon/eunomia-salmon.theme.css +1 -1
  142. package/dist/cdn/styles/themes/prebuilt-themes/salmon/eunomia-salmon.theme.scss +3 -2
  143. package/dist/cdn/styles/themes/prebuilt-themes/violet/eunomia-violet-dark.mixin.scss +10 -10
  144. package/dist/cdn/styles/themes/prebuilt-themes/violet/eunomia-violet.mixin.scss +10 -10
  145. package/dist/cdn/styles/themes/prebuilt-themes/violet/eunomia-violet.theme.css +1 -1
  146. package/dist/cdn/styles/themes/prebuilt-themes/violet/eunomia-violet.theme.scss +3 -2
  147. package/dist/cdn/styles/tools/indexes/eunomia-media-queries.index.scss +1 -3
  148. package/dist/cdn/styles/tools/indexes/eunomia-tools.index.scss +1 -0
  149. package/dist/cdn/styles/tools/mixins/layout/eunomia-grid.mixin.scss +117 -53
  150. package/dist/cdn/styles/tools/mixins/media-queries/eunomia-media-queries.mixin.scss +69 -0
  151. package/dist/cdn/styles/tools/mixins/settings/eunomia-alpha.mixin.scss +28 -4
  152. package/dist/cdn/styles/tools/mixins/settings/eunomia-motion.mixin.scss +9 -0
  153. package/dist/cdn/styles/tools/variables/eunomia.breakpoints.scss +4 -0
  154. package/dist/components/action/button/button.d.ts +51 -19
  155. package/dist/components/action/button/button.js +107 -24
  156. package/dist/components/action/button/button.styles.js +115 -62
  157. package/dist/components/action/dropdown-button/dropdown-button.d.ts +10 -10
  158. package/dist/components/action/dropdown-button/dropdown-button.js +18 -17
  159. package/dist/components/action/dropdown-button/dropdown-button.styles.js +3 -2
  160. package/dist/components/action/dropdown-button/dropdown-item.js +5 -6
  161. package/dist/components/action/dropdown-button/dropdown-item.styles.js +3 -2
  162. package/dist/components/action/fab/fab.d.ts +7 -6
  163. package/dist/components/action/fab/fab.js +11 -8
  164. package/dist/components/action/fab/fab.styles.js +18 -17
  165. package/dist/components/action/link/link.d.ts +72 -9
  166. package/dist/components/action/link/link.js +183 -23
  167. package/dist/components/action/link/link.styles.js +146 -10
  168. package/dist/components/action/shared-styles/button-palette.styles.d.ts +3 -3
  169. package/dist/components/action/shared-styles/button-palette.styles.js +29 -29
  170. package/dist/components/action/toggle/toggle-group.d.ts +17 -5
  171. package/dist/components/action/toggle/toggle-group.js +46 -19
  172. package/dist/components/action/toggle/toggle-group.styles.d.ts +4 -1
  173. package/dist/components/action/toggle/toggle-group.styles.js +56 -27
  174. package/dist/components/action/toggle/toggle-item.d.ts +4 -4
  175. package/dist/components/action/toggle/toggle-item.js +2 -2
  176. package/dist/components/action/toggle/toggle-item.styles.js +20 -17
  177. package/dist/components/action/toggle/toggle.d.ts +2 -0
  178. package/dist/components/action/toggle/toggle.js +2 -0
  179. package/dist/components/action/toggle/toggle.styles.d.ts +2 -0
  180. package/dist/components/action/toggle/toggle.styles.js +12 -8
  181. package/dist/components/alert/alert/alert.d.ts +2 -0
  182. package/dist/components/alert/alert/alert.js +2 -0
  183. package/dist/components/alert/alert/alert.styles.js +23 -13
  184. package/dist/components/alert/drawer/drawer.d.ts +3 -1
  185. package/dist/components/alert/drawer/drawer.js +5 -3
  186. package/dist/components/alert/drawer/drawer.styles.js +25 -9
  187. package/dist/components/alert/modal/modal.d.ts +11 -9
  188. package/dist/components/alert/modal/modal.js +15 -13
  189. package/dist/components/alert/modal/modal.styles.js +16 -7
  190. package/dist/components/alert/toast/toast.d.ts +2 -0
  191. package/dist/components/alert/toast/toast.js +2 -0
  192. package/dist/components/alert/toast/toast.styles.js +37 -15
  193. package/dist/components/content/badge/badge.d.ts +6 -6
  194. package/dist/components/content/badge/badge.js +4 -4
  195. package/dist/components/content/badge/badge.styles.js +4 -4
  196. package/dist/components/content/card/card.d.ts +39 -7
  197. package/dist/components/content/card/card.js +71 -7
  198. package/dist/components/content/card/card.styles.js +58 -11
  199. package/dist/components/content/card/image-card.d.ts +62 -7
  200. package/dist/components/content/card/image-card.js +121 -11
  201. package/dist/components/content/card/image-card.styles.js +66 -15
  202. package/dist/components/content/card/sliding-card.d.ts +4 -1
  203. package/dist/components/content/card/sliding-card.js +3 -0
  204. package/dist/components/content/card/sliding-card.styles.js +5 -1
  205. package/dist/components/content/collapse/collapse.d.ts +39 -12
  206. package/dist/components/content/collapse/collapse.js +65 -25
  207. package/dist/components/content/collapse/collapse.styles.d.ts +10 -1
  208. package/dist/components/content/collapse/collapse.styles.js +69 -18
  209. package/dist/components/content/description-list/description-list.d.ts +87 -0
  210. package/dist/components/content/description-list/description-list.js +107 -0
  211. package/dist/components/content/description-list/description-list.styles.d.ts +1 -0
  212. package/dist/components/content/description-list/description-list.styles.js +185 -0
  213. package/dist/components/content/icon/icon.d.ts +14 -2
  214. package/dist/components/content/icon/icon.js +54 -22
  215. package/dist/components/content/icon/icon.style.d.ts +2 -0
  216. package/dist/components/content/icon/icon.style.js +8 -2
  217. package/dist/components/content/image-carousel/image-carousel.d.ts +30 -19
  218. package/dist/components/content/image-carousel/image-carousel.js +48 -45
  219. package/dist/components/content/image-carousel/image-carousel.styles.js +69 -30
  220. package/dist/components/content/password-strength/password-strength.styles.js +5 -1
  221. package/dist/components/content/progress-bar/progress-bar.styles.js +12 -4
  222. package/dist/components/content/shared-styles/card-link.styles.js +5 -3
  223. package/dist/components/content/shared-styles/tone-surface.styles.d.ts +5 -0
  224. package/dist/components/content/shared-styles/tone-surface.styles.js +19 -14
  225. package/dist/components/content/tag/tag.d.ts +4 -0
  226. package/dist/components/content/tag/tag.js +4 -0
  227. package/dist/components/content/tag/tag.styles.js +14 -4
  228. package/dist/components/content/tooltip/tooltip.d.ts +3 -2
  229. package/dist/components/content/tooltip/tooltip.js +9 -16
  230. package/dist/components/content/tooltip/tooltip.styles.js +25 -7
  231. package/dist/components/form/checkbox/check-card.d.ts +4 -1
  232. package/dist/components/form/checkbox/check-card.js +4 -1
  233. package/dist/components/form/checkbox/check-card.styles.js +13 -4
  234. package/dist/components/form/checkbox/checkbox-list.d.ts +4 -1
  235. package/dist/components/form/checkbox/checkbox-list.js +11 -7
  236. package/dist/components/form/checkbox/checkbox.d.ts +4 -1
  237. package/dist/components/form/checkbox/checkbox.js +4 -1
  238. package/dist/components/form/checkbox/checkbox.styles.js +2 -2
  239. package/dist/components/form/color-picker/color-picker.d.ts +10 -4
  240. package/dist/components/form/color-picker/color-picker.js +23 -25
  241. package/dist/components/form/color-picker/color-picker.styles.js +6 -5
  242. package/dist/components/form/counter/counter.d.ts +7 -4
  243. package/dist/components/form/counter/counter.js +8 -5
  244. package/dist/components/form/counter/counter.styles.js +10 -9
  245. package/dist/components/form/date-picker/date-picker.d.ts +40 -36
  246. package/dist/components/form/date-picker/date-picker.js +35 -45
  247. package/dist/components/form/date-picker/date-picker.styles.js +13 -10
  248. package/dist/components/form/input/input.d.ts +10 -4
  249. package/dist/components/form/input/input.js +12 -6
  250. package/dist/components/form/input-file/input-file.d.ts +4 -1
  251. package/dist/components/form/input-file/input-file.js +8 -5
  252. package/dist/components/form/input-file/input-file.styles.js +3 -2
  253. package/dist/components/form/input-phone/input-phone.d.ts +11 -5
  254. package/dist/components/form/input-phone/input-phone.js +16 -20
  255. package/dist/components/form/input-phone/input-phone.styles.js +16 -16
  256. package/dist/components/form/label/label.styles.js +3 -3
  257. package/dist/components/form/radio/radio-card.d.ts +5 -2
  258. package/dist/components/form/radio/radio-card.js +5 -2
  259. package/dist/components/form/radio/radio-card.styles.js +14 -5
  260. package/dist/components/form/radio/radio-list.d.ts +5 -2
  261. package/dist/components/form/radio/radio-list.js +14 -10
  262. package/dist/components/form/radio/radio.d.ts +5 -2
  263. package/dist/components/form/radio/radio.js +5 -2
  264. package/dist/components/form/radio/radio.styles.js +4 -4
  265. package/dist/components/form/search/search.d.ts +2 -1
  266. package/dist/components/form/search/search.js +6 -15
  267. package/dist/components/form/search/search.styles.js +8 -7
  268. package/dist/components/form/select/select.d.ts +12 -5
  269. package/dist/components/form/select/select.js +18 -21
  270. package/dist/components/form/select/select.styles.js +3 -2
  271. package/dist/components/form/shared-styles/card.styles.js +5 -4
  272. package/dist/components/form/shared-styles/field.styles.js +21 -11
  273. package/dist/components/form/shared-styles/form-labels.styles.js +1 -1
  274. package/dist/components/form/slider/slider.d.ts +4 -1
  275. package/dist/components/form/slider/slider.js +5 -2
  276. package/dist/components/form/slider/slider.styles.js +8 -7
  277. package/dist/components/form/switch/switch.d.ts +4 -1
  278. package/dist/components/form/switch/switch.js +4 -1
  279. package/dist/components/form/switch/switch.styles.js +7 -5
  280. package/dist/components/form/textarea/textarea.d.ts +10 -4
  281. package/dist/components/form/textarea/textarea.js +18 -12
  282. package/dist/components/form/time-picker/time-picker-columns.styles.js +4 -2
  283. package/dist/components/form/time-picker/time-picker-list.styles.js +2 -1
  284. package/dist/components/form/time-picker/time-picker.d.ts +9 -5
  285. package/dist/components/form/time-picker/time-picker.js +25 -29
  286. package/dist/components/form/time-picker/time-picker.styles.js +9 -8
  287. package/dist/components/layout/animate/animate.d.ts +41 -11
  288. package/dist/components/layout/animate/animate.js +48 -16
  289. package/dist/components/layout/animate/animate.styles.js +9 -16
  290. package/dist/components/layout/infinite-list/infinite-list.d.ts +33 -10
  291. package/dist/components/layout/infinite-list/infinite-list.js +115 -18
  292. package/dist/components/layout/infinite-list/infinite-list.styles.d.ts +3 -0
  293. package/dist/components/layout/infinite-list/infinite-list.styles.js +17 -5
  294. package/dist/components/loader/loader/loader.styles.js +3 -1
  295. package/dist/components/navigation/breadcrumb/breadcrumb.d.ts +31 -45
  296. package/dist/components/navigation/breadcrumb/breadcrumb.js +111 -77
  297. package/dist/components/navigation/breadcrumb/breadcrumb.styles.d.ts +3 -0
  298. package/dist/components/navigation/breadcrumb/breadcrumb.styles.js +22 -26
  299. package/dist/components/navigation/carousel/carousel.d.ts +30 -10
  300. package/dist/components/navigation/carousel/carousel.js +37 -18
  301. package/dist/components/navigation/carousel/carousel.styles.js +60 -20
  302. package/dist/components/navigation/pagination/pagination.styles.js +8 -3
  303. package/dist/components/navigation/sidebar/sidebar.d.ts +2 -0
  304. package/dist/components/navigation/sidebar/sidebar.js +2 -0
  305. package/dist/components/navigation/sidebar/sidebar.styles.js +27 -11
  306. package/dist/components/navigation/stepper/step.styles.js +16 -5
  307. package/dist/components/navigation/stepper/stepper.styles.js +16 -5
  308. package/dist/components/navigation/tabs/tab/tab.d.ts +11 -0
  309. package/dist/components/navigation/tabs/tab/tab.js +11 -0
  310. package/dist/components/navigation/tabs/tab/tab.styles.js +22 -16
  311. package/dist/components/navigation/tabs/tab-group/tab-group.js +7 -8
  312. package/dist/components/navigation/tabs/tab-group/tab-group.styles.js +19 -14
  313. package/dist/components/navigation/tabs/tab-panel/tab-panel.d.ts +4 -0
  314. package/dist/components/navigation/tabs/tab-panel/tab-panel.js +4 -0
  315. package/dist/components/navigation/tabs/tab-panel/tab-panel.styles.js +18 -1
  316. package/dist/components/navigation/toolbar/toolbar.d.ts +17 -49
  317. package/dist/components/navigation/toolbar/toolbar.js +23 -71
  318. package/dist/components/navigation/toolbar/toolbar.styles.js +59 -36
  319. package/dist/events/table-row-click.event.d.ts +5 -4
  320. package/dist/events/table-row-click.event.js +10 -5
  321. package/dist/features/scheduler/calendar/calendar.d.ts +12 -12
  322. package/dist/features/scheduler/calendar/calendar.js +15 -14
  323. package/dist/features/scheduler/calendar/calendar.styles.js +8 -6
  324. package/dist/features/scheduler/scheduler/scheduler.d.ts +7 -7
  325. package/dist/features/scheduler/scheduler/scheduler.js +21 -35
  326. package/dist/features/scheduler/scheduler/scheduler.styles.js +6 -4
  327. package/dist/features/scheduler/scheduler-event/scheduler-event.d.ts +12 -8
  328. package/dist/features/scheduler/scheduler-event/scheduler-event.js +9 -7
  329. package/dist/features/scheduler/scheduler-event/scheduler-event.styles.js +16 -10
  330. package/dist/features/scheduler/scheduler-event-editor/scheduler-event-editor.js +5 -5
  331. package/dist/features/scheduler/shared/scheduler-event-popover.js +2 -2
  332. package/dist/features/scheduler/shared/scheduler-hour-grid.d.ts +2 -2
  333. package/dist/features/scheduler/shared/scheduler-hour-grid.js +3 -3
  334. package/dist/features/scheduler/shared/scheduler-hour-grid.styles.js +3 -3
  335. package/dist/features/scheduler/shared/scheduler-month-view.d.ts +3 -3
  336. package/dist/features/scheduler/shared/scheduler-month-view.js +2 -2
  337. package/dist/features/scheduler/shared/scheduler-month-view.styles.js +2 -2
  338. package/dist/features/scheduler/shared/scheduler-year-view.styles.js +2 -2
  339. package/dist/features/table/table/table-native.styles.d.ts +28 -0
  340. package/dist/features/table/table/table-native.styles.js +502 -0
  341. package/dist/features/table/table/table.d.ts +342 -294
  342. package/dist/features/table/table/table.js +820 -546
  343. package/dist/features/table/table/table.styles.js +31 -17
  344. package/dist/features/table/table/table.types.d.ts +18 -23
  345. package/dist/features/table/table/table.utils.d.ts +6 -36
  346. package/dist/features/table/table/table.utils.js +16 -65
  347. package/dist/features/table/table-action-bar/table-action-bar.d.ts +33 -16
  348. package/dist/features/table/table-action-bar/table-action-bar.js +18 -25
  349. package/dist/features/table/table-action-bar/table-action-bar.styles.js +15 -3
  350. package/dist/i18n/en.d.ts +34 -0
  351. package/dist/i18n/en.js +34 -0
  352. package/dist/i18n/fr.js +38 -0
  353. package/dist/index.d.ts +7 -13
  354. package/dist/index.js +6 -10
  355. package/dist/jsx.d.ts +1 -1
  356. package/dist/mixins/button.mixin.d.ts +10 -9
  357. package/dist/mixins/button.mixin.js +14 -6
  358. package/dist/mixins/card-link.mixin.d.ts +3 -2
  359. package/dist/mixins/card-link.mixin.js +12 -5
  360. package/dist/mixins/check.mixin.js +4 -5
  361. package/dist/mixins/disabled.mixin.d.ts +3 -3
  362. package/dist/mixins/disabled.mixin.js +10 -5
  363. package/dist/mixins/field-appearance.mixin.d.ts +19 -0
  364. package/dist/mixins/{field-variant.mixin.js → field-appearance.mixin.js} +11 -11
  365. package/dist/mixins/form.mixin.d.ts +17 -1
  366. package/dist/mixins/form.mixin.js +80 -25
  367. package/dist/mixins/radio.mixin.js +4 -5
  368. package/dist/mixins/validators.mixin.d.ts +7 -1
  369. package/dist/mixins/validators.mixin.js +36 -17
  370. package/dist/ssr/animate-ssr.css +27 -72
  371. package/dist/ssr/button-ssr.css +319 -0
  372. package/dist/ssr/card-ssr.css +184 -0
  373. package/dist/ssr/description-list-ssr.css +150 -0
  374. package/dist/ssr/links-ssr.css +35 -0
  375. package/dist/ssr/overlay-ssr.css +0 -41
  376. package/dist/ssr/rich-text.css +38 -0
  377. package/dist/ssr/sidebar-ssr.css +0 -38
  378. package/dist/ssr/table-ssr.css +472 -0
  379. package/dist/ssr/tag-ssr.css +197 -0
  380. package/dist/ssr/toolbar-ssr.css +77 -39
  381. package/dist/types/variant.type.d.ts +26 -10
  382. package/dist/utils/drag-drop.utils.js +18 -2
  383. package/dist/utils/dropdown-menu.utils.d.ts +2 -2
  384. package/dist/utils/dropdown-menu.utils.js +4 -14
  385. package/dist/utils/light-dom-styles.utils.d.ts +14 -0
  386. package/dist/utils/light-dom-styles.utils.js +37 -0
  387. package/dist/utils/media.styles.d.ts +19 -0
  388. package/dist/utils/media.styles.js +23 -0
  389. package/dist/utils/motion.utils.d.ts +40 -0
  390. package/dist/utils/motion.utils.js +51 -0
  391. package/dist/utils/navigation.utils.d.ts +14 -0
  392. package/dist/utils/navigation.utils.js +33 -0
  393. package/dist/utils/new-tab-anchor.controller.d.ts +44 -0
  394. package/dist/utils/new-tab-anchor.controller.js +102 -0
  395. package/dist/utils/severity-icon.utils.d.ts +1 -1
  396. package/dist/utils/severity-icon.utils.js +1 -1
  397. package/dist/utils/theme-generator.utils.d.ts +9 -2
  398. package/dist/utils/theme-generator.utils.js +62 -21
  399. package/package.json +24 -29
  400. package/styles/breakpoints.scss +8 -0
  401. package/styles/eunomia-grid.scss +6 -0
  402. package/styles/themes/eunomia-theme.scss +4 -3
  403. package/styles/themes/mixins/eunomia-a11y-color.mixin.scss +34 -1
  404. package/styles/themes/mixins/eunomia-neutrals.mixin.scss +4 -0
  405. package/styles/themes/mixins/eunomia-states.mixin.scss +36 -12
  406. package/styles/themes/prebuilt-themes/eunomia/eunomia-dark.mixin.scss +10 -10
  407. package/styles/themes/prebuilt-themes/eunomia/eunomia.mixin.scss +10 -10
  408. package/styles/themes/prebuilt-themes/eunomia/eunomia.theme.scss +3 -2
  409. package/styles/themes/prebuilt-themes/fire/eunomia-fire-dark.mixin.scss +10 -10
  410. package/styles/themes/prebuilt-themes/fire/eunomia-fire.mixin.scss +10 -10
  411. package/styles/themes/prebuilt-themes/fire/eunomia-fire.theme.scss +3 -2
  412. package/styles/themes/prebuilt-themes/forest/eunomia-forest-dark.mixin.scss +10 -10
  413. package/styles/themes/prebuilt-themes/forest/eunomia-forest.mixin.scss +10 -10
  414. package/styles/themes/prebuilt-themes/forest/eunomia-forest.theme.scss +3 -2
  415. package/styles/themes/prebuilt-themes/gold/eunomia-gold-dark.mixin.scss +10 -10
  416. package/styles/themes/prebuilt-themes/gold/eunomia-gold.mixin.scss +10 -10
  417. package/styles/themes/prebuilt-themes/gold/eunomia-gold.theme.scss +3 -2
  418. package/styles/themes/prebuilt-themes/ocean/eunomia-ocean-dark.mixin.scss +10 -10
  419. package/styles/themes/prebuilt-themes/ocean/eunomia-ocean.mixin.scss +10 -10
  420. package/styles/themes/prebuilt-themes/ocean/eunomia-ocean.theme.scss +3 -2
  421. package/styles/themes/prebuilt-themes/salmon/eunomia-salmon-dark.mixin.scss +10 -10
  422. package/styles/themes/prebuilt-themes/salmon/eunomia-salmon.mixin.scss +10 -10
  423. package/styles/themes/prebuilt-themes/salmon/eunomia-salmon.theme.scss +3 -2
  424. package/styles/themes/prebuilt-themes/violet/eunomia-violet-dark.mixin.scss +10 -10
  425. package/styles/themes/prebuilt-themes/violet/eunomia-violet.mixin.scss +10 -10
  426. package/styles/themes/prebuilt-themes/violet/eunomia-violet.theme.scss +3 -2
  427. package/styles/tools/indexes/eunomia-media-queries.index.scss +1 -3
  428. package/styles/tools/indexes/eunomia-tools.index.scss +1 -0
  429. package/styles/tools/mixins/layout/eunomia-grid.mixin.scss +117 -53
  430. package/styles/tools/mixins/media-queries/eunomia-media-queries.mixin.scss +69 -0
  431. package/styles/tools/mixins/settings/eunomia-alpha.mixin.scss +28 -4
  432. package/styles/tools/mixins/settings/eunomia-motion.mixin.scss +9 -0
  433. package/styles/tools/variables/eunomia.breakpoints.scss +4 -0
  434. package/dist/cdn/components/chunks/chunk-2JLDJFYD.js +0 -48
  435. package/dist/cdn/components/chunks/chunk-A6KGFALA.js +0 -132
  436. package/dist/cdn/components/chunks/chunk-ATH2OAMU.js +0 -1
  437. package/dist/cdn/components/chunks/chunk-BKHD4S57.js +0 -1
  438. package/dist/cdn/components/chunks/chunk-CEDHJUUH.js +0 -1
  439. package/dist/cdn/components/chunks/chunk-D2M4BV5Y.js +0 -328
  440. package/dist/cdn/components/chunks/chunk-E74CM7UG.js +0 -66
  441. package/dist/cdn/components/chunks/chunk-EST3M4AO.js +0 -1
  442. package/dist/cdn/components/chunks/chunk-GU6BVMFJ.js +0 -1
  443. package/dist/cdn/components/chunks/chunk-KVYUNKUO.js +0 -27
  444. package/dist/cdn/components/chunks/chunk-OAULRKHQ.js +0 -38
  445. package/dist/cdn/components/chunks/chunk-PCM7D3YF.js +0 -84
  446. package/dist/cdn/components/chunks/chunk-T4PTHUYF.js +0 -10
  447. package/dist/cdn/components/chunks/chunk-T7WABJEJ.js +0 -1
  448. package/dist/cdn/components/chunks/chunk-VRGRCYIO.js +0 -1
  449. package/dist/cdn/components/chunks/chunk-X2AUBZD2.js +0 -30
  450. package/dist/cdn/components/chunks/chunk-XSMCT4DF.js +0 -1
  451. package/dist/cdn/components/eun-breadcrumb-item.js +0 -93
  452. package/dist/cdn/components/eun-table-cell.js +0 -1
  453. package/dist/cdn/components/eun-table-column.js +0 -1
  454. package/dist/cdn/components/eun-table-header.js +0 -1
  455. package/dist/cdn/components/eun-table-item.js +0 -1
  456. package/dist/cdn/components/eun-table-skeleton-row.js +0 -1
  457. package/dist/cdn/styles/tools/mixins/media-queries/eunomia-device.mixin.scss +0 -14
  458. package/dist/cdn/styles/tools/mixins/media-queries/eunomia-gte.mixin.scss +0 -26
  459. package/dist/cdn/styles/tools/mixins/media-queries/eunomia-lte.mixin.scss +0 -26
  460. package/dist/components/navigation/breadcrumb/breadcrumb-item.d.ts +0 -77
  461. package/dist/components/navigation/breadcrumb/breadcrumb-item.js +0 -130
  462. package/dist/components/navigation/breadcrumb/breadcrumb-item.styles.d.ts +0 -6
  463. package/dist/components/navigation/breadcrumb/breadcrumb-item.styles.js +0 -78
  464. package/dist/features/table/native-table.css +0 -276
  465. package/dist/features/table/shared-styles/table-row.styles.d.ts +0 -7
  466. package/dist/features/table/shared-styles/table-row.styles.js +0 -47
  467. package/dist/features/table/shared-styles/table-shared.styles.d.ts +0 -23
  468. package/dist/features/table/shared-styles/table-shared.styles.js +0 -33
  469. package/dist/features/table/table-cell/table-cell.d.ts +0 -59
  470. package/dist/features/table/table-cell/table-cell.js +0 -73
  471. package/dist/features/table/table-cell/table-cell.styles.d.ts +0 -1
  472. package/dist/features/table/table-cell/table-cell.styles.js +0 -46
  473. package/dist/features/table/table-column/table-column.d.ts +0 -73
  474. package/dist/features/table/table-column/table-column.js +0 -89
  475. package/dist/features/table/table-column/table-column.styles.d.ts +0 -1
  476. package/dist/features/table/table-column/table-column.styles.js +0 -51
  477. package/dist/features/table/table-header/table-header.d.ts +0 -102
  478. package/dist/features/table/table-header/table-header.js +0 -165
  479. package/dist/features/table/table-header/table-header.styles.d.ts +0 -1
  480. package/dist/features/table/table-header/table-header.styles.js +0 -147
  481. package/dist/features/table/table-item/table-item.d.ts +0 -179
  482. package/dist/features/table/table-item/table-item.js +0 -291
  483. package/dist/features/table/table-item/table-item.styles.d.ts +0 -1
  484. package/dist/features/table/table-item/table-item.styles.js +0 -404
  485. package/dist/features/table/table-managed/table-managed.d.ts +0 -19
  486. package/dist/features/table/table-managed/table-managed.js +0 -128
  487. package/dist/features/table/table-skeleton-row/table-skeleton-row.d.ts +0 -46
  488. package/dist/features/table/table-skeleton-row/table-skeleton-row.js +0 -110
  489. package/dist/features/table/table-skeleton-row/table-skeleton-row.styles.d.ts +0 -12
  490. package/dist/features/table/table-skeleton-row/table-skeleton-row.styles.js +0 -65
  491. package/dist/mixins/field-variant.mixin.d.ts +0 -22
  492. package/dist/mixins/table-row.mixin.d.ts +0 -37
  493. package/dist/mixins/table-row.mixin.js +0 -103
  494. package/styles/tools/mixins/media-queries/eunomia-device.mixin.scss +0 -14
  495. package/styles/tools/mixins/media-queries/eunomia-gte.mixin.scss +0 -26
  496. package/styles/tools/mixins/media-queries/eunomia-lte.mixin.scss +0 -26
@@ -4,140 +4,190 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, property, query, queryAssignedElements, state, } from "lit/decorators.js";
8
- import { html, LitElement, nothing } from "lit";
9
- import { EunomiaTableHeaderTagName, } from "../table-header/table-header.js";
10
- import { EunomiaTableItemTagName, } from "../table-item/table-item.js";
7
+ import { customElement, property, query, state } from "lit/decorators.js";
8
+ import { html, LitElement, nothing, render, } from "lit";
9
+ import { ifDefined } from "lit/directives/if-defined.js";
10
+ import { repeat } from "lit/directives/repeat.js";
11
11
  import { EunomiaTableActionBarTagName, } from "../table-action-bar/table-action-bar.js";
12
- import { computeColumnsTemplate, resolveRowKey } from "./table.utils.js";
13
- import { buildManagedHeader, buildManagedItem, } from "../table-managed/table-managed.js";
12
+ import { alignToTextAlign, resolveRowKey } from "./table.utils.js";
14
13
  import { TableSelectionChangeEvent } from "../../../events/table-selection-change.event.js";
15
14
  import { TableActionEvent } from "../../../events/table-action.event.js";
15
+ import { TableRowClickEvent } from "../../../events/table-row-click.event.js";
16
16
  import { LoadMoreEvent } from "../../../events/loadmore.event.js";
17
17
  import { LoadMoreSentinelMixin } from "../../../mixins/load-more-sentinel.mixin.js";
18
+ import { LocalizableMixin } from "../../../mixins/localizable.mixin.js";
18
19
  import { tableStyles } from "./table.styles.js";
19
- import "../table-skeleton-row/table-skeleton-row.js";
20
+ import { tableNativeStyles } from "./table-native.styles.js";
21
+ import { adoptLightDomStyles } from "../../../utils/light-dom-styles.utils.js";
22
+ import "../../../components/form/checkbox/checkbox.js";
23
+ import "../../../components/action/button/button.js";
24
+ import "../../../components/action/dropdown-button/dropdown-button.js";
25
+ import "../../../components/content/icon/icon.js";
20
26
  import "../../../components/loader/loader/loader.js";
27
+ import "../../../components/loader/skeleton/skeleton.js";
21
28
  /**
22
- * Class marker of a virtualized-out page's placeholder: a full-width
23
- * (`grid-column: 1 / -1`) spacer sized to the page's last measured height,
24
- * standing in for its detached `eun-table-item`s so the grid's scroll
25
- * position/scrollbar don't jump ; excluded from row bookkeeping since it's
26
- * a plain `<div>`, never matching the `eun-table-item` selector queries rely
27
- * on.
29
+ * Tag name of the table component.
30
+ */
31
+ export const EunomiaTableTagName = "eun-table";
32
+ /**
33
+ * Marks the selection column cells `eun-table` generates.
34
+ * @internal
35
+ */
36
+ const SELECT_ATTR = "data-table-select";
37
+ /**
38
+ * Marks a row end action cell, authored by the consumer.
39
+ * @internal
40
+ */
41
+ const ACTION_ATTR = "data-table-action";
42
+ /**
43
+ * Marks the link a body row click follows, authored by the consumer.
44
+ * @internal
45
+ */
46
+ const ROW_LINK_ATTR = "data-table-row-link";
47
+ /**
48
+ * Marks the placeholder body rendered while `loading`.
49
+ * @internal
50
+ */
51
+ const SKELETON_ATTR = "data-table-skeleton";
52
+ /**
53
+ * Reflected on a selected body row.
28
54
  * @internal
29
55
  */
30
- const TABLE_ROW_SPACER_CLASS = "eun-table-row-spacer";
56
+ const SELECTED_ATTR = "data-selected";
31
57
  /**
32
- * Row height (px) assumed for a virtualized page never measured yet:
33
- * refined into a real running average the moment any page actually gets
34
- * measured. See `eun-infinite-list`'s own `DEFAULT_ITEM_HEIGHT` for the same
35
- * reasoning.
58
+ * Class of a virtualized-out page's placeholder row, sized to the page's
59
+ * last measured height so the scroll position doesn't jump.
60
+ * @internal
61
+ */
62
+ const SPACER_CLASS = "eun-table-row-spacer";
63
+ /**
64
+ * Row height (px) assumed for a virtualized page never measured yet.
36
65
  * @internal
37
66
  */
38
67
  const DEFAULT_ROW_HEIGHT = 44;
39
68
  /**
40
- * Tag name of the table component.
69
+ * Elements a click on which belongs to that element, not to its row.
70
+ * @internal
41
71
  */
42
- export const EunomiaTableTagName = "eun-table";
72
+ const INTERACTIVE_SELECTOR = 'a[href], button, input, select, textarea, summary, label, [contenteditable=""], [contenteditable="true"], [role="button"], [role="link"], [role="checkbox"], [role="switch"], [role="menuitem"]';
43
73
  /**
44
- * A row-and-column-based grid (`<eun-table>`), usable two ways, both
45
- * producing the exact same DOM, CSS, and behavior underneath, since the
46
- * second is just a generator for the first :
74
+ * A data table built on a native `<table>` (`<eun-table><table>…</table></eun-table>`).
75
+ * The table keeps its native semantics (`table`, `row`, `columnheader`,
76
+ * `rowheader`, `cell`), renders before any script runs, and holds any
77
+ * content: tags, icons, buttons, dropdown buttons, links.
47
78
  *
48
- * Every row shares one set of column tracks via CSS Subgrid : `eun-table`'s
49
- * own shadow root is `display: grid` with a bare `<slot>` (no wrapper), per
50
- * the flat-tree slotting model, a slot's assigned nodes render as if they
51
- * were direct children of the host, so `eun-table-header`/`eun-table-item`
52
- * become direct items of this grid despite living in separate shadow
53
- * roots. Each of those, in turn, sets `grid-template-columns: subgrid` on
54
- * its own `:host`, inheriting the exact same tracks the browser computed
55
- * once for the whole grid: this is what keeps `auto`/flexible columns
56
- * pixel-aligned across every row with zero JS measurement. The track list
57
- * itself (from `eun-table-column`'s `width`, plus a select/action track
58
- * when relevant) is computed in `updateColumnsTemplate` and applied as the
59
- * `--_columns` custom property `table.styles.ts`'s `:host` reads.
79
+ * `eun-table` styles the slotted table from the light DOM and layers the
80
+ * optional behaviors on top: row selection with a checkbox column it
81
+ * generates and a floating action bar, row end actions revealed on hover
82
+ * (cells marked `data-table-action`), clickable rows, sticky header,
83
+ * footer, and columns, loading placeholders, and scroll-driven pagination.
84
+ * Without any of those, it's a plain, read-only table.
60
85
  *
61
- * Selection, sticky flags, and row-to-row keyboard navigation are all
62
- * pushed down onto the header/items imperatively (the same "poke state onto
63
- * slotted children" pattern `eun-toggle-group` uses for its own
64
- * `eun-toggle-item`s) rather than those elements pulling state from a
65
- * shared config object, since they're consumer-authored tags, not
66
- * data-driven renders.
86
+ * A row click follows the link marked `data-table-row-link` in that row,
87
+ * which stays the keyboard way to reach it. Rows are identified by their
88
+ * `data-row-key` attribute. A `<tfoot>` holds
89
+ * summary rows, kept out of the selection. A `<caption>` stays in the
90
+ * table for its semantics, and shows above the table's frame.
67
91
  *
68
- * @slot [default] - One `eun-table-header` followed by any number of `eun-table-item`s
69
- * @prop {boolean} [selectable=false] - Enables row selection (checkboxes, select-all, the floating action bar)
70
- * @prop {boolean} [checkboxAlwaysVisible=false] - Keeps every row's own checkbox visible at all times instead of only on hover/focus/selection ; the header's own select-all checkbox is already always visible regardless of this (see `stickyHeader`)
92
+ * @slot [default] - A native `<table>`, with a `<thead>`, a `<tbody>`, and an optional `<caption>` and `<tfoot>`
93
+ * @csspart frame - The bordered scroll container around the table
94
+ * @prop {boolean} [selectable=false] - Enables row selection (generated checkbox column, select-all, the floating action bar)
95
+ * @prop {boolean} [checkboxAlwaysVisible=false] - Keeps every row's own checkbox visible at all times instead of only on hover/focus/selection
71
96
  * @attr {boolean} [checkbox-always-visible=false] - Keeps every row's own checkbox visible at all times instead of only on hover/focus/selection
72
- * @prop {boolean} [actionAlwaysVisible=false] - Keeps every row's own row-end action (`slot="action"`, or a `rowAction`-generated one) visible at all times instead of only on hover/focus ; still cedes to the floating action bar once a row is selected, same as the hover-only default
73
- * @attr {boolean} [action-always-visible=false] - Keeps every row's own row-end action visible at all times instead of only on hover/focus
74
- * @prop {boolean} [stickyHeader=false] - Pins the header to the top of the table's own scroll container ; once pinned, the header never renders the select-all checkbox (even while `selectable`) or any hover styling, since it stays permanently visible rather than only appearing on hover/focus
97
+ * @prop {boolean} [actionAlwaysVisible=false] - Keeps every row end action (a `data-table-action` cell's content) visible at all times instead of only on hover/focus ; still hidden while rows are selected
98
+ * @attr {boolean} [action-always-visible=false] - Keeps every row end action visible at all times instead of only on hover/focus
99
+ * @prop {boolean} [stickyHeader=false] - Pins the `<thead>` to the top of the table's own scroll container
75
100
  * @attr {boolean} [sticky-header=false] - Pins the header to the top
101
+ * @prop {boolean} [stickyFooter=false] - Pins the `<tfoot>` to the bottom of the table's own scroll container
102
+ * @attr {boolean} [sticky-footer=false] - Pins the footer to the bottom
76
103
  * @prop {boolean} [stickyFirstColumn=false] - Pins the first data column to the start edge
77
104
  * @attr {boolean} [sticky-first-column=false] - Pins the first data column to the start edge
78
- * @prop {boolean} [stickyLastColumn=false] - Pins the last data column to the end edge
105
+ * @prop {boolean} [stickyLastColumn=false] - Pins the last data column, and the row end action column, to the end edge
79
106
  * @attr {boolean} [sticky-last-column=false] - Pins the last data column to the end edge
80
- * @prop {boolean} [clearOnEscape=false] - Whether `Escape` clears the selection while the action bar is shown ; off by default so it never fights a consumer's own global `Escape` handling
107
+ * @prop {boolean} [clearOnEscape=false] - Whether `Escape` clears the selection while the action bar is shown
81
108
  * @attr {boolean} [clear-on-escape=false] - Whether `Escape` clears the selection while the action bar is shown
82
- * @prop {Array<string>} [selectedKeys=[]] - The currently selected rows' keys ; settable from outside for a "controlled" table, always reflecting the live selection either way
109
+ * @prop {Array<string>} [selectedKeys=[]] - The currently selected rows' keys ; settable from outside for a "controlled" table
83
110
  * @prop {Array<import('./table.types.js').EunomiaTableAction>} [actions=[]] - Custom actions rendered in the floating action bar's center region
84
- * @prop {string} [selectedLabel='selected'] - Text shown after the count in the action bar's badge
111
+ * @prop {string} [selectedLabel='selected'] - Text shown after the count in the action bar's badge ; translated from `locale` while unset
85
112
  * @attr {string} [selected-label='selected'] - Text shown after the count in the action bar's badge
86
- * @prop {string} [selectAllLabel='Select all'] - Label of the action bar's select-all button while not every row is selected
113
+ * @prop {string} [selectAllLabel='Select all'] - Label of the action bar's select-all button while not every row is selected ; translated from `locale` while unset
87
114
  * @attr {string} [select-all-label='Select all'] - Label of the action bar's select-all button
88
- * @prop {string} [deselectAllLabel='Deselect all'] - Label of the action bar's select-all button once every row is selected
115
+ * @prop {string} [deselectAllLabel='Deselect all'] - Label of the action bar's select-all button once every row is selected ; translated from `locale` while unset
89
116
  * @attr {string} [deselect-all-label='Deselect all'] - Label of the action bar's select-all button once every row is selected
90
- * @prop {string} [selectAllAriaLabel='Select all rows'] - Accessible label for the header's select-all checkbox while not every row is selected
117
+ * @prop {string} [selectAllAriaLabel='Select all rows'] - Accessible label for the header's select-all checkbox while not every row is selected ; translated from `locale` while unset
91
118
  * @attr {string} [select-all-aria-label='Select all rows'] - Accessible label for the header's select-all checkbox
92
- * @prop {string} [deselectAllAriaLabel='Deselect all rows'] - Accessible label for the header's select-all checkbox once every row is selected
119
+ * @prop {string} [deselectAllAriaLabel='Deselect all rows'] - Accessible label for the header's select-all checkbox once every row is selected ; translated from `locale` while unset
93
120
  * @attr {string} [deselect-all-aria-label='Deselect all rows'] - Accessible label for the header's select-all checkbox
94
- * @prop {string} [selectRowAriaLabel='Select row'] - Accessible label prefix for each row's own checkbox (its 1-based row number is appended)
121
+ * @prop {string} [selectRowAriaLabel='Select row'] - Accessible label prefix for each row's own checkbox, followed by the row header's text, or its 1-based row number without one ; translated from `locale` while unset
95
122
  * @attr {string} [select-row-aria-label='Select row'] - Accessible label prefix for each row's own checkbox
96
- * @prop {string} [toolbarAriaLabel='Selection actions'] - Accessible label for the floating action bar itself
123
+ * @prop {string} [toolbarAriaLabel='Selection actions'] - Accessible label for the floating action bar itself ; translated from `locale` while unset
97
124
  * @attr {string} [toolbar-aria-label='Selection actions'] - Accessible label for the floating action bar itself
98
- * @prop {string} [closeActionBarAriaLabel='Clear selection'] - Accessible label for the action bar's close button
125
+ * @prop {string} [closeActionBarAriaLabel='Clear selection'] - Accessible label for the action bar's close button ; translated from `locale` while unset
99
126
  * @attr {string} [close-action-bar-aria-label='Clear selection'] - Accessible label for the action bar's close button
100
- * @prop {Array<import('./table.types.js').EunomiaTableColumnDef<T>>} [columns] - Programmatic alternative to hand-authoring `eun-table-header`/`eun-table-column` : when set, `eun-table` generates that exact markup itself from `columns`/`data`. Setting this puts the table in "managed" mode: it owns its own light DOM from then on ; don't also hand-author `eun-table-header`/`eun-table-item` children
127
+ * @prop {Array<import('./table.types.js').EunomiaTableColumnDef<T>>} [columns] - Programmatic alternative to authoring the `<table>` : when set, `eun-table` renders the native table itself from `columns`/`data`, and owns its light DOM from then on
101
128
  * @prop {Array<T>} [data=[]] - The rows to render, in "managed" mode (see `columns`)
102
- * @prop {import('./table.types.js').EunomiaTableRowActionDef<T>} [rowAction] - A single row-end action (button or dropdown), in "managed" mode: the data-driven equivalent of a `slot="action"` element
103
- * @prop {(row: T, index: number) => string} [rowKey] - Computes each row's stable key, in "managed" mode ; defaults to a DOM-order fallback, same caveat as `eun-table-item`'s own `row-key`
104
- * @fires {TableSelectionChangeEvent} euntableselectionchange - Emitted whenever the selection changes, from any source
105
- * @fires {TableActionEvent} euntableaction - Emitted when a floating action bar action is activated
106
- * @prop {boolean} [loading=false] - Shows `skeletonRows` shimmering placeholder rows in place of the real ones: the header stays visible, only `eun-table-item`s are hidden ; set back to `false` once real data has arrived
129
+ * @prop {import('./table.types.js').EunomiaTableRowActionDef<T>} [rowAction] - A single row end action (button or dropdown), in "managed" mode
130
+ * @prop {(row: T, index: number) => string} [rowKey] - Computes each row's stable key, in "managed" mode ; defaults to a DOM-order fallback
131
+ * @prop {boolean} [loading=false] - Shows `skeletonRows` placeholder rows in place of the body rows ; the header stays visible
107
132
  * @prop {number} [skeletonRows=6] - Number of placeholder rows rendered while `loading`
108
133
  * @attr {number} [skeleton-rows=6] - Number of placeholder rows rendered while `loading`
109
- * @prop {boolean} [syncing=false] - Shows a shimmering overlay sweeping across every already-visible `eun-table-item`, without hiding any of them (unlike `loading`) : for a background refresh of data the user can already see, rather than an initial load with nothing to show yet. Reflects `aria-busy` on the table itself while set
110
- * @prop {boolean} [clickable=false] - Shows a pointer cursor on a row while it has no row-end action content of its own ; purely visual; a row click already fires `euntablerowclick` regardless of this or any row-end action (see `eun-table-item`)
111
- * @attr {boolean} [clickable=false] - Shows a pointer cursor on a row while it has no row-end action content of its own
112
- * @prop {boolean} [infinite=false] - Enables scroll-triggered pagination : dispatches `eunloadmore` once the user scrolls near the end of the table's own scroll container and `hasMore` is `true`, showing a trailing `eun-loader` while `loadingMore` is `true`. Independent of `virtualize`: combine both for `eun-infinite-list` parity, or use `infinite` alone to paginate without detaching any DOM
113
- * @prop {boolean} [virtualize=false] - Detaches `eun-table-item` pages far from the current scroll position once the known row count exceeds `virtualizeThreshold`, replacing each with a single full-width spacer sized to its last measured height, the same mechanism `eun-infinite-list` uses for its own items, adapted to this table's CSS Subgrid rows. Independent of `infinite`: useful on its own for a very large, already-fully-loaded dataset
114
- * @prop {number} [pageSize=20] - Number of rows per page: both the unit `eunloadmore` requests are made in, and the chunk size `virtualize` mounts/unmounts as a whole
134
+ * @prop {boolean} [syncing=false] - Sweeps a shimmer across every visible body row without hiding any, for a background refresh. Sets `aria-busy` on the table while set
135
+ * @prop {boolean} [clickable=false] - Shows a pointer cursor on body rows ; a row click fires `euntablerowclick` and follows the row's `data-table-row-link` link regardless, which stays the keyboard way to do the same
136
+ * @prop {boolean} [infinite=false] - Enables scroll-triggered pagination : dispatches `eunloadmore` once the user scrolls near the end of the table's own scroll container and `hasMore` is `true`
137
+ * @prop {boolean} [virtualize=false] - Detaches body row pages far from the current scroll position once the row count exceeds `virtualizeThreshold`, each replaced by a spacer row of the same height
138
+ * @prop {number} [pageSize=20] - Number of rows per page, for both `eunloadmore` and `virtualize`
115
139
  * @attr {number} [page-size=20] - Number of rows per page
116
- * @prop {number} [virtualizeThreshold=100] - Total known row count above which `virtualize` actually starts detaching pages
117
- * @attr {number} [virtualize-threshold=100] - Total known row count above which `virtualize` actually starts detaching pages
118
- * @prop {number} [bufferPages=1] - Number of extra pages kept mounted before and after the page currently in view
119
- * @attr {number} [buffer-pages=1] - Number of extra pages kept mounted before and after the page currently in view
120
- * @prop {boolean} [hasMore=false] - Whether more rows can still be obtained ; the `infinite` sentinel never fires `eunloadmore` while this is `false`
140
+ * @prop {number} [virtualizeThreshold=100] - Row count above which `virtualize` starts detaching pages
141
+ * @attr {number} [virtualize-threshold=100] - Row count above which `virtualize` starts detaching pages
142
+ * @prop {number} [bufferPages=1] - Number of extra pages kept mounted before and after the page in view
143
+ * @attr {number} [buffer-pages=1] - Number of extra pages kept mounted before and after the page in view
144
+ * @prop {boolean} [hasMore=false] - Whether more rows can still be obtained
121
145
  * @attr {boolean} [has-more=false] - Whether more rows can still be obtained
122
- * @prop {boolean} [loadingMore=false] - Whether a `eunloadmore` request is currently in flight ; set to `true` right before dispatching the event, left to the consumer to reset once new rows are appended
123
- * @attr {boolean} [loading-more=false] - Whether a `eunloadmore` request is currently in flight
124
- * @prop {string} [rootMargin='400px'] - How far before the physical end of the table `eunloadmore` triggers, as a CSS-like margin
125
- * @attr {string} [root-margin='400px'] - How far before the physical end of the table `eunloadmore` triggers
126
- * @prop {string} [loadingMoreLabel='Loading more rows…'] - Accessible label passed to the trailing `eun-loader`
146
+ * @prop {boolean} [loadingMore=false] - Whether a `eunloadmore` request is in flight ; set to `true` right before the event fires, reset by the consumer once rows are appended
147
+ * @attr {boolean} [loading-more=false] - Whether a `eunloadmore` request is in flight
148
+ * @prop {string} [rootMargin='400px'] - How far before the end of the table `eunloadmore` triggers, as a CSS-like margin
149
+ * @attr {string} [root-margin='400px'] - How far before the end of the table `eunloadmore` triggers
150
+ * @prop {string} [loadingMoreLabel='Loading more rows…'] - Accessible label passed to the trailing `eun-loader` ; translated from `locale` while unset
127
151
  * @attr {string} [loading-more-label='Loading more rows…'] - Accessible label passed to the trailing `eun-loader`
128
- * @fires {LoadMoreEvent} eunloadmore - Dispatched when `infinite` is set, more rows are needed, and `hasMore` is `true` ; `loadingMore` is already `true` by the time this fires
129
- * @cssprop [--table-border-color] - Set a custom border color
130
- * @cssprop [--table-background] - Set a custom background color
131
- * @cssprop [--table-max-height] - Set a custom max height (with scroll) ; `none` by default
132
- * @cssprop [--table-skeleton-stagger] - Delay between each skeleton row's entrance animation, as a CSS time (e.g. `60ms`)
133
- * @cssprop [--table-skeleton-duration] - Duration of each skeleton row's own entrance animation, as a CSS time (e.g. `320ms`)
134
- * @cssprop [--table-loader-color] - Set a custom color for the trailing `eun-loader` shown while `loadingMore` (an `infinite` table)
135
- * @cssprop [--table-sync-overlay-color] - Set a custom highlight color for the shimmer sweep shown while `syncing`
136
- * @cssprop [--table-sync-duration] - Set a custom duration for the `syncing` shimmer sweep, as a CSS time (default `1.6s`)
152
+ * @fires {TableSelectionChangeEvent} euntableselectionchange - Emitted whenever the selection changes, from any source
153
+ * @fires {TableActionEvent} euntableaction - Emitted when a floating action bar action is activated
154
+ * @fires {TableRowClickEvent} euntablerowclick - Emitted on a body row click outside its checkbox, its action cell, and any interactive content, while no row is selected ; cancelable, `preventDefault()` keeps the row's `data-table-row-link` link from being followed
155
+ * @fires {LoadMoreEvent} eunloadmore - Dispatched when `infinite` is set, more rows are needed, and `hasMore` is `true`
156
+ * @cssprop [--table-border-color] - Sets the border color of the frame and the rows
157
+ * @cssprop [--table-background] - Sets the background color of the frame
158
+ * @cssprop [--table-max-height] - Sets a max height, scrolling past it ; `none` by default
159
+ * @cssprop [--table-caption-color] - Sets the text color of the `<caption>`
160
+ * @cssprop [--table-caption-font-size] - Sets the font size of the `<caption>`
161
+ * @cssprop [--table-caption-font-weight] - Sets the font weight of the `<caption>`
162
+ * @cssprop [--table-header-background] - Sets the background color of the header cells
163
+ * @cssprop [--table-footer-background] - Sets the background color of the footer cells
164
+ * @cssprop [--table-row-background] - Sets the background color of the body cells
165
+ * @cssprop [--table-row-hover-background] - Sets the tint of a hovered or focused body row
166
+ * @cssprop [--table-row-selected-background] - Sets the tint of a selected body row
167
+ * @cssprop [--table-row-dragging-background] - Sets the tint of a row being reordered with `createDragReorder`
168
+ * @cssprop [--table-row-transition-duration] - Sets the duration of the hover and selection tint transitions
169
+ * @cssprop [--table-column-color] - Sets the text color of the header cells
170
+ * @cssprop [--table-column-font-weight] - Sets the font weight of the header cells
171
+ * @cssprop [--table-cell-color] - Sets the text color of the cells
172
+ * @cssprop [--table-column-border-color] - Sets the color of a vertical divider between columns ; transparent by default
173
+ * @cssprop [--table-sticky-column-background] - Sets the background color of a sticky column's cells, and of the selection column
174
+ * @cssprop [--table-sticky-row-background] - Sets the background color of the footer cells while `stickyFooter`
175
+ * @cssprop [--table-sticky-border-color] - Sets the color of the edges of a sticky header, footer, or column
176
+ * @cssprop [--table-checkbox-background] - Sets the background color of an unchecked selection checkbox
177
+ * @cssprop [--table-checkbox-border-color] - Sets the border color of an unchecked selection checkbox
178
+ * @cssprop [--table-checkbox-hover] - Sets the background color of an unchecked selection checkbox while hovered
179
+ * @cssprop [--table-action-color] - Sets the text and icon color of a row end action
180
+ * @cssprop [--eun-table-select-col-width] - Sets the width of the selection column ; 40px by default
181
+ * @cssprop [--eun-table-action-col-width] - Sets the width of the row end action column, which stays fixed so a sticky last column lines up with it ; 56px by default, enough for an icon-only button
182
+ * @cssprop [--table-skeleton-stagger] - Delay between each placeholder row's entrance animation, as a CSS time (e.g. `60ms`)
183
+ * @cssprop [--table-skeleton-duration] - Duration of each placeholder row's entrance animation, as a CSS time (e.g. `320ms`)
184
+ * @cssprop [--table-loader-color] - Sets the color of the trailing `eun-loader` shown while `loadingMore`
185
+ * @cssprop [--table-sync-overlay-color] - Sets the highlight color of the `syncing` shimmer sweep
186
+ * @cssprop [--table-sync-duration] - Sets the duration of the `syncing` shimmer sweep, as a CSS time (default `1.6s`)
137
187
  * @element eun-table
138
188
  * @template T Shape of a data row, in "managed" `columns`/`data` mode.
139
189
  */
140
- let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement) {
190
+ let EunomiaTable = class EunomiaTable extends LocalizableMixin(LoadMoreSentinelMixin(LitElement)) {
141
191
  constructor() {
142
192
  super(...arguments);
143
193
  /** @default false */
@@ -149,6 +199,8 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
149
199
  /** @default false */
150
200
  this.stickyHeader = false;
151
201
  /** @default false */
202
+ this.stickyFooter = false;
203
+ /** @default false */
152
204
  this.stickyFirstColumn = false;
153
205
  /** @default false */
154
206
  this.stickyLastColumn = false;
@@ -178,72 +230,50 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
178
230
  this.loadingMore = false;
179
231
  /** @default 400px */
180
232
  this.rootMargin = "400px";
181
- /** @default Loading more rows… */
182
- this.loadingMoreLabel = "Loading more rows…";
183
233
  /** @default [] */
184
234
  this.actions = [];
185
- /** @default selected */
186
- this.selectedLabel = "selected";
187
- /** @default Select all */
188
- this.selectAllLabel = "Select all";
189
- /** @default Deselect all */
190
- this.deselectAllLabel = "Deselect all";
191
- /** @default Select all rows */
192
- this.selectAllAriaLabel = "Select all rows";
193
- /** @default Deselect all rows */
194
- this.deselectAllAriaLabel = "Deselect all rows";
195
- /** @default Select row */
196
- this.selectRowAriaLabel = "Select row";
197
- /** @default Selection actions */
198
- this.toolbarAriaLabel = "Selection actions";
199
- /** @default Clear selection */
200
- this.closeActionBarAriaLabel = "Clear selection";
201
235
  /**
202
236
  * The rows to render, in "managed" mode (see `columns`).
203
237
  * @default []
204
238
  */
205
239
  this.data = [];
206
240
  /**
207
- * The current selection, as a `Set` internally — the source of truth
208
- * `selectedKeys` reads/writes through. A `@state()`, not a `@property()` :
209
- * external reads/writes go through the `selectedKeys` accessor, which
210
- * handles change detection and event dispatch explicitly.
241
+ * The current selection, the source of truth `selectedKeys` reads and
242
+ * writes through.
211
243
  * @internal
212
244
  */
213
245
  this.internalSelection = new Set();
214
246
  /**
215
- * Whether any row currently has action content — computed by scanning
216
- * every item's `slot="action"` content, not author-set.
247
+ * The resolved key of every body row known at the last sync.
248
+ * @internal
249
+ */
250
+ this.rowKeys = new Map();
251
+ /**
252
+ * Whether `aria-rowcount`/`aria-rowindex` were set for `virtualize`, so
253
+ * they're only ever removed by this component.
217
254
  * @internal
218
255
  */
219
- this.hasAction = false;
256
+ this.ariaRowsApplied = false;
220
257
  /**
221
- * Every logical `eun-table-item`, mounted or currently virtualized-out
222
- * (replaced by a `TABLE_ROW_SPACER_CLASS` placeholder), in document
223
- * order — only kept up to date while `virtualize` is set ; `itemEls`
224
- * alone (mounted-only) is used otherwise. Reconstructed on every
225
- * legitimate `slotchange` (see {@link syncLogicalItems}) by threading each
226
- * spacer's cached nodes back into their position, the same reconciliation
227
- * `eun-infinite-list`'s own `syncItemsFromLightDom` performs.
258
+ * Every body row, mounted or virtualized-out, in document order ; only
259
+ * kept up to date while `virtualize` is set.
228
260
  * @internal
229
261
  */
230
- this.logicalItems = [];
262
+ this.logicalRows = [];
231
263
  /**
232
- * Page indices currently mounted (their rows are real children of this
233
- * host, not replaced by a spacer).
264
+ * Page indices currently mounted.
234
265
  * @internal
235
266
  */
236
267
  this.mountedPages = new Set();
237
268
  /**
238
- * Cached row nodes for each currently-virtualized-out page, keyed by page
239
- * index — reattached as-is once a page remounts, so no consumer
240
- * state/listeners inside them are ever lost.
269
+ * Cached rows of each virtualized-out page, reattached as-is once the
270
+ * page remounts.
241
271
  * @internal
242
272
  */
243
273
  this.detachedByPage = new Map();
244
274
  /**
245
275
  * Measured height (px) of each page, filled in the first time it's
246
- * unmounted ; used instead of {@link avgRowHeight} once known.
276
+ * unmounted.
247
277
  * @internal
248
278
  */
249
279
  this.pageHeights = [];
@@ -253,73 +283,81 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
253
283
  */
254
284
  this.avgRowHeight = DEFAULT_ROW_HEIGHT;
255
285
  /**
256
- * Set while this component is itself inserting/removing light DOM
257
- * children for `virtualize` (mount/unmount), so the resulting
258
- * `slotchange` isn't misread as a consumer-driven content change.
259
- * @internal
260
- */
261
- this.mutatingSlot = false;
262
- /**
263
- * Count of `eunloadmore` events dispatched so far ; becomes the event's
264
- * own `page` field.
286
+ * Count of `eunloadmore` events dispatched so far.
265
287
  * @internal
266
288
  */
267
289
  this.requestedPages = 0;
268
290
  /**
269
- * {@link lastFocusedRow}'s row index at the time it was last focused,
270
- * used to refocus whatever row now sits in that same slot.
271
- * @internal
272
- */
273
- this.lastFocusedRowIndex = -1;
274
- /**
275
- * Tracks {@link lastFocusedRow}/{@link lastFocusedRowIndex} from any
276
- * `focusin` landing on a row's own leading stop or its content —
277
- * `event.target` is that row itself either way, whether directly focused
278
- * or retargeted through its own shadow root ; slotted content (an
279
- * `action` button, ...) instead needs `closest`, since it's a light-DOM
280
- * descendant rather than something the shadow boundary retargets.
291
+ * Re-syncs on structural light DOM changes only: added or removed rows,
292
+ * cells, or sections, and a changed `data-row-key`. A change deeper
293
+ * inside a cell's own content is ignored.
281
294
  * @private
282
295
  */
283
- this.handleFocusIn = (event) => {
284
- const row = event.target.closest(`${EunomiaTableHeaderTagName}, ${EunomiaTableItemTagName}`);
285
- if (!row) {
296
+ this.handleMutations = (records) => {
297
+ const structural = records.some((record) => {
298
+ if (record.type === "attributes") {
299
+ return true;
300
+ }
301
+ const target = record.target;
302
+ return (target === this ||
303
+ /^(TABLE|THEAD|TBODY|TFOOT|TR)$/.test(target.nodeName ?? ""));
304
+ });
305
+ if (!structural) {
286
306
  return;
287
307
  }
288
- const index = this.rowIndexOf(row);
289
- if (index < 0) {
290
- return;
308
+ this.sync();
309
+ this.announceLoadedRows();
310
+ if (this.virtualize) {
311
+ this.scheduleAnimationFrame(() => this.syncPages());
312
+ }
313
+ if (this.infinite) {
314
+ this.reobserveSentinel(this.sentinelEl);
291
315
  }
292
- this.lastFocusedRow = row;
293
- this.lastFocusedRowIndex = index;
294
316
  };
295
317
  /** @private */
296
- this.handleSlotChange = () => {
297
- if (this.mutatingSlot) {
318
+ this.handleClick = (event) => {
319
+ const target = event.target;
320
+ const selectCell = target.closest?.(`[${SELECT_ATTR}]`);
321
+ if (selectCell) {
322
+ const checkbox = target.closest("eun-checkbox");
323
+ if (checkbox?.indeterminate) {
324
+ this.toggleSelectAll();
325
+ }
298
326
  return;
299
327
  }
300
- if (this.virtualize) {
301
- this.syncLogicalItems();
328
+ const row = this.bodyRowOf(target);
329
+ if (!row ||
330
+ target.closest(`[${ACTION_ATTR}]`) ||
331
+ this.isInteractiveClick(event, row)) {
332
+ return;
302
333
  }
303
- this.syncChildren();
304
- this.restoreFocusIfLost();
305
- if (this.virtualize) {
306
- this.scheduleAnimationFrame(() => this.syncPages());
334
+ this.activateRow(row, event);
335
+ };
336
+ /** @private */
337
+ this.handleCheckboxChange = (event) => {
338
+ const target = event.target;
339
+ const cell = target.closest?.(`[${SELECT_ATTR}]`);
340
+ if (!cell || target.nodeName !== "EUN-CHECKBOX") {
341
+ return;
307
342
  }
308
- if (this.infinite) {
309
- this.reobserveSentinel(this.sentinelEl);
343
+ const row = cell.parentElement;
344
+ if (row.parentElement?.nodeName === "THEAD") {
345
+ this.toggleSelectAll();
346
+ return;
347
+ }
348
+ const key = this.rowKeys.get(row);
349
+ if (key !== undefined) {
350
+ this.toggleRow(key);
310
351
  }
311
352
  };
312
353
  /**
313
- * Re-dispatches `euntableaction` from `eun-table` itself : the action bar
314
- * lives outside this component's own DOM tree entirely (portaled to
315
- * `document.body`), so its own `bubbles: true, composed: false` event
316
- * never reaches here on its own — the same reasoning `eun-scheduler`'s
317
- * `handleEditorCreate` documents for its own embedded (non-portaled but
318
- * still non-composed) editor.
354
+ * Re-dispatches `euntableaction` from `eun-table` itself, since the
355
+ * action bar is a sibling of this element, not a descendant.
319
356
  * @private
320
357
  */
321
358
  this.handlePortaledAction = (event) => {
322
359
  const actionEvent = event;
360
+ event.stopPropagation();
323
361
  this.dispatchEvent(new TableActionEvent(actionEvent.key, actionEvent.optionKey));
324
362
  };
325
363
  /** @private */
@@ -335,25 +373,37 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
335
373
  }
336
374
  connectedCallback() {
337
375
  super.connectedCallback?.();
338
- if (!this.hasAttribute("role")) {
339
- this.setAttribute("role", "grid");
376
+ adoptLightDomStyles(this, tableNativeStyles);
377
+ this.addEventListener("click", this.handleClick);
378
+ this.addEventListener("eunchange", this.handleCheckboxChange);
379
+ this.mutationObserver = new MutationObserver(this.handleMutations);
380
+ this.mutationObserver.observe(this, {
381
+ childList: true,
382
+ subtree: true,
383
+ attributes: true,
384
+ attributeFilter: ["data-row-key"],
385
+ });
386
+ if (this.hasUpdated) {
387
+ this.sync();
340
388
  }
341
- this.addEventListener("focusin", this.handleFocusIn);
342
389
  }
343
390
  disconnectedCallback() {
344
- this.removeEventListener("focusin", this.handleFocusIn);
345
- this.headerObserver?.disconnect();
391
+ this.removeEventListener("click", this.handleClick);
392
+ this.removeEventListener("eunchange", this.handleCheckboxChange);
393
+ this.mutationObserver?.disconnect();
394
+ this.mutationObserver = undefined;
346
395
  this.teardownActionBar();
347
396
  this.teardownLoadMoreSentinel();
348
397
  this.resizeObserver?.disconnect();
398
+ this.resizeObserver = undefined;
399
+ this.captionObserver?.disconnect();
400
+ this.captionObserver = undefined;
401
+ this.observedCaption = undefined;
349
402
  super.disconnectedCallback?.();
350
403
  }
351
404
  firstUpdated(changed) {
352
405
  super.firstUpdated(changed);
353
- if (this.virtualize) {
354
- this.syncLogicalItems();
355
- }
356
- this.syncChildren();
406
+ this.sync();
357
407
  this.ensureVirtualizeObservers();
358
408
  if (this.infinite) {
359
409
  this.setupTableLoadObserver();
@@ -361,33 +411,23 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
361
411
  }
362
412
  updated(changed) {
363
413
  super.updated(changed);
364
- if (changed.has("selectable")) {
365
- if (this.selectable) {
366
- this.setAttribute("aria-multiselectable", "true");
367
- }
368
- else {
369
- this.removeAttribute("aria-multiselectable");
370
- }
371
- }
372
- if (changed.has("syncing")) {
373
- if (this.syncing) {
374
- this.setAttribute("aria-busy", "true");
375
- }
376
- else {
377
- this.removeAttribute("aria-busy");
378
- }
379
- }
380
414
  const managedTriggers = ["columns", "data", "rowAction", "rowKey"];
381
- if (managedTriggers.some((prop) => changed.has(prop))) {
382
- this.regenerateManagedChildren();
415
+ if (this.columns && managedTriggers.some((prop) => changed.has(prop))) {
416
+ this.renderManagedTable();
383
417
  }
384
418
  const syncTriggers = [
419
+ "columns",
420
+ "data",
421
+ "rowAction",
422
+ "rowKey",
385
423
  "selectable",
386
- "checkboxAlwaysVisible",
387
- "actionAlwaysVisible",
388
424
  "stickyHeader",
389
- "stickyFirstColumn",
390
- "stickyLastColumn",
425
+ "loading",
426
+ "virtualize",
427
+ "infinite",
428
+ "hasMore",
429
+ "skeletonRows",
430
+ "syncing",
391
431
  "internalSelection",
392
432
  "actions",
393
433
  "selectedLabel",
@@ -398,12 +438,10 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
398
438
  "selectRowAriaLabel",
399
439
  "toolbarAriaLabel",
400
440
  "closeActionBarAriaLabel",
401
- "loading",
402
- "clickable",
403
- "syncing",
441
+ "locale",
404
442
  ];
405
443
  if (syncTriggers.some((prop) => changed.has(prop))) {
406
- this.syncChildren();
444
+ this.sync();
407
445
  }
408
446
  if (changed.has("virtualize")) {
409
447
  this.ensureVirtualizeObservers();
@@ -414,7 +452,9 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
414
452
  changed.has("bufferPages"))) {
415
453
  this.scheduleAnimationFrame(() => this.syncPages());
416
454
  }
417
- if (changed.has("infinite") || changed.has("rootMargin")) {
455
+ if (changed.has("infinite") ||
456
+ changed.has("rootMargin") ||
457
+ changed.has("loading")) {
418
458
  if (this.infinite) {
419
459
  this.setupTableLoadObserver();
420
460
  }
@@ -435,255 +475,421 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
435
475
  this.setSelection(new Set(keys));
436
476
  }
437
477
  /**
438
- * Regenerates this table's own light DOM from `columns`/`data`/
439
- * `rowAction`/`rowKey` — see the class doc's "Programmatic `columns`/
440
- * `data`" section. A no-op unless `columns` is set (light-DOM-authored
441
- * tables never call this). Previously-selected keys that still exist
442
- * among the regenerated rows stay selected ; ones that don't (a row was
443
- * removed from `data`) drop out of the selection.
478
+ * The slotted native table.
444
479
  * @private
445
480
  */
446
- regenerateManagedChildren() {
447
- const columns = this.columns;
448
- if (!columns) {
449
- return;
450
- }
451
- const header = buildManagedHeader(columns);
452
- const rows = this.data.map((row, index) => buildManagedItem(row, index, columns, this.rowKey, this.rowAction));
453
- this.detachedByPage.clear();
454
- this.pageHeights = [];
455
- this.mutatingSlot = true;
456
- this.replaceChildren(header, ...rows);
457
- queueMicrotask(() => {
458
- this.mutatingSlot = false;
459
- });
460
- const stillPresent = new Set(rows.map((item) => item.rowKey));
461
- const preserved = new Set([...this.internalSelection].filter((key) => stillPresent.has(key)));
462
- if (preserved.size !== this.internalSelection.size) {
463
- this.internalSelection = preserved;
464
- }
465
- if (this.virtualize) {
466
- this.syncLogicalItems();
467
- }
468
- this.syncChildren();
469
- this.restoreFocusIfLost();
481
+ get tableEl() {
482
+ return this.querySelector(":scope > table");
483
+ }
484
+ /**
485
+ * Whether any body row is currently selected.
486
+ * @private
487
+ */
488
+ get selectionActive() {
489
+ return this.selectable && this.internalSelection.size > 0;
490
+ }
491
+ /**
492
+ * Every visible or accessible text the table renders: its own property
493
+ * when set, else the `locale` dictionary's.
494
+ * @private
495
+ */
496
+ get labels() {
497
+ const strings = this.dictionary.table;
498
+ return {
499
+ ...strings,
500
+ loadingMore: this.loadingMoreLabel ?? strings.loadingMore,
501
+ selected: this.selectedLabel ?? strings.selected,
502
+ selectAll: this.selectAllLabel ?? strings.selectAll,
503
+ deselectAll: this.deselectAllLabel ?? strings.deselectAll,
504
+ selectAllRows: this.selectAllAriaLabel ?? strings.selectAllRows,
505
+ deselectAllRows: this.deselectAllAriaLabel ?? strings.deselectAllRows,
506
+ selectRow: this.selectRowAriaLabel ?? strings.selectRow,
507
+ selectionActions: this.toolbarAriaLabel ?? strings.selectionActions,
508
+ clearSelection: this.closeActionBarAriaLabel ?? strings.clearSelection,
509
+ };
510
+ }
511
+ /**
512
+ * Every body row this table currently knows about: the full logical set
513
+ * (including virtualized-out pages) while `virtualize` is set, the
514
+ * mounted rows otherwise.
515
+ * @private
516
+ */
517
+ currentRows() {
470
518
  if (this.virtualize) {
471
- this.scheduleAnimationFrame(() => this.syncPages());
519
+ return this.logicalRows;
472
520
  }
473
- if (this.infinite) {
474
- this.reobserveSentinel(this.sentinelEl);
521
+ const table = this.tableEl;
522
+ if (!table) {
523
+ return [];
475
524
  }
525
+ return Array.from(table.tBodies)
526
+ .filter((body) => !body.hasAttribute(SKELETON_ATTR))
527
+ .flatMap((body) => Array.from(body.rows))
528
+ .filter((row) => !row.classList.contains(SPACER_CLASS));
476
529
  }
477
530
  /**
478
- * Every `eun-table-item` this table currently knows about — the full
479
- * logical set (including virtualized-out pages) while `virtualize` is
480
- * set, the mounted-only `itemEls` otherwise.
531
+ * The body section `virtualize` operates on.
481
532
  * @private
482
533
  */
483
- currentItems() {
484
- return this.virtualize ? this.logicalItems : this.itemEls;
534
+ get bodyEl() {
535
+ const table = this.tableEl;
536
+ return table
537
+ ? Array.from(table.tBodies).find((body) => !body.hasAttribute(SKELETON_ATTR))
538
+ : undefined;
485
539
  }
486
540
  /**
487
- * Refocuses whatever row now sits at {@link lastFocusedRowIndex} once
488
- * {@link lastFocusedRow} itself has disconnected (removed outright, or
489
- * recreated wholesale by `regenerateManagedChildren`) while focus was
490
- * still on it or its content — otherwise that focus would just silently
491
- * fall back to the document body. Left alone if focus already landed
492
- * somewhere else on its own (the consumer moved it deliberately before
493
- * the removal), detected by the document's active element no longer
494
- * being the body.
541
+ * Pushes the selection and configuration state down onto the slotted
542
+ * table: generated selection cells, row state, action cells, the
543
+ * loading placeholder body, and the action bar. Its own mutations are
544
+ * dropped from the observer queue so they never trigger another sync.
495
545
  * @private
496
546
  */
497
- restoreFocusIfLost() {
498
- if (!this.lastFocusedRow || this.lastFocusedRow.isConnected) {
547
+ sync() {
548
+ if (this.virtualize) {
549
+ this.syncLogicalRows();
550
+ }
551
+ const table = this.tableEl;
552
+ if (table) {
553
+ this.syncHeader(table);
554
+ this.syncFooter(table);
555
+ this.syncRows(this.currentRows());
556
+ this.syncSkeleton(table);
557
+ this.syncAriaRows(table);
558
+ if (this.syncing || this.loading) {
559
+ table.setAttribute("aria-busy", "true");
560
+ }
561
+ else {
562
+ table.removeAttribute("aria-busy");
563
+ }
564
+ }
565
+ this.watchCaption(table?.caption ?? undefined);
566
+ this.toggleAttribute("selection-active", this.selectionActive);
567
+ this.syncActionBar();
568
+ this.mutationObserver?.takeRecords();
569
+ }
570
+ /**
571
+ * Reserves the height of the table's `<caption>` above the frame, where
572
+ * the light DOM sheet positions it, and keeps it up to date as the
573
+ * caption resizes.
574
+ * @private
575
+ */
576
+ watchCaption(caption) {
577
+ if (caption === this.observedCaption) {
499
578
  return;
500
579
  }
501
- const rowIndex = this.lastFocusedRowIndex;
502
- this.lastFocusedRow = undefined;
503
- this.lastFocusedRowIndex = -1;
504
- if (this.ownerDocument.activeElement !== this.ownerDocument.body) {
580
+ this.captionObserver?.disconnect();
581
+ this.observedCaption = caption;
582
+ if (!caption) {
583
+ this.style.removeProperty("--_table-caption-height");
505
584
  return;
506
585
  }
507
- this.focusRowAt(rowIndex);
586
+ const reserve = () => {
587
+ this.style.setProperty("--_table-caption-height", `${caption.getBoundingClientRect().height}px`);
588
+ };
589
+ this.captionObserver ??= new ResizeObserver(() => this.observedCaption ? reserve() : undefined);
590
+ this.captionObserver.observe(caption);
591
+ reserve();
508
592
  }
509
593
  /**
510
- * Re-scans the slotted header/items and pushes every derived/config value
511
- * down onto them — called after structural changes (`slotchange`, the
512
- * header's own `MutationObserver`, {@link regenerateManagedChildren}) and
513
- * whenever a reactive property that affects children changes (see
514
- * {@link updated}).
594
+ * Adds the selection column's leading cell to `row` if missing, and
595
+ * returns it.
515
596
  * @private
516
597
  */
517
- syncChildren() {
518
- const header = this.headerEls[0];
519
- const items = this.currentItems();
520
- this.watchHeaderColumns(header);
521
- this.updateColumnsTemplate(header, items);
522
- this.wireHeader(header);
523
- this.wireItems(items);
524
- this.syncActionBar();
598
+ ensureSelectCell(row, tagName) {
599
+ const first = row.cells[0];
600
+ if (first?.hasAttribute(SELECT_ATTR)) {
601
+ return first;
602
+ }
603
+ const cell = document.createElement(tagName);
604
+ cell.setAttribute(SELECT_ATTR, "");
605
+ row.prepend(cell);
606
+ return cell;
607
+ }
608
+ /** @private */
609
+ removeSelectCell(row) {
610
+ const first = row.cells[0];
611
+ if (first?.hasAttribute(SELECT_ATTR)) {
612
+ first.remove();
613
+ }
525
614
  }
526
615
  /**
527
- * (Re)installs {@link headerObserver} on `header`, only when it's actually
528
- * a different element than last time.
616
+ * Returns the checkbox inside a selection cell, creating it if missing.
529
617
  * @private
530
618
  */
531
- watchHeaderColumns(header) {
532
- if (this.observedHeader === header) {
619
+ ensureCheckbox(cell) {
620
+ let checkbox = cell.querySelector("eun-checkbox");
621
+ if (!checkbox) {
622
+ checkbox = document.createElement("eun-checkbox");
623
+ checkbox.setAttribute("tabindex", "0");
624
+ cell.replaceChildren(checkbox);
625
+ }
626
+ return checkbox;
627
+ }
628
+ /**
629
+ * Adds or removes the header's selection cell, holding the select-all
630
+ * checkbox.
631
+ * @private
632
+ */
633
+ syncHeader(table) {
634
+ const headerRows = table.tHead ? Array.from(table.tHead.rows) : [];
635
+ const [firstRow, ...otherRows] = headerRows;
636
+ otherRows.forEach((row) => this.removeSelectCell(row));
637
+ if (!firstRow) {
533
638
  return;
534
639
  }
535
- this.headerObserver?.disconnect();
536
- this.observedHeader = header;
537
- if (!header) {
640
+ if (!this.selectable) {
641
+ this.removeSelectCell(firstRow);
538
642
  return;
539
643
  }
540
- this.headerObserver = new MutationObserver(() => this.syncChildren());
541
- this.headerObserver.observe(header, {
542
- childList: true,
543
- attributes: true,
544
- attributeFilter: ["width"],
545
- subtree: true,
644
+ const cell = this.ensureSelectCell(firstRow, "th");
645
+ cell.setAttribute("scope", "col");
646
+ cell.rowSpan = headerRows.length;
647
+ const labels = this.labels;
648
+ const total = this.currentRows().length;
649
+ const selectedCount = this.internalSelection.size;
650
+ const allSelected = total > 0 && selectedCount === total;
651
+ const checkbox = this.ensureCheckbox(cell);
652
+ checkbox.checked = allSelected;
653
+ checkbox.indeterminate = selectedCount > 0 && !allSelected;
654
+ checkbox.disabled = total === 0;
655
+ checkbox.setAttribute("aria-label", allSelected ? labels.deselectAllRows : labels.selectAllRows);
656
+ }
657
+ /**
658
+ * Keeps footer rows aligned with an empty selection cell.
659
+ * @private
660
+ */
661
+ syncFooter(table) {
662
+ const rows = table.tFoot ? Array.from(table.tFoot.rows) : [];
663
+ rows.forEach((row) => {
664
+ if (this.selectable) {
665
+ this.ensureSelectCell(row, "td").replaceChildren();
666
+ }
667
+ else {
668
+ this.removeSelectCell(row);
669
+ }
546
670
  });
547
671
  }
548
672
  /**
549
- * Rebuilds the shared `grid-template-columns` track list from the
550
- * header's own `eun-table-column` children's `width`s, and whether any
551
- * item currently has action content — see the class doc's CSS Subgrid
552
- * section.
673
+ * Resolves each body row's key and pushes its selection and action cell
674
+ * state.
553
675
  * @private
554
676
  */
555
- updateColumnsTemplate(header, items) {
556
- const columns = header
557
- ? Array.from(header.querySelectorAll(":scope > eun-table-column"))
558
- : [];
559
- const widths = columns.map((column) => column.width);
560
- this.hasAction = items.some((item) => !!item.querySelector(':scope > [slot="action"]'));
561
- const template = computeColumnsTemplate(widths, {
562
- selectable: this.selectable,
563
- hasAction: this.hasAction,
677
+ syncRows(rows) {
678
+ const rowKeys = new Map();
679
+ const selectionActive = this.selectionActive;
680
+ const labels = this.labels;
681
+ rows.forEach((row, index) => {
682
+ const key = resolveRowKey(row.dataset.rowKey, index);
683
+ rowKeys.set(row, key);
684
+ const selected = this.selectable && this.internalSelection.has(key);
685
+ row.toggleAttribute(SELECTED_ATTR, selected);
686
+ if (this.selectable) {
687
+ const checkbox = this.ensureCheckbox(this.ensureSelectCell(row, "td"));
688
+ checkbox.checked = selected;
689
+ checkbox.setAttribute("aria-label", `${labels.selectRow} ${this.rowName(row) ?? index + 1}`);
690
+ }
691
+ else {
692
+ this.removeSelectCell(row);
693
+ }
694
+ for (const cell of Array.from(row.cells)) {
695
+ if (cell.hasAttribute(ACTION_ATTR)) {
696
+ cell.inert = selectionActive;
697
+ }
698
+ }
564
699
  });
565
- this.style.setProperty("--_columns", template);
700
+ this.rowKeys = rowKeys;
566
701
  }
567
- /** @private */
568
- wireHeader(header) {
569
- if (!header) {
702
+ /**
703
+ * The name of a body row: the text of its row header (`<th scope="row">`,
704
+ * or else its first `<th>`), used to label its checkbox.
705
+ * @private
706
+ */
707
+ rowName(row) {
708
+ const header = row.querySelector(':scope > th[scope="row"]') ??
709
+ row.querySelector(`:scope > th:not([${SELECT_ATTR}])`);
710
+ const name = header?.textContent?.replace(/\s+/g, " ").trim();
711
+ return name || undefined;
712
+ }
713
+ /**
714
+ * Gives every row its position and the table its total row count while
715
+ * `virtualize` is set, so assistive technology announces the real size
716
+ * of the table even though only part of its rows are in the DOM. The
717
+ * count is `-1` (unknown) while `infinite` can still load more.
718
+ * @private
719
+ */
720
+ syncAriaRows(table) {
721
+ if (!this.virtualize) {
722
+ if (this.ariaRowsApplied) {
723
+ table.removeAttribute("aria-rowcount");
724
+ table
725
+ .querySelectorAll("tr[aria-rowindex]")
726
+ .forEach((row) => row.removeAttribute("aria-rowindex"));
727
+ this.ariaRowsApplied = false;
728
+ }
570
729
  return;
571
730
  }
572
- header.selectable = this.selectable;
573
- header.hasAction = this.hasAction;
574
- header.sticky = this.stickyHeader;
575
- header.stickyFirstColumn = this.stickyFirstColumn;
576
- header.stickyLastColumn = this.stickyLastColumn;
577
- header.selectedCount = this.internalSelection.size;
578
- header.totalCount = this.currentItems().length;
579
- header.selectAllAriaLabel = this.selectAllAriaLabel;
580
- header.deselectAllAriaLabel = this.deselectAllAriaLabel;
581
- header.onSelectAllToggle = () => this.toggleSelectAll();
582
- header.requestAdjacentRow = (direction) => this.moveFocus(this.rowIndexOf(header), direction);
583
- header.requestGridEdge = (edge) => this.moveToGridEdge(edge);
731
+ const rows = [
732
+ ...(table.tHead ? Array.from(table.tHead.rows) : []),
733
+ ...this.logicalRows,
734
+ ...(table.tFoot ? Array.from(table.tFoot.rows) : []),
735
+ ];
736
+ table.setAttribute("aria-rowcount", this.infinite && this.hasMore ? "-1" : String(rows.length));
737
+ rows.forEach((row, index) => row.setAttribute("aria-rowindex", String(index + 1)));
738
+ this.ariaRowsApplied = true;
584
739
  }
585
- /** @private */
586
- wireItems(items) {
587
- items.forEach((item, index) => {
588
- const key = resolveRowKey(item.rowKey, index);
589
- item.resolvedKey = key;
590
- item.selectable = this.selectable;
591
- item.hasAction = this.hasAction;
592
- item.checkboxAlwaysVisible = this.checkboxAlwaysVisible;
593
- item.actionAlwaysVisible = this.actionAlwaysVisible;
594
- item.stickyFirstColumn = this.stickyFirstColumn;
595
- item.stickyLastColumn = this.stickyLastColumn;
596
- item.clickable = this.clickable;
597
- item.syncing = this.syncing;
598
- item.selected = this.internalSelection.has(key);
599
- item.selectionActive = this.selectable && this.internalSelection.size > 0;
600
- item.selectCheckboxLabel = `${this.selectRowAriaLabel} ${index + 1}`;
601
- item.onToggleSelected = () => this.toggleRow(key);
602
- item.requestAdjacentRow = (direction) => this.moveFocus(this.rowIndexOf(item), direction);
603
- item.requestGridEdge = (edge) => this.moveToGridEdge(edge);
604
- });
740
+ /**
741
+ * Announces how many rows the last `eunloadmore` added, once they're in.
742
+ * @private
743
+ */
744
+ announceLoadedRows() {
745
+ if (this.rowCountBeforeLoad === undefined || !this.announcerEl) {
746
+ return;
747
+ }
748
+ const added = this.currentRows().length - this.rowCountBeforeLoad;
749
+ if (added <= 0) {
750
+ return;
751
+ }
752
+ this.rowCountBeforeLoad = undefined;
753
+ this.announcerEl.textContent = this.dictionary.table.rowsLoaded(added);
605
754
  }
606
755
  /**
607
- * Every row, header first — the space `ArrowUp`/`ArrowDown`/`Ctrl+Home`/
608
- * `Ctrl+End` move across. Excludes every `eun-table-item` while `loading`
609
- * is set, since they're hidden (`display: none`) and shouldn't be
610
- * reachable ; otherwise the full logical set (mounted or virtualized-out)
611
- * while `virtualize` is on, so `Ctrl+End`/`ArrowDown` math stays correct
612
- * regardless of what's currently mounted (see {@link ensureRowMounted}).
756
+ * Adds the placeholder body while `loading`, shaped after the header's
757
+ * own cells so its columns line up, and removes it otherwise.
613
758
  * @private
614
759
  */
615
- allRows() {
616
- const header = this.headerEls[0];
617
- if (this.loading) {
618
- return header ? [header] : [];
760
+ syncSkeleton(table) {
761
+ const existing = table.querySelector(`:scope > tbody[${SKELETON_ATTR}]`);
762
+ if (!this.loading) {
763
+ existing?.remove();
764
+ return;
765
+ }
766
+ const headerCells = table.tHead?.rows[0]
767
+ ? Array.from(table.tHead.rows[0].cells).filter((cell) => !cell.hasAttribute(SELECT_ATTR))
768
+ : [];
769
+ const signature = [
770
+ this.selectable,
771
+ this.skeletonRows,
772
+ ...headerCells.map((cell) => `${cell.colSpan}${cell.hasAttribute(ACTION_ATTR) ? "a" : ""}`),
773
+ ].join(",");
774
+ if (existing?.dataset.tableSkeleton === signature) {
775
+ return;
776
+ }
777
+ const body = document.createElement("tbody");
778
+ body.setAttribute(SKELETON_ATTR, signature);
779
+ body.setAttribute("aria-hidden", "true");
780
+ for (let index = 0; index < this.skeletonRows; index++) {
781
+ const row = body.insertRow();
782
+ row.style.setProperty("--_row-index", String(index));
783
+ if (this.selectable) {
784
+ const cell = row.insertCell();
785
+ cell.setAttribute(SELECT_ATTR, "");
786
+ cell.append(this.createSkeleton("18px", "18px", "4px"));
787
+ }
788
+ for (const headerCell of headerCells) {
789
+ const cell = row.insertCell();
790
+ cell.colSpan = headerCell.colSpan;
791
+ if (headerCell.hasAttribute(ACTION_ATTR)) {
792
+ cell.setAttribute(ACTION_ATTR, "");
793
+ cell.append(this.createSkeleton("28px", "28px", "8px"));
794
+ }
795
+ else {
796
+ cell.append(this.createSkeleton("50%", "14px", "4px"));
797
+ }
798
+ }
799
+ }
800
+ if (existing) {
801
+ existing.replaceWith(body);
802
+ return;
803
+ }
804
+ const bodies = Array.from(table.tBodies);
805
+ const lastBody = bodies[bodies.length - 1];
806
+ if (lastBody) {
807
+ lastBody.after(body);
808
+ }
809
+ else if (table.tFoot) {
810
+ table.tFoot.before(body);
811
+ }
812
+ else {
813
+ table.append(body);
619
814
  }
620
- const items = this.currentItems();
621
- return header ? [header, ...items] : [...items];
622
815
  }
623
816
  /** @private */
624
- rowIndexOf(row) {
625
- return this.allRows().indexOf(row);
817
+ createSkeleton(width, height, radius) {
818
+ const skeleton = document.createElement("eun-skeleton");
819
+ skeleton.setAttribute("variant", "rect");
820
+ skeleton.setAttribute("width", width);
821
+ skeleton.setAttribute("height", height);
822
+ skeleton.setAttribute("radius", radius);
823
+ if (width.endsWith("%")) {
824
+ skeleton.style.width = width;
825
+ }
826
+ return skeleton;
626
827
  }
627
828
  /**
628
- * Moves focus to the row before/after `rowIndex` — `ArrowUp`/`ArrowDown`
629
- * delegated up from a row's own `TableRowMixin`. Every row is always its
630
- * own permanent `Tab` stop (see `TableRowMixin`'s own class doc), so this
631
- * only ever needs to resolve *which* row and hand it a plain `.focus()`,
632
- * no column bookkeeping of any kind.
829
+ * The body row `target` belongs to, if any.
633
830
  * @private
634
831
  */
635
- moveFocus(rowIndex, direction) {
636
- this.focusRowAt(rowIndex + direction);
832
+ bodyRowOf(target) {
833
+ const row = target.closest?.("tr");
834
+ return row && this.rowKeys.has(row) ? row : undefined;
637
835
  }
638
836
  /**
639
- * Moves focus to the header (`Ctrl+Home`) or the last row (`Ctrl+End`).
837
+ * Whether the click went to interactive content of `row` (a link, a
838
+ * button, an input, a nested eunomia control) rather than to the row.
640
839
  * @private
641
840
  */
642
- moveToGridEdge(edge) {
643
- const rows = this.allRows();
644
- if (!rows.length) {
645
- return;
841
+ isInteractiveClick(event, row) {
842
+ for (const node of event.composedPath()) {
843
+ if (node === row) {
844
+ return false;
845
+ }
846
+ if (node instanceof Element && node.matches(INTERACTIVE_SELECTOR)) {
847
+ return true;
848
+ }
646
849
  }
647
- this.focusRowAt(edge === "start" ? 0 : rows.length - 1);
850
+ return false;
648
851
  }
649
852
  /**
650
- * Moves focus to `rows[rowIndex]`, clamped to the grid's own bounds,
651
- * mounting its page first if it's currently virtualized-out.
853
+ * Toggles a row while rows are selected, otherwise fires
854
+ * {@link TableRowClickEvent}.
652
855
  * @private
653
856
  */
654
- focusRowAt(rowIndex) {
655
- const rows = this.allRows();
656
- if (!rows.length) {
857
+ activateRow(row, event) {
858
+ const key = this.rowKeys.get(row) ?? "";
859
+ if (this.selectionActive) {
860
+ this.toggleRow(key);
657
861
  return;
658
862
  }
659
- const clampedRow = Math.max(0, Math.min(rowIndex, rows.length - 1));
660
- this.ensureRowMounted(rows, clampedRow);
661
- this.allRows()[clampedRow]?.focus();
863
+ const rowClick = new TableRowClickEvent(key);
864
+ this.dispatchEvent(rowClick);
865
+ if (!rowClick.defaultPrevented) {
866
+ this.followRowLink(row, event);
867
+ }
662
868
  }
663
869
  /**
664
- * Mounts `rows[rowIndex]`'s page first if it's a `eun-table-item`
665
- * currently virtualized-out — the same "mount before focusing" fix
666
- * `eun-infinite-list`'s own `#focusItem` applies, needed because
667
- * `Ctrl+End`/a long `ArrowDown` run can target a row several pages away
668
- * from what's currently mounted.
870
+ * Follows the row's `data-table-row-link` link, if any, keeping the
871
+ * modifier keys so a `Ctrl`/`Cmd` click still opens a new tab. The link
872
+ * stays the row's one keyboard-reachable way to do the same.
669
873
  * @private
670
874
  */
671
- ensureRowMounted(rows, rowIndex) {
672
- if (!this.virtualize) {
673
- return;
674
- }
675
- const row = rows[rowIndex];
676
- if (!row || row.isConnected) {
875
+ followRowLink(row, event) {
876
+ const marker = row.querySelector(`[${ROW_LINK_ATTR}]`);
877
+ if (!marker) {
677
878
  return;
678
879
  }
679
- const logicalIndex = this.logicalItems.indexOf(row);
680
- if (logicalIndex < 0) {
681
- return;
682
- }
683
- const page = Math.floor(logicalIndex / this.pageSize);
684
- if (this.mountPage(page)) {
685
- this.syncChildren();
686
- }
880
+ const link = (marker.matches("a[href]") ? marker : null) ??
881
+ marker.querySelector("a[href]") ??
882
+ marker.shadowRoot?.querySelector("a[href]") ??
883
+ marker;
884
+ link.dispatchEvent(new MouseEvent("click", {
885
+ bubbles: true,
886
+ cancelable: true,
887
+ composed: true,
888
+ ctrlKey: event.ctrlKey,
889
+ metaKey: event.metaKey,
890
+ shiftKey: event.shiftKey,
891
+ altKey: event.altKey,
892
+ }));
687
893
  }
688
894
  /** @private */
689
895
  toggleRow(key) {
@@ -698,21 +904,13 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
698
904
  }
699
905
  /** @private */
700
906
  toggleSelectAll() {
701
- const items = this.currentItems();
702
- const allSelected = items.length > 0 && this.internalSelection.size === items.length;
703
- if (allSelected) {
704
- this.setSelection(new Set());
705
- return;
706
- }
707
- this.setSelection(new Set(items.map((item, index) => resolveRowKey(item.rowKey, index))));
907
+ const keys = Array.from(this.rowKeys.values());
908
+ const allSelected = keys.length > 0 && keys.every((key) => this.internalSelection.has(key));
909
+ this.setSelection(allSelected ? new Set() : new Set(keys));
708
910
  }
709
911
  /**
710
- * Applies a new selection and fires {@link TableSelectionChangeEvent} —
711
- * the single path both internal interactions (a checkbox, select-all) and
712
- * an external `selectedKeys` assignment funnel through, so the event
713
- * fires consistently either way (the same "always fires, whatever the
714
- * source" convention `eun-scheduler`'s own `eunperiodchange` uses for its
715
- * controlled `view`/`displayedDate`).
912
+ * Applies a new selection and fires {@link TableSelectionChangeEvent},
913
+ * whatever the source.
716
914
  * @private
717
915
  */
718
916
  setSelection(next) {
@@ -720,25 +918,27 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
720
918
  this.dispatchEvent(new TableSelectionChangeEvent(Array.from(next)));
721
919
  }
722
920
  /**
723
- * Creates the body-portaled action bar the first time it's needed.
921
+ * Creates the action bar the first time it's needed, right after the
922
+ * table so it comes next in the `Tab` order, and shows it as a manual
923
+ * popover so it renders above the page whatever the table's ancestors
924
+ * clip or transform.
724
925
  * @private
725
926
  */
726
927
  ensureActionBar() {
727
928
  if (!this.actionBarEl) {
728
- this.actionBarEl = document.createElement(EunomiaTableActionBarTagName);
729
- this.actionBarEl.addEventListener(TableActionEvent.eventName, this.handlePortaledAction);
730
- document.body.appendChild(this.actionBarEl);
929
+ const bar = document.createElement(EunomiaTableActionBarTagName);
930
+ bar.setAttribute("popover", "manual");
931
+ bar.addEventListener(TableActionEvent.eventName, this.handlePortaledAction);
932
+ this.after(bar);
933
+ bar.showPopover?.();
934
+ this.actionBarEl = bar;
731
935
  }
732
936
  return this.actionBarEl;
733
937
  }
734
938
  /**
735
- * Tears down the body-portaled action bar. If focus is currently inside
736
- * it (the close button, most often), removing it would otherwise drop
737
- * focus to the document body the same way removing a focused row would
738
- * (see {@link restoreFocusIfLost}) — except the bar lives outside this
739
- * component's own DOM subtree entirely, so that row-focused mechanism
740
- * can't see it ; this restores focus back onto the grid itself (its
741
- * header, or its first row absent one) instead.
939
+ * Tears down the action bar. If focus was inside it, it
940
+ * moves back to the table's first selection checkbox rather than
941
+ * falling to the document body.
742
942
  * @private
743
943
  */
744
944
  teardownActionBar() {
@@ -751,16 +951,16 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
751
951
  this.actionBarEl.remove();
752
952
  this.actionBarEl = undefined;
753
953
  if (hadFocus) {
754
- this.focusRowAt(0);
954
+ this.querySelector(`[${SELECT_ATTR}] eun-checkbox:not([disabled])`)?.focus();
755
955
  }
756
956
  }
757
957
  /**
758
- * Mounts/updates/unmounts the floating action bar based on the current
759
- * selection.
958
+ * Mounts, updates, or unmounts the floating action bar based on the
959
+ * current selection.
760
960
  * @private
761
961
  */
762
962
  syncActionBar() {
763
- if (!this.selectable || this.internalSelection.size === 0) {
963
+ if (!this.selectionActive) {
764
964
  this.teardownActionBar();
765
965
  return;
766
966
  }
@@ -770,41 +970,142 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
770
970
  document.addEventListener("keydown", this.handleEscapeClear);
771
971
  }
772
972
  bar.selectedCount = this.internalSelection.size;
773
- bar.totalCount = this.currentItems().length;
973
+ bar.totalCount = this.rowKeys.size;
774
974
  bar.actions = this.actions;
775
- bar.toolbarAriaLabel = this.toolbarAriaLabel;
776
- bar.selectedLabel = this.selectedLabel;
777
- bar.selectAllLabel = this.selectAllLabel;
778
- bar.deselectAllLabel = this.deselectAllLabel;
779
- bar.closeAriaLabel = this.closeActionBarAriaLabel;
975
+ const labels = this.labels;
976
+ bar.toolbarAriaLabel = labels.selectionActions;
977
+ bar.selectedLabel = labels.selected;
978
+ bar.selectAllLabel = labels.selectAll;
979
+ bar.deselectAllLabel = labels.deselectAll;
980
+ bar.closeAriaLabel = labels.clearSelection;
780
981
  bar.onSelectAll = () => this.toggleSelectAll();
781
982
  bar.onClose = () => this.setSelection(new Set());
782
983
  }
783
984
  /**
784
- * Rebuilds {@link logicalItems} from the current light DOM, reconciling it
785
- * with whatever's still cached behind a spacer — the same reconciliation
786
- * `eun-infinite-list`'s own `syncItemsFromLightDom` performs, scoped here
787
- * to `eun-table-item` children only (the header is excluded, it's never
788
- * virtualized). Also recomputes {@link mountedPages} from the rebuilt
789
- * page count. A no-op unless `virtualize` is set.
985
+ * Renders the native table from `columns`/`data`/`rowAction`/`rowKey`
986
+ * into this element's light DOM. Previously selected keys that no
987
+ * longer match a row drop out of the selection.
988
+ * @private
989
+ */
990
+ renderManagedTable() {
991
+ const columns = this.columns ?? [];
992
+ const keys = this.data.map((row, index) => this.rowKey ? this.rowKey(row, index) : resolveRowKey(undefined, index));
993
+ this.mountAllPages();
994
+ render(html `<table>
995
+ <thead>
996
+ <tr>
997
+ ${columns.map((column) => html `<th
998
+ scope="col"
999
+ style=${ifDefined(this.columnStyle(column, true))}
1000
+ aria-sort=${ifDefined(column.sort)}
1001
+ >
1002
+ ${column.label}
1003
+ </th>`)}
1004
+ ${this.rowAction
1005
+ ? html `<th scope="col" data-table-action>
1006
+ <span class="eun-table-visually-hidden"
1007
+ >${this.rowAction.label ?? this.labels.rowActions}</span
1008
+ >
1009
+ </th>`
1010
+ : nothing}
1011
+ </tr>
1012
+ </thead>
1013
+ <tbody>
1014
+ ${repeat(this.data, (_, index) => keys[index], (row, index) => html `<tr data-row-key=${keys[index]}>
1015
+ ${columns.map((column) => html `<td
1016
+ style=${ifDefined(this.columnStyle(column, false))}
1017
+ >
1018
+ ${this.renderManagedCell(column, row, index)}
1019
+ </td>`)}
1020
+ ${this.rowAction
1021
+ ? html `<td data-table-action>
1022
+ ${this.renderRowAction(this.rowAction, row, index)}
1023
+ </td>`
1024
+ : nothing}
1025
+ </tr>`)}
1026
+ </tbody>
1027
+ </table>`, this);
1028
+ const stillPresent = new Set(keys);
1029
+ const preserved = new Set([...this.internalSelection].filter((key) => stillPresent.has(key)));
1030
+ if (preserved.size !== this.internalSelection.size) {
1031
+ this.internalSelection = preserved;
1032
+ }
1033
+ if (this.virtualize) {
1034
+ this.scheduleAnimationFrame(() => this.syncPages());
1035
+ }
1036
+ if (this.infinite) {
1037
+ this.reobserveSentinel(this.sentinelEl);
1038
+ }
1039
+ }
1040
+ /** @private */
1041
+ columnStyle(column, header) {
1042
+ const declarations = [];
1043
+ if (header && column.width) {
1044
+ declarations.push(`width: ${column.width}`);
1045
+ }
1046
+ const align = alignToTextAlign(column.align);
1047
+ if (align) {
1048
+ declarations.push(`text-align: ${align}`);
1049
+ }
1050
+ return declarations.length ? declarations.join("; ") : undefined;
1051
+ }
1052
+ /** @private */
1053
+ renderManagedCell(column, row, index) {
1054
+ const value = row[column.key];
1055
+ if (column.render) {
1056
+ return column.render(value, row, index);
1057
+ }
1058
+ return value === null || value === undefined ? "" : String(value);
1059
+ }
1060
+ /**
1061
+ * Renders a row's `rowAction`: a `eun-dropdown-button` for
1062
+ * `type: "dropdown"`, a `eun-button` otherwise.
1063
+ * @private
1064
+ */
1065
+ renderRowAction(action, row, index) {
1066
+ if (action.type === "dropdown") {
1067
+ return html `<eun-dropdown-button
1068
+ size="xs"
1069
+ appearance="ghost"
1070
+ icon=${ifDefined(action.icon)}
1071
+ label=${ifDefined(action.icon ? undefined : action.label)}
1072
+ aria-label=${action.label ?? this.labels.rowActions}
1073
+ .options=${action.options(row)}
1074
+ .disabledKeys=${action.disabledKeys?.(row) ?? []}
1075
+ ?disabled=${action.disabled?.(row) ?? false}
1076
+ @eunselect=${(event) => action.onSelect(event.key, row, index)}
1077
+ ></eun-dropdown-button>`;
1078
+ }
1079
+ return html `<eun-button
1080
+ size="xs"
1081
+ appearance=${action.color ? "default" : "ghost"}
1082
+ color=${ifDefined(action.color)}
1083
+ aria-label=${action.label}
1084
+ ?disabled=${action.disabled?.(row) ?? false}
1085
+ @click=${() => action.onClick(row, index)}
1086
+ >
1087
+ ${action.icon
1088
+ ? html `<eun-icon name=${action.icon} size="16"></eun-icon>`
1089
+ : action.label}
1090
+ </eun-button>`;
1091
+ }
1092
+ /**
1093
+ * Rebuilds {@link logicalRows} from the body, threading each spacer's
1094
+ * cached rows back into their position, and recomputes
1095
+ * {@link mountedPages}.
790
1096
  * @private
791
1097
  */
792
- syncLogicalItems() {
1098
+ syncLogicalRows() {
793
1099
  const rebuilt = [];
794
- for (const child of Array.from(this.children)) {
795
- if (!(child instanceof HTMLElement)) {
796
- continue;
1100
+ for (const row of Array.from(this.bodyEl?.rows ?? [])) {
1101
+ if (row.classList.contains(SPACER_CLASS)) {
1102
+ rebuilt.push(...(this.detachedByPage.get(Number(row.dataset.eunPage)) ?? []));
797
1103
  }
798
- if (child.classList.contains(TABLE_ROW_SPACER_CLASS)) {
799
- const page = Number(child.dataset.eunPage);
800
- rebuilt.push(...(this.detachedByPage.get(page) ?? []));
801
- continue;
802
- }
803
- if (child.tagName.toLowerCase() === EunomiaTableItemTagName) {
804
- rebuilt.push(child);
1104
+ else {
1105
+ rebuilt.push(row);
805
1106
  }
806
1107
  }
807
- this.logicalItems = rebuilt;
1108
+ this.logicalRows = rebuilt;
808
1109
  const totalPages = Math.ceil(rebuilt.length / this.pageSize);
809
1110
  const mountedPages = new Set();
810
1111
  for (let page = 0; page < totalPages; page++) {
@@ -814,14 +1115,27 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
814
1115
  }
815
1116
  this.mountedPages = mountedPages;
816
1117
  }
1118
+ /**
1119
+ * Remounts every virtualized-out page, before the light DOM gets
1120
+ * re-rendered wholesale.
1121
+ * @private
1122
+ */
1123
+ mountAllPages() {
1124
+ for (const page of Array.from(this.detachedByPage.keys())) {
1125
+ this.mountPage(page);
1126
+ }
1127
+ this.detachedByPage.clear();
1128
+ this.pageHeights = [];
1129
+ this.mutationObserver?.takeRecords();
1130
+ }
817
1131
  /**
818
1132
  * Mounts the page range around the current scroll position and
819
- * virtualizes everything outside it, once the known row count passes
820
- * `virtualizeThreshold` ; below that, every page simply stays mounted.
1133
+ * virtualizes everything outside it, once the row count passes
1134
+ * `virtualizeThreshold`.
821
1135
  * @private
822
1136
  */
823
1137
  syncPages() {
824
- const totalRows = this.logicalItems.length;
1138
+ const totalRows = this.logicalRows.length;
825
1139
  if (totalRows === 0) {
826
1140
  return;
827
1141
  }
@@ -833,7 +1147,7 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
833
1147
  }
834
1148
  }
835
1149
  else {
836
- if (this.clientHeight === 0) {
1150
+ if (!this.frameEl?.clientHeight) {
837
1151
  return;
838
1152
  }
839
1153
  const centerPage = this.estimateCenterPage(totalPages);
@@ -849,19 +1163,17 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
849
1163
  }
850
1164
  }
851
1165
  if (changed) {
852
- this.syncChildren();
1166
+ this.sync();
853
1167
  }
854
1168
  }
855
1169
  /**
856
- * Estimates which page the middle of the current viewport falls into, by
857
- * walking cumulative page heights — measured ones where known, an
858
- * average-based estimate otherwise. `this` is the scroll container itself
859
- * (`:host` sets `overflow: auto`, see table.styles.ts), so its own
860
- * `scrollTop`/`clientHeight` are read directly.
1170
+ * Estimates which page the middle of the viewport falls into, from
1171
+ * measured page heights where known, an average otherwise.
861
1172
  * @private
862
1173
  */
863
1174
  estimateCenterPage(totalPages) {
864
- const target = this.scrollTop + this.clientHeight / 2;
1175
+ const frame = this.frameEl;
1176
+ const target = frame ? frame.scrollTop + frame.clientHeight / 2 : 0;
865
1177
  let offset = 0;
866
1178
  for (let page = 0; page < totalPages; page++) {
867
1179
  const height = this.pageHeights[page] ?? this.estimatePageHeight(page);
@@ -874,55 +1186,38 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
874
1186
  }
875
1187
  /** @private */
876
1188
  estimatePageHeight(page) {
877
- const totalPages = Math.ceil(this.logicalItems.length / this.pageSize);
1189
+ const totalPages = Math.ceil(this.logicalRows.length / this.pageSize);
878
1190
  const rowsInPage = page === totalPages - 1
879
- ? this.logicalItems.length - page * this.pageSize
1191
+ ? this.logicalRows.length - page * this.pageSize
880
1192
  : this.pageSize;
881
1193
  return rowsInPage * this.avgRowHeight;
882
1194
  }
883
1195
  /**
884
- * Remounts a virtualized-out page by reattaching its cached nodes in
885
- * place of its spacer. Returns whether it actually did anything (a no-op
886
- * if already mounted), so {@link syncPages} only re-syncs children once
887
- * per batch rather than once per page.
1196
+ * Remounts a virtualized-out page in place of its spacer. Returns
1197
+ * whether it did anything.
888
1198
  * @private
889
1199
  */
890
1200
  mountPage(page) {
891
- if (this.mountedPages.has(page)) {
892
- return false;
893
- }
894
1201
  const cached = this.detachedByPage.get(page);
895
- if (!cached || cached.length === 0) {
1202
+ if (this.mountedPages.has(page) || !cached?.length) {
896
1203
  return false;
897
1204
  }
898
- const spacer = this.querySelector(`.${TABLE_ROW_SPACER_CLASS}[data-eun-page="${page}"]`);
899
- this.mutatingSlot = true;
1205
+ const spacer = this.bodyEl?.querySelector(`:scope > .${SPACER_CLASS}[data-eun-page="${page}"]`);
900
1206
  if (spacer) {
901
- for (const row of cached) {
902
- spacer.before(row);
903
- }
1207
+ spacer.before(...cached);
904
1208
  spacer.remove();
905
1209
  }
906
1210
  else {
907
- for (const row of cached) {
908
- this.append(row);
909
- }
1211
+ this.bodyEl?.append(...cached);
910
1212
  }
911
- queueMicrotask(() => {
912
- this.mutatingSlot = false;
913
- });
914
1213
  this.detachedByPage.delete(page);
915
1214
  this.mountedPages.add(page);
916
1215
  return true;
917
1216
  }
918
1217
  /**
919
- * Virtualizes out a mounted page : measures its current rendered height
920
- * (used both as its own spacer size and to refine {@link avgRowHeight}),
921
- * replaces its rows with a single full-width (`grid-column: 1 / -1`)
922
- * spacer of that height so the scrollbar/scroll position don't jump, and
923
- * caches the actual nodes for an exact reattach later. Returns whether it
924
- * actually did anything (a no-op if already virtualized-out, or if the
925
- * page can't be measured — not currently rendered with real layout).
1218
+ * Replaces a mounted page's rows with a single spacer row of the same
1219
+ * measured height, caching the rows for an exact reattach later.
1220
+ * Returns whether it did anything.
926
1221
  * @private
927
1222
  */
928
1223
  unmountPage(page) {
@@ -930,8 +1225,8 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
930
1225
  return false;
931
1226
  }
932
1227
  const start = page * this.pageSize;
933
- const end = Math.min(start + this.pageSize, this.logicalItems.length);
934
- const rows = this.logicalItems
1228
+ const end = Math.min(start + this.pageSize, this.logicalRows.length);
1229
+ const rows = this.logicalRows
935
1230
  .slice(start, end)
936
1231
  .filter((row) => row.isConnected);
937
1232
  if (rows.length === 0) {
@@ -945,26 +1240,21 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
945
1240
  }
946
1241
  this.pageHeights[page] = height;
947
1242
  this.avgRowHeight = (this.avgRowHeight + height / rows.length) / 2;
948
- const spacer = document.createElement("div");
949
- spacer.className = TABLE_ROW_SPACER_CLASS;
1243
+ const spacer = document.createElement("tr");
1244
+ spacer.className = SPACER_CLASS;
950
1245
  spacer.dataset.eunPage = String(page);
951
- spacer.style.gridColumn = "1 / -1";
952
- spacer.style.blockSize = `${height}px`;
953
1246
  spacer.setAttribute("aria-hidden", "true");
954
- this.mutatingSlot = true;
1247
+ const cell = spacer.insertCell();
1248
+ cell.colSpan = 1000;
1249
+ cell.style.blockSize = `${height}px`;
955
1250
  first.before(spacer);
956
- for (const row of rows) {
957
- row.remove();
958
- }
959
- queueMicrotask(() => {
960
- this.mutatingSlot = false;
961
- });
1251
+ rows.forEach((row) => row.remove());
962
1252
  this.detachedByPage.set(page, rows);
963
1253
  this.mountedPages.delete(page);
964
1254
  return true;
965
1255
  }
966
1256
  /**
967
- * (Re)creates {@link resizeObserver}/the scroll listener once `virtualize`
1257
+ * (Re)creates the resize observer and scroll listener once `virtualize`
968
1258
  * is set, and tears them down once it's cleared.
969
1259
  * @private
970
1260
  */
@@ -972,31 +1262,31 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
972
1262
  if (!this.virtualize) {
973
1263
  this.resizeObserver?.disconnect();
974
1264
  this.resizeObserver = undefined;
975
- this.removeEventListener("scroll", this.handleTableScroll);
1265
+ this.frameEl?.removeEventListener("scroll", this.handleTableScroll);
976
1266
  if (this.syncHandle) {
977
1267
  cancelAnimationFrame(this.syncHandle);
978
1268
  this.syncHandle = undefined;
979
1269
  }
980
1270
  return;
981
1271
  }
982
- if (!this.resizeObserver) {
1272
+ if (!this.resizeObserver && this.frameEl) {
983
1273
  this.resizeObserver = new ResizeObserver(() => this.scheduleAnimationFrame(() => this.syncPages()));
984
- this.resizeObserver.observe(this);
985
- this.addEventListener("scroll", this.handleTableScroll, {
1274
+ this.resizeObserver.observe(this.frameEl);
1275
+ this.frameEl.addEventListener("scroll", this.handleTableScroll, {
986
1276
  passive: true,
987
1277
  });
988
1278
  }
1279
+ this.sync();
989
1280
  this.scheduleAnimationFrame(() => this.syncPages());
990
1281
  }
991
1282
  /**
992
1283
  * Sets up (or re-creates, after `rootMargin` changes) the observer
993
- * driving {@link maybeLoadMore} — the sentinel lives in this component's
994
- * own shadow root (see {@link sentinelEl}), so `this` itself (the scroll
995
- * container) is used as the observer's `root`.
1284
+ * driving {@link maybeLoadMore}, with the frame, the scroll container,
1285
+ * as its root.
996
1286
  * @private
997
1287
  */
998
1288
  setupTableLoadObserver() {
999
- this.setupLoadObserver(this.sentinelEl, this, this.rootMargin, () => this.maybeLoadMore());
1289
+ this.setupLoadObserver(this.sentinelEl, this.frameEl ?? this, this.rootMargin, () => this.maybeLoadMore());
1000
1290
  }
1001
1291
  /** @private */
1002
1292
  maybeLoadMore() {
@@ -1004,45 +1294,29 @@ let EunomiaTable = class EunomiaTable extends LoadMoreSentinelMixin(LitElement)
1004
1294
  return;
1005
1295
  }
1006
1296
  this.loadingMore = true;
1297
+ this.rowCountBeforeLoad = this.currentRows().length;
1007
1298
  this.requestedPages += 1;
1008
1299
  this.dispatchEvent(new LoadMoreEvent(this.requestedPages, this.pageSize));
1009
1300
  }
1010
- /**
1011
- * Renders `skeletonRows` placeholder rows, each shaped like a real
1012
- * `eun-table-item` (same select/data/action cell count, via CSS Subgrid)
1013
- * so they align under the real header's columns. `--_row-index` drives
1014
- * each row's own staggered entrance animation delay (see
1015
- * table-skeleton-row.styles.ts).
1016
- * @private
1017
- */
1018
- renderSkeletonRows() {
1019
- const header = this.querySelector(":scope > eun-table-header");
1020
- const columnCount = this.columns?.length ??
1021
- header?.querySelectorAll(":scope > eun-table-column").length ??
1022
- 0;
1023
- return html `
1024
- ${Array.from({ length: this.skeletonRows }, (_, index) => html `
1025
- <eun-table-skeleton-row
1026
- ?selectable=${this.selectable}
1027
- ?has-action=${this.hasAction}
1028
- column-count=${columnCount}
1029
- style="--_row-index: ${index}"
1030
- ></eun-table-skeleton-row>
1031
- `)}
1032
- `;
1033
- }
1034
1301
  render() {
1035
1302
  return html `
1036
- <slot @slotchange=${this.handleSlotChange}></slot>
1037
- ${!this.loading && this.infinite
1303
+ <div id="frame" part="frame">
1304
+ <slot></slot>
1305
+ ${!this.loading && this.infinite
1038
1306
  ? html `
1039
- <div id="table-sentinel" aria-hidden="true"></div>
1040
- <div id="table-loading-row" ?hidden=${!this.loadingMore}>
1041
- <eun-loader label=${this.loadingMoreLabel}></eun-loader>
1042
- </div>
1043
- `
1307
+ <div id="table-sentinel" aria-hidden="true"></div>
1308
+ <div id="table-loading-row" ?hidden=${!this.loadingMore}>
1309
+ <eun-loader label=${this.labels.loadingMore}></eun-loader>
1310
+ </div>
1311
+ `
1044
1312
  : nothing}
1045
- ${this.loading ? this.renderSkeletonRows() : nothing}
1313
+ </div>
1314
+ <div
1315
+ id="announcer"
1316
+ role="status"
1317
+ aria-live="polite"
1318
+ class="visually-hidden"
1319
+ ></div>
1046
1320
  `;
1047
1321
  }
1048
1322
  };
@@ -1074,6 +1348,9 @@ __decorate([
1074
1348
  __decorate([
1075
1349
  property({ type: Boolean, reflect: true, attribute: "sticky-header" })
1076
1350
  ], EunomiaTable.prototype, "stickyHeader", void 0);
1351
+ __decorate([
1352
+ property({ type: Boolean, reflect: true, attribute: "sticky-footer" })
1353
+ ], EunomiaTable.prototype, "stickyFooter", void 0);
1077
1354
  __decorate([
1078
1355
  property({ type: Boolean, reflect: true, attribute: "sticky-first-column" })
1079
1356
  ], EunomiaTable.prototype, "stickyFirstColumn", void 0);
@@ -1164,18 +1441,15 @@ __decorate([
1164
1441
  __decorate([
1165
1442
  state()
1166
1443
  ], EunomiaTable.prototype, "internalSelection", void 0);
1167
- __decorate([
1168
- state()
1169
- ], EunomiaTable.prototype, "hasAction", void 0);
1170
- __decorate([
1171
- queryAssignedElements({ selector: "eun-table-header" })
1172
- ], EunomiaTable.prototype, "headerEls", void 0);
1173
- __decorate([
1174
- queryAssignedElements({ selector: "eun-table-item" })
1175
- ], EunomiaTable.prototype, "itemEls", void 0);
1176
1444
  __decorate([
1177
1445
  query("#table-sentinel")
1178
1446
  ], EunomiaTable.prototype, "sentinelEl", void 0);
1447
+ __decorate([
1448
+ query("#frame")
1449
+ ], EunomiaTable.prototype, "frameEl", void 0);
1450
+ __decorate([
1451
+ query("#announcer")
1452
+ ], EunomiaTable.prototype, "announcerEl", void 0);
1179
1453
  EunomiaTable = __decorate([
1180
1454
  customElement(EunomiaTableTagName)
1181
1455
  ], EunomiaTable);