react-horizontal-scrolling-menu
Version:
Horizontal scrolling menu component for React with per-item visibility tracking. Supports mouse and touch devices, SSR-safe.
2 lines (1 loc) • 15.9 kB
JavaScript
import e from"react";const t="react-horizontal-scrolling-menu",n=`${t}--item`,r=`${t}--scroll-container`,o=`${t}--wrapper`,s=`${t}--inner-wrapper`,i=`${t}--header`,l=`${t}--arrow-left`,a=`${t}--arrow-right`,u=`${t}--footer`,c="itemId",d="data-key",h="data-index",f={first:"first",last:"last",onInit:"onInit",onUpdate:"onUpdate"},v="",m={current:null};var b=Object.freeze({__proto__:null,arrowLeftClassName:l,arrowRightClassName:a,dataIndexAttribute:h,dataKeyAttribute:d,emptyRef:m,emptyStr:v,events:f,footerClassName:u,headerClassName:i,id:c,innerWrapperClassName:s,itemClassName:n,rootClassName:t,scrollContainerClassName:r,wrapperClassName:o});const p=e=>"object"==typeof e&&null!=e&&1===e.nodeType,g=(e,t)=>(!t||"hidden"!==e)&&"visible"!==e&&"clip"!==e,y=(e,t)=>{if(e.clientHeight<e.scrollHeight||e.clientWidth<e.scrollWidth){const n=getComputedStyle(e,null);return g(n.overflowY,t)||g(n.overflowX,t)||(e=>{const t=(e=>{if(!e.ownerDocument||!e.ownerDocument.defaultView)return null;try{return e.ownerDocument.defaultView.frameElement}catch(e){return null}})(e);return!!t&&(t.clientHeight<e.scrollHeight||t.clientWidth<e.scrollWidth)})(e)}return!1},w=(e,t,n,r,o,s,i,l)=>s<e&&i>t||s>e&&i<t?0:s<=e&&l<=n||i>=t&&l>=n?s-e-r:i>t&&l<n||s<e&&l>n?i-t+o:0,M=e=>{const t=e.parentElement;return null==t?e.getRootNode().host||null:t},C=(e,t)=>{var n,r,o,s;if("undefined"==typeof document)return[];const{scrollMode:i,block:l,inline:a,boundary:u,skipOverflowHiddenElements:c}=t,d="function"==typeof u?u:e=>e!==u;if(!p(e))throw new TypeError("Invalid target");const h=document.scrollingElement||document.documentElement,f=[];let v=e;for(;p(v)&&d(v);){if(v=M(v),v===h){f.push(v);break}null!=v&&v===document.body&&y(v)&&!y(document.documentElement)||null!=v&&y(v,c)&&f.push(v)}const m=null!=(r=null==(n=window.visualViewport)?void 0:n.width)?r:innerWidth,b=null!=(s=null==(o=window.visualViewport)?void 0:o.height)?s:innerHeight,{scrollX:g,scrollY:C}=window,{height:E,width:I,top:k,right:x,bottom:N,left:R}=e.getBoundingClientRect(),{top:S,right:O,bottom:$,left:j}=(e=>{const t=window.getComputedStyle(e);return{top:parseFloat(t.scrollMarginTop)||0,right:parseFloat(t.scrollMarginRight)||0,bottom:parseFloat(t.scrollMarginBottom)||0,left:parseFloat(t.scrollMarginLeft)||0}})(e);let B="start"===l||"nearest"===l?k-S:"end"===l?N+$:k+E/2-S+$,T="center"===a?R+I/2-j+O:"end"===a?x+O:R-j;const W=[];for(let e=0;e<f.length;e++){const t=f[e],{height:n,width:r,top:o,right:s,bottom:u,left:c}=t.getBoundingClientRect();if("if-needed"===i&&k>=0&&R>=0&&N<=b&&x<=m&&(t===h&&!y(t)||k>=o&&N<=u&&R>=c&&x<=s))return W;const d=getComputedStyle(t),v=parseInt(d.borderLeftWidth,10),p=parseInt(d.borderTopWidth,10),M=parseInt(d.borderRightWidth,10),S=parseInt(d.borderBottomWidth,10);let O=0,$=0;const j="offsetWidth"in t?t.offsetWidth-t.clientWidth-v-M:0,A="offsetHeight"in t?t.offsetHeight-t.clientHeight-p-S:0,L="offsetWidth"in t?0===t.offsetWidth?0:r/t.offsetWidth:0,V="offsetHeight"in t?0===t.offsetHeight?0:n/t.offsetHeight:0;if(h===t)O="start"===l?B:"end"===l?B-b:"nearest"===l?w(C,C+b,b,p,S,C+B,C+B+E,E):B-b/2,$="start"===a?T:"center"===a?T-m/2:"end"===a?T-m:w(g,g+m,m,v,M,g+T,g+T+I,I),O=Math.max(0,O+C),$=Math.max(0,$+g);else{O="start"===l?B-o-p:"end"===l?B-u+S+A:"nearest"===l?w(o,u,n,p,S+A,B,B+E,E):B-(o+n/2)+A/2,$="start"===a?T-c-v:"center"===a?T-(c+r/2)+j/2:"end"===a?T-s+M+j:w(c,s,r,v,M+j,T,T+I,I);const{scrollLeft:e,scrollTop:i}=t;O=0===V?0:Math.max(0,Math.min(i+O/V,t.scrollHeight-n/V+A)),$=0===L?0:Math.max(0,Math.min(e+$/L,t.scrollWidth-r/L+j)),B+=i-O,T+=e-$}W.push({el:t,top:O,left:$})}return W};function E(e,t){if(!e.isConnected||!(e=>{let t=e;for(;t&&t.parentNode;){if(t.parentNode===document)return!0;t=t.parentNode instanceof ShadowRoot?t.parentNode.host:t.parentNode}return!1})(e))return;const n=(e=>{const t=window.getComputedStyle(e);return{top:parseFloat(t.scrollMarginTop)||0,right:parseFloat(t.scrollMarginRight)||0,bottom:parseFloat(t.scrollMarginBottom)||0,left:parseFloat(t.scrollMarginLeft)||0}})(e);if((e=>"object"==typeof e&&"function"==typeof e.behavior)(t))return t.behavior(C(e,t));const r="boolean"==typeof t||null==t?void 0:t.behavior;for(const{el:o,top:s,left:i}of C(e,(e=>!1===e?{block:"end",inline:"nearest"}:(e=>e===Object(e)&&0!==Object.keys(e).length)(e)?e:{block:"start",inline:"nearest"})(t))){const e=s-n.top+n.bottom,t=i-n.left+n.right;o.scroll({top:e,left:t,behavior:r})}}let I;const k=()=>(I||(I="performance"in window?performance.now.bind(performance):Date.now),I());function x(e){const t=k(),n=Math.min((t-e.startTime)/e.duration,1),r=e.ease(n),o=e.startX+(e.x-e.startX)*r,s=e.startY+(e.y-e.startY)*r;e.method(o,s,n,r),o!==e.x||s!==e.y?requestAnimationFrame(()=>x(e)):e.cb()}function N(e,t,n){let r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:600,o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:e=>1+--e*e*e*e*e,s=arguments.length>5?arguments[5]:void 0,i=arguments.length>6?arguments[6]:void 0;const l=e,a=e.scrollLeft,u=e.scrollTop;x({scrollable:l,method:(t,n,r,o)=>{const s=Math.ceil(t),l=Math.ceil(n);e.scrollLeft=s,e.scrollTop=l,null==i||i({target:e,elapsed:r,value:o,left:s,top:l})},startTime:k(),startX:a,startY:u,x:t,y:n,duration:r,ease:o,cb:s})}function R(e,t,n,r,o,s){var i;const l=(null===(i=null==e?void 0:e.entry)||void 0===i?void 0:i.target)||e;if(!l)return;const a={behavior:t||"smooth",inline:n||"end",block:r||"nearest"};return s?l.scrollIntoView(a):function(e,t){const n=t||{};return(e=>e&&!e.behavior||"smooth"===e.behavior)(n)?E(e,{block:n.block,inline:n.inline,scrollMode:n.scrollMode,boundary:n.boundary,skipOverflowHiddenElements:n.skipOverflowHiddenElements,behavior:e=>Promise.all(e.reduce((e,t)=>{let{el:r,left:o,top:s}=t;const i=r.scrollLeft,l=r.scrollTop;return i===o&&l===s?e:[...e,new Promise(e=>N(r,o,s,n.duration,n.ease,()=>e({el:r,left:[i,o],top:[l,s]}),n.onScrollChange))]},[]))}):Promise.resolve(E(e,t))}(l,Object.assign(Object.assign({},o),a))}const S=e=>document.querySelector(`[${d}='${e}']`),O=e=>document.querySelector(`[${h}='${e}']`);function $(t){return e.isValidElement(t)&&t||"function"==typeof t&&e.createElement(t,null)||!!t&&"object"==typeof t&&e.createElement(t,null)||null}const j=e=>{var t;return String((null===(t=null==e?void 0:e.props)||void 0===t?void 0:t[c])||((null==e?void 0:e.key)||v).replace(/^\.\$/,v))};function B(e){return!!e&&Object.prototype.hasOwnProperty.call(e,"current")}var T=e.memo(function({children:t,className:n,id:r,index:o,registerRef:s}){const i=e.useCallback(e=>s(o,e),[s,o]);return e.createElement("div",{className:n,[d]:r,[h]:o,ref:i},t)});function W({children:t,itemClassName:r="",registerRef:o}){const s=e.Children.toArray(t).filter(Boolean),i=e.useMemo(()=>`${n} ${r}`,[r]);return s.map((t,n)=>{const r=j(t);return e.createElement(T,{className:i,id:r,key:r,registerRef:o,index:n},t)})}function A({className:t="",children:n,onScroll:o=()=>{},scrollRef:s,containerRef:i}){const l=e.useMemo(()=>`${r} ${t}`,[t]),a=e.useCallback(e=>{B(s)?s.current=e:s(e),B(i)?i.current=e:i(e)},[s,i]);return e.createElement("div",{className:l,onScroll:o,ref:a},n)}const L=e.createContext({});const V=e=>{var t;const n=e[Math.floor(e.length/2)];return{first:null==e?void 0:e[0],center:n,last:null===(t=e.slice(-1))||void 0===t?void 0:t[0]}},H="undefined"!=typeof window?e.useLayoutEffect:e.useEffect;function U({items:t,itemsChanged:n,refs:r,options:o,root:s}){const i=e.useRef(void 0),l=e.useCallback(e=>{t.setBatch(function(e,t){return[...e].map(e=>{var n,r,o,s;const i=e.target,l=String(null!==(r=null===(n=null==i?void 0:i.dataset)||void 0===n?void 0:n.key)&&void 0!==r?r:v);return[l,{index:String(null!==(s=null===(o=null==i?void 0:i.dataset)||void 0===o?void 0:o.index)&&void 0!==s?s:v),key:l,entry:e,visible:e.intersectionRatio>=t.ratio}]})}(e,o))},[t,o]);H(()=>{const e=(e=>Object.values(e).map(e=>e.current).filter(Boolean))(r.current),t=i.current||new IntersectionObserver(l,Object.assign(Object.assign({},o),{root:s.current}));return i.current=t,e.forEach(e=>t.observe(e)),()=>{t.disconnect(),i.current=void 0}},[l,n,o,r,s])}function F(t,n){const r=e.useMemo(()=>{return n=t,e.Children.toArray(n).map(j).filter(Boolean);var n},[t]),o=e.useMemo(()=>r.filter(Boolean).join(v),[r]);return e.useEffect(()=>{n.toItems().filter(e=>!r.includes(e)).forEach(e=>{n.delete(e)})},[r,n]),o}class P{constructor(){this.subscribe=(e,t)=>{this.observers.set(e,(this.observers.get(e)||[]).concat(t))},this.unsubscribe=(e,t)=>{const n=(this.observers.get(e)||[]).filter(e=>e!==t);n.length?this.observers.set(e,n):this.observers.delete(e)},this.emitUpdates=(e,t)=>{const n=this.observers.get(e)||[];null==n||n.forEach(e=>e(t))},this.updateBatch=(e,t=!0)=>{e.length&&(e.forEach(([e,t])=>this.emitUpdates(e,t)),t&&this.emitUpdates(f.onUpdate))},this.update=(e,t)=>{this.emitUpdates(e,t),this.emitUpdates(f.onUpdate)},this.observers=new Map}}class D extends Map{constructor(){super(),this.subscribe=(e,t)=>this.observer.subscribe(e,t),this.unsubscribe=(e,t)=>this.observer.unsubscribe(e,t),this.isEdgeItem=({key:e,value:t,first:n=this.first(),last:r=this.last()})=>{const o=[];return e===(null==n?void 0:n.key)?o.push([f.first,t]):e===(null==r?void 0:r.key)&&o.push([f.last,t]),o},this.edgeItemsCheck=e=>{const t=this.first(),n=this.last(),r=e.find(([e])=>e===(null==t?void 0:t.key)),o=[];r&&o.push([f.first,r[1]]);const s=e.find(([e])=>e===(null==n?void 0:n.key));return s&&o.push([f.last,s[1]]),o},this.toArr=()=>this.sort([...this]),this.toItems=()=>this.toArr().map(([e])=>e),this.sort=e=>e.sort(([,e],[,t])=>+e.index-+t.index),this.set=(e,t)=>{const n=String(e),r=[[n,t]];return super.set(n,t),r.push(...this.isEdgeItem({key:n,value:t,first:this.first(),last:this.last()})),this.observer.updateBatch(r),this},this.setBatch=e=>{this.firstRun&&this.observer.update(f.onInit);const t=[...e];return this.sort(t).forEach(([e,t])=>{super.set(String(e),t)}),t.push(...this.edgeItemsCheck(t)),this.observer.updateBatch(t,!this.firstRun),this.firstRun=!1,this},this.first=()=>{var e;return null===(e=this.toArr()[0])||void 0===e?void 0:e[1]},this.last=()=>{var e,t;return null===(t=null===(e=this.toArr().slice(-1))||void 0===e?void 0:e[0])||void 0===t?void 0:t[1]},this.filter=e=>this.toArr().filter(e),this.find=e=>this.toArr().find(e),this.findIndex=e=>this.toArr().findIndex(e),this.getCurrentPos=e=>{const t=this.toArr(),n=t.findIndex(([t,n])=>t===e||n===e);return[t,n]},this.prev=e=>{var t;const[n,r]=this.getCurrentPos(e);return-1!==r?null===(t=n[r-1])||void 0===t?void 0:t[1]:void 0},this.next=e=>{var t;const[n,r]=this.getCurrentPos(e);return-1!==r?null===(t=n[r+1])||void 0===t?void 0:t[1]:void 0},this.getVisible=()=>this.filter(e=>e[1].visible),this.observer=new P,this.firstRun=!0}}const X={ratio:.9,rootMargin:"5px",threshold:[.05,.5,.75,.95]};function Y(e,t){return{prev:()=>function(e,t){const n=e.findIndex(e=>e===(null==t?void 0:t[0])),r=t.length,o=n-r,s=o<0,i=s?0:o,l=e.slice(i,s?r:n);return l.length===r?l:e.slice(n,r)}(e,t),next:()=>function(e,t){const n=e.findIndex(e=>{var n;return e===(null===(n=t.slice(-1))||void 0===n?void 0:n[0])}),r=t.length,o=n+r+1,s=o>e.length-1,i=s?e.length-1:o,l=e.slice(s?i-r+1:n+1,i);return l.length===r?l:e.slice(e.length-r,e.length+r)}(e,t)}}const _={current:{}},q=()=>{};function z({LeftArrow:t,RightArrow:n,children:r,Header:c,Footer:d,transitionDuration:h=500,transitionBehavior:b,onInit:p=q,onUpdate:g=q,onMouseDown:y,onMouseLeave:w,onMouseUp:M,onMouseMove:C,onScroll:E=q,onTouchMove:I,onTouchStart:k,onTouchEnd:x,onWheel:N=q,options:j=X,scrollContainerClassName:T="",containerRef:V=m,itemClassName:P="",wrapperClassName:Y="",apiRef:z=_,RTL:K,noPolyfill:G=!0}){const J=$(t),Q=$(n),Z=$(c),ee=$(d),te=e.useRef(null),ne=e.useRef({}),re=e.useCallback((e,t)=>{ne.current[String(e)]={current:t}},[]),oe=e.useMemo(()=>Object.assign(Object.assign({},X),j),[j]),[se]=e.useState(()=>new D),ie=F(r,se),le=((t,n)=>{const r=e.useRef(!0),o=e.useMemo(()=>[n-.05,n-.01,n,n+.01,n+.05],[n]),s=e.useCallback(e=>{var t;r.current=(null===(t=null==e?void 0:e[0])||void 0===t?void 0:t.intersectionRatio)>=n},[n]);return H(()=>{const e=new IntersectionObserver(s,{threshold:o,rootMargin:"0px"});return t.current&&e.observe(t.current),()=>e.disconnect()},[r,t,s,o]),r})(te,oe.ratio+.05<1?oe.ratio+.05:.95);U(e.useMemo(()=>({items:se,itemsChanged:ie,options:oe,refs:ne,root:te}),[se,ie,ne,oe]));const ae=e.useMemo(()=>function(t,n,r,o){var s,i,l;const a=(n,r=!1)=>{const[o,s]=e.useState(r),i=e.useCallback(e=>{s(!!(null==e?void 0:e.visible))},[]);return e.useEffect(()=>{t.subscribe(n,i);const e=String(n),r=e===f.first?t.first():e===f.last?t.last():t.get(e);return r&&s(!!r.visible),()=>{t.unsubscribe(n,i)}},[n,i]),o},u=!!(null===(s=t.first())||void 0===s?void 0:s.visible),c=!!(null===(i=t.last())||void 0===i?void 0:i.visible),d=e=>{var n;return null===(n=t.find(t=>t[1].key===String(e)))||void 0===n?void 0:n[1]},h=()=>{var e,n;const r=null===(n=null===(e=t.getVisible())||void 0===e?void 0:e[0])||void 0===n?void 0:n[1];return r?t.prev(r):void 0},v=()=>{var e;const n=null===(e=t.getVisible().findLast(()=>!0))||void 0===e?void 0:e[1];return n?t.next(n):void 0},m=null===(l=null==r?void 0:r.boundary)||void 0===l?void 0:l.current;return{getItemById:d,getItemElementById:S,getItemByIndex:e=>{var n;return null===(n=t.find(t=>String(t[1].index)===String(e)))||void 0===n?void 0:n[1]},getItemElementByIndex:O,getNextElement:v,getPrevElement:h,isFirstItemVisible:u,isItemVisible:e=>t.getVisible().map(e=>e[0]).includes(String(e)),isLastItem:e=>t.last()===d(e),isLastItemVisible:c,scrollNext:(e,t,n,{duration:s,boundary:i=m}={})=>{const l=null!=e?e:null==r?void 0:r.behavior;return R(v(),l,t||"start",n||"nearest",{boundary:i,duration:null!=s?s:null==r?void 0:r.duration},o)},scrollPrev:(e,t,n,{duration:s,boundary:i=m}={})=>{const l=null!=e?e:null==r?void 0:r.behavior;return R(h(),l,t||"end",n||"nearest",{boundary:i,duration:null!=s?s:null==r?void 0:r.duration},o)},useIsVisible:a,useLeftArrowVisible:()=>{const t=a("first",!0),[r,o]=e.useState(t);return e.useEffect(()=>{n.current&&o(t)},[t]),r},useRightArrowVisible:()=>{const t=a("last",!1),[r,o]=e.useState(t);return e.useEffect(()=>{n.current&&o(t)},[t]),r},scrollToItem:(e,t,n,s,i)=>{var l;return R(e,null!=t?t:null==r?void 0:r.behavior,n,s,Object.assign(Object.assign({boundary:m},i),{duration:null!==(l=null==i?void 0:i.duration)&&void 0!==l?l:null==r?void 0:r.duration}),o)}}}(se,le,{duration:h,behavior:b,boundary:te},G),[se,h,b,G,le]),ue=e.useCallback(()=>Object.assign(Object.assign({},ae),{items:se,scrollContainer:te,menuVisible:le}),[ae,se,te,le]),ce=e.useMemo(()=>ue(),[ue]);(({context:t,onInit:n,onUpdate:r})=>{const o=e.useRef(!1),s=e.useCallback(()=>{o.current||(o.current=!0,n(t))},[n,t]),i=e.useCallback(()=>r(t),[r,t]),{items:l}=t;e.useEffect(()=>(l.subscribe(f.onInit,s),l.subscribe(f.onUpdate,i),l.firstRun||s(),()=>{l.unsubscribe(f.onInit,s),l.unsubscribe(f.onUpdate,i)}),[l,s,i])})({context:ce,onInit:p,onUpdate:g}),e.useEffect(()=>{B(z)?z.current=ce:z(ce)},[ce,z]);const de=e.useCallback(e=>E(ce,e),[E,ce]),he=e.useCallback(e=>N(ce,e),[N,ce]),fe=e.useMemo(()=>`${o} ${Y}`,[Y]),ve=e.useMemo(()=>`${T}${K?" rtl":v}`,[K,T]);return e.createElement("div",{className:fe,onWheel:he,onMouseDown:null==y?void 0:y(ce),onMouseLeave:null==w?void 0:w(ce),onMouseUp:null==M?void 0:M(ce),onMouseMove:null==C?void 0:C(ce),onTouchStart:null==k?void 0:k(ce),onTouchMove:null==I?void 0:I(ce),onTouchEnd:null==x?void 0:x(ce)},e.createElement(L.Provider,{value:ce},e.createElement("div",{className:i},Z),e.createElement("div",{className:s},e.createElement("div",{className:l},J),e.createElement(A,{className:ve,onScroll:de,scrollRef:te,containerRef:V},e.createElement(W,{registerRef:re,itemClassName:P},r)),e.createElement("div",{className:a},Q)),e.createElement("div",{className:u},ee)))}export{z as ScrollMenu,L as VisibilityContext,b as constants,V as getItemsPos,Y as slidingWindow};