@via-profit/ui-kit 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/Swiper/Swiper.d.ts +68 -0
- package/Swiper/Swiper.js +268 -135
- package/Swiper/SwiperSlide.js +3 -0
- package/Swiper/SwiperSlidesRenderer.d.ts +4 -0
- package/Swiper/SwiperSlidesRenderer.js +2 -1
- package/Swiper/SwiperTrack.d.ts +20 -0
- package/Swiper/SwiperTrack.js +28 -5
- package/Swiper/SwiperWrapper.d.ts +3 -0
- package/Swiper/SwiperWrapper.js +5 -4
- package/esm/Swiper/Swiper.js +268 -135
- package/esm/Swiper/SwiperSlide.js +3 -0
- package/esm/Swiper/SwiperSlidesRenderer.js +2 -1
- package/esm/Swiper/SwiperTrack.js +28 -5
- package/esm/Swiper/SwiperWrapper.js +5 -4
- package/package.json +1 -1
package/Swiper/Swiper.js
CHANGED
|
@@ -58,44 +58,64 @@ const SwiperTrack_1 = __importDefault(require("./SwiperTrack"));
|
|
|
58
58
|
const SwiperSlidesRenderer_1 = __importDefault(require("./SwiperSlidesRenderer"));
|
|
59
59
|
__exportStar(require("./SwiperSlide"), exports);
|
|
60
60
|
const defaultSlideLabel = (index, total) => `${index + 1} / ${total}`;
|
|
61
|
+
const prefersReducedMotion = () => typeof window !== 'undefined' &&
|
|
62
|
+
typeof window.matchMedia === 'function' &&
|
|
63
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
61
64
|
exports.Swiper = React.forwardRef((props, ref) => {
|
|
62
|
-
const { children, dragThreshold = 240, snap = true, draggable = true, overrides, initialIndex = 0, infinite = false, onSlideChange, autoplay = false, autoplayInterval = 3000, pauseOnHover = true, threshold = 20, resistance = true, slidesPerView = 1, keyboardControl = true, slideLabel = defaultSlideLabel } = props, restProps = __rest(props, ["children", "dragThreshold", "snap", "draggable", "overrides", "initialIndex", "infinite", "onSlideChange", "autoplay", "autoplayInterval", "pauseOnHover", "threshold", "resistance", "slidesPerView", "keyboardControl", "slideLabel"]);
|
|
65
|
+
const { children, dragThreshold = 240, snap = true, draggable: draggableProp = true, overrides, initialIndex = 0, infinite = false, onSlideChange, autoplay = false, autoplayInterval = 3000, pauseOnHover = true, threshold = 20, resistance = true, slidesPerView: slidesPerViewProp = 1, centered = false, direction = 'horizontal', effect = 'slide', speed = 300, autoScroll, keyboardControl: keyboardControlProp = true, slideLabel = defaultSlideLabel } = props, restProps = __rest(props, ["children", "dragThreshold", "snap", "draggable", "overrides", "initialIndex", "infinite", "onSlideChange", "autoplay", "autoplayInterval", "pauseOnHover", "threshold", "resistance", "slidesPerView", "centered", "direction", "effect", "speed", "autoScroll", "keyboardControl", "slideLabel"]);
|
|
66
|
+
const isFade = effect === 'fade';
|
|
67
|
+
const isTicker = typeof autoScroll === 'number' && autoScroll > 0 && !isFade;
|
|
68
|
+
const isVertical = direction === 'vertical';
|
|
69
|
+
const slidesPerView = isFade ? 1 : Math.max(slidesPerViewProp, 0.1);
|
|
70
|
+
const draggable = draggableProp && !isTicker;
|
|
71
|
+
const keyboardControl = keyboardControlProp && !isTicker;
|
|
72
|
+
// The current slide is moved to the middle of the view by this number of slides
|
|
73
|
+
const centerShift = centered && !isFade ? (slidesPerView - 1) / 2 : 0;
|
|
63
74
|
// #region Slides
|
|
64
|
-
const slides = React.useMemo(() => {
|
|
75
|
+
const { slides, clones } = React.useMemo(() => {
|
|
65
76
|
// toArray skips null/false and keeps the user keys (prefixed), so the slides are not remounted
|
|
66
77
|
// when a slide is added to the start
|
|
67
78
|
const childrenSlides = React.Children.toArray(children).filter(React.isValidElement);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
// The fade effect does not move the track, the loop does not need the copies
|
|
80
|
+
if (!(infinite || isTicker) || isFade) {
|
|
81
|
+
return { slides: childrenSlides, clones: 0 };
|
|
82
|
+
}
|
|
83
|
+
// The copies on each side cover the whole view (also its partially visible slides)
|
|
84
|
+
// and the part of the view in front of the centered slide
|
|
85
|
+
const count = Math.ceil(slidesPerView) + Math.ceil(centerShift);
|
|
86
|
+
if (childrenSlides.length < Math.max(2, count)) {
|
|
87
|
+
console.error(`For infinite loop with slidesPerView=${slidesPerView}${centered ? ' and centered' : ''}, need at least ${Math.max(2, count)} slides`);
|
|
88
|
+
return { slides: childrenSlides, clones: 0 };
|
|
89
|
+
}
|
|
90
|
+
const headClones = childrenSlides.slice(-count).map((slide, i) => React.cloneElement(slide, {
|
|
91
|
+
key: `clone-head-${i}`,
|
|
92
|
+
'data-clone': 'head',
|
|
93
|
+
}));
|
|
94
|
+
const tailClones = childrenSlides.slice(0, count).map((slide, i) => React.cloneElement(slide, {
|
|
95
|
+
key: `clone-tail-${i}`,
|
|
96
|
+
'data-clone': 'tail',
|
|
97
|
+
}));
|
|
98
|
+
return { slides: [...headClones, ...childrenSlides, ...tailClones], clones: count };
|
|
99
|
+
}, [children, infinite, isTicker, isFade, slidesPerView, centerShift, centered]);
|
|
100
|
+
// The infinite loop by the copies of the slides (slide effect and ticker)
|
|
101
|
+
const isLoop = clones > 0;
|
|
102
|
+
// The fade effect loops by the index alone
|
|
103
|
+
const isFadeLoop = isFade && infinite && slides.length > 1;
|
|
90
104
|
const total = slides.length;
|
|
91
|
-
const realSlidesCount =
|
|
92
|
-
|
|
105
|
+
const realSlidesCount = total - 2 * clones;
|
|
106
|
+
// Without the loop the last position shows the last slide at the end of the view
|
|
107
|
+
// (in the middle of the view when centered). With a fractional slidesPerView it is not a whole
|
|
108
|
+
// slide: the index goes up to the next whole number and the track stops at the last position
|
|
109
|
+
const lastPosition = isFade || centerShift > 0 ? Math.max(0, total - 1) : Math.max(0, total - slidesPerView);
|
|
110
|
+
const maxIndex = Math.ceil(lastPosition - 0.001);
|
|
111
|
+
// The index while the track is between the slides (drag, free mode) without the loop
|
|
112
|
+
const maxFreeIndex = Math.floor(lastPosition + 0.001);
|
|
93
113
|
// #region States
|
|
94
114
|
const [index, setIndex] = React.useState(() => {
|
|
95
|
-
if (!
|
|
115
|
+
if (!isLoop) {
|
|
96
116
|
return Math.max(0, Math.min(initialIndex, maxIndex));
|
|
97
117
|
}
|
|
98
|
-
return initialIndex +
|
|
118
|
+
return initialIndex + clones;
|
|
99
119
|
});
|
|
100
120
|
const [offset, setOffset] = React.useState(0);
|
|
101
121
|
// The long decelerating transition after a free swipe (snap=false)
|
|
@@ -109,8 +129,8 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
109
129
|
const isPaused = isPausedByApi || isHovered || isFocused;
|
|
110
130
|
// #region Refs
|
|
111
131
|
const dragging = React.useRef(false);
|
|
112
|
-
const
|
|
113
|
-
const
|
|
132
|
+
const startCoord = React.useRef(0);
|
|
133
|
+
const lastCoord = React.useRef(0);
|
|
114
134
|
const lastTime = React.useRef(0);
|
|
115
135
|
const velocity = React.useRef(0);
|
|
116
136
|
const wrapperRef = React.useRef(null);
|
|
@@ -118,12 +138,13 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
118
138
|
const isAnimating = React.useRef(false);
|
|
119
139
|
const mounted = React.useRef(true);
|
|
120
140
|
const suppressClick = React.useRef(false);
|
|
141
|
+
const getCoord = React.useCallback((e) => (isVertical ? e.clientY : e.clientX), [isVertical]);
|
|
121
142
|
// #region Real Index
|
|
122
143
|
const realIndex = React.useMemo(() => {
|
|
123
|
-
if (!
|
|
144
|
+
if (!isLoop) {
|
|
124
145
|
return index;
|
|
125
146
|
}
|
|
126
|
-
const realIndex = index -
|
|
147
|
+
const realIndex = index - clones;
|
|
127
148
|
if (realIndex < 0) {
|
|
128
149
|
return realSlidesCount + realIndex;
|
|
129
150
|
}
|
|
@@ -132,7 +153,7 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
132
153
|
}
|
|
133
154
|
// In the infinite mode any slide can be the first visible one, so there is no clamp
|
|
134
155
|
return realIndex;
|
|
135
|
-
}, [index,
|
|
156
|
+
}, [index, isLoop, clones, realSlidesCount]);
|
|
136
157
|
const prevRealIndex = React.useRef(realIndex);
|
|
137
158
|
// #region Effects
|
|
138
159
|
React.useEffect(() => {
|
|
@@ -145,18 +166,18 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
145
166
|
if (!mounted.current)
|
|
146
167
|
return;
|
|
147
168
|
// The slides were removed: keep the index inside the allowed range
|
|
148
|
-
const lastIndex =
|
|
169
|
+
const lastIndex = isLoop ? total - clones : maxIndex;
|
|
149
170
|
if (index > lastIndex) {
|
|
150
|
-
setIndex(
|
|
171
|
+
setIndex(isLoop ? clones : maxIndex);
|
|
151
172
|
setOffset(0);
|
|
152
173
|
}
|
|
153
|
-
}, [index,
|
|
174
|
+
}, [index, isLoop, total, clones, maxIndex]);
|
|
154
175
|
// #region Normalization
|
|
155
176
|
const normalizeIndex = React.useCallback(() => {
|
|
156
|
-
if (!
|
|
177
|
+
if (!isLoop || !mounted.current)
|
|
157
178
|
return;
|
|
158
|
-
const firstRealIndex =
|
|
159
|
-
const lastRealIndex = total -
|
|
179
|
+
const firstRealIndex = clones;
|
|
180
|
+
const lastRealIndex = total - clones - 1;
|
|
160
181
|
if (index < firstRealIndex || index > lastRealIndex) {
|
|
161
182
|
// A clone position shows the same slides as the real position shifted by the slides count.
|
|
162
183
|
// Not always the first/last real slide: after several fast clicks the index may be deeper in the clones
|
|
@@ -176,7 +197,7 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
176
197
|
});
|
|
177
198
|
});
|
|
178
199
|
}
|
|
179
|
-
}, [index,
|
|
200
|
+
}, [index, isLoop, clones, total, realSlidesCount]);
|
|
180
201
|
// #region Navigation API
|
|
181
202
|
const indexRef = React.useRef(index);
|
|
182
203
|
React.useEffect(() => {
|
|
@@ -185,13 +206,13 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
185
206
|
const goToIndex = React.useCallback((i) => {
|
|
186
207
|
// In the infinite mode the index is normalized after the transition, so wait for it.
|
|
187
208
|
// In the regular mode the transition is simply retargeted
|
|
188
|
-
if (
|
|
209
|
+
if (isLoop && isAnimating.current)
|
|
189
210
|
return;
|
|
190
211
|
let targetIndex = i;
|
|
191
|
-
if (
|
|
192
|
-
targetIndex = i +
|
|
193
|
-
const minIndex =
|
|
194
|
-
const maxIndex =
|
|
212
|
+
if (isLoop) {
|
|
213
|
+
targetIndex = i + clones;
|
|
214
|
+
const minIndex = clones;
|
|
215
|
+
const maxIndex = clones + realSlidesCount - 1;
|
|
195
216
|
targetIndex = Math.max(minIndex, Math.min(maxIndex, targetIndex));
|
|
196
217
|
}
|
|
197
218
|
else {
|
|
@@ -205,17 +226,20 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
205
226
|
setOffset(0);
|
|
206
227
|
setIndex(targetIndex);
|
|
207
228
|
isAnimating.current = true;
|
|
208
|
-
}, [
|
|
229
|
+
}, [isLoop, clones, realSlidesCount, maxIndex]);
|
|
209
230
|
const next = React.useCallback(() => {
|
|
210
231
|
setDisableAnimation(false);
|
|
211
232
|
setIsMomentum(false);
|
|
212
233
|
setOffset(0);
|
|
213
234
|
setIndex(prev => {
|
|
214
235
|
let nextIndex;
|
|
215
|
-
if (
|
|
236
|
+
if (isLoop) {
|
|
216
237
|
// The last position is the first tail clone: it mirrors the first real slide.
|
|
217
238
|
// Further positions would show the empty space when slidesPerView > 1
|
|
218
|
-
nextIndex = Math.min(prev + 1, total -
|
|
239
|
+
nextIndex = Math.min(prev + 1, total - clones);
|
|
240
|
+
}
|
|
241
|
+
else if (isFadeLoop) {
|
|
242
|
+
nextIndex = (prev + 1) % total;
|
|
219
243
|
}
|
|
220
244
|
else {
|
|
221
245
|
nextIndex = Math.min(prev + 1, maxIndex);
|
|
@@ -227,50 +251,61 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
227
251
|
isAnimating.current = true;
|
|
228
252
|
return nextIndex;
|
|
229
253
|
});
|
|
230
|
-
}, [
|
|
254
|
+
}, [isLoop, isFadeLoop, maxIndex, total, clones]);
|
|
231
255
|
const prev = React.useCallback(() => {
|
|
232
256
|
setDisableAnimation(false);
|
|
233
257
|
setIsMomentum(false);
|
|
234
258
|
setOffset(0);
|
|
235
259
|
setIndex(prev => {
|
|
236
|
-
const nextIndex = Math.max(prev - 1, 0);
|
|
260
|
+
const nextIndex = isFadeLoop ? (prev - 1 + total) % total : Math.max(prev - 1, 0);
|
|
237
261
|
if (nextIndex === prev) {
|
|
238
262
|
return prev;
|
|
239
263
|
}
|
|
240
264
|
isAnimating.current = true;
|
|
241
265
|
return nextIndex;
|
|
242
266
|
});
|
|
243
|
-
}, []);
|
|
267
|
+
}, [isFadeLoop, total]);
|
|
244
268
|
// #region Position
|
|
245
|
-
// The position is the distance in pixels from the start of the track to the
|
|
246
|
-
// The state keeps it as index (whole slides) + offset (pixels)
|
|
247
|
-
const
|
|
269
|
+
// The position is the distance in pixels from the start of the track to the start of the view
|
|
270
|
+
// without the centering shift. The state keeps it as index (whole slides) + offset (pixels)
|
|
271
|
+
const getSlideSize = React.useCallback(() => {
|
|
272
|
+
const wrapper = wrapperRef.current;
|
|
273
|
+
const size = (isVertical ? wrapper === null || wrapper === void 0 ? void 0 : wrapper.clientHeight : wrapper === null || wrapper === void 0 ? void 0 : wrapper.clientWidth) || 0;
|
|
274
|
+
return size / slidesPerView;
|
|
275
|
+
}, [isVertical, slidesPerView]);
|
|
248
276
|
// The last allowed position. In the infinite mode it is the first tail clone
|
|
249
|
-
const maxPosition = React.useCallback(() => (
|
|
277
|
+
const maxPosition = React.useCallback(() => (isLoop ? total - clones : lastPosition) * getSlideSize(), [isLoop, total, clones, lastPosition, getSlideSize]);
|
|
278
|
+
// The index of the track: without the loop the track stops at the last position
|
|
279
|
+
// even if the index is the next whole number
|
|
280
|
+
const trackIndex = (isLoop ? index : Math.min(index, lastPosition)) - centerShift;
|
|
250
281
|
// The position on the screen right now, also in the middle of a transition
|
|
251
282
|
const readPosition = React.useCallback(() => {
|
|
252
283
|
const track = trackRef.current;
|
|
284
|
+
const size = getSlideSize();
|
|
253
285
|
if (track && typeof DOMMatrixReadOnly !== 'undefined') {
|
|
254
286
|
const { transform } = getComputedStyle(track);
|
|
255
287
|
if (transform && transform !== 'none') {
|
|
256
|
-
|
|
288
|
+
const matrix = new DOMMatrixReadOnly(transform);
|
|
289
|
+
return -(isVertical ? matrix.m42 : matrix.m41) + centerShift * size;
|
|
257
290
|
}
|
|
258
291
|
}
|
|
259
|
-
return
|
|
260
|
-
}, [
|
|
292
|
+
return (trackIndex + centerShift) * size - offset;
|
|
293
|
+
}, [trackIndex, centerShift, offset, getSlideSize, isVertical]);
|
|
261
294
|
// Infinite mode: the same view is shown by the real slides and by the clones,
|
|
262
295
|
// so the position is moved by the whole slides count when it comes close to the edge of the track
|
|
263
296
|
const wrapPosition = React.useCallback((position) => {
|
|
264
|
-
const
|
|
265
|
-
const span = realSlidesCount *
|
|
266
|
-
if (!
|
|
297
|
+
const size = getSlideSize();
|
|
298
|
+
const span = realSlidesCount * size;
|
|
299
|
+
if (!isLoop || size === 0)
|
|
267
300
|
return 0;
|
|
268
|
-
if (position <
|
|
301
|
+
if (position < size * 0.5)
|
|
269
302
|
return span;
|
|
270
|
-
if (position > maxPosition() -
|
|
303
|
+
if (position > maxPosition() - size * 0.5)
|
|
271
304
|
return -span;
|
|
272
305
|
return 0;
|
|
273
|
-
}, [
|
|
306
|
+
}, [isLoop, realSlidesCount, getSlideSize, maxPosition]);
|
|
307
|
+
// Without the loop the index between the slides does not go past the last position
|
|
308
|
+
const clampFreeIndex = React.useCallback((i) => (isLoop ? i : Math.max(0, Math.min(maxFreeIndex, i))), [isLoop, maxFreeIndex]);
|
|
274
309
|
const dragBasePosition = React.useRef(0);
|
|
275
310
|
const dragAnchorIndex = React.useRef(0);
|
|
276
311
|
const dragPosition = React.useRef(0);
|
|
@@ -281,49 +316,61 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
281
316
|
// Only the main mouse button, the touch or the pen
|
|
282
317
|
if (e.pointerType === 'mouse' && e.button !== 0)
|
|
283
318
|
return;
|
|
284
|
-
|
|
319
|
+
suppressClick.current = false;
|
|
320
|
+
dragging.current = true;
|
|
321
|
+
startCoord.current = getCoord(e);
|
|
322
|
+
lastCoord.current = getCoord(e);
|
|
323
|
+
lastTime.current = performance.now();
|
|
324
|
+
velocity.current = 0;
|
|
325
|
+
// The capture fails for a pointer that is already gone: the drag still works without it
|
|
326
|
+
try {
|
|
327
|
+
e.target.setPointerCapture(e.pointerId);
|
|
328
|
+
}
|
|
329
|
+
catch (_a) {
|
|
330
|
+
// nothing
|
|
331
|
+
}
|
|
332
|
+
// The fade does not move the track: the slide changes after the release
|
|
333
|
+
if (isFade)
|
|
334
|
+
return;
|
|
335
|
+
const size = getSlideSize();
|
|
285
336
|
// Grab the track where it is now: a running transition stops under the finger instead of jumping
|
|
286
337
|
let position = readPosition();
|
|
287
338
|
position += wrapPosition(position);
|
|
288
|
-
const anchor =
|
|
339
|
+
const anchor = clampFreeIndex(size > 0 ? Math.round(position / size) : index);
|
|
289
340
|
dragBasePosition.current = position;
|
|
290
341
|
dragPosition.current = position;
|
|
291
342
|
dragAnchorIndex.current = anchor;
|
|
292
|
-
suppressClick.current = false;
|
|
293
|
-
dragging.current = true;
|
|
294
343
|
isAnimating.current = false;
|
|
295
344
|
setDisableAnimation(true);
|
|
296
345
|
setIsMomentum(false);
|
|
297
346
|
setIsDragging(true);
|
|
298
347
|
setIndex(anchor);
|
|
299
|
-
setOffset(anchor *
|
|
300
|
-
|
|
301
|
-
lastX.current = e.clientX;
|
|
302
|
-
lastTime.current = performance.now();
|
|
303
|
-
velocity.current = 0;
|
|
304
|
-
e.target.setPointerCapture(e.pointerId);
|
|
305
|
-
}, [draggable, getSlideWidth, readPosition, wrapPosition, index]);
|
|
348
|
+
setOffset(anchor * size - position);
|
|
349
|
+
}, [draggable, isFade, getCoord, getSlideSize, readPosition, wrapPosition, clampFreeIndex, index]);
|
|
306
350
|
const onPointerMove = React.useCallback((e) => {
|
|
307
351
|
if (!dragging.current)
|
|
308
352
|
return;
|
|
353
|
+
const coord = getCoord(e);
|
|
309
354
|
const now = performance.now();
|
|
310
|
-
const
|
|
355
|
+
const delta = coord - lastCoord.current;
|
|
311
356
|
const dt = now - lastTime.current;
|
|
312
357
|
if (dt > 0) {
|
|
313
|
-
velocity.current =
|
|
358
|
+
velocity.current = delta / dt;
|
|
314
359
|
}
|
|
315
|
-
|
|
360
|
+
lastCoord.current = coord;
|
|
316
361
|
lastTime.current = now;
|
|
317
|
-
|
|
318
|
-
|
|
362
|
+
if (isFade)
|
|
363
|
+
return;
|
|
364
|
+
const size = getSlideSize();
|
|
365
|
+
let position = dragBasePosition.current - (coord - startCoord.current);
|
|
319
366
|
// Endless drag in the infinite mode: move the base together with the position
|
|
320
367
|
const shift = wrapPosition(position);
|
|
321
368
|
if (shift !== 0) {
|
|
322
369
|
dragBasePosition.current += shift;
|
|
323
|
-
dragAnchorIndex.current += Math.round(shift /
|
|
370
|
+
dragAnchorIndex.current += Math.round(shift / size);
|
|
324
371
|
position += shift;
|
|
325
372
|
}
|
|
326
|
-
if (!
|
|
373
|
+
if (!isLoop) {
|
|
327
374
|
const max = maxPosition();
|
|
328
375
|
if (position < 0) {
|
|
329
376
|
position = resistance ? position * 0.3 : 0;
|
|
@@ -335,27 +382,55 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
335
382
|
dragPosition.current = position;
|
|
336
383
|
// snap: the index stays the same during the drag, the slide changes after the release.
|
|
337
384
|
// free: the index follows the position, so onSlideChange reports the slide under the view
|
|
338
|
-
const nextIndex = snap
|
|
385
|
+
const nextIndex = snap
|
|
386
|
+
? dragAnchorIndex.current
|
|
387
|
+
: clampFreeIndex(Math.round(position / size));
|
|
339
388
|
setIndex(nextIndex);
|
|
340
|
-
setOffset(nextIndex *
|
|
341
|
-
}, [
|
|
389
|
+
setOffset(nextIndex * size - position);
|
|
390
|
+
}, [
|
|
391
|
+
getCoord,
|
|
392
|
+
isFade,
|
|
393
|
+
getSlideSize,
|
|
394
|
+
wrapPosition,
|
|
395
|
+
isLoop,
|
|
396
|
+
maxPosition,
|
|
397
|
+
resistance,
|
|
398
|
+
snap,
|
|
399
|
+
clampFreeIndex,
|
|
400
|
+
]);
|
|
342
401
|
const onPointerUp = React.useCallback((e) => {
|
|
343
402
|
if (!dragging.current)
|
|
344
403
|
return;
|
|
345
|
-
const
|
|
346
|
-
const position = dragPosition.current;
|
|
347
|
-
const delta = lastX.current - startX.current;
|
|
348
|
-
// The pointer stopped before the release: no inertia
|
|
349
|
-
const v = performance.now() - lastTime.current > 100 ? 0 : velocity.current;
|
|
350
|
-
const lastIndex = infinite ? total - slidesPerView : maxIndex;
|
|
404
|
+
const delta = lastCoord.current - startCoord.current;
|
|
351
405
|
// It was a drag, not a click: the click on a link or a button inside the slide must not fire
|
|
352
406
|
if (Math.abs(delta) > threshold) {
|
|
353
407
|
suppressClick.current = true;
|
|
354
408
|
}
|
|
409
|
+
dragging.current = false;
|
|
410
|
+
try {
|
|
411
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
412
|
+
}
|
|
413
|
+
catch (_a) {
|
|
414
|
+
// nothing
|
|
415
|
+
}
|
|
416
|
+
if (isFade) {
|
|
417
|
+
if (delta < -threshold) {
|
|
418
|
+
next();
|
|
419
|
+
}
|
|
420
|
+
else if (delta > threshold) {
|
|
421
|
+
prev();
|
|
422
|
+
}
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const size = getSlideSize();
|
|
426
|
+
const position = dragPosition.current;
|
|
427
|
+
// The pointer stopped before the release: no inertia
|
|
428
|
+
const v = performance.now() - lastTime.current > 100 ? 0 : velocity.current;
|
|
429
|
+
const lastIndex = isLoop ? total - clones : maxIndex;
|
|
355
430
|
setDisableAnimation(false);
|
|
356
431
|
if (snap) {
|
|
357
432
|
const anchor = dragAnchorIndex.current;
|
|
358
|
-
const displacement = position - anchor *
|
|
433
|
+
const displacement = position - anchor * size;
|
|
359
434
|
const velocityFactor = Math.min(Math.abs(v) * 250, 0.65);
|
|
360
435
|
const dynamicThreshold = Math.max(threshold, Math.min(dragThreshold * (1 - velocityFactor), dragThreshold));
|
|
361
436
|
let nextIndex = anchor;
|
|
@@ -363,37 +438,42 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
363
438
|
// At least one slide, more if the slide was dragged further
|
|
364
439
|
nextIndex =
|
|
365
440
|
displacement > 0
|
|
366
|
-
? Math.max(anchor + 1, Math.round(position /
|
|
367
|
-
: Math.min(anchor - 1, Math.round(position /
|
|
441
|
+
? Math.max(anchor + 1, Math.round(position / size))
|
|
442
|
+
: Math.min(anchor - 1, Math.round(position / size));
|
|
368
443
|
}
|
|
369
444
|
nextIndex = Math.max(0, Math.min(lastIndex, nextIndex));
|
|
370
445
|
setIndex(nextIndex);
|
|
371
446
|
setOffset(0);
|
|
372
447
|
// A click without movement does not start a transition, so transitionend never comes
|
|
373
448
|
// and the flag would block autoplay and goToIndex forever
|
|
374
|
-
|
|
449
|
+
const target = (isLoop ? nextIndex : Math.min(nextIndex, lastPosition)) * size;
|
|
450
|
+
isAnimating.current = Math.abs(target - position) > 0.5;
|
|
375
451
|
}
|
|
376
452
|
else {
|
|
377
453
|
// Free mode: the track keeps moving by inertia and stops where it stops
|
|
378
|
-
const
|
|
379
|
-
const
|
|
454
|
+
const maxProjected = (isLoop ? lastIndex : lastPosition) * size;
|
|
455
|
+
const projected = Math.max(0, Math.min(maxProjected, position - v * 300));
|
|
456
|
+
const nextIndex = clampFreeIndex(size > 0 ? Math.round(projected / size) : index);
|
|
380
457
|
setIsMomentum(true);
|
|
381
458
|
setIndex(nextIndex);
|
|
382
|
-
setOffset(nextIndex *
|
|
459
|
+
setOffset(nextIndex * size - projected);
|
|
383
460
|
isAnimating.current = Math.abs(projected - position) > 0.5;
|
|
384
461
|
}
|
|
385
|
-
dragging.current = false;
|
|
386
462
|
setIsDragging(false);
|
|
387
|
-
e.target.releasePointerCapture(e.pointerId);
|
|
388
463
|
}, [
|
|
389
|
-
|
|
390
|
-
|
|
464
|
+
isFade,
|
|
465
|
+
next,
|
|
466
|
+
prev,
|
|
467
|
+
getSlideSize,
|
|
468
|
+
isLoop,
|
|
391
469
|
total,
|
|
392
|
-
|
|
470
|
+
clones,
|
|
393
471
|
maxIndex,
|
|
472
|
+
lastPosition,
|
|
394
473
|
threshold,
|
|
395
474
|
snap,
|
|
396
475
|
dragThreshold,
|
|
476
|
+
clampFreeIndex,
|
|
397
477
|
index,
|
|
398
478
|
]);
|
|
399
479
|
const onLostPointerCapture = React.useCallback((e) => {
|
|
@@ -405,7 +485,7 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
405
485
|
const autoplayStep = React.useRef(() => undefined);
|
|
406
486
|
autoplayStep.current = () => {
|
|
407
487
|
// Without the infinite loop the autoplay returns to the first slide instead of stopping at the end
|
|
408
|
-
if (!
|
|
488
|
+
if (!isLoop && !isFadeLoop && indexRef.current >= maxIndex) {
|
|
409
489
|
goToIndex(0);
|
|
410
490
|
}
|
|
411
491
|
else {
|
|
@@ -415,7 +495,7 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
415
495
|
const pause = React.useCallback(() => setIsPausedByApi(true), []);
|
|
416
496
|
const resume = React.useCallback(() => setIsPausedByApi(false), []);
|
|
417
497
|
React.useEffect(() => {
|
|
418
|
-
if (!autoplay || isPaused)
|
|
498
|
+
if (!autoplay || isTicker || isPaused)
|
|
419
499
|
return undefined;
|
|
420
500
|
const timer = setInterval(() => {
|
|
421
501
|
if (!dragging.current && !isAnimating.current && mounted.current) {
|
|
@@ -423,13 +503,57 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
423
503
|
}
|
|
424
504
|
}, autoplayInterval);
|
|
425
505
|
return () => clearInterval(timer);
|
|
426
|
-
}, [autoplay, autoplayInterval, isPaused]);
|
|
506
|
+
}, [autoplay, isTicker, autoplayInterval, isPaused]);
|
|
507
|
+
// #region Ticker
|
|
508
|
+
// The continuous scroll writes the transform directly: 60 renders per second are not needed for it.
|
|
509
|
+
// The position is kept in slides, so it stays in place when the size of the swiper changes
|
|
510
|
+
const tickerPosition = React.useRef(null);
|
|
511
|
+
React.useEffect(() => {
|
|
512
|
+
const track = trackRef.current;
|
|
513
|
+
if (!isTicker || !isLoop || !track)
|
|
514
|
+
return undefined;
|
|
515
|
+
if (isPaused || prefersReducedMotion())
|
|
516
|
+
return undefined;
|
|
517
|
+
let frame = 0;
|
|
518
|
+
let last = performance.now();
|
|
519
|
+
const step = (now) => {
|
|
520
|
+
var _a;
|
|
521
|
+
const size = getSlideSize();
|
|
522
|
+
if (size > 0) {
|
|
523
|
+
let position = (_a = tickerPosition.current) !== null && _a !== void 0 ? _a : clones;
|
|
524
|
+
position += ((autoScroll || 0) * (now - last)) / 1000 / size;
|
|
525
|
+
if (position >= clones + realSlidesCount) {
|
|
526
|
+
position -= realSlidesCount;
|
|
527
|
+
}
|
|
528
|
+
tickerPosition.current = position;
|
|
529
|
+
const translate = -(position - centerShift) * size;
|
|
530
|
+
track.style.transition = 'none';
|
|
531
|
+
track.style.transform = isVertical
|
|
532
|
+
? `translateY(${translate}px)`
|
|
533
|
+
: `translateX(${translate}px)`;
|
|
534
|
+
}
|
|
535
|
+
last = now;
|
|
536
|
+
frame = requestAnimationFrame(step);
|
|
537
|
+
};
|
|
538
|
+
frame = requestAnimationFrame(step);
|
|
539
|
+
return () => cancelAnimationFrame(frame);
|
|
540
|
+
}, [
|
|
541
|
+
isTicker,
|
|
542
|
+
isLoop,
|
|
543
|
+
isPaused,
|
|
544
|
+
autoScroll,
|
|
545
|
+
getSlideSize,
|
|
546
|
+
realSlidesCount,
|
|
547
|
+
clones,
|
|
548
|
+
centerShift,
|
|
549
|
+
isVertical,
|
|
550
|
+
]);
|
|
427
551
|
// #region Hover and focus
|
|
428
552
|
// The autoplay pauses while the pointer is over the swiper (pauseOnHover)
|
|
429
553
|
// and always while the focus is inside it, so the keyboard user is not interrupted
|
|
430
554
|
React.useEffect(() => {
|
|
431
555
|
const wrapper = wrapperRef.current;
|
|
432
|
-
if (!autoplay || !wrapper)
|
|
556
|
+
if (!(autoplay || isTicker) || !wrapper)
|
|
433
557
|
return undefined;
|
|
434
558
|
const handleMouseEnter = () => pauseOnHover && setIsHovered(true);
|
|
435
559
|
const handleMouseLeave = () => setIsHovered(false);
|
|
@@ -449,7 +573,7 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
449
573
|
wrapper.removeEventListener('focusin', handleFocusIn);
|
|
450
574
|
wrapper.removeEventListener('focusout', handleFocusOut);
|
|
451
575
|
};
|
|
452
|
-
}, [autoplay, pauseOnHover]);
|
|
576
|
+
}, [autoplay, isTicker, pauseOnHover]);
|
|
453
577
|
// #region Keyboard
|
|
454
578
|
const onKeyDown = React.useCallback((event) => {
|
|
455
579
|
if (!keyboardControl)
|
|
@@ -458,15 +582,15 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
458
582
|
const target = event.target;
|
|
459
583
|
if (target.closest('input, textarea, select, [contenteditable="true"]'))
|
|
460
584
|
return;
|
|
461
|
-
if (event.key === 'ArrowLeft') {
|
|
585
|
+
if (event.key === (isVertical ? 'ArrowUp' : 'ArrowLeft')) {
|
|
462
586
|
event.preventDefault();
|
|
463
587
|
prev();
|
|
464
588
|
}
|
|
465
|
-
if (event.key === 'ArrowRight') {
|
|
589
|
+
if (event.key === (isVertical ? 'ArrowDown' : 'ArrowRight')) {
|
|
466
590
|
event.preventDefault();
|
|
467
591
|
next();
|
|
468
592
|
}
|
|
469
|
-
}, [keyboardControl, prev, next]);
|
|
593
|
+
}, [keyboardControl, isVertical, prev, next]);
|
|
470
594
|
const onClickCapture = React.useCallback((event) => {
|
|
471
595
|
if (suppressClick.current) {
|
|
472
596
|
suppressClick.current = false;
|
|
@@ -485,14 +609,16 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
485
609
|
const track = trackRef.current;
|
|
486
610
|
if (!track)
|
|
487
611
|
return;
|
|
612
|
+
// The fade changes the opacity of the slides, the event bubbles up to the track
|
|
613
|
+
const property = isFade ? 'opacity' : 'transform';
|
|
488
614
|
const handleTransitionEnd = (e) => {
|
|
489
|
-
if (e.propertyName !==
|
|
615
|
+
if (e.propertyName !== property)
|
|
490
616
|
return;
|
|
491
617
|
isAnimating.current = false;
|
|
492
|
-
normalizeIndex();
|
|
618
|
+
normalizeIndex();
|
|
493
619
|
};
|
|
494
620
|
const handleTransitionCancel = (e) => {
|
|
495
|
-
if (e.propertyName !==
|
|
621
|
+
if (e.propertyName !== property)
|
|
496
622
|
return;
|
|
497
623
|
isAnimating.current = false;
|
|
498
624
|
};
|
|
@@ -502,7 +628,7 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
502
628
|
track.removeEventListener('transitionend', handleTransitionEnd);
|
|
503
629
|
track.removeEventListener('transitioncancel', handleTransitionCancel);
|
|
504
630
|
};
|
|
505
|
-
}, [normalizeIndex]);
|
|
631
|
+
}, [normalizeIndex, isFade]);
|
|
506
632
|
// #region Mount
|
|
507
633
|
React.useEffect(() => {
|
|
508
634
|
mounted.current = true;
|
|
@@ -527,34 +653,41 @@ exports.Swiper = React.forwardRef((props, ref) => {
|
|
|
527
653
|
Wrapper: (overrides === null || overrides === void 0 ? void 0 : overrides.Wrapper) || SwiperWrapper_1.default,
|
|
528
654
|
}), [overrides]);
|
|
529
655
|
const getSlideRealIndex = React.useCallback((domIndex) => {
|
|
530
|
-
if (!
|
|
656
|
+
if (!isLoop) {
|
|
531
657
|
return domIndex;
|
|
532
658
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
return totalSlides - slidesPerView + domIndex;
|
|
659
|
+
if (domIndex < clones) {
|
|
660
|
+
return realSlidesCount - clones + domIndex;
|
|
536
661
|
}
|
|
537
|
-
if (domIndex >= total -
|
|
538
|
-
return domIndex - (total -
|
|
662
|
+
if (domIndex >= total - clones) {
|
|
663
|
+
return domIndex - (total - clones);
|
|
539
664
|
}
|
|
540
|
-
return domIndex -
|
|
541
|
-
}, [
|
|
665
|
+
return domIndex - clones;
|
|
666
|
+
}, [isLoop, realSlidesCount, clones, total]);
|
|
542
667
|
// The slides at least partially in the view: they are not inert, so a partially visible slide
|
|
543
|
-
// can be clicked. Between the slides (drag, free mode)
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
668
|
+
// can be clicked. Between the slides (drag, free mode), with a fractional slidesPerView
|
|
669
|
+
// and with the centering one more slide is partially visible
|
|
670
|
+
const wrapper = wrapperRef.current;
|
|
671
|
+
const slideSize = wrapper
|
|
672
|
+
? (isVertical ? wrapper.clientHeight : wrapper.clientWidth) / slidesPerView
|
|
673
|
+
: 0;
|
|
674
|
+
const viewPosition = slideSize > 0 ? trackIndex - offset / slideSize : trackIndex;
|
|
675
|
+
let firstVisibleDomIndex = Math.floor(viewPosition + 0.001);
|
|
676
|
+
let visibleCount = Math.ceil(viewPosition + slidesPerView - 0.001) - firstVisibleDomIndex;
|
|
677
|
+
if (isFade) {
|
|
678
|
+
firstVisibleDomIndex = index;
|
|
679
|
+
visibleCount = 1;
|
|
552
680
|
}
|
|
553
681
|
firstVisibleDomIndex = Math.max(0, Math.min(total - 1, firstVisibleDomIndex));
|
|
682
|
+
// The ticker moves without the state: all the slides are rendered
|
|
683
|
+
if (isTicker) {
|
|
684
|
+
firstVisibleDomIndex = clones;
|
|
685
|
+
visibleCount = realSlidesCount;
|
|
686
|
+
}
|
|
554
687
|
return (React.createElement(overridesMap.Container, Object.assign({ role: "region", "aria-roledescription": "carousel" }, restProps),
|
|
555
|
-
React.createElement(overridesMap.Wrapper, { ref: wrapperRef, draggable: draggable, slidesPerView: slidesPerView, tabIndex: keyboardControl ? 0 : undefined, "aria-live": autoplay && !isPaused ? 'off' : 'polite', onKeyDown: onKeyDown, onClickCapture: onClickCapture, onDragStart: onDragStart, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onLostPointerCapture: onLostPointerCapture },
|
|
556
|
-
React.createElement(overridesMap.Track, { ref: trackRef, dragging: isDragging, index:
|
|
557
|
-
React.createElement(SwiperSlidesRenderer_1.default, { slides: slides, slidesPerView: slidesPerView, realIndex: realIndex, firstVisibleIndex: getSlideRealIndex(firstVisibleDomIndex), visibleCount: visibleCount, realSlidesCount: realSlidesCount, getSlideRealIndex: getSlideRealIndex, slideLabel: slideLabel })))));
|
|
688
|
+
React.createElement(overridesMap.Wrapper, { ref: wrapperRef, draggable: draggable, slidesPerView: slidesPerView, vertical: isVertical, tabIndex: keyboardControl ? 0 : undefined, "aria-live": (autoplay || isTicker) && !isPaused ? 'off' : 'polite', onKeyDown: onKeyDown, onClickCapture: onClickCapture, onDragStart: onDragStart, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onLostPointerCapture: onLostPointerCapture },
|
|
689
|
+
React.createElement(overridesMap.Track, { ref: trackRef, dragging: isDragging, index: isFade ? 0 : trackIndex, offset: isFade ? 0 : offset, disableAnimation: disableAnimation, momentum: isMomentum, slidesPerView: slidesPerView, vertical: isVertical, effect: effect, speed: speed },
|
|
690
|
+
React.createElement(SwiperSlidesRenderer_1.default, { slides: slides, slidesPerView: slidesPerView, realIndex: realIndex, firstVisibleIndex: getSlideRealIndex(firstVisibleDomIndex), visibleCount: visibleCount, realSlidesCount: realSlidesCount, getSlideRealIndex: getSlideRealIndex, slideLabel: slideLabel, activeDomIndex: isFade ? index : null })))));
|
|
558
691
|
});
|
|
559
692
|
exports.Swiper.displayName = 'Swiper';
|
|
560
693
|
exports.default = exports.Swiper;
|