@thangdevalone/meeting-grid-layout-core 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,902 @@
1
+ const DEFAULT_FLOAT_BREAKPOINTS = [
2
+ { minWidth: 0, width: 100, height: 135 },
3
+ { minWidth: 480, width: 130, height: 175 },
4
+ { minWidth: 768, width: 160, height: 215 },
5
+ { minWidth: 1024, width: 180, height: 240 },
6
+ { minWidth: 1440, width: 220, height: 295 }
7
+ ];
8
+ function resolveFloatSize(containerWidth, breakpoints) {
9
+ const sorted = [...breakpoints].sort((a, b) => b.minWidth - a.minWidth);
10
+ const match = sorted.find((bp) => containerWidth >= bp.minWidth);
11
+ if (match) {
12
+ return { width: match.width, height: match.height };
13
+ }
14
+ const smallest = sorted[sorted.length - 1];
15
+ return smallest ? { width: smallest.width, height: smallest.height } : { width: 120, height: 160 };
16
+ }
17
+ function getAspectRatio(ratio) {
18
+ const [width, height] = ratio.split(":");
19
+ if (!width || !height) {
20
+ throw new Error(
21
+ 'meet-layout-grid: Invalid aspect ratio provided, expected format is "width:height".'
22
+ );
23
+ }
24
+ return Number.parseInt(height) / Number.parseInt(width);
25
+ }
26
+ function parseAspectRatio(ratio) {
27
+ const [width, height] = ratio.split(":").map(Number);
28
+ if (!width || !height || isNaN(width) || isNaN(height)) {
29
+ throw new Error(
30
+ 'meet-layout-grid: Invalid aspect ratio provided, expected format is "width:height".'
31
+ );
32
+ }
33
+ return { widthRatio: width, heightRatio: height };
34
+ }
35
+ function calculateContentDimensions(cellDimensions, itemRatio, defaultRatio) {
36
+ const { width: cellW, height: cellH } = cellDimensions;
37
+ const effectiveRatio = itemRatio ?? (defaultRatio ? defaultRatio : void 0);
38
+ if (!effectiveRatio || effectiveRatio === "auto") {
39
+ return {
40
+ width: cellW,
41
+ height: cellH,
42
+ offsetTop: 0,
43
+ offsetLeft: 0
44
+ };
45
+ }
46
+ const ratio = getAspectRatio(effectiveRatio);
47
+ let contentW = cellW;
48
+ let contentH = contentW * ratio;
49
+ if (contentH > cellH) {
50
+ contentH = cellH;
51
+ contentW = contentH / ratio;
52
+ }
53
+ const offsetTop = (cellH - contentH) / 2;
54
+ const offsetLeft = (cellW - contentW) / 2;
55
+ return {
56
+ width: contentW,
57
+ height: contentH,
58
+ offsetTop,
59
+ offsetLeft
60
+ };
61
+ }
62
+ function createGetItemContentDimensions(getItemDimensions, itemAspectRatios, defaultRatio) {
63
+ return (index, itemRatio) => {
64
+ const cellDimensions = getItemDimensions(index);
65
+ const effectiveRatio = itemRatio ?? itemAspectRatios?.[index] ?? defaultRatio;
66
+ return calculateContentDimensions(cellDimensions, effectiveRatio, defaultRatio);
67
+ };
68
+ }
69
+ function getGridItemDimensions({ count, dimensions, aspectRatio, gap }) {
70
+ let { width: W, height: H } = dimensions;
71
+ if (W === 0 || H === 0 || count === 0) {
72
+ return { width: 0, height: 0, rows: 1, cols: 1 };
73
+ }
74
+ W -= gap * 2;
75
+ H -= gap * 2;
76
+ const s = gap;
77
+ const N = count;
78
+ const r = getAspectRatio(aspectRatio);
79
+ let w = 0;
80
+ let h = 0;
81
+ let a = 1;
82
+ let b = 1;
83
+ const minCols = 1;
84
+ const widths = [];
85
+ for (let n = 1; n <= N; n++) {
86
+ widths.push((W - s * (n - 1)) / n, (H - s * (n - 1)) / (n * r));
87
+ }
88
+ widths.sort((a2, b2) => b2 - a2);
89
+ for (const width of widths) {
90
+ w = width;
91
+ h = w * r;
92
+ a = Math.floor((W + s) / (w + s));
93
+ b = Math.floor((H + s) / (h + s));
94
+ if (a < minCols && N >= minCols) {
95
+ continue;
96
+ }
97
+ if (a * b >= N) {
98
+ a = Math.max(minCols, Math.ceil(N / b));
99
+ b = Math.ceil(N / a);
100
+ break;
101
+ }
102
+ }
103
+ if (a < minCols && N >= minCols) {
104
+ a = minCols;
105
+ b = Math.ceil(N / a);
106
+ w = (W - s * (a - 1)) / a;
107
+ h = w * r;
108
+ const requiredHeight = b * h + (b - 1) * s;
109
+ if (requiredHeight > H) {
110
+ const scale = H / requiredHeight;
111
+ h = h * scale;
112
+ w = h / r;
113
+ }
114
+ }
115
+ return { width: w, height: h, rows: b, cols: a };
116
+ }
117
+ function createGridItemPositioner({
118
+ parentDimensions,
119
+ dimensions,
120
+ rows,
121
+ cols,
122
+ count,
123
+ gap
124
+ }) {
125
+ const { width: W, height: H } = parentDimensions;
126
+ const { width: w, height: h } = dimensions;
127
+ const firstTop = (H - (h * rows + (rows - 1) * gap)) / 2;
128
+ let firstLeft = (W - (w * cols + (cols - 1) * gap)) / 2;
129
+ const topAdd = h + gap;
130
+ const leftAdd = w + gap;
131
+ const incompleteRowCols = count % cols;
132
+ const lastRowStartIndex = incompleteRowCols > 0 ? count - incompleteRowCols : count - cols;
133
+ function getPosition(index) {
134
+ const row = Math.floor(index / cols);
135
+ const col = index % cols;
136
+ const isInLastRow = incompleteRowCols > 0 && index >= lastRowStartIndex;
137
+ let leftOffset = firstLeft;
138
+ if (isInLastRow) {
139
+ const lastRowItemCount = incompleteRowCols;
140
+ const colInLastRow = index - lastRowStartIndex;
141
+ leftOffset = (W - (w * lastRowItemCount + (lastRowItemCount - 1) * gap)) / 2;
142
+ const top2 = firstTop + row * topAdd;
143
+ const left2 = leftOffset + colInLastRow * leftAdd;
144
+ return { top: top2, left: left2 };
145
+ }
146
+ const top = firstTop + row * topAdd;
147
+ const left = leftOffset + col * leftAdd;
148
+ return { top, left };
149
+ }
150
+ return getPosition;
151
+ }
152
+ function createGrid({ aspectRatio, count, dimensions, gap }) {
153
+ const { width, height, rows, cols } = getGridItemDimensions({
154
+ aspectRatio,
155
+ count,
156
+ dimensions,
157
+ gap
158
+ });
159
+ const getPosition = createGridItemPositioner({
160
+ parentDimensions: dimensions,
161
+ dimensions: { width, height },
162
+ rows,
163
+ cols,
164
+ count,
165
+ gap
166
+ });
167
+ return {
168
+ width,
169
+ height,
170
+ rows,
171
+ cols,
172
+ getPosition
173
+ };
174
+ }
175
+ function createFlexiblePinGrid(options) {
176
+ const {
177
+ dimensions,
178
+ gap,
179
+ aspectRatio,
180
+ count,
181
+ othersPosition = "right",
182
+ pinnedIndex = 0,
183
+ maxVisible = 0,
184
+ currentVisiblePage = 0
185
+ } = options;
186
+ if (count === 0) {
187
+ return createEmptyMeetGridResult("gallery");
188
+ }
189
+ if (count === 1) {
190
+ const { width: W2, height: H2 } = dimensions;
191
+ const mainWidth2 = W2 - gap * 2;
192
+ const mainHeight2 = H2 - gap * 2;
193
+ const getItemDimensions2 = () => ({ width: mainWidth2, height: mainHeight2 });
194
+ const pagination2 = createDefaultPagination(1);
195
+ return {
196
+ width: mainWidth2,
197
+ height: mainHeight2,
198
+ rows: 1,
199
+ cols: 1,
200
+ layoutMode: "gallery",
201
+ getPosition: () => ({ top: gap, left: gap }),
202
+ getItemDimensions: getItemDimensions2,
203
+ isMainItem: () => true,
204
+ pagination: pagination2,
205
+ isItemVisible: () => true,
206
+ hiddenCount: 0,
207
+ getLastVisibleOthersIndex: () => -1,
208
+ getItemContentDimensions: createGetItemContentDimensions(
209
+ getItemDimensions2,
210
+ options.itemAspectRatios,
211
+ aspectRatio
212
+ )
213
+ };
214
+ }
215
+ const { width: W, height: H } = dimensions;
216
+ const isPortrait = H > W;
217
+ const effectivePosition = isPortrait ? "bottom" : othersPosition;
218
+ const isVertical = effectivePosition === "bottom" || effectivePosition === "top";
219
+ const ratio = getAspectRatio(aspectRatio);
220
+ const totalOthers = count - 1;
221
+ const visibleOthers = maxVisible > 0 ? Math.min(maxVisible, totalOthers) : totalOthers;
222
+ const othersTotalPages = maxVisible > 0 ? Math.ceil(totalOthers / maxVisible) : 1;
223
+ const safeCurrentVisiblePage = Math.min(currentVisiblePage, Math.max(0, othersTotalPages - 1));
224
+ const startOthersIndex = safeCurrentVisiblePage * (maxVisible > 0 ? maxVisible : totalOthers);
225
+ const endOthersIndex = Math.min(startOthersIndex + visibleOthers, totalOthers);
226
+ const itemsOnPage = endOthersIndex - startOthersIndex;
227
+ const isPaginationMode = othersTotalPages > 1;
228
+ const isActivelyPaginating = isPaginationMode && currentVisiblePage > 0;
229
+ const hiddenCount = isActivelyPaginating ? 0 : totalOthers > itemsOnPage ? totalOthers - itemsOnPage + 1 : 0;
230
+ let mainWidth;
231
+ let mainHeight;
232
+ let othersAreaWidth;
233
+ let othersAreaHeight;
234
+ if (isVertical) {
235
+ const areaW = W - gap * 2;
236
+ const isMobilePin = W < 500;
237
+ if (isMobilePin && isPortrait && visibleOthers > 0) {
238
+ const mobileCols = Math.min(visibleOthers, 2);
239
+ const mobileRows = Math.ceil(visibleOthers / mobileCols);
240
+ let thumbW = (areaW - (mobileCols - 1) * gap) / mobileCols;
241
+ let thumbH = thumbW * ratio;
242
+ let neededH = mobileRows * thumbH + (mobileRows - 1) * gap + gap;
243
+ const maxOthersH = H * 0.7;
244
+ if (neededH > maxOthersH) {
245
+ neededH = maxOthersH;
246
+ const availThumbH = (neededH - (mobileRows - 1) * gap - gap * 2) / mobileRows;
247
+ thumbH = availThumbH;
248
+ thumbW = thumbH / ratio;
249
+ }
250
+ othersAreaHeight = neededH;
251
+ othersAreaWidth = areaW;
252
+ } else {
253
+ const othersRatio = isPortrait ? 1 : ratio;
254
+ let bestOthersH = 0;
255
+ let bestThumbArea = 0;
256
+ const maxRows = Math.min(3, visibleOthers || 1);
257
+ const maxOthersRatio = 0.5;
258
+ for (let rows = 1; rows <= maxRows; rows++) {
259
+ const cols = Math.ceil((visibleOthers || 1) / rows);
260
+ const thumbW = (areaW - (cols - 1) * gap) / cols;
261
+ const thumbH = thumbW * othersRatio;
262
+ const requiredH = rows * thumbH + (rows - 1) * gap + gap;
263
+ const areaRatio = requiredH / H;
264
+ if (areaRatio > maxOthersRatio)
265
+ continue;
266
+ if (thumbH < 40)
267
+ continue;
268
+ const thumbArea = thumbW * thumbH;
269
+ if (thumbArea > bestThumbArea) {
270
+ bestThumbArea = thumbArea;
271
+ bestOthersH = requiredH;
272
+ }
273
+ }
274
+ if (bestOthersH === 0) {
275
+ bestOthersH = H * (isPortrait ? 0.25 : 0.2);
276
+ }
277
+ const minRatio = 0.12;
278
+ const maxRatio = 0.45;
279
+ if (bestOthersH / H < minRatio)
280
+ bestOthersH = H * minRatio;
281
+ else if (bestOthersH / H > maxRatio)
282
+ bestOthersH = H * maxRatio;
283
+ othersAreaHeight = bestOthersH;
284
+ othersAreaWidth = areaW;
285
+ }
286
+ mainHeight = H - othersAreaHeight - gap * 3;
287
+ mainWidth = areaW;
288
+ } else {
289
+ const areaH = H - gap * 2;
290
+ let bestOthersW = W * 0.2;
291
+ let bestScore = 0;
292
+ const maxCols = Math.min(3, visibleOthers || 1);
293
+ for (let cols = 1; cols <= maxCols; cols++) {
294
+ const rows = Math.ceil((visibleOthers || 1) / cols);
295
+ const thumbH = (areaH - (rows - 1) * gap) / rows;
296
+ const thumbW = thumbH / ratio;
297
+ const requiredW = cols * thumbW + (cols - 1) * gap + gap * 2;
298
+ const areaRatio = requiredW / W;
299
+ const thumbArea = thumbW * thumbH;
300
+ const mainAreaBonus = (1 - areaRatio) * 0.5;
301
+ const score = thumbArea * (1 + mainAreaBonus);
302
+ if (areaRatio >= 0.1 && areaRatio <= 0.4 && score > bestScore) {
303
+ bestOthersW = requiredW;
304
+ bestScore = score;
305
+ }
306
+ }
307
+ if (bestOthersW / W < 0.1)
308
+ bestOthersW = W * 0.12;
309
+ else if (bestOthersW / W > 0.4)
310
+ bestOthersW = W * 0.35;
311
+ othersAreaWidth = bestOthersW;
312
+ othersAreaHeight = areaH;
313
+ mainWidth = W - othersAreaWidth - gap * 2;
314
+ mainHeight = areaH;
315
+ }
316
+ const mainItemWidth = mainWidth;
317
+ const mainItemHeight = mainHeight;
318
+ const positions = [];
319
+ let mainLeft;
320
+ let mainTop;
321
+ if (isVertical) {
322
+ mainLeft = gap + (mainWidth - mainItemWidth) / 2;
323
+ mainTop = effectivePosition === "top" ? othersAreaHeight + gap * 2 + (mainHeight - mainItemHeight) / 2 : gap + (mainHeight - mainItemHeight) / 2;
324
+ } else {
325
+ mainLeft = effectivePosition === "left" ? othersAreaWidth + gap * 2 + (mainWidth - mainItemWidth) / 2 : gap + (mainWidth - mainItemWidth) / 2;
326
+ mainTop = gap + (mainHeight - mainItemHeight) / 2;
327
+ }
328
+ positions[pinnedIndex] = {
329
+ position: { top: mainTop, left: mainLeft },
330
+ dimensions: { width: mainItemWidth, height: mainItemHeight }
331
+ };
332
+ {
333
+ const isMobileOthers = W < 500;
334
+ const othersRatio = isPortrait ? 1 : ratio;
335
+ let thumbCols = 1;
336
+ let thumbRows = 1;
337
+ let thumbWidth = 0;
338
+ let thumbHeight = 0;
339
+ if (visibleOthers > 0) {
340
+ if (isVertical) {
341
+ if (isMobileOthers && isPortrait) {
342
+ thumbCols = Math.min(visibleOthers, 2);
343
+ thumbRows = Math.ceil(visibleOthers / thumbCols);
344
+ const maxW = (othersAreaWidth - (thumbCols - 1) * gap) / thumbCols;
345
+ const maxH = (othersAreaHeight - (thumbRows - 1) * gap - gap) / thumbRows;
346
+ thumbWidth = maxW;
347
+ thumbHeight = thumbWidth * ratio;
348
+ if (thumbHeight > maxH) {
349
+ thumbHeight = maxH;
350
+ thumbWidth = thumbHeight / ratio;
351
+ }
352
+ } else {
353
+ let bestScore = -1;
354
+ for (let cols = 1; cols <= visibleOthers; cols++) {
355
+ const rows = Math.ceil(visibleOthers / cols);
356
+ const maxTileW = (othersAreaWidth - (cols - 1) * gap) / cols;
357
+ const maxTileH = (othersAreaHeight - (rows - 1) * gap) / rows;
358
+ let tileW = maxTileW;
359
+ let tileH = tileW * othersRatio;
360
+ if (tileH > maxTileH) {
361
+ tileH = maxTileH;
362
+ tileW = tileH / othersRatio;
363
+ }
364
+ const area = tileW * tileH;
365
+ const colsMultiplier = cols >= rows ? 1.5 : 0.5;
366
+ const score = area * colsMultiplier;
367
+ if (score > bestScore) {
368
+ bestScore = score;
369
+ thumbCols = cols;
370
+ thumbRows = rows;
371
+ thumbWidth = tileW;
372
+ thumbHeight = tileH;
373
+ }
374
+ }
375
+ }
376
+ } else {
377
+ let bestScore = -1;
378
+ const targetRatio = 1 / ratio;
379
+ for (let rows = 1; rows <= visibleOthers; rows++) {
380
+ const cols = Math.ceil(visibleOthers / rows);
381
+ const maxTileH = (othersAreaHeight - (rows - 1) * gap) / rows;
382
+ const idealTileW = maxTileH * targetRatio;
383
+ const maxTileW = (othersAreaWidth - (cols - 1) * gap) / cols;
384
+ let tileW, tileH;
385
+ if (idealTileW <= maxTileW) {
386
+ tileW = idealTileW;
387
+ tileH = maxTileH;
388
+ } else {
389
+ tileW = maxTileW;
390
+ tileH = tileW / targetRatio;
391
+ }
392
+ const area = tileW * tileH * visibleOthers;
393
+ if (area > bestScore) {
394
+ bestScore = area;
395
+ thumbCols = cols;
396
+ thumbRows = rows;
397
+ thumbWidth = tileW;
398
+ thumbHeight = tileH;
399
+ }
400
+ }
401
+ }
402
+ }
403
+ const totalGridWidth = thumbCols * thumbWidth + (thumbCols - 1) * gap;
404
+ const totalGridHeight = thumbRows * thumbHeight + (thumbRows - 1) * gap;
405
+ let gridStartLeft;
406
+ let gridStartTop;
407
+ if (isVertical) {
408
+ gridStartLeft = gap + (othersAreaWidth - totalGridWidth) / 2;
409
+ gridStartTop = effectivePosition === "top" ? gap + (othersAreaHeight - totalGridHeight) / 2 : mainHeight + gap * 2 + (othersAreaHeight - totalGridHeight) / 2;
410
+ } else {
411
+ gridStartLeft = effectivePosition === "left" ? gap + (othersAreaWidth - totalGridWidth) / 2 : mainWidth + gap * 2 + (othersAreaWidth - totalGridWidth) / 2;
412
+ gridStartTop = gap + (othersAreaHeight - totalGridHeight) / 2;
413
+ }
414
+ let othersIndex = 0;
415
+ for (let i = 0; i < count; i++) {
416
+ if (i === pinnedIndex)
417
+ continue;
418
+ const isInVisibleRange = othersIndex >= startOthersIndex && othersIndex < endOthersIndex;
419
+ if (isInVisibleRange) {
420
+ const pageRelativeIndex = othersIndex - startOthersIndex;
421
+ const row = Math.floor(pageRelativeIndex / thumbCols);
422
+ const col = pageRelativeIndex % thumbCols;
423
+ const itemsInLastRow = itemsOnPage % thumbCols || thumbCols;
424
+ let rowLeft = gridStartLeft;
425
+ const lastRowIndex = Math.ceil(itemsOnPage / thumbCols) - 1;
426
+ if (row === lastRowIndex && itemsInLastRow < thumbCols) {
427
+ const rowWidth = itemsInLastRow * thumbWidth + (itemsInLastRow - 1) * gap;
428
+ if (isVertical) {
429
+ rowLeft = gap + (othersAreaWidth - rowWidth) / 2;
430
+ } else {
431
+ rowLeft = (effectivePosition === "left" ? gap : mainWidth + gap * 2) + (othersAreaWidth - rowWidth) / 2;
432
+ }
433
+ }
434
+ positions[i] = {
435
+ position: {
436
+ top: gridStartTop + row * (thumbHeight + gap),
437
+ left: rowLeft + col * (thumbWidth + gap)
438
+ },
439
+ dimensions: { width: thumbWidth, height: thumbHeight }
440
+ };
441
+ } else {
442
+ positions[i] = {
443
+ position: { top: -9999, left: -9999 },
444
+ dimensions: { width: 0, height: 0 }
445
+ };
446
+ }
447
+ othersIndex++;
448
+ }
449
+ }
450
+ const pagination = {
451
+ enabled: maxVisible > 0 && totalOthers > maxVisible,
452
+ currentPage: safeCurrentVisiblePage,
453
+ totalPages: othersTotalPages,
454
+ itemsOnPage,
455
+ startIndex: startOthersIndex,
456
+ endIndex: endOthersIndex
457
+ };
458
+ const getItemDimensions = (index) => positions[index]?.dimensions ?? { width: 0, height: 0 };
459
+ const getLastVisibleOthersIndex = () => {
460
+ if (itemsOnPage === 0)
461
+ return -1;
462
+ let othersIdx = 0;
463
+ let lastVisibleOriginalIdx = -1;
464
+ for (let i = 0; i < count; i++) {
465
+ if (i === pinnedIndex)
466
+ continue;
467
+ if (othersIdx >= startOthersIndex && othersIdx < endOthersIndex) {
468
+ lastVisibleOriginalIdx = i;
469
+ }
470
+ othersIdx++;
471
+ }
472
+ return lastVisibleOriginalIdx;
473
+ };
474
+ return {
475
+ width: mainItemWidth,
476
+ height: mainItemHeight,
477
+ rows: isVertical ? 2 : 1,
478
+ cols: isVertical ? 1 : 2,
479
+ layoutMode: "gallery",
480
+ getPosition: (index) => positions[index]?.position ?? { top: 0, left: 0 },
481
+ getItemDimensions,
482
+ isMainItem: (index) => index === pinnedIndex,
483
+ pagination,
484
+ isItemVisible: (index) => {
485
+ if (index === pinnedIndex)
486
+ return true;
487
+ let sIdx = 0;
488
+ for (let i = 0; i < index; i++) {
489
+ if (i !== pinnedIndex)
490
+ sIdx++;
491
+ }
492
+ return sIdx >= startOthersIndex && sIdx < endOthersIndex;
493
+ },
494
+ hiddenCount,
495
+ getLastVisibleOthersIndex,
496
+ getItemContentDimensions: createGetItemContentDimensions(
497
+ getItemDimensions,
498
+ options.itemAspectRatios,
499
+ aspectRatio
500
+ )
501
+ };
502
+ }
503
+ function createSpotlightGrid(options) {
504
+ const { dimensions, gap, aspectRatio, pinnedIndex = 0, itemAspectRatios } = options;
505
+ const { width: W, height: H } = dimensions;
506
+ const itemRatio = itemAspectRatios?.[pinnedIndex];
507
+ const shouldFill = itemRatio === "auto";
508
+ let spotWidth;
509
+ let spotHeight;
510
+ if (shouldFill) {
511
+ spotWidth = W - gap * 2;
512
+ spotHeight = H - gap * 2;
513
+ } else {
514
+ const ratio = itemRatio ? getAspectRatio(itemRatio) : getAspectRatio(aspectRatio);
515
+ spotWidth = W - gap * 2;
516
+ spotHeight = spotWidth * ratio;
517
+ if (spotHeight > H - gap * 2) {
518
+ spotHeight = H - gap * 2;
519
+ spotWidth = spotHeight / ratio;
520
+ }
521
+ }
522
+ const position = {
523
+ top: gap + (H - gap * 2 - spotHeight) / 2,
524
+ left: gap + (W - gap * 2 - spotWidth) / 2
525
+ };
526
+ const pagination = createDefaultPagination(1);
527
+ const getItemDimensions = (index) => index === pinnedIndex ? { width: spotWidth, height: spotHeight } : { width: 0, height: 0 };
528
+ return {
529
+ width: spotWidth,
530
+ height: spotHeight,
531
+ rows: 1,
532
+ cols: 1,
533
+ layoutMode: "spotlight",
534
+ getPosition: (index) => index === pinnedIndex ? position : { top: -9999, left: -9999 },
535
+ getItemDimensions,
536
+ isMainItem: (index) => index === pinnedIndex,
537
+ pagination,
538
+ isItemVisible: (index) => index === pinnedIndex,
539
+ hiddenCount: 0,
540
+ getLastVisibleOthersIndex: () => -1,
541
+ getItemContentDimensions: createGetItemContentDimensions(
542
+ getItemDimensions,
543
+ options.itemAspectRatios,
544
+ aspectRatio
545
+ )
546
+ };
547
+ }
548
+ function createDefaultPagination(count) {
549
+ return {
550
+ enabled: false,
551
+ currentPage: 0,
552
+ totalPages: 1,
553
+ itemsOnPage: count,
554
+ startIndex: 0,
555
+ endIndex: count
556
+ };
557
+ }
558
+ function createPaginationInfo(count, maxItemsPerPage, currentPage) {
559
+ if (!maxItemsPerPage || maxItemsPerPage <= 0 || maxItemsPerPage >= count) {
560
+ return createDefaultPagination(count);
561
+ }
562
+ const totalPages = Math.ceil(count / maxItemsPerPage);
563
+ const page = Math.min(Math.max(0, currentPage ?? 0), totalPages - 1);
564
+ const startIndex = page * maxItemsPerPage;
565
+ const endIndex = Math.min(startIndex + maxItemsPerPage, count);
566
+ return {
567
+ enabled: true,
568
+ currentPage: page,
569
+ totalPages,
570
+ itemsOnPage: endIndex - startIndex,
571
+ startIndex,
572
+ endIndex
573
+ };
574
+ }
575
+ function createEmptyMeetGridResult(layoutMode) {
576
+ const getItemDimensions = () => ({ width: 0, height: 0 });
577
+ return {
578
+ width: 0,
579
+ height: 0,
580
+ rows: 0,
581
+ cols: 0,
582
+ layoutMode,
583
+ getPosition: () => ({ top: 0, left: 0 }),
584
+ getItemDimensions,
585
+ isMainItem: () => false,
586
+ pagination: createDefaultPagination(0),
587
+ isItemVisible: () => false,
588
+ hiddenCount: 0,
589
+ getLastVisibleOthersIndex: () => -1,
590
+ getItemContentDimensions: () => ({ width: 0, height: 0, offsetTop: 0, offsetLeft: 0 })
591
+ };
592
+ }
593
+ function createFlexibleGalleryGrid(options) {
594
+ const {
595
+ dimensions,
596
+ gap,
597
+ aspectRatio,
598
+ count,
599
+ itemAspectRatios = [],
600
+ maxItemsPerPage,
601
+ currentPage,
602
+ maxVisible = 0
603
+ } = options;
604
+ if (count === 0) {
605
+ return createEmptyMeetGridResult("gallery");
606
+ }
607
+ const { width: W, height: H } = dimensions;
608
+ const availW = W - gap * 2;
609
+ const availH = H - gap * 2;
610
+ let visibleCount = count;
611
+ let hiddenCount = 0;
612
+ let startIndex = 0;
613
+ let endIndex = count;
614
+ if (maxItemsPerPage && maxItemsPerPage > 0) {
615
+ const pagInfo = createPaginationInfo(count, maxItemsPerPage, currentPage);
616
+ visibleCount = pagInfo.itemsOnPage;
617
+ startIndex = pagInfo.startIndex;
618
+ endIndex = pagInfo.endIndex;
619
+ } else if (maxVisible > 0 && count > maxVisible) {
620
+ visibleCount = maxVisible;
621
+ hiddenCount = count - maxVisible + 1;
622
+ startIndex = 0;
623
+ endIndex = maxVisible;
624
+ }
625
+ const pagination = maxItemsPerPage && maxItemsPerPage > 0 ? createPaginationInfo(count, maxItemsPerPage, currentPage) : {
626
+ enabled: false,
627
+ currentPage: 0,
628
+ totalPages: 1,
629
+ itemsOnPage: visibleCount,
630
+ startIndex,
631
+ endIndex
632
+ };
633
+ const visibleIndices = [];
634
+ for (let i = startIndex; i < endIndex; i++) {
635
+ visibleIndices.push(i);
636
+ }
637
+ const itemWHRatios = [];
638
+ for (const idx of visibleIndices) {
639
+ const ratioStr = itemAspectRatios[idx] ?? aspectRatio;
640
+ const hw = getAspectRatio(ratioStr);
641
+ itemWHRatios.push(1 / hw);
642
+ }
643
+ const effectiveCount = visibleIndices.length;
644
+ function computeTotalHeightForRows(numRows) {
645
+ const base = Math.floor(effectiveCount / numRows);
646
+ const extra = effectiveCount % numRows;
647
+ let totalH = 0;
648
+ let itemIdx = 0;
649
+ for (let r = 0; r < numRows; r++) {
650
+ const rowSize = base + (r < extra ? 1 : 0);
651
+ let totalUnitW = 0;
652
+ for (let i = 0; i < rowSize; i++) {
653
+ totalUnitW += itemWHRatios[itemIdx + i];
654
+ }
655
+ const netW = availW - (rowSize - 1) * gap;
656
+ totalH += netW / totalUnitW;
657
+ itemIdx += rowSize;
658
+ }
659
+ return totalH + (numRows - 1) * gap;
660
+ }
661
+ function distributeEvenly(numRows) {
662
+ const result = [];
663
+ const base = Math.floor(effectiveCount / numRows);
664
+ const extra = effectiveCount % numRows;
665
+ let idx = 0;
666
+ for (let r = 0; r < numRows; r++) {
667
+ const size = base + (r < extra ? 1 : 0);
668
+ result.push(Array.from({ length: size }, (_, i) => idx + i));
669
+ idx += size;
670
+ }
671
+ return result;
672
+ }
673
+ let bestRowCount = 1;
674
+ let bestDiff = Infinity;
675
+ const maxTryRows = Math.min(effectiveCount, Math.ceil(Math.sqrt(effectiveCount) * 2.5));
676
+ let prevTotalH = 0;
677
+ for (let numRows = 1; numRows <= maxTryRows; numRows++) {
678
+ if (Math.floor(effectiveCount / numRows) === 0)
679
+ break;
680
+ const totalH = computeTotalHeightForRows(numRows);
681
+ const diff = Math.abs(totalH - availH);
682
+ if (diff < bestDiff) {
683
+ bestDiff = diff;
684
+ bestRowCount = numRows;
685
+ }
686
+ if (numRows > 1 && totalH > availH && prevTotalH < availH) {
687
+ break;
688
+ }
689
+ prevTotalH = totalH;
690
+ }
691
+ const bestRows = distributeEvenly(bestRowCount);
692
+ const posMap = /* @__PURE__ */ new Map();
693
+ const rowCount = bestRows.length;
694
+ const rowHeights = [];
695
+ for (const row of bestRows) {
696
+ const totalUnitW = row.reduce((s, relIdx) => s + itemWHRatios[relIdx], 0);
697
+ const netW = availW - (row.length - 1) * gap;
698
+ rowHeights.push(netW / totalUnitW);
699
+ }
700
+ const totalRowH = rowHeights.reduce((s, h) => s + h, 0) + (rowCount - 1) * gap;
701
+ const globalScale = Math.min(1, availH / totalRowH);
702
+ const scaledTotalH = rowHeights.reduce((s, h) => s + h * globalScale, 0) + (rowCount - 1) * gap;
703
+ const verticalOffset = (availH - scaledTotalH) / 2;
704
+ let currentTop = gap + verticalOffset;
705
+ for (let ri = 0; ri < rowCount; ri++) {
706
+ const row = bestRows[ri];
707
+ const finalRowH = rowHeights[ri] * globalScale;
708
+ const totalUnitW = row.reduce((s, relIdx) => s + itemWHRatios[relIdx], 0);
709
+ const netW = availW - (row.length - 1) * gap;
710
+ const itemWidths = row.map((relIdx) => itemWHRatios[relIdx] / totalUnitW * netW * globalScale);
711
+ const scaledRowW = itemWidths.reduce((s, w) => s + w, 0) + (row.length - 1) * gap;
712
+ const horizontalOffset = (availW - scaledRowW) / 2;
713
+ let currentLeft = gap + horizontalOffset;
714
+ for (let ci = 0; ci < row.length; ci++) {
715
+ posMap.set(row[ci], {
716
+ position: { top: currentTop, left: currentLeft },
717
+ dimensions: { width: itemWidths[ci], height: finalRowH }
718
+ });
719
+ currentLeft += itemWidths[ci] + gap;
720
+ }
721
+ currentTop += finalRowH + gap;
722
+ }
723
+ const getPosition = (index) => {
724
+ const relativeIndex = index - startIndex;
725
+ if (relativeIndex < 0 || relativeIndex >= effectiveCount) {
726
+ return { top: -9999, left: -9999 };
727
+ }
728
+ return posMap.get(relativeIndex)?.position ?? { top: -9999, left: -9999 };
729
+ };
730
+ const getItemDimensions = (index) => {
731
+ const relativeIndex = index - startIndex;
732
+ if (relativeIndex < 0 || relativeIndex >= effectiveCount) {
733
+ return { width: 0, height: 0 };
734
+ }
735
+ return posMap.get(relativeIndex)?.dimensions ?? { width: 0, height: 0 };
736
+ };
737
+ const lastVisibleIndex = endIndex - 1;
738
+ return {
739
+ width: availW,
740
+ height: availH,
741
+ rows: rowCount,
742
+ cols: rowCount > 0 ? Math.max(...bestRows.map((r) => r.length)) : 0,
743
+ layoutMode: "gallery",
744
+ getPosition,
745
+ getItemDimensions,
746
+ isMainItem: () => false,
747
+ pagination,
748
+ isItemVisible: (index) => index >= startIndex && index < endIndex,
749
+ hiddenCount,
750
+ getLastVisibleOthersIndex: () => hiddenCount > 0 ? lastVisibleIndex : -1,
751
+ getItemContentDimensions: createGetItemContentDimensions(
752
+ getItemDimensions,
753
+ itemAspectRatios,
754
+ aspectRatio
755
+ )
756
+ };
757
+ }
758
+ function createMeetGrid(options) {
759
+ const { layoutMode = "gallery", count } = options;
760
+ if (count === 0) {
761
+ return createEmptyMeetGridResult(layoutMode);
762
+ }
763
+ switch (layoutMode) {
764
+ case "spotlight":
765
+ return createSpotlightGrid(options);
766
+ case "gallery":
767
+ default: {
768
+ const { maxItemsPerPage, currentPage, pinnedIndex, maxVisible = 0 } = options;
769
+ if (pinnedIndex !== void 0 && pinnedIndex >= 0 && pinnedIndex < count) {
770
+ return createFlexiblePinGrid(options);
771
+ }
772
+ if (count === 2) {
773
+ const { width: W, height: H } = options.dimensions;
774
+ const mainWidth = W;
775
+ const mainHeight = H;
776
+ let floatW;
777
+ let floatH;
778
+ if (options.floatBreakpoints) {
779
+ const resolved = resolveFloatSize(W, options.floatBreakpoints);
780
+ floatW = options.floatWidth ?? resolved.width;
781
+ floatH = options.floatHeight ?? resolved.height;
782
+ } else {
783
+ const isMobileSize = W < 500;
784
+ floatW = options.floatWidth ?? (isMobileSize ? 130 : 180);
785
+ floatH = options.floatHeight ?? (isMobileSize ? 175 : 240);
786
+ }
787
+ const pagination2 = createDefaultPagination(2);
788
+ const getItemDimensions2 = (index) => index === 0 ? { width: mainWidth, height: mainHeight } : { width: floatW, height: floatH };
789
+ return {
790
+ width: mainWidth,
791
+ height: mainHeight,
792
+ rows: 1,
793
+ cols: 1,
794
+ layoutMode: "gallery",
795
+ getPosition: (index) => index === 0 ? { top: 0, left: 0 } : { top: -9999, left: -9999 },
796
+ getItemDimensions: getItemDimensions2,
797
+ isMainItem: (index) => index === 0,
798
+ pagination: pagination2,
799
+ isItemVisible: () => true,
800
+ hiddenCount: 0,
801
+ getLastVisibleOthersIndex: () => -1,
802
+ getItemContentDimensions: createGetItemContentDimensions(
803
+ getItemDimensions2,
804
+ options.itemAspectRatios,
805
+ options.aspectRatio
806
+ ),
807
+ floatIndex: 1,
808
+ floatDimensions: { width: floatW, height: floatH }
809
+ };
810
+ }
811
+ const isMobile = options.dimensions.width < 500;
812
+ if (isMobile && count > 1) {
813
+ const { width: cw, height: ch } = options.dimensions;
814
+ options = { ...options, aspectRatio: `${Math.round(cw)}:${Math.round(ch)}` };
815
+ }
816
+ if (options.itemAspectRatios && options.itemAspectRatios.length > 0) {
817
+ const ratios = /* @__PURE__ */ new Set();
818
+ for (const r of options.itemAspectRatios) {
819
+ ratios.add(r ?? options.aspectRatio);
820
+ }
821
+ if (ratios.size === 1) {
822
+ const sharedRatio = [...ratios][0];
823
+ if (sharedRatio !== options.aspectRatio) {
824
+ options = { ...options, aspectRatio: sharedRatio };
825
+ }
826
+ } else {
827
+ if (!isMobile) {
828
+ return createFlexibleGalleryGrid(options);
829
+ }
830
+ }
831
+ }
832
+ let visibleCount = count;
833
+ let hiddenCount = 0;
834
+ let startIndex = 0;
835
+ let endIndex = count;
836
+ if (maxItemsPerPage && maxItemsPerPage > 0) {
837
+ const pagination2 = createPaginationInfo(count, maxItemsPerPage, currentPage);
838
+ visibleCount = pagination2.itemsOnPage;
839
+ startIndex = pagination2.startIndex;
840
+ endIndex = pagination2.endIndex;
841
+ } else if (maxVisible > 0 && count > maxVisible) {
842
+ visibleCount = maxVisible;
843
+ hiddenCount = count - maxVisible + 1;
844
+ startIndex = 0;
845
+ endIndex = maxVisible;
846
+ }
847
+ const pagination = maxItemsPerPage && maxItemsPerPage > 0 ? createPaginationInfo(count, maxItemsPerPage, currentPage) : {
848
+ enabled: false,
849
+ currentPage: 0,
850
+ totalPages: 1,
851
+ itemsOnPage: visibleCount,
852
+ startIndex,
853
+ endIndex
854
+ };
855
+ const effectiveCount = visibleCount;
856
+ const grid = createGrid({ ...options, count: effectiveCount });
857
+ const getPosition = (index) => {
858
+ const relativeIndex = index - startIndex;
859
+ if (relativeIndex < 0 || relativeIndex >= effectiveCount) {
860
+ return { top: -9999, left: -9999 };
861
+ }
862
+ return grid.getPosition(relativeIndex);
863
+ };
864
+ const getItemDimensions = () => ({ width: grid.width, height: grid.height });
865
+ const lastVisibleIndex = endIndex - 1;
866
+ return {
867
+ ...grid,
868
+ layoutMode: "gallery",
869
+ getPosition,
870
+ getItemDimensions,
871
+ isMainItem: () => false,
872
+ pagination,
873
+ isItemVisible: (index) => index >= startIndex && index < endIndex,
874
+ hiddenCount,
875
+ getLastVisibleOthersIndex: () => hiddenCount > 0 ? lastVisibleIndex : -1,
876
+ getItemContentDimensions: createGetItemContentDimensions(
877
+ getItemDimensions,
878
+ options.itemAspectRatios,
879
+ options.aspectRatio
880
+ )
881
+ };
882
+ }
883
+ }
884
+ }
885
+ const springPresets = {
886
+ /** Snappy animations for UI interactions */
887
+ snappy: { stiffness: 400, damping: 30 },
888
+ /** Smooth animations for layout changes */
889
+ smooth: { stiffness: 300, damping: 30 },
890
+ /** Gentle animations for subtle effects */
891
+ gentle: { stiffness: 200, damping: 25 },
892
+ /** Bouncy animations for playful effects */
893
+ bouncy: { stiffness: 400, damping: 15 }
894
+ };
895
+ function getSpringConfig(preset = "smooth") {
896
+ return {
897
+ type: "spring",
898
+ ...springPresets[preset]
899
+ };
900
+ }
901
+
902
+ export { DEFAULT_FLOAT_BREAKPOINTS, calculateContentDimensions, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, parseAspectRatio, resolveFloatSize, springPresets };