@justanarthur/modo-ui 0.0.0-stage → 0.1.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 (172) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSE.beui +21 -0
  3. package/LICENSE.fluid-functionalism +21 -0
  4. package/LICENSE.motion-primitives +21 -0
  5. package/LICENSE.sileo +21 -0
  6. package/README.md +608 -2
  7. package/dist/base.css +994 -0
  8. package/dist/blocks/sidebar-app/index.d.ts +34 -0
  9. package/dist/blocks/sidebar-app/index.js +34 -0
  10. package/dist/components/badge/index.d.ts +59 -0
  11. package/dist/components/badge/index.js +18 -0
  12. package/dist/components/button/index.d.ts +59 -0
  13. package/dist/components/button/index.js +16 -0
  14. package/dist/components/card/index.d.ts +141 -0
  15. package/dist/components/card/index.js +44 -0
  16. package/dist/components/inputs/checkbox-group/index.d.ts +50 -0
  17. package/dist/components/inputs/checkbox-group/index.js +23 -0
  18. package/dist/components/inputs/color-picker/index.d.ts +137 -0
  19. package/dist/components/inputs/color-picker/index.js +37 -0
  20. package/dist/components/inputs/combobox/index.d.ts +161 -0
  21. package/dist/components/inputs/combobox/index.js +41 -0
  22. package/dist/components/inputs/date-time/calendar/index.d.ts +45 -0
  23. package/dist/components/inputs/date-time/calendar/index.js +20 -0
  24. package/dist/components/inputs/date-time/date-picker/index.d.ts +82 -0
  25. package/dist/components/inputs/date-time/date-picker/index.js +34 -0
  26. package/dist/components/inputs/date-time/date-range-picker/index.d.ts +64 -0
  27. package/dist/components/inputs/date-time/date-range-picker/index.js +33 -0
  28. package/dist/components/inputs/date-time/duration-picker/index.d.ts +57 -0
  29. package/dist/components/inputs/date-time/duration-picker/index.js +33 -0
  30. package/dist/components/inputs/date-time/month-picker/index.d.ts +57 -0
  31. package/dist/components/inputs/date-time/month-picker/index.js +32 -0
  32. package/dist/components/inputs/date-time/time-picker/index.d.ts +59 -0
  33. package/dist/components/inputs/date-time/time-picker/index.js +33 -0
  34. package/dist/components/inputs/input/index.d.ts +84 -0
  35. package/dist/components/inputs/input/index.js +20 -0
  36. package/dist/components/inputs/input-copy/index.d.ts +44 -0
  37. package/dist/components/inputs/input-copy/index.js +18 -0
  38. package/dist/components/inputs/input-message/index.d.ts +125 -0
  39. package/dist/components/inputs/input-message/index.js +27 -0
  40. package/dist/components/inputs/radio-group/index.d.ts +58 -0
  41. package/dist/components/inputs/radio-group/index.js +22 -0
  42. package/dist/components/inputs/search/index.d.ts +83 -0
  43. package/dist/components/inputs/search/index.js +28 -0
  44. package/dist/components/inputs/select/index.d.ts +105 -0
  45. package/dist/components/inputs/select/index.js +37 -0
  46. package/dist/components/inputs/slider/index.d.ts +108 -0
  47. package/dist/components/inputs/slider/index.js +16 -0
  48. package/dist/components/inputs/switch/index.d.ts +43 -0
  49. package/dist/components/inputs/switch/index.js +16 -0
  50. package/dist/components/navigation/accordion/index.d.ts +117 -0
  51. package/dist/components/navigation/accordion/index.js +30 -0
  52. package/dist/components/navigation/sidebar/index.d.ts +388 -0
  53. package/dist/components/navigation/sidebar/index.js +101 -0
  54. package/dist/components/navigation/tabs/index.d.ts +70 -0
  55. package/dist/components/navigation/tabs/index.js +25 -0
  56. package/dist/components/navigation/tabs-subtle/index.d.ts +73 -0
  57. package/dist/components/navigation/tabs-subtle/index.js +23 -0
  58. package/dist/components/overlays/alert-dialog/index.d.ts +67 -0
  59. package/dist/components/overlays/alert-dialog/index.js +24 -0
  60. package/dist/components/overlays/command/index.d.ts +121 -0
  61. package/dist/components/overlays/command/index.js +39 -0
  62. package/dist/components/overlays/context-menu/index.d.ts +97 -0
  63. package/dist/components/overlays/context-menu/index.js +32 -0
  64. package/dist/components/overlays/dialog/index.d.ts +130 -0
  65. package/dist/components/overlays/dialog/index.js +39 -0
  66. package/dist/components/overlays/dropdown/index.d.ts +222 -0
  67. package/dist/components/overlays/dropdown/index.js +47 -0
  68. package/dist/components/overlays/popover/index.d.ts +87 -0
  69. package/dist/components/overlays/popover/index.js +28 -0
  70. package/dist/components/overlays/preview-card/index.d.ts +75 -0
  71. package/dist/components/overlays/preview-card/index.js +21 -0
  72. package/dist/components/overlays/sheet/index.d.ts +67 -0
  73. package/dist/components/overlays/sheet/index.js +26 -0
  74. package/dist/components/overlays/toast/index.d.ts +107 -0
  75. package/dist/components/overlays/toast/index.js +23 -0
  76. package/dist/components/overlays/tooltip/index.d.ts +75 -0
  77. package/dist/components/overlays/tooltip/index.js +19 -0
  78. package/dist/components/table/index.d.ts +44 -0
  79. package/dist/components/table/index.js +28 -0
  80. package/dist/components/thinking-indicator/index.d.ts +32 -0
  81. package/dist/components/thinking-indicator/index.js +15 -0
  82. package/dist/index.d.ts +3926 -0
  83. package/dist/index.js +513 -0
  84. package/dist/preset.d.ts +104 -0
  85. package/dist/preset.js +124 -0
  86. package/dist/primitives/code/index.d.ts +23 -0
  87. package/dist/primitives/code/index.js +12 -0
  88. package/dist/primitives/craft/index.d.ts +31 -0
  89. package/dist/primitives/craft/index.js +13 -0
  90. package/dist/primitives/fluid-hover/index.d.ts +233 -0
  91. package/dist/primitives/fluid-hover/index.js +27 -0
  92. package/dist/primitives/morph/index.d.ts +127 -0
  93. package/dist/primitives/morph/index.js +21 -0
  94. package/dist/primitives/motion/index.d.ts +115 -0
  95. package/dist/primitives/motion/index.js +20 -0
  96. package/dist/primitives/scroll-area/index.d.ts +41 -0
  97. package/dist/primitives/scroll-area/index.js +13 -0
  98. package/dist/primitives/sizes/index.d.ts +128 -0
  99. package/dist/primitives/sizes/index.js +22 -0
  100. package/dist/primitives/surface/index.d.ts +69 -0
  101. package/dist/primitives/surface/index.js +18 -0
  102. package/dist/shared/components-0f0as45n.js +17 -0
  103. package/dist/shared/components-40j34hdb.js +986 -0
  104. package/dist/shared/components-4hh0gm0v.js +292 -0
  105. package/dist/shared/components-4xxddc44.js +15 -0
  106. package/dist/shared/components-4xxtggzp.js +110 -0
  107. package/dist/shared/components-54nf2x99.js +293 -0
  108. package/dist/shared/components-5pkcfprd.js +232 -0
  109. package/dist/shared/components-5vxtcp88.js +9 -0
  110. package/dist/shared/components-5zry9te1.js +152 -0
  111. package/dist/shared/components-61ckf42z.js +220 -0
  112. package/dist/shared/components-6hzwfmfd.js +58 -0
  113. package/dist/shared/components-6phwsry6.js +361 -0
  114. package/dist/shared/components-6t6nr33r.js +86 -0
  115. package/dist/shared/components-6y1th6k4.js +111 -0
  116. package/dist/shared/components-74c20v2f.js +32 -0
  117. package/dist/shared/components-7cqq1sqy.js +193 -0
  118. package/dist/shared/components-7gv95yfy.js +496 -0
  119. package/dist/shared/components-7nww01w4.js +375 -0
  120. package/dist/shared/components-7zp99w5d.js +62 -0
  121. package/dist/shared/components-8hk9ve3r.js +1200 -0
  122. package/dist/shared/components-8hzbsfbd.js +123 -0
  123. package/dist/shared/components-9p8s8n5q.js +171 -0
  124. package/dist/shared/components-aaq3yk4f.js +304 -0
  125. package/dist/shared/components-bf2d9k02.js +484 -0
  126. package/dist/shared/components-bkvb6nem.js +63 -0
  127. package/dist/shared/components-brnzrp2t.js +262 -0
  128. package/dist/shared/components-csxz9f52.js +42 -0
  129. package/dist/shared/components-de8xsm9y.js +22 -0
  130. package/dist/shared/components-dg0w8g5p.js +26 -0
  131. package/dist/shared/components-dphg9gjr.js +42 -0
  132. package/dist/shared/components-e46kf6cp.js +298 -0
  133. package/dist/shared/components-ed7cmssr.js +105 -0
  134. package/dist/shared/components-ess3ch6w.js +137 -0
  135. package/dist/shared/components-gjfrycb5.js +984 -0
  136. package/dist/shared/components-gndm6am4.js +128 -0
  137. package/dist/shared/components-hjkrv00c.js +191 -0
  138. package/dist/shared/components-hzfp4tjb.js +270 -0
  139. package/dist/shared/components-j65qt1mq.js +719 -0
  140. package/dist/shared/components-jw16snbn.js +19 -0
  141. package/dist/shared/components-jxgznftn.js +146 -0
  142. package/dist/shared/components-kf2ehztc.js +216 -0
  143. package/dist/shared/components-kpbcms10.js +9 -0
  144. package/dist/shared/components-m5br529r.js +141 -0
  145. package/dist/shared/components-nn6xezy2.js +1828 -0
  146. package/dist/shared/components-nnnd2xef.js +9 -0
  147. package/dist/shared/components-p2vmyfe1.js +345 -0
  148. package/dist/shared/components-peg09pzs.js +79 -0
  149. package/dist/shared/components-r22vtgqc.js +45 -0
  150. package/dist/shared/components-r2gke5p6.js +1759 -0
  151. package/dist/shared/components-r43mqnd6.js +160 -0
  152. package/dist/shared/components-r4jt6hgd.js +58 -0
  153. package/dist/shared/components-rc63qsy6.js +159 -0
  154. package/dist/shared/components-s3yfvfkr.js +149 -0
  155. package/dist/shared/components-s5pkbjx1.js +445 -0
  156. package/dist/shared/components-tad5h7ta.js +346 -0
  157. package/dist/shared/components-taxmgbnh.js +120 -0
  158. package/dist/shared/components-tm88ghva.js +215 -0
  159. package/dist/shared/components-v7es8aan.js +144 -0
  160. package/dist/shared/components-vb033rns.js +28 -0
  161. package/dist/shared/components-vc1k7yv2.js +30 -0
  162. package/dist/shared/components-vktws5r6.js +667 -0
  163. package/dist/shared/components-x2xyckd5.js +198 -0
  164. package/dist/shared/components-x3rz4cyn.js +61 -0
  165. package/dist/shared/components-xszag63d.js +141 -0
  166. package/dist/shared/components-y3cqdrgy.js +291 -0
  167. package/dist/shared/components-zvb11ker.js +288 -0
  168. package/dist/shared/components-zx2hr57f.js +124 -0
  169. package/dist/styles.css +1949 -0
  170. package/dist/tailwind.css +219 -0
  171. package/dist/tokens.css +345 -0
  172. package/package.json +102 -3
@@ -0,0 +1,1200 @@
1
+ import {
2
+ useShape
3
+ } from "./components-7zp99w5d.js";
4
+ import {
5
+ useControllableState
6
+ } from "./components-dg0w8g5p.js";
7
+ import {
8
+ spring
9
+ } from "./components-vc1k7yv2.js";
10
+ import {
11
+ useSizeVariant
12
+ } from "./components-x3rz4cyn.js";
13
+ import {
14
+ cn
15
+ } from "./components-0f0as45n.js";
16
+
17
+ // components/inputs/slider/index.tsx
18
+ import { Slider as SliderPrimitive } from "@base-ui/react/slider";
19
+ import { AnimatePresence, animate, motion, useMotionValue, useTransform } from "motion/react";
20
+ import {
21
+ forwardRef,
22
+ useCallback,
23
+ useEffect,
24
+ useLayoutEffect,
25
+ useMemo,
26
+ useRef,
27
+ useState
28
+ } from "react";
29
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
30
+ var THUMB_SIZE = 20;
31
+ var THUMB_SIZE_REST = 16;
32
+ var TRACK_BG_HEIGHT = 18;
33
+ var DOT_SIZE = 4;
34
+ var PIP_SIZE = 5;
35
+ var TRACK_INSET = (THUMB_SIZE - TRACK_BG_HEIGHT) / 2;
36
+ function valueToPixel(v, min, max, trackWidth) {
37
+ if (max === min)
38
+ return 0;
39
+ const usable = trackWidth - THUMB_SIZE;
40
+ return (v - min) / (max - min) * usable;
41
+ }
42
+ function nearestStepIndex(v, steps) {
43
+ let idx = 0;
44
+ for (let i = 1;i < steps.length; i++) {
45
+ if (Math.abs(steps[i] - v) < Math.abs(steps[idx] - v))
46
+ idx = i;
47
+ }
48
+ return idx;
49
+ }
50
+ function pixelToValue(px, min, max, step, trackWidth, stepValues = null) {
51
+ const usable = trackWidth - THUMB_SIZE;
52
+ if (usable <= 0)
53
+ return min;
54
+ const raw = px / usable * (max - min) + min;
55
+ if (stepValues)
56
+ return stepValues[nearestStepIndex(raw, stepValues)];
57
+ const snapped = Math.round((raw - min) / step) * step + min;
58
+ return Math.max(min, Math.min(max, snapped));
59
+ }
60
+ function toRadixValue(value) {
61
+ return Array.isArray(value) ? value : [value];
62
+ }
63
+ function ValueDisplay({
64
+ values,
65
+ editingIndex,
66
+ onStartEdit,
67
+ onCommitEdit,
68
+ onCancelEdit,
69
+ min,
70
+ max,
71
+ step,
72
+ stepValues,
73
+ formatValue,
74
+ label,
75
+ isRange,
76
+ isInteracting
77
+ }) {
78
+ const shape = useShape();
79
+ const [inputValue, setInputValue] = useState("");
80
+ const inputRef = useRef(null);
81
+ useEffect(() => {
82
+ if (editingIndex !== null) {
83
+ setInputValue(String(values[editingIndex]));
84
+ requestAnimationFrame(() => inputRef.current?.select());
85
+ }
86
+ }, [editingIndex]);
87
+ const commitEdit = useCallback((index) => {
88
+ const parsed = parseFloat(inputValue);
89
+ if (!isNaN(parsed)) {
90
+ const clamped = Math.max(min, Math.min(max, parsed));
91
+ const snapped = stepValues ? stepValues[nearestStepIndex(clamped, stepValues)] : Math.round((clamped - min) / step) * step + min;
92
+ onCommitEdit(index, snapped);
93
+ } else {
94
+ onCancelEdit();
95
+ }
96
+ }, [inputValue, min, max, step, stepValues, onCommitEdit, onCancelEdit]);
97
+ const renderValue = (index) => {
98
+ if (editingIndex === index) {
99
+ return /* @__PURE__ */ jsxs("span", {
100
+ className: "inline-grid text-body",
101
+ children: [
102
+ /* @__PURE__ */ jsxs("span", {
103
+ className: "col-start-1 row-start-1 invisible weight-medium",
104
+ "aria-hidden": "true",
105
+ children: [
106
+ label ? `${label}: ` : "",
107
+ formatValue(max)
108
+ ]
109
+ }),
110
+ /* @__PURE__ */ jsxs("span", {
111
+ className: "col-start-1 row-start-1 flex items-center gap-1",
112
+ children: [
113
+ label && /* @__PURE__ */ jsxs("span", {
114
+ className: "text-muted-foreground",
115
+ children: [
116
+ label,
117
+ ":"
118
+ ]
119
+ }),
120
+ /* @__PURE__ */ jsx("input", {
121
+ ref: inputRef,
122
+ type: "number",
123
+ value: inputValue,
124
+ min,
125
+ max,
126
+ step: stepValues ? "any" : step,
127
+ onChange: (e) => setInputValue(e.target.value),
128
+ onBlur: () => commitEdit(index),
129
+ onKeyDown: (e) => {
130
+ if (e.key === "Enter")
131
+ commitEdit(index);
132
+ if (e.key === "Escape")
133
+ onCancelEdit();
134
+ },
135
+ "aria-label": `Edit slider value${isRange ? index === 0 ? " (start)" : " (end)" : ""}`,
136
+ className: cn("w-[5ch] bg-transparent text-foreground outline-none border-b border-border text-center", shape.input, "weight-medium")
137
+ })
138
+ ]
139
+ })
140
+ ]
141
+ });
142
+ }
143
+ return /* @__PURE__ */ jsx("span", {
144
+ className: "cursor-text select-none",
145
+ onClick: () => onStartEdit(index),
146
+ children: formatValue(values[index])
147
+ });
148
+ };
149
+ const widestValue = isRange ? `${label ? `${label}: ` : ""}${formatValue(max)} — ${formatValue(max)}` : `${label ? `${label}: ` : ""}${formatValue(max)}`;
150
+ return /* @__PURE__ */ jsxs("span", {
151
+ className: cn("inline-grid shrink-0 text-body leading-none text-muted-foreground transition-[font-variation-settings] duration-100", "tabular-nums", isInteracting ? "weight-medium" : "weight-normal"),
152
+ children: [
153
+ /* @__PURE__ */ jsx("span", {
154
+ className: "col-start-1 row-start-1 invisible whitespace-nowrap weight-medium",
155
+ "aria-hidden": "true",
156
+ children: widestValue
157
+ }),
158
+ /* @__PURE__ */ jsxs("span", {
159
+ className: "col-start-1 row-start-1 whitespace-nowrap",
160
+ children: [
161
+ label && editingIndex === null && /* @__PURE__ */ jsxs("span", {
162
+ className: "text-muted-foreground",
163
+ children: [
164
+ label,
165
+ ": "
166
+ ]
167
+ }),
168
+ isRange ? /* @__PURE__ */ jsxs(Fragment, {
169
+ children: [
170
+ renderValue(0),
171
+ /* @__PURE__ */ jsx("span", {
172
+ className: "mx-1 text-muted-foreground/50",
173
+ children: "—"
174
+ }),
175
+ renderValue(1)
176
+ ]
177
+ }) : renderValue(0)
178
+ ]
179
+ })
180
+ ]
181
+ });
182
+ }
183
+ function TooltipValue({ value, formatValue, motionX }) {
184
+ const shape = useShape();
185
+ const tooltipX = useTransform(motionX, (x) => x + THUMB_SIZE / 2);
186
+ return /* @__PURE__ */ jsx(motion.div, {
187
+ className: "absolute -translate-x-1/2 pointer-events-none z-20",
188
+ style: {
189
+ x: tooltipX,
190
+ top: -16
191
+ },
192
+ initial: { opacity: 0, y: 4 },
193
+ animate: { opacity: 1, y: 0 },
194
+ exit: { opacity: 0, y: 4, transition: spring.fast.exit },
195
+ transition: spring.fast,
196
+ children: /* @__PURE__ */ jsx("span", {
197
+ className: cn("text-caption text-background tabular-nums whitespace-nowrap bg-foreground px-2 py-1", shape.bg, "weight-medium"),
198
+ children: formatValue(value)
199
+ })
200
+ });
201
+ }
202
+ var CompactSlider = forwardRef(({
203
+ value,
204
+ onChange,
205
+ min: minProp = 0,
206
+ max: maxProp = 100,
207
+ step = 1,
208
+ steps,
209
+ showSteps = false,
210
+ showValue = true,
211
+ valuePosition = "left",
212
+ formatValue = String,
213
+ label,
214
+ disabled = false,
215
+ trackClassName,
216
+ trackStyle,
217
+ fillClassName,
218
+ fillStyle,
219
+ hideFill = false,
220
+ thumbColor,
221
+ thumbBorderColor,
222
+ className,
223
+ ...props
224
+ }, ref) => {
225
+ const isRange = Array.isArray(value);
226
+ const values = toRadixValue(value);
227
+ const shape = useShape();
228
+ const stepsKey = steps ? steps.join(",") : "";
229
+ const stepValues = useMemo(() => {
230
+ if (!stepsKey)
231
+ return null;
232
+ const parsed = Array.from(new Set(stepsKey.split(",").map(Number))).sort((a, b) => a - b);
233
+ return parsed.length > 1 ? parsed : null;
234
+ }, [stepsKey]);
235
+ const min = stepValues ? stepValues[0] : minProp;
236
+ const max = stepValues ? stepValues[stepValues.length - 1] : maxProp;
237
+ const trackRef = useRef(null);
238
+ const trackWidthRef = useRef(0);
239
+ const dragging = useRef(false);
240
+ const activeDragThumb = useRef(0);
241
+ const valuesRef = useRef(values);
242
+ const minRef = useRef(min);
243
+ const maxRef = useRef(max);
244
+ valuesRef.current = values;
245
+ minRef.current = min;
246
+ maxRef.current = max;
247
+ const [isHovered, setIsHovered] = useState(false);
248
+ const [isPressed, setIsPressed] = useState(false);
249
+ const [editingIndex, setEditingIndex] = useState(null);
250
+ const [hoverPreview, setHoverPreview] = useState(null);
251
+ const [focusedThumb, setFocusedThumb] = useState(null);
252
+ const [showHoverTooltip, setShowHoverTooltip] = useState(false);
253
+ const hoverDelayRef = useRef(null);
254
+ useEffect(() => {
255
+ if (isHovered) {
256
+ hoverDelayRef.current = setTimeout(() => setShowHoverTooltip(true), 100);
257
+ } else {
258
+ if (hoverDelayRef.current)
259
+ clearTimeout(hoverDelayRef.current);
260
+ setShowHoverTooltip(false);
261
+ }
262
+ return () => {
263
+ if (hoverDelayRef.current)
264
+ clearTimeout(hoverDelayRef.current);
265
+ };
266
+ }, [isHovered]);
267
+ const motionX0 = useMotionValue(0);
268
+ const motionX1 = useMotionValue(0);
269
+ const fillLeft = useTransform(motionX0, (x) => isRange ? x + THUMB_SIZE / 2 - TRACK_INSET : 0);
270
+ const fillWidthSingle = useTransform(motionX0, (x) => x + THUMB_SIZE / 2 - TRACK_INSET);
271
+ const fillWidthRange = useTransform([motionX0, motionX1], ([x0, x1]) => x1 - x0);
272
+ const fillWidth = isRange ? fillWidthRange : fillWidthSingle;
273
+ const stepDotsMaskSingle = useTransform(motionX0, (x) => {
274
+ const edge = x + THUMB_SIZE / 2;
275
+ return `linear-gradient(to right, transparent ${edge}px, black ${edge + 2}px)`;
276
+ });
277
+ const stepDotsMaskRange = useTransform([motionX0, motionX1], ([x0, x1]) => {
278
+ const left = x0 + THUMB_SIZE / 2;
279
+ const right = x1 + THUMB_SIZE / 2;
280
+ return `linear-gradient(to right, black ${left - 2}px, transparent ${left}px, transparent ${right}px, black ${right + 2}px)`;
281
+ });
282
+ const stepDotsMask = isRange ? stepDotsMaskRange : stepDotsMaskSingle;
283
+ const computeHoverPreview = useCallback((cursorX, trackWidth) => {
284
+ const usable = trackWidth - THUMB_SIZE;
285
+ const rawPx = cursorX - THUMB_SIZE / 2;
286
+ const clampedPx = Math.max(0, Math.min(usable, rawPx));
287
+ const rawVal = usable > 0 ? clampedPx / usable * (max - min) + min : min;
288
+ const snappedVal = stepValues ? stepValues[nearestStepIndex(rawVal, stepValues)] : Math.max(min, Math.min(max, Math.round((rawVal - min) / step) * step + min));
289
+ const snappedPercent = max === min ? 0 : (snappedVal - min) / (max - min);
290
+ const snappedX = THUMB_SIZE / 2 + snappedPercent * usable;
291
+ const c0 = motionX0.get() + THUMB_SIZE / 2;
292
+ const c1 = motionX1.get() + THUMB_SIZE / 2;
293
+ const nearestIdx = isRange ? Math.abs(snappedX - c0) <= Math.abs(snappedX - c1) ? 0 : 1 : 0;
294
+ const nearest = nearestIdx === 0 ? c0 : c1;
295
+ const edgeX = snappedVal === min ? 0 : snappedVal === max ? trackWidth : snappedX;
296
+ const left = Math.min(nearest, edgeX);
297
+ const width = Math.abs(edgeX - nearest);
298
+ setHoverPreview({ left, width, snappedValue: snappedVal, cursorX: snappedX });
299
+ }, [min, max, step, stepValues, isRange, motionX0, motionX1]);
300
+ const initialSyncDone = useRef(false);
301
+ const [ready, setReady] = useState(false);
302
+ useLayoutEffect(() => {
303
+ const el = trackRef.current;
304
+ if (!el || initialSyncDone.current)
305
+ return;
306
+ const w = el.offsetWidth;
307
+ trackWidthRef.current = w;
308
+ const px0 = valueToPixel(values[0], min, max, w);
309
+ motionX0.set(px0);
310
+ if (isRange && values[1] !== undefined) {
311
+ const px1 = valueToPixel(values[1], min, max, w);
312
+ motionX1.set(px1);
313
+ }
314
+ initialSyncDone.current = true;
315
+ setReady(true);
316
+ }, []);
317
+ useEffect(() => {
318
+ const el = trackRef.current;
319
+ if (!el)
320
+ return;
321
+ const ro = new ResizeObserver(([entry]) => {
322
+ const w = entry.contentRect.width;
323
+ trackWidthRef.current = w;
324
+ if (!dragging.current && initialSyncDone.current) {
325
+ const v = valuesRef.current;
326
+ const mn = minRef.current;
327
+ const mx = maxRef.current;
328
+ const px0 = valueToPixel(v[0], mn, mx, w);
329
+ animate(motionX0, px0, spring.moderate);
330
+ if (isRange && v[1] !== undefined) {
331
+ const px1 = valueToPixel(v[1], mn, mx, w);
332
+ animate(motionX1, px1, spring.moderate);
333
+ }
334
+ }
335
+ });
336
+ ro.observe(el);
337
+ return () => ro.disconnect();
338
+ }, [isRange, motionX0, motionX1]);
339
+ const valuesKey = values.join(",");
340
+ useEffect(() => {
341
+ if (!initialSyncDone.current)
342
+ return;
343
+ if (dragging.current)
344
+ return;
345
+ const tw = trackWidthRef.current;
346
+ if (tw <= 0)
347
+ return;
348
+ const v = valuesRef.current;
349
+ const px0 = valueToPixel(v[0], min, max, tw);
350
+ animate(motionX0, px0, spring.moderate);
351
+ if (isRange && v[1] !== undefined) {
352
+ const px1 = valueToPixel(v[1], min, max, tw);
353
+ animate(motionX1, px1, spring.moderate);
354
+ }
355
+ }, [valuesKey, min, max, isRange, motionX0, motionX1]);
356
+ const clampForRange = useCallback((px, thumbIndex) => {
357
+ if (!isRange)
358
+ return px;
359
+ if (thumbIndex === 0) {
360
+ return Math.min(px, motionX1.get() - THUMB_SIZE * 0.5);
361
+ }
362
+ return Math.max(px, motionX0.get() + THUMB_SIZE * 0.5);
363
+ }, [isRange, motionX0, motionX1]);
364
+ const emitChange = useCallback((thumbIndex, newValue) => {
365
+ if (isRange) {
366
+ const newValues = [...values];
367
+ newValues[thumbIndex] = newValue;
368
+ onChange(newValues);
369
+ } else {
370
+ onChange(newValue);
371
+ }
372
+ }, [isRange, values, onChange]);
373
+ const handlePointerDown = useCallback((e) => {
374
+ if (disabled)
375
+ return;
376
+ if (e.pointerType === "mouse" && e.button !== 0)
377
+ return;
378
+ e.preventDefault();
379
+ e.stopPropagation();
380
+ const trackEl = trackRef.current;
381
+ if (!trackEl)
382
+ return;
383
+ const trackRect = trackEl.getBoundingClientRect();
384
+ const layoutWidth = trackEl.offsetWidth;
385
+ if (layoutWidth <= 0 || trackRect.width <= 0)
386
+ return;
387
+ const scale = trackRect.width / layoutWidth;
388
+ const localX = (e.clientX - trackRect.left) / scale - THUMB_SIZE / 2;
389
+ const clamped = Math.max(0, Math.min(layoutWidth - THUMB_SIZE, localX));
390
+ if (isRange) {
391
+ const dist0 = Math.abs(clamped - motionX0.get());
392
+ const dist1 = Math.abs(clamped - motionX1.get());
393
+ activeDragThumb.current = dist0 <= dist1 ? 0 : 1;
394
+ } else {
395
+ activeDragThumb.current = 0;
396
+ }
397
+ dragging.current = true;
398
+ setIsPressed(true);
399
+ const motionX = activeDragThumb.current === 0 ? motionX0 : motionX1;
400
+ const snappedValue = pixelToValue(clamped, min, max, step, layoutWidth, stepValues);
401
+ const snappedPx = valueToPixel(snappedValue, min, max, layoutWidth);
402
+ const finalPx = clampForRange(snappedPx, activeDragThumb.current);
403
+ animate(motionX, finalPx, spring.moderate);
404
+ const finalValue = pixelToValue(finalPx, min, max, step, layoutWidth, stepValues);
405
+ emitChange(activeDragThumb.current, finalValue);
406
+ e.currentTarget.setPointerCapture(e.pointerId);
407
+ }, [disabled, isRange, min, max, step, stepValues, motionX0, motionX1, clampForRange, emitChange]);
408
+ const handlePointerMove = useCallback((e) => {
409
+ if (!dragging.current)
410
+ return;
411
+ e.stopPropagation();
412
+ const trackEl = trackRef.current;
413
+ if (!trackEl)
414
+ return;
415
+ const trackRect = trackEl.getBoundingClientRect();
416
+ const layoutWidth = trackEl.offsetWidth;
417
+ if (layoutWidth <= 0 || trackRect.width <= 0)
418
+ return;
419
+ const scale = trackRect.width / layoutWidth;
420
+ const localX = (e.clientX - trackRect.left) / scale - THUMB_SIZE / 2;
421
+ const clamped = Math.max(0, Math.min(layoutWidth - THUMB_SIZE, localX));
422
+ const motionX = activeDragThumb.current === 0 ? motionX0 : motionX1;
423
+ const snappedValue = pixelToValue(clamped, min, max, step, layoutWidth, stepValues);
424
+ const snappedPx = valueToPixel(snappedValue, min, max, layoutWidth);
425
+ const finalPx = clampForRange(snappedPx, activeDragThumb.current);
426
+ motionX.set(finalPx);
427
+ const finalValue = pixelToValue(finalPx, min, max, step, layoutWidth, stepValues);
428
+ emitChange(activeDragThumb.current, finalValue);
429
+ }, [min, max, step, stepValues, motionX0, motionX1, clampForRange, emitChange]);
430
+ const handlePointerUp = useCallback(() => {
431
+ if (!dragging.current)
432
+ return;
433
+ dragging.current = false;
434
+ setIsPressed(false);
435
+ setHoverPreview(null);
436
+ const tw = trackWidthRef.current;
437
+ const motionX = activeDragThumb.current === 0 ? motionX0 : motionX1;
438
+ const currentPx = motionX.get();
439
+ const snapped = pixelToValue(currentPx, min, max, step, tw, stepValues);
440
+ const snappedPx = valueToPixel(snapped, min, max, tw);
441
+ animate(motionX, snappedPx, spring.moderate);
442
+ }, [min, max, step, stepValues, motionX0, motionX1]);
443
+ const handleRadixChange = useCallback((newValues) => {
444
+ if (dragging.current)
445
+ return;
446
+ const mapped = stepValues ? newValues.map((i) => stepValues[Math.round(i)]) : newValues;
447
+ if (isRange) {
448
+ onChange(mapped);
449
+ } else {
450
+ onChange(mapped[0]);
451
+ }
452
+ }, [isRange, onChange, stepValues]);
453
+ const handleStartEdit = useCallback((index) => {
454
+ setEditingIndex(index);
455
+ }, []);
456
+ const handleCommitEdit = useCallback((index, v) => {
457
+ emitChange(index, v);
458
+ setEditingIndex(null);
459
+ }, [emitChange]);
460
+ const handleCancelEdit = useCallback(() => {
461
+ setEditingIndex(null);
462
+ }, []);
463
+ const stepDots = useMemo(() => showSteps ? stepValues ? stepValues.map((v) => ({
464
+ value: v,
465
+ percent: max === min ? 0 : (v - min) / (max - min)
466
+ })) : Array.from({ length: Math.round((max - min) / step) + 1 }, (_, i) => {
467
+ const v = min + i * step;
468
+ const percent = (v - min) / (max - min);
469
+ return { value: v, percent };
470
+ }) : [], [showSteps, min, max, step, stepValues]);
471
+ const isInteracting = isHovered || isPressed;
472
+ const thumbAriaLabel = (index) => {
473
+ if (!isRange)
474
+ return label;
475
+ if (!label)
476
+ return index === 0 ? "Minimum" : "Maximum";
477
+ return index === 0 ? `${label} minimum` : `${label} maximum`;
478
+ };
479
+ const valueDisplay = showValue && valuePosition !== "tooltip" && /* @__PURE__ */ jsx(ValueDisplay, {
480
+ values,
481
+ editingIndex,
482
+ onStartEdit: handleStartEdit,
483
+ onCommitEdit: handleCommitEdit,
484
+ onCancelEdit: handleCancelEdit,
485
+ min,
486
+ max,
487
+ step,
488
+ stepValues,
489
+ formatValue,
490
+ label,
491
+ isRange,
492
+ isInteracting
493
+ });
494
+ const renderVisualThumb = (index) => {
495
+ const motionX = index === 0 ? motionX0 : motionX1;
496
+ return /* @__PURE__ */ jsxs(motion.span, {
497
+ className: "flex items-center justify-center pointer-events-none",
498
+ style: {
499
+ width: THUMB_SIZE,
500
+ height: THUMB_SIZE,
501
+ marginTop: -THUMB_SIZE / 2,
502
+ x: motionX,
503
+ position: "absolute",
504
+ top: "50%",
505
+ left: 0,
506
+ zIndex: 10
507
+ },
508
+ initial: false,
509
+ children: [
510
+ /* @__PURE__ */ jsx(motion.span, {
511
+ className: cn("block rounded-full shadow-thumb", !thumbColor && "bg-thumb"),
512
+ initial: false,
513
+ animate: {
514
+ width: THUMB_SIZE_REST,
515
+ height: THUMB_SIZE_REST
516
+ },
517
+ transition: spring.fast,
518
+ style: {
519
+ backgroundColor: thumbColor,
520
+ border: thumbBorderColor ? `1px solid ${thumbBorderColor}` : undefined
521
+ }
522
+ }),
523
+ /* @__PURE__ */ jsx(motion.span, {
524
+ className: "absolute rounded-full border border-focus-ring pointer-events-none",
525
+ initial: false,
526
+ animate: {
527
+ opacity: focusedThumb === index ? 1 : 0,
528
+ width: THUMB_SIZE + 4,
529
+ height: THUMB_SIZE + 4
530
+ },
531
+ transition: spring.fast
532
+ })
533
+ ]
534
+ }, `visual-thumb-${index}`);
535
+ };
536
+ return /* @__PURE__ */ jsxs("div", {
537
+ ref,
538
+ className: cn("flex flex-col gap-0 w-full select-none touch-none overflow-visible", valuePosition === "left" || valuePosition === "right" ? "flex-row items-center gap-2 mb-2" : "flex-col", disabled && "opacity-50 pointer-events-none", className),
539
+ ...props,
540
+ children: [
541
+ (valuePosition === "top" || valuePosition === "left") && valueDisplay,
542
+ /* @__PURE__ */ jsxs("div", {
543
+ className: "relative flex-1 overflow-visible",
544
+ style: {
545
+ height: valuePosition === "left" || valuePosition === "right" ? THUMB_SIZE + 16 : THUMB_SIZE + (valuePosition === "tooltip" ? 16 : 0),
546
+ paddingTop: valuePosition === "tooltip" ? 16 : 0
547
+ },
548
+ onPointerEnter: () => setIsHovered(true),
549
+ onPointerLeave: () => {
550
+ setIsHovered(false);
551
+ setHoverPreview(null);
552
+ },
553
+ onMouseMove: (e) => {
554
+ if (dragging.current)
555
+ return;
556
+ const trackEl = trackRef.current;
557
+ if (!trackEl)
558
+ return;
559
+ const trackRect = trackEl.getBoundingClientRect();
560
+ const layoutWidth = trackEl.offsetWidth;
561
+ if (layoutWidth <= 0 || trackRect.width <= 0)
562
+ return;
563
+ const scale = trackRect.width / layoutWidth;
564
+ const layoutX = (e.clientX - trackRect.left) / scale;
565
+ const clamped = Math.max(0, Math.min(layoutWidth, layoutX));
566
+ computeHoverPreview(clamped, layoutWidth);
567
+ },
568
+ children: [
569
+ showValue && valuePosition === "tooltip" && /* @__PURE__ */ jsxs(AnimatePresence, {
570
+ children: [
571
+ isInteracting && /* @__PURE__ */ jsx(TooltipValue, {
572
+ value: values[0],
573
+ formatValue,
574
+ motionX: motionX0
575
+ }, "tooltip-0"),
576
+ isInteracting && isRange && values[1] !== undefined && /* @__PURE__ */ jsx(TooltipValue, {
577
+ value: values[1],
578
+ formatValue,
579
+ motionX: motionX1
580
+ }, "tooltip-1")
581
+ ]
582
+ }),
583
+ /* @__PURE__ */ jsx(SliderPrimitive.Root, {
584
+ value: stepValues ? values.map((v) => nearestStepIndex(v, stepValues)) : values,
585
+ onValueChange: (v) => handleRadixChange(v),
586
+ min: stepValues ? 0 : min,
587
+ max: stepValues ? stepValues.length - 1 : max,
588
+ step: stepValues ? 1 : step,
589
+ disabled,
590
+ className: "absolute inset-0 opacity-0 pointer-events-none",
591
+ style: { height: THUMB_SIZE },
592
+ children: /* @__PURE__ */ jsxs(SliderPrimitive.Control, {
593
+ className: "w-full h-full",
594
+ children: [
595
+ /* @__PURE__ */ jsx(SliderPrimitive.Track, {
596
+ className: "w-full h-full",
597
+ children: /* @__PURE__ */ jsx(SliderPrimitive.Indicator, {})
598
+ }),
599
+ /* @__PURE__ */ jsx(SliderPrimitive.Thumb, {
600
+ index: 0,
601
+ "aria-label": thumbAriaLabel(0),
602
+ getAriaValueText: stepValues ? () => formatValue(values[0]) : undefined,
603
+ className: "block outline-none",
604
+ style: { width: THUMB_SIZE, height: THUMB_SIZE },
605
+ onFocus: (e) => {
606
+ if (e.currentTarget.matches(":focus-visible"))
607
+ setFocusedThumb(0);
608
+ },
609
+ onBlur: () => setFocusedThumb((prev) => prev === 0 ? null : prev)
610
+ }),
611
+ isRange && /* @__PURE__ */ jsx(SliderPrimitive.Thumb, {
612
+ index: 1,
613
+ "aria-label": thumbAriaLabel(1),
614
+ getAriaValueText: stepValues ? () => formatValue(values[1]) : undefined,
615
+ className: "block outline-none",
616
+ style: { width: THUMB_SIZE, height: THUMB_SIZE },
617
+ onFocus: (e) => {
618
+ if (e.currentTarget.matches(":focus-visible"))
619
+ setFocusedThumb(1);
620
+ },
621
+ onBlur: () => setFocusedThumb((prev) => prev === 1 ? null : prev)
622
+ })
623
+ ]
624
+ })
625
+ }),
626
+ /* @__PURE__ */ jsxs("div", {
627
+ ref: trackRef,
628
+ className: "relative w-full cursor-ew-resize py-2",
629
+ style: { height: THUMB_SIZE + 16, opacity: ready ? 1 : 0 },
630
+ onPointerDown: handlePointerDown,
631
+ onPointerMove: handlePointerMove,
632
+ onPointerUp: handlePointerUp,
633
+ onPointerCancel: handlePointerUp,
634
+ children: [
635
+ /* @__PURE__ */ jsx("div", {
636
+ className: "absolute cursor-ew-resize",
637
+ style: { left: -8, right: -8, top: 0, bottom: 0 },
638
+ onPointerDown: handlePointerDown,
639
+ onPointerMove: handlePointerMove,
640
+ onPointerUp: handlePointerUp,
641
+ onPointerCancel: handlePointerUp
642
+ }),
643
+ /* @__PURE__ */ jsx(AnimatePresence, {
644
+ children: hoverPreview && showHoverTooltip && !isPressed && valuePosition !== "tooltip" && /* @__PURE__ */ jsx(motion.div, {
645
+ className: "absolute -translate-x-1/2 pointer-events-none z-20",
646
+ initial: { opacity: 0, y: 4 },
647
+ animate: { opacity: 1, y: 0 },
648
+ exit: { opacity: 0, y: 4, transition: spring.fast.exit },
649
+ transition: spring.fast,
650
+ style: {
651
+ left: hoverPreview.cursorX,
652
+ top: -20
653
+ },
654
+ children: /* @__PURE__ */ jsx("span", {
655
+ className: cn("text-caption text-background tabular-nums whitespace-nowrap bg-foreground px-2 py-1", shape.bg, "weight-medium"),
656
+ children: formatValue(hoverPreview.snappedValue)
657
+ })
658
+ }, "hover-tooltip")
659
+ }),
660
+ /* @__PURE__ */ jsxs(motion.div, {
661
+ className: cn("absolute bg-transparent border border-border overflow-hidden rounded-full", trackClassName),
662
+ initial: false,
663
+ animate: {
664
+ height: TRACK_BG_HEIGHT,
665
+ top: 8 + (THUMB_SIZE - TRACK_BG_HEIGHT) / 2
666
+ },
667
+ transition: spring.fast,
668
+ style: {
669
+ left: TRACK_INSET,
670
+ right: TRACK_INSET,
671
+ ...trackStyle
672
+ },
673
+ children: [
674
+ !hideFill && /* @__PURE__ */ jsx(motion.div, {
675
+ className: cn("absolute h-full bg-selected/50 dark:bg-accent/40", fillClassName),
676
+ style: {
677
+ left: fillLeft,
678
+ width: fillWidth,
679
+ ...fillStyle
680
+ }
681
+ }),
682
+ /* @__PURE__ */ jsx(motion.div, {
683
+ className: "absolute h-full bg-accent/40 pointer-events-none z-[2]",
684
+ initial: false,
685
+ animate: {
686
+ opacity: hoverPreview && !isPressed ? 1 : 0
687
+ },
688
+ transition: {
689
+ opacity: { duration: 0.15 }
690
+ },
691
+ style: {
692
+ left: hoverPreview ? hoverPreview.left - TRACK_INSET : 0,
693
+ width: hoverPreview ? hoverPreview.width : 0,
694
+ borderRadius: hoverPreview && hoverPreview.cursorX > hoverPreview.left ? "0 9999px 9999px 0" : "9999px 0 0 9999px"
695
+ }
696
+ })
697
+ ]
698
+ }),
699
+ stepDots.length > 0 && /* @__PURE__ */ jsx(motion.div, {
700
+ className: "absolute left-0 right-0 pointer-events-none",
701
+ style: {
702
+ top: 8 + (THUMB_SIZE - TRACK_BG_HEIGHT) / 2,
703
+ height: TRACK_BG_HEIGHT,
704
+ WebkitMaskImage: stepDotsMask,
705
+ maskImage: stepDotsMask
706
+ },
707
+ children: stepDots.map(({ value: v, percent }) => /* @__PURE__ */ jsx("div", {
708
+ className: "absolute pointer-events-none flex items-center justify-center",
709
+ style: {
710
+ left: `calc(${THUMB_SIZE / 2}px + ${percent} * (100% - ${THUMB_SIZE}px))`,
711
+ top: "50%",
712
+ width: 0,
713
+ height: 0
714
+ },
715
+ children: /* @__PURE__ */ jsx(motion.div, {
716
+ className: "rounded-full flex-shrink-0 bg-muted-foreground opacity-30",
717
+ initial: false,
718
+ animate: {
719
+ width: isHovered ? DOT_SIZE * 1.25 : DOT_SIZE,
720
+ height: isHovered ? DOT_SIZE * 1.25 : DOT_SIZE
721
+ },
722
+ transition: spring.moderate
723
+ })
724
+ }, v))
725
+ }),
726
+ renderVisualThumb(0),
727
+ isRange && renderVisualThumb(1)
728
+ ]
729
+ })
730
+ ]
731
+ }),
732
+ (valuePosition === "bottom" || valuePosition === "right") && valueDisplay
733
+ ]
734
+ });
735
+ });
736
+ CompactSlider.displayName = "SliderCompact";
737
+ var ComfortableSlider = forwardRef(({
738
+ value,
739
+ onChange,
740
+ min = 0,
741
+ max = 100,
742
+ step = 1,
743
+ variant = "pips",
744
+ label,
745
+ formatValue = String,
746
+ disabled = false,
747
+ className,
748
+ ...props
749
+ }, ref) => {
750
+ const containerRef = useRef(null);
751
+ const dragging = useRef(false);
752
+ const handleDragging = useRef(false);
753
+ const [isHovered, setIsHovered] = useState(false);
754
+ const [isPressed, setIsPressed] = useState(false);
755
+ const [isFocused, setIsFocused] = useState(false);
756
+ const [hoverPreview, setHoverPreview] = useState(null);
757
+ const [showHoverTooltip, setShowHoverTooltip] = useState(false);
758
+ const hoverDelayRef = useRef(null);
759
+ const shape = useShape();
760
+ useEffect(() => {
761
+ if (isHovered) {
762
+ hoverDelayRef.current = setTimeout(() => setShowHoverTooltip(true), 100);
763
+ } else {
764
+ if (hoverDelayRef.current)
765
+ clearTimeout(hoverDelayRef.current);
766
+ setShowHoverTooltip(false);
767
+ }
768
+ return () => {
769
+ if (hoverDelayRef.current)
770
+ clearTimeout(hoverDelayRef.current);
771
+ };
772
+ }, [isHovered]);
773
+ const mergedRef = useCallback((el) => {
774
+ containerRef.current = el;
775
+ if (typeof ref === "function")
776
+ ref(el);
777
+ else if (ref)
778
+ ref.current = el;
779
+ }, [ref]);
780
+ const pipSteps = useMemo(() => Array.from({ length: Math.round((max - min) / step) + 1 }, (_, i) => min + i * step), [min, max, step]);
781
+ const pipCount = pipSteps.length;
782
+ const fillPercent = useMotionValue(max === min ? 0 : Math.max(0, Math.min(1, (value - min) / (max - min))));
783
+ const zeroTarget = variant === "pips" ? 8 : 17;
784
+ const zeroOffset = useMotionValue(value === min ? zeroTarget : 0);
785
+ const fillWidthStyle = useTransform(fillPercent, (p) => `${p * 100}%`);
786
+ const handleLeftStyle = useTransform([fillPercent, zeroOffset], ([p, zo]) => `calc(${p * 100}% - 8px + ${zo}px)`);
787
+ const handleLineLeftStyle = useTransform([fillPercent, zeroOffset], ([p, zo]) => `calc(${p * 100}% - 9px + ${zo}px)`);
788
+ const pipsFillWidthStyle = useTransform([fillPercent, zeroOffset], ([p, zo]) => `calc(${p * 100}% + ${20 - 20 * p - zo * 2.5}px)`);
789
+ const pipsHandleLineLeftStyle = useTransform(fillPercent, (p) => `calc(${p * 100}% + ${11 - 24 * p}px)`);
790
+ const pipsMaskStyle = useTransform([fillPercent, zeroOffset], ([p, zo]) => {
791
+ const offset = 20 - 20 * p - zo * 2.5;
792
+ return `linear-gradient(to right, transparent calc(${p * 100}% + ${offset}px), black calc(${p * 100}% + ${offset + 2}px))`;
793
+ });
794
+ const computeHoverPreview = useCallback((clientX) => {
795
+ const el = containerRef.current;
796
+ if (!el)
797
+ return;
798
+ const rect = el.getBoundingClientRect();
799
+ const w = el.clientWidth;
800
+ if (w <= 0 || rect.width <= 0)
801
+ return;
802
+ const scale = rect.width / el.offsetWidth;
803
+ const borderLeftLayout = (el.offsetWidth - w) / 2;
804
+ const visualX = clientX - rect.left;
805
+ const layoutX = visualX / scale - borderLeftLayout;
806
+ const clamped = Math.max(0, Math.min(w, layoutX));
807
+ let snappedVal;
808
+ if (variant === "pips") {
809
+ if (pipCount <= 1)
810
+ return;
811
+ const index = Math.max(0, Math.min(pipCount - 1, Math.round(clamped / w * (pipCount - 1))));
812
+ snappedVal = pipSteps[index];
813
+ } else {
814
+ const raw = min + clamped / w * (max - min);
815
+ snappedVal = Math.max(min, Math.min(max, Math.round((raw - min) / step) * step + min));
816
+ }
817
+ const snappedPercent = max === min ? 0 : (snappedVal - min) / (max - min);
818
+ const snappedX = snappedPercent * w;
819
+ const currentPercent = fillPercent.get();
820
+ let handleX;
821
+ if (variant === "pips") {
822
+ const zo = zeroOffset.get();
823
+ handleX = currentPercent * w + (20 - 20 * currentPercent - zo * 2.5);
824
+ } else {
825
+ handleX = currentPercent * w;
826
+ }
827
+ const edgeX = snappedVal === min ? 0 : snappedVal === max ? w : snappedX;
828
+ const left = Math.min(handleX, edgeX);
829
+ const width = Math.abs(edgeX - handleX);
830
+ setHoverPreview({ left, width, snappedValue: snappedVal, cursorX: snappedX });
831
+ }, [variant, pipSteps, pipCount, min, max, step, fillPercent, zeroOffset]);
832
+ useEffect(() => {
833
+ if (dragging.current || handleDragging.current)
834
+ return;
835
+ const percent = max === min ? 0 : Math.max(0, Math.min(1, (value - min) / (max - min)));
836
+ animate(fillPercent, percent, spring.fast);
837
+ animate(zeroOffset, value === min ? zeroTarget : 0, spring.fast);
838
+ }, [value, min, max, variant, fillPercent, zeroOffset, zeroTarget]);
839
+ const getValueFromX = useCallback((clientX) => {
840
+ const rect = containerRef.current?.getBoundingClientRect();
841
+ if (!rect)
842
+ return min;
843
+ const x = clientX - rect.left;
844
+ const clamped = Math.max(0, Math.min(rect.width, x));
845
+ if (variant === "pips") {
846
+ if (pipCount <= 1)
847
+ return min;
848
+ const index = Math.max(0, Math.min(pipCount - 1, Math.round(clamped / rect.width * (pipCount - 1))));
849
+ return pipSteps[index];
850
+ }
851
+ const raw = min + clamped / rect.width * (max - min);
852
+ const snapped = Math.round((raw - min) / step) * step + min;
853
+ return Math.max(min, Math.min(max, snapped));
854
+ }, [variant, pipSteps, pipCount, min, max, step]);
855
+ const handlePointerDown = useCallback((e) => {
856
+ if (disabled)
857
+ return;
858
+ if (e.pointerType === "mouse" && e.button !== 0)
859
+ return;
860
+ e.preventDefault();
861
+ dragging.current = true;
862
+ setIsPressed(true);
863
+ const newVal = getValueFromX(e.clientX);
864
+ onChange(newVal);
865
+ const newPercent = Math.max(0, Math.min(1, (newVal - min) / (max - min)));
866
+ animate(fillPercent, newPercent, spring.fast);
867
+ animate(zeroOffset, newVal === min ? zeroTarget : 0, spring.fast);
868
+ e.currentTarget.setPointerCapture(e.pointerId);
869
+ }, [disabled, getValueFromX, onChange, fillPercent, zeroOffset, zeroTarget, min, max]);
870
+ const handlePointerMove = useCallback((e) => {
871
+ if (!dragging.current)
872
+ return;
873
+ const newVal = getValueFromX(e.clientX);
874
+ onChange(newVal);
875
+ const newPercent = Math.max(0, Math.min(1, (newVal - min) / (max - min)));
876
+ if (variant === "scrubber") {
877
+ fillPercent.set(newPercent);
878
+ } else {
879
+ animate(fillPercent, newPercent, spring.fast);
880
+ }
881
+ animate(zeroOffset, newVal === min ? zeroTarget : 0, spring.fast);
882
+ }, [getValueFromX, onChange, variant, fillPercent, zeroOffset, zeroTarget, min, max]);
883
+ const handlePointerUp = useCallback(() => {
884
+ dragging.current = false;
885
+ setIsPressed(false);
886
+ setHoverPreview(null);
887
+ }, []);
888
+ const handleResizePointerDown = useCallback((e) => {
889
+ if (disabled)
890
+ return;
891
+ if (e.pointerType === "mouse" && e.button !== 0)
892
+ return;
893
+ e.preventDefault();
894
+ e.stopPropagation();
895
+ handleDragging.current = true;
896
+ setIsPressed(true);
897
+ const newVal = getValueFromX(e.clientX);
898
+ onChange(newVal);
899
+ fillPercent.set(Math.max(0, Math.min(1, (newVal - min) / (max - min))));
900
+ animate(zeroOffset, newVal === min ? zeroTarget : 0, spring.fast);
901
+ e.currentTarget.setPointerCapture(e.pointerId);
902
+ }, [disabled, getValueFromX, onChange, fillPercent, zeroOffset, zeroTarget, min, max]);
903
+ const handleResizePointerMove = useCallback((e) => {
904
+ if (!handleDragging.current)
905
+ return;
906
+ const newVal = getValueFromX(e.clientX);
907
+ onChange(newVal);
908
+ fillPercent.set(Math.max(0, Math.min(1, (newVal - min) / (max - min))));
909
+ animate(zeroOffset, newVal === min ? zeroTarget : 0, spring.fast);
910
+ }, [getValueFromX, onChange, fillPercent, zeroOffset, zeroTarget, min, max]);
911
+ const handleResizePointerUp = useCallback(() => {
912
+ handleDragging.current = false;
913
+ setIsPressed(false);
914
+ setHoverPreview(null);
915
+ }, []);
916
+ const handleRadixChange = useCallback((newValues) => {
917
+ onChange(newValues[0]);
918
+ }, [onChange]);
919
+ const isActive = isHovered || isFocused;
920
+ return /* @__PURE__ */ jsxs("div", {
921
+ className: "relative w-full touch-none",
922
+ onPointerEnter: () => {
923
+ if (!disabled)
924
+ setIsHovered(true);
925
+ },
926
+ onPointerLeave: () => {
927
+ if (!disabled) {
928
+ setIsHovered(false);
929
+ setHoverPreview(null);
930
+ }
931
+ },
932
+ onMouseMove: (e) => {
933
+ if (disabled || dragging.current || handleDragging.current)
934
+ return;
935
+ computeHoverPreview(e.clientX);
936
+ },
937
+ children: [
938
+ /* @__PURE__ */ jsx("div", {
939
+ className: "absolute cursor-ew-resize",
940
+ style: { left: -8, right: -8, top: 0, bottom: 0 },
941
+ onPointerDown: handlePointerDown,
942
+ onPointerMove: handlePointerMove,
943
+ onPointerUp: handlePointerUp,
944
+ onPointerCancel: handlePointerUp
945
+ }),
946
+ /* @__PURE__ */ jsx(AnimatePresence, {
947
+ children: hoverPreview && showHoverTooltip && !isPressed && /* @__PURE__ */ jsx(motion.div, {
948
+ className: "absolute -translate-x-1/2 pointer-events-none z-20",
949
+ initial: { opacity: 0, y: 4 },
950
+ animate: { opacity: 1, y: 0 },
951
+ exit: { opacity: 0, y: 4, transition: spring.fast.exit },
952
+ transition: spring.fast,
953
+ style: {
954
+ left: hoverPreview.cursorX,
955
+ top: -30
956
+ },
957
+ children: /* @__PURE__ */ jsx("span", {
958
+ className: cn("text-caption text-background tabular-nums whitespace-nowrap bg-foreground px-2 py-1", shape.bg, "weight-medium"),
959
+ children: formatValue(hoverPreview.snappedValue)
960
+ })
961
+ }, "hover-tooltip")
962
+ }),
963
+ /* @__PURE__ */ jsxs(motion.div, {
964
+ ref: mergedRef,
965
+ className: cn("relative w-full h-8 select-none touch-none border border-border overflow-hidden outline-offset-2", variant === "scrubber" ? "flex items-center gap-3 px-4 cursor-ew-resize" : "cursor-ew-resize", shape.bg, disabled && "opacity-50 pointer-events-none", className),
966
+ initial: false,
967
+ animate: {
968
+ outline: isFocused ? "1px solid var(--focus-ring)" : "1px solid transparent"
969
+ },
970
+ transition: spring.fast,
971
+ onPointerDown: handlePointerDown,
972
+ onPointerMove: handlePointerMove,
973
+ onPointerUp: handlePointerUp,
974
+ onPointerCancel: handlePointerUp,
975
+ ...props,
976
+ children: [
977
+ /* @__PURE__ */ jsx(SliderPrimitive.Root, {
978
+ value: [value],
979
+ onValueChange: (v) => handleRadixChange(v),
980
+ min,
981
+ max,
982
+ step,
983
+ disabled,
984
+ className: "absolute inset-0 opacity-0 pointer-events-none [&_*]:pointer-events-none",
985
+ children: /* @__PURE__ */ jsxs(SliderPrimitive.Control, {
986
+ className: "w-full h-full",
987
+ children: [
988
+ /* @__PURE__ */ jsx(SliderPrimitive.Track, {
989
+ className: "w-full h-full",
990
+ children: /* @__PURE__ */ jsx(SliderPrimitive.Indicator, {})
991
+ }),
992
+ /* @__PURE__ */ jsx(SliderPrimitive.Thumb, {
993
+ index: 0,
994
+ "aria-label": label,
995
+ className: "block outline-none",
996
+ onFocus: (e) => {
997
+ if (e.currentTarget.matches(":focus-visible"))
998
+ setIsFocused(true);
999
+ },
1000
+ onBlur: () => setIsFocused(false)
1001
+ })
1002
+ ]
1003
+ })
1004
+ }),
1005
+ /* @__PURE__ */ jsx(motion.div, {
1006
+ className: "absolute inset-y-0 bg-accent/40 pointer-events-none z-[3]",
1007
+ initial: false,
1008
+ animate: {
1009
+ opacity: hoverPreview && !isPressed ? 1 : 0
1010
+ },
1011
+ transition: { opacity: { duration: 0.15 } },
1012
+ style: {
1013
+ left: hoverPreview ? hoverPreview.left : 0,
1014
+ width: hoverPreview ? hoverPreview.width : 0
1015
+ }
1016
+ }),
1017
+ variant === "pips" && /* @__PURE__ */ jsx(motion.div, {
1018
+ className: "absolute inset-0 flex justify-between items-center px-3 pointer-events-none z-[1]",
1019
+ style: { WebkitMaskImage: pipsMaskStyle, maskImage: pipsMaskStyle },
1020
+ children: pipSteps.map((pipValue) => {
1021
+ const isActivePip = pipValue === value;
1022
+ return /* @__PURE__ */ jsx("div", {
1023
+ className: "relative flex items-center justify-center",
1024
+ style: { width: PIP_SIZE, height: PIP_SIZE },
1025
+ children: /* @__PURE__ */ jsx(motion.div, {
1026
+ className: "rounded-full",
1027
+ initial: false,
1028
+ animate: {
1029
+ backgroundColor: isActivePip ? "var(--foreground)" : "var(--muted-foreground)",
1030
+ opacity: isActivePip ? 1 : 0.3
1031
+ },
1032
+ transition: spring.fast,
1033
+ style: { width: PIP_SIZE, height: PIP_SIZE }
1034
+ })
1035
+ }, pipValue);
1036
+ })
1037
+ }),
1038
+ variant === "pips" && /* @__PURE__ */ jsxs("div", {
1039
+ className: "absolute inset-0 flex items-center px-2 z-[2] pointer-events-none",
1040
+ "aria-hidden": true,
1041
+ children: [
1042
+ label && /* @__PURE__ */ jsx("span", {
1043
+ className: "text-body px-2 bg-background text-transparent select-none",
1044
+ children: label
1045
+ }),
1046
+ /* @__PURE__ */ jsx("span", {
1047
+ className: "text-body tabular-nums ml-auto px-2 bg-background text-transparent select-none",
1048
+ style: { minWidth: `${String(formatValue(max)).length}ch` },
1049
+ children: formatValue(value)
1050
+ })
1051
+ ]
1052
+ }),
1053
+ variant === "pips" && /* @__PURE__ */ jsx(motion.div, {
1054
+ className: "absolute left-0 top-0 bottom-0 bg-active pointer-events-none z-[3]",
1055
+ style: {
1056
+ width: pipsFillWidthStyle
1057
+ }
1058
+ }),
1059
+ variant === "pips" && /* @__PURE__ */ jsx(motion.div, {
1060
+ className: "absolute rounded-full pointer-events-none z-[3]",
1061
+ initial: false,
1062
+ animate: {
1063
+ top: isActive ? 7 : 8,
1064
+ bottom: isActive ? 7 : 8,
1065
+ backgroundColor: isFocused ? "var(--foreground)" : isHovered ? "color-mix(in srgb, var(--foreground) 50%, transparent)" : "color-mix(in srgb, var(--foreground) 25%, transparent)"
1066
+ },
1067
+ transition: spring.fast,
1068
+ style: {
1069
+ left: pipsHandleLineLeftStyle,
1070
+ width: 2
1071
+ }
1072
+ }),
1073
+ variant === "pips" && /* @__PURE__ */ jsxs("div", {
1074
+ className: "absolute inset-0 flex items-center px-2 z-[4] pointer-events-none",
1075
+ children: [
1076
+ label && /* @__PURE__ */ jsx(motion.span, {
1077
+ className: "text-body px-2",
1078
+ initial: false,
1079
+ animate: { color: isActive ? "var(--foreground)" : "var(--muted-foreground)" },
1080
+ transition: spring.fast,
1081
+ children: label
1082
+ }),
1083
+ /* @__PURE__ */ jsx(motion.span, {
1084
+ className: "text-body tabular-nums ml-auto px-2",
1085
+ initial: false,
1086
+ animate: { color: isActive ? "var(--foreground)" : "var(--muted-foreground)" },
1087
+ transition: spring.fast,
1088
+ style: { minWidth: `${String(formatValue(max)).length}ch`, textAlign: "right" },
1089
+ children: formatValue(value)
1090
+ })
1091
+ ]
1092
+ }),
1093
+ variant === "scrubber" && /* @__PURE__ */ jsx(motion.div, {
1094
+ className: "absolute left-0 top-0 bottom-0 bg-active pointer-events-none",
1095
+ style: {
1096
+ width: fillWidthStyle
1097
+ }
1098
+ }),
1099
+ variant === "scrubber" && /* @__PURE__ */ jsx(motion.div, {
1100
+ className: "absolute rounded-full pointer-events-none z-10",
1101
+ initial: false,
1102
+ animate: {
1103
+ top: isActive ? 7 : 8,
1104
+ bottom: isActive ? 7 : 8,
1105
+ backgroundColor: isFocused ? "var(--foreground)" : isHovered ? "color-mix(in srgb, var(--foreground) 50%, transparent)" : "color-mix(in srgb, var(--foreground) 25%, transparent)"
1106
+ },
1107
+ transition: spring.fast,
1108
+ style: {
1109
+ left: handleLineLeftStyle,
1110
+ width: 2
1111
+ }
1112
+ }),
1113
+ variant === "scrubber" && label && /* @__PURE__ */ jsx(motion.span, {
1114
+ className: "text-body shrink-0 z-10",
1115
+ initial: false,
1116
+ animate: { color: isActive ? "var(--foreground)" : "var(--muted-foreground)" },
1117
+ transition: spring.fast,
1118
+ children: label
1119
+ }),
1120
+ variant === "scrubber" && /* @__PURE__ */ jsxs(Fragment, {
1121
+ children: [
1122
+ /* @__PURE__ */ jsx("div", {
1123
+ className: "flex-1"
1124
+ }),
1125
+ /* @__PURE__ */ jsx(motion.span, {
1126
+ className: "text-body shrink-0 tabular-nums text-right z-10",
1127
+ initial: false,
1128
+ animate: { color: isActive ? "var(--foreground)" : "var(--muted-foreground)" },
1129
+ transition: spring.fast,
1130
+ style: { minWidth: `${String(formatValue(max)).length}ch` },
1131
+ children: formatValue(value)
1132
+ })
1133
+ ]
1134
+ }),
1135
+ variant === "scrubber" && /* @__PURE__ */ jsx(motion.div, {
1136
+ className: "absolute top-0 bottom-0 w-2 cursor-ew-resize z-20",
1137
+ style: { left: handleLeftStyle },
1138
+ onPointerDown: handleResizePointerDown,
1139
+ onPointerMove: handleResizePointerMove,
1140
+ onPointerUp: handleResizePointerUp,
1141
+ onPointerCancel: handleResizePointerUp
1142
+ })
1143
+ ]
1144
+ })
1145
+ ]
1146
+ });
1147
+ });
1148
+ ComfortableSlider.displayName = "SliderComfortable";
1149
+ var Slider = forwardRef(({ size, variant = "pips", value: valueProp, defaultValue, onChange: onChangeProp, ...rest }, ref) => {
1150
+ const [value, onChange] = useControllableState(valueProp, defaultValue ?? (rest.steps && rest.steps.length > 0 ? Math.min(...rest.steps) : rest.min ?? 0), onChangeProp);
1151
+ const props = { ...rest, value, onChange };
1152
+ const resolved = useSizeVariant(size);
1153
+ const needsCompactEngine = Array.isArray(props.value) || props.steps !== undefined || props.showSteps !== undefined || props.showValue !== undefined || props.valuePosition !== undefined || props.trackClassName !== undefined || props.trackStyle !== undefined || props.fillClassName !== undefined || props.fillStyle !== undefined || props.hideFill !== undefined || props.thumbColor !== undefined || props.thumbBorderColor !== undefined;
1154
+ if (resolved === "compact" || needsCompactEngine) {
1155
+ return /* @__PURE__ */ jsx(CompactSlider, {
1156
+ ref,
1157
+ ...props
1158
+ });
1159
+ }
1160
+ const {
1161
+ value: singleValue,
1162
+ onChange: singleOnChange,
1163
+ min,
1164
+ max,
1165
+ step,
1166
+ label,
1167
+ formatValue,
1168
+ disabled,
1169
+ steps: _steps,
1170
+ showSteps: _showSteps,
1171
+ showValue: _showValue,
1172
+ valuePosition: _valuePosition,
1173
+ trackClassName: _trackClassName,
1174
+ trackStyle: _trackStyle,
1175
+ fillClassName: _fillClassName,
1176
+ fillStyle: _fillStyle,
1177
+ hideFill: _hideFill,
1178
+ thumbColor: _thumbColor,
1179
+ thumbBorderColor: _thumbBorderColor,
1180
+ ...html
1181
+ } = props;
1182
+ return /* @__PURE__ */ jsx(ComfortableSlider, {
1183
+ ref,
1184
+ value: singleValue,
1185
+ onChange: singleOnChange,
1186
+ min,
1187
+ max,
1188
+ step,
1189
+ variant,
1190
+ label,
1191
+ formatValue,
1192
+ disabled,
1193
+ ...html
1194
+ });
1195
+ });
1196
+ Slider.displayName = "Slider";
1197
+ var SliderComfortable = ComfortableSlider;
1198
+ var slider_default = Slider;
1199
+
1200
+ export { Slider, SliderComfortable, slider_default };