version:element-plus 1.0.1-beta.0
<template>
<div class="el-collapse" role="tablist" aria-multiselectable="true">
<slot></slot>
</div>
</template>
<script lang="ts">
import {
defineComponent,
ref,
watch,
provide,
PropType,
Ref,
onUnmounted,
} from 'vue'
import mitt, { Emitter } from 'mitt'
export interface CollapseProvider {
activeNames: Ref
collapseMitt: Emitter
}
export default defineComponent({
name: 'ElCollapse',
props: {
// 是否手风琴模式
accordion: Boolean,
// string | number | Array<string | number>
// 文档说 如果是手风琴模式,绑定值类型需要为string,否则为array
modelValue: {
type: [Array, String, Number] as PropType<
string | number | Array<string | number>
>,
default: () => [],
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {
// concat 接受modelValue限制的所有类型 拼接成数组
const activeNames = ref([].concat(props.modelValue))
const collapseMitt: Emitter = mitt()
const setActiveNames = _activeNames => {
activeNames.value = [].concat(_activeNames)
// 手风琴模式下 返回第一个 不然 数组返回
const value = props.accordion ? activeNames.value[0] : activeNames.value
emit('update:modelValue', value)
}
// 子组件item被点击
const handleItemClick = name => {
// 是否是手风琴模式
if (props.accordion) {
// 手风琴模式其实就是一个字符串
setActiveNames(
// 如果激活的有1个 || 第一个 === 0
(activeNames.value[0] || activeNames.value[0] === 0) &&
// 第一个 === 传入的name ? '' : name
// 就是 点击展开的 -> 要收回去
activeNames.value[0] === name
? ''
: name,
)
} else {
// 非手风琴模式
// 深拷贝一份
let _activeNames = activeNames.value.slice(0)
// 找到name在[]中的index
const index = _activeNames.indexOf(name)
// 存在就收回去 不存在就push进去
if (index > -1) {
_activeNames.splice(index, 1)
} else {
_activeNames.push(name)
}
setActiveNames(_activeNames)
}
}
// props.modelValue 每次变化 都会更新activeNames.value的值,使得每次都是最近的[]结构
watch(
() => props.modelValue,
() => {
activeNames.value = [].concat(props.modelValue)
},
)
// 接收到子组件传上来的click事件
collapseMitt.on('item-click', handleItemClick)
onUnmounted(() => {
collapseMitt.all.clear()
})
provide('collapse', {
activeNames,
collapseMitt,
})
return {
activeNames,
setActiveNames,
handleItemClick,
}
},
})
</script>
<template>
<div
class="el-collapse-item"
:class="{'is-active': isActive, 'is-disabled': disabled }"
>
<!-- title -->
<div
role="tab"
:aria-expanded="isActive"
:aria-controls="`el-collapse-content-${id}`"
:aria-describedby="`el-collapse-content-${id}`"
>
<div
:id="`el-collapse-head-${id}`"
class="el-collapse-item__header"
role="button"
:tabindex="disabled ? -1 : 0"
:class="{
'focusing': focusing,
'is-active': isActive
}"
@click="handleHeaderClick"
@keyup.space.enter.stop="handleEnterClick"
@focus="handleFocus"
@blur="focusing = false"
>
<!-- title 插槽 -->
<slot name="title">{{ title }}</slot>
<!-- 右侧的箭头 -->
<i
class="el-collapse-item__arrow el-icon-arrow-right"
:class="{'is-active': isActive}"
>
</i>
</div>
</div>
<el-collapse-transition>
<!-- v-show 控制 -->
<div
v-show="isActive"
:id="`el-collapse-content-${id}`"
class="el-collapse-item__wrap"
role="tabpanel"
:aria-hidden="!isActive"
:aria-labelledby="`el-collapse-head-${id}`"
>
<div class="el-collapse-item__content">
<slot></slot>
</div>
</div>
</el-collapse-transition>
</div>
</template>
<script lang='ts'>
import { defineComponent, PropType, inject, computed, ref } from 'vue'
import { CollapseProvider } from './collapse'
// 生成[0,10000]的随机数
import { generateId } from '@element-plus/utils/util'
import ElCollapseTransition from '@element-plus/collapse-transition'
export default defineComponent({
name: 'ElCollapseItem',
components: { ElCollapseTransition },
props: {
title: {
type: String,
default: '',
},
// 默认值是随机生成的number [0,10000]
name: {
type: [String, Number] as PropType<string | number>,
default: () => {
return generateId()
},
},
disabled: Boolean,
},
setup(props) {
const collapse = inject<CollapseProvider>('collapse')
const collapseMitt = collapse?.collapseMitt
// 暴露出去但是没有使用
const contentWrapStyle = ref({
height: 'auto',
display: 'block',
})
// 暴露出去但是没有使用
const contentHeight = ref(0)
const focusing = ref(false)
const isClick = ref(false)
const id = ref(generateId())
// 是否是展开状态 判断activeNames中是哦否含有 当前item的name
// is-active 的样式就是title中的border-bottom 为transparent
const isActive = computed(() => {
return collapse?.activeNames.value.indexOf(props.name) > -1
})
// focus 事件
const handleFocus = () => {
// 50ms后 如果不是isClick 则置为true 反则false
// 我看了感觉就是isClick取反
// 我看了一下样式 focusing 就是 title 的 color 是主题蓝色
setTimeout(() => {
// ? isClick.value = !isClick.value
if(!isClick.value) {
focusing.value = true
} else {
isClick.value = false
}
}, 50)
}
// item 点击事件
const handleHeaderClick = () => {
if(props.disabled) return
collapseMitt?.emit('item-click', props.name)
focusing.value = false
isClick.value = true
}
// focus 的可以通过键盘中的 space 和 enter 触发点击事件
// 但是space会往下滚动 不太清楚原因
const handleEnterClick = () => {
collapseMitt?.emit('item-click', props.name)
}
return {
isActive,
contentWrapStyle,
contentHeight,
focusing,
isClick,
id,
handleFocus,
handleHeaderClick,
handleEnterClick,
collapse,
}
},
})
</script>