Skip to content

Commit e298a6f

Browse files
authored
fix(carousel): emit igcSlideChanged on slide change (#1778)
1 parent 46259a7 commit e298a6f

File tree

3 files changed

+141
-17
lines changed

3 files changed

+141
-17
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
88
### Fixed
99
- #### Carousel
1010
- Pause automatic rotation on pointer-initiated focus [#1731](https://github.com/IgniteUI/igniteui-webcomponents/issues/1731)
11+
- Ensure `igcSlideChanged` event is emitted when a slide is changed [#1772](https://github.com/IgniteUI/igniteui-webcomponents/issues/1772)
1112

1213
## [6.1.1] - 2025-06-25
1314
### Fixed

src/components/carousel/carousel.spec.ts

Lines changed: 124 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
waitUntil,
88
} from '@open-wc/testing';
99

10-
import { type SinonFakeTimers, spy, useFakeTimers } from 'sinon';
10+
import { type SinonFakeTimers, spy, stub, useFakeTimers } from 'sinon';
1111
import IgcButtonComponent from '../button/button.js';
1212
import {
1313
arrowLeft,
@@ -88,12 +88,6 @@ describe('Carousel', () => {
8888
IgcCarouselIndicatorComponent.tagName
8989
)
9090
);
91-
92-
clock = useFakeTimers({ toFake: ['setInterval'] });
93-
});
94-
95-
afterEach(() => {
96-
clock.restore();
9791
});
9892

9993
describe('Initialization', () => {
@@ -556,6 +550,26 @@ describe('Carousel', () => {
556550
detail: 0,
557551
});
558552
});
553+
554+
it('should properly call `igcSlideChanged` event', async () => {
555+
const eventSpy = spy(carousel, 'emitEvent');
556+
557+
stub(carousel, 'select')
558+
.onFirstCall()
559+
.resolves(true)
560+
.onSecondCall()
561+
.resolves(false);
562+
563+
// select second indicator
564+
simulateClick(defaultIndicators[1]);
565+
await slideChangeComplete(slides[0], slides[1]);
566+
567+
// select second indicator again
568+
simulateClick(defaultIndicators[1]);
569+
await slideChangeComplete(slides[0], slides[1]);
570+
571+
expect(eventSpy.callCount).to.equal(1);
572+
});
559573
});
560574

561575
describe('Keyboard', () => {
@@ -643,6 +657,12 @@ describe('Carousel', () => {
643657
});
644658

645659
describe('Automatic rotation', () => {
660+
beforeEach(async () => {
661+
clock = useFakeTimers({ toFake: ['setInterval'] });
662+
});
663+
664+
afterEach(() => clock.restore());
665+
646666
it('should automatically change slides', async () => {
647667
expect(carousel.current).to.equal(0);
648668

@@ -655,6 +675,21 @@ describe('Carousel', () => {
655675
expect(carousel.current).to.equal(1);
656676
});
657677

678+
it('should properly call `igcSlideChanged` event', async () => {
679+
const eventSpy = spy(carousel, 'emitEvent');
680+
681+
carousel.disableLoop = true;
682+
carousel.interval = 100;
683+
await elementUpdated(carousel);
684+
685+
expect(carousel.current).to.equal(0);
686+
687+
await clock.tickAsync(300);
688+
689+
expect(carousel.current).to.equal(2);
690+
expect(eventSpy.callCount).to.equal(2);
691+
});
692+
658693
it('should pause/play on pointerenter/pointerleave', async () => {
659694
const eventSpy = spy(carousel, 'emitEvent');
660695
const divContainer = carousel.shadowRoot?.querySelector(
@@ -1021,6 +1056,88 @@ describe('Carousel', () => {
10211056

10221057
expect(carousel.current).to.equal(0);
10231058
});
1059+
1060+
it('should properly call `igcSlideChanged` event', async () => {
1061+
carousel = await fixture<IgcCarouselComponent>(
1062+
html`<igc-carousel>
1063+
<igc-carousel-slide>
1064+
<span>1</span>
1065+
</igc-carousel-slide>
1066+
<igc-carousel-slide>
1067+
<span>2</span>
1068+
</igc-carousel-slide>
1069+
</igc-carousel>`
1070+
);
1071+
1072+
carouselSlidesContainer = carousel.shadowRoot?.querySelector(
1073+
'div[aria-live="polite"]'
1074+
) as Element;
1075+
1076+
const eventSpy = spy(carousel, 'emitEvent');
1077+
1078+
const prevStub = stub(carousel, 'prev');
1079+
const nextStub = stub(carousel, 'next');
1080+
1081+
prevStub.resolves(false);
1082+
nextStub.onFirstCall().resolves(true).onSecondCall().resolves(false);
1083+
1084+
carousel.disableLoop = true;
1085+
await elementUpdated(carousel);
1086+
1087+
expect(carousel.current).to.equal(0);
1088+
1089+
// swipe right - disabled
1090+
simulatePointerDown(carouselSlidesContainer);
1091+
simulatePointerMove(carouselSlidesContainer, {}, { x: 100 }, 10);
1092+
simulateLostPointerCapture(carouselSlidesContainer);
1093+
await slideChangeComplete(slides[0], slides[2]);
1094+
1095+
// swipe left
1096+
simulatePointerDown(carouselSlidesContainer);
1097+
simulatePointerMove(carouselSlidesContainer, {}, { x: -100 }, 10);
1098+
simulateLostPointerCapture(carouselSlidesContainer);
1099+
await slideChangeComplete(slides[0], slides[1]);
1100+
1101+
// swipe left - disabled
1102+
simulatePointerDown(carouselSlidesContainer);
1103+
simulatePointerMove(carouselSlidesContainer, {}, { x: -100 }, 10);
1104+
simulateLostPointerCapture(carouselSlidesContainer);
1105+
await slideChangeComplete(slides[0], slides[1]);
1106+
1107+
expect(eventSpy.callCount).to.equal(1);
1108+
1109+
eventSpy.resetHistory();
1110+
prevStub.resetHistory();
1111+
nextStub.resetHistory();
1112+
1113+
prevStub.resolves(false);
1114+
nextStub.onFirstCall().resolves(true).onSecondCall().resolves(false);
1115+
1116+
carousel.vertical = true;
1117+
await elementUpdated(carousel);
1118+
1119+
expect(eventSpy.callCount).to.equal(0);
1120+
1121+
// swipe down - disabled
1122+
simulatePointerDown(carouselSlidesContainer);
1123+
simulatePointerMove(carouselSlidesContainer, {}, { y: 100 }, 10);
1124+
simulateLostPointerCapture(carouselSlidesContainer);
1125+
await slideChangeComplete(slides[2], slides[0]);
1126+
1127+
// swipe up
1128+
simulatePointerDown(carouselSlidesContainer);
1129+
simulatePointerMove(carouselSlidesContainer, {}, { y: -100 }, 10);
1130+
simulateLostPointerCapture(carouselSlidesContainer);
1131+
await slideChangeComplete(slides[2], slides[1]);
1132+
1133+
// swipe up - disabled
1134+
simulatePointerDown(carouselSlidesContainer);
1135+
simulatePointerMove(carouselSlidesContainer, {}, { y: -100 }, 10);
1136+
simulateLostPointerCapture(carouselSlidesContainer);
1137+
await slideChangeComplete(slides[1], slides[0]);
1138+
1139+
expect(eventSpy.callCount).to.equal(1);
1140+
});
10241141
});
10251142
});
10261143
});

src/components/carousel/carousel.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -451,13 +451,14 @@ export default class IgcCarouselComponent extends EventEmitterMixin<
451451

452452
private handleHorizontalSwipe({ data: { direction } }: SwipeEvent) {
453453
if (!this.vertical) {
454-
this.handleInteraction(async () => {
454+
const callback = () => {
455455
if (isLTR(this)) {
456-
direction === 'left' ? await this.next() : await this.prev();
457-
} else {
458-
direction === 'left' ? await this.prev() : await this.next();
456+
return direction === 'left' ? this.next : this.prev;
459457
}
460-
});
458+
return direction === 'left' ? this.prev : this.next;
459+
};
460+
461+
this.handleInteraction(callback());
461462
}
462463
}
463464

@@ -485,14 +486,15 @@ export default class IgcCarouselComponent extends EventEmitterMixin<
485486
}
486487

487488
private async handleInteraction(
488-
callback: () => Promise<unknown>
489+
callback: () => Promise<boolean>
489490
): Promise<void> {
490491
if (this.interval) {
491492
this.resetInterval();
492493
}
493494

494-
await callback.call(this);
495-
this.emitEvent('igcSlideChanged', { detail: this.current });
495+
if (await callback.call(this)) {
496+
this.emitEvent('igcSlideChanged', { detail: this.current });
497+
}
496498

497499
if (this.interval) {
498500
this.restartInterval();
@@ -538,8 +540,12 @@ export default class IgcCarouselComponent extends EventEmitterMixin<
538540

539541
if (asNumber(this.interval) > 0) {
540542
this._lastInterval = setInterval(() => {
541-
if (this.isPlaying && this.total) {
542-
this.next();
543+
if (
544+
this.isPlaying &&
545+
this.total &&
546+
!(this.disableLoop && this.nextIndex === 0)
547+
) {
548+
this.select(this.slides[this.nextIndex], 'next');
543549
this.emitEvent('igcSlideChanged', { detail: this.current });
544550
} else {
545551
this.pause();

0 commit comments

Comments
 (0)