@kubex/zinc 1.1.185 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/.browserslistrc +2 -0
  2. package/.github/workflows/js_build_and_deploy.yaml +54 -1
  3. package/.github/workflows/test.yaml +27 -0
  4. package/dist/chunks/zn.UWJLIXDT.js +1 -0
  5. package/dist/custom-elements.json +10319 -2666
  6. package/dist/vscode.html-custom-data.json +1012 -57
  7. package/dist/web-types.json +2097 -145
  8. package/dist/zn.d.ts +2294 -416
  9. package/dist/zn.min.css +1 -1
  10. package/dist/zn.min.js +2310 -1637
  11. package/docs/_includes/default.njk +4 -1
  12. package/docs/assets/scripts/docs.js +22 -6
  13. package/docs/assets/styles/docs.css +9 -2
  14. package/docs/data/report-table-day-compare.json +380 -0
  15. package/docs/data/report-table.json +314 -0
  16. package/docs/pages/components/avatar-group.md +57 -0
  17. package/docs/pages/components/badge.md +67 -0
  18. package/docs/pages/components/button.md +41 -1
  19. package/docs/pages/components/chat-message.md +16 -0
  20. package/docs/pages/components/checkbox.md +1 -1
  21. package/docs/pages/components/chip.md +25 -13
  22. package/docs/pages/components/color-picker.md +37 -0
  23. package/docs/pages/components/copy-button.md +2 -2
  24. package/docs/pages/components/data-table-filter.md +33 -50
  25. package/docs/pages/components/data-table-search.md +4 -4
  26. package/docs/pages/components/data-table.md +27 -3
  27. package/docs/pages/components/date-range.md +116 -0
  28. package/docs/pages/components/dialog.md +29 -37
  29. package/docs/pages/components/diff-row.md +28 -0
  30. package/docs/pages/components/diff.md +46 -0
  31. package/docs/pages/components/editor.md +55 -0
  32. package/docs/pages/components/icon.md +46 -1
  33. package/docs/pages/components/input.md +30 -11
  34. package/docs/pages/components/item.md +35 -0
  35. package/docs/pages/components/key-value.md +25 -0
  36. package/docs/pages/components/menu.md +74 -7
  37. package/docs/pages/components/nav-item.md +15 -0
  38. package/docs/pages/components/nav-list.md +132 -0
  39. package/docs/pages/components/navbar.md +100 -1
  40. package/docs/pages/components/page.md +242 -74
  41. package/docs/pages/components/priority-list.md +43 -5
  42. package/docs/pages/components/query-builder.md +23 -0
  43. package/docs/pages/components/radio-group.md +33 -1
  44. package/docs/pages/components/radio.md +1 -1
  45. package/docs/pages/components/report-table.md +202 -0
  46. package/docs/pages/components/stat.md +30 -0
  47. package/docs/pages/components/tile.md +11 -0
  48. package/docs/pages/components/tooltip.md +1 -1
  49. package/docs/pages/components/translation-grid.md +86 -0
  50. package/docs/pages/components/vertical-stepper.md +108 -64
  51. package/package.json +2 -1
  52. package/scss/_global-spacing.scss +2 -2
  53. package/scss/_root.scss +11 -171
  54. package/scss/boot.scss +14 -0
  55. package/scss/mixins/_helper-mixins.scss +24 -2
  56. package/scss/mixins/_motion-mixins.scss +126 -0
  57. package/scss/mixins/_spacing-mixins.scss +4 -3
  58. package/scss/mixins/_tab-indicator-mixins.scss +12 -0
  59. package/scss/mixins/_typography-mixins.scss +1 -1
  60. package/scss/mixins/index.scss +14 -0
  61. package/scss/shared/_base.scss +32 -1
  62. package/scss/shared/_button-mixin.scss +7 -5
  63. package/scss/shared/_table.scss +35 -4
  64. package/scss/shared/nav.scss +4 -0
  65. package/scss/shared/shimmer.scss +1 -1
  66. package/scss/table-controls.scss +206 -0
  67. package/scss/themes/_dark.scss +1 -3
  68. package/scss/themes/_index.scss +1 -1
  69. package/scss/themes/_light.scss +46 -6
  70. package/scss/themes/_modes.scss +11 -11
  71. package/scss/themes/_openbyte.scss +944 -0
  72. package/scss/themes/_typography.scss +4 -2
  73. package/scss/variables/index.scss +0 -9
  74. package/src/components/alert/alert.component.ts +14 -4
  75. package/src/components/alert/alert.scss +55 -69
  76. package/src/components/alert/alert.test.ts +8 -0
  77. package/src/components/animated-button/animated-button.component.ts +3 -2
  78. package/src/components/animated-button/animated-button.scss +6 -23
  79. package/src/components/animated-button/animated-button.test.ts +4 -3
  80. package/src/components/avatar-group/avatar-group.component.ts +84 -0
  81. package/src/components/avatar-group/avatar-group.scss +54 -0
  82. package/src/components/avatar-group/avatar-group.test.ts +59 -0
  83. package/src/components/avatar-group/index.ts +12 -0
  84. package/src/components/badge/badge.component.ts +49 -0
  85. package/src/components/badge/badge.scss +80 -0
  86. package/src/components/badge/badge.test.ts +34 -0
  87. package/src/components/badge/index.ts +12 -0
  88. package/src/components/bulk-actions/bulk-actions.component.ts +14 -2
  89. package/src/components/button/button.component.ts +228 -8
  90. package/src/components/button/button.scss +115 -32
  91. package/src/components/button/button.test.ts +122 -0
  92. package/src/components/button-menu/button-menu.component.ts +32 -11
  93. package/src/components/button-menu/button-menu.scss +5 -1
  94. package/src/components/channel-tile/channel-tile.component.ts +34 -27
  95. package/src/components/channel-tile/channel-tile.scss +76 -43
  96. package/src/components/chart/builders.test.ts +46 -0
  97. package/src/components/chart/builders.ts +27 -3
  98. package/src/components/chart/chart-live.test.ts +4 -6
  99. package/src/components/chart/chart.component.ts +125 -5
  100. package/src/components/chart/chart.scss +121 -0
  101. package/src/components/chart/chart.test.ts +81 -0
  102. package/src/components/chat-message/chat-message.component.ts +21 -1
  103. package/src/components/chat-message/chat-message.scss +13 -2
  104. package/src/components/chat-message/chat-message.test.ts +9 -0
  105. package/src/components/checkbox/checkbox.component.ts +61 -27
  106. package/src/components/checkbox/checkbox.scss +58 -25
  107. package/src/components/checkbox-group/checkbox-group.component.ts +7 -1
  108. package/src/components/chip/chip.component.ts +11 -1
  109. package/src/components/chip/chip.scss +91 -26
  110. package/src/components/chip/chip.test.ts +17 -0
  111. package/src/components/collapsible/collapsible.component.ts +17 -3
  112. package/src/components/collapsible/collapsible.scss +19 -10
  113. package/src/components/color-picker/color-picker.component.ts +277 -0
  114. package/src/components/color-picker/color-picker.scss +141 -0
  115. package/src/components/color-picker/color-picker.test.ts +57 -0
  116. package/src/components/color-picker/index.ts +12 -0
  117. package/src/components/cols/cols.component.ts +10 -0
  118. package/src/components/cols/cols.test.ts +17 -0
  119. package/src/components/confirm/confirm.component.ts +20 -6
  120. package/src/components/confirm/confirm.test.ts +52 -0
  121. package/src/components/content-block/content-block.component.ts +38 -9
  122. package/src/components/content-block/content-block.scss +11 -5
  123. package/src/components/content-block/content-block.test.ts +26 -0
  124. package/src/components/copy-button/copy-button.component.ts +2 -2
  125. package/src/components/copy-button/copy-button.scss +17 -4
  126. package/src/components/data-select/data-select.component.ts +3 -1
  127. package/src/components/data-table/data-table.component.ts +362 -140
  128. package/src/components/data-table/data-table.scss +136 -60
  129. package/src/components/data-table/data-table.test.ts +356 -4
  130. package/src/components/data-table-filter/data-table-filter.component.ts +426 -406
  131. package/src/components/data-table-filter/data-table-filter.scss +176 -148
  132. package/src/components/data-table-filter/data-table-filter.test.ts +271 -273
  133. package/src/components/data-table-search/data-table-search.component.ts +4 -2
  134. package/src/components/data-table-search/data-table-search.scss +8 -1
  135. package/src/components/data-table-search/data-table-search.test.ts +29 -0
  136. package/src/components/date-range/date-range-math.test.ts +78 -0
  137. package/src/components/date-range/date-range-math.ts +105 -0
  138. package/src/components/date-range/date-range.component.ts +375 -0
  139. package/src/components/date-range/date-range.scss +272 -0
  140. package/src/components/date-range/date-range.test.ts +247 -0
  141. package/src/components/date-range/index.ts +12 -0
  142. package/src/components/datepicker/datepicker.component.ts +44 -7
  143. package/src/components/datepicker/datepicker.scss +9 -0
  144. package/src/components/datepicker/datepicker.test.ts +2 -2
  145. package/src/components/dialog/dialog.component.ts +38 -19
  146. package/src/components/dialog/dialog.scss +65 -36
  147. package/src/components/dialog/dialog.test.ts +14 -0
  148. package/src/components/diff/diff.component.ts +55 -0
  149. package/src/components/diff/diff.scss +46 -0
  150. package/src/components/diff/diff.test.ts +15 -0
  151. package/src/components/diff/index.ts +12 -0
  152. package/src/components/diff-row/diff-row.component.ts +75 -0
  153. package/src/components/diff-row/diff-row.scss +70 -0
  154. package/src/components/diff-row/diff-row.test.ts +37 -0
  155. package/src/components/diff-row/index.ts +12 -0
  156. package/src/components/dropdown/dropdown.component.ts +12 -10
  157. package/src/components/dropdown/dropdown.scss +2 -0
  158. package/src/components/editor/editor.component.ts +159 -11
  159. package/src/components/editor/editor.scss +250 -1
  160. package/src/components/editor/editor.test.ts +31 -3
  161. package/src/components/editor/modules/ai/panel/ai-panel.scss +0 -1
  162. package/src/components/editor/modules/attachment/attachment.ts +53 -31
  163. package/src/components/editor/modules/context-menu/context-menu.ts +17 -0
  164. package/src/components/editor/modules/dialog/dialog.component.ts +23 -1
  165. package/src/components/editor/modules/dialog/dialog.test.ts +28 -0
  166. package/src/components/editor/modules/emoji/emoji.ts +4 -5
  167. package/src/components/editor/modules/merge-field/merge-field.ts +83 -0
  168. package/src/components/editor/modules/toolbar/toolbar.component.ts +121 -68
  169. package/src/components/editor/modules/toolbar/toolbar.scss +38 -0
  170. package/src/components/editor/modules/toolbar/toolbar.ts +25 -1
  171. package/src/components/empty-state/empty-state.component.ts +6 -1
  172. package/src/components/empty-state/empty-state.scss +21 -4
  173. package/src/components/expanding-action/expanding-action.component.ts +22 -34
  174. package/src/components/expanding-action/expanding-action.scss +3 -45
  175. package/src/components/expanding-action/expanding-action.test.ts +2 -1
  176. package/src/components/file/file.component.ts +30 -2
  177. package/src/components/file/file.scss +6 -0
  178. package/src/components/flow-builder/flow-builder.component.ts +36 -3
  179. package/src/components/flow-builder/flow-builder.scss +0 -6
  180. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.component.ts +8 -0
  181. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.scss +10 -5
  182. package/src/components/flow-builder/modules/flow-node/flow-node.component.ts +17 -0
  183. package/src/components/flow-builder/modules/flow-node/flow-node.scss +10 -0
  184. package/src/components/form-actions/form-actions.component.ts +48 -2
  185. package/src/components/form-actions/form-actions.scss +1 -1
  186. package/src/components/form-group/form-group.component.ts +10 -3
  187. package/src/components/form-group/form-group.scss +37 -4
  188. package/src/components/form-group/form-group.test.ts +10 -0
  189. package/src/components/header/header.component.ts +11 -4
  190. package/src/components/header/header.scss +47 -5
  191. package/src/components/hover-container/hover-container.component.ts +4 -5
  192. package/src/components/icon/icon.component.ts +95 -9
  193. package/src/components/icon/icon.scss +70 -6
  194. package/src/components/icon/icon.test.ts +55 -0
  195. package/src/components/icon-picker/icon-picker.component.ts +35 -1
  196. package/src/components/inline-edit/inline-edit.component.ts +38 -8
  197. package/src/components/inline-edit/inline-edit.scss +128 -19
  198. package/src/components/input/input.component.ts +173 -297
  199. package/src/components/input/input.scss +65 -43
  200. package/src/components/input/input.test.ts +86 -4
  201. package/src/components/item/item.component.ts +12 -0
  202. package/src/components/item/item.scss +102 -3
  203. package/src/components/item/item.test.ts +23 -0
  204. package/src/components/key/key.component.ts +2 -2
  205. package/src/components/key/key.scss +11 -0
  206. package/src/components/key-value/index.ts +12 -0
  207. package/src/components/key-value/key-value.component.ts +160 -0
  208. package/src/components/key-value/key-value.scss +20 -0
  209. package/src/components/key-value/key-value.test.ts +22 -0
  210. package/src/components/markdown-editor/markdown-editor.component.ts +8 -1
  211. package/src/components/menu/menu.component.ts +21 -2
  212. package/src/components/menu/menu.scss +43 -23
  213. package/src/components/menu/menu.test.ts +33 -0
  214. package/src/components/menu-item/menu-item.component.ts +13 -2
  215. package/src/components/menu-item/menu-item.scss +56 -23
  216. package/src/components/menu-item/menu-item.test.ts +31 -0
  217. package/src/components/menu-item/submenu-controller.ts +25 -15
  218. package/src/components/menu-label/index.ts +12 -0
  219. package/src/components/menu-label/menu-label.component.ts +30 -0
  220. package/src/components/menu-label/menu-label.scss +18 -0
  221. package/src/components/nav-item/index.ts +12 -0
  222. package/src/components/nav-item/nav-item.component.ts +83 -0
  223. package/src/components/nav-item/nav-item.scss +128 -0
  224. package/src/components/nav-list/index.ts +12 -0
  225. package/src/components/nav-list/nav-list.component.ts +313 -0
  226. package/src/components/nav-list/nav-list.scss +87 -0
  227. package/src/components/nav-list/nav-list.test.ts +250 -0
  228. package/src/components/navbar/navbar.component.ts +235 -171
  229. package/src/components/navbar/navbar.scss +333 -169
  230. package/src/components/navbar/navbar.test.ts +63 -143
  231. package/src/components/page/page.component.ts +114 -29
  232. package/src/components/page/page.scss +223 -95
  233. package/src/components/page/page.test.ts +214 -3
  234. package/src/components/page-builder/page-builder.component.ts +51 -5
  235. package/src/components/page-builder/page-builder.scss +6 -1
  236. package/src/components/page-nav/page-nav.component.ts +25 -3
  237. package/src/components/page-nav/page-nav.scss +10 -2
  238. package/src/components/pagination/pagination.scss +1 -1
  239. package/src/components/panel/panel.component.ts +14 -13
  240. package/src/components/panel/panel.scss +14 -10
  241. package/src/components/panel/panel.test.ts +42 -0
  242. package/src/components/popup/popup.component.ts +20 -13
  243. package/src/components/popup/popup.test.ts +21 -1
  244. package/src/components/priority-list/priority-list.component.ts +157 -172
  245. package/src/components/priority-list/priority-list.scss +113 -17
  246. package/src/components/priority-list/priority-list.test.ts +301 -3
  247. package/src/components/progress-bar/progress-bar.component.ts +1 -1
  248. package/src/components/progress-bar/progress-bar.scss +16 -4
  249. package/src/components/progress-tile/progress-tile.scss +1 -1
  250. package/src/components/query-builder/query-builder.component.ts +85 -10
  251. package/src/components/query-builder/query-builder.scss +7 -1
  252. package/src/components/query-builder/query-builder.test.ts +40 -0
  253. package/src/components/radio/radio.component.ts +57 -18
  254. package/src/components/radio/radio.scss +101 -26
  255. package/src/components/radio-group/radio-group.component.ts +45 -7
  256. package/src/components/radio-group/radio-group.scss +17 -0
  257. package/src/components/radio-group/radio-group.test.ts +63 -2
  258. package/src/components/rating/rating.component.ts +23 -0
  259. package/src/components/rating/rating.scss +1 -1
  260. package/src/components/remarkd-editor/remarkd-editor.component.ts +60 -5
  261. package/src/components/remarkd-editor/remarkd-editor.scss +12 -4
  262. package/src/components/report-table/index.ts +12 -0
  263. package/src/components/report-table/report-format.test.ts +86 -0
  264. package/src/components/report-table/report-format.ts +72 -0
  265. package/src/components/report-table/report-math.test.ts +74 -0
  266. package/src/components/report-table/report-math.ts +252 -0
  267. package/src/components/report-table/report-table.component.ts +681 -0
  268. package/src/components/report-table/report-table.scss +328 -0
  269. package/src/components/report-table/report-table.test.ts +702 -0
  270. package/src/components/reveal/reveal.component.ts +15 -1
  271. package/src/components/reveal/reveal.scss +5 -0
  272. package/src/components/schedule-builder/schedule-builder.component.ts +23 -8
  273. package/src/components/scroll-container/scroll-container.component.ts +19 -1
  274. package/src/components/scroll-container/scroll-container.scss +6 -1
  275. package/src/components/select/select.component.ts +52 -36
  276. package/src/components/select/select.scss +42 -25
  277. package/src/components/select/select.test.ts +14 -2
  278. package/src/components/settings-container/settings-container.scss +18 -5
  279. package/src/components/sidebar/sidebar.component.ts +8 -1
  280. package/src/components/sidebar/sidebar.scss +6 -1
  281. package/src/components/skeleton/skeleton.component.ts +3 -3
  282. package/src/components/skeleton/skeleton.scss +6 -6
  283. package/src/components/skeleton/skeleton.test.ts +9 -0
  284. package/src/components/slash-menu/slash-menu.scss +1 -1
  285. package/src/components/slideout/slideout.component.ts +20 -3
  286. package/src/components/slideout/slideout.scss +13 -6
  287. package/src/components/slideout/slideout.test.ts +19 -0
  288. package/src/components/split-button/split-button.component.ts +1 -1
  289. package/src/components/split-pane/split-pane.component.ts +71 -8
  290. package/src/components/split-pane/split-pane.scss +145 -41
  291. package/src/components/split-pane/split-pane.test.ts +23 -3
  292. package/src/components/stat/stat.component.ts +209 -14
  293. package/src/components/stat/stat.scss +305 -5
  294. package/src/components/stat/stat.test.ts +143 -2
  295. package/src/components/status-indicator/status-indicator.scss +22 -21
  296. package/src/components/stepper/stepper.scss +3 -3
  297. package/src/components/tab/tab.component.ts +1 -0
  298. package/src/components/table/table.component.ts +12 -1
  299. package/src/components/table/table.scss +21 -13
  300. package/src/components/table/table.test.ts +17 -0
  301. package/src/components/tabs/tabs.component.ts +150 -37
  302. package/src/components/tabs/tabs.scss +48 -7
  303. package/src/components/tabs/tabs.test.ts +169 -0
  304. package/src/components/textarea/textarea.component.ts +25 -3
  305. package/src/components/textarea/textarea.scss +10 -0
  306. package/src/components/textarea/textarea.test.ts +8 -4
  307. package/src/components/theme-editor/theme-editor.scss +0 -6
  308. package/src/components/tile/tile.component.ts +32 -2
  309. package/src/components/tile/tile.scss +40 -7
  310. package/src/components/tile/tile.test.ts +10 -0
  311. package/src/components/toggle/toggle.component.ts +23 -7
  312. package/src/components/toggle/toggle.scss +36 -29
  313. package/src/components/toggle/toggle.test.ts +17 -0
  314. package/src/components/tooltip/tooltip.component.ts +3 -0
  315. package/src/components/tooltip/tooltip.scss +11 -6
  316. package/src/components/tooltip/tooltip.test.ts +12 -0
  317. package/src/components/translation-field/index.ts +12 -0
  318. package/src/components/translation-field/translation-field.component.ts +55 -0
  319. package/src/components/translation-field/translation-field.scss +101 -0
  320. package/src/components/translation-fields/index.ts +12 -0
  321. package/src/components/translation-fields/translation-fields.component.ts +80 -0
  322. package/src/components/translation-fields/translation-fields.scss +60 -0
  323. package/src/components/translation-grid/index.ts +12 -0
  324. package/src/components/translation-grid/translation-grid.component.ts +157 -0
  325. package/src/components/translation-grid/translation-grid.scss +138 -0
  326. package/src/components/translation-grid/translation-grid.test.ts +28 -0
  327. package/src/components/vertical-stepper/vertical-stepper.component.ts +35 -9
  328. package/src/components/vertical-stepper/vertical-stepper.scss +110 -39
  329. package/src/components/vertical-stepper/vertical-stepper.test.ts +35 -0
  330. package/src/components/well/well.scss +2 -2
  331. package/src/events/events.ts +2 -0
  332. package/src/events/zn-chart-click.ts +13 -0
  333. package/src/events/zn-report-change.ts +7 -0
  334. package/src/form-control.scss +36 -3
  335. package/src/internal/anchored-popover.ts +175 -0
  336. package/src/internal/animate.ts +19 -5
  337. package/src/internal/cosmic-border.ts +15 -0
  338. package/src/internal/form.ts +1 -1
  339. package/src/internal/hover-intent.ts +15 -0
  340. package/src/internal/input-modality.ts +15 -0
  341. package/src/internal/pointer-intent.ts +37 -0
  342. package/src/internal/reorder.ts +632 -0
  343. package/src/internal/scroll.ts +51 -3
  344. package/src/internal/selection-card.ts +1 -1
  345. package/src/internal/tab-indicator.ts +92 -0
  346. package/src/internal/table-sort.ts +25 -0
  347. package/src/internal/table-tabs.ts +108 -0
  348. package/src/internal/test-drag.ts +35 -0
  349. package/src/internal/theme-openbyte.test.ts +126 -0
  350. package/src/internal/theme.ts +4 -4
  351. package/src/internal/zinc-element.ts +2 -2
  352. package/src/selection-card.scss +25 -0
  353. package/src/types/web-test-runner-commands.d.ts +1 -0
  354. package/src/utilities/color.test.ts +43 -0
  355. package/src/utilities/color.ts +189 -0
  356. package/src/zinc.ts +14 -0
  357. package/.github/workflows/eleventy_build.yml +0 -42
  358. package/scss/themes/_aura.scss +0 -114
@@ -1,5 +1,6 @@
1
1
  import '../../../dist/zn.min.js';
2
2
  import { expect, fixture, html, waitUntil } from '@open-wc/testing';
3
+ import { centre, drop, dragTo, startDrag } from '../../internal/test-drag';
3
4
  import { render } from 'lit';
4
5
  import type ZnDataTable from './data-table.component';
5
6
 
@@ -126,6 +127,94 @@ describe('<zn-data-table>', () => {
126
127
  expect(() => (el as unknown as { updateKeys: () => void }).updateKeys()).not.to.throw();
127
128
  });
128
129
 
130
+ it('marks the nearest column boundary while dragging and moves the column on drop', async () => {
131
+ const el = await fixture<ZnDataTable>(html`
132
+ <zn-data-table
133
+ headers='{"a": {"key": "a", "label": "A"}, "b": {"key": "b", "label": "A really quite long column heading"}, "c": {"key": "c", "label": "C"}}'
134
+ .data=${[{id: '1', cells: [{text: 'a1', column: 'a'}, {text: 'b1', column: 'b'}, {text: 'c1', column: 'c'}]}]}>
135
+ </zn-data-table>`);
136
+ const root = el.shadowRoot!;
137
+ const heading = (key: string) => root.querySelector<HTMLElement>(`th[data-column="${key}"]`)!;
138
+ const order = () => [...root.querySelectorAll<HTMLElement>('th[data-column]')].map(th => th.dataset.column);
139
+ const cells = () => [...root.querySelectorAll('tbody tr.table__row--data td')].map(td => td.textContent!.trim());
140
+ const line = () => root.querySelector('.table__drop-line')?.getBoundingClientRect();
141
+ const dragOver = async (target: Element, x: number) => {
142
+ dragTo(x, centre(target).y);
143
+ await el.updateComplete;
144
+ };
145
+ await waitUntil(() => order().length === 3);
146
+ let reordered = 0;
147
+ el.addEventListener('zn-reorder', () => reordered++);
148
+
149
+ startDrag(heading('a'));
150
+ const b = heading('b').getBoundingClientRect();
151
+ await dragOver(heading('b'), b.left + 10);
152
+ expect(line(), 'the boundary the column already sits at').to.be.undefined;
153
+
154
+ // Over a body cell, so the whole column is a drop target
155
+ const c = heading('c').getBoundingClientRect();
156
+ await dragOver(root.querySelector('td[data-column="c"]')!, c.right - 1);
157
+ expect(line()!.left + line()!.width / 2).to.be.closeTo(c.right - 1, 1);
158
+ expect(line()!.right, 'held inside the table, so it never widens the scroller').to.be.at.most(c.right + 0.5);
159
+ expect(line()!.height, 'down the heading and the body').to.be.greaterThan(c.height);
160
+ expect(order(), 'nothing moves until the drop').to.deep.equal(['a', 'b', 'c']);
161
+
162
+ drop(c.right - 1, centre(heading('c')).y);
163
+ await el.updateComplete;
164
+ expect(order()).to.deep.equal(['b', 'c', 'a']);
165
+ expect(cells()).to.deep.equal(['b1', 'c1', 'a1']);
166
+ expect(line()).to.be.undefined;
167
+ expect(reordered).to.equal(1);
168
+ });
169
+
170
+ it('shows a closed hand over the page while dragging, and drops without sorting', async () => {
171
+ const el = await fixture<ZnDataTable>(html`
172
+ <zn-data-table
173
+ headers='{"a": {"key": "a", "label": "A"}, "b": {"key": "b", "label": "B"}}'
174
+ .data=${[{id: '1', cells: [{text: 'a1', column: 'a'}, {text: 'b1', column: 'b'}]}]}>
175
+ </zn-data-table>`);
176
+ const root = el.shadowRoot!;
177
+ const heading = (key: string) => root.querySelector<HTMLElement>(`th[data-column="${key}"]`)!;
178
+ await waitUntil(() => heading('b'));
179
+
180
+ startDrag(heading('a'));
181
+ const b = heading('b').getBoundingClientRect();
182
+ dragTo(b.right - 1, centre(heading('b')).y);
183
+ const under = root.elementFromPoint(b.right - 1, centre(heading('b')).y)!;
184
+ expect(getComputedStyle(under).cursor).to.equal('grabbing');
185
+ expect(root.querySelector('.table__drag-ghost')!.textContent).to.equal('A');
186
+
187
+ drop(b.right - 1, centre(heading('b')).y);
188
+ heading('a').click();
189
+ await el.updateComplete;
190
+ expect(root.querySelector('.table__drag-ghost')).to.not.exist;
191
+ expect(heading('a').getAttribute('aria-sort'), 'the click that ends a drag does not sort').to.not.equal('ascending');
192
+ });
193
+
194
+ it('does not offer to drag a lone column', async () => {
195
+ const el = await fixture<ZnDataTable>(html`
196
+ <zn-data-table headers='{"a": {"key": "a", "label": "A"}}' .data=${[{id: '1', cells: [{text: 'a1', column: 'a'}]}]}>
197
+ </zn-data-table>`);
198
+ await waitUntil(() => el.shadowRoot!.querySelector('th[data-column="a"]'));
199
+
200
+ expect(el.shadowRoot!.querySelector('th[data-column="a"]')!.classList.contains('table__head--reorderable')).to.be.false;
201
+ });
202
+
203
+ it('moves a column with Alt + arrow keys', async () => {
204
+ const el = await fixture<ZnDataTable>(html`
205
+ <zn-data-table
206
+ headers='{"a": {"key": "a", "label": "A"}, "b": {"key": "b", "label": "B"}}'
207
+ .data=${[{id: '1', cells: [{text: 'a1', column: 'a'}, {text: 'b1', column: 'b'}]}]}>
208
+ </zn-data-table>`);
209
+ const root = el.shadowRoot!;
210
+ const order = () => [...root.querySelectorAll<HTMLElement>('th[data-column]')].map(th => th.dataset.column);
211
+ await waitUntil(() => order().length === 2);
212
+
213
+ root.querySelector('th[data-column="a"]')!.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowRight', altKey: true}));
214
+ await el.updateComplete;
215
+ expect(order()).to.deep.equal(['b', 'a']);
216
+ });
217
+
129
218
  it('accepts a single Row object on the data property', async () => {
130
219
  const el = await fixture<ZnDataTable>(html` <zn-data-table></zn-data-table> `);
131
220
  (el as unknown as {data: unknown}).data = {
@@ -613,7 +702,7 @@ describe('<zn-data-table>', () => {
613
702
  });
614
703
  });
615
704
 
616
- it('shows a server-rendered filter as pills in the slotted filter bar', async () => {
705
+ it('shows a server-rendered filter as chips under the header of a slotted filter', async () => {
617
706
  const originalFetch = window.fetch;
618
707
  window.fetch = () => Promise.resolve(new Response(JSON.stringify({
619
708
  rows: [{id: '1', cells: [{text: 'Row', column: 'name'}]}], page: 1, perPage: 10, total: 1,
@@ -633,13 +722,276 @@ describe('<zn-data-table>', () => {
633
722
  <zn-data-table filter="${filter}" data-uri="/test-data" headers='{"name": {"key": "name", "label": "Name"}}'>
634
723
  <zn-data-table-filter slot="filter" filters="${filters}"></zn-data-table-filter>
635
724
  </zn-data-table>`);
636
- const bar = el.querySelector('zn-data-table-filter') as HTMLElement & { updateComplete: Promise<unknown> };
725
+ await waitUntil(() => el.shadowRoot!.querySelector('.table__chips'));
726
+ const chipsEl = el.shadowRoot!.querySelector<HTMLElement & {updateComplete: Promise<unknown>}>('.table__chips')!;
727
+ await chipsEl.updateComplete;
728
+
729
+ const chips = [...chipsEl.shadowRoot!.querySelectorAll('.filter__chip__text')].map(p => p.textContent!.trim());
730
+ expect(chips).to.deep.equal(['Status is any of Pending Reply, Open']);
731
+ expect(chipsEl.shadowRoot!.querySelectorAll('.filter__chip__remove').length).to.equal(1);
732
+ } finally {
733
+ window.fetch = originalFetch;
734
+ }
735
+ });
736
+
737
+ it('sizes data rows from --zn-table-row-height', async () => {
738
+ const originalFetch = window.fetch;
739
+ window.fetch = () => Promise.resolve(new Response(JSON.stringify({
740
+ rows: [{id: '1', cells: [{text: 'Row', column: 'name'}]}],
741
+ page: 1,
742
+ perPage: 10,
743
+ total: 1,
744
+ }), {status: 200, headers: {'Content-Type': 'application/json'}}));
745
+
746
+ try {
747
+ const el = await fixture<ZnDataTable>(html`
748
+ <zn-data-table data-uri="/test-data" style="--zn-table-row-height: 77px"
749
+ headers='{"name": {"key": "name", "label": "Name"}}'></zn-data-table>`);
750
+ await waitUntil(() => el.shadowRoot!.querySelector('tbody tr.table__row--data'));
751
+
752
+ const cell = el.shadowRoot!.querySelector('tbody tr.table__row--data td')!;
753
+ expect(getComputedStyle(cell).height).to.equal('77px');
754
+ } finally {
755
+ window.fetch = originalFetch;
756
+ }
757
+ });
758
+
759
+ describe('table controls', () => {
760
+ const rowResponse = () => new Response(JSON.stringify({
761
+ rows: [{id: '1', cells: [{text: 'Row', column: 'name'}]}], page: 1, perPage: 10, total: 1,
762
+ }), {status: 200, headers: {'Content-Type': 'application/json'}});
763
+ const click = (el: Element) => el.dispatchEvent(new MouseEvent('click', {bubbles: true, composed: true, cancelable: true}));
764
+ const choose = (select: Element, value: string) => {
765
+ (select as HTMLElement & {value: string}).value = value;
766
+ select.dispatchEvent(new CustomEvent('zn-change', {bubbles: true, composed: true}));
767
+ };
768
+
769
+ let originalFetch: typeof window.fetch;
770
+ let bodies: Record<string, unknown>[];
771
+ beforeEach(() => {
772
+ originalFetch = window.fetch;
773
+ bodies = [];
774
+ window.fetch = (_url: RequestInfo | URL, options?: RequestInit) => {
775
+ if (options?.body) bodies.push(JSON.parse(options.body as string) as Record<string, unknown>);
776
+ return Promise.resolve(rowResponse());
777
+ };
778
+ });
779
+ afterEach(() => {
780
+ window.fetch = originalFetch;
781
+ });
782
+
783
+ const filterTable = async () => {
784
+ const el = await fixture<ZnDataTable>(html`
785
+ <zn-data-table data-uri="/test-data" caption="Things" headers='{"name": {"key": "name", "label": "Name"}}'
786
+ filter-fields='[{"id":"status","name":"Status","options":{"active":"Active"}}]'></zn-data-table>`);
787
+ await waitUntil(() => bodies.length > 0);
788
+ return el;
789
+ };
790
+
791
+ type FilterEl = HTMLElement & {updateComplete: Promise<unknown>};
792
+ const funnel = (el: ZnDataTable) => el.shadowRoot!.querySelector<FilterEl>('.table__header__right zn-data-table-filter')!;
793
+ const chipRow = (el: ZnDataTable) => el.shadowRoot!.querySelector<FilterEl>('.table__chips')!;
794
+ const settle = async (el: ZnDataTable) => {
795
+ await el.updateComplete;
796
+ await funnel(el).updateComplete;
797
+ await chipRow(el)?.updateComplete;
798
+ };
799
+
800
+ const addRule = async (el: ZnDataTable) => {
801
+ const root = funnel(el).shadowRoot!;
802
+ choose(root.querySelector('.filter__rule zn-select[aria-label="Filter field"]')!, 'status');
803
+ await settle(el);
804
+ await waitUntil(() => root.querySelector('.filter__rule zn-select[aria-label="Filter value"]'));
805
+ choose(root.querySelector('.filter__rule zn-select[aria-label="Filter value"]')!, 'active');
806
+ await settle(el);
807
+ click(root.querySelector('.filter__pop__foot zn-button[color="primary"]')!);
808
+ await settle(el);
809
+ };
810
+
811
+ it('opens the filter builder and encodes a completed rule', async () => {
812
+ const el = await filterTable();
813
+ const root = funnel(el).shadowRoot!;
814
+
815
+ click(root.querySelector('.filter__toggle')!);
816
+ await settle(el);
817
+ expect(root.querySelector('.filter__pop')).to.exist;
818
+
819
+ await addRule(el);
820
+ const expected = btoa(JSON.stringify([{key: 'status', comparator: 'eq', value: 'active'}]));
821
+ await waitUntil(() => bodies[bodies.length - 1].filter === expected);
822
+ await settle(el);
823
+
824
+ expect(el.filter).to.equal(expected);
825
+ const chips = chipRow(el).shadowRoot!;
826
+ expect(chips.querySelectorAll('.filter__chip').length).to.equal(1);
827
+ expect(chips.querySelector('.filter__chip__text')!.textContent!.trim()).to.equal('Status is Active');
828
+ });
829
+
830
+ it('closes the filter builder on Escape and drops incomplete rules', async () => {
831
+ const el = await filterTable();
832
+ const root = funnel(el).shadowRoot!;
833
+
834
+ click(root.querySelector('.filter__toggle')!);
835
+ await settle(el);
836
+ await addRule(el);
837
+ click(root.querySelector('.filter__toggle')!);
838
+ await settle(el);
839
+ click(root.querySelector('.filter__pop__foot zn-button[icon="plus@lu"]')!);
840
+ await settle(el);
841
+ expect(root.querySelectorAll('.filter__rule').length).to.equal(2);
842
+
843
+ document.dispatchEvent(new KeyboardEvent('keydown', {key: 'Escape', bubbles: true}));
844
+ await settle(el);
845
+ expect(root.querySelector('.filter__pop')).to.not.exist;
846
+ expect(chipRow(el).shadowRoot!.querySelectorAll('.filter__chip').length).to.equal(1);
847
+
848
+ click(root.querySelector('.filter__toggle')!);
849
+ await settle(el);
850
+ expect(root.querySelectorAll('.filter__rule').length).to.equal(1);
851
+ });
852
+
853
+ const headerOrder = (el: ZnDataTable) => {
854
+ const right = el.shadowRoot!.querySelector('.table__header__right')!;
855
+ const order = [
856
+ right.querySelector('slot[name="search"]'),
857
+ right.querySelector('zn-data-table-filter, slot[name="filter"]'),
858
+ right.querySelector('zn-dropdown zn-button[icon="columns-3@lu"]')?.closest('zn-dropdown'),
859
+ right.querySelector('zn-button[icon="refresh-cw@lu"]'),
860
+ right.querySelector('slot[name="sort"]'),
861
+ right.querySelector('slot[name="create-action"]'),
862
+ ];
863
+ const children = [...right.children];
864
+ return order.map(node => node ? children.indexOf(node) : -1);
865
+ };
866
+ const ascending = (indexes: number[]) => indexes.every((index, i) => index >= 0 && (i === 0 || index > indexes[i - 1]));
867
+ const twoColumns = '{"name": {"key": "name", "label": "Name"}, "age": {"key": "age", "label": "Age"}}';
868
+
869
+ it('orders the header search, funnel, columns, refresh with filter-fields', async () => {
870
+ const el = await fixture<ZnDataTable>(html`
871
+ <zn-data-table data-uri="/test-data" caption="Things" headers="${twoColumns}"
872
+ filter-fields='[{"id":"status","name":"Status"}]'>
873
+ <zn-data-table-search slot="search"></zn-data-table-search>
874
+ </zn-data-table>`);
875
+ await waitUntil(() => el.shadowRoot!.querySelector('zn-button[icon="refresh-cw@lu"]'));
876
+
877
+ const order = headerOrder(el);
878
+ expect(ascending(order), `header order ${order.join(',')}`).to.be.true;
879
+ });
880
+
881
+ it('orders the header search, funnel, columns, refresh with a slotted filter', async () => {
882
+ const el = await fixture<ZnDataTable>(html`
883
+ <zn-data-table data-uri="/test-data" caption="Things" headers="${twoColumns}">
884
+ <zn-data-table-search slot="search"></zn-data-table-search>
885
+ <zn-data-table-filter slot="filter" filters='[{"id":"status","name":"Status","operators":["eq"]}]'></zn-data-table-filter>
886
+ </zn-data-table>`);
887
+ await waitUntil(() => el.shadowRoot!.querySelector('zn-button[icon="refresh-cw@lu"]'));
888
+
889
+ const order = headerOrder(el);
890
+ expect(ascending(order), `header order ${order.join(',')}`).to.be.true;
891
+ });
892
+
893
+ it('hides the column select when every column is required', async () => {
894
+ const required = '{"name": {"key": "name", "label": "Name", "required": true}, "age": {"key": "age", "label": "Age", "required": true}}';
895
+ const el = await fixture<ZnDataTable>(html`
896
+ <zn-data-table data-uri="/test-data" caption="Things" headers="${required}"></zn-data-table>`);
897
+ await waitUntil(() => el.shadowRoot!.querySelector('zn-button[icon="refresh-cw@lu"]'));
898
+
899
+ expect(el.shadowRoot!.querySelector('zn-button[icon="columns-3@lu"]')).to.not.exist;
900
+ });
901
+
902
+ it('reloads with a slotted filter\'s encoded rules', async () => {
903
+ const el = await fixture<ZnDataTable>(html`
904
+ <zn-data-table data-uri="/test-data" caption="Things" headers='{"name": {"key": "name", "label": "Name"}}'>
905
+ <zn-data-table-filter slot="filter" filters='[{"id":"status","name":"Status","operators":["eq"],"options":{"active":"Active"}}]'></zn-data-table-filter>
906
+ </zn-data-table>`);
907
+ await waitUntil(() => bodies.length > 0);
908
+ const bar = el.querySelector<FilterEl>('zn-data-table-filter')!;
909
+ const root = bar.shadowRoot!;
910
+
911
+ click(root.querySelector('.filter__toggle')!);
912
+ await bar.updateComplete;
913
+ choose(root.querySelector('.filter__rule zn-select[aria-label="Filter field"]')!, 'status');
637
914
  await bar.updateComplete;
915
+ choose(root.querySelector('.filter__rule zn-select[aria-label="Filter value"]')!, 'active');
916
+ await bar.updateComplete;
917
+ click(root.querySelector('.filter__pop__foot zn-button[color="primary"]')!);
918
+
919
+ const expected = btoa(JSON.stringify([{key: 'status', comparator: 'eq', value: 'active'}]));
920
+ await waitUntil(() => bodies[bodies.length - 1].filter === expected);
921
+ await waitUntil(() => el.shadowRoot!.querySelector<FilterEl>('.table__chips')?.shadowRoot?.querySelector('.filter__chip'));
922
+ expect(root.querySelector('.filter__chip')).to.not.exist;
923
+ });
924
+
925
+ it('selecting a tab reloads with the tab param', async () => {
926
+ const el = await fixture<ZnDataTable>(html`
927
+ <zn-data-table data-uri="/test-data" headers='{"name": {"key": "name", "label": "Name"}}'
928
+ tabs='[{"value":"a","label":"A"},{"value":"b","label":"B"}]'></zn-data-table>`);
929
+ await waitUntil(() => bodies.length > 0);
930
+ expect(bodies[0].tab).to.equal('a');
931
+
932
+ const tabs = el.shadowRoot!.querySelectorAll<HTMLElement>('.table__tab');
933
+ click(tabs[1]);
934
+ await waitUntil(() => bodies[bodies.length - 1].tab === 'b');
935
+ await el.updateComplete;
936
+
937
+ const second = el.shadowRoot!.querySelectorAll('.table__tab')[1];
938
+ expect(second.getAttribute('aria-selected')).to.equal('true');
939
+ expect(el.shadowRoot!.querySelectorAll('.table__tab')[0].getAttribute('aria-selected')).to.equal('false');
940
+ expect(el.shadowRoot!.querySelector('.table__tabs__indicator')).to.exist;
941
+ });
638
942
 
639
- const pills = [...bar.shadowRoot!.querySelectorAll('.filter-bar__pill')].map(p => p.textContent!.trim());
640
- expect(pills).to.deep.equal(['Status (2)', 'Department']);
943
+ it('sort heading toggles aria-sort with Enter', async () => {
944
+ const el = await fixture<ZnDataTable>(html`
945
+ <zn-data-table data-uri="/test-data" headers='{"name": {"key": "name", "label": "Name"}}'></zn-data-table>`);
946
+ await waitUntil(() => el.shadowRoot!.querySelector('th.table__head[tabindex]'));
947
+ const head = () => el.shadowRoot!.querySelector('th.table__head[tabindex]')!;
948
+ expect(head().getAttribute('aria-sort')).to.equal('none');
949
+ expect(head().querySelector('.table__head__sort--active')).to.not.exist;
950
+
951
+ head().dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter', bubbles: true, cancelable: true}));
952
+ await waitUntil(() => head().getAttribute('aria-sort') === 'descending');
953
+ expect(head().querySelector('.table__head__sort--active zn-icon')!.getAttribute('src')).to.contain('arrow-down');
954
+
955
+ head().dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter', bubbles: true, cancelable: true}));
956
+ await waitUntil(() => head().getAttribute('aria-sort') === 'ascending');
957
+ expect(bodies[bodies.length - 1].sortColumn).to.equal('name');
958
+ });
959
+ });
960
+
961
+ it('uses the singular total label for a single row', async () => {
962
+ const originalFetch = window.fetch;
963
+ window.fetch = () => Promise.resolve(new Response(JSON.stringify({
964
+ rows: [{id: '1', cells: [{text: 'Row', column: 'name'}]}],
965
+ page: 1,
966
+ perPage: 10,
967
+ total: 1,
968
+ }), {status: 200, headers: {'Content-Type': 'application/json'}}));
969
+
970
+ try {
971
+ const el = await fixture<ZnDataTable>(html`
972
+ <zn-data-table standalone caption="Emails" total-label="emails" total-label-singular="email" data-uri="/test-data"
973
+ headers='{"name": {"key": "name", "label": "Name"}}'></zn-data-table>`);
974
+ await waitUntil(() => el.shadowRoot!.querySelector('tbody tr.table__row--data'));
975
+ await el.updateComplete;
976
+
977
+ expect(el.shadowRoot!.innerHTML).to.match(/1 email["<]/);
641
978
  } finally {
642
979
  window.fetch = originalFetch;
643
980
  }
644
981
  });
982
+
983
+ it('keeps the slotted search readable in a narrow header', async () => {
984
+ const wrap = await fixture<HTMLElement>(html`
985
+ <div style="width: 340px">
986
+ <zn-data-table standalone no-initial-load caption="A fairly long table caption" total-label="rows">
987
+ <zn-data-table-search slot="search" placeholder="Search..."></zn-data-table-search>
988
+ <div slot="empty-state">Nothing yet</div>
989
+ </zn-data-table>
990
+ </div>`);
991
+ const el = wrap.querySelector<ZnDataTable>('zn-data-table')!;
992
+ await el.updateComplete;
993
+ const search = el.querySelector('zn-data-table-search')!;
994
+
995
+ expect(search.getBoundingClientRect().width).to.be.at.least(160);
996
+ });
645
997
  });