mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
refactor(Collapse): use composition api
This commit is contained in:
parent
f82834886b
commit
84fe7db57d
@ -1,19 +1,20 @@
|
|||||||
|
import { ref, watch, computed, nextTick } from 'vue';
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { createNamespace, isDef } from '../utils';
|
import { createNamespace, isDef } from '../utils';
|
||||||
import { raf, doubleRaf } from '../utils/dom/raf';
|
import { raf, doubleRaf } from '../utils/dom/raf';
|
||||||
|
|
||||||
// Mixins
|
// Composition
|
||||||
import { ChildrenMixin } from '../mixins/relation';
|
import { useParent } from '../api/use-relation';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Cell from '../cell';
|
import Cell from '../cell';
|
||||||
|
import { COLLAPSE_KEY } from '../collapse';
|
||||||
import { cellProps } from '../cell/shared';
|
import { cellProps } from '../cell/shared';
|
||||||
|
|
||||||
const [createComponent, bem] = createNamespace('collapse-item');
|
const [createComponent, bem] = createNamespace('collapse-item');
|
||||||
|
|
||||||
export default createComponent({
|
export default createComponent({
|
||||||
mixins: [ChildrenMixin('vanCollapse')],
|
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
...cellProps,
|
...cellProps,
|
||||||
name: [Number, String],
|
name: [Number, String],
|
||||||
@ -24,152 +25,119 @@ export default createComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
setup(props, { slots }) {
|
||||||
return {
|
const wrapper = ref(null);
|
||||||
show: null,
|
const content = ref(null);
|
||||||
inited: null,
|
const { parent, index } = useParent(COLLAPSE_KEY, ref());
|
||||||
|
|
||||||
|
const currentName = computed(() =>
|
||||||
|
isDef(props.name) ? props.name : index.value
|
||||||
|
);
|
||||||
|
|
||||||
|
const expanded = computed(() => {
|
||||||
|
if (parent) {
|
||||||
|
return parent.isExpanded(currentName.value);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const show = ref(expanded.value);
|
||||||
|
const inited = ref(expanded.value);
|
||||||
|
|
||||||
|
const onTransitionEnd = () => {
|
||||||
|
if (!expanded.value) {
|
||||||
|
show.value = false;
|
||||||
|
} else {
|
||||||
|
wrapper.value.style.height = '';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
watch(expanded, (value, oldValue) => {
|
||||||
currentName() {
|
if (oldValue === null) {
|
||||||
return isDef(this.name) ? this.name : this.index;
|
|
||||||
},
|
|
||||||
|
|
||||||
expanded() {
|
|
||||||
if (!this.parent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { modelValue, accordion } = this.parent;
|
|
||||||
|
|
||||||
if (
|
|
||||||
process.env.NODE_ENV !== 'production' &&
|
|
||||||
!accordion &&
|
|
||||||
!Array.isArray(modelValue)
|
|
||||||
) {
|
|
||||||
console.error(
|
|
||||||
'[Vant] Collapse: type of prop "modelValue" should be Array'
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return accordion
|
if (value) {
|
||||||
? modelValue === this.currentName
|
show.value = true;
|
||||||
: modelValue.some((name) => name === this.currentName);
|
inited.value = true;
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
this.show = this.expanded;
|
|
||||||
this.inited = this.expanded;
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
expanded(expanded, prev) {
|
|
||||||
if (prev === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expanded) {
|
|
||||||
this.show = true;
|
|
||||||
this.inited = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use raf: flick when opened in safari
|
// Use raf: flick when opened in safari
|
||||||
// Use nextTick: closing animation failed when set `user-select: none`
|
// Use nextTick: closing animation failed when set `user-select: none`
|
||||||
const nextTick = expanded ? this.$nextTick : raf;
|
const tick = value ? nextTick : raf;
|
||||||
|
|
||||||
nextTick(() => {
|
tick(() => {
|
||||||
const { content, wrapper } = this.$refs;
|
if (!content.value || !wrapper.value) {
|
||||||
|
|
||||||
if (!content || !wrapper) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { offsetHeight } = content;
|
const { offsetHeight } = content.value;
|
||||||
if (offsetHeight) {
|
if (offsetHeight) {
|
||||||
const contentHeight = `${offsetHeight}px`;
|
const contentHeight = `${offsetHeight}px`;
|
||||||
wrapper.style.height = expanded ? 0 : contentHeight;
|
wrapper.value.style.height = value ? 0 : contentHeight;
|
||||||
|
|
||||||
// use double raf to ensure animation can start
|
// use double raf to ensure animation can start
|
||||||
doubleRaf(() => {
|
doubleRaf(() => {
|
||||||
wrapper.style.height = expanded ? contentHeight : 0;
|
wrapper.value.style.height = value ? contentHeight : 0;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.onTransitionEnd();
|
onTransitionEnd();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
const onClickTitle = () => {
|
||||||
onClick() {
|
if (!props.disabled) {
|
||||||
if (this.disabled) {
|
parent.toggle(currentName.value, !expanded.value);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const { parent, currentName } = this;
|
const renderTitle = () => {
|
||||||
const close = parent.accordion && currentName === parent.modelValue;
|
const { border, disabled } = props;
|
||||||
const name = close ? '' : currentName;
|
|
||||||
|
|
||||||
parent.switch(name, !this.expanded);
|
|
||||||
},
|
|
||||||
|
|
||||||
onTransitionEnd() {
|
|
||||||
if (!this.expanded) {
|
|
||||||
this.show = false;
|
|
||||||
} else {
|
|
||||||
this.$refs.wrapper.style.height = '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
genTitle() {
|
|
||||||
const { border, disabled, expanded } = this;
|
|
||||||
|
|
||||||
const slots = {
|
|
||||||
icon: this.$slots.icon,
|
|
||||||
title: this.$slots.title,
|
|
||||||
default: this.$slots.value,
|
|
||||||
'right-icon': this.$slots['right-icon'],
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Cell
|
<Cell
|
||||||
v-slots={slots}
|
v-slots={{
|
||||||
|
icon: slots.icon,
|
||||||
|
title: slots.title,
|
||||||
|
default: slots.value,
|
||||||
|
'right-icon': slots['right-icon'],
|
||||||
|
}}
|
||||||
role="button"
|
role="button"
|
||||||
class={bem('title', { disabled, expanded, borderless: !border })}
|
class={bem('title', {
|
||||||
onClick={this.onClick}
|
disabled,
|
||||||
|
expanded: expanded.value,
|
||||||
|
borderless: !border,
|
||||||
|
})}
|
||||||
tabindex={disabled ? -1 : 0}
|
tabindex={disabled ? -1 : 0}
|
||||||
aria-expanded={String(expanded)}
|
aria-expanded={String(expanded.value)}
|
||||||
{...this.$props}
|
onClick={onClickTitle}
|
||||||
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
|
|
||||||
genContent() {
|
const renderContent = () => {
|
||||||
if (this.inited) {
|
if (inited.value) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
vShow={this.show}
|
ref={wrapper}
|
||||||
ref="wrapper"
|
vShow={show.value}
|
||||||
class={bem('wrapper')}
|
class={bem('wrapper')}
|
||||||
onTransitionend={this.onTransitionEnd}
|
onTransitionend={onTransitionEnd}
|
||||||
>
|
>
|
||||||
<div ref="content" class={bem('content')}>
|
<div ref={content} class={bem('content')}>
|
||||||
{this.$slots.default?.()}
|
{slots.default?.()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
return () => (
|
||||||
return (
|
<div class={[bem({ border: index.value && props.border })]}>
|
||||||
<div class={[bem({ border: this.index && this.border })]}>
|
{renderTitle()}
|
||||||
{this.genTitle()}
|
{renderContent()}
|
||||||
{this.genContent()}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
|
import { ref, provide } from 'vue';
|
||||||
import { createNamespace } from '../utils';
|
import { createNamespace } from '../utils';
|
||||||
import { ParentMixin } from '../mixins/relation';
|
|
||||||
import { BORDER_TOP_BOTTOM } from '../utils/constant';
|
import { BORDER_TOP_BOTTOM } from '../utils/constant';
|
||||||
|
|
||||||
const [createComponent, bem] = createNamespace('collapse');
|
const [createComponent, bem] = createNamespace('collapse');
|
||||||
|
|
||||||
export default createComponent({
|
export const COLLAPSE_KEY = 'vanCollapse';
|
||||||
mixins: [ParentMixin('vanCollapse')],
|
|
||||||
|
|
||||||
|
export default createComponent({
|
||||||
props: {
|
props: {
|
||||||
accordion: Boolean,
|
accordion: Boolean,
|
||||||
modelValue: [String, Number, Array],
|
modelValue: [String, Number, Array],
|
||||||
@ -18,22 +18,52 @@ export default createComponent({
|
|||||||
|
|
||||||
emits: ['change', 'update:modelValue'],
|
emits: ['change', 'update:modelValue'],
|
||||||
|
|
||||||
methods: {
|
setup(props, { emit, slots }) {
|
||||||
switch(name, expanded) {
|
const children = ref([]);
|
||||||
if (!this.accordion) {
|
|
||||||
name = expanded
|
|
||||||
? this.modelValue.concat(name)
|
|
||||||
: this.modelValue.filter((activeName) => activeName !== name);
|
|
||||||
}
|
|
||||||
this.$emit('change', name);
|
|
||||||
this.$emit('update:modelValue', name);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
const toggle = (name, expanded) => {
|
||||||
return (
|
const { accordion, modelValue } = props;
|
||||||
<div class={[bem(), { [BORDER_TOP_BOTTOM]: this.border }]}>
|
|
||||||
{this.$slots.default?.()}
|
if (accordion) {
|
||||||
|
if (name === modelValue) {
|
||||||
|
name = '';
|
||||||
|
}
|
||||||
|
} else if (expanded) {
|
||||||
|
name = modelValue.concat(name);
|
||||||
|
} else {
|
||||||
|
name = modelValue.filter((activeName) => activeName !== name);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('change', name);
|
||||||
|
emit('update:modelValue', name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isExpanded = (name) => {
|
||||||
|
const { accordion, modelValue } = props;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!accordion &&
|
||||||
|
!Array.isArray(modelValue) &&
|
||||||
|
process.env.NODE_ENV !== 'production'
|
||||||
|
) {
|
||||||
|
console.error(
|
||||||
|
'[Vant] Collapse: type of prop "modelValue" should be Array'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return accordion ? modelValue === name : modelValue.indexOf(name) !== -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
provide(COLLAPSE_KEY, {
|
||||||
|
toggle,
|
||||||
|
children,
|
||||||
|
isExpanded,
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<div class={[bem(), { [BORDER_TOP_BOTTOM]: props.border }]}>
|
||||||
|
{slots.default?.()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user