new rn(t),getInstance:t=>p(t,"ScrollSpy")});const ln=t=>p(t,"Tab"),cn=g("show.bs.tab"),dn=g("shown.bs.tab"),un=g("hide.bs.tab"),hn=g("hidden.bs.tab");let mn,pn,fn,gn,vn,bn,wn;function yn(t){const{tabContent:e,nav:n}=t;e.style.height="",d(e,"collapsing"),n.isAnimating=!1}function En(t){const{tabContent:e,nav:n}=t;e?wn?yn(t):setTimeout(()=>{e.style.height=fn+"px",S(e),a(e,()=>yn(t))},50):n.isAnimating=!1,dn.relatedTarget=gn,mn.dispatchEvent(dn)}function An(t){const{tabContent:e}=t;e&&(vn.style.float="left",pn.style.float="left",bn=vn.scrollHeight),cn.relatedTarget=gn,hn.relatedTarget=mn,mn.dispatchEvent(cn),cn.defaultPrevented||(k(pn,"active"),d(vn,"active"),e&&(fn=pn.scrollHeight,wn=fn===bn,k(e,"collapsing"),e.style.height=bn+"px",S(e),vn.style.float="",pn.style.float=""),c(pn,"fade")?setTimeout(()=>{k(pn,"show"),a(pn,()=>{En(t)})},20):En(t),gn.dispatchEvent(hn))}function Tn({nav:t}){const e=t.getElementsByClassName("active");return 1!==e.length||ut.some(t=>c(e[0].parentNode,t))?e.length>1&&(gn=e[e.length-1]):[gn]=e,gn}function xn(t){return gn=Tn(t),tt(gn)}function kn(t,e){const n=e?u:"removeEventListener";t.element[n]("click",Cn)}function Cn(t){const e=ln(this);t.preventDefault(),e.nav.isAnimating||e.show()}class Nn extends b{constructor(t){super(t);const{element:n}=this;this.nav=n.closest(".nav");const{nav:o}=this;this.dropdown=o&&l(`.${ut[0]}-toggle`,o),vn=xn(this),this.tabContent=e&&vn.closest(".tab-content"),bn=vn.scrollHeight,o.isAnimating=!1,kn(this,!0)}get name(){return"Tab"}show(){const t=this,{element:e,nav:n,dropdown:o}=t;if(mn=e,!c(mn,"active")){if(pn=l(mn.getAttribute("href")),gn=Tn({nav:n}),vn=xn({nav:n}),un.relatedTarget=mn,gn.dispatchEvent(un),un.defaultPrevented)return;n.isAnimating=!0,d(gn,"active"),gn.setAttribute("aria-selected","false"),k(mn,"active"),mn.setAttribute("aria-selected","true"),o&&(c(e.parentNode,"dropdown-menu")?c(o,"active")||k(o,"active"):c(o,"active")&&d(o,"active")),c(vn,"fade")?(d(vn,"show"),a(vn,()=>An(t))):An(t)}}dispose(){kn(this),super.dispose()}}Object.assign(Nn,{selector:'[data-bs-toggle="tab"]',init:t=>new Nn(t),getInstance:ln});const Hn={animation:!0,autohide:!0,delay:500},Ln=g("show.bs.toast"),Pn=g("hide.bs.toast"),Sn=g("shown.bs.toast"),On=g("hidden.bs.toast");function In(t){const{element:e,options:n}=t;d(e,"showing"),e.dispatchEvent(Sn),n.autohide&&t.hide()}function jn(t){const{element:e}=t;d(e,"showing"),d(e,"show"),k(e,"hide"),e.dispatchEvent(On)}function Dn(t,e){const n=e?u:"removeEventListener";t.dismiss&&t.dismiss[n]("click",t.hide)}class Bn extends b{constructor(t,e){super(t,e);const{element:n,options:o}=this;o.animation&&!c(n,"fade")?k(n,"fade"):!o.animation&&c(n,"fade")&&d(n,"fade"),this.timer=null,this.dismiss=l('[data-bs-dismiss="toast"]',n),this.show=this.show.bind(this),this.hide=this.hide.bind(this),Dn(this,!0)}get name(){return"Toast"}get defaults(){return Hn}show(){const t=this,{element:e}=t;if(e&&!c(e,"show")){if(e.dispatchEvent(Ln),Ln.defaultPrevented)return;clearTimeout(t.timer),t.timer=setTimeout(()=>function(t){const{element:e,options:n}=t;d(e,"hide"),S(e),k(e,"show"),k(e,"showing"),n.animation?a(e,()=>In(t)):In(t)}(t),10)}}hide(){const t=this,{element:e,options:n}=t;if(e&&c(e,"show")){if(e.dispatchEvent(Pn),Pn.defaultPrevented)return;clearTimeout(t.timer),t.timer=setTimeout(()=>function(t){const{element:e,options:n}=t;k(e,"showing"),n.animation?(S(e),a(e,()=>jn(t))):jn(t)}(t),n.delay)}}dispose(){const{element:t}=this;c(t,"show")&&d(t,"show"),function(t){clearTimeout(t.timer),Dn(t)}(this),super.dispose()}}Object.assign(Bn,{selector:".toast",init:t=>new Bn(t),getInstance:t=>p(t,"Toast")});const Rn={template:'',title:null,customClass:null,placement:"top",sanitizeFn:null,animation:!0,delay:200,container:null},Wn=t=>p(t,"Tooltip"),Mn=g("show.bs.tooltip"),Fn=g("shown.bs.tooltip"),$n=g("hide.bs.tooltip"),zn=g("hidden.bs.tooltip");function qn(t){const{element:e}=t;Gn(t),e.hasAttribute("data-original-title")&&Zn(t)}function Xn(t,e){const n=e?u:"removeEventListener";document[n]("touchstart",Jn,P),Le(t.element)||(window[n]("scroll",t.update,P),window[n]("resize",t.update,P))}function Yn(t){Xn(t,!0),t.element.dispatchEvent(Fn)}function Vn(t){Xn(t),function(t){const{element:e,tooltip:n}=t;e.removeAttribute("aria-describedby"),n.remove(),t.timer=null}(t),t.element.dispatchEvent(zn)}function Gn(t,e){const n=e?u:"removeEventListener",{element:o}=t;Le(o)&&o[n]("mousemove",t.update,P),o[n]("mousedown",t.show),o[n]("mouseenter",t.show),o[n]("mouseleave",t.hide)}function Zn(t,e){const n=["data-original-title","title"],{element:o}=t;o.setAttribute(n[e?0:1],e||o.getAttribute(n[0])),o.removeAttribute(n[e?1:0])}function Jn({target:t}){const{tooltip:e,element:n}=this;e.contains(t)||t===n||n.contains(t)||this.hide()}class Kn extends b{constructor(t,e){const n=l(t);Rn.title=n.getAttribute("title"),Rn.container=We(n),super(t,e);this.tooltip=null,this.arrow=null,this.timer=null,this.enabled=!0;const{options:o}=this;if(this.options.container=Le(n)?Rn.container:l(o.container),Rn.container=null,Rn.title=null,!o.title)return;Jn.bind(this),this.update=this.update.bind(this),n.hasAttribute("title")&&Zn(this,o.title),this.id="tooltip-"+Re(n),function(t){const{options:e,id:n}=t,{title:o,template:s,customClass:i,animation:a,placement:r,sanitizeFn:d}=e,u="bs-tooltip-"+Ie[r];if(!o)return;let h;if("object"==typeof s)h=s;else{const t=document.createElement("div");Fe(t,s,d),h=t.firstChild}t.tooltip=h.cloneNode(!0);const{tooltip:m}=t;Fe(l(".tooltip-inner",m),o,d),m.setAttribute("id",n),m.setAttribute("role","tooltip"),t.arrow=l(".tooltip-arrow",m),c(m,"tooltip")||k(m,"tooltip"),a&&!c(m,"fade")&&k(m,"fade"),i&&!c(m,i)&&k(m,i),c(m,u)||k(m,u)}(this);const{container:s}=this.options,i=getComputedStyle(n).position,a=getComputedStyle(s).position,r=s===document.body,d=!r&&"static"===a,u=!r&&"relative"===a,h=d&&Me(s);this.positions={elementPosition:i,containerIsRelative:u,containerIsStatic:d,relContainer:h},Gn(this,!0)}get name(){return"Tooltip"}get defaults(){return Rn}show(t){const e=t?Wn(this):this,{options:n,tooltip:o,element:s,id:i}=e,{container:r,animation:l}=n;if(clearTimeout(e.timer),!je(o,r)){if(s.dispatchEvent(Mn),Mn.defaultPrevented)return;r.append(o),s.setAttribute("aria-describedby",i),e.update(t),c(o,"show")||k(o,"show"),l?a(o,()=>Yn(e)):Yn(e)}}hide(t){const e=t?Wn(this):this,{options:n,tooltip:o,element:s}=e;clearTimeout(e.timer),e.timer=setTimeout(()=>{if(je(o,n.container)){if(s.dispatchEvent($n),$n.defaultPrevented)return;d(o,"show"),n.animation?a(o,()=>Vn(e)):Vn(e)}},n.delay)}update(t){De(this,t)}toggle(t){const e=t?Wn(this):this,{tooltip:n,options:o}=e;je(n,o.container)?e.hide():e.show()}enable(){const t=this,{enabled:e}=t;e||(Gn(t,!0),t.enabled=!e)}disable(){const t=this,{tooltip:e,options:n,enabled:o}=t;o&&(!je(e,n.container)&&n.animation?(t.hide(),setTimeout(()=>Gn(t),i(e)+n.delay+17)):Gn(t),t.enabled=!o)}toggleEnabled(){this.enabled?this.disable():this.enable()}dispose(){const t=this,{tooltip:e,options:n}=t;n.animation&&je(e,n.container)?(n.delay=0,t.hide(),a(e,()=>qn(t))):qn(t),super.dispose()}}Object.assign(Kn,{selector:'[data-bs-toggle="tooltip"],[data-tip="tooltip"]',init:t=>new Kn(t),getInstance:Wn});const Qn={Alert:x,Button:L,Carousel:_,Collapse:dt,Dropdown:Rt,Modal:me,Offcanvas:He,Popover:Ue,ScrollSpy:rn,Tab:Nn,Toast:Bn,Tooltip:Kn},Un=Object.keys(Qn);function _n(t){const e=t instanceof Element?t:document;Un.forEach(t=>{const{init:n,selector:o}=Qn[t];var s,i;s=n,i=e.querySelectorAll(o),Array.from(i).forEach(t=>s(t))})}document.body?_n():document.addEventListener("DOMContentLoaded",()=>_n(),{once:!0});const to={Alert:x,Button:L,Carousel:_,Collapse:dt,Dropdown:Rt,Modal:me,Offcanvas:He,Popover:Ue,ScrollSpy:rn,Tab:Nn,Toast:Bn,Tooltip:Kn,initCallback:_n,removeDataAPI:function(t){const e=t instanceof Element?t:document;Un.forEach(t=>{!function(t,e){Array.from(m.getAllFor(t)).forEach(t=>{const[n,o]=t;e.contains(n)&&o.dispose()})}(t,e)})},Version:"4.1.0"};export{to as default};
+// Native JavaScript for Bootstrap v4.1.0alpha1 | 2022 © dnp_theme | MIT-License
+function t(t,e){const n=getComputedStyle(t);return e in n?n[e]:""}function e(e){const n=t(e,"transitionProperty"),o=t(e,"transitionDuration"),s=o.includes("ms")?1:1e3,i=n&&"none"!==n?parseFloat(o)*s:0;return Number.isNaN(i)?0:i}function n(t,e,n,o){const s=o||!1;t.addEventListener(e,n,s)}function o(t,e,n,o){const s=o||!1;t.removeEventListener(e,n,s)}function s(s,i){let a=0;const l=new Event("transitionend"),r=e(s),c=function(e){const n=t(e,"transitionProperty"),o=t(e,"transitionDelay"),s=o.includes("ms")?1:1e3,i=n&&"none"!==n?parseFloat(o)*s:0;return Number.isNaN(i)?0:i}(s);if(r){const t=e=>{e.target===s&&(i.apply(s,[e]),o(s,"transitionend",t),a=1)};n(s,"transitionend",t),setTimeout(()=>{a||s.dispatchEvent(l)},r+c+17)}else i.apply(s,[l])}function i(t){return t instanceof HTMLElement?t.ownerDocument:t instanceof Window?t.document:window.document}const a=[Document,Node,Element,HTMLElement],l=[Element,HTMLElement];function r(t,e){const n="string"==typeof t,o=e&&a.some(t=>e instanceof t)?e:i();return!n&&[...l].some(e=>t instanceof e)?t:n?o.querySelector(t):null}function c(t,e){return t?t.closest(e)||c(t.getRootNode().host,e):null}const d=(t,e)=>Object.assign(t,e);function h(t,e){return t.classList.contains(e)}function u(t,e){t.classList.remove(e)}const f=(t,e)=>t.dispatchEvent(e),p=new Map,g={set:(t,e,n)=>{const o=r(t);if(!o)return;p.has(e)||p.set(e,new Map);p.get(e).set(o,n)},getAllFor:t=>p.get(t)||null,get:(t,e)=>{const n=r(t),o=g.getAllFor(e);return n&&o&&o.get(n)||null},remove:(t,e)=>{const n=r(t),o=p.get(e);o&&n&&(o.delete(n),0===o.size&&p.delete(e))}},m=(t,e)=>g.get(t,e);function v(t,e){const n=new CustomEvent(t,{cancelable:!0,bubbles:!0});return e instanceof Object&&d(n,e),n}function b(t){return"true"===t||"false"!==t&&(Number.isNaN(+t)?""===t||"null"===t?null:t:+t)}const w=t=>Object.keys(t);class y{constructor(t,e){const n=this,o=r(t);if(!o)throw Error(`${n.name} Error: "${t}" is not a valid selector.`);n.options={};const s=g.get(o,n.name);s&&s.dispose(),n.element=o,n.defaults&&Object.keys(n.defaults).length&&(n.options=function(t,e,n,o){const s={...t.dataset},i={},a={};return w(s).forEach(t=>{const e=o&&t.includes(o)?t.replace(o,"").replace(/[A-Z]/,t=>t.toLowerCase()):t;a[e]=b(s[t])}),w(n).forEach(t=>{n[t]=b(n[t])}),w(e).forEach(t=>{i[t]=t in n?n[t]:t in a?a[t]:e[t]}),i}(o,n.defaults,e||{},"bs")),g.set(o,n.name,n)}get version(){return"4.1.0alpha1"}get name(){return this.constructor.name}get defaults(){return this.constructor.defaults}dispose(){const t=this;g.remove(t.element,t.name),w(t).forEach(e=>{t[e]=null})}}const T=t=>m(t,"Alert"),E=v("close.bs.alert"),x=v("closed.bs.alert");function H(t){const{element:e}=t;k(t),f(e,x),t.dispose(),e.remove()}function k(t,e){const s=e?n:o,{dismiss:i}=t;i&&s(i,"click",t.close)}class A extends y{constructor(t){super(t);const{element:e}=this;this.dismiss=r('[data-bs-dismiss="alert"]',e),k(this,!0)}get name(){return"Alert"}close(t){const e=t?T(c(this,".alert")):this;if(!e)return;const{element:n}=e;if(h(n,"show")){if(f(n,E),E.defaultPrevented)return;u(n,"show"),h(n,"fade")?s(n,()=>H(e)):H(e)}}dispose(){k(this),super.dispose()}}d(A,{selector:".alert",init:t=>new A(t),getInstance:T});const P=(t,e,n)=>t.setAttribute(e,n);function M(t,e){t.classList.add(e)}const N="data-bs-toggle",L=t=>m(t,"Button");function D(t,e){(e?n:o)(t.element,"click",t.toggle)}class C extends y{constructor(t){super(t);const{element:e}=this;this.isActive=h(e,"active"),P(e,"aria-pressed",""+!!this.isActive),D(this,!0)}get name(){return"Button"}toggle(t){t&&t.preventDefault();const e=t?L(this):this;if(!e)return;const{element:n}=e;if(h(n,"disabled"))return;e.isActive=h(n,"active");const{isActive:o}=e;(o?u:M)(n,"active"),P(n,"aria-pressed",o?"false":"true")}dispose(){D(this),super.dispose()}}d(C,{selector:'[data-bs-toggle="button"]',init:t=>new C(t),getInstance:L});const S={passive:!0},R=t=>t.offsetHeight;function I(t,e){const{width:n,height:o,top:s,right:i,bottom:a,left:l}=t.getBoundingClientRect();let r=1,c=1;if(e&&t instanceof HTMLElement){const{offsetWidth:e,offsetHeight:s}=t;r=e>0&&Math.round(n)/e||1,c=s>0&&Math.round(o)/s||1}return{width:n/r,height:o/c,top:s/c,right:i/r,bottom:a/c,left:l/r,x:l/r,y:s/c}}function F(t){return i(t).documentElement}const O=t=>{const{top:e,bottom:n}=I(t),{clientHeight:o}=F(t);return e<=o&&n>=0};function W(t,e){return(e&&a.some(t=>e instanceof t)?e:i()).querySelectorAll(t)}function B(t,e){return(e&&a.some(t=>e instanceof t)?e:i()).getElementsByClassName(t)}const X="mouseenter",z="mouseleave",Y=(t,e)=>t.getAttribute(e),$=t=>"rtl"===F(t).dir,j=new Map,V=(t,e,n,o)=>{const s=r(t);if(s)if(o&&o.length){j.has(s)||j.set(s,new Map);j.get(s).set(o,setTimeout(e,n))}else j.set(s,setTimeout(e,n))},q=(t,e)=>{const n=r(t);if(!n)return null;const o=j.get(n);return e&&e.length&&o&&o.get?o.get(e)||null:o||null},G=(t,e)=>{const n=r(t);if(n)if(e&&e.length){const t=j.get(n);t&&t.get&&(clearTimeout(t.get(e)),t.delete(e),0===t.size&&j.delete(n))}else clearTimeout(j.get(n)),j.delete(n)};function U(t){if(null==t)return window;if(!(t instanceof Window)){const{ownerDocument:e}=t;return e&&e.defaultView||window}return t}function Z(t){const e=["data-bs-target","data-bs-parent","data-bs-container","href"],n=i(t);return e.map(e=>{const o=Y(t,e);return o?"data-bs-parent"===e?c(t,o):r(o,n):null}).filter(t=>t)[0]}const J='[data-bs-ride="carousel"]',K="carousel-item",Q={pause:"hover",keyboard:!1,touch:!0,interval:5e3},_=t=>m(t,"Carousel");let tt=0,et=0,nt=0;const ot=v("slide.bs.carousel"),st=v("slid.bs.carousel");function it(t){const e=t.target,n=_(c(e,J));n&&!n.isPaused&&n.pause()}function at(t){const{target:e}=t,n=_(c(e,J));if(!n)return;const{element:o}=n;n.isPaused&&(u(o,"paused"),n.cycle())}function lt(t){t.preventDefault();const e=c(this,J)||Z(this);if(!e)return;const n=_(e);if(!n||n.isAnimating)return;const o=+Y(this,"data-bs-slide-to");!this||h(this,"active")||Number.isNaN(o)||n.to(o)}function rt(t){t.preventDefault();const e=c(this,J)||Z(this),n=e&&_(e);if(!n||n.isAnimating)return;const o=Y(this,"data-bs-slide");"next"===o?n.next():"prev"===o&&n.prev()}function ct({code:t}){const[e]=[...W(J)].filter(t=>O(t)),n=_(e);if(!n)return;const o=$(),s=o?"ArrowLeft":"ArrowRight";t===(o?"ArrowRight":"ArrowLeft")?n.prev():t===s&&n.next()}function dt(t){const e=_(this);e&&!e.isTouch&&(tt=t.changedTouches[0].pageX,this.contains(t.target)&&(e.isTouch=!0,pt(e,!0)))}function ht(t){const{changedTouches:e,type:n}=t,o=_(this);o&&o.isTouch&&(et=e[0].pageX,"touchmove"===n&&e.length>1&&t.preventDefault())}function ut(t){const e=this,n=_(e);if(n&&n.isTouch&&(nt=et||t.changedTouches[0].pageX,n.isTouch)){if((!e.contains(t.target)||!e.contains(t.relatedTarget))&&Math.abs(tt-nt)<75)return;ettt&&(n.index-=1),n.isTouch=!1,n.to(n.index),pt(n)}}function ft(t,e){const{indicators:n}=t;[...n].forEach(t=>u(t,"active")),t.indicators[e]&&M(n[e],"active")}function pt(t,e){const{element:s}=t,i=e?n:o;i(s,"touchmove",ht,S),i(s,"touchend",ut,S)}function gt(t,e){const{element:s,options:i,slides:a,controls:l,indicators:r}=t,{touch:c,pause:d,interval:h,keyboard:u}=i,f=e?n:o;d&&h&&(f(s,X,it),f(s,z,at),f(s,"touchstart",it,S),f(s,"touchend",at,S)),c&&a.length>1&&f(s,"touchstart",dt,S),l.length&&l.forEach(t=>{t&&f(t,"click",rt)}),r.length&&r.forEach(t=>{f(t,"click",lt)}),u&&f(U(s),"keydown",ct)}function mt(t){const{slides:e,element:n}=t,o=r(".carousel-item.active",n);return[...e].indexOf(o)}class vt extends y{constructor(t,e){super(t,e);const n=this;n.direction=$()?"right":"left",n.index=0,n.isTouch=!1;const{element:o}=n;n.slides=B(K,o);const{slides:s}=n;if(s.length<2)return;n.controls=[...W("[data-bs-slide]",o),...W(`[data-bs-slide][data-bs-target="#${o.id}"]`)],n.indicator=r(".carousel-indicators",o),n.indicators=[...n.indicator?W("[data-bs-slide-to]",n.indicator):[],...W(`[data-bs-slide-to][data-bs-target="#${o.id}"]`)];const{options:i}=n;n.options.interval=!0===i.interval?Q.interval:i.interval,mt(n)<0&&(s.length&&M(s[0],"active"),n.indicators.length&&ft(n,0)),gt(n,!0),i.interval&&n.cycle()}get name(){return"Carousel"}get defaults(){return Q}get isPaused(){return h(this.element,"paused")}get isAnimating(){return null!==r(".carousel-item-next,.carousel-item-prev",this.element)}cycle(){const t=this,{element:e,options:n}=t;G(e,"carousel"),V(e,()=>{!t.isPaused&&O(e)&&(t.index+=1,t.to(t.index))},n.interval,"carousel")}pause(){const{element:t,options:e}=this;!this.isPaused&&e.interval&&M(t,"paused")}next(){const t=this;t.isAnimating||(t.index+=1,t.to(t.index))}prev(){const t=this;t.isAnimating||(t.index-=1,t.to(t.index))}to(t){const n=this,{element:o,slides:a,options:l}=n,r=mt(n),c=$();let p=t;if(n.isAnimating||r===p)return;rp||r===a.length-1&&0===p)&&(n.direction=c?"left":"right");const{direction:g}=n;p<0?p=a.length-1:p>=a.length&&(p=0);const m="left"===g?"next":"prev",v="left"===g?"start":"end",b={relatedTarget:a[p],from:r,to:p,direction:g};d(ot,b),d(st,b),f(o,ot),ot.defaultPrevented||(n.index=p,ft(n,p),e(a[p])&&h(o,"slide")?V(o,()=>{M(a[p],"carousel-item-"+m),R(a[p]),M(a[p],"carousel-item-"+v),M(a[r],"carousel-item-"+v),s(a[p],()=>function(t){const{index:e,direction:n,element:o,slides:s,options:a}=t;if(t.isAnimating&&_(o)){const l=mt(t),r="left"===n?"next":"prev",c="left"===n?"start":"end";M(s[e],"active"),u(s[l],"active"),u(s[e],"carousel-item-"+r),u(s[e],"carousel-item-"+c),u(s[l],"carousel-item-"+c),f(o,st),G(o,"data-bs-slide"),i(o).hidden||!a.interval||t.isPaused||t.cycle()}}(n))},17,"data-bs-slide"):(M(a[p],"active"),u(a[r],"active"),V(o,()=>{G(o,"data-bs-slide"),o&&l.interval&&!n.isPaused&&n.cycle(),f(o,st)},17,"data-bs-slide")))}dispose(){const t=this,{slides:e}=t,n=["start","end","prev","next"];[...e].forEach((e,o)=>{h(e,"active")&&ft(t,o),n.forEach(t=>u(e,"carousel-item-"+t))}),gt(t),super.dispose()}}d(vt,{selector:J,init:t=>new vt(t),getInstance:_});const bt={parent:null},wt=t=>m(t,"Collapse"),yt=v("show.bs.collapse"),Tt=v("shown.bs.collapse"),Et=v("hide.bs.collapse"),xt=v("hidden.bs.collapse");function Ht(t){const{element:e,parent:n,triggers:o}=t;f(e,Et),Et.defaultPrevented||(V(e,()=>{},17),n&&V(n,()=>{},17),e.style.height=e.scrollHeight+"px",u(e,"collapse"),u(e,"show"),M(e,"collapsing"),R(e),e.style.height="0px",s(e,()=>{G(e),n&&G(n),o.forEach(t=>P(t,"aria-expanded","false")),u(e,"collapsing"),M(e,"collapse"),e.style.height="",f(e,xt)}))}function kt(t,e){const s=e?n:o,{triggers:i}=t;i.length&&i.forEach(t=>s(t,"click",At))}function At(t){const{target:e}=t,n=e&&c(e,'[data-bs-toggle="collapse"]'),o=n&&Z(n),s=o&&wt(o);s&&s.toggle(),n&&"A"===n.tagName&&t.preventDefault()}class Pt extends y{constructor(t,e){super(t,e);const{element:n,options:o}=this;this.triggers=[...W('[data-bs-toggle="collapse"]')].filter(t=>Z(t)===n),this.parent=r(o.parent),kt(this,!0)}get name(){return"Collapse"}get defaults(){return bt}toggle(){h(this.element,"show")?this.hide():this.show()}hide(){const{triggers:t,element:e}=this;q(e)||(Ht(this),t.length&&t.forEach(t=>M(t,"collapsed")))}show(){const t=this,{element:e,parent:n,triggers:o}=t;let i,a;n&&(i=[...W(".collapse.show",n)].find(t=>wt(t)),a=i&&wt(i)),n&&(!n||q(n))||q(e)||(a&&i!==e&&(Ht(a),a.triggers.forEach(t=>{M(t,"collapsed")})),function(t){const{element:e,parent:n,triggers:o}=t;f(e,yt),yt.defaultPrevented||(V(e,()=>{},17),n&&V(n,()=>{},17),M(e,"collapsing"),u(e,"collapse"),e.style.height=e.scrollHeight+"px",s(e,()=>{G(e),n&&G(n),o.forEach(t=>P(t,"aria-expanded","true")),u(e,"collapsing"),M(e,"collapse"),M(e,"show"),e.style.height="",f(e,Tt)}))}(t),o.length&&o.forEach(t=>u(t,"collapsed")))}dispose(){kt(this),super.dispose()}}d(Pt,{selector:".collapse",init:t=>new Pt(t),getInstance:wt});const Mt="scroll",Nt="resize",Lt="ArrowUp",Dt="ArrowDown",Ct=(t,e)=>{d(t.style,e)},St=t=>t.focus(),Rt=(t,e)=>t.hasAttribute(e),It=["dropdown","dropup","dropstart","dropend"];function Ft(t){const e=c(t,"A");return t&&(Rt(t,"href")&&"#"===t.href.slice(-1)||e&&Rt(e,"href")&&"#"===e.href.slice(-1))}const[Ot,Wt,Bt,Xt]=It,zt=`[data-bs-toggle="${Ot}"]`,Yt=t=>m(t,"Dropdown"),$t=[Ot,Wt],jt=[Bt,Xt],Vt=["A","BUTTON"],qt={offset:5,display:"dynamic"},Gt=v("show.bs."+Ot),Ut=v("shown.bs."+Ot),Zt=v("hide.bs."+Ot),Jt=v("hidden.bs."+Ot);function Kt(e){const{element:n,menu:o,parentElement:s,options:i}=e,{offset:a}=i;if("static"===t(o,"position"))return;const l=$(n),r=h(s,"dropdown-menu-end");["margin","top","bottom","left","right"].forEach(t=>{o.style[t]=""});let c=It.find(t=>h(s,t))||Ot,u={dropdown:[a,0,0],dropup:[0,0,a],dropstart:l?[-1,0,0,a]:[-1,a,0],dropend:l?[-1,a,0]:[-1,0,0,a]};const f={dropdown:{top:"100%"},dropup:{top:"auto",bottom:"100%"},dropstart:l?{left:"100%",right:"auto"}:{left:"auto",right:"100%"},dropend:l?{left:"auto",right:"100%"}:{left:"100%",right:"auto"},menuEnd:l?{right:"auto",left:0}:{right:0,left:"auto"}},{offsetWidth:p,offsetHeight:g}=o,{clientWidth:m,clientHeight:v}=F(n),{left:b,top:w,width:y,height:T}=I(n),E=b-p-a<0,x=b+p+y+a>=m,H=w+g+a>=v,k=w+g+T+a>=v,A=w-g-a<0,P=(!l&&r||l&&!r)&&b+y-p<0,M=(l&&r||!l&&!r)&&b+p>=m;jt.includes(c)&&E&&x&&(c=Ot),c===Bt&&(l?x:E)&&(c=Xt),c===Xt&&(l?E:x)&&(c=Bt),c===Wt&&A&&!k&&(c=Ot),c===Ot&&k&&!A&&(c=Wt),jt.includes(c)&&H&&d(f[c],{top:"auto",bottom:0}),$t.includes(c)&&(P||M)&&b+y+Math.abs(p-y)+at?t+"px":t).join(" "),Ct(o,f[c]),h(o,"dropdown-menu-end")&&Ct(o,f.menuEnd)}function Qt(t){const{element:e}=t,s=t.open?n:o,a=i(e);s(a,"click",ee),s(a,"focus",ee),s(a,"keydown",oe),s(a,"keyup",se),"dynamic"===t.options.display&&[Mt,Nt].forEach(t=>{s(U(e),t,ie,S)})}function _t(t,e){(e?n:o)(t.element,"click",ne)}function te(t){const e=[...It,"btn-group","input-group"].map(t=>B(t+" show"),i(t)).find(t=>t.length);return e&&e.length?[...e[0].children].find(t=>Rt(t,N)):null}function ee(t){const{target:e,type:n}=t;if(!e||!e.closest)return;const o=te(e);if(!o)return;const s=Yt(o);if(!s)return;const{parentElement:i,menu:a}=s,l=null!==c(e,zt),r=i&&i.contains(e)&&("form"===e.tagName||null!==c(e,"form"));"click"===n&&Ft(e)&&t.preventDefault(),("focus"!==n||e!==o&&e!==a&&!a.contains(e))&&(r||l||s&&s.hide())}function ne(t){const{target:e}=t,n=Yt(this);n&&(n.toggle(),e&&Ft(e)&&t.preventDefault())}function oe(t){[Dt,Lt].includes(t.code)&&t.preventDefault()}function se(t){const{code:e}=t,n=te(this),o=n&&Yt(n),s=n&&i(n).activeElement;if(!o||!s)return;const{menu:a,open:l}=o,r=function(t){return[...t.children].map(t=>{if(t&&Vt.includes(t.tagName))return t;const{firstElementChild:e}=t;return e&&Vt.includes(e.tagName)?e:null}).filter(t=>t)}(a);if(r&&r.length){let t=r.indexOf(s);s===n?t=0:e===Lt?t=t>1?t-1:0:e===Dt&&(t=t{t.relatedTarget=e}),f(s,Gt),Gt.defaultPrevented||(M(o,"show"),M(s,"show"),P(e,"aria-expanded","true"),Kt(t),t.open=!n,setTimeout(()=>{St(e),Qt(t),f(s,Ut)},1))}hide(){const t=this,{element:e,open:n,menu:o,parentElement:s}=t;[Zt,Jt].forEach(t=>{t.relatedTarget=e}),f(s,Zt),Zt.defaultPrevented||(u(o,"show"),u(s,"show"),P(e,"aria-expanded","false"),t.open=!n,setTimeout(()=>Qt(t),1),f(s,Jt))}dispose(){const{parentElement:t}=this;h(t,"show")&&this.open&&this.hide(),_t(this),super.dispose()}}d(ae,{selector:zt,init:t=>new ae(t),getInstance:Yt});const le=(t,e)=>t.removeAttribute(e);function re(t){return i(t).body}const ce=t=>t instanceof U(t).ShadowRoot||t instanceof ShadowRoot;const de=t=>["TABLE","TD","TH"].includes(t.tagName);function he(e,n){const o=["HTML","BODY"];if(n){let{offsetParent:n}=e;for(;n&&de(n)&&"static"===t(n,"position")&&n instanceof HTMLElement&&"fixed"!==t(n,"position");)n=n.offsetParent;return(!n||n&&o.includes(n.tagName)&&"static"===t(n,"position"))&&(n=U(e)),n}const s=[];let{parentNode:i}=e;for(;i&&!o.includes(i.nodeName);)i="HTML"===(a=i).nodeName?a:a.assignedSlot||a.parentNode||(ce(a)?a.host:null)||F(a),ce(i)||i.shadowRoot||de(i)||s.push(i);var a;return s.find((e,n)=>"relative"!==t(e,"position")&&s.slice(n+1).every(e=>"static"===t(e,"position"))?e:null)||re(e)}const ue="sticky-top",fe="position-sticky",pe=t=>[...B("fixed-top",t),...B("fixed-bottom",t),...B(ue,t),...B(fe,t),...B("is-fixed",t)];function ge(t){const{clientWidth:e}=F(t),{innerWidth:n}=U(t);return Math.abs(n-e)}function me(e,n){const o=re(e),s=parseInt(t(o,"paddingRight"),10),i="hidden"===t(o,"overflow")&&s?0:ge(e),a=pe(o);n&&(Ct(o,{overflow:"hidden",paddingRight:s+i+"px"}),a.length&&a.forEach(e=>{const n=t(e,"paddingRight");if(e.style.paddingRight=parseInt(n,10)+i+"px",[ue,fe].some(t=>h(e,t))){const n=t(e,"marginRight");e.style.marginRight=parseInt(n,10)-i+"px"}}))}const ve="modal-backdrop",be="offcanvas-backdrop",we=i().createElement("div");function ye(t){return r(".modal.show,.offcanvas.show",i(t))}function Te(t){const e=t?ve:be;[ve,be].forEach(t=>{u(we,t)}),M(we,e)}function Ee(t,e,n){Te(n),t.append(we),e&&M(we,"fade")}function xe(){M(we,"show"),R(we)}function He(){u(we,"show")}function ke(t){ye(t)||(u(we,"fade"),we.remove(),function(t){const e=re(t);Ct(e,{paddingRight:"",overflow:""});const n=pe(e);n.length&&n.forEach(t=>{Ct(t,{paddingRight:"",marginRight:""})})}(t))}function Ae(e){return e&&"hidden"!==t(e,"visibility")&&null!==e.offsetParent}const Pe={backdrop:!0,keyboard:!0},Me=t=>m(t,"Modal"),Ne=v("show.bs.modal"),Le=v("shown.bs.modal"),De=v("hide.bs.modal"),Ce=v("hidden.bs.modal");function Se(t){const{element:e}=t,n=ge(e),{clientHeight:o,scrollHeight:s}=F(e),{clientHeight:i,scrollHeight:a}=e,l=i!==a;if(!l&&n){const t=$(e)?"paddingLeft":"paddingRight";e.style[t]=n+"px"}me(e,l||o!==s)}function Re(t,e){const s=e?n:o,{element:a}=t;s(a,"click",Ye),s(U(a),Nt,t.update,S),s(i(a),"keydown",ze)}function Ie(t,e){const s=e?n:o,{triggers:i}=t;i.length&&i.forEach(t=>s(t,"click",Xe))}function Fe(t){const{triggers:e,element:n}=t;if(ke(n),n.style.paddingRight="",e.length){const t=e.find(t=>Ae(t));t&&St(t)}}function Oe(t){const{element:e,relatedTarget:n}=t;St(e),Re(t,!0),Le.relatedTarget=n,f(e,Le)}function We(t){const{element:e,hasFade:n}=t;e.style.display="block",Se(t),ye(e)||(re(e).style.overflow="hidden"),M(e,"show"),le(e,"aria-hidden"),P(e,"aria-modal","true"),n?s(e,()=>Oe(t)):Oe(t)}function Be(t,e){const{element:n,options:o,relatedTarget:i,hasFade:a}=t;n.style.display="",o.backdrop&&!e&&a&&h(we,"show")&&!ye(n)?(He(),s(we,()=>Fe(t))):Fe(t),Re(t),Ce.relatedTarget=i,f(n,Ce)}function Xe(t){const{target:e}=t,n=e&&c(this,'[data-bs-toggle="modal"]'),o=n&&Z(n),s=o&&Me(o);s&&(n&&"A"===n.tagName&&t.preventDefault(),s.relatedTarget=n,s.toggle())}function ze({code:t}){const e=r(".modal.show"),n=e&&Me(e);if(!n)return;const{options:o}=n;o.keyboard&&"Escape"===t&&h(e,"show")&&(n.relatedTarget=null,n.hide())}function Ye(t){const n=this,o=Me(n);if(!o||q(n))return;const{options:a,isStatic:l,modalDialog:r}=o,{backdrop:d}=a,{target:h}=t,f=i(n).getSelection().toString().length,p=r.contains(h),g=h&&c(h,'[data-bs-dismiss="modal"]');if(l&&!p){const t=()=>{M(n,"modal-static"),s(r,()=>function(t){const{element:n,modalDialog:o}=t,s=e(o)+17;u(n,"modal-static"),V(n,()=>G(n),s)}(o))};V(n,t,17)}else(g||!f&&!l&&!p&&d)&&(o.relatedTarget=g||null,o.hide(),t.preventDefault())}class $e extends y{constructor(t,e){super(t,e);const{element:n}=this;this.modalDialog=r(".modal-dialog",n),this.triggers=[...W('[data-bs-toggle="modal"]')].filter(t=>Z(t)===n),this.isStatic="static"===this.options.backdrop,this.hasFade=h(n,"fade"),this.relatedTarget=null,this.container=he(n),Ie(this,!0),this.update=this.update.bind(this)}get name(){return"Modal"}get defaults(){return Pe}toggle(){h(this.element,"show")?this.hide():this.show()}show(){const t=this,{element:n,options:o,hasFade:s,relatedTarget:i,container:a}=t,{backdrop:l}=o;let r=0;if(h(n,"show"))return;if(Ne.relatedTarget=i||null,f(n,Ne),Ne.defaultPrevented)return;const c=ye(n);if(c&&c!==n){(Me(c)||m(c,"Offcanvas")).hide()}l?(c||h(we,"show")?Te(!0):Ee(a,s,!0),r=e(we),h(we,"show")||xe(),setTimeout(()=>We(t),r)):(We(t),c&&h(we,"show")&&He())}hide(t){const e=this,{element:n,hasFade:o,relatedTarget:i}=e;h(n,"show")&&(De.relatedTarget=i||null,f(n,De),De.defaultPrevented||(u(n,"show"),P(n,"aria-hidden","true"),le(n,"aria-modal"),o&&!1!==t?s(n,()=>Be(e)):Be(e,t)))}update(){h(this.element,"show")&&Se(this)}dispose(){this.hide(!0),Ie(this),super.dispose()}}d($e,{selector:".modal",init:t=>new $e(t),getInstance:Me});const je={backdrop:!0,keyboard:!0,scroll:!1},Ve=t=>m(t,"Offcanvas"),qe=v("show.bs.offcanvas"),Ge=v("shown.bs.offcanvas"),Ue=v("hide.bs.offcanvas"),Ze=v("hidden.bs.offcanvas");function Je(t,e){const s=e?n:o;t.triggers.forEach(t=>s(t,"click",tn))}function Ke(t,e){const s=e?n:o,a=i(t.element);s(a,"keydown",nn),s(a,"click",en)}function Qe(t){const{element:e,options:n}=t;n.scroll||(!function(t){const{element:e}=t,{clientHeight:n,scrollHeight:o}=F(e);me(e,n!==o)}(t),re(e).style.overflow="hidden"),M(e,"offcanvas-toggling"),M(e,"show"),e.style.visibility="visible",s(e,()=>function(t){const{element:e,triggers:n}=t;u(e,"offcanvas-toggling"),le(e,"aria-hidden"),P(e,"aria-modal","true"),P(e,"role","dialog"),n.length&&n.forEach(t=>P(t,"aria-expanded","true"));f(e,Ge),Ke(t,!0),St(e)}(t))}function _e(t){const{element:e,options:n}=t,o=ye(e);e.blur(),!o&&n.backdrop&&h(we,"show")?(He(),s(we,()=>on(t))):on(t)}function tn(t){const e=c(this,'[data-bs-toggle="offcanvas"]'),n=e&&Z(e),o=n&&Ve(n);o&&(o.relatedTarget=e,o.toggle(),e&&"A"===e.tagName&&t.preventDefault())}function en(t){const e=r(".offcanvas.show",this);if(!e)return;const n=r('[data-bs-dismiss="offcanvas"]',e),o=Ve(e);if(!o)return;const{options:s,triggers:a}=o,{target:l}=t,d=c(l,'[data-bs-toggle="offcanvas"]'),h=i(e).getSelection();h&&h.toString().length||!(!e.contains(l)&&s.backdrop&&(!d||d&&!a.includes(d))||n&&n.contains(l))||(o.relatedTarget=n&&n.contains(l)?n:null,o.hide()),d&&"A"===d.tagName&&t.preventDefault()}function nn({code:t}){const e=r(".offcanvas.show",this);if(!e)return;const n=Ve(e);n&&n.options.keyboard&&"Escape"===t&&(n.relatedTarget=null,n.hide())}function on(t){const{element:e,triggers:n}=t;if(P(e,"aria-hidden","true"),le(e,"aria-modal"),le(e,"role"),e.style.visibility="",n.length){n.forEach(t=>P(t,"aria-expanded","false"));const t=n.find(t=>Ae(t));t&&St(t)}ke(e),f(e,Ze),u(e,"offcanvas-toggling"),ye(e)||Ke(t)}class sn extends y{constructor(t,e){super(t,e);const{element:n}=this;this.triggers=[...W('[data-bs-toggle="offcanvas"]')].filter(t=>Z(t)===n),this.container=he(n),this.relatedTarget=null,Je(this,!0)}get name(){return"Offcanvas"}get defaults(){return je}toggle(){h(this.element,"show")?this.hide():this.show()}show(){const t=this,{element:n,options:o,container:s,relatedTarget:i}=t;let a=0;if(h(n,"show"))return;if(qe.relatedTarget=i,Ge.relatedTarget=i,f(n,qe),qe.defaultPrevented)return;const l=ye(n);if(l&&l!==n){(Ve(l)||m(l,"Modal")).hide()}o.backdrop?(l?Te():Ee(s,!0),a=e(we),h(we,"show")||xe(),setTimeout(()=>Qe(t),a)):(Qe(t),l&&h(we,"show")&&He())}hide(t){const e=this,{element:n,relatedTarget:o}=e;h(n,"show")&&(Ue.relatedTarget=o,Ze.relatedTarget=o,f(n,Ue),Ue.defaultPrevented||(M(n,"offcanvas-toggling"),u(n,"show"),t?_e(e):s(n,()=>_e(e))))}dispose(){this.hide(!0),Je(this),super.dispose()}}d(sn,{selector:".offcanvas",init:t=>new sn(t),getInstance:Ve});const an=t=>t&&[SVGElement,HTMLImageElement,HTMLVideoElement].some(e=>t instanceof e),{userAgentData:ln}=navigator,rn=ln,{userAgent:cn}=navigator,dn=cn,hn=/(iPhone|iPod|iPad)/,un=rn?rn.brands.some(t=>hn.test(t.brand)):hn.test(dn);let fn=1;const pn=new Map;function gn(t,e){fn+=1;let n=pn.get(t),o=fn;return n?o=e&&e.length&&n.get&&n.get(e)?n.get(e):n:e&&e.length?(n||(pn.set(t,new Map),n=pn.get(t)),n.set(e,o)):pn.set(t,o),o}const mn="focusin",vn="focusout";var bn={top:"top",bottom:"bottom",left:"start",right:"end"};function wn(t,e){return t instanceof HTMLElement&&e.contains(t)}const yn=!!dn&&dn.includes("Firefox");function Tn(t,e,n){const o=e instanceof HTMLElement,s=I(t,o&&function(t){const{width:e,height:n}=I(t),{offsetWidth:o,offsetHeight:s}=t;return Math.round(e)!==o||Math.round(n)!==s}(e)),i={x:0,y:0};if(o){const t=I(e,!0);i.x=t.x+e.clientLeft,i.y=t.y+e.clientTop}return{x:s.left+n.x-i.x,y:s.top+n.y-i.y,width:s.width,height:s.height}}function En(e,n){const o=/\b(top|bottom|start|end)+/,s=e.tooltip||e.popover,i={...bn};Ct(s,{top:"0px",left:"0px",right:""});const a=!!e.popover,l=s.offsetWidth,r=s.offsetHeight,{element:c,options:d,arrow:h,offsetParent:u}=e,f=$(c);f&&(i.left="end",i.right="start");const p=F(c),g=p.clientWidth,m=p.clientHeight,{container:v}=d;let{placement:b}=d;const w="BODY"===v.tagName,{left:y,right:T}=I(v,!0),E=t(v,"position"),x="fixed"===E,H="static"===E,k="absolute"===t(c,"position"),A=v.clientWidth+y+(g-T)-1,{width:P,height:M,left:N,right:L,top:D}=I(c,!0),C=function(t){const e="scrollX"in t;return{x:e?t.scrollX:t.scrollLeft,y:e?t.scrollY:t.scrollTop}}(w||H?U(c):v),S=(t=>t instanceof SVGElement)(c),{x:R,y:O}=Tn(c,u,C);let W,B,X,z,Y,j;Ct(h,{top:"",left:"",right:""});const V=h.offsetWidth||0,q=h.offsetHeight||0,G=V/2;let Z=D-r-q<0,J=D+r+M+q>=m,K=N-l-V<0,Q=N+l+P+V>=A;const _=["left","right"],tt=["top","bottom"];if(Z=_.includes(b)?D+M/2-r/2-q<0:Z,J=_.includes(b)?D+r/2+M/2+q>=m:J,K=tt.includes(b)?N+P/2-l/2<0:K,Q=tt.includes(b)?N+l/2+P/2>=A:Q,b=_.includes(b)&&K&&Q?"top":b,b="top"===b&&Z?"bottom":b,b="bottom"===b&&J?"top":b,b="left"===b&&K?"right":b,b="right"===b&&Q?"left":b,s.className.includes(b)||(s.className=s.className.replace(o,i[b])),_.includes(b))B="left"===b?R-l-(a?V:0):R+P+(a?V:0),Z?(W=O,z=M/2-V):J?(W=O-r+M,z=r-M/2-V):(W=O-r/2+M/2,z=r/2-q/2);else if(tt.includes(b))if(n&&an(c)){let t=0,e=0;w||H?(t=n.pageX,e=n.pageY):["sticky","fixed"].includes(E)?(t=n.clientX+C.x,e=n.clientY+C.y):(t=n.layerX+(S&&!yn||k?R:0),e=n.layerY+(S&&!yn||k?O:0));const o=T-v.clientWidth;t-=f&&x?o:0,W="top"===b?e-r-V:e+V,n.clientX-l/2<0?(B=0,Y=t-G):n.clientX+l/2>A?(B="auto",X=0,j=A-t-G):(B=t-l/2,Y=l/2-G)}else W="top"===b?O-r-(a?q:0):O+M+(a?q:0),K?(B=0,Y=R+P/2-G):Q?(B="auto",X=0,j=P/2+A-L-G):(B=R-l/2+P/2,Y=l/2-G);Ct(s,{top:W+"px",left:"auto"===B?B:B+"px",right:void 0!==X?X+"px":""}),void 0!==z&&(h.style.top=z+"px"),void 0!==Y?h.style.left=Y+"px":void 0!==j&&(h.style.right=j+"px")}function xn(t,e,n){if("string"!=typeof e||e.length)if("string"==typeof e){let o=e.trim();"function"==typeof n&&(o=n(o));const s=(new DOMParser).parseFromString(o,"text/html"),{body:i}=s,a=i.children.length?"innerHTML":"innerText";t[a]=i[a]}else e instanceof HTMLElement&&t.append(e)}const Hn=v("show.bs.popover"),kn=v("shown.bs.popover"),An=v("hide.bs.popover"),Pn=v("hidden.bs.popover"),Mn={template:'',title:null,content:null,customClass:null,trigger:"hover",placement:"top",btnClose:'',sanitizeFn:null,dismissible:!1,animation:!0,delay:200,container:null},Nn=t=>m(t,"Popover");function Ln({target:t}){const e=this,{popover:n,element:o}=e;n&&n.contains(t)||t===o||o.contains(t)||e.hide()}function Dn(t,e){const s=e?n:o,{element:i,options:a}=t,{trigger:l,dismissible:r}=a;t.enabled=!!e,"hover"===l?(s(i,"mousedown",t.show),s(i,X,t.show),an(i)&&s(i,"mousemove",t.update,S),r||s(i,z,t.hide)):"click"===l?s(i,l,t.toggle):"focus"===l&&(un&&s(i,"click",()=>St(i)),s(i,mn,t.show))}function Cn(t,e){const s=e?n:o,{options:a,element:l,btn:r}=t,{trigger:c,dismissible:d}=a;d?r&&s(r,"click",t.hide):("focus"===c&&s(l,vn,t.hide),"hover"===c&&s(i(l),"touchstart",Ln,S)),an(l)||[Mt,Nt].forEach(e=>{s(U(l),e,t.update,S)})}function Sn(t){const{element:e}=t;f(e,kn),G(e,"in")}function Rn(t){!function(t){const{element:e,popover:n}=t;le(e,"aria-describedby"),n.remove()}(t);const{element:e}=t;f(e,Pn),G(e,"out")}class In extends y{constructor(e,n){super(e,n);const{element:o}=this;this.popover={},this.arrow=null,this.btn=null,this.offsetParent={},this.enabled=!0,this.id="popover-"+gn(o,"popover");const{options:s}=this;if(!s.content)return;const a=r(s.container),l=he(o);this.options.container=!a||a&&["static","relative"].includes(t(a,"position"))?l:a||re(o),function(t){const{id:e,element:n,options:o}=t,{animation:s,customClass:a,sanitizeFn:l,placement:c,dismissible:d}=o;let{title:u,content:f}=o;const{template:p,btnClose:g}=o,m={...bn};$(n)&&(m.left="end",m.right="start");const v="bs-popover-"+m[c];let b;if([Element,HTMLElement].some(t=>p instanceof t))b=p;else{const t=i(n).createElement("div");xn(t,p,l),b=t.firstElementChild}t.popover=b&&b.cloneNode(!0);const{popover:w}=t;P(w,"id",e),P(w,"role","tooltip");const y=r(".popover-header",w),T=r(".popover-body",w);t.arrow=r(".popover-arrow",w),d&&(u?u instanceof HTMLElement?xn(u,g,l):u+=g:(y&&y.remove(),f instanceof HTMLElement?xn(f,g,l):f+=g)),u&&y&&xn(y,u,l),f&&T&&xn(T,f,l),[t.btn]=B("btn-close",w),h(w,"popover")||M(w,"popover"),s&&!h(w,"fade")&&M(w,"fade"),a&&!h(w,a)&&M(w,a),h(w,v)||M(w,v)}(this),Ln.bind(this),this.update=this.update.bind(this),Dn(this,!0)}get name(){return"Popover"}get defaults(){return Mn}update(t){En(this,t)}toggle(t){const e=t?Nn(this):this;if(!e)return;const{popover:n,options:o}=e;wn(n,o.container)?e.hide():e.show()}show(t){const e=t?Nn(this):this;if(!e)return;const{element:n,popover:o,options:i,id:a}=e,{container:l,animation:r}=i,c=q(n,"out");if(G(n,"out"),o&&!c&&!wn(o,l)){V(n,()=>{f(n,Hn),Hn.defaultPrevented||(l.append(o),P(n,"aria-describedby","#"+a),e.offsetParent=he(o,!0),e.update(t),h(o,"show")||M(o,"show"),Cn(e,!0),r?s(o,()=>Sn(e)):Sn(e))},17,"in")}}hide(t){let e;if(t){if(e=Nn(this),!e){const t=this.closest(".popover"),n=t&&r(`[aria-describedby="#${t.id}"]`);e=Nn(n)}}else e=this;const{element:n,popover:o,options:i}=e,{container:a,animation:l,delay:c}=i;if(G(n,"in"),o&&wn(o,a)){V(n,()=>{f(n,An),An.defaultPrevented||(u(o,"show"),Cn(e),l?s(o,()=>Rn(e)):Rn(e))},c+17,"out")}}enable(){const t=this,{enabled:e}=t;e||(Dn(t,!0),t.enabled=!e)}disable(){const t=this,{element:n,enabled:o,popover:s,options:i}=t,{container:a,animation:l,delay:r}=i;o&&(wn(s,a)&&l?(t.hide(),V(n,()=>{Dn(t),G(n,"popover")},e(s)+r+17,"popover")):Dn(t),t.enabled=!o)}toggleEnabled(){this.enabled?this.disable():this.enable()}dispose(){const t=this,{popover:e,options:n}=t,{container:o,animation:i}=n;i&&wn(e,o)?(t.options.delay=0,t.hide(),s(e,()=>Dn(t))):Dn(t),super.dispose()}}function Fn(t,e){return(e&&a.some(t=>e instanceof t)?e:i()).getElementsByTagName(t)}d(In,{selector:'[data-bs-toggle="popover"],[data-tip="popover"]',init:t=>new In(t),getInstance:Nn,styleTip:En});const On={offset:10,target:null},Wn=v("activate.bs.scrollspy");function Bn(t){const{target:e,scrollTarget:n,options:o,itemsLength:s,scrollHeight:a,element:l}=t,{offset:c}=o,d=n instanceof Window,h=e&&Fn("A",e),u=n&&function(t){return t instanceof HTMLElement?t.scrollHeight:F(t).scrollHeight}(n);if(t.scrollTop=d?n.scrollY:n.scrollTop,h&&(s!==h.length||u!==a)){let e,n,o;t.items=[],t.offsets=[],t.scrollHeight=u,t.maxScroll=t.scrollHeight-function({element:t,scrollTarget:e}){return e instanceof Window?e.innerHeight:I(t).height}(t),[...h].forEach(s=>{e=Y(s,"href"),n=e&&"#"===e.charAt(0)&&"#"!==e.slice(-1)&&r(e,i(l)),n&&(t.items.push(s),o=n.getBoundingClientRect(),t.offsets.push((d?o.top+t.scrollTop:n.offsetTop)-c))}),t.itemsLength=t.items.length}}function Xn(t){[...Fn("A",t)].forEach(t=>{h(t,"active")&&u(t,"active")})}function zn(t,e){const{target:n,element:o}=t;Xn(n),t.activeItem=e,M(e,"active");const s=[];let i=e;for(;i!==re(o);)i=i.parentElement,(h(i,"nav")||h(i,"dropdown-menu"))&&s.push(i);s.forEach(t=>{const e=t.previousElementSibling;e&&!h(e,"active")&&M(e,"active")}),Wn.relatedTarget=e,f(o,Wn)}function Yn(t,e){(e?n:o)(t.scrollTarget,Mt,t.refresh,S)}class $n extends y{constructor(t,e){super(t,e);const{element:n,options:o}=this;if(this.target=r(o.target,i(n)),!this.target)return;const s=U(n);this.scrollTarget=n.clientHeight=o){const e=i[s-1];return void(a!==e&&zn(t,e))}const{offsets:l}=t;if(a&&n0)return t.activeItem=null,void Xn(e);i.forEach((e,o)=>{a!==e&&n>=l[o]&&(void 0===l[o+1]||nnew $n(t),getInstance:t=>m(t,"ScrollSpy")});const jn=t=>m(t,"Tab"),Vn=v("show.bs.tab"),qn=v("shown.bs.tab"),Gn=v("hide.bs.tab"),Un=v("hidden.bs.tab"),Zn=new Map;function Jn(t){const{tabContent:e,nav:n}=t;e&&(e.style.height="",u(e,"collapsing")),n&&G(n)}function Kn(t){const{element:e,tabContent:n,nav:o}=t,{currentHeight:i,nextHeight:a}=Zn.get(e),{tab:l}=o&&Zn.get(o);n?i===a?Jn(t):setTimeout(()=>{n.style.height=a+"px",R(n),s(n,()=>Jn(t))},50):o&&G(o),qn.relatedTarget=l,f(e,qn)}function Qn(t){const{element:e,content:n,tabContent:o,nav:i}=t,{tab:a,content:l}=i&&Zn.get(i);let r=0;if(o&&([l,n].forEach(t=>M(t,"overflow-hidden")),r=l.scrollHeight),Vn.relatedTarget=a,Un.relatedTarget=e,f(e,Vn),!Vn.defaultPrevented){if(M(n,"active"),u(l,"active"),o){const t=n.scrollHeight;Zn.set(e,{currentHeight:r,nextHeight:t}),M(o,"collapsing"),o.style.height=r+"px",R(o),[l,n].forEach(t=>u(t,"overflow-hidden"))}n&&h(n,"fade")?setTimeout(()=>{M(n,"show"),s(n,()=>{Kn(t)})},17):Kn(t),f(a,Un)}}function _n(t,e){(e?n:o)(t.element,"click",to)}function to(t){const e=jn(this);e&&(t.preventDefault(),e.show())}class eo extends y{constructor(t){super(t);const{element:e}=this,n=Z(e);if(!n)return;const o=c(e,".nav"),s=c(n,".tab-content");this.nav=o,this.content=n,this.tabContent=s,this.dropdown=o&&r(`.${It[0]}-toggle`,o),_n(this,!0)}get name(){return"Tab"}show(){const t=this,{element:e,nav:n,dropdown:o}=t;if(!(n&&q(n)||h(e,"active"))){const{tab:i,content:a}=function(t){const{nav:e}=t,n=B("active",e);let o;return 1!==n.length||It.some(t=>h(n[0].parentElement,t))?n.length>1&&(o=n[n.length-1]):[o]=n,{tab:o,content:o?Z(o):null}}(t);if(n&&Zn.set(n,{tab:i,content:a}),Gn.relatedTarget=e,f(i,Gn),Gn.defaultPrevented)return;n&&V(n,()=>{},17),u(i,"active"),P(i,"aria-selected","false"),M(e,"active"),P(e,"aria-selected","true"),o&&(h(e.parentNode,"dropdown-menu")?h(o,"active")||M(o,"active"):h(o,"active")&&u(o,"active")),h(a,"fade")?(u(a,"show"),s(a,()=>Qn(t))):Qn(t)}}dispose(){_n(this),super.dispose()}}d(eo,{selector:'[data-bs-toggle="tab"]',init:t=>new eo(t),getInstance:jn});const no={animation:!0,autohide:!0,delay:5e3},oo=t=>m(t,"Toast"),so=v("show.bs.toast"),io=v("shown.bs.toast"),ao=v("hide.bs.toast"),lo=v("hidden.bs.toast");function ro(t){const{element:e,options:n}=t;u(e,"showing"),G(e,"showing"),f(e,io),n.autohide&&V(e,()=>t.hide(),n.delay,"toast")}function co(t){const{element:e}=t;u(e,"showing"),u(e,"show"),M(e,"hide"),G(e,"toast"),f(e,lo)}function ho(t,e){const s=e?n:o,{element:i,dismiss:a,options:l}=t;a&&s(a,"click",t.hide),l.autohide&&[mn,vn,X,z].forEach(t=>s(i,t,uo))}function uo(t){const e=this,n=oo(e),{type:o,relatedTarget:s}=t;n&&e!==s&&!e.contains(s)&&([X,mn].includes(o)?G(e,"toast"):V(e,()=>n.hide(),n.options.delay,"toast"))}class fo extends y{constructor(t,e){super(t,e);const{element:n,options:o}=this;o.animation&&!h(n,"fade")?M(n,"fade"):!o.animation&&h(n,"fade")&&u(n,"fade"),this.dismiss=r('[data-bs-dismiss="toast"]',n),this.show=this.show.bind(this),this.hide=this.hide.bind(this),ho(this,!0)}get name(){return"Toast"}get defaults(){return no}show(){const t=this,{element:e}=t;if(e&&!h(e,"show")){if(f(e,so),so.defaultPrevented)return;!function(t){const{element:e,options:n}=t;V(e,()=>{u(e,"hide"),R(e),M(e,"show"),M(e,"showing"),n.animation?s(e,()=>ro(t)):ro(t)},17,"showing")}(t)}}hide(){const t=this,{element:e}=t;if(e&&h(e,"show")){if(f(e,ao),ao.defaultPrevented)return;!function(t){const{element:e,options:n}=t;M(e,"showing"),n.animation?(R(e),s(e,()=>co(t))):co(t)}(t)}}dispose(){const{element:t}=this;h(t,"show")&&u(t,"show"),function(t){G(t.element,"toast"),ho(t)}(this),super.dispose()}}d(fo,{selector:".toast",init:t=>new fo(t),getInstance:oo});const po={template:'',title:null,customClass:null,placement:"top",sanitizeFn:null,animation:!0,delay:200,container:null},go=t=>m(t,"Tooltip"),mo=v("show.bs.tooltip"),vo=v("shown.bs.tooltip"),bo=v("hide.bs.tooltip"),wo=v("hidden.bs.tooltip");function yo(t){const{element:e}=t;Ho(t),e.hasAttribute("data-original-title")&&ko(t)}function To(t,e){const s=e?n:o,{element:a}=t;s(i(a),"touchstart",Ao,S),an(a)||[Mt,Nt].forEach(e=>{s(U(a),e,t.update,S)})}function Eo(t){const{element:e}=t;To(t,!0),f(e,vo),G(e,"in")}function xo(t){const{element:e}=t;To(t),function(t){const{element:e,tooltip:n}=t;le(e,"aria-describedby"),n.remove()}(t),f(e,wo),G(e,"out")}function Ho(t,e){const s=e?n:o,{element:i}=t;an(i)&&s(i,"mousemove",t.update,S),s(i,"mousedown",t.show),s(i,X,t.show),s(i,z,t.hide)}function ko(t,e){const n=["data-original-title","title"],{element:o}=t;P(o,n[e?0:1],e||Y(o,n[0])),le(o,n[e?1:0])}function Ao({target:t}){const{tooltip:e,element:n}=this;e.contains(t)||t===n||n.contains(t)||this.hide()}class Po extends y{constructor(e,n){const o=r(e);po.title=o&&Y(o,"title"),super(e,n);if(!o)return;this.tooltip={},this.arrow=null,this.offsetParent={},this.enabled=!0,this.id="tooltip-"+gn(o,"tooltip");const{options:s}=this;if(!s.title)return;const a=r(s.container),l=he(o);this.options.container=!a||a&&["static","relative"].includes(t(a,"position"))?l:a||re(o),po.title=null,Ao.bind(this),this.update=this.update.bind(this),o.hasAttribute("title")&&ko(this,s.title),function(t){const{element:e,options:n,id:o}=t,{title:s,template:a,customClass:l,animation:c,placement:d,sanitizeFn:u}=n,f={...bn};$(e)&&(f.left="end",f.right="start");const p="bs-tooltip-"+f[d];if(!s)return;let g;if([Element,HTMLElement].some(t=>a instanceof t))g=a;else{const t=i(e).createElement("div");xn(t,a,u),g=t.firstElementChild}t.tooltip=g&&g.cloneNode(!0);const{tooltip:m}=t,v=r(".tooltip-inner",m);v&&xn(v,s,u),P(m,"id",o),P(m,"role","tooltip"),t.arrow=r(".tooltip-arrow",m),h(m,"tooltip")||M(m,"tooltip"),c&&!h(m,"fade")&&M(m,"fade"),l&&!h(m,l)&&M(m,l),h(m,p)||M(m,p)}(this),Ho(this,!0)}get name(){return"Tooltip"}get defaults(){return po}show(t){const e=t?go(this):this;if(!e)return;const{options:n,tooltip:o,element:i,id:a}=e,{container:l,animation:r}=n,c=q(i,"out");if(G(i,"out"),o&&!c&&!wn(o,l)){V(i,()=>{f(i,mo),mo.defaultPrevented||(l.append(o),P(i,"aria-describedby","#"+a),e.offsetParent=he(o,!0),e.update(t),h(o,"show")||M(o,"show"),r?s(o,()=>Eo(e)):Eo(e))},17,"in")}}hide(t){const e=t?go(this):this;if(!e)return;const{options:n,tooltip:o,element:i}=e,{container:a,animation:l,delay:r}=n;if(G(i,"in"),o&&wn(o,a)){V(i,()=>{f(i,bo),bo.defaultPrevented||(u(o,"show"),l?s(o,()=>xo(e)):xo(e))},r+17,"out")}}update(t){En(this,t)}toggle(t){const e=t?go(this):this;if(!e)return;const{tooltip:n,options:o}=e;wn(n,o.container)?e.hide():e.show()}enable(){const t=this,{enabled:e}=t;e||(Ho(t,!0),t.enabled=!e)}disable(){const t=this,{element:n,tooltip:o,options:s,enabled:i}=t,{animation:a,container:l,delay:r}=s;i&&(!wn(o,l)&&a?(t.hide(),V(n,()=>{Ho(t),G(n,"tooltip")},e(o)+r+17,"tooltip")):Ho(t),t.enabled=!i)}toggleEnabled(){this.enabled?this.disable():this.enable()}dispose(){const t=this,{tooltip:e,options:n}=t;n.animation&&wn(e,n.container)?(n.delay=0,t.hide(),s(e,()=>yo(t))):yo(t),super.dispose()}}d(Po,{selector:'[data-bs-toggle="tooltip"],[data-tip="tooltip"]',init:t=>new Po(t),getInstance:go,styleTip:En});const Mo=t=>t&&!!t.shadowRoot;function No(t){return[...(t&&a.some(e=>t instanceof e)?t:i()).querySelectorAll("*")].filter(Mo)}const Lo={Alert:A,Button:C,Carousel:vt,Collapse:Pt,Dropdown:ae,Modal:$e,Offcanvas:sn,Popover:In,ScrollSpy:$n,Tab:eo,Toast:fo,Tooltip:Po},Do=w(Lo);function Co(t,e){[...e].forEach(e=>t(e))}function So(t,e){const n=g.getAllFor(t);n&&[...n].forEach(t=>{const[n,o]=t;e&&e.contains(n)&&o.dispose()})}function Ro(t){const e=t&&a.some(e=>t instanceof e)?t:void 0,n=No(e);Do.forEach(t=>{const{init:o,selector:s}=Lo[t];Co(o,W(s,e)),n.forEach(t=>Co(o,W(s,t.shadowRoot)))})}document.body?Ro():document.addEventListener("DOMContentLoaded",()=>Ro(),{once:!0});const Io={Alert:A,Button:C,Carousel:vt,Collapse:Pt,Dropdown:ae,Modal:$e,Offcanvas:sn,Popover:In,ScrollSpy:$n,Tab:eo,Toast:fo,Tooltip:Po,initCallback:Ro,removeDataAPI:function(t){const e=t&&a.some(e=>t instanceof e)?t:void 0,n=No(e);Do.forEach(t=>{So(t,e),n.forEach(e=>So(t,e.shadowRoot))})},Version:"4.1.0alpha1"};export{Io as default};
diff --git a/dist/bootstrap-native.js b/dist/bootstrap-native.js
index 9858e581..cc668498 100644
--- a/dist/bootstrap-native.js
+++ b/dist/bootstrap-native.js
@@ -1,6 +1,6 @@
/*!
- * Native JavaScript for Bootstrap v4.1.0 (https://thednp.github.io/bootstrap.native/)
- * Copyright 2015-2021 © dnp_theme
+ * Native JavaScript for Bootstrap v4.1.0alpha1 (https://thednp.github.io/bootstrap.native/)
+ * Copyright 2015-2022 © dnp_theme
* Licensed under MIT (https://github.com/thednp/bootstrap.native/blob/master/LICENSE)
*/
(function (global, factory) {
@@ -10,42 +10,64 @@
})(this, (function () { 'use strict';
/**
- * A global namespace for 'transitionend' string.
+ * A global namespace for `click` event.
* @type {string}
*/
- const transitionEndEvent = 'webkitTransition' in document.head.style ? 'webkitTransitionEnd' : 'transitionend';
+ const mouseclickEvent = 'click';
/**
- * A global namespace for CSS3 transition support.
- * @type {boolean}
+ * A global namespace for 'transitionend' string.
+ * @type {string}
*/
- const supportTransition = 'webkitTransition' in document.head.style || 'transition' in document.head.style;
+ const transitionEndEvent = 'transitionend';
/**
* A global namespace for 'transitionDelay' string.
* @type {string}
*/
- const transitionDelay = 'webkitTransition' in document.head.style ? 'webkitTransitionDelay' : 'transitionDelay';
+ const transitionDelay = 'transitionDelay';
/**
- * A global namespace for 'transitionProperty' string.
+ * A global namespace for:
+ * * `transitionProperty` string for Firefox,
+ * * `transition` property for all other browsers.
+ *
* @type {string}
*/
- const transitionProperty = 'webkitTransition' in document.head.style ? 'webkitTransitionProperty' : 'transitionProperty';
+ const transitionProperty = 'transitionProperty';
+
+ /**
+ * Shortcut for `window.getComputedStyle(element).propertyName`
+ * static method.
+ *
+ * * If `element` parameter is not an `HTMLElement`, `getComputedStyle`
+ * throws a `ReferenceError`.
+ *
+ * @param {HTMLElement | Element} element target
+ * @param {string} property the css property
+ * @return {string} the css property value
+ */
+ function getElementStyle(element, property) {
+ const computedStyle = getComputedStyle(element);
+
+ // @ts-ignore -- must use camelcase strings,
+ // or non-camelcase strings with `getPropertyValue`
+ return property in computedStyle ? computedStyle[property] : '';
+ }
/**
- * Utility to get the computed transitionDelay
+ * Utility to get the computed `transitionDelay`
* from Element in miliseconds.
*
- * @param {Element} element target
+ * @param {HTMLElement | Element} element target
* @return {number} the value in miliseconds
*/
function getElementTransitionDelay(element) {
- const computedStyle = getComputedStyle(element);
- const propertyValue = computedStyle[transitionProperty];
- const delayValue = computedStyle[transitionDelay];
+ const propertyValue = getElementStyle(element, transitionProperty);
+ const delayValue = getElementStyle(element, transitionDelay);
+
const delayScale = delayValue.includes('ms') ? 1 : 1000;
- const duration = supportTransition && propertyValue && propertyValue !== 'none'
+ const duration = propertyValue && propertyValue !== 'none'
? parseFloat(delayValue) * delayScale : 0;
return !Number.isNaN(duration) ? duration : 0;
@@ -55,32 +77,57 @@
* A global namespace for 'transitionDuration' string.
* @type {string}
*/
- const transitionDuration = 'webkitTransition' in document.head.style ? 'webkitTransitionDuration' : 'transitionDuration';
+ const transitionDuration = 'transitionDuration';
/**
- * Utility to get the computed transitionDuration
+ * Utility to get the computed `transitionDuration`
* from Element in miliseconds.
*
- * @param {Element} element target
+ * @param {HTMLElement | Element} element target
* @return {number} the value in miliseconds
*/
function getElementTransitionDuration(element) {
- const computedStyle = getComputedStyle(element);
- const propertyValue = computedStyle[transitionProperty];
- const durationValue = computedStyle[transitionDuration];
+ const propertyValue = getElementStyle(element, transitionProperty);
+ const durationValue = getElementStyle(element, transitionDuration);
const durationScale = durationValue.includes('ms') ? 1 : 1000;
- const duration = supportTransition && propertyValue && propertyValue !== 'none'
+ const duration = propertyValue && propertyValue !== 'none'
? parseFloat(durationValue) * durationScale : 0;
return !Number.isNaN(duration) ? duration : 0;
}
+ /**
+ * Add eventListener to an `Element` | `HTMLElement` | `Document` target.
+ *
+ * @param {HTMLElement | Element | Document | Window} element event.target
+ * @param {string} eventName event.type
+ * @param {EventListenerObject['handleEvent']} handler callback
+ * @param {(EventListenerOptions | boolean)=} options other event options
+ */
+ function on(element, eventName, handler, options) {
+ const ops = options || false;
+ element.addEventListener(eventName, handler, ops);
+ }
+
+ /**
+ * Remove eventListener from an `Element` | `HTMLElement` | `Document` | `Window` target.
+ *
+ * @param {HTMLElement | Element | Document | Window} element event.target
+ * @param {string} eventName event.type
+ * @param {EventListenerObject['handleEvent']} handler callback
+ * @param {(EventListenerOptions | boolean)=} options other event options
+ */
+ function off(element, eventName, handler, options) {
+ const ops = options || false;
+ element.removeEventListener(eventName, handler, ops);
+ }
+
/**
* Utility to make sure callbacks are consistently
* called when transition ends.
*
- * @param {Element} element target
- * @param {function} handler `transitionend` callback
+ * @param {HTMLElement | Element} element target
+ * @param {EventListener} handler `transitionend` callback
*/
function emulateTransitionEnd(element, handler) {
let called = 0;
@@ -91,17 +138,16 @@
if (duration) {
/**
* Wrap the handler in on -> off callback
- * @param {Event} e Event object
- * @callback
+ * @param {TransitionEvent} e Event object
*/
const transitionEndWrapper = (e) => {
if (e.target === element) {
handler.apply(element, [e]);
- element.removeEventListener(transitionEndEvent, transitionEndWrapper);
+ off(element, transitionEndEvent, transitionEndWrapper);
called = 1;
}
};
- element.addEventListener(transitionEndEvent, transitionEndWrapper);
+ on(element, transitionEndEvent, transitionEndWrapper);
setTimeout(() => {
if (!called) element.dispatchEvent(endEvent);
}, duration + delay + 17);
@@ -111,33 +157,75 @@
}
/**
- * Checks if an element is an `Element`.
- *
- * @param {any} element the target element
- * @returns {boolean} the query result
+ * Returns the `document` or the `#document` element.
+ * @see https://github.com/floating-ui/floating-ui
+ * @param {(Node | HTMLElement | Element | globalThis)=} node
+ * @returns {Document}
*/
- function isElement(element) {
- return element instanceof Element;
+ function getDocument(node) {
+ if (node instanceof HTMLElement) return node.ownerDocument;
+ if (node instanceof Window) return node.document;
+ return window.document;
}
/**
- * Utility to check if target is typeof Element
+ * A global array of possible `ParentNode`.
+ */
+ const parentNodes = [Document, Node, Element, HTMLElement];
+
+ /**
+ * A global array with `Element` | `HTMLElement`.
+ */
+ const elementNodes = [Element, HTMLElement];
+
+ /**
+ * Utility to check if target is typeof `HTMLElement`, `Element`, `Node`
* or find one that matches a selector.
*
- * @param {Element | string} selector the input selector or target element
- * @param {Element=} parent optional Element to look into
- * @return {Element?} the Element or `querySelector` result
+ * @param {HTMLElement | Element | string} selector the input selector or target element
+ * @param {(HTMLElement | Element | Node | Document)=} parent optional node to look into
+ * @return {(HTMLElement | Element)?} the `HTMLElement` or `querySelector` result
*/
- function queryElement(selector, parent) {
- const lookUp = parent && isElement(parent) ? parent : document;
- // @ts-ignore
- return isElement(selector) ? selector : lookUp.querySelector(selector);
+ function querySelector(selector, parent) {
+ const selectorIsString = typeof selector === 'string';
+ const lookUp = parent && parentNodes.some((x) => parent instanceof x)
+ ? parent : getDocument();
+
+ if (!selectorIsString && [...elementNodes].some((x) => selector instanceof x)) {
+ return selector;
+ }
+ // @ts-ignore -- `ShadowRoot` is also a node
+ return selectorIsString ? lookUp.querySelector(selector) : null;
}
/**
- * Check class in Element.classList
+ * Shortcut for `HTMLElement.closest` method which also works
+ * with children of `ShadowRoot`. The order of the parameters
+ * is intentional since they're both required.
+ *
+ * @see https://stackoverflow.com/q/54520554/803358
*
- * @param {Element} element target
+ * @param {HTMLElement | Element} element Element to look into
+ * @param {string} selector the selector name
+ * @return {(HTMLElement | Element)?} the query result
+ */
+ function closest(element, selector) {
+ return element ? (element.closest(selector)
+ // @ts-ignore -- break out of `ShadowRoot`
+ || closest(element.getRootNode().host, selector)) : null;
+ }
+
+ /**
+ * Shortcut for `Object.assign()` static method.
+ * @param {Record} obj a target object
+ * @param {Record} source a source object
+ */
+ const ObjectAssign = (obj, source) => Object.assign(obj, source);
+
+ /**
+ * Check class in `HTMLElement.classList`.
+ *
+ * @param {HTMLElement | Element} element target
* @param {string} classNAME to check
* @return {boolean}
*/
@@ -146,9 +234,9 @@
}
/**
- * Remove class from Element.classList
+ * Remove class from `HTMLElement.classList`.
*
- * @param {Element} element target
+ * @param {HTMLElement | Element} element target
* @param {string} classNAME to remove
*/
function removeClass(element, classNAME) {
@@ -156,17 +244,14 @@
}
/**
- * A global namespace for 'addEventListener' string.
- * @type {string}
- */
- const addEventListener = 'addEventListener';
-
- /**
- * A global namespace for 'removeEventListener' string.
- * @type {string}
+ * Shortcut for the `Element.dispatchEvent(Event)` method.
+ *
+ * @param {HTMLElement | Element} element is the target
+ * @param {Event} event is the `Event` object
*/
- const removeEventListener = 'removeEventListener';
+ const dispatchEvent = (element, event) => element.dispatchEvent(event);
+ /** @type {Map>>} */
const componentData = new Map();
/**
* An interface for web components background data.
@@ -175,59 +260,58 @@
const Data = {
/**
* Sets web components data.
- * @param {Element | string} element target element
+ * @param {HTMLElement | Element | string} target target element
* @param {string} component the component's name or a unique key
- * @param {any} instance the component instance
+ * @param {Record} instance the component instance
*/
- set: (element, component, instance) => {
- const ELEMENT = queryElement(element);
- if (!isElement(ELEMENT)) return;
+ set: (target, component, instance) => {
+ const element = querySelector(target);
+ if (!element) return;
if (!componentData.has(component)) {
componentData.set(component, new Map());
}
const instanceMap = componentData.get(component);
- instanceMap.set(ELEMENT, instance);
+ // @ts-ignore - not undefined, but defined right above
+ instanceMap.set(element, instance);
},
/**
* Returns all instances for specified component.
* @param {string} component the component's name or a unique key
- * @returns {any?} all the component instances
+ * @returns {Map>?} all the component instances
*/
getAllFor: (component) => {
- if (componentData.has(component)) {
- return componentData.get(component);
- }
- return null;
+ const instanceMap = componentData.get(component);
+
+ return instanceMap || null;
},
/**
* Returns the instance associated with the target.
- * @param {Element | string} element target element
+ * @param {HTMLElement | Element | string} target target element
* @param {string} component the component's name or a unique key
- * @returns {any?} the instance
+ * @returns {Record?} the instance
*/
- get: (element, component) => {
- const ELEMENT = queryElement(element);
-
+ get: (target, component) => {
+ const element = querySelector(target);
const allForC = Data.getAllFor(component);
- if (allForC && isElement(ELEMENT) && allForC.has(ELEMENT)) {
- return allForC.get(ELEMENT);
- }
- return null;
+ const instance = element && allForC && allForC.get(element);
+
+ return instance || null;
},
/**
* Removes web components data.
- * @param {Element} element target element
+ * @param {HTMLElement | Element | string} target target element
* @param {string} component the component's name or a unique key
*/
- remove: (element, component) => {
- if (!componentData.has(component)) return;
-
+ remove: (target, component) => {
+ const element = querySelector(target);
const instanceMap = componentData.get(component);
+ if (!instanceMap || !element) return;
+
instanceMap.delete(element);
if (instanceMap.size === 0) {
@@ -238,11 +322,9 @@
/**
* An alias for `Data.get()`.
- * @param {Element | string} element target element
- * @param {string} component the component's name or a unique key
- * @returns {any} the request result
+ * @type {SHORTER.getInstance}
*/
- const getInstance = (element, component) => Data.get(element, component);
+ const getInstance = (target, component) => Data.get(target, component);
/**
* Global namespace for most components `fade` class.
@@ -259,30 +341,17 @@
*/
const dataBsDismiss = 'data-bs-dismiss';
- /** Returns an original event for Bootstrap Native components. */
- class OriginalEvent extends CustomEvent {
- /**
- * @param {string} EventType event.type
- * @param {Record=} config Event.options | Event.properties
- */
- constructor(EventType, config) {
- super(EventType, config);
- /** @type {EventTarget?} */
- this.relatedTarget = null;
- }
- }
-
/**
* Returns a namespaced `CustomEvent` specific to each component.
* @param {string} EventType Event.type
* @param {Record=} config Event.options | Event.properties
- * @returns {OriginalEvent} a new namespaced event
+ * @returns {BSN.OriginalEvent} a new namespaced event
*/
function bootstrapCustomEvent(EventType, config) {
- const OriginalCustomEvent = new OriginalEvent(EventType, { cancelable: true, bubbles: true });
+ const OriginalCustomEvent = new CustomEvent(EventType, { cancelable: true, bubbles: true });
if (config instanceof Object) {
- Object.assign(OriginalCustomEvent, config);
+ ObjectAssign(OriginalCustomEvent, config);
}
return OriginalCustomEvent;
}
@@ -290,7 +359,7 @@
/**
* The raw value or a given component option.
*
- * @typedef {string | Element | Function | number | boolean | null} niceValue
+ * @typedef {string | HTMLElement | Function | number | boolean | null} niceValue
*/
/**
@@ -316,94 +385,93 @@
return null;
}
- // string / function / Element / object
+ // string / function / HTMLElement / object
return value;
}
/**
- * Utility to normalize component options
+ * Shortcut for `Object.keys()` static method.
+ * @param {Record} obj a target object
+ * @returns {string[]}
+ */
+ const ObjectKeys = (obj) => Object.keys(obj);
+
+ /**
+ * Utility to normalize component options.
*
- * @param {Element} element target
- * @param {object} defaultOps component default options
- * @param {object} inputOps component instance options
- * @param {string} ns component namespace
- * @return {object} normalized component options object
+ * @param {HTMLElement | Element} element target
+ * @param {Record} defaultOps component default options
+ * @param {Record} inputOps component instance options
+ * @param {string=} ns component namespace
+ * @return {Record} normalized component options object
*/
function normalizeOptions(element, defaultOps, inputOps, ns) {
- // @ts-ignore
+ // @ts-ignore -- our targets are always `HTMLElement`
const data = { ...element.dataset };
+ /** @type {Record} */
const normalOps = {};
+ /** @type {Record} */
const dataOps = {};
- Object.keys(data)
- .forEach((k) => {
- const key = k.includes(ns)
- ? k.replace(ns, '').replace(/[A-Z]/, (match) => match.toLowerCase())
- : k;
+ ObjectKeys(data).forEach((k) => {
+ const key = ns && k.includes(ns)
+ ? k.replace(ns, '').replace(/[A-Z]/, (match) => match.toLowerCase())
+ : k;
- dataOps[key] = normalizeValue(data[k]);
- });
+ dataOps[key] = normalizeValue(data[k]);
+ });
- Object.keys(inputOps)
- .forEach((k) => {
- inputOps[k] = normalizeValue(inputOps[k]);
- });
+ ObjectKeys(inputOps).forEach((k) => {
+ inputOps[k] = normalizeValue(inputOps[k]);
+ });
- Object.keys(defaultOps)
- .forEach((k) => {
- if (k in inputOps) {
- normalOps[k] = inputOps[k];
- } else if (k in dataOps) {
- normalOps[k] = dataOps[k];
- } else {
- normalOps[k] = defaultOps[k];
- }
- });
+ ObjectKeys(defaultOps).forEach((k) => {
+ if (k in inputOps) {
+ normalOps[k] = inputOps[k];
+ } else if (k in dataOps) {
+ normalOps[k] = dataOps[k];
+ } else {
+ normalOps[k] = defaultOps[k];
+ }
+ });
return normalOps;
}
- var version = "4.1.0";
+ var version = "4.1.0alpha1";
const Version = version;
/* Native JavaScript for Bootstrap 5 | Base Component
----------------------------------------------------- */
- /**
- * Returns a new `BaseComponent` instance.
- */
+ /** Returns a new `BaseComponent` instance. */
class BaseComponent {
/**
- * @param {Element | string} target Element or selector string
+ * @param {HTMLElement | Element | string} target `Element` or selector string
* @param {BSN.ComponentOptions=} config component instance options
*/
constructor(target, config) {
const self = this;
- const element = queryElement(target);
+ const element = querySelector(target);
- if (!isElement(element)) {
- throw TypeError(`${self.name} Error: "${target}" not a valid selector.`);
+ if (!element) {
+ throw Error(`${self.name} Error: "${target}" is not a valid selector.`);
}
- /** @type {BSN.ComponentOptions} */
+ /** @static @type {BSN.ComponentOptions} */
self.options = {};
- // @ts-ignore
const prevInstance = Data.get(element, self.name);
if (prevInstance) prevInstance.dispose();
- /** @type {Element} */
- // @ts-ignore
+ /** @type {HTMLElement | Element} */
self.element = element;
if (self.defaults && Object.keys(self.defaults).length) {
- /** @static @type {Record} */
- // @ts-ignore
self.options = normalizeOptions(element, self.defaults, (config || {}), 'bs');
}
- // @ts-ignore
Data.set(element, self.name, self);
}
@@ -424,10 +492,9 @@
*/
dispose() {
const self = this;
- // @ts-ignore
Data.remove(self.element, self.name);
// @ts-ignore
- Object.keys(self).forEach((prop) => { self[prop] = null; });
+ ObjectKeys(self).forEach((prop) => { self[prop] = null; });
}
}
@@ -470,7 +537,7 @@
const { element } = self;
toggleAlertHandler(self);
- element.dispatchEvent(closedAlertEvent);
+ dispatchEvent(element, closedAlertEvent);
self.dispose();
element.remove();
@@ -484,16 +551,16 @@
* @param {boolean=} add when `true`, event listener is added
*/
function toggleAlertHandler(self, add) {
- const action = add ? addEventListener : removeEventListener;
- // @ts-ignore
- if (isElement(self.dismiss)) self.dismiss[action]('click', self.close);
+ const action = add ? on : off;
+ const { dismiss } = self;
+ if (dismiss) action(dismiss, mouseclickEvent, self.close);
}
// ALERT DEFINITION
// ================
/** Creates a new Alert instance. */
class Alert extends BaseComponent {
- /** @param {Element | string} target element or selector */
+ /** @param {HTMLElement | Element | string} target element or selector */
constructor(target) {
super(target);
// bind
@@ -503,11 +570,8 @@
const { element } = self;
// the dismiss button
- /** @static @type {Element?} */
- // @ts-ignore
- self.dismiss = queryElement(alertDismissSelector, element);
- /** @static @type {Element?} */
- self.relatedTarget = null;
+ /** @static @type {(HTMLElement | Element)?} */
+ self.dismiss = querySelector(alertDismissSelector, element);
// add event listener
toggleAlertHandler(self, true);
@@ -528,16 +592,17 @@
* disposes the instance once animation is complete, then
* removes the element from the DOM.
*
- * @param {Event} e most likely the `click` event
+ * @param {Event=} e most likely the `click` event
+ * @this {Alert} the `Alert` instance or `EventTarget`
*/
close(e) {
- const target = e ? e.target : null;
// @ts-ignore
- const self = e ? getAlertInstance(target.closest(alertSelector)) : this;
+ const self = e ? getAlertInstance(closest(this, alertSelector)) : this;
+ if (!self) return;
const { element } = self;
- if (self && element && hasClass(element, showClass)) {
- element.dispatchEvent(closeAlertEvent);
+ if (hasClass(element, showClass)) {
+ dispatchEvent(element, closeAlertEvent);
if (closeAlertEvent.defaultPrevented) return;
removeClass(element, showClass);
@@ -555,28 +620,36 @@
}
}
- Object.assign(Alert, {
+ ObjectAssign(Alert, {
selector: alertSelector,
init: alertInitCallback,
getInstance: getAlertInstance,
});
/**
- * Add class to Element.classList
+ * A global namespace for aria-pressed.
+ * @type {string}
+ */
+ const ariaPressed = 'aria-pressed';
+
+ /**
+ * Shortcut for `HTMLElement.setAttribute()` method.
+ * @param {HTMLElement | Element} element target element
+ * @param {string} attribute attribute name
+ * @param {string} value attribute value
+ */
+ const setAttribute = (element, attribute, value) => element.setAttribute(attribute, value);
+
+ /**
+ * Add class to `HTMLElement.classList`.
*
- * @param {Element} element target
+ * @param {HTMLElement | Element} element target
* @param {string} classNAME to add
*/
function addClass(element, classNAME) {
element.classList.add(classNAME);
}
- /**
- * A global namespace for aria-pressed.
- * @type {string}
- */
- const ariaPressed = 'aria-pressed';
-
/**
* Global namespace for most components active class.
*/
@@ -618,9 +691,8 @@
* @param {boolean=} add when `true`, event listener is added
*/
function toggleButtonHandler(self, add) {
- const action = add ? addEventListener : removeEventListener;
- // @ts-ignore
- self.element[action]('click', self.toggle);
+ const action = add ? on : off;
+ action(self.element, mouseclickEvent, self.toggle);
}
// BUTTON DEFINITION
@@ -628,7 +700,7 @@
/** Creates a new `Button` instance. */
class Button extends BaseComponent {
/**
- * @param {Element | string} target usually a `.btn` element
+ * @param {HTMLElement | Element | string} target usually a `.btn` element
*/
constructor(target) {
super(target);
@@ -638,9 +710,9 @@
const { element } = self;
// set initial state
- /** @private @type {boolean} */
+ /** @type {boolean} */
self.isActive = hasClass(element, activeClass);
- element.setAttribute(ariaPressed, `${!!self.isActive}`);
+ setAttribute(element, ariaPressed, `${!!self.isActive}`);
// add event listener
toggleButtonHandler(self, true);
@@ -658,12 +730,13 @@
// =====================
/**
* Toggles the state of the target button.
- * @param {Event} e usually `click` Event object
+ * @param {MouseEvent} e usually `click` Event object
*/
toggle(e) {
if (e) e.preventDefault();
// @ts-ignore
const self = e ? getButtonInstance(this) : this;
+ if (!self) return;
const { element } = self;
if (hasClass(element, 'disabled')) return;
@@ -673,7 +746,7 @@
const action = isActive ? removeClass : addClass;
action(element, activeClass);
- element.setAttribute(ariaPressed, isActive ? 'false' : 'true');
+ setAttribute(element, ariaPressed, isActive ? 'false' : 'true');
}
/** Removes the `Button` component from the target element. */
@@ -683,64 +756,304 @@
}
}
- Object.assign(Button, {
+ ObjectAssign(Button, {
selector: buttonSelector,
init: buttonInitCallback,
getInstance: getButtonInstance,
});
/**
- * A global namespace for passive events support.
- * @type {boolean}
+ * A global namespace for most scroll event listeners.
+ * @type {Partial}
*/
- const supportPassive = (() => {
- let result = false;
- try {
- const opts = Object.defineProperty({}, 'passive', {
- get() {
- result = true;
- return result;
- },
- });
- document[addEventListener]('DOMContentLoaded', function wrap() {
- document[removeEventListener]('DOMContentLoaded', wrap, opts);
- }, opts);
- } catch (e) {
- throw Error('Passive events are not supported');
- }
+ const passiveHandler = { passive: true };
- return result;
- })();
+ /**
+ * Utility to force re-paint of an `HTMLElement` target.
+ *
+ * @param {HTMLElement | Element} element is the target
+ * @return {number} the `Element.offsetHeight` value
+ */
+ // @ts-ignore
+ const reflow = (element) => element.offsetHeight;
- // general event options
+ /**
+ * Returns the bounding client rect of a target `HTMLElement`.
+ *
+ * @see https://github.com/floating-ui/floating-ui
+ *
+ * @param {HTMLElement | Element} element event.target
+ * @param {boolean=} includeScale when *true*, the target scale is also computed
+ * @returns {SHORTER.BoundingClientRect} the bounding client rect object
+ */
+ function getBoundingClientRect(element, includeScale) {
+ const {
+ width, height, top, right, bottom, left,
+ } = element.getBoundingClientRect();
+ let scaleX = 1;
+ let scaleY = 1;
+
+ if (includeScale && element instanceof HTMLElement) {
+ const { offsetWidth, offsetHeight } = element;
+ scaleX = offsetWidth > 0 ? Math.round(width) / offsetWidth || 1 : 1;
+ scaleY = offsetHeight > 0 ? Math.round(height) / offsetHeight || 1 : 1;
+ }
+
+ return {
+ width: width / scaleX,
+ height: height / scaleY,
+ top: top / scaleY,
+ right: right / scaleX,
+ bottom: bottom / scaleY,
+ left: left / scaleX,
+ x: left / scaleX,
+ y: top / scaleY,
+ };
+ }
/**
- * A global namespace for most scroll event listeners.
+ * Returns the `document.documentElement` or the `` element.
+ *
+ * @param {(Node | HTMLElement | Element | globalThis)=} node
+ * @returns {HTMLElement | HTMLHtmlElement}
+ */
+ function getDocumentElement(node) {
+ return getDocument(node).documentElement;
+ }
+
+ /**
+ * Utility to determine if an `HTMLElement`
+ * is partially visible in viewport.
+ *
+ * @param {HTMLElement | Element} element target
+ * @return {boolean} the query result
+ */
+ const isElementInScrollRange = (element) => {
+ const { top, bottom } = getBoundingClientRect(element);
+ const { clientHeight } = getDocumentElement(element);
+ // checks bottom && top
+ return top <= clientHeight && bottom >= 0;
+ };
+
+ /**
+ * A shortcut for `(document|Element).querySelectorAll`.
+ *
+ * @param {string} selector the input selector
+ * @param {(HTMLElement | Element | Document | Node)=} parent optional node to look into
+ * @return {NodeListOf} the query result
*/
- const passiveHandler = supportPassive ? { passive: true } : false;
+ function querySelectorAll(selector, parent) {
+ const lookUp = parent && parentNodes
+ .some((x) => parent instanceof x) ? parent : getDocument();
+ // @ts-ignore -- `ShadowRoot` is also a node
+ return lookUp.querySelectorAll(selector);
+ }
/**
- * Utility to force re-paint of an Element
+ * Shortcut for `HTMLElement.getElementsByClassName` method. Some `Node` elements
+ * like `ShadowRoot` do not support `getElementsByClassName`.
*
- * @param {Element | HTMLElement} element is the target
- * @return {number} the Element.offsetHeight value
+ * @param {string} selector the class name
+ * @param {(HTMLElement | Element | Document)=} parent optional Element to look into
+ * @return {HTMLCollectionOf} the 'HTMLCollection'
+ */
+ function getElementsByClassName(selector, parent) {
+ const lookUp = parent && parentNodes.some((x) => parent instanceof x)
+ ? parent : getDocument();
+ return lookUp.getElementsByClassName(selector);
+ }
+
+ /**
+ * A global namespace for `mouseenter` event.
+ * @type {string}
*/
- function reflow(element) {
+ const mouseenterEvent = 'mouseenter';
+
+ /**
+ * A global namespace for `mouseleave` event.
+ * @type {string}
+ */
+ const mouseleaveEvent = 'mouseleave';
+
+ /**
+ * A global namespace for `keydown` event.
+ * @type {string}
+ */
+ const keydownEvent = 'keydown';
+
+ /**
+ * A global namespace for `touchmove` event.
+ * @type {string}
+ */
+ const touchmoveEvent = 'touchmove';
+
+ /**
+ * A global namespace for `touchend` event.
+ * @type {string}
+ */
+ const touchendEvent = 'touchend';
+
+ /**
+ * A global namespace for `touchstart` event.
+ * @type {string}
+ */
+ const touchstartEvent = 'touchstart';
+
+ /**
+ * Shortcut for `HTMLElement.getAttribute()` method.
+ * @param {HTMLElement | Element} element target element
+ * @param {string} attribute attribute name
+ */
+ const getAttribute = (element, attribute) => element.getAttribute(attribute);
+
+ /**
+ * A global namespace for `ArrowLeft` key.
+ * @type {string} e.which = 37 equivalent
+ */
+ const keyArrowLeft = 'ArrowLeft';
+
+ /**
+ * A global namespace for `ArrowRight` key.
+ * @type {string} e.which = 39 equivalent
+ */
+ const keyArrowRight = 'ArrowRight';
+
+ /**
+ * Checks if a page is Right To Left.
+ * @param {(HTMLElement | Element)=} node the target
+ * @returns {boolean} the query result
+ */
+ const isRTL = (node) => getDocumentElement(node).dir === 'rtl';
+
+ /** @type {Map} */
+ const TimeCache = new Map();
+ /**
+ * An interface for one or more `TimerHandler`s per `Element`.
+ * @see https://github.com/thednp/navbar.js/
+ */
+ const Timer = {
+ /**
+ * Sets a new timeout timer for an element, or element -> key association.
+ * @param {HTMLElement | Element | string} target target element
+ * @param {ReturnType} callback the callback
+ * @param {number} delay the execution delay
+ * @param {string=} key a unique
+ */
+ set: (target, callback, delay, key) => {
+ const element = querySelector(target);
+
+ if (!element) return;
+
+ if (key && key.length) {
+ if (!TimeCache.has(element)) {
+ TimeCache.set(element, new Map());
+ }
+ const keyTimers = TimeCache.get(element);
+ keyTimers.set(key, setTimeout(callback, delay));
+ } else {
+ TimeCache.set(element, setTimeout(callback, delay));
+ }
+ },
+
+ /**
+ * Returns the timer associated with the target.
+ * @param {HTMLElement | Element | string} target target element
+ * @param {string=} key a unique
+ * @returns {number?} the timer
+ */
+ get: (target, key) => {
+ const element = querySelector(target);
+
+ if (!element) return null;
+ const keyTimers = TimeCache.get(element);
+
+ if (key && key.length && keyTimers && keyTimers.get) {
+ return keyTimers.get(key) || null;
+ }
+ return keyTimers || null;
+ },
+
+ /**
+ * Clears the element's timer.
+ * @param {HTMLElement | Element | string} target target element
+ * @param {string=} key a unique key
+ */
+ clear: (target, key) => {
+ const element = querySelector(target);
+
+ if (!element) return;
+
+ if (key && key.length) {
+ const keyTimers = TimeCache.get(element);
+
+ if (keyTimers && keyTimers.get) {
+ clearTimeout(keyTimers.get(key));
+ keyTimers.delete(key);
+ if (keyTimers.size === 0) {
+ TimeCache.delete(element);
+ }
+ }
+ } else {
+ clearTimeout(TimeCache.get(element));
+ TimeCache.delete(element);
+ }
+ },
+ };
+
+ /**
+ * Returns the `Window` object of a target node.
+ * @see https://github.com/floating-ui/floating-ui
+ *
+ * @param {(Node | HTMLElement | Element | Window)=} node target node
+ * @returns {globalThis}
+ */
+ function getWindow(node) {
+ if (node == null) {
+ return window;
+ }
+
+ if (!(node instanceof Window)) {
+ const { ownerDocument } = node;
+ return ownerDocument ? ownerDocument.defaultView || window : window;
+ }
+
// @ts-ignore
- return element.offsetHeight;
+ return node;
}
/**
- * Utility to determine if an `Element`
- * is partially visible in viewport.
+ * Global namespace for most components `target` option.
+ */
+ const dataBsTarget = 'data-bs-target';
+
+ /**
+ * Global namespace for most components `parent` option.
+ */
+ const dataBsParent = 'data-bs-parent';
+
+ /**
+ * Global namespace for most components `container` option.
+ */
+ const dataBsContainer = 'data-bs-container';
+
+ /**
+ * Returns the `Element` that THIS one targets
+ * via `data-bs-target`, `href`, `data-bs-parent` or `data-bs-container`.
*
- * @param {Element} element target
- * @return {boolean} Boolean
+ * @param {HTMLElement | Element} element the target element
+ * @returns {(HTMLElement | Element)?} the query result
*/
- function isElementInScrollRange(element) {
- const bcr = element.getBoundingClientRect();
- const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
- return bcr.top <= viewportHeight && bcr.bottom >= 0; // bottom && top
+ function getTargetElement(element) {
+ const targetAttr = [dataBsTarget, dataBsParent, dataBsContainer, 'href'];
+ const doc = getDocument(element);
+
+ return targetAttr.map((att) => {
+ const attValue = getAttribute(element, att);
+ if (attValue) {
+ return att === dataBsParent ? closest(element, attValue) : querySelector(attValue, doc);
+ }
+ return null;
+ }).filter((x) => x)[0];
}
/* Native JavaScript for Bootstrap 5 | Carousel
@@ -751,9 +1064,10 @@
const carouselString = 'carousel';
const carouselComponent = 'Carousel';
const carouselSelector = `[data-bs-ride="${carouselString}"]`;
- const carouselControl = `${carouselString}-control`;
const carouselItem = `${carouselString}-item`;
const dataBsSlideTo = 'data-bs-slide-to';
+ const dataBsSlide = 'data-bs-slide';
+
const pausedClass = 'paused';
const carouselDefaults = {
@@ -794,18 +1108,14 @@
*/
function carouselTransitionEndHandler(self) {
const {
- // @ts-ignore
- index, direction, element, slides, options, isAnimating,
+ index, direction, element, slides, options,
} = self;
// discontinue disposed instances
- // @ts-ignore
- if (isAnimating && getCarouselInstance(element)) {
+ if (self.isAnimating && getCarouselInstance(element)) {
const activeItem = getActiveIndex(self);
const orientation = direction === 'left' ? 'next' : 'prev';
const directionClass = direction === 'left' ? 'start' : 'end';
- // @ts-ignore
- self.isAnimating = false;
addClass(slides[index], activeClass);
removeClass(slides[activeItem], activeClass);
@@ -814,13 +1124,12 @@
removeClass(slides[index], `${carouselItem}-${directionClass}`);
removeClass(slides[activeItem], `${carouselItem}-${directionClass}`);
- // @ts-ignore
- element.dispatchEvent(carouselSlidEvent);
+ dispatchEvent(element, carouselSlidEvent);
+ Timer.clear(element, dataBsSlide);
// check for element, might have been disposed
- if (!document.hidden && options.interval
- // @ts-ignore
- && !hasClass(element, pausedClass)) {
+ if (!getDocument(element).hidden && options.interval
+ && !self.isPaused) {
self.cycle();
}
}
@@ -830,97 +1139,78 @@
* Handles the `mouseenter` / `touchstart` events when *options.pause*
* is set to `hover`.
*
- * @param {Event} e the `Event` object
+ * @param {MouseEvent} e the `Event` object
*/
function carouselPauseHandler(e) {
const eventTarget = e.target;
// @ts-ignore
- const self = getCarouselInstance(eventTarget.closest(carouselSelector));
- // @ts-ignore
- const { element, isAnimating } = self;
+ const self = getCarouselInstance(closest(eventTarget, carouselSelector));
- // @ts-ignore
- if (!hasClass(element, pausedClass)) {
- // @ts-ignore
- addClass(element, pausedClass);
- if (!isAnimating) {
- // @ts-ignore
- clearInterval(self.timer);
- // @ts-ignore
- self.timer = null;
- }
+ if (self && !self.isPaused) {
+ self.pause();
}
}
/**
- * Handles the `mouseleave` / `touchsend` events when *options.pause*
+ * Handles the `mouseleave` / `touchend` events when *options.pause*
* is set to `hover`.
*
- * @param {Event} e the `Event` object
+ * @param {MouseEvent} e the `Event` object
*/
function carouselResumeHandler(e) {
const { target } = e;
// @ts-ignore
- const self = getCarouselInstance(target.closest(carouselSelector));
- // @ts-ignore
- const { isPaused, isAnimating, element } = self;
+ const self = getCarouselInstance(closest(target, carouselSelector));
+ if (!self) return;
+ const { element } = self;
- // @ts-ignore
- if (!isPaused && hasClass(element, pausedClass)) {
- // @ts-ignore
+ if (self.isPaused) {
removeClass(element, pausedClass);
-
- if (!isAnimating) {
- // @ts-ignore
- clearInterval(self.timer);
- // @ts-ignore
- self.timer = null;
- self.cycle();
- }
+ self.cycle();
}
}
/**
* Handles the `click` event for the `Carousel` indicators.
*
- * @param {Event} e the `Event` object
+ * @this {HTMLElement}
+ * @param {MouseEvent} e the `Event` object
*/
function carouselIndicatorHandler(e) {
e.preventDefault();
- const { target } = e;
- // @ts-ignore
- const self = getCarouselInstance(target.closest(carouselSelector));
- // @ts-ignore
- if (self.isAnimating) return;
+ const indicator = this;
+ const element = closest(indicator, carouselSelector) || getTargetElement(indicator);
+ if (!element) return;
+ const self = getCarouselInstance(element);
- // @ts-ignore
- const newIndex = target.getAttribute(dataBsSlideTo);
+ if (!self || self.isAnimating) return;
// @ts-ignore
- if (target && !hasClass(target, activeClass) // event target is not active
- && newIndex) { // AND has the specific attribute
- self.to(+newIndex); // do the slide
+ const newIndex = +getAttribute(indicator, dataBsSlideTo);
+
+ if (indicator && !hasClass(indicator, activeClass) // event target is not active
+ && !Number.isNaN(newIndex)) { // AND has the specific attribute
+ self.to(newIndex); // do the slide
}
}
/**
* Handles the `click` event for the `Carousel` arrows.
*
- * @this {Element}
- * @param {Event} e the `Event` object
+ * @this {HTMLElement}
+ * @param {MouseEvent} e the `Event` object
*/
function carouselControlsHandler(e) {
e.preventDefault();
- const that = this;
- // @ts-ignore
- const self = getCarouselInstance(that.closest(carouselSelector));
-
- // @ts-ignore
- const { controls } = self;
+ const control = this;
+ const element = closest(control, carouselSelector) || getTargetElement(control);
+ const self = element && getCarouselInstance(element);
+ if (!self || self.isAnimating) return;
+ const orientation = getAttribute(control, dataBsSlide);
- if (controls[1] && that === controls[1]) {
+ if (orientation === 'next') {
self.next();
- } else if (controls[1] && that === controls[0]) {
+ } else if (orientation === 'prev') {
self.prev();
}
}
@@ -928,23 +1218,20 @@
/**
* Handles the keyboard `keydown` event for the visible `Carousel` elements.
*
- * @param {{which: number}} e the `Event` object
+ * @param {KeyboardEvent} e the `Event` object
*/
- function carouselKeyHandler({ which }) {
- const [element] = Array.from(document.querySelectorAll(carouselSelector))
+ function carouselKeyHandler({ code }) {
+ const [element] = [...querySelectorAll(carouselSelector)]
.filter((x) => isElementInScrollRange(x));
- if (!element) return;
const self = getCarouselInstance(element);
+ if (!self) return;
+ const RTL = isRTL();
+ const arrowKeyNext = !RTL ? keyArrowRight : keyArrowLeft;
+ const arrowKeyPrev = !RTL ? keyArrowLeft : keyArrowRight;
- switch (which) {
- case 39:
- self.next();
- break;
- case 37:
- self.prev();
- break;
- }
+ if (code === arrowKeyPrev) self.prev();
+ else if (code === arrowKeyNext) self.next();
}
// CAROUSEL TOUCH HANDLERS
@@ -952,22 +1239,19 @@
/**
* Handles the `touchdown` event for the `Carousel` element.
*
- * @this {Element}
- * @param {Event} e the `Event` object
+ * @this {HTMLElement | Element}
+ * @param {TouchEvent} e the `Event` object
*/
function carouselTouchDownHandler(e) {
const element = this;
const self = getCarouselInstance(element);
- // @ts-ignore
if (!self || self.isTouch) { return; }
- // @ts-ignore
startX = e.changedTouches[0].pageX;
// @ts-ignore
if (element.contains(e.target)) {
- // @ts-ignore
self.isTouch = true;
toggleCarouselTouchHandlers(self, true);
}
@@ -976,21 +1260,19 @@
/**
* Handles the `touchmove` event for the `Carousel` element.
*
- * @this {Element}
- * @param {Event} e the `Event` object
+ * @this {HTMLElement | Element}
+ * @param {TouchEvent} e
*/
function carouselTouchMoveHandler(e) {
- // @ts-ignore
const { changedTouches, type } = e;
const self = getCarouselInstance(this);
- // @ts-ignore
if (!self || !self.isTouch) { return; }
currentX = changedTouches[0].pageX;
// cancel touch if more than one changedTouches detected
- if (type === 'touchmove' && changedTouches.length > 1) {
+ if (type === touchmoveEvent && changedTouches.length > 1) {
e.preventDefault();
}
}
@@ -998,20 +1280,18 @@
/**
* Handles the `touchend` event for the `Carousel` element.
*
- * @this {Element}
- * @param {Event} e the `Event` object
+ * @this {HTMLElement | Element}
+
+ * @param {TouchEvent} e
*/
function carouselTouchEndHandler(e) {
const element = this;
const self = getCarouselInstance(element);
- // @ts-ignore
if (!self || !self.isTouch) { return; }
- // @ts-ignore
endX = currentX || e.changedTouches[0].pageX;
- // @ts-ignore
if (self.isTouch) {
// the event target is outside the carousel OR carousel doens't include the related target
// @ts-ignore
@@ -1021,16 +1301,12 @@
return;
} // OR determine next index to slide to
if (currentX < startX) {
- // @ts-ignore
self.index += 1;
} else if (currentX > startX) {
- // @ts-ignore
self.index -= 1;
}
- // @ts-ignore
self.isTouch = false;
- // @ts-ignore
self.to(self.index); // do the slide
toggleCarouselTouchHandlers(self); // remove touch events handlers
@@ -1045,10 +1321,9 @@
* @param {number} pageIndex the index of the new active indicator
*/
function activateCarouselIndicator(self, pageIndex) {
- // @ts-ignore
const { indicators } = self;
- Array.from(indicators).forEach((x) => removeClass(x, activeClass));
- // @ts-ignore
+ [...indicators].forEach((x) => removeClass(x, activeClass));
+
if (self.indicators[pageIndex]) addClass(indicators[pageIndex], activeClass);
}
@@ -1059,11 +1334,9 @@
*/
function toggleCarouselTouchHandlers(self, add) {
const { element } = self;
- const action = add ? addEventListener : removeEventListener;
- // @ts-ignore
- element[action]('touchmove', carouselTouchMoveHandler, passiveHandler);
- // @ts-ignore
- element[action]('touchend', carouselTouchEndHandler, passiveHandler);
+ const action = add ? on : off;
+ action(element, touchmoveEvent, carouselTouchMoveHandler, passiveHandler);
+ action(element, touchendEvent, carouselTouchEndHandler, passiveHandler);
}
/**
@@ -1073,39 +1346,37 @@
*/
function toggleCarouselHandlers(self, add) {
const {
- // @ts-ignore
- element, options, slides, controls, indicator,
+ element, options, slides, controls, indicators,
} = self;
const {
touch, pause, interval, keyboard,
} = options;
- const action = add ? addEventListener : removeEventListener;
+ const action = add ? on : off;
if (pause && interval) {
- // @ts-ignore
- element[action]('mouseenter', carouselPauseHandler);
- // @ts-ignore
- element[action]('mouseleave', carouselResumeHandler);
- // @ts-ignore
- element[action]('touchstart', carouselPauseHandler, passiveHandler);
- // @ts-ignore
- element[action]('touchend', carouselResumeHandler, passiveHandler);
+ action(element, mouseenterEvent, carouselPauseHandler);
+ action(element, mouseleaveEvent, carouselResumeHandler);
+ action(element, touchstartEvent, carouselPauseHandler, passiveHandler);
+ action(element, touchendEvent, carouselResumeHandler, passiveHandler);
}
if (touch && slides.length > 1) {
- // @ts-ignore
- element[action]('touchstart', carouselTouchDownHandler, passiveHandler);
+ action(element, touchstartEvent, carouselTouchDownHandler, passiveHandler);
}
- controls.forEach((arrow) => {
- // @ts-ignore
- if (arrow) arrow[action]('click', carouselControlsHandler);
- });
+ if (controls.length) {
+ controls.forEach((arrow) => {
+ if (arrow) action(arrow, mouseclickEvent, carouselControlsHandler);
+ });
+ }
+ if (indicators.length) {
+ indicators.forEach((indicator) => {
+ action(indicator, mouseclickEvent, carouselIndicatorHandler);
+ });
+ }
// @ts-ignore
- if (indicator) indicator[action]('click', carouselIndicatorHandler);
- // @ts-ignore
- if (keyboard) window[action]('keydown', carouselKeyHandler);
+ if (keyboard) action(getWindow(element), keydownEvent, carouselKeyHandler);
}
/**
@@ -1114,10 +1385,10 @@
* @returns {number} the query result
*/
function getActiveIndex(self) {
- // @ts-ignore
const { slides, element } = self;
- return Array.from(slides)
- .indexOf(element.getElementsByClassName(`${carouselItem} ${activeClass}`)[0]) || 0;
+ const activeItem = querySelector(`.${carouselItem}.${activeClass}`, element);
+ // @ts-ignore
+ return [...slides].indexOf(activeItem);
}
// CAROUSEL DEFINITION
@@ -1125,7 +1396,7 @@
/** Creates a new `Carousel` instance. */
class Carousel extends BaseComponent {
/**
- * @param {Element | string} target mostly a `.carousel` element
+ * @param {HTMLElement | Element | string} target mostly a `.carousel` element
* @param {BSN.Options.Carousel=} config instance options
*/
constructor(target, config) {
@@ -1134,42 +1405,38 @@
const self = this;
// additional properties
- /** @private @type {any?} */
- self.timer = null;
- /** @private @type {string} */
- self.direction = 'left';
- /** @private @type {boolean} */
- self.isPaused = false;
- /** @private @type {boolean} */
- self.isAnimating = false;
- /** @private @type {number} */
+ /** @type {string} */
+ self.direction = isRTL() ? 'right' : 'left';
+ /** @type {number} */
self.index = 0;
- /** @private @type {boolean} */
+ /** @type {boolean} */
self.isTouch = false;
// initialization element
const { element } = self;
// carousel elements
// a LIVE collection is prefferable
- /** @private @type {HTMLCollection} */
- self.slides = element.getElementsByClassName(carouselItem);
+ self.slides = getElementsByClassName(carouselItem, element);
const { slides } = self;
// invalidate when not enough items
// no need to go further
if (slides.length < 2) { return; }
- /** @private @type {[?Element, ?Element]} */
self.controls = [
- queryElement(`.${carouselControl}-prev`, element),
- queryElement(`.${carouselControl}-next`, element),
+ ...querySelectorAll(`[${dataBsSlide}]`, element),
+ ...querySelectorAll(`[${dataBsSlide}][${dataBsTarget}="#${element.id}"]`),
];
+ /** @type {(HTMLElement | Element)?} */
+ self.indicator = querySelector(`.${carouselString}-indicators`, element);
+
// a LIVE collection is prefferable
- /** @private @type {Element?} */
- self.indicator = queryElement('.carousel-indicators', element);
- /** @private @type {NodeList | any[]} */
- self.indicators = (self.indicator && self.indicator.querySelectorAll(`[${dataBsSlideTo}]`)) || [];
+ /** @type {(HTMLElement | Element)[]} */
+ self.indicators = [
+ ...(self.indicator ? querySelectorAll(`[${dataBsSlideTo}]`, self.indicator) : []),
+ ...querySelectorAll(`[${dataBsSlideTo}][${dataBsTarget}="#${element.id}"]`),
+ ];
// set JavaScript and DATA API options
const { options } = self;
@@ -1205,39 +1472,45 @@
get defaults() { return carouselDefaults; }
/* eslint-enable */
+ /**
+ * Check if instance is paused.
+ * @returns {boolean}
+ */
+ get isPaused() {
+ return hasClass(this.element, pausedClass);
+ }
+
+ /**
+ * Check if instance is animating.
+ * @returns {boolean}
+ */
+ get isAnimating() {
+ return querySelector(`.${carouselItem}-next,.${carouselItem}-prev`, this.element) !== null;
+ }
+
// CAROUSEL PUBLIC METHODS
// =======================
/** Slide automatically through items. */
cycle() {
const self = this;
- const { isPaused, element, options } = self;
- if (self.timer) {
- clearInterval(self.timer);
- self.timer = null;
- }
+ const { element, options } = self;
- if (isPaused) {
- removeClass(element, pausedClass);
- self.isPaused = !isPaused;
- }
+ Timer.clear(element, carouselString);
- self.timer = setInterval(() => {
- if (isElementInScrollRange(element)) {
+ Timer.set(element, () => {
+ if (!self.isPaused && isElementInScrollRange(element)) {
self.index += 1;
self.to(self.index);
}
- }, options.interval);
+ }, options.interval, carouselString);
}
/** Pause the automatic cycle. */
pause() {
const self = this;
- const { element, options, isPaused } = self;
- if (options.interval && !isPaused) {
- clearInterval(self.timer);
- self.timer = null;
+ const { element, options } = self;
+ if (!self.isPaused && options.interval) {
addClass(element, pausedClass);
- self.isPaused = !isPaused;
}
}
@@ -1260,20 +1533,21 @@
to(idx) {
const self = this;
const {
- element, isAnimating, slides, options,
+ element, slides, options,
} = self;
const activeItem = getActiveIndex(self);
+ const RTL = isRTL();
let next = idx;
// when controled via methods, make sure to check again
// first return if we're on the same item #227
- if (isAnimating || activeItem === next) return;
+ if (self.isAnimating || activeItem === next) return;
// determine transition direction
if ((activeItem < next) || (activeItem === 0 && next === slides.length - 1)) {
- self.direction = 'left'; // next
+ self.direction = RTL ? 'right' : 'left'; // next
} else if ((activeItem > next) || (activeItem === slides.length - 1 && next === 0)) {
- self.direction = 'right'; // prev
+ self.direction = RTL ? 'left' : 'right'; // prev
}
const { direction } = self;
@@ -1292,43 +1566,39 @@
};
// update event properties
- Object.assign(carouselSlideEvent, eventProperties);
- Object.assign(carouselSlidEvent, eventProperties);
+ ObjectAssign(carouselSlideEvent, eventProperties);
+ ObjectAssign(carouselSlidEvent, eventProperties);
// discontinue when prevented
- element.dispatchEvent(carouselSlideEvent);
+ dispatchEvent(element, carouselSlideEvent);
if (carouselSlideEvent.defaultPrevented) return;
// update index
self.index = next;
-
- clearInterval(self.timer);
- self.timer = null;
-
- self.isAnimating = true;
activateCarouselIndicator(self, next);
if (getElementTransitionDuration(slides[next]) && hasClass(element, 'slide')) {
- addClass(slides[next], `${carouselItem}-${orientation}`);
- reflow(slides[next]);
- addClass(slides[next], `${carouselItem}-${directionClass}`);
- addClass(slides[activeItem], `${carouselItem}-${directionClass}`);
-
- emulateTransitionEnd(slides[next], () => carouselTransitionEndHandler(self));
+ Timer.set(element, () => {
+ addClass(slides[next], `${carouselItem}-${orientation}`);
+ reflow(slides[next]);
+ addClass(slides[next], `${carouselItem}-${directionClass}`);
+ addClass(slides[activeItem], `${carouselItem}-${directionClass}`);
+
+ emulateTransitionEnd(slides[next], () => carouselTransitionEndHandler(self));
+ }, 17, dataBsSlide);
} else {
addClass(slides[next], activeClass);
removeClass(slides[activeItem], activeClass);
- setTimeout(() => {
- self.isAnimating = false;
-
+ Timer.set(element, () => {
+ Timer.clear(element, dataBsSlide);
// check for element, might have been disposed
- if (element && options.interval && !hasClass(element, pausedClass)) {
+ if (element && options.interval && !self.isPaused) {
self.cycle();
}
- element.dispatchEvent(carouselSlidEvent);
- }, 17);
+ dispatchEvent(element, carouselSlidEvent);
+ }, 17, dataBsSlide);
}
}
@@ -1338,18 +1608,17 @@
const { slides } = self;
const itemClasses = ['start', 'end', 'prev', 'next'];
- Array.from(slides).forEach((slide, idx) => {
+ [...slides].forEach((slide, idx) => {
if (hasClass(slide, activeClass)) activateCarouselIndicator(self, idx);
itemClasses.forEach((c) => removeClass(slide, `${carouselItem}-${c}`));
});
toggleCarouselHandlers(self);
- clearInterval(self.timer);
super.dispose();
}
}
- Object.assign(Carousel, {
+ ObjectAssign(Carousel, {
selector: carouselSelector,
init: carouselInitCallback,
getInstance: getCarouselInstance,
@@ -1367,36 +1636,6 @@
*/
const collapsingClass = 'collapsing';
- /**
- * Global namespace for most components `target` option.
- */
- const dataBsTarget = 'data-bs-target';
-
- /**
- * Global namespace for most components `parent` option.
- */
- const dataBsParent = 'data-bs-parent';
-
- /**
- * Global namespace for most components `container` option.
- */
- const dataBsContainer = 'data-bs-container';
-
- // @ts-nocheck
-
- /**
- * Returns the `Element` that THIS one targets
- * via `data-bs-target`, `href`, `data-bs-parent` or `data-bs-container`.
- *
- * @param {Element} element the target element
- * @returns {Element?} the query result
- */
- function getTargetElement(element) {
- return queryElement(element.getAttribute(dataBsTarget) || element.getAttribute('href'))
- || element.closest(element.getAttribute(dataBsParent))
- || queryElement(element.getAttribute(dataBsContainer));
- }
-
/* Native JavaScript for Bootstrap 5 | Collapse
----------------------------------------------- */
@@ -1437,17 +1676,14 @@
*/
function expandCollapse(self) {
const {
- // @ts-ignore
element, parent, triggers,
} = self;
- element.dispatchEvent(showCollapseEvent);
+ dispatchEvent(element, showCollapseEvent);
if (showCollapseEvent.defaultPrevented) return;
- // @ts-ignore
- self.isAnimating = true;
- // @ts-ignore
- if (parent) parent.isAnimating = true;
+ Timer.set(element, () => {}, 17);
+ if (parent) Timer.set(parent, () => {}, 17);
addClass(element, collapsingClass);
removeClass(element, collapseString);
@@ -1456,12 +1692,10 @@
element.style.height = `${element.scrollHeight}px`;
emulateTransitionEnd(element, () => {
- // @ts-ignore
- self.isAnimating = false;
- // @ts-ignore
- if (parent) parent.isAnimating = false;
+ Timer.clear(element);
+ if (parent) Timer.clear(parent);
- triggers.forEach((btn) => btn.setAttribute(ariaExpanded, 'true'));
+ triggers.forEach((btn) => setAttribute(btn, ariaExpanded, 'true'));
removeClass(element, collapsingClass);
addClass(element, collapseString);
@@ -1470,7 +1704,7 @@
// @ts-ignore
element.style.height = '';
- element.dispatchEvent(shownCollapseEvent);
+ dispatchEvent(element, shownCollapseEvent);
});
}
@@ -1484,14 +1718,12 @@
element, parent, triggers,
} = self;
- element.dispatchEvent(hideCollapseEvent);
+ dispatchEvent(element, hideCollapseEvent);
if (hideCollapseEvent.defaultPrevented) return;
- // @ts-ignore
- self.isAnimating = true;
- // @ts-ignore
- if (parent) parent.isAnimating = true;
+ Timer.set(element, () => {}, 17);
+ if (parent) Timer.set(parent, () => {}, 17);
// @ts-ignore
element.style.height = `${element.scrollHeight}px`;
@@ -1505,12 +1737,10 @@
element.style.height = '0px';
emulateTransitionEnd(element, () => {
- // @ts-ignore
- self.isAnimating = false;
- // @ts-ignore
- if (parent) parent.isAnimating = false;
+ Timer.clear(element);
+ if (parent) Timer.clear(parent);
- triggers.forEach((btn) => btn.setAttribute(ariaExpanded, 'false'));
+ triggers.forEach((btn) => setAttribute(btn, ariaExpanded, 'false'));
removeClass(element, collapsingClass);
addClass(element, collapseString);
@@ -1518,7 +1748,7 @@
// @ts-ignore
element.style.height = '';
- element.dispatchEvent(hiddenCollapseEvent);
+ dispatchEvent(element, hiddenCollapseEvent);
});
}
@@ -1528,13 +1758,11 @@
* @param {boolean=} add when `true`, the event listener is added
*/
function toggleCollapseHandler(self, add) {
- const action = add ? addEventListener : removeEventListener;
- // @ts-ignore
+ const action = add ? on : off;
const { triggers } = self;
if (triggers.length) {
- // @ts-ignore
- triggers.forEach((btn) => btn[action]('click', collapseClickHandler));
+ triggers.forEach((btn) => action(btn, mouseclickEvent, collapseClickHandler));
}
}
@@ -1542,13 +1770,12 @@
// ======================
/**
* Handles the `click` event for the `Collapse` instance.
- * @param {Event} e the `Event` object
+ * @param {MouseEvent} e the `Event` object
*/
function collapseClickHandler(e) {
- const { target } = e;
- // @ts-ignore
- const trigger = target.closest(collapseToggleSelector);
- const element = getTargetElement(trigger);
+ const { target } = e; // @ts-ignore - our target is `HTMLElement`
+ const trigger = target && closest(target, collapseToggleSelector);
+ const element = trigger && getTargetElement(trigger);
const self = element && getCollapseInstance(element);
if (self) self.toggle();
@@ -1562,7 +1789,7 @@
/** Returns a new `Colapse` instance. */
class Collapse extends BaseComponent {
/**
- * @param {Element | string} target and `Element` that matches the selector
+ * @param {HTMLElement | Element | string} target and `Element` that matches the selector
* @param {BSN.Options.Collapse=} config instance options
*/
constructor(target, config) {
@@ -1574,20 +1801,13 @@
const { element, options } = self;
// set triggering elements
- /** @private @type {Element[]} */
- self.triggers = Array.from(document.querySelectorAll(collapseToggleSelector))
+ /** @type {(HTMLElement | Element)[]} */
+ self.triggers = [...querySelectorAll(collapseToggleSelector)]
.filter((btn) => getTargetElement(btn) === element);
// set parent accordion
- /** @private @type {Element?} */
- self.parent = queryElement(options.parent);
- const { parent } = self;
-
- // set initial state
- /** @private @type {boolean} */
- self.isAnimating = false;
- // @ts-ignore
- if (parent) parent.isAnimating = false;
+ /** @type {(HTMLElement | Element)?} */
+ self.parent = querySelector(options.parent);
// add event listeners
toggleCollapseHandler(self, true);
@@ -1618,8 +1838,8 @@
/** Hides the collapse. */
hide() {
const self = this;
- const { triggers, isAnimating } = self;
- if (isAnimating) return;
+ const { triggers, element } = self;
+ if (Timer.get(element)) return;
collapseContent(self);
if (triggers.length) {
@@ -1631,19 +1851,18 @@
show() {
const self = this;
const {
- element, parent, triggers, isAnimating,
+ element, parent, triggers,
} = self;
let activeCollapse;
let activeCollapseInstance;
if (parent) {
- activeCollapse = Array.from(parent.querySelectorAll(`.${collapseString}.${showClass}`))
+ activeCollapse = [...querySelectorAll(`.${collapseString}.${showClass}`, parent)]
.find((i) => getCollapseInstance(i));
activeCollapseInstance = activeCollapse && getCollapseInstance(activeCollapse);
}
- // @ts-ignore
- if ((!parent || (parent && !parent.isAnimating)) && !isAnimating) {
+ if ((!parent || (parent && !Timer.get(parent))) && !Timer.get(element)) {
if (activeCollapseInstance && activeCollapse !== element) {
collapseContent(activeCollapseInstance);
activeCollapseInstance.triggers.forEach((btn) => {
@@ -1661,21 +1880,83 @@
/** Remove the `Collapse` component from the target `Element`. */
dispose() {
const self = this;
- const { parent } = self;
toggleCollapseHandler(self);
- // @ts-ignore
- if (parent) delete parent.isAnimating;
super.dispose();
}
}
- Object.assign(Collapse, {
+ ObjectAssign(Collapse, {
selector: collapseSelector,
init: collapseInitCallback,
getInstance: getCollapseInstance,
});
+ /**
+ * A global namespace for `focus` event.
+ * @type {string}
+ */
+ const focusEvent = 'focus';
+
+ /**
+ * A global namespace for `keyup` event.
+ * @type {string}
+ */
+ const keyupEvent = 'keyup';
+
+ /**
+ * A global namespace for `scroll` event.
+ * @type {string}
+ */
+ const scrollEvent = 'scroll';
+
+ /**
+ * A global namespace for `resize` event.
+ * @type {string}
+ */
+ const resizeEvent = 'resize';
+
+ /**
+ * A global namespace for `ArrowUp` key.
+ * @type {string} e.which = 38 equivalent
+ */
+ const keyArrowUp = 'ArrowUp';
+
+ /**
+ * A global namespace for `ArrowDown` key.
+ * @type {string} e.which = 40 equivalent
+ */
+ const keyArrowDown = 'ArrowDown';
+
+ /**
+ * A global namespace for `Escape` key.
+ * @type {string} e.which = 27 equivalent
+ */
+ const keyEscape = 'Escape';
+
+ /**
+ * Shortcut for multiple uses of `HTMLElement.style.propertyName` method.
+ * @param {HTMLElement | Element} element target element
+ * @param {Partial} styles attribute value
+ */
+ // @ts-ignore
+ const setElementStyle = (element, styles) => { ObjectAssign(element.style, styles); };
+
+ /**
+ * Utility to focus an `HTMLElement` target.
+ *
+ * @param {HTMLElement | Element} element is the target
+ */
+ // @ts-ignore -- `Element`s resulted from querySelector can focus too
+ const focus = (element) => element.focus();
+
+ /**
+ * Shortcut for `HTMLElement.hasAttribute()` method.
+ * @param {HTMLElement | Element} element target element
+ * @param {string} attribute attribute name
+ */
+ const hasAttribute = (element, attribute) => element.hasAttribute(attribute);
+
/**
* Global namespace for `Dropdown` types / classes.
*/
@@ -1690,23 +1971,16 @@
* Checks if an *event.target* or its parent has an `href="#"` value.
* We need to prevent jumping around onclick, don't we?
*
- * @param {Element} elem the target element
+ * @param {HTMLElement | HTMLAnchorElement | EventTarget} element the target element
* @returns {boolean} the query result
*/
- function isEmptyAnchor(elem) {
- const parentAnchor = elem.closest('A');
- // anchor href starts with #
- return elem && ((elem.hasAttribute('href') && elem.href.slice(-1) === '#')
- // OR a child of an anchor with href starts with #
- || (parentAnchor && parentAnchor.hasAttribute('href') && parentAnchor.href.slice(-1) === '#'));
- }
-
- /**
- * Points the focus to a specific element.
- * @param {Element} element target
- */
- function setFocus(element) {
- element.focus();
+ function isEmptyAnchor(element) {
+ // @ts-ignore -- `EventTarget` must be `HTMLElement`
+ const parentAnchor = closest(element, 'A');
+ // @ts-ignore -- anchor href starts with #
+ return element && ((hasAttribute(element, 'href') && element.href.slice(-1) === '#')
+ // @ts-ignore -- OR a child of an anchor with href starts with #
+ || (parentAnchor && hasAttribute(parentAnchor, 'href') && parentAnchor.href.slice(-1) === '#'));
}
/* Native JavaScript for Bootstrap 5 | Dropdown
@@ -1714,7 +1988,12 @@
// DROPDOWN PRIVATE GC
// ===================
- const [dropdownString] = dropdownMenuClasses;
+ const [
+ dropdownString,
+ dropupString,
+ dropstartString,
+ dropendString,
+ ] = dropdownMenuClasses;
const dropdownComponent = 'Dropdown';
const dropdownSelector = `[${dataBsToggle}="${dropdownString}"]`;
@@ -1734,13 +2013,10 @@
// DROPDOWN PRIVATE GC
// ===================
- const dropupString = dropdownMenuClasses[1];
- const dropstartString = dropdownMenuClasses[2];
- const dropendString = dropdownMenuClasses[3];
const dropdownMenuEndClass = `${dropdownMenuClass}-end`;
- const hideMenuClass = ['d-block', 'invisible'];
const verticalClass = [dropdownString, dropupString];
const horizontalClass = [dropstartString, dropendString];
+ const menuFocusTags = ['A', 'BUTTON'];
const dropdownDefaults = {
offset: 5, // [number] 5(px)
@@ -1761,90 +2037,79 @@
* accomodate the layout and the page scroll.
*
* @param {Dropdown} self the `Dropdown` instance
- * @param {boolean=} show when `true` will have a different effect
*/
- function styleDropdown(self, show) {
+ function styleDropdown(self) {
const {
- // @ts-ignore
- element, menu, originalClass, menuEnd, options,
+ element, menu, parentElement, options,
} = self;
const { offset } = options;
- const parent = element.parentElement;
+
+ // don't apply any style on mobile view
+ if (getElementStyle(menu, 'position') === 'static') return;
+
+ const RTL = isRTL(element);
+ const menuEnd = hasClass(parentElement, dropdownMenuEndClass);
// reset menu offset and position
const resetProps = ['margin', 'top', 'bottom', 'left', 'right'];
// @ts-ignore
resetProps.forEach((p) => { menu.style[p] = ''; });
- // @ts-ignore
- removeClass(parent, 'position-static');
-
- if (!show) {
- const menuEndNow = hasClass(menu, dropdownMenuEndClass);
- // @ts-ignore
- parent.className = originalClass.join(' ');
- if (menuEndNow && !menuEnd) removeClass(menu, dropdownMenuEndClass);
- else if (!menuEndNow && menuEnd) addClass(menu, dropdownMenuEndClass);
- return;
- }
// set initial position class
// take into account .btn-group parent as .dropdown
- let positionClass = dropdownMenuClasses.find((c) => originalClass.includes(c)) || dropdownString;
+ let positionClass = dropdownMenuClasses.find((c) => hasClass(parentElement, c)) || dropdownString;
+ /** @type {Record>} */
let dropdownMargin = {
dropdown: [offset, 0, 0],
dropup: [0, 0, offset],
- dropstart: [-1, offset, 0],
- dropend: [-1, 0, 0, offset],
+ dropstart: RTL ? [-1, 0, 0, offset] : [-1, offset, 0],
+ dropend: RTL ? [-1, offset, 0] : [-1, 0, 0, offset],
};
+ /** @type {Record>} */
const dropdownPosition = {
dropdown: { top: '100%' },
dropup: { top: 'auto', bottom: '100%' },
- dropstart: { left: 'auto', right: '100%' },
- dropend: { left: '100%', right: 'auto' },
- menuEnd: { right: 0, left: 'auto' },
+ dropstart: RTL ? { left: '100%', right: 'auto' } : { left: 'auto', right: '100%' },
+ dropend: RTL ? { left: 'auto', right: '100%' } : { left: '100%', right: 'auto' },
+ menuEnd: RTL ? { right: 'auto', left: 0 } : { right: 0, left: 'auto' },
};
- // force showing the menu to calculate its size
- hideMenuClass.forEach((c) => addClass(menu, c));
-
- const dropdownRegex = new RegExp(`\\b(${dropdownString}|${dropupString}|${dropstartString}|${dropendString})+`);
// @ts-ignore
- const elementDimensions = { w: element.offsetWidth, h: element.offsetHeight };
- // @ts-ignore
- const menuDimensions = { w: menu.offsetWidth, h: menu.offsetHeight };
- const HTML = document.documentElement;
- const BD = document.body;
- const windowWidth = (HTML.clientWidth || BD.clientWidth);
- const windowHeight = (HTML.clientHeight || BD.clientHeight);
- const targetBCR = element.getBoundingClientRect();
- // dropdownMenuEnd && [ dropdown | dropup ]
- const leftExceed = targetBCR.left + elementDimensions.w - menuDimensions.w < 0;
- // dropstart
- const leftFullExceed = targetBCR.left - menuDimensions.w < 0;
- // !dropdownMenuEnd && [ dropdown | dropup ]
- const rightExceed = targetBCR.left + menuDimensions.w >= windowWidth;
+ const { offsetWidth: menuWidth, offsetHeight: menuHeight } = menu;
+
+ const { clientWidth, clientHeight } = getDocumentElement(element);
+ const {
+ left: targetLeft, top: targetTop,
+ width: targetWidth, height: targetHeight,
+ } = getBoundingClientRect(element);
+
+ // dropstart | dropend
+ const leftFullExceed = targetLeft - menuWidth - offset < 0;
// dropend
- const rightFullExceed = targetBCR.left + menuDimensions.w + elementDimensions.w >= windowWidth;
+ const rightFullExceed = targetLeft + menuWidth + targetWidth + offset >= clientWidth;
// dropstart | dropend
- const bottomExceed = targetBCR.top + menuDimensions.h >= windowHeight;
+ const bottomExceed = targetTop + menuHeight + offset >= clientHeight;
// dropdown
- const bottomFullExceed = targetBCR.top + menuDimensions.h + elementDimensions.h >= windowHeight;
+ const bottomFullExceed = targetTop + menuHeight + targetHeight + offset >= clientHeight;
// dropup
- const topExceed = targetBCR.top - menuDimensions.h < 0;
+ const topExceed = targetTop - menuHeight - offset < 0;
+ // dropdown / dropup
+ const leftExceed = ((!RTL && menuEnd) || (RTL && !menuEnd))
+ && targetLeft + targetWidth - menuWidth < 0;
+ const rightExceed = ((RTL && menuEnd) || (!RTL && !menuEnd))
+ && targetLeft + menuWidth >= clientWidth;
// recompute position
+ // handle RTL as well
if (horizontalClass.includes(positionClass) && leftFullExceed && rightFullExceed) {
positionClass = dropdownString;
}
- if (horizontalClass.includes(positionClass) && bottomExceed) {
- positionClass = dropupString;
- }
- if (positionClass === dropstartString && leftFullExceed && !bottomExceed) {
+ if (positionClass === dropstartString && (!RTL ? leftFullExceed : rightFullExceed)) {
positionClass = dropendString;
}
- if (positionClass === dropendString && rightFullExceed && !bottomExceed) {
+ if (positionClass === dropendString && (RTL ? leftFullExceed : rightFullExceed)) {
positionClass = dropstartString;
}
if (positionClass === dropupString && topExceed && !bottomFullExceed) {
@@ -1853,41 +2118,49 @@
if (positionClass === dropdownString && bottomFullExceed && !topExceed) {
positionClass = dropupString;
}
+ // override position for horizontal classes
+ if (horizontalClass.includes(positionClass) && bottomExceed) {
+ ObjectAssign(dropdownPosition[positionClass], {
+ top: 'auto', bottom: 0,
+ });
+ }
+ // override position for vertical classes
+ if (verticalClass.includes(positionClass) && (leftExceed || rightExceed)) {
+ // don't realign when menu is wider than window
+ // in both RTL and non-RTL readability is KING
+ if (targetLeft + targetWidth + Math.abs(menuWidth - targetWidth) + offset < clientWidth) {
+ ObjectAssign(dropdownPosition[positionClass],
+ leftExceed ? { left: 0, right: 'auto' } : { left: 'auto', right: 0 });
+ }
+ }
- // set spacing
- // @ts-ignore
dropdownMargin = dropdownMargin[positionClass];
// @ts-ignore
menu.style.margin = `${dropdownMargin.map((x) => (x ? `${x}px` : x)).join(' ')}`;
- // @ts-ignore
- Object.keys(dropdownPosition[positionClass]).forEach((position) => {
- // @ts-ignore
- menu.style[position] = dropdownPosition[positionClass][position];
- });
-
- // update dropdown position class
- // @ts-ignore
- if (!hasClass(parent, positionClass)) {
- // @ts-ignore
- parent.className = parent.className.replace(dropdownRegex, positionClass);
- }
- // update dropdown / dropup to handle parent btn-group element
- // as well as the dropdown-menu-end utility class
- if (verticalClass.includes(positionClass)) {
- if (!menuEnd && rightExceed) addClass(menu, dropdownMenuEndClass);
- else if (menuEnd && leftExceed) removeClass(menu, dropdownMenuEndClass);
+ setElementStyle(menu, dropdownPosition[positionClass]);
- if (hasClass(menu, dropdownMenuEndClass)) {
- Object.keys(dropdownPosition.menuEnd).forEach((p) => {
- // @ts-ignore
- menu.style[p] = dropdownPosition.menuEnd[p];
- });
- }
+ // update dropdown-menu-end
+ if (hasClass(menu, dropdownMenuEndClass)) {
+ setElementStyle(menu, dropdownPosition.menuEnd);
}
+ }
- // remove util classes from the menu, we have its size
- hideMenuClass.forEach((c) => removeClass(menu, c));
+ /**
+ * Returns an `Array` of focusable items in the given dropdown-menu.
+ * @param {HTMLElement | Element} menu
+ * @returns {(HTMLElement | Element)[]}
+ */
+ function getMenuItems(menu) {
+ // @ts-ignore
+ return [...menu.children].map((c) => {
+ if (c && menuFocusTags.includes(c.tagName)) return c;
+ const { firstElementChild } = c;
+ if (firstElementChild && menuFocusTags.includes(firstElementChild.tagName)) {
+ return firstElementChild;
+ }
+ return null;
+ }).filter((c) => c);
}
/**
@@ -1897,23 +2170,20 @@
* @param {Dropdown} self the `Dropdown` instance
*/
function toggleDropdownDismiss(self) {
- // @ts-ignore
- const action = self.open ? addEventListener : removeEventListener;
+ const { element } = self;
+ const action = self.open ? on : off;
+ const doc = getDocument(element);
- // @ts-ignore
- document[action]('click', dropdownDismissHandler);
- // @ts-ignore
- document[action]('focus', dropdownDismissHandler);
- // @ts-ignore
- document[action]('keydown', dropdownPreventScroll);
- // @ts-ignore
- document[action]('keyup', dropdownKeyHandler);
+ action(doc, mouseclickEvent, dropdownDismissHandler);
+ action(doc, focusEvent, dropdownDismissHandler);
+ action(doc, keydownEvent, dropdownPreventScroll);
+ action(doc, keyupEvent, dropdownKeyHandler);
if (self.options.display === 'dynamic') {
- // @ts-ignore
- window[action]('scroll', dropdownLayoutHandler, passiveHandler);
- // @ts-ignore
- window[action]('resize', dropdownLayoutHandler, passiveHandler);
+ [scrollEvent, resizeEvent].forEach((ev) => {
+ // @ts-ignore
+ action(getWindow(element), ev, dropdownLayoutHandler, passiveHandler);
+ });
}
}
@@ -1924,25 +2194,25 @@
* @param {boolean=} add when `true`, it will add the event listener
*/
function toggleDropdownHandler(self, add) {
- const action = add ? addEventListener : removeEventListener;
- // @ts-ignore
- self.element[action]('click', dropdownClickHandler);
+ const action = add ? on : off;
+ action(self.element, mouseclickEvent, dropdownClickHandler);
}
/**
* Returns the currently open `.dropdown` element.
*
- * @returns {Element?} the query result
+ * @param {(Document | HTMLElement | Element | globalThis)=} element target
+ * @returns {HTMLElement?} the query result
*/
- function getCurrentOpenDropdown() {
+ function getCurrentOpenDropdown(element) {
const currentParent = [...dropdownMenuClasses, 'btn-group', 'input-group']
- .map((c) => document.getElementsByClassName(`${c} ${showClass}`))
+ .map((c) => getElementsByClassName(`${c} ${showClass}`), getDocument(element))
.find((x) => x.length);
if (currentParent && currentParent.length) {
- // @ts-ignore
- return Array.from(currentParent[0].children)
- .find((x) => x.hasAttribute(dataBsToggle));
+ // @ts-ignore -- HTMLElement is also Element
+ return [...currentParent[0].children]
+ .find((x) => hasAttribute(x, dataBsToggle));
}
return null;
}
@@ -1952,33 +2222,35 @@
/**
* Handles the `click` event for the `Dropdown` instance.
*
- * @param {Event} e event object
+ * @param {MouseEvent} e event object
+ * @this {Document}
*/
function dropdownDismissHandler(e) {
const { target, type } = e;
// @ts-ignore
- if (!target.closest) return; // some weird FF bug #409
+ if (!target || !target.closest) return; // some weird FF bug #409
- const element = getCurrentOpenDropdown();
+ // @ts-ignore
+ const element = getCurrentOpenDropdown(target);
if (!element) return;
const self = getDropdownInstance(element);
- const parent = element.parentNode;
- // @ts-ignore
- const menu = self && self.menu;
+ if (!self) return;
+
+ const { parentElement, menu } = self;
// @ts-ignore
- const hasData = target.closest(dropdownSelector) !== null;
+ const hasData = closest(target, dropdownSelector) !== null;
// @ts-ignore
- const isForm = parent && parent.contains(target)
+ const isForm = parentElement && parentElement.contains(target)
// @ts-ignore
- && (target.tagName === 'form' || target.closest('form') !== null);
+ && (target.tagName === 'form' || closest(target, 'form') !== null);
// @ts-ignore
- if (type === 'click' && isEmptyAnchor(target)) {
+ if (type === mouseclickEvent && isEmptyAnchor(target)) {
e.preventDefault();
}
- if (type === 'focus' // @ts-ignore
+ if (type === focusEvent // @ts-ignore
&& (target === element || target === menu || menu.contains(target))) {
return;
}
@@ -1990,87 +2262,78 @@
/**
* Handles `click` event listener for `Dropdown`.
- * @this {Element}
- * @param {Event} e event object
+ * @this {HTMLElement | Element}
+ * @param {MouseEvent} e event object
*/
function dropdownClickHandler(e) {
const element = this;
+ const { target } = e;
const self = getDropdownInstance(element);
- self.toggle();
- // @ts-ignore
- if (isEmptyAnchor(e.target)) e.preventDefault();
+ if (self) {
+ self.toggle();
+ if (target && isEmptyAnchor(target)) e.preventDefault();
+ }
}
/**
* Prevents scroll when dropdown-menu is visible.
- * @param {Event} e event object
+ * @param {KeyboardEvent} e event object
*/
function dropdownPreventScroll(e) {
- // @ts-ignore
- if (e.which === 38 || e.which === 40) e.preventDefault();
+ if ([keyArrowDown, keyArrowUp].includes(e.code)) e.preventDefault();
}
/**
* Handles keyboard `keydown` events for `Dropdown`.
- * @param {{which: number}} e keyboard key
+ * @param {KeyboardEvent} e keyboard key
+ * @this {Document}
*/
- function dropdownKeyHandler({ which }) {
- const element = getCurrentOpenDropdown();
- // @ts-ignore
- const self = getDropdownInstance(element);
- // @ts-ignore
- const { menu, menuItems, open } = self;
- const activeItem = document.activeElement;
- const isSameElement = activeItem === element;
- const isInsideMenu = menu.contains(activeItem);
- // @ts-ignore
- const isMenuItem = activeItem.parentNode === menu || activeItem.parentNode.parentNode === menu;
-
- // @ts-ignore
- let idx = menuItems.indexOf(activeItem);
-
- if (isMenuItem) { // navigate up | down
- if (isSameElement) {
+ function dropdownKeyHandler(e) {
+ const { code } = e;
+ const element = getCurrentOpenDropdown(this);
+ const self = element && getDropdownInstance(element);
+ const activeItem = element && getDocument(element).activeElement;
+ if (!self || !activeItem) return;
+ const { menu, open } = self;
+ const menuItems = getMenuItems(menu);
+
+ // arrow up & down
+ if (menuItems && menuItems.length) {
+ let idx = menuItems.indexOf(activeItem);
+ if (activeItem === element) {
idx = 0;
- } else if (which === 38) {
+ } else if (code === keyArrowUp) {
idx = idx > 1 ? idx - 1 : 0;
- } else if (which === 40) {
+ } else if (code === keyArrowDown) {
idx = idx < menuItems.length - 1 ? idx + 1 : idx;
}
-
- if (menuItems[idx]) setFocus(menuItems[idx]);
+ if (menuItems[idx]) focus(menuItems[idx]);
}
- if (((menuItems.length && isMenuItem) // menu has items
- || (!menuItems.length && (isInsideMenu || isSameElement)) // menu might be a form
- || !isInsideMenu) // or the focused element is not in the menu at all
- && open && which === 27 // menu must be open
- ) {
+ if (keyEscape === code && open) {
self.toggle();
+ focus(element);
}
}
/**
+ * @this {globalThis}
* @returns {void}
*/
function dropdownLayoutHandler() {
- const element = getCurrentOpenDropdown();
+ const element = getCurrentOpenDropdown(this);
const self = element && getDropdownInstance(element);
- // @ts-ignore
- if (self && self.open) styleDropdown(self, true);
+ if (self && self.open) styleDropdown(self);
}
// DROPDOWN DEFINITION
// ===================
- /**
- * Returns a new Dropdown instance.
- * @implements {BaseComponent}
- */
+ /** Returns a new Dropdown instance. */
class Dropdown extends BaseComponent {
/**
- * @param {Element | string} target Element or string selector
+ * @param {HTMLElement | Element | string} target Element or string selector
* @param {BSN.Options.Dropdown=} config the instance options
*/
constructor(target, config) {
@@ -2080,32 +2343,18 @@
// initialization element
const { element } = self;
+ const { parentElement } = element;
// set targets
- const { parentElement } = element;
- /** @private @type {Element} */
+ /** @type {(Element | HTMLElement)} */
// @ts-ignore
- self.menu = queryElement(`.${dropdownMenuClass}`, parentElement);
- const { menu } = self;
-
- /** @private @type {string[]} */
+ self.parentElement = parentElement;
+ /** @type {(Element | HTMLElement)} */
// @ts-ignore
- self.originalClass = Array.from(parentElement.classList);
-
- // set original position
- /** @private @type {boolean} */
- self.menuEnd = hasClass(menu, dropdownMenuEndClass);
-
- /** @private @type {Element[]} */
- self.menuItems = [];
-
- Array.from(menu.children).forEach((child) => {
- if (child.children.length && (child.children[0].tagName === 'A')) self.menuItems.push(child.children[0]);
- if (child.tagName === 'A') self.menuItems.push(child);
- });
+ self.menu = querySelector(`.${dropdownMenuClass}`, parentElement);
// set initial state to closed
- /** @private @type {boolean} */
+ /** @type {boolean} */
self.open = false;
// add event listener
@@ -2138,99 +2387,201 @@
/** Shows the dropdown menu to the user. */
show() {
const self = this;
- const currentParent = queryElement(dropdownMenuClasses.concat('btn-group', 'input-group').map((c) => `.${c}.${showClass}`).join(','));
- const currentElement = currentParent && queryElement(dropdownSelector, currentParent);
-
- if (currentElement) getDropdownInstance(currentElement).hide();
+ const {
+ element, open, menu, parentElement,
+ } = self;
- const { element, menu, open } = self;
- const { parentElement } = element;
+ const currentElement = getCurrentOpenDropdown(element);
+ const currentInstance = currentElement && getDropdownInstance(currentElement);
+ if (currentInstance) currentInstance.hide();
// dispatch
[showDropdownEvent, shownDropdownEvent].forEach((e) => { e.relatedTarget = element; });
-
- // @ts-ignore
- parentElement.dispatchEvent(showDropdownEvent);
+ dispatchEvent(parentElement, showDropdownEvent);
if (showDropdownEvent.defaultPrevented) return;
- // change menu position
- styleDropdown(self, true);
-
addClass(menu, showClass);
- // @ts-ignore
addClass(parentElement, showClass);
+ setAttribute(element, ariaExpanded, 'true');
+
+ // change menu position
+ styleDropdown(self);
- element.setAttribute(ariaExpanded, 'true');
self.open = !open;
setTimeout(() => {
- setFocus(element); // focus the element
+ focus(element); // focus the element
toggleDropdownDismiss(self);
- // @ts-ignore
- parentElement.dispatchEvent(shownDropdownEvent);
+ dispatchEvent(parentElement, shownDropdownEvent);
}, 1);
}
/** Hides the dropdown menu from the user. */
hide() {
const self = this;
- const { element, menu, open } = self;
- const { parentElement } = element;
- // @ts-ignore
+ const {
+ element, open, menu, parentElement,
+ } = self;
[hideDropdownEvent, hiddenDropdownEvent].forEach((e) => { e.relatedTarget = element; });
- // @ts-ignore
- parentElement.dispatchEvent(hideDropdownEvent);
+ dispatchEvent(parentElement, hideDropdownEvent);
if (hideDropdownEvent.defaultPrevented) return;
removeClass(menu, showClass);
- // @ts-ignore
removeClass(parentElement, showClass);
+ setAttribute(element, ariaExpanded, 'false');
- // revert to original position
- styleDropdown(self);
-
- element.setAttribute(ariaExpanded, 'false');
self.open = !open;
// only re-attach handler if the instance is not disposed
setTimeout(() => toggleDropdownDismiss(self), 1);
- // @ts-ignore
- parentElement.dispatchEvent(hiddenDropdownEvent);
+ dispatchEvent(parentElement, hiddenDropdownEvent);
}
/** Removes the `Dropdown` component from the target element. */
dispose() {
const self = this;
- const { element } = self;
+ const { parentElement } = self;
- // @ts-ignore
- if (hasClass(element.parentNode, showClass) && self.open) self.hide();
+ if (hasClass(parentElement, showClass) && self.open) self.hide();
toggleDropdownHandler(self);
- super.dispose();
+ super.dispose();
+ }
+ }
+
+ ObjectAssign(Dropdown, {
+ selector: dropdownSelector,
+ init: dropdownInitCallback,
+ getInstance: getDropdownInstance,
+ });
+
+ /**
+ * Shortcut for `HTMLElement.removeAttribute()` method.
+ * @param {HTMLElement | Element} element target element
+ * @param {string} attribute attribute name
+ */
+ const removeAttribute = (element, attribute) => element.removeAttribute(attribute);
+
+ /**
+ * Returns the `document.body` or the `` element.
+ *
+ * @param {(Node | HTMLElement | Element | globalThis)=} node
+ * @returns {HTMLElement | HTMLBodyElement}
+ */
+ function getDocumentBody(node) {
+ return getDocument(node).body;
+ }
+
+ /**
+ * A global namespace for aria-hidden.
+ * @type {string}
+ */
+ const ariaHidden = 'aria-hidden';
+
+ /**
+ * A global namespace for aria-modal.
+ * @type {string}
+ */
+ const ariaModal = 'aria-modal';
+
+ /**
+ * Check if target is a `ShadowRoot`.
+ *
+ * @param {any} element target
+ * @returns {boolean} the query result
+ */
+ const isShadowRoot = (element) => {
+ const OwnElement = getWindow(element).ShadowRoot;
+ return element instanceof OwnElement || element instanceof ShadowRoot;
+ };
+
+ /**
+ * Returns the `parentNode` also going through `ShadowRoot`.
+ * @see https://github.com/floating-ui/floating-ui
+ *
+ * @param {Node | HTMLElement | Element} node the target node
+ * @returns {Node | HTMLElement | Element} the apropriate parent node
+ */
+ function getParentNode(node) {
+ if (node.nodeName === 'HTML') {
+ return node;
+ }
+
+ // this is a quicker (but less type safe) way to save quite some bytes from the bundle
+ return (
+ // @ts-ignore
+ node.assignedSlot // step into the shadow DOM of the parent of a slotted node
+ || node.parentNode // @ts-ignore DOM Element detected
+ || (isShadowRoot(node) ? node.host : null) // ShadowRoot detected
+ || getDocumentElement(node) // fallback
+ );
+ }
+
+ /**
+ * Check if a target element is a ``, `` or ` | `.
+ * @param {any} element the target element
+ * @returns {boolean} the query result
+ */
+ const isTableElement = (element) => ['TABLE', 'TD', 'TH'].includes(element.tagName);
+
+ /**
+ * Returns an `HTMLElement` to be used as default value for *options.container*
+ * for `Tooltip` / `Popover` components.
+ *
+ * When `getOffset` is *true*, it returns the `offsetParent` for tooltip/popover
+ * offsets computation similar to **floating-ui**.
+ * @see https://github.com/floating-ui/floating-ui
+ *
+ * @param {HTMLElement | Element} element the target
+ * @param {boolean=} getOffset when *true* it will return an `offsetParent`
+ * @returns {HTMLElement | HTMLBodyElement | Window} the query result
+ */
+ function getElementContainer(element, getOffset) {
+ const majorBlockTags = ['HTML', 'BODY'];
+
+ if (getOffset) {
+ /** @type {any} */
+ let { offsetParent } = element;
+
+ while (offsetParent && isTableElement(offsetParent)
+ && getElementStyle(offsetParent, 'position') === 'static'
+ && offsetParent instanceof HTMLElement
+ && getElementStyle(offsetParent, 'position') !== 'fixed') {
+ offsetParent = offsetParent.offsetParent;
+ }
+
+ if (!offsetParent || (offsetParent
+ && (majorBlockTags.includes(offsetParent.tagName)
+ && getElementStyle(offsetParent, 'position') === 'static'))) {
+ offsetParent = getWindow(element);
+ }
+ return offsetParent;
}
- }
- Object.assign(Dropdown, {
- selector: dropdownSelector,
- init: dropdownInitCallback,
- getInstance: getDropdownInstance,
- });
+ /** @type {(HTMLElement)[]} */
+ const containers = [];
+ /** @type {any} */
+ let { parentNode } = element;
- /**
- * A global namespace for aria-hidden.
- * @type {string}
- */
- const ariaHidden = 'aria-hidden';
+ while (parentNode && !majorBlockTags.includes(parentNode.nodeName)) {
+ parentNode = getParentNode(parentNode);
+ if (!(isShadowRoot(parentNode) || !!parentNode.shadowRoot
+ || isTableElement(parentNode))) {
+ containers.push(parentNode);
+ }
+ }
- /**
- * A global namespace for aria-modal.
- * @type {string}
- */
- const ariaModal = 'aria-modal';
+ return containers.find((c, i) => {
+ if (getElementStyle(c, 'position') !== 'relative'
+ && containers.slice(i + 1).every((r) => getElementStyle(r, 'position') === 'static')) {
+ return c;
+ }
+ return null;
+ }) || getDocumentBody(element);
+ }
/**
* Global namespace for components `fixed-top` class.
@@ -2247,28 +2598,40 @@
*/
const stickyTopClass = 'sticky-top';
- const fixedItems = [
- ...document.getElementsByClassName(fixedTopClass),
- ...document.getElementsByClassName(fixedBottomClass),
- ...document.getElementsByClassName(stickyTopClass),
- ...document.getElementsByClassName('is-fixed'),
+ /**
+ * Global namespace for components `position-sticky` class.
+ */
+ const positionStickyClass = 'position-sticky';
+
+ /** @param {(HTMLElement | Element | Document)=} parent */
+ const getFixedItems = (parent) => [
+ ...getElementsByClassName(fixedTopClass, parent),
+ ...getElementsByClassName(fixedBottomClass, parent),
+ ...getElementsByClassName(stickyTopClass, parent),
+ ...getElementsByClassName(positionStickyClass, parent),
+ ...getElementsByClassName('is-fixed', parent),
];
/**
* Removes *padding* and *overflow* from the ``
* and all spacing from fixed items.
+ * @param {(HTMLElement | Element)=} element the target modal/offcanvas
*/
- function resetScrollbar() {
- const bd = document.body;
- bd.style.paddingRight = '';
- bd.style.overflow = '';
+ function resetScrollbar(element) {
+ const bd = getDocumentBody(element);
+ setElementStyle(bd, {
+ paddingRight: '',
+ overflow: '',
+ });
+
+ const fixedItems = getFixedItems(bd);
if (fixedItems.length) {
fixedItems.forEach((fixed) => {
- // @ts-ignore
- fixed.style.paddingRight = '';
- // @ts-ignore
- fixed.style.marginRight = '';
+ setElementStyle(fixed, {
+ paddingRight: '',
+ marginRight: '',
+ });
});
}
}
@@ -2276,39 +2639,42 @@
/**
* Returns the scrollbar width if the body does overflow
* the window.
+ * @param {(HTMLElement | Element)=} element
* @returns {number} the value
*/
- function measureScrollbar() {
- const { clientWidth } = document.documentElement;
- return Math.abs(window.innerWidth - clientWidth);
+ function measureScrollbar(element) {
+ const { clientWidth } = getDocumentElement(element);
+ const { innerWidth } = getWindow(element);
+ return Math.abs(innerWidth - clientWidth);
}
/**
* Sets the `` and fixed items style when modal / offcanvas
* is shown to the user.
*
- * @param {number} scrollbarWidth the previously measured scrollbar width
- * @param {boolean | number} overflow body does overflow or not
+ * @param {HTMLElement | Element} element the target modal/offcanvas
+ * @param {boolean=} overflow body does overflow or not
*/
- function setScrollbar(scrollbarWidth, overflow) {
- const bd = document.body;
- const bdStyle = getComputedStyle(bd);
- const bodyPad = parseInt(bdStyle.paddingRight, 10);
- const isOpen = bdStyle.overflow === 'hidden';
- const sbWidth = isOpen && bodyPad ? 0 : scrollbarWidth;
+ function setScrollbar(element, overflow) {
+ const bd = getDocumentBody(element);
+ const bodyPad = parseInt(getElementStyle(bd, 'paddingRight'), 10);
+ const isOpen = getElementStyle(bd, 'overflow') === 'hidden';
+ const sbWidth = isOpen && bodyPad ? 0 : measureScrollbar(element);
+ const fixedItems = getFixedItems(bd);
if (overflow) {
- bd.style.overflow = 'hidden';
- bd.style.paddingRight = `${bodyPad + sbWidth}px`;
+ setElementStyle(bd, {
+ overflow: 'hidden',
+ paddingRight: `${bodyPad + sbWidth}px`,
+ });
if (fixedItems.length) {
fixedItems.forEach((fixed) => {
- const isSticky = hasClass(fixed, stickyTopClass);
- const itemPadValue = getComputedStyle(fixed).paddingRight;
+ const itemPadValue = getElementStyle(fixed, 'paddingRight');
// @ts-ignore
fixed.style.paddingRight = `${parseInt(itemPadValue, 10) + sbWidth}px`;
- if (isSticky) {
- const itemMValue = getComputedStyle(fixed).marginRight;
+ if ([stickyTopClass, positionStickyClass].some((c) => hasClass(fixed, c))) {
+ const itemMValue = getElementStyle(fixed, 'marginRight');
// @ts-ignore
fixed.style.marginRight = `${parseInt(itemMValue, 10) - sbWidth}px`;
}
@@ -2321,14 +2687,17 @@
const offcanvasBackdropClass = 'offcanvas-backdrop';
const modalActiveSelector = `.modal.${showClass}`;
const offcanvasActiveSelector = `.offcanvas.${showClass}`;
- const overlay = document.createElement('div');
+
+ // any document would suffice
+ const overlay = getDocument().createElement('div');
/**
* Returns the current active modal / offcancas element.
- * @returns {Element?} the requested element
+ * @param {(HTMLElement | Element)=} element the context element
+ * @returns {(HTMLElement | Element)?} the requested element
*/
- function getCurrentOpen() {
- return queryElement(`${modalActiveSelector},${offcanvasActiveSelector}`);
+ function getCurrentOpen(element) {
+ return querySelector(`${modalActiveSelector},${offcanvasActiveSelector}`, getDocument(element));
}
/**
@@ -2345,12 +2714,13 @@
/**
* Append the overlay to DOM.
+ * @param {HTMLElement | Element} container
* @param {boolean} hasFade
* @param {boolean=} isModal
*/
- function appendOverlay(hasFade, isModal) {
+ function appendOverlay(container, hasFade, isModal) {
toggleOverlayType(isModal);
- document.body.append(overlay);
+ container.append(overlay);
if (hasFade) addClass(overlay, fadeClass);
}
@@ -2371,17 +2741,23 @@
/**
* Removes the overlay from DOM.
+ * @param {(HTMLElement | Element)=} element
*/
- function removeOverlay() {
- if (!getCurrentOpen()) {
+ function removeOverlay(element) {
+ if (!getCurrentOpen(element)) {
removeClass(overlay, fadeClass);
overlay.remove();
- resetScrollbar();
+ resetScrollbar(element);
}
}
+ /**
+ * @param {HTMLElement | Element} element target
+ * @returns {boolean}
+ */
function isVisible(element) {
- return getComputedStyle(element).visibility !== 'hidden'
+ return element && getElementStyle(element, 'visibility') !== 'hidden'
+ // @ts-ignore
&& element.offsetParent !== null;
}
@@ -2432,19 +2808,18 @@
* @param {Modal} self the `Modal` instance
*/
function setModalScrollbar(self) {
- // @ts-ignore
- const { element, scrollbarWidth } = self;
- const bd = document.body;
- const html = document.documentElement;
- const bodyOverflow = html.clientHeight !== html.scrollHeight
- || bd.clientHeight !== bd.scrollHeight;
- const modalOverflow = element.clientHeight !== element.scrollHeight;
+ const { element } = self;
+ const scrollbarWidth = measureScrollbar(element);
+ const { clientHeight, scrollHeight } = getDocumentElement(element);
+ const { clientHeight: modalHeight, scrollHeight: modalScrollHeight } = element;
+ const modalOverflow = modalHeight !== modalScrollHeight;
if (!modalOverflow && scrollbarWidth) {
+ const pad = isRTL(element) ? 'paddingLeft' : 'paddingRight';
// @ts-ignore
- element.style.paddingRight = `${scrollbarWidth}px`;
+ element.style[pad] = `${scrollbarWidth}px`;
}
- setScrollbar(scrollbarWidth, (modalOverflow || bodyOverflow));
+ setScrollbar(element, (modalOverflow || clientHeight !== scrollHeight));
}
/**
@@ -2454,13 +2829,12 @@
* @param {boolean=} add when `true`, event listeners are added
*/
function toggleModalDismiss(self, add) {
- const action = add ? addEventListener : removeEventListener;
- // @ts-ignore
- window[action]('resize', self.update, passiveHandler);
- // @ts-ignore
- self.element[action]('click', modalDismissHandler);
+ const action = add ? on : off;
+ const { element } = self;
+ action(element, mouseclickEvent, modalDismissHandler);
// @ts-ignore
- document[action]('keydown', modalKeyHandler);
+ action(getWindow(element), resizeEvent, self.update, passiveHandler);
+ action(getDocument(element), keydownEvent, modalKeyHandler);
}
/**
@@ -2469,13 +2843,11 @@
* @param {boolean=} add when `true`, event listener is added
*/
function toggleModalHandler(self, add) {
- const action = add ? addEventListener : removeEventListener;
- // @ts-ignore
+ const action = add ? on : off;
const { triggers } = self;
if (triggers.length) {
- // @ts-ignore
- triggers.forEach((btn) => btn[action]('click', modalClickHandler));
+ triggers.forEach((btn) => action(btn, mouseclickEvent, modalClickHandler));
}
}
@@ -2484,17 +2856,14 @@
* @param {Modal} self the `Modal` instance
*/
function afterModalHide(self) {
+ const { triggers, element } = self;
+ removeOverlay(element);
// @ts-ignore
- const { triggers } = self;
- removeOverlay();
- // @ts-ignore
- self.element.style.paddingRight = '';
- // @ts-ignore
- self.isAnimating = false;
+ element.style.paddingRight = '';
if (triggers.length) {
const visibleTrigger = triggers.find((x) => isVisible(x));
- if (visibleTrigger) setFocus(visibleTrigger);
+ if (visibleTrigger) focus(visibleTrigger);
}
}
@@ -2503,17 +2872,12 @@
* @param {Modal} self the `Modal` instance
*/
function afterModalShow(self) {
- // @ts-ignore
const { element, relatedTarget } = self;
- setFocus(element);
- // @ts-ignore
- self.isAnimating = false;
-
+ focus(element);
toggleModalDismiss(self, true);
- // @ts-ignore
shownModalEvent.relatedTarget = relatedTarget;
- element.dispatchEvent(shownModalEvent);
+ dispatchEvent(element, shownModalEvent);
}
/**
@@ -2521,19 +2885,18 @@
* @param {Modal} self the `Modal` instance
*/
function beforeModalShow(self) {
- // @ts-ignore
const { element, hasFade } = self;
// @ts-ignore
element.style.display = 'block';
setModalScrollbar(self);
- if (!getCurrentOpen()) {
- document.body.style.overflow = 'hidden';
+ if (!getCurrentOpen(element)) {
+ getDocumentBody(element).style.overflow = 'hidden';
}
addClass(element, showClass);
- element.removeAttribute(ariaHidden);
- element.setAttribute(ariaModal, 'true');
+ removeAttribute(element, ariaHidden);
+ setAttribute(element, ariaModal, 'true');
if (hasFade) emulateTransitionEnd(element, () => afterModalShow(self));
else afterModalShow(self);
@@ -2546,7 +2909,6 @@
*/
function beforeModalHide(self, force) {
const {
- // @ts-ignore
element, options, relatedTarget, hasFade,
} = self;
@@ -2556,7 +2918,7 @@
// force can also be the transitionEvent object, we wanna make sure it's not
// call is not forced and overlay is visible
if (options.backdrop && !force && hasFade && hasClass(overlay, showClass)
- && !getCurrentOpen()) { // AND no modal is visible
+ && !getCurrentOpen(element)) { // AND no modal is visible
hideOverlay();
emulateTransitionEnd(overlay, () => afterModalHide(self));
} else {
@@ -2566,31 +2928,27 @@
toggleModalDismiss(self);
hiddenModalEvent.relatedTarget = relatedTarget;
- element.dispatchEvent(hiddenModalEvent);
+ dispatchEvent(element, hiddenModalEvent);
}
// MODAL EVENT HANDLERS
// ====================
/**
* Handles the `click` event listener for modal.
- * @param {Event} e the `Event` object
+ * @param {MouseEvent} e the `Event` object
+ * @this {HTMLElement | Element}
*/
function modalClickHandler(e) {
const { target } = e;
- // @ts-ignore
- const trigger = target.closest(modalToggleSelector);
- const element = getTargetElement(trigger);
- const self = element && getModalInstance(element);
- if (!self) return;
- if (trigger.tagName === 'A') e.preventDefault();
+ const trigger = target && closest(this, modalToggleSelector);
+ const element = trigger && getTargetElement(trigger);
+ const self = element && getModalInstance(element);
- // @ts-ignore
- if (self.isAnimating) return;
+ if (!self) return;
- // @ts-ignore
+ if (trigger && trigger.tagName === 'A') e.preventDefault();
self.relatedTarget = trigger;
-
self.toggle();
}
@@ -2598,19 +2956,15 @@
* Handles the `keydown` event listener for modal
* to hide the modal when user type the `ESC` key.
*
- * @param {{which: number}} e the `Event` object
+ * @param {KeyboardEvent} e the `Event` object
*/
- function modalKeyHandler({ which }) {
- const element = queryElement(modalActiveSelector);
- // @ts-ignore
- const self = getModalInstance(element);
- // @ts-ignore
- const { options, isAnimating } = self;
- if (!isAnimating // modal has no animations running
- && options.keyboard && which === 27 // the keyboard option is enabled and the key is 27
- // @ts-ignore
+ function modalKeyHandler({ code }) {
+ const element = querySelector(modalActiveSelector);
+ const self = element && getModalInstance(element);
+ if (!self) return;
+ const { options } = self;
+ if (options.keyboard && code === keyEscape // the keyboard option is enabled and the key is 27
&& hasClass(element, showClass)) { // the modal is not visible
- // @ts-ignore
self.relatedTarget = null;
self.hide();
}
@@ -2619,35 +2973,34 @@
/**
* Handles the `click` event listeners that hide the modal.
*
- * @this {Element}
- * @param {Event} e the `Event` object
+ * @this {HTMLElement | Element}
+ * @param {MouseEvent} e the `Event` object
*/
function modalDismissHandler(e) {
const element = this;
const self = getModalInstance(element);
- // @ts-ignore
- if (self.isAnimating) return;
+ // this timer is needed
+ if (!self || Timer.get(element)) return;
- // @ts-ignore
const { options, isStatic, modalDialog } = self;
const { backdrop } = options;
const { target } = e;
+
// @ts-ignore
- const selectedText = document.getSelection().toString().length;
+ const selectedText = getDocument(element).getSelection().toString().length;
// @ts-ignore
const targetInsideDialog = modalDialog.contains(target);
// @ts-ignore
- const dismiss = target.closest(modalDismissSelector);
+ const dismiss = target && closest(target, modalDismissSelector);
if (isStatic && !targetInsideDialog) {
- addClass(element, modalStaticClass);
- // @ts-ignore
- self.isAnimating = true;
- // @ts-ignore
- emulateTransitionEnd(modalDialog, () => staticTransitionEnd(self));
+ const dismissCallback = () => {
+ addClass(element, modalStaticClass);
+ emulateTransitionEnd(modalDialog, () => staticTransitionEnd(self));
+ };
+ Timer.set(element, dismissCallback, 17);
} else if (dismiss || (!selectedText && !isStatic && !targetInsideDialog && backdrop)) {
- // @ts-ignore
self.relatedTarget = dismiss || null;
self.hide();
e.preventDefault();
@@ -2660,12 +3013,11 @@
* @param {Modal} self the `Modal` instance
*/
function staticTransitionEnd(self) {
- // @ts-ignore
- const duration = getElementTransitionDuration(self.modalDialog) + 17;
- removeClass(self.element, modalStaticClass);
+ const { element, modalDialog } = self;
+ const duration = getElementTransitionDuration(modalDialog) + 17;
+ removeClass(element, modalStaticClass);
// user must wait for zoom out transition
- // @ts-ignore
- setTimeout(() => { self.isAnimating = false; }, duration);
+ Timer.set(element, () => Timer.clear(element), duration);
}
// MODAL DEFINITION
@@ -2673,7 +3025,7 @@
/** Returns a new `Modal` instance. */
class Modal extends BaseComponent {
/**
- * @param {Element | string} target usually the `.modal` element
+ * @param {HTMLElement | Element | string} target usually the `.modal` element
* @param {BSN.Options.Modal=} config instance options
*/
constructor(target, config) {
@@ -2686,25 +3038,25 @@
const { element } = self;
// the modal-dialog
- /** @private @type {Element?} */
- self.modalDialog = queryElement(`.${modalString}-dialog`, element);
+ /** @type {(HTMLElement | Element)} */
+ // @ts-ignore
+ self.modalDialog = querySelector(`.${modalString}-dialog`, element);
// modal can have multiple triggering elements
- /** @private @type {Element[]} */
- self.triggers = Array.from(document.querySelectorAll(modalToggleSelector))
+ /** @type {(HTMLElement | Element)[]} */
+ self.triggers = [...querySelectorAll(modalToggleSelector)]
.filter((btn) => getTargetElement(btn) === element);
// additional internals
- /** @private @type {boolean} */
+ /** @type {boolean} */
self.isStatic = self.options.backdrop === 'static';
- /** @private @type {boolean} */
+ /** @type {boolean} */
self.hasFade = hasClass(element, fadeClass);
- /** @private @type {boolean} */
- self.isAnimating = false;
- /** @private @type {number} */
- self.scrollbarWidth = measureScrollbar();
- /** @private @type {Element?} */
+ /** @type {(HTMLElement | Element)?} */
self.relatedTarget = null;
+ /** @type {HTMLBodyElement | HTMLElement | Element} */
+ // @ts-ignore
+ self.container = getElementContainer(element);
// attach event listeners
toggleModalHandler(self, true);
@@ -2739,31 +3091,28 @@
show() {
const self = this;
const {
- element, options, isAnimating, hasFade, relatedTarget,
+ element, options, hasFade, relatedTarget, container,
} = self;
const { backdrop } = options;
let overlayDelay = 0;
- if (hasClass(element, showClass) && !isAnimating) return;
+ if (hasClass(element, showClass)) return;
- // @ts-ignore
showModalEvent.relatedTarget = relatedTarget || null;
- element.dispatchEvent(showModalEvent);
+ dispatchEvent(element, showModalEvent);
if (showModalEvent.defaultPrevented) return;
// we elegantly hide any opened modal/offcanvas
- const currentOpen = getCurrentOpen();
+ const currentOpen = getCurrentOpen(element);
if (currentOpen && currentOpen !== element) {
const this1 = getModalInstance(currentOpen);
const that1 = this1 || getInstance(currentOpen, 'Offcanvas');
that1.hide();
}
- self.isAnimating = true;
-
if (backdrop) {
if (!currentOpen && !hasClass(overlay, showClass)) {
- appendOverlay(hasFade, true);
+ appendOverlay(container, hasFade, true);
} else {
toggleOverlayType(true);
}
@@ -2786,19 +3135,17 @@
hide(force) {
const self = this;
const {
- element, isAnimating, hasFade, relatedTarget,
+ element, hasFade, relatedTarget,
} = self;
- if (!hasClass(element, showClass) && !isAnimating) return;
- // @ts-ignore
+ if (!hasClass(element, showClass)) return;
+
hideModalEvent.relatedTarget = relatedTarget || null;
- element.dispatchEvent(hideModalEvent);
+ dispatchEvent(element, hideModalEvent);
if (hideModalEvent.defaultPrevented) return;
-
- self.isAnimating = true;
removeClass(element, showClass);
- element.setAttribute(ariaHidden, 'true');
- element.removeAttribute(ariaModal);
+ setAttribute(element, ariaHidden, 'true');
+ removeAttribute(element, ariaModal);
if (hasFade && force !== false) {
emulateTransitionEnd(element, () => beforeModalHide(self));
@@ -2825,7 +3172,7 @@
}
}
- Object.assign(Modal, {
+ ObjectAssign(Modal, {
selector: modalSelector,
init: modalInitCallback,
getInstance: getModalInstance,
@@ -2879,37 +3226,33 @@
* @param {Offcanvas} self the `Offcanvas` instance
*/
function setOffCanvasScrollbar(self) {
- const bd = document.body;
- const html = document.documentElement;
- const bodyOverflow = html.clientHeight !== html.scrollHeight
- || bd.clientHeight !== bd.scrollHeight;
- // @ts-ignore
- setScrollbar(self.scrollbarWidth, bodyOverflow);
+ const { element } = self;
+ const { clientHeight, scrollHeight } = getDocumentElement(element);
+ setScrollbar(element, clientHeight !== scrollHeight);
}
/**
* Toggles on/off the `click` event listeners.
*
* @param {Offcanvas} self the `Offcanvas` instance
- * @param {boolean=} add when `true`, listeners are added
+ * @param {boolean=} add when *true*, listeners are added
*/
function toggleOffcanvasEvents(self, add) {
- const action = add ? addEventListener : removeEventListener;
- // @ts-ignore
- self.triggers.forEach((btn) => btn[action]('click', offcanvasTriggerHandler));
+ const action = add ? on : off;
+ self.triggers.forEach((btn) => action(btn, mouseclickEvent, offcanvasTriggerHandler));
}
/**
* Toggles on/off the listeners of the events that close the offcanvas.
*
- * @param {boolean=} add the `Offcanvas` instance
+ * @param {Offcanvas} self the `Offcanvas` instance
+ * @param {boolean=} add when *true* listeners are added
*/
- function toggleOffCanvasDismiss(add) {
- const action = add ? addEventListener : removeEventListener;
- // @ts-ignore
- document[action]('keydown', offcanvasKeyDismissHandler);
- // @ts-ignore
- document[action]('click', offcanvasDismissHandler);
+ function toggleOffCanvasDismiss(self, add) {
+ const action = add ? on : off;
+ const doc = getDocument(self.element);
+ action(doc, keydownEvent, offcanvasKeyDismissHandler);
+ action(doc, mouseclickEvent, offcanvasDismissHandler);
}
/**
@@ -2921,8 +3264,8 @@
const { element, options } = self;
if (!options.scroll) {
- document.body.style.overflow = 'hidden';
setOffCanvasScrollbar(self);
+ getDocumentBody(element).style.overflow = 'hidden';
}
addClass(element, offcanvasTogglingClass);
@@ -2940,7 +3283,7 @@
*/
function beforeOffcanvasHide(self) {
const { element, options } = self;
- const currentOpen = getCurrentOpen();
+ const currentOpen = getCurrentOpen(element);
// @ts-ignore
element.blur();
@@ -2956,63 +3299,73 @@
/**
* Handles the `click` event listeners.
*
- * @this {Element}
- * @param {Event} e the `Event` object
+ * @this {HTMLElement | Element}
+ * @param {MouseEvent} e the `Event` object
*/
function offcanvasTriggerHandler(e) {
- const trigger = this.closest(offcanvasToggleSelector);
+ const trigger = closest(this, offcanvasToggleSelector);
const element = trigger && getTargetElement(trigger);
const self = element && getOffcanvasInstance(element);
- if (trigger && trigger.tagName === 'A') e.preventDefault();
if (self) {
+ self.relatedTarget = trigger;
self.toggle();
+ if (trigger && trigger.tagName === 'A') {
+ e.preventDefault();
+ }
}
}
/**
* Handles the event listeners that close the offcanvas.
*
- * @param {Event} e the `Event` object
+ * @this {Document}
+ * @param {MouseEvent} e the `Event` object
*/
function offcanvasDismissHandler(e) {
- const element = queryElement(offcanvasActiveSelector);
+ const element = querySelector(offcanvasActiveSelector, this);
if (!element) return;
- const offCanvasDismiss = queryElement(offcanvasDismissSelector, element);
+ const offCanvasDismiss = querySelector(offcanvasDismissSelector, element);
const self = getOffcanvasInstance(element);
+
if (!self) return;
- // @ts-ignore
const { options, triggers } = self;
const { target } = e;
- // @ts-ignore
- const trigger = target.closest(offcanvasToggleSelector);
-
- if (trigger && trigger.tagName === 'A') e.preventDefault();
+ // @ts-ignore -- `EventTarget` is `HTMLElement`
+ const trigger = closest(target, offcanvasToggleSelector);
+ const selection = getDocument(element).getSelection();
- // @ts-ignore
- if ((!element.contains(target) && options.backdrop
+ if (!(selection && selection.toString().length)
+ // @ts-ignore
+ && ((!element.contains(target) && options.backdrop
&& (!trigger || (trigger && !triggers.includes(trigger))))
// @ts-ignore
- || (offCanvasDismiss && offCanvasDismiss.contains(target))) {
+ || (offCanvasDismiss && offCanvasDismiss.contains(target)))) {
+ // @ts-ignore
+ self.relatedTarget = offCanvasDismiss && offCanvasDismiss.contains(target)
+ ? offCanvasDismiss : null;
self.hide();
}
+ if (trigger && trigger.tagName === 'A') e.preventDefault();
}
/**
* Handles the `keydown` event listener for offcanvas
* to hide it when user type the `ESC` key.
*
- * @param {{which: number}} e the `Event` object
+ * @param {KeyboardEvent} e the `Event` object
+ * @this {Document}
*/
- function offcanvasKeyDismissHandler({ which }) {
- const element = queryElement(offcanvasActiveSelector);
+ function offcanvasKeyDismissHandler({ code }) {
+ const element = querySelector(offcanvasActiveSelector, this);
if (!element) return;
const self = getOffcanvasInstance(element);
- if (self && self.options.keyboard && which === 27) {
+ if (self && self.options.keyboard && code === keyEscape) {
+ self.relatedTarget = null;
self.hide();
}
}
@@ -3023,24 +3376,21 @@
* @param {Offcanvas} self the `Offcanvas` instance
*/
function showOffcanvasComplete(self) {
- // @ts-ignore
const { element, triggers } = self;
removeClass(element, offcanvasTogglingClass);
- element.removeAttribute(ariaHidden);
- element.setAttribute(ariaModal, 'true');
- element.setAttribute('role', 'dialog');
- // @ts-ignore
- self.isAnimating = false;
+ removeAttribute(element, ariaHidden);
+ setAttribute(element, ariaModal, 'true');
+ setAttribute(element, 'role', 'dialog');
if (triggers.length) {
- triggers.forEach((btn) => btn.setAttribute(ariaExpanded, 'true'));
+ triggers.forEach((btn) => setAttribute(btn, ariaExpanded, 'true'));
}
- element.dispatchEvent(shownOffcanvasEvent);
+ dispatchEvent(element, shownOffcanvasEvent);
- toggleOffCanvasDismiss(true);
- setFocus(element);
+ toggleOffCanvasDismiss(self, true);
+ focus(element);
}
/**
@@ -3049,31 +3399,29 @@
* @param {Offcanvas} self the `Offcanvas` instance
*/
function hideOffcanvasComplete(self) {
- const {
- // @ts-ignore
- element, triggers,
- } = self;
+ const { element, triggers } = self;
- element.setAttribute(ariaHidden, 'true');
- element.removeAttribute(ariaModal);
- element.removeAttribute('role');
+ setAttribute(element, ariaHidden, 'true');
+ removeAttribute(element, ariaModal);
+ removeAttribute(element, 'role');
// @ts-ignore
element.style.visibility = '';
- // @ts-ignore
- self.isAnimating = false;
if (triggers.length) {
- triggers.forEach((btn) => btn.setAttribute(ariaExpanded, 'false'));
+ triggers.forEach((btn) => setAttribute(btn, ariaExpanded, 'false'));
const visibleTrigger = triggers.find((x) => isVisible(x));
- if (visibleTrigger) setFocus(visibleTrigger);
+ if (visibleTrigger) focus(visibleTrigger);
}
- removeOverlay();
+ removeOverlay(element);
- element.dispatchEvent(hiddenOffcanvasEvent);
+ dispatchEvent(element, hiddenOffcanvasEvent);
removeClass(element, offcanvasTogglingClass);
- toggleOffCanvasDismiss();
+ // must check for open instances
+ if (!getCurrentOpen(element)) {
+ toggleOffCanvasDismiss(self);
+ }
}
// OFFCANVAS DEFINITION
@@ -3081,7 +3429,7 @@
/** Returns a new `Offcanvas` instance. */
class Offcanvas extends BaseComponent {
/**
- * @param {Element | string} target usually an `.offcanvas` element
+ * @param {HTMLElement | Element | string} target usually an `.offcanvas` element
* @param {BSN.Options.Offcanvas=} config instance options
*/
constructor(target, config) {
@@ -3092,15 +3440,16 @@
const { element } = self;
// all the triggering buttons
- /** @private @type {Element[]} */
- self.triggers = Array.from(document.querySelectorAll(offcanvasToggleSelector))
+ /** @type {(HTMLElement | Element)[]} */
+ self.triggers = [...querySelectorAll(offcanvasToggleSelector)]
.filter((btn) => getTargetElement(btn) === element);
// additional instance property
- /** @private @type {boolean} */
- self.isAnimating = false;
- /** @private @type {number} */
- self.scrollbarWidth = measureScrollbar();
+ /** @type {HTMLBodyElement | HTMLElement | Element} */
+ // @ts-ignore
+ self.container = getElementContainer(element);
+ /** @type {(HTMLElement | Element)?} */
+ self.relatedTarget = null;
// attach event listeners
toggleOffcanvasEvents(self, true);
@@ -3132,35 +3481,32 @@
show() {
const self = this;
const {
- element, options, isAnimating,
+ element, options, container, relatedTarget,
} = self;
let overlayDelay = 0;
- if (hasClass(element, showClass) || isAnimating) return;
-
- element.dispatchEvent(showOffcanvasEvent);
+ if (hasClass(element, showClass)) return;
+ showOffcanvasEvent.relatedTarget = relatedTarget;
+ shownOffcanvasEvent.relatedTarget = relatedTarget;
+ dispatchEvent(element, showOffcanvasEvent);
if (showOffcanvasEvent.defaultPrevented) return;
// we elegantly hide any opened modal/offcanvas
- const currentOpen = getCurrentOpen();
+ const currentOpen = getCurrentOpen(element);
if (currentOpen && currentOpen !== element) {
const this1 = getOffcanvasInstance(currentOpen);
const that1 = this1 || getInstance(currentOpen, 'Modal');
that1.hide();
}
- self.isAnimating = true;
-
if (options.backdrop) {
if (!currentOpen) {
- appendOverlay(true);
+ appendOverlay(container, true);
} else {
toggleOverlayType();
}
-
overlayDelay = getElementTransitionDuration(overlay);
-
if (!hasClass(overlay, showClass)) showOverlay();
setTimeout(() => beforeOffcanvasShow(self), overlayDelay);
@@ -3178,14 +3524,15 @@
*/
hide(force) {
const self = this;
- const { element, isAnimating } = self;
+ const { element, relatedTarget } = self;
- if (!hasClass(element, showClass) || isAnimating) return;
+ if (!hasClass(element, showClass)) return;
- element.dispatchEvent(hideOffcanvasEvent);
+ hideOffcanvasEvent.relatedTarget = relatedTarget;
+ hiddenOffcanvasEvent.relatedTarget = relatedTarget;
+ dispatchEvent(element, hideOffcanvasEvent);
if (hideOffcanvasEvent.defaultPrevented) return;
- self.isAnimating = true;
addClass(element, offcanvasTogglingClass);
removeClass(element, showClass);
@@ -3203,7 +3550,7 @@
}
}
- Object.assign(Offcanvas, {
+ ObjectAssign(Offcanvas, {
selector: offcanvasSelector,
init: offcanvasInitCallback,
getInstance: getOffcanvasInstance,
@@ -3216,89 +3563,244 @@
const ariaDescribedBy = 'aria-describedby';
/**
- * Checks if an element is an ` |