react-native-zoom-toolkit
Version:
Smoothly zoom any image, video or component you want!
343 lines (338 loc) • 14.3 kB
JavaScript
"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