@thangdevalone/meeting-grid-layout-react 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.cjs ADDED
@@ -0,0 +1,531 @@
1
+ 'use strict';
2
+
3
+ const React = require('react');
4
+ const meetingGridLayoutCore = require('@thangdevalone/meeting-grid-layout-core');
5
+ const react = require('motion/react');
6
+
7
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
8
+
9
+ const React__default = /*#__PURE__*/_interopDefaultCompat(React);
10
+
11
+ const GridContext = React.createContext(null);
12
+ function useGridContext() {
13
+ const context = React.useContext(GridContext);
14
+ if (!context) {
15
+ throw new Error("useGridContext must be used within a GridContainer");
16
+ }
17
+ return context;
18
+ }
19
+ function useGridDimensions(ref) {
20
+ const [dimensions, setDimensions] = React.useState({ width: 0, height: 0 });
21
+ React.useEffect(() => {
22
+ const element = ref.current;
23
+ if (!element) {
24
+ return;
25
+ }
26
+ const observer = new ResizeObserver((entries) => {
27
+ for (const entry of entries) {
28
+ const { clientWidth: width, clientHeight: height } = entry.target;
29
+ setDimensions((prev) => {
30
+ if (prev.width === width && prev.height === height) {
31
+ return prev;
32
+ }
33
+ return { width, height };
34
+ });
35
+ }
36
+ });
37
+ observer.observe(element);
38
+ setDimensions({
39
+ width: element.clientWidth,
40
+ height: element.clientHeight
41
+ });
42
+ return () => {
43
+ observer.disconnect();
44
+ };
45
+ }, [ref]);
46
+ return dimensions;
47
+ }
48
+ function useMeetGrid(options) {
49
+ const itemAspectRatiosKey = options.itemAspectRatios?.join(",") ?? "";
50
+ return React.useMemo(() => {
51
+ return meetingGridLayoutCore.createMeetGrid(options);
52
+ }, [
53
+ options.dimensions.width,
54
+ options.dimensions.height,
55
+ options.count,
56
+ options.aspectRatio,
57
+ options.gap,
58
+ options.layoutMode,
59
+ options.pinnedIndex,
60
+ options.othersPosition,
61
+ options.maxItemsPerPage,
62
+ options.currentPage,
63
+ options.maxVisible,
64
+ options.currentVisiblePage,
65
+ itemAspectRatiosKey
66
+ ]);
67
+ }
68
+ function useGridAnimation(preset = "smooth") {
69
+ return React.useMemo(() => meetingGridLayoutCore.getSpringConfig(preset), [preset]);
70
+ }
71
+
72
+ const GridContainer = React.forwardRef(function GridContainer2({
73
+ children,
74
+ aspectRatio = "16:9",
75
+ gap = 8,
76
+ count,
77
+ layoutMode = "gallery",
78
+ pinnedIndex,
79
+ othersPosition,
80
+ springPreset = "smooth",
81
+ style,
82
+ className,
83
+ maxItemsPerPage,
84
+ currentPage,
85
+ maxVisible,
86
+ currentVisiblePage,
87
+ itemAspectRatios,
88
+ floatWidth,
89
+ floatHeight,
90
+ floatBreakpoints,
91
+ ...props
92
+ }, forwardedRef) {
93
+ const internalRef = React.useRef(null);
94
+ const ref = forwardedRef || internalRef;
95
+ const dimensions = useGridDimensions(ref);
96
+ const childCount = count ?? React__default.Children.count(children);
97
+ const gridOptions = {
98
+ dimensions,
99
+ count: childCount,
100
+ aspectRatio,
101
+ gap,
102
+ layoutMode,
103
+ pinnedIndex,
104
+ othersPosition,
105
+ maxItemsPerPage,
106
+ currentPage,
107
+ maxVisible,
108
+ currentVisiblePage,
109
+ itemAspectRatios,
110
+ floatWidth,
111
+ floatHeight,
112
+ floatBreakpoints
113
+ };
114
+ const grid = useMeetGrid(gridOptions);
115
+ const containerStyle = {
116
+ position: "relative",
117
+ width: "100%",
118
+ height: "100%",
119
+ overflow: "hidden",
120
+ ...style
121
+ };
122
+ return /* @__PURE__ */ React__default.createElement(GridContext.Provider, { value: { dimensions, grid, springPreset } }, /* @__PURE__ */ React__default.createElement("div", { ref, style: containerStyle, className, ...props }, children));
123
+ });
124
+ const GridItem = React.forwardRef(function GridItem2({
125
+ index,
126
+ children,
127
+ itemAspectRatio,
128
+ transition: customTransition,
129
+ disableAnimation = false,
130
+ className,
131
+ style,
132
+ ...props
133
+ }, ref) {
134
+ const { grid, springPreset, dimensions: containerDimensions } = useGridContext();
135
+ const isFloat = grid ? grid.floatIndex === index : false;
136
+ const isVisible = grid ? grid.isItemVisible(index) : false;
137
+ const isMain = grid ? grid.isMainItem(index) : false;
138
+ const isHidden = !grid || !isVisible || grid.layoutMode === "spotlight" && !isMain;
139
+ const position = grid && !isHidden ? grid.getPosition(index) : { top: 0, left: 0 };
140
+ const itemDims = grid && !isHidden ? grid.getItemDimensions(index) : { width: 0, height: 0 };
141
+ const floatDims = grid?.floatDimensions ?? { width: 120, height: 160 };
142
+ const [floatAnchor, setFloatAnchor] = React__default.useState("bottom-right");
143
+ const floatX = react.useMotionValue(0);
144
+ const floatY = react.useMotionValue(0);
145
+ const [floatInitialized, setFloatInitialized] = React__default.useState(false);
146
+ const getFloatCornerPos = React__default.useCallback(
147
+ (corner) => {
148
+ const padding = 12;
149
+ const fw = floatDims.width;
150
+ const fh = floatDims.height;
151
+ switch (corner) {
152
+ case "top-left":
153
+ return { x: padding, y: padding };
154
+ case "top-right":
155
+ return { x: containerDimensions.width - fw - padding, y: padding };
156
+ case "bottom-left":
157
+ return { x: padding, y: containerDimensions.height - fh - padding };
158
+ case "bottom-right":
159
+ default:
160
+ return {
161
+ x: containerDimensions.width - fw - padding,
162
+ y: containerDimensions.height - fh - padding
163
+ };
164
+ }
165
+ },
166
+ [containerDimensions.width, containerDimensions.height, floatDims.width, floatDims.height]
167
+ );
168
+ React__default.useEffect(() => {
169
+ if (!isFloat) {
170
+ setFloatInitialized(false);
171
+ }
172
+ }, [isFloat]);
173
+ React__default.useEffect(() => {
174
+ if (isFloat && containerDimensions.width > 0 && containerDimensions.height > 0 && !floatInitialized) {
175
+ const pos = getFloatCornerPos(floatAnchor);
176
+ floatX.set(pos.x);
177
+ floatY.set(pos.y);
178
+ setFloatInitialized(true);
179
+ }
180
+ }, [
181
+ isFloat,
182
+ containerDimensions.width,
183
+ containerDimensions.height,
184
+ floatAnchor,
185
+ getFloatCornerPos,
186
+ floatInitialized,
187
+ floatX,
188
+ floatY
189
+ ]);
190
+ React__default.useEffect(() => {
191
+ if (isFloat && floatInitialized && containerDimensions.width > 0 && containerDimensions.height > 0) {
192
+ const pos = getFloatCornerPos(floatAnchor);
193
+ const cfg = { type: "spring", stiffness: 400, damping: 30 };
194
+ react.animate(floatX, pos.x, cfg);
195
+ react.animate(floatY, pos.y, cfg);
196
+ }
197
+ }, [
198
+ isFloat,
199
+ floatAnchor,
200
+ containerDimensions.width,
201
+ containerDimensions.height,
202
+ getFloatCornerPos,
203
+ floatInitialized,
204
+ floatX,
205
+ floatY
206
+ ]);
207
+ const gridX = react.useMotionValue(0);
208
+ const gridY = react.useMotionValue(0);
209
+ const gridAnimReady = React.useRef(false);
210
+ const springConfig = meetingGridLayoutCore.getSpringConfig(springPreset);
211
+ React__default.useEffect(() => {
212
+ if (isFloat || isHidden) {
213
+ gridAnimReady.current = false;
214
+ return;
215
+ }
216
+ if (!gridAnimReady.current) {
217
+ gridX.set(position.left);
218
+ gridY.set(position.top);
219
+ gridAnimReady.current = true;
220
+ } else {
221
+ const cfg = {
222
+ type: "spring",
223
+ stiffness: springConfig.stiffness,
224
+ damping: springConfig.damping
225
+ };
226
+ react.animate(gridX, position.left, cfg);
227
+ react.animate(gridY, position.top, cfg);
228
+ }
229
+ }, [
230
+ position.top,
231
+ position.left,
232
+ isFloat,
233
+ isHidden,
234
+ gridX,
235
+ gridY,
236
+ springConfig.stiffness,
237
+ springConfig.damping
238
+ ]);
239
+ if (isHidden) {
240
+ return null;
241
+ }
242
+ const contentDimensions = grid.getItemContentDimensions(index, itemAspectRatio);
243
+ const transition = customTransition ?? {
244
+ type: springConfig.type,
245
+ stiffness: springConfig.stiffness,
246
+ damping: springConfig.damping
247
+ };
248
+ const lastVisibleOthersIndex = grid.getLastVisibleOthersIndex();
249
+ const isLastVisibleOther = index === lastVisibleOthersIndex;
250
+ const hiddenCount = grid.hiddenCount;
251
+ const renderChildren = () => {
252
+ if (typeof children === "function") {
253
+ return children({ contentDimensions, isLastVisibleOther, hiddenCount, isFloat });
254
+ }
255
+ return children;
256
+ };
257
+ if (isFloat) {
258
+ if (containerDimensions.width === 0 || containerDimensions.height === 0)
259
+ return null;
260
+ const findNearestCorner = (posX, posY) => {
261
+ const centerX = posX + floatDims.width / 2;
262
+ const centerY = posY + floatDims.height / 2;
263
+ const isLeft = centerX < containerDimensions.width / 2;
264
+ const isTop = centerY < containerDimensions.height / 2;
265
+ if (isTop && isLeft)
266
+ return "top-left";
267
+ if (isTop && !isLeft)
268
+ return "top-right";
269
+ if (!isTop && isLeft)
270
+ return "bottom-left";
271
+ return "bottom-right";
272
+ };
273
+ const dragConstraints = {
274
+ left: 12,
275
+ right: containerDimensions.width - floatDims.width - 12,
276
+ top: 12,
277
+ bottom: containerDimensions.height - floatDims.height - 12
278
+ };
279
+ const handleDragEnd = () => {
280
+ const currentX = floatX.get();
281
+ const currentY = floatY.get();
282
+ const nearestCorner = findNearestCorner(currentX, currentY);
283
+ setFloatAnchor(nearestCorner);
284
+ const snapPos = getFloatCornerPos(nearestCorner);
285
+ const springCfg = { type: "spring", stiffness: 400, damping: 30 };
286
+ react.animate(floatX, snapPos.x, springCfg);
287
+ react.animate(floatY, snapPos.y, springCfg);
288
+ };
289
+ const floatingStyle = {
290
+ position: "absolute",
291
+ width: floatDims.width,
292
+ height: floatDims.height,
293
+ borderRadius: 12,
294
+ boxShadow: "0 4px 20px rgba(0,0,0,0.3)",
295
+ overflow: "hidden",
296
+ cursor: "grab",
297
+ zIndex: 100,
298
+ touchAction: "none",
299
+ left: 0,
300
+ top: 0,
301
+ ...style
302
+ };
303
+ return /* @__PURE__ */ React__default.createElement(
304
+ react.motion.div,
305
+ {
306
+ ref,
307
+ drag: true,
308
+ dragMomentum: false,
309
+ dragElastic: 0.1,
310
+ dragConstraints,
311
+ style: { ...floatingStyle, x: floatX, y: floatY },
312
+ className,
313
+ onDragEnd: handleDragEnd,
314
+ whileDrag: { cursor: "grabbing", scale: 1.05, boxShadow: "0 8px 32px rgba(0,0,0,0.4)" },
315
+ transition: { type: "spring", stiffness: 400, damping: 30 },
316
+ "data-grid-index": index,
317
+ "data-grid-float": true,
318
+ ...props
319
+ },
320
+ renderChildren()
321
+ );
322
+ }
323
+ if (disableAnimation) {
324
+ return /* @__PURE__ */ React__default.createElement(
325
+ "div",
326
+ {
327
+ ref,
328
+ style: {
329
+ position: "absolute",
330
+ width: itemDims.width,
331
+ height: itemDims.height,
332
+ top: position.top,
333
+ left: position.left,
334
+ ...style
335
+ },
336
+ className,
337
+ "data-grid-index": index,
338
+ "data-grid-main": isMain,
339
+ ...props
340
+ },
341
+ renderChildren()
342
+ );
343
+ }
344
+ return /* @__PURE__ */ React__default.createElement(
345
+ react.motion.div,
346
+ {
347
+ ref,
348
+ initial: { width: itemDims.width, height: itemDims.height },
349
+ animate: { width: itemDims.width, height: itemDims.height },
350
+ transition,
351
+ style: { position: "absolute", top: 0, left: 0, x: gridX, y: gridY, ...style },
352
+ className,
353
+ "data-grid-index": index,
354
+ "data-grid-main": isMain,
355
+ ...props
356
+ },
357
+ renderChildren()
358
+ );
359
+ });
360
+ const FloatingGridItem = React.forwardRef(
361
+ function FloatingGridItem2({
362
+ children,
363
+ width = 120,
364
+ height = 160,
365
+ breakpoints,
366
+ initialPosition = { x: 16, y: 16 },
367
+ anchor: initialAnchor = "bottom-right",
368
+ visible = true,
369
+ edgePadding = 12,
370
+ onAnchorChange,
371
+ transition,
372
+ borderRadius = 12,
373
+ boxShadow = "0 4px 20px rgba(0,0,0,0.3)",
374
+ className,
375
+ style,
376
+ ...props
377
+ }, ref) {
378
+ const { dimensions } = useGridContext();
379
+ const [currentAnchor, setCurrentAnchor] = React__default.useState(initialAnchor);
380
+ const resolvedSize = React__default.useMemo(() => {
381
+ if (breakpoints && breakpoints.length > 0 && dimensions.width > 0) {
382
+ return resolveFloatSize(dimensions.width, breakpoints);
383
+ }
384
+ return null;
385
+ }, [breakpoints, dimensions.width]);
386
+ const effectiveWidth = resolvedSize?.width ?? width;
387
+ const effectiveHeight = resolvedSize?.height ?? height;
388
+ const x = react.useMotionValue(0);
389
+ const y = react.useMotionValue(0);
390
+ const [isInitialized, setIsInitialized] = React__default.useState(false);
391
+ const getCornerPosition = React__default.useCallback(
392
+ (corner) => {
393
+ const padding = edgePadding + initialPosition.x;
394
+ switch (corner) {
395
+ case "top-left":
396
+ return { x: padding, y: padding };
397
+ case "top-right":
398
+ return { x: dimensions.width - effectiveWidth - padding, y: padding };
399
+ case "bottom-left":
400
+ return { x: padding, y: dimensions.height - effectiveHeight - padding };
401
+ case "bottom-right":
402
+ default:
403
+ return {
404
+ x: dimensions.width - effectiveWidth - padding,
405
+ y: dimensions.height - effectiveHeight - padding
406
+ };
407
+ }
408
+ },
409
+ [dimensions.width, dimensions.height, effectiveWidth, effectiveHeight, edgePadding, initialPosition.x]
410
+ );
411
+ React__default.useEffect(() => {
412
+ if (dimensions.width > 0 && dimensions.height > 0 && !isInitialized) {
413
+ const pos = getCornerPosition(currentAnchor);
414
+ x.set(pos.x);
415
+ y.set(pos.y);
416
+ setIsInitialized(true);
417
+ }
418
+ }, [dimensions.width, dimensions.height, currentAnchor, getCornerPosition, isInitialized, x, y]);
419
+ React__default.useEffect(() => {
420
+ if (isInitialized && dimensions.width > 0 && dimensions.height > 0) {
421
+ const pos = getCornerPosition(currentAnchor);
422
+ const springConfig = { type: "spring", stiffness: 400, damping: 30 };
423
+ react.animate(x, pos.x, springConfig);
424
+ react.animate(y, pos.y, springConfig);
425
+ }
426
+ }, [currentAnchor, dimensions.width, dimensions.height, getCornerPosition, isInitialized, x, y]);
427
+ if (!visible || dimensions.width === 0 || dimensions.height === 0)
428
+ return null;
429
+ const findNearestCorner = (posX, posY) => {
430
+ const centerX = posX + effectiveWidth / 2;
431
+ const centerY = posY + effectiveHeight / 2;
432
+ const containerCenterX = dimensions.width / 2;
433
+ const containerCenterY = dimensions.height / 2;
434
+ const isLeft = centerX < containerCenterX;
435
+ const isTop = centerY < containerCenterY;
436
+ if (isTop && isLeft)
437
+ return "top-left";
438
+ if (isTop && !isLeft)
439
+ return "top-right";
440
+ if (!isTop && isLeft)
441
+ return "bottom-left";
442
+ return "bottom-right";
443
+ };
444
+ const dragConstraints = {
445
+ left: edgePadding,
446
+ right: dimensions.width - effectiveWidth - edgePadding,
447
+ top: edgePadding,
448
+ bottom: dimensions.height - effectiveHeight - edgePadding
449
+ };
450
+ const floatingStyle = {
451
+ position: "absolute",
452
+ width: effectiveWidth,
453
+ height: effectiveHeight,
454
+ borderRadius,
455
+ boxShadow,
456
+ overflow: "hidden",
457
+ cursor: "grab",
458
+ zIndex: 100,
459
+ touchAction: "none",
460
+ left: 0,
461
+ top: 0,
462
+ ...style
463
+ };
464
+ const handleDragEnd = () => {
465
+ const currentX = x.get();
466
+ const currentY = y.get();
467
+ const nearestCorner = findNearestCorner(currentX, currentY);
468
+ setCurrentAnchor(nearestCorner);
469
+ onAnchorChange?.(nearestCorner);
470
+ const snapPos = getCornerPosition(nearestCorner);
471
+ const springConfig = { type: "spring", stiffness: 400, damping: 30 };
472
+ react.animate(x, snapPos.x, springConfig);
473
+ react.animate(y, snapPos.y, springConfig);
474
+ };
475
+ return /* @__PURE__ */ React__default.createElement(
476
+ react.motion.div,
477
+ {
478
+ ref,
479
+ drag: true,
480
+ dragMomentum: false,
481
+ dragElastic: 0.1,
482
+ dragConstraints,
483
+ style: { ...floatingStyle, x, y },
484
+ className,
485
+ onDragEnd: handleDragEnd,
486
+ whileDrag: { cursor: "grabbing", scale: 1.05, boxShadow: "0 8px 32px rgba(0,0,0,0.4)" },
487
+ transition: transition ?? { type: "spring", stiffness: 400, damping: 30 },
488
+ ...props
489
+ },
490
+ children
491
+ );
492
+ }
493
+ );
494
+ const GridOverlay = React.forwardRef(function GridOverlay2({ visible = true, backgroundColor = "rgba(0,0,0,0.5)", children, style, ...props }, ref) {
495
+ if (!visible)
496
+ return null;
497
+ return /* @__PURE__ */ React__default.createElement(
498
+ "div",
499
+ {
500
+ ref,
501
+ style: {
502
+ position: "absolute",
503
+ inset: 0,
504
+ display: "flex",
505
+ alignItems: "center",
506
+ justifyContent: "center",
507
+ backgroundColor,
508
+ ...style
509
+ },
510
+ ...props
511
+ },
512
+ children
513
+ );
514
+ });
515
+
516
+ exports.createGrid = meetingGridLayoutCore.createGrid;
517
+ exports.createGridItemPositioner = meetingGridLayoutCore.createGridItemPositioner;
518
+ exports.createMeetGrid = meetingGridLayoutCore.createMeetGrid;
519
+ exports.getAspectRatio = meetingGridLayoutCore.getAspectRatio;
520
+ exports.getGridItemDimensions = meetingGridLayoutCore.getGridItemDimensions;
521
+ exports.getSpringConfig = meetingGridLayoutCore.getSpringConfig;
522
+ exports.springPresets = meetingGridLayoutCore.springPresets;
523
+ exports.FloatingGridItem = FloatingGridItem;
524
+ exports.GridContainer = GridContainer;
525
+ exports.GridContext = GridContext;
526
+ exports.GridItem = GridItem;
527
+ exports.GridOverlay = GridOverlay;
528
+ exports.useGridAnimation = useGridAnimation;
529
+ exports.useGridContext = useGridContext;
530
+ exports.useGridDimensions = useGridDimensions;
531
+ exports.useMeetGrid = useMeetGrid;