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); } import CSSMotion from '@rc-component/motion'; import Portal from '@rc-component/portal'; import { useEvent } from '@rc-component/util'; import { useLockFocus } from "@rc-component/util/es/Dom/focus"; import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect"; import KeyCode from "@rc-component/util/es/KeyCode"; import { clsx } from 'clsx'; import React, { useContext, useEffect, useRef, useState } from 'react'; import { PreviewGroupContext } from "../context"; import useImageTransform from "../hooks/useImageTransform"; import useMouseEvent from "../hooks/useMouseEvent"; import useStatus from "../hooks/useStatus"; import useTouchEvent from "../hooks/useTouchEvent"; import { BASE_SCALE_RATIO } from "../previewConfig"; import CloseBtn from "./CloseBtn"; import Footer from "./Footer"; import PrevNext from "./PrevNext"; // Note: if you want to add `action`, // pls contact @zombieJ or @thinkasany first. const PreviewImage = ({ fallback, src, imgRef, ...props }) => { const [getImgRef, srcAndOnload] = useStatus({ src, fallback }); return /*#__PURE__*/React.createElement("img", _extends({ ref: ref => { imgRef.current = ref; getImgRef(ref); } }, props, srcAndOnload)); }; const Preview = props => { const { prefixCls, rootClassName, src, alt, imageInfo, fallback, movable = true, onClose, open, afterOpenChange, maskClosable = true, icons = {}, closeIcon, getContainer, current = 0, count = 1, countRender, scaleStep = 0.5, minScale = 1, maxScale = 50, motionName = 'fade', imageRender, imgCommonProps, actionsRender, onTransform, onChange, classNames = {}, styles = {}, mousePosition, zIndex, focusTrap = true } = props; const imgRef = useRef(); const wrapperRef = useRef(null); const triggerRef = useRef(null); const groupContext = useContext(PreviewGroupContext); const showLeftOrRightSwitches = groupContext && count > 1; const showOperationsProgress = groupContext && count >= 1; // ======================== Transform ========================= const [enableTransition, setEnableTransition] = useState(true); const { transform, resetTransform, updateTransform, dispatchZoomChange } = useImageTransform(imgRef, minScale, maxScale, onTransform); const { isMoving, onMouseDown, onWheel } = useMouseEvent(imgRef, movable, open, scaleStep, transform, updateTransform, dispatchZoomChange); const { isTouching, onTouchStart, onTouchMove, onTouchEnd } = useTouchEvent(imgRef, movable, open, minScale, transform, updateTransform, dispatchZoomChange); const { rotate, scale } = transform; useEffect(() => { if (!enableTransition) { setEnableTransition(true); } }, [enableTransition]); useEffect(() => { if (!open) { resetTransform('close'); } }, [open]); // ========================== Image =========================== const onDoubleClick = event => { if (open) { if (scale !== 1) { updateTransform({ x: 0, y: 0, scale: 1 }, 'doubleClick'); } else { dispatchZoomChange(BASE_SCALE_RATIO + scaleStep, 'doubleClick', event.clientX, event.clientY); } } }; const imgNode = /*#__PURE__*/React.createElement(PreviewImage, _extends({}, imgCommonProps, { width: props.width, height: props.height, imgRef: imgRef, className: `${prefixCls}-img`, alt: alt, style: { transform: `translate3d(${transform.x}px, ${transform.y}px, 0) scale3d(${transform.flipX ? '-' : ''}${scale}, ${transform.flipY ? '-' : ''}${scale}, 1) rotate(${rotate}deg)`, transitionDuration: (!enableTransition || isTouching) && '0s' }, fallback: fallback, src: src, onWheel: onWheel, onMouseDown: onMouseDown, onDoubleClick: onDoubleClick, onTouchStart: onTouchStart, onTouchMove: onTouchMove, onTouchEnd: onTouchEnd, onTouchCancel: onTouchEnd })); const image = { url: src, alt, ...imageInfo }; // ======================== Operation ========================= // >>>>> Actions const onZoomIn = () => { dispatchZoomChange(BASE_SCALE_RATIO + scaleStep, 'zoomIn'); }; const onZoomOut = () => { dispatchZoomChange(BASE_SCALE_RATIO / (BASE_SCALE_RATIO + scaleStep), 'zoomOut'); }; const onRotateRight = () => { updateTransform({ rotate: rotate + 90 }, 'rotateRight'); }; const onRotateLeft = () => { updateTransform({ rotate: rotate - 90 }, 'rotateLeft'); }; const onFlipX = () => { updateTransform({ flipX: !transform.flipX }, 'flipX'); }; const onFlipY = () => { updateTransform({ flipY: !transform.flipY }, 'flipY'); }; const onReset = () => { resetTransform('reset'); }; const onActive = offset => { const nextCurrent = current + offset; if (nextCurrent >= 0 && nextCurrent <= count - 1) { setEnableTransition(false); resetTransform(offset < 0 ? 'prev' : 'next'); onChange?.(nextCurrent, current); } }; // >>>>> Effect: Keyboard const onKeyDown = useEvent(event => { if (open) { const { keyCode } = event; if (showLeftOrRightSwitches) { if (keyCode === KeyCode.LEFT) { onActive(-1); } else if (keyCode === KeyCode.RIGHT) { onActive(1); } } } }); useEffect(() => { if (open) { window.addEventListener('keydown', onKeyDown); return () => { window.removeEventListener('keydown', onKeyDown); }; } }, [open]); // ======================= Lock Scroll ======================== const [lockScroll, setLockScroll] = useState(false); React.useEffect(() => { if (open) { setLockScroll(true); } }, [open]); const onVisibleChanged = nextVisible => { if (!nextVisible) { setLockScroll(false); // Restore focus to the trigger element after leave animation triggerRef.current?.focus?.(); triggerRef.current = null; } afterOpenChange?.(nextVisible); }; // ========================== Portal ========================== const [portalRender, setPortalRender] = useState(false); useLayoutEffect(() => { if (open) { setPortalRender(true); } }, [open]); const onEsc = ({ top }) => { if (top) { onClose?.(); } }; // =========================== Focus ============================ useLayoutEffect(() => { if (open) { triggerRef.current = document.activeElement; } }, [open]); useLockFocus(focusTrap && open && portalRender, () => wrapperRef.current); // ========================== Render ========================== const bodyStyle = { ...styles.body }; if (mousePosition) { bodyStyle.transformOrigin = `${mousePosition.x}px ${mousePosition.y}px`; } return /*#__PURE__*/React.createElement(Portal, { open: portalRender && open, autoDestroy: false, getContainer: getContainer, autoLock: lockScroll, onEsc: onEsc }, /*#__PURE__*/React.createElement(CSSMotion, { motionName: motionName, visible: portalRender && open, motionAppear: true, motionEnter: true, motionLeave: true, onVisibleChanged: onVisibleChanged }, ({ className: motionClassName, style: motionStyle }) => { const mergedStyle = { ...styles.root, ...motionStyle }; if (zIndex) { mergedStyle.zIndex = zIndex; } return /*#__PURE__*/React.createElement("div", { ref: wrapperRef, className: clsx(prefixCls, rootClassName, classNames.root, motionClassName, { [`${prefixCls}-movable`]: movable, [`${prefixCls}-moving`]: isMoving }), style: mergedStyle, role: "dialog", "aria-modal": "true", "aria-label": alt, tabIndex: -1 }, /*#__PURE__*/React.createElement("div", { className: clsx(`${prefixCls}-mask`, classNames.mask), style: styles.mask, onClick: maskClosable ? onClose : undefined }), /*#__PURE__*/React.createElement("div", { className: clsx(`${prefixCls}-body`, classNames.body), style: bodyStyle }, imageRender ? imageRender(imgNode, { transform, image, ...(groupContext ? { current } : {}) }) : imgNode), closeIcon !== false && closeIcon !== null && /*#__PURE__*/React.createElement(CloseBtn, { prefixCls: prefixCls, icon: closeIcon === true ? icons.close : closeIcon || icons.close, onClick: onClose, className: classNames.close, style: styles.close }), showLeftOrRightSwitches && /*#__PURE__*/React.createElement(PrevNext, { prefixCls: prefixCls, current: current, count: count, icons: icons, onActive: onActive }), /*#__PURE__*/React.createElement(Footer, { prefixCls: prefixCls, showProgress: showOperationsProgress, current: current, count: count, showSwitch: showLeftOrRightSwitches // Style , classNames: classNames, styles: styles // Render , image: image, transform: transform, icons: icons, countRender: countRender, actionsRender: actionsRender // Scale , scale: scale, minScale: minScale, maxScale: maxScale // Actions , onActive: onActive, onFlipY: onFlipY, onFlipX: onFlipX, onRotateLeft: onRotateLeft, onRotateRight: onRotateRight, onZoomOut: onZoomOut, onZoomIn: onZoomIn, onClose: onClose, onReset: onReset })); })); }; export default Preview;