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.
package/src/js/others.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  ACTION_MOVE,
3
+ ACTION_ROTATE,
3
4
  ACTION_SWITCH,
5
+ ACTION_TRANSFORM,
4
6
  ACTION_ZOOM,
5
7
  CLASS_HIDE,
6
8
  CLASS_OPEN,
@@ -13,6 +15,7 @@ import {
13
15
  addListener,
14
16
  dispatchEvent,
15
17
  forEach,
18
+ getMaxRotateDegree,
16
19
  getMaxZoomRatio,
17
20
  isFunction,
18
21
  isPlainObject,
@@ -106,10 +109,14 @@ export default {
106
109
  });
107
110
  }
108
111
 
109
- if (dispatchEvent(element, EVENT_SHOWN) === false) {
112
+ if (dispatchEvent(element, EVENT_SHOWN, {
113
+ originalEvent: this.showOriginalEvent || null,
114
+ }) === false) {
110
115
  return;
111
116
  }
112
117
 
118
+ this.showOriginalEvent = null;
119
+
113
120
  if (this.ready && this.isShown && !this.hiding) {
114
121
  this.view(this.index);
115
122
  }
@@ -149,14 +156,18 @@ export default {
149
156
  });
150
157
  }
151
158
 
152
- dispatchEvent(element, EVENT_HIDDEN, null, {
159
+ dispatchEvent(element, EVENT_HIDDEN, {
160
+ originalEvent: this.hideOriginalEvent || null,
161
+ }, {
153
162
  cancelable: false,
154
163
  });
164
+
165
+ this.hideOriginalEvent = null;
155
166
  }
156
167
  },
157
168
 
158
169
  requestFullscreen(options) {
159
- const document = this.element.ownerDocument;
170
+ const { ownerDocument: document } = this;
160
171
 
161
172
  if (this.fulled && !(
162
173
  document.fullscreenElement
@@ -185,7 +196,7 @@ export default {
185
196
  },
186
197
 
187
198
  exitFullscreen() {
188
- const document = this.element.ownerDocument;
199
+ const { ownerDocument: document } = this;
189
200
 
190
201
  if (this.fulled && (
191
202
  document.fullscreenElement
@@ -223,13 +234,54 @@ export default {
223
234
  case ACTION_MOVE:
224
235
  if (offsetX !== 0 || offsetY !== 0) {
225
236
  this.pointerMoved = true;
226
- this.move(offsetX, offsetY, event);
237
+ this.actionEvent = event;
238
+ this.move(offsetX, offsetY);
227
239
  }
228
240
  break;
229
241
 
230
242
  // Zoom the current image
231
243
  case ACTION_ZOOM:
232
- this.zoom(getMaxZoomRatio(pointers), false, null, event);
244
+ if (options.zoomable && options.zoomOnTouch) {
245
+ const zoomRatio = getMaxZoomRatio(pointers);
246
+
247
+ if (zoomRatio !== 0) {
248
+ this.actionEvent = event;
249
+ this.zoom(zoomRatio);
250
+ }
251
+ }
252
+ break;
253
+
254
+ // Rotate the current image
255
+ case ACTION_ROTATE:
256
+ if (options.rotatable && options.rotateOnTouch) {
257
+ const rotateDegree = getMaxRotateDegree(pointers);
258
+
259
+ if (rotateDegree !== 0) {
260
+ this.actionEvent = event;
261
+ this.rotate(rotateDegree);
262
+ }
263
+ }
264
+ break;
265
+
266
+ // Transform the current image
267
+ case ACTION_TRANSFORM:
268
+ if (options.zoomable && options.zoomOnTouch) {
269
+ const zoomRatio = getMaxZoomRatio(pointers);
270
+
271
+ if (zoomRatio !== 0) {
272
+ this.actionEvent = event;
273
+ this.zoom(zoomRatio);
274
+ }
275
+ }
276
+
277
+ if (options.rotatable && options.rotateOnTouch) {
278
+ const rotateDegree = getMaxRotateDegree(pointers);
279
+
280
+ if (rotateDegree !== 0) {
281
+ this.actionEvent = event;
282
+ this.rotate(rotateDegree);
283
+ }
284
+ }
233
285
  break;
234
286
 
235
287
  case ACTION_SWITCH: {
@@ -241,6 +293,8 @@ export default {
241
293
  // Empty `pointers` as `touchend` event will not be fired after swiped in iOS browsers.
242
294
  this.pointers = {};
243
295
 
296
+ this.actionEvent = event;
297
+
244
298
  if (offsetX > 1) {
245
299
  this.prev(options.loop);
246
300
  } else if (offsetX < -1) {
package/src/js/render.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ CLASS_ACTIVE,
2
3
  CLASS_LOADING,
3
4
  CLASS_TRANSITION,
4
5
  EVENT_ERROR,
@@ -11,11 +12,15 @@ import {
11
12
  addListener,
12
13
  assign,
13
14
  forEach,
15
+ getData,
14
16
  getImageNameFromURL,
15
17
  getImageNaturalSizes,
16
18
  getTransforms,
17
19
  hasClass,
20
+ inheritAttributes,
18
21
  isNumber,
22
+ isPlainObject,
23
+ isTransitionEnabled,
19
24
  removeClass,
20
25
  removeListener,
21
26
  setData,
@@ -31,7 +36,7 @@ export default {
31
36
  },
32
37
 
33
38
  initBody() {
34
- const { ownerDocument } = this.element;
39
+ const { ownerDocument } = this;
35
40
  const body = ownerDocument.body || ownerDocument.documentElement;
36
41
 
37
42
  this.body = body;
@@ -73,14 +78,45 @@ export default {
73
78
  }
74
79
  },
75
80
 
76
- initList() {
81
+ initList(viewIndex = this.index) {
77
82
  const { element, options, list } = this;
78
83
  const items = [];
84
+ const navbarOptions = isPlainObject(options.navbar) ? options.navbar : {};
85
+ const probe = document.createElement('li');
86
+
87
+ if (!this.containerData) {
88
+ this.initContainer();
89
+ }
90
+
91
+ list.appendChild(probe);
92
+ const itemWidth = probe.offsetWidth + parseInt(window.getComputedStyle(probe).marginLeft, 10);
93
+
94
+ list.removeChild(probe);
95
+
96
+ let visibleItemCount = isNumber(navbarOptions.visibleItemCount)
97
+ ? Math.floor(navbarOptions.visibleItemCount)
98
+ : Math.floor(this.containerData.width / itemWidth);
99
+
100
+ visibleItemCount = Math.min(visibleItemCount, this.length);
101
+
102
+ const start = visibleItemCount > 0
103
+ ? Math.min(
104
+ Math.max(0, viewIndex - Math.floor(visibleItemCount / 2)),
105
+ Math.max(0, this.length - visibleItemCount),
106
+ )
107
+ : 0;
108
+ const end = visibleItemCount > 0
109
+ ? Math.min(this.length, start + visibleItemCount)
110
+ : this.length;
79
111
 
80
112
  // initList may be called in this.update, so should keep idempotent
81
113
  list.innerHTML = '';
82
114
 
83
115
  forEach(this.images, (image, index) => {
116
+ if (visibleItemCount > 0 && (index < start || index >= end)) {
117
+ return;
118
+ }
119
+
84
120
  const { src } = image;
85
121
  const alt = image.alt || getImageNameFromURL(src);
86
122
  const url = this.getImageURL(image);
@@ -89,13 +125,7 @@ export default {
89
125
  const item = document.createElement('li');
90
126
  const img = document.createElement('img');
91
127
 
92
- forEach(options.inheritedAttributes, (name) => {
93
- const value = image.getAttribute(name);
94
-
95
- if (value !== null) {
96
- img.setAttribute(name, value);
97
- }
98
- });
128
+ inheritAttributes(img, image, options.inheritedAttributes);
99
129
 
100
130
  if (options.navbar) {
101
131
  img.src = src || url;
@@ -119,6 +149,15 @@ export default {
119
149
 
120
150
  this.items = items;
121
151
 
152
+ if (this.viewed) {
153
+ const activeItem = this.getItem(this.index);
154
+
155
+ if (activeItem) {
156
+ addClass(activeItem, CLASS_ACTIVE);
157
+ activeItem.setAttribute('aria-selected', true);
158
+ }
159
+ }
160
+
122
161
  forEach(items, (item) => {
123
162
  const image = item.firstElementChild;
124
163
  let onLoad;
@@ -152,7 +191,7 @@ export default {
152
191
  });
153
192
  });
154
193
 
155
- if (options.transition) {
194
+ if (isTransitionEnabled(options, 'view')) {
156
195
  addListener(element, EVENT_VIEWED, () => {
157
196
  addClass(list, CLASS_TRANSITION);
158
197
  }, {
@@ -161,9 +200,24 @@ export default {
161
200
  }
162
201
  },
163
202
 
203
+ getItem(index) {
204
+ let item;
205
+
206
+ forEach(this.items, (candidate) => {
207
+ if (Number(getData(candidate, 'index')) === index) {
208
+ item = candidate;
209
+ return false;
210
+ }
211
+
212
+ return true;
213
+ });
214
+
215
+ return item;
216
+ },
217
+
164
218
  renderList() {
165
219
  const { index } = this;
166
- const item = this.items[index];
220
+ const item = this.getItem(index);
167
221
 
168
222
  if (!item) {
169
223
  return;
@@ -176,9 +230,9 @@ export default {
176
230
 
177
231
  // Place the active item in the center of the screen
178
232
  setStyle(this.list, assign({
179
- width: outerWidth * this.length - gutter,
233
+ width: outerWidth * this.items.length - gutter,
180
234
  }, getTransforms({
181
- translateX: ((this.viewerData.width - offsetWidth) / 2) - outerWidth * index,
235
+ translateX: ((this.viewerData.width - offsetWidth) / 2) - item.offsetLeft,
182
236
  })));
183
237
  },
184
238
 
@@ -277,9 +331,27 @@ export default {
277
331
  marginTop: imageData.y,
278
332
  }, getTransforms(imageData)));
279
333
 
334
+ if (this.magnifierPoint) {
335
+ this.renderMagnifier();
336
+ }
337
+
280
338
  if (done) {
281
- if ((this.viewing || this.moving || this.rotating || this.scaling || this.zooming)
282
- && this.options.transition
339
+ let action = false;
340
+
341
+ if (this.viewing) {
342
+ action = 'view';
343
+ } else if (this.moving) {
344
+ action = 'move';
345
+ } else if (this.rotating) {
346
+ action = 'rotate';
347
+ } else if (this.scaling) {
348
+ action = 'scale';
349
+ } else if (this.zooming) {
350
+ action = 'zoom';
351
+ }
352
+
353
+ if (action
354
+ && isTransitionEnabled(this.options, action)
283
355
  && hasClass(image, CLASS_TRANSITION)) {
284
356
  const onTransitionEnd = () => {
285
357
  this.imageRendering = false;
@@ -1,15 +1,22 @@
1
1
  export default (
2
2
  '<div class="viewer-container" tabindex="-1" touch-action="none">'
3
3
  + '<div class="viewer-canvas"></div>'
4
- + '<div class="viewer-footer">'
5
- + '<div class="viewer-title"></div>'
6
- + '<div class="viewer-toolbar"></div>'
7
- + '<div class="viewer-navbar">'
4
+ + '<div class="viewer-magnifier" aria-hidden="true">'
5
+ + '<img class="viewer-magnifier-image" alt="">'
6
+ + '</div>'
7
+ + '<div class="viewer-navigation" aria-hidden="true">'
8
+ + '<div class="viewer-prev" data-viewer-action="prev" role="button" aria-label="Previous"></div>'
9
+ + '<div class="viewer-next" data-viewer-action="next" role="button" aria-label="Next"></div>'
10
+ + '</div>'
11
+ + '<div class="viewer-footer" aria-hidden="true">'
12
+ + '<div class="viewer-title" aria-hidden="true"></div>'
13
+ + '<div class="viewer-toolbar" aria-hidden="true"></div>'
14
+ + '<div class="viewer-navbar" aria-hidden="true">'
8
15
  + '<ul class="viewer-list" role="navigation"></ul>'
9
16
  + '</div>'
10
17
  + '</div>'
11
18
  + '<div class="viewer-tooltip" role="alert" aria-hidden="true"></div>'
12
- + '<div class="viewer-button" data-viewer-action="mix" role="button"></div>'
13
- + '<div class="viewer-player"></div>'
19
+ + '<div class="viewer-button" data-viewer-action="mix" role="button" aria-hidden="true"></div>'
20
+ + '<div class="viewer-player" aria-hidden="true"></div>'
14
21
  + '</div>'
15
22
  );
@@ -16,6 +16,33 @@ export function isString(value) {
16
16
  return typeof value === 'string';
17
17
  }
18
18
 
19
+ const MODIFIER_KEY_PROPERTIES = {
20
+ ctrl: 'ctrlKey',
21
+ shift: 'shiftKey',
22
+ alt: 'altKey',
23
+ meta: 'metaKey',
24
+ };
25
+
26
+ /**
27
+ * Check if a wheel option (`zoomOnWheel` or `slideOnWheel`) takes effect for the given wheel event.
28
+ * @param {boolean | string} option - The option value.
29
+ * @param {WheelEvent} event - The wheel event.
30
+ * @returns {boolean} Returns `true` if the option takes effect for the event, else `false`.
31
+ */
32
+ export function isWheelActionEnabled(option, event) {
33
+ if (!option) {
34
+ return false;
35
+ }
36
+
37
+ if (option === true) {
38
+ return true;
39
+ }
40
+
41
+ return isString(option) && option
42
+ .split('+')
43
+ .every((key) => event[MODIFIER_KEY_PROPERTIES[key.trim().toLowerCase()]]);
44
+ }
45
+
19
46
  /**
20
47
  * Check if the given value is not a number.
21
48
  */
@@ -106,6 +133,34 @@ export function forEach(data, callback) {
106
133
  return data;
107
134
  }
108
135
 
136
+ /**
137
+ * Inherit attributes from the original image.
138
+ * @param {Element} image - The target image.
139
+ * @param {Element} originalImage - The original image.
140
+ * @param {Array} inheritedAttributes - The attributes to inherit.
141
+ */
142
+ export function inheritAttributes(image, originalImage, inheritedAttributes) {
143
+ forEach(inheritedAttributes, (name) => {
144
+ const value = originalImage.getAttribute(name);
145
+
146
+ if (value !== null) {
147
+ image.setAttribute(name, value);
148
+ }
149
+ });
150
+ }
151
+
152
+ /**
153
+ * Check if transition is enabled for the given action.
154
+ * @param {Object} options - The viewer options.
155
+ * @param {string} action - The transition action.
156
+ * @returns {boolean} Returns `true` if transition is enabled.
157
+ */
158
+ export function isTransitionEnabled(options, action) {
159
+ const { transition } = options;
160
+
161
+ return transition && transition[action] !== false;
162
+ }
163
+
109
164
  /**
110
165
  * Extend the given object.
111
166
  * @param {*} obj - The object to be extended.
@@ -495,11 +550,8 @@ export function getTransforms({
495
550
  values.push(`rotate(${rotate}deg)`);
496
551
  }
497
552
 
498
- if (isNumber(scaleX) && scaleX !== 1) {
553
+ if (isNumber(scaleX) && isNumber(scaleY) && (scaleX !== 1 || scaleY !== 1)) {
499
554
  values.push(`scaleX(${scaleX})`);
500
- }
501
-
502
- if (isNumber(scaleY) && scaleY !== 1) {
503
555
  values.push(`scaleY(${scaleY})`);
504
556
  }
505
557
 
@@ -552,14 +604,7 @@ export function getImageNaturalSizes(image, options, callback) {
552
604
  }
553
605
  };
554
606
 
555
- forEach(options.inheritedAttributes, (name) => {
556
- const value = image.getAttribute(name);
557
-
558
- if (value !== null) {
559
- newImage.setAttribute(name, value);
560
- }
561
- });
562
-
607
+ inheritAttributes(newImage, image, options.inheritedAttributes);
563
608
  newImage.src = image.src;
564
609
 
565
610
  // iOS Safari will convert the image automatically
@@ -633,6 +678,44 @@ export function getMaxZoomRatio(pointers) {
633
678
  return ratios[0];
634
679
  }
635
680
 
681
+ /**
682
+ * Get the max rotation degree of a group of pointers.
683
+ * @param {Object} pointers - The target pointers.
684
+ * @returns {number} The result degree.
685
+ */
686
+ export function getMaxRotateDegree(pointers) {
687
+ const pointers2 = { ...pointers };
688
+ const degrees = [];
689
+
690
+ forEach(pointers, (pointer, pointerId) => {
691
+ delete pointers2[pointerId];
692
+
693
+ forEach(pointers2, (pointer2) => {
694
+ const start = Math.atan2(
695
+ pointer2.startY - pointer.startY,
696
+ pointer2.startX - pointer.startX,
697
+ );
698
+ const end = Math.atan2(
699
+ pointer2.endY - pointer.endY,
700
+ pointer2.endX - pointer.endX,
701
+ );
702
+ let radians = end - start;
703
+
704
+ if (radians > Math.PI) {
705
+ radians -= Math.PI * 2;
706
+ } else if (radians < -Math.PI) {
707
+ radians += Math.PI * 2;
708
+ }
709
+
710
+ degrees.push((radians * 180) / Math.PI);
711
+ });
712
+ });
713
+
714
+ degrees.sort((a, b) => Math.abs(b) - Math.abs(a));
715
+
716
+ return degrees[0] || 0;
717
+ }
718
+
636
719
  /**
637
720
  * Get a pointer from an event object.
638
721
  * @param {Object} event - The target event object.
package/src/js/viewer.js CHANGED
@@ -29,6 +29,7 @@ import {
29
29
  assign,
30
30
  dispatchEvent,
31
31
  forEach,
32
+ getData,
32
33
  getResponsiveClass,
33
34
  hyphenate,
34
35
  isFunction,
@@ -51,17 +52,19 @@ const getUniqueID = ((id) => (() => {
51
52
  class Viewer {
52
53
  /**
53
54
  * Create a new Viewer.
54
- * @param {Element} element - The target element for viewing.
55
+ * @param {Element} element - The target image, or a container of images, to view.
55
56
  * @param {Object} [options={}] - The configuration options.
56
57
  */
57
58
  constructor(element, options = {}) {
58
- if (!element || element.nodeType !== 1) {
59
+ if (!element || (element.nodeType !== 1 && element.nodeType !== 11)) {
59
60
  throw new Error('The first argument is required and must be an element.');
60
61
  }
61
62
 
62
63
  this.element = element;
64
+ this.ownerDocument = element.ownerDocument || element.host.ownerDocument;
63
65
  this.options = assign({}, DEFAULTS, isPlainObject(options) && options);
64
66
  this.action = false;
67
+ this.actionEvent = null;
65
68
  this.fading = false;
66
69
  this.fulled = false;
67
70
  this.hiding = false;
@@ -123,7 +126,7 @@ class Viewer {
123
126
  this.initBody();
124
127
 
125
128
  // Override `transition` option if it is not supported
126
- if (isUndefined(document.createElement(NAMESPACE).style.transition)) {
129
+ if (isUndefined(this.ownerDocument.createElement(NAMESPACE).style.transition)) {
127
130
  options.transition = false;
128
131
  }
129
132
 
@@ -183,8 +186,11 @@ class Viewer {
183
186
  }
184
187
  });
185
188
  } else {
186
- addListener(element, EVENT_CLICK, (this.onElementClick = ({ target }) => {
189
+ addListener(element, EVENT_CLICK, (this.onElementClick = (event) => {
190
+ const { target } = event;
191
+
187
192
  if (target.localName === 'img' && (!isFunction(options.filter) || options.filter.call(this, target))) {
193
+ this.actionEvent = event;
188
194
  this.view(this.images.indexOf(target));
189
195
  }
190
196
  }));
@@ -195,6 +201,7 @@ class Viewer {
195
201
  event.preventDefault();
196
202
 
197
203
  if (!isFunction(options.filter) || options.filter.call(this, target)) {
204
+ this.actionEvent = event;
198
205
  this.view(this.images.indexOf(target));
199
206
  }
200
207
  }
@@ -217,6 +224,7 @@ class Viewer {
217
224
  const title = viewer.querySelector(`.${NAMESPACE}-title`);
218
225
  const toolbar = viewer.querySelector(`.${NAMESPACE}-toolbar`);
219
226
  const navbar = viewer.querySelector(`.${NAMESPACE}-navbar`);
227
+ const navigation = viewer.querySelector(`.${NAMESPACE}-navigation`);
220
228
  const button = viewer.querySelector(`.${NAMESPACE}-button`);
221
229
  const canvas = viewer.querySelector(`.${NAMESPACE}-canvas`);
222
230
 
@@ -225,9 +233,12 @@ class Viewer {
225
233
  this.title = title;
226
234
  this.toolbar = toolbar;
227
235
  this.navbar = navbar;
236
+ this.navigation = navigation;
228
237
  this.button = button;
229
238
  this.canvas = canvas;
230
239
  this.footer = viewer.querySelector(`.${NAMESPACE}-footer`);
240
+ this.magnifier = viewer.querySelector(`.${NAMESPACE}-magnifier`);
241
+ this.magnifierImage = viewer.querySelector(`.${NAMESPACE}-magnifier-image`);
231
242
  this.tooltipBox = viewer.querySelector(`.${NAMESPACE}-tooltip`);
232
243
  this.player = viewer.querySelector(`.${NAMESPACE}-player`);
233
244
  this.list = viewer.querySelector(`.${NAMESPACE}-list`);
@@ -237,11 +248,70 @@ class Viewer {
237
248
  addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title)
238
249
  ? options.title[0]
239
250
  : options.title));
240
- addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar));
251
+
252
+ if (options.title) {
253
+ title.removeAttribute('aria-hidden');
254
+ }
255
+ const navbarOptions = isPlainObject(options.navbar) ? options.navbar : {};
256
+ let navbarShow = options.navbar;
257
+ const navbarSize = !isUndefined(navbarOptions.size) ? navbarOptions.size : options.navbar;
258
+
259
+ if (isPlainObject(options.navbar)) {
260
+ navbarShow = !isUndefined(navbarOptions.show) ? navbarOptions.show : true;
261
+ }
262
+
263
+ addClass(navbar, !navbarShow ? CLASS_HIDE : getResponsiveClass(navbarShow));
264
+
265
+ if (navbarShow) {
266
+ navbar.removeAttribute('aria-hidden');
267
+ }
268
+
269
+ if (['small', 'medium', 'large'].indexOf(navbarSize) !== -1) {
270
+ addClass(navbar, `${NAMESPACE}-${navbarSize}`);
271
+ }
272
+
273
+ if (isPlainObject(options.navigation)) {
274
+ forEach(navigation.querySelectorAll('[role="button"]'), (item) => {
275
+ const name = getData(item, DATA_ACTION);
276
+ const value = options.navigation[name];
277
+ const deep = isPlainObject(value);
278
+ const show = deep && !isUndefined(value.show) ? value.show : value;
279
+ const size = deep && !isUndefined(value.size)
280
+ ? value.size
281
+ : value;
282
+
283
+ toggleClass(item, CLASS_HIDE, !show);
284
+
285
+ if (isNumber(show)) {
286
+ addClass(item, getResponsiveClass(show));
287
+ }
288
+
289
+ if (['small', 'large'].indexOf(size) !== -1) {
290
+ addClass(item, `${NAMESPACE}-${size}`);
291
+ }
292
+ });
293
+ } else {
294
+ addClass(
295
+ navigation,
296
+ !options.navigation ? CLASS_HIDE : getResponsiveClass(options.navigation),
297
+ );
298
+ }
299
+
300
+ if (options.navigation) {
301
+ navigation.removeAttribute('aria-hidden');
302
+ }
303
+
241
304
  toggleClass(button, CLASS_HIDE, !options.button);
242
305
 
306
+ if (options.button) {
307
+ button.removeAttribute('aria-hidden');
308
+ }
309
+
243
310
  if (options.keyboard) {
244
311
  button.setAttribute('tabindex', 0);
312
+ forEach(navigation.querySelectorAll('[role="button"]'), (item) => {
313
+ item.setAttribute('tabindex', 0);
314
+ });
245
315
  }
246
316
 
247
317
  if (options.backdrop) {
@@ -270,6 +340,8 @@ class Viewer {
270
340
  addClass(toolbar, getResponsiveClass(options.toolbar));
271
341
  }
272
342
 
343
+ toolbar.removeAttribute('aria-hidden');
344
+
273
345
  forEach(custom ? options.toolbar : BUTTONS, (value, index) => {
274
346
  const deep = custom && isPlainObject(value);
275
347
  const name = custom ? hyphenate(index) : value;
@@ -321,6 +393,12 @@ class Viewer {
321
393
  addClass(toolbar, CLASS_HIDE);
322
394
  }
323
395
 
396
+ if (options.title || navbarShow || options.toolbar) {
397
+ this.footer.removeAttribute('aria-hidden');
398
+ } else {
399
+ addClass(this.footer, CLASS_HIDE);
400
+ }
401
+
324
402
  if (!options.rotatable) {
325
403
  const rotates = toolbar.querySelectorAll('li[class*="rotate"]');
326
404
 
@@ -356,7 +434,7 @@ class Viewer {
356
434
  let { container } = options;
357
435
 
358
436
  if (isString(container)) {
359
- container = element.ownerDocument.querySelector(container);
437
+ container = this.ownerDocument.querySelector(container);
360
438
  }
361
439
 
362
440
  if (!container) {
@@ -391,12 +469,13 @@ class Viewer {
391
469
  }
392
470
 
393
471
  /**
394
- * Get the no conflict viewer class.
395
- * @returns {Viewer} The viewer class.
472
+ * Create a new Viewer instance.
473
+ * @param {Element} element - The target image, or a container of images, to view.
474
+ * @param {Object} [options={}] - The configuration options.
475
+ * @returns {Viewer} A new Viewer instance.
396
476
  */
397
- static noConflict() {
398
- window.Viewer = AnotherViewer;
399
- return Viewer;
477
+ static create(element, options) {
478
+ return new Viewer(element, options);
400
479
  }
401
480
 
402
481
  /**
@@ -406,6 +485,15 @@ class Viewer {
406
485
  static setDefaults(options) {
407
486
  assign(DEFAULTS, isPlainObject(options) && options);
408
487
  }
488
+
489
+ /**
490
+ * Get the no conflict viewer class.
491
+ * @returns {Viewer} The viewer class.
492
+ */
493
+ static noConflict() {
494
+ window.Viewer = AnotherViewer;
495
+ return Viewer;
496
+ }
409
497
  }
410
498
 
411
499
  assign(Viewer.prototype, render, events, handlers, methods, others);