@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
package/dist/forms.mjs ADDED
@@ -0,0 +1,4228 @@
1
+ "use client";
2
+ import { m, AnimatePresence, useReducedMotion, LazyMotion, domMax } from 'motion/react';
3
+ import * as React4 from 'react';
4
+ import { useMemo } from 'react';
5
+ import { clsx } from 'clsx';
6
+ import { twMerge } from 'tailwind-merge';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+ import { cva } from 'class-variance-authority';
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
+ var RippleItem = React4.memo(function RippleItem2({
46
+ ripple,
47
+ onDone
48
+ }) {
49
+ return /* @__PURE__ */ jsx(
50
+ m.span,
51
+ {
52
+ "aria-hidden": "true",
53
+ style: {
54
+ position: "absolute",
55
+ left: ripple.x - ripple.size / 2,
56
+ top: ripple.y - ripple.size / 2,
57
+ width: ripple.size,
58
+ height: ripple.size,
59
+ borderRadius: "50%",
60
+ backgroundColor: "currentColor",
61
+ pointerEvents: "none",
62
+ transformOrigin: "center"
63
+ },
64
+ initial: { scale: 0, opacity: 0.12 },
65
+ animate: { scale: 1, opacity: 0 },
66
+ exit: { opacity: 0 },
67
+ transition: {
68
+ scale: { duration: 0.5, ease: [0.2, 0, 0, 1] },
69
+ opacity: { duration: 0.4, ease: "easeOut", delay: 0.1 }
70
+ },
71
+ onAnimationComplete: () => onDone(ripple.id)
72
+ },
73
+ ripple.id
74
+ );
75
+ });
76
+ function Ripple({
77
+ ripples,
78
+ onRippleDone,
79
+ disabled = false,
80
+ respectSystemMotion = true
81
+ }) {
82
+ const prefersReduced = useReducedMotion();
83
+ if (disabled) return null;
84
+ if (respectSystemMotion && prefersReduced) return null;
85
+ return /* @__PURE__ */ jsx(AnimatePresence, { children: ripples.map((r) => /* @__PURE__ */ jsx(RippleItem, { ripple: r, onDone: onRippleDone }, r.id)) });
86
+ }
87
+ function useRippleState(options = {}) {
88
+ const { disabled = false } = options;
89
+ const [ripples, setRipples] = React4.useState([]);
90
+ const onPointerDown = React4.useCallback(
91
+ (e) => {
92
+ if (disabled) return;
93
+ const rect = e.currentTarget.getBoundingClientRect();
94
+ const x = e.clientX - rect.left;
95
+ const y = e.clientY - rect.top;
96
+ const rippleSize = Math.hypot(rect.width, rect.height) * 2;
97
+ setRipples((prev) => [
98
+ ...prev,
99
+ { id: Date.now(), x, y, size: rippleSize }
100
+ ]);
101
+ },
102
+ [disabled]
103
+ );
104
+ const removeRipple = React4.useCallback((id) => {
105
+ setRipples((prev) => prev.filter((r) => r.id !== id));
106
+ }, []);
107
+ return { ripples, onPointerDown, removeRipple };
108
+ }
109
+ var MD3_STANDARD = [0.2, 0, 0, 1];
110
+ var MD3_FAST_EFFECTS = [0.3, 0, 1, 1];
111
+ var CHECKMARK_PATH = "M 4.5 9.5 L 7.5 12.5 L 13.5 5.5";
112
+ var DASH_PATH = "M 4.5 9 L 9 9 L 13.5 9";
113
+ function resolveState(checked, indeterminate, state) {
114
+ if (state !== void 0) return state;
115
+ return checked ? "checked" : "unchecked";
116
+ }
117
+ var NEXT_STATE = {
118
+ unchecked: "checked",
119
+ checked: "indeterminate",
120
+ indeterminate: "unchecked"
121
+ };
122
+ var CheckboxVisual = React4.memo(function CheckboxVisual2({
123
+ isSelected,
124
+ isIndeterminate,
125
+ containerBg,
126
+ containerBorderColor,
127
+ containerBorderWidth,
128
+ iconColor,
129
+ svgPath,
130
+ pathLength,
131
+ fillDuration,
132
+ drawDuration,
133
+ morphDuration,
134
+ prefersReduced
135
+ }) {
136
+ return /* @__PURE__ */ jsx(
137
+ m.div,
138
+ {
139
+ "aria-hidden": "true",
140
+ className: "relative flex items-center justify-center w-4.5 h-4.5 rounded-sm overflow-hidden",
141
+ animate: {
142
+ backgroundColor: containerBg,
143
+ borderColor: containerBorderColor,
144
+ borderWidth: containerBorderWidth
145
+ },
146
+ transition: {
147
+ backgroundColor: {
148
+ duration: fillDuration,
149
+ ease: isSelected ? MD3_FAST_EFFECTS : "easeOut"
150
+ },
151
+ borderColor: { duration: fillDuration, ease: "easeOut" },
152
+ borderWidth: { duration: fillDuration, ease: "easeOut" }
153
+ },
154
+ style: { borderStyle: "solid" },
155
+ children: /* @__PURE__ */ jsx(AnimatePresence, { children: isSelected && /* @__PURE__ */ jsx(
156
+ m.svg,
157
+ {
158
+ viewBox: "0 0 18 18",
159
+ fill: "none",
160
+ strokeLinecap: "round",
161
+ strokeLinejoin: "round",
162
+ width: 18,
163
+ height: 18,
164
+ initial: { opacity: 0 },
165
+ animate: { opacity: 1 },
166
+ exit: { opacity: 0 },
167
+ transition: { duration: prefersReduced ? 0 : 0.1 },
168
+ "aria-hidden": "true",
169
+ children: /* @__PURE__ */ jsx(
170
+ m.path,
171
+ {
172
+ d: svgPath,
173
+ stroke: iconColor,
174
+ strokeWidth: 2,
175
+ animate: {
176
+ d: svgPath,
177
+ pathLength: isIndeterminate ? 1 : pathLength
178
+ },
179
+ initial: { pathLength: 0 },
180
+ transition: {
181
+ d: { duration: morphDuration, ease: MD3_STANDARD },
182
+ pathLength: { duration: drawDuration, ease: MD3_STANDARD }
183
+ }
184
+ }
185
+ )
186
+ },
187
+ "icon"
188
+ ) })
189
+ }
190
+ );
191
+ });
192
+ function useMergedRef(externalRef, internalRef) {
193
+ return React4.useCallback(
194
+ (node) => {
195
+ internalRef.current = node;
196
+ if (!externalRef) return;
197
+ if (typeof externalRef === "function") {
198
+ externalRef(node);
199
+ } else {
200
+ externalRef.current = node;
201
+ }
202
+ },
203
+ [externalRef, internalRef]
204
+ );
205
+ }
206
+ var CheckboxComponent = React4.forwardRef(
207
+ ({
208
+ checked,
209
+ defaultChecked = false,
210
+ indeterminate = false,
211
+ onCheckedChange,
212
+ state: stateProp,
213
+ onStateChange,
214
+ disabled = false,
215
+ error = false,
216
+ label,
217
+ "aria-label": ariaLabel,
218
+ "aria-labelledby": ariaLabelledby,
219
+ "aria-describedby": ariaDescribedby,
220
+ "aria-required": ariaRequired,
221
+ name,
222
+ value,
223
+ id: idProp,
224
+ className
225
+ }, ref) => {
226
+ var _a;
227
+ const prefersReduced = (_a = useReducedMotion()) != null ? _a : false;
228
+ const generatedId = React4.useId();
229
+ const inputId = idProp != null ? idProp : label ? `checkbox-${generatedId}` : void 0;
230
+ const [internalState, setInternalState] = React4.useState(
231
+ () => defaultChecked ? "checked" : "unchecked"
232
+ );
233
+ const isControlled = stateProp !== void 0 || checked !== void 0;
234
+ const baseState = isControlled ? resolveState(checked, false, stateProp) : internalState;
235
+ const effectiveState = indeterminate ? "indeterminate" : baseState;
236
+ const [ripples, setRipples] = React4.useState([]);
237
+ const removeRipple = React4.useCallback(
238
+ (id) => setRipples((prev) => prev.filter((r) => r.id !== id)),
239
+ []
240
+ );
241
+ const onPointerDown = React4.useCallback(
242
+ (e) => {
243
+ if (disabled) return;
244
+ const rect = e.currentTarget.getBoundingClientRect();
245
+ const x = e.clientX - rect.left - 4;
246
+ const y = e.clientY - rect.top - 4;
247
+ const rippleSize = Math.hypot(40, 40) * 2;
248
+ setRipples((prev) => [
249
+ ...prev,
250
+ { id: Date.now(), x, y, size: rippleSize }
251
+ ]);
252
+ },
253
+ [disabled]
254
+ );
255
+ const handleChange = React4.useCallback(
256
+ (e) => {
257
+ if (disabled) return;
258
+ if (stateProp !== void 0) {
259
+ onStateChange == null ? void 0 : onStateChange(NEXT_STATE[effectiveState]);
260
+ } else if (checked !== void 0) {
261
+ onCheckedChange == null ? void 0 : onCheckedChange(e.target.checked);
262
+ } else {
263
+ const next = NEXT_STATE[effectiveState];
264
+ setInternalState(next);
265
+ if (next === "indeterminate") {
266
+ onStateChange == null ? void 0 : onStateChange(next);
267
+ } else {
268
+ onCheckedChange == null ? void 0 : onCheckedChange(next === "checked");
269
+ }
270
+ }
271
+ },
272
+ [
273
+ disabled,
274
+ stateProp,
275
+ checked,
276
+ effectiveState,
277
+ onStateChange,
278
+ onCheckedChange
279
+ ]
280
+ );
281
+ const inputRef = React4.useRef(null);
282
+ const mergedRef = useMergedRef(ref, inputRef);
283
+ React4.useEffect(() => {
284
+ if (inputRef.current) {
285
+ inputRef.current.indeterminate = effectiveState === "indeterminate";
286
+ }
287
+ }, [effectiveState]);
288
+ const isChecked = effectiveState === "checked";
289
+ const isIndeterminate = effectiveState === "indeterminate";
290
+ const isSelected = isChecked || isIndeterminate;
291
+ const ariaChecked = isIndeterminate ? "mixed" : isChecked;
292
+ const accentColor = error ? "var(--color-m3-error)" : "var(--color-m3-primary)";
293
+ const onAccentColor = error ? "var(--color-m3-on-error)" : "var(--color-m3-on-primary)";
294
+ const outlineColor = error ? "var(--color-m3-error)" : "rgba(0, 0, 0, 0.38)";
295
+ const containerBg = isSelected ? accentColor : "rgba(0, 0, 0, 0)";
296
+ const containerBorderColor = isSelected ? "rgba(0, 0, 0, 0)" : outlineColor;
297
+ const containerBorderWidth = isSelected ? 0 : 2;
298
+ const iconColor = isSelected ? onAccentColor : "rgba(0, 0, 0, 0)";
299
+ const svgPath = isIndeterminate ? DASH_PATH : CHECKMARK_PATH;
300
+ const pathLength = isSelected ? 1 : 0;
301
+ const fillDuration = prefersReduced ? 0 : isSelected ? 0.15 : 0.1;
302
+ const drawDuration = prefersReduced ? 0 : isSelected ? 0.2 : 0.1;
303
+ const morphDuration = prefersReduced ? 0 : 0.2;
304
+ const stateLayerBg = isSelected ? error ? "before:bg-m3-error" : "before:bg-m3-primary" : error ? "before:bg-m3-error" : "before:bg-m3-on-surface";
305
+ const stateLayerClass = cn(
306
+ "before:absolute before:inset-0 before:rounded-full before:pointer-events-none",
307
+ "before:transition-opacity before:duration-150 before:opacity-0",
308
+ "group-hover/cbx:before:opacity-[0.08]",
309
+ "group-focus-within/cbx:before:opacity-[0.10]",
310
+ "group-active/cbx:before:opacity-[0.10]",
311
+ stateLayerBg
312
+ );
313
+ const visualProps = {
314
+ isSelected,
315
+ isIndeterminate,
316
+ containerBg,
317
+ containerBorderColor,
318
+ containerBorderWidth,
319
+ iconColor,
320
+ svgPath,
321
+ pathLength,
322
+ fillDuration,
323
+ drawDuration,
324
+ morphDuration,
325
+ prefersReduced
326
+ };
327
+ const touchTargetClass = cn(
328
+ "relative inline-flex items-center justify-center outline-none shrink-0",
329
+ "w-12 h-12 group/cbx",
330
+ disabled && "pointer-events-none"
331
+ );
332
+ const stateLayerAndRipple = /* @__PURE__ */ jsx(
333
+ "div",
334
+ {
335
+ className: cn(
336
+ "absolute flex items-center justify-center w-10 h-10 m-auto inset-0 rounded-full overflow-hidden pointer-events-none",
337
+ stateLayerClass
338
+ ),
339
+ "aria-hidden": "true",
340
+ children: /* @__PURE__ */ jsx(
341
+ Ripple,
342
+ {
343
+ ripples,
344
+ onRippleDone: removeRipple,
345
+ disabled
346
+ }
347
+ )
348
+ }
349
+ );
350
+ const hiddenInput = /* @__PURE__ */ jsx(
351
+ "input",
352
+ {
353
+ ref: mergedRef,
354
+ type: "checkbox",
355
+ id: inputId,
356
+ name,
357
+ value,
358
+ checked: isChecked,
359
+ disabled,
360
+ "aria-checked": ariaChecked,
361
+ "aria-disabled": disabled || void 0,
362
+ "aria-invalid": error || void 0,
363
+ "aria-label": ariaLabel,
364
+ "aria-labelledby": ariaLabelledby,
365
+ "aria-describedby": ariaDescribedby,
366
+ "aria-required": ariaRequired,
367
+ onChange: handleChange,
368
+ className: "sr-only"
369
+ }
370
+ );
371
+ if (label) {
372
+ return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: /* @__PURE__ */ jsxs(
373
+ "label",
374
+ {
375
+ htmlFor: inputId,
376
+ className: cn(
377
+ "inline-flex items-center gap-2 cursor-pointer select-none",
378
+ disabled && "cursor-not-allowed opacity-[0.38] pointer-events-none",
379
+ className
380
+ ),
381
+ children: [
382
+ /* @__PURE__ */ jsxs("div", { className: touchTargetClass, onPointerDown, children: [
383
+ stateLayerAndRipple,
384
+ hiddenInput,
385
+ /* @__PURE__ */ jsx(CheckboxVisual, __spreadValues({}, visualProps))
386
+ ] }),
387
+ /* @__PURE__ */ jsx("span", { className: "text-sm leading-none text-m3-on-surface", children: label })
388
+ ]
389
+ }
390
+ ) });
391
+ }
392
+ return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: /* @__PURE__ */ jsxs(
393
+ "label",
394
+ {
395
+ htmlFor: inputId,
396
+ className: cn(touchTargetClass, "cursor-pointer", className),
397
+ onPointerDown,
398
+ children: [
399
+ stateLayerAndRipple,
400
+ hiddenInput,
401
+ /* @__PURE__ */ jsx(CheckboxVisual, __spreadValues({}, visualProps))
402
+ ]
403
+ }
404
+ ) });
405
+ }
406
+ );
407
+ CheckboxComponent.displayName = "Checkbox";
408
+ var Checkbox = React4.memo(CheckboxComponent);
409
+ var TriStateCheckboxComponent = React4.forwardRef((_a, ref) => {
410
+ var _b = _a, { state, onStateChange } = _b, rest = __objRest(_b, ["state", "onStateChange"]);
411
+ return /* @__PURE__ */ jsx(Checkbox, __spreadValues({ ref, state, onStateChange }, rest));
412
+ });
413
+ TriStateCheckboxComponent.displayName = "TriStateCheckbox";
414
+ var TriStateCheckbox = React4.memo(TriStateCheckboxComponent);
415
+ function CheckIcon({ className }) {
416
+ return /* @__PURE__ */ jsx(
417
+ "svg",
418
+ {
419
+ xmlns: "http://www.w3.org/2000/svg",
420
+ viewBox: "0 0 24 24",
421
+ width: 18,
422
+ height: 18,
423
+ fill: "none",
424
+ stroke: "currentColor",
425
+ strokeWidth: 2.5,
426
+ strokeLinecap: "round",
427
+ strokeLinejoin: "round",
428
+ "aria-hidden": "true",
429
+ className,
430
+ children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" })
431
+ }
432
+ );
433
+ }
434
+ function CloseIcon({ className }) {
435
+ return /* @__PURE__ */ jsxs(
436
+ "svg",
437
+ {
438
+ xmlns: "http://www.w3.org/2000/svg",
439
+ viewBox: "0 0 24 24",
440
+ width: 18,
441
+ height: 18,
442
+ fill: "none",
443
+ stroke: "currentColor",
444
+ strokeWidth: 2.5,
445
+ strokeLinecap: "round",
446
+ strokeLinejoin: "round",
447
+ "aria-hidden": "true",
448
+ className,
449
+ children: [
450
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
451
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
452
+ ]
453
+ }
454
+ );
455
+ }
456
+ var chipVariants = cva(
457
+ [
458
+ // Base layout
459
+ "inline-flex items-center h-8 rounded-lg",
460
+ // Typography: LabelLarge
461
+ "text-sm font-medium leading-none",
462
+ // Interaction
463
+ "relative overflow-hidden cursor-pointer select-none",
464
+ "transition-all duration-200 ease-in-out",
465
+ // Remove browser default focus; use MD3 focus ring
466
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1",
467
+ "focus-visible:ring-m3-secondary",
468
+ // Expressive scale feedback (MD3 "bouncy" press)
469
+ "active:scale-[0.98]",
470
+ // MD3 State Layer (pseudo-element overlay)
471
+ // Use inset-[-1px] to ensure the state layer covers the 1px border perfectly
472
+ "before:absolute before:inset-[-1px] before:pointer-events-none before:rounded-lg",
473
+ "before:transition-opacity before:duration-200 before:opacity-0",
474
+ "hover:before:opacity-[0.08] focus-visible:before:opacity-[0.10] active:before:opacity-[0.10]"
475
+ ].join(" "),
476
+ {
477
+ variants: {
478
+ /**
479
+ * Chip variant controlling default colors and border behavior.
480
+ * Selected/elevated state overrides are applied via `cn()` at runtime.
481
+ */
482
+ variant: {
483
+ /**
484
+ * Assist chip – FlatOutlineColor: outline-variant, LabelTextColor: on-surface
485
+ * Leading icon color: primary (AssistChipTokens.IconColor)
486
+ */
487
+ assist: "border border-m3-outline-variant text-m3-on-surface before:bg-m3-on-surface",
488
+ /**
489
+ * Filter chip (unselected) – FlatUnselectedOutlineColor: outline-variant
490
+ * UnselectedLabelTextColor: on-surface-variant
491
+ */
492
+ filter: "border border-m3-outline-variant text-m3-on-surface-variant before:bg-m3-on-surface-variant",
493
+ /**
494
+ * Input chip (unselected) – UnselectedOutlineColor: outline-variant
495
+ * UnselectedLabelTextColor: on-surface-variant
496
+ */
497
+ input: "border border-m3-outline-variant text-m3-on-surface-variant before:bg-m3-on-surface-variant",
498
+ /**
499
+ * Suggestion chip – FlatOutlineColor: outline-variant
500
+ * LabelTextColor: on-surface-variant (SuggestionChipTokens)
501
+ */
502
+ suggestion: "border border-m3-outline-variant text-m3-on-surface-variant before:bg-m3-on-surface-variant"
503
+ }
504
+ },
505
+ defaultVariants: { variant: "assist" }
506
+ }
507
+ );
508
+ var ChipImpl = React4.forwardRef(
509
+ (_a, ref) => {
510
+ var _b = _a, {
511
+ variant = "assist",
512
+ elevated = false,
513
+ selected = false,
514
+ disabled = false,
515
+ label,
516
+ leadingIcon,
517
+ trailingIcon,
518
+ avatar,
519
+ onRemove,
520
+ className,
521
+ onClick
522
+ } = _b, props = __objRest(_b, [
523
+ "variant",
524
+ "elevated",
525
+ "selected",
526
+ "disabled",
527
+ "label",
528
+ "leadingIcon",
529
+ "trailingIcon",
530
+ "avatar",
531
+ "onRemove",
532
+ "className",
533
+ "onClick"
534
+ ]);
535
+ const { ripples, onPointerDown, removeRipple } = useRippleState({
536
+ disabled
537
+ });
538
+ const isFilter = variant === "filter";
539
+ const isInput = variant === "input";
540
+ const resolvedLeadingIcon = isInput && avatar ? avatar : leadingIcon;
541
+ const showCheckmark = isFilter && selected;
542
+ const hasTrailingContent = !!trailingIcon || !!onRemove;
543
+ const hasLeadingContent = isFilter || !!resolvedLeadingIcon;
544
+ const paddingClass = React4.useMemo(
545
+ () => cn(
546
+ !isInput && !hasLeadingContent && !hasTrailingContent && "px-4",
547
+ !isInput && hasLeadingContent && !hasTrailingContent && "pl-2 pr-4",
548
+ !isInput && !hasLeadingContent && hasTrailingContent && "pl-4 pr-2",
549
+ !isInput && hasLeadingContent && hasTrailingContent && "px-2",
550
+ isInput && !hasLeadingContent && !hasTrailingContent && "px-3",
551
+ isInput && hasLeadingContent && !hasTrailingContent && "pl-1 pr-3",
552
+ isInput && !hasLeadingContent && hasTrailingContent && "pl-3 pr-2",
553
+ isInput && hasLeadingContent && hasTrailingContent && "pl-1 pr-2"
554
+ ),
555
+ [isInput, hasLeadingContent, hasTrailingContent]
556
+ );
557
+ const stateClass = React4.useMemo(
558
+ () => cn(
559
+ (isFilter || isInput) && selected && "bg-m3-secondary-container text-m3-on-secondary-container border-none before:bg-m3-on-secondary-container",
560
+ elevated && !selected && "bg-m3-surface-container-low border-none elevation-1",
561
+ elevated && isFilter && selected && "elevation-1",
562
+ disabled && "opacity-[0.38] pointer-events-none cursor-not-allowed",
563
+ disabled && !selected && "border-m3-outline-variant/[.12]",
564
+ disabled && selected && "bg-m3-on-surface/[.12] text-m3-on-surface border-none"
565
+ ),
566
+ [isFilter, isInput, selected, elevated, disabled]
567
+ );
568
+ const leadingIconColorClass = React4.useMemo(
569
+ () => cn(
570
+ (variant === "assist" || variant === "suggestion") && "text-m3-primary",
571
+ isFilter && !selected && "text-m3-primary",
572
+ isFilter && selected && "text-m3-on-secondary-container",
573
+ isInput && !selected && "text-m3-on-surface-variant",
574
+ isInput && selected && "text-m3-primary"
575
+ ),
576
+ [variant, isFilter, isInput, selected]
577
+ );
578
+ const isCompound = !!onRemove;
579
+ const Root2 = isCompound ? "div" : "button";
580
+ const containerClass = cn(
581
+ chipVariants({ variant }),
582
+ !isCompound && paddingClass,
583
+ !isCompound && "gap-2",
584
+ stateClass,
585
+ // When compound, the root div handles the overall shape but not the main interaction
586
+ // We disable the root's state layer (hover/focus) to avoid the 1px gap issue
587
+ isCompound && "items-stretch cursor-default active:scale-100 before:opacity-0 gap-0",
588
+ className
589
+ );
590
+ const mainContent = /* @__PURE__ */ jsxs(Fragment, { children: [
591
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, mode: "wait", children: isFilter ? showCheckmark ? /* @__PURE__ */ jsx(
592
+ m.span,
593
+ {
594
+ initial: { width: 0, opacity: 0 },
595
+ animate: { width: 18, opacity: 1 },
596
+ exit: { width: 0, opacity: 0 },
597
+ transition: {
598
+ width: { duration: 0.2, ease: [0.2, 0, 0, 1] },
599
+ opacity: { duration: 0.15, ease: "easeOut" }
600
+ },
601
+ className: "flex items-center justify-center shrink-0 overflow-hidden",
602
+ "aria-hidden": "true",
603
+ children: /* @__PURE__ */ jsx(CheckIcon, {})
604
+ },
605
+ "checkmark"
606
+ ) : resolvedLeadingIcon ? /* @__PURE__ */ jsx(
607
+ m.span,
608
+ {
609
+ initial: { width: 0, opacity: 0 },
610
+ animate: { width: 18, opacity: 1 },
611
+ exit: { width: 0, opacity: 0 },
612
+ transition: {
613
+ width: { duration: 0.2, ease: [0.2, 0, 0, 1] },
614
+ opacity: { duration: 0.15, ease: "easeOut" }
615
+ },
616
+ className: cn(
617
+ "flex items-center justify-center shrink-0 overflow-hidden [&_.md-icon]:text-[length:inherit]!",
618
+ leadingIconColorClass
619
+ ),
620
+ style: { fontSize: 18 },
621
+ "aria-hidden": "true",
622
+ children: resolvedLeadingIcon
623
+ },
624
+ "leading-icon"
625
+ ) : null : resolvedLeadingIcon ? isInput && avatar ? /* @__PURE__ */ jsx(
626
+ "span",
627
+ {
628
+ className: "flex items-center justify-center shrink-0 w-6 h-6 rounded-full overflow-hidden",
629
+ "aria-hidden": "true",
630
+ children: resolvedLeadingIcon
631
+ },
632
+ "avatar"
633
+ ) : /* @__PURE__ */ jsx(
634
+ "span",
635
+ {
636
+ className: cn(
637
+ "flex items-center justify-center shrink-0 w-4.5 h-4.5 [&_.md-icon]:text-[length:inherit]!",
638
+ leadingIconColorClass
639
+ ),
640
+ style: { fontSize: 18 },
641
+ "aria-hidden": "true",
642
+ children: resolvedLeadingIcon
643
+ },
644
+ "leading-icon"
645
+ ) : null }),
646
+ /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap", children: label })
647
+ ] });
648
+ return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: /* @__PURE__ */ jsxs(
649
+ Root2,
650
+ __spreadProps(__spreadValues(__spreadProps(__spreadValues({
651
+ ref: !isCompound ? ref : void 0,
652
+ type: !isCompound ? "button" : void 0
653
+ }, isFilter ? {
654
+ role: "checkbox",
655
+ "aria-checked": selected
656
+ } : isInput && !isCompound && selected ? { role: "button", "aria-pressed": true } : isCompound ? { role: "group" } : { role: "button" }), {
657
+ "aria-disabled": disabled || void 0,
658
+ tabIndex: isCompound ? -1 : disabled ? -1 : 0,
659
+ disabled: !isCompound ? disabled : void 0,
660
+ onClick: !isCompound ? onClick : void 0,
661
+ onPointerDown: !isCompound ? onPointerDown : void 0,
662
+ className: containerClass
663
+ }), isCompound ? {} : props), {
664
+ children: [
665
+ !isCompound && /* @__PURE__ */ jsx(Ripple, { ripples, onRippleDone: removeRipple }),
666
+ isCompound ? /* @__PURE__ */ jsxs(
667
+ "button",
668
+ {
669
+ ref,
670
+ type: "button",
671
+ tabIndex: disabled ? -1 : 0,
672
+ disabled,
673
+ onClick,
674
+ onPointerDown,
675
+ className: cn(
676
+ "flex items-center h-full grow focus:outline-none appearance-none bg-transparent border-none",
677
+ "text-inherit font-inherit cursor-pointer",
678
+ // Move padding here; keep horizontal padding but remove trailing to fit next to X
679
+ paddingClass,
680
+ "gap-2",
681
+ "pr-0",
682
+ // Re-apply focus ring to internal button instead of root div
683
+ "focus-visible:ring-2 focus-visible:ring-m3-secondary focus-visible:ring-inset",
684
+ "rounded-l-[7px] rounded-r-none",
685
+ // State layer for main area
686
+ "relative overflow-hidden",
687
+ "before:absolute before:-inset-px before:pointer-events-none before:bg-current",
688
+ "before:transition-opacity before:duration-200 before:opacity-0 before:rounded-l-[7px]",
689
+ "hover:before:opacity-[0.08] focus-visible:before:opacity-[0.10] active:before:opacity-[0.10]"
690
+ ),
691
+ children: [
692
+ /* @__PURE__ */ jsx(Ripple, { ripples, onRippleDone: removeRipple }),
693
+ mainContent
694
+ ]
695
+ }
696
+ ) : mainContent,
697
+ hasTrailingContent && /* @__PURE__ */ jsx("span", { className: "flex items-center justify-center shrink-0", children: onRemove ? /* @__PURE__ */ jsx(
698
+ "button",
699
+ {
700
+ type: "button",
701
+ tabIndex: disabled ? -1 : 0,
702
+ "aria-label": typeof label === "string" ? `Remove ${label}` : "Remove",
703
+ onClick: (e) => {
704
+ e.stopPropagation();
705
+ onRemove(e);
706
+ },
707
+ onPointerDown: (e) => e.stopPropagation(),
708
+ className: cn(
709
+ "flex items-center justify-center w-8.5 h-full",
710
+ // 18px icon + 8px left + 8px right padding
711
+ "cursor-pointer focus-visible:outline-none",
712
+ "transition-all duration-150",
713
+ "relative overflow-hidden rounded-r-[7px] rounded-l-none",
714
+ "before:absolute before:-inset-px before:pointer-events-none before:bg-current",
715
+ "before:transition-opacity before:duration-200 before:opacity-0 before:rounded-r-[7px]",
716
+ "hover:before:opacity-[0.08] active:before:opacity-[0.12]",
717
+ // TrailingIcon color tokens
718
+ selected ? "text-m3-on-secondary-container" : "text-m3-on-surface-variant"
719
+ ),
720
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
721
+ }
722
+ ) : trailingIcon ? /* @__PURE__ */ jsx(
723
+ "span",
724
+ {
725
+ className: cn(
726
+ "flex items-center justify-center w-4.5 h-4.5 [&_.md-icon]:text-[length:inherit]!",
727
+ selected ? "text-m3-on-secondary-container" : "text-m3-on-surface-variant"
728
+ ),
729
+ style: { fontSize: 18 },
730
+ "aria-hidden": "true",
731
+ children: trailingIcon
732
+ }
733
+ ) : null })
734
+ ]
735
+ })
736
+ ) });
737
+ }
738
+ );
739
+ ChipImpl.displayName = "Chip";
740
+ var Chip = React4.memo(ChipImpl);
741
+ var MD3_FAST_EFFECTS2 = [0.3, 0, 1, 1];
742
+ var RadioGroupContext = React4.createContext(
743
+ null
744
+ );
745
+ function useMergedRef2(externalRef, internalRef) {
746
+ return React4.useCallback(
747
+ (node) => {
748
+ internalRef.current = node;
749
+ if (!externalRef) return;
750
+ if (typeof externalRef === "function") {
751
+ externalRef(node);
752
+ } else {
753
+ externalRef.current = node;
754
+ }
755
+ },
756
+ [externalRef, internalRef]
757
+ );
758
+ }
759
+ var RadioVisual = React4.memo(function RadioVisual2({
760
+ isSelected,
761
+ disabled,
762
+ error,
763
+ isHovered,
764
+ prefersReduced
765
+ }) {
766
+ const accentColor = error ? "var(--color-m3-error)" : "var(--color-m3-primary)";
767
+ const disabledColor = "rgba(0, 0, 0, 0.38)";
768
+ const outerStroke = disabled ? disabledColor : isSelected ? accentColor : isHovered ? "var(--color-m3-on-surface)" : "var(--color-m3-on-surface-variant)";
769
+ const dotFill = disabled ? disabledColor : isSelected ? accentColor : "rgba(0, 0, 0, 0)";
770
+ const ringDuration = prefersReduced ? 0 : 0.15;
771
+ const dotDuration = prefersReduced ? 0 : isSelected ? 0.2 : 0.1;
772
+ const dotEase = isSelected ? MD3_FAST_EFFECTS2 : "easeOut";
773
+ return /* @__PURE__ */ jsxs(
774
+ "svg",
775
+ {
776
+ viewBox: "0 0 20 20",
777
+ width: 20,
778
+ height: 20,
779
+ fill: "none",
780
+ "aria-hidden": "true",
781
+ children: [
782
+ /* @__PURE__ */ jsx(
783
+ m.circle,
784
+ {
785
+ cx: 10,
786
+ cy: 10,
787
+ r: 9,
788
+ strokeWidth: 2,
789
+ fill: "none",
790
+ animate: { stroke: outerStroke },
791
+ transition: { duration: ringDuration, ease: "easeOut" }
792
+ }
793
+ ),
794
+ /* @__PURE__ */ jsx(
795
+ m.circle,
796
+ {
797
+ cx: 10,
798
+ cy: 10,
799
+ initial: { r: 0 },
800
+ animate: { r: isSelected ? 5 : 0, fill: dotFill },
801
+ transition: {
802
+ r: { duration: dotDuration, ease: dotEase },
803
+ fill: { duration: ringDuration, ease: "easeOut" }
804
+ },
805
+ stroke: "none"
806
+ }
807
+ )
808
+ ]
809
+ }
810
+ );
811
+ });
812
+ var RadioButtonComponent = React4.forwardRef(
813
+ ({
814
+ selected,
815
+ defaultSelected = false,
816
+ onClick,
817
+ disabled: disabledProp = false,
818
+ color,
819
+ error: errorProp = false,
820
+ label,
821
+ value,
822
+ name: nameProp,
823
+ id: idProp,
824
+ className,
825
+ "aria-label": ariaLabel,
826
+ "aria-labelledby": ariaLabelledby,
827
+ "aria-describedby": ariaDescribedby,
828
+ required: requiredProp
829
+ }, ref) => {
830
+ var _a, _b;
831
+ const group = React4.useContext(RadioGroupContext);
832
+ const prefersReduced = (_a = useReducedMotion()) != null ? _a : false;
833
+ const generatedId = React4.useId();
834
+ const inputId = idProp != null ? idProp : label ? `radio-${generatedId}` : void 0;
835
+ const name = (_b = group == null ? void 0 : group.name) != null ? _b : nameProp;
836
+ const disabled = (group == null ? void 0 : group.disabled) || disabledProp;
837
+ const error = (group == null ? void 0 : group.error) || errorProp || color === "error";
838
+ const required = (group == null ? void 0 : group.required) || requiredProp;
839
+ const [internalSelected, setInternalSelected] = React4.useState(defaultSelected);
840
+ const isControlled = selected !== void 0;
841
+ const isSelected = group ? group.selectedValue === value : isControlled ? selected != null ? selected : false : internalSelected;
842
+ const [ripples, setRipples] = React4.useState([]);
843
+ const removeRipple = React4.useCallback(
844
+ (id) => setRipples((prev) => prev.filter((r) => r.id !== id)),
845
+ []
846
+ );
847
+ const onPointerDown = React4.useCallback(
848
+ (e) => {
849
+ if (disabled) return;
850
+ const rect = e.currentTarget.getBoundingClientRect();
851
+ const x = e.clientX - rect.left - 4;
852
+ const y = e.clientY - rect.top - 4;
853
+ const rippleSize = Math.hypot(40, 40) * 2;
854
+ setRipples((prev) => [
855
+ ...prev,
856
+ { id: Date.now(), x, y, size: rippleSize }
857
+ ]);
858
+ },
859
+ [disabled]
860
+ );
861
+ const [isHovered, setIsHovered] = React4.useState(false);
862
+ const onPointerEnter = React4.useCallback(() => {
863
+ if (!disabled) setIsHovered(true);
864
+ }, [disabled]);
865
+ const onPointerLeave = React4.useCallback(() => setIsHovered(false), []);
866
+ const handleChange = React4.useCallback(
867
+ (_e) => {
868
+ if (disabled || onClick === null) return;
869
+ if (group) {
870
+ if (value !== void 0) group.onValueChange(value);
871
+ } else if (!isControlled) {
872
+ setInternalSelected(true);
873
+ onClick == null ? void 0 : onClick();
874
+ } else {
875
+ onClick == null ? void 0 : onClick();
876
+ }
877
+ },
878
+ [disabled, onClick, group, value, isControlled]
879
+ );
880
+ const inputRef = React4.useRef(null);
881
+ const mergedRef = useMergedRef2(ref, inputRef);
882
+ const stateLayerBg = error ? "before:bg-m3-error" : "before:bg-m3-primary";
883
+ const stateLayerClass = cn(
884
+ "before:absolute before:inset-0 before:rounded-full before:pointer-events-none",
885
+ "before:transition-opacity before:duration-150 before:opacity-0",
886
+ "group-hover/radio:before:opacity-[0.08]",
887
+ "group-focus-within/radio:before:opacity-[0.10]",
888
+ "group-active/radio:before:opacity-[0.10]",
889
+ stateLayerBg
890
+ );
891
+ const touchTargetClass = cn(
892
+ "relative inline-flex items-center justify-center outline-none shrink-0",
893
+ "w-12 h-12 group/radio cursor-pointer",
894
+ disabled && "pointer-events-none"
895
+ );
896
+ const stateLayerAndRipple = /* @__PURE__ */ jsx(
897
+ "div",
898
+ {
899
+ className: cn(
900
+ "absolute flex items-center justify-center w-10 h-10 m-auto inset-0 rounded-full overflow-hidden pointer-events-none",
901
+ stateLayerClass
902
+ ),
903
+ "aria-hidden": "true",
904
+ children: /* @__PURE__ */ jsx(
905
+ Ripple,
906
+ {
907
+ ripples,
908
+ onRippleDone: removeRipple,
909
+ disabled
910
+ }
911
+ )
912
+ }
913
+ );
914
+ const hiddenInput = /* @__PURE__ */ jsx(
915
+ "input",
916
+ {
917
+ ref: mergedRef,
918
+ type: "radio",
919
+ id: inputId,
920
+ name,
921
+ value,
922
+ checked: isSelected,
923
+ disabled,
924
+ "aria-disabled": disabled || void 0,
925
+ "aria-label": ariaLabel,
926
+ "aria-labelledby": ariaLabelledby,
927
+ "aria-describedby": ariaDescribedby,
928
+ required,
929
+ onChange: handleChange,
930
+ className: "sr-only"
931
+ }
932
+ );
933
+ const visual = /* @__PURE__ */ jsx(
934
+ RadioVisual,
935
+ {
936
+ isSelected,
937
+ disabled,
938
+ error,
939
+ isHovered,
940
+ prefersReduced
941
+ }
942
+ );
943
+ if (label) {
944
+ return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: /* @__PURE__ */ jsxs(
945
+ "label",
946
+ {
947
+ htmlFor: inputId,
948
+ className: cn(
949
+ "inline-flex items-center gap-2 cursor-pointer select-none",
950
+ disabled && "cursor-not-allowed opacity-[0.38] pointer-events-none",
951
+ className
952
+ ),
953
+ children: [
954
+ /* @__PURE__ */ jsxs(
955
+ "div",
956
+ {
957
+ className: touchTargetClass,
958
+ onPointerDown,
959
+ onPointerEnter,
960
+ onPointerLeave,
961
+ children: [
962
+ stateLayerAndRipple,
963
+ hiddenInput,
964
+ visual
965
+ ]
966
+ }
967
+ ),
968
+ /* @__PURE__ */ jsx("span", { className: "text-sm leading-none text-m3-on-surface", children: label })
969
+ ]
970
+ }
971
+ ) });
972
+ }
973
+ return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: /* @__PURE__ */ jsxs(
974
+ "label",
975
+ {
976
+ htmlFor: inputId,
977
+ className: cn(touchTargetClass, className),
978
+ onPointerDown,
979
+ onPointerEnter,
980
+ onPointerLeave,
981
+ children: [
982
+ stateLayerAndRipple,
983
+ hiddenInput,
984
+ visual
985
+ ]
986
+ }
987
+ ) });
988
+ }
989
+ );
990
+ RadioButtonComponent.displayName = "RadioButton";
991
+ var RadioButton = React4.memo(RadioButtonComponent);
992
+ var RadioGroupComponent = React4.forwardRef(
993
+ ({
994
+ name,
995
+ value: valueProp,
996
+ defaultValue,
997
+ onValueChange,
998
+ disabled = false,
999
+ error = false,
1000
+ label,
1001
+ "aria-labelledby": ariaLabelledby,
1002
+ required = false,
1003
+ orientation = "vertical",
1004
+ children,
1005
+ className
1006
+ }, ref) => {
1007
+ const [internalValue, setInternalValue] = React4.useState(defaultValue);
1008
+ const isControlled = valueProp !== void 0;
1009
+ const selectedValue = isControlled ? valueProp : internalValue;
1010
+ const handleValueChange = React4.useCallback(
1011
+ (val) => {
1012
+ if (!isControlled) setInternalValue(val);
1013
+ onValueChange == null ? void 0 : onValueChange(val);
1014
+ },
1015
+ [isControlled, onValueChange]
1016
+ );
1017
+ const groupRef = React4.useRef(null);
1018
+ const mergedRef = useMergedRef2(ref, groupRef);
1019
+ const onKeyDown = React4.useCallback(
1020
+ (e) => {
1021
+ var _a, _b;
1022
+ if (disabled) return;
1023
+ const isNext = e.key === "ArrowDown" || e.key === "ArrowRight";
1024
+ const isPrev = e.key === "ArrowUp" || e.key === "ArrowLeft";
1025
+ if (!isNext && !isPrev) return;
1026
+ e.preventDefault();
1027
+ const inputs = Array.from(
1028
+ (_b = (_a = groupRef.current) == null ? void 0 : _a.querySelectorAll(
1029
+ 'input[type="radio"]:not(:disabled)'
1030
+ )) != null ? _b : []
1031
+ );
1032
+ if (inputs.length === 0) return;
1033
+ const currentIdx = inputs.indexOf(
1034
+ document.activeElement
1035
+ );
1036
+ const nextIdx = isNext ? currentIdx < inputs.length - 1 ? currentIdx + 1 : 0 : currentIdx > 0 ? currentIdx - 1 : inputs.length - 1;
1037
+ const target = inputs[nextIdx];
1038
+ target.focus();
1039
+ handleValueChange(target.value);
1040
+ },
1041
+ [disabled, handleValueChange]
1042
+ );
1043
+ const contextValue = React4.useMemo(
1044
+ () => ({
1045
+ name,
1046
+ selectedValue,
1047
+ onValueChange: handleValueChange,
1048
+ disabled,
1049
+ error,
1050
+ required
1051
+ }),
1052
+ [name, selectedValue, handleValueChange, disabled, error, required]
1053
+ );
1054
+ return /* @__PURE__ */ jsx(RadioGroupContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
1055
+ "div",
1056
+ {
1057
+ ref: mergedRef,
1058
+ role: "radiogroup",
1059
+ "aria-label": label && !ariaLabelledby ? label : void 0,
1060
+ "aria-labelledby": ariaLabelledby,
1061
+ "aria-disabled": disabled || void 0,
1062
+ "aria-required": required || void 0,
1063
+ className: cn(
1064
+ "flex",
1065
+ orientation === "horizontal" ? "flex-row gap-4" : "flex-col gap-1",
1066
+ className
1067
+ ),
1068
+ onKeyDown,
1069
+ children: [
1070
+ label && !ariaLabelledby && /* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
1071
+ children
1072
+ ]
1073
+ }
1074
+ ) });
1075
+ }
1076
+ );
1077
+ RadioGroupComponent.displayName = "RadioGroup";
1078
+ var RadioGroup = React4.memo(RadioGroupComponent);
1079
+ function coerceValue(value, min, max) {
1080
+ return Math.max(min, Math.min(max, value));
1081
+ }
1082
+ function snapToStep(value, min, step) {
1083
+ if (step <= 0) return value;
1084
+ const steps = Math.round((value - min) / step);
1085
+ return Number((min + steps * step).toPrecision(10));
1086
+ }
1087
+ function valueToPercent(value, min, max) {
1088
+ if (max === min) return 0;
1089
+ return (value - min) / (max - min);
1090
+ }
1091
+ function percentToValue(percent, min, max, step) {
1092
+ const raw = min + percent * (max - min);
1093
+ const snapped = snapToStep(raw, min, step);
1094
+ return coerceValue(snapped, min, max);
1095
+ }
1096
+ function getKeyboardDelta(key, step, min, max) {
1097
+ const range = max - min;
1098
+ const discreteStep = step > 0 ? step : range / 100;
1099
+ switch (key) {
1100
+ case "ArrowRight":
1101
+ case "ArrowUp":
1102
+ return discreteStep;
1103
+ case "ArrowLeft":
1104
+ case "ArrowDown":
1105
+ return -discreteStep;
1106
+ case "PageUp":
1107
+ return step > 0 ? snapToStep(range * 0.1, 0, step) : range * 0.1;
1108
+ case "PageDown":
1109
+ return step > 0 ? -snapToStep(range * 0.1, 0, step) : -(range * 0.1);
1110
+ default:
1111
+ return null;
1112
+ }
1113
+ }
1114
+ function generateTicks(min, max, step) {
1115
+ if (step <= 0) return [];
1116
+ const ticks = [];
1117
+ let current = min;
1118
+ while (current <= max) {
1119
+ ticks.push(Number(current.toPrecision(10)));
1120
+ current += step;
1121
+ }
1122
+ if (ticks[ticks.length - 1] !== max) {
1123
+ ticks.push(max);
1124
+ }
1125
+ return ticks;
1126
+ }
1127
+ function useSliderMath({
1128
+ min,
1129
+ max,
1130
+ step
1131
+ }) {
1132
+ return useMemo(
1133
+ () => ({
1134
+ coerce: (v) => coerceValue(v, min, max),
1135
+ snap: (v) => snapToStep(v, min, step),
1136
+ toPercent: (v) => valueToPercent(v, min, max),
1137
+ fromPercent: (pct) => percentToValue(pct, min, max, step),
1138
+ getKeyDelta: (key) => getKeyboardDelta(key, step, min, max),
1139
+ ticks: generateTicks(min, max, step)
1140
+ }),
1141
+ [min, max, step]
1142
+ );
1143
+ }
1144
+
1145
+ // src/ui/slider/slider.tokens.ts
1146
+ var SliderTokens = {
1147
+ // ── Track sizes (height for horizontal, width for vertical) ────────────────
1148
+ trackSizes: {
1149
+ xs: 16,
1150
+ s: 24,
1151
+ m: 40,
1152
+ l: 56,
1153
+ xl: 96
1154
+ },
1155
+ // ── Track shapes (border radius) ──────────────────────────────────────────
1156
+ trackShapes: {
1157
+ xs: 8,
1158
+ s: 8,
1159
+ m: 12,
1160
+ l: 16,
1161
+ xl: 28
1162
+ },
1163
+ // ── Thumb (handle) ────────────────────────────────────────────────────────
1164
+ /** Thumb height grows with track size */
1165
+ thumbHeights: {
1166
+ xs: 44,
1167
+ s: 44,
1168
+ m: 52,
1169
+ l: 68,
1170
+ xl: 108
1171
+ },
1172
+ /**
1173
+ * Thumb width at rest and on hover.
1174
+ * MD3 Expressive handle pill: 4dp wide.
1175
+ */
1176
+ thumbWidthDefault: 4,
1177
+ /**
1178
+ * Thumb width while pressed/dragging.
1179
+ * "Squeeze" animation: 4dp → 2dp (M3 Expressive behavior).
1180
+ */
1181
+ thumbWidthPressed: 2,
1182
+ // ── Track–thumb gap ───────────────────────────────────────────────────────
1183
+ /**
1184
+ * Transparent gap between the active/inactive track segments and the thumb.
1185
+ * Maps to ActiveHandleLeadingSpace / ActiveHandleTrailingSpace = 6dp.
1186
+ *
1187
+ * This gap is rendered by mathematically subtracting it from the track
1188
+ * segment width — NOT using margin/padding.
1189
+ */
1190
+ thumbGap: 6,
1191
+ // ── Track corner radii ────────────────────────────────────────────────────
1192
+ /**
1193
+ * Corner radius on the INNER ends of each track segment (facing the thumb).
1194
+ * Fixed to 2px according to MD3 Expressive Slider specs (TrackInsideCornerSize).
1195
+ * Outer ends use `trackSize / 2` (pill cap) — computed inline per component.
1196
+ */
1197
+ trackInnerRadius: 2,
1198
+ // ── Stop indicator / Tick ─────────────────────────────────────────────────
1199
+ /** Tick dot size (width and height). = 4dp. */
1200
+ tickSize: 4,
1201
+ // ── Touch target ─────────────────────────────────────────────────────────
1202
+ /**
1203
+ * Minimum touch target for the thumb wrapper.
1204
+ * MD3 requires 48dp minimum touch target for interactive elements.
1205
+ */
1206
+ thumbTouchTarget: 48,
1207
+ // ── Value indicator ───────────────────────────────────────────────────────
1208
+ /** Offset above the thumb center for the value indicator tooltip. */
1209
+ valueIndicatorOffset: 52,
1210
+ // ── Inset icon ────────────────────────────────────────────────────────────
1211
+ /** Standard icon size map for inset icons inside the track. */
1212
+ insetIconSizes: {
1213
+ xs: 0,
1214
+ s: 0,
1215
+ m: 24,
1216
+ l: 24,
1217
+ xl: 32
1218
+ },
1219
+ /**
1220
+ * Padding between icon and track edge (horizontal).
1221
+ * Keeps the icon visually centered within the pill track.
1222
+ */
1223
+ insetIconPadding: 4
1224
+ };
1225
+ var SliderColors = {
1226
+ // ── Track ─────────────────────────────────────────────────────────────────
1227
+ /** Active track color. Maps to ActiveTrackColor token. */
1228
+ activeTrack: "var(--md-sys-color-primary)",
1229
+ /** Inactive track color. Maps to InactiveTrackColor token. */
1230
+ inactiveTrack: "var(--md-sys-color-secondary-container)",
1231
+ /**
1232
+ * Disabled track color.
1233
+ * Uses opacity + on-surface per MD3 disabled state spec.
1234
+ */
1235
+ disabledActiveTrack: "var(--md-sys-color-on-surface)",
1236
+ disabledInactiveTrack: "var(--md-sys-color-on-surface)",
1237
+ // ── Thumb ─────────────────────────────────────────────────────────────────
1238
+ /** Thumb (handle) color. Maps to HandleColor token. */
1239
+ thumb: "var(--md-sys-color-primary)",
1240
+ /** Disabled thumb color. */
1241
+ disabledThumb: "var(--md-sys-color-on-surface)",
1242
+ // ── Value indicator ───────────────────────────────────────────────────────
1243
+ /** Value indicator (tooltip) background. Maps to InverseSurface. */
1244
+ valueIndicatorBg: "var(--md-sys-color-inverse-surface)",
1245
+ /** Value indicator (tooltip) text. Maps to InverseOnSurface. */
1246
+ valueIndicatorText: "var(--md-sys-color-inverse-on-surface)",
1247
+ // ── Ticks ────────────────────────────────────────────────────────────────
1248
+ /**
1249
+ * Tick on the INACTIVE portion of the track.
1250
+ * Maps to TickMarkActiveContainerColor (in spec: primary color stands out).
1251
+ */
1252
+ tickOnInactive: "var(--md-sys-color-primary)",
1253
+ /**
1254
+ * Tick on the ACTIVE portion of the track.
1255
+ * Maps to TickMarkInactiveContainerColor (secondary-container blends in).
1256
+ */
1257
+ tickOnActive: "var(--md-sys-color-secondary-container)",
1258
+ disabledTick: "var(--md-sys-color-on-surface)"
1259
+ };
1260
+ var SLIDER_THUMB_SPRING = {
1261
+ type: "spring",
1262
+ stiffness: 500,
1263
+ damping: 40
1264
+ };
1265
+ var SLIDER_INDICATOR_TRANSITION = {
1266
+ type: "spring",
1267
+ stiffness: 450,
1268
+ damping: 32
1269
+ };
1270
+ var ValueIndicator = React4.memo(function ValueIndicator2({
1271
+ value,
1272
+ visible,
1273
+ orientation,
1274
+ formatValue,
1275
+ prefersReduced,
1276
+ trackSize
1277
+ }) {
1278
+ const label = formatValue ? formatValue(value) : String(Math.round(value));
1279
+ const isHorizontal = orientation === "horizontal";
1280
+ const thumbHeight = SliderTokens.thumbHeights[trackSize];
1281
+ const offsetFromCenter = thumbHeight / 2 + 8;
1282
+ return /* @__PURE__ */ jsx(AnimatePresence, { children: visible && /* @__PURE__ */ jsx(
1283
+ m.div,
1284
+ {
1285
+ role: "tooltip",
1286
+ "aria-hidden": "true",
1287
+ initial: prefersReduced ? false : {
1288
+ scale: 0,
1289
+ opacity: 0,
1290
+ y: isHorizontal ? 6 : 0,
1291
+ x: !isHorizontal ? -6 : 0
1292
+ },
1293
+ animate: { scale: 1, opacity: 1, y: 0, x: 0 },
1294
+ exit: prefersReduced ? {} : {
1295
+ scale: 0,
1296
+ opacity: 0,
1297
+ y: isHorizontal ? 6 : 0,
1298
+ x: !isHorizontal ? -6 : 0
1299
+ },
1300
+ transition: prefersReduced ? { duration: 0 } : SLIDER_INDICATOR_TRANSITION,
1301
+ className: cn(
1302
+ "absolute pointer-events-none select-none",
1303
+ "rounded-full px-2.5 py-1 text-xs font-medium leading-none whitespace-nowrap",
1304
+ // Position: above for horizontal, right for vertical
1305
+ isHorizontal ? "-translate-x-1/2 left-1/2" : "translate-y-1/2 bottom-1/2"
1306
+ ),
1307
+ style: __spreadValues({
1308
+ backgroundColor: SliderColors.valueIndicatorBg,
1309
+ color: SliderColors.valueIndicatorText,
1310
+ transformOrigin: isHorizontal ? "bottom center" : "center left"
1311
+ }, isHorizontal ? { bottom: `calc(50% + ${offsetFromCenter}px)` } : { left: `calc(50% + ${offsetFromCenter}px)` }),
1312
+ children: label
1313
+ },
1314
+ "value-indicator"
1315
+ ) });
1316
+ });
1317
+ var SliderThumb = React4.memo(function SliderThumb2({
1318
+ value,
1319
+ percent,
1320
+ min,
1321
+ max,
1322
+ step,
1323
+ disabled,
1324
+ orientation,
1325
+ showValueIndicator,
1326
+ trackRef,
1327
+ onValueChange,
1328
+ onValueChangeEnd,
1329
+ formatValue,
1330
+ thumbId,
1331
+ trackSize,
1332
+ variant,
1333
+ zIndex = 1,
1334
+ "aria-label": ariaLabel,
1335
+ "aria-labelledby": ariaLabelledBy
1336
+ }) {
1337
+ var _a;
1338
+ const isHorizontal = orientation === "horizontal";
1339
+ const insetLimit = SliderTokens.thumbGap + SliderTokens.thumbWidthDefault / 2;
1340
+ const trackInset = Math.min(
1341
+ SliderTokens.trackSizes[trackSize] / 2,
1342
+ insetLimit
1343
+ );
1344
+ const posTarget = isHorizontal ? "left" : "bottom";
1345
+ const motionStyle = {
1346
+ [posTarget]: `calc(${trackInset}px + ${percent} * (100% - ${trackInset * 2}px))`
1347
+ };
1348
+ const prefersReduced = (_a = useReducedMotion()) != null ? _a : false;
1349
+ const [isDragging, setIsDragging] = React4.useState(false);
1350
+ const [isHovered, setIsHovered] = React4.useState(false);
1351
+ const [isFocused, setIsFocused] = React4.useState(false);
1352
+ const pointerIdRef = React4.useRef(null);
1353
+ const thumbRef = React4.useRef(null);
1354
+ const showIndicator = showValueIndicator && (isDragging || isHovered || isFocused);
1355
+ const positionStyle = isHorizontal ? __spreadProps(__spreadValues({
1356
+ position: "absolute"
1357
+ }, motionStyle), {
1358
+ top: "50%",
1359
+ transform: "translate(-50%, -50%)",
1360
+ zIndex
1361
+ }) : __spreadProps(__spreadValues({
1362
+ position: "absolute"
1363
+ }, motionStyle), {
1364
+ left: "50%",
1365
+ transform: "translate(-50%, 50%)",
1366
+ zIndex
1367
+ });
1368
+ const getDeltaFromPointer = React4.useCallback(
1369
+ (e) => {
1370
+ const trackEl = trackRef.current;
1371
+ if (!trackEl) return percent;
1372
+ const rect = trackEl.getBoundingClientRect();
1373
+ const space = isHorizontal ? rect.width : rect.height;
1374
+ const insetLimit2 = SliderTokens.thumbGap + SliderTokens.thumbWidthDefault / 2;
1375
+ const trackInset2 = Math.min(
1376
+ SliderTokens.trackSizes[trackSize] / 2,
1377
+ insetLimit2
1378
+ );
1379
+ const safeSpace = space - trackInset2 * 2;
1380
+ if (safeSpace <= 0) return percent;
1381
+ let rawPercent;
1382
+ if (isHorizontal) {
1383
+ rawPercent = (e.clientX - rect.left - trackInset2) / safeSpace;
1384
+ } else {
1385
+ rawPercent = (rect.bottom - e.clientY - trackInset2) / safeSpace;
1386
+ }
1387
+ const clamped = Math.max(0, Math.min(1, rawPercent));
1388
+ const range = max - min;
1389
+ const rawValue = min + clamped * range;
1390
+ const snapped = step > 0 ? Math.round((rawValue - min) / step) * step + min : rawValue;
1391
+ return Math.max(min, Math.min(max, snapped));
1392
+ },
1393
+ [isHorizontal, max, min, percent, step, trackRef, trackSize]
1394
+ );
1395
+ const handlePointerDown = React4.useCallback(
1396
+ (e) => {
1397
+ if (disabled) return;
1398
+ e.preventDefault();
1399
+ e.stopPropagation();
1400
+ e.currentTarget.setPointerCapture(e.pointerId);
1401
+ pointerIdRef.current = e.pointerId;
1402
+ setIsDragging(true);
1403
+ },
1404
+ [disabled]
1405
+ );
1406
+ const handlePointerMove = React4.useCallback(
1407
+ (e) => {
1408
+ if (!isDragging || e.pointerId !== pointerIdRef.current) return;
1409
+ const newValue = getDeltaFromPointer(e.nativeEvent);
1410
+ onValueChange(newValue);
1411
+ },
1412
+ [isDragging, getDeltaFromPointer, onValueChange]
1413
+ );
1414
+ const handlePointerUp = React4.useCallback(
1415
+ (e) => {
1416
+ if (e.pointerId !== pointerIdRef.current) return;
1417
+ e.currentTarget.releasePointerCapture(e.pointerId);
1418
+ pointerIdRef.current = null;
1419
+ setIsDragging(false);
1420
+ onValueChangeEnd == null ? void 0 : onValueChangeEnd(value);
1421
+ },
1422
+ [onValueChangeEnd, value]
1423
+ );
1424
+ const handleKeyDown = React4.useCallback(
1425
+ (e) => {
1426
+ if (disabled) return;
1427
+ if (e.key === "Home") {
1428
+ e.preventDefault();
1429
+ onValueChange(min);
1430
+ onValueChangeEnd == null ? void 0 : onValueChangeEnd(min);
1431
+ return;
1432
+ }
1433
+ if (e.key === "End") {
1434
+ e.preventDefault();
1435
+ onValueChange(max);
1436
+ onValueChangeEnd == null ? void 0 : onValueChangeEnd(max);
1437
+ return;
1438
+ }
1439
+ const delta = getKeyboardDelta(e.key, step, min, max);
1440
+ if (delta === null) return;
1441
+ e.preventDefault();
1442
+ const newValue = Math.max(min, Math.min(max, value + delta));
1443
+ onValueChange(newValue);
1444
+ onValueChangeEnd == null ? void 0 : onValueChangeEnd(newValue);
1445
+ },
1446
+ [disabled, max, min, onValueChange, onValueChangeEnd, step, value]
1447
+ );
1448
+ const thumbColor = disabled ? SliderColors.disabledThumb : `var(--md-sys-color-${variant})`;
1449
+ const squeezeSize = isDragging ? SliderTokens.thumbWidthPressed : SliderTokens.thumbWidthDefault;
1450
+ return /* @__PURE__ */ jsxs(
1451
+ "div",
1452
+ {
1453
+ style: __spreadProps(__spreadValues({}, positionStyle), {
1454
+ width: SliderTokens.thumbTouchTarget,
1455
+ height: SliderTokens.thumbTouchTarget,
1456
+ display: "flex",
1457
+ alignItems: "center",
1458
+ justifyContent: "center",
1459
+ touchAction: "none"
1460
+ }),
1461
+ className: cn(
1462
+ "cursor-grab",
1463
+ isDragging && "cursor-grabbing",
1464
+ disabled && "cursor-not-allowed"
1465
+ ),
1466
+ onPointerDown: handlePointerDown,
1467
+ onPointerMove: handlePointerMove,
1468
+ onPointerUp: handlePointerUp,
1469
+ onPointerCancel: handlePointerUp,
1470
+ children: [
1471
+ /* @__PURE__ */ jsx(
1472
+ ValueIndicator,
1473
+ {
1474
+ value,
1475
+ visible: showIndicator,
1476
+ orientation,
1477
+ formatValue,
1478
+ prefersReduced,
1479
+ trackSize
1480
+ }
1481
+ ),
1482
+ /* @__PURE__ */ jsx(
1483
+ m.div,
1484
+ {
1485
+ ref: thumbRef,
1486
+ id: thumbId,
1487
+ role: "slider",
1488
+ "aria-valuemin": min,
1489
+ "aria-valuemax": max,
1490
+ "aria-valuenow": value,
1491
+ "aria-orientation": orientation,
1492
+ "aria-disabled": disabled || void 0,
1493
+ "aria-label": ariaLabel,
1494
+ "aria-labelledby": ariaLabelledBy,
1495
+ tabIndex: disabled ? -1 : 0,
1496
+ onKeyDown: handleKeyDown,
1497
+ onFocus: () => setIsFocused(true),
1498
+ onBlur: () => setIsFocused(false),
1499
+ onMouseEnter: () => !disabled && setIsHovered(true),
1500
+ onMouseLeave: () => setIsHovered(false),
1501
+ animate: __spreadProps(__spreadValues({}, isHorizontal ? { width: squeezeSize } : { height: squeezeSize }), {
1502
+ backgroundColor: thumbColor,
1503
+ opacity: disabled ? 0.38 : 1
1504
+ }),
1505
+ transition: prefersReduced ? { duration: 0 } : SLIDER_THUMB_SPRING,
1506
+ className: cn(
1507
+ "relative shrink-0 rounded-full select-none outline-none",
1508
+ "focus-visible:outline-2 focus-visible:outline-offset-2",
1509
+ "focus-visible:outline-(--md-sys-color-secondary)"
1510
+ ),
1511
+ style: __spreadProps(__spreadValues({}, isHorizontal ? {
1512
+ height: SliderTokens.thumbHeights[trackSize],
1513
+ width: SliderTokens.thumbWidthDefault,
1514
+ willChange: "width"
1515
+ } : {
1516
+ width: SliderTokens.thumbHeights[trackSize],
1517
+ height: SliderTokens.thumbWidthDefault,
1518
+ willChange: "height"
1519
+ }), {
1520
+ backgroundColor: thumbColor
1521
+ })
1522
+ }
1523
+ )
1524
+ ]
1525
+ }
1526
+ );
1527
+ });
1528
+ var RangeTrack = React4.memo(function RangeTrack2({
1529
+ startPercent,
1530
+ endPercent,
1531
+ trackSize,
1532
+ orientation,
1533
+ variant,
1534
+ disabled,
1535
+ ticks,
1536
+ min,
1537
+ max,
1538
+ trackShape = "md3",
1539
+ trackRef,
1540
+ onTrackPointerDown
1541
+ }) {
1542
+ const isHorizontal = orientation === "horizontal";
1543
+ const size = SliderTokens.trackSizes[trackSize];
1544
+ const thumbHeight = SliderTokens.thumbHeights[trackSize];
1545
+ const { thumbGap, thumbWidthDefault, tickSize } = SliderTokens;
1546
+ const thumbHalfWidth = thumbWidthDefault / 2;
1547
+ const activeTrack = disabled ? SliderColors.disabledActiveTrack : `var(--md-sys-color-${variant})`;
1548
+ const inactiveTrack = disabled ? SliderColors.disabledInactiveTrack : `var(--md-sys-color-${variant}-container)`;
1549
+ const innerR = SliderTokens.trackInnerRadius;
1550
+ let outerR = size / 2;
1551
+ if (trackShape === "md3") {
1552
+ outerR = Math.min(SliderTokens.trackShapes[trackSize], size / 2);
1553
+ } else if (typeof trackShape === "number") {
1554
+ outerR = Math.min(trackShape, size / 2);
1555
+ }
1556
+ if (isHorizontal) {
1557
+ const insetLimit2 = thumbGap + thumbWidthDefault / 2;
1558
+ const trackInset2 = Math.min(size / 2, insetLimit2);
1559
+ const cxStart = `calc(${trackInset2}px + ${startPercent} * (100% - ${trackInset2 * 2}px))`;
1560
+ const cxEnd = `calc(${trackInset2}px + ${endPercent} * (100% - ${trackInset2 * 2}px))`;
1561
+ const gapWithThumbStr2 = `${thumbGap + thumbHalfWidth}px`;
1562
+ const leadingWidth = `max(0px, calc(${cxStart} - ${gapWithThumbStr2}))`;
1563
+ const activeLeft = `calc(${cxStart} + ${gapWithThumbStr2})`;
1564
+ const activeWidth = `max(0px, calc(${cxEnd} - ${cxStart} - ${gapWithThumbStr2} * 2))`;
1565
+ const trailingLeft = `calc(${cxEnd} + ${gapWithThumbStr2})`;
1566
+ const trailingWidth = `max(0px, calc(100% - (${cxEnd} + ${gapWithThumbStr2})))`;
1567
+ return /* @__PURE__ */ jsxs(
1568
+ "div",
1569
+ {
1570
+ ref: trackRef,
1571
+ className: cn(
1572
+ "relative w-full",
1573
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
1574
+ ),
1575
+ style: { height: thumbHeight },
1576
+ onPointerDown: onTrackPointerDown,
1577
+ "aria-hidden": "true",
1578
+ children: [
1579
+ /* @__PURE__ */ jsx(
1580
+ "div",
1581
+ {
1582
+ "aria-hidden": "true",
1583
+ style: {
1584
+ position: "absolute",
1585
+ left: 0,
1586
+ top: "50%",
1587
+ transform: "translateY(-50%)",
1588
+ width: leadingWidth,
1589
+ height: size,
1590
+ backgroundColor: inactiveTrack,
1591
+ opacity: disabled ? 0.38 : 1,
1592
+ borderTopLeftRadius: outerR,
1593
+ borderBottomLeftRadius: outerR,
1594
+ borderTopRightRadius: innerR,
1595
+ borderBottomRightRadius: innerR
1596
+ }
1597
+ }
1598
+ ),
1599
+ /* @__PURE__ */ jsx(
1600
+ "div",
1601
+ {
1602
+ "aria-hidden": "true",
1603
+ style: {
1604
+ position: "absolute",
1605
+ left: activeLeft,
1606
+ top: "50%",
1607
+ transform: "translateY(-50%)",
1608
+ width: activeWidth,
1609
+ height: size,
1610
+ backgroundColor: activeTrack,
1611
+ opacity: disabled ? 0.38 : 1,
1612
+ borderTopLeftRadius: startPercent <= 0 ? outerR : innerR,
1613
+ borderBottomLeftRadius: startPercent <= 0 ? outerR : innerR,
1614
+ borderTopRightRadius: endPercent >= 1 ? outerR : innerR,
1615
+ borderBottomRightRadius: endPercent >= 1 ? outerR : innerR
1616
+ }
1617
+ }
1618
+ ),
1619
+ /* @__PURE__ */ jsx(
1620
+ "div",
1621
+ {
1622
+ "aria-hidden": "true",
1623
+ style: {
1624
+ position: "absolute",
1625
+ left: trailingLeft,
1626
+ top: "50%",
1627
+ transform: "translateY(-50%)",
1628
+ width: trailingWidth,
1629
+ height: size,
1630
+ backgroundColor: inactiveTrack,
1631
+ opacity: disabled ? 0.38 : 1,
1632
+ borderTopLeftRadius: innerR,
1633
+ borderBottomLeftRadius: innerR,
1634
+ borderTopRightRadius: outerR,
1635
+ borderBottomRightRadius: outerR
1636
+ }
1637
+ }
1638
+ ),
1639
+ ticks.map((tick) => {
1640
+ const tickPct = (tick - min) / (max - min);
1641
+ const isActive = tickPct > startPercent && tickPct < endPercent;
1642
+ return /* @__PURE__ */ jsx(
1643
+ "div",
1644
+ {
1645
+ "aria-hidden": "true",
1646
+ style: {
1647
+ position: "absolute",
1648
+ width: tickSize,
1649
+ height: tickSize,
1650
+ borderRadius: "50%",
1651
+ backgroundColor: disabled ? SliderColors.disabledTick : isActive ? `var(--md-sys-color-${variant}-container)` : `var(--md-sys-color-${variant})`,
1652
+ opacity: disabled ? 0.38 : 1,
1653
+ left: `calc(${trackInset2}px + ${tickPct} * (100% - ${trackInset2 * 2}px) - ${tickSize / 2}px)`,
1654
+ top: "50%",
1655
+ transform: "translateY(-50%)"
1656
+ }
1657
+ },
1658
+ tick
1659
+ );
1660
+ })
1661
+ ]
1662
+ }
1663
+ );
1664
+ }
1665
+ const insetLimit = thumbGap + thumbWidthDefault / 2;
1666
+ const trackInset = Math.min(size / 2, insetLimit);
1667
+ const cyStart = `calc(${trackInset}px + ${startPercent} * (100% - ${trackInset * 2}px))`;
1668
+ const cyEnd = `calc(${trackInset}px + ${endPercent} * (100% - ${trackInset * 2}px))`;
1669
+ const gapWithThumbStr = `${thumbGap + thumbHalfWidth}px`;
1670
+ const leadingHeight = `max(0px, calc(${cyStart} - ${gapWithThumbStr}))`;
1671
+ const activeBottom = `calc(${cyStart} + ${gapWithThumbStr})`;
1672
+ const activeHeight = `max(0px, calc(${cyEnd} - ${cyStart} - ${gapWithThumbStr} * 2))`;
1673
+ const trailingBottom = `calc(${cyEnd} + ${gapWithThumbStr})`;
1674
+ const trailingHeight = `max(0px, calc(100% - (${cyEnd} + ${gapWithThumbStr})))`;
1675
+ return /* @__PURE__ */ jsxs(
1676
+ "div",
1677
+ {
1678
+ ref: trackRef,
1679
+ className: cn(
1680
+ "relative h-full",
1681
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
1682
+ ),
1683
+ style: { width: thumbHeight },
1684
+ onPointerDown: onTrackPointerDown,
1685
+ "aria-hidden": "true",
1686
+ children: [
1687
+ /* @__PURE__ */ jsx(
1688
+ "div",
1689
+ {
1690
+ "aria-hidden": "true",
1691
+ style: {
1692
+ position: "absolute",
1693
+ bottom: 0,
1694
+ left: "50%",
1695
+ transform: "translateX(-50%)",
1696
+ height: leadingHeight,
1697
+ width: size,
1698
+ backgroundColor: inactiveTrack,
1699
+ opacity: disabled ? 0.38 : 1,
1700
+ borderBottomLeftRadius: outerR,
1701
+ borderBottomRightRadius: outerR,
1702
+ borderTopLeftRadius: innerR,
1703
+ borderTopRightRadius: innerR
1704
+ }
1705
+ }
1706
+ ),
1707
+ /* @__PURE__ */ jsx(
1708
+ "div",
1709
+ {
1710
+ "aria-hidden": "true",
1711
+ style: {
1712
+ position: "absolute",
1713
+ bottom: activeBottom,
1714
+ left: "50%",
1715
+ transform: "translateX(-50%)",
1716
+ height: activeHeight,
1717
+ width: size,
1718
+ backgroundColor: activeTrack,
1719
+ opacity: disabled ? 0.38 : 1,
1720
+ borderBottomLeftRadius: startPercent <= 0 ? outerR : innerR,
1721
+ borderBottomRightRadius: startPercent <= 0 ? outerR : innerR,
1722
+ borderTopLeftRadius: endPercent >= 1 ? outerR : innerR,
1723
+ borderTopRightRadius: endPercent >= 1 ? outerR : innerR
1724
+ }
1725
+ }
1726
+ ),
1727
+ /* @__PURE__ */ jsx(
1728
+ "div",
1729
+ {
1730
+ "aria-hidden": "true",
1731
+ style: {
1732
+ position: "absolute",
1733
+ bottom: trailingBottom,
1734
+ left: "50%",
1735
+ transform: "translateX(-50%)",
1736
+ height: trailingHeight,
1737
+ width: size,
1738
+ backgroundColor: inactiveTrack,
1739
+ opacity: disabled ? 0.38 : 1,
1740
+ borderTopLeftRadius: outerR,
1741
+ borderTopRightRadius: outerR,
1742
+ borderBottomLeftRadius: innerR,
1743
+ borderBottomRightRadius: innerR
1744
+ }
1745
+ }
1746
+ ),
1747
+ ticks.map((tick) => {
1748
+ const tickPct = (tick - min) / (max - min);
1749
+ const isActive = tickPct > startPercent && tickPct < endPercent;
1750
+ return /* @__PURE__ */ jsx(
1751
+ "div",
1752
+ {
1753
+ "aria-hidden": "true",
1754
+ style: {
1755
+ position: "absolute",
1756
+ width: tickSize,
1757
+ height: tickSize,
1758
+ borderRadius: "50%",
1759
+ backgroundColor: disabled ? SliderColors.disabledTick : isActive ? `var(--md-sys-color-${variant}-container)` : `var(--md-sys-color-${variant})`,
1760
+ opacity: disabled ? 0.38 : 1,
1761
+ bottom: `calc(${trackInset}px + ${tickPct} * (100% - ${trackInset * 2}px) - ${tickSize / 2}px)`,
1762
+ left: "50%",
1763
+ transform: "translateX(-50%)"
1764
+ }
1765
+ },
1766
+ tick
1767
+ );
1768
+ })
1769
+ ]
1770
+ }
1771
+ );
1772
+ });
1773
+ var RangeSliderComponent = React4.forwardRef(
1774
+ ({
1775
+ value: controlledValue,
1776
+ defaultValue,
1777
+ onValueChange,
1778
+ onValueChangeEnd,
1779
+ min = 0,
1780
+ max = 100,
1781
+ step = 0,
1782
+ orientation = "horizontal",
1783
+ trackSize = "m",
1784
+ trackShape = "md3",
1785
+ variant = "primary",
1786
+ disabled = false,
1787
+ showValueIndicator = false,
1788
+ showTicks = false,
1789
+ formatValue,
1790
+ className,
1791
+ "aria-label": ariaLabel
1792
+ }, ref) => {
1793
+ var _a, _b;
1794
+ const isHorizontal = orientation === "horizontal";
1795
+ const defaultStart = (_a = defaultValue == null ? void 0 : defaultValue[0]) != null ? _a : min;
1796
+ const defaultEnd = (_b = defaultValue == null ? void 0 : defaultValue[1]) != null ? _b : max;
1797
+ const [internalValue, setInternalValue] = React4.useState([
1798
+ defaultStart,
1799
+ defaultEnd
1800
+ ]);
1801
+ const resolvedValue = controlledValue !== void 0 ? controlledValue : internalValue;
1802
+ const { coerce, snap, toPercent, ticks } = useSliderMath({
1803
+ min,
1804
+ max,
1805
+ step
1806
+ });
1807
+ const startValue = snap(coerce(resolvedValue[0]));
1808
+ const endValue = snap(coerce(resolvedValue[1]));
1809
+ const startPercent = toPercent(startValue);
1810
+ const endPercent = toPercent(endValue);
1811
+ const [topThumb, setTopThumb] = React4.useState("end");
1812
+ const trackRef = React4.useRef(null);
1813
+ const minGap = step > 0 ? step : (max - min) / 1e3;
1814
+ const handleStartChange = React4.useCallback(
1815
+ (newStart) => {
1816
+ setTopThumb("start");
1817
+ const clamped = Math.min(newStart, endValue - minGap);
1818
+ const final = snap(coerce(clamped));
1819
+ if (controlledValue === void 0) {
1820
+ setInternalValue([final, endValue]);
1821
+ }
1822
+ onValueChange == null ? void 0 : onValueChange([final, endValue]);
1823
+ },
1824
+ [controlledValue, coerce, endValue, minGap, onValueChange, snap]
1825
+ );
1826
+ const handleEndChange = React4.useCallback(
1827
+ (newEnd) => {
1828
+ setTopThumb("end");
1829
+ const clamped = Math.max(newEnd, startValue + minGap);
1830
+ const final = snap(coerce(clamped));
1831
+ if (controlledValue === void 0) {
1832
+ setInternalValue([startValue, final]);
1833
+ }
1834
+ onValueChange == null ? void 0 : onValueChange([startValue, final]);
1835
+ },
1836
+ [controlledValue, coerce, minGap, onValueChange, snap, startValue]
1837
+ );
1838
+ const handleStartChangeEnd = React4.useCallback(
1839
+ (v) => onValueChangeEnd == null ? void 0 : onValueChangeEnd([v, endValue]),
1840
+ [endValue, onValueChangeEnd]
1841
+ );
1842
+ const handleEndChangeEnd = React4.useCallback(
1843
+ (v) => onValueChangeEnd == null ? void 0 : onValueChangeEnd([startValue, v]),
1844
+ [onValueChangeEnd, startValue]
1845
+ );
1846
+ const handleTrackPointerDown = React4.useCallback(
1847
+ (e) => {
1848
+ if (disabled) return;
1849
+ const trackEl = trackRef.current;
1850
+ if (!trackEl) return;
1851
+ const rect = trackEl.getBoundingClientRect();
1852
+ let clickPercent;
1853
+ const insetLimit = SliderTokens.thumbGap + SliderTokens.thumbWidthDefault / 2;
1854
+ const trackInset = Math.min(
1855
+ SliderTokens.trackSizes[trackSize] / 2,
1856
+ insetLimit
1857
+ );
1858
+ if (isHorizontal) {
1859
+ const x = e.clientX - rect.left;
1860
+ clickPercent = (x - trackInset) / (rect.width - trackInset * 2);
1861
+ } else {
1862
+ const y = rect.bottom - e.clientY;
1863
+ clickPercent = (y - trackInset) / (rect.height - trackInset * 2);
1864
+ }
1865
+ const clamped = Math.max(0, Math.min(1, clickPercent));
1866
+ const rawValue = min + clamped * (max - min);
1867
+ const clickValue = snap(coerce(rawValue));
1868
+ const distStart = Math.abs(clickValue - startValue);
1869
+ const distEnd = Math.abs(clickValue - endValue);
1870
+ if (distStart <= distEnd) {
1871
+ handleStartChange(clickValue);
1872
+ } else {
1873
+ handleEndChange(clickValue);
1874
+ }
1875
+ },
1876
+ [
1877
+ coerce,
1878
+ disabled,
1879
+ endValue,
1880
+ handleEndChange,
1881
+ handleStartChange,
1882
+ isHorizontal,
1883
+ max,
1884
+ min,
1885
+ snap,
1886
+ startValue,
1887
+ trackSize
1888
+ ]
1889
+ );
1890
+ const id = React4.useId();
1891
+ return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: /* @__PURE__ */ jsxs(
1892
+ "div",
1893
+ {
1894
+ ref,
1895
+ className: cn(
1896
+ "relative flex items-center",
1897
+ isHorizontal ? "w-full flex-row" : "h-full flex-col",
1898
+ disabled && "pointer-events-none",
1899
+ className
1900
+ ),
1901
+ style: isHorizontal ? { minWidth: 0 } : { minHeight: 0 },
1902
+ children: [
1903
+ /* @__PURE__ */ jsx(
1904
+ RangeTrack,
1905
+ {
1906
+ startPercent,
1907
+ endPercent,
1908
+ trackSize,
1909
+ trackShape,
1910
+ orientation,
1911
+ variant,
1912
+ disabled,
1913
+ ticks: showTicks ? ticks : [],
1914
+ min,
1915
+ max,
1916
+ trackRef,
1917
+ onTrackPointerDown: handleTrackPointerDown
1918
+ }
1919
+ ),
1920
+ /* @__PURE__ */ jsx(
1921
+ SliderThumb,
1922
+ {
1923
+ value: startValue,
1924
+ percent: startPercent,
1925
+ min,
1926
+ max,
1927
+ step,
1928
+ disabled,
1929
+ orientation,
1930
+ trackSize,
1931
+ variant,
1932
+ showValueIndicator,
1933
+ trackRef,
1934
+ onValueChange: handleStartChange,
1935
+ onValueChangeEnd: handleStartChangeEnd,
1936
+ formatValue,
1937
+ thumbId: `${id}-start`,
1938
+ zIndex: topThumb === "start" ? 2 : 1,
1939
+ "aria-label": ariaLabel ? `${ariaLabel} start` : "Range start"
1940
+ }
1941
+ ),
1942
+ /* @__PURE__ */ jsx(
1943
+ SliderThumb,
1944
+ {
1945
+ value: endValue,
1946
+ percent: endPercent,
1947
+ min,
1948
+ max,
1949
+ step,
1950
+ disabled,
1951
+ orientation,
1952
+ trackSize,
1953
+ variant,
1954
+ showValueIndicator,
1955
+ trackRef,
1956
+ onValueChange: handleEndChange,
1957
+ onValueChangeEnd: handleEndChangeEnd,
1958
+ formatValue,
1959
+ thumbId: `${id}-end`,
1960
+ zIndex: topThumb === "end" ? 2 : 1,
1961
+ "aria-label": ariaLabel ? `${ariaLabel} end` : "Range end"
1962
+ }
1963
+ )
1964
+ ]
1965
+ }
1966
+ ) });
1967
+ }
1968
+ );
1969
+ RangeSliderComponent.displayName = "RangeSlider";
1970
+ var RangeSlider = React4.memo(RangeSliderComponent);
1971
+ function getHorizontalRadius(isLeading, innerR, outerR) {
1972
+ if (isLeading) {
1973
+ return {
1974
+ borderTopLeftRadius: outerR,
1975
+ borderBottomLeftRadius: outerR,
1976
+ borderTopRightRadius: innerR,
1977
+ borderBottomRightRadius: innerR
1978
+ };
1979
+ }
1980
+ return {
1981
+ borderTopLeftRadius: innerR,
1982
+ borderBottomLeftRadius: innerR,
1983
+ borderTopRightRadius: outerR,
1984
+ borderBottomRightRadius: outerR
1985
+ };
1986
+ }
1987
+ function getVerticalRadius(isLeading, innerR, outerR) {
1988
+ if (isLeading) {
1989
+ return {
1990
+ borderBottomLeftRadius: outerR,
1991
+ borderBottomRightRadius: outerR,
1992
+ borderTopLeftRadius: innerR,
1993
+ borderTopRightRadius: innerR
1994
+ };
1995
+ }
1996
+ return {
1997
+ borderTopLeftRadius: outerR,
1998
+ borderTopRightRadius: outerR,
1999
+ borderBottomLeftRadius: innerR,
2000
+ borderBottomRightRadius: innerR
2001
+ };
2002
+ }
2003
+ var allInnerRadius = (innerR) => ({
2004
+ borderTopLeftRadius: innerR,
2005
+ borderBottomLeftRadius: innerR,
2006
+ borderTopRightRadius: innerR,
2007
+ borderBottomRightRadius: innerR
2008
+ });
2009
+ var InsetIcon = React4.memo(function InsetIcon2({
2010
+ icon,
2011
+ isOnActiveSegment,
2012
+ position,
2013
+ orientation,
2014
+ trackSize,
2015
+ trackSizeToken,
2016
+ disabled,
2017
+ variant,
2018
+ prefersReduced
2019
+ }) {
2020
+ const iconSize = Math.min(
2021
+ SliderTokens.insetIconSizes[trackSizeToken],
2022
+ Math.max(4, trackSize - SliderTokens.insetIconPadding * 2)
2023
+ );
2024
+ const activeColor = `var(--md-sys-color-on-${variant})`;
2025
+ const inactiveColor = `var(--md-sys-color-${variant})`;
2026
+ const isHorizontal = orientation === "horizontal";
2027
+ const fastFade = prefersReduced ? { duration: 0 } : { duration: 0.12 };
2028
+ return /* @__PURE__ */ jsx(
2029
+ m.div,
2030
+ {
2031
+ "aria-hidden": "true",
2032
+ className: "[&_svg]:w-full [&_svg]:h-full",
2033
+ initial: __spreadValues({
2034
+ opacity: 0
2035
+ }, isHorizontal ? { left: position } : { bottom: position }),
2036
+ animate: {
2037
+ [isHorizontal ? "left" : "bottom"]: position,
2038
+ opacity: disabled ? 0.38 : 1,
2039
+ color: isOnActiveSegment ? activeColor : inactiveColor
2040
+ },
2041
+ exit: { opacity: 0, transition: fastFade },
2042
+ transition: prefersReduced ? { duration: 0 } : {
2043
+ left: { type: "spring", stiffness: 500, damping: 40 },
2044
+ bottom: { type: "spring", stiffness: 500, damping: 40 },
2045
+ opacity: fastFade,
2046
+ color: fastFade
2047
+ },
2048
+ style: __spreadValues({
2049
+ position: "absolute",
2050
+ width: iconSize,
2051
+ height: iconSize,
2052
+ display: "flex",
2053
+ alignItems: "center",
2054
+ justifyContent: "center",
2055
+ pointerEvents: "none",
2056
+ zIndex: 1,
2057
+ willChange: isHorizontal ? "left" : "bottom"
2058
+ }, isHorizontal ? { top: "50%", transform: "translateY(-50%)" } : { left: "50%", transform: "translateX(-50%)" }),
2059
+ children: icon
2060
+ }
2061
+ );
2062
+ });
2063
+ function Ticks({
2064
+ ticks,
2065
+ min,
2066
+ max,
2067
+ percent,
2068
+ orientation,
2069
+ variant,
2070
+ isCentered,
2071
+ disabled,
2072
+ trackInset
2073
+ }) {
2074
+ if (ticks.length === 0) return null;
2075
+ const { thumbGap, thumbWidthDefault, tickSize } = SliderTokens;
2076
+ return /* @__PURE__ */ jsx(Fragment, { children: ticks.map((tick) => {
2077
+ const tickPercent = (tick - min) / (max - min);
2078
+ const isOnActive = isCentered ? percent >= 0.5 ? tickPercent >= 0.5 && tickPercent <= percent : tickPercent <= 0.5 && tickPercent >= percent : tickPercent <= percent;
2079
+ const thumbStart = percent - (thumbGap + thumbWidthDefault / 2) / 100;
2080
+ const thumbEnd = percent + (thumbGap + thumbWidthDefault / 2) / 100;
2081
+ if (tickPercent > thumbStart && tickPercent < thumbEnd) return null;
2082
+ const color = disabled ? SliderColors.disabledTick : isOnActive ? `var(--md-sys-color-${variant}-container)` : `var(--md-sys-color-${variant})`;
2083
+ const style = __spreadValues({
2084
+ position: "absolute",
2085
+ width: tickSize,
2086
+ height: tickSize,
2087
+ borderRadius: "50%",
2088
+ backgroundColor: color,
2089
+ opacity: disabled ? 0.38 : 1
2090
+ }, orientation === "horizontal" ? {
2091
+ left: `calc(${trackInset}px + ${tickPercent} * (100% - ${trackInset * 2}px) - ${tickSize / 2}px)`,
2092
+ top: "50%",
2093
+ transform: "translateY(-50%)"
2094
+ } : {
2095
+ // Vertical: bottom=0%, top=100% (inverted Y-axis)
2096
+ bottom: `calc(${trackInset}px + ${tickPercent} * (100% - ${trackInset * 2}px) - ${tickSize / 2}px)`,
2097
+ left: "50%",
2098
+ transform: "translateX(-50%)"
2099
+ });
2100
+ return /* @__PURE__ */ jsx("div", { style, "aria-hidden": "true" }, tick);
2101
+ }) });
2102
+ }
2103
+ var SliderTrack = React4.memo(function SliderTrack2({
2104
+ percent,
2105
+ trackSize,
2106
+ orientation,
2107
+ variant,
2108
+ isCentered,
2109
+ min,
2110
+ max,
2111
+ disabled,
2112
+ trackRef,
2113
+ onTrackPointerDown,
2114
+ ticks = [],
2115
+ insetIcon,
2116
+ insetIconAtMin,
2117
+ insetIconTrailing,
2118
+ insetIconAtMax,
2119
+ value,
2120
+ trackShape = "md3"
2121
+ }) {
2122
+ var _a;
2123
+ const isHorizontal = orientation === "horizontal";
2124
+ const size = SliderTokens.trackSizes[trackSize];
2125
+ const thumbHeight = SliderTokens.thumbHeights[trackSize];
2126
+ const { thumbGap, thumbWidthDefault, trackInnerRadius } = SliderTokens;
2127
+ const innerR = trackInnerRadius;
2128
+ let outerR = size / 2;
2129
+ if (trackShape === "md3") {
2130
+ outerR = Math.min(SliderTokens.trackShapes[trackSize], size / 2);
2131
+ } else if (typeof trackShape === "number") {
2132
+ outerR = Math.min(trackShape, size / 2);
2133
+ }
2134
+ const thumbHalfWidth = thumbWidthDefault / 2;
2135
+ const gapWithThumbStr = `${thumbGap + thumbHalfWidth}px`;
2136
+ const hasAnyInsetIcon = Boolean(insetIcon || insetIconTrailing);
2137
+ const prefersReduced = (_a = useReducedMotion()) != null ? _a : false;
2138
+ const [trackWidth, setTrackWidth] = React4.useState(0);
2139
+ React4.useLayoutEffect(() => {
2140
+ const el = trackRef.current;
2141
+ if (!el || !hasAnyInsetIcon) return;
2142
+ setTrackWidth(isHorizontal ? el.clientWidth : el.clientHeight);
2143
+ const ro = new ResizeObserver(() => {
2144
+ setTrackWidth(isHorizontal ? el.clientWidth : el.clientHeight);
2145
+ });
2146
+ ro.observe(el);
2147
+ return () => ro.disconnect();
2148
+ }, [hasAnyInsetIcon, isHorizontal, trackRef]);
2149
+ const activeIconSize = Math.min(
2150
+ SliderTokens.insetIconSizes[trackSize],
2151
+ Math.max(4, size - SliderTokens.insetIconPadding * 2)
2152
+ );
2153
+ const iconTotalWidth = activeIconSize + SliderTokens.insetIconPadding * 2 + thumbGap + thumbHalfWidth;
2154
+ const iconThreshold = trackWidth > 0 ? iconTotalWidth / trackWidth : 0.15;
2155
+ const HYSTERESIS_GAP = 0.04;
2156
+ const trailingActiveRef = React4.useRef(1 - percent <= iconThreshold);
2157
+ const leadingActiveRef = React4.useRef(percent > iconThreshold);
2158
+ const trailingPercent = 1 - percent;
2159
+ if (trailingActiveRef.current) {
2160
+ if (trailingPercent > iconThreshold + HYSTERESIS_GAP) {
2161
+ trailingActiveRef.current = false;
2162
+ }
2163
+ } else {
2164
+ if (trailingPercent <= iconThreshold) {
2165
+ trailingActiveRef.current = true;
2166
+ }
2167
+ }
2168
+ const trailingOnActive = trailingActiveRef.current;
2169
+ if (leadingActiveRef.current) {
2170
+ if (percent <= iconThreshold - HYSTERESIS_GAP) {
2171
+ leadingActiveRef.current = false;
2172
+ }
2173
+ } else {
2174
+ if (percent > iconThreshold) {
2175
+ leadingActiveRef.current = true;
2176
+ }
2177
+ }
2178
+ const leadingOnActive = leadingActiveRef.current;
2179
+ const isAtMin = value !== void 0 && value <= min;
2180
+ const resolvedLeadingIcon = isAtMin && insetIconAtMin ? insetIconAtMin : insetIcon;
2181
+ const isAtMax = value !== void 0 && value >= max;
2182
+ const resolvedTrailingIcon = isAtMax && insetIconAtMax ? insetIconAtMax : insetIconTrailing;
2183
+ const activeColor = disabled ? SliderColors.disabledActiveTrack : `var(--md-sys-color-${variant})`;
2184
+ const inactiveColor = disabled ? SliderColors.disabledInactiveTrack : `var(--md-sys-color-${variant}-container)`;
2185
+ const insetLimit = SliderTokens.thumbGap + SliderTokens.thumbWidthDefault / 2;
2186
+ const trackInset = Math.min(size / 2, insetLimit);
2187
+ const gapTotal = thumbGap + thumbHalfWidth;
2188
+ const thumbCenter = `calc(${trackInset}px + ${percent} * (100% - ${trackInset * 2}px))`;
2189
+ const leadingActiveLeft = `${trackInset + SliderTokens.insetIconPadding}px`;
2190
+ const leadingInactiveLeft = `calc(${thumbCenter} + ${gapTotal}px + ${SliderTokens.insetIconPadding}px)`;
2191
+ const trailingInactiveLeft = `calc(100% - ${trackInset}px - ${activeIconSize}px - ${SliderTokens.insetIconPadding}px)`;
2192
+ const trailingActiveLeft = `calc(${thumbCenter} - ${gapTotal}px - ${activeIconSize}px - ${SliderTokens.insetIconPadding}px)`;
2193
+ if (isHorizontal) {
2194
+ const cxStr = `calc(${trackInset}px + ${percent} * (100% - ${trackInset * 2}px))`;
2195
+ const segments2 = [];
2196
+ if (!isCentered) {
2197
+ const leftSegmentWidth = `max(0px, calc(${cxStr} - ${gapWithThumbStr}))`;
2198
+ const rightSegmentLeft = `calc(${cxStr} + ${gapWithThumbStr})`;
2199
+ const rightSegmentWidth = `max(0px, calc(100% - (${cxStr} + ${gapWithThumbStr})))`;
2200
+ segments2.push(
2201
+ /* @__PURE__ */ jsx(
2202
+ "div",
2203
+ {
2204
+ "aria-hidden": "true",
2205
+ style: __spreadValues({
2206
+ position: "absolute",
2207
+ left: 0,
2208
+ top: "50%",
2209
+ transform: "translateY(-50%)",
2210
+ width: leftSegmentWidth,
2211
+ height: size,
2212
+ backgroundColor: activeColor,
2213
+ opacity: disabled ? 0.38 : 1
2214
+ }, getHorizontalRadius(true, innerR, outerR))
2215
+ },
2216
+ "left"
2217
+ )
2218
+ );
2219
+ segments2.push(
2220
+ /* @__PURE__ */ jsx(
2221
+ "div",
2222
+ {
2223
+ "aria-hidden": "true",
2224
+ style: __spreadValues({
2225
+ position: "absolute",
2226
+ left: rightSegmentLeft,
2227
+ top: "50%",
2228
+ transform: "translateY(-50%)",
2229
+ width: rightSegmentWidth,
2230
+ height: size,
2231
+ backgroundColor: inactiveColor,
2232
+ opacity: disabled ? 0.38 : 1
2233
+ }, getHorizontalRadius(false, innerR, outerR))
2234
+ },
2235
+ "right"
2236
+ )
2237
+ );
2238
+ } else {
2239
+ const halfCenterGap = SliderTokens.thumbGap / 2;
2240
+ if (percent >= 0.5) {
2241
+ const leftBaseWidth = `max(0px, min(calc(50% - ${halfCenterGap}px), calc(${cxStr} - ${gapWithThumbStr})))`;
2242
+ segments2.push(
2243
+ /* @__PURE__ */ jsx(
2244
+ "div",
2245
+ {
2246
+ "aria-hidden": "true",
2247
+ style: __spreadValues({
2248
+ position: "absolute",
2249
+ left: 0,
2250
+ top: "50%",
2251
+ transform: "translateY(-50%)",
2252
+ width: leftBaseWidth,
2253
+ height: size,
2254
+ backgroundColor: inactiveColor,
2255
+ opacity: disabled ? 0.38 : 1
2256
+ }, getHorizontalRadius(true, innerR, outerR))
2257
+ },
2258
+ "left-base"
2259
+ )
2260
+ );
2261
+ const centerActiveLeft = `calc(50% + ${halfCenterGap}px)`;
2262
+ const centerActiveWidth = `max(0px, calc(${cxStr} - ${gapWithThumbStr} - (50% + ${halfCenterGap}px)))`;
2263
+ segments2.push(
2264
+ /* @__PURE__ */ jsx(
2265
+ "div",
2266
+ {
2267
+ "aria-hidden": "true",
2268
+ style: __spreadValues({
2269
+ position: "absolute",
2270
+ left: centerActiveLeft,
2271
+ top: "50%",
2272
+ transform: "translateY(-50%)",
2273
+ width: centerActiveWidth,
2274
+ height: size,
2275
+ backgroundColor: activeColor,
2276
+ opacity: disabled ? 0.38 : 1
2277
+ }, allInnerRadius(innerR))
2278
+ },
2279
+ "center-active"
2280
+ )
2281
+ );
2282
+ const rightBaseLeft = `calc(${cxStr} + ${gapWithThumbStr})`;
2283
+ const rightBaseWidth = `max(0px, calc(100% - (${cxStr} + ${gapWithThumbStr})))`;
2284
+ segments2.push(
2285
+ /* @__PURE__ */ jsx(
2286
+ "div",
2287
+ {
2288
+ "aria-hidden": "true",
2289
+ style: __spreadValues({
2290
+ position: "absolute",
2291
+ left: rightBaseLeft,
2292
+ top: "50%",
2293
+ transform: "translateY(-50%)",
2294
+ width: rightBaseWidth,
2295
+ height: size,
2296
+ backgroundColor: inactiveColor,
2297
+ opacity: disabled ? 0.38 : 1
2298
+ }, getHorizontalRadius(false, innerR, outerR))
2299
+ },
2300
+ "right-base"
2301
+ )
2302
+ );
2303
+ } else {
2304
+ const leftBaseWidth = `max(0px, calc(${cxStr} - ${gapWithThumbStr}))`;
2305
+ segments2.push(
2306
+ /* @__PURE__ */ jsx(
2307
+ "div",
2308
+ {
2309
+ "aria-hidden": "true",
2310
+ style: __spreadValues({
2311
+ position: "absolute",
2312
+ left: 0,
2313
+ top: "50%",
2314
+ transform: "translateY(-50%)",
2315
+ width: leftBaseWidth,
2316
+ height: size,
2317
+ backgroundColor: inactiveColor,
2318
+ opacity: disabled ? 0.38 : 1
2319
+ }, getHorizontalRadius(true, innerR, outerR))
2320
+ },
2321
+ "left-base"
2322
+ )
2323
+ );
2324
+ const centerActiveLeft = `calc(${cxStr} + ${gapWithThumbStr})`;
2325
+ const centerActiveWidth = `max(0px, calc(50% - ${halfCenterGap}px - (${cxStr} + ${gapWithThumbStr})))`;
2326
+ segments2.push(
2327
+ /* @__PURE__ */ jsx(
2328
+ "div",
2329
+ {
2330
+ "aria-hidden": "true",
2331
+ style: __spreadValues({
2332
+ position: "absolute",
2333
+ left: centerActiveLeft,
2334
+ top: "50%",
2335
+ transform: "translateY(-50%)",
2336
+ width: centerActiveWidth,
2337
+ height: size,
2338
+ backgroundColor: activeColor,
2339
+ opacity: disabled ? 0.38 : 1
2340
+ }, allInnerRadius(innerR))
2341
+ },
2342
+ "center-active"
2343
+ )
2344
+ );
2345
+ const rightBaseLeft = `max(calc(50% + ${halfCenterGap}px), calc(${cxStr} + ${gapWithThumbStr}))`;
2346
+ const rightBaseWidth = `max(0px, calc(100% - max(calc(50% + ${halfCenterGap}px), calc(${cxStr} + ${gapWithThumbStr}))))`;
2347
+ segments2.push(
2348
+ /* @__PURE__ */ jsx(
2349
+ "div",
2350
+ {
2351
+ "aria-hidden": "true",
2352
+ style: __spreadValues({
2353
+ position: "absolute",
2354
+ left: rightBaseLeft,
2355
+ top: "50%",
2356
+ transform: "translateY(-50%)",
2357
+ width: rightBaseWidth,
2358
+ height: size,
2359
+ backgroundColor: inactiveColor,
2360
+ opacity: disabled ? 0.38 : 1
2361
+ }, getHorizontalRadius(false, innerR, outerR))
2362
+ },
2363
+ "right-base"
2364
+ )
2365
+ );
2366
+ }
2367
+ }
2368
+ return /* @__PURE__ */ jsxs(
2369
+ "div",
2370
+ {
2371
+ ref: trackRef,
2372
+ className: cn(
2373
+ "relative w-full",
2374
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
2375
+ ),
2376
+ style: { height: thumbHeight },
2377
+ onPointerDown: onTrackPointerDown,
2378
+ "aria-hidden": "true",
2379
+ children: [
2380
+ segments2,
2381
+ ticks.length > 0 && /* @__PURE__ */ jsx(
2382
+ Ticks,
2383
+ {
2384
+ ticks,
2385
+ min,
2386
+ max,
2387
+ percent,
2388
+ orientation,
2389
+ disabled,
2390
+ variant,
2391
+ isCentered,
2392
+ trackInset
2393
+ }
2394
+ ),
2395
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: resolvedLeadingIcon && /* @__PURE__ */ jsx(
2396
+ InsetIcon,
2397
+ {
2398
+ icon: resolvedLeadingIcon,
2399
+ isOnActiveSegment: leadingOnActive,
2400
+ position: leadingOnActive ? leadingActiveLeft : leadingInactiveLeft,
2401
+ orientation,
2402
+ trackSize: size,
2403
+ trackSizeToken: trackSize,
2404
+ disabled,
2405
+ variant,
2406
+ prefersReduced
2407
+ },
2408
+ isAtMin ? "lead-min" : leadingOnActive ? "lead-active" : "lead-inactive"
2409
+ ) }),
2410
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: resolvedTrailingIcon && /* @__PURE__ */ jsx(
2411
+ InsetIcon,
2412
+ {
2413
+ icon: resolvedTrailingIcon,
2414
+ isOnActiveSegment: trailingOnActive,
2415
+ position: trailingOnActive ? trailingActiveLeft : trailingInactiveLeft,
2416
+ orientation,
2417
+ trackSize: size,
2418
+ trackSizeToken: trackSize,
2419
+ disabled,
2420
+ variant,
2421
+ prefersReduced
2422
+ },
2423
+ isAtMax ? "trail-max" : trailingOnActive ? "trail-active" : "trail-inactive"
2424
+ ) })
2425
+ ]
2426
+ }
2427
+ );
2428
+ }
2429
+ const cyStr = `calc(${trackInset}px + ${percent} * (100% - ${trackInset * 2}px))`;
2430
+ const segments = [];
2431
+ if (!isCentered) {
2432
+ const bottomSegmentHeight = `max(0px, calc(${cyStr} - ${gapWithThumbStr}))`;
2433
+ const topSegmentBottom = `calc(${cyStr} + ${gapWithThumbStr})`;
2434
+ const topSegmentHeight = `max(0px, calc(100% - (${cyStr} + ${gapWithThumbStr})))`;
2435
+ segments.push(
2436
+ /* @__PURE__ */ jsx(
2437
+ "div",
2438
+ {
2439
+ "aria-hidden": "true",
2440
+ style: __spreadValues({
2441
+ position: "absolute",
2442
+ bottom: 0,
2443
+ left: "50%",
2444
+ transform: "translateX(-50%)",
2445
+ height: bottomSegmentHeight,
2446
+ width: size,
2447
+ backgroundColor: activeColor,
2448
+ opacity: disabled ? 0.38 : 1
2449
+ }, getVerticalRadius(true, innerR, outerR))
2450
+ },
2451
+ "bottom"
2452
+ )
2453
+ );
2454
+ segments.push(
2455
+ /* @__PURE__ */ jsx(
2456
+ "div",
2457
+ {
2458
+ "aria-hidden": "true",
2459
+ style: __spreadValues({
2460
+ position: "absolute",
2461
+ bottom: topSegmentBottom,
2462
+ left: "50%",
2463
+ transform: "translateX(-50%)",
2464
+ height: topSegmentHeight,
2465
+ width: size,
2466
+ backgroundColor: inactiveColor,
2467
+ opacity: disabled ? 0.38 : 1
2468
+ }, getVerticalRadius(false, innerR, outerR))
2469
+ },
2470
+ "top"
2471
+ )
2472
+ );
2473
+ } else {
2474
+ const halfCenterGap = SliderTokens.thumbGap / 2;
2475
+ if (percent >= 0.5) {
2476
+ const bottomBaseHeight = `max(0px, min(calc(50% - ${halfCenterGap}px), calc(${cyStr} - ${gapWithThumbStr})))`;
2477
+ segments.push(
2478
+ /* @__PURE__ */ jsx(
2479
+ "div",
2480
+ {
2481
+ "aria-hidden": "true",
2482
+ style: __spreadValues({
2483
+ position: "absolute",
2484
+ bottom: 0,
2485
+ left: "50%",
2486
+ transform: "translateX(-50%)",
2487
+ height: bottomBaseHeight,
2488
+ width: size,
2489
+ backgroundColor: inactiveColor,
2490
+ opacity: disabled ? 0.38 : 1
2491
+ }, getVerticalRadius(true, innerR, outerR))
2492
+ },
2493
+ "bottom-base"
2494
+ )
2495
+ );
2496
+ const centerActiveBottom = `calc(50% + ${halfCenterGap}px)`;
2497
+ const centerActiveHeight = `max(0px, calc(${cyStr} - ${gapWithThumbStr} - (50% + ${halfCenterGap}px)))`;
2498
+ segments.push(
2499
+ /* @__PURE__ */ jsx(
2500
+ "div",
2501
+ {
2502
+ "aria-hidden": "true",
2503
+ style: __spreadValues({
2504
+ position: "absolute",
2505
+ bottom: centerActiveBottom,
2506
+ left: "50%",
2507
+ transform: "translateX(-50%)",
2508
+ height: centerActiveHeight,
2509
+ width: size,
2510
+ backgroundColor: activeColor,
2511
+ opacity: disabled ? 0.38 : 1
2512
+ }, allInnerRadius(innerR))
2513
+ },
2514
+ "center-active"
2515
+ )
2516
+ );
2517
+ const topBaseBottom = `calc(${cyStr} + ${gapWithThumbStr})`;
2518
+ const topBaseHeight = `max(0px, calc(100% - (${cyStr} + ${gapWithThumbStr})))`;
2519
+ segments.push(
2520
+ /* @__PURE__ */ jsx(
2521
+ "div",
2522
+ {
2523
+ "aria-hidden": "true",
2524
+ style: __spreadValues({
2525
+ position: "absolute",
2526
+ bottom: topBaseBottom,
2527
+ left: "50%",
2528
+ transform: "translateX(-50%)",
2529
+ height: topBaseHeight,
2530
+ width: size,
2531
+ backgroundColor: inactiveColor,
2532
+ opacity: disabled ? 0.38 : 1
2533
+ }, getVerticalRadius(false, innerR, outerR))
2534
+ },
2535
+ "top-base"
2536
+ )
2537
+ );
2538
+ } else {
2539
+ const bottomBaseHeight = `max(0px, calc(${cyStr} - ${gapWithThumbStr}))`;
2540
+ segments.push(
2541
+ /* @__PURE__ */ jsx(
2542
+ "div",
2543
+ {
2544
+ "aria-hidden": "true",
2545
+ style: __spreadValues({
2546
+ position: "absolute",
2547
+ bottom: 0,
2548
+ left: "50%",
2549
+ transform: "translateX(-50%)",
2550
+ height: bottomBaseHeight,
2551
+ width: size,
2552
+ backgroundColor: inactiveColor,
2553
+ opacity: disabled ? 0.38 : 1
2554
+ }, getVerticalRadius(true, innerR, outerR))
2555
+ },
2556
+ "bottom-base"
2557
+ )
2558
+ );
2559
+ const centerActiveBottom = `calc(${cyStr} + ${gapWithThumbStr})`;
2560
+ const centerActiveHeight = `max(0px, calc(50% - ${halfCenterGap}px - (${cyStr} + ${gapWithThumbStr})))`;
2561
+ segments.push(
2562
+ /* @__PURE__ */ jsx(
2563
+ "div",
2564
+ {
2565
+ "aria-hidden": "true",
2566
+ style: __spreadValues({
2567
+ position: "absolute",
2568
+ bottom: centerActiveBottom,
2569
+ left: "50%",
2570
+ transform: "translateX(-50%)",
2571
+ height: centerActiveHeight,
2572
+ width: size,
2573
+ backgroundColor: activeColor,
2574
+ opacity: disabled ? 0.38 : 1
2575
+ }, allInnerRadius(innerR))
2576
+ },
2577
+ "center-active"
2578
+ )
2579
+ );
2580
+ const topBaseBottom = `max(calc(50% + ${halfCenterGap}px), calc(${cyStr} + ${gapWithThumbStr}))`;
2581
+ const topBaseHeight = `max(0px, calc(100% - max(calc(50% + ${halfCenterGap}px), calc(${cyStr} + ${gapWithThumbStr}))))`;
2582
+ segments.push(
2583
+ /* @__PURE__ */ jsx(
2584
+ "div",
2585
+ {
2586
+ "aria-hidden": "true",
2587
+ style: __spreadValues({
2588
+ position: "absolute",
2589
+ bottom: topBaseBottom,
2590
+ left: "50%",
2591
+ transform: "translateX(-50%)",
2592
+ height: topBaseHeight,
2593
+ width: size,
2594
+ backgroundColor: inactiveColor,
2595
+ opacity: disabled ? 0.38 : 1
2596
+ }, getVerticalRadius(false, innerR, outerR))
2597
+ },
2598
+ "top-base"
2599
+ )
2600
+ );
2601
+ }
2602
+ }
2603
+ return /* @__PURE__ */ jsxs(
2604
+ "div",
2605
+ {
2606
+ ref: trackRef,
2607
+ className: cn(
2608
+ "relative h-full",
2609
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
2610
+ ),
2611
+ style: { width: thumbHeight },
2612
+ onPointerDown: onTrackPointerDown,
2613
+ "aria-hidden": "true",
2614
+ children: [
2615
+ segments,
2616
+ ticks.length > 0 && /* @__PURE__ */ jsx(
2617
+ Ticks,
2618
+ {
2619
+ ticks,
2620
+ min,
2621
+ max,
2622
+ percent,
2623
+ orientation,
2624
+ disabled,
2625
+ variant,
2626
+ isCentered,
2627
+ trackInset
2628
+ }
2629
+ ),
2630
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: resolvedLeadingIcon && /* @__PURE__ */ jsx(
2631
+ InsetIcon,
2632
+ {
2633
+ icon: resolvedLeadingIcon,
2634
+ isOnActiveSegment: leadingOnActive,
2635
+ position: leadingOnActive ? leadingActiveLeft : leadingInactiveLeft,
2636
+ orientation,
2637
+ trackSize: size,
2638
+ trackSizeToken: trackSize,
2639
+ disabled,
2640
+ variant,
2641
+ prefersReduced
2642
+ },
2643
+ isAtMin ? "lead-min" : leadingOnActive ? "lead-active" : "lead-inactive"
2644
+ ) }),
2645
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: resolvedTrailingIcon && /* @__PURE__ */ jsx(
2646
+ InsetIcon,
2647
+ {
2648
+ icon: resolvedTrailingIcon,
2649
+ isOnActiveSegment: trailingOnActive,
2650
+ position: trailingOnActive ? trailingActiveLeft : trailingInactiveLeft,
2651
+ orientation,
2652
+ trackSize: size,
2653
+ trackSizeToken: trackSize,
2654
+ disabled,
2655
+ variant,
2656
+ prefersReduced
2657
+ },
2658
+ isAtMax ? "trail-max" : trailingOnActive ? "trail-active" : "trail-inactive"
2659
+ ) })
2660
+ ]
2661
+ }
2662
+ );
2663
+ });
2664
+ var SliderComponent = React4.forwardRef(
2665
+ ({
2666
+ value: controlledValue,
2667
+ defaultValue,
2668
+ onValueChange,
2669
+ onValueChangeEnd,
2670
+ min = 0,
2671
+ max = 100,
2672
+ step = 0,
2673
+ orientation = "horizontal",
2674
+ trackSize = "m",
2675
+ trackShape = "md3",
2676
+ variant = "primary",
2677
+ isCentered = false,
2678
+ disabled = false,
2679
+ showValueIndicator = false,
2680
+ showTicks = false,
2681
+ insetIcon,
2682
+ insetIconAtMin,
2683
+ insetIconTrailing,
2684
+ insetIconAtMax,
2685
+ formatValue,
2686
+ className,
2687
+ "aria-label": ariaLabel,
2688
+ "aria-labelledby": ariaLabelledBy
2689
+ }, ref) => {
2690
+ const isHorizontal = orientation === "horizontal";
2691
+ const midpoint = min + (max - min) / 2;
2692
+ const initialValue = defaultValue != null ? defaultValue : midpoint;
2693
+ const [internalValue, setInternalValue] = React4.useState(initialValue);
2694
+ const resolvedValue = controlledValue !== void 0 ? controlledValue : internalValue;
2695
+ const { coerce, snap, toPercent, ticks } = useSliderMath({
2696
+ min,
2697
+ max,
2698
+ step
2699
+ });
2700
+ const safeValue = snap(coerce(resolvedValue));
2701
+ const percent = toPercent(safeValue);
2702
+ const trackRef = React4.useRef(null);
2703
+ const handleValueChange = React4.useCallback(
2704
+ (newValue) => {
2705
+ const clamped = snap(coerce(newValue));
2706
+ if (controlledValue === void 0) {
2707
+ setInternalValue(clamped);
2708
+ }
2709
+ onValueChange == null ? void 0 : onValueChange(clamped);
2710
+ },
2711
+ [coerce, controlledValue, onValueChange, snap]
2712
+ );
2713
+ const handleValueChangeEnd = React4.useCallback(
2714
+ (newValue) => {
2715
+ onValueChangeEnd == null ? void 0 : onValueChangeEnd(snap(coerce(newValue)));
2716
+ },
2717
+ [coerce, onValueChangeEnd, snap]
2718
+ );
2719
+ const handleTrackPointerDown = React4.useCallback(
2720
+ (e) => {
2721
+ if (disabled) return;
2722
+ const trackEl = trackRef.current;
2723
+ if (!trackEl) return;
2724
+ const rect = trackEl.getBoundingClientRect();
2725
+ let clickPercent;
2726
+ const insetLimit = SliderTokens.thumbGap + SliderTokens.thumbWidthDefault / 2;
2727
+ const trackInset = Math.min(
2728
+ SliderTokens.trackSizes[trackSize] / 2,
2729
+ insetLimit
2730
+ );
2731
+ if (isHorizontal) {
2732
+ const x = e.clientX - rect.left;
2733
+ clickPercent = (x - trackInset) / (rect.width - trackInset * 2);
2734
+ } else {
2735
+ const y = rect.bottom - e.clientY;
2736
+ clickPercent = (y - trackInset) / (rect.height - trackInset * 2);
2737
+ }
2738
+ const clamped = Math.max(0, Math.min(1, clickPercent));
2739
+ const rawValue = min + clamped * (max - min);
2740
+ const newValue = snap(coerce(rawValue));
2741
+ handleValueChange(newValue);
2742
+ },
2743
+ [
2744
+ coerce,
2745
+ disabled,
2746
+ handleValueChange,
2747
+ isHorizontal,
2748
+ max,
2749
+ min,
2750
+ snap,
2751
+ trackSize
2752
+ ]
2753
+ );
2754
+ const id = React4.useId();
2755
+ const thumbId = `slider-thumb-${id}`;
2756
+ const supportsInsetIcon = !isCentered && SliderTokens.trackSizes[trackSize] >= 40;
2757
+ const hasAnyInsetIcon = !!(insetIcon || insetIconTrailing);
2758
+ if (hasAnyInsetIcon && !supportsInsetIcon) {
2759
+ console.warn(
2760
+ "[Slider] insetIcon is only supported on track sizes >= 40dp (e.g. xl track sizes). See MD3 spec: https://m3.material.io/components/sliders/specs"
2761
+ );
2762
+ }
2763
+ return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: /* @__PURE__ */ jsxs(
2764
+ "div",
2765
+ {
2766
+ ref,
2767
+ className: cn(
2768
+ "relative flex items-center",
2769
+ isHorizontal ? "w-full flex-row" : "h-full flex-col",
2770
+ disabled && "pointer-events-none",
2771
+ className
2772
+ ),
2773
+ style: isHorizontal ? { minWidth: 0 } : { minHeight: 0 },
2774
+ children: [
2775
+ /* @__PURE__ */ jsx(
2776
+ SliderTrack,
2777
+ {
2778
+ percent,
2779
+ trackSize,
2780
+ trackShape,
2781
+ orientation,
2782
+ isCentered,
2783
+ min,
2784
+ max,
2785
+ disabled,
2786
+ variant,
2787
+ trackRef,
2788
+ onTrackPointerDown: handleTrackPointerDown,
2789
+ ticks: showTicks ? ticks : [],
2790
+ insetIcon: supportsInsetIcon ? insetIcon : void 0,
2791
+ insetIconAtMin: supportsInsetIcon ? insetIconAtMin : void 0,
2792
+ insetIconTrailing: supportsInsetIcon ? insetIconTrailing : void 0,
2793
+ insetIconAtMax: supportsInsetIcon ? insetIconAtMax : void 0,
2794
+ value: safeValue
2795
+ }
2796
+ ),
2797
+ /* @__PURE__ */ jsx(
2798
+ SliderThumb,
2799
+ {
2800
+ value: safeValue,
2801
+ percent,
2802
+ min,
2803
+ max,
2804
+ step,
2805
+ disabled,
2806
+ orientation,
2807
+ trackSize,
2808
+ variant,
2809
+ showValueIndicator,
2810
+ trackRef,
2811
+ onValueChange: handleValueChange,
2812
+ onValueChangeEnd: handleValueChangeEnd,
2813
+ formatValue,
2814
+ thumbId,
2815
+ zIndex: 1,
2816
+ "aria-label": ariaLabel,
2817
+ "aria-labelledby": ariaLabelledBy
2818
+ }
2819
+ )
2820
+ ]
2821
+ }
2822
+ ) });
2823
+ }
2824
+ );
2825
+ SliderComponent.displayName = "Slider";
2826
+ var Slider = React4.memo(SliderComponent);
2827
+
2828
+ // src/ui/shared/motion-tokens.ts
2829
+ var FAST_SPATIAL_SPRING = {
2830
+ type: "spring",
2831
+ stiffness: 800,
2832
+ damping: 33.94
2833
+ };
2834
+ var FAST_EFFECTS_SPRING = {
2835
+ type: "spring",
2836
+ stiffness: 3800,
2837
+ damping: 123.29
2838
+ };
2839
+
2840
+ // src/ui/switch/switch.tokens.ts
2841
+ var SwitchTokens = {
2842
+ // ── Track ─────────────────────────────────────────────────────────────────
2843
+ /** SwitchTokens.TrackWidth = 52dp */
2844
+ trackWidth: 52,
2845
+ /** SwitchTokens.TrackHeight = 32dp */
2846
+ trackHeight: 32,
2847
+ /** SwitchTokens.TrackOutlineWidth = 2dp */
2848
+ trackOutlineWidth: 2,
2849
+ // ── Handle (Thumb) ────────────────────────────────────────────────────────
2850
+ /** SwitchTokens.SelectedHandleWidth/Height = 24dp */
2851
+ selectedHandleSize: 24,
2852
+ /** SwitchTokens.UnselectedHandleWidth/Height = 16dp */
2853
+ unselectedHandleSize: 16,
2854
+ /** SwitchTokens.IconHandleWidth/Height = 24dp (when thumb has icon content) */
2855
+ iconHandleSize: 24,
2856
+ /** SwitchTokens.PressedHandleWidth/Height = 28dp */
2857
+ pressedHandleSize: 28,
2858
+ // ── State Layer ───────────────────────────────────────────────────────────
2859
+ /** SwitchTokens.StateLayerSize = 40dp */
2860
+ stateLayerSize: 40,
2861
+ // ── Icon ──────────────────────────────────────────────────────────────────
2862
+ /** SwitchTokens.SelectedIconSize / UnselectedIconSize = 16dp */
2863
+ iconSize: 16,
2864
+ // ── Opacity (disabled states) ─────────────────────────────────────────────
2865
+ /** SwitchTokens.DisabledTrackOpacity = 0.12 */
2866
+ disabledTrackOpacity: 0.12,
2867
+ /** SwitchTokens.DisabledSelectedHandleOpacity = 1.0 */
2868
+ disabledSelectedHandleOpacity: 1,
2869
+ /** SwitchTokens.DisabledUnselectedHandleOpacity = 0.38 */
2870
+ disabledUnselectedHandleOpacity: 0.38,
2871
+ /** SwitchTokens.DisabledSelectedIconOpacity = 0.38 */
2872
+ disabledSelectedIconOpacity: 0.38,
2873
+ /** SwitchTokens.DisabledUnselectedIconOpacity = 0.38 */
2874
+ disabledUnselectedIconOpacity: 0.38
2875
+ };
2876
+ var SwitchColors = {
2877
+ // Track
2878
+ checkedTrack: "var(--md-sys-color-primary)",
2879
+ uncheckedTrack: "var(--md-sys-color-surface-container-highest)",
2880
+ uncheckedTrackOutline: "var(--md-sys-color-outline)",
2881
+ // Thumb
2882
+ checkedThumb: "var(--md-sys-color-on-primary)",
2883
+ uncheckedThumb: "var(--md-sys-color-outline)",
2884
+ hoverCheckedThumb: "var(--md-sys-color-primary-container)",
2885
+ hoverUncheckedThumb: "var(--md-sys-color-on-surface-variant)",
2886
+ disabledCheckedThumb: "var(--md-sys-color-surface)",
2887
+ // Icon
2888
+ checkedIcon: "var(--md-sys-color-on-primary-container)",
2889
+ uncheckedIcon: "var(--md-sys-color-surface-container-highest)",
2890
+ // State layer
2891
+ checkedStateLayer: "var(--md-sys-color-primary)",
2892
+ uncheckedStateLayer: "var(--md-sys-color-on-surface)",
2893
+ // Focus indicator
2894
+ focusIndicator: "var(--md-sys-color-secondary)"
2895
+ };
2896
+ var SNAP_TRANSITION = { duration: 0 };
2897
+ var COLOR_TRANSITION = {
2898
+ duration: 0.15,
2899
+ ease: [0.31, 0.94, 0.34, 1]
2900
+ };
2901
+ function resolveThumbSize(isPressed, checked, hasIcon) {
2902
+ if (isPressed) return SwitchTokens.pressedHandleSize;
2903
+ if (checked || hasIcon) return SwitchTokens.selectedHandleSize;
2904
+ return SwitchTokens.unselectedHandleSize;
2905
+ }
2906
+ function resolveThumbX(checked, isPressed, thumbSize) {
2907
+ const { trackHeight, trackWidth, trackOutlineWidth, selectedHandleSize } = SwitchTokens;
2908
+ const thumbPaddingStart = (trackHeight - thumbSize) / 2;
2909
+ const thumbPadding = (trackHeight - selectedHandleSize) / 2;
2910
+ const maxBound = trackWidth - selectedHandleSize - thumbPadding;
2911
+ if (isPressed && checked) return maxBound - trackOutlineWidth;
2912
+ if (isPressed) return thumbPaddingStart + trackOutlineWidth;
2913
+ if (checked) return maxBound;
2914
+ return thumbPaddingStart;
2915
+ }
2916
+ function resolveThumbColor(checked, disabled, isInteracting, checkedThumbColor, uncheckedThumbColor) {
2917
+ if (disabled) {
2918
+ return checked ? SwitchColors.disabledCheckedThumb : "rgba(28, 27, 31, 0.38)";
2919
+ }
2920
+ if (checked) {
2921
+ return isInteracting ? checkedThumbColor != null ? checkedThumbColor : SwitchColors.hoverCheckedThumb : checkedThumbColor != null ? checkedThumbColor : SwitchColors.checkedThumb;
2922
+ }
2923
+ return isInteracting ? uncheckedThumbColor != null ? uncheckedThumbColor : SwitchColors.hoverUncheckedThumb : uncheckedThumbColor != null ? uncheckedThumbColor : SwitchColors.uncheckedThumb;
2924
+ }
2925
+ function isIconVisible(thumbContent, icons, showOnlySelectedIcon, checked) {
2926
+ if (thumbContent == null) return false;
2927
+ if (icons) return true;
2928
+ return showOnlySelectedIcon && checked;
2929
+ }
2930
+ var SwitchVisual = React4.memo(function SwitchVisual2({
2931
+ checked,
2932
+ disabled,
2933
+ isPressed,
2934
+ isHovered,
2935
+ isFocused,
2936
+ thumbContent,
2937
+ icons,
2938
+ showOnlySelectedIcon,
2939
+ prefersReduced,
2940
+ checkedTrackColor,
2941
+ uncheckedTrackColor,
2942
+ checkedThumbColor,
2943
+ uncheckedThumbColor
2944
+ }) {
2945
+ const hasIcon = icons && thumbContent != null;
2946
+ const thumbSize = resolveThumbSize(isPressed, checked, hasIcon);
2947
+ const thumbX = resolveThumbX(checked, isPressed, thumbSize);
2948
+ const trackBg = checked ? checkedTrackColor != null ? checkedTrackColor : SwitchColors.checkedTrack : uncheckedTrackColor != null ? uncheckedTrackColor : SwitchColors.uncheckedTrack;
2949
+ const trackBorderColor = checked ? "rgba(0, 0, 0, 0)" : SwitchColors.uncheckedTrackOutline;
2950
+ const trackBorderWidth = checked ? 0 : SwitchTokens.trackOutlineWidth;
2951
+ const trackOpacity = disabled ? SwitchTokens.disabledTrackOpacity : 1;
2952
+ const isInteracting = isHovered || isFocused || isPressed;
2953
+ const thumbBg = resolveThumbColor(
2954
+ checked,
2955
+ disabled,
2956
+ isInteracting,
2957
+ checkedThumbColor,
2958
+ uncheckedThumbColor
2959
+ );
2960
+ const iconColor = disabled ? checked ? "rgba(28, 27, 31, 0.38)" : "rgba(230, 225, 229, 0.38)" : checked ? SwitchColors.checkedIcon : SwitchColors.uncheckedIcon;
2961
+ const stateLayerColor = checked ? SwitchColors.checkedStateLayer : SwitchColors.uncheckedStateLayer;
2962
+ const stateLayerOpacity = isPressed || isFocused ? 0.12 : isHovered ? 0.08 : 0;
2963
+ const stateLayerX = thumbX + thumbSize / 2 - SwitchTokens.stateLayerSize / 2;
2964
+ const showIcon = isIconVisible(
2965
+ thumbContent,
2966
+ icons,
2967
+ showOnlySelectedIcon,
2968
+ checked
2969
+ );
2970
+ const colorTransition = prefersReduced ? { duration: 0 } : COLOR_TRANSITION;
2971
+ const stateLayerTransition = prefersReduced ? { duration: 0 } : FAST_EFFECTS_SPRING;
2972
+ return /* @__PURE__ */ jsxs(
2973
+ "div",
2974
+ {
2975
+ className: "relative",
2976
+ style: {
2977
+ width: SwitchTokens.trackWidth,
2978
+ height: SwitchTokens.trackHeight
2979
+ },
2980
+ "aria-hidden": "true",
2981
+ children: [
2982
+ /* @__PURE__ */ jsx(
2983
+ m.div,
2984
+ {
2985
+ className: "absolute inset-0 rounded-full",
2986
+ style: { borderStyle: "solid", opacity: trackOpacity },
2987
+ animate: {
2988
+ backgroundColor: trackBg,
2989
+ borderColor: trackBorderColor,
2990
+ borderWidth: trackBorderWidth
2991
+ },
2992
+ transition: colorTransition
2993
+ }
2994
+ ),
2995
+ /* @__PURE__ */ jsx(
2996
+ m.div,
2997
+ {
2998
+ className: "absolute rounded-full pointer-events-none",
2999
+ style: {
3000
+ width: SwitchTokens.stateLayerSize,
3001
+ height: SwitchTokens.stateLayerSize,
3002
+ top: (SwitchTokens.trackHeight - SwitchTokens.stateLayerSize) / 2,
3003
+ backgroundColor: stateLayerColor
3004
+ },
3005
+ animate: { x: stateLayerX, opacity: stateLayerOpacity },
3006
+ transition: stateLayerTransition
3007
+ }
3008
+ ),
3009
+ /* @__PURE__ */ jsx(
3010
+ m.div,
3011
+ {
3012
+ className: "absolute rounded-full flex items-center justify-center overflow-hidden",
3013
+ style: { top: "50%", left: 0, y: "-50%" },
3014
+ animate: {
3015
+ x: thumbX,
3016
+ width: thumbSize,
3017
+ height: thumbSize,
3018
+ backgroundColor: thumbBg
3019
+ },
3020
+ transition: prefersReduced ? { duration: 0 } : isPressed ? SNAP_TRANSITION : {
3021
+ x: FAST_SPATIAL_SPRING,
3022
+ width: FAST_SPATIAL_SPRING,
3023
+ height: FAST_SPATIAL_SPRING,
3024
+ backgroundColor: colorTransition
3025
+ },
3026
+ children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: showIcon && /* @__PURE__ */ jsx(
3027
+ m.span,
3028
+ {
3029
+ className: "flex items-center justify-center",
3030
+ style: {
3031
+ width: SwitchTokens.iconSize,
3032
+ height: SwitchTokens.iconSize,
3033
+ color: iconColor,
3034
+ fontSize: SwitchTokens.iconSize
3035
+ },
3036
+ initial: prefersReduced ? false : { opacity: 0, scale: 0.5 },
3037
+ animate: { opacity: 1, scale: 1 },
3038
+ exit: prefersReduced ? {} : { opacity: 0, scale: 0.5 },
3039
+ transition: prefersReduced ? { duration: 0 } : { duration: 0.15 },
3040
+ children: thumbContent
3041
+ },
3042
+ checked ? "icon-on" : "icon-off"
3043
+ ) })
3044
+ }
3045
+ )
3046
+ ]
3047
+ }
3048
+ );
3049
+ });
3050
+ var SwitchComponent = React4.forwardRef(
3051
+ ({
3052
+ checked,
3053
+ onCheckedChange,
3054
+ disabled = false,
3055
+ thumbContent,
3056
+ icons = false,
3057
+ showOnlySelectedIcon = false,
3058
+ label,
3059
+ ariaLabel,
3060
+ className,
3061
+ checkedTrackColor,
3062
+ uncheckedTrackColor,
3063
+ checkedThumbColor,
3064
+ uncheckedThumbColor
3065
+ }, ref) => {
3066
+ var _a;
3067
+ const prefersReduced = (_a = useReducedMotion()) != null ? _a : false;
3068
+ const [isPressed, setIsPressed] = React4.useState(false);
3069
+ const [isHovered, setIsHovered] = React4.useState(false);
3070
+ const [isFocused, setIsFocused] = React4.useState(false);
3071
+ const [ripples, setRipples] = React4.useState([]);
3072
+ const generatedId = React4.useId();
3073
+ const switchId = label ? `switch-${generatedId}` : void 0;
3074
+ const handleClick = React4.useCallback(() => {
3075
+ if (!disabled) onCheckedChange(!checked);
3076
+ }, [disabled, checked, onCheckedChange]);
3077
+ const handleKeyDown = React4.useCallback(
3078
+ (e) => {
3079
+ if (disabled) return;
3080
+ if (e.key === " " || e.key === "Enter") {
3081
+ e.preventDefault();
3082
+ onCheckedChange(!checked);
3083
+ }
3084
+ },
3085
+ [disabled, checked, onCheckedChange]
3086
+ );
3087
+ const handlePointerDown = React4.useCallback(
3088
+ (e) => {
3089
+ if (disabled) return;
3090
+ setIsPressed(true);
3091
+ const rect = e.currentTarget.getBoundingClientRect();
3092
+ const x = e.clientX - rect.left;
3093
+ const y = e.clientY - rect.top;
3094
+ const rippleSize = Math.hypot(rect.width, rect.height) * 2;
3095
+ setRipples((prev) => [
3096
+ ...prev,
3097
+ { id: Date.now(), x, y, size: rippleSize }
3098
+ ]);
3099
+ },
3100
+ [disabled]
3101
+ );
3102
+ const handlePointerUp = React4.useCallback(() => {
3103
+ setIsPressed(false);
3104
+ }, []);
3105
+ const handlePointerEnter = React4.useCallback(() => {
3106
+ if (!disabled) setIsHovered(true);
3107
+ }, [disabled]);
3108
+ const handlePointerLeave = React4.useCallback(() => {
3109
+ setIsHovered(false);
3110
+ setIsPressed(false);
3111
+ }, []);
3112
+ const handleFocus = React4.useCallback(() => setIsFocused(true), []);
3113
+ const handleBlur = React4.useCallback(() => setIsFocused(false), []);
3114
+ const removeRipple = React4.useCallback(
3115
+ (id) => setRipples((prev) => prev.filter((r) => r.id !== id)),
3116
+ []
3117
+ );
3118
+ const buttonAriaLabel = label ? void 0 : ariaLabel;
3119
+ const switchButton = /* @__PURE__ */ jsx(
3120
+ "button",
3121
+ {
3122
+ ref,
3123
+ id: switchId,
3124
+ type: "button",
3125
+ role: "switch",
3126
+ "aria-checked": checked,
3127
+ "aria-disabled": disabled || void 0,
3128
+ "aria-label": buttonAriaLabel,
3129
+ tabIndex: disabled ? -1 : 0,
3130
+ disabled,
3131
+ onClick: handleClick,
3132
+ onKeyDown: handleKeyDown,
3133
+ onPointerDown: handlePointerDown,
3134
+ onPointerUp: handlePointerUp,
3135
+ onPointerLeave: handlePointerLeave,
3136
+ onPointerEnter: handlePointerEnter,
3137
+ onFocus: handleFocus,
3138
+ onBlur: handleBlur,
3139
+ className: cn(
3140
+ "relative inline-flex items-center justify-center cursor-pointer select-none",
3141
+ // Touch target: 48×48 minimum (pad around 32px track)
3142
+ "min-w-12 min-h-12",
3143
+ // Focus ring — MD3 FocusIndicatorColor = secondary
3144
+ "focus-visible:outline-2 focus-visible:outline-offset-2 rounded-full",
3145
+ "focus-visible:outline-(--md-sys-color-secondary)",
3146
+ // Disabled
3147
+ disabled && "pointer-events-none cursor-not-allowed",
3148
+ !label && className
3149
+ ),
3150
+ children: /* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded-full", children: [
3151
+ /* @__PURE__ */ jsx(
3152
+ SwitchVisual,
3153
+ {
3154
+ checked,
3155
+ disabled,
3156
+ isPressed,
3157
+ isHovered,
3158
+ isFocused,
3159
+ thumbContent,
3160
+ icons,
3161
+ showOnlySelectedIcon,
3162
+ prefersReduced,
3163
+ checkedTrackColor,
3164
+ uncheckedTrackColor,
3165
+ checkedThumbColor,
3166
+ uncheckedThumbColor
3167
+ }
3168
+ ),
3169
+ /* @__PURE__ */ jsx(
3170
+ Ripple,
3171
+ {
3172
+ ripples,
3173
+ onRippleDone: removeRipple,
3174
+ disabled
3175
+ }
3176
+ )
3177
+ ] })
3178
+ }
3179
+ );
3180
+ const content = label ? /* @__PURE__ */ jsxs(
3181
+ "label",
3182
+ {
3183
+ htmlFor: switchId,
3184
+ className: cn(
3185
+ "inline-flex items-center gap-3 cursor-pointer select-none",
3186
+ disabled && "cursor-not-allowed pointer-events-none opacity-[0.38]",
3187
+ className
3188
+ ),
3189
+ children: [
3190
+ switchButton,
3191
+ /* @__PURE__ */ jsx("span", { className: "text-sm leading-none text-(--md-sys-color-on-surface)", children: label })
3192
+ ]
3193
+ }
3194
+ ) : switchButton;
3195
+ return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: content });
3196
+ }
3197
+ );
3198
+ SwitchComponent.displayName = "Switch";
3199
+ var Switch = React4.memo(SwitchComponent);
3200
+ var ScrollArea = React4.forwardRef(
3201
+ (_a, ref) => {
3202
+ var _b = _a, {
3203
+ className,
3204
+ viewportClassName,
3205
+ children,
3206
+ type = "hover",
3207
+ orientation = "vertical",
3208
+ scrollHideDelay = 600,
3209
+ viewportRef,
3210
+ viewportProps
3211
+ } = _b, props = __objRest(_b, [
3212
+ "className",
3213
+ "viewportClassName",
3214
+ "children",
3215
+ "type",
3216
+ "orientation",
3217
+ "scrollHideDelay",
3218
+ "viewportRef",
3219
+ "viewportProps"
3220
+ ]);
3221
+ const [isTouchDevice, setIsTouchDevice] = React4.useState(false);
3222
+ React4.useEffect(() => {
3223
+ if (typeof window !== "undefined") {
3224
+ setIsTouchDevice(
3225
+ "ontouchstart" in window || navigator.maxTouchPoints > 0
3226
+ );
3227
+ }
3228
+ }, []);
3229
+ const resolvedType = React4.useMemo(() => {
3230
+ if (type === "hover" && isTouchDevice) {
3231
+ return "scroll";
3232
+ }
3233
+ return type;
3234
+ }, [type, isTouchDevice]);
3235
+ const radixType = resolvedType === "none" ? "always" : resolvedType;
3236
+ return /* @__PURE__ */ jsxs(
3237
+ RadixScrollArea.Root,
3238
+ __spreadProps(__spreadValues({
3239
+ ref,
3240
+ type: radixType,
3241
+ scrollHideDelay,
3242
+ className: cn("relative overflow-hidden flex flex-col", className)
3243
+ }, props), {
3244
+ children: [
3245
+ /* @__PURE__ */ jsx(
3246
+ RadixScrollArea.Viewport,
3247
+ __spreadProps(__spreadValues({
3248
+ ref: viewportRef
3249
+ }, viewportProps), {
3250
+ className: cn(
3251
+ "h-full w-full flex-1 min-h-0 min-w-0 rounded-[inherit]",
3252
+ "outline-none focus-visible:ring-2 focus-visible:ring-m3-primary focus-visible:ring-offset-1",
3253
+ viewportClassName,
3254
+ viewportProps == null ? void 0 : viewportProps.className
3255
+ ),
3256
+ children
3257
+ })
3258
+ ),
3259
+ (orientation === "vertical" || orientation === "both") && /* @__PURE__ */ jsx(
3260
+ ScrollAreaScrollbar,
3261
+ {
3262
+ orientation: "vertical",
3263
+ className: resolvedType === "none" ? "hidden" : void 0
3264
+ }
3265
+ ),
3266
+ (orientation === "horizontal" || orientation === "both") && /* @__PURE__ */ jsx(
3267
+ ScrollAreaScrollbar,
3268
+ {
3269
+ orientation: "horizontal",
3270
+ className: resolvedType === "none" ? "hidden" : void 0
3271
+ }
3272
+ ),
3273
+ /* @__PURE__ */ jsx(RadixScrollArea.Corner, { className: "bg-m3-surface-container" })
3274
+ ]
3275
+ })
3276
+ );
3277
+ }
3278
+ );
3279
+ ScrollArea.displayName = "ScrollArea";
3280
+ var ScrollAreaScrollbar = React4.forwardRef((_a, ref) => {
3281
+ var _b = _a, { className, orientation = "vertical" } = _b, props = __objRest(_b, ["className", "orientation"]);
3282
+ return /* @__PURE__ */ jsx(
3283
+ RadixScrollArea.Scrollbar,
3284
+ __spreadProps(__spreadValues({
3285
+ ref,
3286
+ orientation,
3287
+ className: cn(
3288
+ "flex touch-none select-none transition-all duration-300 ease-in-out",
3289
+ "absolute z-50",
3290
+ orientation === "vertical" && "right-0 top-0 bottom-0 w-2.5 border-l border-l-transparent p-px",
3291
+ orientation === "horizontal" && "bottom-0 left-0 right-0 h-2.5 flex-col border-t border-t-transparent p-px",
3292
+ "data-[state=hidden]:opacity-0 data-[state=visible]:opacity-100",
3293
+ className
3294
+ )
3295
+ }, props), {
3296
+ children: /* @__PURE__ */ jsx(
3297
+ RadixScrollArea.Thumb,
3298
+ {
3299
+ className: cn(
3300
+ "relative flex-1 rounded-full bg-m3-on-surface/25 transition-colors duration-200",
3301
+ "hover:bg-m3-on-surface/40 active:bg-m3-on-surface/55",
3302
+ "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"
3303
+ )
3304
+ }
3305
+ )
3306
+ })
3307
+ );
3308
+ });
3309
+ ScrollAreaScrollbar.displayName = RadixScrollArea.Scrollbar.displayName;
3310
+ var ScrollAreaCorner = React4.forwardRef((_a, ref) => {
3311
+ var _b = _a, { className } = _b, props = __objRest(_b, ["className"]);
3312
+ return /* @__PURE__ */ jsx(
3313
+ RadixScrollArea.Corner,
3314
+ __spreadValues({
3315
+ ref,
3316
+ className: cn("bg-m3-surface-container", className)
3317
+ }, props)
3318
+ );
3319
+ });
3320
+ ScrollAreaCorner.displayName = "ScrollAreaCorner";
3321
+ var ToolbarContext = React4.createContext(
3322
+ null
3323
+ );
3324
+ function useToolbarContext() {
3325
+ return React4.useContext(ToolbarContext);
3326
+ }
3327
+
3328
+ // src/ui/shared/constants.ts
3329
+ var MD3_STANDARD_EASING = [
3330
+ 0.2,
3331
+ 0,
3332
+ 0,
3333
+ 1
3334
+ ];
3335
+ var MD3_LABEL_FLOAT_DURATION = 0.15;
3336
+ var MD3_INDICATOR_DURATION = 0.2;
3337
+ var MD3_SUPPORTING_DURATION = 0.12;
3338
+ var MD3_ICON_SWAP_DURATION = 0.1;
3339
+
3340
+ // src/ui/text-field/text-field.tokens.ts
3341
+ var TF_COLORS = {
3342
+ /** Input text color */
3343
+ inputText: "var(--md-sys-color-on-surface)",
3344
+ /** Label (unfloated) + icons + prefix/suffix + supporting text */
3345
+ onSurfaceVariant: "var(--md-sys-color-on-surface-variant)",
3346
+ /** Focused active indicator / outline, floated label */
3347
+ primary: "var(--md-sys-color-primary)",
3348
+ /** Error indicator / outline / label / icon / supporting text */
3349
+ error: "var(--md-sys-color-error)",
3350
+ /** Outlined border (enabled) */
3351
+ outline: "var(--md-sys-color-outline)"};
3352
+ var TF_SIZE = {
3353
+ /** Container height — normal */
3354
+ height: 56,
3355
+ /** Container height — dense variant */
3356
+ denseHeight: 48,
3357
+ /** Active indicator height — enabled */
3358
+ indicatorThin: 1,
3359
+ /** Active indicator height — focused */
3360
+ indicatorThick: 2,
3361
+ /** Outline stroke width — enabled */
3362
+ outlineThin: 1,
3363
+ /** Outline stroke width — focused */
3364
+ outlineThick: 2,
3365
+ /** Padding inline start (no leading icon) */
3366
+ paddingStart: 16,
3367
+ /** Notch extra padding per side on outlined label gap */
3368
+ notchPadding: 4
3369
+ };
3370
+ var TF_TYPOGRAPHY = {
3371
+ /** Scale ratio when label floats: 12/16 = 0.75 */
3372
+ labelScaleRatio: 12 / 16
3373
+ };
3374
+ var TF_CLASSES = {
3375
+ // Prefix / Suffix
3376
+ prefixSuffix: "text-base text-[var(--md-sys-color-on-surface-variant)] select-none shrink-0"};
3377
+ var ActiveIndicator = React4.memo(function ActiveIndicator2({
3378
+ isFocused,
3379
+ isError,
3380
+ isDisabled,
3381
+ isHovered,
3382
+ prefersReduced
3383
+ }) {
3384
+ const duration = prefersReduced ? 0 : MD3_INDICATOR_DURATION;
3385
+ const ease = MD3_STANDARD_EASING;
3386
+ const height = isFocused || isError ? TF_SIZE.indicatorThick : TF_SIZE.indicatorThin;
3387
+ let backgroundColor;
3388
+ if (isError) {
3389
+ backgroundColor = TF_COLORS.error;
3390
+ } else if (isFocused) {
3391
+ backgroundColor = TF_COLORS.primary;
3392
+ } else if (isHovered && !isDisabled) {
3393
+ backgroundColor = TF_COLORS.inputText;
3394
+ } else {
3395
+ backgroundColor = TF_COLORS.onSurfaceVariant;
3396
+ }
3397
+ return /* @__PURE__ */ jsx(
3398
+ m.div,
3399
+ {
3400
+ "aria-hidden": "true",
3401
+ className: "absolute bottom-0 left-0 right-0 origin-center",
3402
+ animate: { height, backgroundColor },
3403
+ transition: { duration, ease }
3404
+ }
3405
+ );
3406
+ });
3407
+ ActiveIndicator.displayName = "ActiveIndicator";
3408
+ function getFloatedY(variant, containerHeight) {
3409
+ const labelSmallHeight = 16;
3410
+ const paddingTop = 8;
3411
+ if (variant === "filled") {
3412
+ const floatedCenter = paddingTop + labelSmallHeight / 2;
3413
+ const unfloatedCenter = containerHeight / 2;
3414
+ return -(unfloatedCenter - floatedCenter);
3415
+ }
3416
+ return -(containerHeight / 2);
3417
+ }
3418
+ function getLabelColor(isFloated, isFocused, isError, isDisabled) {
3419
+ if (isDisabled) return TF_COLORS.onSurfaceVariant;
3420
+ if (isError) return TF_COLORS.error;
3421
+ if (isFloated && isFocused) return TF_COLORS.primary;
3422
+ return TF_COLORS.onSurfaceVariant;
3423
+ }
3424
+ var FloatingLabel = React4.memo(function FloatingLabel2({
3425
+ text,
3426
+ isFloated,
3427
+ isFocused,
3428
+ isError,
3429
+ isDisabled,
3430
+ variant,
3431
+ containerHeight,
3432
+ prefersReduced,
3433
+ showAsterisk,
3434
+ htmlFor,
3435
+ labelRef,
3436
+ hasLeading = false
3437
+ }) {
3438
+ const duration = prefersReduced ? 0 : MD3_LABEL_FLOAT_DURATION;
3439
+ const ease = MD3_STANDARD_EASING;
3440
+ const y = isFloated ? getFloatedY(variant, containerHeight) : 0;
3441
+ const x = variant === "outlined" && isFloated && hasLeading ? -36 : 0;
3442
+ const scale = isFloated ? TF_TYPOGRAPHY.labelScaleRatio : 1;
3443
+ const color = getLabelColor(isFloated, isFocused, isError, isDisabled);
3444
+ return /* @__PURE__ */ jsx(
3445
+ m.label,
3446
+ {
3447
+ htmlFor,
3448
+ className: cn(
3449
+ "absolute pointer-events-none select-none origin-[left_center] leading-6 text-base whitespace-nowrap",
3450
+ "px-1 -mx-1",
3451
+ // Clear the notch gap and keep text aligned
3452
+ variant === "outlined" && isFloated && "bg-m3-surface",
3453
+ "left-4",
3454
+ isDisabled && "opacity-[0.38]"
3455
+ ),
3456
+ animate: { y, x, scale, color },
3457
+ transition: { duration, ease },
3458
+ style: {
3459
+ top: (containerHeight - 24) / 2,
3460
+ // 24px is the resting line-height
3461
+ transformOrigin: "left center",
3462
+ zIndex: 1
3463
+ },
3464
+ children: /* @__PURE__ */ jsxs("span", { ref: labelRef, className: "inline-block relative", children: [
3465
+ text,
3466
+ /* @__PURE__ */ jsx(AnimatePresence, { children: showAsterisk && /* @__PURE__ */ jsx(
3467
+ m.span,
3468
+ {
3469
+ "aria-hidden": "true",
3470
+ initial: { opacity: 0 },
3471
+ animate: { opacity: 1 },
3472
+ exit: { opacity: 0 },
3473
+ transition: { duration: prefersReduced ? 0 : 0.1 },
3474
+ className: "ml-0.5 text-m3-error",
3475
+ children: "*"
3476
+ },
3477
+ "asterisk"
3478
+ ) })
3479
+ ] })
3480
+ }
3481
+ );
3482
+ });
3483
+ FloatingLabel.displayName = "FloatingLabel";
3484
+ var LeadingIcon = React4.memo(function LeadingIcon2({
3485
+ children,
3486
+ isError,
3487
+ isDisabled
3488
+ }) {
3489
+ const color = isError && !isDisabled ? TF_COLORS.error : TF_COLORS.onSurfaceVariant;
3490
+ return /* @__PURE__ */ jsx(
3491
+ "div",
3492
+ {
3493
+ "aria-hidden": "true",
3494
+ className: "flex items-center justify-center shrink-0 w-6 h-6 ml-3",
3495
+ style: { color },
3496
+ children
3497
+ }
3498
+ );
3499
+ });
3500
+ LeadingIcon.displayName = "LeadingIcon";
3501
+ function getOutlineColor(isFocused, isError, isHovered, isDisabled) {
3502
+ if (isDisabled) return TF_COLORS.onSurfaceVariant;
3503
+ if (isError) return TF_COLORS.error;
3504
+ if (isFocused) return TF_COLORS.primary;
3505
+ if (isHovered) return TF_COLORS.inputText;
3506
+ return TF_COLORS.outline;
3507
+ }
3508
+ var OutlineContainer = React4.memo(function OutlineContainer2({
3509
+ isFloated,
3510
+ isFocused,
3511
+ isError,
3512
+ isDisabled,
3513
+ isHovered,
3514
+ labelWidth,
3515
+ prefersReduced
3516
+ }) {
3517
+ const colorDuration = prefersReduced ? 0 : MD3_INDICATOR_DURATION;
3518
+ const notchDuration = prefersReduced ? 0 : MD3_LABEL_FLOAT_DURATION;
3519
+ const ease = MD3_STANDARD_EASING;
3520
+ const borderColor = getOutlineColor(
3521
+ isFocused,
3522
+ isError,
3523
+ isHovered,
3524
+ isDisabled
3525
+ );
3526
+ const borderWidth = isFocused || isError ? TF_SIZE.outlineThick : TF_SIZE.outlineThin;
3527
+ const leftSegmentWidth = TF_SIZE.paddingStart;
3528
+ const notchWidth = isFloated ? labelWidth * TF_TYPOGRAPHY.labelScaleRatio + TF_SIZE.notchPadding * 2 : 0;
3529
+ const borderTransition = { duration: colorDuration, ease };
3530
+ const notchTransition = { duration: notchDuration, ease };
3531
+ return /* @__PURE__ */ jsxs(
3532
+ "div",
3533
+ {
3534
+ "aria-hidden": "true",
3535
+ className: "absolute inset-0 pointer-events-none flex rounded-[inherit]",
3536
+ children: [
3537
+ /* @__PURE__ */ jsx(
3538
+ m.div,
3539
+ {
3540
+ className: "rounded-tl-[inherit] rounded-bl-[inherit]",
3541
+ style: { width: leftSegmentWidth - TF_SIZE.notchPadding },
3542
+ animate: {
3543
+ borderColor,
3544
+ borderWidth,
3545
+ borderStyle: "solid",
3546
+ borderRightWidth: 0
3547
+ },
3548
+ transition: borderTransition
3549
+ }
3550
+ ),
3551
+ /* @__PURE__ */ jsxs(
3552
+ m.div,
3553
+ {
3554
+ className: "flex flex-col shrink-0",
3555
+ style: { minWidth: 0 },
3556
+ animate: { width: notchWidth },
3557
+ transition: notchTransition,
3558
+ children: [
3559
+ /* @__PURE__ */ jsx(
3560
+ m.div,
3561
+ {
3562
+ className: "shrink-0",
3563
+ style: { height: borderWidth },
3564
+ animate: {
3565
+ borderTopColor: borderColor,
3566
+ opacity: isFloated ? 0 : 1,
3567
+ borderTopWidth: borderWidth,
3568
+ borderTopStyle: "solid"
3569
+ },
3570
+ transition: borderTransition
3571
+ }
3572
+ ),
3573
+ /* @__PURE__ */ jsx(
3574
+ m.div,
3575
+ {
3576
+ className: "flex-1",
3577
+ animate: {
3578
+ borderBottomColor: borderColor,
3579
+ borderBottomWidth: borderWidth,
3580
+ borderBottomStyle: "solid"
3581
+ },
3582
+ transition: borderTransition
3583
+ }
3584
+ )
3585
+ ]
3586
+ }
3587
+ ),
3588
+ /* @__PURE__ */ jsx(
3589
+ m.div,
3590
+ {
3591
+ className: "flex-1 rounded-tr-[inherit] rounded-br-[inherit]",
3592
+ animate: {
3593
+ borderColor,
3594
+ borderWidth,
3595
+ borderStyle: "solid",
3596
+ borderLeftWidth: 0
3597
+ },
3598
+ transition: borderTransition
3599
+ }
3600
+ )
3601
+ ]
3602
+ }
3603
+ );
3604
+ });
3605
+ OutlineContainer.displayName = "OutlineContainer";
3606
+ var PrefixSuffix = React4.memo(function PrefixSuffix2({
3607
+ text,
3608
+ type,
3609
+ visible,
3610
+ prefersReduced
3611
+ }) {
3612
+ const duration = prefersReduced ? 0 : MD3_LABEL_FLOAT_DURATION;
3613
+ return /* @__PURE__ */ jsx(AnimatePresence, { children: visible && /* @__PURE__ */ jsx(
3614
+ m.span,
3615
+ {
3616
+ "aria-hidden": "true",
3617
+ className: `${TF_CLASSES.prefixSuffix} ${type === "suffix" ? "ml-0.5" : "mr-0.5"}`,
3618
+ initial: { opacity: 0 },
3619
+ animate: { opacity: 1 },
3620
+ exit: { opacity: 0 },
3621
+ transition: { duration, ease: MD3_STANDARD_EASING },
3622
+ children: text
3623
+ }
3624
+ ) });
3625
+ });
3626
+ PrefixSuffix.displayName = "PrefixSuffix";
3627
+ function AnimatedText({
3628
+ text,
3629
+ motionKey,
3630
+ className,
3631
+ ariaLive,
3632
+ duration
3633
+ }) {
3634
+ return /* @__PURE__ */ jsx(
3635
+ m.span,
3636
+ {
3637
+ className,
3638
+ initial: { opacity: 0, y: -4 },
3639
+ animate: { opacity: 1, y: 0 },
3640
+ exit: { opacity: 0, y: -4 },
3641
+ transition: { duration, ease: MD3_STANDARD_EASING },
3642
+ "aria-live": ariaLive,
3643
+ children: text
3644
+ },
3645
+ motionKey
3646
+ );
3647
+ }
3648
+ var SupportingText = React4.memo(function SupportingText2({
3649
+ supportingText,
3650
+ errorText,
3651
+ isError,
3652
+ charCount,
3653
+ maxLength,
3654
+ id,
3655
+ prefersReduced
3656
+ }) {
3657
+ const duration = prefersReduced ? 0 : MD3_SUPPORTING_DURATION;
3658
+ const activeText = isError && errorText ? errorText : supportingText;
3659
+ const isOverLimit = maxLength !== void 0 && (charCount != null ? charCount : 0) > maxLength;
3660
+ const counterColor = isOverLimit ? TF_COLORS.error : TF_COLORS.onSurfaceVariant;
3661
+ if (!activeText && maxLength === void 0) return null;
3662
+ return /* @__PURE__ */ jsxs(
3663
+ m.div,
3664
+ {
3665
+ className: "flex items-start justify-between gap-2 mt-1 px-4",
3666
+ initial: { opacity: 0, height: 0 },
3667
+ animate: { opacity: 1, height: "auto" },
3668
+ exit: { opacity: 0, height: 0 },
3669
+ transition: { duration, ease: MD3_STANDARD_EASING },
3670
+ children: [
3671
+ /* @__PURE__ */ jsx("div", { id, className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: activeText && /* @__PURE__ */ jsx(
3672
+ AnimatedText,
3673
+ {
3674
+ motionKey: isError && errorText ? `error-${errorText}` : `helper-${supportingText}`,
3675
+ text: activeText,
3676
+ className: `text-xs leading-4 block ${isError ? "text-m3-error" : "text-m3-on-surface-variant"}`,
3677
+ ariaLive: "polite",
3678
+ duration
3679
+ },
3680
+ isError && errorText ? "error" : "helper"
3681
+ ) }) }),
3682
+ maxLength !== void 0 && typeof charCount === "number" && /* @__PURE__ */ jsxs(
3683
+ m.span,
3684
+ {
3685
+ className: "text-xs leading-4 tabular-nums shrink-0",
3686
+ animate: { color: counterColor },
3687
+ transition: { duration, ease: MD3_STANDARD_EASING },
3688
+ "aria-live": "polite",
3689
+ "aria-atomic": "true",
3690
+ children: [
3691
+ charCount,
3692
+ " / ",
3693
+ maxLength
3694
+ ]
3695
+ }
3696
+ )
3697
+ ]
3698
+ }
3699
+ );
3700
+ });
3701
+ SupportingText.displayName = "SupportingText";
3702
+ function AnimatedIconSlot({
3703
+ children,
3704
+ motionKey,
3705
+ duration
3706
+ }) {
3707
+ return /* @__PURE__ */ jsx(
3708
+ m.span,
3709
+ {
3710
+ className: "flex items-center justify-center",
3711
+ initial: { opacity: 0, scale: 0 },
3712
+ animate: { opacity: 1, scale: 1 },
3713
+ exit: { opacity: 0, scale: 0 },
3714
+ transition: { duration, ease: MD3_STANDARD_EASING },
3715
+ children
3716
+ },
3717
+ motionKey
3718
+ );
3719
+ }
3720
+ function EyeOpenIcon() {
3721
+ return /* @__PURE__ */ jsx(
3722
+ "svg",
3723
+ {
3724
+ width: "24",
3725
+ height: "24",
3726
+ viewBox: "0 0 24 24",
3727
+ fill: "currentColor",
3728
+ "aria-hidden": "true",
3729
+ children: /* @__PURE__ */ jsx("path", { d: "M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" })
3730
+ }
3731
+ );
3732
+ }
3733
+ function EyeOffIcon() {
3734
+ return /* @__PURE__ */ jsx(
3735
+ "svg",
3736
+ {
3737
+ width: "24",
3738
+ height: "24",
3739
+ viewBox: "0 0 24 24",
3740
+ fill: "currentColor",
3741
+ "aria-hidden": "true",
3742
+ children: /* @__PURE__ */ jsx("path", { d: "M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z" })
3743
+ }
3744
+ );
3745
+ }
3746
+ function ClearIcon() {
3747
+ return /* @__PURE__ */ jsx(
3748
+ "svg",
3749
+ {
3750
+ width: "20",
3751
+ height: "20",
3752
+ viewBox: "0 0 24 24",
3753
+ fill: "currentColor",
3754
+ "aria-hidden": "true",
3755
+ children: /* @__PURE__ */ jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" })
3756
+ }
3757
+ );
3758
+ }
3759
+ var TrailingIcon = React4.memo(function TrailingIcon2({
3760
+ mode,
3761
+ children,
3762
+ value,
3763
+ showPassword = false,
3764
+ onClear,
3765
+ onPasswordToggle,
3766
+ isError,
3767
+ isDisabled,
3768
+ prefersReduced
3769
+ }) {
3770
+ const duration = prefersReduced ? 0 : MD3_ICON_SWAP_DURATION;
3771
+ const iconColor = isError && !isDisabled ? TF_COLORS.error : TF_COLORS.onSurfaceVariant;
3772
+ const btnClass = "relative flex items-center justify-center w-12 h-12 -mr-1 rounded-full cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-m3-primary)] transition-colors hover:bg-[var(--color-m3-on-surface)]/8 active:bg-[var(--color-m3-on-surface)]/12 disabled:pointer-events-none";
3773
+ if (mode === "clear") {
3774
+ const hasValue = value.length > 0;
3775
+ return /* @__PURE__ */ jsx(AnimatePresence, { children: hasValue && /* @__PURE__ */ jsx(AnimatedIconSlot, { motionKey: "clear", duration, children: /* @__PURE__ */ jsx(
3776
+ "button",
3777
+ {
3778
+ type: "button",
3779
+ "aria-label": "Clear input",
3780
+ onClick: onClear,
3781
+ disabled: isDisabled,
3782
+ tabIndex: isDisabled ? -1 : 0,
3783
+ className: btnClass,
3784
+ style: { color: iconColor },
3785
+ children: /* @__PURE__ */ jsx(ClearIcon, {})
3786
+ }
3787
+ ) }) });
3788
+ }
3789
+ if (mode === "password-toggle") {
3790
+ return /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsx(
3791
+ AnimatedIconSlot,
3792
+ {
3793
+ motionKey: showPassword ? "eye-off" : "eye-on",
3794
+ duration,
3795
+ children: /* @__PURE__ */ jsx(
3796
+ "button",
3797
+ {
3798
+ type: "button",
3799
+ "aria-label": showPassword ? "Hide password" : "Show password",
3800
+ onClick: onPasswordToggle,
3801
+ disabled: isDisabled,
3802
+ tabIndex: isDisabled ? -1 : 0,
3803
+ className: btnClass,
3804
+ style: { color: iconColor },
3805
+ children: showPassword ? /* @__PURE__ */ jsx(EyeOffIcon, {}) : /* @__PURE__ */ jsx(EyeOpenIcon, {})
3806
+ }
3807
+ )
3808
+ }
3809
+ ) });
3810
+ }
3811
+ if (mode === "custom" && children) {
3812
+ return /* @__PURE__ */ jsx(
3813
+ "div",
3814
+ {
3815
+ className: "flex items-center justify-center w-6 h-6 mr-3",
3816
+ style: { color: iconColor },
3817
+ children
3818
+ }
3819
+ );
3820
+ }
3821
+ return null;
3822
+ });
3823
+ TrailingIcon.displayName = "TrailingIcon";
3824
+ var LINE_HEIGHT_PX = 24;
3825
+ var TextFieldComponent = React4.forwardRef(
3826
+ ({
3827
+ // Core
3828
+ variant = "filled",
3829
+ label,
3830
+ value: valueProp,
3831
+ defaultValue = "",
3832
+ onChange,
3833
+ // Input config
3834
+ type = "text",
3835
+ placeholder,
3836
+ name,
3837
+ id: idProp,
3838
+ autoComplete,
3839
+ inputMode,
3840
+ autoResize = false,
3841
+ maxRows,
3842
+ rows = autoResize ? 1 : 2,
3843
+ cols,
3844
+ textDirection,
3845
+ // Validation
3846
+ required = false,
3847
+ noAsterisk = false,
3848
+ error: errorProp = false,
3849
+ errorText,
3850
+ minLength,
3851
+ maxLength,
3852
+ min,
3853
+ max,
3854
+ step,
3855
+ pattern,
3856
+ multiple,
3857
+ // Supporting text
3858
+ supportingText,
3859
+ // Decorators
3860
+ prefixText,
3861
+ suffixText,
3862
+ leadingIcon,
3863
+ trailingIcon,
3864
+ trailingIconMode = "none",
3865
+ // States
3866
+ disabled = false,
3867
+ readOnly = false,
3868
+ noSpinner = false,
3869
+ // Form
3870
+ form,
3871
+ // Accessibility
3872
+ "aria-label": ariaLabel,
3873
+ "aria-describedby": ariaDescribedby,
3874
+ "aria-labelledby": ariaLabelledby,
3875
+ // Layout
3876
+ className,
3877
+ fullWidth = true,
3878
+ dense = false,
3879
+ // ScrollArea
3880
+ scrollAreaType = "hover",
3881
+ // Callbacks
3882
+ onFocus,
3883
+ onBlur,
3884
+ onKeyDown,
3885
+ onKeyUp
3886
+ }, ref) => {
3887
+ var _a;
3888
+ const prefersReduced = (_a = useReducedMotion()) != null ? _a : false;
3889
+ const toolbarContext = useToolbarContext();
3890
+ const isInToolbar = !!toolbarContext;
3891
+ const resolvedDense = isInToolbar ? true : dense;
3892
+ const resolvedLabel = isInToolbar ? void 0 : label;
3893
+ const generatedId = React4.useId();
3894
+ const inputId = idProp != null ? idProp : `tf-${generatedId}`;
3895
+ const supportingId = `${inputId}-supporting`;
3896
+ const isControlled = valueProp !== void 0;
3897
+ const [internalValue, setInternalValue] = React4.useState(defaultValue);
3898
+ const currentValue = isControlled ? valueProp : internalValue;
3899
+ const [isFocused, setIsFocused] = React4.useState(false);
3900
+ const [showPassword, setShowPassword] = React4.useState(false);
3901
+ const resolvedInputType = type === "password" && showPassword ? "text" : type;
3902
+ const [nativeError, setNativeError] = React4.useState("");
3903
+ const [labelWidth, setLabelWidth] = React4.useState(0);
3904
+ const hasValue = currentValue.length > 0;
3905
+ const isFloated = isFocused || hasValue;
3906
+ const isError = errorProp || !!nativeError || maxLength !== void 0 && currentValue.length > maxLength;
3907
+ const containerHeight = resolvedDense ? TF_SIZE.denseHeight : TF_SIZE.height;
3908
+ const showAsterisk = required && !noAsterisk;
3909
+ const inputRef = React4.useRef(null);
3910
+ const labelSpanRef = React4.useRef(null);
3911
+ React4.useLayoutEffect(() => {
3912
+ if (labelSpanRef.current) {
3913
+ setLabelWidth(labelSpanRef.current.offsetWidth);
3914
+ }
3915
+ }, []);
3916
+ React4.useLayoutEffect(() => {
3917
+ if (type !== "textarea" || !inputRef.current) return;
3918
+ const textarea = inputRef.current;
3919
+ if (autoResize) {
3920
+ textarea.style.height = "auto";
3921
+ textarea.style.height = `${textarea.scrollHeight}px`;
3922
+ if (maxRows) {
3923
+ textarea.style.maxHeight = `${maxRows * LINE_HEIGHT_PX}px`;
3924
+ }
3925
+ }
3926
+ textarea.style.overflowY = "hidden";
3927
+ }, [type, autoResize, maxRows, currentValue]);
3928
+ const handleValueChange = React4.useCallback(
3929
+ (newValue) => {
3930
+ var _a2, _b;
3931
+ if (!isControlled) setInternalValue(newValue);
3932
+ setNativeError("");
3933
+ (_b = (_a2 = inputRef.current) == null ? void 0 : _a2.setCustomValidity) == null ? void 0 : _b.call(_a2, "");
3934
+ },
3935
+ [isControlled]
3936
+ );
3937
+ const handleChange = React4.useCallback(
3938
+ (e) => {
3939
+ const newVal = e.target.value;
3940
+ handleValueChange(newVal);
3941
+ onChange == null ? void 0 : onChange(newVal, e);
3942
+ },
3943
+ [handleValueChange, onChange]
3944
+ );
3945
+ const handleFocus = React4.useCallback(
3946
+ (e) => {
3947
+ setIsFocused(true);
3948
+ onFocus == null ? void 0 : onFocus(e);
3949
+ },
3950
+ [onFocus]
3951
+ );
3952
+ const handleBlur = React4.useCallback(
3953
+ (e) => {
3954
+ setIsFocused(false);
3955
+ const el = inputRef.current;
3956
+ if (el && !el.validity.valid) {
3957
+ setNativeError(el.validationMessage);
3958
+ } else {
3959
+ setNativeError("");
3960
+ }
3961
+ onBlur == null ? void 0 : onBlur(e);
3962
+ },
3963
+ [onBlur]
3964
+ );
3965
+ const handleClear = React4.useCallback(() => {
3966
+ var _a2;
3967
+ handleValueChange("");
3968
+ onChange == null ? void 0 : onChange("", {
3969
+ target: { value: "" }
3970
+ });
3971
+ (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
3972
+ }, [handleValueChange, onChange]);
3973
+ const handlePasswordToggle = React4.useCallback(() => {
3974
+ var _a2;
3975
+ setShowPassword((prev) => !prev);
3976
+ (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
3977
+ }, []);
3978
+ React4.useImperativeHandle(
3979
+ ref,
3980
+ () => ({
3981
+ focus: () => {
3982
+ var _a2;
3983
+ return (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
3984
+ },
3985
+ blur: () => {
3986
+ var _a2;
3987
+ return (_a2 = inputRef.current) == null ? void 0 : _a2.blur();
3988
+ },
3989
+ select: () => {
3990
+ var _a2, _b;
3991
+ return (_b = (_a2 = inputRef.current) == null ? void 0 : _a2.select) == null ? void 0 : _b.call(_a2);
3992
+ },
3993
+ clear: () => handleValueChange(""),
3994
+ setCustomValidity: (msg) => {
3995
+ var _a2, _b;
3996
+ return (_b = (_a2 = inputRef.current) == null ? void 0 : _a2.setCustomValidity) == null ? void 0 : _b.call(_a2, msg);
3997
+ },
3998
+ checkValidity: () => {
3999
+ var _a2, _b, _c;
4000
+ return (_c = (_b = (_a2 = inputRef.current) == null ? void 0 : _a2.checkValidity) == null ? void 0 : _b.call(_a2)) != null ? _c : true;
4001
+ },
4002
+ reportValidity: () => {
4003
+ var _a2, _b, _c;
4004
+ const el = inputRef.current;
4005
+ const valid = (_b = (_a2 = el == null ? void 0 : el.reportValidity) == null ? void 0 : _a2.call(el)) != null ? _b : true;
4006
+ if (!valid) setNativeError((_c = el == null ? void 0 : el.validationMessage) != null ? _c : "");
4007
+ return valid;
4008
+ },
4009
+ getValue: () => currentValue,
4010
+ getInputElement: () => inputRef.current
4011
+ }),
4012
+ [currentValue, handleValueChange]
4013
+ );
4014
+ const hasSupporting = !!(supportingText || errorText || maxLength !== void 0);
4015
+ const computedDescribedby = [hasSupporting ? supportingId : "", ariaDescribedby != null ? ariaDescribedby : ""].filter(Boolean).join(" ") || void 0;
4016
+ const inputClass = cn(
4017
+ "bg-transparent outline-none w-full",
4018
+ isInToolbar ? "self-center" : "self-end",
4019
+ "text-base leading-6 text-m3-on-surface",
4020
+ "caret-[var(--color-m3-primary)]",
4021
+ "placeholder:text-m3-on-surface-variant/60",
4022
+ noSpinner && type === "number" && "[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none",
4023
+ disabled && "cursor-not-allowed"
4024
+ );
4025
+ const inputProps = {
4026
+ id: inputId,
4027
+ name,
4028
+ value: currentValue,
4029
+ placeholder: isFloated ? placeholder != null ? placeholder : "" : "",
4030
+ disabled,
4031
+ readOnly,
4032
+ required,
4033
+ minLength,
4034
+ maxLength,
4035
+ autoComplete,
4036
+ inputMode,
4037
+ form,
4038
+ "aria-label": ariaLabel,
4039
+ "aria-labelledby": ariaLabelledby,
4040
+ "aria-describedby": computedDescribedby,
4041
+ "aria-invalid": isError || void 0,
4042
+ "aria-required": required || void 0,
4043
+ onChange: handleChange,
4044
+ onFocus: handleFocus,
4045
+ onBlur: handleBlur,
4046
+ onKeyDown,
4047
+ onKeyUp
4048
+ };
4049
+ const hasLeading = !!leadingIcon;
4050
+ const paddingStart = "pl-4";
4051
+ const paddingEnd = "pr-4";
4052
+ const containerClass = cn(
4053
+ "relative flex items-center",
4054
+ variant === "filled" ? isInToolbar ? "bg-transparent" : "bg-[var(--color-m3-surface-container-highest)] rounded-tl-sm rounded-tr-sm" : "bg-transparent rounded-sm",
4055
+ fullWidth ? "w-full" : "w-fit"
4056
+ );
4057
+ const innerClass = cn(
4058
+ "relative flex flex-col flex-1 min-w-0 h-full",
4059
+ paddingStart,
4060
+ paddingEnd,
4061
+ isInToolbar ? "justify-center" : variant === "filled" ? resolvedDense ? "pt-5 pb-2" : "pt-6 pb-2" : resolvedDense ? "py-3" : "py-4"
4062
+ );
4063
+ const wrapperClass = cn(
4064
+ "group/tf inline-flex flex-col",
4065
+ fullWidth ? "w-full" : "w-fit",
4066
+ disabled && "opacity-[0.38] pointer-events-none",
4067
+ className
4068
+ );
4069
+ const containerHeightClass = resolvedDense ? "h-12" : "h-14";
4070
+ return /* @__PURE__ */ jsx(LazyMotion, { features: domMax, strict: true, children: /* @__PURE__ */ jsxs("div", { className: wrapperClass, children: [
4071
+ /* @__PURE__ */ jsxs(
4072
+ "div",
4073
+ {
4074
+ className: cn(
4075
+ containerClass,
4076
+ type !== "textarea" && containerHeightClass
4077
+ ),
4078
+ children: [
4079
+ variant === "outlined" && !isInToolbar && /* @__PURE__ */ jsx(
4080
+ OutlineContainer,
4081
+ {
4082
+ isFloated,
4083
+ isFocused,
4084
+ isError,
4085
+ isDisabled: disabled,
4086
+ isHovered: false,
4087
+ labelWidth,
4088
+ prefersReduced
4089
+ }
4090
+ ),
4091
+ variant === "filled" && !isInToolbar && /* @__PURE__ */ jsx(
4092
+ "div",
4093
+ {
4094
+ "aria-hidden": "true",
4095
+ className: cn(
4096
+ "absolute inset-0 pointer-events-none rounded-[inherit] bg-m3-on-surface",
4097
+ "opacity-0 transition-opacity duration-150",
4098
+ "group-hover/tf:opacity-[0.08]"
4099
+ )
4100
+ }
4101
+ ),
4102
+ leadingIcon && /* @__PURE__ */ jsx(LeadingIcon, { isError, isDisabled: disabled, children: leadingIcon }),
4103
+ /* @__PURE__ */ jsxs("div", { className: innerClass, children: [
4104
+ resolvedLabel && /* @__PURE__ */ jsx(
4105
+ FloatingLabel,
4106
+ {
4107
+ text: resolvedLabel,
4108
+ isFloated,
4109
+ isFocused,
4110
+ isError,
4111
+ isDisabled: disabled,
4112
+ variant,
4113
+ containerHeight,
4114
+ prefersReduced,
4115
+ showAsterisk,
4116
+ htmlFor: inputId,
4117
+ labelRef: labelSpanRef,
4118
+ hasLeading
4119
+ }
4120
+ ),
4121
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center w-full", children: [
4122
+ prefixText && /* @__PURE__ */ jsx(
4123
+ PrefixSuffix,
4124
+ {
4125
+ text: prefixText,
4126
+ type: "prefix",
4127
+ visible: isFloated,
4128
+ prefersReduced
4129
+ }
4130
+ ),
4131
+ type === "textarea" ? /* @__PURE__ */ jsx(
4132
+ ScrollArea,
4133
+ {
4134
+ type: scrollAreaType,
4135
+ className: "w-full flex-1",
4136
+ style: {
4137
+ height: !autoResize ? rows * LINE_HEIGHT_PX : void 0,
4138
+ maxHeight: autoResize && maxRows ? maxRows * LINE_HEIGHT_PX : void 0
4139
+ },
4140
+ viewportClassName: "max-h-[inherit]",
4141
+ children: /* @__PURE__ */ jsx(
4142
+ "textarea",
4143
+ __spreadValues({
4144
+ ref: inputRef,
4145
+ rows,
4146
+ cols,
4147
+ className: cn(
4148
+ inputClass,
4149
+ "resize-none mt-2",
4150
+ autoResize ? "h-auto" : "h-full"
4151
+ ),
4152
+ style: { direction: textDirection || void 0 }
4153
+ }, inputProps)
4154
+ )
4155
+ }
4156
+ ) : /* @__PURE__ */ jsx(
4157
+ "input",
4158
+ __spreadValues({
4159
+ ref: inputRef,
4160
+ type: type === "password" ? resolvedInputType : type,
4161
+ min,
4162
+ max,
4163
+ step,
4164
+ pattern,
4165
+ multiple,
4166
+ className: inputClass,
4167
+ style: { direction: textDirection || void 0 }
4168
+ }, inputProps)
4169
+ ),
4170
+ suffixText && /* @__PURE__ */ jsx(
4171
+ PrefixSuffix,
4172
+ {
4173
+ text: suffixText,
4174
+ type: "suffix",
4175
+ visible: isFloated,
4176
+ prefersReduced
4177
+ }
4178
+ )
4179
+ ] })
4180
+ ] }),
4181
+ (trailingIconMode !== "none" || trailingIcon) && /* @__PURE__ */ jsx(
4182
+ TrailingIcon,
4183
+ {
4184
+ mode: trailingIcon ? "custom" : trailingIconMode,
4185
+ value: currentValue,
4186
+ showPassword,
4187
+ onClear: handleClear,
4188
+ onPasswordToggle: handlePasswordToggle,
4189
+ isError,
4190
+ isDisabled: disabled,
4191
+ prefersReduced,
4192
+ children: trailingIcon
4193
+ }
4194
+ ),
4195
+ variant === "filled" && !isInToolbar && /* @__PURE__ */ jsx(
4196
+ ActiveIndicator,
4197
+ {
4198
+ isFocused,
4199
+ isError,
4200
+ isDisabled: disabled,
4201
+ isHovered: false,
4202
+ prefersReduced
4203
+ }
4204
+ )
4205
+ ]
4206
+ }
4207
+ ),
4208
+ hasSupporting && /* @__PURE__ */ jsx(
4209
+ SupportingText,
4210
+ {
4211
+ supportingText,
4212
+ errorText: errorText != null ? errorText : nativeError || void 0,
4213
+ isError,
4214
+ charCount: maxLength !== void 0 ? currentValue.length : void 0,
4215
+ maxLength,
4216
+ id: supportingId,
4217
+ prefersReduced
4218
+ }
4219
+ )
4220
+ ] }) });
4221
+ }
4222
+ );
4223
+ TextFieldComponent.displayName = "TextField";
4224
+ var TextField = React4.memo(TextFieldComponent);
4225
+
4226
+ export { Checkbox, Chip, RadioButton, RadioGroup, RangeSlider, Slider, SliderColors, SliderThumb, SliderTokens, Switch, SwitchColors, SwitchTokens, TextField, TriStateCheckbox };
4227
+ //# sourceMappingURL=forms.mjs.map
4228
+ //# sourceMappingURL=forms.mjs.map