UNPKG

element-plus

Version:

A Component Library for Vue3.0

607 lines (594 loc) 22.5 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var vue = require('vue'); var aria = require('../utils/aria'); var resizeEvent = require('../utils/resize-event'); var dom = require('../utils/dom'); /** * Make a map and return a function for checking if a key * is in that map. * IMPORTANT: all calls of this function must be prefixed with * \/\*#\_\_PURE\_\_\*\/ * So that rollup can tree-shake them if necessary. */ const EMPTY_OBJ = (process.env.NODE_ENV !== 'production') ? Object.freeze({}) : {}; const EMPTY_ARR = (process.env.NODE_ENV !== 'production') ? Object.freeze([]) : []; const NOOP = () => { }; const cacheStringFunction = (fn) => { const cache = Object.create(null); return ((str) => { const hit = cache[str]; return hit || (cache[str] = fn(str)); }); }; /** * @private */ const capitalize = cacheStringFunction((str) => str.charAt(0).toUpperCase() + str.slice(1)); var script = vue.defineComponent({ name: 'ElTabBar', props: { tabs: { type: Array, default: () => [], }, }, setup(props) { const rootTabs = vue.inject('rootTabs'); if (!rootTabs) { throw new Error(`ElTabBar must use with ElTabs`); } const instance = vue.getCurrentInstance(); const getBarStyle = () => { let style = {}; let offset = 0; let tabSize = 0; const sizeName = ['top', 'bottom'].includes(rootTabs.props.tabPosition) ? 'width' : 'height'; const sizeDir = sizeName === 'width' ? 'x' : 'y'; props.tabs.every(tab => { var _a; let $el = (_a = instance.parent.refs) === null || _a === void 0 ? void 0 : _a[`tab-${tab.paneName}`]; if (!$el) { return false; } if (!tab.active) { offset += $el[`client${capitalize(sizeName)}`]; return true; } else { tabSize = $el[`client${capitalize(sizeName)}`]; const tabStyles = window.getComputedStyle($el); if (sizeName === 'width') { if (props.tabs.length > 1) { tabSize -= parseFloat(tabStyles.paddingLeft) + parseFloat(tabStyles.paddingRight); } offset += parseFloat(tabStyles.paddingLeft); } return false; } }); const transform = `translate${capitalize(sizeDir)}(${offset}px)`; style[sizeName] = `${tabSize}px`; style.transform = transform; style.msTransform = transform; style.webkitTransform = transform; return style; }; const barStyle = vue.ref(getBarStyle()); vue.watch(() => props.tabs, () => { vue.nextTick(() => { barStyle.value = getBarStyle(); }); }); return { rootTabs, barStyle, }; }, }); function render(_ctx, _cache, $props, $setup, $data, $options) { return (vue.openBlock(), vue.createBlock("div", { class: ['el-tabs__active-bar', `is-${ _ctx.rootTabs.props.tabPosition }`], style: _ctx.barStyle }, null, 6 /* CLASS, STYLE */)) } script.render = render; script.__file = "packages/tabs/src/tab-bar.vue"; var script$1 = vue.defineComponent({ name: 'ElTabNav', components: { TabBar: script, }, props: { panes: { type: Array, default: () => [], }, currentName: { type: String, default: '', }, editable: Boolean, onTabClick: { type: Function, default: NOOP, }, onTabRemove: { type: Function, default: NOOP, }, type: { type: String, default: '', }, stretch: Boolean, }, setup() { const rootTabs = vue.inject('rootTabs'); if (!rootTabs) { throw new Error(`ElTabNav must use with ElTabs`); } const scrollable = vue.ref(false); const navOffset = vue.ref(0); const isFocus = vue.ref(false); const focusable = vue.ref(true); const navScroll$ = vue.ref(null); const nav$ = vue.ref(null); const el$ = vue.ref(null); const sizeName = vue.computed(() => { return ['top', 'bottom'].includes(rootTabs.props.tabPosition) ? 'width' : 'height'; }); const navStyle = vue.computed(() => { const dir = sizeName.value === 'width' ? 'X' : 'Y'; return { transform: `translate${dir}(-${navOffset.value}px)`, }; }); const scrollPrev = () => { const containerSize = navScroll$.value[`offset${capitalize(sizeName.value)}`]; const currentOffset = navOffset.value; if (!currentOffset) return; let newOffset = currentOffset > containerSize ? currentOffset - containerSize : 0; navOffset.value = newOffset; }; const scrollNext = () => { const navSize = nav$.value[`offset${capitalize(sizeName.value)}`]; const containerSize = navScroll$.value[`offset${capitalize(sizeName.value)}`]; const currentOffset = navOffset.value; if (navSize - currentOffset <= containerSize) return; let newOffset = navSize - currentOffset > containerSize * 2 ? currentOffset + containerSize : (navSize - containerSize); navOffset.value = newOffset; }; const scrollToActiveTab = () => { if (!scrollable.value) return; const nav = nav$.value; const activeTab = el$.value.querySelector('.is-active'); if (!activeTab) return; const navScroll = navScroll$.value; const isHorizontal = ['top', 'bottom'].includes(rootTabs.props.tabPosition); const activeTabBounding = activeTab.getBoundingClientRect(); const navScrollBounding = navScroll.getBoundingClientRect(); const maxOffset = isHorizontal ? nav.offsetWidth - navScrollBounding.width : nav.offsetHeight - navScrollBounding.height; const currentOffset = navOffset.value; 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); navOffset.value = Math.min(newOffset, maxOffset); }; const update = () => { if (!nav$.value) return; const navSize = nav$.value[`offset${capitalize(sizeName.value)}`]; const containerSize = navScroll$.value[`offset${capitalize(sizeName.value)}`]; const currentOffset = navOffset.value; if (containerSize < navSize) { const currentOffset = navOffset.value; scrollable.value = (scrollable.value || {}); scrollable.value.prev = currentOffset; scrollable.value.next = currentOffset + containerSize < navSize; if (navSize - currentOffset < containerSize) { navOffset.value = navSize - containerSize; } } else { scrollable.value = false; if (currentOffset > 0) { navOffset.value = 0; } } }; const changeTab = e => { const code = e.code; let nextIndex; let currentIndex, tabList; const { up, down, left, right } = aria.EVENT_CODE; if ([up, down, left, right].indexOf(code) !== -1) { tabList = e.currentTarget.querySelectorAll('[role=tab]'); currentIndex = Array.prototype.indexOf.call(tabList, e.target); } else { return; } if (code === left || code === up) { if (currentIndex === 0) { nextIndex = tabList.length - 1; } else { nextIndex = currentIndex - 1; } } else { if (currentIndex < tabList.length - 1) { nextIndex = currentIndex + 1; } else { nextIndex = 0; } } tabList[nextIndex].focus(); tabList[nextIndex].click(); setFocus(); }; const setFocus = () => { if (focusable.value) { isFocus.value = true; } }; const removeFocus = () => { isFocus.value = false; }; const visibilityChangeHandler = () => { const visibility = document.visibilityState; if (visibility === 'hidden') { focusable.value = false; } else if (visibility === 'visible') { setTimeout(() => { focusable.value = true; }, 50); } }; const windowBlurHandler = () => { focusable.value = false; }; const windowFocusHandler = () => { setTimeout(() => { focusable.value = true; }, 50); }; vue.onUpdated(() => { update(); }); vue.onMounted(() => { resizeEvent.addResizeListener(el$.value, update); dom.on(document, 'visibilitychange', visibilityChangeHandler); dom.on(window, 'blur', windowBlurHandler); dom.on(window, 'focus', windowFocusHandler); setTimeout(() => { scrollToActiveTab(); }, 0); }); vue.onBeforeUnmount(() => { if (el$.value) { resizeEvent.removeResizeListener(el$.value, update); } dom.off(document, 'visibilitychange', visibilityChangeHandler); dom.off(window, 'blur', windowBlurHandler); dom.off(window, 'focus', windowFocusHandler); }); return { rootTabs, scrollable, navOffset, isFocus, focusable, navScroll$, nav$, el$, sizeName, navStyle, scrollPrev, scrollNext, scrollToActiveTab, update, changeTab, setFocus, removeFocus, visibilityChangeHandler, windowBlurHandler, windowFocusHandler, }; }, render() { const { type, panes, editable, stretch, onTabClick, onTabRemove, navStyle, scrollable, scrollNext, scrollPrev, changeTab, setFocus, removeFocus, rootTabs, isFocus, } = this; const scrollBtn = scrollable ? [ vue.h('span', { class: ['el-tabs__nav-prev', scrollable.prev ? '' : 'is-disabled'], onClick: scrollPrev, }, [vue.h('i', { class: 'el-icon-arrow-left' })]), vue.h('span', { class: ['el-tabs__nav-next', scrollable.next ? '' : 'is-disabled'], onClick: scrollNext, }, [vue.h('i', { class: 'el-icon-arrow-right' })]), ] : null; const tabs = panes.map((pane, index) => { var _a, _b; let tabName = pane.props.name || pane.index || `${index}`; const closable = pane.isClosable || editable; pane.index = `${index}`; const btnClose = closable ? vue.h('span', { class: 'el-icon-close', onClick: ev => { onTabRemove(pane, ev); }, }) : null; const tabLabelContent = ((_b = (_a = pane.instance.slots).label) === null || _b === void 0 ? void 0 : _b.call(_a)) || pane.props.label; const tabindex = pane.active ? 0 : -1; return vue.h('div', { class: { 'el-tabs__item': true, [`is-${rootTabs.props.tabPosition}`]: true, 'is-active': pane.active, 'is-disabled': pane.props.disabled, 'is-closable': closable, 'is-focus': isFocus, }, id: `tab-${tabName}`, key: `tab-${tabName}`, 'aria-controls': `pane-${tabName}`, role: 'tab', 'aria-selected': pane.active, ref: `tab-${tabName}`, tabindex: tabindex, onFocus: () => { setFocus(); }, onBlur: () => { removeFocus(); }, onClick: ev => { removeFocus(); onTabClick(pane, tabName, ev); }, onKeydown: ev => { if (closable && (ev.code === aria.EVENT_CODE.delete || ev.code === aria.EVENT_CODE.backspace)) { onTabRemove(pane, ev); } }, }, [tabLabelContent, btnClose]); }); return vue.h('div', { ref: 'el$', class: ['el-tabs__nav-wrap', scrollable ? 'is-scrollable' : '', `is-${rootTabs.props.tabPosition}`], }, [ scrollBtn, vue.h('div', { class: 'el-tabs__nav-scroll', ref: 'navScroll$', }, [ vue.h('div', { class: ['el-tabs__nav', `is-${rootTabs.props.tabPosition}`, stretch && ['top', 'bottom'].includes(rootTabs.props.tabPosition) ? 'is-stretch' : ''], ref: 'nav$', style: navStyle, role: 'tablist', onKeydown: changeTab, }, [ !type ? vue.h(script, { tabs: panes, }) : null, tabs, ]), ]), ]); }, }); script$1.__file = "packages/tabs/src/tab-nav.vue"; var script$2 = vue.defineComponent({ name: 'ElTabs', components: { TabNav: script$1 }, props: { type: { type: String, default: '', }, activeName: { type: String, default: '', }, closable: Boolean, addable: Boolean, modelValue: { type: String, default: '', }, editable: Boolean, tabPosition: { type: String, default: 'top', }, beforeLeave: { type: Function, default: null, }, stretch: Boolean, }, emits: ['tab-click', 'edit', 'tab-remove', 'tab-add', 'input', 'update:modelValue'], setup(props, ctx) { const nav$ = vue.ref(null); const currentName = vue.ref(props.modelValue || props.activeName || '0'); const panes = vue.ref([]); const instance = vue.getCurrentInstance(); const paneStatesMap = {}; vue.provide('rootTabs', { props, currentName, }); vue.provide('updatePaneState', (pane) => { paneStatesMap[pane.uid] = pane; }); vue.watch(() => props.activeName, modelValue => { setCurrentName(modelValue); }); vue.watch(() => props.modelValue, modelValue => { setCurrentName(modelValue); }); vue.watch(currentName, () => { if (nav$.value) { vue.nextTick(() => { nav$.value.$nextTick(() => { nav$.value.scrollToActiveTab(); }); }); } setPaneInstances(true); }); const getPaneInstanceFromSlot = (vnode, paneInstanceList = []) => { Array.from((vnode.children || [])).forEach(node => { let type = node.type; type = type.name || type; if (type === 'ElTabPane' && node.component) { paneInstanceList.push(node.component); } else if (type === vue.Fragment || type === 'template') { getPaneInstanceFromSlot(node, paneInstanceList); } }); return paneInstanceList; }; const setPaneInstances = (isForceUpdate = false) => { if (ctx.slots.default) { const children = instance.subTree.children; const content = Array.from(children).find(({ props }) => { return props.class === 'el-tabs__content'; }); if (!content) return; const paneInstanceList = getPaneInstanceFromSlot(content).map(paneComponent => { return paneStatesMap[paneComponent.uid]; }); const panesChanged = !(paneInstanceList.length === panes.value.length && paneInstanceList.every((pane, index) => pane.uid === panes.value[index].uid)); if (isForceUpdate || panesChanged) { panes.value = paneInstanceList; } } else if (panes.value.length !== 0) { panes.value = []; } }; const changeCurrentName = value => { currentName.value = value; ctx.emit('input', value); ctx.emit('update:modelValue', value); }; const setCurrentName = value => { if (currentName.value !== value && props.beforeLeave) { const before = props.beforeLeave(value, currentName.value); if (before && before.then) { before.then(() => { changeCurrentName(value); nav$.value && nav$.value.removeFocus(); }, () => { }); } else if (before !== false) { changeCurrentName(value); } } else { changeCurrentName(value); } }; const handleTabClick = (tab, tabName, event) => { if (tab.props.disabled) return; setCurrentName(tabName); ctx.emit('tab-click', tab, event); }; const handleTabRemove = (pane, ev) => { if (pane.props.disabled) return; ev.stopPropagation(); ctx.emit('edit', pane.props.name, 'remove'); ctx.emit('tab-remove', pane.props.name); }; const handleTabAdd = () => { ctx.emit('edit', null, 'add'); ctx.emit('tab-add'); }; vue.onUpdated(() => { setPaneInstances(); }); vue.onMounted(() => { setPaneInstances(); }); return { nav$, handleTabClick, handleTabRemove, handleTabAdd, currentName, panes, }; }, render() { var _a; let { type, handleTabClick, handleTabRemove, handleTabAdd, currentName, panes, editable, addable, tabPosition, stretch, } = this; const newButton = editable || addable ? vue.h('span', { class: 'el-tabs__new-tab', tabindex: '0', onClick: handleTabAdd, onKeydown: ev => { if (ev.code === aria.EVENT_CODE.enter) { handleTabAdd(); } }, }, [vue.h('i', { class: 'el-icon-plus' })]) : null; const header = vue.h('div', { class: ['el-tabs__header', `is-${tabPosition}`], }, [ newButton, vue.h(script$1, { currentName, editable, type, panes, stretch, ref: 'nav$', onTabClick: handleTabClick, onTabRemove: handleTabRemove, }), ]); const panels = vue.h('div', { class: 'el-tabs__content', }, (_a = this.$slots) === null || _a === void 0 ? void 0 : _a.default()); return vue.h('div', { class: { 'el-tabs': true, 'el-tabs--card': type === 'card', [`el-tabs--${tabPosition}`]: true, 'el-tabs--border-card': type === 'border-card', }, }, tabPosition !== 'bottom' ? [header, panels] : [panels, header]); }, }); script$2.__file = "packages/tabs/src/tabs.vue"; script$2.install = (app) => { app.component(script$2.name, script$2); }; const _Tabs = script$2; exports.default = _Tabs;