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 +97 -29
- package/dist/esm/index.css +45 -0
- package/dist/esm/index.css.map +1 -0
- package/dist/esm/index.js +33 -11
- package/dist/esm/index.js.map +1 -1
- package/dist/index.css +45 -0
- package/dist/index.css.map +1 -0
- package/dist/index.js +33 -11
- package/dist/index.js.map +1 -1
- package/package.json +71 -28
- package/.idea/masonry-snap-grid-layout.iml +0 -12
- package/.idea/modules.xml +0 -8
- package/.idea/vcs.xml +0 -6
- package/src/MasonrySnapGridLayout.ts +0 -322
- package/src/index.ts +0 -3
- package/src/masonry-snap-grid-layout.css +0 -75
- package/src/types.ts +0 -51
- package/tsconfig.json +0 -15
- package/tsup.config.ts +0 -15
package/README.md
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
# masonry-snap-grid-layout
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/masonry-snap-grid-layout)
|
|
4
|
+
[](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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
-
|
|
96
|
+
```ts
|
|
97
|
+
new MasonrySnapGridLayout(container: HTMLElement, options?: MasonrySnapGridLayoutOptions)
|
|
98
|
+
```
|
|
57
99
|
|
|
58
|
-
|
|
59
|
-
|
|
100
|
+
* **container** — The container element where items are rendered.
|
|
101
|
+
* **options** — Configuration options (optional).
|
|
60
102
|
|
|
61
103
|
### Methods
|
|
62
104
|
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
71
|
-
|
|
72
|
-
| `gutter`
|
|
73
|
-
| `minColWidth`
|
|
74
|
-
| `animate`
|
|
75
|
-
| `transitionDuration` | `number`
|
|
76
|
-
| `initialItems`
|
|
77
|
-
| `classNames`
|
|
78
|
-
| `itemContent`
|
|
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
|
|
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(
|
|
18
|
-
this.
|
|
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,
|
package/dist/esm/index.js.map
CHANGED
|
@@ -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(
|
|
45
|
-
this.
|
|
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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACUA,IAAqB,wBAArB,MAA2C;AAAA,EACtB;AAAA,EACA;AAAA,EACT;AAAA,EAEA,QAAuB,CAAC;AAAA,EACxB,YAAuE,CAAC;AAAA,EACxE,gBAA0B,CAAC;AAAA,EAC3B,UAAkB;AAAA,EAClB,gBAAuC,CAAC;AAAA,EACxC,YAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOnC,YAAY,WAAwB,UAAwC,CAAC,GAAG;AAC5E,SAAK,YAAY;AAGjB,SAAK,UAAU;AAAA,MACX,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,SAAS;AAAA,MACT,oBAAoB;AAAA,MACpB,cAAc;AAAA,MACd,aAAa;AAAA,MACb,YAAY;AAAA,QACR,WAAW;AAAA,QACX,MAAM;AAAA,QACN,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,aAAa;AAAA,QACb,UAAU;AAAA,QACV,YAAY;AAAA,MAChB;AAAA,MACA,GAAG;AAAA,IACP;AAGA,SAAK,aAAa;AAAA,MACd,WAAW;AAAA,MACX,MAAM;AAAA,MACN,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,aAAa;AAAA,MACb,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,GAAI,KAAK,QAAQ,cAAc,CAAC;AAAA,IACpC;AAGA,SAAK,UAAU,UAAU,IAAI,KAAK,WAAW,SAAS;AAGtD,SAAK,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKQ,OAAO;AACX,SAAK,oBAAoB;AACzB,SAAK,cAAc,KAAK,QAAQ,YAAY;AAC5C,SAAK,gBAAgB;AACrB,SAAK,YAAY,KAAK;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB;AAE1B,UAAM,iBAAiB,IAAI,eAAe,MAAM,KAAK,aAAa,CAAC;AACnE,mBAAe,QAAQ,KAAK,SAAS;AAErC,WAAO,iBAAiB,UAAU,MAAM,KAAK,aAAa,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,cAAc,OAAe;AAEjC,QAAI,QAAQ,KAAK,MAAM,QAAQ;AAC3B,WAAK,MAAM,MAAM,KAAK,EAAE,QAAQ,CAAC,SAAS,KAAK,OAAO,CAAC;AACvD,WAAK,QAAQ,KAAK,MAAM,MAAM,GAAG,KAAK;AACtC;AAAA,IACJ;AAEA,UAAM,aAAa,KAAK,MAAM;AAC9B,UAAM,WAAW,SAAS,uBAAuB;AAEjD,aAAS,IAAI,YAAY,IAAI,OAAO,KAAK;AACrC,YAAM,OAAO,KAAK,WAAW,CAAC;AAC9B,eAAS,YAAY,IAAI;AACzB,WAAK,MAAM,KAAK,IAAI;AAAA,IACxB;AAEA,SAAK,UAAU,YAAY,QAAQ;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,WAAW,OAA4B;AAC3C,UAAM,MAAM,SAAS,cAAc,KAAK;AACxC,QAAI,YAAY,KAAK,WAAW;AAEhC,UAAM,gBAAgB,KAAK,QAAQ;AAGnC,QAAI;AAEJ,QAAI,OAAO,kBAAkB,YAAY;AAErC,gBAAU,cAAc,KAAK;AAAA,IACjC,WAAW,yBAAyB,eAAe,OAAO,kBAAkB,UAAU;AAElF,gBAAU;AAAA,IACd,OAAO;AAEH,gBAAU,KAAK,mBAAmB,KAAK;AAAA,IAC3C;AAGA,QAAI,OAAO,YAAY,UAAU;AAC7B,UAAI,YAAY;AAAA,IACpB,OAAO;AACH,UAAI,YAAY,OAAO;AAAA,IAC3B;AAGA,UAAM,SAAS,MAAM,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AACnD,QAAI,MAAM,SAAS,GAAG,MAAM;AAG5B,UAAM,MAAO,QAAQ,UAAW;AAChC,QAAI,MAAM,aAAa,+BAA+B,GAAG,oBAAoB,MAAM,EAAE;AAErF,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,mBAAmB,OAAuB;AAC9C,WAAO;AAAA,oBACK,KAAK,WAAW,WAAW;AAAA,sBACzB,KAAK,WAAW,UAAU;AAAA,wBACxB,KAAK,WAAW,SAAS,UAAU,QAAQ,CAAC;AAAA,wBAC5C,KAAK,WAAW,MAAM,MAAM,QAAQ,CAAC;AAAA;AAAA,sBAEvC,KAAK,WAAW,QAAQ;AAAA,wBACtB,KAAK,WAAW,WAAW;AAAA,0BACzB,KAAK,WAAW,QAAQ;AAAA;AAAA;AAAA,sBAG5B,KAAK,WAAW,UAAU;AAAA,YACpC,KAAK,eAAe,CAAC;AAAA;AAAA;AAAA;AAAA,EAI7B;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAyB;AAC7B,UAAM,SAAS,CAAC,aAAM,UAAK,aAAM,aAAM,aAAM,aAAM,UAAK,aAAM,aAAM,WAAI;AACxE,WAAO,OAAO,KAAK,MAAM,KAAK,OAAO,IAAI,OAAO,MAAM,CAAC;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAkB;AACtB,UAAM,EAAE,QAAQ,YAAY,IAAI,KAAK;AACrC,UAAM,iBAAiB,KAAK,UAAU;AAGtC,SAAK,UAAU,KAAK,IAAI,GAAG,KAAK,OAAO,iBAAiB,WAAW,cAAc,OAAO,CAAC;AAGzF,UAAM,YAAY,kBAAkB,KAAK,UAAU,KAAK,UAAU,KAAK;AAGvE,SAAK,gBAAgB,CAAC,GAAG,KAAK,SAAS;AAGvC,SAAK,gBAAgB,IAAI,MAAM,KAAK,OAAO,EAAE,KAAK,CAAC;AACnD,SAAK,YAAY,CAAC;AAGlB,SAAK,MAAM,QAAQ,CAAC,MAAM,MAAM;AAC5B,YAAM,SAAS,KAAK;AAGpB,UAAI,SAAS;AACb,eAAS,IAAI,GAAG,IAAI,KAAK,SAAS,KAAK;AACnC,YAAI,KAAK,cAAc,CAAC,IAAI,KAAK,cAAc,MAAM,GAAG;AACpD,mBAAS;AAAA,QACb;AAAA,MACJ;AAGA,YAAM,IAAI,UAAU,WAAW;AAC/B,YAAM,IAAI,KAAK,cAAc,MAAM;AAGnC,WAAK,UAAU,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,UAAU,OAAO;AAGpD,WAAK,cAAc,MAAM,KAAK,SAAS;AAAA,IAC3C,CAAC;AAGD,UAAM,YAAY,KAAK,IAAI,GAAG,KAAK,aAAa;AAChD,SAAK,UAAU,MAAM,SAAS,GAAG,SAAS;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,YAAY,UAAmB,OAAO;AAC1C,UAAM,WAAW,KAAK,QAAQ;AAE9B,SAAK,MAAM,QAAQ,CAAC,MAAM,MAAM;AAC5B,YAAM,MAAM,KAAK,UAAU,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,GAAG,OAAO,EAAE;AACxD,YAAM,UAAU,KAAK,cAAc,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,EAAE;AAGtD,WAAK,MAAM,QAAQ,GAAG,IAAI,KAAK;AAE/B,UAAI,SAAS;AAET,cAAM,KAAK,QAAQ,IAAI,IAAI;AAC3B,cAAM,KAAK,QAAQ,IAAI,IAAI;AAG3B,aAAK,MAAM,aAAa;AACxB,aAAK,MAAM,YAAY,eAAe,IAAI,IAAI,EAAE,OAAO,IAAI,IAAI,EAAE;AAGjE,aAAK,KAAK;AAGV,aAAK,MAAM,aAAa,aAAa,QAAQ;AAC7C,aAAK,MAAM,YAAY,eAAe,IAAI,CAAC,OAAO,IAAI,CAAC;AAAA,MAC3D,OAAO;AAEH,aAAK,MAAM,aAAa;AACxB,aAAK,MAAM,YAAY,eAAe,IAAI,CAAC,OAAO,IAAI,CAAC;AAAA,MAC3D;AAAA,IACJ,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe;AACnB,QAAI,KAAK,UAAW,sBAAqB,KAAK,SAAS;AACvD,SAAK,YAAY,sBAAsB,MAAM;AACzC,WAAK,gBAAgB;AACrB,WAAK,YAAY,IAAI;AAAA,IACzB,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA,EAKO,eAAe;AAElB,aAAS,IAAI,KAAK,MAAM,SAAS,GAAG,IAAI,GAAG,KAAK;AAC5C,YAAM,IAAI,KAAK,MAAM,KAAK,OAAO,KAAK,IAAI,EAAE;AAC5C,OAAC,KAAK,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC;AAAA,IAClE;AAGA,UAAM,WAAW,SAAS,uBAAuB;AACjD,SAAK,MAAM,QAAQ,CAAC,SAAS,SAAS,YAAY,IAAI,CAAC;AACvD,SAAK,UAAU,YAAY,QAAQ;AAGnC,SAAK,gBAAgB;AACrB,SAAK,YAAY,IAAI;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,SAAS,OAAe;AAC3B,UAAM,WAAW,KAAK,MAAM,SAAS;AACrC,SAAK,cAAc,QAAQ;AAC3B,SAAK,gBAAgB;AACrB,SAAK,YAAY,IAAI;AAAA,EACzB;AACJ;;;ADhUA,IAAO,gBAAQ;","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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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,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,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
|
-
});
|