element/packages/tabs/src/tab-nav.vue

281 lines
8.7 KiB
Vue

<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 activeTabBounding = activeTab.getBoundingClientRect();
const navScrollBounding = navScroll.getBoundingClientRect();
const navBounding = nav.getBoundingClientRect();
const currentOffset = this.navOffset;
let newOffset = currentOffset;
if (activeTabBounding.left < navScrollBounding.left) {
newOffset = currentOffset - (navScrollBounding.left - activeTabBounding.left);
}
if (activeTabBounding.right > navScrollBounding.right) {
newOffset = currentOffset + activeTabBounding.right - navScrollBounding.right;
}
if (navBounding.right < navScrollBounding.right) {
newOffset = nav.offsetWidth - navScrollBounding.width;
}
this.navOffset = Math.max(newOffset, 0);
},
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) { // 左右上下键更换tab
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(); // 选中下一个tab
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}`}
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);
},
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>