@past3lle/carousel-hooks
Version:
PASTELLE carousel animation hooks built on top of @usegesture and react-springs
541 lines (527 loc) • 19.1 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var web = require('@react-spring/web');
var react = require('@use-gesture/react');
var clamp = _interopDefault(require('lodash.clamp'));
var react$1 = require('react');
var hooks = require('@past3lle/hooks');
var utils$1 = require('@past3lle/utils');
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
function _objectWithoutPropertiesLoose(r, e) {
if (null == r) return {};
var t = {};
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
if (e.includes(n)) continue;
t[n] = r[n];
}
return t;
}
var _excluded = ["dataLength", "itemSize", "axis", "dAxis", "visible", "prevRef"];
var _closerTo = function _closerTo(a, b, c) {
return Math.abs(c - a) >= Math.abs(b - a) ? b : c;
};
function _getLimits(point, mult) {
var range = point / mult;
var highPoint = mult * Math.ceil(range);
var bounds = [highPoint - mult, highPoint];
return bounds;
}
function _calcAnchorPos(point, mult) {
var _getLimits2 = _getLimits(point, mult),
limitA = _getLimits2[0],
limitB = _getLimits2[1];
return _closerTo(point, limitA, limitB);
}
function getNearestAxisPoint(point, multiple) {
var anchorPoint = _calcAnchorPos(point, multiple);
return anchorPoint;
}
var getIndex = function getIndex(axis, l) {
return (axis < 0 ? axis + l : axis) % l;
};
var getPos = function getPos(i, firstVisible, firstVisibleIndex, length) {
return getIndex(i - firstVisible + firstVisibleIndex, length);
};
var calculateInfiniteScrollApiLogic = function calculateInfiniteScrollApiLogic(i, axisDirection, _ref) {
var _ref2;
var prevRef = _ref.prevRef,
active = _ref.active,
last = _ref.last,
axis = _ref.axis,
dAxis = _ref.dAxis,
mAxis = _ref.mAxis,
firstVis = _ref.firstVis,
firstVisIdx = _ref.firstVisIdx,
scaleOptions = _ref.scaleOptions,
snapOnScroll = _ref.snapOnScroll,
config = _ref.config,
dataLength = _ref.dataLength,
itemSize = _ref.itemSize,
setCurrentIndex = _ref.setCurrentIndex;
var position = getPos(i, firstVis, firstVisIdx, dataLength);
var prevPosition = getPos(i, prevRef.current[0], prevRef.current[1], dataLength);
var rank = firstVis - (axis < 0 ? dataLength : 0) + position - firstVisIdx;
var scale = mAxis && scaleOptions != null && scaleOptions.scaleOnScroll && active ? Math.max(1 - Math.abs(mAxis) / itemSize / 2, scaleOptions.scaleOnScroll) : scaleOptions.initialScale;
var axisPos = -axis % (itemSize * dataLength) + itemSize * rank;
var anchorPoint = last && getNearestAxisPoint(axisPos, itemSize);
var onScreen = anchorPoint === 0;
if (onScreen) {
setCurrentIndex(i);
}
var configPos = dAxis > 0 ? position : dataLength - position;
var immediate = dAxis < 0 ? prevPosition > position : prevPosition < position;
return _ref2 = {}, _ref2[axisDirection] = !active && snapOnScroll ? anchorPoint || undefined : axisPos, _ref2.scale = scale, _ref2.immediate = immediate, _ref2.config = typeof config === 'function' ? config({
configPos: configPos,
length: dataLength
}) : config, _ref2;
};
function runInfiniteScrollSprings(api, axisDirection, _ref3) {
var dataLength = _ref3.dataLength,
itemSize = _ref3.itemSize,
axis = _ref3.axis,
dAxis = _ref3.dAxis,
visible = _ref3.visible,
prevRef = _ref3.prevRef,
rest = _objectWithoutPropertiesLoose(_ref3, _excluded);
var itemPosition = Math.floor(axis / itemSize) % dataLength;
var firstVis = getIndex(itemPosition, dataLength);
var firstVisIdx = dAxis < 0 ? dataLength - visible - 1 : 1;
api.start(function (i) {
return calculateInfiniteScrollApiLogic(i, axisDirection, _extends({
axis: axis,
dAxis: dAxis,
firstVis: firstVis,
firstVisIdx: firstVisIdx,
itemSize: itemSize,
dataLength: dataLength,
prevRef: prevRef
}, rest));
});
prevRef.current = [firstVis, firstVisIdx];
}
var runLimitedSwipe = function runLimitedSwipe(_ref4, _ref5) {
var api = _ref4[1];
var axisDirection = _ref5.axis,
indexOptions = _ref5.indexOptions,
itemSize = _ref5.itemSize;
return function (_ref6) {
var active = _ref6.active,
movement = _ref6.movement,
direction = _ref6.direction,
cancel = _ref6.cancel;
var axis = axisDirection === 'x' ? 0 : 1;
var _ref7 = [movement[axis], direction[axis]],
mAxis = _ref7[0],
gestDir = _ref7[1];
if (gestDir) {
var current = indexOptions.current,
lastIndx = indexOptions.last,
setIndex = indexOptions.setIndex;
var bounds = [current.current - 1 > 0 ? current.current - 1 : 0, current.current + 1 < lastIndx ? current.current + 1 : lastIndx];
if (active && Math.abs(mAxis) > itemSize / 10) {
var clampedIdx = clamp.apply(void 0, [current.current + -gestDir].concat(bounds));
current.current = clampedIdx;
cancel == null || cancel();
setIndex == null || setIndex(clampedIdx);
}
api.start(function (i) {
var _ref8;
if (i < current.current - 1 || i > current.current + 1) return {
display: 'none'
};
var axisPoint = (i - current.current) * itemSize + (active ? mAxis : 0);
return _ref8 = {}, _ref8[axisDirection] = axisPoint, _ref8.display = 'block', _ref8;
});
}
};
};
var runPinchZoom = function runPinchZoom(_ref9, _ref10) {
var springs = _ref9[0],
api = _ref9[1];
var ref = _ref10.ref;
return function (_ref11) {
var _ref11$args = _ref11.args,
index = _ref11$args[0],
_ref11$origin = _ref11.origin,
ox = _ref11$origin[0],
oy = _ref11$origin[1],
first = _ref11.first,
_ref11$movement = _ref11.movement,
ms = _ref11$movement[0],
_ref11$offset = _ref11.offset,
s = _ref11$offset[0],
memo = _ref11.memo;
if (first) {
memo = {};
var refSizes = ref == null ? void 0 : ref.getBoundingClientRect();
var tx = ox - (ox + ((refSizes == null ? void 0 : refSizes.width) || 0) / 2);
var ty = oy - (oy + ((refSizes == null ? void 0 : refSizes.height) || 0) / 2);
memo[index.toString()] = [springs[index].x.get(), springs[index].y.get(), tx, ty];
}
var x = memo[index.toString()][0] - (ms - 1) * memo[index.toString()][2];
var y = memo[index.toString()][1] - (ms - 1) * memo[index.toString()][3];
api.start(function () {
return {
scale: s,
x: x,
y: y
};
});
return memo;
};
};
var utils = {
wheel: {
infinite: runInfiniteScrollSprings
},
drag: {
infinite: runInfiniteScrollSprings,
limited: runLimitedSwipe
},
pinch: {
zoom: runPinchZoom
}
};
function useScrollZoneRefs(axisDirection, sizeOptions, auxOptions) {
if ((sizeOptions == null ? void 0 : sizeOptions.minSize) === 0) utils$1.devWarn('[ScrollRef] Setup warning! Size 0 (ZERO) minSize passed. This could cause layout issues! Check the options object passed to your useScroll animation hooks.');
var windowSizes = hooks.useWindowSize(auxOptions == null ? void 0 : auxOptions.windowSizeOptions);
var _useStateRef = hooks.useStateRef(null, function (node) {
return node;
}),
scrollingZoneTarget = _useStateRef[0],
setScrollingZoneRef = _useStateRef[1];
var isVertical = axisDirection === 'y';
var _useStateRef2 = hooks.useStateRef(0, function (node) {
return isVertical ? node == null ? void 0 : node.clientHeight : node == null ? void 0 : node.clientWidth;
}),
nodeSize = _useStateRef2[0],
setItemSizeRef = _useStateRef2[1];
var itemSize = (sizeOptions == null ? void 0 : sizeOptions.fixedSize) || (sizeOptions == null ? void 0 : sizeOptions.minSize) && Math.min(sizeOptions.minSize, nodeSize) || nodeSize;
react$1.useEffect(function () {
var handler = function handler(e) {
return e.preventDefault();
};
scrollingZoneTarget == null || scrollingZoneTarget.addEventListener('gesturestart', handler);
scrollingZoneTarget == null || scrollingZoneTarget.addEventListener('gesturechange', handler);
scrollingZoneTarget == null || scrollingZoneTarget.addEventListener('gestureend', handler);
return function () {
scrollingZoneTarget == null || scrollingZoneTarget.removeEventListener('gesturestart', handler);
scrollingZoneTarget == null || scrollingZoneTarget.removeEventListener('gesturechange', handler);
scrollingZoneTarget == null || scrollingZoneTarget.removeEventListener('gestureend', handler);
};
}, [scrollingZoneTarget]);
react$1.useEffect(function () {
if (!(sizeOptions != null && sizeOptions.fixedSize) && (isVertical ? scrollingZoneTarget == null ? void 0 : scrollingZoneTarget.clientHeight : scrollingZoneTarget == null ? void 0 : scrollingZoneTarget.clientWidth)) {
setItemSizeRef(scrollingZoneTarget);
}
}, [sizeOptions, setItemSizeRef, windowSizes, scrollingZoneTarget, axisDirection, isVertical]);
return {
refs: {
scrollingZoneTarget: scrollingZoneTarget,
itemSize: itemSize
},
refCallbacks: {
setScrollingZoneRef: setScrollingZoneRef,
setItemSizeRef: setItemSizeRef
}
};
}
function useInfiniteScrollSetup(axisDirection, options, auxOptions) {
var _useState = react$1.useState(false),
firstAnimationOver = _useState[0],
setFirstPaintOver = _useState[1];
var prevRef = react$1.useRef([0, 1]);
var _useState2 = react$1.useState(prevRef.current[0]),
currentIndex = _useState2[0],
setCurrentIndex = _useState2[1];
var _useScrollZoneRefs = useScrollZoneRefs(axisDirection, options.sizeOptions, auxOptions),
_useScrollZoneRefs$re = _useScrollZoneRefs.refs,
itemSize = _useScrollZoneRefs$re.itemSize,
scrollingZoneTarget = _useScrollZoneRefs$re.scrollingZoneTarget,
_useScrollZoneRefs$re2 = _useScrollZoneRefs.refCallbacks,
setItemSizeRef = _useScrollZoneRefs$re2.setItemSizeRef,
setScrollingZoneRef = _useScrollZoneRefs$re2.setScrollingZoneRef;
var gestureParams = react$1.useMemo(function () {
return _extends({}, options, {
prevRef: prevRef,
itemSize: itemSize,
setCurrentIndex: setCurrentIndex
});
}, [itemSize, options]);
return {
gestureParams: gestureParams,
currentIndex: currentIndex,
firstAnimationOver: firstAnimationOver,
scrollingZoneTarget: scrollingZoneTarget,
callbacks: {
setFirstPaintOver: setFirstPaintOver,
setScrollingZoneRef: setScrollingZoneRef,
setItemSizeRef: setItemSizeRef
}
};
}
var _excluded$1 = ["setFirstPaintOver"];
var DRAG_SPEED_COEFFICIENT = 0.5;
function useInfiniteHorizontalScroll(items, options) {
var _useInfiniteScrollSet = useInfiniteScrollSetup('x', options),
gestureParams = _useInfiniteScrollSet.gestureParams,
currentIndex = _useInfiniteScrollSet.currentIndex,
firstAnimationOver = _useInfiniteScrollSet.firstAnimationOver,
scrollingZoneTarget = _useInfiniteScrollSet.scrollingZoneTarget,
_useInfiniteScrollSet2 = _useInfiniteScrollSet.callbacks,
setFirstPaintOver = _useInfiniteScrollSet2.setFirstPaintOver,
restCbs = _objectWithoutPropertiesLoose(_useInfiniteScrollSet2, _excluded$1);
var _useSprings = web.useSprings(items.length, function (i) {
return _extends({}, options == null ? void 0 : options.styleMixin, {
x: (i < items.length - 1 ? i : -1) * gestureParams.itemSize,
onRest: function onRest() {
if (!firstAnimationOver) {
return setFirstPaintOver(true);
}
},
from: {
x: i * gestureParams.itemSize
},
config: {
tension: 260,
friction: 50
}
});
}),
springs = _useSprings[0],
api = _useSprings[1];
var bind = react.useDrag(function (_ref) {
var active = _ref.active,
dragging = _ref.dragging,
_ref$offset = _ref.offset,
x = _ref$offset[0],
_ref$direction = _ref.direction,
dx = _ref$direction[0];
runInfiniteScrollSprings(api, 'x', _extends({}, gestureParams, {
dataLength: items.length,
axis: -x / (options.scrollSpeed || DRAG_SPEED_COEFFICIENT),
dAxis: -dx,
active: !!(active || dragging),
config: options.config
}));
}, {
eventOptions: {
passive: true,
capture: false,
once: true
},
preventDefault: true,
axis: 'x',
filterTaps: true
});
return {
target: scrollingZoneTarget,
bind: bind,
springs: springs,
state: {
currentIndex: currentIndex,
itemSize: gestureParams.itemSize,
firstAnimationOver: firstAnimationOver
},
refCallbacks: restCbs
};
}
var STIFF_SPRINGS = {
tension: 260,
friction: 50
};
var _excluded$2 = ["setFirstPaintOver"];
var CONFIG = {
SCROLL_SPEED_COEFFICIENT: 3.2,
DRAG_SPEED_COEFFICIENT: 0.5
};
function useInfiniteVerticalScroll(items, options) {
var isMobile = hooks.useIsMobile();
var _useInfiniteScrollSet = useInfiniteScrollSetup('y', options),
gestureParams = _useInfiniteScrollSet.gestureParams,
currentIndex = _useInfiniteScrollSet.currentIndex,
firstAnimationOver = _useInfiniteScrollSet.firstAnimationOver,
_useInfiniteScrollSet2 = _useInfiniteScrollSet.callbacks,
setFirstPaintOver = _useInfiniteScrollSet2.setFirstPaintOver,
restCbs = _objectWithoutPropertiesLoose(_useInfiniteScrollSet2, _excluded$2);
var lastIndex = items.length - 1;
var getDefaultPosition = react$1.useCallback(function (i) {
return _extends({}, options == null ? void 0 : options.styleMixin, {
scale: options.scaleOptions.initialScale || 0.92,
y: (i < lastIndex ? i : -1) * gestureParams.itemSize,
onRest: function onRest() {
if (!firstAnimationOver) {
setFirstPaintOver(true);
}
},
config: STIFF_SPRINGS
});
}, [lastIndex, gestureParams.itemSize, firstAnimationOver]);
var gestureApi = web.useSprings(items.length, getDefaultPosition, [gestureParams.itemSize]);
var wheelOffset = react$1.useRef(0);
var dragOffset = react$1.useRef(0);
var dragIndexRef = react$1.useRef(0);
var bind = react.useGesture({
onDrag: utils.drag.limited(gestureApi, {
axis: 'y',
itemSize: gestureParams.itemSize,
indexOptions: {
current: dragIndexRef,
setIndex: undefined,
last: lastIndex
}
}),
onWheel: function onWheel(_ref) {
var active = _ref.active,
last = _ref.last,
_ref$offset = _ref.offset,
y = _ref$offset[1],
_ref$movement = _ref.movement,
my = _ref$movement[1],
_ref$direction = _ref.direction,
dy = _ref$direction[1];
if (dy) {
wheelOffset.current = y;
var computedY = dragOffset.current + y / CONFIG.SCROLL_SPEED_COEFFICIENT;
runInfiniteScrollSprings(gestureApi[1], 'y', _extends({}, gestureParams, {
dataLength: items.length,
active: active,
axis: computedY,
dAxis: dy,
mAxis: my,
last: last
}));
}
}
}, {
drag: {
axis: 'y',
preventScrollAxis: 'x'
}
});
return {
bind: bind,
springs: gestureApi[0],
state: {
currentIndex: isMobile ? 0 : currentIndex,
itemSize: gestureParams.itemSize,
firstAnimationOver: firstAnimationOver
},
refCallbacks: restCbs
};
}
function useLimitedSwipe(axis, data, options, auxOptions) {
var _useScrollZoneRefs = useScrollZoneRefs(axis, options == null ? void 0 : options.sizeOptions, auxOptions),
itemSize = _useScrollZoneRefs.refs.itemSize,
refCallbacks = _useScrollZoneRefs.refCallbacks;
var indexRef = react$1.useRef(0);
var _useState = react$1.useState(indexRef.current),
indexState = _useState[0],
setIndexState = _useState[1];
var _useSprings = web.useSprings(data.length, function (i) {
var _extends2;
return _extends({}, options == null ? void 0 : options.styleMixin, (_extends2 = {}, _extends2[axis] = i * itemSize, _extends2.display = 'block', _extends2));
}, [itemSize]),
springs = _useSprings[0],
api = _useSprings[1];
var bind = react.useDrag(utils.drag.limited([, api], {
axis: axis,
indexOptions: {
current: indexRef,
setIndex: setIndexState,
last: data.length - 1
},
itemSize: itemSize
}), {
axis: axis
});
return {
bind: bind,
springs: springs,
state: {
currentIndex: indexState,
itemSize: itemSize
},
refCallbacks: refCallbacks
};
}
function useLimitedHorizontalSwipe(data, options, auxOptions) {
return useLimitedSwipe('x', data, options, auxOptions);
}
function useLimitedVerticalSwipe(data, options, auxOptions) {
return useLimitedSwipe('y', data, options, auxOptions);
}
function usePinchZoomAndDrag(data, options, auxOptions) {
var _useScrollZoneRefs = useScrollZoneRefs('x', options == null ? void 0 : options.sizeOptions, auxOptions),
_useScrollZoneRefs$re = _useScrollZoneRefs.refs,
itemSize = _useScrollZoneRefs$re.itemSize,
ref = _useScrollZoneRefs$re.scrollingZoneTarget,
refCallbacks = _useScrollZoneRefs.refCallbacks;
var _useSprings = web.useSprings(data.length, function (i) {
return _extends({}, options == null ? void 0 : options.styleMixin, {
x: i * itemSize,
y: 0,
scale: 1,
display: 'block'
});
}, [data.length]),
springs = _useSprings[0],
api = _useSprings[1];
var bind = react.useGesture({
onDrag: function onDrag(_ref) {
var pinching = _ref.pinching,
cancel = _ref.cancel,
_ref$offset = _ref.offset,
x = _ref$offset[0],
y = _ref$offset[1];
if (pinching) return cancel();
api.start({
x: x,
y: y
});
},
onPinch: utils.pinch.zoom([springs, api], {
ref: ref
})
}, {
drag: {
from: function from() {
return [springs[0].x.get(), springs[0].y.get()];
},
bound: ref == null ? void 0 : ref.getBoundingClientRect()
},
pinch: {
scaleBounds: {
min: 0.8,
max: 3
},
rubberband: true
}
});
return {
bind: bind,
springs: springs,
state: {
currentIndex: 0,
itemSize: itemSize
},
refCallbacks: refCallbacks
};
}
exports.useInfiniteHorizontalScroll = useInfiniteHorizontalScroll;
exports.useInfiniteVerticalScroll = useInfiniteVerticalScroll;
exports.useLimitedHorizontalSwipe = useLimitedHorizontalSwipe;
exports.useLimitedVerticalSwipe = useLimitedVerticalSwipe;
exports.usePinchZoomAndDrag = usePinchZoomAndDrag;
exports.useScrollZoneRef = useScrollZoneRefs;
exports.useScrollingAnimationSetup = useInfiniteScrollSetup;
//# sourceMappingURL=carousel-hooks.cjs.development.js.map