masonry-snap-grid-layout 1.0.15 → 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 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.14)
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.
@@ -59,63 +59,76 @@ interface MasonrySnapGridRef {
59
59
  layout: MasonrySnapGridLayout | null;
60
60
  }
61
61
 
62
- /**
63
- * MasonrySnapGridLayout
64
- *
65
- * A lightweight, generic TypeScript implementation of a responsive Masonry-style grid layout
66
- * with optional animations, custom rendering, and automatic re-layout on resize.
67
- *
68
- * @template T - The type of data items that will be rendered into grid elements.
69
- */
70
62
  declare class MasonrySnapGridLayout<T = any> {
71
- /** The container element where the Masonry grid will be rendered. */
72
63
  private readonly container;
73
- /** Fully resolved options object with defaults merged. */
74
64
  private readonly options;
75
- /** A reference to all grid item elements currently rendered. */
76
65
  private items;
77
- /** Tracks the current heights of each column to position new items. */
78
66
  private columnHeights;
79
- /** Observes container resizing to trigger re-layout. */
80
67
  private resizeObserver;
81
- /** Stores requestAnimationFrame ID for layout updates to prevent redundant calls. */
82
68
  private rafId;
69
+ private lastContainerWidth;
70
+ private itemPool;
71
+ private isDestroyed;
72
+ constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions<T>);
83
73
  /**
84
- * Creates a new MasonrySnapGridLayout instance.
85
- *
86
- * @param container - The HTML element that will act as the grid container.
87
- * @param options - Partial configuration object for layout behavior and rendering.
74
+ * Initialize layout: applies base classes, renders initial items,
75
+ * and sets up resize monitoring.
88
76
  */
89
- constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions<T>);
77
+ private init;
90
78
  /**
91
- * Renders the provided items into the container.
92
- * Clears previous items and re-builds DOM structure.
79
+ * Renders items into the container using a pooled DOM strategy:
80
+ * - Avoids DOM churn by reusing elements where possible
81
+ * - Only creates new nodes when needed
82
+ * - Removes unused pool items when shrinking
93
83
  */
94
84
  private renderItems;
95
85
  /**
96
- * Sets up a ResizeObserver to re-calculate layout when container size changes.
86
+ * Sets up a ResizeObserver on the container to trigger re-layout
87
+ * when width changes — throttled to animation frames for performance.
97
88
  */
98
89
  private setupResizeObserver;
99
90
  /**
100
- * Calculates item positions and updates their transforms.
101
- * Also adjusts the container height to fit all items.
91
+ * Core layout function:
92
+ * - Calculates number of columns based on container width & min column width
93
+ * - Measures all items to avoid forced reflows during positioning
94
+ * - Positions items in the shortest column to maintain balance
102
95
  */
103
96
  private updateLayout;
104
97
  /**
105
- * Finds the index of the column with the smallest total height.
106
- *
107
- * @returns Index of the shortest column.
98
+ * Measures item heights without affecting layout:
99
+ * - Temporarily forces block layout for accurate measurement
100
+ * - Restores original styles after measuring
101
+ */
102
+ private measureItems;
103
+ /**
104
+ * Positions items column-by-column:
105
+ * - Chooses the shortest column for each item to maintain balance
106
+ * - Uses transform for GPU-accelerated positioning
107
+ */
108
+ private positionItems;
109
+ /**
110
+ * Sets the container height to match the tallest column
111
+ * while subtracting trailing gutter space for a clean edge.
112
+ */
113
+ private setContainerHeight;
114
+ /**
115
+ * Simple fallback layout in case the Masonry calculation fails:
116
+ * stacks items vertically in one column.
117
+ */
118
+ private applyFallbackLayout;
119
+ /**
120
+ * Finds the column with the least accumulated height.
108
121
  */
109
122
  private findShortestColumn;
110
123
  /**
111
- * Replaces current items with a new set and re-renders the layout.
112
- *
113
- * @param newItems - New set of data items to render.
124
+ * Public method to replace current items and trigger a full re-render.
114
125
  */
115
126
  updateItems(newItems: T[]): void;
116
127
  /**
117
- * Cleans up event listeners, observers, and DOM modifications.
118
- * This should be called before discarding the instance.
128
+ * Cleanly tears down the layout:
129
+ * - Stops observing size changes
130
+ * - Cancels pending animation frames
131
+ * - Clears DOM references and resets container
119
132
  */
120
133
  destroy(): void;
121
134
  }
@@ -59,63 +59,76 @@ interface MasonrySnapGridRef {
59
59
  layout: MasonrySnapGridLayout | null;
60
60
  }
61
61
 
62
- /**
63
- * MasonrySnapGridLayout
64
- *
65
- * A lightweight, generic TypeScript implementation of a responsive Masonry-style grid layout
66
- * with optional animations, custom rendering, and automatic re-layout on resize.
67
- *
68
- * @template T - The type of data items that will be rendered into grid elements.
69
- */
70
62
  declare class MasonrySnapGridLayout<T = any> {
71
- /** The container element where the Masonry grid will be rendered. */
72
63
  private readonly container;
73
- /** Fully resolved options object with defaults merged. */
74
64
  private readonly options;
75
- /** A reference to all grid item elements currently rendered. */
76
65
  private items;
77
- /** Tracks the current heights of each column to position new items. */
78
66
  private columnHeights;
79
- /** Observes container resizing to trigger re-layout. */
80
67
  private resizeObserver;
81
- /** Stores requestAnimationFrame ID for layout updates to prevent redundant calls. */
82
68
  private rafId;
69
+ private lastContainerWidth;
70
+ private itemPool;
71
+ private isDestroyed;
72
+ constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions<T>);
83
73
  /**
84
- * Creates a new MasonrySnapGridLayout instance.
85
- *
86
- * @param container - The HTML element that will act as the grid container.
87
- * @param options - Partial configuration object for layout behavior and rendering.
74
+ * Initialize layout: applies base classes, renders initial items,
75
+ * and sets up resize monitoring.
88
76
  */
89
- constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions<T>);
77
+ private init;
90
78
  /**
91
- * Renders the provided items into the container.
92
- * Clears previous items and re-builds DOM structure.
79
+ * Renders items into the container using a pooled DOM strategy:
80
+ * - Avoids DOM churn by reusing elements where possible
81
+ * - Only creates new nodes when needed
82
+ * - Removes unused pool items when shrinking
93
83
  */
94
84
  private renderItems;
95
85
  /**
96
- * Sets up a ResizeObserver to re-calculate layout when container size changes.
86
+ * Sets up a ResizeObserver on the container to trigger re-layout
87
+ * when width changes — throttled to animation frames for performance.
97
88
  */
98
89
  private setupResizeObserver;
99
90
  /**
100
- * Calculates item positions and updates their transforms.
101
- * Also adjusts the container height to fit all items.
91
+ * Core layout function:
92
+ * - Calculates number of columns based on container width & min column width
93
+ * - Measures all items to avoid forced reflows during positioning
94
+ * - Positions items in the shortest column to maintain balance
102
95
  */
103
96
  private updateLayout;
104
97
  /**
105
- * Finds the index of the column with the smallest total height.
106
- *
107
- * @returns Index of the shortest column.
98
+ * Measures item heights without affecting layout:
99
+ * - Temporarily forces block layout for accurate measurement
100
+ * - Restores original styles after measuring
101
+ */
102
+ private measureItems;
103
+ /**
104
+ * Positions items column-by-column:
105
+ * - Chooses the shortest column for each item to maintain balance
106
+ * - Uses transform for GPU-accelerated positioning
107
+ */
108
+ private positionItems;
109
+ /**
110
+ * Sets the container height to match the tallest column
111
+ * while subtracting trailing gutter space for a clean edge.
112
+ */
113
+ private setContainerHeight;
114
+ /**
115
+ * Simple fallback layout in case the Masonry calculation fails:
116
+ * stacks items vertically in one column.
117
+ */
118
+ private applyFallbackLayout;
119
+ /**
120
+ * Finds the column with the least accumulated height.
108
121
  */
109
122
  private findShortestColumn;
110
123
  /**
111
- * Replaces current items with a new set and re-renders the layout.
112
- *
113
- * @param newItems - New set of data items to render.
124
+ * Public method to replace current items and trigger a full re-render.
114
125
  */
115
126
  updateItems(newItems: T[]): void;
116
127
  /**
117
- * Cleans up event listeners, observers, and DOM modifications.
118
- * This should be called before discarding the instance.
128
+ * Cleanly tears down the layout:
129
+ * - Stops observing size changes
130
+ * - Cancels pending animation frames
131
+ * - Clears DOM references and resets container
119
132
  */
120
133
  destroy(): void;
121
134
  }
package/dist/esm/index.js CHANGED
@@ -1,18 +1,21 @@
1
1
  // src/MasonrySnapGridLayout.ts
2
2
  var MasonrySnapGridLayout = class {
3
- /**
4
- * Creates a new MasonrySnapGridLayout instance.
5
- *
6
- * @param container - The HTML element that will act as the grid container.
7
- * @param options - Partial configuration object for layout behavior and rendering.
8
- */
9
3
  constructor(container, options) {
10
- /** A reference to all grid item elements currently rendered. */
4
+ // Active DOM elements currently in the layout
11
5
  this.items = [];
12
- /** Tracks the current heights of each column to position new items. */
6
+ // Running height for each column (used for placement calculations)
13
7
  this.columnHeights = [];
14
- /** Stores requestAnimationFrame ID for layout updates to prevent redundant calls. */
8
+ // Tracks a pending animation frame request for layout updates
15
9
  this.rafId = null;
10
+ // Cache last measured container width to avoid unnecessary relayouts
11
+ this.lastContainerWidth = 0;
12
+ // Pool of DOM elements for recycling between renders (avoids costly re-creation)
13
+ this.itemPool = [];
14
+ // Flag to prevent operations after destruction
15
+ this.isDestroyed = false;
16
+ if (!container) {
17
+ throw new Error("Container element is required");
18
+ }
16
19
  this.container = container;
17
20
  this.options = {
18
21
  gutter: 16,
@@ -25,87 +28,208 @@ var MasonrySnapGridLayout = class {
25
28
  },
26
29
  ...options
27
30
  };
31
+ this.init();
32
+ }
33
+ /**
34
+ * Initialize layout: applies base classes, renders initial items,
35
+ * and sets up resize monitoring.
36
+ */
37
+ init() {
38
+ if (this.isDestroyed) return;
28
39
  this.container.classList.add(this.options.classNames.container || "");
29
40
  this.renderItems();
30
41
  this.setupResizeObserver();
31
42
  }
32
43
  /**
33
- * Renders the provided items into the container.
34
- * Clears previous items and re-builds DOM structure.
44
+ * Renders items into the container using a pooled DOM strategy:
45
+ * - Avoids DOM churn by reusing elements where possible
46
+ * - Only creates new nodes when needed
47
+ * - Removes unused pool items when shrinking
35
48
  */
36
49
  renderItems() {
37
- this.items.forEach((item) => item.remove());
50
+ if (this.isDestroyed) return;
51
+ this.items.forEach((item) => {
52
+ if (!this.options.items.some((_, i) => this.itemPool[i] === item)) {
53
+ item.remove();
54
+ }
55
+ });
38
56
  this.items = [];
57
+ this.columnHeights = [];
39
58
  const fragment = document.createDocumentFragment();
40
- this.options.items.forEach((itemData) => {
41
- const itemElement = this.options.renderItem(itemData);
42
- itemElement.classList.add(this.options.classNames.item || "");
59
+ this.options.items.forEach((itemData, index) => {
60
+ let itemElement = this.itemPool[index];
61
+ if (!itemElement) {
62
+ itemElement = document.createElement("div");
63
+ itemElement.classList.add(this.options.classNames.item || "");
64
+ this.itemPool[index] = itemElement;
65
+ }
66
+ const content = this.options.renderItem(itemData);
67
+ if (typeof content === "string") {
68
+ itemElement.innerHTML = content;
69
+ } else if (content instanceof Node) {
70
+ itemElement.innerHTML = "";
71
+ itemElement.appendChild(content);
72
+ }
43
73
  fragment.appendChild(itemElement);
44
74
  this.items.push(itemElement);
45
75
  });
76
+ while (this.itemPool.length > this.options.items.length) {
77
+ const item = this.itemPool.pop();
78
+ item.remove();
79
+ }
46
80
  this.container.appendChild(fragment);
47
81
  this.updateLayout();
48
82
  }
49
83
  /**
50
- * Sets up a ResizeObserver to re-calculate layout when container size changes.
84
+ * Sets up a ResizeObserver on the container to trigger re-layout
85
+ * when width changes — throttled to animation frames for performance.
51
86
  */
52
87
  setupResizeObserver() {
88
+ if (this.resizeObserver) {
89
+ this.resizeObserver.disconnect();
90
+ }
53
91
  this.resizeObserver = new ResizeObserver(() => {
54
92
  if (this.rafId) cancelAnimationFrame(this.rafId);
55
- this.rafId = requestAnimationFrame(() => this.updateLayout());
93
+ this.rafId = requestAnimationFrame(() => {
94
+ const newWidth = this.container.clientWidth;
95
+ if (newWidth !== this.lastContainerWidth) {
96
+ this.lastContainerWidth = newWidth;
97
+ this.updateLayout();
98
+ }
99
+ });
56
100
  });
57
101
  this.resizeObserver.observe(this.container);
58
102
  }
59
103
  /**
60
- * Calculates item positions and updates their transforms.
61
- * Also adjusts the container height to fit all items.
104
+ * Core layout function:
105
+ * - Calculates number of columns based on container width & min column width
106
+ * - Measures all items to avoid forced reflows during positioning
107
+ * - Positions items in the shortest column to maintain balance
62
108
  */
63
109
  updateLayout() {
64
- const { gutter, minColWidth, animate, transitionDuration } = this.options;
65
- const containerWidth = this.container.clientWidth;
66
- const columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));
67
- const colWidth = (containerWidth - (columns - 1) * gutter) / columns;
68
- this.columnHeights = new Array(columns).fill(0);
69
- this.items.forEach((item) => {
110
+ if (this.isDestroyed || !this.container.isConnected) return;
111
+ try {
112
+ const { gutter, minColWidth, animate, transitionDuration } = this.options;
113
+ const containerWidth = this.container.clientWidth;
114
+ if (containerWidth <= 0) {
115
+ this.container.style.height = "0";
116
+ return;
117
+ }
118
+ const columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));
119
+ const colWidth = (containerWidth - (columns - 1) * gutter) / columns;
120
+ this.columnHeights = new Array(columns).fill(0);
121
+ const itemHeights = this.measureItems(colWidth);
122
+ this.positionItems(colWidth, gutter, animate, transitionDuration, itemHeights);
123
+ this.setContainerHeight(gutter);
124
+ } catch (error) {
125
+ console.error("Masonry layout failed:", error);
126
+ this.applyFallbackLayout();
127
+ }
128
+ }
129
+ /**
130
+ * Measures item heights without affecting layout:
131
+ * - Temporarily forces block layout for accurate measurement
132
+ * - Restores original styles after measuring
133
+ */
134
+ measureItems(colWidth) {
135
+ return this.items.map((item) => {
136
+ const originalStyles = {
137
+ display: item.style.display,
138
+ visibility: item.style.visibility,
139
+ position: item.style.position,
140
+ width: item.style.width
141
+ };
142
+ item.style.display = "block";
143
+ item.style.visibility = "hidden";
144
+ item.style.position = "absolute";
145
+ item.style.width = `${colWidth}px`;
70
146
  const height = item.offsetHeight;
147
+ Object.assign(item.style, originalStyles);
148
+ return height;
149
+ });
150
+ }
151
+ /**
152
+ * Positions items column-by-column:
153
+ * - Chooses the shortest column for each item to maintain balance
154
+ * - Uses transform for GPU-accelerated positioning
155
+ */
156
+ positionItems(colWidth, gutter, animate, transitionDuration, itemHeights) {
157
+ this.items.forEach((item, index) => {
158
+ const height = itemHeights[index];
71
159
  const minCol = this.findShortestColumn();
72
160
  const x = minCol * (colWidth + gutter);
73
161
  const y = this.columnHeights[minCol];
74
162
  item.style.width = `${colWidth}px`;
75
163
  item.style.transform = `translate3d(${x}px, ${y}px, 0)`;
76
164
  item.style.transition = animate ? `transform ${transitionDuration}ms ease` : "none";
165
+ item.style.willChange = "transform";
77
166
  this.columnHeights[minCol] += height + gutter;
78
167
  });
79
- const maxHeight = Math.max(...this.columnHeights);
80
- this.container.style.height = `${maxHeight}px`;
81
168
  }
82
169
  /**
83
- * Finds the index of the column with the smallest total height.
84
- *
85
- * @returns Index of the shortest column.
170
+ * Sets the container height to match the tallest column
171
+ * while subtracting trailing gutter space for a clean edge.
172
+ */
173
+ setContainerHeight(gutter) {
174
+ const maxHeight = Math.max(0, ...this.columnHeights);
175
+ const containerHeight = maxHeight > 0 ? maxHeight - gutter : 0;
176
+ this.container.style.height = `${containerHeight}px`;
177
+ }
178
+ /**
179
+ * Simple fallback layout in case the Masonry calculation fails:
180
+ * stacks items vertically in one column.
181
+ */
182
+ applyFallbackLayout() {
183
+ let top = 0;
184
+ this.items.forEach((item) => {
185
+ item.style.transform = `translate3d(0, ${top}px, 0)`;
186
+ top += item.offsetHeight + this.options.gutter;
187
+ });
188
+ this.container.style.height = `${top - this.options.gutter}px`;
189
+ }
190
+ /**
191
+ * Finds the column with the least accumulated height.
86
192
  */
87
193
  findShortestColumn() {
88
- return this.columnHeights.indexOf(Math.min(...this.columnHeights));
194
+ let minIndex = 0;
195
+ let minHeight = Infinity;
196
+ this.columnHeights.forEach((height, index) => {
197
+ if (height < minHeight) {
198
+ minHeight = height;
199
+ minIndex = index;
200
+ }
201
+ });
202
+ return minIndex;
89
203
  }
90
204
  /**
91
- * Replaces current items with a new set and re-renders the layout.
92
- *
93
- * @param newItems - New set of data items to render.
205
+ * Public method to replace current items and trigger a full re-render.
94
206
  */
95
207
  updateItems(newItems) {
208
+ if (this.isDestroyed) return;
96
209
  this.options.items = newItems;
97
210
  this.renderItems();
98
211
  }
99
212
  /**
100
- * Cleans up event listeners, observers, and DOM modifications.
101
- * This should be called before discarding the instance.
213
+ * Cleanly tears down the layout:
214
+ * - Stops observing size changes
215
+ * - Cancels pending animation frames
216
+ * - Clears DOM references and resets container
102
217
  */
103
218
  destroy() {
219
+ if (this.isDestroyed) return;
220
+ this.isDestroyed = true;
104
221
  this.resizeObserver?.disconnect();
105
- if (this.rafId) cancelAnimationFrame(this.rafId);
222
+ this.resizeObserver = void 0;
223
+ if (this.rafId) {
224
+ cancelAnimationFrame(this.rafId);
225
+ this.rafId = null;
226
+ }
106
227
  this.container.innerHTML = "";
107
228
  this.container.removeAttribute("style");
108
229
  this.container.classList.remove(this.options.classNames.container || "");
230
+ this.items = [];
231
+ this.columnHeights = [];
232
+ this.itemPool = [];
109
233
  }
110
234
  };
111
235
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/MasonrySnapGridLayout.ts","../../src/index.ts"],"sourcesContent":["import { MasonrySnapGridLayoutOptions } from './types';\n\n/**\n * MasonrySnapGridLayout\n *\n * A lightweight, generic TypeScript implementation of a responsive Masonry-style grid layout\n * with optional animations, custom rendering, and automatic re-layout on resize.\n *\n * @template T - The type of data items that will be rendered into grid elements.\n */\nexport default class MasonrySnapGridLayout<T = any> {\n /** The container element where the Masonry grid will be rendered. */\n private readonly container: HTMLElement;\n\n /** Fully resolved options object with defaults merged. */\n private readonly options: Required<MasonrySnapGridLayoutOptions<T>>;\n\n /** A reference to all grid item elements currently rendered. */\n private items: HTMLElement[] = [];\n\n /** Tracks the current heights of each column to position new items. */\n private columnHeights: number[] = [];\n\n /** Observes container resizing to trigger re-layout. */\n private resizeObserver: ResizeObserver | undefined;\n\n /** Stores requestAnimationFrame ID for layout updates to prevent redundant calls. */\n private rafId: number | null = null;\n\n /**\n * Creates a new MasonrySnapGridLayout instance.\n *\n * @param container - The HTML element that will act as the grid container.\n * @param options - Partial configuration object for layout behavior and rendering.\n */\n constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions<T>) {\n this.container = container;\n\n // Merge default settings with user-provided options\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 // Apply base container class for styling\n this.container.classList.add(this.options.classNames.container || \"\");\n\n // Initial rendering and layout setup\n this.renderItems();\n this.setupResizeObserver();\n }\n\n /**\n * Renders the provided items into the container.\n * Clears previous items and re-builds DOM structure.\n */\n private renderItems() {\n // Remove any previously rendered items\n this.items.forEach(item => item.remove());\n this.items = [];\n\n // Use DocumentFragment for efficient DOM updates\n const fragment = document.createDocumentFragment();\n\n // Create elements for each item and append to fragment\n this.options.items.forEach(itemData => {\n const itemElement = this.options.renderItem(itemData);\n itemElement.classList.add(this.options.classNames.item || \"\");\n fragment.appendChild(itemElement);\n this.items.push(itemElement);\n });\n\n // Append all items to container at once\n this.container.appendChild(fragment);\n\n // Trigger layout positioning\n this.updateLayout();\n }\n\n /**\n * Sets up a ResizeObserver to re-calculate layout when container size changes.\n */\n private setupResizeObserver() {\n this.resizeObserver = new ResizeObserver(() => {\n // Throttle updates to animation frame to avoid layout thrashing\n if (this.rafId) cancelAnimationFrame(this.rafId);\n this.rafId = requestAnimationFrame(() => this.updateLayout());\n });\n this.resizeObserver.observe(this.container);\n }\n\n /**\n * Calculates item positions and updates their transforms.\n * Also adjusts the container height to fit all items.\n */\n private updateLayout() {\n const { gutter, minColWidth, animate, transitionDuration } = this.options;\n const containerWidth = this.container.clientWidth;\n\n // Determine number of columns based on available width and minColWidth\n const columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));\n\n // Calculate actual column width including gutters\n const colWidth = (containerWidth - (columns - 1) * gutter) / columns;\n\n // Reset heights for all columns\n this.columnHeights = new Array(columns).fill(0);\n\n // Position each item in the shortest column\n this.items.forEach((item) => {\n const height = item.offsetHeight;\n const minCol = this.findShortestColumn();\n const x = minCol * (colWidth + gutter);\n const y = this.columnHeights[minCol];\n\n // Apply positioning and animation\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\n // Update column height to account for this item\n this.columnHeights[minCol] += height + gutter;\n });\n\n // Set container height to the tallest column\n const maxHeight = Math.max(...this.columnHeights);\n this.container.style.height = `${maxHeight}px`;\n }\n\n /**\n * Finds the index of the column with the smallest total height.\n *\n * @returns Index of the shortest column.\n */\n private findShortestColumn(): number {\n return this.columnHeights.indexOf(Math.min(...this.columnHeights));\n }\n\n /**\n * Replaces current items with a new set and re-renders the layout.\n *\n * @param newItems - New set of data items to render.\n */\n public updateItems(newItems: T[]) {\n this.options.items = newItems;\n this.renderItems();\n }\n\n /**\n * Cleans up event listeners, observers, and DOM modifications.\n * This should be called before discarding the instance.\n */\n public destroy() {\n this.resizeObserver?.disconnect();\n if (this.rafId) cancelAnimationFrame(this.rafId);\n this.container.innerHTML = '';\n this.container.removeAttribute('style');\n this.container.classList.remove(this.options.classNames.container || \"\");\n }\n}\n","import './index.css'; // Base CSS styles for MasonrySnapGridLayout\nimport MasonrySnapGridLayout from './MasonrySnapGridLayout';\nimport { MasonrySnapGridLayoutOptions } from './types';\n\n/**\n * Entry point for the MasonrySnapGridLayout package.\n *\n * - Imports the default styles for the layout (`index.css`).\n * - Re-exports the core class and associated type definitions.\n * - This allows consumers to either:\n * - Import the default class directly.\n * - Or import named exports for more flexibility.\n */\n\nexport default MasonrySnapGridLayout;\nexport {\n MasonrySnapGridLayout,\n MasonrySnapGridLayoutOptions\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/MasonrySnapGridLayout.ts","../../src/index.ts"],"sourcesContent":["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","import './index.css'; // Base CSS styles for MasonrySnapGridLayout\nimport MasonrySnapGridLayout from './MasonrySnapGridLayout';\nimport { MasonrySnapGridLayoutOptions } from './types';\n\n/**\n * Entry point for the MasonrySnapGridLayout package.\n *\n * - Imports the default styles for the layout (`index.css`).\n * - Re-exports the core class and associated type definitions.\n * - This allows consumers to either:\n * - Import the default class directly.\n * - Or import named exports for more flexibility.\n */\n\nexport default MasonrySnapGridLayout;\nexport {\n MasonrySnapGridLayout,\n MasonrySnapGridLayoutOptions\n};\n"],"mappings":";AAEA,IAAqB,wBAArB,MAAoD;AAAA,EAoBhD,YAAY,WAAwB,SAA0C;AAd9E;AAAA,SAAQ,QAAuB,CAAC;AAEhC;AAAA,SAAQ,gBAA0B,CAAC;AAInC;AAAA,SAAQ,QAAuB;AAE/B;AAAA,SAAQ,qBAAqB;AAE7B;AAAA,SAAQ,WAA0B,CAAC;AAEnC;AAAA,SAAQ,cAAc;AAGlB,QAAI,CAAC,WAAW;AACZ,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACnD;AAEA,SAAK,YAAY;AAEjB,SAAK,UAAU;AAAA,MACX,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,SAAS;AAAA,MACT,oBAAoB;AAAA,MACpB,YAAY;AAAA,QACR,WAAW;AAAA,QACX,MAAM;AAAA,MACV;AAAA,MACA,GAAG;AAAA,IACP;AAEA,SAAK,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,OAAa;AACjB,QAAI,KAAK,YAAa;AAEtB,SAAK,UAAU,UAAU,IAAI,KAAK,QAAQ,WAAW,aAAa,EAAE;AACpE,SAAK,YAAY;AACjB,SAAK,oBAAoB;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,cAAoB;AACxB,QAAI,KAAK,YAAa;AAGtB,SAAK,MAAM,QAAQ,UAAQ;AACvB,UAAI,CAAC,KAAK,QAAQ,MAAM,KAAK,CAAC,GAAG,MAAM,KAAK,SAAS,CAAC,MAAM,IAAI,GAAG;AAC/D,aAAK,OAAO;AAAA,MAChB;AAAA,IACJ,CAAC;AAED,SAAK,QAAQ,CAAC;AACd,SAAK,gBAAgB,CAAC;AAGtB,UAAM,WAAW,SAAS,uBAAuB;AACjD,SAAK,QAAQ,MAAM,QAAQ,CAAC,UAAU,UAAU;AAC5C,UAAI,cAAc,KAAK,SAAS,KAAK;AAErC,UAAI,CAAC,aAAa;AACd,sBAAc,SAAS,cAAc,KAAK;AAC1C,oBAAY,UAAU,IAAI,KAAK,QAAQ,WAAW,QAAQ,EAAE;AAC5D,aAAK,SAAS,KAAK,IAAI;AAAA,MAC3B;AAGA,YAAM,UAAU,KAAK,QAAQ,WAAW,QAAQ;AAChD,UAAI,OAAO,YAAY,UAAU;AAC7B,oBAAY,YAAY;AAAA,MAC5B,WAAW,mBAAmB,MAAM;AAChC,oBAAY,YAAY;AACxB,oBAAY,YAAY,OAAO;AAAA,MACnC;AAEA,eAAS,YAAY,WAAW;AAChC,WAAK,MAAM,KAAK,WAAW;AAAA,IAC/B,CAAC;AAGD,WAAO,KAAK,SAAS,SAAS,KAAK,QAAQ,MAAM,QAAQ;AACrD,YAAM,OAAO,KAAK,SAAS,IAAI;AAC/B,WAAK,OAAO;AAAA,IAChB;AAEA,SAAK,UAAU,YAAY,QAAQ;AACnC,SAAK,aAAa;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,sBAA4B;AAChC,QAAI,KAAK,gBAAgB;AACrB,WAAK,eAAe,WAAW;AAAA,IACnC;AAEA,SAAK,iBAAiB,IAAI,eAAe,MAAM;AAC3C,UAAI,KAAK,MAAO,sBAAqB,KAAK,KAAK;AAC/C,WAAK,QAAQ,sBAAsB,MAAM;AACrC,cAAM,WAAW,KAAK,UAAU;AAChC,YAAI,aAAa,KAAK,oBAAoB;AACtC,eAAK,qBAAqB;AAC1B,eAAK,aAAa;AAAA,QACtB;AAAA,MACJ,CAAC;AAAA,IACL,CAAC;AAED,SAAK,eAAe,QAAQ,KAAK,SAAS;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,eAAqB;AACzB,QAAI,KAAK,eAAe,CAAC,KAAK,UAAU,YAAa;AAErD,QAAI;AACA,YAAM,EAAE,QAAQ,aAAa,SAAS,mBAAmB,IAAI,KAAK;AAClE,YAAM,iBAAiB,KAAK,UAAU;AAGtC,UAAI,kBAAkB,GAAG;AACrB,aAAK,UAAU,MAAM,SAAS;AAC9B;AAAA,MACJ;AAGA,YAAM,UAAU,KAAK,IAAI,GAAG,KAAK,OAAO,iBAAiB,WAAW,cAAc,OAAO,CAAC;AAC1F,YAAM,YAAY,kBAAkB,UAAU,KAAK,UAAU;AAG7D,WAAK,gBAAgB,IAAI,MAAM,OAAO,EAAE,KAAK,CAAC;AAG9C,YAAM,cAAc,KAAK,aAAa,QAAQ;AAG9C,WAAK,cAAc,UAAU,QAAQ,SAAS,oBAAoB,WAAW;AAG7E,WAAK,mBAAmB,MAAM;AAAA,IAClC,SAAS,OAAO;AACZ,cAAQ,MAAM,0BAA0B,KAAK;AAE7C,WAAK,oBAAoB;AAAA,IAC7B;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,aAAa,UAA4B;AAC7C,WAAO,KAAK,MAAM,IAAI,UAAQ;AAC1B,YAAM,iBAAiB;AAAA,QACnB,SAAS,KAAK,MAAM;AAAA,QACpB,YAAY,KAAK,MAAM;AAAA,QACvB,UAAU,KAAK,MAAM;AAAA,QACrB,OAAO,KAAK,MAAM;AAAA,MACtB;AAEA,WAAK,MAAM,UAAU;AACrB,WAAK,MAAM,aAAa;AACxB,WAAK,MAAM,WAAW;AACtB,WAAK,MAAM,QAAQ,GAAG,QAAQ;AAE9B,YAAM,SAAS,KAAK;AAEpB,aAAO,OAAO,KAAK,OAAO,cAAc;AACxC,aAAO;AAAA,IACX,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,cACJ,UACA,QACA,SACA,oBACA,aACI;AACJ,SAAK,MAAM,QAAQ,CAAC,MAAM,UAAU;AAChC,YAAM,SAAS,YAAY,KAAK;AAChC,YAAM,SAAS,KAAK,mBAAmB;AACvC,YAAM,IAAI,UAAU,WAAW;AAC/B,YAAM,IAAI,KAAK,cAAc,MAAM;AAEnC,WAAK,MAAM,QAAQ,GAAG,QAAQ;AAC9B,WAAK,MAAM,YAAY,eAAe,CAAC,OAAO,CAAC;AAC/C,WAAK,MAAM,aAAa,UAClB,aAAa,kBAAkB,YAC/B;AACN,WAAK,MAAM,aAAa;AAExB,WAAK,cAAc,MAAM,KAAK,SAAS;AAAA,IAC3C,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,mBAAmB,QAAsB;AAC7C,UAAM,YAAY,KAAK,IAAI,GAAG,GAAG,KAAK,aAAa;AACnD,UAAM,kBAAkB,YAAY,IAAI,YAAY,SAAS;AAC7D,SAAK,UAAU,MAAM,SAAS,GAAG,eAAe;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,sBAA4B;AAChC,QAAI,MAAM;AACV,SAAK,MAAM,QAAQ,UAAQ;AACvB,WAAK,MAAM,YAAY,kBAAkB,GAAG;AAC5C,aAAO,KAAK,eAAe,KAAK,QAAQ;AAAA,IAC5C,CAAC;AACD,SAAK,UAAU,MAAM,SAAS,GAAG,MAAM,KAAK,QAAQ,MAAM;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAA6B;AACjC,QAAI,WAAW;AACf,QAAI,YAAY;AAEhB,SAAK,cAAc,QAAQ,CAAC,QAAQ,UAAU;AAC1C,UAAI,SAAS,WAAW;AACpB,oBAAY;AACZ,mBAAW;AAAA,MACf;AAAA,IACJ,CAAC;AAED,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA,EAKO,YAAY,UAAqB;AACpC,QAAI,KAAK,YAAa;AACtB,SAAK,QAAQ,QAAQ;AACrB,SAAK,YAAY;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,UAAgB;AACnB,QAAI,KAAK,YAAa;AAEtB,SAAK,cAAc;AAEnB,SAAK,gBAAgB,WAAW;AAChC,SAAK,iBAAiB;AAEtB,QAAI,KAAK,OAAO;AACZ,2BAAqB,KAAK,KAAK;AAC/B,WAAK,QAAQ;AAAA,IACjB;AAEA,SAAK,UAAU,YAAY;AAC3B,SAAK,UAAU,gBAAgB,OAAO;AACtC,SAAK,UAAU,UAAU,OAAO,KAAK,QAAQ,WAAW,aAAa,EAAE;AAEvE,SAAK,QAAQ,CAAC;AACd,SAAK,gBAAgB,CAAC;AACtB,SAAK,WAAW,CAAC;AAAA,EACrB;AACJ;;;AClSA,IAAO,gBAAQ;","names":[]}