r3f-motion 1.0.5 → 1.0.6
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/cjs/index.js +553 -5
- package/dist/es/components/Carousel/index.mjs +147 -0
- package/dist/es/index.mjs +1 -0
- package/dist/es/render/gestures/use-drag.mjs +389 -0
- package/dist/es/render/motion.mjs +8 -1
- package/dist/index.d.ts +58 -4
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -4,9 +4,29 @@
|
|
|
4
4
|
var tslib = require('tslib');
|
|
5
5
|
var react = require('react');
|
|
6
6
|
var motion$1 = require('motion');
|
|
7
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
8
7
|
var fiber = require('@react-three/fiber');
|
|
9
|
-
var
|
|
8
|
+
var THREE = require('three');
|
|
9
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
10
|
+
var react$1 = require('motion/react');
|
|
11
|
+
|
|
12
|
+
function _interopNamespaceDefault(e) {
|
|
13
|
+
var n = Object.create(null);
|
|
14
|
+
if (e) {
|
|
15
|
+
Object.keys(e).forEach(function (k) {
|
|
16
|
+
if (k !== 'default') {
|
|
17
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
18
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () { return e[k]; }
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
n.default = e;
|
|
26
|
+
return Object.freeze(n);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
|
|
10
30
|
|
|
11
31
|
const PresenceContext = react.createContext(null);
|
|
12
32
|
/**
|
|
@@ -249,6 +269,389 @@ function useTap(isStatic, props, options) {
|
|
|
249
269
|
};
|
|
250
270
|
}
|
|
251
271
|
|
|
272
|
+
// World-space units per CSS pixel at the given depth from the camera.
|
|
273
|
+
const getWorldPerPixel = (camera, depth, viewportHeight) => {
|
|
274
|
+
var _a;
|
|
275
|
+
const ortho = camera;
|
|
276
|
+
if (ortho.isOrthographicCamera) {
|
|
277
|
+
return ((ortho.top - ortho.bottom) / (viewportHeight * (ortho.zoom || 1)));
|
|
278
|
+
}
|
|
279
|
+
const persp = camera;
|
|
280
|
+
const fov = ((_a = persp.fov) !== null && _a !== void 0 ? _a : 60) * (Math.PI / 180);
|
|
281
|
+
return (2 * Math.tan(fov / 2) * depth) / viewportHeight;
|
|
282
|
+
};
|
|
283
|
+
const clampWithElastic = (value, min, max, elastic) => {
|
|
284
|
+
if (min !== undefined && value < min)
|
|
285
|
+
return min + (value - min) * elastic;
|
|
286
|
+
if (max !== undefined && value > max)
|
|
287
|
+
return max + (value - max) * elastic;
|
|
288
|
+
return value;
|
|
289
|
+
};
|
|
290
|
+
function useDrag(isStatic, props, options) {
|
|
291
|
+
const { camera, gl } = fiber.useThree();
|
|
292
|
+
const { drag, whileDrag, onDragStart, onPointerDown, transition } = props;
|
|
293
|
+
const { instanceRef, captureInstanceState, buildTargetFromState, animateToTarget, resolveVariant, stopAnimation, } = options;
|
|
294
|
+
const isDraggingRef = react.useRef(false);
|
|
295
|
+
const dragStartPointerRef = react.useRef({ x: 0, y: 0 });
|
|
296
|
+
const dragStartPosRef = react.useRef(new THREE__namespace.Vector3());
|
|
297
|
+
const cameraRightRef = react.useRef(new THREE__namespace.Vector3());
|
|
298
|
+
const cameraUpRef = react.useRef(new THREE__namespace.Vector3());
|
|
299
|
+
const worldPerPixelRef = react.useRef(0);
|
|
300
|
+
const targetPosRef = react.useRef(new THREE__namespace.Vector3());
|
|
301
|
+
const lastPosRef = react.useRef(new THREE__namespace.Vector3());
|
|
302
|
+
const velocityRef = react.useRef(new THREE__namespace.Vector3());
|
|
303
|
+
const lastDeltaRef = react.useRef(new THREE__namespace.Vector3());
|
|
304
|
+
const lastTimeRef = react.useRef(0);
|
|
305
|
+
const preDragStateRef = react.useRef(null);
|
|
306
|
+
const pointerIdRef = react.useRef(null);
|
|
307
|
+
const springStateRef = react.useRef(null);
|
|
308
|
+
const lastFrameTimeRef = react.useRef(0);
|
|
309
|
+
const propsRef = react.useRef(props);
|
|
310
|
+
propsRef.current = props;
|
|
311
|
+
fiber.useFrame(() => {
|
|
312
|
+
var _a;
|
|
313
|
+
const instance = instanceRef.current;
|
|
314
|
+
if (!instance)
|
|
315
|
+
return;
|
|
316
|
+
const obj = instance;
|
|
317
|
+
// Phase 1: dragging — apply the drag target
|
|
318
|
+
if (isDraggingRef.current) {
|
|
319
|
+
const t = targetPosRef.current;
|
|
320
|
+
obj.position.set(t.x, t.y, t.z);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
// Phase 2: spring (momentum / snap-to-origin)
|
|
324
|
+
const s = springStateRef.current;
|
|
325
|
+
if (!s || !s.active)
|
|
326
|
+
return;
|
|
327
|
+
const now = performance.now();
|
|
328
|
+
let dt = (now - lastFrameTimeRef.current) / 1000;
|
|
329
|
+
if (dt > 0.05)
|
|
330
|
+
dt = 0.05;
|
|
331
|
+
if (dt <= 0)
|
|
332
|
+
dt = 1 / 60;
|
|
333
|
+
lastFrameTimeRef.current = now;
|
|
334
|
+
// Sub-step the integrator for stability with stiff springs
|
|
335
|
+
const subSteps = 4;
|
|
336
|
+
const subDt = dt / subSteps;
|
|
337
|
+
for (let i = 0; i < subSteps; i++) {
|
|
338
|
+
if (s.hasX) {
|
|
339
|
+
const force = -s.stiffness * (obj.position.x - s.targetX) - s.damping * s.velX;
|
|
340
|
+
s.velX += force * subDt;
|
|
341
|
+
obj.position.x += s.velX * subDt;
|
|
342
|
+
}
|
|
343
|
+
if (s.hasY) {
|
|
344
|
+
const force = -s.stiffness * (obj.position.y - s.targetY) - s.damping * s.velY;
|
|
345
|
+
s.velY += force * subDt;
|
|
346
|
+
obj.position.y += s.velY * subDt;
|
|
347
|
+
}
|
|
348
|
+
if (s.hasZ) {
|
|
349
|
+
const force = -s.stiffness * (obj.position.z - s.targetZ) - s.damping * s.velZ;
|
|
350
|
+
s.velZ += force * subDt;
|
|
351
|
+
obj.position.z += s.velZ * subDt;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// Rest detection — stop when both speed and offset are tiny
|
|
355
|
+
const speedSq = s.velX * s.velX + s.velY * s.velY + s.velZ * s.velZ;
|
|
356
|
+
const offX = s.hasX ? obj.position.x - s.targetX : 0;
|
|
357
|
+
const offY = s.hasY ? obj.position.y - s.targetY : 0;
|
|
358
|
+
const offZ = s.hasZ ? obj.position.z - s.targetZ : 0;
|
|
359
|
+
const offsetSq = offX * offX + offY * offY + offZ * offZ;
|
|
360
|
+
if (speedSq < 0.0005 && offsetSq < 0.0005) {
|
|
361
|
+
if (s.hasX)
|
|
362
|
+
obj.position.x = s.targetX;
|
|
363
|
+
if (s.hasY)
|
|
364
|
+
obj.position.y = s.targetY;
|
|
365
|
+
if (s.hasZ)
|
|
366
|
+
obj.position.z = s.targetZ;
|
|
367
|
+
s.active = false;
|
|
368
|
+
(_a = s.onComplete) === null || _a === void 0 ? void 0 : _a.call(s);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
const handlePointerDown = react.useCallback((event) => {
|
|
372
|
+
const instance = instanceRef.current;
|
|
373
|
+
if (!instance)
|
|
374
|
+
return;
|
|
375
|
+
event.stopPropagation();
|
|
376
|
+
// Halt any in-flight motion-library animation (e.g. main `animate` prop
|
|
377
|
+
// tween that's still running) and any active drag spring.
|
|
378
|
+
stopAnimation();
|
|
379
|
+
if (springStateRef.current)
|
|
380
|
+
springStateRef.current.active = false;
|
|
381
|
+
if (whileDrag) {
|
|
382
|
+
preDragStateRef.current = captureInstanceState();
|
|
383
|
+
const targetValues = typeof whileDrag === "string"
|
|
384
|
+
? resolveVariant(whileDrag)
|
|
385
|
+
: whileDrag;
|
|
386
|
+
animateToTarget(targetValues, transition || { duration: 0.1 });
|
|
387
|
+
}
|
|
388
|
+
const obj = instance;
|
|
389
|
+
// Capture initial pointer + position. All subsequent moves are computed
|
|
390
|
+
// as `initialPos + pixelDelta * worldPerPixel` — never re-derived from
|
|
391
|
+
// the live (potentially-mid-animation) instance position.
|
|
392
|
+
dragStartPointerRef.current.x = event.nativeEvent.clientX;
|
|
393
|
+
dragStartPointerRef.current.y = event.nativeEvent.clientY;
|
|
394
|
+
dragStartPosRef.current.set(obj.position.x, obj.position.y, obj.position.z);
|
|
395
|
+
targetPosRef.current.copy(dragStartPosRef.current);
|
|
396
|
+
lastPosRef.current.copy(dragStartPosRef.current);
|
|
397
|
+
lastDeltaRef.current.set(0, 0, 0);
|
|
398
|
+
velocityRef.current.set(0, 0, 0);
|
|
399
|
+
lastTimeRef.current = performance.now();
|
|
400
|
+
// Compute world-units-per-pixel at the object's perpendicular depth
|
|
401
|
+
// (distance from camera along its forward axis).
|
|
402
|
+
const cameraForward = new THREE__namespace.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
|
|
403
|
+
const cameraToObj = new THREE__namespace.Vector3().subVectors(dragStartPosRef.current, camera.position);
|
|
404
|
+
const depth = Math.abs(cameraToObj.dot(cameraForward));
|
|
405
|
+
const rect = gl.domElement.getBoundingClientRect();
|
|
406
|
+
worldPerPixelRef.current = getWorldPerPixel(camera, depth, rect.height);
|
|
407
|
+
// Cache camera basis vectors so screen X/Y maps correctly to world
|
|
408
|
+
// X/Y/Z even if the camera is tilted.
|
|
409
|
+
cameraRightRef.current.set(1, 0, 0).applyQuaternion(camera.quaternion);
|
|
410
|
+
cameraUpRef.current.set(0, 1, 0).applyQuaternion(camera.quaternion);
|
|
411
|
+
isDraggingRef.current = true;
|
|
412
|
+
pointerIdRef.current = event.nativeEvent.pointerId;
|
|
413
|
+
try {
|
|
414
|
+
gl.domElement.setPointerCapture(event.nativeEvent.pointerId);
|
|
415
|
+
}
|
|
416
|
+
catch (_a) {
|
|
417
|
+
// not all environments support setPointerCapture
|
|
418
|
+
}
|
|
419
|
+
onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart(event.nativeEvent, {
|
|
420
|
+
point: { x: event.nativeEvent.clientX, y: event.nativeEvent.clientY },
|
|
421
|
+
offset: { x: 0, y: 0, z: 0 },
|
|
422
|
+
delta: { x: 0, y: 0, z: 0 },
|
|
423
|
+
velocity: { x: 0, y: 0, z: 0 },
|
|
424
|
+
});
|
|
425
|
+
onPointerDown === null || onPointerDown === void 0 ? void 0 : onPointerDown(event);
|
|
426
|
+
},
|
|
427
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
428
|
+
[
|
|
429
|
+
whileDrag,
|
|
430
|
+
onDragStart,
|
|
431
|
+
onPointerDown,
|
|
432
|
+
camera,
|
|
433
|
+
gl,
|
|
434
|
+
captureInstanceState,
|
|
435
|
+
resolveVariant,
|
|
436
|
+
animateToTarget,
|
|
437
|
+
stopAnimation,
|
|
438
|
+
transition,
|
|
439
|
+
instanceRef,
|
|
440
|
+
]);
|
|
441
|
+
react.useEffect(() => {
|
|
442
|
+
const handlePointerMove = (event) => {
|
|
443
|
+
var _a;
|
|
444
|
+
if (!isDraggingRef.current || event.pointerId !== pointerIdRef.current)
|
|
445
|
+
return;
|
|
446
|
+
const instance = instanceRef.current;
|
|
447
|
+
if (!instance)
|
|
448
|
+
return;
|
|
449
|
+
const cur = propsRef.current;
|
|
450
|
+
const dragAxis = cur.drag;
|
|
451
|
+
const elasticVal = typeof cur.dragElastic === "number"
|
|
452
|
+
? cur.dragElastic
|
|
453
|
+
: cur.dragElastic === false
|
|
454
|
+
? 0
|
|
455
|
+
: 0.5;
|
|
456
|
+
const constraints = cur.dragConstraints;
|
|
457
|
+
const pixelDx = event.clientX - dragStartPointerRef.current.x;
|
|
458
|
+
const pixelDy = event.clientY - dragStartPointerRef.current.y;
|
|
459
|
+
const scale = worldPerPixelRef.current;
|
|
460
|
+
let newX = dragStartPosRef.current.x;
|
|
461
|
+
let newY = dragStartPosRef.current.y;
|
|
462
|
+
let newZ = dragStartPosRef.current.z;
|
|
463
|
+
if (dragAxis === "z") {
|
|
464
|
+
// Vertical screen movement → Z. Cursor down (pixelDy > 0) = closer.
|
|
465
|
+
newZ = dragStartPosRef.current.z + pixelDy * scale;
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
// Map screen XY to camera-relative world delta.
|
|
469
|
+
const r = cameraRightRef.current;
|
|
470
|
+
const u = cameraUpRef.current;
|
|
471
|
+
const worldDx = r.x * pixelDx * scale + u.x * -pixelDy * scale;
|
|
472
|
+
const worldDy = r.y * pixelDx * scale + u.y * -pixelDy * scale;
|
|
473
|
+
const worldDz = r.z * pixelDx * scale + u.z * -pixelDy * scale;
|
|
474
|
+
if (dragAxis === "x") {
|
|
475
|
+
newX = dragStartPosRef.current.x + worldDx;
|
|
476
|
+
}
|
|
477
|
+
else if (dragAxis === "y") {
|
|
478
|
+
newY = dragStartPosRef.current.y + worldDy;
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
newX = dragStartPosRef.current.x + worldDx;
|
|
482
|
+
newY = dragStartPosRef.current.y + worldDy;
|
|
483
|
+
newZ = dragStartPosRef.current.z + worldDz;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
// Constraints with elastic rubber-banding
|
|
487
|
+
if (constraints) {
|
|
488
|
+
if (dragAxis !== "y" && dragAxis !== "z") {
|
|
489
|
+
newX = clampWithElastic(newX, constraints.left, constraints.right, elasticVal);
|
|
490
|
+
}
|
|
491
|
+
if (dragAxis !== "x" && dragAxis !== "z") {
|
|
492
|
+
newY = clampWithElastic(newY, constraints.bottom, constraints.top, elasticVal);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
// Velocity tracking (used for momentum on release)
|
|
496
|
+
const now = performance.now();
|
|
497
|
+
const dt = (now - lastTimeRef.current) / 1000;
|
|
498
|
+
if (dt > 0) {
|
|
499
|
+
const dx = newX - lastPosRef.current.x;
|
|
500
|
+
const dy = newY - lastPosRef.current.y;
|
|
501
|
+
const dz = newZ - lastPosRef.current.z;
|
|
502
|
+
velocityRef.current.set(dx / dt, dy / dt, dz / dt);
|
|
503
|
+
lastDeltaRef.current.set(dx, dy, dz);
|
|
504
|
+
}
|
|
505
|
+
lastTimeRef.current = now;
|
|
506
|
+
lastPosRef.current.set(newX, newY, newZ);
|
|
507
|
+
// Stash target — useFrame applies it next R3F frame.
|
|
508
|
+
targetPosRef.current.set(newX, newY, newZ);
|
|
509
|
+
const offset = {
|
|
510
|
+
x: newX - dragStartPosRef.current.x,
|
|
511
|
+
y: newY - dragStartPosRef.current.y,
|
|
512
|
+
z: newZ - dragStartPosRef.current.z,
|
|
513
|
+
};
|
|
514
|
+
(_a = cur.onDrag) === null || _a === void 0 ? void 0 : _a.call(cur, event, {
|
|
515
|
+
point: { x: event.clientX, y: event.clientY },
|
|
516
|
+
offset,
|
|
517
|
+
delta: {
|
|
518
|
+
x: lastDeltaRef.current.x,
|
|
519
|
+
y: lastDeltaRef.current.y,
|
|
520
|
+
z: lastDeltaRef.current.z,
|
|
521
|
+
},
|
|
522
|
+
velocity: {
|
|
523
|
+
x: velocityRef.current.x,
|
|
524
|
+
y: velocityRef.current.y,
|
|
525
|
+
z: velocityRef.current.z,
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
};
|
|
529
|
+
const handlePointerUp = (event) => {
|
|
530
|
+
var _a, _b;
|
|
531
|
+
if (!isDraggingRef.current || event.pointerId !== pointerIdRef.current)
|
|
532
|
+
return;
|
|
533
|
+
isDraggingRef.current = false;
|
|
534
|
+
pointerIdRef.current = null;
|
|
535
|
+
const instance = instanceRef.current;
|
|
536
|
+
const cur = propsRef.current;
|
|
537
|
+
// Lock in the final dragged position synchronously. Once useFrame stops
|
|
538
|
+
// applying the drag target, a still-pending write from the *previous*
|
|
539
|
+
// momentum animation can land before the new animateToTarget() call
|
|
540
|
+
// reads instance.position to use as its "from" value — which causes the
|
|
541
|
+
// new animation to start from the old momentum's path instead of where
|
|
542
|
+
// the user actually released.
|
|
543
|
+
if (instance) {
|
|
544
|
+
const obj = instance;
|
|
545
|
+
const t = targetPosRef.current;
|
|
546
|
+
obj.position.set(t.x, t.y, t.z);
|
|
547
|
+
}
|
|
548
|
+
// Restore whileDrag visual state
|
|
549
|
+
if (cur.whileDrag && preDragStateRef.current) {
|
|
550
|
+
const targetValues = buildTargetFromState(preDragStateRef.current);
|
|
551
|
+
animateToTarget(targetValues, options.transition || { duration: 0.2 });
|
|
552
|
+
const dur = ((_a = options.transition) === null || _a === void 0 ? void 0 : _a.duration) || 0.2;
|
|
553
|
+
setTimeout(() => {
|
|
554
|
+
preDragStateRef.current = null;
|
|
555
|
+
}, dur * 1000);
|
|
556
|
+
}
|
|
557
|
+
if (!instance)
|
|
558
|
+
return;
|
|
559
|
+
const finalPos = targetPosRef.current;
|
|
560
|
+
const offset = {
|
|
561
|
+
x: finalPos.x - dragStartPosRef.current.x,
|
|
562
|
+
y: finalPos.y - dragStartPosRef.current.y,
|
|
563
|
+
z: finalPos.z - dragStartPosRef.current.z,
|
|
564
|
+
};
|
|
565
|
+
const vel = velocityRef.current;
|
|
566
|
+
// Read spring tuning from dragTransition (if user provided one).
|
|
567
|
+
const dt = cur.dragTransition;
|
|
568
|
+
const userStiffness = typeof (dt === null || dt === void 0 ? void 0 : dt.stiffness) === "number" ? dt.stiffness : undefined;
|
|
569
|
+
const userDamping = typeof (dt === null || dt === void 0 ? void 0 : dt.damping) === "number" ? dt.damping : undefined;
|
|
570
|
+
if (cur.dragSnapToOrigin) {
|
|
571
|
+
lastFrameTimeRef.current = performance.now();
|
|
572
|
+
springStateRef.current = {
|
|
573
|
+
active: true,
|
|
574
|
+
targetX: dragStartPosRef.current.x,
|
|
575
|
+
targetY: dragStartPosRef.current.y,
|
|
576
|
+
targetZ: dragStartPosRef.current.z,
|
|
577
|
+
velX: vel.x,
|
|
578
|
+
velY: vel.y,
|
|
579
|
+
velZ: vel.z,
|
|
580
|
+
hasX: true,
|
|
581
|
+
hasY: true,
|
|
582
|
+
hasZ: true,
|
|
583
|
+
stiffness: userStiffness !== null && userStiffness !== void 0 ? userStiffness : 400,
|
|
584
|
+
damping: userDamping !== null && userDamping !== void 0 ? userDamping : 40,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
else if (cur.dragMomentum !== false) {
|
|
588
|
+
const speed = Math.sqrt(Math.pow(vel.x, 2) + Math.pow(vel.y, 2) + Math.pow(vel.z, 2));
|
|
589
|
+
if (speed > 0.5) {
|
|
590
|
+
const coeff = 0.25;
|
|
591
|
+
const dragAxis = cur.drag;
|
|
592
|
+
const hasX = dragAxis !== "y" && dragAxis !== "z";
|
|
593
|
+
const hasY = dragAxis !== "x" && dragAxis !== "z";
|
|
594
|
+
const hasZ = dragAxis === "z";
|
|
595
|
+
let targetX = hasX ? finalPos.x + vel.x * coeff : finalPos.x;
|
|
596
|
+
let targetY = hasY ? finalPos.y + vel.y * coeff : finalPos.y;
|
|
597
|
+
const targetZ = hasZ ? finalPos.z + vel.z * coeff : finalPos.z;
|
|
598
|
+
const constraints = cur.dragConstraints;
|
|
599
|
+
if (constraints) {
|
|
600
|
+
if (hasX) {
|
|
601
|
+
if (constraints.left !== undefined)
|
|
602
|
+
targetX = Math.max(targetX, constraints.left);
|
|
603
|
+
if (constraints.right !== undefined)
|
|
604
|
+
targetX = Math.min(targetX, constraints.right);
|
|
605
|
+
}
|
|
606
|
+
if (hasY) {
|
|
607
|
+
if (constraints.bottom !== undefined)
|
|
608
|
+
targetY = Math.max(targetY, constraints.bottom);
|
|
609
|
+
if (constraints.top !== undefined)
|
|
610
|
+
targetY = Math.min(targetY, constraints.top);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
lastFrameTimeRef.current = performance.now();
|
|
614
|
+
springStateRef.current = {
|
|
615
|
+
active: true,
|
|
616
|
+
targetX,
|
|
617
|
+
targetY,
|
|
618
|
+
targetZ,
|
|
619
|
+
velX: hasX ? vel.x : 0,
|
|
620
|
+
velY: hasY ? vel.y : 0,
|
|
621
|
+
velZ: hasZ ? vel.z : 0,
|
|
622
|
+
hasX,
|
|
623
|
+
hasY,
|
|
624
|
+
hasZ,
|
|
625
|
+
stiffness: userStiffness !== null && userStiffness !== void 0 ? userStiffness : 200,
|
|
626
|
+
damping: userDamping !== null && userDamping !== void 0 ? userDamping : 50,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
(_b = cur.onDragEnd) === null || _b === void 0 ? void 0 : _b.call(cur, event, {
|
|
631
|
+
point: { x: event.clientX, y: event.clientY },
|
|
632
|
+
offset,
|
|
633
|
+
delta: {
|
|
634
|
+
x: lastDeltaRef.current.x,
|
|
635
|
+
y: lastDeltaRef.current.y,
|
|
636
|
+
z: lastDeltaRef.current.z,
|
|
637
|
+
},
|
|
638
|
+
velocity: { x: vel.x, y: vel.y, z: vel.z },
|
|
639
|
+
});
|
|
640
|
+
};
|
|
641
|
+
window.addEventListener("pointermove", handlePointerMove);
|
|
642
|
+
window.addEventListener("pointerup", handlePointerUp);
|
|
643
|
+
return () => {
|
|
644
|
+
window.removeEventListener("pointermove", handlePointerMove);
|
|
645
|
+
window.removeEventListener("pointerup", handlePointerUp);
|
|
646
|
+
};
|
|
647
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
648
|
+
}, [gl, instanceRef, animateToTarget, buildTargetFromState]);
|
|
649
|
+
const isDragEnabled = drag !== undefined && drag !== false;
|
|
650
|
+
if (!isDragEnabled)
|
|
651
|
+
return {};
|
|
652
|
+
return { onPointerDown: handlePointerDown };
|
|
653
|
+
}
|
|
654
|
+
|
|
252
655
|
function createAnimationState() {
|
|
253
656
|
return {
|
|
254
657
|
hasStarted: false,
|
|
@@ -533,14 +936,20 @@ function custom(Component) {
|
|
|
533
936
|
return () => { var _a; return (_a = animationRef.current) === null || _a === void 0 ? void 0 : _a.stop(); };
|
|
534
937
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
535
938
|
}, [presenceContext === null || presenceContext === void 0 ? void 0 : presenceContext.isPresent]);
|
|
939
|
+
const stopAnimation = react.useCallback(() => {
|
|
940
|
+
var _a;
|
|
941
|
+
(_a = animationRef.current) === null || _a === void 0 ? void 0 : _a.stop();
|
|
942
|
+
}, []);
|
|
536
943
|
const gestureProps = {
|
|
944
|
+
instanceRef,
|
|
537
945
|
captureInstanceState,
|
|
538
946
|
buildTargetFromState,
|
|
539
947
|
animateToTarget,
|
|
540
948
|
resolveVariant,
|
|
541
949
|
transition,
|
|
950
|
+
stopAnimation,
|
|
542
951
|
};
|
|
543
|
-
const gestureHandlers = Object.assign(Object.assign({}, useHover(false, props, gestureProps)), useTap(false, props, gestureProps));
|
|
952
|
+
const gestureHandlers = Object.assign(Object.assign(Object.assign({}, useHover(false, props, gestureProps)), useTap(false, props, gestureProps)), useDrag(false, props, gestureProps));
|
|
544
953
|
const resolvedInitialValues = typeof initial === "string" ? resolveVariant(initial) : initial;
|
|
545
954
|
const element = useRender(Component, Object.assign(Object.assign(Object.assign({}, restProps), gestureHandlers), { children }), ref, instanceRef, resolvedInitialValues);
|
|
546
955
|
const getNextChildIndex = react.useCallback(() => {
|
|
@@ -581,11 +990,11 @@ const MotionCamera = (props) => {
|
|
|
581
990
|
const aspect = size.width / size.height;
|
|
582
991
|
let cam;
|
|
583
992
|
if (type === "perspective") {
|
|
584
|
-
cam = new
|
|
993
|
+
cam = new THREE.PerspectiveCamera(fov, aspect, near, far);
|
|
585
994
|
}
|
|
586
995
|
else {
|
|
587
996
|
const frustumSize = 10;
|
|
588
|
-
cam = new
|
|
997
|
+
cam = new THREE.OrthographicCamera((-frustumSize * aspect) / 2, (frustumSize * aspect) / 2, frustumSize / 2, -frustumSize / 2, near, far);
|
|
589
998
|
}
|
|
590
999
|
cam.updateProjectionMatrix();
|
|
591
1000
|
return cam;
|
|
@@ -710,7 +1119,146 @@ function AnimatePresence({ children, mode = "sync", onExitComplete, custom, }) {
|
|
|
710
1119
|
});
|
|
711
1120
|
}
|
|
712
1121
|
|
|
1122
|
+
const DEFAULT_SPRING = {
|
|
1123
|
+
type: 'spring',
|
|
1124
|
+
stiffness: 600,
|
|
1125
|
+
damping: 100,
|
|
1126
|
+
restDelta: 0.001,
|
|
1127
|
+
};
|
|
1128
|
+
// Seconds of velocity to project past the release point when snapping —
|
|
1129
|
+
// higher value = a flick advances further.
|
|
1130
|
+
const VELOCITY_PROJECTION = 0.2;
|
|
1131
|
+
// Fraction of slideWidth the user must cross, or minimum velocity (world
|
|
1132
|
+
// units/sec) required, to commit to the next slide. Below both thresholds
|
|
1133
|
+
// the carousel snaps back to the slot the drag started on.
|
|
1134
|
+
const DRAG_THRESHOLD_RATIO = 0.25;
|
|
1135
|
+
const FLICK_VELOCITY = 1.0;
|
|
1136
|
+
const mod = (n, m) => ((n % m) + m) % m;
|
|
1137
|
+
const CarouselSlot = react.memo(({ hadDragRef, item, itemWidth, slotRef }) => {
|
|
1138
|
+
const contentRef = react.useRef(null);
|
|
1139
|
+
const coverRef = react.useRef(null);
|
|
1140
|
+
react.useLayoutEffect(() => {
|
|
1141
|
+
const content = contentRef.current;
|
|
1142
|
+
const cover = coverRef.current;
|
|
1143
|
+
if (!content || !cover)
|
|
1144
|
+
return;
|
|
1145
|
+
const box = new THREE.Box3().setFromObject(content);
|
|
1146
|
+
if (box.isEmpty())
|
|
1147
|
+
return;
|
|
1148
|
+
const h = box.max.y - box.min.y;
|
|
1149
|
+
cover.geometry.dispose();
|
|
1150
|
+
cover.geometry = new THREE.PlaneGeometry(itemWidth, h);
|
|
1151
|
+
cover.position.z = box.max.z + 0.001;
|
|
1152
|
+
}, [item, itemWidth]);
|
|
1153
|
+
// Dispose the generated geometry on unmount — useLayoutEffect's replacement
|
|
1154
|
+
// logic only disposes the *previous* geometry, not the final one.
|
|
1155
|
+
react.useEffect(() => () => { var _a; (_a = coverRef.current) === null || _a === void 0 ? void 0 : _a.geometry.dispose(); }, []);
|
|
1156
|
+
return (jsxRuntime.jsxs("group", { ref: slotRef, children: [jsxRuntime.jsx("group", { ref: contentRef, children: item }), jsxRuntime.jsxs("mesh", { ref: coverRef, onClick: (e) => {
|
|
1157
|
+
if (hadDragRef.current) {
|
|
1158
|
+
e.stopPropagation();
|
|
1159
|
+
hadDragRef.current = false;
|
|
1160
|
+
}
|
|
1161
|
+
}, children: [jsxRuntime.jsx("planeGeometry", { args: [itemWidth, itemWidth] }), jsxRuntime.jsx("meshBasicMaterial", { transparent: true, opacity: 0, depthWrite: false })] })] }));
|
|
1162
|
+
});
|
|
1163
|
+
const Carousel = ({ items, itemWidth = 1.5, gap = 0.5, defaultValue = 0, transition = DEFAULT_SPRING, onSwitch, onDragStart, onDrag, onDragEnd, renderThreshold = 1, dragThreshold = DRAG_THRESHOLD_RATIO, flickVelocity = FLICK_VELOCITY, }) => {
|
|
1164
|
+
const slideWidth = itemWidth + gap;
|
|
1165
|
+
const count = items.length;
|
|
1166
|
+
// Render twice the items and center on the first item of the second copy.
|
|
1167
|
+
// The first copy lives to the left, so wrap-points sit at ±N*slideWidth
|
|
1168
|
+
// from camera center — well off-screen — instead of ±N*slideWidth/2
|
|
1169
|
+
// where they'd pop in and out at the visible edges.
|
|
1170
|
+
const loopedItems = react.useMemo(() => [...items, ...items], [items]);
|
|
1171
|
+
const slotCount = loopedItems.length;
|
|
1172
|
+
const period = slotCount * slideWidth;
|
|
1173
|
+
const startSlot = count + defaultValue;
|
|
1174
|
+
const groupRef = react.useRef(null);
|
|
1175
|
+
const itemRefs = react.useRef([]);
|
|
1176
|
+
// Single source of truth: a fractional slot index that grows up/down
|
|
1177
|
+
// infinitely as the user navigates. group.position.x = -currIndex * slideWidth
|
|
1178
|
+
// at rest. Snap animations animate currIndex toward integer targets;
|
|
1179
|
+
// useFrame applies it to the group every frame (when not dragging).
|
|
1180
|
+
const currIndex = react$1.useMotionValue(startSlot);
|
|
1181
|
+
const isDraggingRef = react.useRef(false);
|
|
1182
|
+
const hadDragRef = react.useRef(false);
|
|
1183
|
+
const dragStartIndexRef = react.useRef(startSlot);
|
|
1184
|
+
const snapAnimRef = react.useRef(null);
|
|
1185
|
+
const initial = react.useMemo(() => ({ x: -startSlot * slideWidth }),
|
|
1186
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1187
|
+
[]);
|
|
1188
|
+
const goTo = react.useCallback((slot) => {
|
|
1189
|
+
var _a;
|
|
1190
|
+
onSwitch === null || onSwitch === void 0 ? void 0 : onSwitch(mod(slot, count));
|
|
1191
|
+
(_a = snapAnimRef.current) === null || _a === void 0 ? void 0 : _a.stop();
|
|
1192
|
+
snapAnimRef.current = react$1.animate(currIndex, slot, transition);
|
|
1193
|
+
}, [count, transition, onSwitch, currIndex]);
|
|
1194
|
+
react.useEffect(() => () => { var _a; return (_a = snapAnimRef.current) === null || _a === void 0 ? void 0 : _a.stop(); }, []);
|
|
1195
|
+
// Drag start: stop any in-flight snap and capture the slot we started on
|
|
1196
|
+
// so an under-threshold release can elastic back to it.
|
|
1197
|
+
const handleDragStart = react.useCallback(() => {
|
|
1198
|
+
var _a;
|
|
1199
|
+
isDraggingRef.current = true;
|
|
1200
|
+
hadDragRef.current = false;
|
|
1201
|
+
dragStartIndexRef.current = Math.round(currIndex.get());
|
|
1202
|
+
(_a = snapAnimRef.current) === null || _a === void 0 ? void 0 : _a.stop();
|
|
1203
|
+
snapAnimRef.current = null;
|
|
1204
|
+
onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart();
|
|
1205
|
+
}, [currIndex, onDragStart]);
|
|
1206
|
+
const handleDragEnd = react.useCallback((_, info) => {
|
|
1207
|
+
isDraggingRef.current = false;
|
|
1208
|
+
const group = groupRef.current;
|
|
1209
|
+
if (!group)
|
|
1210
|
+
return;
|
|
1211
|
+
// Resync the motion value to where use-drag actually locked the position
|
|
1212
|
+
// — the next useFrame will use this same value, so there's no visible
|
|
1213
|
+
// jump between the dragged position and the start of the snap tween.
|
|
1214
|
+
const releasedX = group.position.x;
|
|
1215
|
+
const releasedIndex = -releasedX / slideWidth;
|
|
1216
|
+
currIndex.jump(releasedIndex);
|
|
1217
|
+
const startIndex = dragStartIndexRef.current;
|
|
1218
|
+
const offsetIndex = releasedIndex - startIndex;
|
|
1219
|
+
const overThreshold = Math.abs(offsetIndex) > dragThreshold;
|
|
1220
|
+
const overVelocity = Math.abs(info.velocity.x) > flickVelocity;
|
|
1221
|
+
if (!overThreshold && !overVelocity) {
|
|
1222
|
+
goTo(startIndex);
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
hadDragRef.current = true;
|
|
1226
|
+
const projectedX = releasedX + info.velocity.x * VELOCITY_PROJECTION;
|
|
1227
|
+
const targetSlot = Math.round(-projectedX / slideWidth);
|
|
1228
|
+
goTo(targetSlot);
|
|
1229
|
+
onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(info);
|
|
1230
|
+
}, [slideWidth, goTo, currIndex, dragThreshold, flickVelocity, onDragEnd]);
|
|
1231
|
+
// Drive position from currIndex unless the user is dragging — during drag
|
|
1232
|
+
// use-drag writes to group.position.x directly (and dragMomentum=false
|
|
1233
|
+
// ensures it stops writing the moment the pointer lifts, so there's no
|
|
1234
|
+
// race with the snap animation).
|
|
1235
|
+
fiber.useFrame(() => {
|
|
1236
|
+
const group = groupRef.current;
|
|
1237
|
+
if (!group)
|
|
1238
|
+
return;
|
|
1239
|
+
if (!isDraggingRef.current) {
|
|
1240
|
+
group.position.x = -currIndex.get() * slideWidth;
|
|
1241
|
+
}
|
|
1242
|
+
const groupX = group.position.x;
|
|
1243
|
+
for (let i = 0; i < slotCount; i++) {
|
|
1244
|
+
const ref = itemRefs.current[i];
|
|
1245
|
+
if (!ref)
|
|
1246
|
+
continue;
|
|
1247
|
+
const k = Math.round((-groupX - i * slideWidth) / period);
|
|
1248
|
+
ref.position.x = i * slideWidth + k * period;
|
|
1249
|
+
// +0.5 margin so the leading-edge slot fades in the moment the trailing
|
|
1250
|
+
// one fades out — keeps the loop seamless mid-drag instead of waiting
|
|
1251
|
+
// for snap to settle on an integer slot.
|
|
1252
|
+
const slotDist = (groupX + ref.position.x) / slideWidth;
|
|
1253
|
+
ref.visible = Math.abs(slotDist) <= renderThreshold + 0.5;
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
return (jsxRuntime.jsx(motion.group, { ref: groupRef, drag: "x", dragMomentum: false, initial: initial, onDragStart: handleDragStart, onDragEnd: handleDragEnd, onDrag: (_e, info) => onDrag === null || onDrag === void 0 ? void 0 : onDrag(info), children: loopedItems.map((item, i) => (jsxRuntime.jsx(CarouselSlot, { item: item, itemWidth: itemWidth, slotRef: (el) => { itemRefs.current[i] = el; }, hadDragRef: hadDragRef }, `carouselItem-${i}`))) }));
|
|
1257
|
+
};
|
|
1258
|
+
var index = react.memo(Carousel);
|
|
1259
|
+
|
|
713
1260
|
exports.AnimatePresence = AnimatePresence;
|
|
1261
|
+
exports.Carousel = index;
|
|
714
1262
|
exports.MotionCamera = MotionCamera;
|
|
715
1263
|
exports.motion = motion;
|
|
716
1264
|
exports.usePresence = usePresence;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { memo, useMemo, useRef, useCallback, useEffect, useLayoutEffect } from 'react';
|
|
4
|
+
import { useFrame } from '@react-three/fiber';
|
|
5
|
+
import { Box3, PlaneGeometry } from 'three';
|
|
6
|
+
import { useMotionValue, animate } from 'motion/react';
|
|
7
|
+
import { motion } from '../../render/motion.mjs';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_SPRING = {
|
|
10
|
+
type: 'spring',
|
|
11
|
+
stiffness: 600,
|
|
12
|
+
damping: 100,
|
|
13
|
+
restDelta: 0.001,
|
|
14
|
+
};
|
|
15
|
+
// Seconds of velocity to project past the release point when snapping —
|
|
16
|
+
// higher value = a flick advances further.
|
|
17
|
+
const VELOCITY_PROJECTION = 0.2;
|
|
18
|
+
// Fraction of slideWidth the user must cross, or minimum velocity (world
|
|
19
|
+
// units/sec) required, to commit to the next slide. Below both thresholds
|
|
20
|
+
// the carousel snaps back to the slot the drag started on.
|
|
21
|
+
const DRAG_THRESHOLD_RATIO = 0.25;
|
|
22
|
+
const FLICK_VELOCITY = 1.0;
|
|
23
|
+
const mod = (n, m) => ((n % m) + m) % m;
|
|
24
|
+
const CarouselSlot = memo(({ hadDragRef, item, itemWidth, slotRef }) => {
|
|
25
|
+
const contentRef = useRef(null);
|
|
26
|
+
const coverRef = useRef(null);
|
|
27
|
+
useLayoutEffect(() => {
|
|
28
|
+
const content = contentRef.current;
|
|
29
|
+
const cover = coverRef.current;
|
|
30
|
+
if (!content || !cover)
|
|
31
|
+
return;
|
|
32
|
+
const box = new Box3().setFromObject(content);
|
|
33
|
+
if (box.isEmpty())
|
|
34
|
+
return;
|
|
35
|
+
const h = box.max.y - box.min.y;
|
|
36
|
+
cover.geometry.dispose();
|
|
37
|
+
cover.geometry = new PlaneGeometry(itemWidth, h);
|
|
38
|
+
cover.position.z = box.max.z + 0.001;
|
|
39
|
+
}, [item, itemWidth]);
|
|
40
|
+
// Dispose the generated geometry on unmount — useLayoutEffect's replacement
|
|
41
|
+
// logic only disposes the *previous* geometry, not the final one.
|
|
42
|
+
useEffect(() => () => { var _a; (_a = coverRef.current) === null || _a === void 0 ? void 0 : _a.geometry.dispose(); }, []);
|
|
43
|
+
return (jsxs("group", { ref: slotRef, children: [jsx("group", { ref: contentRef, children: item }), jsxs("mesh", { ref: coverRef, onClick: (e) => {
|
|
44
|
+
if (hadDragRef.current) {
|
|
45
|
+
e.stopPropagation();
|
|
46
|
+
hadDragRef.current = false;
|
|
47
|
+
}
|
|
48
|
+
}, children: [jsx("planeGeometry", { args: [itemWidth, itemWidth] }), jsx("meshBasicMaterial", { transparent: true, opacity: 0, depthWrite: false })] })] }));
|
|
49
|
+
});
|
|
50
|
+
const Carousel = ({ items, itemWidth = 1.5, gap = 0.5, defaultValue = 0, transition = DEFAULT_SPRING, onSwitch, onDragStart, onDrag, onDragEnd, renderThreshold = 1, dragThreshold = DRAG_THRESHOLD_RATIO, flickVelocity = FLICK_VELOCITY, }) => {
|
|
51
|
+
const slideWidth = itemWidth + gap;
|
|
52
|
+
const count = items.length;
|
|
53
|
+
// Render twice the items and center on the first item of the second copy.
|
|
54
|
+
// The first copy lives to the left, so wrap-points sit at ±N*slideWidth
|
|
55
|
+
// from camera center — well off-screen — instead of ±N*slideWidth/2
|
|
56
|
+
// where they'd pop in and out at the visible edges.
|
|
57
|
+
const loopedItems = useMemo(() => [...items, ...items], [items]);
|
|
58
|
+
const slotCount = loopedItems.length;
|
|
59
|
+
const period = slotCount * slideWidth;
|
|
60
|
+
const startSlot = count + defaultValue;
|
|
61
|
+
const groupRef = useRef(null);
|
|
62
|
+
const itemRefs = useRef([]);
|
|
63
|
+
// Single source of truth: a fractional slot index that grows up/down
|
|
64
|
+
// infinitely as the user navigates. group.position.x = -currIndex * slideWidth
|
|
65
|
+
// at rest. Snap animations animate currIndex toward integer targets;
|
|
66
|
+
// useFrame applies it to the group every frame (when not dragging).
|
|
67
|
+
const currIndex = useMotionValue(startSlot);
|
|
68
|
+
const isDraggingRef = useRef(false);
|
|
69
|
+
const hadDragRef = useRef(false);
|
|
70
|
+
const dragStartIndexRef = useRef(startSlot);
|
|
71
|
+
const snapAnimRef = useRef(null);
|
|
72
|
+
const initial = useMemo(() => ({ x: -startSlot * slideWidth }),
|
|
73
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
74
|
+
[]);
|
|
75
|
+
const goTo = useCallback((slot) => {
|
|
76
|
+
var _a;
|
|
77
|
+
onSwitch === null || onSwitch === void 0 ? void 0 : onSwitch(mod(slot, count));
|
|
78
|
+
(_a = snapAnimRef.current) === null || _a === void 0 ? void 0 : _a.stop();
|
|
79
|
+
snapAnimRef.current = animate(currIndex, slot, transition);
|
|
80
|
+
}, [count, transition, onSwitch, currIndex]);
|
|
81
|
+
useEffect(() => () => { var _a; return (_a = snapAnimRef.current) === null || _a === void 0 ? void 0 : _a.stop(); }, []);
|
|
82
|
+
// Drag start: stop any in-flight snap and capture the slot we started on
|
|
83
|
+
// so an under-threshold release can elastic back to it.
|
|
84
|
+
const handleDragStart = useCallback(() => {
|
|
85
|
+
var _a;
|
|
86
|
+
isDraggingRef.current = true;
|
|
87
|
+
hadDragRef.current = false;
|
|
88
|
+
dragStartIndexRef.current = Math.round(currIndex.get());
|
|
89
|
+
(_a = snapAnimRef.current) === null || _a === void 0 ? void 0 : _a.stop();
|
|
90
|
+
snapAnimRef.current = null;
|
|
91
|
+
onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart();
|
|
92
|
+
}, [currIndex, onDragStart]);
|
|
93
|
+
const handleDragEnd = useCallback((_, info) => {
|
|
94
|
+
isDraggingRef.current = false;
|
|
95
|
+
const group = groupRef.current;
|
|
96
|
+
if (!group)
|
|
97
|
+
return;
|
|
98
|
+
// Resync the motion value to where use-drag actually locked the position
|
|
99
|
+
// — the next useFrame will use this same value, so there's no visible
|
|
100
|
+
// jump between the dragged position and the start of the snap tween.
|
|
101
|
+
const releasedX = group.position.x;
|
|
102
|
+
const releasedIndex = -releasedX / slideWidth;
|
|
103
|
+
currIndex.jump(releasedIndex);
|
|
104
|
+
const startIndex = dragStartIndexRef.current;
|
|
105
|
+
const offsetIndex = releasedIndex - startIndex;
|
|
106
|
+
const overThreshold = Math.abs(offsetIndex) > dragThreshold;
|
|
107
|
+
const overVelocity = Math.abs(info.velocity.x) > flickVelocity;
|
|
108
|
+
if (!overThreshold && !overVelocity) {
|
|
109
|
+
goTo(startIndex);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
hadDragRef.current = true;
|
|
113
|
+
const projectedX = releasedX + info.velocity.x * VELOCITY_PROJECTION;
|
|
114
|
+
const targetSlot = Math.round(-projectedX / slideWidth);
|
|
115
|
+
goTo(targetSlot);
|
|
116
|
+
onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd(info);
|
|
117
|
+
}, [slideWidth, goTo, currIndex, dragThreshold, flickVelocity, onDragEnd]);
|
|
118
|
+
// Drive position from currIndex unless the user is dragging — during drag
|
|
119
|
+
// use-drag writes to group.position.x directly (and dragMomentum=false
|
|
120
|
+
// ensures it stops writing the moment the pointer lifts, so there's no
|
|
121
|
+
// race with the snap animation).
|
|
122
|
+
useFrame(() => {
|
|
123
|
+
const group = groupRef.current;
|
|
124
|
+
if (!group)
|
|
125
|
+
return;
|
|
126
|
+
if (!isDraggingRef.current) {
|
|
127
|
+
group.position.x = -currIndex.get() * slideWidth;
|
|
128
|
+
}
|
|
129
|
+
const groupX = group.position.x;
|
|
130
|
+
for (let i = 0; i < slotCount; i++) {
|
|
131
|
+
const ref = itemRefs.current[i];
|
|
132
|
+
if (!ref)
|
|
133
|
+
continue;
|
|
134
|
+
const k = Math.round((-groupX - i * slideWidth) / period);
|
|
135
|
+
ref.position.x = i * slideWidth + k * period;
|
|
136
|
+
// +0.5 margin so the leading-edge slot fades in the moment the trailing
|
|
137
|
+
// one fades out — keeps the loop seamless mid-drag instead of waiting
|
|
138
|
+
// for snap to settle on an integer slot.
|
|
139
|
+
const slotDist = (groupX + ref.position.x) / slideWidth;
|
|
140
|
+
ref.visible = Math.abs(slotDist) <= renderThreshold + 0.5;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return (jsx(motion.group, { ref: groupRef, drag: "x", dragMomentum: false, initial: initial, onDragStart: handleDragStart, onDragEnd: handleDragEnd, onDrag: (_e, info) => onDrag === null || onDrag === void 0 ? void 0 : onDrag(info), children: loopedItems.map((item, i) => (jsx(CarouselSlot, { item: item, itemWidth: itemWidth, slotRef: (el) => { itemRefs.current[i] = el; }, hadDragRef: hadDragRef }, `carouselItem-${i}`))) }));
|
|
144
|
+
};
|
|
145
|
+
var index = memo(Carousel);
|
|
146
|
+
|
|
147
|
+
export { index as default };
|
package/dist/es/index.mjs
CHANGED
|
@@ -3,3 +3,4 @@ export { motion } from './render/motion.mjs';
|
|
|
3
3
|
export { default as MotionCamera } from './components/MotionCamera.mjs';
|
|
4
4
|
export { AnimatePresence } from './components/AnimatePresence/index.mjs';
|
|
5
5
|
export { usePresence } from './components/AnimatePresence/PresenceContext.mjs';
|
|
6
|
+
export { default as Carousel } from './components/Carousel/index.mjs';
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useRef, useCallback, useEffect } from 'react';
|
|
3
|
+
import { useThree, useFrame } from '@react-three/fiber';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
|
|
6
|
+
// World-space units per CSS pixel at the given depth from the camera.
|
|
7
|
+
const getWorldPerPixel = (camera, depth, viewportHeight) => {
|
|
8
|
+
var _a;
|
|
9
|
+
const ortho = camera;
|
|
10
|
+
if (ortho.isOrthographicCamera) {
|
|
11
|
+
return ((ortho.top - ortho.bottom) / (viewportHeight * (ortho.zoom || 1)));
|
|
12
|
+
}
|
|
13
|
+
const persp = camera;
|
|
14
|
+
const fov = ((_a = persp.fov) !== null && _a !== void 0 ? _a : 60) * (Math.PI / 180);
|
|
15
|
+
return (2 * Math.tan(fov / 2) * depth) / viewportHeight;
|
|
16
|
+
};
|
|
17
|
+
const clampWithElastic = (value, min, max, elastic) => {
|
|
18
|
+
if (min !== undefined && value < min)
|
|
19
|
+
return min + (value - min) * elastic;
|
|
20
|
+
if (max !== undefined && value > max)
|
|
21
|
+
return max + (value - max) * elastic;
|
|
22
|
+
return value;
|
|
23
|
+
};
|
|
24
|
+
function useDrag(isStatic, props, options) {
|
|
25
|
+
const { camera, gl } = useThree();
|
|
26
|
+
const { drag, whileDrag, onDragStart, onPointerDown, transition } = props;
|
|
27
|
+
const { instanceRef, captureInstanceState, buildTargetFromState, animateToTarget, resolveVariant, stopAnimation, } = options;
|
|
28
|
+
const isDraggingRef = useRef(false);
|
|
29
|
+
const dragStartPointerRef = useRef({ x: 0, y: 0 });
|
|
30
|
+
const dragStartPosRef = useRef(new THREE.Vector3());
|
|
31
|
+
const cameraRightRef = useRef(new THREE.Vector3());
|
|
32
|
+
const cameraUpRef = useRef(new THREE.Vector3());
|
|
33
|
+
const worldPerPixelRef = useRef(0);
|
|
34
|
+
const targetPosRef = useRef(new THREE.Vector3());
|
|
35
|
+
const lastPosRef = useRef(new THREE.Vector3());
|
|
36
|
+
const velocityRef = useRef(new THREE.Vector3());
|
|
37
|
+
const lastDeltaRef = useRef(new THREE.Vector3());
|
|
38
|
+
const lastTimeRef = useRef(0);
|
|
39
|
+
const preDragStateRef = useRef(null);
|
|
40
|
+
const pointerIdRef = useRef(null);
|
|
41
|
+
const springStateRef = useRef(null);
|
|
42
|
+
const lastFrameTimeRef = useRef(0);
|
|
43
|
+
const propsRef = useRef(props);
|
|
44
|
+
propsRef.current = props;
|
|
45
|
+
useFrame(() => {
|
|
46
|
+
var _a;
|
|
47
|
+
const instance = instanceRef.current;
|
|
48
|
+
if (!instance)
|
|
49
|
+
return;
|
|
50
|
+
const obj = instance;
|
|
51
|
+
// Phase 1: dragging — apply the drag target
|
|
52
|
+
if (isDraggingRef.current) {
|
|
53
|
+
const t = targetPosRef.current;
|
|
54
|
+
obj.position.set(t.x, t.y, t.z);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// Phase 2: spring (momentum / snap-to-origin)
|
|
58
|
+
const s = springStateRef.current;
|
|
59
|
+
if (!s || !s.active)
|
|
60
|
+
return;
|
|
61
|
+
const now = performance.now();
|
|
62
|
+
let dt = (now - lastFrameTimeRef.current) / 1000;
|
|
63
|
+
if (dt > 0.05)
|
|
64
|
+
dt = 0.05;
|
|
65
|
+
if (dt <= 0)
|
|
66
|
+
dt = 1 / 60;
|
|
67
|
+
lastFrameTimeRef.current = now;
|
|
68
|
+
// Sub-step the integrator for stability with stiff springs
|
|
69
|
+
const subSteps = 4;
|
|
70
|
+
const subDt = dt / subSteps;
|
|
71
|
+
for (let i = 0; i < subSteps; i++) {
|
|
72
|
+
if (s.hasX) {
|
|
73
|
+
const force = -s.stiffness * (obj.position.x - s.targetX) - s.damping * s.velX;
|
|
74
|
+
s.velX += force * subDt;
|
|
75
|
+
obj.position.x += s.velX * subDt;
|
|
76
|
+
}
|
|
77
|
+
if (s.hasY) {
|
|
78
|
+
const force = -s.stiffness * (obj.position.y - s.targetY) - s.damping * s.velY;
|
|
79
|
+
s.velY += force * subDt;
|
|
80
|
+
obj.position.y += s.velY * subDt;
|
|
81
|
+
}
|
|
82
|
+
if (s.hasZ) {
|
|
83
|
+
const force = -s.stiffness * (obj.position.z - s.targetZ) - s.damping * s.velZ;
|
|
84
|
+
s.velZ += force * subDt;
|
|
85
|
+
obj.position.z += s.velZ * subDt;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Rest detection — stop when both speed and offset are tiny
|
|
89
|
+
const speedSq = s.velX * s.velX + s.velY * s.velY + s.velZ * s.velZ;
|
|
90
|
+
const offX = s.hasX ? obj.position.x - s.targetX : 0;
|
|
91
|
+
const offY = s.hasY ? obj.position.y - s.targetY : 0;
|
|
92
|
+
const offZ = s.hasZ ? obj.position.z - s.targetZ : 0;
|
|
93
|
+
const offsetSq = offX * offX + offY * offY + offZ * offZ;
|
|
94
|
+
if (speedSq < 0.0005 && offsetSq < 0.0005) {
|
|
95
|
+
if (s.hasX)
|
|
96
|
+
obj.position.x = s.targetX;
|
|
97
|
+
if (s.hasY)
|
|
98
|
+
obj.position.y = s.targetY;
|
|
99
|
+
if (s.hasZ)
|
|
100
|
+
obj.position.z = s.targetZ;
|
|
101
|
+
s.active = false;
|
|
102
|
+
(_a = s.onComplete) === null || _a === void 0 ? void 0 : _a.call(s);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
const handlePointerDown = useCallback((event) => {
|
|
106
|
+
const instance = instanceRef.current;
|
|
107
|
+
if (!instance)
|
|
108
|
+
return;
|
|
109
|
+
event.stopPropagation();
|
|
110
|
+
// Halt any in-flight motion-library animation (e.g. main `animate` prop
|
|
111
|
+
// tween that's still running) and any active drag spring.
|
|
112
|
+
stopAnimation();
|
|
113
|
+
if (springStateRef.current)
|
|
114
|
+
springStateRef.current.active = false;
|
|
115
|
+
if (whileDrag) {
|
|
116
|
+
preDragStateRef.current = captureInstanceState();
|
|
117
|
+
const targetValues = typeof whileDrag === "string"
|
|
118
|
+
? resolveVariant(whileDrag)
|
|
119
|
+
: whileDrag;
|
|
120
|
+
animateToTarget(targetValues, transition || { duration: 0.1 });
|
|
121
|
+
}
|
|
122
|
+
const obj = instance;
|
|
123
|
+
// Capture initial pointer + position. All subsequent moves are computed
|
|
124
|
+
// as `initialPos + pixelDelta * worldPerPixel` — never re-derived from
|
|
125
|
+
// the live (potentially-mid-animation) instance position.
|
|
126
|
+
dragStartPointerRef.current.x = event.nativeEvent.clientX;
|
|
127
|
+
dragStartPointerRef.current.y = event.nativeEvent.clientY;
|
|
128
|
+
dragStartPosRef.current.set(obj.position.x, obj.position.y, obj.position.z);
|
|
129
|
+
targetPosRef.current.copy(dragStartPosRef.current);
|
|
130
|
+
lastPosRef.current.copy(dragStartPosRef.current);
|
|
131
|
+
lastDeltaRef.current.set(0, 0, 0);
|
|
132
|
+
velocityRef.current.set(0, 0, 0);
|
|
133
|
+
lastTimeRef.current = performance.now();
|
|
134
|
+
// Compute world-units-per-pixel at the object's perpendicular depth
|
|
135
|
+
// (distance from camera along its forward axis).
|
|
136
|
+
const cameraForward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
|
|
137
|
+
const cameraToObj = new THREE.Vector3().subVectors(dragStartPosRef.current, camera.position);
|
|
138
|
+
const depth = Math.abs(cameraToObj.dot(cameraForward));
|
|
139
|
+
const rect = gl.domElement.getBoundingClientRect();
|
|
140
|
+
worldPerPixelRef.current = getWorldPerPixel(camera, depth, rect.height);
|
|
141
|
+
// Cache camera basis vectors so screen X/Y maps correctly to world
|
|
142
|
+
// X/Y/Z even if the camera is tilted.
|
|
143
|
+
cameraRightRef.current.set(1, 0, 0).applyQuaternion(camera.quaternion);
|
|
144
|
+
cameraUpRef.current.set(0, 1, 0).applyQuaternion(camera.quaternion);
|
|
145
|
+
isDraggingRef.current = true;
|
|
146
|
+
pointerIdRef.current = event.nativeEvent.pointerId;
|
|
147
|
+
try {
|
|
148
|
+
gl.domElement.setPointerCapture(event.nativeEvent.pointerId);
|
|
149
|
+
}
|
|
150
|
+
catch (_a) {
|
|
151
|
+
// not all environments support setPointerCapture
|
|
152
|
+
}
|
|
153
|
+
onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart(event.nativeEvent, {
|
|
154
|
+
point: { x: event.nativeEvent.clientX, y: event.nativeEvent.clientY },
|
|
155
|
+
offset: { x: 0, y: 0, z: 0 },
|
|
156
|
+
delta: { x: 0, y: 0, z: 0 },
|
|
157
|
+
velocity: { x: 0, y: 0, z: 0 },
|
|
158
|
+
});
|
|
159
|
+
onPointerDown === null || onPointerDown === void 0 ? void 0 : onPointerDown(event);
|
|
160
|
+
},
|
|
161
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
162
|
+
[
|
|
163
|
+
whileDrag,
|
|
164
|
+
onDragStart,
|
|
165
|
+
onPointerDown,
|
|
166
|
+
camera,
|
|
167
|
+
gl,
|
|
168
|
+
captureInstanceState,
|
|
169
|
+
resolveVariant,
|
|
170
|
+
animateToTarget,
|
|
171
|
+
stopAnimation,
|
|
172
|
+
transition,
|
|
173
|
+
instanceRef,
|
|
174
|
+
]);
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
const handlePointerMove = (event) => {
|
|
177
|
+
var _a;
|
|
178
|
+
if (!isDraggingRef.current || event.pointerId !== pointerIdRef.current)
|
|
179
|
+
return;
|
|
180
|
+
const instance = instanceRef.current;
|
|
181
|
+
if (!instance)
|
|
182
|
+
return;
|
|
183
|
+
const cur = propsRef.current;
|
|
184
|
+
const dragAxis = cur.drag;
|
|
185
|
+
const elasticVal = typeof cur.dragElastic === "number"
|
|
186
|
+
? cur.dragElastic
|
|
187
|
+
: cur.dragElastic === false
|
|
188
|
+
? 0
|
|
189
|
+
: 0.5;
|
|
190
|
+
const constraints = cur.dragConstraints;
|
|
191
|
+
const pixelDx = event.clientX - dragStartPointerRef.current.x;
|
|
192
|
+
const pixelDy = event.clientY - dragStartPointerRef.current.y;
|
|
193
|
+
const scale = worldPerPixelRef.current;
|
|
194
|
+
let newX = dragStartPosRef.current.x;
|
|
195
|
+
let newY = dragStartPosRef.current.y;
|
|
196
|
+
let newZ = dragStartPosRef.current.z;
|
|
197
|
+
if (dragAxis === "z") {
|
|
198
|
+
// Vertical screen movement → Z. Cursor down (pixelDy > 0) = closer.
|
|
199
|
+
newZ = dragStartPosRef.current.z + pixelDy * scale;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// Map screen XY to camera-relative world delta.
|
|
203
|
+
const r = cameraRightRef.current;
|
|
204
|
+
const u = cameraUpRef.current;
|
|
205
|
+
const worldDx = r.x * pixelDx * scale + u.x * -pixelDy * scale;
|
|
206
|
+
const worldDy = r.y * pixelDx * scale + u.y * -pixelDy * scale;
|
|
207
|
+
const worldDz = r.z * pixelDx * scale + u.z * -pixelDy * scale;
|
|
208
|
+
if (dragAxis === "x") {
|
|
209
|
+
newX = dragStartPosRef.current.x + worldDx;
|
|
210
|
+
}
|
|
211
|
+
else if (dragAxis === "y") {
|
|
212
|
+
newY = dragStartPosRef.current.y + worldDy;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
newX = dragStartPosRef.current.x + worldDx;
|
|
216
|
+
newY = dragStartPosRef.current.y + worldDy;
|
|
217
|
+
newZ = dragStartPosRef.current.z + worldDz;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Constraints with elastic rubber-banding
|
|
221
|
+
if (constraints) {
|
|
222
|
+
if (dragAxis !== "y" && dragAxis !== "z") {
|
|
223
|
+
newX = clampWithElastic(newX, constraints.left, constraints.right, elasticVal);
|
|
224
|
+
}
|
|
225
|
+
if (dragAxis !== "x" && dragAxis !== "z") {
|
|
226
|
+
newY = clampWithElastic(newY, constraints.bottom, constraints.top, elasticVal);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Velocity tracking (used for momentum on release)
|
|
230
|
+
const now = performance.now();
|
|
231
|
+
const dt = (now - lastTimeRef.current) / 1000;
|
|
232
|
+
if (dt > 0) {
|
|
233
|
+
const dx = newX - lastPosRef.current.x;
|
|
234
|
+
const dy = newY - lastPosRef.current.y;
|
|
235
|
+
const dz = newZ - lastPosRef.current.z;
|
|
236
|
+
velocityRef.current.set(dx / dt, dy / dt, dz / dt);
|
|
237
|
+
lastDeltaRef.current.set(dx, dy, dz);
|
|
238
|
+
}
|
|
239
|
+
lastTimeRef.current = now;
|
|
240
|
+
lastPosRef.current.set(newX, newY, newZ);
|
|
241
|
+
// Stash target — useFrame applies it next R3F frame.
|
|
242
|
+
targetPosRef.current.set(newX, newY, newZ);
|
|
243
|
+
const offset = {
|
|
244
|
+
x: newX - dragStartPosRef.current.x,
|
|
245
|
+
y: newY - dragStartPosRef.current.y,
|
|
246
|
+
z: newZ - dragStartPosRef.current.z,
|
|
247
|
+
};
|
|
248
|
+
(_a = cur.onDrag) === null || _a === void 0 ? void 0 : _a.call(cur, event, {
|
|
249
|
+
point: { x: event.clientX, y: event.clientY },
|
|
250
|
+
offset,
|
|
251
|
+
delta: {
|
|
252
|
+
x: lastDeltaRef.current.x,
|
|
253
|
+
y: lastDeltaRef.current.y,
|
|
254
|
+
z: lastDeltaRef.current.z,
|
|
255
|
+
},
|
|
256
|
+
velocity: {
|
|
257
|
+
x: velocityRef.current.x,
|
|
258
|
+
y: velocityRef.current.y,
|
|
259
|
+
z: velocityRef.current.z,
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
const handlePointerUp = (event) => {
|
|
264
|
+
var _a, _b;
|
|
265
|
+
if (!isDraggingRef.current || event.pointerId !== pointerIdRef.current)
|
|
266
|
+
return;
|
|
267
|
+
isDraggingRef.current = false;
|
|
268
|
+
pointerIdRef.current = null;
|
|
269
|
+
const instance = instanceRef.current;
|
|
270
|
+
const cur = propsRef.current;
|
|
271
|
+
// Lock in the final dragged position synchronously. Once useFrame stops
|
|
272
|
+
// applying the drag target, a still-pending write from the *previous*
|
|
273
|
+
// momentum animation can land before the new animateToTarget() call
|
|
274
|
+
// reads instance.position to use as its "from" value — which causes the
|
|
275
|
+
// new animation to start from the old momentum's path instead of where
|
|
276
|
+
// the user actually released.
|
|
277
|
+
if (instance) {
|
|
278
|
+
const obj = instance;
|
|
279
|
+
const t = targetPosRef.current;
|
|
280
|
+
obj.position.set(t.x, t.y, t.z);
|
|
281
|
+
}
|
|
282
|
+
// Restore whileDrag visual state
|
|
283
|
+
if (cur.whileDrag && preDragStateRef.current) {
|
|
284
|
+
const targetValues = buildTargetFromState(preDragStateRef.current);
|
|
285
|
+
animateToTarget(targetValues, options.transition || { duration: 0.2 });
|
|
286
|
+
const dur = ((_a = options.transition) === null || _a === void 0 ? void 0 : _a.duration) || 0.2;
|
|
287
|
+
setTimeout(() => {
|
|
288
|
+
preDragStateRef.current = null;
|
|
289
|
+
}, dur * 1000);
|
|
290
|
+
}
|
|
291
|
+
if (!instance)
|
|
292
|
+
return;
|
|
293
|
+
const finalPos = targetPosRef.current;
|
|
294
|
+
const offset = {
|
|
295
|
+
x: finalPos.x - dragStartPosRef.current.x,
|
|
296
|
+
y: finalPos.y - dragStartPosRef.current.y,
|
|
297
|
+
z: finalPos.z - dragStartPosRef.current.z,
|
|
298
|
+
};
|
|
299
|
+
const vel = velocityRef.current;
|
|
300
|
+
// Read spring tuning from dragTransition (if user provided one).
|
|
301
|
+
const dt = cur.dragTransition;
|
|
302
|
+
const userStiffness = typeof (dt === null || dt === void 0 ? void 0 : dt.stiffness) === "number" ? dt.stiffness : undefined;
|
|
303
|
+
const userDamping = typeof (dt === null || dt === void 0 ? void 0 : dt.damping) === "number" ? dt.damping : undefined;
|
|
304
|
+
if (cur.dragSnapToOrigin) {
|
|
305
|
+
lastFrameTimeRef.current = performance.now();
|
|
306
|
+
springStateRef.current = {
|
|
307
|
+
active: true,
|
|
308
|
+
targetX: dragStartPosRef.current.x,
|
|
309
|
+
targetY: dragStartPosRef.current.y,
|
|
310
|
+
targetZ: dragStartPosRef.current.z,
|
|
311
|
+
velX: vel.x,
|
|
312
|
+
velY: vel.y,
|
|
313
|
+
velZ: vel.z,
|
|
314
|
+
hasX: true,
|
|
315
|
+
hasY: true,
|
|
316
|
+
hasZ: true,
|
|
317
|
+
stiffness: userStiffness !== null && userStiffness !== void 0 ? userStiffness : 400,
|
|
318
|
+
damping: userDamping !== null && userDamping !== void 0 ? userDamping : 40,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
else if (cur.dragMomentum !== false) {
|
|
322
|
+
const speed = Math.sqrt(Math.pow(vel.x, 2) + Math.pow(vel.y, 2) + Math.pow(vel.z, 2));
|
|
323
|
+
if (speed > 0.5) {
|
|
324
|
+
const coeff = 0.25;
|
|
325
|
+
const dragAxis = cur.drag;
|
|
326
|
+
const hasX = dragAxis !== "y" && dragAxis !== "z";
|
|
327
|
+
const hasY = dragAxis !== "x" && dragAxis !== "z";
|
|
328
|
+
const hasZ = dragAxis === "z";
|
|
329
|
+
let targetX = hasX ? finalPos.x + vel.x * coeff : finalPos.x;
|
|
330
|
+
let targetY = hasY ? finalPos.y + vel.y * coeff : finalPos.y;
|
|
331
|
+
const targetZ = hasZ ? finalPos.z + vel.z * coeff : finalPos.z;
|
|
332
|
+
const constraints = cur.dragConstraints;
|
|
333
|
+
if (constraints) {
|
|
334
|
+
if (hasX) {
|
|
335
|
+
if (constraints.left !== undefined)
|
|
336
|
+
targetX = Math.max(targetX, constraints.left);
|
|
337
|
+
if (constraints.right !== undefined)
|
|
338
|
+
targetX = Math.min(targetX, constraints.right);
|
|
339
|
+
}
|
|
340
|
+
if (hasY) {
|
|
341
|
+
if (constraints.bottom !== undefined)
|
|
342
|
+
targetY = Math.max(targetY, constraints.bottom);
|
|
343
|
+
if (constraints.top !== undefined)
|
|
344
|
+
targetY = Math.min(targetY, constraints.top);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
lastFrameTimeRef.current = performance.now();
|
|
348
|
+
springStateRef.current = {
|
|
349
|
+
active: true,
|
|
350
|
+
targetX,
|
|
351
|
+
targetY,
|
|
352
|
+
targetZ,
|
|
353
|
+
velX: hasX ? vel.x : 0,
|
|
354
|
+
velY: hasY ? vel.y : 0,
|
|
355
|
+
velZ: hasZ ? vel.z : 0,
|
|
356
|
+
hasX,
|
|
357
|
+
hasY,
|
|
358
|
+
hasZ,
|
|
359
|
+
stiffness: userStiffness !== null && userStiffness !== void 0 ? userStiffness : 200,
|
|
360
|
+
damping: userDamping !== null && userDamping !== void 0 ? userDamping : 50,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
(_b = cur.onDragEnd) === null || _b === void 0 ? void 0 : _b.call(cur, event, {
|
|
365
|
+
point: { x: event.clientX, y: event.clientY },
|
|
366
|
+
offset,
|
|
367
|
+
delta: {
|
|
368
|
+
x: lastDeltaRef.current.x,
|
|
369
|
+
y: lastDeltaRef.current.y,
|
|
370
|
+
z: lastDeltaRef.current.z,
|
|
371
|
+
},
|
|
372
|
+
velocity: { x: vel.x, y: vel.y, z: vel.z },
|
|
373
|
+
});
|
|
374
|
+
};
|
|
375
|
+
window.addEventListener("pointermove", handlePointerMove);
|
|
376
|
+
window.addEventListener("pointerup", handlePointerUp);
|
|
377
|
+
return () => {
|
|
378
|
+
window.removeEventListener("pointermove", handlePointerMove);
|
|
379
|
+
window.removeEventListener("pointerup", handlePointerUp);
|
|
380
|
+
};
|
|
381
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
382
|
+
}, [gl, instanceRef, animateToTarget, buildTargetFromState]);
|
|
383
|
+
const isDragEnabled = drag !== undefined && drag !== false;
|
|
384
|
+
if (!isDragEnabled)
|
|
385
|
+
return {};
|
|
386
|
+
return { onPointerDown: handlePointerDown };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export { useDrag };
|
|
@@ -6,6 +6,7 @@ import { PresenceContext } from '../components/AnimatePresence/PresenceContext.m
|
|
|
6
6
|
import { useRender } from './use-render.mjs';
|
|
7
7
|
import { useHover } from './gestures/use-hover.mjs';
|
|
8
8
|
import { useTap } from './gestures/use-tap.mjs';
|
|
9
|
+
import { useDrag } from './gestures/use-drag.mjs';
|
|
9
10
|
import { createAnimationState, createCallbackOptions, registerAnimation } from './events/index.mjs';
|
|
10
11
|
|
|
11
12
|
const MotionContext = createContext(null);
|
|
@@ -245,14 +246,20 @@ function custom(Component) {
|
|
|
245
246
|
return () => { var _a; return (_a = animationRef.current) === null || _a === void 0 ? void 0 : _a.stop(); };
|
|
246
247
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
247
248
|
}, [presenceContext === null || presenceContext === void 0 ? void 0 : presenceContext.isPresent]);
|
|
249
|
+
const stopAnimation = useCallback(() => {
|
|
250
|
+
var _a;
|
|
251
|
+
(_a = animationRef.current) === null || _a === void 0 ? void 0 : _a.stop();
|
|
252
|
+
}, []);
|
|
248
253
|
const gestureProps = {
|
|
254
|
+
instanceRef,
|
|
249
255
|
captureInstanceState,
|
|
250
256
|
buildTargetFromState,
|
|
251
257
|
animateToTarget,
|
|
252
258
|
resolveVariant,
|
|
253
259
|
transition,
|
|
260
|
+
stopAnimation,
|
|
254
261
|
};
|
|
255
|
-
const gestureHandlers = Object.assign(Object.assign({}, useHover(false, props, gestureProps)), useTap(false, props, gestureProps));
|
|
262
|
+
const gestureHandlers = Object.assign(Object.assign(Object.assign({}, useHover(false, props, gestureProps)), useTap(false, props, gestureProps)), useDrag(false, props, gestureProps));
|
|
256
263
|
const resolvedInitialValues = typeof initial === "string" ? resolveVariant(initial) : initial;
|
|
257
264
|
const element = useRender(Component, Object.assign(Object.assign(Object.assign({}, restProps), gestureHandlers), { children }), ref, instanceRef, resolvedInitialValues);
|
|
258
265
|
const getNextChildIndex = useCallback(() => {
|
package/dist/index.d.ts
CHANGED
|
@@ -2,12 +2,49 @@ import { Vector3, Euler, Color, ReactThreeFiber } from '@react-three/fiber';
|
|
|
2
2
|
import * as THREE from 'three';
|
|
3
3
|
import * as react from 'react';
|
|
4
4
|
import { ForwardRefExoticComponent, PropsWithoutRef, RefAttributes, ReactNode } from 'react';
|
|
5
|
-
import { MotionValue, MotionProps, ResolvedValues } from 'motion/react';
|
|
5
|
+
import { MotionValue, MotionProps, Transition, ResolvedValues } from 'motion/react';
|
|
6
6
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
7
7
|
|
|
8
8
|
type ThreeElement = InstanceType<typeof THREE.Object3D> & Record<string, unknown>;
|
|
9
|
-
interface
|
|
9
|
+
interface DragInfo {
|
|
10
|
+
point: {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
};
|
|
14
|
+
offset: {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
z: number;
|
|
18
|
+
};
|
|
19
|
+
delta: {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
z: number;
|
|
23
|
+
};
|
|
24
|
+
velocity: {
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
z: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
interface DragConstraints {
|
|
31
|
+
left?: number;
|
|
32
|
+
right?: number;
|
|
33
|
+
top?: number;
|
|
34
|
+
bottom?: number;
|
|
35
|
+
}
|
|
36
|
+
interface ThreeMotionProps extends Omit<MotionProps, "style" | "children" | "onUpdate" | "onAnimationStart" | "onAnimationComplete" | "drag" | "dragConstraints" | "dragElastic" | "dragMomentum" | "dragSnapToOrigin" | "dragTransition" | "onDrag" | "onDragStart" | "onDragEnd" | "whileDrag" | "dragDirectionLock" | "dragPropagation" | "dragListener"> {
|
|
10
37
|
[key: string]: unknown;
|
|
38
|
+
drag?: boolean | "x" | "y" | "z";
|
|
39
|
+
dragConstraints?: DragConstraints;
|
|
40
|
+
dragElastic?: number | boolean;
|
|
41
|
+
dragMomentum?: boolean;
|
|
42
|
+
dragSnapToOrigin?: boolean;
|
|
43
|
+
dragTransition?: Transition;
|
|
44
|
+
whileDrag?: Record<string, unknown> | string;
|
|
45
|
+
onDragStart?: (event: PointerEvent, info: DragInfo) => void;
|
|
46
|
+
onDrag?: (event: PointerEvent, info: DragInfo) => void;
|
|
47
|
+
onDragEnd?: (event: PointerEvent, info: DragInfo) => void;
|
|
11
48
|
onInstanceUpdate?: ReactThreeFiber.ThreeElements["object3D"]["onUpdate"];
|
|
12
49
|
onUpdate?: (values: Record<string, unknown>, animationVariant?: string) => void;
|
|
13
50
|
onAnimationStart?: (values: Record<string, unknown>, animationVariant?: string) => void;
|
|
@@ -90,5 +127,22 @@ interface AnimatePresenceProps {
|
|
|
90
127
|
}
|
|
91
128
|
declare function AnimatePresence({ children, mode, onExitComplete, custom, }: AnimatePresenceProps): react.FunctionComponentElement<react.ProviderProps<PresenceContextValue | null>>[];
|
|
92
129
|
|
|
93
|
-
|
|
94
|
-
|
|
130
|
+
interface CarouselProps {
|
|
131
|
+
items: ReactNode[];
|
|
132
|
+
itemWidth?: number;
|
|
133
|
+
gap?: number;
|
|
134
|
+
defaultValue?: number;
|
|
135
|
+
transition?: Transition;
|
|
136
|
+
onSwitch?: (index: number) => void;
|
|
137
|
+
onDragStart?: () => void;
|
|
138
|
+
onDrag?: (info: DragInfo) => void;
|
|
139
|
+
onDragEnd?: (info: DragInfo) => void;
|
|
140
|
+
renderThreshold?: number;
|
|
141
|
+
dragThreshold?: number;
|
|
142
|
+
flickVelocity?: number;
|
|
143
|
+
}
|
|
144
|
+
declare const Carousel: ({ items, itemWidth, gap, defaultValue, transition, onSwitch, onDragStart, onDrag, onDragEnd, renderThreshold, dragThreshold, flickVelocity, }: CarouselProps) => react_jsx_runtime.JSX.Element;
|
|
145
|
+
declare const _default: typeof Carousel;
|
|
146
|
+
|
|
147
|
+
export { AnimatePresence, _default as Carousel, MotionCamera, motion, usePresence };
|
|
148
|
+
export type { AcceptMotionValues, CarouselProps, DragConstraints, DragInfo, ForwardRefComponent, ThreeElement, ThreeMotionComponents, ThreeMotionProps, ThreeRenderState };
|
package/package.json
CHANGED