@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
484 lines (483 loc) • 18 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _isFunction2 = _interopRequireDefault(require("lodash/isFunction"));
var _isEqual2 = _interopRequireDefault(require("lodash/isEqual"));
var _react = _interopRequireDefault(require("react"));
var _baseComponent = _interopRequireDefault(require("../_base/baseComponent"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _constants = require("@douyinfe/semi-foundation/lib/cjs/image/constants");
var _classnames = _interopRequireDefault(require("classnames"));
var _portal = _interopRequireDefault(require("../_portal"));
var _semiIcons = require("@douyinfe/semi-icons");
var _previewHeader = _interopRequireDefault(require("./previewHeader"));
var _previewFooter = _interopRequireDefault(require("./previewFooter"));
var _previewImage = _interopRequireDefault(require("./previewImage"));
var _previewInnerFoundation = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/image/previewInnerFoundation"));
var _previewContext = require("./previewContext");
var _utils = require("../_utils");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const prefixCls = _constants.cssClasses.PREFIX;
class PreviewInner extends _baseComponent.default {
get adapter() {
var _this = this;
return Object.assign(Object.assign({}, super.adapter), {
getIsInGroup: () => this.isInGroup(),
disabledBodyScroll: () => {
const {
getPopupContainer
} = this.props;
this.bodyOverflow = document.body.style.overflow || '';
if (!getPopupContainer && this.bodyOverflow !== 'hidden') {
document.body.style.overflow = 'hidden';
document.body.style.width = `calc(${this.originBodyWidth || '100%'} - ${this.scrollBarWidth}px)`;
}
},
enabledBodyScroll: () => {
const {
getPopupContainer
} = this.props;
if (!getPopupContainer && this.bodyOverflow !== 'hidden') {
document.body.style.overflow = this.bodyOverflow;
document.body.style.width = this.originBodyWidth;
}
},
notifyChange: (index, direction) => {
const {
onChange,
onPrev,
onNext
} = this.props;
(0, _isFunction2.default)(onChange) && onChange(index);
if (direction === "prev") {
onPrev && onPrev(index);
} else {
onNext && onNext(index);
}
},
notifyZoom: (zoom, increase) => {
const {
onZoomIn,
onZoomOut
} = this.props;
if (increase) {
(0, _isFunction2.default)(onZoomIn) && onZoomIn(zoom);
} else {
(0, _isFunction2.default)(onZoomOut) && onZoomOut(zoom);
}
},
notifyClose: () => {
const {
onClose
} = this.props;
(0, _isFunction2.default)(onClose) && onClose();
},
notifyVisibleChange: visible => {
const {
onVisibleChange
} = this.props;
(0, _isFunction2.default)(onVisibleChange) && onVisibleChange(visible);
},
notifyRatioChange: type => {
const {
onRatioChange
} = this.props;
(0, _isFunction2.default)(onRatioChange) && onRatioChange(type);
},
notifyRotateChange: angle => {
const {
onRotateLeft
} = this.props;
(0, _isFunction2.default)(onRotateLeft) && onRotateLeft(angle);
},
notifyDownload: (src, index) => {
const {
onDownload
} = this.props;
(0, _isFunction2.default)(onDownload) && onDownload(src, index);
},
notifyDownloadError: src => {
const {
onDownloadError
} = this.props;
(0, _isFunction2.default)(onDownloadError) && onDownloadError(src);
},
registerKeyDownListener: () => {
window && window.addEventListener("keydown", this.handleKeyDown);
},
unregisterKeyDownListener: () => {
window && window.removeEventListener("keydown", this.handleKeyDown);
},
getSetDownloadFunc: () => {
var _a, _b;
return (_b = (_a = this.context) === null || _a === void 0 ? void 0 : _a.setDownloadName) !== null && _b !== void 0 ? _b : this.props.setDownloadName;
},
isValidTarget: e => {
const headerDom = this.headerRef && this.headerRef.current;
const footerDom = this.footerRef && this.footerRef.current;
const leftIconDom = this.leftIconRef && this.leftIconRef.current;
const rightIconDom = this.rightIconRef && this.rightIconRef.current;
const target = e.target;
if (headerDom && headerDom.contains(target) || footerDom && footerDom.contains(target) || leftIconDom && leftIconDom.contains(target) || rightIconDom && rightIconDom.contains(target)) {
// Move in the operation area, return false
return false;
}
// Move in the preview area except the operation area, return true
return true;
},
changeImageZoom: function () {
var _a;
((_a = _this.imageRef) === null || _a === void 0 ? void 0 : _a.current) && _this.imageRef.current.foundation.changeZoom(...arguments);
}
});
}
constructor(props) {
var _this2;
super(props);
_this2 = this;
this.viewVisibleChange = () => {
this.foundation.handleViewVisibleChange();
};
this.handleSwitchImage = direction => {
this.foundation.handleSwitchImage(direction);
};
this.handleDownload = () => {
this.foundation.handleDownload();
};
this.handlePreviewClose = e => {
this.foundation.handlePreviewClose(e);
};
this.handleAdjustRatio = type => {
this.foundation.handleAdjustRatio(type);
};
this.handleRotateImage = direction => {
this.foundation.handleRotateImage(direction);
};
this.handleZoomImage = function (newZoom) {
let notify = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
_this2.foundation.handleZoomImage(newZoom, notify);
};
this.handleMouseUp = e => {
this.foundation.handleMouseUp(e.nativeEvent);
};
this.handleMouseMove = e => {
this.foundation.handleMouseMove(e);
};
this.handleKeyDown = e => {
this.foundation.handleKeyDown(e);
};
this.onImageError = () => {
this.foundation.preloadSingleImage();
};
this.onImageLoad = src => {
this.foundation.onImageLoad(src);
};
this.handleMouseDown = e => {
this.foundation.handleMouseDown(e);
};
this.handleWheel = e => {
this.foundation.handleWheel(e);
};
// 为什么通过 addEventListener 注册 wheel 事件而不是使用 onWheel 事件?
// 因为 Passive Event Listeners(https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners)
// Passive Event Listeners 是一种优化技术,用于提高滚动性能。在默认情况下,浏览器会假设事件的监听器不会调用
// preventDefault() 方法来阻止事件的默认行为,从而允许进行一些优化操作,例如滚动平滑。
// 对于 Image 而言,如果使用触控板,双指朝不同方向分开放大图片,则需要 preventDefault 防止页面整体放大。
// Why register wheel event through addEventListener instead of using onWheel event?
// Because of Passive Event Listeners(an optimization technique used to improve scrolling performance. By default,
// the browser will assume that event listeners will not call preventDefault() method to prevent the default behavior of the event,
// allowing some optimization operations such as scroll smoothing.)
// For Image, if we use the trackpad and spread your fingers in different directions to enlarge the image, we need to preventDefault
// to prevent the page from being enlarged as a whole.
this.registryImageWrapRef = ref => {
if (this.imageWrapRef) {
this.imageWrapRef.removeEventListener("wheel", this.handleWheel);
}
if (ref) {
ref.addEventListener("wheel", this.handleWheel, {
passive: false
});
}
this.imageWrapRef = ref;
};
this.state = {
imgSrc: [],
imgLoadStatus: new Map(),
zoom: 0.1,
currentIndex: 0,
ratio: "adaptation",
rotation: 0,
viewerVisible: true,
visible: false,
preloadAfterVisibleChange: true,
direction: ""
};
this.foundation = new _previewInnerFoundation.default(this.adapter);
this.bodyOverflow = '';
this.originBodyWidth = '100%';
this.scrollBarWidth = 0;
this.imageWrapRef = null;
this.imageRef = /*#__PURE__*/_react.default.createRef();
this.headerRef = /*#__PURE__*/_react.default.createRef();
this.footerRef = /*#__PURE__*/_react.default.createRef();
this.leftIconRef = /*#__PURE__*/_react.default.createRef();
this.rightIconRef = /*#__PURE__*/_react.default.createRef();
}
static getDerivedStateFromProps(props, state) {
const willUpdateStates = {};
let src = [];
if (props.visible) {
// if src in props
src = Array.isArray(props.src) ? props.src : [props.src];
}
if (!(0, _isEqual2.default)(src, state.imgSrc)) {
willUpdateStates.imgSrc = src;
}
if (props.visible !== state.visible) {
willUpdateStates.visible = props.visible;
if (props.visible) {
willUpdateStates.preloadAfterVisibleChange = true;
willUpdateStates.viewerVisible = true;
willUpdateStates.rotation = 0;
willUpdateStates.ratio = 'adaptation';
}
}
if ("currentIndex" in props && props.currentIndex !== state.currentIndex) {
willUpdateStates.currentIndex = props.currentIndex;
// ratio will set to adaptation when change picture,
// attention: If the ratio is controlled, the ratio should not change as the index changes
willUpdateStates.ratio = 'adaptation';
}
return willUpdateStates;
}
componentDidMount() {
this.scrollBarWidth = (0, _utils.getScrollbarWidth)();
this.originBodyWidth = document.body.style.width;
if (this.props.visible) {
this.foundation.beforeShow();
}
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.src !== this.props.src) {
this.foundation.updateTimer();
}
// hide => show
if (!prevProps.visible && this.props.visible) {
this.foundation.beforeShow();
}
// show => hide
if (prevProps.visible && !this.props.visible) {
this.foundation.afterHide();
}
}
componentWillUnmount() {
this.foundation.clearTimer();
}
isInGroup() {
return Boolean(this.context && this.context.isGroup);
}
render() {
const {
getPopupContainer,
closable,
zIndex,
visible,
className,
style,
maxZoom,
minZoom,
initialZoom,
infinite,
zoomStep,
crossOrigin,
prevTip,
nextTip,
zoomInTip,
zoomOutTip,
rotateTip,
downloadTip,
adaptiveTip,
originTip,
showTooltip,
disableDownload,
renderLeftIcon,
renderRightIcon,
renderCloseIcon,
renderPreviewMenu,
renderHeader
} = this.props;
const {
currentIndex,
imgSrc,
zoom,
ratio,
rotation,
viewerVisible
} = this.state;
let wrapperStyle = {
zIndex
};
if (getPopupContainer) {
wrapperStyle = {
zIndex,
position: "static"
};
}
const previewPrefixCls = `${prefixCls}-preview`;
const previewWrapperCls = (0, _classnames.default)(previewPrefixCls, {
[`${prefixCls}-hide`]: !visible,
[`${previewPrefixCls}-popup`]: getPopupContainer
}, className);
const hideViewerCls = !viewerVisible ? `${previewPrefixCls}-hide` : "";
const total = imgSrc.length;
const showPrev = total !== 1 && (infinite || currentIndex !== 0);
const showNext = total !== 1 && (infinite || currentIndex !== total - 1);
const leftIcon = typeof renderLeftIcon === 'function' ? renderLeftIcon(currentIndex) : renderLeftIcon;
const rightIcon = typeof renderRightIcon === 'function' ? renderRightIcon(currentIndex) : renderRightIcon;
return visible && /*#__PURE__*/_react.default.createElement(_portal.default, {
getPopupContainer: getPopupContainer,
style: wrapperStyle
}, /*#__PURE__*/_react.default.createElement("div", {
className: previewWrapperCls,
style: style,
onMouseDown: this.handleMouseDown,
onMouseUp: this.handleMouseUp,
ref: this.registryImageWrapRef,
onMouseMove: this.handleMouseMove
}, /*#__PURE__*/_react.default.createElement(_previewHeader.default, {
ref: this.headerRef,
className: (0, _classnames.default)(hideViewerCls),
onClose: this.handlePreviewClose,
renderHeader: renderHeader,
closable: closable,
renderCloseIcon: renderCloseIcon
}), /*#__PURE__*/_react.default.createElement(_previewImage.default, {
ref: this.imageRef,
src: imgSrc[currentIndex],
onZoom: this.handleZoomImage,
disableDownload: disableDownload,
setRatio: this.handleAdjustRatio,
zoom: zoom,
ratio: ratio,
rotation: rotation,
crossOrigin: crossOrigin,
initialZoom: initialZoom,
maxZoom: maxZoom,
minZoom: minZoom,
onError: this.onImageError,
onLoad: this.onImageLoad
}), showPrev && (
/*#__PURE__*/
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
_react.default.createElement("div", {
ref: this.leftIconRef,
className: (0, _classnames.default)(`${previewPrefixCls}-icon`, `${previewPrefixCls}-prev`, hideViewerCls),
onClick: () => this.handleSwitchImage("prev")
}, /*#__PURE__*/_react.default.isValidElement(leftIcon) ? leftIcon : /*#__PURE__*/_react.default.createElement(_semiIcons.IconArrowLeft, {
size: "large"
}))), showNext && (
/*#__PURE__*/
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
_react.default.createElement("div", {
ref: this.rightIconRef,
className: (0, _classnames.default)(`${previewPrefixCls}-icon`, `${previewPrefixCls}-next`, hideViewerCls),
onClick: () => this.handleSwitchImage("next")
}, /*#__PURE__*/_react.default.isValidElement(rightIcon) ? rightIcon : /*#__PURE__*/_react.default.createElement(_semiIcons.IconArrowRight, {
size: "large"
}))), /*#__PURE__*/_react.default.createElement(_previewFooter.default, {
forwardRef: this.footerRef,
className: hideViewerCls,
totalNum: total,
curPage: currentIndex + 1,
disabledPrev: !showPrev,
disabledNext: !showNext,
zoom: zoom * 100,
min: minZoom * 100,
max: maxZoom * 100,
step: zoomStep * 100,
showTooltip: showTooltip,
ratio: ratio,
prevTip: prevTip,
nextTip: nextTip,
zIndex: zIndex,
zoomInTip: zoomInTip,
zoomOutTip: zoomOutTip,
rotateTip: rotateTip,
downloadTip: downloadTip,
disableDownload: disableDownload,
adaptiveTip: adaptiveTip,
originTip: originTip,
onPrev: () => this.handleSwitchImage("prev"),
onNext: () => this.handleSwitchImage("next"),
onZoomIn: this.handleZoomImage,
onZoomOut: this.handleZoomImage,
onDownload: this.handleDownload,
onRotate: this.handleRotateImage,
onAdjustRatio: this.handleAdjustRatio,
renderPreviewMenu: renderPreviewMenu
})));
}
}
exports.default = PreviewInner;
PreviewInner.contextType = _previewContext.PreviewContext;
PreviewInner.propTypes = {
style: _propTypes.default.object,
className: _propTypes.default.string,
visible: _propTypes.default.bool,
src: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.array]),
currentIndex: _propTypes.default.number,
defaultCurrentIndex: _propTypes.default.number,
defaultVisible: _propTypes.default.bool,
maskClosable: _propTypes.default.bool,
closable: _propTypes.default.bool,
zoomStep: _propTypes.default.number,
infinite: _propTypes.default.bool,
showTooltip: _propTypes.default.bool,
closeOnEsc: _propTypes.default.bool,
prevTip: _propTypes.default.string,
nextTip: _propTypes.default.string,
zoomInTip: _propTypes.default.string,
zoomOutTip: _propTypes.default.string,
downloadTip: _propTypes.default.string,
adaptiveTip: _propTypes.default.string,
originTip: _propTypes.default.string,
lazyLoad: _propTypes.default.bool,
preLoad: _propTypes.default.bool,
preLoadGap: _propTypes.default.number,
disableDownload: _propTypes.default.bool,
viewerVisibleDelay: _propTypes.default.number,
zIndex: _propTypes.default.number,
maxZoom: _propTypes.default.number,
minZoom: _propTypes.default.number,
initialZoom: _propTypes.default.number,
renderHeader: _propTypes.default.func,
renderPreviewMenu: _propTypes.default.func,
getPopupContainer: _propTypes.default.func,
onVisibleChange: _propTypes.default.func,
onChange: _propTypes.default.func,
onClose: _propTypes.default.func,
onZoomIn: _propTypes.default.func,
onZoomOut: _propTypes.default.func,
onPrev: _propTypes.default.func,
onNext: _propTypes.default.func,
onDownload: _propTypes.default.func,
onRatioChange: _propTypes.default.func,
onRotateLeft: _propTypes.default.func
};
PreviewInner.defaultProps = {
showTooltip: false,
zoomStep: 0.1,
infinite: false,
closable: true,
closeOnEsc: true,
lazyLoad: false,
preLoad: true,
preLoadGap: 2,
zIndex: _constants.numbers.DEFAULT_Z_INDEX,
maskClosable: true,
viewerVisibleDelay: 10000,
maxZoom: 5,
minZoom: 0.1
};