refactor: tabs
parent
53625976b8
commit
cd9f592c2e
|
@ -42,38 +42,57 @@ import { defineComponent, ref } from 'vue';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
return {
|
return {
|
||||||
activeKey: ref(1),
|
activeKey: ref('2'),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.card-container {
|
.card-container p {
|
||||||
background: #f5f5f5;
|
margin: 0;
|
||||||
overflow: hidden;
|
|
||||||
padding: 24px;
|
|
||||||
}
|
}
|
||||||
.card-container > .ant-tabs-card > .ant-tabs-content {
|
.card-container > .ant-tabs-card .ant-tabs-content {
|
||||||
height: 120px;
|
height: 120px;
|
||||||
margin-top: -16px;
|
margin-top: -16px;
|
||||||
}
|
}
|
||||||
|
.card-container > .ant-tabs-card .ant-tabs-content > .ant-tabs-tabpane {
|
||||||
.card-container > .ant-tabs-card > .ant-tabs-content > .ant-tabs-tabpane {
|
|
||||||
background: #fff;
|
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
|
||||||
|
|
||||||
.card-container > .ant-tabs-card > .ant-tabs-bar {
|
|
||||||
border-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container > .ant-tabs-card > .ant-tabs-bar .ant-tabs-tab {
|
|
||||||
border-color: transparent;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container > .ant-tabs-card > .ant-tabs-bar .ant-tabs-tab-active {
|
|
||||||
border-color: #fff;
|
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
.card-container > .ant-tabs-card > .ant-tabs-nav::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.card-container > .ant-tabs-card .ant-tabs-tab,
|
||||||
|
[data-theme='compact'] .card-container > .ant-tabs-card .ant-tabs-tab {
|
||||||
|
background: transparent;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
.card-container > .ant-tabs-card .ant-tabs-tab-active,
|
||||||
|
[data-theme='compact'] .card-container > .ant-tabs-card .ant-tabs-tab-active {
|
||||||
|
background: #fff;
|
||||||
|
border-color: #fff;
|
||||||
|
}
|
||||||
|
#components-tabs-demo-card-top .code-box-demo {
|
||||||
|
padding: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
[data-theme='compact'] .card-container > .ant-tabs-card .ant-tabs-content {
|
||||||
|
height: 120px;
|
||||||
|
margin-top: -8px;
|
||||||
|
}
|
||||||
|
[data-theme='dark'] .card-container > .ant-tabs-card .ant-tabs-tab {
|
||||||
|
background: transparent;
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
[data-theme='dark'] #components-tabs-demo-card-top .code-box-demo {
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
[data-theme='dark'] .card-container > .ant-tabs-card .ant-tabs-content > .ant-tabs-tabpane {
|
||||||
|
background: #141414;
|
||||||
|
}
|
||||||
|
[data-theme='dark'] .card-container > .ant-tabs-card .ant-tabs-tab-active {
|
||||||
|
background: #141414;
|
||||||
|
border-color: #141414;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import type { Tab } from './interface';
|
import type { Tab } from './interface';
|
||||||
import type { PropType, InjectionKey } from 'vue';
|
import type { PropType, InjectionKey, Ref } from 'vue';
|
||||||
import { provide, inject, defineComponent } from 'vue';
|
import { provide, inject, defineComponent, toRefs, ref } from 'vue';
|
||||||
|
|
||||||
export interface TabContextProps {
|
export interface TabContextProps {
|
||||||
tabs: Tab[];
|
tabs: Ref<Tab[]>;
|
||||||
prefixCls: string;
|
prefixCls: Ref<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TabsContextKey: InjectionKey<TabContextProps> = Symbol('tabsContextKey');
|
const TabsContextKey: InjectionKey<TabContextProps> = Symbol('tabsContextKey');
|
||||||
|
@ -14,7 +14,7 @@ export const useProvideTabs = (props: TabContextProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useInjectTabs = () => {
|
export const useInjectTabs = () => {
|
||||||
return inject(TabsContextKey, { tabs: [], prefixCls: undefined });
|
return inject(TabsContextKey, { tabs: ref([]), prefixCls: ref() });
|
||||||
};
|
};
|
||||||
|
|
||||||
const TabsContextProvider = defineComponent({
|
const TabsContextProvider = defineComponent({
|
||||||
|
@ -25,7 +25,7 @@ const TabsContextProvider = defineComponent({
|
||||||
prefixCls: { type: String, default: undefined },
|
prefixCls: { type: String, default: undefined },
|
||||||
},
|
},
|
||||||
setup(props, { slots }) {
|
setup(props, { slots }) {
|
||||||
useProvideTabs(props);
|
useProvideTabs(toRefs(props));
|
||||||
return () => slots.default?.();
|
return () => slots.default?.();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +16,7 @@ export interface OperationNodeProps {
|
||||||
tabs: Tab[];
|
tabs: Tab[];
|
||||||
rtl: boolean;
|
rtl: boolean;
|
||||||
tabBarGutter?: number;
|
tabBarGutter?: number;
|
||||||
activeKey: string;
|
activeKey: Key;
|
||||||
mobile: boolean;
|
mobile: boolean;
|
||||||
moreIcon?: VueNode;
|
moreIcon?: VueNode;
|
||||||
moreTransitionName?: string;
|
moreTransitionName?: string;
|
||||||
|
@ -34,7 +34,7 @@ export default defineComponent({
|
||||||
tabs: { type: Object as PropType<Tab[]> },
|
tabs: { type: Object as PropType<Tab[]> },
|
||||||
rtl: { type: Boolean },
|
rtl: { type: Boolean },
|
||||||
tabBarGutter: { type: Number },
|
tabBarGutter: { type: Number },
|
||||||
activeKey: { type: String },
|
activeKey: { type: [String, Number] },
|
||||||
mobile: { type: Boolean },
|
mobile: { type: Boolean },
|
||||||
moreIcon: PropTypes.any,
|
moreIcon: PropTypes.any,
|
||||||
moreTransitionName: { type: String },
|
moreTransitionName: { type: String },
|
||||||
|
@ -47,7 +47,7 @@ export default defineComponent({
|
||||||
setup(props, { attrs, slots }) {
|
setup(props, { attrs, slots }) {
|
||||||
// ======================== Dropdown ========================
|
// ======================== Dropdown ========================
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [selectedKey, setSelectedKey] = useState<string>(null);
|
const [selectedKey, setSelectedKey] = useState<Key>(null);
|
||||||
const selectOffset = (offset: -1 | 1) => {
|
const selectOffset = (offset: -1 | 1) => {
|
||||||
const enabledTabs = props.tabs.filter(tab => !tab.disabled);
|
const enabledTabs = props.tabs.filter(tab => !tab.disabled);
|
||||||
let selectedIndex = enabledTabs.findIndex(tab => tab.key === selectedKey.value) || 0;
|
let selectedIndex = enabledTabs.findIndex(tab => tab.key === selectedKey.value) || 0;
|
||||||
|
|
|
@ -32,7 +32,7 @@ const tabNavListProps = () => {
|
||||||
return {
|
return {
|
||||||
id: { type: String },
|
id: { type: String },
|
||||||
tabPosition: { type: String as PropType<TabPosition> },
|
tabPosition: { type: String as PropType<TabPosition> },
|
||||||
activeKey: { type: String },
|
activeKey: { type: [String, Number] },
|
||||||
rtl: { type: Boolean },
|
rtl: { type: Boolean },
|
||||||
panes: PropTypes.any,
|
panes: PropTypes.any,
|
||||||
animated: { type: Object as PropType<AnimatedConfig>, default: undefined as AnimatedConfig },
|
animated: { type: Object as PropType<AnimatedConfig>, default: undefined as AnimatedConfig },
|
||||||
|
@ -66,7 +66,7 @@ export default defineComponent({
|
||||||
slots: ['panes', 'moreIcon', 'extra'],
|
slots: ['panes', 'moreIcon', 'extra'],
|
||||||
emits: ['tabClick', 'tabScroll'],
|
emits: ['tabClick', 'tabScroll'],
|
||||||
setup(props, { attrs, slots }) {
|
setup(props, { attrs, slots }) {
|
||||||
const tabsContext = useInjectTabs();
|
const { tabs, prefixCls } = useInjectTabs();
|
||||||
const tabsWrapperRef = ref<HTMLDivElement>();
|
const tabsWrapperRef = ref<HTMLDivElement>();
|
||||||
const tabListRef = ref<HTMLDivElement>();
|
const tabListRef = ref<HTMLDivElement>();
|
||||||
const operationsRef = ref<{ $el: HTMLDivElement }>();
|
const operationsRef = ref<{ $el: HTMLDivElement }>();
|
||||||
|
@ -98,15 +98,10 @@ export default defineComponent({
|
||||||
const [addHeight, setAddHeight] = useState<number>(0);
|
const [addHeight, setAddHeight] = useState<number>(0);
|
||||||
|
|
||||||
const [tabSizes, setTabSizes] = useRafState<TabSizeMap>(new Map());
|
const [tabSizes, setTabSizes] = useRafState<TabSizeMap>(new Map());
|
||||||
const tabOffsets = useOffsets(
|
const tabOffsets = useOffsets(tabs, tabSizes);
|
||||||
computed(() => tabsContext.tabs),
|
|
||||||
tabSizes,
|
|
||||||
);
|
|
||||||
|
|
||||||
// ========================== Util =========================
|
// ========================== Util =========================
|
||||||
const operationsHiddenClassName = computed(
|
const operationsHiddenClassName = computed(() => `${prefixCls.value}-nav-operations-hidden`);
|
||||||
() => `${tabsContext.prefixCls}-nav-operations-hidden`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const transformMin = ref(0);
|
const transformMin = ref(0);
|
||||||
const transformMax = ref(0);
|
const transformMax = ref(0);
|
||||||
|
@ -261,15 +256,15 @@ export default defineComponent({
|
||||||
mergedBasicSize = basicSize - addSize;
|
mergedBasicSize = basicSize - addSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { tabs } = tabsContext;
|
const tabsVal = tabs.value;
|
||||||
if (!tabs.length) {
|
if (!tabsVal.length) {
|
||||||
[visibleStart.value, visibleEnd.value] = [0, 0];
|
[visibleStart.value, visibleEnd.value] = [0, 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
const len = tabs.length;
|
const len = tabsVal.length;
|
||||||
let endIndex = len;
|
let endIndex = len;
|
||||||
for (let i = 0; i < len; i += 1) {
|
for (let i = 0; i < len; i += 1) {
|
||||||
const offset = tabOffsets.value.get(tabs[i].key) || DEFAULT_SIZE;
|
const offset = tabOffsets.value.get(tabsVal[i].key) || DEFAULT_SIZE;
|
||||||
if (offset[position] + offset[unit] > transformSize + mergedBasicSize) {
|
if (offset[position] + offset[unit] > transformSize + mergedBasicSize) {
|
||||||
endIndex = i - 1;
|
endIndex = i - 1;
|
||||||
break;
|
break;
|
||||||
|
@ -278,7 +273,7 @@ export default defineComponent({
|
||||||
|
|
||||||
let startIndex = 0;
|
let startIndex = 0;
|
||||||
for (let i = len - 1; i >= 0; i -= 1) {
|
for (let i = len - 1; i >= 0; i -= 1) {
|
||||||
const offset = tabOffsets.value.get(tabs[i].key) || DEFAULT_SIZE;
|
const offset = tabOffsets.value.get(tabsVal[i].key) || DEFAULT_SIZE;
|
||||||
if (offset[position] < transformSize) {
|
if (offset[position] < transformSize) {
|
||||||
startIndex = i + 1;
|
startIndex = i + 1;
|
||||||
break;
|
break;
|
||||||
|
@ -319,7 +314,7 @@ export default defineComponent({
|
||||||
// Update buttons records
|
// Update buttons records
|
||||||
setTabSizes(() => {
|
setTabSizes(() => {
|
||||||
const newSizes: TabSizeMap = new Map();
|
const newSizes: TabSizeMap = new Map();
|
||||||
tabsContext.tabs.forEach(({ key }) => {
|
tabs.value.forEach(({ key }) => {
|
||||||
const btnRef = getBtnRef(key).value;
|
const btnRef = getBtnRef(key).value;
|
||||||
const btnNode = (btnRef as any).$el || btnRef;
|
const btnNode = (btnRef as any).$el || btnRef;
|
||||||
if (btnNode) {
|
if (btnNode) {
|
||||||
|
@ -337,8 +332,8 @@ export default defineComponent({
|
||||||
|
|
||||||
// ======================== Dropdown =======================
|
// ======================== Dropdown =======================
|
||||||
const hiddenTabs = computed(() => [
|
const hiddenTabs = computed(() => [
|
||||||
...tabsContext.tabs.slice(0, visibleStart.value),
|
...tabs.value.slice(0, visibleStart.value),
|
||||||
...tabsContext.tabs.slice(visibleEnd.value + 1),
|
...tabs.value.slice(visibleEnd.value + 1),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// =================== Link & Operations ===================
|
// =================== Link & Operations ===================
|
||||||
|
@ -385,7 +380,7 @@ export default defineComponent({
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[() => props.rtl, () => props.tabBarGutter, () => props.activeKey, () => tabsContext.tabs],
|
[() => props.rtl, () => props.tabBarGutter, () => props.activeKey, () => tabs.value],
|
||||||
() => {
|
() => {
|
||||||
onListHolderResize();
|
onListHolderResize();
|
||||||
},
|
},
|
||||||
|
@ -406,7 +401,6 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const { prefixCls, tabs } = tabsContext;
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
animated,
|
animated,
|
||||||
|
@ -420,9 +414,10 @@ export default defineComponent({
|
||||||
onTabClick,
|
onTabClick,
|
||||||
} = props;
|
} = props;
|
||||||
const { class: className, style } = attrs;
|
const { class: className, style } = attrs;
|
||||||
|
const pre = prefixCls.value;
|
||||||
// ========================= Render ========================
|
// ========================= Render ========================
|
||||||
const hasDropdown = !!hiddenTabs.value.length;
|
const hasDropdown = !!hiddenTabs.value.length;
|
||||||
const wrapPrefix = `${prefixCls}-nav-wrap`;
|
const wrapPrefix = `${pre}-nav-wrap`;
|
||||||
let pingLeft: boolean;
|
let pingLeft: boolean;
|
||||||
let pingRight: boolean;
|
let pingRight: boolean;
|
||||||
let pingTop: boolean;
|
let pingTop: boolean;
|
||||||
|
@ -450,12 +445,12 @@ export default defineComponent({
|
||||||
typeof tabBarGutter === 'number' ? `${tabBarGutter}px` : tabBarGutter;
|
typeof tabBarGutter === 'number' ? `${tabBarGutter}px` : tabBarGutter;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabNodes = tabs.map((tab, i) => {
|
const tabNodes = tabs.value.map((tab, i) => {
|
||||||
const { key } = tab;
|
const { key } = tab;
|
||||||
return (
|
return (
|
||||||
<TabNode
|
<TabNode
|
||||||
id={id}
|
id={id}
|
||||||
prefixCls={prefixCls}
|
prefixCls={pre}
|
||||||
key={key}
|
key={key}
|
||||||
tab={tab}
|
tab={tab}
|
||||||
/* first node should not have margin left */
|
/* first node should not have margin left */
|
||||||
|
@ -492,14 +487,14 @@ export default defineComponent({
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
role="tablist"
|
role="tablist"
|
||||||
class={classNames(`${prefixCls}-nav`, className)}
|
class={classNames(`${pre}-nav`, className)}
|
||||||
style={style}
|
style={style}
|
||||||
onKeydown={() => {
|
onKeydown={() => {
|
||||||
// No need animation when use keyboard
|
// No need animation when use keyboard
|
||||||
doLockAnimation();
|
doLockAnimation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ExtraContent position="left" extra={extra} prefixCls={prefixCls} />
|
<ExtraContent position="left" extra={extra} prefixCls={pre} />
|
||||||
|
|
||||||
<ResizeObserver onResize={onListHolderResize}>
|
<ResizeObserver onResize={onListHolderResize}>
|
||||||
<div
|
<div
|
||||||
|
@ -514,7 +509,7 @@ export default defineComponent({
|
||||||
<ResizeObserver onResize={onListHolderResize}>
|
<ResizeObserver onResize={onListHolderResize}>
|
||||||
<div
|
<div
|
||||||
ref={tabListRef}
|
ref={tabListRef}
|
||||||
class={`${prefixCls}-nav-list`}
|
class={`${pre}-nav-list`}
|
||||||
style={{
|
style={{
|
||||||
transform: `translate(${transformLeft.value}px, ${transformTop.value}px)`,
|
transform: `translate(${transformLeft.value}px, ${transformTop.value}px)`,
|
||||||
transition: lockAnimation.value ? 'none' : undefined,
|
transition: lockAnimation.value ? 'none' : undefined,
|
||||||
|
@ -523,7 +518,7 @@ export default defineComponent({
|
||||||
{tabNodes}
|
{tabNodes}
|
||||||
<AddButton
|
<AddButton
|
||||||
ref={innerAddButtonRef}
|
ref={innerAddButtonRef}
|
||||||
prefixCls={prefixCls}
|
prefixCls={pre}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
editable={editable}
|
editable={editable}
|
||||||
style={{
|
style={{
|
||||||
|
@ -533,8 +528,8 @@ export default defineComponent({
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={classNames(`${prefixCls}-ink-bar`, {
|
class={classNames(`${pre}-ink-bar`, {
|
||||||
[`${prefixCls}-ink-bar-animated`]: animated.inkBar,
|
[`${pre}-ink-bar-animated`]: animated.inkBar,
|
||||||
})}
|
})}
|
||||||
style={inkStyle.value}
|
style={inkStyle.value}
|
||||||
/>
|
/>
|
||||||
|
@ -546,12 +541,12 @@ export default defineComponent({
|
||||||
<OperationNode
|
<OperationNode
|
||||||
{...props}
|
{...props}
|
||||||
ref={operationsRef}
|
ref={operationsRef}
|
||||||
prefixCls={prefixCls}
|
prefixCls={pre}
|
||||||
tabs={hiddenTabs.value}
|
tabs={hiddenTabs.value}
|
||||||
class={!hasDropdown && operationsHiddenClassName.value}
|
class={!hasDropdown && operationsHiddenClassName.value}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ExtraContent position="right" extra={extra} prefixCls={prefixCls} />
|
<ExtraContent position="right" extra={extra} prefixCls={pre} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { defineComponent, ref, watch, computed } from 'vue';
|
import { defineComponent, ref, watch, computed } from 'vue';
|
||||||
import type { CSSProperties } from 'vue';
|
import type { CSSProperties } from 'vue';
|
||||||
import type { VueNode } from '../../../_util/type';
|
import type { VueNode, Key } from '../../../_util/type';
|
||||||
import PropTypes from '../../../_util/vue-types';
|
import PropTypes from '../../../_util/vue-types';
|
||||||
|
|
||||||
export interface TabPaneProps {
|
export interface TabPaneProps {
|
||||||
|
@ -12,7 +12,7 @@ export interface TabPaneProps {
|
||||||
|
|
||||||
// Pass by TabPaneList
|
// Pass by TabPaneList
|
||||||
prefixCls?: string;
|
prefixCls?: string;
|
||||||
tabKey?: string;
|
tabKey?: Key;
|
||||||
id?: string;
|
id?: string;
|
||||||
animated?: boolean;
|
animated?: boolean;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
@ -33,7 +33,7 @@ export default defineComponent({
|
||||||
|
|
||||||
// Pass by TabPaneList
|
// Pass by TabPaneList
|
||||||
prefixCls: { type: String },
|
prefixCls: { type: String },
|
||||||
tabKey: { type: String },
|
tabKey: { type: [String, Number] },
|
||||||
id: { type: String },
|
id: { type: String },
|
||||||
},
|
},
|
||||||
slots: ['closeIcon', 'tab'],
|
slots: ['closeIcon', 'tab'],
|
||||||
|
|
|
@ -25,22 +25,20 @@ export default defineComponent({
|
||||||
destroyInactiveTabPane: { type: Boolean },
|
destroyInactiveTabPane: { type: Boolean },
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const tabsContext = useInjectTabs();
|
const { tabs, prefixCls } = useInjectTabs();
|
||||||
return () => {
|
return () => {
|
||||||
const { id, activeKey, animated, tabPosition, rtl, destroyInactiveTabPane } = props;
|
const { id, activeKey, animated, tabPosition, rtl, destroyInactiveTabPane } = props;
|
||||||
const { prefixCls, tabs } = tabsContext;
|
|
||||||
const tabPaneAnimated = animated.tabPane;
|
const tabPaneAnimated = animated.tabPane;
|
||||||
|
const pre = prefixCls.value;
|
||||||
const activeIndex = tabs.findIndex(tab => tab.key === activeKey);
|
const activeIndex = tabs.value.findIndex(tab => tab.key === activeKey);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={`${prefixCls}-content-holder`}>
|
<div class={`${pre}-content-holder`}>
|
||||||
<div
|
<div
|
||||||
class={[
|
class={[
|
||||||
`${prefixCls}-content`,
|
`${pre}-content`,
|
||||||
`${prefixCls}-content-${tabPosition}`,
|
`${pre}-content-${tabPosition}`,
|
||||||
{
|
{
|
||||||
[`${prefixCls}-content-animated`]: tabPaneAnimated,
|
[`${pre}-content-animated`]: tabPaneAnimated,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
style={
|
style={
|
||||||
|
@ -49,10 +47,10 @@ export default defineComponent({
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{tabs.map(tab => {
|
{tabs.value.map(tab => {
|
||||||
return cloneElement(tab.node, {
|
return cloneElement(tab.node, {
|
||||||
key: tab.key,
|
key: tab.key,
|
||||||
prefixCls,
|
prefixCls: pre,
|
||||||
tabKey: tab.key,
|
tabKey: tab.key,
|
||||||
id,
|
id,
|
||||||
animated: tabPaneAnimated,
|
animated: tabPaneAnimated,
|
||||||
|
|
|
@ -21,7 +21,8 @@ import classNames from '../../_util/classNames';
|
||||||
import { CloseOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
import { CloseOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||||||
import devWarning from '../../vc-util/devWarning';
|
import devWarning from '../../vc-util/devWarning';
|
||||||
import type { SizeType } from '../../config-provider';
|
import type { SizeType } from '../../config-provider';
|
||||||
import TabsContextProvider from './TabContext';
|
import { useProvideTabs } from './TabContext';
|
||||||
|
import type { Key } from '../../_util/type';
|
||||||
|
|
||||||
export type TabsType = 'line' | 'card' | 'editable-card';
|
export type TabsType = 'line' | 'card' | 'editable-card';
|
||||||
export type TabsPosition = 'top' | 'right' | 'bottom' | 'left';
|
export type TabsPosition = 'top' | 'right' | 'bottom' | 'left';
|
||||||
|
@ -34,8 +35,8 @@ export const tabsProps = () => {
|
||||||
prefixCls: { type: String },
|
prefixCls: { type: String },
|
||||||
id: { type: String },
|
id: { type: String },
|
||||||
|
|
||||||
activeKey: { type: String },
|
activeKey: { type: [String, Number], required: true },
|
||||||
defaultActiveKey: { type: String },
|
defaultActiveKey: { type: [String, Number] },
|
||||||
direction: { type: String as PropType<'ltr' | 'rtl'> },
|
direction: { type: String as PropType<'ltr' | 'rtl'> },
|
||||||
animated: { type: [Boolean, Object] as PropType<boolean | AnimatedConfig> },
|
animated: { type: [Boolean, Object] as PropType<boolean | AnimatedConfig> },
|
||||||
renderTabBar: { type: Function as PropType<RenderTabBar> },
|
renderTabBar: { type: Function as PropType<RenderTabBar> },
|
||||||
|
@ -50,12 +51,12 @@ export const tabsProps = () => {
|
||||||
centered: Boolean,
|
centered: Boolean,
|
||||||
onEdit: {
|
onEdit: {
|
||||||
type: Function as PropType<
|
type: Function as PropType<
|
||||||
(e: MouseEvent | KeyboardEvent | string, action: 'add' | 'remove') => void
|
(e: MouseEvent | KeyboardEvent | Key, action: 'add' | 'remove') => void
|
||||||
>,
|
>,
|
||||||
},
|
},
|
||||||
onChange: { type: Function as PropType<(activeKey: string) => void> },
|
onChange: { type: Function as PropType<(activeKey: Key) => void> },
|
||||||
onTabClick: {
|
onTabClick: {
|
||||||
type: Function as PropType<(activeKey: string, e: KeyboardEvent | MouseEvent) => void>,
|
type: Function as PropType<(activeKey: Key, e: KeyboardEvent | MouseEvent) => void>,
|
||||||
},
|
},
|
||||||
onTabScroll: { type: Function as PropType<OnTabScroll> },
|
onTabScroll: { type: Function as PropType<OnTabScroll> },
|
||||||
|
|
||||||
|
@ -78,7 +79,7 @@ function parseTabList(children: any[]): Tab[] {
|
||||||
props[camelize(k)] = v;
|
props[camelize(k)] = v;
|
||||||
}
|
}
|
||||||
const slots = node.children || {};
|
const slots = node.children || {};
|
||||||
const key = node.key !== undefined ? String(node.key) : undefined;
|
const key = node.key !== undefined ? node.key : undefined;
|
||||||
const {
|
const {
|
||||||
tab = slots.tab,
|
tab = slots.tab,
|
||||||
disabled,
|
disabled,
|
||||||
|
@ -159,7 +160,7 @@ const InternalTabs = defineComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
// ====================== Active Key ======================
|
// ====================== Active Key ======================
|
||||||
const [mergedActiveKey, setMergedActiveKey] = useMergedState<string>(() => props.tabs[0]?.key, {
|
const [mergedActiveKey, setMergedActiveKey] = useMergedState<Key>(() => props.tabs[0]?.key, {
|
||||||
value: computed(() => props.activeKey),
|
value: computed(() => props.activeKey),
|
||||||
defaultValue: props.defaultActiveKey,
|
defaultValue: props.defaultActiveKey,
|
||||||
});
|
});
|
||||||
|
@ -171,7 +172,7 @@ const InternalTabs = defineComponent({
|
||||||
let newActiveIndex = props.tabs.findIndex(tab => tab.key === mergedActiveKey.value);
|
let newActiveIndex = props.tabs.findIndex(tab => tab.key === mergedActiveKey.value);
|
||||||
if (newActiveIndex === -1) {
|
if (newActiveIndex === -1) {
|
||||||
newActiveIndex = Math.max(0, Math.min(activeIndex.value, props.tabs.length - 1));
|
newActiveIndex = Math.max(0, Math.min(activeIndex.value, props.tabs.length - 1));
|
||||||
setMergedActiveKey(props.tabs[newActiveIndex]?.key);
|
mergedActiveKey.value = props.tabs[newActiveIndex]?.key;
|
||||||
}
|
}
|
||||||
setActiveIndex(newActiveIndex);
|
setActiveIndex(newActiveIndex);
|
||||||
});
|
});
|
||||||
|
@ -197,30 +198,30 @@ const InternalTabs = defineComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
// ======================== Events ========================
|
// ======================== Events ========================
|
||||||
const onInternalTabClick = (key: string, e: MouseEvent | KeyboardEvent) => {
|
const onInternalTabClick = (key: Key, e: MouseEvent | KeyboardEvent) => {
|
||||||
props.onTabClick?.(key, e);
|
props.onTabClick?.(key, e);
|
||||||
|
|
||||||
setMergedActiveKey(key);
|
setMergedActiveKey(key);
|
||||||
props.onChange?.(key);
|
props.onChange?.(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useProvideTabs({
|
||||||
|
tabs: computed(() => props.tabs),
|
||||||
|
prefixCls,
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
type,
|
type,
|
||||||
activeKey,
|
|
||||||
defaultActiveKey,
|
|
||||||
tabBarGutter,
|
tabBarGutter,
|
||||||
tabBarStyle,
|
tabBarStyle,
|
||||||
locale,
|
locale,
|
||||||
destroyInactiveTabPane,
|
destroyInactiveTabPane,
|
||||||
renderTabBar,
|
renderTabBar,
|
||||||
onChange,
|
|
||||||
onTabClick,
|
|
||||||
onTabScroll,
|
onTabScroll,
|
||||||
hideAdd,
|
hideAdd,
|
||||||
centered,
|
centered,
|
||||||
...restProps
|
|
||||||
} = props;
|
} = props;
|
||||||
// ======================== Render ========================
|
// ======================== Render ========================
|
||||||
const sharedProps = {
|
const sharedProps = {
|
||||||
|
@ -274,34 +275,31 @@ const InternalTabs = defineComponent({
|
||||||
const pre = prefixCls.value;
|
const pre = prefixCls.value;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabsContextProvider tabs={props.tabs} prefixCls={pre}>
|
<div
|
||||||
<div
|
{...attrs}
|
||||||
{...attrs}
|
id={id}
|
||||||
id={id}
|
class={classNames(
|
||||||
class={classNames(
|
pre,
|
||||||
pre,
|
`${pre}-${mergedTabPosition.value}`,
|
||||||
`${pre}-${mergedTabPosition.value}`,
|
{
|
||||||
{
|
[`${pre}-${size}`]: size.value,
|
||||||
[`${pre}-${size}`]: size.value,
|
[`${pre}-card`]: ['card', 'editable-card'].includes(type as string),
|
||||||
[`${pre}-card`]: ['card', 'editable-card'].includes(type as string),
|
[`${pre}-editable-card`]: type === 'editable-card',
|
||||||
[`${pre}-editable-card`]: type === 'editable-card',
|
[`${pre}-centered`]: centered,
|
||||||
[`${pre}-centered`]: centered,
|
[`${pre}-mobile`]: mobile.value,
|
||||||
[`${pre}-mobile`]: mobile.value,
|
[`${pre}-editable`]: type === 'editable-card',
|
||||||
[`${pre}-editable`]: type === 'editable-card',
|
[`${pre}-rtl`]: rtl.value,
|
||||||
[`${pre}-rtl`]: rtl.value,
|
},
|
||||||
},
|
attrs.class,
|
||||||
attrs.class,
|
)}
|
||||||
)}
|
>
|
||||||
{...restProps}
|
{tabNavBar}
|
||||||
>
|
<TabPanelList
|
||||||
{tabNavBar}
|
destroyInactiveTabPane={destroyInactiveTabPane}
|
||||||
<TabPanelList
|
{...sharedProps}
|
||||||
destroyInactiveTabPane={destroyInactiveTabPane}
|
animated={mergedAnimated.value}
|
||||||
{...sharedProps}
|
/>
|
||||||
animated={mergedAnimated.value}
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TabsContextProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import supportsPassive from 'ant-design-vue/es/_util/supportsPassive';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import { ref, onBeforeUnmount, onMounted } from 'vue';
|
import { ref, onBeforeUnmount, onMounted } from 'vue';
|
||||||
import useState from '../../../_util/hooks/useState';
|
import useState from '../../../_util/hooks/useState';
|
||||||
|
@ -9,7 +10,6 @@ const MIN_SWIPE_DISTANCE = 0.1;
|
||||||
const STOP_SWIPE_DISTANCE = 0.01;
|
const STOP_SWIPE_DISTANCE = 0.01;
|
||||||
const REFRESH_INTERVAL = 20;
|
const REFRESH_INTERVAL = 20;
|
||||||
const SPEED_OFF_MULTIPLE = 0.995 ** REFRESH_INTERVAL;
|
const SPEED_OFF_MULTIPLE = 0.995 ** REFRESH_INTERVAL;
|
||||||
|
|
||||||
// ================================= Hook =================================
|
// ================================= Hook =================================
|
||||||
export default function useTouchMove(
|
export default function useTouchMove(
|
||||||
domRef: Ref<HTMLDivElement>,
|
domRef: Ref<HTMLDivElement>,
|
||||||
|
@ -131,7 +131,11 @@ export default function useTouchMove(
|
||||||
|
|
||||||
// No need to clean up since element removed
|
// No need to clean up since element removed
|
||||||
domRef.value.addEventListener('touchstart', onProxyTouchStart, { passive: false });
|
domRef.value.addEventListener('touchstart', onProxyTouchStart, { passive: false });
|
||||||
domRef.value.addEventListener('wheel', onProxyWheel);
|
domRef.value.addEventListener(
|
||||||
|
'wheel',
|
||||||
|
onProxyWheel,
|
||||||
|
supportsPassive ? { passive: true } : false,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
|
|
@ -15,7 +15,7 @@ export type TabOffsetMap = Map<Key, TabOffset>;
|
||||||
export type TabPosition = 'left' | 'right' | 'top' | 'bottom';
|
export type TabPosition = 'left' | 'right' | 'top' | 'bottom';
|
||||||
|
|
||||||
export interface Tab extends TabPaneProps {
|
export interface Tab extends TabPaneProps {
|
||||||
key: string;
|
key: Key;
|
||||||
node: VueNode;
|
node: VueNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,10 +28,7 @@ export interface TabsLocale {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditableConfig {
|
export interface EditableConfig {
|
||||||
onEdit: (
|
onEdit: (type: 'add' | 'remove', info: { key?: Key; event: MouseEvent | KeyboardEvent }) => void;
|
||||||
type: 'add' | 'remove',
|
|
||||||
info: { key?: string; event: MouseEvent | KeyboardEvent },
|
|
||||||
) => void;
|
|
||||||
showAdd?: boolean;
|
showAdd?: boolean;
|
||||||
removeIcon?: () => VueNode;
|
removeIcon?: () => VueNode;
|
||||||
addIcon?: () => VueNode;
|
addIcon?: () => VueNode;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// debugger tsx
|
// debugger tsx
|
||||||
import Demo from '../../components/tabs/demo/basic.vue';
|
import Demo from '../../components/tabs/demo/card-top.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
render() {
|
render() {
|
||||||
|
|
Loading…
Reference in New Issue