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