UNPKG

quasar

Version:

Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time

307 lines (253 loc) 7.63 kB
import Vue from 'vue' import AnchorMixin from '../../mixins/anchor.js' import ModelToggleMixin from '../../mixins/model-toggle.js' import PortalMixin, { closePortalMenus } from '../../mixins/portal.js' import TransitionMixin from '../../mixins/transition.js' import ClickOutside from './ClickOutside.js' import { getScrollTarget } from '../../utils/scroll.js' import { create, stop, position, listenOpts, stopAndPrevent } from '../../utils/event.js' import EscapeKey from '../../utils/escape-key.js' import slot from '../../utils/slot.js' import { validatePosition, validateOffset, setPosition, parsePosition } from '../../utils/position-engine.js' export default Vue.extend({ name: 'QMenu', mixins: [ AnchorMixin, ModelToggleMixin, PortalMixin, TransitionMixin ], directives: { ClickOutside }, props: { persistent: Boolean, autoClose: Boolean, separateClosePopup: Boolean, noRefocus: Boolean, noFocus: Boolean, fit: Boolean, cover: Boolean, square: Boolean, anchor: { type: String, validator: validatePosition }, self: { type: String, validator: validatePosition }, offset: { type: Array, validator: validateOffset }, touchPosition: Boolean, maxHeight: { type: String, default: null }, maxWidth: { type: String, default: null } }, computed: { horizSide () { return this.$q.lang.rtl ? 'right' : 'left' }, anchorOrigin () { return parsePosition( this.anchor || ( this.cover === true ? `center middle` : `bottom ${this.horizSide}` ) ) }, selfOrigin () { return this.cover === true ? this.anchorOrigin : parsePosition(this.self || `top ${this.horizSide}`) }, menuClass () { return this.square === true ? ' q-menu--square' : '' }, hideOnRouteChange () { return this.persistent !== true } }, methods: { focus () { let node = this.__portal !== void 0 && this.__portal.$refs !== void 0 ? this.__portal.$refs.inner : void 0 if (node !== void 0 && node.contains(document.activeElement) !== true) { node = node.querySelector('[autofocus]') || node node.focus() } }, __show (evt) { // IE can have null document.activeElement this.__refocusTarget = this.noRefocus === false && document.activeElement !== null ? document.activeElement : void 0 EscapeKey.register(this, () => { if (this.persistent !== true) { this.$emit('escape-key') this.hide() } }) this.__showPortal() this.__configureScrollTarget() this.absoluteOffset = void 0 if (evt !== void 0 && (this.touchPosition || this.contextMenu)) { const pos = position(evt) if (pos.left !== void 0) { const { top, left } = this.anchorEl.getBoundingClientRect() this.absoluteOffset = { left: pos.left - left, top: pos.top - top } } } if (this.unwatch === void 0) { this.unwatch = this.$watch('$q.screen.width', this.updatePosition) } this.$el.dispatchEvent(create('popup-show', { bubbles: true })) // IE can have null document.activeElement if (this.noFocus !== true && document.activeElement !== null) { document.activeElement.blur() } this.__nextTick(() => { this.updatePosition() this.noFocus !== true && this.focus() }) this.__setTimeout(() => { this.$emit('show', evt) }, 300) }, __hide (evt) { this.__anchorCleanup(true) // check null for IE if ( this.__refocusTarget !== void 0 && this.__refocusTarget !== null && ( // menu was hidden from code or ESC plugin evt === void 0 || // menu was not closed from a mouse or touch clickOutside evt.qClickOutside !== true ) ) { this.__refocusTarget.focus() } this.$el.dispatchEvent(create('popup-hide', { bubbles: true })) this.__setTimeout(() => { this.__hidePortal() this.$emit('hide', evt) }, 300) }, __anchorCleanup (hiding) { this.absoluteOffset = void 0 if (this.unwatch !== void 0) { this.unwatch() this.unwatch = void 0 } if (hiding === true || this.showing === true) { EscapeKey.pop(this) this.__unconfigureScrollTarget() } }, __unconfigureScrollTarget () { if (this.scrollTarget !== void 0) { this.scrollTarget.removeEventListener('scroll', this.updatePosition, listenOpts.passive) } window.removeEventListener('scroll', this.updatePosition, listenOpts.passive) }, __configureScrollTarget () { if (this.anchorEl !== void 0) { this.scrollTarget = getScrollTarget(this.anchorEl) this.scrollTarget.addEventListener('scroll', this.updatePosition, listenOpts.passive) if (this.scrollTarget !== window) { window.addEventListener('scroll', this.updatePosition, listenOpts.passive) } } }, __onAutoClose (e) { closePortalMenus(this, e) this.$listeners.click !== void 0 && this.$emit('click', e) }, updatePosition () { if (this.__portal === void 0) { return } const el = this.__portal.$el if (el.nodeType === 8) { // IE replaces the comment with delay setTimeout(this.updatePosition, 25) return } setPosition({ el, offset: this.offset, anchorEl: this.anchorEl, anchorOrigin: this.anchorOrigin, selfOrigin: this.selfOrigin, absoluteOffset: this.absoluteOffset, fit: this.fit, cover: this.cover, maxHeight: this.maxHeight, maxWidth: this.maxWidth }) }, __onClickOutside (e) { if (this.persistent !== true && this.showing === true) { const targetClassList = e.target.classList this.hide(e) if ( // always prevent touch event e.type === 'touchstart' || // prevent click if it's on a dialog backdrop targetClassList.contains('q-dialog__backdrop') ) { stopAndPrevent(e) } return true } }, __renderPortal (h) { const on = { ...this.$listeners, // stop propagating these events from children input: stop, 'popup-show': stop, 'popup-hide': stop } if (this.autoClose === true) { on.click = this.__onAutoClose } return h('transition', { props: { name: this.transition } }, [ this.showing === true ? h('div', { ref: 'inner', staticClass: 'q-menu scroll' + this.menuClass, class: this.contentClass, style: this.contentStyle, attrs: { tabindex: -1, ...this.$attrs }, on, directives: [{ name: 'click-outside', value: this.__onClickOutside, arg: this.anchorEl }] }, slot(this, 'default')) : null ]) } }, mounted () { this.__processModelChange(this.value) }, beforeDestroy () { // When the menu is destroyed while open we can only emit the event on anchorEl if (this.showing === true && this.anchorEl !== void 0) { this.anchorEl.dispatchEvent( create('popup-hide', { bubbles: true }) ) } } })