UNPKG

@react-slip-and-slide/native

Version:
676 lines (664 loc) 19.3 kB
'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;