@nextcloud/vue
Version:
Nextcloud vue components
626 lines (625 loc) • 20.3 kB
JavaScript
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