diff --git a/components/_util/antDirective.js b/components/_util/antDirective.js index ebfcbdcab..fb89c3135 100644 --- a/components/_util/antDirective.js +++ b/components/_util/antDirective.js @@ -1,11 +1,9 @@ import { antInput } from './antInputDirective' -import { antRef } from './antRefDirective' import { antDecorator } from './FormDecoratorDirective' export default { install: (Vue, options) => { antInput(Vue) - antRef(Vue) antDecorator(Vue) }, } diff --git a/components/_util/antRefDirective.js b/components/_util/antRefDirective.js deleted file mode 100644 index 318e9655f..000000000 --- a/components/_util/antRefDirective.js +++ /dev/null @@ -1,18 +0,0 @@ -export function antRef (Vue) { - return Vue.directive('ant-ref', { - bind: function (el, binding, vnode) { - binding.value(vnode.componentInstance ? vnode.componentInstance : vnode.elm) - }, - update: function (el, binding, vnode) { - binding.value(vnode.componentInstance ? vnode.componentInstance : vnode.elm) - }, - unbind: function (el, binding, vnode) { - binding.value(null) - }, - }) -} -export default { - install: (Vue, options) => { - antRef(Vue) - }, -} diff --git a/components/tabs/TabBar.jsx b/components/tabs/TabBar.jsx index 00ca88c29..b970d66e5 100644 --- a/components/tabs/TabBar.jsx +++ b/components/tabs/TabBar.jsx @@ -1,7 +1,6 @@ import Icon from '../icon' import ScrollableInkTabBar from '../vc-tabs/src/ScrollableInkTabBar' import { cloneElement } from '../_util/vnode' - const TabBar = { functional: true, render (h, context) { @@ -12,6 +11,8 @@ const TabBar = { tabBarExtraContent, tabPosition, prefixCls, + type = 'line', + size, } = context.props const inkBarAnimated = typeof animated === 'object' ? animated.inkBar : animated @@ -29,6 +30,13 @@ const TabBar = { ) + // Additional className for style usage + const cls = { + [`${prefixCls}-${tabPosition}-bar`]: true, + [`${prefixCls}-${size}-bar`]: !!size, + [`${prefixCls}-card-bar`]: type && type.indexOf('card') >= 0, + } + const renderProps = { props: { ...context.props, @@ -39,6 +47,7 @@ const TabBar = { }, style: tabBarStyle, on: context.listeners, + class: cls, } let RenderTabBar diff --git a/components/tabs/index.js b/components/tabs/index.js index 2531b0221..b8816d244 100644 --- a/components/tabs/index.js +++ b/components/tabs/index.js @@ -1,8 +1,11 @@ +import ref from 'vue-ref' +import Vue from 'vue' import Tabs from './tabs' import TabPane from '../vc-tabs/src/TabPane' import TabContent from '../vc-tabs/src/TabContent' Tabs.TabPane = { ...TabPane, name: 'ATabPane', __ANT_TAB_PANE: true } Tabs.TabContent = { ...TabContent, name: 'ATabContent' } +Vue.use(ref, { name: 'ant-ref' }) /* istanbul ignore next */ Tabs.install = function (Vue) { diff --git a/components/tabs/style/card-style.less b/components/tabs/style/card-style.less index 480917c25..bf65d4fa0 100644 --- a/components/tabs/style/card-style.less +++ b/components/tabs/style/card-style.less @@ -1,17 +1,17 @@ -@import "../../style/themes/default"; -@import "../../style/mixins/index"; +@import '../../style/themes/default'; +@import '../../style/mixins/index'; -@tab-prefix-cls: ~"@{ant-prefix}-tabs"; +@tab-prefix-cls: ~'@{ant-prefix}-tabs'; // card style .@{tab-prefix-cls} { - &&-card > &-bar &-nav-container { + &&-card &-card-bar &-nav-container { height: @tabs-card-height; } - &&-card > &-bar &-ink-bar { + &&-card &-card-bar &-ink-bar { visibility: hidden; } - &&-card > &-bar &-tab { + &&-card &-card-bar &-tab { margin: 0; border: @border-width-base @border-style-base @border-color-split; border-bottom: 0; @@ -22,21 +22,21 @@ transition: all 0.3s @ease-in-out; line-height: @tabs-card-height - 2px; } - &&-card > &-bar &-tab-active { + &&-card &-card-bar &-tab-active { background: @component-background; border-color: @border-color-split; color: @tabs-card-active-color; padding-bottom: 1px; } - &&-card > &-bar &-tab-inactive { + &&-card &-card-bar &-tab-inactive { padding: 0; } - &&-card > &-bar &-nav-wrap { + &&-card &-card-bar &-nav-wrap { margin-bottom: 0; } - &&-card > &-bar &-tab &-close-x { + &&-card &-card-bar &-tab &-close-x { color: @text-color-secondary; - transition: all .3s; + transition: all 0.3s; font-size: @font-size-sm; margin-left: 3px; margin-right: -5px; @@ -50,15 +50,15 @@ } } - &&-card &-content > &-tabpane, - &&-editable-card &-content > &-tabpane { + &&-card &-card-content > &-tabpane, + &&-editable-card &-card-content > &-tabpane { transition: none !important; &-inactive { overflow: hidden; } } - &&-card > &-bar &-tab:hover .@{iconfont-css-prefix}-close { + &&-card &-card-bar &-tab:hover .@{iconfont-css-prefix}-close { opacity: 1; } @@ -76,7 +76,7 @@ border: @border-width-base @border-style-base @border-color-split; font-size: 12px; color: @text-color; - transition: all .3s; + transition: all 0.3s; &:hover { color: @tabs-card-active-color; border-color: @tabs-card-active-color; @@ -93,7 +93,8 @@ } // https://github.com/ant-design/ant-design/issues/4669 - &-vertical&-card > .@{tab-prefix-cls}-bar { + &-vertical&-card &-card-bar&-left-bar, + &-vertical&-card &-card-bar&-right-bar { .@{tab-prefix-cls}-nav-container { height: auto; } @@ -112,7 +113,7 @@ } } - &-vertical&-card&-left > .@{tab-prefix-cls}-bar { + &-vertical&-card&-left &-card-bar&-left-bar { .@{tab-prefix-cls}-nav-wrap { margin-right: 0; } @@ -127,7 +128,7 @@ } } - &-vertical&-card&-right > .@{tab-prefix-cls}-bar { + &-vertical&-card&-right &-card-bar&-right-bar { .@{tab-prefix-cls}-nav-wrap { margin-left: 0; } @@ -143,15 +144,15 @@ } // https://github.com/ant-design/ant-design/issues/9104 - &&-card&-bottom > &-bar &-tab { + & &-card-bar&-bottom-bar &-tab { border-bottom: @border-width-base @border-style-base @border-color-split; border-top: 0; border-radius: 0 0 @border-radius-base @border-radius-base; } - &&-card&-bottom > &-bar &-tab-active { + & &-card-bar&-bottom-bar &-tab-active { color: @primary-color; padding-bottom: 0; padding-top: 1px; } -} +} \ No newline at end of file diff --git a/components/tabs/style/index.less b/components/tabs/style/index.less index 8b650f49d..166b7a749 100644 --- a/components/tabs/style/index.less +++ b/components/tabs/style/index.less @@ -1,8 +1,8 @@ -@import "../../style/themes/default"; -@import "../../style/mixins/index"; -@import "./card-style"; +@import '../../style/themes/default'; +@import '../../style/mixins/index'; +@import './card-style'; -@tab-prefix-cls: ~"@{ant-prefix}-tabs"; +@tab-prefix-cls: ~'@{ant-prefix}-tabs'; .@{tab-prefix-cls} { .reset-component; @@ -25,7 +25,7 @@ border-bottom: @border-width-base @border-style-base @border-color-split; margin: @tabs-bar-margin; outline: none; - transition: padding .3s @ease-in-out; + transition: padding 0.3s @ease-in-out; } &-nav-container { @@ -36,7 +36,7 @@ position: relative; white-space: nowrap; margin-bottom: -1px; - transition: padding .3s @ease-in-out; + transition: padding 0.3s @ease-in-out; .clearfix; &-scrolling { @@ -46,17 +46,19 @@ } // https://github.com/ant-design/ant-design/issues/9104 - &-bottom &-bar { + &-bottom &-bottom-bar { + margin-bottom: 0; + margin-top: 16px; border-bottom: none; border-top: @border-width-base @border-style-base @border-color-split; } - &-bottom &-ink-bar { + &-bottom &-bottom-bar &-ink-bar { bottom: auto; top: 1px; } - &-bottom &-nav-container { + &-bottom &-bottom-bar &-nav-container { margin-bottom: 0; margin-top: -1px; } @@ -73,7 +75,7 @@ position: absolute; text-align: center; color: @text-color-secondary; - transition: width .3s @ease-in-out, opacity .3s @ease-in-out, color .3s @ease-in-out; + transition: width 0.3s @ease-in-out, opacity 0.3s @ease-in-out, color 0.3s @ease-in-out; opacity: 0; pointer-events: none; @@ -149,7 +151,7 @@ &:before, &:after { display: table; - content: " "; + content: ' '; } &:after { @@ -197,50 +199,47 @@ } } - &-large { - > .@{tab-prefix-cls}-bar { - .@{tab-prefix-cls}-nav-container { - font-size: @tabs-title-font-size-lg; - } - .@{tab-prefix-cls}-tab { - padding: 16px; - } + .@{tab-prefix-cls}-large-bar { + .@{tab-prefix-cls}-nav-container { + font-size: @tabs-title-font-size-lg; + } + .@{tab-prefix-cls}-tab { + padding: 16px; } } - &-small { - > .@{tab-prefix-cls}-bar { - .@{tab-prefix-cls}-nav-container { - font-size: @tabs-title-font-size-sm; - } - .@{tab-prefix-cls}-tab { - padding: 8px 16px; - } + .@{tab-prefix-cls}-small-bar { + .@{tab-prefix-cls}-nav-container { + font-size: @tabs-title-font-size-sm; + } + .@{tab-prefix-cls}-tab { + padding: 8px 16px; } } - &:not(&-vertical) { - > .@{tab-prefix-cls}-content { + // Horizontal Content + .@{tab-prefix-cls}-top-content, + .@{tab-prefix-cls}-bottom-content { + width: 100%; + + > .@{tab-prefix-cls}-tabpane { + flex-shrink: 0; width: 100%; + transition: opacity 0.45s; + opacity: 1; + } - > .@{tab-prefix-cls}-tabpane { - flex-shrink: 0; - width: 100%; - transition: opacity .45s; - opacity: 1; - } - - > .@{tab-prefix-cls}-tabpane-inactive { - opacity: 0; - height: 0; - padding: 0 !important; - pointer-events: none; - input { - visibility: hidden; - } + > .@{tab-prefix-cls}-tabpane-inactive { + opacity: 0; + height: 0; + padding: 0 !important; + pointer-events: none; + input { + visibility: hidden; } } - > .@{tab-prefix-cls}-content-animated { + + &.@{tab-prefix-cls}-content-animated { display: flex; flex-direction: row; will-change: margin-left; @@ -248,162 +247,167 @@ } } - &-vertical { - > .@{tab-prefix-cls}-bar { - border-bottom: 0; - height: 100%; - &-tab-prev, &-tab-next { - width: @tabs-scrolling-size; - height: 0; - transition: height .3s @ease-in-out, opacity .3s @ease-in-out, color .3s @ease-in-out; - } - &-tab-prev.@{tab-prefix-cls}-tab-arrow-show, - &-tab-next.@{tab-prefix-cls}-tab-arrow-show { - width: 100%; - height: @tabs-scrolling-size; - } + // Vertical Bar + .@{tab-prefix-cls}-left-bar, + .@{tab-prefix-cls}-right-bar { + border-bottom: 0; + height: 100%; + &-tab-prev, + &-tab-next { + width: @tabs-scrolling-size; + height: 0; + transition: height 0.3s @ease-in-out, opacity 0.3s @ease-in-out, color 0.3s @ease-in-out; + } + &-tab-prev.@{tab-prefix-cls}-tab-arrow-show, + &-tab-next.@{tab-prefix-cls}-tab-arrow-show { + width: 100%; + height: @tabs-scrolling-size; + } - .@{tab-prefix-cls}-tab { - float: none; - margin: @tabs-vertical-margin; - padding: @tabs-vertical-padding; - display: block; + .@{tab-prefix-cls}-tab { + float: none; + margin: @tabs-vertical-margin; + padding: @tabs-vertical-padding; + display: block; - &:last-child { - margin-bottom: 0; - } - } - - .@{tab-prefix-cls}-extra-content { - text-align: center; - } - - .@{tab-prefix-cls}-nav-scroll { - width: auto; - } - - .@{tab-prefix-cls}-nav-container, - .@{tab-prefix-cls}-nav-wrap { - height: 100%; - } - - .@{tab-prefix-cls}-nav-container { + &:last-child { margin-bottom: 0; - - &.@{tab-prefix-cls}-nav-container-scrolling { - padding: @tabs-scrolling-size 0; - } - } - - .@{tab-prefix-cls}-nav-wrap { - margin-bottom: 0; - } - - .@{tab-prefix-cls}-nav { - width: 100%; - } - - .@{tab-prefix-cls}-ink-bar { - width: 2px; - top: 0; - left: auto; - height: auto; - bottom: auto; - } - - .@{tab-prefix-cls}-tab-next { - width: 100%; - bottom: 0; - height: @tabs-scrolling-size; - } - - .@{tab-prefix-cls}-tab-prev { - top: 0; - width: 100%; - height: @tabs-scrolling-size; } } - > .@{tab-prefix-cls}-content { - overflow: hidden; + .@{tab-prefix-cls}-extra-content { + text-align: center; + } + + .@{tab-prefix-cls}-nav-scroll { width: auto; - margin-top: 0 !important; } - } - &-vertical&-left { - > .@{tab-prefix-cls}-bar { - float: left; - border-right: @border-width-base @border-style-base @border-color-split; - margin-right: -1px; + .@{tab-prefix-cls}-nav-container, + .@{tab-prefix-cls}-nav-wrap { + height: 100%; + } + + .@{tab-prefix-cls}-nav-container { margin-bottom: 0; - .@{tab-prefix-cls}-tab { - text-align: right; - } - .@{tab-prefix-cls}-nav-container { - margin-right: -1px; - } - .@{tab-prefix-cls}-nav-wrap { - margin-right: -1px; - } - .@{tab-prefix-cls}-ink-bar { - right: 1px; - } - } - > .@{tab-prefix-cls}-content { - padding-left: 24px; - border-left: @border-width-base @border-style-base @border-color-split; - } - } - &-vertical&-right { - > .@{tab-prefix-cls}-bar { - float: right; - border-left: @border-width-base @border-style-base @border-color-split; - margin-left: -1px; + &.@{tab-prefix-cls}-nav-container-scrolling { + padding: @tabs-scrolling-size 0; + } + } + + .@{tab-prefix-cls}-nav-wrap { margin-bottom: 0; - .@{tab-prefix-cls}-nav-container { - margin-left: -1px; - } - .@{tab-prefix-cls}-nav-wrap { - margin-left: -1px; - } - .@{tab-prefix-cls}-ink-bar { - left: 1px; - } } - > .@{tab-prefix-cls}-content { - padding-right: 24px; - border-right: @border-width-base @border-style-base @border-color-split; + + .@{tab-prefix-cls}-nav { + width: 100%; + } + + .@{tab-prefix-cls}-ink-bar { + width: 2px; + top: 0; + left: auto; + height: auto; + bottom: auto; + } + + .@{tab-prefix-cls}-tab-next { + width: 100%; + bottom: 0; + height: @tabs-scrolling-size; + } + + .@{tab-prefix-cls}-tab-prev { + top: 0; + width: 100%; + height: @tabs-scrolling-size; } } - &-bottom > &-bar { + // Vertical Content + .@{tab-prefix-cls}-left-content, + .@{tab-prefix-cls}-right-content { + overflow: hidden; + width: auto; + margin-top: 0 !important; + } + + // Vertical - Left + .@{tab-prefix-cls}-left-bar { + float: left; + border-right: @border-width-base @border-style-base @border-color-split; + margin-right: -1px; margin-bottom: 0; - margin-top: 16px; + .@{tab-prefix-cls}-tab { + text-align: right; + } + .@{tab-prefix-cls}-nav-container { + margin-right: -1px; + } + .@{tab-prefix-cls}-nav-wrap { + margin-right: -1px; + } + .@{tab-prefix-cls}-ink-bar { + right: 1px; + } + } + .@{tab-prefix-cls}-left-content { + padding-left: 24px; + border-left: @border-width-base @border-style-base @border-color-split; + } + + // Vertical - Right + .@{tab-prefix-cls}-right-bar { + float: right; + border-left: @border-width-base @border-style-base @border-color-split; + margin-left: -1px; + margin-bottom: 0; + .@{tab-prefix-cls}-nav-container { + margin-left: -1px; + } + .@{tab-prefix-cls}-nav-wrap { + margin-left: -1px; + } + .@{tab-prefix-cls}-ink-bar { + left: 1px; + } + } + .@{tab-prefix-cls}-right-content { + padding-right: 24px; + border-right: @border-width-base @border-style-base @border-color-split; } } .@{tab-prefix-cls}-top .@{tab-prefix-cls}-ink-bar-animated, .@{tab-prefix-cls}-bottom .@{tab-prefix-cls}-ink-bar-animated { - transition: transform .3s @ease-in-out, width .3s @ease-in-out, left .3s @ease-in-out; + transition: transform 0.3s @ease-in-out, width 0.3s @ease-in-out, left 0.3s @ease-in-out; } .@{tab-prefix-cls}-left .@{tab-prefix-cls}-ink-bar-animated, .@{tab-prefix-cls}-right .@{tab-prefix-cls}-ink-bar-animated { - transition: transform .3s @ease-in-out, height .3s @ease-in-out, top .3s @ease-in-out; + transition: transform 0.3s @ease-in-out, height 0.3s @ease-in-out, top 0.3s @ease-in-out; +} + +// No animation +.tabs-no-animation() { + > .@{tab-prefix-cls}-content-animated { + transform: none !important; + margin-left: 0 !important; + } + > .@{tab-prefix-cls}-tabpane-inactive { + display: none; + } } .no-flex, -.@{tab-prefix-cls}-no-animation, -.@{tab-prefix-cls}-vertical { +.@{tab-prefix-cls}-no-animation { > .@{tab-prefix-cls}-content { - &-animated { - transform: none !important; - margin-left: 0 !important; - } - > .@{tab-prefix-cls}-tabpane-inactive { - display: none; - } + .tabs-no-animation(); } } + +.@{tab-prefix-cls}-left-content, +.@{tab-prefix-cls}-right-content { + .tabs-no-animation(); +} \ No newline at end of file diff --git a/components/tabs/tabs.jsx b/components/tabs/tabs.jsx index 100edcfcd..4ee79e0b8 100644 --- a/components/tabs/tabs.jsx +++ b/components/tabs/tabs.jsx @@ -23,7 +23,7 @@ export default { ]), destroyInactiveTabPane: PropTypes.bool.def(false), type: PropTypes.oneOf(['line', 'card', 'editable-card']), - tabPosition: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), + tabPosition: PropTypes.oneOf(['top', 'right', 'bottom', 'left']).def('top'), size: PropTypes.oneOf(['default', 'small', 'large']), animated: PropTypes.oneOfType([ PropTypes.bool, @@ -70,6 +70,7 @@ export default { }, render () { + const props = getOptionProps(this) const { prefixCls, size, @@ -78,8 +79,7 @@ export default { animated = true, hideAdd, renderTabBar, - } = this.$props - + } = props const children = filterEmpty(this.$slots.default) let tabBarExtraContent = getComponentFromProp(this, 'tabBarExtraContent') @@ -87,7 +87,7 @@ export default { // card tabs should not have animation if (type !== 'line') { - tabPaneAnimated = animated === undefined ? false : tabPaneAnimated + tabPaneAnimated = 'animated' in props ? tabPaneAnimated : false } const cls = { [`${prefixCls}-vertical`]: tabPosition === 'left' || tabPosition === 'right', @@ -135,9 +135,7 @@ export default { } tabBarExtraContent = tabBarExtraContent ? ( -
- {tabBarExtraContent} -
+
{tabBarExtraContent}
) : null const renderTabBarSlot = renderTabBar || this.$scopedSlots.renderTabBar @@ -151,13 +149,16 @@ export default { ...this.$listeners, }, } - + const contentCls = { + [`${prefixCls}-${tabPosition}-content`]: true, + [`${prefixCls}-card-content`]: type.indexOf('card') >= 0, + } const tabsProps = { props: { ...getOptionProps(this), tabBarPosition: tabPosition, renderTabBar: () => , - renderTabContent: () => , + renderTabContent: () => , children: childrenWithClose.length > 0 ? childrenWithClose : children, __propsSymbol__: Symbol(), }, diff --git a/components/vc-select/DropdownMenu.jsx b/components/vc-select/DropdownMenu.jsx index e7576e798..66ace3f4a 100644 --- a/components/vc-select/DropdownMenu.jsx +++ b/components/vc-select/DropdownMenu.jsx @@ -150,7 +150,7 @@ export default { foundFirst = true return cloneElement(item, { directives: [{ - name: 'ref', + name: 'ant-ref', value: ref => { this.firstActiveItem = ref }, diff --git a/components/vc-select/Select.jsx b/components/vc-select/Select.jsx index 2d21c0e44..721220ae7 100644 --- a/components/vc-select/Select.jsx +++ b/components/vc-select/Select.jsx @@ -726,7 +726,7 @@ const Select = { })}
{}), getRef: PropTypes.func.def(() => {}), + panels: PropTypes.array, + activeKey: PropTypes.string, }, updated () { this.$nextTick(function () { @@ -108,7 +114,7 @@ export default { class={classes} key='inkBar' {...{ directives: [{ - name: 'ref', + name: 'ant-ref', value: this.saveRef('inkBar'), }] }} /> diff --git a/components/vc-tabs/src/ScrollableTabBarNode.jsx b/components/vc-tabs/src/ScrollableTabBarNode.jsx index b3962c08c..0de86284d 100644 --- a/components/vc-tabs/src/ScrollableTabBarNode.jsx +++ b/components/vc-tabs/src/ScrollableTabBarNode.jsx @@ -1,9 +1,10 @@ -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' import { getComponentFromProp } from '../../_util/props-util' +import { setTransform, isTransformSupported } from './utils' function noop () { } @@ -88,7 +89,9 @@ export default { const navNode = this.$props.getRef('nav') const navTabsContainer = this.$props.getRef('navTabsContainer') const navNodeWH = this.getScrollWH(navTabsContainer || navNode) - const containerWH = this.getOffsetWH(this.$props.getRef('container')) + // Add 1px to fix `offsetWidth` with decimal in Chrome not correct handle + // https://github.com/ant-design/ant-design/issues/13423 + const containerWH = this.getOffsetWH(this.$props.getRef('container')) + 1 const navWrapNodeWH = this.getOffsetWH(this.$props.getRef('navWrap')) let { offset } = this const minOffset = containerWH - navNodeWH @@ -169,16 +172,14 @@ export default { value: `${target}px`, } } + } else if (transformSupported) { + navOffset = { + value: `translate3d(${target}px,0,0)`, + } } else { - if (transformSupported) { - navOffset = { - value: `translate3d(${target}px,0,0)`, - } - } else { - navOffset = { - name: 'left', - value: `${target}px`, - } + navOffset = { + name: 'left', + value: `${target}px`, } } if (transformSupported) { @@ -327,7 +328,7 @@ export default { }} key='container' {...{ directives: [{ - name: 'ref', + name: 'ant-ref', value: this.saveRef('container'), }] }} > @@ -336,7 +337,7 @@ export default {
@@ -344,7 +345,7 @@ export default {
diff --git a/components/vc-tabs/src/Sentinel.jsx b/components/vc-tabs/src/Sentinel.jsx new file mode 100644 index 000000000..89b4ce168 --- /dev/null +++ b/components/vc-tabs/src/Sentinel.jsx @@ -0,0 +1,44 @@ +import PropTypes from '../../_util/vue-types' +import KeyCode from '../../_util/KeyCode' + +const sentinelStyle = { width: 0, height: 0, overflow: 'hidden' } +export default { + name: 'Sentinel', + props: { + setRef: PropTypes.func, + prevElement: PropTypes.any, + nextElement: PropTypes.any, + }, + methods: { + onKeyDown ({ target, which, shiftKey }) { + const { nextElement, prevElement } = this.$props + if (which !== KeyCode.TAB || document.activeElement !== target) return + + // Tab next + if (!shiftKey && nextElement) { + nextElement.focus() + } + + // Tab prev + if (shiftKey && prevElement) { + prevElement.focus() + } + }, + }, + render () { + const { setRef } = this.$props + + return ( +
{this.$slots.default}
+ ) + }, +} diff --git a/components/vc-tabs/src/TabBarRootNode.jsx b/components/vc-tabs/src/TabBarRootNode.jsx index 7145084a0..2cf29febc 100644 --- a/components/vc-tabs/src/TabBarRootNode.jsx +++ b/components/vc-tabs/src/TabBarRootNode.jsx @@ -50,7 +50,7 @@ export default { tabIndex='0' onKeydown={onKeyDown} {...{ directives: [{ - name: 'ref', + name: 'ant-ref', value: this.saveRef('root'), }] }} > diff --git a/components/vc-tabs/src/TabBarTabsNode.jsx b/components/vc-tabs/src/TabBarTabsNode.jsx index 3ae896d91..67954acfd 100644 --- a/components/vc-tabs/src/TabBarTabsNode.jsx +++ b/components/vc-tabs/src/TabBarTabsNode.jsx @@ -3,7 +3,7 @@ import warning from 'warning' import PropTypes from '../../_util/vue-types' import BaseMixin from '../../_util/BaseMixin' import { getOptionProps, getComponentFromProp } from '../../_util/props-util' - +import { isVertical } from './utils' function noop () { } export default { @@ -17,9 +17,13 @@ export default { onTabClick: PropTypes.func, saveRef: PropTypes.func.def(noop), getRef: PropTypes.func.def(noop), + tabBarPosition: PropTypes.string, }, render () { - const { panels: children, activeKey, prefixCls, tabBarGutter, saveRef } = this.$props + const { panels: children, + activeKey, prefixCls, + tabBarGutter, saveRef, + tabBarPosition } = this.$props const rst = [] children.forEach((child, index) => { @@ -42,11 +46,16 @@ export default { const directives = [] if (activeKey === key) { directives.push({ - name: 'ref', + name: 'ant-ref', value: saveRef('activeTab'), }) } const tab = getComponentFromProp(child, 'tab') + let gutter = tabBarGutter && index === children.length - 1 ? 0 : tabBarGutter + gutter = typeof gutter === 'number' ? `${gutter}px` : gutter + const style = { + [isVertical(tabBarPosition) ? 'marginBottom' : 'marginRight']: gutter, + } warning(tab !== undefined, 'There must be `tab` property or slot on children of Tabs.') rst.push(
{tab} @@ -67,7 +76,7 @@ export default { return (
{rst}
diff --git a/components/vc-tabs/src/TabPane.jsx b/components/vc-tabs/src/TabPane.jsx index 8a08f6d9c..f82fbd192 100644 --- a/components/vc-tabs/src/TabPane.jsx +++ b/components/vc-tabs/src/TabPane.jsx @@ -1,5 +1,7 @@ import PropTypes from '../../_util/vue-types' import { getComponentFromProp } from '../../_util/props-util' +import Sentinel from './Sentinel' + export default { name: 'TabPane', props: { @@ -12,6 +14,9 @@ export default { closable: PropTypes.bool, disabled: PropTypes.bool, }, + inject: { + sentinelContext: { default: {}}, + }, render () { const { destroyInactiveTabPane, active, forceRender, @@ -27,13 +32,33 @@ export default { [`${prefixCls}-active`]: active, } const isRender = destroyInactiveTabPane ? active : this._isActived + const shouldRender = isRender || forceRender + const { sentinelStart, sentinelEnd, setPanelSentinelStart, setPanelSentinelEnd } = this.sentinelContext + let panelSentinelStart + let panelSentinelEnd + if (active && shouldRender) { + panelSentinelStart = ( + + ) + panelSentinelEnd = ( + + ) + } return (
- {isRender || forceRender ? children : placeholder} + {panelSentinelStart} + {shouldRender ? children : placeholder} + {panelSentinelEnd}
) }, diff --git a/components/vc-tabs/src/Tabs.jsx b/components/vc-tabs/src/Tabs.jsx index 92b50a2eb..aea8df2a0 100644 --- a/components/vc-tabs/src/Tabs.jsx +++ b/components/vc-tabs/src/Tabs.jsx @@ -1,9 +1,11 @@ import omit from 'omit.js' import BaseMixin from '../../_util/BaseMixin' import PropTypes from '../../_util/vue-types' +import raf from 'raf' import KeyCode from './KeyCode' import { getOptionProps } from '../../_util/props-util' import { cloneElement } from '../../_util/vnode' +import Sentinel from './Sentinel' function getDefaultActiveKey (props) { let activeKey @@ -55,6 +57,11 @@ export default { _activeKey: activeKey, } }, + provide () { + return { + sentinelContext: this, + } + }, watch: { __propsSymbol__ () { const nextProps = getOptionProps(this) @@ -70,6 +77,10 @@ export default { } }, }, + beforeDestroy () { + this.destroy = true + raf.cancel(this.sentinelId) + }, methods: { onTabClick (activeKey, e) { if (this.tabBar.componentOptions && @@ -93,6 +104,35 @@ export default { } }, + onScroll ({ target, currentTarget }) { + if (target === currentTarget && target.scrollLeft > 0) { + target.scrollLeft = 0 + } + }, + + // Sentinel for tab index + setSentinelStart (node) { + this.sentinelStart = node + }, + + setSentinelEnd (node) { + this.sentinelEnd = node + }, + + setPanelSentinelStart (node) { + if (node !== this.panelSentinelStart) { + this.updateSentinelContext() + } + this.panelSentinelStart = node + }, + + setPanelSentinelEnd (node) { + if (node !== this.panelSentinelEnd) { + this.updateSentinelContext() + } + this.panelSentinelEnd = node + }, + setActiveKey (activeKey) { if (this.$data._activeKey !== activeKey) { const props = getOptionProps(this) @@ -130,6 +170,14 @@ export default { }) return ret }, + updateSentinelContext () { + if (this.destroy) return + + raf.cancel(this.sentinelId) + this.sentinelId = raf(() => { + this.$forceUpdate() + }) + }, }, render () { const props = this.$props @@ -147,40 +195,62 @@ export default { } this.tabBar = renderTabBar() - const contents = [ - cloneElement(this.tabBar, { - props: { - prefixCls, - navWrapper, - tabBarPosition, - panels: props.children, - activeKey: this.$data._activeKey, - }, - on: { - keydown: this.onNavKeyDown, - tabClick: this.onTabClick, - }, - key: 'tabBar', - }), - cloneElement(renderTabContent(), { - props: { - prefixCls, - tabBarPosition, - activeKey: this.$data._activeKey, - destroyInactiveTabPane, - }, - on: { - change: this.setActiveKey, - }, - children: props.children, - key: 'tabContent', - }), - ] + const tabBar = cloneElement(this.tabBar, { + props: { + prefixCls, + navWrapper, + tabBarPosition, + panels: props.children, + activeKey: this.$data._activeKey, + }, + on: { + keydown: this.onNavKeyDown, + tabClick: this.onTabClick, + }, + key: 'tabBar', + }) + const tabContent = cloneElement(renderTabContent(), { + props: { + prefixCls, + tabBarPosition, + activeKey: this.$data._activeKey, + destroyInactiveTabPane, + }, + on: { + change: this.setActiveKey, + }, + children: props.children, + key: 'tabContent', + }) + + const sentinelStart = ( + + ) + const sentinelEnd = ( + + ) + + const contents = [] + if (tabBarPosition === 'bottom') { - contents.reverse() + contents.push(sentinelStart, tabContent, sentinelEnd, tabBar) + } else { + contents.push(tabBar, sentinelStart, tabContent, sentinelEnd) + } + const listeners = { + ...omit(this.$listeners, ['change']), + scroll: this.onScroll, } return ( -
+
{contents}
) diff --git a/components/vc-tabs/src/index.js b/components/vc-tabs/src/index.js index d646f05ac..368b0a61e 100755 --- a/components/vc-tabs/src/index.js +++ b/components/vc-tabs/src/index.js @@ -1,7 +1,11 @@ -// based on rc-tabs 9.4.8 +// based on rc-tabs 9.5.7 +import ref from 'vue-ref' +import Vue from 'vue' import Tabs from './Tabs' import TabPane from './TabPane' import TabContent from './TabContent' +Vue.use(ref, { name: 'ant-ref' }) + export default Tabs export { TabPane, TabContent } diff --git a/components/vc-tabs/src/utils.js b/components/vc-tabs/src/utils.js index 80f2712a8..bfe509f6e 100644 --- a/components/vc-tabs/src/utils.js +++ b/components/vc-tabs/src/utils.js @@ -94,11 +94,11 @@ function getTypeValue (start, current, end, tabNode, wrapperNode) { const { childNodes } = tabNode.parentNode Array.prototype.some.call(childNodes, (node) => { - const style = getComputedStyle(node) + const style = window.getComputedStyle(node) if (node !== tabNode) { total += toNum(style, `margin-${start}`) - total += toNum(style, `margin-${end}`) total += node[current] + total += toNum(style, `margin-${end}`) if (style.boxSizing === 'content-box') { total += toNum(style, `border-${start}-width`) + toNum(style, `border-${end}-width`)