UNPKG

react-native-zoom-toolkit

Version:

Most complete set of pinch to zoom utilites for React Native

305 lines (302 loc) 11.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireWildcard(require("react")); var _reactNative = require("react-native"); var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated")); var _reactNativeGestureHandler = require("react-native-gesture-handler"); var _clamp = require("../../commons/utils/clamp"); var _useVector = require("../../commons/hooks/useVector"); var _getMaxScale = require("../../commons/utils/getMaxScale"); var _useSizeVector = require("../../commons/hooks/useSizeVector"); var _usePanCommons = require("../../commons/hooks/usePanCommons"); var _usePinchCommons = require("../../commons/hooks/usePinchCommons"); var _useDoubleTapCommons = require("../../commons/hooks/useDoubleTapCommons"); var _getVisibleRect = require("../../commons/utils/getVisibleRect"); var _withResumableValidation = _interopRequireDefault(require("../../commons/hoc/withResumableValidation")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 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 ResumableZoom = props => { const { reference, children, style, extendGestures = false, decay = true, tapsEnabled = true, panEnabled = true, pinchEnabled = true, minScale = 1, maxScale: userMaxScale = 6, panMode = 'clamp', scaleMode = 'bounce', pinchMode = 'clamp', allowPinchPanning = true, longPressDuration = 500, onTap, onLongPress, onUpdate, onGestureEnd, onSwipe, onPinchStart: onUserPinchStart, onPinchEnd: onUserPinchEnd, onPanStart: onUserPanStart, onPanEnd: onUserPanEnd, onOverPanning } = props; const rootSize = (0, _useSizeVector.useSizeVector)(1, 1); const childSize = (0, _useSizeVector.useSizeVector)(1, 1); const extendedSize = (0, _useSizeVector.useSizeVector)(1, 1); const translate = (0, _useVector.useVector)(0, 0); const offset = (0, _useVector.useVector)(0, 0); const scale = (0, _reactNativeReanimated.useSharedValue)(minScale); const scaleOffset = (0, _reactNativeReanimated.useSharedValue)(minScale); const maxScale = (0, _reactNativeReanimated.useDerivedValue)(() => { if (typeof userMaxScale === 'object') { return (0, _getMaxScale.getMaxScale)({ width: childSize.width.value, height: childSize.height.value }, userMaxScale); } return userMaxScale; }, [userMaxScale, childSize]); (0, _reactNativeReanimated.useDerivedValue)(() => { extendedSize.width.value = extendGestures ? Math.max(rootSize.width.value, childSize.width.value) : childSize.width.value; extendedSize.height.value = extendGestures ? Math.max(rootSize.height.value, childSize.height.value) : childSize.height.value; }, [extendGestures, rootSize, childSize]); const boundsFn = optionalScale => { 'worklet'; const actualScale = optionalScale ?? scale.value; const { width: cWidth, height: cHeight } = childSize; const { width: rWidth, height: rHeight } = rootSize; const boundX = Math.max(0, cWidth.value * actualScale - rWidth.value) / 2; const boundY = Math.max(0, cHeight.value * actualScale - rHeight.value) / 2; return { x: boundX, y: boundY }; }; const set = (toX, toY, toScale, animate) => { 'worklet'; 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; scaleOffset.value = toScale; }; (0, _reactNativeReanimated.useDerivedValue)(() => { onUpdate === null || onUpdate === void 0 || onUpdate({ containerSize: { width: rootSize.width.value, height: rootSize.height.value }, childSize: { width: childSize.width.value, height: childSize.height.value }, maxScale: maxScale.value, translateX: translate.x.value, translateY: translate.y.value, scale: scale.value }); }, [rootSize, childSize, translate, maxScale, scale]); const { gesturesEnabled, onTouchesDown, onTouchesMove, onTouchesUp, onPinchStart, onPinchUpdate, onPinchEnd } = (0, _usePinchCommons.usePinchCommons)({ container: extendedSize, translate, offset, scale, scaleOffset, minScale, maxScale, allowPinchPanning, scaleMode, pinchMode, boundFn: boundsFn, userCallbacks: { onGestureEnd, onPinchStart: onUserPinchStart, onPinchEnd: onUserPinchEnd } }); const { onPanStart, onPanChange, onPanEnd } = (0, _usePanCommons.usePanCommons)({ container: extendedSize, translate, offset, panMode, boundFn: boundsFn, decay, userCallbacks: { onSwipe, onGestureEnd, onPanStart: onUserPanStart, onPanEnd: onUserPanEnd, onOverPanning } }); const { onDoubleTapEnd } = (0, _useDoubleTapCommons.useDoubleTapCommons)({ container: extendedSize, translate, scale, minScale, maxScale, scaleOffset, boundsFn: boundsFn, onGestureEnd }); const pinch = _reactNativeGestureHandler.Gesture.Pinch().withTestId('pinch').enabled(pinchEnabled).manualActivation(true).onTouchesDown(onTouchesDown).onTouchesMove(onTouchesMove).onTouchesUp(onTouchesUp).onStart(onPinchStart).onUpdate(onPinchUpdate).onEnd(onPinchEnd); const pan = _reactNativeGestureHandler.Gesture.Pan().withTestId('pan').enabled(panEnabled && gesturesEnabled).maxPointers(1).onStart(onPanStart).onChange(onPanChange).onEnd(onPanEnd); const tap = _reactNativeGestureHandler.Gesture.Tap().withTestId('tap').enabled(tapsEnabled && gesturesEnabled).maxDuration(250).numberOfTaps(1).runOnJS(true).onEnd(e => onTap === null || onTap === void 0 ? void 0 : onTap(e)); const doubleTap = _reactNativeGestureHandler.Gesture.Tap().withTestId('doubleTap').enabled(tapsEnabled && gesturesEnabled).maxDuration(250).numberOfTaps(2).onEnd(onDoubleTapEnd); const longPress = _reactNativeGestureHandler.Gesture.LongPress().withTestId('longPress').enabled(gesturesEnabled).minDuration(longPressDuration).runOnJS(true).onStart(e => { onLongPress && onLongPress(e); }); const measureRoot = e => { rootSize.width.value = e.nativeEvent.layout.width; rootSize.height.value = e.nativeEvent.layout.height; }; const measureChild = e => { childSize.width.value = e.nativeEvent.layout.width; childSize.height.value = e.nativeEvent.layout.height; }; const detectorStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => { return { width: extendedSize.width.value, height: extendedSize.height.value, transform: [{ translateX: translate.x.value }, { translateY: translate.y.value }, { scale: scale.value }] }; }, [extendedSize, translate, scale]); const getState = () => { return { containerSize: { width: rootSize.width.value, height: rootSize.height.value }, childSize: { width: childSize.width.value, height: childSize.height.value }, maxScale: maxScale.value, translateX: translate.x.value, translateY: translate.y.value, scale: scale.value }; }; const setState = (state, animate = true) => { const toScale = (0, _clamp.clamp)(state.scale, minScale, maxScale.value); const { x: boundX, y: boundY } = boundsFn(toScale); const toX = (0, _clamp.clamp)(state.translateX, -1 * boundX, boundX); const toY = (0, _clamp.clamp)(state.translateY, -1 * boundY, boundY); set(toX, toY, toScale, animate); }; const getVisibleRect = () => { return (0, _getVisibleRect.getVisibleRect)({ scale: scale.value, itemSize: { width: childSize.width.value, height: childSize.height.value }, containerSize: { width: rootSize.width.value, height: rootSize.height.value }, translation: { x: translate.x.value, y: translate.y.value } }); }; const zoom = (newScale, position) => { const toScale = (0, _clamp.clamp)(newScale, minScale, maxScale.value); let focal = position; if (focal !== undefined) { focal = { x: (0, _clamp.clamp)(focal.x, 0, childSize.width.value), y: (0, _clamp.clamp)(focal.y, 0, childSize.height.value) }; } else { const frame = getVisibleRect(); focal = { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 }; } const centerX = childSize.width.value / 2; const centerY = childSize.height.value / 2; const originX = focal.x - centerX; const originY = focal.y - centerY; const signedDistanceCenterX = centerX - focal.x; const signedDistanceCenterY = centerY - focal.y; const translateX = signedDistanceCenterX + (originX - originX * toScale); const translateY = signedDistanceCenterY + (originY - originY * toScale); const { x: boundX, y: boundY } = boundsFn(toScale); const toX = (0, _clamp.clamp)(translateX, -1 * boundX, boundX); const toY = (0, _clamp.clamp)(translateY, -1 * boundY, boundY); set(toX, toY, toScale, true); }; (0, _react.useImperativeHandle)(reference, () => ({ reset: (animate = true) => set(0, 0, minScale, animate), getState: getState, setTransformState: setState, zoom: zoom, getVisibleRect: getVisibleRect })); const composedTap = _reactNativeGestureHandler.Gesture.Exclusive(doubleTap, tap, longPress); const composedGesture = _reactNativeGestureHandler.Gesture.Race(pinch, pan, composedTap); return /*#__PURE__*/_react.default.createElement(_reactNative.View, { style: [style ?? styles.flex, styles.center], onLayout: measureRoot }, /*#__PURE__*/_react.default.createElement(_reactNativeGestureHandler.GestureDetector, { gesture: composedGesture }, /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, { testID: 'root', style: [detectorStyle, styles.center] }, /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, { testID: 'child', onLayout: measureChild }, children)))); }; const styles = _reactNative.StyleSheet.create({ flex: { flex: 1 }, center: { justifyContent: 'center', alignItems: 'center' } }); var _default = exports.default = (0, _withResumableValidation.default)(ResumableZoom); //# sourceMappingURL=ResumableZoom.js.map