refactor: collapse
parent
af182533c1
commit
0ade597b55
|
@ -5,3 +5,5 @@ tree、tree-slelct: 新增虚拟滚动、title 逻辑变动
|
||||||
date 相关组件: dayjs, UI 变动
|
date 相关组件: dayjs, UI 变动
|
||||||
|
|
||||||
steps: add responsive、percent
|
steps: add responsive、percent
|
||||||
|
|
||||||
|
collapse: add ghost、collapsible
|
||||||
|
|
|
@ -1,87 +1,179 @@
|
||||||
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
|
import {
|
||||||
import { defineComponent, inject } from 'vue';
|
isEmptyElement,
|
||||||
import animation from '../_util/openAnimation';
|
initDefaultProps,
|
||||||
import { getOptionProps, getComponent, isValidElement, getSlot } from '../_util/props-util';
|
flattenChildren,
|
||||||
|
isValidElement,
|
||||||
|
} from '../_util/props-util';
|
||||||
import { cloneElement } from '../_util/vnode';
|
import { cloneElement } from '../_util/vnode';
|
||||||
import VcCollapse from '../vc-collapse';
|
import type { CollapsibleType } from './commonProps';
|
||||||
|
import { collapseProps } from './commonProps';
|
||||||
|
import { getDataAndAriaProps } from '../_util/util';
|
||||||
|
import type { ExtractPropTypes } from 'vue';
|
||||||
|
import { computed, defineComponent, ref, watch } from 'vue';
|
||||||
import RightOutlined from '@ant-design/icons-vue/RightOutlined';
|
import RightOutlined from '@ant-design/icons-vue/RightOutlined';
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
import firstNotUndefined from '../_util/firstNotUndefined';
|
||||||
import PropTypes from '../_util/vue-types';
|
import classNames from '../_util/classNames';
|
||||||
import type { VueNode } from '../_util/type';
|
import animation from '../_util/openAnimation';
|
||||||
import { tuple } from '../_util/type';
|
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||||
|
import type { CollapsePanelProps } from './CollapsePanel';
|
||||||
|
|
||||||
export interface PanelProps {
|
type Key = number | string;
|
||||||
isActive?: boolean;
|
|
||||||
header?: VueNode;
|
function getActiveKeysArray(activeKey: Key | Key[]) {
|
||||||
className?: string;
|
let currentActiveKey = activeKey;
|
||||||
class?: string;
|
if (!Array.isArray(currentActiveKey)) {
|
||||||
style?: CSSProperties;
|
const activeKeyType = typeof currentActiveKey;
|
||||||
showArrow?: boolean;
|
currentActiveKey =
|
||||||
forceRender?: boolean;
|
activeKeyType === 'number' || activeKeyType === 'string' ? [currentActiveKey] : [];
|
||||||
disabled?: boolean;
|
}
|
||||||
extra?: VueNode;
|
return currentActiveKey.map(key => String(key));
|
||||||
}
|
}
|
||||||
type ActiveKeyType = Array<string | number> | string | number;
|
export { collapseProps };
|
||||||
|
export type CollapseProps = Partial<ExtractPropTypes<ReturnType<typeof collapseProps>>>;
|
||||||
const collapseProps = {
|
|
||||||
prefixCls: PropTypes.string,
|
|
||||||
activeKey: { type: [Array, Number, String] as PropType<ActiveKeyType> },
|
|
||||||
defaultActiveKey: { type: [Array, Number, String] as PropType<ActiveKeyType> },
|
|
||||||
accordion: PropTypes.looseBool,
|
|
||||||
destroyInactivePanel: PropTypes.looseBool,
|
|
||||||
bordered: PropTypes.looseBool.def(true),
|
|
||||||
expandIcon: PropTypes.func,
|
|
||||||
openAnimation: PropTypes.object.def(animation),
|
|
||||||
expandIconPosition: PropTypes.oneOf(tuple('left', 'right')).def('left'),
|
|
||||||
'onUpdate:activeKey': PropTypes.func,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CollapseProps = Partial<ExtractPropTypes<typeof collapseProps>>;
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ACollapse',
|
name: 'ACollapse',
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: collapseProps,
|
props: initDefaultProps(collapseProps(), {
|
||||||
setup() {
|
accordion: false,
|
||||||
return {
|
destroyInactivePanel: false,
|
||||||
configProvider: inject('configProvider', defaultConfigProvider),
|
bordered: true,
|
||||||
};
|
openAnimation: animation,
|
||||||
},
|
expandIconPosition: 'left',
|
||||||
methods: {
|
}),
|
||||||
renderExpandIcon(panelProps: PanelProps = {}, prefixCls: string) {
|
slots: ['expandIcon'],
|
||||||
const expandIcon = getComponent(this, 'expandIcon', panelProps);
|
emits: ['change', 'update:activeKey'],
|
||||||
const icon = expandIcon || <RightOutlined rotate={panelProps.isActive ? 90 : undefined} />;
|
setup(props, { attrs, slots, emit }) {
|
||||||
|
const stateActiveKey = ref<Key[]>(
|
||||||
|
getActiveKeysArray(firstNotUndefined([props.activeKey, props.defaultActiveKey])),
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.activeKey,
|
||||||
|
() => {
|
||||||
|
stateActiveKey.value = getActiveKeysArray(props.activeKey);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const { prefixCls, direction } = useConfigInject('collapse', props);
|
||||||
|
const iconPosition = computed(() => {
|
||||||
|
const { expandIconPosition } = props;
|
||||||
|
if (expandIconPosition !== undefined) {
|
||||||
|
return expandIconPosition;
|
||||||
|
}
|
||||||
|
return direction.value === 'rtl' ? 'right' : 'left';
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderExpandIcon = (panelProps: CollapsePanelProps) => {
|
||||||
|
const { expandIcon = slots.expandIcon } = props;
|
||||||
|
const icon = expandIcon ? (
|
||||||
|
expandIcon(panelProps)
|
||||||
|
) : (
|
||||||
|
<RightOutlined rotate={panelProps.isActive ? 90 : undefined} />
|
||||||
|
);
|
||||||
|
|
||||||
return isValidElement(Array.isArray(expandIcon) ? icon[0] : icon)
|
return isValidElement(Array.isArray(expandIcon) ? icon[0] : icon)
|
||||||
? cloneElement(icon, {
|
? cloneElement(
|
||||||
class: `${prefixCls}-arrow`,
|
icon,
|
||||||
})
|
{
|
||||||
|
class: `${prefixCls.value}-arrow`,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
: icon;
|
: icon;
|
||||||
},
|
|
||||||
handleChange(activeKey: ActiveKeyType) {
|
|
||||||
this.$emit('update:activeKey', activeKey);
|
|
||||||
this.$emit('change', activeKey);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
const { prefixCls: customizePrefixCls, bordered, expandIconPosition } = this;
|
|
||||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
|
||||||
const prefixCls = getPrefixCls('collapse', customizePrefixCls);
|
|
||||||
const { class: className, ...restAttrs } = this.$attrs;
|
|
||||||
const collapseClassName = {
|
|
||||||
[className as string]: className,
|
|
||||||
[`${prefixCls}-borderless`]: !bordered,
|
|
||||||
[`${prefixCls}-icon-position-${expandIconPosition}`]: true,
|
|
||||||
};
|
};
|
||||||
const rcCollapeProps = {
|
const setActiveKey = (activeKey: Key[]) => {
|
||||||
...getOptionProps(this),
|
if (props.activeKey === undefined) {
|
||||||
prefixCls,
|
stateActiveKey.value = activeKey;
|
||||||
expandIcon: (panelProps: PanelProps) => this.renderExpandIcon(panelProps, prefixCls),
|
}
|
||||||
class: collapseClassName,
|
const newKey = props.accordion ? activeKey[0] : activeKey;
|
||||||
...restAttrs,
|
emit('update:activeKey', newKey);
|
||||||
onChange: this.handleChange,
|
emit('change', newKey);
|
||||||
|
};
|
||||||
|
const onClickItem = (key: Key) => {
|
||||||
|
let activeKey = stateActiveKey.value;
|
||||||
|
if (props.accordion) {
|
||||||
|
activeKey = activeKey[0] === key ? [] : [key];
|
||||||
|
} else {
|
||||||
|
activeKey = [...activeKey];
|
||||||
|
const index = activeKey.indexOf(key);
|
||||||
|
const isActive = index > -1;
|
||||||
|
if (isActive) {
|
||||||
|
// remove active state
|
||||||
|
activeKey.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
activeKey.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setActiveKey(activeKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
return <VcCollapse {...rcCollapeProps}>{getSlot(this)}</VcCollapse>;
|
const getNewChild = (child, index) => {
|
||||||
|
if (isEmptyElement(child)) return;
|
||||||
|
const activeKey = stateActiveKey.value;
|
||||||
|
const { accordion, destroyInactivePanel, collapsible, openAnimation } = props;
|
||||||
|
|
||||||
|
// If there is no key provide, use the panel order as default key
|
||||||
|
const key = String(child.key ?? index);
|
||||||
|
const {
|
||||||
|
header = child.children?.header?.(),
|
||||||
|
headerClass,
|
||||||
|
collapsible: childCollapsible,
|
||||||
|
disabled,
|
||||||
|
} = child.props || {};
|
||||||
|
let isActive = false;
|
||||||
|
|
||||||
|
if (accordion) {
|
||||||
|
isActive = activeKey[0] === key;
|
||||||
|
} else {
|
||||||
|
isActive = activeKey.indexOf(key) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mergeCollapsible: CollapsibleType = childCollapsible ?? collapsible;
|
||||||
|
// legacy 2.x
|
||||||
|
if (disabled || disabled === '') {
|
||||||
|
mergeCollapsible = 'disabled';
|
||||||
|
}
|
||||||
|
const newProps = {
|
||||||
|
key,
|
||||||
|
panelKey: key,
|
||||||
|
header,
|
||||||
|
headerClass,
|
||||||
|
isActive,
|
||||||
|
prefixCls: prefixCls.value,
|
||||||
|
destroyInactivePanel,
|
||||||
|
openAnimation,
|
||||||
|
accordion,
|
||||||
|
onItemClick: mergeCollapsible === 'disabled' ? null : onClickItem,
|
||||||
|
expandIcon: renderExpandIcon,
|
||||||
|
collapsible: mergeCollapsible,
|
||||||
|
};
|
||||||
|
|
||||||
|
return cloneElement(child, newProps);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getItems = () => {
|
||||||
|
return flattenChildren(slots.default?.()).map(getNewChild);
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const { accordion, bordered, ghost } = props;
|
||||||
|
const collapseClassName = classNames({
|
||||||
|
[prefixCls.value]: true,
|
||||||
|
[`${prefixCls.value}-borderless`]: !bordered,
|
||||||
|
[`${prefixCls.value}-icon-position-${iconPosition.value}`]: true,
|
||||||
|
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
|
||||||
|
[`${prefixCls.value}-ghost`]: !!ghost,
|
||||||
|
[attrs.class as string]: !!attrs.class,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={collapseClassName}
|
||||||
|
{...getDataAndAriaProps(attrs)}
|
||||||
|
style={attrs.style}
|
||||||
|
role={accordion ? 'tablist' : null}
|
||||||
|
>
|
||||||
|
{getItems()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,54 +1,114 @@
|
||||||
|
import PanelContent from './PanelContent';
|
||||||
|
import { initDefaultProps } from '../_util/props-util';
|
||||||
|
import { collapsePanelProps } from './commonProps';
|
||||||
import type { ExtractPropTypes } from 'vue';
|
import type { ExtractPropTypes } from 'vue';
|
||||||
import { defineComponent, inject } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { getOptionProps, getComponent, getSlot } from '../_util/props-util';
|
import Transition from '../_util/transition';
|
||||||
import VcCollapse from '../vc-collapse';
|
import classNames from '../_util/classNames';
|
||||||
import { defaultConfigProvider } from '../config-provider';
|
import devWarning from '../vc-util/devWarning';
|
||||||
import PropTypes from '../_util/vue-types';
|
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||||
|
|
||||||
const collapsePanelProps = {
|
export { collapsePanelProps };
|
||||||
openAnimation: PropTypes.object,
|
export type CollapsePanelProps = Partial<ExtractPropTypes<ReturnType<typeof collapsePanelProps>>>;
|
||||||
prefixCls: PropTypes.string,
|
|
||||||
header: PropTypes.VNodeChild,
|
|
||||||
headerClass: PropTypes.string,
|
|
||||||
showArrow: PropTypes.looseBool,
|
|
||||||
isActive: PropTypes.looseBool,
|
|
||||||
destroyInactivePanel: PropTypes.looseBool,
|
|
||||||
disabled: PropTypes.looseBool,
|
|
||||||
accordion: PropTypes.looseBool,
|
|
||||||
forceRender: PropTypes.looseBool,
|
|
||||||
expandIcon: PropTypes.func,
|
|
||||||
extra: PropTypes.VNodeChild,
|
|
||||||
panelKey: PropTypes.VNodeChild,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CollapsePanelProps = Partial<ExtractPropTypes<typeof collapsePanelProps>>;
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ACollapsePanel',
|
name: 'ACollapsePanel',
|
||||||
inheritAttrs: false,
|
props: initDefaultProps(collapsePanelProps(), {
|
||||||
props: collapsePanelProps,
|
showArrow: true,
|
||||||
setup() {
|
isActive: false,
|
||||||
return {
|
onItemClick() {},
|
||||||
configProvider: inject('configProvider', defaultConfigProvider),
|
headerClass: '',
|
||||||
|
forceRender: false,
|
||||||
|
}),
|
||||||
|
slots: ['expandIcon', 'extra', 'header'],
|
||||||
|
emits: ['itemClick'],
|
||||||
|
setup(props, { slots, emit }) {
|
||||||
|
devWarning(
|
||||||
|
props.disabled === undefined,
|
||||||
|
'Collapse.Panel',
|
||||||
|
'`disabled` is deprecated. Please use `collapsible="disabled"` instead.',
|
||||||
|
);
|
||||||
|
const { prefixCls } = useConfigInject('collapse', props);
|
||||||
|
const handleItemClick = () => {
|
||||||
|
emit('itemClick', props.panelKey);
|
||||||
};
|
};
|
||||||
},
|
const handleKeyPress = (e: KeyboardEvent) => {
|
||||||
render() {
|
if (e.key === 'Enter' || e.keyCode === 13 || e.which === 13) {
|
||||||
const { prefixCls: customizePrefixCls, showArrow = true } = this;
|
handleItemClick();
|
||||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
}
|
||||||
const prefixCls = getPrefixCls('collapse', customizePrefixCls);
|
|
||||||
const { class: className, ...restAttrs } = this.$attrs;
|
|
||||||
const collapsePanelClassName = {
|
|
||||||
[className as string]: className,
|
|
||||||
[`${prefixCls}-no-arrow`]: !showArrow,
|
|
||||||
};
|
};
|
||||||
|
return () => {
|
||||||
|
const {
|
||||||
|
header = slots.header?.(),
|
||||||
|
headerClass,
|
||||||
|
isActive,
|
||||||
|
showArrow,
|
||||||
|
destroyInactivePanel,
|
||||||
|
accordion,
|
||||||
|
forceRender,
|
||||||
|
openAnimation,
|
||||||
|
expandIcon = slots.expandIcon,
|
||||||
|
extra = slots.extra?.(),
|
||||||
|
collapsible,
|
||||||
|
} = props;
|
||||||
|
const disabled = collapsible === 'disabled';
|
||||||
|
const prefixClsValue = prefixCls.value;
|
||||||
|
const headerCls = classNames(`${prefixClsValue}-header`, {
|
||||||
|
[headerClass]: headerClass,
|
||||||
|
[`${prefixClsValue}-header-collapsible-only`]: collapsible === 'header',
|
||||||
|
});
|
||||||
|
const itemCls = classNames({
|
||||||
|
[`${prefixClsValue}-item`]: true,
|
||||||
|
[`${prefixClsValue}-item-active`]: isActive,
|
||||||
|
[`${prefixClsValue}-item-disabled`]: disabled,
|
||||||
|
[`${prefixClsValue}-no-arrow`]: !showArrow,
|
||||||
|
});
|
||||||
|
|
||||||
const rcCollapePanelProps = {
|
let icon = <i class="arrow" />;
|
||||||
...getOptionProps(this),
|
if (showArrow && typeof expandIcon === 'function') {
|
||||||
header: getComponent(this, 'header'),
|
icon = expandIcon(props);
|
||||||
prefixCls,
|
}
|
||||||
extra: getComponent(this, 'extra'),
|
|
||||||
class: collapsePanelClassName,
|
const panelContent = (
|
||||||
...restAttrs,
|
<PanelContent
|
||||||
|
v-show={isActive}
|
||||||
|
prefixCls={prefixClsValue}
|
||||||
|
isActive={isActive}
|
||||||
|
forceRender={forceRender}
|
||||||
|
role={accordion ? 'tabpanel' : null}
|
||||||
|
v-slots={{ default: slots.default }}
|
||||||
|
></PanelContent>
|
||||||
|
);
|
||||||
|
const transitionProps = {
|
||||||
|
appear: true,
|
||||||
|
css: false,
|
||||||
|
...openAnimation,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={itemCls}>
|
||||||
|
<div
|
||||||
|
class={headerCls}
|
||||||
|
onClick={() => collapsible !== 'header' && handleItemClick()}
|
||||||
|
role={accordion ? 'tab' : 'button'}
|
||||||
|
tabindex={disabled ? -1 : 0}
|
||||||
|
aria-expanded={isActive}
|
||||||
|
onKeypress={handleKeyPress}
|
||||||
|
>
|
||||||
|
{showArrow && icon}
|
||||||
|
{collapsible === 'header' ? (
|
||||||
|
<span onClick={handleItemClick} class={`${prefixClsValue}-header-text`}>
|
||||||
|
{header}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
header
|
||||||
|
)}
|
||||||
|
{extra && <div class={`${prefixClsValue}-extra`}>{extra}</div>}
|
||||||
|
</div>
|
||||||
|
<Transition {...transitionProps}>
|
||||||
|
{!destroyInactivePanel || isActive ? panelContent : null}
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
return <VcCollapse.Panel {...rcCollapePanelProps}>{getSlot(this)}</VcCollapse.Panel>;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { defineComponent, ref, watchEffect } from 'vue';
|
import { defineComponent, ref, watchEffect } from 'vue';
|
||||||
import { panelProps } from './commonProps';
|
import { collapsePanelProps } from './commonProps';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'PanelContent',
|
name: 'PanelContent',
|
||||||
props: panelProps(),
|
props: collapsePanelProps(),
|
||||||
setup(props, { slots }) {
|
setup(props, { slots }) {
|
||||||
const rendered = ref(false);
|
const rendered = ref(false);
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
|
|
||||||
exports[`Collapse should support remove expandIcon 1`] = `
|
exports[`Collapse should support remove expandIcon 1`] = `
|
||||||
<div class="ant-collapse ant-collapse-icon-position-left">
|
<div class="ant-collapse ant-collapse-icon-position-left">
|
||||||
<div class="ant-collapse-item" role="tablist">
|
<div class="ant-collapse-item">
|
||||||
<div class="ant-collapse-header" role="button" tabindex="0" aria-expanded="false"><span role="img" aria-label="right" class="anticon anticon-right ant-collapse-arrow"><svg focusable="false" class="" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"></path></svg></span>header
|
<div class="ant-collapse-header" role="button" tabindex="0" aria-expanded="false">
|
||||||
|
<!---->header
|
||||||
<!---->
|
<!---->
|
||||||
</div>
|
</div>
|
||||||
<!---->
|
<!---->
|
||||||
|
|
|
@ -1,31 +1,25 @@
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
|
import { tuple } from '../_util/type';
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
|
|
||||||
export type CollapsibleType = 'header' | 'disabled';
|
export type CollapsibleType = 'header' | 'disabled';
|
||||||
|
|
||||||
|
export type ActiveKeyType = Array<string | number> | string | number;
|
||||||
const collapseProps = () => ({
|
const collapseProps = () => ({
|
||||||
prefixCls: PropTypes.string,
|
prefixCls: PropTypes.string,
|
||||||
activeKey: PropTypes.oneOfType([
|
activeKey: { type: [Array, Number, String] as PropType<ActiveKeyType> },
|
||||||
PropTypes.string,
|
defaultActiveKey: { type: [Array, Number, String] as PropType<ActiveKeyType> },
|
||||||
PropTypes.number,
|
|
||||||
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
|
|
||||||
]),
|
|
||||||
defaultActiveKey: PropTypes.oneOfType([
|
|
||||||
PropTypes.string,
|
|
||||||
PropTypes.number,
|
|
||||||
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
|
|
||||||
]),
|
|
||||||
accordion: PropTypes.looseBool,
|
accordion: PropTypes.looseBool,
|
||||||
destroyInactivePanel: PropTypes.looseBool,
|
destroyInactivePanel: PropTypes.looseBool,
|
||||||
bordered: PropTypes.looseBool,
|
bordered: PropTypes.looseBool,
|
||||||
expandIcon: PropTypes.func,
|
expandIcon: PropTypes.func,
|
||||||
openAnimation: PropTypes.object,
|
openAnimation: PropTypes.object,
|
||||||
expandIconPosition: PropTypes.oneOf(['left', 'right']),
|
expandIconPosition: PropTypes.oneOf(tuple('left', 'right')),
|
||||||
onChange: PropTypes.func,
|
|
||||||
collapsible: { type: String as PropType<CollapsibleType> },
|
collapsible: { type: String as PropType<CollapsibleType> },
|
||||||
|
ghost: PropTypes.looseBool,
|
||||||
});
|
});
|
||||||
|
|
||||||
const panelProps = () => ({
|
const collapsePanelProps = () => ({
|
||||||
openAnimation: PropTypes.object,
|
openAnimation: PropTypes.object,
|
||||||
prefixCls: PropTypes.string,
|
prefixCls: PropTypes.string,
|
||||||
header: PropTypes.any,
|
header: PropTypes.any,
|
||||||
|
@ -33,6 +27,7 @@ const panelProps = () => ({
|
||||||
showArrow: PropTypes.looseBool,
|
showArrow: PropTypes.looseBool,
|
||||||
isActive: PropTypes.looseBool,
|
isActive: PropTypes.looseBool,
|
||||||
destroyInactivePanel: PropTypes.looseBool,
|
destroyInactivePanel: PropTypes.looseBool,
|
||||||
|
/** @deprecated Use `collapsible="disabled"` instead */
|
||||||
disabled: PropTypes.looseBool,
|
disabled: PropTypes.looseBool,
|
||||||
accordion: PropTypes.looseBool,
|
accordion: PropTypes.looseBool,
|
||||||
forceRender: PropTypes.looseBool,
|
forceRender: PropTypes.looseBool,
|
||||||
|
@ -44,4 +39,4 @@ const panelProps = () => ({
|
||||||
onItemClick: { type: Function as PropType<(panelKey: string | number) => void> },
|
onItemClick: { type: Function as PropType<(panelKey: string | number) => void> },
|
||||||
});
|
});
|
||||||
|
|
||||||
export { collapseProps, panelProps };
|
export { collapseProps, collapsePanelProps };
|
|
@ -1,6 +1,6 @@
|
||||||
import type { App, Plugin } from 'vue';
|
import type { App, Plugin } from 'vue';
|
||||||
import Collapse from './Collapse';
|
import Collapse, { collapseProps } from './Collapse';
|
||||||
import CollapsePanel from './CollapsePanel';
|
import CollapsePanel, { collapsePanelProps } from './CollapsePanel';
|
||||||
export type { CollapseProps } from './Collapse';
|
export type { CollapseProps } from './Collapse';
|
||||||
export type { CollapsePanelProps } from './CollapsePanel';
|
export type { CollapsePanelProps } from './CollapsePanel';
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ Collapse.install = function (app: App) {
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { CollapsePanel };
|
export { CollapsePanel, collapseProps, collapsePanelProps };
|
||||||
export default Collapse as typeof Collapse &
|
export default Collapse as typeof Collapse &
|
||||||
Plugin & {
|
Plugin & {
|
||||||
readonly Panel: typeof CollapsePanel;
|
readonly Panel: typeof CollapsePanel;
|
||||||
|
|
|
@ -24,21 +24,17 @@
|
||||||
> .@{collapse-prefix-cls}-header {
|
> .@{collapse-prefix-cls}-header {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: @collapse-header-padding;
|
padding: @collapse-header-padding;
|
||||||
padding-left: @collapse-header-padding-extra;
|
|
||||||
color: @heading-color;
|
color: @heading-color;
|
||||||
line-height: 22px;
|
line-height: @line-height-base;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s, visibility 0s;
|
||||||
|
.clearfix();
|
||||||
|
|
||||||
.@{collapse-prefix-cls}-arrow {
|
.@{collapse-prefix-cls}-arrow {
|
||||||
.iconfont-mixin();
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: @padding-md;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
margin-right: 12px;
|
||||||
font-size: @font-size-sm;
|
font-size: @font-size-sm;
|
||||||
transform: translateY(-50%);
|
vertical-align: -1px;
|
||||||
|
|
||||||
& svg {
|
& svg {
|
||||||
transition: transform 0.24s;
|
transition: transform 0.24s;
|
||||||
|
@ -54,6 +50,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.@{collapse-prefix-cls}-header-collapsible-only {
|
||||||
|
cursor: default;
|
||||||
|
.@{collapse-prefix-cls}-header-text {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.@{collapse-prefix-cls}-no-arrow {
|
&.@{collapse-prefix-cls}-no-arrow {
|
||||||
> .@{collapse-prefix-cls}-header {
|
> .@{collapse-prefix-cls}-header {
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
|
@ -69,19 +72,18 @@
|
||||||
padding-right: @collapse-header-padding-extra;
|
padding-right: @collapse-header-padding-extra;
|
||||||
|
|
||||||
.@{collapse-prefix-cls}-arrow {
|
.@{collapse-prefix-cls}-arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
right: @padding-md;
|
right: @padding-md;
|
||||||
left: auto;
|
left: auto;
|
||||||
|
margin: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-anim-active {
|
|
||||||
transition: height 0.2s @ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-content {
|
&-content {
|
||||||
overflow: hidden;
|
|
||||||
color: @text-color;
|
color: @text-color;
|
||||||
background-color: @collapse-content-bg;
|
background-color: @collapse-content-bg;
|
||||||
border-top: @border-width-base @border-style-base @border-color-base;
|
border-top: @border-width-base @border-style-base @border-color-base;
|
||||||
|
@ -90,7 +92,7 @@
|
||||||
padding: @collapse-content-padding;
|
padding: @collapse-content-padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-inactive {
|
&-hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,6 +126,22 @@
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-ghost {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
> .@{collapse-prefix-cls}-item {
|
||||||
|
border-bottom: 0;
|
||||||
|
> .@{collapse-prefix-cls}-content {
|
||||||
|
background-color: transparent;
|
||||||
|
border-top: 0;
|
||||||
|
> .@{collapse-prefix-cls}-content-box {
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
& &-item-disabled > &-header {
|
& &-item-disabled > &-header {
|
||||||
&,
|
&,
|
||||||
& > .arrow {
|
& > .arrow {
|
||||||
|
@ -132,3 +150,5 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@import './rtl';
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
@import '../../style/themes/index';
|
||||||
|
@import '../../style/mixins/index';
|
||||||
|
|
||||||
|
@collapse-prefix-cls: ~'@{ant-prefix}-collapse';
|
||||||
|
|
||||||
|
.@{collapse-prefix-cls} {
|
||||||
|
&-rtl {
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > &-item {
|
||||||
|
> .@{collapse-prefix-cls}-header {
|
||||||
|
.@{collapse-prefix-cls}-rtl & {
|
||||||
|
padding: @collapse-header-padding;
|
||||||
|
padding-right: @collapse-header-padding-extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{collapse-prefix-cls}-arrow {
|
||||||
|
& svg {
|
||||||
|
.@{collapse-prefix-cls}-rtl& {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{collapse-prefix-cls}-extra {
|
||||||
|
.@{collapse-prefix-cls}-rtl& {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.@{collapse-prefix-cls}-no-arrow {
|
||||||
|
> .@{collapse-prefix-cls}-header {
|
||||||
|
.@{collapse-prefix-cls}-rtl& {
|
||||||
|
padding-right: 12px;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,143 +0,0 @@
|
||||||
import { isEmptyElement, initDefaultProps, flattenChildren } from '../_util/props-util';
|
|
||||||
import { cloneElement } from '../_util/vnode';
|
|
||||||
import openAnimationFactory from './openAnimationFactory';
|
|
||||||
import { collapseProps, CollapsibleType } from './commonProps';
|
|
||||||
import { getDataAndAriaProps } from '../_util/util';
|
|
||||||
import { computed, defineComponent, ref, watch } from 'vue';
|
|
||||||
import firstNotUndefined from '../_util/firstNotUndefined';
|
|
||||||
import classNames from '../_util/classNames';
|
|
||||||
|
|
||||||
type Key = number | string;
|
|
||||||
|
|
||||||
function getActiveKeysArray(activeKey: Key | Key[]) {
|
|
||||||
let currentActiveKey = activeKey;
|
|
||||||
if (!Array.isArray(currentActiveKey)) {
|
|
||||||
const activeKeyType = typeof currentActiveKey;
|
|
||||||
currentActiveKey =
|
|
||||||
activeKeyType === 'number' || activeKeyType === 'string' ? [currentActiveKey] : [];
|
|
||||||
}
|
|
||||||
return currentActiveKey.map(key => String(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'Collapse',
|
|
||||||
inheritAttrs: false,
|
|
||||||
props: initDefaultProps(collapseProps(), {
|
|
||||||
prefixCls: 'rc-collapse',
|
|
||||||
accordion: false,
|
|
||||||
destroyInactivePanel: false,
|
|
||||||
}),
|
|
||||||
slots: ['expandIcon'],
|
|
||||||
emits: ['change'],
|
|
||||||
setup(props, { attrs, slots, emit }) {
|
|
||||||
const stateActiveKey = ref<Key[]>(
|
|
||||||
getActiveKeysArray(firstNotUndefined([props.activeKey, props.defaultActiveKey])),
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.activeKey,
|
|
||||||
() => {
|
|
||||||
stateActiveKey.value = getActiveKeysArray(props.activeKey);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const currentOpenAnimations = computed(
|
|
||||||
() => props.openAnimation || openAnimationFactory(props.prefixCls),
|
|
||||||
);
|
|
||||||
|
|
||||||
const setActiveKey = (activeKey: Key[]) => {
|
|
||||||
if (props.activeKey === undefined) {
|
|
||||||
stateActiveKey.value = activeKey;
|
|
||||||
}
|
|
||||||
emit('change', props.accordion ? activeKey[0] : activeKey);
|
|
||||||
};
|
|
||||||
const onClickItem = (key: Key) => {
|
|
||||||
let activeKey = stateActiveKey.value;
|
|
||||||
if (props.accordion) {
|
|
||||||
activeKey = activeKey[0] === key ? [] : [key];
|
|
||||||
} else {
|
|
||||||
activeKey = [...activeKey];
|
|
||||||
const index = activeKey.indexOf(key);
|
|
||||||
const isActive = index > -1;
|
|
||||||
if (isActive) {
|
|
||||||
// remove active state
|
|
||||||
activeKey.splice(index, 1);
|
|
||||||
} else {
|
|
||||||
activeKey.push(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setActiveKey(activeKey);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getNewChild = (child, index) => {
|
|
||||||
if (isEmptyElement(child)) return;
|
|
||||||
const activeKey = stateActiveKey.value;
|
|
||||||
const {
|
|
||||||
prefixCls,
|
|
||||||
accordion,
|
|
||||||
destroyInactivePanel,
|
|
||||||
expandIcon = slots.expandIcon,
|
|
||||||
collapsible,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
// If there is no key provide, use the panel order as default key
|
|
||||||
const key = String(child.key ?? index);
|
|
||||||
const {
|
|
||||||
header = child.children?.header?.(),
|
|
||||||
headerClass,
|
|
||||||
collapsible: childCollapsible,
|
|
||||||
disabled,
|
|
||||||
} = child.props || {};
|
|
||||||
let isActive = false;
|
|
||||||
|
|
||||||
if (accordion) {
|
|
||||||
isActive = activeKey[0] === key;
|
|
||||||
} else {
|
|
||||||
isActive = activeKey.indexOf(key) > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mergeCollapsible: CollapsibleType = childCollapsible ?? collapsible;
|
|
||||||
// legacy 2.x
|
|
||||||
if (disabled || disabled === '') {
|
|
||||||
mergeCollapsible = 'disabled';
|
|
||||||
}
|
|
||||||
const newProps = {
|
|
||||||
key,
|
|
||||||
panelKey: key,
|
|
||||||
header,
|
|
||||||
headerClass,
|
|
||||||
isActive,
|
|
||||||
prefixCls,
|
|
||||||
destroyInactivePanel,
|
|
||||||
openAnimation: currentOpenAnimations.value,
|
|
||||||
accordion,
|
|
||||||
onItemClick: mergeCollapsible === 'disabled' ? null : onClickItem,
|
|
||||||
expandIcon,
|
|
||||||
collapsible: mergeCollapsible,
|
|
||||||
};
|
|
||||||
|
|
||||||
return cloneElement(child, newProps);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getItems = () => {
|
|
||||||
return flattenChildren(slots.default?.()).map(getNewChild);
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
const { prefixCls, accordion } = props;
|
|
||||||
const collapseClassName = classNames({
|
|
||||||
[prefixCls]: true,
|
|
||||||
[attrs.class as string]: !!attrs.class,
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={collapseClassName}
|
|
||||||
{...getDataAndAriaProps(attrs)}
|
|
||||||
style={attrs.style}
|
|
||||||
role={accordion ? 'tablist' : null}
|
|
||||||
>
|
|
||||||
{getItems()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,109 +0,0 @@
|
||||||
import PanelContent from './PanelContent';
|
|
||||||
import { initDefaultProps } from '../_util/props-util';
|
|
||||||
import { panelProps } from './commonProps';
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import Transition from '../_util/transition';
|
|
||||||
import classNames from '../_util/classNames';
|
|
||||||
import devWarning from '../vc-util/devWarning';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'Panel',
|
|
||||||
props: initDefaultProps(panelProps(), {
|
|
||||||
showArrow: true,
|
|
||||||
isActive: false,
|
|
||||||
onItemClick() {},
|
|
||||||
headerClass: '',
|
|
||||||
forceRender: false,
|
|
||||||
}),
|
|
||||||
slots: ['expandIcon', 'extra', 'header'],
|
|
||||||
emits: ['itemClick'],
|
|
||||||
setup(props, { slots, emit }) {
|
|
||||||
devWarning(
|
|
||||||
!('disabled' in props),
|
|
||||||
'Collapse.Panel',
|
|
||||||
'`disabled` is deprecated. Please use `collapsible="disabled"` instead.',
|
|
||||||
);
|
|
||||||
const handleItemClick = () => {
|
|
||||||
emit('itemClick', props.panelKey);
|
|
||||||
};
|
|
||||||
const handleKeyPress = (e: KeyboardEvent) => {
|
|
||||||
if (e.key === 'Enter' || e.keyCode === 13 || e.which === 13) {
|
|
||||||
handleItemClick();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return () => {
|
|
||||||
const {
|
|
||||||
prefixCls,
|
|
||||||
header = slots.header?.(),
|
|
||||||
headerClass,
|
|
||||||
isActive,
|
|
||||||
showArrow,
|
|
||||||
destroyInactivePanel,
|
|
||||||
accordion,
|
|
||||||
forceRender,
|
|
||||||
openAnimation,
|
|
||||||
expandIcon = slots.expandIcon,
|
|
||||||
extra = slots.extra?.(),
|
|
||||||
collapsible,
|
|
||||||
} = props;
|
|
||||||
const disabled = collapsible === 'disabled';
|
|
||||||
|
|
||||||
const headerCls = classNames(`${prefixCls}-header`, {
|
|
||||||
[headerClass]: headerClass,
|
|
||||||
[`${prefixCls}-header-collapsible-only`]: collapsible === 'header',
|
|
||||||
});
|
|
||||||
const itemCls = classNames({
|
|
||||||
[`${prefixCls}-item`]: true,
|
|
||||||
[`${prefixCls}-item-active`]: isActive,
|
|
||||||
[`${prefixCls}-item-disabled`]: disabled,
|
|
||||||
});
|
|
||||||
|
|
||||||
let icon = <i class="arrow" />;
|
|
||||||
if (showArrow && typeof expandIcon === 'function') {
|
|
||||||
icon = expandIcon(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
const panelContent = (
|
|
||||||
<PanelContent
|
|
||||||
v-show={isActive}
|
|
||||||
prefixCls={prefixCls}
|
|
||||||
isActive={isActive}
|
|
||||||
forceRender={forceRender}
|
|
||||||
role={accordion ? 'tabpanel' : null}
|
|
||||||
v-slots={{ default: slots.default }}
|
|
||||||
></PanelContent>
|
|
||||||
);
|
|
||||||
const transitionProps = {
|
|
||||||
appear: true,
|
|
||||||
css: false,
|
|
||||||
...openAnimation,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class={itemCls}>
|
|
||||||
<div
|
|
||||||
class={headerCls}
|
|
||||||
onClick={() => collapsible !== 'header' && handleItemClick()}
|
|
||||||
role={accordion ? 'tab' : 'button'}
|
|
||||||
tabindex={disabled ? -1 : 0}
|
|
||||||
aria-expanded={isActive}
|
|
||||||
onKeypress={handleKeyPress}
|
|
||||||
>
|
|
||||||
{showArrow && icon}
|
|
||||||
{collapsible === 'header' ? (
|
|
||||||
<span onClick={handleItemClick} class={`${prefixCls}-header-text`}>
|
|
||||||
{header}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
header
|
|
||||||
)}
|
|
||||||
{extra && <div class={`${prefixCls}-extra`}>{extra}</div>}
|
|
||||||
</div>
|
|
||||||
<Transition {...transitionProps}>
|
|
||||||
{!destroyInactivePanel || isActive ? panelContent : null}
|
|
||||||
</Transition>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,9 +0,0 @@
|
||||||
// based on rc-collapse 3.1.1
|
|
||||||
import CollapsePanel from './Panel';
|
|
||||||
import Collapse from './Collapse';
|
|
||||||
import { collapseProps, panelProps } from './commonProps';
|
|
||||||
|
|
||||||
Collapse.Panel = CollapsePanel;
|
|
||||||
|
|
||||||
export { collapseProps, panelProps };
|
|
||||||
export default Collapse;
|
|
|
@ -1,35 +0,0 @@
|
||||||
import cssAnimation from '../_util/css-animation';
|
|
||||||
|
|
||||||
function animate(node: HTMLElement, show: boolean, transitionName: string, done: () => void) {
|
|
||||||
let height: number;
|
|
||||||
return cssAnimation(node, transitionName, {
|
|
||||||
start() {
|
|
||||||
if (!show) {
|
|
||||||
node.style.height = `${node.offsetHeight}px`;
|
|
||||||
} else {
|
|
||||||
height = node.offsetHeight;
|
|
||||||
node.style.height = '0px';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
active() {
|
|
||||||
node.style.height = `${show ? height : 0}px`;
|
|
||||||
},
|
|
||||||
end() {
|
|
||||||
node.style.height = '';
|
|
||||||
done();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function animation(prefixCls: string) {
|
|
||||||
return {
|
|
||||||
onEnter(node: HTMLElement, done: () => void) {
|
|
||||||
return animate(node, true, `${prefixCls}-anim`, done);
|
|
||||||
},
|
|
||||||
onLeave(node: HTMLElement, done: () => void) {
|
|
||||||
return animate(node, false, `${prefixCls}-anim`, done);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default animation;
|
|
2
v2-doc
2
v2-doc
|
@ -1 +1 @@
|
||||||
Subproject commit c703df2dd803ee5097b414e2a32482b8fc2225d7
|
Subproject commit dcd8922ce91de1a48c515d3b06b898d27fcb30f4
|
Loading…
Reference in New Issue