@sanguo-dev/calendar-ui 0.0.0-stage → 0.0.2

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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1079 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ // src/utils/dom.ts
3
+ /** 简单建节点:文本一律走 textContent,避免节假日名里的特殊字符被当成 HTML */
4
+ const node = function(tag, className, text) {
5
+ const el = document.createElement(tag);
6
+ if (className) el.className = className;
7
+ if (text !== void 0) el.textContent = text;
8
+ return el;
9
+ };
10
+ // src/utils/motion.ts
11
+ /** 是否开启了「减少动态效果」;jsdom 等环境没有 matchMedia 时当作未开启 */
12
+ const prefersReducedMotion = function() {
13
+ return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
14
+ };
15
+ /** 把 CSS 里的时长读成毫秒,支持 `200ms` / `0.2s` / 纯数字;非法值回退 */
16
+ const parseCssDuration = function(value, fallback) {
17
+ const match = /^\s*(-?\d*\.?\d+)\s*(ms|s)?\s*$/i.exec(value);
18
+ if (match === null) return fallback;
19
+ const amount = Number(match[1]);
20
+ if (!Number.isFinite(amount) || amount < 0) return fallback;
21
+ return match[2]?.toLowerCase() === "s" ? amount * 1e3 : amount;
22
+ };
23
+ /** 当前元素上的 CSS 变量优先,CSS 变量没有声明时用 JS 选项 */
24
+ const readCssDuration = function(style, name, fallback) {
25
+ const value = style.getPropertyValue(name).trim();
26
+ return value === "" ? fallback : parseCssDuration(value, fallback);
27
+ };
28
+ const readCssEasing = function(style, name, fallback) {
29
+ const value = style.getPropertyValue(name).trim();
30
+ return value === "" ? fallback : value;
31
+ };
32
+ /** WAAPI 的 easing 不认非法值会抛错,这里退回到 ease */
33
+ const normalizeEasing = function(easing) {
34
+ if (typeof CSS === "undefined" || typeof CSS.supports !== "function") return easing;
35
+ return CSS.supports("animation-timing-function", easing) ? easing : "ease";
36
+ };
37
+ // src/components/day.ts
38
+ /** 周末判断:只看公历星期,与节日 / 调休数据无关 */
39
+ const isWeekend = function(year, month, day) {
40
+ const weekday = new Date(Date.UTC(year, month - 1, day)).getUTCDay();
41
+ return weekday === 0 || weekday === 6;
42
+ };
43
+ /**
44
+ * 建一个空的日期格
45
+ *
46
+ * 节点只建一次,之后靠 setDay / setDayState 更新,换月不再分配新节点。
47
+ */
48
+ const createDayCell = function() {
49
+ const cell = node("button", "cui__day");
50
+ cell.type = "button";
51
+ const tag = node("span", "cui__day-tag");
52
+ tag.hidden = true;
53
+ tag.setAttribute("aria-hidden", "true");
54
+ cell.append(tag, node("span", "cui__day-num"), node("span", "cui__day-sub"));
55
+ return cell;
56
+ };
57
+ /** 更新格子的日期内容;同一日期不重复写文本,只刷新状态 */
58
+ const setDay = function(cell, day) {
59
+ const key = `${day.year}-${day.month}-${day.day}`;
60
+ if (cell.dataset.date !== key) {
61
+ cell.dataset.date = key;
62
+ const tag = cell.firstElementChild;
63
+ const num = tag.nextElementSibling;
64
+ const sub = num.nextElementSibling;
65
+ const badge = day.badge;
66
+ tag.textContent = badge?.text ?? "";
67
+ tag.hidden = badge === null;
68
+ if (badge?.tone !== void 0) {
69
+ tag.dataset.tone = badge.tone;
70
+ cell.dataset.tone = badge.tone;
71
+ } else {
72
+ delete tag.dataset.tone;
73
+ delete cell.dataset.tone;
74
+ }
75
+ num.textContent = String(day.day);
76
+ sub.textContent = day.subtitle;
77
+ cell.classList.toggle("is-weekend", isWeekend(day.year, day.month, day.day));
78
+ }
79
+ setDayState(cell, day);
80
+ };
81
+ /** 只更新状态类,不动文本;选中、hover 预览这类高频操作走这里 */
82
+ const setDayState = function(cell, day) {
83
+ cell.classList.toggle("is-outside", !day.inCurrentMonth);
84
+ cell.classList.toggle("is-today", day.isToday);
85
+ cell.classList.toggle("is-selected", day.isSelected);
86
+ cell.classList.toggle("is-range-start", day.isRangeStart);
87
+ cell.classList.toggle("is-range-end", day.isRangeEnd);
88
+ cell.classList.toggle("is-in-range", day.isInRange);
89
+ cell.classList.toggle("is-disabled", day.isDisabled);
90
+ cell.disabled = day.isDisabled;
91
+ cell.tabIndex = day.inCurrentMonth && !day.isDisabled ? 0 : -1;
92
+ cell.setAttribute("aria-label", day.ariaLabel);
93
+ if (day.isDisabled) cell.setAttribute("aria-disabled", "true");
94
+ else cell.removeAttribute("aria-disabled");
95
+ if (day.isSelected) cell.setAttribute("aria-pressed", "true");
96
+ else cell.removeAttribute("aria-pressed");
97
+ };
98
+ // src/components/grid.ts
99
+ /** 固定 6 行 × 7 列 */
100
+ const CELL_COUNT = 42;
101
+ const ROW_COUNT = 6;
102
+ /** 读一行当前的视觉尺寸(动画进行中时读到的是动画值) */
103
+ const readRowMetrics = function(row) {
104
+ const rect = row.getBoundingClientRect();
105
+ const style = getComputedStyle(row);
106
+ const margin = Number.parseFloat(style.marginBottom);
107
+ return {
108
+ height: rect.height,
109
+ opacity: Number.parseFloat(style.opacity) || 0,
110
+ marginBottom: Number.isFinite(margin) ? margin : 0
111
+ };
112
+ };
113
+ /**
114
+ * 日期网格:42 个格子只建一次,之后按索引复用
115
+ *
116
+ * - render:换月时更新日期/副标题 + 状态
117
+ * - updateState:只切选中/今天等状态类,不动文本
118
+ * - setVisibleRows:折叠时按行动画高度,其余行收起
119
+ *
120
+ * 行高动画必须按真实像素走:CSS 的 max-height 过渡从 200px 收到 0,
121
+ * 真正可见的变化只落在缓动的最后 20%,看起来就是「先不动、再突然合上」。
122
+ *
123
+ * 没有做“按日期复用 + DOM 重排”:42 个节点的移动比 42 次文本写入更贵,
124
+ * 实测换月反而从 0.60ms 退化到 0.93ms。
125
+ */
126
+ const createGrid = function(options) {
127
+ const el = node("div", "cui__grid");
128
+ const rowEls = Array.from({ length: ROW_COUNT }, () => node("div", "cui__week"));
129
+ const cells = Array.from({ length: CELL_COUNT }, () => createDayCell());
130
+ rowEls.forEach((row, index) => {
131
+ row.append(...cells.slice(index * 7, index * 7 + 7));
132
+ });
133
+ el.append(...rowEls);
134
+ let days = [];
135
+ /** 上一次应用的行集合,重复渲染时直接跳过 */
136
+ let visibleKey = null;
137
+ /** 每一行正在跑的高度动画;再次切换时从当前可见高度接着走 */
138
+ const rowAnimations = /* @__PURE__ */ new Map();
139
+ const onClick = (event) => {
140
+ const cell = event.target.closest(".cui__day");
141
+ if (!cell) return;
142
+ const picked = days[cells.indexOf(cell)];
143
+ if (picked && !picked.isDisabled) options.onSelect(picked);
144
+ };
145
+ const applyRowState = (row, visible) => {
146
+ row.classList.toggle("is-collapsed-hidden", !visible);
147
+ row.toggleAttribute("inert", !visible);
148
+ if (visible) row.removeAttribute("aria-hidden");
149
+ else row.setAttribute("aria-hidden", "true");
150
+ };
151
+ const cancelRowAnimations = () => {
152
+ rowAnimations.forEach((animation) => {
153
+ try {
154
+ animation.cancel();
155
+ } catch {}
156
+ });
157
+ rowAnimations.clear();
158
+ };
159
+ const applyInstant = (visible) => {
160
+ rowEls.forEach((row, index) => {
161
+ applyRowState(row, visible === null || visible.has(index));
162
+ });
163
+ };
164
+ const animateRows = (visible) => {
165
+ const firstRow = rowEls[0];
166
+ const gridStyle = getComputedStyle(el);
167
+ const duration = readCssDuration(gridStyle, "--cui-collapse-duration", 200);
168
+ const easing = readCssEasing(gridStyle, "--cui-collapse-easing", "ease");
169
+ if (typeof firstRow.animate !== "function" || prefersReducedMotion() || duration === 0) {
170
+ cancelRowAnimations();
171
+ applyInstant(visible);
172
+ return;
173
+ }
174
+ const before = rowEls.map(readRowMetrics);
175
+ cancelRowAnimations();
176
+ applyInstant(visible);
177
+ const after = rowEls.map(readRowMetrics);
178
+ rowEls.forEach((row, index) => {
179
+ const from = before[index];
180
+ const to = after[index];
181
+ if (Math.abs(from.height - to.height) < .5) return;
182
+ const animation = row.animate([{
183
+ height: `${from.height}px`,
184
+ opacity: from.opacity,
185
+ marginBottom: `${from.marginBottom}px`
186
+ }, {
187
+ height: `${to.height}px`,
188
+ opacity: to.opacity,
189
+ marginBottom: `${to.marginBottom}px`
190
+ }], {
191
+ duration,
192
+ easing,
193
+ fill: "none"
194
+ });
195
+ rowAnimations.set(row, animation);
196
+ animation.finished.then(() => {
197
+ if (rowAnimations.get(row) === animation) rowAnimations.delete(row);
198
+ }).catch(() => {});
199
+ });
200
+ };
201
+ el.addEventListener("click", onClick);
202
+ return {
203
+ el,
204
+ render: (next) => {
205
+ days = next;
206
+ for (let i = 0; i < CELL_COUNT; i++) {
207
+ const day = next[i];
208
+ if (day) setDay(cells[i], day);
209
+ }
210
+ },
211
+ updateState: (next) => {
212
+ days = next;
213
+ for (let i = 0; i < CELL_COUNT; i++) {
214
+ const day = next[i];
215
+ if (day) setDayState(cells[i], day);
216
+ }
217
+ },
218
+ setVisibleRows: (rows, animate = true) => {
219
+ const visible = rows === null ? null : new Set(rows);
220
+ const key = visible === null ? "*" : [...visible].sort((a, b) => a - b).join(",");
221
+ if (!animate) {
222
+ cancelRowAnimations();
223
+ applyInstant(visible);
224
+ visibleKey = key;
225
+ return;
226
+ }
227
+ if (key === visibleKey) return;
228
+ visibleKey = key;
229
+ animateRows(visible);
230
+ },
231
+ destroy: () => {
232
+ cancelRowAnimations();
233
+ el.removeEventListener("click", onClick);
234
+ }
235
+ };
236
+ };
237
+ // src/components/nav.ts
238
+ /** 头部左右切换按钮 */
239
+ const createNav = function(options) {
240
+ const el = node("button", "cui__nav", options.label);
241
+ el.type = "button";
242
+ el.setAttribute("aria-label", options.ariaLabel);
243
+ el.addEventListener("click", options.onClick);
244
+ return {
245
+ el,
246
+ setDisabled: (disabled) => {
247
+ el.disabled = disabled;
248
+ if (disabled) el.setAttribute("aria-disabled", "true");
249
+ else el.removeAttribute("aria-disabled");
250
+ },
251
+ destroy: () => el.removeEventListener("click", options.onClick)
252
+ };
253
+ };
254
+ // src/components/header.ts
255
+ /** 头部:默认是 ‹ 年月 ›,也可以交给 renderer 完全自定义 */
256
+ const createHeader = function(options) {
257
+ const el = node("div", "cui__header");
258
+ const title = node("div", "cui__title");
259
+ const prev = createNav({
260
+ label: "‹",
261
+ ariaLabel: options.locale.prevMonth,
262
+ onClick: options.onPrev
263
+ });
264
+ const next = createNav({
265
+ label: "›",
266
+ ariaLabel: options.locale.nextMonth,
267
+ onClick: options.onNext
268
+ });
269
+ let year = options.year;
270
+ let month = options.month;
271
+ const titleText = () => options.title ? options.title(year, month) : options.locale.monthTitle(year, month);
272
+ const renderDefault = () => {
273
+ if (el.childElementCount === 0) el.append(prev.el, title, next.el);
274
+ const nextTitle = titleText();
275
+ if (title.textContent !== nextTitle) title.textContent = nextTitle;
276
+ };
277
+ const renderCustom = () => {
278
+ el.replaceChildren(options.renderer({
279
+ year,
280
+ month,
281
+ locale: options.locale,
282
+ prevMonth: options.onPrev,
283
+ nextMonth: options.onNext
284
+ }));
285
+ };
286
+ const render = () => {
287
+ if (options.renderer) renderCustom();
288
+ else renderDefault();
289
+ };
290
+ render();
291
+ return {
292
+ el,
293
+ update: (nextYear, nextMonth) => {
294
+ year = nextYear;
295
+ month = nextMonth;
296
+ render();
297
+ },
298
+ setNavDisabled: (prevDisabled, nextDisabled) => {
299
+ prev.setDisabled(prevDisabled);
300
+ next.setDisabled(nextDisabled);
301
+ },
302
+ destroy: () => {
303
+ prev.destroy();
304
+ next.destroy();
305
+ }
306
+ };
307
+ };
308
+ // src/components/weekdays.ts
309
+ /** 星期表头,按一周起始日轮转 */
310
+ const createWeekdays = function(weekStartsOn, locale) {
311
+ const el = node("div", "cui__weekdays");
312
+ for (let i = 0; i < 7; i++) {
313
+ const weekday = (weekStartsOn + i) % 7;
314
+ const cell = node("div", "cui__weekday", locale.weekdays[weekday]);
315
+ cell.title = locale.weekdaysLong[weekday];
316
+ el.appendChild(cell);
317
+ }
318
+ return { el };
319
+ };
320
+ // src/locales/en-US.ts
321
+ const MONTHS = [
322
+ "January",
323
+ "February",
324
+ "March",
325
+ "April",
326
+ "May",
327
+ "June",
328
+ "July",
329
+ "August",
330
+ "September",
331
+ "October",
332
+ "November",
333
+ "December"
334
+ ];
335
+ /** 英文 */
336
+ const enUS = {
337
+ name: "en-US",
338
+ weekdays: [
339
+ "Sun",
340
+ "Mon",
341
+ "Tue",
342
+ "Wed",
343
+ "Thu",
344
+ "Fri",
345
+ "Sat"
346
+ ],
347
+ weekdaysLong: [
348
+ "Sunday",
349
+ "Monday",
350
+ "Tuesday",
351
+ "Wednesday",
352
+ "Thursday",
353
+ "Friday",
354
+ "Saturday"
355
+ ],
356
+ monthTitle: (year, month) => `${MONTHS[month - 1] ?? month} ${year}`,
357
+ prevMonth: "Previous month",
358
+ nextMonth: "Next month",
359
+ collapse: "Collapse calendar",
360
+ expand: "Expand calendar"
361
+ };
362
+ // src/locales/zh-CN.ts
363
+ /** 简体中文:默认语言 */
364
+ const zhCN = {
365
+ name: "zh-CN",
366
+ weekdays: [
367
+ "日",
368
+ "一",
369
+ "二",
370
+ "三",
371
+ "四",
372
+ "五",
373
+ "六"
374
+ ],
375
+ weekdaysLong: [
376
+ "星期日",
377
+ "星期一",
378
+ "星期二",
379
+ "星期三",
380
+ "星期四",
381
+ "星期五",
382
+ "星期六"
383
+ ],
384
+ monthTitle: (year, month) => `${year} 年 ${month} 月`,
385
+ prevMonth: "上个月",
386
+ nextMonth: "下个月",
387
+ collapse: "收起日历",
388
+ expand: "展开日历"
389
+ };
390
+ // src/locales/index.ts
391
+ /** 内置语言包,key 可直接传给 createCalendarUI 的 locale 选项 */
392
+ const locales = {
393
+ "zh-CN": zhCN,
394
+ "en-US": enUS
395
+ };
396
+ /** 把 "zh-CN" 这类名称或自定义语言包,统一解析成语言包对象 */
397
+ const resolveLocale = function(locale = "zh-CN") {
398
+ if (typeof locale !== "string") return locale;
399
+ return locales[locale];
400
+ };
401
+ // src/utils/date.ts
402
+ /** 日期字符串:YYYY-M-D,分隔符必须一致 */
403
+ const DATE_INPUT_PATTERN = /^(\d{4})([-/])(\d{1,2})\2(\d{1,2})$/;
404
+ /** 去掉时分秒,只保留本地年月日 */
405
+ const toDateOnly = function(date) {
406
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
407
+ };
408
+ /**
409
+ * 解析用户传入的日期
410
+ *
411
+ * 支持 Date,以及 `2026-10-01`、`2026-10-1`、`2026/10/01`、`2026/10/1`。
412
+ * 不交给 `new Date(string)`,避免时区/格式歧义。
413
+ */
414
+ const parseDateInput = function(value, path = "date") {
415
+ if (value instanceof Date) {
416
+ if (Number.isNaN(value.getTime())) throw new TypeError(`[calendar-ui] ${path} 必须是有效的 Date`);
417
+ return toDateOnly(value);
418
+ }
419
+ if (typeof value !== "string") throw new TypeError(`[calendar-ui] ${path} 必须是 Date 或 YYYY-MM-DD / YYYY/MM/DD 字符串`);
420
+ const match = DATE_INPUT_PATTERN.exec(value.trim());
421
+ if (!match) throw new TypeError(`[calendar-ui] ${path} 必须是 Date 或 YYYY-MM-DD / YYYY/MM/DD 字符串,收到 ${JSON.stringify(value)}`);
422
+ const year = Number(match[1]);
423
+ const month = Number(match[3]);
424
+ const day = Number(match[4]);
425
+ if (month < 1 || month > 12) throw new RangeError(`[calendar-ui] ${path} 月份必须在 1-12 之间`);
426
+ const date = /* @__PURE__ */ new Date(0);
427
+ date.setHours(0, 0, 0, 0);
428
+ date.setFullYear(year, month - 1, day);
429
+ if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) throw new RangeError(`[calendar-ui] ${path} 不是有效日期: ${value}`);
430
+ return date;
431
+ };
432
+ /** 把日期转成可比较的数字 key:YYYYMMDD */
433
+ const dateKey = function(date) {
434
+ return date.getFullYear() * 1e4 + (date.getMonth() + 1) * 100 + date.getDate();
435
+ };
436
+ /** 加减天数,按本地日历计算 */
437
+ const addDays = function(date, amount) {
438
+ const next = toDateOnly(date);
439
+ next.setDate(next.getDate() + amount);
440
+ return next;
441
+ };
442
+ /** 比较两个日期,只比年月日 */
443
+ const compareDay = function(a, b) {
444
+ return dateKey(a) - dateKey(b);
445
+ };
446
+ /** 同一天(只比年月日,忽略时分秒) */
447
+ const sameDay = function(a, b) {
448
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
449
+ };
450
+ /** 把年月规范成合法值,月份越界时自动进位/退位 */
451
+ const normalizeMonth = function(year, month) {
452
+ const date = new Date(year, month - 1, 1);
453
+ return {
454
+ year: date.getFullYear(),
455
+ month: date.getMonth() + 1
456
+ };
457
+ };
458
+ // src/utils/days.ts
459
+ /** 某个年月 1 号是星期几(0 为周日) */
460
+ const firstWeekdayOf = function(year, month) {
461
+ return new Date(year, month - 1, 1).getDay();
462
+ };
463
+ /** 生成 6 行 × 7 列共 42 个公历日期,包含上下月补位格 */
464
+ const buildMonthDates = function(year, month, weekStartsOn) {
465
+ const offset = (firstWeekdayOf(year, month) - weekStartsOn + 7) % 7;
466
+ const days = [];
467
+ for (let i = 0; i < 42; i++) {
468
+ const date = new Date(year, month - 1, i - offset + 1);
469
+ days.push({
470
+ year: date.getFullYear(),
471
+ month: date.getMonth() + 1,
472
+ day: date.getDate(),
473
+ date,
474
+ inCurrentMonth: date.getMonth() + 1 === month && date.getFullYear() === year
475
+ });
476
+ }
477
+ return days;
478
+ };
479
+ /** 生成 42 个格子的数据;今天/选中每次现算,适配器只负责装饰 */
480
+ const buildDays = function(options) {
481
+ const dates = buildMonthDates(options.year, options.month, options.weekStartsOn);
482
+ const decorations = options.adapter ? options.adapter.decorateMonth({
483
+ year: options.year,
484
+ month: options.month,
485
+ weekStartsOn: options.weekStartsOn,
486
+ today: options.today,
487
+ locale: options.locale,
488
+ days: dates
489
+ }) : [];
490
+ return dates.map((date, index) => {
491
+ const decoration = decorations[index] ?? null;
492
+ const badge = decoration?.badge ?? null;
493
+ const adapterSubtitle = decoration?.subtitle ?? "";
494
+ const customSubtitle = options.subtitle?.(date);
495
+ const subtitle = customSubtitle === null || customSubtitle === void 0 ? adapterSubtitle : customSubtitle;
496
+ const key = `${date.year}-${date.month}-${date.day}`;
497
+ const isDisabled = options.isDisabled?.(date.date) ?? false;
498
+ const rangeStart = options.rangeStart ?? null;
499
+ const rangeEnd = options.rangeEnd ?? null;
500
+ const isRangeStart = rangeStart !== null && sameDay(date.date, rangeStart);
501
+ const isRangeEnd = rangeEnd !== null && sameDay(date.date, rangeEnd);
502
+ const isInRange = rangeStart !== null && rangeEnd !== null && compareDay(date.date, rangeStart) >= 0 && compareDay(date.date, rangeEnd) <= 0;
503
+ return {
504
+ ...date,
505
+ isToday: sameDay(date.date, options.today),
506
+ isSelected: options.selected !== null && sameDay(date.date, options.selected) || isRangeStart || isRangeEnd,
507
+ isDisabled,
508
+ isRangeStart,
509
+ isRangeEnd,
510
+ isInRange,
511
+ subtitle,
512
+ badge,
513
+ data: decoration?.data ?? null,
514
+ ariaLabel: decoration?.ariaLabel ?? [
515
+ key,
516
+ badge?.text ?? "",
517
+ subtitle,
518
+ isDisabled ? "不可选" : ""
519
+ ].filter((part) => part !== "").join(" ")
520
+ };
521
+ });
522
+ };
523
+ // src/calendar.ts
524
+ /** 没有显式 min/max 时,搜索可选日期的兜底范围 */
525
+ const SEARCH_MIN = new Date(1900, 0, 1);
526
+ const SEARCH_MAX = new Date(2100, 11, 31);
527
+ const readDate = function(value, path) {
528
+ if (value === null || value === void 0) return null;
529
+ return parseDateInput(value, path);
530
+ };
531
+ const monthIndexOf = function(year, month) {
532
+ return year * 12 + (month - 1);
533
+ };
534
+ const monthFromIndex = function(index) {
535
+ return {
536
+ year: Math.floor(index / 12),
537
+ month: index % 12 + 1
538
+ };
539
+ };
540
+ /** 在 [min, max] 内找离 desired 最近的可选日期 */
541
+ const findSelectable = function(desired, minDate, maxDate, disabled) {
542
+ const blocked = function(date) {
543
+ const key = dateKey(date);
544
+ return minDate !== null && compareDay(date, minDate) < 0 || maxDate !== null && compareDay(date, maxDate) > 0 || disabled.has(key);
545
+ };
546
+ let candidate = toDateOnly(desired);
547
+ if (minDate !== null && compareDay(candidate, minDate) < 0) candidate = minDate;
548
+ if (maxDate !== null && compareDay(candidate, maxDate) > 0) candidate = maxDate;
549
+ if (!blocked(candidate)) return candidate;
550
+ const lower = minDate ?? SEARCH_MIN;
551
+ const upper = maxDate ?? SEARCH_MAX;
552
+ const maxOffset = Math.max(0, compareDay(upper, lower));
553
+ for (let offset = 1; offset <= maxOffset; offset++) {
554
+ const forward = addDays(candidate, offset);
555
+ if (compareDay(forward, upper) <= 0 && !blocked(forward)) return forward;
556
+ const backward = addDays(candidate, -offset);
557
+ if (compareDay(backward, lower) >= 0 && !blocked(backward)) return backward;
558
+ }
559
+ throw new RangeError("[calendar-ui] 当前日期约束下没有可选日期");
560
+ };
561
+ /**
562
+ * 在容器里创建一个日历
563
+ *
564
+ * 只依赖 DOM,不依赖任何框架;重复调用 createCalendarUI 会各自独立。
565
+ * 不传 adapter 时就是纯公历,适配器负责提供副标题、标记和透传数据。
566
+ */
567
+ function createCalendarUI(container, options = {}) {
568
+ const weekStartsOn = options.weekStartsOn ?? 1;
569
+ const locale = resolveLocale(options.locale);
570
+ const adapter = options.adapter;
571
+ let theme = options.theme ?? "auto";
572
+ const today = /* @__PURE__ */ new Date();
573
+ const readDuration = function(value, fallback, path) {
574
+ const duration = value ?? fallback;
575
+ if (!Number.isFinite(duration) || duration < 0) throw new TypeError(`[calendar-ui] ${path} 必须是大于等于 0 的数字`);
576
+ return duration;
577
+ };
578
+ const readEasing = function(value, path) {
579
+ const easing = value ?? "ease";
580
+ if (typeof easing !== "string" || easing.trim() === "") throw new TypeError(`[calendar-ui] ${path} 必须是非空字符串`);
581
+ return easing;
582
+ };
583
+ const animation = options.animation;
584
+ if (animation !== void 0 && (animation === null || typeof animation !== "object")) throw new TypeError("[calendar-ui] animation 必须是对象");
585
+ const slide = animation?.slide ?? null;
586
+ const collapseOptions = animation?.collapse ?? null;
587
+ const monthTransition = slide === null ? "none" : "slide";
588
+ const monthDuration = readDuration(slide?.duration, 220, "animation.slide.duration");
589
+ const monthEasing = readEasing(slide?.easing, "animation.slide.easing");
590
+ const collapsible = collapseOptions !== null;
591
+ const collapseDuration = readDuration(collapseOptions?.duration, 200, "animation.collapse.duration");
592
+ const collapseEasing = readEasing(collapseOptions?.easing, "animation.collapse.easing");
593
+ const mode = options.mode ?? "single";
594
+ if (mode !== "single" && mode !== "range") throw new TypeError("[calendar-ui] mode 必须是 single 或 range");
595
+ const requestedMonths = options.months ?? 1;
596
+ if (requestedMonths !== 1 && requestedMonths !== 2) throw new RangeError("[calendar-ui] months 只能是 1 或 2");
597
+ let minDate = readDate(options.minDate, "minDate");
598
+ let maxDate = readDate(options.maxDate, "maxDate");
599
+ if (minDate !== null && maxDate !== null && compareDay(minDate, maxDate) > 0) throw new RangeError("[calendar-ui] minDate 不能晚于 maxDate");
600
+ const disabledDates = options.disabledDates ?? [];
601
+ if (!Array.isArray(disabledDates)) throw new TypeError("[calendar-ui] disabledDates 必须是 Date 数组");
602
+ const disabledSet = /* @__PURE__ */ new Set();
603
+ disabledDates.forEach((value, index) => {
604
+ const date = readDate(value, `disabledDates[${index}]`);
605
+ if (date !== null) disabledSet.add(dateKey(date));
606
+ });
607
+ const isDisabled = function(date) {
608
+ const key = dateKey(date);
609
+ return minDate !== null && key < dateKey(minDate) || maxDate !== null && key > dateKey(maxDate) || disabledSet.has(key);
610
+ };
611
+ const initial = findSelectable(readDate(options.initialDate ?? today, "initialDate"), minDate, maxDate, disabledSet);
612
+ let monthCount = mode === "range" ? requestedMonths : 1;
613
+ if (monthCount === 2 && minDate !== null && maxDate !== null && monthIndexOf(maxDate.getFullYear(), maxDate.getMonth() + 1) - monthIndexOf(minDate.getFullYear(), minDate.getMonth() + 1) < 1) monthCount = 1;
614
+ let firstYear = initial.getFullYear();
615
+ let firstMonth = initial.getMonth() + 1;
616
+ if (monthCount === 2 && maxDate !== null) {
617
+ const maxFirst = monthIndexOf(maxDate.getFullYear(), maxDate.getMonth() + 1) - 1;
618
+ if (monthIndexOf(firstYear, firstMonth) > maxFirst) {
619
+ const clamped = monthFromIndex(maxFirst);
620
+ firstYear = clamped.year;
621
+ firstMonth = clamped.month;
622
+ }
623
+ }
624
+ const state = {
625
+ year: firstYear,
626
+ month: firstMonth,
627
+ selected: initial
628
+ };
629
+ let rangeStart = null;
630
+ let rangeEnd = null;
631
+ let collapsed = collapsible && collapseOptions?.defaultCollapsed === true;
632
+ let collapseButton = null;
633
+ const root = node("div", "cui");
634
+ root.dataset.theme = theme;
635
+ root.style.setProperty("--cui-month-duration", `${monthDuration}ms`);
636
+ root.style.setProperty("--cui-month-easing", monthEasing);
637
+ root.style.setProperty("--cui-collapse-duration", `${collapseDuration}ms`);
638
+ root.style.setProperty("--cui-collapse-easing", collapseEasing);
639
+ if (monthCount === 2) root.classList.add("cui--range");
640
+ const monthOf = function(offset = 0) {
641
+ return normalizeMonth(state.year, state.month + offset);
642
+ };
643
+ /** 按当前 state 生成某一列的 42 个格子 */
644
+ const currentDays = function(offset = 0) {
645
+ const month = monthOf(offset);
646
+ return buildDays({
647
+ year: month.year,
648
+ month: month.month,
649
+ weekStartsOn,
650
+ today,
651
+ selected: mode === "single" ? state.selected : null,
652
+ rangeStart,
653
+ rangeEnd,
654
+ locale,
655
+ adapter,
656
+ isDisabled,
657
+ subtitle: options.subtitle
658
+ });
659
+ };
660
+ const rangeCopy = function() {
661
+ return {
662
+ start: rangeStart === null ? null : new Date(rangeStart),
663
+ end: rangeEnd === null ? null : new Date(rangeEnd)
664
+ };
665
+ };
666
+ const withRangeFields = function(day) {
667
+ const isStart = rangeStart !== null && sameDay(day.date, rangeStart);
668
+ const isEnd = rangeEnd !== null && sameDay(day.date, rangeEnd);
669
+ return {
670
+ ...day,
671
+ isSelected: isStart || isEnd,
672
+ isRangeStart: isStart,
673
+ isRangeEnd: isEnd,
674
+ isInRange: rangeStart !== null && rangeEnd !== null && compareDay(day.date, rangeStart) >= 0 && compareDay(day.date, rangeEnd) <= 0
675
+ };
676
+ };
677
+ const updateCollapseButton = function() {
678
+ if (collapseButton === null) return;
679
+ collapseButton.textContent = collapsed ? "⌄" : "⌃";
680
+ collapseButton.setAttribute("aria-expanded", String(!collapsed));
681
+ collapseButton.setAttribute("aria-label", collapsed ? locale.expand ?? "展开日历" : locale.collapse ?? "收起日历");
682
+ };
683
+ const updateCollapsedRows = function(dayLists, animate) {
684
+ if (!collapsed) {
685
+ panels.forEach((panel) => panel.grid.setVisibleRows(null, animate));
686
+ return;
687
+ }
688
+ const targets = mode === "range" ? [rangeStart, rangeEnd].filter((date) => date !== null) : [state.selected];
689
+ if (targets.length === 0) targets.push(state.selected);
690
+ panels.forEach((panel, index) => {
691
+ const rows = /* @__PURE__ */ new Set();
692
+ const days = dayLists[index] ?? [];
693
+ targets.forEach((target) => {
694
+ const dayIndex = days.findIndex((day) => sameDay(day.date, target) && (monthCount === 1 || day.inCurrentMonth));
695
+ if (dayIndex >= 0) rows.add(Math.floor(dayIndex / 7));
696
+ });
697
+ if (rows.size === 0) {
698
+ const first = days.findIndex((day) => day.inCurrentMonth);
699
+ rows.add(first >= 0 ? Math.floor(first / 7) : 0);
700
+ }
701
+ panel.grid.setVisibleRows([...rows].sort((a, b) => a - b), animate);
702
+ });
703
+ };
704
+ function applyCollapse(next) {
705
+ if (!collapsible) return;
706
+ collapsed = next;
707
+ updateCollapseButton();
708
+ render();
709
+ }
710
+ function toggleCollapsed() {
711
+ applyCollapse(!collapsed);
712
+ }
713
+ function updatePanelLabels() {
714
+ panelEls.forEach((el, index) => {
715
+ const month = monthOf(index);
716
+ el.setAttribute("aria-label", locale.monthTitle(month.year, month.month));
717
+ });
718
+ }
719
+ function updateNav() {
720
+ const first = monthIndexOf(state.year, state.month);
721
+ const last = first + monthCount - 1;
722
+ const min = minDate === null ? Number.NEGATIVE_INFINITY : monthIndexOf(minDate.getFullYear(), minDate.getMonth() + 1);
723
+ const max = maxDate === null ? Number.POSITIVE_INFINITY : monthIndexOf(maxDate.getFullYear(), maxDate.getMonth() + 1);
724
+ header.setNavDisabled(first <= min, last >= max);
725
+ }
726
+ function render(animateRows = true) {
727
+ header.update(state.year, state.month);
728
+ const dayLists = panels.map((_, index) => currentDays(index));
729
+ panels.forEach((panel, index) => panel.grid.render(dayLists[index]));
730
+ updatePanelLabels();
731
+ updateNav();
732
+ updateCollapsedRows(dayLists, animateRows);
733
+ updateCollapseButton();
734
+ }
735
+ function applyRangeClick(target) {
736
+ const date = toDateOnly(target);
737
+ if (rangeStart === null || rangeEnd !== null) {
738
+ rangeStart = date;
739
+ rangeEnd = null;
740
+ } else if (compareDay(date, rangeStart) < 0) {
741
+ rangeEnd = rangeStart;
742
+ rangeStart = date;
743
+ } else rangeEnd = date;
744
+ render();
745
+ options.onRangeChange?.(rangeCopy());
746
+ }
747
+ /** 处理点选:补位格只改选中,不切换展示月份 */
748
+ function handleSelect(day) {
749
+ if (day.isDisabled) return;
750
+ if (mode === "range") {
751
+ applyRangeClick(day.date);
752
+ options.onSelect?.(withRangeFields(day));
753
+ return;
754
+ }
755
+ state.selected = toDateOnly(day.date);
756
+ if (day.inCurrentMonth) {
757
+ state.year = day.year;
758
+ state.month = day.month;
759
+ }
760
+ if (collapsed) {
761
+ render();
762
+ const days = currentDays();
763
+ options.onSelect?.(days.find((item) => sameDay(item.date, day.date)) ?? day);
764
+ return;
765
+ }
766
+ const days = currentDays();
767
+ panels[0]?.grid.updateState(days);
768
+ options.onSelect?.(days.find((item) => sameDay(item.date, day.date)) ?? day);
769
+ }
770
+ let monthAnimationToken = 0;
771
+ let monthAnimation = null;
772
+ /** 结束翻月动画:各层留在终点,并移除旧月份的克隆层 */
773
+ function stopMonthAnimation() {
774
+ const active = monthAnimation;
775
+ if (active === null) return;
776
+ monthAnimation = null;
777
+ clearTimeout(active.timer);
778
+ active.animations.forEach((animation) => {
779
+ try {
780
+ animation.cancel();
781
+ } catch {}
782
+ });
783
+ active.layers.forEach((layer) => layer.remove());
784
+ panels.forEach((panel) => {
785
+ panel.grid.el.classList.remove("is-month-in-next", "is-month-in-prev", "is-month-out-next", "is-month-out-prev");
786
+ });
787
+ }
788
+ /** 克隆旧月份作为离场层;折叠行在换月后由 syncLayerRows 对齐 */
789
+ function captureMonthSnapshot() {
790
+ return panels.map((panel) => {
791
+ const clone = panel.grid.el.cloneNode(true);
792
+ clone.classList.add("is-month-clone");
793
+ clone.classList.remove("is-month-in-next", "is-month-in-prev", "is-month-out-next", "is-month-out-prev");
794
+ clone.setAttribute("aria-hidden", "true");
795
+ clone.querySelectorAll("button").forEach((button) => {
796
+ button.tabIndex = -1;
797
+ });
798
+ panel.viewport.append(clone);
799
+ return clone;
800
+ });
801
+ }
802
+ /** 离场层和新月份用同一套折叠行,两层高度一致,滑动时不会露出空隙 */
803
+ function syncLayerRows(layers) {
804
+ layers.forEach((layer, index) => {
805
+ const sourceRows = panels[index]?.grid.el.querySelectorAll(".cui__week");
806
+ const layerRows = layer.querySelectorAll(".cui__week");
807
+ sourceRows?.forEach((source, rowIndex) => {
808
+ const target = layerRows[rowIndex];
809
+ if (target === void 0) return;
810
+ const hidden = source.classList.contains("is-collapsed-hidden");
811
+ target.classList.toggle("is-collapsed-hidden", hidden);
812
+ target.toggleAttribute("inert", hidden);
813
+ if (hidden) target.setAttribute("aria-hidden", "true");
814
+ else target.removeAttribute("aria-hidden");
815
+ });
816
+ });
817
+ }
818
+ /**
819
+ * 用 Web Animations 驱动翻月
820
+ *
821
+ * 不靠切换类名重启动画,也就不需要在点击处理里强制同步一次布局;
822
+ * 快速连点时上一段会被 stopMonthAnimation 直接停到终点,不会叠加克隆层。
823
+ */
824
+ function startMonthAnimation(direction, layers, duration, easing) {
825
+ const enter = `translateX(${direction * 100}%)`;
826
+ const leave = `translateX(${-direction * 100}%)`;
827
+ const inClass = direction === 1 ? "is-month-in-next" : "is-month-in-prev";
828
+ const outClass = direction === 1 ? "is-month-out-next" : "is-month-out-prev";
829
+ const animations = [];
830
+ panels.forEach((panel, index) => {
831
+ const grid = panel.grid.el;
832
+ const oldLayer = layers[index] ?? null;
833
+ grid.classList.add(inClass);
834
+ oldLayer?.classList.add(outClass);
835
+ if (typeof grid.animate !== "function") return;
836
+ animations.push(grid.animate([{ transform: enter }, { transform: "translateX(0)" }], {
837
+ duration,
838
+ easing,
839
+ fill: "both"
840
+ }));
841
+ if (oldLayer !== null) animations.push(oldLayer.animate([{ transform: "translateX(0)" }, { transform: leave }], {
842
+ duration,
843
+ easing,
844
+ fill: "both"
845
+ }));
846
+ });
847
+ const token = ++monthAnimationToken;
848
+ const timer = setTimeout(() => {
849
+ if (monthAnimation?.token === token) stopMonthAnimation();
850
+ }, duration + 80);
851
+ monthAnimation = {
852
+ token,
853
+ layers: [...layers],
854
+ animations,
855
+ timer
856
+ };
857
+ }
858
+ function goTo(year, month) {
859
+ let next = normalizeMonth(year, month);
860
+ if (minDate !== null) {
861
+ const min = monthIndexOf(minDate.getFullYear(), minDate.getMonth() + 1);
862
+ if (monthIndexOf(next.year, next.month) < min) next = monthFromIndex(min);
863
+ }
864
+ if (maxDate !== null) {
865
+ const maxFirst = monthIndexOf(maxDate.getFullYear(), maxDate.getMonth() + 1) - (monthCount - 1);
866
+ if (monthIndexOf(next.year, next.month) > maxFirst) next = monthFromIndex(maxFirst);
867
+ }
868
+ const currentIndex = monthIndexOf(state.year, state.month);
869
+ const nextIndex = monthIndexOf(next.year, next.month);
870
+ if (nextIndex === currentIndex) return;
871
+ const direction = nextIndex > currentIndex ? 1 : -1;
872
+ const rootStyle = getComputedStyle(root);
873
+ const duration = readCssDuration(rootStyle, "--cui-month-duration", monthDuration);
874
+ const easing = normalizeEasing(readCssEasing(rootStyle, "--cui-month-easing", monthEasing));
875
+ const shouldAnimate = monthTransition === "slide" && duration > 0 && !prefersReducedMotion();
876
+ stopMonthAnimation();
877
+ const layers = shouldAnimate ? captureMonthSnapshot() : [];
878
+ state.year = next.year;
879
+ state.month = next.month;
880
+ render(false);
881
+ syncLayerRows(layers);
882
+ if (shouldAnimate) startMonthAnimation(direction, layers, duration, easing);
883
+ options.onMonthChange?.(state.year, state.month);
884
+ }
885
+ function applyBounds(nextMin, nextMax) {
886
+ if (nextMin !== null && nextMax !== null && compareDay(nextMin, nextMax) > 0) throw new RangeError("[calendar-ui] minDate 不能晚于 maxDate");
887
+ const nextSelected = findSelectable(state.selected, nextMin, nextMax, disabledSet);
888
+ minDate = nextMin;
889
+ maxDate = nextMax;
890
+ state.selected = nextSelected;
891
+ state.year = nextSelected.getFullYear();
892
+ state.month = nextSelected.getMonth() + 1;
893
+ if (monthCount === 2 && maxDate !== null) {
894
+ const maxFirst = monthIndexOf(maxDate.getFullYear(), maxDate.getMonth() + 1) - 1;
895
+ if (monthIndexOf(state.year, state.month) > maxFirst) {
896
+ const clamped = monthFromIndex(maxFirst);
897
+ state.year = clamped.year;
898
+ state.month = clamped.month;
899
+ }
900
+ }
901
+ render();
902
+ }
903
+ const panelEls = [];
904
+ const panels = Array.from({ length: monthCount }, () => {
905
+ const viewport = node("div", "cui__viewport");
906
+ const grid = createGrid({ onSelect: handleSelect });
907
+ viewport.append(grid.el);
908
+ return {
909
+ weekdays: createWeekdays(weekStartsOn, locale),
910
+ viewport,
911
+ grid
912
+ };
913
+ });
914
+ const header = createHeader({
915
+ year: state.year,
916
+ month: state.month,
917
+ locale,
918
+ title: monthCount === 2 ? (year, month) => {
919
+ const next = normalizeMonth(year, month + 1);
920
+ return `${locale.monthTitle(year, month)} – ${locale.monthTitle(next.year, next.month)}`;
921
+ } : void 0,
922
+ onPrev: () => goTo(state.year, state.month - 1),
923
+ onNext: () => goTo(state.year, state.month + 1),
924
+ renderer: options.renderHeader
925
+ });
926
+ header.el.hidden = options.header === false;
927
+ if (monthCount === 1) root.append(header.el, panels[0].weekdays.el, panels[0].viewport);
928
+ else {
929
+ const monthsEl = node("div", "cui__months");
930
+ panels.forEach((panel) => {
931
+ const monthEl = node("div", "cui__month");
932
+ monthEl.setAttribute("role", "group");
933
+ monthEl.append(panel.weekdays.el, panel.viewport);
934
+ monthsEl.append(monthEl);
935
+ panelEls.push(monthEl);
936
+ });
937
+ root.append(header.el, monthsEl);
938
+ }
939
+ if (collapsible) {
940
+ collapseButton = node("button", "cui__collapse");
941
+ collapseButton.type = "button";
942
+ collapseButton.addEventListener("click", toggleCollapsed);
943
+ root.append(collapseButton);
944
+ }
945
+ root.classList.add("cui--no-animation");
946
+ container.appendChild(root);
947
+ render(false);
948
+ root.classList.remove("cui--no-animation");
949
+ return {
950
+ el: root,
951
+ setMonth: goTo,
952
+ getMonth: () => ({
953
+ year: state.year,
954
+ month: state.month
955
+ }),
956
+ prevMonth: () => goTo(state.year, state.month - 1),
957
+ nextMonth: () => goTo(state.year, state.month + 1),
958
+ select: (date) => {
959
+ const target = readDate(date, "select(date)");
960
+ if (target === null || isDisabled(target)) return;
961
+ if (mode === "range") {
962
+ const targetIndex = monthIndexOf(target.getFullYear(), target.getMonth() + 1);
963
+ const first = monthIndexOf(state.year, state.month);
964
+ if (targetIndex < first || targetIndex > first + monthCount - 1) {
965
+ state.year = target.getFullYear();
966
+ state.month = target.getMonth() + 1;
967
+ }
968
+ if (monthCount === 2 && maxDate !== null) {
969
+ const maxFirst = monthIndexOf(maxDate.getFullYear(), maxDate.getMonth() + 1) - 1;
970
+ if (monthIndexOf(state.year, state.month) > maxFirst) {
971
+ const clamped = monthFromIndex(maxFirst);
972
+ state.year = clamped.year;
973
+ state.month = clamped.month;
974
+ }
975
+ }
976
+ applyRangeClick(target);
977
+ const day = buildDays({
978
+ year: target.getFullYear(),
979
+ month: target.getMonth() + 1,
980
+ weekStartsOn,
981
+ today,
982
+ selected: null,
983
+ rangeStart,
984
+ rangeEnd,
985
+ locale,
986
+ adapter,
987
+ isDisabled,
988
+ subtitle: options.subtitle
989
+ }).find((item) => sameDay(item.date, target));
990
+ if (day) options.onSelect?.(withRangeFields(day));
991
+ return;
992
+ }
993
+ state.selected = target;
994
+ state.year = target.getFullYear();
995
+ state.month = target.getMonth() + 1;
996
+ render();
997
+ const day = currentDays().find((item) => sameDay(item.date, target));
998
+ if (day) options.onSelect?.(day);
999
+ },
1000
+ getSelected: () => {
1001
+ if (mode === "range") return rangeStart === null ? null : new Date(rangeStart);
1002
+ return new Date(state.selected);
1003
+ },
1004
+ getRange: rangeCopy,
1005
+ setRange: (start, end) => {
1006
+ const nextStart = readDate(start, "setRange(start)");
1007
+ const nextEnd = end === void 0 ? null : readDate(end, "setRange(end)");
1008
+ if (nextStart === null) {
1009
+ rangeStart = null;
1010
+ rangeEnd = null;
1011
+ } else {
1012
+ if (isDisabled(nextStart) || nextEnd !== null && isDisabled(nextEnd)) return;
1013
+ if (nextEnd === null) {
1014
+ rangeStart = nextStart;
1015
+ rangeEnd = null;
1016
+ } else if (compareDay(nextStart, nextEnd) <= 0) {
1017
+ rangeStart = nextStart;
1018
+ rangeEnd = nextEnd;
1019
+ } else {
1020
+ rangeStart = nextEnd;
1021
+ rangeEnd = nextStart;
1022
+ }
1023
+ }
1024
+ render();
1025
+ options.onRangeChange?.(rangeCopy());
1026
+ },
1027
+ clearRange: () => {
1028
+ rangeStart = null;
1029
+ rangeEnd = null;
1030
+ render();
1031
+ options.onRangeChange?.(rangeCopy());
1032
+ },
1033
+ show: () => {
1034
+ root.hidden = false;
1035
+ },
1036
+ hide: () => {
1037
+ root.hidden = true;
1038
+ },
1039
+ toggle: () => {
1040
+ root.hidden = !root.hidden;
1041
+ },
1042
+ isVisible: () => !root.hidden,
1043
+ showHeader: () => {
1044
+ header.el.hidden = false;
1045
+ },
1046
+ hideHeader: () => {
1047
+ header.el.hidden = true;
1048
+ },
1049
+ toggleHeader: () => {
1050
+ header.el.hidden = !header.el.hidden;
1051
+ },
1052
+ isHeaderVisible: () => !header.el.hidden,
1053
+ getMinDate: () => minDate === null ? null : new Date(minDate),
1054
+ setMinDate: (date) => {
1055
+ applyBounds(readDate(date, "setMinDate(date)"), maxDate);
1056
+ },
1057
+ getMaxDate: () => maxDate === null ? null : new Date(maxDate),
1058
+ setMaxDate: (date) => {
1059
+ applyBounds(minDate, readDate(date, "setMaxDate(date)"));
1060
+ },
1061
+ getTheme: () => theme,
1062
+ setTheme: (next) => {
1063
+ theme = next;
1064
+ root.dataset.theme = next;
1065
+ },
1066
+ destroy: () => {
1067
+ stopMonthAnimation();
1068
+ collapseButton?.removeEventListener("click", toggleCollapsed);
1069
+ header.destroy();
1070
+ panels.forEach((panel) => panel.grid.destroy());
1071
+ root.remove();
1072
+ }
1073
+ };
1074
+ }
1075
+ exports.createCalendarUI = createCalendarUI;
1076
+ exports.enUS = enUS;
1077
+ exports.locales = locales;
1078
+ exports.resolveLocale = resolveLocale;
1079
+ exports.zhCN = zhCN;