diff --git a/packages/components/carousel/index.ts b/packages/components/carousel/index.ts index c3d4352236..8840107e34 100644 --- a/packages/components/carousel/index.ts +++ b/packages/components/carousel/index.ts @@ -13,4 +13,4 @@ export const ElCarouselItem = withNoopInstall(CarouselItem) export * from './src/carousel' export * from './src/carousel-item' -export type { CarouselInstance } from './src/instance' +export type { CarouselInstance, CarouselItemInstance } from './src/instance' diff --git a/packages/components/carousel/src/carousel-item.ts b/packages/components/carousel/src/carousel-item.ts index 00426004dd..e4d824d8a4 100644 --- a/packages/components/carousel/src/carousel-item.ts +++ b/packages/components/carousel/src/carousel-item.ts @@ -1,6 +1,5 @@ import { buildProps } from '@element-plus/utils' import type { ExtractPropTypes } from 'vue' -import type CarouselItem from './carousel-item.vue' export const carouselItemProps = buildProps({ name: { type: String, default: '' }, @@ -11,5 +10,3 @@ export const carouselItemProps = buildProps({ } as const) export type CarouselItemProps = ExtractPropTypes - -export type CarouselItemInstance = InstanceType diff --git a/packages/components/carousel/src/carousel-item.vue b/packages/components/carousel/src/carousel-item.vue index c64ea1e465..06c914db53 100644 --- a/packages/components/carousel/src/carousel-item.vue +++ b/packages/components/carousel/src/carousel-item.vue @@ -18,20 +18,10 @@ diff --git a/packages/components/carousel/src/instance.ts b/packages/components/carousel/src/instance.ts index e935041c3a..eb29006421 100644 --- a/packages/components/carousel/src/instance.ts +++ b/packages/components/carousel/src/instance.ts @@ -1,3 +1,5 @@ import type Carousel from './carousel.vue' +import type CarouselItem from './carousel-item.vue' export type CarouselInstance = InstanceType +export type CarouselItemInstance = InstanceType diff --git a/packages/components/carousel/src/use-carousel-item.ts b/packages/components/carousel/src/use-carousel-item.ts new file mode 100644 index 0000000000..a5f3249470 --- /dev/null +++ b/packages/components/carousel/src/use-carousel-item.ts @@ -0,0 +1,173 @@ +import { + getCurrentInstance, + inject, + onMounted, + onUnmounted, + reactive, + ref, + unref, +} from 'vue' +import { debugWarn, isUndefined } from '@element-plus/utils' +import { carouselContextKey } from '@element-plus/tokens' + +import type { CarouselItemProps } from './carousel-item' + +export const useCarouselItem = ( + props: CarouselItemProps, + componentName: string +) => { + const carouselContext = inject(carouselContextKey)! + // instance + const instance = getCurrentInstance()! + if (!carouselContext) { + debugWarn( + componentName, + 'usage: ' + ) + } + + if (!instance) { + debugWarn( + componentName, + 'compositional hook can only be invoked inside setups' + ) + } + + const CARD_SCALE = 0.83 + + const hover = ref(false) + const translate = ref(0) + const scale = ref(1) + const active = ref(false) + const ready = ref(false) + const inStage = ref(false) + const animating = ref(false) + + // computed + const { isCardType, isVertical } = carouselContext + + // methods + + function processIndex(index: number, activeIndex: number, length: number) { + const lastItemIndex = length - 1 + const prevItemIndex = activeIndex - 1 + const nextItemIndex = activeIndex + 1 + const halfItemIndex = length / 2 + + if (activeIndex === 0 && index === lastItemIndex) { + return -1 + } else if (activeIndex === lastItemIndex && index === 0) { + return length + } else if (index < prevItemIndex && activeIndex - index >= halfItemIndex) { + return length + 1 + } else if (index > nextItemIndex && index - activeIndex >= halfItemIndex) { + return -2 + } + return index + } + + function calcCardTranslate(index: number, activeIndex: number) { + const parentWidth = carouselContext.root.value?.offsetWidth || 0 + if (inStage.value) { + return (parentWidth * ((2 - CARD_SCALE) * (index - activeIndex) + 1)) / 4 + } else if (index < activeIndex) { + return (-(1 + CARD_SCALE) * parentWidth) / 4 + } else { + return ((3 + CARD_SCALE) * parentWidth) / 4 + } + } + + function calcTranslate( + index: number, + activeIndex: number, + isVertical: boolean + ) { + const rootEl = carouselContext.root.value + if (!rootEl) return 0 + + const distance = + (isVertical ? rootEl.offsetHeight : rootEl.offsetWidth) || 0 + return distance * (index - activeIndex) + } + + const translateItem = ( + index: number, + activeIndex: number, + oldIndex?: number + ) => { + const _isCardType = unref(isCardType) + const carouselItemLength = carouselContext.items.value.length ?? Number.NaN + + const isActive = index === activeIndex + if (!_isCardType && !isUndefined(oldIndex)) { + animating.value = isActive || index === oldIndex + } + + if (!isActive && carouselItemLength > 2 && carouselContext.loop) { + index = processIndex(index, activeIndex, carouselItemLength) + } + + const _isVertical = unref(isVertical) + active.value = isActive + + if (_isCardType) { + if (_isVertical) { + debugWarn( + 'Carousel', + 'vertical direction is not supported for card mode' + ) + } + inStage.value = Math.round(Math.abs(index - activeIndex)) <= 1 + translate.value = calcCardTranslate(index, activeIndex) + scale.value = unref(active) ? 1 : CARD_SCALE + } else { + translate.value = calcTranslate(index, activeIndex, _isVertical) + } + + ready.value = true + } + + function handleItemClick() { + if (carouselContext && unref(isCardType)) { + const index = carouselContext.items.value.findIndex( + ({ uid }) => uid === instance.uid + ) + carouselContext.setActiveItem(index) + } + } + + // lifecycle + onMounted(() => { + carouselContext.addItem({ + props, + states: reactive({ + hover, + translate, + scale, + active, + ready, + inStage, + animating, + }), + uid: instance.uid, + translateItem, + }) + }) + + onUnmounted(() => { + carouselContext.removeItem(instance.uid) + }) + + return { + active, + animating, + hover, + inStage, + isVertical, + translate, + isCardType, + scale, + ready, + handleItemClick, + } +}