UNPKG

quasar-framework

Version:

Build responsive SPA, SSR, PWA, Hybrid Mobile Apps and Electron apps, all simultaneously using the same codebase

311 lines (296 loc) 7.48 kB
import EscapeKey from '../../utils/escape-key.js' import ModelToggleMixin from '../../mixins/model-toggle.js' import preventScroll from '../../utils/prevent-scroll.js' const positions = { top: 'items-start justify-center with-backdrop', bottom: 'items-end justify-center with-backdrop', right: 'items-center justify-end with-backdrop', left: 'items-center justify-start with-backdrop' } const positionCSS = process.env.THEME === 'mat' ? { maxHeight: '80vh', height: 'auto' } : { maxHeight: '80vh', height: 'auto', boxShadow: 'none' } function additionalCSS (position) { let css = {} if (['left', 'right'].includes(position)) { css.maxWidth = '90vw' } if (['left', 'top'].includes(position)) { css.borderTopLeftRadius = 0 } if (['right', 'top'].includes(position)) { css.borderTopRightRadius = 0 } if (['left', 'bottom'].includes(position)) { css.borderBottomLeftRadius = 0 } if (['right', 'bottom'].includes(position)) { css.borderBottomRightRadius = 0 } return css } let modals = { responsive: 0, maximized: 0 } export default { name: 'QModal', mixins: [ModelToggleMixin], provide () { return { __qmodal: { register: layout => { if (this.layout !== layout) { this.layout = layout } }, unregister: layout => { if (this.layout === layout) { this.layout = null } } } } }, props: { position: { type: String, default: '', validator (val) { return val === '' || ['top', 'bottom', 'left', 'right'].includes(val) } }, transition: String, enterClass: String, leaveClass: String, positionClasses: { type: String, default: 'flex-center' }, contentClasses: [Object, Array, String], contentCss: [Object, Array, String], noBackdropDismiss: { type: Boolean, default: false }, noEscDismiss: { type: Boolean, default: false }, noRouteDismiss: Boolean, noRefocus: Boolean, minimized: Boolean, maximized: Boolean }, data () { return { layout: null } }, watch: { $route () { if (!this.noRouteDismiss) { this.hide() } }, maximized (newV, oldV) { this.__register(false, oldV) this.__register(true, newV) }, minimized (newV, oldV) { this.__register(false, this.maximized, oldV) this.__register(true, this.maximized, newV) } }, computed: { modalClasses () { const cls = this.position ? positions[this.position] : this.positionClasses if (this.maximized) { return ['maximized', cls] } else if (this.minimized) { return ['minimized', cls] } return cls }, contentClassesCalc () { if (this.layout) { return [this.contentClasses, 'column no-wrap'] } return this.contentClasses }, transitionProps () { if (this.position) { return { name: `q-modal-${this.position}` } } if (this.enterClass || this.leaveClass) { return { enterActiveClass: this.enterClass, leaveActiveClass: this.leaveClass } } return { name: this.transition || 'q-modal' } }, modalCss () { if (this.position) { const css = Array.isArray(this.contentCss) ? this.contentCss : [this.contentCss] css.unshift(Object.assign( {}, positionCSS, additionalCSS(this.position) )) return css } return this.contentCss } }, methods: { __dismiss () { if (this.noBackdropDismiss) { this.__shake() return } this.hide().then(() => { this.$emit('dismiss') }) }, __show () { if (!this.noRefocus) { this.__refocusTarget = document.activeElement } document.body.appendChild(this.$el) this.__register(true) preventScroll(true) EscapeKey.register(() => { if (this.noEscDismiss) { this.__shake() } else { this.$emit('escape-key') this.hide().then(() => { this.$emit('dismiss') }) } }) const content = this.$refs.content if (this.$q.platform.is.ios) { // workaround the iOS hover/touch issue content.click() } content.scrollTop = 0 ;['modal-scroll', 'layout-view'].forEach(c => { [].slice.call(content.getElementsByClassName(c)).forEach(el => { el.scrollTop = 0 }) }) this.$nextTick(() => content && content.focus()) }, __hide () { this.__cleanup() if (!this.noRefocus && this.__refocusTarget) { this.__refocusTarget.focus() !this.__refocusTarget.classList.contains('q-if') && this.__refocusTarget.blur() } }, __cleanup () { EscapeKey.pop() preventScroll(false) this.__register(false) }, __stopPropagation (e) { e.stopPropagation() }, __register (opening, maximized = this.maximized, minimized = this.minimized) { let state = opening ? { action: 'add', step: 1 } : { action: 'remove', step: -1 } if (maximized) { modals.maximized += state.step if (!opening && modals.maximized > 0) { return } document.body.classList[state.action]('q-maximized-modal') } else if (!minimized) { modals.responsive += state.step if (!opening && modals.responsive > 0) { return } document.body.classList[state.action]('q-responsive-modal') } }, __shake () { this.$el.classList.remove('animate-shake') this.$el.classList.add('animate-shake') clearTimeout(this.shakeTimeout) this.shakeTimeout = setTimeout(() => { this.$el.classList.remove('animate-shake') }, 150) } }, mounted () { this.value && this.show() }, beforeDestroy () { clearTimeout(this.shakeTimeout) this.$el.remove() this.showing && this.__cleanup() }, render (h) { return h('transition', { props: this.transitionProps, on: { afterEnter: () => { this.showPromise && this.showPromiseResolve() }, enterCancelled: () => { this.showPromise && this.showPromiseReject() this.$el.remove() }, afterLeave: () => { this.hidePromise && this.hidePromiseResolve() this.$el.remove() }, leaveCancelled: () => { this.hidePromise && this.hidePromiseReject() } } }, [ h('div', { staticClass: 'modal fullscreen row', 'class': this.modalClasses, directives: [{ name: 'show', value: this.showing }] }, [ h('div', { staticClass: 'modal-backdrop absolute-full', on: { click: this.__dismiss } }), h('div', { ref: 'content', staticClass: 'modal-content', style: this.modalCss, 'class': this.contentClassesCalc, attrs: { tabindex: -1 }, on: { click: this.__stopPropagation, touchstart: this.__stopPropagation } }, this.$slots.default) ]) ]) } }