@ishitatsuyuki/oruga-next
Version:
UI components for Vue.js and CSS framework agnostic
436 lines (426 loc) • 15.1 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var vue = require('vue');
var helpers = require('./helpers.js');
var config = require('./config.js');
var BaseComponentMixin = require('./BaseComponentMixin-a03c02e3.js');
var Icon = require('./Icon-172f9998.js');
var plugins = require('./plugins-5849434f.js');
var MatchMediaMixin = require('./MatchMediaMixin-08658b15.js');
var trapFocus = require('./trapFocus-cf475da4.js');
var InstanceRegistry = require('./InstanceRegistry-14c98540.js');
/**
* Classic modal overlay to include any content you may need
* @displayName Modal
* @style _modal.scss
*/
var script = vue.defineComponent({
name: 'OModal',
components: {
[Icon.script.name]: Icon.script
},
configField: 'modal',
directives: {
trapFocus: trapFocus.directive
},
mixins: [BaseComponentMixin.BaseComponentMixin, MatchMediaMixin.MatchMediaMixin],
emits: ['update:active', 'close'],
props: {
/** Whether modal is active or not, use v-model:active to make it two-way binding */
active: Boolean,
/** Component to be injected, used to open a component modal programmatically. Close modal within the component by emitting a 'close' event — this.$emit('close') */
component: [Object, Function],
/** Text content */
content: String,
/** @ignore */
programmatic: Object,
/** @ignore */
promise: Promise,
/** Props to be binded to the injected component */
props: Object,
/** Events to be binded to the injected component */
events: Object,
/** Width of the Modal */
width: {
type: [String, Number],
default: () => {
return helpers.getValueByPath(config.getOptions(), 'modal.width', 960);
}
},
/** Enable custom style on modal content */
custom: Boolean,
/** Custom animation (transition name) */
animation: {
type: String,
default: () => {
return helpers.getValueByPath(config.getOptions(), 'modal.animation', 'zoom-out');
}
},
/**
* Can close Modal by clicking 'X', pressing escape or clicking outside
* @values escape, x, outside, button
*/
canCancel: {
type: [Array, Boolean],
default: () => {
return helpers.getValueByPath(config.getOptions(), 'modal.canCancel', ['escape', 'x', 'outside', 'button']);
}
},
/** Callback function to call after user canceled (clicked 'X' / pressed escape / clicked outside) */
onCancel: {
type: Function,
default: () => { }
},
/** Callback function to call after close (programmatically close or user canceled) */
onClose: {
type: Function,
default: () => { }
},
/**
* clip to remove the body scrollbar, keep to have a non scrollable scrollbar to avoid shifting background, but will set body to position fixed, might break some layouts
* @values keep, clip
*/
scroll: {
type: String,
default: () => {
return helpers.getValueByPath(config.getOptions(), 'modal.scroll', 'keep');
}
},
/** Display modal as full screen */
fullScreen: Boolean,
/** Trap focus inside the modal. */
trapFocus: {
type: Boolean,
default: () => {
return helpers.getValueByPath(config.getOptions(), 'modal.trapFocus', true);
}
},
ariaRole: {
type: String,
validator: (value) => {
return ['dialog', 'alertdialog'].indexOf(value) >= 0;
}
},
ariaModal: Boolean,
ariaLabel: String,
/** Destroy modal on hide */
destroyOnHide: {
type: Boolean,
default: () => {
return helpers.getValueByPath(config.getOptions(), 'modal.destroyOnHide', true);
}
},
/** Automatically focus modal when active */
autoFocus: {
type: Boolean,
default: () => {
return helpers.getValueByPath(config.getOptions(), 'modal.autoFocus', true);
}
},
/** Icon name */
closeIcon: {
type: String,
default: () => {
return helpers.getValueByPath(config.getOptions(), 'modal.closeIcon', 'close');
}
},
closeIconSize: {
type: String,
default: 'medium'
},
rootClass: [String, Function, Array],
overlayClass: [String, Function, Array],
contentClass: [String, Function, Array],
closeClass: [String, Function, Array],
fullScreenClass: [String, Function, Array],
mobileClass: [String, Function, Array],
},
data() {
return {
isActive: this.active || false,
savedScrollTop: null,
newWidth: helpers.toCssDimension(this.width),
animating: !this.active,
destroyed: !this.active
};
},
computed: {
rootClasses() {
return [
this.computedClass('rootClass', 'o-modal'),
{ [this.computedClass('mobileClass', 'o-modal--mobile')]: this.isMatchMedia },
];
},
overlayClasses() {
return [
this.computedClass('overlayClass', 'o-modal__overlay')
];
},
contentClasses() {
return [
{ [this.computedClass('contentClass', 'o-modal__content')]: !this.custom },
{ [this.computedClass('fullScreenClass', 'o-modal__content--full-screen')]: this.fullScreen }
];
},
closeClasses() {
return [
this.computedClass('closeClass', 'o-modal__close')
];
},
cancelOptions() {
return typeof this.canCancel === 'boolean'
? this.canCancel
? helpers.getValueByPath(config.getOptions(), 'modal.canCancel', ['escape', 'x', 'outside', 'button'])
: []
: this.canCancel;
},
showX() {
return this.cancelOptions.indexOf('x') >= 0;
},
customStyle() {
if (!this.fullScreen) {
return { maxWidth: this.newWidth };
}
return null;
}
},
watch: {
active(value) {
this.isActive = value;
},
isActive(value) {
if (value)
this.destroyed = false;
this.handleScroll();
this.$nextTick(() => {
if (value && this.$el && this.$el.focus && this.autoFocus) {
this.$el.focus();
}
});
}
},
methods: {
handleScroll() {
if (typeof window === 'undefined')
return;
if (this.scroll === 'clip') {
if (this.isActive) {
document.documentElement.classList.add('o-clipped');
}
else {
document.documentElement.classList.remove('o-clipped');
}
return;
}
this.savedScrollTop = !this.savedScrollTop
? document.documentElement.scrollTop
: this.savedScrollTop;
if (this.isActive) {
document.body.classList.add('o-noscroll');
}
else {
document.body.classList.remove('o-noscroll');
}
if (this.isActive) {
document.body.style.top = `-${this.savedScrollTop}px`;
return;
}
document.documentElement.scrollTop = this.savedScrollTop;
document.body.style.top = null;
this.savedScrollTop = null;
},
/**
* Close the Modal if canCancel and call the onCancel prop (function).
*/
cancel(method) {
if (this.cancelOptions.indexOf(method) < 0)
return;
this.onCancel.apply(null, arguments);
this.close({ action: 'cancel', method });
},
/**
* Emit events, and destroy modal if it's programmatic.
*/
close() {
this.isActive = false;
if (this.destroyOnHide) {
this.destroyed = true;
}
this.$emit('close');
this.$emit('update:active', false);
this.onClose.apply(null, arguments);
// Waiting for the animation complete before destroying
if (this.programmatic) {
if (this.programmatic.instances) {
this.programmatic.instances.remove(this);
}
if (this.programmatic.resolve) {
this.programmatic.resolve.apply(null, arguments);
}
window.requestAnimationFrame(() => {
helpers.removeElement(this.$el);
});
}
},
/**
* Keypress event that is bound to the document.
*/
keyPress({ key }) {
if (this.isActive && (key === 'Escape' || key === 'Esc'))
this.cancel('escape');
},
/**
* Transition after-enter hook
*/
afterEnter() {
this.animating = false;
},
/**
* Transition before-leave hook
*/
beforeLeave() {
this.animating = true;
}
},
created() {
if (typeof window !== 'undefined') {
document.addEventListener('keyup', this.keyPress);
}
},
mounted() {
if (this.programmatic) {
if (this.programmatic.instances) {
this.programmatic.instances.add(this);
}
// Insert the Modal component in body tag
// only if it's programmatic
document.body.appendChild(this.$el);
this.isActive = true;
}
else if (this.isActive)
this.handleScroll();
},
beforeUnmount() {
if (typeof window !== 'undefined') {
document.removeEventListener('keyup', this.keyPress);
// reset scroll
document.documentElement.classList.remove('o-clipped');
const savedScrollTop = !this.savedScrollTop
? document.documentElement.scrollTop
: this.savedScrollTop;
document.body.classList.remove('o-noscroll');
document.documentElement.scrollTop = savedScrollTop;
document.body.style.top = null;
}
}
});
const _hoisted_1 = {
key: 1
};
function render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_o_icon = vue.resolveComponent("o-icon");
const _directive_trap_focus = vue.resolveDirective("trap-focus");
return vue.openBlock(), vue.createBlock(vue.Transition, {
name: _ctx.animation,
"onAfter-enter": _ctx.afterEnter,
"onBefore-leave": _ctx.beforeLeave
}, {
default: vue.withCtx(() => [!_ctx.destroyed ? vue.withDirectives((vue.openBlock(), vue.createBlock("div", {
key: 0,
class: _ctx.rootClasses,
tabindex: -1,
role: _ctx.ariaRole,
"aria-label": _ctx.ariaLabel,
"aria-modal": _ctx.ariaModal
}, [vue.createVNode("div", {
class: _ctx.overlayClasses,
onClick: _cache[1] || (_cache[1] = $event => _ctx.cancel('outside'))
}, null, 2
/* CLASS */
), vue.createVNode("div", {
class: _ctx.contentClasses,
style: _ctx.customStyle
}, [_ctx.component ? (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(_ctx.component), vue.mergeProps({
key: 0
}, _ctx.props, vue.toHandlers(_ctx.events || {}), {
onClose: _ctx.close
}), null, 16
/* FULL_PROPS */
, ["onClose"])) : _ctx.content ? (vue.openBlock(), vue.createBlock("div", _hoisted_1, vue.toDisplayString(_ctx.content), 1
/* TEXT */
)) : vue.renderSlot(_ctx.$slots, "default", {
key: 2
}), _ctx.showX ? vue.withDirectives(vue.createVNode(_component_o_icon, {
key: 3,
clickable: "",
both: "",
class: _ctx.closeClasses,
icon: _ctx.closeIcon,
size: _ctx.closeIconSize,
onClick: _cache[2] || (_cache[2] = $event => _ctx.cancel('x'))
}, null, 8
/* PROPS */
, ["class", "icon", "size"]), [[vue.vShow, !_ctx.animating]]) : vue.createCommentVNode("v-if", true)], 6
/* CLASS, STYLE */
)], 10
/* CLASS, PROPS */
, ["role", "aria-label", "aria-modal"])), [[vue.vShow, _ctx.isActive], [_directive_trap_focus, _ctx.trapFocus]]) : vue.createCommentVNode("v-if", true)]),
_: 1
}, 8
/* PROPS */
, ["name", "onAfter-enter", "onBefore-leave"]);
}
script.render = render;
script.__file = "src/components/modal/Modal.vue";
let localVueInstance;
let instances = new InstanceRegistry.InstanceRegistry();
const ModalProgrammatic = {
open(params) {
let newParams;
if (typeof params === 'string') {
newParams = {
content: params
};
}
else {
newParams = params;
}
const defaultParam = {
programmatic: { instances }
};
let slot;
if (Array.isArray(newParams.content)) {
slot = newParams.content;
delete newParams.content;
}
const propsData = helpers.merge(defaultParam, newParams);
propsData.promise = new Promise((p1, p2) => {
propsData.programmatic.resolve = p1;
propsData.programmatic.reject = p2;
});
const app = localVueInstance || config.VueInstance;
const vnode = vue.createVNode(script, propsData);
vnode.appContext = app._context;
vue.render(vnode, document.createElement('div'));
if (slot) {
vnode.component.slots.default = slot;
}
return vnode.component.proxy;
},
closeAll() {
console.log(instances);
instances.walk((entry) => {
entry.close(...arguments);
});
}
};
var index = {
install(app) {
localVueInstance = app;
plugins.registerComponent(app, script);
plugins.registerComponentProgrammatic(app, 'modal', ModalProgrammatic);
}
};
exports.ModalProgrammatic = ModalProgrammatic;
exports.OModal = script;
exports.default = index;