photoswipe_lightbox.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158
  1. import "./chunk-WFTEJBJX.js";
  2. // node_modules/photoswipe/dist/photoswipe-lightbox.esm.js
  3. function createElement(className, tagName, appendToEl) {
  4. const el = document.createElement(tagName);
  5. if (className) {
  6. el.className = className;
  7. }
  8. if (appendToEl) {
  9. appendToEl.appendChild(el);
  10. }
  11. return el;
  12. }
  13. function toTransformString(x, y, scale) {
  14. let propValue = `translate3d(${x}px,${y || 0}px,0)`;
  15. if (scale !== void 0) {
  16. propValue += ` scale3d(${scale},${scale},1)`;
  17. }
  18. return propValue;
  19. }
  20. function setWidthHeight(el, w, h) {
  21. el.style.width = typeof w === "number" ? `${w}px` : w;
  22. el.style.height = typeof h === "number" ? `${h}px` : h;
  23. }
  24. var LOAD_STATE = {
  25. IDLE: "idle",
  26. LOADING: "loading",
  27. LOADED: "loaded",
  28. ERROR: "error"
  29. };
  30. function specialKeyUsed(e) {
  31. return "button" in e && e.button === 1 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey;
  32. }
  33. function getElementsFromOption(option, legacySelector, parent = document) {
  34. let elements = [];
  35. if (option instanceof Element) {
  36. elements = [option];
  37. } else if (option instanceof NodeList || Array.isArray(option)) {
  38. elements = Array.from(option);
  39. } else {
  40. const selector = typeof option === "string" ? option : legacySelector;
  41. if (selector) {
  42. elements = Array.from(parent.querySelectorAll(selector));
  43. }
  44. }
  45. return elements;
  46. }
  47. function isPswpClass(fn) {
  48. return typeof fn === "function" && fn.prototype && fn.prototype.goTo;
  49. }
  50. function isSafari() {
  51. return !!(navigator.vendor && navigator.vendor.match(/apple/i));
  52. }
  53. var PhotoSwipeEvent = class {
  54. /**
  55. * @param {T} type
  56. * @param {PhotoSwipeEventsMap[T]} [details]
  57. */
  58. constructor(type, details) {
  59. this.type = type;
  60. this.defaultPrevented = false;
  61. if (details) {
  62. Object.assign(this, details);
  63. }
  64. }
  65. preventDefault() {
  66. this.defaultPrevented = true;
  67. }
  68. };
  69. var Eventable = class {
  70. constructor() {
  71. this._listeners = {};
  72. this._filters = {};
  73. this.pswp = void 0;
  74. this.options = void 0;
  75. }
  76. /**
  77. * @template {keyof PhotoSwipeFiltersMap} T
  78. * @param {T} name
  79. * @param {PhotoSwipeFiltersMap[T]} fn
  80. * @param {number} priority
  81. */
  82. addFilter(name, fn, priority = 100) {
  83. var _this$_filters$name, _this$_filters$name2, _this$pswp;
  84. if (!this._filters[name]) {
  85. this._filters[name] = [];
  86. }
  87. (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.push({
  88. fn,
  89. priority
  90. });
  91. (_this$_filters$name2 = this._filters[name]) === null || _this$_filters$name2 === void 0 || _this$_filters$name2.sort((f1, f2) => f1.priority - f2.priority);
  92. (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.addFilter(name, fn, priority);
  93. }
  94. /**
  95. * @template {keyof PhotoSwipeFiltersMap} T
  96. * @param {T} name
  97. * @param {PhotoSwipeFiltersMap[T]} fn
  98. */
  99. removeFilter(name, fn) {
  100. if (this._filters[name]) {
  101. this._filters[name] = this._filters[name].filter((filter) => filter.fn !== fn);
  102. }
  103. if (this.pswp) {
  104. this.pswp.removeFilter(name, fn);
  105. }
  106. }
  107. /**
  108. * @template {keyof PhotoSwipeFiltersMap} T
  109. * @param {T} name
  110. * @param {Parameters<PhotoSwipeFiltersMap[T]>} args
  111. * @returns {Parameters<PhotoSwipeFiltersMap[T]>[0]}
  112. */
  113. applyFilters(name, ...args) {
  114. var _this$_filters$name3;
  115. (_this$_filters$name3 = this._filters[name]) === null || _this$_filters$name3 === void 0 || _this$_filters$name3.forEach((filter) => {
  116. args[0] = filter.fn.apply(this, args);
  117. });
  118. return args[0];
  119. }
  120. /**
  121. * @template {keyof PhotoSwipeEventsMap} T
  122. * @param {T} name
  123. * @param {EventCallback<T>} fn
  124. */
  125. on(name, fn) {
  126. var _this$_listeners$name, _this$pswp2;
  127. if (!this._listeners[name]) {
  128. this._listeners[name] = [];
  129. }
  130. (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.push(fn);
  131. (_this$pswp2 = this.pswp) === null || _this$pswp2 === void 0 || _this$pswp2.on(name, fn);
  132. }
  133. /**
  134. * @template {keyof PhotoSwipeEventsMap} T
  135. * @param {T} name
  136. * @param {EventCallback<T>} fn
  137. */
  138. off(name, fn) {
  139. var _this$pswp3;
  140. if (this._listeners[name]) {
  141. this._listeners[name] = this._listeners[name].filter((listener) => fn !== listener);
  142. }
  143. (_this$pswp3 = this.pswp) === null || _this$pswp3 === void 0 || _this$pswp3.off(name, fn);
  144. }
  145. /**
  146. * @template {keyof PhotoSwipeEventsMap} T
  147. * @param {T} name
  148. * @param {PhotoSwipeEventsMap[T]} [details]
  149. * @returns {AugmentedEvent<T>}
  150. */
  151. dispatch(name, details) {
  152. var _this$_listeners$name2;
  153. if (this.pswp) {
  154. return this.pswp.dispatch(name, details);
  155. }
  156. const event = (
  157. /** @type {AugmentedEvent<T>} */
  158. new PhotoSwipeEvent(name, details)
  159. );
  160. (_this$_listeners$name2 = this._listeners[name]) === null || _this$_listeners$name2 === void 0 || _this$_listeners$name2.forEach((listener) => {
  161. listener.call(this, event);
  162. });
  163. return event;
  164. }
  165. };
  166. var Placeholder = class {
  167. /**
  168. * @param {string | false} imageSrc
  169. * @param {HTMLElement} container
  170. */
  171. constructor(imageSrc, container) {
  172. this.element = createElement("pswp__img pswp__img--placeholder", imageSrc ? "img" : "div", container);
  173. if (imageSrc) {
  174. const imgEl = (
  175. /** @type {HTMLImageElement} */
  176. this.element
  177. );
  178. imgEl.decoding = "async";
  179. imgEl.alt = "";
  180. imgEl.src = imageSrc;
  181. imgEl.setAttribute("role", "presentation");
  182. }
  183. this.element.setAttribute("aria-hidden", "true");
  184. }
  185. /**
  186. * @param {number} width
  187. * @param {number} height
  188. */
  189. setDisplayedSize(width, height) {
  190. if (!this.element) {
  191. return;
  192. }
  193. if (this.element.tagName === "IMG") {
  194. setWidthHeight(this.element, 250, "auto");
  195. this.element.style.transformOrigin = "0 0";
  196. this.element.style.transform = toTransformString(0, 0, width / 250);
  197. } else {
  198. setWidthHeight(this.element, width, height);
  199. }
  200. }
  201. destroy() {
  202. var _this$element;
  203. if ((_this$element = this.element) !== null && _this$element !== void 0 && _this$element.parentNode) {
  204. this.element.remove();
  205. }
  206. this.element = null;
  207. }
  208. };
  209. var Content = class {
  210. /**
  211. * @param {SlideData} itemData Slide data
  212. * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance
  213. * @param {number} index
  214. */
  215. constructor(itemData, instance, index) {
  216. this.instance = instance;
  217. this.data = itemData;
  218. this.index = index;
  219. this.element = void 0;
  220. this.placeholder = void 0;
  221. this.slide = void 0;
  222. this.displayedImageWidth = 0;
  223. this.displayedImageHeight = 0;
  224. this.width = Number(this.data.w) || Number(this.data.width) || 0;
  225. this.height = Number(this.data.h) || Number(this.data.height) || 0;
  226. this.isAttached = false;
  227. this.hasSlide = false;
  228. this.isDecoding = false;
  229. this.state = LOAD_STATE.IDLE;
  230. if (this.data.type) {
  231. this.type = this.data.type;
  232. } else if (this.data.src) {
  233. this.type = "image";
  234. } else {
  235. this.type = "html";
  236. }
  237. this.instance.dispatch("contentInit", {
  238. content: this
  239. });
  240. }
  241. removePlaceholder() {
  242. if (this.placeholder && !this.keepPlaceholder()) {
  243. setTimeout(() => {
  244. if (this.placeholder) {
  245. this.placeholder.destroy();
  246. this.placeholder = void 0;
  247. }
  248. }, 1e3);
  249. }
  250. }
  251. /**
  252. * Preload content
  253. *
  254. * @param {boolean} isLazy
  255. * @param {boolean} [reload]
  256. */
  257. load(isLazy, reload) {
  258. if (this.slide && this.usePlaceholder()) {
  259. if (!this.placeholder) {
  260. const placeholderSrc = this.instance.applyFilters(
  261. "placeholderSrc",
  262. // use image-based placeholder only for the first slide,
  263. // as rendering (even small stretched thumbnail) is an expensive operation
  264. this.data.msrc && this.slide.isFirstSlide ? this.data.msrc : false,
  265. this
  266. );
  267. this.placeholder = new Placeholder(placeholderSrc, this.slide.container);
  268. } else {
  269. const placeholderEl = this.placeholder.element;
  270. if (placeholderEl && !placeholderEl.parentElement) {
  271. this.slide.container.prepend(placeholderEl);
  272. }
  273. }
  274. }
  275. if (this.element && !reload) {
  276. return;
  277. }
  278. if (this.instance.dispatch("contentLoad", {
  279. content: this,
  280. isLazy
  281. }).defaultPrevented) {
  282. return;
  283. }
  284. if (this.isImageContent()) {
  285. this.element = createElement("pswp__img", "img");
  286. if (this.displayedImageWidth) {
  287. this.loadImage(isLazy);
  288. }
  289. } else {
  290. this.element = createElement("pswp__content", "div");
  291. this.element.innerHTML = this.data.html || "";
  292. }
  293. if (reload && this.slide) {
  294. this.slide.updateContentSize(true);
  295. }
  296. }
  297. /**
  298. * Preload image
  299. *
  300. * @param {boolean} isLazy
  301. */
  302. loadImage(isLazy) {
  303. var _this$data$src, _this$data$alt;
  304. if (!this.isImageContent() || !this.element || this.instance.dispatch("contentLoadImage", {
  305. content: this,
  306. isLazy
  307. }).defaultPrevented) {
  308. return;
  309. }
  310. const imageElement = (
  311. /** @type HTMLImageElement */
  312. this.element
  313. );
  314. this.updateSrcsetSizes();
  315. if (this.data.srcset) {
  316. imageElement.srcset = this.data.srcset;
  317. }
  318. imageElement.src = (_this$data$src = this.data.src) !== null && _this$data$src !== void 0 ? _this$data$src : "";
  319. imageElement.alt = (_this$data$alt = this.data.alt) !== null && _this$data$alt !== void 0 ? _this$data$alt : "";
  320. this.state = LOAD_STATE.LOADING;
  321. if (imageElement.complete) {
  322. this.onLoaded();
  323. } else {
  324. imageElement.onload = () => {
  325. this.onLoaded();
  326. };
  327. imageElement.onerror = () => {
  328. this.onError();
  329. };
  330. }
  331. }
  332. /**
  333. * Assign slide to content
  334. *
  335. * @param {Slide} slide
  336. */
  337. setSlide(slide) {
  338. this.slide = slide;
  339. this.hasSlide = true;
  340. this.instance = slide.pswp;
  341. }
  342. /**
  343. * Content load success handler
  344. */
  345. onLoaded() {
  346. this.state = LOAD_STATE.LOADED;
  347. if (this.slide && this.element) {
  348. this.instance.dispatch("loadComplete", {
  349. slide: this.slide,
  350. content: this
  351. });
  352. if (this.slide.isActive && this.slide.heavyAppended && !this.element.parentNode) {
  353. this.append();
  354. this.slide.updateContentSize(true);
  355. }
  356. if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {
  357. this.removePlaceholder();
  358. }
  359. }
  360. }
  361. /**
  362. * Content load error handler
  363. */
  364. onError() {
  365. this.state = LOAD_STATE.ERROR;
  366. if (this.slide) {
  367. this.displayError();
  368. this.instance.dispatch("loadComplete", {
  369. slide: this.slide,
  370. isError: true,
  371. content: this
  372. });
  373. this.instance.dispatch("loadError", {
  374. slide: this.slide,
  375. content: this
  376. });
  377. }
  378. }
  379. /**
  380. * @returns {Boolean} If the content is currently loading
  381. */
  382. isLoading() {
  383. return this.instance.applyFilters("isContentLoading", this.state === LOAD_STATE.LOADING, this);
  384. }
  385. /**
  386. * @returns {Boolean} If the content is in error state
  387. */
  388. isError() {
  389. return this.state === LOAD_STATE.ERROR;
  390. }
  391. /**
  392. * @returns {boolean} If the content is image
  393. */
  394. isImageContent() {
  395. return this.type === "image";
  396. }
  397. /**
  398. * Update content size
  399. *
  400. * @param {Number} width
  401. * @param {Number} height
  402. */
  403. setDisplayedSize(width, height) {
  404. if (!this.element) {
  405. return;
  406. }
  407. if (this.placeholder) {
  408. this.placeholder.setDisplayedSize(width, height);
  409. }
  410. if (this.instance.dispatch("contentResize", {
  411. content: this,
  412. width,
  413. height
  414. }).defaultPrevented) {
  415. return;
  416. }
  417. setWidthHeight(this.element, width, height);
  418. if (this.isImageContent() && !this.isError()) {
  419. const isInitialSizeUpdate = !this.displayedImageWidth && width;
  420. this.displayedImageWidth = width;
  421. this.displayedImageHeight = height;
  422. if (isInitialSizeUpdate) {
  423. this.loadImage(false);
  424. } else {
  425. this.updateSrcsetSizes();
  426. }
  427. if (this.slide) {
  428. this.instance.dispatch("imageSizeChange", {
  429. slide: this.slide,
  430. width,
  431. height,
  432. content: this
  433. });
  434. }
  435. }
  436. }
  437. /**
  438. * @returns {boolean} If the content can be zoomed
  439. */
  440. isZoomable() {
  441. return this.instance.applyFilters("isContentZoomable", this.isImageContent() && this.state !== LOAD_STATE.ERROR, this);
  442. }
  443. /**
  444. * Update image srcset sizes attribute based on width and height
  445. */
  446. updateSrcsetSizes() {
  447. if (!this.isImageContent() || !this.element || !this.data.srcset) {
  448. return;
  449. }
  450. const image = (
  451. /** @type HTMLImageElement */
  452. this.element
  453. );
  454. const sizesWidth = this.instance.applyFilters("srcsetSizesWidth", this.displayedImageWidth, this);
  455. if (!image.dataset.largestUsedSize || sizesWidth > parseInt(image.dataset.largestUsedSize, 10)) {
  456. image.sizes = sizesWidth + "px";
  457. image.dataset.largestUsedSize = String(sizesWidth);
  458. }
  459. }
  460. /**
  461. * @returns {boolean} If content should use a placeholder (from msrc by default)
  462. */
  463. usePlaceholder() {
  464. return this.instance.applyFilters("useContentPlaceholder", this.isImageContent(), this);
  465. }
  466. /**
  467. * Preload content with lazy-loading param
  468. */
  469. lazyLoad() {
  470. if (this.instance.dispatch("contentLazyLoad", {
  471. content: this
  472. }).defaultPrevented) {
  473. return;
  474. }
  475. this.load(true);
  476. }
  477. /**
  478. * @returns {boolean} If placeholder should be kept after content is loaded
  479. */
  480. keepPlaceholder() {
  481. return this.instance.applyFilters("isKeepingPlaceholder", this.isLoading(), this);
  482. }
  483. /**
  484. * Destroy the content
  485. */
  486. destroy() {
  487. this.hasSlide = false;
  488. this.slide = void 0;
  489. if (this.instance.dispatch("contentDestroy", {
  490. content: this
  491. }).defaultPrevented) {
  492. return;
  493. }
  494. this.remove();
  495. if (this.placeholder) {
  496. this.placeholder.destroy();
  497. this.placeholder = void 0;
  498. }
  499. if (this.isImageContent() && this.element) {
  500. this.element.onload = null;
  501. this.element.onerror = null;
  502. this.element = void 0;
  503. }
  504. }
  505. /**
  506. * Display error message
  507. */
  508. displayError() {
  509. if (this.slide) {
  510. var _this$instance$option, _this$instance$option2;
  511. let errorMsgEl = createElement("pswp__error-msg", "div");
  512. errorMsgEl.innerText = (_this$instance$option = (_this$instance$option2 = this.instance.options) === null || _this$instance$option2 === void 0 ? void 0 : _this$instance$option2.errorMsg) !== null && _this$instance$option !== void 0 ? _this$instance$option : "";
  513. errorMsgEl = /** @type {HTMLDivElement} */
  514. this.instance.applyFilters("contentErrorElement", errorMsgEl, this);
  515. this.element = createElement("pswp__content pswp__error-msg-container", "div");
  516. this.element.appendChild(errorMsgEl);
  517. this.slide.container.innerText = "";
  518. this.slide.container.appendChild(this.element);
  519. this.slide.updateContentSize(true);
  520. this.removePlaceholder();
  521. }
  522. }
  523. /**
  524. * Append the content
  525. */
  526. append() {
  527. if (this.isAttached || !this.element) {
  528. return;
  529. }
  530. this.isAttached = true;
  531. if (this.state === LOAD_STATE.ERROR) {
  532. this.displayError();
  533. return;
  534. }
  535. if (this.instance.dispatch("contentAppend", {
  536. content: this
  537. }).defaultPrevented) {
  538. return;
  539. }
  540. const supportsDecode = "decode" in this.element;
  541. if (this.isImageContent()) {
  542. if (supportsDecode && this.slide && (!this.slide.isActive || isSafari())) {
  543. this.isDecoding = true;
  544. this.element.decode().catch(() => {
  545. }).finally(() => {
  546. this.isDecoding = false;
  547. this.appendImage();
  548. });
  549. } else {
  550. this.appendImage();
  551. }
  552. } else if (this.slide && !this.element.parentNode) {
  553. this.slide.container.appendChild(this.element);
  554. }
  555. }
  556. /**
  557. * Activate the slide,
  558. * active slide is generally the current one,
  559. * meaning the user can see it.
  560. */
  561. activate() {
  562. if (this.instance.dispatch("contentActivate", {
  563. content: this
  564. }).defaultPrevented || !this.slide) {
  565. return;
  566. }
  567. if (this.isImageContent() && this.isDecoding && !isSafari()) {
  568. this.appendImage();
  569. } else if (this.isError()) {
  570. this.load(false, true);
  571. }
  572. if (this.slide.holderElement) {
  573. this.slide.holderElement.setAttribute("aria-hidden", "false");
  574. }
  575. }
  576. /**
  577. * Deactivate the content
  578. */
  579. deactivate() {
  580. this.instance.dispatch("contentDeactivate", {
  581. content: this
  582. });
  583. if (this.slide && this.slide.holderElement) {
  584. this.slide.holderElement.setAttribute("aria-hidden", "true");
  585. }
  586. }
  587. /**
  588. * Remove the content from DOM
  589. */
  590. remove() {
  591. this.isAttached = false;
  592. if (this.instance.dispatch("contentRemove", {
  593. content: this
  594. }).defaultPrevented) {
  595. return;
  596. }
  597. if (this.element && this.element.parentNode) {
  598. this.element.remove();
  599. }
  600. if (this.placeholder && this.placeholder.element) {
  601. this.placeholder.element.remove();
  602. }
  603. }
  604. /**
  605. * Append the image content to slide container
  606. */
  607. appendImage() {
  608. if (!this.isAttached) {
  609. return;
  610. }
  611. if (this.instance.dispatch("contentAppendImage", {
  612. content: this
  613. }).defaultPrevented) {
  614. return;
  615. }
  616. if (this.slide && this.element && !this.element.parentNode) {
  617. this.slide.container.appendChild(this.element);
  618. }
  619. if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {
  620. this.removePlaceholder();
  621. }
  622. }
  623. };
  624. function getViewportSize(options, pswp) {
  625. if (options.getViewportSizeFn) {
  626. const newViewportSize = options.getViewportSizeFn(options, pswp);
  627. if (newViewportSize) {
  628. return newViewportSize;
  629. }
  630. }
  631. return {
  632. x: document.documentElement.clientWidth,
  633. // TODO: height on mobile is very incosistent due to toolbar
  634. // find a way to improve this
  635. //
  636. // document.documentElement.clientHeight - doesn't seem to work well
  637. y: window.innerHeight
  638. };
  639. }
  640. function parsePaddingOption(prop, options, viewportSize, itemData, index) {
  641. let paddingValue = 0;
  642. if (options.paddingFn) {
  643. paddingValue = options.paddingFn(viewportSize, itemData, index)[prop];
  644. } else if (options.padding) {
  645. paddingValue = options.padding[prop];
  646. } else {
  647. const legacyPropName = "padding" + prop[0].toUpperCase() + prop.slice(1);
  648. if (options[legacyPropName]) {
  649. paddingValue = options[legacyPropName];
  650. }
  651. }
  652. return Number(paddingValue) || 0;
  653. }
  654. function getPanAreaSize(options, viewportSize, itemData, index) {
  655. return {
  656. x: viewportSize.x - parsePaddingOption("left", options, viewportSize, itemData, index) - parsePaddingOption("right", options, viewportSize, itemData, index),
  657. y: viewportSize.y - parsePaddingOption("top", options, viewportSize, itemData, index) - parsePaddingOption("bottom", options, viewportSize, itemData, index)
  658. };
  659. }
  660. var MAX_IMAGE_WIDTH = 4e3;
  661. var ZoomLevel = class {
  662. /**
  663. * @param {PhotoSwipeOptions} options PhotoSwipe options
  664. * @param {SlideData} itemData Slide data
  665. * @param {number} index Slide index
  666. * @param {PhotoSwipe} [pswp] PhotoSwipe instance, can be undefined if not initialized yet
  667. */
  668. constructor(options, itemData, index, pswp) {
  669. this.pswp = pswp;
  670. this.options = options;
  671. this.itemData = itemData;
  672. this.index = index;
  673. this.panAreaSize = null;
  674. this.elementSize = null;
  675. this.fit = 1;
  676. this.fill = 1;
  677. this.vFill = 1;
  678. this.initial = 1;
  679. this.secondary = 1;
  680. this.max = 1;
  681. this.min = 1;
  682. }
  683. /**
  684. * Calculate initial, secondary and maximum zoom level for the specified slide.
  685. *
  686. * It should be called when either image or viewport size changes.
  687. *
  688. * @param {number} maxWidth
  689. * @param {number} maxHeight
  690. * @param {Point} panAreaSize
  691. */
  692. update(maxWidth, maxHeight, panAreaSize) {
  693. const elementSize = {
  694. x: maxWidth,
  695. y: maxHeight
  696. };
  697. this.elementSize = elementSize;
  698. this.panAreaSize = panAreaSize;
  699. const hRatio = panAreaSize.x / elementSize.x;
  700. const vRatio = panAreaSize.y / elementSize.y;
  701. this.fit = Math.min(1, hRatio < vRatio ? hRatio : vRatio);
  702. this.fill = Math.min(1, hRatio > vRatio ? hRatio : vRatio);
  703. this.vFill = Math.min(1, vRatio);
  704. this.initial = this._getInitial();
  705. this.secondary = this._getSecondary();
  706. this.max = Math.max(this.initial, this.secondary, this._getMax());
  707. this.min = Math.min(this.fit, this.initial, this.secondary);
  708. if (this.pswp) {
  709. this.pswp.dispatch("zoomLevelsUpdate", {
  710. zoomLevels: this,
  711. slideData: this.itemData
  712. });
  713. }
  714. }
  715. /**
  716. * Parses user-defined zoom option.
  717. *
  718. * @private
  719. * @param {'initial' | 'secondary' | 'max'} optionPrefix Zoom level option prefix (initial, secondary, max)
  720. * @returns { number | undefined }
  721. */
  722. _parseZoomLevelOption(optionPrefix) {
  723. const optionName = (
  724. /** @type {'initialZoomLevel' | 'secondaryZoomLevel' | 'maxZoomLevel'} */
  725. optionPrefix + "ZoomLevel"
  726. );
  727. const optionValue = this.options[optionName];
  728. if (!optionValue) {
  729. return;
  730. }
  731. if (typeof optionValue === "function") {
  732. return optionValue(this);
  733. }
  734. if (optionValue === "fill") {
  735. return this.fill;
  736. }
  737. if (optionValue === "fit") {
  738. return this.fit;
  739. }
  740. return Number(optionValue);
  741. }
  742. /**
  743. * Get zoom level to which image will be zoomed after double-tap gesture,
  744. * or when user clicks on zoom icon,
  745. * or mouse-click on image itself.
  746. * If you return 1 image will be zoomed to its original size.
  747. *
  748. * @private
  749. * @return {number}
  750. */
  751. _getSecondary() {
  752. let currZoomLevel = this._parseZoomLevelOption("secondary");
  753. if (currZoomLevel) {
  754. return currZoomLevel;
  755. }
  756. currZoomLevel = Math.min(1, this.fit * 3);
  757. if (this.elementSize && currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) {
  758. currZoomLevel = MAX_IMAGE_WIDTH / this.elementSize.x;
  759. }
  760. return currZoomLevel;
  761. }
  762. /**
  763. * Get initial image zoom level.
  764. *
  765. * @private
  766. * @return {number}
  767. */
  768. _getInitial() {
  769. return this._parseZoomLevelOption("initial") || this.fit;
  770. }
  771. /**
  772. * Maximum zoom level when user zooms
  773. * via zoom/pinch gesture,
  774. * via cmd/ctrl-wheel or via trackpad.
  775. *
  776. * @private
  777. * @return {number}
  778. */
  779. _getMax() {
  780. return this._parseZoomLevelOption("max") || Math.max(1, this.fit * 4);
  781. }
  782. };
  783. function lazyLoadData(itemData, instance, index) {
  784. const content = instance.createContentFromData(itemData, index);
  785. let zoomLevel;
  786. const {
  787. options
  788. } = instance;
  789. if (options) {
  790. zoomLevel = new ZoomLevel(options, itemData, -1);
  791. let viewportSize;
  792. if (instance.pswp) {
  793. viewportSize = instance.pswp.viewportSize;
  794. } else {
  795. viewportSize = getViewportSize(options, instance);
  796. }
  797. const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index);
  798. zoomLevel.update(content.width, content.height, panAreaSize);
  799. }
  800. content.lazyLoad();
  801. if (zoomLevel) {
  802. content.setDisplayedSize(Math.ceil(content.width * zoomLevel.initial), Math.ceil(content.height * zoomLevel.initial));
  803. }
  804. return content;
  805. }
  806. function lazyLoadSlide(index, instance) {
  807. const itemData = instance.getItemData(index);
  808. if (instance.dispatch("lazyLoadSlide", {
  809. index,
  810. itemData
  811. }).defaultPrevented) {
  812. return;
  813. }
  814. return lazyLoadData(itemData, instance, index);
  815. }
  816. var PhotoSwipeBase = class extends Eventable {
  817. /**
  818. * Get total number of slides
  819. *
  820. * @returns {number}
  821. */
  822. getNumItems() {
  823. var _this$options;
  824. let numItems = 0;
  825. const dataSource = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.dataSource;
  826. if (dataSource && "length" in dataSource) {
  827. numItems = dataSource.length;
  828. } else if (dataSource && "gallery" in dataSource) {
  829. if (!dataSource.items) {
  830. dataSource.items = this._getGalleryDOMElements(dataSource.gallery);
  831. }
  832. if (dataSource.items) {
  833. numItems = dataSource.items.length;
  834. }
  835. }
  836. const event = this.dispatch("numItems", {
  837. dataSource,
  838. numItems
  839. });
  840. return this.applyFilters("numItems", event.numItems, dataSource);
  841. }
  842. /**
  843. * @param {SlideData} slideData
  844. * @param {number} index
  845. * @returns {Content}
  846. */
  847. createContentFromData(slideData, index) {
  848. return new Content(slideData, this, index);
  849. }
  850. /**
  851. * Get item data by index.
  852. *
  853. * "item data" should contain normalized information that PhotoSwipe needs to generate a slide.
  854. * For example, it may contain properties like
  855. * `src`, `srcset`, `w`, `h`, which will be used to generate a slide with image.
  856. *
  857. * @param {number} index
  858. * @returns {SlideData}
  859. */
  860. getItemData(index) {
  861. var _this$options2;
  862. const dataSource = (_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : _this$options2.dataSource;
  863. let dataSourceItem = {};
  864. if (Array.isArray(dataSource)) {
  865. dataSourceItem = dataSource[index];
  866. } else if (dataSource && "gallery" in dataSource) {
  867. if (!dataSource.items) {
  868. dataSource.items = this._getGalleryDOMElements(dataSource.gallery);
  869. }
  870. dataSourceItem = dataSource.items[index];
  871. }
  872. let itemData = dataSourceItem;
  873. if (itemData instanceof Element) {
  874. itemData = this._domElementToItemData(itemData);
  875. }
  876. const event = this.dispatch("itemData", {
  877. itemData: itemData || {},
  878. index
  879. });
  880. return this.applyFilters("itemData", event.itemData, index);
  881. }
  882. /**
  883. * Get array of gallery DOM elements,
  884. * based on childSelector and gallery element.
  885. *
  886. * @param {HTMLElement} galleryElement
  887. * @returns {HTMLElement[]}
  888. */
  889. _getGalleryDOMElements(galleryElement) {
  890. var _this$options3, _this$options4;
  891. if ((_this$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.children || (_this$options4 = this.options) !== null && _this$options4 !== void 0 && _this$options4.childSelector) {
  892. return getElementsFromOption(this.options.children, this.options.childSelector, galleryElement) || [];
  893. }
  894. return [galleryElement];
  895. }
  896. /**
  897. * Converts DOM element to item data object.
  898. *
  899. * @param {HTMLElement} element DOM element
  900. * @returns {SlideData}
  901. */
  902. _domElementToItemData(element) {
  903. const itemData = {
  904. element
  905. };
  906. const linkEl = (
  907. /** @type {HTMLAnchorElement} */
  908. element.tagName === "A" ? element : element.querySelector("a")
  909. );
  910. if (linkEl) {
  911. itemData.src = linkEl.dataset.pswpSrc || linkEl.href;
  912. if (linkEl.dataset.pswpSrcset) {
  913. itemData.srcset = linkEl.dataset.pswpSrcset;
  914. }
  915. itemData.width = linkEl.dataset.pswpWidth ? parseInt(linkEl.dataset.pswpWidth, 10) : 0;
  916. itemData.height = linkEl.dataset.pswpHeight ? parseInt(linkEl.dataset.pswpHeight, 10) : 0;
  917. itemData.w = itemData.width;
  918. itemData.h = itemData.height;
  919. if (linkEl.dataset.pswpType) {
  920. itemData.type = linkEl.dataset.pswpType;
  921. }
  922. const thumbnailEl = element.querySelector("img");
  923. if (thumbnailEl) {
  924. var _thumbnailEl$getAttri;
  925. itemData.msrc = thumbnailEl.currentSrc || thumbnailEl.src;
  926. itemData.alt = (_thumbnailEl$getAttri = thumbnailEl.getAttribute("alt")) !== null && _thumbnailEl$getAttri !== void 0 ? _thumbnailEl$getAttri : "";
  927. }
  928. if (linkEl.dataset.pswpCropped || linkEl.dataset.cropped) {
  929. itemData.thumbCropped = true;
  930. }
  931. }
  932. return this.applyFilters("domItemData", itemData, element, linkEl);
  933. }
  934. /**
  935. * Lazy-load by slide data
  936. *
  937. * @param {SlideData} itemData Data about the slide
  938. * @param {number} index
  939. * @returns {Content} Image that is being decoded or false.
  940. */
  941. lazyLoadData(itemData, index) {
  942. return lazyLoadData(itemData, this, index);
  943. }
  944. };
  945. var PhotoSwipeLightbox = class extends PhotoSwipeBase {
  946. /**
  947. * @param {PhotoSwipeOptions} [options]
  948. */
  949. constructor(options) {
  950. super();
  951. this.options = options || {};
  952. this._uid = 0;
  953. this.shouldOpen = false;
  954. this._preloadedContent = void 0;
  955. this.onThumbnailsClick = this.onThumbnailsClick.bind(this);
  956. }
  957. /**
  958. * Initialize lightbox, should be called only once.
  959. * It's not included in the main constructor, so you may bind events before it.
  960. */
  961. init() {
  962. getElementsFromOption(this.options.gallery, this.options.gallerySelector).forEach((galleryElement) => {
  963. galleryElement.addEventListener("click", this.onThumbnailsClick, false);
  964. });
  965. }
  966. /**
  967. * @param {MouseEvent} e
  968. */
  969. onThumbnailsClick(e) {
  970. if (specialKeyUsed(e) || window.pswp) {
  971. return;
  972. }
  973. let initialPoint = {
  974. x: e.clientX,
  975. y: e.clientY
  976. };
  977. if (!initialPoint.x && !initialPoint.y) {
  978. initialPoint = null;
  979. }
  980. let clickedIndex = this.getClickedIndex(e);
  981. clickedIndex = this.applyFilters("clickedIndex", clickedIndex, e, this);
  982. const dataSource = {
  983. gallery: (
  984. /** @type {HTMLElement} */
  985. e.currentTarget
  986. )
  987. };
  988. if (clickedIndex >= 0) {
  989. e.preventDefault();
  990. this.loadAndOpen(clickedIndex, dataSource, initialPoint);
  991. }
  992. }
  993. /**
  994. * Get index of gallery item that was clicked.
  995. *
  996. * @param {MouseEvent} e click event
  997. * @returns {number}
  998. */
  999. getClickedIndex(e) {
  1000. if (this.options.getClickedIndexFn) {
  1001. return this.options.getClickedIndexFn.call(this, e);
  1002. }
  1003. const clickedTarget = (
  1004. /** @type {HTMLElement} */
  1005. e.target
  1006. );
  1007. const childElements = getElementsFromOption(
  1008. this.options.children,
  1009. this.options.childSelector,
  1010. /** @type {HTMLElement} */
  1011. e.currentTarget
  1012. );
  1013. const clickedChildIndex = childElements.findIndex((child) => child === clickedTarget || child.contains(clickedTarget));
  1014. if (clickedChildIndex !== -1) {
  1015. return clickedChildIndex;
  1016. } else if (this.options.children || this.options.childSelector) {
  1017. return -1;
  1018. }
  1019. return 0;
  1020. }
  1021. /**
  1022. * Load and open PhotoSwipe
  1023. *
  1024. * @param {number} index
  1025. * @param {DataSource} [dataSource]
  1026. * @param {Point | null} [initialPoint]
  1027. * @returns {boolean}
  1028. */
  1029. loadAndOpen(index, dataSource, initialPoint) {
  1030. if (window.pswp || !this.options) {
  1031. return false;
  1032. }
  1033. if (!dataSource && this.options.gallery && this.options.children) {
  1034. const galleryElements = getElementsFromOption(this.options.gallery);
  1035. if (galleryElements[0]) {
  1036. dataSource = {
  1037. gallery: galleryElements[0]
  1038. };
  1039. }
  1040. }
  1041. this.options.index = index;
  1042. this.options.initialPointerPos = initialPoint;
  1043. this.shouldOpen = true;
  1044. this.preload(index, dataSource);
  1045. return true;
  1046. }
  1047. /**
  1048. * Load the main module and the slide content by index
  1049. *
  1050. * @param {number} index
  1051. * @param {DataSource} [dataSource]
  1052. */
  1053. preload(index, dataSource) {
  1054. const {
  1055. options
  1056. } = this;
  1057. if (dataSource) {
  1058. options.dataSource = dataSource;
  1059. }
  1060. const promiseArray = [];
  1061. const pswpModuleType = typeof options.pswpModule;
  1062. if (isPswpClass(options.pswpModule)) {
  1063. promiseArray.push(Promise.resolve(
  1064. /** @type {Type<PhotoSwipe>} */
  1065. options.pswpModule
  1066. ));
  1067. } else if (pswpModuleType === "string") {
  1068. throw new Error("pswpModule as string is no longer supported");
  1069. } else if (pswpModuleType === "function") {
  1070. promiseArray.push(
  1071. /** @type {() => Promise<Type<PhotoSwipe>>} */
  1072. options.pswpModule()
  1073. );
  1074. } else {
  1075. throw new Error("pswpModule is not valid");
  1076. }
  1077. if (typeof options.openPromise === "function") {
  1078. promiseArray.push(options.openPromise());
  1079. }
  1080. if (options.preloadFirstSlide !== false && index >= 0) {
  1081. this._preloadedContent = lazyLoadSlide(index, this);
  1082. }
  1083. const uid = ++this._uid;
  1084. Promise.all(promiseArray).then((iterableModules) => {
  1085. if (this.shouldOpen) {
  1086. const mainModule = iterableModules[0];
  1087. this._openPhotoswipe(mainModule, uid);
  1088. }
  1089. });
  1090. }
  1091. /**
  1092. * @private
  1093. * @param {Type<PhotoSwipe> | { default: Type<PhotoSwipe> }} module
  1094. * @param {number} uid
  1095. */
  1096. _openPhotoswipe(module, uid) {
  1097. if (uid !== this._uid && this.shouldOpen) {
  1098. return;
  1099. }
  1100. this.shouldOpen = false;
  1101. if (window.pswp) {
  1102. return;
  1103. }
  1104. const pswp = typeof module === "object" ? new module.default(this.options) : new module(this.options);
  1105. this.pswp = pswp;
  1106. window.pswp = pswp;
  1107. Object.keys(this._listeners).forEach((name) => {
  1108. var _this$_listeners$name;
  1109. (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.forEach((fn) => {
  1110. pswp.on(
  1111. name,
  1112. /** @type {EventCallback<typeof name>} */
  1113. fn
  1114. );
  1115. });
  1116. });
  1117. Object.keys(this._filters).forEach((name) => {
  1118. var _this$_filters$name;
  1119. (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.forEach((filter) => {
  1120. pswp.addFilter(name, filter.fn, filter.priority);
  1121. });
  1122. });
  1123. if (this._preloadedContent) {
  1124. pswp.contentLoader.addToCache(this._preloadedContent);
  1125. this._preloadedContent = void 0;
  1126. }
  1127. pswp.on("destroy", () => {
  1128. this.pswp = void 0;
  1129. delete window.pswp;
  1130. });
  1131. pswp.init();
  1132. }
  1133. /**
  1134. * Unbinds all events, closes PhotoSwipe if it's open.
  1135. */
  1136. destroy() {
  1137. var _this$pswp;
  1138. (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.destroy();
  1139. this.shouldOpen = false;
  1140. this._listeners = {};
  1141. getElementsFromOption(this.options.gallery, this.options.gallerySelector).forEach((galleryElement) => {
  1142. galleryElement.removeEventListener("click", this.onThumbnailsClick, false);
  1143. });
  1144. }
  1145. };
  1146. export {
  1147. PhotoSwipeLightbox as default
  1148. };
  1149. /*! Bundled license information:
  1150. photoswipe/dist/photoswipe-lightbox.esm.js:
  1151. (*!
  1152. * PhotoSwipe Lightbox 5.4.4 - https://photoswipe.com
  1153. * (c) 2024 Dmytro Semenov
  1154. *)
  1155. */
  1156. //# sourceMappingURL=photoswipe_lightbox.js.map