naive-ui
Version:
A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast
450 lines (449 loc) • 22.7 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const evtd_1 = require("evtd");
const lodash_1 = require("lodash");
const seemly_1 = require("seemly");
const vdirs_1 = require("vdirs");
const vooks_1 = require("vooks");
const vue_1 = require("vue");
const vueuc_1 = require("vueuc");
const _internal_1 = require("../../_internal");
const icons_1 = require("../../_internal/icons");
const _mixins_1 = require("../../_mixins");
const _utils_1 = require("../../_utils");
const tooltip_1 = require("../../tooltip");
const styles_1 = require("../styles");
const icons_2 = require("./icons");
const interface_1 = require("./interface");
const index_cssr_1 = __importDefault(require("./styles/index.cssr"));
const BLEEDING = 32;
exports.default = (0, vue_1.defineComponent)({
name: 'ImagePreview',
props: Object.assign(Object.assign({}, interface_1.imagePreviewSharedProps), { onNext: Function, onPrev: Function, clsPrefix: {
type: String,
required: true
} }),
setup(props) {
const themeRef = (0, _mixins_1.useTheme)('Image', '-image', index_cssr_1.default, styles_1.imageLight, props, (0, vue_1.toRef)(props, 'clsPrefix'));
let thumbnailEl = null;
const previewRef = (0, vue_1.ref)(null);
const previewWrapperRef = (0, vue_1.ref)(null);
const previewSrcRef = (0, vue_1.ref)(undefined);
const showRef = (0, vue_1.ref)(false);
const displayedRef = (0, vue_1.ref)(false);
const { localeRef } = (0, _mixins_1.useLocale)('Image');
function syncTransformOrigin() {
const { value: previewWrapper } = previewWrapperRef;
if (!thumbnailEl || !previewWrapper)
return;
const { style } = previewWrapper;
const tbox = thumbnailEl.getBoundingClientRect();
const tx = tbox.left + tbox.width / 2;
const ty = tbox.top + tbox.height / 2;
style.transformOrigin = `${tx}px ${ty}px`;
}
function handleKeydown(e) {
var _a, _b;
switch (e.key) {
case ' ':
e.preventDefault();
break;
case 'ArrowLeft':
(_a = props.onPrev) === null || _a === void 0 ? void 0 : _a.call(props);
break;
case 'ArrowRight':
(_b = props.onNext) === null || _b === void 0 ? void 0 : _b.call(props);
break;
case 'Escape':
toggleShow();
break;
}
}
(0, vue_1.watch)(showRef, (value) => {
if (value) {
(0, evtd_1.on)('keydown', document, handleKeydown);
}
else {
(0, evtd_1.off)('keydown', document, handleKeydown);
}
});
(0, vue_1.onBeforeUnmount)(() => {
(0, evtd_1.off)('keydown', document, handleKeydown);
});
let startX = 0;
let startY = 0;
let offsetX = 0;
let offsetY = 0;
let startOffsetX = 0;
let startOffsetY = 0;
let mouseDownClientX = 0;
let mouseDownClientY = 0;
let dragging = false;
function handleMouseMove(e) {
const { clientX, clientY } = e;
offsetX = clientX - startX;
offsetY = clientY - startY;
(0, seemly_1.beforeNextFrameOnce)(derivePreviewStyle);
}
function getMoveStrategy(opts) {
const { mouseUpClientX, mouseUpClientY, mouseDownClientX, mouseDownClientY } = opts;
const deltaHorizontal = mouseDownClientX - mouseUpClientX;
const deltaVertical = mouseDownClientY - mouseUpClientY;
const moveVerticalDirection = `vertical${deltaVertical > 0 ? 'Top' : 'Bottom'}`;
const moveHorizontalDirection = `horizontal${deltaHorizontal > 0 ? 'Left' : 'Right'}`;
return {
moveVerticalDirection,
moveHorizontalDirection,
deltaHorizontal,
deltaVertical
};
}
// avoid image move outside viewport
function getDerivedOffset(moveStrategy) {
const { value: preview } = previewRef;
if (!preview)
return { offsetX: 0, offsetY: 0 };
const pbox = preview.getBoundingClientRect();
const { moveVerticalDirection, moveHorizontalDirection, deltaHorizontal, deltaVertical } = moveStrategy || {};
let nextOffsetX = 0;
let nextOffsetY = 0;
if (pbox.width <= window.innerWidth) {
nextOffsetX = 0;
}
else if (pbox.left > 0) {
nextOffsetX = (pbox.width - window.innerWidth) / 2;
}
else if (pbox.right < window.innerWidth) {
nextOffsetX = -(pbox.width - window.innerWidth) / 2;
}
else if (moveHorizontalDirection === 'horizontalRight') {
nextOffsetX = Math.min((pbox.width - window.innerWidth) / 2, startOffsetX - (deltaHorizontal !== null && deltaHorizontal !== void 0 ? deltaHorizontal : 0));
}
else {
nextOffsetX = Math.max(-((pbox.width - window.innerWidth) / 2), startOffsetX - (deltaHorizontal !== null && deltaHorizontal !== void 0 ? deltaHorizontal : 0));
}
if (pbox.height <= window.innerHeight) {
nextOffsetY = 0;
}
else if (pbox.top > 0) {
nextOffsetY = (pbox.height - window.innerHeight) / 2;
}
else if (pbox.bottom < window.innerHeight) {
nextOffsetY = -(pbox.height - window.innerHeight) / 2;
}
else if (moveVerticalDirection === 'verticalBottom') {
nextOffsetY = Math.min((pbox.height - window.innerHeight) / 2, startOffsetY - (deltaVertical !== null && deltaVertical !== void 0 ? deltaVertical : 0));
}
else {
nextOffsetY = Math.max(-((pbox.height - window.innerHeight) / 2), startOffsetY - (deltaVertical !== null && deltaVertical !== void 0 ? deltaVertical : 0));
}
return {
offsetX: nextOffsetX,
offsetY: nextOffsetY
};
}
function handleMouseUp(e) {
(0, evtd_1.off)('mousemove', document, handleMouseMove);
(0, evtd_1.off)('mouseup', document, handleMouseUp);
const { clientX: mouseUpClientX, clientY: mouseUpClientY } = e;
dragging = false;
const moveStrategy = getMoveStrategy({
mouseUpClientX,
mouseUpClientY,
mouseDownClientX,
mouseDownClientY
});
const offset = getDerivedOffset(moveStrategy);
offsetX = offset.offsetX;
offsetY = offset.offsetY;
derivePreviewStyle();
}
const imageContext = (0, vue_1.inject)(interface_1.imageContextKey, null);
function handlePreviewMousedown(e) {
var _a, _b;
(_b = (_a = imageContext === null || imageContext === void 0 ? void 0 : imageContext.previewedImgPropsRef.value) === null || _a === void 0 ? void 0 : _a.onMousedown) === null || _b === void 0 ? void 0 : _b.call(_a, e);
if (e.button !== 0)
return;
const { clientX, clientY } = e;
dragging = true;
startX = clientX - offsetX;
startY = clientY - offsetY;
startOffsetX = offsetX;
startOffsetY = offsetY;
mouseDownClientX = clientX;
mouseDownClientY = clientY;
derivePreviewStyle();
(0, evtd_1.on)('mousemove', document, handleMouseMove);
(0, evtd_1.on)('mouseup', document, handleMouseUp);
}
const scaleRadix = 1.5;
let scaleExp = 0;
let scale = 1;
let rotate = 0;
function handlePreviewDblclick(e) {
var _a, _b;
(_b = (_a = imageContext === null || imageContext === void 0 ? void 0 : imageContext.previewedImgPropsRef.value) === null || _a === void 0 ? void 0 : _a.onDblclick) === null || _b === void 0 ? void 0 : _b.call(_a, e);
const originalImageSizeScale = getOrignalImageSizeScale();
scale = scale === originalImageSizeScale ? 1 : originalImageSizeScale;
derivePreviewStyle();
}
function resetScale() {
scale = 1;
scaleExp = 0;
}
function handleSwitchPrev() {
var _a;
resetScale();
rotate = 0;
(_a = props.onPrev) === null || _a === void 0 ? void 0 : _a.call(props);
}
function handleSwitchNext() {
var _a;
resetScale();
rotate = 0;
(_a = props.onNext) === null || _a === void 0 ? void 0 : _a.call(props);
}
function rotateCounterclockwise() {
rotate -= 90;
derivePreviewStyle();
}
function rotateClockwise() {
rotate += 90;
derivePreviewStyle();
}
function getMaxScale() {
const { value: preview } = previewRef;
if (!preview)
return 1;
const { innerWidth, innerHeight } = window;
const heightMaxScale = Math.max(1, preview.naturalHeight / (innerHeight - BLEEDING));
const widthMaxScale = Math.max(1, preview.naturalWidth / (innerWidth - BLEEDING));
return Math.max(3, heightMaxScale * 2, widthMaxScale * 2);
}
function getOrignalImageSizeScale() {
const { value: preview } = previewRef;
if (!preview)
return 1;
const { innerWidth, innerHeight } = window;
const heightScale = preview.naturalHeight / (innerHeight - BLEEDING);
const widthScale = preview.naturalWidth / (innerWidth - BLEEDING);
if (heightScale < 1 && widthScale < 1) {
return 1;
}
return Math.max(heightScale, widthScale);
}
function zoomIn() {
const maxScale = getMaxScale();
if (scale < maxScale) {
scaleExp += 1;
scale = Math.min(maxScale, Math.pow(scaleRadix, scaleExp));
derivePreviewStyle();
}
}
function zoomOut() {
if (scale > 0.5) {
const originalScale = scale;
scaleExp -= 1;
scale = Math.max(0.5, Math.pow(scaleRadix, scaleExp));
const diff = originalScale - scale;
derivePreviewStyle(false);
const offset = getDerivedOffset();
scale += diff;
derivePreviewStyle(false);
scale -= diff;
offsetX = offset.offsetX;
offsetY = offset.offsetY;
derivePreviewStyle();
}
}
function handleDownloadClick() {
const src = previewSrcRef.value;
if (src) {
(0, _utils_1.download)(src, undefined);
}
}
function derivePreviewStyle(transition = true) {
var _a;
const { value: preview } = previewRef;
if (!preview)
return;
const { style } = preview;
const controlledStyle = (0, vue_1.normalizeStyle)((_a = imageContext === null || imageContext === void 0 ? void 0 : imageContext.previewedImgPropsRef.value) === null || _a === void 0 ? void 0 : _a.style);
let controlledStyleString = '';
if (typeof controlledStyle === 'string') {
controlledStyleString = `${controlledStyle};`;
}
else {
for (const key in controlledStyle) {
controlledStyleString += `${(0, lodash_1.kebabCase)(key)}: ${controlledStyle[key]};`;
}
}
const transformStyle = `transform-origin: center; transform: translateX(${offsetX}px) translateY(${offsetY}px) rotate(${rotate}deg) scale(${scale});`;
if (dragging) {
style.cssText = `${controlledStyleString}cursor: grabbing; transition: none;${transformStyle}`;
}
else {
style.cssText = `${controlledStyleString}cursor: grab;${transformStyle}${transition ? '' : 'transition: none;'}`;
}
if (!transition) {
void preview.offsetHeight;
}
}
function toggleShow() {
showRef.value = !showRef.value;
displayedRef.value = true;
}
function resizeToOrignalImageSize() {
scale = getOrignalImageSizeScale();
scaleExp = Math.ceil(Math.log(scale) / Math.log(scaleRadix));
offsetX = 0;
offsetY = 0;
derivePreviewStyle();
}
const exposedMethods = {
setPreviewSrc: (src) => {
previewSrcRef.value = src;
},
setThumbnailEl: (el) => {
thumbnailEl = el;
},
toggleShow
};
function withTooltip(node, tooltipKey) {
if (props.showToolbarTooltip) {
const { value: theme } = themeRef;
return ((0, vue_1.h)(tooltip_1.NTooltip, { to: false, theme: theme.peers.Tooltip, themeOverrides: theme.peerOverrides.Tooltip, keepAliveOnHover: false }, {
default: () => {
return localeRef.value[tooltipKey];
},
trigger: () => node
}));
}
else {
return node;
}
}
const cssVarsRef = (0, vue_1.computed)(() => {
const { common: { cubicBezierEaseInOut }, self: { toolbarIconColor, toolbarBorderRadius, toolbarBoxShadow, toolbarColor } } = themeRef.value;
return {
'--n-bezier': cubicBezierEaseInOut,
'--n-toolbar-icon-color': toolbarIconColor,
'--n-toolbar-color': toolbarColor,
'--n-toolbar-border-radius': toolbarBorderRadius,
'--n-toolbar-box-shadow': toolbarBoxShadow
};
});
const { inlineThemeDisabled } = (0, _mixins_1.useConfig)();
const themeClassHandle = inlineThemeDisabled
? (0, _mixins_1.useThemeClass)('image-preview', undefined, cssVarsRef, props)
: undefined;
return Object.assign({ previewRef,
previewWrapperRef, previewSrc: previewSrcRef, show: showRef, appear: (0, vooks_1.useIsMounted)(), displayed: displayedRef, previewedImgProps: imageContext === null || imageContext === void 0 ? void 0 : imageContext.previewedImgPropsRef, handleWheel(e) {
e.preventDefault();
},
handlePreviewMousedown,
handlePreviewDblclick,
syncTransformOrigin, handleAfterLeave: () => {
resetScale();
rotate = 0;
displayedRef.value = false;
}, handleDragStart: (e) => {
var _a, _b;
(_b = (_a = imageContext === null || imageContext === void 0 ? void 0 : imageContext.previewedImgPropsRef.value) === null || _a === void 0 ? void 0 : _a.onDragstart) === null || _b === void 0 ? void 0 : _b.call(_a, e);
e.preventDefault();
}, zoomIn,
zoomOut,
handleDownloadClick,
rotateCounterclockwise,
rotateClockwise,
handleSwitchPrev,
handleSwitchNext,
withTooltip,
resizeToOrignalImageSize, cssVars: inlineThemeDisabled ? undefined : cssVarsRef, themeClass: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass, onRender: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.onRender }, exposedMethods);
},
render() {
var _a, _b;
const { clsPrefix, renderToolbar, withTooltip } = this;
const prevNode = withTooltip((0, vue_1.h)(_internal_1.NBaseIcon, { clsPrefix: clsPrefix, onClick: this.handleSwitchPrev }, { default: icons_2.renderPrevIcon }), 'tipPrevious');
const nextNode = withTooltip((0, vue_1.h)(_internal_1.NBaseIcon, { clsPrefix: clsPrefix, onClick: this.handleSwitchNext }, { default: icons_2.renderNextIcon }), 'tipNext');
const rotateCounterclockwiseNode = withTooltip((0, vue_1.h)(_internal_1.NBaseIcon, { clsPrefix: clsPrefix, onClick: this.rotateCounterclockwise }, {
default: () => (0, vue_1.h)(icons_1.RotateCounterclockwiseIcon, null)
}), 'tipCounterclockwise');
const rotateClockwiseNode = withTooltip((0, vue_1.h)(_internal_1.NBaseIcon, { clsPrefix: clsPrefix, onClick: this.rotateClockwise }, {
default: () => (0, vue_1.h)(icons_1.RotateClockwiseIcon, null)
}), 'tipClockwise');
const originalSizeNode = withTooltip((0, vue_1.h)(_internal_1.NBaseIcon, { clsPrefix: clsPrefix, onClick: this.resizeToOrignalImageSize }, {
default: () => {
return (0, vue_1.h)(icons_1.ResizeSmallIcon, null);
}
}), 'tipOriginalSize');
const zoomOutNode = withTooltip((0, vue_1.h)(_internal_1.NBaseIcon, { clsPrefix: clsPrefix, onClick: this.zoomOut }, { default: () => (0, vue_1.h)(icons_1.ZoomOutIcon, null) }), 'tipZoomOut');
const downloadNode = withTooltip((0, vue_1.h)(_internal_1.NBaseIcon, { clsPrefix: clsPrefix, onClick: this.handleDownloadClick }, { default: () => (0, vue_1.h)(icons_1.DownloadIcon, null) }), 'tipDownload');
const closeNode = withTooltip((0, vue_1.h)(_internal_1.NBaseIcon, { clsPrefix: clsPrefix, onClick: this.toggleShow }, { default: icons_2.renderCloseIcon }), 'tipClose');
const zoomInNode = withTooltip((0, vue_1.h)(_internal_1.NBaseIcon, { clsPrefix: clsPrefix, onClick: this.zoomIn }, { default: () => (0, vue_1.h)(icons_1.ZoomInIcon, null) }), 'tipZoomIn');
return ((0, vue_1.h)(vue_1.Fragment, null, (_b = (_a = this.$slots).default) === null || _b === void 0 ? void 0 :
_b.call(_a),
(0, vue_1.h)(vueuc_1.LazyTeleport, { show: this.show }, {
default: () => {
var _a;
if (!(this.show || this.displayed)) {
return null;
}
(_a = this.onRender) === null || _a === void 0 ? void 0 : _a.call(this);
return (0, vue_1.withDirectives)((0, vue_1.h)("div", { class: [
`${clsPrefix}-image-preview-container`,
this.themeClass
], style: this.cssVars, onWheel: this.handleWheel },
(0, vue_1.h)(vue_1.Transition, { name: "fade-in-transition", appear: this.appear }, {
default: () => this.show ? ((0, vue_1.h)("div", { class: `${clsPrefix}-image-preview-overlay`, onClick: this.toggleShow })) : null
}),
this.showToolbar ? ((0, vue_1.h)(vue_1.Transition, { name: "fade-in-transition", appear: this.appear }, {
default: () => {
if (!this.show)
return null;
return ((0, vue_1.h)("div", { class: `${clsPrefix}-image-preview-toolbar` }, renderToolbar ? (renderToolbar({
nodes: {
prev: prevNode,
next: nextNode,
rotateCounterclockwise: rotateCounterclockwiseNode,
rotateClockwise: rotateClockwiseNode,
resizeToOriginalSize: originalSizeNode,
zoomOut: zoomOutNode,
zoomIn: zoomInNode,
download: downloadNode,
close: closeNode
}
})) : ((0, vue_1.h)(vue_1.Fragment, null,
this.onPrev ? ((0, vue_1.h)(vue_1.Fragment, null,
prevNode,
nextNode)) : null,
rotateCounterclockwiseNode,
rotateClockwiseNode,
originalSizeNode,
zoomOutNode,
zoomInNode,
downloadNode,
closeNode))));
}
})) : null,
(0, vue_1.h)(vue_1.Transition, { name: "fade-in-scale-up-transition", onAfterLeave: this.handleAfterLeave, appear: this.appear,
// BUG:
// onEnter will be called twice, I don't know why
// Maybe it is a bug of vue
onEnter: this.syncTransformOrigin, onBeforeLeave: this.syncTransformOrigin }, {
default: () => {
const { previewedImgProps = {} } = this;
return (0, vue_1.withDirectives)((0, vue_1.h)("div", { class: `${clsPrefix}-image-preview-wrapper`, ref: "previewWrapperRef" },
(0, vue_1.h)("img", Object.assign({}, previewedImgProps, { draggable: false, onMousedown: this.handlePreviewMousedown, onDblclick: this.handlePreviewDblclick, class: [
`${clsPrefix}-image-preview`,
previewedImgProps.class
], key: this.previewSrc, src: this.previewSrc, ref: "previewRef", onDragstart: this.handleDragStart }))), [[vue_1.vShow, this.show]]);
}
})), [[vdirs_1.zindexable, { enabled: this.show }]]);
}
})));
}
});
;