UNPKG

@nextcloud/vue

Version:
444 lines (443 loc) 17.7 kB
import '../assets/NcModal.css'; import { getCurrentInstance, warn, defineComponent, useCssVars, useModel, useTemplateRef, onUnmounted, watch, toRef, ref, watchEffect, computed, useSlots, onMounted, openBlock, createBlock, Teleport, createVNode, Transition, withCtx, withDirectives, createElementVNode, mergeProps, unref, createElementBlock, toDisplayString, createCommentVNode, normalizeClass, renderSlot, withModifiers, vShow, mergeModels, nextTick } from "vue"; import { z as mdiPause, A as mdiPlay, b as mdiClose, B as mdiChevronLeft, c as mdiChevronRight } from "./mdi.mjs"; import { useIntervalFn, useSwipe } from "@vueuse/core"; import { createFocusTrap } from "focus-trap"; import { N as NcActions } from "./NcActions.mjs"; import { N as NcButton } from "./NcButton.mjs"; import { N as NcIconSvgWrapper } from "./NcIconSvgWrapper.mjs"; import "../composables/useFormatDateTime/index.mjs"; import { useHotKey } from "../composables/useHotKey/index.mjs"; import "../composables/useIsDarkTheme/index.mjs"; import "../composables/useIsFullscreen/index.mjs"; import "../composables/useIsMobile/index.mjs"; import { r as register, D as t38, A as t20, a as t } from "./_l10n.mjs"; import { c as createElementId } from "./createElementId.mjs"; import { g as getTrapStack } from "./focusTrap.mjs"; import { i as isRtl } from "./rtl.mjs"; import { _ as _export_sfc } from "./_plugin-vue_export-helper.mjs"; /*! * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ function getSameNodeParent(instance) { if (!instance.parent) { return null; } if ("vapor" in instance || "vapor" in instance.parent) { warn("Vapor instances are not supported in useScopeIdAttrs :("); return null; } if (instance.parent.subTree !== instance.vnode) { return null; } return instance.parent; } function getSameNodeAncestors(instance) { const ancestors = [instance]; let parent = getSameNodeParent(instance); while (parent) { ancestors.push(parent); parent = getSameNodeParent(parent); } return ancestors; } function useScopeIdAttrs() { const instance = getCurrentInstance(); if (!instance) { throw new Error("useScopeId must be called within a setup context"); } const sameNodeAncestors = getSameNodeAncestors(instance); const scopeIds = sameNodeAncestors.map((instance2) => instance2.vnode.scopeId).filter(Boolean); const scopeIdAttrs = Object.fromEntries(scopeIds.map((scopeId) => [scopeId, ""])); return scopeIdAttrs; } register(t20, t38); const _hoisted_1 = ["aria-labelledby", "aria-describedby"]; const _hoisted_2 = ["data-theme-light", "data-theme-dark"]; const _hoisted_3 = ["id"]; const _hoisted_4 = { class: "icons-menu" }; const _hoisted_5 = ["title"]; const _hoisted_6 = ["id"]; const _hoisted_7 = { class: "modal-container__content" }; const _sfc_main = /* @__PURE__ */ defineComponent({ ...{ inheritAttrs: false }, __name: "NcModal", props: /* @__PURE__ */ mergeModels({ name: { default: "" }, hasPrevious: { type: Boolean }, hasNext: { type: Boolean }, outTransition: { type: Boolean }, enableSlideshow: { type: Boolean }, slideshowDelay: { default: 5e3 }, slideshowPaused: { type: Boolean }, disableSwipe: { type: Boolean }, spreadNavigation: { type: Boolean }, size: { default: "normal" }, noClose: { type: Boolean }, closeOnClickOutside: { type: Boolean }, dark: { type: Boolean }, lightBackdrop: { type: Boolean }, container: { default: "body" }, closeButtonOutside: { type: Boolean }, additionalTrapElements: { default: () => [] }, inlineActions: { default: 0 }, labelId: { default: "" }, setReturnFocus: { default: void 0 } }, { "show": { type: Boolean, ...{ default: true } }, "showModifiers": {}, "slideshowRunning": { type: Boolean, ...{ default: false } }, "slideshowRunningModifiers": {} }), emits: /* @__PURE__ */ mergeModels(["next", "previous", "close", "update:show"], ["update:show", "update:slideshowRunning"]), setup(__props, { emit: __emit }) { useCssVars((_ctx) => ({ "v11237964": numHeaderActions.value, "v5117887e": cssSlideshowDelay.value })); const showModal = useModel(__props, "show"); const runSlideshow = useModel(__props, "slideshowRunning"); const props = __props; const emit = __emit; const scopeIdAttrs = useScopeIdAttrs(); const modalId = createElementId(); const maskElement = useTemplateRef("mask"); let focusTrap; onUnmounted(() => clearFocusTrap()); watch(() => props.additionalTrapElements, (elements) => { if (focusTrap) { focusTrap.updateContainerElements([maskElement.value, ...elements]); } }); const { isActive: isPlaying, pause: stopSlideshow, resume: startSlideshow } = useIntervalFn(nextSlide, toRef(() => props.slideshowDelay), { immediate: false }); const animationKey = ref(0); watchEffect(() => { if (runSlideshow.value && !props.slideshowPaused) { startSlideshow(); } else if (isPlaying.value) { stopSlideshow(); } }); const cssSlideshowDelay = computed(() => `${props.slideshowDelay}ms`); const { stop: stopSwipe } = useSwipe(maskElement, { onSwipeEnd: handleSwipe }); onUnmounted(stopSwipe); useHotKey("Escape", () => { const trapStack = getTrapStack(); if (trapStack.at(-1) === focusTrap) { close(); } }, { allowInModal: true }); useHotKey(["ArrowLeft", "ArrowRight"], (event) => { if (document.activeElement && !maskElement.value.contains(document.activeElement)) { return; } if (event.key === "ArrowLeft" !== isRtl) { previousSlide(); } else { nextSlide(); } }, { allowInModal: true }); const slots = useSlots(); const numHeaderActions = computed(() => { let actions = 0; if (props.hasNext && props.enableSlideshow) { actions++; } if (!props.noClose && props.closeButtonOutside) { actions++; } if (slots.actions) { actions++; } return actions; }); const hasHeader = computed(() => props.name.trim() !== "" || numHeaderActions.value > 0); onMounted(() => { if (!props.name && !props.labelId) { warn("[NcModal] You need either set the name or set a `labelId` for accessibility."); } }); function nextSlide(event) { if (!props.hasNext) { runSlideshow.value = false; return; } if (event && isPlaying.value) { restartSlideshow(); } emit("next", event); } function previousSlide(event) { if (!props.hasPrevious) { return; } if (event && isPlaying.value) { restartSlideshow(); } emit("previous", event); } function handleSwipe(e, direction) { if (!props.disableSwipe) { if (direction !== "left" && direction !== "right") { return; } if (direction === "left" !== isRtl) { nextSlide(e); } else { previousSlide(e); } } } function restartSlideshow() { stopSlideshow(); startSlideshow(); animationKey.value++; } function close(event) { if (props.noClose) { return; } showModal.value = false; setTimeout(() => { emit("close", event); }, 300); } function handleClickModalWrapper(event) { if (props.closeOnClickOutside) { close(event); } } async function useFocusTrap() { if (focusTrap) { return; } await nextTick(); const options = { allowOutsideClick: true, fallbackFocus: maskElement.value, trapStack: getTrapStack(), // Esc can be used without stop in content or additionalTrapElements where it should not deactivate modal's focus trap. // Focus trap is deactivated on modal close anyway. escapeDeactivates: false, setReturnFocus: props.setReturnFocus }; focusTrap = createFocusTrap([maskElement.value, ...props.additionalTrapElements], options); focusTrap.activate(); } function clearFocusTrap() { if (!focusTrap) { return; } focusTrap?.deactivate(); focusTrap = void 0; } return (_ctx, _cache) => { return openBlock(), createBlock(Teleport, { disabled: __props.container === null, to: __props.container }, [ createVNode(Transition, { name: "fade", appear: "", onAfterEnter: useFocusTrap, onBeforeLeave: clearFocusTrap }, { default: withCtx(() => [ withDirectives(createElementVNode("div", mergeProps({ ..._ctx.$attrs, ...unref(scopeIdAttrs) }, { ref: "mask", class: ["modal-mask", { "modal-mask--opaque": __props.dark || __props.closeButtonOutside || __props.hasPrevious || __props.hasNext, "modal-mask--light": __props.lightBackdrop }], role: "dialog", "aria-modal": "true", "aria-labelledby": __props.labelId || `modal-name-${unref(modalId)}`, "aria-describedby": "modal-description-" + unref(modalId), tabindex: "-1" }), [ createVNode(Transition, { name: "fade-visibility", appear: "" }, { default: withCtx(() => [ hasHeader.value ? (openBlock(), createElementBlock("div", { key: 0, class: "modal-header", "data-theme-light": __props.lightBackdrop, "data-theme-dark": !__props.lightBackdrop }, [ __props.name.trim() !== "" ? (openBlock(), createElementBlock("h2", { key: 0, id: "modal-name-" + unref(modalId), class: "modal-header__name" }, toDisplayString(__props.name), 9, _hoisted_3)) : createCommentVNode("", true), createElementVNode("div", _hoisted_4, [ __props.hasNext && __props.enableSlideshow ? (openBlock(), createElementBlock("button", { key: 0, class: normalizeClass(["play-pause-icons", { "play-pause-icons--paused": __props.slideshowPaused }]), title: unref(isPlaying) ? unref(t)("Pause slideshow") : unref(t)("Start slideshow"), type: "button", onClick: _cache[0] || (_cache[0] = ($event) => runSlideshow.value = !runSlideshow.value) }, [ createVNode(NcIconSvgWrapper, { class: "play-pause-icons__icon", inline: "", name: unref(isPlaying) ? unref(t)("Pause slideshow") : unref(t)("Start slideshow"), path: unref(isPlaying) ? unref(mdiPause) : unref(mdiPlay) }, null, 8, ["name", "path"]), unref(isPlaying) ? (openBlock(), createElementBlock("svg", { key: `${unref(modalId)}-animation-${animationKey.value}`, class: "progress-ring", height: "50", width: "50" }, [..._cache[1] || (_cache[1] = [ createElementVNode("circle", { class: "progress-ring__circle", stroke: "white", "stroke-width": "2", fill: "transparent", r: "15", cx: "25", cy: "25" }, null, -1) ])])) : createCommentVNode("", true) ], 10, _hoisted_5)) : createCommentVNode("", true), createVNode(NcActions, { class: "header-actions", inline: __props.inlineActions }, { default: withCtx(() => [ renderSlot(_ctx.$slots, "actions", {}, void 0, true) ]), _: 3 }, 8, ["inline"]), !__props.noClose && __props.closeButtonOutside ? (openBlock(), createBlock(NcButton, { key: 1, "aria-label": unref(t)("Close"), class: "header-close", variant: "tertiary", onClick: close }, { icon: withCtx(() => [ createVNode(NcIconSvgWrapper, { path: unref(mdiClose) }, null, 8, ["path"]) ]), _: 1 }, 8, ["aria-label"])) : createCommentVNode("", true) ]) ], 8, _hoisted_2)) : createCommentVNode("", true) ]), _: 3 }), createVNode(Transition, { name: `modal-${__props.outTransition ? "out" : "in"}`, appear: "" }, { default: withCtx(() => [ withDirectives(createElementVNode("div", { class: normalizeClass(["modal-wrapper", [ `modal-wrapper--${__props.size}`, { "modal-wrapper--spread-navigation": __props.spreadNavigation, "modal-wrapper--no-header": !hasHeader.value } ]]), onMousedown: withModifiers(handleClickModalWrapper, ["self"]) }, [ createVNode(Transition, { name: "fade-visibility", appear: "" }, { default: withCtx(() => [ withDirectives(createVNode(NcButton, { "aria-label": unref(t)("Previous"), class: "prev", variant: "tertiary-no-background", onClick: previousSlide }, { icon: withCtx(() => [ createVNode(NcIconSvgWrapper, { directional: "", path: unref(mdiChevronLeft), size: 40 }, null, 8, ["path"]) ]), _: 1 }, 8, ["aria-label"]), [ [vShow, __props.hasPrevious] ]) ]), _: 1 }), createElementVNode("div", { id: "modal-description-" + unref(modalId), class: "modal-container" }, [ createElementVNode("div", _hoisted_7, [ renderSlot(_ctx.$slots, "default", {}, void 0, true) ]), !__props.noClose && !__props.closeButtonOutside ? (openBlock(), createBlock(NcButton, { key: 0, "aria-label": unref(t)("Close"), class: "modal-container__close", variant: "tertiary", onClick: close }, { icon: withCtx(() => [ createVNode(NcIconSvgWrapper, { path: unref(mdiClose) }, null, 8, ["path"]) ]), _: 1 }, 8, ["aria-label"])) : createCommentVNode("", true) ], 8, _hoisted_6), createVNode(Transition, { name: "fade-visibility", appear: "" }, { default: withCtx(() => [ withDirectives(createVNode(NcButton, { "aria-label": unref(t)("Next"), class: "next", variant: "tertiary-no-background", onClick: nextSlide }, { icon: withCtx(() => [ createVNode(NcIconSvgWrapper, { directional: "", path: unref(mdiChevronRight), size: 40 }, null, 8, ["path"]) ]), _: 1 }, 8, ["aria-label"]), [ [vShow, __props.hasNext] ]) ]), _: 1 }) ], 34), [ [vShow, showModal.value] ]) ]), _: 3 }, 8, ["name"]) ], 16, _hoisted_1), [ [vShow, showModal.value] ]) ]), _: 3 }) ], 8, ["disabled", "to"]); }; } }); const NcModal = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-8a639337"]]); export { NcModal as N }; //# sourceMappingURL=NcModal.mjs.map