@thangdevalone/meet-layout-grid-vue 1.0.0

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 ADDED
@@ -0,0 +1,126 @@
1
+ # @meet-layout-grid/vue
2
+
3
+ Vue 3 integration for meet-layout-grid with Motion animations.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @meet-layout-grid/vue
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```vue
14
+ <script setup>
15
+ import { GridContainer, GridItem } from '@meet-layout-grid/vue'
16
+ import { ref } from 'vue'
17
+
18
+ const participants = ref([
19
+ { id: 1, name: 'You' },
20
+ { id: 2, name: 'User 1' },
21
+ ])
22
+ </script>
23
+
24
+ <template>
25
+ <GridContainer
26
+ aspect-ratio="16:9"
27
+ :gap="8"
28
+ :count="participants.length"
29
+ layout-mode="gallery"
30
+ >
31
+ <GridItem
32
+ v-for="(participant, index) in participants"
33
+ :key="participant.id"
34
+ :index="index"
35
+ >
36
+ <VideoTile :participant="participant" />
37
+ </GridItem>
38
+ </GridContainer>
39
+ </template>
40
+ ```
41
+
42
+ ## Components
43
+
44
+ ### `<GridContainer>`
45
+
46
+ Container component with provide/inject context.
47
+
48
+ ```vue
49
+ <GridContainer
50
+ aspect-ratio="16:9"
51
+ :gap="8"
52
+ :count="6"
53
+ layout-mode="gallery"
54
+ :speaker-index="0"
55
+ :pinned-index="0"
56
+ sidebar-position="right"
57
+ :sidebar-ratio="0.25"
58
+ spring-preset="smooth"
59
+ tag="div"
60
+ >
61
+ <slot />
62
+ </GridContainer>
63
+ ```
64
+
65
+ ### `<GridItem>`
66
+
67
+ Animated grid item with motion-v animations.
68
+
69
+ ```vue
70
+ <GridItem
71
+ :index="0"
72
+ :disable-animation="false"
73
+ tag="div"
74
+ >
75
+ <slot />
76
+ </GridItem>
77
+ ```
78
+
79
+ ## Composables
80
+
81
+ ### `useGridDimensions(ref)`
82
+
83
+ ```ts
84
+ import { useGridDimensions } from '@meet-layout-grid/vue'
85
+ import { ref } from 'vue'
86
+
87
+ const containerRef = ref<HTMLElement | null>(null)
88
+ const dimensions = useGridDimensions(containerRef)
89
+ // ComputedRef<{ width: number, height: number }>
90
+ ```
91
+
92
+ ### `useMeetGrid(options)`
93
+
94
+ ```ts
95
+ import { useMeetGrid } from '@meet-layout-grid/vue'
96
+ import { computed } from 'vue'
97
+
98
+ const options = computed(() => ({
99
+ dimensions: dimensions.value,
100
+ count: 6,
101
+ aspectRatio: '16:9',
102
+ gap: 8,
103
+ layoutMode: 'speaker',
104
+ }))
105
+
106
+ const grid = useMeetGrid(options)
107
+ ```
108
+
109
+ ### `useGridItemPosition(grid, index)`
110
+
111
+ ```ts
112
+ import { useGridItemPosition } from '@meet-layout-grid/vue'
113
+
114
+ const { position, dimensions, isMain, isHidden } = useGridItemPosition(grid, 0)
115
+ ```
116
+
117
+ ## Layout Modes
118
+
119
+ - **gallery** - Equal-sized tiles
120
+ - **speaker** - Active speaker larger
121
+ - **spotlight** - Single participant focus
122
+ - **sidebar** - Main view + thumbnails
123
+
124
+ ## License
125
+
126
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,277 @@
1
+ 'use strict';
2
+
3
+ const vue = require('vue');
4
+ const core$1 = require('@vueuse/core');
5
+ const core = require('@meet-layout-grid/core');
6
+ const motionV = require('motion-v');
7
+
8
+ function useGridDimensions(elementRef) {
9
+ const width = vue.ref(0);
10
+ const height = vue.ref(0);
11
+ core$1.useResizeObserver(elementRef, (entries) => {
12
+ const entry = entries[0];
13
+ if (entry) {
14
+ width.value = entry.contentRect.width;
15
+ height.value = entry.contentRect.height;
16
+ }
17
+ });
18
+ vue.onMounted(() => {
19
+ if (elementRef.value) {
20
+ width.value = elementRef.value.clientWidth;
21
+ height.value = elementRef.value.clientHeight;
22
+ }
23
+ });
24
+ return vue.computed(() => ({
25
+ width: width.value,
26
+ height: height.value
27
+ }));
28
+ }
29
+ function useMeetGrid(options) {
30
+ const getOptions = typeof options === "function" ? options : () => options.value;
31
+ return vue.computed(() => {
32
+ const opts = getOptions();
33
+ return core.createMeetGrid(opts);
34
+ });
35
+ }
36
+ function useGridAnimation(preset = "smooth") {
37
+ return vue.computed(() => core.getSpringConfig(preset));
38
+ }
39
+ function useGridItemPosition(grid, index) {
40
+ const getIndex = () => typeof index === "number" ? index : index.value;
41
+ const position = vue.computed(() => grid.value.getPosition(getIndex()));
42
+ const dimensions = vue.computed(() => grid.value.getItemDimensions(getIndex()));
43
+ const isMain = vue.computed(() => grid.value.isMainItem(getIndex()));
44
+ const isHidden = vue.computed(() => {
45
+ return grid.value.layoutMode === "spotlight" && !isMain.value;
46
+ });
47
+ return {
48
+ position,
49
+ dimensions,
50
+ isMain,
51
+ isHidden
52
+ };
53
+ }
54
+
55
+ const GridContextKey = Symbol("MeetGridContext");
56
+ const GridContainer = vue.defineComponent({
57
+ name: "GridContainer",
58
+ props: {
59
+ /** Aspect ratio in format "width:height" */
60
+ aspectRatio: {
61
+ type: String,
62
+ default: "16:9"
63
+ },
64
+ /** Gap between items in pixels */
65
+ gap: {
66
+ type: Number,
67
+ default: 8
68
+ },
69
+ /** Number of items */
70
+ count: {
71
+ type: Number,
72
+ required: true
73
+ },
74
+ /** Layout mode */
75
+ layoutMode: {
76
+ type: String,
77
+ default: "gallery"
78
+ },
79
+ /** Index of pinned item */
80
+ pinnedIndex: {
81
+ type: Number,
82
+ default: void 0
83
+ },
84
+ /** Index of active speaker */
85
+ speakerIndex: {
86
+ type: Number,
87
+ default: void 0
88
+ },
89
+ /** Sidebar position */
90
+ sidebarPosition: {
91
+ type: String,
92
+ default: "right"
93
+ },
94
+ /** Sidebar ratio (0-1) */
95
+ sidebarRatio: {
96
+ type: Number,
97
+ default: 0.25
98
+ },
99
+ /** Spring animation preset */
100
+ springPreset: {
101
+ type: String,
102
+ default: "smooth"
103
+ },
104
+ /** HTML tag to render */
105
+ tag: {
106
+ type: String,
107
+ default: "div"
108
+ }
109
+ },
110
+ setup(props, { slots }) {
111
+ const containerRef = vue.ref(null);
112
+ const dimensions = useGridDimensions(containerRef);
113
+ const gridOptions = vue.computed(() => ({
114
+ dimensions: dimensions.value,
115
+ count: props.count,
116
+ aspectRatio: props.aspectRatio,
117
+ gap: props.gap,
118
+ layoutMode: props.layoutMode,
119
+ pinnedIndex: props.pinnedIndex,
120
+ speakerIndex: props.speakerIndex,
121
+ sidebarPosition: props.sidebarPosition,
122
+ sidebarRatio: props.sidebarRatio
123
+ }));
124
+ const grid = useMeetGrid(gridOptions);
125
+ vue.provide(GridContextKey, {
126
+ grid,
127
+ springPreset: props.springPreset
128
+ });
129
+ return () => vue.h(
130
+ props.tag,
131
+ {
132
+ ref: containerRef,
133
+ style: {
134
+ position: "relative",
135
+ width: "100%",
136
+ height: "100%",
137
+ overflow: "hidden"
138
+ }
139
+ },
140
+ slots.default?.()
141
+ );
142
+ }
143
+ });
144
+ const GridItem = vue.defineComponent({
145
+ name: "GridItem",
146
+ props: {
147
+ /** Index of this item in the grid */
148
+ index: {
149
+ type: Number,
150
+ required: true
151
+ },
152
+ /** Whether to disable animations */
153
+ disableAnimation: {
154
+ type: Boolean,
155
+ default: false
156
+ },
157
+ /** HTML tag to render */
158
+ tag: {
159
+ type: String,
160
+ default: "div"
161
+ }
162
+ },
163
+ setup(props, { slots }) {
164
+ const context = vue.inject(GridContextKey);
165
+ if (!context) {
166
+ console.warn("GridItem must be used inside a GridContainer");
167
+ return () => null;
168
+ }
169
+ const { grid, springPreset } = context;
170
+ const position = vue.computed(() => grid.value.getPosition(props.index));
171
+ const dimensions = vue.computed(() => grid.value.getItemDimensions(props.index));
172
+ const isMain = vue.computed(() => grid.value.isMainItem(props.index));
173
+ const isHidden = vue.computed(() => {
174
+ return grid.value.layoutMode === "spotlight" && !isMain.value;
175
+ });
176
+ const springConfig = core.getSpringConfig(springPreset);
177
+ return () => {
178
+ if (isHidden.value) {
179
+ return null;
180
+ }
181
+ const animateProps = {
182
+ width: dimensions.value.width,
183
+ height: dimensions.value.height,
184
+ top: position.value.top,
185
+ left: position.value.left
186
+ };
187
+ if (props.disableAnimation) {
188
+ return vue.h(
189
+ props.tag,
190
+ {
191
+ style: {
192
+ position: "absolute",
193
+ ...animateProps,
194
+ width: `${animateProps.width}px`,
195
+ height: `${animateProps.height}px`,
196
+ top: `${animateProps.top}px`,
197
+ left: `${animateProps.left}px`
198
+ },
199
+ "data-grid-index": props.index,
200
+ "data-grid-main": isMain.value
201
+ },
202
+ slots.default?.()
203
+ );
204
+ }
205
+ return vue.h(
206
+ motionV.Motion,
207
+ {
208
+ tag: props.tag,
209
+ animate: animateProps,
210
+ transition: {
211
+ type: springConfig.type,
212
+ stiffness: springConfig.stiffness,
213
+ damping: springConfig.damping
214
+ },
215
+ style: {
216
+ position: "absolute"
217
+ },
218
+ "data-grid-index": props.index,
219
+ "data-grid-main": isMain.value
220
+ },
221
+ slots.default
222
+ );
223
+ };
224
+ }
225
+ });
226
+ const GridOverlay = vue.defineComponent({
227
+ name: "GridOverlay",
228
+ props: {
229
+ /** Whether to show the overlay */
230
+ visible: {
231
+ type: Boolean,
232
+ default: true
233
+ },
234
+ /** Background color */
235
+ backgroundColor: {
236
+ type: String,
237
+ default: "rgba(0,0,0,0.5)"
238
+ }
239
+ },
240
+ setup(props, { slots }) {
241
+ return () => {
242
+ if (!props.visible) {
243
+ return null;
244
+ }
245
+ return vue.h(
246
+ "div",
247
+ {
248
+ style: {
249
+ position: "absolute",
250
+ inset: 0,
251
+ display: "flex",
252
+ alignItems: "center",
253
+ justifyContent: "center",
254
+ backgroundColor: props.backgroundColor
255
+ }
256
+ },
257
+ slots.default?.()
258
+ );
259
+ };
260
+ }
261
+ });
262
+
263
+ exports.createGrid = core.createGrid;
264
+ exports.createGridItemPositioner = core.createGridItemPositioner;
265
+ exports.createMeetGrid = core.createMeetGrid;
266
+ exports.getAspectRatio = core.getAspectRatio;
267
+ exports.getGridItemDimensions = core.getGridItemDimensions;
268
+ exports.getSpringConfig = core.getSpringConfig;
269
+ exports.springPresets = core.springPresets;
270
+ exports.GridContainer = GridContainer;
271
+ exports.GridContextKey = GridContextKey;
272
+ exports.GridItem = GridItem;
273
+ exports.GridOverlay = GridOverlay;
274
+ exports.useGridAnimation = useGridAnimation;
275
+ exports.useGridDimensions = useGridDimensions;
276
+ exports.useGridItemPosition = useGridItemPosition;
277
+ exports.useMeetGrid = useMeetGrid;
@@ -0,0 +1,237 @@
1
+ import * as _meet_layout_grid_core from '@meet-layout-grid/core';
2
+ import { GridDimensions, MeetGridOptions, MeetGridResult, SpringPreset, LayoutMode } from '@meet-layout-grid/core';
3
+ export { GridDimensions, GridOptions, LayoutMode, MeetGridOptions, MeetGridResult, Position, SpringPreset, createGrid, createGridItemPositioner, createMeetGrid, getAspectRatio, getGridItemDimensions, getSpringConfig, springPresets } from '@meet-layout-grid/core';
4
+ import * as vue from 'vue';
5
+ import { Ref, ComputedRef, InjectionKey, PropType } from 'vue';
6
+
7
+ /**
8
+ * Vue composable to track element dimensions using ResizeObserver.
9
+ * @param elementRef A ref to the target element
10
+ * @returns Reactive dimensions object
11
+ */
12
+ declare function useGridDimensions(elementRef: Ref<HTMLElement | null | undefined>): ComputedRef<GridDimensions>;
13
+ /**
14
+ * Vue composable for creating a meet-style responsive grid.
15
+ * @param options Reactive or static grid options
16
+ * @returns Reactive grid result
17
+ */
18
+ declare function useMeetGrid(options: Ref<MeetGridOptions> | ComputedRef<MeetGridOptions> | (() => MeetGridOptions)): ComputedRef<MeetGridResult>;
19
+ /**
20
+ * Composable to get animation configuration for Motion
21
+ */
22
+ declare function useGridAnimation(preset?: SpringPreset): ComputedRef<{
23
+ stiffness: 400;
24
+ damping: 30;
25
+ type: "spring";
26
+ } | {
27
+ stiffness: 300;
28
+ damping: 30;
29
+ type: "spring";
30
+ } | {
31
+ stiffness: 200;
32
+ damping: 25;
33
+ type: "spring";
34
+ } | {
35
+ stiffness: 400;
36
+ damping: 15;
37
+ type: "spring";
38
+ }>;
39
+ /**
40
+ * Composable to get position for a specific grid item index
41
+ */
42
+ declare function useGridItemPosition(grid: ComputedRef<MeetGridResult>, index: Ref<number> | ComputedRef<number> | number): {
43
+ position: ComputedRef<_meet_layout_grid_core.Position>;
44
+ dimensions: ComputedRef<GridDimensions>;
45
+ isMain: ComputedRef<boolean>;
46
+ isHidden: ComputedRef<boolean>;
47
+ };
48
+
49
+ interface GridContextValue {
50
+ grid: ComputedRef<MeetGridResult>;
51
+ springPreset: SpringPreset;
52
+ }
53
+ declare const GridContextKey: InjectionKey<GridContextValue>;
54
+ declare const GridContainer: vue.DefineComponent<vue.ExtractPropTypes<{
55
+ /** Aspect ratio in format "width:height" */
56
+ aspectRatio: {
57
+ type: StringConstructor;
58
+ default: string;
59
+ };
60
+ /** Gap between items in pixels */
61
+ gap: {
62
+ type: NumberConstructor;
63
+ default: number;
64
+ };
65
+ /** Number of items */
66
+ count: {
67
+ type: NumberConstructor;
68
+ required: true;
69
+ };
70
+ /** Layout mode */
71
+ layoutMode: {
72
+ type: PropType<LayoutMode>;
73
+ default: string;
74
+ };
75
+ /** Index of pinned item */
76
+ pinnedIndex: {
77
+ type: NumberConstructor;
78
+ default: undefined;
79
+ };
80
+ /** Index of active speaker */
81
+ speakerIndex: {
82
+ type: NumberConstructor;
83
+ default: undefined;
84
+ };
85
+ /** Sidebar position */
86
+ sidebarPosition: {
87
+ type: PropType<"left" | "right" | "bottom">;
88
+ default: string;
89
+ };
90
+ /** Sidebar ratio (0-1) */
91
+ sidebarRatio: {
92
+ type: NumberConstructor;
93
+ default: number;
94
+ };
95
+ /** Spring animation preset */
96
+ springPreset: {
97
+ type: PropType<SpringPreset>;
98
+ default: string;
99
+ };
100
+ /** HTML tag to render */
101
+ tag: {
102
+ type: StringConstructor;
103
+ default: string;
104
+ };
105
+ }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
106
+ [key: string]: any;
107
+ }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
108
+ /** Aspect ratio in format "width:height" */
109
+ aspectRatio: {
110
+ type: StringConstructor;
111
+ default: string;
112
+ };
113
+ /** Gap between items in pixels */
114
+ gap: {
115
+ type: NumberConstructor;
116
+ default: number;
117
+ };
118
+ /** Number of items */
119
+ count: {
120
+ type: NumberConstructor;
121
+ required: true;
122
+ };
123
+ /** Layout mode */
124
+ layoutMode: {
125
+ type: PropType<LayoutMode>;
126
+ default: string;
127
+ };
128
+ /** Index of pinned item */
129
+ pinnedIndex: {
130
+ type: NumberConstructor;
131
+ default: undefined;
132
+ };
133
+ /** Index of active speaker */
134
+ speakerIndex: {
135
+ type: NumberConstructor;
136
+ default: undefined;
137
+ };
138
+ /** Sidebar position */
139
+ sidebarPosition: {
140
+ type: PropType<"left" | "right" | "bottom">;
141
+ default: string;
142
+ };
143
+ /** Sidebar ratio (0-1) */
144
+ sidebarRatio: {
145
+ type: NumberConstructor;
146
+ default: number;
147
+ };
148
+ /** Spring animation preset */
149
+ springPreset: {
150
+ type: PropType<SpringPreset>;
151
+ default: string;
152
+ };
153
+ /** HTML tag to render */
154
+ tag: {
155
+ type: StringConstructor;
156
+ default: string;
157
+ };
158
+ }>> & Readonly<{}>, {
159
+ aspectRatio: string;
160
+ gap: number;
161
+ layoutMode: LayoutMode;
162
+ pinnedIndex: number;
163
+ speakerIndex: number;
164
+ sidebarPosition: "left" | "right" | "bottom";
165
+ sidebarRatio: number;
166
+ springPreset: "snappy" | "smooth" | "gentle" | "bouncy";
167
+ tag: string;
168
+ }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
169
+ declare const GridItem: vue.DefineComponent<vue.ExtractPropTypes<{
170
+ /** Index of this item in the grid */
171
+ index: {
172
+ type: NumberConstructor;
173
+ required: true;
174
+ };
175
+ /** Whether to disable animations */
176
+ disableAnimation: {
177
+ type: BooleanConstructor;
178
+ default: boolean;
179
+ };
180
+ /** HTML tag to render */
181
+ tag: {
182
+ type: StringConstructor;
183
+ default: string;
184
+ };
185
+ }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
186
+ [key: string]: any;
187
+ }> | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
188
+ /** Index of this item in the grid */
189
+ index: {
190
+ type: NumberConstructor;
191
+ required: true;
192
+ };
193
+ /** Whether to disable animations */
194
+ disableAnimation: {
195
+ type: BooleanConstructor;
196
+ default: boolean;
197
+ };
198
+ /** HTML tag to render */
199
+ tag: {
200
+ type: StringConstructor;
201
+ default: string;
202
+ };
203
+ }>> & Readonly<{}>, {
204
+ tag: string;
205
+ disableAnimation: boolean;
206
+ }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
207
+ declare const GridOverlay: vue.DefineComponent<vue.ExtractPropTypes<{
208
+ /** Whether to show the overlay */
209
+ visible: {
210
+ type: BooleanConstructor;
211
+ default: boolean;
212
+ };
213
+ /** Background color */
214
+ backgroundColor: {
215
+ type: StringConstructor;
216
+ default: string;
217
+ };
218
+ }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
219
+ [key: string]: any;
220
+ }> | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
221
+ /** Whether to show the overlay */
222
+ visible: {
223
+ type: BooleanConstructor;
224
+ default: boolean;
225
+ };
226
+ /** Background color */
227
+ backgroundColor: {
228
+ type: StringConstructor;
229
+ default: string;
230
+ };
231
+ }>> & Readonly<{}>, {
232
+ backgroundColor: string;
233
+ visible: boolean;
234
+ }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
235
+
236
+ export { GridContainer, GridContextKey, GridItem, GridOverlay, useGridAnimation, useGridDimensions, useGridItemPosition, useMeetGrid };
237
+ export type { GridContextValue };