@kubex/zinc 1.1.184 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (362) 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 +10344 -2583
  6. package/dist/vscode.html-custom-data.json +1102 -125
  7. package/dist/web-types.json +2217 -224
  8. package/dist/zn.d.ts +2306 -417
  9. package/dist/zn.min.css +1 -1
  10. package/dist/zn.min.js +2313 -1635
  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/filter-wrapper.md +20 -0
  33. package/docs/pages/components/icon.md +46 -1
  34. package/docs/pages/components/input.md +30 -11
  35. package/docs/pages/components/item.md +35 -0
  36. package/docs/pages/components/key-value.md +25 -0
  37. package/docs/pages/components/menu.md +74 -7
  38. package/docs/pages/components/nav-item.md +15 -0
  39. package/docs/pages/components/nav-list.md +132 -0
  40. package/docs/pages/components/navbar.md +100 -1
  41. package/docs/pages/components/page.md +242 -74
  42. package/docs/pages/components/priority-list.md +43 -5
  43. package/docs/pages/components/query-builder.md +23 -0
  44. package/docs/pages/components/radio-group.md +33 -1
  45. package/docs/pages/components/radio.md +1 -1
  46. package/docs/pages/components/report-table.md +202 -0
  47. package/docs/pages/components/stat.md +30 -0
  48. package/docs/pages/components/tile.md +11 -0
  49. package/docs/pages/components/tooltip.md +1 -1
  50. package/docs/pages/components/translation-grid.md +86 -0
  51. package/docs/pages/components/vertical-stepper.md +108 -64
  52. package/package.json +2 -1
  53. package/scss/_global-spacing.scss +2 -2
  54. package/scss/_root.scss +11 -171
  55. package/scss/boot.scss +14 -0
  56. package/scss/mixins/_helper-mixins.scss +24 -2
  57. package/scss/mixins/_motion-mixins.scss +126 -0
  58. package/scss/mixins/_spacing-mixins.scss +4 -3
  59. package/scss/mixins/_tab-indicator-mixins.scss +12 -0
  60. package/scss/mixins/_typography-mixins.scss +1 -1
  61. package/scss/mixins/index.scss +14 -0
  62. package/scss/shared/_base.scss +32 -1
  63. package/scss/shared/_button-mixin.scss +7 -5
  64. package/scss/shared/_table.scss +35 -4
  65. package/scss/shared/nav.scss +4 -0
  66. package/scss/shared/shimmer.scss +1 -1
  67. package/scss/table-controls.scss +206 -0
  68. package/scss/themes/_dark.scss +1 -3
  69. package/scss/themes/_index.scss +1 -1
  70. package/scss/themes/_light.scss +46 -6
  71. package/scss/themes/_modes.scss +11 -11
  72. package/scss/themes/_openbyte.scss +944 -0
  73. package/scss/themes/_typography.scss +4 -2
  74. package/scss/variables/index.scss +0 -9
  75. package/src/components/alert/alert.component.ts +14 -4
  76. package/src/components/alert/alert.scss +55 -69
  77. package/src/components/alert/alert.test.ts +8 -0
  78. package/src/components/animated-button/animated-button.component.ts +3 -2
  79. package/src/components/animated-button/animated-button.scss +6 -23
  80. package/src/components/animated-button/animated-button.test.ts +4 -3
  81. package/src/components/avatar-group/avatar-group.component.ts +84 -0
  82. package/src/components/avatar-group/avatar-group.scss +54 -0
  83. package/src/components/avatar-group/avatar-group.test.ts +59 -0
  84. package/src/components/avatar-group/index.ts +12 -0
  85. package/src/components/badge/badge.component.ts +49 -0
  86. package/src/components/badge/badge.scss +80 -0
  87. package/src/components/badge/badge.test.ts +34 -0
  88. package/src/components/badge/index.ts +12 -0
  89. package/src/components/bulk-actions/bulk-actions.component.ts +14 -2
  90. package/src/components/button/button.component.ts +242 -11
  91. package/src/components/button/button.scss +133 -25
  92. package/src/components/button/button.test.ts +122 -0
  93. package/src/components/button-menu/button-menu.component.ts +32 -11
  94. package/src/components/button-menu/button-menu.scss +5 -1
  95. package/src/components/channel-tile/channel-tile.component.ts +34 -27
  96. package/src/components/channel-tile/channel-tile.scss +76 -43
  97. package/src/components/chart/builders.test.ts +46 -0
  98. package/src/components/chart/builders.ts +27 -3
  99. package/src/components/chart/chart-live.test.ts +4 -6
  100. package/src/components/chart/chart.component.ts +125 -5
  101. package/src/components/chart/chart.scss +121 -0
  102. package/src/components/chart/chart.test.ts +81 -0
  103. package/src/components/chat-message/chat-message.component.ts +21 -1
  104. package/src/components/chat-message/chat-message.scss +13 -2
  105. package/src/components/chat-message/chat-message.test.ts +9 -0
  106. package/src/components/checkbox/checkbox.component.ts +61 -27
  107. package/src/components/checkbox/checkbox.scss +58 -25
  108. package/src/components/checkbox-group/checkbox-group.component.ts +7 -1
  109. package/src/components/chip/chip.component.ts +11 -1
  110. package/src/components/chip/chip.scss +91 -26
  111. package/src/components/chip/chip.test.ts +17 -0
  112. package/src/components/collapsible/collapsible.component.ts +17 -3
  113. package/src/components/collapsible/collapsible.scss +19 -10
  114. package/src/components/color-picker/color-picker.component.ts +277 -0
  115. package/src/components/color-picker/color-picker.scss +141 -0
  116. package/src/components/color-picker/color-picker.test.ts +57 -0
  117. package/src/components/color-picker/index.ts +12 -0
  118. package/src/components/cols/cols.component.ts +17 -3
  119. package/src/components/cols/cols.scss +24 -13
  120. package/src/components/cols/cols.test.ts +33 -0
  121. package/src/components/confirm/confirm.component.ts +20 -6
  122. package/src/components/confirm/confirm.test.ts +52 -0
  123. package/src/components/content-block/content-block.component.ts +38 -9
  124. package/src/components/content-block/content-block.scss +11 -5
  125. package/src/components/content-block/content-block.test.ts +26 -0
  126. package/src/components/copy-button/copy-button.component.ts +2 -2
  127. package/src/components/copy-button/copy-button.scss +17 -4
  128. package/src/components/data-select/data-select.component.ts +3 -1
  129. package/src/components/data-table/data-table.component.ts +364 -142
  130. package/src/components/data-table/data-table.scss +133 -60
  131. package/src/components/data-table/data-table.test.ts +356 -4
  132. package/src/components/data-table-filter/data-table-filter.component.ts +426 -406
  133. package/src/components/data-table-filter/data-table-filter.scss +176 -148
  134. package/src/components/data-table-filter/data-table-filter.test.ts +271 -273
  135. package/src/components/data-table-search/data-table-search.component.ts +4 -2
  136. package/src/components/data-table-search/data-table-search.scss +8 -1
  137. package/src/components/data-table-search/data-table-search.test.ts +29 -0
  138. package/src/components/date-range/date-range-math.test.ts +78 -0
  139. package/src/components/date-range/date-range-math.ts +105 -0
  140. package/src/components/date-range/date-range.component.ts +375 -0
  141. package/src/components/date-range/date-range.scss +272 -0
  142. package/src/components/date-range/date-range.test.ts +247 -0
  143. package/src/components/date-range/index.ts +12 -0
  144. package/src/components/datepicker/datepicker.component.ts +44 -7
  145. package/src/components/datepicker/datepicker.scss +9 -0
  146. package/src/components/datepicker/datepicker.test.ts +2 -2
  147. package/src/components/dialog/dialog.component.ts +38 -19
  148. package/src/components/dialog/dialog.scss +65 -36
  149. package/src/components/dialog/dialog.test.ts +14 -0
  150. package/src/components/diff/diff.component.ts +55 -0
  151. package/src/components/diff/diff.scss +46 -0
  152. package/src/components/diff/diff.test.ts +15 -0
  153. package/src/components/diff/index.ts +12 -0
  154. package/src/components/diff-row/diff-row.component.ts +75 -0
  155. package/src/components/diff-row/diff-row.scss +70 -0
  156. package/src/components/diff-row/diff-row.test.ts +37 -0
  157. package/src/components/diff-row/index.ts +12 -0
  158. package/src/components/dropdown/dropdown.component.ts +12 -10
  159. package/src/components/dropdown/dropdown.scss +2 -0
  160. package/src/components/editor/editor.component.ts +159 -11
  161. package/src/components/editor/editor.scss +250 -1
  162. package/src/components/editor/editor.test.ts +31 -3
  163. package/src/components/editor/modules/ai/panel/ai-panel.scss +0 -1
  164. package/src/components/editor/modules/attachment/attachment.ts +53 -31
  165. package/src/components/editor/modules/context-menu/context-menu.ts +17 -0
  166. package/src/components/editor/modules/dialog/dialog.component.ts +23 -1
  167. package/src/components/editor/modules/dialog/dialog.test.ts +28 -0
  168. package/src/components/editor/modules/emoji/emoji.ts +4 -5
  169. package/src/components/editor/modules/merge-field/merge-field.ts +83 -0
  170. package/src/components/editor/modules/toolbar/toolbar.component.ts +121 -68
  171. package/src/components/editor/modules/toolbar/toolbar.scss +38 -0
  172. package/src/components/editor/modules/toolbar/toolbar.ts +25 -1
  173. package/src/components/empty-state/empty-state.component.ts +6 -1
  174. package/src/components/empty-state/empty-state.scss +21 -4
  175. package/src/components/expanding-action/expanding-action.component.ts +22 -34
  176. package/src/components/expanding-action/expanding-action.scss +3 -45
  177. package/src/components/expanding-action/expanding-action.test.ts +2 -1
  178. package/src/components/file/file.component.ts +30 -2
  179. package/src/components/file/file.scss +6 -0
  180. package/src/components/filter-wrapper/filter-wrapper.component.ts +45 -10
  181. package/src/components/filter-wrapper/filter-wrapper.test.ts +58 -2
  182. package/src/components/flow-builder/flow-builder.component.ts +36 -3
  183. package/src/components/flow-builder/flow-builder.scss +0 -6
  184. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.component.ts +8 -0
  185. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.scss +10 -5
  186. package/src/components/flow-builder/modules/flow-node/flow-node.component.ts +17 -0
  187. package/src/components/flow-builder/modules/flow-node/flow-node.scss +10 -0
  188. package/src/components/form-actions/form-actions.component.ts +48 -2
  189. package/src/components/form-actions/form-actions.scss +1 -1
  190. package/src/components/form-group/form-group.component.ts +10 -3
  191. package/src/components/form-group/form-group.scss +37 -4
  192. package/src/components/form-group/form-group.test.ts +10 -0
  193. package/src/components/header/header.component.ts +11 -4
  194. package/src/components/header/header.scss +47 -5
  195. package/src/components/hover-container/hover-container.component.ts +4 -5
  196. package/src/components/icon/icon.component.ts +95 -9
  197. package/src/components/icon/icon.scss +70 -6
  198. package/src/components/icon/icon.test.ts +55 -0
  199. package/src/components/icon-picker/icon-picker.component.ts +35 -1
  200. package/src/components/inline-edit/inline-edit.component.ts +38 -8
  201. package/src/components/inline-edit/inline-edit.scss +128 -19
  202. package/src/components/input/input.component.ts +173 -297
  203. package/src/components/input/input.scss +65 -43
  204. package/src/components/input/input.test.ts +86 -4
  205. package/src/components/item/item.component.ts +12 -0
  206. package/src/components/item/item.scss +102 -3
  207. package/src/components/item/item.test.ts +23 -0
  208. package/src/components/key/key.component.ts +2 -2
  209. package/src/components/key/key.scss +11 -0
  210. package/src/components/key-value/index.ts +12 -0
  211. package/src/components/key-value/key-value.component.ts +160 -0
  212. package/src/components/key-value/key-value.scss +20 -0
  213. package/src/components/key-value/key-value.test.ts +22 -0
  214. package/src/components/markdown-editor/markdown-editor.component.ts +8 -1
  215. package/src/components/menu/menu.component.ts +21 -2
  216. package/src/components/menu/menu.scss +43 -23
  217. package/src/components/menu/menu.test.ts +33 -0
  218. package/src/components/menu-item/menu-item.component.ts +13 -2
  219. package/src/components/menu-item/menu-item.scss +56 -23
  220. package/src/components/menu-item/menu-item.test.ts +31 -0
  221. package/src/components/menu-item/submenu-controller.ts +25 -15
  222. package/src/components/menu-label/index.ts +12 -0
  223. package/src/components/menu-label/menu-label.component.ts +30 -0
  224. package/src/components/menu-label/menu-label.scss +18 -0
  225. package/src/components/nav-item/index.ts +12 -0
  226. package/src/components/nav-item/nav-item.component.ts +83 -0
  227. package/src/components/nav-item/nav-item.scss +128 -0
  228. package/src/components/nav-list/index.ts +12 -0
  229. package/src/components/nav-list/nav-list.component.ts +313 -0
  230. package/src/components/nav-list/nav-list.scss +87 -0
  231. package/src/components/nav-list/nav-list.test.ts +250 -0
  232. package/src/components/navbar/navbar.component.ts +235 -171
  233. package/src/components/navbar/navbar.scss +333 -169
  234. package/src/components/navbar/navbar.test.ts +63 -143
  235. package/src/components/page/page.component.ts +114 -29
  236. package/src/components/page/page.scss +223 -95
  237. package/src/components/page/page.test.ts +214 -3
  238. package/src/components/page-builder/page-builder.component.ts +51 -5
  239. package/src/components/page-builder/page-builder.scss +6 -1
  240. package/src/components/page-nav/page-nav.component.ts +25 -3
  241. package/src/components/page-nav/page-nav.scss +10 -2
  242. package/src/components/pagination/pagination.scss +1 -1
  243. package/src/components/panel/panel.component.ts +14 -13
  244. package/src/components/panel/panel.scss +14 -10
  245. package/src/components/panel/panel.test.ts +42 -0
  246. package/src/components/popup/popup.component.ts +20 -13
  247. package/src/components/popup/popup.test.ts +21 -1
  248. package/src/components/priority-list/priority-list.component.ts +157 -172
  249. package/src/components/priority-list/priority-list.scss +113 -17
  250. package/src/components/priority-list/priority-list.test.ts +301 -3
  251. package/src/components/progress-bar/progress-bar.component.ts +1 -1
  252. package/src/components/progress-bar/progress-bar.scss +16 -4
  253. package/src/components/progress-tile/progress-tile.scss +1 -1
  254. package/src/components/query-builder/query-builder.component.ts +85 -10
  255. package/src/components/query-builder/query-builder.scss +7 -1
  256. package/src/components/query-builder/query-builder.test.ts +40 -0
  257. package/src/components/radio/radio.component.ts +57 -18
  258. package/src/components/radio/radio.scss +101 -26
  259. package/src/components/radio-group/radio-group.component.ts +45 -7
  260. package/src/components/radio-group/radio-group.scss +17 -0
  261. package/src/components/radio-group/radio-group.test.ts +63 -2
  262. package/src/components/rating/rating.component.ts +23 -0
  263. package/src/components/rating/rating.scss +1 -1
  264. package/src/components/remarkd-editor/remarkd-editor.component.ts +60 -5
  265. package/src/components/remarkd-editor/remarkd-editor.scss +12 -4
  266. package/src/components/report-table/index.ts +12 -0
  267. package/src/components/report-table/report-format.test.ts +86 -0
  268. package/src/components/report-table/report-format.ts +72 -0
  269. package/src/components/report-table/report-math.test.ts +74 -0
  270. package/src/components/report-table/report-math.ts +252 -0
  271. package/src/components/report-table/report-table.component.ts +681 -0
  272. package/src/components/report-table/report-table.scss +328 -0
  273. package/src/components/report-table/report-table.test.ts +702 -0
  274. package/src/components/reveal/reveal.component.ts +15 -1
  275. package/src/components/reveal/reveal.scss +5 -0
  276. package/src/components/schedule-builder/schedule-builder.component.ts +23 -8
  277. package/src/components/scroll-container/scroll-container.component.ts +19 -1
  278. package/src/components/scroll-container/scroll-container.scss +6 -1
  279. package/src/components/select/select.component.ts +52 -36
  280. package/src/components/select/select.scss +42 -25
  281. package/src/components/select/select.test.ts +14 -2
  282. package/src/components/settings-container/settings-container.scss +18 -5
  283. package/src/components/sidebar/sidebar.component.ts +8 -1
  284. package/src/components/sidebar/sidebar.scss +6 -1
  285. package/src/components/skeleton/skeleton.component.ts +3 -3
  286. package/src/components/skeleton/skeleton.scss +6 -6
  287. package/src/components/skeleton/skeleton.test.ts +9 -0
  288. package/src/components/slash-menu/slash-menu.scss +1 -1
  289. package/src/components/slideout/slideout.component.ts +20 -3
  290. package/src/components/slideout/slideout.scss +13 -6
  291. package/src/components/slideout/slideout.test.ts +19 -0
  292. package/src/components/split-button/split-button.component.ts +1 -1
  293. package/src/components/split-pane/split-pane.component.ts +71 -8
  294. package/src/components/split-pane/split-pane.scss +145 -41
  295. package/src/components/split-pane/split-pane.test.ts +23 -3
  296. package/src/components/stat/stat.component.ts +209 -14
  297. package/src/components/stat/stat.scss +305 -5
  298. package/src/components/stat/stat.test.ts +143 -2
  299. package/src/components/status-indicator/status-indicator.scss +22 -21
  300. package/src/components/stepper/stepper.scss +3 -3
  301. package/src/components/tab/tab.component.ts +1 -0
  302. package/src/components/table/table.component.ts +12 -1
  303. package/src/components/table/table.scss +21 -13
  304. package/src/components/table/table.test.ts +17 -0
  305. package/src/components/tabs/tabs.component.ts +150 -37
  306. package/src/components/tabs/tabs.scss +48 -7
  307. package/src/components/tabs/tabs.test.ts +169 -0
  308. package/src/components/textarea/textarea.component.ts +25 -3
  309. package/src/components/textarea/textarea.scss +10 -0
  310. package/src/components/textarea/textarea.test.ts +8 -4
  311. package/src/components/theme-editor/theme-editor.scss +0 -6
  312. package/src/components/tile/tile.component.ts +32 -2
  313. package/src/components/tile/tile.scss +40 -7
  314. package/src/components/tile/tile.test.ts +10 -0
  315. package/src/components/toggle/toggle.component.ts +23 -7
  316. package/src/components/toggle/toggle.scss +36 -29
  317. package/src/components/toggle/toggle.test.ts +17 -0
  318. package/src/components/tooltip/tooltip.component.ts +3 -0
  319. package/src/components/tooltip/tooltip.scss +11 -6
  320. package/src/components/tooltip/tooltip.test.ts +12 -0
  321. package/src/components/translation-field/index.ts +12 -0
  322. package/src/components/translation-field/translation-field.component.ts +55 -0
  323. package/src/components/translation-field/translation-field.scss +101 -0
  324. package/src/components/translation-fields/index.ts +12 -0
  325. package/src/components/translation-fields/translation-fields.component.ts +80 -0
  326. package/src/components/translation-fields/translation-fields.scss +60 -0
  327. package/src/components/translation-grid/index.ts +12 -0
  328. package/src/components/translation-grid/translation-grid.component.ts +157 -0
  329. package/src/components/translation-grid/translation-grid.scss +138 -0
  330. package/src/components/translation-grid/translation-grid.test.ts +28 -0
  331. package/src/components/vertical-stepper/vertical-stepper.component.ts +35 -9
  332. package/src/components/vertical-stepper/vertical-stepper.scss +110 -39
  333. package/src/components/vertical-stepper/vertical-stepper.test.ts +35 -0
  334. package/src/components/well/well.scss +2 -2
  335. package/src/events/events.ts +2 -0
  336. package/src/events/zn-chart-click.ts +13 -0
  337. package/src/events/zn-report-change.ts +7 -0
  338. package/src/form-control.scss +36 -3
  339. package/src/internal/anchored-popover.ts +175 -0
  340. package/src/internal/animate.ts +19 -5
  341. package/src/internal/cosmic-border.ts +15 -0
  342. package/src/internal/form.ts +1 -1
  343. package/src/internal/hover-intent.ts +15 -0
  344. package/src/internal/input-modality.ts +15 -0
  345. package/src/internal/pointer-intent.ts +37 -0
  346. package/src/internal/reorder.ts +625 -0
  347. package/src/internal/scroll.ts +51 -3
  348. package/src/internal/selection-card.ts +1 -1
  349. package/src/internal/tab-indicator.ts +92 -0
  350. package/src/internal/table-sort.ts +25 -0
  351. package/src/internal/table-tabs.ts +108 -0
  352. package/src/internal/test-drag.ts +35 -0
  353. package/src/internal/theme-openbyte.test.ts +126 -0
  354. package/src/internal/theme.ts +4 -4
  355. package/src/internal/zinc-element.ts +2 -2
  356. package/src/selection-card.scss +25 -0
  357. package/src/types/web-test-runner-commands.d.ts +1 -0
  358. package/src/utilities/color.test.ts +43 -0
  359. package/src/utilities/color.ts +189 -0
  360. package/src/zinc.ts +14 -0
  361. package/.github/workflows/eleventy_build.yml +0 -42
  362. package/scss/themes/_aura.scss +0 -114
@@ -278,6 +278,9 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
278
278
  */
279
279
  @property({attribute: 'upload-url'}) uploadUrl = '';
280
280
 
281
+ /** The largest each file can be, in megabytes. A bigger file makes the control invalid. 0 for no limit. */
282
+ @property({attribute: 'max-size', type: Number}) maxSize = 0;
283
+
281
284
  @state() private uploading = false;
282
285
 
283
286
  /** Gets the validity state object */
@@ -407,13 +410,32 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
407
410
  this.input.click();
408
411
  }
409
412
 
413
+ private handleDropareaKeyDown(e: KeyboardEvent) {
414
+ if ((e.key === 'Enter' || e.key === ' ') && e.target === e.currentTarget) {
415
+ this.handleClick(e);
416
+ }
417
+ }
418
+
410
419
  /** Handles the change event of the native input */
420
+ private checkSize(): boolean {
421
+ const tooBig = this.maxSize > 0
422
+ ? Array.from(this.files || []).find(file => file.size > this.maxSize * 1024 * 1024)
423
+ : undefined;
424
+ this.setCustomValidity(tooBig ? `${tooBig.name} is larger than the ${this.maxSize} MB limit.` : '');
425
+ return !tooBig;
426
+ }
427
+
411
428
  private handleChange(e: Event) {
412
429
  e.preventDefault();
413
430
  e.stopPropagation();
414
431
 
415
432
  this.updatePreview();
416
433
 
434
+ if (!this.checkSize()) {
435
+ this.reportValidity();
436
+ return;
437
+ }
438
+
417
439
  const file = this.files?.[0];
418
440
  if (this.uploadUrl && file) {
419
441
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -661,7 +683,11 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
661
683
  class="input__delete"
662
684
  src="close"
663
685
  size="16"
686
+ role="button"
687
+ tabindex="0"
688
+ aria-label="${this.localize.term('clearEntry')}"
664
689
  @click="${this.removeFile}"
690
+ @keydown="${(e: KeyboardEvent) => (e.key === 'Enter' || e.key === ' ') && this.removeFile(e)}"
665
691
  part="delete"></zn-icon>` : ''}
666
692
  </zn-chip>
667
693
  `)}
@@ -691,7 +717,7 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
691
717
  'droparea--has-preview': showImagePreview,
692
718
  })}
693
719
  @click="${this.handleClick}"
694
- @keypress="${this.handleClick}"
720
+ @keydown="${this.handleDropareaKeyDown}"
695
721
  @focus="${this.handleFocus}"
696
722
  @blur="${this.handleBlur}"
697
723
  tabindex="${this.disabled ? '-1' : '0'}"
@@ -721,8 +747,10 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
721
747
  : (hasValue
722
748
  ? html`<span class="droparea__filename" part="filename">${displayName}</span>`
723
749
  : html`
750
+ <!-- The focusable droparea is the control; the button is its label -->
724
751
  <zn-button
725
752
  class="droparea__upload"
753
+ inert
726
754
  icon="cloud_upload"
727
755
  icon-size="20"
728
756
  panel-bg
@@ -792,7 +820,7 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
792
820
  part="form-control">
793
821
  ${hasTrigger ?
794
822
  html`
795
- <slot name="trigger" part="trigger" @click="${this.handleClick}" @keypress="${this.handleClick}"></slot>` :
823
+ <slot name="trigger" part="trigger" @click="${this.handleClick}"></slot>` :
796
824
  html`
797
825
  <label
798
826
  aria-hidden="${hasLabel ? 'false' : 'true'}"
@@ -64,6 +64,12 @@
64
64
 
65
65
  .input__delete {
66
66
  cursor: pointer;
67
+
68
+ &:focus-visible {
69
+ outline: var(--zn-focus-ring);
70
+ border-radius: 50%;
71
+ }
72
+
67
73
  color: var(--zn-input-color);
68
74
  font-size: var(--zn-input-label-font-size-medium);
69
75
 
@@ -1,9 +1,10 @@
1
1
  import {type CSSResultGroup, html, unsafeCSS} from 'lit';
2
- import {property} from "lit/decorators.js";
2
+ import {property, state} from "lit/decorators.js";
3
3
  import {trimControlValue} from '../../internal/form';
4
4
  import ZincElement from '../../internal/zinc-element';
5
5
 
6
6
  import type ZnDataSelect from "../data-select";
7
+ import type ZnButton from "../button";
7
8
  import type ZnDataTable from "../data-table";
8
9
  import type ZnInput from "../input";
9
10
  import type ZnQueryBuilder from "../query-builder";
@@ -42,12 +43,20 @@ export default class ZnFilterWrapper extends ZincElement {
42
43
 
43
44
  @property() button: string = 'Filter';
44
45
 
45
- private hasSubmitButton: boolean = false;
46
+ /** Submits the filters when Enter is pressed in a text field. */
47
+ @property({attribute: 'submit-on-enter', type: Boolean}) submitOnEnter = false;
48
+
49
+ /** Shown on the submit button while results load. */
50
+ @property({attribute: 'loading-text'}) loadingText = 'Searching...';
51
+
52
+ @state() private loading = false;
53
+
54
+ private submitButton: ZnButton | null = null;
46
55
 
47
56
  handleSubmit = (event: Event) => {
48
57
  event.preventDefault();
49
58
  const dataTable: ZnDataTable | null = this.closest('zn-data-table');
50
- if (dataTable) {
59
+ if (dataTable && !this.loading) {
51
60
  const allowedInputs = ['zn-input', 'zn-select', 'zn-query-builder', 'zn-data-select', 'zn-datepicker', 'input', 'select', 'textarea'];
52
61
  const inputs = Array.from(this.querySelectorAll(allowedInputs.join(','))) as AllowedInputElement[];
53
62
  const params: Record<string, any> = {};
@@ -60,17 +69,41 @@ export default class ZnFilterWrapper extends ZincElement {
60
69
  });
61
70
 
62
71
  dataTable.requestParams = params;
63
- dataTable.refresh();
72
+ this.setLoading(true);
73
+ Promise.resolve(dataTable.refresh()).finally(() => this.setLoading(false));
74
+ }
75
+ }
76
+
77
+ private setLoading(loading: boolean) {
78
+ this.loading = loading;
79
+ if (this.submitButton) {
80
+ this.submitButton.processingText = this.loadingText;
81
+ this.submitButton.processing = loading;
82
+ }
83
+ }
84
+
85
+ handleKeyDown = (event: KeyboardEvent) => {
86
+ if (!this.submitOnEnter || event.key !== 'Enter' || event.defaultPrevented || event.isComposing
87
+ || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
88
+ return;
89
+ }
90
+
91
+ // Only plain text fields: selects, buttons and textareas use Enter themselves
92
+ if (event.composedPath()[0] instanceof HTMLInputElement) {
93
+ this.handleSubmit(event);
64
94
  }
65
95
  }
66
96
 
67
97
  connectedCallback() {
68
98
  super.connectedCallback();
69
- const button: HTMLElement | null = this.querySelector('zn-button[submit]');
70
- if (button) {
71
- this.hasSubmitButton = true;
72
- button.addEventListener('click', this.handleSubmit);
73
- }
99
+ this.addEventListener('keydown', this.handleKeyDown);
100
+ this.submitButton = this.querySelector('zn-button[submit]');
101
+ this.submitButton?.addEventListener('click', this.handleSubmit);
102
+ }
103
+
104
+ disconnectedCallback() {
105
+ super.disconnectedCallback();
106
+ this.removeEventListener('keydown', this.handleKeyDown);
74
107
  }
75
108
 
76
109
  private renderDefaultButton() {
@@ -78,6 +111,8 @@ export default class ZnFilterWrapper extends ZincElement {
78
111
  <zn-button part="search-button"
79
112
  class="filter-wrapper__submit"
80
113
  type="submit"
114
+ ?processing="${this.loading}"
115
+ processing-text="${this.loadingText}"
81
116
  @click="${this.handleSubmit}">
82
117
  ${this.button}
83
118
  </zn-button>
@@ -87,7 +122,7 @@ export default class ZnFilterWrapper extends ZincElement {
87
122
  render() {
88
123
  return html`
89
124
  <slot></slot>
90
- ${this.hasSubmitButton ? '' : this.renderDefaultButton()}
125
+ ${this.submitButton ? '' : this.renderDefaultButton()}
91
126
  `;
92
127
  }
93
128
  }
@@ -1,6 +1,7 @@
1
1
  import '../../../dist/zn.min.js';
2
2
  import { expect, fixture, html } from '@open-wc/testing';
3
3
  import { sendKeys } from '@web/test-runner-commands';
4
+ import type ZnButton from '../button';
4
5
  import type ZnDataTable from '../data-table';
5
6
  import type ZnFilterWrapper from './filter-wrapper.component';
6
7
  import type ZnInput from '../input';
@@ -20,7 +21,7 @@ describe('<zn-filter-wrapper>', () => {
20
21
  </zn-filter-wrapper>
21
22
  </zn-data-table>
22
23
  `);
23
- table.refresh = () => undefined;
24
+ table.refresh = async () => undefined;
24
25
  const wrapper = table.querySelector<ZnFilterWrapper>('zn-filter-wrapper')!;
25
26
  const input = table.querySelector<ZnInput>('zn-input')!;
26
27
  await input.updateComplete;
@@ -40,7 +41,7 @@ describe('<zn-filter-wrapper>', () => {
40
41
  </zn-filter-wrapper>
41
42
  </zn-data-table>
42
43
  `);
43
- table.refresh = () => undefined;
44
+ table.refresh = async () => undefined;
44
45
  const [input, clean] = table.querySelectorAll<ZnInput>('zn-input');
45
46
  await input.updateComplete;
46
47
 
@@ -53,4 +54,59 @@ describe('<zn-filter-wrapper>', () => {
53
54
  expect(table.requestParams.email).to.equal('a@b.com');
54
55
  expect(input.input.scrollWidth).to.equal(clean.input.scrollWidth);
55
56
  });
57
+
58
+ it('submits on Enter only when submit-on-enter is set', async () => {
59
+ const table = await fixture<ZnDataTable>(html`
60
+ <zn-data-table>
61
+ <zn-filter-wrapper slot="filter-top">
62
+ <zn-input name="email"></zn-input>
63
+ </zn-filter-wrapper>
64
+ </zn-data-table>
65
+ `);
66
+ let refreshes = 0;
67
+ table.refresh = async () => { refreshes++; };
68
+ const wrapper = table.querySelector<ZnFilterWrapper>('zn-filter-wrapper')!;
69
+ const input = table.querySelector<ZnInput>('zn-input')!;
70
+ await input.updateComplete;
71
+
72
+ input.focus();
73
+ await sendKeys({type: 'a@b.com'});
74
+ await sendKeys({press: 'Enter'});
75
+ expect(refreshes).to.equal(0);
76
+
77
+ wrapper.submitOnEnter = true;
78
+ await sendKeys({press: 'Enter'});
79
+ expect(refreshes).to.equal(1);
80
+ expect(table.requestParams).to.deep.equal({email: 'a@b.com'});
81
+ });
82
+
83
+ it('shows the submit button as processing until results load', async () => {
84
+ const table = await fixture<ZnDataTable>(html`
85
+ <zn-data-table>
86
+ <zn-filter-wrapper slot="filter-top">
87
+ <zn-input name="email"></zn-input>
88
+ <zn-button submit>Submit</zn-button>
89
+ </zn-filter-wrapper>
90
+ </zn-data-table>
91
+ `);
92
+ let finish!: () => void;
93
+ let refreshes = 0;
94
+ table.refresh = () => {
95
+ refreshes++;
96
+ return new Promise<void>(resolve => { finish = resolve; });
97
+ };
98
+ const button = table.querySelector<ZnButton>('zn-button')!;
99
+ await button.updateComplete;
100
+ const base = button.shadowRoot!.querySelector<HTMLButtonElement>('[part="base"]')!;
101
+
102
+ base.click();
103
+ base.click();
104
+ expect(refreshes).to.equal(1);
105
+ expect(button.processing).to.be.true;
106
+ expect(button.processingText).to.equal('Searching...');
107
+
108
+ finish();
109
+ await new Promise(resolve => setTimeout(resolve));
110
+ expect(button.processing).to.be.false;
111
+ });
56
112
  });
@@ -171,6 +171,8 @@ export default class ZnFlowBuilder extends ZincElement {
171
171
  @state() private _movingNodeId: string | null = null;
172
172
  /** The "+" picker popover target (an open output, or a wire to insert into), if open. */
173
173
  @state() private _picker: { x: number; y: number; target: PickerTarget } | null = null;
174
+ /** Where focus goes back to when the picker or branch editor closes. */
175
+ private _focusReturn: HTMLElement | null = null;
174
176
  /** The node type currently being dragged from the steps panel, for the canvas drop preview. */
175
177
  @state() private _draggingType: string | null = null;
176
178
 
@@ -338,12 +340,41 @@ export default class ZnFlowBuilder extends ZincElement {
338
340
 
339
341
  private _onKeyDown = (e: KeyboardEvent) => {
340
342
  if (e.key === 'Escape' && (this._movingNodeId || this._picker || this._selectedBranch)) {
343
+ const hadFocus = !!this.shadowRoot?.querySelector('.picker:focus-within, .inspector:focus-within');
341
344
  this._movingNodeId = null;
342
345
  this._picker = null;
343
346
  this._selectedBranch = null;
347
+ if (hadFocus) void this._returnFocus(this._focusReturn);
344
348
  }
345
349
  };
346
350
 
351
+ private static _deepActiveElement() {
352
+ let el = document.activeElement;
353
+ while (el?.shadowRoot?.activeElement) el = el.shadowRoot.activeElement;
354
+ return el as HTMLElement | null;
355
+ }
356
+
357
+ private _nodeCard(nodeId: string) {
358
+ const canvas = this.shadowRoot?.querySelector('zn-flow-canvas');
359
+ const node = [...(canvas?.shadowRoot?.querySelectorAll('zn-flow-node') ?? [])].find(n => n.node?.id === nodeId);
360
+ return node?.shadowRoot?.querySelector<HTMLElement>('.card');
361
+ }
362
+
363
+ private async _returnFocus(target: HTMLElement | null | undefined) {
364
+ await this.updateComplete;
365
+ if (target?.isConnected) target.focus();
366
+ }
367
+
368
+ private _closeInspector(nodeId: string) {
369
+ this._select(null);
370
+ void this._returnFocus(this._nodeCard(nodeId));
371
+ }
372
+
373
+ private _closeBranchEditor(nodeId: string) {
374
+ this._selectedBranch = null;
375
+ void this._returnFocus(this._focusReturn?.isConnected ? this._focusReturn : this._nodeCard(nodeId));
376
+ }
377
+
347
378
  protected willUpdate(changed: PropertyValues) {
348
379
  if (changed.has('nodeTypes') && this.nodeTypes?.length) {
349
380
  this.registry.registerAll(this.nodeTypes);
@@ -974,6 +1005,7 @@ export default class ZnFlowBuilder extends ZincElement {
974
1005
  }
975
1006
 
976
1007
  private _onBranchPick = (e: CustomEvent<{ nodeId: string; port: string }>) => {
1008
+ this._focusReturn = ZnFlowBuilder._deepActiveElement();
977
1009
  this._select(null);
978
1010
  this._sideCollapsed = false;
979
1011
  this._selectedBranch = {nodeId: e.detail.nodeId, port: e.detail.port};
@@ -1031,6 +1063,7 @@ export default class ZnFlowBuilder extends ZincElement {
1031
1063
  };
1032
1064
 
1033
1065
  private _onWirePick = (e: CustomEvent<{ connectionId: string; clientX: number; clientY: number }>) => {
1066
+ this._focusReturn = ZnFlowBuilder._deepActiveElement();
1034
1067
  this._picker = {
1035
1068
  x: e.detail.clientX,
1036
1069
  y: e.detail.clientY,
@@ -1260,7 +1293,7 @@ export default class ZnFlowBuilder extends ZincElement {
1260
1293
  <div class="inspector-head__title">${node.label ?? type?.label ?? node.type}</div>
1261
1294
  <div class="inspector-head__type">${type?.label ?? node.type}</div>
1262
1295
  </div>
1263
- <button class="inspector-close" title="Close" @click="${() => this._select(null)}">
1296
+ <button class="inspector-close" title="Close" @click="${() => this._closeInspector(node.id)}">
1264
1297
  <zn-icon src="x@lu" size="18"></zn-icon>
1265
1298
  </button>
1266
1299
  </div>
@@ -1337,7 +1370,7 @@ export default class ZnFlowBuilder extends ZincElement {
1337
1370
  : `Branch of ${node.label ?? type?.label ?? node.type}`}
1338
1371
  </div>
1339
1372
  </div>
1340
- <button class="inspector-close" title="Close" @click="${() => (this._selectedBranch = null)}">
1373
+ <button class="inspector-close" title="Close" @click="${() => this._closeBranchEditor(node.id)}">
1341
1374
  <zn-icon src="x@lu" size="18"></zn-icon>
1342
1375
  </button>
1343
1376
  </div>
@@ -1358,7 +1391,7 @@ export default class ZnFlowBuilder extends ZincElement {
1358
1391
  .value="${branchConditions(port)}"
1359
1392
  @flow-conditions-save="${(e: CustomEvent<{ conditions: FlowBranchConditions }>) =>
1360
1393
  this._saveBranchConditions(node, port, e.detail.conditions)}"
1361
- @flow-conditions-cancel="${() => (this._selectedBranch = null)}"
1394
+ @flow-conditions-cancel="${() => this._closeBranchEditor(node.id)}"
1362
1395
  ></zn-flow-branch-conditions>`
1363
1396
  : html`<p class="inspector-hint">This step type has no branch conditions.</p>`}
1364
1397
  </div>
@@ -141,12 +141,6 @@
141
141
  // rather than the navbar's default body colour.
142
142
  --zn-active-nav-background: var(--zn-panel);
143
143
 
144
- // Themes with a glass page tab bar (e.g. Aura) frost inactive tabs on every
145
- // navbar under a zn-page — over the panel the translucent white just reads
146
- // as white. Pin the standard tab tint instead.
147
- --navbar-page-inactive-tab-background: rgb(var(--zn-color-tab));
148
- --navbar-page-inactive-tab-filter: none;
149
-
150
144
  border-right: 1px solid rgb(var(--zn-border-color));
151
145
 
152
146
  // The registry-driven steps panel fills the column below the title block.
@@ -1181,11 +1181,19 @@ export default class ZnFlowCanvas extends ZincElement {
1181
1181
  <span
1182
1182
  class="branch-pill-port"
1183
1183
  title="Start a branch"
1184
+ role="button"
1185
+ tabindex="0"
1186
+ aria-label="Start a branch"
1184
1187
  @pointerdown="${(e: PointerEvent) => e.button === 0 && e.stopPropagation()}"
1185
1188
  @click="${(e: Event) => {
1186
1189
  e.stopPropagation();
1187
1190
  this._onBranchPortClick(i.nodeId, i.portId);
1188
1191
  }}"
1192
+ @keydown="${(e: KeyboardEvent) => {
1193
+ if (e.key !== 'Enter' && e.key !== ' ') return;
1194
+ e.preventDefault();
1195
+ (e.currentTarget as HTMLElement).click();
1196
+ }}"
1189
1197
  ></span>` : ''}
1190
1198
  </div>
1191
1199
  `
@@ -248,6 +248,11 @@
248
248
  transform: translate(-50%, 50%) scale(1.35);
249
249
  box-shadow: 0 0 0 4px color-mix(in srgb, rgb(var(--zn-color-primary)) 20%, transparent);
250
250
  }
251
+
252
+ &:focus-visible {
253
+ outline: var(--zn-focus-ring);
254
+ outline-offset: 2px;
255
+ }
251
256
  }
252
257
 
253
258
  // Slides out from behind the pill on hover; deletes the branch and its wire.
@@ -372,8 +377,8 @@
372
377
  position: absolute;
373
378
  display: flex;
374
379
  flex-direction: column;
375
- background: #fef9c3;
376
- border: 1px solid #fde68a;
380
+ background: var(--zn-flow-note-background, #fef9c3);
381
+ border: 1px solid var(--zn-flow-note-border, #fde68a);
377
382
  border-radius: 4px;
378
383
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
379
384
 
@@ -385,7 +390,7 @@
385
390
  height: 16px;
386
391
  cursor: nwse-resize;
387
392
  // Visual corner grip.
388
- background: linear-gradient(135deg, transparent 0 50%, #d9c64a 50% 60%, transparent 60% 70%, #d9c64a 70% 80%, transparent 80%);
393
+ background: linear-gradient(135deg, transparent 0 50%, var(--zn-flow-note-fold, #d9c64a) 50% 60%, transparent 60% 70%, var(--zn-flow-note-fold, #d9c64a) 70% 80%, transparent 80%);
389
394
  }
390
395
 
391
396
  &__bar {
@@ -401,7 +406,7 @@
401
406
  padding: 0;
402
407
  font-size: 14px;
403
408
  line-height: 1;
404
- color: #92400e;
409
+ color: var(--zn-flow-note-meta-color, #92400e);
405
410
  background: transparent;
406
411
  cursor: pointer;
407
412
  }
@@ -416,6 +421,6 @@
416
421
  background: transparent;
417
422
  font: inherit;
418
423
  font-size: 0.8125rem;
419
- color: #713f12;
424
+ color: var(--zn-flow-note-color, #713f12);
420
425
  }
421
426
  }
@@ -91,6 +91,13 @@ export default class ZnFlowNode extends ZincElement {
91
91
  this._emit('flow-node-grab', {nodeId: this.node.id, clientX: e.clientX, clientY: e.clientY});
92
92
  };
93
93
 
94
+ private _onBodyKeyDown = (e: KeyboardEvent) => {
95
+ // The node's menu trigger keeps its own keys
96
+ if (e.target !== e.currentTarget || (e.key !== 'Enter' && e.key !== ' ')) return;
97
+ e.preventDefault();
98
+ this._emit('flow-node-select', {nodeId: this.node.id});
99
+ };
100
+
94
101
  private _action(action: 'delete' | 'duplicate' | 'move') {
95
102
  this._emit('flow-node-action', {nodeId: this.node.id, action});
96
103
  }
@@ -126,7 +133,9 @@ export default class ZnFlowNode extends ZincElement {
126
133
  <div
127
134
  part="base"
128
135
  class="${classMap({card: true, selected: this.selected, error: this.error, 'link-target': this.linkTarget})}"
136
+ tabindex="0"
129
137
  @pointerdown="${this._onBodyPointerDown}"
138
+ @keydown="${this._onBodyKeyDown}"
130
139
  >
131
140
  ${inputs.length ? this._renderPorts(inputs, 'in') : ''}
132
141
 
@@ -159,6 +168,14 @@ export default class ZnFlowNode extends ZincElement {
159
168
  <span
160
169
  class="port port--out"
161
170
  title="Start a branch"
171
+ role="button"
172
+ tabindex="0"
173
+ aria-label="Start a branch"
174
+ @keydown="${(e: KeyboardEvent) => {
175
+ if (e.key !== 'Enter' && e.key !== ' ') return;
176
+ e.preventDefault();
177
+ (e.currentTarget as HTMLElement).click();
178
+ }}"
162
179
  @pointerdown="${(e: PointerEvent) => e.button === 0 && e.stopPropagation()}"
163
180
  @click="${(e: Event) => {
164
181
  e.stopPropagation();
@@ -35,6 +35,11 @@
35
35
  border-color: rgb(var(--zn-color-border-active));
36
36
  }
37
37
 
38
+ &:focus-visible {
39
+ outline: var(--zn-focus-ring);
40
+ outline-offset: 2px;
41
+ }
42
+
38
43
  &.selected {
39
44
  border-color: var(--node-accent);
40
45
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--node-accent) 35%, transparent);
@@ -157,6 +162,11 @@ zn-menu-item.danger {
157
162
  background: var(--node-accent);
158
163
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--node-accent) 25%, transparent);
159
164
  }
165
+
166
+ &:focus-visible {
167
+ outline: var(--zn-focus-ring);
168
+ outline-offset: 2px;
169
+ }
160
170
  }
161
171
 
162
172
  // Anchored at the card edge, at its horizontal position (set inline as left:%).
@@ -1,6 +1,6 @@
1
1
  import {type CSSResultGroup, html, unsafeCSS} from 'lit';
2
2
  import {idSelector} from "../../utilities/query";
3
- import {property} from 'lit/decorators.js';
3
+ import {property, state} from 'lit/decorators.js';
4
4
  import ZincElement from '../../internal/zinc-element';
5
5
  import ZnButton from "../button";
6
6
 
@@ -58,6 +58,51 @@ export default class ZnFormActions extends ZincElement {
58
58
  /** The id of the form to act on. If omitted, the closest containing form is used. */
59
59
  @property() form: string;
60
60
 
61
+ /**
62
+ * Shows the submit button loading and ignores further submits until the console reports the request finished,
63
+ * so a slow save such as an upload can't be sent twice. Only for forms the console submits.
64
+ */
65
+ @property({attribute: 'loading-on-submit', type: Boolean}) loadingOnSubmit = false;
66
+
67
+ @state() private submitting = false;
68
+
69
+ private watchedForm: HTMLFormElement | null = null;
70
+
71
+ connectedCallback() {
72
+ super.connectedCallback();
73
+ if (this.loadingOnSubmit) {
74
+ this.watchedForm = this.getForm();
75
+ this.watchedForm?.addEventListener('submit', this.handleFormSubmit, {capture: true});
76
+ this.watchedForm?.addEventListener('requested', this.handleRequested);
77
+ ['complete', 'error', 'cancelled'].forEach(e => this.watchedForm?.addEventListener(e, this.handleDone));
78
+ }
79
+ }
80
+
81
+ disconnectedCallback() {
82
+ this.watchedForm?.removeEventListener('submit', this.handleFormSubmit, {capture: true});
83
+ this.watchedForm?.removeEventListener('requested', this.handleRequested);
84
+ ['complete', 'error', 'cancelled'].forEach(e => this.watchedForm?.removeEventListener(e, this.handleDone));
85
+ this.watchedForm = null;
86
+ super.disconnectedCallback();
87
+ }
88
+
89
+ // Only the original submit is blocked; the console re-dispatches it as a composed event
90
+ private handleFormSubmit = (e: Event) => {
91
+ if (this.submitting && !e.composed) {
92
+ e.preventDefault();
93
+ e.stopImmediatePropagation();
94
+ }
95
+ };
96
+
97
+ // Set once the console sends the request, so a submit that validation stops doesn't stick
98
+ private handleRequested = () => {
99
+ this.submitting = true;
100
+ };
101
+
102
+ private handleDone = () => {
103
+ this.submitting = false;
104
+ };
105
+
61
106
  // The buttons live in this component's shadow root, so zn-button's own
62
107
  // submit/reset handling can't find the form — resolve it from the host.
63
108
  private getForm(): HTMLFormElement | null {
@@ -92,7 +137,8 @@ export default class ZnFormActions extends ZincElement {
92
137
  <zn-button part="reset-button" panel-bg icon="${this.resetIcon}" @click="${this.handleReset}">
93
138
  ${this.resetText}
94
139
  </zn-button>` : ''}
95
- <zn-button part="submit-button" color="primary" icon="${this.submitIcon}" @click="${this.handleSubmit}">
140
+ <zn-button part="submit-button" color="primary" icon="${this.submitIcon}" @click="${this.handleSubmit}"
141
+ ?loading=${this.submitting} ?disabled=${this.submitting} loading-text="${this.submitText}">
96
142
  ${this.submitText}
97
143
  </zn-button>`;
98
144
  }
@@ -5,7 +5,7 @@
5
5
  display: flex;
6
6
  justify-content: flex-end;
7
7
  align-items: center;
8
- gap: var(--zn-spacing-small);
8
+ gap: var(--zn-form-actions-gap, var(--zn-spacing-small));
9
9
  }
10
10
 
11
11
  // The spacer, not an auto margin, is what splits the two sides: an auto margin on every left-hand action would open
@@ -43,6 +43,9 @@ export default class ZnFormGroup extends ZincElement {
43
43
  @property({ attribute: 'cols', type: Boolean }) forceCols = false;
44
44
  @property({ attribute: 'layout', type: String }) layout = "1,2";
45
45
 
46
+ /** Labels the group like one of its fields and keeps it tight to them, for a section inside a form or dialog. */
47
+ @property({ type: Boolean }) compact = false;
48
+
46
49
  @property({ attribute: 'pad', type: Boolean }) pad: boolean = false;
47
50
 
48
51
  /** The scroller the label is moved against by hand; null while native sticky is enough. */
@@ -326,7 +329,10 @@ export default class ZnFormGroup extends ZincElement {
326
329
  'form-control--has-label': hasLabel,
327
330
  'form-control--has-label-tooltip': hasLabelTooltip,
328
331
  'form-control--has-help-text': hasHelpText,
329
- 'form-control--pad': this.pad
332
+ 'form-control--pad': this.pad,
333
+ 'form-control--compact': this.compact,
334
+ 'form-control--stacked': this.layout === '1',
335
+ 'form-control--full': !hasLabel && !hasHelpText
330
336
  })}"
331
337
  aria-labelledby="label"
332
338
  aria-describedby="help-text">
@@ -345,10 +351,11 @@ export default class ZnFormGroup extends ZincElement {
345
351
  ${hasLabelTooltip
346
352
  ? html`
347
353
  <zn-tooltip class="form-control--label-tooltip">
348
- <div slot="content">
354
+ <div slot="content" id="label-tooltip">
349
355
  <slot name="label-tooltip">${this.labelTooltip}</slot>
350
356
  </div>
351
- <zn-icon src="info"></zn-icon>
357
+ <zn-icon src="info" tabindex="0" role="img" aria-label="More information"
358
+ aria-describedby="label-tooltip"></zn-icon>
352
359
  </zn-tooltip>`
353
360
  : ''}
354
361
  </label>` : html``}