Comparing version 3.1.1 to 4.0.0-rc.14
@@ -1,2 +0,2 @@ | ||
import t from"delegate-it";function e(){return e=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t},e.apply(this,arguments)}const n=(t,e)=>String(t).toLowerCase().replace(/[\s/_.]+/g,"-").replace(/[^\w-]+/g,"").replace(/--+/g,"-").replace(/^-+|-+$/g,"")||e||"",i=({hash:t}={})=>location.pathname+location.search+(t?location.hash:""),s=(t,n={})=>{const s=e({url:t=t||i({hash:!0}),random:Math.random(),source:"swup"},n);history.pushState(s,"",t)},o=(t=null,n={})=>{t=t||i({hash:!0});const s=e({},history.state,{url:t,random:Math.random(),source:"swup"},n);history.replaceState(s,"",t)},r=(e,n,i,s)=>{const o=new AbortController;return t(e,n,i,s),{destroy:()=>o.abort()}},a=(t,e=document)=>e.querySelector(t),l=(t,e=document)=>Array.from(e.querySelectorAll(t)),c=t=>{requestAnimationFrame(()=>{requestAnimationFrame(()=>{t()})})},u=t=>window.CSS&&window.CSS.escape?CSS.escape(t):t,h=t=>1e3*Number(t.slice(0,-1).replace(",",".")),d=(t,e)=>{var n,i;let s=document.createElement("html");s.innerHTML=t;let o=[];e.forEach(t=>{if(null==a(t,s))return console.warn(`[swup] Container ${t} not found on page.`),null;l(t).length!==l(t,s).length&&console.warn("[swup] Mismatched number of containers found on new page."),l(t).forEach((e,n)=>{l(t,s)[n].setAttribute("data-swup",String(o.length)),o.push(l(t,s)[n].outerHTML)})});const r=(null==(n=a("title",s))?void 0:n.innerText)||"",c=null==(i=a("body",s))?void 0:i.className;return s.innerHTML="",s=null,{title:r,pageClass:c,blocks:o,originalContent:t}},p=(t,n)=>{const i={url:window.location.pathname+window.location.search,method:"GET",data:null,headers:{}},{url:s,method:o,headers:r,data:a}=e({},i,t),l=new XMLHttpRequest;return l.onreadystatechange=function(){4===l.readyState&&n(l)},l.open(o,s,!0),Object.entries(r).forEach(([t,e])=>{l.setRequestHeader(t,e)}),l.send(a),l};class g extends URL{constructor(t,e=document.baseURI){super(t.toString(),e)}get url(){return this.pathname+this.search}static fromElement(t){const e=t.getAttribute("href")||t.getAttribute("xlink:href");return new g(e)}static fromUrl(t){return new g(t)}}const m=(t,e)=>{let n=0;e.forEach(e=>{null==a(e,t)?console.warn(`[swup] Container ${e} not found on page.`):l(e).forEach((i,s)=>{l(e,t)[s].setAttribute("data-swup",String(n)),n++})})},f=t=>/^to-/.test(t)||["is-changing","is-rendering","is-popstate"].includes(t),v=()=>{const t=document.documentElement.className.split(" ").filter(f);document.documentElement.classList.remove(...t)};class w{constructor(t){this.pages={},this.last=null,this.swup=void 0,this.swup=t}getCacheUrl(t){return this.swup.resolveUrl(g.fromUrl(t).url)}cacheUrl(t){t.url=this.getCacheUrl(t.url),t.url in this.pages==0&&(this.pages[t.url]=t),t.responseURL=this.getCacheUrl(t.responseURL),this.last=this.pages[t.url],this.swup.log(`Cache (${Object.keys(this.pages).length})`,this.pages)}getPage(t){return t=this.getCacheUrl(t),this.pages[t]}getCurrentPage(){return this.getPage(i())}exists(t){return(t=this.getCacheUrl(t))in this.pages}empty(){this.pages={},this.last=null,this.swup.log("Cache cleared")}remove(t){delete this.pages[this.getCacheUrl(t)]}}const E=function({event:t,skipTransition:e}={}){if(e)return this.triggerEvent("transitionEnd",t),this.cleanupAnimationClasses(),[Promise.resolve()];c(()=>{this.triggerEvent("animationInStart"),document.documentElement.classList.remove("is-animating")});const n=this.getAnimationPromises("in");return Promise.all(n).then(()=>{this.triggerEvent("animationInDone"),this.triggerEvent("transitionEnd",t),this.cleanupAnimationClasses()}),n},P=t=>{if(t&&"#"===t.charAt(0)&&(t=t.substring(1)),!t)return null;const e=decodeURIComponent(t);let n=document.getElementById(t)||document.getElementById(e)||a(`a[name='${u(t)}']`)||a(`a[name='${u(e)}']`);return n||"top"!==t||(n=document.body),n};let S="transition",b="transitionend",y="animation",U="animationend";function k(t){const e=this.options.animationSelector;if(!1===e)return[Promise.resolve()];const n=l(e,document.body);if(!n.length)return console.warn(`[swup] No elements found matching animationSelector \`${e}\``),[Promise.resolve()];const i=n.map(t=>function(t){const{type:e,timeout:n,propCount:i}=function(t,e=null){const n=window.getComputedStyle(t),i=`${S}Duration`,s=`${y}Delay`,o=`${y}Duration`,r=n[`${S}Delay`].split(", "),a=(n[i]||"").split(", "),l=L(r,a),c=(n[s]||"").split(", "),u=(n[o]||"").split(", "),h=L(c,u);let d="",p=0,g=0;return"transition"===e?l>0&&(d="transition",p=l,g=a.length):"animation"===e?h>0&&(d="animation",p=h,g=u.length):(p=Math.max(l,h),d=p>0?l>h?"transition":"animation":null,g=d?"transition"===d?a.length:u.length:0),{type:d,timeout:p,propCount:g}}(t);if(e&&n)return new Promise(s=>{const o="transition"===e?b:U,r=performance.now();let a=0;const l=()=>{t.removeEventListener(o,c),s()},c=e=>{if(e.target===t){if(!(t=>[b,U].includes(t.type))(e))throw new Error("Not a transition or animation event.");(performance.now()-r)/1e3<e.elapsedTime||++a>=i&&l()}};setTimeout(()=>{a<i&&l()},n+1),t.addEventListener(o,c)})}(t)).filter(Boolean);return i.length?i:(console.warn(`[swup] No CSS animation duration defined on elements matching \`${e}\``),[Promise.resolve()])}function L(t,e){for(;t.length<e.length;)t=t.concat(t);return Math.max(...e.map((e,n)=>h(e)+h(t[n])))}void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(S="WebkitTransition",b="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(y="WebkitAnimation",U="webkitAnimationEnd");const C=function(t){const n=d(t.responseText,this.options.containers);return n?e({},n,{responseURL:t.responseURL||window.location.href}):(console.warn("[swup] Received page is invalid."),null)};function T(t){const n=this.options.requestHeaders,{url:i}=t;return this.cache.exists(i)?(this.triggerEvent("pageRetrievedFromCache"),Promise.resolve(this.cache.getPage(i))):new Promise((s,o)=>{p(e({},t,{headers:n}),t=>{if(500===t.status)return this.triggerEvent("serverError"),void o(i);const n=this.getPageData(t);if(!n||!n.blocks.length)return void o(i);const r=e({},n,{url:i});this.cache.cacheUrl(r),this.triggerEvent("pageLoaded"),s(r)})})}const H=function({event:t,skipTransition:e}={}){const n=t instanceof PopStateEvent;if(e)return this.triggerEvent("animationSkipped"),[Promise.resolve()];this.triggerEvent("animationOutStart"),document.documentElement.classList.add("is-changing","is-leaving","is-animating"),n&&document.documentElement.classList.add("is-popstate");const i=this.getAnimationPromises("out");return Promise.all(i).then(()=>{this.triggerEvent("animationOutDone")}),i};function R(t){const{url:e}=t;this.shouldIgnoreVisit(e)?window.location.href=e:this.performPageLoad(t)}function A(t){const{url:e,event:r,customTransition:a,history:l="push"}=null!=t?t:{},c=r instanceof PopStateEvent,u=this.shouldSkipTransition({url:e,event:r});this.triggerEvent("transitionStart",r),this.updateTransition(i(),e,a),null!=a&&document.documentElement.classList.add(`to-${n(a)}`);const h=this.leavePage({event:r,skipTransition:u}),d=this.fetchPage(t);if(!c){const t=e+(this.scrollToElement||"");"replace"===l?o(t):s(t)}this.currentPageUrl=i(),Promise.all([d,...h]).then(([t])=>{this.renderPage(t,{event:r,skipTransition:u})}).catch(t=>{void 0!==t&&(this.options.skipPopStateHandling=()=>(window.location=t,!0),history.go(-1))})}const $=function({blocks:t,title:e}){return t.forEach((t,e)=>{document.body.querySelector(`[data-swup="${e}"]`).outerHTML=t}),document.title=e,Promise.resolve()};function _(t,e){const n=this._handlers[t];n?n.push(e):console.warn(`Unsupported event ${t}.`)}function I(t,e){if(t&&e){const n=this._handlers[t];n.includes(e)?this._handlers[t]=n.filter(t=>t!==e):console.warn(`Handler for event '${t}' not found.`)}else t?this._handlers[t]=[]:Object.keys(this._handlers).forEach(t=>{this._handlers[t]=[]})}function x(t,e){this._handlers[t].forEach(t=>{try{t(e)}catch(t){console.error(t)}});const n=new CustomEvent(`swup:${t}`,{detail:t});document.dispatchEvent(n)}const O=function(t){var e;if(null==(e=t)?void 0:e.isSwupPlugin){if(t.swup=this,!t._checkRequirements||t._checkRequirements())return t._beforeMount&&t._beforeMount(),t.mount(),this.plugins.push(t),this.plugins}else console.error("Not a swup plugin instance",t)};function q(t){const e=this.findPlugin(t);if(e)return e.unmount(),e._afterUnmount&&e._afterUnmount(),this.plugins=this.plugins.filter(t=>t!==e),this.plugins;console.error("No such plugin",e)}function D(t){return this.plugins.find(e=>e===t||e.name===t)}const M=function(t,{event:n,skipTransition:s}={}){if(document.documentElement.classList.remove("is-leaving"),!this.isSameResolvedUrl(i(),t.url))return;const{url:r}=g.fromUrl(t.responseURL);this.isSameResolvedUrl(i(),r)||(this.cache.cacheUrl(e({},t,{url:r})),this.currentPageUrl=i(),o(r)),s||document.documentElement.classList.add("is-rendering"),this.triggerEvent("willReplaceContent",n),this.replaceContent(t).then(()=>{this.triggerEvent("contentReplaced",n),this.triggerEvent("pageView",n),this.options.cache||this.cache.empty(),this.enterPage({event:n,skipTransition:s}),this.scrollToElement=null})};function N(t,e,n){this.transition={from:t,to:e,custom:n}}function W({event:t}){return!(!(t instanceof PopStateEvent)||this.options.animateHistoryBrowsing)}class V{constructor(t={}){this.version="3.1.1",this._handlers={animationInDone:[],animationInStart:[],animationOutDone:[],animationOutStart:[],animationSkipped:[],clickLink:[],contentReplaced:[],disabled:[],enabled:[],openPageInNewTab:[],pageLoaded:[],pageRetrievedFromCache:[],pageView:[],popState:[],samePage:[],samePageWithHash:[],serverError:[],transitionStart:[],transitionEnd:[],willReplaceContent:[]},this.scrollToElement=null,this.options=void 0,this.plugins=[],this.transition={},this.cache=void 0,this.currentPageUrl=i(),this.delegatedListeners={},this.boundPopStateHandler=void 0,this.loadPage=R,this.performPageLoad=A,this.leavePage=H,this.renderPage=M,this.replaceContent=$,this.enterPage=E,this.triggerEvent=x,this.delegateEvent=r,this.on=_,this.off=I,this.updateTransition=N,this.shouldSkipTransition=W,this.getAnimationPromises=k,this.getPageData=C,this.fetchPage=T,this.getAnchorElement=P,this.log=()=>{},this.use=O,this.unuse=q,this.findPlugin=D,this.getCurrentUrl=i,this.cleanupAnimationClasses=v,this.defaults={animateHistoryBrowsing:!1,animationSelector:'[class*="transition-"]',cache:!0,containers:["#swup"],ignoreVisit:(t,{el:e}={})=>!(null==e||!e.closest("[data-no-swup]")),linkSelector:"a[href]",plugins:[],resolveUrl:t=>t,requestHeaders:{"X-Requested-With":"swup",Accept:"text/html, application/xhtml+xml"},skipPopStateHandling:t=>{var e;return"swup"!==(null==(e=t.state)?void 0:e.source)}},this.options=e({},this.defaults,t),this.boundPopStateHandler=this.popStateHandler.bind(this),this.cache=new w(this),this.enable()}enable(){"undefined"!=typeof Promise?(this.delegatedListeners.click=r(this.options.linkSelector,"click",this.linkClickHandler.bind(this)),window.addEventListener("popstate",this.boundPopStateHandler),m(document.documentElement,this.options.containers),this.options.plugins.forEach(t=>this.use(t)),o(),this.triggerEvent("enabled"),document.documentElement.classList.add("swup-enabled"),this.triggerEvent("pageView")):console.warn("Promise is not supported")}destroy(){this.delegatedListeners.click.destroy(),window.removeEventListener("popstate",this.boundPopStateHandler),this.cache.empty(),this.options.plugins.forEach(t=>{this.unuse(t)}),l("[data-swup]").forEach(t=>{t.removeAttribute("data-swup")}),this.off(),this.triggerEvent("disabled"),document.documentElement.classList.remove("swup-enabled")}shouldIgnoreVisit(t,{el:e,event:n}={}){const{origin:i,url:s,hash:o}=g.fromUrl(t);return i!==window.location.origin||!(!e||!this.triggerWillOpenNewWindow(e))||!!this.options.ignoreVisit(s+o,{el:e,event:n})}linkClickHandler(t){const e=t.delegateTarget,{href:n,url:s,hash:o}=g.fromElement(e);if(this.shouldIgnoreVisit(n,{el:e,event:t}))return;if(t.metaKey||t.ctrlKey||t.shiftKey||t.altKey)return void this.triggerEvent("openPageInNewTab",t);if(0!==t.button)return;if(this.triggerEvent("clickLink",t),t.preventDefault(),!s||s===i())return void this.handleLinkToSamePage(s,o,t);if(this.isSameResolvedUrl(s,i()))return;this.scrollToElement=o||null;const r=e.getAttribute("data-swup-transition")||void 0;let a;const l=e.getAttribute("data-swup-history");l&&["push","replace"].includes(l)&&(a=l),this.performPageLoad({url:s,customTransition:r,history:a})}handleLinkToSamePage(t,e,n){if(e){if(this.triggerEvent("samePageWithHash",n),!P(e))return console.warn(`Element for offset not found (#${e})`);o(t+e)}else this.triggerEvent("samePage",n)}triggerWillOpenNewWindow(t){return!!t.matches('[download], [target="_blank"]')}popStateHandler(t){var e,n;if(this.options.skipPopStateHandling(t))return;if(this.isSameResolvedUrl(i(),this.currentPageUrl))return;const s=null!=(e=null==(n=t.state)?void 0:n.url)?e:location.href;if(this.shouldIgnoreVisit(s,{event:t}))return;const{url:o,hash:r}=g.fromUrl(s);r?this.scrollToElement=r:t.preventDefault(),this.triggerEvent("popState",t),this.options.animateHistoryBrowsing||(document.documentElement.classList.remove("is-animating"),v()),this.performPageLoad({url:o,event:t})}resolveUrl(t){if("function"!=typeof this.options.resolveUrl)return console.warn("[swup] options.resolveUrl expects a callback function."),t;const e=this.options.resolveUrl(t);return e&&"string"==typeof e?e.startsWith("//")||e.startsWith("http")?(console.warn("[swup] options.resolveUrl needs to return a relative url"),t):e:(console.warn("[swup] options.resolveUrl needs to return a url"),t)}isSameResolvedUrl(t,e){return this.resolveUrl(t)===this.resolveUrl(e)}}export{g as Location,n as classify,v as cleanupAnimationClasses,s as createHistoryRecord,V as default,r as delegateEvent,u as escapeCssIdentifier,p as fetch,i as getCurrentUrl,d as getDataFromHtml,m as markSwupElements,c as nextTick,a as query,l as queryAll,h as toMs,o as updateHistoryRecord}; | ||
import t from"delegate-it";function e(){return e=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t},e.apply(this,arguments)}const n=(t,e)=>String(t).toLowerCase().replace(/[\s/_.]+/g,"-").replace(/[^\w-]+/g,"").replace(/--+/g,"-").replace(/^-+|-+$/g,"")||e||"",i=({hash:t}={})=>location.pathname+location.search+(t?location.hash:""),s=(t,n={})=>{const s=e({url:t=t||i({hash:!0}),random:Math.random(),source:"swup"},n);history.pushState(s,"",t)},o=(t=null,n={})=>{t=t||i({hash:!0});const s=e({},history.state,{url:t,random:Math.random(),source:"swup"},n);history.replaceState(s,"",t)},r=["base"],a=(e,n,i,s={})=>{let{base:o=document}=s,a=function(t,e){if(null==t)return{};var n,i,s={},o=Object.keys(t);for(i=0;i<o.length;i++)e.indexOf(n=o[i])>=0||(s[n]=t[n]);return s}(s,r);const l=t(o,e,n,i,a);return{destroy:()=>l.destroy()}},l=(t,e=document)=>e.querySelector(t),u=(t,e=document)=>Array.from(e.querySelectorAll(t)),c=t=>{requestAnimationFrame(()=>{requestAnimationFrame(()=>{t()})})},h=t=>window.CSS&&window.CSS.escape?CSS.escape(t):t,d=t=>1e3*Number(t.slice(0,-1).replace(",",".")),p=(t,e)=>{var n,i;let s=document.createElement("html");s.innerHTML=t;let o=[];e.forEach(t=>{if(null==l(t,s))return console.warn(`[swup] Container ${t} not found on page.`),null;u(t).length!==u(t,s).length&&console.warn("[swup] Mismatched number of containers found on new page."),u(t).forEach((e,n)=>{u(t,s)[n].setAttribute("data-swup",String(o.length)),o.push(u(t,s)[n].outerHTML)})});const r=(null==(n=l("title",s))?void 0:n.innerText)||"",a=null==(i=l("body",s))?void 0:i.className;return s.innerHTML="",s=null,{title:r,pageClass:a,blocks:o,originalContent:t}},g=(t,n)=>{const i={url:window.location.pathname+window.location.search,method:"GET",data:null,headers:{}},{url:s,method:o,headers:r,data:a}=e({},i,t),l=new XMLHttpRequest;return l.onreadystatechange=function(){4===l.readyState&&n(l)},l.open(o,s,!0),Object.entries(r).forEach(([t,e])=>{l.setRequestHeader(t,e)}),l.send(a),l};class m extends URL{constructor(t,e=document.baseURI){super(t.toString(),e)}get url(){return this.pathname+this.search}static fromElement(t){const e=t.getAttribute("href")||t.getAttribute("xlink:href");return new m(e)}static fromUrl(t){return new m(t)}}const f=(t,e)=>{let n=0;e.forEach(e=>{null==l(e,t)?console.warn(`[swup] Container ${e} not found on page.`):u(e).forEach((i,s)=>{u(e,t)[s].setAttribute("data-swup",String(n)),n++})})},v=t=>/^to-/.test(t)||["is-changing","is-rendering","is-popstate"].includes(t),w=()=>{const t=document.documentElement.className.split(" ").filter(v);document.documentElement.classList.remove(...t)};class E{constructor(t){this.pages={},this.last=null,this.swup=void 0,this.swup=t}getCacheUrl(t){return this.swup.resolveUrl(m.fromUrl(t).url)}cacheUrl(t){t.url=this.getCacheUrl(t.url),t.url in this.pages==0&&(this.pages[t.url]=t),t.responseURL=this.getCacheUrl(t.responseURL),this.last=this.pages[t.url],this.swup.log(`Cache (${Object.keys(this.pages).length})`,this.pages)}getPage(t){return t=this.getCacheUrl(t),this.pages[t]}getCurrentPage(){return this.getPage(i())}exists(t){return(t=this.getCacheUrl(t))in this.pages}empty(){this.pages={},this.last=null,this.swup.log("Cache cleared")}remove(t){delete this.pages[this.getCacheUrl(t)]}}const P=function({event:t,skipTransition:e}={}){if(e)return this.triggerEvent("transitionEnd",t),this.cleanupAnimationClasses(),[Promise.resolve()];c(()=>{this.triggerEvent("animationInStart"),document.documentElement.classList.remove("is-animating")});const n=this.getAnimationPromises("in");return Promise.all(n).then(()=>{this.triggerEvent("animationInDone"),this.triggerEvent("transitionEnd",t),this.cleanupAnimationClasses()}),n},S=t=>t?("#"===t.charAt(0)&&(t=t.substring(1)),t=decodeURIComponent(t),t=h(t),l(`#${t}`)||l(`a[name='${t}']`)):null;let b="transition",k="transitionend",U="animation",y="animationend";function L(t){const e=this.options.animationSelector;if(!1===e)return[Promise.resolve()];const n=u(e,document.body);return n.length?n.map(t=>function(t,e,n=null){const{type:i,timeout:s,propCount:o}=function(t,e=null){const n=window.getComputedStyle(t),i=`${b}Duration`,s=`${U}Delay`,o=`${U}Duration`,r=n[`${b}Delay`].split(", "),a=(n[i]||"").split(", "),l=C(r,a),u=(n[s]||"").split(", "),c=(n[o]||"").split(", "),h=C(u,c);let d="",p=0,g=0;return"transition"===e?l>0&&(d="transition",p=l,g=a.length):"animation"===e?h>0&&(d="animation",p=h,g=c.length):(p=Math.max(l,h),d=p>0?l>h?"transition":"animation":null,g=d?"transition"===d?a.length:c.length:0),{type:d,timeout:p,propCount:g}}(t,n);return i&&s?new Promise(e=>{const n="transition"===i?k:y,r=performance.now();let a=0;const l=()=>{t.removeEventListener(n,u),e()},u=e=>{if(e.target===t){if(!(t=>!!t.elapsedTime)(e))throw new Error("Not a transition or animation event.");(performance.now()-r)/1e3<e.elapsedTime||++a>=o&&l()}};setTimeout(()=>{a<o&&l()},s+1),t.addEventListener(n,u)}):(console.warn(`[swup] No CSS transition duration defined for element of selector ${e}`),Promise.resolve())}(t,e)):(console.warn(`[swup] No animated elements found by selector ${e}`),[Promise.resolve()])}function C(t,e){for(;t.length<e.length;)t=t.concat(t);return Math.max(...e.map((e,n)=>d(e)+d(t[n])))}void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(b="WebkitTransition",k="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(U="WebkitAnimation",y="webkitAnimationEnd");const T=function(t){const n=p(t.responseText,this.options.containers);return n?e({},n,{responseURL:t.responseURL||window.location.href}):(console.warn("[swup] Received page is invalid."),null)};function H(t){const n=this.options.requestHeaders,{url:i}=t;return this.cache.exists(i)?(this.triggerEvent("pageRetrievedFromCache"),Promise.resolve(this.cache.getPage(i))):new Promise((s,o)=>{g(e({},t,{headers:n}),t=>{if(500===t.status)return this.triggerEvent("serverError"),void o(i);const n=this.getPageData(t);if(!n||!n.blocks.length)return void o(i);const r=e({},n,{url:i});this.cache.cacheUrl(r),this.triggerEvent("pageLoaded"),s(r)})})}const R=function({event:t,skipTransition:e}={}){const n=t instanceof PopStateEvent;if(e)return this.triggerEvent("animationSkipped"),[Promise.resolve()];this.triggerEvent("animationOutStart"),document.documentElement.classList.add("is-changing","is-leaving","is-animating"),n&&document.documentElement.classList.add("is-popstate");const i=this.getAnimationPromises("out");return Promise.all(i).then(()=>{this.triggerEvent("animationOutDone")}),i};function A(t){const{url:e}=t;this.shouldIgnoreVisit(e)?window.location.href=e:this.performPageLoad(t)}function $(t){const{url:e,event:o,customTransition:r}=null!=t?t:{},a=o instanceof PopStateEvent,l=this.shouldSkipTransition({url:e,event:o});this.triggerEvent("transitionStart",o),this.updateTransition(i(),e,r),null!=r&&document.documentElement.classList.add(`to-${n(r)}`);const u=this.leavePage({event:o,skipTransition:l}),c=this.fetchPage(t);a||s(e+(this.scrollToElement||"")),this.currentPageUrl=i(),Promise.all([c,...u]).then(([t])=>{this.renderPage(t,{event:o,skipTransition:l})}).catch(t=>{void 0!==t&&(this.options.skipPopStateHandling=()=>(window.location=t,!0),history.go(-1))})}const _=function({blocks:t,title:e}){return t.forEach((t,e)=>{document.body.querySelector(`[data-swup="${e}"]`).outerHTML=t}),document.title=e,Promise.resolve()};function O(t,e){const n=this._handlers[t];n?n.push(e):console.warn(`Unsupported event ${t}.`)}function x(t,e){if(t&&e){const n=this._handlers[t];n.includes(e)?this._handlers[t]=n.filter(t=>t!==e):console.warn(`Handler for event '${t}' not found.`)}else t?this._handlers[t]=[]:Object.keys(this._handlers).forEach(t=>{this._handlers[t]=[]})}function q(t,e){this._handlers[t].forEach(t=>{try{t(e)}catch(t){console.error(t)}});const n=new CustomEvent(`swup:${t}`,{detail:t});document.dispatchEvent(n)}const D=function(t){var e;if(null==(e=t)?void 0:e.isSwupPlugin){if(t.swup=this,!t._checkRequirements||t._checkRequirements())return t._beforeMount&&t._beforeMount(),t.mount(),this.plugins.push(t),this.plugins}else console.error("Not a swup plugin instance",t)};function I(t){const e=this.findPlugin(t);if(e)return e.unmount(),e._afterUnmount&&e._afterUnmount(),this.plugins=this.plugins.filter(t=>t!==e),this.plugins;console.error("No such plugin",e)}function M(t){return this.plugins.find(e=>e===t||e.name===t)}const N=function(t,{event:n,skipTransition:s}={}){if(document.documentElement.classList.remove("is-leaving"),!this.isSameResolvedUrl(i(),t.url))return;const{url:r}=m.fromUrl(t.responseURL);this.isSameResolvedUrl(i(),r)||(this.cache.cacheUrl(e({},t,{url:r})),this.currentPageUrl=i(),o(r)),s||document.documentElement.classList.add("is-rendering"),this.triggerEvent("willReplaceContent",n),this.replaceContent(t).then(()=>{this.triggerEvent("contentReplaced",n),this.triggerEvent("pageView",n),this.options.cache||this.cache.empty(),this.enterPage({event:n,skipTransition:s}),this.scrollToElement=null})};function W(t,e,n){this.transition={from:t,to:e,custom:n}}function V({event:t}){return!(!(t instanceof PopStateEvent)||this.options.animateHistoryBrowsing)}class j{constructor(t={}){this.version="3.0.4",this._handlers={animationInDone:[],animationInStart:[],animationOutDone:[],animationOutStart:[],animationSkipped:[],clickLink:[],contentReplaced:[],disabled:[],enabled:[],openPageInNewTab:[],pageLoaded:[],pageRetrievedFromCache:[],pageView:[],popState:[],samePage:[],samePageWithHash:[],serverError:[],transitionStart:[],transitionEnd:[],willReplaceContent:[]},this.scrollToElement=null,this.options=void 0,this.plugins=[],this.transition={},this.cache=void 0,this.currentPageUrl=i(),this.delegatedListeners={},this.boundPopStateHandler=void 0,this.loadPage=A,this.performPageLoad=$,this.leavePage=R,this.renderPage=N,this.replaceContent=_,this.enterPage=P,this.triggerEvent=q,this.delegateEvent=a,this.on=O,this.off=x,this.updateTransition=W,this.shouldSkipTransition=V,this.getAnimationPromises=L,this.getPageData=T,this.fetchPage=H,this.getAnchorElement=S,this.log=()=>{},this.use=D,this.unuse=I,this.findPlugin=M,this.getCurrentUrl=i,this.cleanupAnimationClasses=w,this.defaults={animateHistoryBrowsing:!1,animationSelector:'[class*="transition-"]',cache:!0,containers:["#swup"],ignoreVisit:(t,{el:e}={})=>!(null==e||!e.closest("[data-no-swup]")),linkSelector:"a[href]",plugins:[],resolveUrl:t=>t,requestHeaders:{"X-Requested-With":"swup",Accept:"text/html, application/xhtml+xml"},skipPopStateHandling:t=>{var e;return"swup"!==(null==(e=t.state)?void 0:e.source)}},this.options=e({},this.defaults,t),this.boundPopStateHandler=this.popStateHandler.bind(this),this.cache=new E(this),this.enable()}enable(){"undefined"!=typeof Promise?(this.delegatedListeners.click=a(this.options.linkSelector,"click",this.linkClickHandler.bind(this)),window.addEventListener("popstate",this.boundPopStateHandler),f(document.documentElement,this.options.containers),this.options.plugins.forEach(t=>this.use(t)),o(),this.triggerEvent("enabled"),document.documentElement.classList.add("swup-enabled"),this.triggerEvent("pageView")):console.warn("Promise is not supported")}destroy(){this.delegatedListeners.click.destroy(),window.removeEventListener("popstate",this.boundPopStateHandler),this.cache.empty(),this.options.plugins.forEach(t=>{this.unuse(t)}),u("[data-swup]").forEach(t=>{t.removeAttribute("data-swup")}),this.off(),this.triggerEvent("disabled"),document.documentElement.classList.remove("swup-enabled")}shouldIgnoreVisit(t,{el:e}={}){const{origin:n,url:i,hash:s}=m.fromUrl(t);return n!==window.location.origin||!(!e||!this.triggerWillOpenNewWindow(e))||!!this.options.ignoreVisit(i+s,{el:e})}linkClickHandler(t){const e=t.delegateTarget,{href:n,url:s,hash:o}=m.fromElement(e);if(this.shouldIgnoreVisit(n,{el:e}))return;if(t.metaKey||t.ctrlKey||t.shiftKey||t.altKey)return void this.triggerEvent("openPageInNewTab",t);if(0!==t.button)return;if(this.triggerEvent("clickLink",t),t.preventDefault(),!s||s===i())return void this.handleLinkToSamePage(s,o,t);if(this.isSameResolvedUrl(s,i()))return;this.scrollToElement=o||null;const r=e.getAttribute("data-swup-transition")||void 0;this.performPageLoad({url:s,customTransition:r})}handleLinkToSamePage(t,e,n){if(e){if(this.triggerEvent("samePageWithHash",n),!S(e))return console.warn(`Element for offset not found (#${e})`);o(t+e)}else this.triggerEvent("samePage",n)}triggerWillOpenNewWindow(t){return!!t.matches('[download], [target="_blank"]')}popStateHandler(t){var e,n;if(this.options.skipPopStateHandling(t))return;if(this.isSameResolvedUrl(i(),this.currentPageUrl))return;const s=null!=(e=null==(n=t.state)?void 0:n.url)?e:location.href;if(this.shouldIgnoreVisit(s))return;const{url:o,hash:r}=m.fromUrl(s);r?this.scrollToElement=r:t.preventDefault(),this.triggerEvent("popState",t),this.options.animateHistoryBrowsing||(document.documentElement.classList.remove("is-animating"),w()),this.performPageLoad({url:o,event:t})}resolveUrl(t){if("function"!=typeof this.options.resolveUrl)return console.warn("[swup] options.resolveUrl expects a callback function."),t;const e=this.options.resolveUrl(t);return e&&"string"==typeof e?e.startsWith("//")||e.startsWith("http")?(console.warn("[swup] options.resolveUrl needs to return a relative url"),t):e:(console.warn("[swup] options.resolveUrl needs to return a url"),t)}isSameResolvedUrl(t,e){return this.resolveUrl(t)===this.resolveUrl(e)}}export{m as Location,n as classify,w as cleanupAnimationClasses,s as createHistoryRecord,j as default,a as delegateEvent,h as escapeCssIdentifier,g as fetch,i as getCurrentUrl,p as getDataFromHtml,f as markSwupElements,c as nextTick,l as query,u as queryAll,d as toMs,o as updateHistoryRecord}; | ||
//# sourceMappingURL=Swup.modern.js.map |
@@ -1,2 +0,2 @@ | ||
import t from"delegate-it";const e=(t,e)=>String(t).toLowerCase().replace(/[\s/_.]+/g,"-").replace(/[^\w-]+/g,"").replace(/--+/g,"-").replace(/^-+|-+$/g,"")||e||"",n=function(t){let{hash:e}=void 0===t?{}:t;return location.pathname+location.search+(e?location.hash:"")},i=function(t,e){void 0===e&&(e={});const i={url:t=t||n({hash:!0}),random:Math.random(),source:"swup",...e};history.pushState(i,"",t)},s=function(t,e){void 0===t&&(t=null),void 0===e&&(e={}),t=t||n({hash:!0});const i={...history.state,url:t,random:Math.random(),source:"swup",...e};history.replaceState(i,"",t)},o=(e,n,i,s)=>{const o=new AbortController;return t(e,n,i,s),{destroy:()=>o.abort()}},r=function(t,e){return void 0===e&&(e=document),e.querySelector(t)},a=function(t,e){return void 0===e&&(e=document),Array.from(e.querySelectorAll(t))},l=t=>{requestAnimationFrame(()=>{requestAnimationFrame(()=>{t()})})},c=t=>window.CSS&&window.CSS.escape?CSS.escape(t):t,u=t=>1e3*Number(t.slice(0,-1).replace(",",".")),h=(t,e)=>{let n=document.createElement("html");n.innerHTML=t;let i=[];e.forEach(t=>{if(null==r(t,n))return console.warn(`[swup] Container ${t} not found on page.`),null;a(t).length!==a(t,n).length&&console.warn("[swup] Mismatched number of containers found on new page."),a(t).forEach((e,s)=>{a(t,n)[s].setAttribute("data-swup",String(i.length)),i.push(a(t,n)[s].outerHTML)})});const s=r("title",n)?.innerText||"",o=r("body",n)?.className;return n.innerHTML="",n=null,{title:s,pageClass:o,blocks:i,originalContent:t}},d=(t,e)=>{const n={url:window.location.pathname+window.location.search,method:"GET",data:null,headers:{}},{url:i,method:s,headers:o,data:r}={...n,...t},a=new XMLHttpRequest;return a.onreadystatechange=function(){4===a.readyState&&e(a)},a.open(s,i,!0),Object.entries(o).forEach(t=>{let[e,n]=t;a.setRequestHeader(e,n)}),a.send(r),a};class p extends URL{constructor(t,e){void 0===e&&(e=document.baseURI),super(t.toString(),e)}get url(){return this.pathname+this.search}static fromElement(t){const e=t.getAttribute("href")||t.getAttribute("xlink:href");return new p(e)}static fromUrl(t){return new p(t)}}const g=(t,e)=>{let n=0;e.forEach(e=>{null==r(e,t)?console.warn(`[swup] Container ${e} not found on page.`):a(e).forEach((i,s)=>{a(e,t)[s].setAttribute("data-swup",String(n)),n++})})},m=t=>/^to-/.test(t)||["is-changing","is-rendering","is-popstate"].includes(t),f=()=>{const t=document.documentElement.className.split(" ").filter(m);document.documentElement.classList.remove(...t)};class v{constructor(t){this.pages={},this.last=null,this.swup=void 0,this.swup=t}getCacheUrl(t){return this.swup.resolveUrl(p.fromUrl(t).url)}cacheUrl(t){t.url=this.getCacheUrl(t.url),t.url in this.pages==0&&(this.pages[t.url]=t),t.responseURL=this.getCacheUrl(t.responseURL),this.last=this.pages[t.url],this.swup.log(`Cache (${Object.keys(this.pages).length})`,this.pages)}getPage(t){return t=this.getCacheUrl(t),this.pages[t]}getCurrentPage(){return this.getPage(n())}exists(t){return(t=this.getCacheUrl(t))in this.pages}empty(){this.pages={},this.last=null,this.swup.log("Cache cleared")}remove(t){delete this.pages[this.getCacheUrl(t)]}}const w=function(t){let{event:e,skipTransition:n}=void 0===t?{}:t;if(n)return this.triggerEvent("transitionEnd",e),this.cleanupAnimationClasses(),[Promise.resolve()];l(()=>{this.triggerEvent("animationInStart"),document.documentElement.classList.remove("is-animating")});const i=this.getAnimationPromises("in");return Promise.all(i).then(()=>{this.triggerEvent("animationInDone"),this.triggerEvent("transitionEnd",e),this.cleanupAnimationClasses()}),i},E=t=>{if(t&&"#"===t.charAt(0)&&(t=t.substring(1)),!t)return null;const e=decodeURIComponent(t);let n=document.getElementById(t)||document.getElementById(e)||r(`a[name='${c(t)}']`)||r(`a[name='${c(e)}']`);return n||"top"!==t||(n=document.body),n};let P="transition",S="transitionend",b="animation",U="animationend";function k(t){const e=this.options.animationSelector;if(!1===e)return[Promise.resolve()];const n=a(e,document.body);if(!n.length)return console.warn(`[swup] No elements found matching animationSelector \`${e}\``),[Promise.resolve()];const i=n.map(t=>function(t){const{type:e,timeout:n,propCount:i}=function(t,e){void 0===e&&(e=null);const n=window.getComputedStyle(t),i=`${P}Duration`,s=`${b}Delay`,o=`${b}Duration`,r=n[`${P}Delay`].split(", "),a=(n[i]||"").split(", "),l=y(r,a),c=(n[s]||"").split(", "),u=(n[o]||"").split(", "),h=y(c,u);let d="",p=0,g=0;return"transition"===e?l>0&&(d="transition",p=l,g=a.length):"animation"===e?h>0&&(d="animation",p=h,g=u.length):(p=Math.max(l,h),d=p>0?l>h?"transition":"animation":null,g=d?"transition"===d?a.length:u.length:0),{type:d,timeout:p,propCount:g}}(t);if(e&&n)return new Promise(s=>{const o="transition"===e?S:U,r=performance.now();let a=0;const l=()=>{t.removeEventListener(o,c),s()},c=e=>{if(e.target===t){if(!(t=>[S,U].includes(t.type))(e))throw new Error("Not a transition or animation event.");(performance.now()-r)/1e3<e.elapsedTime||++a>=i&&l()}};setTimeout(()=>{a<i&&l()},n+1),t.addEventListener(o,c)})}(t)).filter(Boolean);return i.length?i:(console.warn(`[swup] No CSS animation duration defined on elements matching \`${e}\``),[Promise.resolve()])}function y(t,e){for(;t.length<e.length;)t=t.concat(t);return Math.max(...e.map((e,n)=>u(e)+u(t[n])))}void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(P="WebkitTransition",S="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(b="WebkitAnimation",U="webkitAnimationEnd");const L=function(t){const e=h(t.responseText,this.options.containers);return e?{...e,responseURL:t.responseURL||window.location.href}:(console.warn("[swup] Received page is invalid."),null)};function C(t){const e=this.options.requestHeaders,{url:n}=t;return this.cache.exists(n)?(this.triggerEvent("pageRetrievedFromCache"),Promise.resolve(this.cache.getPage(n))):new Promise((i,s)=>{d({...t,headers:e},t=>{if(500===t.status)return this.triggerEvent("serverError"),void s(n);const e=this.getPageData(t);if(!e||!e.blocks.length)return void s(n);const o={...e,url:n};this.cache.cacheUrl(o),this.triggerEvent("pageLoaded"),i(o)})})}const T=function(t){let{event:e,skipTransition:n}=void 0===t?{}:t;const i=e instanceof PopStateEvent;if(n)return this.triggerEvent("animationSkipped"),[Promise.resolve()];this.triggerEvent("animationOutStart"),document.documentElement.classList.add("is-changing","is-leaving","is-animating"),i&&document.documentElement.classList.add("is-popstate");const s=this.getAnimationPromises("out");return Promise.all(s).then(()=>{this.triggerEvent("animationOutDone")}),s};function H(t){const{url:e}=t;this.shouldIgnoreVisit(e)?window.location.href=e:this.performPageLoad(t)}function R(t){const{url:o,event:r,customTransition:a,history:l="push"}=t??{},c=r instanceof PopStateEvent,u=this.shouldSkipTransition({url:o,event:r});this.triggerEvent("transitionStart",r),this.updateTransition(n(),o,a),null!=a&&document.documentElement.classList.add(`to-${e(a)}`);const h=this.leavePage({event:r,skipTransition:u}),d=this.fetchPage(t);if(!c){const t=o+(this.scrollToElement||"");"replace"===l?s(t):i(t)}this.currentPageUrl=n(),Promise.all([d,...h]).then(t=>{let[e]=t;this.renderPage(e,{event:r,skipTransition:u})}).catch(t=>{void 0!==t&&(this.options.skipPopStateHandling=()=>(window.location=t,!0),history.go(-1))})}const A=function(t){let{blocks:e,title:n}=t;return e.forEach((t,e)=>{document.body.querySelector(`[data-swup="${e}"]`).outerHTML=t}),document.title=n,Promise.resolve()};function $(t,e){const n=this._handlers[t];n?n.push(e):console.warn(`Unsupported event ${t}.`)}function _(t,e){if(t&&e){const n=this._handlers[t];n.includes(e)?this._handlers[t]=n.filter(t=>t!==e):console.warn(`Handler for event '${t}' not found.`)}else t?this._handlers[t]=[]:Object.keys(this._handlers).forEach(t=>{this._handlers[t]=[]})}function I(t,e){this._handlers[t].forEach(t=>{try{t(e)}catch(t){console.error(t)}});const n=new CustomEvent(`swup:${t}`,{detail:t});document.dispatchEvent(n)}const x=function(t){if(t?.isSwupPlugin){if(t.swup=this,!t._checkRequirements||t._checkRequirements())return t._beforeMount&&t._beforeMount(),t.mount(),this.plugins.push(t),this.plugins}else console.error("Not a swup plugin instance",t)};function q(t){const e=this.findPlugin(t);if(e)return e.unmount(),e._afterUnmount&&e._afterUnmount(),this.plugins=this.plugins.filter(t=>t!==e),this.plugins;console.error("No such plugin",e)}function D(t){return this.plugins.find(e=>e===t||e.name===t)}const M=function(t,e){let{event:i,skipTransition:o}=void 0===e?{}:e;if(document.documentElement.classList.remove("is-leaving"),!this.isSameResolvedUrl(n(),t.url))return;const{url:r}=p.fromUrl(t.responseURL);this.isSameResolvedUrl(n(),r)||(this.cache.cacheUrl({...t,url:r}),this.currentPageUrl=n(),s(r)),o||document.documentElement.classList.add("is-rendering"),this.triggerEvent("willReplaceContent",i),this.replaceContent(t).then(()=>{this.triggerEvent("contentReplaced",i),this.triggerEvent("pageView",i),this.options.cache||this.cache.empty(),this.enterPage({event:i,skipTransition:o}),this.scrollToElement=null})};function N(t,e,n){this.transition={from:t,to:e,custom:n}}function W(t){let{event:e}=t;return!(!(e instanceof PopStateEvent)||this.options.animateHistoryBrowsing)}class O{constructor(t){void 0===t&&(t={}),this.version="3.1.1",this._handlers={animationInDone:[],animationInStart:[],animationOutDone:[],animationOutStart:[],animationSkipped:[],clickLink:[],contentReplaced:[],disabled:[],enabled:[],openPageInNewTab:[],pageLoaded:[],pageRetrievedFromCache:[],pageView:[],popState:[],samePage:[],samePageWithHash:[],serverError:[],transitionStart:[],transitionEnd:[],willReplaceContent:[]},this.scrollToElement=null,this.options=void 0,this.plugins=[],this.transition={},this.cache=void 0,this.currentPageUrl=n(),this.delegatedListeners={},this.boundPopStateHandler=void 0,this.loadPage=H,this.performPageLoad=R,this.leavePage=T,this.renderPage=M,this.replaceContent=A,this.enterPage=w,this.triggerEvent=I,this.delegateEvent=o,this.on=$,this.off=_,this.updateTransition=N,this.shouldSkipTransition=W,this.getAnimationPromises=k,this.getPageData=L,this.fetchPage=C,this.getAnchorElement=E,this.log=()=>{},this.use=x,this.unuse=q,this.findPlugin=D,this.getCurrentUrl=n,this.cleanupAnimationClasses=f,this.defaults={animateHistoryBrowsing:!1,animationSelector:'[class*="transition-"]',cache:!0,containers:["#swup"],ignoreVisit:function(t,e){let{el:n}=void 0===e?{}:e;return!!n?.closest("[data-no-swup]")},linkSelector:"a[href]",plugins:[],resolveUrl:t=>t,requestHeaders:{"X-Requested-With":"swup",Accept:"text/html, application/xhtml+xml"},skipPopStateHandling:t=>"swup"!==t.state?.source},this.options={...this.defaults,...t},this.boundPopStateHandler=this.popStateHandler.bind(this),this.cache=new v(this),this.enable()}enable(){"undefined"!=typeof Promise?(this.delegatedListeners.click=o(this.options.linkSelector,"click",this.linkClickHandler.bind(this)),window.addEventListener("popstate",this.boundPopStateHandler),g(document.documentElement,this.options.containers),this.options.plugins.forEach(t=>this.use(t)),s(),this.triggerEvent("enabled"),document.documentElement.classList.add("swup-enabled"),this.triggerEvent("pageView")):console.warn("Promise is not supported")}destroy(){this.delegatedListeners.click.destroy(),window.removeEventListener("popstate",this.boundPopStateHandler),this.cache.empty(),this.options.plugins.forEach(t=>{this.unuse(t)}),a("[data-swup]").forEach(t=>{t.removeAttribute("data-swup")}),this.off(),this.triggerEvent("disabled"),document.documentElement.classList.remove("swup-enabled")}shouldIgnoreVisit(t,e){let{el:n,event:i}=void 0===e?{}:e;const{origin:s,url:o,hash:r}=p.fromUrl(t);return s!==window.location.origin||!(!n||!this.triggerWillOpenNewWindow(n))||!!this.options.ignoreVisit(o+r,{el:n,event:i})}linkClickHandler(t){const e=t.delegateTarget,{href:i,url:s,hash:o}=p.fromElement(e);if(this.shouldIgnoreVisit(i,{el:e,event:t}))return;if(t.metaKey||t.ctrlKey||t.shiftKey||t.altKey)return void this.triggerEvent("openPageInNewTab",t);if(0!==t.button)return;if(this.triggerEvent("clickLink",t),t.preventDefault(),!s||s===n())return void this.handleLinkToSamePage(s,o,t);if(this.isSameResolvedUrl(s,n()))return;this.scrollToElement=o||null;const r=e.getAttribute("data-swup-transition")||void 0;let a;const l=e.getAttribute("data-swup-history");l&&["push","replace"].includes(l)&&(a=l),this.performPageLoad({url:s,customTransition:r,history:a})}handleLinkToSamePage(t,e,n){if(e){if(this.triggerEvent("samePageWithHash",n),!E(e))return console.warn(`Element for offset not found (#${e})`);s(t+e)}else this.triggerEvent("samePage",n)}triggerWillOpenNewWindow(t){return!!t.matches('[download], [target="_blank"]')}popStateHandler(t){if(this.options.skipPopStateHandling(t))return;if(this.isSameResolvedUrl(n(),this.currentPageUrl))return;const e=t.state?.url??location.href;if(this.shouldIgnoreVisit(e,{event:t}))return;const{url:i,hash:s}=p.fromUrl(e);s?this.scrollToElement=s:t.preventDefault(),this.triggerEvent("popState",t),this.options.animateHistoryBrowsing||(document.documentElement.classList.remove("is-animating"),f()),this.performPageLoad({url:i,event:t})}resolveUrl(t){if("function"!=typeof this.options.resolveUrl)return console.warn("[swup] options.resolveUrl expects a callback function."),t;const e=this.options.resolveUrl(t);return e&&"string"==typeof e?e.startsWith("//")||e.startsWith("http")?(console.warn("[swup] options.resolveUrl needs to return a relative url"),t):e:(console.warn("[swup] options.resolveUrl needs to return a url"),t)}isSameResolvedUrl(t,e){return this.resolveUrl(t)===this.resolveUrl(e)}}export{p as Location,e as classify,f as cleanupAnimationClasses,i as createHistoryRecord,O as default,o as delegateEvent,c as escapeCssIdentifier,d as fetch,n as getCurrentUrl,h as getDataFromHtml,g as markSwupElements,l as nextTick,r as query,a as queryAll,u as toMs,s as updateHistoryRecord}; | ||
import t from"delegate-it";const e=(t,e)=>String(t).toLowerCase().replace(/[\s/_.]+/g,"-").replace(/[^\w-]+/g,"").replace(/--+/g,"-").replace(/^-+|-+$/g,"")||e||"",n=function(t){let{hash:e}=void 0===t?{}:t;return location.pathname+location.search+(e?location.hash:"")},i=function(t,e){void 0===e&&(e={});const i={url:t=t||n({hash:!0}),random:Math.random(),source:"swup",...e};history.pushState(i,"",t)},s=function(t,e){void 0===t&&(t=null),void 0===e&&(e={}),t=t||n({hash:!0});const i={...history.state,url:t,random:Math.random(),source:"swup",...e};history.replaceState(i,"",t)},o=function(e,n,i,s){let{base:o=document,...r}=void 0===s?{}:s;const a=t(o,e,n,i,r);return{destroy:()=>a.destroy()}},r=function(t,e){return void 0===e&&(e=document),e.querySelector(t)},a=function(t,e){return void 0===e&&(e=document),Array.from(e.querySelectorAll(t))},l=t=>{requestAnimationFrame(()=>{requestAnimationFrame(()=>{t()})})},c=t=>window.CSS&&window.CSS.escape?CSS.escape(t):t,u=t=>1e3*Number(t.slice(0,-1).replace(",",".")),h=(t,e)=>{let n=document.createElement("html");n.innerHTML=t;let i=[];e.forEach(t=>{if(null==r(t,n))return console.warn(`[swup] Container ${t} not found on page.`),null;a(t).length!==a(t,n).length&&console.warn("[swup] Mismatched number of containers found on new page."),a(t).forEach((e,s)=>{a(t,n)[s].setAttribute("data-swup",String(i.length)),i.push(a(t,n)[s].outerHTML)})});const s=r("title",n)?.innerText||"",o=r("body",n)?.className;return n.innerHTML="",n=null,{title:s,pageClass:o,blocks:i,originalContent:t}},d=(t,e)=>{const n={url:window.location.pathname+window.location.search,method:"GET",data:null,headers:{}},{url:i,method:s,headers:o,data:r}={...n,...t},a=new XMLHttpRequest;return a.onreadystatechange=function(){4===a.readyState&&e(a)},a.open(s,i,!0),Object.entries(o).forEach(t=>{let[e,n]=t;a.setRequestHeader(e,n)}),a.send(r),a};class p extends URL{constructor(t,e){void 0===e&&(e=document.baseURI),super(t.toString(),e)}get url(){return this.pathname+this.search}static fromElement(t){const e=t.getAttribute("href")||t.getAttribute("xlink:href");return new p(e)}static fromUrl(t){return new p(t)}}const g=(t,e)=>{let n=0;e.forEach(e=>{null==r(e,t)?console.warn(`[swup] Container ${e} not found on page.`):a(e).forEach((i,s)=>{a(e,t)[s].setAttribute("data-swup",String(n)),n++})})},m=t=>/^to-/.test(t)||["is-changing","is-rendering","is-popstate"].includes(t),f=()=>{const t=document.documentElement.className.split(" ").filter(m);document.documentElement.classList.remove(...t)};class v{constructor(t){this.pages={},this.last=null,this.swup=void 0,this.swup=t}getCacheUrl(t){return this.swup.resolveUrl(p.fromUrl(t).url)}cacheUrl(t){t.url=this.getCacheUrl(t.url),t.url in this.pages==0&&(this.pages[t.url]=t),t.responseURL=this.getCacheUrl(t.responseURL),this.last=this.pages[t.url],this.swup.log(`Cache (${Object.keys(this.pages).length})`,this.pages)}getPage(t){return t=this.getCacheUrl(t),this.pages[t]}getCurrentPage(){return this.getPage(n())}exists(t){return(t=this.getCacheUrl(t))in this.pages}empty(){this.pages={},this.last=null,this.swup.log("Cache cleared")}remove(t){delete this.pages[this.getCacheUrl(t)]}}const w=function(t){let{event:e,skipTransition:n}=void 0===t?{}:t;if(n)return this.triggerEvent("transitionEnd",e),this.cleanupAnimationClasses(),[Promise.resolve()];l(()=>{this.triggerEvent("animationInStart"),document.documentElement.classList.remove("is-animating")});const i=this.getAnimationPromises("in");return Promise.all(i).then(()=>{this.triggerEvent("animationInDone"),this.triggerEvent("transitionEnd",e),this.cleanupAnimationClasses()}),i},E=t=>t?("#"===t.charAt(0)&&(t=t.substring(1)),t=decodeURIComponent(t),t=c(t),r(`#${t}`)||r(`a[name='${t}']`)):null;let P="transition",S="transitionend",b="animation",U="animationend";function k(t){const e=this.options.animationSelector;if(!1===e)return[Promise.resolve()];const n=a(e,document.body);return n.length?n.map(t=>function(t,e,n){void 0===n&&(n=null);const{type:i,timeout:s,propCount:o}=function(t,e){void 0===e&&(e=null);const n=window.getComputedStyle(t),i=`${P}Duration`,s=`${b}Delay`,o=`${b}Duration`,r=n[`${P}Delay`].split(", "),a=(n[i]||"").split(", "),l=y(r,a),c=(n[s]||"").split(", "),u=(n[o]||"").split(", "),h=y(c,u);let d="",p=0,g=0;return"transition"===e?l>0&&(d="transition",p=l,g=a.length):"animation"===e?h>0&&(d="animation",p=h,g=u.length):(p=Math.max(l,h),d=p>0?l>h?"transition":"animation":null,g=d?"transition"===d?a.length:u.length:0),{type:d,timeout:p,propCount:g}}(t,n);return i&&s?new Promise(e=>{const n="transition"===i?S:U,r=performance.now();let a=0;const l=()=>{t.removeEventListener(n,c),e()},c=e=>{if(e.target===t){if(!(t=>!!t.elapsedTime)(e))throw new Error("Not a transition or animation event.");(performance.now()-r)/1e3<e.elapsedTime||++a>=o&&l()}};setTimeout(()=>{a<o&&l()},s+1),t.addEventListener(n,c)}):(console.warn(`[swup] No CSS transition duration defined for element of selector ${e}`),Promise.resolve())}(t,e)):(console.warn(`[swup] No animated elements found by selector ${e}`),[Promise.resolve()])}function y(t,e){for(;t.length<e.length;)t=t.concat(t);return Math.max(...e.map((e,n)=>u(e)+u(t[n])))}void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(P="WebkitTransition",S="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(b="WebkitAnimation",U="webkitAnimationEnd");const L=function(t){const e=h(t.responseText,this.options.containers);return e?{...e,responseURL:t.responseURL||window.location.href}:(console.warn("[swup] Received page is invalid."),null)};function C(t){const e=this.options.requestHeaders,{url:n}=t;return this.cache.exists(n)?(this.triggerEvent("pageRetrievedFromCache"),Promise.resolve(this.cache.getPage(n))):new Promise((i,s)=>{d({...t,headers:e},t=>{if(500===t.status)return this.triggerEvent("serverError"),void s(n);const e=this.getPageData(t);if(!e||!e.blocks.length)return void s(n);const o={...e,url:n};this.cache.cacheUrl(o),this.triggerEvent("pageLoaded"),i(o)})})}const T=function(t){let{event:e,skipTransition:n}=void 0===t?{}:t;const i=e instanceof PopStateEvent;if(n)return this.triggerEvent("animationSkipped"),[Promise.resolve()];this.triggerEvent("animationOutStart"),document.documentElement.classList.add("is-changing","is-leaving","is-animating"),i&&document.documentElement.classList.add("is-popstate");const s=this.getAnimationPromises("out");return Promise.all(s).then(()=>{this.triggerEvent("animationOutDone")}),s};function H(t){const{url:e}=t;this.shouldIgnoreVisit(e)?window.location.href=e:this.performPageLoad(t)}function R(t){const{url:s,event:o,customTransition:r}=t??{},a=o instanceof PopStateEvent,l=this.shouldSkipTransition({url:s,event:o});this.triggerEvent("transitionStart",o),this.updateTransition(n(),s,r),null!=r&&document.documentElement.classList.add(`to-${e(r)}`);const c=this.leavePage({event:o,skipTransition:l}),u=this.fetchPage(t);a||i(s+(this.scrollToElement||"")),this.currentPageUrl=n(),Promise.all([u,...c]).then(t=>{let[e]=t;this.renderPage(e,{event:o,skipTransition:l})}).catch(t=>{void 0!==t&&(this.options.skipPopStateHandling=()=>(window.location=t,!0),history.go(-1))})}const A=function(t){let{blocks:e,title:n}=t;return e.forEach((t,e)=>{document.body.querySelector(`[data-swup="${e}"]`).outerHTML=t}),document.title=n,Promise.resolve()};function $(t,e){const n=this._handlers[t];n?n.push(e):console.warn(`Unsupported event ${t}.`)}function _(t,e){if(t&&e){const n=this._handlers[t];n.includes(e)?this._handlers[t]=n.filter(t=>t!==e):console.warn(`Handler for event '${t}' not found.`)}else t?this._handlers[t]=[]:Object.keys(this._handlers).forEach(t=>{this._handlers[t]=[]})}function x(t,e){this._handlers[t].forEach(t=>{try{t(e)}catch(t){console.error(t)}});const n=new CustomEvent(`swup:${t}`,{detail:t});document.dispatchEvent(n)}const q=function(t){if(t?.isSwupPlugin){if(t.swup=this,!t._checkRequirements||t._checkRequirements())return t._beforeMount&&t._beforeMount(),t.mount(),this.plugins.push(t),this.plugins}else console.error("Not a swup plugin instance",t)};function D(t){const e=this.findPlugin(t);if(e)return e.unmount(),e._afterUnmount&&e._afterUnmount(),this.plugins=this.plugins.filter(t=>t!==e),this.plugins;console.error("No such plugin",e)}function I(t){return this.plugins.find(e=>e===t||e.name===t)}const M=function(t,e){let{event:i,skipTransition:o}=void 0===e?{}:e;if(document.documentElement.classList.remove("is-leaving"),!this.isSameResolvedUrl(n(),t.url))return;const{url:r}=p.fromUrl(t.responseURL);this.isSameResolvedUrl(n(),r)||(this.cache.cacheUrl({...t,url:r}),this.currentPageUrl=n(),s(r)),o||document.documentElement.classList.add("is-rendering"),this.triggerEvent("willReplaceContent",i),this.replaceContent(t).then(()=>{this.triggerEvent("contentReplaced",i),this.triggerEvent("pageView",i),this.options.cache||this.cache.empty(),this.enterPage({event:i,skipTransition:o}),this.scrollToElement=null})};function N(t,e,n){this.transition={from:t,to:e,custom:n}}function W(t){let{event:e}=t;return!(!(e instanceof PopStateEvent)||this.options.animateHistoryBrowsing)}class O{constructor(t){void 0===t&&(t={}),this.version="3.0.4",this._handlers={animationInDone:[],animationInStart:[],animationOutDone:[],animationOutStart:[],animationSkipped:[],clickLink:[],contentReplaced:[],disabled:[],enabled:[],openPageInNewTab:[],pageLoaded:[],pageRetrievedFromCache:[],pageView:[],popState:[],samePage:[],samePageWithHash:[],serverError:[],transitionStart:[],transitionEnd:[],willReplaceContent:[]},this.scrollToElement=null,this.options=void 0,this.plugins=[],this.transition={},this.cache=void 0,this.currentPageUrl=n(),this.delegatedListeners={},this.boundPopStateHandler=void 0,this.loadPage=H,this.performPageLoad=R,this.leavePage=T,this.renderPage=M,this.replaceContent=A,this.enterPage=w,this.triggerEvent=x,this.delegateEvent=o,this.on=$,this.off=_,this.updateTransition=N,this.shouldSkipTransition=W,this.getAnimationPromises=k,this.getPageData=L,this.fetchPage=C,this.getAnchorElement=E,this.log=()=>{},this.use=q,this.unuse=D,this.findPlugin=I,this.getCurrentUrl=n,this.cleanupAnimationClasses=f,this.defaults={animateHistoryBrowsing:!1,animationSelector:'[class*="transition-"]',cache:!0,containers:["#swup"],ignoreVisit:function(t,e){let{el:n}=void 0===e?{}:e;return!!n?.closest("[data-no-swup]")},linkSelector:"a[href]",plugins:[],resolveUrl:t=>t,requestHeaders:{"X-Requested-With":"swup",Accept:"text/html, application/xhtml+xml"},skipPopStateHandling:t=>"swup"!==t.state?.source},this.options={...this.defaults,...t},this.boundPopStateHandler=this.popStateHandler.bind(this),this.cache=new v(this),this.enable()}enable(){"undefined"!=typeof Promise?(this.delegatedListeners.click=o(this.options.linkSelector,"click",this.linkClickHandler.bind(this)),window.addEventListener("popstate",this.boundPopStateHandler),g(document.documentElement,this.options.containers),this.options.plugins.forEach(t=>this.use(t)),s(),this.triggerEvent("enabled"),document.documentElement.classList.add("swup-enabled"),this.triggerEvent("pageView")):console.warn("Promise is not supported")}destroy(){this.delegatedListeners.click.destroy(),window.removeEventListener("popstate",this.boundPopStateHandler),this.cache.empty(),this.options.plugins.forEach(t=>{this.unuse(t)}),a("[data-swup]").forEach(t=>{t.removeAttribute("data-swup")}),this.off(),this.triggerEvent("disabled"),document.documentElement.classList.remove("swup-enabled")}shouldIgnoreVisit(t,e){let{el:n}=void 0===e?{}:e;const{origin:i,url:s,hash:o}=p.fromUrl(t);return i!==window.location.origin||!(!n||!this.triggerWillOpenNewWindow(n))||!!this.options.ignoreVisit(s+o,{el:n})}linkClickHandler(t){const e=t.delegateTarget,{href:i,url:s,hash:o}=p.fromElement(e);if(this.shouldIgnoreVisit(i,{el:e}))return;if(t.metaKey||t.ctrlKey||t.shiftKey||t.altKey)return void this.triggerEvent("openPageInNewTab",t);if(0!==t.button)return;if(this.triggerEvent("clickLink",t),t.preventDefault(),!s||s===n())return void this.handleLinkToSamePage(s,o,t);if(this.isSameResolvedUrl(s,n()))return;this.scrollToElement=o||null;const r=e.getAttribute("data-swup-transition")||void 0;this.performPageLoad({url:s,customTransition:r})}handleLinkToSamePage(t,e,n){if(e){if(this.triggerEvent("samePageWithHash",n),!E(e))return console.warn(`Element for offset not found (#${e})`);s(t+e)}else this.triggerEvent("samePage",n)}triggerWillOpenNewWindow(t){return!!t.matches('[download], [target="_blank"]')}popStateHandler(t){if(this.options.skipPopStateHandling(t))return;if(this.isSameResolvedUrl(n(),this.currentPageUrl))return;const e=t.state?.url??location.href;if(this.shouldIgnoreVisit(e))return;const{url:i,hash:s}=p.fromUrl(e);s?this.scrollToElement=s:t.preventDefault(),this.triggerEvent("popState",t),this.options.animateHistoryBrowsing||(document.documentElement.classList.remove("is-animating"),f()),this.performPageLoad({url:i,event:t})}resolveUrl(t){if("function"!=typeof this.options.resolveUrl)return console.warn("[swup] options.resolveUrl expects a callback function."),t;const e=this.options.resolveUrl(t);return e&&"string"==typeof e?e.startsWith("//")||e.startsWith("http")?(console.warn("[swup] options.resolveUrl needs to return a relative url"),t):e:(console.warn("[swup] options.resolveUrl needs to return a url"),t)}isSameResolvedUrl(t,e){return this.resolveUrl(t)===this.resolveUrl(e)}}export{p as Location,e as classify,f as cleanupAnimationClasses,i as createHistoryRecord,O as default,o as delegateEvent,c as escapeCssIdentifier,d as fetch,n as getCurrentUrl,h as getDataFromHtml,g as markSwupElements,l as nextTick,r as query,a as queryAll,u as toMs,s as updateHistoryRecord}; | ||
//# sourceMappingURL=Swup.module.js.map |
@@ -1,2 +0,2 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t||self).Swup=e()}(this,function(){const t=function(t){let{hash:e}=void 0===t?{}:t;return location.pathname+location.search+(e?location.hash:"")},e=function(e,n){void 0===e&&(e=null),void 0===n&&(n={}),e=e||t({hash:!0});const i={...history.state,url:e,random:Math.random(),source:"swup",...n};history.replaceState(i,"",e)},n=new WeakMap;function i(t,e,i,s){if(!t&&!n.has(e))return!1;const o=n.get(e)??new WeakMap;n.set(e,o);const r=o.get(i)??new Set;o.set(i,r);const a=r.has(s);return t?r.add(s):r.delete(s),a&&t}const s=(t,e,n,s)=>{const o=new AbortController;return function(t,e,n,s={}){const{signal:o,base:r=document}=s;if(o?.aborted)return;const{once:a,...l}=s,c=r instanceof Document?r.documentElement:r,u=Boolean("object"==typeof s?s.capture:s),h=s=>{const o=function(t,e){let n=t.target;if(n instanceof Text&&(n=n.parentElement),n instanceof Element&&t.currentTarget instanceof Element){const i=n.closest(e);if(i&&t.currentTarget.contains(i))return i}}(s,t);if(o){const t=Object.assign(s,{delegateTarget:o});n.call(c,t),a&&(c.removeEventListener(e,h,l),i(!1,c,n,d))}},d=JSON.stringify({selector:t,type:e,capture:u});i(!0,c,n,d)||c.addEventListener(e,h,l),o?.addEventListener("abort",()=>{i(!1,c,n,d)})}(t,e,n,s),{destroy:()=>o.abort()}},o=function(t,e){return void 0===e&&(e=document),e.querySelector(t)},r=function(t,e){return void 0===e&&(e=document),Array.from(e.querySelectorAll(t))},a=t=>window.CSS&&window.CSS.escape?CSS.escape(t):t,l=t=>1e3*Number(t.slice(0,-1).replace(",","."));class c extends URL{constructor(t,e){void 0===e&&(e=document.baseURI),super(t.toString(),e)}get url(){return this.pathname+this.search}static fromElement(t){const e=t.getAttribute("href")||t.getAttribute("xlink:href");return new c(e)}static fromUrl(t){return new c(t)}}const u=t=>/^to-/.test(t)||["is-changing","is-rendering","is-popstate"].includes(t),h=()=>{const t=document.documentElement.className.split(" ").filter(u);document.documentElement.classList.remove(...t)};class d{constructor(t){this.pages={},this.last=null,this.swup=void 0,this.swup=t}getCacheUrl(t){return this.swup.resolveUrl(c.fromUrl(t).url)}cacheUrl(t){t.url=this.getCacheUrl(t.url),t.url in this.pages==0&&(this.pages[t.url]=t),t.responseURL=this.getCacheUrl(t.responseURL),this.last=this.pages[t.url],this.swup.log(`Cache (${Object.keys(this.pages).length})`,this.pages)}getPage(t){return t=this.getCacheUrl(t),this.pages[t]}getCurrentPage(){return this.getPage(t())}exists(t){return(t=this.getCacheUrl(t))in this.pages}empty(){this.pages={},this.last=null,this.swup.log("Cache cleared")}remove(t){delete this.pages[this.getCacheUrl(t)]}}const p=function(t){let{event:e,skipTransition:n}=void 0===t?{}:t;if(n)return this.triggerEvent("transitionEnd",e),this.cleanupAnimationClasses(),[Promise.resolve()];var i;i=()=>{this.triggerEvent("animationInStart"),document.documentElement.classList.remove("is-animating")},requestAnimationFrame(()=>{requestAnimationFrame(()=>{i()})});const s=this.getAnimationPromises("in");return Promise.all(s).then(()=>{this.triggerEvent("animationInDone"),this.triggerEvent("transitionEnd",e),this.cleanupAnimationClasses()}),s},g=t=>{if(t&&"#"===t.charAt(0)&&(t=t.substring(1)),!t)return null;const e=decodeURIComponent(t);let n=document.getElementById(t)||document.getElementById(e)||o(`a[name='${a(t)}']`)||o(`a[name='${a(e)}']`);return n||"top"!==t||(n=document.body),n};let m="transition",f="transitionend",v="animation",w="animationend";function E(t){const e=this.options.animationSelector;if(!1===e)return[Promise.resolve()];const n=r(e,document.body);if(!n.length)return console.warn(`[swup] No elements found matching animationSelector \`${e}\``),[Promise.resolve()];const i=n.map(t=>function(t){const{type:e,timeout:n,propCount:i}=function(t,e){void 0===e&&(e=null);const n=window.getComputedStyle(t),i=`${m}Duration`,s=`${v}Delay`,o=`${v}Duration`,r=n[`${m}Delay`].split(", "),a=(n[i]||"").split(", "),l=P(r,a),c=(n[s]||"").split(", "),u=(n[o]||"").split(", "),h=P(c,u);let d="",p=0,g=0;return"transition"===e?l>0&&(d="transition",p=l,g=a.length):"animation"===e?h>0&&(d="animation",p=h,g=u.length):(p=Math.max(l,h),d=p>0?l>h?"transition":"animation":null,g=d?"transition"===d?a.length:u.length:0),{type:d,timeout:p,propCount:g}}(t);if(e&&n)return new Promise(s=>{const o="transition"===e?f:w,r=performance.now();let a=0;const l=()=>{t.removeEventListener(o,c),s()},c=e=>{if(e.target===t){if(!(t=>[f,w].includes(t.type))(e))throw new Error("Not a transition or animation event.");(performance.now()-r)/1e3<e.elapsedTime||++a>=i&&l()}};setTimeout(()=>{a<i&&l()},n+1),t.addEventListener(o,c)})}(t)).filter(Boolean);return i.length?i:(console.warn(`[swup] No CSS animation duration defined on elements matching \`${e}\``),[Promise.resolve()])}function P(t,e){for(;t.length<e.length;)t=t.concat(t);return Math.max(...e.map((e,n)=>l(e)+l(t[n])))}void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(m="WebkitTransition",f="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(v="WebkitAnimation",w="webkitAnimationEnd");const S=function(t){const e=((t,e)=>{let n=document.createElement("html");n.innerHTML=t;let i=[];e.forEach(t=>{if(null==o(t,n))return console.warn(`[swup] Container ${t} not found on page.`),null;r(t).length!==r(t,n).length&&console.warn("[swup] Mismatched number of containers found on new page."),r(t).forEach((e,s)=>{r(t,n)[s].setAttribute("data-swup",String(i.length)),i.push(r(t,n)[s].outerHTML)})});const s=o("title",n)?.innerText||"",a=o("body",n)?.className;return n.innerHTML="",n=null,{title:s,pageClass:a,blocks:i,originalContent:t}})(t.responseText,this.options.containers);return e?{...e,responseURL:t.responseURL||window.location.href}:(console.warn("[swup] Received page is invalid."),null)};function b(t){const e=this.options.requestHeaders,{url:n}=t;return this.cache.exists(n)?(this.triggerEvent("pageRetrievedFromCache"),Promise.resolve(this.cache.getPage(n))):new Promise((i,s)=>{((t,e)=>{const n={url:window.location.pathname+window.location.search,method:"GET",data:null,headers:{}},{url:i,method:s,headers:o,data:r}={...n,...t},a=new XMLHttpRequest;a.onreadystatechange=function(){4===a.readyState&&e(a)},a.open(s,i,!0),Object.entries(o).forEach(t=>{let[e,n]=t;a.setRequestHeader(e,n)}),a.send(r)})({...t,headers:e},t=>{if(500===t.status)return this.triggerEvent("serverError"),void s(n);const e=this.getPageData(t);if(!e||!e.blocks.length)return void s(n);const o={...e,url:n};this.cache.cacheUrl(o),this.triggerEvent("pageLoaded"),i(o)})})}const y=function(t){let{event:e,skipTransition:n}=void 0===t?{}:t;const i=e instanceof PopStateEvent;if(n)return this.triggerEvent("animationSkipped"),[Promise.resolve()];this.triggerEvent("animationOutStart"),document.documentElement.classList.add("is-changing","is-leaving","is-animating"),i&&document.documentElement.classList.add("is-popstate");const s=this.getAnimationPromises("out");return Promise.all(s).then(()=>{this.triggerEvent("animationOutDone")}),s};function k(t){const{url:e}=t;this.shouldIgnoreVisit(e)?window.location.href=e:this.performPageLoad(t)}function U(n){const{url:i,event:s,customTransition:o,history:r="push"}=n??{},a=s instanceof PopStateEvent,l=this.shouldSkipTransition({url:i,event:s});var c;this.triggerEvent("transitionStart",s),this.updateTransition(t(),i,o),null!=o&&document.documentElement.classList.add(`to-${c=o,String(c).toLowerCase().replace(/[\s/_.]+/g,"-").replace(/[^\w-]+/g,"").replace(/--+/g,"-").replace(/^-+|-+$/g,"")||""}`);const u=this.leavePage({event:s,skipTransition:l}),h=this.fetchPage(n);if(!a){const n=i+(this.scrollToElement||"");"replace"===r?e(n):function(e,n){void 0===n&&(n={});const i={url:e=e||t({hash:!0}),random:Math.random(),source:"swup",...n};history.pushState(i,"",e)}(n)}this.currentPageUrl=t(),Promise.all([h,...u]).then(t=>{let[e]=t;this.renderPage(e,{event:s,skipTransition:l})}).catch(t=>{void 0!==t&&(this.options.skipPopStateHandling=()=>(window.location=t,!0),history.go(-1))})}const L=function(t){let{blocks:e,title:n}=t;return e.forEach((t,e)=>{document.body.querySelector(`[data-swup="${e}"]`).outerHTML=t}),document.title=n,Promise.resolve()};function T(t,e){const n=this._handlers[t];n?n.push(e):console.warn(`Unsupported event ${t}.`)}function C(t,e){if(t&&e){const n=this._handlers[t];n.includes(e)?this._handlers[t]=n.filter(t=>t!==e):console.warn(`Handler for event '${t}' not found.`)}else t?this._handlers[t]=[]:Object.keys(this._handlers).forEach(t=>{this._handlers[t]=[]})}function H(t,e){this._handlers[t].forEach(t=>{try{t(e)}catch(t){console.error(t)}});const n=new CustomEvent(`swup:${t}`,{detail:t});document.dispatchEvent(n)}const R=function(t){if(t?.isSwupPlugin){if(t.swup=this,!t._checkRequirements||t._checkRequirements())return t._beforeMount&&t._beforeMount(),t.mount(),this.plugins.push(t),this.plugins}else console.error("Not a swup plugin instance",t)};function A(t){const e=this.findPlugin(t);if(e)return e.unmount(),e._afterUnmount&&e._afterUnmount(),this.plugins=this.plugins.filter(t=>t!==e),this.plugins;console.error("No such plugin",e)}function $(t){return this.plugins.find(e=>e===t||e.name===t)}const _=function(n,i){let{event:s,skipTransition:o}=void 0===i?{}:i;if(document.documentElement.classList.remove("is-leaving"),!this.isSameResolvedUrl(t(),n.url))return;const{url:r}=c.fromUrl(n.responseURL);this.isSameResolvedUrl(t(),r)||(this.cache.cacheUrl({...n,url:r}),this.currentPageUrl=t(),e(r)),o||document.documentElement.classList.add("is-rendering"),this.triggerEvent("willReplaceContent",s),this.replaceContent(n).then(()=>{this.triggerEvent("contentReplaced",s),this.triggerEvent("pageView",s),this.options.cache||this.cache.empty(),this.enterPage({event:s,skipTransition:o}),this.scrollToElement=null})};function x(t,e,n){this.transition={from:t,to:e,custom:n}}function I(t){let{event:e}=t;return!(!(e instanceof PopStateEvent)||this.options.animateHistoryBrowsing)}return class{constructor(e){void 0===e&&(e={}),this.version="3.1.1",this._handlers={animationInDone:[],animationInStart:[],animationOutDone:[],animationOutStart:[],animationSkipped:[],clickLink:[],contentReplaced:[],disabled:[],enabled:[],openPageInNewTab:[],pageLoaded:[],pageRetrievedFromCache:[],pageView:[],popState:[],samePage:[],samePageWithHash:[],serverError:[],transitionStart:[],transitionEnd:[],willReplaceContent:[]},this.scrollToElement=null,this.options=void 0,this.plugins=[],this.transition={},this.cache=void 0,this.currentPageUrl=t(),this.delegatedListeners={},this.boundPopStateHandler=void 0,this.loadPage=k,this.performPageLoad=U,this.leavePage=y,this.renderPage=_,this.replaceContent=L,this.enterPage=p,this.triggerEvent=H,this.delegateEvent=s,this.on=T,this.off=C,this.updateTransition=x,this.shouldSkipTransition=I,this.getAnimationPromises=E,this.getPageData=S,this.fetchPage=b,this.getAnchorElement=g,this.log=()=>{},this.use=R,this.unuse=A,this.findPlugin=$,this.getCurrentUrl=t,this.cleanupAnimationClasses=h,this.defaults={animateHistoryBrowsing:!1,animationSelector:'[class*="transition-"]',cache:!0,containers:["#swup"],ignoreVisit:function(t,e){let{el:n}=void 0===e?{}:e;return!!n?.closest("[data-no-swup]")},linkSelector:"a[href]",plugins:[],resolveUrl:t=>t,requestHeaders:{"X-Requested-With":"swup",Accept:"text/html, application/xhtml+xml"},skipPopStateHandling:t=>"swup"!==t.state?.source},this.options={...this.defaults,...e},this.boundPopStateHandler=this.popStateHandler.bind(this),this.cache=new d(this),this.enable()}enable(){"undefined"!=typeof Promise?(this.delegatedListeners.click=s(this.options.linkSelector,"click",this.linkClickHandler.bind(this)),window.addEventListener("popstate",this.boundPopStateHandler),((t,e)=>{let n=0;this.options.containers.forEach(e=>{null==o(e,t)?console.warn(`[swup] Container ${e} not found on page.`):r(e).forEach((i,s)=>{r(e,t)[s].setAttribute("data-swup",String(n)),n++})})})(document.documentElement),this.options.plugins.forEach(t=>this.use(t)),e(),this.triggerEvent("enabled"),document.documentElement.classList.add("swup-enabled"),this.triggerEvent("pageView")):console.warn("Promise is not supported")}destroy(){this.delegatedListeners.click.destroy(),window.removeEventListener("popstate",this.boundPopStateHandler),this.cache.empty(),this.options.plugins.forEach(t=>{this.unuse(t)}),r("[data-swup]").forEach(t=>{t.removeAttribute("data-swup")}),this.off(),this.triggerEvent("disabled"),document.documentElement.classList.remove("swup-enabled")}shouldIgnoreVisit(t,e){let{el:n,event:i}=void 0===e?{}:e;const{origin:s,url:o,hash:r}=c.fromUrl(t);return s!==window.location.origin||!(!n||!this.triggerWillOpenNewWindow(n))||!!this.options.ignoreVisit(o+r,{el:n,event:i})}linkClickHandler(e){const n=e.delegateTarget,{href:i,url:s,hash:o}=c.fromElement(n);if(this.shouldIgnoreVisit(i,{el:n,event:e}))return;if(e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)return void this.triggerEvent("openPageInNewTab",e);if(0!==e.button)return;if(this.triggerEvent("clickLink",e),e.preventDefault(),!s||s===t())return void this.handleLinkToSamePage(s,o,e);if(this.isSameResolvedUrl(s,t()))return;this.scrollToElement=o||null;const r=n.getAttribute("data-swup-transition")||void 0;let a;const l=n.getAttribute("data-swup-history");l&&["push","replace"].includes(l)&&(a=l),this.performPageLoad({url:s,customTransition:r,history:a})}handleLinkToSamePage(t,n,i){if(n){if(this.triggerEvent("samePageWithHash",i),!g(n))return console.warn(`Element for offset not found (#${n})`);e(t+n)}else this.triggerEvent("samePage",i)}triggerWillOpenNewWindow(t){return!!t.matches('[download], [target="_blank"]')}popStateHandler(e){if(this.options.skipPopStateHandling(e))return;if(this.isSameResolvedUrl(t(),this.currentPageUrl))return;const n=e.state?.url??location.href;if(this.shouldIgnoreVisit(n,{event:e}))return;const{url:i,hash:s}=c.fromUrl(n);s?this.scrollToElement=s:e.preventDefault(),this.triggerEvent("popState",e),this.options.animateHistoryBrowsing||(document.documentElement.classList.remove("is-animating"),h()),this.performPageLoad({url:i,event:e})}resolveUrl(t){if("function"!=typeof this.options.resolveUrl)return console.warn("[swup] options.resolveUrl expects a callback function."),t;const e=this.options.resolveUrl(t);return e&&"string"==typeof e?e.startsWith("//")||e.startsWith("http")?(console.warn("[swup] options.resolveUrl needs to return a relative url"),t):e:(console.warn("[swup] options.resolveUrl needs to return a url"),t)}isSameResolvedUrl(t,e){return this.resolveUrl(t)===this.resolveUrl(e)}}}); | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t||self).Swup=e()}(this,function(){const t=function(t){let{hash:e}=void 0===t?{}:t;return location.pathname+location.search+(e?location.hash:"")},e=function(e,n){void 0===e&&(e=null),void 0===n&&(n={}),e=e||t({hash:!0});const i={...history.state,url:e,random:Math.random(),source:"swup",...n};history.replaceState(i,"",e)},n=new WeakMap;function i(t,e,i,s){var o,r;if(!t&&!n.has(e))return!1;const a=null!==(o=n.get(e))&&void 0!==o?o:new WeakMap;if(n.set(e,a),!t&&!n.has(e))return!1;const l=null!==(r=a.get(i))&&void 0!==r?r:new Set;a.set(i,l);const c=l.has(s);return t?l.add(s):l.delete(s),c&&t}function s(t,e,n,o,r){if("string"==typeof t&&(t=document.querySelectorAll(t)),"function"!=typeof t.addEventListener){const i=Array.prototype.map.call(t,t=>s(t,e,n,o,r));return{destroy(){for(const t of i)t.destroy()}}}const a=t instanceof Document?t.documentElement:t,l=Boolean("object"==typeof r?r.capture:r),c=t=>{const n=function(t,e){let n=t.target;if(n instanceof Text&&(n=n.parentElement),n instanceof Element&&t.currentTarget instanceof Element){const i=n.closest(e);if(i&&t.currentTarget.contains(i))return i}}(t,e);n&&(t.delegateTarget=n,o.call(a,t))};"object"==typeof r&&delete r.once;const u=JSON.stringify({selector:e,type:n,capture:l}),h={destroy(){a.removeEventListener(n,c,r),i(!1,a,o,u)}};return i(!0,a,o,u)||a.addEventListener(n,c,r),h}const o=function(t,e,n,i){let{base:o=document,...r}=void 0===i?{}:i;const a=s(o,t,e,n,r);return{destroy:()=>a.destroy()}},r=function(t,e){return void 0===e&&(e=document),e.querySelector(t)},a=function(t,e){return void 0===e&&(e=document),Array.from(e.querySelectorAll(t))},l=t=>1e3*Number(t.slice(0,-1).replace(",","."));class c extends URL{constructor(t,e){void 0===e&&(e=document.baseURI),super(t.toString(),e)}get url(){return this.pathname+this.search}static fromElement(t){const e=t.getAttribute("href")||t.getAttribute("xlink:href");return new c(e)}static fromUrl(t){return new c(t)}}const u=t=>/^to-/.test(t)||["is-changing","is-rendering","is-popstate"].includes(t),h=()=>{const t=document.documentElement.className.split(" ").filter(u);document.documentElement.classList.remove(...t)};class d{constructor(t){this.pages={},this.last=null,this.swup=void 0,this.swup=t}getCacheUrl(t){return this.swup.resolveUrl(c.fromUrl(t).url)}cacheUrl(t){t.url=this.getCacheUrl(t.url),t.url in this.pages==0&&(this.pages[t.url]=t),t.responseURL=this.getCacheUrl(t.responseURL),this.last=this.pages[t.url],this.swup.log(`Cache (${Object.keys(this.pages).length})`,this.pages)}getPage(t){return t=this.getCacheUrl(t),this.pages[t]}getCurrentPage(){return this.getPage(t())}exists(t){return(t=this.getCacheUrl(t))in this.pages}empty(){this.pages={},this.last=null,this.swup.log("Cache cleared")}remove(t){delete this.pages[this.getCacheUrl(t)]}}const p=function(t){let{event:e,skipTransition:n}=void 0===t?{}:t;if(n)return this.triggerEvent("transitionEnd",e),this.cleanupAnimationClasses(),[Promise.resolve()];var i;i=()=>{this.triggerEvent("animationInStart"),document.documentElement.classList.remove("is-animating")},requestAnimationFrame(()=>{requestAnimationFrame(()=>{i()})});const s=this.getAnimationPromises("in");return Promise.all(s).then(()=>{this.triggerEvent("animationInDone"),this.triggerEvent("transitionEnd",e),this.cleanupAnimationClasses()}),s},g=t=>{return t?("#"===t.charAt(0)&&(t=t.substring(1)),e=t=decodeURIComponent(t),t=window.CSS&&window.CSS.escape?CSS.escape(e):e,r(`#${t}`)||r(`a[name='${t}']`)):null;var e};let m="transition",f="transitionend",v="animation",w="animationend";function E(t){const e=this.options.animationSelector;if(!1===e)return[Promise.resolve()];const n=a(e,document.body);return n.length?n.map(t=>function(t,e,n){void 0===n&&(n=null);const{type:i,timeout:s,propCount:o}=function(t,e){void 0===e&&(e=null);const n=window.getComputedStyle(t),i=`${m}Duration`,s=`${v}Delay`,o=`${v}Duration`,r=n[`${m}Delay`].split(", "),a=(n[i]||"").split(", "),l=P(r,a),c=(n[s]||"").split(", "),u=(n[o]||"").split(", "),h=P(c,u);let d="",p=0,g=0;return"transition"===e?l>0&&(d="transition",p=l,g=a.length):"animation"===e?h>0&&(d="animation",p=h,g=u.length):(p=Math.max(l,h),d=p>0?l>h?"transition":"animation":null,g=d?"transition"===d?a.length:u.length:0),{type:d,timeout:p,propCount:g}}(t,n);return i&&s?new Promise(e=>{const n="transition"===i?f:w,r=performance.now();let a=0;const l=()=>{t.removeEventListener(n,c),e()},c=e=>{if(e.target===t){if(!(t=>!!t.elapsedTime)(e))throw new Error("Not a transition or animation event.");(performance.now()-r)/1e3<e.elapsedTime||++a>=o&&l()}};setTimeout(()=>{a<o&&l()},s+1),t.addEventListener(n,c)}):(console.warn(`[swup] No CSS transition duration defined for element of selector ${e}`),Promise.resolve())}(t,e)):(console.warn(`[swup] No animated elements found by selector ${e}`),[Promise.resolve()])}function P(t,e){for(;t.length<e.length;)t=t.concat(t);return Math.max(...e.map((e,n)=>l(e)+l(t[n])))}void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(m="WebkitTransition",f="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(v="WebkitAnimation",w="webkitAnimationEnd");const S=function(t){const e=((t,e)=>{let n=document.createElement("html");n.innerHTML=t;let i=[];e.forEach(t=>{if(null==r(t,n))return console.warn(`[swup] Container ${t} not found on page.`),null;a(t).length!==a(t,n).length&&console.warn("[swup] Mismatched number of containers found on new page."),a(t).forEach((e,s)=>{a(t,n)[s].setAttribute("data-swup",String(i.length)),i.push(a(t,n)[s].outerHTML)})});const s=r("title",n)?.innerText||"",o=r("body",n)?.className;return n.innerHTML="",n=null,{title:s,pageClass:o,blocks:i,originalContent:t}})(t.responseText,this.options.containers);return e?{...e,responseURL:t.responseURL||window.location.href}:(console.warn("[swup] Received page is invalid."),null)};function y(t){const e=this.options.requestHeaders,{url:n}=t;return this.cache.exists(n)?(this.triggerEvent("pageRetrievedFromCache"),Promise.resolve(this.cache.getPage(n))):new Promise((i,s)=>{((t,e)=>{const n={url:window.location.pathname+window.location.search,method:"GET",data:null,headers:{}},{url:i,method:s,headers:o,data:r}={...n,...t},a=new XMLHttpRequest;a.onreadystatechange=function(){4===a.readyState&&e(a)},a.open(s,i,!0),Object.entries(o).forEach(t=>{let[e,n]=t;a.setRequestHeader(e,n)}),a.send(r)})({...t,headers:e},t=>{if(500===t.status)return this.triggerEvent("serverError"),void s(n);const e=this.getPageData(t);if(!e||!e.blocks.length)return void s(n);const o={...e,url:n};this.cache.cacheUrl(o),this.triggerEvent("pageLoaded"),i(o)})})}const b=function(t){let{event:e,skipTransition:n}=void 0===t?{}:t;const i=e instanceof PopStateEvent;if(n)return this.triggerEvent("animationSkipped"),[Promise.resolve()];this.triggerEvent("animationOutStart"),document.documentElement.classList.add("is-changing","is-leaving","is-animating"),i&&document.documentElement.classList.add("is-popstate");const s=this.getAnimationPromises("out");return Promise.all(s).then(()=>{this.triggerEvent("animationOutDone")}),s};function k(t){const{url:e}=t;this.shouldIgnoreVisit(e)?window.location.href=e:this.performPageLoad(t)}function U(e){const{url:n,event:i,customTransition:s}=e??{},o=i instanceof PopStateEvent,r=this.shouldSkipTransition({url:n,event:i});var a;this.triggerEvent("transitionStart",i),this.updateTransition(t(),n,s),null!=s&&document.documentElement.classList.add(`to-${a=s,String(a).toLowerCase().replace(/[\s/_.]+/g,"-").replace(/[^\w-]+/g,"").replace(/--+/g,"-").replace(/^-+|-+$/g,"")||""}`);const l=this.leavePage({event:i,skipTransition:r}),c=this.fetchPage(e);o||function(e,n){void 0===n&&(n={});const i={url:e=e||t({hash:!0}),random:Math.random(),source:"swup",...n};history.pushState(i,"",e)}(n+(this.scrollToElement||"")),this.currentPageUrl=t(),Promise.all([c,...l]).then(t=>{let[e]=t;this.renderPage(e,{event:i,skipTransition:r})}).catch(t=>{void 0!==t&&(this.options.skipPopStateHandling=()=>(window.location=t,!0),history.go(-1))})}const L=function(t){let{blocks:e,title:n}=t;return e.forEach((t,e)=>{document.body.querySelector(`[data-swup="${e}"]`).outerHTML=t}),document.title=n,Promise.resolve()};function T(t,e){const n=this._handlers[t];n?n.push(e):console.warn(`Unsupported event ${t}.`)}function C(t,e){if(t&&e){const n=this._handlers[t];n.includes(e)?this._handlers[t]=n.filter(t=>t!==e):console.warn(`Handler for event '${t}' not found.`)}else t?this._handlers[t]=[]:Object.keys(this._handlers).forEach(t=>{this._handlers[t]=[]})}function H(t,e){this._handlers[t].forEach(t=>{try{t(e)}catch(t){console.error(t)}});const n=new CustomEvent(`swup:${t}`,{detail:t});document.dispatchEvent(n)}const R=function(t){if(t?.isSwupPlugin){if(t.swup=this,!t._checkRequirements||t._checkRequirements())return t._beforeMount&&t._beforeMount(),t.mount(),this.plugins.push(t),this.plugins}else console.error("Not a swup plugin instance",t)};function A(t){const e=this.findPlugin(t);if(e)return e.unmount(),e._afterUnmount&&e._afterUnmount(),this.plugins=this.plugins.filter(t=>t!==e),this.plugins;console.error("No such plugin",e)}function $(t){return this.plugins.find(e=>e===t||e.name===t)}const _=function(n,i){let{event:s,skipTransition:o}=void 0===i?{}:i;if(document.documentElement.classList.remove("is-leaving"),!this.isSameResolvedUrl(t(),n.url))return;const{url:r}=c.fromUrl(n.responseURL);this.isSameResolvedUrl(t(),r)||(this.cache.cacheUrl({...n,url:r}),this.currentPageUrl=t(),e(r)),o||document.documentElement.classList.add("is-rendering"),this.triggerEvent("willReplaceContent",s),this.replaceContent(n).then(()=>{this.triggerEvent("contentReplaced",s),this.triggerEvent("pageView",s),this.options.cache||this.cache.empty(),this.enterPage({event:s,skipTransition:o}),this.scrollToElement=null})};function x(t,e,n){this.transition={from:t,to:e,custom:n}}function M(t){let{event:e}=t;return!(!(e instanceof PopStateEvent)||this.options.animateHistoryBrowsing)}return class{constructor(e){void 0===e&&(e={}),this.version="3.0.4",this._handlers={animationInDone:[],animationInStart:[],animationOutDone:[],animationOutStart:[],animationSkipped:[],clickLink:[],contentReplaced:[],disabled:[],enabled:[],openPageInNewTab:[],pageLoaded:[],pageRetrievedFromCache:[],pageView:[],popState:[],samePage:[],samePageWithHash:[],serverError:[],transitionStart:[],transitionEnd:[],willReplaceContent:[]},this.scrollToElement=null,this.options=void 0,this.plugins=[],this.transition={},this.cache=void 0,this.currentPageUrl=t(),this.delegatedListeners={},this.boundPopStateHandler=void 0,this.loadPage=k,this.performPageLoad=U,this.leavePage=b,this.renderPage=_,this.replaceContent=L,this.enterPage=p,this.triggerEvent=H,this.delegateEvent=o,this.on=T,this.off=C,this.updateTransition=x,this.shouldSkipTransition=M,this.getAnimationPromises=E,this.getPageData=S,this.fetchPage=y,this.getAnchorElement=g,this.log=()=>{},this.use=R,this.unuse=A,this.findPlugin=$,this.getCurrentUrl=t,this.cleanupAnimationClasses=h,this.defaults={animateHistoryBrowsing:!1,animationSelector:'[class*="transition-"]',cache:!0,containers:["#swup"],ignoreVisit:function(t,e){let{el:n}=void 0===e?{}:e;return!!n?.closest("[data-no-swup]")},linkSelector:"a[href]",plugins:[],resolveUrl:t=>t,requestHeaders:{"X-Requested-With":"swup",Accept:"text/html, application/xhtml+xml"},skipPopStateHandling:t=>"swup"!==t.state?.source},this.options={...this.defaults,...e},this.boundPopStateHandler=this.popStateHandler.bind(this),this.cache=new d(this),this.enable()}enable(){"undefined"!=typeof Promise?(this.delegatedListeners.click=o(this.options.linkSelector,"click",this.linkClickHandler.bind(this)),window.addEventListener("popstate",this.boundPopStateHandler),((t,e)=>{let n=0;this.options.containers.forEach(e=>{null==r(e,t)?console.warn(`[swup] Container ${e} not found on page.`):a(e).forEach((i,s)=>{a(e,t)[s].setAttribute("data-swup",String(n)),n++})})})(document.documentElement),this.options.plugins.forEach(t=>this.use(t)),e(),this.triggerEvent("enabled"),document.documentElement.classList.add("swup-enabled"),this.triggerEvent("pageView")):console.warn("Promise is not supported")}destroy(){this.delegatedListeners.click.destroy(),window.removeEventListener("popstate",this.boundPopStateHandler),this.cache.empty(),this.options.plugins.forEach(t=>{this.unuse(t)}),a("[data-swup]").forEach(t=>{t.removeAttribute("data-swup")}),this.off(),this.triggerEvent("disabled"),document.documentElement.classList.remove("swup-enabled")}shouldIgnoreVisit(t,e){let{el:n}=void 0===e?{}:e;const{origin:i,url:s,hash:o}=c.fromUrl(t);return i!==window.location.origin||!(!n||!this.triggerWillOpenNewWindow(n))||!!this.options.ignoreVisit(s+o,{el:n})}linkClickHandler(e){const n=e.delegateTarget,{href:i,url:s,hash:o}=c.fromElement(n);if(this.shouldIgnoreVisit(i,{el:n}))return;if(e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)return void this.triggerEvent("openPageInNewTab",e);if(0!==e.button)return;if(this.triggerEvent("clickLink",e),e.preventDefault(),!s||s===t())return void this.handleLinkToSamePage(s,o,e);if(this.isSameResolvedUrl(s,t()))return;this.scrollToElement=o||null;const r=n.getAttribute("data-swup-transition")||void 0;this.performPageLoad({url:s,customTransition:r})}handleLinkToSamePage(t,n,i){if(n){if(this.triggerEvent("samePageWithHash",i),!g(n))return console.warn(`Element for offset not found (#${n})`);e(t+n)}else this.triggerEvent("samePage",i)}triggerWillOpenNewWindow(t){return!!t.matches('[download], [target="_blank"]')}popStateHandler(e){if(this.options.skipPopStateHandling(e))return;if(this.isSameResolvedUrl(t(),this.currentPageUrl))return;const n=e.state?.url??location.href;if(this.shouldIgnoreVisit(n))return;const{url:i,hash:s}=c.fromUrl(n);s?this.scrollToElement=s:e.preventDefault(),this.triggerEvent("popState",e),this.options.animateHistoryBrowsing||(document.documentElement.classList.remove("is-animating"),h()),this.performPageLoad({url:i,event:e})}resolveUrl(t){if("function"!=typeof this.options.resolveUrl)return console.warn("[swup] options.resolveUrl expects a callback function."),t;const e=this.options.resolveUrl(t);return e&&"string"==typeof e?e.startsWith("//")||e.startsWith("http")?(console.warn("[swup] options.resolveUrl needs to return a relative url"),t):e:(console.warn("[swup] options.resolveUrl needs to return a url"),t)}isSameResolvedUrl(t,e){return this.resolveUrl(t)===this.resolveUrl(e)}}}); | ||
//# sourceMappingURL=Swup.umd.js.map |
@@ -1,10 +0,10 @@ | ||
export { classify } from './helpers/classify.js'; | ||
export { createHistoryRecord } from './helpers/createHistoryRecord.js'; | ||
export { updateHistoryRecord } from './helpers/updateHistoryRecord.js'; | ||
export { delegateEvent } from './helpers/delegateEvent.js'; | ||
export { getDataFromHtml } from './helpers/getDataFromHtml.js'; | ||
export { fetch } from './helpers/fetch.js'; | ||
export { getCurrentUrl } from './helpers/getCurrentUrl.js'; | ||
export { Location } from './helpers/Location.js'; | ||
export { markSwupElements } from './helpers/markSwupElements.js'; | ||
export { cleanupAnimationClasses } from './helpers/cleanupAnimationClasses.js'; | ||
export { classify } from './helpers/classify'; | ||
export { createHistoryRecord } from './helpers/createHistoryRecord'; | ||
export { updateHistoryRecord } from './helpers/updateHistoryRecord'; | ||
export { delegateEvent } from './helpers/delegateEvent'; | ||
export { getDataFromHtml } from './helpers/getDataFromHtml'; | ||
export { fetch } from './helpers/fetch'; | ||
export { getCurrentUrl } from './helpers/getCurrentUrl'; | ||
export { Location } from './helpers/Location'; | ||
export { markSwupElements } from './helpers/markSwupElements'; | ||
export { cleanupAnimationClasses } from './helpers/cleanupAnimationClasses'; |
@@ -1,5 +0,7 @@ | ||
import { DelegateEventHandler, DelegateOptions } from 'delegate-it'; | ||
import delegate from 'delegate-it'; | ||
export type Unsubscribe = { | ||
destroy: () => void; | ||
}; | ||
export declare const delegateEvent: <Selector extends string, TEvent extends keyof GlobalEventHandlersEventMap>(selector: Selector, type: TEvent, callback: DelegateEventHandler<GlobalEventHandlersEventMap[TEvent], Element>, options?: DelegateOptions) => Unsubscribe; | ||
export declare const delegateEvent: <Selector extends string, TEvent extends keyof GlobalEventHandlersEventMap>(selector: Selector, type: TEvent, callback: delegate.EventHandler<GlobalEventHandlersEventMap[TEvent], Element>, { base, ...eventOptions }?: { | ||
base?: Document | undefined; | ||
}) => Unsubscribe; |
@@ -1,5 +0,5 @@ | ||
import { TransitionOptions } from '../modules/loadPage.js'; | ||
import { Options } from '../Swup.js'; | ||
import { TransitionOptions } from '../modules/loadPage'; | ||
import { Options } from '../Swup'; | ||
export declare const fetch: (options: TransitionOptions & { | ||
headers: Options['requestHeaders']; | ||
}, callback: (request: XMLHttpRequest) => void) => XMLHttpRequest; |
@@ -1,7 +0,7 @@ | ||
import Swup, { Options } from './Swup.js'; | ||
import { Plugin } from './modules/plugins.js'; | ||
import { Handler } from './modules/events.js'; | ||
import Swup, { Options } from './Swup'; | ||
import { Plugin } from './modules/plugins'; | ||
import { Handler } from './modules/events'; | ||
export default Swup; | ||
export * from './helpers.js'; | ||
export * from './utils.js'; | ||
export * from './helpers'; | ||
export * from './utils'; | ||
export type { Options, Plugin, Handler }; |
@@ -1,3 +0,3 @@ | ||
import Swup from '../Swup.js'; | ||
import { PageData } from './getPageData.js'; | ||
import Swup from '../Swup'; | ||
import { PageData } from './getPageData'; | ||
export interface PageRecord extends PageData { | ||
@@ -4,0 +4,0 @@ url: string; |
@@ -1,3 +0,3 @@ | ||
import Swup from '../Swup.js'; | ||
import { PageRenderOptions } from './renderPage.js'; | ||
import Swup from '../Swup'; | ||
import { PageRenderOptions } from './renderPage'; | ||
export declare const enterPage: (this: Swup, { event, skipTransition }?: PageRenderOptions) => Promise<void>[]; |
@@ -1,3 +0,3 @@ | ||
import Swup from '../Swup.js'; | ||
import { DelegateEvent } from 'delegate-it'; | ||
import Swup from '../Swup'; | ||
import delegate from 'delegate-it'; | ||
type HandlersEventMap = { | ||
@@ -9,7 +9,7 @@ animationInDone: undefined; | ||
animationSkipped: undefined; | ||
clickLink: DelegateEvent<MouseEvent>; | ||
clickLink: delegate.Event<MouseEvent>; | ||
contentReplaced: PopStateEvent | undefined; | ||
disabled: undefined; | ||
enabled: undefined; | ||
openPageInNewTab: DelegateEvent<MouseEvent>; | ||
openPageInNewTab: delegate.Event<MouseEvent>; | ||
pageLoaded: undefined; | ||
@@ -19,4 +19,4 @@ pageRetrievedFromCache: undefined; | ||
popState: PopStateEvent; | ||
samePage: DelegateEvent<MouseEvent>; | ||
samePageWithHash: DelegateEvent<MouseEvent>; | ||
samePage: delegate.Event<MouseEvent>; | ||
samePageWithHash: delegate.Event<MouseEvent>; | ||
serverError: undefined; | ||
@@ -23,0 +23,0 @@ transitionStart: PopStateEvent | undefined; |
@@ -1,4 +0,4 @@ | ||
import Swup from '../Swup.js'; | ||
import { TransitionOptions } from './loadPage.js'; | ||
import { PageRecord } from './Cache.js'; | ||
import Swup from '../Swup'; | ||
import { TransitionOptions } from './loadPage'; | ||
import { PageRecord } from './Cache'; | ||
export declare function fetchPage(this: Swup, data: TransitionOptions): Promise<PageRecord>; |
@@ -1,8 +0,1 @@ | ||
/** | ||
* Find the anchor element for a given hash. | ||
* @see https://html.spec.whatwg.org/#find-a-potential-indicated-element | ||
* | ||
* @param hash Hash with or without leading '#' | ||
* @returns The element, if found, or null. | ||
*/ | ||
export declare const getAnchorElement: (hash: string) => Element | null; |
@@ -1,2 +0,2 @@ | ||
import Swup from '../Swup.js'; | ||
import Swup from '../Swup'; | ||
export declare function getAnimationPromises(this: Swup, animationType: 'in' | 'out'): Promise<void>[]; | ||
@@ -3,0 +3,0 @@ export declare function getTransitionInfo(element: Element, expectedType?: 'animation' | 'transition' | null): { |
@@ -1,3 +0,3 @@ | ||
import Swup from '../Swup.js'; | ||
import { PageHtmlData } from '../helpers/getDataFromHtml.js'; | ||
import Swup from '../Swup'; | ||
import { PageHtmlData } from '../helpers/getDataFromHtml'; | ||
export type PageData = PageHtmlData & { | ||
@@ -4,0 +4,0 @@ responseURL: string; |
@@ -1,3 +0,3 @@ | ||
import Swup from '../Swup.js'; | ||
import { PageRenderOptions } from './renderPage.js'; | ||
import Swup from '../Swup'; | ||
import { PageRenderOptions } from './renderPage'; | ||
export declare const leavePage: (this: Swup, { event, skipTransition }?: PageRenderOptions) => Promise<void>[]; |
@@ -1,15 +0,12 @@ | ||
import Swup from '../Swup.js'; | ||
export type HistoryAction = 'push' | 'replace'; | ||
import Swup from '../Swup'; | ||
export type TransitionOptions = { | ||
url: string; | ||
customTransition?: string; | ||
history?: HistoryAction; | ||
}; | ||
export type PageLoadOptions = { | ||
url: string; | ||
event?: PopStateEvent; | ||
customTransition?: string; | ||
history?: HistoryAction; | ||
event?: PopStateEvent; | ||
}; | ||
export declare function loadPage(this: Swup, data: TransitionOptions): void; | ||
export declare function performPageLoad(this: Swup, data: PageLoadOptions): void; |
@@ -1,2 +0,2 @@ | ||
import Swup from '../Swup.js'; | ||
import Swup from '../Swup'; | ||
export type Plugin = { | ||
@@ -3,0 +3,0 @@ name: string; |
@@ -1,3 +0,3 @@ | ||
import Swup from '../Swup.js'; | ||
import { PageRecord } from './Cache.js'; | ||
import Swup from '../Swup'; | ||
import { PageRecord } from './Cache'; | ||
export type PageRenderOptions = { | ||
@@ -4,0 +4,0 @@ event?: PopStateEvent; |
@@ -1,2 +0,2 @@ | ||
import Swup from '../Swup.js'; | ||
import Swup from '../Swup'; | ||
export declare function updateTransition(this: Swup, from: string, to: string, custom?: string): void; | ||
@@ -3,0 +3,0 @@ export declare function shouldSkipTransition(this: Swup, { event }: { |
@@ -1,10 +0,10 @@ | ||
import { DelegateEvent } from 'delegate-it'; | ||
import { Unsubscribe } from './helpers/delegateEvent.js'; | ||
import { Cache } from './modules/Cache.js'; | ||
import { getAnimationPromises } from './modules/getAnimationPromises.js'; | ||
import { fetchPage } from './modules/fetchPage.js'; | ||
import { loadPage, performPageLoad } from './modules/loadPage.js'; | ||
import { on, off, triggerEvent, Handlers } from './modules/events.js'; | ||
import { unuse, findPlugin, Plugin } from './modules/plugins.js'; | ||
import { updateTransition, shouldSkipTransition } from './modules/transitions.js'; | ||
import delegate from 'delegate-it'; | ||
import { Unsubscribe } from './helpers/delegateEvent'; | ||
import { Cache } from './modules/Cache'; | ||
import { getAnimationPromises } from './modules/getAnimationPromises'; | ||
import { fetchPage } from './modules/fetchPage'; | ||
import { loadPage, performPageLoad } from './modules/loadPage'; | ||
import { on, off, triggerEvent, Handlers } from './modules/events'; | ||
import { unuse, findPlugin, Plugin } from './modules/plugins'; | ||
import { updateTransition, shouldSkipTransition } from './modules/transitions'; | ||
export type Transition = { | ||
@@ -27,5 +27,4 @@ from?: string; | ||
skipPopStateHandling: (event: any) => boolean; | ||
ignoreVisit: (url: string, { el, event }: { | ||
ignoreVisit: (url: string, { el }: { | ||
el?: Element; | ||
event?: Event; | ||
}) => boolean; | ||
@@ -47,4 +46,4 @@ resolveUrl: (url: string) => string; | ||
performPageLoad: typeof performPageLoad; | ||
leavePage: (this: Swup, { event, skipTransition }?: import("./modules/renderPage.js").PageRenderOptions) => Promise<void>[]; | ||
renderPage: (this: Swup, page: import("./modules/Cache.js").PageRecord, { event, skipTransition }?: import("./modules/renderPage.js").PageRenderOptions) => void; | ||
leavePage: (this: Swup, { event, skipTransition }?: import("./modules/renderPage").PageRenderOptions) => Promise<void>[]; | ||
renderPage: (this: Swup, page: import("./modules/Cache").PageRecord, { event, skipTransition }?: import("./modules/renderPage").PageRenderOptions) => void; | ||
replaceContent: ({ blocks, title }: { | ||
@@ -54,5 +53,7 @@ blocks: string[]; | ||
}) => Promise<void>; | ||
enterPage: (this: Swup, { event, skipTransition }?: import("./modules/renderPage.js").PageRenderOptions) => Promise<void>[]; | ||
enterPage: (this: Swup, { event, skipTransition }?: import("./modules/renderPage").PageRenderOptions) => Promise<void>[]; | ||
triggerEvent: typeof triggerEvent; | ||
delegateEvent: <Selector extends string, TEvent extends keyof GlobalEventHandlersEventMap>(selector: Selector, type: TEvent, callback: import("delegate-it").DelegateEventHandler<GlobalEventHandlersEventMap[TEvent], Element>, options?: import("delegate-it").DelegateOptions | undefined) => Unsubscribe; | ||
delegateEvent: <Selector extends string, TEvent extends keyof GlobalEventHandlersEventMap>(selector: Selector, type: TEvent, callback: delegate.EventHandler<GlobalEventHandlersEventMap[TEvent], Element>, { base, ...eventOptions }?: { | ||
base?: Document | undefined; | ||
}) => Unsubscribe; | ||
on: typeof on; | ||
@@ -63,3 +64,3 @@ off: typeof off; | ||
getAnimationPromises: typeof getAnimationPromises; | ||
getPageData: (this: Swup, request: XMLHttpRequest) => import("./modules/getPageData.js").PageData | null; | ||
getPageData: (this: Swup, request: XMLHttpRequest) => import("./modules/getPageData").PageData | null; | ||
fetchPage: typeof fetchPage; | ||
@@ -79,8 +80,7 @@ getAnchorElement: (hash: string) => Element | null; | ||
destroy(): void; | ||
shouldIgnoreVisit(href: string, { el, event }?: { | ||
shouldIgnoreVisit(href: string, { el }?: { | ||
el?: Element; | ||
event?: Event; | ||
}): boolean; | ||
linkClickHandler(event: DelegateEvent<MouseEvent>): void; | ||
handleLinkToSamePage(url: string, hash: string, event: DelegateEvent<MouseEvent>): void; | ||
linkClickHandler(event: delegate.Event<MouseEvent>): void; | ||
handleLinkToSamePage(url: string, hash: string, event: delegate.Event<MouseEvent>): void; | ||
triggerWillOpenNewWindow(triggerEl: Element): boolean; | ||
@@ -87,0 +87,0 @@ popStateHandler(event: PopStateEvent): void; |
@@ -1,1 +0,1 @@ | ||
export * from './utils/index.js'; | ||
export * from './utils/index'; |
{ | ||
"name": "swup", | ||
"amdName": "Swup", | ||
"version": "3.1.1", | ||
"description": "Complete, flexible, extensible, and easy-to-use page transition library for your server-side rendered website.", | ||
"version": "4.0.0-rc.14", | ||
"description": "Versatile and extensible page transition library for server-side rendered websites", | ||
"type": "module", | ||
@@ -37,2 +37,3 @@ "source": "./src/Swup.ts", | ||
"test:unit": "vitest run --config ./vitest/vitest.config.ts", | ||
"test:unit:watch": "vitest --config ./vitest/vitest.config.ts", | ||
"test:e2e": "start-server-and-test test:e2e:start 8274 cy:run", | ||
@@ -63,3 +64,4 @@ "test:e2e:ci": "start-server-and-test test:e2e:start 8274 cy:run:record", | ||
"delegate-it": "^6.0.0", | ||
"opencollective-postinstall": "^2.0.2" | ||
"opencollective-postinstall": "^2.0.2", | ||
"path-to-regexp": "^6.2.1" | ||
}, | ||
@@ -75,7 +77,8 @@ "devDependencies": { | ||
"istanbul-lib-coverage": "^3.2.0", | ||
"jsdom": "^22.1.0", | ||
"microbundle": "^0.15.0", | ||
"nyc": "^15.1.0", | ||
"prettier": "^2.8.2", | ||
"start-server-and-test": "^1.14.0", | ||
"vitest": "^0.29.8" | ||
"start-server-and-test": "^2.0.0", | ||
"vitest": "^0.31.2" | ||
}, | ||
@@ -82,0 +85,0 @@ "collective": { |
@@ -19,3 +19,3 @@ <br> | ||
Complete, flexible, extensible, and easy-to-use page transition library for your server-side rendered website. | ||
Versatile and extensible page transition library for server-side rendered websites | ||
@@ -30,18 +30,36 @@ [Features](#features) • | ||
Swup is a library that helps you add page transitions to server-side rendered websites. It handles | ||
the complete lifecycle of a page visit by intercepting link clicks, loading the new page in the | ||
background, replacing the content and transitioning between the old and the new page. | ||
Swup adds page transitions to server-side rendered websites. It manages the complete lifecycle of a | ||
page visit by intercepting link clicks, loading the new page in the background and smoothly | ||
transitioning between the old and new content. | ||
Its goal is to make adding transitions to a site as simple as possible, while providing lots of | ||
other quality-of-life improvements. | ||
Its goal is to make it effortless to add page transitions to a site, while providing lots of other | ||
quality-of-life improvements. | ||
## Features | ||
- ✨ Auto-detects [CSS transitions](https://swup.js.org/getting-started/how-it-works) for perfect timing | ||
- 🔗 Updates URLs and preserves native browser history behavior | ||
- 📦 Uses a [cache](https://swup.js.org/api/cache) to speed up subsequent page loads | ||
- 💡 Offers [events](https://swup.js.org/events) for hooking into the lifecycle | ||
- 🔌 Has a powerful [plugin system](https://swup.js.org/plugins) and many official and third-party plugins | ||
- 🎨 Provides ready-to-go [themes](https://swup.js.org/themes) to get started quickly | ||
- ✏️ Works out of the box with [minimal markup](https://swup.js.org/getting-started/example/) | ||
- ✨ Auto-detects [CSS transitions](https://swup.js.org/getting-started/how-it-works/) for perfect timing | ||
- 🔗 Updates URLs and preserves native [browser history](https://swup.js.org/options/#animatehistorybrowsing) | ||
- 🏓 Manages scroll position between pages and anchor jump links | ||
- 🚀 Uses a [cache](https://swup.js.org/api/cache/) to speed up subsequent page loads | ||
- 📡 Offers [hooks](https://swup.js.org/hooks/) to customize and extend the page load lifecycle | ||
- 🔌 Has a powerful [plugin system](https://swup.js.org/plugins/) and many official and third-party plugins | ||
- 🎨 Provides ready-to-go [themes](https://swup.js.org/themes/) to get started quickly | ||
## Plugins | ||
Swup is small by design. Extended features can be added via [plugins](https://swup.js.org/plugins/): | ||
- Display a [progress bar](https://swup.js.org/plugins/progress-plugin/) while loading | ||
- Enable [smooth scrolling](https://swup.js.org/plugins/scroll-plugin/) between visits | ||
- Update [meta tags and stylesheets](https://swup.js.org/plugins/head-plugin/) after page loads | ||
- Add support for [preloading pages](https://swup.js.org/plugins/preload-plugin/) in the background | ||
- Improve [accessibility](https://swup.js.org/plugins/a11y-plugin/) for screen readers | ||
- Perform your [animations in JS](https://swup.js.org/plugins/js-plugin/) instead of CSS transitions | ||
- Animate [form submissions](https://swup.js.org/plugins/forms-plugin/) | ||
- Get help in [debug mode](https://swup.js.org/plugins/debug-plugin/) | ||
Check out the list of [all official plugins](https://swup.js.org/plugins/) as well as | ||
[third-party integrations](https://swup.js.org/third-party-integrations/). | ||
## Examples | ||
@@ -48,0 +66,0 @@ |
@@ -6,2 +6,3 @@ import { DelegateEvent } from 'delegate-it'; | ||
import Swup, { Options, Plugin } from '../index.js'; | ||
import * as SwupTS from '../Swup.js'; | ||
@@ -55,2 +56,6 @@ const baseUrl = window.location.origin; | ||
}); | ||
it('UMD compatibility: Swup.ts should only have a default export', () => { | ||
expect(Object.keys(SwupTS)).toEqual(['default']); | ||
}); | ||
}); | ||
@@ -87,3 +92,3 @@ | ||
const swup = new Swup({ ignoreVisit }); | ||
swup.loadPage({ url: '/path/' }); | ||
swup.loadPage('/path/'); | ||
@@ -90,0 +95,0 @@ expect(ignoreVisit.mock.calls).toHaveLength(1); |
// Re-export all helpers to allow custom package export path | ||
// e.g. import { getPageData } from 'swup/helpers' | ||
// e.g. import { updateHistoryRecord } from 'swup' | ||
@@ -8,7 +8,5 @@ export { classify } from './helpers/classify.js'; | ||
export { delegateEvent } from './helpers/delegateEvent.js'; | ||
export { getDataFromHtml } from './helpers/getDataFromHtml.js'; | ||
export { fetch } from './helpers/fetch.js'; | ||
export { getCurrentUrl } from './helpers/getCurrentUrl.js'; | ||
export { Location } from './helpers/Location.js'; | ||
export { markSwupElements } from './helpers/markSwupElements.js'; | ||
export { cleanupAnimationClasses } from './helpers/cleanupAnimationClasses.js'; | ||
export { matchPath } from './helpers/matchPath.js'; |
@@ -15,2 +15,3 @@ import delegate, { DelegateEventHandler, DelegateOptions, EventType } from 'delegate-it'; | ||
const controller = new AbortController(); | ||
options = { ...options, signal: controller.signal }; | ||
delegate<string, ParseSelector<Selector, HTMLElement>, TEvent>( | ||
@@ -17,0 +18,0 @@ selector, |
@@ -8,7 +8,10 @@ /** | ||
export class Location extends URL { | ||
constructor(url: string, base: string = document.baseURI) { | ||
constructor(url: URL | string, base: string = document.baseURI) { | ||
super(url.toString(), base); | ||
} | ||
get url() { | ||
/** | ||
* The full local path including query params. | ||
*/ | ||
get url(): string { | ||
return this.pathname + this.search; | ||
@@ -19,7 +22,7 @@ } | ||
* Instantiate a Location from an element's href attribute | ||
* @param {Element} el | ||
* @param el | ||
* @return new Location instance | ||
*/ | ||
static fromElement(el: HTMLAnchorElement): Location { | ||
const href = el.getAttribute('href') || el.getAttribute('xlink:href'); | ||
static fromElement(el: Element): Location { | ||
const href = el.getAttribute('href') || el.getAttribute('xlink:href') || ''; | ||
return new Location(href!); | ||
@@ -30,8 +33,8 @@ } | ||
* Instantiate a Location from a URL object or string | ||
* @param {URL|string} url | ||
* @param url | ||
* @return new Location instance | ||
*/ | ||
static fromUrl(url: string): Location { | ||
static fromUrl(url: URL | string): Location { | ||
return new Location(url); | ||
} | ||
} |
@@ -1,4 +0,7 @@ | ||
import Swup, { Options } from './Swup.js'; | ||
import { Plugin } from './modules/plugins.js'; | ||
import { Handler } from './modules/events.js'; | ||
import Swup, { type Options } from './Swup.js'; | ||
import type { CacheData } from './modules/Cache.js'; | ||
import type { Context, PageContext } from './modules/Context.js'; | ||
import type { Plugin } from './modules/plugins.js'; | ||
import type { HookDefinitions, Handler } from './modules/Hooks.js'; | ||
import type { Path } from 'path-to-regexp'; | ||
@@ -10,2 +13,2 @@ export default Swup; | ||
export type { Options, Plugin, Handler }; | ||
export type { Options, Plugin, CacheData, Context, PageContext, HookDefinitions, Handler, Path }; |
@@ -1,13 +0,10 @@ | ||
import { getCurrentUrl, Location } from '../helpers.js'; | ||
import Swup from '../Swup.js'; | ||
import { PageData } from './getPageData.js'; | ||
import { Location } from '../helpers.js'; | ||
import { PageData } from './fetchPage.js'; | ||
export interface PageRecord extends PageData { | ||
url: string; | ||
responseURL: string; | ||
} | ||
export interface CacheData extends PageData {} | ||
export class Cache { | ||
pages: Record<string, PageRecord> = {}; | ||
last: PageRecord | null = null; | ||
swup: Swup; | ||
private swup: Swup; | ||
private pages: Map<string, CacheData> = new Map(); | ||
@@ -18,39 +15,52 @@ constructor(swup: Swup) { | ||
getCacheUrl(url: string): string { | ||
return this.swup.resolveUrl(Location.fromUrl(url).url); | ||
get size() { | ||
return this.pages.size; | ||
} | ||
cacheUrl(page: PageRecord) { | ||
page.url = this.getCacheUrl(page.url); | ||
if (page.url in this.pages === false) { | ||
this.pages[page.url] = page; | ||
} | ||
page.responseURL = this.getCacheUrl(page.responseURL); | ||
this.last = this.pages[page.url]; | ||
this.swup.log(`Cache (${Object.keys(this.pages).length})`, this.pages); | ||
get all() { | ||
return this.pages; | ||
} | ||
getPage(url: string): PageRecord { | ||
url = this.getCacheUrl(url); | ||
return this.pages[url]; | ||
public has(url: string): boolean { | ||
return this.pages.has(this.resolve(url)); | ||
} | ||
getCurrentPage(): PageRecord { | ||
return this.getPage(getCurrentUrl()); | ||
public get(url: string): CacheData | undefined { | ||
return this.pages.get(this.resolve(url)); | ||
} | ||
exists(url: string): boolean { | ||
url = this.getCacheUrl(url); | ||
return url in this.pages; | ||
public set(url: string, page: CacheData) { | ||
url = this.resolve(url); | ||
page = { ...page, url }; | ||
this.pages.set(url, page); | ||
this.swup.hooks.triggerSync('pageCached', { page }); | ||
} | ||
empty(): void { | ||
this.pages = {}; | ||
this.last = null; | ||
this.swup.log('Cache cleared'); | ||
public update(url: string, page: CacheData) { | ||
url = this.resolve(url); | ||
page = { ...this.get(url), ...page, url }; | ||
this.pages.set(url, page); | ||
} | ||
remove(url: string): void { | ||
delete this.pages[this.getCacheUrl(url)]; | ||
public delete(url: string): void { | ||
this.pages.delete(this.resolve(url)); | ||
} | ||
public clear(): void { | ||
this.pages.clear(); | ||
this.swup.hooks.triggerSync('cacheCleared'); | ||
} | ||
public prune(predicate: (url: string, page: CacheData) => boolean): void { | ||
this.pages.forEach((page, url) => { | ||
if (predicate(url, page)) { | ||
this.delete(url); | ||
} | ||
}); | ||
} | ||
private resolve(urlToResolve: string): string { | ||
const { url } = Location.fromUrl(urlToResolve); | ||
return this.swup.resolveUrl(url); | ||
} | ||
} |
@@ -0,24 +1,26 @@ | ||
import Swup from '../Swup.js'; | ||
import { nextTick } from '../utils.js'; | ||
import Swup from '../Swup.js'; | ||
import { PageRenderOptions } from './renderPage.js'; | ||
export const enterPage = function (this: Swup, { event, skipTransition }: PageRenderOptions = {}) { | ||
if (skipTransition) { | ||
this.triggerEvent('transitionEnd', event); | ||
this.cleanupAnimationClasses(); | ||
return [Promise.resolve()]; | ||
export const enterPage = async function (this: Swup) { | ||
if (this.context.transition.animate) { | ||
const animation = this.hooks.trigger( | ||
'awaitAnimation', | ||
{ selector: this.options.animationSelector, direction: 'in' }, | ||
async (context, { selector, direction }) => { | ||
await Promise.all(this.getAnimationPromises({ selector, direction })); | ||
} | ||
); | ||
await nextTick(); | ||
await this.hooks.trigger('animationInStart', undefined, () => { | ||
document.documentElement.classList.remove('is-animating'); | ||
}); | ||
await animation; | ||
await this.hooks.trigger('animationInDone'); | ||
} | ||
nextTick(() => { | ||
this.triggerEvent('animationInStart'); | ||
document.documentElement.classList.remove('is-animating'); | ||
await this.hooks.trigger('transitionEnd', undefined, () => { | ||
this.cleanupAnimationClasses(); | ||
}); | ||
const animationPromises = this.getAnimationPromises('in'); | ||
Promise.all(animationPromises).then(() => { | ||
this.triggerEvent('animationInDone'); | ||
this.triggerEvent('transitionEnd', event); | ||
this.cleanupAnimationClasses(); | ||
}); | ||
return animationPromises; | ||
this.context = this.createContext({ to: undefined }); | ||
}; |
import Swup from '../Swup.js'; | ||
import { fetch } from '../helpers.js'; | ||
import { TransitionOptions } from './loadPage.js'; | ||
import { PageRecord } from './Cache.js'; | ||
import { Location } from '../helpers.js'; | ||
export function fetchPage(this: Swup, data: TransitionOptions): Promise<PageRecord> { | ||
const headers = this.options.requestHeaders; | ||
const { url } = data; | ||
export interface PageData { | ||
url: string; | ||
html: string; | ||
} | ||
if (this.cache.exists(url)) { | ||
this.triggerEvent('pageRetrievedFromCache'); | ||
return Promise.resolve(this.cache.getPage(url)); | ||
export interface FetchOptions extends RequestInit { | ||
method?: 'GET' | 'POST'; | ||
body?: string | FormData | URLSearchParams; | ||
headers?: Record<string, string>; | ||
} | ||
export class FetchError extends Error { | ||
url: string; | ||
status: number; | ||
constructor(message: string, details: { url: string; status: number }) { | ||
super(message); | ||
this.name = 'FetchError'; | ||
this.url = details.url; | ||
this.status = details.status; | ||
} | ||
} | ||
return new Promise((resolve, reject) => { | ||
fetch({ ...data, headers }, (response) => { | ||
if (response.status === 500) { | ||
this.triggerEvent('serverError'); | ||
reject(url); | ||
return; | ||
} | ||
// get json data | ||
const page = this.getPageData(response); | ||
if (!page || !page.blocks.length) { | ||
reject(url); | ||
return; | ||
} | ||
// render page | ||
const cacheablePageData = { ...page, url }; | ||
this.cache.cacheUrl(cacheablePageData); | ||
this.triggerEvent('pageLoaded'); | ||
resolve(cacheablePageData); | ||
}); | ||
}); | ||
/** | ||
* Fetch a page from the server, return it and cache it. | ||
*/ | ||
export async function fetchPage( | ||
this: Swup, | ||
url: URL | string, | ||
options: FetchOptions & { triggerHooks?: boolean } = {} | ||
): Promise<PageData> { | ||
const { url: requestUrl } = Location.fromUrl(url); | ||
if (this.cache.has(requestUrl)) { | ||
const page = this.cache.get(requestUrl) as PageData; | ||
if (options.triggerHooks !== false) { | ||
await this.hooks.trigger('pageLoaded', { page, cache: true }); | ||
} | ||
return page; | ||
} | ||
const headers = { ...this.options.requestHeaders, ...options.headers }; | ||
options = { ...options, headers }; | ||
// Allow hooking before this and returning a custom response-like object (e.g. custom fetch implementation) | ||
const response = await this.hooks.trigger( | ||
'fetchPage', | ||
{ url: requestUrl, options }, | ||
async (context, { url, options, response }) => await (response || fetch(url, options)) | ||
); | ||
const { status, url: responseUrl } = response; | ||
const html = await response.text(); | ||
if (status === 500) { | ||
this.hooks.trigger('serverError', { status, response, url: responseUrl }); | ||
throw new FetchError(`Server error: ${responseUrl}`, { status, url: responseUrl }); | ||
} | ||
if (!html) { | ||
throw new FetchError(`Empty response: ${responseUrl}`, { status, url: responseUrl }); | ||
} | ||
// Resolve real url after potential redirect | ||
const { url: finalUrl } = new Location(responseUrl); | ||
const page = { url: finalUrl, html }; | ||
// Only save cache entry for non-redirects | ||
if (requestUrl === finalUrl) { | ||
this.cache.set(page.url, page); | ||
} | ||
if (options.triggerHooks !== false) { | ||
await this.hooks.trigger('pageLoaded', { page, cache: false }); | ||
} | ||
return page; | ||
} |
import { queryAll, toMs } from '../utils.js'; | ||
import Swup from '../Swup.js'; | ||
import Swup, { Options } from '../Swup.js'; | ||
// Transition property/event sniffing | ||
let transitionProp = 'transition'; | ||
let transitionEndEvent = 'transitionend'; | ||
let animationProp = 'animation'; | ||
let animationEndEvent = 'animationend'; | ||
const TRANSITION = 'transition'; | ||
const ANIMATION = 'animation'; | ||
if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) { | ||
transitionProp = 'WebkitTransition'; | ||
transitionEndEvent = 'webkitTransitionEnd'; | ||
} | ||
type AnimationTypes = typeof TRANSITION | typeof ANIMATION; | ||
type AnimationProperties = 'Delay' | 'Duration'; | ||
type AnimationStyleKeys = `${AnimationTypes}${AnimationProperties}` | 'transitionProperty'; | ||
type AnimationStyleDeclarations = Pick<CSSStyleDeclaration, AnimationStyleKeys>; | ||
if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) { | ||
animationProp = 'WebkitAnimation'; | ||
animationEndEvent = 'webkitAnimationEnd'; | ||
} | ||
export type AnimationDirection = 'in' | 'out'; | ||
/** | ||
* Get an array of Promises that resolve when all animations are done on the page. | ||
* @note We don't make use of the `direction` argument, but it's required by JS plugin | ||
*/ | ||
export function getAnimationPromises( | ||
this: Swup, | ||
// we don't use this argument, but JS plugin depends on it with | ||
// its own version of getAnimationPromises, so it must be specified when | ||
// getAnimationPromises is being used | ||
animationType: 'in' | 'out' | ||
{ | ||
elements, | ||
selector | ||
}: { | ||
selector: Options['animationSelector']; | ||
elements?: NodeListOf<HTMLElement> | HTMLElement[]; | ||
direction?: AnimationDirection; | ||
} | ||
): Promise<void>[] { | ||
const selector = this.options.animationSelector; | ||
// Use array of a single resolved promise instead of an empty array to allow | ||
// possible future use with Promise.race() which requires an actual value | ||
const resolved = [Promise.resolve()]; | ||
// Allow usage of swup without animations | ||
if (selector === false) { | ||
// Use array of a single resolved promise instead of an empty array to allow | ||
// possible future use with Promise.race() which requires an actual value | ||
return [Promise.resolve()]; | ||
if (selector === false && !elements) { | ||
return resolved; | ||
} | ||
const animatedElements = queryAll(selector, document.body); | ||
// Warn if no elements match the animationSelector, but keep things going | ||
if (!animatedElements.length) { | ||
console.warn(`[swup] No elements found matching animationSelector \`${selector}\``); | ||
return [Promise.resolve()]; | ||
// Allow passing in elements | ||
let animatedElements: HTMLElement[] = []; | ||
if (elements) { | ||
animatedElements = Array.from(elements); | ||
} else if (selector) { | ||
animatedElements = queryAll(selector, document.body); | ||
// Warn if no elements match the selector, but keep things going | ||
if (!animatedElements.length) { | ||
console.warn(`[swup] No elements found matching animationSelector \`${selector}\``); | ||
return resolved; | ||
} | ||
} | ||
@@ -49,6 +56,8 @@ | ||
if (!animationPromises.length) { | ||
console.warn( | ||
`[swup] No CSS animation duration defined on elements matching \`${selector}\`` | ||
); | ||
return [Promise.resolve()]; | ||
if (selector) { | ||
console.warn( | ||
`[swup] No CSS animation duration defined on elements matching \`${selector}\`` | ||
); | ||
} | ||
return resolved; | ||
} | ||
@@ -59,5 +68,2 @@ | ||
const isTransitionOrAnimationEvent = (event: any): event is TransitionEvent | AnimationEvent => | ||
[transitionEndEvent, animationEndEvent].includes(event.type); | ||
function getAnimationPromiseForElement(element: Element): Promise<void> | undefined { | ||
@@ -72,3 +78,3 @@ const { type, timeout, propCount } = getTransitionInfo(element); | ||
return new Promise((resolve) => { | ||
const endEvent = type === 'transition' ? transitionEndEvent : animationEndEvent; | ||
const endEvent = `${type}end`; | ||
const startTime = performance.now(); | ||
@@ -114,43 +120,25 @@ let propsTransitioned = 0; | ||
export function getTransitionInfo( | ||
element: Element, | ||
expectedType: 'animation' | 'transition' | null = null | ||
) { | ||
const styles = window.getComputedStyle(element); | ||
export function getTransitionInfo(element: Element, expectedType?: AnimationTypes) { | ||
const styles = window.getComputedStyle(element) as AnimationStyleDeclarations; | ||
// not sure what to do about the below mess other than casting, but it's a mess | ||
const transitionDelay = `${transitionProp}Delay` as keyof CSSStyleDeclaration; | ||
const transitionDuration = `${transitionProp}Duration` as keyof CSSStyleDeclaration; | ||
const animationDelay = `${animationProp}Delay` as keyof CSSStyleDeclaration; | ||
const animationDuration = `${animationProp}Duration` as keyof CSSStyleDeclaration; | ||
const transitionDelays = ( | ||
styles[transitionDelay] as CSSStyleDeclaration['transitionDelay'] | ||
).split(', '); | ||
const transitionDurations = ( | ||
(styles[transitionDuration] || '') as CSSStyleDeclaration['transitionDuration'] | ||
).split(', '); | ||
const transitionDelays = getStyleProperties(styles, `${TRANSITION}Delay`); | ||
const transitionDurations = getStyleProperties(styles, `${TRANSITION}Duration`); | ||
const transitionTimeout = calculateTimeout(transitionDelays, transitionDurations); | ||
const animationDelays = ( | ||
(styles[animationDelay] || '') as CSSStyleDeclaration['animationDelay'] | ||
).split(', '); | ||
const animationDurations = ( | ||
(styles[animationDuration] || '') as CSSStyleDeclaration['animationDuration'] | ||
).split(', '); | ||
const animationDelays = getStyleProperties(styles, `${ANIMATION}Delay`); | ||
const animationDurations = getStyleProperties(styles, `${ANIMATION}Duration`); | ||
const animationTimeout = calculateTimeout(animationDelays, animationDurations); | ||
let type: string | null = ''; | ||
let type: AnimationTypes | null = null; | ||
let timeout = 0; | ||
let propCount = 0; | ||
if (expectedType === 'transition') { | ||
if (expectedType === TRANSITION) { | ||
if (transitionTimeout > 0) { | ||
type = 'transition'; | ||
type = TRANSITION; | ||
timeout = transitionTimeout; | ||
propCount = transitionDurations.length; | ||
} | ||
} else if (expectedType === 'animation') { | ||
} else if (expectedType === ANIMATION) { | ||
if (animationTimeout > 0) { | ||
type = 'animation'; | ||
type = ANIMATION; | ||
timeout = animationTimeout; | ||
@@ -161,10 +149,5 @@ propCount = animationDurations.length; | ||
timeout = Math.max(transitionTimeout, animationTimeout); | ||
type = | ||
timeout > 0 | ||
? transitionTimeout > animationTimeout | ||
? 'transition' | ||
: 'animation' | ||
: null; | ||
type = timeout > 0 ? (transitionTimeout > animationTimeout ? TRANSITION : ANIMATION) : null; | ||
propCount = type | ||
? type === 'transition' | ||
? type === TRANSITION | ||
? transitionDurations.length | ||
@@ -182,3 +165,11 @@ : animationDurations.length | ||
function calculateTimeout(delays: string[], durations: string[]) { | ||
function isTransitionOrAnimationEvent(event: any): event is TransitionEvent | AnimationEvent { | ||
return [`${TRANSITION}end`, `${ANIMATION}end`].includes(event.type); | ||
} | ||
function getStyleProperties(styles: AnimationStyleDeclarations, key: AnimationStyleKeys): string[] { | ||
return (styles[key] || '').split(', '); | ||
} | ||
function calculateTimeout(delays: string[], durations: string[]): number { | ||
while (delays.length < durations.length) { | ||
@@ -185,0 +176,0 @@ delays = delays.concat(delays); |
import Swup from '../Swup.js'; | ||
import { PageRenderOptions } from './renderPage.js'; | ||
import { classify } from '../helpers.js'; | ||
export const leavePage = function (this: Swup, { event, skipTransition }: PageRenderOptions = {}) { | ||
const isHistoryVisit = event instanceof PopStateEvent; | ||
if (skipTransition) { | ||
this.triggerEvent('animationSkipped'); | ||
return [Promise.resolve()]; | ||
export const leavePage = async function (this: Swup) { | ||
if (!this.context.transition.animate) { | ||
await this.hooks.trigger('animationSkipped'); | ||
return; | ||
} | ||
this.triggerEvent('animationOutStart'); | ||
await this.hooks.trigger('animationOutStart', undefined, () => { | ||
document.documentElement.classList.add('is-changing', 'is-leaving', 'is-animating'); | ||
if (this.context.history.popstate) { | ||
document.documentElement.classList.add('is-popstate'); | ||
} | ||
if (this.context.transition.name) { | ||
document.documentElement.classList.add(`to-${classify(this.context.transition.name)}`); | ||
} | ||
}); | ||
// handle classes | ||
document.documentElement.classList.add('is-changing', 'is-leaving', 'is-animating'); | ||
if (isHistoryVisit) { | ||
document.documentElement.classList.add('is-popstate'); | ||
} | ||
await this.hooks.trigger( | ||
'awaitAnimation', | ||
{ selector: this.options.animationSelector, direction: 'out' }, | ||
async (context, { selector, direction }) => { | ||
await Promise.all(this.getAnimationPromises({ selector, direction })); | ||
} | ||
); | ||
// animation promise stuff | ||
const animationPromises: Promise<void>[] = this.getAnimationPromises('out'); | ||
Promise.all(animationPromises).then(() => { | ||
this.triggerEvent('animationOutDone'); | ||
}); | ||
return animationPromises; | ||
await this.hooks.trigger('animationOutDone'); | ||
}; |
@@ -1,23 +0,20 @@ | ||
import { classify, createHistoryRecord, updateHistoryRecord, getCurrentUrl } from '../helpers.js'; | ||
import Swup from '../Swup.js'; | ||
import { PageRecord } from './Cache.js'; | ||
import { createHistoryRecord, updateHistoryRecord, getCurrentUrl, Location } from '../helpers.js'; | ||
import { FetchOptions } from '../modules/fetchPage.js'; | ||
import { ContextInitOptions } from './Context.js'; | ||
export type HistoryAction = 'push' | 'replace'; | ||
export type TransitionOptions = { | ||
url: string; | ||
customTransition?: string; | ||
history?: HistoryAction; | ||
}; | ||
export type PageLoadOptions = { | ||
url: string; | ||
customTransition?: string; | ||
animate?: boolean; | ||
transition?: string; | ||
history?: HistoryAction; | ||
event?: PopStateEvent; | ||
}; | ||
export function loadPage(this: Swup, data: TransitionOptions) { | ||
const { url } = data; | ||
export function loadPage( | ||
this: Swup, | ||
url: string, | ||
options: PageLoadOptions & FetchOptions = {}, | ||
context: Omit<ContextInitOptions, 'to'> = {} | ||
) { | ||
// Check if the visit should be ignored | ||
@@ -27,56 +24,77 @@ if (this.shouldIgnoreVisit(url)) { | ||
} else { | ||
this.performPageLoad(data); | ||
const { url: to, hash } = Location.fromUrl(url); | ||
this.context = this.createContext({ ...context, to, hash }); | ||
this.performPageLoad(to, options); | ||
} | ||
} | ||
export function performPageLoad(this: Swup, data: PageLoadOptions) { | ||
const { url, event, customTransition, history: historyAction = 'push' } = data ?? {}; | ||
export async function performPageLoad( | ||
this: Swup, | ||
url: string, | ||
options: PageLoadOptions & FetchOptions = {} | ||
) { | ||
if (typeof url !== 'string') { | ||
throw new Error(`loadPage requires a URL parameter`); | ||
} | ||
const isHistoryVisit = event instanceof PopStateEvent; | ||
const skipTransition = this.shouldSkipTransition({ url, event }); | ||
const { url: requestedUrl } = Location.fromUrl(url); | ||
const { transition, animate, history: historyAction } = options; | ||
options.referrer = options.referrer || this.currentPageUrl; | ||
this.triggerEvent('transitionStart', event); | ||
if (animate === false) { | ||
this.context.transition.animate = false; | ||
} | ||
if (historyAction) { | ||
this.context.history.action = historyAction; | ||
} | ||
// set transition object | ||
this.updateTransition(getCurrentUrl(), url, customTransition); | ||
if (customTransition != null) { | ||
document.documentElement.classList.add(`to-${classify(customTransition)}`); | ||
if (!this.context.transition.animate) { | ||
document.documentElement.classList.remove('is-animating'); | ||
this.cleanupAnimationClasses(); | ||
} else if (transition) { | ||
this.context.transition.name = transition; | ||
} | ||
// start/skip animation | ||
const animationPromises = this.leavePage({ event, skipTransition }); | ||
try { | ||
await this.hooks.trigger('transitionStart'); | ||
const animationPromise = this.leavePage(); | ||
const pagePromise = this.hooks.trigger( | ||
'loadPage', | ||
{ url, options }, | ||
async (context, { url, options, page }) => await (page || this.fetchPage(url, options)) | ||
); | ||
// Load page data | ||
const fetchPromise = this.fetchPage(data); | ||
// create history record if this is not a popstate call (with or without anchor) | ||
if (!this.context.history.popstate) { | ||
const newUrl = url + (this.context.scroll.target || ''); | ||
if (this.context.history.action === 'replace') { | ||
updateHistoryRecord(newUrl); | ||
} else { | ||
createHistoryRecord(newUrl); | ||
} | ||
} | ||
// create history record if this is not a popstate call (with or without anchor) | ||
if (!isHistoryVisit) { | ||
const historyUrl = url + (this.scrollToElement || ''); | ||
if (historyAction === 'replace') { | ||
updateHistoryRecord(historyUrl); | ||
} else { | ||
createHistoryRecord(historyUrl); | ||
this.currentPageUrl = getCurrentUrl(); | ||
// when everything is ready, render the page | ||
const [page] = await Promise.all([pagePromise, animationPromise]); | ||
this.renderPage(requestedUrl, page); | ||
} catch (error: unknown) { | ||
// Return early if error is undefined (probably aborted preload request) | ||
if (!error) { | ||
return; | ||
} | ||
} | ||
this.currentPageUrl = getCurrentUrl(); | ||
// Log to console as we swallow almost all hook errors | ||
console.error(error); | ||
// when everything is ready, render the page | ||
Promise.all<PageRecord | void>([fetchPromise, ...animationPromises]) | ||
.then(([pageData]) => { | ||
this.renderPage(pageData as PageRecord, { event, skipTransition }); | ||
}) | ||
.catch((errorUrl) => { | ||
// Return early if errorUrl is not defined (probably aborted preload request) | ||
if (errorUrl === undefined) return; | ||
// Rewrite `skipPopStateHandling` to redirect manually when `history.go` is processed | ||
this.options.skipPopStateHandling = () => { | ||
window.location.href = requestedUrl; | ||
return true; | ||
}; | ||
// Rewrite `skipPopStateHandling` to redirect manually when `history.go` is processed | ||
this.options.skipPopStateHandling = () => { | ||
window.location = errorUrl; | ||
return true; | ||
}; | ||
// Go back to the actual page we're still at | ||
history.go(-1); | ||
}); | ||
// Go back to the actual page we're still at | ||
history.go(-1); | ||
} | ||
} |
@@ -1,53 +0,62 @@ | ||
import { Location, updateHistoryRecord, getCurrentUrl } from '../helpers.js'; | ||
import { updateHistoryRecord, getCurrentUrl } from '../helpers.js'; | ||
import Swup from '../Swup.js'; | ||
import { PageRecord } from './Cache.js'; | ||
import { PageData } from './fetchPage.js'; | ||
export type PageRenderOptions = { | ||
event?: PopStateEvent; | ||
skipTransition?: boolean; | ||
}; | ||
export const renderPage = async function (this: Swup, requestedUrl: string, page: PageData) { | ||
const { url } = page; | ||
export const renderPage = function ( | ||
this: Swup, | ||
page: PageRecord, | ||
{ event, skipTransition }: PageRenderOptions = {} | ||
) { | ||
document.documentElement.classList.remove('is-leaving'); | ||
// do nothing if another page was requested in the meantime | ||
if (!this.isSameResolvedUrl(getCurrentUrl(), page.url)) { | ||
if (!this.isSameResolvedUrl(getCurrentUrl(), requestedUrl)) { | ||
return; | ||
} | ||
const { url } = Location.fromUrl(page.responseURL); | ||
// update cache and state if the url was redirected | ||
// update state if the url was redirected | ||
if (!this.isSameResolvedUrl(getCurrentUrl(), url)) { | ||
this.cache.cacheUrl({ ...page, url }); | ||
updateHistoryRecord(url); | ||
this.currentPageUrl = getCurrentUrl(); | ||
updateHistoryRecord(url); | ||
this.context.to!.url = this.currentPageUrl; | ||
} | ||
// only add for page loads with transitions | ||
if (!skipTransition) { | ||
if (this.context.transition.animate) { | ||
document.documentElement.classList.add('is-rendering'); | ||
} | ||
this.triggerEvent('willReplaceContent', event); | ||
// replace content: allow handlers and plugins to overwrite paga data and containers | ||
await this.hooks.trigger( | ||
'replaceContent', | ||
{ page, containers: this.context.containers }, | ||
(context, { page, containers }) => { | ||
this.replaceContent(page, { containers }); | ||
} | ||
); | ||
this.replaceContent(page).then(() => { | ||
this.triggerEvent('contentReplaced', event); | ||
this.triggerEvent('pageView', event); | ||
// empty cache if it's disabled (in case preload plugin filled it) | ||
if (!this.options.cache) { | ||
this.cache.empty(); | ||
await this.hooks.trigger( | ||
'scrollToContent', | ||
{ options: { behavior: 'auto' } }, | ||
(context, { options }) => { | ||
if (this.context.scroll.target) { | ||
const target = this.getAnchorElement(this.context.scroll.target); | ||
if (target) { | ||
target.scrollIntoView(options); | ||
return; | ||
} | ||
} | ||
if (this.context.scroll.reset) { | ||
window.scrollTo(0, 0); | ||
} | ||
} | ||
); | ||
// Perform in transition | ||
this.enterPage({ event, skipTransition }); | ||
await this.hooks.trigger('pageView', { url: this.currentPageUrl, title: document.title }); | ||
// reset scroll-to element | ||
this.scrollToElement = null; | ||
}); | ||
// empty cache if it's disabled (in case preload plugin filled it) | ||
if (!this.options.cache) { | ||
this.cache.clear(); | ||
} | ||
// Perform in transition | ||
this.enterPage(); | ||
}; |
@@ -0,26 +1,35 @@ | ||
import Swup, { Options } from '../Swup.js'; | ||
import { PageData } from './fetchPage.js'; | ||
/** | ||
* Perform the replacement of content after loading a page. | ||
* | ||
* This method can be replaced or augmented by plugins to allow pausing. | ||
* | ||
* It takes an object with the page data as return from `getPageData` and has to | ||
* return a Promise that resolves once all content has been replaced and the | ||
* site is ready to start animating in the new page. | ||
* | ||
* @param {object} page The page object | ||
* @returns Promise | ||
* It takes an object with the page data as returned from `fetchPage` and a list | ||
* of container selectors to replace. | ||
*/ | ||
export const replaceContent = function ({ blocks, title }: { blocks: string[]; title: string }) { | ||
// Replace content blocks | ||
blocks.forEach((html, i) => { | ||
// we know the block exists at this point | ||
const block = document.body.querySelector(`[data-swup="${i}"]`)!; | ||
block.outerHTML = html; | ||
}); | ||
export const replaceContent = function ( | ||
this: Swup, | ||
{ html }: PageData, | ||
{ containers }: { containers: Options['containers'] } = this.options | ||
): void { | ||
const doc = new DOMParser().parseFromString(html, 'text/html'); | ||
// Update browser title | ||
const title = doc.querySelector('title')?.innerText || ''; | ||
document.title = title; | ||
// Return a Promise to allow plugins to defer | ||
return Promise.resolve(); | ||
// Update content containers | ||
containers.forEach((selector) => { | ||
const currentEl = document.querySelector(selector); | ||
const incomingEl = doc.querySelector(selector); | ||
if (!currentEl) { | ||
console.warn(`[swup] Container missing in current document: ${selector}`); | ||
return; | ||
} | ||
if (!incomingEl) { | ||
console.warn(`[swup] Container missing in incoming document: ${selector}`); | ||
return; | ||
} | ||
currentEl.replaceWith(incomingEl); | ||
}); | ||
}; |
254
src/Swup.ts
@@ -10,3 +10,2 @@ import { DelegateEvent } from 'delegate-it'; | ||
Location, | ||
markSwupElements, | ||
updateHistoryRecord | ||
@@ -17,6 +16,6 @@ } from './helpers.js'; | ||
import { Cache } from './modules/Cache.js'; | ||
import { Context, createContext } from './modules/Context.js'; | ||
import { enterPage } from './modules/enterPage.js'; | ||
import { getAnchorElement } from './modules/getAnchorElement.js'; | ||
import { getAnimationPromises } from './modules/getAnimationPromises.js'; | ||
import { getPageData } from './modules/getPageData.js'; | ||
import { fetchPage } from './modules/fetchPage.js'; | ||
@@ -26,9 +25,6 @@ import { leavePage } from './modules/leavePage.js'; | ||
import { replaceContent } from './modules/replaceContent.js'; | ||
import { on, off, triggerEvent, Handlers } from './modules/events.js'; | ||
import { Handler, HookName, Hooks } from './modules/Hooks.js'; | ||
import { use, unuse, findPlugin, Plugin } from './modules/plugins.js'; | ||
import { renderPage } from './modules/renderPage.js'; | ||
import { updateTransition, shouldSkipTransition } from './modules/transitions.js'; | ||
import { queryAll } from './utils.js'; | ||
export type Transition = { | ||
@@ -58,29 +54,3 @@ from?: string; | ||
export default class Swup { | ||
version = version; | ||
_handlers: Handlers = { | ||
animationInDone: [], | ||
animationInStart: [], | ||
animationOutDone: [], | ||
animationOutStart: [], | ||
animationSkipped: [], | ||
clickLink: [], | ||
contentReplaced: [], | ||
disabled: [], | ||
enabled: [], | ||
openPageInNewTab: [], | ||
pageLoaded: [], | ||
pageRetrievedFromCache: [], | ||
pageView: [], | ||
popState: [], | ||
samePage: [], | ||
samePageWithHash: [], | ||
serverError: [], | ||
transitionStart: [], | ||
transitionEnd: [], | ||
willReplaceContent: [] | ||
}; | ||
// variable for anchor to scroll to after render | ||
scrollToElement: string | null = null; | ||
version: string = version; | ||
// variable for save options | ||
@@ -90,12 +60,12 @@ options: Options; | ||
plugins: Plugin[] = []; | ||
// variable for current transition info object | ||
transition: Transition = {}; | ||
// context data | ||
context: Context; | ||
// cache instance | ||
cache: Cache; | ||
// hook registry | ||
hooks: Hooks; | ||
// variable for keeping event listeners from "delegate" | ||
delegatedListeners: DelegatedListeners = {}; | ||
// allows us to compare the current and new path inside popStateHandler | ||
currentPageUrl = getCurrentUrl(); | ||
// variable for keeping event listeners from "delegate" | ||
delegatedListeners: DelegatedListeners = {}; | ||
// so we are able to remove the listener | ||
boundPopStateHandler: (event: PopStateEvent) => void; | ||
@@ -108,10 +78,4 @@ loadPage = loadPage; | ||
enterPage = enterPage; | ||
triggerEvent = triggerEvent; | ||
delegateEvent = delegateEvent; | ||
on = on; | ||
off = off; | ||
updateTransition = updateTransition; | ||
shouldSkipTransition = shouldSkipTransition; | ||
getAnimationPromises = getAnimationPromises; | ||
getPageData = getPageData; | ||
fetchPage = fetchPage; | ||
@@ -125,2 +89,3 @@ getAnchorElement = getAnchorElement; | ||
cleanupAnimationClasses = cleanupAnimationClasses; | ||
createContext = createContext; | ||
@@ -147,24 +112,30 @@ defaults: Options = { | ||
this.boundPopStateHandler = this.popStateHandler.bind(this); | ||
this.linkClickHandler = this.linkClickHandler.bind(this); | ||
this.popStateHandler = this.popStateHandler.bind(this); | ||
this.cache = new Cache(this); | ||
this.hooks = new Hooks(this); | ||
this.context = this.createContext({ to: undefined }); | ||
if (!this.checkRequirements()) { | ||
return; | ||
} | ||
this.enable(); | ||
} | ||
enable() { | ||
// Check for Promise support | ||
checkRequirements() { | ||
if (typeof Promise === 'undefined') { | ||
console.warn('Promise is not supported'); | ||
return; | ||
return false; | ||
} | ||
return true; | ||
} | ||
async enable() { | ||
// Add event listeners | ||
this.delegatedListeners.click = delegateEvent( | ||
this.options.linkSelector, | ||
'click', | ||
this.linkClickHandler.bind(this) | ||
); | ||
const { linkSelector } = this.options; | ||
this.delegatedListeners.click = delegateEvent(linkSelector, 'click', this.linkClickHandler); | ||
window.addEventListener('popstate', this.boundPopStateHandler); | ||
window.addEventListener('popstate', this.popStateHandler); | ||
@@ -177,5 +148,2 @@ // Initial save to cache | ||
// Mark swup blocks in html | ||
markSwupElements(document.documentElement, this.options.containers); | ||
// Mount plugins | ||
@@ -188,12 +156,12 @@ this.options.plugins.forEach((plugin) => this.use(plugin)); | ||
// Trigger enabled event | ||
this.triggerEvent('enabled'); | ||
await this.hooks.trigger('enabled', undefined, () => { | ||
// Add swup-enabled class to html tag | ||
document.documentElement.classList.add('swup-enabled'); | ||
}); | ||
// Add swup-enabled class to html tag | ||
document.documentElement.classList.add('swup-enabled'); | ||
// Trigger page view event | ||
this.triggerEvent('pageView'); | ||
await this.hooks.trigger('pageView', { url: this.currentPageUrl, title: document.title }); | ||
} | ||
destroy() { | ||
async destroy() { | ||
// remove delegated listeners | ||
@@ -203,25 +171,18 @@ this.delegatedListeners.click!.destroy(); | ||
// remove popstate listener | ||
window.removeEventListener('popstate', this.boundPopStateHandler); | ||
window.removeEventListener('popstate', this.popStateHandler); | ||
// empty cache | ||
this.cache.empty(); | ||
this.cache.clear(); | ||
// unmount plugins | ||
this.options.plugins.forEach((plugin) => { | ||
this.unuse(plugin); | ||
}); | ||
this.options.plugins.forEach((plugin) => this.unuse(plugin)); | ||
// remove swup data atributes from blocks | ||
queryAll('[data-swup]').forEach((element) => { | ||
element.removeAttribute('data-swup'); | ||
// trigger disable event | ||
await this.hooks.trigger('disabled', undefined, () => { | ||
// remove swup-enabled class from html tag | ||
document.documentElement.classList.remove('swup-enabled'); | ||
}); | ||
// remove handlers | ||
this.off(); | ||
// trigger disable event | ||
this.triggerEvent('disabled'); | ||
// remove swup-enabled class from html tag | ||
document.documentElement.classList.remove('swup-enabled'); | ||
this.hooks.clear(); | ||
} | ||
@@ -252,13 +213,32 @@ | ||
linkClickHandler(event: DelegateEvent<MouseEvent>) { | ||
const linkEl = event.delegateTarget; | ||
const { href, url, hash } = Location.fromElement(linkEl as HTMLAnchorElement); | ||
const el = event.delegateTarget as HTMLAnchorElement; | ||
const { href, url, hash } = Location.fromElement(el); | ||
// Get the transition name, if specified | ||
const transition = el.getAttribute('data-swup-transition') || undefined; | ||
// Get the history action, if specified | ||
let historyAction: HistoryAction | undefined; | ||
const historyAttr = el.getAttribute('data-swup-history'); | ||
if (historyAttr && ['push', 'replace'].includes(historyAttr)) { | ||
historyAction = historyAttr as HistoryAction; | ||
} | ||
// Exit early if the link should be ignored | ||
if (this.shouldIgnoreVisit(href, { el: linkEl, event })) { | ||
if (this.shouldIgnoreVisit(href, { el, event })) { | ||
return; | ||
} | ||
this.context = this.createContext({ | ||
to: url, | ||
hash, | ||
transition, | ||
el, | ||
event, | ||
action: historyAction | ||
}); | ||
// Exit early if control key pressed | ||
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) { | ||
this.triggerEvent('openPageInNewTab', event); | ||
this.hooks.trigger('openPageInNewTab', { href }); | ||
return; | ||
@@ -272,51 +252,39 @@ } | ||
this.triggerEvent('clickLink', event); | ||
event.preventDefault(); | ||
this.hooks.triggerSync('clickLink', { el, event }, () => { | ||
const from = this.context.from?.url ?? ''; | ||
// Handle links to the same page and exit early, where applicable | ||
if (!url || url === getCurrentUrl()) { | ||
this.handleLinkToSamePage(url, hash, event); | ||
return; | ||
} | ||
event.preventDefault(); | ||
// Exit early if the resolved path hasn't changed | ||
if (this.isSameResolvedUrl(url, getCurrentUrl())) return; | ||
// Handle links to the same page: with or without hash | ||
if (!url || url === from) { | ||
if (hash) { | ||
updateHistoryRecord(url + hash); | ||
this.hooks.triggerSync( | ||
'samePageWithHash', | ||
{ hash, options: { behavior: 'auto' } }, | ||
(context, { hash, options }) => { | ||
const target = this.getAnchorElement(hash); | ||
if (target) { | ||
target.scrollIntoView(options); | ||
} | ||
} | ||
); | ||
} else { | ||
this.hooks.triggerSync('samePage', undefined, () => { | ||
window.scroll({ top: 0, left: 0, behavior: 'auto' }); | ||
}); | ||
} | ||
return; | ||
} | ||
// Store the element that should be scrolled to after loading the next page | ||
this.scrollToElement = hash || null; | ||
// Exit early if the resolved path hasn't changed | ||
if (this.isSameResolvedUrl(url, from)) { | ||
return; | ||
} | ||
// Get the custom transition name, if present | ||
const customTransition = linkEl.getAttribute('data-swup-transition') || undefined; | ||
// Get the history action, if set | ||
let history: HistoryAction | undefined; | ||
const historyAttr = linkEl.getAttribute('data-swup-history'); | ||
if (historyAttr && ['push', 'replace'].includes(historyAttr)) { | ||
history = historyAttr as HistoryAction; | ||
} | ||
// Finally, proceed with loading the page | ||
this.performPageLoad({ url, customTransition, history }); | ||
// Finally, proceed with loading the page | ||
this.performPageLoad(url, { transition, history: historyAction }); | ||
}); | ||
} | ||
handleLinkToSamePage(url: string, hash: string, event: DelegateEvent<MouseEvent>) { | ||
// Emit event and exit early if the url points to the same page without hash | ||
if (!hash) { | ||
this.triggerEvent('samePage', event); | ||
return; | ||
} | ||
// link to the same URL with hash | ||
this.triggerEvent('samePageWithHash', event); | ||
const element = getAnchorElement(hash); | ||
// Warn and exit early if no matching element was found for the hash | ||
if (!element) { | ||
return console.warn(`Element for offset not found (#${hash})`); | ||
} | ||
updateHistoryRecord(url + hash); | ||
} | ||
triggerWillOpenNewWindow(triggerEl: Element) { | ||
@@ -330,2 +298,4 @@ if (triggerEl.matches('[download], [target="_blank"]')) { | ||
popStateHandler(event: PopStateEvent) { | ||
const href = event.state?.url ?? location.href; | ||
// Exit early if this event should be ignored | ||
@@ -341,4 +311,2 @@ if (this.options.skipPopStateHandling(event)) { | ||
const href = event.state?.url ?? location.href; | ||
// Exit early if the link should be ignored | ||
@@ -350,17 +318,21 @@ if (this.shouldIgnoreVisit(href, { event })) { | ||
const { url, hash } = Location.fromUrl(href); | ||
const animate = this.options.animateHistoryBrowsing; | ||
const resetScroll = this.options.animateHistoryBrowsing; | ||
this.context = this.createContext({ | ||
to: url, | ||
hash, | ||
event, | ||
animate, | ||
resetScroll, | ||
popstate: true | ||
}); | ||
if (hash) { | ||
this.scrollToElement = hash; | ||
} else { | ||
event.preventDefault(); | ||
} | ||
// Does this even do anything? | ||
// if (!hash) { | ||
// event.preventDefault(); | ||
// } | ||
this.triggerEvent('popState', event); | ||
if (!this.options.animateHistoryBrowsing) { | ||
document.documentElement.classList.remove('is-animating'); | ||
cleanupAnimationClasses(); | ||
} | ||
this.performPageLoad({ url, event }); | ||
this.hooks.triggerSync('popState', { event }, () => { | ||
this.performPageLoad(url); | ||
}); | ||
} | ||
@@ -367,0 +339,0 @@ |
@@ -12,6 +12,8 @@ export const query = (selector: string, context: Document | Element = document) => { | ||
export const nextTick = (callback: () => void) => { | ||
requestAnimationFrame(() => { | ||
export const nextTick = (): Promise<void> => { | ||
return new Promise((resolve) => { | ||
requestAnimationFrame(() => { | ||
callback(); | ||
requestAnimationFrame(() => { | ||
resolve(); | ||
}); | ||
}); | ||
@@ -21,2 +23,21 @@ }); | ||
export function isPromise<T>(obj: any): obj is PromiseLike<T> { | ||
return ( | ||
!!obj && | ||
(typeof obj === 'object' || typeof obj === 'function') && | ||
typeof obj.then === 'function' | ||
); | ||
} | ||
export function runAsPromise(func: Function, args: any[] = [], ctx: any = {}): Promise<any> { | ||
return new Promise((resolve, reject) => { | ||
const result = func.apply(ctx, args); | ||
if (isPromise(result)) { | ||
result.then(resolve, reject); | ||
} else { | ||
resolve(result); | ||
} | ||
}); | ||
} | ||
export const escapeCssIdentifier = (ident: string) => { | ||
@@ -23,0 +44,0 @@ // @ts-ignore this is for support check, so it's correct that TS complains |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
378156
86
2512
79
3
14
3
1
+ Addedpath-to-regexp@^6.2.1
+ Addedpath-to-regexp@6.3.0(transitive)