quasar-framework
Version:
Simultaneously build desktop/mobile SPA websites & phone/tablet apps with VueJS
159 lines (151 loc) • 4.29 kB
JavaScript
import {
positionValidator,
offsetValidator,
parsePosition,
setPosition
} from '../../utils/popup'
import { frameDebounce } from '../../utils/debounce'
import { getScrollTarget } from '../../utils/scroll'
import { width, viewport } from '../../utils/dom'
import EscapeKey from '../../utils/escape-key'
import ModelToggleMixin from '../../mixins/model-toggle'
import { listenOpts } from '../../utils/event'
export default {
name: 'q-popover',
mixins: [ModelToggleMixin],
props: {
anchor: {
type: String,
default: 'bottom left',
validator: positionValidator
},
self: {
type: String,
default: 'top left',
validator: positionValidator
},
fit: Boolean,
maxHeight: String,
touchPosition: Boolean,
anchorClick: {
/*
for handling anchor outside of Popover
example: context menu component
*/
type: Boolean,
default: true
},
offset: {
type: Array,
validator: offsetValidator
},
disable: Boolean
},
watch: {
$route () {
this.hide()
}
},
computed: {
anchorOrigin () {
return parsePosition(this.anchor)
},
selfOrigin () {
return parsePosition(this.self)
}
},
render (h) {
return h('div', {
staticClass: 'q-popover scroll',
on: {
click (e) { e.stopPropagation() }
}
}, [
this.$slots.default
])
},
created () {
this.__updatePosition = frameDebounce(() => { this.reposition() })
},
mounted () {
this.$nextTick(() => {
this.anchorEl = this.$el.parentNode
this.anchorEl.removeChild(this.$el)
if (this.anchorEl.classList.contains('q-btn-inner') || this.anchorEl.classList.contains('q-if-inner')) {
this.anchorEl = this.anchorEl.parentNode
}
if (this.anchorClick) {
this.anchorEl.classList.add('cursor-pointer')
this.anchorEl.addEventListener('click', this.toggle)
}
})
if (this.value) {
this.show()
}
},
beforeDestroy () {
if (this.anchorClick && this.anchorEl) {
this.anchorEl.removeEventListener('click', this.toggle)
}
},
methods: {
__show (evt) {
document.body.appendChild(this.$el)
EscapeKey.register(() => { this.hide() })
this.scrollTarget = getScrollTarget(this.anchorEl)
this.scrollTarget.addEventListener('scroll', this.__updatePosition, listenOpts.passive)
window.addEventListener('resize', this.__updatePosition, listenOpts.passive)
this.reposition(evt, true)
clearTimeout(this.timer)
this.timer = setTimeout(() => {
document.body.addEventListener('click', this.__bodyHide, true)
document.body.addEventListener('touchstart', this.__bodyHide, true)
this.showPromise && this.showPromiseResolve()
}, 0)
},
__bodyHide (evt) {
if (
evt && evt.target &&
(this.$el.contains(evt.target) || this.anchorEl.contains(evt.target))
) {
return
}
this.hide(evt)
},
__hide () {
clearTimeout(this.timer)
document.body.removeEventListener('click', this.__bodyHide, true)
document.body.removeEventListener('touchstart', this.__bodyHide, true)
this.scrollTarget.removeEventListener('scroll', this.__updatePosition, listenOpts.passive)
window.removeEventListener('resize', this.__updatePosition, listenOpts.passive)
EscapeKey.pop()
document.body.removeChild(this.$el)
this.hidePromise && this.hidePromiseResolve()
},
reposition (event, animate) {
this.$nextTick(() => {
if (this.fit) {
this.$el.style.minWidth = width(this.anchorEl) + 'px'
}
const
{ top } = this.anchorEl.getBoundingClientRect(),
{ height } = viewport()
if (top < 0 || top > height) {
return this.hide()
}
setPosition({
event,
animate,
el: this.$el,
offset: this.offset,
anchorEl: this.anchorEl,
anchorOrigin: this.anchorOrigin,
selfOrigin: this.selfOrigin,
maxHeight: this.maxHeight,
anchorClick: this.anchorClick,
touchPosition: this.touchPosition
})
})
}
}
}