@gfazioli/mantine-picker 2.3.14 → 3.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.
- package/README.md +19 -7
- package/dist/cjs/Picker.cjs +190 -259
- package/dist/cjs/Picker.cjs.map +1 -1
- package/dist/esm/Picker.mjs +191 -260
- package/dist/esm/Picker.mjs.map +1 -1
- package/dist/types/Picker.d.ts +13 -5
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Mantine Picker Component
|
|
2
2
|
|
|
3
|
-
<img alt="Mantine Picker" src="https://github.com/gfazioli/mantine-picker/blob/master/logo.
|
|
3
|
+
<img alt="Mantine Picker" src="https://github.com/gfazioli/mantine-picker/blob/master/logo.jpeg" />
|
|
4
4
|
|
|
5
5
|
<div align="center">
|
|
6
6
|
|
|
@@ -18,12 +18,24 @@
|
|
|
18
18
|
## Overview
|
|
19
19
|
|
|
20
20
|
This component is created on top of the [Mantine](https://mantine.dev/) library.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
It requires **Mantine 9.x** and **React 19**.
|
|
22
|
+
|
|
23
|
+
[Mantine Picker](https://gfazioli.github.io/mantine-picker/) is an iOS-style wheel picker for React with Mantine, enabling selection through dragging, mouse wheel, clicking, and keyboard navigation.
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- 🎡 **iOS-style wheel picker**: Smooth drag, wheel, click, and keyboard navigation
|
|
28
|
+
- 🎲 **3D rotation effect**: Configurable `perspective`, `maxRotation`, `cylinderRadius`, `rotateY`
|
|
29
|
+
- 🔄 **Loop mode**: Infinite circular scrolling through items
|
|
30
|
+
- 🎯 **Momentum scrolling**: Inertia-based deceleration after drag release
|
|
31
|
+
- 🎨 **Custom item rendering**: `renderItem` for color swatches, badges, icons, or any React content
|
|
32
|
+
- 📐 **Left/Right sections**: Fixed content beside the picker (icons, labels)
|
|
33
|
+
- 🔒 **Read-only mode**: Programmatic updates without user interaction (counters, clocks)
|
|
34
|
+
- 🎭 **Visual effects**: Configurable blur, opacity, scale gradients for non-selected items
|
|
35
|
+
- 🖌 **Mask, highlight, dividers**: Toggle gradient mask, selection highlight, and divider lines
|
|
36
|
+
- ♿ **Accessible**: `aria-label`, keyboard navigation, `focusable` prop, screen reader support
|
|
37
|
+
- 🎨 **Styles API**: Full Mantine Styles API support with 6 selectors
|
|
38
|
+
- 📦 **TypeScript**: Full type safety with generic `Picker<T>` support
|
|
27
39
|
|
|
28
40
|
> [!note]
|
|
29
41
|
>
|
package/dist/cjs/Picker.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var React = require('react');
|
|
5
|
+
var hooks = require('@mantine/hooks');
|
|
5
6
|
var core = require('@mantine/core');
|
|
6
7
|
var Picker_module = require('./Picker.module.css.cjs');
|
|
7
8
|
|
|
@@ -62,8 +63,9 @@ const varsResolver = core.createVarsResolver(
|
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
65
|
);
|
|
65
|
-
const Picker = core.polymorphicFactory((_props
|
|
66
|
-
const
|
|
66
|
+
const Picker = core.polymorphicFactory((_props) => {
|
|
67
|
+
const { ref, ...restProps } = _props;
|
|
68
|
+
const props = core.useProps("Picker", defaultProps, restProps);
|
|
67
69
|
const {
|
|
68
70
|
data,
|
|
69
71
|
value,
|
|
@@ -155,10 +157,10 @@ const Picker = core.polymorphicFactory((_props, ref) => {
|
|
|
155
157
|
const prevValueRef = React.useRef(value);
|
|
156
158
|
const containerRef = React.useRef(null);
|
|
157
159
|
const rootRef = React.useRef(null);
|
|
160
|
+
const mergedRootRef = hooks.useMergedRef(ref, rootRef);
|
|
158
161
|
const [isDragging, setIsDragging] = React.useState(false);
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
const [velocity, setVelocity] = React.useState(0);
|
|
162
|
+
const lastYRef = React.useRef(0);
|
|
163
|
+
const velocityRef = React.useRef(0);
|
|
162
164
|
const [isMomentumScrolling, setIsMomentumScrolling] = React.useState(false);
|
|
163
165
|
const lastMoveTime = React.useRef(0);
|
|
164
166
|
const lastMovePosition = React.useRef(0);
|
|
@@ -167,10 +169,14 @@ const Picker = core.polymorphicFactory((_props, ref) => {
|
|
|
167
169
|
const wheelTimeoutRef = React.useRef(null);
|
|
168
170
|
const lastWheelEventTime = React.useRef(0);
|
|
169
171
|
const [currentPosition, setCurrentPosition] = React.useState(selectedIndex);
|
|
172
|
+
const currentPositionRef = React.useRef(currentPosition);
|
|
173
|
+
currentPositionRef.current = currentPosition;
|
|
170
174
|
const [isAnimating, setIsAnimating] = React.useState(false);
|
|
171
175
|
const animationRef = React.useRef(null);
|
|
172
176
|
const [isFocused, setIsFocused] = React.useState(false);
|
|
173
177
|
const prevLoopRef = React.useRef(loop);
|
|
178
|
+
const originalOverflowRef = React.useRef(null);
|
|
179
|
+
const isWindows = typeof navigator !== "undefined" && navigator.userAgent.includes("Windows");
|
|
174
180
|
React.useEffect(() => {
|
|
175
181
|
if (value !== prevValueRef.current && selectedIndex !== -1) {
|
|
176
182
|
if (animate) {
|
|
@@ -238,58 +244,69 @@ const Picker = core.polymorphicFactory((_props, ref) => {
|
|
|
238
244
|
}
|
|
239
245
|
return currentRoundedPos - directPath;
|
|
240
246
|
};
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
let clampedTargetPosition = targetPosition;
|
|
246
|
-
if (!loop) {
|
|
247
|
-
clampedTargetPosition = Math.max(0, Math.min(data.length - 1, targetPosition));
|
|
248
|
-
}
|
|
249
|
-
if (animationRef.current !== null) {
|
|
250
|
-
cancelAnimationFrame(animationRef.current);
|
|
251
|
-
}
|
|
252
|
-
if (momentumAnimationRef.current !== null) {
|
|
253
|
-
cancelAnimationFrame(momentumAnimationRef.current);
|
|
254
|
-
setIsMomentumScrolling(false);
|
|
255
|
-
}
|
|
256
|
-
setIsAnimating(true);
|
|
257
|
-
const startTime = performance.now();
|
|
258
|
-
const startPosition = currentPosition;
|
|
259
|
-
const distance = Math.abs(clampedTargetPosition - startPosition);
|
|
260
|
-
const duration = Math.min(
|
|
261
|
-
animationDuration || 300,
|
|
262
|
-
Math.max(100, (animationDuration || 300) * Math.min(distance / 3, 1))
|
|
263
|
-
);
|
|
264
|
-
const animate2 = (time) => {
|
|
265
|
-
const elapsed = time - startTime;
|
|
266
|
-
const progress = Math.min(elapsed / duration, 1);
|
|
267
|
-
const easeProgress = 1 - (1 - progress) * (1 - progress);
|
|
268
|
-
const position = startPosition + (clampedTargetPosition - startPosition) * easeProgress;
|
|
269
|
-
setCurrentPosition(position);
|
|
270
|
-
if (progress < 1) {
|
|
271
|
-
animationRef.current = requestAnimationFrame(animate2);
|
|
272
|
-
} else {
|
|
273
|
-
setIsAnimating(false);
|
|
274
|
-
animationRef.current = null;
|
|
275
|
-
setCurrentPosition(clampedTargetPosition);
|
|
276
|
-
if (!disabled) {
|
|
277
|
-
const realIndex2 = loop ? (Math.round(clampedTargetPosition) % data.length + data.length) % data.length : Math.round(clampedTargetPosition);
|
|
278
|
-
onChange?.(data[realIndex2]);
|
|
279
|
-
}
|
|
280
|
-
const realIndex = loop ? (Math.round(clampedTargetPosition) % data.length + data.length) % data.length : Math.round(clampedTargetPosition);
|
|
281
|
-
prevValueRef.current = data[realIndex];
|
|
247
|
+
const snapAndNotify = React.useCallback(
|
|
248
|
+
(roundedPosition) => {
|
|
249
|
+
if (data.length === 0) {
|
|
250
|
+
return;
|
|
282
251
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
252
|
+
const realIndex = loop ? (roundedPosition % data.length + data.length) % data.length : Math.max(0, Math.min(data.length - 1, roundedPosition));
|
|
253
|
+
if (!disabled) {
|
|
254
|
+
onChange?.(data[realIndex]);
|
|
255
|
+
}
|
|
256
|
+
prevValueRef.current = data[realIndex];
|
|
257
|
+
},
|
|
258
|
+
[disabled, loop, data, onChange]
|
|
259
|
+
);
|
|
260
|
+
const animateToPosition = React.useCallback(
|
|
261
|
+
(targetPosition) => {
|
|
262
|
+
if (targetPosition === currentPositionRef.current || isAnimating) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
let clampedTargetPosition = targetPosition;
|
|
266
|
+
if (!loop) {
|
|
267
|
+
clampedTargetPosition = Math.max(0, Math.min(data.length - 1, targetPosition));
|
|
268
|
+
}
|
|
269
|
+
if (animationRef.current !== null) {
|
|
270
|
+
cancelAnimationFrame(animationRef.current);
|
|
271
|
+
}
|
|
272
|
+
if (momentumAnimationRef.current !== null) {
|
|
273
|
+
cancelAnimationFrame(momentumAnimationRef.current);
|
|
274
|
+
setIsMomentumScrolling(false);
|
|
275
|
+
}
|
|
276
|
+
setIsAnimating(true);
|
|
277
|
+
const startTime = performance.now();
|
|
278
|
+
const startPosition = currentPositionRef.current;
|
|
279
|
+
const distance = Math.abs(clampedTargetPosition - startPosition);
|
|
280
|
+
const duration = Math.min(
|
|
281
|
+
animationDuration || 300,
|
|
282
|
+
Math.max(100, (animationDuration || 300) * Math.min(distance / 3, 1))
|
|
283
|
+
);
|
|
284
|
+
const animate2 = (time) => {
|
|
285
|
+
const elapsed = time - startTime;
|
|
286
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
287
|
+
const easeProgress = 1 - (1 - progress) * (1 - progress);
|
|
288
|
+
const position = startPosition + (clampedTargetPosition - startPosition) * easeProgress;
|
|
289
|
+
setCurrentPosition(position);
|
|
290
|
+
if (progress < 1) {
|
|
291
|
+
animationRef.current = requestAnimationFrame(animate2);
|
|
292
|
+
} else {
|
|
293
|
+
setIsAnimating(false);
|
|
294
|
+
animationRef.current = null;
|
|
295
|
+
setCurrentPosition(clampedTargetPosition);
|
|
296
|
+
snapAndNotify(Math.round(clampedTargetPosition));
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
animationRef.current = requestAnimationFrame(animate2);
|
|
300
|
+
},
|
|
301
|
+
[isAnimating, loop, data.length, animationDuration, snapAndNotify]
|
|
302
|
+
);
|
|
303
|
+
const applyMomentum = React.useCallback(() => {
|
|
304
|
+
if (velocityRef.current === 0 || disabled) {
|
|
288
305
|
return;
|
|
289
306
|
}
|
|
290
307
|
setIsMomentumScrolling(true);
|
|
291
|
-
let currentVelocity =
|
|
292
|
-
let currentPos =
|
|
308
|
+
let currentVelocity = velocityRef.current * (momentum || 0.95);
|
|
309
|
+
let currentPos = currentPositionRef.current;
|
|
293
310
|
let lastDirection = Math.sign(currentVelocity);
|
|
294
311
|
const momentumScroll = () => {
|
|
295
312
|
currentVelocity *= decelerationRate || 0.95;
|
|
@@ -329,18 +346,13 @@ const Picker = core.polymorphicFactory((_props, ref) => {
|
|
|
329
346
|
}
|
|
330
347
|
setCurrentPosition(roundedPosition);
|
|
331
348
|
requestAnimationFrame(() => {
|
|
332
|
-
|
|
333
|
-
const realIndex2 = loop ? (roundedPosition % data.length + data.length) % data.length : roundedPosition;
|
|
334
|
-
onChange?.(data[realIndex2]);
|
|
335
|
-
}
|
|
336
|
-
const realIndex = loop ? (roundedPosition % data.length + data.length) % data.length : roundedPosition;
|
|
337
|
-
prevValueRef.current = data[realIndex];
|
|
349
|
+
snapAndNotify(roundedPosition);
|
|
338
350
|
});
|
|
339
351
|
}
|
|
340
352
|
lastDirection = currentDirection;
|
|
341
353
|
};
|
|
342
354
|
momentumAnimationRef.current = requestAnimationFrame(momentumScroll);
|
|
343
|
-
};
|
|
355
|
+
}, [disabled, momentum, decelerationRate, loop, data.length, snapAndNotify]);
|
|
344
356
|
React.useEffect(() => {
|
|
345
357
|
return () => {
|
|
346
358
|
if (animationRef.current !== null) {
|
|
@@ -357,23 +369,26 @@ const Picker = core.polymorphicFactory((_props, ref) => {
|
|
|
357
369
|
const handleMouseEnter = () => {
|
|
358
370
|
const isInteractionDisabled = disabled || readOnly;
|
|
359
371
|
if (preventPageScroll && !isInteractionDisabled && typeof document !== "undefined") {
|
|
372
|
+
originalOverflowRef.current = document.body.style.overflow;
|
|
360
373
|
document.body.style.overflow = "hidden";
|
|
361
374
|
}
|
|
362
375
|
};
|
|
363
376
|
const handleMouseLeave = () => {
|
|
364
|
-
if (preventPageScroll && typeof document !== "undefined") {
|
|
365
|
-
document.body.style.overflow =
|
|
377
|
+
if (preventPageScroll && typeof document !== "undefined" && originalOverflowRef.current !== null) {
|
|
378
|
+
document.body.style.overflow = originalOverflowRef.current;
|
|
379
|
+
originalOverflowRef.current = null;
|
|
366
380
|
}
|
|
367
381
|
};
|
|
368
382
|
React.useEffect(() => {
|
|
369
383
|
return () => {
|
|
370
|
-
if (preventPageScroll && typeof document !== "undefined") {
|
|
371
|
-
document.body.style.overflow =
|
|
384
|
+
if (preventPageScroll && typeof document !== "undefined" && originalOverflowRef.current !== null) {
|
|
385
|
+
document.body.style.overflow = originalOverflowRef.current;
|
|
386
|
+
originalOverflowRef.current = null;
|
|
372
387
|
}
|
|
373
388
|
};
|
|
374
389
|
}, [preventPageScroll]);
|
|
375
390
|
const halfVisible = Math.floor((visibleItems || 5) / 2);
|
|
376
|
-
const
|
|
391
|
+
const startDrag = (clientY) => {
|
|
377
392
|
const isInteractionDisabled = disabled || readOnly;
|
|
378
393
|
if (isAnimating || isInteractionDisabled || isMomentumScrolling) {
|
|
379
394
|
return;
|
|
@@ -382,163 +397,118 @@ const Picker = core.polymorphicFactory((_props, ref) => {
|
|
|
382
397
|
cancelAnimationFrame(momentumAnimationRef.current);
|
|
383
398
|
setIsMomentumScrolling(false);
|
|
384
399
|
}
|
|
385
|
-
e.preventDefault();
|
|
386
400
|
setIsDragging(true);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
setVelocity(0);
|
|
401
|
+
lastYRef.current = clientY;
|
|
402
|
+
velocityRef.current = 0;
|
|
390
403
|
lastMoveTime.current = performance.now();
|
|
391
404
|
lastMovePosition.current = currentPosition;
|
|
392
405
|
};
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
if (momentumAnimationRef.current !== null) {
|
|
399
|
-
cancelAnimationFrame(momentumAnimationRef.current);
|
|
400
|
-
setIsMomentumScrolling(false);
|
|
401
|
-
}
|
|
402
|
-
setIsDragging(true);
|
|
403
|
-
setLastY(e.touches[0].clientY);
|
|
404
|
-
setDragOffset(0);
|
|
405
|
-
setVelocity(0);
|
|
406
|
-
lastMoveTime.current = performance.now();
|
|
407
|
-
lastMovePosition.current = currentPosition;
|
|
406
|
+
const handleMouseDown = (e) => {
|
|
407
|
+
e.preventDefault();
|
|
408
|
+
startDrag(e.clientY);
|
|
408
409
|
};
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
return position;
|
|
412
|
-
}
|
|
413
|
-
if (position < 0) {
|
|
414
|
-
return position * 0.3;
|
|
415
|
-
} else if (position > data.length - 1) {
|
|
416
|
-
return data.length - 1 + (position - (data.length - 1)) * 0.3;
|
|
417
|
-
}
|
|
418
|
-
return position;
|
|
410
|
+
const handleTouchStart = (e) => {
|
|
411
|
+
startDrag(e.touches[0].clientY);
|
|
419
412
|
};
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
const currentTime = performance.now();
|
|
426
|
-
const newY = e.clientY;
|
|
427
|
-
const deltaY = newY - lastY;
|
|
428
|
-
const timeDelta = currentTime - lastMoveTime.current;
|
|
429
|
-
const itemOffsetRatio = deltaY / (itemHeight || 40);
|
|
430
|
-
setCurrentPosition((prev) => {
|
|
431
|
-
let newPosition = prev - itemOffsetRatio;
|
|
432
|
-
newPosition = clampPosition(newPosition);
|
|
433
|
-
if (timeDelta > 0) {
|
|
434
|
-
const positionDelta = newPosition - lastMovePosition.current;
|
|
435
|
-
setVelocity(positionDelta / timeDelta * 16);
|
|
413
|
+
const clampPosition = React.useCallback(
|
|
414
|
+
(position) => {
|
|
415
|
+
if (loop) {
|
|
416
|
+
return position;
|
|
436
417
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
setLastY(newY);
|
|
442
|
-
};
|
|
443
|
-
const handleTouchMove = (e) => {
|
|
444
|
-
const isInteractionDisabled = disabled || readOnly;
|
|
445
|
-
if (!isDragging || isInteractionDisabled) {
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
const currentTime = performance.now();
|
|
449
|
-
const newY = e.touches[0].clientY;
|
|
450
|
-
const deltaY = newY - lastY;
|
|
451
|
-
const timeDelta = currentTime - lastMoveTime.current;
|
|
452
|
-
const itemOffsetRatio = deltaY / (itemHeight || 40);
|
|
453
|
-
setCurrentPosition((prev) => {
|
|
454
|
-
let newPosition = prev - itemOffsetRatio;
|
|
455
|
-
newPosition = clampPosition(newPosition);
|
|
456
|
-
if (timeDelta > 0) {
|
|
457
|
-
const positionDelta = newPosition - lastMovePosition.current;
|
|
458
|
-
setVelocity(positionDelta / timeDelta * 16);
|
|
418
|
+
if (position < 0) {
|
|
419
|
+
return position * 0.3;
|
|
420
|
+
} else if (position > data.length - 1) {
|
|
421
|
+
return data.length - 1 + (position - (data.length - 1)) * 0.3;
|
|
459
422
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
setIsDragging(false);
|
|
472
|
-
setDragOffset(0);
|
|
473
|
-
if (Math.abs(velocity) > 0.05) {
|
|
474
|
-
applyMomentum();
|
|
475
|
-
} else {
|
|
476
|
-
let roundedPosition = Math.round(currentPosition);
|
|
477
|
-
if (!loop) {
|
|
478
|
-
roundedPosition = Math.max(0, Math.min(data.length - 1, roundedPosition));
|
|
423
|
+
return position;
|
|
424
|
+
},
|
|
425
|
+
[loop, data.length]
|
|
426
|
+
);
|
|
427
|
+
const handleDragMove = React.useCallback(
|
|
428
|
+
(clientY) => {
|
|
429
|
+
const isInteractionDisabled = disabled || readOnly;
|
|
430
|
+
if (!isDragging || isInteractionDisabled) {
|
|
431
|
+
return;
|
|
479
432
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
433
|
+
const currentTime = performance.now();
|
|
434
|
+
const deltaY = clientY - lastYRef.current;
|
|
435
|
+
const timeDelta = currentTime - lastMoveTime.current;
|
|
436
|
+
const itemOffsetRatio = deltaY / (itemHeight || 40);
|
|
437
|
+
setCurrentPosition((prev) => {
|
|
438
|
+
let newPosition = prev - itemOffsetRatio;
|
|
439
|
+
newPosition = clampPosition(newPosition);
|
|
440
|
+
if (timeDelta > 0) {
|
|
441
|
+
const positionDelta = newPosition - lastMovePosition.current;
|
|
442
|
+
velocityRef.current = positionDelta / timeDelta * 16;
|
|
486
443
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
444
|
+
lastMovePosition.current = newPosition;
|
|
445
|
+
lastMoveTime.current = currentTime;
|
|
446
|
+
return newPosition;
|
|
447
|
+
});
|
|
448
|
+
lastYRef.current = clientY;
|
|
449
|
+
},
|
|
450
|
+
[isDragging, disabled, readOnly, itemHeight, clampPosition]
|
|
451
|
+
);
|
|
452
|
+
const handleDragEnd = React.useCallback(() => {
|
|
493
453
|
const isInteractionDisabled = disabled || readOnly;
|
|
494
454
|
if (!isDragging || isInteractionDisabled) {
|
|
495
455
|
return;
|
|
496
456
|
}
|
|
497
457
|
setIsDragging(false);
|
|
498
|
-
|
|
499
|
-
if (Math.abs(velocity) > 0.05) {
|
|
458
|
+
if (Math.abs(velocityRef.current) > 0.05) {
|
|
500
459
|
applyMomentum();
|
|
501
460
|
} else {
|
|
502
|
-
let roundedPosition = Math.round(
|
|
461
|
+
let roundedPosition = Math.round(currentPositionRef.current);
|
|
503
462
|
if (!loop) {
|
|
504
463
|
roundedPosition = Math.max(0, Math.min(data.length - 1, roundedPosition));
|
|
505
464
|
}
|
|
506
|
-
if (roundedPosition !==
|
|
465
|
+
if (roundedPosition !== currentPositionRef.current) {
|
|
507
466
|
animateToPosition(roundedPosition);
|
|
508
467
|
} else {
|
|
509
|
-
|
|
510
|
-
const realIndex2 = loop ? (roundedPosition % data.length + data.length) % data.length : roundedPosition;
|
|
511
|
-
onChange?.(data[realIndex2]);
|
|
512
|
-
}
|
|
513
|
-
const realIndex = loop ? (roundedPosition % data.length + data.length) % data.length : roundedPosition;
|
|
514
|
-
prevValueRef.current = data[realIndex];
|
|
468
|
+
snapAndNotify(roundedPosition);
|
|
515
469
|
}
|
|
516
470
|
}
|
|
517
|
-
}
|
|
471
|
+
}, [
|
|
472
|
+
isDragging,
|
|
473
|
+
disabled,
|
|
474
|
+
readOnly,
|
|
475
|
+
loop,
|
|
476
|
+
data.length,
|
|
477
|
+
applyMomentum,
|
|
478
|
+
animateToPosition,
|
|
479
|
+
snapAndNotify
|
|
480
|
+
]);
|
|
518
481
|
React.useEffect(() => {
|
|
519
|
-
const
|
|
520
|
-
const
|
|
521
|
-
const
|
|
522
|
-
const
|
|
482
|
+
const onMouseMove = (e) => handleDragMove(e.clientY);
|
|
483
|
+
const onMouseUp = () => handleDragEnd();
|
|
484
|
+
const onTouchMove = (e) => handleDragMove(e.touches[0].clientY);
|
|
485
|
+
const onTouchEnd = () => handleDragEnd();
|
|
523
486
|
if (isDragging) {
|
|
524
|
-
window.addEventListener("mousemove",
|
|
525
|
-
window.addEventListener("mouseup",
|
|
526
|
-
window.addEventListener("touchmove",
|
|
527
|
-
window.addEventListener("touchend",
|
|
487
|
+
window.addEventListener("mousemove", onMouseMove, { passive: false });
|
|
488
|
+
window.addEventListener("mouseup", onMouseUp);
|
|
489
|
+
window.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
490
|
+
window.addEventListener("touchend", onTouchEnd);
|
|
528
491
|
}
|
|
529
492
|
return () => {
|
|
530
|
-
window.removeEventListener("mousemove",
|
|
531
|
-
window.removeEventListener("mouseup",
|
|
532
|
-
window.removeEventListener("touchmove",
|
|
533
|
-
window.removeEventListener("touchend",
|
|
493
|
+
window.removeEventListener("mousemove", onMouseMove);
|
|
494
|
+
window.removeEventListener("mouseup", onMouseUp);
|
|
495
|
+
window.removeEventListener("touchmove", onTouchMove);
|
|
496
|
+
window.removeEventListener("touchend", onTouchEnd);
|
|
534
497
|
};
|
|
535
|
-
}, [isDragging,
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
498
|
+
}, [isDragging, handleDragMove, handleDragEnd]);
|
|
499
|
+
const wheelSnapToNearest = React.useCallback(() => {
|
|
500
|
+
setIsWheeling(false);
|
|
501
|
+
let roundedPosition = Math.round(currentPositionRef.current);
|
|
502
|
+
if (!loop) {
|
|
503
|
+
roundedPosition = Math.max(0, Math.min(data.length - 1, roundedPosition));
|
|
539
504
|
}
|
|
540
|
-
|
|
541
|
-
|
|
505
|
+
if (roundedPosition !== currentPositionRef.current) {
|
|
506
|
+
animateToPosition(roundedPosition);
|
|
507
|
+
} else {
|
|
508
|
+
snapAndNotify(roundedPosition);
|
|
509
|
+
}
|
|
510
|
+
wheelTimeoutRef.current = null;
|
|
511
|
+
}, [loop, data.length, snapAndNotify, animateToPosition]);
|
|
542
512
|
const handleWheel = React.useCallback(
|
|
543
513
|
(e) => {
|
|
544
514
|
const isInteractionDisabled = disabled || readOnly;
|
|
@@ -559,7 +529,7 @@ const Picker = core.polymorphicFactory((_props, ref) => {
|
|
|
559
529
|
} else if (e.deltaMode === 2) {
|
|
560
530
|
delta *= 100;
|
|
561
531
|
}
|
|
562
|
-
if (isWindows
|
|
532
|
+
if (isWindows) {
|
|
563
533
|
sensitivity *= 0.2;
|
|
564
534
|
}
|
|
565
535
|
delta *= sensitivity;
|
|
@@ -580,61 +550,38 @@ const Picker = core.polymorphicFactory((_props, ref) => {
|
|
|
580
550
|
lastWheelEventTime.current = Date.now();
|
|
581
551
|
wheelTimeoutRef.current = setTimeout(() => {
|
|
582
552
|
if (Date.now() - lastWheelEventTime.current >= 150) {
|
|
583
|
-
|
|
584
|
-
let roundedPosition = Math.round(currentPosition);
|
|
585
|
-
if (!loop) {
|
|
586
|
-
roundedPosition = Math.max(0, Math.min(data.length - 1, roundedPosition));
|
|
587
|
-
}
|
|
588
|
-
if (roundedPosition !== currentPosition) {
|
|
589
|
-
animateToPosition(roundedPosition);
|
|
590
|
-
} else {
|
|
591
|
-
if (!disabled) {
|
|
592
|
-
const realIndex2 = loop ? (roundedPosition % data.length + data.length) % data.length : roundedPosition;
|
|
593
|
-
onChange?.(data[realIndex2]);
|
|
594
|
-
}
|
|
595
|
-
const realIndex = loop ? (roundedPosition % data.length + data.length) % data.length : roundedPosition;
|
|
596
|
-
prevValueRef.current = data[realIndex];
|
|
597
|
-
}
|
|
598
|
-
wheelTimeoutRef.current = null;
|
|
553
|
+
wheelSnapToNearest();
|
|
599
554
|
} else {
|
|
600
555
|
if (wheelTimeoutRef.current) {
|
|
601
556
|
clearTimeout(wheelTimeoutRef.current);
|
|
602
557
|
}
|
|
603
|
-
wheelTimeoutRef.current = setTimeout(
|
|
604
|
-
setIsWheeling(false);
|
|
605
|
-
let roundedPosition = Math.round(currentPosition);
|
|
606
|
-
if (!loop) {
|
|
607
|
-
roundedPosition = Math.max(0, Math.min(data.length - 1, roundedPosition));
|
|
608
|
-
}
|
|
609
|
-
if (roundedPosition !== currentPosition) {
|
|
610
|
-
animateToPosition(roundedPosition);
|
|
611
|
-
} else {
|
|
612
|
-
if (!disabled) {
|
|
613
|
-
const realIndex2 = loop ? (roundedPosition % data.length + data.length) % data.length : roundedPosition;
|
|
614
|
-
onChange?.(data[realIndex2]);
|
|
615
|
-
}
|
|
616
|
-
const realIndex = loop ? (roundedPosition % data.length + data.length) % data.length : roundedPosition;
|
|
617
|
-
prevValueRef.current = data[realIndex];
|
|
618
|
-
}
|
|
619
|
-
wheelTimeoutRef.current = null;
|
|
620
|
-
}, 150);
|
|
558
|
+
wheelTimeoutRef.current = setTimeout(wheelSnapToNearest, 150);
|
|
621
559
|
}
|
|
622
560
|
}, 150);
|
|
623
561
|
},
|
|
624
562
|
[
|
|
625
|
-
currentPosition,
|
|
626
563
|
data,
|
|
627
564
|
loop,
|
|
628
565
|
isAnimating,
|
|
629
566
|
disabled,
|
|
630
567
|
readOnly,
|
|
631
568
|
itemHeight,
|
|
632
|
-
onChange,
|
|
633
569
|
wheelSensitivity,
|
|
634
570
|
isMomentumScrolling,
|
|
635
|
-
isWindows
|
|
571
|
+
isWindows,
|
|
572
|
+
wheelSnapToNearest
|
|
636
573
|
]
|
|
637
574
|
);
|
|
575
|
+
React.useEffect(() => {
|
|
576
|
+
const el = containerRef.current;
|
|
577
|
+
if (!el || disabled || readOnly) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
el.addEventListener("wheel", handleWheel, { passive: false });
|
|
581
|
+
return () => {
|
|
582
|
+
el.removeEventListener("wheel", handleWheel);
|
|
583
|
+
};
|
|
584
|
+
}, [handleWheel, disabled, readOnly]);
|
|
638
585
|
const handleKeyDown = React.useCallback(
|
|
639
586
|
(e) => {
|
|
640
587
|
const isInteractionDisabled = disabled || readOnly;
|
|
@@ -696,7 +643,7 @@ const Picker = core.polymorphicFactory((_props, ref) => {
|
|
|
696
643
|
}
|
|
697
644
|
}
|
|
698
645
|
},
|
|
699
|
-
[currentPosition, data.length, loop, disabled, readOnly,
|
|
646
|
+
[currentPosition, data.length, loop, disabled, readOnly, animateToPosition]
|
|
700
647
|
);
|
|
701
648
|
const handleFocus = () => {
|
|
702
649
|
if (!disabled) {
|
|
@@ -718,33 +665,25 @@ const Picker = core.polymorphicFactory((_props, ref) => {
|
|
|
718
665
|
const currentRoundedPos = Math.round(currentPosition);
|
|
719
666
|
const directVisualPath = virtualIndex - currentRoundedPos;
|
|
720
667
|
animateToPosition(currentRoundedPos + directVisualPath);
|
|
721
|
-
|
|
722
|
-
onChange?.(data[clickedIndex]);
|
|
723
|
-
}
|
|
724
|
-
prevValueRef.current = data[clickedIndex];
|
|
668
|
+
snapAndNotify(clickedIndex);
|
|
725
669
|
};
|
|
726
|
-
const
|
|
670
|
+
const flooredPosition = Math.floor(currentPosition);
|
|
671
|
+
const continuousIndices = React.useMemo(() => {
|
|
727
672
|
if (!loop || data.length <= 1) {
|
|
728
673
|
return Array.from({ length: data.length }, (_, i) => ({ dataIndex: i, virtualIndex: i }));
|
|
729
674
|
}
|
|
730
|
-
const duplicatesPerSide = Math.max(
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
data.length * 2
|
|
735
|
-
);
|
|
736
|
-
const continuousIndices = [];
|
|
737
|
-
const startIndex = Math.floor(currentPosition) - duplicatesPerSide;
|
|
738
|
-
const endIndex = Math.floor(currentPosition) + duplicatesPerSide;
|
|
675
|
+
const duplicatesPerSide = Math.max((visibleItems || 5) * 2, data.length * 2);
|
|
676
|
+
const indices = [];
|
|
677
|
+
const startIndex = flooredPosition - duplicatesPerSide;
|
|
678
|
+
const endIndex = flooredPosition + duplicatesPerSide;
|
|
739
679
|
for (let i = startIndex; i <= endIndex; i++) {
|
|
740
680
|
const dataIndex = (i % data.length + data.length) % data.length;
|
|
741
|
-
|
|
681
|
+
indices.push({ dataIndex, virtualIndex: i });
|
|
742
682
|
}
|
|
743
|
-
return
|
|
744
|
-
};
|
|
683
|
+
return indices;
|
|
684
|
+
}, [loop, data.length, visibleItems, flooredPosition]);
|
|
745
685
|
const renderItems = () => {
|
|
746
686
|
const items = [];
|
|
747
|
-
const continuousIndices = createContinuousIndices();
|
|
748
687
|
const visibleRange = Math.max(
|
|
749
688
|
halfVisible + 1,
|
|
750
689
|
// Add just 1 extra item on each side for smoother scrolling
|
|
@@ -754,7 +693,7 @@ const Picker = core.polymorphicFactory((_props, ref) => {
|
|
|
754
693
|
for (const { dataIndex, virtualIndex } of continuousIndices) {
|
|
755
694
|
const relativePos = virtualIndex - currentPosition;
|
|
756
695
|
if (Math.abs(relativePos) <= visibleRange) {
|
|
757
|
-
const itemOffset = relativePos * (itemHeight || 40)
|
|
696
|
+
const itemOffset = relativePos * (itemHeight || 40);
|
|
758
697
|
const distanceFromCenter = Math.abs(relativePos);
|
|
759
698
|
const maxDistance = halfVisible;
|
|
760
699
|
const normalizedDistance = Math.min(distanceFromCenter / maxDistance, 1);
|
|
@@ -829,14 +768,7 @@ const Picker = core.polymorphicFactory((_props, ref) => {
|
|
|
829
768
|
return /* @__PURE__ */ React.createElement(
|
|
830
769
|
core.Box,
|
|
831
770
|
{
|
|
832
|
-
ref:
|
|
833
|
-
if (typeof ref === "function") {
|
|
834
|
-
ref(node);
|
|
835
|
-
} else if (ref) {
|
|
836
|
-
ref.current = node;
|
|
837
|
-
}
|
|
838
|
-
rootRef.current = node;
|
|
839
|
-
},
|
|
771
|
+
ref: mergedRootRef,
|
|
840
772
|
...getStyles("root", {
|
|
841
773
|
style: {
|
|
842
774
|
perspective: enable3D ? `${perspective}px` : void 0
|
|
@@ -865,7 +797,6 @@ const Picker = core.polymorphicFactory((_props, ref) => {
|
|
|
865
797
|
className: Picker_module.container,
|
|
866
798
|
onMouseDown: disabled || readOnly ? void 0 : handleMouseDown,
|
|
867
799
|
onTouchStart: disabled || readOnly ? void 0 : handleTouchStart,
|
|
868
|
-
onWheel: disabled || readOnly ? void 0 : handleWheel,
|
|
869
800
|
onKeyDown: disabled || readOnly ? void 0 : handleKeyDown,
|
|
870
801
|
onFocus: handleFocus,
|
|
871
802
|
onBlur: handleBlur,
|