UNPKG

clusterize-lazy

Version:

Lightweight virtual list helper for the browser.

5 lines 19.3 kB
"use strict";var Clusterize=(()=>{var E=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var N=Object.prototype.hasOwnProperty;var P=(l,i)=>{for(var e in i)E(l,e,{get:i[e],enumerable:!0})},H=(l,i,e,s)=>{if(i&&typeof i=="object"||typeof i=="function")for(let t of W(i))!N.call(l,t)&&t!==e&&E(l,t,{get:()=>i[t],enumerable:!(s=D(i,t))||s.enumerable});return l};var V=l=>H(E({},"__esModule",{value:!0}),l);var U={};P(U,{ClusterizeLazy:()=>x,default:()=>B});function m(l,i,e){let s=e.initialDeps??[],t;function n(){var o,r,a,c;let h;e.key&&((o=e.debug)!=null&&o.call(e))&&(h=Date.now());let d=l();if(!(d.length!==s.length||d.some((p,f)=>s[f]!==p)))return t;s=d;let w;if(e.key&&((r=e.debug)!=null&&r.call(e))&&(w=Date.now()),t=i(...d),e.key&&((a=e.debug)!=null&&a.call(e))){let p=Math.round((Date.now()-h)*100)/100,f=Math.round((Date.now()-w)*100)/100,y=f/16,v=(g,A)=>{for(g=String(g);g.length<A;)g=" "+g;return g};console.info(`%c\u23F1 ${v(f,5)} /${v(p,5)} ms`,` font-size: .6rem; font-weight: bold; color: hsl(${Math.max(0,Math.min(120-120*y,120))}deg 100% 31%);`,e?.key)}return(c=e?.onChange)==null||c.call(e,t),t}return n.updateDeps=o=>{s=o},n}function R(l,i){if(l===void 0)throw new Error(`Unexpected undefined${i?`: ${i}`:""}`);return l}var I=(l,i)=>Math.abs(l-i)<=1,O=(l,i,e)=>{let s;return function(...t){l.clearTimeout(s),s=l.setTimeout(()=>i.apply(this,t),e)}};var M=l=>{let{offsetWidth:i,offsetHeight:e}=l;return{width:i,height:e}},j=l=>l,K=l=>{let i=Math.max(l.startIndex-l.overscan,0),e=Math.min(l.endIndex+l.overscan,l.count-1),s=[];for(let t=i;t<=e;t++)s.push(t);return s},k=(l,i)=>{let e=l.scrollElement;if(!e)return;let s=l.targetWindow;if(!s)return;let t=o=>{let{width:r,height:a}=o;i({width:Math.round(r),height:Math.round(a)})};if(t(M(e)),!s.ResizeObserver)return()=>{};let n=new s.ResizeObserver(o=>{let r=()=>{let a=o[0];if(a?.borderBoxSize){let c=a.borderBoxSize[0];if(c){t({width:c.inlineSize,height:c.blockSize});return}}t(M(e))};l.options.useAnimationFrameWithResizeObserver?requestAnimationFrame(r):r()});return n.observe(e,{box:"border-box"}),()=>{n.unobserve(e)}},z={passive:!0};var C=typeof window>"u"?!0:"onscrollend"in window,L=(l,i)=>{let e=l.scrollElement;if(!e)return;let s=l.targetWindow;if(!s)return;let t=0,n=l.options.useScrollendEvent&&C?()=>{}:O(s,()=>{i(t,!1)},l.options.isScrollingResetDelay),o=h=>()=>{let{horizontal:d,isRtl:u}=l.options;t=d?e.scrollLeft*(u&&-1||1):e.scrollTop,n(),i(t,h)},r=o(!0),a=o(!1);a(),e.addEventListener("scroll",r,z);let c=l.options.useScrollendEvent&&C;return c&&e.addEventListener("scrollend",a,z),()=>{e.removeEventListener("scroll",r),c&&e.removeEventListener("scrollend",a)}};var $=(l,i,e)=>{if(i?.borderBoxSize){let s=i.borderBoxSize[0];if(s)return Math.round(s[e.options.horizontal?"inlineSize":"blockSize"])}return l[e.options.horizontal?"offsetWidth":"offsetHeight"]};var _=(l,{adjustments:i=0,behavior:e},s)=>{var t,n;let o=l+i;(n=(t=s.scrollElement)==null?void 0:t.scrollTo)==null||n.call(t,{[s.options.horizontal?"left":"top"]:o,behavior:e})},b=class{constructor(i){this.unsubs=[],this.scrollElement=null,this.targetWindow=null,this.isScrolling=!1,this.scrollToIndexTimeoutId=null,this.measurementsCache=[],this.itemSizeCache=new Map,this.pendingMeasuredCacheIndexes=[],this.scrollRect=null,this.scrollOffset=null,this.scrollDirection=null,this.scrollAdjustments=0,this.elementsCache=new Map,this.observer=(()=>{let e=null,s=()=>e||(!this.targetWindow||!this.targetWindow.ResizeObserver?null:e=new this.targetWindow.ResizeObserver(t=>{t.forEach(n=>{let o=()=>{this._measureElement(n.target,n)};this.options.useAnimationFrameWithResizeObserver?requestAnimationFrame(o):o()})}));return{disconnect:()=>{var t;(t=s())==null||t.disconnect(),e=null},observe:t=>{var n;return(n=s())==null?void 0:n.observe(t,{box:"border-box"})},unobserve:t=>{var n;return(n=s())==null?void 0:n.unobserve(t)}}})(),this.range=null,this.setOptions=e=>{Object.entries(e).forEach(([s,t])=>{typeof t>"u"&&delete e[s]}),this.options={debug:!1,initialOffset:0,overscan:1,paddingStart:0,paddingEnd:0,scrollPaddingStart:0,scrollPaddingEnd:0,horizontal:!1,getItemKey:j,rangeExtractor:K,onChange:()=>{},measureElement:$,initialRect:{width:0,height:0},scrollMargin:0,gap:0,indexAttribute:"data-index",initialMeasurementsCache:[],lanes:1,isScrollingResetDelay:150,enabled:!0,isRtl:!1,useScrollendEvent:!1,useAnimationFrameWithResizeObserver:!1,...e}},this.notify=e=>{var s,t;(t=(s=this.options).onChange)==null||t.call(s,this,e)},this.maybeNotify=m(()=>(this.calculateRange(),[this.isScrolling,this.range?this.range.startIndex:null,this.range?this.range.endIndex:null]),e=>{this.notify(e)},{key:!1,debug:()=>this.options.debug,initialDeps:[this.isScrolling,this.range?this.range.startIndex:null,this.range?this.range.endIndex:null]}),this.cleanup=()=>{this.unsubs.filter(Boolean).forEach(e=>e()),this.unsubs=[],this.observer.disconnect(),this.scrollElement=null,this.targetWindow=null},this._didMount=()=>()=>{this.cleanup()},this._willUpdate=()=>{var e;let s=this.options.enabled?this.options.getScrollElement():null;if(this.scrollElement!==s){if(this.cleanup(),!s){this.maybeNotify();return}this.scrollElement=s,this.scrollElement&&"ownerDocument"in this.scrollElement?this.targetWindow=this.scrollElement.ownerDocument.defaultView:this.targetWindow=((e=this.scrollElement)==null?void 0:e.window)??null,this.elementsCache.forEach(t=>{this.observer.observe(t)}),this._scrollToOffset(this.getScrollOffset(),{adjustments:void 0,behavior:void 0}),this.unsubs.push(this.options.observeElementRect(this,t=>{this.scrollRect=t,this.maybeNotify()})),this.unsubs.push(this.options.observeElementOffset(this,(t,n)=>{this.scrollAdjustments=0,this.scrollDirection=n?this.getScrollOffset()<t?"forward":"backward":null,this.scrollOffset=t,this.isScrolling=n,this.maybeNotify()}))}},this.getSize=()=>this.options.enabled?(this.scrollRect=this.scrollRect??this.options.initialRect,this.scrollRect[this.options.horizontal?"width":"height"]):(this.scrollRect=null,0),this.getScrollOffset=()=>this.options.enabled?(this.scrollOffset=this.scrollOffset??(typeof this.options.initialOffset=="function"?this.options.initialOffset():this.options.initialOffset),this.scrollOffset):(this.scrollOffset=null,0),this.getFurthestMeasurement=(e,s)=>{let t=new Map,n=new Map;for(let o=s-1;o>=0;o--){let r=e[o];if(t.has(r.lane))continue;let a=n.get(r.lane);if(a==null||r.end>a.end?n.set(r.lane,r):r.end<a.end&&t.set(r.lane,!0),t.size===this.options.lanes)break}return n.size===this.options.lanes?Array.from(n.values()).sort((o,r)=>o.end===r.end?o.index-r.index:o.end-r.end)[0]:void 0},this.getMeasurementOptions=m(()=>[this.options.count,this.options.paddingStart,this.options.scrollMargin,this.options.getItemKey,this.options.enabled],(e,s,t,n,o)=>(this.pendingMeasuredCacheIndexes=[],{count:e,paddingStart:s,scrollMargin:t,getItemKey:n,enabled:o}),{key:!1}),this.getMeasurements=m(()=>[this.getMeasurementOptions(),this.itemSizeCache],({count:e,paddingStart:s,scrollMargin:t,getItemKey:n,enabled:o},r)=>{if(!o)return this.measurementsCache=[],this.itemSizeCache.clear(),[];this.measurementsCache.length===0&&(this.measurementsCache=this.options.initialMeasurementsCache,this.measurementsCache.forEach(h=>{this.itemSizeCache.set(h.key,h.size)}));let a=this.pendingMeasuredCacheIndexes.length>0?Math.min(...this.pendingMeasuredCacheIndexes):0;this.pendingMeasuredCacheIndexes=[];let c=this.measurementsCache.slice(0,a);for(let h=a;h<e;h++){let d=n(h),u=this.options.lanes===1?c[h-1]:this.getFurthestMeasurement(c,h),w=u?u.end+this.options.gap:s+t,p=r.get(d),f=typeof p=="number"?p:this.options.estimateSize(h),y=w+f,v=u?u.lane:h%this.options.lanes;c[h]={index:h,start:w,size:f,end:y,key:d,lane:v}}return this.measurementsCache=c,c},{key:!1,debug:()=>this.options.debug}),this.calculateRange=m(()=>[this.getMeasurements(),this.getSize(),this.getScrollOffset(),this.options.lanes],(e,s,t,n)=>this.range=e.length>0&&s>0?q({measurements:e,outerSize:s,scrollOffset:t,lanes:n}):null,{key:!1,debug:()=>this.options.debug}),this.getVirtualIndexes=m(()=>{let e=null,s=null,t=this.calculateRange();return t&&(e=t.startIndex,s=t.endIndex),this.maybeNotify.updateDeps([this.isScrolling,e,s]),[this.options.rangeExtractor,this.options.overscan,this.options.count,e,s]},(e,s,t,n,o)=>n===null||o===null?[]:e({startIndex:n,endIndex:o,overscan:s,count:t}),{key:!1,debug:()=>this.options.debug}),this.indexFromElement=e=>{let s=this.options.indexAttribute,t=e.getAttribute(s);return t?parseInt(t,10):(console.warn(`Missing attribute name '${s}={index}' on measured element.`),-1)},this._measureElement=(e,s)=>{let t=this.indexFromElement(e),n=this.measurementsCache[t];if(!n)return;let o=n.key,r=this.elementsCache.get(o);r!==e&&(r&&this.observer.unobserve(r),this.observer.observe(e),this.elementsCache.set(o,e)),e.isConnected&&this.resizeItem(t,this.options.measureElement(e,s,this))},this.resizeItem=(e,s)=>{let t=this.measurementsCache[e];if(!t)return;let n=this.itemSizeCache.get(t.key)??t.size,o=s-n;o!==0&&((this.shouldAdjustScrollPositionOnItemSizeChange!==void 0?this.shouldAdjustScrollPositionOnItemSizeChange(t,o,this):this.scrollDirection==="backward"&&t.start<this.getScrollOffset()+this.scrollAdjustments)&&this._scrollToOffset(this.getScrollOffset(),{adjustments:this.scrollAdjustments+=o,behavior:void 0}),this.pendingMeasuredCacheIndexes.push(t.index),this.itemSizeCache=new Map(this.itemSizeCache.set(t.key,s)),this.notify(!1))},this.measureElement=e=>{if(!e){this.elementsCache.forEach((s,t)=>{s.isConnected||(this.observer.unobserve(s),this.elementsCache.delete(t))});return}this._measureElement(e,void 0)},this.getVirtualItems=m(()=>[this.getVirtualIndexes(),this.getMeasurements()],(e,s)=>{let t=[];for(let n=0,o=e.length;n<o;n++){let r=e[n],a=s[r];t.push(a)}return t},{key:!1,debug:()=>this.options.debug}),this.getVirtualItemForOffset=e=>{let s=this.getMeasurements();if(s.length!==0)return R(s[F(0,s.length-1,t=>R(s[t]).start,e)])},this.getOffsetForAlignment=(e,s,t=0)=>{let n=this.getSize(),o=this.getScrollOffset();s==="auto"&&(s=e>=o+n?"end":"start"),s==="center"?e+=(t-n)/2:s==="end"&&(e-=n);let r=this.getTotalSize()-n;return Math.max(Math.min(r,e),0)},this.getOffsetForIndex=(e,s="auto")=>{e=Math.max(0,Math.min(e,this.options.count-1));let t=this.measurementsCache[e];if(!t)return;let n=this.getSize(),o=this.getScrollOffset();if(s==="auto")if(t.end>=o+n-this.options.scrollPaddingEnd)s="end";else if(t.start<=o+this.options.scrollPaddingStart)s="start";else return[o,s];let r=s==="end"?t.end+this.options.scrollPaddingEnd:t.start-this.options.scrollPaddingStart;return[this.getOffsetForAlignment(r,s,t.size),s]},this.isDynamicMode=()=>this.elementsCache.size>0,this.cancelScrollToIndex=()=>{this.scrollToIndexTimeoutId!==null&&this.targetWindow&&(this.targetWindow.clearTimeout(this.scrollToIndexTimeoutId),this.scrollToIndexTimeoutId=null)},this.scrollToOffset=(e,{align:s="start",behavior:t}={})=>{this.cancelScrollToIndex(),t==="smooth"&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size."),this._scrollToOffset(this.getOffsetForAlignment(e,s),{adjustments:void 0,behavior:t})},this.scrollToIndex=(e,{align:s="auto",behavior:t}={})=>{e=Math.max(0,Math.min(e,this.options.count-1)),this.cancelScrollToIndex(),t==="smooth"&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size.");let n=this.getOffsetForIndex(e,s);if(!n)return;let[o,r]=n;this._scrollToOffset(o,{adjustments:void 0,behavior:t}),t!=="smooth"&&this.isDynamicMode()&&this.targetWindow&&(this.scrollToIndexTimeoutId=this.targetWindow.setTimeout(()=>{if(this.scrollToIndexTimeoutId=null,this.elementsCache.has(this.options.getItemKey(e))){let c=this.getOffsetForIndex(e,r);if(!c)return;let[h]=c,d=this.getScrollOffset();I(h,d)||this.scrollToIndex(e,{align:r,behavior:t})}else this.scrollToIndex(e,{align:r,behavior:t})}))},this.scrollBy=(e,{behavior:s}={})=>{this.cancelScrollToIndex(),s==="smooth"&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size."),this._scrollToOffset(this.getScrollOffset()+e,{adjustments:void 0,behavior:s})},this.getTotalSize=()=>{var e;let s=this.getMeasurements(),t;if(s.length===0)t=this.options.paddingStart;else if(this.options.lanes===1)t=((e=s[s.length-1])==null?void 0:e.end)??0;else{let n=Array(this.options.lanes).fill(null),o=s.length-1;for(;o>=0&&n.some(r=>r===null);){let r=s[o];n[r.lane]===null&&(n[r.lane]=r.end),o--}t=Math.max(...n.filter(r=>r!==null))}return Math.max(t-this.options.scrollMargin+this.options.paddingEnd,0)},this._scrollToOffset=(e,{adjustments:s,behavior:t})=>{this.options.scrollToFn(e,{behavior:t,adjustments:s},this)},this.measure=()=>{this.itemSizeCache=new Map,this.notify(!1)},this.setOptions(i)}},F=(l,i,e,s)=>{for(;l<=i;){let t=(l+i)/2|0,n=e(t);if(n<s)l=t+1;else if(n>s)i=t-1;else return t}return l>0?l-1:0};function q({measurements:l,outerSize:i,scrollOffset:e,lanes:s}){let t=l.length-1,n=a=>l[a].start;if(l.length<=s)return{startIndex:0,endIndex:t};let o=F(0,t,n,e),r=o;if(s===1)for(;r<t&&l[r].end<e+i;)r++;else if(s>1){let a=Array(s).fill(0);for(;r<t&&a.some(h=>h<e+i);){let h=l[r];a[h.lane]=h.end,r++}let c=Array(s).fill(e+i);for(;o>=0&&c.some(h=>h>=e);){let h=l[o];c[h.lane]=h.start,o--}o=Math.max(0,o-o%s),r=Math.min(t,r+(s-1-r%s))}return{startIndex:o,endIndex:r}}var T=class{opts;totalRows=0;cache=[];inFlight=new Set;v;now=()=>Date.now();debounceTimer=null;pendingOffset=null;cleanup;index;log=(...i)=>this.opts.debug&&console.log(`[Clusterize] [${new Date().toISOString()}]`,...i);constructor(i){this.opts={buffer:5,prefetchRows:i.buffer??5,cacheTTL:3e5,debounceMs:120,autoEvict:!1,debug:!1,showInitSkeletons:!0,buildIndex:!1,primaryKey:"id",...i},this.opts.buildIndex&&(this.index=new Map),this.v=new b({count:0,overscan:this.opts.buffer,estimateSize:()=>this.opts.rowHeight,getScrollElement:()=>this.opts.scrollElem,scrollToFn:_,observeElementRect:k,observeElementOffset:L,onChange:()=>this.render()}),this.v._willUpdate(),this.cleanup=this.v._didMount(),this.opts.showInitSkeletons&&this.paintInitialSkeletons(),this.log("ctor \u2192 schedule first fetch (micro-task)"),queueMicrotask(()=>{this.firstFetch()})}destroy(){this.log("destroy \u2192 destroy"),this.cleanup?.(),this.cache=[],this.inFlight.clear(),this.opts.contentElem.innerHTML=""}refresh(){this.log("refresh \u2192 refresh"),this.render()}scrollToRow(i,e=!0){this.log("scrollToRow \u2192 scrollToRow",i,e),this.v.scrollToIndex(i,{behavior:e?"smooth":"auto"})}getLoadedCount(){return this.log("getLoadedCount \u2192 getLoadedCount"),this.cache.reduce((i,e)=>i+(e?1:0),0)}update(i){this.log("update()",i);for(let e of i){if(!this.opts.buildIndex&&e.id!=null)throw new Error("Cannot use { id } in update() when buildIndex is false. Set buildIndex: true or use { index } instead.");let s=this.resolveIndex(e);s!==-1&&this.installRows(s,[e.data])}this.render()}insert(i,e=0){if(this.log("insert()",i.length,"rows at",e),e<0||e>this.totalRows)throw new Error(`Invalid insertion index ${e}. Must be between 0 and ${this.totalRows}.`);this.cache.splice(e,0,...new Array(i.length)),this.totalRows+=i.length,this.index&&(this.index.size>1e4?this.rebuildIndex():this.shiftIndex(e,i.length)),this.v.setOptions({...this.v.options,count:this.totalRows}),this.installRows(e,i),this.render()}remove(i){this.log("remove()",i);let e=i.some(n=>typeof n!="number");if(!this.opts.buildIndex&&e)throw new Error("Cannot use non-numeric keys in remove() when buildIndex is false. Set buildIndex: true or use numeric indexes only.");let s=i.map(n=>typeof n=="number"?n:this.index?.get(n)??-1),t=[...new Set(s)].filter(n=>n>=0).sort((n,o)=>o-n);for(let n of t)this.cache.splice(n,1),this.totalRows--;this.index&&this.rebuildIndex(),this.v.setOptions({...this.v.options,count:this.totalRows}),this.render()}_dump(){return{cache:this.cache,index:this.index}}paintInitialSkeletons(){this.log("paintInitialSkeletons \u2192 paintInitialSkeletons");let i=Math.ceil(this.opts.scrollElem.clientHeight/this.opts.rowHeight)+this.opts.buffer*2,e=Array.from({length:i},(s,t)=>this.opts.renderSkeletonRow(this.opts.rowHeight,t)).join("");this.opts.contentElem.innerHTML=e}async firstFetch(){this.log("firstFetch \u2192 fetchOnInit");let i=await this.opts.fetchOnInit();Array.isArray(i)?(this.totalRows=i.length,this.cache=new Array(this.totalRows),this.installRows(0,i)):(this.totalRows=i.totalRows,this.cache=new Array(this.totalRows),this.installRows(0,i.rows)),this.v.setOptions({...this.v.options,count:this.totalRows}),this.render()}render(){if(!this.totalRows)return;let i=this.v.getVirtualItems();if(!i.length)return;this.opts.scrollingProgress?.(i[0].index);let e=Math.max(0,i[0].index-this.opts.prefetchRows),s=Math.min(this.totalRows-1,i.at(-1).index+this.opts.prefetchRows),t=[],n=i[0],o=i.at(-1);n.start&&t.push(`<div style="height:${n.start}px"></div>`);for(let c=n.index;c<=o.index;c++){let h=this.cache[c];t.push(h&&this.isLive(h)?h.html:this.opts.renderSkeletonRow(this.opts.rowHeight,c))}let r=this.v.getTotalSize()-o.end;r&&t.push(`<div style="height:${r}px"></div>`),this.opts.contentElem.innerHTML=t.join(""),this.opts.contentElem.querySelectorAll("[data-index]").forEach(c=>this.v.measureElement(c));let a=null;for(let c=e;c<=s;c++){let h=this.cache[c];if(!h||!this.isLive(h)){a=c;break}}a!==null&&this.scheduleFetch(a),this.opts.autoEvict&&this.evictStale()}installRows(i,e){this.log("installRows",i,e.length);let s=this.opts.primaryKey;e.forEach((t,n)=>{let o=i+n,r={html:typeof t=="string"?t:this.renderRaw(o,t),ts:this.now(),data:t};if(this.cache[o]=r,this.index&&typeof t=="object"&&t!==null&&s in t){let a=t[s];this.index.set(a,o)}})}renderRaw(i,e){if(typeof e=="string")return e;if(!this.opts.renderRaw)throw new Error("renderRaw missing");return this.opts.renderRaw(i,e)}isLive(i){return!this.opts.autoEvict||this.now()-i.ts<this.opts.cacheTTL}evictStale(){this.log("evictStale \u2192 evictStale");let i=this.now();this.cache.forEach((e,s)=>{e&&i-e.ts>=this.opts.cacheTTL&&(this.cache[s]=void 0)})}resolveIndex(i){return i.index!=null?i.index:i.id!=null&&this.index?this.index.get(i.id)??-1:-1}shiftIndex(i,e){if(this.index)for(let[s,t]of Array.from(this.index.entries()))t>=i&&this.index.set(s,t+e)}rebuildIndex(){if(!this.index)return;this.index.clear();let i=this.opts.primaryKey;this.cache.forEach((e,s)=>{let t=e?.data;if(t&&typeof t=="object"&&t!==null&&i in t){let n=t[i];this.index.set(n,s)}})}scheduleFetch(i){this.inFlight.has(i)||this.pendingOffset===i&&this.debounceTimer||(this.debounceTimer&&clearTimeout(this.debounceTimer),this.pendingOffset=i,this.debounceTimer=setTimeout(()=>{this.log("scheduleFetch \u2192 scheduleFetch",i);let e=this.pendingOffset;this.pendingOffset=null,this.debounceTimer=null,this.inFlight.add(e),this.opts.fetchOnScroll(e).then(s=>this.installRows(e,s)).catch(s=>console.error("[Clusterize] fetch err",s)).finally(()=>{this.inFlight.delete(e),this.render()})},this.opts.debounceMs))}},x=T;function S(l){return new x(l)}S.prototype=x.prototype;var B=S;typeof window<"u"&&(window.Clusterize=S);return V(U);})(); //# sourceMappingURL=index.iife.js.map