@appnest/masonry-layout
Advanced tools
Comparing version 2.0.2 to 2.0.3
@@ -10,5 +10,2 @@ export declare const DEFAULT_MAX_COL_WIDTH = 400; | ||
export declare type MasonryCols = number | "auto"; | ||
export declare type MasonryItemCachedRead = { | ||
height: number; | ||
}; | ||
/** | ||
@@ -15,0 +12,0 @@ * Returns a number attribute from an element. |
@@ -1,2 +0,1 @@ | ||
import { MasonryItemCachedRead } from "./masonry-helpers"; | ||
declare type ResizeObserverEntries = { | ||
@@ -59,5 +58,5 @@ contentRect: { | ||
private debounceId; | ||
private cachedReads; | ||
private $unsetElementsSlot; | ||
private ro; | ||
private currentRequestAnimationFrameCallback; | ||
/** | ||
@@ -95,17 +94,10 @@ * Attach the shadow DOM. | ||
/** | ||
* Caches a read for an element. | ||
* @param $elem | ||
*/ | ||
cacheRead($elem: HTMLElement): MasonryItemCachedRead; | ||
/** | ||
* Schedules a layout. | ||
* @param ms | ||
* @param invalidateCache | ||
*/ | ||
scheduleLayout(ms?: number, invalidateCache?: boolean): void; | ||
scheduleLayout(ms?: number): void; | ||
/** | ||
* Layouts the elements. | ||
* @param invalidateCache | ||
*/ | ||
layout(invalidateCache?: boolean): void; | ||
layout(): void; | ||
} | ||
@@ -112,0 +104,0 @@ declare global { |
@@ -59,6 +59,6 @@ import { COL_COUNT_CSS_VAR_NAME, debounce, DEFAULT_COLS, DEFAULT_DEBOUNCE_MS, DEFAULT_GAP_PX, DEFAULT_MAX_COL_WIDTH, ELEMENT_NODE_TYPE, findSmallestColIndex, GAP_CSS_VAR_NAME, getColCount, getNumberAttribute } from "./masonry-helpers"; | ||
this.debounceId = `layout_${Math.random()}`; | ||
// Prepare a weakmap for the cache | ||
this.cachedReads = new WeakMap(); | ||
// Resize observer that layouts when necessary | ||
this.ro = undefined; | ||
// The current request animation frame callback | ||
this.currentRequestAnimationFrameCallback = undefined; | ||
const shadow = this.attachShadow({ mode: "open" }); | ||
@@ -167,3 +167,4 @@ shadow.appendChild($template.content.cloneNode(true)); | ||
// Grab unset elements | ||
const $unsetElements = this.$unsetElementsSlot.assignedNodes().filter(node => node.nodeType === ELEMENT_NODE_TYPE); | ||
const $unsetElements = this.$unsetElementsSlot.assignedNodes() | ||
.filter(node => node.nodeType === ELEMENT_NODE_TYPE); | ||
// If there are more items not yet set layout straight awy to avoid the item being delayed in its render. | ||
@@ -187,5 +188,5 @@ if ($unsetElements.length > 0) { | ||
// Compare the amount of columns we should have to the current amount of columns. | ||
// Schedule a layout that invalidates the cache if they are no longer the same. | ||
// Schedule a layout if they are no longer the same. | ||
if (colCount !== this.$columns.length) { | ||
this.scheduleLayout(this.debounce, true); | ||
this.scheduleLayout(); | ||
} | ||
@@ -225,32 +226,23 @@ } | ||
/** | ||
* Caches a read for an element. | ||
* @param $elem | ||
*/ | ||
cacheRead($elem) { | ||
// Read properties of the element | ||
const value = { | ||
height: $elem.getBoundingClientRect().height | ||
}; | ||
// Cache the read of the element | ||
this.cachedReads.set($elem, value); | ||
return value; | ||
} | ||
/** | ||
* Schedules a layout. | ||
* @param ms | ||
* @param invalidateCache | ||
*/ | ||
scheduleLayout(ms = this.debounce, invalidateCache = false) { | ||
debounce(() => this.layout(invalidateCache), ms, this.debounceId); | ||
scheduleLayout(ms = this.debounce) { | ||
debounce(this.layout, ms, this.debounceId); | ||
} | ||
/** | ||
* Layouts the elements. | ||
* @param invalidateCache | ||
*/ | ||
layout(invalidateCache = false) { | ||
requestAnimationFrame(() => { | ||
layout() { | ||
// Cancel the current animation frame callback | ||
if (this.currentRequestAnimationFrameCallback != null) { | ||
window.cancelAnimationFrame(this.currentRequestAnimationFrameCallback); | ||
} | ||
// Layout in the next animationframe | ||
this.currentRequestAnimationFrameCallback = requestAnimationFrame(() => { | ||
// console.time("layout"); | ||
// Compute relevant values we are going to use for layouting the elements. | ||
const gap = this.gap; | ||
const $elements = Array.from(this.children).filter(node => node.nodeType === ELEMENT_NODE_TYPE); | ||
const $elements = Array.from(this.children) | ||
.filter(node => node.nodeType === ELEMENT_NODE_TYPE); | ||
const colCount = getColCount(this.offsetWidth, this.cols, this.maxColWidth); | ||
@@ -262,7 +254,6 @@ // Have an array that keeps track of the highest col height. | ||
// Go through all elements and figure out what column (aka slot) they should be put in. | ||
// We only do reads in this for loop and postpone the writes | ||
for (const $elem of $elements) { | ||
// Get the read data of the item (either, pick the cached value or cache it while reading it). | ||
let { height } = invalidateCache || !this.cachedReads.has($elem) | ||
? this.cacheRead($elem) | ||
: this.cachedReads.get($elem); | ||
// Read the height of the element | ||
const height = $elem.getBoundingClientRect().height; | ||
// Find the currently smallest column | ||
@@ -269,0 +260,0 @@ let smallestColIndex = findSmallestColIndex(colHeights); |
{ | ||
"name": "@appnest/masonry-layout", | ||
"version": "2.0.2", | ||
"version": "2.0.3", | ||
"license": "MIT", | ||
@@ -5,0 +5,0 @@ "module": "index.js", |
@@ -29,3 +29,3 @@ <h1 align="center">@appnest/masonry-layout</h1> | ||
* **User friendly:** Automatically re-distribute items when the size of the grid changes or new elements are added | ||
* **Performant:** Efficient & fast (10.000 items takes 40ms for the initial layout and 10ms for the subsequent ones) | ||
* **Performant:** Efficient & fast - Build with performance in mind | ||
@@ -32,0 +32,0 @@ |
@@ -1,4 +0,1 @@ | ||
declare type MasonryItemCachedRead = { | ||
height: number; | ||
}; | ||
declare type ResizeObserverEntries = { | ||
@@ -53,5 +50,5 @@ contentRect: { | ||
private debounceId; | ||
private cachedReads; | ||
private $unsetElementsSlot; | ||
private ro; | ||
private currentRequestAnimationFrameCallback; | ||
/** | ||
@@ -89,18 +86,11 @@ * Attach the shadow DOM. | ||
/** | ||
* Caches a read for an element. | ||
* @param $elem | ||
*/ | ||
cacheRead($elem: HTMLElement): MasonryItemCachedRead; | ||
/** | ||
* Schedules a layout. | ||
* @param ms | ||
* @param invalidateCache | ||
*/ | ||
scheduleLayout(ms?: number, invalidateCache?: boolean): void; | ||
scheduleLayout(ms?: number): void; | ||
/** | ||
* Layouts the elements. | ||
* @param invalidateCache | ||
*/ | ||
layout(invalidateCache?: boolean): void; | ||
layout(): void; | ||
} | ||
export { MasonryLayout }; |
@@ -1,1 +0,1 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self)["masonry-layout"]={})}(this,(function(t){"use strict";const e=400,n="auto",s=300,o=24,i="--_masonry-layout-col-count",h="--_masonry-layout-gap",l=1,a=new Map;function r(t,e,n){const s=parseFloat(t.getAttribute(e)||"");return isNaN(s)?n:s}function c(t,e,n){return isNaN(e)?Math.max(1,Math.floor(t/n)):e}function d(t){let e=0,n=1/0;return t.forEach((t,s)=>{t<n&&(n=t,e=s)}),e}const u=document.createElement("template");u.innerHTML=`\n <style>\n :host {\n display: flex;\n align-items: flex-start;\n justify-content: stretch;\n }\n\n .column {\n width: calc((100% / var(${i}, 1)) - var(${h}, ${o}px));\n flex: 1;\n display: flex;\n flex-direction: column;\n }\n\n .column:not(:last-child) {\n margin-right: var(${h}, ${o}px);\n }\n\n .column ::slotted(*) {\n margin-bottom: var(${h}, ${o}px);\n box-sizing: border-box;\n }\n\n /* Hide the items that has not yet found the correct slot */\n #unset-items {\n opacity: 0;\n position: absolute;\n pointer-events: none;\n }\n </style>\n <div id="unset-items">\n <slot></slot>\n </div>\n`,window.ShadyCSS&&window.ShadyCSS.prepareTemplateStyles(u,"masonry-layout");class m extends HTMLElement{constructor(){super(),this.debounceId=`layout_${Math.random()}`,this.cachedReads=new WeakMap,this.ro=void 0,this.attachShadow({mode:"open"}).appendChild(u.content.cloneNode(!0)),this.onSlotChange=this.onSlotChange.bind(this),this.onResize=this.onResize.bind(this),this.layout=this.layout.bind(this),this.$unsetElementsSlot=this.shadowRoot.querySelector("#unset-items > slot")}static get observedAttributes(){return["maxcolwidth","gap","cols"]}set maxColWidth(t){this.setAttribute("maxcolwidth",t.toString())}get maxColWidth(){return r(this,"maxcolwidth",e)}set cols(t){this.setAttribute("cols",t.toString())}get cols(){return r(this,"cols",n)}set gap(t){this.setAttribute("gap",t.toString())}get gap(){return r(this,"gap",o)}set debounce(t){this.setAttribute("debounce",t.toString())}get debounce(){return r(this,"debounce",s)}get $columns(){return Array.from(this.shadowRoot.querySelectorAll(".column"))}connectedCallback(){this.$unsetElementsSlot.addEventListener("slotchange",this.onSlotChange),"ResizeObserver"in window?(this.ro=new ResizeObserver(this.onResize),this.ro.observe(this)):window.addEventListener("resize",this.onResize)}disconnectedCallback(){this.$unsetElementsSlot.removeEventListener("slotchange",this.onSlotChange),window.removeEventListener("resize",this.onResize),null!=this.ro&&this.ro.unobserve(this)}attributeChangedCallback(t){switch(t){case"gap":this.style.setProperty(`${h}`,`${this.gap}px`)}this.scheduleLayout()}onSlotChange(){this.$unsetElementsSlot.assignedNodes().filter(t=>t.nodeType===l).length>0&&this.layout()}onResize(t){const{width:e}=null!=t&&t.length>0?t[0].contentRect:{width:this.offsetWidth};c(e,this.cols,this.maxColWidth)!==this.$columns.length&&this.scheduleLayout(this.debounce,!0)}renderCols(t){const e=this.$columns;if(e.length!==t){for(const t of e)t.remove();for(let e=0;e<t;e++){const t=document.createElement("div");t.classList.add("column"),t.setAttribute("part",`column column-${e}`);const n=document.createElement("slot");n.setAttribute("name",e.toString()),t.appendChild(n),this.shadowRoot.appendChild(t)}this.style.setProperty(`${i}`,t.toString())}}cacheRead(t){const e={height:t.getBoundingClientRect().height};return this.cachedReads.set(t,e),e}scheduleLayout(t=this.debounce,e=!1){!function(t,e,n){const s=a.get(n);null!=s&&window.clearTimeout(s),a.set(n,window.setTimeout(t,e))}(()=>this.layout(e),t,this.debounceId)}layout(t=!1){requestAnimationFrame(()=>{const e=this.gap,n=Array.from(this.children).filter(t=>t.nodeType===l),s=c(this.offsetWidth,this.cols,this.maxColWidth),o=Array(s).fill(0),i=[];for(const s of n){let{height:n}=t||!this.cachedReads.has(s)?this.cacheRead(s):this.cachedReads.get(s),h=d(o);o[h]+=n+e;const l=h.toString();s.slot!==l&&i.push(()=>s.slot=l)}for(const t of i)t();this.renderCols(s),window.ShadyCSS&&window.ShadyCSS.styleElement(this)})}}customElements.define("masonry-layout",m),t.MasonryLayout=m,Object.defineProperty(t,"__esModule",{value:!0})})); | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self)["masonry-layout"]={})}(this,(function(t){"use strict";const e=400,n="auto",o=300,s=24,i="--_masonry-layout-col-count",l="--_masonry-layout-gap",r=1,a=new Map;function h(t,e,n){const o=parseFloat(t.getAttribute(e)||"");return isNaN(o)?n:o}function c(t,e,n){return isNaN(e)?Math.max(1,Math.floor(t/n)):e}function u(t){let e=0,n=1/0;return t.forEach((t,o)=>{t<n&&(n=t,e=o)}),e}const d=document.createElement("template");d.innerHTML=`\n <style>\n :host {\n display: flex;\n align-items: flex-start;\n justify-content: stretch;\n }\n\n .column {\n width: calc((100% / var(${i}, 1)) - var(${l}, ${s}px));\n flex: 1;\n display: flex;\n flex-direction: column;\n }\n\n .column:not(:last-child) {\n margin-right: var(${l}, ${s}px);\n }\n\n .column ::slotted(*) {\n margin-bottom: var(${l}, ${s}px);\n box-sizing: border-box;\n }\n\n /* Hide the items that has not yet found the correct slot */\n #unset-items {\n opacity: 0;\n position: absolute;\n pointer-events: none;\n }\n </style>\n <div id="unset-items">\n <slot></slot>\n </div>\n`,window.ShadyCSS&&window.ShadyCSS.prepareTemplateStyles(d,"masonry-layout");class m extends HTMLElement{constructor(){super(),this.debounceId=`layout_${Math.random()}`,this.ro=void 0,this.currentRequestAnimationFrameCallback=void 0,this.attachShadow({mode:"open"}).appendChild(d.content.cloneNode(!0)),this.onSlotChange=this.onSlotChange.bind(this),this.onResize=this.onResize.bind(this),this.layout=this.layout.bind(this),this.$unsetElementsSlot=this.shadowRoot.querySelector("#unset-items > slot")}static get observedAttributes(){return["maxcolwidth","gap","cols"]}set maxColWidth(t){this.setAttribute("maxcolwidth",t.toString())}get maxColWidth(){return h(this,"maxcolwidth",e)}set cols(t){this.setAttribute("cols",t.toString())}get cols(){return h(this,"cols",n)}set gap(t){this.setAttribute("gap",t.toString())}get gap(){return h(this,"gap",s)}set debounce(t){this.setAttribute("debounce",t.toString())}get debounce(){return h(this,"debounce",o)}get $columns(){return Array.from(this.shadowRoot.querySelectorAll(".column"))}connectedCallback(){this.$unsetElementsSlot.addEventListener("slotchange",this.onSlotChange),"ResizeObserver"in window?(this.ro=new ResizeObserver(this.onResize),this.ro.observe(this)):window.addEventListener("resize",this.onResize)}disconnectedCallback(){this.$unsetElementsSlot.removeEventListener("slotchange",this.onSlotChange),window.removeEventListener("resize",this.onResize),null!=this.ro&&this.ro.unobserve(this)}attributeChangedCallback(t){switch(t){case"gap":this.style.setProperty(`${l}`,`${this.gap}px`)}this.scheduleLayout()}onSlotChange(){this.$unsetElementsSlot.assignedNodes().filter(t=>t.nodeType===r).length>0&&this.layout()}onResize(t){const{width:e}=null!=t&&t.length>0?t[0].contentRect:{width:this.offsetWidth};c(e,this.cols,this.maxColWidth)!==this.$columns.length&&this.scheduleLayout()}renderCols(t){const e=this.$columns;if(e.length!==t){for(const t of e)t.remove();for(let e=0;e<t;e++){const t=document.createElement("div");t.classList.add("column"),t.setAttribute("part",`column column-${e}`);const n=document.createElement("slot");n.setAttribute("name",e.toString()),t.appendChild(n),this.shadowRoot.appendChild(t)}this.style.setProperty(`${i}`,t.toString())}}scheduleLayout(t=this.debounce){!function(t,e,n){const o=a.get(n);null!=o&&window.clearTimeout(o),a.set(n,window.setTimeout(t,e))}(this.layout,t,this.debounceId)}layout(){null!=this.currentRequestAnimationFrameCallback&&window.cancelAnimationFrame(this.currentRequestAnimationFrameCallback),this.currentRequestAnimationFrameCallback=requestAnimationFrame(()=>{const t=this.gap,e=Array.from(this.children).filter(t=>t.nodeType===r),n=c(this.offsetWidth,this.cols,this.maxColWidth),o=Array(n).fill(0),s=[];for(const n of e){const e=n.getBoundingClientRect().height;let i=u(o);o[i]+=e+t;const l=i.toString();n.slot!==l&&s.push(()=>n.slot=l)}for(const t of s)t();this.renderCols(n),window.ShadyCSS&&window.ShadyCSS.styleElement(this)})}}customElements.define("masonry-layout",m),t.MasonryLayout=m,Object.defineProperty(t,"__esModule",{value:!0})})); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
43623
665