react-grid-layout 1.3.4 → 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/lib/utils.js CHANGED
@@ -1,10 +1,21 @@
1
1
  // @flow
2
- import isEqual from "lodash.isequal";
2
+ import { deepEqual } from "fast-equals";
3
3
  import React from "react";
4
4
  import type {
5
5
  ChildrenArray as ReactChildrenArray,
6
6
  Element as ReactElement
7
7
  } from "react";
8
+
9
+ export type ResizeHandleAxis =
10
+ | "s"
11
+ | "w"
12
+ | "e"
13
+ | "n"
14
+ | "sw"
15
+ | "nw"
16
+ | "se"
17
+ | "ne";
18
+
8
19
  export type LayoutItem = {
9
20
  w: number,
10
21
  h: number,
@@ -19,7 +30,7 @@ export type LayoutItem = {
19
30
  static?: boolean,
20
31
  isDraggable?: ?boolean,
21
32
  isResizable?: ?boolean,
22
- resizeHandles?: Array<"s" | "w" | "e" | "n" | "sw" | "nw" | "se" | "ne">,
33
+ resizeHandles?: Array<ResizeHandleAxis>,
23
34
  isBounded?: ?boolean
24
35
  };
25
36
  export type Layout = $ReadOnlyArray<LayoutItem>;
@@ -47,7 +58,12 @@ export type GridDragEvent = {
47
58
  node: HTMLElement,
48
59
  newPosition: PartialPosition
49
60
  };
50
- export type GridResizeEvent = { e: Event, node: HTMLElement, size: Size };
61
+ export type GridResizeEvent = {
62
+ e: Event,
63
+ node: HTMLElement,
64
+ size: Size,
65
+ handle: string
66
+ };
51
67
  export type DragOverEvent = MouseEvent & {
52
68
  nativeEvent: {
53
69
  layerX: number,
@@ -158,9 +174,15 @@ export function cloneLayoutItem(layoutItem: LayoutItem): LayoutItem {
158
174
  * This will catch differences in keys, order, and length.
159
175
  */
160
176
  export function childrenEqual(a: ReactChildren, b: ReactChildren): boolean {
161
- return isEqual(
162
- React.Children.map(a, c => c?.key),
163
- React.Children.map(b, c => c?.key)
177
+ return (
178
+ deepEqual(
179
+ React.Children.map(a, c => c?.key),
180
+ React.Children.map(b, c => c?.key)
181
+ ) &&
182
+ deepEqual(
183
+ React.Children.map(a, c => c?.props["data-grid"]),
184
+ React.Children.map(b, c => c?.props["data-grid"])
185
+ )
164
186
  );
165
187
  }
166
188
 
@@ -206,12 +228,14 @@ export function collides(l1: LayoutItem, l2: LayoutItem): boolean {
206
228
  * @param {Array} layout Layout.
207
229
  * @param {Boolean} verticalCompact Whether or not to compact the layout
208
230
  * vertically.
231
+ * @param {Boolean} allowOverlap When `true`, allows overlapping grid items.
209
232
  * @return {Array} Compacted Layout.
210
233
  */
211
234
  export function compact(
212
235
  layout: Layout,
213
236
  compactType: CompactType,
214
- cols: number
237
+ cols: number,
238
+ allowOverlap: ?boolean
215
239
  ): Layout {
216
240
  // Statics go in the compareWith array right away so items flow around them.
217
241
  const compareWith = getStatics(layout);
@@ -225,7 +249,7 @@ export function compact(
225
249
 
226
250
  // Don't move static elements
227
251
  if (!l.static) {
228
- l = compactItem(compareWith, l, compactType, cols, sorted);
252
+ l = compactItem(compareWith, l, compactType, cols, sorted, allowOverlap);
229
253
 
230
254
  // Add to comparison array. We only collide with items before this one.
231
255
  // Statics are already in this array.
@@ -294,7 +318,8 @@ export function compactItem(
294
318
  l: LayoutItem,
295
319
  compactType: CompactType,
296
320
  cols: number,
297
- fullLayout: Layout
321
+ fullLayout: Layout,
322
+ allowOverlap: ?boolean
298
323
  ): LayoutItem {
299
324
  const compactV = compactType === "vertical";
300
325
  const compactH = compactType === "horizontal";
@@ -316,7 +341,11 @@ export function compactItem(
316
341
 
317
342
  // Move it down, and keep moving it down if it's colliding.
318
343
  let collides;
319
- while ((collides = getFirstCollision(compareWith, l))) {
344
+ // Checking the compactType null value to avoid breaking the layout when overlapping is allowed.
345
+ while (
346
+ (collides = getFirstCollision(compareWith, l)) &&
347
+ !(compactType === null && allowOverlap)
348
+ ) {
320
349
  if (compactH) {
321
350
  resolveCompactionCollision(fullLayout, l, collides.x + collides.w, "x");
322
351
  } else {
@@ -326,6 +355,10 @@ export function compactItem(
326
355
  if (compactH && l.x + l.w > cols) {
327
356
  l.x = cols - l.w;
328
357
  l.y++;
358
+ // ALso move element as left as we can
359
+ while (l.x > 0 && !getFirstCollision(compareWith, l)) {
360
+ l.x--;
361
+ }
329
362
  }
330
363
  }
331
364
 
@@ -541,7 +574,7 @@ export function moveElementAwayFromCollision(
541
574
  ): Layout {
542
575
  const compactH = compactType === "horizontal";
543
576
  // Compact vertically if not set to horizontal
544
- const compactV = compactType !== "horizontal";
577
+ const compactV = compactType === "vertical";
545
578
  const preventCollision = collidesWith.static; // we're already colliding (not for static items)
546
579
 
547
580
  // If there is enough space above the collision to put this element, move it there.
@@ -560,8 +593,14 @@ export function moveElementAwayFromCollision(
560
593
  i: "-1"
561
594
  };
562
595
 
596
+ const firstCollision = getFirstCollision(layout, fakeItem);
597
+ const collisionNorth =
598
+ firstCollision && firstCollision.y + firstCollision.h > collidesWith.y;
599
+ const collisionWest =
600
+ firstCollision && collidesWith.x + collidesWith.w > firstCollision.x;
601
+
563
602
  // No collision? If so, we can go up there; otherwise, we'll end up moving down as normal
564
- if (!getFirstCollision(layout, fakeItem)) {
603
+ if (!firstCollision) {
565
604
  log(
566
605
  `Doing reverse collision on ${itemToMove.i} up to [${fakeItem.x},${fakeItem.y}].`
567
606
  );
@@ -575,9 +614,42 @@ export function moveElementAwayFromCollision(
575
614
  compactType,
576
615
  cols
577
616
  );
617
+ } else if (collisionNorth && compactV) {
618
+ return moveElement(
619
+ layout,
620
+ itemToMove,
621
+ undefined,
622
+ collidesWith.y + 1,
623
+ isUserAction,
624
+ preventCollision,
625
+ compactType,
626
+ cols
627
+ );
628
+ } else if (collisionNorth && compactType == null) {
629
+ collidesWith.y = itemToMove.y;
630
+ itemToMove.y = itemToMove.y + itemToMove.h;
631
+
632
+ return layout;
633
+ } else if (collisionWest && compactH) {
634
+ return moveElement(
635
+ layout,
636
+ collidesWith,
637
+ itemToMove.x,
638
+ undefined,
639
+ isUserAction,
640
+ preventCollision,
641
+ compactType,
642
+ cols
643
+ );
578
644
  }
579
645
  }
580
646
 
647
+ const newX = compactH ? itemToMove.x + 1 : undefined;
648
+ const newY = compactV ? itemToMove.y + 1 : undefined;
649
+
650
+ if (newX == null && newY == null) {
651
+ return layout;
652
+ }
581
653
  return moveElement(
582
654
  layout,
583
655
  itemToMove,
@@ -600,6 +672,126 @@ export function perc(num: number): string {
600
672
  return num * 100 + "%";
601
673
  }
602
674
 
675
+ /**
676
+ * Helper functions to constrain dimensions of a GridItem
677
+ */
678
+ const constrainWidth = (
679
+ left: number,
680
+ currentWidth: number,
681
+ newWidth: number,
682
+ containerWidth: number
683
+ ) => {
684
+ return left + newWidth > containerWidth ? currentWidth : newWidth;
685
+ };
686
+
687
+ const constrainHeight = (
688
+ top: number,
689
+ currentHeight: number,
690
+ newHeight: number
691
+ ) => {
692
+ return top < 0 ? currentHeight : newHeight;
693
+ };
694
+
695
+ const constrainLeft = (left: number) => Math.max(0, left);
696
+
697
+ const constrainTop = (top: number) => Math.max(0, top);
698
+
699
+ const resizeNorth = (currentSize, { left, height, width }, _containerWidth) => {
700
+ const top = currentSize.top - (height - currentSize.height);
701
+
702
+ return {
703
+ left,
704
+ width,
705
+ height: constrainHeight(top, currentSize.height, height),
706
+ top: constrainTop(top)
707
+ };
708
+ };
709
+
710
+ const resizeEast = (
711
+ currentSize,
712
+ { top, left, height, width },
713
+ containerWidth
714
+ ) => ({
715
+ top,
716
+ height,
717
+ width: constrainWidth(
718
+ currentSize.left,
719
+ currentSize.width,
720
+ width,
721
+ containerWidth
722
+ ),
723
+ left: constrainLeft(left)
724
+ });
725
+
726
+ const resizeWest = (currentSize, { top, height, width }, containerWidth) => {
727
+ const left = currentSize.left - (width - currentSize.width);
728
+
729
+ return {
730
+ height,
731
+ width:
732
+ left < 0
733
+ ? currentSize.width
734
+ : constrainWidth(
735
+ currentSize.left,
736
+ currentSize.width,
737
+ width,
738
+ containerWidth
739
+ ),
740
+ top: constrainTop(top),
741
+ left: constrainLeft(left)
742
+ };
743
+ };
744
+
745
+ const resizeSouth = (
746
+ currentSize,
747
+ { top, left, height, width },
748
+ containerWidth
749
+ ) => ({
750
+ width,
751
+ left,
752
+ height: constrainHeight(top, currentSize.height, height),
753
+ top: constrainTop(top)
754
+ });
755
+
756
+ const resizeNorthEast = (...args) =>
757
+ resizeNorth(args[0], resizeEast(...args), args[2]);
758
+ const resizeNorthWest = (...args) =>
759
+ resizeNorth(args[0], resizeWest(...args), args[2]);
760
+ const resizeSouthEast = (...args) =>
761
+ resizeSouth(args[0], resizeEast(...args), args[2]);
762
+ const resizeSouthWest = (...args) =>
763
+ resizeSouth(args[0], resizeWest(...args), args[2]);
764
+
765
+ const ordinalResizeHandlerMap = {
766
+ n: resizeNorth,
767
+ ne: resizeNorthEast,
768
+ e: resizeEast,
769
+ se: resizeSouthEast,
770
+ s: resizeSouth,
771
+ sw: resizeSouthWest,
772
+ w: resizeWest,
773
+ nw: resizeNorthWest
774
+ };
775
+
776
+ /**
777
+ * Helper for clamping width and position when resizing an item.
778
+ */
779
+ export function resizeItemInDirection(
780
+ direction: ResizeHandleAxis,
781
+ currentSize: Position,
782
+ newSize: Position,
783
+ containerWidth: number
784
+ ): Position {
785
+ const ordinalHandler = ordinalResizeHandlerMap[direction];
786
+ // Shouldn't be possible given types; that said, don't fail hard
787
+ if (!ordinalHandler) return newSize;
788
+ return ordinalHandler(
789
+ currentSize,
790
+ { ...currentSize, ...newSize },
791
+ containerWidth
792
+ );
793
+ }
794
+
603
795
  export function setTransform({ top, left, width, height }: Position): Object {
604
796
  // Replace unitless items with px
605
797
  const translate = `translate(${left}px,${top}px)`;
@@ -698,19 +890,13 @@ export function synchronizeLayoutWithChildren(
698
890
  // Child may not exist
699
891
  if (child?.key == null) return;
700
892
 
701
- // Don't overwrite if it already exists.
702
893
  const exists = getLayoutItem(initialLayout, String(child.key));
703
- if (exists) {
894
+ const g = child.props["data-grid"];
895
+ // Don't overwrite the layout item if it's already in the initial layout.
896
+ // If it has a `data-grid` property, prefer that over what's in the layout.
897
+ if (exists && g == null) {
704
898
  layout.push(cloneLayoutItem(exists));
705
899
  } else {
706
- if (!isProduction && child.props._grid) {
707
- console.warn(
708
- "`_grid` properties on children have been deprecated as of React 15.2. " +
709
- "Please use `data-grid` or add your properties directly to the `layout`."
710
- );
711
- }
712
- const g = child.props["data-grid"] || child.props._grid;
713
-
714
900
  // Hey, this item has a data-grid property, use it.
715
901
  if (g) {
716
902
  if (!isProduction) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-grid-layout",
3
- "version": "1.3.4",
3
+ "version": "1.4.1",
4
4
  "description": "A draggable and resizable grid layout with responsive breakpoints, for React.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -37,54 +37,59 @@
37
37
  },
38
38
  "homepage": "https://github.com/STRML/react-grid-layout",
39
39
  "dependencies": {
40
- "clsx": "^1.1.1",
41
- "lodash.isequal": "^4.0.0",
40
+ "clsx": "^2.0.0",
41
+ "fast-equals": "^4.0.3",
42
42
  "prop-types": "^15.8.1",
43
- "react-draggable": "^4.0.0",
44
- "react-resizable": "^3.0.4"
43
+ "react-draggable": "^4.4.5",
44
+ "react-resizable": "^3.0.5",
45
+ "resize-observer-polyfill": "^1.5.1"
46
+ },
47
+ "_dependencyNotes": {
48
+ "fast-equals": "Bug in CRA5 causes fast-equals@5 to fail to import due to .cjs file. See https://github.com/react-grid-layout/react-grid-layout/issues/1904"
45
49
  },
46
50
  "devDependencies": {
47
- "@babel/cli": "^7.17.3",
48
- "@babel/core": "^7.17.5",
49
- "@babel/eslint-parser": "^7.17.0",
50
- "@babel/plugin-proposal-class-properties": "^7.16.7",
51
- "@babel/plugin-transform-flow-comments": "^7.16.7",
52
- "@babel/preset-env": "^7.16.11",
53
- "@babel/preset-flow": "^7.16.7",
54
- "@babel/preset-react": "^7.16.7",
55
- "@babel/register": "^7.17.0",
56
- "@webpack-cli/serve": "^1.6.1",
57
- "babel-jest": "^27.5.1",
58
- "babel-loader": "^8.2.2",
51
+ "@babel/cli": "^7.22.15",
52
+ "@babel/core": "^7.22.17",
53
+ "@babel/eslint-parser": "^7.22.15",
54
+ "@babel/plugin-proposal-class-properties": "^7.18.6",
55
+ "@babel/plugin-transform-flow-comments": "^7.22.10",
56
+ "@babel/preset-env": "^7.22.15",
57
+ "@babel/preset-flow": "^7.22.15",
58
+ "@babel/preset-react": "^7.22.15",
59
+ "@babel/register": "^7.22.15",
60
+ "@webpack-cli/serve": "^2.0.5",
61
+ "babel-jest": "^29.6.4",
62
+ "babel-loader": "^9.1.3",
59
63
  "babel-plugin-preval": "^5.1.0",
60
- "css-loader": "^6.6.0",
61
- "ejs": "^3.1.6",
64
+ "css-loader": "^6.8.1",
65
+ "ejs": "^3.1.9",
62
66
  "enzyme": "^3.11.0",
63
- "enzyme-adapter-react-16": "^1.15.6",
67
+ "enzyme-adapter-react-16": "^1.15.7",
64
68
  "enzyme-to-json": "^3.6.2",
65
- "eslint": "^8.9.0",
69
+ "eslint": "^8.49.0",
66
70
  "eslint-plugin-flowtype": "^8.0.3",
67
- "eslint-plugin-mocha": "^10.0.3",
68
- "eslint-plugin-react": "^7.28.0",
69
- "eslint-plugin-unicorn": "^41.0.0",
70
- "exports-loader": "^3.0.0",
71
+ "eslint-plugin-mocha": "^10.1.0",
72
+ "eslint-plugin-react": "^7.33.2",
73
+ "eslint-plugin-unicorn": "^48.0.1",
74
+ "exports-loader": "^4.0.0",
71
75
  "flow-bin": "^0.172.0",
72
- "husky": "^7.0.2",
73
- "imports-loader": "^3.0.0",
74
- "jest": "^27.5.1",
75
- "lint-staged": "^12.3.4",
76
+ "husky": "^8.0.3",
77
+ "imports-loader": "^4.0.1",
78
+ "jest": "^29.6.4",
79
+ "jest-environment-jsdom": "^29.6.4",
80
+ "lint-staged": "^14.0.1",
76
81
  "lodash": "^4.17.21",
77
82
  "opener": "^1.5.2",
78
- "prettier": "^2.3.2",
83
+ "prettier": "^3.0.3",
79
84
  "react": "^16.13.1",
80
85
  "react-dom": "^16.13.1",
81
- "react-hot-loader": "^4.13.0",
86
+ "react-hot-loader": "^4.13.1",
82
87
  "react-transform-hmr": "^1.0.2",
83
- "style-loader": "^3.2.1",
88
+ "style-loader": "^3.3.3",
84
89
  "timsort": "^0.3.0",
85
- "webpack": "^5.69.1",
86
- "webpack-cli": "^4.9.2",
87
- "webpack-dev-server": "^4.7.4"
90
+ "webpack": "^5.88.2",
91
+ "webpack-cli": "^5.1.4",
92
+ "webpack-dev-server": "^4.15.1"
88
93
  },
89
94
  "peerDependencies": {
90
95
  "react": ">= 16.3.0",
@@ -104,7 +109,9 @@
104
109
  "<rootDir>/test/spec/*.js"
105
110
  ],
106
111
  "testEnvironment": "jsdom",
107
- "testURL": "http://localhost",
112
+ "testEnvironmentOptions": {
113
+ "url": "http://localhost"
114
+ },
108
115
  "coverageThreshold": {
109
116
  "global": {
110
117
  "statements": 77,