@icen.ai/ui 0.8.0 → 0.9.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 (228) hide show
  1. package/AGENTS.md +63 -14
  2. package/README.md +17 -9
  3. package/dist/base.css +4 -3
  4. package/dist/behaviors/accordion.d.mts +5 -2
  5. package/dist/behaviors/accordion.mjs +36 -11
  6. package/dist/behaviors/ai-branch.d.mts +19 -0
  7. package/dist/behaviors/ai-branch.mjs +217 -0
  8. package/dist/behaviors/ai-chat.d.mts +11 -7
  9. package/dist/behaviors/ai-chat.mjs +134 -51
  10. package/dist/behaviors/ai-composer.d.mts +28 -3
  11. package/dist/behaviors/ai-composer.mjs +399 -229
  12. package/dist/behaviors/ai-core.d.mts +9 -4
  13. package/dist/behaviors/ai-core.mjs +21 -8
  14. package/dist/behaviors/ai-diff.d.mts +2 -2
  15. package/dist/behaviors/ai-diff.mjs +172 -41
  16. package/dist/behaviors/ai-feedback.d.mts +26 -0
  17. package/dist/behaviors/ai-feedback.mjs +303 -0
  18. package/dist/behaviors/ai-panel.d.mts +26 -9
  19. package/dist/behaviors/ai-panel.mjs +211 -110
  20. package/dist/behaviors/ai-provider.mjs +54 -48
  21. package/dist/behaviors/ai-threads.d.mts +38 -0
  22. package/dist/behaviors/ai-threads.mjs +319 -0
  23. package/dist/behaviors/ai-tool.d.mts +12 -4
  24. package/dist/behaviors/ai-tool.mjs +316 -263
  25. package/dist/behaviors/ai-tools.d.mts +9 -6
  26. package/dist/behaviors/ai-tools.mjs +264 -155
  27. package/dist/behaviors/back-top.mjs +16 -1
  28. package/dist/behaviors/carousel.d.mts +5 -2
  29. package/dist/behaviors/carousel.mjs +53 -26
  30. package/dist/behaviors/charts.d.mts +170 -22
  31. package/dist/behaviors/charts.mjs +1016 -150
  32. package/dist/behaviors/command-palette.d.mts +40 -6
  33. package/dist/behaviors/command-palette.mjs +143 -15
  34. package/dist/behaviors/context-menu.d.mts +21 -4
  35. package/dist/behaviors/context-menu.mjs +122 -38
  36. package/dist/behaviors/controls.d.mts +59 -0
  37. package/dist/behaviors/controls.mjs +601 -0
  38. package/dist/behaviors/copy.d.mts +5 -2
  39. package/dist/behaviors/copy.mjs +37 -12
  40. package/dist/behaviors/datatable.d.mts +12 -0
  41. package/dist/behaviors/datatable.mjs +226 -43
  42. package/dist/behaviors/date-picker.d.mts +2 -2
  43. package/dist/behaviors/date-picker.mjs +171 -32
  44. package/dist/behaviors/dropdown.d.mts +6 -2
  45. package/dist/behaviors/dropdown.mjs +164 -25
  46. package/dist/behaviors/events.d.mts +523 -0
  47. package/dist/behaviors/events.mjs +208 -0
  48. package/dist/behaviors/input.mjs +25 -12
  49. package/dist/behaviors/kb-access.d.mts +34 -0
  50. package/dist/behaviors/kb-access.mjs +569 -0
  51. package/dist/behaviors/kb-acl.d.mts +50 -0
  52. package/dist/behaviors/kb-acl.mjs +405 -0
  53. package/dist/behaviors/kb-answer.d.mts +38 -0
  54. package/dist/behaviors/kb-answer.mjs +2055 -0
  55. package/dist/behaviors/kb-audit.d.mts +23 -0
  56. package/dist/behaviors/kb-audit.mjs +375 -0
  57. package/dist/behaviors/kb-canvas.d.mts +44 -0
  58. package/dist/behaviors/kb-canvas.mjs +589 -0
  59. package/dist/behaviors/kb-chain.d.mts +15 -0
  60. package/dist/behaviors/kb-chain.mjs +270 -0
  61. package/dist/behaviors/kb-checkpoint.d.mts +16 -0
  62. package/dist/behaviors/kb-checkpoint.mjs +342 -0
  63. package/dist/behaviors/kb-chunks.d.mts +31 -0
  64. package/dist/behaviors/kb-chunks.mjs +475 -0
  65. package/dist/behaviors/kb-citation.d.mts +38 -0
  66. package/dist/behaviors/kb-citation.mjs +677 -0
  67. package/dist/behaviors/kb-clarify.d.mts +20 -0
  68. package/dist/behaviors/kb-clarify.mjs +248 -0
  69. package/dist/behaviors/kb-conflict.d.mts +22 -0
  70. package/dist/behaviors/kb-conflict.mjs +284 -0
  71. package/dist/behaviors/kb-connector.d.mts +17 -0
  72. package/dist/behaviors/kb-connector.mjs +375 -0
  73. package/dist/behaviors/kb-core.d.mts +529 -0
  74. package/dist/behaviors/kb-core.mjs +890 -0
  75. package/dist/behaviors/kb-eval.d.mts +22 -0
  76. package/dist/behaviors/kb-eval.mjs +408 -0
  77. package/dist/behaviors/kb-explain.d.mts +11 -0
  78. package/dist/behaviors/kb-explain.mjs +243 -0
  79. package/dist/behaviors/kb-filter.d.mts +31 -0
  80. package/dist/behaviors/kb-filter.mjs +587 -0
  81. package/dist/behaviors/kb-gap.d.mts +27 -0
  82. package/dist/behaviors/kb-gap.mjs +273 -0
  83. package/dist/behaviors/kb-hittest.d.mts +34 -0
  84. package/dist/behaviors/kb-hittest.mjs +409 -0
  85. package/dist/behaviors/kb-metadata.d.mts +29 -0
  86. package/dist/behaviors/kb-metadata.mjs +326 -0
  87. package/dist/behaviors/kb-passage.d.mts +41 -0
  88. package/dist/behaviors/kb-passage.mjs +529 -0
  89. package/dist/behaviors/kb-pipeline.d.mts +18 -0
  90. package/dist/behaviors/kb-pipeline.mjs +381 -0
  91. package/dist/behaviors/kb-qa.d.mts +26 -0
  92. package/dist/behaviors/kb-qa.mjs +332 -0
  93. package/dist/behaviors/kb-rerank.d.mts +20 -0
  94. package/dist/behaviors/kb-rerank.mjs +315 -0
  95. package/dist/behaviors/kb-retrieval.d.mts +49 -0
  96. package/dist/behaviors/kb-retrieval.mjs +746 -0
  97. package/dist/behaviors/kb-review.d.mts +44 -0
  98. package/dist/behaviors/kb-review.mjs +451 -0
  99. package/dist/behaviors/kb-sandbox.d.mts +33 -0
  100. package/dist/behaviors/kb-sandbox.mjs +325 -0
  101. package/dist/behaviors/kb-segment.d.mts +32 -0
  102. package/dist/behaviors/kb-segment.mjs +499 -0
  103. package/dist/behaviors/kb-sources.d.mts +27 -0
  104. package/dist/behaviors/kb-sources.mjs +373 -0
  105. package/dist/behaviors/kb-sql.d.mts +43 -0
  106. package/dist/behaviors/kb-sql.mjs +948 -0
  107. package/dist/behaviors/kb-trace.d.mts +16 -0
  108. package/dist/behaviors/kb-trace.mjs +414 -0
  109. package/dist/behaviors/kb-visibility.d.mts +44 -0
  110. package/dist/behaviors/kb-visibility.mjs +403 -0
  111. package/dist/behaviors/kb-who-can.d.mts +32 -0
  112. package/dist/behaviors/kb-who-can.mjs +379 -0
  113. package/dist/behaviors/modal.d.mts +24 -5
  114. package/dist/behaviors/modal.mjs +76 -10
  115. package/dist/behaviors/nav.d.mts +1 -1
  116. package/dist/behaviors/nav.mjs +33 -7
  117. package/dist/behaviors/notification.d.mts +20 -4
  118. package/dist/behaviors/notification.mjs +53 -73
  119. package/dist/behaviors/popover.d.mts +39 -3
  120. package/dist/behaviors/popover.mjs +90 -4
  121. package/dist/behaviors/select.d.mts +4 -2
  122. package/dist/behaviors/select.mjs +161 -24
  123. package/dist/behaviors/sidebar.d.mts +5 -2
  124. package/dist/behaviors/sidebar.mjs +43 -13
  125. package/dist/behaviors/slider.d.mts +5 -2
  126. package/dist/behaviors/slider.mjs +54 -43
  127. package/dist/behaviors/split-pane.d.mts +6 -2
  128. package/dist/behaviors/split-pane.mjs +62 -21
  129. package/dist/behaviors/tabs.d.mts +3 -2
  130. package/dist/behaviors/tabs.mjs +65 -14
  131. package/dist/behaviors/tag-input.d.mts +5 -2
  132. package/dist/behaviors/tag-input.mjs +61 -21
  133. package/dist/behaviors/theme.d.mts +13 -2
  134. package/dist/behaviors/theme.mjs +48 -4
  135. package/dist/behaviors/toast.d.mts +30 -8
  136. package/dist/behaviors/toast.mjs +74 -7
  137. package/dist/behaviors/tree.d.mts +5 -2
  138. package/dist/behaviors/tree.mjs +38 -15
  139. package/dist/behaviors/upload.mjs +21 -23
  140. package/dist/cli.mjs +1 -1
  141. package/dist/components/ai-branch.mjs +3 -0
  142. package/dist/components/ai-chat.css +295 -0
  143. package/dist/components/ai-feedback.mjs +3 -0
  144. package/dist/components/ai-panel.css +218 -1
  145. package/dist/components/ai-threads.mjs +3 -0
  146. package/dist/components/ai-tool-call.mjs +1 -1
  147. package/dist/components/badge.css +1 -1
  148. package/dist/components/breadcrumb.css +0 -32
  149. package/dist/components/chart-calendar.mjs +1 -2
  150. package/dist/components/chart-graph.css +57 -0
  151. package/dist/components/chart-graph.mjs +4 -0
  152. package/dist/components/chart-map.mjs +3 -0
  153. package/dist/components/chart-pie.css +0 -6
  154. package/dist/components/chart-scatter.mjs +1 -1
  155. package/dist/components/charts.css +33 -1
  156. package/dist/components/charts.mjs +1 -0
  157. package/dist/components/content.css +3 -16
  158. package/dist/components/datatable.css +1 -1
  159. package/dist/components/date-picker.css +1 -1
  160. package/dist/components/kb-access.mjs +4 -0
  161. package/dist/components/kb-acl.mjs +4 -0
  162. package/dist/components/kb-agent.css +481 -0
  163. package/dist/components/kb-answer.mjs +4 -0
  164. package/dist/components/kb-audit.mjs +4 -0
  165. package/dist/components/kb-canvas.mjs +4 -0
  166. package/dist/components/kb-chain.mjs +4 -0
  167. package/dist/components/kb-checkpoint.mjs +4 -0
  168. package/dist/components/kb-chunks.mjs +4 -0
  169. package/dist/components/kb-citation.mjs +4 -0
  170. package/dist/components/kb-clarify.mjs +4 -0
  171. package/dist/components/kb-conflict.mjs +4 -0
  172. package/dist/components/kb-connector.mjs +4 -0
  173. package/dist/components/kb-data.css +649 -0
  174. package/dist/components/kb-eval.mjs +4 -0
  175. package/dist/components/kb-explain.mjs +4 -0
  176. package/dist/components/kb-filter.mjs +4 -0
  177. package/dist/components/kb-gap.mjs +4 -0
  178. package/dist/components/kb-ground.css +566 -0
  179. package/dist/components/kb-hittest.mjs +4 -0
  180. package/dist/components/kb-ingest.css +898 -0
  181. package/dist/components/kb-metadata.mjs +4 -0
  182. package/dist/components/kb-ops.css +548 -0
  183. package/dist/components/kb-passage.mjs +4 -0
  184. package/dist/components/kb-perm.css +444 -0
  185. package/dist/components/kb-pipeline.mjs +4 -0
  186. package/dist/components/kb-qa.mjs +4 -0
  187. package/dist/components/kb-rerank.mjs +4 -0
  188. package/dist/components/kb-retrieval.mjs +4 -0
  189. package/dist/components/kb-review.mjs +4 -0
  190. package/dist/components/kb-sandbox.mjs +4 -0
  191. package/dist/components/kb-search.css +940 -0
  192. package/dist/components/kb-segment.mjs +4 -0
  193. package/dist/components/kb-sources.mjs +4 -0
  194. package/dist/components/kb-sql.mjs +4 -0
  195. package/dist/components/kb-trace.mjs +4 -0
  196. package/dist/components/kb-visibility.mjs +4 -0
  197. package/dist/components/kb-who-can.mjs +4 -0
  198. package/dist/components/kb.css +252 -0
  199. package/dist/components/kb.mjs +9 -0
  200. package/dist/components/notification.css +2 -3
  201. package/dist/components/pagination.css +3 -1
  202. package/dist/components/pagination.mjs +1 -0
  203. package/dist/components/rating.mjs +1 -0
  204. package/dist/components/segmented.css +9 -1
  205. package/dist/components/segmented.mjs +1 -1
  206. package/dist/components/select.css +67 -4
  207. package/dist/components/steps.css +13 -3
  208. package/dist/components/steps.mjs +1 -0
  209. package/dist/components/switch.css +3 -1
  210. package/dist/components/switch.mjs +1 -0
  211. package/dist/components/table.mjs +1 -0
  212. package/dist/components/tabs.css +23 -0
  213. package/dist/components/tag-input.css +1 -6
  214. package/dist/components/upload.css +0 -15
  215. package/dist/index.d.mts +48 -11
  216. package/dist/index.mjs +13205 -3615
  217. package/dist/registry.json +366 -15
  218. package/dist/retro-effects.css +3 -16
  219. package/dist/tokens/retro-effects.css +3 -16
  220. package/dist/tokens/style-profiles.css +3 -2
  221. package/dist/tokens/typography.css +2 -0
  222. package/dist/tokens.css +5 -2
  223. package/dist/ui.css +5713 -267
  224. package/docs/research/2026-10-03-ai-kb-components.md +392 -0
  225. package/docs/research/2026-10-03-kb-permissions.md +992 -0
  226. package/docs/spec/ai-native.md +7 -4
  227. package/docs/spec/kb-family.md +443 -0
  228. package/package.json +2 -2
@@ -0,0 +1,601 @@
1
+ // src/behaviors/events.ts
2
+ function emitIcen(source, type, detail) {
3
+ return source.dispatchEvent(
4
+ new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
5
+ );
6
+ }
7
+
8
+ // src/behaviors/input.ts
9
+ function trackComposing(target) {
10
+ let composing = false;
11
+ target.addEventListener("compositionstart", () => {
12
+ composing = true;
13
+ });
14
+ target.addEventListener("compositionend", () => {
15
+ composing = false;
16
+ });
17
+ return () => composing;
18
+ }
19
+ function setupClear(wrap) {
20
+ const btn = wrap.querySelector(".input-clear");
21
+ const field = wrap.querySelector("input.input, textarea.textarea");
22
+ if (!btn || !field) return;
23
+ btn.addEventListener("click", () => {
24
+ field.value = "";
25
+ field.dispatchEvent(new Event("input", { bubbles: true }));
26
+ field.focus();
27
+ });
28
+ }
29
+ function setupPasswordToggle(wrap) {
30
+ const btn = wrap.querySelector(".input-pw-toggle");
31
+ const field = wrap.querySelector("input.input");
32
+ if (!btn || !field) return;
33
+ const iconShow = btn.querySelector('[data-icon="show"]');
34
+ const iconHide = btn.querySelector('[data-icon="hide"]');
35
+ btn.setAttribute("aria-label", "\u663E\u793A\u5BC6\u7801");
36
+ btn.addEventListener("click", () => {
37
+ const show = field.type === "password";
38
+ field.type = show ? "text" : "password";
39
+ if (iconShow) iconShow.hidden = show;
40
+ if (iconHide) iconHide.hidden = !show;
41
+ btn.setAttribute("aria-label", show ? "\u9690\u85CF\u5BC6\u7801" : "\u663E\u793A\u5BC6\u7801");
42
+ });
43
+ }
44
+ function resizeTextarea(ta) {
45
+ ta.style.height = "auto";
46
+ const cs = getComputedStyle(ta);
47
+ const lineHeight = parseFloat(cs.lineHeight) || 20;
48
+ const padY = (parseFloat(cs.paddingTop) || 0) + (parseFloat(cs.paddingBottom) || 0);
49
+ const minRows = Number(ta.getAttribute("data-min-rows") ?? "3") || 3;
50
+ const maxAttr = ta.getAttribute("data-max-rows");
51
+ const maxRows = maxAttr ? Number(maxAttr) || 0 : 0;
52
+ const minH = minRows * lineHeight + padY;
53
+ const maxH = maxRows > 0 ? maxRows * lineHeight + padY : Infinity;
54
+ ta.style.height = `${Math.max(minH, Math.min(ta.scrollHeight, maxH))}px`;
55
+ }
56
+ function setupAutosize(ta) {
57
+ const el = ta;
58
+ if (el.__icenInputInit) return;
59
+ el.__icenInputInit = true;
60
+ ta.addEventListener("input", () => resizeTextarea(ta));
61
+ resizeTextarea(ta);
62
+ }
63
+ function setupCounter(field) {
64
+ const el = field;
65
+ if (el.__icenCounterInit) return;
66
+ const maxAttr = field.getAttribute("maxlength");
67
+ const dataCount = field.getAttribute("data-count");
68
+ if (!maxAttr && dataCount === null) return;
69
+ const max = maxAttr ? Number(maxAttr) : 0;
70
+ let counter = null;
71
+ const wrap = field.closest(".input-wrap, .textarea-wrap");
72
+ if (wrap) {
73
+ counter = wrap.querySelector(".input-counter");
74
+ if (!counter) {
75
+ counter = document.createElement("span");
76
+ counter.className = "input-counter";
77
+ wrap.appendChild(counter);
78
+ if (wrap.classList.contains("input-wrap")) {
79
+ wrap.classList.add("input-wrap--has-counter");
80
+ }
81
+ }
82
+ }
83
+ const counterEl = counter;
84
+ if (!counterEl) return;
85
+ el.__icenCounterInit = true;
86
+ const isComposing = trackComposing(field);
87
+ function update() {
88
+ const len = field.value.length;
89
+ counterEl.textContent = max > 0 ? `${len} / ${max}` : `${len}`;
90
+ counterEl.classList.remove("is-warn", "is-error");
91
+ if (max > 0) {
92
+ const ratio = len / max;
93
+ if (ratio >= 1) counterEl.classList.add("is-error");
94
+ else if (ratio >= 0.8) counterEl.classList.add("is-warn");
95
+ }
96
+ }
97
+ ;
98
+ field.addEventListener("input", () => {
99
+ if (!isComposing()) update();
100
+ });
101
+ update();
102
+ }
103
+ function setupMask(field) {
104
+ const el = field;
105
+ if (el.__icenMaskInit) return;
106
+ const mask = field.getAttribute("data-mask");
107
+ if (!mask) return;
108
+ el.__icenMaskInit = true;
109
+ const isComposing = trackComposing(field);
110
+ const maskChars = [];
111
+ for (const ch of mask) maskChars.push(ch);
112
+ function isInputSlot(ch) {
113
+ return ch === "#" || ch === "A" || ch === "*";
114
+ }
115
+ function validate(rawChar, slotType) {
116
+ if (slotType === "#") return /[0-9]/.test(rawChar);
117
+ if (slotType === "A") return /[a-zA-Z]/.test(rawChar);
118
+ return true;
119
+ }
120
+ function applyMask(input) {
121
+ let result = "";
122
+ let rawIdx = 0;
123
+ const rawChars = input.split("");
124
+ for (let i = 0; i < maskChars.length && rawIdx < rawChars.length; i++) {
125
+ const slot = maskChars[i];
126
+ if (isInputSlot(slot)) {
127
+ while (rawIdx < rawChars.length && !validate(rawChars[rawIdx], slot)) {
128
+ rawIdx++;
129
+ }
130
+ if (rawIdx < rawChars.length) {
131
+ result += rawChars[rawIdx];
132
+ rawIdx++;
133
+ }
134
+ } else {
135
+ result += slot;
136
+ }
137
+ }
138
+ return result;
139
+ }
140
+ function extractRaw(formatted) {
141
+ let raw = "";
142
+ for (let i = 0; i < formatted.length && i < maskChars.length; i++) {
143
+ if (isInputSlot(maskChars[i])) raw += formatted[i];
144
+ }
145
+ return raw;
146
+ }
147
+ let lastFormatted = applyMask(field.value);
148
+ field.value = lastFormatted;
149
+ field.setAttribute("data-raw-value", extractRaw(lastFormatted));
150
+ field.addEventListener("input", () => {
151
+ if (isComposing()) return;
152
+ const cursorPos = field.selectionStart ?? 0;
153
+ const oldRaw = extractRaw(lastFormatted);
154
+ const newFull = field.value;
155
+ const newRaw = extractRaw(newFull);
156
+ const formatted = applyMask(newRaw);
157
+ field.value = formatted;
158
+ field.setAttribute("data-raw-value", extractRaw(formatted));
159
+ lastFormatted = formatted;
160
+ const rawDelta = newRaw.length - oldRaw.length;
161
+ let targetCursor = cursorPos;
162
+ if (rawDelta > 0) {
163
+ let count = 0;
164
+ for (let i = 0; i < formatted.length; i++) {
165
+ if (isInputSlot(maskChars[i])) {
166
+ count++;
167
+ if (count >= newRaw.length) {
168
+ targetCursor = i + 1;
169
+ break;
170
+ }
171
+ }
172
+ }
173
+ } else if (rawDelta < 0) {
174
+ targetCursor = Math.min(cursorPos, formatted.length);
175
+ }
176
+ field.setSelectionRange(targetCursor, targetCursor);
177
+ });
178
+ field.addEventListener("keydown", (ev) => {
179
+ if (isComposing()) return;
180
+ if (ev.ctrlKey || ev.metaKey || ev.altKey) return;
181
+ if (["Backspace", "Delete", "Tab", "Escape", "ArrowLeft", "ArrowRight", "Home", "End", "Enter"].includes(ev.key)) return;
182
+ const currentRaw = extractRaw(field.value);
183
+ const nextSlot = maskChars.find((ch, i) => {
184
+ const filledSlots = maskChars.slice(0, i).filter(isInputSlot).length;
185
+ return isInputSlot(ch) && filledSlots >= currentRaw.length;
186
+ });
187
+ if (nextSlot && !validate(ev.key, nextSlot)) {
188
+ ev.preventDefault();
189
+ }
190
+ });
191
+ }
192
+ function setupTrim(field) {
193
+ if (!field.hasAttribute("data-trim")) return;
194
+ const el = field;
195
+ if (el.__icenTrimInit) return;
196
+ el.__icenTrimInit = true;
197
+ field.addEventListener("blur", () => {
198
+ const trimmed = field.value.trim();
199
+ if (trimmed !== field.value) {
200
+ field.value = trimmed;
201
+ field.dispatchEvent(new Event("input", { bubbles: true }));
202
+ }
203
+ });
204
+ }
205
+ function setupOtp(group) {
206
+ const el = group;
207
+ if (el.__icenInputInit) return;
208
+ el.__icenInputInit = true;
209
+ const isComposing = trackComposing(group);
210
+ const cells = Array.from(group.querySelectorAll(".otp-cell"));
211
+ const syncFilled = (cell) => {
212
+ cell.classList.toggle("is-filled", cell.value.length > 0);
213
+ };
214
+ cells.forEach((cell, i) => {
215
+ if (!cell.hasAttribute("aria-label")) {
216
+ cell.setAttribute("aria-label", `\u7B2C ${i + 1} \u4F4D\uFF0C\u5171 ${cells.length} \u4F4D`);
217
+ }
218
+ syncFilled(cell);
219
+ cell.addEventListener("input", () => {
220
+ if (isComposing()) return;
221
+ const ch = cell.value.slice(-1);
222
+ cell.value = ch;
223
+ syncFilled(cell);
224
+ if (ch && i < cells.length - 1) cells[i + 1].focus();
225
+ });
226
+ cell.addEventListener("keydown", (ev) => {
227
+ if (ev.key === "Backspace" && !cell.value && i > 0) {
228
+ ev.preventDefault();
229
+ cells[i - 1].focus();
230
+ }
231
+ if (ev.key === "ArrowLeft" && i > 0) {
232
+ ev.preventDefault();
233
+ cells[i - 1].focus();
234
+ }
235
+ if (ev.key === "ArrowRight" && i < cells.length - 1) {
236
+ ev.preventDefault();
237
+ cells[i + 1].focus();
238
+ }
239
+ });
240
+ cell.addEventListener("paste", (ev) => {
241
+ ev.preventDefault();
242
+ const text = (ev.clipboardData?.getData("text") ?? "").replace(/\s/g, "").slice(0, cells.length - i);
243
+ for (let k = 0; k < text.length; k++) {
244
+ const target = cells[i + k];
245
+ target.value = text[k] ?? "";
246
+ syncFilled(target);
247
+ }
248
+ cells[Math.min(i + text.length, cells.length - 1)].focus();
249
+ });
250
+ });
251
+ const fireComplete = () => {
252
+ const code = cells.map((c) => c.value).join("");
253
+ if (code.length === cells.length && cells.every((c) => c.value.length > 0)) {
254
+ group.classList.add("is-complete");
255
+ emitIcen(group, "icen:otp-complete", { code });
256
+ group.dispatchEvent(new CustomEvent("otp:complete", { detail: { code }, bubbles: true }));
257
+ }
258
+ };
259
+ cells.forEach((cell) => cell.addEventListener("input", fireComplete));
260
+ }
261
+ function initInput(root) {
262
+ if (typeof document === "undefined") return;
263
+ const scope = root ?? document;
264
+ const wraps = [];
265
+ if (scope instanceof Element && scope.matches(".input-wrap")) wraps.push(scope);
266
+ wraps.push(...Array.from(scope.querySelectorAll(".input-wrap")));
267
+ for (const w of wraps) {
268
+ const el = w;
269
+ if (el.__icenInputInit) continue;
270
+ el.__icenInputInit = true;
271
+ setupClear(w);
272
+ setupPasswordToggle(w);
273
+ }
274
+ const textareas = [];
275
+ if (scope instanceof HTMLTextAreaElement && scope.matches("[data-autosize]")) textareas.push(scope);
276
+ textareas.push(...Array.from(scope.querySelectorAll("textarea[data-autosize]")));
277
+ for (const ta of textareas) setupAutosize(ta);
278
+ const countedFields = [];
279
+ if (scope instanceof Element) {
280
+ const fields = Array.from(scope.querySelectorAll(
281
+ "input[maxlength], input[data-count], textarea[maxlength], textarea[data-count]"
282
+ ));
283
+ countedFields.push(...fields);
284
+ if (scope instanceof HTMLInputElement || scope instanceof HTMLTextAreaElement) {
285
+ if (scope.hasAttribute("maxlength") || scope.hasAttribute("data-count")) countedFields.push(scope);
286
+ }
287
+ }
288
+ for (const f of countedFields) setupCounter(f);
289
+ const maskedInputs = [];
290
+ if (scope instanceof Element) {
291
+ maskedInputs.push(...Array.from(scope.querySelectorAll("input[data-mask]")));
292
+ if (scope instanceof HTMLInputElement && scope.hasAttribute("data-mask")) maskedInputs.push(scope);
293
+ }
294
+ for (const inp of maskedInputs) setupMask(inp);
295
+ const trimFields = [];
296
+ if (scope instanceof Element) {
297
+ trimFields.push(...Array.from(scope.querySelectorAll("[data-trim]")));
298
+ if ((scope instanceof HTMLInputElement || scope instanceof HTMLTextAreaElement) && scope.hasAttribute("data-trim")) {
299
+ trimFields.push(scope);
300
+ }
301
+ }
302
+ for (const f of trimFields) setupTrim(f);
303
+ const otps = [];
304
+ if (scope instanceof Element && scope.matches(".otp")) otps.push(scope);
305
+ otps.push(...Array.from(scope.querySelectorAll(".otp")));
306
+ for (const g of otps) setupOtp(g);
307
+ }
308
+
309
+ // src/behaviors/controls.ts
310
+ function isDisabled(el) {
311
+ return el instanceof HTMLButtonElement && el.disabled || el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true" || el.classList.contains("is-disabled") || el.hasAttribute("data-disabled");
312
+ }
313
+ function isActivateKey(ev) {
314
+ return ev.key === "Enter" || ev.key === " ";
315
+ }
316
+ function createDelegatedInit(handlers) {
317
+ let inited = false;
318
+ return () => {
319
+ if (inited || typeof document === "undefined") return () => {
320
+ };
321
+ inited = true;
322
+ if (handlers.click) document.addEventListener("click", handlers.click);
323
+ if (handlers.keydown) document.addEventListener("keydown", handlers.keydown);
324
+ return () => {
325
+ if (handlers.click) document.removeEventListener("click", handlers.click);
326
+ if (handlers.keydown) document.removeEventListener("keydown", handlers.keydown);
327
+ inited = false;
328
+ };
329
+ };
330
+ }
331
+ var SWITCH_LIKE = '.switch[role="switch"], .checkbox[role="checkbox"], .radio-item[role="radio"], .radio[role="radio"], .radio-card[role="radio"]';
332
+ var seenCheckboxes = /* @__PURE__ */ new WeakSet();
333
+ function toggleSwitch(el) {
334
+ const checked = el.getAttribute("aria-checked") !== "true";
335
+ el.setAttribute("aria-checked", String(checked));
336
+ emitIcen(el, "icen:switch-change", { el, checked });
337
+ }
338
+ function toggleCheckbox(el) {
339
+ const cur = el.getAttribute("aria-checked") ?? "false";
340
+ const firstSeen = !seenCheckboxes.has(el);
341
+ seenCheckboxes.add(el);
342
+ let next;
343
+ if (cur === "mixed") next = firstSeen ? "false" : "true";
344
+ else next = cur === "true" ? "false" : "mixed";
345
+ el.setAttribute("aria-checked", next);
346
+ const checked = next === "mixed" ? "mixed" : next === "true";
347
+ emitIcen(el, "icen:check-change", { el, checked });
348
+ }
349
+ function selectRadio(el) {
350
+ if (el.getAttribute("aria-checked") === "true") return;
351
+ const group = el.closest('[data-radio-group], .radio-group, [role="radiogroup"], .radio-card-grid');
352
+ const peers = group ? Array.from(group.querySelectorAll('[role="radio"]')).filter(
353
+ (it) => it.closest('[data-radio-group], .radio-group, [role="radiogroup"], .radio-card-grid') === group
354
+ ) : [el];
355
+ for (const it of peers) {
356
+ const on = it === el;
357
+ it.classList.toggle("is-selected", on);
358
+ it.setAttribute("aria-checked", String(on));
359
+ }
360
+ emitIcen(el, "icen:check-change", { el, checked: true });
361
+ }
362
+ function activateSwitchLike(el) {
363
+ if (el.matches('.switch[role="switch"]')) toggleSwitch(el);
364
+ else if (el.matches('.checkbox[role="checkbox"]')) toggleCheckbox(el);
365
+ else selectRadio(el);
366
+ }
367
+ function resolveSwitchTarget(t) {
368
+ if (!(t instanceof Element)) return null;
369
+ const direct = t.closest(SWITCH_LIKE);
370
+ if (direct) return isDisabled(direct) ? null : direct;
371
+ const row = t.closest(".checkbox-row");
372
+ if (row && !t.closest("button, a, input, select, textarea, [contenteditable]")) {
373
+ const box = row.querySelector('.checkbox[role="checkbox"]');
374
+ if (box && !isDisabled(box)) return box;
375
+ }
376
+ return null;
377
+ }
378
+ function onSwitchClick(ev) {
379
+ const el = resolveSwitchTarget(ev.target);
380
+ if (el) activateSwitchLike(el);
381
+ }
382
+ function onSwitchKeydown(ev) {
383
+ if (!isActivateKey(ev)) return;
384
+ const t = ev.target;
385
+ if (!(t instanceof Element)) return;
386
+ const el = t.closest(SWITCH_LIKE);
387
+ if (!el || isDisabled(el)) return;
388
+ ev.preventDefault();
389
+ activateSwitchLike(el);
390
+ }
391
+ var initSwitch = createDelegatedInit({ click: onSwitchClick, keydown: onSwitchKeydown });
392
+ function stepStepper(btn) {
393
+ const root = btn.closest(".stepper");
394
+ if (!root) return;
395
+ const input = root.querySelector('input.stepper-input, input[type="number"]');
396
+ if (!input || input.disabled) return;
397
+ const step = Number(btn.getAttribute("data-step")) || 1;
398
+ const minAttr = input.min;
399
+ const maxAttr = input.max;
400
+ const min = minAttr !== "" && Number.isFinite(Number(minAttr)) ? Number(minAttr) : -Infinity;
401
+ const max = maxAttr !== "" && Number.isFinite(Number(maxAttr)) ? Number(maxAttr) : Infinity;
402
+ const base = Number.isFinite(Number(input.value)) ? Number(input.value) : 0;
403
+ const next = Number(Math.min(max, Math.max(min, base + step)).toPrecision(12));
404
+ const delta = next - base;
405
+ input.value = String(next);
406
+ emitIcen(root, "icen:stepper-change", { el: root, value: next, delta });
407
+ }
408
+ function onStepperClick(ev) {
409
+ const t = ev.target;
410
+ if (!(t instanceof Element)) return;
411
+ const btn = t.closest(".stepper-btn");
412
+ if (!btn || isDisabled(btn)) return;
413
+ stepStepper(btn);
414
+ }
415
+ var initStepper = createDelegatedInit({ click: onStepperClick });
416
+ function selectSegmentedItem(item) {
417
+ const container = item.closest(".segmented");
418
+ if (!container) return;
419
+ const items = Array.from(container.querySelectorAll(".segmented-item")).filter(
420
+ (it) => it.closest(".segmented") === container
421
+ );
422
+ const index = items.indexOf(item);
423
+ if (index === -1) return;
424
+ if (item.classList.contains("is-active") || item.getAttribute("aria-checked") === "true") return;
425
+ for (const it of items) {
426
+ const on = it === item;
427
+ it.classList.toggle("is-active", on);
428
+ if (it.getAttribute("role") === "radio") it.setAttribute("aria-checked", String(on));
429
+ }
430
+ emitIcen(container, "icen:segmented-change", {
431
+ el: container,
432
+ value: item.getAttribute("data-value") ?? String(index),
433
+ index
434
+ });
435
+ }
436
+ function onSegmentedClick(ev) {
437
+ const t = ev.target;
438
+ if (!(t instanceof Element)) return;
439
+ const item = t.closest(".segmented-item");
440
+ if (!item || isDisabled(item)) return;
441
+ selectSegmentedItem(item);
442
+ }
443
+ var initSegmented = createDelegatedInit({ click: onSegmentedClick });
444
+ function activateStep(step) {
445
+ const container = step.closest(".steps");
446
+ if (!container) return;
447
+ const list = Array.from(container.querySelectorAll(".step")).filter(
448
+ (s) => s.closest(".steps") === container
449
+ );
450
+ const index = list.indexOf(step);
451
+ if (index === -1) return;
452
+ list.forEach((s, i) => {
453
+ s.classList.toggle("is-active", i === index);
454
+ if (i < index) {
455
+ s.classList.add("step--finish");
456
+ s.classList.remove("step--process", "step--wait");
457
+ } else if (i === index) {
458
+ s.classList.add("step--process");
459
+ s.classList.remove("step--finish", "step--wait", "step--error");
460
+ } else {
461
+ s.classList.add("step--wait");
462
+ s.classList.remove("step--finish", "step--process");
463
+ }
464
+ });
465
+ emitIcen(container, "icen:step-change", { step, index });
466
+ }
467
+ function onStepsClick(ev) {
468
+ const t = ev.target;
469
+ if (!(t instanceof Element)) return;
470
+ const step = t.closest('.step[role="button"]');
471
+ if (!step || isDisabled(step)) return;
472
+ activateStep(step);
473
+ }
474
+ function onStepsKeydown(ev) {
475
+ if (!isActivateKey(ev)) return;
476
+ const t = ev.target;
477
+ if (!(t instanceof Element)) return;
478
+ const step = t.closest('.step[role="button"]');
479
+ if (!step) return;
480
+ ev.preventDefault();
481
+ activateStep(step);
482
+ }
483
+ var initSteps = createDelegatedInit({ click: onStepsClick, keydown: onStepsKeydown });
484
+ function rateStar(star) {
485
+ const container = star.closest(".rating");
486
+ if (!container || container.classList.contains("rating--readonly")) return;
487
+ const stars = Array.from(container.querySelectorAll(".rating-star")).filter(
488
+ (s) => s.closest(".rating") === container
489
+ );
490
+ const index = stars.indexOf(star);
491
+ if (index === -1) return;
492
+ const value = index + 1;
493
+ stars.forEach((s, i) => {
494
+ s.classList.remove("is-half");
495
+ s.classList.toggle("is-filled", i <= index);
496
+ });
497
+ const valueEl = container.querySelector(".rating-value");
498
+ if (valueEl) valueEl.textContent = String(value);
499
+ emitIcen(container, "icen:rating-change", { el: container, value });
500
+ }
501
+ function onRatingClick(ev) {
502
+ const t = ev.target;
503
+ if (!(t instanceof Element)) return;
504
+ const star = t.closest(".rating-star");
505
+ if (!star || isDisabled(star)) return;
506
+ rateStar(star);
507
+ }
508
+ var initRating = createDelegatedInit({ click: onRatingClick });
509
+ function pageOf(btn, btns) {
510
+ const dp = btn.getAttribute("data-page");
511
+ if (dp != null && dp.trim() !== "" && Number.isFinite(Number(dp))) return Number(dp);
512
+ const text = Number((btn.textContent ?? "").trim());
513
+ if (Number.isFinite(text) && text > 0) return text;
514
+ return btns.indexOf(btn) + 1;
515
+ }
516
+ function dirOf(btn) {
517
+ const dir = (btn.getAttribute("data-dir") ?? "").toLowerCase();
518
+ if (dir === "prev" || dir === "-1" || dir === "previous") return -1;
519
+ if (dir === "next" || dir === "+1" || dir === "1") return 1;
520
+ const label = btn.getAttribute("aria-label");
521
+ if (label && /^上一页$|^上一张$|^向前$|^(prev|previous)$/i.test(label)) return -1;
522
+ if (label && /^下一页$|^下一张$|^向后$|^next$/i.test(label)) return 1;
523
+ return 0;
524
+ }
525
+ function setActivePage(btn, btns) {
526
+ for (const b of btns) {
527
+ const on = b === btn;
528
+ b.classList.toggle("is-active", on);
529
+ if (on) b.setAttribute("aria-current", "page");
530
+ else b.removeAttribute("aria-current");
531
+ }
532
+ }
533
+ function goPage(btn) {
534
+ const nav = btn.closest(".pagination");
535
+ if (!nav) return;
536
+ const btns = Array.from(nav.querySelectorAll(".page-btn")).filter(
537
+ (b) => b.closest(".pagination") === nav
538
+ );
539
+ const dir = dirOf(btn);
540
+ if (dir !== 0) {
541
+ const curBtn = btns.find(
542
+ (b) => b.classList.contains("is-active") || b.getAttribute("aria-current") === "page"
543
+ );
544
+ const cur = curBtn ? pageOf(curBtn, btns) : 1;
545
+ const target = Math.max(1, cur + dir);
546
+ const targetBtn = btns.find((b) => dirOf(b) === 0 && pageOf(b, btns) === target);
547
+ if (targetBtn) setActivePage(targetBtn, btns);
548
+ emitIcen(nav, "icen:page-change", { page: target });
549
+ return;
550
+ }
551
+ const page = pageOf(btn, btns);
552
+ setActivePage(btn, btns);
553
+ emitIcen(nav, "icen:page-change", { page });
554
+ }
555
+ function onPaginationClick(ev) {
556
+ const t = ev.target;
557
+ if (!(t instanceof Element)) return;
558
+ const btn = t.closest(".page-btn");
559
+ if (!btn || isDisabled(btn)) return;
560
+ goPage(btn);
561
+ }
562
+ var initPagination = createDelegatedInit({ click: onPaginationClick });
563
+ function sortKeyOf(th) {
564
+ const cur = th.getAttribute("data-sort") ?? "";
565
+ return th.getAttribute("data-sort-key") ?? (cur === "asc" || cur === "desc" ? "" : cur);
566
+ }
567
+ function resetSortHeader(th) {
568
+ th.setAttribute("data-sort", sortKeyOf(th));
569
+ }
570
+ function cycleSort(th) {
571
+ const cur = th.getAttribute("data-sort") ?? "";
572
+ const key = sortKeyOf(th);
573
+ if (!th.hasAttribute("data-sort-key") && key !== "") th.setAttribute("data-sort-key", key);
574
+ const dir = cur === "asc" ? "desc" : cur === "desc" ? null : "asc";
575
+ const table = th.closest("table");
576
+ if (table) {
577
+ for (const h of table.querySelectorAll("th[data-sort]")) {
578
+ if (h !== th) resetSortHeader(h);
579
+ }
580
+ }
581
+ th.setAttribute("data-sort", dir ?? key);
582
+ emitIcen(table ?? th, "icen:sort-change", { th, key, dir });
583
+ }
584
+ function onSortClick(ev) {
585
+ const t = ev.target;
586
+ if (!(t instanceof Element)) return;
587
+ const th = t.closest("th[data-sort]");
588
+ if (!th) return;
589
+ cycleSort(th);
590
+ }
591
+ var initTableSort = createDelegatedInit({ click: onSortClick });
592
+ export {
593
+ initInput,
594
+ initPagination,
595
+ initRating,
596
+ initSegmented,
597
+ initStepper,
598
+ initSteps,
599
+ initSwitch,
600
+ initTableSort
601
+ };
@@ -1,4 +1,7 @@
1
- /** 委托监听 click;同一 root(含默认的 document)重复调用幂等。 */
2
- declare function initCopy(root?: ParentNode): void;
1
+ /**
2
+ * 委托监听 click;同一 root(含默认的 document)重复调用幂等。
3
+ * 返回销毁函数:移除委托监听并复位幂等标记(销毁后可重新 init)。SSR 下返回 no-op。
4
+ */
5
+ declare function initCopy(root?: ParentNode): () => void;
3
6
 
4
7
  export { initCopy };
@@ -1,5 +1,12 @@
1
+ // src/behaviors/events.ts
2
+ function emitIcen(source, type, detail) {
3
+ return source.dispatchEvent(
4
+ new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
5
+ );
6
+ }
7
+
1
8
  // src/behaviors/copy.ts
2
- var initialized = /* @__PURE__ */ new WeakSet();
9
+ var handlers = /* @__PURE__ */ new WeakMap();
3
10
  var flashing = /* @__PURE__ */ new WeakMap();
4
11
  function legacyCopy(text) {
5
12
  const ta = document.createElement("textarea");
@@ -19,14 +26,20 @@ function legacyCopy(text) {
19
26
  return ok;
20
27
  }
21
28
  async function copyText(text) {
22
- try {
23
- if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
29
+ const hasApi = typeof navigator !== "undefined" && !!navigator.clipboard?.writeText;
30
+ if (hasApi) {
31
+ try {
24
32
  await navigator.clipboard.writeText(text);
25
- return true;
33
+ return { ok: true };
34
+ } catch (err) {
35
+ const fallbackOk = legacyCopy(text);
36
+ if (fallbackOk) return { ok: true };
37
+ const reason = err instanceof Error ? err.message : String(err);
38
+ return { ok: false, reason: `clipboard API \u88AB\u62D2\uFF08${reason}\uFF09\u4E14\u56DE\u9000 execCommand \u5931\u8D25` };
26
39
  }
27
- } catch {
28
40
  }
29
- return legacyCopy(text);
41
+ if (legacyCopy(text)) return { ok: true };
42
+ return { ok: false, reason: "\u526A\u8D34\u677F\u4E0D\u53EF\u7528\uFF08\u65E0 clipboard API \u4E14 execCommand \u56DE\u9000\u5931\u8D25\uFF09" };
30
43
  }
31
44
  function flashDone(btn) {
32
45
  const prev = flashing.get(btn);
@@ -48,16 +61,28 @@ function onClick(root, ev) {
48
61
  const btn = target.closest(".copy-btn[data-copy]");
49
62
  if (!btn || !root.contains(btn)) return;
50
63
  const text = btn.getAttribute("data-copy") ?? "";
51
- void copyText(text).then((ok) => {
52
- if (ok) flashDone(btn);
64
+ void copyText(text).then((outcome) => {
65
+ if (outcome.ok) {
66
+ flashDone(btn);
67
+ emitIcen(btn, "icen:copy-success", { text });
68
+ } else {
69
+ emitIcen(btn, "icen:copy-error", { text, reason: outcome.reason ?? "\u672A\u77E5\u9519\u8BEF" });
70
+ }
53
71
  });
54
72
  }
55
73
  function initCopy(root) {
56
- if (typeof document === "undefined") return;
74
+ if (typeof document === "undefined") return () => {
75
+ };
57
76
  const scope = root ?? document;
58
- if (initialized.has(scope)) return;
59
- initialized.add(scope);
60
- scope.addEventListener("click", (ev) => onClick(scope, ev));
77
+ if (handlers.has(scope)) return () => {
78
+ };
79
+ const handler = (ev) => onClick(scope, ev);
80
+ handlers.set(scope, handler);
81
+ scope.addEventListener("click", handler);
82
+ return () => {
83
+ scope.removeEventListener("click", handler);
84
+ handlers.delete(scope);
85
+ };
61
86
  }
62
87
  export {
63
88
  initCopy