@ncdai/react-wheel-picker 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -54,5 +54,4 @@ type WheelPickerWrapperProps = {
54
54
  declare const WheelPickerWrapper: React$1.FC<WheelPickerWrapperProps>;
55
55
  declare const WheelPicker: React$1.FC<WheelPickerProps>;
56
56
 
57
- export { WheelPicker, WheelPickerWrapper };
58
- export type { WheelPickerClassNames, WheelPickerOption };
57
+ export { WheelPicker, type WheelPickerClassNames, type WheelPickerOption, WheelPickerWrapper };
package/dist/index.d.ts CHANGED
@@ -54,5 +54,4 @@ type WheelPickerWrapperProps = {
54
54
  declare const WheelPickerWrapper: React$1.FC<WheelPickerWrapperProps>;
55
55
  declare const WheelPicker: React$1.FC<WheelPickerProps>;
56
56
 
57
- export { WheelPicker, WheelPickerWrapper };
58
- export type { WheelPickerClassNames, WheelPickerOption };
57
+ export { WheelPicker, type WheelPickerClassNames, type WheelPickerOption, WheelPickerWrapper };
package/dist/index.js CHANGED
@@ -1,488 +1 @@
1
- 'use client';
2
- Object.defineProperty(exports, '__esModule', { value: true });
3
-
4
- var React = require('react');
5
-
6
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
-
8
- var React__default = /*#__PURE__*/_interopDefault(React);
9
-
10
- /* eslint-disable @typescript-eslint/no-explicit-any */ // This code comes from https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/useControllableState.tsx
11
- function useCallbackRef(callback) {
12
- const callbackRef = React__default.default.useRef(callback);
13
- React__default.default.useEffect(()=>{
14
- callbackRef.current = callback;
15
- });
16
- // https://github.com/facebook/react/issues/19240
17
- return React__default.default.useMemo(()=>(...args)=>callbackRef.current == null ? void 0 : callbackRef.current.call(callbackRef, ...args), []);
18
- }
19
- function useUncontrolledState({ defaultProp, onChange }) {
20
- const uncontrolledState = React__default.default.useState(defaultProp);
21
- const [value] = uncontrolledState;
22
- const prevValueRef = React__default.default.useRef(value);
23
- const handleChange = useCallbackRef(onChange);
24
- React__default.default.useEffect(()=>{
25
- if (prevValueRef.current !== value) {
26
- handleChange(value);
27
- prevValueRef.current = value;
28
- }
29
- }, [
30
- value,
31
- prevValueRef,
32
- handleChange
33
- ]);
34
- return uncontrolledState;
35
- }
36
- function useControllableState({ prop, defaultProp, onChange = ()=>{} }) {
37
- const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({
38
- defaultProp,
39
- onChange
40
- });
41
- const isControlled = prop !== undefined;
42
- const value = isControlled ? prop : uncontrolledProp;
43
- const handleChange = useCallbackRef(onChange);
44
- const setValue = React__default.default.useCallback((nextValue)=>{
45
- if (isControlled) {
46
- const setter = nextValue;
47
- const value = typeof nextValue === "function" ? setter(prop) : nextValue;
48
- if (value !== prop) handleChange(value);
49
- } else {
50
- setUncontrolledProp(nextValue);
51
- }
52
- }, [
53
- isControlled,
54
- prop,
55
- setUncontrolledProp,
56
- handleChange
57
- ]);
58
- return [
59
- value,
60
- setValue
61
- ];
62
- }
63
-
64
- const WHEEL_THROTTLE = 150; // ms
65
- const RESISTANCE = 0.3; // Resistance when scrolling above the top or below the bottom
66
- const MAX_VELOCITY = 30; // Maximum velocity for the scroll animation
67
- const easeOutCubic = (p)=>Math.pow(p - 1, 3) + 1;
68
- const WheelPickerWrapper = ({ className, children })=>{
69
- return /*#__PURE__*/ React__default.default.createElement("div", {
70
- className: className,
71
- "data-rwp-wrapper": true
72
- }, children);
73
- };
74
- const WheelPicker = ({ defaultValue, value: valueProp, onValueChange, options: optionsProp, infinite: infiniteProp = false, visibleCount: countProp = 20, dragSensitivity: dragSensitivityProp = 3, classNames })=>{
75
- var _optionsProp__value;
76
- const [value = (()=>{
77
- var _optionsProp_;
78
- return (_optionsProp__value = (_optionsProp_ = optionsProp[0]) == null ? void 0 : _optionsProp_.value) != null ? _optionsProp__value : "";
79
- })(), setValue] = useControllableState({
80
- defaultProp: defaultValue,
81
- prop: valueProp,
82
- onChange: onValueChange
83
- });
84
- const options = React.useMemo(()=>{
85
- if (!infiniteProp) {
86
- return optionsProp;
87
- }
88
- const result = [];
89
- const halfCount = Math.ceil(countProp / 2);
90
- if (optionsProp.length === 0) {
91
- return result;
92
- }
93
- while(result.length < halfCount){
94
- result.push(...optionsProp);
95
- }
96
- return result;
97
- }, [
98
- countProp,
99
- optionsProp,
100
- infiniteProp
101
- ]);
102
- const itemHeight = 28;
103
- const halfItemHeight = itemHeight * 0.5;
104
- const itemAngle = 360 / countProp;
105
- const radius = itemHeight / Math.tan(itemAngle * Math.PI / 180);
106
- const containerHeight = Math.round(radius * 2 + itemHeight * 0.25);
107
- const quarterCount = countProp >> 2; // Divide by 4
108
- const baseDeceleration = dragSensitivityProp * 10;
109
- const snapBackDeceleration = 10;
110
- const containerRef = React.useRef(null);
111
- const wheelItemsRef = React.useRef(null);
112
- const highlightListRef = React.useRef(null);
113
- const scrollRef = React.useRef(0);
114
- const moveId = React.useRef(0);
115
- const dragingRef = React.useRef(false);
116
- const lastWheelRef = React.useRef(0);
117
- const touchDataRef = React.useRef({
118
- startY: 0,
119
- yList: []
120
- });
121
- const dragControllerRef = React.useRef(null);
122
- const renderWheelItems = React.useMemo(()=>{
123
- const renderItem = (item, index, angle)=>/*#__PURE__*/ React__default.default.createElement("li", {
124
- key: index,
125
- className: classNames == null ? void 0 : classNames.optionItem,
126
- "data-rwp-option": true,
127
- "data-index": index,
128
- style: {
129
- top: -14,
130
- height: itemHeight,
131
- lineHeight: `${itemHeight}px`,
132
- transform: `rotateX(${angle}deg) translateZ(${radius}px)`,
133
- visibility: "hidden"
134
- }
135
- }, item.label);
136
- const items = options.map((option, index)=>renderItem(option, index, -itemAngle * index));
137
- if (infiniteProp) {
138
- for(let i = 0; i < quarterCount; ++i){
139
- const prependIndex = -i - 1;
140
- const appendIndex = i + options.length;
141
- items.unshift(renderItem(options[options.length - i - 1], prependIndex, itemAngle * (i + 1)));
142
- items.push(renderItem(options[i], appendIndex, -itemAngle * appendIndex));
143
- }
144
- }
145
- return items;
146
- }, [
147
- halfItemHeight,
148
- infiniteProp,
149
- itemAngle,
150
- options,
151
- quarterCount,
152
- radius,
153
- classNames == null ? void 0 : classNames.optionItem
154
- ]);
155
- const renderHighlightItems = React.useMemo(()=>{
156
- const renderItem = (item, key)=>/*#__PURE__*/ React__default.default.createElement("li", {
157
- key: key,
158
- "data-slot": "highlight-item",
159
- className: classNames == null ? void 0 : classNames.highlightItem,
160
- style: {
161
- height: itemHeight
162
- }
163
- }, item.label);
164
- const items = options.map((option, index)=>renderItem(option, index));
165
- if (infiniteProp) {
166
- const firstItem = options[0];
167
- const lastItem = options[options.length - 1];
168
- items.unshift(renderItem(lastItem, "infinite-start"));
169
- items.push(renderItem(firstItem, "infinite-end"));
170
- }
171
- return items;
172
- }, [
173
- classNames == null ? void 0 : classNames.highlightItem,
174
- infiniteProp,
175
- options
176
- ]);
177
- const normalizeScroll = (scroll)=>(scroll % options.length + options.length) % options.length;
178
- const scrollTo = (scroll)=>{
179
- const normalizedScroll = infiniteProp ? normalizeScroll(scroll) : scroll;
180
- if (wheelItemsRef.current) {
181
- const transform = `translateZ(${-radius}px) rotateX(${itemAngle * normalizedScroll}deg)`;
182
- wheelItemsRef.current.style.transform = transform;
183
- wheelItemsRef.current.childNodes.forEach((node)=>{
184
- const li = node;
185
- const distance = Math.abs(Number(li.dataset.index) - normalizedScroll);
186
- li.style.visibility = distance > quarterCount ? "hidden" : "visible";
187
- });
188
- }
189
- if (highlightListRef.current) {
190
- highlightListRef.current.style.transform = `translateY(${-normalizedScroll * itemHeight}px)`;
191
- }
192
- return normalizedScroll;
193
- };
194
- const cancelAnimation = ()=>{
195
- cancelAnimationFrame(moveId.current);
196
- };
197
- const animateScroll = (startScroll, endScroll, duration, onComplete)=>{
198
- if (startScroll === endScroll || duration === 0) {
199
- scrollTo(startScroll);
200
- return;
201
- }
202
- const startTime = performance.now();
203
- const totalDistance = endScroll - startScroll;
204
- const tick = (currentTime)=>{
205
- const elapsed = (currentTime - startTime) / 1000;
206
- if (elapsed < duration) {
207
- const progress = easeOutCubic(elapsed / duration);
208
- scrollRef.current = scrollTo(startScroll + progress * totalDistance);
209
- moveId.current = requestAnimationFrame(tick);
210
- } else {
211
- cancelAnimation();
212
- scrollRef.current = scrollTo(endScroll);
213
- onComplete == null ? void 0 : onComplete();
214
- }
215
- };
216
- requestAnimationFrame(tick);
217
- };
218
- const selectByScroll = (scroll)=>{
219
- const normalized = normalizeScroll(scroll) | 0;
220
- const boundedScroll = infiniteProp ? normalized : Math.min(Math.max(normalized, 0), options.length - 1);
221
- if (!infiniteProp && boundedScroll !== scroll) return;
222
- scrollRef.current = scrollTo(boundedScroll);
223
- const selected = options[scrollRef.current];
224
- setValue(selected.value);
225
- };
226
- const selectByValue = (value)=>{
227
- const index = options.findIndex((opt)=>opt.value === value);
228
- if (index === -1) {
229
- console.error("Invalid value selected:", value);
230
- return;
231
- }
232
- cancelAnimation();
233
- selectByScroll(index);
234
- };
235
- const updateScrollDuringDrag = (e)=>{
236
- try {
237
- var _e_touches_, _e_touches;
238
- const currentY = (e instanceof MouseEvent ? e.clientY : (_e_touches = e.touches) == null ? void 0 : (_e_touches_ = _e_touches[0]) == null ? void 0 : _e_touches_.clientY) || 0;
239
- const touchData = touchDataRef.current;
240
- // Record current Y position with timestamp
241
- touchData.yList.push([
242
- currentY,
243
- Date.now()
244
- ]);
245
- if (touchData.yList.length > 5) {
246
- touchData.yList.shift(); // Keep latest 5 points for velocity calc
247
- }
248
- // Calculate delta in scroll position based on drag distance
249
- const dragDelta = (touchData.startY - currentY) / itemHeight;
250
- let nextScroll = scrollRef.current + dragDelta;
251
- if (infiniteProp) {
252
- // Wrap scroll for infinite lists
253
- nextScroll = normalizeScroll(nextScroll);
254
- } else {
255
- const maxIndex = options.length;
256
- if (nextScroll < 0) {
257
- // Apply resistance when dragging above top
258
- nextScroll *= RESISTANCE;
259
- } else if (nextScroll > maxIndex) {
260
- // Apply resistance when dragging below bottom
261
- nextScroll = maxIndex + (nextScroll - maxIndex) * RESISTANCE;
262
- }
263
- }
264
- // Update visual scroll and store position
265
- touchData.touchScroll = scrollTo(nextScroll);
266
- } catch (error) {
267
- console.error("Error in updateScrollDuringDrag:", error);
268
- }
269
- };
270
- const handleDragMoveEvent = (event)=>{
271
- if (!dragingRef.current && !containerRef.current.contains(event.target) && event.target !== containerRef.current) {
272
- return;
273
- }
274
- if (event.cancelable) {
275
- event.preventDefault();
276
- }
277
- if (options.length) {
278
- updateScrollDuringDrag(event);
279
- }
280
- };
281
- const initiateDragGesture = (event)=>{
282
- try {
283
- var _containerRef_current, _event_touches_, _event_touches;
284
- dragingRef.current = true;
285
- const controller = new AbortController();
286
- const { signal } = controller;
287
- dragControllerRef.current = controller;
288
- // Listen to movement events
289
- const passiveOpts = {
290
- signal,
291
- passive: false
292
- };
293
- (_containerRef_current = containerRef.current) == null ? void 0 : _containerRef_current.addEventListener("touchmove", handleDragMoveEvent, passiveOpts);
294
- document.addEventListener("mousemove", handleDragMoveEvent, passiveOpts);
295
- const startY = (event instanceof MouseEvent ? event.clientY : (_event_touches = event.touches) == null ? void 0 : (_event_touches_ = _event_touches[0]) == null ? void 0 : _event_touches_.clientY) || 0;
296
- // Initialize touch tracking
297
- const touchData = touchDataRef.current;
298
- touchData.startY = startY;
299
- touchData.yList = [
300
- [
301
- startY,
302
- Date.now()
303
- ]
304
- ];
305
- touchData.touchScroll = scrollRef.current;
306
- // Stop any ongoing scroll animation
307
- cancelAnimation();
308
- } catch (error) {
309
- console.error("Error in initiateDragGesture:", error);
310
- }
311
- };
312
- const handleDragStartEvent = React.useCallback((e)=>{
313
- const isDragging = dragingRef.current;
314
- const isTargetValid = containerRef.current.contains(e.target) || e.target === containerRef.current;
315
- if ((isDragging || isTargetValid) && e.cancelable) {
316
- e.preventDefault();
317
- if (options.length) {
318
- initiateDragGesture(e);
319
- }
320
- }
321
- }, // eslint-disable-next-line react-hooks/exhaustive-deps
322
- [
323
- initiateDragGesture
324
- ]);
325
- const decelerateAndAnimateScroll = (initialVelocity)=>{
326
- const currentScroll = scrollRef.current;
327
- let targetScroll = currentScroll;
328
- let deceleration = initialVelocity > 0 ? -baseDeceleration : baseDeceleration;
329
- let duration = 0;
330
- // Clamp utility to constrain a value within bounds
331
- const clamp = (value, min, max)=>Math.max(min, Math.min(value, max));
332
- if (infiniteProp) {
333
- // Infinite mode: apply uniform deceleration to calculate scroll distance
334
- duration = Math.abs(initialVelocity / deceleration);
335
- const scrollDistance = initialVelocity * duration + 0.5 * deceleration * duration * duration;
336
- targetScroll = Math.round(currentScroll + scrollDistance);
337
- } else if (currentScroll < 0 || currentScroll > options.length - 1) {
338
- // Out-of-bounds: snap back to nearest valid scroll index
339
- const target = clamp(currentScroll, 0, options.length - 1);
340
- const scrollDistance = currentScroll - target;
341
- deceleration = snapBackDeceleration;
342
- duration = Math.sqrt(Math.abs(scrollDistance / deceleration));
343
- initialVelocity = deceleration * duration;
344
- initialVelocity = currentScroll > 0 ? -initialVelocity : initialVelocity;
345
- targetScroll = target;
346
- } else {
347
- // Normal decelerated scroll within bounds
348
- duration = Math.abs(initialVelocity / deceleration);
349
- const scrollDistance = initialVelocity * duration + 0.5 * deceleration * duration * duration;
350
- targetScroll = Math.round(currentScroll + scrollDistance);
351
- targetScroll = clamp(targetScroll, 0, options.length - 1);
352
- const adjustedDistance = targetScroll - currentScroll;
353
- duration = Math.sqrt(Math.abs(adjustedDistance / deceleration));
354
- }
355
- // Start animation to target scroll position with calculated duration
356
- animateScroll(currentScroll, targetScroll, duration, ()=>{
357
- selectByScroll(scrollRef.current); // Ensure selected item updates at end
358
- });
359
- // Fallback selection update (in case animation callback fails)
360
- selectByScroll(scrollRef.current);
361
- };
362
- const finalizeDragAndStartInertiaScroll = ()=>{
363
- try {
364
- var _dragControllerRef_current;
365
- (_dragControllerRef_current = dragControllerRef.current) == null ? void 0 : _dragControllerRef_current.abort();
366
- dragControllerRef.current = null;
367
- const touchData = touchDataRef.current;
368
- const yList = touchData.yList;
369
- let velocity = 0;
370
- if (yList.length > 1) {
371
- const len = yList.length;
372
- var _yList_;
373
- const [startY, startTime] = (_yList_ = yList[len - 2]) != null ? _yList_ : [
374
- 0,
375
- 0
376
- ];
377
- var _yList_1;
378
- const [endY, endTime] = (_yList_1 = yList[len - 1]) != null ? _yList_1 : [
379
- 0,
380
- 0
381
- ];
382
- const timeDiff = endTime - startTime;
383
- if (timeDiff > 0) {
384
- const distance = startY - endY;
385
- const velocityPerSecond = distance / itemHeight * 1000 / timeDiff;
386
- const maxVelocity = MAX_VELOCITY;
387
- const direction = velocityPerSecond > 0 ? 1 : -1;
388
- const absVelocity = Math.min(Math.abs(velocityPerSecond), maxVelocity);
389
- velocity = absVelocity * direction;
390
- }
391
- }
392
- var _touchData_touchScroll;
393
- scrollRef.current = (_touchData_touchScroll = touchData.touchScroll) != null ? _touchData_touchScroll : scrollRef.current;
394
- decelerateAndAnimateScroll(velocity);
395
- } catch (error) {
396
- console.error("Error in finalizeDragAndStartInertiaScroll:", error);
397
- } finally{
398
- dragingRef.current = false;
399
- }
400
- };
401
- const handleDragEndEvent = React.useCallback((event)=>{
402
- if (!options.length) return;
403
- const isDragging = dragingRef.current;
404
- const isTargetValid = containerRef.current.contains(event.target) || event.target === containerRef.current;
405
- if ((isDragging || isTargetValid) && event.cancelable) {
406
- event.preventDefault();
407
- finalizeDragAndStartInertiaScroll();
408
- }
409
- }, // eslint-disable-next-line react-hooks/exhaustive-deps
410
- [
411
- finalizeDragAndStartInertiaScroll
412
- ]);
413
- const scrollByWheel = (event)=>{
414
- event.preventDefault();
415
- const now = Date.now();
416
- if (now - lastWheelRef.current < WHEEL_THROTTLE) return;
417
- const direction = Math.sign(event.deltaY);
418
- if (direction !== 0) {
419
- selectByScroll(scrollRef.current + direction);
420
- lastWheelRef.current = now;
421
- }
422
- };
423
- const handleWheelEvent = React.useCallback((event)=>{
424
- if (!options.length || !containerRef.current) return;
425
- const isDragging = dragingRef.current;
426
- const isTargetValid = containerRef.current.contains(event.target) || event.target === containerRef.current;
427
- if ((isDragging || isTargetValid) && event.cancelable) {
428
- event.preventDefault();
429
- scrollByWheel(event);
430
- }
431
- }, // eslint-disable-next-line react-hooks/exhaustive-deps
432
- [
433
- scrollByWheel
434
- ]);
435
- React.useEffect(()=>{
436
- const container = containerRef.current;
437
- if (!container) return;
438
- const controller = new AbortController();
439
- const { signal } = controller;
440
- const opts = {
441
- signal,
442
- passive: false
443
- };
444
- container.addEventListener("touchstart", handleDragStartEvent, opts);
445
- container.addEventListener("touchend", handleDragEndEvent, opts);
446
- container.addEventListener("wheel", handleWheelEvent, opts);
447
- document.addEventListener("mousedown", handleDragStartEvent, opts);
448
- document.addEventListener("mouseup", handleDragEndEvent, opts);
449
- return ()=>controller.abort();
450
- }, [
451
- handleDragEndEvent,
452
- handleDragStartEvent,
453
- handleWheelEvent
454
- ]);
455
- React.useEffect(()=>{
456
- selectByValue(value);
457
- // eslint-disable-next-line react-hooks/exhaustive-deps
458
- }, [
459
- value,
460
- valueProp
461
- ]);
462
- return /*#__PURE__*/ React__default.default.createElement("div", {
463
- ref: containerRef,
464
- "data-rwp": true,
465
- style: {
466
- height: containerHeight
467
- }
468
- }, /*#__PURE__*/ React__default.default.createElement("ul", {
469
- ref: wheelItemsRef,
470
- "data-rwp-options": true
471
- }, renderWheelItems), /*#__PURE__*/ React__default.default.createElement("div", {
472
- className: classNames == null ? void 0 : classNames.highlightWrapper,
473
- "data-rwp-highlight-wrapper": true,
474
- style: {
475
- height: itemHeight,
476
- lineHeight: itemHeight + "px"
477
- }
478
- }, /*#__PURE__*/ React__default.default.createElement("ul", {
479
- ref: highlightListRef,
480
- "data-rwp-highlight-list": true,
481
- style: {
482
- top: infiniteProp ? -28 : undefined
483
- }
484
- }, renderHighlightItems)));
485
- };
486
-
487
- exports.WheelPicker = WheelPicker;
488
- exports.WheelPickerWrapper = WheelPickerWrapper;
1
+ "use client";var vt=Object.create;var x=Object.defineProperty;var Et=Object.getOwnPropertyDescriptor;var Tt=Object.getOwnPropertyNames;var Dt=Object.getPrototypeOf,Mt=Object.prototype.hasOwnProperty;var St=(c,l)=>{for(var d in l)x(c,d,{get:l[d],enumerable:!0})},et=(c,l,d,h)=>{if(l&&typeof l=="object"||typeof l=="function")for(let i of Tt(l))!Mt.call(c,i)&&i!==d&&x(c,i,{get:()=>l[i],enumerable:!(h=Et(l,i))||h.enumerable});return c};var nt=(c,l,d)=>(d=c!=null?vt(Dt(c)):{},et(l||!c||!c.__esModule?x(d,"default",{value:c,enumerable:!0}):d,c)),yt=c=>et(x({},"__esModule",{value:!0}),c);var xt={};St(xt,{WheelPicker:()=>kt,WheelPickerWrapper:()=>wt});module.exports=yt(xt);var M=nt(require("react")),u=require("react");var D=nt(require("react"));function rt(c){let l=D.default.useRef(c);return D.default.useEffect(()=>{l.current=c}),D.default.useMemo(()=>(...d)=>{var h;return(h=l.current)==null?void 0:h.call(l,...d)},[])}function Lt({defaultProp:c,onChange:l}){let d=D.default.useState(c),[h]=d,i=D.default.useRef(h),E=rt(l);return D.default.useEffect(()=>{i.current!==h&&(E(h),i.current=h)},[h,i,E]),d}function ot({prop:c,defaultProp:l,onChange:d=()=>{}}){let[h,i]=Lt({defaultProp:l,onChange:d}),E=c!==void 0,A=E?c:h,f=rt(d),W=D.default.useCallback(S=>{if(E){let m=typeof S=="function"?S(c):S;m!==c&&f(m)}else i(S)},[E,c,i,f]);return[A,W]}var It=150,ct=.3,Wt=30,Ct=c=>Math.pow(c-1,3)+1,wt=({className:c,children:l})=>M.default.createElement("div",{className:c,"data-rwp-wrapper":!0},l),kt=({defaultValue:c,value:l,onValueChange:d,options:h,infinite:i=!1,visibleCount:E=20,dragSensitivity:A=3,classNames:f})=>{var Q,V;let[W=(V=(Q=h[0])==null?void 0:Q.value)!=null?V:"",S]=ot({defaultProp:c,prop:l,onChange:d}),s=(0,u.useMemo)(()=>{if(!i)return h;let t=[],e=Math.ceil(E/2);if(h.length===0)return t;for(;t.length<e;)t.push(...h);return t},[E,h,i]),m=28,z=m*.5,y=360/E,C=m/Math.tan(y*Math.PI/180),st=Math.round(C*2+m*.25),Y=E>>2,B=A*10,at=10,b=(0,u.useRef)(null),w=(0,u.useRef)(null),R=(0,u.useRef)(null),v=(0,u.useRef)(0),X=(0,u.useRef)(0),L=(0,u.useRef)(!1),G=(0,u.useRef)(0),H=(0,u.useRef)({startY:0,yList:[]}),O=(0,u.useRef)(null),lt=(0,u.useMemo)(()=>{let t=(n,r,o)=>M.default.createElement("li",{key:r,className:f==null?void 0:f.optionItem,"data-rwp-option":!0,"data-index":r,style:{top:-z,height:m,lineHeight:`${m}px`,transform:`rotateX(${o}deg) translateZ(${C}px)`,visibility:"hidden"}},n.label),e=s.map((n,r)=>t(n,r,-y*r));if(i)for(let n=0;n<Y;++n){let r=-n-1,o=n+s.length;e.unshift(t(s[s.length-n-1],r,y*(n+1))),e.push(t(s[n],o,-y*o))}return e},[z,i,y,s,Y,C,f==null?void 0:f.optionItem]),it=(0,u.useMemo)(()=>{let t=(n,r)=>M.default.createElement("li",{key:r,"data-slot":"highlight-item",className:f==null?void 0:f.highlightItem,style:{height:m}},n.label),e=s.map((n,r)=>t(n,r));if(i){let n=s[0],r=s[s.length-1];e.unshift(t(r,"infinite-start")),e.push(t(n,"infinite-end"))}return e},[f==null?void 0:f.highlightItem,i,s]),F=t=>(t%s.length+s.length)%s.length,I=t=>{let e=i?F(t):t;if(w.current){let n=`translateZ(${-C}px) rotateX(${y*e}deg)`;w.current.style.transform=n,w.current.childNodes.forEach(r=>{let o=r,p=Math.abs(Number(o.dataset.index)-e);o.style.visibility=p>Y?"hidden":"visible"})}return R.current&&(R.current.style.transform=`translateY(${-e*m}px)`),e},P=()=>{cancelAnimationFrame(X.current)},ut=(t,e,n,r)=>{if(t===e||n===0){I(t);return}let o=performance.now(),p=e-t,a=g=>{let T=(g-o)/1e3;if(T<n){let q=Ct(T/n);v.current=I(t+q*p),X.current=requestAnimationFrame(a)}else P(),v.current=I(e),r==null||r()};requestAnimationFrame(a)},k=t=>{let e=F(t)|0,n=i?e:Math.min(Math.max(e,0),s.length-1);if(!i&&n!==t)return;v.current=I(n);let r=s[v.current];S(r.value)},ht=t=>{let e=s.findIndex(n=>n.value===t);if(e===-1){console.error("Invalid value selected:",t);return}P(),k(e)},dt=t=>{var e,n;try{let r=(t instanceof MouseEvent?t.clientY:(n=(e=t.touches)==null?void 0:e[0])==null?void 0:n.clientY)||0,o=H.current;o.yList.push([r,Date.now()]),o.yList.length>5&&o.yList.shift();let p=(o.startY-r)/m,a=v.current+p;if(i)a=F(a);else{let g=s.length;a<0?a*=ct:a>g&&(a=g+(a-g)*ct)}o.touchScroll=I(a)}catch(r){console.error("Error in updateScrollDuringDrag:",r)}},Z=t=>{!L.current&&!b.current.contains(t.target)&&t.target!==b.current||(t.cancelable&&t.preventDefault(),s.length&&dt(t))},_=t=>{var e,n,r;try{L.current=!0;let o=new AbortController,{signal:p}=o;O.current=o;let a={signal:p,passive:!1};(e=b.current)==null||e.addEventListener("touchmove",Z,a),document.addEventListener("mousemove",Z,a);let g=(t instanceof MouseEvent?t.clientY:(r=(n=t.touches)==null?void 0:n[0])==null?void 0:r.clientY)||0,T=H.current;T.startY=g,T.yList=[[g,Date.now()]],T.touchScroll=v.current,P()}catch(o){console.error("Error in initiateDragGesture:",o)}},U=(0,u.useCallback)(t=>{let e=L.current,n=b.current.contains(t.target)||t.target===b.current;(e||n)&&t.cancelable&&(t.preventDefault(),s.length&&_(t))},[_]),ft=t=>{let e=v.current,n=e,r=t>0?-B:B,o=0,p=(a,g,T)=>Math.max(g,Math.min(a,T));if(i){o=Math.abs(t/r);let a=t*o+.5*r*o*o;n=Math.round(e+a)}else if(e<0||e>s.length-1){let a=p(e,0,s.length-1),g=e-a;r=at,o=Math.sqrt(Math.abs(g/r)),t=r*o,t=e>0?-t:t,n=a}else{o=Math.abs(t/r);let a=t*o+.5*r*o*o;n=Math.round(e+a),n=p(n,0,s.length-1);let g=n-e;o=Math.sqrt(Math.abs(g/r))}ut(e,n,o,()=>{k(v.current)}),k(v.current)},j=()=>{var t,e,n,r;try{(t=O.current)==null||t.abort(),O.current=null;let o=H.current,p=o.yList,a=0;if(p.length>1){let g=p.length,[T,q]=(e=p[g-2])!=null?e:[0,0],[gt,mt]=(n=p[g-1])!=null?n:[0,0],N=mt-q;if(N>0){let tt=(T-gt)/m*1e3/N,pt=Wt,bt=tt>0?1:-1;a=Math.min(Math.abs(tt),pt)*bt}}v.current=(r=o.touchScroll)!=null?r:v.current,ft(a)}catch(o){console.error("Error in finalizeDragAndStartInertiaScroll:",o)}finally{L.current=!1}},$=(0,u.useCallback)(t=>{if(!s.length)return;let e=L.current,n=b.current.contains(t.target)||t.target===b.current;(e||n)&&t.cancelable&&(t.preventDefault(),j())},[j]),K=t=>{t.preventDefault();let e=Date.now();if(e-G.current<It)return;let n=Math.sign(t.deltaY);n!==0&&(k(v.current+n),G.current=e)},J=(0,u.useCallback)(t=>{if(!s.length||!b.current)return;let e=L.current,n=b.current.contains(t.target)||t.target===b.current;(e||n)&&t.cancelable&&(t.preventDefault(),K(t))},[K]);return(0,u.useEffect)(()=>{let t=b.current;if(!t)return;let e=new AbortController,{signal:n}=e,r={signal:n,passive:!1};return t.addEventListener("touchstart",U,r),t.addEventListener("touchend",$,r),t.addEventListener("wheel",J,r),document.addEventListener("mousedown",U,r),document.addEventListener("mouseup",$,r),()=>e.abort()},[$,U,J]),(0,u.useEffect)(()=>{ht(W)},[W,l]),M.default.createElement("div",{ref:b,"data-rwp":!0,style:{height:st}},M.default.createElement("ul",{ref:w,"data-rwp-options":!0},lt),M.default.createElement("div",{className:f==null?void 0:f.highlightWrapper,"data-rwp-highlight-wrapper":!0,style:{height:m,lineHeight:m+"px"}},M.default.createElement("ul",{ref:R,"data-rwp-highlight-list":!0,style:{top:i?-m:void 0}},it)))};0&&(module.exports={WheelPicker,WheelPickerWrapper});
package/dist/index.mjs CHANGED
@@ -1,481 +1 @@
1
- 'use client';
2
- import React, { useMemo, useRef, useCallback, useEffect } from 'react';
3
-
4
- /* eslint-disable @typescript-eslint/no-explicit-any */ // This code comes from https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/useControllableState.tsx
5
- function useCallbackRef(callback) {
6
- const callbackRef = React.useRef(callback);
7
- React.useEffect(()=>{
8
- callbackRef.current = callback;
9
- });
10
- // https://github.com/facebook/react/issues/19240
11
- return React.useMemo(()=>(...args)=>callbackRef.current == null ? void 0 : callbackRef.current.call(callbackRef, ...args), []);
12
- }
13
- function useUncontrolledState({ defaultProp, onChange }) {
14
- const uncontrolledState = React.useState(defaultProp);
15
- const [value] = uncontrolledState;
16
- const prevValueRef = React.useRef(value);
17
- const handleChange = useCallbackRef(onChange);
18
- React.useEffect(()=>{
19
- if (prevValueRef.current !== value) {
20
- handleChange(value);
21
- prevValueRef.current = value;
22
- }
23
- }, [
24
- value,
25
- prevValueRef,
26
- handleChange
27
- ]);
28
- return uncontrolledState;
29
- }
30
- function useControllableState({ prop, defaultProp, onChange = ()=>{} }) {
31
- const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({
32
- defaultProp,
33
- onChange
34
- });
35
- const isControlled = prop !== undefined;
36
- const value = isControlled ? prop : uncontrolledProp;
37
- const handleChange = useCallbackRef(onChange);
38
- const setValue = React.useCallback((nextValue)=>{
39
- if (isControlled) {
40
- const setter = nextValue;
41
- const value = typeof nextValue === "function" ? setter(prop) : nextValue;
42
- if (value !== prop) handleChange(value);
43
- } else {
44
- setUncontrolledProp(nextValue);
45
- }
46
- }, [
47
- isControlled,
48
- prop,
49
- setUncontrolledProp,
50
- handleChange
51
- ]);
52
- return [
53
- value,
54
- setValue
55
- ];
56
- }
57
-
58
- const WHEEL_THROTTLE = 150; // ms
59
- const RESISTANCE = 0.3; // Resistance when scrolling above the top or below the bottom
60
- const MAX_VELOCITY = 30; // Maximum velocity for the scroll animation
61
- const easeOutCubic = (p)=>Math.pow(p - 1, 3) + 1;
62
- const WheelPickerWrapper = ({ className, children })=>{
63
- return /*#__PURE__*/ React.createElement("div", {
64
- className: className,
65
- "data-rwp-wrapper": true
66
- }, children);
67
- };
68
- const WheelPicker = ({ defaultValue, value: valueProp, onValueChange, options: optionsProp, infinite: infiniteProp = false, visibleCount: countProp = 20, dragSensitivity: dragSensitivityProp = 3, classNames })=>{
69
- var _optionsProp__value;
70
- const [value = (()=>{
71
- var _optionsProp_;
72
- return (_optionsProp__value = (_optionsProp_ = optionsProp[0]) == null ? void 0 : _optionsProp_.value) != null ? _optionsProp__value : "";
73
- })(), setValue] = useControllableState({
74
- defaultProp: defaultValue,
75
- prop: valueProp,
76
- onChange: onValueChange
77
- });
78
- const options = useMemo(()=>{
79
- if (!infiniteProp) {
80
- return optionsProp;
81
- }
82
- const result = [];
83
- const halfCount = Math.ceil(countProp / 2);
84
- if (optionsProp.length === 0) {
85
- return result;
86
- }
87
- while(result.length < halfCount){
88
- result.push(...optionsProp);
89
- }
90
- return result;
91
- }, [
92
- countProp,
93
- optionsProp,
94
- infiniteProp
95
- ]);
96
- const itemHeight = 28;
97
- const halfItemHeight = itemHeight * 0.5;
98
- const itemAngle = 360 / countProp;
99
- const radius = itemHeight / Math.tan(itemAngle * Math.PI / 180);
100
- const containerHeight = Math.round(radius * 2 + itemHeight * 0.25);
101
- const quarterCount = countProp >> 2; // Divide by 4
102
- const baseDeceleration = dragSensitivityProp * 10;
103
- const snapBackDeceleration = 10;
104
- const containerRef = useRef(null);
105
- const wheelItemsRef = useRef(null);
106
- const highlightListRef = useRef(null);
107
- const scrollRef = useRef(0);
108
- const moveId = useRef(0);
109
- const dragingRef = useRef(false);
110
- const lastWheelRef = useRef(0);
111
- const touchDataRef = useRef({
112
- startY: 0,
113
- yList: []
114
- });
115
- const dragControllerRef = useRef(null);
116
- const renderWheelItems = useMemo(()=>{
117
- const renderItem = (item, index, angle)=>/*#__PURE__*/ React.createElement("li", {
118
- key: index,
119
- className: classNames == null ? void 0 : classNames.optionItem,
120
- "data-rwp-option": true,
121
- "data-index": index,
122
- style: {
123
- top: -14,
124
- height: itemHeight,
125
- lineHeight: `${itemHeight}px`,
126
- transform: `rotateX(${angle}deg) translateZ(${radius}px)`,
127
- visibility: "hidden"
128
- }
129
- }, item.label);
130
- const items = options.map((option, index)=>renderItem(option, index, -itemAngle * index));
131
- if (infiniteProp) {
132
- for(let i = 0; i < quarterCount; ++i){
133
- const prependIndex = -i - 1;
134
- const appendIndex = i + options.length;
135
- items.unshift(renderItem(options[options.length - i - 1], prependIndex, itemAngle * (i + 1)));
136
- items.push(renderItem(options[i], appendIndex, -itemAngle * appendIndex));
137
- }
138
- }
139
- return items;
140
- }, [
141
- halfItemHeight,
142
- infiniteProp,
143
- itemAngle,
144
- options,
145
- quarterCount,
146
- radius,
147
- classNames == null ? void 0 : classNames.optionItem
148
- ]);
149
- const renderHighlightItems = useMemo(()=>{
150
- const renderItem = (item, key)=>/*#__PURE__*/ React.createElement("li", {
151
- key: key,
152
- "data-slot": "highlight-item",
153
- className: classNames == null ? void 0 : classNames.highlightItem,
154
- style: {
155
- height: itemHeight
156
- }
157
- }, item.label);
158
- const items = options.map((option, index)=>renderItem(option, index));
159
- if (infiniteProp) {
160
- const firstItem = options[0];
161
- const lastItem = options[options.length - 1];
162
- items.unshift(renderItem(lastItem, "infinite-start"));
163
- items.push(renderItem(firstItem, "infinite-end"));
164
- }
165
- return items;
166
- }, [
167
- classNames == null ? void 0 : classNames.highlightItem,
168
- infiniteProp,
169
- options
170
- ]);
171
- const normalizeScroll = (scroll)=>(scroll % options.length + options.length) % options.length;
172
- const scrollTo = (scroll)=>{
173
- const normalizedScroll = infiniteProp ? normalizeScroll(scroll) : scroll;
174
- if (wheelItemsRef.current) {
175
- const transform = `translateZ(${-radius}px) rotateX(${itemAngle * normalizedScroll}deg)`;
176
- wheelItemsRef.current.style.transform = transform;
177
- wheelItemsRef.current.childNodes.forEach((node)=>{
178
- const li = node;
179
- const distance = Math.abs(Number(li.dataset.index) - normalizedScroll);
180
- li.style.visibility = distance > quarterCount ? "hidden" : "visible";
181
- });
182
- }
183
- if (highlightListRef.current) {
184
- highlightListRef.current.style.transform = `translateY(${-normalizedScroll * itemHeight}px)`;
185
- }
186
- return normalizedScroll;
187
- };
188
- const cancelAnimation = ()=>{
189
- cancelAnimationFrame(moveId.current);
190
- };
191
- const animateScroll = (startScroll, endScroll, duration, onComplete)=>{
192
- if (startScroll === endScroll || duration === 0) {
193
- scrollTo(startScroll);
194
- return;
195
- }
196
- const startTime = performance.now();
197
- const totalDistance = endScroll - startScroll;
198
- const tick = (currentTime)=>{
199
- const elapsed = (currentTime - startTime) / 1000;
200
- if (elapsed < duration) {
201
- const progress = easeOutCubic(elapsed / duration);
202
- scrollRef.current = scrollTo(startScroll + progress * totalDistance);
203
- moveId.current = requestAnimationFrame(tick);
204
- } else {
205
- cancelAnimation();
206
- scrollRef.current = scrollTo(endScroll);
207
- onComplete == null ? void 0 : onComplete();
208
- }
209
- };
210
- requestAnimationFrame(tick);
211
- };
212
- const selectByScroll = (scroll)=>{
213
- const normalized = normalizeScroll(scroll) | 0;
214
- const boundedScroll = infiniteProp ? normalized : Math.min(Math.max(normalized, 0), options.length - 1);
215
- if (!infiniteProp && boundedScroll !== scroll) return;
216
- scrollRef.current = scrollTo(boundedScroll);
217
- const selected = options[scrollRef.current];
218
- setValue(selected.value);
219
- };
220
- const selectByValue = (value)=>{
221
- const index = options.findIndex((opt)=>opt.value === value);
222
- if (index === -1) {
223
- console.error("Invalid value selected:", value);
224
- return;
225
- }
226
- cancelAnimation();
227
- selectByScroll(index);
228
- };
229
- const updateScrollDuringDrag = (e)=>{
230
- try {
231
- var _e_touches_, _e_touches;
232
- const currentY = (e instanceof MouseEvent ? e.clientY : (_e_touches = e.touches) == null ? void 0 : (_e_touches_ = _e_touches[0]) == null ? void 0 : _e_touches_.clientY) || 0;
233
- const touchData = touchDataRef.current;
234
- // Record current Y position with timestamp
235
- touchData.yList.push([
236
- currentY,
237
- Date.now()
238
- ]);
239
- if (touchData.yList.length > 5) {
240
- touchData.yList.shift(); // Keep latest 5 points for velocity calc
241
- }
242
- // Calculate delta in scroll position based on drag distance
243
- const dragDelta = (touchData.startY - currentY) / itemHeight;
244
- let nextScroll = scrollRef.current + dragDelta;
245
- if (infiniteProp) {
246
- // Wrap scroll for infinite lists
247
- nextScroll = normalizeScroll(nextScroll);
248
- } else {
249
- const maxIndex = options.length;
250
- if (nextScroll < 0) {
251
- // Apply resistance when dragging above top
252
- nextScroll *= RESISTANCE;
253
- } else if (nextScroll > maxIndex) {
254
- // Apply resistance when dragging below bottom
255
- nextScroll = maxIndex + (nextScroll - maxIndex) * RESISTANCE;
256
- }
257
- }
258
- // Update visual scroll and store position
259
- touchData.touchScroll = scrollTo(nextScroll);
260
- } catch (error) {
261
- console.error("Error in updateScrollDuringDrag:", error);
262
- }
263
- };
264
- const handleDragMoveEvent = (event)=>{
265
- if (!dragingRef.current && !containerRef.current.contains(event.target) && event.target !== containerRef.current) {
266
- return;
267
- }
268
- if (event.cancelable) {
269
- event.preventDefault();
270
- }
271
- if (options.length) {
272
- updateScrollDuringDrag(event);
273
- }
274
- };
275
- const initiateDragGesture = (event)=>{
276
- try {
277
- var _containerRef_current, _event_touches_, _event_touches;
278
- dragingRef.current = true;
279
- const controller = new AbortController();
280
- const { signal } = controller;
281
- dragControllerRef.current = controller;
282
- // Listen to movement events
283
- const passiveOpts = {
284
- signal,
285
- passive: false
286
- };
287
- (_containerRef_current = containerRef.current) == null ? void 0 : _containerRef_current.addEventListener("touchmove", handleDragMoveEvent, passiveOpts);
288
- document.addEventListener("mousemove", handleDragMoveEvent, passiveOpts);
289
- const startY = (event instanceof MouseEvent ? event.clientY : (_event_touches = event.touches) == null ? void 0 : (_event_touches_ = _event_touches[0]) == null ? void 0 : _event_touches_.clientY) || 0;
290
- // Initialize touch tracking
291
- const touchData = touchDataRef.current;
292
- touchData.startY = startY;
293
- touchData.yList = [
294
- [
295
- startY,
296
- Date.now()
297
- ]
298
- ];
299
- touchData.touchScroll = scrollRef.current;
300
- // Stop any ongoing scroll animation
301
- cancelAnimation();
302
- } catch (error) {
303
- console.error("Error in initiateDragGesture:", error);
304
- }
305
- };
306
- const handleDragStartEvent = useCallback((e)=>{
307
- const isDragging = dragingRef.current;
308
- const isTargetValid = containerRef.current.contains(e.target) || e.target === containerRef.current;
309
- if ((isDragging || isTargetValid) && e.cancelable) {
310
- e.preventDefault();
311
- if (options.length) {
312
- initiateDragGesture(e);
313
- }
314
- }
315
- }, // eslint-disable-next-line react-hooks/exhaustive-deps
316
- [
317
- initiateDragGesture
318
- ]);
319
- const decelerateAndAnimateScroll = (initialVelocity)=>{
320
- const currentScroll = scrollRef.current;
321
- let targetScroll = currentScroll;
322
- let deceleration = initialVelocity > 0 ? -baseDeceleration : baseDeceleration;
323
- let duration = 0;
324
- // Clamp utility to constrain a value within bounds
325
- const clamp = (value, min, max)=>Math.max(min, Math.min(value, max));
326
- if (infiniteProp) {
327
- // Infinite mode: apply uniform deceleration to calculate scroll distance
328
- duration = Math.abs(initialVelocity / deceleration);
329
- const scrollDistance = initialVelocity * duration + 0.5 * deceleration * duration * duration;
330
- targetScroll = Math.round(currentScroll + scrollDistance);
331
- } else if (currentScroll < 0 || currentScroll > options.length - 1) {
332
- // Out-of-bounds: snap back to nearest valid scroll index
333
- const target = clamp(currentScroll, 0, options.length - 1);
334
- const scrollDistance = currentScroll - target;
335
- deceleration = snapBackDeceleration;
336
- duration = Math.sqrt(Math.abs(scrollDistance / deceleration));
337
- initialVelocity = deceleration * duration;
338
- initialVelocity = currentScroll > 0 ? -initialVelocity : initialVelocity;
339
- targetScroll = target;
340
- } else {
341
- // Normal decelerated scroll within bounds
342
- duration = Math.abs(initialVelocity / deceleration);
343
- const scrollDistance = initialVelocity * duration + 0.5 * deceleration * duration * duration;
344
- targetScroll = Math.round(currentScroll + scrollDistance);
345
- targetScroll = clamp(targetScroll, 0, options.length - 1);
346
- const adjustedDistance = targetScroll - currentScroll;
347
- duration = Math.sqrt(Math.abs(adjustedDistance / deceleration));
348
- }
349
- // Start animation to target scroll position with calculated duration
350
- animateScroll(currentScroll, targetScroll, duration, ()=>{
351
- selectByScroll(scrollRef.current); // Ensure selected item updates at end
352
- });
353
- // Fallback selection update (in case animation callback fails)
354
- selectByScroll(scrollRef.current);
355
- };
356
- const finalizeDragAndStartInertiaScroll = ()=>{
357
- try {
358
- var _dragControllerRef_current;
359
- (_dragControllerRef_current = dragControllerRef.current) == null ? void 0 : _dragControllerRef_current.abort();
360
- dragControllerRef.current = null;
361
- const touchData = touchDataRef.current;
362
- const yList = touchData.yList;
363
- let velocity = 0;
364
- if (yList.length > 1) {
365
- const len = yList.length;
366
- var _yList_;
367
- const [startY, startTime] = (_yList_ = yList[len - 2]) != null ? _yList_ : [
368
- 0,
369
- 0
370
- ];
371
- var _yList_1;
372
- const [endY, endTime] = (_yList_1 = yList[len - 1]) != null ? _yList_1 : [
373
- 0,
374
- 0
375
- ];
376
- const timeDiff = endTime - startTime;
377
- if (timeDiff > 0) {
378
- const distance = startY - endY;
379
- const velocityPerSecond = distance / itemHeight * 1000 / timeDiff;
380
- const maxVelocity = MAX_VELOCITY;
381
- const direction = velocityPerSecond > 0 ? 1 : -1;
382
- const absVelocity = Math.min(Math.abs(velocityPerSecond), maxVelocity);
383
- velocity = absVelocity * direction;
384
- }
385
- }
386
- var _touchData_touchScroll;
387
- scrollRef.current = (_touchData_touchScroll = touchData.touchScroll) != null ? _touchData_touchScroll : scrollRef.current;
388
- decelerateAndAnimateScroll(velocity);
389
- } catch (error) {
390
- console.error("Error in finalizeDragAndStartInertiaScroll:", error);
391
- } finally{
392
- dragingRef.current = false;
393
- }
394
- };
395
- const handleDragEndEvent = useCallback((event)=>{
396
- if (!options.length) return;
397
- const isDragging = dragingRef.current;
398
- const isTargetValid = containerRef.current.contains(event.target) || event.target === containerRef.current;
399
- if ((isDragging || isTargetValid) && event.cancelable) {
400
- event.preventDefault();
401
- finalizeDragAndStartInertiaScroll();
402
- }
403
- }, // eslint-disable-next-line react-hooks/exhaustive-deps
404
- [
405
- finalizeDragAndStartInertiaScroll
406
- ]);
407
- const scrollByWheel = (event)=>{
408
- event.preventDefault();
409
- const now = Date.now();
410
- if (now - lastWheelRef.current < WHEEL_THROTTLE) return;
411
- const direction = Math.sign(event.deltaY);
412
- if (direction !== 0) {
413
- selectByScroll(scrollRef.current + direction);
414
- lastWheelRef.current = now;
415
- }
416
- };
417
- const handleWheelEvent = useCallback((event)=>{
418
- if (!options.length || !containerRef.current) return;
419
- const isDragging = dragingRef.current;
420
- const isTargetValid = containerRef.current.contains(event.target) || event.target === containerRef.current;
421
- if ((isDragging || isTargetValid) && event.cancelable) {
422
- event.preventDefault();
423
- scrollByWheel(event);
424
- }
425
- }, // eslint-disable-next-line react-hooks/exhaustive-deps
426
- [
427
- scrollByWheel
428
- ]);
429
- useEffect(()=>{
430
- const container = containerRef.current;
431
- if (!container) return;
432
- const controller = new AbortController();
433
- const { signal } = controller;
434
- const opts = {
435
- signal,
436
- passive: false
437
- };
438
- container.addEventListener("touchstart", handleDragStartEvent, opts);
439
- container.addEventListener("touchend", handleDragEndEvent, opts);
440
- container.addEventListener("wheel", handleWheelEvent, opts);
441
- document.addEventListener("mousedown", handleDragStartEvent, opts);
442
- document.addEventListener("mouseup", handleDragEndEvent, opts);
443
- return ()=>controller.abort();
444
- }, [
445
- handleDragEndEvent,
446
- handleDragStartEvent,
447
- handleWheelEvent
448
- ]);
449
- useEffect(()=>{
450
- selectByValue(value);
451
- // eslint-disable-next-line react-hooks/exhaustive-deps
452
- }, [
453
- value,
454
- valueProp
455
- ]);
456
- return /*#__PURE__*/ React.createElement("div", {
457
- ref: containerRef,
458
- "data-rwp": true,
459
- style: {
460
- height: containerHeight
461
- }
462
- }, /*#__PURE__*/ React.createElement("ul", {
463
- ref: wheelItemsRef,
464
- "data-rwp-options": true
465
- }, renderWheelItems), /*#__PURE__*/ React.createElement("div", {
466
- className: classNames == null ? void 0 : classNames.highlightWrapper,
467
- "data-rwp-highlight-wrapper": true,
468
- style: {
469
- height: itemHeight,
470
- lineHeight: itemHeight + "px"
471
- }
472
- }, /*#__PURE__*/ React.createElement("ul", {
473
- ref: highlightListRef,
474
- "data-rwp-highlight-list": true,
475
- style: {
476
- top: infiniteProp ? -28 : undefined
477
- }
478
- }, renderHighlightItems)));
479
- };
480
-
481
- export { WheelPicker, WheelPickerWrapper };
1
+ "use client";import M from"react";import{useCallback as q,useEffect as ot,useMemo as z,useRef as T}from"react";import D from"react";function nt(u){let g=D.useRef(u);return D.useEffect(()=>{g.current=u}),D.useMemo(()=>(...E)=>{var h;return(h=g.current)==null?void 0:h.call(g,...E)},[])}function vt({defaultProp:u,onChange:g}){let E=D.useState(u),[h]=E,a=D.useRef(h),b=nt(g);return D.useEffect(()=>{a.current!==h&&(b(h),a.current=h)},[h,a,b]),E}function rt({prop:u,defaultProp:g,onChange:E=()=>{}}){let[h,a]=vt({defaultProp:g,onChange:E}),b=u!==void 0,x=b?u:h,l=nt(E),W=D.useCallback(S=>{if(b){let d=typeof S=="function"?S(u):S;d!==u&&l(d)}else a(S)},[b,u,a,l]);return[x,W]}var Et=150,ct=.3,Tt=30,Dt=u=>Math.pow(u-1,3)+1,wt=({className:u,children:g})=>M.createElement("div",{className:u,"data-rwp-wrapper":!0},g),kt=({defaultValue:u,value:g,onValueChange:E,options:h,infinite:a=!1,visibleCount:b=20,dragSensitivity:x=3,classNames:l})=>{var V,N;let[W=(N=(V=h[0])==null?void 0:V.value)!=null?N:"",S]=rt({defaultProp:u,prop:g,onChange:E}),c=z(()=>{if(!a)return h;let t=[],e=Math.ceil(b/2);if(h.length===0)return t;for(;t.length<e;)t.push(...h);return t},[b,h,a]),d=28,B=d*.5,y=360/b,C=d/Math.tan(y*Math.PI/180),st=Math.round(C*2+d*.25),A=b>>2,X=x*10,at=10,m=T(null),w=T(null),Y=T(null),p=T(0),G=T(0),L=T(!1),Z=T(0),R=T({startY:0,yList:[]}),H=T(null),lt=z(()=>{let t=(n,r,o)=>M.createElement("li",{key:r,className:l==null?void 0:l.optionItem,"data-rwp-option":!0,"data-index":r,style:{top:-B,height:d,lineHeight:`${d}px`,transform:`rotateX(${o}deg) translateZ(${C}px)`,visibility:"hidden"}},n.label),e=c.map((n,r)=>t(n,r,-y*r));if(a)for(let n=0;n<A;++n){let r=-n-1,o=n+c.length;e.unshift(t(c[c.length-n-1],r,y*(n+1))),e.push(t(c[n],o,-y*o))}return e},[B,a,y,c,A,C,l==null?void 0:l.optionItem]),it=z(()=>{let t=(n,r)=>M.createElement("li",{key:r,"data-slot":"highlight-item",className:l==null?void 0:l.highlightItem,style:{height:d}},n.label),e=c.map((n,r)=>t(n,r));if(a){let n=c[0],r=c[c.length-1];e.unshift(t(r,"infinite-start")),e.push(t(n,"infinite-end"))}return e},[l==null?void 0:l.highlightItem,a,c]),O=t=>(t%c.length+c.length)%c.length,I=t=>{let e=a?O(t):t;if(w.current){let n=`translateZ(${-C}px) rotateX(${y*e}deg)`;w.current.style.transform=n,w.current.childNodes.forEach(r=>{let o=r,f=Math.abs(Number(o.dataset.index)-e);o.style.visibility=f>A?"hidden":"visible"})}return Y.current&&(Y.current.style.transform=`translateY(${-e*d}px)`),e},F=()=>{cancelAnimationFrame(G.current)},ut=(t,e,n,r)=>{if(t===e||n===0){I(t);return}let o=performance.now(),f=e-t,s=i=>{let v=(i-o)/1e3;if(v<n){let $=Dt(v/n);p.current=I(t+$*f),G.current=requestAnimationFrame(s)}else F(),p.current=I(e),r==null||r()};requestAnimationFrame(s)},k=t=>{let e=O(t)|0,n=a?e:Math.min(Math.max(e,0),c.length-1);if(!a&&n!==t)return;p.current=I(n);let r=c[p.current];S(r.value)},ht=t=>{let e=c.findIndex(n=>n.value===t);if(e===-1){console.error("Invalid value selected:",t);return}F(),k(e)},dt=t=>{var e,n;try{let r=(t instanceof MouseEvent?t.clientY:(n=(e=t.touches)==null?void 0:e[0])==null?void 0:n.clientY)||0,o=R.current;o.yList.push([r,Date.now()]),o.yList.length>5&&o.yList.shift();let f=(o.startY-r)/d,s=p.current+f;if(a)s=O(s);else{let i=c.length;s<0?s*=ct:s>i&&(s=i+(s-i)*ct)}o.touchScroll=I(s)}catch(r){console.error("Error in updateScrollDuringDrag:",r)}},_=t=>{!L.current&&!m.current.contains(t.target)&&t.target!==m.current||(t.cancelable&&t.preventDefault(),c.length&&dt(t))},j=t=>{var e,n,r;try{L.current=!0;let o=new AbortController,{signal:f}=o;H.current=o;let s={signal:f,passive:!1};(e=m.current)==null||e.addEventListener("touchmove",_,s),document.addEventListener("mousemove",_,s);let i=(t instanceof MouseEvent?t.clientY:(r=(n=t.touches)==null?void 0:n[0])==null?void 0:r.clientY)||0,v=R.current;v.startY=i,v.yList=[[i,Date.now()]],v.touchScroll=p.current,F()}catch(o){console.error("Error in initiateDragGesture:",o)}},P=q(t=>{let e=L.current,n=m.current.contains(t.target)||t.target===m.current;(e||n)&&t.cancelable&&(t.preventDefault(),c.length&&j(t))},[j]),ft=t=>{let e=p.current,n=e,r=t>0?-X:X,o=0,f=(s,i,v)=>Math.max(i,Math.min(s,v));if(a){o=Math.abs(t/r);let s=t*o+.5*r*o*o;n=Math.round(e+s)}else if(e<0||e>c.length-1){let s=f(e,0,c.length-1),i=e-s;r=at,o=Math.sqrt(Math.abs(i/r)),t=r*o,t=e>0?-t:t,n=s}else{o=Math.abs(t/r);let s=t*o+.5*r*o*o;n=Math.round(e+s),n=f(n,0,c.length-1);let i=n-e;o=Math.sqrt(Math.abs(i/r))}ut(e,n,o,()=>{k(p.current)}),k(p.current)},K=()=>{var t,e,n,r;try{(t=H.current)==null||t.abort(),H.current=null;let o=R.current,f=o.yList,s=0;if(f.length>1){let i=f.length,[v,$]=(e=f[i-2])!=null?e:[0,0],[gt,mt]=(n=f[i-1])!=null?n:[0,0],tt=mt-$;if(tt>0){let et=(v-gt)/d*1e3/tt,pt=Tt,bt=et>0?1:-1;s=Math.min(Math.abs(et),pt)*bt}}p.current=(r=o.touchScroll)!=null?r:p.current,ft(s)}catch(o){console.error("Error in finalizeDragAndStartInertiaScroll:",o)}finally{L.current=!1}},U=q(t=>{if(!c.length)return;let e=L.current,n=m.current.contains(t.target)||t.target===m.current;(e||n)&&t.cancelable&&(t.preventDefault(),K())},[K]),J=t=>{t.preventDefault();let e=Date.now();if(e-Z.current<Et)return;let n=Math.sign(t.deltaY);n!==0&&(k(p.current+n),Z.current=e)},Q=q(t=>{if(!c.length||!m.current)return;let e=L.current,n=m.current.contains(t.target)||t.target===m.current;(e||n)&&t.cancelable&&(t.preventDefault(),J(t))},[J]);return ot(()=>{let t=m.current;if(!t)return;let e=new AbortController,{signal:n}=e,r={signal:n,passive:!1};return t.addEventListener("touchstart",P,r),t.addEventListener("touchend",U,r),t.addEventListener("wheel",Q,r),document.addEventListener("mousedown",P,r),document.addEventListener("mouseup",U,r),()=>e.abort()},[U,P,Q]),ot(()=>{ht(W)},[W,g]),M.createElement("div",{ref:m,"data-rwp":!0,style:{height:st}},M.createElement("ul",{ref:w,"data-rwp-options":!0},lt),M.createElement("div",{className:l==null?void 0:l.highlightWrapper,"data-rwp-highlight-wrapper":!0,style:{height:d,lineHeight:d+"px"}},M.createElement("ul",{ref:Y,"data-rwp-highlight-list":!0,style:{top:a?-d:void 0}},it)))};export{kt as WheelPicker,wt as WheelPickerWrapper};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ncdai/react-wheel-picker",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "iOS-like wheel picker for React with smooth inertia scrolling and infinite loop support.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -21,11 +21,22 @@
21
21
  },
22
22
  "./dist/style.css": "./dist/style.css"
23
23
  },
24
+ "tsup": {
25
+ "entry": [
26
+ "src/index.tsx"
27
+ ],
28
+ "format": [
29
+ "esm",
30
+ "cjs"
31
+ ],
32
+ "dts": true,
33
+ "clean": true,
34
+ "publicDir": "./src/assets"
35
+ },
24
36
  "scripts": {
25
- "dev": "bunchee --watch",
26
- "build": "bunchee && pnpm copy-assets",
37
+ "dev": "tsup --watch",
38
+ "build": "tsup --minify",
27
39
  "type-check": "tsc --noEmit",
28
- "copy-assets": "cp -r ./src/style.css ./dist/style.css",
29
40
  "dev:website": "turbo run dev --filter=website...",
30
41
  "prettier:check": "prettier --check .",
31
42
  "format": "prettier --write .",
@@ -53,9 +64,9 @@
53
64
  "devDependencies": {
54
65
  "@types/node": "^20",
55
66
  "@types/react": "^19",
56
- "bunchee": "^6.5.1",
57
67
  "prettier": "3.5.3",
58
68
  "react": "^19.0.0",
69
+ "tsup": "8.5.0",
59
70
  "turbo": "^2.5.3",
60
71
  "typescript": "^5.8.3"
61
72
  },