Skip to content

Commit

Permalink
feat(detachable): add activator scoped slot (#5318)
Browse files Browse the repository at this point in the history
fixes #2489
resolves #3030
fixes #3107
fixes #4428
fixes #5193
  • Loading branch information
KaelWD committed Dec 18, 2018
1 parent 37044c8 commit a025093
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 72 deletions.
47 changes: 30 additions & 17 deletions packages/vuetify/src/components/VDialog/VDialog.js
Expand Up @@ -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 () {
Expand Down Expand Up @@ -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])
}
},

Expand Down Expand Up @@ -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) {
Expand Down
Expand Up @@ -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
}
},
Expand Down
38 changes: 24 additions & 14 deletions packages/vuetify/src/components/VMenu/mixins/menu-generators.js
Expand Up @@ -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 () {
Expand Down
52 changes: 35 additions & 17 deletions packages/vuetify/src/components/VTooltip/VTooltip.js
Expand Up @@ -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)
)
Expand All @@ -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)
)
Expand Down Expand Up @@ -130,8 +132,10 @@ export default {
}
},

mounted () {
this.value && this.callActivate()
beforeMount () {
this.$nextTick(() => {
this.value && this.callActivate()
})
},

methods: {
Expand All @@ -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)
}
}
},

Expand Down Expand Up @@ -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()
])
}
}
8 changes: 8 additions & 0 deletions packages/vuetify/src/mixins/detachable.js
Expand Up @@ -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()
},
Expand Down
57 changes: 41 additions & 16 deletions packages/vuetify/src/mixins/menuable.js
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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)
Expand All @@ -120,17 +122,19 @@ 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)

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
Expand All @@ -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,
Expand Down Expand Up @@ -250,7 +255,7 @@ export default Vue.extend({
}
},
deactivate () {},
getActivator () {
getActivator (e) {
if (this.inputActivator) {
return this.$el.querySelector('.v-input__slot')
}
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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(() => {
Expand Down
1 change: 0 additions & 1 deletion packages/vuetify/src/stylus/components/_inputs.styl
Expand Up @@ -72,7 +72,6 @@ theme(v-input, "v-input")
height: auto
flex-grow: 1
flex-wrap: wrap
position: relative
width: 100% // For IE11

&__icon
Expand Down

0 comments on commit a025093

Please sign in to comment.