UNPKG

vuetify

Version:

Vue.js 2 Semantic Component Framework

244 lines (216 loc) 6.37 kB
// Styles import '../../stylus/components/_tabs.styl' // Component imports import VIcon from '../VIcon' import VTabsItems from './VTabsItems' import VTabsSlider from './VTabsSlider' // Component level mixins import TabsComputed from './mixins/tabs-computed' import TabsGenerators from './mixins/tabs-generators' import TabsProps from './mixins/tabs-props' import TabsTouch from './mixins/tabs-touch' import TabsWatchers from './mixins/tabs-watchers' // Mixins import Colorable from '../../mixins/colorable' import SSRBootable from '../../mixins/ssr-bootable' import Themeable from '../../mixins/themeable' import { provide as RegistrableProvide } from '../../mixins/registrable' // Directives import Resize from '../../directives/resize' import Touch from '../../directives/touch' export default { name: 'v-tabs', components: { VIcon, VTabsItems, VTabsSlider }, mixins: [ RegistrableProvide('tabs'), Colorable, SSRBootable, TabsComputed, TabsProps, TabsGenerators, TabsTouch, TabsWatchers, Themeable ], directives: { Resize, Touch }, provide () { return { tabClick: this.tabClick, tabProxy: this.tabProxy, registerItems: this.registerItems, unregisterItems: this.unregisterItems } }, data () { return { prependIconVisible: false, appendIconVisible: false, bar: [], content: [], isBooted: false, isOverflowing: false, lazyValue: this.value, resizeTimeout: null, reverse: false, scrollOffset: 0, sliderWidth: null, sliderLeft: null, startX: 0, tabsContainer: null, tabs: [], tabItems: null, transitionTime: 300 } }, methods: { checkPrependIcon () { return this.scrollOffset > 0 }, checkAppendIcon () { // Check one scroll ahead to know the width of right-most item const container = this.$refs.container const wrapper = this.$refs.wrapper return container.clientWidth > this.scrollOffset + wrapper.clientWidth }, callSlider () { this.setOverflow() if (this.hideSlider || !this.activeTab) return false // Give screen time to paint const action = this.activeTab.action const activeTab = action === this.activeTab ? this.activeTab : this.tabs.find(tab => tab.action === action) if (!activeTab) return this.sliderWidth = activeTab.$el.scrollWidth this.sliderLeft = activeTab.$el.offsetLeft }, /** * When v-navigation-drawer changes the * width of the container, call resize * after the transition is complete */ onContainerResize () { clearTimeout(this.resizeTimeout) this.resizeTimeout = setTimeout(this.callSlider, this.transitionTime) }, onResize () { if (this._isDestroyed) return this.callSlider() this.scrollIntoView() }, overflowCheck (e, fn) { this.isOverflowing && fn(e) }, scrollTo (direction) { this.scrollOffset = this.newOffset(direction) }, setOverflow () { this.isOverflowing = this.$refs.bar.clientWidth < this.$refs.container.clientWidth }, findActiveLink () { if (!this.tabs.length || this.lazyValue) return const activeIndex = this.tabs.findIndex((tabItem, index) => { const id = tabItem.action === tabItem ? index.toString() : tabItem.action return id === this.lazyValue || tabItem.$el.firstChild.className.indexOf(this.activeClass) > -1 }) const index = activeIndex > -1 ? activeIndex : 0 const tab = this.tabs[index] /* istanbul ignore next */ // There is not a reliable way to test this.inputValue = tab.action === tab ? index : tab.action }, parseNodes () { const item = [] const items = [] const slider = [] const tab = [] const length = (this.$slots.default || []).length for (let i = 0; i < length; i++) { const vnode = this.$slots.default[i] /* istanbul ignore else */ if (vnode.componentOptions) { switch (vnode.componentOptions.Ctor.options.name) { case 'v-tabs-slider': slider.push(vnode) break case 'v-tabs-items': items.push(vnode) break case 'v-tab-item': item.push(vnode) break // case 'v-tab' - intentionally omitted default: tab.push(vnode) } } } return { tab, slider, items, item } }, register (options) { this.tabs.push(options) }, scrollIntoView () { if (!this.activeTab) return false const { clientWidth, offsetLeft } = this.activeTab.$el const wrapperWidth = this.$refs.wrapper.clientWidth const totalWidth = wrapperWidth + this.scrollOffset const itemOffset = clientWidth + offsetLeft const additionalOffset = clientWidth * 0.3 /* instanbul ignore else */ if (offsetLeft < this.scrollOffset) { this.scrollOffset = Math.max(offsetLeft - additionalOffset, 0) } else if (totalWidth < itemOffset) { this.scrollOffset -= totalWidth - itemOffset - additionalOffset } }, tabClick (tab) { this.inputValue = tab.action === tab ? this.tabs.indexOf(tab) : tab.action this.scrollIntoView() }, tabProxy (val) { this.inputValue = val }, registerItems (fn) { this.tabItems = fn }, unregisterItems () { this.tabItems = null }, unregister (tab) { this.tabs = this.tabs.filter(o => o !== tab) }, updateTabs () { for (let index = this.tabs.length; --index >= 0;) { this.tabs[index].toggle(this.target) } this.setOverflow() } }, mounted () { this.callSlider() this.prependIconVisible = this.checkPrependIcon() this.appendIconVisible = this.checkAppendIcon() }, render (h) { const { tab, slider, items, item } = this.parseNodes() return h('div', { staticClass: 'tabs', directives: [{ name: 'resize', arg: 400, modifiers: { quiet: true }, value: this.onResize }] }, [ this.genBar([this.hideSlider ? null : this.genSlider(slider), tab]), this.genItems(items, item) ]) } }