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) • 16.1 kB
JavaScript
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["react-horizontal-scrolling-menu"]={},e.React)}(this,function(e,t){"use strict";const n="react-horizontal-scrolling-menu",r=`${n}--item`,o=`${n}--scroll-container`,s=`${n}--wrapper`,i=`${n}--inner-wrapper`,l=`${n}--header`,a=`${n}--arrow-left`,u=`${n}--arrow-right`,c=`${n}--footer`,d="itemId",h="data-key",f="data-index",v={first:"first",last:"last",onInit:"onInit",onUpdate:"onUpdate"},m="",b={current:null};var p=Object.freeze({__proto__:null,arrowLeftClassName:a,arrowRightClassName:u,dataIndexAttribute:f,dataKeyAttribute:h,emptyRef:b,emptyStr:m,events:v,footerClassName:c,headerClassName:l,id:d,innerWrapperClassName:i,itemClassName:r,rootClassName:n,scrollContainerClassName:o,wrapperClassName:s});const g=e=>"object"==typeof e&&null!=e&&1===e.nodeType,y=(e,t)=>(!t||"hidden"!==e)&&"visible"!==e&&"clip"!==e,w=(e,t)=>{if(e.clientHeight<e.scrollHeight||e.clientWidth<e.scrollWidth){const n=getComputedStyle(e,null);return y(n.overflowY,t)||y(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},M=(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,C=e=>{const t=e.parentElement;return null==t?e.getRootNode().host||null:t},E=(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(!g(e))throw new TypeError("Invalid target");const h=document.scrollingElement||document.documentElement,f=[];let v=e;for(;g(v)&&d(v);){if(v=C(v),v===h){f.push(v);break}null!=v&&v===document.body&&w(v)&&!w(document.documentElement)||null!=v&&w(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:p,scrollY:y}=window,{height:E,width:I,top:x,right:k,bottom:R,left:N}=e.getBoundingClientRect(),{top:S,right:O,bottom:T,left:$}=(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 j="start"===l||"nearest"===l?x-S:"end"===l?R+T:x+E/2-S+T,W="center"===a?N+I/2-$+O:"end"===a?k+O:N-$;const B=[];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&&x>=0&&N>=0&&R<=b&&k<=m&&(t===h&&!w(t)||x>=o&&R<=u&&N>=c&&k<=s))return B;const d=getComputedStyle(t),v=parseInt(d.borderLeftWidth,10),g=parseInt(d.borderTopWidth,10),C=parseInt(d.borderRightWidth,10),S=parseInt(d.borderBottomWidth,10);let O=0,T=0;const $="offsetWidth"in t?t.offsetWidth-t.clientWidth-v-C:0,V="offsetHeight"in t?t.offsetHeight-t.clientHeight-g-S:0,A="offsetWidth"in t?0===t.offsetWidth?0:r/t.offsetWidth:0,L="offsetHeight"in t?0===t.offsetHeight?0:n/t.offsetHeight:0;if(h===t)O="start"===l?j:"end"===l?j-b:"nearest"===l?M(y,y+b,b,g,S,y+j,y+j+E,E):j-b/2,T="start"===a?W:"center"===a?W-m/2:"end"===a?W-m:M(p,p+m,m,v,C,p+W,p+W+I,I),O=Math.max(0,O+y),T=Math.max(0,T+p);else{O="start"===l?j-o-g:"end"===l?j-u+S+V:"nearest"===l?M(o,u,n,g,S+V,j,j+E,E):j-(o+n/2)+V/2,T="start"===a?W-c-v:"center"===a?W-(c+r/2)+$/2:"end"===a?W-s+C+$:M(c,s,r,v,C+$,W,W+I,I);const{scrollLeft:e,scrollTop:i}=t;O=0===L?0:Math.max(0,Math.min(i+O/L,t.scrollHeight-n/L+V)),T=0===A?0:Math.max(0,Math.min(e+T/A,t.scrollWidth-r/A+$)),j+=i-O,W+=e-T}B.push({el:t,top:O,left:T})}return B};function I(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(E(e,t));const r="boolean"==typeof t||null==t?void 0:t.behavior;for(const{el:o,top:s,left:i}of E(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 x;const k=()=>(x||(x="performance"in window?performance.now.bind(performance):Date.now),x());function R(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(()=>R(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;R({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 S(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)?I(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(I(e,t))}(l,Object.assign(Object.assign({},o),a))}const O=e=>document.querySelector(`[${h}='${e}']`),T=e=>document.querySelector(`[${f}='${e}']`);function $(e){return t.isValidElement(e)&&e||"function"==typeof e&&t.createElement(e,null)||!!e&&"object"==typeof e&&t.createElement(e,null)||null}const j=e=>{var t;return String((null===(t=null==e?void 0:e.props)||void 0===t?void 0:t[d])||((null==e?void 0:e.key)||m).replace(/^\.\$/,m))};function W(e){return!!e&&Object.prototype.hasOwnProperty.call(e,"current")}var B=t.memo(function({children:e,className:n,id:r,index:o,registerRef:s}){const i=t.useCallback(e=>s(o,e),[s,o]);return t.createElement("div",{className:n,[h]:r,[f]:o,ref:i},e)});function V({children:e,itemClassName:n="",registerRef:o}){const s=t.Children.toArray(e).filter(Boolean),i=t.useMemo(()=>`${r} ${n}`,[n]);return s.map((e,n)=>{const r=j(e);return t.createElement(B,{className:i,id:r,key:r,registerRef:o,index:n},e)})}function A({className:e="",children:n,onScroll:r=()=>{},scrollRef:s,containerRef:i}){const l=t.useMemo(()=>`${o} ${e}`,[e]),a=t.useCallback(e=>{W(s)?s.current=e:s(e),W(i)?i.current=e:i(e)},[s,i]);return t.createElement("div",{className:l,onScroll:r,ref:a},n)}const L=t.createContext({});const H="undefined"!=typeof window?t.useLayoutEffect:t.useEffect;function U({items:e,itemsChanged:n,refs:r,options:o,root:s}){const i=t.useRef(void 0),l=t.useCallback(t=>{e.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:m);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:m),key:l,entry:e,visible:e.intersectionRatio>=t.ratio}]})}(t,o))},[e,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 P(e,n){const r=t.useMemo(()=>{return n=e,t.Children.toArray(n).map(j).filter(Boolean);var n},[e]),o=t.useMemo(()=>r.filter(Boolean).join(m),[r]);return t.useEffect(()=>{n.toItems().filter(e=>!r.includes(e)).forEach(e=>{n.delete(e)})},[r,n]),o}class F{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(v.onUpdate))},this.update=(e,t)=>{this.emitUpdates(e,t),this.emitUpdates(v.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([v.first,t]):e===(null==r?void 0:r.key)&&o.push([v.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([v.first,r[1]]);const s=e.find(([e])=>e===(null==n?void 0:n.key));return s&&o.push([v.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(v.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 F,this.firstRun=!0}}const X={ratio:.9,rootMargin:"5px",threshold:[.05,.5,.75,.95]};const Y={current:{}},q=()=>{};e.ScrollMenu=function({LeftArrow:e,RightArrow:n,children:r,Header:o,Footer:d,transitionDuration:h=500,transitionBehavior:f,onInit:p=q,onUpdate:g=q,onMouseDown:y,onMouseLeave:w,onMouseUp:M,onMouseMove:C,onScroll:E=q,onTouchMove:I,onTouchStart:x,onTouchEnd:k,onWheel:R=q,options:N=X,scrollContainerClassName:j="",containerRef:B=b,itemClassName:F="",wrapperClassName:_="",apiRef:z=Y,RTL:K,noPolyfill:G=!0}){const J=$(e),Q=$(n),Z=$(o),ee=$(d),te=t.useRef(null),ne=t.useRef({}),re=t.useCallback((e,t)=>{ne.current[String(e)]={current:t}},[]),oe=t.useMemo(()=>Object.assign(Object.assign({},X),N),[N]),[se]=t.useState(()=>new D),ie=P(r,se),le=((e,n)=>{const r=t.useRef(!0),o=t.useMemo(()=>[n-.05,n-.01,n,n+.01,n+.05],[n]),s=t.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 t=new IntersectionObserver(s,{threshold:o,rootMargin:"0px"});return e.current&&t.observe(e.current),()=>t.disconnect()},[r,e,s,o]),r})(te,oe.ratio+.05<1?oe.ratio+.05:.95);U(t.useMemo(()=>({items:se,itemsChanged:ie,options:oe,refs:ne,root:te}),[se,ie,ne,oe]));const ae=t.useMemo(()=>function(e,n,r,o){var s,i,l;const a=(n,r=!1)=>{const[o,s]=t.useState(r),i=t.useCallback(e=>{s(!!(null==e?void 0:e.visible))},[]);return t.useEffect(()=>{e.subscribe(n,i);const t=String(n),r=t===v.first?e.first():t===v.last?e.last():e.get(t);return r&&s(!!r.visible),()=>{e.unsubscribe(n,i)}},[n,i]),o},u=!!(null===(s=e.first())||void 0===s?void 0:s.visible),c=!!(null===(i=e.last())||void 0===i?void 0:i.visible),d=t=>{var n;return null===(n=e.find(e=>e[1].key===String(t)))||void 0===n?void 0:n[1]},h=()=>{var t,n;const r=null===(n=null===(t=e.getVisible())||void 0===t?void 0:t[0])||void 0===n?void 0:n[1];return r?e.prev(r):void 0},f=()=>{var t;const n=null===(t=e.getVisible().findLast(()=>!0))||void 0===t?void 0:t[1];return n?e.next(n):void 0},m=null===(l=null==r?void 0:r.boundary)||void 0===l?void 0:l.current;return{getItemById:d,getItemElementById:O,getItemByIndex:t=>{var n;return null===(n=e.find(e=>String(e[1].index)===String(t)))||void 0===n?void 0:n[1]},getItemElementByIndex:T,getNextElement:f,getPrevElement:h,isFirstItemVisible:u,isItemVisible:t=>e.getVisible().map(e=>e[0]).includes(String(t)),isLastItem:t=>e.last()===d(t),isLastItemVisible:c,scrollNext:(e,t,n,{duration:s,boundary:i=m}={})=>{const l=null!=e?e:null==r?void 0:r.behavior;return S(f(),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 S(h(),l,t||"end",n||"nearest",{boundary:i,duration:null!=s?s:null==r?void 0:r.duration},o)},useIsVisible:a,useLeftArrowVisible:()=>{const e=a("first",!0),[r,o]=t.useState(e);return t.useEffect(()=>{n.current&&o(e)},[e]),r},useRightArrowVisible:()=>{const e=a("last",!1),[r,o]=t.useState(e);return t.useEffect(()=>{n.current&&o(e)},[e]),r},scrollToItem:(e,t,n,s,i)=>{var l;return S(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:f,boundary:te},G),[se,h,f,G,le]),ue=t.useCallback(()=>Object.assign(Object.assign({},ae),{items:se,scrollContainer:te,menuVisible:le}),[ae,se,te,le]),ce=t.useMemo(()=>ue(),[ue]);(({context:e,onInit:n,onUpdate:r})=>{const o=t.useRef(!1),s=t.useCallback(()=>{o.current||(o.current=!0,n(e))},[n,e]),i=t.useCallback(()=>r(e),[r,e]),{items:l}=e;t.useEffect(()=>(l.subscribe(v.onInit,s),l.subscribe(v.onUpdate,i),l.firstRun||s(),()=>{l.unsubscribe(v.onInit,s),l.unsubscribe(v.onUpdate,i)}),[l,s,i])})({context:ce,onInit:p,onUpdate:g}),t.useEffect(()=>{W(z)?z.current=ce:z(ce)},[ce,z]);const de=t.useCallback(e=>E(ce,e),[E,ce]),he=t.useCallback(e=>R(ce,e),[R,ce]),fe=t.useMemo(()=>`${s} ${_}`,[_]),ve=t.useMemo(()=>`${j}${K?" rtl":m}`,[K,j]);return t.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==x?void 0:x(ce),onTouchMove:null==I?void 0:I(ce),onTouchEnd:null==k?void 0:k(ce)},t.createElement(L.Provider,{value:ce},t.createElement("div",{className:l},Z),t.createElement("div",{className:i},t.createElement("div",{className:a},J),t.createElement(A,{className:ve,onScroll:de,scrollRef:te,containerRef:B},t.createElement(V,{registerRef:re,itemClassName:F},r)),t.createElement("div",{className:u},Q)),t.createElement("div",{className:c},ee)))},e.VisibilityContext=L,e.constants=p,e.getItemsPos=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]}},e.slidingWindow=function(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)}}});