photoswipe.js 130 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553
  1. import "./chunk-WFTEJBJX.js";
  2. // node_modules/photoswipe/dist/photoswipe.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 equalizePoints(p1, p2) {
  14. p1.x = p2.x;
  15. p1.y = p2.y;
  16. if (p2.id !== void 0) {
  17. p1.id = p2.id;
  18. }
  19. return p1;
  20. }
  21. function roundPoint(p) {
  22. p.x = Math.round(p.x);
  23. p.y = Math.round(p.y);
  24. }
  25. function getDistanceBetween(p1, p2) {
  26. const x = Math.abs(p1.x - p2.x);
  27. const y = Math.abs(p1.y - p2.y);
  28. return Math.sqrt(x * x + y * y);
  29. }
  30. function pointsEqual(p1, p2) {
  31. return p1.x === p2.x && p1.y === p2.y;
  32. }
  33. function clamp(val, min, max) {
  34. return Math.min(Math.max(val, min), max);
  35. }
  36. function toTransformString(x, y, scale) {
  37. let propValue = `translate3d(${x}px,${y || 0}px,0)`;
  38. if (scale !== void 0) {
  39. propValue += ` scale3d(${scale},${scale},1)`;
  40. }
  41. return propValue;
  42. }
  43. function setTransform(el, x, y, scale) {
  44. el.style.transform = toTransformString(x, y, scale);
  45. }
  46. var defaultCSSEasing = "cubic-bezier(.4,0,.22,1)";
  47. function setTransitionStyle(el, prop, duration, ease) {
  48. el.style.transition = prop ? `${prop} ${duration}ms ${ease || defaultCSSEasing}` : "none";
  49. }
  50. function setWidthHeight(el, w, h) {
  51. el.style.width = typeof w === "number" ? `${w}px` : w;
  52. el.style.height = typeof h === "number" ? `${h}px` : h;
  53. }
  54. function removeTransitionStyle(el) {
  55. setTransitionStyle(el);
  56. }
  57. function decodeImage(img) {
  58. if ("decode" in img) {
  59. return img.decode().catch(() => {
  60. });
  61. }
  62. if (img.complete) {
  63. return Promise.resolve(img);
  64. }
  65. return new Promise((resolve, reject) => {
  66. img.onload = () => resolve(img);
  67. img.onerror = reject;
  68. });
  69. }
  70. var LOAD_STATE = {
  71. IDLE: "idle",
  72. LOADING: "loading",
  73. LOADED: "loaded",
  74. ERROR: "error"
  75. };
  76. function specialKeyUsed(e) {
  77. return "button" in e && e.button === 1 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey;
  78. }
  79. function getElementsFromOption(option, legacySelector, parent = document) {
  80. let elements = [];
  81. if (option instanceof Element) {
  82. elements = [option];
  83. } else if (option instanceof NodeList || Array.isArray(option)) {
  84. elements = Array.from(option);
  85. } else {
  86. const selector = typeof option === "string" ? option : legacySelector;
  87. if (selector) {
  88. elements = Array.from(parent.querySelectorAll(selector));
  89. }
  90. }
  91. return elements;
  92. }
  93. function isSafari() {
  94. return !!(navigator.vendor && navigator.vendor.match(/apple/i));
  95. }
  96. var supportsPassive = false;
  97. try {
  98. window.addEventListener("test", null, Object.defineProperty({}, "passive", {
  99. get: () => {
  100. supportsPassive = true;
  101. }
  102. }));
  103. } catch (e) {
  104. }
  105. var DOMEvents = class {
  106. constructor() {
  107. this._pool = [];
  108. }
  109. /**
  110. * Adds event listeners
  111. *
  112. * @param {PoolItem['target']} target
  113. * @param {PoolItem['type']} type Can be multiple, separated by space.
  114. * @param {PoolItem['listener']} listener
  115. * @param {PoolItem['passive']} [passive]
  116. */
  117. add(target, type, listener, passive) {
  118. this._toggleListener(target, type, listener, passive);
  119. }
  120. /**
  121. * Removes event listeners
  122. *
  123. * @param {PoolItem['target']} target
  124. * @param {PoolItem['type']} type
  125. * @param {PoolItem['listener']} listener
  126. * @param {PoolItem['passive']} [passive]
  127. */
  128. remove(target, type, listener, passive) {
  129. this._toggleListener(target, type, listener, passive, true);
  130. }
  131. /**
  132. * Removes all bound events
  133. */
  134. removeAll() {
  135. this._pool.forEach((poolItem) => {
  136. this._toggleListener(poolItem.target, poolItem.type, poolItem.listener, poolItem.passive, true, true);
  137. });
  138. this._pool = [];
  139. }
  140. /**
  141. * Adds or removes event
  142. *
  143. * @private
  144. * @param {PoolItem['target']} target
  145. * @param {PoolItem['type']} type
  146. * @param {PoolItem['listener']} listener
  147. * @param {PoolItem['passive']} [passive]
  148. * @param {boolean} [unbind] Whether the event should be added or removed
  149. * @param {boolean} [skipPool] Whether events pool should be skipped
  150. */
  151. _toggleListener(target, type, listener, passive, unbind, skipPool) {
  152. if (!target) {
  153. return;
  154. }
  155. const methodName = unbind ? "removeEventListener" : "addEventListener";
  156. const types = type.split(" ");
  157. types.forEach((eType) => {
  158. if (eType) {
  159. if (!skipPool) {
  160. if (unbind) {
  161. this._pool = this._pool.filter((poolItem) => {
  162. return poolItem.type !== eType || poolItem.listener !== listener || poolItem.target !== target;
  163. });
  164. } else {
  165. this._pool.push({
  166. target,
  167. type: eType,
  168. listener,
  169. passive
  170. });
  171. }
  172. }
  173. const eventOptions = supportsPassive ? {
  174. passive: passive || false
  175. } : false;
  176. target[methodName](eType, listener, eventOptions);
  177. }
  178. });
  179. }
  180. };
  181. function getViewportSize(options, pswp) {
  182. if (options.getViewportSizeFn) {
  183. const newViewportSize = options.getViewportSizeFn(options, pswp);
  184. if (newViewportSize) {
  185. return newViewportSize;
  186. }
  187. }
  188. return {
  189. x: document.documentElement.clientWidth,
  190. // TODO: height on mobile is very incosistent due to toolbar
  191. // find a way to improve this
  192. //
  193. // document.documentElement.clientHeight - doesn't seem to work well
  194. y: window.innerHeight
  195. };
  196. }
  197. function parsePaddingOption(prop, options, viewportSize, itemData, index) {
  198. let paddingValue = 0;
  199. if (options.paddingFn) {
  200. paddingValue = options.paddingFn(viewportSize, itemData, index)[prop];
  201. } else if (options.padding) {
  202. paddingValue = options.padding[prop];
  203. } else {
  204. const legacyPropName = "padding" + prop[0].toUpperCase() + prop.slice(1);
  205. if (options[legacyPropName]) {
  206. paddingValue = options[legacyPropName];
  207. }
  208. }
  209. return Number(paddingValue) || 0;
  210. }
  211. function getPanAreaSize(options, viewportSize, itemData, index) {
  212. return {
  213. x: viewportSize.x - parsePaddingOption("left", options, viewportSize, itemData, index) - parsePaddingOption("right", options, viewportSize, itemData, index),
  214. y: viewportSize.y - parsePaddingOption("top", options, viewportSize, itemData, index) - parsePaddingOption("bottom", options, viewportSize, itemData, index)
  215. };
  216. }
  217. var PanBounds = class {
  218. /**
  219. * @param {Slide} slide
  220. */
  221. constructor(slide) {
  222. this.slide = slide;
  223. this.currZoomLevel = 1;
  224. this.center = /** @type {Point} */
  225. {
  226. x: 0,
  227. y: 0
  228. };
  229. this.max = /** @type {Point} */
  230. {
  231. x: 0,
  232. y: 0
  233. };
  234. this.min = /** @type {Point} */
  235. {
  236. x: 0,
  237. y: 0
  238. };
  239. }
  240. /**
  241. * _getItemBounds
  242. *
  243. * @param {number} currZoomLevel
  244. */
  245. update(currZoomLevel) {
  246. this.currZoomLevel = currZoomLevel;
  247. if (!this.slide.width) {
  248. this.reset();
  249. } else {
  250. this._updateAxis("x");
  251. this._updateAxis("y");
  252. this.slide.pswp.dispatch("calcBounds", {
  253. slide: this.slide
  254. });
  255. }
  256. }
  257. /**
  258. * _calculateItemBoundsForAxis
  259. *
  260. * @param {Axis} axis
  261. */
  262. _updateAxis(axis) {
  263. const {
  264. pswp
  265. } = this.slide;
  266. const elSize = this.slide[axis === "x" ? "width" : "height"] * this.currZoomLevel;
  267. const paddingProp = axis === "x" ? "left" : "top";
  268. const padding = parsePaddingOption(paddingProp, pswp.options, pswp.viewportSize, this.slide.data, this.slide.index);
  269. const panAreaSize = this.slide.panAreaSize[axis];
  270. this.center[axis] = Math.round((panAreaSize - elSize) / 2) + padding;
  271. this.max[axis] = elSize > panAreaSize ? Math.round(panAreaSize - elSize) + padding : this.center[axis];
  272. this.min[axis] = elSize > panAreaSize ? padding : this.center[axis];
  273. }
  274. // _getZeroBounds
  275. reset() {
  276. this.center.x = 0;
  277. this.center.y = 0;
  278. this.max.x = 0;
  279. this.max.y = 0;
  280. this.min.x = 0;
  281. this.min.y = 0;
  282. }
  283. /**
  284. * Correct pan position if it's beyond the bounds
  285. *
  286. * @param {Axis} axis x or y
  287. * @param {number} panOffset
  288. * @returns {number}
  289. */
  290. correctPan(axis, panOffset) {
  291. return clamp(panOffset, this.max[axis], this.min[axis]);
  292. }
  293. };
  294. var MAX_IMAGE_WIDTH = 4e3;
  295. var ZoomLevel = class {
  296. /**
  297. * @param {PhotoSwipeOptions} options PhotoSwipe options
  298. * @param {SlideData} itemData Slide data
  299. * @param {number} index Slide index
  300. * @param {PhotoSwipe} [pswp] PhotoSwipe instance, can be undefined if not initialized yet
  301. */
  302. constructor(options, itemData, index, pswp) {
  303. this.pswp = pswp;
  304. this.options = options;
  305. this.itemData = itemData;
  306. this.index = index;
  307. this.panAreaSize = null;
  308. this.elementSize = null;
  309. this.fit = 1;
  310. this.fill = 1;
  311. this.vFill = 1;
  312. this.initial = 1;
  313. this.secondary = 1;
  314. this.max = 1;
  315. this.min = 1;
  316. }
  317. /**
  318. * Calculate initial, secondary and maximum zoom level for the specified slide.
  319. *
  320. * It should be called when either image or viewport size changes.
  321. *
  322. * @param {number} maxWidth
  323. * @param {number} maxHeight
  324. * @param {Point} panAreaSize
  325. */
  326. update(maxWidth, maxHeight, panAreaSize) {
  327. const elementSize = {
  328. x: maxWidth,
  329. y: maxHeight
  330. };
  331. this.elementSize = elementSize;
  332. this.panAreaSize = panAreaSize;
  333. const hRatio = panAreaSize.x / elementSize.x;
  334. const vRatio = panAreaSize.y / elementSize.y;
  335. this.fit = Math.min(1, hRatio < vRatio ? hRatio : vRatio);
  336. this.fill = Math.min(1, hRatio > vRatio ? hRatio : vRatio);
  337. this.vFill = Math.min(1, vRatio);
  338. this.initial = this._getInitial();
  339. this.secondary = this._getSecondary();
  340. this.max = Math.max(this.initial, this.secondary, this._getMax());
  341. this.min = Math.min(this.fit, this.initial, this.secondary);
  342. if (this.pswp) {
  343. this.pswp.dispatch("zoomLevelsUpdate", {
  344. zoomLevels: this,
  345. slideData: this.itemData
  346. });
  347. }
  348. }
  349. /**
  350. * Parses user-defined zoom option.
  351. *
  352. * @private
  353. * @param {'initial' | 'secondary' | 'max'} optionPrefix Zoom level option prefix (initial, secondary, max)
  354. * @returns { number | undefined }
  355. */
  356. _parseZoomLevelOption(optionPrefix) {
  357. const optionName = (
  358. /** @type {'initialZoomLevel' | 'secondaryZoomLevel' | 'maxZoomLevel'} */
  359. optionPrefix + "ZoomLevel"
  360. );
  361. const optionValue = this.options[optionName];
  362. if (!optionValue) {
  363. return;
  364. }
  365. if (typeof optionValue === "function") {
  366. return optionValue(this);
  367. }
  368. if (optionValue === "fill") {
  369. return this.fill;
  370. }
  371. if (optionValue === "fit") {
  372. return this.fit;
  373. }
  374. return Number(optionValue);
  375. }
  376. /**
  377. * Get zoom level to which image will be zoomed after double-tap gesture,
  378. * or when user clicks on zoom icon,
  379. * or mouse-click on image itself.
  380. * If you return 1 image will be zoomed to its original size.
  381. *
  382. * @private
  383. * @return {number}
  384. */
  385. _getSecondary() {
  386. let currZoomLevel = this._parseZoomLevelOption("secondary");
  387. if (currZoomLevel) {
  388. return currZoomLevel;
  389. }
  390. currZoomLevel = Math.min(1, this.fit * 3);
  391. if (this.elementSize && currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) {
  392. currZoomLevel = MAX_IMAGE_WIDTH / this.elementSize.x;
  393. }
  394. return currZoomLevel;
  395. }
  396. /**
  397. * Get initial image zoom level.
  398. *
  399. * @private
  400. * @return {number}
  401. */
  402. _getInitial() {
  403. return this._parseZoomLevelOption("initial") || this.fit;
  404. }
  405. /**
  406. * Maximum zoom level when user zooms
  407. * via zoom/pinch gesture,
  408. * via cmd/ctrl-wheel or via trackpad.
  409. *
  410. * @private
  411. * @return {number}
  412. */
  413. _getMax() {
  414. return this._parseZoomLevelOption("max") || Math.max(1, this.fit * 4);
  415. }
  416. };
  417. var Slide = class {
  418. /**
  419. * @param {SlideData} data
  420. * @param {number} index
  421. * @param {PhotoSwipe} pswp
  422. */
  423. constructor(data, index, pswp) {
  424. this.data = data;
  425. this.index = index;
  426. this.pswp = pswp;
  427. this.isActive = index === pswp.currIndex;
  428. this.currentResolution = 0;
  429. this.panAreaSize = {
  430. x: 0,
  431. y: 0
  432. };
  433. this.pan = {
  434. x: 0,
  435. y: 0
  436. };
  437. this.isFirstSlide = this.isActive && !pswp.opener.isOpen;
  438. this.zoomLevels = new ZoomLevel(pswp.options, data, index, pswp);
  439. this.pswp.dispatch("gettingData", {
  440. slide: this,
  441. data: this.data,
  442. index
  443. });
  444. this.content = this.pswp.contentLoader.getContentBySlide(this);
  445. this.container = createElement("pswp__zoom-wrap", "div");
  446. this.holderElement = null;
  447. this.currZoomLevel = 1;
  448. this.width = this.content.width;
  449. this.height = this.content.height;
  450. this.heavyAppended = false;
  451. this.bounds = new PanBounds(this);
  452. this.prevDisplayedWidth = -1;
  453. this.prevDisplayedHeight = -1;
  454. this.pswp.dispatch("slideInit", {
  455. slide: this
  456. });
  457. }
  458. /**
  459. * If this slide is active/current/visible
  460. *
  461. * @param {boolean} isActive
  462. */
  463. setIsActive(isActive) {
  464. if (isActive && !this.isActive) {
  465. this.activate();
  466. } else if (!isActive && this.isActive) {
  467. this.deactivate();
  468. }
  469. }
  470. /**
  471. * Appends slide content to DOM
  472. *
  473. * @param {HTMLElement} holderElement
  474. */
  475. append(holderElement) {
  476. this.holderElement = holderElement;
  477. this.container.style.transformOrigin = "0 0";
  478. if (!this.data) {
  479. return;
  480. }
  481. this.calculateSize();
  482. this.load();
  483. this.updateContentSize();
  484. this.appendHeavy();
  485. this.holderElement.appendChild(this.container);
  486. this.zoomAndPanToInitial();
  487. this.pswp.dispatch("firstZoomPan", {
  488. slide: this
  489. });
  490. this.applyCurrentZoomPan();
  491. this.pswp.dispatch("afterSetContent", {
  492. slide: this
  493. });
  494. if (this.isActive) {
  495. this.activate();
  496. }
  497. }
  498. load() {
  499. this.content.load(false);
  500. this.pswp.dispatch("slideLoad", {
  501. slide: this
  502. });
  503. }
  504. /**
  505. * Append "heavy" DOM elements
  506. *
  507. * This may depend on a type of slide,
  508. * but generally these are large images.
  509. */
  510. appendHeavy() {
  511. const {
  512. pswp
  513. } = this;
  514. const appendHeavyNearby = true;
  515. if (this.heavyAppended || !pswp.opener.isOpen || pswp.mainScroll.isShifted() || !this.isActive && !appendHeavyNearby) {
  516. return;
  517. }
  518. if (this.pswp.dispatch("appendHeavy", {
  519. slide: this
  520. }).defaultPrevented) {
  521. return;
  522. }
  523. this.heavyAppended = true;
  524. this.content.append();
  525. this.pswp.dispatch("appendHeavyContent", {
  526. slide: this
  527. });
  528. }
  529. /**
  530. * Triggered when this slide is active (selected).
  531. *
  532. * If it's part of opening/closing transition -
  533. * activate() will trigger after the transition is ended.
  534. */
  535. activate() {
  536. this.isActive = true;
  537. this.appendHeavy();
  538. this.content.activate();
  539. this.pswp.dispatch("slideActivate", {
  540. slide: this
  541. });
  542. }
  543. /**
  544. * Triggered when this slide becomes inactive.
  545. *
  546. * Slide can become inactive only after it was active.
  547. */
  548. deactivate() {
  549. this.isActive = false;
  550. this.content.deactivate();
  551. if (this.currZoomLevel !== this.zoomLevels.initial) {
  552. this.calculateSize();
  553. }
  554. this.currentResolution = 0;
  555. this.zoomAndPanToInitial();
  556. this.applyCurrentZoomPan();
  557. this.updateContentSize();
  558. this.pswp.dispatch("slideDeactivate", {
  559. slide: this
  560. });
  561. }
  562. /**
  563. * The slide should destroy itself, it will never be used again.
  564. * (unbind all events and destroy internal components)
  565. */
  566. destroy() {
  567. this.content.hasSlide = false;
  568. this.content.remove();
  569. this.container.remove();
  570. this.pswp.dispatch("slideDestroy", {
  571. slide: this
  572. });
  573. }
  574. resize() {
  575. if (this.currZoomLevel === this.zoomLevels.initial || !this.isActive) {
  576. this.calculateSize();
  577. this.currentResolution = 0;
  578. this.zoomAndPanToInitial();
  579. this.applyCurrentZoomPan();
  580. this.updateContentSize();
  581. } else {
  582. this.calculateSize();
  583. this.bounds.update(this.currZoomLevel);
  584. this.panTo(this.pan.x, this.pan.y);
  585. }
  586. }
  587. /**
  588. * Apply size to current slide content,
  589. * based on the current resolution and scale.
  590. *
  591. * @param {boolean} [force] if size should be updated even if dimensions weren't changed
  592. */
  593. updateContentSize(force) {
  594. const scaleMultiplier = this.currentResolution || this.zoomLevels.initial;
  595. if (!scaleMultiplier) {
  596. return;
  597. }
  598. const width = Math.round(this.width * scaleMultiplier) || this.pswp.viewportSize.x;
  599. const height = Math.round(this.height * scaleMultiplier) || this.pswp.viewportSize.y;
  600. if (!this.sizeChanged(width, height) && !force) {
  601. return;
  602. }
  603. this.content.setDisplayedSize(width, height);
  604. }
  605. /**
  606. * @param {number} width
  607. * @param {number} height
  608. */
  609. sizeChanged(width, height) {
  610. if (width !== this.prevDisplayedWidth || height !== this.prevDisplayedHeight) {
  611. this.prevDisplayedWidth = width;
  612. this.prevDisplayedHeight = height;
  613. return true;
  614. }
  615. return false;
  616. }
  617. /** @returns {HTMLImageElement | HTMLDivElement | null | undefined} */
  618. getPlaceholderElement() {
  619. var _this$content$placeho;
  620. return (_this$content$placeho = this.content.placeholder) === null || _this$content$placeho === void 0 ? void 0 : _this$content$placeho.element;
  621. }
  622. /**
  623. * Zoom current slide image to...
  624. *
  625. * @param {number} destZoomLevel Destination zoom level.
  626. * @param {Point} [centerPoint]
  627. * Transform origin center point, or false if viewport center should be used.
  628. * @param {number | false} [transitionDuration] Transition duration, may be set to 0.
  629. * @param {boolean} [ignoreBounds] Minimum and maximum zoom levels will be ignored.
  630. */
  631. zoomTo(destZoomLevel, centerPoint, transitionDuration, ignoreBounds) {
  632. const {
  633. pswp
  634. } = this;
  635. if (!this.isZoomable() || pswp.mainScroll.isShifted()) {
  636. return;
  637. }
  638. pswp.dispatch("beforeZoomTo", {
  639. destZoomLevel,
  640. centerPoint,
  641. transitionDuration
  642. });
  643. pswp.animations.stopAllPan();
  644. const prevZoomLevel = this.currZoomLevel;
  645. if (!ignoreBounds) {
  646. destZoomLevel = clamp(destZoomLevel, this.zoomLevels.min, this.zoomLevels.max);
  647. }
  648. this.setZoomLevel(destZoomLevel);
  649. this.pan.x = this.calculateZoomToPanOffset("x", centerPoint, prevZoomLevel);
  650. this.pan.y = this.calculateZoomToPanOffset("y", centerPoint, prevZoomLevel);
  651. roundPoint(this.pan);
  652. const finishTransition = () => {
  653. this._setResolution(destZoomLevel);
  654. this.applyCurrentZoomPan();
  655. };
  656. if (!transitionDuration) {
  657. finishTransition();
  658. } else {
  659. pswp.animations.startTransition({
  660. isPan: true,
  661. name: "zoomTo",
  662. target: this.container,
  663. transform: this.getCurrentTransform(),
  664. onComplete: finishTransition,
  665. duration: transitionDuration,
  666. easing: pswp.options.easing
  667. });
  668. }
  669. }
  670. /**
  671. * @param {Point} [centerPoint]
  672. */
  673. toggleZoom(centerPoint) {
  674. this.zoomTo(this.currZoomLevel === this.zoomLevels.initial ? this.zoomLevels.secondary : this.zoomLevels.initial, centerPoint, this.pswp.options.zoomAnimationDuration);
  675. }
  676. /**
  677. * Updates zoom level property and recalculates new pan bounds,
  678. * unlike zoomTo it does not apply transform (use applyCurrentZoomPan)
  679. *
  680. * @param {number} currZoomLevel
  681. */
  682. setZoomLevel(currZoomLevel) {
  683. this.currZoomLevel = currZoomLevel;
  684. this.bounds.update(this.currZoomLevel);
  685. }
  686. /**
  687. * Get pan position after zoom at a given `point`.
  688. *
  689. * Always call setZoomLevel(newZoomLevel) beforehand to recalculate
  690. * pan bounds according to the new zoom level.
  691. *
  692. * @param {'x' | 'y'} axis
  693. * @param {Point} [point]
  694. * point based on which zoom is performed, usually refers to the current mouse position,
  695. * if false - viewport center will be used.
  696. * @param {number} [prevZoomLevel] Zoom level before new zoom was applied.
  697. * @returns {number}
  698. */
  699. calculateZoomToPanOffset(axis, point, prevZoomLevel) {
  700. const totalPanDistance = this.bounds.max[axis] - this.bounds.min[axis];
  701. if (totalPanDistance === 0) {
  702. return this.bounds.center[axis];
  703. }
  704. if (!point) {
  705. point = this.pswp.getViewportCenterPoint();
  706. }
  707. if (!prevZoomLevel) {
  708. prevZoomLevel = this.zoomLevels.initial;
  709. }
  710. const zoomFactor = this.currZoomLevel / prevZoomLevel;
  711. return this.bounds.correctPan(axis, (this.pan[axis] - point[axis]) * zoomFactor + point[axis]);
  712. }
  713. /**
  714. * Apply pan and keep it within bounds.
  715. *
  716. * @param {number} panX
  717. * @param {number} panY
  718. */
  719. panTo(panX, panY) {
  720. this.pan.x = this.bounds.correctPan("x", panX);
  721. this.pan.y = this.bounds.correctPan("y", panY);
  722. this.applyCurrentZoomPan();
  723. }
  724. /**
  725. * If the slide in the current state can be panned by the user
  726. * @returns {boolean}
  727. */
  728. isPannable() {
  729. return Boolean(this.width) && this.currZoomLevel > this.zoomLevels.fit;
  730. }
  731. /**
  732. * If the slide can be zoomed
  733. * @returns {boolean}
  734. */
  735. isZoomable() {
  736. return Boolean(this.width) && this.content.isZoomable();
  737. }
  738. /**
  739. * Apply transform and scale based on
  740. * the current pan position (this.pan) and zoom level (this.currZoomLevel)
  741. */
  742. applyCurrentZoomPan() {
  743. this._applyZoomTransform(this.pan.x, this.pan.y, this.currZoomLevel);
  744. if (this === this.pswp.currSlide) {
  745. this.pswp.dispatch("zoomPanUpdate", {
  746. slide: this
  747. });
  748. }
  749. }
  750. zoomAndPanToInitial() {
  751. this.currZoomLevel = this.zoomLevels.initial;
  752. this.bounds.update(this.currZoomLevel);
  753. equalizePoints(this.pan, this.bounds.center);
  754. this.pswp.dispatch("initialZoomPan", {
  755. slide: this
  756. });
  757. }
  758. /**
  759. * Set translate and scale based on current resolution
  760. *
  761. * @param {number} x
  762. * @param {number} y
  763. * @param {number} zoom
  764. * @private
  765. */
  766. _applyZoomTransform(x, y, zoom) {
  767. zoom /= this.currentResolution || this.zoomLevels.initial;
  768. setTransform(this.container, x, y, zoom);
  769. }
  770. calculateSize() {
  771. const {
  772. pswp
  773. } = this;
  774. equalizePoints(this.panAreaSize, getPanAreaSize(pswp.options, pswp.viewportSize, this.data, this.index));
  775. this.zoomLevels.update(this.width, this.height, this.panAreaSize);
  776. pswp.dispatch("calcSlideSize", {
  777. slide: this
  778. });
  779. }
  780. /** @returns {string} */
  781. getCurrentTransform() {
  782. const scale = this.currZoomLevel / (this.currentResolution || this.zoomLevels.initial);
  783. return toTransformString(this.pan.x, this.pan.y, scale);
  784. }
  785. /**
  786. * Set resolution and re-render the image.
  787. *
  788. * For example, if the real image size is 2000x1500,
  789. * and resolution is 0.5 - it will be rendered as 1000x750.
  790. *
  791. * Image with zoom level 2 and resolution 0.5 is
  792. * the same as image with zoom level 1 and resolution 1.
  793. *
  794. * Used to optimize animations and make
  795. * sure that browser renders image in the highest quality.
  796. * Also used by responsive images to load the correct one.
  797. *
  798. * @param {number} newResolution
  799. */
  800. _setResolution(newResolution) {
  801. if (newResolution === this.currentResolution) {
  802. return;
  803. }
  804. this.currentResolution = newResolution;
  805. this.updateContentSize();
  806. this.pswp.dispatch("resolutionChanged");
  807. }
  808. };
  809. var PAN_END_FRICTION = 0.35;
  810. var VERTICAL_DRAG_FRICTION = 0.6;
  811. var MIN_RATIO_TO_CLOSE = 0.4;
  812. var MIN_NEXT_SLIDE_SPEED = 0.5;
  813. function project(initialVelocity, decelerationRate) {
  814. return initialVelocity * decelerationRate / (1 - decelerationRate);
  815. }
  816. var DragHandler = class {
  817. /**
  818. * @param {Gestures} gestures
  819. */
  820. constructor(gestures) {
  821. this.gestures = gestures;
  822. this.pswp = gestures.pswp;
  823. this.startPan = {
  824. x: 0,
  825. y: 0
  826. };
  827. }
  828. start() {
  829. if (this.pswp.currSlide) {
  830. equalizePoints(this.startPan, this.pswp.currSlide.pan);
  831. }
  832. this.pswp.animations.stopAll();
  833. }
  834. change() {
  835. const {
  836. p1,
  837. prevP1,
  838. dragAxis
  839. } = this.gestures;
  840. const {
  841. currSlide
  842. } = this.pswp;
  843. if (dragAxis === "y" && this.pswp.options.closeOnVerticalDrag && currSlide && currSlide.currZoomLevel <= currSlide.zoomLevels.fit && !this.gestures.isMultitouch) {
  844. const panY = currSlide.pan.y + (p1.y - prevP1.y);
  845. if (!this.pswp.dispatch("verticalDrag", {
  846. panY
  847. }).defaultPrevented) {
  848. this._setPanWithFriction("y", panY, VERTICAL_DRAG_FRICTION);
  849. const bgOpacity = 1 - Math.abs(this._getVerticalDragRatio(currSlide.pan.y));
  850. this.pswp.applyBgOpacity(bgOpacity);
  851. currSlide.applyCurrentZoomPan();
  852. }
  853. } else {
  854. const mainScrollChanged = this._panOrMoveMainScroll("x");
  855. if (!mainScrollChanged) {
  856. this._panOrMoveMainScroll("y");
  857. if (currSlide) {
  858. roundPoint(currSlide.pan);
  859. currSlide.applyCurrentZoomPan();
  860. }
  861. }
  862. }
  863. }
  864. end() {
  865. const {
  866. velocity
  867. } = this.gestures;
  868. const {
  869. mainScroll,
  870. currSlide
  871. } = this.pswp;
  872. let indexDiff = 0;
  873. this.pswp.animations.stopAll();
  874. if (mainScroll.isShifted()) {
  875. const mainScrollShiftDiff = mainScroll.x - mainScroll.getCurrSlideX();
  876. const currentSlideVisibilityRatio = mainScrollShiftDiff / this.pswp.viewportSize.x;
  877. if (velocity.x < -MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio < 0 || velocity.x < 0.1 && currentSlideVisibilityRatio < -0.5) {
  878. indexDiff = 1;
  879. velocity.x = Math.min(velocity.x, 0);
  880. } else if (velocity.x > MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio > 0 || velocity.x > -0.1 && currentSlideVisibilityRatio > 0.5) {
  881. indexDiff = -1;
  882. velocity.x = Math.max(velocity.x, 0);
  883. }
  884. mainScroll.moveIndexBy(indexDiff, true, velocity.x);
  885. }
  886. if (currSlide && currSlide.currZoomLevel > currSlide.zoomLevels.max || this.gestures.isMultitouch) {
  887. this.gestures.zoomLevels.correctZoomPan(true);
  888. } else {
  889. this._finishPanGestureForAxis("x");
  890. this._finishPanGestureForAxis("y");
  891. }
  892. }
  893. /**
  894. * @private
  895. * @param {'x' | 'y'} axis
  896. */
  897. _finishPanGestureForAxis(axis) {
  898. const {
  899. velocity
  900. } = this.gestures;
  901. const {
  902. currSlide
  903. } = this.pswp;
  904. if (!currSlide) {
  905. return;
  906. }
  907. const {
  908. pan,
  909. bounds
  910. } = currSlide;
  911. const panPos = pan[axis];
  912. const restoreBgOpacity = this.pswp.bgOpacity < 1 && axis === "y";
  913. const decelerationRate = 0.995;
  914. const projectedPosition = panPos + project(velocity[axis], decelerationRate);
  915. if (restoreBgOpacity) {
  916. const vDragRatio = this._getVerticalDragRatio(panPos);
  917. const projectedVDragRatio = this._getVerticalDragRatio(projectedPosition);
  918. if (vDragRatio < 0 && projectedVDragRatio < -MIN_RATIO_TO_CLOSE || vDragRatio > 0 && projectedVDragRatio > MIN_RATIO_TO_CLOSE) {
  919. this.pswp.close();
  920. return;
  921. }
  922. }
  923. const correctedPanPosition = bounds.correctPan(axis, projectedPosition);
  924. if (panPos === correctedPanPosition) {
  925. return;
  926. }
  927. const dampingRatio = correctedPanPosition === projectedPosition ? 1 : 0.82;
  928. const initialBgOpacity = this.pswp.bgOpacity;
  929. const totalPanDist = correctedPanPosition - panPos;
  930. this.pswp.animations.startSpring({
  931. name: "panGesture" + axis,
  932. isPan: true,
  933. start: panPos,
  934. end: correctedPanPosition,
  935. velocity: velocity[axis],
  936. dampingRatio,
  937. onUpdate: (pos) => {
  938. if (restoreBgOpacity && this.pswp.bgOpacity < 1) {
  939. const animationProgressRatio = 1 - (correctedPanPosition - pos) / totalPanDist;
  940. this.pswp.applyBgOpacity(clamp(initialBgOpacity + (1 - initialBgOpacity) * animationProgressRatio, 0, 1));
  941. }
  942. pan[axis] = Math.floor(pos);
  943. currSlide.applyCurrentZoomPan();
  944. }
  945. });
  946. }
  947. /**
  948. * Update position of the main scroll,
  949. * or/and update pan position of the current slide.
  950. *
  951. * Should return true if it changes (or can change) main scroll.
  952. *
  953. * @private
  954. * @param {'x' | 'y'} axis
  955. * @returns {boolean}
  956. */
  957. _panOrMoveMainScroll(axis) {
  958. const {
  959. p1,
  960. dragAxis,
  961. prevP1,
  962. isMultitouch
  963. } = this.gestures;
  964. const {
  965. currSlide,
  966. mainScroll
  967. } = this.pswp;
  968. const delta = p1[axis] - prevP1[axis];
  969. const newMainScrollX = mainScroll.x + delta;
  970. if (!delta || !currSlide) {
  971. return false;
  972. }
  973. if (axis === "x" && !currSlide.isPannable() && !isMultitouch) {
  974. mainScroll.moveTo(newMainScrollX, true);
  975. return true;
  976. }
  977. const {
  978. bounds
  979. } = currSlide;
  980. const newPan = currSlide.pan[axis] + delta;
  981. if (this.pswp.options.allowPanToNext && dragAxis === "x" && axis === "x" && !isMultitouch) {
  982. const currSlideMainScrollX = mainScroll.getCurrSlideX();
  983. const mainScrollShiftDiff = mainScroll.x - currSlideMainScrollX;
  984. const isLeftToRight = delta > 0;
  985. const isRightToLeft = !isLeftToRight;
  986. if (newPan > bounds.min[axis] && isLeftToRight) {
  987. const wasAtMinPanPosition = bounds.min[axis] <= this.startPan[axis];
  988. if (wasAtMinPanPosition) {
  989. mainScroll.moveTo(newMainScrollX, true);
  990. return true;
  991. } else {
  992. this._setPanWithFriction(axis, newPan);
  993. }
  994. } else if (newPan < bounds.max[axis] && isRightToLeft) {
  995. const wasAtMaxPanPosition = this.startPan[axis] <= bounds.max[axis];
  996. if (wasAtMaxPanPosition) {
  997. mainScroll.moveTo(newMainScrollX, true);
  998. return true;
  999. } else {
  1000. this._setPanWithFriction(axis, newPan);
  1001. }
  1002. } else {
  1003. if (mainScrollShiftDiff !== 0) {
  1004. if (mainScrollShiftDiff > 0) {
  1005. mainScroll.moveTo(Math.max(newMainScrollX, currSlideMainScrollX), true);
  1006. return true;
  1007. } else if (mainScrollShiftDiff < 0) {
  1008. mainScroll.moveTo(Math.min(newMainScrollX, currSlideMainScrollX), true);
  1009. return true;
  1010. }
  1011. } else {
  1012. this._setPanWithFriction(axis, newPan);
  1013. }
  1014. }
  1015. } else {
  1016. if (axis === "y") {
  1017. if (!mainScroll.isShifted() && bounds.min.y !== bounds.max.y) {
  1018. this._setPanWithFriction(axis, newPan);
  1019. }
  1020. } else {
  1021. this._setPanWithFriction(axis, newPan);
  1022. }
  1023. }
  1024. return false;
  1025. }
  1026. // If we move above - the ratio is negative
  1027. // If we move below the ratio is positive
  1028. /**
  1029. * Relation between pan Y position and third of viewport height.
  1030. *
  1031. * When we are at initial position (center bounds) - the ratio is 0,
  1032. * if position is shifted upwards - the ratio is negative,
  1033. * if position is shifted downwards - the ratio is positive.
  1034. *
  1035. * @private
  1036. * @param {number} panY The current pan Y position.
  1037. * @returns {number}
  1038. */
  1039. _getVerticalDragRatio(panY) {
  1040. var _this$pswp$currSlide$, _this$pswp$currSlide;
  1041. return (panY - ((_this$pswp$currSlide$ = (_this$pswp$currSlide = this.pswp.currSlide) === null || _this$pswp$currSlide === void 0 ? void 0 : _this$pswp$currSlide.bounds.center.y) !== null && _this$pswp$currSlide$ !== void 0 ? _this$pswp$currSlide$ : 0)) / (this.pswp.viewportSize.y / 3);
  1042. }
  1043. /**
  1044. * Set pan position of the current slide.
  1045. * Apply friction if the position is beyond the pan bounds,
  1046. * or if custom friction is defined.
  1047. *
  1048. * @private
  1049. * @param {'x' | 'y'} axis
  1050. * @param {number} potentialPan
  1051. * @param {number} [customFriction] (0.1 - 1)
  1052. */
  1053. _setPanWithFriction(axis, potentialPan, customFriction) {
  1054. const {
  1055. currSlide
  1056. } = this.pswp;
  1057. if (!currSlide) {
  1058. return;
  1059. }
  1060. const {
  1061. pan,
  1062. bounds
  1063. } = currSlide;
  1064. const correctedPan = bounds.correctPan(axis, potentialPan);
  1065. if (correctedPan !== potentialPan || customFriction) {
  1066. const delta = Math.round(potentialPan - pan[axis]);
  1067. pan[axis] += delta * (customFriction || PAN_END_FRICTION);
  1068. } else {
  1069. pan[axis] = potentialPan;
  1070. }
  1071. }
  1072. };
  1073. var UPPER_ZOOM_FRICTION = 0.05;
  1074. var LOWER_ZOOM_FRICTION = 0.15;
  1075. function getZoomPointsCenter(p, p1, p2) {
  1076. p.x = (p1.x + p2.x) / 2;
  1077. p.y = (p1.y + p2.y) / 2;
  1078. return p;
  1079. }
  1080. var ZoomHandler = class {
  1081. /**
  1082. * @param {Gestures} gestures
  1083. */
  1084. constructor(gestures) {
  1085. this.gestures = gestures;
  1086. this._startPan = {
  1087. x: 0,
  1088. y: 0
  1089. };
  1090. this._startZoomPoint = {
  1091. x: 0,
  1092. y: 0
  1093. };
  1094. this._zoomPoint = {
  1095. x: 0,
  1096. y: 0
  1097. };
  1098. this._wasOverFitZoomLevel = false;
  1099. this._startZoomLevel = 1;
  1100. }
  1101. start() {
  1102. const {
  1103. currSlide
  1104. } = this.gestures.pswp;
  1105. if (currSlide) {
  1106. this._startZoomLevel = currSlide.currZoomLevel;
  1107. equalizePoints(this._startPan, currSlide.pan);
  1108. }
  1109. this.gestures.pswp.animations.stopAllPan();
  1110. this._wasOverFitZoomLevel = false;
  1111. }
  1112. change() {
  1113. const {
  1114. p1,
  1115. startP1,
  1116. p2,
  1117. startP2,
  1118. pswp
  1119. } = this.gestures;
  1120. const {
  1121. currSlide
  1122. } = pswp;
  1123. if (!currSlide) {
  1124. return;
  1125. }
  1126. const minZoomLevel = currSlide.zoomLevels.min;
  1127. const maxZoomLevel = currSlide.zoomLevels.max;
  1128. if (!currSlide.isZoomable() || pswp.mainScroll.isShifted()) {
  1129. return;
  1130. }
  1131. getZoomPointsCenter(this._startZoomPoint, startP1, startP2);
  1132. getZoomPointsCenter(this._zoomPoint, p1, p2);
  1133. let currZoomLevel = 1 / getDistanceBetween(startP1, startP2) * getDistanceBetween(p1, p2) * this._startZoomLevel;
  1134. if (currZoomLevel > currSlide.zoomLevels.initial + currSlide.zoomLevels.initial / 15) {
  1135. this._wasOverFitZoomLevel = true;
  1136. }
  1137. if (currZoomLevel < minZoomLevel) {
  1138. if (pswp.options.pinchToClose && !this._wasOverFitZoomLevel && this._startZoomLevel <= currSlide.zoomLevels.initial) {
  1139. const bgOpacity = 1 - (minZoomLevel - currZoomLevel) / (minZoomLevel / 1.2);
  1140. if (!pswp.dispatch("pinchClose", {
  1141. bgOpacity
  1142. }).defaultPrevented) {
  1143. pswp.applyBgOpacity(bgOpacity);
  1144. }
  1145. } else {
  1146. currZoomLevel = minZoomLevel - (minZoomLevel - currZoomLevel) * LOWER_ZOOM_FRICTION;
  1147. }
  1148. } else if (currZoomLevel > maxZoomLevel) {
  1149. currZoomLevel = maxZoomLevel + (currZoomLevel - maxZoomLevel) * UPPER_ZOOM_FRICTION;
  1150. }
  1151. currSlide.pan.x = this._calculatePanForZoomLevel("x", currZoomLevel);
  1152. currSlide.pan.y = this._calculatePanForZoomLevel("y", currZoomLevel);
  1153. currSlide.setZoomLevel(currZoomLevel);
  1154. currSlide.applyCurrentZoomPan();
  1155. }
  1156. end() {
  1157. const {
  1158. pswp
  1159. } = this.gestures;
  1160. const {
  1161. currSlide
  1162. } = pswp;
  1163. if ((!currSlide || currSlide.currZoomLevel < currSlide.zoomLevels.initial) && !this._wasOverFitZoomLevel && pswp.options.pinchToClose) {
  1164. pswp.close();
  1165. } else {
  1166. this.correctZoomPan();
  1167. }
  1168. }
  1169. /**
  1170. * @private
  1171. * @param {'x' | 'y'} axis
  1172. * @param {number} currZoomLevel
  1173. * @returns {number}
  1174. */
  1175. _calculatePanForZoomLevel(axis, currZoomLevel) {
  1176. const zoomFactor = currZoomLevel / this._startZoomLevel;
  1177. return this._zoomPoint[axis] - (this._startZoomPoint[axis] - this._startPan[axis]) * zoomFactor;
  1178. }
  1179. /**
  1180. * Correct currZoomLevel and pan if they are
  1181. * beyond minimum or maximum values.
  1182. * With animation.
  1183. *
  1184. * @param {boolean} [ignoreGesture]
  1185. * Wether gesture coordinates should be ignored when calculating destination pan position.
  1186. */
  1187. correctZoomPan(ignoreGesture) {
  1188. const {
  1189. pswp
  1190. } = this.gestures;
  1191. const {
  1192. currSlide
  1193. } = pswp;
  1194. if (!(currSlide !== null && currSlide !== void 0 && currSlide.isZoomable())) {
  1195. return;
  1196. }
  1197. if (this._zoomPoint.x === 0) {
  1198. ignoreGesture = true;
  1199. }
  1200. const prevZoomLevel = currSlide.currZoomLevel;
  1201. let destinationZoomLevel;
  1202. let currZoomLevelNeedsChange = true;
  1203. if (prevZoomLevel < currSlide.zoomLevels.initial) {
  1204. destinationZoomLevel = currSlide.zoomLevels.initial;
  1205. } else if (prevZoomLevel > currSlide.zoomLevels.max) {
  1206. destinationZoomLevel = currSlide.zoomLevels.max;
  1207. } else {
  1208. currZoomLevelNeedsChange = false;
  1209. destinationZoomLevel = prevZoomLevel;
  1210. }
  1211. const initialBgOpacity = pswp.bgOpacity;
  1212. const restoreBgOpacity = pswp.bgOpacity < 1;
  1213. const initialPan = equalizePoints({
  1214. x: 0,
  1215. y: 0
  1216. }, currSlide.pan);
  1217. let destinationPan = equalizePoints({
  1218. x: 0,
  1219. y: 0
  1220. }, initialPan);
  1221. if (ignoreGesture) {
  1222. this._zoomPoint.x = 0;
  1223. this._zoomPoint.y = 0;
  1224. this._startZoomPoint.x = 0;
  1225. this._startZoomPoint.y = 0;
  1226. this._startZoomLevel = prevZoomLevel;
  1227. equalizePoints(this._startPan, initialPan);
  1228. }
  1229. if (currZoomLevelNeedsChange) {
  1230. destinationPan = {
  1231. x: this._calculatePanForZoomLevel("x", destinationZoomLevel),
  1232. y: this._calculatePanForZoomLevel("y", destinationZoomLevel)
  1233. };
  1234. }
  1235. currSlide.setZoomLevel(destinationZoomLevel);
  1236. destinationPan = {
  1237. x: currSlide.bounds.correctPan("x", destinationPan.x),
  1238. y: currSlide.bounds.correctPan("y", destinationPan.y)
  1239. };
  1240. currSlide.setZoomLevel(prevZoomLevel);
  1241. const panNeedsChange = !pointsEqual(destinationPan, initialPan);
  1242. if (!panNeedsChange && !currZoomLevelNeedsChange && !restoreBgOpacity) {
  1243. currSlide._setResolution(destinationZoomLevel);
  1244. currSlide.applyCurrentZoomPan();
  1245. return;
  1246. }
  1247. pswp.animations.stopAllPan();
  1248. pswp.animations.startSpring({
  1249. isPan: true,
  1250. start: 0,
  1251. end: 1e3,
  1252. velocity: 0,
  1253. dampingRatio: 1,
  1254. naturalFrequency: 40,
  1255. onUpdate: (now) => {
  1256. now /= 1e3;
  1257. if (panNeedsChange || currZoomLevelNeedsChange) {
  1258. if (panNeedsChange) {
  1259. currSlide.pan.x = initialPan.x + (destinationPan.x - initialPan.x) * now;
  1260. currSlide.pan.y = initialPan.y + (destinationPan.y - initialPan.y) * now;
  1261. }
  1262. if (currZoomLevelNeedsChange) {
  1263. const newZoomLevel = prevZoomLevel + (destinationZoomLevel - prevZoomLevel) * now;
  1264. currSlide.setZoomLevel(newZoomLevel);
  1265. }
  1266. currSlide.applyCurrentZoomPan();
  1267. }
  1268. if (restoreBgOpacity && pswp.bgOpacity < 1) {
  1269. pswp.applyBgOpacity(clamp(initialBgOpacity + (1 - initialBgOpacity) * now, 0, 1));
  1270. }
  1271. },
  1272. onComplete: () => {
  1273. currSlide._setResolution(destinationZoomLevel);
  1274. currSlide.applyCurrentZoomPan();
  1275. }
  1276. });
  1277. }
  1278. };
  1279. function didTapOnMainContent(event) {
  1280. return !!/** @type {HTMLElement} */
  1281. event.target.closest(".pswp__container");
  1282. }
  1283. var TapHandler = class {
  1284. /**
  1285. * @param {Gestures} gestures
  1286. */
  1287. constructor(gestures) {
  1288. this.gestures = gestures;
  1289. }
  1290. /**
  1291. * @param {Point} point
  1292. * @param {PointerEvent} originalEvent
  1293. */
  1294. click(point, originalEvent) {
  1295. const targetClassList = (
  1296. /** @type {HTMLElement} */
  1297. originalEvent.target.classList
  1298. );
  1299. const isImageClick = targetClassList.contains("pswp__img");
  1300. const isBackgroundClick = targetClassList.contains("pswp__item") || targetClassList.contains("pswp__zoom-wrap");
  1301. if (isImageClick) {
  1302. this._doClickOrTapAction("imageClick", point, originalEvent);
  1303. } else if (isBackgroundClick) {
  1304. this._doClickOrTapAction("bgClick", point, originalEvent);
  1305. }
  1306. }
  1307. /**
  1308. * @param {Point} point
  1309. * @param {PointerEvent} originalEvent
  1310. */
  1311. tap(point, originalEvent) {
  1312. if (didTapOnMainContent(originalEvent)) {
  1313. this._doClickOrTapAction("tap", point, originalEvent);
  1314. }
  1315. }
  1316. /**
  1317. * @param {Point} point
  1318. * @param {PointerEvent} originalEvent
  1319. */
  1320. doubleTap(point, originalEvent) {
  1321. if (didTapOnMainContent(originalEvent)) {
  1322. this._doClickOrTapAction("doubleTap", point, originalEvent);
  1323. }
  1324. }
  1325. /**
  1326. * @private
  1327. * @param {Actions} actionName
  1328. * @param {Point} point
  1329. * @param {PointerEvent} originalEvent
  1330. */
  1331. _doClickOrTapAction(actionName, point, originalEvent) {
  1332. var _this$gestures$pswp$e;
  1333. const {
  1334. pswp
  1335. } = this.gestures;
  1336. const {
  1337. currSlide
  1338. } = pswp;
  1339. const actionFullName = (
  1340. /** @type {AddPostfix<Actions, 'Action'>} */
  1341. actionName + "Action"
  1342. );
  1343. const optionValue = pswp.options[actionFullName];
  1344. if (pswp.dispatch(actionFullName, {
  1345. point,
  1346. originalEvent
  1347. }).defaultPrevented) {
  1348. return;
  1349. }
  1350. if (typeof optionValue === "function") {
  1351. optionValue.call(pswp, point, originalEvent);
  1352. return;
  1353. }
  1354. switch (optionValue) {
  1355. case "close":
  1356. case "next":
  1357. pswp[optionValue]();
  1358. break;
  1359. case "zoom":
  1360. currSlide === null || currSlide === void 0 || currSlide.toggleZoom(point);
  1361. break;
  1362. case "zoom-or-close":
  1363. if (currSlide !== null && currSlide !== void 0 && currSlide.isZoomable() && currSlide.zoomLevels.secondary !== currSlide.zoomLevels.initial) {
  1364. currSlide.toggleZoom(point);
  1365. } else if (pswp.options.clickToCloseNonZoomable) {
  1366. pswp.close();
  1367. }
  1368. break;
  1369. case "toggle-controls":
  1370. (_this$gestures$pswp$e = this.gestures.pswp.element) === null || _this$gestures$pswp$e === void 0 || _this$gestures$pswp$e.classList.toggle("pswp--ui-visible");
  1371. break;
  1372. }
  1373. }
  1374. };
  1375. var AXIS_SWIPE_HYSTERISIS = 10;
  1376. var DOUBLE_TAP_DELAY = 300;
  1377. var MIN_TAP_DISTANCE = 25;
  1378. var Gestures = class {
  1379. /**
  1380. * @param {PhotoSwipe} pswp
  1381. */
  1382. constructor(pswp) {
  1383. this.pswp = pswp;
  1384. this.dragAxis = null;
  1385. this.p1 = {
  1386. x: 0,
  1387. y: 0
  1388. };
  1389. this.p2 = {
  1390. x: 0,
  1391. y: 0
  1392. };
  1393. this.prevP1 = {
  1394. x: 0,
  1395. y: 0
  1396. };
  1397. this.prevP2 = {
  1398. x: 0,
  1399. y: 0
  1400. };
  1401. this.startP1 = {
  1402. x: 0,
  1403. y: 0
  1404. };
  1405. this.startP2 = {
  1406. x: 0,
  1407. y: 0
  1408. };
  1409. this.velocity = {
  1410. x: 0,
  1411. y: 0
  1412. };
  1413. this._lastStartP1 = {
  1414. x: 0,
  1415. y: 0
  1416. };
  1417. this._intervalP1 = {
  1418. x: 0,
  1419. y: 0
  1420. };
  1421. this._numActivePoints = 0;
  1422. this._ongoingPointers = [];
  1423. this._touchEventEnabled = "ontouchstart" in window;
  1424. this._pointerEventEnabled = !!window.PointerEvent;
  1425. this.supportsTouch = this._touchEventEnabled || this._pointerEventEnabled && navigator.maxTouchPoints > 1;
  1426. this._numActivePoints = 0;
  1427. this._intervalTime = 0;
  1428. this._velocityCalculated = false;
  1429. this.isMultitouch = false;
  1430. this.isDragging = false;
  1431. this.isZooming = false;
  1432. this.raf = null;
  1433. this._tapTimer = null;
  1434. if (!this.supportsTouch) {
  1435. pswp.options.allowPanToNext = false;
  1436. }
  1437. this.drag = new DragHandler(this);
  1438. this.zoomLevels = new ZoomHandler(this);
  1439. this.tapHandler = new TapHandler(this);
  1440. pswp.on("bindEvents", () => {
  1441. pswp.events.add(
  1442. pswp.scrollWrap,
  1443. "click",
  1444. /** @type EventListener */
  1445. this._onClick.bind(this)
  1446. );
  1447. if (this._pointerEventEnabled) {
  1448. this._bindEvents("pointer", "down", "up", "cancel");
  1449. } else if (this._touchEventEnabled) {
  1450. this._bindEvents("touch", "start", "end", "cancel");
  1451. if (pswp.scrollWrap) {
  1452. pswp.scrollWrap.ontouchmove = () => {
  1453. };
  1454. pswp.scrollWrap.ontouchend = () => {
  1455. };
  1456. }
  1457. } else {
  1458. this._bindEvents("mouse", "down", "up");
  1459. }
  1460. });
  1461. }
  1462. /**
  1463. * @private
  1464. * @param {'mouse' | 'touch' | 'pointer'} pref
  1465. * @param {'down' | 'start'} down
  1466. * @param {'up' | 'end'} up
  1467. * @param {'cancel'} [cancel]
  1468. */
  1469. _bindEvents(pref, down, up, cancel) {
  1470. const {
  1471. pswp
  1472. } = this;
  1473. const {
  1474. events
  1475. } = pswp;
  1476. const cancelEvent = cancel ? pref + cancel : "";
  1477. events.add(
  1478. pswp.scrollWrap,
  1479. pref + down,
  1480. /** @type EventListener */
  1481. this.onPointerDown.bind(this)
  1482. );
  1483. events.add(
  1484. window,
  1485. pref + "move",
  1486. /** @type EventListener */
  1487. this.onPointerMove.bind(this)
  1488. );
  1489. events.add(
  1490. window,
  1491. pref + up,
  1492. /** @type EventListener */
  1493. this.onPointerUp.bind(this)
  1494. );
  1495. if (cancelEvent) {
  1496. events.add(
  1497. pswp.scrollWrap,
  1498. cancelEvent,
  1499. /** @type EventListener */
  1500. this.onPointerUp.bind(this)
  1501. );
  1502. }
  1503. }
  1504. /**
  1505. * @param {PointerEvent} e
  1506. */
  1507. onPointerDown(e) {
  1508. const isMousePointer = e.type === "mousedown" || e.pointerType === "mouse";
  1509. if (isMousePointer && e.button > 0) {
  1510. return;
  1511. }
  1512. const {
  1513. pswp
  1514. } = this;
  1515. if (!pswp.opener.isOpen) {
  1516. e.preventDefault();
  1517. return;
  1518. }
  1519. if (pswp.dispatch("pointerDown", {
  1520. originalEvent: e
  1521. }).defaultPrevented) {
  1522. return;
  1523. }
  1524. if (isMousePointer) {
  1525. pswp.mouseDetected();
  1526. this._preventPointerEventBehaviour(e, "down");
  1527. }
  1528. pswp.animations.stopAll();
  1529. this._updatePoints(e, "down");
  1530. if (this._numActivePoints === 1) {
  1531. this.dragAxis = null;
  1532. equalizePoints(this.startP1, this.p1);
  1533. }
  1534. if (this._numActivePoints > 1) {
  1535. this._clearTapTimer();
  1536. this.isMultitouch = true;
  1537. } else {
  1538. this.isMultitouch = false;
  1539. }
  1540. }
  1541. /**
  1542. * @param {PointerEvent} e
  1543. */
  1544. onPointerMove(e) {
  1545. this._preventPointerEventBehaviour(e, "move");
  1546. if (!this._numActivePoints) {
  1547. return;
  1548. }
  1549. this._updatePoints(e, "move");
  1550. if (this.pswp.dispatch("pointerMove", {
  1551. originalEvent: e
  1552. }).defaultPrevented) {
  1553. return;
  1554. }
  1555. if (this._numActivePoints === 1 && !this.isDragging) {
  1556. if (!this.dragAxis) {
  1557. this._calculateDragDirection();
  1558. }
  1559. if (this.dragAxis && !this.isDragging) {
  1560. if (this.isZooming) {
  1561. this.isZooming = false;
  1562. this.zoomLevels.end();
  1563. }
  1564. this.isDragging = true;
  1565. this._clearTapTimer();
  1566. this._updateStartPoints();
  1567. this._intervalTime = Date.now();
  1568. this._velocityCalculated = false;
  1569. equalizePoints(this._intervalP1, this.p1);
  1570. this.velocity.x = 0;
  1571. this.velocity.y = 0;
  1572. this.drag.start();
  1573. this._rafStopLoop();
  1574. this._rafRenderLoop();
  1575. }
  1576. } else if (this._numActivePoints > 1 && !this.isZooming) {
  1577. this._finishDrag();
  1578. this.isZooming = true;
  1579. this._updateStartPoints();
  1580. this.zoomLevels.start();
  1581. this._rafStopLoop();
  1582. this._rafRenderLoop();
  1583. }
  1584. }
  1585. /**
  1586. * @private
  1587. */
  1588. _finishDrag() {
  1589. if (this.isDragging) {
  1590. this.isDragging = false;
  1591. if (!this._velocityCalculated) {
  1592. this._updateVelocity(true);
  1593. }
  1594. this.drag.end();
  1595. this.dragAxis = null;
  1596. }
  1597. }
  1598. /**
  1599. * @param {PointerEvent} e
  1600. */
  1601. onPointerUp(e) {
  1602. if (!this._numActivePoints) {
  1603. return;
  1604. }
  1605. this._updatePoints(e, "up");
  1606. if (this.pswp.dispatch("pointerUp", {
  1607. originalEvent: e
  1608. }).defaultPrevented) {
  1609. return;
  1610. }
  1611. if (this._numActivePoints === 0) {
  1612. this._rafStopLoop();
  1613. if (this.isDragging) {
  1614. this._finishDrag();
  1615. } else if (!this.isZooming && !this.isMultitouch) {
  1616. this._finishTap(e);
  1617. }
  1618. }
  1619. if (this._numActivePoints < 2 && this.isZooming) {
  1620. this.isZooming = false;
  1621. this.zoomLevels.end();
  1622. if (this._numActivePoints === 1) {
  1623. this.dragAxis = null;
  1624. this._updateStartPoints();
  1625. }
  1626. }
  1627. }
  1628. /**
  1629. * @private
  1630. */
  1631. _rafRenderLoop() {
  1632. if (this.isDragging || this.isZooming) {
  1633. this._updateVelocity();
  1634. if (this.isDragging) {
  1635. if (!pointsEqual(this.p1, this.prevP1)) {
  1636. this.drag.change();
  1637. }
  1638. } else {
  1639. if (!pointsEqual(this.p1, this.prevP1) || !pointsEqual(this.p2, this.prevP2)) {
  1640. this.zoomLevels.change();
  1641. }
  1642. }
  1643. this._updatePrevPoints();
  1644. this.raf = requestAnimationFrame(this._rafRenderLoop.bind(this));
  1645. }
  1646. }
  1647. /**
  1648. * Update velocity at 50ms interval
  1649. *
  1650. * @private
  1651. * @param {boolean} [force]
  1652. */
  1653. _updateVelocity(force) {
  1654. const time = Date.now();
  1655. const duration = time - this._intervalTime;
  1656. if (duration < 50 && !force) {
  1657. return;
  1658. }
  1659. this.velocity.x = this._getVelocity("x", duration);
  1660. this.velocity.y = this._getVelocity("y", duration);
  1661. this._intervalTime = time;
  1662. equalizePoints(this._intervalP1, this.p1);
  1663. this._velocityCalculated = true;
  1664. }
  1665. /**
  1666. * @private
  1667. * @param {PointerEvent} e
  1668. */
  1669. _finishTap(e) {
  1670. const {
  1671. mainScroll
  1672. } = this.pswp;
  1673. if (mainScroll.isShifted()) {
  1674. mainScroll.moveIndexBy(0, true);
  1675. return;
  1676. }
  1677. if (e.type.indexOf("cancel") > 0) {
  1678. return;
  1679. }
  1680. if (e.type === "mouseup" || e.pointerType === "mouse") {
  1681. this.tapHandler.click(this.startP1, e);
  1682. return;
  1683. }
  1684. const tapDelay = this.pswp.options.doubleTapAction ? DOUBLE_TAP_DELAY : 0;
  1685. if (this._tapTimer) {
  1686. this._clearTapTimer();
  1687. if (getDistanceBetween(this._lastStartP1, this.startP1) < MIN_TAP_DISTANCE) {
  1688. this.tapHandler.doubleTap(this.startP1, e);
  1689. }
  1690. } else {
  1691. equalizePoints(this._lastStartP1, this.startP1);
  1692. this._tapTimer = setTimeout(() => {
  1693. this.tapHandler.tap(this.startP1, e);
  1694. this._clearTapTimer();
  1695. }, tapDelay);
  1696. }
  1697. }
  1698. /**
  1699. * @private
  1700. */
  1701. _clearTapTimer() {
  1702. if (this._tapTimer) {
  1703. clearTimeout(this._tapTimer);
  1704. this._tapTimer = null;
  1705. }
  1706. }
  1707. /**
  1708. * Get velocity for axis
  1709. *
  1710. * @private
  1711. * @param {'x' | 'y'} axis
  1712. * @param {number} duration
  1713. * @returns {number}
  1714. */
  1715. _getVelocity(axis, duration) {
  1716. const displacement = this.p1[axis] - this._intervalP1[axis];
  1717. if (Math.abs(displacement) > 1 && duration > 5) {
  1718. return displacement / duration;
  1719. }
  1720. return 0;
  1721. }
  1722. /**
  1723. * @private
  1724. */
  1725. _rafStopLoop() {
  1726. if (this.raf) {
  1727. cancelAnimationFrame(this.raf);
  1728. this.raf = null;
  1729. }
  1730. }
  1731. /**
  1732. * @private
  1733. * @param {PointerEvent} e
  1734. * @param {'up' | 'down' | 'move'} pointerType Normalized pointer type
  1735. */
  1736. _preventPointerEventBehaviour(e, pointerType) {
  1737. const preventPointerEvent = this.pswp.applyFilters("preventPointerEvent", true, e, pointerType);
  1738. if (preventPointerEvent) {
  1739. e.preventDefault();
  1740. }
  1741. }
  1742. /**
  1743. * Parses and normalizes points from the touch, mouse or pointer event.
  1744. * Updates p1 and p2.
  1745. *
  1746. * @private
  1747. * @param {PointerEvent | TouchEvent} e
  1748. * @param {'up' | 'down' | 'move'} pointerType Normalized pointer type
  1749. */
  1750. _updatePoints(e, pointerType) {
  1751. if (this._pointerEventEnabled) {
  1752. const pointerEvent = (
  1753. /** @type {PointerEvent} */
  1754. e
  1755. );
  1756. const pointerIndex = this._ongoingPointers.findIndex((ongoingPointer) => {
  1757. return ongoingPointer.id === pointerEvent.pointerId;
  1758. });
  1759. if (pointerType === "up" && pointerIndex > -1) {
  1760. this._ongoingPointers.splice(pointerIndex, 1);
  1761. } else if (pointerType === "down" && pointerIndex === -1) {
  1762. this._ongoingPointers.push(this._convertEventPosToPoint(pointerEvent, {
  1763. x: 0,
  1764. y: 0
  1765. }));
  1766. } else if (pointerIndex > -1) {
  1767. this._convertEventPosToPoint(pointerEvent, this._ongoingPointers[pointerIndex]);
  1768. }
  1769. this._numActivePoints = this._ongoingPointers.length;
  1770. if (this._numActivePoints > 0) {
  1771. equalizePoints(this.p1, this._ongoingPointers[0]);
  1772. }
  1773. if (this._numActivePoints > 1) {
  1774. equalizePoints(this.p2, this._ongoingPointers[1]);
  1775. }
  1776. } else {
  1777. const touchEvent = (
  1778. /** @type {TouchEvent} */
  1779. e
  1780. );
  1781. this._numActivePoints = 0;
  1782. if (touchEvent.type.indexOf("touch") > -1) {
  1783. if (touchEvent.touches && touchEvent.touches.length > 0) {
  1784. this._convertEventPosToPoint(touchEvent.touches[0], this.p1);
  1785. this._numActivePoints++;
  1786. if (touchEvent.touches.length > 1) {
  1787. this._convertEventPosToPoint(touchEvent.touches[1], this.p2);
  1788. this._numActivePoints++;
  1789. }
  1790. }
  1791. } else {
  1792. this._convertEventPosToPoint(
  1793. /** @type {PointerEvent} */
  1794. e,
  1795. this.p1
  1796. );
  1797. if (pointerType === "up") {
  1798. this._numActivePoints = 0;
  1799. } else {
  1800. this._numActivePoints++;
  1801. }
  1802. }
  1803. }
  1804. }
  1805. /** update points that were used during previous rAF tick
  1806. * @private
  1807. */
  1808. _updatePrevPoints() {
  1809. equalizePoints(this.prevP1, this.p1);
  1810. equalizePoints(this.prevP2, this.p2);
  1811. }
  1812. /** update points at the start of gesture
  1813. * @private
  1814. */
  1815. _updateStartPoints() {
  1816. equalizePoints(this.startP1, this.p1);
  1817. equalizePoints(this.startP2, this.p2);
  1818. this._updatePrevPoints();
  1819. }
  1820. /** @private */
  1821. _calculateDragDirection() {
  1822. if (this.pswp.mainScroll.isShifted()) {
  1823. this.dragAxis = "x";
  1824. } else {
  1825. const diff = Math.abs(this.p1.x - this.startP1.x) - Math.abs(this.p1.y - this.startP1.y);
  1826. if (diff !== 0) {
  1827. const axisToCheck = diff > 0 ? "x" : "y";
  1828. if (Math.abs(this.p1[axisToCheck] - this.startP1[axisToCheck]) >= AXIS_SWIPE_HYSTERISIS) {
  1829. this.dragAxis = axisToCheck;
  1830. }
  1831. }
  1832. }
  1833. }
  1834. /**
  1835. * Converts touch, pointer or mouse event
  1836. * to PhotoSwipe point.
  1837. *
  1838. * @private
  1839. * @param {Touch | PointerEvent} e
  1840. * @param {Point} p
  1841. * @returns {Point}
  1842. */
  1843. _convertEventPosToPoint(e, p) {
  1844. p.x = e.pageX - this.pswp.offset.x;
  1845. p.y = e.pageY - this.pswp.offset.y;
  1846. if ("pointerId" in e) {
  1847. p.id = e.pointerId;
  1848. } else if (e.identifier !== void 0) {
  1849. p.id = e.identifier;
  1850. }
  1851. return p;
  1852. }
  1853. /**
  1854. * @private
  1855. * @param {PointerEvent} e
  1856. */
  1857. _onClick(e) {
  1858. if (this.pswp.mainScroll.isShifted()) {
  1859. e.preventDefault();
  1860. e.stopPropagation();
  1861. }
  1862. }
  1863. };
  1864. var MAIN_SCROLL_END_FRICTION = 0.35;
  1865. var MainScroll = class {
  1866. /**
  1867. * @param {PhotoSwipe} pswp
  1868. */
  1869. constructor(pswp) {
  1870. this.pswp = pswp;
  1871. this.x = 0;
  1872. this.slideWidth = 0;
  1873. this._currPositionIndex = 0;
  1874. this._prevPositionIndex = 0;
  1875. this._containerShiftIndex = -1;
  1876. this.itemHolders = [];
  1877. }
  1878. /**
  1879. * Position the scroller and slide containers
  1880. * according to viewport size.
  1881. *
  1882. * @param {boolean} [resizeSlides] Whether slides content should resized
  1883. */
  1884. resize(resizeSlides) {
  1885. const {
  1886. pswp
  1887. } = this;
  1888. const newSlideWidth = Math.round(pswp.viewportSize.x + pswp.viewportSize.x * pswp.options.spacing);
  1889. const slideWidthChanged = newSlideWidth !== this.slideWidth;
  1890. if (slideWidthChanged) {
  1891. this.slideWidth = newSlideWidth;
  1892. this.moveTo(this.getCurrSlideX());
  1893. }
  1894. this.itemHolders.forEach((itemHolder, index) => {
  1895. if (slideWidthChanged) {
  1896. setTransform(itemHolder.el, (index + this._containerShiftIndex) * this.slideWidth);
  1897. }
  1898. if (resizeSlides && itemHolder.slide) {
  1899. itemHolder.slide.resize();
  1900. }
  1901. });
  1902. }
  1903. /**
  1904. * Reset X position of the main scroller to zero
  1905. */
  1906. resetPosition() {
  1907. this._currPositionIndex = 0;
  1908. this._prevPositionIndex = 0;
  1909. this.slideWidth = 0;
  1910. this._containerShiftIndex = -1;
  1911. }
  1912. /**
  1913. * Create and append array of three items
  1914. * that hold data about slides in DOM
  1915. */
  1916. appendHolders() {
  1917. this.itemHolders = [];
  1918. for (let i = 0; i < 3; i++) {
  1919. const el = createElement("pswp__item", "div", this.pswp.container);
  1920. el.setAttribute("role", "group");
  1921. el.setAttribute("aria-roledescription", "slide");
  1922. el.setAttribute("aria-hidden", "true");
  1923. el.style.display = i === 1 ? "block" : "none";
  1924. this.itemHolders.push({
  1925. el
  1926. //index: -1
  1927. });
  1928. }
  1929. }
  1930. /**
  1931. * Whether the main scroll can be horizontally swiped to the next or previous slide.
  1932. * @returns {boolean}
  1933. */
  1934. canBeSwiped() {
  1935. return this.pswp.getNumItems() > 1;
  1936. }
  1937. /**
  1938. * Move main scroll by X amount of slides.
  1939. * For example:
  1940. * `-1` will move to the previous slide,
  1941. * `0` will reset the scroll position of the current slide,
  1942. * `3` will move three slides forward
  1943. *
  1944. * If loop option is enabled - index will be automatically looped too,
  1945. * (for example `-1` will move to the last slide of the gallery).
  1946. *
  1947. * @param {number} diff
  1948. * @param {boolean} [animate]
  1949. * @param {number} [velocityX]
  1950. * @returns {boolean} whether index was changed or not
  1951. */
  1952. moveIndexBy(diff, animate, velocityX) {
  1953. const {
  1954. pswp
  1955. } = this;
  1956. let newIndex = pswp.potentialIndex + diff;
  1957. const numSlides = pswp.getNumItems();
  1958. if (pswp.canLoop()) {
  1959. newIndex = pswp.getLoopedIndex(newIndex);
  1960. const distance = (diff + numSlides) % numSlides;
  1961. if (distance <= numSlides / 2) {
  1962. diff = distance;
  1963. } else {
  1964. diff = distance - numSlides;
  1965. }
  1966. } else {
  1967. if (newIndex < 0) {
  1968. newIndex = 0;
  1969. } else if (newIndex >= numSlides) {
  1970. newIndex = numSlides - 1;
  1971. }
  1972. diff = newIndex - pswp.potentialIndex;
  1973. }
  1974. pswp.potentialIndex = newIndex;
  1975. this._currPositionIndex -= diff;
  1976. pswp.animations.stopMainScroll();
  1977. const destinationX = this.getCurrSlideX();
  1978. if (!animate) {
  1979. this.moveTo(destinationX);
  1980. this.updateCurrItem();
  1981. } else {
  1982. pswp.animations.startSpring({
  1983. isMainScroll: true,
  1984. start: this.x,
  1985. end: destinationX,
  1986. velocity: velocityX || 0,
  1987. naturalFrequency: 30,
  1988. dampingRatio: 1,
  1989. //0.7,
  1990. onUpdate: (x) => {
  1991. this.moveTo(x);
  1992. },
  1993. onComplete: () => {
  1994. this.updateCurrItem();
  1995. pswp.appendHeavy();
  1996. }
  1997. });
  1998. let currDiff = pswp.potentialIndex - pswp.currIndex;
  1999. if (pswp.canLoop()) {
  2000. const currDistance = (currDiff + numSlides) % numSlides;
  2001. if (currDistance <= numSlides / 2) {
  2002. currDiff = currDistance;
  2003. } else {
  2004. currDiff = currDistance - numSlides;
  2005. }
  2006. }
  2007. if (Math.abs(currDiff) > 1) {
  2008. this.updateCurrItem();
  2009. }
  2010. }
  2011. return Boolean(diff);
  2012. }
  2013. /**
  2014. * X position of the main scroll for the current slide
  2015. * (ignores position during dragging)
  2016. * @returns {number}
  2017. */
  2018. getCurrSlideX() {
  2019. return this.slideWidth * this._currPositionIndex;
  2020. }
  2021. /**
  2022. * Whether scroll position is shifted.
  2023. * For example, it will return true if the scroll is being dragged or animated.
  2024. * @returns {boolean}
  2025. */
  2026. isShifted() {
  2027. return this.x !== this.getCurrSlideX();
  2028. }
  2029. /**
  2030. * Update slides X positions and set their content
  2031. */
  2032. updateCurrItem() {
  2033. var _this$itemHolders$;
  2034. const {
  2035. pswp
  2036. } = this;
  2037. const positionDifference = this._prevPositionIndex - this._currPositionIndex;
  2038. if (!positionDifference) {
  2039. return;
  2040. }
  2041. this._prevPositionIndex = this._currPositionIndex;
  2042. pswp.currIndex = pswp.potentialIndex;
  2043. let diffAbs = Math.abs(positionDifference);
  2044. let tempHolder;
  2045. if (diffAbs >= 3) {
  2046. this._containerShiftIndex += positionDifference + (positionDifference > 0 ? -3 : 3);
  2047. diffAbs = 3;
  2048. this.itemHolders.forEach((itemHolder) => {
  2049. var _itemHolder$slide;
  2050. (_itemHolder$slide = itemHolder.slide) === null || _itemHolder$slide === void 0 || _itemHolder$slide.destroy();
  2051. itemHolder.slide = void 0;
  2052. });
  2053. }
  2054. for (let i = 0; i < diffAbs; i++) {
  2055. if (positionDifference > 0) {
  2056. tempHolder = this.itemHolders.shift();
  2057. if (tempHolder) {
  2058. this.itemHolders[2] = tempHolder;
  2059. this._containerShiftIndex++;
  2060. setTransform(tempHolder.el, (this._containerShiftIndex + 2) * this.slideWidth);
  2061. pswp.setContent(tempHolder, pswp.currIndex - diffAbs + i + 2);
  2062. }
  2063. } else {
  2064. tempHolder = this.itemHolders.pop();
  2065. if (tempHolder) {
  2066. this.itemHolders.unshift(tempHolder);
  2067. this._containerShiftIndex--;
  2068. setTransform(tempHolder.el, this._containerShiftIndex * this.slideWidth);
  2069. pswp.setContent(tempHolder, pswp.currIndex + diffAbs - i - 2);
  2070. }
  2071. }
  2072. }
  2073. if (Math.abs(this._containerShiftIndex) > 50 && !this.isShifted()) {
  2074. this.resetPosition();
  2075. this.resize();
  2076. }
  2077. pswp.animations.stopAllPan();
  2078. this.itemHolders.forEach((itemHolder, i) => {
  2079. if (itemHolder.slide) {
  2080. itemHolder.slide.setIsActive(i === 1);
  2081. }
  2082. });
  2083. pswp.currSlide = (_this$itemHolders$ = this.itemHolders[1]) === null || _this$itemHolders$ === void 0 ? void 0 : _this$itemHolders$.slide;
  2084. pswp.contentLoader.updateLazy(positionDifference);
  2085. if (pswp.currSlide) {
  2086. pswp.currSlide.applyCurrentZoomPan();
  2087. }
  2088. pswp.dispatch("change");
  2089. }
  2090. /**
  2091. * Move the X position of the main scroll container
  2092. *
  2093. * @param {number} x
  2094. * @param {boolean} [dragging]
  2095. */
  2096. moveTo(x, dragging) {
  2097. if (!this.pswp.canLoop() && dragging) {
  2098. let newSlideIndexOffset = (this.slideWidth * this._currPositionIndex - x) / this.slideWidth;
  2099. newSlideIndexOffset += this.pswp.currIndex;
  2100. const delta = Math.round(x - this.x);
  2101. if (newSlideIndexOffset < 0 && delta > 0 || newSlideIndexOffset >= this.pswp.getNumItems() - 1 && delta < 0) {
  2102. x = this.x + delta * MAIN_SCROLL_END_FRICTION;
  2103. }
  2104. }
  2105. this.x = x;
  2106. if (this.pswp.container) {
  2107. setTransform(this.pswp.container, x);
  2108. }
  2109. this.pswp.dispatch("moveMainScroll", {
  2110. x,
  2111. dragging: dragging !== null && dragging !== void 0 ? dragging : false
  2112. });
  2113. }
  2114. };
  2115. var KeyboardKeyCodesMap = {
  2116. Escape: 27,
  2117. z: 90,
  2118. ArrowLeft: 37,
  2119. ArrowUp: 38,
  2120. ArrowRight: 39,
  2121. ArrowDown: 40,
  2122. Tab: 9
  2123. };
  2124. var getKeyboardEventKey = (key, isKeySupported) => {
  2125. return isKeySupported ? key : KeyboardKeyCodesMap[key];
  2126. };
  2127. var Keyboard = class {
  2128. /**
  2129. * @param {PhotoSwipe} pswp
  2130. */
  2131. constructor(pswp) {
  2132. this.pswp = pswp;
  2133. this._wasFocused = false;
  2134. pswp.on("bindEvents", () => {
  2135. if (pswp.options.trapFocus) {
  2136. if (!pswp.options.initialPointerPos) {
  2137. this._focusRoot();
  2138. }
  2139. pswp.events.add(
  2140. document,
  2141. "focusin",
  2142. /** @type EventListener */
  2143. this._onFocusIn.bind(this)
  2144. );
  2145. }
  2146. pswp.events.add(
  2147. document,
  2148. "keydown",
  2149. /** @type EventListener */
  2150. this._onKeyDown.bind(this)
  2151. );
  2152. });
  2153. const lastActiveElement = (
  2154. /** @type {HTMLElement} */
  2155. document.activeElement
  2156. );
  2157. pswp.on("destroy", () => {
  2158. if (pswp.options.returnFocus && lastActiveElement && this._wasFocused) {
  2159. lastActiveElement.focus();
  2160. }
  2161. });
  2162. }
  2163. /** @private */
  2164. _focusRoot() {
  2165. if (!this._wasFocused && this.pswp.element) {
  2166. this.pswp.element.focus();
  2167. this._wasFocused = true;
  2168. }
  2169. }
  2170. /**
  2171. * @private
  2172. * @param {KeyboardEvent} e
  2173. */
  2174. _onKeyDown(e) {
  2175. const {
  2176. pswp
  2177. } = this;
  2178. if (pswp.dispatch("keydown", {
  2179. originalEvent: e
  2180. }).defaultPrevented) {
  2181. return;
  2182. }
  2183. if (specialKeyUsed(e)) {
  2184. return;
  2185. }
  2186. let keydownAction;
  2187. let axis;
  2188. let isForward = false;
  2189. const isKeySupported = "key" in e;
  2190. switch (isKeySupported ? e.key : e.keyCode) {
  2191. case getKeyboardEventKey("Escape", isKeySupported):
  2192. if (pswp.options.escKey) {
  2193. keydownAction = "close";
  2194. }
  2195. break;
  2196. case getKeyboardEventKey("z", isKeySupported):
  2197. keydownAction = "toggleZoom";
  2198. break;
  2199. case getKeyboardEventKey("ArrowLeft", isKeySupported):
  2200. axis = "x";
  2201. break;
  2202. case getKeyboardEventKey("ArrowUp", isKeySupported):
  2203. axis = "y";
  2204. break;
  2205. case getKeyboardEventKey("ArrowRight", isKeySupported):
  2206. axis = "x";
  2207. isForward = true;
  2208. break;
  2209. case getKeyboardEventKey("ArrowDown", isKeySupported):
  2210. isForward = true;
  2211. axis = "y";
  2212. break;
  2213. case getKeyboardEventKey("Tab", isKeySupported):
  2214. this._focusRoot();
  2215. break;
  2216. }
  2217. if (axis) {
  2218. e.preventDefault();
  2219. const {
  2220. currSlide
  2221. } = pswp;
  2222. if (pswp.options.arrowKeys && axis === "x" && pswp.getNumItems() > 1) {
  2223. keydownAction = isForward ? "next" : "prev";
  2224. } else if (currSlide && currSlide.currZoomLevel > currSlide.zoomLevels.fit) {
  2225. currSlide.pan[axis] += isForward ? -80 : 80;
  2226. currSlide.panTo(currSlide.pan.x, currSlide.pan.y);
  2227. }
  2228. }
  2229. if (keydownAction) {
  2230. e.preventDefault();
  2231. pswp[keydownAction]();
  2232. }
  2233. }
  2234. /**
  2235. * Trap focus inside photoswipe
  2236. *
  2237. * @private
  2238. * @param {FocusEvent} e
  2239. */
  2240. _onFocusIn(e) {
  2241. const {
  2242. template
  2243. } = this.pswp;
  2244. if (template && document !== e.target && template !== e.target && !template.contains(
  2245. /** @type {Node} */
  2246. e.target
  2247. )) {
  2248. template.focus();
  2249. }
  2250. }
  2251. };
  2252. var DEFAULT_EASING = "cubic-bezier(.4,0,.22,1)";
  2253. var CSSAnimation = class {
  2254. /**
  2255. * onComplete can be unpredictable, be careful about current state
  2256. *
  2257. * @param {CssAnimationProps} props
  2258. */
  2259. constructor(props) {
  2260. var _props$prop;
  2261. this.props = props;
  2262. const {
  2263. target,
  2264. onComplete,
  2265. transform,
  2266. onFinish = () => {
  2267. },
  2268. duration = 333,
  2269. easing = DEFAULT_EASING
  2270. } = props;
  2271. this.onFinish = onFinish;
  2272. const prop = transform ? "transform" : "opacity";
  2273. const propValue = (_props$prop = props[prop]) !== null && _props$prop !== void 0 ? _props$prop : "";
  2274. this._target = target;
  2275. this._onComplete = onComplete;
  2276. this._finished = false;
  2277. this._onTransitionEnd = this._onTransitionEnd.bind(this);
  2278. this._helperTimeout = setTimeout(() => {
  2279. setTransitionStyle(target, prop, duration, easing);
  2280. this._helperTimeout = setTimeout(() => {
  2281. target.addEventListener("transitionend", this._onTransitionEnd, false);
  2282. target.addEventListener("transitioncancel", this._onTransitionEnd, false);
  2283. this._helperTimeout = setTimeout(() => {
  2284. this._finalizeAnimation();
  2285. }, duration + 500);
  2286. target.style[prop] = propValue;
  2287. }, 30);
  2288. }, 0);
  2289. }
  2290. /**
  2291. * @private
  2292. * @param {TransitionEvent} e
  2293. */
  2294. _onTransitionEnd(e) {
  2295. if (e.target === this._target) {
  2296. this._finalizeAnimation();
  2297. }
  2298. }
  2299. /**
  2300. * @private
  2301. */
  2302. _finalizeAnimation() {
  2303. if (!this._finished) {
  2304. this._finished = true;
  2305. this.onFinish();
  2306. if (this._onComplete) {
  2307. this._onComplete();
  2308. }
  2309. }
  2310. }
  2311. // Destroy is called automatically onFinish
  2312. destroy() {
  2313. if (this._helperTimeout) {
  2314. clearTimeout(this._helperTimeout);
  2315. }
  2316. removeTransitionStyle(this._target);
  2317. this._target.removeEventListener("transitionend", this._onTransitionEnd, false);
  2318. this._target.removeEventListener("transitioncancel", this._onTransitionEnd, false);
  2319. if (!this._finished) {
  2320. this._finalizeAnimation();
  2321. }
  2322. }
  2323. };
  2324. var DEFAULT_NATURAL_FREQUENCY = 12;
  2325. var DEFAULT_DAMPING_RATIO = 0.75;
  2326. var SpringEaser = class {
  2327. /**
  2328. * @param {number} initialVelocity Initial velocity, px per ms.
  2329. *
  2330. * @param {number} [dampingRatio]
  2331. * Determines how bouncy animation will be.
  2332. * From 0 to 1, 0 - always overshoot, 1 - do not overshoot.
  2333. * "overshoot" refers to part of animation that
  2334. * goes beyond the final value.
  2335. *
  2336. * @param {number} [naturalFrequency]
  2337. * Determines how fast animation will slow down.
  2338. * The higher value - the stiffer the transition will be,
  2339. * and the faster it will slow down.
  2340. * Recommended value from 10 to 50
  2341. */
  2342. constructor(initialVelocity, dampingRatio, naturalFrequency) {
  2343. this.velocity = initialVelocity * 1e3;
  2344. this._dampingRatio = dampingRatio || DEFAULT_DAMPING_RATIO;
  2345. this._naturalFrequency = naturalFrequency || DEFAULT_NATURAL_FREQUENCY;
  2346. this._dampedFrequency = this._naturalFrequency;
  2347. if (this._dampingRatio < 1) {
  2348. this._dampedFrequency *= Math.sqrt(1 - this._dampingRatio * this._dampingRatio);
  2349. }
  2350. }
  2351. /**
  2352. * @param {number} deltaPosition Difference between current and end position of the animation
  2353. * @param {number} deltaTime Frame duration in milliseconds
  2354. *
  2355. * @returns {number} Displacement, relative to the end position.
  2356. */
  2357. easeFrame(deltaPosition, deltaTime) {
  2358. let displacement = 0;
  2359. let coeff;
  2360. deltaTime /= 1e3;
  2361. const naturalDumpingPow = Math.E ** (-this._dampingRatio * this._naturalFrequency * deltaTime);
  2362. if (this._dampingRatio === 1) {
  2363. coeff = this.velocity + this._naturalFrequency * deltaPosition;
  2364. displacement = (deltaPosition + coeff * deltaTime) * naturalDumpingPow;
  2365. this.velocity = displacement * -this._naturalFrequency + coeff * naturalDumpingPow;
  2366. } else if (this._dampingRatio < 1) {
  2367. coeff = 1 / this._dampedFrequency * (this._dampingRatio * this._naturalFrequency * deltaPosition + this.velocity);
  2368. const dumpedFCos = Math.cos(this._dampedFrequency * deltaTime);
  2369. const dumpedFSin = Math.sin(this._dampedFrequency * deltaTime);
  2370. displacement = naturalDumpingPow * (deltaPosition * dumpedFCos + coeff * dumpedFSin);
  2371. this.velocity = displacement * -this._naturalFrequency * this._dampingRatio + naturalDumpingPow * (-this._dampedFrequency * deltaPosition * dumpedFSin + this._dampedFrequency * coeff * dumpedFCos);
  2372. }
  2373. return displacement;
  2374. }
  2375. };
  2376. var SpringAnimation = class {
  2377. /**
  2378. * @param {SpringAnimationProps} props
  2379. */
  2380. constructor(props) {
  2381. this.props = props;
  2382. this._raf = 0;
  2383. const {
  2384. start,
  2385. end,
  2386. velocity,
  2387. onUpdate,
  2388. onComplete,
  2389. onFinish = () => {
  2390. },
  2391. dampingRatio,
  2392. naturalFrequency
  2393. } = props;
  2394. this.onFinish = onFinish;
  2395. const easer = new SpringEaser(velocity, dampingRatio, naturalFrequency);
  2396. let prevTime = Date.now();
  2397. let deltaPosition = start - end;
  2398. const animationLoop = () => {
  2399. if (this._raf) {
  2400. deltaPosition = easer.easeFrame(deltaPosition, Date.now() - prevTime);
  2401. if (Math.abs(deltaPosition) < 1 && Math.abs(easer.velocity) < 50) {
  2402. onUpdate(end);
  2403. if (onComplete) {
  2404. onComplete();
  2405. }
  2406. this.onFinish();
  2407. } else {
  2408. prevTime = Date.now();
  2409. onUpdate(deltaPosition + end);
  2410. this._raf = requestAnimationFrame(animationLoop);
  2411. }
  2412. }
  2413. };
  2414. this._raf = requestAnimationFrame(animationLoop);
  2415. }
  2416. // Destroy is called automatically onFinish
  2417. destroy() {
  2418. if (this._raf >= 0) {
  2419. cancelAnimationFrame(this._raf);
  2420. }
  2421. this._raf = 0;
  2422. }
  2423. };
  2424. var Animations = class {
  2425. constructor() {
  2426. this.activeAnimations = [];
  2427. }
  2428. /**
  2429. * @param {SpringAnimationProps} props
  2430. */
  2431. startSpring(props) {
  2432. this._start(props, true);
  2433. }
  2434. /**
  2435. * @param {CssAnimationProps} props
  2436. */
  2437. startTransition(props) {
  2438. this._start(props);
  2439. }
  2440. /**
  2441. * @private
  2442. * @param {AnimationProps} props
  2443. * @param {boolean} [isSpring]
  2444. * @returns {Animation}
  2445. */
  2446. _start(props, isSpring) {
  2447. const animation = isSpring ? new SpringAnimation(
  2448. /** @type SpringAnimationProps */
  2449. props
  2450. ) : new CSSAnimation(
  2451. /** @type CssAnimationProps */
  2452. props
  2453. );
  2454. this.activeAnimations.push(animation);
  2455. animation.onFinish = () => this.stop(animation);
  2456. return animation;
  2457. }
  2458. /**
  2459. * @param {Animation} animation
  2460. */
  2461. stop(animation) {
  2462. animation.destroy();
  2463. const index = this.activeAnimations.indexOf(animation);
  2464. if (index > -1) {
  2465. this.activeAnimations.splice(index, 1);
  2466. }
  2467. }
  2468. stopAll() {
  2469. this.activeAnimations.forEach((animation) => {
  2470. animation.destroy();
  2471. });
  2472. this.activeAnimations = [];
  2473. }
  2474. /**
  2475. * Stop all pan or zoom transitions
  2476. */
  2477. stopAllPan() {
  2478. this.activeAnimations = this.activeAnimations.filter((animation) => {
  2479. if (animation.props.isPan) {
  2480. animation.destroy();
  2481. return false;
  2482. }
  2483. return true;
  2484. });
  2485. }
  2486. stopMainScroll() {
  2487. this.activeAnimations = this.activeAnimations.filter((animation) => {
  2488. if (animation.props.isMainScroll) {
  2489. animation.destroy();
  2490. return false;
  2491. }
  2492. return true;
  2493. });
  2494. }
  2495. /**
  2496. * Returns true if main scroll transition is running
  2497. */
  2498. // isMainScrollRunning() {
  2499. // return this.activeAnimations.some((animation) => {
  2500. // return animation.props.isMainScroll;
  2501. // });
  2502. // }
  2503. /**
  2504. * Returns true if any pan or zoom transition is running
  2505. */
  2506. isPanRunning() {
  2507. return this.activeAnimations.some((animation) => {
  2508. return animation.props.isPan;
  2509. });
  2510. }
  2511. };
  2512. var ScrollWheel = class {
  2513. /**
  2514. * @param {PhotoSwipe} pswp
  2515. */
  2516. constructor(pswp) {
  2517. this.pswp = pswp;
  2518. pswp.events.add(
  2519. pswp.element,
  2520. "wheel",
  2521. /** @type EventListener */
  2522. this._onWheel.bind(this)
  2523. );
  2524. }
  2525. /**
  2526. * @private
  2527. * @param {WheelEvent} e
  2528. */
  2529. _onWheel(e) {
  2530. e.preventDefault();
  2531. const {
  2532. currSlide
  2533. } = this.pswp;
  2534. let {
  2535. deltaX,
  2536. deltaY
  2537. } = e;
  2538. if (!currSlide) {
  2539. return;
  2540. }
  2541. if (this.pswp.dispatch("wheel", {
  2542. originalEvent: e
  2543. }).defaultPrevented) {
  2544. return;
  2545. }
  2546. if (e.ctrlKey || this.pswp.options.wheelToZoom) {
  2547. if (currSlide.isZoomable()) {
  2548. let zoomFactor = -deltaY;
  2549. if (e.deltaMode === 1) {
  2550. zoomFactor *= 0.05;
  2551. } else {
  2552. zoomFactor *= e.deltaMode ? 1 : 2e-3;
  2553. }
  2554. zoomFactor = 2 ** zoomFactor;
  2555. const destZoomLevel = currSlide.currZoomLevel * zoomFactor;
  2556. currSlide.zoomTo(destZoomLevel, {
  2557. x: e.clientX,
  2558. y: e.clientY
  2559. });
  2560. }
  2561. } else {
  2562. if (currSlide.isPannable()) {
  2563. if (e.deltaMode === 1) {
  2564. deltaX *= 18;
  2565. deltaY *= 18;
  2566. }
  2567. currSlide.panTo(currSlide.pan.x - deltaX, currSlide.pan.y - deltaY);
  2568. }
  2569. }
  2570. }
  2571. };
  2572. function addElementHTML(htmlData) {
  2573. if (typeof htmlData === "string") {
  2574. return htmlData;
  2575. }
  2576. if (!htmlData || !htmlData.isCustomSVG) {
  2577. return "";
  2578. }
  2579. const svgData = htmlData;
  2580. let out = '<svg aria-hidden="true" class="pswp__icn" viewBox="0 0 %d %d" width="%d" height="%d">';
  2581. out = out.split("%d").join(
  2582. /** @type {string} */
  2583. svgData.size || 32
  2584. );
  2585. if (svgData.outlineID) {
  2586. out += '<use class="pswp__icn-shadow" xlink:href="#' + svgData.outlineID + '"/>';
  2587. }
  2588. out += svgData.inner;
  2589. out += "</svg>";
  2590. return out;
  2591. }
  2592. var UIElement = class {
  2593. /**
  2594. * @param {PhotoSwipe} pswp
  2595. * @param {UIElementData} data
  2596. */
  2597. constructor(pswp, data) {
  2598. var _container;
  2599. const name = data.name || data.className;
  2600. let elementHTML = data.html;
  2601. if (pswp.options[name] === false) {
  2602. return;
  2603. }
  2604. if (typeof pswp.options[name + "SVG"] === "string") {
  2605. elementHTML = pswp.options[name + "SVG"];
  2606. }
  2607. pswp.dispatch("uiElementCreate", {
  2608. data
  2609. });
  2610. let className = "";
  2611. if (data.isButton) {
  2612. className += "pswp__button ";
  2613. className += data.className || `pswp__button--${data.name}`;
  2614. } else {
  2615. className += data.className || `pswp__${data.name}`;
  2616. }
  2617. let tagName = data.isButton ? data.tagName || "button" : data.tagName || "div";
  2618. tagName = /** @type {keyof HTMLElementTagNameMap} */
  2619. tagName.toLowerCase();
  2620. const element = createElement(className, tagName);
  2621. if (data.isButton) {
  2622. if (tagName === "button") {
  2623. element.type = "button";
  2624. }
  2625. let {
  2626. title
  2627. } = data;
  2628. const {
  2629. ariaLabel
  2630. } = data;
  2631. if (typeof pswp.options[name + "Title"] === "string") {
  2632. title = pswp.options[name + "Title"];
  2633. }
  2634. if (title) {
  2635. element.title = title;
  2636. }
  2637. const ariaText = ariaLabel || title;
  2638. if (ariaText) {
  2639. element.setAttribute("aria-label", ariaText);
  2640. }
  2641. }
  2642. element.innerHTML = addElementHTML(elementHTML);
  2643. if (data.onInit) {
  2644. data.onInit(element, pswp);
  2645. }
  2646. if (data.onClick) {
  2647. element.onclick = (e) => {
  2648. if (typeof data.onClick === "string") {
  2649. pswp[data.onClick]();
  2650. } else if (typeof data.onClick === "function") {
  2651. data.onClick(e, element, pswp);
  2652. }
  2653. };
  2654. }
  2655. const appendTo = data.appendTo || "bar";
  2656. let container = pswp.element;
  2657. if (appendTo === "bar") {
  2658. if (!pswp.topBar) {
  2659. pswp.topBar = createElement("pswp__top-bar pswp__hide-on-close", "div", pswp.scrollWrap);
  2660. }
  2661. container = pswp.topBar;
  2662. } else {
  2663. element.classList.add("pswp__hide-on-close");
  2664. if (appendTo === "wrapper") {
  2665. container = pswp.scrollWrap;
  2666. }
  2667. }
  2668. (_container = container) === null || _container === void 0 || _container.appendChild(pswp.applyFilters("uiElement", element, data));
  2669. }
  2670. };
  2671. function initArrowButton(element, pswp, isNextButton) {
  2672. element.classList.add("pswp__button--arrow");
  2673. element.setAttribute("aria-controls", "pswp__items");
  2674. pswp.on("change", () => {
  2675. if (!pswp.options.loop) {
  2676. if (isNextButton) {
  2677. element.disabled = !(pswp.currIndex < pswp.getNumItems() - 1);
  2678. } else {
  2679. element.disabled = !(pswp.currIndex > 0);
  2680. }
  2681. }
  2682. });
  2683. }
  2684. var arrowPrev = {
  2685. name: "arrowPrev",
  2686. className: "pswp__button--arrow--prev",
  2687. title: "Previous",
  2688. order: 10,
  2689. isButton: true,
  2690. appendTo: "wrapper",
  2691. html: {
  2692. isCustomSVG: true,
  2693. size: 60,
  2694. inner: '<path d="M29 43l-3 3-16-16 16-16 3 3-13 13 13 13z" id="pswp__icn-arrow"/>',
  2695. outlineID: "pswp__icn-arrow"
  2696. },
  2697. onClick: "prev",
  2698. onInit: initArrowButton
  2699. };
  2700. var arrowNext = {
  2701. name: "arrowNext",
  2702. className: "pswp__button--arrow--next",
  2703. title: "Next",
  2704. order: 11,
  2705. isButton: true,
  2706. appendTo: "wrapper",
  2707. html: {
  2708. isCustomSVG: true,
  2709. size: 60,
  2710. inner: '<use xlink:href="#pswp__icn-arrow"/>',
  2711. outlineID: "pswp__icn-arrow"
  2712. },
  2713. onClick: "next",
  2714. onInit: (el, pswp) => {
  2715. initArrowButton(el, pswp, true);
  2716. }
  2717. };
  2718. var closeButton = {
  2719. name: "close",
  2720. title: "Close",
  2721. order: 20,
  2722. isButton: true,
  2723. html: {
  2724. isCustomSVG: true,
  2725. inner: '<path d="M24 10l-2-2-6 6-6-6-2 2 6 6-6 6 2 2 6-6 6 6 2-2-6-6z" id="pswp__icn-close"/>',
  2726. outlineID: "pswp__icn-close"
  2727. },
  2728. onClick: "close"
  2729. };
  2730. var zoomButton = {
  2731. name: "zoom",
  2732. title: "Zoom",
  2733. order: 10,
  2734. isButton: true,
  2735. html: {
  2736. isCustomSVG: true,
  2737. // eslint-disable-next-line max-len
  2738. inner: '<path d="M17.426 19.926a6 6 0 1 1 1.5-1.5L23 22.5 21.5 24l-4.074-4.074z" id="pswp__icn-zoom"/><path fill="currentColor" class="pswp__zoom-icn-bar-h" d="M11 16v-2h6v2z"/><path fill="currentColor" class="pswp__zoom-icn-bar-v" d="M13 12h2v6h-2z"/>',
  2739. outlineID: "pswp__icn-zoom"
  2740. },
  2741. onClick: "toggleZoom"
  2742. };
  2743. var loadingIndicator = {
  2744. name: "preloader",
  2745. appendTo: "bar",
  2746. order: 7,
  2747. html: {
  2748. isCustomSVG: true,
  2749. // eslint-disable-next-line max-len
  2750. inner: '<path fill-rule="evenodd" clip-rule="evenodd" d="M21.2 16a5.2 5.2 0 1 1-5.2-5.2V8a8 8 0 1 0 8 8h-2.8Z" id="pswp__icn-loading"/>',
  2751. outlineID: "pswp__icn-loading"
  2752. },
  2753. onInit: (indicatorElement, pswp) => {
  2754. let isVisible;
  2755. let delayTimeout = null;
  2756. const toggleIndicatorClass = (className, add) => {
  2757. indicatorElement.classList.toggle("pswp__preloader--" + className, add);
  2758. };
  2759. const setIndicatorVisibility = (visible) => {
  2760. if (isVisible !== visible) {
  2761. isVisible = visible;
  2762. toggleIndicatorClass("active", visible);
  2763. }
  2764. };
  2765. const updatePreloaderVisibility = () => {
  2766. var _pswp$currSlide;
  2767. if (!((_pswp$currSlide = pswp.currSlide) !== null && _pswp$currSlide !== void 0 && _pswp$currSlide.content.isLoading())) {
  2768. setIndicatorVisibility(false);
  2769. if (delayTimeout) {
  2770. clearTimeout(delayTimeout);
  2771. delayTimeout = null;
  2772. }
  2773. return;
  2774. }
  2775. if (!delayTimeout) {
  2776. delayTimeout = setTimeout(() => {
  2777. var _pswp$currSlide2;
  2778. setIndicatorVisibility(Boolean((_pswp$currSlide2 = pswp.currSlide) === null || _pswp$currSlide2 === void 0 ? void 0 : _pswp$currSlide2.content.isLoading()));
  2779. delayTimeout = null;
  2780. }, pswp.options.preloaderDelay);
  2781. }
  2782. };
  2783. pswp.on("change", updatePreloaderVisibility);
  2784. pswp.on("loadComplete", (e) => {
  2785. if (pswp.currSlide === e.slide) {
  2786. updatePreloaderVisibility();
  2787. }
  2788. });
  2789. if (pswp.ui) {
  2790. pswp.ui.updatePreloaderVisibility = updatePreloaderVisibility;
  2791. }
  2792. }
  2793. };
  2794. var counterIndicator = {
  2795. name: "counter",
  2796. order: 5,
  2797. onInit: (counterElement, pswp) => {
  2798. pswp.on("change", () => {
  2799. counterElement.innerText = pswp.currIndex + 1 + pswp.options.indexIndicatorSep + pswp.getNumItems();
  2800. });
  2801. }
  2802. };
  2803. function setZoomedIn(el, isZoomedIn) {
  2804. el.classList.toggle("pswp--zoomed-in", isZoomedIn);
  2805. }
  2806. var UI = class {
  2807. /**
  2808. * @param {PhotoSwipe} pswp
  2809. */
  2810. constructor(pswp) {
  2811. this.pswp = pswp;
  2812. this.isRegistered = false;
  2813. this.uiElementsData = [];
  2814. this.items = [];
  2815. this.updatePreloaderVisibility = () => {
  2816. };
  2817. this._lastUpdatedZoomLevel = void 0;
  2818. }
  2819. init() {
  2820. const {
  2821. pswp
  2822. } = this;
  2823. this.isRegistered = false;
  2824. this.uiElementsData = [closeButton, arrowPrev, arrowNext, zoomButton, loadingIndicator, counterIndicator];
  2825. pswp.dispatch("uiRegister");
  2826. this.uiElementsData.sort((a, b) => {
  2827. return (a.order || 0) - (b.order || 0);
  2828. });
  2829. this.items = [];
  2830. this.isRegistered = true;
  2831. this.uiElementsData.forEach((uiElementData) => {
  2832. this.registerElement(uiElementData);
  2833. });
  2834. pswp.on("change", () => {
  2835. var _pswp$element;
  2836. (_pswp$element = pswp.element) === null || _pswp$element === void 0 || _pswp$element.classList.toggle("pswp--one-slide", pswp.getNumItems() === 1);
  2837. });
  2838. pswp.on("zoomPanUpdate", () => this._onZoomPanUpdate());
  2839. }
  2840. /**
  2841. * @param {UIElementData} elementData
  2842. */
  2843. registerElement(elementData) {
  2844. if (this.isRegistered) {
  2845. this.items.push(new UIElement(this.pswp, elementData));
  2846. } else {
  2847. this.uiElementsData.push(elementData);
  2848. }
  2849. }
  2850. /**
  2851. * Fired each time zoom or pan position is changed.
  2852. * Update classes that control visibility of zoom button and cursor icon.
  2853. *
  2854. * @private
  2855. */
  2856. _onZoomPanUpdate() {
  2857. const {
  2858. template,
  2859. currSlide,
  2860. options
  2861. } = this.pswp;
  2862. if (this.pswp.opener.isClosing || !template || !currSlide) {
  2863. return;
  2864. }
  2865. let {
  2866. currZoomLevel
  2867. } = currSlide;
  2868. if (!this.pswp.opener.isOpen) {
  2869. currZoomLevel = currSlide.zoomLevels.initial;
  2870. }
  2871. if (currZoomLevel === this._lastUpdatedZoomLevel) {
  2872. return;
  2873. }
  2874. this._lastUpdatedZoomLevel = currZoomLevel;
  2875. const currZoomLevelDiff = currSlide.zoomLevels.initial - currSlide.zoomLevels.secondary;
  2876. if (Math.abs(currZoomLevelDiff) < 0.01 || !currSlide.isZoomable()) {
  2877. setZoomedIn(template, false);
  2878. template.classList.remove("pswp--zoom-allowed");
  2879. return;
  2880. }
  2881. template.classList.add("pswp--zoom-allowed");
  2882. const potentialZoomLevel = currZoomLevel === currSlide.zoomLevels.initial ? currSlide.zoomLevels.secondary : currSlide.zoomLevels.initial;
  2883. setZoomedIn(template, potentialZoomLevel <= currZoomLevel);
  2884. if (options.imageClickAction === "zoom" || options.imageClickAction === "zoom-or-close") {
  2885. template.classList.add("pswp--click-to-zoom");
  2886. }
  2887. }
  2888. };
  2889. function getBoundsByElement(el) {
  2890. const thumbAreaRect = el.getBoundingClientRect();
  2891. return {
  2892. x: thumbAreaRect.left,
  2893. y: thumbAreaRect.top,
  2894. w: thumbAreaRect.width
  2895. };
  2896. }
  2897. function getCroppedBoundsByElement(el, imageWidth, imageHeight) {
  2898. const thumbAreaRect = el.getBoundingClientRect();
  2899. const hRatio = thumbAreaRect.width / imageWidth;
  2900. const vRatio = thumbAreaRect.height / imageHeight;
  2901. const fillZoomLevel = hRatio > vRatio ? hRatio : vRatio;
  2902. const offsetX = (thumbAreaRect.width - imageWidth * fillZoomLevel) / 2;
  2903. const offsetY = (thumbAreaRect.height - imageHeight * fillZoomLevel) / 2;
  2904. const bounds = {
  2905. x: thumbAreaRect.left + offsetX,
  2906. y: thumbAreaRect.top + offsetY,
  2907. w: imageWidth * fillZoomLevel
  2908. };
  2909. bounds.innerRect = {
  2910. w: thumbAreaRect.width,
  2911. h: thumbAreaRect.height,
  2912. x: offsetX,
  2913. y: offsetY
  2914. };
  2915. return bounds;
  2916. }
  2917. function getThumbBounds(index, itemData, instance) {
  2918. const event = instance.dispatch("thumbBounds", {
  2919. index,
  2920. itemData,
  2921. instance
  2922. });
  2923. if (event.thumbBounds) {
  2924. return event.thumbBounds;
  2925. }
  2926. const {
  2927. element
  2928. } = itemData;
  2929. let thumbBounds;
  2930. let thumbnail;
  2931. if (element && instance.options.thumbSelector !== false) {
  2932. const thumbSelector = instance.options.thumbSelector || "img";
  2933. thumbnail = element.matches(thumbSelector) ? element : (
  2934. /** @type {HTMLElement | null} */
  2935. element.querySelector(thumbSelector)
  2936. );
  2937. }
  2938. thumbnail = instance.applyFilters("thumbEl", thumbnail, itemData, index);
  2939. if (thumbnail) {
  2940. if (!itemData.thumbCropped) {
  2941. thumbBounds = getBoundsByElement(thumbnail);
  2942. } else {
  2943. thumbBounds = getCroppedBoundsByElement(thumbnail, itemData.width || itemData.w || 0, itemData.height || itemData.h || 0);
  2944. }
  2945. }
  2946. return instance.applyFilters("thumbBounds", thumbBounds, itemData, index);
  2947. }
  2948. var PhotoSwipeEvent = class {
  2949. /**
  2950. * @param {T} type
  2951. * @param {PhotoSwipeEventsMap[T]} [details]
  2952. */
  2953. constructor(type, details) {
  2954. this.type = type;
  2955. this.defaultPrevented = false;
  2956. if (details) {
  2957. Object.assign(this, details);
  2958. }
  2959. }
  2960. preventDefault() {
  2961. this.defaultPrevented = true;
  2962. }
  2963. };
  2964. var Eventable = class {
  2965. constructor() {
  2966. this._listeners = {};
  2967. this._filters = {};
  2968. this.pswp = void 0;
  2969. this.options = void 0;
  2970. }
  2971. /**
  2972. * @template {keyof PhotoSwipeFiltersMap} T
  2973. * @param {T} name
  2974. * @param {PhotoSwipeFiltersMap[T]} fn
  2975. * @param {number} priority
  2976. */
  2977. addFilter(name, fn, priority = 100) {
  2978. var _this$_filters$name, _this$_filters$name2, _this$pswp;
  2979. if (!this._filters[name]) {
  2980. this._filters[name] = [];
  2981. }
  2982. (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.push({
  2983. fn,
  2984. priority
  2985. });
  2986. (_this$_filters$name2 = this._filters[name]) === null || _this$_filters$name2 === void 0 || _this$_filters$name2.sort((f1, f2) => f1.priority - f2.priority);
  2987. (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.addFilter(name, fn, priority);
  2988. }
  2989. /**
  2990. * @template {keyof PhotoSwipeFiltersMap} T
  2991. * @param {T} name
  2992. * @param {PhotoSwipeFiltersMap[T]} fn
  2993. */
  2994. removeFilter(name, fn) {
  2995. if (this._filters[name]) {
  2996. this._filters[name] = this._filters[name].filter((filter) => filter.fn !== fn);
  2997. }
  2998. if (this.pswp) {
  2999. this.pswp.removeFilter(name, fn);
  3000. }
  3001. }
  3002. /**
  3003. * @template {keyof PhotoSwipeFiltersMap} T
  3004. * @param {T} name
  3005. * @param {Parameters<PhotoSwipeFiltersMap[T]>} args
  3006. * @returns {Parameters<PhotoSwipeFiltersMap[T]>[0]}
  3007. */
  3008. applyFilters(name, ...args) {
  3009. var _this$_filters$name3;
  3010. (_this$_filters$name3 = this._filters[name]) === null || _this$_filters$name3 === void 0 || _this$_filters$name3.forEach((filter) => {
  3011. args[0] = filter.fn.apply(this, args);
  3012. });
  3013. return args[0];
  3014. }
  3015. /**
  3016. * @template {keyof PhotoSwipeEventsMap} T
  3017. * @param {T} name
  3018. * @param {EventCallback<T>} fn
  3019. */
  3020. on(name, fn) {
  3021. var _this$_listeners$name, _this$pswp2;
  3022. if (!this._listeners[name]) {
  3023. this._listeners[name] = [];
  3024. }
  3025. (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.push(fn);
  3026. (_this$pswp2 = this.pswp) === null || _this$pswp2 === void 0 || _this$pswp2.on(name, fn);
  3027. }
  3028. /**
  3029. * @template {keyof PhotoSwipeEventsMap} T
  3030. * @param {T} name
  3031. * @param {EventCallback<T>} fn
  3032. */
  3033. off(name, fn) {
  3034. var _this$pswp3;
  3035. if (this._listeners[name]) {
  3036. this._listeners[name] = this._listeners[name].filter((listener) => fn !== listener);
  3037. }
  3038. (_this$pswp3 = this.pswp) === null || _this$pswp3 === void 0 || _this$pswp3.off(name, fn);
  3039. }
  3040. /**
  3041. * @template {keyof PhotoSwipeEventsMap} T
  3042. * @param {T} name
  3043. * @param {PhotoSwipeEventsMap[T]} [details]
  3044. * @returns {AugmentedEvent<T>}
  3045. */
  3046. dispatch(name, details) {
  3047. var _this$_listeners$name2;
  3048. if (this.pswp) {
  3049. return this.pswp.dispatch(name, details);
  3050. }
  3051. const event = (
  3052. /** @type {AugmentedEvent<T>} */
  3053. new PhotoSwipeEvent(name, details)
  3054. );
  3055. (_this$_listeners$name2 = this._listeners[name]) === null || _this$_listeners$name2 === void 0 || _this$_listeners$name2.forEach((listener) => {
  3056. listener.call(this, event);
  3057. });
  3058. return event;
  3059. }
  3060. };
  3061. var Placeholder = class {
  3062. /**
  3063. * @param {string | false} imageSrc
  3064. * @param {HTMLElement} container
  3065. */
  3066. constructor(imageSrc, container) {
  3067. this.element = createElement("pswp__img pswp__img--placeholder", imageSrc ? "img" : "div", container);
  3068. if (imageSrc) {
  3069. const imgEl = (
  3070. /** @type {HTMLImageElement} */
  3071. this.element
  3072. );
  3073. imgEl.decoding = "async";
  3074. imgEl.alt = "";
  3075. imgEl.src = imageSrc;
  3076. imgEl.setAttribute("role", "presentation");
  3077. }
  3078. this.element.setAttribute("aria-hidden", "true");
  3079. }
  3080. /**
  3081. * @param {number} width
  3082. * @param {number} height
  3083. */
  3084. setDisplayedSize(width, height) {
  3085. if (!this.element) {
  3086. return;
  3087. }
  3088. if (this.element.tagName === "IMG") {
  3089. setWidthHeight(this.element, 250, "auto");
  3090. this.element.style.transformOrigin = "0 0";
  3091. this.element.style.transform = toTransformString(0, 0, width / 250);
  3092. } else {
  3093. setWidthHeight(this.element, width, height);
  3094. }
  3095. }
  3096. destroy() {
  3097. var _this$element;
  3098. if ((_this$element = this.element) !== null && _this$element !== void 0 && _this$element.parentNode) {
  3099. this.element.remove();
  3100. }
  3101. this.element = null;
  3102. }
  3103. };
  3104. var Content = class {
  3105. /**
  3106. * @param {SlideData} itemData Slide data
  3107. * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance
  3108. * @param {number} index
  3109. */
  3110. constructor(itemData, instance, index) {
  3111. this.instance = instance;
  3112. this.data = itemData;
  3113. this.index = index;
  3114. this.element = void 0;
  3115. this.placeholder = void 0;
  3116. this.slide = void 0;
  3117. this.displayedImageWidth = 0;
  3118. this.displayedImageHeight = 0;
  3119. this.width = Number(this.data.w) || Number(this.data.width) || 0;
  3120. this.height = Number(this.data.h) || Number(this.data.height) || 0;
  3121. this.isAttached = false;
  3122. this.hasSlide = false;
  3123. this.isDecoding = false;
  3124. this.state = LOAD_STATE.IDLE;
  3125. if (this.data.type) {
  3126. this.type = this.data.type;
  3127. } else if (this.data.src) {
  3128. this.type = "image";
  3129. } else {
  3130. this.type = "html";
  3131. }
  3132. this.instance.dispatch("contentInit", {
  3133. content: this
  3134. });
  3135. }
  3136. removePlaceholder() {
  3137. if (this.placeholder && !this.keepPlaceholder()) {
  3138. setTimeout(() => {
  3139. if (this.placeholder) {
  3140. this.placeholder.destroy();
  3141. this.placeholder = void 0;
  3142. }
  3143. }, 1e3);
  3144. }
  3145. }
  3146. /**
  3147. * Preload content
  3148. *
  3149. * @param {boolean} isLazy
  3150. * @param {boolean} [reload]
  3151. */
  3152. load(isLazy, reload) {
  3153. if (this.slide && this.usePlaceholder()) {
  3154. if (!this.placeholder) {
  3155. const placeholderSrc = this.instance.applyFilters(
  3156. "placeholderSrc",
  3157. // use image-based placeholder only for the first slide,
  3158. // as rendering (even small stretched thumbnail) is an expensive operation
  3159. this.data.msrc && this.slide.isFirstSlide ? this.data.msrc : false,
  3160. this
  3161. );
  3162. this.placeholder = new Placeholder(placeholderSrc, this.slide.container);
  3163. } else {
  3164. const placeholderEl = this.placeholder.element;
  3165. if (placeholderEl && !placeholderEl.parentElement) {
  3166. this.slide.container.prepend(placeholderEl);
  3167. }
  3168. }
  3169. }
  3170. if (this.element && !reload) {
  3171. return;
  3172. }
  3173. if (this.instance.dispatch("contentLoad", {
  3174. content: this,
  3175. isLazy
  3176. }).defaultPrevented) {
  3177. return;
  3178. }
  3179. if (this.isImageContent()) {
  3180. this.element = createElement("pswp__img", "img");
  3181. if (this.displayedImageWidth) {
  3182. this.loadImage(isLazy);
  3183. }
  3184. } else {
  3185. this.element = createElement("pswp__content", "div");
  3186. this.element.innerHTML = this.data.html || "";
  3187. }
  3188. if (reload && this.slide) {
  3189. this.slide.updateContentSize(true);
  3190. }
  3191. }
  3192. /**
  3193. * Preload image
  3194. *
  3195. * @param {boolean} isLazy
  3196. */
  3197. loadImage(isLazy) {
  3198. var _this$data$src, _this$data$alt;
  3199. if (!this.isImageContent() || !this.element || this.instance.dispatch("contentLoadImage", {
  3200. content: this,
  3201. isLazy
  3202. }).defaultPrevented) {
  3203. return;
  3204. }
  3205. const imageElement = (
  3206. /** @type HTMLImageElement */
  3207. this.element
  3208. );
  3209. this.updateSrcsetSizes();
  3210. if (this.data.srcset) {
  3211. imageElement.srcset = this.data.srcset;
  3212. }
  3213. imageElement.src = (_this$data$src = this.data.src) !== null && _this$data$src !== void 0 ? _this$data$src : "";
  3214. imageElement.alt = (_this$data$alt = this.data.alt) !== null && _this$data$alt !== void 0 ? _this$data$alt : "";
  3215. this.state = LOAD_STATE.LOADING;
  3216. if (imageElement.complete) {
  3217. this.onLoaded();
  3218. } else {
  3219. imageElement.onload = () => {
  3220. this.onLoaded();
  3221. };
  3222. imageElement.onerror = () => {
  3223. this.onError();
  3224. };
  3225. }
  3226. }
  3227. /**
  3228. * Assign slide to content
  3229. *
  3230. * @param {Slide} slide
  3231. */
  3232. setSlide(slide) {
  3233. this.slide = slide;
  3234. this.hasSlide = true;
  3235. this.instance = slide.pswp;
  3236. }
  3237. /**
  3238. * Content load success handler
  3239. */
  3240. onLoaded() {
  3241. this.state = LOAD_STATE.LOADED;
  3242. if (this.slide && this.element) {
  3243. this.instance.dispatch("loadComplete", {
  3244. slide: this.slide,
  3245. content: this
  3246. });
  3247. if (this.slide.isActive && this.slide.heavyAppended && !this.element.parentNode) {
  3248. this.append();
  3249. this.slide.updateContentSize(true);
  3250. }
  3251. if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {
  3252. this.removePlaceholder();
  3253. }
  3254. }
  3255. }
  3256. /**
  3257. * Content load error handler
  3258. */
  3259. onError() {
  3260. this.state = LOAD_STATE.ERROR;
  3261. if (this.slide) {
  3262. this.displayError();
  3263. this.instance.dispatch("loadComplete", {
  3264. slide: this.slide,
  3265. isError: true,
  3266. content: this
  3267. });
  3268. this.instance.dispatch("loadError", {
  3269. slide: this.slide,
  3270. content: this
  3271. });
  3272. }
  3273. }
  3274. /**
  3275. * @returns {Boolean} If the content is currently loading
  3276. */
  3277. isLoading() {
  3278. return this.instance.applyFilters("isContentLoading", this.state === LOAD_STATE.LOADING, this);
  3279. }
  3280. /**
  3281. * @returns {Boolean} If the content is in error state
  3282. */
  3283. isError() {
  3284. return this.state === LOAD_STATE.ERROR;
  3285. }
  3286. /**
  3287. * @returns {boolean} If the content is image
  3288. */
  3289. isImageContent() {
  3290. return this.type === "image";
  3291. }
  3292. /**
  3293. * Update content size
  3294. *
  3295. * @param {Number} width
  3296. * @param {Number} height
  3297. */
  3298. setDisplayedSize(width, height) {
  3299. if (!this.element) {
  3300. return;
  3301. }
  3302. if (this.placeholder) {
  3303. this.placeholder.setDisplayedSize(width, height);
  3304. }
  3305. if (this.instance.dispatch("contentResize", {
  3306. content: this,
  3307. width,
  3308. height
  3309. }).defaultPrevented) {
  3310. return;
  3311. }
  3312. setWidthHeight(this.element, width, height);
  3313. if (this.isImageContent() && !this.isError()) {
  3314. const isInitialSizeUpdate = !this.displayedImageWidth && width;
  3315. this.displayedImageWidth = width;
  3316. this.displayedImageHeight = height;
  3317. if (isInitialSizeUpdate) {
  3318. this.loadImage(false);
  3319. } else {
  3320. this.updateSrcsetSizes();
  3321. }
  3322. if (this.slide) {
  3323. this.instance.dispatch("imageSizeChange", {
  3324. slide: this.slide,
  3325. width,
  3326. height,
  3327. content: this
  3328. });
  3329. }
  3330. }
  3331. }
  3332. /**
  3333. * @returns {boolean} If the content can be zoomed
  3334. */
  3335. isZoomable() {
  3336. return this.instance.applyFilters("isContentZoomable", this.isImageContent() && this.state !== LOAD_STATE.ERROR, this);
  3337. }
  3338. /**
  3339. * Update image srcset sizes attribute based on width and height
  3340. */
  3341. updateSrcsetSizes() {
  3342. if (!this.isImageContent() || !this.element || !this.data.srcset) {
  3343. return;
  3344. }
  3345. const image = (
  3346. /** @type HTMLImageElement */
  3347. this.element
  3348. );
  3349. const sizesWidth = this.instance.applyFilters("srcsetSizesWidth", this.displayedImageWidth, this);
  3350. if (!image.dataset.largestUsedSize || sizesWidth > parseInt(image.dataset.largestUsedSize, 10)) {
  3351. image.sizes = sizesWidth + "px";
  3352. image.dataset.largestUsedSize = String(sizesWidth);
  3353. }
  3354. }
  3355. /**
  3356. * @returns {boolean} If content should use a placeholder (from msrc by default)
  3357. */
  3358. usePlaceholder() {
  3359. return this.instance.applyFilters("useContentPlaceholder", this.isImageContent(), this);
  3360. }
  3361. /**
  3362. * Preload content with lazy-loading param
  3363. */
  3364. lazyLoad() {
  3365. if (this.instance.dispatch("contentLazyLoad", {
  3366. content: this
  3367. }).defaultPrevented) {
  3368. return;
  3369. }
  3370. this.load(true);
  3371. }
  3372. /**
  3373. * @returns {boolean} If placeholder should be kept after content is loaded
  3374. */
  3375. keepPlaceholder() {
  3376. return this.instance.applyFilters("isKeepingPlaceholder", this.isLoading(), this);
  3377. }
  3378. /**
  3379. * Destroy the content
  3380. */
  3381. destroy() {
  3382. this.hasSlide = false;
  3383. this.slide = void 0;
  3384. if (this.instance.dispatch("contentDestroy", {
  3385. content: this
  3386. }).defaultPrevented) {
  3387. return;
  3388. }
  3389. this.remove();
  3390. if (this.placeholder) {
  3391. this.placeholder.destroy();
  3392. this.placeholder = void 0;
  3393. }
  3394. if (this.isImageContent() && this.element) {
  3395. this.element.onload = null;
  3396. this.element.onerror = null;
  3397. this.element = void 0;
  3398. }
  3399. }
  3400. /**
  3401. * Display error message
  3402. */
  3403. displayError() {
  3404. if (this.slide) {
  3405. var _this$instance$option, _this$instance$option2;
  3406. let errorMsgEl = createElement("pswp__error-msg", "div");
  3407. 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 : "";
  3408. errorMsgEl = /** @type {HTMLDivElement} */
  3409. this.instance.applyFilters("contentErrorElement", errorMsgEl, this);
  3410. this.element = createElement("pswp__content pswp__error-msg-container", "div");
  3411. this.element.appendChild(errorMsgEl);
  3412. this.slide.container.innerText = "";
  3413. this.slide.container.appendChild(this.element);
  3414. this.slide.updateContentSize(true);
  3415. this.removePlaceholder();
  3416. }
  3417. }
  3418. /**
  3419. * Append the content
  3420. */
  3421. append() {
  3422. if (this.isAttached || !this.element) {
  3423. return;
  3424. }
  3425. this.isAttached = true;
  3426. if (this.state === LOAD_STATE.ERROR) {
  3427. this.displayError();
  3428. return;
  3429. }
  3430. if (this.instance.dispatch("contentAppend", {
  3431. content: this
  3432. }).defaultPrevented) {
  3433. return;
  3434. }
  3435. const supportsDecode = "decode" in this.element;
  3436. if (this.isImageContent()) {
  3437. if (supportsDecode && this.slide && (!this.slide.isActive || isSafari())) {
  3438. this.isDecoding = true;
  3439. this.element.decode().catch(() => {
  3440. }).finally(() => {
  3441. this.isDecoding = false;
  3442. this.appendImage();
  3443. });
  3444. } else {
  3445. this.appendImage();
  3446. }
  3447. } else if (this.slide && !this.element.parentNode) {
  3448. this.slide.container.appendChild(this.element);
  3449. }
  3450. }
  3451. /**
  3452. * Activate the slide,
  3453. * active slide is generally the current one,
  3454. * meaning the user can see it.
  3455. */
  3456. activate() {
  3457. if (this.instance.dispatch("contentActivate", {
  3458. content: this
  3459. }).defaultPrevented || !this.slide) {
  3460. return;
  3461. }
  3462. if (this.isImageContent() && this.isDecoding && !isSafari()) {
  3463. this.appendImage();
  3464. } else if (this.isError()) {
  3465. this.load(false, true);
  3466. }
  3467. if (this.slide.holderElement) {
  3468. this.slide.holderElement.setAttribute("aria-hidden", "false");
  3469. }
  3470. }
  3471. /**
  3472. * Deactivate the content
  3473. */
  3474. deactivate() {
  3475. this.instance.dispatch("contentDeactivate", {
  3476. content: this
  3477. });
  3478. if (this.slide && this.slide.holderElement) {
  3479. this.slide.holderElement.setAttribute("aria-hidden", "true");
  3480. }
  3481. }
  3482. /**
  3483. * Remove the content from DOM
  3484. */
  3485. remove() {
  3486. this.isAttached = false;
  3487. if (this.instance.dispatch("contentRemove", {
  3488. content: this
  3489. }).defaultPrevented) {
  3490. return;
  3491. }
  3492. if (this.element && this.element.parentNode) {
  3493. this.element.remove();
  3494. }
  3495. if (this.placeholder && this.placeholder.element) {
  3496. this.placeholder.element.remove();
  3497. }
  3498. }
  3499. /**
  3500. * Append the image content to slide container
  3501. */
  3502. appendImage() {
  3503. if (!this.isAttached) {
  3504. return;
  3505. }
  3506. if (this.instance.dispatch("contentAppendImage", {
  3507. content: this
  3508. }).defaultPrevented) {
  3509. return;
  3510. }
  3511. if (this.slide && this.element && !this.element.parentNode) {
  3512. this.slide.container.appendChild(this.element);
  3513. }
  3514. if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {
  3515. this.removePlaceholder();
  3516. }
  3517. }
  3518. };
  3519. var MIN_SLIDES_TO_CACHE = 5;
  3520. function lazyLoadData(itemData, instance, index) {
  3521. const content = instance.createContentFromData(itemData, index);
  3522. let zoomLevel;
  3523. const {
  3524. options
  3525. } = instance;
  3526. if (options) {
  3527. zoomLevel = new ZoomLevel(options, itemData, -1);
  3528. let viewportSize;
  3529. if (instance.pswp) {
  3530. viewportSize = instance.pswp.viewportSize;
  3531. } else {
  3532. viewportSize = getViewportSize(options, instance);
  3533. }
  3534. const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index);
  3535. zoomLevel.update(content.width, content.height, panAreaSize);
  3536. }
  3537. content.lazyLoad();
  3538. if (zoomLevel) {
  3539. content.setDisplayedSize(Math.ceil(content.width * zoomLevel.initial), Math.ceil(content.height * zoomLevel.initial));
  3540. }
  3541. return content;
  3542. }
  3543. function lazyLoadSlide(index, instance) {
  3544. const itemData = instance.getItemData(index);
  3545. if (instance.dispatch("lazyLoadSlide", {
  3546. index,
  3547. itemData
  3548. }).defaultPrevented) {
  3549. return;
  3550. }
  3551. return lazyLoadData(itemData, instance, index);
  3552. }
  3553. var ContentLoader = class {
  3554. /**
  3555. * @param {PhotoSwipe} pswp
  3556. */
  3557. constructor(pswp) {
  3558. this.pswp = pswp;
  3559. this.limit = Math.max(pswp.options.preload[0] + pswp.options.preload[1] + 1, MIN_SLIDES_TO_CACHE);
  3560. this._cachedItems = [];
  3561. }
  3562. /**
  3563. * Lazy load nearby slides based on `preload` option.
  3564. *
  3565. * @param {number} [diff] Difference between slide indexes that was changed recently, or 0.
  3566. */
  3567. updateLazy(diff) {
  3568. const {
  3569. pswp
  3570. } = this;
  3571. if (pswp.dispatch("lazyLoad").defaultPrevented) {
  3572. return;
  3573. }
  3574. const {
  3575. preload
  3576. } = pswp.options;
  3577. const isForward = diff === void 0 ? true : diff >= 0;
  3578. let i;
  3579. for (i = 0; i <= preload[1]; i++) {
  3580. this.loadSlideByIndex(pswp.currIndex + (isForward ? i : -i));
  3581. }
  3582. for (i = 1; i <= preload[0]; i++) {
  3583. this.loadSlideByIndex(pswp.currIndex + (isForward ? -i : i));
  3584. }
  3585. }
  3586. /**
  3587. * @param {number} initialIndex
  3588. */
  3589. loadSlideByIndex(initialIndex) {
  3590. const index = this.pswp.getLoopedIndex(initialIndex);
  3591. let content = this.getContentByIndex(index);
  3592. if (!content) {
  3593. content = lazyLoadSlide(index, this.pswp);
  3594. if (content) {
  3595. this.addToCache(content);
  3596. }
  3597. }
  3598. }
  3599. /**
  3600. * @param {Slide} slide
  3601. * @returns {Content}
  3602. */
  3603. getContentBySlide(slide) {
  3604. let content = this.getContentByIndex(slide.index);
  3605. if (!content) {
  3606. content = this.pswp.createContentFromData(slide.data, slide.index);
  3607. this.addToCache(content);
  3608. }
  3609. content.setSlide(slide);
  3610. return content;
  3611. }
  3612. /**
  3613. * @param {Content} content
  3614. */
  3615. addToCache(content) {
  3616. this.removeByIndex(content.index);
  3617. this._cachedItems.push(content);
  3618. if (this._cachedItems.length > this.limit) {
  3619. const indexToRemove = this._cachedItems.findIndex((item) => {
  3620. return !item.isAttached && !item.hasSlide;
  3621. });
  3622. if (indexToRemove !== -1) {
  3623. const removedItem = this._cachedItems.splice(indexToRemove, 1)[0];
  3624. removedItem.destroy();
  3625. }
  3626. }
  3627. }
  3628. /**
  3629. * Removes an image from cache, does not destroy() it, just removes.
  3630. *
  3631. * @param {number} index
  3632. */
  3633. removeByIndex(index) {
  3634. const indexToRemove = this._cachedItems.findIndex((item) => item.index === index);
  3635. if (indexToRemove !== -1) {
  3636. this._cachedItems.splice(indexToRemove, 1);
  3637. }
  3638. }
  3639. /**
  3640. * @param {number} index
  3641. * @returns {Content | undefined}
  3642. */
  3643. getContentByIndex(index) {
  3644. return this._cachedItems.find((content) => content.index === index);
  3645. }
  3646. destroy() {
  3647. this._cachedItems.forEach((content) => content.destroy());
  3648. this._cachedItems = [];
  3649. }
  3650. };
  3651. var PhotoSwipeBase = class extends Eventable {
  3652. /**
  3653. * Get total number of slides
  3654. *
  3655. * @returns {number}
  3656. */
  3657. getNumItems() {
  3658. var _this$options;
  3659. let numItems = 0;
  3660. const dataSource = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.dataSource;
  3661. if (dataSource && "length" in dataSource) {
  3662. numItems = dataSource.length;
  3663. } else if (dataSource && "gallery" in dataSource) {
  3664. if (!dataSource.items) {
  3665. dataSource.items = this._getGalleryDOMElements(dataSource.gallery);
  3666. }
  3667. if (dataSource.items) {
  3668. numItems = dataSource.items.length;
  3669. }
  3670. }
  3671. const event = this.dispatch("numItems", {
  3672. dataSource,
  3673. numItems
  3674. });
  3675. return this.applyFilters("numItems", event.numItems, dataSource);
  3676. }
  3677. /**
  3678. * @param {SlideData} slideData
  3679. * @param {number} index
  3680. * @returns {Content}
  3681. */
  3682. createContentFromData(slideData, index) {
  3683. return new Content(slideData, this, index);
  3684. }
  3685. /**
  3686. * Get item data by index.
  3687. *
  3688. * "item data" should contain normalized information that PhotoSwipe needs to generate a slide.
  3689. * For example, it may contain properties like
  3690. * `src`, `srcset`, `w`, `h`, which will be used to generate a slide with image.
  3691. *
  3692. * @param {number} index
  3693. * @returns {SlideData}
  3694. */
  3695. getItemData(index) {
  3696. var _this$options2;
  3697. const dataSource = (_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : _this$options2.dataSource;
  3698. let dataSourceItem = {};
  3699. if (Array.isArray(dataSource)) {
  3700. dataSourceItem = dataSource[index];
  3701. } else if (dataSource && "gallery" in dataSource) {
  3702. if (!dataSource.items) {
  3703. dataSource.items = this._getGalleryDOMElements(dataSource.gallery);
  3704. }
  3705. dataSourceItem = dataSource.items[index];
  3706. }
  3707. let itemData = dataSourceItem;
  3708. if (itemData instanceof Element) {
  3709. itemData = this._domElementToItemData(itemData);
  3710. }
  3711. const event = this.dispatch("itemData", {
  3712. itemData: itemData || {},
  3713. index
  3714. });
  3715. return this.applyFilters("itemData", event.itemData, index);
  3716. }
  3717. /**
  3718. * Get array of gallery DOM elements,
  3719. * based on childSelector and gallery element.
  3720. *
  3721. * @param {HTMLElement} galleryElement
  3722. * @returns {HTMLElement[]}
  3723. */
  3724. _getGalleryDOMElements(galleryElement) {
  3725. var _this$options3, _this$options4;
  3726. 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) {
  3727. return getElementsFromOption(this.options.children, this.options.childSelector, galleryElement) || [];
  3728. }
  3729. return [galleryElement];
  3730. }
  3731. /**
  3732. * Converts DOM element to item data object.
  3733. *
  3734. * @param {HTMLElement} element DOM element
  3735. * @returns {SlideData}
  3736. */
  3737. _domElementToItemData(element) {
  3738. const itemData = {
  3739. element
  3740. };
  3741. const linkEl = (
  3742. /** @type {HTMLAnchorElement} */
  3743. element.tagName === "A" ? element : element.querySelector("a")
  3744. );
  3745. if (linkEl) {
  3746. itemData.src = linkEl.dataset.pswpSrc || linkEl.href;
  3747. if (linkEl.dataset.pswpSrcset) {
  3748. itemData.srcset = linkEl.dataset.pswpSrcset;
  3749. }
  3750. itemData.width = linkEl.dataset.pswpWidth ? parseInt(linkEl.dataset.pswpWidth, 10) : 0;
  3751. itemData.height = linkEl.dataset.pswpHeight ? parseInt(linkEl.dataset.pswpHeight, 10) : 0;
  3752. itemData.w = itemData.width;
  3753. itemData.h = itemData.height;
  3754. if (linkEl.dataset.pswpType) {
  3755. itemData.type = linkEl.dataset.pswpType;
  3756. }
  3757. const thumbnailEl = element.querySelector("img");
  3758. if (thumbnailEl) {
  3759. var _thumbnailEl$getAttri;
  3760. itemData.msrc = thumbnailEl.currentSrc || thumbnailEl.src;
  3761. itemData.alt = (_thumbnailEl$getAttri = thumbnailEl.getAttribute("alt")) !== null && _thumbnailEl$getAttri !== void 0 ? _thumbnailEl$getAttri : "";
  3762. }
  3763. if (linkEl.dataset.pswpCropped || linkEl.dataset.cropped) {
  3764. itemData.thumbCropped = true;
  3765. }
  3766. }
  3767. return this.applyFilters("domItemData", itemData, element, linkEl);
  3768. }
  3769. /**
  3770. * Lazy-load by slide data
  3771. *
  3772. * @param {SlideData} itemData Data about the slide
  3773. * @param {number} index
  3774. * @returns {Content} Image that is being decoded or false.
  3775. */
  3776. lazyLoadData(itemData, index) {
  3777. return lazyLoadData(itemData, this, index);
  3778. }
  3779. };
  3780. var MIN_OPACITY = 3e-3;
  3781. var Opener = class {
  3782. /**
  3783. * @param {PhotoSwipe} pswp
  3784. */
  3785. constructor(pswp) {
  3786. this.pswp = pswp;
  3787. this.isClosed = true;
  3788. this.isOpen = false;
  3789. this.isClosing = false;
  3790. this.isOpening = false;
  3791. this._duration = void 0;
  3792. this._useAnimation = false;
  3793. this._croppedZoom = false;
  3794. this._animateRootOpacity = false;
  3795. this._animateBgOpacity = false;
  3796. this._placeholder = void 0;
  3797. this._opacityElement = void 0;
  3798. this._cropContainer1 = void 0;
  3799. this._cropContainer2 = void 0;
  3800. this._thumbBounds = void 0;
  3801. this._prepareOpen = this._prepareOpen.bind(this);
  3802. pswp.on("firstZoomPan", this._prepareOpen);
  3803. }
  3804. open() {
  3805. this._prepareOpen();
  3806. this._start();
  3807. }
  3808. close() {
  3809. if (this.isClosed || this.isClosing || this.isOpening) {
  3810. return;
  3811. }
  3812. const slide = this.pswp.currSlide;
  3813. this.isOpen = false;
  3814. this.isOpening = false;
  3815. this.isClosing = true;
  3816. this._duration = this.pswp.options.hideAnimationDuration;
  3817. if (slide && slide.currZoomLevel * slide.width >= this.pswp.options.maxWidthToAnimate) {
  3818. this._duration = 0;
  3819. }
  3820. this._applyStartProps();
  3821. setTimeout(() => {
  3822. this._start();
  3823. }, this._croppedZoom ? 30 : 0);
  3824. }
  3825. /** @private */
  3826. _prepareOpen() {
  3827. this.pswp.off("firstZoomPan", this._prepareOpen);
  3828. if (!this.isOpening) {
  3829. const slide = this.pswp.currSlide;
  3830. this.isOpening = true;
  3831. this.isClosing = false;
  3832. this._duration = this.pswp.options.showAnimationDuration;
  3833. if (slide && slide.zoomLevels.initial * slide.width >= this.pswp.options.maxWidthToAnimate) {
  3834. this._duration = 0;
  3835. }
  3836. this._applyStartProps();
  3837. }
  3838. }
  3839. /** @private */
  3840. _applyStartProps() {
  3841. const {
  3842. pswp
  3843. } = this;
  3844. const slide = this.pswp.currSlide;
  3845. const {
  3846. options
  3847. } = pswp;
  3848. if (options.showHideAnimationType === "fade") {
  3849. options.showHideOpacity = true;
  3850. this._thumbBounds = void 0;
  3851. } else if (options.showHideAnimationType === "none") {
  3852. options.showHideOpacity = false;
  3853. this._duration = 0;
  3854. this._thumbBounds = void 0;
  3855. } else if (this.isOpening && pswp._initialThumbBounds) {
  3856. this._thumbBounds = pswp._initialThumbBounds;
  3857. } else {
  3858. this._thumbBounds = this.pswp.getThumbBounds();
  3859. }
  3860. this._placeholder = slide === null || slide === void 0 ? void 0 : slide.getPlaceholderElement();
  3861. pswp.animations.stopAll();
  3862. this._useAnimation = Boolean(this._duration && this._duration > 50);
  3863. this._animateZoom = Boolean(this._thumbBounds) && (slide === null || slide === void 0 ? void 0 : slide.content.usePlaceholder()) && (!this.isClosing || !pswp.mainScroll.isShifted());
  3864. if (!this._animateZoom) {
  3865. this._animateRootOpacity = true;
  3866. if (this.isOpening && slide) {
  3867. slide.zoomAndPanToInitial();
  3868. slide.applyCurrentZoomPan();
  3869. }
  3870. } else {
  3871. var _options$showHideOpac;
  3872. this._animateRootOpacity = (_options$showHideOpac = options.showHideOpacity) !== null && _options$showHideOpac !== void 0 ? _options$showHideOpac : false;
  3873. }
  3874. this._animateBgOpacity = !this._animateRootOpacity && this.pswp.options.bgOpacity > MIN_OPACITY;
  3875. this._opacityElement = this._animateRootOpacity ? pswp.element : pswp.bg;
  3876. if (!this._useAnimation) {
  3877. this._duration = 0;
  3878. this._animateZoom = false;
  3879. this._animateBgOpacity = false;
  3880. this._animateRootOpacity = true;
  3881. if (this.isOpening) {
  3882. if (pswp.element) {
  3883. pswp.element.style.opacity = String(MIN_OPACITY);
  3884. }
  3885. pswp.applyBgOpacity(1);
  3886. }
  3887. return;
  3888. }
  3889. if (this._animateZoom && this._thumbBounds && this._thumbBounds.innerRect) {
  3890. var _this$pswp$currSlide;
  3891. this._croppedZoom = true;
  3892. this._cropContainer1 = this.pswp.container;
  3893. this._cropContainer2 = (_this$pswp$currSlide = this.pswp.currSlide) === null || _this$pswp$currSlide === void 0 ? void 0 : _this$pswp$currSlide.holderElement;
  3894. if (pswp.container) {
  3895. pswp.container.style.overflow = "hidden";
  3896. pswp.container.style.width = pswp.viewportSize.x + "px";
  3897. }
  3898. } else {
  3899. this._croppedZoom = false;
  3900. }
  3901. if (this.isOpening) {
  3902. if (this._animateRootOpacity) {
  3903. if (pswp.element) {
  3904. pswp.element.style.opacity = String(MIN_OPACITY);
  3905. }
  3906. pswp.applyBgOpacity(1);
  3907. } else {
  3908. if (this._animateBgOpacity && pswp.bg) {
  3909. pswp.bg.style.opacity = String(MIN_OPACITY);
  3910. }
  3911. if (pswp.element) {
  3912. pswp.element.style.opacity = "1";
  3913. }
  3914. }
  3915. if (this._animateZoom) {
  3916. this._setClosedStateZoomPan();
  3917. if (this._placeholder) {
  3918. this._placeholder.style.willChange = "transform";
  3919. this._placeholder.style.opacity = String(MIN_OPACITY);
  3920. }
  3921. }
  3922. } else if (this.isClosing) {
  3923. if (pswp.mainScroll.itemHolders[0]) {
  3924. pswp.mainScroll.itemHolders[0].el.style.display = "none";
  3925. }
  3926. if (pswp.mainScroll.itemHolders[2]) {
  3927. pswp.mainScroll.itemHolders[2].el.style.display = "none";
  3928. }
  3929. if (this._croppedZoom) {
  3930. if (pswp.mainScroll.x !== 0) {
  3931. pswp.mainScroll.resetPosition();
  3932. pswp.mainScroll.resize();
  3933. }
  3934. }
  3935. }
  3936. }
  3937. /** @private */
  3938. _start() {
  3939. if (this.isOpening && this._useAnimation && this._placeholder && this._placeholder.tagName === "IMG") {
  3940. new Promise((resolve) => {
  3941. let decoded = false;
  3942. let isDelaying = true;
  3943. decodeImage(
  3944. /** @type {HTMLImageElement} */
  3945. this._placeholder
  3946. ).finally(() => {
  3947. decoded = true;
  3948. if (!isDelaying) {
  3949. resolve(true);
  3950. }
  3951. });
  3952. setTimeout(() => {
  3953. isDelaying = false;
  3954. if (decoded) {
  3955. resolve(true);
  3956. }
  3957. }, 50);
  3958. setTimeout(resolve, 250);
  3959. }).finally(() => this._initiate());
  3960. } else {
  3961. this._initiate();
  3962. }
  3963. }
  3964. /** @private */
  3965. _initiate() {
  3966. var _this$pswp$element, _this$pswp$element2;
  3967. (_this$pswp$element = this.pswp.element) === null || _this$pswp$element === void 0 || _this$pswp$element.style.setProperty("--pswp-transition-duration", this._duration + "ms");
  3968. this.pswp.dispatch(this.isOpening ? "openingAnimationStart" : "closingAnimationStart");
  3969. this.pswp.dispatch(
  3970. /** @type {'initialZoomIn' | 'initialZoomOut'} */
  3971. "initialZoom" + (this.isOpening ? "In" : "Out")
  3972. );
  3973. (_this$pswp$element2 = this.pswp.element) === null || _this$pswp$element2 === void 0 || _this$pswp$element2.classList.toggle("pswp--ui-visible", this.isOpening);
  3974. if (this.isOpening) {
  3975. if (this._placeholder) {
  3976. this._placeholder.style.opacity = "1";
  3977. }
  3978. this._animateToOpenState();
  3979. } else if (this.isClosing) {
  3980. this._animateToClosedState();
  3981. }
  3982. if (!this._useAnimation) {
  3983. this._onAnimationComplete();
  3984. }
  3985. }
  3986. /** @private */
  3987. _onAnimationComplete() {
  3988. const {
  3989. pswp
  3990. } = this;
  3991. this.isOpen = this.isOpening;
  3992. this.isClosed = this.isClosing;
  3993. this.isOpening = false;
  3994. this.isClosing = false;
  3995. pswp.dispatch(this.isOpen ? "openingAnimationEnd" : "closingAnimationEnd");
  3996. pswp.dispatch(
  3997. /** @type {'initialZoomInEnd' | 'initialZoomOutEnd'} */
  3998. "initialZoom" + (this.isOpen ? "InEnd" : "OutEnd")
  3999. );
  4000. if (this.isClosed) {
  4001. pswp.destroy();
  4002. } else if (this.isOpen) {
  4003. var _pswp$currSlide;
  4004. if (this._animateZoom && pswp.container) {
  4005. pswp.container.style.overflow = "visible";
  4006. pswp.container.style.width = "100%";
  4007. }
  4008. (_pswp$currSlide = pswp.currSlide) === null || _pswp$currSlide === void 0 || _pswp$currSlide.applyCurrentZoomPan();
  4009. }
  4010. }
  4011. /** @private */
  4012. _animateToOpenState() {
  4013. const {
  4014. pswp
  4015. } = this;
  4016. if (this._animateZoom) {
  4017. if (this._croppedZoom && this._cropContainer1 && this._cropContainer2) {
  4018. this._animateTo(this._cropContainer1, "transform", "translate3d(0,0,0)");
  4019. this._animateTo(this._cropContainer2, "transform", "none");
  4020. }
  4021. if (pswp.currSlide) {
  4022. pswp.currSlide.zoomAndPanToInitial();
  4023. this._animateTo(pswp.currSlide.container, "transform", pswp.currSlide.getCurrentTransform());
  4024. }
  4025. }
  4026. if (this._animateBgOpacity && pswp.bg) {
  4027. this._animateTo(pswp.bg, "opacity", String(pswp.options.bgOpacity));
  4028. }
  4029. if (this._animateRootOpacity && pswp.element) {
  4030. this._animateTo(pswp.element, "opacity", "1");
  4031. }
  4032. }
  4033. /** @private */
  4034. _animateToClosedState() {
  4035. const {
  4036. pswp
  4037. } = this;
  4038. if (this._animateZoom) {
  4039. this._setClosedStateZoomPan(true);
  4040. }
  4041. if (this._animateBgOpacity && pswp.bgOpacity > 0.01 && pswp.bg) {
  4042. this._animateTo(pswp.bg, "opacity", "0");
  4043. }
  4044. if (this._animateRootOpacity && pswp.element) {
  4045. this._animateTo(pswp.element, "opacity", "0");
  4046. }
  4047. }
  4048. /**
  4049. * @private
  4050. * @param {boolean} [animate]
  4051. */
  4052. _setClosedStateZoomPan(animate) {
  4053. if (!this._thumbBounds)
  4054. return;
  4055. const {
  4056. pswp
  4057. } = this;
  4058. const {
  4059. innerRect
  4060. } = this._thumbBounds;
  4061. const {
  4062. currSlide,
  4063. viewportSize
  4064. } = pswp;
  4065. if (this._croppedZoom && innerRect && this._cropContainer1 && this._cropContainer2) {
  4066. const containerOnePanX = -viewportSize.x + (this._thumbBounds.x - innerRect.x) + innerRect.w;
  4067. const containerOnePanY = -viewportSize.y + (this._thumbBounds.y - innerRect.y) + innerRect.h;
  4068. const containerTwoPanX = viewportSize.x - innerRect.w;
  4069. const containerTwoPanY = viewportSize.y - innerRect.h;
  4070. if (animate) {
  4071. this._animateTo(this._cropContainer1, "transform", toTransformString(containerOnePanX, containerOnePanY));
  4072. this._animateTo(this._cropContainer2, "transform", toTransformString(containerTwoPanX, containerTwoPanY));
  4073. } else {
  4074. setTransform(this._cropContainer1, containerOnePanX, containerOnePanY);
  4075. setTransform(this._cropContainer2, containerTwoPanX, containerTwoPanY);
  4076. }
  4077. }
  4078. if (currSlide) {
  4079. equalizePoints(currSlide.pan, innerRect || this._thumbBounds);
  4080. currSlide.currZoomLevel = this._thumbBounds.w / currSlide.width;
  4081. if (animate) {
  4082. this._animateTo(currSlide.container, "transform", currSlide.getCurrentTransform());
  4083. } else {
  4084. currSlide.applyCurrentZoomPan();
  4085. }
  4086. }
  4087. }
  4088. /**
  4089. * @private
  4090. * @param {HTMLElement} target
  4091. * @param {'transform' | 'opacity'} prop
  4092. * @param {string} propValue
  4093. */
  4094. _animateTo(target, prop, propValue) {
  4095. if (!this._duration) {
  4096. target.style[prop] = propValue;
  4097. return;
  4098. }
  4099. const {
  4100. animations
  4101. } = this.pswp;
  4102. const animProps = {
  4103. duration: this._duration,
  4104. easing: this.pswp.options.easing,
  4105. onComplete: () => {
  4106. if (!animations.activeAnimations.length) {
  4107. this._onAnimationComplete();
  4108. }
  4109. },
  4110. target
  4111. };
  4112. animProps[prop] = propValue;
  4113. animations.startTransition(animProps);
  4114. }
  4115. };
  4116. var defaultOptions = {
  4117. allowPanToNext: true,
  4118. spacing: 0.1,
  4119. loop: true,
  4120. pinchToClose: true,
  4121. closeOnVerticalDrag: true,
  4122. hideAnimationDuration: 333,
  4123. showAnimationDuration: 333,
  4124. zoomAnimationDuration: 333,
  4125. escKey: true,
  4126. arrowKeys: true,
  4127. trapFocus: true,
  4128. returnFocus: true,
  4129. maxWidthToAnimate: 4e3,
  4130. clickToCloseNonZoomable: true,
  4131. imageClickAction: "zoom-or-close",
  4132. bgClickAction: "close",
  4133. tapAction: "toggle-controls",
  4134. doubleTapAction: "zoom",
  4135. indexIndicatorSep: " / ",
  4136. preloaderDelay: 2e3,
  4137. bgOpacity: 0.8,
  4138. index: 0,
  4139. errorMsg: "The image cannot be loaded",
  4140. preload: [1, 2],
  4141. easing: "cubic-bezier(.4,0,.22,1)"
  4142. };
  4143. var PhotoSwipe = class extends PhotoSwipeBase {
  4144. /**
  4145. * @param {PhotoSwipeOptions} [options]
  4146. */
  4147. constructor(options) {
  4148. super();
  4149. this.options = this._prepareOptions(options || {});
  4150. this.offset = {
  4151. x: 0,
  4152. y: 0
  4153. };
  4154. this._prevViewportSize = {
  4155. x: 0,
  4156. y: 0
  4157. };
  4158. this.viewportSize = {
  4159. x: 0,
  4160. y: 0
  4161. };
  4162. this.bgOpacity = 1;
  4163. this.currIndex = 0;
  4164. this.potentialIndex = 0;
  4165. this.isOpen = false;
  4166. this.isDestroying = false;
  4167. this.hasMouse = false;
  4168. this._initialItemData = {};
  4169. this._initialThumbBounds = void 0;
  4170. this.topBar = void 0;
  4171. this.element = void 0;
  4172. this.template = void 0;
  4173. this.container = void 0;
  4174. this.scrollWrap = void 0;
  4175. this.currSlide = void 0;
  4176. this.events = new DOMEvents();
  4177. this.animations = new Animations();
  4178. this.mainScroll = new MainScroll(this);
  4179. this.gestures = new Gestures(this);
  4180. this.opener = new Opener(this);
  4181. this.keyboard = new Keyboard(this);
  4182. this.contentLoader = new ContentLoader(this);
  4183. }
  4184. /** @returns {boolean} */
  4185. init() {
  4186. if (this.isOpen || this.isDestroying) {
  4187. return false;
  4188. }
  4189. this.isOpen = true;
  4190. this.dispatch("init");
  4191. this.dispatch("beforeOpen");
  4192. this._createMainStructure();
  4193. let rootClasses = "pswp--open";
  4194. if (this.gestures.supportsTouch) {
  4195. rootClasses += " pswp--touch";
  4196. }
  4197. if (this.options.mainClass) {
  4198. rootClasses += " " + this.options.mainClass;
  4199. }
  4200. if (this.element) {
  4201. this.element.className += " " + rootClasses;
  4202. }
  4203. this.currIndex = this.options.index || 0;
  4204. this.potentialIndex = this.currIndex;
  4205. this.dispatch("firstUpdate");
  4206. this.scrollWheel = new ScrollWheel(this);
  4207. if (Number.isNaN(this.currIndex) || this.currIndex < 0 || this.currIndex >= this.getNumItems()) {
  4208. this.currIndex = 0;
  4209. }
  4210. if (!this.gestures.supportsTouch) {
  4211. this.mouseDetected();
  4212. }
  4213. this.updateSize();
  4214. this.offset.y = window.pageYOffset;
  4215. this._initialItemData = this.getItemData(this.currIndex);
  4216. this.dispatch("gettingData", {
  4217. index: this.currIndex,
  4218. data: this._initialItemData,
  4219. slide: void 0
  4220. });
  4221. this._initialThumbBounds = this.getThumbBounds();
  4222. this.dispatch("initialLayout");
  4223. this.on("openingAnimationEnd", () => {
  4224. const {
  4225. itemHolders
  4226. } = this.mainScroll;
  4227. if (itemHolders[0]) {
  4228. itemHolders[0].el.style.display = "block";
  4229. this.setContent(itemHolders[0], this.currIndex - 1);
  4230. }
  4231. if (itemHolders[2]) {
  4232. itemHolders[2].el.style.display = "block";
  4233. this.setContent(itemHolders[2], this.currIndex + 1);
  4234. }
  4235. this.appendHeavy();
  4236. this.contentLoader.updateLazy();
  4237. this.events.add(window, "resize", this._handlePageResize.bind(this));
  4238. this.events.add(window, "scroll", this._updatePageScrollOffset.bind(this));
  4239. this.dispatch("bindEvents");
  4240. });
  4241. if (this.mainScroll.itemHolders[1]) {
  4242. this.setContent(this.mainScroll.itemHolders[1], this.currIndex);
  4243. }
  4244. this.dispatch("change");
  4245. this.opener.open();
  4246. this.dispatch("afterInit");
  4247. return true;
  4248. }
  4249. /**
  4250. * Get looped slide index
  4251. * (for example, -1 will return the last slide)
  4252. *
  4253. * @param {number} index
  4254. * @returns {number}
  4255. */
  4256. getLoopedIndex(index) {
  4257. const numSlides = this.getNumItems();
  4258. if (this.options.loop) {
  4259. if (index > numSlides - 1) {
  4260. index -= numSlides;
  4261. }
  4262. if (index < 0) {
  4263. index += numSlides;
  4264. }
  4265. }
  4266. return clamp(index, 0, numSlides - 1);
  4267. }
  4268. appendHeavy() {
  4269. this.mainScroll.itemHolders.forEach((itemHolder) => {
  4270. var _itemHolder$slide;
  4271. (_itemHolder$slide = itemHolder.slide) === null || _itemHolder$slide === void 0 || _itemHolder$slide.appendHeavy();
  4272. });
  4273. }
  4274. /**
  4275. * Change the slide
  4276. * @param {number} index New index
  4277. */
  4278. goTo(index) {
  4279. this.mainScroll.moveIndexBy(this.getLoopedIndex(index) - this.potentialIndex);
  4280. }
  4281. /**
  4282. * Go to the next slide.
  4283. */
  4284. next() {
  4285. this.goTo(this.potentialIndex + 1);
  4286. }
  4287. /**
  4288. * Go to the previous slide.
  4289. */
  4290. prev() {
  4291. this.goTo(this.potentialIndex - 1);
  4292. }
  4293. /**
  4294. * @see slide/slide.js zoomTo
  4295. *
  4296. * @param {Parameters<Slide['zoomTo']>} args
  4297. */
  4298. zoomTo(...args) {
  4299. var _this$currSlide;
  4300. (_this$currSlide = this.currSlide) === null || _this$currSlide === void 0 || _this$currSlide.zoomTo(...args);
  4301. }
  4302. /**
  4303. * @see slide/slide.js toggleZoom
  4304. */
  4305. toggleZoom() {
  4306. var _this$currSlide2;
  4307. (_this$currSlide2 = this.currSlide) === null || _this$currSlide2 === void 0 || _this$currSlide2.toggleZoom();
  4308. }
  4309. /**
  4310. * Close the gallery.
  4311. * After closing transition ends - destroy it
  4312. */
  4313. close() {
  4314. if (!this.opener.isOpen || this.isDestroying) {
  4315. return;
  4316. }
  4317. this.isDestroying = true;
  4318. this.dispatch("close");
  4319. this.events.removeAll();
  4320. this.opener.close();
  4321. }
  4322. /**
  4323. * Destroys the gallery:
  4324. * - instantly closes the gallery
  4325. * - unbinds events,
  4326. * - cleans intervals and timeouts
  4327. * - removes elements from DOM
  4328. */
  4329. destroy() {
  4330. var _this$element;
  4331. if (!this.isDestroying) {
  4332. this.options.showHideAnimationType = "none";
  4333. this.close();
  4334. return;
  4335. }
  4336. this.dispatch("destroy");
  4337. this._listeners = {};
  4338. if (this.scrollWrap) {
  4339. this.scrollWrap.ontouchmove = null;
  4340. this.scrollWrap.ontouchend = null;
  4341. }
  4342. (_this$element = this.element) === null || _this$element === void 0 || _this$element.remove();
  4343. this.mainScroll.itemHolders.forEach((itemHolder) => {
  4344. var _itemHolder$slide2;
  4345. (_itemHolder$slide2 = itemHolder.slide) === null || _itemHolder$slide2 === void 0 || _itemHolder$slide2.destroy();
  4346. });
  4347. this.contentLoader.destroy();
  4348. this.events.removeAll();
  4349. }
  4350. /**
  4351. * Refresh/reload content of a slide by its index
  4352. *
  4353. * @param {number} slideIndex
  4354. */
  4355. refreshSlideContent(slideIndex) {
  4356. this.contentLoader.removeByIndex(slideIndex);
  4357. this.mainScroll.itemHolders.forEach((itemHolder, i) => {
  4358. var _this$currSlide$index, _this$currSlide3;
  4359. let potentialHolderIndex = ((_this$currSlide$index = (_this$currSlide3 = this.currSlide) === null || _this$currSlide3 === void 0 ? void 0 : _this$currSlide3.index) !== null && _this$currSlide$index !== void 0 ? _this$currSlide$index : 0) - 1 + i;
  4360. if (this.canLoop()) {
  4361. potentialHolderIndex = this.getLoopedIndex(potentialHolderIndex);
  4362. }
  4363. if (potentialHolderIndex === slideIndex) {
  4364. this.setContent(itemHolder, slideIndex, true);
  4365. if (i === 1) {
  4366. var _itemHolder$slide3;
  4367. this.currSlide = itemHolder.slide;
  4368. (_itemHolder$slide3 = itemHolder.slide) === null || _itemHolder$slide3 === void 0 || _itemHolder$slide3.setIsActive(true);
  4369. }
  4370. }
  4371. });
  4372. this.dispatch("change");
  4373. }
  4374. /**
  4375. * Set slide content
  4376. *
  4377. * @param {ItemHolder} holder mainScroll.itemHolders array item
  4378. * @param {number} index Slide index
  4379. * @param {boolean} [force] If content should be set even if index wasn't changed
  4380. */
  4381. setContent(holder, index, force) {
  4382. if (this.canLoop()) {
  4383. index = this.getLoopedIndex(index);
  4384. }
  4385. if (holder.slide) {
  4386. if (holder.slide.index === index && !force) {
  4387. return;
  4388. }
  4389. holder.slide.destroy();
  4390. holder.slide = void 0;
  4391. }
  4392. if (!this.canLoop() && (index < 0 || index >= this.getNumItems())) {
  4393. return;
  4394. }
  4395. const itemData = this.getItemData(index);
  4396. holder.slide = new Slide(itemData, index, this);
  4397. if (index === this.currIndex) {
  4398. this.currSlide = holder.slide;
  4399. }
  4400. holder.slide.append(holder.el);
  4401. }
  4402. /** @returns {Point} */
  4403. getViewportCenterPoint() {
  4404. return {
  4405. x: this.viewportSize.x / 2,
  4406. y: this.viewportSize.y / 2
  4407. };
  4408. }
  4409. /**
  4410. * Update size of all elements.
  4411. * Executed on init and on page resize.
  4412. *
  4413. * @param {boolean} [force] Update size even if size of viewport was not changed.
  4414. */
  4415. updateSize(force) {
  4416. if (this.isDestroying) {
  4417. return;
  4418. }
  4419. const newViewportSize = getViewportSize(this.options, this);
  4420. if (!force && pointsEqual(newViewportSize, this._prevViewportSize)) {
  4421. return;
  4422. }
  4423. equalizePoints(this._prevViewportSize, newViewportSize);
  4424. this.dispatch("beforeResize");
  4425. equalizePoints(this.viewportSize, this._prevViewportSize);
  4426. this._updatePageScrollOffset();
  4427. this.dispatch("viewportSize");
  4428. this.mainScroll.resize(this.opener.isOpen);
  4429. if (!this.hasMouse && window.matchMedia("(any-hover: hover)").matches) {
  4430. this.mouseDetected();
  4431. }
  4432. this.dispatch("resize");
  4433. }
  4434. /**
  4435. * @param {number} opacity
  4436. */
  4437. applyBgOpacity(opacity) {
  4438. this.bgOpacity = Math.max(opacity, 0);
  4439. if (this.bg) {
  4440. this.bg.style.opacity = String(this.bgOpacity * this.options.bgOpacity);
  4441. }
  4442. }
  4443. /**
  4444. * Whether mouse is detected
  4445. */
  4446. mouseDetected() {
  4447. if (!this.hasMouse) {
  4448. var _this$element2;
  4449. this.hasMouse = true;
  4450. (_this$element2 = this.element) === null || _this$element2 === void 0 || _this$element2.classList.add("pswp--has_mouse");
  4451. }
  4452. }
  4453. /**
  4454. * Page resize event handler
  4455. *
  4456. * @private
  4457. */
  4458. _handlePageResize() {
  4459. this.updateSize();
  4460. if (/iPhone|iPad|iPod/i.test(window.navigator.userAgent)) {
  4461. setTimeout(() => {
  4462. this.updateSize();
  4463. }, 500);
  4464. }
  4465. }
  4466. /**
  4467. * Page scroll offset is used
  4468. * to get correct coordinates
  4469. * relative to PhotoSwipe viewport.
  4470. *
  4471. * @private
  4472. */
  4473. _updatePageScrollOffset() {
  4474. this.setScrollOffset(0, window.pageYOffset);
  4475. }
  4476. /**
  4477. * @param {number} x
  4478. * @param {number} y
  4479. */
  4480. setScrollOffset(x, y) {
  4481. this.offset.x = x;
  4482. this.offset.y = y;
  4483. this.dispatch("updateScrollOffset");
  4484. }
  4485. /**
  4486. * Create main HTML structure of PhotoSwipe,
  4487. * and add it to DOM
  4488. *
  4489. * @private
  4490. */
  4491. _createMainStructure() {
  4492. this.element = createElement("pswp", "div");
  4493. this.element.setAttribute("tabindex", "-1");
  4494. this.element.setAttribute("role", "dialog");
  4495. this.template = this.element;
  4496. this.bg = createElement("pswp__bg", "div", this.element);
  4497. this.scrollWrap = createElement("pswp__scroll-wrap", "section", this.element);
  4498. this.container = createElement("pswp__container", "div", this.scrollWrap);
  4499. this.scrollWrap.setAttribute("aria-roledescription", "carousel");
  4500. this.container.setAttribute("aria-live", "off");
  4501. this.container.setAttribute("id", "pswp__items");
  4502. this.mainScroll.appendHolders();
  4503. this.ui = new UI(this);
  4504. this.ui.init();
  4505. (this.options.appendToEl || document.body).appendChild(this.element);
  4506. }
  4507. /**
  4508. * Get position and dimensions of small thumbnail
  4509. * {x:,y:,w:}
  4510. *
  4511. * Height is optional (calculated based on the large image)
  4512. *
  4513. * @returns {Bounds | undefined}
  4514. */
  4515. getThumbBounds() {
  4516. return getThumbBounds(this.currIndex, this.currSlide ? this.currSlide.data : this._initialItemData, this);
  4517. }
  4518. /**
  4519. * If the PhotoSwipe can have continuous loop
  4520. * @returns Boolean
  4521. */
  4522. canLoop() {
  4523. return this.options.loop && this.getNumItems() > 2;
  4524. }
  4525. /**
  4526. * @private
  4527. * @param {PhotoSwipeOptions} options
  4528. * @returns {PreparedPhotoSwipeOptions}
  4529. */
  4530. _prepareOptions(options) {
  4531. if (window.matchMedia("(prefers-reduced-motion), (update: slow)").matches) {
  4532. options.showHideAnimationType = "none";
  4533. options.zoomAnimationDuration = 0;
  4534. }
  4535. return {
  4536. ...defaultOptions,
  4537. ...options
  4538. };
  4539. }
  4540. };
  4541. export {
  4542. PhotoSwipe as default
  4543. };
  4544. /*! Bundled license information:
  4545. photoswipe/dist/photoswipe.esm.js:
  4546. (*!
  4547. * PhotoSwipe 5.4.4 - https://photoswipe.com
  4548. * (c) 2024 Dmytro Semenov
  4549. *)
  4550. */
  4551. //# sourceMappingURL=photoswipe.js.map