349 lines
9.3 KiB
Vue
349 lines
9.3 KiB
Vue
|
import { setTransform, isTransformSupported } from './utils'
|
||
|
import addDOMEventListener from 'add-dom-event-listener'
|
||
|
import debounce from 'lodash/debounce'
|
||
|
import PropTypes from '../../_util/vue-types'
|
||
|
import BaseMixin from '../../_util/BaseMixin'
|
||
|
|
||
|
function noop () {
|
||
|
}
|
||
|
export default {
|
||
|
name: 'ScrollableTabBarNode',
|
||
|
mixins: [BaseMixin],
|
||
|
props: {
|
||
|
saveRef: PropTypes.func.def(() => {}),
|
||
|
getRef: PropTypes.func.def(() => {}),
|
||
|
tabBarPosition: PropTypes.oneOf(['left', 'right', 'top', 'bottom']).def('left'),
|
||
|
prefixCls: PropTypes.string.def(''),
|
||
|
scrollAnimated: PropTypes.bool.def(true),
|
||
|
navWrapper: PropTypes.func.def(arg => arg),
|
||
|
},
|
||
|
|
||
|
data () {
|
||
|
this.offset = 0
|
||
|
this.prevProps = this.$props
|
||
|
return {
|
||
|
next: false,
|
||
|
prev: false,
|
||
|
}
|
||
|
},
|
||
|
|
||
|
mounted () {
|
||
|
this.$nextTick(() => {
|
||
|
this.updatedCal()
|
||
|
this.debouncedResize = debounce(() => {
|
||
|
this.setNextPrev()
|
||
|
this.scrollToActiveTab()
|
||
|
}, 200)
|
||
|
this.resizeEvent = addDOMEventListener(window, 'resize', this.debouncedResize)
|
||
|
})
|
||
|
},
|
||
|
|
||
|
updated () {
|
||
|
this.$nextTick(() => {
|
||
|
this.updatedCal(this.prevProps)
|
||
|
this.prevProps = this.$props
|
||
|
})
|
||
|
},
|
||
|
|
||
|
beforeDestroy () {
|
||
|
if (this.resizeEvent) {
|
||
|
this.resizeEvent.remove()
|
||
|
}
|
||
|
if (this.debouncedResize && this.debouncedResize.cancel) {
|
||
|
this.debouncedResize.cancel()
|
||
|
}
|
||
|
},
|
||
|
watch: {
|
||
|
tabBarPosition (val) {
|
||
|
this.tabBarPositionChange = true
|
||
|
this.$nextTick(() => {
|
||
|
this.setOffset(0)
|
||
|
})
|
||
|
},
|
||
|
},
|
||
|
methods: {
|
||
|
updatedCal (prevProps) {
|
||
|
const props = this.$props
|
||
|
if (prevProps && prevProps.tabBarPosition !== props.tabBarPosition) {
|
||
|
this.setOffset(0)
|
||
|
return
|
||
|
}
|
||
|
const nextPrev = this.setNextPrev()
|
||
|
// wait next, prev show hide
|
||
|
/* eslint react/no-did-update-set-state:0 */
|
||
|
if (this.isNextPrevShown(this.$data) !== this.isNextPrevShown(nextPrev)) {
|
||
|
this.$foreceUpdate()
|
||
|
this.$nextTick(() => {
|
||
|
this.scrollToActiveTab()
|
||
|
})
|
||
|
} else if (!prevProps || props.activeKey !== prevProps.activeKey) {
|
||
|
// can not use props.activeKey
|
||
|
this.scrollToActiveTab()
|
||
|
}
|
||
|
},
|
||
|
setNextPrev () {
|
||
|
const navNode = this.$props.getRef('nav')
|
||
|
const navNodeWH = this.getScrollWH(navNode)
|
||
|
const containerWH = this.getOffsetWH(this.$props.getRef('container'))
|
||
|
const navWrapNodeWH = this.getOffsetWH(this.$props.getRef('navWrap'))
|
||
|
let { offset } = this
|
||
|
const minOffset = containerWH - navNodeWH
|
||
|
let { next, prev } = this
|
||
|
if (minOffset >= 0) {
|
||
|
next = false
|
||
|
this.setOffset(0, false)
|
||
|
offset = 0
|
||
|
} else if (minOffset < offset) {
|
||
|
next = true
|
||
|
} else {
|
||
|
next = false
|
||
|
// Fix https://github.com/ant-design/ant-design/issues/8861
|
||
|
// Test with container offset which is stable
|
||
|
// and set the offset of the nav wrap node
|
||
|
const realOffset = navWrapNodeWH - navNodeWH
|
||
|
this.setOffset(realOffset, false)
|
||
|
offset = realOffset
|
||
|
}
|
||
|
|
||
|
if (offset < 0) {
|
||
|
prev = true
|
||
|
} else {
|
||
|
prev = false
|
||
|
}
|
||
|
|
||
|
this.setNext(next)
|
||
|
this.setPrev(prev)
|
||
|
return {
|
||
|
next,
|
||
|
prev,
|
||
|
}
|
||
|
},
|
||
|
|
||
|
getOffsetWH (node) {
|
||
|
const tabBarPosition = this.$props.tabBarPosition
|
||
|
let prop = 'offsetWidth'
|
||
|
if (tabBarPosition === 'left' || tabBarPosition === 'right') {
|
||
|
prop = 'offsetHeight'
|
||
|
}
|
||
|
return node[prop]
|
||
|
},
|
||
|
|
||
|
getScrollWH (node) {
|
||
|
const tabBarPosition = this.tabBarPosition
|
||
|
let prop = 'scrollWidth'
|
||
|
if (tabBarPosition === 'left' || tabBarPosition === 'right') {
|
||
|
prop = 'scrollHeight'
|
||
|
}
|
||
|
return node[prop]
|
||
|
},
|
||
|
|
||
|
getOffsetLT (node) {
|
||
|
const tabBarPosition = this.$props.tabBarPosition
|
||
|
let prop = 'left'
|
||
|
if (tabBarPosition === 'left' || tabBarPosition === 'right') {
|
||
|
prop = 'top'
|
||
|
}
|
||
|
return node.getBoundingClientRect()[prop]
|
||
|
},
|
||
|
|
||
|
setOffset (offset, checkNextPrev = true) {
|
||
|
const target = Math.min(0, offset)
|
||
|
if (this.offset !== target) {
|
||
|
this.offset = target
|
||
|
let navOffset = {}
|
||
|
const tabBarPosition = this.$props.tabBarPosition
|
||
|
const navStyle = this.$props.getRef('nav').style
|
||
|
const transformSupported = isTransformSupported(navStyle)
|
||
|
if (tabBarPosition === 'left' || tabBarPosition === 'right') {
|
||
|
if (transformSupported) {
|
||
|
navOffset = {
|
||
|
value: `translate3d(0,${target}px,0)`,
|
||
|
}
|
||
|
} else {
|
||
|
navOffset = {
|
||
|
name: 'top',
|
||
|
value: `${target}px`,
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if (transformSupported) {
|
||
|
navOffset = {
|
||
|
value: `translate3d(${target}px,0,0)`,
|
||
|
}
|
||
|
} else {
|
||
|
navOffset = {
|
||
|
name: 'left',
|
||
|
value: `${target}px`,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (transformSupported) {
|
||
|
setTransform(navStyle, navOffset.value)
|
||
|
} else {
|
||
|
navStyle[navOffset.name] = navOffset.value
|
||
|
}
|
||
|
if (checkNextPrev) {
|
||
|
this.setNextPrev()
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
setPrev (v) {
|
||
|
if (this.prev !== v) {
|
||
|
this.prev = v
|
||
|
}
|
||
|
},
|
||
|
|
||
|
setNext (v) {
|
||
|
if (!v) {
|
||
|
// debugger
|
||
|
}
|
||
|
if (this.next !== v) {
|
||
|
this.next = v
|
||
|
}
|
||
|
},
|
||
|
|
||
|
isNextPrevShown (state) {
|
||
|
if (state) {
|
||
|
return state.next || state.prev
|
||
|
}
|
||
|
return this.next || this.prev
|
||
|
},
|
||
|
|
||
|
prevTransitionEnd (e) {
|
||
|
if (e.propertyName !== 'opacity') {
|
||
|
return
|
||
|
}
|
||
|
const container = this.$props.getRef('container')
|
||
|
this.scrollToActiveTab({
|
||
|
target: container,
|
||
|
currentTarget: container,
|
||
|
})
|
||
|
},
|
||
|
|
||
|
scrollToActiveTab (e) {
|
||
|
const activeTab = this.$props.getRef('activeTab')
|
||
|
const navWrap = this.$props.getRef('navWrap')
|
||
|
if (e && e.target !== e.currentTarget || !activeTab) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// when not scrollable or enter scrollable first time, don't emit scrolling
|
||
|
const needToSroll = this.isNextPrevShown() && this.lastNextPrevShown
|
||
|
this.lastNextPrevShown = this.isNextPrevShown()
|
||
|
if (!needToSroll) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
const activeTabWH = this.getScrollWH(activeTab)
|
||
|
const navWrapNodeWH = this.getOffsetWH(navWrap)
|
||
|
let { offset } = this
|
||
|
const wrapOffset = this.getOffsetLT(navWrap)
|
||
|
const activeTabOffset = this.getOffsetLT(activeTab)
|
||
|
if (wrapOffset > activeTabOffset) {
|
||
|
offset += (wrapOffset - activeTabOffset)
|
||
|
this.setOffset(offset)
|
||
|
} else if ((wrapOffset + navWrapNodeWH) < (activeTabOffset + activeTabWH)) {
|
||
|
offset -= (activeTabOffset + activeTabWH) - (wrapOffset + navWrapNodeWH)
|
||
|
this.setOffset(offset)
|
||
|
}
|
||
|
},
|
||
|
|
||
|
prevClick (e) {
|
||
|
this.__emit('prevClick', e)
|
||
|
const navWrapNode = this.$props.getRef('navWrap')
|
||
|
const navWrapNodeWH = this.getOffsetWH(navWrapNode)
|
||
|
const { offset } = this
|
||
|
this.setOffset(offset + navWrapNodeWH)
|
||
|
},
|
||
|
|
||
|
nextClick (e) {
|
||
|
// this.__emit('nextClick', e)
|
||
|
const navWrapNode = this.$props.getRef('navWrap')
|
||
|
const navWrapNodeWH = this.getOffsetWH(navWrapNode)
|
||
|
const { offset } = this
|
||
|
this.setOffset(offset - navWrapNodeWH)
|
||
|
},
|
||
|
},
|
||
|
render () {
|
||
|
const { next, prev } = this
|
||
|
const { prefixCls, scrollAnimated, navWrapper } = this.$props
|
||
|
const showNextPrev = prev || next
|
||
|
|
||
|
const prevButton = (
|
||
|
<span
|
||
|
onClick={prev ? this.prevClick : noop}
|
||
|
unselectable='unselectable'
|
||
|
class={{
|
||
|
[`${prefixCls}-tab-prev`]: 1,
|
||
|
[`${prefixCls}-tab-btn-disabled`]: !prev,
|
||
|
[`${prefixCls}-tab-arrow-show`]: showNextPrev,
|
||
|
}}
|
||
|
onTransitionend={this.prevTransitionEnd}
|
||
|
>
|
||
|
<span class={`${prefixCls}-tab-prev-icon`} />
|
||
|
</span>
|
||
|
)
|
||
|
|
||
|
const nextButton = (
|
||
|
<span
|
||
|
onClick={next ? this.nextClick : noop}
|
||
|
unselectable='unselectable'
|
||
|
class={{
|
||
|
[`${prefixCls}-tab-next`]: 1,
|
||
|
[`${prefixCls}-tab-btn-disabled`]: !next,
|
||
|
[`${prefixCls}-tab-arrow-show`]: showNextPrev,
|
||
|
}}
|
||
|
>
|
||
|
<span class={`${prefixCls}-tab-next-icon`} />
|
||
|
</span>
|
||
|
)
|
||
|
|
||
|
const navClassName = `${prefixCls}-nav`
|
||
|
const navClasses = {
|
||
|
[navClassName]: true,
|
||
|
[
|
||
|
scrollAnimated
|
||
|
? `${navClassName}-animated`
|
||
|
: `${navClassName}-no-animated`
|
||
|
]: true,
|
||
|
}
|
||
|
|
||
|
return (
|
||
|
<div
|
||
|
class={{
|
||
|
[`${prefixCls}-nav-container`]: 1,
|
||
|
[`${prefixCls}-nav-container-scrolling`]: showNextPrev,
|
||
|
}}
|
||
|
key='container'
|
||
|
{...{ directives: [{
|
||
|
name: 'ref',
|
||
|
value: this.saveRef('container'),
|
||
|
}] }}
|
||
|
>
|
||
|
{prevButton}
|
||
|
{nextButton}
|
||
|
<div
|
||
|
class={`${prefixCls}-nav-wrap`}
|
||
|
{...{ directives: [{
|
||
|
name: 'ref',
|
||
|
value: this.saveRef('navWrap'),
|
||
|
}] }}
|
||
|
>
|
||
|
<div class={`${prefixCls}-nav-scroll`}>
|
||
|
<div
|
||
|
class={navClasses}
|
||
|
{...{ directives: [{
|
||
|
name: 'ref',
|
||
|
value: this.saveRef('nav'),
|
||
|
}] }}
|
||
|
>
|
||
|
{navWrapper(this.$slots.default)}
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
)
|
||
|
},
|
||
|
}
|