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/GridItem.jsx CHANGED
@@ -3,7 +3,13 @@ import React from "react";
3
3
  import PropTypes from "prop-types";
4
4
  import { DraggableCore } from "react-draggable";
5
5
  import { Resizable } from "react-resizable";
6
- import { fastPositionEqual, perc, setTopLeft, setTransform } from "./utils";
6
+ import {
7
+ fastPositionEqual,
8
+ perc,
9
+ resizeItemInDirection,
10
+ setTopLeft,
11
+ setTransform
12
+ } from "./utils";
7
13
  import {
8
14
  calcGridItemPosition,
9
15
  calcGridItemWHPx,
@@ -24,15 +30,12 @@ import type {
24
30
  GridDragEvent,
25
31
  GridResizeEvent,
26
32
  DroppingPosition,
27
- Position
33
+ Position,
34
+ ResizeHandleAxis
28
35
  } from "./utils";
29
36
 
30
37
  import type { PositionParams } from "./calculateUtils";
31
- import type {
32
- ResizeHandleAxis,
33
- ResizeHandle,
34
- ReactRef
35
- } from "./ReactGridLayoutPropTypes";
38
+ import type { ResizeHandle, ReactRef } from "./ReactGridLayoutPropTypes";
36
39
 
37
40
  type PartialPosition = { top: number, left: number };
38
41
  type GridItemCallback<Data: GridDragEvent | GridResizeEvent> = (
@@ -42,8 +45,20 @@ type GridItemCallback<Data: GridDragEvent | GridResizeEvent> = (
42
45
  Data
43
46
  ) => void;
44
47
 
48
+ type ResizeCallbackData = {
49
+ node: HTMLElement,
50
+ size: Position,
51
+ handle: ResizeHandleAxis
52
+ };
53
+
54
+ type GridItemResizeCallback = (
55
+ e: Event,
56
+ data: ResizeCallbackData,
57
+ position: Position
58
+ ) => void;
59
+
45
60
  type State = {
46
- resizing: ?{ width: number, height: number },
61
+ resizing: ?{ top: number, left: number, width: number, height: number },
47
62
  dragging: ?{ top: number, left: number },
48
63
  className: string
49
64
  };
@@ -356,6 +371,15 @@ export default class GridItem extends React.Component<Props, State> {
356
371
  );
357
372
  }
358
373
 
374
+ /**
375
+ * Utility function to setup callback handler definitions for
376
+ * similarily structured resize events.
377
+ */
378
+ curryResizeHandler(position: Position, handler: Function): Function {
379
+ return (e: Event, data: ResizeCallbackData): Function =>
380
+ handler(e, data, position);
381
+ }
382
+
359
383
  /**
360
384
  * Mix a Resizable instance into a child.
361
385
  * @param {Element} child Child element.
@@ -369,7 +393,6 @@ export default class GridItem extends React.Component<Props, State> {
369
393
  ): ReactElement<any> {
370
394
  const {
371
395
  cols,
372
- x,
373
396
  minW,
374
397
  minH,
375
398
  maxW,
@@ -381,13 +404,7 @@ export default class GridItem extends React.Component<Props, State> {
381
404
  const positionParams = this.getPositionParams();
382
405
 
383
406
  // This is the max possible width - doesn't go to infinity because of the width of the window
384
- const maxWidth = calcGridItemPosition(
385
- positionParams,
386
- 0,
387
- 0,
388
- cols - x,
389
- 0
390
- ).width;
407
+ const maxWidth = calcGridItemPosition(positionParams, 0, 0, cols, 0).width;
391
408
 
392
409
  // Calculate min/max constraints using our min & maxes
393
410
  const mins = calcGridItemPosition(positionParams, 0, 0, minW, minH);
@@ -408,9 +425,9 @@ export default class GridItem extends React.Component<Props, State> {
408
425
  height={position.height}
409
426
  minConstraints={minConstraints}
410
427
  maxConstraints={maxConstraints}
411
- onResizeStop={this.onResizeStop}
412
- onResizeStart={this.onResizeStart}
413
- onResize={this.onResize}
428
+ onResizeStop={this.curryResizeHandler(position, this.onResizeStop)}
429
+ onResizeStart={this.curryResizeHandler(position, this.onResizeStart)}
430
+ onResize={this.curryResizeHandler(position, this.onResize)}
414
431
  transformScale={transformScale}
415
432
  resizeHandles={resizeHandles}
416
433
  handle={resizeHandle}
@@ -541,77 +558,61 @@ export default class GridItem extends React.Component<Props, State> {
541
558
  * @param {Event} e event data
542
559
  * @param {Object} callbackData an object with node and size information
543
560
  */
544
- onResizeStop: (Event, { node: HTMLElement, size: Position }) => void = (
545
- e,
546
- callbackData
547
- ) => {
548
- this.onResizeHandler(e, callbackData, "onResizeStop");
549
- };
561
+ onResizeStop: GridItemResizeCallback = (e, callbackData, position) =>
562
+ this.onResizeHandler(e, callbackData, position, "onResizeStop");
550
563
 
551
- /**
552
- * onResizeStart event handler
553
- * @param {Event} e event data
554
- * @param {Object} callbackData an object with node and size information
555
- */
556
- onResizeStart: (Event, { node: HTMLElement, size: Position }) => void = (
557
- e,
558
- callbackData
559
- ) => {
560
- this.onResizeHandler(e, callbackData, "onResizeStart");
561
- };
564
+ // onResizeStart event handler
565
+ onResizeStart: GridItemResizeCallback = (e, callbackData, position) =>
566
+ this.onResizeHandler(e, callbackData, position, "onResizeStart");
562
567
 
563
- /**
564
- * onResize event handler
565
- * @param {Event} e event data
566
- * @param {Object} callbackData an object with node and size information
567
- */
568
- onResize: (Event, { node: HTMLElement, size: Position }) => void = (
569
- e,
570
- callbackData
571
- ) => {
572
- this.onResizeHandler(e, callbackData, "onResize");
573
- };
568
+ // onResize event handler
569
+ onResize: GridItemResizeCallback = (e, callbackData, position) =>
570
+ this.onResizeHandler(e, callbackData, position, "onResize");
574
571
 
575
572
  /**
576
- * Wrapper around drag events to provide more useful data.
577
- * All drag events call the function with the given handler name,
578
- * with the signature (index, x, y).
579
- *
580
- * @param {String} handlerName Handler name to wrap.
581
- * @return {Function} Handler function.
573
+ * Wrapper around resize events to provide more useful data.
582
574
  */
583
575
  onResizeHandler(
584
576
  e: Event,
585
- { node, size }: { node: HTMLElement, size: Position },
577
+ { node, size, handle }: ResizeCallbackData, // 'size' is updated position
578
+ position: Position, // existing position
586
579
  handlerName: string
587
580
  ): void {
588
581
  const handler = this.props[handlerName];
589
582
  if (!handler) return;
590
- const { cols, x, y, i, maxH, minH } = this.props;
591
- let { minW, maxW } = this.props;
583
+ const { x, y, i, maxH, minH, containerWidth } = this.props;
584
+ const { minW, maxW } = this.props;
585
+
586
+ // Clamping of dimensions based on resize direction
587
+ let updatedSize = size;
588
+ if (node) {
589
+ updatedSize = resizeItemInDirection(
590
+ handle,
591
+ position,
592
+ size,
593
+ containerWidth
594
+ );
595
+ this.setState({
596
+ resizing: handlerName === "onResizeStop" ? null : updatedSize
597
+ });
598
+ }
592
599
 
593
- // Get new XY
600
+ // Get new XY based on pixel size
594
601
  let { w, h } = calcWH(
595
602
  this.getPositionParams(),
596
- size.width,
597
- size.height,
603
+ updatedSize.width,
604
+ updatedSize.height,
598
605
  x,
599
- y
606
+ y,
607
+ handle
600
608
  );
601
609
 
610
+ // Min/max capping.
602
611
  // minW should be at least 1 (TODO propTypes validation?)
603
- minW = Math.max(minW, 1);
604
-
605
- // maxW should be at most (cols - x)
606
- maxW = Math.min(maxW, cols - x);
607
-
608
- // Min/max capping
609
- w = clamp(w, minW, maxW);
612
+ w = clamp(w, Math.max(minW, 1), maxW);
610
613
  h = clamp(h, minH, maxH);
611
614
 
612
- this.setState({ resizing: handlerName === "onResizeStop" ? null : size });
613
-
614
- handler.call(this, i, w, h, { e, node, size });
615
+ handler.call(this, i, w, h, { e, node, size: updatedSize, handle });
615
616
  }
616
617
 
617
618
  render(): ReactNode {
@@ -1,7 +1,7 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
 
4
- import isEqual from "lodash.isequal";
4
+ import { deepEqual } from "fast-equals";
5
5
  import clsx from "clsx";
6
6
  import {
7
7
  bottom,
@@ -47,6 +47,7 @@ type State = {
47
47
  oldDragItem: ?LayoutItem,
48
48
  oldLayout: ?Layout,
49
49
  oldResizeItem: ?LayoutItem,
50
+ resizing: boolean,
50
51
  droppingDOMNode: ?ReactElement<any>,
51
52
  droppingPosition?: DroppingPosition,
52
53
  // Mirrored props
@@ -132,6 +133,7 @@ export default class ReactGridLayout extends React.Component<Props, State> {
132
133
  oldDragItem: null,
133
134
  oldLayout: null,
134
135
  oldResizeItem: null,
136
+ resizing: false,
135
137
  droppingDOMNode: null,
136
138
  children: []
137
139
  };
@@ -158,7 +160,7 @@ export default class ReactGridLayout extends React.Component<Props, State> {
158
160
  // Legacy support for compactType
159
161
  // Allow parent to set layout directly.
160
162
  if (
161
- !isEqual(nextProps.layout, prevState.propsLayout) ||
163
+ !deepEqual(nextProps.layout, prevState.propsLayout) ||
162
164
  nextProps.compactType !== prevState.compactType
163
165
  ) {
164
166
  newLayoutBase = nextProps.layout;
@@ -198,7 +200,7 @@ export default class ReactGridLayout extends React.Component<Props, State> {
198
200
  // from SCU is if the user intentionally memoizes children. If they do, and they can
199
201
  // handle changes properly, performance will increase.
200
202
  this.props.children !== nextProps.children ||
201
- !fastRGLPropsEqual(this.props, nextProps, isEqual) ||
203
+ !fastRGLPropsEqual(this.props, nextProps, deepEqual) ||
202
204
  this.state.activeDrag !== nextState.activeDrag ||
203
205
  this.state.mounted !== nextState.mounted ||
204
206
  this.state.droppingPosition !== nextState.droppingPosition
@@ -348,12 +350,13 @@ export default class ReactGridLayout extends React.Component<Props, State> {
348
350
  allowOverlap
349
351
  );
350
352
 
351
- this.props.onDragStop(layout, oldDragItem, l, null, e, node);
352
-
353
353
  // Set state
354
354
  const newLayout = allowOverlap
355
355
  ? layout
356
356
  : compact(layout, compactType(this.props), cols);
357
+
358
+ this.props.onDragStop(newLayout, oldDragItem, l, null, e, node);
359
+
357
360
  const { oldLayout } = this.state;
358
361
  this.setState({
359
362
  activeDrag: null,
@@ -368,7 +371,7 @@ export default class ReactGridLayout extends React.Component<Props, State> {
368
371
  onLayoutMaybeChanged(newLayout: Layout, oldLayout: ?Layout) {
369
372
  if (!oldLayout) oldLayout = this.state.layout;
370
373
 
371
- if (!isEqual(oldLayout, newLayout)) {
374
+ if (!deepEqual(oldLayout, newLayout)) {
372
375
  this.props.onLayoutChange(newLayout);
373
376
  }
374
377
  }
@@ -385,7 +388,8 @@ export default class ReactGridLayout extends React.Component<Props, State> {
385
388
 
386
389
  this.setState({
387
390
  oldResizeItem: cloneLayoutItem(l),
388
- oldLayout: this.state.layout
391
+ oldLayout: this.state.layout,
392
+ resizing: true
389
393
  });
390
394
 
391
395
  this.props.onResizeStart(layout, l, l, null, e, node);
@@ -395,9 +399,10 @@ export default class ReactGridLayout extends React.Component<Props, State> {
395
399
  i,
396
400
  w,
397
401
  h,
398
- { e, node }
402
+ { e, node, size, handle }
399
403
  ) => {
400
- const { layout, oldResizeItem } = this.state;
404
+ const { oldResizeItem } = this.state;
405
+ let { layout } = this.state;
401
406
  const { cols, preventCollision, allowOverlap } = this.props;
402
407
 
403
408
  const [newLayout, l] = withLayoutItem(layout, i, l => {
@@ -425,18 +430,49 @@ export default class ReactGridLayout extends React.Component<Props, State> {
425
430
  }
426
431
  }
427
432
 
428
- if (!hasCollisions) {
429
- // Set new width and height.
430
- l.w = w;
431
- l.h = h;
432
- }
433
-
434
433
  return l;
435
434
  });
436
435
 
437
436
  // Shouldn't ever happen, but typechecking makes it necessary
438
437
  if (!l) return;
439
438
 
439
+ let finalLayout;
440
+ if (["sw", "w", "nw", "n", "ne"].indexOf(handle) !== -1) {
441
+ let x = l.x;
442
+ let y = l.y;
443
+ if (["sw", "nw", "w"].indexOf(handle) !== -1) {
444
+ x = l.x + (l.w - w);
445
+ w = l.x !== x && x < 0 ? l.w : w;
446
+ x = x < 0 ? 0 : x;
447
+ }
448
+
449
+ if (["ne", "n", "nw"].indexOf(handle) !== -1) {
450
+ y = l.y + (l.h - h);
451
+ h = l.y !== y && y < 0 ? l.h : h;
452
+ y = y < 0 ? 0 : y;
453
+ }
454
+
455
+ l.w = w;
456
+ l.h = h;
457
+ // Move the element to the new position.
458
+ const isUserAction = true;
459
+ finalLayout = moveElement(
460
+ newLayout,
461
+ l,
462
+ x,
463
+ y,
464
+ isUserAction,
465
+ this.props.preventCollision,
466
+ compactType(this.props),
467
+ cols,
468
+ allowOverlap
469
+ );
470
+ } else {
471
+ l.w = w;
472
+ l.h = h;
473
+ finalLayout = newLayout;
474
+ }
475
+
440
476
  // Create placeholder element (display only)
441
477
  const placeholder = {
442
478
  w: l.w,
@@ -447,13 +483,13 @@ export default class ReactGridLayout extends React.Component<Props, State> {
447
483
  i: i
448
484
  };
449
485
 
450
- this.props.onResize(newLayout, oldResizeItem, l, placeholder, e, node);
486
+ this.props.onResize(finalLayout, oldResizeItem, l, placeholder, e, node);
451
487
 
452
488
  // Re-compact the newLayout and set the drag placeholder.
453
489
  this.setState({
454
490
  layout: allowOverlap
455
- ? newLayout
456
- : compact(newLayout, compactType(this.props), cols),
491
+ ? finalLayout
492
+ : compact(finalLayout, compactType(this.props), cols),
457
493
  activeDrag: placeholder
458
494
  });
459
495
  };
@@ -468,18 +504,20 @@ export default class ReactGridLayout extends React.Component<Props, State> {
468
504
  const { cols, allowOverlap } = this.props;
469
505
  const l = getLayoutItem(layout, i);
470
506
 
471
- this.props.onResizeStop(layout, oldResizeItem, l, null, e, node);
472
-
473
507
  // Set state
474
508
  const newLayout = allowOverlap
475
509
  ? layout
476
510
  : compact(layout, compactType(this.props), cols);
511
+
512
+ this.props.onResizeStop(newLayout, oldResizeItem, l, null, e, node);
513
+
477
514
  const { oldLayout } = this.state;
478
515
  this.setState({
479
516
  activeDrag: null,
480
517
  layout: newLayout,
481
518
  oldResizeItem: null,
482
- oldLayout: null
519
+ oldLayout: null,
520
+ resizing: false
483
521
  });
484
522
 
485
523
  this.onLayoutMaybeChanged(newLayout, oldLayout);
@@ -511,7 +549,9 @@ export default class ReactGridLayout extends React.Component<Props, State> {
511
549
  x={activeDrag.x}
512
550
  y={activeDrag.y}
513
551
  i={activeDrag.i}
514
- className="react-grid-placeholder"
552
+ className={`react-grid-placeholder ${
553
+ this.state.resizing ? "placeholder-resizing" : ""
554
+ }`}
515
555
  containerWidth={width}
516
556
  cols={cols}
517
557
  margin={margin}
@@ -713,7 +753,8 @@ export default class ReactGridLayout extends React.Component<Props, State> {
713
753
  const newLayout = compact(
714
754
  layout.filter(l => l.i !== droppingItem.i),
715
755
  compactType(this.props),
716
- cols
756
+ cols,
757
+ this.props.allowOverlap
717
758
  );
718
759
 
719
760
  this.setState({
@@ -11,7 +11,8 @@ import type {
11
11
  EventCallback,
12
12
  CompactType,
13
13
  Layout,
14
- LayoutItem
14
+ LayoutItem,
15
+ ResizeHandleAxis
15
16
  } from "./utils";
16
17
 
17
18
  // util
@@ -19,15 +20,6 @@ export type ReactRef<T: HTMLElement> = {|
19
20
  +current: T | null
20
21
  |};
21
22
 
22
- export type ResizeHandleAxis =
23
- | "s"
24
- | "w"
25
- | "e"
26
- | "n"
27
- | "sw"
28
- | "nw"
29
- | "se"
30
- | "ne";
31
23
  export type ResizeHandle =
32
24
  | ReactElement<any>
33
25
  | ((
@@ -1,7 +1,7 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
  import PropTypes from "prop-types";
4
- import isEqual from "lodash.isequal";
4
+ import { deepEqual } from "fast-equals";
5
5
 
6
6
  import {
7
7
  cloneLayout,
@@ -203,7 +203,7 @@ export default class ResponsiveReactGridLayout extends React.Component<
203
203
  nextProps: Props<*>,
204
204
  prevState: State
205
205
  ): ?$Shape<State> {
206
- if (!isEqual(nextProps.layouts, prevState.layouts)) {
206
+ if (!deepEqual(nextProps.layouts, prevState.layouts)) {
207
207
  // Allow parent to set layouts directly.
208
208
  const { breakpoint, cols } = prevState;
209
209
 
@@ -228,8 +228,8 @@ export default class ResponsiveReactGridLayout extends React.Component<
228
228
  if (
229
229
  this.props.width != prevProps.width ||
230
230
  this.props.breakpoint !== prevProps.breakpoint ||
231
- !isEqual(this.props.breakpoints, prevProps.breakpoints) ||
232
- !isEqual(this.props.cols, prevProps.cols)
231
+ !deepEqual(this.props.breakpoints, prevProps.breakpoints) ||
232
+ !deepEqual(this.props.cols, prevProps.cols)
233
233
  ) {
234
234
  this.onWidthChange(prevProps);
235
235
  }
@@ -71,6 +71,14 @@ export function calcGridItemPosition(
71
71
  if (state && state.dragging) {
72
72
  out.top = Math.round(state.dragging.top);
73
73
  out.left = Math.round(state.dragging.left);
74
+ } else if (
75
+ state &&
76
+ state.resizing &&
77
+ typeof state.resizing.top === "number" &&
78
+ typeof state.resizing.left === "number"
79
+ ) {
80
+ out.top = Math.round(state.resizing.top);
81
+ out.left = Math.round(state.resizing.left);
74
82
  }
75
83
  // Otherwise, calculate from grid units.
76
84
  else {
@@ -123,6 +131,7 @@ export function calcXY(
123
131
  * @param {Number} width Width in pixels.
124
132
  * @param {Number} x X coordinate in grid units.
125
133
  * @param {Number} y Y coordinate in grid units.
134
+ * @param {String} handle Resize Handle.
126
135
  * @return {Object} w, h as grid units.
127
136
  */
128
137
  export function calcWH(
@@ -130,7 +139,8 @@ export function calcWH(
130
139
  width: number,
131
140
  height: number,
132
141
  x: number,
133
- y: number
142
+ y: number,
143
+ handle: string
134
144
  ): { w: number, h: number } {
135
145
  const { margin, maxRows, cols, rowHeight } = positionParams;
136
146
  const colWidth = calcGridColWidth(positionParams);
@@ -142,9 +152,15 @@ export function calcWH(
142
152
  let h = Math.round((height + margin[1]) / (rowHeight + margin[1]));
143
153
 
144
154
  // Capping
145
- w = clamp(w, 0, cols - x);
146
- h = clamp(h, 0, maxRows - y);
147
- return { w, h };
155
+ let _w = clamp(w, 0, cols - x);
156
+ let _h = clamp(h, 0, maxRows - y);
157
+ if (["sw", "w", "nw"].indexOf(handle) !== -1) {
158
+ _w = clamp(w, 0, cols);
159
+ }
160
+ if (["nw", "n", "ne"].indexOf(handle) !== -1) {
161
+ _h = clamp(h, 0, maxRows);
162
+ }
163
+ return { w: _w, h: _h };
148
164
  }
149
165
 
150
166
  // Similar to _.clamp
@@ -1,6 +1,7 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
  import PropTypes from "prop-types";
4
+ import ResizeObserver from "resize-observer-polyfill";
4
5
  import clsx from "clsx";
5
6
  import type { ReactRef } from "../ReactGridLayoutPropTypes";
6
7
 
@@ -58,30 +59,31 @@ export default function WidthProvideRGL<Config>(
58
59
 
59
60
  elementRef: ReactRef<HTMLDivElement> = React.createRef();
60
61
  mounted: boolean = false;
62
+ resizeObserver: ResizeObserver;
61
63
 
62
64
  componentDidMount() {
63
65
  this.mounted = true;
64
- window.addEventListener("resize", this.onWindowResize);
65
- // Call to properly set the breakpoint and resize the elements.
66
- // Note that if you're doing a full-width element, this can get a little wonky if a scrollbar
67
- // appears because of the grid. In that case, fire your own resize event, or set `overflow: scroll` on your body.
68
- this.onWindowResize();
66
+ this.resizeObserver = new ResizeObserver(entries => {
67
+ const node = this.elementRef.current;
68
+ if (node instanceof HTMLElement) {
69
+ const width = entries[0].contentRect.width;
70
+ this.setState({ width });
71
+ }
72
+ });
73
+ const node = this.elementRef.current;
74
+ if (node instanceof HTMLElement) {
75
+ this.resizeObserver.observe(node);
76
+ }
69
77
  }
70
78
 
71
79
  componentWillUnmount() {
72
80
  this.mounted = false;
73
- window.removeEventListener("resize", this.onWindowResize);
74
- }
75
-
76
- onWindowResize = () => {
77
- if (!this.mounted) return;
78
- const node = this.elementRef.current; // Flow casts this to Text | Element
79
- // fix: grid position error when node or parentNode display is none by window resize
80
- // #924 #1084
81
- if (node instanceof HTMLElement && node.offsetWidth) {
82
- this.setState({ width: node.offsetWidth });
81
+ const node = this.elementRef.current;
82
+ if (node instanceof HTMLElement) {
83
+ this.resizeObserver.unobserve(node);
83
84
  }
84
- };
85
+ this.resizeObserver.disconnect();
86
+ }
85
87
 
86
88
  render() {
87
89
  const { measureBeforeMount, ...rest } = this.props;