@icen.ai/ui 0.10.0 → 0.11.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 (362) hide show
  1. package/AGENTS.md +79 -309
  2. package/LICENSE +21 -0
  3. package/README.md +43 -54
  4. package/dist/base.css +68 -22
  5. package/dist/behaviors/accordion.mjs +1 -1
  6. package/dist/behaviors/ai-branch.mjs +14 -0
  7. package/dist/behaviors/ai-chat.d.mts +2 -2
  8. package/dist/behaviors/ai-chat.mjs +66 -10
  9. package/dist/behaviors/ai-composer.d.mts +5 -5
  10. package/dist/behaviors/ai-composer.mjs +34 -10
  11. package/dist/behaviors/ai-core.mjs +14 -0
  12. package/dist/behaviors/ai-diff.mjs +14 -0
  13. package/dist/behaviors/ai-feedback.mjs +14 -0
  14. package/dist/behaviors/ai-panel.d.mts +2 -2
  15. package/dist/behaviors/ai-panel.mjs +14 -0
  16. package/dist/behaviors/ai-provider.d.mts +1 -1
  17. package/dist/behaviors/ai-provider.mjs +14 -0
  18. package/dist/behaviors/ai-threads.mjs +14 -0
  19. package/dist/behaviors/ai-tool.d.mts +1 -1
  20. package/dist/behaviors/ai-tool.mjs +299 -24
  21. package/dist/behaviors/ai-tools.mjs +1179 -28
  22. package/dist/behaviors/autocomplete.d.mts +1 -1
  23. package/dist/behaviors/autocomplete.mjs +14 -0
  24. package/dist/behaviors/back-top.mjs +14 -0
  25. package/dist/behaviors/carousel.mjs +21 -1
  26. package/dist/behaviors/cascader.mjs +27 -0
  27. package/dist/behaviors/charts.d.mts +12 -1
  28. package/dist/behaviors/charts.mjs +298 -20
  29. package/dist/behaviors/code-block.mjs +22 -1
  30. package/dist/behaviors/command-palette.mjs +3 -3
  31. package/dist/behaviors/context-menu.d.mts +2 -2
  32. package/dist/behaviors/context-menu.mjs +14 -0
  33. package/dist/behaviors/controls.d.mts +8 -8
  34. package/dist/behaviors/controls.mjs +50 -6
  35. package/dist/behaviors/copy.mjs +14 -0
  36. package/dist/behaviors/datatable.d.mts +3 -3
  37. package/dist/behaviors/datatable.mjs +89 -11
  38. package/dist/behaviors/date-picker.mjs +14 -0
  39. package/dist/behaviors/events.d.mts +10 -10
  40. package/dist/behaviors/events.mjs +11 -1
  41. package/dist/behaviors/form-validate.d.mts +10 -1
  42. package/dist/behaviors/form-validate.mjs +279 -4611
  43. package/dist/behaviors/i18n.d.mts +14 -0
  44. package/dist/behaviors/i18n.mjs +28 -0
  45. package/dist/behaviors/input.mjs +16 -0
  46. package/dist/behaviors/kb-access.d.mts +2 -2
  47. package/dist/behaviors/kb-access.mjs +21 -2
  48. package/dist/behaviors/kb-acl.d.mts +2 -2
  49. package/dist/behaviors/kb-acl.mjs +14 -0
  50. package/dist/behaviors/kb-answer.d.mts +2 -2
  51. package/dist/behaviors/kb-answer.mjs +298 -23
  52. package/dist/behaviors/kb-audit.d.mts +2 -2
  53. package/dist/behaviors/kb-audit.mjs +14 -0
  54. package/dist/behaviors/kb-canvas.d.mts +3 -3
  55. package/dist/behaviors/kb-canvas.mjs +14 -0
  56. package/dist/behaviors/kb-chain.d.mts +2 -2
  57. package/dist/behaviors/kb-chain.mjs +14 -0
  58. package/dist/behaviors/kb-checkpoint.d.mts +2 -2
  59. package/dist/behaviors/kb-checkpoint.mjs +14 -0
  60. package/dist/behaviors/kb-chunks.d.mts +2 -2
  61. package/dist/behaviors/kb-chunks.mjs +14 -0
  62. package/dist/behaviors/kb-citation.d.mts +2 -2
  63. package/dist/behaviors/kb-citation.mjs +33 -6
  64. package/dist/behaviors/kb-clarify.d.mts +2 -2
  65. package/dist/behaviors/kb-clarify.mjs +14 -0
  66. package/dist/behaviors/kb-conflict.d.mts +2 -2
  67. package/dist/behaviors/kb-conflict.mjs +14 -0
  68. package/dist/behaviors/kb-connector.d.mts +2 -2
  69. package/dist/behaviors/kb-connector.mjs +14 -0
  70. package/dist/behaviors/kb-core.d.mts +1 -1
  71. package/dist/behaviors/kb-core.mjs +14 -0
  72. package/dist/behaviors/kb-eval.d.mts +2 -2
  73. package/dist/behaviors/kb-eval.mjs +21 -2
  74. package/dist/behaviors/kb-explain.d.mts +2 -2
  75. package/dist/behaviors/kb-explain.mjs +26 -2
  76. package/dist/behaviors/kb-filter.d.mts +2 -2
  77. package/dist/behaviors/kb-filter.mjs +21 -2
  78. package/dist/behaviors/kb-gap.d.mts +2 -2
  79. package/dist/behaviors/kb-gap.mjs +14 -0
  80. package/dist/behaviors/kb-hittest.d.mts +2 -2
  81. package/dist/behaviors/kb-hittest.mjs +15 -1
  82. package/dist/behaviors/kb-metadata.mjs +19 -0
  83. package/dist/behaviors/kb-passage.d.mts +2 -2
  84. package/dist/behaviors/kb-passage.mjs +14 -0
  85. package/dist/behaviors/kb-pipeline.d.mts +2 -2
  86. package/dist/behaviors/kb-pipeline.mjs +14 -0
  87. package/dist/behaviors/kb-qa.mjs +14 -0
  88. package/dist/behaviors/kb-rerank.d.mts +2 -2
  89. package/dist/behaviors/kb-rerank.mjs +14 -0
  90. package/dist/behaviors/kb-retrieval.d.mts +2 -2
  91. package/dist/behaviors/kb-retrieval.mjs +31 -4
  92. package/dist/behaviors/kb-review.d.mts +2 -2
  93. package/dist/behaviors/kb-review.mjs +21 -2
  94. package/dist/behaviors/kb-sandbox.d.mts +2 -2
  95. package/dist/behaviors/kb-sandbox.mjs +20 -1
  96. package/dist/behaviors/kb-segment.d.mts +2 -2
  97. package/dist/behaviors/kb-segment.mjs +15 -1
  98. package/dist/behaviors/kb-select.mjs +21 -2
  99. package/dist/behaviors/kb-sources.d.mts +2 -2
  100. package/dist/behaviors/kb-sources.mjs +14 -0
  101. package/dist/behaviors/kb-sql.d.mts +2 -2
  102. package/dist/behaviors/kb-sql.mjs +14 -0
  103. package/dist/behaviors/kb-trace.d.mts +2 -2
  104. package/dist/behaviors/kb-trace.mjs +22 -3
  105. package/dist/behaviors/kb-visibility.d.mts +2 -2
  106. package/dist/behaviors/kb-visibility.mjs +14 -0
  107. package/dist/behaviors/kb-who-can.d.mts +2 -2
  108. package/dist/behaviors/kb-who-can.mjs +21 -2
  109. package/dist/behaviors/mentions.d.mts +1 -1
  110. package/dist/behaviors/mentions.mjs +14 -0
  111. package/dist/behaviors/modal.d.mts +1 -1
  112. package/dist/behaviors/modal.mjs +14 -0
  113. package/dist/behaviors/motion.d.mts +16 -0
  114. package/dist/behaviors/motion.mjs +39 -0
  115. package/dist/behaviors/notification.d.mts +2 -2
  116. package/dist/behaviors/notification.mjs +27 -3
  117. package/dist/behaviors/panel.mjs +14 -0
  118. package/dist/behaviors/scroll-area.d.mts +17 -0
  119. package/dist/behaviors/scroll-area.mjs +59 -0
  120. package/dist/behaviors/select.mjs +21 -2
  121. package/dist/behaviors/slider.mjs +3 -3
  122. package/dist/behaviors/split-pane.mjs +2 -0
  123. package/dist/behaviors/suggestions.mjs +14 -0
  124. package/dist/behaviors/tabs.mjs +10 -0
  125. package/dist/behaviors/tag-input.mjs +14 -0
  126. package/dist/behaviors/theme-switcher.d.mts +2 -2
  127. package/dist/behaviors/theme-switcher.mjs +30 -2
  128. package/dist/behaviors/theme.mjs +16 -2
  129. package/dist/behaviors/toast.d.mts +2 -2
  130. package/dist/behaviors/toast.mjs +14 -0
  131. package/dist/behaviors/tree-select.mjs +27 -0
  132. package/dist/behaviors/tree.mjs +4 -4
  133. package/dist/behaviors/upload.mjs +14 -0
  134. package/dist/behaviors/url-safety.d.mts +8 -0
  135. package/dist/behaviors/url-safety.mjs +11 -0
  136. package/dist/components/accordion.d.mts +2 -0
  137. package/dist/components/ai-branch.d.mts +2 -0
  138. package/dist/components/ai-chat.css +40 -33
  139. package/dist/components/ai-chat.d.mts +2 -0
  140. package/dist/components/ai-composer.d.mts +2 -0
  141. package/dist/components/ai-context.d.mts +2 -0
  142. package/dist/components/ai-context.mjs +1 -0
  143. package/dist/components/ai-diff.css +6 -6
  144. package/dist/components/ai-diff.d.mts +2 -0
  145. package/dist/components/ai-feedback.d.mts +2 -0
  146. package/dist/components/ai-files.d.mts +2 -0
  147. package/dist/components/ai-message.d.mts +2 -0
  148. package/dist/components/ai-panel.css +12 -10
  149. package/dist/components/ai-reasoning.d.mts +2 -0
  150. package/dist/components/ai-subagent.d.mts +2 -0
  151. package/dist/components/ai-subagent.mjs +1 -0
  152. package/dist/components/ai-threads.d.mts +2 -0
  153. package/dist/components/ai-todo.d.mts +2 -0
  154. package/dist/components/ai-todo.mjs +1 -0
  155. package/dist/components/ai-tool-call.d.mts +2 -0
  156. package/dist/components/ai-tool-call.mjs +1 -0
  157. package/dist/components/ai-tool.css +16 -16
  158. package/dist/components/ai-usage.d.mts +2 -0
  159. package/dist/components/alert.d.mts +2 -0
  160. package/dist/components/autocomplete.css +12 -12
  161. package/dist/components/autocomplete.d.mts +2 -0
  162. package/dist/components/avatar.d.mts +2 -0
  163. package/dist/components/back-top.css +47 -16
  164. package/dist/components/back-top.d.mts +2 -0
  165. package/dist/components/badge.css +1 -1
  166. package/dist/components/badge.d.mts +2 -0
  167. package/dist/components/breadcrumb.css +26 -8
  168. package/dist/components/breadcrumb.d.mts +2 -0
  169. package/dist/components/btn.css +67 -62
  170. package/dist/components/btn.d.mts +2 -0
  171. package/dist/components/card.css +22 -22
  172. package/dist/components/card.d.mts +2 -0
  173. package/dist/components/carousel.d.mts +2 -0
  174. package/dist/components/cascader.css +56 -35
  175. package/dist/components/cascader.d.mts +2 -0
  176. package/dist/components/chart-area.css +46 -0
  177. package/dist/components/chart-area.d.mts +2 -0
  178. package/dist/components/chart-bar.css +66 -19
  179. package/dist/components/chart-bar.d.mts +2 -0
  180. package/dist/components/chart-calendar.d.mts +2 -0
  181. package/dist/components/chart-gauge.css +23 -3
  182. package/dist/components/chart-gauge.d.mts +2 -0
  183. package/dist/components/chart-graph.css +15 -3
  184. package/dist/components/chart-graph.d.mts +2 -0
  185. package/dist/components/chart-heatmap.css +117 -95
  186. package/dist/components/chart-heatmap.d.mts +2 -0
  187. package/dist/components/chart-line.css +54 -0
  188. package/dist/components/chart-line.d.mts +2 -0
  189. package/dist/components/chart-map.d.mts +2 -0
  190. package/dist/components/chart-pie.css +29 -0
  191. package/dist/components/chart-pie.d.mts +2 -0
  192. package/dist/components/chart-radar.css +50 -1
  193. package/dist/components/chart-radar.d.mts +2 -0
  194. package/dist/components/chart-scatter.css +39 -2
  195. package/dist/components/chart-scatter.d.mts +2 -0
  196. package/dist/components/chart-sparkline.css +20 -0
  197. package/dist/components/chart-sparkline.d.mts +2 -0
  198. package/dist/components/chart-stack.css +16 -5
  199. package/dist/components/chart-stack.d.mts +2 -0
  200. package/dist/components/charts.css +109 -35
  201. package/dist/components/charts.d.mts +2 -0
  202. package/dist/components/code-block.css +10 -8
  203. package/dist/components/code-block.d.mts +2 -0
  204. package/dist/components/command-palette.css +45 -38
  205. package/dist/components/command-palette.d.mts +2 -0
  206. package/dist/components/content.css +62 -57
  207. package/dist/components/context-menu.d.mts +2 -0
  208. package/dist/components/copy.css +10 -10
  209. package/dist/components/copy.d.mts +2 -0
  210. package/dist/components/datatable.css +72 -59
  211. package/dist/components/datatable.d.mts +2 -0
  212. package/dist/components/date-picker.css +26 -26
  213. package/dist/components/date-picker.d.mts +2 -0
  214. package/dist/components/desc.d.mts +2 -0
  215. package/dist/components/dropdown.d.mts +2 -0
  216. package/dist/components/empty.css +16 -16
  217. package/dist/components/empty.d.mts +2 -0
  218. package/dist/components/feedback.css +31 -29
  219. package/dist/components/form-validate.css +20 -6
  220. package/dist/components/form-validate.d.mts +2 -0
  221. package/dist/components/form.css +17 -14
  222. package/dist/components/form.d.mts +2 -0
  223. package/dist/components/icon.css +21 -10
  224. package/dist/components/icon.d.mts +2 -0
  225. package/dist/components/input.css +22 -22
  226. package/dist/components/input.d.mts +2 -0
  227. package/dist/components/kb-access.d.mts +2 -0
  228. package/dist/components/kb-acl.d.mts +2 -0
  229. package/dist/components/kb-agent.css +134 -55
  230. package/dist/components/kb-answer.d.mts +2 -0
  231. package/dist/components/kb-audit.d.mts +2 -0
  232. package/dist/components/kb-canvas.d.mts +2 -0
  233. package/dist/components/kb-chain.d.mts +2 -0
  234. package/dist/components/kb-checkpoint.d.mts +2 -0
  235. package/dist/components/kb-chunks.d.mts +2 -0
  236. package/dist/components/kb-citation.d.mts +2 -0
  237. package/dist/components/kb-clarify.d.mts +2 -0
  238. package/dist/components/kb-conflict.d.mts +2 -0
  239. package/dist/components/kb-connector.d.mts +2 -0
  240. package/dist/components/kb-data.css +192 -80
  241. package/dist/components/kb-eval.d.mts +2 -0
  242. package/dist/components/kb-explain.d.mts +2 -0
  243. package/dist/components/kb-filter.d.mts +2 -0
  244. package/dist/components/kb-gap.d.mts +2 -0
  245. package/dist/components/kb-ground.css +165 -73
  246. package/dist/components/kb-hittest.d.mts +2 -0
  247. package/dist/components/kb-ingest.css +308 -113
  248. package/dist/components/kb-metadata.d.mts +2 -0
  249. package/dist/components/kb-ops.css +189 -75
  250. package/dist/components/kb-passage.d.mts +2 -0
  251. package/dist/components/kb-perm.css +158 -87
  252. package/dist/components/kb-pipeline.d.mts +2 -0
  253. package/dist/components/kb-qa.d.mts +2 -0
  254. package/dist/components/kb-rerank.d.mts +2 -0
  255. package/dist/components/kb-retrieval.d.mts +2 -0
  256. package/dist/components/kb-review.d.mts +2 -0
  257. package/dist/components/kb-sandbox.d.mts +2 -0
  258. package/dist/components/kb-search.css +353 -116
  259. package/dist/components/kb-segment.d.mts +2 -0
  260. package/dist/components/kb-sources.d.mts +2 -0
  261. package/dist/components/kb-sql.d.mts +2 -0
  262. package/dist/components/kb-trace.d.mts +2 -0
  263. package/dist/components/kb-visibility.d.mts +2 -0
  264. package/dist/components/kb-who-can.d.mts +2 -0
  265. package/dist/components/kb.css +60 -36
  266. package/dist/components/kb.d.mts +2 -0
  267. package/dist/components/kbd.d.mts +2 -0
  268. package/dist/components/layout.css +10 -10
  269. package/dist/components/layout.d.mts +2 -0
  270. package/dist/components/list.d.mts +2 -0
  271. package/dist/components/material-chinese.css +10 -0
  272. package/dist/components/material-soviet.css +10 -0
  273. package/dist/components/material.css +61 -18
  274. package/dist/components/media-card.d.mts +2 -0
  275. package/dist/components/media.css +22 -27
  276. package/dist/components/mentions.css +18 -17
  277. package/dist/components/mentions.d.mts +2 -0
  278. package/dist/components/menu.css +32 -28
  279. package/dist/components/modal.css +30 -27
  280. package/dist/components/modal.d.mts +2 -0
  281. package/dist/components/nav.css +48 -44
  282. package/dist/components/nav.d.mts +2 -0
  283. package/dist/components/notification.css +31 -25
  284. package/dist/components/notification.d.mts +2 -0
  285. package/dist/components/pagination.css +15 -13
  286. package/dist/components/pagination.d.mts +2 -0
  287. package/dist/components/panel.css +19 -20
  288. package/dist/components/panel.d.mts +2 -0
  289. package/dist/components/pill.css +8 -7
  290. package/dist/components/pill.d.mts +2 -0
  291. package/dist/components/popover.css +32 -23
  292. package/dist/components/popover.d.mts +2 -0
  293. package/dist/components/progress.d.mts +2 -0
  294. package/dist/components/rating.d.mts +2 -0
  295. package/dist/components/result.d.mts +2 -0
  296. package/dist/components/scroll-area.css +84 -112
  297. package/dist/components/scroll-area.d.mts +2 -0
  298. package/dist/components/scroll-area.mjs +1 -0
  299. package/dist/components/segmented.css +47 -29
  300. package/dist/components/segmented.d.mts +2 -0
  301. package/dist/components/select.css +80 -41
  302. package/dist/components/select.d.mts +2 -0
  303. package/dist/components/sidebar.css +42 -38
  304. package/dist/components/sidebar.d.mts +2 -0
  305. package/dist/components/skeleton.d.mts +2 -0
  306. package/dist/components/slider.css +26 -22
  307. package/dist/components/slider.d.mts +2 -0
  308. package/dist/components/spinner.d.mts +2 -0
  309. package/dist/components/split-pane.css +16 -7
  310. package/dist/components/split-pane.d.mts +2 -0
  311. package/dist/components/stat.css +17 -16
  312. package/dist/components/stat.d.mts +2 -0
  313. package/dist/components/steps.css +26 -26
  314. package/dist/components/steps.d.mts +2 -0
  315. package/dist/components/switch.css +78 -39
  316. package/dist/components/switch.d.mts +2 -0
  317. package/dist/components/table.css +54 -32
  318. package/dist/components/table.d.mts +2 -0
  319. package/dist/components/tabs.css +53 -32
  320. package/dist/components/tabs.d.mts +2 -0
  321. package/dist/components/tag-input.css +12 -8
  322. package/dist/components/tag-input.d.mts +2 -0
  323. package/dist/components/tag.css +20 -12
  324. package/dist/components/tag.d.mts +2 -0
  325. package/dist/components/theme-switcher.css +7 -7
  326. package/dist/components/theme-switcher.d.mts +2 -0
  327. package/dist/components/timeline.d.mts +2 -0
  328. package/dist/components/toast.css +34 -30
  329. package/dist/components/toast.d.mts +2 -0
  330. package/dist/components/toolbar.css +19 -18
  331. package/dist/components/toolbar.d.mts +2 -0
  332. package/dist/components/tooltip.css +9 -8
  333. package/dist/components/tooltip.d.mts +2 -0
  334. package/dist/components/tree-select.css +63 -40
  335. package/dist/components/tree-select.d.mts +2 -0
  336. package/dist/components/tree.d.mts +2 -0
  337. package/dist/components/upload.css +9 -11
  338. package/dist/components/upload.d.mts +2 -0
  339. package/dist/index.d.mts +85 -84
  340. package/dist/index.mjs +14644 -13935
  341. package/dist/react.d.mts +18 -18
  342. package/dist/react.mjs +377 -36
  343. package/dist/registry.json +797 -64
  344. package/dist/tokens/colors.css +6 -0
  345. package/dist/tokens/design.css +137 -31
  346. package/dist/tokens/dtcg.json +9592 -2008
  347. package/dist/tokens/figma.variables.json +7451 -2141
  348. package/dist/tokens/motion.css +51 -0
  349. package/dist/tokens/pixel.css +10 -0
  350. package/dist/tokens/scrollbars.css +40 -0
  351. package/dist/tokens/tokens-studio.json +8390 -1142
  352. package/dist/tokens/typography.css +33 -0
  353. package/dist/tokens.css +402 -82
  354. package/dist/ui.css +4196 -2163
  355. package/dist/vue.d.mts +17 -17
  356. package/dist/vue.mjs +371 -34
  357. package/docs/spec/charts-scale-a11y.md +115 -0
  358. package/docs/spec/design-philosophy.md +5 -5
  359. package/docs/spec/design-primitives.md +10 -10
  360. package/docs/spec/motion-typography.md +105 -0
  361. package/docs/spec/scrollbars.md +46 -0
  362. package/package.json +17 -3
@@ -116,6 +116,7 @@ var ICEN_LOCALE_ZH_CN = {
116
116
  "aiChat.roleTool": "\u5DE5",
117
117
  "aiChat.roleUser": "\u6211",
118
118
  "aiChat.thinking": "\u601D\u8003 {p0}",
119
+ "aiChat.thinkingNow": "\u6B63\u5728\u601D\u8003\u2026",
119
120
  /* ── aiComposer ── */
120
121
  "aiComposer.attachAgent": "\u4EE3\u7406",
121
122
  "aiComposer.library": "\u6DFB\u52A0\u4E0A\u4E0B\u6587\u4E0E\u547D\u4EE4",
@@ -298,6 +299,19 @@ var ICEN_LOCALE_ZH_CN = {
298
299
  "charts.noData": "\u6682\u65E0\u6570\u636E",
299
300
  "charts.resetZoom": "\u91CD\u7F6E\u7F29\u653E",
300
301
  "charts.sampled": "\u5DF2\u62BD\u6837 {p0}\u2192{p1} \u70B9",
302
+ "charts.a11y.keyboardHint": "\u65B9\u5411\u952E\u9010\u70B9\u6D4F\u89C8\uFF0CHome/End \u8DF3\u9996\u5C3E\uFF0CEsc \u91CD\u7F6E\u7F29\u653E",
303
+ "charts.a11y.max": "\u6700\u5927 {p0}\uFF08{p1}\uFF09",
304
+ "charts.a11y.min": "\u6700\u5C0F {p0}\uFF08{p1}\uFF09",
305
+ "charts.a11y.point": "{p0}\uFF1A{p1}\uFF0C\u7B2C {p2}/{p3} \u70B9",
306
+ "charts.a11y.pointSeries": "{p0} \xB7 {p1}\uFF1A{p2}\uFF0C\u7B2C {p3}/{p4} \u70B9",
307
+ "charts.a11y.range": "\u7C7B\u76EE {p0} \u81F3 {p1}",
308
+ "charts.a11y.scatterRange": "x \u8303\u56F4 {p0}\u2013{p1}\uFF0Cy \u8303\u56F4 {p2}\u2013{p3}",
309
+ "charts.a11y.seriesCount": "{p0} \u4E2A\u7CFB\u5217",
310
+ "charts.a11y.tableCaption": "{p0} \u6570\u636E\u8868",
311
+ "charts.a11y.trend": "\u9996\u7CFB\u5217\u8D8B\u52BF{p0}",
312
+ "charts.a11y.trendDown": "\u4E0B\u964D",
313
+ "charts.a11y.trendFlat": "\u6301\u5E73",
314
+ "charts.a11y.trendUp": "\u4E0A\u5347",
301
315
  "charts.type.area": "\u9762\u79EF\u56FE",
302
316
  "charts.type.donut": "\u73AF\u5F62\u56FE",
303
317
  "charts.type.gauge": "\u4EEA\u8868\u76D8",
@@ -1163,7 +1177,8 @@ function withChrome(el, opts, hasLegend, paint, state, extras) {
1163
1177
  emitIcen(el, "icen:chart-legend-visibility", { visible: hidden ? false : true });
1164
1178
  });
1165
1179
  syncEye();
1166
- head.append(title, eye);
1180
+ if (hasLegend) head.append(title, eye);
1181
+ else head.append(title);
1167
1182
  el.append(head, body);
1168
1183
  paint(body);
1169
1184
  }
@@ -1264,7 +1279,7 @@ function renderVBar(el, opts) {
1264
1279
  wrap.appendChild(track);
1265
1280
  }
1266
1281
  body.append(wrap, labelsRow(labelsUsed));
1267
- if (seriesList.length > 1) body.appendChild(toggleLegend(seriesList.map((s, si) => ({ key: s.name, label: s.name, tone: s.tone ?? (si === 0 ? opts.tone : void 0), si })), wrap));
1282
+ if (seriesList.length > 1) body.appendChild(toggleLegend(seriesList.map((s, si) => ({ key: `${si}:${s.name}`, label: s.name, tone: s.tone ?? (si === 0 ? opts.tone : void 0), si })), wrap));
1268
1283
  });
1269
1284
  attachChartInteraction(el, "vbar");
1270
1285
  return el;
@@ -1359,8 +1374,10 @@ function renderDonut(el, opts) {
1359
1374
  "stroke-width": "14"
1360
1375
  }));
1361
1376
  let offset = 0;
1377
+ const gap = opts.segments.length > 1 ? 2.5 : 0;
1362
1378
  opts.segments.forEach((seg, segIndex) => {
1363
- const dash = seg.value / total * circumference;
1379
+ const arcLength = Math.max(0, seg.value / total * circumference);
1380
+ const dash = arcLength - Math.min(gap, arcLength * 0.1);
1364
1381
  const arc = svgEl("circle", {
1365
1382
  cx: "60",
1366
1383
  cy: "60",
@@ -1374,7 +1391,7 @@ function renderDonut(el, opts) {
1374
1391
  });
1375
1392
  setMarkData(arc, { index: segIndex, label: seg.label, value: seg.value });
1376
1393
  svg.appendChild(arc);
1377
- offset += dash;
1394
+ offset += arcLength;
1378
1395
  });
1379
1396
  const legend = document.createElement("div");
1380
1397
  legend.className = "icen-chart-legend";
@@ -1409,8 +1426,9 @@ function renderLine(el, opts) {
1409
1426
  const svg = svgEl("svg", { viewBox: `0 0 ${width} ${height}`, role: "img", "aria-label": chartAriaLabel(opts.title, t("charts.type.line")) });
1410
1427
  const defs = svgEl("defs", {});
1411
1428
  for (let i = 0; i < 4; i++) {
1412
- const y = pad + i * (height - pad * 2) / 3;
1429
+ const y = Math.round(pad + i * (height - pad * 2) / 3) + 0.5;
1413
1430
  svg.appendChild(svgEl("line", {
1431
+ class: i === 3 ? "icen-chart-grid icen-chart-baseline" : "icen-chart-grid",
1414
1432
  x1: String(pad),
1415
1433
  x2: String(width - pad),
1416
1434
  y1: String(y),
@@ -1494,7 +1512,7 @@ function renderLine(el, opts) {
1494
1512
  if (defs.firstChild) svg.insertBefore(defs, svg.firstChild);
1495
1513
  wrap.append(svg, pointLabelsRow(labelsUsed, width, pad));
1496
1514
  body.appendChild(wrap);
1497
- if (seriesList.length > 1) body.appendChild(toggleLegend(seriesList.map((s, si) => ({ key: s.name, label: s.name, tone: s.tone ?? (si === 0 ? opts.tone : void 0), si })), wrap));
1515
+ if (seriesList.length > 1) body.appendChild(toggleLegend(seriesList.map((s, si) => ({ key: `${si}:${s.name}`, label: s.name, tone: s.tone ?? (si === 0 ? opts.tone : void 0), si })), wrap));
1498
1516
  });
1499
1517
  if (el.dataset.icenChartBody == null) observeFluidWidth(el, () => renderLine(el, opts));
1500
1518
  attachChartInteraction(el, "line");
@@ -1601,7 +1619,7 @@ function renderRadar(el, opts) {
1601
1619
  setMarkData(dot, { index: i, seriesIndex: si, seriesName: s.name || void 0, label: axes[i] ?? "", value: s.values[i] ?? 0 });
1602
1620
  svg.appendChild(dot);
1603
1621
  }
1604
- legendItems.push({ key: s.name || t("charts.seriesName", { p0: si + 1 }), label: s.name || t("charts.seriesName", { p0: si + 1 }), tone: s.tone, si });
1622
+ legendItems.push({ key: `${si}:${s.name || t("charts.seriesName", { p0: si + 1 })}`, label: s.name || t("charts.seriesName", { p0: si + 1 }), tone: s.tone, si });
1605
1623
  });
1606
1624
  wrap.appendChild(svg);
1607
1625
  body.appendChild(wrap);
@@ -1623,6 +1641,11 @@ function niceCeil(v) {
1623
1641
  else nice = 10;
1624
1642
  return nice * base;
1625
1643
  }
1644
+ function tickPrecision(min, max, steps) {
1645
+ const span = Math.abs(max - min);
1646
+ if (!Number.isFinite(span) || span <= 0) return 0;
1647
+ return Math.max(0, Math.ceil(-Math.log10(span / steps)));
1648
+ }
1626
1649
  function isoDate(d) {
1627
1650
  const m = String(d.getMonth() + 1).padStart(2, "0");
1628
1651
  const day = String(d.getDate()).padStart(2, "0");
@@ -1718,7 +1741,8 @@ function renderSparkline(el, opts) {
1718
1741
  const tone = toneVar(opts.tone);
1719
1742
  const points = values.map((value, i) => ({
1720
1743
  x: values.length <= 1 ? width / 2 : pad + i / (values.length - 1) * (width - pad * 2),
1721
- y: height - pad - value / max * (height - pad * 2),
1744
+ /* 负值钳到基线(与折线图同口径):viewBox 仅 28 高,反向射出更刺眼 */
1745
+ y: height - pad - Math.max(0, value) / max * (height - pad * 2),
1722
1746
  value
1723
1747
  }));
1724
1748
  const line = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ");
@@ -1843,6 +1867,7 @@ function renderCalendar(el, opts) {
1843
1867
  weekdayRow.appendChild(s);
1844
1868
  });
1845
1869
  const max = maxOf(entries.map((e) => e.value));
1870
+ entries.sort((a, b) => a.date.localeCompare(b.date));
1846
1871
  const pad = padFirstWeek(grid, entries[0].date, weekStart);
1847
1872
  let lastMonth = -1;
1848
1873
  entries.forEach((entry, i) => {
@@ -1901,8 +1926,9 @@ function renderScatter(el, opts) {
1901
1926
  const xAt = (x) => pad + (x - xMin) / (xMax - xMin) * (width - pad * 2);
1902
1927
  const yAt = (y) => height - pad - (y - yMin) / (yMax - yMin) * (height - pad * 2);
1903
1928
  for (let i = 0; i <= 3; i++) {
1904
- const y = pad + i * (height - pad * 2) / 3;
1929
+ const y = Math.round(pad + i * (height - pad * 2) / 3) + 0.5;
1905
1930
  svg.appendChild(svgEl("line", {
1931
+ class: i === 3 ? "icen-chart-grid icen-chart-baseline" : "icen-chart-grid",
1906
1932
  x1: String(pad),
1907
1933
  x2: String(width - pad),
1908
1934
  y1: String(y),
@@ -1918,12 +1944,13 @@ function renderScatter(el, opts) {
1918
1944
  "font-size": "8",
1919
1945
  fill: "var(--icen-token-text-faint)"
1920
1946
  });
1921
- txt.textContent = fmt(opts, Math.round(tv * 10) / 10);
1947
+ txt.textContent = fmt(opts, +tv.toFixed(tickPrecision(yMin, yMax, 3)));
1922
1948
  svg.appendChild(txt);
1923
1949
  }
1924
1950
  for (let i = 0; i <= 4; i++) {
1925
- const x = pad + i * (width - pad * 2) / 4;
1951
+ const x = Math.round(pad + i * (width - pad * 2) / 4) + 0.5;
1926
1952
  svg.appendChild(svgEl("line", {
1953
+ class: i === 0 ? "icen-chart-grid icen-chart-grid--v icen-chart-axis" : "icen-chart-grid icen-chart-grid--v",
1927
1954
  x1: String(x),
1928
1955
  x2: String(x),
1929
1956
  y1: String(pad),
@@ -1940,7 +1967,7 @@ function renderScatter(el, opts) {
1940
1967
  "font-size": "8",
1941
1968
  fill: "var(--icen-token-text-faint)"
1942
1969
  });
1943
- txt.textContent = fmt(opts, Math.round(tv * 10) / 10);
1970
+ txt.textContent = fmt(opts, +tv.toFixed(tickPrecision(xMin, xMax, 4)));
1944
1971
  svg.appendChild(txt);
1945
1972
  }
1946
1973
  if (opts.yLabel) {
@@ -2177,6 +2204,14 @@ function normalizeChartSpec(raw) {
2177
2204
  }
2178
2205
  if (s.zoom === "x" || s.zoom === "brush") spec.zoom = s.zoom;
2179
2206
  if (typeof s.loading === "boolean") spec.loading = s.loading;
2207
+ if (s.a11y && typeof s.a11y === "object") {
2208
+ const a = s.a11y;
2209
+ const a11y = {};
2210
+ if (typeof a.summary === "boolean") a11y.summary = a.summary;
2211
+ if (typeof a.table === "boolean") a11y.table = a.table;
2212
+ if (typeof a.keyboard === "boolean") a11y.keyboard = a.keyboard;
2213
+ if (Object.keys(a11y).length > 0) spec.a11y = a11y;
2214
+ }
2180
2215
  if (Array.isArray(s.marks)) {
2181
2216
  const marks = [];
2182
2217
  for (const raw2 of s.marks) {
@@ -2304,6 +2339,7 @@ function renderChartSkeleton(body) {
2304
2339
  body.appendChild(box);
2305
2340
  }
2306
2341
  var CHART_MARK_ACCENT = "var(--icen-token-accent)";
2342
+ var CHART_MARK_ACCENT_TEXT = "var(--icen-token-accent-text)";
2307
2343
  function markToneVar(tone) {
2308
2344
  switch (tone) {
2309
2345
  case "success":
@@ -2439,7 +2475,7 @@ function paintSvgChartMarks(root, view, marks, formatValue, warned) {
2439
2475
  y: String(clampNum(y - 10, 8, CHART_LINE_H - 4)),
2440
2476
  "text-anchor": "middle",
2441
2477
  "font-size": "8",
2442
- fill: CHART_MARK_ACCENT
2478
+ fill: CHART_MARK_ACCENT_TEXT
2443
2479
  });
2444
2480
  label.textContent = mark.text ?? mark.label;
2445
2481
  g.appendChild(label);
@@ -2531,6 +2567,81 @@ function paintVBarChartMarks(root, view, spec, marks, formatValue, warned) {
2531
2567
  function chartAriaLabel(title, typeName) {
2532
2568
  return title ? `${typeName}\uFF1A${title}` : typeName;
2533
2569
  }
2570
+ function summarizeChart(view, type, fmt2) {
2571
+ const series = view.series && view.series.length > 0 ? view.series : [{ name: "", values: view.values ?? [] }];
2572
+ const parts = [];
2573
+ if (series.length > 1) parts.push(t("charts.a11y.seriesCount", { p0: series.length }));
2574
+ if (type === "scatter") {
2575
+ const pts = view.points ?? [];
2576
+ if (pts.length > 0) {
2577
+ const xs = pts.map((p) => p.x);
2578
+ const ys = pts.map((p) => p.y);
2579
+ parts.push(t("charts.a11y.scatterRange", { p0: fmt2(Math.min(...xs)), p1: fmt2(Math.max(...xs)), p2: fmt2(Math.min(...ys)), p3: fmt2(Math.max(...ys)) }));
2580
+ }
2581
+ return parts.join("\uFF1B");
2582
+ }
2583
+ const labels = view.labels ?? [];
2584
+ if (labels.length > 1) parts.push(t("charts.a11y.range", { p0: labels[0], p1: labels[labels.length - 1] }));
2585
+ let min = Infinity;
2586
+ let max = -Infinity;
2587
+ let minAt = "";
2588
+ let maxAt = "";
2589
+ for (const s of series) {
2590
+ s.values.forEach((v, i) => {
2591
+ if (v < min) {
2592
+ min = v;
2593
+ minAt = labels[i] ?? String(i + 1);
2594
+ }
2595
+ if (v > max) {
2596
+ max = v;
2597
+ maxAt = labels[i] ?? String(i + 1);
2598
+ }
2599
+ });
2600
+ }
2601
+ if (Number.isFinite(min) && Number.isFinite(max)) {
2602
+ parts.push(t("charts.a11y.min", { p0: fmt2(min), p1: minAt }));
2603
+ parts.push(t("charts.a11y.max", { p0: fmt2(max), p1: maxAt }));
2604
+ }
2605
+ const first = series[0]?.values ?? [];
2606
+ if (first.length > 1) {
2607
+ const d = first[first.length - 1] - first[0];
2608
+ const key = Math.abs(d) < 1e-9 ? "charts.a11y.trendFlat" : d > 0 ? "charts.a11y.trendUp" : "charts.a11y.trendDown";
2609
+ parts.push(t("charts.a11y.trend", { p0: t(key) }));
2610
+ }
2611
+ return parts.join("\uFF1B");
2612
+ }
2613
+ function buildChartTable(view, spec, type, fmt2) {
2614
+ const table = document.createElement("table");
2615
+ table.className = "icen-chart-table";
2616
+ const caption = document.createElement("caption");
2617
+ caption.textContent = t("charts.a11y.tableCaption", { p0: spec.title ?? String(type) });
2618
+ table.appendChild(caption);
2619
+ const addRow = (target, cells, head) => {
2620
+ const tr = document.createElement("tr");
2621
+ for (const c of cells) {
2622
+ const cell = document.createElement(head ? "th" : "td");
2623
+ if (head) cell.scope = target.tagName === "THEAD" ? "col" : "row";
2624
+ cell.textContent = c;
2625
+ tr.appendChild(cell);
2626
+ }
2627
+ target.appendChild(tr);
2628
+ };
2629
+ const thead = table.createTHead();
2630
+ const tbody = table.createTBody();
2631
+ if (type === "scatter") {
2632
+ addRow(thead, ["x", "y"], true);
2633
+ for (const p of view.points ?? []) addRow(tbody, [fmt2(p.x), fmt2(p.y)], false);
2634
+ return table;
2635
+ }
2636
+ const series = view.series && view.series.length > 0 ? view.series : [{ name: "", values: view.values ?? [] }];
2637
+ const labels = view.labels ?? [];
2638
+ addRow(thead, ["", ...series.map((s, i) => s.name || String(i + 1))], true);
2639
+ const n = Math.max(labels.length, ...series.map((s) => s.values.length));
2640
+ for (let i = 0; i < n; i++) {
2641
+ addRow(tbody, [labels[i] ?? String(i + 1), ...series.map((s) => fmt2(s.values[i] ?? 0))], false);
2642
+ }
2643
+ return table;
2644
+ }
2534
2645
  function svgDimsOf(svg) {
2535
2646
  const vb = (svg.getAttribute("viewBox") ?? "").split(/\s+/).map(Number);
2536
2647
  const w = Number.isFinite(vb[2]) && (vb[2] ?? 0) > 0 ? vb[2] : CHART_LINE_W;
@@ -2871,6 +2982,7 @@ function renderChart(el, raw) {
2871
2982
  if (!isZoomableType(t2) || fullLength() < 2) return;
2872
2983
  const plot = t2 === "vbar" ? root.querySelector(".icen-chart-vbar") : root.querySelector(".icen-chart-line svg");
2873
2984
  if (!plot) return;
2985
+ plot.style.touchAction = current2.zoom === "x" ? "pan-y" : "none";
2874
2986
  const on = (type, fn) => {
2875
2987
  plot.addEventListener(type, fn);
2876
2988
  };
@@ -2926,6 +3038,149 @@ function renderChart(el, raw) {
2926
3038
  render();
2927
3039
  });
2928
3040
  };
3041
+ const KB_TYPES = /* @__PURE__ */ new Set(["line", "area", "vbar", "hbar", "scatter"]);
3042
+ const kbCursor = { si: 0, i: 0, active: false };
3043
+ let liveRegion = null;
3044
+ let a11ySeq = 0;
3045
+ const a11yOf = () => ({
3046
+ summary: current2.a11y?.summary !== false,
3047
+ table: current2.a11y?.table === true,
3048
+ keyboard: current2.a11y?.keyboard !== false
3049
+ });
3050
+ const seriesViewOf = (view) => view.series && view.series.length > 0 ? view.series : [{ name: "", values: view.values ?? [] }];
3051
+ const kbPointCount = (view) => typeOf() === "scatter" ? view.points?.length ?? 0 : Math.max(0, ...seriesViewOf(view).map((s) => s.values.length), view.labels?.length ?? 0);
3052
+ const kbCursorEl = () => {
3053
+ const t2 = typeOf();
3054
+ const view = viewOf();
3055
+ const series = seriesViewOf(view);
3056
+ const n = kbPointCount(view);
3057
+ if (n < 1) return null;
3058
+ const si = clampNum(kbCursor.si, 0, Math.max(0, series.length - 1));
3059
+ const i = clampNum(kbCursor.i, 0, n - 1);
3060
+ if (t2 === "line" || t2 === "area") {
3061
+ const svg = root.querySelector(".icen-chart-line svg");
3062
+ if (!svg) return null;
3063
+ const dots = svg.querySelectorAll("circle");
3064
+ let base = 0;
3065
+ for (let k = 0; k < si; k++) base += Math.min(series[k].values.length, n);
3066
+ return dots.item(base + i) ?? null;
3067
+ }
3068
+ if (t2 === "scatter") return root.querySelector(".icen-chart-scatter svg")?.querySelectorAll("circle").item(i) ?? null;
3069
+ if (t2 === "vbar") return root.querySelectorAll(".icen-chart-vbar-bar").item(series.length > 1 ? i * series.length + si : i) ?? null;
3070
+ if (t2 === "hbar") return root.querySelectorAll(".icen-chart-hbar-bar").item(i) ?? null;
3071
+ return null;
3072
+ };
3073
+ const announceKb = (text) => {
3074
+ if (!liveRegion || !liveRegion.isConnected) {
3075
+ liveRegion = document.createElement("div");
3076
+ liveRegion.className = "icen-chart-live";
3077
+ liveRegion.setAttribute("aria-live", "polite");
3078
+ root.appendChild(liveRegion);
3079
+ }
3080
+ liveRegion.textContent = "";
3081
+ liveRegion.textContent = text;
3082
+ };
3083
+ const applyKbCursor = () => {
3084
+ for (const el2 of root.querySelectorAll(".icen-chart-kb-focus")) el2.classList.remove("icen-chart-kb-focus");
3085
+ if (!kbCursor.active) return;
3086
+ kbCursorEl()?.classList.add("icen-chart-kb-focus");
3087
+ };
3088
+ const announceCursor = () => {
3089
+ const view = viewOf();
3090
+ const series = seriesViewOf(view);
3091
+ const n = kbPointCount(view);
3092
+ if (n < 1) return;
3093
+ const si = clampNum(kbCursor.si, 0, Math.max(0, series.length - 1));
3094
+ const i = clampNum(kbCursor.i, 0, Math.max(0, n - 1));
3095
+ const fmt2 = chartFormatValue(current2.format);
3096
+ if (typeOf() === "scatter") {
3097
+ const p = view.points?.[i];
3098
+ if (!p) return;
3099
+ announceKb(t("charts.a11y.point", { p0: `x ${fmt2(p.x)}`, p1: `y ${fmt2(p.y)}`, p2: i + 1, p3: n }));
3100
+ return;
3101
+ }
3102
+ const label = view.labels?.[i] ?? String(i + 1);
3103
+ const value = series[si]?.values[i] ?? 0;
3104
+ if (series.length > 1) announceKb(t("charts.a11y.pointSeries", { p0: series[si].name || String(si + 1), p1: label, p2: fmt2(value), p3: i + 1, p4: n }));
3105
+ else announceKb(t("charts.a11y.point", { p0: label, p1: fmt2(value), p2: i + 1, p3: n }));
3106
+ };
3107
+ const onKbKeydown = (e) => {
3108
+ const conf = a11yOf();
3109
+ if (!conf.keyboard || !KB_TYPES.has(typeOf())) return;
3110
+ const g = root.querySelector("[data-icen-chart-kb]");
3111
+ if (!g || e.target !== g) return;
3112
+ if (e.key === "Escape") {
3113
+ if (zoomWindow) {
3114
+ zoomWindow = null;
3115
+ kbCursor.active = false;
3116
+ render();
3117
+ }
3118
+ return;
3119
+ }
3120
+ const view = viewOf();
3121
+ const series = seriesViewOf(view);
3122
+ const n = kbPointCount(view);
3123
+ if (n < 1) return;
3124
+ const siMax = Math.max(0, series.length - 1);
3125
+ let moved = true;
3126
+ switch (e.key) {
3127
+ case "ArrowRight":
3128
+ kbCursor.i = clampNum(kbCursor.i + 1, 0, n - 1);
3129
+ break;
3130
+ case "ArrowLeft":
3131
+ kbCursor.i = clampNum(kbCursor.i - 1, 0, n - 1);
3132
+ break;
3133
+ case "ArrowDown":
3134
+ kbCursor.si = clampNum(kbCursor.si + 1, 0, siMax);
3135
+ break;
3136
+ case "ArrowUp":
3137
+ kbCursor.si = clampNum(kbCursor.si - 1, 0, siMax);
3138
+ break;
3139
+ case "Home":
3140
+ kbCursor.i = 0;
3141
+ break;
3142
+ case "End":
3143
+ kbCursor.i = n - 1;
3144
+ break;
3145
+ default:
3146
+ moved = false;
3147
+ }
3148
+ if (!moved) return;
3149
+ e.preventDefault();
3150
+ kbCursor.active = true;
3151
+ applyKbCursor();
3152
+ announceCursor();
3153
+ };
3154
+ root.addEventListener("keydown", onKbKeydown);
3155
+ const applyChartA11yPlus = (view, spec, type, formatValue) => {
3156
+ const conf = a11yOf();
3157
+ const graphic = root.querySelector('[role="img"]');
3158
+ if (!graphic) return;
3159
+ const body = root.querySelector(".icen-chart-body") ?? graphic.parentElement ?? root;
3160
+ const summaryParts = [];
3161
+ if (conf.summary) summaryParts.push(summarizeChart(view, type, formatValue));
3162
+ if (conf.keyboard && KB_TYPES.has(type)) summaryParts.push(t("charts.a11y.keyboardHint"));
3163
+ const summaryText = summaryParts.filter(Boolean).join("\uFF1B");
3164
+ if (summaryText) {
3165
+ const p = document.createElement("p");
3166
+ p.className = "icen-chart-summary";
3167
+ p.id = `icen-chart-a11y-${++a11ySeq}`;
3168
+ p.textContent = summaryText;
3169
+ body.appendChild(p);
3170
+ graphic.setAttribute("aria-describedby", p.id);
3171
+ } else {
3172
+ graphic.removeAttribute("aria-describedby");
3173
+ }
3174
+ if (conf.keyboard && KB_TYPES.has(type)) {
3175
+ graphic.setAttribute("tabindex", "0");
3176
+ graphic.setAttribute("data-icen-chart-kb", "");
3177
+ } else {
3178
+ graphic.removeAttribute("tabindex");
3179
+ graphic.removeAttribute("data-icen-chart-kb");
3180
+ }
3181
+ if (conf.table) body.appendChild(buildChartTable(view, spec, type, formatValue));
3182
+ applyKbCursor();
3183
+ };
2929
3184
  const render = () => {
2930
3185
  if (!current2.type) current2 = { ...current2, type: inferChartType(current2) };
2931
3186
  const spec = current2;
@@ -2997,9 +3252,16 @@ function renderChart(el, raw) {
2997
3252
  const graphic = root.querySelector('[role="img"]');
2998
3253
  if (graphic && !graphic.getAttribute("aria-label")) {
2999
3254
  graphic.setAttribute("aria-label", spec.title ? `${spec.title}\uFF08${type}\uFF09` : type);
3255
+ } else if (!graphic) {
3256
+ const dom = root.querySelector(".icen-chart-vbar, .icen-chart-hbar");
3257
+ if (dom) {
3258
+ dom.setAttribute("role", "img");
3259
+ dom.setAttribute("aria-label", spec.title ? `${spec.title}\uFF08${type}\uFF09` : type);
3260
+ }
3000
3261
  }
3001
3262
  }
3002
3263
  if (loading) return;
3264
+ applyChartA11yPlus(view, spec, type, formatValue);
3003
3265
  paintChartMarks(root, view, spec, formatValue, warnedMarks);
3004
3266
  bindZoomGesture();
3005
3267
  };
@@ -3081,6 +3343,7 @@ function renderChart(el, raw) {
3081
3343
  releaseFluidWidth(root);
3082
3344
  hiddenLegendKeys.delete(root);
3083
3345
  zoomDragCleanup?.();
3346
+ root.removeEventListener("keydown", onKbKeydown);
3084
3347
  root.textContent = "";
3085
3348
  root.classList.remove("is-loading");
3086
3349
  unbindEvents();
@@ -3498,7 +3761,13 @@ function onKeyDown(e) {
3498
3761
 
3499
3762
  // src/behaviors/datatable.ts
3500
3763
  function defaultCompare(a, b) {
3501
- if (typeof a === "number" && typeof b === "number") return a - b;
3764
+ if (typeof a === "number" && typeof b === "number") {
3765
+ if (Number.isNaN(a) || Number.isNaN(b)) {
3766
+ if (Number.isNaN(a) && Number.isNaN(b)) return 0;
3767
+ return Number.isNaN(a) ? 1 : -1;
3768
+ }
3769
+ return a - b;
3770
+ }
3502
3771
  return String(a ?? "").localeCompare(String(b ?? ""), "zh-CN", { numeric: true });
3503
3772
  }
3504
3773
  function toNum2(v) {
@@ -3787,11 +4056,13 @@ function createTable(el, opts) {
3787
4056
  const body = document.createElement("div");
3788
4057
  body.className = "icen-dt-body";
3789
4058
  body.setAttribute("role", "rowgroup");
3790
- const summaryEl = document.createElement("div");
3791
- summaryEl.className = "icen-dt-row icen-dt-row--summary";
3792
- summaryEl.setAttribute("role", "row");
3793
- summaryEl.hidden = true;
3794
- canvas.append(head, body, summaryEl);
4059
+ const summaryEl = opts.summaryRow ? document.createElement("div") : null;
4060
+ if (summaryEl) {
4061
+ summaryEl.className = "icen-dt-row icen-dt-row--summary";
4062
+ summaryEl.setAttribute("role", "row");
4063
+ summaryEl.hidden = true;
4064
+ }
4065
+ canvas.append(head, body, ...summaryEl ? [summaryEl] : []);
3795
4066
  scroll.appendChild(canvas);
3796
4067
  root.appendChild(scroll);
3797
4068
  const foot = document.createElement("div");
@@ -4604,7 +4875,8 @@ function createTable(el, opts) {
4604
4875
  else wrap.textContent = String(out);
4605
4876
  return wrap;
4606
4877
  }
4607
- function renderSummary() {
4878
+ function renderSummary2() {
4879
+ if (!summaryEl) return;
4608
4880
  summaryEl.textContent = "";
4609
4881
  summaryEl.hidden = true;
4610
4882
  const cfg = opts.summaryRow;
@@ -4801,9 +5073,9 @@ function createTable(el, opts) {
4801
5073
  }
4802
5074
  }
4803
5075
  let currentTotal = 0;
4804
- function visibleRange(total) {
5076
+ function visibleRange(total, avail) {
4805
5077
  if (!virtual) {
4806
- if (opts.remote || opts.pagination === false) return [0, total];
5078
+ if (opts.remote || opts.pagination === false) return [0, Math.min(total, avail)];
4807
5079
  const start2 = (page - 1) * pageSize;
4808
5080
  return [start2, Math.min(total, start2 + pageSize)];
4809
5081
  }
@@ -4818,6 +5090,10 @@ function createTable(el, opts) {
4818
5090
  const rows = pipeline();
4819
5091
  const entries = groupFn ? flatEntries() : null;
4820
5092
  const total = entries ? entries.length : opts.remote ? opts.totalCount ?? data.length : rows.length;
5093
+ if (!opts.remote && opts.pagination !== false) {
5094
+ const pages = Math.max(1, Math.ceil(total / pageSize));
5095
+ if (page > pages) page = pages;
5096
+ }
4821
5097
  navTotal = entries ? entries.length : rows.length;
4822
5098
  currentTotal = total;
4823
5099
  const keepTop = virtual ? scroll.scrollTop : 0;
@@ -4834,7 +5110,7 @@ function createTable(el, opts) {
4834
5110
  body.appendChild(sk);
4835
5111
  }
4836
5112
  renderFoot(total, rows.length);
4837
- renderSummary();
5113
+ renderSummary2();
4838
5114
  return;
4839
5115
  }
4840
5116
  if (total === 0) {
@@ -4843,10 +5119,10 @@ function createTable(el, opts) {
4843
5119
  empty.textContent = opts.emptyLabel ?? t("datatable.empty");
4844
5120
  body.appendChild(empty);
4845
5121
  renderFoot(total, rows.length);
4846
- renderSummary();
5122
+ renderSummary2();
4847
5123
  return;
4848
5124
  }
4849
- const [start, end] = visibleRange(total);
5125
+ const [start, end] = visibleRange(total, rows.length);
4850
5126
  const frag = document.createDocumentFragment();
4851
5127
  const offsets = frozenOffsets();
4852
5128
  if (virtual && start > 0) {
@@ -4880,7 +5156,7 @@ function createTable(el, opts) {
4880
5156
  if (virtual) scroll.scrollTop = keepTop;
4881
5157
  syncNavFocus();
4882
5158
  renderFoot(total, rows.length);
4883
- renderSummary();
5159
+ renderSummary2();
4884
5160
  }
4885
5161
  function renderFoot(total, dataCount = total) {
4886
5162
  foot.textContent = "";
@@ -4909,11 +5185,21 @@ function createTable(el, opts) {
4909
5185
  const pages = Math.ceil(total / pageSize);
4910
5186
  const pager = document.createElement("nav");
4911
5187
  pager.className = "icen-dt-pager";
4912
- pager.setAttribute("aria-label", t("datatable.paginationAria"));
5188
+ {
5189
+ const base = t("datatable.paginationAria");
5190
+ const used = /* @__PURE__ */ new Set();
5191
+ for (const el2 of document.querySelectorAll(".icen-dt-pager[aria-label]")) {
5192
+ if (!root.contains(el2)) used.add(el2.getAttribute("aria-label") ?? "");
5193
+ }
5194
+ let label = base, n = 2;
5195
+ while (used.has(label)) label = `${base} ${n++}`;
5196
+ pager.setAttribute("aria-label", label);
5197
+ }
4913
5198
  const sizeSel = document.createElement("select");
4914
5199
  sizeSel.className = "icen-dt-page-size";
4915
5200
  sizeSel.setAttribute("aria-label", t("datatable.pageSizeAria"));
4916
- for (const s of pageSizes) {
5201
+ const sizeOptions = pageSizes.includes(pageSize) ? pageSizes : [...pageSizes, pageSize].sort((a, b) => a - b);
5202
+ for (const s of sizeOptions) {
4917
5203
  const o = document.createElement("option");
4918
5204
  o.value = String(s);
4919
5205
  o.textContent = t("pagination.pageSize", { p0: s });
@@ -5005,6 +5291,7 @@ function createTable(el, opts) {
5005
5291
  if (destroyed) return;
5006
5292
  renderHead();
5007
5293
  renderBody();
5294
+ syncFrozenWidths();
5008
5295
  }
5009
5296
  rekey();
5010
5297
  renderAll();
@@ -5025,12 +5312,52 @@ function createTable(el, opts) {
5025
5312
  opts.onPageChange?.(page, pageSize);
5026
5313
  renderAll();
5027
5314
  }
5315
+ function hasFluidFrozen() {
5316
+ return cols().some((c) => frozenSide(c) != null && !/^(\d+(?:\.\d+)?)px/.test(colWidths.get(c.key) ?? c.width ?? ""));
5317
+ }
5318
+ function syncFrozenWidths() {
5319
+ if (!hasFluidFrozen()) return;
5320
+ const visible = cols();
5321
+ const headCells = Array.from(headRow.children);
5322
+ const lead = headCells.length - visible.length;
5323
+ const left = /* @__PURE__ */ new Map();
5324
+ const right = /* @__PURE__ */ new Map();
5325
+ let acc = headCells.slice(0, lead).reduce((s, c) => s + c.offsetWidth, 0);
5326
+ for (let i = 0; i < visible.length; i++) {
5327
+ const c = visible[i];
5328
+ if (frozenSide(c) === "left") left.set(c.key, acc);
5329
+ acc += headCells[lead + i]?.offsetWidth ?? 0;
5330
+ }
5331
+ let accR = 0;
5332
+ for (let i = visible.length - 1; i >= 0; i--) {
5333
+ const c = visible[i];
5334
+ if (frozenSide(c) === "right") right.set(c.key, accR);
5335
+ accR += headCells[lead + i]?.offsetWidth ?? 0;
5336
+ }
5337
+ const rows = [headRow, ...summaryEl ? [summaryEl] : [], ...Array.from(body.querySelectorAll(".icen-dt-row"))];
5338
+ for (const row of rows) {
5339
+ const cells = Array.from(row.children);
5340
+ if (cells.length !== headCells.length) continue;
5341
+ cells.forEach((cell, i) => {
5342
+ const col = i >= lead ? visible[i - lead] : void 0;
5343
+ if (!col) return;
5344
+ const l = left.get(col.key);
5345
+ if (l != null && cell.classList.contains("icen-dt-cell--frozen")) cell.style.left = `${l}px`;
5346
+ const r = right.get(col.key);
5347
+ if (r != null && cell.classList.contains("icen-dt-cell--frozen-right")) cell.style.right = `${r}px`;
5348
+ });
5349
+ }
5350
+ }
5028
5351
  return {
5029
5352
  setPage,
5030
5353
  setPageSize,
5031
5354
  setData(next) {
5032
5355
  if (editing) cancelEdit(true);
5033
5356
  data = next;
5357
+ if (typeof opts.rowKey !== "function" && typeof opts.rowKey !== "string" && selected.size > 0) {
5358
+ selected.clear();
5359
+ emitSelection();
5360
+ }
5034
5361
  rekey();
5035
5362
  invalidate();
5036
5363
  page = 1;
@@ -5107,6 +5434,823 @@ function createTable(el, opts) {
5107
5434
  };
5108
5435
  }
5109
5436
 
5437
+ // src/behaviors/form-validate.ts
5438
+ var DEFAULT_MESSAGES = {
5439
+ required: t("formValidate.required"),
5440
+ email: t("formValidate.email"),
5441
+ url: t("formValidate.url"),
5442
+ number: t("formValidate.number"),
5443
+ minlength: t("formValidate.minlength"),
5444
+ maxlength: t("formValidate.maxlength"),
5445
+ pattern: t("formValidate.pattern"),
5446
+ min: t("formValidate.min"),
5447
+ max: t("formValidate.max"),
5448
+ step: t("formValidate.step"),
5449
+ matches: t("formValidate.matches")
5450
+ };
5451
+ var messageTable = { ...DEFAULT_MESSAGES };
5452
+ function formatMessage(template, tokens) {
5453
+ return template.replace(/\{(\w+)\}/g, (m, key) => key in tokens ? String(tokens[key]) : m);
5454
+ }
5455
+ var customValidators = /* @__PURE__ */ new Map();
5456
+ function attrNumber(el, name) {
5457
+ const raw = el.getAttribute(name);
5458
+ if (raw == null || raw === "" || !Number.isFinite(Number(raw))) return null;
5459
+ return Number(raw);
5460
+ }
5461
+ function resolveFieldRules(field) {
5462
+ const rules = [];
5463
+ const type = (field.getAttribute("type") ?? "").toLowerCase();
5464
+ if (field.hasAttribute("required")) rules.push({ rule: "required" });
5465
+ if (type === "email" || type === "url") rules.push({ rule: type });
5466
+ if (type === "number" || type === "range") rules.push({ rule: "number" });
5467
+ const minlength = attrNumber(field, "minlength");
5468
+ if (minlength != null) rules.push({ rule: "minlength", attr: String(minlength) });
5469
+ const maxlength = attrNumber(field, "maxlength");
5470
+ if (maxlength != null) rules.push({ rule: "maxlength", attr: String(maxlength) });
5471
+ const pattern = field.getAttribute("pattern");
5472
+ if (pattern) rules.push({ rule: "pattern", attr: pattern });
5473
+ const min = field.getAttribute("min");
5474
+ if (min != null && min !== "") rules.push({ rule: "min", attr: min });
5475
+ const max = field.getAttribute("max");
5476
+ if (max != null && max !== "") rules.push({ rule: "max", attr: max });
5477
+ const step = field.getAttribute("step");
5478
+ if (step != null && step !== "" && step !== "any") rules.push({ rule: "step", attr: step });
5479
+ const extra = field.getAttribute("data-icen-validate") ?? "";
5480
+ for (const token of extra.trim().split(/\s+/)) {
5481
+ if (!token) continue;
5482
+ const idx = token.indexOf(":");
5483
+ const rule = idx === -1 ? token : token.slice(0, idx);
5484
+ const arg = idx === -1 ? void 0 : token.slice(idx + 1) || void 0;
5485
+ if (!rule || rule === "matches" && !arg) continue;
5486
+ rules.push({ rule, arg });
5487
+ }
5488
+ return rules;
5489
+ }
5490
+ function fieldValue(field) {
5491
+ if (field instanceof HTMLInputElement && (field.type === "checkbox" || field.type === "radio")) {
5492
+ return field.checked ? field.value || "on" : "";
5493
+ }
5494
+ return field.value;
5495
+ }
5496
+ function fieldLabel(field, fallback = "") {
5497
+ if (!field) return fallback;
5498
+ const wrap = field.closest(".icen-field, .icen-form-field");
5499
+ const labelEl = wrap?.querySelector(".icen-field-label, .icen-form-label, label");
5500
+ const text = labelEl?.textContent?.replace(/\s+/g, " ").trim();
5501
+ return text || field.getAttribute("aria-label") || field.getAttribute("placeholder") || field.getAttribute("name") || fallback;
5502
+ }
5503
+ function ruleMessage(field, rule, tokens) {
5504
+ const custom2 = field?.getAttribute(`data-icen-msg-${rule}`);
5505
+ const template = custom2 || messageTable[rule] || DEFAULT_MESSAGES[rule] || t("formValidate.invalid");
5506
+ return formatMessage(template, tokens);
5507
+ }
5508
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
5509
+ var NUMBER_TYPES = /* @__PURE__ */ new Set(["number", "range"]);
5510
+ function compareValue(field, value, attr) {
5511
+ const numericAttr = Number.isFinite(Number(attr));
5512
+ const type = field?.getAttribute("type")?.toLowerCase() ?? "";
5513
+ if (numericAttr && (NUMBER_TYPES.has(type) || Number.isFinite(Number(value)))) {
5514
+ if (!Number.isFinite(Number(value))) return null;
5515
+ return Number(value) - Number(attr);
5516
+ }
5517
+ if (!numericAttr) return value < attr ? -1 : value > attr ? 1 : 0;
5518
+ return null;
5519
+ }
5520
+ async function applyRule(value, rule, ctx) {
5521
+ const field = ctx.field;
5522
+ const tokens = {
5523
+ min: rule.attr ?? "",
5524
+ max: rule.attr ?? "",
5525
+ step: rule.attr ?? "",
5526
+ length: value.length,
5527
+ label: fieldLabel(field, rule.arg ?? rule.rule)
5528
+ };
5529
+ switch (rule.rule) {
5530
+ case "required": {
5531
+ if (field instanceof HTMLInputElement && field.type === "checkbox") {
5532
+ return field.checked ? true : ruleMessage(field, "required", tokens);
5533
+ }
5534
+ if (field instanceof HTMLInputElement && field.type === "radio") {
5535
+ const form = ctx.form ?? field.form;
5536
+ const checked = form ? Array.from(form.elements).some(
5537
+ (el) => el instanceof HTMLInputElement && el.type === "radio" && el.name === field.name && el.checked
5538
+ ) : field.checked;
5539
+ return checked ? true : ruleMessage(field, "required", tokens);
5540
+ }
5541
+ return value.trim() !== "" ? true : ruleMessage(field, "required", tokens);
5542
+ }
5543
+ case "email":
5544
+ return value === "" || EMAIL_RE.test(value) ? true : ruleMessage(field, "email", tokens);
5545
+ case "url": {
5546
+ if (value === "") return true;
5547
+ try {
5548
+ new URL(value);
5549
+ return true;
5550
+ } catch {
5551
+ return ruleMessage(field, "url", tokens);
5552
+ }
5553
+ }
5554
+ case "number":
5555
+ return value === "" || Number.isFinite(Number(value)) ? true : ruleMessage(field, "number", tokens);
5556
+ case "minlength": {
5557
+ if (value === "") return true;
5558
+ const min = Number(rule.attr);
5559
+ return value.length >= min ? true : ruleMessage(field, "minlength", tokens);
5560
+ }
5561
+ case "maxlength": {
5562
+ if (value === "") return true;
5563
+ const max = Number(rule.attr);
5564
+ return value.length <= max ? true : ruleMessage(field, "maxlength", tokens);
5565
+ }
5566
+ case "pattern": {
5567
+ if (value === "") return true;
5568
+ try {
5569
+ return new RegExp(`^(?:${rule.attr})$`).test(value) ? true : ruleMessage(field, "pattern", tokens);
5570
+ } catch {
5571
+ return true;
5572
+ }
5573
+ }
5574
+ case "min": {
5575
+ if (value === "") return true;
5576
+ const cmp = compareValue(field, value, rule.attr ?? "");
5577
+ return cmp == null || cmp >= 0 ? true : ruleMessage(field, "min", tokens);
5578
+ }
5579
+ case "max": {
5580
+ if (value === "") return true;
5581
+ const cmp = compareValue(field, value, rule.attr ?? "");
5582
+ return cmp == null || cmp <= 0 ? true : ruleMessage(field, "max", tokens);
5583
+ }
5584
+ case "step": {
5585
+ if (value === "") return true;
5586
+ const step = Number(rule.attr);
5587
+ if (!Number.isFinite(step) || step <= 0 || !Number.isFinite(Number(value))) return true;
5588
+ const minAttr = field?.getAttribute("min");
5589
+ const base = minAttr != null && minAttr !== "" && Number.isFinite(Number(minAttr)) ? Number(minAttr) : 0;
5590
+ const rem = Math.abs((Number(value) - base) % step);
5591
+ return rem < 1e-9 || step - rem < 1e-9 ? true : ruleMessage(field, "step", tokens);
5592
+ }
5593
+ case "matches": {
5594
+ const form = ctx.form ?? field?.form ?? null;
5595
+ const other = form && rule.arg ? form.elements.namedItem(rule.arg) : null;
5596
+ const otherEl = other instanceof Element ? other : null;
5597
+ if (!otherEl) return true;
5598
+ return value === otherEl.value ? true : ruleMessage(field, "matches", tokens);
5599
+ }
5600
+ default: {
5601
+ const fn = customValidators.get(rule.rule);
5602
+ if (!fn || !field) return true;
5603
+ const result = await fn(value, field, ctx.form ?? field.form ?? null);
5604
+ return result === true ? true : typeof result === "string" ? result : true;
5605
+ }
5606
+ }
5607
+ }
5608
+ async function evaluateRules(value, rules, ctx = {}) {
5609
+ for (const rule of rules) {
5610
+ const fail = await applyRule(value, rule, ctx);
5611
+ if (fail !== true) return fail;
5612
+ }
5613
+ return true;
5614
+ }
5615
+ var DEFAULT_DEBOUNCE = 160;
5616
+ var uidSeq = 0;
5617
+ var liveValidators = /* @__PURE__ */ new WeakMap();
5618
+ var composingFlags = /* @__PURE__ */ new WeakMap();
5619
+ var compositionTracked = /* @__PURE__ */ new WeakSet();
5620
+ function trackComposition(el) {
5621
+ if (compositionTracked.has(el)) return;
5622
+ compositionTracked.add(el);
5623
+ el.addEventListener("compositionstart", () => composingFlags.set(el, true));
5624
+ el.addEventListener("compositionend", () => composingFlags.set(el, false));
5625
+ }
5626
+ function isComposing(el) {
5627
+ return composingFlags.get(el) === true;
5628
+ }
5629
+ function collectFormFields(form) {
5630
+ const out = [];
5631
+ for (const el of Array.from(form.elements)) {
5632
+ if (!(el instanceof HTMLInputElement) && !(el instanceof HTMLTextAreaElement) && !(el instanceof HTMLSelectElement)) {
5633
+ continue;
5634
+ }
5635
+ if (el.disabled || !el.name) continue;
5636
+ if (el instanceof HTMLInputElement) {
5637
+ const type = (el.getAttribute("type") ?? "").toLowerCase();
5638
+ if (["submit", "button", "reset", "image", "file"].includes(type)) continue;
5639
+ }
5640
+ out.push(el);
5641
+ }
5642
+ return out;
5643
+ }
5644
+ function findFormField(form, name) {
5645
+ const item = form.elements.namedItem(name);
5646
+ if (item instanceof Element && (item instanceof HTMLInputElement || item instanceof HTMLTextAreaElement || item instanceof HTMLSelectElement)) {
5647
+ return item;
5648
+ }
5649
+ if (item instanceof RadioNodeList) {
5650
+ for (const el of Array.from(item)) {
5651
+ if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
5652
+ return el;
5653
+ }
5654
+ }
5655
+ }
5656
+ return null;
5657
+ }
5658
+ function fieldWrap(field) {
5659
+ return closestSelfOrAncestor(field, ".icen-field, .icen-form-field");
5660
+ }
5661
+ function closestSelfOrAncestor(el, selector) {
5662
+ return el.matches(selector) ? el : el.closest(selector);
5663
+ }
5664
+ function ensureControlId(field) {
5665
+ if (!field.id) field.id = `icen-fv-${++uidSeq}`;
5666
+ return field.id;
5667
+ }
5668
+ function findErrorEl(wrap) {
5669
+ for (const el of Array.from(wrap.children)) {
5670
+ if (el instanceof HTMLElement && el.classList.contains("icen-field-error")) return el;
5671
+ }
5672
+ return null;
5673
+ }
5674
+ function setValidating(field, on) {
5675
+ const wrap = fieldWrap(field);
5676
+ if (wrap) wrap.classList.toggle("is-validating", on);
5677
+ if (on) field.setAttribute("aria-busy", "true");
5678
+ else field.removeAttribute("aria-busy");
5679
+ }
5680
+ function presentField(field, outcome, showValid) {
5681
+ const wrap = fieldWrap(field);
5682
+ setValidating(field, false);
5683
+ if (!wrap) {
5684
+ if (!outcome.ok) field.setAttribute("aria-invalid", "true");
5685
+ else field.removeAttribute("aria-invalid");
5686
+ return;
5687
+ }
5688
+ if (!outcome.ok) {
5689
+ wrap.classList.add("is-error");
5690
+ wrap.setAttribute("data-icen-invalid", "true");
5691
+ wrap.classList.remove("is-valid");
5692
+ wrap.removeAttribute("data-icen-valid");
5693
+ let errEl2 = findErrorEl(wrap);
5694
+ if (!errEl2) {
5695
+ errEl2 = document.createElement("p");
5696
+ errEl2.className = "icen-field-error";
5697
+ errEl2.id = `icen-fv-err-${++uidSeq}`;
5698
+ errEl2.setAttribute("role", "alert");
5699
+ errEl2.setAttribute("data-icen-fv-auto", "");
5700
+ wrap.appendChild(errEl2);
5701
+ }
5702
+ errEl2.hidden = false;
5703
+ errEl2.textContent = outcome.message ?? "";
5704
+ field.setAttribute("aria-invalid", "true");
5705
+ const described = new Set((field.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(Boolean));
5706
+ described.add(errEl2.id);
5707
+ field.setAttribute("aria-describedby", Array.from(described).join(" "));
5708
+ return;
5709
+ }
5710
+ wrap.classList.remove("is-error");
5711
+ wrap.removeAttribute("data-icen-invalid");
5712
+ field.removeAttribute("aria-invalid");
5713
+ const errEl = findErrorEl(wrap);
5714
+ if (errEl) {
5715
+ const rest = (field.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(Boolean).filter((id) => id !== errEl.id);
5716
+ if (rest.length) field.setAttribute("aria-describedby", rest.join(" "));
5717
+ else field.removeAttribute("aria-describedby");
5718
+ if (errEl.hasAttribute("data-icen-fv-auto")) errEl.remove();
5719
+ else {
5720
+ errEl.textContent = "";
5721
+ errEl.hidden = true;
5722
+ }
5723
+ }
5724
+ if (showValid) {
5725
+ wrap.classList.add("is-valid");
5726
+ wrap.setAttribute("data-icen-valid", "true");
5727
+ } else {
5728
+ wrap.classList.remove("is-valid");
5729
+ wrap.removeAttribute("data-icen-valid");
5730
+ }
5731
+ }
5732
+ function clearFieldPresentation(field) {
5733
+ const wrap = fieldWrap(field);
5734
+ if (wrap) {
5735
+ wrap.classList.remove("is-error", "is-valid", "is-validating");
5736
+ wrap.removeAttribute("data-icen-invalid");
5737
+ wrap.removeAttribute("data-icen-valid");
5738
+ const errEl = findErrorEl(wrap);
5739
+ if (errEl?.hasAttribute("data-icen-fv-auto")) errEl.remove();
5740
+ else if (errEl) {
5741
+ errEl.textContent = "";
5742
+ errEl.hidden = true;
5743
+ }
5744
+ }
5745
+ field.removeAttribute("aria-invalid");
5746
+ field.removeAttribute("aria-busy");
5747
+ const rest = (field.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(Boolean).filter((id) => !id.startsWith("icen-fv-err-"));
5748
+ if (rest.length) field.setAttribute("aria-describedby", rest.join(" "));
5749
+ else field.removeAttribute("aria-describedby");
5750
+ }
5751
+ function focusField(field) {
5752
+ try {
5753
+ field.focus();
5754
+ } catch {
5755
+ }
5756
+ if (typeof field.scrollIntoView === "function") {
5757
+ try {
5758
+ const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
5759
+ field.scrollIntoView({ block: "center", behavior: reduced ? "auto" : "smooth" });
5760
+ } catch {
5761
+ try {
5762
+ field.scrollIntoView();
5763
+ } catch {
5764
+ }
5765
+ }
5766
+ }
5767
+ }
5768
+ function renderSummary(form, errorsObj) {
5769
+ if (!form.hasAttribute("data-icen-validate-summary")) return;
5770
+ const names = Object.keys(errorsObj);
5771
+ const existing = form.querySelector(":scope > .icen-form-validate-summary");
5772
+ if (names.length === 0) {
5773
+ existing?.remove();
5774
+ return;
5775
+ }
5776
+ let box = existing;
5777
+ if (!box) {
5778
+ box = document.createElement("div");
5779
+ box.className = "icen-form-validate-summary";
5780
+ box.setAttribute("role", "alert");
5781
+ box.setAttribute("data-icen-fv-auto", "");
5782
+ box.addEventListener("click", (e) => {
5783
+ const a = e.target instanceof Element ? closestSelfOrAncestor(e.target, "a[data-icen-fv-name]") : null;
5784
+ if (!(a instanceof HTMLAnchorElement)) return;
5785
+ e.preventDefault();
5786
+ const field = findFormField(form, a.getAttribute("data-icen-fv-name") ?? "");
5787
+ if (field) focusField(field);
5788
+ });
5789
+ form.prepend(box);
5790
+ }
5791
+ box.textContent = "";
5792
+ const title = document.createElement("p");
5793
+ title.className = "icen-form-validate-summary-title";
5794
+ title.textContent = t("formValidate.summary", { p0: names.length });
5795
+ const list = document.createElement("ul");
5796
+ list.className = "icen-form-validate-summary-list";
5797
+ for (const name of names) {
5798
+ const field = findFormField(form, name);
5799
+ if (!field) continue;
5800
+ const li = document.createElement("li");
5801
+ const a = document.createElement("a");
5802
+ a.className = "icen-form-validate-summary-item";
5803
+ a.href = `#${ensureControlId(field)}`;
5804
+ a.setAttribute("data-icen-fv-name", name);
5805
+ a.textContent = `${fieldLabel(field, name)}\uFF1A${errorsObj[name]}`;
5806
+ li.appendChild(a);
5807
+ list.appendChild(li);
5808
+ }
5809
+ box.append(title, list);
5810
+ }
5811
+ function createFormValidator(form, opts = {}) {
5812
+ if (typeof document === "undefined") {
5813
+ const stub = () => void 0;
5814
+ return {
5815
+ form,
5816
+ validate: async () => true,
5817
+ validateField: async () => true,
5818
+ reset: stub,
5819
+ errors: () => ({}),
5820
+ destroy: stub
5821
+ };
5822
+ }
5823
+ const existing = liveValidators.get(form);
5824
+ if (existing) return existing;
5825
+ const state = {
5826
+ errors: /* @__PURE__ */ new Map(),
5827
+ touched: /* @__PURE__ */ new Set(),
5828
+ submitted: false,
5829
+ seq: /* @__PURE__ */ new Map(),
5830
+ debounceTimers: /* @__PURE__ */ new Map()
5831
+ };
5832
+ let pointerDown = false;
5833
+ const pendingPresent = /* @__PURE__ */ new Map();
5834
+ let pendingSummary = null;
5835
+ let flushScheduled = false;
5836
+ const showValidFor = (field) => field.hasAttribute("data-icen-validate-show-valid") || form.hasAttribute("data-icen-validate-show-valid");
5837
+ async function checkField(field) {
5838
+ const name = field.name;
5839
+ const rules = resolveFieldRules(field);
5840
+ if (rules.length === 0) return true;
5841
+ trackComposition(field);
5842
+ const seq = (state.seq.get(name) ?? 0) + 1;
5843
+ state.seq.set(name, seq);
5844
+ const hasAsync = rules.some((r) => customValidators.has(r.rule));
5845
+ if (hasAsync) setValidating(field, true);
5846
+ const result = await evaluateRules(fieldValue(field), rules, { field, form });
5847
+ if (state.seq.get(name) !== seq) {
5848
+ if (hasAsync) setValidating(field, false);
5849
+ return !state.errors.has(name);
5850
+ }
5851
+ if (hasAsync) setValidating(field, false);
5852
+ const outcome = result === true ? { ok: true } : { ok: false, message: result };
5853
+ if (outcome.ok) state.errors.delete(name);
5854
+ else state.errors.set(name, outcome.message ?? "");
5855
+ presentFieldGuarded(field, outcome, showValidFor(field));
5856
+ if (form.querySelector(":scope > .icen-form-validate-summary")) {
5857
+ renderSummaryGuarded(errors());
5858
+ }
5859
+ emitIcen(
5860
+ field,
5861
+ "icen:form-field-validate",
5862
+ outcome.ok ? { name, valid: true } : { name, valid: false, message: outcome.message }
5863
+ );
5864
+ return outcome.ok;
5865
+ }
5866
+ function debouncedCheck(field) {
5867
+ const name = field.name;
5868
+ const timer = state.debounceTimers.get(name);
5869
+ if (timer) clearTimeout(timer);
5870
+ const delay = opts.strategy?.debounceMs ?? DEFAULT_DEBOUNCE;
5871
+ state.debounceTimers.set(
5872
+ name,
5873
+ setTimeout(() => {
5874
+ state.debounceTimers.delete(name);
5875
+ void checkField(field);
5876
+ }, delay)
5877
+ );
5878
+ }
5879
+ function presentFieldPressed(field, outcome, showValid) {
5880
+ pendingPresent.set(field, { outcome, showValid });
5881
+ const wrap = fieldWrap(field);
5882
+ setValidating(field, false);
5883
+ if (!wrap) {
5884
+ if (!outcome.ok) field.setAttribute("aria-invalid", "true");
5885
+ else field.removeAttribute("aria-invalid");
5886
+ return;
5887
+ }
5888
+ if (!outcome.ok) {
5889
+ wrap.classList.add("is-error");
5890
+ wrap.setAttribute("data-icen-invalid", "true");
5891
+ wrap.classList.remove("is-valid");
5892
+ wrap.removeAttribute("data-icen-valid");
5893
+ field.setAttribute("aria-invalid", "true");
5894
+ const errEl = findErrorEl(wrap);
5895
+ if (errEl && !errEl.hidden) errEl.textContent = outcome.message ?? "";
5896
+ return;
5897
+ }
5898
+ wrap.classList.remove("is-error");
5899
+ wrap.removeAttribute("data-icen-invalid");
5900
+ field.removeAttribute("aria-invalid");
5901
+ if (showValid) {
5902
+ wrap.classList.add("is-valid");
5903
+ wrap.setAttribute("data-icen-valid", "true");
5904
+ } else {
5905
+ wrap.classList.remove("is-valid");
5906
+ wrap.removeAttribute("data-icen-valid");
5907
+ }
5908
+ }
5909
+ function presentFieldGuarded(field, outcome, showValid) {
5910
+ if (pointerDown) presentFieldPressed(field, outcome, showValid);
5911
+ else presentField(field, outcome, showValid);
5912
+ }
5913
+ function renderSummaryGuarded(errorsObj) {
5914
+ if (pointerDown) pendingSummary = errorsObj;
5915
+ else renderSummary(form, errorsObj);
5916
+ }
5917
+ function flushDeferredPresentation() {
5918
+ flushScheduled = false;
5919
+ if (destroyed) return;
5920
+ const items = Array.from(pendingPresent.entries());
5921
+ pendingPresent.clear();
5922
+ const summary = pendingSummary;
5923
+ pendingSummary = null;
5924
+ for (const [field, it] of items) presentField(field, it.outcome, it.showValid);
5925
+ if (summary) renderSummary(form, summary);
5926
+ }
5927
+ function scheduleFlush() {
5928
+ if (flushScheduled) return;
5929
+ flushScheduled = true;
5930
+ if (typeof requestAnimationFrame === "function") requestAnimationFrame(flushDeferredPresentation);
5931
+ else setTimeout(flushDeferredPresentation, 0);
5932
+ }
5933
+ function resolveEventField(target) {
5934
+ if (!(target instanceof Element)) return null;
5935
+ const el = closestSelfOrAncestor(target, "input, textarea, select");
5936
+ if (!(el instanceof HTMLInputElement) && !(el instanceof HTMLTextAreaElement) && !(el instanceof HTMLSelectElement)) {
5937
+ return null;
5938
+ }
5939
+ if (el.form !== form || el.disabled || !el.name) return null;
5940
+ if (el instanceof HTMLInputElement) {
5941
+ const type = (el.getAttribute("type") ?? "").toLowerCase();
5942
+ if (["submit", "button", "reset", "image", "file"].includes(type)) return null;
5943
+ }
5944
+ return el;
5945
+ }
5946
+ const onSubmit = (e) => {
5947
+ e.preventDefault();
5948
+ void validate();
5949
+ };
5950
+ const onFocusOut = (e) => {
5951
+ const field = resolveEventField(e.target);
5952
+ if (!field) return;
5953
+ state.touched.add(field.name);
5954
+ const mode = opts.strategy?.on;
5955
+ if (mode === "blur") void checkField(field);
5956
+ else if (!mode && state.submitted) void checkField(field);
5957
+ };
5958
+ const onInput = (e) => {
5959
+ const field = resolveEventField(e.target);
5960
+ if (!field) return;
5961
+ trackComposition(field);
5962
+ if (isComposing(field)) return;
5963
+ state.touched.add(field.name);
5964
+ const mode = opts.strategy?.on;
5965
+ if (mode === "input") debouncedCheck(field);
5966
+ else if (!mode && state.submitted && state.errors.has(field.name)) debouncedCheck(field);
5967
+ };
5968
+ async function validate() {
5969
+ state.submitted = true;
5970
+ const fields = collectFormFields(form);
5971
+ const names = [];
5972
+ let firstInvalid = null;
5973
+ for (const field of fields) {
5974
+ if (resolveFieldRules(field).length === 0) continue;
5975
+ names.push(field.name);
5976
+ const ok = await checkField(field);
5977
+ if (!ok && !firstInvalid) firstInvalid = field;
5978
+ }
5979
+ const errorsSnapshot = errors();
5980
+ if (firstInvalid) {
5981
+ renderSummaryGuarded(errorsSnapshot);
5982
+ emitIcen(form, "icen:form-invalid", { errors: errorsSnapshot, first: firstInvalid.name });
5983
+ if (opts.scrollToError !== false) focusField(firstInvalid);
5984
+ return false;
5985
+ }
5986
+ renderSummaryGuarded({});
5987
+ emitIcen(form, "icen:form-valid", { names });
5988
+ return true;
5989
+ }
5990
+ function errors() {
5991
+ return Object.fromEntries(state.errors);
5992
+ }
5993
+ function reset() {
5994
+ state.errors.clear();
5995
+ state.touched.clear();
5996
+ state.submitted = false;
5997
+ state.seq.clear();
5998
+ for (const timer of state.debounceTimers.values()) clearTimeout(timer);
5999
+ state.debounceTimers.clear();
6000
+ pendingPresent.clear();
6001
+ pendingSummary = null;
6002
+ for (const field of collectFormFields(form)) clearFieldPresentation(field);
6003
+ form.querySelector(":scope > .icen-form-validate-summary")?.remove();
6004
+ }
6005
+ const onFormPointerDown = (e) => {
6006
+ const btn = e.button;
6007
+ if (typeof btn === "number" && btn !== 0) return;
6008
+ pointerDown = true;
6009
+ };
6010
+ const onPressEnd = () => {
6011
+ if (!pointerDown) return;
6012
+ pointerDown = false;
6013
+ if (pendingPresent.size > 0 || pendingSummary !== null) scheduleFlush();
6014
+ };
6015
+ form.addEventListener("submit", onSubmit);
6016
+ form.addEventListener("focusout", onFocusOut);
6017
+ form.addEventListener("input", onInput);
6018
+ form.addEventListener("pointerdown", onFormPointerDown, true);
6019
+ document.addEventListener("pointerup", onPressEnd, true);
6020
+ document.addEventListener("pointercancel", onPressEnd, true);
6021
+ window.addEventListener("blur", onPressEnd);
6022
+ let destroyed = false;
6023
+ const handle = {
6024
+ form,
6025
+ validate,
6026
+ async validateField(name) {
6027
+ const field = findFormField(form, name);
6028
+ if (!field || field.disabled) return true;
6029
+ return checkField(field);
6030
+ },
6031
+ reset,
6032
+ errors,
6033
+ destroy() {
6034
+ if (destroyed) return;
6035
+ destroyed = true;
6036
+ form.removeEventListener("submit", onSubmit);
6037
+ form.removeEventListener("focusout", onFocusOut);
6038
+ form.removeEventListener("input", onInput);
6039
+ form.removeEventListener("pointerdown", onFormPointerDown, true);
6040
+ document.removeEventListener("pointerup", onPressEnd, true);
6041
+ document.removeEventListener("pointercancel", onPressEnd, true);
6042
+ window.removeEventListener("blur", onPressEnd);
6043
+ reset();
6044
+ liveValidators.delete(form);
6045
+ }
6046
+ };
6047
+ liveValidators.set(form, handle);
6048
+ return handle;
6049
+ }
6050
+ var SHOW_FORM_SCHEMA = {
6051
+ type: "object",
6052
+ properties: {
6053
+ title: { type: "string", description: t("formValidate.tool.title") },
6054
+ fields: {
6055
+ type: "array",
6056
+ description: t("formValidate.tool.fields"),
6057
+ items: {
6058
+ type: "object",
6059
+ properties: {
6060
+ name: { type: "string", description: t("formValidate.tool.name") },
6061
+ label: { type: "string", description: t("formValidate.tool.label") },
6062
+ type: {
6063
+ type: "string",
6064
+ enum: ["text", "email", "number", "url", "textarea", "select", "date"],
6065
+ description: t("formValidate.tool.type")
6066
+ },
6067
+ required: { type: "boolean", description: t("formValidate.tool.required") },
6068
+ pattern: { type: "string", description: t("formValidate.tool.pattern") },
6069
+ min: { type: "number", description: t("formValidate.tool.min") },
6070
+ max: { type: "number", description: t("formValidate.tool.max") },
6071
+ options: {
6072
+ type: "array",
6073
+ description: t("formValidate.tool.options"),
6074
+ items: {
6075
+ anyOf: [
6076
+ { type: "string" },
6077
+ {
6078
+ type: "object",
6079
+ properties: { label: { type: "string" }, value: { type: "string" } },
6080
+ required: ["label", "value"]
6081
+ }
6082
+ ]
6083
+ }
6084
+ },
6085
+ help: { type: "string", description: t("formValidate.tool.help") }
6086
+ },
6087
+ required: ["name", "label"]
6088
+ }
6089
+ },
6090
+ submitLabel: { type: "string", description: t("formValidate.tool.submitText") }
6091
+ },
6092
+ required: ["fields"]
6093
+ };
6094
+ function normalizeShowForm(input) {
6095
+ const raw = input ?? {};
6096
+ const fields = [];
6097
+ if (Array.isArray(raw.fields)) {
6098
+ for (const f of raw.fields) {
6099
+ if (!f || typeof f !== "object") continue;
6100
+ const name = typeof f.name === "string" ? f.name.trim() : "";
6101
+ if (!name) continue;
6102
+ const label = typeof f.label === "string" && f.label ? f.label : name;
6103
+ fields.push({
6104
+ name,
6105
+ label,
6106
+ type: f.type,
6107
+ required: f.required === true,
6108
+ pattern: typeof f.pattern === "string" ? f.pattern : void 0,
6109
+ min: typeof f.min === "number" ? f.min : void 0,
6110
+ max: typeof f.max === "number" ? f.max : void 0,
6111
+ options: Array.isArray(f.options) ? f.options : void 0,
6112
+ help: typeof f.help === "string" ? f.help : void 0
6113
+ });
6114
+ }
6115
+ }
6116
+ return {
6117
+ title: typeof raw.title === "string" && raw.title ? raw.title : void 0,
6118
+ fields,
6119
+ submitLabel: typeof raw.submitLabel === "string" && raw.submitLabel ? raw.submitLabel : void 0
6120
+ };
6121
+ }
6122
+ function renderShowFormField(spec) {
6123
+ const wrap = document.createElement("div");
6124
+ wrap.className = "icen-field";
6125
+ const id = `icen-ai-form-${++uidSeq}`;
6126
+ const label = document.createElement("label");
6127
+ label.className = "icen-field-label";
6128
+ label.htmlFor = id;
6129
+ label.textContent = spec.label;
6130
+ if (spec.required) {
6131
+ const req = document.createElement("span");
6132
+ req.className = "icen-req";
6133
+ req.textContent = "*";
6134
+ label.appendChild(req);
6135
+ }
6136
+ wrap.appendChild(label);
6137
+ let control;
6138
+ if (spec.type === "textarea") {
6139
+ const ta = document.createElement("textarea");
6140
+ ta.className = "icen-form-textarea";
6141
+ ta.setAttribute("rows", "3");
6142
+ control = ta;
6143
+ } else if (spec.type === "select") {
6144
+ const sel = document.createElement("select");
6145
+ sel.className = "icen-form-input";
6146
+ const placeholder = document.createElement("option");
6147
+ placeholder.value = "";
6148
+ placeholder.textContent = t("formValidate.selectPlaceholder");
6149
+ sel.appendChild(placeholder);
6150
+ for (const opt of spec.options ?? []) {
6151
+ const option = document.createElement("option");
6152
+ if (typeof opt === "string") {
6153
+ option.value = opt;
6154
+ option.textContent = opt;
6155
+ } else {
6156
+ option.value = opt.value;
6157
+ option.textContent = opt.label;
6158
+ }
6159
+ sel.appendChild(option);
6160
+ }
6161
+ control = sel;
6162
+ } else {
6163
+ const input = document.createElement("input");
6164
+ input.className = "icen-form-input";
6165
+ input.type = spec.type ?? "text";
6166
+ control = input;
6167
+ }
6168
+ control.id = id;
6169
+ control.name = spec.name;
6170
+ if (spec.required) control.required = true;
6171
+ if (spec.pattern) control.setAttribute("pattern", spec.pattern);
6172
+ if (spec.min != null) control.setAttribute("min", String(spec.min));
6173
+ if (spec.max != null) control.setAttribute("max", String(spec.max));
6174
+ wrap.appendChild(control);
6175
+ if (spec.help) {
6176
+ const hint = document.createElement("span");
6177
+ hint.className = "icen-field-hint";
6178
+ hint.textContent = spec.help;
6179
+ wrap.appendChild(hint);
6180
+ }
6181
+ return wrap;
6182
+ }
6183
+ function collectFormValues(form) {
6184
+ const out = {};
6185
+ for (const field of collectFormFields(form)) {
6186
+ if (field instanceof HTMLInputElement && field.type === "checkbox") {
6187
+ out[field.name] = field.checked;
6188
+ continue;
6189
+ }
6190
+ if (field instanceof HTMLInputElement && field.type === "radio") {
6191
+ if (!(field.name in out)) out[field.name] = field.checked ? field.value : "";
6192
+ else if (field.checked) out[field.name] = field.value;
6193
+ continue;
6194
+ }
6195
+ out[field.name] = field.value;
6196
+ }
6197
+ return out;
6198
+ }
6199
+ function runShowForm(input, mount) {
6200
+ return new Promise((resolve) => {
6201
+ if (typeof document === "undefined") {
6202
+ resolve({ cancelled: true });
6203
+ return;
6204
+ }
6205
+ const spec = normalizeShowForm(input);
6206
+ const root = document.createElement("div");
6207
+ root.className = "icen-ai-form";
6208
+ if (spec.title) {
6209
+ const title = document.createElement("p");
6210
+ title.className = "icen-ai-form-title";
6211
+ title.textContent = spec.title;
6212
+ root.appendChild(title);
6213
+ }
6214
+ const form = document.createElement("form");
6215
+ form.className = "icen-form icen-ai-form-fields";
6216
+ form.setAttribute("data-icen-validate", "");
6217
+ form.setAttribute("data-icen-validate-summary", "");
6218
+ form.setAttribute("novalidate", "");
6219
+ for (const field of spec.fields) form.appendChild(renderShowFormField(field));
6220
+ const actions = document.createElement("div");
6221
+ actions.className = "icen-ai-form-actions";
6222
+ const submit = document.createElement("button");
6223
+ submit.type = "submit";
6224
+ submit.className = "icen-btn icen-btn-primary";
6225
+ submit.textContent = spec.submitLabel ?? t("common.submit");
6226
+ const cancel = document.createElement("button");
6227
+ cancel.type = "button";
6228
+ cancel.className = "icen-btn icen-btn-ghost";
6229
+ cancel.setAttribute("data-icen-ai-form-cancel", "");
6230
+ cancel.textContent = t("common.cancel");
6231
+ actions.append(submit, cancel);
6232
+ form.appendChild(actions);
6233
+ root.appendChild(form);
6234
+ mount.appendChild(root);
6235
+ const handle = createFormValidator(form, { scrollToError: true });
6236
+ const finish = (value) => {
6237
+ form.removeEventListener("icen:form-valid", onValid);
6238
+ cancel.removeEventListener("click", onCancel);
6239
+ handle.destroy();
6240
+ root.remove();
6241
+ resolve(value);
6242
+ };
6243
+ const onValid = () => {
6244
+ finish(collectFormValues(form));
6245
+ };
6246
+ const onCancel = () => {
6247
+ finish({ cancelled: true });
6248
+ };
6249
+ form.addEventListener("icen:form-valid", onValid);
6250
+ cancel.addEventListener("click", onCancel);
6251
+ });
6252
+ }
6253
+
5110
6254
  // src/behaviors/ai-tools.ts
5111
6255
  var toolRegistry = /* @__PURE__ */ new Map();
5112
6256
  var LIVE_AREAS = /* @__PURE__ */ new Set();
@@ -5435,6 +6579,13 @@ registerAiTool({
5435
6579
  return { title, rows: rows.length };
5436
6580
  }
5437
6581
  });
6582
+ registerAiTool({
6583
+ name: "show_form",
6584
+ description: t("formValidate.tool.description"),
6585
+ inputSchema: SHOW_FORM_SCHEMA,
6586
+ present: "mount",
6587
+ run: (input, mount) => runShowForm(input, mount)
6588
+ });
5438
6589
  function isBrowserTools() {
5439
6590
  return typeof document !== "undefined" && typeof window !== "undefined";
5440
6591
  }