maz-ui
Version:
A standalone components library for Vue.Js 3 & Nuxt.Js 3
322 lines (321 loc) • 14.9 kB
JavaScript
import { useMountComponent } from "../composables/useMountComponent.js";
import { defineComponent, ref, reactive, computed, watch, onMounted, onBeforeUnmount, createElementBlock, openBlock, withKeys, withModifiers, createCommentVNode, createElementVNode, createVNode, unref, withDirectives, normalizeClass, vShow } from "vue";
import { MazChevronLeft, MazXMark } from "@maz-ui/icons";
import { c as checkAvailability } from "./checkAvailability.aTpZidex.js";
import MazSpinner from "../components/MazSpinner.js";
import { _ as _export_sfc } from "./_plugin-vue_export-helper.B--vMWp3.js";
import '../assets/fullscreen-img.BGt9mY2g.css';const _hoisted_1 = ["onKeypress"], _hoisted_2 = { class: "m-fullscreen-img-scroller" }, _hoisted_3 = ["src", "alt"], _sfc_main = /* @__PURE__ */ defineComponent({
__name: "MazFullscreenImg",
props: {
src: {},
clickedElementBounds: { default: void 0 },
offset: { default: void 0 },
animation: { default: () => ({
duration: 300,
easing: "ease-in-out"
}) },
openInstanceClass: { default: "m-fullscreen-img-instance" },
clickedElement: {},
destroy: { type: Function, default: void 0 },
alt: { default: void 0 },
zoom: { type: Boolean, default: !0 }
},
emits: ["close", "previous", "next", "before-close"],
setup(__props, { emit: __emit }) {
const props = __props, emits = __emit, imageLoaded = ref(!1), showLoader = ref(!1), loadedOnce = ref(!1), hasMultipleInstances = ref(!1), isZoomed = ref(!1), animationState = reactive({
running: !1,
ended: !1
}), currentClickedElement = ref(props.clickedElement), currentClickedElementBounds = computed(() => props.clickedElement.getBoundingClientRect()), isLandscapeImage = ref(), currentSrc = ref(props.src), currentAlt = ref(props.alt), FullscreenImgElement = ref(), ImgElement = ref(), hideImage = ref(!0), imageZoomClasses = computed(() => ({
"--is-zoomed": isZoomed.value,
"--invisible": hideImage.value,
"--absolute": !isZoomed.value
}));
function onImageLoaded() {
ImgElement.value && (isLandscapeImage.value = ImgElement.value?.naturalWidth > ImgElement.value?.naturalHeight), imageLoaded.value = !0, showLoader.value = !1, loadedOnce.value = !0;
}
watch(
loadedOnce,
(value) => {
value && openFullscreen();
},
{ immediate: !0 }
);
function close() {
emits("before-close"), closeFullscreen();
}
function keydownLister(e) {
e.key === "Escape" && (e.preventDefault(), close()), (e.key === "ArrowLeft" || e.key === "ArrowRight") && (e.preventDefault(), nextPreviousImage(e.key === "ArrowRight" ? "next" : "previous"));
}
function addClassToDocument() {
document.documentElement.classList.add("--m-fullscreen-open");
}
function removeClassFromDocument() {
document.documentElement.classList.remove("--m-fullscreen-open");
}
function getAllInstances() {
return [...document.querySelectorAll(".m-fullscreen-img-instance")];
}
function getNewInstanceIndex(allInstances, newInstanceIndex) {
return newInstanceIndex < 0 ? allInstances.length - 1 : newInstanceIndex >= allInstances.length ? 0 : newInstanceIndex;
}
function useNextInstance(currentInstance, nextInstance) {
currentInstance.classList.remove(props.openInstanceClass), nextInstance.classList.add(props.openInstanceClass);
const src = nextInstance.getAttribute("data-src"), alt = nextInstance.getAttribute("data-alt");
currentAlt.value = alt, currentSrc.value = src ?? currentSrc.value;
}
function nextPreviousImage(which) {
hideImage.value = !0;
const currentInstance = document.querySelector(
`.m-fullscreen-img-instance.${props.openInstanceClass}`
);
if (currentInstance) {
const allInstances = getAllInstances(), currentInstanceIndex = allInstances.indexOf(currentInstance), newInstanceIndex = which === "next" ? currentInstanceIndex + 1 : currentInstanceIndex - 1, nextInstance = allInstances[getNewInstanceIndex(allInstances, newInstanceIndex)];
currentClickedElement.value = nextInstance, nextInstance && useNextInstance(currentInstance, nextInstance), emits(which), imageLoaded.value = !1, showLoader.value = !0, checkAvailability(() => imageLoaded.value === !0, () => {
hideImage.value = !1, isZoomed.value ? setZoomStyles() : setEndAnimationStyles();
}, {
expectedValue: !0,
interval: 100,
maxAttempts: 50
});
}
}
function setZoomStyles() {
const imgElement = ImgElement.value;
if (!imgElement) {
console.error("[maz-ui](vFullscreenImg) ImgElement is not defined");
return;
}
imgElement.style.removeProperty("max-width"), imgElement.style.removeProperty("max-height"), imgElement?.style.removeProperty("top"), imgElement?.style.removeProperty("left"), isLandscapeImage.value ? (imgElement.style.height = "100vh", imgElement.style.removeProperty("width")) : (imgElement.style.width = "100vw", imgElement.style.removeProperty("height"));
}
function toggleZoom() {
isZoomed.value ? (isZoomed.value = !isZoomed.value, setEndAnimationStyles()) : (isZoomed.value = !isZoomed.value, setZoomStyles());
}
function runAnimation(frames) {
animationState.running = !0, hideImage.value = !1;
const animation = ImgElement.value?.animate(frames, {
duration: props.animation.duration,
easing: props.animation.easing
});
if (!animation) {
console.error("[maz-ui](vFullscreenImg) animation is not defined"), animationState.running = !1, animationState.ended = !0;
return;
}
return animation;
}
function getPositionsOfClikedElement(offset = props.offset ?? 0) {
const width = currentClickedElement.value.clientWidth || 1, height = currentClickedElement.value.clientHeight || 1, windowWidth = window.innerWidth, windowHeight = window.innerHeight, scale = Math.min(
(windowWidth - 2 * offset) / width,
(windowHeight - 2 * offset) / height
), centerX = (windowWidth - width * scale) / 2, centerY = (windowHeight - height * scale) / 2;
return {
centerX,
centerY,
width,
height,
scale
};
}
function getAnimationFrames({ trigger }) {
const { width, height, scale, centerX, centerY } = getPositionsOfClikedElement(), { top, left, width: clickedElementWidth, height: clickedElementHeight } = currentClickedElementBounds.value, frames = [
{
top: `${top}px`,
left: `${left}px`,
width: `${clickedElementWidth}px`,
height: `${clickedElementHeight}px`,
opacity: 0
},
{
top: `${centerY}px`,
left: `${centerX}px`,
width: `${width * scale}px`,
height: `${height * scale}px`,
opacity: 1
}
];
return {
frames: trigger === "open" ? frames : frames.reverse()
};
}
function setEndAnimationStyles() {
const { height, width, scale } = getPositionsOfClikedElement(), finalStyles = isLandscapeImage.value ? {
width: `${width * scale}px`,
maxHeight: `${height * scale}px`
} : {
height: `${height * scale}px`,
maxWidth: `${width * scale}px`
};
if (!ImgElement.value) {
console.error("[maz-ui](vFullscreenImg) ImgElement is not defined");
return;
}
isLandscapeImage.value ? (ImgElement.value.style.removeProperty("height"), ImgElement.value.style.removeProperty("maxHeight")) : (ImgElement.value.style.removeProperty("width"), ImgElement.value.style.removeProperty("maxWidth")), Object.assign(ImgElement.value.style, finalStyles);
}
function openFullscreen() {
const { frames } = getAnimationFrames({
trigger: "open"
}), openAnimation = runAnimation(frames);
if (!openAnimation) {
console.error("[maz-ui](vFullscreenImg) open animation is not defined"), setEndAnimationStyles();
return;
}
openAnimation.onfinish = () => {
setEndAnimationStyles(), animationState.running = !1, animationState.ended = !0;
};
}
function closeFullscreen() {
const { frames } = getAnimationFrames({
trigger: "close"
}), closeAnimation = runAnimation(frames);
function onFinish() {
emits("close"), FullscreenImgElement.value?.remove(), props.destroy?.(), animationState.running = !1, animationState.ended = !0;
}
if (!closeAnimation) {
console.error("[maz-ui](vFullscreenImg) close animation is not defined"), onFinish();
return;
}
closeAnimation.onfinish = onFinish;
}
function onResizeWindow() {
isZoomed.value || setEndAnimationStyles();
}
return onMounted(() => {
showLoader.value = !0, document.addEventListener("keydown", keydownLister), window.addEventListener("resize", onResizeWindow), addClassToDocument(), hasMultipleInstances.value = getAllInstances().length > 1;
}), onBeforeUnmount(() => {
document.removeEventListener("keydown", keydownLister), window.removeEventListener("resize", onResizeWindow), removeClassFromDocument();
}), (_ctx, _cache) => (openBlock(), createElementBlock("div", {
ref_key: "FullscreenImgElement",
ref: FullscreenImgElement,
role: "button",
class: "m-fullscreen-img m-reset-css",
tabindex: "0",
onClick: withModifiers(close, ["stop"]),
onKeypress: withKeys(withModifiers(close, ["prevent"]), ["esc"])
}, [
loadedOnce.value && hasMultipleInstances.value ? (openBlock(), createElementBlock("button", {
key: 0,
type: "button",
class: "m-fullscreen-btn --next",
onClick: _cache[0] || (_cache[0] = withModifiers(($event) => nextPreviousImage("next"), ["stop"]))
}, [
createVNode(unref(MazChevronLeft), { class: "maz-rotate-180" })
])) : createCommentVNode("", !0),
loadedOnce.value && hasMultipleInstances.value ? (openBlock(), createElementBlock("button", {
key: 1,
type: "button",
class: "m-fullscreen-btn --previous",
onClick: _cache[1] || (_cache[1] = withModifiers(($event) => nextPreviousImage("previous"), ["stop"]))
}, [
createVNode(unref(MazChevronLeft))
])) : createCommentVNode("", !0),
createElementVNode("button", {
type: "button",
class: "m-fullscreen-btn --close",
onClick: close
}, [
createVNode(unref(MazXMark))
]),
createElementVNode("div", _hoisted_2, [
createElementVNode("img", {
ref_key: "ImgElement",
ref: ImgElement,
src: currentSrc.value,
alt: currentAlt.value ?? void 0,
tabindex: "0",
class: normalizeClass([imageZoomClasses.value]),
onLoad: onImageLoaded,
onClick: _cache[2] || (_cache[2] = withModifiers(($event) => __props.zoom && toggleZoom(), ["stop"]))
}, null, 42, _hoisted_3),
withDirectives(createVNode(MazSpinner, { class: "m-fullscreen-img-loader" }, null, 512), [
[vShow, showLoader.value]
])
])
], 40, _hoisted_1));
}
}), MazFullscreenImg = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-3fb56303"]]), STATE_OPEN_CLASS = "m-fullscreen-is-open";
class FullscreenImgHandler {
options = void 0;
defaultOptions = {
scaleOnHover: !1,
blurOnHover: !1,
disabled: !1,
zoom: !0,
offset: 80,
animation: {
duration: 300,
easing: "ease-in-out"
}
};
mouseEnterListener = void 0;
mouseLeaveListener = void 0;
renderPreviewListener = void 0;
buildOptions(el, binding) {
const options = typeof binding.value == "object" ? binding.value : { src: binding.value, alt: void 0 }, src = options?.src ?? this.getImgSrc(el), alt = options?.alt ?? this.getImgAlt(el);
return {
...this.defaultOptions,
...options,
src,
alt
};
}
get allInstances() {
return [...document.querySelectorAll(".m-fullscreen-img-instance")];
}
getImgSrc(el) {
const imgSrc = this.options?.src || el.getAttribute("src") || el.getAttribute("data-src");
if (!imgSrc)
throw new Error(
'[maz-ui](fullscreen-img) src of image must be provided by `v-fullscreen=""`, `v-fullscreen="{ src: "" }"`, `src=""` or `data-src=""` atributes'
);
return imgSrc;
}
getImgAlt(el) {
return this.options?.alt || el.getAttribute("alt") || el.getAttribute("data-alt");
}
create(el, binding) {
if (this.options = this.buildOptions(el, binding), this.options.disabled)
return;
el.style.cursor = "move", (this.options.scaleOnHover || this.options.blurOnHover) && (el.style.transition = "all 200ms ease-in-out"), el.classList.add("m-fullscreen-img-instance"), el.setAttribute("data-src", this.getImgSrc(el));
const alt = this.getImgAlt(el);
alt && el.setAttribute("data-alt", alt), this.mouseEnterListener = () => this.mouseEnter(el), this.mouseLeaveListener = () => this.mouseLeave(el), this.renderPreviewListener = () => this.renderPreview(el), el.addEventListener("mouseenter", this.mouseEnterListener), el.addEventListener("mouseleave", this.mouseLeaveListener), el.addEventListener("click", this.renderPreviewListener);
}
update(el, binding) {
this.options = this.buildOptions(el, binding);
}
remove(el) {
this.mouseEnterListener && el.removeEventListener("mouseenter", this.mouseEnterListener), this.mouseLeaveListener && el.removeEventListener("mouseleave", this.mouseLeaveListener), this.renderPreviewListener && el.removeEventListener("click", this.renderPreviewListener), el.classList.remove("m-fullscreen-img-instance"), el.style.cursor = "";
}
renderPreview(el) {
return el.classList.add(STATE_OPEN_CLASS), useMountComponent(MazFullscreenImg, {
props: {
...this.options,
src: this.options?.src || this.getImgSrc(el),
alt: this.options?.alt || this.getImgAlt(el),
openInstanceClass: STATE_OPEN_CLASS,
clickedElement: el,
clickedElementBounds: el.getBoundingClientRect()
},
element: document.body
});
}
mouseLeave(el) {
this.options?.scaleOnHover && (el.style.transform = ""), this.options?.blurOnHover && (el.style.filter = ""), el.style.zIndex = "";
}
mouseEnter(el) {
el.style.zIndex = "1", this.options?.scaleOnHover && (el.style.transform = "scale(1.04)"), this.options?.blurOnHover && (el.style.filter = "blur(3px)");
}
}
let instance;
const directive = {
mounted(el, binding) {
return instance = new FullscreenImgHandler(), instance.create(el, binding);
},
updated(el, binding) {
return instance.update(el, binding);
},
unmounted(el) {
return instance.remove(el);
}
};
export {
directive as d
};