@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,3 +1,126 @@
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 applyPanelSizing(panel, sizing) {
21
+ if (sizing == null) {
22
+ panel.style.width = "";
23
+ panel.style.minWidth = "";
24
+ panel.style.maxWidth = "";
25
+ panel.style.minHeight = "";
26
+ panel.style.maxHeight = "";
27
+ return;
28
+ }
29
+ const vw = typeof document === "undefined" ? Number.POSITIVE_INFINITY : document.documentElement.clientWidth;
30
+ const fitW = (n) => Math.min(n, Math.max(80, vw - 24));
31
+ if (sizing.width != null) {
32
+ const w = fitW(sizing.width);
33
+ panel.style.width = `${w}px`;
34
+ panel.style.maxWidth = `${w}px`;
35
+ }
36
+ if (sizing.minWidth != null) panel.style.minWidth = `${fitW(sizing.minWidth)}px`;
37
+ if (sizing.maxWidth != null && sizing.width == null) panel.style.maxWidth = `${fitW(sizing.maxWidth)}px`;
38
+ if (sizing.minHeight != null) panel.style.minHeight = `${sizing.minHeight}px`;
39
+ if (sizing.maxHeight != null) panel.style.maxHeight = `${sizing.maxHeight}px`;
40
+ }
41
+ var DEFAULT_MIN_WIDTH = 220;
42
+ var DEFAULT_MAX_WIDTH = 420;
43
+ var DEFAULT_MIN_HEIGHT = 120;
44
+ var DEFAULT_MAX_HEIGHT = 420;
45
+ function clamp(value, min, max) {
46
+ if (max < min) return min;
47
+ return Math.min(max, Math.max(min, value));
48
+ }
49
+ function chooseSide(preferredSide, availableTop, availableBottom, desiredHeight) {
50
+ const preferredSpace = preferredSide === "top" ? availableTop : availableBottom;
51
+ const oppositeSpace = preferredSide === "top" ? availableBottom : availableTop;
52
+ if (preferredSpace >= desiredHeight) return preferredSide;
53
+ if (oppositeSpace >= desiredHeight) return preferredSide === "top" ? "bottom" : "top";
54
+ if (preferredSpace >= oppositeSpace) return preferredSide;
55
+ return preferredSide === "top" ? "bottom" : "top";
56
+ }
57
+ function horizontalAnchor(anchor, width, align) {
58
+ if (align === "center") return anchor.left + anchor.width / 2 - width / 2;
59
+ if (align === "end") return anchor.right - width;
60
+ return anchor.left;
61
+ }
62
+ function computePopoverLayout(anchor, viewport, opts = {}) {
63
+ const margin = opts.margin ?? 12;
64
+ const offset = opts.offset ?? 8;
65
+ const preferredSide = opts.side ?? "top";
66
+ const align = opts.align ?? "start";
67
+ if (preferredSide === "left" || preferredSide === "right") {
68
+ const availableLeft = Math.max(0, anchor.left - margin - offset);
69
+ const availableRight = Math.max(0, viewport.width - anchor.right - margin - offset);
70
+ const side2 = (preferredSide === "left" ? availableLeft : availableRight) >= DEFAULT_MIN_WIDTH ? preferredSide : preferredSide === "left" ? "right" : "left";
71
+ const minWidth2 = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
72
+ const maxWidth2 = Math.min(
73
+ Math.max(opts.maxWidth ?? DEFAULT_MAX_WIDTH, minWidth2),
74
+ side2 === "left" ? availableLeft : availableRight
75
+ );
76
+ const width2 = minWidth2;
77
+ const left2 = clamp(
78
+ side2 === "left" ? anchor.left - offset - width2 : anchor.right + offset,
79
+ margin,
80
+ viewport.width - margin - width2
81
+ );
82
+ const availableHeight2 = Math.max(80, viewport.height - margin * 2);
83
+ const maxHeight2 = Math.min(opts.maxHeight ?? DEFAULT_MAX_HEIGHT, availableHeight2);
84
+ const desired = clamp(opts.contentHeightHint ?? Math.min(anchor.height, maxHeight2), 40, maxHeight2);
85
+ const anchorTop = align === "center" ? anchor.top + anchor.height / 2 - desired / 2 : align === "end" ? anchor.bottom - desired : anchor.top;
86
+ const top2 = clamp(anchorTop, margin, viewport.height - margin - desired);
87
+ return { side: side2, left: left2, top: top2, width: width2, maxHeight: maxHeight2 };
88
+ }
89
+ const availableWidth = Math.max(80, viewport.width - margin * 2);
90
+ const requestedMinWidth = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
91
+ const requestedMaxWidth = Math.max(requestedMinWidth, opts.maxWidth ?? DEFAULT_MAX_WIDTH);
92
+ const maxWidth = Math.min(requestedMaxWidth, availableWidth);
93
+ const minWidth = Math.min(requestedMinWidth, maxWidth);
94
+ const availableTop = Math.max(0, anchor.top - margin - offset);
95
+ const availableBottom = Math.max(0, viewport.height - anchor.bottom - margin - offset);
96
+ const availableHeight = Math.max(80, viewport.height - margin * 2);
97
+ const minHeight = Math.min(Math.max(80, opts.minHeight ?? DEFAULT_MIN_HEIGHT), availableHeight);
98
+ const requestedMaxHeight = Math.max(minHeight, opts.maxHeight ?? DEFAULT_MAX_HEIGHT);
99
+ const fallbackHeight = Math.min(260, requestedMaxHeight);
100
+ const contentHeight = Math.max(opts.contentHeightHint ?? 0, fallbackHeight);
101
+ const desiredHeight = clamp(contentHeight, minHeight, Math.min(requestedMaxHeight, availableHeight));
102
+ const side = chooseSide(preferredSide, availableTop, availableBottom, desiredHeight);
103
+ const sideSpace = side === "top" ? availableTop : availableBottom;
104
+ const maxHeight = Math.min(
105
+ requestedMaxHeight,
106
+ Math.max(80, sideSpace || Math.max(availableTop, availableBottom)),
107
+ availableHeight
108
+ );
109
+ const positionedHeight = clamp(desiredHeight, 40, maxHeight);
110
+ const width = align === "stretch" ? clamp(anchor.width, minWidth, maxWidth) : maxWidth;
111
+ const left = clamp(horizontalAnchor(anchor, width, align), margin, viewport.width - margin - width);
112
+ const desiredTop = side === "top" ? anchor.top - offset - positionedHeight : anchor.bottom + offset;
113
+ const top = clamp(desiredTop, margin, viewport.height - margin - positionedHeight);
114
+ return { side, left, top, width, maxHeight };
115
+ }
116
+
117
+ // src/behaviors/events.ts
118
+ function emitIcen(source, type, detail) {
119
+ return source.dispatchEvent(
120
+ new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
121
+ );
122
+ }
123
+
1
124
  // src/behaviors/date-picker.ts
2
125
  var openPicker = null;
3
126
  var panelEl = null;
@@ -33,16 +156,21 @@ function ensurePanel() {
33
156
  }
34
157
  function positionPanel(panel, trigger) {
35
158
  const r = trigger.getBoundingClientRect();
36
- const pw = 280;
37
- const ph = panel.offsetHeight || 320;
38
- let left = r.left;
39
- let top = r.bottom + 4;
40
- if (left + pw > window.innerWidth - 12) left = window.innerWidth - pw - 12;
41
- if (left < 12) left = 12;
42
- if (top + ph > window.innerHeight - 12) top = r.top - ph - 4;
43
- if (top < 12) top = 12;
44
- panel.style.left = left + "px";
45
- panel.style.top = top + "px";
159
+ const sizing = readPanelSizing(trigger.closest("[data-date-picker]"));
160
+ applyPanelSizing(panel, null);
161
+ if (sizing.width != null || sizing.minWidth != null) {
162
+ applyPanelSizing(panel, { width: sizing.width ?? sizing.minWidth, maxWidth: sizing.maxWidth, maxHeight: sizing.maxHeight });
163
+ } else {
164
+ applyPanelSizing(panel, sizing);
165
+ }
166
+ const pw = sizing.width ?? sizing.minWidth ?? 280;
167
+ const layout = computePopoverLayout(
168
+ r,
169
+ { width: window.innerWidth, height: window.innerHeight },
170
+ { side: "bottom", offset: 4, margin: 12, minWidth: pw, maxWidth: pw, maxHeight: sizing.maxHeight }
171
+ );
172
+ panel.style.left = `${layout.left}px`;
173
+ panel.style.top = `${layout.top}px`;
46
174
  }
47
175
  var SVG_NS = "http://www.w3.org/2000/svg";
48
176
  function buildCalIcon() {
@@ -150,12 +278,10 @@ function renderCalendar(panel, ctx) {
150
278
  foot.append(todayBtn, clearBtn);
151
279
  panel.append(head, weekdays, grid, foot);
152
280
  prevBtn.addEventListener("click", () => {
153
- ctx.view = new Date(year, month - 1, 15);
154
- renderCalendar(panel, { ...ctx, view: ctx.view });
281
+ renderCalendar(panel, { ...ctx, view: new Date(year, month - 1, 15) });
155
282
  });
156
283
  nextBtn.addEventListener("click", () => {
157
- ctx.view = new Date(year, month + 1, 15);
158
- renderCalendar(panel, { ...ctx, view: ctx.view });
284
+ renderCalendar(panel, { ...ctx, view: new Date(year, month + 1, 15) });
159
285
  });
160
286
  todayBtn.addEventListener("click", () => {
161
287
  onPick(new Date(today.getFullYear(), today.getMonth(), today.getDate()));
@@ -183,10 +309,20 @@ function closePanel() {
183
309
  repositionHandler = null;
184
310
  }
185
311
  }
312
+ var pickersInit = /* @__PURE__ */ new WeakSet();
186
313
  function setupPicker(container) {
314
+ if (pickersInit.has(container)) return () => {
315
+ };
316
+ pickersInit.add(container);
187
317
  const el = container;
188
- if (el.__icenDatePickerInit) return;
189
- el.__icenDatePickerInit = true;
318
+ const ac = new AbortController();
319
+ const disposers = [
320
+ () => {
321
+ if (openPicker === el) closePanel();
322
+ ac.abort();
323
+ pickersInit.delete(container);
324
+ }
325
+ ];
190
326
  const fmt = el.dataset.datePickerFormat ?? "yyyy-mm-dd";
191
327
  const minStr = el.dataset.datePickerMin;
192
328
  const maxStr = el.dataset.datePickerMax;
@@ -224,8 +360,9 @@ function setupPicker(container) {
224
360
  if (hidden) {
225
361
  const obs = new MutationObserver(() => paint());
226
362
  obs.observe(hidden, { attributes: true, attributeFilter: ["value"] });
363
+ disposers.push(() => obs.disconnect());
227
364
  }
228
- trigger.addEventListener("click", () => {
365
+ const onTriggerClick = () => {
229
366
  if (openPicker === el) {
230
367
  closePanel();
231
368
  return;
@@ -246,17 +383,9 @@ function setupPicker(container) {
246
383
  max,
247
384
  weekStart,
248
385
  onPick: (d) => {
249
- if (d === null) {
250
- if (hidden) {
251
- hidden.value = "";
252
- hidden.dispatchEvent(new Event("change", { bubbles: true }));
253
- }
254
- } else {
255
- if (hidden) {
256
- hidden.value = toDateStr(d);
257
- hidden.dispatchEvent(new Event("change", { bubbles: true }));
258
- }
259
- }
386
+ if (hidden) hidden.value = d ? toDateStr(d) : "";
387
+ emitIcen(el, "icen:date-change", { value: d ? toDateStr(d) : "", date: d });
388
+ if (hidden) hidden.dispatchEvent(new Event("change", { bubbles: true }));
260
389
  paint();
261
390
  closePanel();
262
391
  }
@@ -280,16 +409,26 @@ function setupPicker(container) {
280
409
  };
281
410
  window.addEventListener("scroll", repositionHandler, { passive: true });
282
411
  window.addEventListener("resize", repositionHandler, { passive: true });
283
- });
412
+ };
413
+ trigger.addEventListener("click", onTriggerClick, { signal: ac.signal });
414
+ return () => {
415
+ for (const d of disposers) d();
416
+ };
284
417
  }
285
418
  function initDatePicker(root) {
286
- if (typeof document === "undefined") return;
287
- if (typeof window === "undefined") return;
419
+ if (typeof document === "undefined") return () => {
420
+ };
421
+ if (typeof window === "undefined") return () => {
422
+ };
288
423
  const scope = root ?? document;
289
424
  const pickers = [];
290
425
  if (scope instanceof Element && scope.matches(".date-picker[data-date-picker]")) pickers.push(scope);
291
426
  pickers.push(...Array.from(scope.querySelectorAll(".date-picker[data-date-picker]")));
292
- for (const p of pickers) setupPicker(p);
427
+ const disposers = [];
428
+ for (const p of pickers) disposers.push(setupPicker(p));
429
+ return () => {
430
+ for (const d of disposers) d();
431
+ };
293
432
  }
294
433
  export {
295
434
  initDatePicker
@@ -1,4 +1,8 @@
1
- /** 绑定全部 [data-dropdown-trigger](document 级委托,重复调用安全)。 */
2
- declare function initDropdown(root?: ParentNode): void;
1
+ /**
2
+ * 绑定全部 [data-dropdown-trigger](document 级委托,重复调用安全)。
3
+ * 返回销毁函数:摘除 document 委托并复位幂等标记(销毁后可重新 init);
4
+ * 销毁时若有面板开着会顺带收起。SSR 下返回 no-op。
5
+ */
6
+ declare function initDropdown(root?: ParentNode): () => void;
3
7
 
4
8
  export { initDropdown };
@@ -1,12 +1,142 @@
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 applyPanelSizing(panel, sizing) {
21
+ if (sizing == null) {
22
+ panel.style.width = "";
23
+ panel.style.minWidth = "";
24
+ panel.style.maxWidth = "";
25
+ panel.style.minHeight = "";
26
+ panel.style.maxHeight = "";
27
+ return;
28
+ }
29
+ const vw = typeof document === "undefined" ? Number.POSITIVE_INFINITY : document.documentElement.clientWidth;
30
+ const fitW = (n) => Math.min(n, Math.max(80, vw - 24));
31
+ if (sizing.width != null) {
32
+ const w = fitW(sizing.width);
33
+ panel.style.width = `${w}px`;
34
+ panel.style.maxWidth = `${w}px`;
35
+ }
36
+ if (sizing.minWidth != null) panel.style.minWidth = `${fitW(sizing.minWidth)}px`;
37
+ if (sizing.maxWidth != null && sizing.width == null) panel.style.maxWidth = `${fitW(sizing.maxWidth)}px`;
38
+ if (sizing.minHeight != null) panel.style.minHeight = `${sizing.minHeight}px`;
39
+ if (sizing.maxHeight != null) panel.style.maxHeight = `${sizing.maxHeight}px`;
40
+ }
41
+ var DEFAULT_MIN_WIDTH = 220;
42
+ var DEFAULT_MAX_WIDTH = 420;
43
+ var DEFAULT_MIN_HEIGHT = 120;
44
+ var DEFAULT_MAX_HEIGHT = 420;
45
+ function clamp(value, min, max) {
46
+ if (max < min) return min;
47
+ return Math.min(max, Math.max(min, value));
48
+ }
49
+ function chooseSide(preferredSide, availableTop, availableBottom, desiredHeight) {
50
+ const preferredSpace = preferredSide === "top" ? availableTop : availableBottom;
51
+ const oppositeSpace = preferredSide === "top" ? availableBottom : availableTop;
52
+ if (preferredSpace >= desiredHeight) return preferredSide;
53
+ if (oppositeSpace >= desiredHeight) return preferredSide === "top" ? "bottom" : "top";
54
+ if (preferredSpace >= oppositeSpace) return preferredSide;
55
+ return preferredSide === "top" ? "bottom" : "top";
56
+ }
57
+ function horizontalAnchor(anchor, width, align) {
58
+ if (align === "center") return anchor.left + anchor.width / 2 - width / 2;
59
+ if (align === "end") return anchor.right - width;
60
+ return anchor.left;
61
+ }
62
+ function computePopoverLayout(anchor, viewport, opts = {}) {
63
+ const margin = opts.margin ?? 12;
64
+ const offset = opts.offset ?? 8;
65
+ const preferredSide = opts.side ?? "top";
66
+ const align = opts.align ?? "start";
67
+ if (preferredSide === "left" || preferredSide === "right") {
68
+ const availableLeft = Math.max(0, anchor.left - margin - offset);
69
+ const availableRight = Math.max(0, viewport.width - anchor.right - margin - offset);
70
+ const side2 = (preferredSide === "left" ? availableLeft : availableRight) >= DEFAULT_MIN_WIDTH ? preferredSide : preferredSide === "left" ? "right" : "left";
71
+ const minWidth2 = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
72
+ const maxWidth2 = Math.min(
73
+ Math.max(opts.maxWidth ?? DEFAULT_MAX_WIDTH, minWidth2),
74
+ side2 === "left" ? availableLeft : availableRight
75
+ );
76
+ const width2 = minWidth2;
77
+ const left2 = clamp(
78
+ side2 === "left" ? anchor.left - offset - width2 : anchor.right + offset,
79
+ margin,
80
+ viewport.width - margin - width2
81
+ );
82
+ const availableHeight2 = Math.max(80, viewport.height - margin * 2);
83
+ const maxHeight2 = Math.min(opts.maxHeight ?? DEFAULT_MAX_HEIGHT, availableHeight2);
84
+ const desired = clamp(opts.contentHeightHint ?? Math.min(anchor.height, maxHeight2), 40, maxHeight2);
85
+ const anchorTop = align === "center" ? anchor.top + anchor.height / 2 - desired / 2 : align === "end" ? anchor.bottom - desired : anchor.top;
86
+ const top2 = clamp(anchorTop, margin, viewport.height - margin - desired);
87
+ return { side: side2, left: left2, top: top2, width: width2, maxHeight: maxHeight2 };
88
+ }
89
+ const availableWidth = Math.max(80, viewport.width - margin * 2);
90
+ const requestedMinWidth = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
91
+ const requestedMaxWidth = Math.max(requestedMinWidth, opts.maxWidth ?? DEFAULT_MAX_WIDTH);
92
+ const maxWidth = Math.min(requestedMaxWidth, availableWidth);
93
+ const minWidth = Math.min(requestedMinWidth, maxWidth);
94
+ const availableTop = Math.max(0, anchor.top - margin - offset);
95
+ const availableBottom = Math.max(0, viewport.height - anchor.bottom - margin - offset);
96
+ const availableHeight = Math.max(80, viewport.height - margin * 2);
97
+ const minHeight = Math.min(Math.max(80, opts.minHeight ?? DEFAULT_MIN_HEIGHT), availableHeight);
98
+ const requestedMaxHeight = Math.max(minHeight, opts.maxHeight ?? DEFAULT_MAX_HEIGHT);
99
+ const fallbackHeight = Math.min(260, requestedMaxHeight);
100
+ const contentHeight = Math.max(opts.contentHeightHint ?? 0, fallbackHeight);
101
+ const desiredHeight = clamp(contentHeight, minHeight, Math.min(requestedMaxHeight, availableHeight));
102
+ const side = chooseSide(preferredSide, availableTop, availableBottom, desiredHeight);
103
+ const sideSpace = side === "top" ? availableTop : availableBottom;
104
+ const maxHeight = Math.min(
105
+ requestedMaxHeight,
106
+ Math.max(80, sideSpace || Math.max(availableTop, availableBottom)),
107
+ availableHeight
108
+ );
109
+ const positionedHeight = clamp(desiredHeight, 40, maxHeight);
110
+ const width = align === "stretch" ? clamp(anchor.width, minWidth, maxWidth) : maxWidth;
111
+ const left = clamp(horizontalAnchor(anchor, width, align), margin, viewport.width - margin - width);
112
+ const desiredTop = side === "top" ? anchor.top - offset - positionedHeight : anchor.bottom + offset;
113
+ const top = clamp(desiredTop, margin, viewport.height - margin - positionedHeight);
114
+ return { side, left, top, width, maxHeight };
115
+ }
116
+
117
+ // src/behaviors/events.ts
118
+ function emitIcen(source, type, detail) {
119
+ return source.dispatchEvent(
120
+ new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
121
+ );
122
+ }
123
+
124
+ // src/behaviors/context-menu.ts
125
+ function emitMenuSelect(source, item) {
126
+ emitIcen(item, "icen:menu-select", {
127
+ source,
128
+ item,
129
+ value: item.dataset.value ?? "",
130
+ /* 优先专用 label 子元素(避开快捷键/图标文本混入),无则退整项文本 */
131
+ label: (item.querySelector(".menu-item-label, .command-item-label")?.textContent ?? item.textContent ?? "").trim().slice(0, 80)
132
+ });
133
+ }
134
+
1
135
  // src/behaviors/dropdown.ts
2
136
  var VIEWPORT_MARGIN = 12;
3
137
  var SIDE_OFFSET = 4;
4
138
  var active = null;
5
139
  var initialized = false;
6
- function clamp(value, min, max) {
7
- if (max < min) return min;
8
- return Math.min(max, Math.max(min, value));
9
- }
10
140
  function menuItems(panel) {
11
141
  return Array.from(panel.querySelectorAll(".menu-item:not(.is-disabled):not(:disabled)"));
12
142
  }
@@ -19,21 +149,14 @@ function place() {
19
149
  if (!active) return;
20
150
  const { trigger, panel } = active;
21
151
  const rect = trigger.getBoundingClientRect();
22
- const vw = window.innerWidth;
23
- const vh = window.innerHeight;
24
- const pw = panel.offsetWidth;
25
- const ph = panel.offsetHeight;
26
- const left = clamp(rect.left, VIEWPORT_MARGIN, vw - VIEWPORT_MARGIN - pw);
27
- let top = rect.bottom + SIDE_OFFSET;
28
- let side = "bottom";
29
- if (top + ph > vh - VIEWPORT_MARGIN && rect.top - SIDE_OFFSET - ph >= VIEWPORT_MARGIN) {
30
- top = rect.top - SIDE_OFFSET - ph;
31
- side = "top";
32
- }
33
- top = clamp(top, VIEWPORT_MARGIN, vh - VIEWPORT_MARGIN - ph);
34
- panel.style.left = `${left}px`;
35
- panel.style.top = `${top}px`;
36
- panel.dataset.side = side;
152
+ const layout = computePopoverLayout(
153
+ rect,
154
+ { width: window.innerWidth, height: window.innerHeight },
155
+ { side: "bottom", offset: SIDE_OFFSET, margin: VIEWPORT_MARGIN }
156
+ );
157
+ panel.style.left = `${layout.left}px`;
158
+ panel.style.top = `${layout.top}px`;
159
+ panel.dataset.side = layout.side;
37
160
  }
38
161
  function openDropdown(trigger, highlightFirst = false) {
39
162
  closeDropdown();
@@ -44,6 +167,7 @@ function openDropdown(trigger, highlightFirst = false) {
44
167
  panel.className = "menu";
45
168
  panel.setAttribute("role", "menu");
46
169
  panel.style.visibility = "hidden";
170
+ applyPanelSizing(panel, readPanelSizing(root));
47
171
  while (template.firstChild) panel.appendChild(template.firstChild);
48
172
  panel.querySelectorAll(".menu-item").forEach((el) => el.setAttribute("role", "menuitem"));
49
173
  document.body.appendChild(panel);
@@ -100,22 +224,28 @@ function onKeyDown(e) {
100
224
  if (e.key === "Enter" && active.highlight >= 0) {
101
225
  e.preventDefault();
102
226
  const item = items[active.highlight];
227
+ emitMenuSelect(active.panel, item);
103
228
  closeDropdown(true);
104
229
  item.click();
105
230
  }
106
231
  }
107
232
  function initDropdown(root) {
108
- if (typeof document === "undefined") return;
233
+ if (typeof document === "undefined") return () => {
234
+ };
109
235
  const scope = root ?? document;
110
236
  scope.querySelectorAll("[data-dropdown-trigger]").forEach((el) => el.setAttribute("aria-expanded", "false"));
111
- if (initialized) return;
237
+ if (initialized) return () => {
238
+ };
112
239
  initialized = true;
113
- document.addEventListener("click", (e) => {
240
+ const onClick = (e) => {
114
241
  const target = e.target instanceof Element ? e.target : null;
115
242
  if (!target) return;
116
243
  if (active && target.closest(".menu-item") && active.panel.contains(target)) {
117
244
  const item = target.closest(".menu-item");
118
245
  if (item) {
246
+ if (!item.matches(".is-disabled") && !item.matches(":disabled")) {
247
+ emitMenuSelect(active.panel, item);
248
+ }
119
249
  closeDropdown();
120
250
  return;
121
251
  }
@@ -124,8 +254,9 @@ function initDropdown(root) {
124
254
  if (!trigger) return;
125
255
  if (active && active.trigger === trigger) closeDropdown();
126
256
  else openDropdown(trigger);
127
- });
128
- document.addEventListener("keydown", (e) => {
257
+ };
258
+ document.addEventListener("click", onClick);
259
+ const onKeyDown2 = (e) => {
129
260
  if (e.key !== "ArrowDown") return;
130
261
  if (active) return;
131
262
  const target = e.target instanceof Element ? e.target : null;
@@ -133,7 +264,15 @@ function initDropdown(root) {
133
264
  if (!trigger) return;
134
265
  e.preventDefault();
135
266
  openDropdown(trigger, true);
136
- });
267
+ };
268
+ document.addEventListener("keydown", onKeyDown2);
269
+ const destroy = () => {
270
+ document.removeEventListener("click", onClick);
271
+ document.removeEventListener("keydown", onKeyDown2);
272
+ closeDropdown();
273
+ initialized = false;
274
+ };
275
+ return destroy;
137
276
  }
138
277
  export {
139
278
  initDropdown