UNPKG

react-native-zoom-toolkit

Version:

Most complete set of pinch to zoom utilites for React Native

366 lines (362 loc) 13.7 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 _crop = require("../../commons/utils/crop"); var _useSizeVector = require("../../commons/hooks/useSizeVector"); var _getCropRotatedSize = require("../../commons/utils/getCropRotatedSize"); var _usePanCommons = require("../../commons/hooks/usePanCommons"); var _usePinchCommons = require("../../commons/hooks/usePinchCommons"); var _getMaxScale = require("../../commons/utils/getMaxScale"); var _useVector = require("../../commons/hooks/useVector"); var _withCropValidation = _interopRequireDefault(require("../../commons/hoc/withCropValidation")); 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 TAU = Math.PI * 2; const RAD2DEG = 180 / Math.PI; const CropZoom = props => { const { reference, children, cropSize, resolution, minScale = 1, maxScale: userMaxScale, scaleMode = 'bounce', panMode = 'free', allowPinchPanning = true, onUpdate, onGestureEnd, OverlayComponent, onPanStart: onUserPanStart, onPanEnd: onUserPanEnd, onPinchStart: onUserPinchStart, onPinchEnd: onUserPinchEnd, onTap } = props; const initialSize = (0, _getCropRotatedSize.getCropRotatedSize)({ crop: cropSize, resolution: resolution, angle: 0 }); 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 rotation = (0, _reactNativeReanimated.useSharedValue)(0); const rotate = (0, _useVector.useVector)(0, 0); const rootSize = (0, _useSizeVector.useSizeVector)(0, 0); const childSize = (0, _useSizeVector.useSizeVector)(initialSize.width, initialSize.height); const gestureSize = (0, _useSizeVector.useSizeVector)(initialSize.width, initialSize.height); const sizeAngle = (0, _reactNativeReanimated.useSharedValue)(0); const maxScale = (0, _reactNativeReanimated.useDerivedValue)(() => { const scaleValue = (0, _getMaxScale.getMaxScale)({ width: childSize.width.value, height: childSize.height.value }, resolution); return userMaxScale ?? scaleValue; }, [childSize, userMaxScale, resolution]); (0, _reactNativeReanimated.useDerivedValue)(() => { const size = (0, _getCropRotatedSize.getCropRotatedSize)({ crop: cropSize, resolution, angle: sizeAngle.value }); let finalSize = 0; const max = Math.max(rootSize.width.value, rootSize.height.value); if (childSize.width.value > childSize.height.value) { const sizeOffset = initialSize.width - cropSize.width; finalSize = max + sizeOffset; } else { const sizeOffset = initialSize.height - cropSize.height; finalSize = max + sizeOffset; } gestureSize.width.value = finalSize; gestureSize.height.value = finalSize; childSize.width.value = (0, _reactNativeReanimated.withTiming)(size.width); childSize.height.value = (0, _reactNativeReanimated.withTiming)(size.height); }, [rootSize, cropSize, resolution, childSize, sizeAngle]); (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, rotate: rotation.value, rotateX: rotate.x.value, rotateY: rotate.y.value }); }, [rootSize, childSize, maxScale, translate, scale, rotation, rotate]); const boundsFn = optionalScale => { 'worklet'; const scaleVal = optionalScale ?? scale.value; let size = { width: childSize.width.value, height: childSize.height.value }; const isInInverseAspectRatio = rotation.value % Math.PI !== 0; if (isInInverseAspectRatio) { size = { width: size.height, height: size.width }; } const boundX = Math.max(0, size.width * scaleVal - cropSize.width) / 2; const boundY = Math.max(0, size.height * scaleVal - cropSize.height) / 2; return { x: boundX, y: boundY }; }; function measureRootContainer(e) { rootSize.width.value = e.nativeEvent.layout.width; rootSize.height.value = e.nativeEvent.layout.height; } const { gesturesEnabled, onTouchesDown, onTouchesMove, onTouchesUp, onPinchStart, onPinchUpdate, onPinchEnd } = (0, _usePinchCommons.usePinchCommons)({ container: gestureSize, translate, offset, scale, scaleOffset, minScale, maxScale, allowPinchPanning, scaleMode, pinchMode: 'free', boundFn: boundsFn, userCallbacks: { onGestureEnd: onGestureEnd, onPinchStart: onUserPinchStart, onPinchEnd: onUserPinchEnd } }); const { onPanStart, onPanChange, onPanEnd } = (0, _usePanCommons.usePanCommons)({ container: gestureSize, translate, offset, panMode, boundFn: boundsFn, userCallbacks: { onGestureEnd: onGestureEnd, onPanStart: onUserPanStart, onPanEnd: onUserPanEnd } }); const pinch = _reactNativeGestureHandler.Gesture.Pinch().withTestId('pinch').manualActivation(true).onTouchesDown(onTouchesDown).onTouchesMove(onTouchesMove).onTouchesUp(onTouchesUp).onStart(onPinchStart).onUpdate(onPinchUpdate).onEnd(onPinchEnd); const pan = _reactNativeGestureHandler.Gesture.Pan().withTestId('pan').enabled(gesturesEnabled).maxPointers(1).onStart(onPanStart).onChange(onPanChange).onEnd(onPanEnd); const tap = _reactNativeGestureHandler.Gesture.Tap().withTestId('tap').enabled(gesturesEnabled).maxDuration(250).numberOfTaps(1).runOnJS(true).onEnd(e => onTap === null || onTap === void 0 ? void 0 : onTap(e)); const detectorStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => { return { width: gestureSize.width.value, height: gestureSize.height.value, position: 'absolute', transform: [{ translateX: translate.x.value }, { translateY: translate.y.value }, { scale: scale.value }] }; }, [gestureSize, translate, scale]); const childStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => { return { width: childSize.width.value, height: childSize.height.value, transform: [{ translateX: translate.x.value }, { translateY: translate.y.value }, { scale: scale.value }, { rotate: `${rotation.value}rad` }, { rotateX: `${rotate.x.value}rad` }, { rotateY: `${rotate.y.value}rad` }] }; }, [childSize, translate, scale, rotation, rotate]); // Reference handling section const resetTo = (st, animate = true) => { translate.x.value = animate ? (0, _reactNativeReanimated.withTiming)(st.translateX) : st.translateX; translate.y.value = animate ? (0, _reactNativeReanimated.withTiming)(st.translateY) : st.translateY; scale.value = animate ? (0, _reactNativeReanimated.withTiming)(st.scale) : st.scale; scaleOffset.value = st.scale; rotate.x.value = animate ? (0, _reactNativeReanimated.withTiming)(st.rotateX) : st.rotateX; rotate.y.value = animate ? (0, _reactNativeReanimated.withTiming)(st.rotateY) : st.rotateY; rotation.value = animate ? (0, _reactNativeReanimated.withTiming)(st.rotate, undefined, () => { canRotate.value = true; rotation.value = rotation.value % TAU; }) : st.rotate % TAU; }; const canRotate = (0, _reactNativeReanimated.useSharedValue)(true); const handleRotate = (animate = true, clockwise = true, cb) => { if (!canRotate.value) return; if (animate) canRotate.value = false; // Determine the direction multiplier based on clockwise or counterclockwise rotation const direction = clockwise ? 1 : -1; const toAngle = rotation.value + direction * (Math.PI / 2); sizeAngle.value = toAngle; cb === null || cb === void 0 || cb(toAngle % TAU); resetTo({ translateX: 0, translateY: 0, scale: minScale, rotate: toAngle, rotateX: rotate.x.value, rotateY: rotate.y.value }, animate); }; const flipHorizontal = (animate = true, cb) => { const toAngle = rotate.y.value !== Math.PI ? Math.PI : 0; cb === null || cb === void 0 || cb(toAngle * RAD2DEG); rotate.y.value = animate ? (0, _reactNativeReanimated.withTiming)(toAngle) : toAngle; }; const flipVertical = (animate = true, cb) => { const toAngle = rotate.x.value !== Math.PI ? Math.PI : 0; cb === null || cb === void 0 || cb(toAngle * RAD2DEG); rotate.x.value = animate ? (0, _reactNativeReanimated.withTiming)(toAngle) : toAngle; }; const handleCrop = fixedWidth => { const context = { rotationAngle: rotation.value * RAD2DEG, flipHorizontal: rotate.y.value === Math.PI, flipVertical: rotate.x.value === Math.PI }; const result = (0, _crop.crop)({ scale: scale.value, cropSize: cropSize, resolution: resolution, itemSize: { width: childSize.width.value, height: childSize.height.value }, translation: { x: translate.x.value, y: translate.y.value }, isRotated: context.rotationAngle % 180 !== 0, fixedWidth }); return { crop: result.crop, resize: result.resize, context }; }; 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, rotate: rotation.value, rotateX: rotate.x.value, rotateY: rotate.y.value }; }; const setTransformState = (state, animate = true) => { const toScale = (0, _reactNativeReanimated.clamp)(state.scale, minScale, maxScale.value); const { x: boundX, y: boundY } = boundsFn(toScale); const translateX = (0, _reactNativeReanimated.clamp)(state.translateX, -1 * boundX, boundX); const translateY = (0, _reactNativeReanimated.clamp)(state.translateY, -1 * boundY, boundY); const DEG90 = Math.PI / 2; const toRotate = Math.floor(state.rotate % (Math.PI * 2) / DEG90) * DEG90; const rotateX = Math.sign(state.rotateX - DEG90) === 1 ? Math.PI : 0; const rotateY = Math.sign(state.rotateY - DEG90) === 1 ? Math.PI : 0; resetTo({ translateX, translateY, scale: toScale, rotate: toRotate, rotateX, rotateY }, animate); }; (0, _react.useImperativeHandle)(reference, () => ({ getState: getState, setTransformState: setTransformState, rotate: handleRotate, flipHorizontal: flipHorizontal, flipVertical: flipVertical, reset: animate => resetTo({ translateX: 0, translateY: 0, scale: minScale, rotate: 0, rotateX: 0, rotateY: 0 }, animate), crop: handleCrop })); const rootStyle = { minWidth: cropSize.width, minHeight: cropSize.height }; return /*#__PURE__*/_react.default.createElement(_reactNative.View, { style: [styles.root, rootStyle, styles.center], onLayout: measureRootContainer }, /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, { style: childStyle }, children), /*#__PURE__*/_react.default.createElement(_reactNative.View, { style: styles.absolute, pointerEvents: 'none' }, OverlayComponent === null || OverlayComponent === void 0 ? void 0 : OverlayComponent()), /*#__PURE__*/_react.default.createElement(_reactNativeGestureHandler.GestureDetector, { gesture: _reactNativeGestureHandler.Gesture.Race(pinch, pan, tap) }, /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, { style: detectorStyle }))); }; const styles = _reactNative.StyleSheet.create({ root: { flex: 1 }, absolute: { position: 'absolute' }, center: { justifyContent: 'center', alignItems: 'center' } }); var _default = exports.default = (0, _withCropValidation.default)(CropZoom); //# sourceMappingURL=CropZoom.js.map