@haxtheweb/haxcms-nodejs
Version:
HAXcms single and multisite nodejs server, api, and administration
6 lines • 13 kB
JavaScript
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
import{RangeChangedEvent as t,VisibilityChangedEvent as e,UnpinnedEvent as i}from"./events.js";import{ScrollerController as s}from"./ScrollerController.js";let l="undefined"!=typeof window?window.ResizeObserver:void 0;export function provideResizeObserver(t){l=t}export const virtualizerRef=Symbol("virtualizerRef");const n="virtualizer-sizer";let r;export class Virtualizer{constructor(t){if(this._benchmarkStart=null,this._layout=null,this._clippingAncestors=[],this._scrollSize=null,this._scrollError=null,this._childrenPos=null,this._childMeasurements=null,this._toBeMeasured=new Map,this._rangeChanged=!0,this._itemsChanged=!0,this._visibilityChanged=!0,this._scrollerController=null,this._isScroller=!1,this._sizer=null,this._hostElementRO=null,this._childrenRO=null,this._mutationObserver=null,this._scrollEventListeners=[],this._scrollEventListenerOptions={passive:!0},this._loadListener=this._childLoaded.bind(this),this._scrollIntoViewTarget=null,this._updateScrollIntoViewCoordinates=null,this._items=[],this._first=-1,this._last=-1,this._firstVisible=-1,this._lastVisible=-1,this._scheduled=new WeakSet,this._measureCallback=null,this._measureChildOverride=null,this._layoutCompletePromise=null,this._layoutCompleteResolver=null,this._layoutCompleteRejecter=null,this._pendingLayoutComplete=null,this._layoutInitialized=null,this._connected=!1,!t)throw new Error("Virtualizer constructor requires a configuration object");if(!t.hostElement)throw new Error('Virtualizer configuration requires the "hostElement" property');this._init(t)}set items(t){Array.isArray(t)&&t!==this._items&&(this._itemsChanged=!0,this._items=t,this._schedule(this._updateLayout))}_init(t){this._isScroller=!!t.scroller,this._initHostElement(t);const e=t.layout||{};this._layoutInitialized=this._initLayout(e)}_initObservers(){this._mutationObserver=new MutationObserver(this._finishDOMUpdate.bind(this)),this._hostElementRO=new l((()=>this._hostElementSizeChanged())),this._childrenRO=new l(this._childrenSizeChanged.bind(this))}_initHostElement(t){const e=this._hostElement=t.hostElement;this._applyVirtualizerStyles(),e[virtualizerRef]=this}connected(){this._initObservers();const t=this._isScroller;this._clippingAncestors=function getClippingAncestors(t,e=!1){let i=!1;return function getElementAncestors(t,e=!1){const i=[];let s=e?t:getParentElement(t);for(;null!==s;)i.push(s),s=getParentElement(s);return i}(t,e).filter((t=>{if(i)return!1;const e=getComputedStyle(t);return i="fixed"===e.position,"visible"!==e.overflow}))}(this._hostElement,t),this._scrollerController=new s(this,this._clippingAncestors[0]),this._schedule(this._updateLayout),this._observeAndListen(),this._connected=!0}_observeAndListen(){this._mutationObserver.observe(this._hostElement,{childList:!0}),this._hostElementRO.observe(this._hostElement),this._scrollEventListeners.push(window),window.addEventListener("scroll",this,this._scrollEventListenerOptions),this._clippingAncestors.forEach((t=>{t.addEventListener("scroll",this,this._scrollEventListenerOptions),this._scrollEventListeners.push(t),this._hostElementRO.observe(t)})),this._hostElementRO.observe(this._scrollerController.element),this._children.forEach((t=>this._childrenRO.observe(t))),this._scrollEventListeners.forEach((t=>t.addEventListener("scroll",this,this._scrollEventListenerOptions)))}disconnected(){this._scrollEventListeners.forEach((t=>t.removeEventListener("scroll",this,this._scrollEventListenerOptions))),this._scrollEventListeners=[],this._clippingAncestors=[],this._scrollerController?.detach(this),this._scrollerController=null,this._mutationObserver?.disconnect(),this._mutationObserver=null,this._hostElementRO?.disconnect(),this._hostElementRO=null,this._childrenRO?.disconnect(),this._childrenRO=null,this._rejectLayoutCompletePromise("disconnected"),this._connected=!1}_applyVirtualizerStyles(){const t=this._hostElement.style;t.display=t.display||"block",t.position=t.position||"relative",t.contain=t.contain||"size layout",this._isScroller&&(t.overflow=t.overflow||"auto",t.minHeight=t.minHeight||"150px")}_getSizer(){const t=this._hostElement;if(!this._sizer){let e=t.querySelector(`[${n}]`);e||(e=document.createElement("div"),e.setAttribute(n,""),t.appendChild(e)),Object.assign(e.style,{position:"absolute",margin:"-2px 0 0 0",padding:0,visibility:"hidden",fontSize:"2px"}),e.textContent=" ",e.setAttribute(n,""),this._sizer=e}return this._sizer}async updateLayoutConfig(t){await this._layoutInitialized;const e=t.type||r;if("function"==typeof e&&this._layout instanceof e){const e={...t};return delete e.type,this._layout.config=e,!0}return!1}async _initLayout(t){let e,i;if("function"==typeof t.type){i=t.type;const s={...t};delete s.type,e=s}else e=t;void 0===i&&(r=i=(await import("./layouts/flow.js")).FlowLayout),this._layout=new i((t=>this._handleLayoutMessage(t)),e),this._layout.measureChildren&&"function"==typeof this._layout.updateItemSizes&&("function"==typeof this._layout.measureChildren&&(this._measureChildOverride=this._layout.measureChildren),this._measureCallback=this._layout.updateItemSizes.bind(this._layout)),this._layout.listenForChildLoadEvents&&this._hostElement.addEventListener("load",this._loadListener,!0),this._schedule(this._updateLayout)}startBenchmarking(){null===this._benchmarkStart&&(this._benchmarkStart=window.performance.now())}stopBenchmarking(){if(null!==this._benchmarkStart){const t=window.performance.now(),e=t-this._benchmarkStart,i=performance.getEntriesByName("uv-virtualizing","measure").filter((e=>e.startTime>=this._benchmarkStart&&e.startTime<t)).reduce(((t,e)=>t+e.duration),0);return this._benchmarkStart=null,{timeElapsed:e,virtualizationTime:i}}return null}_measureChildren(){const t={},e=this._children,i=this._measureChildOverride||this._measureChild;for(let s=0;s<e.length;s++){const l=e[s],n=this._first+s;(this._itemsChanged||this._toBeMeasured.has(l))&&(t[n]=i.call(this,l,this._items[n]))}this._childMeasurements=t,this._schedule(this._updateLayout),this._toBeMeasured.clear()}_measureChild(t){const{width:e,height:i}=t.getBoundingClientRect();return Object.assign({width:e,height:i},function getMargins(t){const e=window.getComputedStyle(t);return{marginTop:getMarginValue(e.marginTop),marginRight:getMarginValue(e.marginRight),marginBottom:getMarginValue(e.marginBottom),marginLeft:getMarginValue(e.marginLeft)}}(t))}async _schedule(t){this._scheduled.has(t)||(this._scheduled.add(t),await Promise.resolve(),this._scheduled.delete(t),t.call(this))}async _updateDOM(t){this._scrollSize=t.scrollSize,this._adjustRange(t.range),this._childrenPos=t.childPositions,this._scrollError=t.scrollError||null;const{_rangeChanged:e,_itemsChanged:i}=this;this._visibilityChanged&&(this._notifyVisibility(),this._visibilityChanged=!1),(e||i)&&(this._notifyRange(),this._rangeChanged=!1),this._finishDOMUpdate()}_finishDOMUpdate(){this._connected&&(this._children.forEach((t=>this._childrenRO.observe(t))),this._checkScrollIntoViewTarget(this._childrenPos),this._positionChildren(this._childrenPos),this._sizeHostElement(this._scrollSize),this._correctScrollError(),this._benchmarkStart&&"mark"in window.performance&&window.performance.mark("uv-end"))}_updateLayout(){this._layout&&this._connected&&(this._layout.items=this._items,this._updateView(),null!==this._childMeasurements&&(this._measureCallback&&this._measureCallback(this._childMeasurements),this._childMeasurements=null),this._layout.reflowIfNeeded(),this._benchmarkStart&&"mark"in window.performance&&window.performance.mark("uv-end"))}_handleScrollEvent(){if(this._benchmarkStart&&"mark"in window.performance){try{window.performance.measure("uv-virtualizing","uv-start","uv-end")}catch(t){console.warn("Error measuring performance data: ",t)}window.performance.mark("uv-start")}!1===this._scrollerController.correctingScrollError&&this._layout?.unpin(),this._schedule(this._updateLayout)}handleEvent(t){if("scroll"===t.type)(t.currentTarget===window||this._clippingAncestors.includes(t.currentTarget))&&this._handleScrollEvent();else console.warn("event not handled",t)}_handleLayoutMessage(t){"stateChanged"===t.type?this._updateDOM(t):"visibilityChanged"===t.type?(this._firstVisible=t.firstVisible,this._lastVisible=t.lastVisible,this._notifyVisibility()):"unpinned"===t.type&&this._hostElement.dispatchEvent(new i)}get _children(){const t=[];let e=this._hostElement.firstElementChild;for(;e;)e.hasAttribute(n)||t.push(e),e=e.nextElementSibling;return t}_updateView(){const t=this._hostElement,e=this._scrollerController?.element,i=this._layout;if(t&&e&&i){let s,l,n,r;const o=t.getBoundingClientRect();s=0,l=0,n=window.innerHeight,r=window.innerWidth;const h=this._clippingAncestors.map((t=>t.getBoundingClientRect()));h.unshift(o);for(const t of h)s=Math.max(s,t.top),l=Math.max(l,t.left),n=Math.min(n,t.bottom),r=Math.min(r,t.right);const a=e.getBoundingClientRect(),_={left:o.left-a.left,top:o.top-a.top},c={width:e.scrollWidth,height:e.scrollHeight},u=s-o.top+t.scrollTop,d=l-o.left+t.scrollLeft,m=Math.max(0,n-s),p=Math.max(0,r-l);i.viewportSize={width:p,height:m},i.viewportScroll={top:u,left:d},i.totalScrollSize=c,i.offsetWithinScroller=_}}_sizeHostElement(t){const e=82e5,i=t&&null!==t.width?Math.min(e,t.width):0,s=t&&null!==t.height?Math.min(e,t.height):0;if(this._isScroller)this._getSizer().style.transform=`translate(${i}px, ${s}px)`;else{const t=this._hostElement.style;t.minWidth=i?`${i}px`:"100%",t.minHeight=s?`${s}px`:"100%"}}_positionChildren(t){t&&t.forEach((({top:t,left:e,width:i,height:s,xOffset:l,yOffset:n},r)=>{const o=this._children[r-this._first];o&&(o.style.position="absolute",o.style.boxSizing="border-box",o.style.transform=`translate(${e}px, ${t}px)`,void 0!==i&&(o.style.width=i+"px"),void 0!==s&&(o.style.height=s+"px"),o.style.left=void 0===l?null:l+"px",o.style.top=void 0===n?null:n+"px")}))}async _adjustRange(t){const{_first:e,_last:i,_firstVisible:s,_lastVisible:l}=this;this._first=t.first,this._last=t.last,this._firstVisible=t.firstVisible,this._lastVisible=t.lastVisible,this._rangeChanged=this._rangeChanged||this._first!==e||this._last!==i,this._visibilityChanged=this._visibilityChanged||this._firstVisible!==s||this._lastVisible!==l}_correctScrollError(){if(this._scrollError){const{scrollTop:t,scrollLeft:e}=this._scrollerController,{top:i,left:s}=this._scrollError;this._scrollError=null,this._scrollerController.correctScrollError({top:t-i,left:e-s})}}element(t){return t===1/0&&(t=this._items.length-1),void 0===this._items?.[t]?void 0:{scrollIntoView:(e={})=>this._scrollElementIntoView({...e,index:t})}}_scrollElementIntoView(t){if(t.index>=this._first&&t.index<=this._last)this._children[t.index-this._first].scrollIntoView(t);else if(t.index=Math.min(t.index,this._items.length-1),"smooth"===t.behavior){const e=this._layout.getScrollIntoViewCoordinates(t),{behavior:i}=t;this._updateScrollIntoViewCoordinates=this._scrollerController.managedScrollTo(Object.assign(e,{behavior:i}),(()=>this._layout.getScrollIntoViewCoordinates(t)),(()=>this._scrollIntoViewTarget=null)),this._scrollIntoViewTarget=t}else this._layout.pin=t}_checkScrollIntoViewTarget(t){const{index:e}=this._scrollIntoViewTarget||{};e&&t?.has(e)&&this._updateScrollIntoViewCoordinates(this._layout.getScrollIntoViewCoordinates(this._scrollIntoViewTarget))}_notifyRange(){this._hostElement.dispatchEvent(new t({first:this._first,last:this._last}))}_notifyVisibility(){this._hostElement.dispatchEvent(new e({first:this._firstVisible,last:this._lastVisible}))}get layoutComplete(){return this._layoutCompletePromise||(this._layoutCompletePromise=new Promise(((t,e)=>{this._layoutCompleteResolver=t,this._layoutCompleteRejecter=e}))),this._layoutCompletePromise}_rejectLayoutCompletePromise(t){null!==this._layoutCompleteRejecter&&this._layoutCompleteRejecter(t),this._resetLayoutCompleteState()}_scheduleLayoutComplete(){this._layoutCompletePromise&&null===this._pendingLayoutComplete&&(this._pendingLayoutComplete=requestAnimationFrame((()=>requestAnimationFrame((()=>this._resolveLayoutCompletePromise())))))}_resolveLayoutCompletePromise(){null!==this._layoutCompleteResolver&&this._layoutCompleteResolver(),this._resetLayoutCompleteState()}_resetLayoutCompleteState(){this._layoutCompletePromise=null,this._layoutCompleteResolver=null,this._layoutCompleteRejecter=null,this._pendingLayoutComplete=null}_hostElementSizeChanged(){this._schedule(this._updateLayout)}_childLoaded(){}_childrenSizeChanged(t){if(this._layout?.measureChildren){for(const e of t)this._toBeMeasured.set(e.target,e.contentRect);this._measureChildren()}this._scheduleLayoutComplete(),this._itemsChanged=!1,this._rangeChanged=!1}}function getMarginValue(t){const e=t?parseFloat(t):NaN;return Number.isNaN(e)?0:e}function getParentElement(t){if(null!==t.assignedSlot)return t.assignedSlot;if(null!==t.parentElement)return t.parentElement;const e=t.parentNode;return e&&e.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&e.host||null}