1
// Mixins
2 1
import Bootable from '../bootable'
3

4
// Utilities
5 1
import { getObjectValueByPath } from '../../util/helpers'
6 1
import mixins, { ExtractVue } from '../../util/mixins'
7 1
import { consoleWarn } from '../../util/console'
8

9
// Types
10
import Vue, { PropOptions } from 'vue'
11
import { VNode } from 'vue/types'
12

13
interface options extends Vue {
14
  $el: HTMLElement
15
  $refs: {
16
    content: HTMLElement
17
  }
18
}
19

20 1
function validateAttachTarget (val: any) {
21 1
  const type = typeof val
22

23 1
  if (type === 'boolean' || type === 'string') return true
24

25 1
  return val.nodeType === Node.ELEMENT_NODE
26
}
27

28
/* @vue/component */
29 1
export default mixins<options &
30
  /* eslint-disable indent */
31
  ExtractVue<typeof Bootable>
32
  /* eslint-enable indent */
33
>(Bootable).extend({
34
  name: 'detachable',
35

36
  props: {
37
    attach: {
38
      default: false,
39
      validator: validateAttachTarget,
40
    } as PropOptions<boolean | string | Element>,
41
    contentClass: {
42
      type: String,
43
      default: '',
44
    },
45
  },
46

47 1
  data: () => ({
48
    activatorNode: null as null | VNode | VNode[],
49
    hasDetached: false,
50
  }),
51

52
  watch: {
53 1
    attach () {
54 1
      this.hasDetached = false
55 1
      this.initDetach()
56
    },
57 1
    hasContent () {
58 1
      this.$nextTick(this.initDetach)
59
    },
60
  },
61

62 1
  beforeMount () {
63 1
    this.$nextTick(() => {
64 1
      if (this.activatorNode) {
65 1
        const activator = Array.isArray(this.activatorNode) ? this.activatorNode : [this.activatorNode]
66

67 1
        activator.forEach(node => {
68 1
          if (!node.elm) return
69 1
          if (!this.$el.parentNode) return
70

71 0
          const target = this.$el === this.$el.parentNode.firstChild
72 1
            ? this.$el
73 0
            : this.$el.nextSibling
74

75 0
          this.$el.parentNode.insertBefore(node.elm, target)
76
        })
77
      }
78
    })
79
  },
80

81 1
  mounted () {
82 1
    this.hasContent && this.initDetach()
83
  },
84

85 0
  deactivated () {
86 0
    this.isActive = false
87
  },
88

89 1
  beforeDestroy () {
90
    // IE11 Fix
91 1
    try {
92 1
      if (
93 1
        this.$refs.content &&
94 1
        this.$refs.content.parentNode
95
      ) {
96 1
        this.$refs.content.parentNode.removeChild(this.$refs.content)
97
      }
98

99 1
      if (this.activatorNode) {
100 1
        const activator = Array.isArray(this.activatorNode) ? this.activatorNode : [this.activatorNode]
101 0
        activator.forEach(node => {
102 1
          node.elm &&
103 0
            node.elm.parentNode &&
104 0
            node.elm.parentNode.removeChild(node.elm)
105
        })
106
      }
107 0
    } catch (e) { console.log(e) }
108
  },
109

110
  methods: {
111 1
    getScopeIdAttrs () {
112 1
      const scopeId = getObjectValueByPath(this.$vnode, 'context.$options._scopeId')
113

114 1
      return scopeId && {
115
        [scopeId]: '',
116
      }
117
    },
118 1
    initDetach () {
119 1
      if (this._isDestroyed ||
120 1
        !this.$refs.content ||
121 1
        this.hasDetached ||
122
        // Leave menu in place if attached
123
        // and dev has not changed target
124 1
        this.attach === '' || // If used as a boolean prop (<v-menu attach>)
125 1
        this.attach === true || // If bound to a boolean (<v-menu :attach="true">)
126 1
        this.attach === 'attach' // If bound as boolean prop in pug (v-menu(attach))
127 1
      ) return
128

129
      let target
130 1
      if (this.attach === false) {
131
        // Default, detach to app
132 1
        target = document.querySelector('[data-app]')
133 1
      } else if (typeof this.attach === 'string') {
134
        // CSS selector
135 1
        target = document.querySelector(this.attach)
136
      } else {
137
        // DOM Element
138 1
        target = this.attach
139
      }
140

141 1
      if (!target) {
142 1
        consoleWarn(`Unable to locate target ${this.attach || '[data-app]'}`, this)
143 1
        return
144
      }
145

146 1
      target.appendChild(this.$refs.content)
147

148 1
      this.hasDetached = true
149
    },
150
  },
151
})

Read our documentation on viewing source code .

Loading