react-image-gallery 1.0.3 → 1.0.7

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/README.md CHANGED
@@ -4,21 +4,23 @@ React Carousel Image Gallery
4
4
  [![npm version](https://badge.fury.io/js/react-image-gallery.svg)](https://badge.fury.io/js/react-image-gallery)
5
5
  [![Download Count](http://img.shields.io/npm/dm/react-image-gallery.svg?style=flat)](http://www.npmjs.com/package/react-image-gallery)
6
6
 
7
- ## Live Demo (try it on mobile for swipe support)
8
- #### [`linxtion.com/demo/react-image-gallery`](http://linxtion.com/demo/react-image-gallery)
7
+ ### Live Demo (try it on mobile for swipe support)
8
+ [`linxtion.com/demo/react-image-gallery`](http://linxtion.com/demo/react-image-gallery)
9
9
 
10
10
  ![demo gif](https://github.com/xiaolin/react-image-gallery/raw/master/static/image_gallery_v1.0.2.gif)
11
11
 
12
12
  React image gallery is a React component for building image galleries and carousels
13
13
 
14
- Features of `react-image-gallery`
15
- * Mobile Swipe Gestures
16
- * Thumbnail Navigation
17
- * Fullscreen Support
18
- * Custom Rendered Slides
19
- * Responsive Design
14
+ ## Features
15
+
16
+ * Mobile swipe gestures
17
+ * Thumbnail navigation
18
+ * Fullscreen support
19
+ * Custom rendered slides
20
+ * RTL support
21
+ * Responsive design
20
22
  * Tons of customization options (see props below)
21
- * Lightweight ~8kb (gzipped, excluding react)
23
+ * Lightweight ~7kb gzipped
22
24
 
23
25
  ## Getting started
24
26
 
@@ -70,6 +72,7 @@ class MyGallery extends React.Component {
70
72
  * Available Properties
71
73
  * `original` - image src url
72
74
  * `thumbnail` - image thumbnail src url
75
+ * `fullscreen` - image for fullscreen (defaults to original)
73
76
  * `originalClass` - custom image class
74
77
  * `thumbnailClass` - custom thumbnail class
75
78
  * `renderItem` - Function for custom renderer (see renderItem below)
@@ -136,6 +139,7 @@ class MyGallery extends React.Component {
136
139
  * `onThumbnailClick`: Function, `callback(event, index)`
137
140
  * `onImageLoad`: Function, `callback(event)`
138
141
  * `onSlide`: Function, `callback(currentIndex)`
142
+ * `onBeforeSlide`: Function, `callback(nextIndex)`
139
143
  * `onScreenChange`: Function, `callback(fullscreenElement)`
140
144
  * `onPause`: Function, `callback(currentIndex)`
141
145
  * `onPlay`: Function, `callback(currentIndex)`
@@ -73,7 +73,7 @@ var ImageGallery = function (_React$Component) {
73
73
  _this.state = {
74
74
  currentIndex: props.startIndex,
75
75
  thumbsTranslate: 0,
76
- offsetPercentage: 0,
76
+ currentSlideOffset: 0,
77
77
  galleryWidth: 0,
78
78
  thumbnailsWrapperWidth: 0,
79
79
  thumbnailsWrapperHeight: 0,
@@ -93,6 +93,7 @@ var ImageGallery = function (_React$Component) {
93
93
  _this.handleScreenChange = _this.handleScreenChange.bind(_this);
94
94
  _this.handleSwiping = _this.handleSwiping.bind(_this);
95
95
  _this.onThumbnailMouseLeave = _this.onThumbnailMouseLeave.bind(_this);
96
+ _this.handleImageError = _this.handleImageError.bind(_this);
96
97
  _this.pauseOrPlay = _this.pauseOrPlay.bind(_this);
97
98
  _this.renderThumbInner = _this.renderThumbInner.bind(_this);
98
99
  _this.renderItem = _this.renderItem.bind(_this);
@@ -132,13 +133,15 @@ var ImageGallery = function (_React$Component) {
132
133
  lazyLoad = _props.lazyLoad,
133
134
  slideDuration = _props.slideDuration,
134
135
  startIndex = _props.startIndex,
135
- thumbnailPosition = _props.thumbnailPosition;
136
+ thumbnailPosition = _props.thumbnailPosition,
137
+ showThumbnails = _props.showThumbnails;
136
138
  var currentIndex = this.state.currentIndex;
137
139
 
138
140
  var itemsSizeChanged = prevProps.items.length !== items.length;
139
141
  var itemsChanged = !(0, _lodash6.default)(prevProps.items, items);
140
142
  var startIndexUpdated = prevProps.startIndex !== startIndex;
141
143
  var thumbnailsPositionChanged = prevProps.thumbnailPosition !== thumbnailPosition;
144
+ var showThumbnailsChanged = prevProps.showThumbnails !== showThumbnails;
142
145
 
143
146
  if (thumbnailsPositionChanged) {
144
147
  // re-initialize resizeObserver because slides was unmounted and mounted again
@@ -146,7 +149,7 @@ var ImageGallery = function (_React$Component) {
146
149
  this.initResizeObserver(this.imageGallerySlideWrapper);
147
150
  }
148
151
 
149
- if (itemsSizeChanged) {
152
+ if (itemsSizeChanged || showThumbnailsChanged) {
150
153
  this.handleResize();
151
154
  }
152
155
  if (prevState.currentIndex !== currentIndex) {
@@ -173,9 +176,9 @@ var ImageGallery = function (_React$Component) {
173
176
  window.removeEventListener('mousedown', this.handleMouseDown);
174
177
  this.removeScreenChangeEvent();
175
178
  this.removeResizeObserver();
176
- if (this.intervalId) {
177
- window.clearInterval(this.intervalId);
178
- this.intervalId = null;
179
+ if (this.playPauseIntervalId) {
180
+ window.clearInterval(this.playPauseIntervalId);
181
+ this.playPauseIntervalId = null;
179
182
  }
180
183
  if (this.transitionTimer) {
181
184
  window.clearTimeout(this.transitionTimer);
@@ -351,39 +354,50 @@ var ImageGallery = function (_React$Component) {
351
354
  // For taking care of infinite swipe when there are only two slides
352
355
  var _state4 = this.state,
353
356
  currentIndex = _state4.currentIndex,
354
- offsetPercentage = _state4.offsetPercentage,
357
+ currentSlideOffset = _state4.currentSlideOffset,
355
358
  previousIndex = _state4.previousIndex;
356
359
 
360
+ var indexChanged = currentIndex !== previousIndex;
361
+ var firstSlideWasPrevSlide = index === 0 && previousIndex === 0;
362
+ var secondSlideWasPrevSlide = index === 1 && previousIndex === 1;
363
+ var firstSlideIsNextSlide = index === 0 && currentIndex === 1;
364
+ var secondSlideIsNextSlide = index === 1 && currentIndex === 0;
365
+ var swipingEnded = currentSlideOffset === 0;
357
366
  var baseTranslateX = -100 * currentIndex;
358
- var translateX = baseTranslateX + index * 100 + offsetPercentage;
367
+ var translateX = baseTranslateX + index * 100 + currentSlideOffset;
359
368
 
360
369
  // keep track of user swiping direction
361
- if (offsetPercentage > 0) {
370
+ // important to understand how to translateX based on last direction
371
+ if (currentSlideOffset > 0) {
362
372
  this.direction = 'left';
363
- } else if (offsetPercentage < 0) {
373
+ } else if (currentSlideOffset < 0) {
364
374
  this.direction = 'right';
365
375
  }
366
376
 
367
- // when swiping make sure the slides are on the correct side
368
- if (currentIndex === 0 && index === 1 && offsetPercentage > 0) {
369
- translateX = -100 + offsetPercentage;
370
- } else if (currentIndex === 1 && index === 0 && offsetPercentage < 0) {
371
- translateX = 100 + offsetPercentage;
377
+ // when swiping between two slides make sure the next and prev slides
378
+ // are on both left and right
379
+ if (secondSlideIsNextSlide && currentSlideOffset > 0) {
380
+ // swiping right
381
+ translateX = -100 + currentSlideOffset;
382
+ }
383
+ if (firstSlideIsNextSlide && currentSlideOffset < 0) {
384
+ // swiping left
385
+ translateX = 100 + currentSlideOffset;
372
386
  }
373
387
 
374
- if (currentIndex !== previousIndex) {
375
- // when swiped move the slide to the correct side
376
- if (previousIndex === 0 && index === 0 && offsetPercentage === 0 && this.direction === 'left') {
388
+ if (indexChanged) {
389
+ // when indexChanged move the slide to the correct side
390
+ if (firstSlideWasPrevSlide && swipingEnded && this.direction === 'left') {
377
391
  translateX = 100;
378
- } else if (previousIndex === 1 && index === 1 && offsetPercentage === 0 && this.direction === 'right') {
392
+ } else if (secondSlideWasPrevSlide && swipingEnded && this.direction === 'right') {
379
393
  translateX = -100;
380
394
  }
381
395
  } else {
382
- // keep the slide on the correct slide even when not a swipe
383
- if (currentIndex === 0 && index === 1 && offsetPercentage === 0 && this.direction === 'left') {
396
+ // keep the slide on the correct side if the swipe was not successful
397
+ if (secondSlideIsNextSlide && swipingEnded && this.direction === 'left') {
384
398
  translateX = -100;
385
399
  }
386
- if (currentIndex === 1 && index === 0 && offsetPercentage === 0 && this.direction === 'right') {
400
+ if (firstSlideIsNextSlide && swipingEnded && this.direction === 'right') {
387
401
  translateX = 100;
388
402
  }
389
403
  }
@@ -405,7 +419,7 @@ var ImageGallery = function (_React$Component) {
405
419
  value: function getSlideStyle(index) {
406
420
  var _state5 = this.state,
407
421
  currentIndex = _state5.currentIndex,
408
- offsetPercentage = _state5.offsetPercentage,
422
+ currentSlideOffset = _state5.currentSlideOffset,
409
423
  slideStyle = _state5.slideStyle;
410
424
  var _props5 = this.props,
411
425
  infinite = _props5.infinite,
@@ -418,17 +432,17 @@ var ImageGallery = function (_React$Component) {
418
432
 
419
433
  // calculates where the other slides belong based on currentIndex
420
434
  // if it is RTL the base line should be reversed
421
- var translateX = (baseTranslateX + index * 100) * (isRTL ? -1 : 1) + offsetPercentage;
435
+ var translateX = (baseTranslateX + index * 100) * (isRTL ? -1 : 1) + currentSlideOffset;
422
436
 
423
437
  if (infinite && items.length > 2) {
424
438
  if (currentIndex === 0 && index === totalSlides) {
425
439
  // make the last slide the slide before the first
426
440
  // if it is RTL the base line should be reversed
427
- translateX = -100 * (isRTL ? -1 : 1) + offsetPercentage;
441
+ translateX = -100 * (isRTL ? -1 : 1) + currentSlideOffset;
428
442
  } else if (currentIndex === totalSlides && index === 0) {
429
443
  // make the first slide the slide after the last
430
444
  // if it is RTL the base line should be reversed
431
- translateX = 100 * (isRTL ? -1 : 1) + offsetPercentage;
445
+ translateX = 100 * (isRTL ? -1 : 1) + currentSlideOffset;
432
446
  }
433
447
  }
434
448
 
@@ -490,12 +504,13 @@ var ImageGallery = function (_React$Component) {
490
504
  }
491
505
  }, {
492
506
  key: 'getSlideItems',
493
- value: function getSlideItems(items) {
507
+ value: function getSlideItems() {
494
508
  var _this4 = this;
495
509
 
496
510
  var currentIndex = this.state.currentIndex;
497
511
  var _props7 = this.props,
498
512
  infinite = _props7.infinite,
513
+ items = _props7.items,
499
514
  slideOnThumbnailOver = _props7.slideOnThumbnailOver,
500
515
  onClick = _props7.onClick,
501
516
  lazyLoad = _props7.lazyLoad,
@@ -531,7 +546,7 @@ var ImageGallery = function (_React$Component) {
531
546
  var slide = _react2.default.createElement(
532
547
  'div',
533
548
  {
534
- key: 'slide-' + item.original,
549
+ key: 'slide-' + item.original + '-' + index,
535
550
  tabIndex: '-1',
536
551
  className: 'image-gallery-slide ' + alignment + ' ' + originalClass,
537
552
  style: slideStyle,
@@ -562,7 +577,7 @@ var ImageGallery = function (_React$Component) {
562
577
  thumbnails.push(_react2.default.createElement(
563
578
  'button',
564
579
  {
565
- key: 'thumbnail-' + item.original,
580
+ key: 'thumbnail-' + item.original + '-' + index,
566
581
  type: 'button',
567
582
  tabIndex: '0',
568
583
  'aria-pressed': currentIndex === index ? 'true' : 'false',
@@ -597,7 +612,7 @@ var ImageGallery = function (_React$Component) {
597
612
  var igBulletClass = (0, _clsx2.default)('image-gallery-bullet', item.bulletClass, { active: currentIndex === index });
598
613
  bullets.push(_react2.default.createElement('button', {
599
614
  type: 'button',
600
- key: 'bullet-' + item.original,
615
+ key: 'bullet-' + item.original + '-' + index,
601
616
  className: igBulletClass,
602
617
  onClick: bulletOnClick,
603
618
  'aria-pressed': currentIndex === index ? 'true' : 'false',
@@ -762,9 +777,9 @@ var ImageGallery = function (_React$Component) {
762
777
  if (!isTransitioning && !scrollingUpDown) {
763
778
  var side = dir === _reactSwipeable.RIGHT ? 1 : -1;
764
779
 
765
- var offsetPercentage = absX / galleryWidth * 100;
766
- if (Math.abs(offsetPercentage) >= 100) {
767
- offsetPercentage = 100;
780
+ var currentSlideOffset = absX / galleryWidth * 100;
781
+ if (Math.abs(currentSlideOffset) >= 100) {
782
+ currentSlideOffset = 100;
768
783
  }
769
784
 
770
785
  var swipingTransition = {
@@ -772,21 +787,21 @@ var ImageGallery = function (_React$Component) {
772
787
  };
773
788
 
774
789
  this.setState({
775
- offsetPercentage: side * offsetPercentage,
790
+ currentSlideOffset: side * currentSlideOffset,
776
791
  slideStyle: swipingTransition
777
792
  });
778
793
  } else {
779
794
  // don't move the slide
780
- this.setState({ offsetPercentage: 0 });
795
+ this.setState({ currentSlideOffset: 0 });
781
796
  }
782
797
  }
783
798
  }, {
784
799
  key: 'sufficientSwipe',
785
800
  value: function sufficientSwipe() {
786
- var offsetPercentage = this.state.offsetPercentage;
801
+ var currentSlideOffset = this.state.currentSlideOffset;
787
802
  var swipeThreshold = this.props.swipeThreshold;
788
803
 
789
- return Math.abs(offsetPercentage) > swipeThreshold;
804
+ return Math.abs(currentSlideOffset) > swipeThreshold;
790
805
  }
791
806
  }, {
792
807
  key: 'handleOnSwiped',
@@ -872,13 +887,13 @@ var ImageGallery = function (_React$Component) {
872
887
 
873
888
  switch (key) {
874
889
  case LEFT_ARROW:
875
- if (this.canSlideLeft() && !this.intervalId) {
876
- this.slideLeft();
890
+ if (this.canSlideLeft() && !this.playPauseIntervalId) {
891
+ this.slideLeft(event);
877
892
  }
878
893
  break;
879
894
  case RIGHT_ARROW:
880
- if (this.canSlideRight() && !this.intervalId) {
881
- this.slideRight();
895
+ if (this.canSlideRight() && !this.playPauseIntervalId) {
896
+ this.slideRight(event);
882
897
  }
883
898
  break;
884
899
  case ESC_KEY:
@@ -961,7 +976,7 @@ var ImageGallery = function (_React$Component) {
961
976
  }, {
962
977
  key: 'togglePlay',
963
978
  value: function togglePlay() {
964
- if (this.intervalId) {
979
+ if (this.playPauseIntervalId) {
965
980
  this.pause();
966
981
  } else {
967
982
  this.play();
@@ -973,12 +988,16 @@ var ImageGallery = function (_React$Component) {
973
988
  /*
974
989
  handles screen change events that the browser triggers e.g. esc key
975
990
  */
976
- var onScreenChange = this.props.onScreenChange;
991
+ var _props13 = this.props,
992
+ onScreenChange = _props13.onScreenChange,
993
+ useBrowserFullscreen = _props13.useBrowserFullscreen;
977
994
 
978
995
  var fullScreenElement = document.fullscreenElement || document.msFullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement;
979
996
 
980
- if (onScreenChange) onScreenChange(fullScreenElement);
981
- this.setState({ isFullscreen: !!fullScreenElement });
997
+ // check if screenchange element is the gallery
998
+ var isFullscreen = this.imageGallery.current === fullScreenElement;
999
+ if (onScreenChange) onScreenChange(isFullscreen);
1000
+ if (useBrowserFullscreen) this.setState({ isFullscreen: isFullscreen });
982
1001
  }
983
1002
  }, {
984
1003
  key: 'slideToIndex',
@@ -986,14 +1005,15 @@ var ImageGallery = function (_React$Component) {
986
1005
  var _state12 = this.state,
987
1006
  currentIndex = _state12.currentIndex,
988
1007
  isTransitioning = _state12.isTransitioning;
989
- var _props13 = this.props,
990
- items = _props13.items,
991
- slideDuration = _props13.slideDuration;
1008
+ var _props14 = this.props,
1009
+ items = _props14.items,
1010
+ slideDuration = _props14.slideDuration,
1011
+ onBeforeSlide = _props14.onBeforeSlide;
992
1012
 
993
1013
 
994
1014
  if (!isTransitioning) {
995
1015
  if (event) {
996
- if (this.intervalId) {
1016
+ if (this.playPauseIntervalId) {
997
1017
  // user triggered event while ImageGallery is playing, reset interval
998
1018
  this.pause(false);
999
1019
  this.play(false);
@@ -1008,50 +1028,102 @@ var ImageGallery = function (_React$Component) {
1008
1028
  nextIndex = 0;
1009
1029
  }
1010
1030
 
1031
+ if (onBeforeSlide && nextIndex !== currentIndex) {
1032
+ onBeforeSlide(nextIndex);
1033
+ }
1034
+
1011
1035
  this.setState({
1012
1036
  previousIndex: currentIndex,
1013
1037
  currentIndex: nextIndex,
1014
1038
  isTransitioning: nextIndex !== currentIndex,
1015
- offsetPercentage: 0,
1039
+ currentSlideOffset: 0,
1016
1040
  slideStyle: { transition: 'all ' + slideDuration + 'ms ease-out' }
1017
1041
  }, this.onSliding);
1018
1042
  }
1019
1043
  }
1020
1044
  }, {
1021
1045
  key: 'slideLeft',
1022
- value: function slideLeft() {
1046
+ value: function slideLeft(event) {
1023
1047
  var isRTL = this.props.isRTL;
1024
1048
 
1025
1049
  if (isRTL) {
1026
- this.slideNext();
1050
+ this.slideNext(event);
1027
1051
  } else {
1028
- this.slidePrevious();
1052
+ this.slidePrevious(event);
1029
1053
  }
1030
1054
  }
1031
1055
  }, {
1032
1056
  key: 'slideRight',
1033
- value: function slideRight() {
1057
+ value: function slideRight(event) {
1034
1058
  var isRTL = this.props.isRTL;
1035
1059
 
1036
1060
  if (isRTL) {
1037
- this.slidePrevious();
1061
+ this.slidePrevious(event);
1038
1062
  } else {
1039
- this.slideNext();
1063
+ this.slideNext(event);
1040
1064
  }
1041
1065
  }
1042
1066
  }, {
1043
1067
  key: 'slidePrevious',
1044
1068
  value: function slidePrevious(event) {
1045
- var currentIndex = this.state.currentIndex;
1069
+ var _this6 = this;
1046
1070
 
1047
- this.slideToIndex(currentIndex - 1, event);
1071
+ var _state13 = this.state,
1072
+ currentIndex = _state13.currentIndex,
1073
+ currentSlideOffset = _state13.currentSlideOffset,
1074
+ isTransitioning = _state13.isTransitioning;
1075
+ var items = this.props.items;
1076
+
1077
+ var nextIndex = currentIndex - 1;
1078
+
1079
+ if (isTransitioning) return;
1080
+
1081
+ if (items.length === 2) {
1082
+ /*
1083
+ When there are only 2 slides fake a tiny swipe to get the slides
1084
+ on the correct side for transitioning
1085
+ */
1086
+ this.setState({
1087
+ currentSlideOffset: currentSlideOffset + 0.001, // this will reset once index changes
1088
+ slideStyle: { transition: 'none' } // move the slide over instantly
1089
+ }, function () {
1090
+ // add 25ms timeout to avoid delay in moving slides over
1091
+ window.setTimeout(function () {
1092
+ return _this6.slideToIndex(nextIndex, event);
1093
+ }, 25);
1094
+ });
1095
+ } else {
1096
+ this.slideToIndex(nextIndex, event);
1097
+ }
1048
1098
  }
1049
1099
  }, {
1050
1100
  key: 'slideNext',
1051
1101
  value: function slideNext(event) {
1052
- var currentIndex = this.state.currentIndex;
1102
+ var _this7 = this;
1103
+
1104
+ var _state14 = this.state,
1105
+ currentIndex = _state14.currentIndex,
1106
+ currentSlideOffset = _state14.currentSlideOffset,
1107
+ isTransitioning = _state14.isTransitioning;
1108
+ var items = this.props.items;
1053
1109
 
1054
- this.slideToIndex(currentIndex + 1, event);
1110
+ var nextIndex = currentIndex + 1;
1111
+
1112
+ if (isTransitioning) return;
1113
+
1114
+ if (items.length === 2) {
1115
+ // same as above for 2 slides
1116
+ this.setState({
1117
+ currentSlideOffset: currentSlideOffset - 0.001,
1118
+ slideStyle: { transition: 'none' }
1119
+ }, function () {
1120
+ window.setTimeout(function () {
1121
+ return _this7.slideToIndex(nextIndex, event);
1122
+ }, 25);
1123
+ });
1124
+ } else {
1125
+ this.slideToIndex(nextIndex, event);
1126
+ }
1055
1127
  }
1056
1128
  }, {
1057
1129
  key: 'handleThumbnailMouseOver',
@@ -1086,19 +1158,19 @@ var ImageGallery = function (_React$Component) {
1086
1158
  }, {
1087
1159
  key: 'addScreenChangeEvent',
1088
1160
  value: function addScreenChangeEvent() {
1089
- var _this6 = this;
1161
+ var _this8 = this;
1090
1162
 
1091
1163
  screenChangeEvents.forEach(function (eventName) {
1092
- document.addEventListener(eventName, _this6.handleScreenChange);
1164
+ document.addEventListener(eventName, _this8.handleScreenChange);
1093
1165
  });
1094
1166
  }
1095
1167
  }, {
1096
1168
  key: 'removeScreenChangeEvent',
1097
1169
  value: function removeScreenChangeEvent() {
1098
- var _this7 = this;
1170
+ var _this9 = this;
1099
1171
 
1100
1172
  screenChangeEvents.forEach(function (eventName) {
1101
- document.removeEventListener(eventName, _this7.handleScreenChange);
1173
+ document.removeEventListener(eventName, _this9.handleScreenChange);
1102
1174
  });
1103
1175
  }
1104
1176
  }, {
@@ -1167,15 +1239,15 @@ var ImageGallery = function (_React$Component) {
1167
1239
  key: 'play',
1168
1240
  value: function play() {
1169
1241
  var shouldCallOnPlay = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
1170
- var _props14 = this.props,
1171
- onPlay = _props14.onPlay,
1172
- slideInterval = _props14.slideInterval,
1173
- slideDuration = _props14.slideDuration;
1242
+ var _props15 = this.props,
1243
+ onPlay = _props15.onPlay,
1244
+ slideInterval = _props15.slideInterval,
1245
+ slideDuration = _props15.slideDuration;
1174
1246
  var currentIndex = this.state.currentIndex;
1175
1247
 
1176
- if (!this.intervalId) {
1248
+ if (!this.playPauseIntervalId) {
1177
1249
  this.setState({ isPlaying: true });
1178
- this.intervalId = window.setInterval(this.pauseOrPlay, Math.max(slideInterval, slideDuration));
1250
+ this.playPauseIntervalId = window.setInterval(this.pauseOrPlay, Math.max(slideInterval, slideDuration));
1179
1251
  if (onPlay && shouldCallOnPlay) {
1180
1252
  onPlay(currentIndex);
1181
1253
  }
@@ -1188,9 +1260,9 @@ var ImageGallery = function (_React$Component) {
1188
1260
  var onPause = this.props.onPause;
1189
1261
  var currentIndex = this.state.currentIndex;
1190
1262
 
1191
- if (this.intervalId) {
1192
- window.clearInterval(this.intervalId);
1193
- this.intervalId = null;
1263
+ if (this.playPauseIntervalId) {
1264
+ window.clearInterval(this.playPauseIntervalId);
1265
+ this.playPauseIntervalId = null;
1194
1266
  this.setState({ isPlaying: false });
1195
1267
  if (onPause && shouldCallOnPause) {
1196
1268
  onPause(currentIndex);
@@ -1212,14 +1284,28 @@ var ImageGallery = function (_React$Component) {
1212
1284
  this.loadedImages[item.original] = true;
1213
1285
  return false;
1214
1286
  }
1287
+ }, {
1288
+ key: 'handleImageLoaded',
1289
+ value: function handleImageLoaded(event, item) {
1290
+ var onImageLoad = this.props.onImageLoad;
1291
+
1292
+ var imageExists = this.loadedImages[item.original];
1293
+ if (!imageExists && onImageLoad) {
1294
+ this.loadedImages[item.original] = true; // prevent from call again
1295
+ // image just loaded, call onImageLoad
1296
+ onImageLoad(event);
1297
+ }
1298
+ }
1215
1299
  }, {
1216
1300
  key: 'renderItem',
1217
1301
  value: function renderItem(item) {
1218
- var _props15 = this.props,
1219
- onImageError = _props15.onImageError,
1220
- onImageLoad = _props15.onImageLoad;
1302
+ var _this10 = this;
1303
+
1304
+ var isFullscreen = this.state.isFullscreen;
1305
+ var onImageError = this.props.onImageError;
1221
1306
 
1222
1307
  var handleImageError = onImageError || this.handleImageError;
1308
+ var itemSrc = isFullscreen ? item.fullscreen || item.original : item.original;
1223
1309
 
1224
1310
  return _react2.default.createElement(
1225
1311
  'div',
@@ -1227,12 +1313,14 @@ var ImageGallery = function (_React$Component) {
1227
1313
  item.imageSet ? _react2.default.createElement(
1228
1314
  'picture',
1229
1315
  {
1230
- onLoad: !this.isImageLoaded(item) ? onImageLoad : null,
1316
+ onLoad: function onLoad(event) {
1317
+ return _this10.handleImageLoaded(event, item);
1318
+ },
1231
1319
  onError: handleImageError
1232
1320
  },
1233
- item.imageSet.map(function (source) {
1321
+ item.imageSet.map(function (source, index) {
1234
1322
  return _react2.default.createElement('source', {
1235
- key: source.media,
1323
+ key: 'media-' + source.srcSet + '-' + index,
1236
1324
  media: source.media,
1237
1325
  srcSet: source.srcSet,
1238
1326
  type: source.type
@@ -1241,16 +1329,18 @@ var ImageGallery = function (_React$Component) {
1241
1329
  _react2.default.createElement('img', {
1242
1330
  className: 'image-gallery-image',
1243
1331
  alt: item.originalAlt,
1244
- src: item.original
1332
+ src: itemSrc
1245
1333
  })
1246
1334
  ) : _react2.default.createElement('img', {
1247
1335
  className: 'image-gallery-image',
1248
- src: item.original,
1336
+ src: itemSrc,
1249
1337
  alt: item.originalAlt,
1250
1338
  srcSet: item.srcSet,
1251
1339
  sizes: item.sizes,
1252
1340
  title: item.originalTitle,
1253
- onLoad: !this.isImageLoaded(item) ? onImageLoad : null,
1341
+ onLoad: function onLoad(event) {
1342
+ return _this10.handleImageLoaded(event, item);
1343
+ },
1254
1344
  onError: handleImageError
1255
1345
  }),
1256
1346
  item.description && _react2.default.createElement(
@@ -1287,11 +1377,11 @@ var ImageGallery = function (_React$Component) {
1287
1377
  }, {
1288
1378
  key: 'render',
1289
1379
  value: function render() {
1290
- var _state13 = this.state,
1291
- currentIndex = _state13.currentIndex,
1292
- isFullscreen = _state13.isFullscreen,
1293
- modalFullscreen = _state13.modalFullscreen,
1294
- isPlaying = _state13.isPlaying;
1380
+ var _state15 = this.state,
1381
+ currentIndex = _state15.currentIndex,
1382
+ isFullscreen = _state15.isFullscreen,
1383
+ modalFullscreen = _state15.modalFullscreen,
1384
+ isPlaying = _state15.isPlaying;
1295
1385
  var _props16 = this.props,
1296
1386
  additionalClass = _props16.additionalClass,
1297
1387
  indexSeparator = _props16.indexSeparator,
@@ -1313,7 +1403,7 @@ var ImageGallery = function (_React$Component) {
1313
1403
 
1314
1404
  var thumbnailStyle = this.getThumbnailStyle();
1315
1405
 
1316
- var _getSlideItems = this.getSlideItems(items),
1406
+ var _getSlideItems = this.getSlideItems(),
1317
1407
  slides = _getSlideItems.slides,
1318
1408
  thumbnails = _getSlideItems.thumbnails,
1319
1409
  bullets = _getSlideItems.bullets;
@@ -1328,8 +1418,8 @@ var ImageGallery = function (_React$Component) {
1328
1418
  _react2.default.Fragment,
1329
1419
  null,
1330
1420
  showNav && _react2.default.createElement(
1331
- 'span',
1332
- { key: 'navigation' },
1421
+ _react2.default.Fragment,
1422
+ null,
1333
1423
  renderLeftNav(this.slideLeft, !this.canSlideLeft()),
1334
1424
  renderRightNav(this.slideRight, !this.canSlideRight())
1335
1425
  ),
@@ -1337,7 +1427,6 @@ var ImageGallery = function (_React$Component) {
1337
1427
  _reactSwipeable.Swipeable,
1338
1428
  {
1339
1429
  className: 'image-gallery-swipe',
1340
- key: 'swipeable',
1341
1430
  delta: 0,
1342
1431
  onSwiping: this.handleSwiping,
1343
1432
  onSwiped: this.handleOnSwiped
@@ -1443,6 +1532,7 @@ ImageGallery.propTypes = {
1443
1532
  bulletOnClick: _propTypes.func,
1444
1533
  description: _propTypes.string,
1445
1534
  original: _propTypes.string.isRequired,
1535
+ fullscreen: _propTypes.string,
1446
1536
  originalAlt: _propTypes.string,
1447
1537
  originalTitle: _propTypes.string,
1448
1538
  thumbnail: _propTypes.string,
@@ -1481,6 +1571,7 @@ ImageGallery.propTypes = {
1481
1571
  swipeThreshold: _propTypes.number,
1482
1572
  swipingTransitionDuration: _propTypes.number,
1483
1573
  onSlide: _propTypes.func,
1574
+ onBeforeSlide: _propTypes.func,
1484
1575
  onScreenChange: _propTypes.func,
1485
1576
  onPause: _propTypes.func,
1486
1577
  onPlay: _propTypes.func,
@@ -1533,6 +1624,7 @@ ImageGallery.defaultProps = {
1533
1624
  slideDuration: 450,
1534
1625
  swipingTransitionDuration: 0,
1535
1626
  onSlide: null,
1627
+ onBeforeSlide: null,
1536
1628
  onScreenChange: null,
1537
1629
  onPause: null,
1538
1630
  onPlay: null,
package/gulpfile.js CHANGED
@@ -1,32 +1,32 @@
1
- var babel = require('gulp-babel');
2
- var browserify = require('browserify');
3
- var concat = require('gulp-concat');
4
- var connect = require('gulp-connect');
5
- var gulp = require('gulp');
6
- var livereload = require('gulp-livereload');
7
- var rename = require('gulp-rename');
8
- var sass = require('gulp-sass');
9
- var uglify = require('gulp-uglify');
10
- var cleanCSS = require('gulp-clean-css');
11
- var source = require('vinyl-source-stream');
12
- var buffer = require('vinyl-buffer');
13
- var watchify = require('watchify');
1
+ const babel = require('gulp-babel');
2
+ const browserify = require('browserify');
3
+ const concat = require('gulp-concat');
4
+ const connect = require('gulp-connect');
5
+ const gulp = require('gulp');
6
+ const livereload = require('gulp-livereload');
7
+ const rename = require('gulp-rename');
8
+ const sass = require('gulp-sass');
9
+ const uglify = require('gulp-uglify');
10
+ const cleanCSS = require('gulp-clean-css');
11
+ const source = require('vinyl-source-stream');
12
+ const buffer = require('vinyl-buffer');
13
+ const watchify = require('watchify');
14
14
 
15
- var babelOptions = {
15
+ const babelOptions = {
16
16
  plugins: ['transform-object-assign'],
17
- presets: ['es2015', 'react', 'stage-0']
17
+ presets: ['es2015', 'react', 'stage-0'],
18
18
  };
19
19
 
20
- gulp.task('server', function () {
20
+ gulp.task('server', () => {
21
21
  connect.server({
22
22
  host: '0.0.0.0',
23
23
  root: ['example', 'build', 'styles'],
24
24
  port: 8001,
25
- livereload: true
25
+ livereload: true,
26
26
  });
27
27
  });
28
28
 
29
- gulp.task('sass', function () {
29
+ gulp.task('sass', () => {
30
30
  gulp.src('./styles/scss/image-gallery.scss')
31
31
  .pipe(sass())
32
32
  .pipe(rename('image-gallery.css'))
@@ -34,26 +34,26 @@ gulp.task('sass', function () {
34
34
  .pipe(livereload());
35
35
  });
36
36
 
37
- gulp.task('scripts', function() {
37
+ gulp.task('scripts', () => {
38
38
  watchify(browserify({
39
39
  entries: './example/app.js',
40
40
  extensions: ['.jsx'],
41
- debug: true
41
+ debug: true,
42
42
  }).transform('babelify', babelOptions))
43
43
  .bundle()
44
- .on('error', err => { console.error('error is', err) })
44
+ .on('error', (err) => console.error('error is', err))
45
45
  .pipe(source('example.js'))
46
46
  .pipe(buffer())
47
47
  .pipe(gulp.dest('./example/'))
48
48
  .pipe(livereload());
49
49
  });
50
50
 
51
- gulp.task('demo-src', function() {
51
+ gulp.task('demo-src', () => {
52
52
  process.env.NODE_ENV = 'production';
53
53
  browserify({
54
54
  entries: './example/app.js',
55
55
  extensions: ['.jsx'],
56
- debug: true
56
+ debug: true,
57
57
  }).transform('babelify', babelOptions)
58
58
  .bundle()
59
59
  .pipe(source('demo.js'))
@@ -63,26 +63,25 @@ gulp.task('demo-src', function() {
63
63
 
64
64
  gulp.src(['./styles/css/image-gallery.css', './example/app.css'])
65
65
  .pipe(concat('demo.css'))
66
- .pipe(cleanCSS({keepSpecialComments: false}))
66
+ .pipe(cleanCSS({ keepSpecialComments: false }))
67
67
  .pipe(gulp.dest('./demo/'));
68
68
  });
69
69
 
70
- gulp.task('source-js', function () {
71
- return gulp.src('./src/ImageGallery.jsx')
70
+ gulp.task('source-js', () => (
71
+ gulp.src('./src/ImageGallery.jsx')
72
72
  .pipe(concat('image-gallery.js'))
73
73
  .pipe(babel(babelOptions))
74
- .pipe(gulp.dest('./build'));
75
- });
74
+ .pipe(gulp.dest('./build'))
75
+ ));
76
76
 
77
- // todo fix this to do it in on task
78
- gulp.task('svg-js', function () {
79
- return gulp.src('./src/SVG.jsx')
77
+ gulp.task('svg-js', () => (
78
+ gulp.src('./src/SVG.jsx')
80
79
  .pipe(concat('SVG.js'))
81
80
  .pipe(babel(babelOptions))
82
- .pipe(gulp.dest('./build'));
83
- });
81
+ .pipe(gulp.dest('./build'))
82
+ ));
84
83
 
85
- gulp.task('watch', function() {
84
+ gulp.task('watch', () => {
86
85
  livereload.listen();
87
86
  gulp.watch(['styles/**/*.scss'], ['sass']);
88
87
  gulp.watch(['src/*.jsx', 'src/icons/*.jsx', 'example/app.js'], ['scripts']);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-image-gallery",
3
- "version": "1.0.3",
3
+ "version": "1.0.7",
4
4
  "description": "React carousel image gallery component with thumbnail and mobile support",
5
5
  "main": "./build/image-gallery.js",
6
6
  "scripts": {
@@ -42,6 +42,7 @@ export default class ImageGallery extends React.Component {
42
42
  bulletOnClick: func,
43
43
  description: string,
44
44
  original: string.isRequired,
45
+ fullscreen: string,
45
46
  originalAlt: string,
46
47
  originalTitle: string,
47
48
  thumbnail: string,
@@ -80,6 +81,7 @@ export default class ImageGallery extends React.Component {
80
81
  swipeThreshold: number,
81
82
  swipingTransitionDuration: number,
82
83
  onSlide: func,
84
+ onBeforeSlide: func,
83
85
  onScreenChange: func,
84
86
  onPause: func,
85
87
  onPlay: func,
@@ -133,6 +135,7 @@ export default class ImageGallery extends React.Component {
133
135
  slideDuration: 450,
134
136
  swipingTransitionDuration: 0,
135
137
  onSlide: null,
138
+ onBeforeSlide: null,
136
139
  onScreenChange: null,
137
140
  onPause: null,
138
141
  onPlay: null,
@@ -201,7 +204,7 @@ export default class ImageGallery extends React.Component {
201
204
  this.state = {
202
205
  currentIndex: props.startIndex,
203
206
  thumbsTranslate: 0,
204
- offsetPercentage: 0,
207
+ currentSlideOffset: 0,
205
208
  galleryWidth: 0,
206
209
  thumbnailsWrapperWidth: 0,
207
210
  thumbnailsWrapperHeight: 0,
@@ -221,6 +224,7 @@ export default class ImageGallery extends React.Component {
221
224
  this.handleScreenChange = this.handleScreenChange.bind(this);
222
225
  this.handleSwiping = this.handleSwiping.bind(this);
223
226
  this.onThumbnailMouseLeave = this.onThumbnailMouseLeave.bind(this);
227
+ this.handleImageError = this.handleImageError.bind(this);
224
228
  this.pauseOrPlay = this.pauseOrPlay.bind(this);
225
229
  this.renderThumbInner = this.renderThumbInner.bind(this);
226
230
  this.renderItem = this.renderItem.bind(this);
@@ -258,12 +262,14 @@ export default class ImageGallery extends React.Component {
258
262
  slideDuration,
259
263
  startIndex,
260
264
  thumbnailPosition,
265
+ showThumbnails,
261
266
  } = this.props;
262
267
  const { currentIndex } = this.state;
263
268
  const itemsSizeChanged = prevProps.items.length !== items.length;
264
269
  const itemsChanged = !isEqual(prevProps.items, items);
265
270
  const startIndexUpdated = prevProps.startIndex !== startIndex;
266
271
  const thumbnailsPositionChanged = prevProps.thumbnailPosition !== thumbnailPosition;
272
+ const showThumbnailsChanged = prevProps.showThumbnails !== showThumbnails;
267
273
 
268
274
  if (thumbnailsPositionChanged) {
269
275
  // re-initialize resizeObserver because slides was unmounted and mounted again
@@ -271,7 +277,7 @@ export default class ImageGallery extends React.Component {
271
277
  this.initResizeObserver(this.imageGallerySlideWrapper);
272
278
  }
273
279
 
274
- if (itemsSizeChanged) {
280
+ if (itemsSizeChanged || showThumbnailsChanged) {
275
281
  this.handleResize();
276
282
  }
277
283
  if (prevState.currentIndex !== currentIndex) {
@@ -299,9 +305,9 @@ export default class ImageGallery extends React.Component {
299
305
  window.removeEventListener('mousedown', this.handleMouseDown);
300
306
  this.removeScreenChangeEvent();
301
307
  this.removeResizeObserver();
302
- if (this.intervalId) {
303
- window.clearInterval(this.intervalId);
304
- this.intervalId = null;
308
+ if (this.playPauseIntervalId) {
309
+ window.clearInterval(this.playPauseIntervalId);
310
+ this.playPauseIntervalId = null;
305
311
  }
306
312
  if (this.transitionTimer) {
307
313
  window.clearTimeout(this.transitionTimer);
@@ -442,37 +448,47 @@ export default class ImageGallery extends React.Component {
442
448
 
443
449
  getTranslateXForTwoSlide(index) {
444
450
  // For taking care of infinite swipe when there are only two slides
445
- const { currentIndex, offsetPercentage, previousIndex } = this.state;
451
+ const { currentIndex, currentSlideOffset, previousIndex } = this.state;
452
+ const indexChanged = currentIndex !== previousIndex;
453
+ const firstSlideWasPrevSlide = index === 0 && previousIndex === 0;
454
+ const secondSlideWasPrevSlide = index === 1 && previousIndex === 1;
455
+ const firstSlideIsNextSlide = index === 0 && currentIndex === 1;
456
+ const secondSlideIsNextSlide = index === 1 && currentIndex === 0;
457
+ const swipingEnded = currentSlideOffset === 0;
446
458
  const baseTranslateX = -100 * currentIndex;
447
- let translateX = baseTranslateX + (index * 100) + offsetPercentage;
459
+ let translateX = baseTranslateX + (index * 100) + currentSlideOffset;
448
460
 
449
461
  // keep track of user swiping direction
450
- if (offsetPercentage > 0) {
462
+ // important to understand how to translateX based on last direction
463
+ if (currentSlideOffset > 0) {
451
464
  this.direction = 'left';
452
- } else if (offsetPercentage < 0) {
465
+ } else if (currentSlideOffset < 0) {
453
466
  this.direction = 'right';
454
467
  }
455
468
 
456
- // when swiping make sure the slides are on the correct side
457
- if (currentIndex === 0 && index === 1 && offsetPercentage > 0) {
458
- translateX = -100 + offsetPercentage;
459
- } else if (currentIndex === 1 && index === 0 && offsetPercentage < 0) {
460
- translateX = 100 + offsetPercentage;
469
+
470
+ // when swiping between two slides make sure the next and prev slides
471
+ // are on both left and right
472
+ if (secondSlideIsNextSlide && currentSlideOffset > 0) { // swiping right
473
+ translateX = -100 + currentSlideOffset;
474
+ }
475
+ if (firstSlideIsNextSlide && currentSlideOffset < 0) { // swiping left
476
+ translateX = 100 + currentSlideOffset;
461
477
  }
462
478
 
463
- if (currentIndex !== previousIndex) {
464
- // when swiped move the slide to the correct side
465
- if (previousIndex === 0 && index === 0 && offsetPercentage === 0 && this.direction === 'left') {
479
+ if (indexChanged) {
480
+ // when indexChanged move the slide to the correct side
481
+ if (firstSlideWasPrevSlide && swipingEnded && this.direction === 'left') {
466
482
  translateX = 100;
467
- } else if (previousIndex === 1 && index === 1 && offsetPercentage === 0 && this.direction === 'right') {
483
+ } else if (secondSlideWasPrevSlide && swipingEnded && this.direction === 'right') {
468
484
  translateX = -100;
469
485
  }
470
486
  } else {
471
- // keep the slide on the correct slide even when not a swipe
472
- if (currentIndex === 0 && index === 1 && offsetPercentage === 0 && this.direction === 'left') {
487
+ // keep the slide on the correct side if the swipe was not successful
488
+ if (secondSlideIsNextSlide && swipingEnded && this.direction === 'left') {
473
489
  translateX = -100;
474
490
  }
475
- if (currentIndex === 1 && index === 0 && offsetPercentage === 0 && this.direction === 'right') {
491
+ if (firstSlideIsNextSlide && swipingEnded && this.direction === 'right') {
476
492
  translateX = 100;
477
493
  }
478
494
  }
@@ -489,7 +505,7 @@ export default class ImageGallery extends React.Component {
489
505
  }
490
506
 
491
507
  getSlideStyle(index) {
492
- const { currentIndex, offsetPercentage, slideStyle } = this.state;
508
+ const { currentIndex, currentSlideOffset, slideStyle } = this.state;
493
509
  const {
494
510
  infinite,
495
511
  items,
@@ -501,17 +517,17 @@ export default class ImageGallery extends React.Component {
501
517
 
502
518
  // calculates where the other slides belong based on currentIndex
503
519
  // if it is RTL the base line should be reversed
504
- let translateX = (baseTranslateX + (index * 100)) * (isRTL ? -1 : 1) + offsetPercentage;
520
+ let translateX = (baseTranslateX + (index * 100)) * (isRTL ? -1 : 1) + currentSlideOffset;
505
521
 
506
522
  if (infinite && items.length > 2) {
507
523
  if (currentIndex === 0 && index === totalSlides) {
508
524
  // make the last slide the slide before the first
509
525
  // if it is RTL the base line should be reversed
510
- translateX = -100 * (isRTL ? -1 : 1) + offsetPercentage;
526
+ translateX = -100 * (isRTL ? -1 : 1) + currentSlideOffset;
511
527
  } else if (currentIndex === totalSlides && index === 0) {
512
528
  // make the first slide the slide after the last
513
529
  // if it is RTL the base line should be reversed
514
- translateX = 100 * (isRTL ? -1 : 1) + offsetPercentage;
530
+ translateX = 100 * (isRTL ? -1 : 1) + currentSlideOffset;
515
531
  }
516
532
  }
517
533
 
@@ -566,10 +582,11 @@ export default class ImageGallery extends React.Component {
566
582
  };
567
583
  }
568
584
 
569
- getSlideItems(items) {
585
+ getSlideItems() {
570
586
  const { currentIndex } = this.state;
571
587
  const {
572
588
  infinite,
589
+ items,
573
590
  slideOnThumbnailOver,
574
591
  onClick,
575
592
  lazyLoad,
@@ -605,7 +622,7 @@ export default class ImageGallery extends React.Component {
605
622
 
606
623
  const slide = (
607
624
  <div
608
- key={`slide-${item.original}`}
625
+ key={`slide-${item.original}-${index}`}
609
626
  tabIndex="-1"
610
627
  className={`image-gallery-slide ${alignment} ${originalClass}`}
611
628
  style={slideStyle}
@@ -640,7 +657,7 @@ export default class ImageGallery extends React.Component {
640
657
  );
641
658
  thumbnails.push(
642
659
  <button
643
- key={`thumbnail-${item.original}`}
660
+ key={`thumbnail-${item.original}-${index}`}
644
661
  type="button"
645
662
  tabIndex="0"
646
663
  aria-pressed={currentIndex === index ? 'true' : 'false'}
@@ -673,7 +690,7 @@ export default class ImageGallery extends React.Component {
673
690
  bullets.push(
674
691
  <button
675
692
  type="button"
676
- key={`bullet-${item.original}`}
693
+ key={`bullet-${item.original}-${index}`}
677
694
  className={igBulletClass}
678
695
  onClick={bulletOnClick}
679
696
  aria-pressed={currentIndex === index ? 'true' : 'false'}
@@ -806,9 +823,9 @@ export default class ImageGallery extends React.Component {
806
823
  if (!isTransitioning && !scrollingUpDown) {
807
824
  const side = dir === RIGHT ? 1 : -1;
808
825
 
809
- let offsetPercentage = (absX / galleryWidth * 100);
810
- if (Math.abs(offsetPercentage) >= 100) {
811
- offsetPercentage = 100;
826
+ let currentSlideOffset = (absX / galleryWidth * 100);
827
+ if (Math.abs(currentSlideOffset) >= 100) {
828
+ currentSlideOffset = 100;
812
829
  }
813
830
 
814
831
  const swipingTransition = {
@@ -816,19 +833,19 @@ export default class ImageGallery extends React.Component {
816
833
  };
817
834
 
818
835
  this.setState({
819
- offsetPercentage: side * offsetPercentage,
836
+ currentSlideOffset: side * currentSlideOffset,
820
837
  slideStyle: swipingTransition,
821
838
  });
822
839
  } else {
823
840
  // don't move the slide
824
- this.setState({ offsetPercentage: 0 });
841
+ this.setState({ currentSlideOffset: 0 });
825
842
  }
826
843
  }
827
844
 
828
845
  sufficientSwipe() {
829
- const { offsetPercentage } = this.state;
846
+ const { currentSlideOffset } = this.state;
830
847
  const { swipeThreshold } = this.props;
831
- return Math.abs(offsetPercentage) > swipeThreshold;
848
+ return Math.abs(currentSlideOffset) > swipeThreshold;
832
849
  }
833
850
 
834
851
  handleOnSwiped({ event, dir, velocity }) {
@@ -894,13 +911,13 @@ export default class ImageGallery extends React.Component {
894
911
 
895
912
  switch (key) {
896
913
  case LEFT_ARROW:
897
- if (this.canSlideLeft() && !this.intervalId) {
898
- this.slideLeft();
914
+ if (this.canSlideLeft() && !this.playPauseIntervalId) {
915
+ this.slideLeft(event);
899
916
  }
900
917
  break;
901
918
  case RIGHT_ARROW:
902
- if (this.canSlideRight() && !this.intervalId) {
903
- this.slideRight();
919
+ if (this.canSlideRight() && !this.playPauseIntervalId) {
920
+ this.slideRight(event);
904
921
  }
905
922
  break;
906
923
  case ESC_KEY:
@@ -973,7 +990,7 @@ export default class ImageGallery extends React.Component {
973
990
  }
974
991
 
975
992
  togglePlay() {
976
- if (this.intervalId) {
993
+ if (this.playPauseIntervalId) {
977
994
  this.pause();
978
995
  } else {
979
996
  this.play();
@@ -985,23 +1002,25 @@ export default class ImageGallery extends React.Component {
985
1002
  /*
986
1003
  handles screen change events that the browser triggers e.g. esc key
987
1004
  */
988
- const { onScreenChange } = this.props;
1005
+ const { onScreenChange, useBrowserFullscreen } = this.props;
989
1006
  const fullScreenElement = document.fullscreenElement
990
1007
  || document.msFullscreenElement
991
1008
  || document.mozFullScreenElement
992
1009
  || document.webkitFullscreenElement;
993
1010
 
994
- if (onScreenChange) onScreenChange(fullScreenElement);
995
- this.setState({ isFullscreen: !!fullScreenElement });
1011
+ // check if screenchange element is the gallery
1012
+ const isFullscreen = this.imageGallery.current === fullScreenElement;
1013
+ if (onScreenChange) onScreenChange(isFullscreen);
1014
+ if (useBrowserFullscreen) this.setState({ isFullscreen });
996
1015
  }
997
1016
 
998
1017
  slideToIndex(index, event) {
999
1018
  const { currentIndex, isTransitioning } = this.state;
1000
- const { items, slideDuration } = this.props;
1019
+ const { items, slideDuration, onBeforeSlide } = this.props;
1001
1020
 
1002
1021
  if (!isTransitioning) {
1003
1022
  if (event) {
1004
- if (this.intervalId) {
1023
+ if (this.playPauseIntervalId) {
1005
1024
  // user triggered event while ImageGallery is playing, reset interval
1006
1025
  this.pause(false);
1007
1026
  this.play(false);
@@ -1016,42 +1035,80 @@ export default class ImageGallery extends React.Component {
1016
1035
  nextIndex = 0;
1017
1036
  }
1018
1037
 
1038
+ if (onBeforeSlide && nextIndex !== currentIndex) {
1039
+ onBeforeSlide(nextIndex);
1040
+ }
1041
+
1019
1042
  this.setState({
1020
1043
  previousIndex: currentIndex,
1021
1044
  currentIndex: nextIndex,
1022
1045
  isTransitioning: nextIndex !== currentIndex,
1023
- offsetPercentage: 0,
1046
+ currentSlideOffset: 0,
1024
1047
  slideStyle: { transition: `all ${slideDuration}ms ease-out` },
1025
1048
  }, this.onSliding);
1026
1049
  }
1027
1050
  }
1028
1051
 
1029
- slideLeft() {
1052
+ slideLeft(event) {
1030
1053
  const { isRTL } = this.props;
1031
1054
  if (isRTL) {
1032
- this.slideNext();
1055
+ this.slideNext(event);
1033
1056
  } else {
1034
- this.slidePrevious();
1057
+ this.slidePrevious(event);
1035
1058
  }
1036
1059
  }
1037
1060
 
1038
- slideRight() {
1061
+ slideRight(event) {
1039
1062
  const { isRTL } = this.props;
1040
1063
  if (isRTL) {
1041
- this.slidePrevious();
1064
+ this.slidePrevious(event);
1042
1065
  } else {
1043
- this.slideNext();
1066
+ this.slideNext(event);
1044
1067
  }
1045
1068
  }
1046
1069
 
1047
1070
  slidePrevious(event) {
1048
- const { currentIndex } = this.state;
1049
- this.slideToIndex(currentIndex - 1, event);
1071
+ const { currentIndex, currentSlideOffset, isTransitioning } = this.state;
1072
+ const { items } = this.props;
1073
+ const nextIndex = currentIndex - 1;
1074
+
1075
+ if (isTransitioning) return;
1076
+
1077
+ if (items.length === 2) {
1078
+ /*
1079
+ When there are only 2 slides fake a tiny swipe to get the slides
1080
+ on the correct side for transitioning
1081
+ */
1082
+ this.setState({
1083
+ currentSlideOffset: currentSlideOffset + 0.001, // this will reset once index changes
1084
+ slideStyle: { transition: 'none' }, // move the slide over instantly
1085
+ }, () => {
1086
+ // add 25ms timeout to avoid delay in moving slides over
1087
+ window.setTimeout(() => this.slideToIndex(nextIndex, event), 25);
1088
+ });
1089
+ } else {
1090
+ this.slideToIndex(nextIndex, event);
1091
+ }
1050
1092
  }
1051
1093
 
1052
1094
  slideNext(event) {
1053
- const { currentIndex } = this.state;
1054
- this.slideToIndex(currentIndex + 1, event);
1095
+ const { currentIndex, currentSlideOffset, isTransitioning } = this.state;
1096
+ const { items } = this.props;
1097
+ const nextIndex = currentIndex + 1;
1098
+
1099
+ if (isTransitioning) return;
1100
+
1101
+ if (items.length === 2) {
1102
+ // same as above for 2 slides
1103
+ this.setState({
1104
+ currentSlideOffset: currentSlideOffset - 0.001,
1105
+ slideStyle: { transition: 'none' },
1106
+ }, () => {
1107
+ window.setTimeout(() => this.slideToIndex(nextIndex, event), 25);
1108
+ });
1109
+ } else {
1110
+ this.slideToIndex(nextIndex, event);
1111
+ }
1055
1112
  }
1056
1113
 
1057
1114
  handleThumbnailMouseOver(event, index) {
@@ -1152,9 +1209,9 @@ export default class ImageGallery extends React.Component {
1152
1209
  slideDuration,
1153
1210
  } = this.props;
1154
1211
  const { currentIndex } = this.state;
1155
- if (!this.intervalId) {
1212
+ if (!this.playPauseIntervalId) {
1156
1213
  this.setState({ isPlaying: true });
1157
- this.intervalId = window.setInterval(
1214
+ this.playPauseIntervalId = window.setInterval(
1158
1215
  this.pauseOrPlay,
1159
1216
  Math.max(slideInterval, slideDuration),
1160
1217
  );
@@ -1167,9 +1224,9 @@ export default class ImageGallery extends React.Component {
1167
1224
  pause(shouldCallOnPause = true) {
1168
1225
  const { onPause } = this.props;
1169
1226
  const { currentIndex } = this.state;
1170
- if (this.intervalId) {
1171
- window.clearInterval(this.intervalId);
1172
- this.intervalId = null;
1227
+ if (this.playPauseIntervalId) {
1228
+ window.clearInterval(this.playPauseIntervalId);
1229
+ this.playPauseIntervalId = null;
1173
1230
  this.setState({ isPlaying: false });
1174
1231
  if (onPause && shouldCallOnPause) {
1175
1232
  onPause(currentIndex);
@@ -1191,22 +1248,34 @@ export default class ImageGallery extends React.Component {
1191
1248
  return false;
1192
1249
  }
1193
1250
 
1251
+ handleImageLoaded(event, item) {
1252
+ const { onImageLoad } = this.props;
1253
+ const imageExists = this.loadedImages[item.original];
1254
+ if (!imageExists && onImageLoad) {
1255
+ this.loadedImages[item.original] = true; // prevent from call again
1256
+ // image just loaded, call onImageLoad
1257
+ onImageLoad(event);
1258
+ }
1259
+ }
1260
+
1194
1261
  renderItem(item) {
1195
- const { onImageError, onImageLoad } = this.props;
1262
+ const { isFullscreen } = this.state;
1263
+ const { onImageError } = this.props;
1196
1264
  const handleImageError = onImageError || this.handleImageError;
1265
+ const itemSrc = isFullscreen ? (item.fullscreen || item.original) : item.original;
1197
1266
 
1198
1267
  return (
1199
1268
  <div>
1200
1269
  {
1201
1270
  item.imageSet ? (
1202
1271
  <picture
1203
- onLoad={!this.isImageLoaded(item) ? onImageLoad : null}
1272
+ onLoad={event => this.handleImageLoaded(event, item)}
1204
1273
  onError={handleImageError}
1205
1274
  >
1206
1275
  {
1207
- item.imageSet.map(source => (
1276
+ item.imageSet.map((source, index) => (
1208
1277
  <source
1209
- key={source.media}
1278
+ key={`media-${source.srcSet}-${index}`}
1210
1279
  media={source.media}
1211
1280
  srcSet={source.srcSet}
1212
1281
  type={source.type}
@@ -1216,18 +1285,18 @@ export default class ImageGallery extends React.Component {
1216
1285
  <img
1217
1286
  className="image-gallery-image"
1218
1287
  alt={item.originalAlt}
1219
- src={item.original}
1288
+ src={itemSrc}
1220
1289
  />
1221
1290
  </picture>
1222
1291
  ) : (
1223
1292
  <img
1224
1293
  className="image-gallery-image"
1225
- src={item.original}
1294
+ src={itemSrc}
1226
1295
  alt={item.originalAlt}
1227
1296
  srcSet={item.srcSet}
1228
1297
  sizes={item.sizes}
1229
1298
  title={item.originalTitle}
1230
- onLoad={!this.isImageLoaded(item) ? onImageLoad : null}
1299
+ onLoad={event => this.handleImageLoaded(event, item)}
1231
1300
  onError={handleImageError}
1232
1301
  />
1233
1302
  )
@@ -1296,7 +1365,7 @@ export default class ImageGallery extends React.Component {
1296
1365
  } = this.props;
1297
1366
 
1298
1367
  const thumbnailStyle = this.getThumbnailStyle();
1299
- const { slides, thumbnails, bullets } = this.getSlideItems(items);
1368
+ const { slides, thumbnails, bullets } = this.getSlideItems();
1300
1369
  const slideWrapperClass = clsx(
1301
1370
  'image-gallery-slide-wrapper',
1302
1371
  thumbnailPosition,
@@ -1311,15 +1380,14 @@ export default class ImageGallery extends React.Component {
1311
1380
  <React.Fragment>
1312
1381
  {
1313
1382
  showNav && (
1314
- <span key="navigation">
1383
+ <React.Fragment>
1315
1384
  {renderLeftNav(this.slideLeft, !this.canSlideLeft())}
1316
1385
  {renderRightNav(this.slideRight, !this.canSlideRight())}
1317
- </span>
1386
+ </React.Fragment>
1318
1387
  )
1319
1388
  }
1320
1389
  <Swipeable
1321
1390
  className="image-gallery-swipe"
1322
- key="swipeable"
1323
1391
  delta={0}
1324
1392
  onSwiping={this.handleSwiping}
1325
1393
  onSwiped={this.handleOnSwiped}