@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
@@ -1,4 +1,31 @@
1
1
  // src/behaviors/popover.ts
2
+ var PANEL_SIZING_ATTRS = {
3
+ width: "data-panel-width",
4
+ minWidth: "data-panel-min",
5
+ maxWidth: "data-panel-max",
6
+ minHeight: "data-panel-min-height",
7
+ maxHeight: "data-panel-max-height"
8
+ };
9
+ function readPanelSizing(el) {
10
+ const out = {};
11
+ if (!el) return out;
12
+ for (const key of Object.keys(PANEL_SIZING_ATTRS)) {
13
+ const raw = el.getAttribute(PANEL_SIZING_ATTRS[key]);
14
+ if (raw == null) continue;
15
+ const n = Number(raw);
16
+ if (Number.isFinite(n) && n > 0) out[key] = n;
17
+ }
18
+ return out;
19
+ }
20
+ function resolvePanelSizing(el, overrides) {
21
+ const base = readPanelSizing(el);
22
+ if (!overrides) return base;
23
+ for (const key of Object.keys(overrides)) {
24
+ const v = overrides[key];
25
+ if (typeof v === "number" && Number.isFinite(v) && v > 0) base[key] = v;
26
+ }
27
+ return base;
28
+ }
2
29
  var DEFAULT_MIN_WIDTH = 220;
3
30
  var DEFAULT_MAX_WIDTH = 420;
4
31
  var DEFAULT_MIN_HEIGHT = 120;
@@ -23,13 +50,35 @@ function horizontalAnchor(anchor, width, align) {
23
50
  function computePopoverLayout(anchor, viewport, opts = {}) {
24
51
  const margin = opts.margin ?? 12;
25
52
  const offset = opts.offset ?? 8;
53
+ const preferredSide = opts.side ?? "top";
54
+ const align = opts.align ?? "start";
55
+ if (preferredSide === "left" || preferredSide === "right") {
56
+ const availableLeft = Math.max(0, anchor.left - margin - offset);
57
+ const availableRight = Math.max(0, viewport.width - anchor.right - margin - offset);
58
+ const side2 = (preferredSide === "left" ? availableLeft : availableRight) >= DEFAULT_MIN_WIDTH ? preferredSide : preferredSide === "left" ? "right" : "left";
59
+ const minWidth2 = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
60
+ const maxWidth2 = Math.min(
61
+ Math.max(opts.maxWidth ?? DEFAULT_MAX_WIDTH, minWidth2),
62
+ side2 === "left" ? availableLeft : availableRight
63
+ );
64
+ const width2 = minWidth2;
65
+ const left2 = clamp(
66
+ side2 === "left" ? anchor.left - offset - width2 : anchor.right + offset,
67
+ margin,
68
+ viewport.width - margin - width2
69
+ );
70
+ const availableHeight2 = Math.max(80, viewport.height - margin * 2);
71
+ const maxHeight2 = Math.min(opts.maxHeight ?? DEFAULT_MAX_HEIGHT, availableHeight2);
72
+ const desired = clamp(opts.contentHeightHint ?? Math.min(anchor.height, maxHeight2), 40, maxHeight2);
73
+ const anchorTop = align === "center" ? anchor.top + anchor.height / 2 - desired / 2 : align === "end" ? anchor.bottom - desired : anchor.top;
74
+ const top2 = clamp(anchorTop, margin, viewport.height - margin - desired);
75
+ return { side: side2, left: left2, top: top2, width: width2, maxHeight: maxHeight2 };
76
+ }
26
77
  const availableWidth = Math.max(80, viewport.width - margin * 2);
27
78
  const requestedMinWidth = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
28
79
  const requestedMaxWidth = Math.max(requestedMinWidth, opts.maxWidth ?? DEFAULT_MAX_WIDTH);
29
80
  const maxWidth = Math.min(requestedMaxWidth, availableWidth);
30
81
  const minWidth = Math.min(requestedMinWidth, maxWidth);
31
- const preferredSide = opts.side ?? "top";
32
- const align = opts.align ?? "start";
33
82
  const availableTop = Math.max(0, anchor.top - margin - offset);
34
83
  const availableBottom = Math.max(0, viewport.height - anchor.bottom - margin - offset);
35
84
  const availableHeight = Math.max(80, viewport.height - margin * 2);
@@ -53,6 +102,13 @@ function computePopoverLayout(anchor, viewport, opts = {}) {
53
102
  return { side, left, top, width, maxHeight };
54
103
  }
55
104
 
105
+ // src/behaviors/events.ts
106
+ function emitIcen(source, type, detail) {
107
+ return source.dispatchEvent(
108
+ new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
109
+ );
110
+ }
111
+
56
112
  // src/behaviors/select.ts
57
113
  var openSelect = null;
58
114
  var globalBound = false;
@@ -98,18 +154,33 @@ function unbindOpenListeners() {
98
154
  }
99
155
  function setup(container) {
100
156
  const el = container;
101
- if (el.__icenSelectInit) return;
102
- el.__icenSelectInit = true;
157
+ if (el.__icenSelectInit) return () => {
158
+ };
159
+ const ac = new AbortController();
160
+ const on = (t, ev, fn) => {
161
+ const listener = (evt) => fn(evt);
162
+ t.addEventListener(ev, listener, { signal: ac.signal });
163
+ };
103
164
  const triggerEl = container.querySelector(".select-trigger");
104
165
  const panelEl = container.querySelector(".select-panel");
105
166
  const valueEl = container.querySelector(".select-value");
106
167
  const hiddenInput = container.querySelector("input[data-select-value]");
107
- if (!triggerEl || !panelEl) return;
168
+ if (!triggerEl || !panelEl) return () => {
169
+ };
170
+ el.__icenSelectInit = true;
108
171
  const trigger = triggerEl;
109
172
  const panel = panelEl;
110
173
  const isMultiple = container.hasAttribute("data-select-multiple");
111
174
  const isSearchable = container.hasAttribute("data-select-search");
112
175
  const maxSelect = Number(container.getAttribute("data-select-max")) || 0;
176
+ const panelSizing = resolvePanelSizing(container);
177
+ function resolvedPanelWidth(triggerWidth) {
178
+ let w = triggerWidth;
179
+ if (panelSizing.minWidth != null) w = Math.max(w, panelSizing.minWidth);
180
+ if (panelSizing.maxWidth != null) w = Math.min(w, panelSizing.maxWidth);
181
+ if (panelSizing.width != null) w = panelSizing.width;
182
+ return w;
183
+ }
113
184
  const placeholder = container.getAttribute("data-select-placeholder") ?? (valueEl?.classList.contains("is-empty") ? valueEl?.textContent ?? "" : "");
114
185
  if (isMultiple) panel.setAttribute("data-select-multiple", "");
115
186
  let searchInput = null;
@@ -177,8 +248,8 @@ function setup(container) {
177
248
  setFocus(firstEnabled());
178
249
  }
179
250
  if (searchInput) {
180
- searchInput.addEventListener("input", () => filterOptions(searchInput.value));
181
- searchInput.addEventListener("keydown", (ev) => {
251
+ on(searchInput, "input", () => filterOptions(searchInput.value));
252
+ on(searchInput, "keydown", (ev) => {
182
253
  if (ev.key === "Escape") {
183
254
  ev.stopPropagation();
184
255
  searchInput.value = "";
@@ -189,6 +260,7 @@ function setup(container) {
189
260
  }
190
261
  function positionPanel() {
191
262
  const rect = trigger.getBoundingClientRect();
263
+ const panelWidth = resolvedPanelWidth(rect.width);
192
264
  const layout = computePopoverLayout(
193
265
  rect,
194
266
  { width: window.innerWidth, height: window.innerHeight },
@@ -196,9 +268,9 @@ function setup(container) {
196
268
  side: "bottom",
197
269
  align: "stretch",
198
270
  offset: PANEL_OFFSET,
199
- minWidth: rect.width,
200
- maxWidth: rect.width,
201
- maxHeight: PANEL_MAX_HEIGHT
271
+ minWidth: panelWidth,
272
+ maxWidth: panelWidth,
273
+ maxHeight: panelSizing.maxHeight ?? PANEL_MAX_HEIGHT
202
274
  }
203
275
  );
204
276
  panel.style.left = `${layout.left + window.scrollX}px`;
@@ -242,19 +314,59 @@ function setup(container) {
242
314
  function updateMultiTrigger() {
243
315
  if (!valueEl) return;
244
316
  const selected = allOptions().filter((o) => o.classList.contains("is-selected"));
317
+ let chipsEl = trigger.querySelector(".select-chips");
245
318
  if (selected.length === 0) {
246
319
  valueEl.textContent = placeholder;
247
320
  valueEl.classList.add("is-empty");
321
+ valueEl.hidden = false;
322
+ if (chipsEl) chipsEl.hidden = true;
248
323
  container.removeAttribute("data-value");
249
324
  } else {
250
325
  valueEl.classList.remove("is-empty");
251
- const maxLabel = Number(container.getAttribute("data-select-max-label")) || 2;
252
- if (selected.length <= maxLabel) {
253
- valueEl.textContent = selected.map((o) => o.textContent ?? "").join("\u3001");
254
- } else {
255
- valueEl.textContent = `\u5DF2\u9009 ${selected.length} \u9879`;
326
+ valueEl.hidden = true;
327
+ if (!chipsEl) {
328
+ chipsEl = document.createElement("span");
329
+ chipsEl.className = "select-chips";
330
+ valueEl.insertAdjacentElement("afterend", chipsEl);
331
+ }
332
+ chipsEl.hidden = false;
333
+ chipsEl.textContent = "";
334
+ const maxLabel = Number(container.getAttribute("data-select-max-label")) || 3;
335
+ const shown = selected.slice(0, maxLabel);
336
+ const rest = selected.length - shown.length;
337
+ for (const o of shown) {
338
+ const label = o.textContent ?? "";
339
+ const chip = document.createElement("span");
340
+ chip.className = "select-chip";
341
+ const lb = document.createElement("span");
342
+ lb.textContent = label;
343
+ chip.appendChild(lb);
344
+ const x = document.createElement("span");
345
+ x.className = "select-chip-x";
346
+ x.setAttribute("role", "button");
347
+ x.setAttribute("tabindex", "-1");
348
+ x.setAttribute("aria-label", `\u79FB\u9664 ${label}`);
349
+ x.textContent = "\xD7";
350
+ on(x, "pointerdown", (ev) => ev.stopPropagation());
351
+ on(x, "click", (ev) => {
352
+ ev.stopPropagation();
353
+ ev.preventDefault();
354
+ o.classList.remove("is-selected");
355
+ updateMultiTrigger();
356
+ syncHiddenInput();
357
+ });
358
+ chip.appendChild(x);
359
+ chipsEl.appendChild(chip);
256
360
  }
361
+ if (rest > 0) {
362
+ const more = document.createElement("span");
363
+ more.className = "select-chip select-chip--more";
364
+ more.textContent = `+${rest}`;
365
+ chipsEl.appendChild(more);
366
+ }
367
+ container.setAttribute("data-value", getSelectedValues().join(","));
257
368
  }
369
+ if (clearBtn) clearBtn.hidden = selected.length === 0;
258
370
  if (maxSelect > 0) {
259
371
  const atMax = selected.length >= maxSelect;
260
372
  allOptions().forEach((o) => {
@@ -265,8 +377,10 @@ function setup(container) {
265
377
  }
266
378
  }
267
379
  function syncHiddenInput() {
268
- if (!hiddenInput) return;
269
380
  const values = getSelectedValues();
381
+ const label = allOptions().filter((o) => o.classList.contains("is-selected")).map((o) => (o.textContent ?? "").trim()).join(", ");
382
+ emitIcen(container, "icen:select-change", { value: values[0] ?? "", values, label });
383
+ if (!hiddenInput) return;
270
384
  hiddenInput.value = isMultiple ? values.join(",") : values[0] ?? "";
271
385
  hiddenInput.dispatchEvent(new Event("change", { bubbles: true }));
272
386
  }
@@ -297,16 +411,16 @@ function setup(container) {
297
411
  }
298
412
  const clearBtn = container.querySelector(".select-clear");
299
413
  if (clearBtn && isMultiple) {
300
- clearBtn.addEventListener("click", (ev) => {
414
+ on(clearBtn, "click", (ev) => {
301
415
  ev.stopPropagation();
302
416
  allOptions().forEach((o) => o.classList.remove("is-selected"));
303
417
  updateMultiTrigger();
304
418
  syncHiddenInput();
305
419
  });
306
- clearBtn.addEventListener("pointerdown", (ev) => ev.stopPropagation());
420
+ on(clearBtn, "pointerdown", (ev) => ev.stopPropagation());
307
421
  }
308
- trigger.addEventListener("click", () => isOpen() ? closePanel() : openPanel());
309
- trigger.addEventListener("keydown", (ev) => {
422
+ on(trigger, "click", () => isOpen() ? closePanel() : openPanel());
423
+ on(trigger, "keydown", (ev) => {
310
424
  if (!isOpen()) {
311
425
  if (ev.key === "ArrowDown" || ev.key === "ArrowUp" || ev.key === "Enter" || ev.key === " ") {
312
426
  ev.preventDefault();
@@ -336,13 +450,13 @@ function setup(container) {
336
450
  break;
337
451
  }
338
452
  });
339
- panel.addEventListener("click", (ev) => {
453
+ on(panel, "click", (ev) => {
340
454
  const t = ev.target;
341
455
  if (!(t instanceof Element)) return;
342
456
  const opt = t.closest(".select-option");
343
457
  if (opt instanceof HTMLButtonElement && panel.contains(opt)) choose(opt);
344
458
  });
345
- panel.addEventListener("mouseover", (ev) => {
459
+ on(panel, "mouseover", (ev) => {
346
460
  const t = ev.target;
347
461
  if (!(t instanceof Element)) return;
348
462
  const opt = t.closest(".select-option");
@@ -351,15 +465,38 @@ function setup(container) {
351
465
  if (idx >= 0 && !opt.classList.contains("is-disabled")) setFocus(idx);
352
466
  });
353
467
  if (isMultiple) updateMultiTrigger();
468
+ return () => {
469
+ if (openSelect?.panel === panel) closePanel();
470
+ ac.abort();
471
+ el.__icenSelectInit = false;
472
+ };
354
473
  }
474
+ var activeSetups = /* @__PURE__ */ new Set();
355
475
  function initSelect(root) {
356
- if (typeof document === "undefined") return;
476
+ if (typeof document === "undefined") return () => {
477
+ };
357
478
  const scope = root ?? document;
358
479
  bindGlobalListeners();
359
480
  const containers = [];
360
481
  if (scope instanceof Element && scope.matches("[data-select]")) containers.push(scope);
361
482
  containers.push(...Array.from(scope.querySelectorAll("[data-select]")));
362
- for (const c of containers) setup(c);
483
+ const disposers = [];
484
+ for (const c of containers) {
485
+ const dispose = setup(c);
486
+ activeSetups.add(dispose);
487
+ disposers.push(dispose);
488
+ }
489
+ return () => {
490
+ for (const d of disposers) {
491
+ d();
492
+ activeSetups.delete(d);
493
+ }
494
+ if (activeSetups.size === 0 && globalBound) {
495
+ document.removeEventListener("pointerdown", onGlobalPointerDown);
496
+ document.removeEventListener("keydown", onGlobalKeyDown);
497
+ globalBound = false;
498
+ }
499
+ };
363
500
  }
364
501
  export {
365
502
  initSelect
@@ -1,4 +1,7 @@
1
- /** 为 root 下每个 .sidebar 初始化(root 自身是 .sidebar 也算)。 */
2
- declare function initSidebar(root?: ParentNode): void;
1
+ /**
2
+ * 为 root 下每个 .sidebar 初始化(root 自身是 .sidebar 也算)。
3
+ * 返回销毁函数:移除本次挂上的容器监听并复位幂等标记(销毁后可重新 init)。SSR 下返回 no-op。
4
+ */
5
+ declare function initSidebar(root?: ParentNode): () => void;
3
6
 
4
7
  export { initSidebar };
@@ -1,7 +1,14 @@
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/sidebar.ts
2
9
  function setup(sidebar) {
3
10
  const el = sidebar;
4
- if (el.__icenSidebarInit) return;
11
+ if (el.__icenSidebarInit) return void 0;
5
12
  el.__icenSidebarInit = true;
6
13
  sidebar.querySelectorAll(".sidebar-group-title").forEach((title) => {
7
14
  const group = title.closest(".sidebar-group");
@@ -11,9 +18,21 @@ function setup(sidebar) {
11
18
  title.setAttribute("aria-controls", panel.id);
12
19
  }
13
20
  });
14
- el.addEventListener("click", (ev) => {
21
+ sidebar.querySelectorAll("[data-sidebar-collapse]").forEach((btn) => {
22
+ if (!btn.hasAttribute("aria-expanded")) {
23
+ btn.setAttribute("aria-expanded", String(!sidebar.classList.contains("sidebar--collapsed")));
24
+ }
25
+ });
26
+ const onClick = (ev) => {
15
27
  const target = ev.target;
16
28
  if (!(target instanceof Element)) return;
29
+ const collapseBtn = target.closest("[data-sidebar-collapse]");
30
+ if (collapseBtn && collapseBtn.closest(".sidebar") === el) {
31
+ const collapsed = el.classList.toggle("sidebar--collapsed");
32
+ collapseBtn.setAttribute("aria-expanded", String(!collapsed));
33
+ emitIcen(el, "icen:sidebar-collapse", { collapsed });
34
+ return;
35
+ }
17
36
  const title = target.closest(".sidebar-group-title");
18
37
  if (!(title instanceof HTMLElement)) return;
19
38
  if (title.closest(".sidebar") !== el) return;
@@ -21,19 +40,30 @@ function setup(sidebar) {
21
40
  if (!group) return;
22
41
  const open = group.classList.toggle("is-open");
23
42
  title.setAttribute("aria-expanded", String(open));
24
- sidebar.dispatchEvent(new CustomEvent("icen:sidebar-toggle", {
25
- bubbles: true,
26
- detail: { group, open }
27
- }));
28
- });
43
+ emitIcen(sidebar, "icen:sidebar-toggle", { group, open });
44
+ };
45
+ el.addEventListener("click", onClick);
46
+ return () => {
47
+ el.removeEventListener("click", onClick);
48
+ el.__icenSidebarInit = false;
49
+ };
29
50
  }
30
51
  function initSidebar(root) {
31
- if (typeof document === "undefined") return;
32
- const scope = root ?? document;
33
- const sidebars = [];
34
- if (scope instanceof Element && scope.matches(".sidebar")) sidebars.push(scope);
35
- sidebars.push(...Array.from(scope.querySelectorAll(".sidebar")));
36
- for (const s of sidebars) setup(s);
52
+ const cleanups = [];
53
+ if (typeof document !== "undefined") {
54
+ const scope = root ?? document;
55
+ const sidebars = [];
56
+ if (scope instanceof Element && scope.matches(".sidebar")) sidebars.push(scope);
57
+ sidebars.push(...Array.from(scope.querySelectorAll(".sidebar")));
58
+ for (const s of sidebars) {
59
+ const cleanup = setup(s);
60
+ if (cleanup) cleanups.push(cleanup);
61
+ }
62
+ }
63
+ return () => {
64
+ for (const fn of cleanups) fn();
65
+ cleanups.length = 0;
66
+ };
37
67
  }
38
68
  export {
39
69
  initSidebar
@@ -1,4 +1,7 @@
1
- /** 为 root 下每个 .slider 容器初始化(root 自身是 .slider 也算)。 */
2
- declare function initSlider(root?: ParentNode): void;
1
+ /**
2
+ * 为 root 下每个 .slider 容器初始化(root 自身是 .slider 也算)。
3
+ * 返回销毁函数:移除本次挂上的全部监听并复位幂等标记(销毁后可重新 init)。SSR 下返回 no-op。
4
+ */
5
+ declare function initSlider(root?: ParentNode): () => void;
3
6
 
4
7
  export { initSlider };
@@ -1,4 +1,15 @@
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/slider.ts
9
+ function bind(target, type, fn, bag) {
10
+ target.addEventListener(type, fn);
11
+ bag.push(() => target.removeEventListener(type, fn));
12
+ }
2
13
  function pct(input) {
3
14
  const min = Number(input.min || "0");
4
15
  const max = Number(input.max || "100");
@@ -6,7 +17,7 @@ function pct(input) {
6
17
  if (range <= 0) return 0;
7
18
  return (Number(input.value) - min) / range * 100;
8
19
  }
9
- function setupSingle(slider, native) {
20
+ function setupSingle(slider, native, bag) {
10
21
  const fill = slider.querySelector(".slider-fill");
11
22
  const thumb = slider.querySelector(".slider-thumb");
12
23
  const sync = () => {
@@ -14,22 +25,16 @@ function setupSingle(slider, native) {
14
25
  if (fill) fill.style.width = `${p}%`;
15
26
  if (thumb) thumb.style.left = `calc(${p}% - 8px)`;
16
27
  };
17
- native.addEventListener("input", () => {
28
+ bind(native, "input", () => {
18
29
  sync();
19
- slider.dispatchEvent(new CustomEvent("icen:slider-input", {
20
- bubbles: true,
21
- detail: { value: Number(native.value) }
22
- }));
23
- });
24
- native.addEventListener("change", () => {
25
- slider.dispatchEvent(new CustomEvent("icen:slider-change", {
26
- bubbles: true,
27
- detail: { value: Number(native.value) }
28
- }));
29
- });
30
+ emitIcen(slider, "icen:slider-input", { value: Number(native.value) });
31
+ }, bag);
32
+ bind(native, "change", () => {
33
+ emitIcen(slider, "icen:slider-change", { value: Number(native.value) });
34
+ }, bag);
30
35
  sync();
31
36
  }
32
- function setupDual(slider, natives) {
37
+ function setupDual(slider, natives, bag) {
33
38
  const lo = natives.find((n) => n.getAttribute("data-thumb") === "lo") ?? natives[0];
34
39
  const hi = natives.find((n) => n.getAttribute("data-thumb") === "hi") ?? natives[1];
35
40
  if (!lo || !hi || lo === hi) return;
@@ -59,46 +64,52 @@ function setupDual(slider, natives) {
59
64
  lo.style.clipPath = `inset(0 ${100 - pLo}% 0 0)`;
60
65
  hi.style.clipPath = `inset(0 0 0 ${pHi}%)`;
61
66
  };
62
- lo.addEventListener("input", () => {
67
+ bind(lo, "input", () => {
63
68
  sync(lo);
64
- slider.dispatchEvent(new CustomEvent("icen:slider-input", {
65
- bubbles: true,
66
- detail: { lo: Number(lo.value), hi: Number(hi.value) }
67
- }));
68
- });
69
- hi.addEventListener("input", () => {
69
+ emitIcen(slider, "icen:slider-input", { lo: Number(lo.value), hi: Number(hi.value) });
70
+ }, bag);
71
+ bind(hi, "input", () => {
70
72
  sync(hi);
71
- slider.dispatchEvent(new CustomEvent("icen:slider-input", {
72
- bubbles: true,
73
- detail: { lo: Number(lo.value), hi: Number(hi.value) }
74
- }));
75
- });
73
+ emitIcen(slider, "icen:slider-input", { lo: Number(lo.value), hi: Number(hi.value) });
74
+ }, bag);
76
75
  const onChange = () => {
77
- slider.dispatchEvent(new CustomEvent("icen:slider-change", {
78
- bubbles: true,
79
- detail: { lo: Number(lo.value), hi: Number(hi.value) }
80
- }));
76
+ emitIcen(slider, "icen:slider-change", { lo: Number(lo.value), hi: Number(hi.value) });
81
77
  };
82
- lo.addEventListener("change", onChange);
83
- hi.addEventListener("change", onChange);
78
+ bind(lo, "change", onChange, bag);
79
+ bind(hi, "change", onChange, bag);
84
80
  sync(null);
85
81
  }
86
82
  function setup(slider) {
87
83
  const el = slider;
88
- if (el.__icenSliderInit) return;
89
- el.__icenSliderInit = true;
84
+ if (el.__icenSliderInit) return void 0;
90
85
  const natives = Array.from(slider.querySelectorAll(".slider-native"));
91
- if (natives.length === 0) return;
92
- if (slider.classList.contains("slider--dual")) setupDual(slider, natives);
93
- else setupSingle(slider, natives[0]);
86
+ if (natives.length === 0) return void 0;
87
+ el.__icenSliderInit = true;
88
+ const bag = [];
89
+ if (slider.classList.contains("slider--dual")) setupDual(slider, natives, bag);
90
+ else setupSingle(slider, natives[0], bag);
91
+ return () => {
92
+ for (const off of bag) off();
93
+ bag.length = 0;
94
+ el.__icenSliderInit = false;
95
+ };
94
96
  }
95
97
  function initSlider(root) {
96
- if (typeof document === "undefined") return;
97
- const scope = root ?? document;
98
- const containers = [];
99
- if (scope instanceof Element && scope.matches(".slider")) containers.push(scope);
100
- containers.push(...Array.from(scope.querySelectorAll(".slider")));
101
- for (const c of containers) setup(c);
98
+ const cleanups = [];
99
+ if (typeof document !== "undefined") {
100
+ const scope = root ?? document;
101
+ const containers = [];
102
+ if (scope instanceof Element && scope.matches(".slider")) containers.push(scope);
103
+ containers.push(...Array.from(scope.querySelectorAll(".slider")));
104
+ for (const c of containers) {
105
+ const cleanup = setup(c);
106
+ if (cleanup) cleanups.push(cleanup);
107
+ }
108
+ }
109
+ return () => {
110
+ for (const fn of cleanups) fn();
111
+ cleanups.length = 0;
112
+ };
102
113
  }
103
114
  export {
104
115
  initSlider
@@ -1,4 +1,8 @@
1
- /** 为 root 下每个 .split-pane[data-split-pane] 初始化(root 自身匹配也算)。 */
2
- declare function initSplitPane(root?: ParentNode): void;
1
+ /**
2
+ * 为 root 下每个 .split-pane[data-split-pane] 初始化(root 自身匹配也算)。
3
+ * 返回销毁函数:移除本次初始化挂上的全部监听并复位幂等标记(销毁后可重新 init)。
4
+ * SSR 下返回 no-op。
5
+ */
6
+ declare function initSplitPane(root?: ParentNode): () => void;
3
7
 
4
8
  export { initSplitPane };