viewerjs 1.12.0 → 1.14.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,11 +1,11 @@
1
1
  /*!
2
- * Viewer.js v1.12.0
2
+ * Viewer.js v1.14.0
3
3
  * https://fengyuanchen.github.io/viewerjs
4
4
  *
5
5
  * Copyright 2015-present Chen Fengyuan
6
6
  * Released under the MIT license
7
7
  *
8
- * Date: 2026-08-22T13:00:39.652Z
8
+ * Date: 2026-09-12T12:12:29.730Z
9
9
  */
10
10
 
11
11
  function _classCallCheck(a, n) {
@@ -89,9 +89,14 @@ var DEFAULTS = {
89
89
  button: true,
90
90
  /**
91
91
  * Show the navbar.
92
- * @type {boolean | number}
92
+ * @type {boolean | number | string | Object}
93
93
  */
94
94
  navbar: true,
95
+ /**
96
+ * Show the navigation buttons.
97
+ * @type {boolean | number | Object}
98
+ */
99
+ navigation: false,
95
100
  /**
96
101
  * Specify the visibility and the content of the title.
97
102
  * @type {boolean | number | Function | Array}
@@ -143,6 +148,11 @@ var DEFAULTS = {
143
148
  * @type {boolean}
144
149
  */
145
150
  inline: false,
151
+ /**
152
+ * Enable to automatically play the images when playing.
153
+ * @type {boolean}
154
+ */
155
+ autoplay: true,
146
156
  /**
147
157
  * The amount of time to delay between automatically cycling an image when playing.
148
158
  * @type {number}
@@ -168,6 +178,11 @@ var DEFAULTS = {
168
178
  * @type {boolean}
169
179
  */
170
180
  loop: true,
181
+ /**
182
+ * Enable to preload the next or previous image before viewing it.
183
+ * @type {boolean}
184
+ */
185
+ preload: true,
171
186
  /**
172
187
  * Min width of the viewer in inline mode.
173
188
  * @type {number}
@@ -183,11 +198,26 @@ var DEFAULTS = {
183
198
  * @type {boolean}
184
199
  */
185
200
  movable: true,
201
+ /**
202
+ * Show a magnifier when hovering over the image in fullscreen mode.
203
+ * @type {boolean | Object}
204
+ */
205
+ magnifier: false,
186
206
  /**
187
207
  * Enable to rotate the image.
188
208
  * @type {boolean}
189
209
  */
190
210
  rotatable: true,
211
+ /**
212
+ * Enable to rotate the current image by gesture.
213
+ * @type {boolean}
214
+ */
215
+ rotateOnGesture: true,
216
+ /**
217
+ * Enable to rotate the current image by dragging on the touch screen.
218
+ * @type {boolean}
219
+ */
220
+ rotateOnTouch: true,
191
221
  /**
192
222
  * Enable to scale the image.
193
223
  * @type {boolean}
@@ -204,15 +234,33 @@ var DEFAULTS = {
204
234
  */
205
235
  zoomOnTouch: true,
206
236
  /**
207
- * Enable to zoom the image by wheeling mouse.
237
+ * Enable to zoom the current image by gesture.
208
238
  * @type {boolean}
209
239
  */
240
+ zoomOnGesture: true,
241
+ /**
242
+ * Enable to zoom the image by wheeling mouse.
243
+ * Set to a modifier key name (`ctrl`, `shift`, `alt`, `meta`), or a
244
+ * combination joined with `+` (e.g. `ctrl+shift`), to only zoom when
245
+ * the given modifier key(s) are held down while wheeling.
246
+ * @type {boolean | string}
247
+ */
210
248
  zoomOnWheel: true,
211
249
  /**
212
250
  * Enable to slide to the next or previous image by swiping on the touch screen.
213
251
  * @type {boolean}
214
252
  */
215
253
  slideOnTouch: true,
254
+ /**
255
+ * Enable to slide to the next or previous image by wheeling mouse.
256
+ * Set to a modifier key name (`ctrl`, `shift`, `alt`, `meta`), or a
257
+ * combination joined with `+` (e.g. `ctrl+shift`), to only slide when
258
+ * the given modifier key(s) are held down while wheeling.
259
+ * Takes effect over the navbar, and also over the rest of the viewer
260
+ * when `zoomOnWheel` doesn't take effect for the wheel event.
261
+ * @type {boolean | string}
262
+ */
263
+ slideOnWheel: true,
216
264
  /**
217
265
  * Indicate if toggle the image size between its natural size
218
266
  * and initial size when double click on the image or not.
@@ -226,7 +274,7 @@ var DEFAULTS = {
226
274
  tooltip: true,
227
275
  /**
228
276
  * Enable CSS3 Transition for some special elements.
229
- * @type {boolean}
277
+ * @type {boolean | Object}
230
278
  */
231
279
  transition: true,
232
280
  /**
@@ -246,12 +294,12 @@ var DEFAULTS = {
246
294
  zoomRatio: 0.1,
247
295
  /**
248
296
  * Define the min ratio of the image when zoom out.
249
- * @type {number}
297
+ * @type {number | Function}
250
298
  */
251
299
  minZoomRatio: 0.01,
252
300
  /**
253
301
  * Define the max ratio of the image when zoom in.
254
- * @type {number}
302
+ * @type {number | Function}
255
303
  */
256
304
  maxZoomRatio: 100,
257
305
  /**
@@ -279,10 +327,11 @@ var DEFAULTS = {
279
327
  zoom: null,
280
328
  zoomed: null,
281
329
  play: null,
330
+ playing: null,
282
331
  stop: null
283
332
  };
284
333
 
285
- var TEMPLATE = '<div class="viewer-container" tabindex="-1" touch-action="none">' + '<div class="viewer-canvas"></div>' + '<div class="viewer-footer">' + '<div class="viewer-title"></div>' + '<div class="viewer-toolbar"></div>' + '<div class="viewer-navbar">' + '<ul class="viewer-list" role="navigation"></ul>' + '</div>' + '</div>' + '<div class="viewer-tooltip" role="alert" aria-hidden="true"></div>' + '<div class="viewer-button" data-viewer-action="mix" role="button"></div>' + '<div class="viewer-player"></div>' + '</div>';
334
+ var TEMPLATE = '<div class="viewer-container" tabindex="-1" touch-action="none">' + '<div class="viewer-canvas"></div>' + '<div class="viewer-magnifier" aria-hidden="true">' + '<img class="viewer-magnifier-image" alt="">' + '</div>' + '<div class="viewer-navigation" aria-hidden="true">' + '<div class="viewer-prev" data-viewer-action="prev" role="button" aria-label="Previous"></div>' + '<div class="viewer-next" data-viewer-action="next" role="button" aria-label="Next"></div>' + '</div>' + '<div class="viewer-footer" aria-hidden="true">' + '<div class="viewer-title" aria-hidden="true"></div>' + '<div class="viewer-toolbar" aria-hidden="true"></div>' + '<div class="viewer-navbar" aria-hidden="true">' + '<ul class="viewer-list" role="navigation"></ul>' + '</div>' + '</div>' + '<div class="viewer-tooltip" role="alert" aria-hidden="true"></div>' + '<div class="viewer-button" data-viewer-action="mix" role="button" aria-hidden="true"></div>' + '<div class="viewer-player" aria-hidden="true"></div>' + '</div>';
286
335
 
287
336
  var IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';
288
337
  var WINDOW = IS_BROWSER ? window : {};
@@ -292,7 +341,9 @@ var NAMESPACE = 'viewer';
292
341
 
293
342
  // Actions
294
343
  var ACTION_MOVE = 'move';
344
+ var ACTION_ROTATE = 'rotate';
295
345
  var ACTION_SWITCH = 'switch';
346
+ var ACTION_TRANSFORM = 'transform';
296
347
  var ACTION_ZOOM = 'zoom';
297
348
 
298
349
  // Classes
@@ -326,11 +377,14 @@ var EVENT_TOUCH_END = IS_TOUCH_DEVICE ? 'touchend touchcancel' : 'mouseup';
326
377
  var EVENT_TOUCH_MOVE = IS_TOUCH_DEVICE ? 'touchmove' : 'mousemove';
327
378
  var EVENT_TOUCH_START = IS_TOUCH_DEVICE ? 'touchstart' : 'mousedown';
328
379
  var EVENT_POINTER_DOWN = HAS_POINTER_EVENT ? 'pointerdown' : EVENT_TOUCH_START;
380
+ var EVENT_POINTER_ENTER = HAS_POINTER_EVENT ? 'pointerenter' : 'mouseenter';
381
+ var EVENT_POINTER_LEAVE = HAS_POINTER_EVENT ? 'pointerleave' : 'mouseleave';
329
382
  var EVENT_POINTER_MOVE = HAS_POINTER_EVENT ? 'pointermove' : EVENT_TOUCH_MOVE;
330
383
  var EVENT_POINTER_UP = HAS_POINTER_EVENT ? 'pointerup pointercancel' : EVENT_TOUCH_END;
331
384
  var EVENT_RESIZE = 'resize';
332
385
  var EVENT_TRANSITION_END = 'transitionend';
333
386
  var EVENT_WHEEL = 'wheel';
387
+ var EVENT_GESTURE = 'gesturestart gesturechange gestureend';
334
388
 
335
389
  // Custom events
336
390
  var EVENT_READY = 'ready';
@@ -349,6 +403,7 @@ var EVENT_SCALED = 'scaled';
349
403
  var EVENT_ZOOM = 'zoom';
350
404
  var EVENT_ZOOMED = 'zoomed';
351
405
  var EVENT_PLAY = 'play';
406
+ var EVENT_PLAYING = 'playing';
352
407
  var EVENT_STOP = 'stop';
353
408
 
354
409
  // Data keys
@@ -368,6 +423,30 @@ var BUTTONS = ['zoom-in', 'zoom-out', 'one-to-one', 'reset', 'prev', 'play', 'ne
368
423
  function isString(value) {
369
424
  return typeof value === 'string';
370
425
  }
426
+ var MODIFIER_KEY_PROPERTIES = {
427
+ ctrl: 'ctrlKey',
428
+ shift: 'shiftKey',
429
+ alt: 'altKey',
430
+ meta: 'metaKey'
431
+ };
432
+
433
+ /**
434
+ * Check if a wheel option (`zoomOnWheel` or `slideOnWheel`) takes effect for the given wheel event.
435
+ * @param {boolean | string} option - The option value.
436
+ * @param {WheelEvent} event - The wheel event.
437
+ * @returns {boolean} Returns `true` if the option takes effect for the event, else `false`.
438
+ */
439
+ function isWheelActionEnabled(option, event) {
440
+ if (!option) {
441
+ return false;
442
+ }
443
+ if (option === true) {
444
+ return true;
445
+ }
446
+ return isString(option) && option.split('+').every(function (key) {
447
+ return event[MODIFIER_KEY_PROPERTIES[key.trim().toLowerCase()]];
448
+ });
449
+ }
371
450
 
372
451
  /**
373
452
  * Check if the given value is not a number.
@@ -454,6 +533,32 @@ function forEach(data, callback) {
454
533
  return data;
455
534
  }
456
535
 
536
+ /**
537
+ * Inherit attributes from the original image.
538
+ * @param {Element} image - The target image.
539
+ * @param {Element} originalImage - The original image.
540
+ * @param {Array} inheritedAttributes - The attributes to inherit.
541
+ */
542
+ function inheritAttributes(image, originalImage, inheritedAttributes) {
543
+ forEach(inheritedAttributes, function (name) {
544
+ var value = originalImage.getAttribute(name);
545
+ if (value !== null) {
546
+ image.setAttribute(name, value);
547
+ }
548
+ });
549
+ }
550
+
551
+ /**
552
+ * Check if transition is enabled for the given action.
553
+ * @param {Object} options - The viewer options.
554
+ * @param {string} action - The transition action.
555
+ * @returns {boolean} Returns `true` if transition is enabled.
556
+ */
557
+ function isTransitionEnabled(options, action) {
558
+ var transition = options.transition;
559
+ return transition && transition[action] !== false;
560
+ }
561
+
457
562
  /**
458
563
  * Extend the given object.
459
564
  * @param {*} obj - The object to be extended.
@@ -783,10 +888,8 @@ function getTransforms(_ref) {
783
888
  if (isNumber(rotate) && rotate !== 0) {
784
889
  values.push("rotate(".concat(rotate, "deg)"));
785
890
  }
786
- if (isNumber(scaleX) && scaleX !== 1) {
891
+ if (isNumber(scaleX) && isNumber(scaleY) && (scaleX !== 1 || scaleY !== 1)) {
787
892
  values.push("scaleX(".concat(scaleX, ")"));
788
- }
789
- if (isNumber(scaleY) && scaleY !== 1) {
790
893
  values.push("scaleY(".concat(scaleY, ")"));
791
894
  }
792
895
  var transform = values.length ? values.join(' ') : 'none';
@@ -832,12 +935,7 @@ function getImageNaturalSizes(image, options, callback) {
832
935
  body.removeChild(newImage);
833
936
  }
834
937
  };
835
- forEach(options.inheritedAttributes, function (name) {
836
- var value = image.getAttribute(name);
837
- if (value !== null) {
838
- newImage.setAttribute(name, value);
839
- }
840
- });
938
+ inheritAttributes(newImage, image, options.inheritedAttributes);
841
939
  newImage.src = image.src;
842
940
 
843
941
  // iOS Safari will convert the image automatically
@@ -894,6 +992,34 @@ function getMaxZoomRatio(pointers) {
894
992
  return ratios[0];
895
993
  }
896
994
 
995
+ /**
996
+ * Get the max rotation degree of a group of pointers.
997
+ * @param {Object} pointers - The target pointers.
998
+ * @returns {number} The result degree.
999
+ */
1000
+ function getMaxRotateDegree(pointers) {
1001
+ var pointers2 = _objectSpread2({}, pointers);
1002
+ var degrees = [];
1003
+ forEach(pointers, function (pointer, pointerId) {
1004
+ delete pointers2[pointerId];
1005
+ forEach(pointers2, function (pointer2) {
1006
+ var start = Math.atan2(pointer2.startY - pointer.startY, pointer2.startX - pointer.startX);
1007
+ var end = Math.atan2(pointer2.endY - pointer.endY, pointer2.endX - pointer.endX);
1008
+ var radians = end - start;
1009
+ if (radians > Math.PI) {
1010
+ radians -= Math.PI * 2;
1011
+ } else if (radians < -Math.PI) {
1012
+ radians += Math.PI * 2;
1013
+ }
1014
+ degrees.push(radians * 180 / Math.PI);
1015
+ });
1016
+ });
1017
+ degrees.sort(function (a, b) {
1018
+ return Math.abs(b) - Math.abs(a);
1019
+ });
1020
+ return degrees[0] || 0;
1021
+ }
1022
+
897
1023
  /**
898
1024
  * Get a pointer from an event object.
899
1025
  * @param {Object} event - The target event object.
@@ -946,7 +1072,7 @@ var render = {
946
1072
  this.renderViewer();
947
1073
  },
948
1074
  initBody: function initBody() {
949
- var ownerDocument = this.element.ownerDocument;
1075
+ var ownerDocument = this.ownerDocument;
950
1076
  var body = ownerDocument.body || ownerDocument.documentElement;
951
1077
  this.body = body;
952
1078
  this.scrollbarWidth = window.innerWidth - ownerDocument.documentElement.clientWidth;
@@ -982,26 +1108,37 @@ var render = {
982
1108
  },
983
1109
  initList: function initList() {
984
1110
  var _this = this;
1111
+ var viewIndex = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.index;
985
1112
  var element = this.element,
986
1113
  options = this.options,
987
1114
  list = this.list;
988
1115
  var items = [];
1116
+ var navbarOptions = isPlainObject(options.navbar) ? options.navbar : {};
1117
+ var probe = document.createElement('li');
1118
+ if (!this.containerData) {
1119
+ this.initContainer();
1120
+ }
1121
+ list.appendChild(probe);
1122
+ var itemWidth = probe.offsetWidth + parseInt(window.getComputedStyle(probe).marginLeft, 10);
1123
+ list.removeChild(probe);
1124
+ var visibleItemCount = isNumber(navbarOptions.visibleItemCount) ? Math.floor(navbarOptions.visibleItemCount) : Math.floor(this.containerData.width / itemWidth);
1125
+ visibleItemCount = Math.min(visibleItemCount, this.length);
1126
+ var start = visibleItemCount > 0 ? Math.min(Math.max(0, viewIndex - Math.floor(visibleItemCount / 2)), Math.max(0, this.length - visibleItemCount)) : 0;
1127
+ var end = visibleItemCount > 0 ? Math.min(this.length, start + visibleItemCount) : this.length;
989
1128
 
990
1129
  // initList may be called in this.update, so should keep idempotent
991
1130
  list.innerHTML = '';
992
1131
  forEach(this.images, function (image, index) {
1132
+ if (visibleItemCount > 0 && (index < start || index >= end)) {
1133
+ return;
1134
+ }
993
1135
  var src = image.src;
994
1136
  var alt = image.alt || getImageNameFromURL(src);
995
1137
  var url = _this.getImageURL(image);
996
1138
  if (src || url) {
997
1139
  var item = document.createElement('li');
998
1140
  var img = document.createElement('img');
999
- forEach(options.inheritedAttributes, function (name) {
1000
- var value = image.getAttribute(name);
1001
- if (value !== null) {
1002
- img.setAttribute(name, value);
1003
- }
1004
- });
1141
+ inheritAttributes(img, image, options.inheritedAttributes);
1005
1142
  if (options.navbar) {
1006
1143
  img.src = src || url;
1007
1144
  }
@@ -1019,6 +1156,13 @@ var render = {
1019
1156
  }
1020
1157
  });
1021
1158
  this.items = items;
1159
+ if (this.viewed) {
1160
+ var activeItem = this.getItem(this.index);
1161
+ if (activeItem) {
1162
+ addClass(activeItem, CLASS_ACTIVE);
1163
+ activeItem.setAttribute('aria-selected', true);
1164
+ }
1165
+ }
1022
1166
  forEach(items, function (item) {
1023
1167
  var image = item.firstElementChild;
1024
1168
  var onLoad;
@@ -1045,7 +1189,7 @@ var render = {
1045
1189
  once: true
1046
1190
  });
1047
1191
  });
1048
- if (options.transition) {
1192
+ if (isTransitionEnabled(options, 'view')) {
1049
1193
  addListener(element, EVENT_VIEWED, function () {
1050
1194
  addClass(list, CLASS_TRANSITION);
1051
1195
  }, {
@@ -1053,9 +1197,20 @@ var render = {
1053
1197
  });
1054
1198
  }
1055
1199
  },
1200
+ getItem: function getItem(index) {
1201
+ var item;
1202
+ forEach(this.items, function (candidate) {
1203
+ if (Number(getData(candidate, 'index')) === index) {
1204
+ item = candidate;
1205
+ return false;
1206
+ }
1207
+ return true;
1208
+ });
1209
+ return item;
1210
+ },
1056
1211
  renderList: function renderList() {
1057
1212
  var index = this.index;
1058
- var item = this.items[index];
1213
+ var item = this.getItem(index);
1059
1214
  if (!item) {
1060
1215
  return;
1061
1216
  }
@@ -1066,9 +1221,9 @@ var render = {
1066
1221
 
1067
1222
  // Place the active item in the center of the screen
1068
1223
  setStyle(this.list, assign({
1069
- width: outerWidth * this.length - gutter
1224
+ width: outerWidth * this.items.length - gutter
1070
1225
  }, getTransforms({
1071
- translateX: (this.viewerData.width - offsetWidth) / 2 - outerWidth * index
1226
+ translateX: (this.viewerData.width - offsetWidth) / 2 - item.offsetLeft
1072
1227
  })));
1073
1228
  },
1074
1229
  resetList: function resetList() {
@@ -1154,8 +1309,23 @@ var render = {
1154
1309
  marginLeft: imageData.x,
1155
1310
  marginTop: imageData.y
1156
1311
  }, getTransforms(imageData)));
1312
+ if (this.magnifierPoint) {
1313
+ this.renderMagnifier();
1314
+ }
1157
1315
  if (done) {
1158
- if ((this.viewing || this.moving || this.rotating || this.scaling || this.zooming) && this.options.transition && hasClass(image, CLASS_TRANSITION)) {
1316
+ var action = false;
1317
+ if (this.viewing) {
1318
+ action = 'view';
1319
+ } else if (this.moving) {
1320
+ action = 'move';
1321
+ } else if (this.rotating) {
1322
+ action = 'rotate';
1323
+ } else if (this.scaling) {
1324
+ action = 'scale';
1325
+ } else if (this.zooming) {
1326
+ action = 'zoom';
1327
+ }
1328
+ if (action && isTransitionEnabled(this.options, action) && hasClass(image, CLASS_TRANSITION)) {
1159
1329
  var onTransitionEnd = function onTransitionEnd() {
1160
1330
  _this3.imageRendering = false;
1161
1331
  done();
@@ -1191,20 +1361,28 @@ var events = {
1191
1361
  var options = this.options,
1192
1362
  viewer = this.viewer,
1193
1363
  canvas = this.canvas;
1194
- var document = this.element.ownerDocument;
1364
+ var document = this.ownerDocument;
1195
1365
  addListener(viewer, EVENT_CLICK, this.onClick = this.click.bind(this));
1196
1366
  addListener(viewer, EVENT_DRAG_START, this.onDragStart = this.dragstart.bind(this));
1367
+ addListener(viewer, EVENT_POINTER_ENTER, this.onMagnifyEnter = this.magnify.bind(this));
1368
+ addListener(viewer, EVENT_POINTER_MOVE, this.onMagnify = this.magnify.bind(this));
1369
+ addListener(viewer, EVENT_POINTER_LEAVE, this.onMagnifierLeave = this.hideMagnifier.bind(this));
1197
1370
  addListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown = this.pointerdown.bind(this));
1198
1371
  addListener(document, EVENT_POINTER_MOVE, this.onPointerMove = this.pointermove.bind(this));
1199
1372
  addListener(document, EVENT_POINTER_UP, this.onPointerUp = this.pointerup.bind(this));
1200
1373
  addListener(document, EVENT_KEY_DOWN, this.onKeyDown = this.keydown.bind(this));
1201
1374
  addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this));
1202
- if (options.zoomable && options.zoomOnWheel) {
1375
+ if (options.zoomable && options.zoomOnWheel || options.slideOnWheel) {
1203
1376
  addListener(viewer, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), {
1204
1377
  passive: false,
1205
1378
  capture: true
1206
1379
  });
1207
1380
  }
1381
+ if (options.zoomable && options.zoomOnGesture || options.rotatable && options.rotateOnGesture) {
1382
+ addListener(viewer, EVENT_GESTURE, this.onGesture = this.gesture.bind(this), {
1383
+ passive: false
1384
+ });
1385
+ }
1208
1386
  if (options.toggleOnDblclick) {
1209
1387
  addListener(canvas, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this));
1210
1388
  }
@@ -1213,20 +1391,28 @@ var events = {
1213
1391
  var options = this.options,
1214
1392
  viewer = this.viewer,
1215
1393
  canvas = this.canvas;
1216
- var document = this.element.ownerDocument;
1394
+ var document = this.ownerDocument;
1217
1395
  removeListener(viewer, EVENT_CLICK, this.onClick);
1218
1396
  removeListener(viewer, EVENT_DRAG_START, this.onDragStart);
1397
+ removeListener(viewer, EVENT_POINTER_ENTER, this.onMagnifyEnter);
1398
+ removeListener(viewer, EVENT_POINTER_MOVE, this.onMagnify);
1399
+ removeListener(viewer, EVENT_POINTER_LEAVE, this.onMagnifierLeave);
1219
1400
  removeListener(canvas, EVENT_POINTER_DOWN, this.onPointerDown);
1220
1401
  removeListener(document, EVENT_POINTER_MOVE, this.onPointerMove);
1221
1402
  removeListener(document, EVENT_POINTER_UP, this.onPointerUp);
1222
1403
  removeListener(document, EVENT_KEY_DOWN, this.onKeyDown);
1223
1404
  removeListener(window, EVENT_RESIZE, this.onResize);
1224
- if (options.zoomable && options.zoomOnWheel) {
1405
+ if (options.zoomable && options.zoomOnWheel || options.slideOnWheel) {
1225
1406
  removeListener(viewer, EVENT_WHEEL, this.onWheel, {
1226
1407
  passive: false,
1227
1408
  capture: true
1228
1409
  });
1229
1410
  }
1411
+ if (options.zoomable && options.zoomOnGesture || options.rotatable && options.rotateOnGesture) {
1412
+ removeListener(viewer, EVENT_GESTURE, this.onGesture, {
1413
+ passive: false
1414
+ });
1415
+ }
1230
1416
  if (options.toggleOnDblclick) {
1231
1417
  removeListener(canvas, EVENT_DBLCLICK, this.onDblclick);
1232
1418
  }
@@ -1248,6 +1434,7 @@ var handlers = {
1248
1434
  if (IS_TOUCH_DEVICE && event.isTrusted && target === this.canvas) {
1249
1435
  clearTimeout(this.clickCanvasTimeout);
1250
1436
  }
1437
+ this.actionEvent = event;
1251
1438
  switch (action) {
1252
1439
  case 'mix':
1253
1440
  if (this.played) {
@@ -1318,7 +1505,8 @@ var handlers = {
1318
1505
  }
1319
1506
 
1320
1507
  // XXX: No pageX/Y properties in custom event, fallback to the original event.
1321
- this.toggle(event.isTrusted ? event : event.detail && event.detail.originalEvent);
1508
+ this.actionEvent = event.isTrusted ? event : event.detail && event.detail.originalEvent;
1509
+ this.toggle();
1322
1510
  }
1323
1511
  },
1324
1512
  load: function load() {
@@ -1339,10 +1527,13 @@ var handlers = {
1339
1527
  image.style.cssText = 'height:0;' + "margin-left:".concat(viewerData.width / 2, "px;") + "margin-top:".concat(viewerData.height / 2, "px;") + 'max-width:none!important;' + 'position:relative;' + 'width:0;';
1340
1528
  this.initImage(function () {
1341
1529
  toggleClass(image, CLASS_MOVE, options.movable);
1342
- toggleClass(image, CLASS_TRANSITION, options.transition);
1530
+ toggleClass(image, CLASS_TRANSITION, isTransitionEnabled(options, 'view'));
1343
1531
  _this.renderImage(function () {
1344
1532
  _this.viewed = true;
1345
1533
  _this.viewing = false;
1534
+ setTimeout(function () {
1535
+ toggleClass(image, CLASS_TRANSITION, options.transition);
1536
+ }, 300);
1346
1537
  if (isFunction(options.viewed)) {
1347
1538
  addListener(element, EVENT_VIEWED, options.viewed, {
1348
1539
  once: true
@@ -1351,10 +1542,12 @@ var handlers = {
1351
1542
  dispatchEvent(element, EVENT_VIEWED, {
1352
1543
  originalImage: _this.images[index],
1353
1544
  index: index,
1354
- image: image
1545
+ image: image,
1546
+ originalEvent: _this.viewOriginalEvent || null
1355
1547
  }, {
1356
1548
  cancelable: false
1357
1549
  });
1550
+ _this.viewOriginalEvent = null;
1358
1551
  });
1359
1552
  });
1360
1553
  },
@@ -1394,6 +1587,7 @@ var handlers = {
1394
1587
  return;
1395
1588
  }
1396
1589
  var keyCode = event.keyCode || event.which || event.charCode;
1590
+ this.actionEvent = event;
1397
1591
  switch (keyCode) {
1398
1592
  // Enter
1399
1593
  case 13:
@@ -1481,6 +1675,88 @@ var handlers = {
1481
1675
  event.preventDefault();
1482
1676
  }
1483
1677
  },
1678
+ magnify: function magnify(event) {
1679
+ if (event.changedTouches || event.pointerType && event.pointerType !== 'mouse') {
1680
+ this.hideMagnifier();
1681
+ return;
1682
+ }
1683
+ this.magnifierPoint = {
1684
+ clientX: event.clientX,
1685
+ clientY: event.clientY
1686
+ };
1687
+ this.renderMagnifier();
1688
+ },
1689
+ renderMagnifier: function renderMagnifier() {
1690
+ var options = this.options,
1691
+ imageData = this.imageData,
1692
+ magnifier = this.magnifier,
1693
+ magnifierImage = this.magnifierImage,
1694
+ viewer = this.viewer;
1695
+ var config = isPlainObject(options.magnifier) ? options.magnifier : {};
1696
+ var point = this.magnifierPoint;
1697
+ if (!options.magnifier || !this.fulled || !this.viewed || !magnifier || !magnifierImage || !point) {
1698
+ this.hideMagnifier();
1699
+ return;
1700
+ }
1701
+ var size = Math.max(1, Number(config.size) || 100);
1702
+ var zoomRatio = Math.max(1, Number(config.zoomRatio) || 2);
1703
+ var opacity = Number(config.opacity);
1704
+ var rect = viewer.getBoundingClientRect();
1705
+ var x = point.clientX - rect.left;
1706
+ var y = point.clientY - rect.top;
1707
+ var imageURL = this.image.currentSrc || this.image.src;
1708
+ var imageRect = this.image.getBoundingClientRect();
1709
+ if (point.clientX < imageRect.left || point.clientX > imageRect.right || point.clientY < imageRect.top || point.clientY > imageRect.bottom) {
1710
+ this.hideMagnifier();
1711
+ return;
1712
+ }
1713
+ var scaleX = isNumber(imageData.scaleX) ? imageData.scaleX : 1;
1714
+ var scaleY = isNumber(imageData.scaleY) ? imageData.scaleY : 1;
1715
+ var rotate = (imageData.rotate || 0) * Math.PI / 180;
1716
+ var cos = Math.cos(rotate);
1717
+ var sin = Math.sin(rotate);
1718
+ var matrixA = cos * scaleX;
1719
+ var matrixB = sin * scaleX;
1720
+ var matrixC = -sin * scaleY;
1721
+ var matrixD = cos * scaleY;
1722
+ var determinant = scaleX * scaleY;
1723
+ if (determinant === 0) {
1724
+ this.hideMagnifier();
1725
+ return;
1726
+ }
1727
+ var centerX = imageData.x + imageData.width / 2;
1728
+ var centerY = imageData.y + imageData.height / 2;
1729
+ var localX = (matrixD * (x - centerX) - matrixC * (y - centerY)) / determinant;
1730
+ var localY = (-matrixB * (x - centerX) + matrixA * (y - centerY)) / determinant;
1731
+ var sourceX = localX + imageData.width / 2;
1732
+ var sourceY = localY + imageData.height / 2;
1733
+ var magnifiedWidth = imageData.width * zoomRatio;
1734
+ var magnifiedHeight = imageData.height * zoomRatio;
1735
+ var transformedX = matrixA * (sourceX * zoomRatio - magnifiedWidth / 2) + matrixC * (sourceY * zoomRatio - magnifiedHeight / 2);
1736
+ var transformedY = matrixB * (sourceX * zoomRatio - magnifiedWidth / 2) + matrixD * (sourceY * zoomRatio - magnifiedHeight / 2);
1737
+ this.magnifierSourcePoint = {
1738
+ x: sourceX,
1739
+ y: sourceY
1740
+ };
1741
+ magnifier.style.width = "".concat(size, "px");
1742
+ magnifier.style.height = "".concat(size, "px");
1743
+ magnifier.style.opacity = "".concat(Math.max(0, Math.min(1, isNumber(opacity) ? opacity : 1)));
1744
+ magnifierImage.src = imageURL;
1745
+ magnifierImage.style.width = "".concat(magnifiedWidth, "px");
1746
+ magnifierImage.style.height = "".concat(magnifiedHeight, "px");
1747
+ magnifierImage.style.left = "".concat(size / 2 - magnifiedWidth / 2 - transformedX, "px");
1748
+ magnifierImage.style.top = "".concat(size / 2 - magnifiedHeight / 2 - transformedY, "px");
1749
+ magnifierImage.style.transform = "rotate(".concat(imageData.rotate || 0, "deg) scaleX(").concat(scaleX, ") scaleY(").concat(scaleY, ")");
1750
+ magnifier.removeAttribute('aria-hidden');
1751
+ addClass(magnifier, 'viewer-show');
1752
+ },
1753
+ hideMagnifier: function hideMagnifier() {
1754
+ if (this.magnifier) {
1755
+ removeClass(this.magnifier, 'viewer-show');
1756
+ this.magnifier.setAttribute('aria-hidden', true);
1757
+ this.magnifierPoint = null;
1758
+ }
1759
+ },
1484
1760
  pointerdown: function pointerdown(event) {
1485
1761
  var options = this.options,
1486
1762
  pointers = this.pointers;
@@ -1509,12 +1785,14 @@ var handlers = {
1509
1785
  pointers[event.pointerId || 0] = getPointer(event);
1510
1786
  }
1511
1787
  var action = options.movable ? ACTION_MOVE : false;
1512
- if (options.zoomOnTouch && options.zoomable && Object.keys(pointers).length > 1) {
1513
- action = ACTION_ZOOM;
1788
+ if ((options.zoomable && options.zoomOnTouch || options.rotatable && options.rotateOnTouch) && Object.keys(pointers).length > 1) {
1789
+ // action = ACTION_ZOOM;
1790
+ // action = ACTION_ROTATE;
1791
+ action = ACTION_TRANSFORM;
1514
1792
  } else if (options.slideOnTouch && (event.pointerType === 'touch' || event.type === 'touchstart') && this.isSwitchable()) {
1515
1793
  action = ACTION_SWITCH;
1516
1794
  }
1517
- if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {
1795
+ if (action === ACTION_MOVE || action === ACTION_ZOOM || action === ACTION_ROTATE || action === ACTION_TRANSFORM) {
1518
1796
  removeClass(this.image, CLASS_TRANSITION);
1519
1797
  }
1520
1798
  this.action = action;
@@ -1554,13 +1832,14 @@ var handlers = {
1554
1832
  return;
1555
1833
  }
1556
1834
  event.preventDefault();
1557
- if (options.transition && (action === ACTION_MOVE || action === ACTION_ZOOM)) {
1558
- addClass(this.image, CLASS_TRANSITION);
1835
+ if (action === ACTION_MOVE || action === ACTION_ZOOM || action === ACTION_ROTATE || action === ACTION_TRANSFORM) {
1836
+ var transition = action === ACTION_TRANSFORM ? isTransitionEnabled(options, ACTION_ZOOM) || isTransitionEnabled(options, ACTION_ROTATE) : isTransitionEnabled(options, action);
1837
+ toggleClass(this.image, CLASS_TRANSITION, transition);
1559
1838
  }
1560
1839
  this.action = false;
1561
1840
 
1562
1841
  // Emulate click and double click in touch devices to support backdrop and image zooming (#210).
1563
- if (IS_TOUCH_DEVICE && action !== ACTION_ZOOM && pointer && Date.now() - pointer.timeStamp < 500) {
1842
+ if (IS_TOUCH_DEVICE && action !== ACTION_ZOOM && action !== ACTION_TRANSFORM && pointer && Date.now() - pointer.timeStamp < 500) {
1564
1843
  clearTimeout(this.clickCanvasTimeout);
1565
1844
  clearTimeout(this.doubleClickImageTimeout);
1566
1845
  if (options.toggleOnDblclick && this.viewed && event.target === this.image) {
@@ -1609,6 +1888,10 @@ var handlers = {
1609
1888
  this.initContainer();
1610
1889
  this.initViewer();
1611
1890
  this.renderViewer();
1891
+ var navbarOptions = isPlainObject(this.options.navbar) ? this.options.navbar : {};
1892
+ if (isUndefined(navbarOptions.visibleItemCount)) {
1893
+ this.initList(this.index);
1894
+ }
1612
1895
  this.renderList();
1613
1896
  if (this.viewed) {
1614
1897
  this.initImage(function () {
@@ -1630,12 +1913,17 @@ var handlers = {
1630
1913
  },
1631
1914
  wheel: function wheel(event) {
1632
1915
  var _this4 = this;
1916
+ var options = this.options,
1917
+ navbar = this.navbar;
1633
1918
  if (!this.viewed) {
1634
1919
  return;
1635
1920
  }
1636
1921
  event.preventDefault();
1922
+ if (this.gesturing) {
1923
+ return;
1924
+ }
1637
1925
 
1638
- // Limit wheel speed to prevent zoom too fast
1926
+ // Limit wheel speed to prevent zoom or slide too fast
1639
1927
  if (this.wheeling) {
1640
1928
  return;
1641
1929
  }
@@ -1643,7 +1931,6 @@ var handlers = {
1643
1931
  setTimeout(function () {
1644
1932
  _this4.wheeling = false;
1645
1933
  }, 50);
1646
- var ratio = Number(this.options.zoomRatio) || 0.1;
1647
1934
  var delta = 1;
1648
1935
  if (event.deltaY) {
1649
1936
  delta = event.deltaY > 0 ? 1 : -1;
@@ -1652,7 +1939,63 @@ var handlers = {
1652
1939
  } else if (event.detail) {
1653
1940
  delta = event.detail > 0 ? 1 : -1;
1654
1941
  }
1655
- this.zoom(-delta * ratio, true, null, event);
1942
+
1943
+ // Wheeling over the navbar never zooms, only slides
1944
+ var overNavbar = navbar && navbar.contains(event.target);
1945
+ var zoomable = !overNavbar && options.zoomable && isWheelActionEnabled(options.zoomOnWheel, event);
1946
+ if (zoomable) {
1947
+ var ratio = Number(options.zoomRatio) || 0.1;
1948
+ this.actionEvent = event;
1949
+ this.zoom(-delta * ratio, true);
1950
+ return;
1951
+ }
1952
+ if (isWheelActionEnabled(options.slideOnWheel, event)) {
1953
+ this.actionEvent = event;
1954
+ if (delta > 0) {
1955
+ this.next(options.loop);
1956
+ } else if (delta < 0) {
1957
+ this.prev(options.loop);
1958
+ }
1959
+ }
1960
+ },
1961
+ gesture: function gesture(event) {
1962
+ var options = this.options;
1963
+ if (!this.viewed || !options.zoomOnGesture && !options.rotateOnGesture) {
1964
+ return;
1965
+ }
1966
+ event.preventDefault();
1967
+ switch (event.type) {
1968
+ case 'gesturestart':
1969
+ this.gesturing = true;
1970
+ this.gestureScale = event.scale || 1;
1971
+ this.gestureRotation = event.rotation || 0;
1972
+ break;
1973
+ case 'gesturechange':
1974
+ {
1975
+ var scale = event.scale || 1;
1976
+ var ratio = scale / (this.gestureScale || 1);
1977
+ var rotation = Number(event.rotation);
1978
+ var degree = rotation - (this.gestureRotation || 0);
1979
+ this.gestureScale = scale;
1980
+ if (options.zoomable && options.zoomOnGesture && ratio !== 1) {
1981
+ this.actionEvent = event;
1982
+ this.zoom(ratio >= 1 ? ratio - 1 : 1 - 1 / ratio);
1983
+ }
1984
+ if (options.rotatable && options.rotateOnGesture && isNumber(rotation)) {
1985
+ this.gestureRotation = rotation;
1986
+ if (degree !== 0) {
1987
+ this.actionEvent = event;
1988
+ this.rotate(degree);
1989
+ }
1990
+ }
1991
+ break;
1992
+ }
1993
+ case 'gestureend':
1994
+ this.gesturing = false;
1995
+ this.gestureScale = 1;
1996
+ this.gestureRotation = 0;
1997
+ break;
1998
+ }
1656
1999
  }
1657
2000
  };
1658
2001
 
@@ -1675,12 +2018,17 @@ var methods = {
1675
2018
  }
1676
2019
  return this;
1677
2020
  }
2021
+ var originalEvent = this.actionEvent || this.showOriginalEvent || this.viewOriginalEvent || null;
2022
+ this.actionEvent = null;
2023
+ this.showOriginalEvent = originalEvent;
1678
2024
  if (isFunction(options.show)) {
1679
2025
  addListener(element, EVENT_SHOW, options.show, {
1680
2026
  once: true
1681
2027
  });
1682
2028
  }
1683
- if (dispatchEvent(element, EVENT_SHOW) === false || !this.ready) {
2029
+ if (dispatchEvent(element, EVENT_SHOW, {
2030
+ originalEvent: originalEvent
2031
+ }) === false || !this.ready) {
1684
2032
  return this;
1685
2033
  }
1686
2034
  if (this.hiding) {
@@ -1694,7 +2042,7 @@ var methods = {
1694
2042
  viewer.setAttribute('aria-labelledby', this.title.id);
1695
2043
  viewer.setAttribute('aria-modal', true);
1696
2044
  viewer.removeAttribute('aria-hidden');
1697
- if (options.transition && !immediate) {
2045
+ if (isTransitionEnabled(options, 'show') && !immediate) {
1698
2046
  var shown = this.shown.bind(this);
1699
2047
  this.transitioning = {
1700
2048
  abort: function abort() {
@@ -1729,12 +2077,17 @@ var methods = {
1729
2077
  if (options.inline || this.hiding || !(this.isShown || this.showing)) {
1730
2078
  return this;
1731
2079
  }
2080
+ var originalEvent = this.actionEvent || this.hideOriginalEvent || null;
2081
+ this.actionEvent = null;
2082
+ this.hideOriginalEvent = originalEvent;
1732
2083
  if (isFunction(options.hide)) {
1733
2084
  addListener(element, EVENT_HIDE, options.hide, {
1734
2085
  once: true
1735
2086
  });
1736
2087
  }
1737
- if (dispatchEvent(element, EVENT_HIDE) === false) {
2088
+ if (dispatchEvent(element, EVENT_HIDE, {
2089
+ originalEvent: originalEvent
2090
+ }) === false || this.destroyed) {
1738
2091
  return this;
1739
2092
  }
1740
2093
  if (this.showing) {
@@ -1752,7 +2105,7 @@ var methods = {
1752
2105
  removeClass(viewer, CLASS_IN);
1753
2106
  _this.hidden();
1754
2107
  };
1755
- if (options.transition && !immediate) {
2108
+ if (isTransitionEnabled(options, 'hide') && !immediate) {
1756
2109
  var _onViewerTransitionEnd = function onViewerTransitionEnd(event) {
1757
2110
  // Ignore all propagating `transitionend` events (#275).
1758
2111
  if (event && event.target === viewer) {
@@ -1785,7 +2138,7 @@ var methods = {
1785
2138
  addListener(image, EVENT_TRANSITION_END, onImageTransitionEnd, {
1786
2139
  once: true
1787
2140
  });
1788
- this.zoomTo(0, false, null, null, true);
2141
+ this.zoomTo(0, false, null, true);
1789
2142
  } else {
1790
2143
  onImageTransitionEnd();
1791
2144
  }
@@ -1802,10 +2155,14 @@ var methods = {
1802
2155
  view: function view() {
1803
2156
  var _this2 = this;
1804
2157
  var index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.initialViewIndex;
2158
+ var previousIndex = this.index;
1805
2159
  index = Number(index) || 0;
1806
- if (this.hiding || this.played || index < 0 || index >= this.length || this.viewed && index === this.index) {
2160
+ if (this.hiding || this.played || index < 0 || index >= this.length || this.viewed && index === previousIndex) {
1807
2161
  return this;
1808
2162
  }
2163
+ var originalEvent = this.actionEvent || this.viewOriginalEvent || null;
2164
+ this.actionEvent = null;
2165
+ this.viewOriginalEvent = originalEvent;
1809
2166
  if (!this.isShown) {
1810
2167
  this.index = index;
1811
2168
  return this.show();
@@ -1817,17 +2174,16 @@ var methods = {
1817
2174
  options = this.options,
1818
2175
  title = this.title,
1819
2176
  canvas = this.canvas;
1820
- var item = this.items[index];
2177
+ var item = this.getItem(index);
2178
+ if (!item) {
2179
+ this.initList(index);
2180
+ item = this.getItem(index);
2181
+ }
1821
2182
  var img = item.querySelector('img');
1822
2183
  var url = getData(img, 'originalUrl');
1823
2184
  var alt = img.getAttribute('alt');
1824
2185
  var image = document.createElement('img');
1825
- forEach(options.inheritedAttributes, function (name) {
1826
- var value = img.getAttribute(name);
1827
- if (value !== null) {
1828
- image.setAttribute(name, value);
1829
- }
1830
- });
2186
+ inheritAttributes(image, img, options.inheritedAttributes);
1831
2187
  image.src = url;
1832
2188
  image.alt = alt;
1833
2189
  if (isFunction(options.view)) {
@@ -1838,11 +2194,13 @@ var methods = {
1838
2194
  if (dispatchEvent(element, EVENT_VIEW, {
1839
2195
  originalImage: this.images[index],
1840
2196
  index: index,
1841
- image: image
2197
+ image: image,
2198
+ originalEvent: originalEvent
1842
2199
  }) === false || !this.isShown || this.hiding || this.played) {
1843
2200
  return this;
1844
2201
  }
1845
- var activeItem = this.items[this.index];
2202
+ this.hideMagnifier();
2203
+ var activeItem = this.getItem(previousIndex);
1846
2204
  if (activeItem) {
1847
2205
  removeClass(activeItem, CLASS_ACTIVE);
1848
2206
  activeItem.removeAttribute('aria-selected');
@@ -1874,63 +2232,95 @@ var methods = {
1874
2232
  var imageData = _this2.imageData;
1875
2233
  var render = Array.isArray(options.title) ? options.title[1] : options.title;
1876
2234
  title.innerHTML = escapeHTMLEntities(isFunction(render) ? render.call(_this2, image, imageData) : "".concat(alt, " (").concat(imageData.naturalWidth, " \xD7 ").concat(imageData.naturalHeight, ")"));
1877
- };
1878
- var onLoad;
1879
- var onError;
1880
- addListener(element, EVENT_VIEWED, onViewed, {
1881
- once: true
1882
- });
1883
- this.viewing = {
1884
- abort: function abort() {
1885
- removeListener(element, EVENT_VIEWED, onViewed);
1886
- if (image.complete) {
1887
- if (_this2.imageRendering) {
1888
- _this2.imageRendering.abort();
1889
- } else if (_this2.imageInitializing) {
1890
- _this2.imageInitializing.abort();
1891
- }
1892
- } else {
1893
- // Cancel download to save bandwidth.
1894
- image.src = '';
1895
- removeListener(image, EVENT_LOAD, onLoad);
1896
- if (_this2.timeout) {
1897
- clearTimeout(_this2.timeout);
2235
+
2236
+ // Preload the image in the direction of navigation
2237
+ if (options.preload) {
2238
+ var direction = index < previousIndex ? -1 : 1;
2239
+ var nextIndex = index + direction;
2240
+ if (nextIndex < 0 || nextIndex >= _this2.length) {
2241
+ if (options.loop) {
2242
+ nextIndex = direction > 0 ? 0 : _this2.length - 1;
2243
+ } else {
2244
+ nextIndex = -1;
1898
2245
  }
1899
2246
  }
2247
+ if (nextIndex !== index) {
2248
+ var nextImage = _this2.images[nextIndex];
2249
+ var preloadedImage = document.createElement('img');
2250
+ inheritAttributes(preloadedImage, nextImage, options.inheritedAttributes);
2251
+ preloadedImage.src = _this2.getImageURL(nextImage) || nextImage.src;
2252
+ }
1900
2253
  }
1901
2254
  };
1902
- if (image.complete) {
1903
- this.load();
1904
- } else {
1905
- addListener(image, EVENT_LOAD, onLoad = function onLoad() {
1906
- removeListener(image, EVENT_ERROR, onError);
1907
- _this2.load();
1908
- }, {
1909
- once: true
1910
- });
1911
- addListener(image, EVENT_ERROR, onError = function onError() {
2255
+ addListener(element, EVENT_VIEWED, onViewed, {
2256
+ once: true
2257
+ });
2258
+ var _loadImage = function loadImage() {
2259
+ var isFallback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
2260
+ var onLoad;
2261
+ var onError;
2262
+ var clean = function clean() {
1912
2263
  removeListener(image, EVENT_LOAD, onLoad);
2264
+ removeListener(image, EVENT_ERROR, onError);
1913
2265
  if (_this2.timeout) {
1914
2266
  clearTimeout(_this2.timeout);
1915
2267
  _this2.timeout = false;
1916
2268
  }
1917
- removeClass(image, CLASS_INVISIBLE);
1918
- if (options.loading) {
1919
- removeClass(_this2.canvas, CLASS_LOADING);
2269
+ };
2270
+ _this2.viewing = {
2271
+ abort: function abort() {
2272
+ removeListener(element, EVENT_VIEWED, onViewed);
2273
+ if (image.complete) {
2274
+ if (_this2.imageRendering) {
2275
+ _this2.imageRendering.abort();
2276
+ } else if (_this2.imageInitializing) {
2277
+ _this2.imageInitializing.abort();
2278
+ }
2279
+ } else {
2280
+ // Cancel download to save bandwidth.
2281
+ image.src = '';
2282
+ clean();
2283
+ }
2284
+ }
2285
+ };
2286
+ if (image.complete) {
2287
+ _this2.load();
2288
+ } else {
2289
+ addListener(image, EVENT_LOAD, onLoad = function onLoad() {
2290
+ clean();
2291
+ _this2.load();
2292
+ }, {
2293
+ once: true
2294
+ });
2295
+ addListener(image, EVENT_ERROR, onError = function onError() {
2296
+ clean();
2297
+ if (!isFallback) {
2298
+ var fallbackSrc = img.src;
2299
+ if (fallbackSrc && fallbackSrc !== image.src) {
2300
+ image.src = fallbackSrc;
2301
+ _loadImage(true);
2302
+ return;
2303
+ }
2304
+ }
2305
+ removeClass(image, CLASS_INVISIBLE);
2306
+ if (options.loading) {
2307
+ removeClass(_this2.canvas, CLASS_LOADING);
2308
+ }
2309
+ }, {
2310
+ once: true
2311
+ });
2312
+ if (_this2.timeout) {
2313
+ clearTimeout(_this2.timeout);
1920
2314
  }
1921
- }, {
1922
- once: true
1923
- });
1924
- if (this.timeout) {
1925
- clearTimeout(this.timeout);
1926
- }
1927
2315
 
1928
- // Make the image visible if it fails to load within 1s
1929
- this.timeout = setTimeout(function () {
1930
- removeClass(image, CLASS_INVISIBLE);
1931
- _this2.timeout = false;
1932
- }, 1000);
1933
- }
2316
+ // Make the image visible if it fails to load within 1s
2317
+ _this2.timeout = setTimeout(function () {
2318
+ removeClass(image, CLASS_INVISIBLE);
2319
+ _this2.timeout = false;
2320
+ }, 1000);
2321
+ }
2322
+ };
2323
+ _loadImage();
1934
2324
  return this;
1935
2325
  },
1936
2326
  /**
@@ -1980,13 +2370,11 @@ var methods = {
1980
2370
  * Move the image to an absolute point.
1981
2371
  * @param {number} x - The new position in the horizontal direction.
1982
2372
  * @param {number} [y=x] - The new position in the vertical direction.
1983
- * @param {Event} [_originalEvent=null] - The original event if any.
1984
2373
  * @returns {Viewer} this
1985
2374
  */
1986
2375
  moveTo: function moveTo(x) {
1987
2376
  var _this3 = this;
1988
2377
  var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x;
1989
- var _originalEvent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
1990
2378
  var element = this.element,
1991
2379
  options = this.options,
1992
2380
  imageData = this.imageData;
@@ -2007,6 +2395,8 @@ var methods = {
2007
2395
  y = oldY;
2008
2396
  }
2009
2397
  if (changed) {
2398
+ var originalEvent = this.actionEvent || null;
2399
+ this.actionEvent = null;
2010
2400
  if (isFunction(options.move)) {
2011
2401
  addListener(element, EVENT_MOVE, options.move, {
2012
2402
  once: true
@@ -2017,7 +2407,7 @@ var methods = {
2017
2407
  y: y,
2018
2408
  oldX: oldX,
2019
2409
  oldY: oldY,
2020
- originalEvent: _originalEvent
2410
+ originalEvent: originalEvent
2021
2411
  }) === false) {
2022
2412
  return this;
2023
2413
  }
@@ -2025,6 +2415,7 @@ var methods = {
2025
2415
  imageData.y = y;
2026
2416
  imageData.left = x;
2027
2417
  imageData.top = y;
2418
+ toggleClass(this.image, CLASS_TRANSITION, isTransitionEnabled(options, 'move'));
2028
2419
  this.moving = true;
2029
2420
  this.renderImage(function () {
2030
2421
  _this3.moving = false;
@@ -2038,7 +2429,7 @@ var methods = {
2038
2429
  y: y,
2039
2430
  oldX: oldX,
2040
2431
  oldY: oldY,
2041
- originalEvent: _originalEvent
2432
+ originalEvent: originalEvent
2042
2433
  }, {
2043
2434
  cancelable: false
2044
2435
  });
@@ -2069,6 +2460,8 @@ var methods = {
2069
2460
  degree = Number(degree);
2070
2461
  if (isNumber(degree) && this.viewed && !this.played && options.rotatable) {
2071
2462
  var oldDegree = imageData.rotate;
2463
+ var originalEvent = this.actionEvent || null;
2464
+ this.actionEvent = null;
2072
2465
  if (isFunction(options.rotate)) {
2073
2466
  addListener(element, EVENT_ROTATE, options.rotate, {
2074
2467
  once: true
@@ -2076,11 +2469,13 @@ var methods = {
2076
2469
  }
2077
2470
  if (dispatchEvent(element, EVENT_ROTATE, {
2078
2471
  degree: degree,
2079
- oldDegree: oldDegree
2472
+ oldDegree: oldDegree,
2473
+ originalEvent: originalEvent
2080
2474
  }) === false) {
2081
2475
  return this;
2082
2476
  }
2083
2477
  imageData.rotate = degree;
2478
+ toggleClass(this.image, CLASS_TRANSITION, isTransitionEnabled(options, 'rotate'));
2084
2479
  this.rotating = true;
2085
2480
  this.renderImage(function () {
2086
2481
  _this4.rotating = false;
@@ -2091,7 +2486,8 @@ var methods = {
2091
2486
  }
2092
2487
  dispatchEvent(element, EVENT_ROTATED, {
2093
2488
  degree: degree,
2094
- oldDegree: oldDegree
2489
+ oldDegree: oldDegree,
2490
+ originalEvent: originalEvent
2095
2491
  }, {
2096
2492
  cancelable: false
2097
2493
  });
@@ -2146,6 +2542,8 @@ var methods = {
2146
2542
  scaleY = oldScaleY;
2147
2543
  }
2148
2544
  if (changed) {
2545
+ var originalEvent = this.actionEvent || null;
2546
+ this.actionEvent = null;
2149
2547
  if (isFunction(options.scale)) {
2150
2548
  addListener(element, EVENT_SCALE, options.scale, {
2151
2549
  once: true
@@ -2155,12 +2553,14 @@ var methods = {
2155
2553
  scaleX: scaleX,
2156
2554
  scaleY: scaleY,
2157
2555
  oldScaleX: oldScaleX,
2158
- oldScaleY: oldScaleY
2556
+ oldScaleY: oldScaleY,
2557
+ originalEvent: originalEvent
2159
2558
  }) === false) {
2160
2559
  return this;
2161
2560
  }
2162
2561
  imageData.scaleX = scaleX;
2163
2562
  imageData.scaleY = scaleY;
2563
+ toggleClass(this.image, CLASS_TRANSITION, isTransitionEnabled(options, 'scale'));
2164
2564
  this.scaling = true;
2165
2565
  this.renderImage(function () {
2166
2566
  _this5.scaling = false;
@@ -2173,7 +2573,8 @@ var methods = {
2173
2573
  scaleX: scaleX,
2174
2574
  scaleY: scaleY,
2175
2575
  oldScaleX: oldScaleX,
2176
- oldScaleY: oldScaleY
2576
+ oldScaleY: oldScaleY,
2577
+ originalEvent: originalEvent
2177
2578
  }, {
2178
2579
  cancelable: false
2179
2580
  });
@@ -2187,13 +2588,11 @@ var methods = {
2187
2588
  * @param {number} ratio - The target ratio.
2188
2589
  * @param {boolean} [showTooltip=false] - Indicates whether to show the tooltip.
2189
2590
  * @param {Object} [pivot] - The pivot point coordinate for zooming.
2190
- * @param {Event} [_originalEvent=null] - The original event if any.
2191
2591
  * @returns {Viewer} this
2192
2592
  */
2193
2593
  zoom: function zoom(ratio) {
2194
2594
  var showTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
2195
2595
  var pivot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
2196
- var _originalEvent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
2197
2596
  var imageData = this.imageData;
2198
2597
  ratio = Number(ratio);
2199
2598
  if (ratio < 0) {
@@ -2201,7 +2600,7 @@ var methods = {
2201
2600
  } else {
2202
2601
  ratio = 1 + ratio;
2203
2602
  }
2204
- this.zoomTo(imageData.width * ratio / imageData.naturalWidth, showTooltip, pivot, _originalEvent);
2603
+ this.zoomTo(imageData.width * ratio / imageData.naturalWidth, showTooltip, pivot);
2205
2604
  return this;
2206
2605
  },
2207
2606
  /**
@@ -2209,16 +2608,14 @@ var methods = {
2209
2608
  * @param {number} ratio - The target ratio.
2210
2609
  * @param {boolean} [showTooltip] - Indicates whether to show the tooltip.
2211
2610
  * @param {Object} [pivot] - The pivot point coordinate for zooming.
2212
- * @param {Event} [_originalEvent=null] - The original event if any.
2213
- * @param {Event} [_zoomable=false] - Indicates if the current zoom is available or not.
2611
+ * @param {boolean} [_zoomable=false] - Indicates if the current zoom is available or not.
2214
2612
  * @returns {Viewer} this
2215
2613
  */
2216
2614
  zoomTo: function zoomTo(ratio) {
2217
2615
  var _this6 = this;
2218
2616
  var showTooltip = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
2219
2617
  var pivot = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
2220
- var _originalEvent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
2221
- var _zoomable = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
2618
+ var _zoomable = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
2222
2619
  var element = this.element,
2223
2620
  options = this.options,
2224
2621
  pointers = this.pointers,
@@ -2232,12 +2629,14 @@ var methods = {
2232
2629
  ratio = Math.max(0, ratio);
2233
2630
  if (isNumber(ratio) && this.viewed && !this.played && (_zoomable || options.zoomable)) {
2234
2631
  if (!_zoomable) {
2235
- var minZoomRatio = Math.max(0.01, options.minZoomRatio);
2236
- var maxZoomRatio = Math.min(100, options.maxZoomRatio);
2632
+ var minZoomRatio = Math.max(0.01, isFunction(options.minZoomRatio) ? options.minZoomRatio.call(this, this.image, imageData) : options.minZoomRatio);
2633
+ var maxZoomRatio = Math.min(100, isFunction(options.maxZoomRatio) ? options.maxZoomRatio.call(this, this.image, imageData) : options.maxZoomRatio);
2237
2634
  ratio = Math.min(Math.max(ratio, minZoomRatio), maxZoomRatio);
2238
2635
  }
2239
- if (_originalEvent) {
2240
- switch (_originalEvent.type) {
2636
+ var originalEvent = this.actionEvent || null;
2637
+ this.actionEvent = null;
2638
+ if (originalEvent && originalEvent.type !== EVENT_CLICK) {
2639
+ switch (originalEvent.type) {
2241
2640
  case 'wheel':
2242
2641
  if (options.zoomRatio >= 0.055 && ratio > 0.95 && ratio < 1.05) {
2243
2642
  ratio = 1;
@@ -2265,16 +2664,16 @@ var methods = {
2265
2664
  if (dispatchEvent(element, EVENT_ZOOM, {
2266
2665
  ratio: ratio,
2267
2666
  oldRatio: oldRatio,
2268
- originalEvent: _originalEvent
2667
+ originalEvent: originalEvent
2269
2668
  }) === false) {
2270
2669
  return this;
2271
2670
  }
2272
2671
  this.zooming = true;
2273
- if (_originalEvent) {
2672
+ if (originalEvent && originalEvent.type !== EVENT_CLICK) {
2274
2673
  var offset = getOffset(this.viewer);
2275
2674
  var center = pointers && Object.keys(pointers).length > 0 ? getPointersCenter(pointers) : {
2276
- pageX: _originalEvent.pageX,
2277
- pageY: _originalEvent.pageY
2675
+ pageX: originalEvent.pageX,
2676
+ pageY: originalEvent.pageY
2278
2677
  };
2279
2678
 
2280
2679
  // Zoom from the triggering point of the event
@@ -2294,6 +2693,7 @@ var methods = {
2294
2693
  imageData.height = newHeight;
2295
2694
  imageData.oldRatio = oldRatio;
2296
2695
  imageData.ratio = ratio;
2696
+ toggleClass(this.image, CLASS_TRANSITION, isTransitionEnabled(options, 'zoom'));
2297
2697
  this.renderImage(function () {
2298
2698
  _this6.zooming = false;
2299
2699
  if (isFunction(options.zoomed)) {
@@ -2304,7 +2704,7 @@ var methods = {
2304
2704
  dispatchEvent(element, EVENT_ZOOMED, {
2305
2705
  ratio: ratio,
2306
2706
  oldRatio: oldRatio,
2307
- originalEvent: _originalEvent
2707
+ originalEvent: originalEvent
2308
2708
  }, {
2309
2709
  cancelable: false
2310
2710
  });
@@ -2328,12 +2728,16 @@ var methods = {
2328
2728
  }
2329
2729
  var element = this.element,
2330
2730
  options = this.options;
2731
+ var originalEvent = this.actionEvent || null;
2732
+ this.actionEvent = null;
2331
2733
  if (isFunction(options.play)) {
2332
2734
  addListener(element, EVENT_PLAY, options.play, {
2333
2735
  once: true
2334
2736
  });
2335
2737
  }
2336
- if (dispatchEvent(element, EVENT_PLAY) === false) {
2738
+ if (dispatchEvent(element, EVENT_PLAY, {
2739
+ originalEvent: originalEvent
2740
+ }) === false) {
2337
2741
  return this;
2338
2742
  }
2339
2743
  var player = this.player;
@@ -2347,16 +2751,16 @@ var methods = {
2347
2751
  this.requestFullscreen(fullscreen);
2348
2752
  }
2349
2753
  addClass(player, CLASS_SHOW);
2350
- forEach(this.items, function (item, i) {
2351
- var img = item.querySelector('img');
2754
+ player.removeAttribute('aria-hidden');
2755
+ forEach(this.images, function (originalImage, i) {
2352
2756
  var image = document.createElement('img');
2353
- image.src = getData(img, 'originalUrl');
2354
- image.alt = img.getAttribute('alt');
2355
- image.referrerPolicy = img.referrerPolicy;
2757
+ image.src = _this7.getImageURL(originalImage) || originalImage.src;
2758
+ image.alt = originalImage.alt || '';
2759
+ image.referrerPolicy = originalImage.referrerPolicy;
2356
2760
  total += 1;
2357
2761
  addClass(image, CLASS_FADE);
2358
- toggleClass(image, CLASS_TRANSITION, options.transition);
2359
- if (hasClass(item, CLASS_ACTIVE)) {
2762
+ toggleClass(image, CLASS_TRANSITION, isTransitionEnabled(options, 'play'));
2763
+ if (i === _this7.index) {
2360
2764
  addClass(image, CLASS_IN);
2361
2765
  index = i;
2362
2766
  }
@@ -2369,31 +2773,68 @@ var methods = {
2369
2773
  if (isNumber(options.interval) && options.interval > 0) {
2370
2774
  var _prev = function prev() {
2371
2775
  clearTimeout(_this7.playing.timeout);
2776
+ var currentOriginalEvent = _this7.actionEvent || null;
2777
+ _this7.actionEvent = null;
2778
+ var prevIndex = index - 1;
2779
+ prevIndex = prevIndex >= 0 ? prevIndex : total - 1;
2780
+ if (isFunction(options.playing)) {
2781
+ addListener(element, EVENT_PLAYING, options.playing, {
2782
+ once: true
2783
+ });
2784
+ }
2785
+ if (dispatchEvent(element, EVENT_PLAYING, {
2786
+ originalImage: _this7.images[prevIndex],
2787
+ index: prevIndex,
2788
+ image: list[prevIndex],
2789
+ originalEvent: currentOriginalEvent
2790
+ }) === false) {
2791
+ _this7.playing.timeout = options.autoplay ? setTimeout(_prev, options.interval) : null;
2792
+ return;
2793
+ }
2372
2794
  removeClass(list[index], CLASS_IN);
2373
- index -= 1;
2374
- index = index >= 0 ? index : total - 1;
2795
+ index = prevIndex;
2375
2796
  addClass(list[index], CLASS_IN);
2376
- _this7.playing.timeout = setTimeout(_prev, options.interval);
2797
+ _this7.playing.timeout = options.autoplay ? setTimeout(_prev, options.interval) : null;
2377
2798
  };
2378
2799
  var _next = function next() {
2379
2800
  clearTimeout(_this7.playing.timeout);
2801
+ var currentOriginalEvent = _this7.actionEvent || null;
2802
+ _this7.actionEvent = null;
2803
+ var nextIndex = index + 1;
2804
+ nextIndex = nextIndex < total ? nextIndex : 0;
2805
+ if (isFunction(options.playing)) {
2806
+ addListener(element, EVENT_PLAYING, options.playing, {
2807
+ once: true
2808
+ });
2809
+ }
2810
+ if (dispatchEvent(element, EVENT_PLAYING, {
2811
+ originalImage: _this7.images[nextIndex],
2812
+ index: nextIndex,
2813
+ image: list[nextIndex],
2814
+ originalEvent: currentOriginalEvent
2815
+ }) === false) {
2816
+ _this7.playing.timeout = options.autoplay ? setTimeout(_next, options.interval) : null;
2817
+ return;
2818
+ }
2380
2819
  removeClass(list[index], CLASS_IN);
2381
- index += 1;
2382
- index = index < total ? index : 0;
2820
+ index = nextIndex;
2383
2821
  addClass(list[index], CLASS_IN);
2384
- _this7.playing.timeout = setTimeout(_next, options.interval);
2822
+ _this7.playing.timeout = options.autoplay ? setTimeout(_next, options.interval) : null;
2385
2823
  };
2386
2824
  if (total > 1) {
2387
2825
  this.playing = {
2388
2826
  prev: _prev,
2389
2827
  next: _next,
2390
- timeout: setTimeout(_next, options.interval)
2828
+ timeout: options.autoplay ? setTimeout(_next, options.interval) : null
2391
2829
  };
2392
2830
  }
2393
2831
  }
2394
2832
  return this;
2395
2833
  },
2396
- // Stop play
2834
+ /**
2835
+ * Stop play
2836
+ * @returns {Viewer} this
2837
+ */
2397
2838
  stop: function stop() {
2398
2839
  var _this8 = this;
2399
2840
  if (!this.played) {
@@ -2401,12 +2842,16 @@ var methods = {
2401
2842
  }
2402
2843
  var element = this.element,
2403
2844
  options = this.options;
2845
+ var originalEvent = this.actionEvent || null;
2846
+ this.actionEvent = null;
2404
2847
  if (isFunction(options.stop)) {
2405
2848
  addListener(element, EVENT_STOP, options.stop, {
2406
2849
  once: true
2407
2850
  });
2408
2851
  }
2409
- if (dispatchEvent(element, EVENT_STOP) === false) {
2852
+ if (dispatchEvent(element, EVENT_STOP, {
2853
+ originalEvent: originalEvent
2854
+ }) === false) {
2410
2855
  return this;
2411
2856
  }
2412
2857
  var player = this.player;
@@ -2417,6 +2862,7 @@ var methods = {
2417
2862
  removeListener(image, EVENT_LOAD, _this8.onLoadWhenPlay);
2418
2863
  });
2419
2864
  removeClass(player, CLASS_SHOW);
2865
+ player.setAttribute('aria-hidden', true);
2420
2866
  player.innerHTML = '';
2421
2867
  this.exitFullscreen();
2422
2868
  return this;
@@ -2434,11 +2880,9 @@ var methods = {
2434
2880
  this.fulled = true;
2435
2881
  this.open();
2436
2882
  addClass(this.button, CLASS_FULLSCREEN_EXIT);
2437
- if (options.transition) {
2438
- removeClass(list, CLASS_TRANSITION);
2439
- if (this.viewed) {
2440
- removeClass(image, CLASS_TRANSITION);
2441
- }
2883
+ removeClass(list, CLASS_TRANSITION);
2884
+ if (this.viewed) {
2885
+ removeClass(image, CLASS_TRANSITION);
2442
2886
  }
2443
2887
  addClass(viewer, CLASS_FIXED);
2444
2888
  viewer.setAttribute('role', 'dialog');
@@ -2456,14 +2900,7 @@ var methods = {
2456
2900
  this.renderList();
2457
2901
  if (this.viewed) {
2458
2902
  this.initImage(function () {
2459
- _this9.renderImage(function () {
2460
- if (options.transition) {
2461
- setTimeout(function () {
2462
- addClass(image, CLASS_TRANSITION);
2463
- addClass(list, CLASS_TRANSITION);
2464
- }, 0);
2465
- }
2466
- });
2903
+ _this9.renderImage();
2467
2904
  });
2468
2905
  }
2469
2906
  return this;
@@ -2481,11 +2918,9 @@ var methods = {
2481
2918
  this.fulled = false;
2482
2919
  this.close();
2483
2920
  removeClass(this.button, CLASS_FULLSCREEN_EXIT);
2484
- if (options.transition) {
2485
- removeClass(list, CLASS_TRANSITION);
2486
- if (this.viewed) {
2487
- removeClass(image, CLASS_TRANSITION);
2488
- }
2921
+ removeClass(list, CLASS_TRANSITION);
2922
+ if (this.viewed) {
2923
+ removeClass(image, CLASS_TRANSITION);
2489
2924
  }
2490
2925
  if (options.focus) {
2491
2926
  this.clearEnforceFocus();
@@ -2502,14 +2937,7 @@ var methods = {
2502
2937
  this.renderList();
2503
2938
  if (this.viewed) {
2504
2939
  this.initImage(function () {
2505
- _this0.renderImage(function () {
2506
- if (options.transition) {
2507
- setTimeout(function () {
2508
- addClass(image, CLASS_TRANSITION);
2509
- addClass(list, CLASS_TRANSITION);
2510
- }, 0);
2511
- }
2512
- });
2940
+ _this0.renderImage();
2513
2941
  });
2514
2942
  }
2515
2943
  return this;
@@ -2525,7 +2953,7 @@ var methods = {
2525
2953
  }
2526
2954
  tooltipBox.textContent = "".concat(Math.round(imageData.ratio * 100), "%");
2527
2955
  if (!this.tooltipping) {
2528
- if (options.transition) {
2956
+ if (isTransitionEnabled(options, 'tooltip')) {
2529
2957
  if (this.fading) {
2530
2958
  dispatchEvent(tooltipBox, EVENT_TRANSITION_END);
2531
2959
  }
@@ -2545,7 +2973,7 @@ var methods = {
2545
2973
  clearTimeout(this.tooltipping);
2546
2974
  }
2547
2975
  this.tooltipping = setTimeout(function () {
2548
- if (options.transition) {
2976
+ if (isTransitionEnabled(options, 'tooltip')) {
2549
2977
  addListener(tooltipBox, EVENT_TRANSITION_END, function () {
2550
2978
  removeClass(tooltipBox, CLASS_SHOW);
2551
2979
  removeClass(tooltipBox, CLASS_FADE);
@@ -2567,15 +2995,13 @@ var methods = {
2567
2995
  },
2568
2996
  /**
2569
2997
  * Toggle the image size between its current size and natural size
2570
- * @param {Event} [_originalEvent=null] - The original event if any.
2571
2998
  * @returns {Viewer} this
2572
2999
  */
2573
3000
  toggle: function toggle() {
2574
- var _originalEvent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
2575
3001
  if (this.imageData.ratio === 1) {
2576
- this.zoomTo(this.imageData.oldRatio, true, null, _originalEvent);
3002
+ this.zoomTo(this.imageData.oldRatio, true);
2577
3003
  } else {
2578
- this.zoomTo(1, true, null, _originalEvent);
3004
+ this.zoomTo(1, true);
2579
3005
  }
2580
3006
  return this;
2581
3007
  },
@@ -2587,12 +3013,19 @@ var methods = {
2587
3013
  }
2588
3014
  return this;
2589
3015
  },
2590
- // Update viewer when images changed
2591
- update: function update() {
3016
+ /**
3017
+ * Update the viewer when images or options changed.
3018
+ * @param {Object} [updateOptions] - The options to update.
3019
+ * @returns {Viewer} this
3020
+ */
3021
+ update: function update(updateOptions) {
2592
3022
  var _this10 = this;
2593
3023
  var element = this.element,
2594
3024
  options = this.options,
2595
3025
  isImg = this.isImg;
3026
+ if (isPlainObject(updateOptions)) {
3027
+ assign(options, updateOptions);
3028
+ }
2596
3029
 
2597
3030
  // Destroy viewer if the target image was deleted
2598
3031
  if (isImg && !element.parentNode) {
@@ -2615,18 +3048,19 @@ var methods = {
2615
3048
  this.length = images.length;
2616
3049
  if (this.ready) {
2617
3050
  var changedIndexes = [];
2618
- forEach(this.items, function (item, i) {
3051
+ forEach(this.items, function (item) {
2619
3052
  var img = item.querySelector('img');
2620
- var image = images[i];
3053
+ var index = Number(getData(item, 'index'));
3054
+ var image = images[index];
2621
3055
  if (image && img) {
2622
3056
  if (image.src !== img.src
2623
3057
 
2624
3058
  // Title changed (#408)
2625
3059
  || image.alt !== img.alt) {
2626
- changedIndexes.push(i);
3060
+ changedIndexes.push(index);
2627
3061
  }
2628
3062
  } else {
2629
- changedIndexes.push(i);
3063
+ changedIndexes.push(index);
2630
3064
  }
2631
3065
  });
2632
3066
  setStyle(this.list, {
@@ -2641,7 +3075,7 @@ var methods = {
2641
3075
  this.viewed = false;
2642
3076
  this.view(Math.max(Math.min(this.index - changedIndex, this.length - 1), 0));
2643
3077
  } else {
2644
- var activeItem = this.items[this.index];
3078
+ var activeItem = this.getItem(this.index);
2645
3079
 
2646
3080
  // Reactivate the current viewing item after reset the list.
2647
3081
  addClass(activeItem, CLASS_ACTIVE);
@@ -2782,9 +3216,12 @@ var others = {
2782
3216
  once: true
2783
3217
  });
2784
3218
  }
2785
- if (dispatchEvent(element, EVENT_SHOWN) === false) {
3219
+ if (dispatchEvent(element, EVENT_SHOWN, {
3220
+ originalEvent: this.showOriginalEvent || null
3221
+ }) === false) {
2786
3222
  return;
2787
3223
  }
3224
+ this.showOriginalEvent = null;
2788
3225
  if (this.ready && this.isShown && !this.hiding) {
2789
3226
  this.view(this.index);
2790
3227
  }
@@ -2821,13 +3258,16 @@ var others = {
2821
3258
  once: true
2822
3259
  });
2823
3260
  }
2824
- dispatchEvent(element, EVENT_HIDDEN, null, {
3261
+ dispatchEvent(element, EVENT_HIDDEN, {
3262
+ originalEvent: this.hideOriginalEvent || null
3263
+ }, {
2825
3264
  cancelable: false
2826
3265
  });
3266
+ this.hideOriginalEvent = null;
2827
3267
  }
2828
3268
  },
2829
3269
  requestFullscreen: function requestFullscreen(options) {
2830
- var document = this.element.ownerDocument;
3270
+ var document = this.ownerDocument;
2831
3271
  if (this.fulled && !(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {
2832
3272
  var documentElement = document.documentElement;
2833
3273
 
@@ -2849,7 +3289,7 @@ var others = {
2849
3289
  }
2850
3290
  },
2851
3291
  exitFullscreen: function exitFullscreen() {
2852
- var document = this.element.ownerDocument;
3292
+ var document = this.ownerDocument;
2853
3293
  if (this.fulled && (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement)) {
2854
3294
  // Document.exitFullscreen()
2855
3295
  if (document.exitFullscreen) {
@@ -2879,13 +3319,49 @@ var others = {
2879
3319
  case ACTION_MOVE:
2880
3320
  if (offsetX !== 0 || offsetY !== 0) {
2881
3321
  this.pointerMoved = true;
2882
- this.move(offsetX, offsetY, event);
3322
+ this.actionEvent = event;
3323
+ this.move(offsetX, offsetY);
2883
3324
  }
2884
3325
  break;
2885
3326
 
2886
3327
  // Zoom the current image
2887
3328
  case ACTION_ZOOM:
2888
- this.zoom(getMaxZoomRatio(pointers), false, null, event);
3329
+ if (options.zoomable && options.zoomOnTouch) {
3330
+ var zoomRatio = getMaxZoomRatio(pointers);
3331
+ if (zoomRatio !== 0) {
3332
+ this.actionEvent = event;
3333
+ this.zoom(zoomRatio);
3334
+ }
3335
+ }
3336
+ break;
3337
+
3338
+ // Rotate the current image
3339
+ case ACTION_ROTATE:
3340
+ if (options.rotatable && options.rotateOnTouch) {
3341
+ var rotateDegree = getMaxRotateDegree(pointers);
3342
+ if (rotateDegree !== 0) {
3343
+ this.actionEvent = event;
3344
+ this.rotate(rotateDegree);
3345
+ }
3346
+ }
3347
+ break;
3348
+
3349
+ // Transform the current image
3350
+ case ACTION_TRANSFORM:
3351
+ if (options.zoomable && options.zoomOnTouch) {
3352
+ var _zoomRatio = getMaxZoomRatio(pointers);
3353
+ if (_zoomRatio !== 0) {
3354
+ this.actionEvent = event;
3355
+ this.zoom(_zoomRatio);
3356
+ }
3357
+ }
3358
+ if (options.rotatable && options.rotateOnTouch) {
3359
+ var _rotateDegree = getMaxRotateDegree(pointers);
3360
+ if (_rotateDegree !== 0) {
3361
+ this.actionEvent = event;
3362
+ this.rotate(_rotateDegree);
3363
+ }
3364
+ }
2889
3365
  break;
2890
3366
  case ACTION_SWITCH:
2891
3367
  {
@@ -2894,6 +3370,7 @@ var others = {
2894
3370
  if (absoluteOffsetX > 1 && absoluteOffsetX > Math.abs(offsetY)) {
2895
3371
  // Empty `pointers` as `touchend` event will not be fired after swiped in iOS browsers.
2896
3372
  this.pointers = {};
3373
+ this.actionEvent = event;
2897
3374
  if (offsetX > 1) {
2898
3375
  this.prev(options.loop);
2899
3376
  } else if (offsetX < -1) {
@@ -2927,18 +3404,20 @@ var getUniqueID = function (id) {
2927
3404
  var Viewer = /*#__PURE__*/function () {
2928
3405
  /**
2929
3406
  * Create a new Viewer.
2930
- * @param {Element} element - The target element for viewing.
3407
+ * @param {Element} element - The target image, or a container of images, to view.
2931
3408
  * @param {Object} [options={}] - The configuration options.
2932
3409
  */
2933
3410
  function Viewer(element) {
2934
3411
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
2935
3412
  _classCallCheck(this, Viewer);
2936
- if (!element || element.nodeType !== 1) {
3413
+ if (!element || element.nodeType !== 1 && element.nodeType !== 11) {
2937
3414
  throw new Error('The first argument is required and must be an element.');
2938
3415
  }
2939
3416
  this.element = element;
3417
+ this.ownerDocument = element.ownerDocument || element.host.ownerDocument;
2940
3418
  this.options = assign({}, DEFAULTS, isPlainObject(options) && options);
2941
3419
  this.action = false;
3420
+ this.actionEvent = null;
2942
3421
  this.fading = false;
2943
3422
  this.fulled = false;
2944
3423
  this.hiding = false;
@@ -2998,7 +3477,7 @@ var Viewer = /*#__PURE__*/function () {
2998
3477
  this.initBody();
2999
3478
 
3000
3479
  // Override `transition` option if it is not supported
3001
- if (isUndefined(document.createElement(NAMESPACE).style.transition)) {
3480
+ if (isUndefined(this.ownerDocument.createElement(NAMESPACE).style.transition)) {
3002
3481
  options.transition = false;
3003
3482
  }
3004
3483
  if (options.inline) {
@@ -3052,9 +3531,10 @@ var Viewer = /*#__PURE__*/function () {
3052
3531
  }
3053
3532
  });
3054
3533
  } else {
3055
- addListener(element, EVENT_CLICK, this.onElementClick = function (_ref) {
3056
- var target = _ref.target;
3534
+ addListener(element, EVENT_CLICK, this.onElementClick = function (event) {
3535
+ var target = event.target;
3057
3536
  if (target.localName === 'img' && (!isFunction(options.filter) || options.filter.call(_this, target))) {
3537
+ _this.actionEvent = event;
3058
3538
  _this.view(_this.images.indexOf(target));
3059
3539
  }
3060
3540
  });
@@ -3064,6 +3544,7 @@ var Viewer = /*#__PURE__*/function () {
3064
3544
  if (target.localName === 'img' && (key === 'Enter' || key === ' ')) {
3065
3545
  event.preventDefault();
3066
3546
  if (!isFunction(options.filter) || options.filter.call(_this, target)) {
3547
+ _this.actionEvent = event;
3067
3548
  _this.view(_this.images.indexOf(target));
3068
3549
  }
3069
3550
  }
@@ -3085,6 +3566,7 @@ var Viewer = /*#__PURE__*/function () {
3085
3566
  var title = viewer.querySelector(".".concat(NAMESPACE, "-title"));
3086
3567
  var toolbar = viewer.querySelector(".".concat(NAMESPACE, "-toolbar"));
3087
3568
  var navbar = viewer.querySelector(".".concat(NAMESPACE, "-navbar"));
3569
+ var navigation = viewer.querySelector(".".concat(NAMESPACE, "-navigation"));
3088
3570
  var button = viewer.querySelector(".".concat(NAMESPACE, "-button"));
3089
3571
  var canvas = viewer.querySelector(".".concat(NAMESPACE, "-canvas"));
3090
3572
  this.parent = parent;
@@ -3092,19 +3574,64 @@ var Viewer = /*#__PURE__*/function () {
3092
3574
  this.title = title;
3093
3575
  this.toolbar = toolbar;
3094
3576
  this.navbar = navbar;
3577
+ this.navigation = navigation;
3095
3578
  this.button = button;
3096
3579
  this.canvas = canvas;
3097
3580
  this.footer = viewer.querySelector(".".concat(NAMESPACE, "-footer"));
3581
+ this.magnifier = viewer.querySelector(".".concat(NAMESPACE, "-magnifier"));
3582
+ this.magnifierImage = viewer.querySelector(".".concat(NAMESPACE, "-magnifier-image"));
3098
3583
  this.tooltipBox = viewer.querySelector(".".concat(NAMESPACE, "-tooltip"));
3099
3584
  this.player = viewer.querySelector(".".concat(NAMESPACE, "-player"));
3100
3585
  this.list = viewer.querySelector(".".concat(NAMESPACE, "-list"));
3101
3586
  viewer.id = "".concat(NAMESPACE).concat(this.id);
3102
3587
  title.id = "".concat(NAMESPACE, "Title").concat(this.id);
3103
3588
  addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title) ? options.title[0] : options.title));
3104
- addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar));
3589
+ if (options.title) {
3590
+ title.removeAttribute('aria-hidden');
3591
+ }
3592
+ var navbarOptions = isPlainObject(options.navbar) ? options.navbar : {};
3593
+ var navbarShow = options.navbar;
3594
+ var navbarSize = !isUndefined(navbarOptions.size) ? navbarOptions.size : options.navbar;
3595
+ if (isPlainObject(options.navbar)) {
3596
+ navbarShow = !isUndefined(navbarOptions.show) ? navbarOptions.show : true;
3597
+ }
3598
+ addClass(navbar, !navbarShow ? CLASS_HIDE : getResponsiveClass(navbarShow));
3599
+ if (navbarShow) {
3600
+ navbar.removeAttribute('aria-hidden');
3601
+ }
3602
+ if (['small', 'medium', 'large'].indexOf(navbarSize) !== -1) {
3603
+ addClass(navbar, "".concat(NAMESPACE, "-").concat(navbarSize));
3604
+ }
3605
+ if (isPlainObject(options.navigation)) {
3606
+ forEach(navigation.querySelectorAll('[role="button"]'), function (item) {
3607
+ var name = getData(item, DATA_ACTION);
3608
+ var value = options.navigation[name];
3609
+ var deep = isPlainObject(value);
3610
+ var show = deep && !isUndefined(value.show) ? value.show : value;
3611
+ var size = deep && !isUndefined(value.size) ? value.size : value;
3612
+ toggleClass(item, CLASS_HIDE, !show);
3613
+ if (isNumber(show)) {
3614
+ addClass(item, getResponsiveClass(show));
3615
+ }
3616
+ if (['small', 'large'].indexOf(size) !== -1) {
3617
+ addClass(item, "".concat(NAMESPACE, "-").concat(size));
3618
+ }
3619
+ });
3620
+ } else {
3621
+ addClass(navigation, !options.navigation ? CLASS_HIDE : getResponsiveClass(options.navigation));
3622
+ }
3623
+ if (options.navigation) {
3624
+ navigation.removeAttribute('aria-hidden');
3625
+ }
3105
3626
  toggleClass(button, CLASS_HIDE, !options.button);
3627
+ if (options.button) {
3628
+ button.removeAttribute('aria-hidden');
3629
+ }
3106
3630
  if (options.keyboard) {
3107
3631
  button.setAttribute('tabindex', 0);
3632
+ forEach(navigation.querySelectorAll('[role="button"]'), function (item) {
3633
+ item.setAttribute('tabindex', 0);
3634
+ });
3108
3635
  }
3109
3636
  if (options.backdrop) {
3110
3637
  addClass(viewer, "".concat(NAMESPACE, "-backdrop"));
@@ -3127,6 +3654,7 @@ var Viewer = /*#__PURE__*/function () {
3127
3654
  if (!custom) {
3128
3655
  addClass(toolbar, getResponsiveClass(options.toolbar));
3129
3656
  }
3657
+ toolbar.removeAttribute('aria-hidden');
3130
3658
  forEach(custom ? options.toolbar : BUTTONS, function (value, index) {
3131
3659
  var deep = custom && isPlainObject(value);
3132
3660
  var name = custom ? hyphenate(index) : value;
@@ -3162,6 +3690,11 @@ var Viewer = /*#__PURE__*/function () {
3162
3690
  } else {
3163
3691
  addClass(toolbar, CLASS_HIDE);
3164
3692
  }
3693
+ if (options.title || navbarShow || options.toolbar) {
3694
+ this.footer.removeAttribute('aria-hidden');
3695
+ } else {
3696
+ addClass(this.footer, CLASS_HIDE);
3697
+ }
3165
3698
  if (!options.rotatable) {
3166
3699
  var rotates = toolbar.querySelectorAll('li[class*="rotate"]');
3167
3700
  addClass(rotates, CLASS_INVISIBLE);
@@ -3190,7 +3723,7 @@ var Viewer = /*#__PURE__*/function () {
3190
3723
  });
3191
3724
  var container = options.container;
3192
3725
  if (isString(container)) {
3193
- container = element.ownerDocument.querySelector(container);
3726
+ container = this.ownerDocument.querySelector(container);
3194
3727
  }
3195
3728
  if (!container) {
3196
3729
  container = this.body;
@@ -3218,14 +3751,15 @@ var Viewer = /*#__PURE__*/function () {
3218
3751
  }
3219
3752
 
3220
3753
  /**
3221
- * Get the no conflict viewer class.
3222
- * @returns {Viewer} The viewer class.
3754
+ * Create a new Viewer instance.
3755
+ * @param {Element} element - The target image, or a container of images, to view.
3756
+ * @param {Object} [options={}] - The configuration options.
3757
+ * @returns {Viewer} A new Viewer instance.
3223
3758
  */
3224
3759
  }], [{
3225
- key: "noConflict",
3226
- value: function noConflict() {
3227
- window.Viewer = AnotherViewer;
3228
- return Viewer;
3760
+ key: "create",
3761
+ value: function create(element, options) {
3762
+ return new Viewer(element, options);
3229
3763
  }
3230
3764
 
3231
3765
  /**
@@ -3237,6 +3771,17 @@ var Viewer = /*#__PURE__*/function () {
3237
3771
  value: function setDefaults(options) {
3238
3772
  assign(DEFAULTS, isPlainObject(options) && options);
3239
3773
  }
3774
+
3775
+ /**
3776
+ * Get the no conflict viewer class.
3777
+ * @returns {Viewer} The viewer class.
3778
+ */
3779
+ }, {
3780
+ key: "noConflict",
3781
+ value: function noConflict() {
3782
+ window.Viewer = AnotherViewer;
3783
+ return Viewer;
3784
+ }
3240
3785
  }]);
3241
3786
  }();
3242
3787
  assign(Viewer.prototype, render, events, handlers, methods, others);