@kubex/zinc 1.1.185 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/.browserslistrc +2 -0
  2. package/.github/workflows/js_build_and_deploy.yaml +54 -1
  3. package/.github/workflows/test.yaml +27 -0
  4. package/dist/chunks/zn.UWJLIXDT.js +1 -0
  5. package/dist/custom-elements.json +10319 -2666
  6. package/dist/vscode.html-custom-data.json +1012 -57
  7. package/dist/web-types.json +2097 -145
  8. package/dist/zn.d.ts +2294 -416
  9. package/dist/zn.min.css +1 -1
  10. package/dist/zn.min.js +2310 -1637
  11. package/docs/_includes/default.njk +4 -1
  12. package/docs/assets/scripts/docs.js +22 -6
  13. package/docs/assets/styles/docs.css +9 -2
  14. package/docs/data/report-table-day-compare.json +380 -0
  15. package/docs/data/report-table.json +314 -0
  16. package/docs/pages/components/avatar-group.md +57 -0
  17. package/docs/pages/components/badge.md +67 -0
  18. package/docs/pages/components/button.md +41 -1
  19. package/docs/pages/components/chat-message.md +16 -0
  20. package/docs/pages/components/checkbox.md +1 -1
  21. package/docs/pages/components/chip.md +25 -13
  22. package/docs/pages/components/color-picker.md +37 -0
  23. package/docs/pages/components/copy-button.md +2 -2
  24. package/docs/pages/components/data-table-filter.md +33 -50
  25. package/docs/pages/components/data-table-search.md +4 -4
  26. package/docs/pages/components/data-table.md +27 -3
  27. package/docs/pages/components/date-range.md +116 -0
  28. package/docs/pages/components/dialog.md +29 -37
  29. package/docs/pages/components/diff-row.md +28 -0
  30. package/docs/pages/components/diff.md +46 -0
  31. package/docs/pages/components/editor.md +55 -0
  32. package/docs/pages/components/icon.md +46 -1
  33. package/docs/pages/components/input.md +30 -11
  34. package/docs/pages/components/item.md +35 -0
  35. package/docs/pages/components/key-value.md +25 -0
  36. package/docs/pages/components/menu.md +74 -7
  37. package/docs/pages/components/nav-item.md +15 -0
  38. package/docs/pages/components/nav-list.md +132 -0
  39. package/docs/pages/components/navbar.md +100 -1
  40. package/docs/pages/components/page.md +242 -74
  41. package/docs/pages/components/priority-list.md +43 -5
  42. package/docs/pages/components/query-builder.md +23 -0
  43. package/docs/pages/components/radio-group.md +33 -1
  44. package/docs/pages/components/radio.md +1 -1
  45. package/docs/pages/components/report-table.md +202 -0
  46. package/docs/pages/components/stat.md +30 -0
  47. package/docs/pages/components/tile.md +11 -0
  48. package/docs/pages/components/tooltip.md +1 -1
  49. package/docs/pages/components/translation-grid.md +86 -0
  50. package/docs/pages/components/vertical-stepper.md +108 -64
  51. package/package.json +2 -1
  52. package/scss/_global-spacing.scss +2 -2
  53. package/scss/_root.scss +11 -171
  54. package/scss/boot.scss +14 -0
  55. package/scss/mixins/_helper-mixins.scss +24 -2
  56. package/scss/mixins/_motion-mixins.scss +126 -0
  57. package/scss/mixins/_spacing-mixins.scss +4 -3
  58. package/scss/mixins/_tab-indicator-mixins.scss +12 -0
  59. package/scss/mixins/_typography-mixins.scss +1 -1
  60. package/scss/mixins/index.scss +14 -0
  61. package/scss/shared/_base.scss +32 -1
  62. package/scss/shared/_button-mixin.scss +7 -5
  63. package/scss/shared/_table.scss +35 -4
  64. package/scss/shared/nav.scss +4 -0
  65. package/scss/shared/shimmer.scss +1 -1
  66. package/scss/table-controls.scss +206 -0
  67. package/scss/themes/_dark.scss +1 -3
  68. package/scss/themes/_index.scss +1 -1
  69. package/scss/themes/_light.scss +46 -6
  70. package/scss/themes/_modes.scss +11 -11
  71. package/scss/themes/_openbyte.scss +944 -0
  72. package/scss/themes/_typography.scss +4 -2
  73. package/scss/variables/index.scss +0 -9
  74. package/src/components/alert/alert.component.ts +14 -4
  75. package/src/components/alert/alert.scss +55 -69
  76. package/src/components/alert/alert.test.ts +8 -0
  77. package/src/components/animated-button/animated-button.component.ts +3 -2
  78. package/src/components/animated-button/animated-button.scss +6 -23
  79. package/src/components/animated-button/animated-button.test.ts +4 -3
  80. package/src/components/avatar-group/avatar-group.component.ts +84 -0
  81. package/src/components/avatar-group/avatar-group.scss +54 -0
  82. package/src/components/avatar-group/avatar-group.test.ts +59 -0
  83. package/src/components/avatar-group/index.ts +12 -0
  84. package/src/components/badge/badge.component.ts +49 -0
  85. package/src/components/badge/badge.scss +80 -0
  86. package/src/components/badge/badge.test.ts +34 -0
  87. package/src/components/badge/index.ts +12 -0
  88. package/src/components/bulk-actions/bulk-actions.component.ts +14 -2
  89. package/src/components/button/button.component.ts +228 -8
  90. package/src/components/button/button.scss +115 -32
  91. package/src/components/button/button.test.ts +122 -0
  92. package/src/components/button-menu/button-menu.component.ts +32 -11
  93. package/src/components/button-menu/button-menu.scss +5 -1
  94. package/src/components/channel-tile/channel-tile.component.ts +34 -27
  95. package/src/components/channel-tile/channel-tile.scss +76 -43
  96. package/src/components/chart/builders.test.ts +46 -0
  97. package/src/components/chart/builders.ts +27 -3
  98. package/src/components/chart/chart-live.test.ts +4 -6
  99. package/src/components/chart/chart.component.ts +125 -5
  100. package/src/components/chart/chart.scss +121 -0
  101. package/src/components/chart/chart.test.ts +81 -0
  102. package/src/components/chat-message/chat-message.component.ts +21 -1
  103. package/src/components/chat-message/chat-message.scss +13 -2
  104. package/src/components/chat-message/chat-message.test.ts +9 -0
  105. package/src/components/checkbox/checkbox.component.ts +61 -27
  106. package/src/components/checkbox/checkbox.scss +58 -25
  107. package/src/components/checkbox-group/checkbox-group.component.ts +7 -1
  108. package/src/components/chip/chip.component.ts +11 -1
  109. package/src/components/chip/chip.scss +91 -26
  110. package/src/components/chip/chip.test.ts +17 -0
  111. package/src/components/collapsible/collapsible.component.ts +17 -3
  112. package/src/components/collapsible/collapsible.scss +19 -10
  113. package/src/components/color-picker/color-picker.component.ts +277 -0
  114. package/src/components/color-picker/color-picker.scss +141 -0
  115. package/src/components/color-picker/color-picker.test.ts +57 -0
  116. package/src/components/color-picker/index.ts +12 -0
  117. package/src/components/cols/cols.component.ts +10 -0
  118. package/src/components/cols/cols.test.ts +17 -0
  119. package/src/components/confirm/confirm.component.ts +20 -6
  120. package/src/components/confirm/confirm.test.ts +52 -0
  121. package/src/components/content-block/content-block.component.ts +38 -9
  122. package/src/components/content-block/content-block.scss +11 -5
  123. package/src/components/content-block/content-block.test.ts +26 -0
  124. package/src/components/copy-button/copy-button.component.ts +2 -2
  125. package/src/components/copy-button/copy-button.scss +17 -4
  126. package/src/components/data-select/data-select.component.ts +3 -1
  127. package/src/components/data-table/data-table.component.ts +362 -140
  128. package/src/components/data-table/data-table.scss +136 -60
  129. package/src/components/data-table/data-table.test.ts +356 -4
  130. package/src/components/data-table-filter/data-table-filter.component.ts +426 -406
  131. package/src/components/data-table-filter/data-table-filter.scss +176 -148
  132. package/src/components/data-table-filter/data-table-filter.test.ts +271 -273
  133. package/src/components/data-table-search/data-table-search.component.ts +4 -2
  134. package/src/components/data-table-search/data-table-search.scss +8 -1
  135. package/src/components/data-table-search/data-table-search.test.ts +29 -0
  136. package/src/components/date-range/date-range-math.test.ts +78 -0
  137. package/src/components/date-range/date-range-math.ts +105 -0
  138. package/src/components/date-range/date-range.component.ts +375 -0
  139. package/src/components/date-range/date-range.scss +272 -0
  140. package/src/components/date-range/date-range.test.ts +247 -0
  141. package/src/components/date-range/index.ts +12 -0
  142. package/src/components/datepicker/datepicker.component.ts +44 -7
  143. package/src/components/datepicker/datepicker.scss +9 -0
  144. package/src/components/datepicker/datepicker.test.ts +2 -2
  145. package/src/components/dialog/dialog.component.ts +38 -19
  146. package/src/components/dialog/dialog.scss +65 -36
  147. package/src/components/dialog/dialog.test.ts +14 -0
  148. package/src/components/diff/diff.component.ts +55 -0
  149. package/src/components/diff/diff.scss +46 -0
  150. package/src/components/diff/diff.test.ts +15 -0
  151. package/src/components/diff/index.ts +12 -0
  152. package/src/components/diff-row/diff-row.component.ts +75 -0
  153. package/src/components/diff-row/diff-row.scss +70 -0
  154. package/src/components/diff-row/diff-row.test.ts +37 -0
  155. package/src/components/diff-row/index.ts +12 -0
  156. package/src/components/dropdown/dropdown.component.ts +12 -10
  157. package/src/components/dropdown/dropdown.scss +2 -0
  158. package/src/components/editor/editor.component.ts +159 -11
  159. package/src/components/editor/editor.scss +250 -1
  160. package/src/components/editor/editor.test.ts +31 -3
  161. package/src/components/editor/modules/ai/panel/ai-panel.scss +0 -1
  162. package/src/components/editor/modules/attachment/attachment.ts +53 -31
  163. package/src/components/editor/modules/context-menu/context-menu.ts +17 -0
  164. package/src/components/editor/modules/dialog/dialog.component.ts +23 -1
  165. package/src/components/editor/modules/dialog/dialog.test.ts +28 -0
  166. package/src/components/editor/modules/emoji/emoji.ts +4 -5
  167. package/src/components/editor/modules/merge-field/merge-field.ts +83 -0
  168. package/src/components/editor/modules/toolbar/toolbar.component.ts +121 -68
  169. package/src/components/editor/modules/toolbar/toolbar.scss +38 -0
  170. package/src/components/editor/modules/toolbar/toolbar.ts +25 -1
  171. package/src/components/empty-state/empty-state.component.ts +6 -1
  172. package/src/components/empty-state/empty-state.scss +21 -4
  173. package/src/components/expanding-action/expanding-action.component.ts +22 -34
  174. package/src/components/expanding-action/expanding-action.scss +3 -45
  175. package/src/components/expanding-action/expanding-action.test.ts +2 -1
  176. package/src/components/file/file.component.ts +30 -2
  177. package/src/components/file/file.scss +6 -0
  178. package/src/components/flow-builder/flow-builder.component.ts +36 -3
  179. package/src/components/flow-builder/flow-builder.scss +0 -6
  180. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.component.ts +8 -0
  181. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.scss +10 -5
  182. package/src/components/flow-builder/modules/flow-node/flow-node.component.ts +17 -0
  183. package/src/components/flow-builder/modules/flow-node/flow-node.scss +10 -0
  184. package/src/components/form-actions/form-actions.component.ts +48 -2
  185. package/src/components/form-actions/form-actions.scss +1 -1
  186. package/src/components/form-group/form-group.component.ts +10 -3
  187. package/src/components/form-group/form-group.scss +37 -4
  188. package/src/components/form-group/form-group.test.ts +10 -0
  189. package/src/components/header/header.component.ts +11 -4
  190. package/src/components/header/header.scss +47 -5
  191. package/src/components/hover-container/hover-container.component.ts +4 -5
  192. package/src/components/icon/icon.component.ts +95 -9
  193. package/src/components/icon/icon.scss +70 -6
  194. package/src/components/icon/icon.test.ts +55 -0
  195. package/src/components/icon-picker/icon-picker.component.ts +35 -1
  196. package/src/components/inline-edit/inline-edit.component.ts +38 -8
  197. package/src/components/inline-edit/inline-edit.scss +128 -19
  198. package/src/components/input/input.component.ts +173 -297
  199. package/src/components/input/input.scss +65 -43
  200. package/src/components/input/input.test.ts +86 -4
  201. package/src/components/item/item.component.ts +12 -0
  202. package/src/components/item/item.scss +102 -3
  203. package/src/components/item/item.test.ts +23 -0
  204. package/src/components/key/key.component.ts +2 -2
  205. package/src/components/key/key.scss +11 -0
  206. package/src/components/key-value/index.ts +12 -0
  207. package/src/components/key-value/key-value.component.ts +160 -0
  208. package/src/components/key-value/key-value.scss +20 -0
  209. package/src/components/key-value/key-value.test.ts +22 -0
  210. package/src/components/markdown-editor/markdown-editor.component.ts +8 -1
  211. package/src/components/menu/menu.component.ts +21 -2
  212. package/src/components/menu/menu.scss +43 -23
  213. package/src/components/menu/menu.test.ts +33 -0
  214. package/src/components/menu-item/menu-item.component.ts +13 -2
  215. package/src/components/menu-item/menu-item.scss +56 -23
  216. package/src/components/menu-item/menu-item.test.ts +31 -0
  217. package/src/components/menu-item/submenu-controller.ts +25 -15
  218. package/src/components/menu-label/index.ts +12 -0
  219. package/src/components/menu-label/menu-label.component.ts +30 -0
  220. package/src/components/menu-label/menu-label.scss +18 -0
  221. package/src/components/nav-item/index.ts +12 -0
  222. package/src/components/nav-item/nav-item.component.ts +83 -0
  223. package/src/components/nav-item/nav-item.scss +128 -0
  224. package/src/components/nav-list/index.ts +12 -0
  225. package/src/components/nav-list/nav-list.component.ts +313 -0
  226. package/src/components/nav-list/nav-list.scss +87 -0
  227. package/src/components/nav-list/nav-list.test.ts +250 -0
  228. package/src/components/navbar/navbar.component.ts +235 -171
  229. package/src/components/navbar/navbar.scss +333 -169
  230. package/src/components/navbar/navbar.test.ts +63 -143
  231. package/src/components/page/page.component.ts +114 -29
  232. package/src/components/page/page.scss +223 -95
  233. package/src/components/page/page.test.ts +214 -3
  234. package/src/components/page-builder/page-builder.component.ts +51 -5
  235. package/src/components/page-builder/page-builder.scss +6 -1
  236. package/src/components/page-nav/page-nav.component.ts +25 -3
  237. package/src/components/page-nav/page-nav.scss +10 -2
  238. package/src/components/pagination/pagination.scss +1 -1
  239. package/src/components/panel/panel.component.ts +14 -13
  240. package/src/components/panel/panel.scss +14 -10
  241. package/src/components/panel/panel.test.ts +42 -0
  242. package/src/components/popup/popup.component.ts +20 -13
  243. package/src/components/popup/popup.test.ts +21 -1
  244. package/src/components/priority-list/priority-list.component.ts +157 -172
  245. package/src/components/priority-list/priority-list.scss +113 -17
  246. package/src/components/priority-list/priority-list.test.ts +301 -3
  247. package/src/components/progress-bar/progress-bar.component.ts +1 -1
  248. package/src/components/progress-bar/progress-bar.scss +16 -4
  249. package/src/components/progress-tile/progress-tile.scss +1 -1
  250. package/src/components/query-builder/query-builder.component.ts +85 -10
  251. package/src/components/query-builder/query-builder.scss +7 -1
  252. package/src/components/query-builder/query-builder.test.ts +40 -0
  253. package/src/components/radio/radio.component.ts +57 -18
  254. package/src/components/radio/radio.scss +101 -26
  255. package/src/components/radio-group/radio-group.component.ts +45 -7
  256. package/src/components/radio-group/radio-group.scss +17 -0
  257. package/src/components/radio-group/radio-group.test.ts +63 -2
  258. package/src/components/rating/rating.component.ts +23 -0
  259. package/src/components/rating/rating.scss +1 -1
  260. package/src/components/remarkd-editor/remarkd-editor.component.ts +60 -5
  261. package/src/components/remarkd-editor/remarkd-editor.scss +12 -4
  262. package/src/components/report-table/index.ts +12 -0
  263. package/src/components/report-table/report-format.test.ts +86 -0
  264. package/src/components/report-table/report-format.ts +72 -0
  265. package/src/components/report-table/report-math.test.ts +74 -0
  266. package/src/components/report-table/report-math.ts +252 -0
  267. package/src/components/report-table/report-table.component.ts +681 -0
  268. package/src/components/report-table/report-table.scss +328 -0
  269. package/src/components/report-table/report-table.test.ts +702 -0
  270. package/src/components/reveal/reveal.component.ts +15 -1
  271. package/src/components/reveal/reveal.scss +5 -0
  272. package/src/components/schedule-builder/schedule-builder.component.ts +23 -8
  273. package/src/components/scroll-container/scroll-container.component.ts +19 -1
  274. package/src/components/scroll-container/scroll-container.scss +6 -1
  275. package/src/components/select/select.component.ts +52 -36
  276. package/src/components/select/select.scss +42 -25
  277. package/src/components/select/select.test.ts +14 -2
  278. package/src/components/settings-container/settings-container.scss +18 -5
  279. package/src/components/sidebar/sidebar.component.ts +8 -1
  280. package/src/components/sidebar/sidebar.scss +6 -1
  281. package/src/components/skeleton/skeleton.component.ts +3 -3
  282. package/src/components/skeleton/skeleton.scss +6 -6
  283. package/src/components/skeleton/skeleton.test.ts +9 -0
  284. package/src/components/slash-menu/slash-menu.scss +1 -1
  285. package/src/components/slideout/slideout.component.ts +20 -3
  286. package/src/components/slideout/slideout.scss +13 -6
  287. package/src/components/slideout/slideout.test.ts +19 -0
  288. package/src/components/split-button/split-button.component.ts +1 -1
  289. package/src/components/split-pane/split-pane.component.ts +71 -8
  290. package/src/components/split-pane/split-pane.scss +145 -41
  291. package/src/components/split-pane/split-pane.test.ts +23 -3
  292. package/src/components/stat/stat.component.ts +209 -14
  293. package/src/components/stat/stat.scss +305 -5
  294. package/src/components/stat/stat.test.ts +143 -2
  295. package/src/components/status-indicator/status-indicator.scss +22 -21
  296. package/src/components/stepper/stepper.scss +3 -3
  297. package/src/components/tab/tab.component.ts +1 -0
  298. package/src/components/table/table.component.ts +12 -1
  299. package/src/components/table/table.scss +21 -13
  300. package/src/components/table/table.test.ts +17 -0
  301. package/src/components/tabs/tabs.component.ts +150 -37
  302. package/src/components/tabs/tabs.scss +48 -7
  303. package/src/components/tabs/tabs.test.ts +169 -0
  304. package/src/components/textarea/textarea.component.ts +25 -3
  305. package/src/components/textarea/textarea.scss +10 -0
  306. package/src/components/textarea/textarea.test.ts +8 -4
  307. package/src/components/theme-editor/theme-editor.scss +0 -6
  308. package/src/components/tile/tile.component.ts +32 -2
  309. package/src/components/tile/tile.scss +40 -7
  310. package/src/components/tile/tile.test.ts +10 -0
  311. package/src/components/toggle/toggle.component.ts +23 -7
  312. package/src/components/toggle/toggle.scss +36 -29
  313. package/src/components/toggle/toggle.test.ts +17 -0
  314. package/src/components/tooltip/tooltip.component.ts +3 -0
  315. package/src/components/tooltip/tooltip.scss +11 -6
  316. package/src/components/tooltip/tooltip.test.ts +12 -0
  317. package/src/components/translation-field/index.ts +12 -0
  318. package/src/components/translation-field/translation-field.component.ts +55 -0
  319. package/src/components/translation-field/translation-field.scss +101 -0
  320. package/src/components/translation-fields/index.ts +12 -0
  321. package/src/components/translation-fields/translation-fields.component.ts +80 -0
  322. package/src/components/translation-fields/translation-fields.scss +60 -0
  323. package/src/components/translation-grid/index.ts +12 -0
  324. package/src/components/translation-grid/translation-grid.component.ts +157 -0
  325. package/src/components/translation-grid/translation-grid.scss +138 -0
  326. package/src/components/translation-grid/translation-grid.test.ts +28 -0
  327. package/src/components/vertical-stepper/vertical-stepper.component.ts +35 -9
  328. package/src/components/vertical-stepper/vertical-stepper.scss +110 -39
  329. package/src/components/vertical-stepper/vertical-stepper.test.ts +35 -0
  330. package/src/components/well/well.scss +2 -2
  331. package/src/events/events.ts +2 -0
  332. package/src/events/zn-chart-click.ts +13 -0
  333. package/src/events/zn-report-change.ts +7 -0
  334. package/src/form-control.scss +36 -3
  335. package/src/internal/anchored-popover.ts +175 -0
  336. package/src/internal/animate.ts +19 -5
  337. package/src/internal/cosmic-border.ts +15 -0
  338. package/src/internal/form.ts +1 -1
  339. package/src/internal/hover-intent.ts +15 -0
  340. package/src/internal/input-modality.ts +15 -0
  341. package/src/internal/pointer-intent.ts +37 -0
  342. package/src/internal/reorder.ts +632 -0
  343. package/src/internal/scroll.ts +51 -3
  344. package/src/internal/selection-card.ts +1 -1
  345. package/src/internal/tab-indicator.ts +92 -0
  346. package/src/internal/table-sort.ts +25 -0
  347. package/src/internal/table-tabs.ts +108 -0
  348. package/src/internal/test-drag.ts +35 -0
  349. package/src/internal/theme-openbyte.test.ts +126 -0
  350. package/src/internal/theme.ts +4 -4
  351. package/src/internal/zinc-element.ts +2 -2
  352. package/src/selection-card.scss +25 -0
  353. package/src/types/web-test-runner-commands.d.ts +1 -0
  354. package/src/utilities/color.test.ts +43 -0
  355. package/src/utilities/color.ts +189 -0
  356. package/src/zinc.ts +14 -0
  357. package/.github/workflows/eleventy_build.yml +0 -42
  358. package/scss/themes/_aura.scss +0 -114
@@ -15,6 +15,7 @@
15
15
  }
16
16
 
17
17
  &__caption {
18
+ font-family: var(--zn-text-h1-font-family, inherit);
18
19
  font-weight: var(--zn-text-h1-font-weight);
19
20
  font-size: var(--zn-text-h1-font-size);
20
21
  text-transform: var(--zn-text-h1-transform, none);
@@ -84,7 +85,7 @@
84
85
  }
85
86
 
86
87
  zn-icon:hover {
87
- color: rgb(var(--zn-primary));
88
+ color: var(--zn-color-primary-fg, rgb(var(--zn-primary)));
88
89
  cursor: pointer;
89
90
  }
90
91
  }
@@ -131,8 +132,7 @@
131
132
  flex-grow: 1;
132
133
  flex-shrink: 1;
133
134
  // Chrome ignores overflow-clip-margin unless both axes clip
134
- overflow: clip;
135
- overflow-clip-margin: var(--zn-focus-ring-outset);
135
+ @include wc.clip-focus-room;
136
136
  min-width: 20px;
137
137
  }
138
138
 
@@ -159,7 +159,7 @@
159
159
  }
160
160
 
161
161
  &::slotted(a:hover) {
162
- color: rgb(var(--zn-primary));
162
+ color: var(--zn-color-primary-fg, rgb(var(--zn-primary)));
163
163
  }
164
164
 
165
165
  &::slotted(a)::after {
@@ -190,7 +190,7 @@
190
190
  cursor: pointer;
191
191
 
192
192
  &:hover {
193
- color: rgb(var(--zn-primary));
193
+ color: var(--zn-color-primary-fg, rgb(var(--zn-primary)));
194
194
  }
195
195
  }
196
196
  }
@@ -232,6 +232,12 @@
232
232
  flex-wrap: nowrap;
233
233
  }
234
234
 
235
+ // Controls in the actions row line up with the buttons beside them, without
236
+ // the label/help-text spacing a form field otherwise carries.
237
+ slot[name="actions"]::slotted(*) {
238
+ --zn-form-control-input-margin: 0;
239
+ }
240
+
235
241
  slot[name="actions"]::slotted(zn-button-menu) {
236
242
  flex-grow: 1;
237
243
  flex-shrink: 1;
@@ -247,3 +253,39 @@
247
253
  border-bottom: none !important;
248
254
  }
249
255
  }
256
+
257
+ .header__left:has(.header__description) {
258
+ display: flex;
259
+ flex-direction: column;
260
+ gap: var(--zn-spacing-2x-small);
261
+ }
262
+
263
+ .header--inline-description .header__left:has(.header__description) {
264
+ flex-direction: row;
265
+ flex-wrap: wrap;
266
+ justify-content: flex-start;
267
+ align-items: baseline;
268
+ gap: 0 var(--zn-spacing-x-small);
269
+ }
270
+
271
+ // Narrow headers drop the actions onto their own row instead of squeezing the caption
272
+ .header .caption {
273
+ flex-wrap: wrap;
274
+ row-gap: var(--zn-spacing-small);
275
+ }
276
+
277
+ .header__caption {
278
+ flex-shrink: 0;
279
+ min-width: min-content;
280
+ }
281
+
282
+ .header__right {
283
+ min-width: min-content;
284
+ }
285
+
286
+ .header__description {
287
+ font-size: var(--zn-header-description-font-size, var(--zn-font-size-x-small));
288
+ font-weight: normal;
289
+ line-height: var(--zn-line-height-normal, 1.45);
290
+ color: var(--zn-header-description-color, var(--zn-text-color-muted));
291
+ }
@@ -81,8 +81,9 @@ export default class ZnHoverContainer extends ZincElement {
81
81
  return triggers.includes(triggerType);
82
82
  }
83
83
 
84
- private handleBlur = () => {
85
- if (this.hasTrigger('focus')) {
84
+ // relatedTarget is retargeted to this host when focus moves into the shadow root, so contains() covers both trees
85
+ private handleBlur = (event: FocusEvent) => {
86
+ if (this.hasTrigger('focus') && !this.contains(event.relatedTarget as Node | null)) {
86
87
  this.hide();
87
88
  }
88
89
  };
@@ -204,9 +205,7 @@ export default class ZnHoverContainer extends ZincElement {
204
205
  hover-bridge>
205
206
  <slot slot="anchor"></slot>
206
207
  <div part="body" id="hover-container" class="hover-container__body"
207
- role="hover-container"
208
- aria-live=${this.open ? 'polite' : 'off'}
209
- aria-label="hover-container">
208
+ aria-live=${this.open ? 'polite' : 'off'}>
210
209
  <slot name="content">${this.content}</slot>
211
210
  </div>
212
211
  </zn-popup>`;
@@ -5,6 +5,7 @@ import {property} from 'lit/decorators.js';
5
5
  import {sha256} from '../../utilities/sha256';
6
6
  import {styleMap} from "lit/directives/style-map.js";
7
7
  import {unsafeSVG} from 'lit/directives/unsafe-svg.js';
8
+ import lineIconStyles from './lni.scss';
8
9
  import ZincElement from '../../internal/zinc-element';
9
10
  import type {IconNode, SVGProps} from 'lucide';
10
11
 
@@ -28,9 +29,27 @@ function loadLucideIcons(): Promise<void> {
28
29
  return lucidePromise;
29
30
  }
30
31
 
32
+ // Country flags (3:2 SVGs keyed by ISO 3166 alpha-2 code) load lazily like lucide
33
+ let flagIcons: Record<string, string> | undefined;
34
+ let flagPromise: Promise<void> | undefined;
35
+
36
+ function loadFlagIcons(): Promise<void> {
37
+ flagPromise ??= import('country-flag-icons/string/3x2').then(m => {
38
+ flagIcons = m as unknown as Record<string, string>;
39
+ }).catch((error: unknown) => {
40
+ flagPromise = undefined;
41
+ console.warn('<zn-icon> failed to load the flag icon chunk', error);
42
+ });
43
+ return flagPromise;
44
+ }
45
+
31
46
  export type IconLibrary = "src" | "material" | "material-outlined" | "material-round" | "material-sharp" |
32
47
  "material-two-tone" | "material-symbols-outlined" | "gravatar" | "libravatar" | "avatar" | "brands" | "line" |
33
- "lucide";
48
+ "lucide" | "flag";
49
+
50
+ export type AvatarPresence = 'online' | 'away' | 'busy' | 'meeting' | 'offline';
51
+
52
+ const namedSizes: Record<string, number> = {sm: 22, md: 28, lg: 40, xl: 56};
34
53
 
35
54
  export type IconColor =
36
55
  "default"
@@ -70,14 +89,24 @@ export type IconColor =
70
89
  *
71
90
  * @cssproperty --example - An example CSS custom property.
72
91
  */
92
+ // Lineicons' ~600 glyph rules are adopted only by icons that use them: every adopted rule is restyled per shadow
93
+ // root, and icons are the most numerous component on a page (over 1,000 on some views, seconds of style work in Safari).
94
+ const lineIcons = unsafeCSS(lineIconStyles);
95
+
73
96
  export default class ZnIcon extends ZincElement {
74
97
  static styles: CSSResultGroup = unsafeCSS(styles);
75
98
 
99
+ private _lineIconsAdopted = false;
100
+
76
101
  @property({reflect: true}) src: string;
77
102
 
78
103
  @property({reflect: true}) alt: string;
79
104
 
80
- @property({type: Number, reflect: true}) size: number = 24;
105
+ /** Size in pixels, or one of the avatar sizes `sm` (22), `md` (28), `lg` (40) and `xl` (56). */
106
+ @property({
107
+ reflect: true,
108
+ converter: {fromAttribute: (value: string | null) => namedSizes[value ?? ''] ?? (value === null ? 24 : Number(value))}
109
+ }) size: number = 24;
81
110
 
82
111
  @property({type: Boolean, reflect: true}) round = false;
83
112
  @property({type: Boolean, reflect: true}) tile = false;
@@ -94,6 +123,12 @@ export default class ZnIcon extends ZincElement {
94
123
 
95
124
  @property({type: Boolean}) squared: boolean = false;
96
125
 
126
+ /** A live-status dot on an avatar: `online`, `away`, `busy`, `meeting` or `offline`. */
127
+ @property({reflect: true}) presence: AvatarPresence;
128
+
129
+ /** Draws an avatar as a solid circle even where the theme tints avatars. */
130
+ @property({type: Boolean, reflect: true}) solid = false;
131
+
97
132
  private static readonly presetColors = new Set([
98
133
  'default', 'primary', 'accent', 'info', 'warning', 'error', 'success',
99
134
  'white', 'disabled', 'red', 'blue', 'green', 'orange', 'yellow',
@@ -148,6 +183,8 @@ export default class ZnIcon extends ZincElement {
148
183
  case "lucide":
149
184
  case "lu":
150
185
  return "lucide";
186
+ case "flag":
187
+ return "flag";
151
188
  case 'brand':
152
189
  case 'brands':
153
190
  return "brands";
@@ -191,6 +228,13 @@ export default class ZnIcon extends ZincElement {
191
228
  await super.getUpdateComplete();
192
229
  }
193
230
  }
231
+ if (this.library === 'flag' && !flagIcons) {
232
+ await loadFlagIcons();
233
+ if (flagIcons) {
234
+ this.requestUpdate();
235
+ await super.getUpdateComplete();
236
+ }
237
+ }
194
238
  return result;
195
239
  }
196
240
 
@@ -273,6 +317,26 @@ export default class ZnIcon extends ZincElement {
273
317
  return email.trim().toLowerCase();
274
318
  }
275
319
 
320
+ protected override updated(changedProperties: PropertyValues<this>) {
321
+ super.updated(changedProperties);
322
+ if (this.library === 'line') {
323
+ this.adoptLineIcons();
324
+ }
325
+ }
326
+
327
+ private adoptLineIcons() {
328
+ const root = this.shadowRoot;
329
+ if (!root || this._lineIconsAdopted) return;
330
+ this._lineIconsAdopted = true;
331
+ if (lineIcons.styleSheet) {
332
+ root.adoptedStyleSheets = [...root.adoptedStyleSheets, lineIcons.styleSheet];
333
+ } else {
334
+ const style = document.createElement('style');
335
+ style.textContent = lineIcons.cssText;
336
+ root.appendChild(style);
337
+ }
338
+ }
339
+
276
340
  render() {
277
341
  return html`
278
342
  <div class="icon-wrapper">
@@ -300,6 +364,9 @@ export default class ZnIcon extends ZincElement {
300
364
  'icon--pink': this.color === "pink",
301
365
  'icon--grey': this.color === "grey",
302
366
  'icon--avatar': this.library === "avatar",
367
+ 'icon--avatar-solid': this.library === "avatar" && this.solid,
368
+ 'icon--flag': this.library === "flag",
369
+ 'icon--has-color': Boolean(this.color),
303
370
  'icon--padded': this.padded,
304
371
  'icon--blink': this.blink,
305
372
  'icon--squared': this.squared,
@@ -309,7 +376,7 @@ export default class ZnIcon extends ZincElement {
309
376
  '--icon-size': this.size + "px",
310
377
  '--icon-color': (this.color && !this.isPresetColor(this.color)) ? this.color : null,
311
378
  '--icon-fill': (this.fill && !this.isPresetColor(this.fill)) ? this.fill : null,
312
- '--avatar-color': this.color ? null : (this.library === 'avatar' ? this.getColorForAvatar(this.getAvatarInitials(this.src)) : null)
379
+ '--avatar-color': this.color ? null : (this.library === 'avatar' ? this.getColorForAvatar(this.src) : null)
313
380
  })}">
314
381
  ${this.library && this.library !== "src" ? html`
315
382
  ${choose(this.library, [
@@ -321,12 +388,14 @@ export default class ZnIcon extends ZincElement {
321
388
  ["brands", () => html`<i part="icon" class="kb">${this.src}</i>`],
322
389
  ["line", () => html`<i part="icon" class="lni lni-${this.src}"></i>`],
323
390
  ["lucide", () => this.renderLucideIcon()],
391
+ ["flag", () => this.renderFlagIcon()],
324
392
  ["material-symbols-outlined", () => html`<i part="icon" class="mi mi--symbol-outlined">${this.src}</i>`],
325
393
  ["gravatar", () => html`<img part="icon" alt="${this.alt}"
326
394
  src="https://www.gravatar.com/avatar/${this.src}?s=${this.size}${this.gravatarOptions}"/>`],
327
395
  ["libravatar", () => html`<img part="icon" alt="${this.alt}"
328
396
  src="https://seccdn.libravatar.org/avatar/${this.src}?s=${this.size}${this.gravatarOptions}"/>`],
329
- ["avatar", () => html`<span class="avatar__text">${this.getAvatarInitials(this.src)}</span>`]
397
+ ["avatar", () => html`<span class="avatar__text" role="img"
398
+ aria-label="${this.alt || this.src}">${this.getAvatarInitials(this.src)}</span>`]
330
399
  ], () => html`Library not supported: ${this.library}`)}` : ''}
331
400
 
332
401
  ${(!this.library || this.library === 'src') && this.src ? html`
@@ -336,7 +405,10 @@ export default class ZnIcon extends ZincElement {
336
405
  ${!this.library && !this.src ? html`
337
406
  <slot></slot>` : ''}
338
407
  </div>
339
- </div>`;
408
+ </div>
409
+ ${this.presence ? html`
410
+ <span part="presence" class="presence presence--${this.presence}" role="img"
411
+ aria-label="${this.presence}"></span>` : ''}`;
340
412
  }
341
413
 
342
414
  private getAvatarInitials(avatar: string) {
@@ -376,6 +448,21 @@ export default class ZnIcon extends ZincElement {
376
448
  return unsafeSVG(this.getLucideSvg(icon));
377
449
  }
378
450
 
451
+ private renderFlagIcon() {
452
+ if (!flagIcons) {
453
+ void loadFlagIcons().then(() => {
454
+ if (flagIcons) {
455
+ this.requestUpdate();
456
+ }
457
+ });
458
+ return html``;
459
+ }
460
+
461
+ const code = (this.src ?? '').toUpperCase();
462
+ const svg = Object.prototype.hasOwnProperty.call(flagIcons, code) ? flagIcons[code] : undefined;
463
+ return svg ? html`<span part="icon" class="flag" role="img" aria-label="${this.alt || code}">${unsafeSVG(svg)}</span>` : html``;
464
+ }
465
+
379
466
  private getLucideIcon(src = ''): IconNode | undefined {
380
467
  if (!src) {
381
468
  return undefined;
@@ -438,17 +525,16 @@ export default class ZnIcon extends ZincElement {
438
525
  }
439
526
 
440
527
 
441
- protected getColorForAvatar(avatarInitials: string) {
528
+ protected getColorForAvatar(name: string) {
442
529
  const colors = [
443
530
  '#00AA55', '#0882a8', '#ed55ed', '#5eae00',
444
531
  '#09389e', '#d17300', '#5e0be2', '#5b7a13',
445
532
  '#ddb100', '#d400c2', '#DC2A2A', '#a967ff',
446
533
  ];
447
534
 
448
- // Generate a hash from the initials
449
- const hash = Array.from(avatarInitials).reduce((acc, char) => acc + char.charCodeAt(0), 0);
535
+ // Order-sensitive, so names sharing initials or letters still spread across the palette
536
+ const hash = Array.from(name.trim().toLowerCase()).reduce((acc, char) => (acc * 31 + char.charCodeAt(0)) >>> 0, 0);
450
537
 
451
- // Use the hash to select a color
452
538
  return colors[hash % colors.length];
453
539
  }
454
540
  }
@@ -1,5 +1,4 @@
1
1
  @use "../../wc";
2
- @use "./lni";
3
2
 
4
3
  $icon-colors: (
5
4
  default: --zn-text,
@@ -30,6 +29,40 @@ $icon-colors: (
30
29
  --icon-color: inherit;
31
30
  }
32
31
 
32
+ :host([presence]) {
33
+ position: relative;
34
+ }
35
+
36
+ // A live-status dot on the avatar's corner, ringed in the surface colour so it reads against the avatar.
37
+ .presence {
38
+ position: absolute;
39
+ right: -1px;
40
+ bottom: -1px;
41
+ width: 28%;
42
+ height: 28%;
43
+ min-width: 7px;
44
+ min-height: 7px;
45
+ border: 2px solid var(--zn-avatar-ring-color, rgb(var(--zn-panel)));
46
+ border-radius: 50%;
47
+ background: rgb(var(--zn-color-disabled));
48
+
49
+ &--online {
50
+ background: rgb(var(--zn-color-success));
51
+ }
52
+
53
+ &--away {
54
+ background: rgb(var(--zn-color-warning));
55
+ }
56
+
57
+ &--busy {
58
+ background: rgb(var(--zn-color-error));
59
+ }
60
+
61
+ &--meeting {
62
+ background: rgb(var(--zn-primary));
63
+ }
64
+ }
65
+
33
66
  :host([split-pane-focus]) .icon-wrapper {
34
67
  cursor: pointer;
35
68
  }
@@ -62,6 +95,19 @@ $icon-colors: (
62
95
  font-size: var(--icon-size) !important;
63
96
  }
64
97
 
98
+ &--flag {
99
+ display: inline-flex;
100
+ align-items: center;
101
+ }
102
+
103
+ .flag svg {
104
+ display: block;
105
+ width: var(--icon-size);
106
+ height: calc(var(--icon-size) * 2 / 3);
107
+ border-radius: 2px;
108
+ box-shadow: 0 0 0 1px rgb(0 0 0 / 0.08);
109
+ }
110
+
65
111
  // Colors
66
112
 
67
113
  &--default {
@@ -178,20 +224,38 @@ $icon-colors: (
178
224
  animation: blink-animation 1.5s steps(5, start) infinite;
179
225
  }
180
226
 
227
+ // Solid by default. A theme sets --zn-avatar-tint: 1 for a tinted circle with darker initials,
228
+ // and --zn-avatar-default-color to replace the per-name colour.
181
229
  &--avatar {
182
- background-color: var(--avatar-color, var(--icon-color));
183
- color: hsl(from var(--avatar-color, var(--icon-color)) h s 95);
230
+ --_avatar: var(--zn-avatar-default-color, var(--avatar-color));
231
+ --_tint: var(--zn-avatar-tint, 0);
232
+ --_solid-ink: hsl(from var(--_avatar) h s 95);
233
+ // Mixed towards the text colour so the initials stay readable in light and dark modes
234
+ --_tint-ink: color-mix(in srgb, var(--_avatar) 72%, rgb(var(--zn-text)));
235
+
236
+ // Mixed with the surface, not made translucent, so overlapping avatars in a group stay opaque
237
+ background-color: color-mix(in srgb, var(--_avatar) calc(100% - var(--_tint) * 86%), rgb(var(--zn-panel)));
238
+ color: color-mix(in srgb, var(--_tint-ink) calc(var(--_tint) * 100%), var(--_solid-ink));
184
239
  display: flex;
185
240
  justify-content: center;
186
241
  align-items: center;
187
242
 
188
243
  .avatar__text {
189
- font-size: calc((var(--icon-size) / 2.4));
190
- color: hsl(from var(--avatar-color, var(--icon-color)) h s 95);
191
- font-weight: var(--zn-font-weight-semibold);
244
+ font-size: calc(var(--icon-size) * var(--zn-avatar-font-scale, 0.4167));
245
+ font-weight: var(--zn-avatar-font-weight, var(--zn-font-weight-semibold));
246
+ letter-spacing: .02em;
247
+ color: inherit;
192
248
  }
193
249
  }
194
250
 
251
+ &--avatar.icon--has-color {
252
+ --_avatar: var(--icon-color);
253
+ }
254
+
255
+ &--avatar.icon--avatar-solid {
256
+ --_tint: 0;
257
+ }
258
+
195
259
  &--padded {
196
260
  padding: var(--zn-spacing-small);
197
261
  }
@@ -31,6 +31,19 @@ describe('<zn-icon>', () => {
31
31
  );
32
32
  });
33
33
 
34
+ it('should style Lineicons glyphs only on line icons', async () => {
35
+ const hasLineRules = (el: Element) =>
36
+ el.shadowRoot!.adoptedStyleSheets.some(sheet => Array.from(sheet.cssRules).some(rule => rule.cssText.includes(".lni-home-2")));
37
+
38
+ const line = await fixture<ZnIcon>(html`<zn-icon src="home-2" library="line"></zn-icon>`);
39
+ const glyph = line.shadowRoot!.querySelector('.lni')!;
40
+ expect(getComputedStyle(glyph, '::before').content).to.not.equal('none');
41
+ expect(hasLineRules(line)).to.be.true;
42
+
43
+ const lucide = await fixture<ZnIcon>(html`<zn-icon src="home@lu"></zn-icon>`);
44
+ expect(hasLineRules(lucide)).to.be.false;
45
+ });
46
+
34
47
  it('should render Lucide icons as inline SVGs', async () => {
35
48
  const el = await fixture<ZnIcon>(html`
36
49
  <zn-icon src="cloud-upload" library="lucide" size="48"></zn-icon>
@@ -55,6 +68,19 @@ describe('<zn-icon>', () => {
55
68
  expect(svg).to.exist;
56
69
  });
57
70
 
71
+ it('should render country flags from the flag library', async () => {
72
+ const el = await fixture<ZnIcon>(html`<zn-icon src="gb@flag" size="20"></zn-icon>`);
73
+
74
+ expect(el.library).to.equal('flag');
75
+ expect(el.shadowRoot?.querySelector('.flag[aria-label="GB"] svg')).to.exist;
76
+ });
77
+
78
+ it('should render nothing for an unknown flag', async () => {
79
+ const el = await fixture<ZnIcon>(html`<zn-icon src="zz@flag"></zn-icon>`);
80
+
81
+ expect(el.shadowRoot?.querySelector('.flag')).to.not.exist;
82
+ });
83
+
58
84
  it('should remove a matching library indicator from the icon src', async () => {
59
85
  const el = await fixture<ZnIcon>(html`
60
86
  <zn-icon src="home@lu" library="lucide" size="48"></zn-icon>
@@ -75,4 +101,33 @@ describe('<zn-icon>', () => {
75
101
  expect(el.library).to.equal('material-symbols-outlined');
76
102
  expect(el.src).to.equal('home@lu');
77
103
  });
104
+
105
+ it('should accept the named avatar sizes', async () => {
106
+ const el = await fixture<ZnIcon>(html`<zn-icon src="Fiona Hart" library="avatar" size="lg"></zn-icon>`);
107
+ expect(el.size).to.equal(40);
108
+ expect(el.shadowRoot!.querySelector<HTMLElement>('.icon')!.getBoundingClientRect().width).to.equal(40);
109
+ });
110
+
111
+ it('should still accept a size in pixels', async () => {
112
+ const el = await fixture<ZnIcon>(html`<zn-icon src="home" size="18"></zn-icon>`);
113
+ expect(el.size).to.equal(18);
114
+ });
115
+
116
+ it('should show a presence dot only when presence is set', async () => {
117
+ const el = await fixture<ZnIcon>(html`<zn-icon src="Ben Brooks" library="avatar" round></zn-icon>`);
118
+ expect(el.shadowRoot!.querySelector('[part="presence"]')).to.be.null;
119
+
120
+ el.presence = 'online';
121
+ await el.updateComplete;
122
+ const dot = el.shadowRoot!.querySelector('[part="presence"]')!;
123
+ expect(dot.classList.contains('presence--online')).to.be.true;
124
+ expect(dot.getAttribute('aria-label')).to.equal('online');
125
+ });
126
+
127
+ it('should name an avatar for assistive technology', async () => {
128
+ const el = await fixture<ZnIcon>(html`<zn-icon src="BB" alt="Ben Brooks" library="avatar"></zn-icon>`);
129
+ const text = el.shadowRoot!.querySelector('.avatar__text')!;
130
+ expect(text.getAttribute('role')).to.equal('img');
131
+ expect(text.getAttribute('aria-label')).to.equal('Ben Brooks');
132
+ });
78
133
  });
@@ -60,6 +60,7 @@ export default class ZnIconPicker extends ZincElement implements ZincFormControl
60
60
  @state() private _searchQuery = '';
61
61
  @state() private _iconList: string[] = [];
62
62
  @state() private _filteredIcons: string[] = [];
63
+ @state() private _gridFocusIcon = '';
63
64
 
64
65
  // Pending selections (not committed until confirm)
65
66
  @state() private _pendingIcon = '';
@@ -280,6 +281,35 @@ export default class ZnIconPicker extends ZincElement implements ZincFormControl
280
281
 
281
282
  private handleIconSelect(iconName: string) {
282
283
  this._pendingIcon = iconName;
284
+ this._gridFocusIcon = iconName;
285
+ }
286
+
287
+ private _gridTabStop() {
288
+ if (this._filteredIcons.includes(this._gridFocusIcon)) return this._gridFocusIcon;
289
+ if (this._filteredIcons.includes(this._pendingIcon)) return this._pendingIcon;
290
+ return this._filteredIcons[0];
291
+ }
292
+
293
+ private _handleGridKeyDown(e: KeyboardEvent) {
294
+ const items = [...(e.currentTarget as HTMLElement).querySelectorAll<HTMLButtonElement>('.icon-picker__grid-item')];
295
+ const index = items.indexOf(e.target as HTMLButtonElement);
296
+ if (index === -1) return;
297
+
298
+ const columns = Math.max(1, items.filter(item => item.offsetTop === items[0].offsetTop).length);
299
+ let next: number;
300
+ switch (e.key) {
301
+ case 'ArrowRight': next = index + 1; break;
302
+ case 'ArrowLeft': next = index - 1; break;
303
+ case 'ArrowDown': next = index + columns; break;
304
+ case 'ArrowUp': next = index - columns; break;
305
+ case 'Home': next = 0; break;
306
+ case 'End': next = items.length - 1; break;
307
+ default: return;
308
+ }
309
+ e.preventDefault();
310
+ next = Math.max(0, Math.min(items.length - 1, next));
311
+ this._gridFocusIcon = this._filteredIcons[next];
312
+ items[next].focus();
283
313
  }
284
314
 
285
315
  private async handleLibraryChange(e: Event) {
@@ -326,6 +356,7 @@ export default class ZnIconPicker extends ZincElement implements ZincFormControl
326
356
  }
327
357
 
328
358
  private _handleTriggerKeyDown(e: KeyboardEvent) {
359
+ if (e.target !== e.currentTarget) return;
329
360
  if (e.key === 'Enter' || e.key === ' ') {
330
361
  e.preventDefault();
331
362
  this._handleTriggerClick();
@@ -340,6 +371,7 @@ export default class ZnIconPicker extends ZincElement implements ZincFormControl
340
371
  // auto-detection renders an <img> instead of a font ligature.
341
372
  const triggerIcon = this._fileUrl ?? this.icon;
342
373
  const triggerLibrary = hasValue && !this.isImageValue ? this.library : nothing;
374
+ const gridTabStop = this._gridTabStop();
343
375
  const pendingPreviewUrl = this._pendingFileUrl ?? (this.isImageValue && !this._file ? this.icon : this._fileUrl);
344
376
 
345
377
  return html`
@@ -503,7 +535,7 @@ export default class ZnIconPicker extends ZincElement implements ZincFormControl
503
535
  <zn-icon slot="prefix" src="search" size="18"></zn-icon>
504
536
  </zn-input>
505
537
 
506
- <div class="icon-picker__grid">
538
+ <div class="icon-picker__grid" @keydown=${this._handleGridKeyDown}>
507
539
  ${this._filteredIcons.length === 0 ? html`
508
540
  <div class="icon-picker__no-results">No icons found</div>
509
541
  ` : this._filteredIcons.map(iconName => html`
@@ -514,6 +546,8 @@ export default class ZnIconPicker extends ZincElement implements ZincFormControl
514
546
  'icon-picker__grid-item--selected': iconName === this._pendingIcon
515
547
  })}
516
548
  title=${iconName}
549
+ aria-pressed=${iconName === this._pendingIcon ? 'true' : 'false'}
550
+ tabindex=${iconName === gridTabStop ? '0' : '-1'}
517
551
  @click=${() => this.handleIconSelect(iconName)}>
518
552
  <zn-icon
519
553
  src=${iconName}