@icen.ai/ui 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/AGENTS.md +138 -80
  2. package/README.md +66 -91
  3. package/dist/base.css +208 -179
  4. package/dist/behaviors/accordion.mjs +8 -8
  5. package/dist/behaviors/ai-branch.d.mts +1 -1
  6. package/dist/behaviors/ai-branch.mjs +1010 -94
  7. package/dist/behaviors/ai-chat.d.mts +7 -7
  8. package/dist/behaviors/ai-chat.mjs +1071 -161
  9. package/dist/behaviors/ai-composer.d.mts +44 -12
  10. package/dist/behaviors/ai-composer.mjs +1997 -524
  11. package/dist/behaviors/ai-core.d.mts +1 -1
  12. package/dist/behaviors/ai-core.mjs +1016 -107
  13. package/dist/behaviors/ai-diff.mjs +1019 -127
  14. package/dist/behaviors/ai-feedback.d.mts +1 -1
  15. package/dist/behaviors/ai-feedback.mjs +1035 -126
  16. package/dist/behaviors/ai-panel.d.mts +8 -8
  17. package/dist/behaviors/ai-panel.mjs +1284 -266
  18. package/dist/behaviors/ai-provider.mjs +1003 -101
  19. package/dist/behaviors/ai-threads.d.mts +1 -1
  20. package/dist/behaviors/ai-threads.mjs +1068 -154
  21. package/dist/behaviors/ai-tool.d.mts +2 -2
  22. package/dist/behaviors/ai-tool.mjs +2037 -396
  23. package/dist/behaviors/ai-tools.d.mts +89 -16
  24. package/dist/behaviors/ai-tools.mjs +4448 -372
  25. package/dist/behaviors/autocomplete.d.mts +53 -0
  26. package/dist/behaviors/autocomplete.mjs +1600 -0
  27. package/dist/behaviors/back-top.d.mts +1 -1
  28. package/dist/behaviors/back-top.mjs +951 -14
  29. package/dist/behaviors/carousel.d.mts +1 -1
  30. package/dist/behaviors/carousel.mjs +906 -11
  31. package/dist/behaviors/cascader.d.mts +48 -0
  32. package/dist/behaviors/cascader.mjs +1853 -0
  33. package/dist/behaviors/charts.d.mts +72 -8
  34. package/dist/behaviors/charts.mjs +1901 -256
  35. package/dist/behaviors/code-block.d.mts +36 -0
  36. package/dist/behaviors/code-block.mjs +1611 -0
  37. package/dist/behaviors/command-palette.d.mts +9 -6
  38. package/dist/behaviors/command-palette.mjs +161 -59
  39. package/dist/behaviors/context-menu.d.mts +9 -8
  40. package/dist/behaviors/context-menu.mjs +1112 -44
  41. package/dist/behaviors/controls.d.mts +13 -13
  42. package/dist/behaviors/controls.mjs +989 -130
  43. package/dist/behaviors/copy.d.mts +21 -2
  44. package/dist/behaviors/copy.mjs +1206 -42
  45. package/dist/behaviors/datatable.d.mts +53 -3
  46. package/dist/behaviors/datatable.mjs +1963 -207
  47. package/dist/behaviors/date-picker.d.mts +0 -1
  48. package/dist/behaviors/date-picker.mjs +1340 -293
  49. package/dist/behaviors/design.d.mts +54 -0
  50. package/dist/behaviors/design.mjs +109 -0
  51. package/dist/behaviors/dropdown.d.mts +5 -6
  52. package/dist/behaviors/dropdown.mjs +262 -110
  53. package/dist/behaviors/events.d.mts +120 -3
  54. package/dist/behaviors/events.mjs +47 -2
  55. package/dist/behaviors/feedback.d.mts +30 -0
  56. package/dist/behaviors/feedback.mjs +296 -0
  57. package/dist/behaviors/focus.d.mts +8 -0
  58. package/dist/behaviors/focus.mjs +24 -0
  59. package/dist/behaviors/form-validate.d.mts +102 -0
  60. package/dist/behaviors/form-validate.mjs +6091 -0
  61. package/dist/behaviors/i18n.d.mts +789 -0
  62. package/dist/behaviors/i18n.mjs +1711 -0
  63. package/dist/behaviors/icons.d.mts +93 -0
  64. package/dist/behaviors/icons.mjs +113 -0
  65. package/dist/behaviors/input.d.mts +9 -1
  66. package/dist/behaviors/input.mjs +1056 -60
  67. package/dist/behaviors/kb-access.d.mts +1 -1
  68. package/dist/behaviors/kb-access.mjs +2129 -239
  69. package/dist/behaviors/kb-acl.d.mts +6 -6
  70. package/dist/behaviors/kb-acl.mjs +1119 -179
  71. package/dist/behaviors/kb-answer.mjs +2070 -418
  72. package/dist/behaviors/kb-audit.d.mts +1 -1
  73. package/dist/behaviors/kb-audit.mjs +1076 -154
  74. package/dist/behaviors/kb-canvas.mjs +1100 -177
  75. package/dist/behaviors/kb-chain.mjs +1022 -100
  76. package/dist/behaviors/kb-checkpoint.d.mts +1 -1
  77. package/dist/behaviors/kb-checkpoint.mjs +1319 -169
  78. package/dist/behaviors/kb-chunks.mjs +1152 -213
  79. package/dist/behaviors/kb-citation.d.mts +1 -1
  80. package/dist/behaviors/kb-citation.mjs +1201 -179
  81. package/dist/behaviors/kb-clarify.mjs +1058 -130
  82. package/dist/behaviors/kb-conflict.mjs +1041 -119
  83. package/dist/behaviors/kb-connector.mjs +1104 -176
  84. package/dist/behaviors/kb-core.d.mts +6 -1
  85. package/dist/behaviors/kb-core.mjs +1077 -162
  86. package/dist/behaviors/kb-eval.mjs +1971 -144
  87. package/dist/behaviors/kb-explain.d.mts +1 -1
  88. package/dist/behaviors/kb-explain.mjs +1024 -102
  89. package/dist/behaviors/kb-filter.mjs +2008 -178
  90. package/dist/behaviors/kb-gap.d.mts +1 -1
  91. package/dist/behaviors/kb-gap.mjs +1043 -121
  92. package/dist/behaviors/kb-hittest.d.mts +1 -1
  93. package/dist/behaviors/kb-hittest.mjs +1093 -150
  94. package/dist/behaviors/kb-metadata.mjs +1052 -130
  95. package/dist/behaviors/kb-passage.d.mts +2 -2
  96. package/dist/behaviors/kb-passage.mjs +1101 -161
  97. package/dist/behaviors/kb-pipeline.d.mts +2 -2
  98. package/dist/behaviors/kb-pipeline.mjs +1082 -162
  99. package/dist/behaviors/kb-qa.mjs +1088 -160
  100. package/dist/behaviors/kb-rerank.mjs +1047 -114
  101. package/dist/behaviors/kb-retrieval.mjs +2063 -224
  102. package/dist/behaviors/kb-review.mjs +1990 -159
  103. package/dist/behaviors/kb-sandbox.d.mts +1 -1
  104. package/dist/behaviors/kb-sandbox.mjs +1026 -104
  105. package/dist/behaviors/kb-segment.mjs +1111 -179
  106. package/dist/behaviors/kb-select.d.mts +24 -0
  107. package/dist/behaviors/kb-select.mjs +1989 -0
  108. package/dist/behaviors/kb-sources.d.mts +1 -1
  109. package/dist/behaviors/kb-sources.mjs +1084 -170
  110. package/dist/behaviors/kb-sql.d.mts +1 -1
  111. package/dist/behaviors/kb-sql.mjs +1143 -226
  112. package/dist/behaviors/kb-trace.d.mts +1 -1
  113. package/dist/behaviors/kb-trace.mjs +1042 -128
  114. package/dist/behaviors/kb-visibility.mjs +1086 -158
  115. package/dist/behaviors/kb-who-can.mjs +1974 -137
  116. package/dist/behaviors/mentions.d.mts +93 -0
  117. package/dist/behaviors/mentions.mjs +2152 -0
  118. package/dist/behaviors/modal.d.mts +4 -3
  119. package/dist/behaviors/modal.mjs +1055 -33
  120. package/dist/behaviors/nav.d.mts +1 -1
  121. package/dist/behaviors/nav.mjs +6 -6
  122. package/dist/behaviors/notification.d.mts +16 -8
  123. package/dist/behaviors/notification.mjs +1615 -265
  124. package/dist/behaviors/panel.d.mts +4 -0
  125. package/dist/behaviors/panel.mjs +994 -0
  126. package/dist/behaviors/popover.d.mts +28 -5
  127. package/dist/behaviors/popover.mjs +225 -24
  128. package/dist/behaviors/select.d.mts +5 -2
  129. package/dist/behaviors/select.mjs +1410 -117
  130. package/dist/behaviors/sidebar.d.mts +1 -1
  131. package/dist/behaviors/sidebar.mjs +13 -13
  132. package/dist/behaviors/slider.d.mts +1 -1
  133. package/dist/behaviors/slider.mjs +9 -9
  134. package/dist/behaviors/split-pane.d.mts +1 -1
  135. package/dist/behaviors/split-pane.mjs +7 -7
  136. package/dist/behaviors/suggestions.d.mts +27 -0
  137. package/dist/behaviors/suggestions.mjs +1089 -0
  138. package/dist/behaviors/svg-parse.d.mts +14 -0
  139. package/dist/behaviors/svg-parse.mjs +37 -0
  140. package/dist/behaviors/tabs.d.mts +2 -2
  141. package/dist/behaviors/tabs.mjs +25 -25
  142. package/dist/behaviors/tag-input.d.mts +23 -5
  143. package/dist/behaviors/tag-input.mjs +1359 -104
  144. package/dist/behaviors/theme-switcher.d.mts +24 -0
  145. package/dist/behaviors/theme-switcher.mjs +1981 -0
  146. package/dist/behaviors/theme.d.mts +15 -8
  147. package/dist/behaviors/theme.mjs +390 -28
  148. package/dist/behaviors/toast.d.mts +24 -25
  149. package/dist/behaviors/toast.mjs +1266 -88
  150. package/dist/behaviors/tooltip.d.mts +4 -0
  151. package/dist/behaviors/tooltip.mjs +378 -0
  152. package/dist/behaviors/tree-select.d.mts +69 -0
  153. package/dist/behaviors/tree-select.mjs +1907 -0
  154. package/dist/behaviors/tree.d.mts +1 -1
  155. package/dist/behaviors/tree.mjs +15 -15
  156. package/dist/behaviors/upload.d.mts +3 -2
  157. package/dist/behaviors/upload.mjs +1076 -66
  158. package/dist/cli.mjs +5 -6
  159. package/dist/components/ai-chat.css +666 -573
  160. package/dist/components/ai-diff.css +153 -132
  161. package/dist/components/ai-panel.css +351 -343
  162. package/dist/components/ai-tool.css +393 -238
  163. package/dist/components/autocomplete.css +61 -0
  164. package/dist/components/autocomplete.mjs +3 -0
  165. package/dist/components/back-top.css +34 -31
  166. package/dist/components/badge.css +44 -41
  167. package/dist/components/breadcrumb.css +67 -46
  168. package/dist/components/btn.css +80 -74
  169. package/dist/components/card.css +117 -106
  170. package/dist/components/cascader.css +311 -0
  171. package/dist/components/cascader.mjs +3 -0
  172. package/dist/components/chart-area.css +8 -2
  173. package/dist/components/chart-bar.css +95 -33
  174. package/dist/components/chart-gauge.css +14 -11
  175. package/dist/components/chart-graph.css +20 -17
  176. package/dist/components/chart-heatmap.css +96 -90
  177. package/dist/components/chart-line.css +8 -3
  178. package/dist/components/chart-pie.css +9 -6
  179. package/dist/components/chart-radar.css +8 -5
  180. package/dist/components/chart-scatter.css +9 -6
  181. package/dist/components/chart-sparkline.css +5 -2
  182. package/dist/components/chart-stack.css +14 -11
  183. package/dist/components/charts.css +164 -73
  184. package/dist/components/code-block.css +418 -0
  185. package/dist/components/code-block.mjs +4 -0
  186. package/dist/components/command-palette.css +138 -117
  187. package/dist/components/content.css +257 -251
  188. package/dist/components/copy.css +72 -44
  189. package/dist/components/datatable.css +423 -181
  190. package/dist/components/date-picker.css +135 -104
  191. package/dist/components/empty.css +68 -65
  192. package/dist/components/feedback.css +201 -183
  193. package/dist/components/form-validate.css +120 -0
  194. package/dist/components/form-validate.mjs +3 -0
  195. package/dist/components/form.css +150 -127
  196. package/dist/components/icon.css +102 -0
  197. package/dist/components/icon.mjs +3 -0
  198. package/dist/components/input.css +159 -104
  199. package/dist/components/kb-agent.css +188 -173
  200. package/dist/components/kb-data.css +257 -253
  201. package/dist/components/kb-ground.css +197 -185
  202. package/dist/components/kb-ingest.css +334 -350
  203. package/dist/components/kb-ops.css +265 -259
  204. package/dist/components/kb-perm.css +260 -271
  205. package/dist/components/kb-search.css +317 -360
  206. package/dist/components/kb.css +120 -109
  207. package/dist/components/layout.css +85 -82
  208. package/dist/components/material-chinese.css +73 -0
  209. package/dist/components/material-soviet.css +76 -0
  210. package/dist/components/material.css +275 -0
  211. package/dist/components/media.css +220 -166
  212. package/dist/components/mentions.css +189 -0
  213. package/dist/components/mentions.mjs +3 -0
  214. package/dist/components/menu.css +113 -105
  215. package/dist/components/modal.css +107 -112
  216. package/dist/components/nav.css +118 -115
  217. package/dist/components/notification.css +238 -174
  218. package/dist/components/pagination.css +56 -53
  219. package/dist/components/panel.css +66 -63
  220. package/dist/components/panel.mjs +1 -0
  221. package/dist/components/pill.css +81 -71
  222. package/dist/components/popover.css +67 -53
  223. package/dist/components/scroll-area.css +113 -110
  224. package/dist/components/segmented.css +146 -143
  225. package/dist/components/select.css +206 -186
  226. package/dist/components/sidebar.css +137 -127
  227. package/dist/components/slider.css +117 -114
  228. package/dist/components/split-pane.css +112 -109
  229. package/dist/components/stat.css +112 -106
  230. package/dist/components/steps.css +109 -103
  231. package/dist/components/switch.css +138 -134
  232. package/dist/components/table.css +131 -107
  233. package/dist/components/tabs.css +114 -108
  234. package/dist/components/tag-input.css +48 -133
  235. package/dist/components/tag.css +81 -71
  236. package/dist/components/theme-switcher.css +105 -0
  237. package/dist/components/theme-switcher.mjs +3 -0
  238. package/dist/components/toast.css +100 -86
  239. package/dist/components/toolbar.css +45 -42
  240. package/dist/components/tooltip.css +102 -43
  241. package/dist/components/tooltip.mjs +1 -0
  242. package/dist/components/tree-select.css +416 -0
  243. package/dist/components/tree-select.mjs +3 -0
  244. package/dist/components/upload.css +135 -92
  245. package/dist/fonts/LICENSES/ark-pixel/OFL.txt +96 -0
  246. package/dist/fonts/LICENSES/cubic-11/OFL.txt +108 -0
  247. package/dist/fonts/LICENSES/galmuri/LICENSE.txt +93 -0
  248. package/dist/fonts/OFL.txt +96 -0
  249. package/dist/fonts/README.md +14 -0
  250. package/dist/fonts/fusion-pixel-12px-zh-hans.woff2 +0 -0
  251. package/dist/index.d.mts +28 -14
  252. package/dist/index.mjs +20705 -9601
  253. package/dist/react.d.mts +91 -0
  254. package/dist/react.mjs +8208 -0
  255. package/dist/registry.json +130 -13
  256. package/dist/tokens/chinese.css +44 -0
  257. package/dist/tokens/colors.css +365 -1222
  258. package/dist/tokens/design.css +109 -0
  259. package/dist/tokens/dtcg.json +11198 -0
  260. package/dist/tokens/figma.variables.json +7017 -0
  261. package/dist/tokens/icon-masks.css +78 -0
  262. package/dist/tokens/pixel.css +50 -0
  263. package/dist/tokens/soviet.css +44 -0
  264. package/dist/tokens/style-profiles.css +36 -547
  265. package/dist/tokens/themes.json +308 -0
  266. package/dist/tokens/tokens-studio.json +6728 -0
  267. package/dist/tokens/typography.css +21 -18
  268. package/dist/tokens.css +1555 -1801
  269. package/dist/ui.css +11975 -9831
  270. package/dist/vue.d.mts +344 -0
  271. package/dist/vue.mjs +7781 -0
  272. package/docs/spec/ai-input-experience.md +75 -0
  273. package/docs/spec/ai-native.md +238 -147
  274. package/docs/spec/design-philosophy.md +189 -0
  275. package/docs/spec/design-primitives.md +196 -0
  276. package/docs/spec/kb-family.md +147 -115
  277. package/package.json +43 -8
  278. package/dist/retro-effects.css +0 -255
  279. package/dist/tokens/retro-effects.css +0 -255
  280. package/docs/research/2026-10-03-ai-kb-components.md +0 -392
  281. package/docs/research/2026-10-03-kb-permissions.md +0 -992
@@ -0,0 +1,120 @@
1
+ @layer icen.components {
2
+ /*
3
+ * form-validate.css — 表单校验呈现层(与 behaviors/form-validate.ts 配套)
4
+ *
5
+ * DOM 契约(行为层写入;本文件只负责呈现,不驱动逻辑):
6
+ * <form data-icen-validate [data-icen-validate-summary] [data-icen-validate-show-valid]>
7
+ * 字段 = form 内有 name 的 input / textarea / select;initFormValidate() 自动挂 novalidate。
8
+ * 校验态落在「最近的 .icen-field / .icen-form-field 包装器」上:
9
+ * .is-error + data-icen-invalid="true" 未过:控件 aria-invalid="true",
10
+ * 自动创建 <p class="icen-field-error" role="alert" data-icen-fv-auto>(id 自动生成,
11
+ * 控件 aria-describedby 接线;手写错误节点不带 data-icen-fv-auto,清空后保留)
12
+ * .is-valid + data-icen-valid="true" 通过(仅 form/字段带 data-icen-validate-show-valid 时挂)
13
+ * .is-validating 异步校验中(控件 aria-busy="true")
14
+ * 提交失败且 form 带 data-icen-validate-summary → 表单顶部渲染错误清单:
15
+ * <div class="icen-form-validate-summary" role="alert">
16
+ * <p class="icen-form-validate-summary-title">…</p>
17
+ * <ul class="icen-form-validate-summary-list">
18
+ * <li><a class="icen-form-validate-summary-item" href="#字段 id">…</a></li>…
19
+ * </ul></div>
20
+ * AI 工具 show_form 挂载体(import behaviors/form-validate.ts 即注册):
21
+ * <div class="icen-ai-form"><p class="icen-ai-form-title">…</p>
22
+ * <form class="icen-form icen-ai-form-fields" data-icen-validate data-icen-validate-summary>
23
+ * <div class="icen-field"><label class="icen-field-label">…<span class="icen-req">*</span></label>
24
+ * <input|icen-textarea|select class="icen-form-input|icen-form-textarea">…
25
+ * [<span class="icen-field-hint">…</span>]</div>…
26
+ * <div class="icen-ai-form-actions"><button class="icen-btn icen-btn-primary">提交</button>
27
+ * <button class="icen-btn icen-btn-ghost" data-icen-ai-form-cancel>取消</button></div>
28
+ * </form></div>
29
+ *
30
+ * 样式策略:控件本身的错误边框/焦点环归 form.css(:invalid / [data-icen-invalid] 同已支持),
31
+ * 本文件只补包装器态(.is-error/.is-valid/.is-validating)、字段级错误/提示文案、
32
+ * 提交失败汇总清单与 show_form 挂载体布局——全部消费 --icen-token-*。
33
+ */
34
+
35
+ /* ── 字段包装器校验态(控件视觉归 form.css,这里只挂状态色与动效提示) ── */
36
+
37
+ .icen-field.is-error > .icen-field-label,
38
+ .icen-field.is-error > .icen-form-label { color: var(--icen-token-error); }
39
+
40
+ .icen-field.is-valid > .icen-field-label,
41
+ .icen-field.is-valid > .icen-form-label { color: var(--icen-token-success-text, color-mix(in srgb, var(--icen-token-success) 90%, black)); }
42
+ [data-icen-theme='dark'] .icen-field.is-valid > .icen-field-label,
43
+ [data-icen-theme='dark'] .icen-field.is-valid > .icen-form-label { color: var(--icen-token-success-text, var(--icen-token-success)); }
44
+
45
+ .icen-field.is-validating { cursor: progress; }
46
+ .icen-field.is-validating > :is(input, textarea, select) { opacity: var(--icen-disabled-opacity); }
47
+
48
+ /* ── 字段级文案 ── */
49
+
50
+ .icen-field-label {
51
+ display: block;
52
+ font-size: var(--icen-density-font-size-sm);
53
+ color: var(--icen-token-text);
54
+ margin-bottom: var(--icen-density-space-xs);
55
+ }
56
+ .icen-field-label .icen-req { color: var(--icen-token-error); margin-inline-start: 2px; }
57
+
58
+ .icen-field-hint {
59
+ display: block;
60
+ font-size: var(--icen-density-font-size-xs);
61
+ color: var(--icen-token-text-faint);
62
+ line-height: 1.4;
63
+ margin-top: var(--icen-density-space-xs);
64
+ }
65
+
66
+ .icen-field-error {
67
+ margin: var(--icen-density-space-xs) 0 0;
68
+ font-size: var(--icen-density-font-size-xs);
69
+ color: var(--icen-token-error);
70
+ line-height: 1.4;
71
+ }
72
+
73
+ /* ── 提交失败汇总清单(form[data-icen-validate-summary],表单顶部,锚点聚焦字段) ── */
74
+
75
+ .icen-form-validate-summary {
76
+ border: 1px solid var(--icen-token-error);
77
+ border-radius: var(--icen-radius-md);
78
+ background: var(--icen-token-error-soft, var(--icen-token-bg-soft));
79
+ padding: var(--icen-density-space-sm) var(--icen-density-space-md);
80
+ margin-bottom: var(--icen-density-space-md);
81
+ }
82
+
83
+ .icen-form-validate-summary-title {
84
+ margin: 0 0 var(--icen-density-space-xs);
85
+ font-size: var(--icen-density-font-size-sm);
86
+ font-weight: 600;
87
+ color: var(--icen-token-error);
88
+ }
89
+
90
+ .icen-form-validate-summary-list {
91
+ margin: 0;
92
+ padding-inline-start: 1.2em;
93
+ display: flex;
94
+ flex-direction: column;
95
+ gap: 2px;
96
+ }
97
+
98
+ .icen-form-validate-summary-item { color: var(--icen-token-error); text-decoration: none; }
99
+ .icen-form-validate-summary-item:hover { text-decoration: underline; }
100
+
101
+ /* ── AI 工具 show_form 挂载体(嵌在 ai-tool 卡的 IO 区,窄宽单列) ── */
102
+
103
+ .icen-ai-form { display: flex; flex-direction: column; gap: var(--icen-density-space-sm); }
104
+
105
+ .icen-ai-form-title {
106
+ margin: 0;
107
+ font-size: var(--icen-density-font-size-md);
108
+ font-weight: 600;
109
+ color: var(--icen-token-text);
110
+ }
111
+
112
+ .icen-ai-form-fields { gap: var(--icen-density-space-sm); }
113
+
114
+ .icen-ai-form-actions {
115
+ display: flex;
116
+ gap: var(--icen-density-space-sm);
117
+ justify-content: flex-end;
118
+ }
119
+
120
+ }
@@ -0,0 +1,3 @@
1
+ // @icen.ai/ui — kit: form-validate(CSS + behavior 一行入口;构建产物,勿手改)
2
+ import './form-validate.css';
3
+ export * from '../behaviors/form-validate.mjs';
@@ -1,3 +1,4 @@
1
+ @layer icen.components {
1
2
  /*
2
3
  * @icen.ai/ui — Component: form(表单结构:字段组 / label / hint / 横幅提示)
3
4
  *
@@ -6,261 +7,283 @@
6
7
  * - form.css 处理"字段→label→hint 的纵向布局"和"页面级结果横幅"
7
8
  *
8
9
  * 契约:
9
- * <form class="form">
10
- * <fieldset class="form-field">
11
- * <label class="form-label" for="x">姓名</label>
12
- * <span class="control"><input id="x" class="form-input" /></span>
13
- * <span class="form-hint">必填,3-20 个字符</span> <!-- 可选 -->
14
- * <span class="form-error">姓名长度不足</span> <!-- 可选,aria-live=polite -->
10
+ * <icen-form class="icen-form">
11
+ * <fieldset class="icen-form-field">
12
+ * <label class="icen-form-label" for="x">姓名</label>
13
+ * <span class="icen-control"><icen-input id="x" class="icen-form-input" /></span>
14
+ * <span class="icen-form-hint">必填,3-20 个字符</span> <!-- 可选 -->
15
+ * <span class="icen-form-error">姓名长度不足</span> <!-- 可选,aria-live=polite -->
15
16
  * </fieldset>
16
- * </form>
17
+ * </icen-form>
17
18
  *
18
- * <div class="banner-ok [.banner-block]">已保存</div>
19
- * <div class="banner-err [.banner-block]">提交失败:网络错误</div>
19
+ * <div class="icen-banner-ok [.icen-banner-block]">已保存</div>
20
+ * <div class="icen-banner-err [.icen-banner-block]">提交失败:网络错误</div>
20
21
  *
21
- * form-input 同时支持 :invalid 与 [data-invalid=true],behavior 二选一驱动。
22
+ * form-input 的错误态以 :user-invalid(交互后)与 [data-icen-invalid=true](behavior 驱动)为通道;
23
+ * 原生回退 :invalid:not(:placeholder-shown) 启发式。
22
24
  */
23
25
 
24
- .form { display: flex; flex-direction: column; gap: var(--density-space-md); }
26
+ .icen-form { display: flex; flex-direction: column; gap: var(--icen-density-space-md); }
25
27
 
26
- .form-field {
28
+ .icen-form-field {
27
29
  display: flex;
28
30
  flex-direction: column;
29
- gap: var(--density-space-xs);
31
+ gap: var(--icen-density-space-xs);
30
32
  margin: 0;
31
33
  padding: 0;
32
34
  border: 0;
33
35
  }
34
36
 
35
- .form-label {
36
- font-size: var(--density-font-size-sm);
37
- color: var(--token-text-muted);
37
+ .icen-form-label {
38
+ font-size: var(--icen-density-font-size-sm);
39
+ color: var(--icen-token-text-muted);
38
40
  letter-spacing: 0.02em;
39
41
  font-weight: 500;
40
42
  }
41
- .form-label .req { color: var(--token-error); margin-left: 2px; }
43
+ .icen-form-label .icen-req { color: var(--icen-token-error); margin-inline-start: 2px; }
42
44
 
43
- /* 裸 input(非 .control wrapper 用法) */
44
- .form-input,
45
- .form-textarea {
46
- width: 100%;
47
- padding: 0 var(--density-space-md);
48
- height: var(--density-input-height-md);
49
- background: var(--token-control);
50
- color: var(--token-text);
51
- border: 1px solid var(--token-outline-soft);
52
- border-radius: var(--radius-md);
53
- font-family: var(--font-body);
54
- font-size: var(--density-font-size-md);
45
+ /* 裸 input(非 .icen-control wrapper 用法) */
46
+ .icen-form-input,
47
+ .icen-form-textarea {
48
+ inline-size: 100%;
49
+ padding: 0 var(--icen-density-space-md);
50
+ height: var(--icen-density-input-height-md);
51
+ background: var(--icen-token-control);
52
+ color: var(--icen-token-text);
53
+ border: 1px solid var(--icen-token-outline-soft);
54
+ border-radius: var(--icen-radius-md);
55
+ font-family: var(--icen-font-body);
56
+ font-size: var(--icen-density-font-size-md);
55
57
  outline: none;
56
58
  transition:
57
59
  border-color var(--icen-tx-fast),
58
60
  box-shadow var(--icen-tx-fast),
59
61
  background-color var(--icen-tx-fast);
60
62
  }
61
- .form-textarea {
63
+ .icen-form-textarea {
62
64
  height: auto;
63
65
  min-height: calc(4 * 1.5em + 24px);
64
- padding: var(--density-space-sm) var(--density-space-md);
66
+ padding: var(--icen-density-space-sm) var(--icen-density-space-md);
65
67
  line-height: 1.5;
66
68
  resize: vertical;
67
69
  }
68
- .form-input::placeholder,
69
- .form-textarea::placeholder {
70
- color: color-mix(in srgb, var(--token-text-faint) 60%, transparent);
70
+ .icen-form-input::placeholder,
71
+ .icen-form-textarea::placeholder {
72
+ color: color-mix(in srgb, var(--icen-token-text-faint) 60%, transparent);
71
73
  }
72
- .form-input:hover,
73
- .form-textarea:hover {
74
+ .icen-form-input:hover,
75
+ .icen-form-textarea:hover {
74
76
  border-color: var(--icen-hover-border);
75
77
  }
76
- .form-input:focus-visible,
77
- .form-textarea:focus-visible {
78
+ .icen-form-input:focus-visible,
79
+ .icen-form-textarea:focus-visible {
78
80
  border-color: var(--icen-control-focus-border);
79
81
  box-shadow: var(--icen-control-focus-shadow);
80
82
  }
81
- .form-input:disabled,
82
- .form-textarea:disabled,
83
- .form-input[aria-disabled='true'],
84
- .form-textarea[aria-disabled='true'] {
83
+ .icen-form-input:disabled,
84
+ .icen-form-textarea:disabled,
85
+ .icen-form-input[aria-disabled='true'],
86
+ .icen-form-textarea[aria-disabled='true'] {
85
87
  opacity: var(--icen-disabled-opacity);
86
88
  cursor: not-allowed;
87
- background: var(--token-bg-soft);
89
+ background: var(--icen-token-bg-soft);
88
90
  }
89
91
 
90
- /* 校验态:原生 :invalid 与显式 [data-invalid] 同时支持 */
91
- .form-input:where(:invalid:not(:placeholder-shown), [data-invalid='true']),
92
- .form-textarea:where(:invalid:not(:placeholder-shown), [data-invalid='true']) {
93
- border-color: var(--token-error);
94
- }
95
- .form-input:where(:invalid:not(:placeholder-shown), [data-invalid='true']):focus-visible,
96
- .form-textarea:where(:invalid:not(:placeholder-shown), [data-invalid='true']):focus-visible {
97
- border-color: var(--token-error);
92
+ /* 校验态::user-invalid(交互后才判 invalid,现代引擎)为主通道;
93
+ 显式 [data-icen-invalid] 由 behavior 驱动;原生回退 :invalid:not(:placeholder-shown) 启发式 */
94
+ .icen-form-input:where(:user-invalid, [data-icen-invalid='true']),
95
+ .icen-form-textarea:where(:user-invalid, [data-icen-invalid='true']) {
96
+ border-color: var(--icen-token-error);
97
+ }
98
+ .icen-form-input:where(:user-invalid, [data-icen-invalid='true']):focus-visible,
99
+ .icen-form-textarea:where(:user-invalid, [data-icen-invalid='true']):focus-visible {
100
+ border-color: var(--icen-token-error);
98
101
  box-shadow: var(--icen-control-error-shadow);
99
102
  }
103
+ @supports not selector(:user-invalid) {
104
+ .icen-form-input:where(:invalid:not(:placeholder-shown), [data-icen-invalid='true']),
105
+ .icen-form-textarea:where(:invalid:not(:placeholder-shown), [data-icen-invalid='true']) {
106
+ border-color: var(--icen-token-error);
107
+ }
108
+ .icen-form-input:where(:invalid:not(:placeholder-shown), [data-icen-invalid='true']):focus-visible,
109
+ .icen-form-textarea:where(:invalid:not(:placeholder-shown), [data-icen-invalid='true']):focus-visible {
110
+ border-color: var(--icen-token-error);
111
+ box-shadow: var(--icen-control-error-shadow);
112
+ }
113
+ }
100
114
 
101
115
  /* 尺寸变体 */
102
- .form-input--sm, .form-textarea--sm { height: var(--density-input-height-sm); font-size: var(--density-font-size-sm); }
103
- .form-input--lg, .form-textarea--lg { height: var(--density-input-height-lg); font-size: var(--density-font-size-lg); }
116
+ .icen-form-input--sm, .icen-form-textarea--sm { height: var(--icen-density-input-height-sm); font-size: var(--icen-density-font-size-sm); }
117
+ .icen-form-input--lg, .icen-form-textarea--lg { height: var(--icen-density-input-height-lg); font-size: var(--icen-density-font-size-lg); }
104
118
 
105
- .form-hint {
106
- font-size: var(--density-font-size-xs);
107
- color: var(--token-text-faint);
119
+ .icen-form-hint {
120
+ font-size: var(--icen-density-font-size-xs);
121
+ color: var(--icen-token-text-faint);
108
122
  line-height: 1.4;
109
123
  }
110
- .form-error {
111
- font-size: var(--density-font-size-xs);
112
- color: var(--token-error);
124
+ .icen-form-error {
125
+ font-size: var(--icen-density-font-size-xs);
126
+ color: var(--icen-token-error);
113
127
  line-height: 1.4;
114
128
  }
115
- .form-field[data-invalid='true'] .form-label { color: var(--token-error); }
129
+ .icen-form-field[data-icen-invalid='true'] .icen-form-label { color: var(--icen-token-error); }
116
130
 
117
131
  /* ── 表单增强 ── */
118
132
 
119
133
  /* 水平布局(label 与控件同行) */
120
- .form-field--horizontal {
134
+ .icen-form-field--horizontal {
121
135
  display: flex;
122
136
  flex-direction: row;
123
137
  align-items: flex-start;
124
- gap: var(--density-space-md);
138
+ gap: var(--icen-density-space-md);
125
139
  }
126
- .form-field--horizontal .form-label {
140
+ .icen-form-field--horizontal .icen-form-label {
127
141
  flex-shrink: 0;
128
- width: 120px;
129
- text-align: right;
130
- padding-top: calc((var(--density-input-height-md) - 1lh) / 2);
142
+ inline-size: 120px;
143
+ text-align: end;
144
+ padding-top: calc((var(--icen-density-input-height-md) - 1lh) / 2);
131
145
  }
132
- .form-field--horizontal .form-field-control {
146
+ .icen-form-field--horizontal .icen-form-field-control {
133
147
  flex: 1;
134
148
  min-width: 0;
135
149
  }
136
150
 
137
151
  /* label 带帮助提示图标 */
138
- .form-label-help {
152
+ .icen-form-label-help {
139
153
  display: inline-flex;
140
154
  align-items: center;
141
- margin-left: 4px;
155
+ margin-inline-start: 4px;
142
156
  width: 14px;
143
157
  height: 14px;
144
- color: var(--token-text-faint);
158
+ color: var(--icen-token-text-faint);
145
159
  cursor: help;
146
160
  }
147
- .form-label-help svg { width: 12px; height: 12px; }
161
+ .icen-form-label-help svg { width: 12px; height: 12px; }
148
162
 
149
163
  /* label 带可选标记 */
150
- .form-label-optional {
151
- font-size: var(--density-font-size-2xs);
164
+ .icen-form-label-optional {
165
+ font-size: var(--icen-density-font-size-2xs);
152
166
  font-weight: 400;
153
- color: var(--token-text-faint);
154
- margin-left: 4px;
167
+ color: var(--icen-token-text-faint);
168
+ margin-inline-start: 4px;
155
169
  }
156
170
 
157
171
  /* 表单区块标题 */
158
- .form-section {
159
- padding: var(--density-space-sm) 0;
172
+ .icen-form-section {
173
+ padding: var(--icen-density-space-sm) 0;
160
174
  }
161
- .form-section-title {
162
- font-size: var(--density-font-size-md);
175
+ .icen-form-section-title {
176
+ font-size: var(--icen-density-font-size-md);
163
177
  font-weight: 600;
164
- color: var(--token-text);
165
- margin: 0 0 var(--density-space-sm);
166
- padding-bottom: var(--density-space-xs);
167
- border-bottom: 1px solid var(--token-outline-soft);
178
+ color: var(--icen-token-text);
179
+ margin: 0 0 var(--icen-density-space-sm);
180
+ padding-bottom: var(--icen-density-space-xs);
181
+ border-bottom: 1px solid var(--icen-token-outline-soft);
168
182
  }
169
- .form-section-desc {
170
- font-size: var(--density-font-size-sm);
171
- color: var(--token-text-muted);
172
- margin: calc(-1 * var(--density-space-xs)) 0 var(--density-space-sm);
183
+ .icen-form-section-desc {
184
+ font-size: var(--icen-density-font-size-sm);
185
+ color: var(--icen-token-text-muted);
186
+ margin: calc(-1 * var(--icen-density-space-xs)) 0 var(--icen-density-space-sm);
173
187
  }
174
188
 
175
189
  /* 表单操作区(底部按钮行) */
176
- .form-actions {
190
+ .icen-form-actions {
177
191
  display: flex;
178
192
  align-items: center;
179
- gap: var(--density-space-sm);
180
- padding-top: var(--density-space-sm);
181
- border-top: 1px solid var(--token-outline-soft);
193
+ gap: var(--icen-density-space-sm);
194
+ padding-top: var(--icen-density-space-sm);
195
+ border-top: 1px solid var(--icen-token-outline-soft);
182
196
  }
183
- .form-actions--right { justify-content: flex-end; }
184
- .form-actions--center { justify-content: center; }
185
- .form-actions--split { justify-content: space-between; }
197
+ .icen-form-actions--right { justify-content: flex-end; }
198
+ .icen-form-actions--center { justify-content: center; }
199
+ .icen-form-actions--split { justify-content: space-between; }
186
200
 
187
201
  /* 行内表单(紧凑,单行多字段) */
188
- .form--inline {
202
+ .icen-form--inline {
189
203
  flex-direction: row;
190
204
  align-items: flex-end;
191
- gap: var(--density-space-sm);
205
+ gap: var(--icen-density-space-sm);
192
206
  flex-wrap: wrap;
193
207
  }
194
- .form--inline .form-field {
208
+ .icen-form--inline .icen-form-field {
195
209
  flex: 1;
196
210
  min-width: 160px;
197
211
  }
198
212
 
199
213
  /* 字段网格(多列布局) */
200
- .form-grid {
214
+ .icen-form-grid {
201
215
  display: grid;
202
216
  grid-template-columns: repeat(var(--form-cols, 2), 1fr);
203
- gap: var(--density-space-md);
217
+ gap: var(--icen-density-space-md);
204
218
  }
205
- .form-grid .form-field--full { grid-column: 1 / -1; }
219
+ .icen-form-grid .icen-form-field--full { grid-column: 1 / -1; }
206
220
 
207
221
  /* 字段成功态 */
208
- .form-field[data-valid='true'] .form-label::after {
209
- content: '✓';
210
- margin-left: 4px;
211
- color: var(--token-success);
222
+ .icen-form-field[data-icen-valid='true'] .icen-form-label::after {
223
+ content: '';
224
+ display: inline-block;
225
+ inline-size: 16px;
226
+ block-size: 16px;
227
+ flex: none;
228
+ vertical-align: middle;
229
+ background: currentColor;
230
+ mask: var(--icen-token-icon-check) center / contain no-repeat;
231
+ margin-inline-start: 4px;
232
+ color: var(--icen-token-success);
212
233
  font-weight: 700;
213
234
  }
214
235
 
215
236
  /* 表单加载遮罩 */
216
- .form-loading {
237
+ .icen-form-loading {
217
238
  position: relative;
218
239
  pointer-events: none;
219
240
  opacity: 0.6;
220
241
  }
221
- .form-loading::after {
242
+ .icen-form-loading::after {
222
243
  content: '';
223
244
  position: absolute;
224
245
  top: 50%;
225
- left: 50%;
246
+ left: 50%; /* guard:allow-physical 圆心居中:left 50% + 负边距 任意方向下均为中心 */
226
247
  width: 24px;
227
248
  height: 24px;
228
- margin: -12px 0 0 -12px;
229
- border: 2px solid color-mix(in srgb, var(--token-accent) 30%, transparent);
230
- border-top-color: var(--token-accent);
249
+ margin: -12px 0 0 -12px; /* guard:allow-physical 居中负边距与 left 50% 配套(物理坐标对),RTL 下同样居中 */
250
+ border: 2px solid color-mix(in srgb, var(--icen-token-accent) 30%, transparent);
251
+ border-top-color: var(--icen-token-accent);
231
252
  border-radius: 50%;
232
253
  animation: icen-spin var(--form-loading-duration, 0.8s) linear infinite;
233
254
  }
234
255
 
235
256
  /* ── 页面级横幅:操作结果 / 引导提示 ──
236
257
  与 feedback.css 的 alert 共享同一套视觉 token(--banner-* 仅在底色/文字/描边上分流) */
237
- .banner-ok,
238
- .banner-err {
258
+ .icen-banner-ok,
259
+ .icen-banner-err {
239
260
  display: flex;
240
261
  align-items: flex-start;
241
- gap: var(--density-space-sm);
242
- padding: var(--density-space-sm) var(--density-space-md);
243
- margin-bottom: var(--density-space-md);
244
- border-radius: var(--radius-md);
245
- font-size: var(--density-font-size-sm);
262
+ gap: var(--icen-density-space-sm);
263
+ padding: var(--icen-density-space-sm) var(--icen-density-space-md);
264
+ margin-bottom: var(--icen-density-space-md);
265
+ border-radius: var(--icen-radius-md);
266
+ font-size: var(--icen-density-font-size-sm);
246
267
  line-height: 1.5;
247
268
  border: 1px solid transparent;
248
269
  background: var(--banner-bg);
249
270
  color: var(--banner-fg);
250
271
  border-color: var(--banner-border);
251
272
  }
252
- .banner-ok {
253
- --banner-bg: var(--token-success-muted);
254
- --banner-fg: var(--token-success);
255
- --banner-border: color-mix(in srgb, var(--token-success) 25%, transparent);
273
+ .icen-banner-ok {
274
+ --banner-bg: var(--icen-token-success-muted);
275
+ --banner-fg: var(--icen-token-success);
276
+ --banner-border: color-mix(in srgb, var(--icen-token-success) 25%, transparent);
256
277
  }
257
- .banner-err {
258
- --banner-bg: var(--token-error-muted);
259
- --banner-fg: var(--token-error);
260
- --banner-border: color-mix(in srgb, var(--token-error) 25%, transparent);
278
+ .icen-banner-err {
279
+ --banner-bg: var(--icen-token-error-muted);
280
+ --banner-fg: var(--icen-token-error);
281
+ --banner-border: color-mix(in srgb, var(--icen-token-error) 25%, transparent);
261
282
  }
262
283
  /* block 模式:横向占满,带图标位 */
263
- .banner-block {
284
+ .icen-banner-block {
264
285
  align-items: center;
265
- padding: var(--density-space-md);
286
+ padding: var(--icen-density-space-md);
287
+ }
288
+
266
289
  }
@@ -0,0 +1,102 @@
1
+ @layer icen.components {
2
+ /* Interface symbols and quiet actions. SVG is usable without this CSS; this file
3
+ * adds optical alignment, fixed action hit areas and progressive disclosure.
4
+ * data-icen-reveal on a labelled secondary action: parent hover/focus reveals it;
5
+ * keyboard and coarse pointers always retain access. No layout shift on reveal. */
6
+ .icen-symbol { display: inline-block; flex: none; vertical-align: middle; overflow: visible; }
7
+ .icen-icon-action {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ flex: 0 0 auto;
12
+ inline-size: var(--icen-density-input-height-sm);
13
+ block-size: var(--icen-density-input-height-sm);
14
+ padding: 0;
15
+ border: 0;
16
+ border-radius: var(--icen-radius-sm);
17
+ background: transparent;
18
+ color: var(--icen-token-text-muted);
19
+ cursor: pointer;
20
+ transition: opacity var(--icen-tx-fast), background-color var(--icen-tx-fast), color var(--icen-tx-fast), transform var(--icen-tx-fast);
21
+ }
22
+ .icen-icon-action:hover { background: var(--icen-token-fill-1); color: var(--icen-token-text); }
23
+ .icen-icon-action:active { transform: scale(.94); }
24
+ .icen-icon-action:focus-visible { outline: 2px solid var(--icen-token-accent); outline-offset: 2px; }
25
+ .icen-icon-action:disabled { opacity: var(--icen-disabled-opacity); pointer-events: none; }
26
+ @media (hover: hover) and (pointer: fine) {
27
+ .icen-icon-action[data-icen-reveal] { opacity: 0; }
28
+ :hover > .icen-icon-action[data-icen-reveal],
29
+ :focus-within > .icen-icon-action[data-icen-reveal],
30
+ .icen-icon-action[data-icen-reveal]:focus-visible { opacity: 1; }
31
+ }
32
+ @media (pointer: coarse) {
33
+ .icen-icon-action { min-inline-size: 44px; min-block-size: 44px; }
34
+ }
35
+ @media (prefers-reduced-motion: reduce) {
36
+ .icen-icon-action { transition: none; }
37
+ .icen-icon-action:active { transform: none; }
38
+ }
39
+
40
+ :root[data-icen-style="pixel"] .icen-symbol { stroke-linecap: square; stroke-linejoin: miter; shape-rendering: crispEdges; }
41
+
42
+ /* Search action: enhanceSearchField(input) adds these classes to a dedicated
43
+ wrapper and input, plus one labelled button with a decorative SVG.
44
+ Zero specificity positioning preserves component sticky search headers. */
45
+ :where(.icen-search-field) { position: relative; }
46
+ .icen-search-input::-webkit-search-cancel-button,
47
+ .icen-search-input::-webkit-search-decoration {
48
+ -webkit-appearance: none;
49
+ appearance: none;
50
+ }
51
+ .icen-search-clear {
52
+ display: inline-flex;
53
+ align-items: center;
54
+ justify-content: center;
55
+ inline-size: 24px;
56
+ block-size: 24px;
57
+ flex-shrink: 0;
58
+ padding: 0;
59
+ border: 0;
60
+ border-radius: var(--icen-radius-sm);
61
+ color: var(--icen-token-text-muted);
62
+ background: transparent;
63
+ cursor: pointer;
64
+ transition: color var(--icen-tx-fast), background var(--icen-tx-fast);
65
+ }
66
+ .icen-search-clear > svg { inline-size: 16px; block-size: 16px; pointer-events: none; }
67
+ .icen-search-clear:hover {
68
+ color: var(--icen-token-text);
69
+ background: var(--icen-token-surface-hover);
70
+ }
71
+ .icen-search-clear:focus-visible {
72
+ outline: 2px solid var(--icen-token-accent);
73
+ outline-offset: -2px;
74
+ }
75
+ @media (prefers-reduced-motion: reduce) {
76
+ .icen-search-clear { transition: none; }
77
+ }
78
+
79
+ /* Feedback actions keep their label's footprint while work is pending. */
80
+ .icen-toast-action, .icen-notification-action { position: relative; }
81
+ .icen-feedback-action-label { display: inline-block; }
82
+ [aria-busy="true"] > .icen-feedback-action-label { opacity: 0; }
83
+ .icen-feedback-action-spinner { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; }
84
+ .icen-feedback-action-spinner > svg { inline-size: 16px; block-size: 16px; animation: icen-spin calc(var(--icen-duration-normal) * 4) linear infinite; }
85
+ .icen-toast-action[aria-disabled="true"], .icen-notification-action[aria-disabled="true"] { cursor: progress; }
86
+ .icen-toast-action[aria-disabled="true"]:not([aria-busy="true"]), .icen-notification-action[aria-disabled="true"]:not([aria-busy="true"]) { opacity: var(--icen-disabled-opacity); }
87
+ .icen-feedback-error { display: block; margin-block-start: var(--icen-density-space-xs); color: var(--icen-token-error); font-size: var(--icen-density-font-size-sm); line-height: 1.5; }
88
+ :root[data-icen-style="pixel"] .icen-feedback-action-spinner > svg { animation-timing-function: steps(8,end); }
89
+ @media (prefers-reduced-motion: reduce) { .icen-feedback-action-spinner > svg { animation: none; } }
90
+ .icen-suggestion-status { display: flex; align-items: center; gap: var(--icen-density-space-xs); padding: var(--icen-density-space-sm); color: var(--icen-token-text-muted); font-size: var(--icen-density-font-size-sm); line-height: var(--icen-line-height-body); }
91
+ .icen-suggestion-symbol { display: inline-flex; align-items: center; justify-content: center; inline-size: 20px; block-size: 20px; flex: 0 0 20px; }
92
+ .icen-suggestion-symbol > svg { inline-size: 16px; block-size: 16px; }
93
+ .icen-suggestion-copy { flex: 1; min-inline-size: 0; overflow-wrap: anywhere; }
94
+ .icen-suggestion-status.is-loading .icen-suggestion-symbol > svg { animation: icen-spin calc(var(--icen-duration-normal) * 4) linear infinite; }
95
+ .icen-suggestion-retry { flex: 0 0 auto; padding-inline: var(--icen-density-space-sm); min-block-size: var(--icen-density-input-height-sm); border: 1px solid var(--icen-token-outline-soft); border-radius: var(--icen-radius-sm); background: var(--icen-token-fill-1); color: var(--icen-token-text); font: inherit; cursor: pointer; }
96
+ .icen-suggestion-retry:focus-visible { outline: 2px solid var(--icen-token-accent); outline-offset: 1px; }
97
+ @media (hover: hover) and (pointer: fine) { .icen-suggestion-retry:hover { background: var(--icen-token-fill-2); } }
98
+ @media (pointer: coarse) { .icen-suggestion-retry { min-inline-size: 44px; min-block-size: 44px; } }
99
+ :root[data-icen-style="pixel"] .icen-suggestion-status.is-loading .icen-suggestion-symbol > svg { animation-timing-function: steps(8,end); }
100
+ @media (prefers-reduced-motion: reduce) { .icen-suggestion-status.is-loading .icen-suggestion-symbol > svg { animation: none; } }
101
+
102
+ }
@@ -0,0 +1,3 @@
1
+ // @icen.ai/ui — kit: icon(CSS + behavior 一行入口;构建产物,勿手改)
2
+ import './icon.css';
3
+ export { ICON_PATHS, iconPath, iconHTML, createIcon, registerIcon } from '../behaviors/icons.mjs';