@thangdevalone/meet-layout-grid-react 1.0.9 → 1.1.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/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @thangdevalone/meet-layout-grid-react
2
2
 
3
- React bindings for meet-layout-grid with Motion animations.
3
+ React 18+ bindings for meet-layout-grid with Motion animations.
4
+
5
+ > For full documentation, layout modes, and API reference, see the [main README](https://github.com/thangdevalone/meet-layout-grid#readme).
4
6
 
5
7
  ## Installation
6
8
 
@@ -8,19 +10,14 @@ React bindings for meet-layout-grid with Motion animations.
8
10
  npm install @thangdevalone/meet-layout-grid-react
9
11
  ```
10
12
 
11
- ## Quick start
13
+ ## Quick Start
12
14
 
13
15
  ```tsx
14
16
  import { GridContainer, GridItem } from '@thangdevalone/meet-layout-grid-react'
15
17
 
16
18
  function MeetingGrid({ participants }) {
17
19
  return (
18
- <GridContainer
19
- aspectRatio="16:9"
20
- gap={8}
21
- layoutMode="gallery"
22
- count={participants.length}
23
- >
20
+ <GridContainer aspectRatio="16:9" gap={8} layoutMode="gallery" count={participants.length}>
24
21
  {participants.map((p, index) => (
25
22
  <GridItem key={p.id} index={index}>
26
23
  <VideoTile participant={p} />
@@ -35,76 +32,64 @@ function MeetingGrid({ participants }) {
35
32
 
36
33
  ### `<GridContainer>`
37
34
 
38
- Wraps the grid, computes layout, provides context.
39
-
40
- ```tsx
41
- <GridContainer
42
- aspectRatio="16:9"
43
- gap={8}
44
- count={6}
45
- layoutMode="gallery"
46
- speakerIndex={0}
47
- pinnedIndex={0}
48
- sidebarPosition="right"
49
- sidebarRatio={0.25}
50
- springPreset="smooth"
51
- >
52
- {children}
53
- </GridContainer>
54
- ```
35
+ Wraps the grid, computes layout, and provides context to children.
36
+
37
+ | Prop | Type | Default | Description |
38
+ | ------------------ | -------------- | ----------- | ------------------------------ |
39
+ | `aspectRatio` | `string` | `'16:9'` | Default tile aspect ratio |
40
+ | `gap` | `number` | `8` | Gap between tiles (px) |
41
+ | `count` | `number` | required | Number of items |
42
+ | `layoutMode` | `LayoutMode` | `'gallery'` | `gallery` \| `spotlight` |
43
+ | `pinnedIndex` | `number` | - | Pinned participant index |
44
+ | `othersPosition` | `string` | `'right'` | Thumbnail position in pin mode |
45
+ | `springPreset` | `SpringPreset` | `'smooth'` | Animation preset |
46
+ | `maxItemsPerPage` | `number` | - | Max items per page |
47
+ | `currentPage` | `number` | `0` | Current page |
48
+ | `maxVisible` | `number` | - | Max visible in others area |
49
+ | `itemAspectRatios` | `array` | - | Per-item aspect ratios |
55
50
 
56
51
  ### `<GridItem>`
57
52
 
58
- One grid cell; uses Motion for layout animation.
59
-
60
- ```tsx
61
- <GridItem index={0} disableAnimation={false}>
62
- {content}
63
- </GridItem>
64
- ```
65
-
66
- ## Hooks
53
+ Single grid cell with Motion layout animation.
67
54
 
68
- ### `useGridDimensions(ref)`
55
+ | Prop | Type | Default | Description |
56
+ | ------------------ | --------- | -------- | ------------------------ |
57
+ | `index` | `number` | required | Item index |
58
+ | `disableAnimation` | `boolean` | `false` | Disable layout animation |
69
59
 
70
- Uses ResizeObserver to track element size.
60
+ Render prop: `{({ contentDimensions, isLastVisibleOther, hiddenCount }) => ...}`
71
61
 
72
- ```tsx
73
- const ref = useRef<HTMLDivElement>(null)
74
- const dimensions = useGridDimensions(ref)
75
- // { width: number, height: number }
76
- ```
62
+ ### `<FloatingGridItem>`
77
63
 
78
- ### `useMeetGrid(options)`
64
+ Draggable Picture-in-Picture overlay with corner snapping.
79
65
 
80
- Compute grid layout yourself.
66
+ | Prop | Type | Default | Description |
67
+ | ---------------- | ---------- | ---------------- | ---------------------------- |
68
+ | `width` | `number` | - | Floating item width |
69
+ | `height` | `number` | - | Floating item height |
70
+ | `anchor` | `string` | `'bottom-right'` | Corner anchor position |
71
+ | `visible` | `boolean` | `true` | Show/hide the floating item |
72
+ | `edgePadding` | `number` | - | Padding from container edges |
73
+ | `borderRadius` | `number` | - | Border radius |
74
+ | `onAnchorChange` | `function` | - | Callback when anchor changes |
81
75
 
82
- ```tsx
83
- const grid = useMeetGrid({
84
- dimensions: { width: 800, height: 600 },
85
- count: 6,
86
- aspectRatio: '16:9',
87
- gap: 8,
88
- layoutMode: 'speaker',
89
- })
90
-
91
- const { top, left } = grid.getPosition(index)
92
- const { width, height } = grid.getItemDimensions(index)
93
- ```
76
+ ### `<GridOverlay>`
94
77
 
95
- ## Layout modes
78
+ Full-grid overlay for screen sharing, whiteboard, etc.
96
79
 
97
- - **gallery** Same-size tiles
98
- - **speaker** One large tile
99
- - **spotlight** Single participant
100
- - **sidebar** Main + thumbnails
80
+ | Prop | Type | Default | Description |
81
+ | ----------------- | --------- | ------- | ------------------------ |
82
+ | `visible` | `boolean` | `false` | Show/hide the overlay |
83
+ | `backgroundColor` | `string` | - | Overlay background color |
101
84
 
102
- ## Animation presets
85
+ ## Hooks
103
86
 
104
- - `snappy` — Fast feedback
105
- - `smooth` Layout changes (default)
106
- - `gentle` Subtle
107
- - `bouncy` Slight overshoot
87
+ | Hook | Description |
88
+ | ------------------------ | ----------------------------------------- |
89
+ | `useGridDimensions(ref)` | Track element size via ResizeObserver |
90
+ | `useMeetGrid(options)` | Compute grid layout programmatically |
91
+ | `useGridContext()` | Access grid context from child components |
92
+ | `useGridAnimation()` | Access animation config from context |
108
93
 
109
94
  ## License
110
95
 
package/dist/index.cjs CHANGED
@@ -46,6 +46,7 @@ function useGridDimensions(ref) {
46
46
  return dimensions;
47
47
  }
48
48
  function useMeetGrid(options) {
49
+ const itemAspectRatiosKey = options.itemAspectRatios?.join(",") ?? "";
49
50
  return React.useMemo(() => {
50
51
  return meetLayoutGridCore.createMeetGrid(options);
51
52
  }, [
@@ -56,13 +57,14 @@ function useMeetGrid(options) {
56
57
  options.gap,
57
58
  options.layoutMode,
58
59
  options.pinnedIndex,
59
- options.speakerIndex,
60
60
  options.sidebarPosition,
61
61
  options.sidebarRatio,
62
62
  options.maxItemsPerPage,
63
63
  options.currentPage,
64
- options.maxVisibleOthers,
65
- options.currentOthersPage
64
+ options.maxVisible,
65
+ options.currentVisiblePage,
66
+ options.flexLayout,
67
+ itemAspectRatiosKey
66
68
  ]);
67
69
  }
68
70
  function useGridAnimation(preset = "smooth") {
@@ -77,7 +79,6 @@ const GridContainer = React.forwardRef(
77
79
  count,
78
80
  layoutMode = "gallery",
79
81
  pinnedIndex,
80
- speakerIndex,
81
82
  sidebarPosition,
82
83
  sidebarRatio,
83
84
  springPreset = "smooth",
@@ -85,8 +86,10 @@ const GridContainer = React.forwardRef(
85
86
  className,
86
87
  maxItemsPerPage,
87
88
  currentPage,
88
- maxVisibleOthers,
89
- currentOthersPage,
89
+ maxVisible,
90
+ currentVisiblePage,
91
+ itemAspectRatios,
92
+ flexLayout,
90
93
  ...props
91
94
  }, forwardedRef) {
92
95
  const internalRef = React.useRef(null);
@@ -100,13 +103,14 @@ const GridContainer = React.forwardRef(
100
103
  gap,
101
104
  layoutMode,
102
105
  pinnedIndex,
103
- speakerIndex,
104
106
  sidebarPosition,
105
107
  sidebarRatio,
106
108
  maxItemsPerPage,
107
109
  currentPage,
108
- maxVisibleOthers,
109
- currentOthersPage
110
+ maxVisible,
111
+ currentVisiblePage,
112
+ itemAspectRatios,
113
+ flexLayout
110
114
  };
111
115
  const grid = useMeetGrid(gridOptions);
112
116
  const containerStyle = {
@@ -123,6 +127,7 @@ const GridItem = React.forwardRef(
123
127
  function GridItem2({
124
128
  index,
125
129
  children,
130
+ itemAspectRatio,
126
131
  transition: customTransition,
127
132
  disableAnimation = false,
128
133
  className,
@@ -138,6 +143,7 @@ const GridItem = React.forwardRef(
138
143
  }
139
144
  const { top, left } = grid.getPosition(index);
140
145
  const { width, height } = grid.getItemDimensions(index);
146
+ const contentDimensions = grid.getItemContentDimensions(index, itemAspectRatio);
141
147
  const isMain = grid.isMainItem(index);
142
148
  if (grid.layoutMode === "spotlight" && !isMain) {
143
149
  return null;
@@ -155,6 +161,15 @@ const GridItem = React.forwardRef(
155
161
  top,
156
162
  left
157
163
  };
164
+ const lastVisibleOthersIndex = grid.getLastVisibleOthersIndex();
165
+ const isLastVisibleOther = index === lastVisibleOthersIndex;
166
+ const hiddenCount = grid.hiddenCount;
167
+ const renderChildren = () => {
168
+ if (typeof children === "function") {
169
+ return children({ contentDimensions, isLastVisibleOther, hiddenCount });
170
+ }
171
+ return children;
172
+ };
158
173
  if (disableAnimation) {
159
174
  return /* @__PURE__ */ React__default.createElement(
160
175
  "div",
@@ -166,7 +181,7 @@ const GridItem = React.forwardRef(
166
181
  "data-grid-main": isMain,
167
182
  ...props
168
183
  },
169
- children
184
+ renderChildren()
170
185
  );
171
186
  }
172
187
  return /* @__PURE__ */ React__default.createElement(
@@ -183,6 +198,125 @@ const GridItem = React.forwardRef(
183
198
  "data-grid-main": isMain,
184
199
  ...props
185
200
  },
201
+ renderChildren()
202
+ );
203
+ }
204
+ );
205
+ const FloatingGridItem = React.forwardRef(
206
+ function FloatingGridItem2({
207
+ children,
208
+ width = 120,
209
+ height = 160,
210
+ initialPosition = { x: 16, y: 16 },
211
+ anchor: initialAnchor = "bottom-right",
212
+ visible = true,
213
+ edgePadding = 12,
214
+ onAnchorChange,
215
+ transition,
216
+ borderRadius = 12,
217
+ boxShadow = "0 4px 20px rgba(0,0,0,0.3)",
218
+ className,
219
+ style,
220
+ ...props
221
+ }, ref) {
222
+ const { dimensions } = useGridContext();
223
+ const [currentAnchor, setCurrentAnchor] = React__default.useState(initialAnchor);
224
+ const x = react.useMotionValue(0);
225
+ const y = react.useMotionValue(0);
226
+ const [isInitialized, setIsInitialized] = React__default.useState(false);
227
+ const getCornerPosition = React__default.useCallback((corner) => {
228
+ const padding = edgePadding + initialPosition.x;
229
+ switch (corner) {
230
+ case "top-left":
231
+ return { x: padding, y: padding };
232
+ case "top-right":
233
+ return { x: dimensions.width - width - padding, y: padding };
234
+ case "bottom-left":
235
+ return { x: padding, y: dimensions.height - height - padding };
236
+ case "bottom-right":
237
+ default:
238
+ return { x: dimensions.width - width - padding, y: dimensions.height - height - padding };
239
+ }
240
+ }, [dimensions.width, dimensions.height, width, height, edgePadding, initialPosition.x]);
241
+ React__default.useEffect(() => {
242
+ if (dimensions.width > 0 && dimensions.height > 0 && !isInitialized) {
243
+ const pos = getCornerPosition(currentAnchor);
244
+ x.set(pos.x);
245
+ y.set(pos.y);
246
+ setIsInitialized(true);
247
+ }
248
+ }, [dimensions.width, dimensions.height, currentAnchor, getCornerPosition, isInitialized, x, y]);
249
+ React__default.useEffect(() => {
250
+ if (isInitialized && dimensions.width > 0 && dimensions.height > 0) {
251
+ const pos = getCornerPosition(currentAnchor);
252
+ const springConfig = { type: "spring", stiffness: 400, damping: 30 };
253
+ react.animate(x, pos.x, springConfig);
254
+ react.animate(y, pos.y, springConfig);
255
+ }
256
+ }, [currentAnchor, dimensions.width, dimensions.height, getCornerPosition, isInitialized, x, y]);
257
+ if (!visible || dimensions.width === 0 || dimensions.height === 0)
258
+ return null;
259
+ const findNearestCorner = (posX, posY) => {
260
+ const centerX = posX + width / 2;
261
+ const centerY = posY + height / 2;
262
+ const containerCenterX = dimensions.width / 2;
263
+ const containerCenterY = dimensions.height / 2;
264
+ const isLeft = centerX < containerCenterX;
265
+ const isTop = centerY < containerCenterY;
266
+ if (isTop && isLeft)
267
+ return "top-left";
268
+ if (isTop && !isLeft)
269
+ return "top-right";
270
+ if (!isTop && isLeft)
271
+ return "bottom-left";
272
+ return "bottom-right";
273
+ };
274
+ const dragConstraints = {
275
+ left: edgePadding,
276
+ right: dimensions.width - width - edgePadding,
277
+ top: edgePadding,
278
+ bottom: dimensions.height - height - edgePadding
279
+ };
280
+ const floatingStyle = {
281
+ position: "absolute",
282
+ width,
283
+ height,
284
+ borderRadius,
285
+ boxShadow,
286
+ overflow: "hidden",
287
+ cursor: "grab",
288
+ zIndex: 100,
289
+ touchAction: "none",
290
+ left: 0,
291
+ top: 0,
292
+ ...style
293
+ };
294
+ const handleDragEnd = () => {
295
+ const currentX = x.get();
296
+ const currentY = y.get();
297
+ const nearestCorner = findNearestCorner(currentX, currentY);
298
+ setCurrentAnchor(nearestCorner);
299
+ onAnchorChange?.(nearestCorner);
300
+ const snapPos = getCornerPosition(nearestCorner);
301
+ const springConfig = { type: "spring", stiffness: 400, damping: 30 };
302
+ react.animate(x, snapPos.x, springConfig);
303
+ react.animate(y, snapPos.y, springConfig);
304
+ };
305
+ return /* @__PURE__ */ React__default.createElement(
306
+ react.motion.div,
307
+ {
308
+ ref,
309
+ drag: true,
310
+ dragMomentum: false,
311
+ dragElastic: 0.1,
312
+ dragConstraints,
313
+ style: { ...floatingStyle, x, y },
314
+ className,
315
+ onDragEnd: handleDragEnd,
316
+ whileDrag: { cursor: "grabbing", scale: 1.05, boxShadow: "0 8px 32px rgba(0,0,0,0.4)" },
317
+ transition: transition ?? { type: "spring", stiffness: 400, damping: 30 },
318
+ ...props
319
+ },
186
320
  children
187
321
  );
188
322
  }
@@ -218,6 +352,7 @@ exports.getAspectRatio = meetLayoutGridCore.getAspectRatio;
218
352
  exports.getGridItemDimensions = meetLayoutGridCore.getGridItemDimensions;
219
353
  exports.getSpringConfig = meetLayoutGridCore.getSpringConfig;
220
354
  exports.springPresets = meetLayoutGridCore.springPresets;
355
+ exports.FloatingGridItem = FloatingGridItem;
221
356
  exports.GridContainer = GridContainer;
222
357
  exports.GridContext = GridContext;
223
358
  exports.GridItem = GridItem;
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { RefObject, HTMLAttributes, ReactNode, CSSProperties } from 'react';
3
- import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode } from '@thangdevalone/meet-layout-grid-core';
4
- export { GridDimensions, GridOptions, LayoutMode, MeetGridOptions, MeetGridResult, PaginationInfo, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
3
+ import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode, ItemAspectRatio, ContentDimensions } from '@thangdevalone/meet-layout-grid-core';
4
+ export { ContentDimensions, GridDimensions, GridOptions, ItemAspectRatio, LayoutMode, MeetGridOptions, MeetGridResult, PaginationInfo, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
5
5
  import { HTMLMotionProps, Transition } from 'motion/react';
6
6
 
7
7
  interface GridContextValue {
@@ -59,10 +59,8 @@ interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
59
59
  count?: number;
60
60
  /** Layout mode */
61
61
  layoutMode?: LayoutMode;
62
- /** Index of pinned item */
62
+ /** Index of pinned/focused item (main participant for spotlight/sidebar modes) */
63
63
  pinnedIndex?: number;
64
- /** Index of active speaker */
65
- speakerIndex?: number;
66
64
  /** Sidebar position */
67
65
  sidebarPosition?: 'left' | 'right' | 'top' | 'bottom';
68
66
  /** Sidebar ratio (0-1) */
@@ -77,21 +75,71 @@ interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
77
75
  maxItemsPerPage?: number;
78
76
  /** Current page index (0-based) for pagination */
79
77
  currentPage?: number;
80
- /** Maximum visible "others" in speaker/sidebar modes (0 = show all) */
81
- maxVisibleOthers?: number;
82
- /** Current page for "others" in speaker/sidebar modes (0-based) */
83
- currentOthersPage?: number;
78
+ /** Maximum visible items (0 = show all). In gallery mode: limits all items. In sidebar: limits "others". */
79
+ maxVisible?: number;
80
+ /** Current page for visible items (0-based), used when maxVisible > 0 */
81
+ currentVisiblePage?: number;
82
+ /**
83
+ * Per-item aspect ratio configurations.
84
+ * Use different ratios for mobile (9:16), desktop (16:9), or whiteboard (fill).
85
+ * @example ['16:9', '9:16', 'fill', undefined]
86
+ */
87
+ itemAspectRatios?: (ItemAspectRatio | undefined)[];
88
+ /**
89
+ * Enable flexible cell sizing based on item aspect ratios.
90
+ * When true, portrait items (9:16) get narrower cells, landscape items (16:9) get wider cells.
91
+ * Items are packed into rows intelligently.
92
+ * @default false
93
+ */
94
+ flexLayout?: boolean;
84
95
  }
85
96
  /**
86
97
  * Container component for the meet grid.
87
98
  * Provides grid context to child GridItem components.
88
99
  */
89
100
  declare const GridContainer: React__default.ForwardRefExoticComponent<GridContainerProps & React__default.RefAttributes<HTMLDivElement>>;
90
- interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition'> {
101
+ interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition' | 'children'> {
91
102
  /** Index of this item in the grid */
92
103
  index: number;
93
- /** Children to render inside the item */
94
- children: ReactNode;
104
+ /**
105
+ * Children to render inside the item.
106
+ * Can be a ReactNode or a render function that receives contentDimensions and visibility info.
107
+ * @example
108
+ * // Simple usage
109
+ * <GridItem index={0}><Video /></GridItem>
110
+ *
111
+ * // With contentDimensions for flexible aspect ratios
112
+ * <GridItem index={0}>
113
+ * {({ contentDimensions }) => (
114
+ * <Video style={{
115
+ * width: contentDimensions.width,
116
+ * height: contentDimensions.height,
117
+ * marginTop: contentDimensions.offsetTop,
118
+ * marginLeft: contentDimensions.offsetLeft
119
+ * }} />
120
+ * )}
121
+ * </GridItem>
122
+ *
123
+ * // With hidden count for '+X more' indicator
124
+ * <GridItem index={index}>
125
+ * {({ isLastVisibleOther, hiddenCount }) => (
126
+ * <div>
127
+ * {isLastVisibleOther && hiddenCount > 0 && (
128
+ * <span className="more-indicator">+{hiddenCount}</span>
129
+ * )}
130
+ * </div>
131
+ * )}
132
+ * </GridItem>
133
+ */
134
+ children: ReactNode | ((props: {
135
+ contentDimensions: ContentDimensions;
136
+ /** True if this is the last visible item in the "others" section */
137
+ isLastVisibleOther: boolean;
138
+ /** Number of hidden items (for '+X more' indicator) */
139
+ hiddenCount: number;
140
+ }) => ReactNode);
141
+ /** Optional item-specific aspect ratio (overrides itemAspectRatios from container) */
142
+ itemAspectRatio?: ItemAspectRatio;
95
143
  /** Custom transition override */
96
144
  transition?: Transition;
97
145
  /** Whether to disable animations */
@@ -106,6 +154,43 @@ interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initia
106
154
  * Automatically positions itself based on index in the grid.
107
155
  */
108
156
  declare const GridItem: React__default.ForwardRefExoticComponent<Omit<GridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
157
+ interface FloatingGridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'children'> {
158
+ /** Children to render inside the floating item */
159
+ children: ReactNode;
160
+ /** Width of the floating item */
161
+ width?: number;
162
+ /** Height of the floating item */
163
+ height?: number;
164
+ /** Initial position (x, y from container edges) */
165
+ initialPosition?: {
166
+ x: number;
167
+ y: number;
168
+ };
169
+ /** Which corner to anchor: 'top-left', 'top-right', 'bottom-left', 'bottom-right' */
170
+ anchor?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
171
+ /** Whether the item is visible */
172
+ visible?: boolean;
173
+ /** Padding from container edges */
174
+ edgePadding?: number;
175
+ /** Callback when anchor changes after snap */
176
+ onAnchorChange?: (anchor: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right') => void;
177
+ /** Custom transition */
178
+ transition?: Transition;
179
+ /** Border radius */
180
+ borderRadius?: number;
181
+ /** Box shadow */
182
+ boxShadow?: string;
183
+ /** Additional class name */
184
+ className?: string;
185
+ /** Custom style */
186
+ style?: CSSProperties;
187
+ }
188
+ /**
189
+ * Floating Grid Item component that can be dragged around the screen.
190
+ * Snaps to the nearest corner when released (like iOS/Android PiP).
191
+ * Perfect for Picture-in-Picture style floating video in zoom mode.
192
+ */
193
+ declare const FloatingGridItem: React__default.ForwardRefExoticComponent<Omit<FloatingGridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
109
194
  interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
110
195
  /** Whether to show the overlay */
111
196
  visible?: boolean;
@@ -117,5 +202,5 @@ interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
117
202
  */
118
203
  declare const GridOverlay: React__default.ForwardRefExoticComponent<GridOverlayProps & React__default.RefAttributes<HTMLDivElement>>;
119
204
 
120
- export { GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
121
- export type { GridContainerProps, GridItemProps, GridOverlayProps };
205
+ export { FloatingGridItem, GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
206
+ export type { FloatingGridItemProps, GridContainerProps, GridItemProps, GridOverlayProps };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { RefObject, HTMLAttributes, ReactNode, CSSProperties } from 'react';
3
- import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode } from '@thangdevalone/meet-layout-grid-core';
4
- export { GridDimensions, GridOptions, LayoutMode, MeetGridOptions, MeetGridResult, PaginationInfo, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
3
+ import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode, ItemAspectRatio, ContentDimensions } from '@thangdevalone/meet-layout-grid-core';
4
+ export { ContentDimensions, GridDimensions, GridOptions, ItemAspectRatio, LayoutMode, MeetGridOptions, MeetGridResult, PaginationInfo, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
5
5
  import { HTMLMotionProps, Transition } from 'motion/react';
6
6
 
7
7
  interface GridContextValue {
@@ -59,10 +59,8 @@ interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
59
59
  count?: number;
60
60
  /** Layout mode */
61
61
  layoutMode?: LayoutMode;
62
- /** Index of pinned item */
62
+ /** Index of pinned/focused item (main participant for spotlight/sidebar modes) */
63
63
  pinnedIndex?: number;
64
- /** Index of active speaker */
65
- speakerIndex?: number;
66
64
  /** Sidebar position */
67
65
  sidebarPosition?: 'left' | 'right' | 'top' | 'bottom';
68
66
  /** Sidebar ratio (0-1) */
@@ -77,21 +75,71 @@ interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
77
75
  maxItemsPerPage?: number;
78
76
  /** Current page index (0-based) for pagination */
79
77
  currentPage?: number;
80
- /** Maximum visible "others" in speaker/sidebar modes (0 = show all) */
81
- maxVisibleOthers?: number;
82
- /** Current page for "others" in speaker/sidebar modes (0-based) */
83
- currentOthersPage?: number;
78
+ /** Maximum visible items (0 = show all). In gallery mode: limits all items. In sidebar: limits "others". */
79
+ maxVisible?: number;
80
+ /** Current page for visible items (0-based), used when maxVisible > 0 */
81
+ currentVisiblePage?: number;
82
+ /**
83
+ * Per-item aspect ratio configurations.
84
+ * Use different ratios for mobile (9:16), desktop (16:9), or whiteboard (fill).
85
+ * @example ['16:9', '9:16', 'fill', undefined]
86
+ */
87
+ itemAspectRatios?: (ItemAspectRatio | undefined)[];
88
+ /**
89
+ * Enable flexible cell sizing based on item aspect ratios.
90
+ * When true, portrait items (9:16) get narrower cells, landscape items (16:9) get wider cells.
91
+ * Items are packed into rows intelligently.
92
+ * @default false
93
+ */
94
+ flexLayout?: boolean;
84
95
  }
85
96
  /**
86
97
  * Container component for the meet grid.
87
98
  * Provides grid context to child GridItem components.
88
99
  */
89
100
  declare const GridContainer: React__default.ForwardRefExoticComponent<GridContainerProps & React__default.RefAttributes<HTMLDivElement>>;
90
- interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition'> {
101
+ interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition' | 'children'> {
91
102
  /** Index of this item in the grid */
92
103
  index: number;
93
- /** Children to render inside the item */
94
- children: ReactNode;
104
+ /**
105
+ * Children to render inside the item.
106
+ * Can be a ReactNode or a render function that receives contentDimensions and visibility info.
107
+ * @example
108
+ * // Simple usage
109
+ * <GridItem index={0}><Video /></GridItem>
110
+ *
111
+ * // With contentDimensions for flexible aspect ratios
112
+ * <GridItem index={0}>
113
+ * {({ contentDimensions }) => (
114
+ * <Video style={{
115
+ * width: contentDimensions.width,
116
+ * height: contentDimensions.height,
117
+ * marginTop: contentDimensions.offsetTop,
118
+ * marginLeft: contentDimensions.offsetLeft
119
+ * }} />
120
+ * )}
121
+ * </GridItem>
122
+ *
123
+ * // With hidden count for '+X more' indicator
124
+ * <GridItem index={index}>
125
+ * {({ isLastVisibleOther, hiddenCount }) => (
126
+ * <div>
127
+ * {isLastVisibleOther && hiddenCount > 0 && (
128
+ * <span className="more-indicator">+{hiddenCount}</span>
129
+ * )}
130
+ * </div>
131
+ * )}
132
+ * </GridItem>
133
+ */
134
+ children: ReactNode | ((props: {
135
+ contentDimensions: ContentDimensions;
136
+ /** True if this is the last visible item in the "others" section */
137
+ isLastVisibleOther: boolean;
138
+ /** Number of hidden items (for '+X more' indicator) */
139
+ hiddenCount: number;
140
+ }) => ReactNode);
141
+ /** Optional item-specific aspect ratio (overrides itemAspectRatios from container) */
142
+ itemAspectRatio?: ItemAspectRatio;
95
143
  /** Custom transition override */
96
144
  transition?: Transition;
97
145
  /** Whether to disable animations */
@@ -106,6 +154,43 @@ interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initia
106
154
  * Automatically positions itself based on index in the grid.
107
155
  */
108
156
  declare const GridItem: React__default.ForwardRefExoticComponent<Omit<GridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
157
+ interface FloatingGridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'children'> {
158
+ /** Children to render inside the floating item */
159
+ children: ReactNode;
160
+ /** Width of the floating item */
161
+ width?: number;
162
+ /** Height of the floating item */
163
+ height?: number;
164
+ /** Initial position (x, y from container edges) */
165
+ initialPosition?: {
166
+ x: number;
167
+ y: number;
168
+ };
169
+ /** Which corner to anchor: 'top-left', 'top-right', 'bottom-left', 'bottom-right' */
170
+ anchor?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
171
+ /** Whether the item is visible */
172
+ visible?: boolean;
173
+ /** Padding from container edges */
174
+ edgePadding?: number;
175
+ /** Callback when anchor changes after snap */
176
+ onAnchorChange?: (anchor: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right') => void;
177
+ /** Custom transition */
178
+ transition?: Transition;
179
+ /** Border radius */
180
+ borderRadius?: number;
181
+ /** Box shadow */
182
+ boxShadow?: string;
183
+ /** Additional class name */
184
+ className?: string;
185
+ /** Custom style */
186
+ style?: CSSProperties;
187
+ }
188
+ /**
189
+ * Floating Grid Item component that can be dragged around the screen.
190
+ * Snaps to the nearest corner when released (like iOS/Android PiP).
191
+ * Perfect for Picture-in-Picture style floating video in zoom mode.
192
+ */
193
+ declare const FloatingGridItem: React__default.ForwardRefExoticComponent<Omit<FloatingGridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
109
194
  interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
110
195
  /** Whether to show the overlay */
111
196
  visible?: boolean;
@@ -117,5 +202,5 @@ interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
117
202
  */
118
203
  declare const GridOverlay: React__default.ForwardRefExoticComponent<GridOverlayProps & React__default.RefAttributes<HTMLDivElement>>;
119
204
 
120
- export { GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
121
- export type { GridContainerProps, GridItemProps, GridOverlayProps };
205
+ export { FloatingGridItem, GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
206
+ export type { FloatingGridItemProps, GridContainerProps, GridItemProps, GridOverlayProps };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { RefObject, HTMLAttributes, ReactNode, CSSProperties } from 'react';
3
- import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode } from '@thangdevalone/meet-layout-grid-core';
4
- export { GridDimensions, GridOptions, LayoutMode, MeetGridOptions, MeetGridResult, PaginationInfo, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
3
+ import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode, ItemAspectRatio, ContentDimensions } from '@thangdevalone/meet-layout-grid-core';
4
+ export { ContentDimensions, GridDimensions, GridOptions, ItemAspectRatio, LayoutMode, MeetGridOptions, MeetGridResult, PaginationInfo, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
5
5
  import { HTMLMotionProps, Transition } from 'motion/react';
6
6
 
7
7
  interface GridContextValue {
@@ -59,10 +59,8 @@ interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
59
59
  count?: number;
60
60
  /** Layout mode */
61
61
  layoutMode?: LayoutMode;
62
- /** Index of pinned item */
62
+ /** Index of pinned/focused item (main participant for spotlight/sidebar modes) */
63
63
  pinnedIndex?: number;
64
- /** Index of active speaker */
65
- speakerIndex?: number;
66
64
  /** Sidebar position */
67
65
  sidebarPosition?: 'left' | 'right' | 'top' | 'bottom';
68
66
  /** Sidebar ratio (0-1) */
@@ -77,21 +75,71 @@ interface GridContainerProps extends Omit<HTMLAttributes<HTMLDivElement>, 'child
77
75
  maxItemsPerPage?: number;
78
76
  /** Current page index (0-based) for pagination */
79
77
  currentPage?: number;
80
- /** Maximum visible "others" in speaker/sidebar modes (0 = show all) */
81
- maxVisibleOthers?: number;
82
- /** Current page for "others" in speaker/sidebar modes (0-based) */
83
- currentOthersPage?: number;
78
+ /** Maximum visible items (0 = show all). In gallery mode: limits all items. In sidebar: limits "others". */
79
+ maxVisible?: number;
80
+ /** Current page for visible items (0-based), used when maxVisible > 0 */
81
+ currentVisiblePage?: number;
82
+ /**
83
+ * Per-item aspect ratio configurations.
84
+ * Use different ratios for mobile (9:16), desktop (16:9), or whiteboard (fill).
85
+ * @example ['16:9', '9:16', 'fill', undefined]
86
+ */
87
+ itemAspectRatios?: (ItemAspectRatio | undefined)[];
88
+ /**
89
+ * Enable flexible cell sizing based on item aspect ratios.
90
+ * When true, portrait items (9:16) get narrower cells, landscape items (16:9) get wider cells.
91
+ * Items are packed into rows intelligently.
92
+ * @default false
93
+ */
94
+ flexLayout?: boolean;
84
95
  }
85
96
  /**
86
97
  * Container component for the meet grid.
87
98
  * Provides grid context to child GridItem components.
88
99
  */
89
100
  declare const GridContainer: React__default.ForwardRefExoticComponent<GridContainerProps & React__default.RefAttributes<HTMLDivElement>>;
90
- interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition'> {
101
+ interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'transition' | 'children'> {
91
102
  /** Index of this item in the grid */
92
103
  index: number;
93
- /** Children to render inside the item */
94
- children: ReactNode;
104
+ /**
105
+ * Children to render inside the item.
106
+ * Can be a ReactNode or a render function that receives contentDimensions and visibility info.
107
+ * @example
108
+ * // Simple usage
109
+ * <GridItem index={0}><Video /></GridItem>
110
+ *
111
+ * // With contentDimensions for flexible aspect ratios
112
+ * <GridItem index={0}>
113
+ * {({ contentDimensions }) => (
114
+ * <Video style={{
115
+ * width: contentDimensions.width,
116
+ * height: contentDimensions.height,
117
+ * marginTop: contentDimensions.offsetTop,
118
+ * marginLeft: contentDimensions.offsetLeft
119
+ * }} />
120
+ * )}
121
+ * </GridItem>
122
+ *
123
+ * // With hidden count for '+X more' indicator
124
+ * <GridItem index={index}>
125
+ * {({ isLastVisibleOther, hiddenCount }) => (
126
+ * <div>
127
+ * {isLastVisibleOther && hiddenCount > 0 && (
128
+ * <span className="more-indicator">+{hiddenCount}</span>
129
+ * )}
130
+ * </div>
131
+ * )}
132
+ * </GridItem>
133
+ */
134
+ children: ReactNode | ((props: {
135
+ contentDimensions: ContentDimensions;
136
+ /** True if this is the last visible item in the "others" section */
137
+ isLastVisibleOther: boolean;
138
+ /** Number of hidden items (for '+X more' indicator) */
139
+ hiddenCount: number;
140
+ }) => ReactNode);
141
+ /** Optional item-specific aspect ratio (overrides itemAspectRatios from container) */
142
+ itemAspectRatio?: ItemAspectRatio;
95
143
  /** Custom transition override */
96
144
  transition?: Transition;
97
145
  /** Whether to disable animations */
@@ -106,6 +154,43 @@ interface GridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initia
106
154
  * Automatically positions itself based on index in the grid.
107
155
  */
108
156
  declare const GridItem: React__default.ForwardRefExoticComponent<Omit<GridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
157
+ interface FloatingGridItemProps extends Omit<HTMLMotionProps<'div'>, 'animate' | 'initial' | 'children'> {
158
+ /** Children to render inside the floating item */
159
+ children: ReactNode;
160
+ /** Width of the floating item */
161
+ width?: number;
162
+ /** Height of the floating item */
163
+ height?: number;
164
+ /** Initial position (x, y from container edges) */
165
+ initialPosition?: {
166
+ x: number;
167
+ y: number;
168
+ };
169
+ /** Which corner to anchor: 'top-left', 'top-right', 'bottom-left', 'bottom-right' */
170
+ anchor?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
171
+ /** Whether the item is visible */
172
+ visible?: boolean;
173
+ /** Padding from container edges */
174
+ edgePadding?: number;
175
+ /** Callback when anchor changes after snap */
176
+ onAnchorChange?: (anchor: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right') => void;
177
+ /** Custom transition */
178
+ transition?: Transition;
179
+ /** Border radius */
180
+ borderRadius?: number;
181
+ /** Box shadow */
182
+ boxShadow?: string;
183
+ /** Additional class name */
184
+ className?: string;
185
+ /** Custom style */
186
+ style?: CSSProperties;
187
+ }
188
+ /**
189
+ * Floating Grid Item component that can be dragged around the screen.
190
+ * Snaps to the nearest corner when released (like iOS/Android PiP).
191
+ * Perfect for Picture-in-Picture style floating video in zoom mode.
192
+ */
193
+ declare const FloatingGridItem: React__default.ForwardRefExoticComponent<Omit<FloatingGridItemProps, "ref"> & React__default.RefAttributes<HTMLDivElement>>;
109
194
  interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
110
195
  /** Whether to show the overlay */
111
196
  visible?: boolean;
@@ -117,5 +202,5 @@ interface GridOverlayProps extends HTMLAttributes<HTMLDivElement> {
117
202
  */
118
203
  declare const GridOverlay: React__default.ForwardRefExoticComponent<GridOverlayProps & React__default.RefAttributes<HTMLDivElement>>;
119
204
 
120
- export { GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
121
- export type { GridContainerProps, GridItemProps, GridOverlayProps };
205
+ export { FloatingGridItem, GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
206
+ export type { FloatingGridItemProps, GridContainerProps, GridItemProps, GridOverlayProps };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import React, { createContext, useContext, useState, useEffect, useMemo, forwardRef, useRef } from 'react';
2
2
  import { createMeetGrid, getSpringConfig } from '@thangdevalone/meet-layout-grid-core';
3
3
  export { createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@thangdevalone/meet-layout-grid-core';
4
- import { motion } from 'motion/react';
4
+ import { motion, useMotionValue, animate } from 'motion/react';
5
5
 
6
6
  const GridContext = createContext(null);
7
7
  function useGridContext() {
@@ -41,6 +41,7 @@ function useGridDimensions(ref) {
41
41
  return dimensions;
42
42
  }
43
43
  function useMeetGrid(options) {
44
+ const itemAspectRatiosKey = options.itemAspectRatios?.join(",") ?? "";
44
45
  return useMemo(() => {
45
46
  return createMeetGrid(options);
46
47
  }, [
@@ -51,13 +52,14 @@ function useMeetGrid(options) {
51
52
  options.gap,
52
53
  options.layoutMode,
53
54
  options.pinnedIndex,
54
- options.speakerIndex,
55
55
  options.sidebarPosition,
56
56
  options.sidebarRatio,
57
57
  options.maxItemsPerPage,
58
58
  options.currentPage,
59
- options.maxVisibleOthers,
60
- options.currentOthersPage
59
+ options.maxVisible,
60
+ options.currentVisiblePage,
61
+ options.flexLayout,
62
+ itemAspectRatiosKey
61
63
  ]);
62
64
  }
63
65
  function useGridAnimation(preset = "smooth") {
@@ -72,7 +74,6 @@ const GridContainer = forwardRef(
72
74
  count,
73
75
  layoutMode = "gallery",
74
76
  pinnedIndex,
75
- speakerIndex,
76
77
  sidebarPosition,
77
78
  sidebarRatio,
78
79
  springPreset = "smooth",
@@ -80,8 +81,10 @@ const GridContainer = forwardRef(
80
81
  className,
81
82
  maxItemsPerPage,
82
83
  currentPage,
83
- maxVisibleOthers,
84
- currentOthersPage,
84
+ maxVisible,
85
+ currentVisiblePage,
86
+ itemAspectRatios,
87
+ flexLayout,
85
88
  ...props
86
89
  }, forwardedRef) {
87
90
  const internalRef = useRef(null);
@@ -95,13 +98,14 @@ const GridContainer = forwardRef(
95
98
  gap,
96
99
  layoutMode,
97
100
  pinnedIndex,
98
- speakerIndex,
99
101
  sidebarPosition,
100
102
  sidebarRatio,
101
103
  maxItemsPerPage,
102
104
  currentPage,
103
- maxVisibleOthers,
104
- currentOthersPage
105
+ maxVisible,
106
+ currentVisiblePage,
107
+ itemAspectRatios,
108
+ flexLayout
105
109
  };
106
110
  const grid = useMeetGrid(gridOptions);
107
111
  const containerStyle = {
@@ -118,6 +122,7 @@ const GridItem = forwardRef(
118
122
  function GridItem2({
119
123
  index,
120
124
  children,
125
+ itemAspectRatio,
121
126
  transition: customTransition,
122
127
  disableAnimation = false,
123
128
  className,
@@ -133,6 +138,7 @@ const GridItem = forwardRef(
133
138
  }
134
139
  const { top, left } = grid.getPosition(index);
135
140
  const { width, height } = grid.getItemDimensions(index);
141
+ const contentDimensions = grid.getItemContentDimensions(index, itemAspectRatio);
136
142
  const isMain = grid.isMainItem(index);
137
143
  if (grid.layoutMode === "spotlight" && !isMain) {
138
144
  return null;
@@ -150,6 +156,15 @@ const GridItem = forwardRef(
150
156
  top,
151
157
  left
152
158
  };
159
+ const lastVisibleOthersIndex = grid.getLastVisibleOthersIndex();
160
+ const isLastVisibleOther = index === lastVisibleOthersIndex;
161
+ const hiddenCount = grid.hiddenCount;
162
+ const renderChildren = () => {
163
+ if (typeof children === "function") {
164
+ return children({ contentDimensions, isLastVisibleOther, hiddenCount });
165
+ }
166
+ return children;
167
+ };
153
168
  if (disableAnimation) {
154
169
  return /* @__PURE__ */ React.createElement(
155
170
  "div",
@@ -161,7 +176,7 @@ const GridItem = forwardRef(
161
176
  "data-grid-main": isMain,
162
177
  ...props
163
178
  },
164
- children
179
+ renderChildren()
165
180
  );
166
181
  }
167
182
  return /* @__PURE__ */ React.createElement(
@@ -178,6 +193,125 @@ const GridItem = forwardRef(
178
193
  "data-grid-main": isMain,
179
194
  ...props
180
195
  },
196
+ renderChildren()
197
+ );
198
+ }
199
+ );
200
+ const FloatingGridItem = forwardRef(
201
+ function FloatingGridItem2({
202
+ children,
203
+ width = 120,
204
+ height = 160,
205
+ initialPosition = { x: 16, y: 16 },
206
+ anchor: initialAnchor = "bottom-right",
207
+ visible = true,
208
+ edgePadding = 12,
209
+ onAnchorChange,
210
+ transition,
211
+ borderRadius = 12,
212
+ boxShadow = "0 4px 20px rgba(0,0,0,0.3)",
213
+ className,
214
+ style,
215
+ ...props
216
+ }, ref) {
217
+ const { dimensions } = useGridContext();
218
+ const [currentAnchor, setCurrentAnchor] = React.useState(initialAnchor);
219
+ const x = useMotionValue(0);
220
+ const y = useMotionValue(0);
221
+ const [isInitialized, setIsInitialized] = React.useState(false);
222
+ const getCornerPosition = React.useCallback((corner) => {
223
+ const padding = edgePadding + initialPosition.x;
224
+ switch (corner) {
225
+ case "top-left":
226
+ return { x: padding, y: padding };
227
+ case "top-right":
228
+ return { x: dimensions.width - width - padding, y: padding };
229
+ case "bottom-left":
230
+ return { x: padding, y: dimensions.height - height - padding };
231
+ case "bottom-right":
232
+ default:
233
+ return { x: dimensions.width - width - padding, y: dimensions.height - height - padding };
234
+ }
235
+ }, [dimensions.width, dimensions.height, width, height, edgePadding, initialPosition.x]);
236
+ React.useEffect(() => {
237
+ if (dimensions.width > 0 && dimensions.height > 0 && !isInitialized) {
238
+ const pos = getCornerPosition(currentAnchor);
239
+ x.set(pos.x);
240
+ y.set(pos.y);
241
+ setIsInitialized(true);
242
+ }
243
+ }, [dimensions.width, dimensions.height, currentAnchor, getCornerPosition, isInitialized, x, y]);
244
+ React.useEffect(() => {
245
+ if (isInitialized && dimensions.width > 0 && dimensions.height > 0) {
246
+ const pos = getCornerPosition(currentAnchor);
247
+ const springConfig = { type: "spring", stiffness: 400, damping: 30 };
248
+ animate(x, pos.x, springConfig);
249
+ animate(y, pos.y, springConfig);
250
+ }
251
+ }, [currentAnchor, dimensions.width, dimensions.height, getCornerPosition, isInitialized, x, y]);
252
+ if (!visible || dimensions.width === 0 || dimensions.height === 0)
253
+ return null;
254
+ const findNearestCorner = (posX, posY) => {
255
+ const centerX = posX + width / 2;
256
+ const centerY = posY + height / 2;
257
+ const containerCenterX = dimensions.width / 2;
258
+ const containerCenterY = dimensions.height / 2;
259
+ const isLeft = centerX < containerCenterX;
260
+ const isTop = centerY < containerCenterY;
261
+ if (isTop && isLeft)
262
+ return "top-left";
263
+ if (isTop && !isLeft)
264
+ return "top-right";
265
+ if (!isTop && isLeft)
266
+ return "bottom-left";
267
+ return "bottom-right";
268
+ };
269
+ const dragConstraints = {
270
+ left: edgePadding,
271
+ right: dimensions.width - width - edgePadding,
272
+ top: edgePadding,
273
+ bottom: dimensions.height - height - edgePadding
274
+ };
275
+ const floatingStyle = {
276
+ position: "absolute",
277
+ width,
278
+ height,
279
+ borderRadius,
280
+ boxShadow,
281
+ overflow: "hidden",
282
+ cursor: "grab",
283
+ zIndex: 100,
284
+ touchAction: "none",
285
+ left: 0,
286
+ top: 0,
287
+ ...style
288
+ };
289
+ const handleDragEnd = () => {
290
+ const currentX = x.get();
291
+ const currentY = y.get();
292
+ const nearestCorner = findNearestCorner(currentX, currentY);
293
+ setCurrentAnchor(nearestCorner);
294
+ onAnchorChange?.(nearestCorner);
295
+ const snapPos = getCornerPosition(nearestCorner);
296
+ const springConfig = { type: "spring", stiffness: 400, damping: 30 };
297
+ animate(x, snapPos.x, springConfig);
298
+ animate(y, snapPos.y, springConfig);
299
+ };
300
+ return /* @__PURE__ */ React.createElement(
301
+ motion.div,
302
+ {
303
+ ref,
304
+ drag: true,
305
+ dragMomentum: false,
306
+ dragElastic: 0.1,
307
+ dragConstraints,
308
+ style: { ...floatingStyle, x, y },
309
+ className,
310
+ onDragEnd: handleDragEnd,
311
+ whileDrag: { cursor: "grabbing", scale: 1.05, boxShadow: "0 8px 32px rgba(0,0,0,0.4)" },
312
+ transition: transition ?? { type: "spring", stiffness: 400, damping: 30 },
313
+ ...props
314
+ },
181
315
  children
182
316
  );
183
317
  }
@@ -206,4 +340,4 @@ const GridOverlay = forwardRef(
206
340
  }
207
341
  );
208
342
 
209
- export { GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
343
+ export { FloatingGridItem, GridContainer, GridContext, GridItem, GridOverlay, useGridAnimation, useGridContext, useGridDimensions, useMeetGrid };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thangdevalone/meet-layout-grid-react",
3
- "version": "1.0.9",
3
+ "version": "1.1.1",
4
4
  "description": "React integration for meet-layout-grid with Motion animations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "motion": "^11.15.0",
45
- "@thangdevalone/meet-layout-grid-core": "1.0.9"
45
+ "@thangdevalone/meet-layout-grid-core": "1.1.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/react": "^18.2.0",