<script> import TabBar from './tab-bar'; import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event'; function noop() {} const firstUpperCase = str => { return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase()); }; export default { name: 'TabNav', components: { TabBar }, inject: ['rootTabs'], props: { panes: Array, currentName: String, editable: Boolean, onTabClick: { type: Function, default: noop }, onTabRemove: { type: Function, default: noop }, type: String, stretch: Boolean }, data() { return { scrollable: false, navOffset: 0, isFocus: false, focusable: true }; }, computed: { navStyle() { const dir = ['top', 'bottom'].indexOf(this.rootTabs.tabPosition) !== -1 ? 'X' : 'Y'; return { transform: `translate${dir}(-${this.navOffset}px)` }; }, sizeName() { return ['top', 'bottom'].indexOf(this.rootTabs.tabPosition) !== -1 ? 'width' : 'height'; } }, methods: { scrollPrev() { const containerSize = this.$refs.navScroll[`offset${firstUpperCase(this.sizeName)}`]; const currentOffset = this.navOffset; if (!currentOffset) return; let newOffset = currentOffset > containerSize ? currentOffset - containerSize : 0; this.navOffset = newOffset; }, scrollNext() { const navSize = this.$refs.nav[`offset${firstUpperCase(this.sizeName)}`]; const containerSize = this.$refs.navScroll[`offset${firstUpperCase(this.sizeName)}`]; const currentOffset = this.navOffset; if (navSize - currentOffset <= containerSize) return; let newOffset = navSize - currentOffset > containerSize * 2 ? currentOffset + containerSize : (navSize - containerSize); this.navOffset = newOffset; }, scrollToActiveTab() { if (!this.scrollable) return; const nav = this.$refs.nav; const activeTab = this.$el.querySelector('.is-active'); if (!activeTab) return; const navScroll = this.$refs.navScroll; const isHorizontal = ['top', 'bottom'].indexOf(this.rootTabs.tabPosition) !== -1; const activeTabBounding = activeTab.getBoundingClientRect(); const navScrollBounding = navScroll.getBoundingClientRect(); const maxOffset = isHorizontal ? nav.offsetWidth - navScrollBounding.width : nav.offsetHeight - navScrollBounding.height; const currentOffset = this.navOffset; let newOffset = currentOffset; if (isHorizontal) { if (activeTabBounding.left < navScrollBounding.left) { newOffset = currentOffset - (navScrollBounding.left - activeTabBounding.left); } if (activeTabBounding.right > navScrollBounding.right) { newOffset = currentOffset + activeTabBounding.right - navScrollBounding.right; } } else { if (activeTabBounding.top < navScrollBounding.top) { newOffset = currentOffset - (navScrollBounding.top - activeTabBounding.top); } if (activeTabBounding.bottom > navScrollBounding.bottom) { newOffset = currentOffset + (activeTabBounding.bottom - navScrollBounding.bottom); } } newOffset = Math.max(newOffset, 0); this.navOffset = Math.min(newOffset, maxOffset); }, update() { if (!this.$refs.nav) return; const sizeName = this.sizeName; const navSize = this.$refs.nav[`offset${firstUpperCase(sizeName)}`]; const containerSize = this.$refs.navScroll[`offset${firstUpperCase(sizeName)}`]; const currentOffset = this.navOffset; if (containerSize < navSize) { const currentOffset = this.navOffset; this.scrollable = this.scrollable || {}; this.scrollable.prev = currentOffset; this.scrollable.next = currentOffset + containerSize < navSize; if (navSize - currentOffset < containerSize) { this.navOffset = navSize - containerSize; } } else { this.scrollable = false; if (currentOffset > 0) { this.navOffset = 0; } } }, changeTab(e) { const keyCode = e.keyCode; let nextIndex; let currentIndex, tabList; if ([37, 38, 39, 40].indexOf(keyCode) !== -1) { // 宸﹀彸涓婁笅閿洿鎹ab tabList = e.currentTarget.querySelectorAll('[role=tab]'); currentIndex = Array.prototype.indexOf.call(tabList, e.target); } else { return; } if (keyCode === 37 || keyCode === 38) { // left if (currentIndex === 0) { // first nextIndex = tabList.length - 1; } else { nextIndex = currentIndex - 1; } } else { // right if (currentIndex < tabList.length - 1) { // not last nextIndex = currentIndex + 1; } else { nextIndex = 0; } } tabList[nextIndex].focus(); // 鏀瑰彉鐒︾偣鍏冪礌 tabList[nextIndex].click(); // 閫変腑涓嬩竴涓猼ab this.setFocus(); }, setFocus() { if (this.focusable) { this.isFocus = true; } }, removeFocus() { this.isFocus = false; }, visibilityChangeHandler() { const visibility = document.visibilityState; if (visibility === 'hidden') { this.focusable = false; } else if (visibility === 'visible') { setTimeout(() => { this.focusable = true; }, 50); } }, windowBlurHandler() { this.focusable = false; }, windowFocusHandler() { setTimeout(() => { this.focusable = true; }, 50); } }, updated() { this.update(); }, render(h) { const { type, panes, editable, stretch, onTabClick, onTabRemove, navStyle, scrollable, scrollNext, scrollPrev, changeTab, setFocus, removeFocus } = this; const scrollBtn = scrollable ? [ <span class={['el-tabs__nav-prev', scrollable.prev ? '' : 'is-disabled']} on-click={scrollPrev}><i class="el-icon-arrow-left"></i></span>, <span class={['el-tabs__nav-next', scrollable.next ? '' : 'is-disabled']} on-click={scrollNext}><i class="el-icon-arrow-right"></i></span> ] : null; const tabs = this._l(panes, (pane, index) => { let tabName = pane.name || pane.index || index; const closable = pane.isClosable || editable; pane.index = `${index}`; const btnClose = closable ? <span class="el-icon-close" on-click={(ev) => { onTabRemove(pane, ev); }}></span> : null; const tabLabelContent = pane.$slots.label || pane.label; const tabindex = pane.active ? 0 : -1; return ( <div class={{ 'el-tabs__item': true, [`is-${ this.rootTabs.tabPosition }`]: true, 'is-active': pane.active, 'is-disabled': pane.disabled, 'is-closable': closable, 'is-focus': this.isFocus }} id={`tab-${tabName}`} key={`tab-${tabName}`} aria-controls={`pane-${tabName}`} role="tab" aria-selected={ pane.active } ref="tabs" tabindex={tabindex} refInFor on-focus={ ()=> { setFocus(); }} on-blur ={ ()=> { removeFocus(); }} on-click={(ev) => { removeFocus(); onTabClick(pane, tabName, ev); }} on-keydown={(ev) => { if (closable && (ev.keyCode === 46 || ev.keyCode === 8)) { onTabRemove(pane, ev);} }} > {tabLabelContent} {btnClose} </div> ); }); return ( <div class={['el-tabs__nav-wrap', scrollable ? 'is-scrollable' : '', `is-${ this.rootTabs.tabPosition }`]}> {scrollBtn} <div class={['el-tabs__nav-scroll']} ref="navScroll"> <div class={['el-tabs__nav', `is-${ this.rootTabs.tabPosition }`, stretch && ['top', 'bottom'].indexOf(this.rootTabs.tabPosition) !== -1 ? 'is-stretch' : '']} ref="nav" style={navStyle} role="tablist" on-keydown={ changeTab } > {!type ? <tab-bar tabs={panes}></tab-bar> : null} {tabs} </div> </div> </div> ); }, mounted() { addResizeListener(this.$el, this.update); document.addEventListener('visibilitychange', this.visibilityChangeHandler); window.addEventListener('blur', this.windowBlurHandler); window.addEventListener('focus', this.windowFocusHandler); setTimeout(() => { this.scrollToActiveTab(); }, 0); }, beforeDestroy() { if (this.$el && this.update) removeResizeListener(this.$el, this.update); document.removeEventListener('visibilitychange', this.visibilityChangeHandler); window.removeEventListener('blur', this.windowBlurHandler); window.removeEventListener('focus', this.windowFocusHandler); } }; </script>