UNPKG

@nextcloud/vue

Version:
626 lines (625 loc) 20.3 kB
import '../assets/NcModal-BzkYPomo.css'; import { m as mdiChevronRight, j as mdiChevronLeft } from "../chunks/mdi-DXu6GWVJ.mjs"; import { useSwipe } from "@vueuse/core"; import { createFocusTrap } from "focus-trap"; import Vue, { useCssVars } from "vue"; import { C as Close } from "../chunks/Close-BtLPUSdO.mjs"; import { n as normalizeComponent } from "../chunks/_plugin-vue2_normalizer-DU4iP6Vu.mjs"; import { r as register, J as t34, n as t18, a as t } from "../chunks/_l10n-BEfeU7gr.mjs"; import { g as getTrapStack } from "../chunks/focusTrap-HJQ4pqHV.mjs"; import { G as GenRandomId } from "../chunks/GenRandomId-F5ebeBB_.mjs"; import { N as NcActions } from "../chunks/NcActions-C832pWHO.mjs"; import { N as NcButton } from "../chunks/NcButton-CWPBzbcC.mjs"; import { N as NcIconSvgWrapper } from "../chunks/NcIconSvgWrapper-BreCg8pX.mjs"; import { S as ScopeComponent } from "../chunks/ScopeComponent-305QOaqN.mjs"; const _sfc_main$2 = { name: "PauseIcon", emits: ["click"], props: { title: { type: String }, fillColor: { type: String, default: "currentColor" }, size: { type: Number, default: 24 } } }; var _sfc_render$2 = function render() { var _vm = this, _c = _vm._self._c; return _c("span", _vm._b({ staticClass: "material-design-icon pause-icon", attrs: { "aria-hidden": _vm.title ? null : "true", "aria-label": _vm.title, "role": "img" }, on: { "click": function($event) { return _vm.$emit("click", $event); } } }, "span", _vm.$attrs, false), [_c("svg", { staticClass: "material-design-icon__svg", attrs: { "fill": _vm.fillColor, "width": _vm.size, "height": _vm.size, "viewBox": "0 0 24 24" } }, [_c("path", { attrs: { "d": "M14,19H18V5H14M6,19H10V5H6V19Z" } }, [_vm.title ? _c("title", [_vm._v(_vm._s(_vm.title))]) : _vm._e()])])]); }; var _sfc_staticRenderFns$2 = []; var __component__$2 = /* @__PURE__ */ normalizeComponent( _sfc_main$2, _sfc_render$2, _sfc_staticRenderFns$2, false, null, null ); const Pause = __component__$2.exports; const _sfc_main$1 = { name: "PlayIcon", emits: ["click"], props: { title: { type: String }, fillColor: { type: String, default: "currentColor" }, size: { type: Number, default: 24 } } }; var _sfc_render$1 = function render2() { var _vm = this, _c = _vm._self._c; return _c("span", _vm._b({ staticClass: "material-design-icon play-icon", attrs: { "aria-hidden": _vm.title ? null : "true", "aria-label": _vm.title, "role": "img" }, on: { "click": function($event) { return _vm.$emit("click", $event); } } }, "span", _vm.$attrs, false), [_c("svg", { staticClass: "material-design-icon__svg", attrs: { "fill": _vm.fillColor, "width": _vm.size, "height": _vm.size, "viewBox": "0 0 24 24" } }, [_c("path", { attrs: { "d": "M8,5.14V19.14L19,12.14L8,5.14Z" } }, [_vm.title ? _c("title", [_vm._v(_vm._s(_vm.title))]) : _vm._e()])])]); }; var _sfc_staticRenderFns$1 = []; var __component__$1 = /* @__PURE__ */ normalizeComponent( _sfc_main$1, _sfc_render$1, _sfc_staticRenderFns$1, false, null, null ); const Play = __component__$1.exports; register(t18, t34); function timer(callback, delay) { let id; let started; let remaining = delay; let running; this.start = function() { running = true; started = /* @__PURE__ */ new Date(); id = setTimeout(callback, remaining); }; this.pause = function() { running = false; clearTimeout(id); remaining -= /* @__PURE__ */ new Date() - started; }; this.clear = function() { running = false; clearTimeout(id); remaining = 0; }; this.getTimeLeft = function() { if (running) { this.pause(); this.start(); } return remaining; }; this.getStateRunning = function() { return running; }; this.start(); } const __default__ = { name: "NcModal", components: { Close, Pause, Play, NcActions, NcButton, NcIconSvgWrapper }, props: { /** * Name to be shown with the modal */ name: { type: String, default: "" }, /** * Declare if a previous slide is available */ hasPrevious: { type: Boolean, default: false }, /** * Declare if a next slide is available */ hasNext: { type: Boolean, default: false }, /** * Declare if hiding the modal should be animated */ outTransition: { type: Boolean, default: false }, /** * Declare if the slideshow functionality should be enabled */ enableSlideshow: { type: Boolean, default: false }, /** * Declare the slide interval */ slideshowDelay: { type: Number, default: 5e3 }, /** * Allow to pause an ongoing slideshow */ slideshowPaused: { type: Boolean, default: false }, /** * Enable swipe between slides * * @deprecated Will be removed in next version - use `disableSwipe` instead */ enableSwipe: { type: Boolean, // eslint-disable-next-line vue/no-boolean-default default: true }, /** * Disable swipe between slides */ disableSwipe: { type: Boolean, default: false }, /** * Enable spread navigation */ spreadNavigation: { type: Boolean, default: false }, /** * Defines the modal size. * Default is 'normal'. * Available are 'small', 'normal', 'large' and 'full'. * All sizes except 'small' change automatically to full-screen on mobile. */ size: { type: String, default: "normal", validator: (size) => { return ["small", "normal", "large", "full"].includes(size); } }, /** * Do not show the close button for the dialog. * * @default false */ noClose: { type: Boolean, default: false }, /** * Set to false to no show a close button on the dialog * * @deprecated - Use `noClose` instead. Will be removed in v9. * @default true */ canClose: { type: Boolean, // eslint-disable-next-line vue/no-boolean-default default: true }, /** * Close the modal if the user clicked outside the modal * Only relevant if `canClose` is set to true. */ closeOnClickOutside: { type: Boolean, // eslint-disable-next-line vue/no-boolean-default default: true }, /** * Makes the modal backdrop opaque if true. * Will be overwritten if some buttons are shown outside. */ dark: { type: Boolean, default: false }, /** * Set light backdrop. Makes the modal header appear light. */ lightBackdrop: { type: Boolean, default: false }, /** * Selector for the modal container, pass `null` to prevent automatic container mounting */ container: { type: [String, null], default: "body" }, /** * Pass in false if you want the modal 'close' button to be displayed * outside the modal boundaries, in the top right corner of the window */ closeButtonContained: { type: Boolean, // eslint-disable-next-line vue/no-boolean-default default: true }, /** * Additional elements to add to the focus trap */ additionalTrapElements: { type: Array, default: () => [] }, /** * Display x items inline * * @see Actions component usage */ inlineActions: { type: Number, default: 0 }, /** * The current open property of the modal */ show: { type: Boolean, // eslint-disable-next-line vue/no-boolean-default default: void 0 }, /** * Id of the element that labels the dialog (the name) * Not needed if the `name` prop is set, but if no name is set you need to provide the ID of an element to label the dialog for accessibility. */ labelId: { type: String, default: "" }, /** * Set element to return focus to after focus trap deactivation * * @type {import('focus-trap').FocusTargetValueOrFalse} */ setReturnFocus: { default: void 0, type: [Boolean, HTMLElement, SVGElement, String] } }, emits: [ "previous", "next", "close", "update:show" ], setup() { return { mdiChevronLeft, mdiChevronRight }; }, data() { return { mc: null, playing: false, slideshowTimeout: null, focusTrap: null, randId: GenRandomId(), internalShow: true }; }, computed: { /** * slide show delay to set to CSS */ cssSlideshowDelay() { return `${this.slideshowDelay}ms`; }, /** * True if there are any buttons shown on the backdrop or a name (for accessibility) */ forceDarkBackdrop() { return !this.noClose && this.canClose && !this.closeButtonContained || this.hasNext || this.hasPrevious || this.modalName !== "" || Boolean(this.$slots.actions); }, /** * Trimmed modal name */ modalName() { return this.name.trim(); }, /** * ID of the element to label the modal */ modalLabelId() { return this.labelId || `modal-name-${this.randId}`; }, showModal() { return this.show === void 0 ? this.internalShow : this.show; }, modalTransitionName() { return `modal-${this.outTransition ? "out" : "in"}`; }, playPauseName() { return this.playing ? t("Pause slideshow") : t("Start slideshow"); }, closeButtonAriaLabel() { return t("Close"); }, prevButtonAriaLabel() { return t("Previous"); }, nextButtonAriaLabel() { return t("Next"); } }, watch: { /** * Handle play/pause of an ongoing slideshow * * @param {boolean} paused is the player paused */ slideshowPaused(paused) { if (this.slideshowTimeout) { if (paused) { this.slideshowTimeout.pause(); } else { this.slideshowTimeout.start(); } } }, additionalTrapElements(elements) { if (this.focusTrap) { const contentContainer = this.$refs.mask; this.focusTrap.updateContainerElements([contentContainer, ...elements]); } } }, beforeMount() { window.addEventListener("keydown", this.handleKeydown); }, beforeDestroy() { window.removeEventListener("keydown", this.handleKeydown); this.mc.stop(); }, mounted() { if (!this.name && !this.labelId) { Vue.util.warn("[NcModal] You need either set the name or set a `labelId` for accessibility."); } this.useFocusTrap(); this.mc = useSwipe(this.$refs.mask, { onSwipeEnd: this.handleSwipe }); if (this.container) { if (this.container === "body") { document.body.insertBefore(this.$el, document.body.lastChild); } else { const container = document.querySelector(this.container); container.appendChild(this.$el); } } }, destroyed() { this.clearFocusTrap(); this.$el.remove(); }, methods: { t, // Events emitters previous(event) { if (this.hasPrevious) { if (event) { this.resetSlideshow(); } this.$emit("previous", event); } }, next(event) { if (this.hasNext) { if (event) { this.resetSlideshow(); } this.$emit("next", event); } }, close(data) { if (!this.noClose && this.canClose) { this.internalShow = false; this.$emit("update:show", false); setTimeout(() => { this.$emit("close", data); }, 300); } }, /** * Handle click on modal wrapper * If `closeOnClickOutside` is set the modal will be closed * * @param {MouseEvent} event The click event */ handleClickModalWrapper(event) { if (this.closeOnClickOutside) { this.close(event); } }, /** * @param {KeyboardEvent} event - keyboard event */ handleKeydown(event) { if (event.key === "Escape") { const trapStack = getTrapStack(); if (trapStack.length > 0 && trapStack[trapStack.length - 1] !== this.focusTrap) { return; } return this.close(event); } const arrowHandlers = { ArrowLeft: this.previous, ArrowRight: this.next }; if (arrowHandlers[event.key]) { if (document.activeElement && !this.$el.contains(document.activeElement)) { return; } return arrowHandlers[event.key](event); } }, /** * handle the swipe event * * @param {TouchEvent} e The touch event * @param {import('@vueuse/core').SwipeDirection} direction Swipe direction */ handleSwipe(e, direction) { if (this.enableSwipe && !this.disableSwipe) { if (direction === "left") { this.next(e); } else if (direction === "right") { this.previous(e); } } }, /** * Toggle the slideshow state */ togglePlayPause() { this.playing = !this.playing; if (this.playing) { this.handleSlideshow(); } else { this.clearSlideshowTimeout(); } }, /** * Reset the slideshow timer and keep going if it was on */ resetSlideshow() { this.playing = !this.playing; this.clearSlideshowTimeout(); this.$nextTick(function() { this.togglePlayPause(); }); }, /** * Handle the slideshow timer and next event */ handleSlideshow() { this.playing = true; if (this.hasNext) { this.slideshowTimeout = new timer(() => { this.next(); this.handleSlideshow(); }, this.slideshowDelay); } else { this.playing = false; this.clearSlideshowTimeout(); } }, /** * Clear slideshowTimeout if ongoing */ clearSlideshowTimeout() { if (this.slideshowTimeout) { this.slideshowTimeout.clear(); } }, /** * Add focus trap for accessibility. */ async useFocusTrap() { if (!this.showModal || this.focusTrap) { return; } const contentContainer = this.$refs.mask; await this.$nextTick(); const options = { allowOutsideClick: true, fallbackFocus: contentContainer, 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: this.setReturnFocus }; this.focusTrap = createFocusTrap([contentContainer, ...this.additionalTrapElements], options); this.focusTrap.activate(); }, clearFocusTrap() { if (!this.focusTrap) { return; } this.focusTrap?.deactivate(); this.focusTrap = null; } } }; const __injectCSSVars__ = () => { useCssVars((_vm, _setup) => ({ "afe18836": _vm.cssSlideshowDelay })); }; const __setup__ = __default__.setup; __default__.setup = __setup__ ? (props, ctx) => { __injectCSSVars__(); return __setup__(props, ctx); } : __injectCSSVars__; const _sfc_main = __default__; var _sfc_render = function render3() { var _vm = this, _c = _vm._self._c; return _c("transition", { attrs: { "name": "fade", "appear": "" }, on: { "after-enter": _vm.useFocusTrap, "before-leave": _vm.clearFocusTrap } }, [_c("div", { directives: [{ name: "show", rawName: "v-show", value: _vm.showModal, expression: "showModal" }], ref: "mask", staticClass: "modal-mask", class: { "modal-mask--opaque": _vm.dark || _vm.forceDarkBackdrop, "modal-mask--light": _vm.lightBackdrop }, attrs: { "role": "dialog", "aria-modal": "true", "aria-labelledby": _vm.modalLabelId, "aria-describedby": "modal-description-" + _vm.randId, "tabindex": "-1" } }, [_c("transition", { attrs: { "name": "fade-visibility", "appear": "" } }, [_c("div", { staticClass: "modal-header", attrs: { "data-theme-light": _vm.lightBackdrop, "data-theme-dark": !_vm.lightBackdrop } }, [_vm.modalName ? _c("h2", { staticClass: "modal-header__name", attrs: { "id": "modal-name-" + _vm.randId } }, [_vm._v(" " + _vm._s(_vm.modalName) + " ")]) : _vm._e(), _c("div", { staticClass: "icons-menu" }, [_vm.hasNext && _vm.enableSlideshow ? _c("button", { staticClass: "play-pause-icons", class: { "play-pause-icons--paused": _vm.slideshowPaused }, attrs: { "title": _vm.playPauseName, "type": "button" }, on: { "click": _vm.togglePlayPause } }, [!_vm.playing ? _c("Play", { staticClass: "play-pause-icons__play", attrs: { "size": 20 } }) : _c("Pause", { staticClass: "play-pause-icons__pause", attrs: { "size": 20 } }), _c("span", { staticClass: "hidden-visually" }, [_vm._v(" " + _vm._s(_vm.playPauseName) + " ")]), _vm.playing ? _c("svg", { staticClass: "progress-ring", attrs: { "height": "50", "width": "50" } }, [_c("circle", { staticClass: "progress-ring__circle", attrs: { "stroke": "white", "stroke-width": "2", "fill": "transparent", "r": "15", "cx": "25", "cy": "25" } })]) : _vm._e()], 1) : _vm._e(), _c("NcActions", { staticClass: "header-actions", attrs: { "inline": _vm.inlineActions } }, [_vm._t("actions")], 2), !_vm.noClose && _vm.canClose && !_vm.closeButtonContained ? _c("NcButton", { staticClass: "header-close", attrs: { "aria-label": _vm.closeButtonAriaLabel, "variant": "tertiary" }, on: { "click": _vm.close }, scopedSlots: _vm._u([{ key: "icon", fn: function() { return [_c("Close", { attrs: { "size": 20 } })]; }, proxy: true }], null, false, 2121748766) }) : _vm._e()], 1)])]), _c("transition", { attrs: { "name": _vm.modalTransitionName, "appear": "" } }, [_c("div", { directives: [{ name: "show", rawName: "v-show", value: _vm.showModal, expression: "showModal" }], staticClass: "modal-wrapper", class: [ `modal-wrapper--${_vm.size}`, { "modal-wrapper--spread-navigation": _vm.spreadNavigation } ], on: { "mousedown": function($event) { if ($event.target !== $event.currentTarget) return null; return _vm.handleClickModalWrapper.apply(null, arguments); } } }, [_c("transition", { attrs: { "name": "fade-visibility", "appear": "" } }, [_c("NcButton", { directives: [{ name: "show", rawName: "v-show", value: _vm.hasPrevious, expression: "hasPrevious" }], staticClass: "prev", attrs: { "aria-label": _vm.prevButtonAriaLabel, "variant": "tertiary-no-background" }, on: { "click": _vm.previous }, scopedSlots: _vm._u([{ key: "icon", fn: function() { return [_c("NcIconSvgWrapper", { attrs: { "directional": "", "path": _vm.mdiChevronLeft, "size": 40 } })]; }, proxy: true }]) })], 1), _c("div", { staticClass: "modal-container", attrs: { "id": "modal-description-" + _vm.randId } }, [_c("div", { staticClass: "modal-container__content" }, [_vm._t("default")], 2), !_vm.noClose && _vm.canClose && _vm.closeButtonContained ? _c("NcButton", { staticClass: "modal-container__close", attrs: { "aria-label": _vm.closeButtonAriaLabel, "variant": "tertiary" }, on: { "click": _vm.close }, scopedSlots: _vm._u([{ key: "icon", fn: function() { return [_c("Close", { attrs: { "size": 20 } })]; }, proxy: true }], null, false, 2121748766) }) : _vm._e()], 1), _c("transition", { attrs: { "name": "fade-visibility", "appear": "" } }, [_c("NcButton", { directives: [{ name: "show", rawName: "v-show", value: _vm.hasNext, expression: "hasNext" }], staticClass: "next", attrs: { "aria-label": _vm.nextButtonAriaLabel, "variant": "tertiary-no-background" }, on: { "click": _vm.next }, scopedSlots: _vm._u([{ key: "icon", fn: function() { return [_c("NcIconSvgWrapper", { attrs: { "directional": "", "path": _vm.mdiChevronRight, "size": 40 } })]; }, proxy: true }]) })], 1)], 1)])], 1)]); }; var _sfc_staticRenderFns = []; var __component__ = /* @__PURE__ */ normalizeComponent( _sfc_main, _sfc_render, _sfc_staticRenderFns, false, null, "c2daf832" ); const NcModal = __component__.exports; ScopeComponent(NcModal); export { NcModal as default }; //# sourceMappingURL=NcModal.mjs.map