@kubex/zinc 1.1.185 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/.browserslistrc +2 -0
  2. package/.github/workflows/js_build_and_deploy.yaml +54 -1
  3. package/.github/workflows/test.yaml +27 -0
  4. package/dist/chunks/zn.UWJLIXDT.js +1 -0
  5. package/dist/custom-elements.json +9998 -2345
  6. package/dist/vscode.html-custom-data.json +1022 -67
  7. package/dist/web-types.json +2129 -177
  8. package/dist/zn.d.ts +2293 -416
  9. package/dist/zn.min.css +1 -1
  10. package/dist/zn.min.js +2310 -1637
  11. package/docs/_includes/default.njk +4 -1
  12. package/docs/assets/scripts/docs.js +22 -6
  13. package/docs/assets/styles/docs.css +9 -2
  14. package/docs/data/report-table-day-compare.json +380 -0
  15. package/docs/data/report-table.json +314 -0
  16. package/docs/pages/components/avatar-group.md +57 -0
  17. package/docs/pages/components/badge.md +67 -0
  18. package/docs/pages/components/button.md +41 -1
  19. package/docs/pages/components/chat-message.md +16 -0
  20. package/docs/pages/components/checkbox.md +1 -1
  21. package/docs/pages/components/chip.md +25 -13
  22. package/docs/pages/components/color-picker.md +37 -0
  23. package/docs/pages/components/copy-button.md +2 -2
  24. package/docs/pages/components/data-table-filter.md +33 -50
  25. package/docs/pages/components/data-table-search.md +4 -4
  26. package/docs/pages/components/data-table.md +27 -3
  27. package/docs/pages/components/date-range.md +116 -0
  28. package/docs/pages/components/dialog.md +29 -37
  29. package/docs/pages/components/diff-row.md +28 -0
  30. package/docs/pages/components/diff.md +46 -0
  31. package/docs/pages/components/editor.md +55 -0
  32. package/docs/pages/components/icon.md +46 -1
  33. package/docs/pages/components/input.md +30 -11
  34. package/docs/pages/components/item.md +35 -0
  35. package/docs/pages/components/key-value.md +25 -0
  36. package/docs/pages/components/menu.md +74 -7
  37. package/docs/pages/components/nav-item.md +15 -0
  38. package/docs/pages/components/nav-list.md +132 -0
  39. package/docs/pages/components/navbar.md +100 -1
  40. package/docs/pages/components/page.md +242 -74
  41. package/docs/pages/components/priority-list.md +43 -5
  42. package/docs/pages/components/query-builder.md +23 -0
  43. package/docs/pages/components/radio-group.md +33 -1
  44. package/docs/pages/components/radio.md +1 -1
  45. package/docs/pages/components/report-table.md +202 -0
  46. package/docs/pages/components/stat.md +30 -0
  47. package/docs/pages/components/tile.md +11 -0
  48. package/docs/pages/components/tooltip.md +1 -1
  49. package/docs/pages/components/translation-grid.md +86 -0
  50. package/docs/pages/components/vertical-stepper.md +108 -64
  51. package/package.json +2 -1
  52. package/scss/_global-spacing.scss +2 -2
  53. package/scss/_root.scss +11 -171
  54. package/scss/boot.scss +14 -0
  55. package/scss/mixins/_helper-mixins.scss +24 -2
  56. package/scss/mixins/_motion-mixins.scss +126 -0
  57. package/scss/mixins/_spacing-mixins.scss +4 -3
  58. package/scss/mixins/_tab-indicator-mixins.scss +12 -0
  59. package/scss/mixins/_typography-mixins.scss +1 -1
  60. package/scss/mixins/index.scss +14 -0
  61. package/scss/shared/_base.scss +32 -1
  62. package/scss/shared/_button-mixin.scss +7 -5
  63. package/scss/shared/_table.scss +35 -4
  64. package/scss/shared/nav.scss +4 -0
  65. package/scss/shared/shimmer.scss +1 -1
  66. package/scss/table-controls.scss +206 -0
  67. package/scss/themes/_dark.scss +1 -3
  68. package/scss/themes/_index.scss +1 -1
  69. package/scss/themes/_light.scss +46 -6
  70. package/scss/themes/_modes.scss +11 -11
  71. package/scss/themes/_openbyte.scss +944 -0
  72. package/scss/themes/_typography.scss +4 -2
  73. package/scss/variables/index.scss +0 -9
  74. package/src/components/alert/alert.component.ts +14 -4
  75. package/src/components/alert/alert.scss +55 -69
  76. package/src/components/alert/alert.test.ts +8 -0
  77. package/src/components/animated-button/animated-button.component.ts +3 -2
  78. package/src/components/animated-button/animated-button.scss +6 -23
  79. package/src/components/animated-button/animated-button.test.ts +4 -3
  80. package/src/components/avatar-group/avatar-group.component.ts +84 -0
  81. package/src/components/avatar-group/avatar-group.scss +54 -0
  82. package/src/components/avatar-group/avatar-group.test.ts +59 -0
  83. package/src/components/avatar-group/index.ts +12 -0
  84. package/src/components/badge/badge.component.ts +49 -0
  85. package/src/components/badge/badge.scss +80 -0
  86. package/src/components/badge/badge.test.ts +34 -0
  87. package/src/components/badge/index.ts +12 -0
  88. package/src/components/bulk-actions/bulk-actions.component.ts +14 -2
  89. package/src/components/button/button.component.ts +228 -8
  90. package/src/components/button/button.scss +115 -32
  91. package/src/components/button/button.test.ts +122 -0
  92. package/src/components/button-menu/button-menu.component.ts +32 -11
  93. package/src/components/button-menu/button-menu.scss +5 -1
  94. package/src/components/channel-tile/channel-tile.component.ts +34 -27
  95. package/src/components/channel-tile/channel-tile.scss +76 -43
  96. package/src/components/chart/builders.test.ts +46 -0
  97. package/src/components/chart/builders.ts +27 -3
  98. package/src/components/chart/chart-live.test.ts +4 -6
  99. package/src/components/chart/chart.component.ts +125 -5
  100. package/src/components/chart/chart.scss +121 -0
  101. package/src/components/chart/chart.test.ts +81 -0
  102. package/src/components/chat-message/chat-message.component.ts +21 -1
  103. package/src/components/chat-message/chat-message.scss +13 -2
  104. package/src/components/chat-message/chat-message.test.ts +9 -0
  105. package/src/components/checkbox/checkbox.component.ts +61 -27
  106. package/src/components/checkbox/checkbox.scss +58 -25
  107. package/src/components/checkbox-group/checkbox-group.component.ts +7 -1
  108. package/src/components/chip/chip.component.ts +11 -1
  109. package/src/components/chip/chip.scss +91 -26
  110. package/src/components/chip/chip.test.ts +17 -0
  111. package/src/components/collapsible/collapsible.component.ts +17 -3
  112. package/src/components/collapsible/collapsible.scss +19 -10
  113. package/src/components/color-picker/color-picker.component.ts +277 -0
  114. package/src/components/color-picker/color-picker.scss +141 -0
  115. package/src/components/color-picker/color-picker.test.ts +57 -0
  116. package/src/components/color-picker/index.ts +12 -0
  117. package/src/components/cols/cols.component.ts +10 -0
  118. package/src/components/cols/cols.test.ts +17 -0
  119. package/src/components/confirm/confirm.component.ts +20 -6
  120. package/src/components/confirm/confirm.test.ts +52 -0
  121. package/src/components/content-block/content-block.component.ts +38 -9
  122. package/src/components/content-block/content-block.scss +11 -5
  123. package/src/components/content-block/content-block.test.ts +26 -0
  124. package/src/components/copy-button/copy-button.component.ts +2 -2
  125. package/src/components/copy-button/copy-button.scss +17 -4
  126. package/src/components/data-select/data-select.component.ts +3 -1
  127. package/src/components/data-table/data-table.component.ts +362 -140
  128. package/src/components/data-table/data-table.scss +133 -60
  129. package/src/components/data-table/data-table.test.ts +356 -4
  130. package/src/components/data-table-filter/data-table-filter.component.ts +426 -406
  131. package/src/components/data-table-filter/data-table-filter.scss +176 -148
  132. package/src/components/data-table-filter/data-table-filter.test.ts +271 -273
  133. package/src/components/data-table-search/data-table-search.component.ts +4 -2
  134. package/src/components/data-table-search/data-table-search.scss +8 -1
  135. package/src/components/data-table-search/data-table-search.test.ts +29 -0
  136. package/src/components/date-range/date-range-math.test.ts +78 -0
  137. package/src/components/date-range/date-range-math.ts +105 -0
  138. package/src/components/date-range/date-range.component.ts +375 -0
  139. package/src/components/date-range/date-range.scss +272 -0
  140. package/src/components/date-range/date-range.test.ts +247 -0
  141. package/src/components/date-range/index.ts +12 -0
  142. package/src/components/datepicker/datepicker.component.ts +44 -7
  143. package/src/components/datepicker/datepicker.scss +9 -0
  144. package/src/components/datepicker/datepicker.test.ts +2 -2
  145. package/src/components/dialog/dialog.component.ts +38 -19
  146. package/src/components/dialog/dialog.scss +65 -36
  147. package/src/components/dialog/dialog.test.ts +14 -0
  148. package/src/components/diff/diff.component.ts +55 -0
  149. package/src/components/diff/diff.scss +46 -0
  150. package/src/components/diff/diff.test.ts +15 -0
  151. package/src/components/diff/index.ts +12 -0
  152. package/src/components/diff-row/diff-row.component.ts +75 -0
  153. package/src/components/diff-row/diff-row.scss +70 -0
  154. package/src/components/diff-row/diff-row.test.ts +37 -0
  155. package/src/components/diff-row/index.ts +12 -0
  156. package/src/components/dropdown/dropdown.component.ts +12 -10
  157. package/src/components/dropdown/dropdown.scss +2 -0
  158. package/src/components/editor/editor.component.ts +159 -11
  159. package/src/components/editor/editor.scss +250 -1
  160. package/src/components/editor/editor.test.ts +31 -3
  161. package/src/components/editor/modules/ai/panel/ai-panel.scss +0 -1
  162. package/src/components/editor/modules/attachment/attachment.ts +53 -31
  163. package/src/components/editor/modules/context-menu/context-menu.ts +17 -0
  164. package/src/components/editor/modules/dialog/dialog.component.ts +23 -1
  165. package/src/components/editor/modules/dialog/dialog.test.ts +28 -0
  166. package/src/components/editor/modules/emoji/emoji.ts +4 -5
  167. package/src/components/editor/modules/merge-field/merge-field.ts +83 -0
  168. package/src/components/editor/modules/toolbar/toolbar.component.ts +121 -68
  169. package/src/components/editor/modules/toolbar/toolbar.scss +38 -0
  170. package/src/components/editor/modules/toolbar/toolbar.ts +25 -1
  171. package/src/components/empty-state/empty-state.component.ts +6 -1
  172. package/src/components/empty-state/empty-state.scss +21 -4
  173. package/src/components/expanding-action/expanding-action.component.ts +22 -34
  174. package/src/components/expanding-action/expanding-action.scss +3 -45
  175. package/src/components/expanding-action/expanding-action.test.ts +2 -1
  176. package/src/components/file/file.component.ts +30 -2
  177. package/src/components/file/file.scss +6 -0
  178. package/src/components/flow-builder/flow-builder.component.ts +36 -3
  179. package/src/components/flow-builder/flow-builder.scss +0 -6
  180. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.component.ts +8 -0
  181. package/src/components/flow-builder/modules/flow-canvas/flow-canvas.scss +10 -5
  182. package/src/components/flow-builder/modules/flow-node/flow-node.component.ts +17 -0
  183. package/src/components/flow-builder/modules/flow-node/flow-node.scss +10 -0
  184. package/src/components/form-actions/form-actions.component.ts +48 -2
  185. package/src/components/form-actions/form-actions.scss +1 -1
  186. package/src/components/form-group/form-group.component.ts +10 -3
  187. package/src/components/form-group/form-group.scss +37 -4
  188. package/src/components/form-group/form-group.test.ts +10 -0
  189. package/src/components/header/header.component.ts +11 -4
  190. package/src/components/header/header.scss +47 -5
  191. package/src/components/hover-container/hover-container.component.ts +4 -5
  192. package/src/components/icon/icon.component.ts +95 -9
  193. package/src/components/icon/icon.scss +70 -6
  194. package/src/components/icon/icon.test.ts +55 -0
  195. package/src/components/icon-picker/icon-picker.component.ts +35 -1
  196. package/src/components/inline-edit/inline-edit.component.ts +38 -8
  197. package/src/components/inline-edit/inline-edit.scss +128 -19
  198. package/src/components/input/input.component.ts +173 -297
  199. package/src/components/input/input.scss +65 -43
  200. package/src/components/input/input.test.ts +86 -4
  201. package/src/components/item/item.component.ts +12 -0
  202. package/src/components/item/item.scss +102 -3
  203. package/src/components/item/item.test.ts +23 -0
  204. package/src/components/key/key.component.ts +2 -2
  205. package/src/components/key/key.scss +11 -0
  206. package/src/components/key-value/index.ts +12 -0
  207. package/src/components/key-value/key-value.component.ts +160 -0
  208. package/src/components/key-value/key-value.scss +20 -0
  209. package/src/components/key-value/key-value.test.ts +22 -0
  210. package/src/components/markdown-editor/markdown-editor.component.ts +8 -1
  211. package/src/components/menu/menu.component.ts +21 -2
  212. package/src/components/menu/menu.scss +43 -23
  213. package/src/components/menu/menu.test.ts +33 -0
  214. package/src/components/menu-item/menu-item.component.ts +13 -2
  215. package/src/components/menu-item/menu-item.scss +56 -23
  216. package/src/components/menu-item/menu-item.test.ts +31 -0
  217. package/src/components/menu-item/submenu-controller.ts +25 -15
  218. package/src/components/menu-label/index.ts +12 -0
  219. package/src/components/menu-label/menu-label.component.ts +30 -0
  220. package/src/components/menu-label/menu-label.scss +18 -0
  221. package/src/components/nav-item/index.ts +12 -0
  222. package/src/components/nav-item/nav-item.component.ts +83 -0
  223. package/src/components/nav-item/nav-item.scss +128 -0
  224. package/src/components/nav-list/index.ts +12 -0
  225. package/src/components/nav-list/nav-list.component.ts +313 -0
  226. package/src/components/nav-list/nav-list.scss +87 -0
  227. package/src/components/nav-list/nav-list.test.ts +250 -0
  228. package/src/components/navbar/navbar.component.ts +235 -171
  229. package/src/components/navbar/navbar.scss +333 -169
  230. package/src/components/navbar/navbar.test.ts +63 -143
  231. package/src/components/page/page.component.ts +114 -29
  232. package/src/components/page/page.scss +223 -95
  233. package/src/components/page/page.test.ts +214 -3
  234. package/src/components/page-builder/page-builder.component.ts +51 -5
  235. package/src/components/page-builder/page-builder.scss +6 -1
  236. package/src/components/page-nav/page-nav.component.ts +25 -3
  237. package/src/components/page-nav/page-nav.scss +10 -2
  238. package/src/components/pagination/pagination.scss +1 -1
  239. package/src/components/panel/panel.component.ts +14 -13
  240. package/src/components/panel/panel.scss +14 -10
  241. package/src/components/panel/panel.test.ts +42 -0
  242. package/src/components/popup/popup.component.ts +20 -13
  243. package/src/components/popup/popup.test.ts +21 -1
  244. package/src/components/priority-list/priority-list.component.ts +157 -172
  245. package/src/components/priority-list/priority-list.scss +113 -17
  246. package/src/components/priority-list/priority-list.test.ts +301 -3
  247. package/src/components/progress-bar/progress-bar.component.ts +1 -1
  248. package/src/components/progress-bar/progress-bar.scss +16 -4
  249. package/src/components/progress-tile/progress-tile.scss +1 -1
  250. package/src/components/query-builder/query-builder.component.ts +85 -10
  251. package/src/components/query-builder/query-builder.scss +7 -1
  252. package/src/components/query-builder/query-builder.test.ts +40 -0
  253. package/src/components/radio/radio.component.ts +57 -18
  254. package/src/components/radio/radio.scss +101 -26
  255. package/src/components/radio-group/radio-group.component.ts +45 -7
  256. package/src/components/radio-group/radio-group.scss +17 -0
  257. package/src/components/radio-group/radio-group.test.ts +63 -2
  258. package/src/components/rating/rating.component.ts +23 -0
  259. package/src/components/rating/rating.scss +1 -1
  260. package/src/components/remarkd-editor/remarkd-editor.component.ts +60 -5
  261. package/src/components/remarkd-editor/remarkd-editor.scss +12 -4
  262. package/src/components/report-table/index.ts +12 -0
  263. package/src/components/report-table/report-format.test.ts +86 -0
  264. package/src/components/report-table/report-format.ts +72 -0
  265. package/src/components/report-table/report-math.test.ts +74 -0
  266. package/src/components/report-table/report-math.ts +252 -0
  267. package/src/components/report-table/report-table.component.ts +681 -0
  268. package/src/components/report-table/report-table.scss +328 -0
  269. package/src/components/report-table/report-table.test.ts +702 -0
  270. package/src/components/reveal/reveal.component.ts +15 -1
  271. package/src/components/reveal/reveal.scss +5 -0
  272. package/src/components/schedule-builder/schedule-builder.component.ts +23 -8
  273. package/src/components/scroll-container/scroll-container.component.ts +19 -1
  274. package/src/components/scroll-container/scroll-container.scss +6 -1
  275. package/src/components/select/select.component.ts +52 -36
  276. package/src/components/select/select.scss +42 -25
  277. package/src/components/select/select.test.ts +14 -2
  278. package/src/components/settings-container/settings-container.scss +18 -5
  279. package/src/components/sidebar/sidebar.component.ts +8 -1
  280. package/src/components/sidebar/sidebar.scss +6 -1
  281. package/src/components/skeleton/skeleton.component.ts +3 -3
  282. package/src/components/skeleton/skeleton.scss +6 -6
  283. package/src/components/skeleton/skeleton.test.ts +9 -0
  284. package/src/components/slash-menu/slash-menu.scss +1 -1
  285. package/src/components/slideout/slideout.component.ts +20 -3
  286. package/src/components/slideout/slideout.scss +13 -6
  287. package/src/components/slideout/slideout.test.ts +19 -0
  288. package/src/components/split-button/split-button.component.ts +1 -1
  289. package/src/components/split-pane/split-pane.component.ts +71 -8
  290. package/src/components/split-pane/split-pane.scss +145 -41
  291. package/src/components/split-pane/split-pane.test.ts +23 -3
  292. package/src/components/stat/stat.component.ts +209 -14
  293. package/src/components/stat/stat.scss +305 -5
  294. package/src/components/stat/stat.test.ts +143 -2
  295. package/src/components/status-indicator/status-indicator.scss +22 -21
  296. package/src/components/stepper/stepper.scss +3 -3
  297. package/src/components/tab/tab.component.ts +1 -0
  298. package/src/components/table/table.component.ts +12 -1
  299. package/src/components/table/table.scss +21 -13
  300. package/src/components/table/table.test.ts +17 -0
  301. package/src/components/tabs/tabs.component.ts +150 -37
  302. package/src/components/tabs/tabs.scss +48 -7
  303. package/src/components/tabs/tabs.test.ts +169 -0
  304. package/src/components/textarea/textarea.component.ts +25 -3
  305. package/src/components/textarea/textarea.scss +10 -0
  306. package/src/components/textarea/textarea.test.ts +8 -4
  307. package/src/components/theme-editor/theme-editor.scss +0 -6
  308. package/src/components/tile/tile.component.ts +32 -2
  309. package/src/components/tile/tile.scss +40 -7
  310. package/src/components/tile/tile.test.ts +10 -0
  311. package/src/components/toggle/toggle.component.ts +23 -7
  312. package/src/components/toggle/toggle.scss +36 -29
  313. package/src/components/toggle/toggle.test.ts +17 -0
  314. package/src/components/tooltip/tooltip.component.ts +3 -0
  315. package/src/components/tooltip/tooltip.scss +11 -6
  316. package/src/components/tooltip/tooltip.test.ts +12 -0
  317. package/src/components/translation-field/index.ts +12 -0
  318. package/src/components/translation-field/translation-field.component.ts +55 -0
  319. package/src/components/translation-field/translation-field.scss +101 -0
  320. package/src/components/translation-fields/index.ts +12 -0
  321. package/src/components/translation-fields/translation-fields.component.ts +80 -0
  322. package/src/components/translation-fields/translation-fields.scss +60 -0
  323. package/src/components/translation-grid/index.ts +12 -0
  324. package/src/components/translation-grid/translation-grid.component.ts +157 -0
  325. package/src/components/translation-grid/translation-grid.scss +138 -0
  326. package/src/components/translation-grid/translation-grid.test.ts +28 -0
  327. package/src/components/vertical-stepper/vertical-stepper.component.ts +35 -9
  328. package/src/components/vertical-stepper/vertical-stepper.scss +110 -39
  329. package/src/components/vertical-stepper/vertical-stepper.test.ts +35 -0
  330. package/src/components/well/well.scss +2 -2
  331. package/src/events/events.ts +2 -0
  332. package/src/events/zn-chart-click.ts +13 -0
  333. package/src/events/zn-report-change.ts +7 -0
  334. package/src/form-control.scss +36 -3
  335. package/src/internal/anchored-popover.ts +175 -0
  336. package/src/internal/animate.ts +19 -5
  337. package/src/internal/cosmic-border.ts +15 -0
  338. package/src/internal/form.ts +1 -1
  339. package/src/internal/hover-intent.ts +15 -0
  340. package/src/internal/input-modality.ts +15 -0
  341. package/src/internal/pointer-intent.ts +37 -0
  342. package/src/internal/reorder.ts +625 -0
  343. package/src/internal/scroll.ts +51 -3
  344. package/src/internal/selection-card.ts +1 -1
  345. package/src/internal/tab-indicator.ts +92 -0
  346. package/src/internal/table-sort.ts +25 -0
  347. package/src/internal/table-tabs.ts +108 -0
  348. package/src/internal/test-drag.ts +35 -0
  349. package/src/internal/theme-openbyte.test.ts +126 -0
  350. package/src/internal/theme.ts +4 -4
  351. package/src/internal/zinc-element.ts +2 -2
  352. package/src/selection-card.scss +25 -0
  353. package/src/types/web-test-runner-commands.d.ts +1 -0
  354. package/src/utilities/color.test.ts +43 -0
  355. package/src/utilities/color.ts +189 -0
  356. package/src/zinc.ts +14 -0
  357. package/.github/workflows/eleventy_build.yml +0 -42
  358. package/scss/themes/_aura.scss +0 -114
@@ -4,6 +4,7 @@ import {ifDefined} from "lit/directives/if-defined.js";
4
4
  import {MutationController} from '@lit-labs/observers/mutation-controller.js';
5
5
  import {property} from 'lit/decorators.js';
6
6
  import {ResizeController} from '@lit-labs/observers/resize-controller.js';
7
+ import {type IndicatorEdges, slideTabIndicator, squishTabIndicator} from "../../internal/tab-indicator";
7
8
  import {Store} from "../../internal/storage";
8
9
  import ZincElement from '../../internal/zinc-element';
9
10
  import ZnDropdown from "../dropdown";
@@ -17,6 +18,15 @@ interface StoredTab {
17
18
  }
18
19
 
19
20
  const NAVBAR_MIN_WIDTH = 200;
21
+ // Room a scroll arrow covers at either edge of the strip; keep in sync with --navbar-scroll-size in navbar.scss
22
+ const NAVBAR_SCROLL_EDGE = 56;
23
+
24
+ // Runs on every resize: an unchanged write still invalidates style in Safari, so the next read forces another layout
25
+ function setStyle(el: HTMLElement, property: string, value: string) {
26
+ if (el.style.getPropertyValue(property) !== value) {
27
+ el.style.setProperty(property, value);
28
+ }
29
+ }
20
30
 
21
31
  /**
22
32
  * @summary Short summary of the component's intended use.
@@ -33,8 +43,17 @@ const NAVBAR_MIN_WIDTH = 200;
33
43
  * @slot bottom - Content rendered below the navbar row (e.g. chips, filters).
34
44
  *
35
45
  * @csspart base - The component's base wrapper.
46
+ * @csspart indicator - The active-tab indicator that slides between tabs. Exposes
47
+ * `--zn-navbar-indicator-top` / `--zn-navbar-indicator-height` (the active tab's box) for styles that fill the tab.
36
48
  *
37
- * @cssproperty --example - An example CSS custom property.
49
+ * @cssproperty --zn-navbar-tab-indicator-slide - `1` shows the sliding indicator in place of each tab's own line.
50
+ * @cssproperty --zn-navbar-tab-indicator-duration - Duration of the indicator's leading edge (default 300ms).
51
+ * @cssproperty --zn-navbar-tab-indicator-easing - Easing of both indicator edges.
52
+ * @cssproperty --zn-navbar-tab-indicator-trail-duration - Duration of the trailing edge; longer than the lead stretches the indicator.
53
+ * @cssproperty --zn-navbar-tab-indicator-trail-easing - Easing of the trailing edge; defaults to `--zn-navbar-tab-indicator-easing`.
54
+ * @cssproperty --zn-navbar-tab-indicator-trail-delay - Delay before the trailing edge follows (default 0ms).
55
+ * @cssproperty --zn-navbar-tab-indicator-delay - Delay before the indicator starts to move (default 0ms).
56
+ * @cssproperty --zn-navbar-tab-indicator-squash - Vertical scale the indicator squishes to before it travels (default 1, off).
38
57
  */
39
58
  export default class ZnNavbar extends ZincElement {
40
59
  static styles: CSSResultGroup = unsafeCSS(styles);
@@ -50,6 +69,12 @@ export default class ZnNavbar extends ZincElement {
50
69
  @property({attribute: 'hide-one', type: Boolean, reflect: true}) hideOne: boolean;
51
70
  @property({attribute: 'flush', type: Boolean, reflect: true}) flush: boolean = false;
52
71
  @property({attribute: 'gutter', type: Boolean, reflect: true}) gutter: boolean = false;
72
+ /** Draws the tabs as a segmented control: a grey track with a pill that slides to the active tab. */
73
+ @property({type: Boolean, reflect: true}) pill: boolean = false;
74
+ /** With `pill`, `small` draws a shorter track to sit with small controls, such as above a form field. */
75
+ @property({reflect: true}) size: 'small' | 'medium' = 'medium';
76
+ /** The id of a zn-tabs elsewhere on the page that this navbar's tabs switch, such as one below a page header. */
77
+ @property({reflect: true}) tabs: string;
53
78
  @property({type: Boolean}) stacked: boolean;
54
79
  @property({type: Array}) dropdown = [];
55
80
  @property({attribute: "no-pad", type: Boolean}) noPad: false
@@ -75,14 +100,21 @@ export default class ZnNavbar extends ZincElement {
75
100
  callback: () => this._updateVisibility(),
76
101
  });
77
102
 
103
+ private readonly _activeObserver = new MutationController(this, {
104
+ target: null,
105
+ config: {attributes: true, attributeFilter: ['class'], subtree: true, childList: true},
106
+ callback: () => this._scheduleIndicatorSync(),
107
+ });
108
+
78
109
  private _navItems: HTMLElement | null = null;
110
+ private _strip: HTMLElement | null = null;
111
+ private _indicator: HTMLElement | null = null;
112
+ private _indicatorFrame: number | null = null;
113
+ private _indicatorTab: HTMLElement | null = null;
114
+ private _indicatorTarget: IndicatorEdges | null = null;
79
115
  private _expandable: HTMLElement | null = null;
80
- private _extendedMenu: HTMLElement | null = null;
81
- private readonly _cloneSources = new WeakMap<HTMLElement, HTMLElement>();
82
116
  private readonly _lightDomClones = new WeakMap<HTMLElement, HTMLElement>();
83
- private _navItemsGap: number = 0;
84
117
  private _expandableMargin: number = 0;
85
- private _totalItemWidth: number = 0;
86
118
  private _resizeFrame: number | null = null;
87
119
  private _resizeController: ResizeController;
88
120
 
@@ -99,7 +131,6 @@ export default class ZnNavbar extends ZincElement {
99
131
  }
100
132
 
101
133
  const clone = item.cloneNode(true) as HTMLElement;
102
- this._cloneSources.set(clone, item);
103
134
  this._lightDomClones.set(item, clone);
104
135
  clone.addEventListener('click', () => item.click());
105
136
  return clone;
@@ -144,7 +175,17 @@ export default class ZnNavbar extends ZincElement {
144
175
  constructor() {
145
176
  super();
146
177
  this._resizeController = new ResizeController(this, {
147
- callback: () => this._scheduleResize(),
178
+ // Measured here, straight after layout, where reads are free. Deferred to a frame, the reads land after the next
179
+ // drag step has dirtied layout and force a full page layout (tens of ms in Safari). Writes only happen on change.
180
+ // No entries means the controller's own call from hostUpdated, mid-render, where reading forces a layout.
181
+ callback: (entries: ResizeObserverEntry[]) => {
182
+ if (!entries.length) {
183
+ this._scheduleResize();
184
+ return;
185
+ }
186
+ this.handleResize();
187
+ this._syncIndicator();
188
+ },
148
189
  });
149
190
  // eslint-disable-next-line no-new
150
191
  new MutationController(this, {
@@ -167,9 +208,79 @@ export default class ZnNavbar extends ZincElement {
167
208
  this._resizeFrame = requestAnimationFrame(() => {
168
209
  this._resizeFrame = null;
169
210
  this.handleResize();
211
+ this._syncIndicator();
212
+ });
213
+ };
214
+
215
+ private _scheduleIndicatorSync = () => {
216
+ if (this._indicatorFrame !== null) {
217
+ cancelAnimationFrame(this._indicatorFrame);
218
+ }
219
+
220
+ this._indicatorFrame = requestAnimationFrame(() => {
221
+ this._indicatorFrame = null;
222
+ this._syncIndicator();
170
223
  });
171
224
  };
172
225
 
226
+ /** Moves the shared active-tab indicator under the active tab. */
227
+ private _syncIndicator() {
228
+ const indicator = this._indicator;
229
+ const container = indicator?.parentElement;
230
+ if (!indicator || !container) {
231
+ return;
232
+ }
233
+
234
+ const active = this.stacked || this.iconBar ? null : this._navItems?.querySelector<HTMLElement>(
235
+ ':scope > li.active, :scope > li.zn-tb-active');
236
+ const tab = active?.getBoundingClientRect();
237
+ if (!tab || tab.width === 0) {
238
+ indicator.classList.toggle('navbar__indicator--shown', false);
239
+ this._indicatorTarget = null;
240
+ return;
241
+ }
242
+
243
+ // The indicator scrolls with the tabs, so it is placed in the strip's content coordinates
244
+ const box = container.getBoundingClientRect();
245
+ const originLeft = box.left - container.scrollLeft;
246
+ const target = {left: tab.left - originLeft, right: tab.right - originLeft};
247
+ const previous = this._indicatorTarget;
248
+ const tabChanged = active !== this._indicatorTab;
249
+ const ready = indicator.classList.contains('navbar__indicator--ready');
250
+ this._indicatorTab = active ?? null;
251
+ if (tabChanged && active) {
252
+ this._revealTab(active, ready);
253
+ }
254
+ // Exposed for ::part(indicator) styles that fill the tab rather than underline it.
255
+ setStyle(indicator, '--zn-navbar-indicator-top', `${tab.top - box.top}px`);
256
+ setStyle(indicator, '--zn-navbar-indicator-height', `${tab.height}px`);
257
+ indicator.classList.toggle('navbar__indicator--shown', true);
258
+ if (previous && Math.abs(previous.left - target.left) < 0.5 && Math.abs(previous.right - target.right) < 0.5) {
259
+ return;
260
+ }
261
+
262
+ // Only a tab change slides; a resize or reflow under the same tab follows it instantly, unless a slide is still
263
+ // running and should retarget. Either way the slide starts from where the indicator is drawn right now.
264
+ const slide = ready && previous !== null && (tabChanged || indicator.getAnimations().length > 0);
265
+ const drawn = indicator.getBoundingClientRect();
266
+ if (slide && tabChanged) {
267
+ squishTabIndicator(indicator);
268
+ }
269
+
270
+ indicator.style.left = `${target.left}px`;
271
+ indicator.style.width = `${target.right - target.left}px`;
272
+ this._indicatorTarget = target;
273
+ if (slide) {
274
+ slideTabIndicator(indicator, {left: drawn.left - originLeft, right: drawn.right - originLeft}, target);
275
+ }
276
+
277
+ // The first placement jumps into position; only later moves animate.
278
+ if (!ready) {
279
+ requestAnimationFrame(() => indicator.classList.add('navbar__indicator--ready'));
280
+ }
281
+ }
282
+
283
+
173
284
  private _observeExpandingAction(action: Element) {
174
285
  this._expandingActionObserver.observe(action);
175
286
  this._resizeController.observe(action);
@@ -224,7 +335,7 @@ export default class ZnNavbar extends ZincElement {
224
335
  };
225
336
 
226
337
  itemCount(): number {
227
- const items = this._navItems?.querySelectorAll(':scope > li:not(.more):not(#dropdown-item)') || [];
338
+ const items = this._navItems?.querySelectorAll(':scope > li:not(#dropdown-item)') || [];
228
339
  return items.length;
229
340
  }
230
341
 
@@ -242,65 +353,18 @@ export default class ZnNavbar extends ZincElement {
242
353
  return availableWidth;
243
354
  }
244
355
 
245
- private _measureTotalItemWidth(): number {
246
- const items = this._navItems?.querySelectorAll(':scope > li') || [];
247
- let totalWidth = 0;
248
-
249
- for (const item of items) {
250
- if (item.classList.contains('more') || !(item instanceof HTMLElement)) {
251
- continue;
252
- }
253
-
254
- totalWidth += this._getItemWidth(item);
255
- }
256
-
257
- return totalWidth;
258
- }
259
-
260
- private _getItemWidth(item: HTMLElement): number {
261
- return (item.getBoundingClientRect().width || item.offsetWidth || 0) + this._navItemsGap + 5;
262
- }
263
-
264
- private _getHorizontalSpacing(element: HTMLElement): number {
265
- const computed = getComputedStyle(element);
266
- return (parseFloat(computed.paddingLeft) || 0) + (parseFloat(computed.paddingRight) || 0);
267
- }
268
-
269
- private _getMoreItemWidth(): number {
270
- const moreItem = this._navItems?.querySelector<HTMLElement>(':scope > li.more');
271
- if (!moreItem || !this._navItems) {
272
- return 0;
273
- }
274
-
275
- let moreWidth = this._getItemWidth(moreItem);
276
- if (moreWidth > 0) {
277
- return moreWidth;
278
- }
279
-
280
- const hadHidden = this._navItems.classList.contains('has-hidden');
281
- this._navItems.classList.add('has-hidden');
282
- moreWidth = this._getItemWidth(moreItem);
283
- this._navItems.classList.toggle('has-hidden', hadHidden);
284
-
285
- return moreWidth;
286
- }
287
-
288
356
  private _syncLastVisibleItem() {
289
357
  const items = this._navItems?.querySelectorAll<HTMLElement>(':scope > li') || [];
290
358
  let lastVisibleItem: HTMLElement | null = null;
291
359
 
292
360
  for (const item of items) {
293
- item.classList.remove('last-visible');
294
-
295
- if (item.classList.contains('more') || item.classList.contains('hidden') || item.id === 'dropdown-item') {
296
- continue;
361
+ if (item.id !== 'dropdown-item') {
362
+ lastVisibleItem = item;
297
363
  }
298
-
299
- lastVisibleItem = item;
300
364
  }
301
365
 
302
- lastVisibleItem?.classList.add('last-visible');
303
- this._navItems?.classList.toggle('more-only', !lastVisibleItem && this._navItems.classList.contains('has-hidden'));
366
+ // toggle with force leaves an unchanged class alone; remove-then-add would wake _activeObserver every resize
367
+ items.forEach(item => item.classList.toggle('last-visible', item === lastVisibleItem));
304
368
  }
305
369
 
306
370
  private _getExpandableWidth(containerWidth: number): number {
@@ -328,75 +392,60 @@ export default class ZnNavbar extends ZincElement {
328
392
  }
329
393
 
330
394
  handleResize = () => {
331
- if (this._extendedMenu === null || this.iconBar || this.stacked) {
395
+ if (!this._strip) {
332
396
  // If we can't do anything with the nav items, we just return
333
397
  return;
334
398
  }
335
399
 
400
+ // An open fill panel overlays the strip, so the tabs give way to it and scroll instead
336
401
  const availableContainerWidth = this._resolveAvailableWidth();
337
402
  const expandWidth = this._getExpandableWidth(availableContainerWidth);
338
- this._totalItemWidth = this._measureTotalItemWidth();
339
- if (this._totalItemWidth === 0) {
403
+ const fillOpen = this._expanding.some(action => action.getAttribute('method') === 'fill' && action.hasAttribute('open'));
404
+ setStyle(this._strip, 'max-width', fillOpen ? `${Math.max(availableContainerWidth - expandWidth, NAVBAR_MIN_WIDTH)}px` : '');
405
+
406
+ // The tabs' own side spacing scrolls with them, so the arrows sit in from the strip's edges by as much
407
+ const style = getComputedStyle(this._navItems!);
408
+ const startInset = (parseFloat(style.marginLeft) || 0) + (parseFloat(style.paddingLeft) || 0);
409
+ const endInset = (parseFloat(style.marginRight) || 0) + (parseFloat(style.paddingRight) || 0);
410
+ setStyle(this._strip, '--navbar-scroll-inset-start', `${Math.max(startInset, 0)}px`);
411
+ setStyle(this._strip, '--navbar-scroll-inset-end', `${Math.max(endInset, 0)}px`);
412
+
413
+ this._syncLastVisibleItem();
414
+ this._syncScrollEdges();
415
+ }
416
+
417
+ /** Shows a scroll arrow at each edge of the strip that has tabs beyond it. */
418
+ private _syncScrollEdges = () => {
419
+ const strip = this._strip;
420
+ if (!strip) {
340
421
  return;
341
422
  }
342
423
 
343
- let hasHidden = (this._navItems?.querySelectorAll('li.hidden').length || 0) > 0
424
+ const maxScroll = strip.scrollWidth - strip.clientWidth;
425
+ strip.classList.toggle('navbar__strip--scroll-start', strip.scrollLeft > 1);
426
+ strip.classList.toggle('navbar__strip--scroll-end', strip.scrollLeft < maxScroll - 1);
427
+ };
344
428
 
345
- const navSpacing = this._navItems ? this._getHorizontalSpacing(this._navItems) : 0;
346
- const availableWidth = Math.max(availableContainerWidth - expandWidth, NAVBAR_MIN_WIDTH);
347
- const availableWithoutMore = availableWidth - navSpacing;
429
+ private _scrollTabs(direction: 1 | -1) {
430
+ const strip = this._strip;
431
+ strip?.scrollBy({left: direction * Math.max(strip.clientWidth - NAVBAR_SCROLL_EDGE * 2, NAVBAR_SCROLL_EDGE), behavior: 'smooth'});
432
+ }
348
433
 
349
- if (!hasHidden && this._totalItemWidth <= availableWithoutMore) {
350
- this._navItems?.classList.toggle('has-hidden', false)
351
- this._syncLastVisibleItem();
352
- return
434
+ /** Scrolls the strip so the tab clears the scroll arrows. */
435
+ private _revealTab(tab: HTMLElement, smooth: boolean) {
436
+ const strip = this._strip;
437
+ if (!strip || strip.scrollWidth <= strip.clientWidth) {
438
+ return;
353
439
  }
354
440
 
355
- const moreWidth = this._getMoreItemWidth();
356
- const availableWithMore = availableWithoutMore - moreWidth;
357
- // reduce the items
358
- let takenWidth = 0;
359
- let hideRemaining = false;
360
- const items = this._navItems?.querySelectorAll(':scope > li') || [];
361
- this._extendedMenu.innerHTML = '';
362
- for (const item of items) {
363
- if (item.classList.contains('more') || !(item instanceof HTMLElement)) {
364
- continue;
365
- }
366
- const itemWidth = this._getItemWidth(item);
367
- if (hideRemaining || ((itemWidth + takenWidth) > availableWithMore)) {
368
- const extMenu = item.cloneNode(true) as HTMLElement;
369
- extMenu.classList.remove('hidden');
370
- this._cloneSources.set(extMenu, item);
371
- extMenu.addEventListener('click', () => {
372
- item.click();
373
- (this.shadowRoot?.querySelector('#extended-dropdown') as ZnDropdown || null)?.hide()
374
- })
375
- this._extendedMenu?.appendChild(extMenu);
376
- item.classList.add('hidden');
377
- hasHidden = true;
378
- hideRemaining = true;
379
- } else {
380
- item.classList.remove('hidden');
381
- takenWidth += itemWidth;
382
- }
441
+ const start = tab.offsetLeft - NAVBAR_SCROLL_EDGE;
442
+ const end = tab.offsetLeft + tab.offsetWidth + NAVBAR_SCROLL_EDGE - strip.clientWidth;
443
+ const left = start < strip.scrollLeft ? start : end > strip.scrollLeft ? end : null;
444
+ if (left !== null) {
445
+ strip.scrollTo({left, behavior: smooth ? 'smooth' : 'instant'});
383
446
  }
384
- this._navItems?.classList.toggle('has-hidden', hasHidden && hideRemaining)
385
- this._syncLastVisibleItem();
386
447
  }
387
448
 
388
- // Clones in the extended menu are static snapshots, so mirror the active
389
- // state of their source items each time the dropdown opens.
390
- private _syncExtendedActive = () => {
391
- const clones = this._extendedMenu?.querySelectorAll<HTMLElement>(':scope > li') || [];
392
- for (const clone of clones) {
393
- const source = this._cloneSources.get(clone);
394
- if (!source) continue;
395
- clone.classList.toggle('active', source.classList.contains('active'));
396
- clone.classList.toggle('zn-tb-active', source.classList.contains('zn-tb-active'));
397
- }
398
- };
399
-
400
449
  public addItem(item: Element, persist: boolean = true): void {
401
450
  const tabUri = item.getAttribute('tab-uri');
402
451
  if (typeof tabUri !== 'string' || this._openedTabs.includes(tabUri)) {
@@ -426,18 +475,14 @@ export default class ZnNavbar extends ZincElement {
426
475
  protected firstUpdated(_changedProperties: PropertyValues) {
427
476
  super.firstUpdated(_changedProperties);
428
477
 
429
- this._extendedMenu = this.shadowRoot?.querySelector('#extended-menu') as HTMLElement || null;
430
- this.shadowRoot?.querySelector('#extended-dropdown')?.addEventListener('zn-show', this._syncExtendedActive);
431
478
  this._expandable = this.shadowRoot?.querySelector('.navbar__container > div.expandables') as HTMLElement || null;
432
479
  if (this._expandable) {
433
480
  const computed = getComputedStyle(this._expandable);
434
481
  this._expandableMargin = (parseFloat(computed.marginLeft) || 0) + (parseFloat(computed.marginRight) || 0);
435
482
  this._resizeController.observe(this._expandable);
436
483
  }
437
- this._navItems = this.shadowRoot?.querySelector('.navbar__container > ul') as HTMLElement || null;
484
+ this._navItems = this.shadowRoot?.querySelector('ul.navbar') as HTMLElement || null;
438
485
  if (this._navItems) {
439
- const computed = getComputedStyle(this._navItems);
440
- this._navItemsGap = parseFloat(computed.columnGap) || 0;
441
486
  this._resizeController.observe(this._navItems);
442
487
 
443
488
  this._navItems.addEventListener('click', (e) => {
@@ -454,15 +499,24 @@ export default class ZnNavbar extends ZincElement {
454
499
  // Load persisted tabs before calculating widths
455
500
  this._loadStoredTabs();
456
501
 
502
+ this._strip = this.shadowRoot?.querySelector<HTMLElement>('.navbar__strip') ?? null;
503
+ if (this._strip) {
504
+ this._strip.addEventListener('scroll', this._syncScrollEdges, {passive: true});
505
+ this._resizeController.observe(this._strip);
506
+ }
507
+ this._indicator = this.shadowRoot?.querySelector('.navbar__indicator') as HTMLElement || null;
508
+ if (this._indicator?.parentElement) {
509
+ // Tabs can shift inside a resized container while the tab strip keeps its size.
510
+ this._resizeController.observe(this._indicator.parentElement);
511
+ }
457
512
  if (this._navItems) {
458
513
  this._itemsObserver.observe(this._navItems);
514
+ this._activeObserver.observe(this._navItems);
459
515
  }
460
516
  this._expanding.forEach(action => this._observeExpandingAction(action));
461
517
  this._updateVisibility();
462
518
 
463
- setTimeout(() => {
464
- this.handleResize();
465
- }, 100)
519
+ this._scheduleResize();
466
520
 
467
521
  if (this.dropdown.length > 0) {
468
522
  const menu = this.shadowRoot?.querySelector('zn-menu');
@@ -559,6 +613,50 @@ export default class ZnNavbar extends ZincElement {
559
613
  if (!this.navigation) {
560
614
  this.navigation = [];
561
615
  }
616
+ const indicator = html`<span class="navbar__indicator" part="indicator" aria-hidden="true"></span>`;
617
+ const list = html`
618
+ <ul @click="${this.handleClick}" part="navbar" class="${classMap({
619
+ 'navbar': true,
620
+ 'navbar--slim': this.slim,
621
+ 'navbar--border': this.border,
622
+ 'navbar--flush': this.flush,
623
+ 'navbar--no-pad': this.noPad,
624
+ 'navbar--stacked': this.stacked,
625
+ 'navbar--icon-bar': this.iconBar,
626
+ [`navbar--color-${this.color}`]: Boolean(this.color)
627
+ })}">
628
+ ${this._preItems}
629
+ ${this.navigation.map((item: any) => {
630
+ let content = html`${item.title}`;
631
+ if (item.icon != undefined && item.icon != '') {
632
+ content = html`
633
+ <zn-icon src="${item.icon}" size="20"></zn-icon>${content}`;
634
+ }
635
+ if (item.path != undefined) {
636
+ return html`
637
+ <li class="${classMap({'active': item.active})}" part="navbar-item" tab-uri="${item.path}">${content}
638
+ </li>`;
639
+ }
640
+ return html`
641
+ <li class="${classMap({'active': item.active})}"
642
+ part="navbar-item"
643
+ tab="${ifDefined(!this.isolated ? item.tab : undefined)}"
644
+ data-tab="${ifDefined(this.isolated ? item.tab : undefined)}">
645
+ ${content}
646
+ </li>`;
647
+ })}
648
+ ${this.dropdown && this.dropdown.length > 0 ? html`
649
+ <li id="dropdown-item">
650
+ <zn-dropdown>
651
+ <zn-button slot="trigger" part="button" exportparts="base:button__base" color="secondary" square grow>
652
+ <zn-icon src="add" size="18"></zn-icon>
653
+ </zn-button>
654
+ <zn-menu actions="${JSON.stringify(this.dropdown)}"></zn-menu>
655
+ </zn-dropdown>
656
+ </li>` : ''}
657
+ ${this._postItems}
658
+ ${this._appended}
659
+ </ul>`;
562
660
 
563
661
  return html`
564
662
  <div part="container" class="${classMap({
@@ -566,57 +664,23 @@ export default class ZnNavbar extends ZincElement {
566
664
  'navbar__container--stacked': this.stacked,
567
665
  'navbar__container--icon-bar': this.iconBar
568
666
  })}">
569
- <ul @click="${this.handleClick}" part="navbar" class="${classMap({
570
- 'navbar': true,
571
- 'navbar--slim': this.slim,
572
- 'navbar--border': this.border,
573
- 'navbar--flush': this.flush,
574
- 'navbar--no-pad': this.noPad,
575
- 'navbar--stacked': this.stacked,
576
- 'navbar--icon-bar': this.iconBar,
577
- [`navbar--color-${this.color}`]: Boolean(this.color)
578
- })}">
579
- ${this._preItems}
580
- ${this.navigation.map((item: any) => {
581
- let content = html`${item.title}`;
582
- if (item.icon != undefined && item.icon != '') {
583
- content = html`
584
- <zn-icon src="${item.icon}" size="20"></zn-icon>${content}`;
585
- }
586
- if (item.path != undefined) {
587
- return html`
588
- <li class="${classMap({'active': item.active})}" part="navbar-item" tab-uri="${item.path}">${content}
589
- </li>`;
590
- }
591
- return html`
592
- <li class="${classMap({'active': item.active})}"
593
- part="navbar-item"
594
- tab="${ifDefined(!this.isolated ? item.tab : undefined)}"
595
- data-tab="${ifDefined(this.isolated ? item.tab : undefined)}">
596
- ${content}
597
- </li>`;
598
- })}
599
- <li class="more">
600
- <zn-dropdown placement="bottom-end" id="extended-dropdown" distance="1">
601
- <zn-button slot="trigger" text color="transparent">
602
- <zn-icon src="arrow_right_alt" size="20"></zn-icon>
603
- </zn-button>
604
- <ul id="extended-menu">
605
- </ul>
606
- </zn-dropdown>
607
- </li>
608
- ${this.dropdown && this.dropdown.length > 0 ? html`
609
- <li id="dropdown-item">
610
- <zn-dropdown>
611
- <zn-button slot="trigger" part="button" exportparts="base:button__base" color="secondary" square grow>
612
- <zn-icon src="add" size="18"></zn-icon>
613
- </zn-button>
614
- <zn-menu actions="${JSON.stringify(this.dropdown)}"></zn-menu>
615
- </zn-dropdown>
616
- </li>` : ''}
617
- ${this._postItems}
618
- ${this._appended}
619
- </ul>
667
+ ${this.stacked || this.iconBar ? html`${list}${indicator}` : html`
668
+ <div class="navbar__strip">
669
+ <span class="navbar__scroll navbar__scroll--start">
670
+ <button type="button" class="navbar__scroll-button" aria-label="Scroll tabs back"
671
+ @click="${() => this._scrollTabs(-1)}">
672
+ <zn-icon src="arrow_back" size="18"></zn-icon>
673
+ </button>
674
+ </span>
675
+ ${list}
676
+ ${indicator}
677
+ <span class="navbar__scroll navbar__scroll--end">
678
+ <button type="button" class="navbar__scroll-button" aria-label="Scroll tabs forward"
679
+ @click="${() => this._scrollTabs(1)}">
680
+ <zn-icon src="arrow_forward" size="18"></zn-icon>
681
+ </button>
682
+ </span>
683
+ </div>`}
620
684
  ${this._expanding?.length > 0 ? html`
621
685
  <div class="expandables">
622
686
  ${this._expanding}