@kubex/zinc 1.1.184 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (362) hide show
  1. package/.browserslistrc +2 -0
  2. package/.github/workflows/js_build_and_deploy.yaml +54 -1
  3. package/.github/workflows/test.yaml +27 -0
  4. package/dist/chunks/zn.UWJLIXDT.js +1 -0
  5. package/dist/custom-elements.json +10344 -2583
  6. package/dist/vscode.html-custom-data.json +1102 -125
  7. package/dist/web-types.json +2217 -224
  8. package/dist/zn.d.ts +2306 -417
  9. package/dist/zn.min.css +1 -1
  10. package/dist/zn.min.js +2313 -1635
  11. package/docs/_includes/default.njk +4 -1
  12. package/docs/assets/scripts/docs.js +22 -6
  13. package/docs/assets/styles/docs.css +9 -2
  14. package/docs/data/report-table-day-compare.json +380 -0
  15. package/docs/data/report-table.json +314 -0
  16. package/docs/pages/components/avatar-group.md +57 -0
  17. package/docs/pages/components/badge.md +67 -0
  18. package/docs/pages/components/button.md +41 -1
  19. package/docs/pages/components/chat-message.md +16 -0
  20. package/docs/pages/components/checkbox.md +1 -1
  21. package/docs/pages/components/chip.md +25 -13
  22. package/docs/pages/components/color-picker.md +37 -0
  23. package/docs/pages/components/copy-button.md +2 -2
  24. package/docs/pages/components/data-table-filter.md +33 -50
  25. package/docs/pages/components/data-table-search.md +4 -4
  26. package/docs/pages/components/data-table.md +27 -3
  27. package/docs/pages/components/date-range.md +116 -0
  28. package/docs/pages/components/dialog.md +29 -37
  29. package/docs/pages/components/diff-row.md +28 -0
  30. package/docs/pages/components/diff.md +46 -0
  31. package/docs/pages/components/editor.md +55 -0
  32. package/docs/pages/components/filter-wrapper.md +20 -0
  33. package/docs/pages/components/icon.md +46 -1
  34. package/docs/pages/components/input.md +30 -11
  35. package/docs/pages/components/item.md +35 -0
  36. package/docs/pages/components/key-value.md +25 -0
  37. package/docs/pages/components/menu.md +74 -7
  38. package/docs/pages/components/nav-item.md +15 -0
  39. package/docs/pages/components/nav-list.md +132 -0
  40. package/docs/pages/components/navbar.md +100 -1
  41. package/docs/pages/components/page.md +242 -74
  42. package/docs/pages/components/priority-list.md +43 -5
  43. package/docs/pages/components/query-builder.md +23 -0
  44. package/docs/pages/components/radio-group.md +33 -1
  45. package/docs/pages/components/radio.md +1 -1
  46. package/docs/pages/components/report-table.md +202 -0
  47. package/docs/pages/components/stat.md +30 -0
  48. package/docs/pages/components/tile.md +11 -0
  49. package/docs/pages/components/tooltip.md +1 -1
  50. package/docs/pages/components/translation-grid.md +86 -0
  51. package/docs/pages/components/vertical-stepper.md +108 -64
  52. package/package.json +2 -1
  53. package/scss/_global-spacing.scss +2 -2
  54. package/scss/_root.scss +11 -171
  55. package/scss/boot.scss +14 -0
  56. package/scss/mixins/_helper-mixins.scss +24 -2
  57. package/scss/mixins/_motion-mixins.scss +126 -0
  58. package/scss/mixins/_spacing-mixins.scss +4 -3
  59. package/scss/mixins/_tab-indicator-mixins.scss +12 -0
  60. package/scss/mixins/_typography-mixins.scss +1 -1
  61. package/scss/mixins/index.scss +14 -0
  62. package/scss/shared/_base.scss +32 -1
  63. package/scss/shared/_button-mixin.scss +7 -5
  64. package/scss/shared/_table.scss +35 -4
  65. package/scss/shared/nav.scss +4 -0
  66. package/scss/shared/shimmer.scss +1 -1
  67. package/scss/table-controls.scss +206 -0
  68. package/scss/themes/_dark.scss +1 -3
  69. package/scss/themes/_index.scss +1 -1
  70. package/scss/themes/_light.scss +46 -6
  71. package/scss/themes/_modes.scss +11 -11
  72. package/scss/themes/_openbyte.scss +944 -0
  73. package/scss/themes/_typography.scss +4 -2
  74. package/scss/variables/index.scss +0 -9
  75. package/src/components/alert/alert.component.ts +14 -4
  76. package/src/components/alert/alert.scss +55 -69
  77. package/src/components/alert/alert.test.ts +8 -0
  78. package/src/components/animated-button/animated-button.component.ts +3 -2
  79. package/src/components/animated-button/animated-button.scss +6 -23
  80. package/src/components/animated-button/animated-button.test.ts +4 -3
  81. package/src/components/avatar-group/avatar-group.component.ts +84 -0
  82. package/src/components/avatar-group/avatar-group.scss +54 -0
  83. package/src/components/avatar-group/avatar-group.test.ts +59 -0
  84. package/src/components/avatar-group/index.ts +12 -0
  85. package/src/components/badge/badge.component.ts +49 -0
  86. package/src/components/badge/badge.scss +80 -0
  87. package/src/components/badge/badge.test.ts +34 -0
  88. package/src/components/badge/index.ts +12 -0
  89. package/src/components/bulk-actions/bulk-actions.component.ts +14 -2
  90. package/src/components/button/button.component.ts +242 -11
  91. package/src/components/button/button.scss +133 -25
  92. package/src/components/button/button.test.ts +122 -0
  93. package/src/components/button-menu/button-menu.component.ts +32 -11
  94. package/src/components/button-menu/button-menu.scss +5 -1
  95. package/src/components/channel-tile/channel-tile.component.ts +34 -27
  96. package/src/components/channel-tile/channel-tile.scss +76 -43
  97. package/src/components/chart/builders.test.ts +46 -0
  98. package/src/components/chart/builders.ts +27 -3
  99. package/src/components/chart/chart-live.test.ts +4 -6
  100. package/src/components/chart/chart.component.ts +125 -5
  101. package/src/components/chart/chart.scss +121 -0
  102. package/src/components/chart/chart.test.ts +81 -0
  103. package/src/components/chat-message/chat-message.component.ts +21 -1
  104. package/src/components/chat-message/chat-message.scss +13 -2
  105. package/src/components/chat-message/chat-message.test.ts +9 -0
  106. package/src/components/checkbox/checkbox.component.ts +61 -27
  107. package/src/components/checkbox/checkbox.scss +58 -25
  108. package/src/components/checkbox-group/checkbox-group.component.ts +7 -1
  109. package/src/components/chip/chip.component.ts +11 -1
  110. package/src/components/chip/chip.scss +91 -26
  111. package/src/components/chip/chip.test.ts +17 -0
  112. package/src/components/collapsible/collapsible.component.ts +17 -3
  113. package/src/components/collapsible/collapsible.scss +19 -10
  114. package/src/components/color-picker/color-picker.component.ts +277 -0
  115. package/src/components/color-picker/color-picker.scss +141 -0
  116. package/src/components/color-picker/color-picker.test.ts +57 -0
  117. package/src/components/color-picker/index.ts +12 -0
  118. package/src/components/cols/cols.component.ts +17 -3
  119. package/src/components/cols/cols.scss +24 -13
  120. package/src/components/cols/cols.test.ts +33 -0
  121. package/src/components/confirm/confirm.component.ts +20 -6
  122. package/src/components/confirm/confirm.test.ts +52 -0
  123. package/src/components/content-block/content-block.component.ts +38 -9
  124. package/src/components/content-block/content-block.scss +11 -5
  125. package/src/components/content-block/content-block.test.ts +26 -0
  126. package/src/components/copy-button/copy-button.component.ts +2 -2
  127. package/src/components/copy-button/copy-button.scss +17 -4
  128. package/src/components/data-select/data-select.component.ts +3 -1
  129. package/src/components/data-table/data-table.component.ts +364 -142
  130. package/src/components/data-table/data-table.scss +133 -60
  131. package/src/components/data-table/data-table.test.ts +356 -4
  132. package/src/components/data-table-filter/data-table-filter.component.ts +426 -406
  133. package/src/components/data-table-filter/data-table-filter.scss +176 -148
  134. package/src/components/data-table-filter/data-table-filter.test.ts +271 -273
  135. package/src/components/data-table-search/data-table-search.component.ts +4 -2
  136. package/src/components/data-table-search/data-table-search.scss +8 -1
  137. package/src/components/data-table-search/data-table-search.test.ts +29 -0
  138. package/src/components/date-range/date-range-math.test.ts +78 -0
  139. package/src/components/date-range/date-range-math.ts +105 -0
  140. package/src/components/date-range/date-range.component.ts +375 -0
  141. package/src/components/date-range/date-range.scss +272 -0
  142. package/src/components/date-range/date-range.test.ts +247 -0
  143. package/src/components/date-range/index.ts +12 -0
  144. package/src/components/datepicker/datepicker.component.ts +44 -7
  145. package/src/components/datepicker/datepicker.scss +9 -0
  146. package/src/components/datepicker/datepicker.test.ts +2 -2
  147. package/src/components/dialog/dialog.component.ts +38 -19
  148. package/src/components/dialog/dialog.scss +65 -36
  149. package/src/components/dialog/dialog.test.ts +14 -0
  150. package/src/components/diff/diff.component.ts +55 -0
  151. package/src/components/diff/diff.scss +46 -0
  152. package/src/components/diff/diff.test.ts +15 -0
  153. package/src/components/diff/index.ts +12 -0
  154. package/src/components/diff-row/diff-row.component.ts +75 -0
  155. package/src/components/diff-row/diff-row.scss +70 -0
  156. package/src/components/diff-row/diff-row.test.ts +37 -0
  157. package/src/components/diff-row/index.ts +12 -0
  158. package/src/components/dropdown/dropdown.component.ts +12 -10
  159. package/src/components/dropdown/dropdown.scss +2 -0
  160. package/src/components/editor/editor.component.ts +159 -11
  161. package/src/components/editor/editor.scss +250 -1
  162. package/src/components/editor/editor.test.ts +31 -3
  163. package/src/components/editor/modules/ai/panel/ai-panel.scss +0 -1
  164. package/src/components/editor/modules/attachment/attachment.ts +53 -31
  165. package/src/components/editor/modules/context-menu/context-menu.ts +17 -0
  166. package/src/components/editor/modules/dialog/dialog.component.ts +23 -1
  167. package/src/components/editor/modules/dialog/dialog.test.ts +28 -0
  168. package/src/components/editor/modules/emoji/emoji.ts +4 -5
  169. package/src/components/editor/modules/merge-field/merge-field.ts +83 -0
  170. package/src/components/editor/modules/toolbar/toolbar.component.ts +121 -68
  171. package/src/components/editor/modules/toolbar/toolbar.scss +38 -0
  172. package/src/components/editor/modules/toolbar/toolbar.ts +25 -1
  173. package/src/components/empty-state/empty-state.component.ts +6 -1
  174. package/src/components/empty-state/empty-state.scss +21 -4
  175. package/src/components/expanding-action/expanding-action.component.ts +22 -34
  176. package/src/components/expanding-action/expanding-action.scss +3 -45
  177. package/src/components/expanding-action/expanding-action.test.ts +2 -1
  178. package/src/components/file/file.component.ts +30 -2
  179. package/src/components/file/file.scss +6 -0
  180. package/src/components/filter-wrapper/filter-wrapper.component.ts +45 -10
  181. package/src/components/filter-wrapper/filter-wrapper.test.ts +58 -2
  182. package/src/components/flow-builder/flow-builder.component.ts +36 -3
  183. package/src/components/flow-builder/flow-builder.scss +0 -6
  184. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.component.ts +8 -0
  185. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.scss +10 -5
  186. package/src/components/flow-builder/modules/flow-node/flow-node.component.ts +17 -0
  187. package/src/components/flow-builder/modules/flow-node/flow-node.scss +10 -0
  188. package/src/components/form-actions/form-actions.component.ts +48 -2
  189. package/src/components/form-actions/form-actions.scss +1 -1
  190. package/src/components/form-group/form-group.component.ts +10 -3
  191. package/src/components/form-group/form-group.scss +37 -4
  192. package/src/components/form-group/form-group.test.ts +10 -0
  193. package/src/components/header/header.component.ts +11 -4
  194. package/src/components/header/header.scss +47 -5
  195. package/src/components/hover-container/hover-container.component.ts +4 -5
  196. package/src/components/icon/icon.component.ts +95 -9
  197. package/src/components/icon/icon.scss +70 -6
  198. package/src/components/icon/icon.test.ts +55 -0
  199. package/src/components/icon-picker/icon-picker.component.ts +35 -1
  200. package/src/components/inline-edit/inline-edit.component.ts +38 -8
  201. package/src/components/inline-edit/inline-edit.scss +128 -19
  202. package/src/components/input/input.component.ts +173 -297
  203. package/src/components/input/input.scss +65 -43
  204. package/src/components/input/input.test.ts +86 -4
  205. package/src/components/item/item.component.ts +12 -0
  206. package/src/components/item/item.scss +102 -3
  207. package/src/components/item/item.test.ts +23 -0
  208. package/src/components/key/key.component.ts +2 -2
  209. package/src/components/key/key.scss +11 -0
  210. package/src/components/key-value/index.ts +12 -0
  211. package/src/components/key-value/key-value.component.ts +160 -0
  212. package/src/components/key-value/key-value.scss +20 -0
  213. package/src/components/key-value/key-value.test.ts +22 -0
  214. package/src/components/markdown-editor/markdown-editor.component.ts +8 -1
  215. package/src/components/menu/menu.component.ts +21 -2
  216. package/src/components/menu/menu.scss +43 -23
  217. package/src/components/menu/menu.test.ts +33 -0
  218. package/src/components/menu-item/menu-item.component.ts +13 -2
  219. package/src/components/menu-item/menu-item.scss +56 -23
  220. package/src/components/menu-item/menu-item.test.ts +31 -0
  221. package/src/components/menu-item/submenu-controller.ts +25 -15
  222. package/src/components/menu-label/index.ts +12 -0
  223. package/src/components/menu-label/menu-label.component.ts +30 -0
  224. package/src/components/menu-label/menu-label.scss +18 -0
  225. package/src/components/nav-item/index.ts +12 -0
  226. package/src/components/nav-item/nav-item.component.ts +83 -0
  227. package/src/components/nav-item/nav-item.scss +128 -0
  228. package/src/components/nav-list/index.ts +12 -0
  229. package/src/components/nav-list/nav-list.component.ts +313 -0
  230. package/src/components/nav-list/nav-list.scss +87 -0
  231. package/src/components/nav-list/nav-list.test.ts +250 -0
  232. package/src/components/navbar/navbar.component.ts +235 -171
  233. package/src/components/navbar/navbar.scss +333 -169
  234. package/src/components/navbar/navbar.test.ts +63 -143
  235. package/src/components/page/page.component.ts +114 -29
  236. package/src/components/page/page.scss +223 -95
  237. package/src/components/page/page.test.ts +214 -3
  238. package/src/components/page-builder/page-builder.component.ts +51 -5
  239. package/src/components/page-builder/page-builder.scss +6 -1
  240. package/src/components/page-nav/page-nav.component.ts +25 -3
  241. package/src/components/page-nav/page-nav.scss +10 -2
  242. package/src/components/pagination/pagination.scss +1 -1
  243. package/src/components/panel/panel.component.ts +14 -13
  244. package/src/components/panel/panel.scss +14 -10
  245. package/src/components/panel/panel.test.ts +42 -0
  246. package/src/components/popup/popup.component.ts +20 -13
  247. package/src/components/popup/popup.test.ts +21 -1
  248. package/src/components/priority-list/priority-list.component.ts +157 -172
  249. package/src/components/priority-list/priority-list.scss +113 -17
  250. package/src/components/priority-list/priority-list.test.ts +301 -3
  251. package/src/components/progress-bar/progress-bar.component.ts +1 -1
  252. package/src/components/progress-bar/progress-bar.scss +16 -4
  253. package/src/components/progress-tile/progress-tile.scss +1 -1
  254. package/src/components/query-builder/query-builder.component.ts +85 -10
  255. package/src/components/query-builder/query-builder.scss +7 -1
  256. package/src/components/query-builder/query-builder.test.ts +40 -0
  257. package/src/components/radio/radio.component.ts +57 -18
  258. package/src/components/radio/radio.scss +101 -26
  259. package/src/components/radio-group/radio-group.component.ts +45 -7
  260. package/src/components/radio-group/radio-group.scss +17 -0
  261. package/src/components/radio-group/radio-group.test.ts +63 -2
  262. package/src/components/rating/rating.component.ts +23 -0
  263. package/src/components/rating/rating.scss +1 -1
  264. package/src/components/remarkd-editor/remarkd-editor.component.ts +60 -5
  265. package/src/components/remarkd-editor/remarkd-editor.scss +12 -4
  266. package/src/components/report-table/index.ts +12 -0
  267. package/src/components/report-table/report-format.test.ts +86 -0
  268. package/src/components/report-table/report-format.ts +72 -0
  269. package/src/components/report-table/report-math.test.ts +74 -0
  270. package/src/components/report-table/report-math.ts +252 -0
  271. package/src/components/report-table/report-table.component.ts +681 -0
  272. package/src/components/report-table/report-table.scss +328 -0
  273. package/src/components/report-table/report-table.test.ts +702 -0
  274. package/src/components/reveal/reveal.component.ts +15 -1
  275. package/src/components/reveal/reveal.scss +5 -0
  276. package/src/components/schedule-builder/schedule-builder.component.ts +23 -8
  277. package/src/components/scroll-container/scroll-container.component.ts +19 -1
  278. package/src/components/scroll-container/scroll-container.scss +6 -1
  279. package/src/components/select/select.component.ts +52 -36
  280. package/src/components/select/select.scss +42 -25
  281. package/src/components/select/select.test.ts +14 -2
  282. package/src/components/settings-container/settings-container.scss +18 -5
  283. package/src/components/sidebar/sidebar.component.ts +8 -1
  284. package/src/components/sidebar/sidebar.scss +6 -1
  285. package/src/components/skeleton/skeleton.component.ts +3 -3
  286. package/src/components/skeleton/skeleton.scss +6 -6
  287. package/src/components/skeleton/skeleton.test.ts +9 -0
  288. package/src/components/slash-menu/slash-menu.scss +1 -1
  289. package/src/components/slideout/slideout.component.ts +20 -3
  290. package/src/components/slideout/slideout.scss +13 -6
  291. package/src/components/slideout/slideout.test.ts +19 -0
  292. package/src/components/split-button/split-button.component.ts +1 -1
  293. package/src/components/split-pane/split-pane.component.ts +71 -8
  294. package/src/components/split-pane/split-pane.scss +145 -41
  295. package/src/components/split-pane/split-pane.test.ts +23 -3
  296. package/src/components/stat/stat.component.ts +209 -14
  297. package/src/components/stat/stat.scss +305 -5
  298. package/src/components/stat/stat.test.ts +143 -2
  299. package/src/components/status-indicator/status-indicator.scss +22 -21
  300. package/src/components/stepper/stepper.scss +3 -3
  301. package/src/components/tab/tab.component.ts +1 -0
  302. package/src/components/table/table.component.ts +12 -1
  303. package/src/components/table/table.scss +21 -13
  304. package/src/components/table/table.test.ts +17 -0
  305. package/src/components/tabs/tabs.component.ts +150 -37
  306. package/src/components/tabs/tabs.scss +48 -7
  307. package/src/components/tabs/tabs.test.ts +169 -0
  308. package/src/components/textarea/textarea.component.ts +25 -3
  309. package/src/components/textarea/textarea.scss +10 -0
  310. package/src/components/textarea/textarea.test.ts +8 -4
  311. package/src/components/theme-editor/theme-editor.scss +0 -6
  312. package/src/components/tile/tile.component.ts +32 -2
  313. package/src/components/tile/tile.scss +40 -7
  314. package/src/components/tile/tile.test.ts +10 -0
  315. package/src/components/toggle/toggle.component.ts +23 -7
  316. package/src/components/toggle/toggle.scss +36 -29
  317. package/src/components/toggle/toggle.test.ts +17 -0
  318. package/src/components/tooltip/tooltip.component.ts +3 -0
  319. package/src/components/tooltip/tooltip.scss +11 -6
  320. package/src/components/tooltip/tooltip.test.ts +12 -0
  321. package/src/components/translation-field/index.ts +12 -0
  322. package/src/components/translation-field/translation-field.component.ts +55 -0
  323. package/src/components/translation-field/translation-field.scss +101 -0
  324. package/src/components/translation-fields/index.ts +12 -0
  325. package/src/components/translation-fields/translation-fields.component.ts +80 -0
  326. package/src/components/translation-fields/translation-fields.scss +60 -0
  327. package/src/components/translation-grid/index.ts +12 -0
  328. package/src/components/translation-grid/translation-grid.component.ts +157 -0
  329. package/src/components/translation-grid/translation-grid.scss +138 -0
  330. package/src/components/translation-grid/translation-grid.test.ts +28 -0
  331. package/src/components/vertical-stepper/vertical-stepper.component.ts +35 -9
  332. package/src/components/vertical-stepper/vertical-stepper.scss +110 -39
  333. package/src/components/vertical-stepper/vertical-stepper.test.ts +35 -0
  334. package/src/components/well/well.scss +2 -2
  335. package/src/events/events.ts +2 -0
  336. package/src/events/zn-chart-click.ts +13 -0
  337. package/src/events/zn-report-change.ts +7 -0
  338. package/src/form-control.scss +36 -3
  339. package/src/internal/anchored-popover.ts +175 -0
  340. package/src/internal/animate.ts +19 -5
  341. package/src/internal/cosmic-border.ts +15 -0
  342. package/src/internal/form.ts +1 -1
  343. package/src/internal/hover-intent.ts +15 -0
  344. package/src/internal/input-modality.ts +15 -0
  345. package/src/internal/pointer-intent.ts +37 -0
  346. package/src/internal/reorder.ts +625 -0
  347. package/src/internal/scroll.ts +51 -3
  348. package/src/internal/selection-card.ts +1 -1
  349. package/src/internal/tab-indicator.ts +92 -0
  350. package/src/internal/table-sort.ts +25 -0
  351. package/src/internal/table-tabs.ts +108 -0
  352. package/src/internal/test-drag.ts +35 -0
  353. package/src/internal/theme-openbyte.test.ts +126 -0
  354. package/src/internal/theme.ts +4 -4
  355. package/src/internal/zinc-element.ts +2 -2
  356. package/src/selection-card.scss +25 -0
  357. package/src/types/web-test-runner-commands.d.ts +1 -0
  358. package/src/utilities/color.test.ts +43 -0
  359. package/src/utilities/color.ts +189 -0
  360. package/src/zinc.ts +14 -0
  361. package/.github/workflows/eleventy_build.yml +0 -42
  362. package/scss/themes/_aura.scss +0 -114
@@ -71,4 +71,46 @@ describe('<zn-panel>', () => {
71
71
 
72
72
  expect(el.shadowRoot!.querySelector('.panel')!.classList.contains('panel--empty')).to.be.false;
73
73
  });
74
+
75
+ it('shows a description under the caption in the header', async () => {
76
+ const el = await fixture<HTMLElement>(html`<zn-panel caption="Identity" description="Shown to assessors">Body</zn-panel>`);
77
+ await (el as unknown as {updateComplete: Promise<unknown>}).updateComplete;
78
+ const header = el.shadowRoot!.querySelector('zn-header')!;
79
+ await (header as unknown as {updateComplete: Promise<unknown>}).updateComplete;
80
+
81
+ expect(header.shadowRoot!.querySelector('.header__description')!.textContent).to.equal('Shown to assessors');
82
+ });
83
+
84
+ it('renders no description element without one', async () => {
85
+ const el = await fixture<HTMLElement>(html`<zn-panel caption="Identity">Body</zn-panel>`);
86
+ await (el as unknown as {updateComplete: Promise<unknown>}).updateComplete;
87
+ const header = el.shadowRoot!.querySelector('zn-header')!;
88
+ await (header as unknown as {updateComplete: Promise<unknown>}).updateComplete;
89
+
90
+ expect(header.shadowRoot!.querySelector('.header__description')).to.not.exist;
91
+ });
92
+
93
+ describe('transparent', () => {
94
+ // Safari has no overflow-clip-margin, so the room comes from a padding/negative-margin fallback there
95
+ it('leaves room outside the content for the focus ring of a flush control', async () => {
96
+ const el = await fixture(html`
97
+ <zn-panel transparent flush style="--zn-focus-ring-outset: 4px"><input></zn-panel> `);
98
+ const content = el.shadowRoot!.querySelector('.panel__content')!.getBoundingClientRect();
99
+ const input = el.querySelector('input')!.getBoundingClientRect();
100
+ const clipMargin = CSS.supports('overflow-clip-margin: 0px') ? 4 : 0;
101
+
102
+ expect(input.left - (content.left - clipMargin)).to.equal(4);
103
+ expect(input.top - (content.top - clipMargin)).to.equal(4);
104
+ });
105
+
106
+ it('keeps the bottom edge of the header clickable', async () => {
107
+ const el = await fixture(html`
108
+ <zn-panel transparent caption="Example" style="--zn-focus-ring-outset: 4px"><input></zn-panel> `);
109
+ const header = el.shadowRoot!.querySelector('.panel__header')!;
110
+ const rect = header.getBoundingClientRect();
111
+ const hit = el.shadowRoot!.elementFromPoint(rect.left + 10, rect.bottom - 2);
112
+
113
+ expect(header.contains(hit)).to.be.true;
114
+ });
115
+ });
74
116
  });
@@ -1,7 +1,7 @@
1
+ import '../../events/zn-reposition';
1
2
  import {arrow, autoUpdate, computePosition, flip, offset, platform, shift, size} from '@floating-ui/dom';
2
3
  import {classMap} from 'lit/directives/class-map.js';
3
4
  import {html, unsafeCSS} from 'lit';
4
- import {offsetParent} from 'composed-offset-position';
5
5
  import {property, query} from 'lit/decorators.js';
6
6
  import ZincElement from '../../internal/zinc-element';
7
7
  import type {CSSResultGroup} from 'lit';
@@ -65,7 +65,7 @@ export default class ZnPopup extends ZincElement {
65
65
 
66
66
  /**
67
67
  * The preferred placement of the popup. Note that the actual placement will vary as configured to keep the
68
- * panel inside of the viewport.
68
+ * panel inside the viewport.
69
69
  */
70
70
  @property({reflect: true}) placement:
71
71
  | 'top'
@@ -206,7 +206,7 @@ export default class ZnPopup extends ZincElement {
206
206
 
207
207
  disconnectedCallback() {
208
208
  super.disconnectedCallback();
209
- this.stop();
209
+ this.stop().then();
210
210
  }
211
211
 
212
212
  async updated(changedProps: Map<string, unknown>) {
@@ -214,12 +214,16 @@ export default class ZnPopup extends ZincElement {
214
214
 
215
215
  // Start or stop the positioner when active changes
216
216
  if (changedProps.has('active')) {
217
- this.active ? this.start() : this.stop();
217
+ if (this.active) {
218
+ this.start();
219
+ } else {
220
+ this.stop().then();
221
+ }
218
222
  }
219
223
 
220
224
  // Update the anchor when anchor changes
221
225
  if (changedProps.has('anchor')) {
222
- this.handleAnchorChange();
226
+ await this.handleAnchorChange();
223
227
  }
224
228
 
225
229
  // All other properties will trigger a reposition when active
@@ -255,6 +259,10 @@ export default class ZnPopup extends ZincElement {
255
259
  this.anchorEl.removeEventListener('mouseover', this.handleAnchorHover);
256
260
  this.anchorEl.addEventListener('mouseover', this.handleAnchorHover);
257
261
  }
262
+
263
+ if (this.active) {
264
+ this.start();
265
+ }
258
266
  }
259
267
 
260
268
  public handleAnchorHover = () => {
@@ -282,6 +290,9 @@ export default class ZnPopup extends ZincElement {
282
290
  this.removeAttribute('data-current-placement');
283
291
  this.style.removeProperty('--auto-size-available-width');
284
292
  this.style.removeProperty('--auto-size-available-height');
293
+ // WebKit: going display:none and leaving the top layer in one style update leaves the popup's old box as
294
+ // stale scroll overflow on its container (scrollbars flash on close), so flush style between the two.
295
+ void getComputedStyle(this.popup).display;
285
296
  this.popup.hidePopover();
286
297
  requestAnimationFrame(() => resolve());
287
298
  } else {
@@ -306,7 +317,7 @@ export default class ZnPopup extends ZincElement {
306
317
  if (this.sync) {
307
318
  middleware.push(
308
319
  size({
309
- apply: ({rects}: { rects: { reference: { width: number, height: number } } }) => {
320
+ apply: ({rects}: { rects: { reference: { width: number; height: number } } }) => {
310
321
  const syncWidth = this.sync === 'width' || this.sync === 'both';
311
322
  const syncHeight = this.sync === 'height' || this.sync === 'both';
312
323
 
@@ -350,7 +361,7 @@ export default class ZnPopup extends ZincElement {
350
361
  size({
351
362
  boundary: this.autoSizeBoundary,
352
363
  padding: this.autoSizePadding,
353
- apply: ({availableWidth, availableHeight}: { availableWidth: number, availableHeight: number }) => {
364
+ apply: ({availableWidth, availableHeight}: { availableWidth: number; availableHeight: number }) => {
354
365
  if (this.autoSize === 'vertical' || this.autoSize === 'both') {
355
366
  this.style.setProperty('--auto-size-available-height', `${availableHeight}px`);
356
367
  } else {
@@ -381,11 +392,6 @@ export default class ZnPopup extends ZincElement {
381
392
  );
382
393
  }
383
394
 
384
- const getOffsetParent =
385
- this.strategy === 'absolute'
386
- ? (element: Element) => platform.getOffsetParent(element, offsetParent)
387
- : platform.getOffsetParent;
388
-
389
395
  // A closed popover has no size to measure, so it would first land on top of its anchor
390
396
  if (this.popup.isConnected && !this.popup.matches(':popover-open')) {
391
397
  this.popup.style.visibility = 'hidden';
@@ -396,9 +402,10 @@ export default class ZnPopup extends ZincElement {
396
402
  placement: this.placement,
397
403
  middleware,
398
404
  strategy: this.strategy,
405
+ // The popup is a popover in the top layer, so the page, not any ancestor, is what it is positioned within
399
406
  platform: {
400
407
  ...platform,
401
- getOffsetParent
408
+ getOffsetParent: () => window
402
409
  }
403
410
  }).then(({x, y, middlewareData, placement}:
404
411
  { x: number; y: number; middlewareData: MiddlewareData; placement: string }) => {
@@ -1,5 +1,6 @@
1
1
  import '../../../dist/zn.min.js';
2
- import { expect, fixture, html } from '@open-wc/testing';
2
+ import { expect, fixture, html, oneEvent } from '@open-wc/testing';
3
+ import type ZnPopup from './popup.component';
3
4
 
4
5
  describe('<zn-popup>', () => {
5
6
  it('should render a component', async () => {
@@ -7,4 +8,23 @@ describe('<zn-popup>', () => {
7
8
 
8
9
  expect(el).to.exist;
9
10
  });
11
+
12
+ it('keeps following a new anchor set while open', async () => {
13
+ const wrapper = await fixture(html`
14
+ <div>
15
+ <button id="first">First</button>
16
+ <button id="second">Second</button>
17
+ <zn-popup anchor="first" active><div>Content</div></zn-popup>
18
+ </div>`);
19
+ const popup = wrapper.querySelector<ZnPopup>('zn-popup')!;
20
+ await oneEvent(popup, 'zn-reposition');
21
+
22
+ popup.anchor = 'second';
23
+ await oneEvent(popup, 'zn-reposition');
24
+
25
+ // The positioner repositions on window resize; a stopped one never would
26
+ const repositioned = oneEvent(popup, 'zn-reposition');
27
+ window.dispatchEvent(new Event('resize'));
28
+ await repositioned;
29
+ });
10
30
  });
@@ -1,4 +1,5 @@
1
1
  import {classMap} from "lit/directives/class-map.js";
2
+ import {clipCornerRadius, ReorderController} from '../../internal/reorder';
2
3
  import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from 'lit';
3
4
  import {
4
5
  FormControlController,
@@ -8,6 +9,7 @@ import {HasSlotController} from '../../internal/slot';
8
9
  import {property, query, state} from 'lit/decorators.js';
9
10
  import {repeat} from "lit/directives/repeat.js";
10
11
  import ZincElement from '../../internal/zinc-element';
12
+ import ZnButton from '../button';
11
13
  import ZnIcon from '../icon';
12
14
  import type {ZincFormControl} from '../../internal/zinc-element';
13
15
 
@@ -35,11 +37,14 @@ function safeParse(value: string): unknown {
35
37
  * @status experimental
36
38
  * @since 1.0
37
39
  *
40
+ * @dependency zn-button
38
41
  * @dependency zn-icon
39
42
  *
40
43
  * @slot - The default slot where list items are placed. Each slotted element must have a `value` attribute that
41
44
  * uniquely identifies the item. The component automatically assigns slot names to project each item into the
42
45
  * correct position. The item's position in the list determines its priority (top = `priority-start`, incrementing).
46
+ * Use `zn-tile` items for titled rows with their own actions. Set `locked` on an item to pin it where it is.
47
+ * @slot action-{value} - Controls shown at the end of the item with that `value`.
43
48
  * @slot label - The component's label. Required for proper accessibility. Alternatively, use the `label` attribute.
44
49
  * @slot label-tooltip - Used to add text displayed in a tooltip next to the label.
45
50
  * @slot help-text - Text that describes how to use the component. Alternatively, use the `help-text` attribute.
@@ -66,6 +71,7 @@ function safeParse(value: string): unknown {
66
71
  */
67
72
  export default class ZnPriorityList extends ZincElement implements ZincFormControl {
68
73
  static dependencies = {
74
+ 'zn-button': ZnButton,
69
75
  'zn-icon': ZnIcon,
70
76
  };
71
77
 
@@ -90,6 +96,13 @@ export default class ZnPriorityList extends ZincElement implements ZincFormContr
90
96
 
91
97
  private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
92
98
 
99
+ private readonly reorderController: ReorderController = new ReorderController(this, {
100
+ formControl: this.formControlController,
101
+ fallbackFields: () => this.name ? this.getPriorityMap().map(({key, priority}) => [`${this.name}[${key}]`, String(priority)]) : [],
102
+ modifier: 'alt',
103
+ focus: key => this.reorderRow(key)?.focus(),
104
+ });
105
+
93
106
  @query('.priority-list__hidden-inputs') hiddenInputsContainer: HTMLDivElement;
94
107
 
95
108
  /**
@@ -118,6 +131,9 @@ export default class ZnPriorityList extends ZincElement implements ZincFormContr
118
131
 
119
132
  @state() defaultValue: string[] = [];
120
133
 
134
+ /** The item holding the list's single tab stop. */
135
+ @state() private _focusKey = '';
136
+
121
137
  /** The component's label. Required for proper accessibility. */
122
138
  @property() label = '';
123
139
 
@@ -144,6 +160,23 @@ export default class ZnPriorityList extends ZincElement implements ZincFormContr
144
160
  /** The starting priority number. Defaults to 1. */
145
161
  @property({type: Number, attribute: 'priority-start'}) priorityStart = 1;
146
162
 
163
+ /** Draws the items as rows divided edge to edge, for a list filling a flush panel; the label is kept for
164
+ * screen readers only. */
165
+ @property({type: Boolean, reflect: true}) flush = false;
166
+
167
+ /** Lets a click or Enter select an item as well as drag it, for a list that doubles as navigation. The click is
168
+ * passed on to the item, so a `tab` or link on it acts as if clicked. */
169
+ @property({type: Boolean, reflect: true}) selectable = false;
170
+
171
+ /** The selected item's value. An item marked active by zn-tabs takes precedence, so the list follows its tabs. */
172
+ @property({reflect: true}) selected = '';
173
+
174
+ /** Hides each item's priority number, for lists where the order shows the priority. */
175
+ @property({type: Boolean, attribute: 'hide-priority', reflect: true}) hidePriority = false;
176
+
177
+ /** Shows move up and move down buttons on each item. */
178
+ @property({type: Boolean, attribute: 'move-buttons', reflect: true}) moveButtons = false;
179
+
147
180
  /**
148
181
  * When set, the associated form will be submitted with the given action URL whenever items are reordered.
149
182
  * If set to an empty string, the form will be submitted using its existing action.
@@ -157,11 +190,6 @@ export default class ZnPriorityList extends ZincElement implements ZincFormContr
157
190
  */
158
191
  @property() order = '';
159
192
 
160
- // Drag state
161
- @state() private draggedKey: string | null = null;
162
- @state() private isDragging = false;
163
- private dragOverKey: string | null = null;
164
-
165
193
  /** Gets the validity state object. */
166
194
  get validity(): ValidityState {
167
195
  return validValidityState;
@@ -172,9 +200,21 @@ export default class ZnPriorityList extends ZincElement implements ZincFormContr
172
200
  return '';
173
201
  }
174
202
 
203
+ private _itemObserver = new MutationObserver(() => this.requestUpdate());
204
+
175
205
  connectedCallback() {
176
206
  super.connectedCallback();
177
207
  this.defaultValue = [...this._value];
208
+ this._itemObserver.observe(this, {
209
+ childList: true,
210
+ subtree: true,
211
+ attributeFilter: ['locked', 'class'],
212
+ });
213
+ }
214
+
215
+ disconnectedCallback() {
216
+ super.disconnectedCallback();
217
+ this._itemObserver.disconnect();
178
218
  }
179
219
 
180
220
  protected firstUpdated(_changedProperties: PropertyValues) {
@@ -267,188 +307,108 @@ export default class ZnPriorityList extends ZincElement implements ZincFormContr
267
307
  });
268
308
  }
269
309
 
270
- /**
271
- * Moves an item from one index to another.
272
- */
273
- private _moveItem(fromIndex: number, toIndex: number) {
274
- if (fromIndex === toIndex) return;
275
- if (fromIndex < 0 || toIndex < 0) return;
276
- if (fromIndex >= this._value.length || toIndex >= this._value.length) return;
310
+ private _item(key: string) {
311
+ return this.querySelector<HTMLElement>(`:scope > [value="${CSS.escape(key)}"]`);
312
+ }
277
313
 
278
- const newOrder = [...this._value];
279
- const [moved] = newOrder.splice(fromIndex, 1);
280
- newOrder.splice(toIndex, 0, moved);
314
+ /** @internal ReorderHost: the items' keys in order. */
315
+ reorderKeys() {
316
+ return [...this._value];
317
+ }
281
318
 
282
- this._value = newOrder;
319
+ /** @internal ReorderHost: renders the items in a new order. */
320
+ applyReorder(keys: string[]) {
321
+ this._value = keys;
283
322
  this._assignSlotNames();
284
323
  this._syncHiddenInputs();
285
324
  this.requestUpdate();
286
-
287
- this.emit('zn-change');
288
- this.emit('zn-reorder');
289
- this._submitForm();
290
325
  }
291
326
 
292
- /**
293
- * Submits the associated form if a `formaction` attribute is set.
294
- */
295
- private _submitForm() {
296
- if (this.formAction === undefined || this.formAction === null) return;
297
-
298
- const form = this.getForm();
299
- if (form) {
300
- // Submitting through the controller announces the form before it is
301
- // submitted, which is what lets a host intercept the submit: a submit
302
- // event raised on a form inside a shadow root never reaches the document,
303
- // so submitting it directly navigates the page instead.
304
- const submitter = document.createElement('input');
305
- submitter.type = 'submit';
306
- if (this.formAction) {
307
- submitter.setAttribute('formaction', this.formAction);
308
- }
309
- this.formControlController.submit(submitter);
310
- } else if (this.formAction) {
311
- // No parent form — create a temporary one to submit the priority data
312
- const tempForm = document.createElement('form');
313
- tempForm.method = 'post';
314
- tempForm.action = this.formAction;
315
- tempForm.hidden = true;
316
-
317
- this._value.forEach((key, index) => {
318
- if (this.name) {
319
- const input = document.createElement('input');
320
- input.type = 'hidden';
321
- input.name = `${this.name}[${key}]`;
322
- input.value = String(this.priorityStart + index);
323
- tempForm.appendChild(input);
324
- }
325
- });
326
-
327
- document.body.appendChild(tempForm);
328
- // requestSubmit raises a cancellable submit event, so a host can handle
329
- // the submission rather than the browser navigating to formaction.
330
- tempForm.requestSubmit();
331
- tempForm.remove();
332
- }
327
+ /** @internal ReorderHost: the row drawn for an item. */
328
+ reorderRow(key: string) {
329
+ const index = this._value.indexOf(key);
330
+ return this.shadowRoot?.querySelectorAll<HTMLElement>('.priority-list__item')[index] ?? null;
333
331
  }
334
332
 
335
- // --- Drag and Drop Handlers ---
336
-
337
- private _handleDragStart(event: DragEvent, key: string) {
338
- if (this.disabled) {
339
- event.preventDefault();
340
- return;
341
- }
342
-
343
- this.draggedKey = key;
344
- this.isDragging = true;
345
-
346
- if (event.dataTransfer) {
347
- event.dataTransfer.effectAllowed = 'move';
348
- event.dataTransfer.setData('text/plain', key);
349
- }
333
+ /** @internal ReorderHost: whether an item is pinned where it is. */
334
+ reorderLocked(key: string) {
335
+ return this._item(key)?.hasAttribute('locked') ?? false;
350
336
  }
351
337
 
352
- private _handleDragEnd() {
353
- if (this.isDragging) {
354
- // Emit events for the final position change
355
- this._syncHiddenInputs();
356
- this.emit('zn-change');
357
- this.emit('zn-reorder');
358
- this._submitForm();
338
+ private async _handleStep(event: Event, key: string, step: number) {
339
+ event.stopPropagation();
340
+ const button = event.currentTarget as ZnButton;
341
+ const hadFocus = button.matches(':focus-within');
342
+ if (!this.reorderController.step(key, step) || !hadFocus) return;
343
+
344
+ // Moving the row can detach it, which drops focus even when the button stays enabled
345
+ await this.updateComplete;
346
+ const target = [button, ...button.parentElement!.querySelectorAll<ZnButton>('zn-button')].find(b => !b.disabled);
347
+ if (target) {
348
+ target.button.focus();
349
+ } else {
350
+ this.reorderRow(key)?.focus();
359
351
  }
360
-
361
- this.draggedKey = null;
362
- this.dragOverKey = null;
363
- this.isDragging = false;
364
352
  }
365
353
 
366
- private _handleDragOver(event: DragEvent, key: string) {
367
- event.preventDefault();
368
-
369
- if (!this.draggedKey || this.draggedKey === key) return;
370
-
371
- if (event.dataTransfer) {
372
- event.dataTransfer.dropEffect = 'move';
373
- }
374
-
375
- // Move the item in real-time so other items shift around
376
- const fromIndex = this._value.indexOf(this.draggedKey);
377
- const toIndex = this._value.indexOf(key);
378
-
379
- if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) return;
380
-
381
- const newOrder = [...this._value];
382
- const [moved] = newOrder.splice(fromIndex, 1);
383
- newOrder.splice(toIndex, 0, moved);
384
- this._value = newOrder;
385
- this._assignSlotNames();
386
- this.requestUpdate();
354
+ // --- Keyboard Handlers ---
387
355
 
388
- this.dragOverKey = key;
356
+ private _selectedKey(): string {
357
+ if (!this.selectable) return '';
358
+ const active = this._getSlottedItems().find(el => el.classList.contains('zn-tb-active'));
359
+ return active?.getAttribute('value') || this.selected;
389
360
  }
390
361
 
391
- private _handleDragLeave(event: DragEvent, key: string) {
392
- const related = event.relatedTarget as Node | null;
393
- const target = event.currentTarget as HTMLElement;
394
- if (related && target.contains(related)) return;
362
+ private _select(key: string) {
363
+ if (!this.selectable) return;
364
+ this.selected = key;
365
+ const item = this._item(key);
366
+ if (!item) return;
367
+ item.click();
368
+ this.emit('zn-select', {detail: {item}});
369
+ }
395
370
 
396
- if (this.dragOverKey === key) {
397
- this.dragOverKey = null;
371
+ private _matchClipCorners(row: HTMLElement) {
372
+ const radius = clipCornerRadius(this, row);
373
+ if (radius) {
374
+ row.style.setProperty('--zn-priority-list-drag-radius', radius);
375
+ } else {
376
+ row.style.removeProperty('--zn-priority-list-drag-radius');
398
377
  }
399
378
  }
400
379
 
401
- private _handleDrop(event: DragEvent) {
402
- event.preventDefault();
403
- // Item is already in position from real-time dragover moves
404
- // dragend handles cleanup and event emission
380
+ private _handleItemClick(event: MouseEvent, key: string) {
381
+ const path = event.composedPath();
382
+ // An action is its own control, and the click _select passes on to the item bubbles back through the row.
383
+ if (path.some(el => el instanceof HTMLElement && el.classList.contains('priority-list__actions'))
384
+ || path.includes(this._item(key)!)) {
385
+ return;
386
+ }
387
+ this._select(key);
405
388
  }
406
389
 
407
- // --- Keyboard Handlers ---
408
-
409
390
  private _handleKeyDown(event: KeyboardEvent, key: string) {
410
391
  if (this.disabled) return;
411
392
 
412
- const index = this._value.indexOf(key);
413
- if (index === -1) return;
414
-
415
- let newIndex: number | null = null;
416
-
417
- switch (event.key) {
418
- case 'ArrowUp':
419
- if (index > 0) {
420
- newIndex = index - 1;
421
- }
422
- break;
423
- case 'ArrowDown':
424
- if (index < this._value.length - 1) {
425
- newIndex = index + 1;
426
- }
427
- break;
428
- case 'Home':
429
- if (index > 0) {
430
- newIndex = 0;
431
- }
432
- break;
433
- case 'End':
434
- if (index < this._value.length - 1) {
435
- newIndex = this._value.length - 1;
436
- }
437
- break;
438
- default:
439
- return;
393
+ if (this.selectable && (event.key === 'Enter' || event.key === ' ') && event.target === event.currentTarget) {
394
+ event.preventDefault();
395
+ this._select(key);
396
+ return;
440
397
  }
441
398
 
442
- if (newIndex !== null) {
443
- event.preventDefault();
444
- this._moveItem(index, newIndex);
399
+ if (this.reorderController.handleKeyDown(event, key) || event.target !== event.currentTarget) return;
445
400
 
446
- // Focus the moved item after re-render
447
- this.updateComplete.then(() => {
448
- const items = this.shadowRoot?.querySelectorAll<HTMLElement>('.priority-list__item');
449
- items?.[newIndex!]?.focus();
450
- });
451
- }
401
+ const index = this._value.indexOf(key);
402
+ const next = {
403
+ ArrowUp: index - 1,
404
+ ArrowDown: index + 1,
405
+ Home: 0,
406
+ End: this._value.length - 1,
407
+ }[event.key];
408
+ if (next === undefined) return;
409
+
410
+ event.preventDefault();
411
+ this.reorderRow(this._value[Math.max(0, Math.min(next, this._value.length - 1))])?.focus();
452
412
  }
453
413
 
454
414
  // --- Slot Change Handler ---
@@ -514,9 +474,18 @@ export default class ZnPriorityList extends ZincElement implements ZincFormContr
514
474
  if (changedProperties.has('value') || changedProperties.has('name') || changedProperties.has('priorityStart')) {
515
475
  this._syncHiddenInputs();
516
476
  }
477
+
478
+ // Re-measured on every render, as the placeholder moves into and out of the panel's corners during a drag.
479
+ // Runs before the reorder slide starts, so the row is measured in its final place.
480
+ const placeholder = this.shadowRoot?.querySelector<HTMLElement>('.priority-list__item--dragging');
481
+ if (placeholder) {
482
+ this._matchClipCorners(placeholder);
483
+ }
517
484
  }
518
485
 
519
486
  render() {
487
+ const selectedKey = this._selectedKey();
488
+ const tabStop = [this._focusKey, selectedKey].find(key => key && this._value.includes(key)) ?? this._value[0];
520
489
  const hasLabelSlot = this.hasSlotController.test('label');
521
490
  const hasLabelTooltipSlot = this.hasSlotController.test('label-tooltip');
522
491
  const hasHelpTextSlot = this.hasSlotController.test('help-text');
@@ -562,42 +531,58 @@ export default class ZnPriorityList extends ZincElement implements ZincFormContr
562
531
  class=${classMap({
563
532
  'priority-list': true,
564
533
  'priority-list--disabled': this.disabled,
565
- 'priority-list--drag-active': this.isDragging,
534
+ 'priority-list--drag-active': this.reorderController.isDragging,
566
535
  })}
567
536
  role="listbox"
568
537
  aria-label=${this.label || 'Reorderable list'}
569
538
  aria-orientation="vertical">
570
- ${repeat(this._value, (key) => key, (key, index) => html`
539
+ ${repeat(this._value, (key) => key, (key, index) => {
540
+ const item = this._item(key);
541
+ const locked = item?.hasAttribute('locked') ?? false;
542
+ const caption = item?.getAttribute('caption') || key;
543
+ return html`
571
544
  <div
572
545
  part="item"
573
546
  class=${classMap({
574
547
  'priority-list__item': true,
575
- 'priority-list__item--dragging': this.draggedKey === key,
548
+ 'priority-list__item--dragging': this.reorderController.draggedKey === key,
576
549
  'priority-list__item--disabled': this.disabled,
550
+ 'priority-list__item--locked': locked,
551
+ 'priority-list__item--selectable': this.selectable,
552
+ 'priority-list__item--selected': key === selectedKey,
577
553
  })}
578
554
  role="option"
579
- tabindex=${this.disabled ? '-1' : '0'}
580
- aria-selected="false"
581
- aria-label="${key} - priority ${this.priorityStart + index}"
582
- draggable=${this.disabled ? 'false' : 'true'}
583
- @dragstart=${(e: DragEvent) => this._handleDragStart(e, key)}
584
- @dragend=${this._handleDragEnd}
585
- @dragover=${(e: DragEvent) => this._handleDragOver(e, key)}
586
- @dragleave=${(e: DragEvent) => this._handleDragLeave(e, key)}
587
- @drop=${(e: DragEvent) => this._handleDrop(e)}
555
+ tabindex=${!this.disabled && key === tabStop ? '0' : '-1'}
556
+ aria-selected=${key === selectedKey ? 'true' : 'false'}
557
+ aria-label="${caption} - priority ${this.priorityStart + index}"
558
+ @pointerdown=${(e: PointerEvent) => this.reorderController.handlePointerDown(e, key)}
559
+ @focus=${() => { this._focusKey = key; }}
560
+ @click=${(e: MouseEvent) => this._handleItemClick(e, key)}
588
561
  @keydown=${(e: KeyboardEvent) => this._handleKeyDown(e, key)}>
589
562
  <span class="priority-list__handle" part="drag-handle">
590
- <zn-icon src="drag_indicator" size="16"></zn-icon>
563
+ <zn-icon src="${locked ? 'lock@lu' : 'drag_indicator'}" size="16"></zn-icon>
591
564
  </span>
592
565
  <span class="priority-list__priority" part="priority">${this.priorityStart + index}</span>
593
566
  <span class="priority-list__content" part="content">
594
567
  <slot name="_item-${key}"></slot>
595
568
  </span>
596
569
  <span class="priority-list__actions" part="actions">
570
+ ${this.moveButtons && !locked ? html`
571
+ <span class="priority-list__move">
572
+ <zn-button icon="chevron-up@lu" icon-size="18" icon-button="small" plain label="Move up"
573
+ tooltip="Move up"
574
+ ?disabled=${this.disabled || !this.reorderController.canStep(key, -1)}
575
+ @click=${(e: Event) => this._handleStep(e, key, -1)}></zn-button>
576
+ <zn-button icon="chevron-down@lu" icon-size="18" icon-button="small" plain label="Move down"
577
+ tooltip="Move down"
578
+ ?disabled=${this.disabled || !this.reorderController.canStep(key, 1)}
579
+ @click=${(e: Event) => this._handleStep(e, key, 1)}></zn-button>
580
+ </span>` : null}
597
581
  <slot name="action-${key}"></slot>
598
582
  </span>
599
583
  </div>
600
- `)}
584
+ `;
585
+ })}
601
586
  </div>
602
587
 
603
588
  <!-- Hidden default slot to capture light DOM children for initial detection -->