naive-ui/packages/mixins/placeable.js

311 lines
9.9 KiB
JavaScript
Raw Normal View History

import scrollDelegate from '../utils/delegate/scrollDelegate'
import resizeDelegate from '../utils/delegate/resizeDelegate'
import getParentNode from '../utils/dom/getParentNode'
import getScrollParent from '../utils/dom/getScrollParent'
import calcPlacementTransfrom from '../utils/dom/calcPlacementTransform'
2019-08-30 00:07:38 +08:00
function getActivatorEl (componentInstance) {
2019-12-03 21:35:39 +08:00
return componentInstance.$refs.activator.$el || componentInstance.$refs.activator
}
function getContentEl (componentInstance) {
return componentInstance.$refs.content.$el || componentInstance.$refs.content
2019-08-30 00:07:38 +08:00
}
2019-10-23 16:51:29 +08:00
function sortOrigin (origin) {
const origins = origin.split(' ')
if (origins.length === 2) {
if (origins[0] === 'left' || origins[0] === 'right') return origins[1] + ' ' + origins[0]
}
return origin
}
2019-12-03 21:35:39 +08:00
function getPositionInAbsoluteMode (placement, origin) {
let position = {
top: null,
bottom: null,
left: null,
right: null
}
if (placement === 'bottom-start') {
if (~origin.indexOf('top')) {
position.top = '100%'
}
if (~origin.indexOf('bottom')) {
position.bottom = '100%'
}
if (~origin.indexOf('left')) {
position.left = '0'
}
if (~origin.indexOf('right')) {
position.right = '0'
}
} else if (placement === 'right-start') {
if (~origin.indexOf('top')) {
position.top = '0'
}
if (~origin.indexOf('bottom')) {
position.bottom = '0'
}
if (~origin.indexOf('left')) {
position.left = '100%'
}
if (~origin.indexOf('right')) {
position.right = '100%'
}
} else {
console.error('[naive-ui/placeable/getPositionInAbsoluteMode]: placement not implemented')
}
return position
}
/**
* Make $refs.content trace $refs.activator, set $refs.contentInner width by the way
*
* Dependency:
* $refs.activator
* $refs.content
* $refs.contentInner(optional)
* $vm.active
*
* @prop {string} placement determine where should $refs.content be put
* @prop {string} widthMode determine how width is $refs.contentInner
*/
export default {
props: {
positionMode: {
type: String,
default: 'fixed',
validator (value) {
return ['fixed', 'absolute'].includes(value)
}
},
placement: {
validator (value) {
return [
'top',
'bottom',
'left',
'right',
'top-start',
'top-end',
'left-start',
'left-end',
'right-start',
'right-end',
'bottom-start',
'bottom-end'
].includes(value)
},
default: 'bottom'
},
widthMode: {
validator (value) {
return ['self', 'activator'].includes(value)
},
default: 'self'
2019-12-03 18:36:21 +08:00
},
x: {
type: Number,
default: null
},
y: {
type: Number,
default: null
},
manuallyPositioned: {
type: Boolean,
default: false
}
},
computed: {
positionModeisAbsolute () {
return this.positionMode === 'absolute'
}
},
watch: {
active (newValue) {
if (newValue) {
this.$nextTick().then(this.updatePosition)
}
2019-12-03 18:36:21 +08:00
},
x () {
this.$nextTick().then(this.updatePosition)
},
y () {
this.$nextTick().then(this.updatePosition)
}
},
2019-09-01 22:11:30 +08:00
data () {
return {
trackingElement: null,
trackedElement: null,
scrollListeners: []
}
},
mounted () {
2019-09-01 22:11:30 +08:00
this._getTrackingElement()
2019-12-03 21:35:39 +08:00
if (this.trackingElement) {
this.trackingElement.style.position = 'absolute'
}
this.$nextTick().then(() => {
this.registerScrollListeners()
this.registerResizeListener()
2019-09-02 15:55:23 +08:00
this.updatePosition()
})
},
beforeDestroy () {
this.unregisterScrollListeners()
this.unregisterResizeListener()
},
methods: {
2019-09-01 22:11:30 +08:00
_getTrackingElement () {
if (this.$refs && this.$refs.content) {
2019-12-03 21:35:39 +08:00
this.trackingElement = getContentEl(this)
2019-09-01 22:11:30 +08:00
} else if (this.getTrackingElement) {
this.trackingElement = this.getTrackingElement()
}
},
_getTrackedElement () {
if (this.$refs && this.$refs.activator) {
this.trackedElement = getActivatorEl(this)
} else if (this.getTrackedElement) {
this.trackedElement = this.getTrackedElement()
}
},
/**
* Need to be fulfilled!
*/
2019-12-03 21:35:39 +08:00
updatePositionInAbsoluteMode (position, transformOrigin) {
this.trackingElement.style.position = 'absolute'
2019-12-03 21:35:39 +08:00
this.trackingElement.style.top = position.top
this.trackingElement.style.left = position.left
this.trackingElement.style.right = position.right
this.trackingElement.style.bottom = position.bottom
this.trackingElement.style.transformOrigin = transformOrigin
this.trackingElement.setAttribute('n-suggested-transform-origin', transformOrigin)
},
2019-10-23 16:51:29 +08:00
updatePosition (el, cb, keepOrigin = false) {
2019-09-28 13:47:54 +08:00
if (!this.active && !this.show) return
2019-12-03 18:36:21 +08:00
if (!this.manuallyPositioned) {
this._getTrackedElement()
}
2019-09-01 22:11:30 +08:00
this._getTrackingElement()
2019-12-03 18:36:21 +08:00
if (this.manuallyPositioned) {
if (!this.trackingElement) {
console.error('[naive-ui/placeable/updatePosition]: trackingElement not found!')
}
} else {
if (!this.trackedElement || !this.trackingElement) {
console.error('[naive-ui/placeable/updatePosition]: trakedElement or trackingElement not found!')
}
2019-09-28 13:47:54 +08:00
}
2019-12-03 21:35:39 +08:00
2019-08-24 01:45:32 +08:00
// console.log(activator)
2019-12-03 18:36:21 +08:00
let activatorBoundingClientRect = null
if (!this.manuallyPositioned) {
activatorBoundingClientRect = this.trackedElement.getBoundingClientRect()
} else {
activatorBoundingClientRect = {
top: this.y,
left: this.x,
height: 0,
width: 0,
right: this.x,
bottom: this.y
}
// console.log(activatorBoundingClientRect)
}
2019-08-24 01:45:32 +08:00
// console.log(activatorBoundingClientRect)
// console.log(this.$refs.popoverBody)
// debugger
2019-12-03 21:35:39 +08:00
const contentBoundingClientRect = {
width: this.trackingElement.offsetWidth,
height: this.trackingElement.offsetHeight
}
// console.log(contentBoundingClientRect.width, contentBoundingClientRect.height)
// console.log(contentBoundingClientRect2.width, contentBoundingClientRect2.height)
// console.log(contentBoundingClientRect)
// debugger
// console.log('scroll', activatorBoundingClientRect, contentBoundingClientRect)
2019-10-23 16:51:29 +08:00
const [placementTransform, suggestedTransformOrigin] = calcPlacementTransfrom(this.placement, activatorBoundingClientRect, contentBoundingClientRect)
2019-12-03 21:35:39 +08:00
// console.log(this.trackingElement, this.positionMode, this.positionModeisAbsolute)
if (this.positionModeisAbsolute) {
const position = getPositionInAbsoluteMode(this.placement, suggestedTransformOrigin)
// console.log(suggestedTransformOrigin, position)
this.updatePositionInAbsoluteMode(position, suggestedTransformOrigin)
return
}
2019-12-03 18:36:21 +08:00
// console.log(placementTransform)
2019-09-01 22:11:30 +08:00
this.trackingElement.style.position = 'absolute'
this.trackingElement.style.top = placementTransform.top
this.trackingElement.style.left = placementTransform.left
this.trackingElement.style.right = placementTransform.right
this.trackingElement.style.bottom = placementTransform.bottom
2019-10-23 16:51:29 +08:00
// if (suggestedTransformOrigin === 'left bottom') {
// debugger
// }
// console.log(' !!! ', suggestedTransformOrigin)
if (keepOrigin === false) {
this.trackingElement.style.transformOrigin = suggestedTransformOrigin
this.trackingElement.setAttribute('n-suggested-transform-origin', suggestedTransformOrigin)
} else if (keepOrigin !== true) {
if (keepOrigin.horizontal) {
const originalTransformOrigin = this.trackingElement.style.transformOrigin
if (originalTransformOrigin && suggestedTransformOrigin) {
if (suggestedTransformOrigin.match(/top/)) {
this.trackingElement.style.transformOrigin = originalTransformOrigin.replace(/(top|bottom)/g, 'top')
this.trackingElement.setAttribute('n-suggested-transform-origin', sortOrigin(originalTransformOrigin.replace(/(top|bottom)/g, 'top')))
}
if (suggestedTransformOrigin.match(/bottom/)) {
this.trackingElement.style.transformOrigin = sortOrigin(originalTransformOrigin.replace(/(top|bottom)/g, 'bottom'))
this.trackingElement.setAttribute('n-suggested-transform-origin', sortOrigin(originalTransformOrigin.replace(/(top|bottom)/g, 'bottom')))
}
}
}
}
// console.log(this.$refs.contentInner)
if (this.$refs.contentInner) {
let el = this.$refs.contentInner
if (this.$refs.contentInner.$el) {
el = this.$refs.contentInner.$el
}
if (this.widthMode === 'activator') {
el.style.minWidth = activatorBoundingClientRect.width + 'px'
}
}
if (el && cb) {
cb(el, activatorBoundingClientRect, contentBoundingClientRect)
}
},
registerResizeListener () {
resizeDelegate.registerHandler(this.updatePosition)
},
registerScrollListeners () {
2019-09-02 15:55:23 +08:00
this._getTrackedElement()
let currentElement = getParentNode(this.trackedElement)
while (true) {
currentElement = getScrollParent(currentElement)
if (currentElement === null) break
this.scrollListeners.push([currentElement, this.updatePosition])
currentElement = getParentNode(currentElement)
}
// console.log(this.scrollListeners)
for (const [el, handler] of this.scrollListeners) {
scrollDelegate.registerHandler(el, handler)
}
},
unregisterResizeListener () {
resizeDelegate.unregisterHandler(this.updatePosition)
},
unregisterScrollListeners () {
for (const [el, handler] of this.scrollListeners) {
scrollDelegate.unregisterHandler(el, handler)
}
this.scrollListeners = []
}
}
}