react-image-gallery 1.3.0 → 1.4.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.
@@ -1,1727 +0,0 @@
1
- import clsx from "clsx";
2
- import React from "react";
3
- import throttle from "lodash-es/throttle";
4
- import debounce from "lodash-es/debounce";
5
- import isEqual from "react-fast-compare";
6
- import ResizeObserver from "resize-observer-polyfill";
7
- import { LEFT, RIGHT, UP, DOWN } from "react-swipeable";
8
- import { arrayOf, bool, func, number, oneOf, shape, string } from "prop-types";
9
- import Item from "src/components/Item";
10
- import Fullscreen from "src/components/controls/Fullscreen";
11
- import LeftNav from "src/components/controls/LeftNav";
12
- import RightNav from "src/components/controls/RightNav";
13
- import PlayPause from "src/components/controls/PlayPause";
14
- import SwipeWrapper from "src/components/SwipeWrapper";
15
-
16
- const screenChangeEvents = [
17
- "fullscreenchange",
18
- "MSFullscreenChange",
19
- "mozfullscreenchange",
20
- "webkitfullscreenchange",
21
- ];
22
-
23
- const imageSetType = arrayOf(
24
- shape({
25
- srcSet: string,
26
- media: string,
27
- })
28
- );
29
-
30
- function isEnterOrSpaceKey(event) {
31
- const key = parseInt(event.keyCode || event.which || 0, 10);
32
- const ENTER_KEY_CODE = 66;
33
- const SPACEBAR_KEY_CODE = 62;
34
- return key === ENTER_KEY_CODE || key === SPACEBAR_KEY_CODE;
35
- }
36
-
37
- class ImageGallery extends React.Component {
38
- constructor(props) {
39
- super(props);
40
- this.state = {
41
- currentIndex: props.startIndex,
42
- thumbsTranslate: 0,
43
- thumbsSwipedTranslate: 0,
44
- currentSlideOffset: 0,
45
- galleryWidth: 0,
46
- thumbnailsWrapperWidth: 0,
47
- thumbnailsWrapperHeight: 0,
48
- thumbsStyle: { transition: `all ${props.slideDuration}ms ease-out` },
49
- isFullscreen: false,
50
- isSwipingThumbnail: false,
51
- isPlaying: false,
52
- };
53
- this.loadedImages = {};
54
- this.imageGallery = React.createRef();
55
- this.thumbnailsWrapper = React.createRef();
56
- this.thumbnails = React.createRef();
57
- this.imageGallerySlideWrapper = React.createRef();
58
-
59
- // bindings
60
- this.handleImageLoaded = this.handleImageLoaded.bind(this);
61
- this.handleKeyDown = this.handleKeyDown.bind(this);
62
- this.handleMouseDown = this.handleMouseDown.bind(this);
63
- this.handleResize = this.handleResize.bind(this);
64
- this.handleTouchMove = this.handleTouchMove.bind(this);
65
- this.handleOnSwiped = this.handleOnSwiped.bind(this);
66
- this.handleScreenChange = this.handleScreenChange.bind(this);
67
- this.handleSwiping = this.handleSwiping.bind(this);
68
- this.handleThumbnailSwiping = this.handleThumbnailSwiping.bind(this);
69
- this.handleOnThumbnailSwiped = this.handleOnThumbnailSwiped.bind(this);
70
- this.onThumbnailMouseLeave = this.onThumbnailMouseLeave.bind(this);
71
- this.handleImageError = this.handleImageError.bind(this);
72
- this.pauseOrPlay = this.pauseOrPlay.bind(this);
73
- this.renderThumbInner = this.renderThumbInner.bind(this);
74
- this.renderItem = this.renderItem.bind(this);
75
- this.slideLeft = this.slideLeft.bind(this);
76
- this.slideRight = this.slideRight.bind(this);
77
- this.toggleFullScreen = this.toggleFullScreen.bind(this);
78
- this.togglePlay = this.togglePlay.bind(this);
79
-
80
- // Used to update the throttle if slideDuration changes
81
- this.unthrottledSlideToIndex = this.slideToIndex;
82
- this.slideToIndex = throttle(
83
- this.unthrottledSlideToIndex,
84
- props.slideDuration,
85
- { trailing: false }
86
- );
87
-
88
- if (props.lazyLoad) {
89
- this.lazyLoaded = [];
90
- }
91
- }
92
-
93
- componentDidMount() {
94
- const { autoPlay, useWindowKeyDown } = this.props;
95
- if (autoPlay) {
96
- this.play();
97
- }
98
- if (useWindowKeyDown) {
99
- window.addEventListener("keydown", this.handleKeyDown);
100
- } else {
101
- this.imageGallery.current.addEventListener("keydown", this.handleKeyDown);
102
- }
103
- window.addEventListener("mousedown", this.handleMouseDown);
104
- window.addEventListener("touchmove", this.handleTouchMove, {
105
- passive: false,
106
- });
107
- // we're using resize observer to help with detecting containers size changes as images load
108
- this.initSlideWrapperResizeObserver(this.imageGallerySlideWrapper);
109
- this.initThumbnailWrapperResizeObserver(this.thumbnailsWrapper);
110
- this.addScreenChangeEvent();
111
- }
112
-
113
- componentDidUpdate(prevProps, prevState) {
114
- const {
115
- items,
116
- lazyLoad,
117
- slideDuration,
118
- slideInterval,
119
- startIndex,
120
- thumbnailPosition,
121
- showThumbnails,
122
- useWindowKeyDown,
123
- } = this.props;
124
- const { currentIndex, isPlaying } = this.state;
125
- const itemsSizeChanged = prevProps.items.length !== items.length;
126
- const itemsChanged = !isEqual(prevProps.items, items);
127
- const startIndexUpdated = prevProps.startIndex !== startIndex;
128
- const thumbnailsPositionChanged =
129
- prevProps.thumbnailPosition !== thumbnailPosition;
130
- const showThumbnailsChanged = prevProps.showThumbnails !== showThumbnails;
131
-
132
- if (
133
- slideInterval !== prevProps.slideInterval ||
134
- slideDuration !== prevProps.slideDuration
135
- ) {
136
- // refresh setInterval
137
- if (isPlaying) {
138
- this.pause();
139
- this.play();
140
- }
141
- }
142
-
143
- if (thumbnailsPositionChanged) {
144
- // re-initialize resizeObserver because element was unmounted and mounted again
145
- this.removeResizeObserver();
146
- this.initSlideWrapperResizeObserver(this.imageGallerySlideWrapper);
147
- this.initThumbnailWrapperResizeObserver(this.thumbnailsWrapper);
148
- }
149
-
150
- // re-inititalize if thumbnails are shown again
151
- if (showThumbnailsChanged && showThumbnails) {
152
- this.initThumbnailWrapperResizeObserver(this.thumbnailsWrapper);
153
- }
154
-
155
- // remove thumbnails resize observer if not showing thumbnails
156
- if (showThumbnailsChanged && !showThumbnails) {
157
- this.removeThumbnailsResizeObserver();
158
- }
159
-
160
- if (itemsSizeChanged || showThumbnailsChanged) {
161
- this.handleResize();
162
- }
163
-
164
- if (prevState.currentIndex !== currentIndex) {
165
- this.slideThumbnailBar();
166
- }
167
- // if slideDuration changes, update slideToIndex throttle
168
- if (prevProps.slideDuration !== slideDuration) {
169
- this.slideToIndex = throttle(
170
- this.unthrottledSlideToIndex,
171
- slideDuration,
172
- { trailing: false }
173
- );
174
- }
175
- if (lazyLoad && (!prevProps.lazyLoad || itemsChanged)) {
176
- this.lazyLoaded = [];
177
- }
178
-
179
- if (useWindowKeyDown !== prevProps.useWindowKeyDown) {
180
- if (useWindowKeyDown) {
181
- this.imageGallery.current.removeEventListener(
182
- "keydown",
183
- this.handleKeyDown
184
- );
185
- window.addEventListener("keydown", this.handleKeyDown);
186
- } else {
187
- window.removeEventListener("keydown", this.handleKeyDown);
188
- this.imageGallery.current.addEventListener(
189
- "keydown",
190
- this.handleKeyDown
191
- );
192
- }
193
- }
194
-
195
- if (startIndexUpdated || itemsChanged) {
196
- // reset to start index if new items are added
197
- // do not transition when new items are added
198
- this.setState({
199
- currentIndex: startIndex,
200
- slideStyle: { transition: "none" },
201
- });
202
- }
203
- }
204
-
205
- componentWillUnmount() {
206
- const { useWindowKeyDown } = this.props;
207
- window.removeEventListener("mousedown", this.handleMouseDown);
208
- window.removeEventListener("touchmove", this.handleTouchMove);
209
- this.removeScreenChangeEvent();
210
- this.removeResizeObserver();
211
- if (this.playPauseIntervalId) {
212
- window.clearInterval(this.playPauseIntervalId);
213
- this.playPauseIntervalId = null;
214
- }
215
- if (this.transitionTimer) {
216
- window.clearTimeout(this.transitionTimer);
217
- }
218
- if (useWindowKeyDown) {
219
- window.removeEventListener("keydown", this.handleKeyDown);
220
- } else {
221
- this.imageGallery.current.removeEventListener(
222
- "keydown",
223
- this.handleKeyDown
224
- );
225
- }
226
- }
227
-
228
- onSliding() {
229
- const { currentIndex, isTransitioning } = this.state;
230
- const { onSlide, slideDuration } = this.props;
231
- this.transitionTimer = window.setTimeout(() => {
232
- if (isTransitioning) {
233
- this.setState({
234
- isTransitioning: !isTransitioning,
235
- // reset swiping thumbnail after transitioning to new slide,
236
- // so we can resume thumbnail auto translate
237
- isSwipingThumbnail: false,
238
- });
239
- if (onSlide) {
240
- onSlide(currentIndex);
241
- }
242
- }
243
- }, slideDuration + 50);
244
- }
245
-
246
- onThumbnailClick(event, index) {
247
- const { onThumbnailClick, items } = this.props;
248
- const { currentIndex } = this.state;
249
- // blur element to remove outline cause by focus
250
- event.target.parentNode.parentNode.blur();
251
- if (currentIndex !== index) {
252
- if (items.length === 2) {
253
- this.slideToIndexWithStyleReset(index, event);
254
- } else {
255
- this.slideToIndex(index, event);
256
- }
257
- }
258
- if (onThumbnailClick) {
259
- onThumbnailClick(event, index);
260
- }
261
- }
262
-
263
- onBulletClick = (event, index) => {
264
- const { onBulletClick, items } = this.props;
265
- const { currentIndex } = this.state;
266
- // blur element to remove outline caused by focus
267
- event.target.blur();
268
- if (currentIndex !== index) {
269
- if (items.length === 2) {
270
- this.slideToIndexWithStyleReset(index, event);
271
- } else {
272
- this.slideToIndex(index, event);
273
- }
274
- }
275
- if (onBulletClick) {
276
- onBulletClick(event, index);
277
- }
278
- };
279
-
280
- onThumbnailMouseOver(event, index) {
281
- if (this.thumbnailMouseOverTimer) {
282
- window.clearTimeout(this.thumbnailMouseOverTimer);
283
- this.thumbnailMouseOverTimer = null;
284
- }
285
- this.thumbnailMouseOverTimer = window.setTimeout(() => {
286
- this.slideToIndex(index);
287
- this.pause();
288
- }, 300);
289
- }
290
-
291
- onThumbnailMouseLeave() {
292
- if (this.thumbnailMouseOverTimer) {
293
- const { autoPlay } = this.props;
294
- window.clearTimeout(this.thumbnailMouseOverTimer);
295
- this.thumbnailMouseOverTimer = null;
296
- if (autoPlay) {
297
- this.play();
298
- }
299
- }
300
- }
301
-
302
- setThumbsTranslate(thumbsTranslate) {
303
- this.setState({ thumbsTranslate });
304
- }
305
-
306
- setModalFullscreen(state) {
307
- const { onScreenChange } = this.props;
308
- this.setState({ modalFullscreen: state });
309
- // manually call because browser does not support screenchange events
310
- if (onScreenChange) {
311
- onScreenChange(state);
312
- }
313
- }
314
-
315
- getThumbsTranslate(indexDifference) {
316
- const { disableThumbnailScroll, items } = this.props;
317
- const { thumbnailsWrapperWidth, thumbnailsWrapperHeight } = this.state;
318
- // the scroll space that is hidden on the left & right / top & bottom
319
- // when the screen is not large enough to fit all thumbnails
320
- let hiddenScroll;
321
- const thumbsElement = this.thumbnails && this.thumbnails.current;
322
-
323
- if (disableThumbnailScroll) return 0;
324
-
325
- if (thumbsElement) {
326
- // total scroll required to see the last thumbnail
327
- if (this.isThumbnailVertical()) {
328
- if (thumbsElement.scrollHeight <= thumbnailsWrapperHeight) {
329
- return 0;
330
- }
331
- hiddenScroll = thumbsElement.scrollHeight - thumbnailsWrapperHeight;
332
- } else {
333
- if (
334
- thumbsElement.scrollWidth <= thumbnailsWrapperWidth ||
335
- thumbnailsWrapperWidth <= 0
336
- ) {
337
- return 0;
338
- }
339
- hiddenScroll = thumbsElement.scrollWidth - thumbnailsWrapperWidth;
340
- }
341
-
342
- // scroll-x or y required per index change
343
- const perIndexScroll = hiddenScroll / (items.length - 1);
344
- return indexDifference * perIndexScroll;
345
- }
346
- return 0;
347
- }
348
-
349
- getThumbnailPositionClassName(thumbnailPosition) {
350
- // get the specific thumbnailPosition className
351
- const leftClassName = "image-gallery-thumbnails-left";
352
- const rightClassName = "image-gallery-thumbnails-right";
353
- const bottomClassName = "image-gallery-thumbnails-bottom";
354
- const topClassName = "image-gallery-thumbnails-top";
355
-
356
- switch (thumbnailPosition) {
357
- case "left":
358
- thumbnailPosition = ` ${leftClassName}`;
359
- break;
360
- case "right":
361
- thumbnailPosition = ` ${rightClassName}`;
362
- break;
363
- case "bottom":
364
- thumbnailPosition = ` ${bottomClassName}`;
365
- break;
366
- case "top":
367
- thumbnailPosition = ` ${topClassName}`;
368
- break;
369
- default:
370
- break;
371
- }
372
-
373
- return thumbnailPosition;
374
- }
375
-
376
- getAlignmentClassName(index) {
377
- // Necessary for lazing loading
378
- const { currentIndex } = this.state;
379
- const { infinite, items } = this.props;
380
- let alignment = "";
381
- const leftClassName = "image-gallery-left";
382
- const centerClassName = "image-gallery-center";
383
- const rightClassName = "image-gallery-right";
384
-
385
- switch (index) {
386
- case currentIndex - 1:
387
- alignment = ` ${leftClassName}`;
388
- break;
389
- case currentIndex:
390
- alignment = ` ${centerClassName}`;
391
- break;
392
- case currentIndex + 1:
393
- alignment = ` ${rightClassName}`;
394
- break;
395
- default:
396
- break;
397
- }
398
-
399
- if (items.length >= 3 && infinite) {
400
- if (index === 0 && currentIndex === items.length - 1) {
401
- // set first slide as right slide if were sliding right from last slide
402
- alignment = ` ${rightClassName}`;
403
- } else if (index === items.length - 1 && currentIndex === 0) {
404
- // set last slide as left slide if were sliding left from first slide
405
- alignment = ` ${leftClassName}`;
406
- }
407
- }
408
-
409
- return alignment;
410
- }
411
-
412
- getTranslateXForTwoSlide(index) {
413
- // For taking care of infinite swipe when there are only two slides
414
- const { currentIndex, currentSlideOffset, previousIndex } = this.state;
415
- const indexChanged = currentIndex !== previousIndex;
416
- const firstSlideWasPrevSlide = index === 0 && previousIndex === 0;
417
- const secondSlideWasPrevSlide = index === 1 && previousIndex === 1;
418
- const firstSlideIsNextSlide = index === 0 && currentIndex === 1;
419
- const secondSlideIsNextSlide = index === 1 && currentIndex === 0;
420
- const swipingEnded = currentSlideOffset === 0;
421
- const baseTranslateX = -100 * currentIndex;
422
- let translateX = baseTranslateX + index * 100 + currentSlideOffset;
423
-
424
- // keep track of user swiping direction
425
- // important to understand how to translateX based on last direction
426
- if (currentSlideOffset > 0) {
427
- this.direction = "left";
428
- } else if (currentSlideOffset < 0) {
429
- this.direction = "right";
430
- }
431
-
432
- // when swiping between two slides make sure the next and prev slides
433
- // are on both left and right
434
- if (secondSlideIsNextSlide && currentSlideOffset > 0) {
435
- // swiping right
436
- translateX = -100 + currentSlideOffset;
437
- }
438
- if (firstSlideIsNextSlide && currentSlideOffset < 0) {
439
- // swiping left
440
- translateX = 100 + currentSlideOffset;
441
- }
442
-
443
- if (indexChanged) {
444
- // when indexChanged move the slide to the correct side
445
- if (firstSlideWasPrevSlide && swipingEnded && this.direction === "left") {
446
- translateX = 100;
447
- } else if (
448
- secondSlideWasPrevSlide &&
449
- swipingEnded &&
450
- this.direction === "right"
451
- ) {
452
- translateX = -100;
453
- }
454
- } else {
455
- // keep the slide on the correct side if the swipe was not successful
456
- if (secondSlideIsNextSlide && swipingEnded && this.direction === "left") {
457
- translateX = -100;
458
- }
459
- if (firstSlideIsNextSlide && swipingEnded && this.direction === "right") {
460
- translateX = 100;
461
- }
462
- }
463
-
464
- return translateX;
465
- }
466
-
467
- getThumbnailBarHeight() {
468
- if (this.isThumbnailVertical()) {
469
- const { gallerySlideWrapperHeight } = this.state;
470
- return { height: gallerySlideWrapperHeight };
471
- }
472
- return {};
473
- }
474
-
475
- getSlideStyle(index) {
476
- const { currentIndex, currentSlideOffset, slideStyle } = this.state;
477
- const { infinite, items, useTranslate3D, isRTL } = this.props;
478
- const baseTranslateX = -100 * currentIndex;
479
- const totalSlides = items.length - 1;
480
-
481
- // calculates where the other slides belong based on currentIndex
482
- // if it is RTL the base line should be reversed
483
- let translateX =
484
- (baseTranslateX + index * 100) * (isRTL ? -1 : 1) + currentSlideOffset;
485
-
486
- if (infinite && items.length > 2) {
487
- if (currentIndex === 0 && index === totalSlides) {
488
- // make the last slide the slide before the first
489
- // if it is RTL the base line should be reversed
490
- translateX = -100 * (isRTL ? -1 : 1) + currentSlideOffset;
491
- } else if (currentIndex === totalSlides && index === 0) {
492
- // make the first slide the slide after the last
493
- // if it is RTL the base line should be reversed
494
- translateX = 100 * (isRTL ? -1 : 1) + currentSlideOffset;
495
- }
496
- }
497
-
498
- // Special case when there are only 2 items with infinite on
499
- if (infinite && items.length === 2) {
500
- translateX = this.getTranslateXForTwoSlide(index);
501
- }
502
-
503
- let translate = `translate(${translateX}%, 0)`;
504
-
505
- if (useTranslate3D) {
506
- translate = `translate3d(${translateX}%, 0, 0)`;
507
- }
508
-
509
- // don't show some slides while transitioning to avoid background transitions
510
- const isVisible = this.isSlideVisible(index);
511
-
512
- return {
513
- display: isVisible ? "inherit" : "none",
514
- WebkitTransform: translate,
515
- MozTransform: translate,
516
- msTransform: translate,
517
- OTransform: translate,
518
- transform: translate,
519
- ...slideStyle,
520
- };
521
- }
522
-
523
- getCurrentIndex() {
524
- const { currentIndex } = this.state;
525
- return currentIndex;
526
- }
527
-
528
- getThumbnailStyle() {
529
- let translate;
530
- const { useTranslate3D, isRTL } = this.props;
531
- const { thumbsTranslate, thumbsStyle } = this.state;
532
- const verticalTranslateValue = isRTL
533
- ? thumbsTranslate * -1
534
- : thumbsTranslate;
535
-
536
- if (this.isThumbnailVertical()) {
537
- translate = `translate(0, ${thumbsTranslate}px)`;
538
- if (useTranslate3D) {
539
- translate = `translate3d(0, ${thumbsTranslate}px, 0)`;
540
- }
541
- } else {
542
- translate = `translate(${verticalTranslateValue}px, 0)`;
543
- if (useTranslate3D) {
544
- translate = `translate3d(${verticalTranslateValue}px, 0, 0)`;
545
- }
546
- }
547
- return {
548
- WebkitTransform: translate,
549
- MozTransform: translate,
550
- msTransform: translate,
551
- OTransform: translate,
552
- transform: translate,
553
- ...thumbsStyle,
554
- };
555
- }
556
-
557
- getSlideItems() {
558
- const { currentIndex } = this.state;
559
- const {
560
- items,
561
- slideOnThumbnailOver,
562
- onClick,
563
- lazyLoad,
564
- onTouchMove,
565
- onTouchEnd,
566
- onTouchStart,
567
- onMouseOver,
568
- onMouseLeave,
569
- renderItem,
570
- renderThumbInner,
571
- showThumbnails,
572
- showBullets,
573
- } = this.props;
574
-
575
- const slides = [];
576
- const thumbnails = [];
577
- const bullets = [];
578
-
579
- items.forEach((item, index) => {
580
- const alignment = this.getAlignmentClassName(index);
581
- const originalClass = item.originalClass ? ` ${item.originalClass}` : "";
582
- const thumbnailClass = item.thumbnailClass
583
- ? ` ${item.thumbnailClass}`
584
- : "";
585
- const handleRenderItem = item.renderItem || renderItem || this.renderItem;
586
- const handleRenderThumbInner =
587
- item.renderThumbInner || renderThumbInner || this.renderThumbInner;
588
-
589
- const showItem = !lazyLoad || alignment || this.lazyLoaded[index];
590
- if (showItem && lazyLoad && !this.lazyLoaded[index]) {
591
- this.lazyLoaded[index] = true;
592
- }
593
-
594
- const slideStyle = this.getSlideStyle(index);
595
-
596
- const slide = (
597
- <div
598
- aria-label={`Go to Slide ${index + 1}`}
599
- key={`slide-${index}`}
600
- tabIndex="-1"
601
- className={`image-gallery-slide ${alignment} ${originalClass}`}
602
- style={slideStyle}
603
- onClick={onClick}
604
- onKeyUp={this.handleSlideKeyUp}
605
- onTouchMove={onTouchMove}
606
- onTouchEnd={onTouchEnd}
607
- onTouchStart={onTouchStart}
608
- onMouseOver={onMouseOver}
609
- onFocus={onMouseOver}
610
- onMouseLeave={onMouseLeave}
611
- role="button"
612
- >
613
- {showItem ? (
614
- handleRenderItem(item)
615
- ) : (
616
- <div style={{ height: "100%" }} />
617
- )}
618
- </div>
619
- );
620
-
621
- slides.push(slide);
622
-
623
- // Don't add thumbnails if there is none
624
- if (showThumbnails && item.thumbnail) {
625
- const igThumbnailClass = clsx(
626
- "image-gallery-thumbnail",
627
- thumbnailClass,
628
- { active: currentIndex === index }
629
- );
630
- thumbnails.push(
631
- <button
632
- key={`thumbnail-${index}`}
633
- type="button"
634
- tabIndex="0"
635
- aria-pressed={currentIndex === index ? "true" : "false"}
636
- aria-label={`Go to Slide ${index + 1}`}
637
- className={igThumbnailClass}
638
- onMouseLeave={
639
- slideOnThumbnailOver ? this.onThumbnailMouseLeave : null
640
- }
641
- onMouseOver={(event) => this.handleThumbnailMouseOver(event, index)}
642
- onFocus={(event) => this.handleThumbnailMouseOver(event, index)}
643
- onKeyUp={(event) => this.handleThumbnailKeyUp(event, index)}
644
- onClick={(event) => this.onThumbnailClick(event, index)}
645
- >
646
- {handleRenderThumbInner(item)}
647
- </button>
648
- );
649
- }
650
-
651
- if (showBullets) {
652
- const igBulletClass = clsx("image-gallery-bullet", item.bulletClass, {
653
- active: currentIndex === index,
654
- });
655
- bullets.push(
656
- <button
657
- type="button"
658
- key={`bullet-${index}`}
659
- className={igBulletClass}
660
- onClick={(event) => this.onBulletClick(event, index)}
661
- aria-pressed={currentIndex === index ? "true" : "false"}
662
- aria-label={`Go to Slide ${index + 1}`}
663
- />
664
- );
665
- }
666
- });
667
-
668
- return {
669
- slides,
670
- thumbnails,
671
- bullets,
672
- };
673
- }
674
-
675
- ignoreIsTransitioning() {
676
- /*
677
- Ignore isTransitioning because were not going to sibling slides
678
- e.g. center to left or center to right
679
- */
680
- const { items } = this.props;
681
- const { previousIndex, currentIndex } = this.state;
682
- const totalSlides = items.length - 1;
683
-
684
- // we want to show the in between slides transition
685
- const slidingMoreThanOneSlideLeftOrRight =
686
- Math.abs(previousIndex - currentIndex) > 1;
687
- const notGoingFromFirstToLast = !(
688
- previousIndex === 0 && currentIndex === totalSlides
689
- );
690
- const notGoingFromLastToFirst = !(
691
- previousIndex === totalSlides && currentIndex === 0
692
- );
693
-
694
- return (
695
- slidingMoreThanOneSlideLeftOrRight &&
696
- notGoingFromFirstToLast &&
697
- notGoingFromLastToFirst
698
- );
699
- }
700
-
701
- isFirstOrLastSlide(index) {
702
- const { items } = this.props;
703
- const totalSlides = items.length - 1;
704
- const isLastSlide = index === totalSlides;
705
- const isFirstSlide = index === 0;
706
- return isLastSlide || isFirstSlide;
707
- }
708
-
709
- slideIsTransitioning(index) {
710
- /*
711
- returns true if the gallery is transitioning and the index is not the
712
- previous or currentIndex
713
- */
714
- const { isTransitioning, previousIndex, currentIndex } = this.state;
715
- const indexIsNotPreviousOrNextSlide = !(
716
- index === previousIndex || index === currentIndex
717
- );
718
- return isTransitioning && indexIsNotPreviousOrNextSlide;
719
- }
720
-
721
- isSlideVisible(index) {
722
- /*
723
- Show slide if slide is the current slide and the next slide
724
- OR
725
- The slide is going more than one slide left or right, but not going from
726
- first to last and not going from last to first
727
-
728
- Edge case:
729
- If you go to the first or last slide, when they're
730
- not left, or right of each other they will try to catch up in the background
731
- so unless were going from first to last or vice versa we don't want the first
732
- or last slide to show up during the transition
733
- */
734
- return (
735
- !this.slideIsTransitioning(index) ||
736
- (this.ignoreIsTransitioning() && !this.isFirstOrLastSlide(index))
737
- );
738
- }
739
-
740
- slideThumbnailBar() {
741
- const { currentIndex, isSwipingThumbnail } = this.state;
742
- const nextTranslate = -this.getThumbsTranslate(currentIndex);
743
- if (isSwipingThumbnail) {
744
- return;
745
- }
746
-
747
- if (currentIndex === 0) {
748
- this.setState({ thumbsTranslate: 0, thumbsSwipedTranslate: 0 });
749
- } else {
750
- this.setState({
751
- thumbsTranslate: nextTranslate,
752
- thumbsSwipedTranslate: nextTranslate,
753
- });
754
- }
755
- }
756
-
757
- canSlide() {
758
- const { items } = this.props;
759
- return items.length >= 2;
760
- }
761
-
762
- canSlideLeft() {
763
- const { infinite } = this.props;
764
- return infinite || this.canSlidePrevious();
765
- }
766
-
767
- canSlideRight() {
768
- const { infinite } = this.props;
769
- return infinite || this.canSlideNext();
770
- }
771
-
772
- canSlidePrevious() {
773
- const { currentIndex } = this.state;
774
- return currentIndex > 0;
775
- }
776
-
777
- canSlideNext() {
778
- const { currentIndex } = this.state;
779
- const { items } = this.props;
780
- return currentIndex < items.length - 1;
781
- }
782
-
783
- handleSwiping({ event, absX, dir }) {
784
- const { disableSwipe, stopPropagation } = this.props;
785
- const { galleryWidth, isTransitioning, swipingUpDown, swipingLeftRight } =
786
- this.state;
787
-
788
- // if the initial swiping is up/down prevent moving the slides until swipe ends
789
- if ((dir === UP || dir === DOWN || swipingUpDown) && !swipingLeftRight) {
790
- if (!swipingUpDown) {
791
- this.setState({ swipingUpDown: true });
792
- }
793
- return;
794
- }
795
-
796
- if ((dir === LEFT || dir === RIGHT) && !swipingLeftRight) {
797
- this.setState({ swipingLeftRight: true });
798
- }
799
-
800
- if (disableSwipe) return;
801
-
802
- const { swipingTransitionDuration } = this.props;
803
- if (stopPropagation) {
804
- event.preventDefault();
805
- }
806
-
807
- if (!isTransitioning) {
808
- const side = dir === RIGHT ? 1 : -1;
809
-
810
- let currentSlideOffset = (absX / galleryWidth) * 100;
811
- if (Math.abs(currentSlideOffset) >= 100) {
812
- currentSlideOffset = 100;
813
- }
814
-
815
- const swipingTransition = {
816
- transition: `transform ${swipingTransitionDuration}ms ease-out`,
817
- };
818
-
819
- this.setState({
820
- currentSlideOffset: side * currentSlideOffset,
821
- slideStyle: swipingTransition,
822
- });
823
- } else {
824
- // don't move the slide
825
- this.setState({ currentSlideOffset: 0 });
826
- }
827
- }
828
-
829
- handleThumbnailSwiping({ event, absX, absY, dir }) {
830
- const { stopPropagation, swipingThumbnailTransitionDuration } = this.props;
831
- const {
832
- thumbsSwipedTranslate,
833
- thumbnailsWrapperHeight,
834
- thumbnailsWrapperWidth,
835
- swipingUpDown,
836
- swipingLeftRight,
837
- } = this.state;
838
-
839
- if (this.isThumbnailVertical()) {
840
- // if the initial swiping is left/right, prevent moving the thumbnail bar until swipe ends
841
- if (
842
- (dir === LEFT || dir === RIGHT || swipingLeftRight) &&
843
- !swipingUpDown
844
- ) {
845
- if (!swipingLeftRight) {
846
- this.setState({ swipingLeftRight: true });
847
- }
848
- return;
849
- }
850
-
851
- if ((dir === UP || dir === DOWN) && !swipingUpDown) {
852
- this.setState({ swipingUpDown: true });
853
- }
854
- } else {
855
- // if the initial swiping is up/down, prevent moving the thumbnail bar until swipe ends
856
- if ((dir === UP || dir === DOWN || swipingUpDown) && !swipingLeftRight) {
857
- if (!swipingUpDown) {
858
- this.setState({ swipingUpDown: true });
859
- }
860
- return;
861
- }
862
-
863
- if ((dir === LEFT || dir === RIGHT) && !swipingLeftRight) {
864
- this.setState({ swipingLeftRight: true });
865
- }
866
- }
867
-
868
- const thumbsElement = this.thumbnails && this.thumbnails.current;
869
- const emptySpaceMargin = 20; // 20px to add some margin to show empty space
870
-
871
- let thumbsTranslate;
872
- let totalSwipeableLength;
873
- let hasSwipedPassedEnd;
874
- let hasSwipedPassedStart;
875
- let isThumbnailBarSmallerThanContainer;
876
-
877
- if (this.isThumbnailVertical()) {
878
- const slideY = dir === DOWN ? absY : -absY;
879
- thumbsTranslate = thumbsSwipedTranslate + slideY;
880
- totalSwipeableLength =
881
- thumbsElement.scrollHeight - thumbnailsWrapperHeight + emptySpaceMargin;
882
- hasSwipedPassedEnd = Math.abs(thumbsTranslate) > totalSwipeableLength;
883
- hasSwipedPassedStart = thumbsTranslate > emptySpaceMargin;
884
- isThumbnailBarSmallerThanContainer =
885
- thumbsElement.scrollHeight <= thumbnailsWrapperHeight;
886
- } else {
887
- const slideX = dir === RIGHT ? absX : -absX;
888
- thumbsTranslate = thumbsSwipedTranslate + slideX;
889
- totalSwipeableLength =
890
- thumbsElement.scrollWidth - thumbnailsWrapperWidth + emptySpaceMargin;
891
- hasSwipedPassedEnd = Math.abs(thumbsTranslate) > totalSwipeableLength;
892
- hasSwipedPassedStart = thumbsTranslate > emptySpaceMargin;
893
- isThumbnailBarSmallerThanContainer =
894
- thumbsElement.scrollWidth <= thumbnailsWrapperWidth;
895
- }
896
-
897
- if (isThumbnailBarSmallerThanContainer) {
898
- // no need to swipe a thumbnail bar smaller/shorter than its container
899
- return;
900
- }
901
-
902
- if ((dir === LEFT || dir === UP) && hasSwipedPassedEnd) {
903
- // prevent further swipeing
904
- return;
905
- }
906
-
907
- if ((dir === RIGHT || dir === DOWN) && hasSwipedPassedStart) {
908
- // prevent further swipeing
909
- return;
910
- }
911
-
912
- if (stopPropagation) event.stopPropagation();
913
-
914
- const swipingTransition = {
915
- transition: `transform ${swipingThumbnailTransitionDuration}ms ease-out`,
916
- };
917
-
918
- this.setState({
919
- thumbsTranslate,
920
- thumbsStyle: swipingTransition,
921
- });
922
- }
923
-
924
- handleOnThumbnailSwiped() {
925
- const { thumbsTranslate } = this.state;
926
- const { slideDuration } = this.props;
927
- this.resetSwipingDirection();
928
- this.setState({
929
- isSwipingThumbnail: true,
930
- thumbsSwipedTranslate: thumbsTranslate,
931
- thumbsStyle: { transition: `all ${slideDuration}ms ease-out` },
932
- });
933
- }
934
-
935
- sufficientSwipe() {
936
- const { currentSlideOffset } = this.state;
937
- const { swipeThreshold } = this.props;
938
- return Math.abs(currentSlideOffset) > swipeThreshold;
939
- }
940
-
941
- resetSwipingDirection() {
942
- const { swipingUpDown, swipingLeftRight } = this.state;
943
- if (swipingUpDown) {
944
- // user stopped swipingUpDown, reset
945
- this.setState({ swipingUpDown: false });
946
- }
947
-
948
- if (swipingLeftRight) {
949
- // user stopped swipingLeftRight, reset
950
- this.setState({ swipingLeftRight: false });
951
- }
952
- }
953
-
954
- handleOnSwiped({ event, dir, velocity }) {
955
- const { disableSwipe, stopPropagation, flickThreshold } = this.props;
956
-
957
- if (disableSwipe) return;
958
-
959
- const { isRTL } = this.props;
960
- if (stopPropagation) event.stopPropagation();
961
- this.resetSwipingDirection();
962
-
963
- // if it is RTL the direction is reversed
964
- const swipeDirection = (dir === LEFT ? 1 : -1) * (isRTL ? -1 : 1);
965
- const isSwipeUpOrDown = dir === UP || dir === DOWN;
966
- const isLeftRightFlick = velocity > flickThreshold && !isSwipeUpOrDown;
967
- this.handleOnSwipedTo(swipeDirection, isLeftRightFlick);
968
- }
969
-
970
- handleOnSwipedTo(swipeDirection, isLeftRightFlick) {
971
- const { currentIndex, isTransitioning } = this.state;
972
- let slideTo = currentIndex;
973
-
974
- if ((this.sufficientSwipe() || isLeftRightFlick) && !isTransitioning) {
975
- // slideto the next/prev slide
976
- slideTo += swipeDirection;
977
- }
978
-
979
- // If we can't swipe left or right, stay in the current index (noop)
980
- if (
981
- (swipeDirection === -1 && !this.canSlideLeft()) ||
982
- (swipeDirection === 1 && !this.canSlideRight())
983
- ) {
984
- slideTo = currentIndex;
985
- }
986
-
987
- this.unthrottledSlideToIndex(slideTo);
988
- }
989
-
990
- handleTouchMove(event) {
991
- const { swipingLeftRight } = this.state;
992
- if (swipingLeftRight) {
993
- // prevent background scrolling up and down while swiping left and right
994
- event.preventDefault();
995
- }
996
- }
997
-
998
- handleMouseDown() {
999
- // keep track of mouse vs keyboard usage for a11y
1000
- this.imageGallery.current.classList.add("image-gallery-using-mouse");
1001
- }
1002
-
1003
- handleKeyDown(event) {
1004
- const { disableKeyDown, useBrowserFullscreen } = this.props;
1005
- const { isFullscreen } = this.state;
1006
- // keep track of mouse vs keyboard usage for a11y
1007
- this.imageGallery.current.classList.remove("image-gallery-using-mouse");
1008
-
1009
- if (disableKeyDown) return;
1010
- const LEFT_ARROW = 37;
1011
- const RIGHT_ARROW = 39;
1012
- const ESC_KEY = 27;
1013
- const key = parseInt(event.keyCode || event.which || 0, 10);
1014
-
1015
- switch (key) {
1016
- case LEFT_ARROW:
1017
- if (this.canSlideLeft() && !this.playPauseIntervalId) {
1018
- this.slideLeft(event);
1019
- }
1020
- break;
1021
- case RIGHT_ARROW:
1022
- if (this.canSlideRight() && !this.playPauseIntervalId) {
1023
- this.slideRight(event);
1024
- }
1025
- break;
1026
- case ESC_KEY:
1027
- if (isFullscreen && !useBrowserFullscreen) {
1028
- this.exitFullScreen();
1029
- }
1030
- break;
1031
- default:
1032
- break;
1033
- }
1034
- }
1035
-
1036
- handleImageError(event) {
1037
- const { onErrorImageURL } = this.props;
1038
- if (onErrorImageURL && event.target.src.indexOf(onErrorImageURL) === -1) {
1039
- /* eslint-disable no-param-reassign */
1040
- event.target.src = onErrorImageURL;
1041
- /* eslint-enable no-param-reassign */
1042
- }
1043
- }
1044
-
1045
- removeThumbnailsResizeObserver() {
1046
- if (
1047
- this.resizeThumbnailWrapperObserver &&
1048
- this.thumbnailsWrapper &&
1049
- this.thumbnailsWrapper.current
1050
- ) {
1051
- this.resizeThumbnailWrapperObserver.unobserve(
1052
- this.thumbnailsWrapper.current
1053
- );
1054
- this.resizeThumbnailWrapperObserver = null;
1055
- }
1056
- }
1057
-
1058
- removeResizeObserver() {
1059
- if (
1060
- this.resizeSlideWrapperObserver &&
1061
- this.imageGallerySlideWrapper &&
1062
- this.imageGallerySlideWrapper.current
1063
- ) {
1064
- this.resizeSlideWrapperObserver.unobserve(
1065
- this.imageGallerySlideWrapper.current
1066
- );
1067
- this.resizeSlideWrapperObserver = null;
1068
- }
1069
- this.removeThumbnailsResizeObserver();
1070
- }
1071
-
1072
- handleResize() {
1073
- const { currentIndex } = this.state;
1074
-
1075
- // component has been unmounted
1076
- if (!this.imageGallery) {
1077
- return;
1078
- }
1079
-
1080
- if (this.imageGallery && this.imageGallery.current) {
1081
- this.setState({ galleryWidth: this.imageGallery.current.offsetWidth });
1082
- }
1083
-
1084
- if (
1085
- this.imageGallerySlideWrapper &&
1086
- this.imageGallerySlideWrapper.current
1087
- ) {
1088
- this.setState({
1089
- gallerySlideWrapperHeight:
1090
- this.imageGallerySlideWrapper.current.offsetHeight,
1091
- });
1092
- }
1093
-
1094
- // Adjust thumbnail container when thumbnail width or height is adjusted
1095
- this.setThumbsTranslate(-this.getThumbsTranslate(currentIndex));
1096
- }
1097
-
1098
- initSlideWrapperResizeObserver(element) {
1099
- if (element && !element.current) return;
1100
- // keeps track of gallery height changes for vertical thumbnail height
1101
- this.resizeSlideWrapperObserver = new ResizeObserver(
1102
- debounce((entries) => {
1103
- if (!entries) return;
1104
- entries.forEach((entry) => {
1105
- this.setState(
1106
- { thumbnailsWrapperWidth: entry.contentRect.width },
1107
- this.handleResize
1108
- );
1109
- });
1110
- }, 50)
1111
- );
1112
- this.resizeSlideWrapperObserver.observe(element.current);
1113
- }
1114
-
1115
- initThumbnailWrapperResizeObserver(element) {
1116
- if (element && !element.current) return; // thumbnails are not always available
1117
- this.resizeThumbnailWrapperObserver = new ResizeObserver(
1118
- debounce((entries) => {
1119
- if (!entries) return;
1120
- entries.forEach((entry) => {
1121
- this.setState(
1122
- { thumbnailsWrapperHeight: entry.contentRect.height },
1123
- this.handleResize
1124
- );
1125
- });
1126
- }, 50)
1127
- );
1128
- this.resizeThumbnailWrapperObserver.observe(element.current);
1129
- }
1130
-
1131
- toggleFullScreen() {
1132
- const { isFullscreen } = this.state;
1133
- if (isFullscreen) {
1134
- this.exitFullScreen();
1135
- } else {
1136
- this.fullScreen();
1137
- }
1138
- }
1139
-
1140
- togglePlay() {
1141
- if (this.playPauseIntervalId) {
1142
- this.pause();
1143
- } else {
1144
- this.play();
1145
- }
1146
- }
1147
-
1148
- handleScreenChange() {
1149
- /*
1150
- handles screen change events that the browser triggers e.g. esc key
1151
- */
1152
- const { onScreenChange, useBrowserFullscreen } = this.props;
1153
- const fullScreenElement =
1154
- document.fullscreenElement ||
1155
- document.msFullscreenElement ||
1156
- document.mozFullScreenElement ||
1157
- document.webkitFullscreenElement;
1158
-
1159
- // check if screenchange element is the gallery
1160
- const isFullscreen = this.imageGallery.current === fullScreenElement;
1161
- if (onScreenChange) onScreenChange(isFullscreen);
1162
- if (useBrowserFullscreen) this.setState({ isFullscreen });
1163
- }
1164
-
1165
- slideToIndex(index, event) {
1166
- const { currentIndex, isTransitioning } = this.state;
1167
- const { items, slideDuration, onBeforeSlide } = this.props;
1168
-
1169
- if (!isTransitioning) {
1170
- if (event) {
1171
- if (this.playPauseIntervalId) {
1172
- // user triggered event while ImageGallery is playing, reset interval
1173
- this.pause(false);
1174
- this.play(false);
1175
- }
1176
- }
1177
-
1178
- const slideCount = items.length - 1;
1179
- let nextIndex = index;
1180
- if (index < 0) {
1181
- nextIndex = slideCount;
1182
- } else if (index > slideCount) {
1183
- nextIndex = 0;
1184
- }
1185
-
1186
- if (onBeforeSlide && nextIndex !== currentIndex) {
1187
- onBeforeSlide(nextIndex);
1188
- }
1189
-
1190
- this.setState(
1191
- {
1192
- previousIndex: currentIndex,
1193
- currentIndex: nextIndex,
1194
- isTransitioning: nextIndex !== currentIndex,
1195
- currentSlideOffset: 0,
1196
- slideStyle: { transition: `all ${slideDuration}ms ease-out` },
1197
- },
1198
- this.onSliding
1199
- );
1200
- }
1201
- }
1202
-
1203
- slideLeft(event) {
1204
- const { isRTL } = this.props;
1205
- this.slideTo(event, isRTL ? "right" : "left");
1206
- }
1207
-
1208
- slideRight(event) {
1209
- const { isRTL } = this.props;
1210
- this.slideTo(event, isRTL ? "left" : "right");
1211
- }
1212
-
1213
- slideTo(event, direction) {
1214
- const { currentIndex, isTransitioning } = this.state;
1215
- const { items } = this.props;
1216
- const nextIndex = currentIndex + (direction === "left" ? -1 : 1);
1217
-
1218
- if (isTransitioning) return;
1219
-
1220
- if (items.length === 2) {
1221
- this.slideToIndexWithStyleReset(nextIndex, event);
1222
- } else {
1223
- this.slideToIndex(nextIndex, event);
1224
- }
1225
- }
1226
-
1227
- slideToIndexWithStyleReset(nextIndex, event) {
1228
- /*
1229
- When there are only 2 slides fake a tiny swipe to get the slides
1230
- on the correct side for transitioning
1231
- */
1232
- const { currentIndex, currentSlideOffset } = this.state;
1233
- this.setState(
1234
- {
1235
- // this will reset once index changes
1236
- currentSlideOffset:
1237
- currentSlideOffset + (currentIndex > nextIndex ? 0.001 : -0.001),
1238
- slideStyle: { transition: "none" }, // move the slide over instantly
1239
- },
1240
- () => {
1241
- // add 25ms timeout to avoid delay in moving slides over
1242
- window.setTimeout(() => this.slideToIndex(nextIndex, event), 25);
1243
- }
1244
- );
1245
- }
1246
-
1247
- handleThumbnailMouseOver(event, index) {
1248
- const { slideOnThumbnailOver } = this.props;
1249
- if (slideOnThumbnailOver) this.onThumbnailMouseOver(event, index);
1250
- }
1251
-
1252
- handleThumbnailKeyUp(event, index) {
1253
- // a11y support ^_^
1254
- if (isEnterOrSpaceKey(event)) this.onThumbnailClick(event, index);
1255
- }
1256
-
1257
- handleSlideKeyUp(event) {
1258
- // a11y support ^_^
1259
- if (isEnterOrSpaceKey(event)) {
1260
- const { onClick } = this.props;
1261
- onClick(event);
1262
- }
1263
- }
1264
-
1265
- isThumbnailVertical() {
1266
- const { thumbnailPosition } = this.props;
1267
- return thumbnailPosition === "left" || thumbnailPosition === "right";
1268
- }
1269
-
1270
- addScreenChangeEvent() {
1271
- screenChangeEvents.forEach((eventName) => {
1272
- document.addEventListener(eventName, this.handleScreenChange);
1273
- });
1274
- }
1275
-
1276
- removeScreenChangeEvent() {
1277
- screenChangeEvents.forEach((eventName) => {
1278
- document.removeEventListener(eventName, this.handleScreenChange);
1279
- });
1280
- }
1281
-
1282
- fullScreen() {
1283
- const { useBrowserFullscreen } = this.props;
1284
- const gallery = this.imageGallery.current;
1285
- if (useBrowserFullscreen) {
1286
- if (gallery.requestFullscreen) {
1287
- gallery.requestFullscreen();
1288
- } else if (gallery.msRequestFullscreen) {
1289
- gallery.msRequestFullscreen();
1290
- } else if (gallery.mozRequestFullScreen) {
1291
- gallery.mozRequestFullScreen();
1292
- } else if (gallery.webkitRequestFullscreen) {
1293
- gallery.webkitRequestFullscreen();
1294
- } else {
1295
- // fallback to fullscreen modal for unsupported browsers
1296
- this.setModalFullscreen(true);
1297
- }
1298
- } else {
1299
- this.setModalFullscreen(true);
1300
- }
1301
- this.setState({ isFullscreen: true });
1302
- }
1303
-
1304
- exitFullScreen() {
1305
- const { isFullscreen } = this.state;
1306
- const { useBrowserFullscreen } = this.props;
1307
- if (isFullscreen) {
1308
- if (useBrowserFullscreen) {
1309
- if (document.exitFullscreen) {
1310
- document.exitFullscreen();
1311
- } else if (document.webkitExitFullscreen) {
1312
- document.webkitExitFullscreen();
1313
- } else if (document.mozCancelFullScreen) {
1314
- document.mozCancelFullScreen();
1315
- } else if (document.msExitFullscreen) {
1316
- document.msExitFullscreen();
1317
- } else {
1318
- // fallback to fullscreen modal for unsupported browsers
1319
- this.setModalFullscreen(false);
1320
- }
1321
- } else {
1322
- this.setModalFullscreen(false);
1323
- }
1324
- this.setState({ isFullscreen: false });
1325
- }
1326
- }
1327
-
1328
- pauseOrPlay() {
1329
- const { infinite } = this.props;
1330
- const { currentIndex } = this.state;
1331
- if (!infinite && !this.canSlideRight()) {
1332
- this.pause();
1333
- } else {
1334
- this.slideToIndex(currentIndex + 1);
1335
- }
1336
- }
1337
-
1338
- play(shouldCallOnPlay = true) {
1339
- const { onPlay, slideInterval, slideDuration } = this.props;
1340
- const { currentIndex } = this.state;
1341
- if (!this.playPauseIntervalId) {
1342
- this.setState({ isPlaying: true });
1343
- this.playPauseIntervalId = window.setInterval(
1344
- this.pauseOrPlay,
1345
- Math.max(slideInterval, slideDuration)
1346
- );
1347
- if (onPlay && shouldCallOnPlay) {
1348
- onPlay(currentIndex);
1349
- }
1350
- }
1351
- }
1352
-
1353
- pause(shouldCallOnPause = true) {
1354
- const { onPause } = this.props;
1355
- const { currentIndex } = this.state;
1356
- if (this.playPauseIntervalId) {
1357
- window.clearInterval(this.playPauseIntervalId);
1358
- this.playPauseIntervalId = null;
1359
- this.setState({ isPlaying: false });
1360
- if (onPause && shouldCallOnPause) {
1361
- onPause(currentIndex);
1362
- }
1363
- }
1364
- }
1365
-
1366
- isImageLoaded(item) {
1367
- /*
1368
- Keep track of images loaded so that onImageLoad prop is not
1369
- called multiple times when re-render the images
1370
- */
1371
- const imageExists = this.loadedImages[item.original];
1372
- if (imageExists) {
1373
- return true;
1374
- }
1375
- // add image as loaded
1376
- this.loadedImages[item.original] = true;
1377
- return false;
1378
- }
1379
-
1380
- handleImageLoaded(event, original) {
1381
- const { onImageLoad } = this.props;
1382
- const imageExists = this.loadedImages[original];
1383
- if (!imageExists && onImageLoad) {
1384
- this.loadedImages[original] = true; // prevent from call again
1385
- // image just loaded, call onImageLoad
1386
- onImageLoad(event);
1387
- }
1388
- }
1389
-
1390
- renderItem(item) {
1391
- const { isFullscreen } = this.state;
1392
- const { onImageError } = this.props;
1393
- const handleImageError = onImageError || this.handleImageError;
1394
-
1395
- return (
1396
- <Item
1397
- description={item.description}
1398
- fullscreen={item.fullscreen}
1399
- handleImageLoaded={this.handleImageLoaded}
1400
- isFullscreen={isFullscreen}
1401
- onImageError={handleImageError}
1402
- original={item.original}
1403
- originalAlt={item.originalAlt}
1404
- originalHeight={item.originalHeight}
1405
- originalWidth={item.originalWidth}
1406
- originalTitle={item.originalTitle}
1407
- sizes={item.sizes}
1408
- loading={item.loading}
1409
- srcSet={item.srcSet}
1410
- />
1411
- );
1412
- }
1413
-
1414
- renderThumbInner(item) {
1415
- const { onThumbnailError } = this.props;
1416
- const handleThumbnailError = onThumbnailError || this.handleImageError;
1417
-
1418
- return (
1419
- <span className="image-gallery-thumbnail-inner">
1420
- <img
1421
- className="image-gallery-thumbnail-image"
1422
- src={item.thumbnail}
1423
- height={item.thumbnailHeight}
1424
- width={item.thumbnailWidth}
1425
- alt={item.thumbnailAlt}
1426
- title={item.thumbnailTitle}
1427
- loading={item.thumbnailLoading}
1428
- onError={handleThumbnailError}
1429
- />
1430
- {item.thumbnailLabel && (
1431
- <div className="image-gallery-thumbnail-label">
1432
- {item.thumbnailLabel}
1433
- </div>
1434
- )}
1435
- </span>
1436
- );
1437
- }
1438
-
1439
- render() {
1440
- const { currentIndex, isFullscreen, modalFullscreen, isPlaying } =
1441
- this.state;
1442
-
1443
- const {
1444
- additionalClass,
1445
- disableThumbnailSwipe,
1446
- indexSeparator, // deprecate soon, and allow custom render
1447
- isRTL,
1448
- items,
1449
- thumbnailPosition,
1450
- renderFullscreenButton,
1451
- renderCustomControls,
1452
- renderLeftNav,
1453
- renderRightNav,
1454
- showBullets,
1455
- showFullscreenButton,
1456
- showIndex,
1457
- showThumbnails,
1458
- showNav,
1459
- showPlayButton,
1460
- renderPlayPauseButton,
1461
- } = this.props;
1462
-
1463
- const thumbnailStyle = this.getThumbnailStyle();
1464
- const { slides, thumbnails, bullets } = this.getSlideItems();
1465
- const slideWrapperClass = clsx(
1466
- "image-gallery-slide-wrapper",
1467
- this.getThumbnailPositionClassName(thumbnailPosition),
1468
- { "image-gallery-rtl": isRTL }
1469
- );
1470
-
1471
- const slideWrapper = (
1472
- <div ref={this.imageGallerySlideWrapper} className={slideWrapperClass}>
1473
- {renderCustomControls && renderCustomControls()}
1474
- {this.canSlide() ? (
1475
- <React.Fragment>
1476
- {showNav && (
1477
- <React.Fragment>
1478
- {renderLeftNav(this.slideLeft, !this.canSlideLeft())}
1479
- {renderRightNav(this.slideRight, !this.canSlideRight())}
1480
- </React.Fragment>
1481
- )}
1482
- <SwipeWrapper
1483
- className="image-gallery-swipe"
1484
- delta={0}
1485
- onSwiping={this.handleSwiping}
1486
- onSwiped={this.handleOnSwiped}
1487
- >
1488
- <div className="image-gallery-slides">{slides}</div>
1489
- </SwipeWrapper>
1490
- </React.Fragment>
1491
- ) : (
1492
- <div className="image-gallery-slides">{slides}</div>
1493
- )}
1494
- {showPlayButton && renderPlayPauseButton(this.togglePlay, isPlaying)}
1495
- {showBullets && (
1496
- <div className="image-gallery-bullets">
1497
- <div
1498
- className="image-gallery-bullets-container"
1499
- role="navigation"
1500
- aria-label="Bullet Navigation"
1501
- >
1502
- {bullets}
1503
- </div>
1504
- </div>
1505
- )}
1506
- {showFullscreenButton &&
1507
- renderFullscreenButton(this.toggleFullScreen, isFullscreen)}
1508
- {showIndex && (
1509
- <div className="image-gallery-index">
1510
- <span className="image-gallery-index-current">
1511
- {currentIndex + 1}
1512
- </span>
1513
- <span className="image-gallery-index-separator">
1514
- {indexSeparator}
1515
- </span>
1516
- <span className="image-gallery-index-total">{items.length}</span>
1517
- </div>
1518
- )}
1519
- </div>
1520
- );
1521
-
1522
- const igClass = clsx("image-gallery", additionalClass, {
1523
- "fullscreen-modal": modalFullscreen,
1524
- });
1525
- const igContentClass = clsx(
1526
- "image-gallery-content",
1527
- this.getThumbnailPositionClassName(thumbnailPosition),
1528
- { fullscreen: isFullscreen }
1529
- );
1530
- const thumbnailWrapperClass = clsx(
1531
- "image-gallery-thumbnails-wrapper",
1532
- this.getThumbnailPositionClassName(thumbnailPosition),
1533
- { "thumbnails-wrapper-rtl": !this.isThumbnailVertical() && isRTL },
1534
- {
1535
- "thumbnails-swipe-horizontal":
1536
- !this.isThumbnailVertical() && !disableThumbnailSwipe,
1537
- },
1538
- {
1539
- "thumbnails-swipe-vertical":
1540
- this.isThumbnailVertical() && !disableThumbnailSwipe,
1541
- }
1542
- );
1543
- return (
1544
- <div ref={this.imageGallery} className={igClass} aria-live="polite">
1545
- <div className={igContentClass}>
1546
- {(thumbnailPosition === "bottom" || thumbnailPosition === "right") &&
1547
- slideWrapper}
1548
- {showThumbnails && thumbnails.length > 0 ? (
1549
- <SwipeWrapper
1550
- className={thumbnailWrapperClass}
1551
- delta={0}
1552
- onSwiping={!disableThumbnailSwipe && this.handleThumbnailSwiping}
1553
- onSwiped={!disableThumbnailSwipe && this.handleOnThumbnailSwiped}
1554
- >
1555
- <div
1556
- className="image-gallery-thumbnails"
1557
- ref={this.thumbnailsWrapper}
1558
- style={this.getThumbnailBarHeight()}
1559
- >
1560
- <nav
1561
- ref={this.thumbnails}
1562
- className="image-gallery-thumbnails-container"
1563
- style={thumbnailStyle}
1564
- aria-label="Thumbnail Navigation"
1565
- >
1566
- {thumbnails}
1567
- </nav>
1568
- </div>
1569
- </SwipeWrapper>
1570
- ) : null}
1571
- {(thumbnailPosition === "top" || thumbnailPosition === "left") &&
1572
- slideWrapper}
1573
- </div>
1574
- </div>
1575
- );
1576
- }
1577
- }
1578
-
1579
- ImageGallery.propTypes = {
1580
- flickThreshold: number,
1581
- items: arrayOf(
1582
- shape({
1583
- bulletClass: string,
1584
- bulletOnClick: func,
1585
- description: string,
1586
- original: string,
1587
- originalHeight: number,
1588
- originalWidth: number,
1589
- loading: string,
1590
- thumbnailHeight: number,
1591
- thumbnailWidth: number,
1592
- thumbnailLoading: string,
1593
- fullscreen: string,
1594
- originalAlt: string,
1595
- originalTitle: string,
1596
- thumbnail: string,
1597
- thumbnailAlt: string,
1598
- thumbnailLabel: string,
1599
- thumbnailTitle: string,
1600
- originalClass: string,
1601
- thumbnailClass: string,
1602
- renderItem: func,
1603
- renderThumbInner: func,
1604
- imageSet: imageSetType,
1605
- srcSet: string,
1606
- sizes: string,
1607
- })
1608
- ).isRequired,
1609
- showNav: bool,
1610
- autoPlay: bool,
1611
- lazyLoad: bool,
1612
- infinite: bool,
1613
- showIndex: bool,
1614
- showBullets: bool,
1615
- showThumbnails: bool,
1616
- showPlayButton: bool,
1617
- showFullscreenButton: bool,
1618
- disableThumbnailScroll: bool,
1619
- disableKeyDown: bool,
1620
- disableSwipe: bool,
1621
- disableThumbnailSwipe: bool,
1622
- useBrowserFullscreen: bool,
1623
- onErrorImageURL: string,
1624
- indexSeparator: string,
1625
- thumbnailPosition: oneOf(["top", "bottom", "left", "right"]),
1626
- startIndex: number,
1627
- slideDuration: number,
1628
- slideInterval: number,
1629
- slideOnThumbnailOver: bool,
1630
- swipeThreshold: number,
1631
- swipingTransitionDuration: number,
1632
- swipingThumbnailTransitionDuration: number,
1633
- onSlide: func,
1634
- onBeforeSlide: func,
1635
- onScreenChange: func,
1636
- onPause: func,
1637
- onPlay: func,
1638
- onClick: func,
1639
- onImageLoad: func,
1640
- onImageError: func,
1641
- onTouchMove: func,
1642
- onTouchEnd: func,
1643
- onTouchStart: func,
1644
- onMouseOver: func,
1645
- onMouseLeave: func,
1646
- onBulletClick: func,
1647
- onThumbnailError: func,
1648
- onThumbnailClick: func,
1649
- renderCustomControls: func,
1650
- renderLeftNav: func,
1651
- renderRightNav: func,
1652
- renderPlayPauseButton: func,
1653
- renderFullscreenButton: func,
1654
- renderItem: func,
1655
- renderThumbInner: func,
1656
- stopPropagation: bool,
1657
- additionalClass: string,
1658
- useTranslate3D: bool,
1659
- isRTL: bool,
1660
- useWindowKeyDown: bool,
1661
- };
1662
-
1663
- ImageGallery.defaultProps = {
1664
- onErrorImageURL: "",
1665
- additionalClass: "",
1666
- showNav: true,
1667
- autoPlay: false,
1668
- lazyLoad: false,
1669
- infinite: true,
1670
- showIndex: false,
1671
- showBullets: false,
1672
- showThumbnails: true,
1673
- showPlayButton: true,
1674
- showFullscreenButton: true,
1675
- disableThumbnailScroll: false,
1676
- disableKeyDown: false,
1677
- disableSwipe: false,
1678
- disableThumbnailSwipe: false,
1679
- useTranslate3D: true,
1680
- isRTL: false,
1681
- useBrowserFullscreen: true,
1682
- flickThreshold: 0.4,
1683
- stopPropagation: false,
1684
- indexSeparator: " / ",
1685
- thumbnailPosition: "bottom",
1686
- startIndex: 0,
1687
- slideDuration: 450,
1688
- swipingTransitionDuration: 0,
1689
- swipingThumbnailTransitionDuration: 0,
1690
- onSlide: null,
1691
- onBeforeSlide: null,
1692
- onScreenChange: null,
1693
- onPause: null,
1694
- onPlay: null,
1695
- onClick: null,
1696
- onImageLoad: null,
1697
- onImageError: null,
1698
- onTouchMove: null,
1699
- onTouchEnd: null,
1700
- onTouchStart: null,
1701
- onMouseOver: null,
1702
- onMouseLeave: null,
1703
- onBulletClick: null,
1704
- onThumbnailError: null,
1705
- onThumbnailClick: null,
1706
- renderCustomControls: null,
1707
- renderThumbInner: null,
1708
- renderItem: null,
1709
- slideInterval: 3000,
1710
- slideOnThumbnailOver: false,
1711
- swipeThreshold: 30,
1712
- renderLeftNav: (onClick, disabled) => (
1713
- <LeftNav onClick={onClick} disabled={disabled} />
1714
- ),
1715
- renderRightNav: (onClick, disabled) => (
1716
- <RightNav onClick={onClick} disabled={disabled} />
1717
- ),
1718
- renderPlayPauseButton: (onClick, isPlaying) => (
1719
- <PlayPause onClick={onClick} isPlaying={isPlaying} />
1720
- ),
1721
- renderFullscreenButton: (onClick, isFullscreen) => (
1722
- <Fullscreen onClick={onClick} isFullscreen={isFullscreen} />
1723
- ),
1724
- useWindowKeyDown: true,
1725
- };
1726
-
1727
- export default ImageGallery;