UNPKG

react-native-zoom-toolkit

Version:

Smoothly zoom any image, video or component you want!

343 lines (338 loc) 14.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireWildcard(require("react")); var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated")); var _reactNativeGestureHandler = require("react-native-gesture-handler"); var _clamp = require("../../commons/utils/clamp"); var _pinchTransform = require("../../commons/utils/pinchTransform"); var _useVector = require("../../commons/hooks/useVector"); var _snapPoint = require("../../commons/utils/snapPoint"); var _crop = require("../../commons/utils/crop"); var _usePinchCommons = require("../../commons/hooks/usePinchCommons"); var _getSwipeDirection = require("../../commons/utils/getSwipeDirection"); var _context = require("./context"); var _types = require("../../commons/types"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } const minScale = 1; const config = { duration: 300, easing: _reactNativeReanimated.Easing.linear }; /* * Pinchable views are really heavy components, therefore in order to maximize performance * only a single pinchable view is shared among all the list items, items listen to this * component updates and only update themselves if they are the current item. */ const Reflection = ({ length, maxScale, itemSize, vertical, tapOnEdgeToItem, allowPinchPanning, pinchCenteringMode, onTap, onPanStart, onPanEnd, onPinchStart: onUserPinchStart, onPinchEnd: onUserPinchEnd, onSwipe: onUserSwipe, onVerticalPull }) => { const { activeIndex, fetchIndex, scroll, scrollOffset, isScrolling, rootSize, rootChildSize, translate, scale } = (0, _react.useContext)(_context.GalleryContext); const offset = (0, _useVector.useVector)(0, 0); const origin = (0, _useVector.useVector)(0, 0); const delta = (0, _useVector.useVector)(0, 0); const scaleOffset = (0, _reactNativeReanimated.useSharedValue)(1); const detectorTranslate = (0, _useVector.useVector)(0, 0); const detectorScale = (0, _reactNativeReanimated.useSharedValue)(1); const time = (0, _reactNativeReanimated.useSharedValue)(0); const position = (0, _useVector.useVector)(0, 0); const isPullingVertical = (0, _reactNativeReanimated.useSharedValue)(false); const pullReleased = (0, _reactNativeReanimated.useSharedValue)(false); const boundsFn = scaleValue => { 'worklet'; const { width: cWidth, height: cHeight } = rootChildSize; const { width: rWidth, height: rHeight } = rootSize; const boundX = Math.max(0, cWidth.value * scaleValue - rWidth.value) / 2; const boundY = Math.max(0, cHeight.value * scaleValue - rHeight.value) / 2; return { x: boundX, y: boundY }; }; const reset = (toX, toY, toScale, animate = true) => { 'worklet'; detectorTranslate.x.value = translate.x.value; detectorTranslate.y.value = translate.y.value; detectorScale.value = scale.value; translate.x.value = animate ? (0, _reactNativeReanimated.withTiming)(toX) : toX; translate.y.value = animate ? (0, _reactNativeReanimated.withTiming)(toY) : toY; scale.value = animate ? (0, _reactNativeReanimated.withTiming)(toScale) : toScale; detectorTranslate.x.value = animate ? (0, _reactNativeReanimated.withTiming)(toX) : toX; detectorTranslate.y.value = animate ? (0, _reactNativeReanimated.withTiming)(toY) : toY; detectorScale.value = animate ? (0, _reactNativeReanimated.withTiming)(toScale) : toScale; }; const snapToScrollPosition = e => { 'worklet'; const index = activeIndex.value; const prev = itemSize.value * (0, _clamp.clamp)(index - 1, 0, length - 1); const current = itemSize.value * index; const next = itemSize.value * (0, _clamp.clamp)(index + 1, 0, length - 1); const velocity = vertical ? e.velocityY : e.velocityX; const toScroll = (0, _snapPoint.snapPoint)(scroll.value, velocity, [prev, current, next]); if (toScroll !== current) fetchIndex.value = index + (toScroll === next ? 1 : -1); scroll.value = (0, _reactNativeReanimated.withTiming)(toScroll, config, () => { activeIndex.value = fetchIndex.value; isScrolling.value = false; toScroll !== current && reset(0, 0, minScale, false); }); }; const onSwipe = direction => { 'worklet'; let toIndex = activeIndex.value; if (direction === _types.SwipeDirection.UP && vertical) toIndex += 1; if (direction === _types.SwipeDirection.DOWN && vertical) toIndex -= 1; if (direction === _types.SwipeDirection.LEFT && !vertical) toIndex += 1; if (direction === _types.SwipeDirection.RIGHT && !vertical) toIndex -= 1; toIndex = (0, _clamp.clamp)(toIndex, 0, length - 1); if (toIndex === activeIndex.value) return; fetchIndex.value = toIndex; scroll.value = (0, _reactNativeReanimated.withTiming)(toIndex * itemSize.value, config, () => { activeIndex.value = toIndex; isScrolling.value = false; reset(0, 0, minScale, false); }); }; (0, _reactNativeReanimated.useAnimatedReaction)(() => ({ translate: translate.y.value, scale: scale.value, isPulling: isPullingVertical.value, released: pullReleased.value }), val => { const shouldPull = !vertical && val.scale === 1 && val.isPulling; shouldPull && (onVerticalPull === null || onVerticalPull === void 0 ? void 0 : onVerticalPull(val.translate, val.released)); }, [translate, scale, isPullingVertical, pullReleased]); (0, _reactNativeReanimated.useAnimatedReaction)(() => ({ width: rootSize.width.value, height: rootSize.height.value }), () => reset(0, 0, minScale, false), [rootSize]); const { gesturesEnabled, onPinchStart, onPinchUpdate, onPinchEnd } = (0, _usePinchCommons.usePinchCommons)({ container: rootSize, detectorTranslate, detectorScale, translate, offset, origin, scale, scaleOffset, minScale, maxScale, delta, allowPinchPanning, scaleMode: _types.ScaleMode.BOUNCE, pinchCenteringMode, boundFn: boundsFn, userCallbacks: { onPinchStart: onUserPinchStart, onPinchEnd: onUserPinchEnd } }); const pinch = _reactNativeGestureHandler.Gesture.Pinch().onStart(onPinchStart).onUpdate(onPinchUpdate).onEnd(onPinchEnd); const pan = _reactNativeGestureHandler.Gesture.Pan().maxPointers(1).enabled(gesturesEnabled).onStart(e => { onPanStart && (0, _reactNativeReanimated.runOnJS)(onPanStart)(e); (0, _reactNativeReanimated.cancelAnimation)(translate.x); (0, _reactNativeReanimated.cancelAnimation)(translate.y); (0, _reactNativeReanimated.cancelAnimation)(detectorTranslate.x); (0, _reactNativeReanimated.cancelAnimation)(detectorTranslate.y); const isVerticalPan = Math.abs(e.velocityY) > Math.abs(e.velocityX); isPullingVertical.value = isVerticalPan && scale.value === 1 && !vertical; isScrolling.value = true; time.value = performance.now(); position.x.value = e.absoluteX; position.y.value = e.absoluteY; scrollOffset.value = scroll.value; offset.x.value = translate.x.value; offset.y.value = translate.y.value; }).onUpdate(e => { if (isPullingVertical.value) { translate.y.value = e.translationY; return; } const toX = offset.x.value + e.translationX; const toY = offset.y.value + e.translationY; const { x: boundX, y: boundY } = boundsFn(scale.value); const exceedX = Math.max(0, Math.abs(toX) - boundX); const exceedY = Math.max(0, Math.abs(toY) - boundY); const scrollX = -1 * Math.sign(toX) * exceedX; const scrollY = -1 * Math.sign(toY) * exceedY; scroll.value = (0, _clamp.clamp)(scrollOffset.value + (vertical ? scrollY : scrollX), 0, (length - 1) * itemSize.value); translate.x.value = (0, _clamp.clamp)(toX, -1 * boundX, boundX); translate.y.value = (0, _clamp.clamp)(toY, -1 * boundY, boundY); detectorTranslate.x.value = (0, _clamp.clamp)(toX, -1 * boundX, boundX); detectorTranslate.y.value = (0, _clamp.clamp)(toY, -1 * boundY, boundY); }).onEnd(e => { const bounds = boundsFn(scale.value); const direction = (0, _getSwipeDirection.getSwipeDirection)(e, { boundaries: bounds, time: time.value, position: { x: position.x.value, y: position.y.value }, translate: { x: isPullingVertical.value ? 100 : translate.x.value, y: isPullingVertical.value ? 0 : translate.y.value } }); direction !== undefined && onSwipe(direction); direction !== undefined && onUserSwipe && (0, _reactNativeReanimated.runOnJS)(onUserSwipe)(direction); if (isPullingVertical.value) { pullReleased.value = true; translate.y.value = (0, _reactNativeReanimated.withTiming)(0, undefined, finished => { isPullingVertical.value = !finished; pullReleased.value = !finished; }); return; } const isSwipingH = direction === _types.SwipeDirection.LEFT || direction === _types.SwipeDirection.RIGHT; const isSwipingV = direction === _types.SwipeDirection.UP || direction === _types.SwipeDirection.DOWN; const snapV = vertical && (direction === undefined || isSwipingH); const snapH = !vertical && (direction === undefined || isSwipingV); onPanEnd && (0, _reactNativeReanimated.runOnJS)(onPanEnd)(e); (snapV || snapH) && snapToScrollPosition(e); const configX = { velocity: e.velocityX, clamp: [-bounds.x, bounds.x] }; const configY = { velocity: e.velocityY, clamp: [-bounds.y, bounds.y] }; translate.x.value = (0, _reactNativeReanimated.withDecay)(configX); translate.y.value = (0, _reactNativeReanimated.withDecay)(configY); detectorTranslate.x.value = (0, _reactNativeReanimated.withDecay)(configX); detectorTranslate.y.value = (0, _reactNativeReanimated.withDecay)(configY); }); const tap = _reactNativeGestureHandler.Gesture.Tap().enabled(gesturesEnabled).numberOfTaps(1).maxDuration(250).onEnd(e => { const gallerySize = { width: rootSize.width.value, height: rootSize.height.value }; const { crop: result } = (0, _crop.crop)({ scale: scale.value, context: { flipHorizontal: false, flipVertical: false, rotationAngle: 0 }, canvas: gallerySize, cropSize: gallerySize, resolution: gallerySize, position: { x: translate.x.value, y: translate.y.value } }); const tapEdge = 44 / scale.value; const leftEdge = result.originX + tapEdge; const rightEdge = result.originX + result.width - tapEdge; let toIndex = activeIndex.value; const canGoToItem = tapOnEdgeToItem && !vertical; if (e.x <= leftEdge && canGoToItem) toIndex -= 1; if (e.x >= rightEdge && canGoToItem) toIndex += 1; if (toIndex === activeIndex.value && onTap) { (0, _reactNativeReanimated.runOnJS)(onTap)(e, activeIndex.value); return; } toIndex = (0, _clamp.clamp)(toIndex, 0, length - 1); scroll.value = toIndex * itemSize.value; activeIndex.value = toIndex; fetchIndex.value = toIndex; reset(0, 0, minScale, false); }); const doubleTap = _reactNativeGestureHandler.Gesture.Tap().enabled(gesturesEnabled).numberOfTaps(2).maxDuration(250).onEnd(e => { const originX = e.x - rootSize.width.value / 2; const originY = e.y - rootSize.height.value / 2; const toScale = scale.value >= maxScale.value * 0.8 ? minScale : maxScale.value; const { x, y } = (0, _pinchTransform.pinchTransform)({ toScale: toScale, fromScale: scale.value, origin: { x: originX, y: originY }, delta: { x: 0, y: 0 }, offset: { x: translate.x.value, y: translate.y.value } }); const { x: boundX, y: boundY } = boundsFn(toScale); const toX = (0, _clamp.clamp)(x, -1 * boundX, boundX); const toY = (0, _clamp.clamp)(y, -1 * boundY, boundY); reset(toX, toY, toScale); }); const detectorStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({ width: Math.max(rootSize.width.value, rootChildSize.width.value), height: Math.max(rootSize.height.value, rootChildSize.height.value), position: 'absolute', zIndex: Number.MAX_SAFE_INTEGER, transform: [{ translateX: detectorTranslate.x.value }, { translateY: detectorTranslate.y.value }, { scale: detectorScale.value }] })); const composed = _reactNativeGestureHandler.Gesture.Race(pan, pinch, _reactNativeGestureHandler.Gesture.Exclusive(doubleTap, tap)); return /*#__PURE__*/_react.default.createElement(_reactNativeGestureHandler.GestureDetector, { gesture: composed }, /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, { style: detectorStyle })); }; var _default = exports.default = /*#__PURE__*/_react.default.memo(Reflection, (prev, next) => { return prev.onTap === next.onTap && prev.onPanStart === next.onPanStart && prev.onPanEnd === next.onPanEnd && prev.onPinchStart === next.onPinchStart && prev.onPinchEnd === next.onPinchEnd && prev.onSwipe === next.onSwipe && prev.length === next.length && prev.vertical === next.vertical && prev.tapOnEdgeToItem === next.tapOnEdgeToItem && prev.allowPinchPanning === next.allowPinchPanning && prev.pinchCenteringMode === next.pinchCenteringMode && prev.onVerticalPull === next.onVerticalPull; }); //# sourceMappingURL=Reflection.js.map