@adia-ai/web-components 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (468) hide show
  1. package/README.md +195 -0
  2. package/a2ui/dockables/action.js +152 -0
  3. package/a2ui/dockables/base.js +30 -0
  4. package/a2ui/dockables/controller.js +97 -0
  5. package/a2ui/dockables/data-source.js +103 -0
  6. package/a2ui/dockables/index.js +6 -0
  7. package/a2ui/dockables/lifecycle.js +84 -0
  8. package/a2ui/dockables/provider.js +59 -0
  9. package/a2ui/index.js +19 -0
  10. package/a2ui/manifest-runtime.js +226 -0
  11. package/a2ui/registry.js +200 -0
  12. package/a2ui/renderer.js +361 -0
  13. package/a2ui/root.js +152 -0
  14. package/a2ui/stream.js +243 -0
  15. package/a2ui/surface-manifest.js +294 -0
  16. package/a2ui/surface.js +222 -0
  17. package/a2ui/wire-factory.js +134 -0
  18. package/a2ui/wiring-engine.js +209 -0
  19. package/a2ui/wiring-registry.js +342 -0
  20. package/components/accordion/accordion.a2ui.json +129 -0
  21. package/components/accordion/accordion.css +133 -0
  22. package/components/accordion/accordion.js +125 -0
  23. package/components/accordion/accordion.yaml +527 -0
  24. package/components/action-list/action-list.a2ui.json +64 -0
  25. package/components/action-list/action-list.css +115 -0
  26. package/components/action-list/action-list.js +149 -0
  27. package/components/action-list/action-list.yaml +56 -0
  28. package/components/agent-artifact/agent-artifact.a2ui.json +99 -0
  29. package/components/agent-artifact/agent-artifact.css +94 -0
  30. package/components/agent-artifact/agent-artifact.js +169 -0
  31. package/components/agent-artifact/agent-artifact.yaml +71 -0
  32. package/components/agent-feedback-bar/agent-feedback-bar.a2ui.json +91 -0
  33. package/components/agent-feedback-bar/agent-feedback-bar.css +33 -0
  34. package/components/agent-feedback-bar/agent-feedback-bar.js +152 -0
  35. package/components/agent-feedback-bar/agent-feedback-bar.yaml +65 -0
  36. package/components/agent-questions/agent-questions.a2ui.json +89 -0
  37. package/components/agent-questions/agent-questions.css +146 -0
  38. package/components/agent-questions/agent-questions.js +189 -0
  39. package/components/agent-questions/agent-questions.yaml +63 -0
  40. package/components/agent-reasoning/agent-reasoning.a2ui.json +100 -0
  41. package/components/agent-reasoning/agent-reasoning.css +273 -0
  42. package/components/agent-reasoning/agent-reasoning.js +469 -0
  43. package/components/agent-reasoning/agent-reasoning.yaml +70 -0
  44. package/components/agent-suggestions/agent-suggestions.a2ui.json +87 -0
  45. package/components/agent-suggestions/agent-suggestions.css +18 -0
  46. package/components/agent-suggestions/agent-suggestions.js +87 -0
  47. package/components/agent-suggestions/agent-suggestions.yaml +59 -0
  48. package/components/agent-trace/agent-trace.a2ui.json +78 -0
  49. package/components/agent-trace/agent-trace.css +275 -0
  50. package/components/agent-trace/agent-trace.js +216 -0
  51. package/components/agent-trace/agent-trace.yaml +53 -0
  52. package/components/alert/alert.a2ui.json +211 -0
  53. package/components/alert/alert.css +88 -0
  54. package/components/alert/alert.js +96 -0
  55. package/components/alert/alert.yaml +205 -0
  56. package/components/avatar/avatar.a2ui.json +215 -0
  57. package/components/avatar/avatar.css +159 -0
  58. package/components/avatar/avatar.js +157 -0
  59. package/components/avatar/avatar.yaml +559 -0
  60. package/components/badge/badge.a2ui.json +169 -0
  61. package/components/badge/badge.css +78 -0
  62. package/components/badge/badge.js +53 -0
  63. package/components/badge/badge.yaml +612 -0
  64. package/components/block/block.a2ui.json +135 -0
  65. package/components/block/block.css +29 -0
  66. package/components/block/block.js +23 -0
  67. package/components/block/block.yaml +115 -0
  68. package/components/breadcrumb/breadcrumb.a2ui.json +86 -0
  69. package/components/breadcrumb/breadcrumb.css +78 -0
  70. package/components/breadcrumb/breadcrumb.js +44 -0
  71. package/components/breadcrumb/breadcrumb.yaml +84 -0
  72. package/components/button/button.a2ui.json +172 -0
  73. package/components/button/button.css +168 -0
  74. package/components/button/button.js +60 -0
  75. package/components/button/button.yaml +120 -0
  76. package/components/calendar-picker/calendar-picker.a2ui.json +139 -0
  77. package/components/calendar-picker/calendar-picker.css +321 -0
  78. package/components/calendar-picker/calendar-picker.js +324 -0
  79. package/components/calendar-picker/calendar-picker.yaml +243 -0
  80. package/components/canvas/canvas.a2ui.json +75 -0
  81. package/components/canvas/canvas.css +52 -0
  82. package/components/canvas/canvas.js +179 -0
  83. package/components/canvas/canvas.yaml +62 -0
  84. package/components/card/card.a2ui.json +276 -0
  85. package/components/card/card.css +362 -0
  86. package/components/card/card.js +58 -0
  87. package/components/card/card.yaml +527 -0
  88. package/components/chart/chart.a2ui.json +298 -0
  89. package/components/chart/chart.css +512 -0
  90. package/components/chart/chart.js +1075 -0
  91. package/components/chart/chart.yaml +540 -0
  92. package/components/chat/chat-input.css +141 -0
  93. package/components/chat/chat-input.js +242 -0
  94. package/components/chat/chat.a2ui.json +181 -0
  95. package/components/chat/chat.css +193 -0
  96. package/components/chat/chat.js +155 -0
  97. package/components/chat/chat.yaml +230 -0
  98. package/components/check/check.a2ui.json +134 -0
  99. package/components/check/check.css +126 -0
  100. package/components/check/check.js +58 -0
  101. package/components/check/check.yaml +109 -0
  102. package/components/code/code.a2ui.json +153 -0
  103. package/components/code/code.css +133 -0
  104. package/components/code/code.js +114 -0
  105. package/components/code/code.yaml +163 -0
  106. package/components/col/col.a2ui.json +104 -0
  107. package/components/col/col.css +36 -0
  108. package/components/col/col.js +19 -0
  109. package/components/col/col.yaml +364 -0
  110. package/components/color-picker/color-picker.a2ui.json +100 -0
  111. package/components/color-picker/color-picker.css +182 -0
  112. package/components/color-picker/color-picker.js +537 -0
  113. package/components/color-picker/color-picker.yaml +105 -0
  114. package/components/command/command.a2ui.json +221 -0
  115. package/components/command/command.css +251 -0
  116. package/components/command/command.js +284 -0
  117. package/components/command/command.yaml +186 -0
  118. package/components/description-list/description-list.a2ui.json +89 -0
  119. package/components/description-list/description-list.css +70 -0
  120. package/components/description-list/description-list.js +75 -0
  121. package/components/description-list/description-list.yaml +59 -0
  122. package/components/divider/divider.a2ui.json +126 -0
  123. package/components/divider/divider.css +102 -0
  124. package/components/divider/divider.js +47 -0
  125. package/components/divider/divider.yaml +366 -0
  126. package/components/drawer/drawer.a2ui.json +199 -0
  127. package/components/drawer/drawer.css +342 -0
  128. package/components/drawer/drawer.js +263 -0
  129. package/components/drawer/drawer.yaml +366 -0
  130. package/components/embed/embed.a2ui.json +150 -0
  131. package/components/embed/embed.css +28 -0
  132. package/components/embed/embed.js +63 -0
  133. package/components/embed/embed.yaml +224 -0
  134. package/components/empty-state/empty-state.a2ui.json +133 -0
  135. package/components/empty-state/empty-state.css +58 -0
  136. package/components/empty-state/empty-state.js +87 -0
  137. package/components/empty-state/empty-state.yaml +314 -0
  138. package/components/footer/footer.a2ui.json +79 -0
  139. package/components/footer/footer.yaml +239 -0
  140. package/components/grid/grid.a2ui.json +171 -0
  141. package/components/grid/grid.css +37 -0
  142. package/components/grid/grid.js +21 -0
  143. package/components/grid/grid.yaml +577 -0
  144. package/components/header/header.a2ui.json +76 -0
  145. package/components/header/header.yaml +336 -0
  146. package/components/heatmap/heatmap.a2ui.json +150 -0
  147. package/components/heatmap/heatmap.css +146 -0
  148. package/components/heatmap/heatmap.js +246 -0
  149. package/components/heatmap/heatmap.yaml +131 -0
  150. package/components/icon/icon.a2ui.json +79 -0
  151. package/components/icon/icon.css +20 -0
  152. package/components/icon/icon.js +26 -0
  153. package/components/icon/icon.yaml +233 -0
  154. package/components/image/image.a2ui.json +261 -0
  155. package/components/image/image.css +76 -0
  156. package/components/image/image.js +102 -0
  157. package/components/image/image.yaml +477 -0
  158. package/components/index.js +85 -0
  159. package/components/input/input.a2ui.json +284 -0
  160. package/components/input/input.css +162 -0
  161. package/components/input/input.js +148 -0
  162. package/components/input/input.yaml +496 -0
  163. package/components/inspector/inspector.a2ui.json +67 -0
  164. package/components/inspector/inspector.css +31 -0
  165. package/components/inspector/inspector.js +133 -0
  166. package/components/inspector/inspector.yaml +58 -0
  167. package/components/kbd/kbd.a2ui.json +96 -0
  168. package/components/kbd/kbd.css +62 -0
  169. package/components/kbd/kbd.js +24 -0
  170. package/components/kbd/kbd.yaml +213 -0
  171. package/components/list/list.a2ui.json +145 -0
  172. package/components/list/list.css +103 -0
  173. package/components/list/list.js +236 -0
  174. package/components/list/list.yaml +122 -0
  175. package/components/menu/menu.a2ui.json +146 -0
  176. package/components/menu/menu.css +146 -0
  177. package/components/menu/menu.js +296 -0
  178. package/components/menu/menu.yaml +123 -0
  179. package/components/modal/modal.a2ui.json +136 -0
  180. package/components/modal/modal.css +153 -0
  181. package/components/modal/modal.js +181 -0
  182. package/components/modal/modal.yaml +114 -0
  183. package/components/noodles/noodles.a2ui.json +145 -0
  184. package/components/noodles/noodles.css +118 -0
  185. package/components/noodles/noodles.js +470 -0
  186. package/components/noodles/noodles.yaml +123 -0
  187. package/components/otp-input/otp-input.a2ui.json +104 -0
  188. package/components/otp-input/otp-input.css +78 -0
  189. package/components/otp-input/otp-input.js +170 -0
  190. package/components/otp-input/otp-input.yaml +218 -0
  191. package/components/pagination/pagination.a2ui.json +122 -0
  192. package/components/pagination/pagination.css +162 -0
  193. package/components/pagination/pagination.js +185 -0
  194. package/components/pagination/pagination.yaml +165 -0
  195. package/components/pane/pane.a2ui.json +94 -0
  196. package/components/pane/pane.css +166 -0
  197. package/components/pane/pane.js +140 -0
  198. package/components/pane/pane.yaml +197 -0
  199. package/components/pipeline-status/pipeline-status.a2ui.json +90 -0
  200. package/components/pipeline-status/pipeline-status.css +162 -0
  201. package/components/pipeline-status/pipeline-status.js +176 -0
  202. package/components/pipeline-status/pipeline-status.yaml +99 -0
  203. package/components/popover/popover.a2ui.json +181 -0
  204. package/components/popover/popover.css +57 -0
  205. package/components/popover/popover.js +170 -0
  206. package/components/popover/popover.yaml +257 -0
  207. package/components/progress/progress.a2ui.json +199 -0
  208. package/components/progress/progress.css +88 -0
  209. package/components/progress/progress.js +64 -0
  210. package/components/progress/progress.yaml +342 -0
  211. package/components/progress-row/progress-row.a2ui.json +100 -0
  212. package/components/progress-row/progress-row.css +57 -0
  213. package/components/progress-row/progress-row.js +92 -0
  214. package/components/progress-row/progress-row.yaml +87 -0
  215. package/components/radio/radio.a2ui.json +232 -0
  216. package/components/radio/radio.css +102 -0
  217. package/components/radio/radio.js +73 -0
  218. package/components/radio/radio.yaml +248 -0
  219. package/components/range/range.a2ui.json +151 -0
  220. package/components/range/range.css +148 -0
  221. package/components/range/range.js +177 -0
  222. package/components/range/range.yaml +188 -0
  223. package/components/rating/rating.a2ui.json +105 -0
  224. package/components/rating/rating.css +92 -0
  225. package/components/rating/rating.js +138 -0
  226. package/components/rating/rating.yaml +74 -0
  227. package/components/richtext/richtext.a2ui.json +82 -0
  228. package/components/richtext/richtext.css +225 -0
  229. package/components/richtext/richtext.js +74 -0
  230. package/components/richtext/richtext.yaml +89 -0
  231. package/components/row/row.a2ui.json +102 -0
  232. package/components/row/row.css +51 -0
  233. package/components/row/row.js +39 -0
  234. package/components/row/row.yaml +358 -0
  235. package/components/search/search.a2ui.json +186 -0
  236. package/components/search/search.css +28 -0
  237. package/components/search/search.js +124 -0
  238. package/components/search/search.yaml +154 -0
  239. package/components/section/section.a2ui.json +78 -0
  240. package/components/section/section.yaml +338 -0
  241. package/components/segment/segment.a2ui.json +100 -0
  242. package/components/segment/segment.css +81 -0
  243. package/components/segment/segment.js +32 -0
  244. package/components/segment/segment.yaml +216 -0
  245. package/components/segmented/segmented.a2ui.json +106 -0
  246. package/components/segmented/segmented.css +67 -0
  247. package/components/segmented/segmented.js +149 -0
  248. package/components/segmented/segmented.yaml +91 -0
  249. package/components/select/select.a2ui.json +203 -0
  250. package/components/select/select.css +277 -0
  251. package/components/select/select.js +388 -0
  252. package/components/select/select.yaml +375 -0
  253. package/components/skeleton/skeleton.a2ui.json +120 -0
  254. package/components/skeleton/skeleton.css +47 -0
  255. package/components/skeleton/skeleton.js +43 -0
  256. package/components/skeleton/skeleton.yaml +153 -0
  257. package/components/slider/slider.a2ui.json +162 -0
  258. package/components/slider/slider.css +137 -0
  259. package/components/slider/slider.js +162 -0
  260. package/components/slider/slider.yaml +299 -0
  261. package/components/stack/stack.a2ui.json +62 -0
  262. package/components/stack/stack.css +28 -0
  263. package/components/stack/stack.js +18 -0
  264. package/components/stack/stack.yaml +54 -0
  265. package/components/stat/stat.a2ui.json +246 -0
  266. package/components/stat/stat.css +101 -0
  267. package/components/stat/stat.js +91 -0
  268. package/components/stat/stat.yaml +206 -0
  269. package/components/stepper/stepper.a2ui.json +77 -0
  270. package/components/stepper/stepper.css +243 -0
  271. package/components/stepper/stepper.js +118 -0
  272. package/components/stepper/stepper.yaml +73 -0
  273. package/components/stream/stream.a2ui.json +98 -0
  274. package/components/stream/stream.css +37 -0
  275. package/components/stream/stream.js +99 -0
  276. package/components/stream/stream.yaml +87 -0
  277. package/components/swiper/swiper.a2ui.json +140 -0
  278. package/components/swiper/swiper.css +267 -0
  279. package/components/swiper/swiper.js +285 -0
  280. package/components/swiper/swiper.yaml +268 -0
  281. package/components/switch/switch.a2ui.json +134 -0
  282. package/components/switch/switch.css +104 -0
  283. package/components/switch/switch.js +53 -0
  284. package/components/switch/switch.yaml +322 -0
  285. package/components/table/cell-types.js +296 -0
  286. package/components/table/table.a2ui.json +458 -0
  287. package/components/table/table.css +531 -0
  288. package/components/table/table.js +1392 -0
  289. package/components/table/table.yaml +528 -0
  290. package/components/tabs/tab.js +34 -0
  291. package/components/tabs/tabs.a2ui.json +174 -0
  292. package/components/tabs/tabs.css +162 -0
  293. package/components/tabs/tabs.js +226 -0
  294. package/components/tabs/tabs.yaml +255 -0
  295. package/components/tag/tag.a2ui.json +148 -0
  296. package/components/tag/tag.css +118 -0
  297. package/components/tag/tag.js +88 -0
  298. package/components/tag/tag.yaml +125 -0
  299. package/components/text/text.a2ui.json +99 -0
  300. package/components/text/text.css +65 -0
  301. package/components/text/text.js +35 -0
  302. package/components/text/text.yaml +360 -0
  303. package/components/textarea/textarea.a2ui.json +91 -0
  304. package/components/textarea/textarea.css +93 -0
  305. package/components/textarea/textarea.js +114 -0
  306. package/components/textarea/textarea.yaml +79 -0
  307. package/components/timeline/timeline.a2ui.json +82 -0
  308. package/components/timeline/timeline.css +389 -0
  309. package/components/timeline/timeline.js +171 -0
  310. package/components/timeline/timeline.yaml +185 -0
  311. package/components/toast/toast.a2ui.json +199 -0
  312. package/components/toast/toast.css +211 -0
  313. package/components/toast/toast.js +146 -0
  314. package/components/toast/toast.yaml +184 -0
  315. package/components/toggle-group/toggle-group.a2ui.json +126 -0
  316. package/components/toggle-group/toggle-group.css +102 -0
  317. package/components/toggle-group/toggle-group.js +147 -0
  318. package/components/toggle-group/toggle-group.yaml +98 -0
  319. package/components/toolbar/toolbar.a2ui.json +131 -0
  320. package/components/toolbar/toolbar.css +132 -0
  321. package/components/toolbar/toolbar.js +366 -0
  322. package/components/toolbar/toolbar.yaml +238 -0
  323. package/components/tooltip/tooltip.a2ui.json +148 -0
  324. package/components/tooltip/tooltip.css +39 -0
  325. package/components/tooltip/tooltip.js +96 -0
  326. package/components/tooltip/tooltip.yaml +201 -0
  327. package/components/tree/tree.a2ui.json +119 -0
  328. package/components/tree/tree.css +133 -0
  329. package/components/tree/tree.js +253 -0
  330. package/components/tree/tree.yaml +92 -0
  331. package/components/upload/upload.a2ui.json +185 -0
  332. package/components/upload/upload.css +115 -0
  333. package/components/upload/upload.js +189 -0
  334. package/components/upload/upload.yaml +302 -0
  335. package/core/anchor.js +187 -0
  336. package/core/controller.js +182 -0
  337. package/core/element.js +257 -0
  338. package/core/form.js +217 -0
  339. package/core/icons.js +180 -0
  340. package/core/markdown.js +95 -0
  341. package/core/polyfills.js +23 -0
  342. package/core/provider.js +262 -0
  343. package/core/signals.js +113 -0
  344. package/core/template.js +226 -0
  345. package/core/transport.js +77 -0
  346. package/package.json +38 -0
  347. package/patterns/adia-chat/adia-chat.a2ui.json +149 -0
  348. package/patterns/adia-chat/adia-chat.css +10 -0
  349. package/patterns/adia-chat/adia-chat.js +297 -0
  350. package/patterns/adia-chat/adia-chat.yaml +118 -0
  351. package/patterns/adia-chat/css/adia-chat.empty.css +12 -0
  352. package/patterns/adia-chat/css/adia-chat.layout.css +60 -0
  353. package/patterns/adia-chat/css/adia-chat.markdown.css +74 -0
  354. package/patterns/adia-chat/css/adia-chat.messages.css +87 -0
  355. package/patterns/adia-chat/css/adia-chat.streaming.css +30 -0
  356. package/patterns/adia-chat/css/adia-chat.tokens.css +95 -0
  357. package/patterns/adia-chat/index.html +93 -0
  358. package/patterns/adia-editor/adia-editor.a2ui.json +58 -0
  359. package/patterns/adia-editor/adia-editor.css +6 -0
  360. package/patterns/adia-editor/adia-editor.js +56 -0
  361. package/patterns/adia-editor/adia-editor.yaml +36 -0
  362. package/patterns/adia-editor/css/adia-editor.layout.css +86 -0
  363. package/patterns/adia-editor/css/adia-editor.tokens.css +28 -0
  364. package/patterns/adia-editor/index.html +179 -0
  365. package/patterns/app-nav/app-nav.a2ui.json +89 -0
  366. package/patterns/app-nav/app-nav.css +92 -0
  367. package/patterns/app-nav/app-nav.js +99 -0
  368. package/patterns/app-nav/app-nav.yaml +54 -0
  369. package/patterns/app-nav-group/app-nav-group.a2ui.json +82 -0
  370. package/patterns/app-nav-group/app-nav-group.css +261 -0
  371. package/patterns/app-nav-group/app-nav-group.js +116 -0
  372. package/patterns/app-nav-group/app-nav-group.yaml +59 -0
  373. package/patterns/app-nav-item/app-nav-item.a2ui.json +83 -0
  374. package/patterns/app-nav-item/app-nav-item.css +156 -0
  375. package/patterns/app-nav-item/app-nav-item.js +42 -0
  376. package/patterns/app-nav-item/app-nav-item.yaml +62 -0
  377. package/patterns/app-shell/app-shell.a2ui.json +114 -0
  378. package/patterns/app-shell/app-shell.css +14 -0
  379. package/patterns/app-shell/app-shell.js +251 -0
  380. package/patterns/app-shell/app-shell.yaml +66 -0
  381. package/patterns/app-shell/css/app-shell.collapsed.css +86 -0
  382. package/patterns/app-shell/css/app-shell.helpers.css +42 -0
  383. package/patterns/app-shell/css/app-shell.main.css +58 -0
  384. package/patterns/app-shell/css/app-shell.shell.css +44 -0
  385. package/patterns/app-shell/css/app-shell.sidebar.css +116 -0
  386. package/patterns/app-shell/css/app-shell.templates.css +214 -0
  387. package/patterns/app-shell/css/app-shell.tokens.css +116 -0
  388. package/patterns/app-shell/index.html +112 -0
  389. package/patterns/gen-ui/gen-ui.a2ui.json +72 -0
  390. package/patterns/gen-ui/gen-ui.css +83 -0
  391. package/patterns/gen-ui/gen-ui.js +136 -0
  392. package/patterns/gen-ui/gen-ui.yaml +43 -0
  393. package/patterns/index.js +10 -0
  394. package/patterns/section-nav/section-nav.a2ui.json +91 -0
  395. package/patterns/section-nav/section-nav.css +59 -0
  396. package/patterns/section-nav/section-nav.js +42 -0
  397. package/patterns/section-nav/section-nav.yaml +58 -0
  398. package/patterns/section-nav-group/section-nav-group.a2ui.json +95 -0
  399. package/patterns/section-nav-group/section-nav-group.css +74 -0
  400. package/patterns/section-nav-group/section-nav-group.js +84 -0
  401. package/patterns/section-nav-group/section-nav-group.yaml +66 -0
  402. package/patterns/section-nav-item/section-nav-item.a2ui.json +97 -0
  403. package/patterns/section-nav-item/section-nav-item.css +96 -0
  404. package/patterns/section-nav-item/section-nav-item.js +66 -0
  405. package/patterns/section-nav-item/section-nav-item.yaml +70 -0
  406. package/styles/colors/index.css +6 -0
  407. package/styles/colors/parameters.css +52 -0
  408. package/styles/colors/primitives-accent.css +89 -0
  409. package/styles/colors/primitives-brand.css +89 -0
  410. package/styles/colors/primitives-danger.css +89 -0
  411. package/styles/colors/primitives-info.css +89 -0
  412. package/styles/colors/primitives-neutral.css +91 -0
  413. package/styles/colors/primitives-shared.css +57 -0
  414. package/styles/colors/primitives-success.css +89 -0
  415. package/styles/colors/primitives-warning.css +89 -0
  416. package/styles/colors/primitives.css +17 -0
  417. package/styles/colors/scrims.css +182 -0
  418. package/styles/colors/semantics.css +595 -0
  419. package/styles/colors/surfaces.css +43 -0
  420. package/styles/fonts.css +99 -0
  421. package/styles/layouts/admin.css +7 -0
  422. package/styles/prose.css +186 -0
  423. package/styles/styles.css +193 -0
  424. package/styles/themes.css +155 -0
  425. package/styles/tokens.css +304 -0
  426. package/styles/typography.css +853 -0
  427. package/traits/active-state.js +24 -0
  428. package/traits/anchor-positioning.js +66 -0
  429. package/traits/attention-pulse.js +30 -0
  430. package/traits/confetti-burst.js +65 -0
  431. package/traits/confetti.js +57 -0
  432. package/traits/count-up.js +42 -0
  433. package/traits/define.js +76 -0
  434. package/traits/dirty-state.js +38 -0
  435. package/traits/drag-ghost.js +38 -0
  436. package/traits/draggable.js +73 -0
  437. package/traits/fade-presence.js +52 -0
  438. package/traits/focus-trap.js +63 -0
  439. package/traits/focusable.js +38 -0
  440. package/traits/glow-focus.js +33 -0
  441. package/traits/gradient-shift.js +32 -0
  442. package/traits/haptic-feedback.js +28 -0
  443. package/traits/hotkey.js +62 -0
  444. package/traits/hoverable.js +26 -0
  445. package/traits/index.js +55 -0
  446. package/traits/inertia-drag.js +133 -0
  447. package/traits/intersection-observer.js +33 -0
  448. package/traits/keyboard-nav.js +36 -0
  449. package/traits/magnetic-hover.js +37 -0
  450. package/traits/noise-texture.js +30 -0
  451. package/traits/parallax.js +42 -0
  452. package/traits/portal.js +27 -0
  453. package/traits/pressable.js +75 -0
  454. package/traits/resizable.js +100 -0
  455. package/traits/resize-observer.js +31 -0
  456. package/traits/ripple.js +53 -0
  457. package/traits/roving-tabindex.js +67 -0
  458. package/traits/scale-press.js +43 -0
  459. package/traits/scroll-lock.js +27 -0
  460. package/traits/shimmer-loading.js +44 -0
  461. package/traits/snap-to-grid.js +28 -0
  462. package/traits/sound-feedback.js +30 -0
  463. package/traits/spring-animate.js +56 -0
  464. package/traits/tilt-hover.js +37 -0
  465. package/traits/tossable.js +178 -0
  466. package/traits/typeahead.js +63 -0
  467. package/traits/typewriter.js +35 -0
  468. package/traits/validation.js +118 -0
@@ -0,0 +1,1075 @@
1
+ /**
2
+ * <chart-ui type="bar" x="month" y="revenue" heading="Monthly Revenue"></chart-ui>
3
+ *
4
+ * Declarative chart component supporting bar, line, pie, donut, radar,
5
+ * sparkline, stacked-bar, grouped-bar, and multi-line chart types.
6
+ *
7
+ * Attributes:
8
+ * type — chart type (default: 'bar')
9
+ * heading — chart heading text
10
+ * x — key for X-axis data
11
+ * y — key(s) for Y-axis, comma-separated for multi-series
12
+ * hide-average — suppress the overlaid average line on bar/line (default: false — line shown)
13
+ * color — accent/success/warning/danger/info
14
+ * hide-grid — hide gridlines
15
+ * hide-values — hide value labels
16
+ * radius — bar corner radius (default: 4)
17
+ *
18
+ * JS API:
19
+ * .data = [{...}, ...] — array of data objects
20
+ */
21
+
22
+ import { AdiaElement } from '@core/element.js';
23
+
24
+ /* ── Helpers ────────────────────────────────────────────────────── */
25
+
26
+ function niceScale(min, max, ticks = 5) {
27
+ const range = max - min || 1;
28
+ const rough = range / ticks;
29
+ const mag = Math.pow(10, Math.floor(Math.log10(rough)));
30
+ const norm = rough / mag;
31
+ const nice = norm <= 1.5 ? 1 : norm <= 3 ? 2 : norm <= 7 ? 5 : 10;
32
+ const step = nice * mag;
33
+ const lo = Math.floor(min / step) * step;
34
+ const hi = Math.ceil(max / step) * step;
35
+ const result = [];
36
+ for (let v = lo; v <= hi + step * 0.5; v += step) result.push(+v.toFixed(10));
37
+ return result;
38
+ }
39
+
40
+ function fmt(v) {
41
+ if (v == null) return '';
42
+ const n = +v;
43
+ if (Number.isNaN(n)) return String(v);
44
+ if (Math.abs(n) >= 1e6) return (n / 1e6).toFixed(1).replace(/\.0$/, '') + 'M';
45
+ if (Math.abs(n) >= 1e3) return (n / 1e3).toFixed(1).replace(/\.0$/, '') + 'K';
46
+ return Number.isInteger(n) ? String(n) : n.toFixed(1);
47
+ }
48
+
49
+ function esc(s) {
50
+ return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
51
+ }
52
+
53
+ /* Emit tooltip data-attributes for a datum shape. Consumed by the pointer
54
+ delegate below. All fields optional — unused ones are skipped. */
55
+ function tip({ label, value, pct, series }) {
56
+ let s = '';
57
+ if (label != null) s += ` data-tip-label="${esc(String(label))}"`;
58
+ if (value != null) s += ` data-tip-value="${value}"`;
59
+ if (pct != null) s += ` data-tip-pct="${pct}"`;
60
+ if (series != null) s += ` data-tip-series="${esc(String(series))}"`;
61
+ return s;
62
+ }
63
+
64
+ /* Polar helpers for pie/donut */
65
+ function polarX(cx, r, angle) { return cx + r * Math.cos(angle); }
66
+ function polarY(cy, r, angle) { return cy + r * Math.sin(angle); }
67
+
68
+ function arcPath(cx, cy, r, start, end) {
69
+ const x1 = polarX(cx, r, start);
70
+ const y1 = polarY(cy, r, start);
71
+ const x2 = polarX(cx, r, end);
72
+ const y2 = polarY(cy, r, end);
73
+ const large = end - start > Math.PI ? 1 : 0;
74
+ return `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`;
75
+ }
76
+
77
+ function donutArcPath(cx, cy, outer, inner, start, end, radius = 0) {
78
+ // Radius is clamped to half the ring thickness. A radius of 0 renders
79
+ // a flat (original) wedge with sharp corners.
80
+ const ringHalf = (outer - inner) / 2;
81
+ const sliceAngle = end - start;
82
+ const r = Math.max(0, Math.min(radius, ringHalf));
83
+ const flatPath = () => {
84
+ const x1 = polarX(cx, outer, start);
85
+ const y1 = polarY(cy, outer, start);
86
+ const x2 = polarX(cx, outer, end);
87
+ const y2 = polarY(cy, outer, end);
88
+ const x3 = polarX(cx, inner, end);
89
+ const y3 = polarY(cy, inner, end);
90
+ const x4 = polarX(cx, inner, start);
91
+ const y4 = polarY(cy, inner, start);
92
+ const large = sliceAngle > Math.PI ? 1 : 0;
93
+ return `M ${x1} ${y1} A ${outer} ${outer} 0 ${large} 1 ${x2} ${y2} L ${x3} ${y3} A ${inner} ${inner} 0 ${large} 0 ${x4} ${y4} Z`;
94
+ };
95
+ if (r <= 0) return flatPath();
96
+
97
+ // Rounded-CORNER wedge (rounded rectangle-on-ring).
98
+ // Each end has two small corner arcs connected by a short radial line,
99
+ // so thin slices on thick rings don't collapse into pill shapes.
100
+ const aOuter = r / outer;
101
+ const aInner = r / inner;
102
+ if (sliceAngle <= (aOuter + aInner) * 1.05) return flatPath();
103
+
104
+ const sOuter = start + aOuter;
105
+ const eOuter = end - aOuter;
106
+ const sInner = start + aInner;
107
+ const eInner = end - aInner;
108
+ const large = (eOuter - sOuter) > Math.PI ? 1 : 0;
109
+
110
+ // Points: outer-arc ends, radial-line ends at each angular extremum.
111
+ const ox1 = polarX(cx, outer, sOuter);
112
+ const oy1 = polarY(cy, outer, sOuter);
113
+ const ox2 = polarX(cx, outer, eOuter);
114
+ const oy2 = polarY(cy, outer, eOuter);
115
+ const ix2 = polarX(cx, inner, eInner);
116
+ const iy2 = polarY(cy, inner, eInner);
117
+ const ix1 = polarX(cx, inner, sInner);
118
+ const iy1 = polarY(cy, inner, sInner);
119
+
120
+ // Radial-line endpoints: the corner pushes the radial inward by `r`,
121
+ // so the straight segment runs from (outer-r) down to (inner+r) at the
122
+ // end angle, and symmetrically at the start angle.
123
+ const rsOuter = polarX(cx, outer - r, start), rsOuterY = polarY(cy, outer - r, start);
124
+ const rsInner = polarX(cx, inner + r, start), rsInnerY = polarY(cy, inner + r, start);
125
+ const reOuter = polarX(cx, outer - r, end), reOuterY = polarY(cy, outer - r, end);
126
+ const reInner = polarX(cx, inner + r, end), reInnerY = polarY(cy, inner + r, end);
127
+
128
+ return `M ${ox1} ${oy1} ` +
129
+ `A ${outer} ${outer} 0 ${large} 1 ${ox2} ${oy2} ` + // outer arc (CW)
130
+ `A ${r} ${r} 0 0 1 ${reOuter} ${reOuterY} ` + // end outer corner
131
+ `L ${reInner} ${reInnerY} ` + // end radial line
132
+ `A ${r} ${r} 0 0 1 ${ix2} ${iy2} ` + // end inner corner
133
+ `A ${inner} ${inner} 0 ${large} 0 ${ix1} ${iy1} ` + // inner arc (CCW)
134
+ `A ${r} ${r} 0 0 1 ${rsInner} ${rsInnerY} ` + // start inner corner
135
+ `L ${rsOuter} ${rsOuterY} ` + // start radial line
136
+ `A ${r} ${r} 0 0 1 ${ox1} ${oy1} Z`; // start outer corner
137
+ }
138
+
139
+ /**
140
+ * Smooth path — converts points [{x,y}] to a cubic bezier SVG path.
141
+ * t = 0 → straight lines (polyline), t = 1 → maximum smoothing.
142
+ * Uses Catmull-Rom-to-Bezier spline conversion.
143
+ */
144
+ function smoothPath(points, t = 0.4) {
145
+ if (points.length < 2) return '';
146
+ if (t <= 0) return 'M' + points.map(p => `${p.x},${p.y}`).join(' L');
147
+
148
+ const n = points.length;
149
+ let d = `M${points[0].x},${points[0].y}`;
150
+
151
+ for (let i = 0; i < n - 1; i++) {
152
+ const p0 = points[Math.max(i - 1, 0)];
153
+ const p1 = points[i];
154
+ const p2 = points[i + 1];
155
+ const p3 = points[Math.min(i + 2, n - 1)];
156
+
157
+ const cp1x = p1.x + (p2.x - p0.x) * t / 3;
158
+ const cp1y = p1.y + (p2.y - p0.y) * t / 3;
159
+ const cp2x = p2.x - (p3.x - p1.x) * t / 3;
160
+ const cp2y = p2.y - (p3.y - p1.y) * t / 3;
161
+
162
+ d += ` C${cp1x},${cp1y} ${cp2x},${cp2y} ${p2.x},${p2.y}`;
163
+ }
164
+
165
+ return d;
166
+ }
167
+
168
+ /** Build a closed area path from a smooth line path + baseline Y */
169
+ function smoothAreaPath(points, baselineY, t = 0.4) {
170
+ const line = smoothPath(points, t);
171
+ const last = points[points.length - 1];
172
+ const first = points[0];
173
+ return `${line} L${last.x},${baselineY} L${first.x},${baselineY} Z`;
174
+ }
175
+
176
+ /* ── Aspect ratios ─────────────────────────────────────────────── */
177
+
178
+ const ASPECTS = {
179
+ std: { ratio: 4 / 3 }, // default — balanced dataviz proportion
180
+ wide: { ratio: 16 / 9 }, // landscape / video (sparkline, timeline)
181
+ square: { ratio: 1 }, // pie / donut / radar
182
+ tall: { ratio: 3 / 4 }, // vertical column
183
+ };
184
+
185
+ /* ── Component ──────────────────────────────────────────────────── */
186
+
187
+ class AdiaChart extends AdiaElement {
188
+ static properties = {
189
+ type: { type: String, default: 'bar', reflect: true },
190
+ heading: { type: String, default: '', reflect: true },
191
+ x: { type: String, default: '', reflect: true },
192
+ y: { type: String, default: '', reflect: true },
193
+ hideAverage: { type: Boolean, default: false, reflect: true, attribute: 'hide-average' },
194
+ color: { type: String, default: '', reflect: true },
195
+ hideGrid: { type: Boolean, default: false, reflect: true, attribute: 'hide-grid' },
196
+ hideValues: { type: Boolean, default: false, reflect: true, attribute: 'hide-values' },
197
+ radius: { type: Number, default: null, reflect: true },
198
+ smooth: { type: Number, default: 0.4, reflect: true },
199
+ aspect: { type: String, default: 'std', reflect: true }, // std | wide | square | tall
200
+ size: { type: String, default: '', reflect: true }, // sm | md | lg
201
+ };
202
+
203
+ static template = () => null;
204
+
205
+ #data = [];
206
+ #resizeObs = null;
207
+ #resizeRaf = null;
208
+ #lastW = 0;
209
+ #lastH = 0;
210
+
211
+ /** Resolves the corner radius: the `radius` prop if explicitly set,
212
+ * otherwise `--a-radius` from the host's computed style. Falls back
213
+ * to 6 if the token is unset (should not happen in practice). */
214
+ #resolveRadius() {
215
+ if (this.radius != null) return this.radius;
216
+ const val = getComputedStyle(this).getPropertyValue('--a-radius').trim();
217
+ const parsed = parseFloat(val);
218
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 6;
219
+ }
220
+
221
+ set data(arr) {
222
+ this.#data = Array.isArray(arr) ? arr : [];
223
+ this.#renderChart();
224
+ }
225
+
226
+ get data() {
227
+ return this.#data;
228
+ }
229
+
230
+ connected() {
231
+ this.#resizeObs = new ResizeObserver((entries) => {
232
+ const { inlineSize: w, blockSize: h } = entries[0].contentBoxSize[0];
233
+ if (!this.#data.length) return;
234
+ const rw = Math.round(w);
235
+ const rh = Math.round(h);
236
+ if (rw === this.#lastW && (!this.hasAttribute('resize') || rh === this.#lastH)) return;
237
+ this.#lastW = rw;
238
+ this.#lastH = rh;
239
+ if (this.#resizeRaf) return;
240
+ this.#resizeRaf = requestAnimationFrame(() => {
241
+ this.#resizeRaf = null;
242
+ this.#renderChart();
243
+ });
244
+ });
245
+ this.#resizeObs.observe(this);
246
+ }
247
+
248
+ render() {
249
+ this.#renderChart();
250
+ }
251
+
252
+ /**
253
+ * Compute layout dimensions from actual container width.
254
+ * Returns { width, height, pad, fontSize, labelSize, valueSize, barMinW }
255
+ */
256
+ #dims() {
257
+ const containerW = this.clientWidth || 300;
258
+ const containerH = this.clientHeight || 0;
259
+ const aspect = ASPECTS[this.aspect] || ASPECTS.std;
260
+ const n = this.#data.length || 1;
261
+
262
+ const width = Math.max(containerW, 120);
263
+ // When element has explicit height (resize handle, CSS height, inline style),
264
+ // use it. Otherwise derive from aspect ratio. Safe because resize sets
265
+ // overflow:auto which prevents content from pushing height.
266
+ const hasExplicitHeight = this.hasAttribute('resize') || this.style.height;
267
+ let height = hasExplicitHeight && containerH > 40
268
+ ? containerH
269
+ : Math.round(width / aspect.ratio);
270
+
271
+ // Cap against --chart-max-height (CSS-resolved to px by getComputedStyle).
272
+ // Keeps wide-viewport renders from producing absurdly tall charts while
273
+ // staying overridable — set --chart-max-height: none (or a larger value)
274
+ // per-chart to opt out.
275
+ const maxH = parseFloat(getComputedStyle(this).maxHeight);
276
+ if (isFinite(maxH) && maxH > 0 && height > maxH) height = Math.round(maxH);
277
+
278
+ // Scale-aware font sizes: consistent regardless of chart dimensions
279
+ // Use sm/md/lg or auto-detect from smallest dimension
280
+ const minDim = Math.min(width, height);
281
+ const sizeClass = this.size || (minDim < 150 ? 'sm' : minDim < 300 ? 'md' : 'lg');
282
+ const fontSize = sizeClass === 'sm' ? 9 : sizeClass === 'md' ? 10 : 11;
283
+ const labelSize = fontSize;
284
+ const valueSize = sizeClass === 'sm' ? 8 : sizeClass === 'md' ? 9 : 10;
285
+
286
+ // Padding scales with font size and axis visibility.
287
+ // When the grid is hidden (in-card sparkline-like use) we can zero
288
+ // out the Y-axis label gutter entirely — otherwise the plot area
289
+ // collapses at small sizes.
290
+ const hideGrid = this.hideGrid;
291
+ const yLabelW = hideGrid ? 0 : fontSize * 3.2;
292
+ const hasXLabels = !!this.x;
293
+ const pad = {
294
+ top: hideGrid ? 2 : fontSize * 1.6,
295
+ right: hideGrid ? 2 : fontSize * 1.2,
296
+ bottom: !hasXLabels || hideGrid ? 2 : fontSize * 2.2,
297
+ left: yLabelW,
298
+ };
299
+
300
+ // Bar width adapts to data count + container
301
+ const plotW = width - pad.left - pad.right;
302
+ const barMinW = Math.max(4, plotW / n * 0.6);
303
+
304
+ return { width, height, pad, fontSize, labelSize, valueSize, barMinW, plotW, n, sizeClass };
305
+ }
306
+
307
+ /* ── Main render ──────────────────────────────────────────────── */
308
+
309
+ #renderChart() {
310
+ if (!this.isConnected) return;
311
+ this.innerHTML = '';
312
+
313
+ if (!this.#data.length) return;
314
+
315
+ if (this.heading) {
316
+ const headingEl = document.createElement('div');
317
+ headingEl.setAttribute('data-chart-heading', '');
318
+ headingEl.textContent = this.heading;
319
+ this.appendChild(headingEl);
320
+ }
321
+
322
+ const svgWrap = document.createElement('div');
323
+ if (this.type === 'sparkline') svgWrap.setAttribute('data-sparkline', '');
324
+ this.appendChild(svgWrap);
325
+
326
+ const svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
327
+ svgWrap.appendChild(svgEl);
328
+
329
+ let svgContent = '';
330
+ let vb = '';
331
+
332
+ switch (this.type) {
333
+ case 'bar': ({ svg: svgContent, viewBox: vb } = this.#renderBar()); break;
334
+ case 'line': ({ svg: svgContent, viewBox: vb } = this.#renderLine()); break;
335
+ case 'pie': ({ svg: svgContent, viewBox: vb } = this.#renderPie()); break;
336
+ case 'donut': ({ svg: svgContent, viewBox: vb } = this.#renderDonut()); break;
337
+ case 'radar': ({ svg: svgContent, viewBox: vb } = this.#renderRadar()); break;
338
+ case 'sparkline': ({ svg: svgContent, viewBox: vb } = this.#renderSparkline()); break;
339
+ case 'segments': ({ svg: svgContent, viewBox: vb } = this.#renderSegments()); break;
340
+ case 'stacked-bar': ({ svg: svgContent, viewBox: vb } = this.#renderStackedBar()); break;
341
+ case 'grouped-bar': ({ svg: svgContent, viewBox: vb } = this.#renderGroupedBar()); break;
342
+ case 'multi-line': ({ svg: svgContent, viewBox: vb } = this.#renderMultiLine()); break;
343
+ default: ({ svg: svgContent, viewBox: vb } = this.#renderBar()); break;
344
+ }
345
+
346
+ svgEl.setAttribute('viewBox', vb);
347
+ svgEl.setAttribute('preserveAspectRatio', 'xMidYMid meet');
348
+ svgEl.innerHTML = svgContent;
349
+
350
+ /* Append legend for types that need it */
351
+ if (['pie', 'donut', 'stacked-bar', 'grouped-bar', 'multi-line'].includes(this.type)) {
352
+ const legend = this.#buildLegend();
353
+ if (legend) this.appendChild(legend);
354
+ }
355
+
356
+ /* Wire hover tooltip — pointer delegate on the SVG reads data-tip-*
357
+ attrs from the shape under the cursor and shows a single shared
358
+ popover. See #showTooltip / #hideTooltip below. */
359
+ svgEl.addEventListener('pointerover', this.#onPointerOver);
360
+ svgEl.addEventListener('pointermove', this.#onPointerMove);
361
+ svgEl.addEventListener('pointerleave', this.#onPointerLeave);
362
+ }
363
+
364
+ /* ── Tooltip ───────────────────────────────────────────────────── */
365
+
366
+ #tipEl = null;
367
+
368
+ disconnected() {
369
+ this.#resizeObs?.disconnect();
370
+ this.#resizeObs = null;
371
+ if (this.#resizeRaf) { cancelAnimationFrame(this.#resizeRaf); this.#resizeRaf = null; }
372
+ this.#hideTooltip();
373
+ }
374
+
375
+ #onPointerOver = (e) => {
376
+ const t = e.target.closest('[data-tip-label], [data-tip-value]');
377
+ if (t) this.#showTooltip(t, e);
378
+ };
379
+
380
+ #onPointerMove = (e) => {
381
+ const t = e.target.closest('[data-tip-label], [data-tip-value]');
382
+ if (!t) return this.#hideTooltip();
383
+ this.#showTooltip(t, e);
384
+ };
385
+
386
+ #onPointerLeave = () => this.#hideTooltip();
387
+
388
+ #showTooltip(target, event) {
389
+ const { tipLabel, tipValue, tipPct, tipSeries } = target.dataset;
390
+
391
+ if (!this.#tipEl) {
392
+ const el = document.createElement('div');
393
+ el.setAttribute('popover', 'manual');
394
+ el.setAttribute('role', 'tooltip');
395
+ el.classList.add('chart-tooltip-popup');
396
+ document.body.appendChild(el);
397
+ this.#tipEl = el;
398
+ }
399
+
400
+ const lines = [];
401
+ if (tipSeries) lines.push(`<span data-tip-role="series">${esc(tipSeries)}</span>`);
402
+ if (tipLabel) lines.push(`<span data-tip-role="label">${esc(tipLabel)}</span>`);
403
+ if (tipValue !== undefined) {
404
+ const pct = tipPct !== undefined ? ` <span data-tip-role="pct">(${tipPct}%)</span>` : '';
405
+ lines.push(`<span data-tip-role="value">${fmt(tipValue)}${pct}</span>`);
406
+ }
407
+ this.#tipEl.innerHTML = lines.join('');
408
+
409
+ try { this.#tipEl.showPopover(); } catch (_) { /* popover not supported */ }
410
+
411
+ /* Follow the cursor — offset up-right, clamp to viewport */
412
+ const gap = 12;
413
+ const { clientX, clientY } = event;
414
+ const tw = this.#tipEl.offsetWidth || 0;
415
+ const th = this.#tipEl.offsetHeight || 0;
416
+ let x = clientX + gap;
417
+ let y = clientY - th - gap;
418
+ if (x + tw > window.innerWidth) x = clientX - tw - gap;
419
+ if (y < 0) y = clientY + gap;
420
+ this.#tipEl.style.left = `${x}px`;
421
+ this.#tipEl.style.top = `${y}px`;
422
+ }
423
+
424
+ #hideTooltip() {
425
+ if (!this.#tipEl) return;
426
+ try { this.#tipEl.hidePopover(); } catch (_) { /* */ }
427
+ this.#tipEl.remove();
428
+ this.#tipEl = null;
429
+ }
430
+
431
+ /* ── Y keys helper ────────────────────────────────────────────── */
432
+
433
+ #yKeys() {
434
+ return this.y ? this.y.split(',').map(k => k.trim()).filter(Boolean) : [];
435
+ }
436
+
437
+ /* ── Grid + axes helper ───────────────────────────────────────── */
438
+
439
+ #gridAndAxes(width, height, ticks, labels, pad, dims) {
440
+ const p = pad;
441
+ const fs = dims?.fontSize || 10;
442
+ const ls = dims?.labelSize || fs;
443
+ let s = '';
444
+
445
+ // Reduce tick count at small sizes
446
+ let displayTicks = ticks;
447
+ if (dims?.sizeClass === 'sm' && ticks.length > 4) {
448
+ displayTicks = ticks.filter((_, i) => i % 2 === 0 || i === ticks.length - 1);
449
+ }
450
+
451
+ // hideGrid suppresses both gridlines AND axis labels — callers who
452
+ // want labels without gridlines can omit hide-grid and rely on token
453
+ // overrides to make gridlines transparent. This keeps compact in-card
454
+ // charts visually clean.
455
+ if (!this.hideGrid) {
456
+ const tickRange = ticks[ticks.length - 1] - ticks[0];
457
+ const safeRange = tickRange || 1; // Prevent division by zero
458
+ for (const t of displayTicks) {
459
+ const gy = p.top + (height - p.top - p.bottom) * (1 - (t - ticks[0]) / safeRange);
460
+ s += `<line data-grid x1="${p.left}" y1="${gy}" x2="${width - p.right}" y2="${gy}"/>`;
461
+ }
462
+
463
+ /* Y-axis labels */
464
+ for (const t of displayTicks) {
465
+ const gy = p.top + (height - p.top - p.bottom) * (1 - (t - ticks[0]) / safeRange);
466
+ s += `<text data-y-label x="${p.left - 4}" y="${gy + fs * 0.35}" text-anchor="end" font-size="${fs}">${fmt(t)}</text>`;
467
+ }
468
+
469
+ /* X-axis labels — stride based on label width so they never overlap */
470
+ if (labels) {
471
+ const plotW = width - p.left - p.right;
472
+ const step = plotW / labels.length;
473
+ const maxChars = labels.reduce((m, l) => Math.max(m, String(l).length), 1);
474
+ const labelPx = maxChars * ls * 0.6 + ls * 0.75;
475
+ const stride = Math.max(1, Math.ceil(labelPx / step));
476
+ const last = labels.length - 1;
477
+ for (let i = 0; i < labels.length; i++) {
478
+ if (i % stride !== 0 && i !== last) continue;
479
+ if (i !== last && last - i < stride) continue;
480
+ const lx = p.left + step * i + step / 2;
481
+ s += `<text data-x-label x="${lx}" y="${height - fs * 0.5}" text-anchor="middle" font-size="${ls}">${esc(labels[i])}</text>`;
482
+ }
483
+ }
484
+ }
485
+
486
+ return s;
487
+ }
488
+
489
+ /* ── Bar chart ────────────────────────────────────────────────── */
490
+
491
+ #renderBar() {
492
+ const dims = this.#dims();
493
+ const data = this.#data;
494
+ const yKey = this.#yKeys()[0] || this.y;
495
+ const vals = data.map(v => +(v[yKey] ?? 0));
496
+ const labels = data.map(v => v[this.x] ?? '');
497
+ const ticks = niceScale(0, Math.max(...vals), 5);
498
+ const maxVal = ticks[ticks.length - 1];
499
+
500
+ const { width, height, pad } = dims;
501
+ const plotH = height - pad.top - pad.bottom;
502
+ const plotW = width - pad.left - pad.right;
503
+ const barW = plotW / data.length;
504
+ const barInner = barW * 0.6;
505
+ const barGap = (barW - barInner) / 2;
506
+
507
+ let svg = this.#gridAndAxes(width, height, ticks, labels, pad, dims);
508
+
509
+ // At sm auto-hide value labels (they overlap at narrow widths).
510
+ const showValues = !this.hideValues && dims.sizeClass !== 'sm';
511
+ const showAverage = !this.hideAverage && vals.length > 1 && dims.sizeClass !== 'sm' && !this.hideGrid;
512
+
513
+ for (let i = 0; i < data.length; i++) {
514
+ const v = vals[i];
515
+ const barH = maxVal ? (v / maxVal) * plotH : 0;
516
+ const bx = pad.left + barW * i + barGap;
517
+ const by = pad.top + plotH - barH;
518
+
519
+ svg += `<rect data-bar${tip({ label: labels[i], value: v })} x="${bx}" y="${by}" width="${barInner}" height="${barH}" rx="${this.#resolveRadius()}"/>`;
520
+
521
+ if (showValues) {
522
+ svg += `<text data-value x="${bx + barInner / 2}" y="${by - 4}" text-anchor="middle" font-size="${dims.valueSize}">${fmt(v)}</text>`;
523
+ }
524
+ }
525
+
526
+ if (showAverage) {
527
+ const avg = vals.reduce((a, b) => a + b, 0) / vals.length;
528
+ const ay = pad.top + plotH - (maxVal ? (avg / maxVal) * plotH : 0);
529
+ svg += `<line data-avg x1="${pad.left}" y1="${ay}" x2="${width - pad.right}" y2="${ay}"/>`;
530
+ svg += `<text data-avg-label x="${width - pad.right + 2}" y="${ay + 3}" text-anchor="start" font-size="${dims.valueSize}">${fmt(avg)}</text>`;
531
+ /* Wider invisible hit target so the thin dashed line is hoverable */
532
+ svg += `<line data-hit${tip({ label: 'Average', value: avg })} x1="${pad.left}" y1="${ay}" x2="${width - pad.right}" y2="${ay}" stroke="transparent" stroke-width="12"/>`;
533
+ }
534
+
535
+ return { svg, viewBox: `0 0 ${width} ${height}` };
536
+ }
537
+
538
+ /* ── Line chart ───────────────────────────────────────────────── */
539
+
540
+ #renderLine() {
541
+ const dims = this.#dims();
542
+ const data = this.#data;
543
+ const yKey = this.#yKeys()[0] || this.y;
544
+ const vals = data.map(v => +(v[yKey] ?? 0));
545
+ const labels = data.map(v => v[this.x] ?? '');
546
+ const ticks = niceScale(0, Math.max(...vals), 5);
547
+ const maxVal = ticks[ticks.length - 1];
548
+
549
+ const { width, height, pad } = dims;
550
+ const plotH = height - pad.top - pad.bottom;
551
+ const plotW = width - pad.left - pad.right;
552
+ const step = plotW / Math.max(data.length - 1, 1);
553
+
554
+ let svg = this.#gridAndAxes(width, height, ticks, labels, pad, dims);
555
+
556
+ const points = vals.map((v, i) => {
557
+ const px = pad.left + step * i;
558
+ const py = pad.top + plotH - (maxVal ? (v / maxVal) * plotH : 0);
559
+ return { x: px, y: py, v, label: labels[i] };
560
+ });
561
+
562
+ const baseline = pad.top + plotH;
563
+ const t = Math.max(0, Math.min(1, this.smooth));
564
+ svg += `<path data-area d="${smoothAreaPath(points, baseline, t)}"/>`;
565
+ svg += `<path data-line d="${smoothPath(points, t)}"/>`;
566
+
567
+ // Density tuning for sm: smaller dots, no value labels.
568
+ const isSm = dims.sizeClass === 'sm';
569
+ const dotR = isSm ? 1.5 : 3;
570
+ const hitR = Math.max(dotR, 10); // invisible hit-target for tooltip
571
+ const showValues = !this.hideValues && !isSm;
572
+ const showAverage = !this.hideAverage && vals.length > 1 && !isSm && !this.hideGrid;
573
+
574
+ for (const p of points) {
575
+ svg += `<circle data-dot cx="${p.x}" cy="${p.y}" r="${dotR}"/>`;
576
+ svg += `<circle data-hit${tip({ label: p.label, value: p.v })} cx="${p.x}" cy="${p.y}" r="${hitR}" fill="transparent"/>`;
577
+ if (showValues) {
578
+ svg += `<text data-value x="${p.x}" y="${p.y - 8}" text-anchor="middle" font-size="${dims.valueSize}">${fmt(p.v)}</text>`;
579
+ }
580
+ }
581
+
582
+ if (showAverage) {
583
+ const avg = vals.reduce((a, b) => a + b, 0) / vals.length;
584
+ const ay = pad.top + plotH - (maxVal ? (avg / maxVal) * plotH : 0);
585
+ svg += `<line data-avg x1="${pad.left}" y1="${ay}" x2="${width - pad.right}" y2="${ay}"/>`;
586
+ svg += `<text data-avg-label x="${width - pad.right + 2}" y="${ay + 3}" text-anchor="start" font-size="${dims.valueSize}">${fmt(avg)}</text>`;
587
+ /* Wider invisible hit target so the thin dashed line is hoverable */
588
+ svg += `<line data-hit${tip({ label: 'Average', value: avg })} x1="${pad.left}" y1="${ay}" x2="${width - pad.right}" y2="${ay}" stroke="transparent" stroke-width="12"/>`;
589
+ }
590
+
591
+ return { svg, viewBox: `0 0 ${width} ${height}` };
592
+ }
593
+
594
+ /* ── Pie chart ────────────────────────────────────────────────── */
595
+
596
+ #renderPie() {
597
+ const data = this.#data;
598
+ const yKey = this.#yKeys()[0] || this.y;
599
+ const vals = data.map(d => +(d[yKey] ?? 0));
600
+ const total = vals.reduce((a, b) => a + b, 0) || 1;
601
+ const labels = data.map(d => d[this.x] ?? '');
602
+
603
+ // Responsive viewBox so pie respects chart-ui's max-height and
604
+ // doesn't push the legend past the container bounds.
605
+ const dims = this.#dims();
606
+ const { width, height } = dims;
607
+ const cx = width / 2;
608
+ const cy = height / 2;
609
+ const r = Math.max(30, Math.min(width, height) * 0.42);
610
+
611
+ let svg = '';
612
+ let angle = -Math.PI / 2;
613
+
614
+ for (let i = 0; i < vals.length; i++) {
615
+ const slice = (vals[i] / total) * Math.PI * 2;
616
+ if (slice === 0) { continue; }
617
+ const end = angle + slice;
618
+
619
+ const pct = ((vals[i] / total) * 100).toFixed(1);
620
+ const attrs = ` data-slice="${i % 10}"${tip({ label: labels[i], value: vals[i], pct })}`;
621
+
622
+ if (Math.abs(slice - Math.PI * 2) < 0.001) {
623
+ /* Full circle — special case */
624
+ svg += `<circle${attrs} cx="${cx}" cy="${cy}" r="${r}"/>`;
625
+ } else {
626
+ svg += `<path${attrs} d="${arcPath(cx, cy, r, angle, end)}"/>`;
627
+ }
628
+ angle = end;
629
+ }
630
+
631
+ this.#legendData = data.map((d, i) => ({
632
+ label: labels[i],
633
+ value: vals[i],
634
+ pct: ((vals[i] / total) * 100).toFixed(1),
635
+ slot: i % 10,
636
+ }));
637
+
638
+ return { svg, viewBox: `0 0 ${width} ${height}` };
639
+ }
640
+
641
+ /* ── Donut chart ──────────────────────────────────────────────── */
642
+
643
+ #renderDonut() {
644
+ const data = this.#data;
645
+ const yKey = this.#yKeys()[0] || this.y;
646
+ const vals = data.map(d => +(d[yKey] ?? 0));
647
+ const total = vals.reduce((a, b) => a + b, 0) || 1;
648
+ const labels = data.map(d => d[this.x] ?? '');
649
+
650
+ const dims = this.#dims();
651
+ const { width, height } = dims;
652
+ const cx = width / 2;
653
+ const cy = height / 2;
654
+ const outer = Math.max(30, Math.min(width, height) * 0.42);
655
+ const inner = outer * 0.72;
656
+
657
+ let svg = '';
658
+ let angle = -Math.PI / 2;
659
+
660
+ for (let i = 0; i < vals.length; i++) {
661
+ const slice = (vals[i] / total) * Math.PI * 2;
662
+ if (slice === 0) continue;
663
+ const end = angle + slice;
664
+
665
+ const pct = ((vals[i] / total) * 100).toFixed(1);
666
+ const attrs = ` data-slice="${i % 10}"${tip({ label: labels[i], value: vals[i], pct })}`;
667
+
668
+ if (Math.abs(slice - Math.PI * 2) < 0.001) {
669
+ /* Full ring */
670
+ svg += `<circle${attrs} cx="${cx}" cy="${cy}" r="${(outer + inner) / 2}" fill="none" stroke-width="${outer - inner}" style="fill:none"/>`;
671
+ svg += `<path${attrs} d="M ${cx - outer} ${cy} A ${outer} ${outer} 0 1 1 ${cx + outer} ${cy} A ${outer} ${outer} 0 1 1 ${cx - outer} ${cy} Z M ${cx - inner} ${cy} A ${inner} ${inner} 0 1 0 ${cx + inner} ${cy} A ${inner} ${inner} 0 1 0 ${cx - inner} ${cy} Z" fill-rule="evenodd"/>`;
672
+ } else {
673
+ svg += `<path${attrs} d="${donutArcPath(cx, cy, outer, inner, angle, end, this.#resolveRadius())}"/>`;
674
+ }
675
+ angle = end;
676
+ }
677
+
678
+ /* Center total — font size tied to donut radius so it scales with the chart */
679
+ const totalFs = Math.max(14, Math.round(outer * 0.32));
680
+ const labelFs = Math.max(9, Math.round(outer * 0.16));
681
+ svg += `<text data-donut-total x="${cx}" y="${cy}" text-anchor="middle" dominant-baseline="central" font-size="${totalFs}">${fmt(total)}</text>`;
682
+ svg += `<text data-donut-label x="${cx}" y="${cy + totalFs}" text-anchor="middle" dominant-baseline="central" font-size="${labelFs}">Total</text>`;
683
+
684
+ this.#legendData = data.map((d, i) => ({
685
+ label: labels[i],
686
+ value: vals[i],
687
+ pct: ((vals[i] / total) * 100).toFixed(1),
688
+ slot: i % 10,
689
+ }));
690
+
691
+ return { svg, viewBox: `0 0 ${width} ${height}` };
692
+ }
693
+
694
+ /* ── Radar chart ──────────────────────────────────────────────── */
695
+
696
+ #renderRadar() {
697
+ const data = this.#data;
698
+ const yKey = this.#yKeys()[0] || this.y;
699
+ const vals = data.map(d => +(d[yKey] ?? 0));
700
+ const labels = data.map(d => d[this.x] ?? '');
701
+ const maxVal = Math.max(...vals) || 1;
702
+ const n = data.length;
703
+
704
+ // Derive size from container so viewBox ≈ rendered pixels (keeps
705
+ // font-size and stroke widths at their intended visual scale).
706
+ const dims = this.#dims();
707
+ const { width, height, fontSize } = dims;
708
+ const cx = width / 2;
709
+ const cy = height / 2;
710
+ // Leave label breathing room proportional to font size so labels
711
+ // never clip at wide sizes or crowd the polygon at small sizes.
712
+ const labelPad = fontSize * 3.5;
713
+ const r = Math.max(40, Math.min(width, height) / 2 - labelPad);
714
+
715
+ let svg = '';
716
+ const angleStep = (Math.PI * 2) / n;
717
+
718
+ /* Grid rings (3 levels) */
719
+ for (let level = 1; level <= 3; level++) {
720
+ const lr = (r * level) / 3;
721
+ let ring = '';
722
+ for (let i = 0; i < n; i++) {
723
+ const a = -Math.PI / 2 + angleStep * i;
724
+ const px = cx + lr * Math.cos(a);
725
+ const py = cy + lr * Math.sin(a);
726
+ ring += (i === 0 ? 'M' : 'L') + ` ${px} ${py}`;
727
+ }
728
+ ring += ' Z';
729
+ svg += `<path data-grid d="${ring}"/>`;
730
+ }
731
+
732
+ /* Axis lines */
733
+ for (let i = 0; i < n; i++) {
734
+ const a = -Math.PI / 2 + angleStep * i;
735
+ const px = cx + r * Math.cos(a);
736
+ const py = cy + r * Math.sin(a);
737
+ svg += `<line data-grid x1="${cx}" y1="${cy}" x2="${px}" y2="${py}"/>`;
738
+ }
739
+
740
+ /* Data polygon + per-vertex hit targets */
741
+ let poly = '';
742
+ const vertices = [];
743
+ for (let i = 0; i < n; i++) {
744
+ const a = -Math.PI / 2 + angleStep * i;
745
+ const vr = (vals[i] / maxVal) * r;
746
+ const px = cx + vr * Math.cos(a);
747
+ const py = cy + vr * Math.sin(a);
748
+ poly += (i === 0 ? 'M' : 'L') + ` ${px} ${py}`;
749
+ vertices.push({ px, py });
750
+ }
751
+ poly += ' Z';
752
+ svg += `<path data-radar-fill d="${poly}"/>`;
753
+ svg += `<path data-radar-line d="${poly}"/>`;
754
+
755
+ const hitR = Math.max(fontSize, 10);
756
+ for (let i = 0; i < n; i++) {
757
+ const { px, py } = vertices[i];
758
+ svg += `<circle data-hit${tip({ label: labels[i], value: vals[i] })} cx="${px}" cy="${py}" r="${hitR}" fill="transparent"/>`;
759
+ }
760
+
761
+ /* Labels */
762
+ const labelGap = fontSize * 1.3;
763
+ for (let i = 0; i < n; i++) {
764
+ const a = -Math.PI / 2 + angleStep * i;
765
+ const lx = cx + (r + labelGap) * Math.cos(a);
766
+ const ly = cy + (r + labelGap) * Math.sin(a);
767
+ const anchor = Math.abs(Math.cos(a)) < 0.1 ? 'middle' : Math.cos(a) > 0 ? 'start' : 'end';
768
+ svg += `<text data-x-label x="${lx}" y="${ly}" text-anchor="${anchor}" dominant-baseline="central" font-size="${fontSize}">${esc(labels[i])}</text>`;
769
+ }
770
+
771
+ return { svg, viewBox: `0 0 ${width} ${height}` };
772
+ }
773
+
774
+ /* ── Sparkline ────────────────────────────────────────────────── */
775
+
776
+ #renderSparkline() {
777
+ const data = this.#data;
778
+ const yKey = this.#yKeys()[0] || this.y;
779
+ const vals = data.map(d => +(d[yKey] ?? 0));
780
+ const maxVal = Math.max(...vals) || 1;
781
+ const minVal = Math.min(...vals);
782
+ const range = maxVal - minVal || 1;
783
+
784
+ // Use the actual container dims so the aspect ratio matches what
785
+ // the CSS allocates (otherwise the fixed 120×32 viewBox gets
786
+ // stretched vertically in tall in-card slots).
787
+ const containerW = this.clientWidth || 120;
788
+ const containerH = this.clientHeight || 32;
789
+ const w = Math.max(40, containerW);
790
+ const h = Math.max(16, containerH);
791
+ const step = w / Math.max(vals.length - 1, 1);
792
+
793
+ // Breathing room so the line doesn't clip at the top/bottom edges.
794
+ const padY = Math.max(2, Math.min(6, h * 0.1));
795
+
796
+ const labels = data.map(d => d[this.x] ?? '');
797
+ const points = vals.map((v, i) => ({
798
+ x: step * i,
799
+ y: h - ((v - minVal) / range) * (h - padY * 2) - padY,
800
+ v,
801
+ label: labels[i],
802
+ }));
803
+
804
+ const t = Math.max(0, Math.min(1, this.smooth));
805
+ let svg = '';
806
+ svg += `<path data-area d="${smoothAreaPath(points, h, t)}"/>`;
807
+ svg += `<path data-line d="${smoothPath(points, t)}"/>`;
808
+
809
+ /* Invisible hit-targets for tooltip — sized to fill each X-column */
810
+ const hitR = Math.max(step / 2, 6);
811
+ for (const p of points) {
812
+ svg += `<circle data-hit${tip({ label: p.label, value: p.v })} cx="${p.x}" cy="${p.y}" r="${hitR}" fill="transparent"/>`;
813
+ }
814
+
815
+ return { svg, viewBox: `0 0 ${w} ${h}` };
816
+ }
817
+
818
+ /* ── Segments (single horizontal stacked bar) ──────────────────
819
+ Categorical data rendered as one horizontal bar split into
820
+ proportional colored slices. Good for compact in-card
821
+ "distribution" widgets. Legend reuses the pie/donut path. */
822
+ #renderSegments() {
823
+ const data = this.#data;
824
+ const yKey = this.#yKeys()[0] || this.y;
825
+ const vals = data.map(d => +(d[yKey] ?? 0));
826
+ const total = vals.reduce((a, b) => a + b, 0) || 1;
827
+ const labels = data.map(d => d[this.x] ?? '');
828
+
829
+ // ViewBox matches actual render width so there's no horizontal
830
+ // stretching — keeps rounded corners from flattening into ellipses
831
+ // and matches the token-controlled gap at its intended px size.
832
+ const cs = getComputedStyle(this);
833
+ const w = Math.max(40, this.clientWidth || 200);
834
+ const h = 24;
835
+ const r = Math.min(this.#resolveRadius(), h / 2);
836
+ const gap = Math.max(0, parseFloat(cs.getPropertyValue('--chart-segments-gap')) || 2);
837
+
838
+ // Compute segment widths, distributing rounding error so the last
839
+ // segment lands exactly on the right edge.
840
+ const widths = [];
841
+ let acc = 0;
842
+ for (let i = 0; i < vals.length; i++) {
843
+ const nextAcc = (i === vals.length - 1) ? w : Math.round(((acc * total + vals[i] * w) / total));
844
+ // Use the cumulative approach for sub-pixel accuracy.
845
+ const endX = (i === vals.length - 1) ? w : ((vals.slice(0, i + 1).reduce((s, v) => s + v, 0)) / total) * w;
846
+ const startX = (i === 0) ? 0 : ((vals.slice(0, i).reduce((s, v) => s + v, 0)) / total) * w;
847
+ widths.push({ x: startX, w: Math.max(0, endX - startX) });
848
+ acc = nextAcc;
849
+ }
850
+
851
+ let svg = '';
852
+ for (let i = 0; i < widths.length; i++) {
853
+ const { x, w: segW } = widths[i];
854
+ if (segW <= 0) continue;
855
+
856
+ // Only round the outer corners of the first/last visible segment.
857
+ const isFirst = i === 0;
858
+ const isLast = i === widths.length - 1;
859
+ const drawW = Math.max(0, segW - (isLast ? 0 : gap));
860
+
861
+ const pct = ((vals[i] / total) * 100).toFixed(1);
862
+ const tipAttrs = tip({ label: labels[i], value: vals[i], pct });
863
+ const a11yTitle = `<title>${esc(labels[i])}: ${vals[i]} (${pct}%)</title>`;
864
+
865
+ if ((isFirst && isLast) || (!isFirst && !isLast) || r === 0) {
866
+ svg += `<rect data-slice="${i % 10}"${tipAttrs} x="${x}" y="0" width="${drawW}" height="${h}"${(isFirst || isLast) && r ? ` rx="${r}"` : ''}>${a11yTitle}</rect>`;
867
+ } else {
868
+ // Build a path with asymmetric rounded corners.
869
+ const rr = r;
870
+ const x2 = x + drawW;
871
+ let d;
872
+ if (isFirst) {
873
+ d = `M ${x + rr} 0 H ${x2} V ${h} H ${x + rr} A ${rr} ${rr} 0 0 1 ${x} ${h - rr} V ${rr} A ${rr} ${rr} 0 0 1 ${x + rr} 0 Z`;
874
+ } else { // isLast
875
+ d = `M ${x} 0 H ${x2 - rr} A ${rr} ${rr} 0 0 1 ${x2} ${rr} V ${h - rr} A ${rr} ${rr} 0 0 1 ${x2 - rr} ${h} H ${x} Z`;
876
+ }
877
+ svg += `<path data-slice="${i % 10}"${tipAttrs} d="${d}">${a11yTitle}</path>`;
878
+ }
879
+ }
880
+
881
+ // Legend data (same shape pie/donut use).
882
+ this.#legendData = data.map((d, i) => ({
883
+ label: labels[i],
884
+ value: vals[i],
885
+ pct: ((vals[i] / total) * 100).toFixed(1),
886
+ slot: i % 10,
887
+ }));
888
+
889
+ return { svg, viewBox: `0 0 ${w} ${h}` };
890
+ }
891
+
892
+ /* ── Stacked bar ──────────────────────────────────────────────── */
893
+
894
+ #renderStackedBar() {
895
+ const data = this.#data;
896
+ const keys = this.#yKeys();
897
+ const labels = data.map(d => d[this.x] ?? '');
898
+
899
+ /* Get stacked totals for max */
900
+ const totals = data.map(d => keys.reduce((sum, k) => sum + (+(d[k] ?? 0)), 0));
901
+ const ticks = niceScale(0, Math.max(...totals), 5);
902
+ const maxVal = ticks[ticks.length - 1];
903
+
904
+ const dims = this.#dims();
905
+ const { width, height, pad } = dims;
906
+ const plotH = height - pad.top - pad.bottom;
907
+ const plotW = width - pad.left - pad.right;
908
+ const barW = plotW / data.length;
909
+ const barInner = barW * 0.6;
910
+ const barGap = (barW - barInner) / 2;
911
+
912
+ let svg = this.#gridAndAxes(width, height, ticks, labels, pad, dims);
913
+
914
+ for (let i = 0; i < data.length; i++) {
915
+ let stackY = pad.top + plotH;
916
+ const segCount = keys.length;
917
+ for (let k = 0; k < segCount; k++) {
918
+ const v = +(data[i][keys[k]] ?? 0);
919
+ const segH = maxVal ? (v / maxVal) * plotH : 0;
920
+ if (segH <= 0) { stackY -= segH; continue; }
921
+ stackY -= segH;
922
+ const bx = pad.left + barW * i + barGap;
923
+ const by = stackY;
924
+ const bh = segH;
925
+ const isTop = k === segCount - 1;
926
+ const isBottom = k === 0;
927
+ const r = Math.min(this.#resolveRadius(), barInner / 2, bh / 2);
928
+
929
+ const attrs = ` data-slice="${k % 10}"${tip({ label: labels[i], value: v, series: keys[k] })}`;
930
+
931
+ if (isTop && isBottom) {
932
+ // Single segment — round top + bottom
933
+ svg += `<path${attrs} d="M${bx + r},${by} H${bx + barInner - r} Q${bx + barInner},${by} ${bx + barInner},${by + r} V${by + bh - r} Q${bx + barInner},${by + bh} ${bx + barInner - r},${by + bh} H${bx + r} Q${bx},${by + bh} ${bx},${by + bh - r} V${by + r} Q${bx},${by} ${bx + r},${by} Z"/>`;
934
+ } else if (isTop) {
935
+ // Top segment — round top corners only
936
+ svg += `<path${attrs} d="M${bx},${by + bh} V${by + r} Q${bx},${by} ${bx + r},${by} H${bx + barInner - r} Q${bx + barInner},${by} ${bx + barInner},${by + r} V${by + bh} Z"/>`;
937
+ } else if (isBottom) {
938
+ // Bottom segment — round bottom corners only
939
+ svg += `<path${attrs} d="M${bx},${by} H${bx + barInner} V${by + bh - r} Q${bx + barInner},${by + bh} ${bx + barInner - r},${by + bh} H${bx + r} Q${bx},${by + bh} ${bx},${by + bh - r} Z"/>`;
940
+ } else {
941
+ // Middle segment — no radius
942
+ svg += `<rect${attrs} x="${bx}" y="${by}" width="${barInner}" height="${bh}"/>`;
943
+ }
944
+ }
945
+ }
946
+
947
+ this.#legendData = keys.map((k, i) => ({ label: k, slot: i % 10 }));
948
+
949
+ return { svg, viewBox: `0 0 ${width} ${height}` };
950
+ }
951
+
952
+ /* ── Grouped bar ──────────────────────────────────────────────── */
953
+
954
+ #renderGroupedBar() {
955
+ const data = this.#data;
956
+ const keys = this.#yKeys();
957
+ const labels = data.map(d => d[this.x] ?? '');
958
+
959
+ const allVals = data.flatMap(d => keys.map(k => +(d[k] ?? 0)));
960
+ const ticks = niceScale(0, Math.max(...allVals), 5);
961
+ const maxVal = ticks[ticks.length - 1];
962
+
963
+ const dims = this.#dims();
964
+ const { width, height, pad } = dims;
965
+ const plotH = height - pad.top - pad.bottom;
966
+ const plotW = width - pad.left - pad.right;
967
+ const groupW = plotW / data.length;
968
+ const barGap = 3;
969
+ const totalBarSpace = groupW * 0.7;
970
+ const subBarW = (totalBarSpace - barGap * (keys.length - 1)) / keys.length;
971
+ const groupPad = (groupW - totalBarSpace) / 2;
972
+
973
+ let svg = this.#gridAndAxes(width, height, ticks, labels, pad, dims);
974
+
975
+ for (let i = 0; i < data.length; i++) {
976
+ for (let k = 0; k < keys.length; k++) {
977
+ const v = +(data[i][keys[k]] ?? 0);
978
+ const barH = maxVal ? (v / maxVal) * plotH : 0;
979
+ const bx = pad.left + groupW * i + groupPad + (subBarW + barGap) * k;
980
+ const by = pad.top + plotH - barH;
981
+ svg += `<rect data-slice="${k % 10}"${tip({ label: labels[i], value: v, series: keys[k] })} x="${bx}" y="${by}" width="${subBarW}" height="${barH}" rx="${this.#resolveRadius()}"/>`;
982
+
983
+ if (!this.hideValues) {
984
+ svg += `<text data-value x="${bx + subBarW / 2}" y="${by - 4}" text-anchor="middle" font-size="${dims.valueSize}">${fmt(v)}</text>`;
985
+ }
986
+ }
987
+ }
988
+
989
+ this.#legendData = keys.map((k, i) => ({ label: k, slot: i % 10 }));
990
+
991
+ return { svg, viewBox: `0 0 ${width} ${height}` };
992
+ }
993
+
994
+ /* ── Multi-line ───────────────────────────────────────────────── */
995
+
996
+ #renderMultiLine() {
997
+ const data = this.#data;
998
+ const keys = this.#yKeys();
999
+ const labels = data.map(d => d[this.x] ?? '');
1000
+
1001
+ const allVals = data.flatMap(d => keys.map(k => +(d[k] ?? 0)));
1002
+ const ticks = niceScale(0, Math.max(...allVals), 5);
1003
+ const maxVal = ticks[ticks.length - 1];
1004
+
1005
+ const dims = this.#dims();
1006
+ const { width, height, pad } = dims;
1007
+ const plotH = height - pad.top - pad.bottom;
1008
+ const plotW = width - pad.left - pad.right;
1009
+ const step = plotW / Math.max(data.length - 1, 1);
1010
+
1011
+ let svg = this.#gridAndAxes(width, height, ticks, labels, pad, dims);
1012
+
1013
+ for (let k = 0; k < keys.length; k++) {
1014
+ const vals = data.map(d => +(d[keys[k]] ?? 0));
1015
+ const points = vals.map((v, i) => {
1016
+ const px = pad.left + step * i;
1017
+ const py = pad.top + plotH - (maxVal ? (v / maxVal) * plotH : 0);
1018
+ return { x: px, y: py, v, label: labels[i] };
1019
+ });
1020
+
1021
+ const baseline = pad.top + plotH;
1022
+ const t = Math.max(0, Math.min(1, this.smooth));
1023
+
1024
+ /* Area fill */
1025
+ svg += `<path data-area data-slice="${k % 10}" d="${smoothAreaPath(points, baseline, t)}"/>`;
1026
+
1027
+ /* Line */
1028
+ svg += `<path data-line data-slice="${k % 10}" d="${smoothPath(points, t)}"/>`;
1029
+
1030
+ /* Dots + hit targets. Hit circles deliberately omit data-slice so
1031
+ they aren't caught by the circle[data-slice] fill rule in CSS. */
1032
+ for (const p of points) {
1033
+ svg += `<circle data-dot data-slice="${k % 10}" cx="${p.x}" cy="${p.y}" r="3"/>`;
1034
+ svg += `<circle data-hit${tip({ label: p.label, value: p.v, series: keys[k] })} cx="${p.x}" cy="${p.y}" r="10" fill="transparent"/>`;
1035
+ }
1036
+ }
1037
+
1038
+ this.#legendData = keys.map((k, i) => ({ label: k, slot: i % 10 }));
1039
+
1040
+ return { svg, viewBox: `0 0 ${width} ${height}` };
1041
+ }
1042
+
1043
+ /* ── Legend ────────────────────────────────────────────────────── */
1044
+
1045
+ #legendData = null;
1046
+
1047
+ #buildLegend() {
1048
+ if (!this.#legendData || !this.#legendData.length) return null;
1049
+
1050
+ const legend = document.createElement('div');
1051
+ legend.setAttribute('data-legend', '');
1052
+
1053
+ for (const item of this.#legendData) {
1054
+ const el = document.createElement('span');
1055
+ el.setAttribute('data-legend-item', '');
1056
+
1057
+ const dot = document.createElement('span');
1058
+ dot.setAttribute('data-legend-dot', '');
1059
+ dot.setAttribute('data-slice', String(item.slot));
1060
+ el.appendChild(dot);
1061
+
1062
+ const text = document.createElement('span');
1063
+ text.textContent = item.pct ? `${item.label} (${item.pct}%)` : item.label;
1064
+ el.appendChild(text);
1065
+
1066
+ legend.appendChild(el);
1067
+ }
1068
+
1069
+ this.#legendData = null;
1070
+ return legend;
1071
+ }
1072
+ }
1073
+ customElements.define('chart-ui', AdiaChart);
1074
+
1075
+ export { AdiaChart };