@appnest/masonry-layout
Advanced tools
Comparing version 1.0.2 to 2.0.0
@@ -1,30 +0,19 @@ | ||
export declare const MIN_HEIGHT_PRIORITY_SLACK_PX = 10; | ||
export declare const DISTRIBUTED_ATTR = "data-masonry-distributed"; | ||
export declare const DEFAULT_COLS: MasonryCols; | ||
export declare const DEFAULT_MAX_COL_WIDTH = 400; | ||
export declare const DEFAULT_GAP = 24; | ||
export declare const DEFAULT_COLS = "auto"; | ||
export declare const DEFAULT_DEBOUNCE_MS = 300; | ||
export declare const DEFAULT_GAP_PX = 24; | ||
export declare const ELEMENT_NODE_TYPE = 1; | ||
export declare type ColHeightMap = number[]; | ||
export declare type MasonryCols = number | "auto"; | ||
export declare type MasonryItemLayout = { | ||
top: number; | ||
left: number; | ||
col: number; | ||
colWidth: number; | ||
export declare type MasonryItemCachedRead = { | ||
height: number; | ||
}; | ||
/** | ||
* Returns an empty col height map. | ||
* @param colCount | ||
* Returns a number attribute from an element. | ||
* @param $elem | ||
* @param name | ||
* @param defaultValue | ||
*/ | ||
export declare function createEmptyColHeightMap(colCount: number): ColHeightMap; | ||
export declare function getNumberAttribute<T>($elem: HTMLElement, name: string, defaultValue: T): number | T; | ||
/** | ||
* Returns the width of a col. | ||
* The width of the column will be the total width of the element divided by the amount | ||
* of columns, subtracted with the total amount of gap. | ||
* @param totalWidth | ||
* @param gap | ||
* @param colCount | ||
*/ | ||
export declare function getColWidth(totalWidth: number, gap: number, colCount: number): number; | ||
/** | ||
* Returns the amount of cols that the masonry grid should have. | ||
@@ -37,20 +26,2 @@ * @param totalWidth | ||
/** | ||
* Sets the height of the component to the height of the tallest col. | ||
* @param colHeightMap | ||
*/ | ||
export declare function tallestColHeight(colHeightMap: ColHeightMap): number; | ||
/** | ||
* Computes the position of an item. | ||
* @param i | ||
* @param width | ||
* @param gap | ||
* @param colIndex | ||
* @param colCount | ||
* @param colHeightMap | ||
*/ | ||
export declare function itemPosition(i: number, width: number, gap: number, colIndex: number, colCount: number, colHeightMap: ColHeightMap): { | ||
top: number; | ||
left: number; | ||
}; | ||
/** | ||
* Debounces a function. | ||
@@ -63,26 +34,5 @@ * @param cb | ||
/** | ||
* Returns the shortest col from the col height map. When finding the shortest col we need to subtract | ||
* a small offset to account for variations in the rounding. | ||
* @param colHeightMap | ||
* Returns the index of the column with the smallest height. | ||
* @param colHeights | ||
*/ | ||
export declare function getShortestCol(colHeightMap: ColHeightMap): number; | ||
/** | ||
* Sets a boolean attribute on an element. | ||
* @param $elem | ||
* @param name | ||
* @param value | ||
*/ | ||
export declare function setBooleanAttribute($elem: HTMLElement, name: string, value: boolean): void; | ||
/** | ||
* Returns a boolean attribute from an element. | ||
* @param $elem | ||
* @param name | ||
*/ | ||
export declare function getBooleanAttribute($elem: HTMLElement, name: string): boolean; | ||
/** | ||
* Returns a number attribute from an element. | ||
* @param $elem | ||
* @param name | ||
* @param defaultValue | ||
*/ | ||
export declare function getNumberAttribute<T>($elem: HTMLElement, name: string, defaultValue: T): number | T; | ||
export declare function findSmallestColIndex(colHeights: ColHeightMap): number; |
@@ -1,27 +0,19 @@ | ||
export const MIN_HEIGHT_PRIORITY_SLACK_PX = 10; | ||
export const DISTRIBUTED_ATTR = "data-masonry-distributed"; | ||
export const DEFAULT_MAX_COL_WIDTH = 400; | ||
export const DEFAULT_COLS = "auto"; | ||
export const DEFAULT_MAX_COL_WIDTH = 400; | ||
export const DEFAULT_GAP = 24; | ||
export const DEFAULT_DEBOUNCE_MS = 300; | ||
const DEBOUNCE_MAP = {}; | ||
export const DEFAULT_GAP_PX = 24; | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType | ||
export const ELEMENT_NODE_TYPE = 1; | ||
const DEBOUNCE_MAP = new Map(); | ||
/** | ||
* Returns an empty col height map. | ||
* @param colCount | ||
* Returns a number attribute from an element. | ||
* @param $elem | ||
* @param name | ||
* @param defaultValue | ||
*/ | ||
export function createEmptyColHeightMap(colCount) { | ||
return Object.assign([...(new Array(colCount))].map(() => 0)); | ||
export function getNumberAttribute($elem, name, defaultValue) { | ||
const value = parseFloat($elem.getAttribute(name) || ""); | ||
return isNaN(value) ? defaultValue : value; | ||
} | ||
/** | ||
* Returns the width of a col. | ||
* The width of the column will be the total width of the element divided by the amount | ||
* of columns, subtracted with the total amount of gap. | ||
* @param totalWidth | ||
* @param gap | ||
* @param colCount | ||
*/ | ||
export function getColWidth(totalWidth, gap, colCount) { | ||
return (totalWidth / colCount) - ((gap * (colCount - 1)) / colCount); | ||
} | ||
/** | ||
* Returns the amount of cols that the masonry grid should have. | ||
@@ -36,28 +28,2 @@ * @param totalWidth | ||
/** | ||
* Sets the height of the component to the height of the tallest col. | ||
* @param colHeightMap | ||
*/ | ||
export function tallestColHeight(colHeightMap) { | ||
return Object.values(colHeightMap).reduce((acc, height) => Math.max(acc, height), 0); | ||
} | ||
/** | ||
* Computes the position of an item. | ||
* @param i | ||
* @param width | ||
* @param gap | ||
* @param colIndex | ||
* @param colCount | ||
* @param colHeightMap | ||
*/ | ||
export function itemPosition(i, width, gap, colIndex, colCount, colHeightMap) { | ||
// Compute the left offset of the item. We find the left offset by first computing | ||
// the width of the columns added together with the gap before the current element. | ||
const left = (width * colIndex) + (gap * colIndex); | ||
// If the element in the first row we need to treat it different by not adding before it. | ||
const isFirstInRow = i < colCount; | ||
// The top offset will be the height of the chosen column added together with the gap. | ||
const top = (colHeightMap[colIndex] || 0) + (isFirstInRow ? 0 : gap); | ||
return { top, left }; | ||
} | ||
/** | ||
* Debounces a function. | ||
@@ -69,47 +35,22 @@ * @param cb | ||
export function debounce(cb, ms, id) { | ||
const existingTimeout = DEBOUNCE_MAP[id]; | ||
if (existingTimeout) | ||
const existingTimeout = DEBOUNCE_MAP.get(id); | ||
if (existingTimeout != null) | ||
window.clearTimeout(existingTimeout); | ||
DEBOUNCE_MAP[id] = window.setTimeout(cb, ms); | ||
DEBOUNCE_MAP.set(id, window.setTimeout(cb, ms)); | ||
} | ||
/** | ||
* Returns the shortest col from the col height map. When finding the shortest col we need to subtract | ||
* a small offset to account for variations in the rounding. | ||
* @param colHeightMap | ||
* Returns the index of the column with the smallest height. | ||
* @param colHeights | ||
*/ | ||
export function getShortestCol(colHeightMap) { | ||
return colHeightMap.map((height, col) => [col, height]).reduce((shortestColInfo, info) => shortestColInfo[1] - MIN_HEIGHT_PRIORITY_SLACK_PX <= info[1] ? shortestColInfo : info, [0, Number.POSITIVE_INFINITY])[0]; | ||
export function findSmallestColIndex(colHeights) { | ||
let smallestIndex = 0; | ||
let smallestHeight = Infinity; | ||
colHeights.forEach((height, i) => { | ||
if (height < smallestHeight) { | ||
smallestHeight = height; | ||
smallestIndex = i; | ||
} | ||
}); | ||
return smallestIndex; | ||
} | ||
/** | ||
* Sets a boolean attribute on an element. | ||
* @param $elem | ||
* @param name | ||
* @param value | ||
*/ | ||
export function setBooleanAttribute($elem, name, value) { | ||
if (value) { | ||
$elem.setAttribute(name, ""); | ||
} | ||
else { | ||
$elem.removeAttribute(name); | ||
} | ||
} | ||
/** | ||
* Returns a boolean attribute from an element. | ||
* @param $elem | ||
* @param name | ||
*/ | ||
export function getBooleanAttribute($elem, name) { | ||
return $elem.hasAttribute(name); | ||
} | ||
/** | ||
* Returns a number attribute from an element. | ||
* @param $elem | ||
* @param name | ||
* @param defaultValue | ||
*/ | ||
export function getNumberAttribute($elem, name, defaultValue) { | ||
const value = parseFloat($elem.getAttribute(name) || ""); | ||
return isNaN(value) ? defaultValue : value; | ||
} | ||
//# sourceMappingURL=masonry-helpers.js.map |
@@ -1,2 +0,11 @@ | ||
import { MasonryCols } from "./masonry-helpers"; | ||
import { MasonryItemCachedRead } from "./masonry-helpers"; | ||
declare type ResizeObserverEntries = { | ||
contentRect: { | ||
width: number; | ||
height: number; | ||
}; | ||
}[]; | ||
/** | ||
* Typings required for ShadyCSS. | ||
*/ | ||
declare global { | ||
@@ -11,11 +20,8 @@ interface Window { | ||
* @example <masonry-layout><div class="item"></div><div class="item"></div></masonry-layout> | ||
* @csspart column - Each column of the masonry layout. | ||
* @csspart column-index - The specific column at the given index (eg. column-0 would target the first column and so on)) | ||
* @slot - Items that should be distributed in the layout. | ||
* @cssprop --masonry-layout-item-transition - Transition of an item. | ||
*/ | ||
export declare class MasonryLayout extends HTMLElement { | ||
static get observedAttributes(): string[]; | ||
private currentColHeightMap; | ||
private ro; | ||
private cancelNextResizeEvent; | ||
private layoutCache; | ||
/** | ||
@@ -29,8 +35,8 @@ * The maximum width of each column if cols are set to auto. | ||
/** | ||
* Whether the items should be locked in their columns after the have been placed. | ||
* @attr collock | ||
* The amount of columns. | ||
* @attr cols | ||
* @param v | ||
*/ | ||
set colLock(v: boolean); | ||
get colLock(): boolean; | ||
set cols(v: string | number); | ||
get cols(): number | "auto"; | ||
/** | ||
@@ -44,16 +50,2 @@ * The gap in pixels between the columns. | ||
/** | ||
* The amount of columns. | ||
* @attr cols | ||
* @param v | ||
*/ | ||
set cols(v: MasonryCols); | ||
get cols(): MasonryCols; | ||
/** | ||
* Whether the items should have a transition. | ||
* @attr transition | ||
* @param v | ||
*/ | ||
set transition(v: boolean); | ||
get transition(): boolean; | ||
/** | ||
* The ms of debounce when the element resizes. | ||
@@ -66,44 +58,56 @@ * @attr debounce | ||
/** | ||
* The slot element. | ||
* The column elements. | ||
*/ | ||
private get $slot(); | ||
private get $columns(); | ||
private debounceId; | ||
private cachedReads; | ||
private $unsetElementsSlot; | ||
private ro; | ||
/** | ||
* All of the elements in the slot that are an Node.ELEMENT_NODE. | ||
* https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType | ||
* Attach the shadow DOM. | ||
*/ | ||
private get $items(); | ||
constructor(); | ||
/** | ||
* Attaches the listeners when the element is added to the DOM. | ||
* Hook up event listeners when added to the DOM. | ||
*/ | ||
connectedCallback(): void; | ||
/** | ||
* Removes listeners when the element is removed from the DOM. | ||
* Remove event listeners when removed from the DOM. | ||
*/ | ||
disconnectedCallback(): void; | ||
/** | ||
* Updates the layout when the observed attributes changes. | ||
* Updates the layout when one of the observed attributes changes. | ||
*/ | ||
attributeChangedCallback(): void; | ||
attributeChangedCallback(name: string): void; | ||
/** | ||
* Attaches all listeners to the element. | ||
* | ||
*/ | ||
private attachListeners; | ||
onSlotChange(): void; | ||
/** | ||
* Detaches all listeners from the element. | ||
* Each time the element resizes we need to schedule a layout | ||
* if the amount available columns has has changed. | ||
* @param entries | ||
*/ | ||
private detachListeners; | ||
onResize(entries?: ResizeObserverEntries): void; | ||
/** | ||
* Called when the element resizes and schedules a layout. | ||
* Render X amount of columns. | ||
* @param colCount | ||
*/ | ||
private didResize; | ||
renderCols(colCount: number): void; | ||
/** | ||
* Caches a read for an element. | ||
* @param $elem | ||
*/ | ||
cacheRead($elem: HTMLElement): MasonryItemCachedRead; | ||
/** | ||
* Schedules a layout. | ||
* @param ms - The debounce time | ||
* @param ms | ||
* @param invalidateCache | ||
*/ | ||
scheduleLayout(ms?: number): void; | ||
scheduleLayout(ms?: number, invalidateCache?: boolean): void; | ||
/** | ||
* Re-distributes all of the items. | ||
* Layouts the elements. | ||
* @param invalidateCache | ||
*/ | ||
layout(): void; | ||
layout(invalidateCache?: boolean): void; | ||
} | ||
@@ -115,1 +119,2 @@ declare global { | ||
} | ||
export {}; |
@@ -1,33 +0,43 @@ | ||
import { createEmptyColHeightMap, debounce, DEFAULT_COLS, DEFAULT_DEBOUNCE_MS, DEFAULT_MAX_COL_WIDTH, DEFAULT_GAP, DISTRIBUTED_ATTR, getBooleanAttribute, getColCount, getColWidth, getNumberAttribute, getShortestCol, itemPosition, setBooleanAttribute, tallestColHeight } from "./masonry-helpers"; | ||
import { debounce, DEFAULT_COLS, DEFAULT_DEBOUNCE_MS, DEFAULT_GAP_PX, DEFAULT_MAX_COL_WIDTH, ELEMENT_NODE_TYPE, findSmallestColIndex, getColCount, getNumberAttribute } from "./masonry-helpers"; | ||
/** | ||
* Template for the masonry layout. | ||
*/ | ||
const template = document.createElement("template"); | ||
template.innerHTML = ` | ||
<style> | ||
:host { | ||
display: block; | ||
position: relative; | ||
visibility: hidden; | ||
transform: translate3d(0, 0, 0); | ||
} | ||
const $template = document.createElement("template"); | ||
$template.innerHTML = ` | ||
<style> | ||
:host { | ||
display: flex; | ||
align-items: flex-start; | ||
justify-content: stretch; | ||
} | ||
::slotted(*) { | ||
position: absolute; | ||
} | ||
/* Show the items after they have been distributed */ | ||
:host([data-masonry-distributed]) { | ||
visibility: visible; | ||
} | ||
/* Apply the transition after the items have been distributed */ | ||
:host([data-masonry-distributed][transition]) ::slotted([data-masonry-distributed]) { | ||
transition: var(--masonry-layout-item-transition, transform 200ms ease); | ||
} | ||
</style> | ||
<slot id="slot"></slot> | ||
.column { | ||
width: 100%; | ||
flex: 1; | ||
display: flex; | ||
flex-direction: column; | ||
} | ||
.column:not(:last-child) { | ||
margin-right: var(--_masonry-layout-gap, ${DEFAULT_GAP_PX}px); | ||
} | ||
.column ::slotted(*) { | ||
margin-bottom: var(--_masonry-layout-gap, ${DEFAULT_GAP_PX}px); | ||
box-sizing: border-box; | ||
} | ||
/* Hide the items that has not yet found the correct slot */ | ||
#unset-items { | ||
opacity: 0; | ||
position: absolute; | ||
pointer-events: none; | ||
} | ||
</style> | ||
<div id="unset-items"> | ||
<slot></slot> | ||
</div> | ||
`; | ||
// Use polyfill only in browsers that lack native Shadow DOM. | ||
window.ShadyCSS && window.ShadyCSS.prepareTemplateStyles(template, "masonry-layout"); | ||
window.ShadyCSS && window.ShadyCSS.prepareTemplateStyles($template, "masonry-layout"); | ||
/** | ||
@@ -37,17 +47,24 @@ * Masonry layout web component. It places the slotted elements in the optimal position based | ||
* @example <masonry-layout><div class="item"></div><div class="item"></div></masonry-layout> | ||
* @csspart column - Each column of the masonry layout. | ||
* @csspart column-index - The specific column at the given index (eg. column-0 would target the first column and so on)) | ||
* @slot - Items that should be distributed in the layout. | ||
* @cssprop --masonry-layout-item-transition - Transition of an item. | ||
*/ | ||
export class MasonryLayout extends HTMLElement { | ||
/** | ||
* Attach the shadow DOM. | ||
*/ | ||
constructor() { | ||
super(); | ||
// A map containing the height for each col | ||
this.currentColHeightMap = []; | ||
// Unique debounce ID so different masonry layouts on one page won't affect eachother | ||
this.debounceId = `layout_${Math.random()}`; | ||
// Prepare a weakmap for the cache | ||
this.cachedReads = new WeakMap(); | ||
// Resize observer that layouts when necessary | ||
this.ro = undefined; | ||
this.cancelNextResizeEvent = false; | ||
this.layoutCache = new WeakMap(); | ||
// Bind the relevant functions to the element | ||
this.scheduleLayout = this.scheduleLayout.bind(this); | ||
const shadow = this.attachShadow({ mode: "open" }); | ||
shadow.appendChild($template.content.cloneNode(true)); | ||
this.onSlotChange = this.onSlotChange.bind(this); | ||
this.onResize = this.onResize.bind(this); | ||
this.layout = this.layout.bind(this); | ||
this.didResize = this.didResize.bind(this); | ||
this.$unsetElementsSlot = this.shadowRoot.querySelector("#unset-items > slot"); | ||
} | ||
@@ -57,9 +74,3 @@ // The observed attributes. | ||
static get observedAttributes() { | ||
return [ | ||
"maxcolwidth", | ||
"collock", | ||
"gap", | ||
"cols", | ||
"debounce" | ||
]; | ||
return ["maxcolwidth", "gap", "cols"]; | ||
} | ||
@@ -78,24 +89,2 @@ /** | ||
/** | ||
* Whether the items should be locked in their columns after the have been placed. | ||
* @attr collock | ||
* @param v | ||
*/ | ||
set colLock(v) { | ||
setBooleanAttribute(this, "collock", v); | ||
} | ||
get colLock() { | ||
return getBooleanAttribute(this, "collock"); | ||
} | ||
/** | ||
* The gap in pixels between the columns. | ||
* @attr gap | ||
* @param v | ||
*/ | ||
set gap(v) { | ||
this.setAttribute("gap", v.toString()); | ||
} | ||
get gap() { | ||
return getNumberAttribute(this, "gap", DEFAULT_GAP); | ||
} | ||
/** | ||
* The amount of columns. | ||
@@ -112,11 +101,11 @@ * @attr cols | ||
/** | ||
* Whether the items should have a transition. | ||
* @attr transition | ||
* The gap in pixels between the columns. | ||
* @attr gap | ||
* @param v | ||
*/ | ||
set transition(v) { | ||
setBooleanAttribute(this, "transition", v); | ||
set gap(v) { | ||
this.setAttribute("gap", v.toString()); | ||
} | ||
get transition() { | ||
return getBooleanAttribute(this, "transition"); | ||
get gap() { | ||
return getNumberAttribute(this, "gap", DEFAULT_GAP_PX); | ||
} | ||
@@ -135,136 +124,169 @@ /** | ||
/** | ||
* The slot element. | ||
* The column elements. | ||
*/ | ||
get $slot() { | ||
return this.shadowRoot.querySelector("slot"); | ||
get $columns() { | ||
return Array.from(this.shadowRoot.querySelectorAll(`.column`)); | ||
} | ||
/** | ||
* All of the elements in the slot that are an Node.ELEMENT_NODE. | ||
* https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType | ||
* Hook up event listeners when added to the DOM. | ||
*/ | ||
get $items() { | ||
return this.$slot.assignedNodes().filter(node => { | ||
return node.nodeType === 1; | ||
}); | ||
} | ||
/** | ||
* Attaches the listeners when the element is added to the DOM. | ||
*/ | ||
connectedCallback() { | ||
window.ShadyCSS && window.ShadyCSS.styleElement(this); | ||
if (!this.shadowRoot) { | ||
// Attach the shadow root | ||
const shadow = this.attachShadow({ mode: "open" }); | ||
shadow.appendChild(template.content.cloneNode(true)); | ||
this.$unsetElementsSlot.addEventListener("slotchange", this.onSlotChange); | ||
// Attach resize observer so we can relayout eachtime the size changes | ||
if ("ResizeObserver" in window) { | ||
this.ro = new ResizeObserver(this.onResize); | ||
this.ro.observe(this); | ||
} | ||
this.attachListeners(); | ||
else { | ||
window.addEventListener("resize", this.onResize); | ||
} | ||
} | ||
/** | ||
* Removes listeners when the element is removed from the DOM. | ||
* Remove event listeners when removed from the DOM. | ||
*/ | ||
disconnectedCallback() { | ||
this.detachListeners(); | ||
this.$unsetElementsSlot.removeEventListener("slotchange", this.onSlotChange); | ||
window.removeEventListener("resize", this.onResize); | ||
if (this.ro != null) { | ||
this.ro.unobserve(this); | ||
} | ||
} | ||
/** | ||
* Updates the layout when the observed attributes changes. | ||
* Updates the layout when one of the observed attributes changes. | ||
*/ | ||
attributeChangedCallback() { | ||
attributeChangedCallback(name) { | ||
switch (name) { | ||
case "gap": | ||
this.style.setProperty(`--_masonry-layout-gap`, `${this.gap}px`); | ||
break; | ||
} | ||
// Recalculate the layout | ||
this.scheduleLayout(); | ||
} | ||
/** | ||
* Attaches all listeners to the element. | ||
* | ||
*/ | ||
attachListeners() { | ||
this.$slot.addEventListener("slotchange", this.layout); | ||
if ("ResizeObserver" in window) { | ||
this.ro = new ResizeObserver(this.didResize); | ||
this.ro.observe(this); | ||
onSlotChange() { | ||
// Grab unset elements | ||
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. | ||
if ($unsetElements.length > 0) { | ||
this.layout(); | ||
} | ||
else { | ||
window.addEventListener("resize", this.didResize); | ||
} | ||
} | ||
/** | ||
* Detaches all listeners from the element. | ||
* Each time the element resizes we need to schedule a layout | ||
* if the amount available columns has has changed. | ||
* @param entries | ||
*/ | ||
detachListeners() { | ||
this.$slot.removeEventListener("slotchange", this.layout); | ||
window.removeEventListener("resize", this.didResize); | ||
if (this.ro != null) | ||
this.ro.unobserve(this); | ||
onResize(entries) { | ||
// Grab the width of the element. If it isn't provided by the resize observer entry | ||
// we compute it ourselves by looking at the offset width of the element. | ||
const { width } = entries != null && entries.length > 0 | ||
? entries[0].contentRect : { width: this.offsetWidth }; | ||
// Get the amount of columns we should have | ||
const colCount = getColCount(width, this.cols, this.maxColWidth); | ||
// 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. | ||
if (colCount !== this.$columns.length) { | ||
this.scheduleLayout(this.debounce, true); | ||
} | ||
} | ||
/** | ||
* Called when the element resizes and schedules a layout. | ||
* Render X amount of columns. | ||
* @param colCount | ||
*/ | ||
didResize() { | ||
if (this.cancelNextResizeEvent) { | ||
this.cancelNextResizeEvent = false; | ||
renderCols(colCount) { | ||
// Get the current columns | ||
const $columns = this.$columns; | ||
// If the amount of columns is correct we don't have to add new columns. | ||
if ($columns.length === colCount) { | ||
return; | ||
} | ||
this.scheduleLayout(); | ||
// Remove all of the current columns | ||
for (const $column of $columns) { | ||
$column.remove(); | ||
} | ||
// Add some new columns | ||
for (let i = 0; i < colCount; i++) { | ||
// Create a column element | ||
const $column = document.createElement(`div`); | ||
$column.classList.add(`column`); | ||
$column.setAttribute(`part`, `column column-${i}`); | ||
// Add a slot with the name set to the index of the column | ||
const $slot = document.createElement(`slot`); | ||
$slot.setAttribute(`name`, i.toString()); | ||
// Append the slot to the column an the column to the shadow root. | ||
$column.appendChild($slot); | ||
this.shadowRoot.appendChild($column); | ||
} | ||
} | ||
/** | ||
* 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 - The debounce time | ||
* @param ms | ||
* @param invalidateCache | ||
*/ | ||
scheduleLayout(ms) { | ||
debounce(this.layout, ms || this.debounce, "layout"); | ||
scheduleLayout(ms = this.debounce, invalidateCache = false) { | ||
debounce(() => this.layout(invalidateCache), ms, this.debounceId); | ||
} | ||
/** | ||
* Re-distributes all of the items. | ||
* Layouts the elements. | ||
* @param invalidateCache | ||
*/ | ||
layout() { | ||
layout(invalidateCache = false) { | ||
requestAnimationFrame(() => { | ||
const $items = this.$items; | ||
// READ: To begin with we batch the reads to avoid layout trashing. | ||
// The first get will most likely cause a reflow. | ||
const totalWidth = this.offsetWidth; | ||
const itemHeights = $items.map($item => $item.offsetHeight); | ||
// console.time("layout"); | ||
// Compute relevant values we are going to use for layouting the elements. | ||
const gap = this.gap; | ||
const colLock = this.colLock; | ||
const colCount = getColCount(totalWidth, this.cols, this.maxColWidth); | ||
const colWidth = getColWidth(totalWidth, gap, colCount); | ||
const colHeightMap = createEmptyColHeightMap(colCount); | ||
// Check whether the amount of columns has changed. | ||
// If they have changed we need to reorder everything, also if the collock is set to true! | ||
const reorderCols = colHeightMap.length !== this.currentColHeightMap.length; | ||
// Set the position for each item | ||
for (const [i, $item] of $items.entries()) { | ||
// Find the shortest col (we need to prioritize filling that one) or used the existing (locked) one | ||
const currentLayout = this.layoutCache.get($item); | ||
const col = colLock && !reorderCols && currentLayout != null ? currentLayout.col : getShortestCol(colHeightMap); | ||
// Compute the position for the item | ||
const { left, top } = itemPosition(i, colWidth, gap, col, colCount, colHeightMap); | ||
// Check if the layout has changed | ||
if (currentLayout == null || | ||
(currentLayout.colWidth !== colWidth || currentLayout.left !== left || currentLayout.top !== top || currentLayout.col !== col)) { | ||
this.layoutCache.set($item, { left, top, col, colWidth }); | ||
// WRITE: Assign the new position. | ||
Object.assign($item.style, { | ||
transform: `translate(${left}px, ${top}px)`, | ||
width: `${colWidth}px` | ||
}); | ||
// WRITE: Tell the rest of the world that this element has been distributed | ||
// But defer it to allow the transformation to be applied first | ||
if (!$item.hasAttribute(DISTRIBUTED_ATTR)) { | ||
requestAnimationFrame(() => { | ||
$item.setAttribute(DISTRIBUTED_ATTR, ""); | ||
}); | ||
} | ||
const $elements = Array.from(this.children).filter(node => node.nodeType === ELEMENT_NODE_TYPE); | ||
const colCount = getColCount(this.offsetWidth, this.cols, this.maxColWidth); | ||
// Have an array that keeps track of the highest col height. | ||
const colHeights = Array(colCount).fill(0); | ||
// Instead of interleaving reads and writes we create an array for all writes so we can batch them at once. | ||
const writes = []; | ||
// Go through all elements and figure out what column (aka slot) they should be put in. | ||
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); | ||
// Find the currently smallest column | ||
let smallestColIndex = findSmallestColIndex(colHeights); | ||
// Add the height of the item and the gap to the column heights. | ||
// It is very important we add the gap since the more elements we have, | ||
// the bigger the role the margins play when computing the actual height of the columns. | ||
colHeights[smallestColIndex] += height + gap; | ||
// Set the slot on the element to get the element to the correct column. | ||
// Only do it if the slot has actually changed. | ||
const newSlot = smallestColIndex.toString(); | ||
if ($elem.slot !== newSlot) { | ||
writes.push(() => ($elem.slot = newSlot)); | ||
} | ||
// Add the gained height to the height map | ||
colHeightMap[col] = top + itemHeights[i]; | ||
} | ||
// WRITE: Set the height of the entire component to the height of the tallest col | ||
this.style.height = `${tallestColHeight(colHeightMap)}px`; | ||
// WRITE: Tell the rest of the world that the layout has now been distributed | ||
if (!this.hasAttribute(DISTRIBUTED_ATTR)) { | ||
this.setAttribute(DISTRIBUTED_ATTR, ""); | ||
// Batch all the writes at once | ||
for (const write of writes) { | ||
write(); | ||
} | ||
// Store the new heights of the cols | ||
this.currentColHeightMap = colHeightMap; | ||
// Render the columns | ||
this.renderCols(colCount); | ||
// Commit the changes for ShadyCSS | ||
window.ShadyCSS && window.ShadyCSS.styleElement(this); | ||
// console.timeEnd("layout"); | ||
}); | ||
} | ||
} | ||
window.customElements.define("masonry-layout", MasonryLayout); | ||
customElements.define("masonry-layout", MasonryLayout); | ||
//# sourceMappingURL=masonry-layout.js.map |
{ | ||
"name": "@appnest/masonry-layout", | ||
"version": "1.0.2", | ||
"version": "2.0.0", | ||
"license": "MIT", | ||
@@ -35,3 +35,3 @@ "module": "index.js", | ||
"ncu": "ncu -u -a && npm update && npm install", | ||
"b:lib": "node pre-build.js && tsc -p tsconfig.build.json && rollup -c rollup-build.config.ts", | ||
"b:lib": "node pre-build.js && tsc -p tsconfig.build.json && rollup -c rollup-build.config.ts && npm run custom-elements-json", | ||
"b:demo:dev": "rollup -c rollup.config.ts --environment NODE_ENV:dev", | ||
@@ -47,3 +47,4 @@ "b:demo:prod": "rollup -c rollup.config.ts --environment NODE_ENV:prod", | ||
"publish:minor": "np minor --contents=dist --no-cleanup", | ||
"publish:major": "np major --contents=dist --no-cleanup" | ||
"publish:major": "np major --contents=dist --no-cleanup", | ||
"custom-elements-json": "npx wca analyze src/lib --format json --outFile dist/custom-elements.json" | ||
}, | ||
@@ -50,0 +51,0 @@ "devDependencies": { |
@@ -25,7 +25,7 @@ <h1 align="center">@appnest/masonry-layout</h1> | ||
* **Simple:** Works right out of the box (just add it to your markup) | ||
* **Lightweight:** Super small (1.5kb minified & gzipped) | ||
* **Lightweight:** Super small (1kb minified & gzipped) | ||
* **Zero dependencies:** Created using only vanilla js - no dependencies and framework agnostic! | ||
* **Customizable:** Can customize almost everything (eg. columns, transitions, gap). | ||
* **User friendly:** Automatically re-distribute items when the size of the grid changes or new elements are added | ||
* **Performant:** Efficient & fast | ||
* **Performant:** Efficient & fast (10.000 items takes 40ms for the initial layout and 10ms for the subsequent ones) | ||
@@ -48,11 +48,11 @@ | ||
Import `@appnest/masonry-layout` somewhere in your code and you're ready to go! Simply add the masonry layout to your `html` and you'll be singing and dancing from not having to build the masonry layout yourself. | ||
Import `@appnest/masonry-layout` somewhere in your code and you're ready to go! Simply add the `masonry-layout` element to your `html` and then add your elements in between the start and closing tags. | ||
```html | ||
<masonry-layout> | ||
<div class="item">1</div> | ||
<div class="item">2</div> | ||
<div class="item">3</div> | ||
<div class="item">4</div> | ||
<div class="item">5</div> | ||
<div>1</div> | ||
<div>2</div> | ||
<div>3</div> | ||
<div>4</div> | ||
<div>5</div> | ||
</masonry-layout> | ||
@@ -98,12 +98,2 @@ ``` | ||
### Lock columns | ||
The `collock` attribute locks the columns. When the columns are locked, the layout will only distribute on the y axis when elements change their sizes. The default value is `false`. | ||
```html | ||
<masonry-layout collock> | ||
... | ||
</masonry-layout> | ||
``` | ||
### Change debounce time | ||
@@ -141,3 +131,3 @@ | ||
Here's a complete overview of the component. | ||
Here's a complete overview of the element. | ||
@@ -151,10 +141,8 @@ ### masonry-layout | ||
| Property | Attribute | Type | Description | | ||
|---------------|---------------|---------------|--------------------------------------------------| | ||
| `colLock` | `collock` | `boolean` | Whether the items should be locked in their columns after the have been placed. | | ||
| `cols` | `cols` | `MasonryCols` | The amount of columns. | | ||
| `debounce` | `debounce` | `number` | The ms of debounce when the element resizes. | | ||
| `gap` | `gap` | `number` | The gap in pixels between the columns. | | ||
| `maxColWidth` | `maxcolwidth` | `number` | The maximum width of each column if cols are set to auto. | | ||
| `transition` | `transition` | `boolean` | Whether the items should have a transition. | | ||
| Property | Attribute | Type | Description | | ||
|---------------|---------------|--------------------|--------------------------------------------------| | ||
| `cols` | `cols` | `number \| "auto"` | The amount of columns. | | ||
| `debounce` | `debounce` | `number` | The ms of debounce when the element resizes. | | ||
| `gap` | `gap` | `number` | The gap in pixels between the columns. | | ||
| `maxColWidth` | `maxcolwidth` | `number` | The maximum width of each column if cols are set to auto. | | ||
@@ -167,7 +155,8 @@ #### Slots | ||
#### CSS Custom Properties | ||
#### CSS Shadow Parts | ||
| Property | Description | | ||
|------------------------------------|------------------------| | ||
| `--masonry-layout-item-transition` | Transition of an item. | | ||
| Part | Description | | ||
|----------------|--------------------------------------------------| | ||
| `column` | Each column of the masonry layout. | | ||
| `column-index` | The specific column at the given index (eg. column-0 would target the first column and so on)) | | ||
@@ -180,3 +169,3 @@ | ||
You might want to polyfill the `ResizeObserver`. The observer in the component makes sure to distribute the items whenever the size of the grid changes. If this is not polyfilled you will have to call the `layout()` function yourself when the height of the grid changes. If no `ResizeObserver` can be found on the `window` object it will instead re-distribute items when the size of the window changes. | ||
You might want to polyfill the `ResizeObserver`. The observer in the element makes sure to distribute the items whenever the size of the grid changes. If this is not polyfilled you will have to call the `layout()` function yourself when the height of the grid changes. If no `ResizeObserver` can be found on the `window` object it will instead re-distribute items when the size of the window changes. | ||
@@ -183,0 +172,0 @@ |
@@ -1,2 +0,10 @@ | ||
declare type MasonryCols = number | "auto"; | ||
declare type MasonryItemCachedRead = { | ||
height: number; | ||
}; | ||
declare type ResizeObserverEntries = { | ||
contentRect: { | ||
width: number; | ||
height: number; | ||
}; | ||
}[]; | ||
/** | ||
@@ -6,11 +14,8 @@ * Masonry layout web component. It places the slotted elements in the optimal position based | ||
* @example <masonry-layout><div class="item"></div><div class="item"></div></masonry-layout> | ||
* @csspart column - Each column of the masonry layout. | ||
* @csspart column-index - The specific column at the given index (eg. column-0 would target the first column and so on)) | ||
* @slot - Items that should be distributed in the layout. | ||
* @cssprop --masonry-layout-item-transition - Transition of an item. | ||
*/ | ||
declare class MasonryLayout extends HTMLElement { | ||
static get observedAttributes(): string[]; | ||
private currentColHeightMap; | ||
private ro; | ||
private cancelNextResizeEvent; | ||
private layoutCache; | ||
/** | ||
@@ -24,8 +29,8 @@ * The maximum width of each column if cols are set to auto. | ||
/** | ||
* Whether the items should be locked in their columns after the have been placed. | ||
* @attr collock | ||
* The amount of columns. | ||
* @attr cols | ||
* @param v | ||
*/ | ||
set colLock(v: boolean); | ||
get colLock(): boolean; | ||
set cols(v: string | number); | ||
get cols(): number | "auto"; | ||
/** | ||
@@ -39,16 +44,2 @@ * The gap in pixels between the columns. | ||
/** | ||
* The amount of columns. | ||
* @attr cols | ||
* @param v | ||
*/ | ||
set cols(v: MasonryCols); | ||
get cols(): MasonryCols; | ||
/** | ||
* Whether the items should have a transition. | ||
* @attr transition | ||
* @param v | ||
*/ | ||
set transition(v: boolean); | ||
get transition(): boolean; | ||
/** | ||
* The ms of debounce when the element resizes. | ||
@@ -61,45 +52,57 @@ * @attr debounce | ||
/** | ||
* The slot element. | ||
* The column elements. | ||
*/ | ||
private get $slot(); | ||
private get $columns(); | ||
private debounceId; | ||
private cachedReads; | ||
private $unsetElementsSlot; | ||
private ro; | ||
/** | ||
* All of the elements in the slot that are an Node.ELEMENT_NODE. | ||
* https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType | ||
* Attach the shadow DOM. | ||
*/ | ||
private get $items(); | ||
constructor(); | ||
/** | ||
* Attaches the listeners when the element is added to the DOM. | ||
* Hook up event listeners when added to the DOM. | ||
*/ | ||
connectedCallback(): void; | ||
/** | ||
* Removes listeners when the element is removed from the DOM. | ||
* Remove event listeners when removed from the DOM. | ||
*/ | ||
disconnectedCallback(): void; | ||
/** | ||
* Updates the layout when the observed attributes changes. | ||
* Updates the layout when one of the observed attributes changes. | ||
*/ | ||
attributeChangedCallback(): void; | ||
attributeChangedCallback(name: string): void; | ||
/** | ||
* Attaches all listeners to the element. | ||
* | ||
*/ | ||
private attachListeners; | ||
onSlotChange(): void; | ||
/** | ||
* Detaches all listeners from the element. | ||
* Each time the element resizes we need to schedule a layout | ||
* if the amount available columns has has changed. | ||
* @param entries | ||
*/ | ||
private detachListeners; | ||
onResize(entries?: ResizeObserverEntries): void; | ||
/** | ||
* Called when the element resizes and schedules a layout. | ||
* Render X amount of columns. | ||
* @param colCount | ||
*/ | ||
private didResize; | ||
renderCols(colCount: number): void; | ||
/** | ||
* Caches a read for an element. | ||
* @param $elem | ||
*/ | ||
cacheRead($elem: HTMLElement): MasonryItemCachedRead; | ||
/** | ||
* Schedules a layout. | ||
* @param ms - The debounce time | ||
* @param ms | ||
* @param invalidateCache | ||
*/ | ||
scheduleLayout(ms?: number): void; | ||
scheduleLayout(ms?: number, invalidateCache?: boolean): void; | ||
/** | ||
* Re-distributes all of the items. | ||
* Layouts the elements. | ||
* @param invalidateCache | ||
*/ | ||
layout(): void; | ||
layout(invalidateCache?: boolean): 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=10,i="data-masonry-distributed",s="auto",n=400,o=24,r=300,a={};function h(t,e,i,s,n,o){const r=e*s+i*s,a=t<n;return{top:(o[s]||0)+(a?0:i),left:r}}function l(t){return t.map((t,e)=>[e,t]).reduce((t,i)=>t[1]-e<=i[1]?t:i,[0,Number.POSITIVE_INFINITY])[0]}function d(t,e,i){i?t.setAttribute(e,""):t.removeAttribute(e)}function u(t,e){return t.hasAttribute(e)}function c(t,e,i){const s=parseFloat(t.getAttribute(e)||"");return isNaN(s)?i:s}const y=document.createElement("template");y.innerHTML='\n\t<style>\n\t\t:host {\n\t\t\tdisplay: block;\n\t\t\tposition: relative;\n\t\t\tvisibility: hidden;\n\t\t\ttransform: translate3d(0, 0, 0);\n\t\t}\n\n\t\t::slotted(*) {\n\t\t\tposition: absolute;\n\t\t}\n\t\t\n\t\t/* Show the items after they have been distributed */\n\t\t:host([data-masonry-distributed]) {\n\t\t\tvisibility: visible;\n\t\t}\n\t\t\n\t\t/* Apply the transition after the items have been distributed */\n\t\t:host([data-masonry-distributed][transition]) ::slotted([data-masonry-distributed]) {\n\t\t\ttransition: var(--masonry-layout-item-transition, transform 200ms ease);\n\t\t}\n\t</style>\n\t<slot id="slot"></slot>\n',window.ShadyCSS&&window.ShadyCSS.prepareTemplateStyles(y,"masonry-layout");class b extends HTMLElement{constructor(){super(),this.currentColHeightMap=[],this.ro=void 0,this.cancelNextResizeEvent=!1,this.layoutCache=new WeakMap,this.scheduleLayout=this.scheduleLayout.bind(this),this.layout=this.layout.bind(this),this.didResize=this.didResize.bind(this)}static get observedAttributes(){return["maxcolwidth","collock","gap","cols","debounce"]}set maxColWidth(t){this.setAttribute("maxcolwidth",t.toString())}get maxColWidth(){return c(this,"maxcolwidth",n)}set colLock(t){d(this,"collock",t)}get colLock(){return u(this,"collock")}set gap(t){this.setAttribute("gap",t.toString())}get gap(){return c(this,"gap",o)}set cols(t){this.setAttribute("cols",t.toString())}get cols(){return c(this,"cols",s)}set transition(t){d(this,"transition",t)}get transition(){return u(this,"transition")}set debounce(t){this.setAttribute("debounce",t.toString())}get debounce(){return c(this,"debounce",r)}get $slot(){return this.shadowRoot.querySelector("slot")}get $items(){return this.$slot.assignedNodes().filter(t=>1===t.nodeType)}connectedCallback(){if(window.ShadyCSS&&window.ShadyCSS.styleElement(this),!this.shadowRoot){this.attachShadow({mode:"open"}).appendChild(y.content.cloneNode(!0))}this.attachListeners()}disconnectedCallback(){this.detachListeners()}attributeChangedCallback(){this.scheduleLayout()}attachListeners(){this.$slot.addEventListener("slotchange",this.layout),"ResizeObserver"in window?(this.ro=new ResizeObserver(this.didResize),this.ro.observe(this)):window.addEventListener("resize",this.didResize)}detachListeners(){this.$slot.removeEventListener("slotchange",this.layout),window.removeEventListener("resize",this.didResize),null!=this.ro&&this.ro.unobserve(this)}didResize(){this.cancelNextResizeEvent?this.cancelNextResizeEvent=!1:this.scheduleLayout()}scheduleLayout(t){!function(t,e,i){const s=a[i];s&&window.clearTimeout(s),a[i]=window.setTimeout(t,e)}(this.layout,t||this.debounce,"layout")}layout(){requestAnimationFrame(()=>{const t=this.$items,e=this.offsetWidth,s=t.map(t=>t.offsetHeight),n=this.gap,o=this.colLock,r=function(t,e,i){return isNaN(e)?Math.max(1,Math.floor(t/i)):e}(e,this.cols,this.maxColWidth),a=function(t,e,i){return t/i-e*(i-1)/i}(e,n,r),d=function(t){return Object.assign([...new Array(t)].map(()=>0))}(r),u=d.length!==this.currentColHeightMap.length;for(const[e,c]of t.entries()){const t=this.layoutCache.get(c),y=o&&!u&&null!=t?t.col:l(d),{left:b,top:m}=h(e,a,n,y,r,d);null!=t&&t.colWidth===a&&t.left===b&&t.top===m&&t.col===y||(this.layoutCache.set(c,{left:b,top:m,col:y,colWidth:a}),Object.assign(c.style,{transform:`translate(${b}px, ${m}px)`,width:`${a}px`}),c.hasAttribute(i)||requestAnimationFrame(()=>{c.setAttribute(i,"")})),d[y]=m+s[e]}this.style.height=`${function(t){return Object.values(t).reduce((t,e)=>Math.max(t,e),0)}(d)}px`,this.hasAttribute(i)||this.setAttribute(i,""),this.currentColHeightMap=d})}}window.customElements.define("masonry-layout",b),t.MasonryLayout=b,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",s=300,o=24,i=1,a=new Map;function h(t,e,n){const s=parseFloat(t.getAttribute(e)||"");return isNaN(s)?n:s}function l(t,e,n){return isNaN(e)?Math.max(1,Math.floor(t/n)):e}function r(t){let e=0,n=1/0;return t.forEach((t,s)=>{t<n&&(n=t,e=s)}),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: 100%;\n flex: 1;\n display: flex;\n flex-direction: column;\n }\n\n .column:not(:last-child) {\n margin-right: var(--_masonry-layout-gap, ${o}px);\n }\n\n .column ::slotted(*) {\n margin-bottom: var(--_masonry-layout-gap, ${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(d,"masonry-layout");class c extends HTMLElement{constructor(){super(),this.debounceId=`layout_${Math.random()}`,this.cachedReads=new WeakMap,this.ro=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",o)}set debounce(t){this.setAttribute("debounce",t.toString())}get debounce(){return h(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("--_masonry-layout-gap",`${this.gap}px`)}this.scheduleLayout()}onSlotChange(){this.$unsetElementsSlot.assignedNodes().filter(t=>t.nodeType===i).length>0&&this.layout()}onResize(t){const{width:e}=null!=t&&t.length>0?t[0].contentRect:{width:this.offsetWidth};l(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)}}}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===i),s=l(this.offsetWidth,this.cols,this.maxColWidth),o=Array(s).fill(0),a=[];for(const s of n){let{height:n}=t||!this.cachedReads.has(s)?this.cacheRead(s):this.cachedReads.get(s),i=r(o);o[i]+=n+e;const h=i.toString();s.slot!==h&&a.push(()=>s.slot=h)}for(const t of a)t();this.renderCols(s),window.ShadyCSS&&window.ShadyCSS.styleElement(this)})}}customElements.define("masonry-layout",c),t.MasonryLayout=c,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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
14
43904
688
172
1