masonry-snap-grid-layout 0.0.5 → 1.0.1
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/dist/esm/react.js +5 -4
- package/dist/esm/react.js.map +1 -1
- package/dist/react.js +5 -4
- package/dist/react.js.map +1 -1
- package/package.json +4 -3
package/dist/esm/react.js
CHANGED
|
@@ -194,19 +194,20 @@ var MasonrySnapGrid = forwardRef(function MasonrySnapGrid2({ items, options = {}
|
|
|
194
194
|
if (!containerRef.current) return;
|
|
195
195
|
layoutRef.current = new MasonrySnapGridLayout(containerRef.current, {
|
|
196
196
|
...options,
|
|
197
|
-
// We disable internal item generation because React controls content
|
|
198
197
|
initialItems: 0,
|
|
198
|
+
// disable internal item creation
|
|
199
199
|
itemContent: null
|
|
200
200
|
});
|
|
201
|
+
layoutRef.current.calculateLayout();
|
|
201
202
|
return () => {
|
|
202
203
|
layoutRef.current?.destroy();
|
|
203
204
|
layoutRef.current = null;
|
|
204
205
|
};
|
|
205
206
|
}, [options]);
|
|
206
207
|
useEffect(() => {
|
|
207
|
-
if (!
|
|
208
|
-
|
|
209
|
-
}, [items
|
|
208
|
+
if (!layoutRef.current) return;
|
|
209
|
+
layoutRef.current.calculateLayout();
|
|
210
|
+
}, [items]);
|
|
210
211
|
return /* @__PURE__ */ jsx(
|
|
211
212
|
"div",
|
|
212
213
|
{
|
package/dist/esm/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react.tsx","../../src/MasonrySnapGridLayout.ts"],"sourcesContent":["'use client';\r\n\r\nimport React, {\r\n useEffect,\r\n useRef,\r\n useImperativeHandle,\r\n forwardRef,\r\n ReactNode,\r\n} from 'react';\r\nimport MasonrySnapGridLayout from './MasonrySnapGridLayout';\r\nimport { MasonrySnapGridLayoutOptions } from './types';\r\n\r\ntype MasonrySnapGridProps<T = any> = {\r\n items: T[];\r\n options?: MasonrySnapGridLayoutOptions;\r\n renderItem: (item: T, index: number) => ReactNode;\r\n className?: string;\r\n style?: React.CSSProperties;\r\n};\r\n\r\nexport type MasonrySnapGridRef = {\r\n layout: MasonrySnapGridLayout | null;\r\n};\r\n\r\nconst MasonrySnapGrid = forwardRef(function MasonrySnapGrid<T>(\r\n { items, options = {}, renderItem, className, style }: MasonrySnapGridProps<T>,\r\n ref: React.Ref<MasonrySnapGridRef>\r\n) {\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const layoutRef = useRef<MasonrySnapGridLayout | null>(null);\r\n\r\n // Expose layout instance if needed\r\n useImperativeHandle(ref, () => ({\r\n layout: layoutRef.current,\r\n }));\r\n\r\n // Initialize masonry layout once\r\n useEffect(() => {\r\n if (!containerRef.current) return;\r\n\r\n layoutRef.current = new MasonrySnapGridLayout(containerRef.current, {\r\n ...options,\r\n // We disable internal item generation because React controls content\r\n initialItems: 0,\r\n itemContent: null,\r\n });\r\n\r\n return () => {\r\n layoutRef.current?.destroy();\r\n layoutRef.current = null;\r\n };\r\n }, [options]);\r\n\r\n // Whenever items change, re-render React children inside container\r\n useEffect(() => {\r\n if (!containerRef.current || !layoutRef.current) return;\r\n\r\n // Clear container children before React renders new items\r\n containerRef.current.innerHTML = '';\r\n\r\n // Render React children directly inside container\r\n // Using ReactDOM.createPortal for each item\r\n\r\n // However, to keep it simple, we rely on React rendering here:\r\n\r\n }, [items, renderItem]);\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={className}\r\n style={{ position: 'relative', width: '100%', ...style }}\r\n aria-label=\"Masonry grid\"\r\n role=\"list\"\r\n >\r\n {items.map((item, index) => (\r\n <div\r\n key={index}\r\n className=\"masonry-snap-grid-item\"\r\n style={{ breakInside: 'avoid' }}\r\n role=\"listitem\"\r\n aria-posinset={index + 1}\r\n aria-setsize={items.length}\r\n >\r\n {renderItem(item, index)}\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n});\r\n\r\nexport default MasonrySnapGrid;\r\n","import { MasonrySnapGridLayoutClassNames, MasonrySnapGridLayoutOptions } from './types';\r\n\r\nexport default class MasonrySnapGridLayout {\r\n private readonly container: HTMLElement;\r\n private readonly options: Required<MasonrySnapGridLayoutOptions>;\r\n private classNames: Required<MasonrySnapGridLayoutClassNames>;\r\n\r\n private items: HTMLElement[] = [];\r\n private positions: { x: number; y: number; width: number; height: number }[] = [];\r\n private columnHeights: number[] = [];\r\n private columns: number = 0;\r\n private lastPositions: typeof this.positions = [];\r\n private resizeRaf: number | null = null;\r\n\r\n private resizeObserver: ResizeObserver | null = null;\r\n private boundResizeHandler = this.handleResize.bind(this);\r\n\r\n constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions = {}) {\r\n this.container = container;\r\n this.classNames = {\r\n container: 'masonry-snap-grid-container',\r\n item: 'masonry-snap-grid-item',\r\n itemContent: 'masonry-snap-grid-item-content',\r\n itemHeader: 'masonry-snap-grid-item-header',\r\n itemTitle: 'masonry-snap-grid-item-title',\r\n itemId: 'masonry-snap-grid-item-id',\r\n itemBody: 'masonry-snap-grid-item-body',\r\n progressBar: 'masonry-snap-grid-progress-bar',\r\n progress: 'masonry-snap-grid-progress',\r\n itemFooter: 'masonry-snap-grid-item-footer',\r\n ...(options.classNames || {}),\r\n }\r\n this.options = {\r\n gutter: 16,\r\n minColWidth: 250,\r\n animate: true,\r\n transitionDuration: 400,\r\n initialItems: 0,\r\n itemContent: null,\r\n classNames: this.classNames,\r\n ...options,\r\n };\r\n\r\n\r\n this.container.classList.add(this.classNames.container);\r\n this.init();\r\n }\r\n\r\n private init() {\r\n this.setupEventListeners();\r\n this.generateItems(this.options.initialItems);\r\n this.calculateLayout();\r\n this.applyLayout(false);\r\n }\r\n\r\n private setupEventListeners() {\r\n this.resizeObserver = new ResizeObserver(() => this.handleResize());\r\n this.resizeObserver.observe(this.container);\r\n\r\n window.addEventListener('resize', this.boundResizeHandler);\r\n }\r\n\r\n public generateItems(count: number) {\r\n if (count < this.items.length) {\r\n this.items.slice(count).forEach((item) => item.remove());\r\n this.items = this.items.slice(0, count);\r\n return;\r\n }\r\n\r\n const startIndex = this.items.length;\r\n const fragment = document.createDocumentFragment();\r\n\r\n for (let i = startIndex; i < count; i++) {\r\n const item = this.createItem(i);\r\n fragment.appendChild(item);\r\n this.items.push(item);\r\n }\r\n\r\n this.container.appendChild(fragment);\r\n }\r\n\r\n private createItem(index: number): HTMLElement {\r\n const div = document.createElement('div');\r\n div.className = this.classNames.item;\r\n\r\n const contentOption = this.options.itemContent;\r\n let content: HTMLElement | string;\r\n\r\n if (typeof contentOption === 'function') {\r\n content = contentOption(index);\r\n } else if (contentOption instanceof HTMLElement || typeof contentOption === 'string') {\r\n content = contentOption;\r\n } else {\r\n content = `Item ${index + 1}`;\r\n }\r\n\r\n if (typeof content === 'string') {\r\n div.textContent = content;\r\n } else {\r\n div.appendChild(content);\r\n }\r\n\r\n // Assigning height dynamically for layout simulation (no styles applied)\r\n const height = 120 + Math.floor(Math.random() * 100);\r\n div.style.height = `${height}px`;\r\n\r\n return div;\r\n }\r\n\r\n public calculateLayout() {\r\n const { gutter, minColWidth } = this.options;\r\n const containerWidth = this.container.clientWidth;\r\n\r\n this.columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));\r\n const colWidth = (containerWidth - (this.columns - 1) * gutter) / this.columns;\r\n\r\n this.lastPositions = [...this.positions];\r\n this.columnHeights = new Array(this.columns).fill(0);\r\n this.positions = [];\r\n\r\n this.items.forEach((item, i) => {\r\n const height = item.offsetHeight;\r\n\r\n let minCol = 0;\r\n for (let c = 1; c < this.columns; c++) {\r\n if (this.columnHeights[c] < this.columnHeights[minCol]) {\r\n minCol = c;\r\n }\r\n }\r\n\r\n const x = minCol * (colWidth + gutter);\r\n const y = this.columnHeights[minCol];\r\n\r\n this.positions[i] = { x, y, width: colWidth, height };\r\n this.columnHeights[minCol] += height + gutter;\r\n });\r\n\r\n const maxHeight = Math.max(...this.columnHeights);\r\n this.container.style.height = `${maxHeight}px`;\r\n }\r\n\r\n applyLayout(animate: boolean = false) {\r\n const duration = this.options.transitionDuration;\r\n\r\n this.items.forEach((item, i) => {\r\n const pos = this.positions[i] || { x: 0, y: 0, width: 0 };\r\n const lastPos = this.lastPositions[i] || { x: 0, y: 0 };\r\n\r\n item.style.width = `${pos.width}px`;\r\n\r\n if (animate) {\r\n const dx = lastPos.x - pos.x;\r\n const dy = lastPos.y - pos.y;\r\n\r\n item.style.transition = 'none';\r\n item.style.transform = `translate3d(${pos.x + dx}px, ${pos.y + dy}px, 0)`;\r\n void item.offsetHeight;\r\n\r\n item.style.transition = `transform ${duration}ms ease`;\r\n item.style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;\r\n } else {\r\n item.style.transition = 'none';\r\n item.style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;\r\n }\r\n });\r\n }\r\n\r\n private handleResize() {\r\n if (this.resizeRaf) cancelAnimationFrame(this.resizeRaf);\r\n this.resizeRaf = requestAnimationFrame(() => {\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n });\r\n }\r\n\r\n public shuffleItems() {\r\n for (let i = this.items.length - 1; i > 0; i--) {\r\n const j = Math.floor(Math.random() * (i + 1));\r\n [this.items[i], this.items[j]] = [this.items[j], this.items[i]];\r\n }\r\n\r\n const fragment = document.createDocumentFragment();\r\n this.items.forEach((item) => fragment.appendChild(item));\r\n this.container.appendChild(fragment);\r\n\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n }\r\n\r\n public addItems(count: number) {\r\n const newCount = this.items.length + count;\r\n this.generateItems(newCount);\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n }\r\n\r\n public destroy(): void {\r\n if (this.resizeObserver) {\r\n this.resizeObserver.disconnect();\r\n this.resizeObserver = null;\r\n }\r\n\r\n window.removeEventListener('resize', this.boundResizeHandler);\r\n\r\n if (this.resizeRaf) {\r\n cancelAnimationFrame(this.resizeRaf);\r\n this.resizeRaf = null;\r\n }\r\n\r\n this.container.innerHTML = '';\r\n this.container.removeAttribute('style');\r\n this.container.classList.remove(this.classNames.container);\r\n\r\n this.items = [];\r\n this.positions = [];\r\n this.columnHeights = [];\r\n this.columns = 0;\r\n this.lastPositions = [];\r\n }\r\n}"],"mappings":"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","names":["MasonrySnapGrid"]}
|
|
1
|
+
{"version":3,"sources":["../../src/react.tsx","../../src/MasonrySnapGridLayout.ts"],"sourcesContent":["'use client';\r\n\r\nimport React, {\r\n useEffect,\r\n useRef,\r\n useImperativeHandle,\r\n forwardRef,\r\n ReactNode,\r\n} from 'react';\r\nimport MasonrySnapGridLayout from './MasonrySnapGridLayout';\r\nimport { MasonrySnapGridLayoutOptions } from './types';\r\n\r\ntype MasonrySnapGridProps<T = any> = {\r\n items: T[];\r\n options?: MasonrySnapGridLayoutOptions;\r\n renderItem: (item: T, index: number) => ReactNode;\r\n className?: string;\r\n style?: React.CSSProperties;\r\n};\r\n\r\nexport type MasonrySnapGridRef = {\r\n layout: MasonrySnapGridLayout | null;\r\n};\r\n\r\nconst MasonrySnapGrid = forwardRef(function MasonrySnapGrid<T>(\r\n { items, options = {}, renderItem, className, style }: MasonrySnapGridProps<T>,\r\n ref: React.Ref<MasonrySnapGridRef>\r\n) {\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const layoutRef = useRef<MasonrySnapGridLayout | null>(null);\r\n\r\n // Expose layout instance via ref for advanced use\r\n useImperativeHandle(ref, () => ({\r\n layout: layoutRef.current,\r\n }));\r\n\r\n // Initialize MasonrySnapGridLayout instance on mount or options change\r\n useEffect(() => {\r\n if (!containerRef.current) return;\r\n\r\n layoutRef.current = new MasonrySnapGridLayout(containerRef.current, {\r\n ...options,\r\n initialItems: 0, // disable internal item creation\r\n itemContent: null,\r\n });\r\n\r\n // Perform initial layout after creation\r\n layoutRef.current.calculateLayout();\r\n\r\n return () => {\r\n layoutRef.current?.destroy();\r\n layoutRef.current = null;\r\n };\r\n }, [options]);\r\n\r\n // Whenever `items` change, trigger layout recalculation\r\n useEffect(() => {\r\n if (!layoutRef.current) return;\r\n\r\n // Optionally, you can call a method here to notify layout about changes,\r\n // or just rely on React rendering wrapped items (the DOM nodes)\r\n\r\n // Because your React renders items inside container,\r\n // just call calculateLayout to reposition after React update\r\n\r\n layoutRef.current.calculateLayout();\r\n }, [items]);\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={className}\r\n style={{ position: 'relative', width: '100%', ...style }}\r\n aria-label=\"Masonry grid\"\r\n role=\"list\"\r\n >\r\n {items.map((item, index) => (\r\n <div\r\n key={index}\r\n className=\"masonry-snap-grid-item\"\r\n style={{ breakInside: 'avoid' }}\r\n role=\"listitem\"\r\n aria-posinset={index + 1}\r\n aria-setsize={items.length}\r\n >\r\n {renderItem(item, index)}\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n});\r\n\r\nexport default MasonrySnapGrid;\r\n","import { MasonrySnapGridLayoutClassNames, MasonrySnapGridLayoutOptions } from './types';\r\n\r\nexport default class MasonrySnapGridLayout {\r\n private readonly container: HTMLElement;\r\n private readonly options: Required<MasonrySnapGridLayoutOptions>;\r\n private classNames: Required<MasonrySnapGridLayoutClassNames>;\r\n\r\n private items: HTMLElement[] = [];\r\n private positions: { x: number; y: number; width: number; height: number }[] = [];\r\n private columnHeights: number[] = [];\r\n private columns: number = 0;\r\n private lastPositions: typeof this.positions = [];\r\n private resizeRaf: number | null = null;\r\n\r\n private resizeObserver: ResizeObserver | null = null;\r\n private boundResizeHandler = this.handleResize.bind(this);\r\n\r\n constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions = {}) {\r\n this.container = container;\r\n this.classNames = {\r\n container: 'masonry-snap-grid-container',\r\n item: 'masonry-snap-grid-item',\r\n itemContent: 'masonry-snap-grid-item-content',\r\n itemHeader: 'masonry-snap-grid-item-header',\r\n itemTitle: 'masonry-snap-grid-item-title',\r\n itemId: 'masonry-snap-grid-item-id',\r\n itemBody: 'masonry-snap-grid-item-body',\r\n progressBar: 'masonry-snap-grid-progress-bar',\r\n progress: 'masonry-snap-grid-progress',\r\n itemFooter: 'masonry-snap-grid-item-footer',\r\n ...(options.classNames || {}),\r\n }\r\n this.options = {\r\n gutter: 16,\r\n minColWidth: 250,\r\n animate: true,\r\n transitionDuration: 400,\r\n initialItems: 0,\r\n itemContent: null,\r\n classNames: this.classNames,\r\n ...options,\r\n };\r\n\r\n\r\n this.container.classList.add(this.classNames.container);\r\n this.init();\r\n }\r\n\r\n private init() {\r\n this.setupEventListeners();\r\n this.generateItems(this.options.initialItems);\r\n this.calculateLayout();\r\n this.applyLayout(false);\r\n }\r\n\r\n private setupEventListeners() {\r\n this.resizeObserver = new ResizeObserver(() => this.handleResize());\r\n this.resizeObserver.observe(this.container);\r\n\r\n window.addEventListener('resize', this.boundResizeHandler);\r\n }\r\n\r\n public generateItems(count: number) {\r\n if (count < this.items.length) {\r\n this.items.slice(count).forEach((item) => item.remove());\r\n this.items = this.items.slice(0, count);\r\n return;\r\n }\r\n\r\n const startIndex = this.items.length;\r\n const fragment = document.createDocumentFragment();\r\n\r\n for (let i = startIndex; i < count; i++) {\r\n const item = this.createItem(i);\r\n fragment.appendChild(item);\r\n this.items.push(item);\r\n }\r\n\r\n this.container.appendChild(fragment);\r\n }\r\n\r\n private createItem(index: number): HTMLElement {\r\n const div = document.createElement('div');\r\n div.className = this.classNames.item;\r\n\r\n const contentOption = this.options.itemContent;\r\n let content: HTMLElement | string;\r\n\r\n if (typeof contentOption === 'function') {\r\n content = contentOption(index);\r\n } else if (contentOption instanceof HTMLElement || typeof contentOption === 'string') {\r\n content = contentOption;\r\n } else {\r\n content = `Item ${index + 1}`;\r\n }\r\n\r\n if (typeof content === 'string') {\r\n div.textContent = content;\r\n } else {\r\n div.appendChild(content);\r\n }\r\n\r\n // Assigning height dynamically for layout simulation (no styles applied)\r\n const height = 120 + Math.floor(Math.random() * 100);\r\n div.style.height = `${height}px`;\r\n\r\n return div;\r\n }\r\n\r\n public calculateLayout() {\r\n const { gutter, minColWidth } = this.options;\r\n const containerWidth = this.container.clientWidth;\r\n\r\n this.columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));\r\n const colWidth = (containerWidth - (this.columns - 1) * gutter) / this.columns;\r\n\r\n this.lastPositions = [...this.positions];\r\n this.columnHeights = new Array(this.columns).fill(0);\r\n this.positions = [];\r\n\r\n this.items.forEach((item, i) => {\r\n const height = item.offsetHeight;\r\n\r\n let minCol = 0;\r\n for (let c = 1; c < this.columns; c++) {\r\n if (this.columnHeights[c] < this.columnHeights[minCol]) {\r\n minCol = c;\r\n }\r\n }\r\n\r\n const x = minCol * (colWidth + gutter);\r\n const y = this.columnHeights[minCol];\r\n\r\n this.positions[i] = { x, y, width: colWidth, height };\r\n this.columnHeights[minCol] += height + gutter;\r\n });\r\n\r\n const maxHeight = Math.max(...this.columnHeights);\r\n this.container.style.height = `${maxHeight}px`;\r\n }\r\n\r\n applyLayout(animate: boolean = false) {\r\n const duration = this.options.transitionDuration;\r\n\r\n this.items.forEach((item, i) => {\r\n const pos = this.positions[i] || { x: 0, y: 0, width: 0 };\r\n const lastPos = this.lastPositions[i] || { x: 0, y: 0 };\r\n\r\n item.style.width = `${pos.width}px`;\r\n\r\n if (animate) {\r\n const dx = lastPos.x - pos.x;\r\n const dy = lastPos.y - pos.y;\r\n\r\n item.style.transition = 'none';\r\n item.style.transform = `translate3d(${pos.x + dx}px, ${pos.y + dy}px, 0)`;\r\n void item.offsetHeight;\r\n\r\n item.style.transition = `transform ${duration}ms ease`;\r\n item.style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;\r\n } else {\r\n item.style.transition = 'none';\r\n item.style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;\r\n }\r\n });\r\n }\r\n\r\n private handleResize() {\r\n if (this.resizeRaf) cancelAnimationFrame(this.resizeRaf);\r\n this.resizeRaf = requestAnimationFrame(() => {\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n });\r\n }\r\n\r\n public shuffleItems() {\r\n for (let i = this.items.length - 1; i > 0; i--) {\r\n const j = Math.floor(Math.random() * (i + 1));\r\n [this.items[i], this.items[j]] = [this.items[j], this.items[i]];\r\n }\r\n\r\n const fragment = document.createDocumentFragment();\r\n this.items.forEach((item) => fragment.appendChild(item));\r\n this.container.appendChild(fragment);\r\n\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n }\r\n\r\n public addItems(count: number) {\r\n const newCount = this.items.length + count;\r\n this.generateItems(newCount);\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n }\r\n\r\n public destroy(): void {\r\n if (this.resizeObserver) {\r\n this.resizeObserver.disconnect();\r\n this.resizeObserver = null;\r\n }\r\n\r\n window.removeEventListener('resize', this.boundResizeHandler);\r\n\r\n if (this.resizeRaf) {\r\n cancelAnimationFrame(this.resizeRaf);\r\n this.resizeRaf = null;\r\n }\r\n\r\n this.container.innerHTML = '';\r\n this.container.removeAttribute('style');\r\n this.container.classList.remove(this.classNames.container);\r\n\r\n this.items = [];\r\n this.positions = [];\r\n this.columnHeights = [];\r\n this.columns = 0;\r\n this.lastPositions = [];\r\n }\r\n}"],"mappings":"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","names":["MasonrySnapGrid"]}
|
package/dist/react.js
CHANGED
|
@@ -212,19 +212,20 @@ var MasonrySnapGrid = (0, import_react.forwardRef)(function MasonrySnapGrid2({ i
|
|
|
212
212
|
if (!containerRef.current) return;
|
|
213
213
|
layoutRef.current = new MasonrySnapGridLayout(containerRef.current, {
|
|
214
214
|
...options,
|
|
215
|
-
// We disable internal item generation because React controls content
|
|
216
215
|
initialItems: 0,
|
|
216
|
+
// disable internal item creation
|
|
217
217
|
itemContent: null
|
|
218
218
|
});
|
|
219
|
+
layoutRef.current.calculateLayout();
|
|
219
220
|
return () => {
|
|
220
221
|
layoutRef.current?.destroy();
|
|
221
222
|
layoutRef.current = null;
|
|
222
223
|
};
|
|
223
224
|
}, [options]);
|
|
224
225
|
(0, import_react.useEffect)(() => {
|
|
225
|
-
if (!
|
|
226
|
-
|
|
227
|
-
}, [items
|
|
226
|
+
if (!layoutRef.current) return;
|
|
227
|
+
layoutRef.current.calculateLayout();
|
|
228
|
+
}, [items]);
|
|
228
229
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
229
230
|
"div",
|
|
230
231
|
{
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react.tsx","../src/MasonrySnapGridLayout.ts"],"sourcesContent":["'use client';\r\n\r\nimport React, {\r\n useEffect,\r\n useRef,\r\n useImperativeHandle,\r\n forwardRef,\r\n ReactNode,\r\n} from 'react';\r\nimport MasonrySnapGridLayout from './MasonrySnapGridLayout';\r\nimport { MasonrySnapGridLayoutOptions } from './types';\r\n\r\ntype MasonrySnapGridProps<T = any> = {\r\n items: T[];\r\n options?: MasonrySnapGridLayoutOptions;\r\n renderItem: (item: T, index: number) => ReactNode;\r\n className?: string;\r\n style?: React.CSSProperties;\r\n};\r\n\r\nexport type MasonrySnapGridRef = {\r\n layout: MasonrySnapGridLayout | null;\r\n};\r\n\r\nconst MasonrySnapGrid = forwardRef(function MasonrySnapGrid<T>(\r\n { items, options = {}, renderItem, className, style }: MasonrySnapGridProps<T>,\r\n ref: React.Ref<MasonrySnapGridRef>\r\n) {\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const layoutRef = useRef<MasonrySnapGridLayout | null>(null);\r\n\r\n // Expose layout instance if needed\r\n useImperativeHandle(ref, () => ({\r\n layout: layoutRef.current,\r\n }));\r\n\r\n // Initialize masonry layout once\r\n useEffect(() => {\r\n if (!containerRef.current) return;\r\n\r\n layoutRef.current = new MasonrySnapGridLayout(containerRef.current, {\r\n ...options,\r\n // We disable internal item generation because React controls content\r\n initialItems: 0,\r\n itemContent: null,\r\n });\r\n\r\n return () => {\r\n layoutRef.current?.destroy();\r\n layoutRef.current = null;\r\n };\r\n }, [options]);\r\n\r\n // Whenever items change, re-render React children inside container\r\n useEffect(() => {\r\n if (!containerRef.current || !layoutRef.current) return;\r\n\r\n // Clear container children before React renders new items\r\n containerRef.current.innerHTML = '';\r\n\r\n // Render React children directly inside container\r\n // Using ReactDOM.createPortal for each item\r\n\r\n // However, to keep it simple, we rely on React rendering here:\r\n\r\n }, [items, renderItem]);\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={className}\r\n style={{ position: 'relative', width: '100%', ...style }}\r\n aria-label=\"Masonry grid\"\r\n role=\"list\"\r\n >\r\n {items.map((item, index) => (\r\n <div\r\n key={index}\r\n className=\"masonry-snap-grid-item\"\r\n style={{ breakInside: 'avoid' }}\r\n role=\"listitem\"\r\n aria-posinset={index + 1}\r\n aria-setsize={items.length}\r\n >\r\n {renderItem(item, index)}\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n});\r\n\r\nexport default MasonrySnapGrid;\r\n","import { MasonrySnapGridLayoutClassNames, MasonrySnapGridLayoutOptions } from './types';\r\n\r\nexport default class MasonrySnapGridLayout {\r\n private readonly container: HTMLElement;\r\n private readonly options: Required<MasonrySnapGridLayoutOptions>;\r\n private classNames: Required<MasonrySnapGridLayoutClassNames>;\r\n\r\n private items: HTMLElement[] = [];\r\n private positions: { x: number; y: number; width: number; height: number }[] = [];\r\n private columnHeights: number[] = [];\r\n private columns: number = 0;\r\n private lastPositions: typeof this.positions = [];\r\n private resizeRaf: number | null = null;\r\n\r\n private resizeObserver: ResizeObserver | null = null;\r\n private boundResizeHandler = this.handleResize.bind(this);\r\n\r\n constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions = {}) {\r\n this.container = container;\r\n this.classNames = {\r\n container: 'masonry-snap-grid-container',\r\n item: 'masonry-snap-grid-item',\r\n itemContent: 'masonry-snap-grid-item-content',\r\n itemHeader: 'masonry-snap-grid-item-header',\r\n itemTitle: 'masonry-snap-grid-item-title',\r\n itemId: 'masonry-snap-grid-item-id',\r\n itemBody: 'masonry-snap-grid-item-body',\r\n progressBar: 'masonry-snap-grid-progress-bar',\r\n progress: 'masonry-snap-grid-progress',\r\n itemFooter: 'masonry-snap-grid-item-footer',\r\n ...(options.classNames || {}),\r\n }\r\n this.options = {\r\n gutter: 16,\r\n minColWidth: 250,\r\n animate: true,\r\n transitionDuration: 400,\r\n initialItems: 0,\r\n itemContent: null,\r\n classNames: this.classNames,\r\n ...options,\r\n };\r\n\r\n\r\n this.container.classList.add(this.classNames.container);\r\n this.init();\r\n }\r\n\r\n private init() {\r\n this.setupEventListeners();\r\n this.generateItems(this.options.initialItems);\r\n this.calculateLayout();\r\n this.applyLayout(false);\r\n }\r\n\r\n private setupEventListeners() {\r\n this.resizeObserver = new ResizeObserver(() => this.handleResize());\r\n this.resizeObserver.observe(this.container);\r\n\r\n window.addEventListener('resize', this.boundResizeHandler);\r\n }\r\n\r\n public generateItems(count: number) {\r\n if (count < this.items.length) {\r\n this.items.slice(count).forEach((item) => item.remove());\r\n this.items = this.items.slice(0, count);\r\n return;\r\n }\r\n\r\n const startIndex = this.items.length;\r\n const fragment = document.createDocumentFragment();\r\n\r\n for (let i = startIndex; i < count; i++) {\r\n const item = this.createItem(i);\r\n fragment.appendChild(item);\r\n this.items.push(item);\r\n }\r\n\r\n this.container.appendChild(fragment);\r\n }\r\n\r\n private createItem(index: number): HTMLElement {\r\n const div = document.createElement('div');\r\n div.className = this.classNames.item;\r\n\r\n const contentOption = this.options.itemContent;\r\n let content: HTMLElement | string;\r\n\r\n if (typeof contentOption === 'function') {\r\n content = contentOption(index);\r\n } else if (contentOption instanceof HTMLElement || typeof contentOption === 'string') {\r\n content = contentOption;\r\n } else {\r\n content = `Item ${index + 1}`;\r\n }\r\n\r\n if (typeof content === 'string') {\r\n div.textContent = content;\r\n } else {\r\n div.appendChild(content);\r\n }\r\n\r\n // Assigning height dynamically for layout simulation (no styles applied)\r\n const height = 120 + Math.floor(Math.random() * 100);\r\n div.style.height = `${height}px`;\r\n\r\n return div;\r\n }\r\n\r\n public calculateLayout() {\r\n const { gutter, minColWidth } = this.options;\r\n const containerWidth = this.container.clientWidth;\r\n\r\n this.columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));\r\n const colWidth = (containerWidth - (this.columns - 1) * gutter) / this.columns;\r\n\r\n this.lastPositions = [...this.positions];\r\n this.columnHeights = new Array(this.columns).fill(0);\r\n this.positions = [];\r\n\r\n this.items.forEach((item, i) => {\r\n const height = item.offsetHeight;\r\n\r\n let minCol = 0;\r\n for (let c = 1; c < this.columns; c++) {\r\n if (this.columnHeights[c] < this.columnHeights[minCol]) {\r\n minCol = c;\r\n }\r\n }\r\n\r\n const x = minCol * (colWidth + gutter);\r\n const y = this.columnHeights[minCol];\r\n\r\n this.positions[i] = { x, y, width: colWidth, height };\r\n this.columnHeights[minCol] += height + gutter;\r\n });\r\n\r\n const maxHeight = Math.max(...this.columnHeights);\r\n this.container.style.height = `${maxHeight}px`;\r\n }\r\n\r\n applyLayout(animate: boolean = false) {\r\n const duration = this.options.transitionDuration;\r\n\r\n this.items.forEach((item, i) => {\r\n const pos = this.positions[i] || { x: 0, y: 0, width: 0 };\r\n const lastPos = this.lastPositions[i] || { x: 0, y: 0 };\r\n\r\n item.style.width = `${pos.width}px`;\r\n\r\n if (animate) {\r\n const dx = lastPos.x - pos.x;\r\n const dy = lastPos.y - pos.y;\r\n\r\n item.style.transition = 'none';\r\n item.style.transform = `translate3d(${pos.x + dx}px, ${pos.y + dy}px, 0)`;\r\n void item.offsetHeight;\r\n\r\n item.style.transition = `transform ${duration}ms ease`;\r\n item.style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;\r\n } else {\r\n item.style.transition = 'none';\r\n item.style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;\r\n }\r\n });\r\n }\r\n\r\n private handleResize() {\r\n if (this.resizeRaf) cancelAnimationFrame(this.resizeRaf);\r\n this.resizeRaf = requestAnimationFrame(() => {\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n });\r\n }\r\n\r\n public shuffleItems() {\r\n for (let i = this.items.length - 1; i > 0; i--) {\r\n const j = Math.floor(Math.random() * (i + 1));\r\n [this.items[i], this.items[j]] = [this.items[j], this.items[i]];\r\n }\r\n\r\n const fragment = document.createDocumentFragment();\r\n this.items.forEach((item) => fragment.appendChild(item));\r\n this.container.appendChild(fragment);\r\n\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n }\r\n\r\n public addItems(count: number) {\r\n const newCount = this.items.length + count;\r\n this.generateItems(newCount);\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n }\r\n\r\n public destroy(): void {\r\n if (this.resizeObserver) {\r\n this.resizeObserver.disconnect();\r\n this.resizeObserver = null;\r\n }\r\n\r\n window.removeEventListener('resize', this.boundResizeHandler);\r\n\r\n if (this.resizeRaf) {\r\n cancelAnimationFrame(this.resizeRaf);\r\n this.resizeRaf = null;\r\n }\r\n\r\n this.container.innerHTML = '';\r\n this.container.removeAttribute('style');\r\n this.container.classList.remove(this.classNames.container);\r\n\r\n this.items = [];\r\n this.positions = [];\r\n this.columnHeights = [];\r\n this.columns = 0;\r\n this.lastPositions = [];\r\n }\r\n}"],"mappings":"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","names":["MasonrySnapGrid"]}
|
|
1
|
+
{"version":3,"sources":["../src/react.tsx","../src/MasonrySnapGridLayout.ts"],"sourcesContent":["'use client';\r\n\r\nimport React, {\r\n useEffect,\r\n useRef,\r\n useImperativeHandle,\r\n forwardRef,\r\n ReactNode,\r\n} from 'react';\r\nimport MasonrySnapGridLayout from './MasonrySnapGridLayout';\r\nimport { MasonrySnapGridLayoutOptions } from './types';\r\n\r\ntype MasonrySnapGridProps<T = any> = {\r\n items: T[];\r\n options?: MasonrySnapGridLayoutOptions;\r\n renderItem: (item: T, index: number) => ReactNode;\r\n className?: string;\r\n style?: React.CSSProperties;\r\n};\r\n\r\nexport type MasonrySnapGridRef = {\r\n layout: MasonrySnapGridLayout | null;\r\n};\r\n\r\nconst MasonrySnapGrid = forwardRef(function MasonrySnapGrid<T>(\r\n { items, options = {}, renderItem, className, style }: MasonrySnapGridProps<T>,\r\n ref: React.Ref<MasonrySnapGridRef>\r\n) {\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const layoutRef = useRef<MasonrySnapGridLayout | null>(null);\r\n\r\n // Expose layout instance via ref for advanced use\r\n useImperativeHandle(ref, () => ({\r\n layout: layoutRef.current,\r\n }));\r\n\r\n // Initialize MasonrySnapGridLayout instance on mount or options change\r\n useEffect(() => {\r\n if (!containerRef.current) return;\r\n\r\n layoutRef.current = new MasonrySnapGridLayout(containerRef.current, {\r\n ...options,\r\n initialItems: 0, // disable internal item creation\r\n itemContent: null,\r\n });\r\n\r\n // Perform initial layout after creation\r\n layoutRef.current.calculateLayout();\r\n\r\n return () => {\r\n layoutRef.current?.destroy();\r\n layoutRef.current = null;\r\n };\r\n }, [options]);\r\n\r\n // Whenever `items` change, trigger layout recalculation\r\n useEffect(() => {\r\n if (!layoutRef.current) return;\r\n\r\n // Optionally, you can call a method here to notify layout about changes,\r\n // or just rely on React rendering wrapped items (the DOM nodes)\r\n\r\n // Because your React renders items inside container,\r\n // just call calculateLayout to reposition after React update\r\n\r\n layoutRef.current.calculateLayout();\r\n }, [items]);\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n className={className}\r\n style={{ position: 'relative', width: '100%', ...style }}\r\n aria-label=\"Masonry grid\"\r\n role=\"list\"\r\n >\r\n {items.map((item, index) => (\r\n <div\r\n key={index}\r\n className=\"masonry-snap-grid-item\"\r\n style={{ breakInside: 'avoid' }}\r\n role=\"listitem\"\r\n aria-posinset={index + 1}\r\n aria-setsize={items.length}\r\n >\r\n {renderItem(item, index)}\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n});\r\n\r\nexport default MasonrySnapGrid;\r\n","import { MasonrySnapGridLayoutClassNames, MasonrySnapGridLayoutOptions } from './types';\r\n\r\nexport default class MasonrySnapGridLayout {\r\n private readonly container: HTMLElement;\r\n private readonly options: Required<MasonrySnapGridLayoutOptions>;\r\n private classNames: Required<MasonrySnapGridLayoutClassNames>;\r\n\r\n private items: HTMLElement[] = [];\r\n private positions: { x: number; y: number; width: number; height: number }[] = [];\r\n private columnHeights: number[] = [];\r\n private columns: number = 0;\r\n private lastPositions: typeof this.positions = [];\r\n private resizeRaf: number | null = null;\r\n\r\n private resizeObserver: ResizeObserver | null = null;\r\n private boundResizeHandler = this.handleResize.bind(this);\r\n\r\n constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions = {}) {\r\n this.container = container;\r\n this.classNames = {\r\n container: 'masonry-snap-grid-container',\r\n item: 'masonry-snap-grid-item',\r\n itemContent: 'masonry-snap-grid-item-content',\r\n itemHeader: 'masonry-snap-grid-item-header',\r\n itemTitle: 'masonry-snap-grid-item-title',\r\n itemId: 'masonry-snap-grid-item-id',\r\n itemBody: 'masonry-snap-grid-item-body',\r\n progressBar: 'masonry-snap-grid-progress-bar',\r\n progress: 'masonry-snap-grid-progress',\r\n itemFooter: 'masonry-snap-grid-item-footer',\r\n ...(options.classNames || {}),\r\n }\r\n this.options = {\r\n gutter: 16,\r\n minColWidth: 250,\r\n animate: true,\r\n transitionDuration: 400,\r\n initialItems: 0,\r\n itemContent: null,\r\n classNames: this.classNames,\r\n ...options,\r\n };\r\n\r\n\r\n this.container.classList.add(this.classNames.container);\r\n this.init();\r\n }\r\n\r\n private init() {\r\n this.setupEventListeners();\r\n this.generateItems(this.options.initialItems);\r\n this.calculateLayout();\r\n this.applyLayout(false);\r\n }\r\n\r\n private setupEventListeners() {\r\n this.resizeObserver = new ResizeObserver(() => this.handleResize());\r\n this.resizeObserver.observe(this.container);\r\n\r\n window.addEventListener('resize', this.boundResizeHandler);\r\n }\r\n\r\n public generateItems(count: number) {\r\n if (count < this.items.length) {\r\n this.items.slice(count).forEach((item) => item.remove());\r\n this.items = this.items.slice(0, count);\r\n return;\r\n }\r\n\r\n const startIndex = this.items.length;\r\n const fragment = document.createDocumentFragment();\r\n\r\n for (let i = startIndex; i < count; i++) {\r\n const item = this.createItem(i);\r\n fragment.appendChild(item);\r\n this.items.push(item);\r\n }\r\n\r\n this.container.appendChild(fragment);\r\n }\r\n\r\n private createItem(index: number): HTMLElement {\r\n const div = document.createElement('div');\r\n div.className = this.classNames.item;\r\n\r\n const contentOption = this.options.itemContent;\r\n let content: HTMLElement | string;\r\n\r\n if (typeof contentOption === 'function') {\r\n content = contentOption(index);\r\n } else if (contentOption instanceof HTMLElement || typeof contentOption === 'string') {\r\n content = contentOption;\r\n } else {\r\n content = `Item ${index + 1}`;\r\n }\r\n\r\n if (typeof content === 'string') {\r\n div.textContent = content;\r\n } else {\r\n div.appendChild(content);\r\n }\r\n\r\n // Assigning height dynamically for layout simulation (no styles applied)\r\n const height = 120 + Math.floor(Math.random() * 100);\r\n div.style.height = `${height}px`;\r\n\r\n return div;\r\n }\r\n\r\n public calculateLayout() {\r\n const { gutter, minColWidth } = this.options;\r\n const containerWidth = this.container.clientWidth;\r\n\r\n this.columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));\r\n const colWidth = (containerWidth - (this.columns - 1) * gutter) / this.columns;\r\n\r\n this.lastPositions = [...this.positions];\r\n this.columnHeights = new Array(this.columns).fill(0);\r\n this.positions = [];\r\n\r\n this.items.forEach((item, i) => {\r\n const height = item.offsetHeight;\r\n\r\n let minCol = 0;\r\n for (let c = 1; c < this.columns; c++) {\r\n if (this.columnHeights[c] < this.columnHeights[minCol]) {\r\n minCol = c;\r\n }\r\n }\r\n\r\n const x = minCol * (colWidth + gutter);\r\n const y = this.columnHeights[minCol];\r\n\r\n this.positions[i] = { x, y, width: colWidth, height };\r\n this.columnHeights[minCol] += height + gutter;\r\n });\r\n\r\n const maxHeight = Math.max(...this.columnHeights);\r\n this.container.style.height = `${maxHeight}px`;\r\n }\r\n\r\n applyLayout(animate: boolean = false) {\r\n const duration = this.options.transitionDuration;\r\n\r\n this.items.forEach((item, i) => {\r\n const pos = this.positions[i] || { x: 0, y: 0, width: 0 };\r\n const lastPos = this.lastPositions[i] || { x: 0, y: 0 };\r\n\r\n item.style.width = `${pos.width}px`;\r\n\r\n if (animate) {\r\n const dx = lastPos.x - pos.x;\r\n const dy = lastPos.y - pos.y;\r\n\r\n item.style.transition = 'none';\r\n item.style.transform = `translate3d(${pos.x + dx}px, ${pos.y + dy}px, 0)`;\r\n void item.offsetHeight;\r\n\r\n item.style.transition = `transform ${duration}ms ease`;\r\n item.style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;\r\n } else {\r\n item.style.transition = 'none';\r\n item.style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;\r\n }\r\n });\r\n }\r\n\r\n private handleResize() {\r\n if (this.resizeRaf) cancelAnimationFrame(this.resizeRaf);\r\n this.resizeRaf = requestAnimationFrame(() => {\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n });\r\n }\r\n\r\n public shuffleItems() {\r\n for (let i = this.items.length - 1; i > 0; i--) {\r\n const j = Math.floor(Math.random() * (i + 1));\r\n [this.items[i], this.items[j]] = [this.items[j], this.items[i]];\r\n }\r\n\r\n const fragment = document.createDocumentFragment();\r\n this.items.forEach((item) => fragment.appendChild(item));\r\n this.container.appendChild(fragment);\r\n\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n }\r\n\r\n public addItems(count: number) {\r\n const newCount = this.items.length + count;\r\n this.generateItems(newCount);\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n }\r\n\r\n public destroy(): void {\r\n if (this.resizeObserver) {\r\n this.resizeObserver.disconnect();\r\n this.resizeObserver = null;\r\n }\r\n\r\n window.removeEventListener('resize', this.boundResizeHandler);\r\n\r\n if (this.resizeRaf) {\r\n cancelAnimationFrame(this.resizeRaf);\r\n this.resizeRaf = null;\r\n }\r\n\r\n this.container.innerHTML = '';\r\n this.container.removeAttribute('style');\r\n this.container.classList.remove(this.classNames.container);\r\n\r\n this.items = [];\r\n this.positions = [];\r\n this.columnHeights = [];\r\n this.columns = 0;\r\n this.lastPositions = [];\r\n }\r\n}"],"mappings":"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","names":["MasonrySnapGrid"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "masonry-snap-grid-layout",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A performant, responsive masonry layout library with smooth animations, dynamic columns, and zero dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"masonry",
|
|
@@ -65,7 +65,8 @@
|
|
|
65
65
|
"tsup": "8.3.5",
|
|
66
66
|
"typescript": "^5.0.0"
|
|
67
67
|
},
|
|
68
|
-
"
|
|
69
|
-
"react": "^19
|
|
68
|
+
"peerDependencies": {
|
|
69
|
+
"react": "^18 || ^19",
|
|
70
|
+
"react-dom": "^18 || ^19"
|
|
70
71
|
}
|
|
71
72
|
}
|