@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
@@ -16,23 +16,24 @@
16
16
  overflow: hidden;
17
17
  cursor: pointer;
18
18
  border-right: 1px solid rgb(var(--zn-border-color));
19
- background-color: rgb(var(--zn-body));
19
+ background-color: rgb(var(--zn-color-tab));
20
20
  transition: background-color 0.15s ease;
21
21
  user-select: none;
22
22
 
23
23
  &:focus-visible {
24
- outline: 2px solid rgb(var(--zn-primary));
25
- outline-offset: -2px;
24
+ outline: none;
25
+ box-shadow: inset 0 0 0 2px var(--zn-input-focus-ring-color, rgba(var(--zn-primary), 0.35));
26
26
  }
27
27
  }
28
28
 
29
29
  :host(.active) .channel-tile {
30
30
  background-color: rgb(var(--zn-panel));
31
+ box-shadow: inset 0 -2px 0 rgb(var(--zn-primary));
31
32
  cursor: default;
32
33
  }
33
34
 
34
- :host(:not(.active)) .channel-tile--active:hover {
35
- background-color: rgb(var(--zn-panel));
35
+ :host(:not(.active)) .channel-tile:not(.channel-tile--incoming):hover {
36
+ background-color: rgb(var(--zn-body));
36
37
  }
37
38
 
38
39
  .channel-tile--incoming {
@@ -52,15 +53,17 @@
52
53
  align-items: center;
53
54
  flex-shrink: 0;
54
55
  height: 24px;
55
- padding: 0 var(--zn-spacing-small);
56
- background-color: rgb(var(--zn-color-tab));
56
+ padding: 0 12px;
57
+ background-color: var(--zn-color-neutral-100);
57
58
 
58
59
  h3 {
59
60
  margin: 0;
60
- font-size: var(--zn-text-h3-font-size);
61
- line-height: var(--zn-text-h3-line-height);
62
- font-weight: var(--zn-text-h3-font-weight);
63
- color: rgb(var(--zn-text-heading));
61
+ font-size: 10px;
62
+ line-height: 24px;
63
+ font-weight: 600;
64
+ letter-spacing: 0.04em;
65
+ text-transform: uppercase;
66
+ color: rgb(var(--zn-color-muted-text));
64
67
  text-overflow: ellipsis;
65
68
  white-space: nowrap;
66
69
  overflow: hidden;
@@ -84,18 +87,62 @@
84
87
  flex: 1;
85
88
  display: flex;
86
89
  align-items: center;
87
- gap: var(--zn-spacing-small);
88
- padding: var(--zn-spacing-x-small) var(--zn-spacing-small);
90
+ gap: 10px;
91
+ padding: 0 12px;
89
92
  width: 100%;
93
+ min-height: 52px;
94
+ box-sizing: border-box;
90
95
  overflow: hidden;
91
96
  z-index: 1;
92
97
  }
93
98
 
99
+ // The 32px leading circle, shared by the active icon, the accept button and the reject control
100
+ .channel-tile__lead {
101
+ --zn-button-height: 30px; // inside the 1px border
102
+ --zn-button-plain-hover-background: transparent;
103
+
104
+ position: relative;
105
+ flex: none;
106
+ display: grid;
107
+ place-content: center;
108
+ width: 32px;
109
+ height: 32px;
110
+ box-sizing: border-box;
111
+ border-radius: 50%;
112
+ border: 1px solid rgb(var(--zn-border-color));
113
+ background-color: rgb(var(--zn-panel));
114
+ color: rgb(var(--zn-color-muted-text));
115
+ transition: color 0.15s ease, border-color 0.15s ease;
116
+
117
+ zn-icon {
118
+ --icon-color: currentColor;
119
+
120
+ color: inherit;
121
+ }
122
+ }
123
+
124
+ .channel-tile--available .channel-tile__lead {
125
+ border-style: dashed;
126
+ background-color: transparent;
127
+ }
128
+
129
+ .channel-tile:not(.channel-tile--default) .channel-tile__lead {
130
+ color: rgb(var(--channel-tile-color));
131
+ border-color: rgb(var(--channel-tile-color));
132
+ }
133
+
134
+ :host(.active) .channel-tile--default .channel-tile__lead {
135
+ color: rgb(var(--zn-primary));
136
+ border-color: rgb(var(--zn-primary));
137
+ }
138
+
94
139
  .channel-tile__content {
95
140
  display: flex;
96
141
  flex-direction: column;
142
+ justify-content: center;
97
143
  flex-grow: 1;
98
- gap: 4px;
144
+ gap: 2px;
145
+ min-height: 32px;
99
146
  overflow: hidden;
100
147
 
101
148
  @container (max-width: 200px) {
@@ -105,11 +152,10 @@
105
152
 
106
153
  .channel-tile__title {
107
154
  margin: 0;
108
- font-size: var(--zn-text-heading-section-font-size);
109
- line-height: var(--zn-text-heading-section-line-height);
110
- font-weight: var(--zn-text-heading-section-font-weight);
111
- color: var(--zn-text-heading-section-color);
112
- text-transform: var(--zn-text-heading-section-transform);
155
+ font-size: 13px;
156
+ line-height: 15px;
157
+ font-weight: 600;
158
+ color: rgb(var(--zn-text-heading));
113
159
  text-overflow: ellipsis;
114
160
  white-space: nowrap;
115
161
  overflow: hidden;
@@ -117,11 +163,10 @@
117
163
 
118
164
  .channel-tile__subtitle {
119
165
  margin: 0;
120
- font-size: var(--zn-text-subheading-small-font-size);
121
- line-height: var(--zn-text-subheading-small-line-height);
122
- font-weight: var(--zn-text-subheading-small-font-weight);
123
- color: var(--zn-text-subheading-small-color);
124
- text-transform: var(--zn-text-subheading-small-transform);
166
+ font-size: 12px;
167
+ line-height: 15px;
168
+ font-weight: 400;
169
+ color: rgb(var(--zn-color-muted-text));
125
170
  text-overflow: ellipsis;
126
171
  white-space: nowrap;
127
172
  overflow: hidden;
@@ -134,25 +179,18 @@
134
179
  gap: var(--zn-spacing-small);
135
180
  }
136
181
 
137
- .channel-tile__reject {
138
- flex-shrink: 0;
139
- display: flex;
140
- align-items: center;
141
- justify-content: center;
142
- width: 36px;
143
- height: 36px;
144
- border-radius: 50%;
145
- border: none;
182
+ .channel-tile--available .channel-tile__reject {
146
183
  padding: 0;
147
184
  margin: 0;
148
- background-color: rgba(var(--zn-color-panel));
185
+ border-style: solid;
186
+ background-color: rgb(var(--zn-panel));
149
187
  color: rgb(var(--zn-color-error));
150
188
  cursor: pointer;
151
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
152
189
 
153
190
  &:hover {
154
- background-color: rgba(var(--zn-color-error));
155
- color: rgb(var(--zn-color-panel));
191
+ background-color: rgb(var(--zn-color-error));
192
+ border-color: rgb(var(--zn-color-error));
193
+ color: rgb(var(--zn-panel));
156
194
  }
157
195
  }
158
196
 
@@ -161,8 +199,7 @@
161
199
  bottom: 0;
162
200
  left: 0;
163
201
  right: 0;
164
- height: 3px;
165
- background-color: rgba(var(--zn-shadow), 0.2);
202
+ height: 2px;
166
203
  overflow: hidden;
167
204
  z-index: 1;
168
205
 
@@ -196,7 +233,3 @@
196
233
  .channel-tile--disabled {
197
234
  --channel-tile-color: var(--zn-color-disabled, 204, 204, 204);
198
235
  }
199
-
200
- .channel-tile--incoming .channel-tile__body zn-button {
201
- --zn-color-tab: var(--zn-color-panel);
202
- }
@@ -430,3 +430,49 @@ describe('buildSankeyOption', () => {
430
430
  expect((opt.tooltip as any).trigger).to.equal('item');
431
431
  });
432
432
  });
433
+
434
+ describe('cartesian legends', () => {
435
+ it('hides the legend for a single series', () => {
436
+ const opt = buildLineOption({ ...baseProps, type: 'line' });
437
+ expect((opt.legend as any).show).to.equal(false);
438
+ });
439
+
440
+ it('shows the legend for several series, ignoring hidden ones', () => {
441
+ const two = buildLineOption({ ...baseProps, data: [{ name: 'A', data: [1] }, { name: 'B', data: [2] }] });
442
+ const oneVisible = buildLineOption({ ...baseProps, data: [{ name: 'A', data: [1] }, { name: '_base', data: [2] }] });
443
+ expect((two.legend as any).show).to.equal(true);
444
+ expect((oneVisible.legend as any).show).to.equal(false);
445
+ });
446
+
447
+ it('scrolls the legend on one line so it never wraps into the grid', () => {
448
+ const opt = buildLineOption({ ...baseProps, data: [{ name: 'A', data: [1] }, { name: 'B', data: [2] }] });
449
+ expect((opt.legend as any).type).to.equal('scroll');
450
+ });
451
+ });
452
+
453
+ describe('markLatest', () => {
454
+ it('hides every point but the last, which gets a hollow dot', () => {
455
+ const series = (buildAreaOption({ ...baseProps, type: 'area', markLatest: true }).series as any[])[0];
456
+ expect(series.symbol).to.equal('none');
457
+ expect(series.data.slice(0, 2)).to.deep.equal([10, 20]);
458
+ expect(series.data[2]).to.deep.equal({ value: 30, symbol: 'emptyCircle', symbolSize: 8 });
459
+ });
460
+
461
+ it('marks the last point with a value, skipping trailing gaps', () => {
462
+ const data = [{ name: 'A', data: [10, 20, null] }];
463
+ const series = (buildAreaOption({ ...baseProps, type: 'area', markLatest: true, data }).series as any[])[0];
464
+ expect(series.data).to.deep.equal([10, { value: 20, symbol: 'emptyCircle', symbolSize: 8 }, null]);
465
+ });
466
+
467
+ it('leaves a silent reference series unmarked', () => {
468
+ const data = [{ name: 'Pass mark', data: [70, 70], silent: true, symbol: 'none' }];
469
+ const series = (buildAreaOption({ ...baseProps, type: 'area', markLatest: true, data }).series as any[])[0];
470
+ expect(series.data).to.deep.equal([70, 70]);
471
+ });
472
+
473
+ it('leaves the points alone by default', () => {
474
+ const series = (buildLineOption({ ...baseProps, type: 'line' }).series as any[])[0];
475
+ expect(series.symbol).to.equal(undefined);
476
+ expect(series.data).to.deep.equal([10, 20, 30]);
477
+ });
478
+ });
@@ -81,6 +81,8 @@ export interface BuilderProps {
81
81
  indicators?: RadarIndicator[];
82
82
  minValue?: number;
83
83
  maxValue?: number;
84
+ /** Marks each line's last point with a hollow dot and hides the others. */
85
+ markLatest?: boolean;
84
86
  }
85
87
 
86
88
  function baseOption(props: BuilderProps, tooltipTrigger: 'axis' | 'item'): EChartsOption {
@@ -102,17 +104,24 @@ function baseOption(props: BuilderProps, tooltipTrigger: 'axis' | 'item'): EChar
102
104
  : undefined,
103
105
  },
104
106
  legend: {
107
+ type: 'scroll',
105
108
  top: 0,
106
109
  right: 0,
107
110
  icon: 'circle',
108
111
  textStyle: { color: textColor },
112
+ pageIconSize: 10,
113
+ pageTextStyle: { color: textColor },
109
114
  },
110
115
  };
111
116
  }
112
117
 
113
118
  function commonOption(props: BuilderProps): EChartsOption {
119
+ const base = baseOption(props, 'axis');
120
+ // A lone series is named by the chart's own caption, so its legend only repeats it.
121
+ const named = props.data.filter((s) => !s.name.startsWith('_')).length;
114
122
  return {
115
- ...baseOption(props, 'axis'),
123
+ ...base,
124
+ legend: { ...(base.legend as Record<string, unknown>), show: named > 1 },
116
125
  grid: {
117
126
  left: 40,
118
127
  right: 20,
@@ -223,14 +232,28 @@ export function buildBarOption(props: BuilderProps): EChartsOption {
223
232
  };
224
233
  }
225
234
 
235
+ // latestMarker hides a line's points except the last, which gets a hollow dot.
236
+ function latestMarker(props: BuilderProps, s: SeriesItem): Record<string, unknown> {
237
+ if (!props.markLatest || s.silent || !s.data?.length) return {};
238
+ const data = normalizeData(s.data);
239
+ // A null is a gap, so the marker goes on the last point that has a value.
240
+ const last = data.findLastIndex(d => d !== null && d !== undefined && !(Array.isArray(d) && d[d.length - 1] === null));
241
+ if (last < 0) return { symbol: 'none' };
242
+ return {
243
+ symbol: 'none',
244
+ data: data.map((d, i) => (i === last ? { value: d, symbol: 'emptyCircle', symbolSize: 8 } : d)),
245
+ };
246
+ }
247
+
226
248
  export function buildLineOption(props: BuilderProps): EChartsOption {
227
249
  return {
228
250
  ...commonOption(props),
229
251
  xAxis: buildXAxis(props, true),
230
252
  yAxis: buildYAxis(props),
231
- series: seriesFromProps(props, 'line', () => ({
253
+ series: seriesFromProps(props, 'line', (s) => ({
232
254
  symbolSize: props.datapointSize,
233
255
  smooth: props.smooth,
256
+ ...latestMarker(props, s),
234
257
  })),
235
258
  };
236
259
  }
@@ -240,10 +263,11 @@ export function buildAreaOption(props: BuilderProps): EChartsOption {
240
263
  ...commonOption(props),
241
264
  xAxis: buildXAxis(props, true),
242
265
  yAxis: buildYAxis(props),
243
- series: seriesFromProps(props, 'line', () => ({
266
+ series: seriesFromProps(props, 'line', (s) => ({
244
267
  symbolSize: props.datapointSize,
245
268
  smooth: props.smooth,
246
269
  areaStyle: { opacity: 0.1 },
270
+ ...latestMarker(props, s),
247
271
  })),
248
272
  };
249
273
  }
@@ -1,8 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return */
2
+ import '../../../dist/zn.min.js';
2
3
  import { expect, fixture, html } from '@open-wc/testing';
3
- import ZnChart from './chart.component';
4
-
5
- ZnChart.define('zn-chart-live-test');
6
4
  class Source extends EventTarget {
7
5
  static instances: Source[]=[]; closed=false; onopen?:()=>void; onerror?:()=>void;
8
6
  url: string;
@@ -17,7 +15,7 @@ describe('chart SSE component',()=>{
17
15
  beforeEach(()=>{Source.instances=[];window.EventSource=Source as unknown as typeof EventSource;});
18
16
  afterEach(()=>{window.EventSource=original;});
19
17
  it('renders SSE buckets and expected bands, reacts to display changes, and stops when removed',async()=>{
20
- const el:any=await fixture(html`<zn-chart-live-test type="line" xaxis="datetime" enable-animations="350" live stream-url="https://relay.example/events"></zn-chart-live-test>`);
18
+ const el:any=await fixture(html`<zn-chart type="line" xaxis="datetime" enable-animations="350" live stream-url="https://relay.example/events"></zn-chart>`);
21
19
  await el.updateComplete;await pause();
22
20
  const source=Source.instances.at(-1)!;
23
21
  expect(source).to.exist;
@@ -57,7 +55,7 @@ describe('chart SSE component',()=>{
57
55
  el.remove();expect(source.closed).to.equal(true);
58
56
  });
59
57
  it('keeps transitions enabled for a high-volume hour', async()=>{
60
- const el:any=await fixture(html`<zn-chart-live-test type="line" xaxis="datetime" enable-animations="350" live stream-url="https://relay.example/hour"></zn-chart-live-test>`);
58
+ const el:any=await fixture(html`<zn-chart type="line" xaxis="datetime" enable-animations="350" live stream-url="https://relay.example/hour"></zn-chart>`);
61
59
  await el.updateComplete;await pause();
62
60
  Source.instances.at(-1)!.send('snapshot',{
63
61
  from:0,until:3600000,server_time:Date.now(),expires_at:Date.now()+60000,resolution_seconds:1,metric:'average',
@@ -71,7 +69,7 @@ describe('chart SSE component',()=>{
71
69
  el.remove();
72
70
  });
73
71
  it('restarts transport when its URL changes and when reattached',async()=>{
74
- const el:any=await fixture(html`<zn-chart-live-test type="line" live stream-url="https://relay.example/a"></zn-chart-live-test>`);
72
+ const el:any=await fixture(html`<zn-chart type="line" live stream-url="https://relay.example/a"></zn-chart>`);
75
73
  await el.updateComplete;await pause();
76
74
  const old=Source.instances.at(-1)!;
77
75
  el.streamUrl='https://relay.example/b';await el.updateComplete;
@@ -17,6 +17,7 @@ import {
17
17
  type SeriesItem,
18
18
  } from './builders';
19
19
  import { type CSSResultGroup, html, type PropertyValues, unsafeCSS } from 'lit';
20
+ import { classMap } from 'lit/directives/class-map.js';
20
21
  import { type EChartsModule, loadECharts } from './echarts-loader';
21
22
  import { LiveTransport, LiveWindow } from './live';
22
23
  import { property, state } from 'lit/decorators.js';
@@ -32,6 +33,8 @@ import type { ECharts } from 'echarts/core';
32
33
  * @since 1.0
33
34
  *
34
35
  * @csspart base - The component's base wrapper.
36
+ *
37
+ * @event zn-chart-click - Emitted when a data point, bar or slice is clicked.
35
38
  */
36
39
  export default class ZnChart extends ZincElement {
37
40
  static styles: CSSResultGroup = unsafeCSS(styles);
@@ -47,6 +50,7 @@ export default class ZnChart extends ZincElement {
47
50
  @property({ type: Boolean }) stacked = false;
48
51
 
49
52
  @property({ type: Boolean }) live = false;
53
+ /** JSON series polled with `live`; otherwise fetched once, as a series array or `{categories, data}`. */
50
54
  @property({ attribute: 'data-url' }) dataUrl = '';
51
55
  @property({ attribute: 'stream-url' }) streamUrl = '';
52
56
  @property({ attribute: 'live-renew-url' }) liveRenewUrl = '';
@@ -75,6 +79,8 @@ export default class ZnChart extends ZincElement {
75
79
  @property({ attribute: 'show-labels', type: Boolean }) showLabels?: boolean;
76
80
  @property({ attribute: 'min-value', type: Number }) minValue?: number;
77
81
  @property({ attribute: 'max-value', type: Number }) maxValue?: number;
82
+ /** On line and area charts, marks each series' latest point and hides the rest. */
83
+ @property({ attribute: 'mark-latest', type: Boolean }) markLatest = false;
78
84
  @property({
79
85
  converter: {
80
86
  fromAttribute: (value: string | null) => {
@@ -91,6 +97,11 @@ export default class ZnChart extends ZincElement {
91
97
  private initPromise?: Promise<void>;
92
98
  private liveTimer?: number;
93
99
  private pollAbort?: AbortController;
100
+ private loadedUrl = '';
101
+ /** Set while `data-url` loads; cartesian charts show a placeholder until the data arrives. */
102
+ @state() private loading = false;
103
+ @state() private loadingOut = false;
104
+ private riseIn = false;
94
105
  @state() private liveStatus = 'connecting';
95
106
  private liveWindow = new LiveWindow();
96
107
  private replaceLiveSeries = true;
@@ -133,6 +144,7 @@ export default class ZnChart extends ZincElement {
133
144
  option.animationThreshold = 10000;
134
145
  option.animationEasingUpdate = 'linear';
135
146
  }
147
+ if (this.riseIn) this.prepareRise(option);
136
148
  if (this.liveWindow.frame && this.live && !this.replaceLiveSeries) {
137
149
  // Keep the prediction's models and data in ECharts between snapshots.
138
150
  // Only measurements and the moving viewport change on deltas/heartbeats.
@@ -148,16 +160,50 @@ export default class ZnChart extends ZincElement {
148
160
  animationThreshold: 10000,
149
161
  });
150
162
  } else {
151
- this.chart?.setOption(option, { replaceMerge: ['series', 'legend', 'xAxis'] });
163
+ this.chart?.setOption(option, { replaceMerge: ['series', 'legend', 'xAxis', 'yAxis'] });
152
164
  }
153
165
  this.replaceLiveSeries = false;
154
166
  }
155
167
 
168
+ // Draws the loaded series flat on the baseline first, so ECharts animates them up to their values
169
+ private prepareRise(option: ReturnType<ZnChart['buildOption']>) {
170
+ this.riseIn = false;
171
+ if (!ZnChart.riseTypes.includes(this.type) || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
172
+ const series = (Array.isArray(option.series) ? option.series : option.series ? [option.series] : []) as Record<string, unknown>[];
173
+ const flat = (v: unknown): unknown => typeof v === 'number' ? 0
174
+ : Array.isArray(v) ? [v[0], 0, ...v.slice(2)]
175
+ : v && typeof v === 'object' && 'value' in v ? { ...v, value: flat((v as { value: unknown }).value) } : v;
176
+ this.chart?.setOption({
177
+ ...option,
178
+ animation: false,
179
+ series: series.map((item, idx) => ({
180
+ ...item, id: `rise-${idx}`, data: Array.isArray(item.data) ? item.data.map(flat) : item.data,
181
+ })),
182
+ }, { replaceMerge: ['series', 'legend', 'xAxis', 'yAxis'] });
183
+ option.animation = true;
184
+ option.animationDurationUpdate = 450;
185
+ option.animationEasingUpdate = 'cubicOut';
186
+ // Same ids as the flat pass, so ECharts updates those series rather than drawing new ones in
187
+ option.series = series.map((item, idx) => ({
188
+ ...item, id: `rise-${idx}`, animationDelayUpdate: idx * 40,
189
+ })) as typeof option.series;
190
+ }
191
+
192
+ private static readonly riseTypes: ChartType[] = ['line', 'area', 'bar'];
193
+ private static readonly viewOnly = ['liveStatus', 'loading', 'loadingOut'];
194
+
195
+ protected willUpdate(changed: PropertyValues) {
196
+ super.willUpdate(changed);
197
+ if ((changed.has('dataUrl') || changed.has('live')) && this.dataUrl && !this.live && this.dataUrl !== this.loadedUrl) {
198
+ this.loading = true;
199
+ }
200
+ }
201
+
156
202
  protected updated(changed: PropertyValues) {
157
203
  super.updated(changed);
158
204
  if (!this.chart) return;
159
205
  if (['live', 'dataUrl', 'streamUrl', 'liveRenewUrl', 'liveInterval'].some(key => changed.has(key))) this.startLive();
160
- if (changed.size !== 1 || !changed.has('liveStatus')) this.updateChart();
206
+ if ([...changed.keys()].some(key => !ZnChart.viewOnly.includes(key as string))) this.updateChart();
161
207
  }
162
208
 
163
209
  connectedCallback() {
@@ -193,12 +239,24 @@ export default class ZnChart extends ZincElement {
193
239
 
194
240
  private getTextColor(): string | undefined {
195
241
  const cs = getComputedStyle(this);
242
+ const themed = cs.getPropertyValue('--zn-chart-text-color').trim();
243
+ if (themed) return themed;
196
244
  const rgb = cs.getPropertyValue('--zn-text').trim();
197
245
  if (!rgb) return undefined;
198
246
  const opacity = cs.getPropertyValue('--zn-text-opacity').trim() || '1';
199
247
  return `rgba(${rgb}, ${opacity})`;
200
248
  }
201
249
 
250
+ // A theme supplies a categorical palette as --zn-chart-color-1..6; without
251
+ // one, ECharts keeps its own default palette.
252
+ private getPalette(): string[] | undefined {
253
+ const cs = getComputedStyle(this);
254
+ const colors = [1, 2, 3, 4, 5, 6]
255
+ .map(i => cs.getPropertyValue(`--zn-chart-color-${i}`).trim())
256
+ .filter(Boolean);
257
+ return colors.length ? colors : undefined;
258
+ }
259
+
202
260
  private getBorderColor(): string | undefined {
203
261
  const rgb = getComputedStyle(this).getPropertyValue('--zn-border-color').trim();
204
262
  return rgb ? `rgb(${rgb})` : undefined;
@@ -214,7 +272,7 @@ export default class ZnChart extends ZincElement {
214
272
  stacked: this.stacked,
215
273
  enableAnimations: this.enableAnimations,
216
274
  datapointSize: this.datapointSize,
217
- colors: this.colors,
275
+ colors: this.colors ?? this.getPalette(),
218
276
  theme: this.getTheme(),
219
277
  smooth: this.smooth,
220
278
  scale: this.scale,
@@ -227,6 +285,7 @@ export default class ZnChart extends ZincElement {
227
285
  indicators: Array.isArray(this.indicators) ? this.indicators : [],
228
286
  minValue: this.minValue,
229
287
  maxValue: this.maxValue,
288
+ markLatest: this.markLatest,
230
289
  };
231
290
  switch (this.type) {
232
291
  case 'bar': return buildBarOption(props);
@@ -257,6 +316,16 @@ export default class ZnChart extends ZincElement {
257
316
 
258
317
  this.chart = echarts.init(host);
259
318
  this.chart.setOption(this.buildOption());
319
+ this.chart.on('click', params => {
320
+ this.emit('zn-chart-click', {
321
+ detail: {
322
+ seriesName: params.seriesName ?? '',
323
+ name: params.name,
324
+ dataIndex: params.dataIndex,
325
+ value: params.value,
326
+ },
327
+ });
328
+ });
260
329
 
261
330
  if (this.syncGroup) {
262
331
  this.chart.group = this.syncGroup;
@@ -277,7 +346,8 @@ export default class ZnChart extends ZincElement {
277
346
 
278
347
  private startLive() {
279
348
  this.stopLive(); this.liveWindow = new LiveWindow(); this.replaceLiveSeries = true;
280
- if (!this.live || !this.isConnected) return;
349
+ if (!this.isConnected) return;
350
+ if (!this.live) { void this.loadData(); return; }
281
351
  if (this.streamUrl || this.liveRenewUrl) {
282
352
  try { this.transport.start(this.streamUrl, this.liveRenewUrl); } catch { this.stopLive(); }
283
353
  return;
@@ -297,6 +367,34 @@ export default class ZnChart extends ZincElement {
297
367
  this.liveTimer = window.setTimeout(() => void poll(), Math.max(250, this.liveInterval));
298
368
  }
299
369
 
370
+ private async loadData() {
371
+ if (!this.dataUrl || this.dataUrl === this.loadedUrl) return;
372
+ const controller = new AbortController(); this.pollAbort = controller;
373
+ try {
374
+ const res = await fetch(this.dataUrl, {
375
+ signal: controller.signal,
376
+ credentials: 'same-origin',
377
+ headers: { Accept: 'application/json', 'x-kx-fetch-style': 'raw-json' },
378
+ });
379
+ if (res.ok) {
380
+ const body = await res.json() as SeriesItem[] | { data?: SeriesItem[]; categories?: string[] };
381
+ if (controller.signal.aborted) return;
382
+ this.loadedUrl = this.dataUrl;
383
+ this.riseIn = true;
384
+ if (Array.isArray(body)) {
385
+ this.data = body;
386
+ } else {
387
+ this.categories = body.categories ?? this.categories;
388
+ this.data = body.data ?? [];
389
+ }
390
+ }
391
+ } catch { /* keep what the chart already shows */ }
392
+ if (controller.signal.aborted) return;
393
+ this.loading = false;
394
+ this.loadingOut = true;
395
+ window.setTimeout(() => { this.loadingOut = false; }, 400);
396
+ }
397
+
300
398
  private reinit() {
301
399
  this.stopLive();
302
400
  this.chart?.dispose();
@@ -335,8 +433,30 @@ export default class ZnChart extends ZincElement {
335
433
  this.chart = undefined;
336
434
  }
337
435
 
436
+ private renderLoading() {
437
+ if (!(this.loading || this.loadingOut) || !ZnChart.riseTypes.includes(this.type)) return null;
438
+ return html`
439
+ <div class="${classMap({ loading: true, 'loading--out': !this.loading })}" style="height: ${this.height}px"
440
+ role="status" aria-label="Loading chart">
441
+ <div class="loading__legend">
442
+ ${[48, 40, 52, 36].map(w => html`<span class="loading__pill" style="width: ${w}px"></span>`)}
443
+ </div>
444
+ <div class="loading__plot">
445
+ ${[0, 1, 2, 3, 4].map(i => html`
446
+ <span class="loading__row" style="animation-delay: ${i * 120}ms">
447
+ <span class="loading__pill loading__label"></span>
448
+ </span>`)}
449
+ <span class="loading__track"><span class="loading__pill loading__label"></span></span>
450
+ <span class="loading__x">
451
+ ${[0, 1, 2, 3, 4, 5].map(() => html`<span class="loading__pill"></span>`)}
452
+ </span>
453
+ </div>
454
+ </div>`;
455
+ }
456
+
338
457
  protected render(): unknown {
339
- return html`<div class="chart"><div id="chart"></div>
458
+ return html`<div class="${classMap({ chart: true, 'chart--loading': this.loading })}"><div id="chart"></div>
459
+ ${this.renderLoading()}
340
460
  ${this.live && (this.streamUrl || this.liveRenewUrl) ? html`<div role="status" aria-live="polite">Live: ${this.liveStatus}${this.liveWindow.frame ? ` · ${this.liveWindow.frame.resolution_seconds}s intervals · Expected range: ${this.liveWindow.predictionStatus}` : ''}</div>` : ''}
341
461
  </div>`;
342
462
  }