Fixed #5474 - Carousel Enhancement - Empty Slot

pull/5513/head
tugcekucukoglu 2024-04-02 16:23:41 +03:00
parent 038d9959d0
commit 2ddfb689d1
2 changed files with 139 additions and 137 deletions

View File

@ -346,6 +346,10 @@ export interface CarouselSlots {
* Custom next icon template. * Custom next icon template.
*/ */
nexticon(): VNode[]; nexticon(): VNode[];
/**
* Custom empty template.
*/
empty(): VNode[];
} }
/** /**

View File

@ -3,96 +3,95 @@
<div v-if="$slots.header" :class="cx('header')" v-bind="ptm('header')"> <div v-if="$slots.header" :class="cx('header')" v-bind="ptm('header')">
<slot name="header"></slot> <slot name="header"></slot>
</div> </div>
<div :class="[cx('content'), contentClass]" v-bind="ptm('content')"> <div v-if="!empty" :class="[cx('content'), contentClass]" v-bind="ptm('content')">
<template v-if="!empty"> <div :class="[cx('container'), containerClass]" :aria-live="allowAutoplay ? 'polite' : 'off'" v-bind="ptm('container')">
<div :class="[cx('container'), containerClass]" :aria-live="allowAutoplay ? 'polite' : 'off'" v-bind="ptm('container')"> <button
<button v-if="showNavigators"
v-if="showNavigators" v-ripple
v-ripple type="button"
type="button" :class="cx('previousButton')"
:class="cx('previousButton')" :disabled="backwardIsDisabled"
:disabled="backwardIsDisabled" :aria-label="ariaPrevButtonLabel"
:aria-label="ariaPrevButtonLabel" @click="navBackward"
@click="navBackward" v-bind="{ ...prevButtonProps, ...ptm('previousButton') }"
v-bind="{ ...prevButtonProps, ...ptm('previousButton') }" data-pc-group-section="navigator"
data-pc-group-section="navigator" >
> <slot name="previousicon">
<slot name="previousicon"> <component :is="isVertical() ? 'ChevronUpIcon' : 'ChevronLeftIcon'" :class="cx('previousButtonIcon')" v-bind="ptm('previousButtonIcon')" />
<component :is="isVertical() ? 'ChevronUpIcon' : 'ChevronLeftIcon'" :class="cx('previousButtonIcon')" v-bind="ptm('previousButtonIcon')" /> </slot>
</slot> </button>
</button>
<div :class="cx('itemsContent')" :style="[{ height: isVertical() ? verticalViewPortHeight : 'auto' }]" @touchend="onTouchEnd" @touchstart="onTouchStart" @touchmove="onTouchMove" v-bind="ptm('itemsContent')"> <div :class="cx('itemsContent')" :style="[{ height: isVertical() ? verticalViewPortHeight : 'auto' }]" @touchend="onTouchEnd" @touchstart="onTouchStart" @touchmove="onTouchMove" v-bind="ptm('itemsContent')">
<div ref="itemsContainer" :class="cx('itemsContainer')" @transitionend="onTransitionEnd" v-bind="ptm('itemsContainer')"> <div ref="itemsContainer" :class="cx('itemsContainer')" @transitionend="onTransitionEnd" v-bind="ptm('itemsContainer')">
<template v-if="isCircular()"> <template v-if="isCircular()">
<div
v-for="(item, index) of value.slice(-1 * d_numVisible)"
:key="index + '_scloned'"
:class="cx('itemCloned', { index, value, totalShiftedItems, d_numVisible })"
v-bind="ptm('itemCloned')"
:data-p-carousel-item-active="totalShiftedItems * -1 === value.length + d_numVisible"
:data-p-carousel-item-start="index === 0"
:data-p-carousel-item-end="value.slice(-1 * d_numVisible).length - 1 === index"
>
<slot name="item" :data="item" :index="index"></slot>
</div>
</template>
<div <div
v-for="(item, index) of value" v-for="(item, index) of value.slice(-1 * d_numVisible)"
:key="index" :key="index + '_scloned'"
:class="cx('item', { index })" :class="cx('itemCloned', { index, value, totalShiftedItems, d_numVisible })"
role="group" v-bind="ptm('itemCloned')"
:aria-hidden="firstIndex() > index || lastIndex() < index ? true : undefined" :data-p-carousel-item-active="totalShiftedItems * -1 === value.length + d_numVisible"
:aria-label="ariaSlideNumber(index)" :data-p-carousel-item-start="index === 0"
:aria-roledescription="ariaSlideLabel" :data-p-carousel-item-end="value.slice(-1 * d_numVisible).length - 1 === index"
v-bind="ptm('item')"
:data-p-carousel-item-active="firstIndex() <= index && lastIndex() >= index"
:data-p-carousel-item-start="firstIndex() === index"
:data-p-carousel-item-end="lastIndex() === index"
> >
<slot name="item" :data="item" :index="index"></slot> <slot name="item" :data="item" :index="index"></slot>
</div> </div>
<template v-if="isCircular()"> </template>
<div v-for="(item, index) of value.slice(0, d_numVisible)" :key="index + '_fcloned'" :class="cx('itemCloned', { index, value, totalShiftedItems, d_numVisible })" v-bind="ptm('itemCloned')"> <div
<slot name="item" :data="item" :index="index"></slot> v-for="(item, index) of value"
</div> :key="index"
</template> :class="cx('item', { index })"
role="group"
:aria-hidden="firstIndex() > index || lastIndex() < index ? true : undefined"
:aria-label="ariaSlideNumber(index)"
:aria-roledescription="ariaSlideLabel"
v-bind="ptm('item')"
:data-p-carousel-item-active="firstIndex() <= index && lastIndex() >= index"
:data-p-carousel-item-start="firstIndex() === index"
:data-p-carousel-item-end="lastIndex() === index"
>
<slot name="item" :data="item" :index="index"></slot>
</div> </div>
<template v-if="isCircular()">
<div v-for="(item, index) of value.slice(0, d_numVisible)" :key="index + '_fcloned'" :class="cx('itemCloned', { index, value, totalShiftedItems, d_numVisible })" v-bind="ptm('itemCloned')">
<slot name="item" :data="item" :index="index"></slot>
</div>
</template>
</div> </div>
<button
v-if="showNavigators"
v-ripple
type="button"
:class="cx('nextButton')"
:disabled="forwardIsDisabled"
:aria-label="ariaNextButtonLabel"
@click="navForward"
v-bind="{ ...nextButtonProps, ...ptm('nextButton') }"
data-pc-group-section="navigator"
>
<slot name="nexticon">
<component :is="isVertical() ? 'ChevronDownIcon' : 'ChevronRightIcon'" :class="cx('nextButtonIcon')" v-bind="ptm('nextButtonIcon')" />
</slot>
</button>
</div> </div>
<ul v-if="totalIndicators >= 0 && showIndicators" ref="indicatorContent" :class="[cx('indicators'), indicatorsContentClass]" @keydown="onIndicatorKeydown" v-bind="ptm('indicators')">
<li v-for="(indicator, i) of totalIndicators" :key="'p-carousel-indicator-' + i.toString()" :class="cx('indicator', { index: i })" v-bind="ptm('indicator', getIndicatorPTOptions(i))" :data-p-highlight="d_page === i"> <button
<button v-if="showNavigators"
:class="cx('indicatorButton')" v-ripple
type="button" type="button"
:tabindex="d_page === i ? '0' : '-1'" :class="cx('nextButton')"
:aria-label="ariaPageLabel(i + 1)" :disabled="forwardIsDisabled"
:aria-current="d_page === i ? 'page' : undefined" :aria-label="ariaNextButtonLabel"
@click="onIndicatorClick($event, i)" @click="navForward"
v-bind="ptm('indicatorButton', getIndicatorPTOptions(i))" v-bind="{ ...nextButtonProps, ...ptm('nextButton') }"
/> data-pc-group-section="navigator"
</li> >
</ul> <slot name="nexticon">
</template> <component :is="isVertical() ? 'ChevronDownIcon' : 'ChevronRightIcon'" :class="cx('nextButtonIcon')" v-bind="ptm('nextButtonIcon')" />
<slot name="empty" v-else> </slot>
</slot> </button>
</div>
<ul v-if="totalIndicators >= 0 && showIndicators" ref="indicatorContent" :class="[cx('indicators'), indicatorsContentClass]" @keydown="onIndicatorKeydown" v-bind="ptm('indicators')">
<li v-for="(indicator, i) of totalIndicators" :key="'p-carousel-indicator-' + i.toString()" :class="cx('indicator', { index: i })" v-bind="ptm('indicator', getIndicatorPTOptions(i))" :data-p-highlight="d_page === i">
<button
:class="cx('indicatorButton')"
type="button"
:tabindex="d_page === i ? '0' : '-1'"
:aria-label="ariaPageLabel(i + 1)"
:aria-current="d_page === i ? 'page' : undefined"
@click="onIndicatorClick($event, i)"
v-bind="ptm('indicatorButton', getIndicatorPTOptions(i))"
/>
</li>
</ul>
</div> </div>
<slot v-else name="empty">
{{ emptyMessageText }}
</slot>
<div v-if="$slots.footer" :class="cx('footer')" v-bind="ptm('footer')"> <div v-if="$slots.footer" :class="cx('footer')" v-bind="ptm('footer')">
<slot name="footer"></slot> <slot name="footer"></slot>
</div> </div>
@ -190,77 +189,73 @@ export default {
} }
}, },
updated() { updated() {
const isCircular = this.isCircular(); if (!this.empty) {
let stateChanged = false; const isCircular = this.isCircular();
let totalShiftedItems = this.totalShiftedItems; let stateChanged = false;
let totalShiftedItems = this.totalShiftedItems;
if (this.autoplayInterval) { if (this.autoplayInterval) {
this.stopAutoplay(); this.stopAutoplay();
}
if (this.d_oldNumScroll !== this.d_numScroll || this.d_oldNumVisible !== this.d_numVisible || this.d_oldValue.length !== this.value.length) {
this.remainingItems = (this.value.length - this.d_numVisible) % this.d_numScroll;
let page = this.d_page;
if (this.totalIndicators !== 0 && page >= this.totalIndicators) {
page = this.totalIndicators - 1;
this.$emit('update:page', page);
this.d_page = page;
stateChanged = true;
} }
totalShiftedItems = page * this.d_numScroll * -1; if (this.d_oldNumScroll !== this.d_numScroll || this.d_oldNumVisible !== this.d_numVisible || this.d_oldValue.length !== this.value.length) {
this.remainingItems = (this.value.length - this.d_numVisible) % this.d_numScroll;
if (isCircular) { let page = this.d_page;
totalShiftedItems -= this.d_numVisible;
}
if (page === this.totalIndicators - 1 && this.remainingItems > 0) { if (this.totalIndicators !== 0 && page >= this.totalIndicators) {
totalShiftedItems += -1 * this.remainingItems + this.d_numScroll; page = this.totalIndicators - 1;
this.isRemainingItemsAdded = true; this.$emit('update:page', page);
} else { this.d_page = page;
this.isRemainingItemsAdded = false; stateChanged = true;
} }
if (totalShiftedItems !== this.totalShiftedItems) { totalShiftedItems = page * this.d_numScroll * -1;
this.totalShiftedItems = totalShiftedItems;
stateChanged = true; if (isCircular) {
} totalShiftedItems -= this.d_numVisible;
}
this.d_oldNumScroll = this.d_numScroll; if (page === this.totalIndicators - 1 && this.remainingItems > 0) {
this.d_oldNumVisible = this.d_numVisible; totalShiftedItems += -1 * this.remainingItems + this.d_numScroll;
this.d_oldValue = this.value; this.isRemainingItemsAdded = true;
} else {
this.isRemainingItemsAdded = false;
}
if(!this.empty) { if (totalShiftedItems !== this.totalShiftedItems) {
this.totalShiftedItems = totalShiftedItems;
stateChanged = true;
}
this.d_oldNumScroll = this.d_numScroll;
this.d_oldNumVisible = this.d_numVisible;
this.d_oldValue = this.value;
this.$refs.itemsContainer.style.transform = this.isVertical() ? `translate3d(0, ${totalShiftedItems * (100 / this.d_numVisible)}%, 0)` : `translate3d(${totalShiftedItems * (100 / this.d_numVisible)}%, 0, 0)`; this.$refs.itemsContainer.style.transform = this.isVertical() ? `translate3d(0, ${totalShiftedItems * (100 / this.d_numVisible)}%, 0)` : `translate3d(${totalShiftedItems * (100 / this.d_numVisible)}%, 0, 0)`;
} }
}
if (isCircular) { if (isCircular) {
if (this.d_page === 0) { if (this.d_page === 0) {
totalShiftedItems = -1 * this.d_numVisible; totalShiftedItems = -1 * this.d_numVisible;
} else if (totalShiftedItems === 0) { } else if (totalShiftedItems === 0) {
totalShiftedItems = -1 * this.value.length; totalShiftedItems = -1 * this.value.length;
if (this.remainingItems > 0) { if (this.remainingItems > 0) {
this.isRemainingItemsAdded = true; this.isRemainingItemsAdded = true;
}
}
if (totalShiftedItems !== this.totalShiftedItems) {
this.totalShiftedItems = totalShiftedItems;
stateChanged = true;
} }
} }
if (totalShiftedItems !== this.totalShiftedItems) { if (!stateChanged && this.isAutoplay()) {
this.totalShiftedItems = totalShiftedItems; this.startAutoplay();
stateChanged = true;
} }
} }
if (!stateChanged && this.isAutoplay()) {
this.startAutoplay();
}
}, },
beforeUnmount() { beforeUnmount() {
if (this.responsiveOptions) { if (this.responsiveOptions) {
@ -610,9 +605,6 @@ export default {
} }
}, },
computed: { computed: {
empty() {
return !this.value || this.value.length === 0;
},
totalIndicators() { totalIndicators() {
return this.value ? Math.max(Math.ceil((this.value.length - this.d_numVisible) / this.d_numScroll) + 1, 0) : 0; return this.value ? Math.max(Math.ceil((this.value.length - this.d_numVisible) / this.d_numScroll) + 1, 0) : 0;
}, },
@ -633,6 +625,12 @@ export default {
}, },
attributeSelector() { attributeSelector() {
return UniqueComponentId(); return UniqueComponentId();
},
empty() {
return !this.value || this.value.length === 0;
},
emptyMessageText() {
return this.$primevue.config?.locale?.emptyMessage || '';
} }
}, },
components: { components: {