@kubex/zinc 1.1.185 → 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 (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 +9998 -2345
  6. package/dist/vscode.html-custom-data.json +1022 -67
  7. package/dist/web-types.json +2129 -177
  8. package/dist/zn.d.ts +2293 -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 +133 -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 +625 -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
@@ -0,0 +1,160 @@
1
+ import {classMap} from 'lit/directives/class-map.js';
2
+ import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
3
+ import {FormControlController} from '../../internal/form';
4
+ import {HasSlotController} from '../../internal/slot';
5
+ import {property, state} from 'lit/decorators.js';
6
+ import ZincElement, {type ZincFormControl} from '../../internal/zinc-element';
7
+
8
+ import formControlStyles from '../../form-control.scss';
9
+ import styles from './key-value.scss';
10
+
11
+ interface KeyValueRow {
12
+ key: string;
13
+ value: string;
14
+ }
15
+
16
+ /**
17
+ * @summary Edits a list of name and value pairs, submitted as a JSON object.
18
+ * @documentation https://zinc.style/components/key-value
19
+ * @status experimental
20
+ * @since 1.0
21
+ *
22
+ * @dependency zn-input
23
+ * @dependency zn-button
24
+ *
25
+ * @event zn-change - Emitted when a pair is added, removed or edited.
26
+ *
27
+ * @slot label - The label. Alternatively, use the `label` attribute.
28
+ * @slot help-text - Text under the pairs. Alternatively, use the `help-text` attribute.
29
+ *
30
+ * @csspart base - The component's base wrapper.
31
+ * @csspart row - Each name and value pair.
32
+ */
33
+ export default class ZnKeyValue extends ZincElement implements ZincFormControl {
34
+ static styles: CSSResultGroup = [unsafeCSS(formControlStyles), unsafeCSS(styles)];
35
+
36
+ private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
37
+
38
+ private readonly formControlController = new FormControlController(this, {
39
+ value: (control: ZnKeyValue) => control.value,
40
+ });
41
+
42
+ @property() name = '';
43
+
44
+ /** The pairs as a JSON object, such as `{"1": "{{recipient.firstName}}"}`. */
45
+ @property() value = '{}';
46
+
47
+ @property() label = '';
48
+
49
+ @property({attribute: 'help-text'}) helpText = '';
50
+
51
+ @property({attribute: 'key-label'}) keyLabel = 'Name';
52
+
53
+ @property({attribute: 'value-label'}) valueLabel = 'Value';
54
+
55
+ @property({attribute: 'add-text'}) addText = 'Add';
56
+
57
+ /** Passed to each value input, so its slash menu can insert merge fields. */
58
+ @property({attribute: 'slash-items'}) slashItems = '';
59
+
60
+ @property({reflect: true}) form = '';
61
+
62
+ @property({type: Boolean, reflect: true}) disabled = false;
63
+
64
+ @state() private _rows: KeyValueRow[] = [];
65
+
66
+ get validity(): ValidityState {
67
+ return {valid: true} as ValidityState;
68
+ }
69
+
70
+ get validationMessage(): string {
71
+ return '';
72
+ }
73
+
74
+ checkValidity(): boolean {
75
+ return true;
76
+ }
77
+
78
+ getForm(): HTMLFormElement | null {
79
+ return this.formControlController.getForm();
80
+ }
81
+
82
+ reportValidity(): boolean {
83
+ return true;
84
+ }
85
+
86
+ setCustomValidity(): void {
87
+ }
88
+
89
+ protected willUpdate(changed: PropertyValues) {
90
+ if (changed.has('value') && this.value !== this._serialise()) {
91
+ this._rows = this._parse(this.value);
92
+ }
93
+ }
94
+
95
+ private _parse(value: string): KeyValueRow[] {
96
+ try {
97
+ const parsed: unknown = JSON.parse(value || '{}');
98
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
99
+ return Object.entries(parsed as Record<string, unknown>).map(([key, v]) => ({key, value: String(v ?? '')}));
100
+ }
101
+ } catch {
102
+ // An unreadable value starts empty rather than breaking the form
103
+ }
104
+ return [];
105
+ }
106
+
107
+ private _serialise(): string {
108
+ const pairs: Record<string, string> = {};
109
+ this._rows.filter(row => row.key.trim()).forEach(row => pairs[row.key.trim()] = row.value);
110
+ return JSON.stringify(pairs);
111
+ }
112
+
113
+ private _update(rows: KeyValueRow[]) {
114
+ this._rows = rows;
115
+ this.value = this._serialise();
116
+ this.emit('zn-change');
117
+ }
118
+
119
+ private _edit(index: number, field: keyof KeyValueRow, e: Event) {
120
+ const rows = [...this._rows];
121
+ rows[index] = {...rows[index], [field]: (e.target as HTMLInputElement).value};
122
+ this._update(rows);
123
+ }
124
+
125
+ render() {
126
+ const hasLabel = this.label || this.hasSlotController.test('label');
127
+ const hasHelpText = this.helpText || this.hasSlotController.test('help-text');
128
+
129
+ return html`
130
+ <div part="base" class=${classMap({
131
+ 'form-control': true,
132
+ 'form-control--medium': true,
133
+ 'form-control--has-label': !!hasLabel,
134
+ 'form-control--has-help-text': !!hasHelpText,
135
+ })}>
136
+ <label class="form-control__label" aria-hidden=${hasLabel ? 'false' : 'true'}>
137
+ <slot name="label">${this.label}</slot>
138
+ </label>
139
+
140
+ <div class="key-value__rows">
141
+ ${this._rows.map((row, index) => html`
142
+ <div part="row" class="key-value__row">
143
+ <zn-input class="key-value__key" placeholder=${this.keyLabel} .value=${row.key}
144
+ ?disabled=${this.disabled} @zn-input=${(e: Event) => this._edit(index, 'key', e)}></zn-input>
145
+ <zn-input class="key-value__value" placeholder=${this.valueLabel} .value=${row.value}
146
+ slash-items=${this.slashItems} ?disabled=${this.disabled}
147
+ @zn-input=${(e: Event) => this._edit(index, 'value', e)}></zn-input>
148
+ <zn-button icon="x@lu" color="transparent" size="small" title="Remove" icon-button="small" plain ?disabled=${this.disabled}
149
+ @click=${() => this._update(this._rows.filter((_, i) => i !== index))}></zn-button>
150
+ </div>`)}
151
+ <zn-button class="key-value__add" icon="plus@lu" color="secondary" size="small" ?disabled=${this.disabled}
152
+ @click=${() => this._update([...this._rows, {key: '', value: ''}])}>${this.addText}</zn-button>
153
+ </div>
154
+
155
+ <div class="form-control__help-text" aria-hidden=${hasHelpText ? 'false' : 'true'}>
156
+ <slot name="help-text">${this.helpText}</slot>
157
+ </div>
158
+ </div>`;
159
+ }
160
+ }
@@ -0,0 +1,20 @@
1
+ @use "../../wc";
2
+
3
+ :host {
4
+ display: block;
5
+ }
6
+
7
+ .key-value__rows {
8
+ display: flex;
9
+ flex-direction: column;
10
+ align-items: flex-start;
11
+ gap: var(--zn-spacing-x-small);
12
+ }
13
+
14
+ .key-value__row {
15
+ display: grid;
16
+ grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) auto;
17
+ align-items: center;
18
+ gap: var(--zn-spacing-x-small);
19
+ width: 100%;
20
+ }
@@ -0,0 +1,22 @@
1
+ import '../../../dist/zn.min.js';
2
+ import {expect, fixture, html} from '@open-wc/testing';
3
+ import type ZnKeyValue from './key-value.component';
4
+
5
+ describe('<zn-key-value>', () => {
6
+ it('should show a row per pair in its value', async () => {
7
+ const el = await fixture<ZnKeyValue>(html`<zn-key-value value='{"1":"{{recipient.firstName}}","2":"x"}'></zn-key-value>`);
8
+
9
+ expect(el.shadowRoot!.querySelectorAll('.key-value__row').length).to.equal(2);
10
+ });
11
+
12
+ it('should submit the pairs as a JSON object, leaving out unnamed rows', async () => {
13
+ const form = await fixture<HTMLFormElement>(html`
14
+ <form><zn-key-value name="pairs" value='{"1":"a"}'></zn-key-value></form>`);
15
+ const el = form.querySelector<ZnKeyValue>('zn-key-value')!;
16
+
17
+ el.shadowRoot!.querySelector<HTMLElement>('.key-value__add')!.click();
18
+ await el.updateComplete;
19
+
20
+ expect(new FormData(form).get('pairs')).to.equal('{"1":"a"}');
21
+ });
22
+ });
@@ -214,6 +214,13 @@ export default class ZnMarkdownEditor extends ZnPanel implements ZincFormControl
214
214
  this.viewMode = mode;
215
215
  };
216
216
 
217
+ private handleKeyDown = (e: KeyboardEvent) => {
218
+ if (e.key === 'Escape' && this.expanded) {
219
+ e.stopPropagation();
220
+ this.expanded = false;
221
+ }
222
+ };
223
+
217
224
  private handleExpandToggle = () => {
218
225
  this.expanded = !this.expanded;
219
226
  };
@@ -290,7 +297,7 @@ export default class ZnMarkdownEditor extends ZnPanel implements ZincFormControl
290
297
  'markdown-editor--split': this.viewMode === 'split',
291
298
  'markdown-editor--preview-only': this.viewMode === 'preview',
292
299
  'markdown-editor--expanded': this.expanded,
293
- })}>
300
+ })} @keydown=${this.handleKeyDown}>
294
301
  ${hasLabel ? html`
295
302
  <label class="markdown-editor__label">
296
303
  <slot name="label">${this.label}</slot>
@@ -6,6 +6,7 @@ import ZnConfirm from "../confirm";
6
6
  import ZnDropdown from "../dropdown";
7
7
  import ZnIcon from "../icon";
8
8
  import ZnMenuItem from "../menu-item";
9
+ import ZnMenuLabel from "../menu-label";
9
10
  import ZnTooltip from "../tooltip";
10
11
 
11
12
  import { classMap } from "lit/directives/class-map.js";
@@ -13,11 +14,16 @@ import styles from './menu.scss';
13
14
 
14
15
  interface NavItem {
15
16
  title: string;
17
+ /** `label` renders a section heading and `divider` a separator; anything else is an item. */
16
18
  type: string;
17
19
  path: string;
18
20
  target: string;
19
21
  icon: string;
20
22
  style: string;
23
+ /** A semantic colour for the item, such as `error` for a destructive action. */
24
+ color?: string;
25
+ /** A keyboard shortcut shown at the end of the item. */
26
+ shortcut?: string;
21
27
  confirm: {
22
28
  type: string;
23
29
  title: string;
@@ -56,6 +62,7 @@ export default class ZnMenu extends ZincElement {
56
62
  'zn-dropdown': ZnDropdown,
57
63
  'zn-icon': ZnIcon,
58
64
  'zn-menu-item': ZnMenuItem,
65
+ 'zn-menu-label': ZnMenuLabel,
59
66
  'zn-tooltip': ZnTooltip,
60
67
  };
61
68
 
@@ -118,6 +125,12 @@ export default class ZnMenu extends ZincElement {
118
125
  if (item === null || item === undefined) {
119
126
  return html``; // Skip null or undefined items
120
127
  }
128
+ if (item.type === 'label') {
129
+ return html`<zn-menu-label>${item.title}</zn-menu-label>`;
130
+ }
131
+ if (item.type === 'divider') {
132
+ return html`<hr>`;
133
+ }
121
134
  if (item?.confirm) {
122
135
  return html`
123
136
  <zn-confirm trigger="${ifDefined(item.confirm?.trigger)}"
@@ -129,6 +142,8 @@ export default class ZnMenu extends ZincElement {
129
142
  @keydown=${this.handleKeyDown}
130
143
  id="${item.confirm?.trigger}"
131
144
  variant="${this.itemVariant}"
145
+ color="${ifDefined(item.color)}"
146
+ shortcut="${ifDefined(item.shortcut)}"
132
147
  confirm>
133
148
  ${(item.icon) ? html`
134
149
  <zn-icon src="${item.icon}" size="20" slot="prefix"></zn-icon>` : html``}
@@ -140,6 +155,8 @@ export default class ZnMenu extends ZincElement {
140
155
  <zn-menu-item value="${item.title}"
141
156
  href="${item.path}"
142
157
  variant="${this.itemVariant}"
158
+ color="${ifDefined(item.color)}"
159
+ shortcut="${ifDefined(item.shortcut)}"
143
160
  data-target="${ifDefined(item.target)}">
144
161
  ${(item.icon) ? html`
145
162
  <zn-icon src="${item.icon}" size="20" slot="prefix"></zn-icon>` : html``}
@@ -197,8 +214,10 @@ export default class ZnMenu extends ZincElement {
197
214
  event.preventDefault();
198
215
  event.stopPropagation();
199
216
 
200
- // Simulate a click to support @click handlers on menu items that also work with the keyboard
201
- item?.click();
217
+ // Simulate a click to support @click handlers on menu items that also work with the keyboard. A link item's
218
+ // <a> is in its shadow root, and only a click on the anchor itself follows it.
219
+ const anchor = item?.shadowRoot?.querySelector<HTMLElement>('a#anchor');
220
+ (anchor ?? item)?.click();
202
221
  }
203
222
 
204
223
  // Move the selection when pressing down or up
@@ -3,9 +3,9 @@
3
3
  .menu {
4
4
  display: block;
5
5
  position: relative;
6
- padding: 1px;
6
+ padding: var(--zn-menu-padding, 1px);
7
7
  border: 1px solid rgb(var(--zn-border-color)) !important;
8
- border-radius: var(--zn-border-radius);
8
+ border-radius: var(--zn-menu-border-radius, var(--zn-border-radius));
9
9
  width: fit-content;
10
10
  min-width: var(--zn-menu-min-width, var(--zn-size-spanel));
11
11
  // Unconstrained by default — a consumer that needs the menu to scroll inside a bounded
@@ -13,31 +13,16 @@
13
13
  max-height: var(--zn-menu-max-height, none);
14
14
  overflow: auto;
15
15
  overscroll-behavior: none;
16
- box-shadow: 0 4px 8px -4px rgba(33, 33, 33, 0.1);
16
+ box-shadow: var(--zn-menu-shadow, 0 4px 8px -4px rgba(33, 33, 33, 0.1));
17
17
  // Same surface as the shell-header menus below, so every dropdown in the
18
18
  // product reads as one material rather than splitting into --zn-panel menus
19
19
  // and --zn-color-navigation ones depending on where it was mounted.
20
20
  background-color: rgb(var(--zn-color-navigation, 255, 255, 255));
21
21
  }
22
22
 
23
- // A submenu's border sits on its parent menu's edge border, so the two share one line.
24
- :host([slot="submenu"]) .menu {
25
- border-start-start-radius: 0;
26
- border-end-start-radius: 0;
27
- }
28
-
29
- // Set by the submenu controller while this submenu extends past its parent's bottom edge.
30
- :host([slot="submenu"][data-extends-below]) .menu {
31
- border-end-start-radius: var(--zn-border-radius);
32
- }
33
-
34
- // Set by the submenu controller on the parent menu while a submenu covers that corner.
35
- :host([data-submenu-top]) .menu {
36
- border-start-end-radius: 0;
37
- }
38
-
39
- :host([data-submenu-bottom]) .menu {
40
- border-end-end-radius: 0;
23
+ // A submenu sits just off its parent menu, so a page-level shadow would mostly land on the parent
24
+ :host([slot="submenu"]) .menu:not(.menu--shell) {
25
+ box-shadow: var(--zn-menu-submenu-shadow, 0 4px 12px rgba(13, 23, 56, 0.10), 0 1px 3px rgba(13, 23, 56, 0.06));
41
26
  }
42
27
 
43
28
  // A capped menu has to look scrollable, so the bar is always drawn rather than
@@ -73,7 +58,7 @@
73
58
  border: 1px solid rgb(var(--zn-border-color));
74
59
  border-radius: var(--zn-border-radius);
75
60
  min-width: var(--zn-menu-min-width, 245px);
76
- max-width: min(320px, calc(100vw - 16px));
61
+ max-width: var(--zn-menu-max-width, min(320px, calc(100vw - 16px)));
77
62
  max-height: var(--zn-menu-max-height, none);
78
63
  overflow-x: hidden;
79
64
  overflow-y: auto;
@@ -84,6 +69,41 @@
84
69
  }
85
70
 
86
71
 
72
+ // Width, style and colour are set apart so an unresolved colour token can't drop the line.
73
+ .menu > hr {
74
+ height: 0;
75
+ margin: var(--zn-menu-divider-margin, 4px 0);
76
+ border: 0;
77
+ border-top-width: 1px;
78
+ border-top-style: solid;
79
+ border-top-color: var(--zn-menu-divider-color, rgb(var(--zn-border-color)));
80
+ }
81
+
82
+ // !important because the page's own hr styles otherwise outrank ::slotted()
83
+ ::slotted(hr) {
84
+ height: 0 !important;
85
+ margin: var(--zn-menu-divider-margin, 4px 0) !important;
86
+ border: 0 !important;
87
+ border-top-width: 1px !important;
88
+ border-top-style: solid !important;
89
+ border-top-color: var(--zn-menu-divider-color, rgb(var(--zn-border-color))) !important;
90
+ }
91
+
92
+ // A zn-confirm only contributes its dialog, so it must not claim a slot (or a gap) in the item stack.
93
+ zn-confirm, ::slotted(zn-confirm) {
94
+ display: contents;
95
+ }
96
+
87
97
  ::slotted(zn-menu-item + zn-menu-item) {
88
- border-top: 1px solid rgb(var(--zn-border-color));
98
+ border-top: var(--zn-menu-item-divider, 1px solid rgb(var(--zn-border-color)));
99
+ }
100
+
101
+ // A group divider, shown even when the theme drops the line between items
102
+ ::slotted(hr) {
103
+ flex: 0 0 auto;
104
+ margin: var(--zn-spacing-3x-small, 2px) 0;
105
+ border: none;
106
+ border-top-style: solid;
107
+ border-top-width: 1px;
108
+ border-top-color: rgb(var(--zn-border-color));
89
109
  }
@@ -1,10 +1,43 @@
1
1
  import '../../../dist/zn.min.js';
2
2
  import { expect, fixture, html } from '@open-wc/testing';
3
+ import type ZnMenu from './menu.component';
3
4
 
4
5
  describe('<zn-menu>', () => {
6
+ it('should draw a slotted hr as a divider', async () => {
7
+ const el = await fixture(html`<zn-menu><zn-menu-item>Edit</zn-menu-item><hr><zn-menu-item>Delete</zn-menu-item></zn-menu>`);
8
+ const style = getComputedStyle(el.querySelector('hr')!);
9
+ expect(style.borderTopStyle).to.equal('solid');
10
+ expect(style.borderBottomStyle).to.equal('none');
11
+ });
12
+
5
13
  it('should render a component', async () => {
6
14
  const el = await fixture(html` <zn-menu></zn-menu> `);
7
15
 
8
16
  expect(el).to.exist;
9
17
  });
18
+
19
+ it('should skip labels in keyboard navigation', async () => {
20
+ const el = await fixture<ZnMenu>(html`
21
+ <zn-menu><zn-menu-label>Customer</zn-menu-label><zn-menu-item>Send email</zn-menu-item></zn-menu>
22
+ `);
23
+ expect(el.getAllItems().map(item => item.textContent!.trim())).to.deep.equal(['Send email']);
24
+ });
25
+
26
+ it('should render labels, dividers, colours and shortcuts from actions', async () => {
27
+ const el = await fixture<ZnMenu>(html`<zn-menu></zn-menu>`);
28
+ el.actions = [
29
+ {type: 'label', title: 'Customer'},
30
+ {title: 'Send email', shortcut: 'E', path: '#'},
31
+ {type: 'divider'},
32
+ {title: 'Delete', color: 'error', path: '#'},
33
+ ] as unknown as ZnMenu['actions'];
34
+ await el.updateComplete;
35
+
36
+ const root = el.shadowRoot!;
37
+ expect(root.querySelector('zn-menu-label')!.textContent).to.equal('Customer');
38
+ expect(root.querySelector('.menu > hr')).to.exist;
39
+ const items = [...root.querySelectorAll('zn-menu-item')];
40
+ expect(items[0].getAttribute('shortcut')).to.equal('E');
41
+ expect(items[1].getAttribute('color')).to.equal('error');
42
+ });
10
43
  });
@@ -28,6 +28,7 @@ import styles from './menu-item.scss';
28
28
  * @slot example - An example slot.
29
29
  *
30
30
  * @csspart base - The component's base wrapper.
31
+ * @csspart shortcut - The key cap showing the item's `shortcut`.
31
32
  *
32
33
  * @cssproperty --example - An example CSS custom property.
33
34
  */
@@ -100,6 +101,9 @@ export default class ZnMenuItem extends ZincElement {
100
101
  /** Marks the menu item as currently active/selected. */
101
102
  @property({type: Boolean, reflect: true}) active = false;
102
103
 
104
+ /** A keyboard shortcut shown as a key cap at the end of the item, such as `E` or `⌘K`. */
105
+ @property() shortcut: string;
106
+
103
107
  private readonly localize = new LocalizeController(this);
104
108
  private readonly hasSlotController = new HasSlotController(this, 'submenu');
105
109
  private submenuController: SubmenuController = new SubmenuController(this, this.hasSlotController, this.localize);
@@ -142,9 +146,13 @@ export default class ZnMenuItem extends ZincElement {
142
146
  if (!this.isSubmenu()) {
143
147
  const composedPath = event.composedPath();
144
148
  const closestMenu: Element | null = composedPath.find((el: Element) => el?.getAttribute?.('role') === 'menu') as Element;
145
- if (this.confirm) return;
146
-
147
149
  const dropdown = closestMenu?.closest('zn-dropdown') as ZnDropdown | null;
150
+ if (this.confirm) {
151
+ // The confirm dialog lives inside the dropdown panel, so the dropdown can only close once the dialog does
152
+ dropdown?.addEventListener('zn-close', () => dropdown.hide(), {once: true});
153
+ return;
154
+ }
155
+
148
156
  if (!dropdown?.stayOpenOnSelect && !this.keepOpen) {
149
157
  dropdown?.hide();
150
158
  }
@@ -215,6 +223,7 @@ export default class ZnMenuItem extends ZincElement {
215
223
  part="base"
216
224
  data-path=${ifDefined(this.dataPath)}
217
225
  href=${ifDefined(this.href)}
226
+ tabindex=${ifDefined(isLink ? '-1' : undefined)}
218
227
  target=${ifDefined(isLink ? this.target : undefined)}
219
228
  data-target=${ifDefined(isLink ? this.dataTarget : undefined)}
220
229
  rel=${ifDefined(isLink ? this.rel : undefined)}
@@ -254,6 +263,8 @@ export default class ZnMenuItem extends ZincElement {
254
263
 
255
264
  <slot name="suffix" part="suffix" class="menu-item__suffix"></slot>
256
265
 
266
+ ${this.shortcut ? html`<kbd part="shortcut" class="menu-item__shortcut">${this.shortcut}</kbd>` : ''}
267
+
257
268
  ${this.checkedPosition === 'right' ? html`
258
269
  <span part="checked-icon" class="menu-item__check">
259
270
  <zn-icon src="check@lu" size="18" aria-hidden="true"></zn-icon>
@@ -1,8 +1,6 @@
1
1
  @use "../../wc";
2
2
 
3
3
  :host {
4
- // Overlaps the parent menu's padding so the submenu's border lands on the parent's.
5
- --submenu-offset: 1px;
6
4
  // Baseline — standalone items (e.g. app/sidebar navigation): primary hover
7
5
  --color: var(--zn-text-menu-link-color);
8
6
  --hover-color: rgb(var(--zn-primary));
@@ -15,7 +13,7 @@
15
13
  // its colour), no separators between rows. Set by the parent zn-menu.
16
14
  :host([variant="dropdown"]) {
17
15
  --hover-color: var(--color);
18
- --hover-bg: rgba(0, 0, 0, 0.06);
16
+ --hover-bg: var(--zn-menu-item-hover-background, rgba(0, 0, 0, 0.06));
19
17
  }
20
18
 
21
19
  // Shell — app-shell header dropdown look: dark text, primary hover. Set by the
@@ -51,8 +49,10 @@
51
49
 
52
50
  :host([variant="dropdown"]) .menu-item {
53
51
  align-items: center;
54
- padding: var(--zn-spacing-small) var(--zn-spacing-large);
52
+ min-height: var(--zn-menu-item-min-height, auto);
53
+ padding: var(--zn-menu-item-padding, var(--zn-spacing-small) var(--zn-spacing-large));
55
54
  border-bottom-width: 0;
55
+ border-radius: var(--zn-menu-item-border-radius, 0);
56
56
  }
57
57
 
58
58
  :host([variant="shell"]) .menu-item {
@@ -69,6 +69,11 @@
69
69
  color: inherit;
70
70
  }
71
71
 
72
+ // A zn-confirm only contributes its dialog, so it must not take part in the label's layout.
73
+ .menu-item__label::slotted(zn-confirm) {
74
+ display: contents;
75
+ }
76
+
72
77
  .menu-item.menu-item--disabled {
73
78
  outline: none;
74
79
  opacity: 0.5;
@@ -167,7 +172,17 @@
167
172
 
168
173
  .menu-item .menu-item__prefix::slotted(*) {
169
174
  --icon-size: var(--zn-font-size-small);
170
- margin-inline-end: var(--zn-spacing-small);
175
+ margin-inline-end: var(--zn-menu-item-icon-gap, var(--zn-spacing-small));
176
+ color: var(--zn-menu-item-icon-color, inherit);
177
+ }
178
+
179
+ // A status item's icon takes the item's colour
180
+ .menu-item--primary .menu-item__prefix::slotted(*),
181
+ .menu-item--info .menu-item__prefix::slotted(*),
182
+ .menu-item--error .menu-item__prefix::slotted(*),
183
+ .menu-item--warning .menu-item__prefix::slotted(*),
184
+ .menu-item--success .menu-item__prefix::slotted(*) {
185
+ color: inherit;
171
186
  }
172
187
 
173
188
  // Shell items space the icon via the row gap instead
@@ -185,6 +200,29 @@
185
200
  margin-inline-start: var(--zn-spacing-x-small);
186
201
  }
187
202
 
203
+ // A keyboard shortcut, drawn as a key cap at the end of the row
204
+ .menu-item__shortcut {
205
+ display: inline-flex;
206
+ align-items: center;
207
+ justify-content: center;
208
+ box-sizing: border-box;
209
+ flex: 0 0 auto;
210
+ min-width: 20px;
211
+ height: 20px;
212
+ margin-inline-start: var(--zn-spacing-medium);
213
+ padding: 0 5px;
214
+ border: 1px solid rgb(var(--zn-border-color));
215
+ border-bottom-width: 2px;
216
+ border-radius: 4px;
217
+ background: rgb(var(--zn-panel));
218
+ font-family: inherit;
219
+ font-style: normal;
220
+ font-size: 11px;
221
+ font-weight: var(--zn-font-weight-medium);
222
+ line-height: 1;
223
+ color: var(--zn-color-neutral-500);
224
+ }
225
+
188
226
  /* Safe triangle */
189
227
  .menu-item--submenu-expanded::after {
190
228
  content: '';
@@ -256,11 +294,6 @@
256
294
 
257
295
  zn-popup::part(popup) {
258
296
  z-index: 100;
259
- margin-left: var(--submenu-offset);
260
- }
261
-
262
- .menu-item--rtl zn-popup::part(popup) {
263
- margin-left: calc(-1 * var(--submenu-offset));
264
297
  }
265
298
 
266
299
  @media (forced-colors: active) {
@@ -282,32 +315,32 @@ zn-popup::part(popup) {
282
315
  // light accent such as velocity's neon lime).
283
316
  .menu-item--primary {
284
317
  --color: rgba(var(--zn-primary), 0.9);
285
- --hover-color: rgb(var(--zn-color-on-primary, 255, 255, 255));
286
- --hover-bg: rgba(var(--zn-primary), 0.9);
318
+ --hover-color: var(--zn-menu-item-primary-hover-color, rgb(var(--zn-color-on-primary, 255, 255, 255)));
319
+ --hover-bg: var(--zn-menu-item-primary-hover-background, rgba(var(--zn-primary), 0.9));
287
320
  }
288
321
 
289
322
  .menu-item--error {
290
- --color: rgb(var(--zn-color-error));
291
- --hover-color: rgb(var(--zn-color-on-error, 255, 255, 255));
292
- --hover-bg: rgba(var(--zn-color-error), 0.9);
323
+ --color: var(--zn-color-danger-fg);
324
+ --hover-color: var(--zn-menu-item-danger-hover-color, rgb(var(--zn-color-on-error, 255, 255, 255)));
325
+ --hover-bg: var(--zn-menu-item-danger-hover-background, rgba(var(--zn-color-error), 0.9));
293
326
  }
294
327
 
295
328
  .menu-item--info {
296
329
  --color: rgb(var(--zn-color-info));
297
- --hover-color: rgb(var(--zn-color-on-info, 255, 255, 255));
298
- --hover-bg: rgba(var(--zn-color-info), 0.9);
330
+ --hover-color: var(--zn-menu-item-info-hover-color, rgb(var(--zn-color-on-info, 255, 255, 255)));
331
+ --hover-bg: var(--zn-menu-item-info-hover-background, rgba(var(--zn-color-info), 0.9));
299
332
  }
300
333
 
301
334
  .menu-item--warning {
302
- --color: rgb(var(--zn-color-warning));
303
- --hover-color: rgb(var(--zn-color-on-warning, 255, 255, 255));
304
- --hover-bg: rgba(var(--zn-color-warning), 0.9);
335
+ --color: var(--zn-color-warning-fg, rgb(var(--zn-color-warning)));
336
+ --hover-color: var(--zn-menu-item-warning-hover-color, rgb(var(--zn-color-on-warning, 255, 255, 255)));
337
+ --hover-bg: var(--zn-menu-item-warning-hover-background, rgba(var(--zn-color-warning), 0.9));
305
338
  }
306
339
 
307
340
  .menu-item--success {
308
- --color: rgb(var(--zn-color-success));
309
- --hover-color: rgb(var(--zn-color-on-success, 255, 255, 255));
310
- --hover-bg: rgba(var(--zn-color-success), 0.9);
341
+ --color: var(--zn-color-success-fg, rgb(var(--zn-color-success)));
342
+ --hover-color: var(--zn-menu-item-success-hover-color, rgb(var(--zn-color-on-success, 255, 255, 255)));
343
+ --hover-bg: var(--zn-menu-item-success-hover-background, rgba(var(--zn-color-success), 0.9));
311
344
  }
312
345
 
313
346
  .menu-item--secondary {