UNPKG

maz-ui

Version:

A standalone components library for Vue.Js 3 & Nuxt.Js 3

322 lines (321 loc) 14.9 kB
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 };