@icen.ai/ui 0.5.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/AGENTS.md +262 -0
  2. package/README.md +52 -13
  3. package/dist/base.css +33 -5
  4. package/dist/behaviors/accordion.d.mts +7 -0
  5. package/dist/behaviors/accordion.mjs +37 -11
  6. package/dist/behaviors/ai-chat.d.mts +59 -0
  7. package/dist/behaviors/ai-chat.mjs +580 -0
  8. package/dist/behaviors/ai-composer.d.mts +137 -0
  9. package/dist/behaviors/ai-composer.mjs +2114 -0
  10. package/dist/behaviors/ai-core.d.mts +156 -0
  11. package/dist/behaviors/ai-core.mjs +504 -0
  12. package/dist/behaviors/ai-diff.d.mts +53 -0
  13. package/dist/behaviors/ai-diff.mjs +413 -0
  14. package/dist/behaviors/ai-panel.d.mts +101 -0
  15. package/dist/behaviors/ai-panel.mjs +1087 -0
  16. package/dist/behaviors/ai-provider.d.mts +198 -0
  17. package/dist/behaviors/ai-provider.mjs +1061 -0
  18. package/dist/behaviors/ai-tool.d.mts +26 -0
  19. package/dist/behaviors/ai-tool.mjs +2173 -0
  20. package/dist/behaviors/ai-tools.d.mts +84 -0
  21. package/dist/behaviors/ai-tools.mjs +1762 -0
  22. package/dist/behaviors/back-top.d.mts +20 -0
  23. package/dist/behaviors/back-top.mjs +64 -32
  24. package/dist/behaviors/carousel.d.mts +7 -0
  25. package/dist/behaviors/carousel.mjs +72 -27
  26. package/dist/behaviors/charts.d.mts +296 -0
  27. package/dist/behaviors/charts.mjs +1364 -440
  28. package/dist/behaviors/command-palette.d.mts +42 -0
  29. package/dist/behaviors/command-palette.mjs +167 -27
  30. package/dist/behaviors/context-menu.d.mts +53 -0
  31. package/dist/behaviors/context-menu.mjs +127 -39
  32. package/dist/behaviors/controls.d.mts +59 -0
  33. package/dist/behaviors/controls.mjs +601 -0
  34. package/dist/behaviors/copy.d.mts +7 -0
  35. package/dist/behaviors/copy.mjs +41 -15
  36. package/dist/behaviors/datatable.d.mts +90 -0
  37. package/dist/behaviors/datatable.mjs +279 -53
  38. package/dist/behaviors/date-picker.d.mts +4 -0
  39. package/dist/behaviors/date-picker.mjs +276 -69
  40. package/dist/behaviors/dropdown.d.mts +8 -0
  41. package/dist/behaviors/dropdown.mjs +171 -28
  42. package/dist/behaviors/events.d.mts +330 -0
  43. package/dist/behaviors/events.mjs +208 -0
  44. package/dist/behaviors/input.d.mts +4 -0
  45. package/dist/behaviors/input.mjs +52 -32
  46. package/dist/behaviors/modal.d.mts +30 -0
  47. package/dist/behaviors/modal.mjs +87 -16
  48. package/dist/behaviors/nav.d.mts +7 -0
  49. package/dist/behaviors/nav.mjs +57 -15
  50. package/dist/behaviors/notification.d.mts +188 -0
  51. package/dist/behaviors/notification.mjs +274 -140
  52. package/dist/behaviors/popover.d.mts +82 -0
  53. package/dist/behaviors/popover.mjs +92 -5
  54. package/dist/behaviors/select.d.mts +6 -0
  55. package/dist/behaviors/select.mjs +287 -45
  56. package/dist/behaviors/sidebar.d.mts +7 -0
  57. package/dist/behaviors/sidebar.mjs +44 -13
  58. package/dist/behaviors/slider.d.mts +7 -0
  59. package/dist/behaviors/slider.mjs +55 -43
  60. package/dist/behaviors/split-pane.d.mts +8 -0
  61. package/dist/behaviors/split-pane.mjs +70 -29
  62. package/dist/behaviors/tabs.d.mts +9 -0
  63. package/dist/behaviors/tabs.mjs +102 -26
  64. package/dist/behaviors/tag-input.d.mts +7 -0
  65. package/dist/behaviors/tag-input.mjs +66 -17
  66. package/dist/behaviors/theme.d.mts +36 -0
  67. package/dist/behaviors/theme.mjs +51 -5
  68. package/dist/behaviors/toast.d.mts +48 -0
  69. package/dist/behaviors/toast.mjs +98 -25
  70. package/dist/behaviors/tree.d.mts +7 -0
  71. package/dist/behaviors/tree.mjs +39 -15
  72. package/dist/behaviors/upload.d.mts +4 -0
  73. package/dist/behaviors/upload.mjs +79 -44
  74. package/dist/cli.mjs +1 -1
  75. package/dist/components/ai-chat.css +1037 -0
  76. package/dist/components/ai-chat.mjs +3 -0
  77. package/dist/components/ai-composer.mjs +3 -0
  78. package/dist/components/ai-context.mjs +2 -0
  79. package/dist/components/ai-diff.css +311 -0
  80. package/dist/components/ai-diff.mjs +3 -0
  81. package/dist/components/ai-files.mjs +3 -0
  82. package/dist/components/ai-message.mjs +3 -0
  83. package/dist/components/ai-panel.css +682 -0
  84. package/dist/components/ai-reasoning.mjs +3 -0
  85. package/dist/components/ai-subagent.mjs +2 -0
  86. package/dist/components/ai-todo.mjs +2 -0
  87. package/dist/components/ai-tool-call.mjs +2 -0
  88. package/dist/components/ai-tool.css +532 -0
  89. package/dist/components/ai-usage.mjs +3 -0
  90. package/dist/components/badge.css +5 -9
  91. package/dist/components/breadcrumb.css +0 -36
  92. package/dist/components/btn.css +4 -7
  93. package/dist/components/card.css +3 -6
  94. package/dist/components/chart-bar.css +10 -0
  95. package/dist/components/chart-calendar.mjs +4 -0
  96. package/dist/components/chart-gauge.css +1 -1
  97. package/dist/components/chart-heatmap.css +40 -0
  98. package/dist/components/chart-pie.css +0 -6
  99. package/dist/components/chart-scatter.css +24 -0
  100. package/dist/components/chart-scatter.mjs +4 -0
  101. package/dist/components/charts.css +87 -2
  102. package/dist/components/charts.mjs +1 -0
  103. package/dist/components/command-palette.css +2 -2
  104. package/dist/components/content.css +11 -25
  105. package/dist/components/copy.css +8 -8
  106. package/dist/components/datatable.css +5 -9
  107. package/dist/components/date-picker.css +4 -8
  108. package/dist/components/empty.css +0 -4
  109. package/dist/components/feedback.css +10 -17
  110. package/dist/components/form.css +13 -14
  111. package/dist/components/input.css +2 -2
  112. package/dist/components/layout.css +36 -36
  113. package/dist/components/media.css +12 -8
  114. package/dist/components/menu.css +2 -2
  115. package/dist/components/modal.css +1 -1
  116. package/dist/components/nav.css +11 -6
  117. package/dist/components/notification.css +27 -28
  118. package/dist/components/pagination.css +4 -2
  119. package/dist/components/pagination.mjs +1 -0
  120. package/dist/components/panel.css +16 -16
  121. package/dist/components/pill.css +15 -15
  122. package/dist/components/rating.mjs +1 -0
  123. package/dist/components/segmented.css +14 -6
  124. package/dist/components/segmented.mjs +1 -0
  125. package/dist/components/select.css +79 -24
  126. package/dist/components/sidebar.css +1 -1
  127. package/dist/components/split-pane.css +0 -1
  128. package/dist/components/stat.css +14 -18
  129. package/dist/components/steps.css +15 -5
  130. package/dist/components/steps.mjs +1 -0
  131. package/dist/components/switch.css +11 -13
  132. package/dist/components/switch.mjs +1 -0
  133. package/dist/components/table.css +46 -47
  134. package/dist/components/table.mjs +1 -0
  135. package/dist/components/tabs.css +57 -45
  136. package/dist/components/tag-input.css +5 -10
  137. package/dist/components/toast.css +16 -13
  138. package/dist/components/tooltip.css +2 -2
  139. package/dist/components/upload.css +0 -15
  140. package/dist/index.d.mts +39 -0
  141. package/dist/index.mjs +11243 -3312
  142. package/dist/registry.json +147 -9
  143. package/dist/retro-effects.css +23 -32
  144. package/dist/tokens/colors.css +4 -1
  145. package/dist/tokens/retro-effects.css +23 -32
  146. package/dist/tokens/style-profiles.css +24 -2
  147. package/dist/tokens/typography.css +2 -0
  148. package/dist/tokens.css +30 -3
  149. package/dist/ui.css +3420 -645
  150. package/docs/spec/ai-native.md +662 -0
  151. package/package.json +15 -5
@@ -1,8 +1,132 @@
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;
4
127
  var outsideHandler = null;
5
128
  var escHandler = null;
129
+ var repositionHandler = null;
6
130
  function pad(n) {
7
131
  return n < 10 ? "0" + n : String(n);
8
132
  }
@@ -32,18 +156,54 @@ function ensurePanel() {
32
156
  }
33
157
  function positionPanel(panel, trigger) {
34
158
  const r = trigger.getBoundingClientRect();
35
- const pw = 280;
36
- const ph = panel.offsetHeight || 320;
37
- let left = r.left;
38
- let top = r.bottom + 4;
39
- if (left + pw > window.innerWidth - 12) left = window.innerWidth - pw - 12;
40
- if (left < 12) left = 12;
41
- if (top + ph > window.innerHeight - 12) top = r.top - ph - 4;
42
- if (top < 12) top = 12;
43
- panel.style.left = left + "px";
44
- panel.style.top = top + "px";
45
- }
46
- var CAL_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" x2="16" y1="2" y2="6"/><line x1="8" x2="8" y1="2" y2="6"/><line x1="3" x2="21" y1="10" y2="10"/></svg>';
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`;
174
+ }
175
+ var SVG_NS = "http://www.w3.org/2000/svg";
176
+ function buildCalIcon() {
177
+ const svg = document.createElementNS(SVG_NS, "svg");
178
+ svg.setAttribute("viewBox", "0 0 24 24");
179
+ svg.setAttribute("fill", "none");
180
+ svg.setAttribute("stroke", "currentColor");
181
+ svg.setAttribute("stroke-width", "2");
182
+ svg.setAttribute("stroke-linecap", "round");
183
+ svg.setAttribute("stroke-linejoin", "round");
184
+ const rect = document.createElementNS(SVG_NS, "rect");
185
+ rect.setAttribute("x", "3");
186
+ rect.setAttribute("y", "4");
187
+ rect.setAttribute("width", "18");
188
+ rect.setAttribute("height", "18");
189
+ rect.setAttribute("rx", "2");
190
+ rect.setAttribute("ry", "2");
191
+ svg.appendChild(rect);
192
+ const lines = [
193
+ ["16", "16", "2", "6"],
194
+ ["8", "8", "2", "6"],
195
+ ["3", "21", "10", "10"]
196
+ ];
197
+ for (const [x1, x2, y1, y2] of lines) {
198
+ const line = document.createElementNS(SVG_NS, "line");
199
+ line.setAttribute("x1", x1);
200
+ line.setAttribute("x2", x2);
201
+ line.setAttribute("y1", y1);
202
+ line.setAttribute("y2", y2);
203
+ svg.appendChild(line);
204
+ }
205
+ return svg;
206
+ }
47
207
  var WEEK_LABELS_ZH = ["\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u65E5"];
48
208
  var WEEK_LABELS_SUN = ["\u65E5", "\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D"];
49
209
  function renderCalendar(panel, ctx) {
@@ -61,49 +221,72 @@ function renderCalendar(panel, ctx) {
61
221
  for (let i = 0; i < 42; i++) {
62
222
  cells.push(new Date(gridStart.getFullYear(), gridStart.getMonth(), gridStart.getDate() + i));
63
223
  }
64
- let html = `<div class="date-picker-head">
65
- <button class="date-picker-nav" data-dp-nav="-1" type="button" aria-label="\u4E0A\u4E00\u6708">\u2039</button>
66
- <span class="date-picker-title">${year} \u5E74 ${month + 1} \u6708</span>
67
- <button class="date-picker-nav" data-dp-nav="1" type="button" aria-label="\u4E0B\u4E00\u6708">\u203A</button>
68
- </div><div class="date-picker-weekdays">${labels.map((l) => `<span>${l}</span>`).join("")}</div><div class="date-picker-grid">`;
224
+ panel.textContent = "";
225
+ const head = document.createElement("div");
226
+ head.className = "date-picker-head";
227
+ const prevBtn = document.createElement("button");
228
+ prevBtn.type = "button";
229
+ prevBtn.className = "date-picker-nav";
230
+ prevBtn.setAttribute("aria-label", "\u4E0A\u4E00\u6708");
231
+ prevBtn.textContent = "\u2039";
232
+ const title = document.createElement("span");
233
+ title.className = "date-picker-title";
234
+ title.textContent = `${year} \u5E74 ${month + 1} \u6708`;
235
+ const nextBtn = document.createElement("button");
236
+ nextBtn.type = "button";
237
+ nextBtn.className = "date-picker-nav";
238
+ nextBtn.setAttribute("aria-label", "\u4E0B\u4E00\u6708");
239
+ nextBtn.textContent = "\u203A";
240
+ head.append(prevBtn, title, nextBtn);
241
+ const weekdays = document.createElement("div");
242
+ weekdays.className = "date-picker-weekdays";
243
+ for (const label of labels) {
244
+ const s = document.createElement("span");
245
+ s.textContent = label;
246
+ weekdays.appendChild(s);
247
+ }
248
+ const grid = document.createElement("div");
249
+ grid.className = "date-picker-grid";
69
250
  for (const d of cells) {
70
251
  const isOutside = d.getMonth() !== month;
71
- const ds = toDateStr(d);
72
252
  const isToday = d.getTime() === today.getTime();
73
- const isSelected = selected && ds === toDateStr(selected);
74
- const disabled = min && d < min || max && d > max;
253
+ const isSelected = selected !== null && toDateStr(d) === toDateStr(selected);
254
+ const disabled = min !== null && d < min || max !== null && d > max;
75
255
  const classes = ["date-picker-cell"];
76
256
  if (isOutside) classes.push("is-outside");
77
257
  if (isToday) classes.push("is-today");
78
258
  if (isSelected) classes.push("is-selected");
79
259
  if (disabled) classes.push("is-disabled");
80
- html += `<button type="button" class="${classes.join(" ")}" data-dp-date="${ds}"${disabled ? " disabled" : ""}>${d.getDate()}</button>`;
260
+ const btn = document.createElement("button");
261
+ btn.type = "button";
262
+ btn.className = classes.join(" ");
263
+ btn.textContent = String(d.getDate());
264
+ if (disabled) btn.disabled = true;
265
+ btn.addEventListener("click", () => onPick(d));
266
+ grid.appendChild(btn);
81
267
  }
82
- html += `</div><div class="date-picker-foot">
83
- <button type="button" class="date-picker-today" data-dp-today>\u4ECA\u5929</button>
84
- <button type="button" class="date-picker-clear" data-dp-clear>\u6E05\u9664</button>
85
- </div>`;
86
- panel.innerHTML = html;
87
- panel.querySelector('[data-dp-nav="-1"]')?.addEventListener("click", () => {
88
- ctx.view = new Date(year, month - 1, 15);
89
- renderCalendar(panel, { ...ctx, view: ctx.view });
268
+ const foot = document.createElement("div");
269
+ foot.className = "date-picker-foot";
270
+ const todayBtn = document.createElement("button");
271
+ todayBtn.type = "button";
272
+ todayBtn.className = "date-picker-today";
273
+ todayBtn.textContent = "\u4ECA\u5929";
274
+ const clearBtn = document.createElement("button");
275
+ clearBtn.type = "button";
276
+ clearBtn.className = "date-picker-clear";
277
+ clearBtn.textContent = "\u6E05\u9664";
278
+ foot.append(todayBtn, clearBtn);
279
+ panel.append(head, weekdays, grid, foot);
280
+ prevBtn.addEventListener("click", () => {
281
+ renderCalendar(panel, { ...ctx, view: new Date(year, month - 1, 15) });
90
282
  });
91
- panel.querySelector('[data-dp-nav="1"]')?.addEventListener("click", () => {
92
- ctx.view = new Date(year, month + 1, 15);
93
- renderCalendar(panel, { ...ctx, view: ctx.view });
283
+ nextBtn.addEventListener("click", () => {
284
+ renderCalendar(panel, { ...ctx, view: new Date(year, month + 1, 15) });
94
285
  });
95
- panel.querySelectorAll("[data-dp-date]").forEach((btn) => {
96
- btn.addEventListener("click", () => {
97
- const ds = btn.dataset.dpDate;
98
- if (!ds) return;
99
- const d = parseDateStr(ds);
100
- if (d) onPick(d);
101
- });
102
- });
103
- panel.querySelector("[data-dp-today]")?.addEventListener("click", () => {
286
+ todayBtn.addEventListener("click", () => {
104
287
  onPick(new Date(today.getFullYear(), today.getMonth(), today.getDate()));
105
288
  });
106
- panel.querySelector("[data-dp-clear]")?.addEventListener("click", () => {
289
+ clearBtn.addEventListener("click", () => {
107
290
  onPick(null);
108
291
  });
109
292
  }
@@ -120,11 +303,26 @@ function closePanel() {
120
303
  window.removeEventListener("keydown", escHandler);
121
304
  escHandler = null;
122
305
  }
306
+ if (repositionHandler) {
307
+ window.removeEventListener("scroll", repositionHandler);
308
+ window.removeEventListener("resize", repositionHandler);
309
+ repositionHandler = null;
310
+ }
123
311
  }
312
+ var pickersInit = /* @__PURE__ */ new WeakSet();
124
313
  function setupPicker(container) {
314
+ if (pickersInit.has(container)) return () => {
315
+ };
316
+ pickersInit.add(container);
125
317
  const el = container;
126
- if (el.__icenDatePickerInit) return;
127
- 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
+ ];
128
326
  const fmt = el.dataset.datePickerFormat ?? "yyyy-mm-dd";
129
327
  const minStr = el.dataset.datePickerMin;
130
328
  const maxStr = el.dataset.datePickerMax;
@@ -140,7 +338,12 @@ function setupPicker(container) {
140
338
  trigger = document.createElement("button");
141
339
  trigger.type = "button";
142
340
  trigger.className = "date-picker-trigger";
143
- trigger.innerHTML = `<span class="date-picker-value${initialDate ? "" : " is-empty"}"></span><span class="date-picker-icon">${CAL_ICON}</span>`;
341
+ const value = document.createElement("span");
342
+ value.className = initialDate ? "date-picker-value" : "date-picker-value is-empty";
343
+ const icon = document.createElement("span");
344
+ icon.className = "date-picker-icon";
345
+ icon.appendChild(buildCalIcon());
346
+ trigger.append(value, icon);
144
347
  el.prepend(trigger);
145
348
  }
146
349
  trigger.setAttribute("aria-haspopup", "dialog");
@@ -157,8 +360,9 @@ function setupPicker(container) {
157
360
  if (hidden) {
158
361
  const obs = new MutationObserver(() => paint());
159
362
  obs.observe(hidden, { attributes: true, attributeFilter: ["value"] });
363
+ disposers.push(() => obs.disconnect());
160
364
  }
161
- trigger.addEventListener("click", () => {
365
+ const onTriggerClick = () => {
162
366
  if (openPicker === el) {
163
367
  closePanel();
164
368
  return;
@@ -169,7 +373,7 @@ function setupPicker(container) {
169
373
  const panel = ensurePanel();
170
374
  panel.hidden = false;
171
375
  const selected = hidden?.value ? parseDateStr(hidden.value) : null;
172
- const view = selected ?? /* @__PURE__ */ new Date();
376
+ const view = selected ? new Date(selected.getTime()) : /* @__PURE__ */ new Date();
173
377
  view.setDate(15);
174
378
  positionPanel(panel, trigger);
175
379
  renderCalendar(panel, {
@@ -179,17 +383,9 @@ function setupPicker(container) {
179
383
  max,
180
384
  weekStart,
181
385
  onPick: (d) => {
182
- if (d === null) {
183
- if (hidden) {
184
- hidden.value = "";
185
- hidden.dispatchEvent(new Event("change", { bubbles: true }));
186
- }
187
- } else {
188
- if (hidden) {
189
- hidden.value = toDateStr(d);
190
- hidden.dispatchEvent(new Event("change", { bubbles: true }));
191
- }
192
- }
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 }));
193
389
  paint();
194
390
  closePanel();
195
391
  }
@@ -208,20 +404,31 @@ function setupPicker(container) {
208
404
  }
209
405
  };
210
406
  window.addEventListener("keydown", escHandler);
211
- const reposition = () => {
212
- if (openPicker === el && panel && !panel.hidden) positionPanel(panel, trigger);
407
+ repositionHandler = () => {
408
+ if (openPicker === el && !panel.hidden) positionPanel(panel, trigger);
213
409
  };
214
- window.addEventListener("scroll", reposition, { passive: true, once: false });
215
- window.addEventListener("resize", reposition, { passive: true });
216
- });
410
+ window.addEventListener("scroll", repositionHandler, { passive: true });
411
+ window.addEventListener("resize", repositionHandler, { passive: true });
412
+ };
413
+ trigger.addEventListener("click", onTriggerClick, { signal: ac.signal });
414
+ return () => {
415
+ for (const d of disposers) d();
416
+ };
217
417
  }
218
- function initDatePicker(root = document) {
219
- if (typeof document === "undefined") return;
220
- if (typeof window === "undefined") return;
418
+ function initDatePicker(root) {
419
+ if (typeof document === "undefined") return () => {
420
+ };
421
+ if (typeof window === "undefined") return () => {
422
+ };
423
+ const scope = root ?? document;
221
424
  const pickers = [];
222
- if (root instanceof Element && root.matches(".date-picker[data-date-picker]")) pickers.push(root);
223
- pickers.push(...Array.from(root.querySelectorAll(".date-picker[data-date-picker]")));
224
- for (const p of pickers) setupPicker(p);
425
+ if (scope instanceof Element && scope.matches(".date-picker[data-date-picker]")) pickers.push(scope);
426
+ pickers.push(...Array.from(scope.querySelectorAll(".date-picker[data-date-picker]")));
427
+ const disposers = [];
428
+ for (const p of pickers) disposers.push(setupPicker(p));
429
+ return () => {
430
+ for (const d of disposers) d();
431
+ };
225
432
  }
226
433
  export {
227
434
  initDatePicker
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 绑定全部 [data-dropdown-trigger](document 级委托,重复调用安全)。
3
+ * 返回销毁函数:摘除 document 委托并复位幂等标记(销毁后可重新 init);
4
+ * 销毁时若有面板开着会顺带收起。SSR 下返回 no-op。
5
+ */
6
+ declare function initDropdown(root?: ParentNode): () => void;
7
+
8
+ export { initDropdown };