1 | import { extend } from "../shared/utils/extend.js";
|
2 | import { canUseDOM } from "../shared/utils/canUseDOM.js";
|
3 | import { FOCUSABLE_ELEMENTS, setFocusOn } from "../shared/utils/setFocusOn.js";
|
4 |
|
5 | import { Base } from "../shared/Base/Base.js";
|
6 |
|
7 | import { Carousel } from "../Carousel/Carousel.js";
|
8 |
|
9 | import { Plugins } from "./plugins/index.js";
|
10 |
|
11 |
|
12 | import en from "./l10n/en.js";
|
13 |
|
14 |
|
15 | const defaults = {
|
16 |
|
17 | startIndex: 0,
|
18 |
|
19 |
|
20 | preload: 1,
|
21 |
|
22 |
|
23 | infinite: true,
|
24 |
|
25 |
|
26 | showClass: "fancybox-zoomInUp",
|
27 |
|
28 |
|
29 | hideClass: "fancybox-fadeOut",
|
30 |
|
31 |
|
32 | animated: true,
|
33 |
|
34 |
|
35 | hideScrollbar: true,
|
36 |
|
37 |
|
38 | parentEl: null,
|
39 |
|
40 |
|
41 | mainClass: null,
|
42 |
|
43 |
|
44 | autoFocus: true,
|
45 |
|
46 |
|
47 | trapFocus: true,
|
48 |
|
49 |
|
50 | placeFocusBack: true,
|
51 |
|
52 |
|
53 | click: "close",
|
54 |
|
55 |
|
56 | closeButton: "inside",
|
57 |
|
58 |
|
59 | dragToClose: true,
|
60 |
|
61 |
|
62 | keyboard: {
|
63 | Escape: "close",
|
64 | Delete: "close",
|
65 | Backspace: "close",
|
66 | PageUp: "next",
|
67 | PageDown: "prev",
|
68 | ArrowUp: "next",
|
69 | ArrowDown: "prev",
|
70 | ArrowRight: "next",
|
71 | ArrowLeft: "prev",
|
72 | },
|
73 |
|
74 |
|
75 | template: {
|
76 |
|
77 | closeButton:
|
78 | '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" tabindex="-1"><path d="M20 20L4 4m16 0L4 20"/></svg>',
|
79 |
|
80 | spinner:
|
81 | '<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="25 25 50 50" tabindex="-1"><circle cx="50" cy="50" r="20"/></svg>',
|
82 |
|
83 |
|
84 | main: null,
|
85 | },
|
86 |
|
87 | |
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 | l10n: en,
|
96 | };
|
97 |
|
98 |
|
99 | const instances = new Map();
|
100 |
|
101 |
|
102 | let called = 0;
|
103 |
|
104 | class Fancybox extends Base {
|
105 | |
106 |
|
107 |
|
108 |
|
109 |
|
110 | constructor(items, options = {}) {
|
111 |
|
112 | items = items.map((item) => {
|
113 | if (item.width) item._width = item.width;
|
114 | if (item.height) item._height = item.height;
|
115 |
|
116 | return item;
|
117 | });
|
118 |
|
119 | super(extend(true, {}, defaults, options));
|
120 |
|
121 | this.bindHandlers();
|
122 |
|
123 | this.state = "init";
|
124 |
|
125 | this.setItems(items);
|
126 |
|
127 | this.attachPlugins(Fancybox.Plugins);
|
128 |
|
129 |
|
130 | this.trigger("init");
|
131 |
|
132 | if (this.option("hideScrollbar") === true) {
|
133 | this.hideScrollbar();
|
134 | }
|
135 |
|
136 | this.initLayout();
|
137 |
|
138 | this.initCarousel();
|
139 |
|
140 | this.attachEvents();
|
141 |
|
142 | instances.set(this.id, this);
|
143 |
|
144 |
|
145 | this.trigger("prepare");
|
146 |
|
147 | this.state = "ready";
|
148 |
|
149 |
|
150 | this.trigger("ready");
|
151 |
|
152 |
|
153 | this.$container.setAttribute("aria-hidden", "false");
|
154 |
|
155 |
|
156 | if (this.option("trapFocus")) {
|
157 | this.focus();
|
158 | }
|
159 | }
|
160 |
|
161 | |
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 | option(name, ...rest) {
|
168 | const slide = this.getSlide();
|
169 |
|
170 | let value = slide ? slide[name] : undefined;
|
171 |
|
172 | if (value !== undefined) {
|
173 | if (typeof value === "function") {
|
174 | value = value.call(this, this, ...rest);
|
175 | }
|
176 |
|
177 | return value;
|
178 | }
|
179 |
|
180 | return super.option(name, ...rest);
|
181 | }
|
182 |
|
183 | |
184 |
|
185 |
|
186 | bindHandlers() {
|
187 | for (const methodName of [
|
188 | "onMousedown",
|
189 | "onKeydown",
|
190 | "onClick",
|
191 |
|
192 | "onFocus",
|
193 |
|
194 | "onCreateSlide",
|
195 | "onSettle",
|
196 |
|
197 | "onTouchMove",
|
198 | "onTouchEnd",
|
199 |
|
200 | "onTransform",
|
201 | ]) {
|
202 | this[methodName] = this[methodName].bind(this);
|
203 | }
|
204 | }
|
205 |
|
206 | |
207 |
|
208 |
|
209 | attachEvents() {
|
210 | document.addEventListener("mousedown", this.onMousedown);
|
211 | document.addEventListener("keydown", this.onKeydown, true);
|
212 |
|
213 |
|
214 | if (this.option("trapFocus")) {
|
215 | document.addEventListener("focus", this.onFocus, true);
|
216 | }
|
217 |
|
218 | this.$container.addEventListener("click", this.onClick);
|
219 | }
|
220 |
|
221 | |
222 |
|
223 |
|
224 | detachEvents() {
|
225 | document.removeEventListener("mousedown", this.onMousedown);
|
226 | document.removeEventListener("keydown", this.onKeydown, true);
|
227 |
|
228 | document.removeEventListener("focus", this.onFocus, true);
|
229 |
|
230 | this.$container.removeEventListener("click", this.onClick);
|
231 | }
|
232 |
|
233 | |
234 |
|
235 |
|
236 | initLayout() {
|
237 | this.$root = this.option("parentEl") || document.body;
|
238 |
|
239 |
|
240 | let mainTemplate = this.option("template.main");
|
241 |
|
242 | if (mainTemplate) {
|
243 | this.$root.insertAdjacentHTML("beforeend", this.localize(mainTemplate));
|
244 |
|
245 | this.$container = this.$root.querySelector(".fancybox__container");
|
246 | }
|
247 |
|
248 | if (!this.$container) {
|
249 | this.$container = document.createElement("div");
|
250 | this.$root.appendChild(this.$container);
|
251 | }
|
252 |
|
253 |
|
254 |
|
255 | this.$container.onscroll = () => {
|
256 | this.$container.scrollLeft = 0;
|
257 | return false;
|
258 | };
|
259 |
|
260 | Object.entries({
|
261 | class: "fancybox__container",
|
262 | role: "dialog",
|
263 | tabIndex: "-1",
|
264 | "aria-modal": "true",
|
265 | "aria-hidden": "true",
|
266 | "aria-label": this.localize("{{MODAL}}"),
|
267 | }).forEach((args) => this.$container.setAttribute(...args));
|
268 |
|
269 | if (this.option("animated")) {
|
270 | this.$container.classList.add("is-animated");
|
271 | }
|
272 |
|
273 |
|
274 | this.$backdrop = this.$container.querySelector(".fancybox__backdrop");
|
275 |
|
276 | if (!this.$backdrop) {
|
277 | this.$backdrop = document.createElement("div");
|
278 | this.$backdrop.classList.add("fancybox__backdrop");
|
279 |
|
280 | this.$container.appendChild(this.$backdrop);
|
281 | }
|
282 |
|
283 |
|
284 | this.$carousel = this.$container.querySelector(".fancybox__carousel");
|
285 |
|
286 | if (!this.$carousel) {
|
287 | this.$carousel = document.createElement("div");
|
288 | this.$carousel.classList.add("fancybox__carousel");
|
289 |
|
290 | this.$container.appendChild(this.$carousel);
|
291 | }
|
292 |
|
293 |
|
294 | this.$container.Fancybox = this;
|
295 |
|
296 |
|
297 | this.id = this.$container.getAttribute("id");
|
298 |
|
299 | if (!this.id) {
|
300 | this.id = this.options.id || ++called;
|
301 | this.$container.setAttribute("id", "fancybox-" + this.id);
|
302 | }
|
303 |
|
304 |
|
305 | const mainClass = this.option("mainClass");
|
306 |
|
307 | if (mainClass) {
|
308 | this.$container.classList.add(...mainClass.split(" "));
|
309 | }
|
310 |
|
311 |
|
312 | document.documentElement.classList.add("with-fancybox");
|
313 |
|
314 | this.trigger("initLayout");
|
315 |
|
316 | return this;
|
317 | }
|
318 |
|
319 | |
320 |
|
321 |
|
322 |
|
323 | setItems(items) {
|
324 | const slides = [];
|
325 |
|
326 | for (const slide of items) {
|
327 | const $trigger = slide.$trigger;
|
328 |
|
329 | if ($trigger) {
|
330 | const dataset = $trigger.dataset || {};
|
331 |
|
332 | slide.src = dataset.src || $trigger.getAttribute("href") || slide.src;
|
333 | slide.type = dataset.type || slide.type;
|
334 |
|
335 |
|
336 | if (!slide.src && $trigger instanceof HTMLImageElement) {
|
337 | slide.src = $trigger.currentSrc || slide.$trigger.src;
|
338 | }
|
339 | }
|
340 |
|
341 |
|
342 | let $thumb = slide.$thumb;
|
343 |
|
344 | if (!$thumb) {
|
345 | let origTarget = slide.$trigger && slide.$trigger.origTarget;
|
346 |
|
347 | if (origTarget) {
|
348 | if (origTarget instanceof HTMLImageElement) {
|
349 | $thumb = origTarget;
|
350 | } else {
|
351 | $thumb = origTarget.querySelector("img:not([aria-hidden])");
|
352 | }
|
353 | }
|
354 |
|
355 | if (!$thumb && slide.$trigger) {
|
356 | $thumb =
|
357 | slide.$trigger instanceof HTMLImageElement
|
358 | ? slide.$trigger
|
359 | : slide.$trigger.querySelector("img:not([aria-hidden])");
|
360 | }
|
361 | }
|
362 |
|
363 | slide.$thumb = $thumb || null;
|
364 |
|
365 |
|
366 | let thumb = slide.thumb;
|
367 |
|
368 | if (!thumb && $thumb) {
|
369 | thumb = $thumb.currentSrc || $thumb.src;
|
370 |
|
371 | if (!thumb && $thumb.dataset) {
|
372 | thumb = $thumb.dataset.lazySrc || $thumb.dataset.src;
|
373 | }
|
374 | }
|
375 |
|
376 |
|
377 | if (!thumb && slide.type === "image") {
|
378 | thumb = slide.src;
|
379 | }
|
380 |
|
381 | slide.thumb = thumb || null;
|
382 |
|
383 |
|
384 | slide.caption = slide.caption || "";
|
385 |
|
386 | slides.push(slide);
|
387 | }
|
388 |
|
389 | this.items = slides;
|
390 | }
|
391 |
|
392 | |
393 |
|
394 |
|
395 |
|
396 | initCarousel() {
|
397 | this.Carousel = new Carousel(
|
398 | this.$carousel,
|
399 | extend(
|
400 | true,
|
401 | {},
|
402 | {
|
403 | prefix: "",
|
404 |
|
405 | classNames: {
|
406 | viewport: "fancybox__viewport",
|
407 | track: "fancybox__track",
|
408 | slide: "fancybox__slide",
|
409 | },
|
410 |
|
411 | textSelection: true,
|
412 | preload: this.option("preload"),
|
413 |
|
414 | friction: 0.88,
|
415 |
|
416 | slides: this.items,
|
417 | initialPage: this.options.startIndex,
|
418 | slidesPerPage: 1,
|
419 |
|
420 | infiniteX: this.option("infinite"),
|
421 | infiniteY: true,
|
422 |
|
423 | l10n: this.option("l10n"),
|
424 |
|
425 | Dots: false,
|
426 | Navigation: {
|
427 | classNames: {
|
428 | main: "fancybox__nav",
|
429 | button: "carousel__button",
|
430 |
|
431 | next: "is-next",
|
432 | prev: "is-prev",
|
433 | },
|
434 | },
|
435 |
|
436 | Panzoom: {
|
437 | textSelection: true,
|
438 |
|
439 | panOnlyZoomed: () => {
|
440 | return (
|
441 | this.Carousel && this.Carousel.pages && this.Carousel.pages.length < 2 && !this.option("dragToClose")
|
442 | );
|
443 | },
|
444 |
|
445 | lockAxis: () => {
|
446 | if (this.Carousel) {
|
447 | let rez = "x";
|
448 |
|
449 | if (this.option("dragToClose")) {
|
450 | rez += "y";
|
451 | }
|
452 |
|
453 | return rez;
|
454 | }
|
455 | },
|
456 | },
|
457 |
|
458 | on: {
|
459 | "*": (name, ...details) => this.trigger(`Carousel.${name}`, ...details),
|
460 | init: (carousel) => (this.Carousel = carousel),
|
461 | createSlide: this.onCreateSlide,
|
462 | settle: this.onSettle,
|
463 | },
|
464 | },
|
465 |
|
466 | this.option("Carousel")
|
467 | )
|
468 | );
|
469 |
|
470 | if (this.option("dragToClose")) {
|
471 | this.Carousel.Panzoom.on({
|
472 |
|
473 | touchMove: this.onTouchMove,
|
474 |
|
475 |
|
476 | afterTransform: this.onTransform,
|
477 |
|
478 |
|
479 | touchEnd: this.onTouchEnd,
|
480 | });
|
481 | }
|
482 |
|
483 | this.trigger("initCarousel");
|
484 |
|
485 | return this;
|
486 | }
|
487 |
|
488 | |
489 |
|
490 |
|
491 | onCreateSlide(carousel, slide) {
|
492 | let caption = slide.caption || "";
|
493 |
|
494 | if (typeof this.options.caption === "function") {
|
495 | caption = this.options.caption.call(this, this, this.Carousel, slide);
|
496 | }
|
497 |
|
498 | if (typeof caption === "string" && caption.length) {
|
499 | const $caption = document.createElement("div");
|
500 | const id = `fancybox__caption_${this.id}_${slide.index}`;
|
501 |
|
502 | $caption.className = "fancybox__caption";
|
503 | $caption.innerHTML = caption;
|
504 | $caption.setAttribute("id", id);
|
505 |
|
506 | slide.$caption = slide.$el.appendChild($caption);
|
507 |
|
508 | slide.$el.classList.add("has-caption");
|
509 | slide.$el.setAttribute("aria-labelledby", id);
|
510 | }
|
511 | }
|
512 |
|
513 | |
514 |
|
515 |
|
516 | onSettle() {
|
517 | if (this.option("autoFocus")) {
|
518 | this.focus();
|
519 | }
|
520 | }
|
521 |
|
522 | |
523 |
|
524 |
|
525 |
|
526 | onFocus(event) {
|
527 | this.focus(event);
|
528 | }
|
529 |
|
530 | |
531 |
|
532 |
|
533 |
|
534 | onClick(event) {
|
535 | if (event.defaultPrevented) {
|
536 | return;
|
537 | }
|
538 |
|
539 | let eventTarget = event.composedPath()[0];
|
540 |
|
541 | if (eventTarget.matches("[data-fancybox-close]")) {
|
542 | event.preventDefault();
|
543 | Fancybox.close(false, event);
|
544 |
|
545 | return;
|
546 | }
|
547 |
|
548 | if (eventTarget.matches("[data-fancybox-next]")) {
|
549 | event.preventDefault();
|
550 | Fancybox.next();
|
551 |
|
552 | return;
|
553 | }
|
554 |
|
555 | if (eventTarget.matches("[data-fancybox-prev]")) {
|
556 | event.preventDefault();
|
557 | Fancybox.prev();
|
558 |
|
559 | return;
|
560 | }
|
561 |
|
562 | if (!eventTarget.matches(FOCUSABLE_ELEMENTS)) {
|
563 | document.activeElement.blur();
|
564 | }
|
565 |
|
566 |
|
567 | if (eventTarget.closest(".fancybox__content")) {
|
568 | return;
|
569 | }
|
570 |
|
571 |
|
572 | if (getSelection().toString().length) {
|
573 | return;
|
574 | }
|
575 |
|
576 | if (this.trigger("click", event) === false) {
|
577 | return;
|
578 | }
|
579 |
|
580 | const action = this.option("click");
|
581 |
|
582 | switch (action) {
|
583 | case "close":
|
584 | this.close();
|
585 | break;
|
586 | case "next":
|
587 | this.next();
|
588 | break;
|
589 | }
|
590 | }
|
591 |
|
592 | |
593 |
|
594 |
|
595 | onTouchMove() {
|
596 | const panzoom = this.getSlide().Panzoom;
|
597 |
|
598 | return panzoom && panzoom.content.scale !== 1 ? false : true;
|
599 | }
|
600 |
|
601 | |
602 |
|
603 |
|
604 |
|
605 | onTouchEnd(panzoom) {
|
606 | const distanceY = panzoom.dragOffset.y;
|
607 |
|
608 | if (Math.abs(distanceY) >= 150 || (Math.abs(distanceY) >= 35 && panzoom.dragOffset.time < 350)) {
|
609 | if (this.option("hideClass")) {
|
610 | this.getSlide().hideClass = `fancybox-throwOut${panzoom.content.y < 0 ? "Up" : "Down"}`;
|
611 | }
|
612 |
|
613 | this.close();
|
614 | } else if (panzoom.lockAxis === "y") {
|
615 | panzoom.panTo({ y: 0 });
|
616 | }
|
617 | }
|
618 |
|
619 | |
620 |
|
621 |
|
622 |
|
623 | onTransform(panzoom) {
|
624 | const $backdrop = this.$backdrop;
|
625 |
|
626 | if ($backdrop) {
|
627 | const yPos = Math.abs(panzoom.content.y);
|
628 | const opacity = yPos < 1 ? "" : Math.max(0.33, Math.min(1, 1 - (yPos / panzoom.content.fitHeight) * 1.5));
|
629 |
|
630 | this.$container.style.setProperty("--fancybox-ts", opacity ? "0s" : "");
|
631 | this.$container.style.setProperty("--fancybox-opacity", opacity);
|
632 | }
|
633 | }
|
634 |
|
635 | |
636 |
|
637 |
|
638 | onMousedown() {
|
639 | if (this.state === "ready") {
|
640 | document.body.classList.add("is-using-mouse");
|
641 | }
|
642 | }
|
643 |
|
644 | |
645 |
|
646 |
|
647 |
|
648 | onKeydown(event) {
|
649 | if (Fancybox.getInstance().id !== this.id) {
|
650 | return;
|
651 | }
|
652 |
|
653 | document.body.classList.remove("is-using-mouse");
|
654 |
|
655 | const key = event.key;
|
656 | const keyboard = this.option("keyboard");
|
657 |
|
658 | if (!keyboard || event.ctrlKey || event.altKey || event.shiftKey) {
|
659 | return;
|
660 | }
|
661 |
|
662 | const target = event.composedPath()[0];
|
663 |
|
664 | const classList = document.activeElement && document.activeElement.classList;
|
665 | const isUIElement = classList && classList.contains("carousel__button");
|
666 |
|
667 |
|
668 | if (key !== "Escape" && !isUIElement) {
|
669 | let ignoreElements =
|
670 | event.target.isContentEditable ||
|
671 | ["BUTTON", "TEXTAREA", "OPTION", "INPUT", "SELECT", "VIDEO"].indexOf(target.nodeName) !== -1;
|
672 |
|
673 | if (ignoreElements) {
|
674 | return;
|
675 | }
|
676 | }
|
677 |
|
678 | if (this.trigger("keydown", key, event) === false) {
|
679 | return;
|
680 | }
|
681 |
|
682 | const action = keyboard[key];
|
683 |
|
684 | if (typeof this[action] === "function") {
|
685 | this[action]();
|
686 | }
|
687 | }
|
688 |
|
689 | |
690 |
|
691 |
|
692 | getSlide() {
|
693 | const carousel = this.Carousel;
|
694 |
|
695 | if (!carousel) return null;
|
696 |
|
697 | const page = carousel.page === null ? carousel.option("initialPage") : carousel.page;
|
698 | const pages = carousel.pages || [];
|
699 |
|
700 | if (pages.length && pages[page]) {
|
701 | return pages[page].slides[0];
|
702 | }
|
703 |
|
704 | return null;
|
705 | }
|
706 |
|
707 | |
708 |
|
709 |
|
710 |
|
711 | focus(event) {
|
712 | if (Fancybox.ignoreFocusChange) {
|
713 | return;
|
714 | }
|
715 |
|
716 | if (["init", "closing", "customClosing", "destroy"].indexOf(this.state) > -1) {
|
717 | return;
|
718 | }
|
719 |
|
720 | const $container = this.$container;
|
721 | const currentSlide = this.getSlide();
|
722 | const $currentSlide = currentSlide.state === "done" ? currentSlide.$el : null;
|
723 |
|
724 |
|
725 | if ($currentSlide && $currentSlide.contains(document.activeElement)) {
|
726 | return;
|
727 | }
|
728 |
|
729 | if (event) {
|
730 | event.preventDefault();
|
731 | }
|
732 |
|
733 | Fancybox.ignoreFocusChange = true;
|
734 |
|
735 | const allFocusableElems = Array.from($container.querySelectorAll(FOCUSABLE_ELEMENTS));
|
736 |
|
737 | let enabledElems = [];
|
738 | let $firstEl;
|
739 |
|
740 | for (let node of allFocusableElems) {
|
741 |
|
742 |
|
743 | const isNodeVisible = node.offsetParent;
|
744 | const isNodeInsideCurrentSlide = $currentSlide && $currentSlide.contains(node);
|
745 | const isNodeOutsideCarousel = !this.Carousel.$viewport.contains(node);
|
746 |
|
747 | if (isNodeVisible && (isNodeInsideCurrentSlide || isNodeOutsideCarousel)) {
|
748 | enabledElems.push(node);
|
749 |
|
750 | if (node.dataset.origTabindex !== undefined) {
|
751 | node.tabIndex = node.dataset.origTabindex;
|
752 | node.removeAttribute("data-orig-tabindex");
|
753 | }
|
754 |
|
755 | if (
|
756 | node.hasAttribute("autoFocus") ||
|
757 | (!$firstEl && isNodeInsideCurrentSlide && !node.classList.contains("carousel__button"))
|
758 | ) {
|
759 | $firstEl = node;
|
760 | }
|
761 | } else {
|
762 |
|
763 | node.dataset.origTabindex =
|
764 | node.dataset.origTabindex === undefined ? node.getAttribute("tabindex") : node.dataset.origTabindex;
|
765 |
|
766 | node.tabIndex = -1;
|
767 | }
|
768 | }
|
769 |
|
770 | if (!event) {
|
771 | if (this.option("autoFocus") && $firstEl) {
|
772 | setFocusOn($firstEl);
|
773 | } else if (enabledElems.indexOf(document.activeElement) < 0) {
|
774 | setFocusOn($container);
|
775 | }
|
776 | } else {
|
777 | if (enabledElems.indexOf(event.target) > -1) {
|
778 | this.lastFocus = event.target;
|
779 | } else {
|
780 | if (this.lastFocus === $container) {
|
781 | setFocusOn(enabledElems[enabledElems.length - 1]);
|
782 | } else {
|
783 | setFocusOn($container);
|
784 | }
|
785 | }
|
786 | }
|
787 |
|
788 | this.lastFocus = document.activeElement;
|
789 |
|
790 | Fancybox.ignoreFocusChange = false;
|
791 | }
|
792 |
|
793 | |
794 |
|
795 |
|
796 |
|
797 | hideScrollbar() {
|
798 | if (!canUseDOM) {
|
799 | return;
|
800 | }
|
801 |
|
802 | const scrollbarWidth = window.innerWidth - document.documentElement.getBoundingClientRect().width;
|
803 | const id = "fancybox-style-noscroll";
|
804 |
|
805 | let $style = document.getElementById(id);
|
806 |
|
807 | if ($style) {
|
808 | return;
|
809 | }
|
810 |
|
811 | if (scrollbarWidth > 0) {
|
812 | $style = document.createElement("style");
|
813 |
|
814 | $style.id = id;
|
815 | $style.type = "text/css";
|
816 | $style.innerHTML = `.compensate-for-scrollbar {padding-right: ${scrollbarWidth}px;}`;
|
817 |
|
818 | document.getElementsByTagName("head")[0].appendChild($style);
|
819 |
|
820 | document.body.classList.add("compensate-for-scrollbar");
|
821 | }
|
822 | }
|
823 |
|
824 | |
825 |
|
826 |
|
827 | revealScrollbar() {
|
828 | document.body.classList.remove("compensate-for-scrollbar");
|
829 |
|
830 | const el = document.getElementById("fancybox-style-noscroll");
|
831 |
|
832 | if (el) {
|
833 | el.remove();
|
834 | }
|
835 | }
|
836 |
|
837 | |
838 |
|
839 |
|
840 |
|
841 | clearContent(slide) {
|
842 |
|
843 | this.Carousel.trigger("removeSlide", slide);
|
844 |
|
845 | if (slide.$content) {
|
846 | slide.$content.remove();
|
847 | slide.$content = null;
|
848 | }
|
849 |
|
850 | if (slide.$closeButton) {
|
851 | slide.$closeButton.remove();
|
852 | slide.$closeButton = null;
|
853 | }
|
854 |
|
855 | if (slide._className) {
|
856 | slide.$el.classList.remove(slide._className);
|
857 | }
|
858 | }
|
859 |
|
860 | |
861 |
|
862 |
|
863 |
|
864 |
|
865 |
|
866 | setContent(slide, html, opts = {}) {
|
867 | let $content;
|
868 |
|
869 | const $el = slide.$el;
|
870 |
|
871 | if (html instanceof HTMLElement) {
|
872 | if (["img", "iframe", "video", "audio"].indexOf(html.nodeName.toLowerCase()) > -1) {
|
873 | $content = document.createElement("div");
|
874 | $content.appendChild(html);
|
875 | } else {
|
876 | $content = html;
|
877 | }
|
878 | } else {
|
879 | const $fragment = document.createRange().createContextualFragment(html);
|
880 |
|
881 | $content = document.createElement("div");
|
882 | $content.appendChild($fragment);
|
883 | }
|
884 |
|
885 | if (slide.filter && !slide.error) {
|
886 | $content = $content.querySelector(slide.filter);
|
887 | }
|
888 |
|
889 | if (!($content instanceof Element)) {
|
890 | this.setError(slide, "{{ELEMENT_NOT_FOUND}}");
|
891 |
|
892 | return;
|
893 | }
|
894 |
|
895 |
|
896 | slide._className = `has-${opts.suffix || slide.type || "unknown"}`;
|
897 |
|
898 | $el.classList.add(slide._className);
|
899 |
|
900 |
|
901 | $content.classList.add("fancybox__content");
|
902 |
|
903 |
|
904 | if ($content.style.display === "none" || getComputedStyle($content).getPropertyValue("display") === "none") {
|
905 | $content.style.display = slide.display || this.option("defaultDisplay") || "flex";
|
906 | }
|
907 |
|
908 | if (slide.id) {
|
909 | $content.setAttribute("id", slide.id);
|
910 | }
|
911 |
|
912 | slide.$content = $content;
|
913 |
|
914 | $el.prepend($content);
|
915 |
|
916 | this.manageCloseButton(slide);
|
917 |
|
918 | if (slide.state !== "loading") {
|
919 | this.revealContent(slide);
|
920 | }
|
921 |
|
922 | return $content;
|
923 | }
|
924 |
|
925 | |
926 |
|
927 |
|
928 |
|
929 | manageCloseButton(slide) {
|
930 | const position = slide.closeButton === undefined ? this.option("closeButton") : slide.closeButton;
|
931 |
|
932 | if (!position || (position === "top" && this.$closeButton)) {
|
933 | return;
|
934 | }
|
935 |
|
936 | const $btn = document.createElement("button");
|
937 |
|
938 | $btn.classList.add("carousel__button", "is-close");
|
939 | $btn.setAttribute("title", this.options.l10n.CLOSE);
|
940 | $btn.innerHTML = this.option("template.closeButton");
|
941 |
|
942 | $btn.addEventListener("click", (e) => this.close(e));
|
943 |
|
944 | if (position === "inside") {
|
945 |
|
946 | if (slide.$closeButton) {
|
947 | slide.$closeButton.remove();
|
948 | }
|
949 |
|
950 | slide.$closeButton = slide.$content.appendChild($btn);
|
951 | } else {
|
952 | this.$closeButton = this.$container.insertBefore($btn, this.$container.firstChild);
|
953 | }
|
954 | }
|
955 |
|
956 | |
957 |
|
958 |
|
959 |
|
960 | revealContent(slide) {
|
961 | this.trigger("reveal", slide);
|
962 |
|
963 | slide.$content.style.visibility = "";
|
964 |
|
965 |
|
966 | let showClass = false;
|
967 |
|
968 | if (
|
969 | !(
|
970 | slide.error ||
|
971 | slide.state === "loading" ||
|
972 | this.Carousel.prevPage !== null ||
|
973 | slide.index !== this.options.startIndex
|
974 | )
|
975 | ) {
|
976 | showClass = slide.showClass === undefined ? this.option("showClass") : slide.showClass;
|
977 | }
|
978 |
|
979 | if (!showClass) {
|
980 | this.done(slide);
|
981 |
|
982 | return;
|
983 | }
|
984 |
|
985 | slide.state = "animating";
|
986 |
|
987 | this.animateCSS(slide.$content, showClass, () => {
|
988 | this.done(slide);
|
989 | });
|
990 | }
|
991 |
|
992 | |
993 |
|
994 |
|
995 |
|
996 |
|
997 |
|
998 | animateCSS($element, className, callback) {
|
999 | if ($element) {
|
1000 | $element.dispatchEvent(new CustomEvent("animationend", { bubbles: true, cancelable: true }));
|
1001 | }
|
1002 |
|
1003 | if (!$element || !className) {
|
1004 | if (typeof callback === "function") {
|
1005 | callback();
|
1006 | }
|
1007 |
|
1008 | return;
|
1009 | }
|
1010 |
|
1011 | const handleAnimationEnd = function (event) {
|
1012 | if (event.currentTarget === this) {
|
1013 | $element.removeEventListener("animationend", handleAnimationEnd);
|
1014 |
|
1015 | if (callback) {
|
1016 | callback();
|
1017 | }
|
1018 |
|
1019 | $element.classList.remove(className);
|
1020 | }
|
1021 | };
|
1022 |
|
1023 | $element.addEventListener("animationend", handleAnimationEnd);
|
1024 | $element.classList.add(className);
|
1025 | }
|
1026 |
|
1027 | |
1028 |
|
1029 |
|
1030 |
|
1031 | done(slide) {
|
1032 | slide.state = "done";
|
1033 |
|
1034 | this.trigger("done", slide);
|
1035 |
|
1036 |
|
1037 | const currentSlide = this.getSlide();
|
1038 |
|
1039 | if (currentSlide && slide.index === currentSlide.index && this.option("autoFocus")) {
|
1040 | this.focus();
|
1041 | }
|
1042 | }
|
1043 |
|
1044 | |
1045 |
|
1046 |
|
1047 |
|
1048 |
|
1049 | setError(slide, message) {
|
1050 | slide.error = message;
|
1051 |
|
1052 | this.hideLoading(slide);
|
1053 | this.clearContent(slide);
|
1054 |
|
1055 |
|
1056 | const div = document.createElement("div");
|
1057 | div.classList.add("fancybox-error");
|
1058 | div.innerHTML = this.localize(message || "<p>{{ERROR}}</p>");
|
1059 |
|
1060 | this.setContent(slide, div, { suffix: "error" });
|
1061 | }
|
1062 |
|
1063 | |
1064 |
|
1065 |
|
1066 |
|
1067 | showLoading(slide) {
|
1068 | slide.state = "loading";
|
1069 |
|
1070 | slide.$el.classList.add("is-loading");
|
1071 |
|
1072 | let $spinner = slide.$el.querySelector(".fancybox__spinner");
|
1073 |
|
1074 | if ($spinner) {
|
1075 | return;
|
1076 | }
|
1077 |
|
1078 | $spinner = document.createElement("div");
|
1079 |
|
1080 | $spinner.classList.add("fancybox__spinner");
|
1081 | $spinner.innerHTML = this.option("template.spinner");
|
1082 |
|
1083 | $spinner.addEventListener("click", () => {
|
1084 | if (!this.Carousel.Panzoom.velocity) this.close();
|
1085 | });
|
1086 |
|
1087 | slide.$el.prepend($spinner);
|
1088 | }
|
1089 |
|
1090 | |
1091 |
|
1092 |
|
1093 |
|
1094 | hideLoading(slide) {
|
1095 | const $spinner = slide.$el && slide.$el.querySelector(".fancybox__spinner");
|
1096 |
|
1097 | if ($spinner) {
|
1098 | $spinner.remove();
|
1099 |
|
1100 | slide.$el.classList.remove("is-loading");
|
1101 | }
|
1102 |
|
1103 | if (slide.state === "loading") {
|
1104 | this.trigger("load", slide);
|
1105 |
|
1106 | slide.state = "ready";
|
1107 | }
|
1108 | }
|
1109 |
|
1110 | |
1111 |
|
1112 |
|
1113 | next() {
|
1114 | const carousel = this.Carousel;
|
1115 |
|
1116 | if (carousel && carousel.pages.length > 1) {
|
1117 | carousel.slideNext();
|
1118 | }
|
1119 | }
|
1120 |
|
1121 | |
1122 |
|
1123 |
|
1124 | prev() {
|
1125 | const carousel = this.Carousel;
|
1126 |
|
1127 | if (carousel && carousel.pages.length > 1) {
|
1128 | carousel.slidePrev();
|
1129 | }
|
1130 | }
|
1131 |
|
1132 | |
1133 |
|
1134 |
|
1135 |
|
1136 |
|
1137 |
|
1138 |
|
1139 | jumpTo(...args) {
|
1140 | if (this.Carousel) this.Carousel.slideTo(...args);
|
1141 | }
|
1142 |
|
1143 | |
1144 |
|
1145 |
|
1146 |
|
1147 | close(event) {
|
1148 | if (event) event.preventDefault();
|
1149 |
|
1150 |
|
1151 |
|
1152 | if (["closing", "customClosing", "destroy"].includes(this.state)) {
|
1153 | return;
|
1154 | }
|
1155 |
|
1156 |
|
1157 | if (this.trigger("shouldClose", event) === false) {
|
1158 | return;
|
1159 | }
|
1160 |
|
1161 | this.state = "closing";
|
1162 |
|
1163 | this.Carousel.Panzoom.destroy();
|
1164 |
|
1165 | this.detachEvents();
|
1166 |
|
1167 | this.trigger("closing", event);
|
1168 |
|
1169 | if (this.state === "destroy") {
|
1170 | return;
|
1171 | }
|
1172 |
|
1173 |
|
1174 | this.$container.setAttribute("aria-hidden", "true");
|
1175 |
|
1176 | this.$container.classList.add("is-closing");
|
1177 |
|
1178 |
|
1179 | const currentSlide = this.getSlide();
|
1180 |
|
1181 | this.Carousel.slides.forEach((slide) => {
|
1182 | if (slide.$content && slide.index !== currentSlide.index) {
|
1183 | this.Carousel.trigger("removeSlide", slide);
|
1184 | }
|
1185 | });
|
1186 |
|
1187 |
|
1188 | if (this.state === "closing") {
|
1189 | const hideClass = currentSlide.hideClass === undefined ? this.option("hideClass") : currentSlide.hideClass;
|
1190 |
|
1191 | this.animateCSS(
|
1192 | currentSlide.$content,
|
1193 | hideClass,
|
1194 | () => {
|
1195 | this.destroy();
|
1196 | },
|
1197 | true
|
1198 | );
|
1199 | }
|
1200 | }
|
1201 |
|
1202 | |
1203 |
|
1204 |
|
1205 | destroy() {
|
1206 | if (this.state === "destroy") {
|
1207 | return;
|
1208 | }
|
1209 |
|
1210 | this.state = "destroy";
|
1211 |
|
1212 | this.trigger("destroy");
|
1213 |
|
1214 | const $trigger = this.option("placeFocusBack") ? this.getSlide().$trigger : null;
|
1215 |
|
1216 |
|
1217 |
|
1218 | this.Carousel.destroy();
|
1219 |
|
1220 | this.detachPlugins();
|
1221 |
|
1222 | this.Carousel = null;
|
1223 |
|
1224 | this.options = {};
|
1225 | this.events = {};
|
1226 |
|
1227 | this.$container.remove();
|
1228 |
|
1229 | this.$container = this.$backdrop = this.$carousel = null;
|
1230 |
|
1231 | if ($trigger) {
|
1232 | setFocusOn($trigger);
|
1233 | }
|
1234 |
|
1235 | instances.delete(this.id);
|
1236 |
|
1237 | const nextInstance = Fancybox.getInstance();
|
1238 |
|
1239 | if (nextInstance) {
|
1240 | nextInstance.focus();
|
1241 | return;
|
1242 | }
|
1243 |
|
1244 | document.documentElement.classList.remove("with-fancybox");
|
1245 | document.body.classList.remove("is-using-mouse");
|
1246 |
|
1247 | this.revealScrollbar();
|
1248 | }
|
1249 |
|
1250 | |
1251 |
|
1252 |
|
1253 |
|
1254 |
|
1255 |
|
1256 |
|
1257 |
|
1258 | static show(items, options = {}) {
|
1259 | return new Fancybox(items, options);
|
1260 | }
|
1261 |
|
1262 | |
1263 |
|
1264 |
|
1265 |
|
1266 |
|
1267 | static fromEvent(event, options = {}) {
|
1268 |
|
1269 | if (event.defaultPrevented) {
|
1270 | return;
|
1271 | }
|
1272 |
|
1273 |
|
1274 | if (event.button && event.button !== 0) {
|
1275 | return;
|
1276 | }
|
1277 |
|
1278 |
|
1279 | if (event.ctrlKey || event.metaKey || event.shiftKey) {
|
1280 | return;
|
1281 | }
|
1282 |
|
1283 | const origTarget = event.composedPath()[0];
|
1284 |
|
1285 | let eventTarget = origTarget;
|
1286 |
|
1287 |
|
1288 |
|
1289 | let triggerGroupName;
|
1290 |
|
1291 | if (
|
1292 | eventTarget.matches("[data-fancybox-trigger]") ||
|
1293 | (eventTarget = eventTarget.closest("[data-fancybox-trigger]"))
|
1294 | ) {
|
1295 | triggerGroupName = eventTarget && eventTarget.dataset && eventTarget.dataset.fancyboxTrigger;
|
1296 | }
|
1297 |
|
1298 | if (triggerGroupName) {
|
1299 | const triggerItems = document.querySelectorAll(`[data-fancybox="${triggerGroupName}"]`);
|
1300 | const triggerIndex = parseInt(eventTarget.dataset.fancyboxIndex, 10) || 0;
|
1301 |
|
1302 | eventTarget = triggerItems.length ? triggerItems[triggerIndex] : eventTarget;
|
1303 | }
|
1304 |
|
1305 | if (!eventTarget) {
|
1306 | eventTarget = origTarget;
|
1307 | }
|
1308 |
|
1309 |
|
1310 | let matchingOpener;
|
1311 | let target;
|
1312 |
|
1313 | Array.from(Fancybox.openers.keys())
|
1314 | .reverse()
|
1315 | .some((opener) => {
|
1316 | target = eventTarget;
|
1317 |
|
1318 | let found = false;
|
1319 |
|
1320 | try {
|
1321 | if (target instanceof Element && (typeof opener === "string" || opener instanceof String)) {
|
1322 |
|
1323 |
|
1324 | found = target.matches(opener) || (target = target.closest(opener));
|
1325 | }
|
1326 | } catch (error) {}
|
1327 |
|
1328 | if (found) {
|
1329 | event.preventDefault();
|
1330 | matchingOpener = opener;
|
1331 | return true;
|
1332 | }
|
1333 |
|
1334 | return false;
|
1335 | });
|
1336 |
|
1337 | let rez = false;
|
1338 |
|
1339 | if (matchingOpener) {
|
1340 | options.event = event;
|
1341 | options.target = target;
|
1342 |
|
1343 | target.origTarget = origTarget;
|
1344 |
|
1345 | rez = Fancybox.fromOpener(matchingOpener, options);
|
1346 |
|
1347 |
|
1348 |
|
1349 |
|
1350 | const nextInstance = Fancybox.getInstance();
|
1351 |
|
1352 | if (nextInstance && nextInstance.state === "ready" && event.detail) {
|
1353 | document.body.classList.add("is-using-mouse");
|
1354 | }
|
1355 | }
|
1356 |
|
1357 | return rez;
|
1358 | }
|
1359 |
|
1360 | |
1361 |
|
1362 |
|
1363 |
|
1364 |
|
1365 | static fromOpener(opener, options = {}) {
|
1366 |
|
1367 |
|
1368 |
|
1369 | const mapCallback = function (el) {
|
1370 | const falseValues = ["false", "0", "no", "null", "undefined"];
|
1371 | const trueValues = ["true", "1", "yes"];
|
1372 |
|
1373 | const dataset = Object.assign({}, el.dataset);
|
1374 | const options = {};
|
1375 |
|
1376 | for (let [key, value] of Object.entries(dataset)) {
|
1377 | if (key === "fancybox") {
|
1378 | continue;
|
1379 | }
|
1380 |
|
1381 | if (key === "width" || key === "height") {
|
1382 | options[`_${key}`] = value;
|
1383 | } else if (typeof value === "string" || value instanceof String) {
|
1384 | if (falseValues.indexOf(value) > -1) {
|
1385 | options[key] = false;
|
1386 | } else if (trueValues.indexOf(options[key]) > -1) {
|
1387 | options[key] = true;
|
1388 | } else {
|
1389 | try {
|
1390 | options[key] = JSON.parse(value);
|
1391 | } catch (e) {
|
1392 | options[key] = value;
|
1393 | }
|
1394 | }
|
1395 | } else {
|
1396 | options[key] = value;
|
1397 | }
|
1398 | }
|
1399 |
|
1400 | if (el instanceof Element) {
|
1401 | options.$trigger = el;
|
1402 | }
|
1403 |
|
1404 | return options;
|
1405 | };
|
1406 |
|
1407 | let items = [],
|
1408 | index = options.startIndex || 0,
|
1409 | target = options.target || null;
|
1410 |
|
1411 |
|
1412 |
|
1413 | options = extend({}, options, Fancybox.openers.get(opener));
|
1414 |
|
1415 |
|
1416 |
|
1417 | const groupAll = options.groupAll === undefined ? false : options.groupAll;
|
1418 |
|
1419 | const groupAttr = options.groupAttr === undefined ? "data-fancybox" : options.groupAttr;
|
1420 | const groupValue = groupAttr && target ? target.getAttribute(`${groupAttr}`) : "";
|
1421 |
|
1422 | if (!target || groupValue || groupAll) {
|
1423 | const $root = options.root || (target ? target.getRootNode() : document.body);
|
1424 |
|
1425 | items = [].slice.call($root.querySelectorAll(opener));
|
1426 | }
|
1427 |
|
1428 | if (target && !groupAll) {
|
1429 | if (groupValue) {
|
1430 | items = items.filter((el) => el.getAttribute(`${groupAttr}`) === groupValue);
|
1431 | } else {
|
1432 | items = [target];
|
1433 | }
|
1434 | }
|
1435 |
|
1436 | if (!items.length) {
|
1437 | return false;
|
1438 | }
|
1439 |
|
1440 |
|
1441 |
|
1442 | const currentInstance = Fancybox.getInstance();
|
1443 |
|
1444 | if (currentInstance && items.indexOf(currentInstance.options.$trigger) > -1) {
|
1445 | return false;
|
1446 | }
|
1447 |
|
1448 |
|
1449 |
|
1450 |
|
1451 |
|
1452 | index = target ? items.indexOf(target) : index;
|
1453 |
|
1454 |
|
1455 | items = items.map(mapCallback);
|
1456 |
|
1457 |
|
1458 | return new Fancybox(
|
1459 | items,
|
1460 | extend({}, options, {
|
1461 | startIndex: index,
|
1462 | $trigger: target,
|
1463 | })
|
1464 | );
|
1465 | }
|
1466 |
|
1467 | |
1468 |
|
1469 |
|
1470 |
|
1471 |
|
1472 | static bind(selector, options = {}) {
|
1473 | function attachClickEvent() {
|
1474 | document.body.addEventListener("click", Fancybox.fromEvent, false);
|
1475 | }
|
1476 |
|
1477 | if (!canUseDOM) {
|
1478 | return;
|
1479 | }
|
1480 |
|
1481 | if (!Fancybox.openers.size) {
|
1482 | if (/complete|interactive|loaded/.test(document.readyState)) {
|
1483 | attachClickEvent();
|
1484 | } else {
|
1485 | document.addEventListener("DOMContentLoaded", attachClickEvent);
|
1486 | }
|
1487 | }
|
1488 |
|
1489 | Fancybox.openers.set(selector, options);
|
1490 | }
|
1491 |
|
1492 | |
1493 |
|
1494 |
|
1495 |
|
1496 | static unbind(selector) {
|
1497 | Fancybox.openers.delete(selector);
|
1498 |
|
1499 | if (!Fancybox.openers.size) {
|
1500 | Fancybox.destroy();
|
1501 | }
|
1502 | }
|
1503 |
|
1504 | |
1505 |
|
1506 |
|
1507 | static destroy() {
|
1508 | let fb;
|
1509 |
|
1510 | while ((fb = Fancybox.getInstance())) {
|
1511 | fb.destroy();
|
1512 | }
|
1513 |
|
1514 | Fancybox.openers = new Map();
|
1515 |
|
1516 | document.body.removeEventListener("click", Fancybox.fromEvent, false);
|
1517 | }
|
1518 |
|
1519 | |
1520 |
|
1521 |
|
1522 |
|
1523 | static getInstance(id) {
|
1524 | if (id) {
|
1525 | return instances.get(id);
|
1526 | }
|
1527 |
|
1528 | const instance = Array.from(instances.values())
|
1529 | .reverse()
|
1530 | .find((instance) => {
|
1531 | if (!["closing", "customClosing", "destroy"].includes(instance.state)) {
|
1532 | return instance;
|
1533 | }
|
1534 |
|
1535 | return false;
|
1536 | });
|
1537 |
|
1538 | return instance || null;
|
1539 | }
|
1540 |
|
1541 | |
1542 |
|
1543 |
|
1544 |
|
1545 |
|
1546 | static close(all = true, args) {
|
1547 | if (all) {
|
1548 | for (const instance of instances.values()) {
|
1549 | instance.close(args);
|
1550 | }
|
1551 | } else {
|
1552 | const instance = Fancybox.getInstance();
|
1553 |
|
1554 | if (instance) {
|
1555 | instance.close(args);
|
1556 | }
|
1557 | }
|
1558 | }
|
1559 |
|
1560 | |
1561 |
|
1562 |
|
1563 | static next() {
|
1564 | const instance = Fancybox.getInstance();
|
1565 |
|
1566 | if (instance) {
|
1567 | instance.next();
|
1568 | }
|
1569 | }
|
1570 |
|
1571 | |
1572 |
|
1573 |
|
1574 | static prev() {
|
1575 | const instance = Fancybox.getInstance();
|
1576 |
|
1577 | if (instance) {
|
1578 | instance.prev();
|
1579 | }
|
1580 | }
|
1581 | }
|
1582 |
|
1583 |
|
1584 | Fancybox.version = "__VERSION__";
|
1585 |
|
1586 |
|
1587 | Fancybox.defaults = defaults;
|
1588 |
|
1589 |
|
1590 | Fancybox.openers = new Map();
|
1591 |
|
1592 |
|
1593 | Fancybox.Plugins = Plugins;
|
1594 |
|
1595 |
|
1596 | Fancybox.bind("[data-fancybox]");
|
1597 |
|
1598 |
|
1599 | for (const [key, Plugin] of Object.entries(Fancybox.Plugins || {})) {
|
1600 | if (typeof Plugin.create === "function") {
|
1601 | Plugin.create(Fancybox);
|
1602 | }
|
1603 | }
|
1604 |
|
1605 | export { Fancybox };
|