fix(components): [carousel] improper active item (#8904)

* fix(components): [carousel] none of active item

closed #8891

* fix: ts error

* fix: guarantee items order when reusing carousel-item component

* style: code format

* fix: test fail

* chore: correct type

* fix: sorting failed in some cases

* better implementation

better implementation

* impove code accroding to review comment
This commit is contained in:
dopamine
2022-10-02 08:44:40 +08:00
committed by GitHub
parent 30a5e52630
commit a03cf125fd
5 changed files with 92 additions and 23 deletions

View File

@@ -195,4 +195,30 @@ describe('Carousel', () => {
await wait(60)
expect(items[1].classList.contains('is-active')).toBeTruthy()
})
it('should guarantee order of indicators', async () => {
const data = reactive([1, 2, 3, 4])
wrapper = mount({
setup() {
return () => (
<div>
<Carousel>
{data.map((value) => (
<CarouselItem label={value} key={value}>
{value}
</CarouselItem>
))}
</Carousel>
</div>
)
},
})
await nextTick()
data.splice(1, 0, 5)
await nextTick()
const indicators = wrapper.findAll('.el-carousel__button')
data.forEach((value, index) => {
expect(indicators[index].element.textContent).toEqual(value.toString())
})
})
})

View File

@@ -64,7 +64,7 @@
<script lang="ts" setup>
import {
computed,
nextTick,
getCurrentInstance,
onBeforeUnmount,
onMounted,
provide,
@@ -78,7 +78,7 @@ import { useResizeObserver } from '@vueuse/core'
import { debugWarn, isString } from '@element-plus/utils'
import { ElIcon } from '@element-plus/components/icon'
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
import { useNamespace } from '@element-plus/hooks'
import { useNamespace, useOrderedChildren } from '@element-plus/hooks'
import { carouselContextKey } from '@element-plus/tokens'
import { carouselEmits, carouselProps } from './carousel'
import type { CarouselItemContext } from '@element-plus/tokens'
@@ -93,12 +93,20 @@ const ns = useNamespace('carousel')
const COMPONENT_NAME = 'ElCarousel'
const THROTTLE_TIME = 300
const {
children: items,
addChild: addItem,
removeChild: removeItem,
} = useOrderedChildren<CarouselItemContext>(
getCurrentInstance()!,
'ElCarouselItem'
)
// refs
const activeIndex = ref(-1)
const timer = ref<ReturnType<typeof setInterval> | null>(null)
const hover = ref(false)
const root = ref<HTMLDivElement>()
const items = ref<Array<CarouselItemContext>>([])
// computed
const arrowDisplay = computed(
@@ -199,18 +207,6 @@ function resetItemPosition(oldIndex?: number) {
})
}
function addItem(item: CarouselItemContext) {
items.value.push(item)
}
function removeItem(uid?: number) {
const index = items.value.findIndex((item) => item.uid === uid)
if (index !== -1) {
items.value.splice(index, 1)
if (activeIndex.value === index) next()
}
}
function itemInStage(item: CarouselItemContext, index: number) {
const _items = unref(items)
const itemCount = _items.length
@@ -312,17 +308,19 @@ watch(
}
)
watch(
() => items.value,
() => {
if (items.value.length > 0) setActiveItem(props.initialIndex)
}
)
const resizeObserver = shallowRef<ReturnType<typeof useResizeObserver>>()
// lifecycle
onMounted(async () => {
await nextTick()
onMounted(() => {
resizeObserver.value = useResizeObserver(root.value, () => {
resetItemPosition()
})
if (props.initialIndex < items.value.length && props.initialIndex >= 0) {
activeIndex.value = props.initialIndex
}
startTimer()
})

View File

@@ -28,3 +28,4 @@ export * from './use-namespace'
export * from './use-z-index'
export * from './use-floating'
export * from './use-cursor'
export * from './use-ordered-children'

View File

@@ -0,0 +1,44 @@
import { shallowRef } from 'vue'
import { flattedChildren, isVNode } from '@element-plus/utils'
import type { ComponentInternalInstance, VNode } from 'vue'
const getOrderedChildren = <T>(
vm: ComponentInternalInstance,
childComponentName: string,
children: Record<number, T>
): T[] => {
const nodes = flattedChildren(vm.subTree).filter(
(n): n is VNode =>
isVNode(n) &&
(n.type as any)?.name === childComponentName &&
!!n.component
)
const uids = nodes.map((n) => n.component!.uid)
return uids.map((uid) => children[uid]).filter((p) => !!p)
}
export const useOrderedChildren = <T extends { uid: number }>(
vm: ComponentInternalInstance,
childComponentName: string
) => {
const children: Record<number, T> = {}
const orderedChildren = shallowRef<T[]>([])
const addChild = (child: T) => {
children[child.uid] = child
orderedChildren.value = getOrderedChildren(vm, childComponentName, children)
}
const removeChild = (uid: number) => {
delete children[uid]
orderedChildren.value = orderedChildren.value.filter(
(children) => children.uid !== uid
)
}
return {
children: orderedChildren,
addChild,
removeChild,
}
}

View File

@@ -15,7 +15,7 @@ export type CarouselItemStates = {
export type CarouselItemContext = {
props: CarouselItemProps
states: CarouselItemStates
uid: number | undefined
uid: number
translateItem: (index: number, activeIndex: number, oldIndex?: number) => void
}
@@ -26,7 +26,7 @@ export type CarouselContext = {
isVertical: Ref<boolean>
loop: boolean
addItem: (item: CarouselItemContext) => void
removeItem: (uid: number | undefined) => void
removeItem: (uid: number) => void
setActiveItem: (index: number) => void
}