UNPKG

vuetify

Version:

Vue Material Component Framework

152 lines (127 loc) 3.66 kB
// Mixins import Bootable from '../bootable' // Utilities import { getObjectValueByPath } from '../../util/helpers' import mixins, { ExtractVue } from '../../util/mixins' import { consoleWarn } from '../../util/console' // Types import Vue, { PropOptions } from 'vue' import { VNode } from 'vue/types' interface options extends Vue { $el: HTMLElement $refs: { content: HTMLElement } } function validateAttachTarget (val: any) { const type = typeof val if (type === 'boolean' || type === 'string') return true return val.nodeType === Node.ELEMENT_NODE } /* @vue/component */ export default mixins<options & /* eslint-disable indent */ ExtractVue<typeof Bootable> /* eslint-enable indent */ >(Bootable).extend({ name: 'detachable', props: { attach: { default: false, validator: validateAttachTarget, } as PropOptions<boolean | string | Element>, contentClass: { type: String, default: '', }, }, data: () => ({ activatorNode: null as null | VNode | VNode[], hasDetached: false, }), watch: { attach () { this.hasDetached = false this.initDetach() }, hasContent () { this.$nextTick(this.initDetach) }, }, beforeMount () { this.$nextTick(() => { if (this.activatorNode) { const activator = Array.isArray(this.activatorNode) ? this.activatorNode : [this.activatorNode] activator.forEach(node => { if (!node.elm) return if (!this.$el.parentNode) return const target = this.$el === this.$el.parentNode.firstChild ? this.$el : this.$el.nextSibling this.$el.parentNode.insertBefore(node.elm, target) }) } }) }, mounted () { this.hasContent && this.initDetach() }, deactivated () { this.isActive = false }, beforeDestroy () { // IE11 Fix try { if ( this.$refs.content && this.$refs.content.parentNode ) { this.$refs.content.parentNode.removeChild(this.$refs.content) } if (this.activatorNode) { const activator = Array.isArray(this.activatorNode) ? this.activatorNode : [this.activatorNode] activator.forEach(node => { node.elm && node.elm.parentNode && node.elm.parentNode.removeChild(node.elm) }) } } catch (e) { console.log(e) } /* eslint-disable-line no-console */ }, methods: { getScopeIdAttrs () { const scopeId = getObjectValueByPath(this.$vnode, 'context.$options._scopeId') return scopeId && { [scopeId]: '', } }, initDetach () { if (this._isDestroyed || !this.$refs.content || this.hasDetached || // Leave menu in place if attached // and dev has not changed target this.attach === '' || // If used as a boolean prop (<v-menu attach>) this.attach === true || // If bound to a boolean (<v-menu :attach="true">) this.attach === 'attach' // If bound as boolean prop in pug (v-menu(attach)) ) return let target if (this.attach === false) { // Default, detach to app target = document.querySelector('[data-app]') } else if (typeof this.attach === 'string') { // CSS selector target = document.querySelector(this.attach) } else { // DOM Element target = this.attach } if (!target) { consoleWarn(`Unable to locate target ${this.attach || '[data-app]'}`, this) return } target.appendChild(this.$refs.content) this.hasDetached = true }, }, })