Skip to content

Commit a03cf12

Browse files
authored
fix(components): [carousel] improper active item (element-plus#8904)
* fix(components): [carousel] none of active item closed element-plus#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
1 parent 30a5e52 commit a03cf12

File tree

5 files changed

+92
-23
lines changed

5 files changed

+92
-23
lines changed

packages/components/carousel/__tests__/carousel.test.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,30 @@ describe('Carousel', () => {
195195
await wait(60)
196196
expect(items[1].classList.contains('is-active')).toBeTruthy()
197197
})
198+
it('should guarantee order of indicators', async () => {
199+
const data = reactive([1, 2, 3, 4])
200+
wrapper = mount({
201+
setup() {
202+
return () => (
203+
<div>
204+
<Carousel>
205+
{data.map((value) => (
206+
<CarouselItem label={value} key={value}>
207+
{value}
208+
</CarouselItem>
209+
))}
210+
</Carousel>
211+
</div>
212+
)
213+
},
214+
})
215+
await nextTick()
216+
217+
data.splice(1, 0, 5)
218+
await nextTick()
219+
const indicators = wrapper.findAll('.el-carousel__button')
220+
data.forEach((value, index) => {
221+
expect(indicators[index].element.textContent).toEqual(value.toString())
222+
})
223+
})
198224
})

packages/components/carousel/src/carousel.vue

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
<script lang="ts" setup>
6565
import {
6666
computed,
67-
nextTick,
67+
getCurrentInstance,
6868
onBeforeUnmount,
6969
onMounted,
7070
provide,
@@ -78,7 +78,7 @@ import { useResizeObserver } from '@vueuse/core'
7878
import { debugWarn, isString } from '@element-plus/utils'
7979
import { ElIcon } from '@element-plus/components/icon'
8080
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
81-
import { useNamespace } from '@element-plus/hooks'
81+
import { useNamespace, useOrderedChildren } from '@element-plus/hooks'
8282
import { carouselContextKey } from '@element-plus/tokens'
8383
import { carouselEmits, carouselProps } from './carousel'
8484
import type { CarouselItemContext } from '@element-plus/tokens'
@@ -93,12 +93,20 @@ const ns = useNamespace('carousel')
9393
const COMPONENT_NAME = 'ElCarousel'
9494
const THROTTLE_TIME = 300
9595
96+
const {
97+
children: items,
98+
addChild: addItem,
99+
removeChild: removeItem,
100+
} = useOrderedChildren<CarouselItemContext>(
101+
getCurrentInstance()!,
102+
'ElCarouselItem'
103+
)
104+
96105
// refs
97106
const activeIndex = ref(-1)
98107
const timer = ref<ReturnType<typeof setInterval> | null>(null)
99108
const hover = ref(false)
100109
const root = ref<HTMLDivElement>()
101-
const items = ref<Array<CarouselItemContext>>([])
102110
103111
// computed
104112
const arrowDisplay = computed(
@@ -199,18 +207,6 @@ function resetItemPosition(oldIndex?: number) {
199207
})
200208
}
201209
202-
function addItem(item: CarouselItemContext) {
203-
items.value.push(item)
204-
}
205-
206-
function removeItem(uid?: number) {
207-
const index = items.value.findIndex((item) => item.uid === uid)
208-
if (index !== -1) {
209-
items.value.splice(index, 1)
210-
if (activeIndex.value === index) next()
211-
}
212-
}
213-
214210
function itemInStage(item: CarouselItemContext, index: number) {
215211
const _items = unref(items)
216212
const itemCount = _items.length
@@ -312,17 +308,19 @@ watch(
312308
}
313309
)
314310
311+
watch(
312+
() => items.value,
313+
() => {
314+
if (items.value.length > 0) setActiveItem(props.initialIndex)
315+
}
316+
)
317+
315318
const resizeObserver = shallowRef<ReturnType<typeof useResizeObserver>>()
316319
// lifecycle
317-
onMounted(async () => {
318-
await nextTick()
319-
320+
onMounted(() => {
320321
resizeObserver.value = useResizeObserver(root.value, () => {
321322
resetItemPosition()
322323
})
323-
if (props.initialIndex < items.value.length && props.initialIndex >= 0) {
324-
activeIndex.value = props.initialIndex
325-
}
326324
startTimer()
327325
})
328326

packages/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ export * from './use-namespace'
2828
export * from './use-z-index'
2929
export * from './use-floating'
3030
export * from './use-cursor'
31+
export * from './use-ordered-children'
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { shallowRef } from 'vue'
2+
import { flattedChildren, isVNode } from '@element-plus/utils'
3+
4+
import type { ComponentInternalInstance, VNode } from 'vue'
5+
6+
const getOrderedChildren = <T>(
7+
vm: ComponentInternalInstance,
8+
childComponentName: string,
9+
children: Record<number, T>
10+
): T[] => {
11+
const nodes = flattedChildren(vm.subTree).filter(
12+
(n): n is VNode =>
13+
isVNode(n) &&
14+
(n.type as any)?.name === childComponentName &&
15+
!!n.component
16+
)
17+
const uids = nodes.map((n) => n.component!.uid)
18+
return uids.map((uid) => children[uid]).filter((p) => !!p)
19+
}
20+
21+
export const useOrderedChildren = <T extends { uid: number }>(
22+
vm: ComponentInternalInstance,
23+
childComponentName: string
24+
) => {
25+
const children: Record<number, T> = {}
26+
const orderedChildren = shallowRef<T[]>([])
27+
28+
const addChild = (child: T) => {
29+
children[child.uid] = child
30+
orderedChildren.value = getOrderedChildren(vm, childComponentName, children)
31+
}
32+
const removeChild = (uid: number) => {
33+
delete children[uid]
34+
orderedChildren.value = orderedChildren.value.filter(
35+
(children) => children.uid !== uid
36+
)
37+
}
38+
39+
return {
40+
children: orderedChildren,
41+
addChild,
42+
removeChild,
43+
}
44+
}

packages/tokens/carousel.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export type CarouselItemStates = {
1515
export type CarouselItemContext = {
1616
props: CarouselItemProps
1717
states: CarouselItemStates
18-
uid: number | undefined
18+
uid: number
1919
translateItem: (index: number, activeIndex: number, oldIndex?: number) => void
2020
}
2121

@@ -26,7 +26,7 @@ export type CarouselContext = {
2626
isVertical: Ref<boolean>
2727
loop: boolean
2828
addItem: (item: CarouselItemContext) => void
29-
removeItem: (uid: number | undefined) => void
29+
removeItem: (uid: number) => void
3030
setActiveItem: (index: number) => void
3131
}
3232

0 commit comments

Comments
 (0)