@bug-on/m3-expressive 1.0.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 (98) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +110 -0
  3. package/dist/assets/fonts/GoogleSansFlex-VariableFont.woff2 +0 -0
  4. package/dist/assets/fonts/MaterialSymbolsOutlined-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
  5. package/dist/assets/fonts/MaterialSymbolsRounded-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
  6. package/dist/assets/fonts/MaterialSymbolsSharp-VariableFont_FILL,GRAD,opsz,wght.ttf +0 -0
  7. package/dist/assets/loading-indicator.svg +19 -0
  8. package/dist/assets/material-symbols-cdn.css +65 -0
  9. package/dist/assets/material-symbols-self-hosted.css +94 -0
  10. package/dist/buttons.d.mts +180 -0
  11. package/dist/buttons.d.ts +180 -0
  12. package/dist/buttons.js +4282 -0
  13. package/dist/buttons.js.map +1 -0
  14. package/dist/buttons.mjs +4227 -0
  15. package/dist/buttons.mjs.map +1 -0
  16. package/dist/core.d.mts +422 -0
  17. package/dist/core.d.ts +422 -0
  18. package/dist/core.js +2581 -0
  19. package/dist/core.js.map +1 -0
  20. package/dist/core.mjs +2547 -0
  21. package/dist/core.mjs.map +1 -0
  22. package/dist/feedback.d.mts +600 -0
  23. package/dist/feedback.d.ts +600 -0
  24. package/dist/feedback.js +2357 -0
  25. package/dist/feedback.js.map +1 -0
  26. package/dist/feedback.mjs +2321 -0
  27. package/dist/feedback.mjs.map +1 -0
  28. package/dist/forms.d.mts +40 -0
  29. package/dist/forms.d.ts +40 -0
  30. package/dist/forms.js +4263 -0
  31. package/dist/forms.js.map +1 -0
  32. package/dist/forms.mjs +4228 -0
  33. package/dist/forms.mjs.map +1 -0
  34. package/dist/icon-button-D-gs0gfj.d.mts +136 -0
  35. package/dist/icon-button-D-gs0gfj.d.ts +136 -0
  36. package/dist/index.css +309 -0
  37. package/dist/index.css.d.ts +2 -0
  38. package/dist/index.d.mts +24 -0
  39. package/dist/index.d.ts +24 -0
  40. package/dist/index.js +23972 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/index.mjs +23668 -0
  43. package/dist/index.mjs.map +1 -0
  44. package/dist/layout.d.mts +610 -0
  45. package/dist/layout.d.ts +610 -0
  46. package/dist/layout.js +5091 -0
  47. package/dist/layout.js.map +1 -0
  48. package/dist/layout.mjs +5042 -0
  49. package/dist/layout.mjs.map +1 -0
  50. package/dist/material-symbols-cdn.css +65 -0
  51. package/dist/material-symbols-cdn.css.d.ts +2 -0
  52. package/dist/material-symbols-self-hosted.css +94 -0
  53. package/dist/material-symbols-self-hosted.css.d.ts +2 -0
  54. package/dist/md3-DFhj-NZj.d.mts +17 -0
  55. package/dist/md3-DFhj-NZj.d.ts +17 -0
  56. package/dist/navigation.d.mts +2766 -0
  57. package/dist/navigation.d.ts +2766 -0
  58. package/dist/navigation.js +6684 -0
  59. package/dist/navigation.js.map +1 -0
  60. package/dist/navigation.mjs +6573 -0
  61. package/dist/navigation.mjs.map +1 -0
  62. package/dist/overlays.d.mts +161 -0
  63. package/dist/overlays.d.ts +161 -0
  64. package/dist/overlays.js +3298 -0
  65. package/dist/overlays.js.map +1 -0
  66. package/dist/overlays.mjs +3244 -0
  67. package/dist/overlays.mjs.map +1 -0
  68. package/dist/pickers.d.mts +1074 -0
  69. package/dist/pickers.d.ts +1074 -0
  70. package/dist/pickers.js +4240 -0
  71. package/dist/pickers.js.map +1 -0
  72. package/dist/pickers.mjs +4200 -0
  73. package/dist/pickers.mjs.map +1 -0
  74. package/dist/plugin.d.mts +1 -0
  75. package/dist/plugin.d.ts +1 -0
  76. package/dist/plugin.js +13 -0
  77. package/dist/plugin.js.map +1 -0
  78. package/dist/plugin.mjs +3 -0
  79. package/dist/plugin.mjs.map +1 -0
  80. package/dist/scroll-area-CVS6HyLl.d.mts +33 -0
  81. package/dist/scroll-area-CVS6HyLl.d.ts +33 -0
  82. package/dist/shapes.d.mts +1119 -0
  83. package/dist/shapes.d.ts +1119 -0
  84. package/dist/shapes.js +2941 -0
  85. package/dist/shapes.js.map +1 -0
  86. package/dist/shapes.mjs +2889 -0
  87. package/dist/shapes.mjs.map +1 -0
  88. package/dist/side-sheet-modal-BY6VCC8p.d.mts +553 -0
  89. package/dist/side-sheet-modal-CglP6KYA.d.ts +553 -0
  90. package/dist/split-button-trailing-uncheckable-C5CLCIKP.d.ts +731 -0
  91. package/dist/split-button-trailing-uncheckable-MXj_kyNt.d.mts +731 -0
  92. package/dist/text-field-eAIpz9z1.d.mts +1038 -0
  93. package/dist/text-field-eAIpz9z1.d.ts +1038 -0
  94. package/dist/typography-339RV6v7.d.mts +486 -0
  95. package/dist/typography-339RV6v7.d.ts +486 -0
  96. package/dist/typography.css +22 -0
  97. package/dist/typography.css.d.ts +2 -0
  98. package/package.json +207 -0
@@ -0,0 +1,4200 @@
1
+ "use client";
2
+ import { m, LazyMotion, domMax, useMotionValue, animate, useAnimationFrame, AnimatePresence, useReducedMotion } from 'motion/react';
3
+ import * as React17 from 'react';
4
+ import { useState, useCallback, useMemo, useReducer } from 'react';
5
+ import { clsx } from 'clsx';
6
+ import { twMerge } from 'tailwind-merge';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+ import * as RadixDialog from '@radix-ui/react-dialog';
9
+ import * as RadixScrollArea from '@radix-ui/react-scroll-area';
10
+
11
+ var __defProp = Object.defineProperty;
12
+ var __defProps = Object.defineProperties;
13
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
14
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
15
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
16
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
17
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
18
+ var __spreadValues = (a, b) => {
19
+ for (var prop in b || (b = {}))
20
+ if (__hasOwnProp.call(b, prop))
21
+ __defNormalProp(a, prop, b[prop]);
22
+ if (__getOwnPropSymbols)
23
+ for (var prop of __getOwnPropSymbols(b)) {
24
+ if (__propIsEnum.call(b, prop))
25
+ __defNormalProp(a, prop, b[prop]);
26
+ }
27
+ return a;
28
+ };
29
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
30
+ var __objRest = (source, exclude) => {
31
+ var target = {};
32
+ for (var prop in source)
33
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
34
+ target[prop] = source[prop];
35
+ if (source != null && __getOwnPropSymbols)
36
+ for (var prop of __getOwnPropSymbols(source)) {
37
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
38
+ target[prop] = source[prop];
39
+ }
40
+ return target;
41
+ };
42
+ function cn(...inputs) {
43
+ return twMerge(clsx(inputs));
44
+ }
45
+
46
+ // src/ui/date-time-pickers/shared/calendar-model.ts
47
+ var startOfDayUTC = (ms) => {
48
+ const d = new Date(ms);
49
+ return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
50
+ };
51
+ var todayUTC = () => startOfDayUTC(Date.now());
52
+ var getDaysInMonth = (year, month) => new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
53
+ var getFirstDayOfWeek = (year, month) => new Date(Date.UTC(year, month, 1)).getUTCDay();
54
+ var getWeeksInMonth = (year, month, weekStartsOn) => {
55
+ const todayMs = todayUTC();
56
+ const daysInMonth = getDaysInMonth(year, month);
57
+ const firstDow = getFirstDayOfWeek(year, month);
58
+ const startPadding = (firstDow - weekStartsOn + 7) % 7;
59
+ const totalCells = startPadding + daysInMonth;
60
+ const totalWeeks = Math.ceil(totalCells / 7);
61
+ const weeks = [];
62
+ for (let w = 0; w < totalWeeks; w++) {
63
+ const week = [];
64
+ for (let d = 0; d < 7; d++) {
65
+ const cellIndex = w * 7 + d;
66
+ const dayNumber = cellIndex - startPadding + 1;
67
+ if (dayNumber < 1 || dayNumber > daysInMonth) {
68
+ week.push(null);
69
+ } else {
70
+ const utcMs = Date.UTC(year, month, dayNumber);
71
+ week.push({
72
+ utcMs,
73
+ day: dayNumber,
74
+ month,
75
+ year,
76
+ isCurrentMonth: true,
77
+ isToday: utcMs === todayMs
78
+ });
79
+ }
80
+ }
81
+ weeks.push(week);
82
+ }
83
+ return weeks;
84
+ };
85
+ var isSameDay = (a, b) => startOfDayUTC(a) === startOfDayUTC(b);
86
+ var isDateInRange = (ms, startMs, endMs) => {
87
+ if (startMs === null || endMs === null) return false;
88
+ const day = startOfDayUTC(ms);
89
+ const start = startOfDayUTC(startMs);
90
+ const end = startOfDayUTC(endMs);
91
+ return day >= start && day <= end;
92
+ };
93
+ var isRangeStart = (ms, startMs) => startMs !== null && isSameDay(ms, startMs);
94
+ var isRangeEnd = (ms, endMs) => endMs !== null && isSameDay(ms, endMs);
95
+ var isYearSelectable = (year, selectableDates) => selectableDates.isSelectableYear(year);
96
+ var resolveLocale = (locale) => {
97
+ var _a, _b;
98
+ return {
99
+ locale: (_a = locale == null ? void 0 : locale.locale) != null ? _a : typeof navigator !== "undefined" ? navigator.language : "en-US",
100
+ weekStartsOn: (_b = locale == null ? void 0 : locale.weekStartsOn) != null ? _b : 1
101
+ };
102
+ };
103
+ var normalizeToMonthStart = (ms) => {
104
+ const d = new Date(ms);
105
+ return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1);
106
+ };
107
+ var navigateMonth = (currentMs, direction) => {
108
+ const d = new Date(currentMs);
109
+ const year = d.getUTCFullYear();
110
+ const month = d.getUTCMonth();
111
+ if (direction === "next") {
112
+ return month === 11 ? Date.UTC(year + 1, 0, 1) : Date.UTC(year, month + 1, 1);
113
+ }
114
+ return month === 0 ? Date.UTC(year - 1, 11, 1) : Date.UTC(year, month - 1, 1);
115
+ };
116
+ var isMonthNavigable = (year, yearRange, selectableDates) => year >= yearRange[0] && year <= yearRange[1] && selectableDates.isSelectableYear(year);
117
+ var getNavigableMonth = (currentMs, direction, yearRange, selectableDates) => {
118
+ const targetMs = navigateMonth(currentMs, direction);
119
+ const targetYear = new Date(targetMs).getUTCFullYear();
120
+ return isMonthNavigable(targetYear, yearRange, selectableDates) ? targetMs : null;
121
+ };
122
+ var formatDate = (utcMs, locale, options) => {
123
+ const defaultOptions = {
124
+ year: "numeric",
125
+ month: "long",
126
+ day: "numeric",
127
+ timeZone: "UTC"
128
+ };
129
+ return new Intl.DateTimeFormat(locale.locale, __spreadValues(__spreadValues({}, defaultOptions), options)).format(new Date(utcMs));
130
+ };
131
+ var formatMonthYear = (utcMs, locale) => new Intl.DateTimeFormat(locale.locale, {
132
+ year: "numeric",
133
+ month: "long",
134
+ timeZone: "UTC"
135
+ }).format(new Date(utcMs));
136
+ var getWeekdayNames = (locale) => {
137
+ const SUNDAY_MS = Date.UTC(2025, 0, 5);
138
+ const formatter = new Intl.DateTimeFormat(locale.locale, {
139
+ weekday: "narrow",
140
+ timeZone: "UTC"
141
+ });
142
+ const allDays = Array.from(
143
+ { length: 7 },
144
+ (_, i) => formatter.format(new Date(SUNDAY_MS + i * 864e5))
145
+ );
146
+ return [
147
+ ...allDays.slice(locale.weekStartsOn),
148
+ ...allDays.slice(0, locale.weekStartsOn)
149
+ ];
150
+ };
151
+ var parseDateInput = (input) => {
152
+ const match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
153
+ if (!match) return null;
154
+ const [, mm, dd, yyyy] = match;
155
+ const month = parseInt(mm, 10) - 1;
156
+ const day = parseInt(dd, 10);
157
+ const year = parseInt(yyyy, 10);
158
+ if (month < 0 || month > 11 || day < 1 || day > 31 || year < 1) return null;
159
+ const maxDay = getDaysInMonth(year, month);
160
+ if (day > maxDay) return null;
161
+ return Date.UTC(year, month, day);
162
+ };
163
+ var DatePickerInput = ({
164
+ value,
165
+ onChange,
166
+ onCommit,
167
+ label = "Enter date",
168
+ error,
169
+ locale,
170
+ className
171
+ }) => {
172
+ const inputId = React17.useId();
173
+ const errorId = `${inputId}-error`;
174
+ const placeholder = React17.useMemo(() => {
175
+ if (locale == null ? void 0 : locale.startsWith("vi")) return "DD/MM/YYYY";
176
+ return "MM/DD/YYYY";
177
+ }, [locale]);
178
+ const handleKeyDown = React17.useCallback(
179
+ (e) => {
180
+ if (e.key === "Enter") {
181
+ const parsed = parseDateInput(value);
182
+ onCommit(parsed);
183
+ }
184
+ },
185
+ [value, onCommit]
186
+ );
187
+ const handleBlur = React17.useCallback(() => {
188
+ const parsed = parseDateInput(value);
189
+ onCommit(parsed);
190
+ }, [value, onCommit]);
191
+ const handleChange = React17.useCallback(
192
+ (e) => {
193
+ let raw = e.target.value.replace(/\D/g, "");
194
+ if (raw.length > 8) raw = raw.slice(0, 8);
195
+ let formatted = raw;
196
+ if (raw.length > 2) {
197
+ formatted = `${raw.slice(0, 2)}/${raw.slice(2)}`;
198
+ }
199
+ if (raw.length > 4) {
200
+ formatted = `${raw.slice(0, 2)}/${raw.slice(2, 4)}/${raw.slice(4)}`;
201
+ }
202
+ onChange(formatted);
203
+ },
204
+ [onChange]
205
+ );
206
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
207
+ /* @__PURE__ */ jsx(
208
+ "label",
209
+ {
210
+ htmlFor: inputId,
211
+ className: "block text-[length:var(--md-typescale-label-large-size,14px)]\n font-[number:var(--md-typescale-label-large-weight,500)]\n text-m3-on-surface-variant\n mb-1",
212
+ children: label
213
+ }
214
+ ),
215
+ /* @__PURE__ */ jsx(
216
+ "input",
217
+ {
218
+ id: inputId,
219
+ type: "text",
220
+ inputMode: "numeric",
221
+ value,
222
+ onChange: handleChange,
223
+ onBlur: handleBlur,
224
+ onKeyDown: handleKeyDown,
225
+ placeholder,
226
+ "aria-label": label,
227
+ "aria-invalid": !!error || void 0,
228
+ "aria-describedby": error ? errorId : void 0,
229
+ maxLength: 10,
230
+ className: "w-full h-14 px-4 rounded-m3-xs\n bg-m3-surface-container-highest\n text-[length:var(--md-typescale-body-large-size,16px)]\n hover:bg-m3-on-surface/8\n text-m3-on-surface\n caret-m3-primary\n placeholder:text-m3-on-surface-variant\n outline-none border-0\n focus:ring-2 focus:ring-m3-primary\n transition-m3-fast-effects"
231
+ }
232
+ ),
233
+ error && /* @__PURE__ */ jsx("p", { id: errorId, className: "mt-1 text-xs text-m3-error px-1", children: error })
234
+ ] }) }) });
235
+ };
236
+ DatePickerInput.displayName = "DatePickerInput";
237
+
238
+ // src/ui/shared/motion-tokens.ts
239
+ var FAST_SPATIAL_SPRING = {
240
+ type: "spring",
241
+ stiffness: 800,
242
+ damping: 33.94
243
+ };
244
+ var DEFAULT_SPATIAL_SPRING = {
245
+ type: "spring",
246
+ stiffness: 380,
247
+ damping: 31.19
248
+ };
249
+ var FAST_EFFECTS_SPRING = {
250
+ type: "spring",
251
+ stiffness: 3800,
252
+ damping: 123.29
253
+ };
254
+ var DEFAULT_EFFECTS_SPRING = {
255
+ type: "spring",
256
+ stiffness: 1600,
257
+ damping: 80
258
+ };
259
+
260
+ // src/ui/date-time-pickers/shared/animation-tokens.ts
261
+ var PICKER_OVERLAY_ANIM = {
262
+ initial: { opacity: 0 },
263
+ animate: {
264
+ opacity: 1,
265
+ transition: { duration: 0.2, ease: [0, 0, 0, 1] }
266
+ },
267
+ exit: {
268
+ opacity: 0,
269
+ transition: { duration: 0.15, ease: [0.3, 0, 1, 1] }
270
+ }
271
+ };
272
+ var PICKER_CONTENT_ANIM = {
273
+ initial: { opacity: 0, scale: 0.85, y: 16 },
274
+ animate: {
275
+ opacity: 1,
276
+ scale: 1,
277
+ y: 0,
278
+ transition: DEFAULT_SPATIAL_SPRING
279
+ },
280
+ exit: {
281
+ opacity: 0,
282
+ scale: 0.95,
283
+ y: 8,
284
+ transition: { duration: 0.15, ease: [0.3, 0, 1, 1] }
285
+ }
286
+ };
287
+ var CALENDAR_SLIDE_VARIANTS = {
288
+ enterFromRight: {
289
+ x: "100%",
290
+ opacity: 0
291
+ },
292
+ enterFromLeft: {
293
+ x: "-100%",
294
+ opacity: 0
295
+ },
296
+ center: {
297
+ x: 0,
298
+ opacity: 1,
299
+ transition: FAST_SPATIAL_SPRING
300
+ },
301
+ exitToLeft: {
302
+ x: "-100%",
303
+ opacity: 0,
304
+ transition: { duration: 0.15, ease: [0.3, 0, 1, 1] }
305
+ },
306
+ exitToRight: {
307
+ x: "100%",
308
+ opacity: 0,
309
+ transition: { duration: 0.15, ease: [0.3, 0, 1, 1] }
310
+ }
311
+ };
312
+ var DAY_CELL_STATE_TRANSITION = FAST_EFFECTS_SPRING;
313
+ var DayCell = ({
314
+ day,
315
+ selectedMs,
316
+ selectedStartMs = null,
317
+ selectedEndMs = null,
318
+ isRangePicker = false,
319
+ isSelectable,
320
+ onSelect,
321
+ locale
322
+ }) => {
323
+ const { utcMs, day: dayNum, isToday } = day;
324
+ const isSelected = !isRangePicker && selectedMs !== null && isSameDay(utcMs, selectedMs);
325
+ const isStart = isRangePicker && isRangeStart(utcMs, selectedStartMs);
326
+ const isEnd = isRangePicker && isRangeEnd(utcMs, selectedEndMs);
327
+ const isInRange = isRangePicker && !isStart && !isEnd && isDateInRange(utcMs, selectedStartMs, selectedEndMs);
328
+ const isDisabled = !isSelectable;
329
+ const ariaLabel = new Intl.DateTimeFormat(locale, {
330
+ year: "numeric",
331
+ month: "long",
332
+ day: "numeric",
333
+ timeZone: "UTC"
334
+ }).format(new Date(utcMs));
335
+ const rangeHighlightClass = cn(
336
+ "absolute inset-y-0 bg-m3-secondary-container",
337
+ isStart && selectedEndMs !== null && "left-1/2 right-0",
338
+ isEnd && selectedStartMs !== null && "left-0 right-1/2",
339
+ isInRange && "inset-x-0"
340
+ );
341
+ const circleClass = cn(
342
+ "relative z-10 flex items-center justify-center w-10 h-10 rounded-m3-full",
343
+ "text-[length:var(--md-typescale-body-large-size,14px)]",
344
+ "font-[number:var(--md-typescale-body-large-weight,400)]",
345
+ "transition-m3-fast-effects",
346
+ // Selected (single) or range endpoints
347
+ (isSelected || isStart || isEnd) && ["bg-m3-primary", "text-m3-on-primary"],
348
+ // In-range text
349
+ isInRange && "text-m3-on-secondary-container",
350
+ // Today outline (only when not selected)
351
+ isToday && !isSelected && !isStart && !isEnd && ["outline outline-1 outline-m3-primary", "text-m3-primary"],
352
+ // Default text
353
+ !isSelected && !isStart && !isEnd && !isInRange && !isToday && "text-m3-on-surface",
354
+ // Hover states (only when selectable and not selected/range endpoints)
355
+ !isDisabled && !isSelected && !isStart && !isEnd && [
356
+ isInRange ? "hover:bg-m3-on-secondary-container/8" : "hover:bg-m3-on-surface/8"
357
+ ],
358
+ // Disabled
359
+ isDisabled && "opacity-[0.38]"
360
+ );
361
+ const handleClick = React17.useCallback(() => {
362
+ if (isDisabled) return;
363
+ onSelect(utcMs);
364
+ }, [isDisabled, utcMs, onSelect]);
365
+ const handleKeyDown = React17.useCallback(
366
+ (e) => {
367
+ if (e.key === "Enter" || e.key === " ") {
368
+ e.preventDefault();
369
+ handleClick();
370
+ }
371
+ },
372
+ [handleClick]
373
+ );
374
+ return /* @__PURE__ */ jsxs("td", { className: "relative flex items-center justify-center", children: [
375
+ isRangePicker && (isInRange || isStart || isEnd) && /* @__PURE__ */ jsx("div", { className: rangeHighlightClass }),
376
+ /* @__PURE__ */ jsx(
377
+ m.button,
378
+ {
379
+ type: "button",
380
+ className: cn(
381
+ circleClass,
382
+ isDisabled && "pointer-events-none cursor-not-allowed"
383
+ ),
384
+ onClick: handleClick,
385
+ onKeyDown: handleKeyDown,
386
+ "aria-label": ariaLabel,
387
+ "aria-selected": isSelected || isStart || isEnd || void 0,
388
+ "aria-disabled": isDisabled || void 0,
389
+ tabIndex: isDisabled ? -1 : 0,
390
+ whileTap: !isDisabled ? {
391
+ scale: 0.94,
392
+ transition: DAY_CELL_STATE_TRANSITION
393
+ } : void 0,
394
+ children: dayNum
395
+ }
396
+ )
397
+ ] });
398
+ };
399
+ DayCell.displayName = "DayCell";
400
+ var WeekdayRow = ({ locale }) => {
401
+ const names = React17.useMemo(() => getWeekdayNames(locale), [locale]);
402
+ return /* @__PURE__ */ jsx("tr", { className: "grid grid-cols-7 mb-1", children: names.map((name, i) => /* @__PURE__ */ jsx(
403
+ "th",
404
+ {
405
+ scope: "col",
406
+ "aria-label": name,
407
+ className: "flex items-center justify-center h-10 w-10 mx-auto\n text-[length:var(--md-typescale-label-large-size,14px)]\n font-[number:var(--md-typescale-label-large-weight,500)]\n text-m3-on-surface\n select-none\n font-normal",
408
+ children: name
409
+ },
410
+ i
411
+ )) });
412
+ };
413
+ WeekdayRow.displayName = "WeekdayRow";
414
+ var CalendarGrid = ({
415
+ displayedMonthMs,
416
+ slideDirection,
417
+ locale,
418
+ selectableDates,
419
+ selectedMs,
420
+ selectedStartMs = null,
421
+ selectedEndMs = null,
422
+ isRangePicker = false,
423
+ onDaySelect
424
+ }) => {
425
+ const d = new Date(displayedMonthMs);
426
+ const year = d.getUTCFullYear();
427
+ const month = d.getUTCMonth();
428
+ const weeks = React17.useMemo(
429
+ () => getWeeksInMonth(year, month, locale.weekStartsOn),
430
+ [year, month, locale.weekStartsOn]
431
+ );
432
+ const enterVariant = slideDirection === "next" ? "enterFromRight" : "enterFromLeft";
433
+ const exitVariant = slideDirection === "next" ? "exitToLeft" : "exitToRight";
434
+ return /* @__PURE__ */ jsxs("table", { className: "w-full px-3 border-collapse block", children: [
435
+ /* @__PURE__ */ jsx("thead", { className: "block", children: /* @__PURE__ */ jsx(WeekdayRow, { locale }) }),
436
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, mode: "popLayout", children: /* @__PURE__ */ jsx(
437
+ m.tbody,
438
+ {
439
+ variants: CALENDAR_SLIDE_VARIANTS,
440
+ initial: slideDirection !== null ? enterVariant : false,
441
+ animate: "center",
442
+ exit: slideDirection !== null ? exitVariant : void 0,
443
+ className: "overflow-hidden block",
444
+ "aria-label": new Intl.DateTimeFormat(locale.locale, {
445
+ year: "numeric",
446
+ month: "long",
447
+ timeZone: "UTC"
448
+ }).format(new Date(displayedMonthMs)),
449
+ children: weeks.map((week, weekIdx) => /* @__PURE__ */ jsx(
450
+ "tr",
451
+ {
452
+ className: "grid grid-cols-7",
453
+ children: week.map(
454
+ (day, dayIdx) => day === null ? /* @__PURE__ */ jsx(
455
+ "td",
456
+ {
457
+ className: "w-10 h-10 mx-auto block",
458
+ "aria-hidden": true
459
+ },
460
+ `empty-${weekIdx}-${dayIdx}`
461
+ ) : /* @__PURE__ */ jsx(
462
+ DayCell,
463
+ {
464
+ day,
465
+ selectedMs,
466
+ selectedStartMs,
467
+ selectedEndMs,
468
+ isRangePicker,
469
+ isSelectable: selectableDates.isSelectableDate(day.utcMs),
470
+ onSelect: onDaySelect,
471
+ locale: locale.locale
472
+ },
473
+ day.utcMs
474
+ )
475
+ )
476
+ },
477
+ weekIdx
478
+ ))
479
+ },
480
+ displayedMonthMs
481
+ ) })
482
+ ] });
483
+ };
484
+ CalendarGrid.displayName = "CalendarGrid";
485
+ var SHAPE_KEY_TIMES = "0; 0.14; 0.14; 0.29; 0.29; 0.43; 0.43; 0.57; 0.57; 0.71; 0.71; 0.86; 0.86; 1";
486
+ var SHAPE_KEY_SPLINES = [
487
+ "0.5 0.2 0 0.8",
488
+ "0.5 0.2 0 0.8",
489
+ "0.5 0.2 0 0.8",
490
+ "0.5 0.2 0 0.8",
491
+ "0.5 0.2 0 0.8",
492
+ "0.5 0.2 0 0.8",
493
+ "0.5 0.2 0 0.8",
494
+ "0.5 0.2 0 0.8",
495
+ "0.5 0.2 0 0.8",
496
+ "0.5 0.2 0 0.8",
497
+ "0.5 0.2 0 0.8",
498
+ "0.5 0.2 0 0.8",
499
+ "0.5 0.2 0 0.8"
500
+ ].join("; ");
501
+ var SHAPE_VALUES = [
502
+ "M20.9 10.4 21.4 9.5 21.9 8.7 22.5 7.8 23.2 7.2 24.2 7 25.1 7.4 25.7 8.1 26.2 9 26.8 9.8 27.3 10.6 28.1 11.2 29 11.3 30 11 30.9 10.6 31.8 10.3 32.8 9.9 33.7 10 34.5 10.6 34.9 11.5 34.8 12.5 34.8 13.5 34.7 14.5 34.7 15.5 35.2 16.3 36 16.8 37 17.1 37.9 17.3 38.9 17.5 39.8 17.9 40.4 18.7 40.5 19.7 40 20.5 39.4 21.3 38.7 22 38.1 22.8 37.6 23.7 37.7 24.6 38.3 25.5 38.9 26.2 39.6 27 40.2 27.7 40.5 28.7 40.3 29.6 39.5 30.3 38.6 30.6 37.6 30.8 36.7 31 35.7 31.3 35 31.9 34.6 32.8 34.7 33.8 34.8 34.8 34.9 35.8 34.8 36.8 34.3 37.6 33.4 38.1 32.4 38 31.5 37.6 30.6 37.2 29.7 36.9 28.7 36.6 27.8 36.9 27.1 37.6 26.6 38.5 26.1 39.3 25.5 40.2 24.8 40.8 23.8 41 22.9 40.6 22.3 39.9 21.8 39 21.2 38.2 20.7 37.4 19.9 36.8 19 36.7 18 37 17.1 37.4 16.2 37.7 15.2 38.1 14.3 38 13.5 37.4 13.1 36.5 13.2 35.5 13.2 34.5 13.3 33.5 13.3 32.5 12.8 31.7 12 31.2 11 31 10.1 30.7 9.1 30.5 8.2 30.1 7.6 29.3 7.5 28.3 8 27.5 8.7 26.7 9.3 26 9.9 25.2 10.4 24.3 10.3 23.4 9.7 22.5 9.1 21.8 8.4 21 7.8 20.3 7.5 19.3 7.7 18.4 8.5 17.7 9.4 17.4 10.4 17.2 11.3 17 12.3 16.7 13 16.1 13.4 15.2 13.3 14.2 13.2 13.2 13.1 12.2 13.2 11.2 13.7 10.4 14.6 9.9 15.6 10 16.5 10.4 17.4 10.8 18.3 11.1 19.3 11.4 20.2 11.1Z",
503
+ "M20.3 8.6 21.1 8 22 7.6 23 7.3 23 7.3 24 7.2 25 7.3 25.9 7.5 26.8 8 27.6 8.6 28.4 9.1 28.4 9.1 29.3 9.6 30.3 9.8 31.3 9.9 32.3 10 33.3 10.2 34.2 10.6 34.2 10.6 35 11.2 35.7 11.9 36.3 12.7 36.7 13.6 36.9 14.6 37.2 15.5 37.2 15.5 37.6 16.5 38.2 17.3 38.9 18 39.6 18.7 40.2 19.5 40.6 20.4 40.6 20.4 40.9 21.3 41 22.3 40.9 23.3 40.6 24.3 40.2 25.2 39.8 26.1 39.8 26.1 39.5 27 39.4 28 39.5 29 39.6 30 39.5 31 39.3 32 39.3 32 38.9 32.9 38.3 33.7 37.6 34.4 36.8 35 35.9 35.4 35 35.8 35 35.8 34.1 36.3 33.4 37 32.9 37.9 32.3 38.7 31.6 39.4 30.8 40 30.8 40 29.9 40.4 28.9 40.7 27.9 40.8 27 40.7 26 40.4 25 40.1 25 40.1 24 40 23 40.1 22.1 40.4 21.1 40.7 20.1 40.8 19.1 40.7 19.1 40.7 18.2 40.5 17.3 40 16.4 39.5 15.7 38.8 15.2 37.9 14.6 37.1 14.6 37.1 13.9 36.4 13.1 35.8 12.2 35.4 11.3 35 10.5 34.4 9.7 33.8 9.7 33.8 9.1 32.9 8.7 32 8.5 31.1 8.4 30.1 8.5 29.1 8.6 28.1 8.6 28.1 8.5 27.1 8.3 26.1 7.8 25.2 7.4 24.3 7.1 23.4 7 22.4 7 22.4 7.1 21.4 7.3 20.4 7.8 19.5 8.3 18.7 9.1 18 9.8 17.3 9.8 17.3 10.4 16.5 10.8 15.6 11 14.6 11.3 13.7 11.7 12.8 12.2 11.9 12.2 11.9 12.9 11.2 13.8 10.7 14.7 10.2 15.6 10 16.6 9.9 17.6 9.8 17.6 9.8 18.6 9.6 19.5 9.2Z",
504
+ "M18.6 9.6 19.5 9.2 20.3 8.6 21.1 8 22 7.6 23 7.3 24 7.2 25 7.3 25.9 7.5 26.8 8 27.6 8.6 28.4 9.1 29.3 9.6 30.3 9.8 31.3 9.9 32.3 10 33.3 10.2 34.2 10.6 35 11.2 35.7 11.9 36.3 12.7 36.7 13.6 36.9 14.6 37.2 15.5 37.6 16.4 38.2 17.3 38.9 18 39.6 18.7 40.2 19.5 40.6 20.4 40.9 21.3 41 22.3 40.9 23.3 40.6 24.3 40.2 25.2 39.8 26.1 39.5 27 39.4 28 39.5 29 39.6 30 39.5 31 39.3 32 38.9 32.9 38.3 33.7 37.6 34.4 36.8 35 35.9 35.4 35 35.8 34.1 36.3 33.4 37 32.9 37.9 32.3 38.7 31.6 39.4 30.8 40 29.9 40.4 28.9 40.7 27.9 40.8 27 40.7 26 40.4 25 40.1 24 40 23 40.1 22.1 40.4 21.1 40.7 20.1 40.8 19.1 40.7 18.2 40.5 17.3 40 16.4 39.5 15.7 38.8 15.2 37.9 14.6 37.1 13.9 36.4 13.1 35.8 12.2 35.4 11.3 35 10.5 34.4 9.7 33.8 9.1 32.9 8.7 32 8.5 31.1 8.4 30.1 8.5 29.1 8.6 28.1 8.5 27.1 8.3 26.1 7.8 25.2 7.4 24.3 7.1 23.4 7 22.4 7.1 21.4 7.3 20.4 7.8 19.5 8.3 18.7 9.1 18 9.8 17.3 10.4 16.5 10.8 15.6 11 14.6 11.3 13.7 11.7 12.8 12.2 11.9 12.9 11.2 13.8 10.7 14.7 10.2 15.6 10 16.6 9.9 17.6 9.8Z",
505
+ "M18.6 9.9 19.5 9.4 20.3 8.8 21.1 8.2 22 7.8 23 7.6 23.9 7.5 24.9 7.6 25.9 7.8 26.8 8.2 27.6 8.7 28.5 9.3 29.3 9.9 30.1 10.5 30.9 11 31.7 11.6 32.5 12.2 33.3 12.8 33.7 13.1 34.1 13.3 34.9 13.9 35.7 14.5 36.6 15 37.4 15.6 38.2 16.2 39 16.8 39.7 17.5 40.2 18.3 40.7 19.2 40.9 20.1 41 21.1 40.9 22.1 40.7 23.1 40.3 24 40 24.9 39.7 25.9 39.4 26.8 39 27.8 38.7 28.7 38.4 29.6 38.1 30.6 37.8 31.5 37.5 32.5 37.2 33.4 36.9 34.4 36.6 35.3 36.2 36.2 35.7 37.1 35 37.8 34.3 38.4 33.4 38.9 32.5 39.3 31.5 39.5 30.5 39.5 30 39.5 29.5 39.5 28.5 39.5 27.5 39.5 26.5 39.5 25.5 39.4 24.5 39.4 23.6 39.4 22.6 39.4 21.6 39.5 20.6 39.5 19.6 39.5 18.6 39.5 17.6 39.5 16.6 39.5 15.6 39.3 14.7 39 13.8 38.5 13.1 37.9 12.4 37.2 11.9 36.3 11.5 35.4 11.2 34.5 10.9 33.5 10.6 32.6 10.3 31.6 10 30.7 9.7 29.7 9.3 28.8 9 27.9 8.7 26.9 8.4 26 8 25 7.7 24.1 7.4 23.2 7.1 22.2 7.1 21.7 7 21.2 7.1 20.2 7.3 19.3 7.7 18.4 8.3 17.5 8.9 16.8 9.7 16.2 10.5 15.6 11.4 15.1 12.2 14.5 13 14 13.8 13.4 14.6 12.8 15.4 12.3 16.2 11.7 17 11.1 17.8 10.5Z",
506
+ "M15.4 12.3 16.2 11.7 17 11.1 17.8 10.5 18.6 9.9 19.5 9.4 20.3 8.8 21.1 8.3 22 7.8 23 7.6 23.9 7.5 24.9 7.6 25.9 7.8 26.8 8.2 27.6 8.7 28.5 9.3 29.3 9.9 30.1 10.5 30.9 11 31.7 11.6 32.5 12.2 33.3 12.8 34.1 13.3 34.9 13.9 35.7 14.5 36.6 15 37.4 15.6 38.2 16.2 39 16.8 39.7 17.5 40.2 18.3 40.7 19.2 40.9 20.1 41 21.1 40.9 22.1 40.7 23.1 40.3 24 40 24.9 39.7 25.9 39.4 26.8 39 27.8 38.7 28.7 38.4 29.6 38.1 30.6 37.8 31.5 37.5 32.5 37.2 33.4 36.9 34.4 36.6 35.3 36.2 36.2 35.7 37.1 35 37.8 34.3 38.4 33.4 38.9 32.5 39.3 31.5 39.4 30.5 39.5 29.5 39.5 28.5 39.5 27.5 39.5 26.5 39.5 25.5 39.4 24.5 39.4 23.6 39.4 22.6 39.4 21.6 39.5 20.6 39.5 19.6 39.5 18.6 39.5 17.6 39.5 16.6 39.5 15.6 39.3 14.7 39 13.8 38.5 13.1 37.9 12.4 37.2 11.9 36.3 11.5 35.4 11.2 34.5 10.9 33.5 10.6 32.6 10.3 31.6 10 30.7 9.7 29.7 9.3 28.8 9 27.9 8.7 26.9 8.4 26 8 25 7.7 24.1 7.4 23.2 7.1 22.2 7 21.2 7.1 20.2 7.3 19.3 7.7 18.4 8.3 17.5 8.9 16.8 9.7 16.2 10.5 15.6 11.4 15.1 12.2 14.5 13 14 13.8 13.4 14.6 12.8Z",
507
+ "M17 12.8 17.7 12.1 18.5 11.5 19.3 10.9 20.1 10.5 20.2 10.4 21.1 10 22 9.7 23 9.4 24 9.2 25 9 26 9 27 9 27.6 9.1 28 9.1 28.9 9.3 29.9 9.6 30.9 9.9 31.8 10.3 32.6 10.8 33.5 11.3 34.3 11.9 34.6 12.2 35 12.6 35.7 13.3 36.4 14.1 36.9 14.9 37.4 15.8 37.9 16.6 38.3 17.6 38.6 18.5 38.6 18.7 38.8 19.5 38.9 20.5 39 21.5 39 22.5 38.9 23.5 38.7 24.5 38.5 25.4 38.2 26.3 38.2 26.4 37.8 27.3 37.4 28.2 36.8 29.1 36.2 29.9 35.6 30.6 34.9 31.3 34.2 32 33.8 32.5 33.5 32.8 32.8 33.5 32.1 34.2 31.4 34.9 30.7 35.6 29.9 36.2 29.1 36.8 28.2 37.3 27.9 37.5 27.4 37.8 26.4 38.2 25.5 38.5 24.5 38.7 23.5 38.9 22.5 39 21.5 39 20.5 38.9 20.4 38.9 19.5 38.8 18.6 38.6 17.6 38.3 16.7 37.9 15.8 37.5 14.9 37 14.1 36.4 13.4 35.8 13.3 35.8 12.6 35.1 12 34.3 11.3 33.5 10.8 32.7 10.3 31.8 9.9 30.9 9.6 30 9.4 29.3 9.3 29 9.1 28 9 27 9 26 9 25 9.2 24 9.4 23 9.6 22.1 9.8 21.7 10 21.1 10.4 20.2 10.9 19.4 11.4 18.5 12.1 17.8 12.7 17 13.4 16.3 14.2 15.6 14.2 15.5 14.9 14.9 15.6 14.2 16.3 13.5Z",
508
+ "M33.5 11.3 34.3 11.9 35 12.6 35.3 12.8 35.7 13.3 36.4 14.1 36.9 14.9 37.4 15.8 37.9 16.6 38.3 17.6 38.3 17.7 38.6 18.5 38.8 19.5 38.9 20.5 39 21.5 39 22.5 38.9 23.5 38.9 23.5 38.7 24.5 38.5 25.4 38.2 26.4 37.8 27.3 37.4 28.2 36.9 28.9 36.8 29.1 36.2 29.9 35.6 30.6 34.9 31.3 34.2 32 33.5 32.8 33.1 33.2 32.8 33.5 32.1 34.2 31.4 34.9 30.7 35.6 29.9 36.2 29.1 36.8 28.7 37 28.2 37.3 27.4 37.8 26.4 38.2 25.5 38.5 24.5 38.7 23.5 38.9 23.3 38.9 22.5 39 21.5 39 20.5 38.9 19.5 38.8 18.6 38.6 17.6 38.3 17.6 38.3 16.7 37.9 15.8 37.5 14.9 37 14.1 36.4 13.3 35.8 12.7 35.2 12.6 35.1 12 34.3 11.3 33.5 10.8 32.7 10.3 31.8 9.9 30.9 9.7 30.3 9.6 29.9 9.3 29 9.1 28 9 27 9 26 9 25 9.1 24.5 9.2 24 9.4 23 9.6 22.1 10 21.1 10.4 20.2 10.9 19.4 11.1 19.1 11.5 18.5 12.1 17.7 12.7 17 13.5 16.3 14.2 15.6 14.9 14.9 14.9 14.8 15.6 14.2 16.3 13.5 17 12.8 17.7 12.1 18.5 11.5 19.3 11 19.3 10.9 20.2 10.4 21.1 10 22 9.7 23 9.4 24 9.2 24.7 9.1 25 9 26 9 27 9 28 9.1 28.9 9.3 29.9 9.6 30.4 9.7 30.9 9.9 31.8 10.3 32.6 10.8Z",
509
+ "M33.2 11.1 34.2 11.2 35.1 11.4 36 11.9 36.6 12.7 36.8 13.7 36.9 14.7 36.9 15.7 37 16.7 37.1 17.7 37.3 18.6 37.9 19.4 38.5 20.2 39.2 20.9 39.8 21.7 40.5 22.4 40.9 23.3 41 24.3 40.7 25.2 40.1 26 39.4 26.8 38.8 27.5 38.1 28.3 37.5 29.1 37.1 30 37 31 37 31.9 36.9 32.9 36.8 33.9 36.7 34.9 36.2 35.8 35.5 36.4 34.5 36.8 33.6 36.8 32.6 36.9 31.6 37 30.6 37.1 29.6 37.2 28.8 37.7 28 38.4 27.3 39 26.5 39.7 25.8 40.3 24.9 40.8 23.9 41 23 40.8 22.1 40.2 21.4 39.6 20.6 38.9 19.9 38.3 19.1 37.6 18.3 37.2 17.3 37 16.3 37 15.3 36.9 14.3 36.8 13.3 36.7 12.4 36.4 11.7 35.7 11.3 34.8 11.2 33.8 11.1 32.8 11 31.8 11 30.8 10.9 29.9 10.4 29 9.8 28.2 9.1 27.5 8.5 26.7 7.8 26 7.3 25.1 7 24.2 7.1 23.2 7.6 22.3 8.2 21.6 8.9 20.8 9.5 20.1 10.2 19.3 10.7 18.5 10.9 17.5 11 16.6 11.1 15.6 11.1 14.6 11.2 13.6 11.5 12.6 12.1 11.9 13 11.4 13.9 11.2 14.9 11.1 15.9 11 16.9 11 17.9 10.9 18.8 10.6 19.6 10 20.4 9.3 21.1 8.6 21.9 8 22.6 7.4 23.6 7 24.6 7.1 25.5 7.5 26.2 8.1 27 8.7 27.7 9.4 28.5 10 29.3 10.6 30.2 10.9 31.2 11 32.2 11.1Z",
510
+ "M27.7 9.4 28.5 10 29.3 10.6 30.2 10.9 31.2 11 32.2 11.1 33.2 11.1 34.2 11.2 35.1 11.4 36 11.9 36.6 12.7 36.8 13.7 36.9 14.7 36.9 15.7 37 16.7 37.1 17.7 37.3 18.6 37.9 19.4 38.5 20.2 39.2 20.9 39.8 21.7 40.5 22.4 40.9 23.3 41 24.3 40.7 25.2 40.1 26 39.4 26.8 38.8 27.5 38.1 28.3 37.5 29.1 37.1 30 37 31 37 31.9 36.9 32.9 36.8 33.9 36.7 34.9 36.2 35.8 35.5 36.4 34.5 36.8 33.6 36.9 32.6 36.9 31.6 37 30.6 37.1 29.6 37.2 28.8 37.7 28 38.4 27.3 39 26.5 39.7 25.8 40.3 24.9 40.8 23.9 41 23 40.8 22.1 40.2 21.4 39.6 20.6 38.9 19.9 38.3 19.1 37.6 18.3 37.2 17.3 37 16.3 37 15.3 36.9 14.3 36.8 13.3 36.7 12.4 36.4 11.7 35.7 11.3 34.8 11.2 33.8 11.1 32.8 11 31.8 11 30.8 10.9 29.9 10.4 29 9.8 28.2 9.1 27.5 8.5 26.7 7.8 26 7.3 25.1 7 24.2 7.1 23.2 7.6 22.3 8.2 21.6 8.9 20.8 9.5 20.1 10.2 19.3 10.7 18.5 10.9 17.5 11 16.5 11.1 15.6 11.1 14.6 11.2 13.6 11.5 12.6 12.1 11.9 13 11.4 13.9 11.2 14.9 11.1 15.9 11 16.9 11 17.9 10.9 18.8 10.6 19.6 10 20.4 9.3 21.1 8.6 21.9 8 22.6 7.4 23.6 7 24.6 7.1 25.5 7.5 26.2 8.1 27 8.7Z",
511
+ "M27.9 10.6 28.8 10.3 29.8 10.1 30.8 10 31.8 10.1 32.7 10.3 33.7 10.6 34.6 11.1 34.8 11.3 35.4 11.7 36.1 12.4 36.7 13.1 37.2 14 37.6 14.9 37.9 15.9 38 16.9 38 17.9 37.8 18.8 37.5 19.8 37.1 20.7 36.8 21.6 36.5 22.6 36.4 23.6 36.4 24.4 36.4 24.6 36.5 25.5 36.8 26.5 37.2 27.4 37.6 28.3 37.8 29.3 38 30.3 38 31.3 37.8 32.3 37.6 33.2 37.2 34.1 36.6 35 36 35.7 35.3 36.4 34.4 37 34.1 37.2 33.6 37.4 32.6 37.8 31.6 37.9 30.6 38 29.7 37.9 28.7 37.7 27.8 37.3 26.8 36.9 25.9 36.6 24.9 36.4 23.9 36.4 22.9 36.4 22 36.6 21 37 20.1 37.4 20.1 37.4 19.2 37.7 18.2 37.9 17.2 38 16.2 37.9 15.3 37.7 14.3 37.4 13.4 36.9 12.6 36.3 11.9 35.6 11.3 34.9 10.8 34 10.4 33.1 10.1 32.1 10 31.1 10 30.6 10 30.1 10.2 29.2 10.5 28.2 10.9 27.3 11.2 26.4 11.5 25.4 11.6 24.4 11.6 23.4 11.5 22.5 11.2 21.5 10.8 20.6 10.4 19.7 10.2 18.7 10 17.7 10 16.7 10 16.6 10.2 15.7 10.4 14.8 10.8 13.9 11.4 13 12 12.3 12.7 11.6 13.6 11 14.4 10.6 15.4 10.2 16.4 10.1 17.4 10 18.3 10.1 19.3 10.3 20.2 10.7 20.9 11 21.2 11.1 22.1 11.4 23.1 11.6 24.1 11.6 25.1 11.6 26 11.4 27 11Z",
512
+ "M36 35.7 35.3 36.4 34.4 37 33.6 37.4 32.6 37.8 31.6 37.9 30.6 38 29.7 37.9 28.7 37.7 27.8 37.3 26.8 36.9 25.9 36.6 24.9 36.4 23.9 36.4 22.9 36.4 22 36.6 21 37 20.1 37.4 19.2 37.7 18.2 37.9 17.2 38 16.2 37.9 15.3 37.7 14.3 37.4 13.4 36.9 12.6 36.3 11.9 35.6 11.3 34.9 10.8 34 10.4 33.1 10.1 32.1 10 31.1 10 30.2 10.2 29.2 10.5 28.2 10.9 27.3 11.2 26.4 11.5 25.4 11.6 24.4 11.6 23.4 11.5 22.5 11.2 21.5 10.8 20.6 10.4 19.7 10.2 18.7 10 17.7 10 16.7 10.2 15.7 10.4 14.8 10.8 13.9 11.4 13 12 12.3 12.7 11.6 13.6 11 14.4 10.6 15.4 10.2 16.4 10.1 17.4 10 18.3 10.1 19.3 10.3 20.2 10.7 21.2 11.1 22.1 11.4 23.1 11.6 24.1 11.6 25.1 11.6 26 11.4 27 11 27.9 10.6 28.8 10.3 29.8 10.1 30.8 10 31.8 10.1 32.7 10.3 33.7 10.6 34.6 11.1 35.4 11.7 36.1 12.4 36.7 13.1 37.2 14 37.6 14.9 37.9 15.9 38 16.9 38 17.8 37.8 18.8 37.5 19.8 37.1 20.7 36.8 21.6 36.5 22.6 36.4 23.6 36.4 24.6 36.5 25.5 36.8 26.5 37.2 27.4 37.6 28.3 37.8 29.3 38 30.3 38 31.3 37.8 32.3 37.6 33.2 37.2 34.1 36.6 35Z",
513
+ "M32.1 32.1 31.4 32.8 30.7 33.5 29.9 34.1 29.1 34.7 28.3 35.3 27.6 35.8 27.5 35.8 26.6 36.4 25.8 36.8 24.9 37.3 24 37.7 23.1 38 22.1 38.3 21.2 38.6 20.2 38.8 19.2 38.9 18.2 39 17.2 39 16.6 38.9 16.3 38.9 15.3 38.7 14.3 38.4 13.4 38 12.5 37.5 11.7 36.9 11.1 36.3 10.5 35.5 10 34.6 9.6 33.7 9.3 32.7 9.1 31.7 9.1 31.4 9 30.8 9 29.8 9.1 28.8 9.2 27.8 9.4 26.8 9.7 25.9 10 24.9 10.3 24 10.7 23.1 11.2 22.2 11.6 21.4 12.2 20.5 12.2 20.4 12.7 19.7 13.3 18.9 13.9 18.1 14.5 17.3 15.2 16.6 15.9 15.9 16.6 15.2 17.3 14.5 18.1 13.9 18.9 13.3 19.7 12.7 20.4 12.2 20.5 12.2 21.4 11.6 22.2 11.2 23.1 10.7 24 10.3 24.9 10 25.9 9.7 26.8 9.4 27.8 9.2 28.8 9.1 29.8 9 30.8 9 31.4 9.1 31.7 9.1 32.7 9.3 33.7 9.6 34.6 10 35.5 10.5 36.3 11.1 36.9 11.7 37.5 12.5 38 13.4 38.4 14.3 38.7 15.3 38.9 16.3 38.9 16.6 39 17.2 39 18.2 38.9 19.2 38.8 20.2 38.6 21.2 38.3 22.1 38 23.1 37.7 24 37.3 24.9 36.8 25.8 36.4 26.6 35.8 27.5 35.8 27.6 35.3 28.3 34.7 29.1 34.1 29.9 33.5 30.7 32.8 31.4Z",
514
+ "M24.3 10.2 24.9 10 25.9 9.7 26.8 9.4 27.1 9.4 27.8 9.2 28.8 9.1 29.8 9 29.9 9 30.8 9 31.7 9.1 32.7 9.3 32.8 9.3 33.7 9.6 34.6 10 35.5 10.5 35.5 10.5 36.3 11.1 36.9 11.7 37.5 12.5 37.5 12.5 38 13.4 38.4 14.3 38.7 15.2 38.7 15.3 38.9 16.3 39 17.2 39 18.1 39 18.2 38.9 19.2 38.8 20.2 38.6 20.9 38.6 21.2 38.3 22.1 38 23.1 37.8 23.7 37.7 24 37.3 24.9 36.8 25.8 36.5 26.4 36.4 26.6 35.8 27.5 35.3 28.3 35 28.8 34.7 29.1 34.1 29.9 33.5 30.7 33.1 31.1 32.8 31.4 32.1 32.1 31.4 32.8 31.1 33.1 30.7 33.5 29.9 34.1 29.1 34.7 28.8 35 28.3 35.3 27.5 35.8 26.6 36.4 26.4 36.5 25.8 36.8 24.9 37.3 24 37.7 23.7 37.8 23.1 38 22.1 38.3 21.2 38.6 20.9 38.6 20.2 38.8 19.2 38.9 18.2 39 18.1 39 17.2 39 16.3 38.9 15.3 38.7 15.2 38.7 14.3 38.4 13.4 38 12.5 37.5 12.5 37.5 11.7 36.9 11.1 36.3 10.5 35.5 10.5 35.5 10 34.6 9.6 33.7 9.3 32.8 9.3 32.7 9.1 31.7 9 30.8 9 29.9 9 29.8 9.1 28.8 9.2 27.8 9.4 27.1 9.4 26.8 9.7 25.9 10 24.9 10.2 24.3 10.3 24 10.7 23.1 11.2 22.2 11.5 21.6 11.6 21.4 12.2 20.5 12.7 19.7 13 19.2 13.3 18.9 13.9 18.1 14.5 17.3 14.9 16.9 15.2 16.6 15.9 15.9 16.6 15.2 16.9 14.9 17.3 14.5 18.1 13.9 18.9 13.3 19.2 13 19.7 12.7 20.5 12.2 21.4 11.6 21.6 11.5 22.2 11.2 23.1 10.7 24 10.3Z",
515
+ "M22.5 7.8 23.2 7.2 24.2 7 25.1 7.4 25.7 8.1 26.2 9 26.8 9.8 27.3 10.6 28.1 11.2 29 11.3 30 11 30.9 10.6 31.8 10.3 32.8 9.9 33.7 10 34.5 10.6 34.9 11.5 34.8 12.5 34.8 13.5 34.7 14.5 34.7 15.5 35.2 16.3 36 16.8 37 17 37.9 17.3 38.9 17.5 39.8 17.9 40.4 18.7 40.5 19.7 40 20.5 39.3 21.3 38.7 22 38.1 22.8 37.6 23.7 37.7 24.6 38.3 25.4 38.9 26.2 39.6 27 40.2 27.7 40.5 28.6 40.3 29.6 39.5 30.3 38.6 30.6 37.6 30.8 36.7 31 35.7 31.3 35 31.9 34.6 32.8 34.7 33.8 34.8 34.8 34.8 35.8 34.8 36.8 34.3 37.6 33.4 38.1 32.4 38 31.5 37.6 30.6 37.2 29.7 36.9 28.7 36.6 27.8 36.9 27.1 37.6 26.6 38.5 26.1 39.3 25.5 40.2 24.8 40.8 23.8 41 22.9 40.6 22.3 39.9 21.8 39 21.2 38.2 20.7 37.4 19.9 36.8 19 36.7 18 37 17.1 37.4 16.2 37.7 15.2 38.1 14.3 38 13.5 37.4 13.1 36.5 13.2 35.5 13.2 34.5 13.3 33.5 13.3 32.5 12.8 31.7 12 31.2 11 31 10.1 30.7 9.1 30.5 8.2 30.1 7.6 29.3 7.5 28.3 8 27.5 8.7 26.7 9.3 26 9.9 25.2 10.4 24.3 10.3 23.4 9.7 22.6 9.1 21.8 8.4 21 7.8 20.3 7.5 19.4 7.7 18.4 8.5 17.7 9.4 17.4 10.4 17.2 11.3 17 12.3 16.7 13 16.1 13.4 15.2 13.3 14.2 13.2 13.2 13.2 12.2 13.2 11.2 13.7 10.4 14.6 9.9 15.6 10 16.5 10.4 17.4 10.8 18.3 11.1 19.3 11.4 20.2 11.1 20.9 10.4 21.4 9.5 21.9 8.7Z"
516
+ ].join(";");
517
+ var ROTATE_KEY_TIMES = "0; 0.14; 0.29; 0.43; 0.57; 0.71; 0.86; 1";
518
+ var ROTATE_KEY_SPLINES = [
519
+ "0.5 0.2 0 0.8",
520
+ "0.5 0.2 0 0.8",
521
+ "0.5 0.2 0 0.8",
522
+ "0.5 0.2 0 0.8",
523
+ "0.5 0.2 0 0.8",
524
+ "0.5 0.2 0 0.8",
525
+ "0.5 0.2 0 0.8"
526
+ ].join("; ");
527
+ var ROTATE_VALUES = "0 24 24; 154 24 24; 309 24 24; 463 24 24; 617 24 24; 771 24 24; 926 24 24; 1080 24 24";
528
+ var DETERMINATE_CIRCLE = "M24 7 C34.49 7 41 13.51 41 24 C41 34.49 34.49 41 24 41 C13.51 41 7 34.49 7 24 C7 13.51 13.51 7 24 7 Z";
529
+ var DETERMINATE_SOFT_BURST = "M20.9 10.4 21.4 9.5 21.9 8.7 22.5 7.8 23.2 7.2 24.2 7 25.1 7.4 25.7 8.1 26.2 9 26.8 9.8 27.3 10.6 28.1 11.2 29 11.3 30 11 30.9 10.6 31.8 10.3 32.8 9.9 33.7 10 34.5 10.6 34.9 11.5 34.8 12.5 34.8 13.5 34.7 14.5 34.7 15.5 35.2 16.3 36 16.8 37 17.1 37.9 17.3 38.9 17.5 39.8 17.9 40.4 18.7 40.5 19.7 40 20.5 39.4 21.3 38.7 22 38.1 22.8 37.6 23.7 37.7 24.6 38.3 25.5 38.9 26.2 39.6 27 40.2 27.7 40.5 28.7 40.3 29.6 39.5 30.3 38.6 30.6 37.6 30.8 36.7 31 35.7 31.3 35 31.9 34.6 32.8 34.7 33.8 34.8 34.8 34.9 35.8 34.8 36.8 34.3 37.6 33.4 38.1 32.4 38 31.5 37.6 30.6 37.2 29.7 36.9 28.7 36.6 27.8 36.9 27.1 37.6 26.6 38.5 26.1 39.3 25.5 40.2 24.8 40.8 23.8 41 22.9 40.6 22.3 39.9 21.8 39 21.2 38.2 20.7 37.4 19.9 36.8 19 36.7 18 37 17.1 37.4 16.2 37.7 15.2 38.1 14.3 38 13.5 37.4 13.1 36.5 13.2 35.5 13.2 34.5 13.3 33.5 13.3 32.5 12.8 31.7 12 31.2 11 31 10.1 30.7 9.1 30.5 8.2 30.1 7.6 29.3 7.5 28.3 8 27.5 8.7 26.7 9.3 26 9.9 25.2 10.4 24.3 10.3 23.4 9.7 22.5 9.1 21.8 8.4 21 7.8 20.3 7.5 19.3 7.7 18.4 8.5 17.7 9.4 17.4 10.4 17.2 11.3 17 12.3 16.7 13 16.1 13.4 15.2 13.3 14.2 13.2 13.2 13.1 12.2 13.2 11.2 13.7 10.4 14.6 9.9 15.6 10 16.5 10.4 17.4 10.8 18.3 11.1 19.3 11.4 20.2 11.1Z";
530
+ var IndeterminateSvg = React17.memo(function IndeterminateSvg2({
531
+ size
532
+ }) {
533
+ const [ready, setReady] = React17.useState(false);
534
+ React17.useEffect(() => {
535
+ const raf = requestAnimationFrame(() => setReady(true));
536
+ return () => cancelAnimationFrame(raf);
537
+ }, []);
538
+ return /* @__PURE__ */ jsx(
539
+ "svg",
540
+ {
541
+ viewBox: "4 4 40 40",
542
+ width: size,
543
+ height: size,
544
+ fill: "currentColor",
545
+ xmlns: "http://www.w3.org/2000/svg",
546
+ "aria-hidden": "true",
547
+ focusable: "false",
548
+ children: /* @__PURE__ */ jsx("path", { d: SHAPE_VALUES.split(";")[0], children: ready && /* @__PURE__ */ jsxs(Fragment, { children: [
549
+ /* @__PURE__ */ jsx(
550
+ "animate",
551
+ {
552
+ attributeName: "d",
553
+ dur: "5s",
554
+ repeatCount: "indefinite",
555
+ calcMode: "spline",
556
+ keySplines: SHAPE_KEY_SPLINES,
557
+ keyTimes: SHAPE_KEY_TIMES,
558
+ values: SHAPE_VALUES
559
+ }
560
+ ),
561
+ /* @__PURE__ */ jsx(
562
+ "animateTransform",
563
+ {
564
+ attributeName: "transform",
565
+ attributeType: "XML",
566
+ type: "rotate",
567
+ dur: "5s",
568
+ repeatCount: "indefinite",
569
+ calcMode: "spline",
570
+ keySplines: ROTATE_KEY_SPLINES,
571
+ keyTimes: ROTATE_KEY_TIMES,
572
+ values: ROTATE_VALUES
573
+ }
574
+ )
575
+ ] }) })
576
+ }
577
+ );
578
+ });
579
+ var DeterminateSvg = React17.memo(function DeterminateSvg2({
580
+ size,
581
+ progress
582
+ }) {
583
+ const p = Math.min(1, Math.max(0, progress));
584
+ const rotation = -180 * p;
585
+ const pathD = p < 0.5 ? DETERMINATE_CIRCLE : DETERMINATE_SOFT_BURST;
586
+ return /* @__PURE__ */ jsx(
587
+ "svg",
588
+ {
589
+ viewBox: "4 4 40 40",
590
+ width: size,
591
+ height: size,
592
+ fill: "currentColor",
593
+ xmlns: "http://www.w3.org/2000/svg",
594
+ "aria-hidden": "true",
595
+ focusable: "false",
596
+ style: {
597
+ transform: `rotate(${rotation}deg)`,
598
+ transition: "transform 0.3s ease"
599
+ },
600
+ children: /* @__PURE__ */ jsx("path", { d: pathD, style: { transition: "d 0.3s ease" } })
601
+ }
602
+ );
603
+ });
604
+ var LoadingIndicator = React17.forwardRef(
605
+ (_a, ref) => {
606
+ var _b = _a, {
607
+ variant = "uncontained",
608
+ size = 48,
609
+ progress,
610
+ color,
611
+ className,
612
+ "aria-label": ariaLabel,
613
+ style
614
+ } = _b, restProps = __objRest(_b, [
615
+ "variant",
616
+ "size",
617
+ "progress",
618
+ "color",
619
+ "className",
620
+ "aria-label",
621
+ "style"
622
+ ]);
623
+ const isContained = variant === "contained";
624
+ const isDeterminate = progress !== void 0;
625
+ const clampedSize = Math.min(240, Math.max(24, size));
626
+ const scaleFactor = clampedSize / 48;
627
+ const containerSize = isContained ? 38 * scaleFactor : void 0;
628
+ const indicatorSize = isContained ? 24 * scaleFactor : clampedSize;
629
+ const activeColor = isContained ? color != null ? color : "var(--md-sys-color-indicator-contained-active)" : color != null ? color : "var(--md-sys-color-indicator-active)";
630
+ const ariaNow = isDeterminate ? Math.round(Math.min(1, Math.max(0, progress)) * 100) : void 0;
631
+ return /* @__PURE__ */ jsx(
632
+ "div",
633
+ __spreadProps(__spreadValues({
634
+ ref,
635
+ role: "progressbar",
636
+ "aria-label": ariaLabel,
637
+ "aria-valuemin": 0,
638
+ "aria-valuemax": 100,
639
+ "aria-valuenow": ariaNow,
640
+ className: cn(
641
+ "inline-flex items-center justify-center shrink-0",
642
+ className
643
+ ),
644
+ style: __spreadValues({
645
+ width: clampedSize,
646
+ height: clampedSize,
647
+ color: activeColor
648
+ }, style)
649
+ }, restProps), {
650
+ children: isContained ? /* @__PURE__ */ jsx(
651
+ "div",
652
+ {
653
+ className: "flex items-center justify-center rounded-full",
654
+ style: {
655
+ width: containerSize,
656
+ height: containerSize,
657
+ backgroundColor: "var(--md-sys-color-indicator-contained-container)"
658
+ },
659
+ children: isDeterminate ? /* @__PURE__ */ jsx(DeterminateSvg, { size: indicatorSize, progress }) : /* @__PURE__ */ jsx(IndeterminateSvg, { size: indicatorSize })
660
+ }
661
+ ) : isDeterminate ? /* @__PURE__ */ jsx(DeterminateSvg, { size: indicatorSize, progress }) : /* @__PURE__ */ jsx(IndeterminateSvg, { size: indicatorSize })
662
+ })
663
+ );
664
+ }
665
+ );
666
+ LoadingIndicator.displayName = "LoadingIndicator";
667
+
668
+ // src/ui/progress-indicator/utils.ts
669
+ function easeInOutCubic(x) {
670
+ return x < 0.5 ? 4 * x * x * x : 1 - (-2 * x + 2) ** 3 / 2;
671
+ }
672
+ function generateWavyCircularPath(center, radius, amplitude, wavelength) {
673
+ const circumference = 2 * Math.PI * radius;
674
+ const numWaves = Math.max(
675
+ 3,
676
+ Math.round(circumference / Math.max(1, wavelength))
677
+ );
678
+ const steps = numWaves * 4;
679
+ const dt = 2 * Math.PI / steps;
680
+ const rAt = (t) => radius + amplitude * Math.sin(numWaves * t);
681
+ const drAt = (t) => amplitude * numWaves * Math.cos(numWaves * t);
682
+ const xAt = (t) => center + rAt(t) * Math.cos(t);
683
+ const yAt = (t) => center + rAt(t) * Math.sin(t);
684
+ const dxAt = (t) => drAt(t) * Math.cos(t) - rAt(t) * Math.sin(t);
685
+ const dyAt = (t) => drAt(t) * Math.sin(t) + rAt(t) * Math.cos(t);
686
+ let d = "";
687
+ const tStart = 0;
688
+ for (let i = 0; i < steps; i++) {
689
+ const t0 = tStart + i * dt;
690
+ const t1 = tStart + (i + 1) * dt;
691
+ const scale = dt / 3;
692
+ const cp1x = xAt(t0) + scale * dxAt(t0);
693
+ const cp1y = yAt(t0) + scale * dyAt(t0);
694
+ const cp2x = xAt(t1) - scale * dxAt(t1);
695
+ const cp2y = yAt(t1) - scale * dyAt(t1);
696
+ if (i === 0) d += `M ${xAt(t0).toFixed(2)} ${yAt(t0).toFixed(2)}`;
697
+ d += ` C ${cp1x.toFixed(2)} ${cp1y.toFixed(2)}, ${cp2x.toFixed(2)} ${cp2y.toFixed(2)}, ${xAt(t1).toFixed(2)} ${yAt(t1).toFixed(2)}`;
698
+ }
699
+ d += " Z";
700
+ return d;
701
+ }
702
+ function getSinePath(startX, endX, phase, wl, amp) {
703
+ if (startX >= endX) return "";
704
+ let d = "";
705
+ const step = amp === 0 ? Math.max(10, endX - startX) : 1;
706
+ const yStart = Math.sin((startX + phase) / wl * 2 * Math.PI) * amp;
707
+ d += `M ${startX.toFixed(2)} ${yStart.toFixed(2)}`;
708
+ let nextX = Math.ceil(startX / step) * step;
709
+ if (nextX === startX) nextX += step;
710
+ while (nextX < endX) {
711
+ const y = Math.sin((nextX + phase) / wl * 2 * Math.PI) * amp;
712
+ d += ` L ${nextX.toFixed(2)} ${y.toFixed(2)}`;
713
+ nextX += step;
714
+ }
715
+ const yEnd = Math.sin((endX + phase) / wl * 2 * Math.PI) * amp;
716
+ d += ` L ${endX.toFixed(2)} ${yEnd.toFixed(2)}`;
717
+ return d;
718
+ }
719
+ var CircularProgress = React17.forwardRef(
720
+ (_a, ref) => {
721
+ var _b = _a, {
722
+ value,
723
+ size = 48,
724
+ trackHeight = 4,
725
+ shape = "flat",
726
+ amplitude,
727
+ wavelength,
728
+ gapSize = 4,
729
+ crawlerSpeed = 1,
730
+ color,
731
+ trackColor,
732
+ className,
733
+ "aria-label": ariaLabel
734
+ } = _b, restProps = __objRest(_b, [
735
+ "value",
736
+ "size",
737
+ "trackHeight",
738
+ "shape",
739
+ "amplitude",
740
+ "wavelength",
741
+ "gapSize",
742
+ "crawlerSpeed",
743
+ "color",
744
+ "trackColor",
745
+ "className",
746
+ "aria-label"
747
+ ]);
748
+ const isDeterminate = value !== void 0;
749
+ const clampedValue = isDeterminate ? Math.min(100, Math.max(0, value)) : 0;
750
+ const radius = (size - trackHeight) / 2;
751
+ const center = size / 2;
752
+ const activeColor = color || "var(--md-sys-color-indicator-active)";
753
+ const bgTrackColor = trackColor || "var(--md-sys-color-indicator-track)";
754
+ const isWavy = shape === "wavy";
755
+ const BASELINE_SIZE = 48;
756
+ const scaleFactor = size / BASELINE_SIZE;
757
+ const effectiveAmplitude = React17.useMemo(
758
+ () => amplitude != null ? amplitude : 1.6 * scaleFactor,
759
+ [amplitude, scaleFactor]
760
+ );
761
+ const effectiveWavelength = React17.useMemo(
762
+ () => wavelength != null ? wavelength : 15 * scaleFactor,
763
+ [wavelength, scaleFactor]
764
+ );
765
+ const wavyActivePath = React17.useMemo(
766
+ () => isWavy ? generateWavyCircularPath(
767
+ center,
768
+ radius,
769
+ effectiveAmplitude,
770
+ effectiveWavelength
771
+ ) : null,
772
+ [isWavy, center, radius, effectiveAmplitude, effectiveWavelength]
773
+ );
774
+ const circumference = React17.useMemo(() => 2 * Math.PI * radius, [radius]);
775
+ const gapForTrack = React17.useMemo(
776
+ () => (gapSize + trackHeight) / circumference,
777
+ [gapSize, trackHeight, circumference]
778
+ );
779
+ const activeAngularFraction = isDeterminate ? clampedValue / 100 : 0;
780
+ const trackOffset = isDeterminate ? activeAngularFraction + gapForTrack : 0;
781
+ const trackLength = isDeterminate ? Math.max(1e-3, 1 - activeAngularFraction - 2 * gapForTrack) : 1;
782
+ const ActiveCircleElem = m.circle;
783
+ const ActivePathElem = m.path;
784
+ return /* @__PURE__ */ jsx(
785
+ "div",
786
+ __spreadProps(__spreadValues({
787
+ ref,
788
+ role: "progressbar",
789
+ "aria-label": ariaLabel,
790
+ "aria-valuenow": isDeterminate ? clampedValue : void 0,
791
+ "aria-valuemin": 0,
792
+ "aria-valuemax": 100,
793
+ className: cn(
794
+ "relative inline-flex items-center justify-center shrink-0",
795
+ className
796
+ ),
797
+ style: { width: size, height: size }
798
+ }, restProps), {
799
+ children: /* @__PURE__ */ jsxs(LazyMotion, { features: domMax, strict: true, children: [
800
+ /* @__PURE__ */ jsxs(
801
+ "svg",
802
+ {
803
+ width: size,
804
+ height: size,
805
+ viewBox: `0 0 ${size} ${size}`,
806
+ "aria-hidden": "true",
807
+ style: { transform: "rotate(-90deg)", overflow: "visible" },
808
+ children: [
809
+ isDeterminate ? /* @__PURE__ */ jsx(
810
+ m.circle,
811
+ {
812
+ cx: center,
813
+ cy: center,
814
+ r: radius,
815
+ fill: "none",
816
+ stroke: bgTrackColor,
817
+ strokeWidth: trackHeight,
818
+ initial: { pathLength: trackLength, pathOffset: trackOffset },
819
+ animate: { pathLength: trackLength, pathOffset: trackOffset },
820
+ transition: { duration: 0.4, ease: [0.2, 0, 0, 1] },
821
+ strokeLinecap: "round"
822
+ }
823
+ ) : null,
824
+ isDeterminate && (isWavy ? /* @__PURE__ */ jsx(
825
+ ActivePathElem,
826
+ {
827
+ d: wavyActivePath != null ? wavyActivePath : "",
828
+ fill: "none",
829
+ stroke: activeColor,
830
+ strokeWidth: trackHeight,
831
+ strokeLinecap: "round",
832
+ initial: { pathLength: 0 },
833
+ animate: { pathLength: clampedValue / 100 },
834
+ transition: {
835
+ duration: 0.4,
836
+ ease: [0.2, 0, 0, 1]
837
+ }
838
+ }
839
+ ) : /* @__PURE__ */ jsx(
840
+ ActiveCircleElem,
841
+ {
842
+ cx: center,
843
+ cy: center,
844
+ r: radius,
845
+ fill: "none",
846
+ stroke: activeColor,
847
+ strokeWidth: trackHeight,
848
+ strokeLinecap: "round",
849
+ initial: { pathLength: 0 },
850
+ animate: { pathLength: clampedValue / 100 },
851
+ transition: {
852
+ duration: 0.4,
853
+ ease: [0.2, 0, 0, 1]
854
+ }
855
+ }
856
+ ))
857
+ ]
858
+ }
859
+ ),
860
+ !isDeterminate && /* @__PURE__ */ jsxs(
861
+ m.svg,
862
+ {
863
+ width: size,
864
+ height: size,
865
+ viewBox: `0 0 ${size} ${size}`,
866
+ "aria-hidden": "true",
867
+ style: {
868
+ position: "absolute",
869
+ inset: 0,
870
+ overflow: "visible",
871
+ rotate: "-90deg",
872
+ transformOrigin: "center"
873
+ },
874
+ animate: { rotate: ["-90deg", "270deg"] },
875
+ transition: {
876
+ rotate: {
877
+ duration: 2 / Math.max(0.1, crawlerSpeed),
878
+ repeat: Number.POSITIVE_INFINITY,
879
+ ease: "linear"
880
+ }
881
+ },
882
+ children: [
883
+ /* @__PURE__ */ jsx(
884
+ m.circle,
885
+ {
886
+ cx: center,
887
+ cy: center,
888
+ r: radius,
889
+ fill: "none",
890
+ stroke: bgTrackColor,
891
+ strokeWidth: trackHeight,
892
+ strokeLinecap: "round",
893
+ style: { originX: "50%", originY: "50%" },
894
+ animate: {
895
+ pathLength: [
896
+ Math.max(1e-3, 1 - 0.1 - 2 * gapForTrack),
897
+ Math.max(1e-3, 1 - 0.75 - 2 * gapForTrack),
898
+ Math.max(1e-3, 1 - 0.1 - 2 * gapForTrack)
899
+ ],
900
+ rotate: [
901
+ `${(0.1 + gapForTrack) * 360}deg`,
902
+ `${(1 + gapForTrack) * 360}deg`,
903
+ `${(1.1 + gapForTrack) * 360}deg`
904
+ ]
905
+ },
906
+ transition: {
907
+ duration: 2 / Math.max(0.1, crawlerSpeed),
908
+ repeat: Number.POSITIVE_INFINITY,
909
+ ease: [0.4, 0, 0.2, 1]
910
+ }
911
+ }
912
+ ),
913
+ isWavy ? /* @__PURE__ */ jsx(
914
+ ActivePathElem,
915
+ {
916
+ d: wavyActivePath != null ? wavyActivePath : "",
917
+ fill: "none",
918
+ stroke: activeColor,
919
+ strokeWidth: trackHeight,
920
+ strokeLinecap: "round",
921
+ style: { originX: "50%", originY: "50%" },
922
+ animate: {
923
+ pathLength: [0.1, 0.75, 0.1],
924
+ rotate: ["0deg", "90deg", "360deg"]
925
+ },
926
+ transition: {
927
+ duration: 2 / Math.max(0.1, crawlerSpeed),
928
+ repeat: Number.POSITIVE_INFINITY,
929
+ ease: [0.4, 0, 0.2, 1]
930
+ }
931
+ }
932
+ ) : /* @__PURE__ */ jsx(
933
+ ActiveCircleElem,
934
+ {
935
+ cx: center,
936
+ cy: center,
937
+ r: radius,
938
+ fill: "none",
939
+ stroke: activeColor,
940
+ strokeWidth: trackHeight,
941
+ strokeLinecap: "round",
942
+ style: { originX: "50%", originY: "50%" },
943
+ animate: {
944
+ pathLength: [0.1, 0.75, 0.1],
945
+ rotate: ["0deg", "90deg", "360deg"]
946
+ },
947
+ transition: {
948
+ duration: 2 / Math.max(0.1, crawlerSpeed),
949
+ repeat: Number.POSITIVE_INFINITY,
950
+ ease: [0.4, 0, 0.2, 1]
951
+ }
952
+ }
953
+ )
954
+ ]
955
+ }
956
+ )
957
+ ] })
958
+ })
959
+ );
960
+ }
961
+ );
962
+ CircularProgress.displayName = "CircularProgress";
963
+ function useContainerWidth() {
964
+ const [width, setWidth] = React17.useState(0);
965
+ const observerRef = React17.useRef(null);
966
+ const ref = React17.useCallback((node) => {
967
+ if (observerRef.current) {
968
+ observerRef.current.disconnect();
969
+ observerRef.current = null;
970
+ }
971
+ if (node) {
972
+ const obs = new ResizeObserver((entries) => {
973
+ const contentRect = entries[0].contentRect;
974
+ setWidth(contentRect.width);
975
+ });
976
+ obs.observe(node);
977
+ observerRef.current = obs;
978
+ }
979
+ }, []);
980
+ React17.useEffect(() => {
981
+ return () => {
982
+ if (observerRef.current) {
983
+ observerRef.current.disconnect();
984
+ }
985
+ };
986
+ }, []);
987
+ return [ref, width];
988
+ }
989
+ function useMergedRef(...refs) {
990
+ return React17.useCallback(
991
+ (node) => {
992
+ for (const ref of refs) {
993
+ if (typeof ref === "function") {
994
+ ref(node);
995
+ } else if (ref && typeof ref === "object") {
996
+ ref.current = node;
997
+ }
998
+ }
999
+ },
1000
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1001
+ [refs]
1002
+ );
1003
+ }
1004
+ var FlatLinearTrack = React17.memo(function FlatLinearTrack2({
1005
+ trackHeight,
1006
+ activeColor,
1007
+ trackColor,
1008
+ value,
1009
+ isRtl,
1010
+ gapSize,
1011
+ crawlerSpeed
1012
+ }) {
1013
+ const isDeterminate = value !== void 0;
1014
+ const radius = trackHeight / 2;
1015
+ const minActiveWidth = trackHeight;
1016
+ return /* @__PURE__ */ jsx(
1017
+ "div",
1018
+ {
1019
+ className: "relative w-full overflow-hidden",
1020
+ style: { height: trackHeight, borderRadius: radius },
1021
+ children: isDeterminate ? /* @__PURE__ */ jsxs(Fragment, { children: [
1022
+ /* @__PURE__ */ jsx(
1023
+ m.div,
1024
+ {
1025
+ className: "absolute inset-y-0",
1026
+ style: {
1027
+ backgroundColor: trackColor,
1028
+ borderRadius: radius,
1029
+ width: "100%"
1030
+ },
1031
+ initial: {
1032
+ [isRtl ? "right" : "left"]: `calc(max(${minActiveWidth}px, 0%) + ${gapSize}px)`
1033
+ },
1034
+ animate: {
1035
+ [isRtl ? "right" : "left"]: `calc(max(${minActiveWidth}px, ${value}%) + ${gapSize}px)`
1036
+ },
1037
+ transition: { duration: 0.4, ease: [0.2, 0, 0, 1] }
1038
+ }
1039
+ ),
1040
+ /* @__PURE__ */ jsx(
1041
+ m.div,
1042
+ {
1043
+ className: "absolute inset-y-0",
1044
+ style: __spreadValues({
1045
+ backgroundColor: activeColor,
1046
+ borderRadius: radius,
1047
+ minWidth: minActiveWidth
1048
+ }, isRtl ? { right: 0 } : { left: 0 }),
1049
+ initial: { width: `${minActiveWidth}px` },
1050
+ animate: { width: `${value}%` },
1051
+ transition: { duration: 0.4, ease: [0.2, 0, 0, 1] }
1052
+ }
1053
+ )
1054
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1055
+ /* @__PURE__ */ jsx(
1056
+ "div",
1057
+ {
1058
+ className: "absolute inset-0",
1059
+ style: { backgroundColor: trackColor, borderRadius: radius }
1060
+ }
1061
+ ),
1062
+ /* @__PURE__ */ jsx(
1063
+ m.div,
1064
+ {
1065
+ className: "absolute inset-y-0",
1066
+ style: { backgroundColor: activeColor, borderRadius: radius },
1067
+ initial: { left: "-40%", width: "40%" },
1068
+ animate: { left: ["-40%", "100%"], width: ["40%", "20%", "40%"] },
1069
+ transition: {
1070
+ duration: 2 / Math.max(0.1, crawlerSpeed),
1071
+ repeat: Number.POSITIVE_INFINITY,
1072
+ ease: "easeInOut"
1073
+ }
1074
+ }
1075
+ )
1076
+ ] })
1077
+ }
1078
+ );
1079
+ });
1080
+ var WavyLinearTrack = React17.memo(function WavyLinearTrack2({
1081
+ trackHeight,
1082
+ svgHeight,
1083
+ amplitude,
1084
+ wavelength,
1085
+ indeterminateWavelength,
1086
+ activeColor,
1087
+ trackColor,
1088
+ value,
1089
+ isRtl,
1090
+ gapSize,
1091
+ waveSpeed,
1092
+ crawlerSpeed,
1093
+ determinateAnimation,
1094
+ indeterminateAnimation,
1095
+ trackShape
1096
+ }) {
1097
+ const isDeterminate = typeof value === "number";
1098
+ const clampedValue = isDeterminate ? Math.max(0, Math.min(100, value)) : 100;
1099
+ const titleId = React17.useId();
1100
+ const [containerRef, width] = useContainerWidth();
1101
+ const activePathRef = React17.useRef(null);
1102
+ const trackPathRef = React17.useRef(null);
1103
+ const amplitudeMV = useMotionValue(amplitude);
1104
+ const fractionMV = useMotionValue(isDeterminate ? clampedValue / 100 : 1);
1105
+ React17.useEffect(() => {
1106
+ if (isDeterminate) {
1107
+ const fraction = clampedValue / 100;
1108
+ let targetAmp = amplitude;
1109
+ if (determinateAnimation === "md3") {
1110
+ targetAmp = fraction <= 0.1 || fraction >= 0.95 ? 0 : amplitude;
1111
+ }
1112
+ animate(amplitudeMV, targetAmp, {
1113
+ type: "spring",
1114
+ bounce: 0,
1115
+ duration: 0.5
1116
+ });
1117
+ animate(fractionMV, fraction, { duration: 0.4, ease: [0.2, 0, 0, 1] });
1118
+ }
1119
+ }, [
1120
+ clampedValue,
1121
+ isDeterminate,
1122
+ amplitude,
1123
+ amplitudeMV,
1124
+ fractionMV,
1125
+ determinateAnimation
1126
+ ]);
1127
+ const activeWavelength = Math.max(
1128
+ 1,
1129
+ isDeterminate ? wavelength : indeterminateWavelength
1130
+ );
1131
+ const trackAmp = trackShape === "wavy" ? amplitude : 0;
1132
+ useAnimationFrame((time) => {
1133
+ if (width === 0) return;
1134
+ const currentAmp = amplitudeMV.get();
1135
+ const phase = time / 1e3 * waveSpeed * activeWavelength;
1136
+ const capWidth = trackHeight / 2;
1137
+ let activePathD = "";
1138
+ let trackD = "";
1139
+ const totalGap = gapSize + trackHeight;
1140
+ if (isDeterminate) {
1141
+ const fraction = fractionMV.get();
1142
+ const barHead = fraction * width;
1143
+ const adjHead = Math.max(capWidth, Math.min(width - capWidth, barHead));
1144
+ const adjTail = capWidth;
1145
+ if (fraction > 0 && adjHead - adjTail > 0.1) {
1146
+ activePathD = getSinePath(
1147
+ adjTail,
1148
+ adjHead,
1149
+ phase,
1150
+ activeWavelength,
1151
+ currentAmp
1152
+ );
1153
+ } else if (fraction === 0) {
1154
+ activePathD = `M ${capWidth} 0 L ${capWidth + 0.01} 0`;
1155
+ }
1156
+ const trackStart = adjHead + totalGap;
1157
+ if (trackStart < width - capWidth) {
1158
+ trackD = getSinePath(
1159
+ trackStart,
1160
+ width - capWidth,
1161
+ phase,
1162
+ activeWavelength,
1163
+ trackAmp
1164
+ );
1165
+ }
1166
+ } else {
1167
+ const safeCrawlerSpeed = Math.max(0.1, crawlerSpeed);
1168
+ const activeLines = [];
1169
+ if (indeterminateAnimation === "continuous") {
1170
+ const cycle = 2e3 / safeCrawlerSpeed;
1171
+ const fraction = time % cycle / cycle;
1172
+ activeLines.push({
1173
+ tail: easeInOutCubic(Math.max(0, fraction * 1.5 - 0.5)),
1174
+ head: easeInOutCubic(Math.min(1, fraction * 1.5))
1175
+ });
1176
+ } else {
1177
+ const cycle = 1750 / safeCrawlerSpeed;
1178
+ const t = time % cycle * safeCrawlerSpeed;
1179
+ const l1H = easeInOutCubic(Math.max(0, Math.min(1, t / 1e3)));
1180
+ const l1T = easeInOutCubic(Math.max(0, Math.min(1, (t - 250) / 1e3)));
1181
+ const l2H = easeInOutCubic(Math.max(0, Math.min(1, (t - 650) / 850)));
1182
+ const l2T = easeInOutCubic(Math.max(0, Math.min(1, (t - 900) / 850)));
1183
+ activeLines.push({ tail: l1T, head: l1H });
1184
+ activeLines.push({ tail: l2T, head: l2H });
1185
+ }
1186
+ const segments = activeLines.map((line) => {
1187
+ const barTail = line.tail * width;
1188
+ const barHead = line.head * width;
1189
+ const adjTail = Math.max(
1190
+ capWidth,
1191
+ Math.min(width - capWidth, barTail)
1192
+ );
1193
+ const adjHead = Math.max(
1194
+ capWidth,
1195
+ Math.min(width - capWidth, barHead)
1196
+ );
1197
+ return { adjTail, adjHead };
1198
+ }).filter((seg) => seg.adjHead - seg.adjTail > 0.1);
1199
+ activePathD = segments.map(
1200
+ (seg) => getSinePath(
1201
+ seg.adjTail,
1202
+ seg.adjHead,
1203
+ phase,
1204
+ activeWavelength,
1205
+ currentAmp
1206
+ )
1207
+ ).join(" ");
1208
+ let currentTrackX = capWidth;
1209
+ for (const seg of segments) {
1210
+ const trackEnd = seg.adjTail - totalGap;
1211
+ if (trackEnd > currentTrackX) {
1212
+ trackD += `${getSinePath(currentTrackX, trackEnd, phase, activeWavelength, trackAmp)} `;
1213
+ }
1214
+ currentTrackX = Math.max(currentTrackX, seg.adjHead + totalGap);
1215
+ }
1216
+ if (currentTrackX < width - capWidth) {
1217
+ trackD += getSinePath(
1218
+ currentTrackX,
1219
+ width - capWidth,
1220
+ phase,
1221
+ activeWavelength,
1222
+ trackAmp
1223
+ );
1224
+ }
1225
+ }
1226
+ if (activePathRef.current)
1227
+ activePathRef.current.setAttribute("d", activePathD);
1228
+ if (trackPathRef.current)
1229
+ trackPathRef.current.setAttribute("d", trackD.trim());
1230
+ });
1231
+ return /* @__PURE__ */ jsx(
1232
+ "div",
1233
+ {
1234
+ ref: containerRef,
1235
+ className: "relative w-full overflow-hidden",
1236
+ style: { height: svgHeight },
1237
+ children: width > 0 && /* @__PURE__ */ jsxs(
1238
+ "svg",
1239
+ {
1240
+ className: "absolute inset-0 w-full h-full",
1241
+ style: {
1242
+ overflow: "visible",
1243
+ transform: isRtl ? "scaleX(-1)" : void 0
1244
+ },
1245
+ "aria-labelledby": titleId,
1246
+ children: [
1247
+ /* @__PURE__ */ jsx("title", { id: titleId, children: isDeterminate ? `Progress: ${clampedValue}%` : "Indeterminate loading progress" }),
1248
+ /* @__PURE__ */ jsxs("g", { transform: `translate(0, ${svgHeight / 2})`, children: [
1249
+ /* @__PURE__ */ jsx(
1250
+ "path",
1251
+ {
1252
+ ref: trackPathRef,
1253
+ fill: "none",
1254
+ stroke: trackColor,
1255
+ strokeWidth: trackHeight,
1256
+ strokeLinecap: "round"
1257
+ }
1258
+ ),
1259
+ /* @__PURE__ */ jsx(
1260
+ "path",
1261
+ {
1262
+ ref: activePathRef,
1263
+ fill: "none",
1264
+ stroke: activeColor,
1265
+ strokeWidth: trackHeight,
1266
+ strokeLinecap: "round"
1267
+ }
1268
+ )
1269
+ ] })
1270
+ ]
1271
+ }
1272
+ )
1273
+ }
1274
+ );
1275
+ });
1276
+ var LinearProgress = React17.forwardRef(
1277
+ (_a, ref) => {
1278
+ var _b = _a, {
1279
+ value,
1280
+ shape = "flat",
1281
+ trackShape,
1282
+ trackHeight = 4,
1283
+ amplitude,
1284
+ wavelength = 40,
1285
+ indeterminateWavelength = 20,
1286
+ waveSpeed = 1,
1287
+ crawlerSpeed = 1,
1288
+ determinateAnimation = "md3",
1289
+ indeterminateAnimation = "continuous",
1290
+ gapSize = 4,
1291
+ showStopIndicator = "auto",
1292
+ color,
1293
+ trackColor,
1294
+ className,
1295
+ "aria-label": ariaLabel
1296
+ } = _b, restProps = __objRest(_b, [
1297
+ "value",
1298
+ "shape",
1299
+ "trackShape",
1300
+ "trackHeight",
1301
+ "amplitude",
1302
+ "wavelength",
1303
+ "indeterminateWavelength",
1304
+ "waveSpeed",
1305
+ "crawlerSpeed",
1306
+ "determinateAnimation",
1307
+ "indeterminateAnimation",
1308
+ "gapSize",
1309
+ "showStopIndicator",
1310
+ "color",
1311
+ "trackColor",
1312
+ "className",
1313
+ "aria-label"
1314
+ ]);
1315
+ const isDeterminate = value !== void 0;
1316
+ const clampedValue = isDeterminate ? Math.min(100, Math.max(0, value)) : 0;
1317
+ const containerRef = React17.useRef(null);
1318
+ const mergedRef = useMergedRef(ref, containerRef);
1319
+ const [isRtl, setIsRtl] = React17.useState(false);
1320
+ React17.useEffect(() => {
1321
+ if (containerRef.current) {
1322
+ const dir = getComputedStyle(containerRef.current).direction;
1323
+ setIsRtl(dir === "rtl");
1324
+ }
1325
+ }, []);
1326
+ const isWavy = shape === "wavy";
1327
+ const resolvedTrackShape = trackShape != null ? trackShape : shape;
1328
+ const effectiveAmplitude = React17.useMemo(() => amplitude != null ? amplitude : 3, [amplitude]);
1329
+ const svgHeight = React17.useMemo(
1330
+ () => isWavy ? trackHeight + effectiveAmplitude * 2 : trackHeight,
1331
+ [isWavy, trackHeight, effectiveAmplitude]
1332
+ );
1333
+ const shouldShowStop = React17.useMemo(
1334
+ () => isDeterminate && resolvedTrackShape === "flat" && (showStopIndicator === true || showStopIndicator === "auto" && isDeterminate),
1335
+ [isDeterminate, resolvedTrackShape, showStopIndicator]
1336
+ );
1337
+ const stopSize = React17.useMemo(
1338
+ () => Math.max(2, trackHeight > 4 ? 4 : trackHeight / 2),
1339
+ [trackHeight]
1340
+ );
1341
+ const stopOffset = (trackHeight - stopSize) / 2;
1342
+ const activeColor = color || "var(--md-sys-color-indicator-active)";
1343
+ const bgTrackColor = trackColor || "var(--md-sys-color-indicator-track)";
1344
+ return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: /* @__PURE__ */ jsxs(
1345
+ "div",
1346
+ __spreadProps(__spreadValues({
1347
+ ref: mergedRef,
1348
+ role: "progressbar",
1349
+ "aria-label": ariaLabel,
1350
+ "aria-valuenow": isDeterminate ? clampedValue : void 0,
1351
+ "aria-valuemin": 0,
1352
+ "aria-valuemax": 100,
1353
+ className: cn(
1354
+ "relative flex w-full flex-col justify-center",
1355
+ className
1356
+ ),
1357
+ style: { height: svgHeight }
1358
+ }, restProps), {
1359
+ children: [
1360
+ isWavy ? /* @__PURE__ */ jsx(
1361
+ WavyLinearTrack,
1362
+ {
1363
+ trackHeight,
1364
+ svgHeight,
1365
+ amplitude: effectiveAmplitude,
1366
+ wavelength,
1367
+ indeterminateWavelength,
1368
+ activeColor,
1369
+ trackColor: bgTrackColor,
1370
+ value: isDeterminate ? clampedValue : void 0,
1371
+ isRtl,
1372
+ gapSize,
1373
+ waveSpeed,
1374
+ crawlerSpeed,
1375
+ determinateAnimation,
1376
+ indeterminateAnimation,
1377
+ trackShape: resolvedTrackShape
1378
+ }
1379
+ ) : /* @__PURE__ */ jsx(
1380
+ FlatLinearTrack,
1381
+ {
1382
+ trackHeight,
1383
+ activeColor,
1384
+ trackColor: bgTrackColor,
1385
+ value: isDeterminate ? clampedValue : void 0,
1386
+ isRtl,
1387
+ gapSize,
1388
+ crawlerSpeed
1389
+ }
1390
+ ),
1391
+ shouldShowStop && /* @__PURE__ */ jsx(
1392
+ "div",
1393
+ {
1394
+ "aria-hidden": "true",
1395
+ className: "absolute rounded-full",
1396
+ style: __spreadValues({
1397
+ width: stopSize,
1398
+ height: stopSize,
1399
+ backgroundColor: "var(--md-sys-color-indicator-stop)",
1400
+ top: svgHeight / 2 - stopSize / 2
1401
+ }, isRtl ? { left: stopOffset } : { right: stopOffset })
1402
+ }
1403
+ )
1404
+ ]
1405
+ })
1406
+ ) });
1407
+ }
1408
+ );
1409
+ LinearProgress.displayName = "LinearProgress";
1410
+ var ProgressIndicator = React17.forwardRef((props, ref) => {
1411
+ if (props.variant === "circular") {
1412
+ return /* @__PURE__ */ jsx(CircularProgress, __spreadValues({ ref }, props));
1413
+ }
1414
+ return /* @__PURE__ */ jsx(LinearProgress, __spreadValues({ ref }, props));
1415
+ });
1416
+ ProgressIndicator.displayName = "ProgressIndicator";
1417
+ var RippleItem = React17.memo(function RippleItem2({
1418
+ ripple,
1419
+ onDone
1420
+ }) {
1421
+ return /* @__PURE__ */ jsx(
1422
+ m.span,
1423
+ {
1424
+ "aria-hidden": "true",
1425
+ style: {
1426
+ position: "absolute",
1427
+ left: ripple.x - ripple.size / 2,
1428
+ top: ripple.y - ripple.size / 2,
1429
+ width: ripple.size,
1430
+ height: ripple.size,
1431
+ borderRadius: "50%",
1432
+ backgroundColor: "currentColor",
1433
+ pointerEvents: "none",
1434
+ transformOrigin: "center"
1435
+ },
1436
+ initial: { scale: 0, opacity: 0.12 },
1437
+ animate: { scale: 1, opacity: 0 },
1438
+ exit: { opacity: 0 },
1439
+ transition: {
1440
+ scale: { duration: 0.5, ease: [0.2, 0, 0, 1] },
1441
+ opacity: { duration: 0.4, ease: "easeOut", delay: 0.1 }
1442
+ },
1443
+ onAnimationComplete: () => onDone(ripple.id)
1444
+ },
1445
+ ripple.id
1446
+ );
1447
+ });
1448
+ function Ripple({
1449
+ ripples,
1450
+ onRippleDone,
1451
+ disabled = false,
1452
+ respectSystemMotion = true
1453
+ }) {
1454
+ const prefersReduced = useReducedMotion();
1455
+ if (disabled) return null;
1456
+ if (respectSystemMotion && prefersReduced) return null;
1457
+ return /* @__PURE__ */ jsx(AnimatePresence, { children: ripples.map((r) => /* @__PURE__ */ jsx(RippleItem, { ripple: r, onDone: onRippleDone }, r.id)) });
1458
+ }
1459
+ function useRippleState(options = {}) {
1460
+ const { disabled = false } = options;
1461
+ const [ripples, setRipples] = React17.useState([]);
1462
+ const onPointerDown = React17.useCallback(
1463
+ (e) => {
1464
+ if (disabled) return;
1465
+ const rect = e.currentTarget.getBoundingClientRect();
1466
+ const x = e.clientX - rect.left;
1467
+ const y = e.clientY - rect.top;
1468
+ const rippleSize = Math.hypot(rect.width, rect.height) * 2;
1469
+ setRipples((prev) => [
1470
+ ...prev,
1471
+ { id: Date.now(), x, y, size: rippleSize }
1472
+ ]);
1473
+ },
1474
+ [disabled]
1475
+ );
1476
+ const removeRipple = React17.useCallback((id) => {
1477
+ setRipples((prev) => prev.filter((r) => r.id !== id));
1478
+ }, []);
1479
+ return { ripples, onPointerDown, removeRipple };
1480
+ }
1481
+
1482
+ // src/ui/shared/constants.ts
1483
+ var SPRING_TRANSITION_FAST = FAST_EFFECTS_SPRING;
1484
+ var SPRING_TRANSITION = DEFAULT_EFFECTS_SPRING;
1485
+ var ICON_SPAN_VARIANTS = {
1486
+ initial: { scale: 0.01 },
1487
+ animate: { scale: 1 },
1488
+ exit: { scale: 0.01 }
1489
+ };
1490
+ function TouchTarget() {
1491
+ return /* @__PURE__ */ jsx(
1492
+ "span",
1493
+ {
1494
+ "aria-hidden": "true",
1495
+ className: "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 min-w-12 min-h-12 cursor-pointer pointer-events-none"
1496
+ }
1497
+ );
1498
+ }
1499
+ var SIZE_STYLES = {
1500
+ xs: "h-8 w-8",
1501
+ sm: "h-10 w-10",
1502
+ md: "h-14 w-14",
1503
+ lg: "h-24 w-24",
1504
+ xl: "h-[8.5rem] w-[8.5rem]"
1505
+ };
1506
+ var SIZE_ICON = {
1507
+ xs: { cls: "size-5", px: 20 },
1508
+ sm: { cls: "size-6", px: 24 },
1509
+ md: { cls: "size-6", px: 24 },
1510
+ lg: { cls: "size-8", px: 32 },
1511
+ xl: { cls: "size-10", px: 40 }
1512
+ };
1513
+ var RADIUS_MAP = {
1514
+ xs: {
1515
+ round: 16,
1516
+ square: 12,
1517
+ pressed: 8,
1518
+ selectedRound: 12,
1519
+ selectedSquare: 16
1520
+ },
1521
+ sm: {
1522
+ round: 20,
1523
+ square: 12,
1524
+ pressed: 8,
1525
+ selectedRound: 12,
1526
+ selectedSquare: 20
1527
+ },
1528
+ md: {
1529
+ round: 28,
1530
+ square: 16,
1531
+ pressed: 12,
1532
+ selectedRound: 16,
1533
+ selectedSquare: 28
1534
+ },
1535
+ lg: {
1536
+ round: 48,
1537
+ square: 28,
1538
+ pressed: 16,
1539
+ selectedRound: 28,
1540
+ selectedSquare: 48
1541
+ },
1542
+ xl: {
1543
+ round: 68,
1544
+ square: 28,
1545
+ pressed: 16,
1546
+ selectedRound: 28,
1547
+ selectedSquare: 68
1548
+ }
1549
+ };
1550
+ var SIZE_OUTLINE_WIDTH = {
1551
+ xs: "border",
1552
+ sm: "border",
1553
+ md: "border",
1554
+ lg: "border-2",
1555
+ xl: "border-[3px]"
1556
+ };
1557
+ var colorStyles = {
1558
+ standard: {
1559
+ default: "text-m3-on-surface-variant hover:bg-m3-on-surface-variant/8 active:bg-m3-on-surface-variant/12",
1560
+ selected: "text-m3-primary hover:bg-m3-primary/8 active:bg-m3-primary/12"
1561
+ },
1562
+ filled: {
1563
+ default: "bg-m3-surface-container text-m3-on-surface-variant hover:bg-m3-on-surface-variant/8 active:bg-m3-on-surface-variant/12",
1564
+ selected: "bg-m3-primary text-m3-on-primary hover:brightness-95 active:brightness-90"
1565
+ },
1566
+ tonal: {
1567
+ default: "bg-m3-secondary-container text-m3-on-secondary-container hover:bg-m3-on-secondary-container/8 active:bg-m3-on-secondary-container/12",
1568
+ selected: "bg-m3-secondary text-m3-on-secondary hover:brightness-95 active:brightness-90"
1569
+ },
1570
+ outlined: {
1571
+ default: "border-m3-outline-variant text-m3-on-surface-variant hover:bg-m3-on-surface-variant/8 active:bg-m3-on-surface-variant/12",
1572
+ selected: "bg-m3-inverse-surface text-m3-inverse-on-surface border-transparent hover:brightness-95 active:brightness-90"
1573
+ }
1574
+ };
1575
+ var baseIconButtonClasses = [
1576
+ "relative shrink-0 inline-flex items-center justify-center",
1577
+ "select-none cursor-pointer",
1578
+ "transition-[background-color,color,border-color,box-shadow,opacity,filter] duration-200",
1579
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-m3-primary focus-visible:ring-offset-2",
1580
+ "disabled:pointer-events-none disabled:opacity-[0.38]"
1581
+ ];
1582
+ function resolveAnimateRadius(radiusConfig, shape, isToggle, isSelected) {
1583
+ if (isToggle && isSelected) {
1584
+ return shape === "round" ? radiusConfig.selectedRound : radiusConfig.selectedSquare;
1585
+ }
1586
+ return shape === "round" ? radiusConfig.round : radiusConfig.square;
1587
+ }
1588
+ function resolveDisabledBgClass(colorStyle) {
1589
+ if (colorStyle === "filled" || colorStyle === "tonal") {
1590
+ return "disabled:bg-m3-on-surface/12 disabled:text-m3-on-surface/[0.38]";
1591
+ }
1592
+ if (colorStyle === "outlined") {
1593
+ return "disabled:text-m3-on-surface/[0.38] disabled:border-m3-on-surface/[0.12]";
1594
+ }
1595
+ return "disabled:text-m3-on-surface/[0.38]";
1596
+ }
1597
+ var IconButtonComponent = React17.forwardRef(
1598
+ (_a, ref) => {
1599
+ var _b = _a, {
1600
+ className,
1601
+ style,
1602
+ variant = "default",
1603
+ colorStyle = "standard",
1604
+ size = "sm",
1605
+ shape = "round",
1606
+ selected,
1607
+ loading = false,
1608
+ loadingVariant = "loading-indicator",
1609
+ iconSize,
1610
+ children,
1611
+ onClick,
1612
+ onKeyDown,
1613
+ "aria-label": ariaLabel
1614
+ } = _b, restProps = __objRest(_b, [
1615
+ "className",
1616
+ "style",
1617
+ "variant",
1618
+ "colorStyle",
1619
+ "size",
1620
+ "shape",
1621
+ "selected",
1622
+ "loading",
1623
+ "loadingVariant",
1624
+ "iconSize",
1625
+ "children",
1626
+ "onClick",
1627
+ "onKeyDown",
1628
+ "aria-label"
1629
+ ]);
1630
+ var _a2, _b2;
1631
+ const isToggle = variant === "toggle";
1632
+ const isSelected = isToggle && !!selected;
1633
+ const resolvedColorClass = React17.useMemo(
1634
+ () => isSelected ? colorStyles[colorStyle].selected : colorStyles[colorStyle].default,
1635
+ [isSelected, colorStyle]
1636
+ );
1637
+ const outlineWidthClass = React17.useMemo(
1638
+ () => {
1639
+ var _a3;
1640
+ return colorStyle === "outlined" && !isSelected ? (_a3 = SIZE_OUTLINE_WIDTH[size]) != null ? _a3 : "border" : "";
1641
+ },
1642
+ [colorStyle, isSelected, size]
1643
+ );
1644
+ const disabledBgClass = React17.useMemo(
1645
+ () => resolveDisabledBgClass(colorStyle),
1646
+ [colorStyle]
1647
+ );
1648
+ const radiusConfig = (_a2 = RADIUS_MAP[size]) != null ? _a2 : RADIUS_MAP.sm;
1649
+ const animateRadius = resolveAnimateRadius(
1650
+ radiusConfig,
1651
+ shape,
1652
+ isToggle,
1653
+ isSelected
1654
+ );
1655
+ const pressedRadius = radiusConfig.pressed;
1656
+ const sizeIcon = (_b2 = SIZE_ICON[size]) != null ? _b2 : SIZE_ICON.sm;
1657
+ const iconClass = sizeIcon.cls;
1658
+ const defaultIconPx = sizeIcon.px;
1659
+ const iconPx = iconSize != null ? iconSize : defaultIconPx;
1660
+ const isCustomSize = iconSize != null && iconSize !== "inherit";
1661
+ const needsTouchTarget = size === "xs" || size === "sm";
1662
+ const { ripples, onPointerDown, removeRipple } = useRippleState({
1663
+ disabled: loading
1664
+ });
1665
+ const handleClick = React17.useCallback(
1666
+ (e) => {
1667
+ if (loading) {
1668
+ e.preventDefault();
1669
+ return;
1670
+ }
1671
+ onClick == null ? void 0 : onClick(e);
1672
+ },
1673
+ [loading, onClick]
1674
+ );
1675
+ const handleKeyDown = React17.useCallback(
1676
+ (e) => {
1677
+ if (loading) return;
1678
+ if ((e.key === "Enter" || e.key === " ") && onClick) {
1679
+ e.preventDefault();
1680
+ e.currentTarget.click();
1681
+ }
1682
+ onKeyDown == null ? void 0 : onKeyDown(e);
1683
+ },
1684
+ [loading, onClick, onKeyDown]
1685
+ );
1686
+ return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: /* @__PURE__ */ jsxs(
1687
+ m.button,
1688
+ __spreadProps(__spreadValues({
1689
+ ref,
1690
+ type: "button",
1691
+ "aria-pressed": isToggle ? isSelected : void 0,
1692
+ "aria-label": ariaLabel,
1693
+ "aria-busy": loading || void 0,
1694
+ "aria-disabled": loading || restProps.disabled,
1695
+ onClick: handleClick,
1696
+ onPointerDown,
1697
+ onKeyDown: handleKeyDown,
1698
+ style,
1699
+ animate: { borderRadius: animateRadius },
1700
+ whileTap: { borderRadius: pressedRadius },
1701
+ transition: { borderRadius: SPRING_TRANSITION_FAST },
1702
+ className: cn(
1703
+ baseIconButtonClasses,
1704
+ resolvedColorClass,
1705
+ outlineWidthClass,
1706
+ disabledBgClass,
1707
+ "overflow-hidden",
1708
+ SIZE_STYLES[size],
1709
+ loading && "pointer-events-none opacity-75 cursor-not-allowed",
1710
+ className
1711
+ )
1712
+ }, restProps), {
1713
+ children: [
1714
+ needsTouchTarget && /* @__PURE__ */ jsx(TouchTarget, {}),
1715
+ /* @__PURE__ */ jsx(Ripple, { ripples, onRippleDone: removeRipple }),
1716
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", initial: false, children: loading ? /* @__PURE__ */ jsx(
1717
+ m.span,
1718
+ __spreadProps(__spreadValues({}, ICON_SPAN_VARIANTS), {
1719
+ transition: SPRING_TRANSITION,
1720
+ className: cn(
1721
+ "flex items-center justify-center shrink-0",
1722
+ iconSize != null ? void 0 : iconClass
1723
+ ),
1724
+ style: isCustomSize ? { width: `${iconSize}px`, height: `${iconSize}px` } : void 0,
1725
+ children: loadingVariant === "loading-indicator" ? /* @__PURE__ */ jsx(
1726
+ LoadingIndicator,
1727
+ {
1728
+ size: typeof iconPx === "number" ? iconPx : defaultIconPx,
1729
+ color: "currentColor",
1730
+ "aria-label": "Loading"
1731
+ }
1732
+ ) : /* @__PURE__ */ jsx(
1733
+ ProgressIndicator,
1734
+ {
1735
+ variant: "circular",
1736
+ size: typeof iconPx === "number" ? iconPx : defaultIconPx,
1737
+ color: "currentColor",
1738
+ trackColor: "transparent",
1739
+ "aria-label": "Loading"
1740
+ }
1741
+ )
1742
+ }),
1743
+ "loading"
1744
+ ) : /* @__PURE__ */ jsx(
1745
+ m.span,
1746
+ __spreadProps(__spreadValues({}, ICON_SPAN_VARIANTS), {
1747
+ transition: SPRING_TRANSITION,
1748
+ "aria-hidden": "true",
1749
+ className: cn(
1750
+ "flex items-center justify-center shrink-0 [&_svg]:w-full [&_svg]:h-full [&_.md-icon]:text-[length:inherit]!",
1751
+ iconSize != null ? void 0 : iconClass
1752
+ ),
1753
+ style: {
1754
+ fontSize: typeof iconPx === "number" ? `${iconPx}px` : iconPx,
1755
+ width: isCustomSize ? `${iconSize}px` : void 0,
1756
+ height: isCustomSize ? `${iconSize}px` : void 0
1757
+ },
1758
+ children
1759
+ }),
1760
+ "content"
1761
+ ) })
1762
+ ]
1763
+ })
1764
+ ) });
1765
+ }
1766
+ );
1767
+ IconButtonComponent.displayName = "IconButton";
1768
+ var IconButton = React17.memo(IconButtonComponent);
1769
+ var VARIANT_FONT = {
1770
+ outlined: "'Material Symbols Outlined'",
1771
+ rounded: "'Material Symbols Rounded'",
1772
+ sharp: "'Material Symbols Sharp'"
1773
+ };
1774
+ var IconComponent = React17.forwardRef(
1775
+ (_a, ref) => {
1776
+ var _b = _a, {
1777
+ name,
1778
+ variant = "outlined",
1779
+ fill = 0,
1780
+ weight = 400,
1781
+ grade = 0,
1782
+ opticalSize = 24,
1783
+ size,
1784
+ animateFill = false,
1785
+ className,
1786
+ style
1787
+ } = _b, restProps = __objRest(_b, [
1788
+ "name",
1789
+ "variant",
1790
+ "fill",
1791
+ "weight",
1792
+ "grade",
1793
+ "opticalSize",
1794
+ "size",
1795
+ "animateFill",
1796
+ "className",
1797
+ "style"
1798
+ ]);
1799
+ const fontVariationSettings = `'FILL' ${fill}, 'wght' ${weight}, 'GRAD' ${grade}, 'opsz' ${opticalSize}`;
1800
+ const computedStyle = __spreadValues({
1801
+ fontFamily: VARIANT_FONT[variant],
1802
+ fontSize: size === "inherit" ? "inherit" : size != null ? `${size}px` : `${opticalSize}px`,
1803
+ fontVariationSettings
1804
+ }, style);
1805
+ if (animateFill) {
1806
+ return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: /* @__PURE__ */ jsx(
1807
+ m.span,
1808
+ __spreadProps(__spreadValues({
1809
+ ref,
1810
+ className: cn(
1811
+ "md-icon inline-flex items-center justify-center shrink-0 select-none",
1812
+ className
1813
+ ),
1814
+ "aria-hidden": "true",
1815
+ animate: { fontVariationSettings },
1816
+ transition: SPRING_TRANSITION_FAST,
1817
+ style: computedStyle
1818
+ }, restProps), {
1819
+ children: name
1820
+ })
1821
+ ) });
1822
+ }
1823
+ return /* @__PURE__ */ jsx(
1824
+ "span",
1825
+ __spreadProps(__spreadValues({
1826
+ ref,
1827
+ className: cn(
1828
+ "md-icon inline-flex items-center justify-center shrink-0 select-none",
1829
+ className
1830
+ ),
1831
+ "aria-hidden": "true",
1832
+ style: computedStyle
1833
+ }, restProps), {
1834
+ children: name
1835
+ })
1836
+ );
1837
+ }
1838
+ );
1839
+ IconComponent.displayName = "Icon";
1840
+ var Icon = React17.memo(IconComponent);
1841
+ var selectorClassName = "flex items-center gap-1 rounded-m3-full px-2 py-1 text-[length:var(--md-typescale-title-small-size,14px)] font-[number:var(--md-typescale-title-small-weight,500)] text-m3-on-surface transition-m3-fast-effects hover:bg-m3-on-surface/8 focus-visible:outline-2 focus-visible:outline-m3-primary";
1842
+ var CalendarHeader = ({
1843
+ displayedMonthMs,
1844
+ activeSelector,
1845
+ displayMode,
1846
+ showModeToggle,
1847
+ title,
1848
+ headline,
1849
+ locale,
1850
+ canNavigatePrev,
1851
+ canNavigateNext,
1852
+ canNavigatePreviousYear,
1853
+ canNavigateNextYear,
1854
+ onPrev,
1855
+ onNext,
1856
+ onPreviousYear,
1857
+ onNextYear,
1858
+ onToggleSelector,
1859
+ onToggleMode
1860
+ }) => {
1861
+ const date = new Date(displayedMonthMs);
1862
+ const monthLabel = new Intl.DateTimeFormat(locale.locale, {
1863
+ month: "short",
1864
+ timeZone: "UTC"
1865
+ }).format(date);
1866
+ const yearLabel = String(date.getUTCFullYear());
1867
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 px-6 pb-4 pt-5", children: [
1868
+ /* @__PURE__ */ jsx("div", { className: "text-[length:var(--md-typescale-label-large-size,14px)] font-[number:var(--md-typescale-label-large-weight,500)] text-m3-on-surface-variant", children: title }),
1869
+ /* @__PURE__ */ jsx("div", { className: "mb-2 leading-tight text-[length:var(--md-typescale-headline-large-size,32px)] font-[number:var(--md-typescale-headline-large-weight,400)] text-m3-on-surface-variant", children: headline }),
1870
+ displayMode === "picker" && /* @__PURE__ */ jsxs("div", { className: "-mx-2 flex min-h-10 items-center gap-0.5", children: [
1871
+ activeSelector === null && /* @__PURE__ */ jsx(
1872
+ IconButton,
1873
+ {
1874
+ onClick: onPrev,
1875
+ "aria-label": "Previous month",
1876
+ disabled: !canNavigatePrev,
1877
+ size: "sm",
1878
+ children: /* @__PURE__ */ jsx(Icon, { name: "chevron_left" })
1879
+ }
1880
+ ),
1881
+ /* @__PURE__ */ jsxs(
1882
+ "button",
1883
+ {
1884
+ type: "button",
1885
+ onClick: () => onToggleSelector("month"),
1886
+ "aria-expanded": activeSelector === "month",
1887
+ "aria-controls": "date-picker-selection",
1888
+ className: cn(
1889
+ selectorClassName,
1890
+ activeSelector === "month" && "bg-m3-on-surface/8"
1891
+ ),
1892
+ children: [
1893
+ monthLabel,
1894
+ /* @__PURE__ */ jsx(
1895
+ Icon,
1896
+ {
1897
+ name: "arrow_drop_down",
1898
+ className: cn(
1899
+ "text-[20px]",
1900
+ activeSelector === "month" && "rotate-180"
1901
+ )
1902
+ }
1903
+ )
1904
+ ]
1905
+ }
1906
+ ),
1907
+ activeSelector === null && /* @__PURE__ */ jsx(
1908
+ IconButton,
1909
+ {
1910
+ onClick: onNext,
1911
+ "aria-label": "Next month",
1912
+ disabled: !canNavigateNext,
1913
+ size: "sm",
1914
+ children: /* @__PURE__ */ jsx(Icon, { name: "chevron_right" })
1915
+ }
1916
+ ),
1917
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-0.5", children: [
1918
+ activeSelector === null && /* @__PURE__ */ jsx(
1919
+ IconButton,
1920
+ {
1921
+ onClick: onPreviousYear,
1922
+ "aria-label": "Previous year",
1923
+ disabled: !canNavigatePreviousYear,
1924
+ size: "sm",
1925
+ children: /* @__PURE__ */ jsx(Icon, { name: "chevron_left" })
1926
+ }
1927
+ ),
1928
+ /* @__PURE__ */ jsxs(
1929
+ "button",
1930
+ {
1931
+ type: "button",
1932
+ onClick: () => onToggleSelector("year"),
1933
+ "aria-expanded": activeSelector === "year",
1934
+ "aria-controls": "date-picker-selection",
1935
+ className: cn(
1936
+ selectorClassName,
1937
+ activeSelector === "year" && "bg-m3-on-surface/8"
1938
+ ),
1939
+ children: [
1940
+ yearLabel,
1941
+ /* @__PURE__ */ jsx(
1942
+ Icon,
1943
+ {
1944
+ name: "arrow_drop_down",
1945
+ className: cn(
1946
+ "text-[20px]",
1947
+ activeSelector === "year" && "rotate-180"
1948
+ )
1949
+ }
1950
+ )
1951
+ ]
1952
+ }
1953
+ ),
1954
+ activeSelector === null && /* @__PURE__ */ jsx(
1955
+ IconButton,
1956
+ {
1957
+ onClick: onNextYear,
1958
+ "aria-label": "Next year",
1959
+ disabled: !canNavigateNextYear,
1960
+ size: "sm",
1961
+ children: /* @__PURE__ */ jsx(Icon, { name: "chevron_right" })
1962
+ }
1963
+ ),
1964
+ showModeToggle && /* @__PURE__ */ jsx(
1965
+ IconButton,
1966
+ {
1967
+ onClick: onToggleMode,
1968
+ "aria-label": "Switch to text input",
1969
+ size: "sm",
1970
+ children: /* @__PURE__ */ jsx(Icon, { name: "keyboard" })
1971
+ }
1972
+ )
1973
+ ] })
1974
+ ] }),
1975
+ displayMode === "input" && showModeToggle && /* @__PURE__ */ jsx(
1976
+ IconButton,
1977
+ {
1978
+ onClick: onToggleMode,
1979
+ "aria-label": "Switch to calendar",
1980
+ size: "sm",
1981
+ className: "self-end",
1982
+ children: /* @__PURE__ */ jsx(Icon, { name: "calendar_month" })
1983
+ }
1984
+ ),
1985
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: formatMonthYear(displayedMonthMs, locale) })
1986
+ ] });
1987
+ };
1988
+ CalendarHeader.displayName = "CalendarHeader";
1989
+ var getMonthLabel = (month, locale) => new Intl.DateTimeFormat(locale, {
1990
+ month: "long",
1991
+ timeZone: "UTC"
1992
+ }).format(new Date(Date.UTC(2025, month, 1)));
1993
+ var MonthPickerList = ({
1994
+ selectedMonth,
1995
+ locale,
1996
+ onMonthSelect
1997
+ }) => {
1998
+ const selectedRef = React17.useRef(null);
1999
+ React17.useEffect(() => {
2000
+ var _a, _b;
2001
+ (_b = (_a = selectedRef.current) == null ? void 0 : _a.scrollIntoView) == null ? void 0 : _b.call(_a, {
2002
+ block: "center",
2003
+ behavior: "instant"
2004
+ });
2005
+ }, []);
2006
+ const months = React17.useMemo(
2007
+ () => Array.from({ length: 12 }, (_, month) => month),
2008
+ []
2009
+ );
2010
+ return /* @__PURE__ */ jsx(
2011
+ "div",
2012
+ {
2013
+ className: "h-full min-h-[240px] overflow-y-auto py-2",
2014
+ role: "listbox",
2015
+ "aria-label": "Select month",
2016
+ children: months.map((month) => {
2017
+ const isSelected = month === selectedMonth;
2018
+ return /* @__PURE__ */ jsxs(
2019
+ "button",
2020
+ {
2021
+ ref: isSelected ? selectedRef : void 0,
2022
+ type: "button",
2023
+ role: "option",
2024
+ "aria-selected": isSelected,
2025
+ onClick: () => onMonthSelect(month),
2026
+ className: cn(
2027
+ "flex min-h-12 w-full items-center gap-4 px-6 text-left",
2028
+ "text-[length:var(--md-typescale-body-large-size,16px)]",
2029
+ "text-m3-on-surface transition-m3-fast-effects",
2030
+ "hover:bg-m3-on-surface/8 focus-visible:outline-2 focus-visible:outline-m3-primary",
2031
+ isSelected && "bg-m3-secondary-container text-m3-on-secondary-container"
2032
+ ),
2033
+ children: [
2034
+ /* @__PURE__ */ jsx("span", { className: "w-6", "aria-hidden": true, children: isSelected && /* @__PURE__ */ jsx(Icon, { name: "check", className: "text-[20px]" }) }),
2035
+ getMonthLabel(month, locale.locale)
2036
+ ]
2037
+ },
2038
+ month
2039
+ );
2040
+ })
2041
+ }
2042
+ );
2043
+ };
2044
+ MonthPickerList.displayName = "MonthPickerList";
2045
+ var YearPickerList = ({
2046
+ selectedYear,
2047
+ yearRange,
2048
+ selectableDates,
2049
+ onYearSelect
2050
+ }) => {
2051
+ const selectedRef = React17.useRef(null);
2052
+ const years = React17.useMemo(() => {
2053
+ const [min, max] = yearRange;
2054
+ return Array.from({ length: max - min + 1 }, (_, index) => min + index);
2055
+ }, [yearRange]);
2056
+ React17.useEffect(() => {
2057
+ var _a, _b;
2058
+ (_b = (_a = selectedRef.current) == null ? void 0 : _a.scrollIntoView) == null ? void 0 : _b.call(_a, {
2059
+ block: "center",
2060
+ behavior: "instant"
2061
+ });
2062
+ }, []);
2063
+ return /* @__PURE__ */ jsx(
2064
+ "div",
2065
+ {
2066
+ className: "h-full min-h-[240px] overflow-y-auto py-2",
2067
+ role: "listbox",
2068
+ "aria-label": "Select year",
2069
+ children: years.map((year) => {
2070
+ const isSelected = year === selectedYear;
2071
+ const isSelectable = isYearSelectable(year, selectableDates);
2072
+ return /* @__PURE__ */ jsxs(
2073
+ "button",
2074
+ {
2075
+ ref: isSelected ? selectedRef : void 0,
2076
+ type: "button",
2077
+ role: "option",
2078
+ "aria-selected": isSelected,
2079
+ "aria-disabled": !isSelectable || void 0,
2080
+ disabled: !isSelectable,
2081
+ onClick: () => onYearSelect(year),
2082
+ className: cn(
2083
+ "flex min-h-12 w-full items-center gap-4 px-6 text-left",
2084
+ "text-[length:var(--md-typescale-body-large-size,16px)]",
2085
+ "text-m3-on-surface transition-m3-fast-effects",
2086
+ "hover:bg-m3-on-surface/8 focus-visible:outline-2 focus-visible:outline-m3-primary",
2087
+ isSelected && "bg-m3-secondary-container text-m3-on-secondary-container",
2088
+ !isSelectable && "cursor-not-allowed opacity-[0.38]"
2089
+ ),
2090
+ children: [
2091
+ /* @__PURE__ */ jsx("span", { className: "w-6", "aria-hidden": true, children: isSelected && /* @__PURE__ */ jsx(Icon, { name: "check", className: "text-[20px]" }) }),
2092
+ year
2093
+ ]
2094
+ },
2095
+ year
2096
+ );
2097
+ })
2098
+ }
2099
+ );
2100
+ };
2101
+ YearPickerList.displayName = "YearPickerList";
2102
+ var formatInputDate = (ms) => new Intl.DateTimeFormat("en-US", {
2103
+ month: "2-digit",
2104
+ day: "2-digit",
2105
+ year: "numeric",
2106
+ timeZone: "UTC"
2107
+ }).format(new Date(ms));
2108
+ var DatePicker = ({
2109
+ state,
2110
+ showModeToggle = true,
2111
+ title,
2112
+ headline,
2113
+ className
2114
+ }) => {
2115
+ const {
2116
+ selectedDateMs,
2117
+ displayedMonthMs,
2118
+ displayMode,
2119
+ yearRange,
2120
+ locale,
2121
+ selectableDates,
2122
+ selectDate,
2123
+ navigateMonth: navigateMonth2,
2124
+ navigateToMonth,
2125
+ setDisplayMode
2126
+ } = state;
2127
+ const [activeSelector, setActiveSelector] = React17.useState(null);
2128
+ const [slideDirection, setSlideDirection] = React17.useState(null);
2129
+ const handlePrev = React17.useCallback(() => {
2130
+ setSlideDirection("prev");
2131
+ navigateMonth2("prev");
2132
+ }, [navigateMonth2]);
2133
+ const handleNext = React17.useCallback(() => {
2134
+ setSlideDirection("next");
2135
+ navigateMonth2("next");
2136
+ }, [navigateMonth2]);
2137
+ const handleYearSelect = React17.useCallback(
2138
+ (year) => {
2139
+ const d = new Date(displayedMonthMs);
2140
+ navigateToMonth(year, d.getUTCMonth());
2141
+ setActiveSelector(null);
2142
+ },
2143
+ [displayedMonthMs, navigateToMonth]
2144
+ );
2145
+ const handleMonthSelect = React17.useCallback(
2146
+ (month) => {
2147
+ const d = new Date(displayedMonthMs);
2148
+ navigateToMonth(d.getUTCFullYear(), month);
2149
+ setActiveSelector(null);
2150
+ },
2151
+ [displayedMonthMs, navigateToMonth]
2152
+ );
2153
+ const [inputValue, setInputValue] = React17.useState(
2154
+ () => selectedDateMs !== null ? formatInputDate(selectedDateMs) : ""
2155
+ );
2156
+ const [inputError, setInputError] = React17.useState();
2157
+ const handleInputCommit = React17.useCallback(
2158
+ (ms) => {
2159
+ if (ms === null && inputValue.length > 0) {
2160
+ setInputError("Invalid date");
2161
+ } else {
2162
+ setInputError(void 0);
2163
+ selectDate(ms);
2164
+ }
2165
+ },
2166
+ [inputValue, selectDate]
2167
+ );
2168
+ const resolvedTitle = title != null ? title : "Select date";
2169
+ const resolvedHeadline = headline != null ? headline : selectedDateMs !== null ? formatDate(selectedDateMs, locale, {
2170
+ weekday: "short",
2171
+ month: "short",
2172
+ day: "numeric",
2173
+ timeZone: "UTC"
2174
+ }) : "\u2014";
2175
+ const currentYear = new Date(displayedMonthMs).getUTCFullYear();
2176
+ const currentMonth = new Date(displayedMonthMs).getUTCMonth();
2177
+ const computedAriaLabel = typeof resolvedTitle === "string" ? resolvedTitle : "Date picker";
2178
+ const canNavigatePrev = getNavigableMonth(displayedMonthMs, "prev", yearRange, selectableDates) !== null;
2179
+ const canNavigateNext = getNavigableMonth(displayedMonthMs, "next", yearRange, selectableDates) !== null;
2180
+ const canNavigatePreviousYear = isMonthNavigable(
2181
+ currentYear - 1,
2182
+ yearRange,
2183
+ selectableDates
2184
+ );
2185
+ const canNavigateNextYear = isMonthNavigable(
2186
+ currentYear + 1,
2187
+ yearRange,
2188
+ selectableDates
2189
+ );
2190
+ return /* @__PURE__ */ jsxs(
2191
+ "fieldset",
2192
+ {
2193
+ className: cn("flex flex-col select-none", className),
2194
+ style: { width: "min(100%, 360px)" },
2195
+ "aria-label": computedAriaLabel,
2196
+ children: [
2197
+ /* @__PURE__ */ jsx("legend", { className: "sr-only", children: computedAriaLabel }),
2198
+ /* @__PURE__ */ jsx(
2199
+ CalendarHeader,
2200
+ {
2201
+ displayedMonthMs,
2202
+ activeSelector,
2203
+ displayMode,
2204
+ showModeToggle,
2205
+ title: resolvedTitle,
2206
+ headline: resolvedHeadline,
2207
+ locale,
2208
+ canNavigatePrev,
2209
+ canNavigateNext,
2210
+ canNavigatePreviousYear,
2211
+ canNavigateNextYear,
2212
+ onPrev: handlePrev,
2213
+ onNext: handleNext,
2214
+ onPreviousYear: () => navigateToMonth(currentYear - 1, currentMonth),
2215
+ onNextYear: () => navigateToMonth(currentYear + 1, currentMonth),
2216
+ onToggleSelector: (selector) => setActiveSelector((active) => active === selector ? null : selector),
2217
+ onToggleMode: () => {
2218
+ setDisplayMode(displayMode === "picker" ? "input" : "picker");
2219
+ setActiveSelector(null);
2220
+ setInputError(void 0);
2221
+ }
2222
+ }
2223
+ ),
2224
+ /* @__PURE__ */ jsx(
2225
+ "div",
2226
+ {
2227
+ className: "relative flex-1 overflow-hidden",
2228
+ style: { minHeight: "296px" },
2229
+ children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: displayMode === "input" ? (
2230
+ // Input mode
2231
+ /* @__PURE__ */ jsx(
2232
+ DatePickerInput,
2233
+ {
2234
+ value: inputValue,
2235
+ onChange: setInputValue,
2236
+ onCommit: handleInputCommit,
2237
+ error: inputError,
2238
+ locale: locale.locale
2239
+ },
2240
+ "input-mode"
2241
+ )
2242
+ ) : activeSelector === "month" ? /* @__PURE__ */ jsx(
2243
+ "div",
2244
+ {
2245
+ id: "date-picker-selection",
2246
+ className: "absolute inset-0",
2247
+ children: /* @__PURE__ */ jsx(
2248
+ MonthPickerList,
2249
+ {
2250
+ selectedMonth: currentMonth,
2251
+ locale,
2252
+ onMonthSelect: handleMonthSelect
2253
+ }
2254
+ )
2255
+ },
2256
+ "month-list"
2257
+ ) : activeSelector === "year" ? /* @__PURE__ */ jsx(
2258
+ "div",
2259
+ {
2260
+ id: "date-picker-selection",
2261
+ className: "absolute inset-0",
2262
+ children: /* @__PURE__ */ jsx(
2263
+ YearPickerList,
2264
+ {
2265
+ selectedYear: currentYear,
2266
+ yearRange,
2267
+ selectableDates,
2268
+ onYearSelect: handleYearSelect
2269
+ }
2270
+ )
2271
+ },
2272
+ "year-list"
2273
+ ) : (
2274
+ // Calendar grid
2275
+ /* @__PURE__ */ jsx(
2276
+ CalendarGrid,
2277
+ {
2278
+ displayedMonthMs,
2279
+ slideDirection,
2280
+ locale,
2281
+ selectableDates,
2282
+ selectedMs: selectedDateMs,
2283
+ isRangePicker: false,
2284
+ onDaySelect: (ms) => {
2285
+ selectDate(ms);
2286
+ setInputValue(formatInputDate(ms));
2287
+ }
2288
+ },
2289
+ "calendar"
2290
+ )
2291
+ ) })
2292
+ }
2293
+ )
2294
+ ]
2295
+ }
2296
+ );
2297
+ };
2298
+ DatePicker.displayName = "DatePicker";
2299
+
2300
+ // src/ui/date-time-pickers/date-picker/date-picker.tokens.ts
2301
+ var DP_COLORS = {
2302
+ /** Dialog container — SurfaceContainerHigh */
2303
+ container: "var(--md-sys-color-surface-container-high)",
2304
+ /** Selected day background — Primary */
2305
+ daySelected: "var(--md-sys-color-primary)",
2306
+ /** Selected day text — OnPrimary */
2307
+ daySelectedText: "var(--md-sys-color-on-primary)",
2308
+ /** Today outline — Primary */
2309
+ todayOutline: "var(--md-sys-color-primary)",
2310
+ /** Today text — Primary */
2311
+ todayText: "var(--md-sys-color-primary)",
2312
+ /** Unselected day text — OnSurface */
2313
+ dayText: "var(--md-sys-color-on-surface)",
2314
+ /** Weekday header text — OnSurface */
2315
+ weekdayText: "var(--md-sys-color-on-surface)",
2316
+ /** Header headline + supporting text — OnSurfaceVariant */
2317
+ headerText: "var(--md-sys-color-on-surface-variant)",
2318
+ /** Range highlight background — SecondaryContainer */
2319
+ rangeHighlight: "var(--md-sys-color-secondary-container)",
2320
+ /** Range highlight text — OnSecondaryContainer */
2321
+ rangeText: "var(--md-sys-color-on-secondary-container)",
2322
+ /** Unselected year text — OnSurfaceVariant */
2323
+ yearText: "var(--md-sys-color-on-surface-variant)",
2324
+ /** Selected year background — Primary */
2325
+ yearSelected: "var(--md-sys-color-primary)",
2326
+ /** Selected year text — OnPrimary */
2327
+ yearSelectedText: "var(--md-sys-color-on-primary)",
2328
+ /** Scrim overlay */
2329
+ scrim: "var(--md-sys-color-scrim)"
2330
+ };
2331
+ var DP_SHAPE = {
2332
+ /** Dialog container — CornerExtraLarge = 28px */
2333
+ container: "var(--md-sys-shape-corner-extra-large)",
2334
+ /** Day cell — CornerFull = pill */
2335
+ day: "var(--md-sys-shape-corner-full)",
2336
+ /** Range start/end indicator — CornerFull */
2337
+ rangeEndpoint: "var(--md-sys-shape-corner-full)",
2338
+ /** Year chip — CornerFull */
2339
+ year: "var(--md-sys-shape-corner-full)"
2340
+ };
2341
+ var DP_SIZE = {
2342
+ /** Dialog container width */
2343
+ containerWidth: 360,
2344
+ /** Dialog container height */
2345
+ containerHeight: 568,
2346
+ /** Day cell width/height */
2347
+ dayCell: 40,
2348
+ /** Today border width */
2349
+ todayOutlineWidth: 1,
2350
+ /** Header container height */
2351
+ headerHeight: 120,
2352
+ /** Range picker header height */
2353
+ rangeHeaderHeight: 128,
2354
+ /** Year chip height */
2355
+ yearChipHeight: 36,
2356
+ /** Year chip width */
2357
+ yearChipWidth: 72
2358
+ };
2359
+ var DP_CLASSES = {
2360
+ /** Dialog container */
2361
+ dialog: [
2362
+ "bg-m3-surface-container-high",
2363
+ "rounded-m3-xl",
2364
+ "w-[360px]",
2365
+ "overflow-hidden",
2366
+ "elevation-3"
2367
+ ].join(" "),
2368
+ /** Day cell container */
2369
+ dayCell: [
2370
+ "relative flex items-center justify-center",
2371
+ "w-10 h-10 rounded-m3-full",
2372
+ "text-[length:var(--md-typescale-body-large-size,14px)]",
2373
+ "font-[var(--md-typescale-body-large-weight,400)]",
2374
+ "cursor-pointer select-none",
2375
+ "transition-m3-fast-effects"
2376
+ ].join(" "),
2377
+ /** Day cell: selected state */
2378
+ dayCellSelected: ["bg-m3-primary", "text-m3-on-primary"].join(" "),
2379
+ /** Day cell: today state */
2380
+ dayCellToday: [
2381
+ "outline outline-1 outline-m3-primary",
2382
+ "text-m3-primary"
2383
+ ].join(" "),
2384
+ /** Day cell: disabled state */
2385
+ dayCellDisabled: "opacity-[0.38] pointer-events-none cursor-not-allowed",
2386
+ /** Range highlight (between start and end) */
2387
+ rangeHighlight: "bg-m3-secondary-container",
2388
+ /** Weekday header row */
2389
+ weekdayRow: "text-[length:var(--md-typescale-body-large-size,14px)] text-m3-on-surface"
2390
+ };
2391
+ var ScrollArea = React17.forwardRef(
2392
+ (_a, ref) => {
2393
+ var _b = _a, {
2394
+ className,
2395
+ viewportClassName,
2396
+ children,
2397
+ type = "hover",
2398
+ orientation = "vertical",
2399
+ scrollHideDelay = 600,
2400
+ viewportRef,
2401
+ viewportProps
2402
+ } = _b, props = __objRest(_b, [
2403
+ "className",
2404
+ "viewportClassName",
2405
+ "children",
2406
+ "type",
2407
+ "orientation",
2408
+ "scrollHideDelay",
2409
+ "viewportRef",
2410
+ "viewportProps"
2411
+ ]);
2412
+ const [isTouchDevice, setIsTouchDevice] = React17.useState(false);
2413
+ React17.useEffect(() => {
2414
+ if (typeof window !== "undefined") {
2415
+ setIsTouchDevice(
2416
+ "ontouchstart" in window || navigator.maxTouchPoints > 0
2417
+ );
2418
+ }
2419
+ }, []);
2420
+ const resolvedType = React17.useMemo(() => {
2421
+ if (type === "hover" && isTouchDevice) {
2422
+ return "scroll";
2423
+ }
2424
+ return type;
2425
+ }, [type, isTouchDevice]);
2426
+ const radixType = resolvedType === "none" ? "always" : resolvedType;
2427
+ return /* @__PURE__ */ jsxs(
2428
+ RadixScrollArea.Root,
2429
+ __spreadProps(__spreadValues({
2430
+ ref,
2431
+ type: radixType,
2432
+ scrollHideDelay,
2433
+ className: cn("relative overflow-hidden flex flex-col", className)
2434
+ }, props), {
2435
+ children: [
2436
+ /* @__PURE__ */ jsx(
2437
+ RadixScrollArea.Viewport,
2438
+ __spreadProps(__spreadValues({
2439
+ ref: viewportRef
2440
+ }, viewportProps), {
2441
+ className: cn(
2442
+ "h-full w-full flex-1 min-h-0 min-w-0 rounded-[inherit]",
2443
+ "outline-none focus-visible:ring-2 focus-visible:ring-m3-primary focus-visible:ring-offset-1",
2444
+ viewportClassName,
2445
+ viewportProps == null ? void 0 : viewportProps.className
2446
+ ),
2447
+ children
2448
+ })
2449
+ ),
2450
+ (orientation === "vertical" || orientation === "both") && /* @__PURE__ */ jsx(
2451
+ ScrollAreaScrollbar,
2452
+ {
2453
+ orientation: "vertical",
2454
+ className: resolvedType === "none" ? "hidden" : void 0
2455
+ }
2456
+ ),
2457
+ (orientation === "horizontal" || orientation === "both") && /* @__PURE__ */ jsx(
2458
+ ScrollAreaScrollbar,
2459
+ {
2460
+ orientation: "horizontal",
2461
+ className: resolvedType === "none" ? "hidden" : void 0
2462
+ }
2463
+ ),
2464
+ /* @__PURE__ */ jsx(RadixScrollArea.Corner, { className: "bg-m3-surface-container" })
2465
+ ]
2466
+ })
2467
+ );
2468
+ }
2469
+ );
2470
+ ScrollArea.displayName = "ScrollArea";
2471
+ var ScrollAreaScrollbar = React17.forwardRef((_a, ref) => {
2472
+ var _b = _a, { className, orientation = "vertical" } = _b, props = __objRest(_b, ["className", "orientation"]);
2473
+ return /* @__PURE__ */ jsx(
2474
+ RadixScrollArea.Scrollbar,
2475
+ __spreadProps(__spreadValues({
2476
+ ref,
2477
+ orientation,
2478
+ className: cn(
2479
+ "flex touch-none select-none transition-all duration-300 ease-in-out",
2480
+ "absolute z-50",
2481
+ orientation === "vertical" && "right-0 top-0 bottom-0 w-2.5 border-l border-l-transparent p-px",
2482
+ orientation === "horizontal" && "bottom-0 left-0 right-0 h-2.5 flex-col border-t border-t-transparent p-px",
2483
+ "data-[state=hidden]:opacity-0 data-[state=visible]:opacity-100",
2484
+ className
2485
+ )
2486
+ }, props), {
2487
+ children: /* @__PURE__ */ jsx(
2488
+ RadixScrollArea.Thumb,
2489
+ {
2490
+ className: cn(
2491
+ "relative flex-1 rounded-full bg-m3-on-surface/25 transition-colors duration-200",
2492
+ "hover:bg-m3-on-surface/40 active:bg-m3-on-surface/55",
2493
+ "before:absolute before:left-1/2 before:top-1/2 before:min-h-11 before:min-w-11 before:-translate-x-1/2 before:-translate-y-1/2"
2494
+ )
2495
+ }
2496
+ )
2497
+ })
2498
+ );
2499
+ });
2500
+ ScrollAreaScrollbar.displayName = RadixScrollArea.Scrollbar.displayName;
2501
+ var ScrollAreaCorner = React17.forwardRef((_a, ref) => {
2502
+ var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
2503
+ return /* @__PURE__ */ jsx(
2504
+ RadixScrollArea.Corner,
2505
+ __spreadValues({
2506
+ ref,
2507
+ className: cn("bg-m3-surface-container", className)
2508
+ }, props)
2509
+ );
2510
+ });
2511
+ ScrollAreaCorner.displayName = "ScrollAreaCorner";
2512
+ var MD3_SPRING = DEFAULT_SPATIAL_SPRING;
2513
+ var MD3_OVERLAY_ANIM = {
2514
+ initial: { opacity: 0 },
2515
+ animate: {
2516
+ opacity: 1,
2517
+ transition: { duration: 0.2, ease: "easeOut" }
2518
+ },
2519
+ exit: { opacity: 0, transition: { duration: 0.15, ease: "easeIn" } }
2520
+ };
2521
+ var MD3_CONTENT_ANIM = {
2522
+ initial: { opacity: 0, scale: 0.85, y: 24 },
2523
+ animate: { opacity: 1, scale: 1, y: 0, transition: MD3_SPRING },
2524
+ exit: {
2525
+ opacity: 0,
2526
+ scale: 0.95,
2527
+ y: 8,
2528
+ transition: { duration: 0.15, ease: "easeIn" }
2529
+ }
2530
+ };
2531
+ var MD3_FULLSCREEN_ANIM = {
2532
+ initial: { y: "100%" },
2533
+ animate: { y: 0, transition: MD3_SPRING },
2534
+ exit: { y: "100%", transition: { duration: 0.2, ease: "easeIn" } }
2535
+ };
2536
+ var Dialog = ({ open, onOpenChange, children }) => /* @__PURE__ */ jsx(RadixDialog.Root, { open, onOpenChange, children });
2537
+ Dialog.displayName = "Dialog";
2538
+ var DialogTrigger = RadixDialog.Trigger;
2539
+ DialogTrigger.displayName = "DialogTrigger";
2540
+ var DialogPortal = ({
2541
+ open,
2542
+ children
2543
+ }) => /* @__PURE__ */ jsx(RadixDialog.Portal, { forceMount: true, children: /* @__PURE__ */ jsx(AnimatePresence, { children: open ? React17.Children.toArray(children) : null }) });
2544
+ DialogPortal.displayName = "DialogPortal";
2545
+ var DialogOverlay = React17.forwardRef((_a, ref) => {
2546
+ var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
2547
+ return /* @__PURE__ */ jsx(RadixDialog.Overlay, __spreadProps(__spreadValues({ ref, asChild: true }, props), { children: /* @__PURE__ */ jsx(
2548
+ m.div,
2549
+ __spreadValues({
2550
+ className: cn("fixed inset-0 z-50 bg-black/32", className),
2551
+ "aria-hidden": "true"
2552
+ }, MD3_OVERLAY_ANIM)
2553
+ ) }));
2554
+ });
2555
+ DialogOverlay.displayName = "DialogOverlay";
2556
+ var DialogContent = React17.forwardRef((_a, ref) => {
2557
+ var _b = _a, { className, children, hideCloseButton = false } = _b, props = __objRest(_b, ["className", "children", "hideCloseButton"]);
2558
+ return /* @__PURE__ */ jsx(
2559
+ RadixDialog.Content,
2560
+ __spreadProps(__spreadValues({
2561
+ ref,
2562
+ asChild: true,
2563
+ "aria-describedby": void 0
2564
+ }, props), {
2565
+ children: /* @__PURE__ */ jsxs(
2566
+ m.div,
2567
+ __spreadProps(__spreadValues({
2568
+ className: cn(
2569
+ "fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2",
2570
+ "w-[calc(100%-2rem)] max-w-140",
2571
+ "rounded-[28px] bg-m3-surface-container-high p-6",
2572
+ "shadow-lg outline-none focus-visible:ring-2 focus-visible:ring-m3-primary",
2573
+ className
2574
+ ),
2575
+ role: "dialog"
2576
+ }, MD3_CONTENT_ANIM), {
2577
+ children: [
2578
+ children,
2579
+ !hideCloseButton && /* @__PURE__ */ jsx(RadixDialog.Close, { asChild: true, "aria-label": "Close dialog", children: /* @__PURE__ */ jsx(
2580
+ IconButton,
2581
+ {
2582
+ size: "sm",
2583
+ colorStyle: "filled",
2584
+ className: "absolute right-4 top-4",
2585
+ "aria-label": "Close",
2586
+ children: /* @__PURE__ */ jsx(Icon, { name: "close", "aria-hidden": "true" })
2587
+ }
2588
+ ) })
2589
+ ]
2590
+ })
2591
+ )
2592
+ })
2593
+ );
2594
+ });
2595
+ DialogContent.displayName = "DialogContent";
2596
+ var DialogIcon = React17.forwardRef((_a, ref) => {
2597
+ var _b = _a, { className, children } = _b, props = __objRest(_b, ["className", "children"]);
2598
+ return /* @__PURE__ */ jsx(
2599
+ "div",
2600
+ __spreadProps(__spreadValues({
2601
+ ref,
2602
+ className: cn("flex justify-center mb-4 text-m3-secondary", className),
2603
+ "aria-hidden": "true"
2604
+ }, props), {
2605
+ children
2606
+ })
2607
+ );
2608
+ });
2609
+ DialogIcon.displayName = "DialogIcon";
2610
+ var DialogTitle = React17.forwardRef((_a, ref) => {
2611
+ var _b = _a, { className, asChild } = _b, props = __objRest(_b, ["className", "asChild"]);
2612
+ return /* @__PURE__ */ jsx(
2613
+ RadixDialog.Title,
2614
+ __spreadValues({
2615
+ ref,
2616
+ asChild,
2617
+ className: cn(
2618
+ "text-[24px] leading-8 font-normal text-m3-on-surface tracking-[0em]",
2619
+ className
2620
+ )
2621
+ }, props)
2622
+ );
2623
+ });
2624
+ DialogTitle.displayName = "DialogTitle";
2625
+ var DialogDescription = React17.forwardRef((_a, ref) => {
2626
+ var _b = _a, { className, asChild } = _b, props = __objRest(_b, ["className", "asChild"]);
2627
+ return /* @__PURE__ */ jsx(
2628
+ RadixDialog.Description,
2629
+ __spreadValues({
2630
+ ref,
2631
+ asChild,
2632
+ className: cn("text-sm text-m3-on-surface-variant leading-5", className)
2633
+ }, props)
2634
+ );
2635
+ });
2636
+ DialogDescription.displayName = "DialogDescription";
2637
+ var DialogBody = React17.forwardRef((_a, ref) => {
2638
+ var _b = _a, { className, children, dir } = _b, props = __objRest(_b, ["className", "children", "dir"]);
2639
+ return /* @__PURE__ */ jsx(
2640
+ ScrollArea,
2641
+ __spreadProps(__spreadValues({
2642
+ ref,
2643
+ type: "hover",
2644
+ dir,
2645
+ className: cn("max-h-[calc(85dvh-200px)] -mx-6", className),
2646
+ viewportClassName: "px-6"
2647
+ }, props), {
2648
+ children
2649
+ })
2650
+ );
2651
+ });
2652
+ DialogBody.displayName = "DialogBody";
2653
+ var DialogFullScreenContent = React17.forwardRef(
2654
+ (_a, ref) => {
2655
+ var _b = _a, {
2656
+ className,
2657
+ children,
2658
+ title,
2659
+ actionLabel,
2660
+ onAction,
2661
+ showDivider
2662
+ } = _b, props = __objRest(_b, [
2663
+ "className",
2664
+ "children",
2665
+ "title",
2666
+ "actionLabel",
2667
+ "onAction",
2668
+ "showDivider"
2669
+ ]);
2670
+ return /* @__PURE__ */ jsx(
2671
+ RadixDialog.Content,
2672
+ __spreadProps(__spreadValues({
2673
+ ref,
2674
+ asChild: true,
2675
+ "aria-describedby": void 0
2676
+ }, props), {
2677
+ children: /* @__PURE__ */ jsxs(
2678
+ m.div,
2679
+ __spreadProps(__spreadValues({
2680
+ className: cn(
2681
+ "fixed inset-0 z-50 w-full h-full bg-m3-surface flex flex-col",
2682
+ "outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-m3-primary",
2683
+ className
2684
+ ),
2685
+ role: "dialog"
2686
+ }, MD3_FULLSCREEN_ANIM), {
2687
+ children: [
2688
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center px-4 h-14 gap-2 bg-m3-surface", children: [
2689
+ /* @__PURE__ */ jsx(RadixDialog.Close, { asChild: true, "aria-label": "Close dialog", children: /* @__PURE__ */ jsx(IconButton, { size: "sm", colorStyle: "filled", "aria-label": "Close", children: /* @__PURE__ */ jsx(Icon, { name: "close", "aria-hidden": "true" }) }) }),
2690
+ title && /* @__PURE__ */ jsx(DialogTitle, { className: "flex-1 text-[22px] leading-7 font-medium truncate pr-2", children: title }),
2691
+ actionLabel && onAction && /* @__PURE__ */ jsx(
2692
+ "button",
2693
+ {
2694
+ type: "button",
2695
+ onClick: onAction,
2696
+ className: "text-sm font-medium text-m3-primary px-3 py-2 rounded-full hover:bg-m3-primary/8 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-m3-primary transition-colors whitespace-nowrap",
2697
+ children: actionLabel
2698
+ }
2699
+ )
2700
+ ] }),
2701
+ showDivider && /* @__PURE__ */ jsx("hr", { className: "border-m3-outline-variant w-full shrink-0 m-0" }),
2702
+ /* @__PURE__ */ jsx(
2703
+ ScrollArea,
2704
+ {
2705
+ type: "hover",
2706
+ className: "flex-1 w-full",
2707
+ viewportClassName: "p-6",
2708
+ children
2709
+ }
2710
+ )
2711
+ ]
2712
+ })
2713
+ )
2714
+ })
2715
+ );
2716
+ }
2717
+ );
2718
+ DialogFullScreenContent.displayName = "DialogFullScreenContent";
2719
+ var DatePickerDialog = ({
2720
+ open,
2721
+ onDismiss,
2722
+ confirmButton,
2723
+ dismissButton,
2724
+ children,
2725
+ className,
2726
+ title = "Select date"
2727
+ }) => {
2728
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (v) => !v && onDismiss(), children: /* @__PURE__ */ jsxs(DialogPortal, { open, children: [
2729
+ /* @__PURE__ */ jsx(RadixDialog.Overlay, { asChild: true, forceMount: true, children: /* @__PURE__ */ jsx(
2730
+ m.div,
2731
+ {
2732
+ className: "fixed inset-0 z-50 bg-m3-scrim/40",
2733
+ initial: PICKER_OVERLAY_ANIM.initial,
2734
+ animate: PICKER_OVERLAY_ANIM.animate,
2735
+ exit: PICKER_OVERLAY_ANIM.exit,
2736
+ onClick: onDismiss
2737
+ },
2738
+ "picker-overlay"
2739
+ ) }),
2740
+ /* @__PURE__ */ jsx(
2741
+ RadixDialog.Content,
2742
+ {
2743
+ asChild: true,
2744
+ forceMount: true,
2745
+ onEscapeKeyDown: onDismiss,
2746
+ onPointerDownOutside: (e) => e.preventDefault(),
2747
+ "aria-modal": true,
2748
+ "aria-describedby": void 0,
2749
+ children: /* @__PURE__ */ jsxs(
2750
+ m.div,
2751
+ {
2752
+ className: cn(
2753
+ "fixed z-50 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2",
2754
+ "flex flex-col",
2755
+ "bg-m3-surface-container-high",
2756
+ "rounded-m3-xl",
2757
+ "overflow-hidden",
2758
+ // Elevation Level 3
2759
+ "elevation-3",
2760
+ className
2761
+ ),
2762
+ style: { width: "min(calc(100vw - 32px), 360px)" },
2763
+ initial: PICKER_CONTENT_ANIM.initial,
2764
+ animate: PICKER_CONTENT_ANIM.animate,
2765
+ exit: PICKER_CONTENT_ANIM.exit,
2766
+ children: [
2767
+ /* @__PURE__ */ jsx(RadixDialog.Title, { className: "sr-only", children: title }),
2768
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden", children }),
2769
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 px-4 py-3", children: [
2770
+ dismissButton,
2771
+ confirmButton
2772
+ ] })
2773
+ ]
2774
+ },
2775
+ "picker-content"
2776
+ )
2777
+ }
2778
+ )
2779
+ ] }) });
2780
+ };
2781
+ DatePickerDialog.displayName = "DatePickerDialog";
2782
+ var DateRangePicker = ({
2783
+ state,
2784
+ showModeToggle = false,
2785
+ title,
2786
+ className
2787
+ }) => {
2788
+ const {
2789
+ selectedStartMs,
2790
+ selectedEndMs,
2791
+ displayedMonthMs,
2792
+ displayMode,
2793
+ yearRange,
2794
+ locale,
2795
+ selectableDates,
2796
+ selectDate,
2797
+ navigateMonth: navigateMonth2,
2798
+ navigateToMonth,
2799
+ setDisplayMode
2800
+ } = state;
2801
+ const [activeSelector, setActiveSelector] = React17.useState(null);
2802
+ const [slideDirection, setSlideDirection] = React17.useState(null);
2803
+ const handlePrev = React17.useCallback(() => {
2804
+ setSlideDirection("prev");
2805
+ navigateMonth2("prev");
2806
+ }, [navigateMonth2]);
2807
+ const handleNext = React17.useCallback(() => {
2808
+ setSlideDirection("next");
2809
+ navigateMonth2("next");
2810
+ }, [navigateMonth2]);
2811
+ const handleYearSelect = React17.useCallback(
2812
+ (year) => {
2813
+ const d = new Date(displayedMonthMs);
2814
+ navigateToMonth(year, d.getUTCMonth());
2815
+ setActiveSelector(null);
2816
+ },
2817
+ [displayedMonthMs, navigateToMonth]
2818
+ );
2819
+ const handleMonthSelect = React17.useCallback(
2820
+ (month) => {
2821
+ const d = new Date(displayedMonthMs);
2822
+ navigateToMonth(d.getUTCFullYear(), month);
2823
+ setActiveSelector(null);
2824
+ },
2825
+ [displayedMonthMs, navigateToMonth]
2826
+ );
2827
+ const formatShort = React17.useCallback(
2828
+ (ms) => {
2829
+ if (ms === null) return "\u2014";
2830
+ return formatDate(ms, locale, {
2831
+ month: "short",
2832
+ day: "numeric",
2833
+ timeZone: "UTC"
2834
+ });
2835
+ },
2836
+ [locale]
2837
+ );
2838
+ const headline = `${formatShort(selectedStartMs)} \u2013 ${formatShort(selectedEndMs)}`;
2839
+ const currentYear = new Date(displayedMonthMs).getUTCFullYear();
2840
+ const currentMonth = new Date(displayedMonthMs).getUTCMonth();
2841
+ const computedAriaLabel = typeof title === "string" ? title : "Date range picker";
2842
+ const canNavigatePrev = getNavigableMonth(displayedMonthMs, "prev", yearRange, selectableDates) !== null;
2843
+ const canNavigateNext = getNavigableMonth(displayedMonthMs, "next", yearRange, selectableDates) !== null;
2844
+ const canNavigatePreviousYear = isMonthNavigable(
2845
+ currentYear - 1,
2846
+ yearRange,
2847
+ selectableDates
2848
+ );
2849
+ const canNavigateNextYear = isMonthNavigable(
2850
+ currentYear + 1,
2851
+ yearRange,
2852
+ selectableDates
2853
+ );
2854
+ return /* @__PURE__ */ jsxs(
2855
+ "fieldset",
2856
+ {
2857
+ className: cn("flex flex-col select-none", className),
2858
+ style: { width: "min(100%, 360px)" },
2859
+ "aria-label": computedAriaLabel,
2860
+ children: [
2861
+ /* @__PURE__ */ jsx("legend", { className: "sr-only", children: computedAriaLabel }),
2862
+ /* @__PURE__ */ jsx(
2863
+ CalendarHeader,
2864
+ {
2865
+ displayedMonthMs,
2866
+ activeSelector,
2867
+ displayMode,
2868
+ showModeToggle,
2869
+ title: title != null ? title : "Select dates",
2870
+ headline,
2871
+ locale,
2872
+ canNavigatePrev,
2873
+ canNavigateNext,
2874
+ canNavigatePreviousYear,
2875
+ canNavigateNextYear,
2876
+ onPrev: handlePrev,
2877
+ onNext: handleNext,
2878
+ onPreviousYear: () => navigateToMonth(currentYear - 1, currentMonth),
2879
+ onNextYear: () => navigateToMonth(currentYear + 1, currentMonth),
2880
+ onToggleSelector: (selector) => setActiveSelector((active) => active === selector ? null : selector),
2881
+ onToggleMode: () => {
2882
+ setDisplayMode(displayMode === "picker" ? "input" : "picker");
2883
+ setActiveSelector(null);
2884
+ }
2885
+ }
2886
+ ),
2887
+ /* @__PURE__ */ jsx(
2888
+ "div",
2889
+ {
2890
+ className: "relative flex-1 overflow-hidden",
2891
+ style: { minHeight: "296px" },
2892
+ children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: activeSelector === "month" ? /* @__PURE__ */ jsx(
2893
+ "div",
2894
+ {
2895
+ id: "date-picker-selection",
2896
+ className: "absolute inset-0",
2897
+ children: /* @__PURE__ */ jsx(
2898
+ MonthPickerList,
2899
+ {
2900
+ selectedMonth: currentMonth,
2901
+ locale,
2902
+ onMonthSelect: handleMonthSelect
2903
+ }
2904
+ )
2905
+ },
2906
+ "month-list"
2907
+ ) : activeSelector === "year" ? /* @__PURE__ */ jsx(
2908
+ "div",
2909
+ {
2910
+ id: "date-picker-selection",
2911
+ className: "absolute inset-0",
2912
+ children: /* @__PURE__ */ jsx(
2913
+ YearPickerList,
2914
+ {
2915
+ selectedYear: currentYear,
2916
+ yearRange,
2917
+ selectableDates,
2918
+ onYearSelect: handleYearSelect
2919
+ }
2920
+ )
2921
+ },
2922
+ "year-list"
2923
+ ) : /* @__PURE__ */ jsx(
2924
+ CalendarGrid,
2925
+ {
2926
+ displayedMonthMs,
2927
+ slideDirection,
2928
+ locale,
2929
+ selectableDates,
2930
+ selectedMs: null,
2931
+ selectedStartMs,
2932
+ selectedEndMs,
2933
+ isRangePicker: true,
2934
+ onDaySelect: selectDate
2935
+ },
2936
+ "calendar"
2937
+ ) })
2938
+ }
2939
+ )
2940
+ ]
2941
+ }
2942
+ );
2943
+ };
2944
+ DateRangePicker.displayName = "DateRangePicker";
2945
+
2946
+ // src/ui/date-time-pickers/shared/types.ts
2947
+ var ALL_DATES_SELECTABLE = {
2948
+ isSelectableDate: () => true,
2949
+ isSelectableYear: () => true
2950
+ };
2951
+
2952
+ // src/ui/date-time-pickers/date-picker/use-date-picker-state.ts
2953
+ var DEFAULT_YEAR_RANGE = [1900, 2100];
2954
+ var DEFAULT_DISPLAY_MODE = "picker";
2955
+ var useDatePickerState = (options) => {
2956
+ var _a, _b, _c, _d, _e;
2957
+ const initialSelectedMs = (_a = options == null ? void 0 : options.initialSelectedDateMs) != null ? _a : null;
2958
+ const initialMonthMs = normalizeToMonthStart(
2959
+ (_b = options == null ? void 0 : options.initialDisplayedMonthMs) != null ? _b : initialSelectedMs !== null ? initialSelectedMs : todayUTC()
2960
+ );
2961
+ const yearRange = (_c = options == null ? void 0 : options.yearRange) != null ? _c : DEFAULT_YEAR_RANGE;
2962
+ const selectableDates = (_d = options == null ? void 0 : options.selectableDates) != null ? _d : ALL_DATES_SELECTABLE;
2963
+ const initialDisplayMode = (_e = options == null ? void 0 : options.initialDisplayMode) != null ? _e : DEFAULT_DISPLAY_MODE;
2964
+ const locale = resolveLocale(options == null ? void 0 : options.locale);
2965
+ const [selectedDateMs, setSelectedDateMs] = useState(
2966
+ initialSelectedMs !== null ? startOfDayUTC(initialSelectedMs) : null
2967
+ );
2968
+ const [displayedMonthMs, setDisplayedMonthMs] = useState(initialMonthMs);
2969
+ const [displayMode, setDisplayModeState] = useState(initialDisplayMode);
2970
+ const selectDate = useCallback(
2971
+ (ms) => {
2972
+ if (ms === null) {
2973
+ setSelectedDateMs(null);
2974
+ return;
2975
+ }
2976
+ const normalized = startOfDayUTC(ms);
2977
+ if (!selectableDates.isSelectableDate(normalized)) return;
2978
+ setSelectedDateMs(normalized);
2979
+ setDisplayedMonthMs(normalizeToMonthStart(normalized));
2980
+ },
2981
+ [selectableDates]
2982
+ );
2983
+ const navigateMonthAction = useCallback(
2984
+ (direction) => {
2985
+ setDisplayedMonthMs(
2986
+ (prev) => {
2987
+ var _a2;
2988
+ return (_a2 = getNavigableMonth(prev, direction, yearRange, selectableDates)) != null ? _a2 : prev;
2989
+ }
2990
+ );
2991
+ },
2992
+ [yearRange, selectableDates]
2993
+ );
2994
+ const navigateToMonth = useCallback(
2995
+ (year, month) => {
2996
+ if (!isMonthNavigable(year, yearRange, selectableDates)) return;
2997
+ setDisplayedMonthMs(Date.UTC(year, month, 1));
2998
+ },
2999
+ [yearRange, selectableDates]
3000
+ );
3001
+ const setDisplayMode = useCallback((mode) => {
3002
+ setDisplayModeState(mode);
3003
+ }, []);
3004
+ const reset = useCallback(() => {
3005
+ setSelectedDateMs(
3006
+ initialSelectedMs !== null ? startOfDayUTC(initialSelectedMs) : null
3007
+ );
3008
+ setDisplayedMonthMs(initialMonthMs);
3009
+ setDisplayModeState(initialDisplayMode);
3010
+ }, [initialSelectedMs, initialMonthMs, initialDisplayMode]);
3011
+ return useMemo(
3012
+ () => ({
3013
+ selectedDateMs,
3014
+ displayedMonthMs,
3015
+ displayMode,
3016
+ yearRange,
3017
+ locale,
3018
+ selectableDates,
3019
+ selectDate,
3020
+ navigateMonth: navigateMonthAction,
3021
+ navigateToMonth,
3022
+ setDisplayMode,
3023
+ reset
3024
+ }),
3025
+ [
3026
+ selectedDateMs,
3027
+ displayedMonthMs,
3028
+ displayMode,
3029
+ yearRange,
3030
+ locale,
3031
+ selectableDates,
3032
+ selectDate,
3033
+ navigateMonthAction,
3034
+ navigateToMonth,
3035
+ setDisplayMode,
3036
+ reset
3037
+ ]
3038
+ );
3039
+ };
3040
+ var DEFAULT_YEAR_RANGE2 = [1900, 2100];
3041
+ function rangeReducer(state, action) {
3042
+ switch (action.type) {
3043
+ case "SELECT": {
3044
+ const { startMs, endMs } = state;
3045
+ const ms = action.ms;
3046
+ if (startMs === null) return { startMs: ms, endMs: null };
3047
+ if (endMs !== null) return { startMs: ms, endMs: null };
3048
+ if (ms > startMs) return { startMs, endMs: ms };
3049
+ return { startMs: ms, endMs: null };
3050
+ }
3051
+ case "CLEAR":
3052
+ return { startMs: null, endMs: null };
3053
+ case "RESET":
3054
+ return { startMs: action.startMs, endMs: action.endMs };
3055
+ default:
3056
+ return state;
3057
+ }
3058
+ }
3059
+ var useDateRangePickerState = (options) => {
3060
+ var _a, _b, _c, _d;
3061
+ const initialStartMs = (options == null ? void 0 : options.initialStartMs) != null ? startOfDayUTC(options.initialStartMs) : null;
3062
+ const initialEndMs = (options == null ? void 0 : options.initialEndMs) != null ? startOfDayUTC(options.initialEndMs) : null;
3063
+ const initialMonthMs = normalizeToMonthStart(
3064
+ (_a = options == null ? void 0 : options.initialDisplayedMonthMs) != null ? _a : initialStartMs !== null ? initialStartMs : todayUTC()
3065
+ );
3066
+ const yearRange = (_b = options == null ? void 0 : options.yearRange) != null ? _b : DEFAULT_YEAR_RANGE2;
3067
+ const selectableDates = (_c = options == null ? void 0 : options.selectableDates) != null ? _c : ALL_DATES_SELECTABLE;
3068
+ const initialDisplayMode = (_d = options == null ? void 0 : options.initialDisplayMode) != null ? _d : "picker";
3069
+ const locale = resolveLocale(options == null ? void 0 : options.locale);
3070
+ const [{ startMs: selectedStartMs, endMs: selectedEndMs }, dispatch] = useReducer(rangeReducer, {
3071
+ startMs: initialStartMs,
3072
+ endMs: initialEndMs
3073
+ });
3074
+ const [displayedMonthMs, setDisplayedMonthMs] = useState(initialMonthMs);
3075
+ const [displayMode, setDisplayModeState] = useState(initialDisplayMode);
3076
+ const selectDate = useCallback(
3077
+ (ms) => {
3078
+ const normalized = startOfDayUTC(ms);
3079
+ if (!selectableDates.isSelectableDate(normalized)) return;
3080
+ dispatch({ type: "SELECT", ms: normalized });
3081
+ },
3082
+ [selectableDates]
3083
+ );
3084
+ const clearRange = useCallback(() => {
3085
+ dispatch({ type: "CLEAR" });
3086
+ }, []);
3087
+ const navigateMonthAction = useCallback(
3088
+ (direction) => {
3089
+ setDisplayedMonthMs(
3090
+ (prev) => {
3091
+ var _a2;
3092
+ return (_a2 = getNavigableMonth(prev, direction, yearRange, selectableDates)) != null ? _a2 : prev;
3093
+ }
3094
+ );
3095
+ },
3096
+ [yearRange, selectableDates]
3097
+ );
3098
+ const navigateToMonth = useCallback(
3099
+ (year, month) => {
3100
+ if (!isMonthNavigable(year, yearRange, selectableDates)) return;
3101
+ setDisplayedMonthMs(Date.UTC(year, month, 1));
3102
+ },
3103
+ [yearRange, selectableDates]
3104
+ );
3105
+ const setDisplayMode = useCallback((mode) => {
3106
+ setDisplayModeState(mode);
3107
+ }, []);
3108
+ const reset = useCallback(() => {
3109
+ dispatch({ type: "RESET", startMs: initialStartMs, endMs: initialEndMs });
3110
+ setDisplayedMonthMs(initialMonthMs);
3111
+ setDisplayModeState(initialDisplayMode);
3112
+ }, [initialStartMs, initialEndMs, initialMonthMs, initialDisplayMode]);
3113
+ return useMemo(
3114
+ () => ({
3115
+ selectedStartMs,
3116
+ selectedEndMs,
3117
+ displayedMonthMs,
3118
+ displayMode,
3119
+ yearRange,
3120
+ locale,
3121
+ selectableDates,
3122
+ selectDate,
3123
+ clearRange,
3124
+ navigateMonth: navigateMonthAction,
3125
+ navigateToMonth,
3126
+ setDisplayMode,
3127
+ reset
3128
+ }),
3129
+ [
3130
+ selectedStartMs,
3131
+ selectedEndMs,
3132
+ displayedMonthMs,
3133
+ displayMode,
3134
+ yearRange,
3135
+ locale,
3136
+ selectableDates,
3137
+ selectDate,
3138
+ clearRange,
3139
+ navigateMonthAction,
3140
+ navigateToMonth,
3141
+ setDisplayMode,
3142
+ reset
3143
+ ]
3144
+ );
3145
+ };
3146
+
3147
+ // src/ui/date-time-pickers/time-picker/time-picker.tokens.ts
3148
+ var TP_COLORS = {
3149
+ // ── Clock Dial (TimePickerTokens) ─────────────────────────────────────────
3150
+ /** Dial background — SurfaceContainerHighest */
3151
+ clockDial: "var(--md-sys-color-surface-container-highest)",
3152
+ /** Selected number text — OnPrimary */
3153
+ clockDialSelected: "var(--md-sys-color-on-primary)",
3154
+ /** Unselected number text — OnSurface */
3155
+ clockDialUnselected: "var(--md-sys-color-on-surface)",
3156
+ /** Selector handle circle — Primary */
3157
+ selectorHandle: "var(--md-sys-color-primary)",
3158
+ /** Selector track line — Primary */
3159
+ selectorTrack: "var(--md-sys-color-primary)",
3160
+ /** Selector center dot — Primary */
3161
+ selectorCenter: "var(--md-sys-color-primary)",
3162
+ // ── Container ─────────────────────────────────────────────────────────────
3163
+ /** Picker/Input container — SurfaceContainerHigh */
3164
+ container: "var(--md-sys-color-surface-container-high)",
3165
+ // ── Time Selector Buttons (hour/minute display) ───────────────────────────
3166
+ /** Selected selector (hour/min button) — PrimaryContainer */
3167
+ timeSelectorSelected: "var(--md-sys-color-primary-container)",
3168
+ /** Unselected selector — SurfaceContainerHighest */
3169
+ timeSelectorUnselected: "var(--md-sys-color-surface-container-highest)",
3170
+ /** Selected selector text — OnPrimaryContainer */
3171
+ timeSelectorSelectedText: "var(--md-sys-color-on-primary-container)",
3172
+ /** Unselected selector text — OnSurface */
3173
+ timeSelectorUnselectedText: "var(--md-sys-color-on-surface)",
3174
+ // ── Period Selector AM/PM (updated flag = true → PrimaryContainer) ────────
3175
+ /** AM/PM selected background — TertiaryContainer */
3176
+ periodSelected: "var(--md-sys-color-tertiary-container)",
3177
+ /** AM/PM unselected background — transparent */
3178
+ periodUnselected: "transparent",
3179
+ /** AM/PM selected text — OnTertiaryContainer */
3180
+ periodSelectedText: "var(--md-sys-color-on-tertiary-container)",
3181
+ /** AM/PM unselected text — OnSurfaceVariant */
3182
+ periodUnselectedText: "var(--md-sys-color-on-surface-variant)",
3183
+ /** AM/PM border — Outline */
3184
+ periodOutline: "var(--md-sys-color-outline)",
3185
+ // ── Time Field (TimeInput mode, TimeInputTokens) ───────────────────────────
3186
+ /** Time field default background — SurfaceContainerHighest */
3187
+ timeField: "var(--md-sys-color-surface-container-highest)",
3188
+ /** Time field focused background — PrimaryContainer */
3189
+ timeFieldFocus: "var(--md-sys-color-primary-container)",
3190
+ /** Time field focused text — OnPrimaryContainer */
3191
+ timeFieldFocusText: "var(--md-sys-color-on-primary-container)",
3192
+ /** Time field focus outline — Primary */
3193
+ timeFieldFocusOutline: "var(--md-sys-color-primary)",
3194
+ /** Time field default text — OnSurface */
3195
+ timeFieldText: "var(--md-sys-color-on-surface)",
3196
+ /** ":" separator — OnSurface */
3197
+ separator: "var(--md-sys-color-on-surface)",
3198
+ /** Headline label ("Select time") — OnSurfaceVariant */
3199
+ headline: "var(--md-sys-color-on-surface-variant)"
3200
+ };
3201
+ var TP_CLASSES = {
3202
+ // ── Container ─────────────────────────────────────────────────────────────
3203
+ /** Main picker/input container */
3204
+ container: "bg-m3-surface-container-high rounded-m3-xl select-none",
3205
+ // ── Time Selector Buttons ─────────────────────────────────────────────────
3206
+ /** Hour/minute selector button — selected */
3207
+ timeSelectorSelected: "bg-m3-primary-container text-m3-on-primary-container",
3208
+ /** Hour/minute selector button — unselected */
3209
+ timeSelectorUnselected: "bg-m3-surface-container-highest text-m3-on-surface",
3210
+ // ── Period Selector ───────────────────────────────────────────────────────
3211
+ /** AM/PM button — selected */
3212
+ periodSelected: "bg-m3-primary-container text-m3-on-primary-container",
3213
+ /** AM/PM button — unselected */
3214
+ periodUnselected: "bg-transparent text-m3-on-surface-variant",
3215
+ // ── Time Field (input mode) ────────────────────────────────────────────────
3216
+ /** Time input field — default */
3217
+ timeField: "bg-m3-surface-container-highest text-m3-on-surface",
3218
+ /** Time input field — focused */
3219
+ timeFieldFocused: "bg-m3-primary-container text-m3-on-primary-container ring-2 ring-m3-primary",
3220
+ // ── Clock Dial ─────────────────────────────────────────────────────────────
3221
+ /** Clock dial background circle */
3222
+ clockDial: "fill-[var(--md-sys-color-surface-container-highest)]",
3223
+ /** Selected number on dial */
3224
+ clockDialSelectedText: "fill-[var(--md-sys-color-on-primary)]",
3225
+ /** Unselected number on dial */
3226
+ clockDialUnselectedText: "fill-[var(--md-sys-color-on-surface)]",
3227
+ /** Selector handle, track, center dot */
3228
+ selectorPrimary: "fill-m3-primary stroke-m3-primary"
3229
+ };
3230
+ var TP_SHAPE = {
3231
+ /** Dialog container — CornerExtraLarge = 28px */
3232
+ container: "rounded-m3-xl",
3233
+ /** Hour/minute selector button — CornerSmall = 8px */
3234
+ timeSelector: "rounded-m3-sm",
3235
+ /** Period (AM/PM) container — CornerSmall = 8px */
3236
+ periodSelector: "rounded-m3-sm",
3237
+ /** Clock dial circle — CornerFull */
3238
+ clockDial: "rounded-m3-full",
3239
+ /** Time field — CornerSmall = 8px */
3240
+ timeField: "rounded-m3-sm"
3241
+ };
3242
+ var TP_SIZE = {
3243
+ // ── Clock Dial (TimePickerTokens) ─────────────────────────────────────────
3244
+ /** ClockDialContainerSize = 256dp */
3245
+ clockDialSize: 256,
3246
+ /** ClockDialSelectorHandleContainerSize = 48dp */
3247
+ selectorHandleSize: 48,
3248
+ /** ClockDialSelectorCenterContainerSize = 8dp */
3249
+ selectorCenterSize: 8,
3250
+ /** ClockDialSelectorTrackContainerWidth = 2dp */
3251
+ selectorTrackWidth: 2,
3252
+ // ── Time Selector (TimeSelectorContainer) ─────────────────────────────────
3253
+ /** TimeSelectorContainerWidth = 96dp */
3254
+ timeSelectorWidth: 96,
3255
+ /** TimeSelectorContainerHeight = 80dp */
3256
+ timeSelectorHeight: 80,
3257
+ /** TimeSelector24HVerticalContainerWidth = 114dp (24h mode) */
3258
+ timeSelector24hWidth: 114,
3259
+ // ── Period Selector (Picker mode) ─────────────────────────────────────────
3260
+ /** PeriodSelectorVerticalContainerWidth = 52dp */
3261
+ periodSelectorVerticalWidth: 52,
3262
+ /** PeriodSelectorVerticalContainerHeight = 80dp */
3263
+ periodSelectorVerticalHeight: 80,
3264
+ /** PeriodSelectorHorizontalContainerWidth = 216dp */
3265
+ periodSelectorHorizWidth: 216,
3266
+ /** PeriodSelectorHorizontalContainerHeight = 38dp */
3267
+ periodSelectorHorizHeight: 38,
3268
+ // ── Time Field (Input mode, TimeInputTokens) ───────────────────────────────
3269
+ /** TimeFieldContainerWidth = 96dp */
3270
+ timeFieldWidth: 96,
3271
+ /** TimeFieldContainerHeight = 72dp */
3272
+ timeFieldHeight: 72,
3273
+ /** PeriodSelectorContainerHeight (Input mode) = 72dp */
3274
+ periodInputHeight: 72,
3275
+ /** PeriodSelectorContainerWidth (Input mode) = 52dp */
3276
+ periodInputWidth: 52
3277
+ };
3278
+ var PeriodSelector = ({
3279
+ state,
3280
+ layout = "vertical",
3281
+ style,
3282
+ className
3283
+ }) => {
3284
+ if (state.is24hour) return null;
3285
+ const { isPm, isHourInputValid, setIsPm } = state;
3286
+ const handleAmClick = () => {
3287
+ if (isPm && isHourInputValid) {
3288
+ setIsPm(false);
3289
+ }
3290
+ };
3291
+ const handlePmClick = () => {
3292
+ if (!isPm && isHourInputValid) {
3293
+ setIsPm(true);
3294
+ }
3295
+ };
3296
+ const isVertical = layout === "vertical";
3297
+ return /* @__PURE__ */ jsxs(
3298
+ "fieldset",
3299
+ {
3300
+ "aria-label": "AM/PM",
3301
+ style: __spreadValues({
3302
+ width: isVertical ? TP_SIZE.periodSelectorVerticalWidth : TP_SIZE.periodSelectorHorizWidth,
3303
+ height: isVertical ? TP_SIZE.periodSelectorVerticalHeight : TP_SIZE.periodSelectorHorizHeight
3304
+ }, style),
3305
+ className: cn(
3306
+ // Layout
3307
+ isVertical ? "flex flex-col" : "flex flex-row",
3308
+ "gap-0 overflow-hidden",
3309
+ // Container shape (CornerSmall = rounded-m3-sm)
3310
+ "rounded-m3-sm",
3311
+ // Border (period selector outline — updated toggle uses border on container)
3312
+ "border border-m3-outline",
3313
+ className
3314
+ ),
3315
+ children: [
3316
+ /* @__PURE__ */ jsx(
3317
+ PeriodButton,
3318
+ {
3319
+ label: "AM",
3320
+ isSelected: !isPm,
3321
+ onClick: handleAmClick,
3322
+ isFirst: true,
3323
+ isVertical
3324
+ }
3325
+ ),
3326
+ /* @__PURE__ */ jsx(
3327
+ PeriodButton,
3328
+ {
3329
+ label: "PM",
3330
+ isSelected: isPm,
3331
+ onClick: handlePmClick,
3332
+ isFirst: false,
3333
+ isVertical
3334
+ }
3335
+ )
3336
+ ]
3337
+ }
3338
+ );
3339
+ };
3340
+ PeriodSelector.displayName = "PeriodSelector";
3341
+ var PeriodButton = ({
3342
+ label,
3343
+ isSelected,
3344
+ onClick,
3345
+ isFirst,
3346
+ isVertical
3347
+ }) => {
3348
+ return /* @__PURE__ */ jsx(
3349
+ "button",
3350
+ {
3351
+ type: "button",
3352
+ "aria-pressed": isSelected,
3353
+ "aria-label": label,
3354
+ onClick,
3355
+ className: cn(
3356
+ // Layout
3357
+ "flex-1 flex items-center justify-center",
3358
+ "min-w-0 min-h-0",
3359
+ // Typography — TitleMedium equivalent (16px, medium weight)
3360
+ "text-[16px] leading-none",
3361
+ // Selected state — from PeriodToggleImpl (useUpdatedToggle = true)
3362
+ isSelected ? [
3363
+ "bg-m3-tertiary-container text-m3-on-tertiary-container",
3364
+ "font-bold"
3365
+ ] : ["bg-transparent text-m3-on-surface-variant", "font-medium"],
3366
+ // Divider between buttons via border
3367
+ !isFirst && isVertical && "border-t border-m3-outline",
3368
+ !isFirst && !isVertical && "border-l border-m3-outline",
3369
+ // Interaction
3370
+ "cursor-pointer transition-colors transition-m3-fast-effects",
3371
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-m3-primary"
3372
+ ),
3373
+ children: label
3374
+ }
3375
+ );
3376
+ };
3377
+ var TimeField = (props) => {
3378
+ if (props.mode === "selector") {
3379
+ return /* @__PURE__ */ jsx(SelectorField, __spreadValues({}, props));
3380
+ }
3381
+ return /* @__PURE__ */ jsx(InputField, __spreadValues({}, props));
3382
+ };
3383
+ TimeField.displayName = "TimeField";
3384
+ var SelectorField = ({
3385
+ value,
3386
+ field,
3387
+ selection,
3388
+ onActivate,
3389
+ className
3390
+ }) => {
3391
+ const isSelected = selection === field;
3392
+ return /* @__PURE__ */ jsx(
3393
+ "button",
3394
+ {
3395
+ type: "button",
3396
+ "aria-label": field === "hour" ? "Hour" : "Minute",
3397
+ "aria-pressed": isSelected,
3398
+ onClick: onActivate,
3399
+ style: {
3400
+ width: TP_SIZE.timeSelectorWidth,
3401
+ height: TP_SIZE.timeSelectorHeight
3402
+ },
3403
+ className: cn(
3404
+ // Layout
3405
+ "flex items-center justify-center",
3406
+ "rounded-m3-sm",
3407
+ // Typography — DisplayLarge equivalent (57px)
3408
+ "text-[57px] font-normal leading-none tabular-nums",
3409
+ // State
3410
+ isSelected ? "bg-m3-primary-container text-m3-on-primary-container" : "bg-m3-surface-container-highest text-m3-on-surface",
3411
+ // Interaction
3412
+ "cursor-pointer transition-colors transition-m3-fast-effects",
3413
+ "hover:opacity-90 active:opacity-80",
3414
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-m3-primary",
3415
+ className
3416
+ ),
3417
+ children: String(value).padStart(2, "0")
3418
+ }
3419
+ );
3420
+ };
3421
+ var InputField = ({
3422
+ value,
3423
+ field,
3424
+ isFocused,
3425
+ isValid,
3426
+ onChange,
3427
+ onAutoAdvance,
3428
+ onFocus,
3429
+ onBlur,
3430
+ className
3431
+ }) => {
3432
+ const [inputText, setInputText] = React17.useState(
3433
+ String(value).padStart(2, "0")
3434
+ );
3435
+ const inputRef = React17.useRef(null);
3436
+ React17.useEffect(() => {
3437
+ if (!isFocused) {
3438
+ setInputText(String(value).padStart(2, "0"));
3439
+ }
3440
+ }, [value, isFocused]);
3441
+ React17.useEffect(() => {
3442
+ var _a, _b;
3443
+ if (isFocused) {
3444
+ (_a = inputRef.current) == null ? void 0 : _a.focus();
3445
+ (_b = inputRef.current) == null ? void 0 : _b.select();
3446
+ }
3447
+ }, [isFocused]);
3448
+ const handleChange = (e) => {
3449
+ const raw = e.target.value.replace(/\D/g, "");
3450
+ const limited = raw.slice(-2);
3451
+ setInputText(limited);
3452
+ const numVal = parseInt(limited, 10);
3453
+ if (!Number.isNaN(numVal)) {
3454
+ onChange(numVal);
3455
+ if (limited.length === 2) {
3456
+ onAutoAdvance == null ? void 0 : onAutoAdvance();
3457
+ }
3458
+ } else if (limited === "") {
3459
+ onChange(0);
3460
+ }
3461
+ };
3462
+ const handleKeyDown = (e) => {
3463
+ const allowed = [
3464
+ "Backspace",
3465
+ "Delete",
3466
+ "Tab",
3467
+ "ArrowLeft",
3468
+ "ArrowRight",
3469
+ "Enter"
3470
+ ];
3471
+ const isDigit = e.key >= "0" && e.key <= "9";
3472
+ if (!isDigit && !allowed.includes(e.key)) {
3473
+ e.preventDefault();
3474
+ }
3475
+ };
3476
+ const handleFocus = (e) => {
3477
+ e.target.select();
3478
+ onFocus == null ? void 0 : onFocus();
3479
+ };
3480
+ const displayText = isFocused ? inputText : String(value).padStart(2, "0");
3481
+ const isError = isFocused && !isValid && inputText.length === 2;
3482
+ return /* @__PURE__ */ jsx(
3483
+ "div",
3484
+ {
3485
+ style: {
3486
+ width: TP_SIZE.timeFieldWidth,
3487
+ height: TP_SIZE.timeFieldHeight
3488
+ },
3489
+ className: cn(
3490
+ "relative flex items-center justify-center rounded-m3-sm",
3491
+ // Background state
3492
+ isFocused ? "bg-m3-primary-container" : "bg-m3-surface-container-highest",
3493
+ // Error border
3494
+ isError && "ring-2 ring-m3-error",
3495
+ // Focus outline (non-error)
3496
+ isFocused && !isError && "ring-2 ring-m3-primary",
3497
+ className
3498
+ ),
3499
+ children: /* @__PURE__ */ jsx(
3500
+ "input",
3501
+ {
3502
+ ref: inputRef,
3503
+ type: "text",
3504
+ inputMode: "numeric",
3505
+ value: displayText,
3506
+ maxLength: 2,
3507
+ "aria-label": field === "hour" ? "Hour" : "Minute",
3508
+ "aria-invalid": isError,
3509
+ onChange: handleChange,
3510
+ onKeyDown: handleKeyDown,
3511
+ onFocus: handleFocus,
3512
+ onBlur,
3513
+ className: cn(
3514
+ // Reset input styles
3515
+ "w-full h-full bg-transparent border-none outline-none",
3516
+ "text-center tabular-nums select-all",
3517
+ // Typography — DisplayMedium equivalent (45px)
3518
+ "text-[45px] font-normal leading-none",
3519
+ // Color
3520
+ isFocused ? "text-m3-on-primary-container" : "text-m3-on-surface",
3521
+ // Caret
3522
+ "caret-m3-primary"
3523
+ )
3524
+ }
3525
+ )
3526
+ }
3527
+ );
3528
+ };
3529
+ var TimeInput = ({ state, className }) => {
3530
+ const { is24hour, setHourInput, setMinuteInput, setSelection } = state;
3531
+ const [focusedField, setFocusedField] = React17.useState(null);
3532
+ const handleHourAutoAdvance = () => {
3533
+ setFocusedField("minute");
3534
+ setSelection("minute");
3535
+ };
3536
+ return /* @__PURE__ */ jsxs(
3537
+ "div",
3538
+ {
3539
+ className: cn(
3540
+ "bg-m3-surface-container-high rounded-m3-xl",
3541
+ "flex flex-col",
3542
+ "p-6 gap-0 select-none",
3543
+ className
3544
+ ),
3545
+ children: [
3546
+ /* @__PURE__ */ jsx(
3547
+ "p",
3548
+ {
3549
+ className: "w-full text-left text-m3-on-surface-variant mb-5",
3550
+ style: { fontSize: "12px", fontWeight: 500, letterSpacing: "0.1em" },
3551
+ children: "Enter time"
3552
+ }
3553
+ ),
3554
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-row items-start gap-2", children: [
3555
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1", children: [
3556
+ /* @__PURE__ */ jsx(
3557
+ TimeField,
3558
+ {
3559
+ mode: "input",
3560
+ value: state.hourInput,
3561
+ field: "hour",
3562
+ isFocused: focusedField === "hour",
3563
+ isValid: state.isHourInputValid,
3564
+ onChange: setHourInput,
3565
+ onAutoAdvance: handleHourAutoAdvance,
3566
+ onFocus: () => {
3567
+ setFocusedField("hour");
3568
+ setSelection("hour");
3569
+ },
3570
+ onBlur: () => setFocusedField(null)
3571
+ }
3572
+ ),
3573
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-m3-on-surface-variant font-normal leading-none mt-1", children: "Hour" })
3574
+ ] }),
3575
+ /* @__PURE__ */ jsx(
3576
+ "span",
3577
+ {
3578
+ className: "text-m3-on-surface text-center self-start",
3579
+ style: {
3580
+ fontSize: "57px",
3581
+ fontWeight: 400,
3582
+ lineHeight: 1,
3583
+ width: 24,
3584
+ // Căn theo height của input field
3585
+ marginTop: `${(TP_SIZE.timeFieldHeight - 57) / 2}px`
3586
+ },
3587
+ "aria-hidden": true,
3588
+ children: ":"
3589
+ }
3590
+ ),
3591
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1", children: [
3592
+ /* @__PURE__ */ jsx(
3593
+ TimeField,
3594
+ {
3595
+ mode: "input",
3596
+ value: state.minuteInput,
3597
+ field: "minute",
3598
+ isFocused: focusedField === "minute",
3599
+ isValid: state.isMinuteInputValid,
3600
+ onChange: setMinuteInput,
3601
+ onFocus: () => {
3602
+ setFocusedField("minute");
3603
+ setSelection("minute");
3604
+ },
3605
+ onBlur: () => setFocusedField(null)
3606
+ }
3607
+ ),
3608
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-m3-on-surface-variant font-normal leading-none mt-1", children: "Minute" })
3609
+ ] }),
3610
+ !is24hour && /* @__PURE__ */ jsx(
3611
+ PeriodSelector,
3612
+ {
3613
+ state,
3614
+ layout: "vertical",
3615
+ className: "ml-2 self-start",
3616
+ style: {
3617
+ height: TP_SIZE.periodInputHeight,
3618
+ width: TP_SIZE.periodInputWidth
3619
+ }
3620
+ }
3621
+ )
3622
+ ] })
3623
+ ]
3624
+ }
3625
+ );
3626
+ };
3627
+ TimeInput.displayName = "TimeInput";
3628
+ var FULL_CIRCLE = Math.PI * 2;
3629
+ var QUARTER_CIRCLE = Math.PI / 2;
3630
+ var RADIANS_PER_HOUR = FULL_CIRCLE / 12;
3631
+ var RADIANS_PER_MINUTE = FULL_CIRCLE / 60;
3632
+ var OUTER_CIRCLE_RATIO = 101 / TP_SIZE.clockDialSize;
3633
+ var INNER_CIRCLE_RATIO = 69 / TP_SIZE.clockDialSize;
3634
+ var CENTER = TP_SIZE.clockDialSize / 2;
3635
+ var OUTER_RADIUS = TP_SIZE.clockDialSize * OUTER_CIRCLE_RATIO;
3636
+ var INNER_RADIUS = TP_SIZE.clockDialSize * INNER_CIRCLE_RATIO;
3637
+ var HANDLE_RADIUS = TP_SIZE.selectorHandleSize / 2;
3638
+ var SELECTOR_GAP = 4;
3639
+ var getPositionOnDial = (index, total, radius) => {
3640
+ const angle = index / total * FULL_CIRCLE - QUARTER_CIRCLE;
3641
+ return {
3642
+ x: CENTER + radius * Math.cos(angle),
3643
+ y: CENTER + radius * Math.sin(angle)
3644
+ };
3645
+ };
3646
+ var buildHour12Numbers = () => Array.from({ length: 12 }, (_, i) => {
3647
+ const hour = i === 0 ? 12 : i;
3648
+ const pos = getPositionOnDial(i, 12, OUTER_RADIUS);
3649
+ return __spreadProps(__spreadValues({ value: hour }, pos), { radius: OUTER_RADIUS });
3650
+ });
3651
+ var buildHour24Numbers = () => {
3652
+ const outer = Array.from({ length: 12 }, (_, i) => {
3653
+ const hour = i;
3654
+ const pos = getPositionOnDial(i, 12, OUTER_RADIUS);
3655
+ return __spreadProps(__spreadValues({ value: hour }, pos), { radius: OUTER_RADIUS });
3656
+ });
3657
+ const inner = Array.from({ length: 12 }, (_, i) => {
3658
+ const hour = i + 12;
3659
+ const pos = getPositionOnDial(i, 12, INNER_RADIUS);
3660
+ return __spreadProps(__spreadValues({ value: hour }, pos), { radius: INNER_RADIUS });
3661
+ });
3662
+ return [...outer, ...inner];
3663
+ };
3664
+ var buildMinuteNumbers = () => Array.from({ length: 12 }, (_, i) => {
3665
+ const minute = i * 5;
3666
+ const pos = getPositionOnDial(i, 12, OUTER_RADIUS);
3667
+ return __spreadProps(__spreadValues({ value: minute }, pos), { radius: OUTER_RADIUS });
3668
+ });
3669
+ var getAngleFromPointer = (x, y) => {
3670
+ return Math.atan2(y - CENTER, x - CENTER);
3671
+ };
3672
+ var normalizeAngle = (angle) => {
3673
+ let a = angle % FULL_CIRCLE;
3674
+ if (a < 0) a += FULL_CIRCLE;
3675
+ return a;
3676
+ };
3677
+ var angleToHour12 = (angle) => {
3678
+ const hourOffset = RADIANS_PER_HOUR / 2;
3679
+ const totalOffset = hourOffset + QUARTER_CIRCLE;
3680
+ const hour = Math.round((angle + totalOffset) / RADIANS_PER_HOUR) % 12;
3681
+ return hour === 0 ? 12 : hour;
3682
+ };
3683
+ var angleToMinute = (angle, snap = true) => {
3684
+ if (snap) {
3685
+ const minuteOffset = RADIANS_PER_MINUTE / 2;
3686
+ const totalOffset = minuteOffset + QUARTER_CIRCLE;
3687
+ const raw2 = Math.round((angle + totalOffset) / RADIANS_PER_MINUTE) % 60;
3688
+ return raw2 < 0 ? raw2 + 60 : raw2;
3689
+ }
3690
+ const normalized = normalizeAngle(angle + QUARTER_CIRCLE);
3691
+ const raw = Math.round(normalized / FULL_CIRCLE * 60) % 60;
3692
+ return raw < 0 ? raw + 60 : raw;
3693
+ };
3694
+ var getSelectorPosition = (angle, selection, is24hour, isPm) => {
3695
+ const useInner = is24hour && isPm && selection === "hour";
3696
+ const baseRadius = useInner ? INNER_RADIUS : OUTER_RADIUS;
3697
+ const length = Math.min(baseRadius, CENTER - HANDLE_RADIUS - SELECTOR_GAP);
3698
+ return {
3699
+ x: CENTER + length * Math.cos(angle),
3700
+ y: CENTER + length * Math.sin(angle)
3701
+ };
3702
+ };
3703
+ var valueToAngle = (value, selection) => {
3704
+ if (selection === "hour") {
3705
+ return RADIANS_PER_HOUR * (value % 12) - QUARTER_CIRCLE;
3706
+ }
3707
+ return RADIANS_PER_MINUTE * value - QUARTER_CIRCLE;
3708
+ };
3709
+ var ClockDial = ({
3710
+ state,
3711
+ autoSwitchToMinute = true,
3712
+ style,
3713
+ className
3714
+ }) => {
3715
+ const {
3716
+ selection,
3717
+ is24hour,
3718
+ isPm,
3719
+ hour,
3720
+ minute,
3721
+ setHour,
3722
+ setMinute,
3723
+ setSelection
3724
+ } = state;
3725
+ const displayAngle = React17.useMemo(() => {
3726
+ if (selection === "hour") {
3727
+ return valueToAngle(hour, "hour");
3728
+ }
3729
+ return valueToAngle(minute, "minute");
3730
+ }, [selection, hour, minute]);
3731
+ const selectorPos = React17.useMemo(
3732
+ () => getSelectorPosition(displayAngle, selection, is24hour, isPm),
3733
+ [displayAngle, selection, is24hour, isPm]
3734
+ );
3735
+ const trackEnd = React17.useMemo(() => {
3736
+ const dx = selectorPos.x - CENTER;
3737
+ const dy = selectorPos.y - CENTER;
3738
+ const length = Math.hypot(dx, dy);
3739
+ if (length === 0) return selectorPos;
3740
+ return {
3741
+ x: selectorPos.x - dx / length * HANDLE_RADIUS,
3742
+ y: selectorPos.y - dy / length * HANDLE_RADIUS
3743
+ };
3744
+ }, [selectorPos]);
3745
+ const numbers = React17.useMemo(() => {
3746
+ if (selection === "minute") return buildMinuteNumbers();
3747
+ if (is24hour) return buildHour24Numbers();
3748
+ return buildHour12Numbers();
3749
+ }, [selection, is24hour]);
3750
+ const isDragging = React17.useRef(false);
3751
+ const svgRef = React17.useRef(null);
3752
+ const getSVGPoint = (e) => {
3753
+ const svg = svgRef.current;
3754
+ if (!svg) return { x: e.clientX, y: e.clientY };
3755
+ const rect = svg.getBoundingClientRect();
3756
+ const scaleX = TP_SIZE.clockDialSize / rect.width;
3757
+ const scaleY = TP_SIZE.clockDialSize / rect.height;
3758
+ return {
3759
+ x: (e.clientX - rect.left) * scaleX,
3760
+ y: (e.clientY - rect.top) * scaleY
3761
+ };
3762
+ };
3763
+ const handlePosition = (x, y, isPointerUp = false) => {
3764
+ const angle = getAngleFromPointer(x, y);
3765
+ if (selection === "hour") {
3766
+ let newHour;
3767
+ if (is24hour) {
3768
+ const dist = Math.sqrt((x - CENTER) ** 2 + (y - CENTER) ** 2);
3769
+ const isPmHour = dist < (OUTER_RADIUS + INNER_RADIUS) / 2;
3770
+ const hour12 = angleToHour12(angle);
3771
+ newHour = isPmHour ? hour12 + 12 : hour12 === 12 ? 0 : hour12;
3772
+ } else {
3773
+ const hour12 = angleToHour12(angle);
3774
+ newHour = hour12 === 12 ? isPm ? 12 : 0 : isPm ? hour12 + 12 : hour12;
3775
+ }
3776
+ setHour(newHour);
3777
+ if (isPointerUp && autoSwitchToMinute) {
3778
+ setTimeout(() => setSelection("minute"), 100);
3779
+ }
3780
+ } else {
3781
+ const newMinute = angleToMinute(angle, !isDragging.current);
3782
+ setMinute(newMinute);
3783
+ }
3784
+ };
3785
+ const handlePointerDown = (e) => {
3786
+ e.currentTarget.setPointerCapture(e.pointerId);
3787
+ isDragging.current = false;
3788
+ const { x, y } = getSVGPoint(e);
3789
+ handlePosition(x, y);
3790
+ };
3791
+ const handlePointerMove = (e) => {
3792
+ if (e.buttons === 0) return;
3793
+ isDragging.current = true;
3794
+ const { x, y } = getSVGPoint(e);
3795
+ handlePosition(x, y);
3796
+ };
3797
+ const handlePointerUp = (e) => {
3798
+ e.currentTarget.releasePointerCapture(e.pointerId);
3799
+ const { x, y } = getSVGPoint(e);
3800
+ handlePosition(x, y, true);
3801
+ isDragging.current = false;
3802
+ };
3803
+ const getDisplayHour = () => {
3804
+ if (is24hour) return hour;
3805
+ return hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
3806
+ };
3807
+ const isNumberHighlighted = (num) => {
3808
+ if (selection === "hour") return num.value === getDisplayHour();
3809
+ const dx = selectorPos.x - num.x;
3810
+ const dy = selectorPos.y - num.y;
3811
+ return Math.sqrt(dx * dx + dy * dy) < HANDLE_RADIUS;
3812
+ };
3813
+ const isNumberAtHandle = (num) => {
3814
+ if (selection === "hour") return num.value === getDisplayHour();
3815
+ return false;
3816
+ };
3817
+ return /* @__PURE__ */ jsxs(
3818
+ "svg",
3819
+ {
3820
+ ref: svgRef,
3821
+ width: TP_SIZE.clockDialSize,
3822
+ height: TP_SIZE.clockDialSize,
3823
+ viewBox: `0 0 ${TP_SIZE.clockDialSize} ${TP_SIZE.clockDialSize}`,
3824
+ role: "img",
3825
+ "aria-label": `Clock face, ${selection === "hour" ? "select hour" : "select minute"}`,
3826
+ style,
3827
+ overflow: "visible",
3828
+ className: cn("touch-none select-none cursor-pointer", className),
3829
+ onPointerDown: handlePointerDown,
3830
+ onPointerMove: handlePointerMove,
3831
+ onPointerUp: handlePointerUp,
3832
+ children: [
3833
+ /* @__PURE__ */ jsx("circle", { cx: CENTER, cy: CENTER, r: CENTER, fill: TP_COLORS.clockDial }),
3834
+ /* @__PURE__ */ jsx(
3835
+ "line",
3836
+ {
3837
+ x1: CENTER,
3838
+ y1: CENTER,
3839
+ x2: trackEnd.x,
3840
+ y2: trackEnd.y,
3841
+ stroke: TP_COLORS.selectorTrack,
3842
+ strokeWidth: TP_SIZE.selectorTrackWidth,
3843
+ strokeLinecap: "round"
3844
+ }
3845
+ ),
3846
+ /* @__PURE__ */ jsx(
3847
+ "circle",
3848
+ {
3849
+ cx: selectorPos.x,
3850
+ cy: selectorPos.y,
3851
+ r: HANDLE_RADIUS,
3852
+ fill: TP_COLORS.selectorHandle
3853
+ }
3854
+ ),
3855
+ /* @__PURE__ */ jsx(
3856
+ "circle",
3857
+ {
3858
+ cx: CENTER,
3859
+ cy: CENTER,
3860
+ r: TP_SIZE.selectorCenterSize / 2,
3861
+ fill: TP_COLORS.selectorCenter
3862
+ }
3863
+ ),
3864
+ numbers.map((num) => {
3865
+ const highlighted = isNumberHighlighted(num);
3866
+ const atHandle = isNumberAtHandle(num);
3867
+ return /* @__PURE__ */ jsx(
3868
+ "text",
3869
+ {
3870
+ x: atHandle ? selectorPos.x : num.x,
3871
+ y: atHandle ? selectorPos.y : num.y,
3872
+ textAnchor: "middle",
3873
+ dominantBaseline: "central",
3874
+ fontSize: is24hour && num.radius === INNER_RADIUS ? 12 : 14,
3875
+ fontWeight: highlighted ? "600" : "400",
3876
+ fill: highlighted ? TP_COLORS.clockDialSelected : TP_COLORS.clockDialUnselected,
3877
+ "aria-label": String(num.value),
3878
+ className: "pointer-events-none",
3879
+ children: selection === "hour" && is24hour ? String(num.value).padStart(2, "0") : num.value
3880
+ },
3881
+ `${num.value}-${num.radius}`
3882
+ );
3883
+ })
3884
+ ]
3885
+ }
3886
+ );
3887
+ };
3888
+ ClockDial.displayName = "ClockDial";
3889
+ var getHourForDisplay = (state) => {
3890
+ const { hour, is24hour, isPm } = state;
3891
+ if (is24hour) return hour;
3892
+ if (hour % 12 === 0) return 12;
3893
+ return isPm ? hour - 12 : hour;
3894
+ };
3895
+ var getAutoLayout = () => {
3896
+ if (typeof window === "undefined") return "vertical";
3897
+ const { innerHeight, innerWidth } = window;
3898
+ return innerWidth >= 600 && innerHeight >= 300 && innerWidth >= innerHeight ? "horizontal" : "vertical";
3899
+ };
3900
+ var useResolvedLayout = (layout) => {
3901
+ const [autoLayout, setAutoLayout] = React17.useState("vertical");
3902
+ React17.useEffect(() => {
3903
+ if (layout !== "auto") return;
3904
+ const updateLayout = () => setAutoLayout(getAutoLayout());
3905
+ updateLayout();
3906
+ window.addEventListener("resize", updateLayout);
3907
+ return () => window.removeEventListener("resize", updateLayout);
3908
+ }, [layout]);
3909
+ return layout === "auto" ? autoLayout : layout;
3910
+ };
3911
+ var ClockDisplay = ({ state, layout }) => {
3912
+ const { selection, setSelection, is24hour } = state;
3913
+ const periodLayout = layout === "horizontal" ? "horizontal" : "vertical";
3914
+ return /* @__PURE__ */ jsxs(
3915
+ "div",
3916
+ {
3917
+ className: cn(
3918
+ "flex",
3919
+ layout === "horizontal" ? "flex-col items-start justify-center" : "flex-row items-center justify-center"
3920
+ ),
3921
+ children: [
3922
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
3923
+ /* @__PURE__ */ jsx(
3924
+ TimeField,
3925
+ {
3926
+ mode: "selector",
3927
+ value: getHourForDisplay(state),
3928
+ field: "hour",
3929
+ selection,
3930
+ onActivate: () => setSelection("hour")
3931
+ }
3932
+ ),
3933
+ /* @__PURE__ */ jsx(
3934
+ "span",
3935
+ {
3936
+ "aria-hidden": true,
3937
+ className: "w-6 text-center text-[57px] leading-none font-normal text-m3-on-surface",
3938
+ children: ":"
3939
+ }
3940
+ ),
3941
+ /* @__PURE__ */ jsx(
3942
+ TimeField,
3943
+ {
3944
+ mode: "selector",
3945
+ value: state.minute,
3946
+ field: "minute",
3947
+ selection,
3948
+ onActivate: () => setSelection("minute")
3949
+ }
3950
+ )
3951
+ ] }),
3952
+ !is24hour ? /* @__PURE__ */ jsx(
3953
+ PeriodSelector,
3954
+ {
3955
+ state,
3956
+ layout: periodLayout,
3957
+ className: layout === "horizontal" ? "mt-3" : "ml-3"
3958
+ }
3959
+ ) : null
3960
+ ]
3961
+ }
3962
+ );
3963
+ };
3964
+ var TimePicker = ({
3965
+ state,
3966
+ layout = "auto",
3967
+ className
3968
+ }) => {
3969
+ const resolvedLayout = useResolvedLayout(layout);
3970
+ return /* @__PURE__ */ jsxs(
3971
+ "div",
3972
+ {
3973
+ "data-time-picker-layout": resolvedLayout,
3974
+ className: cn(
3975
+ "time-picker rounded-m3-xl bg-m3-surface-container-high p-6 select-none",
3976
+ className
3977
+ ),
3978
+ children: [
3979
+ /* @__PURE__ */ jsx("p", { className: "mb-5 text-left text-[12px] font-medium tracking-[0.1em] text-m3-on-surface-variant", children: "Select time" }),
3980
+ /* @__PURE__ */ jsxs(
3981
+ "div",
3982
+ {
3983
+ className: cn(
3984
+ "flex",
3985
+ resolvedLayout === "horizontal" ? "flex-row items-center gap-[52px]" : "flex-col items-center gap-9"
3986
+ ),
3987
+ children: [
3988
+ /* @__PURE__ */ jsx(ClockDisplay, { state, layout: resolvedLayout }),
3989
+ /* @__PURE__ */ jsx(
3990
+ ClockDial,
3991
+ {
3992
+ state,
3993
+ autoSwitchToMinute: true,
3994
+ style: {
3995
+ width: TP_SIZE.clockDialSize,
3996
+ height: TP_SIZE.clockDialSize
3997
+ }
3998
+ }
3999
+ )
4000
+ ]
4001
+ }
4002
+ )
4003
+ ]
4004
+ }
4005
+ );
4006
+ };
4007
+ TimePicker.displayName = "TimePicker";
4008
+ var TimePickerDialog = ({
4009
+ open,
4010
+ onDismiss,
4011
+ confirmButton,
4012
+ dismissButton,
4013
+ modeToggleButton,
4014
+ title = "Select time",
4015
+ children,
4016
+ className
4017
+ }) => {
4018
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (v) => !v && onDismiss(), children: /* @__PURE__ */ jsxs(DialogPortal, { open, children: [
4019
+ /* @__PURE__ */ jsx(RadixDialog.Overlay, { asChild: true, forceMount: true, children: /* @__PURE__ */ jsx(
4020
+ m.div,
4021
+ {
4022
+ className: "fixed inset-0 z-50 bg-m3-scrim/40",
4023
+ initial: PICKER_OVERLAY_ANIM.initial,
4024
+ animate: PICKER_OVERLAY_ANIM.animate,
4025
+ exit: PICKER_OVERLAY_ANIM.exit,
4026
+ onClick: onDismiss
4027
+ },
4028
+ "time-picker-overlay"
4029
+ ) }),
4030
+ /* @__PURE__ */ jsx(
4031
+ RadixDialog.Content,
4032
+ {
4033
+ asChild: true,
4034
+ forceMount: true,
4035
+ onEscapeKeyDown: onDismiss,
4036
+ onPointerDownOutside: (e) => e.preventDefault(),
4037
+ "aria-modal": true,
4038
+ "aria-describedby": void 0,
4039
+ children: /* @__PURE__ */ jsxs(
4040
+ m.div,
4041
+ {
4042
+ className: cn(
4043
+ // Positioning
4044
+ "fixed z-50 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2",
4045
+ // Layout
4046
+ "flex flex-col",
4047
+ // MD3 Time Picker container tokens
4048
+ "bg-m3-surface-container-high",
4049
+ "rounded-m3-xl",
4050
+ "overflow-hidden",
4051
+ "max-w-[calc(100vw-32px)] max-h-[calc(100dvh-32px)]",
4052
+ // Elevation Level 3
4053
+ "elevation-3",
4054
+ className
4055
+ ),
4056
+ initial: PICKER_CONTENT_ANIM.initial,
4057
+ animate: PICKER_CONTENT_ANIM.animate,
4058
+ exit: PICKER_CONTENT_ANIM.exit,
4059
+ children: [
4060
+ /* @__PURE__ */ jsx(RadixDialog.Title, { className: "sr-only", children: title }),
4061
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children }),
4062
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-16 items-center gap-2 px-6 py-2", children: [
4063
+ modeToggleButton && /* @__PURE__ */ jsx("div", { className: "flex items-center", children: modeToggleButton }),
4064
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
4065
+ dismissButton,
4066
+ confirmButton
4067
+ ] })
4068
+ ]
4069
+ },
4070
+ "time-picker-content"
4071
+ )
4072
+ }
4073
+ )
4074
+ ] }) });
4075
+ };
4076
+ TimePickerDialog.displayName = "TimePickerDialog";
4077
+ var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
4078
+ var isValidHourInput = (h, is24hour) => is24hour ? h >= 0 && h <= 23 : h >= 1 && h <= 12;
4079
+ var isValidMinuteInput = (m11) => m11 >= 0 && m11 <= 59;
4080
+ var to24Hour = (hour12, isPm) => {
4081
+ if (isPm) {
4082
+ return hour12 === 12 ? 12 : hour12 + 12;
4083
+ }
4084
+ return hour12 === 12 ? 0 : hour12;
4085
+ };
4086
+ var to12Hour = (hour24) => {
4087
+ if (hour24 === 0) return 12;
4088
+ if (hour24 > 12) return hour24 - 12;
4089
+ return hour24;
4090
+ };
4091
+ var useTimePickerState = (options) => {
4092
+ var _a, _b, _c;
4093
+ const initialHour = clamp((_a = options == null ? void 0 : options.initialHour) != null ? _a : 0, 0, 23);
4094
+ const initialMinute = clamp((_b = options == null ? void 0 : options.initialMinute) != null ? _b : 0, 0, 59);
4095
+ const is24hour = (_c = options == null ? void 0 : options.is24hour) != null ? _c : false;
4096
+ const [hour, setHourState] = useState(initialHour);
4097
+ const [minute, setMinuteState] = useState(initialMinute);
4098
+ const [hourInput, setHourInputState] = useState(
4099
+ is24hour ? initialHour : to12Hour(initialHour)
4100
+ );
4101
+ const [minuteInput, setMinuteInputState] = useState(initialMinute);
4102
+ const [selection, setSelectionState] = useState("hour");
4103
+ const isPm = hour >= 12;
4104
+ const isHourInputValid = isValidHourInput(hourInput, is24hour);
4105
+ const isMinuteInputValid = isValidMinuteInput(minuteInput);
4106
+ const isInputValid = isHourInputValid && isMinuteInputValid;
4107
+ const setHour = useCallback(
4108
+ (h) => {
4109
+ const clamped = clamp(h, 0, 23);
4110
+ setHourState(clamped);
4111
+ setHourInputState(is24hour ? clamped : to12Hour(clamped));
4112
+ },
4113
+ [is24hour]
4114
+ );
4115
+ const setMinute = useCallback((m11) => {
4116
+ const clamped = clamp(m11, 0, 59);
4117
+ setMinuteState(clamped);
4118
+ setMinuteInputState(clamped);
4119
+ }, []);
4120
+ const setHourInput = useCallback(
4121
+ (h) => {
4122
+ setHourInputState(h);
4123
+ if (isValidHourInput(h, is24hour)) {
4124
+ const h24 = is24hour ? h : to24Hour(h, isPm);
4125
+ setHourState(clamp(h24, 0, 23));
4126
+ }
4127
+ },
4128
+ [is24hour, isPm]
4129
+ );
4130
+ const setMinuteInput = useCallback((m11) => {
4131
+ setMinuteInputState(m11);
4132
+ if (isValidMinuteInput(m11)) {
4133
+ setMinuteState(m11);
4134
+ }
4135
+ }, []);
4136
+ const setSelection = useCallback((mode) => {
4137
+ setSelectionState(mode);
4138
+ }, []);
4139
+ const setIsPm = useCallback((pm) => {
4140
+ setHourState((prev) => {
4141
+ const wasAlreadyPm = prev >= 12;
4142
+ if (pm === wasAlreadyPm) return prev;
4143
+ const newHour = pm ? prev + 12 : prev - 12;
4144
+ const clamped = clamp(newHour, 0, 23);
4145
+ setHourInputState(to12Hour(clamped));
4146
+ return clamped;
4147
+ });
4148
+ }, []);
4149
+ const reset = useCallback(() => {
4150
+ setHourState(initialHour);
4151
+ setMinuteState(initialMinute);
4152
+ setHourInputState(is24hour ? initialHour : to12Hour(initialHour));
4153
+ setMinuteInputState(initialMinute);
4154
+ setSelectionState("hour");
4155
+ }, [initialHour, initialMinute, is24hour]);
4156
+ return useMemo(
4157
+ () => ({
4158
+ hour,
4159
+ minute,
4160
+ hourInput,
4161
+ minuteInput,
4162
+ is24hour,
4163
+ selection,
4164
+ isInputValid,
4165
+ isHourInputValid,
4166
+ isMinuteInputValid,
4167
+ isPm,
4168
+ setHour,
4169
+ setMinute,
4170
+ setHourInput,
4171
+ setMinuteInput,
4172
+ setSelection,
4173
+ setIsPm,
4174
+ reset
4175
+ }),
4176
+ [
4177
+ hour,
4178
+ minute,
4179
+ hourInput,
4180
+ minuteInput,
4181
+ is24hour,
4182
+ selection,
4183
+ isInputValid,
4184
+ isHourInputValid,
4185
+ isMinuteInputValid,
4186
+ isPm,
4187
+ setHour,
4188
+ setMinute,
4189
+ setHourInput,
4190
+ setMinuteInput,
4191
+ setSelection,
4192
+ setIsPm,
4193
+ reset
4194
+ ]
4195
+ );
4196
+ };
4197
+
4198
+ export { DP_CLASSES, DP_COLORS, DP_SHAPE, DP_SIZE, DatePicker, DatePickerDialog, DatePickerInput, DateRangePicker, TP_CLASSES, TP_COLORS, TP_SHAPE, TP_SIZE, TimeInput, TimePicker, TimePickerDialog, useDatePickerState, useDateRangePickerState, useTimePickerState };
4199
+ //# sourceMappingURL=pickers.mjs.map
4200
+ //# sourceMappingURL=pickers.mjs.map