diff --git a/packages/vuetify/src/components/VDialog/VDialog.js b/packages/vuetify/src/components/VDialog/VDialog.js index a5bc467c92e..3086720c85e 100644 --- a/packages/vuetify/src/components/VDialog/VDialog.js +++ b/packages/vuetify/src/components/VDialog/VDialog.js @@ -110,9 +110,11 @@ export default { } }, - mounted () { - this.isBooted = this.isActive - this.isActive && this.show() + beforeMount () { + this.$nextTick(() => { + this.isBooted = this.isActive + this.isActive && this.show() + }) }, beforeDestroy () { @@ -173,6 +175,30 @@ export default { }, onKeydown (e) { this.$emit('keydown', e) + }, + genActivator () { + if (!this.$slots.activator && !this.$scopedSlots.activator) return null + + const listeners = this.disabled ? {} : { + click: e => { + e.stopPropagation() + if (!this.disabled) this.isActive = !this.isActive + } + } + + if (this.$scopedSlots.activator) { + const activator = this.$scopedSlots.activator({ on: listeners }) + this.activatorNode = activator + return activator + } + + return this.$createElement('div', { + staticClass: 'v-dialog__activator', + 'class': { + 'v-dialog__activator--disabled': this.disabled + }, + on: listeners + }, [this.$slots.activator]) } }, @@ -204,20 +230,7 @@ export default { } } - if (this.$slots.activator) { - children.push(h('div', { - staticClass: 'v-dialog__activator', - 'class': { - 'v-dialog__activator--disabled': this.disabled - }, - on: this.disabled ? {} : { - click: e => { - e.stopPropagation() - this.isActive = !this.isActive - } - } - }, [this.$slots.activator])) - } + children.push(this.genActivator()) let dialog = h('div', data, this.showLazyContent(this.$slots.default)) if (this.transition) { diff --git a/packages/vuetify/src/components/VMenu/mixins/menu-activator.js b/packages/vuetify/src/components/VMenu/mixins/menu-activator.js index e1d52c0bbcc..283d610dd3c 100644 --- a/packages/vuetify/src/components/VMenu/mixins/menu-activator.js +++ b/packages/vuetify/src/components/VMenu/mixins/menu-activator.js @@ -11,12 +11,12 @@ export default { methods: { activatorClickHandler (e) { if (this.openOnClick && !this.isActive) { - this.getActivator().focus() + this.getActivator(e).focus() this.isActive = true this.absoluteX = e.clientX this.absoluteY = e.clientY } else if (this.closeOnClick && this.isActive) { - this.getActivator().blur() + this.getActivator(e).blur() this.isActive = false } }, diff --git a/packages/vuetify/src/components/VMenu/mixins/menu-generators.js b/packages/vuetify/src/components/VMenu/mixins/menu-generators.js index b350cec16c9..8a0db92c14a 100644 --- a/packages/vuetify/src/components/VMenu/mixins/menu-generators.js +++ b/packages/vuetify/src/components/VMenu/mixins/menu-generators.js @@ -2,28 +2,38 @@ export default { methods: { genActivator () { - if (!this.$slots.activator) return null + if (!this.$slots.activator && !this.$scopedSlots.activator) return null - const options = { - staticClass: 'v-menu__activator', - 'class': { - 'v-menu__activator--active': this.hasJustFocused || this.isActive, - 'v-menu__activator--disabled': this.disabled - }, - ref: 'activator', - on: {} - } + const listeners = {} if (!this.disabled) { if (this.openOnHover) { - options.on['mouseenter'] = this.mouseEnterHandler - options.on['mouseleave'] = this.mouseLeaveHandler + listeners.mouseenter = this.mouseEnterHandler + listeners.mouseleave = this.mouseLeaveHandler } else if (this.openOnClick) { - options.on['click'] = this.activatorClickHandler + listeners.click = this.activatorClickHandler } } - return this.$createElement('div', options, this.$slots.activator) + if (this.$scopedSlots.activator) { + const activator = this.$scopedSlots.activator({ on: listeners }) + this.activatorNode = activator + return activator + } + + if (this.$slots.activator) { + const options = { + staticClass: 'v-menu__activator', + 'class': { + 'v-menu__activator--active': this.hasJustFocused || this.isActive, + 'v-menu__activator--disabled': this.disabled + }, + ref: 'activator', + on: listeners + } + + return this.$createElement('div', options, this.$slots.activator) + } }, genTransition () { diff --git a/packages/vuetify/src/components/VTooltip/VTooltip.js b/packages/vuetify/src/components/VTooltip/VTooltip.js index ce749bfe295..d332d082611 100644 --- a/packages/vuetify/src/components/VTooltip/VTooltip.js +++ b/packages/vuetify/src/components/VTooltip/VTooltip.js @@ -54,17 +54,18 @@ export default { calculatedLeft () { const { activator, content } = this.dimensions const unknown = !this.bottom && !this.left && !this.top && !this.right + const activatorLeft = this.isAttached ? activator.offsetLeft : activator.left let left = 0 if (this.top || this.bottom || unknown) { left = ( - activator.left + + activatorLeft + (activator.width / 2) - (content.width / 2) ) } else if (this.left || this.right) { left = ( - activator.left + + activatorLeft + (this.right ? activator.width : -content.width) + (this.right ? 10 : -10) ) @@ -77,17 +78,18 @@ export default { }, calculatedTop () { const { activator, content } = this.dimensions + const activatorTop = this.isAttached ? activator.offsetTop : activator.top let top = 0 if (this.top || this.bottom) { top = ( - activator.top + + activatorTop + (this.bottom ? activator.height : -content.height) + (this.bottom ? 10 : -10) ) } else if (this.left || this.right) { top = ( - activator.top + + activatorTop + (activator.height / 2) - (content.height / 2) ) @@ -130,8 +132,10 @@ export default { } }, - mounted () { - this.value && this.callActivate() + beforeMount () { + this.$nextTick(() => { + this.value && this.callActivate() + }) }, methods: { @@ -141,6 +145,30 @@ export default { this.updateDimensions() // Start the transition requestAnimationFrame(this.startTransition) + }, + genActivator () { + const listeners = this.disabled ? {} : { + mouseenter: e => { + this.getActivator(e) + this.runDelay('open') + }, + mouseleave: e => { + this.getActivator(e) + this.runDelay('close') + } + } + + if (this.$scopedSlots.activator) { + const activator = this.$scopedSlots.activator({ on: listeners }) + this.activatorNode = activator + return activator + } + if (this.$slots.activator) { + return this.$createElement('span', { + on: listeners, + ref: 'activator' + }, this.$slots.activator) + } } }, @@ -169,17 +197,7 @@ export default { name: this.computedTransition } }, [tooltip]), - h('span', { - on: this.disabled ? {} : { - mouseenter: () => { - this.runDelay('open') - }, - mouseleave: () => { - this.runDelay('close') - } - }, - ref: 'activator' - }, this.$slots.activator) + this.genActivator() ]) } } diff --git a/packages/vuetify/src/mixins/detachable.js b/packages/vuetify/src/mixins/detachable.js index 792b97a66fe..9b53735e08a 100644 --- a/packages/vuetify/src/mixins/detachable.js +++ b/packages/vuetify/src/mixins/detachable.js @@ -38,6 +38,14 @@ export default { hasContent: 'initDetach' }, + beforeMount () { + this.$nextTick(() => { + if (this.activatorNode && this.activatorNode.elm) { + this.$el.parentNode.insertBefore(this.activatorNode.elm, this.$el) + } + }) + }, + mounted () { !this.lazy && this.initDetach() }, diff --git a/packages/vuetify/src/mixins/menuable.js b/packages/vuetify/src/mixins/menuable.js index 6e3eb34c10a..28afb9cdcbd 100644 --- a/packages/vuetify/src/mixins/menuable.js +++ b/packages/vuetify/src/mixins/menuable.js @@ -3,6 +3,7 @@ import Vue from 'vue' import Positionable from './positionable' import Stackable from './stackable' +import { consoleError } from '../util/console' /* eslint-disable object-property-newline */ const dimensions = { @@ -106,11 +107,12 @@ export default Vue.extend({ computedLeft () { const a = this.dimensions.activator const c = this.dimensions.content - const minWidth = a.width < c.width ? c.width : a.width + const minWidth = Math.max(a.width, c.width) let left = 0 - left += this.left ? a.left - (minWidth - a.width) : a.left - + if (this.left) left += a.left - (minWidth - a.width) + if (this.isAttached) left += a.offsetLeft + else left += a.left if (this.offsetX) left += this.left ? -a.width : a.width if (this.nudgeLeft) left -= parseInt(this.nudgeLeft) if (this.nudgeRight) left += parseInt(this.nudgeRight) @@ -120,9 +122,11 @@ export default Vue.extend({ computedTop () { const a = this.dimensions.activator const c = this.dimensions.content - let top = this.top ? a.bottom - c.height : a.top + let top = 0 - if (!this.isAttached) top += this.pageYOffset + if (this.top) top += a.bottom - c.height + if (this.isAttached) top += a.offsetTop + else top += a.top + this.pageYOffset if (this.offsetY) top += this.top ? -a.height : a.height if (this.nudgeTop) top -= parseInt(this.nudgeTop) if (this.nudgeBottom) top += parseInt(this.nudgeBottom) @@ -130,7 +134,7 @@ export default Vue.extend({ return top }, hasActivator () { - return !!this.$slots.activator || this.activator || this.inputActivator + return !!this.$slots.activator || !!this.$scopedSlots.activator || this.activator || this.inputActivator }, isAttached () { return this.attach !== false @@ -156,6 +160,7 @@ export default Vue.extend({ absolutePosition () { return { offsetTop: 0, + offsetLeft: 0, scrollHeight: 0, top: this.positionY || this.absoluteY, bottom: this.positionY || this.absoluteY, @@ -250,7 +255,7 @@ export default Vue.extend({ } }, deactivate () {}, - getActivator () { + getActivator (e) { if (this.inputActivator) { return this.$el.querySelector('.v-input__slot') } @@ -261,9 +266,20 @@ export default Vue.extend({ : this.activator } - return this.$refs.activator.children.length > 0 - ? this.$refs.activator.children[0] - : this.$refs.activator + if (this.$refs.activator) { + return this.$refs.activator.children.length > 0 + ? this.$refs.activator.children[0] + : this.$refs.activator + } + + if (e) { + this.activatedBy = e.currentTarget || e.target + return this.activatedBy + } + + if (this.activatedBy) return this.activatedBy + + consoleError('No activator found') }, getInnerHeight () { if (!this.hasWindow) return 0 @@ -299,9 +315,7 @@ export default Vue.extend({ height: Math.round(rect.height) } }, - measure (el, selector) { - el = selector ? el.querySelector(selector) : el - + measure (el) { if (!el || !this.hasWindow) return null const rect = this.getRoundedBoundedClientRect(el) @@ -343,9 +357,20 @@ export default Vue.extend({ const dimensions = {} // Activator should already be shown - dimensions.activator = !this.hasActivator || this.absolute - ? this.absolutePosition() - : this.measure(this.getActivator()) + if (!this.hasActivator || this.absolute) { + dimensions.activator = this.absolutePosition() + } else { + const activator = this.getActivator() + dimensions.activator = this.measure(activator) + dimensions.activator.offsetLeft = activator.offsetLeft + if (this.isAttached) { + // account for css padding causing things to not line up + // this is mostly for v-autocomplete, hopefully it won't break anything + dimensions.activator.offsetTop = activator.offsetTop + } else { + dimensions.activator.offsetTop = 0 + } + } // Display and hide to get dimensions this.sneakPeek(() => { diff --git a/packages/vuetify/src/stylus/components/_inputs.styl b/packages/vuetify/src/stylus/components/_inputs.styl index f2d6ea49286..29b961b9539 100644 --- a/packages/vuetify/src/stylus/components/_inputs.styl +++ b/packages/vuetify/src/stylus/components/_inputs.styl @@ -72,7 +72,6 @@ theme(v-input, "v-input") height: auto flex-grow: 1 flex-wrap: wrap - position: relative width: 100% // For IE11 &__icon diff --git a/packages/vuetify/src/stylus/components/_menus.styl b/packages/vuetify/src/stylus/components/_menus.styl index 60b95f3a2fe..82b6a1d10aa 100644 --- a/packages/vuetify/src/stylus/components/_menus.styl +++ b/packages/vuetify/src/stylus/components/_menus.styl @@ -2,7 +2,6 @@ .v-menu display: block - position: relative vertical-align: middle &--inline @@ -12,7 +11,6 @@ align-items: center cursor: pointer display: flex - position: relative * cursor: pointer diff --git a/packages/vuetify/src/stylus/components/_tooltips.styl b/packages/vuetify/src/stylus/components/_tooltips.styl index 5a875aa03b1..ddf5c14d296 100644 --- a/packages/vuetify/src/stylus/components/_tooltips.styl +++ b/packages/vuetify/src/stylus/components/_tooltips.styl @@ -1,7 +1,6 @@ @import '../bootstrap' .v-tooltip - position: relative &__content background: $grey.darken-2 diff --git a/packages/vuetify/test/unit/components/VDialog/VDialog.spec.js b/packages/vuetify/test/unit/components/VDialog/VDialog.spec.js index 9fb48037d60..9aa95b7ec59 100644 --- a/packages/vuetify/test/unit/components/VDialog/VDialog.spec.js +++ b/packages/vuetify/test/unit/components/VDialog/VDialog.spec.js @@ -189,6 +189,7 @@ test('VDialog.js', ({ mount, compileToFunctions }) => { } const wrapper = mount(component) + await wrapper.vm.$nextTick() window.dispatchEvent(new Event('keydown')) expect(keydown).toBeCalled() diff --git a/packages/vuetify/test/unit/components/VTooltip/__snapshots__/VTooltip.spec.js.snap b/packages/vuetify/test/unit/components/VTooltip/__snapshots__/VTooltip.spec.js.snap index 37965ab40ab..0df53495950 100644 --- a/packages/vuetify/test/unit/components/VTooltip/__snapshots__/VTooltip.spec.js.snap +++ b/packages/vuetify/test/unit/components/VTooltip/__snapshots__/VTooltip.spec.js.snap @@ -80,8 +80,6 @@ exports[`VTooltip.js should render component with zIndex prop and match snapshot style="left: 0px; max-width: auto; opacity: 0; top: 12px; z-index: 42; display: none;" > - - `;