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 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 (!containerRef.current || !layoutRef.current) return;
208
- containerRef.current.innerHTML = "";
209
- }, [items, renderItem]);
208
+ if (!layoutRef.current) return;
209
+ layoutRef.current.calculateLayout();
210
+ }, [items]);
210
211
  return /* @__PURE__ */ jsx(
211
212
  "div",
212
213
  {
@@ -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 (!containerRef.current || !layoutRef.current) return;
226
- containerRef.current.innerHTML = "";
227
- }, [items, renderItem]);
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": "0.0.5",
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
- "dependencies": {
69
- "react": "^19.1.1"
68
+ "peerDependencies": {
69
+ "react": "^18 || ^19",
70
+ "react-dom": "^18 || ^19"
70
71
  }
71
72
  }