Skip to content

Commit 201446a

Browse files
antoine4livretitouanmathis
authored andcommitted
Use Indexable for the Carousel
1 parent dd5c869 commit 201446a

11 files changed

Lines changed: 64 additions & 103 deletions

packages/tests/Carousel/AbstractCarouselChild.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('The AbstractCarouselChild class', () => {
1919
expect(child.carousel).toBe(carousel);
2020
const spy = vi.spyOn(child, '$emit');
2121

22-
for (const eventName of ['go-to', 'progress']) {
22+
for (const eventName of ['index', 'progress']) {
2323
carousel.$emit(eventName, 0);
2424
expect(spy).toHaveBeenCalledExactlyOnceWith(`parent-carousel-${eventName}`, 0);
2525
spy.mockClear();
@@ -28,7 +28,7 @@ describe('The AbstractCarouselChild class', () => {
2828
await destroy(child);
2929
spy.mockClear();
3030

31-
for (const eventName of ['go-to', 'progress']) {
31+
for (const eventName of ['index', 'progress']) {
3232
carousel.$emit(eventName, 0);
3333
expect(spy).not.toHaveBeenCalled();
3434
spy.mockClear();

packages/tests/Carousel/Carousel.spec.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,27 @@ describe('The Carousel class', () => {
1414
expect(carousel.isVertical).toBe(true);
1515
});
1616

17-
it('should emit go-to and progress events', async () => {
18-
const div = h('div');
17+
it('should emit index and progress events', async () => {
18+
const items = [
19+
h('div', { dataComponent: 'CarouselItem' }),
20+
h('div', { dataComponent: 'CarouselItem' }),
21+
];
22+
const wrapper = h('div', { dataComponent: 'CarouselWrapper' }, items);
23+
const div = h('div', [wrapper]);
1924
const carousel = new Carousel(div);
20-
const goToFn = vi.fn();
25+
await mount(carousel);
26+
const indexFn = vi.fn();
2127
const progressFn = vi.fn();
22-
carousel.$on('go-to', goToFn);
28+
carousel.$on('index', indexFn);
2329
carousel.$on('progress', progressFn);
2430
carousel.goTo(0);
25-
expect(goToFn).toHaveBeenCalledOnce();
26-
expect(goToFn.mock.lastCall[0].detail).toEqual([0]);
31+
expect(indexFn).toHaveBeenCalledOnce();
32+
expect(indexFn.mock.lastCall[0].detail).toEqual([0]);
2733
await wait();
2834
expect(progressFn).toHaveBeenCalledOnce();
2935
progressFn.mockClear();
3036
carousel.goTo(1);
31-
expect(goToFn.mock.lastCall[0].detail).toEqual([1]);
37+
expect(indexFn.mock.lastCall[0].detail).toEqual([1]);
3238
});
3339

3440
it('should implement an indexable API', async () => {
@@ -43,28 +49,28 @@ describe('The Carousel class', () => {
4349
const carousel = new Carousel(div);
4450
await mount(carousel);
4551

46-
expect(carousel.index).toBe(0);
52+
expect(carousel.currentIndex).toBe(0);
4753
expect(carousel.prevIndex).toBe(0);
4854
expect(carousel.nextIndex).toBe(1);
4955
expect(carousel.lastIndex).toBe(3);
5056

5157
carousel.goTo(1);
5258

53-
expect(carousel.index).toBe(1);
59+
expect(carousel.currentIndex).toBe(1);
5460
expect(carousel.prevIndex).toBe(0);
5561
expect(carousel.nextIndex).toBe(2);
5662
expect(carousel.lastIndex).toBe(3);
5763

5864
carousel.goNext();
5965

60-
expect(carousel.index).toBe(2);
66+
expect(carousel.currentIndex).toBe(2);
6167
expect(carousel.prevIndex).toBe(1);
6268
expect(carousel.nextIndex).toBe(3);
6369
expect(carousel.lastIndex).toBe(3);
6470

6571
carousel.goPrev();
6672

67-
expect(carousel.index).toBe(1);
73+
expect(carousel.currentIndex).toBe(1);
6874
expect(carousel.prevIndex).toBe(0);
6975
expect(carousel.nextIndex).toBe(2);
7076
expect(carousel.lastIndex).toBe(3);
@@ -75,14 +81,14 @@ describe('The Carousel class', () => {
7581
const carousel = new Carousel(div);
7682
const spy = vi.spyOn(carousel, 'goTo');
7783
await mount(carousel);
78-
expect(spy).toHaveBeenCalledExactlyOnceWith(carousel.index);
84+
expect(spy).toHaveBeenCalledExactlyOnceWith(carousel.currentIndex);
7985
});
8086

8187
it('should go to the current index on resize', async () => {
8288
const div = h('div');
8389
const carousel = new Carousel(div);
8490
const spy = vi.spyOn(carousel, 'goTo');
8591
carousel.resized();
86-
expect(spy).toHaveBeenCalledExactlyOnceWith(carousel.index);
92+
expect(spy).toHaveBeenCalledExactlyOnceWith(carousel.currentIndex);
8793
});
8894
});

packages/tests/Carousel/CarouselBtn.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describe('The CarouselBtn class', () => {
1515
const carouselBtn = new CarouselBtn(btn);
1616

1717
const spy = vi.spyOn(carousel, method);
18-
spy.mockImplementation(() => {});
18+
spy.mockImplementation(() => Promise.resolve());
1919
carouselBtn.onClick();
2020
expect(spy).toHaveBeenCalledOnce();
2121
});
@@ -34,7 +34,7 @@ describe('The CarouselBtn class', () => {
3434
const carouselBtn = new CarouselBtn(btn);
3535
const spy = vi.spyOn(carouselBtn, 'carousel', 'get');
3636
// @ts-expect-error mock is partial
37-
spy.mockImplementation(() => ({ index, lastIndex }));
37+
spy.mockImplementation(() => ({ currentIndex: index, lastIndex }));
3838
carouselBtn.onParentCarouselProgress();
3939
expect(btn.disabled).toBe(isDisabled);
4040
});

packages/tests/Carousel/CarouselItem.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ describe('The CarouselItem class', () => {
2828
const carouselItem = new CarouselItem(div);
2929
vi.spyOn(carouselItem, 'index', 'get').mockImplementation(() => 0);
3030
// @ts-expect-error partial mock
31-
vi.spyOn(carouselItem, 'carousel', 'get').mockImplementation(() => ({ index: 0 }));
31+
vi.spyOn(carouselItem, 'carousel', 'get').mockImplementation(() => ({ currentIndex: 0 }));
3232

3333
carouselItem.onParentCarouselProgress();
3434
await wait(20);
3535
expect(div.style.getPropertyValue('--carousel-item-active')).toBe('1');
3636

3737
// @ts-expect-error partial mock
38-
vi.spyOn(carouselItem, 'carousel', 'get').mockImplementation(() => ({ index: 1 }));
38+
vi.spyOn(carouselItem, 'carousel', 'get').mockImplementation(() => ({ currentIndex: 1 }));
3939
carouselItem.onParentCarouselProgress();
4040
expect(div.style.getPropertyValue('--carousel-item-active')).toBe('1');
4141
await wait(20);

packages/tests/Carousel/CarouselWrapper.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('The CarouselWrapper class', () => {
4949
const div = h('div');
5050
const carouselWrapper = new CarouselWrapper(div);
5151
const mock = {
52-
index: 0,
52+
currentIndex: 0,
5353
$services: {
5454
enable: vi.fn(),
5555
},
@@ -79,29 +79,29 @@ describe('The CarouselWrapper class', () => {
7979

8080
carouselWrapper.onScroll();
8181

82-
expect(mock.index).toBe(0);
82+
expect(mock.currentIndex).toBe(0);
8383
expect(mock.$services.enable).toHaveBeenCalledExactlyOnceWith('ticked');
8484

8585
vi.spyOn(div, 'scrollLeft', 'get').mockImplementation(() => -100);
8686
vi.spyOn(div, 'scrollTop', 'get').mockImplementation(() => -100);
8787
carouselWrapper.onScroll();
8888

89-
expect(mock.index).toBe(1);
89+
expect(mock.currentIndex).toBe(1);
9090

9191
vi.spyOn(carouselWrapper, 'isHorizontal', 'get').mockImplementation(() => false);
9292
vi.spyOn(carouselWrapper, 'isVertical', 'get').mockImplementation(() => true);
9393
vi.spyOn(div, 'scrollLeft', 'get').mockImplementation(() => -90);
9494
vi.spyOn(div, 'scrollTop', 'get').mockImplementation(() => -90);
9595
carouselWrapper.onScroll();
9696

97-
expect(mock.index).toBe(1);
97+
expect(mock.currentIndex).toBe(1);
9898
});
9999

100100
it('should scroll to the matching item when the carousel goes to', async () => {
101101
const div = h('div');
102102
const carouselWrapper = new CarouselWrapper(div);
103103
const mock = {
104-
index: 0,
104+
currentIndex: 0,
105105
items: [
106106
{
107107
state: {
@@ -122,17 +122,17 @@ describe('The CarouselWrapper class', () => {
122122
carousel.mockImplementation(() => mock);
123123

124124
const spy = vi.spyOn(div, 'scrollTo');
125-
carouselWrapper.onParentCarouselGoTo();
125+
carouselWrapper.onParentCarouselIndex();
126126
expect(spy).toHaveBeenCalledExactlyOnceWith({
127127
left: 0,
128128
top: 0,
129129
behavior: 'smooth',
130130
});
131131
spy.mockClear();
132132

133-
mock.index = 1;
133+
mock.currentIndex = 1;
134134

135-
carouselWrapper.onParentCarouselGoTo();
135+
carouselWrapper.onParentCarouselIndex();
136136
expect(spy).toHaveBeenCalledExactlyOnceWith({
137137
left: -100,
138138
top: -100,

packages/ui/Carousel/AbstractCarouselChild.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class AbstractCarouselChild<T extends BaseProps = BaseProps> extends Base
1111
*/
1212
static config: BaseConfig = {
1313
name: 'AbstractCarouselChild',
14-
emits: ['parent-carousel-go-to', 'parent-carousel-progress'],
14+
emits: ['parent-carousel-index', 'parent-carousel-progress'],
1515
};
1616

1717
/**
@@ -41,7 +41,7 @@ export class AbstractCarouselChild<T extends BaseProps = BaseProps> extends Base
4141
*/
4242
handleEvent(event: CustomEvent) {
4343
switch (event.type) {
44-
case 'go-to':
44+
case 'index':
4545
case 'progress':
4646
this.$emit(`parent-carousel-${event.type}`, ...event.detail);
4747
break;
@@ -60,15 +60,15 @@ export class AbstractCarouselChild<T extends BaseProps = BaseProps> extends Base
6060
return;
6161
}
6262

63-
carousel.$on('go-to', this);
63+
carousel.$on('index', this);
6464
carousel.$on('progress', this);
6565
}
6666

6767
/**
6868
* Destroyed hook.
6969
*/
7070
destroyed() {
71-
this.carousel?.$off?.('go-to', this);
71+
this.carousel?.$off?.('index', this);
7272
this.carousel?.$off?.('progress', this);
7373
}
7474
}

packages/ui/Carousel/Carousel.ts

Lines changed: 18 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Base } from '@studiometa/js-toolkit';
2-
import type { BaseConfig, BaseProps } from '@studiometa/js-toolkit';
1+
import type { BaseConfig } from '@studiometa/js-toolkit';
2+
import type { IndexableInstructions, IndexableProps } from '../decorators/index.js';
3+
import { Indexable } from '../Indexable/index.js';
34
import { CarouselBtn } from './CarouselBtn.js';
45
import { CarouselDrag } from './CarouselDrag.js';
56
import { CarouselItem } from './CarouselItem.js';
@@ -23,7 +24,7 @@ export interface CarouselProps {
2324
/**
2425
* Carousel class.
2526
*/
26-
export class Carousel<T extends BaseProps = BaseProps> extends Base<T & CarouselProps> {
27+
export class Carousel<T extends IndexableProps = IndexableProps> extends Indexable<T & CarouselProps> {
2728
/**
2829
* Config.
2930
*/
@@ -36,51 +37,12 @@ export class Carousel<T extends BaseProps = BaseProps> extends Base<T & Carousel
3637
CarouselWrapper,
3738
},
3839
options: {
40+
...Indexable.config.options,
3941
axis: { type: String, default: 'x' },
4042
},
41-
emits: ['go-to', 'progress'],
43+
emits: [...(Indexable.config.emits || []), 'progress'],
4244
};
4345

44-
/**
45-
* Carousel index.
46-
*/
47-
__index = 0;
48-
49-
/**
50-
* Get current index.
51-
*/
52-
get index() {
53-
return this.__index;
54-
}
55-
56-
/**
57-
* Set current index.
58-
*/
59-
set index(value) {
60-
this.__index = value;
61-
}
62-
63-
/**
64-
* Previous index.
65-
*/
66-
get prevIndex() {
67-
return Math.max(this.index - 1, 0);
68-
}
69-
70-
/**
71-
* Next index.
72-
*/
73-
get nextIndex() {
74-
return Math.min(this.index + 1, this.lastIndex);
75-
}
76-
77-
/**
78-
* Last index.
79-
*/
80-
get lastIndex() {
81-
return this.items.length - 1;
82-
}
83-
8446
/**
8547
* Is the carousel horizontal?
8648
*/
@@ -102,6 +64,13 @@ export class Carousel<T extends BaseProps = BaseProps> extends Base<T & Carousel
10264
return this.$children.CarouselItem;
10365
}
10466

67+
/**
68+
* Get the carousel's length.
69+
*/
70+
get length() {
71+
return this.items?.length || 0;
72+
}
73+
10574
/**
10675
* Get the carousel's wrapper.
10776
*/
@@ -125,38 +94,23 @@ export class Carousel<T extends BaseProps = BaseProps> extends Base<T & Carousel
12594
* Mounted hook.
12695
*/
12796
mounted() {
128-
this.goTo(this.index);
97+
this.goTo(this.currentIndex);
12998
}
13099

131100
/**
132101
* Resized hook.
133102
*/
134103
resized() {
135-
this.goTo(this.index);
136-
}
137-
138-
/**
139-
* Go to the previous item.
140-
*/
141-
goPrev() {
142-
this.goTo(this.prevIndex);
143-
}
144-
145-
/**
146-
* Go to the next item.
147-
*/
148-
goNext() {
149-
this.goTo(this.nextIndex);
104+
this.goTo(this.currentIndex);
150105
}
151106

152107
/**
153108
* Go to the given item.
154109
*/
155-
goTo(index: number) {
156-
this.$log('goTo', index);
157-
this.index = index;
158-
this.$emit('go-to', index);
110+
goTo(indexOrInstruction: number | IndexableInstructions) {
111+
this.$log('goTo', indexOrInstruction);
159112
this.$services.enable('ticked');
113+
return super.goTo(indexOrInstruction);
160114
}
161115

162116
ticked() {

packages/ui/Carousel/CarouselBtn.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ export class CarouselBtn<T extends BaseProps = BaseProps> extends AbstractCarous
4848
*/
4949
onParentCarouselProgress() {
5050
const { action } = this.$options;
51-
const { index, lastIndex } = this.carousel;
51+
const { currentIndex, lastIndex } = this.carousel;
5252
const shouldDisable =
53-
(action === 'next' && index === lastIndex) ||
54-
(action === 'prev' && index === 0) ||
55-
Number(action) === index;
53+
(action === 'next' && currentIndex === lastIndex) ||
54+
(action === 'prev' && currentIndex === 0) ||
55+
Number(action) === currentIndex;
5656

5757
this.$el.disabled = shouldDisable;
5858
}

packages/ui/Carousel/CarouselItem.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export class CarouselItem<T extends BaseProps = BaseProps> extends AbstractCarou
6060
onParentCarouselProgress() {
6161
domScheduler.read(() => {
6262
const { index } = this;
63-
const { index: carouselIndex } = this.carousel;
63+
const { currentIndex: carouselIndex } = this.carousel;
6464

6565
domScheduler.write(() => {
6666
this.$el.style.setProperty(

0 commit comments

Comments
 (0)