react-grid-layout 1.3.4 → 1.4.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/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  module.exports = require("./build/ReactGridLayout").default;
2
2
  module.exports.utils = require("./build/utils");
3
+ module.exports.calculateUtils = require("./build/calculateUtils");
3
4
  module.exports.Responsive =
4
5
  require("./build/ResponsiveReactGridLayout").default;
5
6
  module.exports.Responsive.utils = require("./build/responsiveUtils");
package/lib/GridItem.jsx CHANGED
@@ -41,9 +41,19 @@ type GridItemCallback<Data: GridDragEvent | GridResizeEvent> = (
41
41
  h: number,
42
42
  Data
43
43
  ) => void;
44
+ type GridItemResizeCallback = (
45
+ e: Event,
46
+ data: ResizeCallbackData,
47
+ position: Position
48
+ ) => void;
49
+ type ResizeCallbackData = {
50
+ node: HTMLElement,
51
+ size: Position,
52
+ handle: string
53
+ };
44
54
 
45
55
  type State = {
46
- resizing: ?{ width: number, height: number },
56
+ resizing: ?{ top: number, left: number, width: number, height: number },
47
57
  dragging: ?{ top: number, left: number },
48
58
  className: string
49
59
  };
@@ -356,6 +366,15 @@ export default class GridItem extends React.Component<Props, State> {
356
366
  );
357
367
  }
358
368
 
369
+ /**
370
+ * Utility function to setup callback handler definitions for
371
+ * similarily structured resize events.
372
+ */
373
+ curryResizeHandler(position: Position, handler: function): function {
374
+ return (e: Event, data: ResizeCallbackData): function =>
375
+ handler(e, data, position);
376
+ }
377
+
359
378
  /**
360
379
  * Mix a Resizable instance into a child.
361
380
  * @param {Element} child Child element.
@@ -381,13 +400,7 @@ export default class GridItem extends React.Component<Props, State> {
381
400
  const positionParams = this.getPositionParams();
382
401
 
383
402
  // 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;
403
+ const maxWidth = calcGridItemPosition(positionParams, 0, 0, cols, 0).width;
391
404
 
392
405
  // Calculate min/max constraints using our min & maxes
393
406
  const mins = calcGridItemPosition(positionParams, 0, 0, minW, minH);
@@ -408,9 +421,9 @@ export default class GridItem extends React.Component<Props, State> {
408
421
  height={position.height}
409
422
  minConstraints={minConstraints}
410
423
  maxConstraints={maxConstraints}
411
- onResizeStop={this.onResizeStop}
412
- onResizeStart={this.onResizeStart}
413
- onResize={this.onResize}
424
+ onResizeStop={this.curryResizeHandler(position, this.onResizeStop)}
425
+ onResizeStart={this.curryResizeHandler(position, this.onResizeStart)}
426
+ onResize={this.curryResizeHandler(position, this.onResize)}
414
427
  transformScale={transformScale}
415
428
  resizeHandles={resizeHandles}
416
429
  handle={resizeHandle}
@@ -541,53 +554,31 @@ export default class GridItem extends React.Component<Props, State> {
541
554
  * @param {Event} e event data
542
555
  * @param {Object} callbackData an object with node and size information
543
556
  */
544
- onResizeStop: (Event, { node: HTMLElement, size: Position }) => void = (
545
- e,
546
- callbackData
547
- ) => {
548
- this.onResizeHandler(e, callbackData, "onResizeStop");
549
- };
557
+ onResizeStop: GridItemResizeCallback = (e, callbackData, position) =>
558
+ this.onResizeHandler(e, callbackData, position, "onResizeStop");
550
559
 
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
- };
560
+ // onResizeStart event handler
561
+ onResizeStart: GridItemResizeCallback = (e, callbackData, position) =>
562
+ this.onResizeHandler(e, callbackData, position, "onResizeStart");
562
563
 
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
- };
564
+ // onResize event handler
565
+ onResize: GridItemResizeCallback = (e, callbackData, position) =>
566
+ this.onResizeHandler(e, callbackData, position, "onResize");
574
567
 
575
568
  /**
576
569
  * Wrapper around drag events to provide more useful data.
577
570
  * All drag events call the function with the given handler name,
578
571
  * with the signature (index, x, y).
579
- *
580
- * @param {String} handlerName Handler name to wrap.
581
- * @return {Function} Handler function.
582
572
  */
583
573
  onResizeHandler(
584
574
  e: Event,
585
- { node, size }: { node: HTMLElement, size: Position },
575
+ { node, size, handle }: ResizeCallbackData,
576
+ position: Position,
586
577
  handlerName: string
587
578
  ): void {
588
579
  const handler = this.props[handlerName];
589
580
  if (!handler) return;
590
- const { cols, x, y, i, maxH, minH } = this.props;
581
+ const { x, y, i, maxH, minH } = this.props;
591
582
  let { minW, maxW } = this.props;
592
583
 
593
584
  // Get new XY
@@ -596,22 +587,121 @@ export default class GridItem extends React.Component<Props, State> {
596
587
  size.width,
597
588
  size.height,
598
589
  x,
599
- y
590
+ y,
591
+ handle
600
592
  );
601
593
 
602
594
  // minW should be at least 1 (TODO propTypes validation?)
603
595
  minW = Math.max(minW, 1);
604
596
 
605
- // maxW should be at most (cols - x)
606
- maxW = Math.min(maxW, cols - x);
607
-
608
597
  // Min/max capping
609
598
  w = clamp(w, minW, maxW);
610
599
  h = clamp(h, minH, maxH);
611
600
 
612
- this.setState({ resizing: handlerName === "onResizeStop" ? null : size });
601
+ let updatedSize = size;
602
+ if (node) {
603
+ const currentLeft = position.left;
604
+ const currentTop = position.top;
605
+ const currentWidth = position.width;
606
+ const currentHeight = position.height;
607
+
608
+ const resizeNorth = ({ left, height, width }) => {
609
+ const top = currentTop - (height - currentHeight);
610
+
611
+ return {
612
+ top,
613
+ left,
614
+ width,
615
+ height: this.constrainHeight(top, currentHeight, height),
616
+ top: this.constrainTop(top)
617
+ };
618
+ };
619
+
620
+ const resizeEast = ({ top, left, height, width }) => ({
621
+ top,
622
+ height,
623
+ width: this.constrainWidth(currentLeft, currentWidth, width),
624
+ left: this.constrainLeft(left)
625
+ });
626
+
627
+ const resizeWest = ({ top, height, width }) => {
628
+ const left = currentLeft - (width - currentWidth);
629
+
630
+ return {
631
+ height,
632
+ width:
633
+ left <= 0
634
+ ? currentWidth
635
+ : this.constrainWidth(currentLeft, currentWidth, width),
636
+ top: this.constrainTop(top),
637
+ left: this.constrainLeft(left)
638
+ };
639
+ };
640
+
641
+ const resizeSouth = ({ top, left, height, width }) => ({
642
+ width,
643
+ left,
644
+ height: this.constrainHeight(top, currentHeight, height),
645
+ top: this.constrainTop(top)
646
+ });
647
+
648
+ const resizeNorthEast = (...args) => resizeNorth(resizeEast(...args));
649
+ const resizeNorthWest = (...args) => resizeNorth(resizeWest(...args));
650
+ const resizeSouthEast = (...args) => resizeSouth(resizeEast(...args));
651
+ const resizeSouthWest = (...args) => resizeSouth(resizeWest(...args));
652
+
653
+ const ordinalResizeHandlerMap = {
654
+ n: resizeNorth,
655
+ ne: resizeNorthEast,
656
+ e: resizeEast,
657
+ se: resizeSouthEast,
658
+ s: resizeSouth,
659
+ sw: resizeSouthWest,
660
+ w: resizeWest,
661
+ nw: resizeNorthWest
662
+ };
663
+
664
+ const resizeHandler = ordinalResizeHandlerMap[handle];
665
+ updatedSize = resizeHandler
666
+ ? resizeHandler({
667
+ top: currentTop,
668
+ left: currentLeft,
669
+ height: currentHeight,
670
+ width: currentWidth,
671
+ ...size
672
+ })
673
+ : size;
674
+ this.setState({
675
+ resizing: handlerName === "onResizeStop" ? null : updatedSize
676
+ });
677
+ }
678
+
679
+ handler.call(this, i, w, h, { e, node, size: updatedSize, handle });
680
+ }
681
+
682
+ /**
683
+ * Helper functions to constrain dimensions of a GridItem
684
+ */
685
+ constrainWidth(left: number, currentWidth: number, newWidth: number): number {
686
+ return left + newWidth > this.props.containerWidth
687
+ ? currentWidth
688
+ : newWidth;
689
+ }
690
+
691
+ constrainHeight(
692
+ top: number,
693
+ currentHeight: number,
694
+ newHeight: number
695
+ ): number {
696
+ return top <= 0 ? currentHeight : newHeight;
697
+ }
698
+
699
+ constrainLeft(left: number): number {
700
+ return Math.max(0, left);
701
+ }
613
702
 
614
- handler.call(this, i, w, h, { e, node, size });
703
+ constrainTop(top: number): number {
704
+ return Math.max(0, top);
615
705
  }
616
706
 
617
707
  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({
@@ -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;