viewerjs 1.11.9 → 1.13.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.
@@ -106,6 +106,34 @@ export function forEach(data, callback) {
106
106
  return data;
107
107
  }
108
108
 
109
+ /**
110
+ * Inherit attributes from the original image.
111
+ * @param {Element} image - The target image.
112
+ * @param {Element} originalImage - The original image.
113
+ * @param {Array} inheritedAttributes - The attributes to inherit.
114
+ */
115
+ export function inheritAttributes(image, originalImage, inheritedAttributes) {
116
+ forEach(inheritedAttributes, (name) => {
117
+ const value = originalImage.getAttribute(name);
118
+
119
+ if (value !== null) {
120
+ image.setAttribute(name, value);
121
+ }
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Check if transition is enabled for the given action.
127
+ * @param {Object} options - The viewer options.
128
+ * @param {string} action - The transition action.
129
+ * @returns {boolean} Returns `true` if transition is enabled.
130
+ */
131
+ export function isTransitionEnabled(options, action) {
132
+ const { transition } = options;
133
+
134
+ return transition && transition[action] !== false;
135
+ }
136
+
109
137
  /**
110
138
  * Extend the given object.
111
139
  * @param {*} obj - The object to be extended.
@@ -172,7 +200,7 @@ export function hasClass(element, value) {
172
200
 
173
201
  return element.classList
174
202
  ? element.classList.contains(value)
175
- : element.className.indexOf(value) > -1;
203
+ : element.className.split(REGEXP_SPACES).indexOf(value) > -1;
176
204
  }
177
205
 
178
206
  /**
@@ -319,7 +347,7 @@ export function removeData(element, name) {
319
347
  element[name] = undefined;
320
348
  }
321
349
  } else if (element.dataset) {
322
- // #128 Safari not allows to delete dataset property
350
+ // Safari doesn't allow deleting dataset properties
323
351
  try {
324
352
  delete element.dataset[name];
325
353
  } catch (error) {
@@ -495,11 +523,8 @@ export function getTransforms({
495
523
  values.push(`rotate(${rotate}deg)`);
496
524
  }
497
525
 
498
- if (isNumber(scaleX) && scaleX !== 1) {
526
+ if (isNumber(scaleX) && isNumber(scaleY) && (scaleX !== 1 || scaleY !== 1)) {
499
527
  values.push(`scaleX(${scaleX})`);
500
- }
501
-
502
- if (isNumber(scaleY) && scaleY !== 1) {
503
528
  values.push(`scaleY(${scaleY})`);
504
529
  }
505
530
 
@@ -552,14 +577,7 @@ export function getImageNaturalSizes(image, options, callback) {
552
577
  }
553
578
  };
554
579
 
555
- forEach(options.inheritedAttributes, (name) => {
556
- const value = image.getAttribute(name);
557
-
558
- if (value !== null) {
559
- newImage.setAttribute(name, value);
560
- }
561
- });
562
-
580
+ inheritAttributes(newImage, image, options.inheritedAttributes);
563
581
  newImage.src = image.src;
564
582
 
565
583
  // iOS Safari will convert the image automatically
@@ -633,6 +651,44 @@ export function getMaxZoomRatio(pointers) {
633
651
  return ratios[0];
634
652
  }
635
653
 
654
+ /**
655
+ * Get the max rotation degree of a group of pointers.
656
+ * @param {Object} pointers - The target pointers.
657
+ * @returns {number} The result degree.
658
+ */
659
+ export function getMaxRotateDegree(pointers) {
660
+ const pointers2 = { ...pointers };
661
+ const degrees = [];
662
+
663
+ forEach(pointers, (pointer, pointerId) => {
664
+ delete pointers2[pointerId];
665
+
666
+ forEach(pointers2, (pointer2) => {
667
+ const start = Math.atan2(
668
+ pointer2.startY - pointer.startY,
669
+ pointer2.startX - pointer.startX,
670
+ );
671
+ const end = Math.atan2(
672
+ pointer2.endY - pointer.endY,
673
+ pointer2.endX - pointer.endX,
674
+ );
675
+ let radians = end - start;
676
+
677
+ if (radians > Math.PI) {
678
+ radians -= Math.PI * 2;
679
+ } else if (radians < -Math.PI) {
680
+ radians += Math.PI * 2;
681
+ }
682
+
683
+ degrees.push((radians * 180) / Math.PI);
684
+ });
685
+ });
686
+
687
+ degrees.sort((a, b) => Math.abs(b) - Math.abs(a));
688
+
689
+ return degrees[0] || 0;
690
+ }
691
+
636
692
  /**
637
693
  * Get a pointer from an event object.
638
694
  * @param {Object} event - The target event object.
package/src/js/viewer.js CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  DATA_ACTION,
17
17
  EVENT_CLICK,
18
18
  EVENT_ERROR,
19
+ EVENT_KEY_DOWN,
19
20
  EVENT_LOAD,
20
21
  EVENT_READY,
21
22
  NAMESPACE,
@@ -28,6 +29,7 @@ import {
28
29
  assign,
29
30
  dispatchEvent,
30
31
  forEach,
32
+ getData,
31
33
  getResponsiveClass,
32
34
  hyphenate,
33
35
  isFunction,
@@ -50,15 +52,16 @@ const getUniqueID = ((id) => (() => {
50
52
  class Viewer {
51
53
  /**
52
54
  * Create a new Viewer.
53
- * @param {Element} element - The target element for viewing.
55
+ * @param {Element} element - The target image, or a container of images, to view.
54
56
  * @param {Object} [options={}] - The configuration options.
55
57
  */
56
58
  constructor(element, options = {}) {
57
- if (!element || element.nodeType !== 1) {
59
+ if (!element || (element.nodeType !== 1 && element.nodeType !== 11)) {
58
60
  throw new Error('The first argument is required and must be an element.');
59
61
  }
60
62
 
61
63
  this.element = element;
64
+ this.ownerDocument = element.ownerDocument || element.host.ownerDocument;
62
65
  this.options = assign({}, DEFAULTS, isPlainObject(options) && options);
63
66
  this.action = false;
64
67
  this.fading = false;
@@ -122,7 +125,7 @@ class Viewer {
122
125
  this.initBody();
123
126
 
124
127
  // Override `transition` option if it is not supported
125
- if (isUndefined(document.createElement(NAMESPACE).style.transition)) {
128
+ if (isUndefined(this.ownerDocument.createElement(NAMESPACE).style.transition)) {
126
129
  options.transition = false;
127
130
  }
128
131
 
@@ -182,11 +185,22 @@ class Viewer {
182
185
  }
183
186
  });
184
187
  } else {
185
- addListener(element, EVENT_CLICK, (this.onStart = ({ target }) => {
188
+ addListener(element, EVENT_CLICK, (this.onElementClick = ({ target }) => {
186
189
  if (target.localName === 'img' && (!isFunction(options.filter) || options.filter.call(this, target))) {
187
190
  this.view(this.images.indexOf(target));
188
191
  }
189
192
  }));
193
+ addListener(element, EVENT_KEY_DOWN, (this.onElementKeyDown = (event) => {
194
+ const { target, key } = event;
195
+
196
+ if (target.localName === 'img' && (key === 'Enter' || key === ' ')) {
197
+ event.preventDefault();
198
+
199
+ if (!isFunction(options.filter) || options.filter.call(this, target)) {
200
+ this.view(this.images.indexOf(target));
201
+ }
202
+ }
203
+ }));
190
204
  }
191
205
  }
192
206
 
@@ -205,6 +219,7 @@ class Viewer {
205
219
  const title = viewer.querySelector(`.${NAMESPACE}-title`);
206
220
  const toolbar = viewer.querySelector(`.${NAMESPACE}-toolbar`);
207
221
  const navbar = viewer.querySelector(`.${NAMESPACE}-navbar`);
222
+ const navigation = viewer.querySelector(`.${NAMESPACE}-navigation`);
208
223
  const button = viewer.querySelector(`.${NAMESPACE}-button`);
209
224
  const canvas = viewer.querySelector(`.${NAMESPACE}-canvas`);
210
225
 
@@ -213,9 +228,12 @@ class Viewer {
213
228
  this.title = title;
214
229
  this.toolbar = toolbar;
215
230
  this.navbar = navbar;
231
+ this.navigation = navigation;
216
232
  this.button = button;
217
233
  this.canvas = canvas;
218
234
  this.footer = viewer.querySelector(`.${NAMESPACE}-footer`);
235
+ this.magnifier = viewer.querySelector(`.${NAMESPACE}-magnifier`);
236
+ this.magnifierImage = viewer.querySelector(`.${NAMESPACE}-magnifier-image`);
219
237
  this.tooltipBox = viewer.querySelector(`.${NAMESPACE}-tooltip`);
220
238
  this.player = viewer.querySelector(`.${NAMESPACE}-player`);
221
239
  this.list = viewer.querySelector(`.${NAMESPACE}-list`);
@@ -225,11 +243,54 @@ class Viewer {
225
243
  addClass(title, !options.title ? CLASS_HIDE : getResponsiveClass(Array.isArray(options.title)
226
244
  ? options.title[0]
227
245
  : options.title));
228
- addClass(navbar, !options.navbar ? CLASS_HIDE : getResponsiveClass(options.navbar));
246
+ const navbarOptions = isPlainObject(options.navbar) ? options.navbar : {};
247
+ let navbarShow = options.navbar;
248
+ const navbarSize = !isUndefined(navbarOptions.size) ? navbarOptions.size : options.navbar;
249
+
250
+ if (isPlainObject(options.navbar)) {
251
+ navbarShow = !isUndefined(navbarOptions.show) ? navbarOptions.show : true;
252
+ }
253
+
254
+ addClass(navbar, !navbarShow ? CLASS_HIDE : getResponsiveClass(navbarShow));
255
+
256
+ if (['small', 'medium', 'large'].indexOf(navbarSize) !== -1) {
257
+ addClass(navbar, `${NAMESPACE}-${navbarSize}`);
258
+ }
259
+
260
+ if (isPlainObject(options.navigation)) {
261
+ forEach(navigation.querySelectorAll('[role="button"]'), (item) => {
262
+ const name = getData(item, DATA_ACTION);
263
+ const value = options.navigation[name];
264
+ const deep = isPlainObject(value);
265
+ const show = deep && !isUndefined(value.show) ? value.show : value;
266
+ const size = deep && !isUndefined(value.size)
267
+ ? value.size
268
+ : value;
269
+
270
+ toggleClass(item, CLASS_HIDE, !show);
271
+
272
+ if (isNumber(show)) {
273
+ addClass(item, getResponsiveClass(show));
274
+ }
275
+
276
+ if (['small', 'large'].indexOf(size) !== -1) {
277
+ addClass(item, `${NAMESPACE}-${size}`);
278
+ }
279
+ });
280
+ } else {
281
+ addClass(
282
+ navigation,
283
+ !options.navigation ? CLASS_HIDE : getResponsiveClass(options.navigation),
284
+ );
285
+ }
286
+
229
287
  toggleClass(button, CLASS_HIDE, !options.button);
230
288
 
231
289
  if (options.keyboard) {
232
290
  button.setAttribute('tabindex', 0);
291
+ forEach(navigation.querySelectorAll('[role="button"]'), (item) => {
292
+ item.setAttribute('tabindex', 0);
293
+ });
233
294
  }
234
295
 
235
296
  if (options.backdrop) {
@@ -344,7 +405,7 @@ class Viewer {
344
405
  let { container } = options;
345
406
 
346
407
  if (isString(container)) {
347
- container = element.ownerDocument.querySelector(container);
408
+ container = this.ownerDocument.querySelector(container);
348
409
  }
349
410
 
350
411
  if (!container) {
@@ -379,12 +440,13 @@ class Viewer {
379
440
  }
380
441
 
381
442
  /**
382
- * Get the no conflict viewer class.
383
- * @returns {Viewer} The viewer class.
443
+ * Create a new Viewer instance.
444
+ * @param {Element} element - The target image, or a container of images, to view.
445
+ * @param {Object} [options={}] - The configuration options.
446
+ * @returns {Viewer} A new Viewer instance.
384
447
  */
385
- static noConflict() {
386
- window.Viewer = AnotherViewer;
387
- return Viewer;
448
+ static create(element, options) {
449
+ return new Viewer(element, options);
388
450
  }
389
451
 
390
452
  /**
@@ -394,6 +456,15 @@ class Viewer {
394
456
  static setDefaults(options) {
395
457
  assign(DEFAULTS, isPlainObject(options) && options);
396
458
  }
459
+
460
+ /**
461
+ * Get the no conflict viewer class.
462
+ * @returns {Viewer} The viewer class.
463
+ */
464
+ static noConflict() {
465
+ window.Viewer = AnotherViewer;
466
+ return Viewer;
467
+ }
397
468
  }
398
469
 
399
470
  assign(Viewer.prototype, render, events, handlers, methods, others);
package/types/index.d.ts CHANGED
@@ -1,10 +1,16 @@
1
1
  declare namespace Viewer {
2
2
  export type Visibility = 0 | 1 | 2 | 3 | 4;
3
3
  export type ToolbarButtonSize = 'small' | 'medium' | 'large';
4
- export type ToolbarOption = boolean | Visibility | ToolbarButtonSize | Function | ToolbarButtonOptions | undefined;
4
+ export type EventHandler<T extends Event = CustomEvent> = (event: T) => void;
5
+ export type Filter = (this: Viewer, image: HTMLImageElement) => boolean;
6
+ export type TitleRenderer = (this: Viewer, image: HTMLImageElement, imageData: Record<string, any>) => string;
7
+ export type ImageURLResolver = (this: Viewer, image: HTMLImageElement) => string;
8
+ export type ZoomRatio = number | ((this: Viewer, image: HTMLImageElement, imageData: Record<string, any>) => number);
9
+ export type ToolbarButtonClick = (this: Viewer, event: Event) => void;
10
+ export type ToolbarOption = boolean | Visibility | ToolbarButtonSize | ToolbarButtonOptions | undefined;
5
11
 
6
12
  export interface ToolbarButtonOptions {
7
- click?: Function,
13
+ click?: ToolbarButtonClick,
8
14
  show?: boolean | Visibility;
9
15
  size?: ToolbarButtonSize,
10
16
  }
@@ -24,9 +30,47 @@ declare namespace Viewer {
24
30
  [x: string]: ToolbarOption;
25
31
  }
26
32
 
33
+ export interface NavigationButtonOptions {
34
+ show?: boolean | Visibility;
35
+ size?: ToolbarButtonSize;
36
+ }
37
+
38
+ export type NavigationOption = boolean | Visibility | NavigationButtonOptions | undefined;
39
+
40
+ export interface NavbarOptions {
41
+ visibleItemCount?: number;
42
+ show?: boolean | Visibility;
43
+ size?: ToolbarButtonSize;
44
+ }
45
+
46
+ export type NavbarOption = boolean | Visibility | ToolbarButtonSize | NavbarOptions | undefined;
47
+
48
+ export interface NavigationOptions {
49
+ next?: NavigationOption;
50
+ prev?: NavigationOption;
51
+ }
52
+
53
+ export interface TransitionOptions {
54
+ hide?: boolean;
55
+ move?: boolean;
56
+ play?: boolean;
57
+ rotate?: boolean;
58
+ scale?: boolean;
59
+ show?: boolean;
60
+ tooltip?: boolean;
61
+ view?: boolean;
62
+ zoom?: boolean;
63
+ }
64
+
65
+ export interface MagnifierOptions {
66
+ size?: number;
67
+ zoomRatio?: number;
68
+ opacity?: number;
69
+ }
70
+
27
71
  export interface Pivot {
28
- x: Number;
29
- y: Number;
72
+ x: number;
73
+ y: number;
30
74
  }
31
75
 
32
76
  export interface MoveEventData {
@@ -37,26 +81,18 @@ declare namespace Viewer {
37
81
  originalEvent: PointerEvent | TouchEvent | MouseEvent | null;
38
82
  }
39
83
 
40
- export interface MoveEvent extends CustomEvent {
41
- detail: MoveEventData;
42
- }
84
+ export interface MoveEvent extends CustomEvent<MoveEventData> {}
43
85
 
44
- export interface MovedEvent extends CustomEvent {
45
- detail: MoveEventData;
46
- }
86
+ export interface MovedEvent extends CustomEvent<MoveEventData> {}
47
87
 
48
88
  export interface RotateEventData {
49
89
  degree: number;
50
90
  oldDegree: number;
51
91
  }
52
92
 
53
- export interface RotateEvent extends CustomEvent {
54
- detail: RotateEventData;
55
- }
93
+ export interface RotateEvent extends CustomEvent<RotateEventData> {}
56
94
 
57
- export interface RotatedEvent extends CustomEvent {
58
- detail: RotateEventData;
59
- }
95
+ export interface RotatedEvent extends CustomEvent<RotateEventData> {}
60
96
 
61
97
  export interface ScaleEventData {
62
98
  scaleX: number;
@@ -65,13 +101,9 @@ declare namespace Viewer {
65
101
  oldScaleY: number;
66
102
  }
67
103
 
68
- export interface ScaleEvent extends CustomEvent {
69
- detail: ScaleEventData;
70
- }
104
+ export interface ScaleEvent extends CustomEvent<ScaleEventData> {}
71
105
 
72
- export interface ScaledEvent extends CustomEvent {
73
- detail: ScaleEventData;
74
- }
106
+ export interface ScaledEvent extends CustomEvent<ScaleEventData> {}
75
107
 
76
108
  export interface ZoomEventData {
77
109
  ratio: number;
@@ -79,73 +111,76 @@ declare namespace Viewer {
79
111
  originalEvent: WheelEvent | PointerEvent | TouchEvent | MouseEvent | null;
80
112
  }
81
113
 
82
- export interface ZoomEvent extends CustomEvent {
83
- detail: ZoomEventData;
84
- }
114
+ export interface ZoomEvent extends CustomEvent<ZoomEventData> {}
85
115
 
86
- export interface ZoomedEvent extends CustomEvent {
87
- detail: ZoomEventData;
88
- }
116
+ export interface ZoomedEvent extends CustomEvent<ZoomEventData> {}
89
117
 
90
118
  export interface Options {
91
119
  backdrop?: boolean | string;
92
120
  button?: boolean;
93
121
  className?: string;
94
122
  container?: string | HTMLElement;
95
- filter?: Function;
123
+ filter?: Filter | null;
96
124
  fullscreen?: boolean | FullscreenOptions;
97
125
  focus?: boolean;
98
- hidden?(event: CustomEvent): void;
99
- hide?(event: CustomEvent): void;
126
+ hidden?: EventHandler;
127
+ hide?: EventHandler;
100
128
  inheritedAttributes?: string[];
101
129
  initialCoverage?: number;
102
130
  initialViewIndex?: number;
103
131
  inline?: boolean;
132
+ autoplay?: boolean;
104
133
  interval?: number;
105
134
  keyboard?: boolean;
106
135
  loading?: boolean;
107
136
  loop?: boolean;
108
- maxZoomRatio?: number;
137
+ magnifier?: boolean | MagnifierOptions;
138
+ maxZoomRatio?: ZoomRatio;
109
139
  minHeight?: number;
110
140
  minWidth?: number;
111
- minZoomRatio?: number;
141
+ minZoomRatio?: ZoomRatio;
112
142
  movable?: boolean;
113
- move?(event: MoveEvent): void;
114
- moved?(event: MovedEvent): void;
115
- navbar?: boolean | Visibility;
116
- play?(event: CustomEvent): void;
117
- ready?(event: CustomEvent): void;
143
+ move?: EventHandler<MoveEvent>;
144
+ moved?: EventHandler<MovedEvent>;
145
+ navbar?: NavbarOption;
146
+ navigation?: boolean | Visibility | NavigationOptions;
147
+ play?: EventHandler;
148
+ preload?: boolean;
149
+ ready?: EventHandler;
118
150
  rotatable?: boolean;
119
- rotate?(event: RotateEvent): void;
120
- rotated?(event: RotatedEvent): void;
151
+ rotateOnGesture?: boolean;
152
+ rotateOnTouch?: boolean;
153
+ rotate?: EventHandler<RotateEvent>;
154
+ rotated?: EventHandler<RotatedEvent>;
121
155
  scalable?: boolean;
122
- scale?(event: ScaleEvent): void;
123
- scaled?(event: ScaledEvent): void;
124
- show?(event: CustomEvent): void;
125
- shown?(event: CustomEvent): void;
156
+ scale?: EventHandler<ScaleEvent>;
157
+ scaled?: EventHandler<ScaledEvent>;
158
+ show?: EventHandler;
159
+ shown?: EventHandler;
126
160
  slideOnTouch?: boolean;
127
- stop?(event: CustomEvent): void;
128
- title?: boolean | Visibility | Function | [Visibility, Function];
161
+ stop?: EventHandler;
162
+ title?: boolean | Visibility | TitleRenderer | [Visibility, TitleRenderer] | null;
129
163
  toggleOnDblclick?: boolean;
130
164
  toolbar?: boolean | Visibility | ToolbarOptions;
131
165
  tooltip?: boolean;
132
- transition?: boolean;
133
- url?: string | Function;
134
- view?(event: CustomEvent): void;
135
- viewed?(event: CustomEvent): void;
166
+ transition?: boolean | TransitionOptions;
167
+ url?: string | ImageURLResolver;
168
+ view?: EventHandler;
169
+ viewed?: EventHandler;
136
170
  zIndex?: number;
137
171
  zIndexInline?: number;
138
- zoom?(event: ZoomEvent): void;
172
+ zoom?: EventHandler<ZoomEvent>;
173
+ zoomOnGesture?: boolean;
139
174
  zoomOnTouch?: boolean;
140
175
  zoomOnWheel?: boolean;
141
176
  zoomRatio?: number;
142
177
  zoomable?: boolean;
143
- zoomed?(event: ZoomedEvent): void;
178
+ zoomed?: EventHandler<ZoomedEvent>;
144
179
  }
145
180
  }
146
181
 
147
182
  declare class Viewer {
148
- constructor(element: HTMLElement, options?: Viewer.Options);
183
+ constructor(element: HTMLElement | ShadowRoot, options?: Viewer.Options);
149
184
  destroy(): Viewer;
150
185
  exit(): Viewer;
151
186
  full(): Viewer;
@@ -165,12 +200,13 @@ declare class Viewer {
165
200
  stop(): Viewer;
166
201
  toggle(): Viewer;
167
202
  tooltip(): Viewer;
168
- update(): Viewer;
203
+ update(options?: Viewer.Options): Viewer;
169
204
  view(index?: number): Viewer;
170
205
  zoom(ratio: number, hasTooltip?: boolean, pivot?: Viewer.Pivot): Viewer;
171
206
  zoomTo(ratio: number, hasTooltip?: boolean, pivot?: Viewer.Pivot): Viewer;
172
- static noConflict(): Viewer;
207
+ static create(element: HTMLElement | ShadowRoot, options?: Viewer.Options): Viewer;
173
208
  static setDefaults(options: Viewer.Options): void;
209
+ static noConflict(): Viewer;
174
210
  }
175
211
 
176
212
  declare module 'viewerjs' {