react-medium-image-zoom
Version:
Accessible medium.com-style image zoom for React
810 lines (804 loc) • 34.5 kB
JavaScript
'use client';
import React from 'react';
import ReactDOM from 'react-dom';
function ICompress() {
return (React.createElement("svg", { "aria-hidden": "true", "data-rmiz-btn-unzoom-icon": true, fill: "currentColor", focusable: "false", viewBox: "0 0 16 16", xmlns: "http://www.w3.org/2000/svg" },
React.createElement("path", { d: "M 14.144531 1.148438 L 9 6.292969 L 9 3 L 8 3 L 8 8 L 13 8 L 13 7 L 9.707031 7 L 14.855469 1.851563 Z M 8 8 L 3 8 L 3 9 L 6.292969 9 L 1.148438 14.144531 L 1.851563 14.855469 L 7 9.707031 L 7 13 L 8 13 Z" })));
}
function IEnlarge() {
return (React.createElement("svg", { "aria-hidden": "true", "data-rmiz-btn-zoom-icon": true, fill: "currentColor", focusable: "false", viewBox: "0 0 16 16", xmlns: "http://www.w3.org/2000/svg" },
React.createElement("path", { d: "M 9 1 L 9 2 L 12.292969 2 L 2 12.292969 L 2 9 L 1 9 L 1 14 L 6 14 L 6 13 L 2.707031 13 L 13 2.707031 L 13 6 L 14 6 L 14 1 Z" })));
}
const testElType = (type, el) => type === el?.tagName?.toUpperCase?.();
const testDiv = (el) => testElType('DIV', el) || testElType('SPAN', el);
const testImg = (el) => testElType('IMG', el);
const testImgLoaded = (el) => el.complete && el.naturalHeight !== 0;
const testSvg = (el) => testElType('SVG', el);
const getScaleToWindow = ({ height, offset, width }) => {
return Math.min((window.innerWidth - offset * 2) / width, (window.innerHeight - offset * 2) / height);
};
const getScaleToWindowMax = ({ containerHeight, containerWidth, offset, targetHeight, targetWidth, }) => {
const scale = getScaleToWindow({
height: targetHeight,
offset,
width: targetWidth,
});
const ratio = targetWidth > targetHeight
? targetWidth / containerWidth
: targetHeight / containerHeight;
return scale > 1 ? ratio : scale * ratio;
};
const getScale = ({ containerHeight, containerWidth, hasScalableSrc, offset, targetHeight, targetWidth, }) => {
if (!containerHeight || !containerWidth) {
return 1;
}
return !hasScalableSrc && targetHeight && targetWidth
? getScaleToWindowMax({
containerHeight,
containerWidth,
offset,
targetHeight,
targetWidth,
})
: getScaleToWindow({
height: containerHeight,
offset,
width: containerWidth,
});
};
const URL_REGEX = /url(?:\(['"]?)(.*?)(?:['"]?\))/;
const getImgSrc = (imgEl) => {
if (imgEl) {
if (testImg(imgEl)) {
return imgEl.currentSrc;
}
else if (testDiv(imgEl)) {
const bgImg = window.getComputedStyle(imgEl).backgroundImage;
if (bgImg) {
return URL_REGEX.exec(bgImg)?.[1];
}
}
}
};
const getImgAlt = (imgEl) => {
if (imgEl) {
if (testImg(imgEl)) {
return imgEl.alt ?? undefined;
}
else {
return imgEl.getAttribute('aria-label') ?? undefined;
}
}
};
const getImgRegularStyle = ({ containerHeight, containerLeft, containerTop, containerWidth, hasScalableSrc, offset, targetHeight, targetWidth, }) => {
const scale = getScale({
containerHeight,
containerWidth,
hasScalableSrc,
offset,
targetHeight,
targetWidth,
});
return {
top: containerTop,
left: containerLeft,
width: containerWidth * scale,
height: containerHeight * scale,
transform: `translate(0,0) scale(${1 / scale})`,
};
};
const parsePosition = ({ position, relativeNum }) => {
const positionNum = parseFloat(position);
return position.endsWith('%')
? relativeNum * positionNum / 100
: positionNum;
};
const getImgObjectFitStyle = ({ containerHeight, containerLeft, containerTop, containerWidth, hasScalableSrc, objectFit, objectPosition, offset, targetHeight, targetWidth, }) => {
if (objectFit === 'scale-down') {
if (targetWidth <= containerWidth && targetHeight <= containerHeight) {
objectFit = 'none';
}
else {
objectFit = 'contain';
}
}
if (objectFit === 'cover' || objectFit === 'contain') {
const widthRatio = containerWidth / targetWidth;
const heightRatio = containerHeight / targetHeight;
const ratio = objectFit === 'cover'
? Math.max(widthRatio, heightRatio)
: Math.min(widthRatio, heightRatio);
const [posLeft = '50%', posTop = '50%'] = objectPosition.split(' ');
const posX = parsePosition({ position: posLeft, relativeNum: containerWidth - targetWidth * ratio });
const posY = parsePosition({ position: posTop, relativeNum: containerHeight - targetHeight * ratio });
const scale = getScale({
containerHeight: targetHeight * ratio,
containerWidth: targetWidth * ratio,
hasScalableSrc,
offset,
targetHeight,
targetWidth,
});
return {
top: containerTop + posY,
left: containerLeft + posX,
width: targetWidth * ratio * scale,
height: targetHeight * ratio * scale,
transform: `translate(0,0) scale(${1 / scale})`,
};
}
else if (objectFit === 'none') {
const [posLeft = '50%', posTop = '50%'] = objectPosition.split(' ');
const posX = parsePosition({ position: posLeft, relativeNum: containerWidth - targetWidth });
const posY = parsePosition({ position: posTop, relativeNum: containerHeight - targetHeight });
const scale = getScale({
containerHeight: targetHeight,
containerWidth: targetWidth,
hasScalableSrc,
offset,
targetHeight,
targetWidth,
});
return {
top: containerTop + posY,
left: containerLeft + posX,
width: targetWidth * scale,
height: targetHeight * scale,
transform: `translate(0,0) scale(${1 / scale})`,
};
}
else if (objectFit === 'fill') {
const widthRatio = containerWidth / targetWidth;
const heightRatio = containerHeight / targetHeight;
const ratio = Math.max(widthRatio, heightRatio);
const scale = getScale({
containerHeight: targetHeight * ratio,
containerWidth: targetWidth * ratio,
hasScalableSrc,
offset,
targetHeight,
targetWidth,
});
return {
width: containerWidth * scale,
height: containerHeight * scale,
transform: `translate(0,0) scale(${1 / scale})`,
};
}
else {
return {};
}
};
const getDivImgStyle = ({ backgroundPosition, backgroundSize, containerHeight, containerLeft, containerTop, containerWidth, hasScalableSrc, offset, targetHeight, targetWidth, }) => {
if (backgroundSize === 'cover' || backgroundSize === 'contain') {
const widthRatio = containerWidth / targetWidth;
const heightRatio = containerHeight / targetHeight;
const ratio = backgroundSize === 'cover'
? Math.max(widthRatio, heightRatio)
: Math.min(widthRatio, heightRatio);
const [posLeft = '50%', posTop = '50%'] = backgroundPosition.split(' ');
const posX = parsePosition({ position: posLeft, relativeNum: containerWidth - targetWidth * ratio });
const posY = parsePosition({ position: posTop, relativeNum: containerHeight - targetHeight * ratio });
const scale = getScale({
containerHeight: targetHeight * ratio,
containerWidth: targetWidth * ratio,
hasScalableSrc,
offset,
targetHeight,
targetWidth,
});
return {
top: containerTop + posY,
left: containerLeft + posX,
width: targetWidth * ratio * scale,
height: targetHeight * ratio * scale,
transform: `translate(0,0) scale(${1 / scale})`,
};
}
else if (backgroundSize === 'auto') {
const [posLeft = '50%', posTop = '50%'] = backgroundPosition.split(' ');
const posX = parsePosition({ position: posLeft, relativeNum: containerWidth - targetWidth });
const posY = parsePosition({ position: posTop, relativeNum: containerHeight - targetHeight });
const scale = getScale({
containerHeight: targetHeight,
containerWidth: targetWidth,
hasScalableSrc,
offset,
targetHeight,
targetWidth,
});
return {
top: containerTop + posY,
left: containerLeft + posX,
width: targetWidth * scale,
height: targetHeight * scale,
transform: `translate(0,0) scale(${1 / scale})`,
};
}
else {
const [sizeW = '50%', sizeH = '50%'] = backgroundSize.split(' ');
const sizeWidth = parsePosition({ position: sizeW, relativeNum: containerWidth });
const sizeHeight = parsePosition({ position: sizeH, relativeNum: containerHeight });
const widthRatio = sizeWidth / targetWidth;
const heightRatio = sizeHeight / targetHeight;
const ratio = Math.min(widthRatio, heightRatio);
const [posLeft = '50%', posTop = '50%'] = backgroundPosition.split(' ');
const posX = parsePosition({ position: posLeft, relativeNum: containerWidth - targetWidth * ratio });
const posY = parsePosition({ position: posTop, relativeNum: containerHeight - targetHeight * ratio });
const scale = getScale({
containerHeight: targetHeight * ratio,
containerWidth: targetWidth * ratio,
hasScalableSrc,
offset,
targetHeight,
targetWidth,
});
return {
top: containerTop + posY,
left: containerLeft + posX,
width: targetWidth * ratio * scale,
height: targetHeight * ratio * scale,
transform: `translate(0,0) scale(${1 / scale})`,
};
}
};
const SRC_SVG_REGEX = /\.svg$/i;
const getStyleModalImg = ({ hasZoomImg, imgSrc, isSvg, isZoomed, loadedImgEl, offset, shouldRefresh, targetEl, }) => {
const hasScalableSrc = isSvg ||
imgSrc?.slice?.(0, 18) === 'data:image/svg+xml' ||
hasZoomImg ||
!!(imgSrc && SRC_SVG_REGEX.test(imgSrc));
const imgRect = targetEl.getBoundingClientRect();
const targetElComputedStyle = window.getComputedStyle(targetEl);
const isDivImg = loadedImgEl != null && testDiv(targetEl);
const isImgObjectFit = loadedImgEl != null && !isDivImg;
const styleImgRegular = getImgRegularStyle({
containerHeight: imgRect.height,
containerLeft: imgRect.left,
containerTop: imgRect.top,
containerWidth: imgRect.width,
hasScalableSrc,
offset,
targetHeight: loadedImgEl?.naturalHeight || imgRect.height,
targetWidth: loadedImgEl?.naturalWidth || imgRect.width,
});
const styleImgObjectFit = isImgObjectFit
? getImgObjectFitStyle({
containerHeight: imgRect.height,
containerLeft: imgRect.left,
containerTop: imgRect.top,
containerWidth: imgRect.width,
hasScalableSrc,
objectFit: targetElComputedStyle.objectFit,
objectPosition: targetElComputedStyle.objectPosition,
offset,
targetHeight: loadedImgEl?.naturalHeight || imgRect.height,
targetWidth: loadedImgEl?.naturalWidth || imgRect.width,
})
: undefined;
const styleDivImg = isDivImg
? getDivImgStyle({
backgroundPosition: targetElComputedStyle.backgroundPosition,
backgroundSize: targetElComputedStyle.backgroundSize,
containerHeight: imgRect.height,
containerLeft: imgRect.left,
containerTop: imgRect.top,
containerWidth: imgRect.width,
hasScalableSrc,
offset,
targetHeight: loadedImgEl?.naturalHeight || imgRect.height,
targetWidth: loadedImgEl?.naturalWidth || imgRect.width,
})
: undefined;
const style = Object.assign({}, styleImgRegular, styleImgObjectFit, styleDivImg);
if (isZoomed) {
const viewportX = window.innerWidth / 2;
const viewportY = window.innerHeight / 2;
const childCenterX = parseFloat(String(style.left || 0)) + (parseFloat(String(style.width || 0)) / 2);
const childCenterY = parseFloat(String(style.top || 0)) + (parseFloat(String(style.height || 0)) / 2);
const translateX = viewportX - childCenterX;
const translateY = viewportY - childCenterY;
if (shouldRefresh) {
style.transitionDuration = '0.01ms';
}
style.transform = `translate(${translateX}px,${translateY}px) scale(1)`;
}
return style;
};
const getStyleGhost = (imgEl) => {
if (!imgEl) {
return {};
}
if (testSvg(imgEl)) {
const parentEl = imgEl.parentElement;
const rect = imgEl.getBoundingClientRect();
if (parentEl) {
const parentRect = parentEl.getBoundingClientRect();
return {
height: rect.height,
left: parentRect.left - rect.left,
top: parentRect.top - rect.top,
width: rect.width,
};
}
else {
return {
height: rect.height,
left: rect.left,
width: rect.width,
top: rect.top,
};
}
}
else {
return {
height: imgEl.offsetHeight,
left: imgEl.offsetLeft,
width: imgEl.offsetWidth,
top: imgEl.offsetTop,
};
}
};
const adjustSvgIDs = (svgEl) => {
const newIdSuffix = '-zoom';
const attrs = [
'clip-path',
'fill',
'mask',
'marker-start',
'marker-mid',
'marker-end',
];
const idMap = new Map();
if (svgEl.hasAttribute('id')) {
const oldId = svgEl.id;
const newId = oldId + newIdSuffix;
idMap.set(oldId, newId);
svgEl.id = newId;
}
svgEl.querySelectorAll('[id]').forEach(el => {
const oldId = el.id;
const newId = oldId + newIdSuffix;
idMap.set(oldId, newId);
el.id = newId;
});
idMap.forEach((newId, oldId) => {
const urlOldID = `url(#${oldId})`;
const urlNewID = `url(#${newId})`;
const attrsQuery = attrs.map(attr => `[${attr}="${urlOldID}"]`).join(', ');
svgEl.querySelectorAll(attrsQuery).forEach(usedEl => {
attrs.forEach(attr => {
if (usedEl.getAttribute(attr) === urlOldID) {
usedEl.setAttribute(attr, urlNewID);
}
});
});
});
svgEl.querySelectorAll('style').forEach(styleEl => {
idMap.forEach((newId, oldId) => {
if (styleEl.textContent) {
styleEl.textContent = styleEl.textContent.replaceAll(`#${oldId}`, `#${newId}`);
}
});
});
};
const IMAGE_QUERY = ['img', 'svg', '[role="img"]', '[data-zoom]']
.map(x => `${x}:not([aria-hidden="true"])`)
.join(',');
const defaultBodyAttrs = {
overflow: '',
width: '',
};
function Controlled(props) {
return React.createElement(ControlledBase, { ...props });
}
class ControlledBase extends React.Component {
constructor() {
super(...arguments);
this.state = {
id: '',
isZoomImgLoaded: false,
loadedImgEl: undefined,
modalState: "UNLOADED",
shouldRefresh: false,
styleGhost: {},
};
this.refContent = React.createRef();
this.refDialog = React.createRef();
this.refModalContent = React.createRef();
this.refModalImg = React.createRef();
this.refWrap = React.createRef();
this.imgEl = null;
this.isScaling = false;
this.prevBodyAttrs = defaultBodyAttrs;
this.styleModalImg = {};
this.handleModalStateChange = (prevModalState) => {
const { modalState } = this.state;
if (prevModalState !== "LOADING" && modalState === "LOADING") {
this.loadZoomImg();
window.addEventListener('resize', this.handleResize, { passive: true });
window.addEventListener('touchstart', this.handleTouchStart, { passive: true });
window.addEventListener('touchmove', this.handleTouchMove, { passive: true });
window.addEventListener('touchend', this.handleTouchEnd, { passive: true });
window.addEventListener('touchcancel', this.handleTouchCancel, { passive: true });
document.addEventListener('keydown', this.handleKeyDown, true);
}
else if (prevModalState !== "LOADED" && modalState === "LOADED") {
window.addEventListener('wheel', this.handleWheel, { passive: true });
}
else if (prevModalState !== "UNLOADING" && modalState === "UNLOADING") {
this.ensureImgTransitionEnd();
window.removeEventListener('wheel', this.handleWheel);
window.removeEventListener('touchstart', this.handleTouchStart);
window.removeEventListener('touchmove', this.handleTouchMove);
window.removeEventListener('touchend', this.handleTouchEnd);
window.removeEventListener('touchcancel', this.handleTouchCancel);
document.removeEventListener('keydown', this.handleKeyDown, true);
}
else if (prevModalState !== "UNLOADED" && modalState === "UNLOADED") {
this.bodyScrollEnable();
window.removeEventListener('resize', this.handleResize);
this.refModalImg.current?.removeEventListener?.('transitionend', this.handleImgTransitionEnd);
this.refDialog.current?.close?.();
}
};
this.getDialogContainer = () => {
let el = document.querySelector('[data-rmiz-portal]');
if (el == null) {
el = document.createElement('div');
el.setAttribute('data-rmiz-portal', '');
document.body.appendChild(el);
}
return el;
};
this.setId = () => {
const gen4 = () => Math.random().toString(16).slice(-4);
this.setState({ id: gen4() + gen4() + gen4() });
};
this.setAndTrackImg = () => {
const contentEl = this.refContent.current;
if (!contentEl)
return;
this.imgEl = contentEl.querySelector(IMAGE_QUERY);
if (this.imgEl) {
this.contentNotFoundChangeObserver?.disconnect?.();
this.imgEl.addEventListener('load', this.handleImgLoad);
this.imgEl.addEventListener('click', this.handleZoom);
if (!this.state.loadedImgEl) {
this.handleImgLoad();
}
this.imgElResizeObserver = new ResizeObserver(entries => {
const entry = entries[0];
if (entry?.target) {
this.imgEl = entry.target;
this.setState({ styleGhost: getStyleGhost(this.imgEl) });
}
});
this.imgElResizeObserver.observe(this.imgEl);
if (!this.contentChangeObserver) {
this.contentChangeObserver = new MutationObserver(() => {
this.setState({ styleGhost: getStyleGhost(this.imgEl) });
});
this.contentChangeObserver.observe(contentEl, { attributes: true, childList: true, subtree: true });
}
}
else if (!this.contentNotFoundChangeObserver) {
this.contentNotFoundChangeObserver = new MutationObserver(this.setAndTrackImg);
this.contentNotFoundChangeObserver.observe(contentEl, { childList: true, subtree: true });
}
};
this.handleIfZoomChanged = (prevIsZoomed) => {
const { isZoomed } = this.props;
if (!prevIsZoomed && isZoomed) {
this.zoom();
}
else if (prevIsZoomed && !isZoomed) {
this.unzoom();
}
};
this.handleImgLoad = () => {
const imgSrc = getImgSrc(this.imgEl);
if (!imgSrc)
return;
const img = new Image();
if (testImg(this.imgEl)) {
img.sizes = this.imgEl.sizes;
img.srcset = this.imgEl.srcset;
img.crossOrigin = this.imgEl.crossOrigin;
}
img.src = imgSrc;
const setLoaded = () => {
this.setState({
loadedImgEl: img,
styleGhost: getStyleGhost(this.imgEl),
});
};
img
.decode()
.then(setLoaded)
.catch(() => {
if (testImgLoaded(img)) {
setLoaded();
return;
}
img.onload = setLoaded;
});
};
this.handleZoom = (e) => {
if (!this.props.isDisabled && this.hasImage()) {
this.props.onZoomChange?.(true, { event: e });
}
};
this.handleUnzoom = (e) => {
if (!this.props.isDisabled) {
this.props.onZoomChange?.(false, { event: e });
}
};
this.handleBtnUnzoomClick = (e) => {
e.preventDefault();
e.stopPropagation();
this.handleUnzoom(e);
};
this.handleDialogCancel = (e) => {
e.preventDefault();
};
this.handleDialogClick = (e) => {
if (e.target === this.refModalContent.current || e.target === this.refModalImg.current) {
e.stopPropagation();
this.handleUnzoom(e);
}
};
this.handleDialogClose = (e) => {
e.stopPropagation();
this.handleUnzoom(e);
};
this.handleKeyDown = (e) => {
if (e.key === 'Escape' || e.keyCode === 27) {
e.preventDefault();
e.stopPropagation();
this.handleUnzoom(e);
}
};
this.handleWheel = (e) => {
if (e.ctrlKey)
return;
e.stopPropagation();
queueMicrotask(() => {
this.handleUnzoom(e);
});
};
this.handleTouchStart = (e) => {
if (e.touches.length > 1) {
this.isScaling = true;
return;
}
if (e.changedTouches.length === 1 && e.changedTouches[0]) {
this.touchYStart = e.changedTouches[0].screenY;
}
};
this.handleTouchMove = (e) => {
const browserScale = window.visualViewport?.scale ?? 1;
if (this.props.canSwipeToUnzoom &&
!this.isScaling &&
browserScale <= 1 && this.touchYStart != null &&
e.changedTouches[0]) {
this.touchYEnd = e.changedTouches[0].screenY;
const max = Math.max(this.touchYStart, this.touchYEnd);
const min = Math.min(this.touchYStart, this.touchYEnd);
const delta = Math.abs(max - min);
if (delta > this.props.swipeToUnzoomThreshold) {
this.touchYStart = undefined;
this.touchYEnd = undefined;
this.handleUnzoom(e);
}
}
};
this.handleTouchEnd = () => {
this.isScaling = false;
this.touchYStart = undefined;
this.touchYEnd = undefined;
};
this.handleTouchCancel = () => {
this.isScaling = false;
this.touchYStart = undefined;
this.touchYEnd = undefined;
};
this.handleResize = () => {
this.setState({ shouldRefresh: true });
};
this.hasImage = () => {
return this.imgEl &&
(this.state.loadedImgEl || testSvg(this.imgEl)) &&
window.getComputedStyle(this.imgEl).display !== 'none';
};
this.zoom = () => {
this.bodyScrollDisable();
this.refDialog.current?.showModal?.();
this.refModalImg.current?.addEventListener?.('transitionend', this.handleImgTransitionEnd);
this.setState({ modalState: "LOADING" });
};
this.unzoom = () => {
this.setState({ modalState: "UNLOADING" });
};
this.handleImgTransitionEnd = () => {
clearTimeout(this.timeoutTransitionEnd);
if (this.state.modalState === "LOADING") {
this.setState({ modalState: "LOADED" });
}
else if (this.state.modalState === "UNLOADING") {
this.setState({ shouldRefresh: false, modalState: "UNLOADED" });
}
};
this.ensureImgTransitionEnd = () => {
if (this.refModalImg.current) {
const td = window.getComputedStyle(this.refModalImg.current).transitionDuration;
const tdFloat = parseFloat(td);
if (tdFloat) {
const tdMs = tdFloat * (td.endsWith('ms') ? 1 : 1000) + 50;
this.timeoutTransitionEnd = setTimeout(this.handleImgTransitionEnd, tdMs);
}
}
};
this.bodyScrollDisable = () => {
this.prevBodyAttrs = {
overflow: document.body.style.overflow,
width: document.body.style.width,
};
const clientWidth = document.body.clientWidth;
document.body.style.overflow = 'hidden';
document.body.style.width = `${clientWidth}px`;
};
this.bodyScrollEnable = () => {
document.body.style.width = this.prevBodyAttrs.width;
document.body.style.overflow = this.prevBodyAttrs.overflow;
this.prevBodyAttrs = defaultBodyAttrs;
};
this.loadZoomImg = () => {
const { props: { zoomImg } } = this;
const zoomImgSrc = zoomImg?.src;
if (zoomImgSrc) {
const img = new Image();
img.sizes = zoomImg?.sizes ?? '';
img.srcset = zoomImg?.srcSet ?? '';
img.crossOrigin = zoomImg?.crossOrigin ?? undefined;
img.src = zoomImgSrc;
const setLoaded = () => {
this.setState({ isZoomImgLoaded: true });
};
img
.decode()
.then(setLoaded)
.catch(() => {
if (testImgLoaded(img)) {
setLoaded();
return;
}
img.onload = setLoaded;
});
}
};
this.UNSAFE_handleSvg = () => {
const { imgEl, refModalImg, styleModalImg } = this;
if (testSvg(imgEl)) {
const svgEl = imgEl.cloneNode(true);
adjustSvgIDs(svgEl);
svgEl.style.width = `${styleModalImg.width || 0}px`;
svgEl.style.height = `${styleModalImg.height || 0}px`;
svgEl.addEventListener('click', this.handleUnzoom);
refModalImg.current?.firstChild?.remove?.();
refModalImg.current?.appendChild?.(svgEl);
}
};
}
render() {
const { handleBtnUnzoomClick, handleDialogCancel, handleDialogClick, handleDialogClose, handleUnzoom, handleZoom, imgEl, props: { a11yNameButtonUnzoom, a11yNameButtonZoom, children, classDialog, IconUnzoom, IconZoom, isZoomed, wrapElement: WrapElement, ZoomContent, zoomImg, zoomMargin, }, refContent, refDialog, refModalContent, refModalImg, refWrap, state: { id, isZoomImgLoaded, loadedImgEl, modalState, shouldRefresh, styleGhost, }, } = this;
const idModal = `rmiz-modal-${id}`;
const idModalImg = `rmiz-modal-img-${id}`;
const isDiv = testDiv(imgEl);
const isImg = testImg(imgEl);
const isSvg = testSvg(imgEl);
const imgAlt = getImgAlt(imgEl);
const imgSrc = getImgSrc(imgEl);
const imgSizes = isImg ? imgEl.sizes : undefined;
const imgSrcSet = isImg ? imgEl.srcset : undefined;
const imgCrossOrigin = isImg ? imgEl.crossOrigin : undefined;
const hasZoomImg = !!zoomImg?.src;
const hasImage = this.hasImage();
const labelBtnZoom = imgAlt
? `${a11yNameButtonZoom}: ${imgAlt}`
: a11yNameButtonZoom;
const isModalActive = modalState === "LOADING" ||
modalState === "LOADED";
const dataContentState = hasImage ? 'found' : 'not-found';
const dataOverlayState = modalState === "UNLOADED" || modalState === "UNLOADING"
? 'hidden'
: 'visible';
const styleContent = {
visibility: modalState === "UNLOADED" ? 'visible' : 'hidden',
};
this.styleModalImg = hasImage
? getStyleModalImg({
hasZoomImg,
imgSrc,
isSvg,
isZoomed: isZoomed && isModalActive,
loadedImgEl,
offset: zoomMargin,
shouldRefresh,
targetEl: imgEl,
})
: {};
let modalContent = null;
if (hasImage) {
const modalImg = isImg || isDiv
? React.createElement("img", { alt: imgAlt, crossOrigin: imgCrossOrigin, sizes: imgSizes, src: imgSrc, srcSet: imgSrcSet, ...isZoomImgLoaded && modalState === "LOADED" ? zoomImg : {}, "data-rmiz-modal-img": "", height: this.styleModalImg.height || undefined, id: idModalImg, ref: refModalImg, style: this.styleModalImg, width: this.styleModalImg.width || undefined })
: isSvg
? React.createElement("div", { "data-rmiz-modal-img": true, ref: refModalImg, style: this.styleModalImg })
: null;
const modalBtnUnzoom = React.createElement("button", { "aria-label": a11yNameButtonUnzoom, "data-rmiz-btn-unzoom": "", onClick: handleBtnUnzoomClick, type: "button" },
React.createElement(IconUnzoom, null));
modalContent = ZoomContent
? React.createElement(ZoomContent, { buttonUnzoom: modalBtnUnzoom, modalState: modalState, img: modalImg, isZoomImgLoaded: isZoomImgLoaded, onUnzoom: handleUnzoom })
: React.createElement(React.Fragment, null,
modalImg,
modalBtnUnzoom);
}
return (React.createElement(WrapElement, { "aria-owns": idModal, "data-rmiz": "", ref: refWrap },
React.createElement(WrapElement, { "data-rmiz-content": dataContentState, ref: refContent, style: styleContent }, children),
hasImage && React.createElement(WrapElement, { "data-rmiz-ghost": "", style: styleGhost },
React.createElement("button", { "aria-label": labelBtnZoom, "data-rmiz-btn-zoom": "", onClick: handleZoom, type: "button" },
React.createElement(IconZoom, null))),
hasImage && ReactDOM.createPortal(React.createElement("dialog", { "aria-labelledby": idModalImg, "aria-modal": "true", className: classDialog, "data-rmiz-modal": "", id: idModal, onClick: handleDialogClick, onClose: handleDialogClose, onCancel: handleDialogCancel, ref: refDialog, role: "dialog" },
React.createElement("div", { "data-rmiz-modal-overlay": dataOverlayState }),
React.createElement("div", { "data-rmiz-modal-content": "", ref: refModalContent }, modalContent)), this.getDialogContainer())));
}
componentDidMount() {
this.setId();
this.setAndTrackImg();
this.handleImgLoad();
this.UNSAFE_handleSvg();
}
componentWillUnmount() {
if (this.state.modalState !== "UNLOADED") {
this.bodyScrollEnable();
}
this.contentChangeObserver?.disconnect?.();
this.contentNotFoundChangeObserver?.disconnect?.();
this.imgElResizeObserver?.disconnect?.();
this.imgEl?.removeEventListener?.('load', this.handleImgLoad);
this.imgEl?.removeEventListener?.('click', this.handleZoom);
this.refModalImg.current?.removeEventListener?.('transitionend', this.handleImgTransitionEnd);
window.removeEventListener('wheel', this.handleWheel);
window.removeEventListener('touchstart', this.handleTouchStart);
window.removeEventListener('touchmove', this.handleTouchMove);
window.removeEventListener('touchend', this.handleTouchEnd);
window.removeEventListener('touchcancel', this.handleTouchCancel);
window.removeEventListener('resize', this.handleResize);
document.removeEventListener('keydown', this.handleKeyDown, true);
}
componentDidUpdate(prevProps, prevState) {
this.handleModalStateChange(prevState.modalState);
this.UNSAFE_handleSvg();
this.handleIfZoomChanged(prevProps.isZoomed);
}
}
ControlledBase.defaultProps = {
a11yNameButtonUnzoom: 'Minimize image',
a11yNameButtonZoom: 'Expand image',
canSwipeToUnzoom: true,
IconUnzoom: ICompress,
IconZoom: IEnlarge,
isDisabled: false,
swipeToUnzoomThreshold: 10,
wrapElement: 'div',
zoomMargin: 0,
};
function Uncontrolled({ onZoomChange, ...props }) {
const [isZoomed, setIsZoomed] = React.useState(false);
const handleZoomChange = React.useCallback((value, { event }) => {
setIsZoomed(value);
onZoomChange?.(value, { event });
}, [onZoomChange]);
return React.createElement(Controlled, { ...props, isZoomed: isZoomed, onZoomChange: handleZoomChange });
}
export { Controlled, Uncontrolled as default };