masonry-snap-grid-layout 1.0.16 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/esm/react.js +66 -41
- package/dist/esm/react.js.map +1 -1
- package/dist/react.d.cts +6 -16
- package/dist/react.d.ts +6 -16
- package/dist/react.js +64 -40
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ A **performant, SSR-friendly** masonry grid layout library with smooth animation
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
## ✨ What's New (v1.0.
|
|
13
|
+
## ✨ What's New (v1.0.17)
|
|
14
14
|
✅ **SSR-Ready Rendering** — On the server, items are rendered as plain HTML so your grid is SEO-friendly and instantly visible.
|
|
15
15
|
✅ **Hydration Takeover** — On the client, the library recalculates and animates the masonry layout after hydration.
|
|
16
16
|
✅ **Zero Dependencies** — Written in TypeScript, works with React and Vanilla JS.
|
package/dist/esm/react.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import {
|
|
3
3
|
useEffect,
|
|
4
4
|
useRef,
|
|
5
|
-
forwardRef
|
|
5
|
+
forwardRef,
|
|
6
|
+
useCallback
|
|
6
7
|
} from "react";
|
|
7
8
|
import ReactDOM from "react-dom/client";
|
|
8
9
|
|
|
@@ -253,7 +254,7 @@ function waitForImages(el, timeout = 1e3) {
|
|
|
253
254
|
const finish = () => {
|
|
254
255
|
if (called) return;
|
|
255
256
|
called = true;
|
|
256
|
-
|
|
257
|
+
resolve();
|
|
257
258
|
};
|
|
258
259
|
const onLoadOrError = () => {
|
|
259
260
|
remaining -= 1;
|
|
@@ -267,7 +268,7 @@ function waitForImages(el, timeout = 1e3) {
|
|
|
267
268
|
img.addEventListener("error", onLoadOrError, { once: true });
|
|
268
269
|
}
|
|
269
270
|
});
|
|
270
|
-
setTimeout(
|
|
271
|
+
setTimeout(finish, timeout);
|
|
271
272
|
});
|
|
272
273
|
}
|
|
273
274
|
var MasonrySnapGridInner = ({
|
|
@@ -280,68 +281,92 @@ var MasonrySnapGridInner = ({
|
|
|
280
281
|
const containerRef = useRef(null);
|
|
281
282
|
const masonryRef = useRef(null);
|
|
282
283
|
const rootsRef = useRef(/* @__PURE__ */ new Map());
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
284
|
+
const isMountedRef = useRef(true);
|
|
285
|
+
const latestRef = useRef(ref);
|
|
286
|
+
latestRef.current = ref;
|
|
287
|
+
const updateForwardedRef = useCallback((instance) => {
|
|
288
|
+
if (latestRef.current) {
|
|
289
|
+
if (typeof latestRef.current === "function") {
|
|
290
|
+
latestRef.current(instance);
|
|
291
|
+
} else {
|
|
292
|
+
latestRef.current.current = instance;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}, []);
|
|
296
|
+
const serverRenderedItems = /* @__PURE__ */ jsx(Fragment, { children: items.map((item, idx) => /* @__PURE__ */ jsx("div", { style: { display: "inline-block", verticalAlign: "top" }, children: renderItem(item) }, idx)) });
|
|
288
297
|
useEffect(() => {
|
|
289
|
-
|
|
298
|
+
isMountedRef.current = true;
|
|
290
299
|
const container = containerRef.current;
|
|
300
|
+
if (!container) return;
|
|
301
|
+
const serverContent = container.cloneNode(true);
|
|
291
302
|
container.innerHTML = "";
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
303
|
+
try {
|
|
304
|
+
masonryRef.current = new MasonrySnapGridLayout(container, {
|
|
305
|
+
...options,
|
|
306
|
+
items,
|
|
307
|
+
renderItem: (item) => {
|
|
308
|
+
const div = document.createElement("div");
|
|
309
|
+
div.style.willChange = "transform, height";
|
|
310
|
+
const root = ReactDOM.createRoot(div);
|
|
311
|
+
root.render(renderItem(item));
|
|
312
|
+
rootsRef.current.set(div, root);
|
|
313
|
+
return div;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
updateForwardedRef({ layout: masonryRef.current });
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.error("Masonry initialization failed:", error);
|
|
319
|
+
container.replaceWith(serverContent);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
304
322
|
let rafId = null;
|
|
305
323
|
let cancelled = false;
|
|
306
324
|
const doInitialLayout = async () => {
|
|
307
|
-
await new Promise((r) => {
|
|
308
|
-
rafId = requestAnimationFrame(() => r());
|
|
309
|
-
});
|
|
310
|
-
await waitForImages(container, 1e3);
|
|
311
|
-
if (cancelled) return;
|
|
312
325
|
try {
|
|
326
|
+
await new Promise((r) => {
|
|
327
|
+
rafId = requestAnimationFrame(() => r());
|
|
328
|
+
});
|
|
329
|
+
if (cancelled || !isMountedRef.current) return;
|
|
330
|
+
await waitForImages(container, 1e3);
|
|
331
|
+
if (cancelled || !isMountedRef.current) return;
|
|
313
332
|
masonryRef.current?.updateItems(items);
|
|
314
|
-
} catch (
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.error("Initial layout failed:", error);
|
|
315
335
|
}
|
|
316
336
|
};
|
|
317
337
|
doInitialLayout();
|
|
318
338
|
return () => {
|
|
319
|
-
|
|
339
|
+
isMountedRef.current = false;
|
|
320
340
|
cancelled = true;
|
|
321
|
-
|
|
341
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
342
|
+
rootsRef.current.forEach((root, el) => {
|
|
322
343
|
try {
|
|
323
344
|
root.unmount();
|
|
324
|
-
|
|
345
|
+
el.remove();
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.warn("Error during unmount:", error);
|
|
325
348
|
}
|
|
326
|
-
if (div.parentNode) div.remove();
|
|
327
349
|
});
|
|
328
350
|
rootsRef.current.clear();
|
|
329
351
|
try {
|
|
330
352
|
masonryRef.current?.destroy();
|
|
331
|
-
} catch (
|
|
353
|
+
} catch (error) {
|
|
354
|
+
console.warn("Error during masonry cleanup:", error);
|
|
332
355
|
}
|
|
333
356
|
masonryRef.current = null;
|
|
357
|
+
updateForwardedRef(null);
|
|
334
358
|
};
|
|
335
|
-
}, [options, renderItem]);
|
|
359
|
+
}, [options, renderItem, updateForwardedRef]);
|
|
336
360
|
useEffect(() => {
|
|
337
|
-
if (masonryRef.current)
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
}
|
|
361
|
+
if (!masonryRef.current) return;
|
|
362
|
+
const rafId = requestAnimationFrame(() => {
|
|
363
|
+
try {
|
|
364
|
+
masonryRef.current?.updateItems(items);
|
|
365
|
+
} catch (error) {
|
|
366
|
+
console.error("Items update failed:", error);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
return () => cancelAnimationFrame(rafId);
|
|
345
370
|
}, [items]);
|
|
346
371
|
return /* @__PURE__ */ jsx(
|
|
347
372
|
"div",
|
package/dist/esm/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react.tsx","../../src/MasonrySnapGridLayout.ts"],"sourcesContent":["import React, {\n useEffect,\n useRef,\n forwardRef,\n} from 'react';\nimport ReactDOM from 'react-dom/client';\nimport MasonrySnapGridLayout from './MasonrySnapGridLayout';\nimport { MasonrySnapGridLayoutOptions, MasonrySnapGridRef } from './types';\n\n/**\n * Props for the MasonrySnapGrid React wrapper.\n *\n * @template T - Type of items in the masonry grid.\n */\ninterface MasonrySnapGridProps<T>\n extends Omit<MasonrySnapGridLayoutOptions<T>, 'items' | 'renderItem'> {\n /** The data items to render into the masonry grid. */\n items: T[];\n\n /** Renders a single data item into a React node. */\n renderItem: (item: T) => React.ReactNode;\n\n /** Optional container class name. */\n className?: string;\n\n /** Optional inline styles for the container. */\n style?: React.CSSProperties;\n}\n\n/**\n * Helper: wait for all <img> inside \"el\" to load (or error) with a timeout fallback.\n * Returns a promise that resolves when all images are either loaded or the timeout hits.\n */\nfunction waitForImages(el: HTMLElement, timeout = 1000): Promise<void> {\n return new Promise((resolve) => {\n if (!el) return resolve();\n\n const images = Array.from(el.querySelectorAll('img'));\n if (images.length === 0) return resolve();\n\n let remaining = images.length;\n let called = false;\n\n const finish = () => {\n if (called) return;\n called = true;\n // small microtask delay to ensure layout has applied\n requestAnimationFrame(() => resolve());\n };\n\n const onLoadOrError = () => {\n remaining -= 1;\n if (remaining <= 0) finish();\n };\n\n images.forEach((img) => {\n if (img.complete) {\n // already finished (loaded or errored)\n onLoadOrError();\n } else {\n img.addEventListener('load', onLoadOrError, { once: true });\n img.addEventListener('error', onLoadOrError, { once: true });\n }\n });\n\n // Timeout fallback — in case images hang or take too long\n setTimeout(() => finish(), timeout);\n });\n}\n\n/**\n * React wrapper for MasonrySnapGridLayout that supports SSR-friendly rendering.\n *\n * SSR Strategy:\n * - On the server: render all items normally with React → HTML is SEO-friendly & visible without JS.\n * - On the client: after hydration, remove the static HTML and let MasonrySnapGridLayout take over.\n *\n * Fixes:\n * - Delay initial layout until after paint (rAF) so measurements are correct.\n * - Wait for images to load (with a timeout fallback) before triggering layout.\n */\nconst MasonrySnapGridInner = <T,>(\n {\n items,\n renderItem,\n className,\n style,\n ...options\n }: MasonrySnapGridProps<T>,\n ref: React.ForwardedRef<MasonrySnapGridRef>\n) => {\n /** Ref to the outer container where the masonry layout will be applied. */\n const containerRef = useRef<HTMLDivElement>(null);\n\n /** Ref to hold the underlying non-React Masonry layout instance. */\n const masonryRef = useRef<MasonrySnapGridLayout<T> | null>(null);\n\n /**\n * Stores references to all mounted React roots.\n * - Needed because we're rendering React components into DOM nodes created manually.\n * - Helps us unmount cleanly when the component unmounts.\n */\n const rootsRef = useRef<Map<HTMLElement, ReactDOM.Root>>(new Map());\n\n /**\n * Server-side / initial render:\n * We render items as plain HTML so they are visible for SSR & SEO.\n * Styling here should approximate the final look to minimize CLS.\n */\n const serverRenderedItems = (\n <>\n {items.map((item, idx) => (\n // Use inline-block to let items flow before masonry takes over.\n // Consumers can override with their own classes/styles.\n <div key={idx} style={{ display: 'inline-block', verticalAlign: 'top' }}>\n {renderItem(item)}\n </div>\n ))}\n </>\n );\n\n /**\n * Client takeover effect:\n * - Remove SSR content\n * - Initialize the Masonry instance (but delay the *first layout* until after paint/images)\n */\n useEffect(() => {\n if (!containerRef.current) return;\n\n const container = containerRef.current;\n\n // Clear any SSR static HTML so Masonry can manage DOM properly.\n container.innerHTML = '';\n\n // Create the masonry instance. We pass a renderItem factory that creates\n // DOM nodes and mounts React roots into them. The masonry implementation\n // is expected to insert these returned elements into the layout.\n masonryRef.current = new MasonrySnapGridLayout(container, {\n ...options,\n items,\n renderItem: (item) => {\n const div = document.createElement('div');\n // Optional: set a sensible default style class to avoid flash\n div.style.willChange = 'transform, height';\n const root = ReactDOM.createRoot(div);\n root.render(renderItem(item));\n rootsRef.current.set(div, root);\n return div;\n },\n });\n\n let rafId: number | null = null;\n let cancelled = false;\n\n // Wait for a paint frame, then wait for images within the container, then trigger layout.\n // This prevents the \"stacked\" initial state where items have zero measured height.\n const doInitialLayout = async () => {\n // Ensure at least one paint has happened so layout/measurements are meaningful.\n await new Promise<void>((r) => {\n rafId = requestAnimationFrame(() => r());\n });\n\n // Wait for images inside container (if any) to finish loading or timeout\n await waitForImages(container, 1000);\n\n if (cancelled) return;\n\n // Trigger initial layout. Use updateItems to be conservative and generic.\n // If your masonry has a dedicated `layout()` method, call that instead.\n try {\n masonryRef.current?.updateItems(items);\n } catch (e) {\n // Defensive: if updateItems isn't implemented or throws, ignore to avoid crash.\n // You may want to surface this in dev mode.\n // console.warn('Masonry initial layout failed', e);\n }\n };\n\n // Kick off the layout sequence\n doInitialLayout();\n\n return () => {\n // cancel RAF if pending\n if (rafId != null) cancelAnimationFrame(rafId);\n cancelled = true;\n\n // Unmount all React roots & remove their DOM nodes\n rootsRef.current.forEach((root, div) => {\n try {\n root.unmount();\n } catch (err) {\n // ignore\n }\n // remove from DOM if still there\n if (div.parentNode) div.remove();\n });\n rootsRef.current.clear();\n\n // Destroy the masonry instance\n try {\n masonryRef.current?.destroy();\n } catch (err) {\n // ignore if destroy not present or errors\n }\n masonryRef.current = null;\n };\n // NOTE: We intentionally don't include `items` in this dependency array because:\n // - this effect is the \"takeover\" after initial hydration; re-initializing masonry\n // on every items change would be expensive.\n // - updates to `items` are handled by the separate `useEffect` below.\n // We keep `options` & `renderItem` because changing these should re-init the library.\n }, [options, renderItem]);\n\n /**\n * When items change, ask the masonry instance to update.\n * This avoids a full re-initialization and is much faster.\n */\n useEffect(() => {\n if (masonryRef.current) {\n // Defensive: call updateItems inside RAF so layout happens after paint\n // and measurements are consistent.\n requestAnimationFrame(() => {\n try {\n masonryRef.current?.updateItems(items);\n } catch (e) {\n // swallow errors to avoid crashing the app\n }\n });\n }\n }, [items]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ position: 'relative', width: '100%', ...style }}\n >\n {/* Static SSR-friendly markup that will be removed during hydrate takeover */}\n {serverRenderedItems}\n </div>\n );\n};\n\n/**\n * ForwardRef wrapper so parent components can access MasonrySnapGrid methods.\n */\nconst MasonrySnapGrid = forwardRef(MasonrySnapGridInner) as <T>(\n props: MasonrySnapGridProps<T> & { ref?: React.ForwardedRef<MasonrySnapGridRef> }\n) => ReturnType<typeof MasonrySnapGridInner>;\n\nexport default MasonrySnapGrid;\n","import { MasonrySnapGridLayoutOptions } from './types';\n\nexport default class MasonrySnapGridLayout<T = any> {\n // Main container for the grid\n private readonly container: HTMLElement;\n // Normalized config options with defaults applied\n private readonly options: Required<MasonrySnapGridLayoutOptions<T>>;\n // Active DOM elements currently in the layout\n private items: HTMLElement[] = [];\n // Running height for each column (used for placement calculations)\n private columnHeights: number[] = [];\n // Resize observer to detect container width changes\n private resizeObserver: ResizeObserver | undefined;\n // Tracks a pending animation frame request for layout updates\n private rafId: number | null = null;\n // Cache last measured container width to avoid unnecessary relayouts\n private lastContainerWidth = 0;\n // Pool of DOM elements for recycling between renders (avoids costly re-creation)\n private itemPool: HTMLElement[] = [];\n // Flag to prevent operations after destruction\n private isDestroyed = false;\n\n constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions<T>) {\n if (!container) {\n throw new Error('Container element is required');\n }\n\n this.container = container;\n // Merge user-provided options with defaults\n this.options = {\n gutter: 16,\n minColWidth: 250,\n animate: true,\n transitionDuration: 400,\n classNames: {\n container: 'masonry-snap-grid-container',\n item: 'masonry-snap-grid-item',\n },\n ...options,\n };\n\n this.init();\n }\n\n /**\n * Initialize layout: applies base classes, renders initial items,\n * and sets up resize monitoring.\n */\n private init(): void {\n if (this.isDestroyed) return;\n\n this.container.classList.add(this.options.classNames.container || '');\n this.renderItems();\n this.setupResizeObserver();\n }\n\n /**\n * Renders items into the container using a pooled DOM strategy:\n * - Avoids DOM churn by reusing elements where possible\n * - Only creates new nodes when needed\n * - Removes unused pool items when shrinking\n */\n private renderItems(): void {\n if (this.isDestroyed) return;\n\n // Remove orphaned elements from the DOM\n this.items.forEach(item => {\n if (!this.options.items.some((_, i) => this.itemPool[i] === item)) {\n item.remove();\n }\n });\n\n this.items = [];\n this.columnHeights = [];\n\n // Use a fragment for batch DOM insertion (better performance)\n const fragment = document.createDocumentFragment();\n this.options.items.forEach((itemData, index) => {\n let itemElement = this.itemPool[index];\n\n if (!itemElement) {\n itemElement = document.createElement('div');\n itemElement.classList.add(this.options.classNames.item || '');\n this.itemPool[index] = itemElement;\n }\n\n // Render content via provided renderItem function\n const content = this.options.renderItem(itemData);\n if (typeof content === 'string') {\n itemElement.innerHTML = content;\n } else if (content instanceof Node) {\n itemElement.innerHTML = '';\n itemElement.appendChild(content);\n }\n\n fragment.appendChild(itemElement);\n this.items.push(itemElement);\n });\n\n // Trim excess pooled items\n while (this.itemPool.length > this.options.items.length) {\n const item = this.itemPool.pop()!;\n item.remove();\n }\n\n this.container.appendChild(fragment);\n this.updateLayout();\n }\n\n /**\n * Sets up a ResizeObserver on the container to trigger re-layout\n * when width changes — throttled to animation frames for performance.\n */\n private setupResizeObserver(): void {\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n\n this.resizeObserver = new ResizeObserver(() => {\n if (this.rafId) cancelAnimationFrame(this.rafId);\n this.rafId = requestAnimationFrame(() => {\n const newWidth = this.container.clientWidth;\n if (newWidth !== this.lastContainerWidth) {\n this.lastContainerWidth = newWidth;\n this.updateLayout();\n }\n });\n });\n\n this.resizeObserver.observe(this.container);\n }\n\n /**\n * Core layout function:\n * - Calculates number of columns based on container width & min column width\n * - Measures all items to avoid forced reflows during positioning\n * - Positions items in the shortest column to maintain balance\n */\n private updateLayout(): void {\n if (this.isDestroyed || !this.container.isConnected) return;\n\n try {\n const { gutter, minColWidth, animate, transitionDuration } = this.options;\n const containerWidth = this.container.clientWidth;\n\n // Avoid layout if container is hidden or collapsed\n if (containerWidth <= 0) {\n this.container.style.height = '0';\n return;\n }\n\n // Determine column count and width\n const columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));\n const colWidth = (containerWidth - (columns - 1) * gutter) / columns;\n\n // Reset tracking for column heights\n this.columnHeights = new Array(columns).fill(0);\n\n // Measure all items with the new column width before positioning\n const itemHeights = this.measureItems(colWidth);\n\n // Place each item in the shortest available column\n this.positionItems(colWidth, gutter, animate, transitionDuration, itemHeights);\n\n // Adjust container height to fit the tallest column\n this.setContainerHeight(gutter);\n } catch (error) {\n console.error('Masonry layout failed:', error);\n // Fallback: simple vertical stacking\n this.applyFallbackLayout();\n }\n }\n\n /**\n * Measures item heights without affecting layout:\n * - Temporarily forces block layout for accurate measurement\n * - Restores original styles after measuring\n */\n private measureItems(colWidth: number): number[] {\n return this.items.map(item => {\n const originalStyles = {\n display: item.style.display,\n visibility: item.style.visibility,\n position: item.style.position,\n width: item.style.width\n };\n\n item.style.display = 'block';\n item.style.visibility = 'hidden';\n item.style.position = 'absolute';\n item.style.width = `${colWidth}px`;\n\n const height = item.offsetHeight;\n\n Object.assign(item.style, originalStyles);\n return height;\n });\n }\n\n /**\n * Positions items column-by-column:\n * - Chooses the shortest column for each item to maintain balance\n * - Uses transform for GPU-accelerated positioning\n */\n private positionItems(\n colWidth: number,\n gutter: number,\n animate: boolean,\n transitionDuration: number,\n itemHeights: number[]\n ): void {\n this.items.forEach((item, index) => {\n const height = itemHeights[index];\n const minCol = this.findShortestColumn();\n const x = minCol * (colWidth + gutter);\n const y = this.columnHeights[minCol];\n\n item.style.width = `${colWidth}px`;\n item.style.transform = `translate3d(${x}px, ${y}px, 0)`;\n item.style.transition = animate\n ? `transform ${transitionDuration}ms ease`\n : 'none';\n item.style.willChange = 'transform';\n\n this.columnHeights[minCol] += height + gutter;\n });\n }\n\n /**\n * Sets the container height to match the tallest column\n * while subtracting trailing gutter space for a clean edge.\n */\n private setContainerHeight(gutter: number): void {\n const maxHeight = Math.max(0, ...this.columnHeights);\n const containerHeight = maxHeight > 0 ? maxHeight - gutter : 0;\n this.container.style.height = `${containerHeight}px`;\n }\n\n /**\n * Simple fallback layout in case the Masonry calculation fails:\n * stacks items vertically in one column.\n */\n private applyFallbackLayout(): void {\n let top = 0;\n this.items.forEach(item => {\n item.style.transform = `translate3d(0, ${top}px, 0)`;\n top += item.offsetHeight + this.options.gutter;\n });\n this.container.style.height = `${top - this.options.gutter}px`;\n }\n\n /**\n * Finds the column with the least accumulated height.\n */\n private findShortestColumn(): number {\n let minIndex = 0;\n let minHeight = Infinity;\n\n this.columnHeights.forEach((height, index) => {\n if (height < minHeight) {\n minHeight = height;\n minIndex = index;\n }\n });\n\n return minIndex;\n }\n\n /**\n * Public method to replace current items and trigger a full re-render.\n */\n public updateItems(newItems: T[]): void {\n if (this.isDestroyed) return;\n this.options.items = newItems;\n this.renderItems();\n }\n\n /**\n * Cleanly tears down the layout:\n * - Stops observing size changes\n * - Cancels pending animation frames\n * - Clears DOM references and resets container\n */\n public destroy(): void {\n if (this.isDestroyed) return;\n\n this.isDestroyed = true;\n\n this.resizeObserver?.disconnect();\n this.resizeObserver = undefined;\n\n if (this.rafId) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n\n this.container.innerHTML = '';\n this.container.removeAttribute('style');\n this.container.classList.remove(this.options.classNames.container || '');\n\n this.items = [];\n this.columnHeights = [];\n this.itemPool = [];\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/react.tsx","../../src/MasonrySnapGridLayout.ts"],"sourcesContent":["import React, {\n useEffect,\n useRef,\n forwardRef,\n useCallback,\n} from 'react';\nimport ReactDOM from 'react-dom/client';\nimport MasonrySnapGridLayout from './MasonrySnapGridLayout';\nimport { MasonrySnapGridLayoutOptions, MasonrySnapGridRef } from './types';\n\n/**\n * Props for the MasonrySnapGrid React wrapper.\n * Generic <T> allows layout to work with any item type.\n */\ninterface MasonrySnapGridProps<T>\n extends Omit<MasonrySnapGridLayoutOptions<T>, 'items' | 'renderItem'> {\n items: T[];\n renderItem: (item: T) => React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n}\n\n/**\n * Utility — Waits until all <img> elements inside a given container\n * have either loaded or errored, or until the timeout expires.\n * This ensures layout measurements are based on final image sizes.\n */\nfunction waitForImages(el: HTMLElement, timeout = 1000): Promise<void> {\n return new Promise((resolve) => {\n if (!el) return resolve();\n\n const images = Array.from(el.querySelectorAll('img'));\n if (images.length === 0) return resolve();\n\n let remaining = images.length;\n let called = false;\n\n const finish = () => {\n if (called) return;\n called = true;\n resolve();\n };\n\n const onLoadOrError = () => {\n remaining -= 1;\n if (remaining <= 0) finish();\n };\n\n images.forEach((img) => {\n if (img.complete) {\n onLoadOrError();\n } else {\n img.addEventListener('load', onLoadOrError, { once: true });\n img.addEventListener('error', onLoadOrError, { once: true });\n }\n });\n\n setTimeout(finish, timeout);\n });\n}\n\n/**\n * React wrapper for MasonrySnapGridLayout\n * ---------------------------------------\n * Handles SSR → CSR transition, forwards ref to parent,\n * and manages async layout initialization after image load.\n */\nconst MasonrySnapGridInner = <T,>(\n {\n items,\n renderItem,\n className,\n style,\n ...options\n }: MasonrySnapGridProps<T>,\n ref: React.ForwardedRef<MasonrySnapGridRef>\n) => {\n // DOM container where the masonry will live\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Underlying vanilla masonry instance\n const masonryRef = useRef<MasonrySnapGridLayout<T> | null>(null);\n\n // Track all React roots rendered inside masonry slots (for cleanup)\n const rootsRef = useRef<Map<HTMLElement, ReactDOM.Root>>(new Map());\n\n // Tracks component mount state to prevent async leaks\n const isMountedRef = useRef(true);\n\n // Latest ref passed from parent (keeps forwardRef stable across renders)\n const latestRef = useRef(ref);\n latestRef.current = ref;\n\n /**\n * Forward ref handling — ensures both function refs\n * and object refs from the parent receive the correct instance.\n */\n const updateForwardedRef = useCallback((instance: MasonrySnapGridRef | null) => {\n if (latestRef.current) {\n if (typeof latestRef.current === 'function') {\n latestRef.current(instance);\n } else {\n latestRef.current.current = instance;\n }\n }\n }, []);\n\n /**\n * Server-rendered placeholder items.\n * Rendered inline so that:\n * - SEO crawlers see real content\n * - No layout shift before hydration\n * - Screen readers have immediate access\n */\n const serverRenderedItems = (\n <>\n {items.map((item, idx) => (\n <div key={idx} style={{ display: 'inline-block', verticalAlign: 'top' }}>\n {renderItem(item)}\n </div>\n ))}\n </>\n );\n\n /**\n * Effect: Client takeover after hydration\n * ----------------------------------------\n * Steps:\n * 1. Clear server-rendered HTML\n * 2. Create and initialize masonry layout\n * 3. Wait for next paint + images load before first layout pass\n */\n useEffect(() => {\n isMountedRef.current = true;\n const container = containerRef.current;\n if (!container) return;\n\n // Save original SSR content in case init fails\n const serverContent = container.cloneNode(true) as HTMLElement;\n container.innerHTML = '';\n\n try {\n // Create Masonry instance with React-powered renderItem\n masonryRef.current = new MasonrySnapGridLayout(container, {\n ...options,\n items,\n renderItem: (item) => {\n const div = document.createElement('div');\n div.style.willChange = 'transform, height'; // Hint for smoother animations\n const root = ReactDOM.createRoot(div);\n root.render(renderItem(item));\n rootsRef.current.set(div, root);\n return div;\n },\n });\n\n // Expose instance to parent via forwarded ref\n updateForwardedRef({ layout: masonryRef.current });\n\n } catch (error) {\n console.error('Masonry initialization failed:', error);\n container.replaceWith(serverContent); // Fallback to SSR markup\n return;\n }\n\n let rafId: number | null = null;\n let cancelled = false;\n\n const doInitialLayout = async () => {\n try {\n // Ensure browser has painted initial DOM\n await new Promise<void>((r) => {\n rafId = requestAnimationFrame(() => r());\n });\n\n if (cancelled || !isMountedRef.current) return;\n\n // Wait for images so item heights are final\n await waitForImages(container, 1000);\n\n if (cancelled || !isMountedRef.current) return;\n\n // Trigger initial layout pass\n masonryRef.current?.updateItems(items);\n } catch (error) {\n console.error('Initial layout failed:', error);\n }\n };\n\n doInitialLayout();\n\n return () => {\n // Cleanup on unmount\n isMountedRef.current = false;\n cancelled = true;\n\n if (rafId) cancelAnimationFrame(rafId);\n\n // Unmount React roots inside masonry slots\n rootsRef.current.forEach((root, el) => {\n try {\n root.unmount();\n el.remove();\n } catch (error) {\n console.warn('Error during unmount:', error);\n }\n });\n rootsRef.current.clear();\n\n // Destroy masonry instance\n try {\n masonryRef.current?.destroy();\n } catch (error) {\n console.warn('Error during masonry cleanup:', error);\n }\n masonryRef.current = null;\n\n // Reset forwarded ref\n updateForwardedRef(null);\n };\n }, [options, renderItem, updateForwardedRef]);\n\n /**\n * Effect: Handle updates when `items` changes\n * --------------------------------------------\n * Avoids full re-init by just telling masonry to refresh layout.\n * Uses rAF to batch updates for better performance.\n */\n useEffect(() => {\n if (!masonryRef.current) return;\n\n const rafId = requestAnimationFrame(() => {\n try {\n masonryRef.current?.updateItems(items);\n } catch (error) {\n console.error('Items update failed:', error);\n }\n });\n\n return () => cancelAnimationFrame(rafId);\n }, [items]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ position: 'relative', width: '100%', ...style }}\n >\n {serverRenderedItems}\n </div>\n );\n};\n\n/**\n * ForwardRef wrapper so parent components can call layout methods.\n */\nconst MasonrySnapGrid = forwardRef(MasonrySnapGridInner) as <T>(\n props: MasonrySnapGridProps<T> & { ref?: React.ForwardedRef<MasonrySnapGridRef> }\n) => ReturnType<typeof MasonrySnapGridInner>;\n\nexport default MasonrySnapGrid;\n","import { MasonrySnapGridLayoutOptions } from './types';\n\nexport default class MasonrySnapGridLayout<T = any> {\n // Main container for the grid\n private readonly container: HTMLElement;\n // Normalized config options with defaults applied\n private readonly options: Required<MasonrySnapGridLayoutOptions<T>>;\n // Active DOM elements currently in the layout\n private items: HTMLElement[] = [];\n // Running height for each column (used for placement calculations)\n private columnHeights: number[] = [];\n // Resize observer to detect container width changes\n private resizeObserver: ResizeObserver | undefined;\n // Tracks a pending animation frame request for layout updates\n private rafId: number | null = null;\n // Cache last measured container width to avoid unnecessary relayouts\n private lastContainerWidth = 0;\n // Pool of DOM elements for recycling between renders (avoids costly re-creation)\n private itemPool: HTMLElement[] = [];\n // Flag to prevent operations after destruction\n private isDestroyed = false;\n\n constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions<T>) {\n if (!container) {\n throw new Error('Container element is required');\n }\n\n this.container = container;\n // Merge user-provided options with defaults\n this.options = {\n gutter: 16,\n minColWidth: 250,\n animate: true,\n transitionDuration: 400,\n classNames: {\n container: 'masonry-snap-grid-container',\n item: 'masonry-snap-grid-item',\n },\n ...options,\n };\n\n this.init();\n }\n\n /**\n * Initialize layout: applies base classes, renders initial items,\n * and sets up resize monitoring.\n */\n private init(): void {\n if (this.isDestroyed) return;\n\n this.container.classList.add(this.options.classNames.container || '');\n this.renderItems();\n this.setupResizeObserver();\n }\n\n /**\n * Renders items into the container using a pooled DOM strategy:\n * - Avoids DOM churn by reusing elements where possible\n * - Only creates new nodes when needed\n * - Removes unused pool items when shrinking\n */\n private renderItems(): void {\n if (this.isDestroyed) return;\n\n // Remove orphaned elements from the DOM\n this.items.forEach(item => {\n if (!this.options.items.some((_, i) => this.itemPool[i] === item)) {\n item.remove();\n }\n });\n\n this.items = [];\n this.columnHeights = [];\n\n // Use a fragment for batch DOM insertion (better performance)\n const fragment = document.createDocumentFragment();\n this.options.items.forEach((itemData, index) => {\n let itemElement = this.itemPool[index];\n\n if (!itemElement) {\n itemElement = document.createElement('div');\n itemElement.classList.add(this.options.classNames.item || '');\n this.itemPool[index] = itemElement;\n }\n\n // Render content via provided renderItem function\n const content = this.options.renderItem(itemData);\n if (typeof content === 'string') {\n itemElement.innerHTML = content;\n } else if (content instanceof Node) {\n itemElement.innerHTML = '';\n itemElement.appendChild(content);\n }\n\n fragment.appendChild(itemElement);\n this.items.push(itemElement);\n });\n\n // Trim excess pooled items\n while (this.itemPool.length > this.options.items.length) {\n const item = this.itemPool.pop()!;\n item.remove();\n }\n\n this.container.appendChild(fragment);\n this.updateLayout();\n }\n\n /**\n * Sets up a ResizeObserver on the container to trigger re-layout\n * when width changes — throttled to animation frames for performance.\n */\n private setupResizeObserver(): void {\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n\n this.resizeObserver = new ResizeObserver(() => {\n if (this.rafId) cancelAnimationFrame(this.rafId);\n this.rafId = requestAnimationFrame(() => {\n const newWidth = this.container.clientWidth;\n if (newWidth !== this.lastContainerWidth) {\n this.lastContainerWidth = newWidth;\n this.updateLayout();\n }\n });\n });\n\n this.resizeObserver.observe(this.container);\n }\n\n /**\n * Core layout function:\n * - Calculates number of columns based on container width & min column width\n * - Measures all items to avoid forced reflows during positioning\n * - Positions items in the shortest column to maintain balance\n */\n private updateLayout(): void {\n if (this.isDestroyed || !this.container.isConnected) return;\n\n try {\n const { gutter, minColWidth, animate, transitionDuration } = this.options;\n const containerWidth = this.container.clientWidth;\n\n // Avoid layout if container is hidden or collapsed\n if (containerWidth <= 0) {\n this.container.style.height = '0';\n return;\n }\n\n // Determine column count and width\n const columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));\n const colWidth = (containerWidth - (columns - 1) * gutter) / columns;\n\n // Reset tracking for column heights\n this.columnHeights = new Array(columns).fill(0);\n\n // Measure all items with the new column width before positioning\n const itemHeights = this.measureItems(colWidth);\n\n // Place each item in the shortest available column\n this.positionItems(colWidth, gutter, animate, transitionDuration, itemHeights);\n\n // Adjust container height to fit the tallest column\n this.setContainerHeight(gutter);\n } catch (error) {\n console.error('Masonry layout failed:', error);\n // Fallback: simple vertical stacking\n this.applyFallbackLayout();\n }\n }\n\n /**\n * Measures item heights without affecting layout:\n * - Temporarily forces block layout for accurate measurement\n * - Restores original styles after measuring\n */\n private measureItems(colWidth: number): number[] {\n return this.items.map(item => {\n const originalStyles = {\n display: item.style.display,\n visibility: item.style.visibility,\n position: item.style.position,\n width: item.style.width\n };\n\n item.style.display = 'block';\n item.style.visibility = 'hidden';\n item.style.position = 'absolute';\n item.style.width = `${colWidth}px`;\n\n const height = item.offsetHeight;\n\n Object.assign(item.style, originalStyles);\n return height;\n });\n }\n\n /**\n * Positions items column-by-column:\n * - Chooses the shortest column for each item to maintain balance\n * - Uses transform for GPU-accelerated positioning\n */\n private positionItems(\n colWidth: number,\n gutter: number,\n animate: boolean,\n transitionDuration: number,\n itemHeights: number[]\n ): void {\n this.items.forEach((item, index) => {\n const height = itemHeights[index];\n const minCol = this.findShortestColumn();\n const x = minCol * (colWidth + gutter);\n const y = this.columnHeights[minCol];\n\n item.style.width = `${colWidth}px`;\n item.style.transform = `translate3d(${x}px, ${y}px, 0)`;\n item.style.transition = animate\n ? `transform ${transitionDuration}ms ease`\n : 'none';\n item.style.willChange = 'transform';\n\n this.columnHeights[minCol] += height + gutter;\n });\n }\n\n /**\n * Sets the container height to match the tallest column\n * while subtracting trailing gutter space for a clean edge.\n */\n private setContainerHeight(gutter: number): void {\n const maxHeight = Math.max(0, ...this.columnHeights);\n const containerHeight = maxHeight > 0 ? maxHeight - gutter : 0;\n this.container.style.height = `${containerHeight}px`;\n }\n\n /**\n * Simple fallback layout in case the Masonry calculation fails:\n * stacks items vertically in one column.\n */\n private applyFallbackLayout(): void {\n let top = 0;\n this.items.forEach(item => {\n item.style.transform = `translate3d(0, ${top}px, 0)`;\n top += item.offsetHeight + this.options.gutter;\n });\n this.container.style.height = `${top - this.options.gutter}px`;\n }\n\n /**\n * Finds the column with the least accumulated height.\n */\n private findShortestColumn(): number {\n let minIndex = 0;\n let minHeight = Infinity;\n\n this.columnHeights.forEach((height, index) => {\n if (height < minHeight) {\n minHeight = height;\n minIndex = index;\n }\n });\n\n return minIndex;\n }\n\n /**\n * Public method to replace current items and trigger a full re-render.\n */\n public updateItems(newItems: T[]): void {\n if (this.isDestroyed) return;\n this.options.items = newItems;\n this.renderItems();\n }\n\n /**\n * Cleanly tears down the layout:\n * - Stops observing size changes\n * - Cancels pending animation frames\n * - Clears DOM references and resets container\n */\n public destroy(): void {\n if (this.isDestroyed) return;\n\n this.isDestroyed = true;\n\n this.resizeObserver?.disconnect();\n this.resizeObserver = undefined;\n\n if (this.rafId) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n\n this.container.innerHTML = '';\n this.container.removeAttribute('style');\n this.container.classList.remove(this.options.classNames.container || '');\n\n this.items = [];\n this.columnHeights = [];\n this.itemPool = [];\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/react.d.cts
CHANGED
|
@@ -4,33 +4,23 @@ import { a as MasonrySnapGridLayoutOptions, b as MasonrySnapGridRef } from './Ma
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Props for the MasonrySnapGrid React wrapper.
|
|
7
|
-
*
|
|
8
|
-
* @template T - Type of items in the masonry grid.
|
|
7
|
+
* Generic <T> allows layout to work with any item type.
|
|
9
8
|
*/
|
|
10
9
|
interface MasonrySnapGridProps<T> extends Omit<MasonrySnapGridLayoutOptions<T>, 'items' | 'renderItem'> {
|
|
11
|
-
/** The data items to render into the masonry grid. */
|
|
12
10
|
items: T[];
|
|
13
|
-
/** Renders a single data item into a React node. */
|
|
14
11
|
renderItem: (item: T) => React.ReactNode;
|
|
15
|
-
/** Optional container class name. */
|
|
16
12
|
className?: string;
|
|
17
|
-
/** Optional inline styles for the container. */
|
|
18
13
|
style?: React.CSSProperties;
|
|
19
14
|
}
|
|
20
15
|
/**
|
|
21
|
-
* React wrapper for MasonrySnapGridLayout
|
|
22
|
-
*
|
|
23
|
-
* SSR
|
|
24
|
-
*
|
|
25
|
-
* - On the client: after hydration, remove the static HTML and let MasonrySnapGridLayout take over.
|
|
26
|
-
*
|
|
27
|
-
* Fixes:
|
|
28
|
-
* - Delay initial layout until after paint (rAF) so measurements are correct.
|
|
29
|
-
* - Wait for images to load (with a timeout fallback) before triggering layout.
|
|
16
|
+
* React wrapper for MasonrySnapGridLayout
|
|
17
|
+
* ---------------------------------------
|
|
18
|
+
* Handles SSR → CSR transition, forwards ref to parent,
|
|
19
|
+
* and manages async layout initialization after image load.
|
|
30
20
|
*/
|
|
31
21
|
declare const MasonrySnapGridInner: <T>({ items, renderItem, className, style, ...options }: MasonrySnapGridProps<T>, ref: React.ForwardedRef<MasonrySnapGridRef>) => react_jsx_runtime.JSX.Element;
|
|
32
22
|
/**
|
|
33
|
-
* ForwardRef wrapper so parent components can
|
|
23
|
+
* ForwardRef wrapper so parent components can call layout methods.
|
|
34
24
|
*/
|
|
35
25
|
declare const MasonrySnapGrid: <T>(props: MasonrySnapGridProps<T> & {
|
|
36
26
|
ref?: React.ForwardedRef<MasonrySnapGridRef>;
|
package/dist/react.d.ts
CHANGED
|
@@ -4,33 +4,23 @@ import { a as MasonrySnapGridLayoutOptions, b as MasonrySnapGridRef } from './Ma
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Props for the MasonrySnapGrid React wrapper.
|
|
7
|
-
*
|
|
8
|
-
* @template T - Type of items in the masonry grid.
|
|
7
|
+
* Generic <T> allows layout to work with any item type.
|
|
9
8
|
*/
|
|
10
9
|
interface MasonrySnapGridProps<T> extends Omit<MasonrySnapGridLayoutOptions<T>, 'items' | 'renderItem'> {
|
|
11
|
-
/** The data items to render into the masonry grid. */
|
|
12
10
|
items: T[];
|
|
13
|
-
/** Renders a single data item into a React node. */
|
|
14
11
|
renderItem: (item: T) => React.ReactNode;
|
|
15
|
-
/** Optional container class name. */
|
|
16
12
|
className?: string;
|
|
17
|
-
/** Optional inline styles for the container. */
|
|
18
13
|
style?: React.CSSProperties;
|
|
19
14
|
}
|
|
20
15
|
/**
|
|
21
|
-
* React wrapper for MasonrySnapGridLayout
|
|
22
|
-
*
|
|
23
|
-
* SSR
|
|
24
|
-
*
|
|
25
|
-
* - On the client: after hydration, remove the static HTML and let MasonrySnapGridLayout take over.
|
|
26
|
-
*
|
|
27
|
-
* Fixes:
|
|
28
|
-
* - Delay initial layout until after paint (rAF) so measurements are correct.
|
|
29
|
-
* - Wait for images to load (with a timeout fallback) before triggering layout.
|
|
16
|
+
* React wrapper for MasonrySnapGridLayout
|
|
17
|
+
* ---------------------------------------
|
|
18
|
+
* Handles SSR → CSR transition, forwards ref to parent,
|
|
19
|
+
* and manages async layout initialization after image load.
|
|
30
20
|
*/
|
|
31
21
|
declare const MasonrySnapGridInner: <T>({ items, renderItem, className, style, ...options }: MasonrySnapGridProps<T>, ref: React.ForwardedRef<MasonrySnapGridRef>) => react_jsx_runtime.JSX.Element;
|
|
32
22
|
/**
|
|
33
|
-
* ForwardRef wrapper so parent components can
|
|
23
|
+
* ForwardRef wrapper so parent components can call layout methods.
|
|
34
24
|
*/
|
|
35
25
|
declare const MasonrySnapGrid: <T>(props: MasonrySnapGridProps<T> & {
|
|
36
26
|
ref?: React.ForwardedRef<MasonrySnapGridRef>;
|
package/dist/react.js
CHANGED
|
@@ -283,7 +283,7 @@ function waitForImages(el, timeout = 1e3) {
|
|
|
283
283
|
const finish = () => {
|
|
284
284
|
if (called) return;
|
|
285
285
|
called = true;
|
|
286
|
-
|
|
286
|
+
resolve();
|
|
287
287
|
};
|
|
288
288
|
const onLoadOrError = () => {
|
|
289
289
|
remaining -= 1;
|
|
@@ -297,7 +297,7 @@ function waitForImages(el, timeout = 1e3) {
|
|
|
297
297
|
img.addEventListener("error", onLoadOrError, { once: true });
|
|
298
298
|
}
|
|
299
299
|
});
|
|
300
|
-
setTimeout(
|
|
300
|
+
setTimeout(finish, timeout);
|
|
301
301
|
});
|
|
302
302
|
}
|
|
303
303
|
var MasonrySnapGridInner = ({
|
|
@@ -310,68 +310,92 @@ var MasonrySnapGridInner = ({
|
|
|
310
310
|
const containerRef = (0, import_react.useRef)(null);
|
|
311
311
|
const masonryRef = (0, import_react.useRef)(null);
|
|
312
312
|
const rootsRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
313
|
+
const isMountedRef = (0, import_react.useRef)(true);
|
|
314
|
+
const latestRef = (0, import_react.useRef)(ref);
|
|
315
|
+
latestRef.current = ref;
|
|
316
|
+
const updateForwardedRef = (0, import_react.useCallback)((instance) => {
|
|
317
|
+
if (latestRef.current) {
|
|
318
|
+
if (typeof latestRef.current === "function") {
|
|
319
|
+
latestRef.current(instance);
|
|
320
|
+
} else {
|
|
321
|
+
latestRef.current.current = instance;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}, []);
|
|
325
|
+
const serverRenderedItems = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: items.map((item, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "inline-block", verticalAlign: "top" }, children: renderItem(item) }, idx)) });
|
|
318
326
|
(0, import_react.useEffect)(() => {
|
|
319
|
-
|
|
327
|
+
isMountedRef.current = true;
|
|
320
328
|
const container = containerRef.current;
|
|
329
|
+
if (!container) return;
|
|
330
|
+
const serverContent = container.cloneNode(true);
|
|
321
331
|
container.innerHTML = "";
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
332
|
+
try {
|
|
333
|
+
masonryRef.current = new MasonrySnapGridLayout(container, {
|
|
334
|
+
...options,
|
|
335
|
+
items,
|
|
336
|
+
renderItem: (item) => {
|
|
337
|
+
const div = document.createElement("div");
|
|
338
|
+
div.style.willChange = "transform, height";
|
|
339
|
+
const root = import_client.default.createRoot(div);
|
|
340
|
+
root.render(renderItem(item));
|
|
341
|
+
rootsRef.current.set(div, root);
|
|
342
|
+
return div;
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
updateForwardedRef({ layout: masonryRef.current });
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.error("Masonry initialization failed:", error);
|
|
348
|
+
container.replaceWith(serverContent);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
334
351
|
let rafId = null;
|
|
335
352
|
let cancelled = false;
|
|
336
353
|
const doInitialLayout = async () => {
|
|
337
|
-
await new Promise((r) => {
|
|
338
|
-
rafId = requestAnimationFrame(() => r());
|
|
339
|
-
});
|
|
340
|
-
await waitForImages(container, 1e3);
|
|
341
|
-
if (cancelled) return;
|
|
342
354
|
try {
|
|
355
|
+
await new Promise((r) => {
|
|
356
|
+
rafId = requestAnimationFrame(() => r());
|
|
357
|
+
});
|
|
358
|
+
if (cancelled || !isMountedRef.current) return;
|
|
359
|
+
await waitForImages(container, 1e3);
|
|
360
|
+
if (cancelled || !isMountedRef.current) return;
|
|
343
361
|
masonryRef.current?.updateItems(items);
|
|
344
|
-
} catch (
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.error("Initial layout failed:", error);
|
|
345
364
|
}
|
|
346
365
|
};
|
|
347
366
|
doInitialLayout();
|
|
348
367
|
return () => {
|
|
349
|
-
|
|
368
|
+
isMountedRef.current = false;
|
|
350
369
|
cancelled = true;
|
|
351
|
-
|
|
370
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
371
|
+
rootsRef.current.forEach((root, el) => {
|
|
352
372
|
try {
|
|
353
373
|
root.unmount();
|
|
354
|
-
|
|
374
|
+
el.remove();
|
|
375
|
+
} catch (error) {
|
|
376
|
+
console.warn("Error during unmount:", error);
|
|
355
377
|
}
|
|
356
|
-
if (div.parentNode) div.remove();
|
|
357
378
|
});
|
|
358
379
|
rootsRef.current.clear();
|
|
359
380
|
try {
|
|
360
381
|
masonryRef.current?.destroy();
|
|
361
|
-
} catch (
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.warn("Error during masonry cleanup:", error);
|
|
362
384
|
}
|
|
363
385
|
masonryRef.current = null;
|
|
386
|
+
updateForwardedRef(null);
|
|
364
387
|
};
|
|
365
|
-
}, [options, renderItem]);
|
|
388
|
+
}, [options, renderItem, updateForwardedRef]);
|
|
366
389
|
(0, import_react.useEffect)(() => {
|
|
367
|
-
if (masonryRef.current)
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
}
|
|
390
|
+
if (!masonryRef.current) return;
|
|
391
|
+
const rafId = requestAnimationFrame(() => {
|
|
392
|
+
try {
|
|
393
|
+
masonryRef.current?.updateItems(items);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
console.error("Items update failed:", error);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
return () => cancelAnimationFrame(rafId);
|
|
375
399
|
}, [items]);
|
|
376
400
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
377
401
|
"div",
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react.tsx","../src/MasonrySnapGridLayout.ts"],"sourcesContent":["import React, {\n useEffect,\n useRef,\n forwardRef,\n} from 'react';\nimport ReactDOM from 'react-dom/client';\nimport MasonrySnapGridLayout from './MasonrySnapGridLayout';\nimport { MasonrySnapGridLayoutOptions, MasonrySnapGridRef } from './types';\n\n/**\n * Props for the MasonrySnapGrid React wrapper.\n *\n * @template T - Type of items in the masonry grid.\n */\ninterface MasonrySnapGridProps<T>\n extends Omit<MasonrySnapGridLayoutOptions<T>, 'items' | 'renderItem'> {\n /** The data items to render into the masonry grid. */\n items: T[];\n\n /** Renders a single data item into a React node. */\n renderItem: (item: T) => React.ReactNode;\n\n /** Optional container class name. */\n className?: string;\n\n /** Optional inline styles for the container. */\n style?: React.CSSProperties;\n}\n\n/**\n * Helper: wait for all <img> inside \"el\" to load (or error) with a timeout fallback.\n * Returns a promise that resolves when all images are either loaded or the timeout hits.\n */\nfunction waitForImages(el: HTMLElement, timeout = 1000): Promise<void> {\n return new Promise((resolve) => {\n if (!el) return resolve();\n\n const images = Array.from(el.querySelectorAll('img'));\n if (images.length === 0) return resolve();\n\n let remaining = images.length;\n let called = false;\n\n const finish = () => {\n if (called) return;\n called = true;\n // small microtask delay to ensure layout has applied\n requestAnimationFrame(() => resolve());\n };\n\n const onLoadOrError = () => {\n remaining -= 1;\n if (remaining <= 0) finish();\n };\n\n images.forEach((img) => {\n if (img.complete) {\n // already finished (loaded or errored)\n onLoadOrError();\n } else {\n img.addEventListener('load', onLoadOrError, { once: true });\n img.addEventListener('error', onLoadOrError, { once: true });\n }\n });\n\n // Timeout fallback — in case images hang or take too long\n setTimeout(() => finish(), timeout);\n });\n}\n\n/**\n * React wrapper for MasonrySnapGridLayout that supports SSR-friendly rendering.\n *\n * SSR Strategy:\n * - On the server: render all items normally with React → HTML is SEO-friendly & visible without JS.\n * - On the client: after hydration, remove the static HTML and let MasonrySnapGridLayout take over.\n *\n * Fixes:\n * - Delay initial layout until after paint (rAF) so measurements are correct.\n * - Wait for images to load (with a timeout fallback) before triggering layout.\n */\nconst MasonrySnapGridInner = <T,>(\n {\n items,\n renderItem,\n className,\n style,\n ...options\n }: MasonrySnapGridProps<T>,\n ref: React.ForwardedRef<MasonrySnapGridRef>\n) => {\n /** Ref to the outer container where the masonry layout will be applied. */\n const containerRef = useRef<HTMLDivElement>(null);\n\n /** Ref to hold the underlying non-React Masonry layout instance. */\n const masonryRef = useRef<MasonrySnapGridLayout<T> | null>(null);\n\n /**\n * Stores references to all mounted React roots.\n * - Needed because we're rendering React components into DOM nodes created manually.\n * - Helps us unmount cleanly when the component unmounts.\n */\n const rootsRef = useRef<Map<HTMLElement, ReactDOM.Root>>(new Map());\n\n /**\n * Server-side / initial render:\n * We render items as plain HTML so they are visible for SSR & SEO.\n * Styling here should approximate the final look to minimize CLS.\n */\n const serverRenderedItems = (\n <>\n {items.map((item, idx) => (\n // Use inline-block to let items flow before masonry takes over.\n // Consumers can override with their own classes/styles.\n <div key={idx} style={{ display: 'inline-block', verticalAlign: 'top' }}>\n {renderItem(item)}\n </div>\n ))}\n </>\n );\n\n /**\n * Client takeover effect:\n * - Remove SSR content\n * - Initialize the Masonry instance (but delay the *first layout* until after paint/images)\n */\n useEffect(() => {\n if (!containerRef.current) return;\n\n const container = containerRef.current;\n\n // Clear any SSR static HTML so Masonry can manage DOM properly.\n container.innerHTML = '';\n\n // Create the masonry instance. We pass a renderItem factory that creates\n // DOM nodes and mounts React roots into them. The masonry implementation\n // is expected to insert these returned elements into the layout.\n masonryRef.current = new MasonrySnapGridLayout(container, {\n ...options,\n items,\n renderItem: (item) => {\n const div = document.createElement('div');\n // Optional: set a sensible default style class to avoid flash\n div.style.willChange = 'transform, height';\n const root = ReactDOM.createRoot(div);\n root.render(renderItem(item));\n rootsRef.current.set(div, root);\n return div;\n },\n });\n\n let rafId: number | null = null;\n let cancelled = false;\n\n // Wait for a paint frame, then wait for images within the container, then trigger layout.\n // This prevents the \"stacked\" initial state where items have zero measured height.\n const doInitialLayout = async () => {\n // Ensure at least one paint has happened so layout/measurements are meaningful.\n await new Promise<void>((r) => {\n rafId = requestAnimationFrame(() => r());\n });\n\n // Wait for images inside container (if any) to finish loading or timeout\n await waitForImages(container, 1000);\n\n if (cancelled) return;\n\n // Trigger initial layout. Use updateItems to be conservative and generic.\n // If your masonry has a dedicated `layout()` method, call that instead.\n try {\n masonryRef.current?.updateItems(items);\n } catch (e) {\n // Defensive: if updateItems isn't implemented or throws, ignore to avoid crash.\n // You may want to surface this in dev mode.\n // console.warn('Masonry initial layout failed', e);\n }\n };\n\n // Kick off the layout sequence\n doInitialLayout();\n\n return () => {\n // cancel RAF if pending\n if (rafId != null) cancelAnimationFrame(rafId);\n cancelled = true;\n\n // Unmount all React roots & remove their DOM nodes\n rootsRef.current.forEach((root, div) => {\n try {\n root.unmount();\n } catch (err) {\n // ignore\n }\n // remove from DOM if still there\n if (div.parentNode) div.remove();\n });\n rootsRef.current.clear();\n\n // Destroy the masonry instance\n try {\n masonryRef.current?.destroy();\n } catch (err) {\n // ignore if destroy not present or errors\n }\n masonryRef.current = null;\n };\n // NOTE: We intentionally don't include `items` in this dependency array because:\n // - this effect is the \"takeover\" after initial hydration; re-initializing masonry\n // on every items change would be expensive.\n // - updates to `items` are handled by the separate `useEffect` below.\n // We keep `options` & `renderItem` because changing these should re-init the library.\n }, [options, renderItem]);\n\n /**\n * When items change, ask the masonry instance to update.\n * This avoids a full re-initialization and is much faster.\n */\n useEffect(() => {\n if (masonryRef.current) {\n // Defensive: call updateItems inside RAF so layout happens after paint\n // and measurements are consistent.\n requestAnimationFrame(() => {\n try {\n masonryRef.current?.updateItems(items);\n } catch (e) {\n // swallow errors to avoid crashing the app\n }\n });\n }\n }, [items]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ position: 'relative', width: '100%', ...style }}\n >\n {/* Static SSR-friendly markup that will be removed during hydrate takeover */}\n {serverRenderedItems}\n </div>\n );\n};\n\n/**\n * ForwardRef wrapper so parent components can access MasonrySnapGrid methods.\n */\nconst MasonrySnapGrid = forwardRef(MasonrySnapGridInner) as <T>(\n props: MasonrySnapGridProps<T> & { ref?: React.ForwardedRef<MasonrySnapGridRef> }\n) => ReturnType<typeof MasonrySnapGridInner>;\n\nexport default MasonrySnapGrid;\n","import { MasonrySnapGridLayoutOptions } from './types';\n\nexport default class MasonrySnapGridLayout<T = any> {\n // Main container for the grid\n private readonly container: HTMLElement;\n // Normalized config options with defaults applied\n private readonly options: Required<MasonrySnapGridLayoutOptions<T>>;\n // Active DOM elements currently in the layout\n private items: HTMLElement[] = [];\n // Running height for each column (used for placement calculations)\n private columnHeights: number[] = [];\n // Resize observer to detect container width changes\n private resizeObserver: ResizeObserver | undefined;\n // Tracks a pending animation frame request for layout updates\n private rafId: number | null = null;\n // Cache last measured container width to avoid unnecessary relayouts\n private lastContainerWidth = 0;\n // Pool of DOM elements for recycling between renders (avoids costly re-creation)\n private itemPool: HTMLElement[] = [];\n // Flag to prevent operations after destruction\n private isDestroyed = false;\n\n constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions<T>) {\n if (!container) {\n throw new Error('Container element is required');\n }\n\n this.container = container;\n // Merge user-provided options with defaults\n this.options = {\n gutter: 16,\n minColWidth: 250,\n animate: true,\n transitionDuration: 400,\n classNames: {\n container: 'masonry-snap-grid-container',\n item: 'masonry-snap-grid-item',\n },\n ...options,\n };\n\n this.init();\n }\n\n /**\n * Initialize layout: applies base classes, renders initial items,\n * and sets up resize monitoring.\n */\n private init(): void {\n if (this.isDestroyed) return;\n\n this.container.classList.add(this.options.classNames.container || '');\n this.renderItems();\n this.setupResizeObserver();\n }\n\n /**\n * Renders items into the container using a pooled DOM strategy:\n * - Avoids DOM churn by reusing elements where possible\n * - Only creates new nodes when needed\n * - Removes unused pool items when shrinking\n */\n private renderItems(): void {\n if (this.isDestroyed) return;\n\n // Remove orphaned elements from the DOM\n this.items.forEach(item => {\n if (!this.options.items.some((_, i) => this.itemPool[i] === item)) {\n item.remove();\n }\n });\n\n this.items = [];\n this.columnHeights = [];\n\n // Use a fragment for batch DOM insertion (better performance)\n const fragment = document.createDocumentFragment();\n this.options.items.forEach((itemData, index) => {\n let itemElement = this.itemPool[index];\n\n if (!itemElement) {\n itemElement = document.createElement('div');\n itemElement.classList.add(this.options.classNames.item || '');\n this.itemPool[index] = itemElement;\n }\n\n // Render content via provided renderItem function\n const content = this.options.renderItem(itemData);\n if (typeof content === 'string') {\n itemElement.innerHTML = content;\n } else if (content instanceof Node) {\n itemElement.innerHTML = '';\n itemElement.appendChild(content);\n }\n\n fragment.appendChild(itemElement);\n this.items.push(itemElement);\n });\n\n // Trim excess pooled items\n while (this.itemPool.length > this.options.items.length) {\n const item = this.itemPool.pop()!;\n item.remove();\n }\n\n this.container.appendChild(fragment);\n this.updateLayout();\n }\n\n /**\n * Sets up a ResizeObserver on the container to trigger re-layout\n * when width changes — throttled to animation frames for performance.\n */\n private setupResizeObserver(): void {\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n\n this.resizeObserver = new ResizeObserver(() => {\n if (this.rafId) cancelAnimationFrame(this.rafId);\n this.rafId = requestAnimationFrame(() => {\n const newWidth = this.container.clientWidth;\n if (newWidth !== this.lastContainerWidth) {\n this.lastContainerWidth = newWidth;\n this.updateLayout();\n }\n });\n });\n\n this.resizeObserver.observe(this.container);\n }\n\n /**\n * Core layout function:\n * - Calculates number of columns based on container width & min column width\n * - Measures all items to avoid forced reflows during positioning\n * - Positions items in the shortest column to maintain balance\n */\n private updateLayout(): void {\n if (this.isDestroyed || !this.container.isConnected) return;\n\n try {\n const { gutter, minColWidth, animate, transitionDuration } = this.options;\n const containerWidth = this.container.clientWidth;\n\n // Avoid layout if container is hidden or collapsed\n if (containerWidth <= 0) {\n this.container.style.height = '0';\n return;\n }\n\n // Determine column count and width\n const columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));\n const colWidth = (containerWidth - (columns - 1) * gutter) / columns;\n\n // Reset tracking for column heights\n this.columnHeights = new Array(columns).fill(0);\n\n // Measure all items with the new column width before positioning\n const itemHeights = this.measureItems(colWidth);\n\n // Place each item in the shortest available column\n this.positionItems(colWidth, gutter, animate, transitionDuration, itemHeights);\n\n // Adjust container height to fit the tallest column\n this.setContainerHeight(gutter);\n } catch (error) {\n console.error('Masonry layout failed:', error);\n // Fallback: simple vertical stacking\n this.applyFallbackLayout();\n }\n }\n\n /**\n * Measures item heights without affecting layout:\n * - Temporarily forces block layout for accurate measurement\n * - Restores original styles after measuring\n */\n private measureItems(colWidth: number): number[] {\n return this.items.map(item => {\n const originalStyles = {\n display: item.style.display,\n visibility: item.style.visibility,\n position: item.style.position,\n width: item.style.width\n };\n\n item.style.display = 'block';\n item.style.visibility = 'hidden';\n item.style.position = 'absolute';\n item.style.width = `${colWidth}px`;\n\n const height = item.offsetHeight;\n\n Object.assign(item.style, originalStyles);\n return height;\n });\n }\n\n /**\n * Positions items column-by-column:\n * - Chooses the shortest column for each item to maintain balance\n * - Uses transform for GPU-accelerated positioning\n */\n private positionItems(\n colWidth: number,\n gutter: number,\n animate: boolean,\n transitionDuration: number,\n itemHeights: number[]\n ): void {\n this.items.forEach((item, index) => {\n const height = itemHeights[index];\n const minCol = this.findShortestColumn();\n const x = minCol * (colWidth + gutter);\n const y = this.columnHeights[minCol];\n\n item.style.width = `${colWidth}px`;\n item.style.transform = `translate3d(${x}px, ${y}px, 0)`;\n item.style.transition = animate\n ? `transform ${transitionDuration}ms ease`\n : 'none';\n item.style.willChange = 'transform';\n\n this.columnHeights[minCol] += height + gutter;\n });\n }\n\n /**\n * Sets the container height to match the tallest column\n * while subtracting trailing gutter space for a clean edge.\n */\n private setContainerHeight(gutter: number): void {\n const maxHeight = Math.max(0, ...this.columnHeights);\n const containerHeight = maxHeight > 0 ? maxHeight - gutter : 0;\n this.container.style.height = `${containerHeight}px`;\n }\n\n /**\n * Simple fallback layout in case the Masonry calculation fails:\n * stacks items vertically in one column.\n */\n private applyFallbackLayout(): void {\n let top = 0;\n this.items.forEach(item => {\n item.style.transform = `translate3d(0, ${top}px, 0)`;\n top += item.offsetHeight + this.options.gutter;\n });\n this.container.style.height = `${top - this.options.gutter}px`;\n }\n\n /**\n * Finds the column with the least accumulated height.\n */\n private findShortestColumn(): number {\n let minIndex = 0;\n let minHeight = Infinity;\n\n this.columnHeights.forEach((height, index) => {\n if (height < minHeight) {\n minHeight = height;\n minIndex = index;\n }\n });\n\n return minIndex;\n }\n\n /**\n * Public method to replace current items and trigger a full re-render.\n */\n public updateItems(newItems: T[]): void {\n if (this.isDestroyed) return;\n this.options.items = newItems;\n this.renderItems();\n }\n\n /**\n * Cleanly tears down the layout:\n * - Stops observing size changes\n * - Cancels pending animation frames\n * - Clears DOM references and resets container\n */\n public destroy(): void {\n if (this.isDestroyed) return;\n\n this.isDestroyed = true;\n\n this.resizeObserver?.disconnect();\n this.resizeObserver = undefined;\n\n if (this.rafId) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n\n this.container.innerHTML = '';\n this.container.removeAttribute('style');\n this.container.classList.remove(this.options.classNames.container || '');\n\n this.items = [];\n this.columnHeights = [];\n this.itemPool = [];\n }\n}\n"],"mappings":"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","names":["ReactDOM"]}
|
|
1
|
+
{"version":3,"sources":["../src/react.tsx","../src/MasonrySnapGridLayout.ts"],"sourcesContent":["import React, {\n useEffect,\n useRef,\n forwardRef,\n useCallback,\n} from 'react';\nimport ReactDOM from 'react-dom/client';\nimport MasonrySnapGridLayout from './MasonrySnapGridLayout';\nimport { MasonrySnapGridLayoutOptions, MasonrySnapGridRef } from './types';\n\n/**\n * Props for the MasonrySnapGrid React wrapper.\n * Generic <T> allows layout to work with any item type.\n */\ninterface MasonrySnapGridProps<T>\n extends Omit<MasonrySnapGridLayoutOptions<T>, 'items' | 'renderItem'> {\n items: T[];\n renderItem: (item: T) => React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n}\n\n/**\n * Utility — Waits until all <img> elements inside a given container\n * have either loaded or errored, or until the timeout expires.\n * This ensures layout measurements are based on final image sizes.\n */\nfunction waitForImages(el: HTMLElement, timeout = 1000): Promise<void> {\n return new Promise((resolve) => {\n if (!el) return resolve();\n\n const images = Array.from(el.querySelectorAll('img'));\n if (images.length === 0) return resolve();\n\n let remaining = images.length;\n let called = false;\n\n const finish = () => {\n if (called) return;\n called = true;\n resolve();\n };\n\n const onLoadOrError = () => {\n remaining -= 1;\n if (remaining <= 0) finish();\n };\n\n images.forEach((img) => {\n if (img.complete) {\n onLoadOrError();\n } else {\n img.addEventListener('load', onLoadOrError, { once: true });\n img.addEventListener('error', onLoadOrError, { once: true });\n }\n });\n\n setTimeout(finish, timeout);\n });\n}\n\n/**\n * React wrapper for MasonrySnapGridLayout\n * ---------------------------------------\n * Handles SSR → CSR transition, forwards ref to parent,\n * and manages async layout initialization after image load.\n */\nconst MasonrySnapGridInner = <T,>(\n {\n items,\n renderItem,\n className,\n style,\n ...options\n }: MasonrySnapGridProps<T>,\n ref: React.ForwardedRef<MasonrySnapGridRef>\n) => {\n // DOM container where the masonry will live\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Underlying vanilla masonry instance\n const masonryRef = useRef<MasonrySnapGridLayout<T> | null>(null);\n\n // Track all React roots rendered inside masonry slots (for cleanup)\n const rootsRef = useRef<Map<HTMLElement, ReactDOM.Root>>(new Map());\n\n // Tracks component mount state to prevent async leaks\n const isMountedRef = useRef(true);\n\n // Latest ref passed from parent (keeps forwardRef stable across renders)\n const latestRef = useRef(ref);\n latestRef.current = ref;\n\n /**\n * Forward ref handling — ensures both function refs\n * and object refs from the parent receive the correct instance.\n */\n const updateForwardedRef = useCallback((instance: MasonrySnapGridRef | null) => {\n if (latestRef.current) {\n if (typeof latestRef.current === 'function') {\n latestRef.current(instance);\n } else {\n latestRef.current.current = instance;\n }\n }\n }, []);\n\n /**\n * Server-rendered placeholder items.\n * Rendered inline so that:\n * - SEO crawlers see real content\n * - No layout shift before hydration\n * - Screen readers have immediate access\n */\n const serverRenderedItems = (\n <>\n {items.map((item, idx) => (\n <div key={idx} style={{ display: 'inline-block', verticalAlign: 'top' }}>\n {renderItem(item)}\n </div>\n ))}\n </>\n );\n\n /**\n * Effect: Client takeover after hydration\n * ----------------------------------------\n * Steps:\n * 1. Clear server-rendered HTML\n * 2. Create and initialize masonry layout\n * 3. Wait for next paint + images load before first layout pass\n */\n useEffect(() => {\n isMountedRef.current = true;\n const container = containerRef.current;\n if (!container) return;\n\n // Save original SSR content in case init fails\n const serverContent = container.cloneNode(true) as HTMLElement;\n container.innerHTML = '';\n\n try {\n // Create Masonry instance with React-powered renderItem\n masonryRef.current = new MasonrySnapGridLayout(container, {\n ...options,\n items,\n renderItem: (item) => {\n const div = document.createElement('div');\n div.style.willChange = 'transform, height'; // Hint for smoother animations\n const root = ReactDOM.createRoot(div);\n root.render(renderItem(item));\n rootsRef.current.set(div, root);\n return div;\n },\n });\n\n // Expose instance to parent via forwarded ref\n updateForwardedRef({ layout: masonryRef.current });\n\n } catch (error) {\n console.error('Masonry initialization failed:', error);\n container.replaceWith(serverContent); // Fallback to SSR markup\n return;\n }\n\n let rafId: number | null = null;\n let cancelled = false;\n\n const doInitialLayout = async () => {\n try {\n // Ensure browser has painted initial DOM\n await new Promise<void>((r) => {\n rafId = requestAnimationFrame(() => r());\n });\n\n if (cancelled || !isMountedRef.current) return;\n\n // Wait for images so item heights are final\n await waitForImages(container, 1000);\n\n if (cancelled || !isMountedRef.current) return;\n\n // Trigger initial layout pass\n masonryRef.current?.updateItems(items);\n } catch (error) {\n console.error('Initial layout failed:', error);\n }\n };\n\n doInitialLayout();\n\n return () => {\n // Cleanup on unmount\n isMountedRef.current = false;\n cancelled = true;\n\n if (rafId) cancelAnimationFrame(rafId);\n\n // Unmount React roots inside masonry slots\n rootsRef.current.forEach((root, el) => {\n try {\n root.unmount();\n el.remove();\n } catch (error) {\n console.warn('Error during unmount:', error);\n }\n });\n rootsRef.current.clear();\n\n // Destroy masonry instance\n try {\n masonryRef.current?.destroy();\n } catch (error) {\n console.warn('Error during masonry cleanup:', error);\n }\n masonryRef.current = null;\n\n // Reset forwarded ref\n updateForwardedRef(null);\n };\n }, [options, renderItem, updateForwardedRef]);\n\n /**\n * Effect: Handle updates when `items` changes\n * --------------------------------------------\n * Avoids full re-init by just telling masonry to refresh layout.\n * Uses rAF to batch updates for better performance.\n */\n useEffect(() => {\n if (!masonryRef.current) return;\n\n const rafId = requestAnimationFrame(() => {\n try {\n masonryRef.current?.updateItems(items);\n } catch (error) {\n console.error('Items update failed:', error);\n }\n });\n\n return () => cancelAnimationFrame(rafId);\n }, [items]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ position: 'relative', width: '100%', ...style }}\n >\n {serverRenderedItems}\n </div>\n );\n};\n\n/**\n * ForwardRef wrapper so parent components can call layout methods.\n */\nconst MasonrySnapGrid = forwardRef(MasonrySnapGridInner) as <T>(\n props: MasonrySnapGridProps<T> & { ref?: React.ForwardedRef<MasonrySnapGridRef> }\n) => ReturnType<typeof MasonrySnapGridInner>;\n\nexport default MasonrySnapGrid;\n","import { MasonrySnapGridLayoutOptions } from './types';\n\nexport default class MasonrySnapGridLayout<T = any> {\n // Main container for the grid\n private readonly container: HTMLElement;\n // Normalized config options with defaults applied\n private readonly options: Required<MasonrySnapGridLayoutOptions<T>>;\n // Active DOM elements currently in the layout\n private items: HTMLElement[] = [];\n // Running height for each column (used for placement calculations)\n private columnHeights: number[] = [];\n // Resize observer to detect container width changes\n private resizeObserver: ResizeObserver | undefined;\n // Tracks a pending animation frame request for layout updates\n private rafId: number | null = null;\n // Cache last measured container width to avoid unnecessary relayouts\n private lastContainerWidth = 0;\n // Pool of DOM elements for recycling between renders (avoids costly re-creation)\n private itemPool: HTMLElement[] = [];\n // Flag to prevent operations after destruction\n private isDestroyed = false;\n\n constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions<T>) {\n if (!container) {\n throw new Error('Container element is required');\n }\n\n this.container = container;\n // Merge user-provided options with defaults\n this.options = {\n gutter: 16,\n minColWidth: 250,\n animate: true,\n transitionDuration: 400,\n classNames: {\n container: 'masonry-snap-grid-container',\n item: 'masonry-snap-grid-item',\n },\n ...options,\n };\n\n this.init();\n }\n\n /**\n * Initialize layout: applies base classes, renders initial items,\n * and sets up resize monitoring.\n */\n private init(): void {\n if (this.isDestroyed) return;\n\n this.container.classList.add(this.options.classNames.container || '');\n this.renderItems();\n this.setupResizeObserver();\n }\n\n /**\n * Renders items into the container using a pooled DOM strategy:\n * - Avoids DOM churn by reusing elements where possible\n * - Only creates new nodes when needed\n * - Removes unused pool items when shrinking\n */\n private renderItems(): void {\n if (this.isDestroyed) return;\n\n // Remove orphaned elements from the DOM\n this.items.forEach(item => {\n if (!this.options.items.some((_, i) => this.itemPool[i] === item)) {\n item.remove();\n }\n });\n\n this.items = [];\n this.columnHeights = [];\n\n // Use a fragment for batch DOM insertion (better performance)\n const fragment = document.createDocumentFragment();\n this.options.items.forEach((itemData, index) => {\n let itemElement = this.itemPool[index];\n\n if (!itemElement) {\n itemElement = document.createElement('div');\n itemElement.classList.add(this.options.classNames.item || '');\n this.itemPool[index] = itemElement;\n }\n\n // Render content via provided renderItem function\n const content = this.options.renderItem(itemData);\n if (typeof content === 'string') {\n itemElement.innerHTML = content;\n } else if (content instanceof Node) {\n itemElement.innerHTML = '';\n itemElement.appendChild(content);\n }\n\n fragment.appendChild(itemElement);\n this.items.push(itemElement);\n });\n\n // Trim excess pooled items\n while (this.itemPool.length > this.options.items.length) {\n const item = this.itemPool.pop()!;\n item.remove();\n }\n\n this.container.appendChild(fragment);\n this.updateLayout();\n }\n\n /**\n * Sets up a ResizeObserver on the container to trigger re-layout\n * when width changes — throttled to animation frames for performance.\n */\n private setupResizeObserver(): void {\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n\n this.resizeObserver = new ResizeObserver(() => {\n if (this.rafId) cancelAnimationFrame(this.rafId);\n this.rafId = requestAnimationFrame(() => {\n const newWidth = this.container.clientWidth;\n if (newWidth !== this.lastContainerWidth) {\n this.lastContainerWidth = newWidth;\n this.updateLayout();\n }\n });\n });\n\n this.resizeObserver.observe(this.container);\n }\n\n /**\n * Core layout function:\n * - Calculates number of columns based on container width & min column width\n * - Measures all items to avoid forced reflows during positioning\n * - Positions items in the shortest column to maintain balance\n */\n private updateLayout(): void {\n if (this.isDestroyed || !this.container.isConnected) return;\n\n try {\n const { gutter, minColWidth, animate, transitionDuration } = this.options;\n const containerWidth = this.container.clientWidth;\n\n // Avoid layout if container is hidden or collapsed\n if (containerWidth <= 0) {\n this.container.style.height = '0';\n return;\n }\n\n // Determine column count and width\n const columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));\n const colWidth = (containerWidth - (columns - 1) * gutter) / columns;\n\n // Reset tracking for column heights\n this.columnHeights = new Array(columns).fill(0);\n\n // Measure all items with the new column width before positioning\n const itemHeights = this.measureItems(colWidth);\n\n // Place each item in the shortest available column\n this.positionItems(colWidth, gutter, animate, transitionDuration, itemHeights);\n\n // Adjust container height to fit the tallest column\n this.setContainerHeight(gutter);\n } catch (error) {\n console.error('Masonry layout failed:', error);\n // Fallback: simple vertical stacking\n this.applyFallbackLayout();\n }\n }\n\n /**\n * Measures item heights without affecting layout:\n * - Temporarily forces block layout for accurate measurement\n * - Restores original styles after measuring\n */\n private measureItems(colWidth: number): number[] {\n return this.items.map(item => {\n const originalStyles = {\n display: item.style.display,\n visibility: item.style.visibility,\n position: item.style.position,\n width: item.style.width\n };\n\n item.style.display = 'block';\n item.style.visibility = 'hidden';\n item.style.position = 'absolute';\n item.style.width = `${colWidth}px`;\n\n const height = item.offsetHeight;\n\n Object.assign(item.style, originalStyles);\n return height;\n });\n }\n\n /**\n * Positions items column-by-column:\n * - Chooses the shortest column for each item to maintain balance\n * - Uses transform for GPU-accelerated positioning\n */\n private positionItems(\n colWidth: number,\n gutter: number,\n animate: boolean,\n transitionDuration: number,\n itemHeights: number[]\n ): void {\n this.items.forEach((item, index) => {\n const height = itemHeights[index];\n const minCol = this.findShortestColumn();\n const x = minCol * (colWidth + gutter);\n const y = this.columnHeights[minCol];\n\n item.style.width = `${colWidth}px`;\n item.style.transform = `translate3d(${x}px, ${y}px, 0)`;\n item.style.transition = animate\n ? `transform ${transitionDuration}ms ease`\n : 'none';\n item.style.willChange = 'transform';\n\n this.columnHeights[minCol] += height + gutter;\n });\n }\n\n /**\n * Sets the container height to match the tallest column\n * while subtracting trailing gutter space for a clean edge.\n */\n private setContainerHeight(gutter: number): void {\n const maxHeight = Math.max(0, ...this.columnHeights);\n const containerHeight = maxHeight > 0 ? maxHeight - gutter : 0;\n this.container.style.height = `${containerHeight}px`;\n }\n\n /**\n * Simple fallback layout in case the Masonry calculation fails:\n * stacks items vertically in one column.\n */\n private applyFallbackLayout(): void {\n let top = 0;\n this.items.forEach(item => {\n item.style.transform = `translate3d(0, ${top}px, 0)`;\n top += item.offsetHeight + this.options.gutter;\n });\n this.container.style.height = `${top - this.options.gutter}px`;\n }\n\n /**\n * Finds the column with the least accumulated height.\n */\n private findShortestColumn(): number {\n let minIndex = 0;\n let minHeight = Infinity;\n\n this.columnHeights.forEach((height, index) => {\n if (height < minHeight) {\n minHeight = height;\n minIndex = index;\n }\n });\n\n return minIndex;\n }\n\n /**\n * Public method to replace current items and trigger a full re-render.\n */\n public updateItems(newItems: T[]): void {\n if (this.isDestroyed) return;\n this.options.items = newItems;\n this.renderItems();\n }\n\n /**\n * Cleanly tears down the layout:\n * - Stops observing size changes\n * - Cancels pending animation frames\n * - Clears DOM references and resets container\n */\n public destroy(): void {\n if (this.isDestroyed) return;\n\n this.isDestroyed = true;\n\n this.resizeObserver?.disconnect();\n this.resizeObserver = undefined;\n\n if (this.rafId) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n\n this.container.innerHTML = '';\n this.container.removeAttribute('style');\n this.container.classList.remove(this.options.classNames.container || '');\n\n this.items = [];\n this.columnHeights = [];\n this.itemPool = [];\n }\n}\n"],"mappings":"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","names":["ReactDOM"]}
|
package/package.json
CHANGED