masonry-snap-grid-layout 0.0.2 → 0.0.4

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
@@ -1,16 +1,19 @@
1
1
  # masonry-snap-grid-layout
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/masonry-snap-grid-layout?color=brightgreen)](https://www.npmjs.com/package/masonry-snap-grid-layout)
4
+ [![CI/CD](https://github.com/khachatryan-dev/masonry-snap-grid-layout/actions/workflows/publish.yml/badge.svg)](https://github.com/khachatryan-dev/masonry-snap-grid-layout/actions)
5
+
3
6
  A performant masonry grid layout library with smooth animations, customizable gutter, columns, and dynamic item content.
4
7
 
5
8
  ---
6
9
 
7
10
  ## 🚀 Features
8
11
 
9
- - **Dynamic Columns & Gutter**: Automatically adapts to container width.
10
- - **Smooth Animations**: Transitions when layout changes or items shuffle.
11
- - **Customizable Item Content**: Pass your own HTML or render functions.
12
- - **Lightweight & Dependency-Free**: Vanilla TypeScript for easy integration.
13
- - **Responsive & Accessible**: Works well on all screen sizes.
12
+ * **Dynamic Columns & Gutter**: Automatically adapts to container width.
13
+ * **Smooth Animations**: Transitions when layout changes or items shuffle.
14
+ * **Customizable Item Content**: Pass your own HTML or render functions.
15
+ * **Lightweight & Dependency-Free**: Vanilla TypeScript for easy integration.
16
+ * **Responsive & Accessible**: Works well on all screen sizes.
14
17
 
15
18
  ---
16
19
 
@@ -20,14 +23,14 @@ A performant masonry grid layout library with smooth animations, customizable gu
20
23
  npm install masonry-snap-grid-layout
21
24
  # or
22
25
  yarn add masonry-snap-grid-layout
23
- ```
26
+ ````
24
27
 
25
28
  ---
26
29
 
27
- ## 💡 Usage Example
30
+ ## 💡 Usage Example (Vanilla JS)
28
31
 
29
32
  ```ts
30
- import { MasonrySnapGridLayout } from 'masonry-snap-grid-layout';
33
+ import MasonrySnapGridLayout from 'masonry-snap-grid-layout';
31
34
 
32
35
  const container = document.getElementById('masonry-container')!;
33
36
  const masonry = new MasonrySnapGridLayout(container, {
@@ -39,52 +42,117 @@ const masonry = new MasonrySnapGridLayout(container, {
39
42
  itemContent: (index) => {
40
43
  const div = document.createElement('div');
41
44
  div.textContent = `Custom Item #${index + 1}`;
42
- div.style.padding = '10px';
43
- div.style.backgroundColor = '#ddd';
44
- div.style.borderRadius = '8px';
45
45
  return div;
46
- }
46
+ },
47
47
  });
48
48
  ```
49
49
 
50
+ In your HTML:
51
+
52
+ ```html
53
+ <div id="masonry-container" style="position: relative; width: 100%;"></div>
54
+ ```
55
+
56
+ ---
57
+
58
+ ## 💙 React Usage Example
59
+
60
+ ```tsx
61
+ 'use client';
62
+
63
+ import React from 'react';
64
+ import MasonrySnapGrid from 'masonry-snap-grid-layout/react'; // Adjust path if needed
65
+
66
+ export default function MasonryGrid() {
67
+ const items = Array.from({ length: 25 }, (_, i) => i);
68
+
69
+ return (
70
+ <MasonrySnapGrid
71
+ items={items}
72
+ options={{
73
+ gutter: 16,
74
+ minColWidth: 220,
75
+ animate: true,
76
+ transitionDuration: 400,
77
+ }}
78
+ renderItem={(item, index) => (
79
+ <div>
80
+ React Item #{index + 1}
81
+ </div>
82
+ )}
83
+ className="my-masonry-container"
84
+ style={{ position: 'relative', width: '100%' }}
85
+ />
86
+ );
87
+ }
88
+ ```
89
+
50
90
  ---
51
91
 
52
92
  ## ⚙️ API
53
93
 
54
94
  ### Constructor
55
95
 
56
- `new MasonrySnapGridLayout(container: HTMLElement, options?: MasonrySnapGridLayoutOptions)`
96
+ ```ts
97
+ new MasonrySnapGridLayout(container: HTMLElement, options?: MasonrySnapGridLayoutOptions)
98
+ ```
57
99
 
58
- - **container** — The container element where items are rendered.
59
- - **options** — Configuration options (optional).
100
+ * **container** — The container element where items are rendered.
101
+ * **options** — Configuration options (optional).
60
102
 
61
103
  ### Methods
62
104
 
63
- - `shuffleItems(): void` — Shuffle items randomly with animation.
64
- - `addItems(count: number): void` — Add more items dynamically.
105
+ * `shuffleItems(): void` — Shuffle items randomly with animation.
106
+ * `addItems(count: number): void` — Add more items dynamically.
107
+ * `destroy(): void` — Clean up and remove all items and event listeners.
65
108
 
66
109
  ---
67
110
 
68
111
  ## 🛠️ Options
69
112
 
70
- | Option | Type | Default | Description |
71
- |--------------------|----------------------------------|-------------|------------------------------------------|
72
- | `gutter` | `number` | `16` | Spacing between items in pixels. |
73
- | `minColWidth` | `number` | `250` | Minimum column width in pixels. |
74
- | `animate` | `boolean` | `true` | Enable/disable animations. |
75
- | `transitionDuration` | `number` | `400` | Animation duration in milliseconds. |
76
- | `initialItems` | `number` | `30` | Number of items generated initially. |
77
- | `classNames` | `object` | Default CSS classes | Override CSS class names for styling. |
78
- | `itemContent` | `string` \| `HTMLElement` \| `(index: number) => HTMLElement \| string` | `null` | Content or content generator callback for items. |
113
+ | Option | Type | Default | Description |
114
+ | -------------------- | ----------------------------------------------------------------------- | ------------------- | ------------------------------------------------ |
115
+ | `gutter` | `number` | `16` | Spacing between items in pixels. |
116
+ | `minColWidth` | `number` | `250` | Minimum column width in pixels. |
117
+ | `animate` | `boolean` | `true` | Enable/disable animations. |
118
+ | `transitionDuration` | `number` | `400` | Animation duration in milliseconds. |
119
+ | `initialItems` | `number` | `30` | Number of items generated initially. |
120
+ | `classNames` | `Partial<MasonrySnapGridLayoutClassNames>` | Default CSS classes | Override CSS class names for styling. |
121
+ | `itemContent` | `string` \| `HTMLElement` \| `(index: number) => HTMLElement \| string` | `null` | Content or content generator callback for items. |
79
122
 
80
123
  ---
81
124
 
82
- ## 🎨 Styling
125
+ ## 🎨 Styling & Animations
83
126
 
84
- You can customize the styles by overriding the CSS classes defined or by providing your own CSS class names through `classNames` option.
127
+ You can customize styles by overriding the CSS classes or providing your own via the `classNames` option.
128
+
129
+ The layout uses smooth `transform` transitions with `cubic-bezier(0.4, 0, 0.2, 1)` easing for a polished, natural animation effect.
130
+
131
+ ---
132
+
133
+ ## 🔁 CI/CD
134
+
135
+ This package uses **GitHub Actions** to automatically publish new versions to **npm** when you push a version tag like `v1.0.0`.
136
+
137
+ To use it:
138
+
139
+ ```bash
140
+ npm version patch
141
+ git push origin main --tags
142
+ ```
143
+
144
+ Make sure your `NPM_TOKEN` is saved in GitHub Secrets for automatic publishing.
145
+
146
+ ---
147
+
148
+ ## 📦 npm Package
149
+
150
+ 📌 [View on npm](https://www.npmjs.com/package/masonry-snap-grid-layout)
85
151
 
86
152
  ---
87
153
 
88
154
  ## 📄 License
89
155
 
90
- MIT © Aram Khachatryan
156
+ MIT © [Aram Khachatryan](https://github.com/khachatryan-dev)
157
+
158
+ ---
@@ -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,71 @@
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.4",
4
+ "description": "A performant, responsive masonry layout library with smooth animations, dynamic columns, and zero dependencies.",
5
+ "keywords": [
6
+ "masonry",
7
+ "grid",
8
+ "layout",
9
+ "responsive",
10
+ "typescript",
11
+ "animation",
12
+ "vanilla-js",
13
+ "masonry-layout"
14
+ ],
15
+ "main": "dist/index.js",
16
+ "module": "dist/index.mjs",
17
+ "types": "dist/index.d.ts",
18
+ "type": "module",
19
+ "exports": {
20
+ ".": {
21
+ "import": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "require": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.cjs"
28
+ }
29
+ },
30
+ "./react": {
31
+ "import": {
32
+ "types": "./dist/react.d.ts",
33
+ "default": "./dist/react.js"
34
+ },
35
+ "require": {
36
+ "types": "./dist/react.d.ts",
37
+ "default": "./dist/react.cjs"
38
+ }
39
+ }
40
+ },
41
+ "files": [
42
+ "dist",
43
+ "README.md",
44
+ "LICENSE"
45
+ ],
46
+ "scripts": {
47
+ "build": "tsup src/index.ts src/reactjs.tsx --dts --format esm,cjs --legacy-output",
48
+ "dev": "tsup src/index.ts src/reactjs.tsx --watch --dts --format esm,cjs --legacy-output"
49
+ },
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "https://github.com/khachatryan-dev/masonry-snap-grid-layout.git"
53
+ },
54
+ "homepage": "https://github.com/khachatryan-dev/masonry-snap-grid-layout#readme",
55
+ "bugs": {
56
+ "url": "https://github.com/khachatryan-dev/masonry-snap-grid-layout/issues"
57
+ },
58
+ "author": "Aram Khachatryan <arkhachat@gmail.com>",
59
+ "license": "MIT",
60
+ "engines": {
61
+ "node": ">=16"
62
+ },
63
+ "devDependencies": {
64
+ "@types/react": "^19.1.9",
65
+ "tsup": "8.3.5",
66
+ "typescript": "^5.0.0"
67
+ },
68
+ "dependencies": {
69
+ "react": "^19.1.1"
70
+ }
71
+ }
@@ -1,12 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <module type="WEB_MODULE" version="4">
3
- <component name="NewModuleRootManager">
4
- <content url="file://$MODULE_DIR$">
5
- <excludeFolder url="file://$MODULE_DIR$/.tmp" />
6
- <excludeFolder url="file://$MODULE_DIR$/temp" />
7
- <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
- </content>
9
- <orderEntry type="inheritedJdk" />
10
- <orderEntry type="sourceFolder" forTests="false" />
11
- </component>
12
- </module>
package/.idea/modules.xml DELETED
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/masonry-snap-grid-layout.iml" filepath="$PROJECT_DIR$/.idea/masonry-snap-grid-layout.iml" />
6
- </modules>
7
- </component>
8
- </project>
package/.idea/vcs.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="VcsDirectoryMappings">
4
- <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
- </component>
6
- </project>
@@ -1,322 +0,0 @@
1
- /**
2
- * MasonrySnapGridLayout
3
- * A performant masonry grid layout library with smooth animations,
4
- * customizable gutter, columns, and dynamic item content.
5
- *
6
- * Package name: masonry-snap-grid-layout
7
- */
8
-
9
- import { MasonrySnapGridLayoutClassNames, MasonrySnapGridLayoutOptions } from "./types";
10
-
11
- export default class MasonrySnapGridLayout {
12
- private readonly container: HTMLElement;
13
- private readonly options: Required<MasonrySnapGridLayoutOptions>;
14
- private classNames: Required<MasonrySnapGridLayoutClassNames>;
15
-
16
- private items: HTMLElement[] = [];
17
- private positions: { x: number; y: number; width: number; height: number }[] = [];
18
- private columnHeights: number[] = [];
19
- private columns: number = 0;
20
- private lastPositions: typeof this.positions = [];
21
- private resizeRaf: number | null = null;
22
-
23
- /**
24
- * Constructor initializes the layout with container element and options
25
- * @param container HTMLElement container where masonry items are rendered
26
- * @param options configuration options for layout and styling
27
- */
28
- constructor(container: HTMLElement, options: MasonrySnapGridLayoutOptions = {}) {
29
- this.container = container;
30
-
31
- // Set default options with overrides from user
32
- this.options = {
33
- gutter: 16,
34
- minColWidth: 250,
35
- animate: true,
36
- transitionDuration: 400,
37
- initialItems: 30,
38
- itemContent: null,
39
- classNames: {
40
- container: "masonry-snap-grid-layout-container",
41
- item: "masonry-snap-grid-layout-item",
42
- itemContent: "item-content",
43
- itemHeader: "item-header",
44
- itemTitle: "item-title",
45
- itemId: "item-id",
46
- itemBody: "item-body",
47
- progressBar: "progress-bar",
48
- progress: "progress",
49
- itemFooter: "item-footer",
50
- },
51
- ...options,
52
- };
53
-
54
- // Merge default classNames with user provided classNames
55
- this.classNames = {
56
- container: "masonry-snap-grid-layout-container",
57
- item: "masonry-snap-grid-layout-item",
58
- itemContent: "item-content",
59
- itemHeader: "item-header",
60
- itemTitle: "item-title",
61
- itemId: "item-id",
62
- itemBody: "item-body",
63
- progressBar: "progress-bar",
64
- progress: "progress",
65
- itemFooter: "item-footer",
66
- ...(this.options.classNames || {}),
67
- };
68
-
69
- // Add container class for styling
70
- this.container.classList.add(this.classNames.container);
71
-
72
- // Initialize the layout
73
- this.init();
74
- }
75
-
76
- /**
77
- * Initialization: set listeners, generate initial items, layout
78
- */
79
- private init() {
80
- this.setupEventListeners();
81
- this.generateItems(this.options.initialItems);
82
- this.calculateLayout();
83
- this.applyLayout(false);
84
- }
85
-
86
- /**
87
- * Setup event listeners for window resize and container resize observer
88
- */
89
- private setupEventListeners() {
90
- // Use ResizeObserver to handle container size changes
91
- const resizeObserver = new ResizeObserver(() => this.handleResize());
92
- resizeObserver.observe(this.container);
93
-
94
- window.addEventListener("resize", () => this.handleResize());
95
- }
96
-
97
- /**
98
- * Generate specified number of items, removing excess if any
99
- * @param count number of items to generate
100
- */
101
- private generateItems(count: number) {
102
- // Remove extra items if reducing count
103
- if (count < this.items.length) {
104
- this.items.slice(count).forEach((item) => item.remove());
105
- this.items = this.items.slice(0, count);
106
- return;
107
- }
108
-
109
- const startIndex = this.items.length;
110
- const fragment = document.createDocumentFragment();
111
-
112
- for (let i = startIndex; i < count; i++) {
113
- const item = this.createItem(i);
114
- fragment.appendChild(item);
115
- this.items.push(item);
116
- }
117
-
118
- this.container.appendChild(fragment);
119
- }
120
-
121
- /**
122
- * Create a single masonry item HTMLElement, with content from renderItemContent callback or default template
123
- * @param index index of the item
124
- * @returns HTMLElement for the item
125
- */
126
- private createItem(index: number): HTMLElement {
127
- const div = document.createElement("div");
128
- div.className = this.classNames.item;
129
-
130
- const contentOption = this.options.itemContent;
131
-
132
- // Determine content type: function, static HTMLElement, static string, or fallback to default
133
- let content: HTMLElement | string;
134
-
135
- if (typeof contentOption === "function") {
136
- // Call function with index to get content
137
- content = contentOption(index);
138
- } else if (contentOption instanceof HTMLElement || typeof contentOption === "string") {
139
- // Use static content
140
- content = contentOption;
141
- } else {
142
- // Fallback to default item content template string
143
- content = this.defaultItemContent(index);
144
- }
145
-
146
- // Insert content into item element
147
- if (typeof content === "string") {
148
- div.innerHTML = content;
149
- } else {
150
- div.appendChild(content);
151
- }
152
-
153
- // Random height to simulate masonry effect
154
- const height = 120 + Math.floor(Math.random() * 180);
155
- div.style.height = `${height}px`;
156
-
157
- // Color with HSL for distinct appearance
158
- const hue = (index * 137.508) % 360; // golden angle
159
- div.style.background = `linear-gradient(135deg, hsl(${hue}, 70%, 60%), hsl(${hue + 40}, 70%, 50%))`;
160
-
161
- return div;
162
- }
163
-
164
- /**
165
- * Default item content template string
166
- * @param index index of the item
167
- * @returns string HTML template for item content
168
- */
169
- private defaultItemContent(index: number): string {
170
- return `
171
- <div class="${this.classNames.itemContent}">
172
- <div class="${this.classNames.itemHeader}">
173
- <div class="${this.classNames.itemTitle}">Item ${index + 1}</div>
174
- <div class="${this.classNames.itemId}">#${index + 1}</div>
175
- </div>
176
- <div class="${this.classNames.itemBody}">
177
- <div class="${this.classNames.progressBar}">
178
- <div class="${this.classNames.progress}"></div>
179
- </div>
180
- </div>
181
- <div class="${this.classNames.itemFooter}">
182
- ${this.getRandomEmoji()}
183
- </div>
184
- </div>
185
- `;
186
- }
187
-
188
- /**
189
- * Returns a random emoji from a fixed set for item footer
190
- */
191
- private getRandomEmoji(): string {
192
- const emojis = ["🚀", "✨", "🔥", "💡", "🌟", "🎯", "⚡", "💻", "🔧", "📊"];
193
- return emojis[Math.floor(Math.random() * emojis.length)];
194
- }
195
-
196
- /**
197
- * Calculate positions of each item in masonry grid based on container width, gutter, min column width
198
- */
199
- private calculateLayout() {
200
- const { gutter, minColWidth } = this.options;
201
- const containerWidth = this.container.clientWidth;
202
-
203
- // Calculate number of columns that fit
204
- this.columns = Math.max(1, Math.floor((containerWidth + gutter) / (minColWidth + gutter)));
205
-
206
- // Calculate each column width
207
- const colWidth = (containerWidth - (this.columns - 1) * gutter) / this.columns;
208
-
209
- // Store previous positions for animation
210
- this.lastPositions = [...this.positions];
211
-
212
- // Reset column heights array
213
- this.columnHeights = new Array(this.columns).fill(0);
214
- this.positions = [];
215
-
216
- // Calculate position for each item and assign column
217
- this.items.forEach((item, i) => {
218
- const height = item.offsetHeight;
219
-
220
- // Find the shortest column index
221
- let minCol = 0;
222
- for (let c = 1; c < this.columns; c++) {
223
- if (this.columnHeights[c] < this.columnHeights[minCol]) {
224
- minCol = c;
225
- }
226
- }
227
-
228
- // Calculate item's x,y position
229
- const x = minCol * (colWidth + gutter);
230
- const y = this.columnHeights[minCol];
231
-
232
- // Save calculated position
233
- this.positions[i] = { x, y, width: colWidth, height };
234
-
235
- // Update column height to include this item + gutter
236
- this.columnHeights[minCol] += height + gutter;
237
- });
238
-
239
- // Set container height to max column height for proper scrolling
240
- const maxHeight = Math.max(...this.columnHeights);
241
- this.container.style.height = `${maxHeight}px`;
242
- }
243
-
244
- /**
245
- * Apply calculated positions to each item with optional animation
246
- * @param animate whether to animate layout changes (default false)
247
- */
248
- private applyLayout(animate: boolean = false) {
249
- const duration = this.options.transitionDuration;
250
-
251
- this.items.forEach((item, i) => {
252
- const pos = this.positions[i] || { x: 0, y: 0, width: 0 };
253
- const lastPos = this.lastPositions[i] || { x: 0, y: 0 };
254
-
255
- // Set item width for responsive columns
256
- item.style.width = `${pos.width}px`;
257
-
258
- if (animate) {
259
- // Calculate differences for smooth animation
260
- const dx = lastPos.x - pos.x;
261
- const dy = lastPos.y - pos.y;
262
-
263
- // Apply initial transform to old position (without transition)
264
- item.style.transition = "none";
265
- item.style.transform = `translate3d(${pos.x + dx}px, ${pos.y + dy}px, 0)`;
266
-
267
- // Trigger reflow to apply the style immediately
268
- void item.offsetHeight;
269
-
270
- // Animate transform to new position
271
- item.style.transition = `transform ${duration}ms cubic-bezier(0.4, 0, 0.2, 1)`;
272
- item.style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;
273
- } else {
274
- // Directly set transform without animation
275
- item.style.transition = "none";
276
- item.style.transform = `translate3d(${pos.x}px, ${pos.y}px, 0)`;
277
- }
278
- });
279
- }
280
-
281
- /**
282
- * Handle container resize using RAF to optimize performance
283
- */
284
- private handleResize() {
285
- if (this.resizeRaf) cancelAnimationFrame(this.resizeRaf);
286
- this.resizeRaf = requestAnimationFrame(() => {
287
- this.calculateLayout();
288
- this.applyLayout(true);
289
- });
290
- }
291
-
292
- /**
293
- * Shuffle items randomly and reapply layout with animation
294
- */
295
- public shuffleItems() {
296
- // Fisher-Yates shuffle algorithm
297
- for (let i = this.items.length - 1; i > 0; i--) {
298
- const j = Math.floor(Math.random() * (i + 1));
299
- [this.items[i], this.items[j]] = [this.items[j], this.items[i]];
300
- }
301
-
302
- // Re-append items in new order in DOM
303
- const fragment = document.createDocumentFragment();
304
- this.items.forEach((item) => fragment.appendChild(item));
305
- this.container.appendChild(fragment);
306
-
307
- // Update layout positions and animate
308
- this.calculateLayout();
309
- this.applyLayout(true);
310
- }
311
-
312
- /**
313
- * Add more items dynamically
314
- * @param count number of items to add
315
- */
316
- public addItems(count: number) {
317
- const newCount = this.items.length + count;
318
- this.generateItems(newCount);
319
- this.calculateLayout();
320
- this.applyLayout(true);
321
- }
322
- }
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- import MasonrySnapGridLayout from './MasonrySnapGridLayout';
2
- export default MasonrySnapGridLayout;
3
- export { MasonrySnapGridLayout };
@@ -1,75 +0,0 @@
1
- /*
2
- Masonry Snap Grid Layout Styles
3
-
4
- CSS variables for easy customization:
5
- --gutter: spacing between items
6
- --columns: number of columns (calculated dynamically)
7
- --min-col-width: minimum width of columns
8
- --transition-duration: animation timing
9
- --primary-color, --secondary-color: gradient colors for items
10
- --text-color: color for text inside items
11
- --shadow-color: shadow color for item depth
12
- --item-radius: border-radius for rounded corners
13
- */
14
-
15
- /* Root CSS variables for theming */
16
- :root {
17
- --gutter: 16px;
18
- --columns: 4;
19
- --min-col-width: 250px;
20
- --transition-duration: 0.4s;
21
- --primary-color: #6b73ff;
22
- --secondary-color: #000dff;
23
- --text-color: #fff;
24
- --shadow-color: rgba(0, 0, 0, 0.1);
25
- --item-radius: 8px;
26
- }
27
-
28
- /* Container for masonry grid — relative positioning for absolute item placement */
29
- .masonry-snap-grid-layout-container {
30
- position: relative;
31
- width: 100%;
32
- max-width: 1200px;
33
- margin: 0 auto;
34
- transition: height var(--transition-duration) ease-out;
35
- }
36
-
37
- /* Each item absolutely positioned within container */
38
- .masonry-snap-grid-layout-item {
39
- position: absolute;
40
- color: var(--text-color);
41
- border-radius: var(--item-radius);
42
- padding: 16px;
43
- font-size: 1rem;
44
- box-shadow: 0 6px 12px var(--shadow-color);
45
- user-select: none;
46
- cursor: grab;
47
- will-change: transform;
48
- transition:
49
- transform var(--transition-duration) cubic-bezier(0.4, 0, 0.2, 1),
50
- box-shadow 0.3s ease,
51
- background 0.4s ease;
52
- overflow: hidden;
53
- z-index: 1;
54
- }
55
-
56
- /* Hover state for better UX */
57
- .masonry-snap-grid-layout-item:hover {
58
- box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
59
- transform: translateY(-2px);
60
- }
61
-
62
- /* Active / grabbing state */
63
- .masonry-snap-grid-layout-item:active {
64
- cursor: grabbing;
65
- box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2);
66
- z-index: 10;
67
- }
68
-
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/src/types.ts DELETED
@@ -1,51 +0,0 @@
1
- /**
2
- * MasonrySnapGridLayoutClassNames
3
- * Optional CSS class names to override default styling selectors.
4
- * Enables user to customize CSS classes easily.
5
- */
6
- export interface MasonrySnapGridLayoutClassNames {
7
- container?: string; // Class for masonry container wrapper
8
- item?: string; // Class for each masonry item
9
- itemContent?: string; // Wrapper for content inside each item
10
- itemHeader?: string; // Header section inside an item
11
- itemTitle?: string; // Title text inside header
12
- itemId?: string; // ID label inside header
13
- itemBody?: string; // Main content/body of item
14
- progressBar?: string; // Progress bar container
15
- progress?: string; // Actual progress bar fill
16
- itemFooter?: string; // Footer section inside item
17
- }
18
-
19
- /**
20
- * MasonrySnapGridLayoutOptions
21
- * Configuration options accepted by MasonrySnapGridLayout.
22
- */
23
- export interface MasonrySnapGridLayoutOptions {
24
- /** Spacing between items in pixels (default: 16) */
25
- gutter?: number;
26
-
27
- /** Minimum width for each column in pixels (default: 250) */
28
- minColWidth?: number;
29
-
30
- /** Enable or disable animations (default: true) */
31
- animate?: boolean;
32
-
33
- /** Duration of animation transitions in milliseconds (default: 400) */
34
- transitionDuration?: number;
35
-
36
- /** Initial number of items to generate on init (default: 30) */
37
- initialItems?: number;
38
-
39
- /**
40
- * Optional class names object to override default CSS class names
41
- */
42
- classNames?: MasonrySnapGridLayoutClassNames;
43
-
44
- /**
45
- * Item content can be:
46
- * - a callback function that receives the item index and returns an HTMLElement or string (HTML markup),
47
- * - a static HTMLElement or string (HTML markup) for all items,
48
- * - or null to use the default template.
49
- */
50
- itemContent?: ((index: number) => HTMLElement | string) | HTMLElement | string | null;
51
- }
package/tsconfig.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "ESNext",
5
- "strict": true,
6
- "esModuleInterop": true,
7
- "skipLibCheck": true,
8
- "forceConsistentCasingInFileNames": true,
9
- "outDir": "./dist",
10
- "declaration": true,
11
- "moduleResolution": "bundler",
12
- "allowImportingTsExtensions": true
13
- },
14
- "include": ["src/**/*"]
15
- }
package/tsup.config.ts DELETED
@@ -1,15 +0,0 @@
1
- import { defineConfig } from 'tsup';
2
-
3
- export default defineConfig({
4
- entry: ['src/index.ts'],
5
- format: ['esm', 'cjs'],
6
- dts: true,
7
- clean: true,
8
- legacyOutput: true, // Important for proper default exports
9
- target: 'esnext',
10
- splitting: false,
11
- sourcemap: true,
12
- esbuildOptions(options) {
13
- options.tsconfig = './tsconfig.json';
14
- }
15
- });