masonry-snap-grid-layout 0.0.2 → 0.0.3

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.
@@ -0,0 +1,45 @@
1
+ /* src/masonry-snap-grid-layout.css */
2
+ :root {
3
+ --gutter: 16px;
4
+ --columns: 4;
5
+ --min-col-width: 250px;
6
+ --transition-duration: 0.4s;
7
+ --primary-color: #6b73ff;
8
+ --secondary-color: #000dff;
9
+ --text-color: #fff;
10
+ --shadow-color: rgba(0, 0, 0, 0.1);
11
+ --item-radius: 8px;
12
+ }
13
+ .masonry-snap-grid-layout-container {
14
+ position: relative;
15
+ width: 100%;
16
+ max-width: 1200px;
17
+ margin: 0 auto;
18
+ transition: height var(--transition-duration) ease-out;
19
+ }
20
+ .masonry-snap-grid-layout-item {
21
+ position: absolute;
22
+ color: var(--text-color);
23
+ border-radius: var(--item-radius);
24
+ font-size: 1rem;
25
+ box-shadow: 0 6px 12px var(--shadow-color);
26
+ user-select: none;
27
+ cursor: grab;
28
+ will-change: transform;
29
+ transition:
30
+ transform var(--transition-duration) cubic-bezier(0.4, 0, 0.2, 1),
31
+ box-shadow 0.3s ease,
32
+ background 0.4s ease;
33
+ overflow: hidden;
34
+ z-index: 1;
35
+ }
36
+ .masonry-snap-grid-layout-item:hover {
37
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
38
+ transform: translateY(-2px);
39
+ }
40
+ .masonry-snap-grid-layout-item:active {
41
+ cursor: grabbing;
42
+ box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2);
43
+ z-index: 10;
44
+ }
45
+ /*# sourceMappingURL=index.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/masonry-snap-grid-layout.css"],"sourcesContent":["/*\r\n Masonry Snap Grid Layout Styles\r\n\r\n CSS variables for easy customization:\r\n --gutter: spacing between items\r\n --columns: number of columns (calculated dynamically)\r\n --min-col-width: minimum width of columns\r\n --transition-duration: animation timing\r\n --primary-color, --secondary-color: gradient colors for items\r\n --text-color: color for text inside items\r\n --shadow-color: shadow color for item depth\r\n --item-radius: border-radius for rounded corners\r\n*/\r\n\r\n/* Root CSS variables for theming */\r\n:root {\r\n --gutter: 16px;\r\n --columns: 4;\r\n --min-col-width: 250px;\r\n --transition-duration: 0.4s;\r\n --primary-color: #6b73ff;\r\n --secondary-color: #000dff;\r\n --text-color: #fff;\r\n --shadow-color: rgba(0, 0, 0, 0.1);\r\n --item-radius: 8px;\r\n}\r\n\r\n/* Container for masonry grid — relative positioning for absolute item placement */\r\n.masonry-snap-grid-layout-container {\r\n position: relative;\r\n width: 100%;\r\n max-width: 1200px;\r\n margin: 0 auto;\r\n transition: height var(--transition-duration) ease-out;\r\n}\r\n\r\n/* Each item absolutely positioned within container */\r\n.masonry-snap-grid-layout-item {\r\n position: absolute;\r\n color: var(--text-color);\r\n border-radius: var(--item-radius);\r\n font-size: 1rem;\r\n box-shadow: 0 6px 12px var(--shadow-color);\r\n user-select: none;\r\n cursor: grab;\r\n will-change: transform;\r\n transition:\r\n transform var(--transition-duration) cubic-bezier(0.4, 0, 0.2, 1),\r\n box-shadow 0.3s ease,\r\n background 0.4s ease;\r\n overflow: hidden;\r\n z-index: 1;\r\n}\r\n\r\n/* Hover state for better UX */\r\n.masonry-snap-grid-layout-item:hover {\r\n box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);\r\n transform: translateY(-2px);\r\n}\r\n\r\n/* Active / grabbing state */\r\n.masonry-snap-grid-layout-item:active {\r\n cursor: grabbing;\r\n box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2);\r\n z-index: 10;\r\n}\r\n\r\n"],"mappings":";AAeA;AACI,YAAU;AACV,aAAW;AACX,mBAAiB;AACjB,yBAAuB;AACvB,mBAAiB;AACjB,qBAAmB;AACnB,gBAAc;AACd,kBAAgB,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAC9B,iBAAe;AACnB;AAGA,CAAC;AACG,YAAU;AACV,SAAO;AACP,aAAW;AACX,UAAQ,EAAE;AACV,cAAY,OAAO,IAAI,uBAAuB;AAClD;AAGA,CAAC;AACG,YAAU;AACV,SAAO,IAAI;AACX,iBAAe,IAAI;AACnB,aAAW;AACX,cAAY,EAAE,IAAI,KAAK,IAAI;AAC3B,eAAa;AACb,UAAQ;AACR,eAAa;AACb;AAAA,IACQ,UAAU,IAAI,uBAAuB,aAAa,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE;AAAA,IACjE,WAAW,KAAK,IAAI;AAAA,IACpB,WAAW,KAAK;AACxB,YAAU;AACV,WAAS;AACb;AAGA,CAlBC,6BAkB6B;AAC1B,cAAY,EAAE,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACrC,aAAW,WAAW;AAC1B;AAGA,CAxBC,6BAwB6B;AAC1B,UAAQ;AACR,cAAY,EAAE,KAAK,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACtC,WAAS;AACb;","names":[]}
package/dist/esm/index.js CHANGED
@@ -1,21 +1,18 @@
1
1
  // src/MasonrySnapGridLayout.ts
2
2
  var MasonrySnapGridLayout = class {
3
- container;
4
- options;
5
- classNames;
6
- items = [];
7
- positions = [];
8
- columnHeights = [];
9
- columns = 0;
10
- lastPositions = [];
11
- resizeRaf = null;
12
3
  /**
13
4
  * Constructor initializes the layout with container element and options
14
5
  * @param container HTMLElement container where masonry items are rendered
15
6
  * @param options configuration options for layout and styling
16
7
  */
17
- constructor(container, options = {}) {
18
- this.container = container;
8
+ constructor(container2, options = {}) {
9
+ this.items = [];
10
+ this.positions = [];
11
+ this.columnHeights = [];
12
+ this.columns = 0;
13
+ this.lastPositions = [];
14
+ this.resizeRaf = null;
15
+ this.container = container2;
19
16
  this.options = {
20
17
  gutter: 16,
21
18
  minColWidth: 250,
@@ -235,6 +232,31 @@ var MasonrySnapGridLayout = class {
235
232
  };
236
233
 
237
234
  // src/index.ts
235
+ var container = document.getElementById("masonry");
236
+ if (container) {
237
+ const masonry = new MasonrySnapGridLayout(container, {
238
+ gutter: 20,
239
+ minColWidth: 200,
240
+ animate: true,
241
+ initialItems: 40,
242
+ // Custom item content example
243
+ itemContent: (index) => {
244
+ const div = document.createElement("div");
245
+ div.textContent = `Custom Item ${index + 1}`;
246
+ div.style.color = "#fff";
247
+ div.style.fontWeight = "bold";
248
+ div.style.textAlign = "center";
249
+ div.style.padding = "1rem";
250
+ return div;
251
+ },
252
+ classNames: {
253
+ container: "masonry-snap-grid-layout-container",
254
+ item: "masonry-snap-grid-layout-item"
255
+ }
256
+ });
257
+ masonry.addItems(5);
258
+ masonry.shuffleItems();
259
+ }
238
260
  var index_default = MasonrySnapGridLayout;
239
261
  export {
240
262
  MasonrySnapGridLayout,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/MasonrySnapGridLayout.ts","../../src/index.ts"],"sourcesContent":["/**\r\n * MasonrySnapGridLayout\r\n * A performant masonry grid layout library with smooth animations,\r\n * customizable gutter, columns, and dynamic item content.\r\n *\r\n * Package name: masonry-snap-grid-layout\r\n */\r\n\r\nimport { 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 /**\r\n * Constructor initializes the layout with container element and options\r\n * @param container HTMLElement container where masonry items are rendered\r\n * @param options configuration options for layout and styling\r\n */\r\n constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions = {}) {\r\n this.container = container;\r\n\r\n // Set default options with overrides from user\r\n this.options = {\r\n gutter: 16,\r\n minColWidth: 250,\r\n animate: true,\r\n transitionDuration: 400,\r\n initialItems: 30,\r\n itemContent: null,\r\n classNames: {\r\n container: \"masonry-snap-grid-layout-container\",\r\n item: \"masonry-snap-grid-layout-item\",\r\n itemContent: \"item-content\",\r\n itemHeader: \"item-header\",\r\n itemTitle: \"item-title\",\r\n itemId: \"item-id\",\r\n itemBody: \"item-body\",\r\n progressBar: \"progress-bar\",\r\n progress: \"progress\",\r\n itemFooter: \"item-footer\",\r\n },\r\n ...options,\r\n };\r\n\r\n // Merge default classNames with user provided classNames\r\n this.classNames = {\r\n container: \"masonry-snap-grid-layout-container\",\r\n item: \"masonry-snap-grid-layout-item\",\r\n itemContent: \"item-content\",\r\n itemHeader: \"item-header\",\r\n itemTitle: \"item-title\",\r\n itemId: \"item-id\",\r\n itemBody: \"item-body\",\r\n progressBar: \"progress-bar\",\r\n progress: \"progress\",\r\n itemFooter: \"item-footer\",\r\n ...(this.options.classNames || {}),\r\n };\r\n\r\n // Add container class for styling\r\n this.container.classList.add(this.classNames.container);\r\n\r\n // Initialize the layout\r\n this.init();\r\n }\r\n\r\n /**\r\n * Initialization: set listeners, generate initial items, layout\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 /**\r\n * Setup event listeners for window resize and container resize observer\r\n */\r\n private setupEventListeners() {\r\n // Use ResizeObserver to handle container size changes\r\n const resizeObserver = new ResizeObserver(() => this.handleResize());\r\n resizeObserver.observe(this.container);\r\n\r\n window.addEventListener(\"resize\", () => this.handleResize());\r\n }\r\n\r\n /**\r\n * Generate specified number of items, removing excess if any\r\n * @param count number of items to generate\r\n */\r\n private generateItems(count: number) {\r\n // Remove extra items if reducing count\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 /**\r\n * Create a single masonry item HTMLElement, with content from renderItemContent callback or default template\r\n * @param index index of the item\r\n * @returns HTMLElement for the item\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\r\n // Determine content type: function, static HTMLElement, static string, or fallback to default\r\n let content: HTMLElement | string;\r\n\r\n if (typeof contentOption === \"function\") {\r\n // Call function with index to get content\r\n content = contentOption(index);\r\n } else if (contentOption instanceof HTMLElement || typeof contentOption === \"string\") {\r\n // Use static content\r\n content = contentOption;\r\n } else {\r\n // Fallback to default item content template string\r\n content = this.defaultItemContent(index);\r\n }\r\n\r\n // Insert content into item element\r\n if (typeof content === \"string\") {\r\n div.innerHTML = content;\r\n } else {\r\n div.appendChild(content);\r\n }\r\n\r\n // Random height to simulate masonry effect\r\n const height = 120 + Math.floor(Math.random() * 180);\r\n div.style.height = `${height}px`;\r\n\r\n // Color with HSL for distinct appearance\r\n const hue = (index * 137.508) % 360; // golden angle\r\n div.style.background = `linear-gradient(135deg, hsl(${hue}, 70%, 60%), hsl(${hue + 40}, 70%, 50%))`;\r\n\r\n return div;\r\n }\r\n\r\n /**\r\n * Default item content template string\r\n * @param index index of the item\r\n * @returns string HTML template for item content\r\n */\r\n private defaultItemContent(index: number): string {\r\n return `\r\n <div class=\"${this.classNames.itemContent}\">\r\n <div class=\"${this.classNames.itemHeader}\">\r\n <div class=\"${this.classNames.itemTitle}\">Item ${index + 1}</div>\r\n <div class=\"${this.classNames.itemId}\">#${index + 1}</div>\r\n </div>\r\n <div class=\"${this.classNames.itemBody}\">\r\n <div class=\"${this.classNames.progressBar}\">\r\n <div class=\"${this.classNames.progress}\"></div>\r\n </div>\r\n </div>\r\n <div class=\"${this.classNames.itemFooter}\">\r\n ${this.getRandomEmoji()}\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n /**\r\n * Returns a random emoji from a fixed set for item footer\r\n */\r\n private getRandomEmoji(): string {\r\n const emojis = [\"🚀\", \"✨\", \"🔥\", \"💡\", \"🌟\", \"🎯\", \"⚡\", \"💻\", \"🔧\", \"📊\"];\r\n return emojis[Math.floor(Math.random() * emojis.length)];\r\n }\r\n\r\n /**\r\n * Calculate positions of each item in masonry grid based on container width, gutter, min column width\r\n */\r\n private calculateLayout() {\r\n const { gutter, minColWidth } = this.options;\r\n const containerWidth = this.container.clientWidth;\r\n\r\n // Calculate number of columns that fit\r\n this.columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));\r\n\r\n // Calculate each column width\r\n const colWidth = (containerWidth - (this.columns - 1) * gutter) / this.columns;\r\n\r\n // Store previous positions for animation\r\n this.lastPositions = [...this.positions];\r\n\r\n // Reset column heights array\r\n this.columnHeights = new Array(this.columns).fill(0);\r\n this.positions = [];\r\n\r\n // Calculate position for each item and assign column\r\n this.items.forEach((item, i) => {\r\n const height = item.offsetHeight;\r\n\r\n // Find the shortest column index\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 // Calculate item's x,y position\r\n const x = minCol * (colWidth + gutter);\r\n const y = this.columnHeights[minCol];\r\n\r\n // Save calculated position\r\n this.positions[i] = { x, y, width: colWidth, height };\r\n\r\n // Update column height to include this item + gutter\r\n this.columnHeights[minCol] += height + gutter;\r\n });\r\n\r\n // Set container height to max column height for proper scrolling\r\n const maxHeight = Math.max(...this.columnHeights);\r\n this.container.style.height = `${maxHeight}px`;\r\n }\r\n\r\n /**\r\n * Apply calculated positions to each item with optional animation\r\n * @param animate whether to animate layout changes (default false)\r\n */\r\n private 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 // Set item width for responsive columns\r\n item.style.width = `${pos.width}px`;\r\n\r\n if (animate) {\r\n // Calculate differences for smooth animation\r\n const dx = lastPos.x - pos.x;\r\n const dy = lastPos.y - pos.y;\r\n\r\n // Apply initial transform to old position (without transition)\r\n item.style.transition = \"none\";\r\n item.style.transform = `translate3d(${pos.x + dx}px, ${pos.y + dy}px, 0)`;\r\n\r\n // Trigger reflow to apply the style immediately\r\n void item.offsetHeight;\r\n\r\n // Animate transform to new position\r\n item.style.transition = `transform ${duration}ms cubic-bezier(0.4, 0, 0.2, 1)`;\r\n item.style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;\r\n } else {\r\n // Directly set transform without animation\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 /**\r\n * Handle container resize using RAF to optimize performance\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 /**\r\n * Shuffle items randomly and reapply layout with animation\r\n */\r\n public shuffleItems() {\r\n // Fisher-Yates shuffle algorithm\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 // Re-append items in new order in DOM\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 // Update layout positions and animate\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n }\r\n\r\n /**\r\n * Add more items dynamically\r\n * @param count number of items to add\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","import MasonrySnapGridLayout from './MasonrySnapGridLayout';\nexport default MasonrySnapGridLayout;\nexport { MasonrySnapGridLayout };"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/MasonrySnapGridLayout.ts","../../src/index.ts"],"sourcesContent":["/**\r\n * MasonrySnapGridLayout\r\n * A performant masonry grid layout library with smooth animations,\r\n * customizable gutter, columns, and dynamic item content.\r\n *\r\n * Package name: masonry-snap-grid-layout\r\n */\r\n\r\nimport { 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 /**\r\n * Constructor initializes the layout with container element and options\r\n * @param container HTMLElement container where masonry items are rendered\r\n * @param options configuration options for layout and styling\r\n */\r\n constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions = {}) {\r\n this.container = container;\r\n\r\n // Set default options with overrides from user\r\n this.options = {\r\n gutter: 16,\r\n minColWidth: 250,\r\n animate: true,\r\n transitionDuration: 400,\r\n initialItems: 30,\r\n itemContent: null,\r\n classNames: {\r\n container: \"masonry-snap-grid-layout-container\",\r\n item: \"masonry-snap-grid-layout-item\",\r\n itemContent: \"item-content\",\r\n itemHeader: \"item-header\",\r\n itemTitle: \"item-title\",\r\n itemId: \"item-id\",\r\n itemBody: \"item-body\",\r\n progressBar: \"progress-bar\",\r\n progress: \"progress\",\r\n itemFooter: \"item-footer\",\r\n },\r\n ...options,\r\n };\r\n\r\n // Merge default classNames with user provided classNames\r\n this.classNames = {\r\n container: \"masonry-snap-grid-layout-container\",\r\n item: \"masonry-snap-grid-layout-item\",\r\n itemContent: \"item-content\",\r\n itemHeader: \"item-header\",\r\n itemTitle: \"item-title\",\r\n itemId: \"item-id\",\r\n itemBody: \"item-body\",\r\n progressBar: \"progress-bar\",\r\n progress: \"progress\",\r\n itemFooter: \"item-footer\",\r\n ...(this.options.classNames || {}),\r\n };\r\n\r\n // Add container class for styling\r\n this.container.classList.add(this.classNames.container);\r\n\r\n // Initialize the layout\r\n this.init();\r\n }\r\n\r\n /**\r\n * Initialization: set listeners, generate initial items, layout\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 /**\r\n * Setup event listeners for window resize and container resize observer\r\n */\r\n private setupEventListeners() {\r\n // Use ResizeObserver to handle container size changes\r\n const resizeObserver = new ResizeObserver(() => this.handleResize());\r\n resizeObserver.observe(this.container);\r\n\r\n window.addEventListener(\"resize\", () => this.handleResize());\r\n }\r\n\r\n /**\r\n * Generate specified number of items, removing excess if any\r\n * @param count number of items to generate\r\n */\r\n private generateItems(count: number) {\r\n // Remove extra items if reducing count\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 /**\r\n * Create a single masonry item HTMLElement, with content from renderItemContent callback or default template\r\n * @param index index of the item\r\n * @returns HTMLElement for the item\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\r\n // Determine content type: function, static HTMLElement, static string, or fallback to default\r\n let content: HTMLElement | string;\r\n\r\n if (typeof contentOption === \"function\") {\r\n // Call function with index to get content\r\n content = contentOption(index);\r\n } else if (contentOption instanceof HTMLElement || typeof contentOption === \"string\") {\r\n // Use static content\r\n content = contentOption;\r\n } else {\r\n // Fallback to default item content template string\r\n content = this.defaultItemContent(index);\r\n }\r\n\r\n // Insert content into item element\r\n if (typeof content === \"string\") {\r\n div.innerHTML = content;\r\n } else {\r\n div.appendChild(content);\r\n }\r\n\r\n // Random height to simulate masonry effect\r\n const height = 120 + Math.floor(Math.random() * 180);\r\n div.style.height = `${height}px`;\r\n\r\n // Color with HSL for distinct appearance\r\n const hue = (index * 137.508) % 360; // golden angle\r\n div.style.background = `linear-gradient(135deg, hsl(${hue}, 70%, 60%), hsl(${hue + 40}, 70%, 50%))`;\r\n\r\n return div;\r\n }\r\n\r\n /**\r\n * Default item content template string\r\n * @param index index of the item\r\n * @returns string HTML template for item content\r\n */\r\n private defaultItemContent(index: number): string {\r\n return `\r\n <div class=\"${this.classNames.itemContent}\">\r\n <div class=\"${this.classNames.itemHeader}\">\r\n <div class=\"${this.classNames.itemTitle}\">Item ${index + 1}</div>\r\n <div class=\"${this.classNames.itemId}\">#${index + 1}</div>\r\n </div>\r\n <div class=\"${this.classNames.itemBody}\">\r\n <div class=\"${this.classNames.progressBar}\">\r\n <div class=\"${this.classNames.progress}\"></div>\r\n </div>\r\n </div>\r\n <div class=\"${this.classNames.itemFooter}\">\r\n ${this.getRandomEmoji()}\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n /**\r\n * Returns a random emoji from a fixed set for item footer\r\n */\r\n private getRandomEmoji(): string {\r\n const emojis = [\"🚀\", \"✨\", \"🔥\", \"💡\", \"🌟\", \"🎯\", \"⚡\", \"💻\", \"🔧\", \"📊\"];\r\n return emojis[Math.floor(Math.random() * emojis.length)];\r\n }\r\n\r\n /**\r\n * Calculate positions of each item in masonry grid based on container width, gutter, min column width\r\n */\r\n private calculateLayout() {\r\n const { gutter, minColWidth } = this.options;\r\n const containerWidth = this.container.clientWidth;\r\n\r\n // Calculate number of columns that fit\r\n this.columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));\r\n\r\n // Calculate each column width\r\n const colWidth = (containerWidth - (this.columns - 1) * gutter) / this.columns;\r\n\r\n // Store previous positions for animation\r\n this.lastPositions = [...this.positions];\r\n\r\n // Reset column heights array\r\n this.columnHeights = new Array(this.columns).fill(0);\r\n this.positions = [];\r\n\r\n // Calculate position for each item and assign column\r\n this.items.forEach((item, i) => {\r\n const height = item.offsetHeight;\r\n\r\n // Find the shortest column index\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 // Calculate item's x,y position\r\n const x = minCol * (colWidth + gutter);\r\n const y = this.columnHeights[minCol];\r\n\r\n // Save calculated position\r\n this.positions[i] = { x, y, width: colWidth, height };\r\n\r\n // Update column height to include this item + gutter\r\n this.columnHeights[minCol] += height + gutter;\r\n });\r\n\r\n // Set container height to max column height for proper scrolling\r\n const maxHeight = Math.max(...this.columnHeights);\r\n this.container.style.height = `${maxHeight}px`;\r\n }\r\n\r\n /**\r\n * Apply calculated positions to each item with optional animation\r\n * @param animate whether to animate layout changes (default false)\r\n */\r\n private 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 // Set item width for responsive columns\r\n item.style.width = `${pos.width}px`;\r\n\r\n if (animate) {\r\n // Calculate differences for smooth animation\r\n const dx = lastPos.x - pos.x;\r\n const dy = lastPos.y - pos.y;\r\n\r\n // Apply initial transform to old position (without transition)\r\n item.style.transition = \"none\";\r\n item.style.transform = `translate3d(${pos.x + dx}px, ${pos.y + dy}px, 0)`;\r\n\r\n // Trigger reflow to apply the style immediately\r\n void item.offsetHeight;\r\n\r\n // Animate transform to new position\r\n item.style.transition = `transform ${duration}ms cubic-bezier(0.4, 0, 0.2, 1)`;\r\n item.style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;\r\n } else {\r\n // Directly set transform without animation\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 /**\r\n * Handle container resize using RAF to optimize performance\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 /**\r\n * Shuffle items randomly and reapply layout with animation\r\n */\r\n public shuffleItems() {\r\n // Fisher-Yates shuffle algorithm\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 // Re-append items in new order in DOM\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 // Update layout positions and animate\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n }\r\n\r\n /**\r\n * Add more items dynamically\r\n * @param count number of items to add\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","import './masonry-snap-grid-layout.css';\r\nimport MasonrySnapGridLayout from './MasonrySnapGridLayout';\r\n\r\nconst container = document.getElementById('masonry');\r\nif (container) {\r\n const masonry = new MasonrySnapGridLayout(container, {\r\n gutter: 20,\r\n minColWidth: 200,\r\n animate: true,\r\n initialItems: 40,\r\n\r\n // Custom item content example\r\n itemContent: (index) => {\r\n const div = document.createElement('div');\r\n div.textContent = `Custom Item ${index + 1}`;\r\n div.style.color = '#fff';\r\n div.style.fontWeight = 'bold';\r\n div.style.textAlign = 'center';\r\n div.style.padding = '1rem';\r\n return div;\r\n },\r\n\r\n classNames: {\r\n container: 'masonry-snap-grid-layout-container',\r\n item: 'masonry-snap-grid-layout-item',\r\n },\r\n });\r\n\r\n // Example to update count later\r\n masonry.addItems(5);\r\n\r\n // Example to shuffle items\r\n masonry.shuffleItems();\r\n}\r\n\r\nexport default MasonrySnapGridLayout;\r\nexport { MasonrySnapGridLayout };"],"mappings":"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","names":["container"]}
package/dist/index.css ADDED
@@ -0,0 +1,45 @@
1
+ /* src/masonry-snap-grid-layout.css */
2
+ :root {
3
+ --gutter: 16px;
4
+ --columns: 4;
5
+ --min-col-width: 250px;
6
+ --transition-duration: 0.4s;
7
+ --primary-color: #6b73ff;
8
+ --secondary-color: #000dff;
9
+ --text-color: #fff;
10
+ --shadow-color: rgba(0, 0, 0, 0.1);
11
+ --item-radius: 8px;
12
+ }
13
+ .masonry-snap-grid-layout-container {
14
+ position: relative;
15
+ width: 100%;
16
+ max-width: 1200px;
17
+ margin: 0 auto;
18
+ transition: height var(--transition-duration) ease-out;
19
+ }
20
+ .masonry-snap-grid-layout-item {
21
+ position: absolute;
22
+ color: var(--text-color);
23
+ border-radius: var(--item-radius);
24
+ font-size: 1rem;
25
+ box-shadow: 0 6px 12px var(--shadow-color);
26
+ user-select: none;
27
+ cursor: grab;
28
+ will-change: transform;
29
+ transition:
30
+ transform var(--transition-duration) cubic-bezier(0.4, 0, 0.2, 1),
31
+ box-shadow 0.3s ease,
32
+ background 0.4s ease;
33
+ overflow: hidden;
34
+ z-index: 1;
35
+ }
36
+ .masonry-snap-grid-layout-item:hover {
37
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
38
+ transform: translateY(-2px);
39
+ }
40
+ .masonry-snap-grid-layout-item:active {
41
+ cursor: grabbing;
42
+ box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2);
43
+ z-index: 10;
44
+ }
45
+ /*# sourceMappingURL=index.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/masonry-snap-grid-layout.css"],"sourcesContent":["/*\r\n Masonry Snap Grid Layout Styles\r\n\r\n CSS variables for easy customization:\r\n --gutter: spacing between items\r\n --columns: number of columns (calculated dynamically)\r\n --min-col-width: minimum width of columns\r\n --transition-duration: animation timing\r\n --primary-color, --secondary-color: gradient colors for items\r\n --text-color: color for text inside items\r\n --shadow-color: shadow color for item depth\r\n --item-radius: border-radius for rounded corners\r\n*/\r\n\r\n/* Root CSS variables for theming */\r\n:root {\r\n --gutter: 16px;\r\n --columns: 4;\r\n --min-col-width: 250px;\r\n --transition-duration: 0.4s;\r\n --primary-color: #6b73ff;\r\n --secondary-color: #000dff;\r\n --text-color: #fff;\r\n --shadow-color: rgba(0, 0, 0, 0.1);\r\n --item-radius: 8px;\r\n}\r\n\r\n/* Container for masonry grid — relative positioning for absolute item placement */\r\n.masonry-snap-grid-layout-container {\r\n position: relative;\r\n width: 100%;\r\n max-width: 1200px;\r\n margin: 0 auto;\r\n transition: height var(--transition-duration) ease-out;\r\n}\r\n\r\n/* Each item absolutely positioned within container */\r\n.masonry-snap-grid-layout-item {\r\n position: absolute;\r\n color: var(--text-color);\r\n border-radius: var(--item-radius);\r\n font-size: 1rem;\r\n box-shadow: 0 6px 12px var(--shadow-color);\r\n user-select: none;\r\n cursor: grab;\r\n will-change: transform;\r\n transition:\r\n transform var(--transition-duration) cubic-bezier(0.4, 0, 0.2, 1),\r\n box-shadow 0.3s ease,\r\n background 0.4s ease;\r\n overflow: hidden;\r\n z-index: 1;\r\n}\r\n\r\n/* Hover state for better UX */\r\n.masonry-snap-grid-layout-item:hover {\r\n box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);\r\n transform: translateY(-2px);\r\n}\r\n\r\n/* Active / grabbing state */\r\n.masonry-snap-grid-layout-item:active {\r\n cursor: grabbing;\r\n box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2);\r\n z-index: 10;\r\n}\r\n\r\n"],"mappings":";AAeA;AACI,YAAU;AACV,aAAW;AACX,mBAAiB;AACjB,yBAAuB;AACvB,mBAAiB;AACjB,qBAAmB;AACnB,gBAAc;AACd,kBAAgB,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAC9B,iBAAe;AACnB;AAGA,CAAC;AACG,YAAU;AACV,SAAO;AACP,aAAW;AACX,UAAQ,EAAE;AACV,cAAY,OAAO,IAAI,uBAAuB;AAClD;AAGA,CAAC;AACG,YAAU;AACV,SAAO,IAAI;AACX,iBAAe,IAAI;AACnB,aAAW;AACX,cAAY,EAAE,IAAI,KAAK,IAAI;AAC3B,eAAa;AACb,UAAQ;AACR,eAAa;AACb;AAAA,IACQ,UAAU,IAAI,uBAAuB,aAAa,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE;AAAA,IACjE,WAAW,KAAK,IAAI;AAAA,IACpB,WAAW,KAAK;AACxB,YAAU;AACV,WAAS;AACb;AAGA,CAlBC,6BAkB6B;AAC1B,cAAY,EAAE,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACrC,aAAW,WAAW;AAC1B;AAGA,CAxBC,6BAwB6B;AAC1B,UAAQ;AACR,cAAY,EAAE,KAAK,KAAK,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACtC,WAAS;AACb;","names":[]}
package/dist/index.js CHANGED
@@ -27,22 +27,19 @@ module.exports = __toCommonJS(index_exports);
27
27
 
28
28
  // src/MasonrySnapGridLayout.ts
29
29
  var MasonrySnapGridLayout = class {
30
- container;
31
- options;
32
- classNames;
33
- items = [];
34
- positions = [];
35
- columnHeights = [];
36
- columns = 0;
37
- lastPositions = [];
38
- resizeRaf = null;
39
30
  /**
40
31
  * Constructor initializes the layout with container element and options
41
32
  * @param container HTMLElement container where masonry items are rendered
42
33
  * @param options configuration options for layout and styling
43
34
  */
44
- constructor(container, options = {}) {
45
- this.container = container;
35
+ constructor(container2, options = {}) {
36
+ this.items = [];
37
+ this.positions = [];
38
+ this.columnHeights = [];
39
+ this.columns = 0;
40
+ this.lastPositions = [];
41
+ this.resizeRaf = null;
42
+ this.container = container2;
46
43
  this.options = {
47
44
  gutter: 16,
48
45
  minColWidth: 250,
@@ -262,6 +259,31 @@ var MasonrySnapGridLayout = class {
262
259
  };
263
260
 
264
261
  // src/index.ts
262
+ var container = document.getElementById("masonry");
263
+ if (container) {
264
+ const masonry = new MasonrySnapGridLayout(container, {
265
+ gutter: 20,
266
+ minColWidth: 200,
267
+ animate: true,
268
+ initialItems: 40,
269
+ // Custom item content example
270
+ itemContent: (index) => {
271
+ const div = document.createElement("div");
272
+ div.textContent = `Custom Item ${index + 1}`;
273
+ div.style.color = "#fff";
274
+ div.style.fontWeight = "bold";
275
+ div.style.textAlign = "center";
276
+ div.style.padding = "1rem";
277
+ return div;
278
+ },
279
+ classNames: {
280
+ container: "masonry-snap-grid-layout-container",
281
+ item: "masonry-snap-grid-layout-item"
282
+ }
283
+ });
284
+ masonry.addItems(5);
285
+ masonry.shuffleItems();
286
+ }
265
287
  var index_default = MasonrySnapGridLayout;
266
288
  // Annotate the CommonJS export names for ESM import in node:
267
289
  0 && (module.exports = {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/MasonrySnapGridLayout.ts"],"sourcesContent":["import MasonrySnapGridLayout from './MasonrySnapGridLayout';\nexport default MasonrySnapGridLayout;\nexport { MasonrySnapGridLayout };","/**\r\n * MasonrySnapGridLayout\r\n * A performant masonry grid layout library with smooth animations,\r\n * customizable gutter, columns, and dynamic item content.\r\n *\r\n * Package name: masonry-snap-grid-layout\r\n */\r\n\r\nimport { 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 /**\r\n * Constructor initializes the layout with container element and options\r\n * @param container HTMLElement container where masonry items are rendered\r\n * @param options configuration options for layout and styling\r\n */\r\n constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions = {}) {\r\n this.container = container;\r\n\r\n // Set default options with overrides from user\r\n this.options = {\r\n gutter: 16,\r\n minColWidth: 250,\r\n animate: true,\r\n transitionDuration: 400,\r\n initialItems: 30,\r\n itemContent: null,\r\n classNames: {\r\n container: \"masonry-snap-grid-layout-container\",\r\n item: \"masonry-snap-grid-layout-item\",\r\n itemContent: \"item-content\",\r\n itemHeader: \"item-header\",\r\n itemTitle: \"item-title\",\r\n itemId: \"item-id\",\r\n itemBody: \"item-body\",\r\n progressBar: \"progress-bar\",\r\n progress: \"progress\",\r\n itemFooter: \"item-footer\",\r\n },\r\n ...options,\r\n };\r\n\r\n // Merge default classNames with user provided classNames\r\n this.classNames = {\r\n container: \"masonry-snap-grid-layout-container\",\r\n item: \"masonry-snap-grid-layout-item\",\r\n itemContent: \"item-content\",\r\n itemHeader: \"item-header\",\r\n itemTitle: \"item-title\",\r\n itemId: \"item-id\",\r\n itemBody: \"item-body\",\r\n progressBar: \"progress-bar\",\r\n progress: \"progress\",\r\n itemFooter: \"item-footer\",\r\n ...(this.options.classNames || {}),\r\n };\r\n\r\n // Add container class for styling\r\n this.container.classList.add(this.classNames.container);\r\n\r\n // Initialize the layout\r\n this.init();\r\n }\r\n\r\n /**\r\n * Initialization: set listeners, generate initial items, layout\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 /**\r\n * Setup event listeners for window resize and container resize observer\r\n */\r\n private setupEventListeners() {\r\n // Use ResizeObserver to handle container size changes\r\n const resizeObserver = new ResizeObserver(() => this.handleResize());\r\n resizeObserver.observe(this.container);\r\n\r\n window.addEventListener(\"resize\", () => this.handleResize());\r\n }\r\n\r\n /**\r\n * Generate specified number of items, removing excess if any\r\n * @param count number of items to generate\r\n */\r\n private generateItems(count: number) {\r\n // Remove extra items if reducing count\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 /**\r\n * Create a single masonry item HTMLElement, with content from renderItemContent callback or default template\r\n * @param index index of the item\r\n * @returns HTMLElement for the item\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\r\n // Determine content type: function, static HTMLElement, static string, or fallback to default\r\n let content: HTMLElement | string;\r\n\r\n if (typeof contentOption === \"function\") {\r\n // Call function with index to get content\r\n content = contentOption(index);\r\n } else if (contentOption instanceof HTMLElement || typeof contentOption === \"string\") {\r\n // Use static content\r\n content = contentOption;\r\n } else {\r\n // Fallback to default item content template string\r\n content = this.defaultItemContent(index);\r\n }\r\n\r\n // Insert content into item element\r\n if (typeof content === \"string\") {\r\n div.innerHTML = content;\r\n } else {\r\n div.appendChild(content);\r\n }\r\n\r\n // Random height to simulate masonry effect\r\n const height = 120 + Math.floor(Math.random() * 180);\r\n div.style.height = `${height}px`;\r\n\r\n // Color with HSL for distinct appearance\r\n const hue = (index * 137.508) % 360; // golden angle\r\n div.style.background = `linear-gradient(135deg, hsl(${hue}, 70%, 60%), hsl(${hue + 40}, 70%, 50%))`;\r\n\r\n return div;\r\n }\r\n\r\n /**\r\n * Default item content template string\r\n * @param index index of the item\r\n * @returns string HTML template for item content\r\n */\r\n private defaultItemContent(index: number): string {\r\n return `\r\n <div class=\"${this.classNames.itemContent}\">\r\n <div class=\"${this.classNames.itemHeader}\">\r\n <div class=\"${this.classNames.itemTitle}\">Item ${index + 1}</div>\r\n <div class=\"${this.classNames.itemId}\">#${index + 1}</div>\r\n </div>\r\n <div class=\"${this.classNames.itemBody}\">\r\n <div class=\"${this.classNames.progressBar}\">\r\n <div class=\"${this.classNames.progress}\"></div>\r\n </div>\r\n </div>\r\n <div class=\"${this.classNames.itemFooter}\">\r\n ${this.getRandomEmoji()}\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n /**\r\n * Returns a random emoji from a fixed set for item footer\r\n */\r\n private getRandomEmoji(): string {\r\n const emojis = [\"🚀\", \"✨\", \"🔥\", \"💡\", \"🌟\", \"🎯\", \"⚡\", \"💻\", \"🔧\", \"📊\"];\r\n return emojis[Math.floor(Math.random() * emojis.length)];\r\n }\r\n\r\n /**\r\n * Calculate positions of each item in masonry grid based on container width, gutter, min column width\r\n */\r\n private calculateLayout() {\r\n const { gutter, minColWidth } = this.options;\r\n const containerWidth = this.container.clientWidth;\r\n\r\n // Calculate number of columns that fit\r\n this.columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));\r\n\r\n // Calculate each column width\r\n const colWidth = (containerWidth - (this.columns - 1) * gutter) / this.columns;\r\n\r\n // Store previous positions for animation\r\n this.lastPositions = [...this.positions];\r\n\r\n // Reset column heights array\r\n this.columnHeights = new Array(this.columns).fill(0);\r\n this.positions = [];\r\n\r\n // Calculate position for each item and assign column\r\n this.items.forEach((item, i) => {\r\n const height = item.offsetHeight;\r\n\r\n // Find the shortest column index\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 // Calculate item's x,y position\r\n const x = minCol * (colWidth + gutter);\r\n const y = this.columnHeights[minCol];\r\n\r\n // Save calculated position\r\n this.positions[i] = { x, y, width: colWidth, height };\r\n\r\n // Update column height to include this item + gutter\r\n this.columnHeights[minCol] += height + gutter;\r\n });\r\n\r\n // Set container height to max column height for proper scrolling\r\n const maxHeight = Math.max(...this.columnHeights);\r\n this.container.style.height = `${maxHeight}px`;\r\n }\r\n\r\n /**\r\n * Apply calculated positions to each item with optional animation\r\n * @param animate whether to animate layout changes (default false)\r\n */\r\n private 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 // Set item width for responsive columns\r\n item.style.width = `${pos.width}px`;\r\n\r\n if (animate) {\r\n // Calculate differences for smooth animation\r\n const dx = lastPos.x - pos.x;\r\n const dy = lastPos.y - pos.y;\r\n\r\n // Apply initial transform to old position (without transition)\r\n item.style.transition = \"none\";\r\n item.style.transform = `translate3d(${pos.x + dx}px, ${pos.y + dy}px, 0)`;\r\n\r\n // Trigger reflow to apply the style immediately\r\n void item.offsetHeight;\r\n\r\n // Animate transform to new position\r\n item.style.transition = `transform ${duration}ms cubic-bezier(0.4, 0, 0.2, 1)`;\r\n item.style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;\r\n } else {\r\n // Directly set transform without animation\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 /**\r\n * Handle container resize using RAF to optimize performance\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 /**\r\n * Shuffle items randomly and reapply layout with animation\r\n */\r\n public shuffleItems() {\r\n // Fisher-Yates shuffle algorithm\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 // Re-append items in new order in DOM\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 // Update layout positions and animate\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n }\r\n\r\n /**\r\n * Add more items dynamically\r\n * @param count number of items to add\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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/MasonrySnapGridLayout.ts"],"sourcesContent":["import './masonry-snap-grid-layout.css';\r\nimport MasonrySnapGridLayout from './MasonrySnapGridLayout';\r\n\r\nconst container = document.getElementById('masonry');\r\nif (container) {\r\n const masonry = new MasonrySnapGridLayout(container, {\r\n gutter: 20,\r\n minColWidth: 200,\r\n animate: true,\r\n initialItems: 40,\r\n\r\n // Custom item content example\r\n itemContent: (index) => {\r\n const div = document.createElement('div');\r\n div.textContent = `Custom Item ${index + 1}`;\r\n div.style.color = '#fff';\r\n div.style.fontWeight = 'bold';\r\n div.style.textAlign = 'center';\r\n div.style.padding = '1rem';\r\n return div;\r\n },\r\n\r\n classNames: {\r\n container: 'masonry-snap-grid-layout-container',\r\n item: 'masonry-snap-grid-layout-item',\r\n },\r\n });\r\n\r\n // Example to update count later\r\n masonry.addItems(5);\r\n\r\n // Example to shuffle items\r\n masonry.shuffleItems();\r\n}\r\n\r\nexport default MasonrySnapGridLayout;\r\nexport { MasonrySnapGridLayout };","/**\r\n * MasonrySnapGridLayout\r\n * A performant masonry grid layout library with smooth animations,\r\n * customizable gutter, columns, and dynamic item content.\r\n *\r\n * Package name: masonry-snap-grid-layout\r\n */\r\n\r\nimport { 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 /**\r\n * Constructor initializes the layout with container element and options\r\n * @param container HTMLElement container where masonry items are rendered\r\n * @param options configuration options for layout and styling\r\n */\r\n constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions = {}) {\r\n this.container = container;\r\n\r\n // Set default options with overrides from user\r\n this.options = {\r\n gutter: 16,\r\n minColWidth: 250,\r\n animate: true,\r\n transitionDuration: 400,\r\n initialItems: 30,\r\n itemContent: null,\r\n classNames: {\r\n container: \"masonry-snap-grid-layout-container\",\r\n item: \"masonry-snap-grid-layout-item\",\r\n itemContent: \"item-content\",\r\n itemHeader: \"item-header\",\r\n itemTitle: \"item-title\",\r\n itemId: \"item-id\",\r\n itemBody: \"item-body\",\r\n progressBar: \"progress-bar\",\r\n progress: \"progress\",\r\n itemFooter: \"item-footer\",\r\n },\r\n ...options,\r\n };\r\n\r\n // Merge default classNames with user provided classNames\r\n this.classNames = {\r\n container: \"masonry-snap-grid-layout-container\",\r\n item: \"masonry-snap-grid-layout-item\",\r\n itemContent: \"item-content\",\r\n itemHeader: \"item-header\",\r\n itemTitle: \"item-title\",\r\n itemId: \"item-id\",\r\n itemBody: \"item-body\",\r\n progressBar: \"progress-bar\",\r\n progress: \"progress\",\r\n itemFooter: \"item-footer\",\r\n ...(this.options.classNames || {}),\r\n };\r\n\r\n // Add container class for styling\r\n this.container.classList.add(this.classNames.container);\r\n\r\n // Initialize the layout\r\n this.init();\r\n }\r\n\r\n /**\r\n * Initialization: set listeners, generate initial items, layout\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 /**\r\n * Setup event listeners for window resize and container resize observer\r\n */\r\n private setupEventListeners() {\r\n // Use ResizeObserver to handle container size changes\r\n const resizeObserver = new ResizeObserver(() => this.handleResize());\r\n resizeObserver.observe(this.container);\r\n\r\n window.addEventListener(\"resize\", () => this.handleResize());\r\n }\r\n\r\n /**\r\n * Generate specified number of items, removing excess if any\r\n * @param count number of items to generate\r\n */\r\n private generateItems(count: number) {\r\n // Remove extra items if reducing count\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 /**\r\n * Create a single masonry item HTMLElement, with content from renderItemContent callback or default template\r\n * @param index index of the item\r\n * @returns HTMLElement for the item\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\r\n // Determine content type: function, static HTMLElement, static string, or fallback to default\r\n let content: HTMLElement | string;\r\n\r\n if (typeof contentOption === \"function\") {\r\n // Call function with index to get content\r\n content = contentOption(index);\r\n } else if (contentOption instanceof HTMLElement || typeof contentOption === \"string\") {\r\n // Use static content\r\n content = contentOption;\r\n } else {\r\n // Fallback to default item content template string\r\n content = this.defaultItemContent(index);\r\n }\r\n\r\n // Insert content into item element\r\n if (typeof content === \"string\") {\r\n div.innerHTML = content;\r\n } else {\r\n div.appendChild(content);\r\n }\r\n\r\n // Random height to simulate masonry effect\r\n const height = 120 + Math.floor(Math.random() * 180);\r\n div.style.height = `${height}px`;\r\n\r\n // Color with HSL for distinct appearance\r\n const hue = (index * 137.508) % 360; // golden angle\r\n div.style.background = `linear-gradient(135deg, hsl(${hue}, 70%, 60%), hsl(${hue + 40}, 70%, 50%))`;\r\n\r\n return div;\r\n }\r\n\r\n /**\r\n * Default item content template string\r\n * @param index index of the item\r\n * @returns string HTML template for item content\r\n */\r\n private defaultItemContent(index: number): string {\r\n return `\r\n <div class=\"${this.classNames.itemContent}\">\r\n <div class=\"${this.classNames.itemHeader}\">\r\n <div class=\"${this.classNames.itemTitle}\">Item ${index + 1}</div>\r\n <div class=\"${this.classNames.itemId}\">#${index + 1}</div>\r\n </div>\r\n <div class=\"${this.classNames.itemBody}\">\r\n <div class=\"${this.classNames.progressBar}\">\r\n <div class=\"${this.classNames.progress}\"></div>\r\n </div>\r\n </div>\r\n <div class=\"${this.classNames.itemFooter}\">\r\n ${this.getRandomEmoji()}\r\n </div>\r\n </div>\r\n `;\r\n }\r\n\r\n /**\r\n * Returns a random emoji from a fixed set for item footer\r\n */\r\n private getRandomEmoji(): string {\r\n const emojis = [\"🚀\", \"✨\", \"🔥\", \"💡\", \"🌟\", \"🎯\", \"⚡\", \"💻\", \"🔧\", \"📊\"];\r\n return emojis[Math.floor(Math.random() * emojis.length)];\r\n }\r\n\r\n /**\r\n * Calculate positions of each item in masonry grid based on container width, gutter, min column width\r\n */\r\n private calculateLayout() {\r\n const { gutter, minColWidth } = this.options;\r\n const containerWidth = this.container.clientWidth;\r\n\r\n // Calculate number of columns that fit\r\n this.columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));\r\n\r\n // Calculate each column width\r\n const colWidth = (containerWidth - (this.columns - 1) * gutter) / this.columns;\r\n\r\n // Store previous positions for animation\r\n this.lastPositions = [...this.positions];\r\n\r\n // Reset column heights array\r\n this.columnHeights = new Array(this.columns).fill(0);\r\n this.positions = [];\r\n\r\n // Calculate position for each item and assign column\r\n this.items.forEach((item, i) => {\r\n const height = item.offsetHeight;\r\n\r\n // Find the shortest column index\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 // Calculate item's x,y position\r\n const x = minCol * (colWidth + gutter);\r\n const y = this.columnHeights[minCol];\r\n\r\n // Save calculated position\r\n this.positions[i] = { x, y, width: colWidth, height };\r\n\r\n // Update column height to include this item + gutter\r\n this.columnHeights[minCol] += height + gutter;\r\n });\r\n\r\n // Set container height to max column height for proper scrolling\r\n const maxHeight = Math.max(...this.columnHeights);\r\n this.container.style.height = `${maxHeight}px`;\r\n }\r\n\r\n /**\r\n * Apply calculated positions to each item with optional animation\r\n * @param animate whether to animate layout changes (default false)\r\n */\r\n private 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 // Set item width for responsive columns\r\n item.style.width = `${pos.width}px`;\r\n\r\n if (animate) {\r\n // Calculate differences for smooth animation\r\n const dx = lastPos.x - pos.x;\r\n const dy = lastPos.y - pos.y;\r\n\r\n // Apply initial transform to old position (without transition)\r\n item.style.transition = \"none\";\r\n item.style.transform = `translate3d(${pos.x + dx}px, ${pos.y + dy}px, 0)`;\r\n\r\n // Trigger reflow to apply the style immediately\r\n void item.offsetHeight;\r\n\r\n // Animate transform to new position\r\n item.style.transition = `transform ${duration}ms cubic-bezier(0.4, 0, 0.2, 1)`;\r\n item.style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;\r\n } else {\r\n // Directly set transform without animation\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 /**\r\n * Handle container resize using RAF to optimize performance\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 /**\r\n * Shuffle items randomly and reapply layout with animation\r\n */\r\n public shuffleItems() {\r\n // Fisher-Yates shuffle algorithm\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 // Re-append items in new order in DOM\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 // Update layout positions and animate\r\n this.calculateLayout();\r\n this.applyLayout(true);\r\n }\r\n\r\n /**\r\n * Add more items dynamically\r\n * @param count number of items to add\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"],"mappings":"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","names":["container"]}
package/package.json CHANGED
@@ -1,28 +1,28 @@
1
- {
2
- "name": "masonry-snap-grid-layout",
3
- "version": "0.0.2",
4
- "main": "dist/index.js",
5
- "module": "dist/index.mjs",
6
- "types": "dist/index.d.ts",
7
- "type": "module",
8
- "exports": {
9
- ".": {
10
- "import": {
11
- "types": "./dist/index.d.ts",
12
- "default": "./dist/index.js"
13
- },
14
- "require": {
15
- "types": "./dist/index.d.ts",
16
- "default": "./dist/index.cjs"
17
- }
18
- }
19
- },
20
- "scripts": {
21
- "build": "tsup src/index.ts --dts --format esm,cjs --legacy-output",
22
- "dev": "tsup src/index.ts --watch --dts --format esm,cjs --legacy-output"
23
- },
24
- "devDependencies": {
25
- "tsup": "8.3.5",
26
- "typescript": "^5.0.0"
27
- }
28
- }
1
+ {
2
+ "name": "masonry-snap-grid-layout",
3
+ "version": "0.0.3",
4
+ "main": "dist/index.js",
5
+ "module": "dist/index.mjs",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.cjs"
17
+ }
18
+ }
19
+ },
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --dts --format esm,cjs --legacy-output",
22
+ "dev": "tsup src/index.ts --watch --dts --format esm,cjs --legacy-output"
23
+ },
24
+ "devDependencies": {
25
+ "tsup": "8.3.5",
26
+ "typescript": "^5.0.0"
27
+ }
28
+ }
package/src/index.ts CHANGED
@@ -1,3 +1,37 @@
1
- import MasonrySnapGridLayout from './MasonrySnapGridLayout';
2
- export default MasonrySnapGridLayout;
1
+ import './masonry-snap-grid-layout.css';
2
+ import MasonrySnapGridLayout from './MasonrySnapGridLayout';
3
+
4
+ const container = document.getElementById('masonry');
5
+ if (container) {
6
+ const masonry = new MasonrySnapGridLayout(container, {
7
+ gutter: 20,
8
+ minColWidth: 200,
9
+ animate: true,
10
+ initialItems: 40,
11
+
12
+ // Custom item content example
13
+ itemContent: (index) => {
14
+ const div = document.createElement('div');
15
+ div.textContent = `Custom Item ${index + 1}`;
16
+ div.style.color = '#fff';
17
+ div.style.fontWeight = 'bold';
18
+ div.style.textAlign = 'center';
19
+ div.style.padding = '1rem';
20
+ return div;
21
+ },
22
+
23
+ classNames: {
24
+ container: 'masonry-snap-grid-layout-container',
25
+ item: 'masonry-snap-grid-layout-item',
26
+ },
27
+ });
28
+
29
+ // Example to update count later
30
+ masonry.addItems(5);
31
+
32
+ // Example to shuffle items
33
+ masonry.shuffleItems();
34
+ }
35
+
36
+ export default MasonrySnapGridLayout;
3
37
  export { MasonrySnapGridLayout };
@@ -39,7 +39,6 @@
39
39
  position: absolute;
40
40
  color: var(--text-color);
41
41
  border-radius: var(--item-radius);
42
- padding: 16px;
43
42
  font-size: 1rem;
44
43
  box-shadow: 0 6px 12px var(--shadow-color);
45
44
  user-select: none;
@@ -66,10 +65,3 @@
66
65
  z-index: 10;
67
66
  }
68
67
 
69
- /* Dragging style */
70
- .masonry-snap-grid-layout-item.dragging {
71
- z-index: 1000;
72
- box-shadow: 0 16px 32px rgba(0, 0, 0, 0.3);
73
- opacity: 0.9;
74
- transition: none;
75
- }
package/tsconfig.json CHANGED
@@ -1,15 +1,14 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ESNext",
3
+ "target": "ES2020",
4
4
  "module": "ESNext",
5
+ "declaration": true,
6
+ "outDir": "./dist",
5
7
  "strict": true,
6
8
  "esModuleInterop": true,
9
+ "moduleResolution": "Node",
7
10
  "skipLibCheck": true,
8
- "forceConsistentCasingInFileNames": true,
9
- "outDir": "./dist",
10
- "declaration": true,
11
- "moduleResolution": "bundler",
12
- "allowImportingTsExtensions": true
11
+ "forceConsistentCasingInFileNames": true
13
12
  },
14
- "include": ["src/**/*"]
13
+ "include": ["src"]
15
14
  }
package/tsup.config.ts CHANGED
@@ -1,3 +1,4 @@
1
+ // tsup.config.ts
1
2
  import { defineConfig } from 'tsup';
2
3
 
3
4
  export default defineConfig({
@@ -5,11 +6,10 @@ export default defineConfig({
5
6
  format: ['esm', 'cjs'],
6
7
  dts: true,
7
8
  clean: true,
8
- legacyOutput: true, // Important for proper default exports
9
9
  target: 'esnext',
10
10
  splitting: false,
11
11
  sourcemap: true,
12
12
  esbuildOptions(options) {
13
13
  options.tsconfig = './tsconfig.json';
14
- }
14
+ },
15
15
  });