@react-slip-and-slide/native
Version:
react-slip-and-slide native
676 lines (664 loc) • 19.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var index_native = require('@react-slip-and-slide/utils/dist/index.native');
var native = require('@react-spring/native');
var lodash = require('lodash');
var React = require('react');
var reactNative = require('react-native');
var reactNativeGestureHandler = require('react-native-gesture-handler');
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefault(React);
function _extends() {
_extends = Object.assign ? Object.assign.bind() : function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function ReactSlipAndSlideComponent({
data,
snap,
centered,
infinite: _infinite,
containerWidth,
overflowHidden = true,
itemHeight,
itemWidth = 0,
pressToSlide,
interpolators,
animateStartup = true,
rubberbandElasticity = 4,
visibleItems = 0,
renderItem,
onChange,
onEdges,
onReady
}, ref) {
const mode = itemWidth && itemHeight ? "fixed" : "dynamic";
const infinite = mode === "fixed" && !!_infinite;
const eagerLoading = mode === "dynamic" || visibleItems === 0;
const shouldAnimatedStartup = animateStartup && eagerLoading;
const index = React__default["default"].useRef(0);
const [_, reRender] = React__default["default"].useState(0);
const {
width: screenWidth
} = index_native.useScreenDimensions();
const lastOffset = React__default["default"].useRef(0);
const [container, setContainerDimensions] = React__default["default"].useState({
width: containerWidth || screenWidth || 0,
height: itemHeight || 0
});
const [_wrapperWidth, _setWrapperWidth] = React__default["default"].useState(0);
const containerRef = React__default["default"].useRef(null);
const isIntentionalDrag = React__default["default"].useRef(false);
const direction = React__default["default"].useRef("center");
const lastValidDirection = React__default["default"].useRef(null);
const isDragging = React__default["default"].useRef(false);
const OffsetX = React__default["default"].useMemo(() => {
return new native.SpringValue(0, {
config: {
tension: 260,
friction: 32,
mass: 1
}
});
}, []);
const Opacity = React__default["default"].useMemo(() => {
const initialOpacity = shouldAnimatedStartup ? 0 : 1;
return new native.SpringValue(initialOpacity, {
config: {
tension: 260,
friction: 32,
mass: 1
}
});
}, [shouldAnimatedStartup]);
const {
itemRefs,
itemDimensionMap
} = index_native.useDynamicDimension({
mode,
dataLength: data.length,
onMeasure: ({
itemWidthSum
}) => {
if (itemWidthSum) {
_setWrapperWidth(itemWidthSum);
}
}
});
const {
ranges
} = index_native.useItemsRange({
mode,
itemDimensionMap,
offsetX: OffsetX.get()
});
React__default["default"].useEffect(() => {
if (containerRef.current && (!containerWidth || !itemHeight)) {
setTimeout(() => {
var _containerRef$current;
(_containerRef$current = containerRef.current) == null ? void 0 : _containerRef$current.measure((_, __, width, height) => {
setContainerDimensions({
width,
height
});
});
}, 200);
} else {
setContainerDimensions(prev => _extends({}, prev, {
width: screenWidth
}));
}
}, [containerWidth, containerRef, itemHeight, screenWidth]);
const processClampOffsets = React__default["default"].useCallback(({
wrapperWidth,
sideMargins
}) => {
const MIN = 0;
let MAX = -wrapperWidth + container.width;
if (centered) {
const _MAX_CENTERED = MAX - sideMargins * 2;
MAX = _MAX_CENTERED;
} else {
if (wrapperWidth < container.width) {
MAX = MIN;
}
}
return {
MIN,
MAX
};
}, [centered, container.width]);
const {
dataLength,
wrapperWidth,
clampOffset
} = React__default["default"].useMemo(() => {
const wrapperWidth = mode === "fixed" ? data.length * itemWidth : _wrapperWidth;
const sideMargins = (container.width - itemWidth) / 2;
const {
MIN,
MAX
} = processClampOffsets({
wrapperWidth,
sideMargins
});
return {
dataLength: data.length,
wrapperWidth,
sideMargins,
halfItem: itemWidth / 2,
clampOffset: {
MIN,
MAX
}
};
}, [_wrapperWidth, container.width, data.length, itemWidth, mode, processClampOffsets]);
const clampReleaseOffset = React__default["default"].useCallback(offset => {
if (infinite && mode === "fixed") {
return offset;
}
if (offset > clampOffset.MIN) {
return clampOffset.MIN;
} else if (offset < clampOffset.MAX) {
return clampOffset.MAX;
}
return offset;
}, [clampOffset.MAX, clampOffset.MIN, infinite, mode]);
const clampIndex = React__default["default"].useCallback(index => lodash.clamp(index, 0, dataLength - 1), [dataLength]);
const processIndex = React__default["default"].useCallback(({
offset
}) => {
const modIndex = offset / itemWidth % dataLength;
return offset <= 0 ? Math.abs(modIndex) : Math.abs(modIndex > 0 ? dataLength - modIndex : 0);
}, [dataLength, itemWidth]);
const getCurrentIndex = React__default["default"].useCallback(({
offset
}) => {
if (infinite) {
return -Math.round(offset / itemWidth);
}
return Math.round(processIndex({
offset
}));
}, [infinite, itemWidth, processIndex]);
const getRelativeIndex = React__default["default"].useCallback(({
offset
}) => {
return Math.floor(processIndex({
offset
}));
}, [processIndex]);
const getCurrentOffset = React__default["default"].useCallback(({
index
}) => {
const finalOffset = -index * itemWidth;
return finalOffset;
}, [itemWidth]);
const checkEdges = React__default["default"].useCallback(({
offset
}) => {
if (offset >= clampOffset.MIN) {
return {
start: true,
end: false
};
} else if (offset <= clampOffset.MAX) {
return {
start: false,
end: true
};
} else {
return {
start: false,
end: false
};
}
}, [clampOffset.MAX, clampOffset.MIN]);
const springIt = React__default["default"].useCallback(({
offset,
immediate,
actionType,
onRest
}) => {
const clampedReleaseOffset = clampReleaseOffset(offset);
OffsetX.start({
to: actionType === "drag" || actionType === "correction" ? offset : clampedReleaseOffset,
immediate: immediate || actionType === "drag",
onRest: x => {
onRest == null ? void 0 : onRest(x);
if (actionType === "release") {
if (mode === "fixed") {
index.current = clampIndex(getRelativeIndex({
offset: clampedReleaseOffset
}));
} else {
index.current = index_native.getCurrentDynamicIndex(offset, ranges);
}
if (!eagerLoading) {
reRender(index.current);
}
onChange == null ? void 0 : onChange(index.current);
}
}
});
if (actionType === "release") {
lastOffset.current = clampedReleaseOffset;
if (!infinite) {
onEdges == null ? void 0 : onEdges(checkEdges({
offset
}));
}
}
}, [OffsetX, checkEdges, clampIndex, clampReleaseOffset, eagerLoading, getRelativeIndex, infinite, mode, onChange, onEdges, ranges]);
const getCurrentIndexByOffset = React__default["default"].useCallback(offset => {
let finalIndex = 0;
const neutralIndex = offset / wrapperWidth * dataLength;
const left = Math.ceil(neutralIndex);
const right = Math.floor(neutralIndex);
if (!snap) {
return right;
}
switch (direction.current) {
case "left":
finalIndex = left;
break;
case "right":
finalIndex = right;
break;
default:
if (lastValidDirection.current === "left") {
finalIndex = left;
} else if (lastValidDirection.current === "right") {
finalIndex = right;
}
break;
}
return finalIndex;
}, [dataLength, snap, wrapperWidth]);
const drag = React__default["default"].useCallback(x => {
const offset = infinite ? x : index_native.rubberband(x, rubberbandElasticity, [clampOffset.MIN, clampOffset.MAX]);
springIt({
offset,
actionType: "drag"
});
}, [clampOffset, infinite, rubberbandElasticity, springIt]);
const withSnap = React__default["default"].useCallback(({
offset
}) => {
if (mode === "fixed") {
const page = getCurrentIndexByOffset(-offset);
const finalOffset = -page * itemWidth;
return finalOffset;
} else {
const edges = checkEdges({
offset
});
return index_native.getNextDynamicOffset({
offsetX: edges.start ? clampOffset.MIN : offset,
ranges,
dir: lastValidDirection.current,
centered: !!centered
});
}
}, [centered, checkEdges, clampOffset.MIN, getCurrentIndexByOffset, itemWidth, mode, ranges]);
const withMomentum = React__default["default"].useCallback(({
offset,
v
}) => {
const momentumOffset = offset + v;
return momentumOffset;
}, []);
const release = React__default["default"].useCallback(({
offset,
v
}) => {
let offsetX = 0;
if (snap) {
if (isIntentionalDrag.current) {
offsetX = withSnap({
offset
});
} else {
springIt({
offset: lastOffset.current,
actionType: "correction"
});
return;
}
} else {
offsetX = withMomentum({
offset,
v
});
}
springIt({
offset: offsetX,
actionType: "release"
});
}, [snap, springIt, withMomentum, withSnap]);
const navigate = React__default["default"].useCallback(({
index: _index,
direction,
immediate
}) => {
let targetOffset = 0;
if (_index) {
targetOffset = getCurrentOffset({
index: _index
});
} else {
if (mode === "fixed") {
const page = getCurrentIndex({
offset: OffsetX.get()
});
if (direction === "next") {
const nextPage = page + 1;
targetOffset = -nextPage * itemWidth;
} else if (direction === "prev") {
const prevPage = page - 1;
targetOffset = -prevPage * itemWidth;
}
} else {
targetOffset = index_native.getNextDynamicOffset({
offsetX: OffsetX.get(),
ranges,
dir: direction === "next" ? "left" : direction === "prev" ? "right" : null,
centered: !!centered
});
}
}
springIt({
offset: targetOffset,
immediate,
actionType: "release"
});
}, [OffsetX, centered, getCurrentIndex, getCurrentOffset, itemWidth, mode, ranges, springIt]);
const move = React__default["default"].useCallback(offset => {
springIt({
offset: OffsetX.get() + offset,
actionType: "release"
});
}, [OffsetX, springIt]);
const panGesture = reactNativeGestureHandler.Gesture.Pan().onUpdate(({
translationX,
velocityX,
state
}) => {
const dir = velocityX > 0 ? "right" : velocityX < 0 ? "left" : "center";
direction.current = dir;
if (dir !== "center") {
lastValidDirection.current = dir;
}
isIntentionalDrag.current = Math.abs(translationX) >= 40;
isDragging.current = state === 4;
const offset = lastOffset.current + translationX;
drag(offset);
}).onEnd(({
velocityX,
translationX,
state
}) => {
isDragging.current = state === 4;
const offset = lastOffset.current + translationX;
release({
offset,
v: velocityX / 12
});
});
const handlePressToSlide = React__default["default"].useCallback(idx => {
if (!pressToSlide || isDragging.current || isIntentionalDrag.current) {
return;
}
if (mode === "fixed") {
const prev = index.current === 0 && idx === dataLength - 1;
const next = index.current === dataLength - 1 && idx === 0;
const smaller = idx < index.current;
const bigger = idx > index.current;
if (prev) {
navigate({
direction: "prev"
});
} else if (next) {
navigate({
direction: "next"
});
} else if (smaller) {
navigate({
direction: "prev"
});
} else if (bigger) {
navigate({
direction: "next"
});
}
} else {
const currIndx = index_native.getCurrentDynamicIndex(OffsetX.get(), ranges);
if (idx < currIndx) {
navigate({
direction: "prev"
});
} else if (idx > currIndx) {
navigate({
direction: "next"
});
}
}
}, [OffsetX, dataLength, index, mode, navigate, pressToSlide, ranges]);
React__default["default"].useEffect(() => {
if (shouldAnimatedStartup) {
if (mode === "dynamic") {
if (ranges.length && container.height) {
Opacity.start({
to: 1,
onRest: () => {
onReady == null ? void 0 : onReady(true);
}
});
}
} else {
Opacity.start({
to: 1,
delay: 100,
onRest: () => {
onReady == null ? void 0 : onReady(true);
}
});
}
} else {
onReady == null ? void 0 : onReady(true);
}
}, [Opacity, container.height, ranges.length, shouldAnimatedStartup]);
React__default["default"].useEffect(() => {
if (mode === "dynamic" && centered) {
var _ranges$;
const alignment = centered ? "center" : "start";
springIt({
offset: -(((_ranges$ = ranges[0]) == null ? void 0 : _ranges$.range[alignment]) || 0),
actionType: "release",
immediate: true
});
}
}, [centered, mode, ranges, springIt]);
React__default["default"].useEffect(() => {
const {
end
} = checkEdges({
offset: OffsetX.get()
});
if (end) {
springIt({
offset: clampOffset.MAX,
actionType: "release"
});
}
}, [clampOffset.MAX]);
React__default["default"].useEffect(() => {
if (!infinite) {
navigate({
index: 0,
immediate: true
});
}
}, [infinite]);
React__default["default"].useImperativeHandle(ref, () => ({
next: () => navigate({
direction: "next"
}),
previous: () => navigate({
direction: "prev"
}),
goTo: ({
index,
animated
}) => navigate({
index,
immediate: !animated
}),
move
}), [move, navigate]);
const shouldRender = React__default["default"].useCallback(i => {
if (eagerLoading) {
return true;
}
return index_native.isInRange(i, {
dataLength,
viewSize: itemWidth,
visibleItems: visibleItems || Math.round(dataLength / 2),
offsetX: OffsetX.get()
});
}, [OffsetX, dataLength, eagerLoading, itemWidth, visibleItems]);
return React__default["default"].createElement(reactNativeGestureHandler.GestureDetector, {
gesture: panGesture
}, React__default["default"].createElement(index_native.Styled.Wrapper, {
ref: containerRef,
style: {
opacity: Opacity,
justifyContent: centered ? "center" : "flex-start",
width: containerWidth || screenWidth,
height: itemHeight || container.height || "100%",
overflow: overflowHidden ? "hidden" : undefined
}
}, data.map((props, i) => {
var _ranges$i, _ranges$i2;
return React__default["default"].createElement(index_native.LazyLoad, {
key: i,
render: shouldRender(i)
}, React__default["default"].createElement(Item, {
ref: itemRefs[i],
index: i,
mode: mode,
item: props,
dataLength: dataLength,
renderItem: renderItem,
infinite: infinite,
itemHeight: itemHeight,
itemWidth: mode === "fixed" ? itemWidth : ((_ranges$i = ranges[i]) == null ? void 0 : _ranges$i.width) || 0,
interpolators: interpolators || {},
dynamicOffset: ((_ranges$i2 = ranges[i]) == null ? void 0 : _ranges$i2.range[centered ? "center" : "start"]) || 0,
onPress: () => pressToSlide && handlePressToSlide(i),
offsetX: OffsetX.to(offsetX => infinite ? offsetX % wrapperWidth : offsetX),
isLazy: !eagerLoading
}));
})));
}
function ItemComponent({
offsetX,
dataLength,
index,
infinite,
itemWidth,
itemHeight,
item,
interpolators,
dynamicOffset,
mode,
isLazy,
renderItem,
onPress
}, ref) {
const Opacity = React__default["default"].useMemo(() => {
return new native.SpringValue(isLazy ? 0 : 1, {
config: {
tension: 260,
friction: 32,
mass: 1
}
});
}, [isLazy]);
const x = index_native.displacement({
offsetX,
dataLength,
index,
itemWidth,
infinite
});
const keys = Object.entries(interpolators);
const translateX = React__default["default"].useMemo(() => {
if (mode === "fixed") {
return x.to(val => val / itemWidth).to([-1, 0, 1], [-itemWidth, 0, itemWidth]);
}
return native.to(offsetX, x => x + dynamicOffset);
}, [dynamicOffset, itemWidth, mode, offsetX, x]);
const {
scale,
opacity
} = React__default["default"].useMemo(() => {
if (itemWidth) {
return keys.reduce((acc, [key, val]) => {
acc[key] = translateX.to(val => val / itemWidth).to([-1, 0, 1], [val, 1, val], "clamp");
return acc;
}, {});
}
return {
scale: 1,
opacity: 1
};
}, [itemWidth, keys, translateX]);
React__default["default"].useEffect(() => {
if (isLazy) {
Opacity.start({
to: 1
});
}
}, [isLazy]);
const memoRenderItem = React__default["default"].useMemo(() => {
return renderItem({
item,
index
});
}, [index, item, renderItem]);
return React__default["default"].createElement(reactNative.TouchableWithoutFeedback, {
onPress: onPress
}, React__default["default"].createElement(index_native.Styled.Item, {
ref: ref,
style: {
transform: [{
translateX
}, {
scale: scale || 1
}],
opacity,
width: itemWidth === 0 ? undefined : itemWidth,
height: itemHeight
}
}, React__default["default"].createElement(index_native.AnimatedBox, {
style: {
width: "100%",
height: "100%",
opacity: Opacity
}
}, memoRenderItem)));
}
const Item = React__default["default"].forwardRef(ItemComponent);
const ForwardReactSlipAndSlideRef = React__default["default"].forwardRef(ReactSlipAndSlideComponent);
const ReactSlipAndSlide = index_native.typedMemo(ForwardReactSlipAndSlideRef);
exports.ReactSlipAndSlide = ReactSlipAndSlide;