@thangdevalone/meet-layout-grid-vue 1.3.2 → 1.3.3

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 CHANGED
@@ -124,6 +124,25 @@ const GridContainer = vue.defineComponent({
124
124
  type: Array,
125
125
  default: void 0
126
126
  },
127
+ /** Custom width for the floating PiP item in 2-person mode */
128
+ floatWidth: {
129
+ type: Number,
130
+ default: void 0
131
+ },
132
+ /** Custom height for the floating PiP item in 2-person mode */
133
+ floatHeight: {
134
+ type: Number,
135
+ default: void 0
136
+ },
137
+ /**
138
+ * Responsive breakpoints for the floating PiP in 2-person mode.
139
+ * When provided, PiP size auto-adjusts based on container width.
140
+ * Use `DEFAULT_FLOAT_BREAKPOINTS` for a ready-made 5-level responsive config.
141
+ */
142
+ floatBreakpoints: {
143
+ type: Array,
144
+ default: void 0
145
+ },
127
146
  /** HTML tag to render */
128
147
  tag: {
129
148
  type: String,
@@ -145,7 +164,10 @@ const GridContainer = vue.defineComponent({
145
164
  currentPage: props.currentPage,
146
165
  maxVisible: props.maxVisible,
147
166
  currentVisiblePage: props.currentVisiblePage,
148
- itemAspectRatios: props.itemAspectRatios
167
+ itemAspectRatios: props.itemAspectRatios,
168
+ floatWidth: props.floatWidth,
169
+ floatHeight: props.floatHeight,
170
+ floatBreakpoints: props.floatBreakpoints
149
171
  }));
150
172
  const grid = useMeetGrid(gridOptions);
151
173
  vue.provide(GridContextKey, {
@@ -215,11 +237,11 @@ const GridItem = vue.defineComponent({
215
237
  });
216
238
  const isFloat = vue.computed(() => grid.value.floatIndex === props.index);
217
239
  const floatDims = vue.computed(() => grid.value.floatDimensions ?? { width: 120, height: 160 });
218
- const floatAnchor = vue.ref("bottom-right");
219
- const floatDragging = vue.ref(false);
220
- const floatDragOffset = vue.ref({ x: 0, y: 0 });
221
- const floatStartPos = vue.ref({ x: 0, y: 0 });
222
- const floatDisplayPos = vue.ref({ x: 0, y: 0 });
240
+ const floatAnchor = vue.ref(
241
+ "bottom-right"
242
+ );
243
+ const x = motionV.useMotionValue(0);
244
+ const y = motionV.useMotionValue(0);
223
245
  const floatInitialized = vue.ref(false);
224
246
  const getFloatCornerPos = (corner) => {
225
247
  const padding = 12;
@@ -238,11 +260,11 @@ const GridItem = vue.defineComponent({
238
260
  return { x: dims.width - fw - padding, y: dims.height - fh - padding };
239
261
  }
240
262
  };
241
- const findFloatNearestCorner = (x, y) => {
263
+ const findFloatNearestCorner = (posX, posY) => {
242
264
  const fw = floatDims.value.width;
243
265
  const fh = floatDims.value.height;
244
- const centerX = x + fw / 2;
245
- const centerY = y + fh / 2;
266
+ const centerX = posX + fw / 2;
267
+ const centerY = posY + fh / 2;
246
268
  const dims = containerDimensions.value;
247
269
  const isLeft = centerX < dims.width / 2;
248
270
  const isTop = centerY < dims.height / 2;
@@ -254,46 +276,77 @@ const GridItem = vue.defineComponent({
254
276
  return "bottom-left";
255
277
  return "bottom-right";
256
278
  };
257
- const floatCornerPos = vue.computed(() => getFloatCornerPos(floatAnchor.value));
258
- const handleFloatDragStart = (e) => {
259
- floatDragging.value = true;
260
- const pos = "touches" in e ? e.touches[0] : e;
261
- floatStartPos.value = { x: pos.clientX, y: pos.clientY };
262
- floatDragOffset.value = { x: 0, y: 0 };
263
- };
264
- const handleFloatDragMove = (e) => {
265
- if (!floatDragging.value)
266
- return;
267
- e.preventDefault();
268
- const pos = "touches" in e ? e.touches[0] : e;
269
- floatDragOffset.value = {
270
- x: pos.clientX - floatStartPos.value.x,
271
- y: pos.clientY - floatStartPos.value.y
272
- };
273
- floatDisplayPos.value = {
274
- x: floatCornerPos.value.x + floatDragOffset.value.x,
275
- y: floatCornerPos.value.y + floatDragOffset.value.y
276
- };
277
- };
278
- const handleFloatDragEnd = () => {
279
- if (!floatDragging.value)
280
- return;
281
- floatDragging.value = false;
282
- const nearest = findFloatNearestCorner(floatDisplayPos.value.x, floatDisplayPos.value.y);
283
- floatAnchor.value = nearest;
284
- floatDisplayPos.value = getFloatCornerPos(nearest);
285
- floatDragOffset.value = { x: 0, y: 0 };
286
- };
279
+ vue.watch(isFloat, (floating) => {
280
+ if (!floating) {
281
+ floatInitialized.value = false;
282
+ }
283
+ });
284
+ vue.watch(
285
+ [isFloat, () => containerDimensions.value.width, () => containerDimensions.value.height],
286
+ ([floating, w, h2]) => {
287
+ if (floating && w > 0 && h2 > 0 && !floatInitialized.value) {
288
+ const pos = getFloatCornerPos(floatAnchor.value);
289
+ x.set(pos.x);
290
+ y.set(pos.y);
291
+ floatInitialized.value = true;
292
+ }
293
+ },
294
+ { immediate: true }
295
+ );
296
+ vue.watch(
297
+ [floatAnchor, () => containerDimensions.value.width, () => containerDimensions.value.height],
298
+ ([, w, h2]) => {
299
+ if (isFloat.value && floatInitialized.value && w > 0 && h2 > 0) {
300
+ const pos = getFloatCornerPos(floatAnchor.value);
301
+ const springCfg = { type: "spring", stiffness: 400, damping: 30 };
302
+ motionV.animate(x, pos.x, springCfg);
303
+ motionV.animate(y, pos.y, springCfg);
304
+ }
305
+ }
306
+ );
287
307
  const isLastVisibleOther = vue.computed(() => {
288
308
  const lastVisibleOthersIndex = grid.value.getLastVisibleOthersIndex();
289
309
  return props.index === lastVisibleOthersIndex;
290
310
  });
291
311
  const hiddenCount = vue.computed(() => grid.value.hiddenCount);
292
312
  const springConfig = meetLayoutGridCore.getSpringConfig(springPreset);
313
+ const gridX = motionV.useMotionValue(0);
314
+ const gridY = motionV.useMotionValue(0);
315
+ const gridAnimReady = vue.ref(false);
316
+ vue.watch(
317
+ [
318
+ () => position.value.top,
319
+ () => position.value.left,
320
+ isFloat,
321
+ isHidden
322
+ ],
323
+ ([, , floating, hidden]) => {
324
+ if (floating || hidden) {
325
+ gridAnimReady.value = false;
326
+ return;
327
+ }
328
+ const pos = position.value;
329
+ if (!gridAnimReady.value) {
330
+ gridX.set(pos.left);
331
+ gridY.set(pos.top);
332
+ gridAnimReady.value = true;
333
+ } else {
334
+ const cfg = {
335
+ type: "spring",
336
+ stiffness: springConfig.stiffness,
337
+ damping: springConfig.damping
338
+ };
339
+ motionV.animate(gridX, pos.left, cfg);
340
+ motionV.animate(gridY, pos.top, cfg);
341
+ }
342
+ },
343
+ { immediate: true }
344
+ );
293
345
  const slotProps = vue.computed(() => ({
294
346
  contentDimensions: contentDimensions.value,
295
347
  isLastVisibleOther: isLastVisibleOther.value,
296
- hiddenCount: hiddenCount.value
348
+ hiddenCount: hiddenCount.value,
349
+ isFloat: isFloat.value
297
350
  }));
298
351
  return () => {
299
352
  if (isHidden.value) {
@@ -303,88 +356,90 @@ const GridItem = vue.defineComponent({
303
356
  const dims = containerDimensions.value;
304
357
  if (dims.width === 0 || dims.height === 0)
305
358
  return null;
306
- if (!floatInitialized.value) {
307
- floatDisplayPos.value = floatCornerPos.value;
308
- floatInitialized.value = true;
309
- }
359
+ const dragConstraints = {
360
+ left: 12,
361
+ right: dims.width - floatDims.value.width - 12,
362
+ top: 12,
363
+ bottom: dims.height - floatDims.value.height - 12
364
+ };
365
+ const handleDragEnd = () => {
366
+ const currentX = x.get();
367
+ const currentY = y.get();
368
+ const nearestCorner = findFloatNearestCorner(currentX, currentY);
369
+ floatAnchor.value = nearestCorner;
370
+ const snapPos = getFloatCornerPos(nearestCorner);
371
+ const springCfg = { type: "spring", stiffness: 400, damping: 30 };
372
+ motionV.animate(x, snapPos.x, springCfg);
373
+ motionV.animate(y, snapPos.y, springCfg);
374
+ };
310
375
  return vue.h(
311
376
  motionV.motion.div,
312
377
  {
313
- animate: {
314
- x: floatDisplayPos.value.x,
315
- y: floatDisplayPos.value.y,
316
- opacity: 1,
317
- scale: floatDragging.value ? 1.05 : 1
318
- },
319
- transition: floatDragging.value ? { duration: 0 } : { type: "spring", stiffness: 400, damping: 30 },
378
+ // Key forces Vue to recreate this element when switching float↔grid
379
+ key: `float-${props.index}`,
380
+ drag: true,
381
+ dragMomentum: false,
382
+ dragElastic: 0.1,
383
+ dragConstraints,
320
384
  style: {
321
385
  position: "absolute",
322
386
  width: `${floatDims.value.width}px`,
323
387
  height: `${floatDims.value.height}px`,
324
388
  borderRadius: "12px",
325
- boxShadow: floatDragging.value ? "0 8px 32px rgba(0,0,0,0.4)" : "0 4px 20px rgba(0,0,0,0.3)",
389
+ boxShadow: "0 4px 20px rgba(0,0,0,0.3)",
326
390
  overflow: "hidden",
327
- cursor: floatDragging.value ? "grabbing" : "grab",
391
+ cursor: "grab",
328
392
  zIndex: 100,
329
393
  touchAction: "none",
330
394
  left: 0,
331
- top: 0
395
+ top: 0,
396
+ x,
397
+ y
332
398
  },
399
+ whileDrag: { cursor: "grabbing", scale: 1.05, boxShadow: "0 8px 32px rgba(0,0,0,0.4)" },
400
+ transition: { type: "spring", stiffness: 400, damping: 30 },
333
401
  "data-grid-index": props.index,
334
402
  "data-grid-float": true,
335
- onMousedown: handleFloatDragStart,
336
- onMousemove: handleFloatDragMove,
337
- onMouseup: handleFloatDragEnd,
338
- onMouseleave: handleFloatDragEnd,
339
- onTouchstart: handleFloatDragStart,
340
- onTouchmove: handleFloatDragMove,
341
- onTouchend: handleFloatDragEnd
403
+ onDragEnd: handleDragEnd
342
404
  },
343
405
  () => slots.default?.(slotProps.value)
344
406
  );
345
407
  }
346
- const animateProps = {
347
- width: dimensions.value.width,
348
- height: dimensions.value.height,
349
- top: position.value.top,
350
- left: position.value.left
351
- };
408
+ const itemWidth = dimensions.value.width;
409
+ const itemHeight = dimensions.value.height;
352
410
  if (props.disableAnimation) {
353
411
  return vue.h(
354
412
  props.tag,
355
413
  {
356
414
  style: {
357
415
  position: "absolute",
358
- ...animateProps,
359
- width: `${animateProps.width}px`,
360
- height: `${animateProps.height}px`,
361
- top: `${animateProps.top}px`,
362
- left: `${animateProps.left}px`
416
+ width: `${itemWidth}px`,
417
+ height: `${itemHeight}px`,
418
+ top: `${position.value.top}px`,
419
+ left: `${position.value.left}px`
363
420
  },
364
421
  "data-grid-index": props.index,
365
422
  "data-grid-main": isMain.value
366
423
  },
367
- // Pass all slot props
368
424
  slots.default?.(slotProps.value)
369
425
  );
370
426
  }
371
427
  return vue.h(
372
428
  motionV.motion.div,
373
429
  {
374
- as: props.tag,
375
- animate: animateProps,
376
- transition: {
377
- type: springConfig.type,
378
- stiffness: springConfig.stiffness,
379
- damping: springConfig.damping
380
- },
430
+ key: `grid-${props.index}`,
381
431
  style: {
382
- position: "absolute"
432
+ position: "absolute",
433
+ top: 0,
434
+ left: 0,
435
+ x: gridX,
436
+ y: gridY,
437
+ width: `${itemWidth}px`,
438
+ height: `${itemHeight}px`
383
439
  },
384
440
  "data-grid-index": props.index,
385
441
  "data-grid-main": isMain.value
386
442
  },
387
- // Pass all slot props
388
443
  () => slots.default?.(slotProps.value)
389
444
  );
390
445
  };
@@ -429,16 +484,26 @@ const GridOverlay = vue.defineComponent({
429
484
  const FloatingGridItem = vue.defineComponent({
430
485
  name: "FloatingGridItem",
431
486
  props: {
432
- /** Width of the floating item */
487
+ /** Width of the floating item (px). Overridden by `breakpoints` when provided. */
433
488
  width: {
434
489
  type: Number,
435
490
  default: 120
436
491
  },
437
- /** Height of the floating item */
492
+ /** Height of the floating item (px). Overridden by `breakpoints` when provided. */
438
493
  height: {
439
494
  type: Number,
440
495
  default: 160
441
496
  },
497
+ /**
498
+ * Responsive breakpoints for PiP sizing.
499
+ * When provided, width/height auto-adjust based on container width.
500
+ * Overrides the fixed `width`/`height` props.
501
+ * Use `DEFAULT_FLOAT_BREAKPOINTS` for a ready-made 5-level responsive config.
502
+ */
503
+ breakpoints: {
504
+ type: Array,
505
+ default: void 0
506
+ },
442
507
  /** Initial position (x, y from container edges) */
443
508
  initialPosition: {
444
509
  type: Object,
@@ -479,10 +544,14 @@ const FloatingGridItem = vue.defineComponent({
479
544
  }
480
545
  const { dimensions } = context;
481
546
  const currentAnchor = vue.ref(props.anchor);
482
- const isDragging = vue.ref(false);
483
- const dragOffset = vue.ref({ x: 0, y: 0 });
484
- const startPos = vue.ref({ x: 0, y: 0 });
485
- const displayPosition = vue.ref({ x: 0, y: 0 });
547
+ const effectiveSize = vue.computed(() => {
548
+ if (props.breakpoints && props.breakpoints.length > 0 && dimensions.value.width > 0) {
549
+ return meetLayoutGridCore.resolveFloatSize(dimensions.value.width, props.breakpoints);
550
+ }
551
+ return { width: props.width, height: props.height };
552
+ });
553
+ const x = motionV.useMotionValue(0);
554
+ const y = motionV.useMotionValue(0);
486
555
  const isInitialized = vue.ref(false);
487
556
  const containerDimensions = vue.computed(() => ({
488
557
  width: dimensions.value.width,
@@ -491,21 +560,23 @@ const FloatingGridItem = vue.defineComponent({
491
560
  const getCornerPosition = (corner) => {
492
561
  const padding = props.edgePadding + props.initialPosition.x;
493
562
  const dims = containerDimensions.value;
563
+ const ew = effectiveSize.value.width;
564
+ const eh = effectiveSize.value.height;
494
565
  switch (corner) {
495
566
  case "top-left":
496
567
  return { x: padding, y: padding };
497
568
  case "top-right":
498
- return { x: dims.width - props.width - padding, y: padding };
569
+ return { x: dims.width - ew - padding, y: padding };
499
570
  case "bottom-left":
500
- return { x: padding, y: dims.height - props.height - padding };
571
+ return { x: padding, y: dims.height - eh - padding };
501
572
  case "bottom-right":
502
573
  default:
503
- return { x: dims.width - props.width - padding, y: dims.height - props.height - padding };
574
+ return { x: dims.width - ew - padding, y: dims.height - eh - padding };
504
575
  }
505
576
  };
506
- const findNearestCorner = (x, y) => {
507
- const centerX = x + props.width / 2;
508
- const centerY = y + props.height / 2;
577
+ const findNearestCorner = (posX, posY) => {
578
+ const centerX = posX + effectiveSize.value.width / 2;
579
+ const centerY = posY + effectiveSize.value.height / 2;
509
580
  const dims = containerDimensions.value;
510
581
  const containerCenterX = dims.width / 2;
511
582
  const containerCenterY = dims.height / 2;
@@ -519,78 +590,95 @@ const FloatingGridItem = vue.defineComponent({
519
590
  return "bottom-left";
520
591
  return "bottom-right";
521
592
  };
522
- const currentPos = vue.computed(() => getCornerPosition(currentAnchor.value));
523
- const handleDragStart = (e) => {
524
- isDragging.value = true;
525
- const pos = "touches" in e ? e.touches[0] : e;
526
- startPos.value = { x: pos.clientX, y: pos.clientY };
527
- dragOffset.value = { x: 0, y: 0 };
528
- };
529
- const handleDragMove = (e) => {
530
- if (!isDragging.value)
531
- return;
532
- e.preventDefault();
533
- const pos = "touches" in e ? e.touches[0] : e;
534
- dragOffset.value = {
535
- x: pos.clientX - startPos.value.x,
536
- y: pos.clientY - startPos.value.y
537
- };
538
- displayPosition.value = {
539
- x: currentPos.value.x + dragOffset.value.x,
540
- y: currentPos.value.y + dragOffset.value.y
541
- };
542
- };
543
- const handleDragEnd = () => {
544
- if (!isDragging.value)
545
- return;
546
- isDragging.value = false;
547
- const finalX = displayPosition.value.x;
548
- const finalY = displayPosition.value.y;
549
- const nearestCorner = findNearestCorner(finalX, finalY);
550
- currentAnchor.value = nearestCorner;
551
- emit("anchorChange", nearestCorner);
552
- displayPosition.value = getCornerPosition(nearestCorner);
553
- dragOffset.value = { x: 0, y: 0 };
554
- };
593
+ vue.watch(
594
+ [() => containerDimensions.value.width, () => containerDimensions.value.height],
595
+ ([w, h2]) => {
596
+ if (w > 0 && h2 > 0 && !isInitialized.value) {
597
+ const pos = getCornerPosition(currentAnchor.value);
598
+ x.set(pos.x);
599
+ y.set(pos.y);
600
+ isInitialized.value = true;
601
+ }
602
+ },
603
+ { immediate: true }
604
+ );
605
+ vue.watch(
606
+ [
607
+ () => props.anchor,
608
+ () => containerDimensions.value.width,
609
+ () => containerDimensions.value.height
610
+ ],
611
+ ([newAnchor, w, h2]) => {
612
+ if (isInitialized.value && w > 0 && h2 > 0 && newAnchor !== currentAnchor.value) {
613
+ currentAnchor.value = newAnchor;
614
+ const pos = getCornerPosition(newAnchor);
615
+ const springCfg = { type: "spring", stiffness: 400, damping: 30 };
616
+ motionV.animate(x, pos.x, springCfg);
617
+ motionV.animate(y, pos.y, springCfg);
618
+ }
619
+ }
620
+ );
621
+ vue.watch(
622
+ [() => effectiveSize.value.width, () => effectiveSize.value.height],
623
+ () => {
624
+ if (isInitialized.value && containerDimensions.value.width > 0 && containerDimensions.value.height > 0) {
625
+ const pos = getCornerPosition(currentAnchor.value);
626
+ const springCfg = { type: "spring", stiffness: 400, damping: 30 };
627
+ motionV.animate(x, pos.x, springCfg);
628
+ motionV.animate(y, pos.y, springCfg);
629
+ }
630
+ }
631
+ );
555
632
  return () => {
556
633
  const dims = containerDimensions.value;
557
634
  if (!props.visible || dims.width === 0 || dims.height === 0) {
558
635
  return null;
559
636
  }
560
- if (!isInitialized.value) {
561
- displayPosition.value = currentPos.value;
562
- isInitialized.value = true;
563
- }
637
+ const ew = effectiveSize.value.width;
638
+ const eh = effectiveSize.value.height;
639
+ const padding = props.edgePadding + props.initialPosition.x;
640
+ const dragConstraints = {
641
+ left: padding,
642
+ right: dims.width - ew - padding,
643
+ top: padding,
644
+ bottom: dims.height - eh - padding
645
+ };
646
+ const handleDragEnd = () => {
647
+ const currentX = x.get();
648
+ const currentY = y.get();
649
+ const nearestCorner = findNearestCorner(currentX, currentY);
650
+ currentAnchor.value = nearestCorner;
651
+ emit("anchorChange", nearestCorner);
652
+ const snapPos = getCornerPosition(nearestCorner);
653
+ const springCfg = { type: "spring", stiffness: 400, damping: 30 };
654
+ motionV.animate(x, snapPos.x, springCfg);
655
+ motionV.animate(y, snapPos.y, springCfg);
656
+ };
564
657
  return vue.h(
565
658
  motionV.motion.div,
566
659
  {
567
- animate: {
568
- x: displayPosition.value.x,
569
- y: displayPosition.value.y,
570
- opacity: 1,
571
- scale: isDragging.value ? 1.05 : 1
572
- },
573
- transition: isDragging.value ? { duration: 0 } : { type: "spring", stiffness: 400, damping: 30 },
660
+ drag: true,
661
+ dragMomentum: false,
662
+ dragElastic: 0.1,
663
+ dragConstraints,
574
664
  style: {
575
665
  position: "absolute",
576
- width: `${props.width}px`,
577
- height: `${props.height}px`,
666
+ width: `${ew}px`,
667
+ height: `${eh}px`,
578
668
  borderRadius: `${props.borderRadius}px`,
579
- boxShadow: isDragging.value ? "0 8px 32px rgba(0,0,0,0.4)" : props.boxShadow,
669
+ boxShadow: props.boxShadow,
580
670
  overflow: "hidden",
581
- cursor: isDragging.value ? "grabbing" : "grab",
671
+ cursor: "grab",
582
672
  zIndex: 100,
583
673
  touchAction: "none",
584
674
  left: 0,
585
- top: 0
675
+ top: 0,
676
+ x,
677
+ y
586
678
  },
587
- onMousedown: handleDragStart,
588
- onMousemove: handleDragMove,
589
- onMouseup: handleDragEnd,
590
- onMouseleave: handleDragEnd,
591
- onTouchstart: handleDragStart,
592
- onTouchmove: handleDragMove,
593
- onTouchend: handleDragEnd
679
+ whileDrag: { cursor: "grabbing", scale: 1.05, boxShadow: "0 8px 32px rgba(0,0,0,0.4)" },
680
+ transition: { type: "spring", stiffness: 400, damping: 30 },
681
+ onDragEnd: handleDragEnd
594
682
  },
595
683
  slots.default?.()
596
684
  );
@@ -598,12 +686,14 @@ const FloatingGridItem = vue.defineComponent({
598
686
  }
599
687
  });
600
688
 
689
+ exports.DEFAULT_FLOAT_BREAKPOINTS = meetLayoutGridCore.DEFAULT_FLOAT_BREAKPOINTS;
601
690
  exports.createGrid = meetLayoutGridCore.createGrid;
602
691
  exports.createGridItemPositioner = meetLayoutGridCore.createGridItemPositioner;
603
692
  exports.createMeetGrid = meetLayoutGridCore.createMeetGrid;
604
693
  exports.getAspectRatio = meetLayoutGridCore.getAspectRatio;
605
694
  exports.getGridItemDimensions = meetLayoutGridCore.getGridItemDimensions;
606
695
  exports.getSpringConfig = meetLayoutGridCore.getSpringConfig;
696
+ exports.resolveFloatSize = meetLayoutGridCore.resolveFloatSize;
607
697
  exports.springPresets = meetLayoutGridCore.springPresets;
608
698
  exports.FloatingGridItem = FloatingGridItem;
609
699
  exports.GridContainer = GridContainer;