Completely updated React, fixed #11, (hopefully)

This commit is contained in:
2018-03-04 19:11:49 -05:00
parent 6e0afd6e2a
commit 34e5f5139a
13674 changed files with 333464 additions and 473223 deletions

View File

@@ -1,24 +1,24 @@
'use strict';
"use strict";
exports.__esModule = true;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _react = require('react');
var _react = require("react");
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes = require("prop-types");
var _propTypes2 = _interopRequireDefault(_propTypes);
var _reactDraggable = require('react-draggable');
var _reactDraggable = require("react-draggable");
var _reactResizable = require('react-resizable');
var _reactResizable = require("react-resizable");
var _utils = require('./utils');
var _utils = require("./utils");
var _classnames = require('classnames');
var _classnames = require("classnames");
var _classnames2 = _interopRequireDefault(_classnames);
@@ -48,7 +48,7 @@ var GridItem = function (_React$Component) {
return _ret = (_temp = (_this = _possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this), _this.state = {
resizing: null,
dragging: null,
className: ''
className: ""
}, _temp), _possibleConstructorReturn(_this, _ret);
}
@@ -197,17 +197,16 @@ var GridItem = function (_React$Component) {
// CSS Transforms support (default)
if (useCSSTransforms) {
style = (0, _utils.setTransform)(pos);
}
// top,left (slow)
else {
style = (0, _utils.setTopLeft)(pos);
} else {
// top,left (slow)
style = (0, _utils.setTopLeft)(pos);
// This is used for server rendering.
if (usePercentages) {
style.left = (0, _utils.perc)(pos.left / containerWidth);
style.width = (0, _utils.perc)(pos.width / containerWidth);
}
// This is used for server rendering.
if (usePercentages) {
style.left = (0, _utils.perc)(pos.left / containerWidth);
style.width = (0, _utils.perc)(pos.width / containerWidth);
}
}
return style;
};
@@ -223,11 +222,12 @@ var GridItem = function (_React$Component) {
return _react2.default.createElement(
_reactDraggable.DraggableCore,
{
onStart: this.onDragHandler('onDragStart'),
onDrag: this.onDragHandler('onDrag'),
onStop: this.onDragHandler('onDragStop'),
onStart: this.onDragHandler("onDragStart"),
onDrag: this.onDragHandler("onDrag"),
onStop: this.onDragHandler("onDragStop"),
handle: this.props.handle,
cancel: ".react-resizable-handle" + (this.props.cancel ? "," + this.props.cancel : "") },
cancel: ".react-resizable-handle" + (this.props.cancel ? "," + this.props.cancel : "")
},
child
);
};
@@ -265,9 +265,10 @@ var GridItem = function (_React$Component) {
height: position.height,
minConstraints: minConstraints,
maxConstraints: maxConstraints,
onResizeStop: this.onResizeHandler('onResizeStop'),
onResizeStart: this.onResizeHandler('onResizeStart'),
onResize: this.onResizeHandler('onResize') },
onResizeStop: this.onResizeHandler("onResizeStop"),
onResizeStart: this.onResizeHandler("onResizeStart"),
onResize: this.onResizeHandler("onResize")
},
child
);
};
@@ -297,37 +298,40 @@ var GridItem = function (_React$Component) {
// Get new XY
switch (handlerName) {
case 'onDragStart':
case "onDragStart":
{
// ToDo this wont work on nested parents
var parentRect = node.offsetParent.getBoundingClientRect();
// TODO: this wont work on nested parents
var offsetParent = node.offsetParent;
if (!offsetParent) return;
var parentRect = offsetParent.getBoundingClientRect();
var clientRect = node.getBoundingClientRect();
newPosition.left = clientRect.left - parentRect.left + node.offsetParent.scrollLeft;
newPosition.top = clientRect.top - parentRect.top + node.offsetParent.scrollTop;
newPosition.left = clientRect.left - parentRect.left + offsetParent.scrollLeft;
newPosition.top = clientRect.top - parentRect.top + offsetParent.scrollTop;
_this2.setState({ dragging: newPosition });
break;
}
case 'onDrag':
if (!_this2.state.dragging) throw new Error('onDrag called before onDragStart.');
case "onDrag":
if (!_this2.state.dragging) throw new Error("onDrag called before onDragStart.");
newPosition.left = _this2.state.dragging.left + deltaX;
newPosition.top = _this2.state.dragging.top + deltaY;
_this2.setState({ dragging: newPosition });
break;
case 'onDragStop':
if (!_this2.state.dragging) throw new Error('onDragEnd called before onDragStart.');
case "onDragStop":
if (!_this2.state.dragging) throw new Error("onDragEnd called before onDragStart.");
newPosition.left = _this2.state.dragging.left;
newPosition.top = _this2.state.dragging.top;
_this2.setState({ dragging: null });
break;
default:
throw new Error('onDragHandler called with unrecognized handlerName: ' + handlerName);
throw new Error("onDragHandler called with unrecognized handlerName: " + handlerName);
}
var _calcXY = _this2.calcXY(newPosition.top, newPosition.left),
x = _calcXY.x,
y = _calcXY.y;
handler.call(_this2, _this2.props.i, x, y, { e: e, node: node, newPosition: newPosition });
return handler.call(_this2, _this2.props.i, x, y, { e: e, node: node, newPosition: newPosition });
};
};
@@ -376,7 +380,7 @@ var GridItem = function (_React$Component) {
w = Math.max(Math.min(w, maxW), minW);
h = Math.max(Math.min(h, maxH), minH);
_this3.setState({ resizing: handlerName === 'onResizeStop' ? null : size });
_this3.setState({ resizing: handlerName === "onResizeStop" ? null : size });
handler.call(_this3, i, w, h, { e: e, node: node, size: size });
};
@@ -398,11 +402,11 @@ var GridItem = function (_React$Component) {
// Create the child element. We clone the existing element but modify its className and style.
var newChild = _react2.default.cloneElement(child, {
className: (0, _classnames2.default)('react-grid-item', child.props.className, this.props.className, {
className: (0, _classnames2.default)("react-grid-item", child.props.className, this.props.className, {
static: this.props.static,
resizing: Boolean(this.state.resizing),
'react-draggable': isDraggable,
'react-draggable-dragging': Boolean(this.state.dragging),
"react-draggable": isDraggable,
"react-draggable-dragging": Boolean(this.state.dragging),
cssTransforms: useCSSTransforms
}),
// We can set the width and height on the child, but unfortunately we can't set the position.
@@ -442,26 +446,26 @@ GridItem.propTypes = {
// All optional
minW: function minW(props, propName) {
var value = props[propName];
if (typeof value !== 'number') return new Error('minWidth not Number');
if (value > props.w || value > props.maxW) return new Error('minWidth larger than item width/maxWidth');
if (typeof value !== "number") return new Error("minWidth not Number");
if (value > props.w || value > props.maxW) return new Error("minWidth larger than item width/maxWidth");
},
maxW: function maxW(props, propName) {
var value = props[propName];
if (typeof value !== 'number') return new Error('maxWidth not Number');
if (value < props.w || value < props.minW) return new Error('maxWidth smaller than item width/minWidth');
if (typeof value !== "number") return new Error("maxWidth not Number");
if (value < props.w || value < props.minW) return new Error("maxWidth smaller than item width/minWidth");
},
minH: function minH(props, propName) {
var value = props[propName];
if (typeof value !== 'number') return new Error('minHeight not Number');
if (value > props.h || value > props.maxH) return new Error('minHeight larger than item height/maxHeight');
if (typeof value !== "number") return new Error("minHeight not Number");
if (value > props.h || value > props.maxH) return new Error("minHeight larger than item height/maxHeight");
},
maxH: function maxH(props, propName) {
var value = props[propName];
if (typeof value !== 'number') return new Error('maxHeight not Number');
if (value < props.h || value < props.minH) return new Error('maxHeight smaller than item height/minHeight');
if (typeof value !== "number") return new Error("maxHeight not Number");
if (value < props.h || value < props.minH) return new Error("maxHeight smaller than item height/minHeight");
},
// ID is nice to have for callbacks
@@ -491,9 +495,9 @@ GridItem.propTypes = {
cancel: _propTypes2.default.string
};
GridItem.defaultProps = {
className: '',
cancel: '',
handle: '',
className: "",
cancel: "",
handle: "",
minH: 1,
minW: 1,
maxH: Infinity,

View File

@@ -1,20 +1,30 @@
// @flow
import React from 'react';
import PropTypes from 'prop-types';
import {DraggableCore} from 'react-draggable';
import {Resizable} from 'react-resizable';
import {perc, setTopLeft, setTransform} from './utils';
import classNames from 'classnames';
import type {Element as ReactElement, Node as ReactNode} from 'react';
import React from "react";
import PropTypes from "prop-types";
import { DraggableCore } from "react-draggable";
import { Resizable } from "react-resizable";
import { perc, setTopLeft, setTransform } from "./utils";
import classNames from "classnames";
import type { Element as ReactElement, Node as ReactNode } from "react";
import type {ReactDraggableCallbackData, GridDragEvent, GridResizeEvent, Position} from './utils';
import type {
ReactDraggableCallbackData,
GridDragEvent,
GridResizeEvent,
Position
} from "./utils";
type PartialPosition = {top: number, left: number};
type GridItemCallback<Data: GridDragEvent | GridResizeEvent> = (i: string, w: number, h: number, Data) => void;
type PartialPosition = { top: number, left: number };
type GridItemCallback<Data: GridDragEvent | GridResizeEvent> = (
i: string,
w: number,
h: number,
Data
) => void;
type State = {
resizing: ?{width: number, height: number},
dragging: ?{top: number, left: number},
resizing: ?{ width: number, height: number },
dragging: ?{ top: number, left: number },
className: string
};
@@ -54,14 +64,13 @@ type Props = {
onDragStop?: GridItemCallback<GridDragEvent>,
onResize?: GridItemCallback<GridResizeEvent>,
onResizeStart?: GridItemCallback<GridResizeEvent>,
onResizeStop?: GridItemCallback<GridResizeEvent>,
onResizeStop?: GridItemCallback<GridResizeEvent>
};
/**
* An individual item within a ReactGridLayout.
*/
export default class GridItem extends React.Component<Props, State> {
static propTypes = {
// Children must be only a single element
children: PropTypes.element,
@@ -81,28 +90,32 @@ export default class GridItem extends React.Component<Props, State> {
h: PropTypes.number.isRequired,
// All optional
minW: function (props, propName) {
minW: function(props: Props, propName: string) {
const value = props[propName];
if (typeof value !== 'number') return new Error('minWidth not Number');
if (value > props.w || value > props.maxW) return new Error('minWidth larger than item width/maxWidth');
if (typeof value !== "number") return new Error("minWidth not Number");
if (value > props.w || value > props.maxW)
return new Error("minWidth larger than item width/maxWidth");
},
maxW: function (props, propName) {
maxW: function(props: Props, propName: string) {
const value = props[propName];
if (typeof value !== 'number') return new Error('maxWidth not Number');
if (value < props.w || value < props.minW) return new Error('maxWidth smaller than item width/minWidth');
if (typeof value !== "number") return new Error("maxWidth not Number");
if (value < props.w || value < props.minW)
return new Error("maxWidth smaller than item width/minWidth");
},
minH: function (props, propName) {
minH: function(props: Props, propName: string) {
const value = props[propName];
if (typeof value !== 'number') return new Error('minHeight not Number');
if (value > props.h || value > props.maxH) return new Error('minHeight larger than item height/maxHeight');
if (typeof value !== "number") return new Error("minHeight not Number");
if (value > props.h || value > props.maxH)
return new Error("minHeight larger than item height/maxHeight");
},
maxH: function (props, propName) {
maxH: function(props: Props, propName: string) {
const value = props[propName];
if (typeof value !== 'number') return new Error('maxHeight not Number');
if (value < props.h || value < props.minH) return new Error('maxHeight smaller than item height/minHeight');
if (typeof value !== "number") return new Error("maxHeight not Number");
if (value < props.h || value < props.minH)
return new Error("maxHeight smaller than item height/minHeight");
},
// ID is nice to have for callbacks
@@ -133,25 +146,27 @@ export default class GridItem extends React.Component<Props, State> {
};
static defaultProps = {
className: '',
cancel: '',
handle: '',
className: "",
cancel: "",
handle: "",
minH: 1,
minW: 1,
maxH: Infinity,
maxW: Infinity,
maxW: Infinity
};
state: State = {
resizing: null,
dragging: null,
className: ''
className: ""
};
// Helper for generating column width
calcColWidth(): number {
const {margin, containerPadding, containerWidth, cols} = this.props;
return (containerWidth - (margin[0] * (cols - 1)) - (containerPadding[0] * 2)) / cols;
const { margin, containerPadding, containerWidth, cols } = this.props;
return (
(containerWidth - margin[0] * (cols - 1) - containerPadding[0] * 2) / cols
);
}
/**
@@ -163,8 +178,14 @@ export default class GridItem extends React.Component<Props, State> {
* @param {Number} h H coordinate in grid units.
* @return {Object} Object containing coords.
*/
calcPosition(x: number, y: number, w: number, h: number, state: ?Object): Position {
const {margin, containerPadding, rowHeight} = this.props;
calcPosition(
x: number,
y: number,
w: number,
h: number,
state: ?Object
): Position {
const { margin, containerPadding, rowHeight } = this.props;
const colWidth = this.calcColWidth();
const out = {
@@ -173,8 +194,14 @@ export default class GridItem extends React.Component<Props, State> {
// 0 * Infinity === NaN, which causes problems with resize constraints;
// Fix this if it occurs.
// Note we do it here rather than later because Math.round(Infinity) causes deopt
width: w === Infinity ? w : Math.round(colWidth * w + Math.max(0, w - 1) * margin[0]),
height: h === Infinity ? h : Math.round(rowHeight * h + Math.max(0, h - 1) * margin[1])
width:
w === Infinity
? w
: Math.round(colWidth * w + Math.max(0, w - 1) * margin[0]),
height:
h === Infinity
? h
: Math.round(rowHeight * h + Math.max(0, h - 1) * margin[1])
};
if (state && state.resizing) {
@@ -196,8 +223,8 @@ export default class GridItem extends React.Component<Props, State> {
* @param {Number} left Left position (relative to parent) in pixels.
* @return {Object} x and y in grid units.
*/
calcXY(top: number, left: number): {x: number, y: number} {
const {margin, cols, rowHeight, w, h, maxRows} = this.props;
calcXY(top: number, left: number): { x: number, y: number } {
const { margin, cols, rowHeight, w, h, maxRows } = this.props;
const colWidth = this.calcColWidth();
// left = colWidth * x + margin * (x + 1)
@@ -214,7 +241,7 @@ export default class GridItem extends React.Component<Props, State> {
x = Math.max(Math.min(x, cols - w), 0);
y = Math.max(Math.min(y, maxRows - h), 0);
return {x, y};
return { x, y };
}
/**
@@ -223,8 +250,14 @@ export default class GridItem extends React.Component<Props, State> {
* @param {Number} width Width in pixels.
* @return {Object} w, h as grid units.
*/
calcWH({height, width}: {height: number, width: number}): {w: number, h: number} {
const {margin, maxRows, cols, rowHeight, x, y} = this.props;
calcWH({
height,
width
}: {
height: number,
width: number
}): { w: number, h: number } {
const { margin, maxRows, cols, rowHeight, x, y } = this.props;
const colWidth = this.calcColWidth();
// width = colWidth * w - (margin * (w - 1))
@@ -236,7 +269,7 @@ export default class GridItem extends React.Component<Props, State> {
// Capping
w = Math.max(Math.min(w, cols - x), 0);
h = Math.max(Math.min(h, maxRows - y), 0);
return {w, h};
return { w, h };
}
/**
@@ -249,16 +282,15 @@ export default class GridItem extends React.Component<Props, State> {
* @param {Object} pos Position object with width, height, left, top.
* @return {Object} Style object.
*/
createStyle(pos: Position): {[key: string]: ?string} {
const {usePercentages, containerWidth, useCSSTransforms} = this.props;
createStyle(pos: Position): { [key: string]: ?string } {
const { usePercentages, containerWidth, useCSSTransforms } = this.props;
let style;
// CSS Transforms support (default)
if (useCSSTransforms) {
style = setTransform(pos);
}
// top,left (slow)
else {
} else {
// top,left (slow)
style = setTopLeft(pos);
// This is used for server rendering.
@@ -279,11 +311,15 @@ export default class GridItem extends React.Component<Props, State> {
mixinDraggable(child: ReactElement<any>): ReactElement<any> {
return (
<DraggableCore
onStart={this.onDragHandler('onDragStart')}
onDrag={this.onDragHandler('onDrag')}
onStop={this.onDragHandler('onDragStop')}
onStart={this.onDragHandler("onDragStart")}
onDrag={this.onDragHandler("onDrag")}
onStop={this.onDragHandler("onDragStop")}
handle={this.props.handle}
cancel={".react-resizable-handle" + (this.props.cancel ? "," + this.props.cancel : "")}>
cancel={
".react-resizable-handle" +
(this.props.cancel ? "," + this.props.cancel : "")
}
>
{child}
</DraggableCore>
);
@@ -295,8 +331,11 @@ export default class GridItem extends React.Component<Props, State> {
* @param {Object} position Position object (pixel values)
* @return {Element} Child wrapped in Resizable.
*/
mixinResizable(child: ReactElement<any>, position: Position): ReactElement<any> {
const {cols, x, minW, minH, maxW, maxH} = this.props;
mixinResizable(
child: ReactElement<any>,
position: Position
): ReactElement<any> {
const { cols, x, minW, minH, maxW, maxH } = this.props;
// This is the max possible width - doesn't go to infinity because of the width of the window
const maxWidth = this.calcPosition(0, 0, cols - x, 0).width;
@@ -305,16 +344,20 @@ export default class GridItem extends React.Component<Props, State> {
const mins = this.calcPosition(0, 0, minW, minH);
const maxes = this.calcPosition(0, 0, maxW, maxH);
const minConstraints = [mins.width, mins.height];
const maxConstraints = [Math.min(maxes.width, maxWidth), Math.min(maxes.height, Infinity)];
const maxConstraints = [
Math.min(maxes.width, maxWidth),
Math.min(maxes.height, Infinity)
];
return (
<Resizable
width={position.width}
height={position.height}
minConstraints={minConstraints}
maxConstraints={maxConstraints}
onResizeStop={this.onResizeHandler('onResizeStop')}
onResizeStart={this.onResizeHandler('onResizeStart')}
onResize={this.onResizeHandler('onResize')}>
onResizeStop={this.onResizeHandler("onResizeStop")}
onResizeStart={this.onResizeHandler("onResizeStart")}
onResize={this.onResizeHandler("onResize")}
>
{child}
</Resizable>
);
@@ -328,43 +371,51 @@ export default class GridItem extends React.Component<Props, State> {
* @param {String} handlerName Handler name to wrap.
* @return {Function} Handler function.
*/
onDragHandler(handlerName:string) {
return (e:Event, {node, deltaX, deltaY}: ReactDraggableCallbackData) => {
onDragHandler(handlerName: string) {
return (e: Event, { node, deltaX, deltaY }: ReactDraggableCallbackData) => {
const handler = this.props[handlerName];
if (!handler) return;
const newPosition: PartialPosition = {top: 0, left: 0};
const newPosition: PartialPosition = { top: 0, left: 0 };
// Get new XY
switch (handlerName) {
case 'onDragStart': {
// ToDo this wont work on nested parents
const parentRect = node.offsetParent.getBoundingClientRect();
case "onDragStart": {
// TODO: this wont work on nested parents
const { offsetParent } = node;
if (!offsetParent) return;
const parentRect = offsetParent.getBoundingClientRect();
const clientRect = node.getBoundingClientRect();
newPosition.left = clientRect.left - parentRect.left + node.offsetParent.scrollLeft;
newPosition.top = clientRect.top - parentRect.top + node.offsetParent.scrollTop;
this.setState({dragging: newPosition});
newPosition.left =
clientRect.left - parentRect.left + offsetParent.scrollLeft;
newPosition.top =
clientRect.top - parentRect.top + offsetParent.scrollTop;
this.setState({ dragging: newPosition });
break;
}
case 'onDrag':
if (!this.state.dragging) throw new Error('onDrag called before onDragStart.');
case "onDrag":
if (!this.state.dragging)
throw new Error("onDrag called before onDragStart.");
newPosition.left = this.state.dragging.left + deltaX;
newPosition.top = this.state.dragging.top + deltaY;
this.setState({dragging: newPosition});
this.setState({ dragging: newPosition });
break;
case 'onDragStop':
if (!this.state.dragging) throw new Error('onDragEnd called before onDragStart.');
case "onDragStop":
if (!this.state.dragging)
throw new Error("onDragEnd called before onDragStart.");
newPosition.left = this.state.dragging.left;
newPosition.top = this.state.dragging.top;
this.setState({dragging: null});
this.setState({ dragging: null });
break;
default:
throw new Error('onDragHandler called with unrecognized handlerName: ' + handlerName);
throw new Error(
"onDragHandler called with unrecognized handlerName: " + handlerName
);
}
const {x, y} = this.calcXY(newPosition.top, newPosition.left);
const { x, y } = this.calcXY(newPosition.top, newPosition.left);
handler.call(this, this.props.i, x, y, {e, node, newPosition});
return handler.call(this, this.props.i, x, y, { e, node, newPosition });
};
}
@@ -377,13 +428,16 @@ export default class GridItem extends React.Component<Props, State> {
* @return {Function} Handler function.
*/
onResizeHandler(handlerName: string) {
return (e:Event, {node, size}: {node: HTMLElement, size: Position}) => {
return (
e: Event,
{ node, size }: { node: HTMLElement, size: Position }
) => {
const handler = this.props[handlerName];
if (!handler) return;
const {cols, x, i, maxW, minW, maxH, minH} = this.props;
const { cols, x, i, maxW, minW, maxH, minH } = this.props;
// Get new XY
let {w, h} = this.calcWH(size);
let { w, h } = this.calcWH(size);
// Cap w at numCols
w = Math.min(w, cols - x);
@@ -394,29 +448,46 @@ export default class GridItem extends React.Component<Props, State> {
w = Math.max(Math.min(w, maxW), minW);
h = Math.max(Math.min(h, maxH), minH);
this.setState({resizing: handlerName === 'onResizeStop' ? null : size});
this.setState({ resizing: handlerName === "onResizeStop" ? null : size });
handler.call(this, i, w, h, {e, node, size});
handler.call(this, i, w, h, { e, node, size });
};
}
render(): ReactNode {
const {x, y, w, h, isDraggable, isResizable, useCSSTransforms} = this.props;
const {
x,
y,
w,
h,
isDraggable,
isResizable,
useCSSTransforms
} = this.props;
const pos = this.calcPosition(x, y, w, h, this.state);
const child = React.Children.only(this.props.children);
// Create the child element. We clone the existing element but modify its className and style.
let newChild = React.cloneElement(child, {
className: classNames('react-grid-item', child.props.className, this.props.className, {
static: this.props.static,
resizing: Boolean(this.state.resizing),
'react-draggable': isDraggable,
'react-draggable-dragging': Boolean(this.state.dragging),
cssTransforms: useCSSTransforms
}),
className: classNames(
"react-grid-item",
child.props.className,
this.props.className,
{
static: this.props.static,
resizing: Boolean(this.state.resizing),
"react-draggable": isDraggable,
"react-draggable-dragging": Boolean(this.state.dragging),
cssTransforms: useCSSTransforms
}
),
// We can set the width and height on the child, but unfortunately we can't set the position.
style: {...this.props.style, ...child.props.style, ...this.createStyle(pos)}
style: {
...this.props.style,
...child.props.style,
...this.createStyle(pos)
}
});
// Resizable support. This is usually on but the user can toggle it off.

View File

@@ -1,28 +1,28 @@
'use strict';
"use strict";
exports.__esModule = true;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _react = require('react');
var _react = require("react");
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes = require("prop-types");
var _propTypes2 = _interopRequireDefault(_propTypes);
var _lodash = require('lodash.isequal');
var _lodash = require("lodash.isequal");
var _lodash2 = _interopRequireDefault(_lodash);
var _classnames = require('classnames');
var _classnames = require("classnames");
var _classnames2 = _interopRequireDefault(_classnames);
var _utils = require('./utils');
var _utils = require("./utils");
var _GridItem = require('./GridItem');
var _GridItem = require("./GridItem");
var _GridItem2 = _interopRequireDefault(_GridItem);
@@ -34,16 +34,13 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var noop = function noop() {};
// Types
// End Types
/**
* A reactive, fluid grid layout with draggable, resizable components.
*/
// Types
var ReactGridLayout = function (_React$Component) {
_inherits(ReactGridLayout, _React$Component);
@@ -55,7 +52,7 @@ var ReactGridLayout = function (_React$Component) {
_initialiseProps.call(_this);
(0, _utils.autoBindHandlers)(_this, ['onDragStart', 'onDrag', 'onDragStop', 'onResizeStart', 'onResize', 'onResizeStop']);
(0, _utils.autoBindHandlers)(_this, ["onDragStart", "onDrag", "onDragStop", "onResizeStart", "onResize", "onResizeStop"]);
return _this;
}
@@ -72,15 +69,13 @@ var ReactGridLayout = function (_React$Component) {
// Allow parent to set layout directly.
if (!(0, _lodash2.default)(nextProps.layout, this.props.layout) || nextProps.compactType !== this.props.compactType) {
newLayoutBase = nextProps.layout;
} else if (!(0, _utils.childrenEqual)(this.props.children, nextProps.children)) {
// If children change, also regenerate the layout. Use our state
// as the base in case because it may be more up to date than
// what is in props.
newLayoutBase = this.state.layout;
}
// If children change, also regenerate the layout. Use our state
// as the base in case because it may be more up to date than
// what is in props.
else if (!(0, _utils.childrenEqual)(this.props.children, nextProps.children)) {
newLayoutBase = this.state.layout;
}
// We need to regenerate the layout.
if (newLayoutBase) {
var newLayout = (0, _utils.synchronizeLayoutWithChildren)(newLayoutBase, nextProps.children, nextProps.cols, this.compactType(nextProps));
@@ -100,7 +95,7 @@ var ReactGridLayout = function (_React$Component) {
if (!this.props.autoSize) return;
var nbRow = (0, _utils.bottom)(this.state.layout);
var containerPaddingY = this.props.containerPadding ? this.props.containerPadding[1] : this.props.margin[1];
return nbRow * this.props.rowHeight + (nbRow - 1) * this.props.margin[1] + containerPaddingY * 2 + 'px';
return nbRow * this.props.rowHeight + (nbRow - 1) * this.props.margin[1] + containerPaddingY * 2 + "px";
};
ReactGridLayout.prototype.compactType = function compactType(props) {
@@ -126,9 +121,12 @@ var ReactGridLayout = function (_React$Component) {
var l = (0, _utils.getLayoutItem)(layout, i);
if (!l) return;
this.setState({ oldDragItem: (0, _utils.cloneLayoutItem)(l), oldLayout: this.state.layout });
this.setState({
oldDragItem: (0, _utils.cloneLayoutItem)(l),
oldLayout: this.state.layout
});
this.props.onDragStart(layout, l, l, null, e, node);
return this.props.onDragStart(layout, l, l, null, e, node);
};
/**
@@ -153,7 +151,12 @@ var ReactGridLayout = function (_React$Component) {
// Create placeholder (display only)
var placeholder = {
w: l.w, h: l.h, x: l.x, y: l.y, placeholder: true, i: i
w: l.w,
h: l.h,
x: l.x,
y: l.y,
placeholder: true,
i: i
};
// Move the element to the dragged location.
@@ -246,18 +249,44 @@ var ReactGridLayout = function (_React$Component) {
var l = (0, _utils.getLayoutItem)(layout, i);
if (!l) return;
// Short circuit if there is a collision in no rearrangement mode.
if (preventCollision && (0, _utils.getFirstCollision)(layout, _extends({}, l, { w: w, h: h }))) {
return;
// Something like quad tree should be used
// to find collisions faster
var hasCollisions = void 0;
if (preventCollision) {
var collisions = (0, _utils.getAllCollisions)(layout, _extends({}, l, { w: w, h: h })).filter(function (layoutItem) {
return layoutItem.i !== l.i;
});
hasCollisions = collisions.length > 0;
// If we're colliding, we need adjust the placeholder.
if (hasCollisions) {
// adjust w && h to maximum allowed space
var leastX = Infinity,
leastY = Infinity;
collisions.forEach(function (layoutItem) {
if (layoutItem.x > l.x) leastX = Math.min(leastX, layoutItem.x);
if (layoutItem.y > l.y) leastY = Math.min(leastY, layoutItem.y);
});
if (Number.isFinite(leastX)) l.w = leastX - l.x;
if (Number.isFinite(leastY)) l.h = leastY - l.y;
}
}
// Set new width and height.
l.w = w;
l.h = h;
if (!hasCollisions) {
// Set new width and height.
l.w = w;
l.h = h;
}
// Create placeholder element (display only)
var placeholder = {
w: w, h: h, x: l.x, y: l.y, static: true, i: i
w: l.w,
h: l.h,
x: l.x,
y: l.y,
static: true,
i: i
};
this.props.onResize(layout, oldResizeItem, l, placeholder, e, node);
@@ -324,7 +353,7 @@ var ReactGridLayout = function (_React$Component) {
x: activeDrag.x,
y: activeDrag.y,
i: activeDrag.i,
className: 'react-grid-placeholder',
className: "react-grid-placeholder",
containerWidth: width,
cols: cols,
margin: margin,
@@ -333,8 +362,9 @@ var ReactGridLayout = function (_React$Component) {
rowHeight: rowHeight,
isDraggable: false,
isResizable: false,
useCSSTransforms: useCSSTransforms },
_react2.default.createElement('div', null)
useCSSTransforms: useCSSTransforms
},
_react2.default.createElement("div", null)
);
};
@@ -346,7 +376,7 @@ var ReactGridLayout = function (_React$Component) {
ReactGridLayout.prototype.processGridItem = function processGridItem(child) {
if (!child.key) return;
if (!child || !child.key) return;
var l = (0, _utils.getLayoutItem)(this.state.layout, String(child.key));
if (!l) return null;
var _props4 = this.props,
@@ -389,7 +419,6 @@ var ReactGridLayout = function (_React$Component) {
isResizable: resizable,
useCSSTransforms: useCSSTransforms && mounted,
usePercentages: !mounted,
w: l.w,
h: l.h,
x: l.x,
@@ -399,7 +428,7 @@ var ReactGridLayout = function (_React$Component) {
minW: l.minW,
maxH: l.maxH,
maxW: l.maxW,
'static': l.static
"static": l.static
},
child
);
@@ -413,15 +442,14 @@ var ReactGridLayout = function (_React$Component) {
style = _props5.style;
var mergedClassName = (0, _classnames2.default)("react-grid-layout", className);
var mergedStyle = _extends({
height: this.containerHeight()
}, style);
return _react2.default.createElement(
'div',
{ className: (0, _classnames2.default)('react-grid-layout', className), style: mergedStyle },
// $FlowIgnore: Appears to think map calls back w/array
"div",
{ className: mergedClassName, style: mergedStyle },
_react2.default.Children.map(this.props.children, function (child) {
return _this2.processGridItem(child);
}),
@@ -457,13 +485,14 @@ ReactGridLayout.propTypes = {
// Deprecated
verticalCompact: function verticalCompact(props) {
if (props.verticalCompact === false && process.env.NODE_ENV !== 'production') {
console.warn( // eslint-disable-line no-console
'`verticalCompact` on <ReactGridLayout> is deprecated and will be removed soon. ' + 'Use `compactType`: "horizontal" | "vertical" | null.');
if (props.verticalCompact === false && process.env.NODE_ENV !== "production") {
console.warn(
// eslint-disable-line no-console
"`verticalCompact` on <ReactGridLayout> is deprecated and will be removed soon. " + 'Use `compactType`: "horizontal" | "vertical" | null.');
}
},
// Choose vertical or hotizontal compaction
compactType: _propTypes2.default.oneOf(['vertical', 'horizontal']),
compactType: _propTypes2.default.oneOf(["vertical", "horizontal"]),
// layout is an array of object with the format:
// {x: Number, y: Number, w: Number, h: Number, i: String}
@@ -471,7 +500,7 @@ ReactGridLayout.propTypes = {
var layout = props.layout;
// I hope you're setting the data-grid property on the grid items
if (layout === undefined) return;
(0, _utils.validateLayout)(layout, 'layout');
(0, _utils.validateLayout)(layout, "layout");
},
//
@@ -527,14 +556,14 @@ ReactGridLayout.propTypes = {
//
// Children must not have duplicate keys.
children: function children(props, propName, _componentName) {
children: function children(props, propName) {
var children = props[propName];
// Check children keys for duplicates. Throw if found.
var keys = {};
_react2.default.Children.forEach(children, function (child) {
if (keys[child.key]) {
throw new Error("Duplicate child key \"" + child.key + "\" found! This will cause problems in ReactGridLayout.");
throw new Error('Duplicate child key "' + child.key + '" found! This will cause problems in ReactGridLayout.');
}
keys[child.key] = true;
});
@@ -543,7 +572,11 @@ ReactGridLayout.propTypes = {
ReactGridLayout.defaultProps = {
autoSize: true,
cols: 12,
className: '',
className: "",
style: {},
draggableHandle: "",
draggableCancel: "",
containerPadding: null,
rowHeight: 150,
maxRows: Infinity, // infinite vertical growth
layout: [],
@@ -552,15 +585,15 @@ ReactGridLayout.defaultProps = {
isResizable: true,
useCSSTransforms: true,
verticalCompact: true,
compactType: 'vertical',
compactType: "vertical",
preventCollision: false,
onLayoutChange: noop,
onDragStart: noop,
onDrag: noop,
onDragStop: noop,
onResizeStart: noop,
onResize: noop,
onResizeStop: noop
onLayoutChange: _utils.noop,
onDragStart: _utils.noop,
onDrag: _utils.noop,
onDragStop: _utils.noop,
onResizeStart: _utils.noop,
onResize: _utils.noop,
onResizeStop: _utils.noop
};
var _initialiseProps = function _initialiseProps() {

View File

@@ -1,16 +1,37 @@
// @flow
import React from 'react';
import PropTypes from 'prop-types';
import isEqual from 'lodash.isequal';
import classNames from 'classnames';
import {autoBindHandlers, bottom, childrenEqual, cloneLayoutItem, compact, getLayoutItem, moveElement,
synchronizeLayoutWithChildren, validateLayout, getFirstCollision} from './utils';
import GridItem from './GridItem';
import type {ChildrenArray as ReactChildrenArray, Element as ReactElement} from 'react';
const noop = function() {};
import React from "react";
import PropTypes from "prop-types";
import isEqual from "lodash.isequal";
import classNames from "classnames";
import {
autoBindHandlers,
bottom,
childrenEqual,
cloneLayoutItem,
compact,
getLayoutItem,
moveElement,
synchronizeLayoutWithChildren,
validateLayout,
getAllCollisions,
noop
} from "./utils";
import GridItem from "./GridItem";
import type {
ChildrenArray as ReactChildrenArray,
Element as ReactElement
} from "react";
// Types
import type {EventCallback, CompactType, GridResizeEvent, GridDragEvent, Layout, LayoutItem} from './utils';
import type {
EventCallback,
CompactType,
GridResizeEvent,
GridDragEvent,
Layout,
LayoutItem
} from "./utils";
type State = {
activeDrag: ?LayoutItem,
layout: Layout,
@@ -29,10 +50,10 @@ export type Props = {
draggableCancel: string,
draggableHandle: string,
verticalCompact: boolean,
compactType: ?('horizontal' | 'vertical'),
compactType: ?("horizontal" | "vertical"),
layout: Layout,
margin: [number, number],
containerPadding: [number, number],
containerPadding: [number, number] | null,
rowHeight: number,
maxRows: number,
isDraggable: boolean,
@@ -41,14 +62,14 @@ export type Props = {
useCSSTransforms: boolean,
// Callbacks
onLayoutChange: (Layout) => void,
onLayoutChange: Layout => void,
onDrag: EventCallback,
onDragStart: EventCallback,
onDragStop: EventCallback,
onResize: EventCallback,
onResizeStart: EventCallback,
onResizeStop: EventCallback,
children: ReactChildrenArray<ReactElement<any>>,
children: ReactChildrenArray<ReactElement<any>>
};
// End Types
@@ -83,23 +104,28 @@ export default class ReactGridLayout extends React.Component<Props, State> {
draggableHandle: PropTypes.string,
// Deprecated
verticalCompact: function(props) {
if (props.verticalCompact === false && process.env.NODE_ENV !== 'production') {
console.warn( // eslint-disable-line no-console
'`verticalCompact` on <ReactGridLayout> is deprecated and will be removed soon. ' +
'Use `compactType`: "horizontal" | "vertical" | null.');
verticalCompact: function(props: Props) {
if (
props.verticalCompact === false &&
process.env.NODE_ENV !== "production"
) {
console.warn(
// eslint-disable-line no-console
"`verticalCompact` on <ReactGridLayout> is deprecated and will be removed soon. " +
'Use `compactType`: "horizontal" | "vertical" | null.'
);
}
},
// Choose vertical or hotizontal compaction
compactType: PropTypes.oneOf(['vertical', 'horizontal']),
compactType: PropTypes.oneOf(["vertical", "horizontal"]),
// layout is an array of object with the format:
// {x: Number, y: Number, w: Number, h: Number, i: String}
layout: function (props) {
layout: function(props: Props) {
var layout = props.layout;
// I hope you're setting the data-grid property on the grid items
if (layout === undefined) return;
validateLayout(layout, 'layout');
validateLayout(layout, "layout");
},
//
@@ -155,14 +181,18 @@ export default class ReactGridLayout extends React.Component<Props, State> {
//
// Children must not have duplicate keys.
children: function (props, propName, _componentName) {
children: function(props: Props, propName: string) {
var children = props[propName];
// Check children keys for duplicates. Throw if found.
var keys = {};
React.Children.forEach(children, function (child) {
React.Children.forEach(children, function(child) {
if (keys[child.key]) {
throw new Error("Duplicate child key \"" + child.key + "\" found! This will cause problems in ReactGridLayout.");
throw new Error(
'Duplicate child key "' +
child.key +
'" found! This will cause problems in ReactGridLayout.'
);
}
keys[child.key] = true;
});
@@ -172,7 +202,11 @@ export default class ReactGridLayout extends React.Component<Props, State> {
static defaultProps = {
autoSize: true,
cols: 12,
className: '',
className: "",
style: {},
draggableHandle: "",
draggableCancel: "",
containerPadding: null,
rowHeight: 150,
maxRows: Infinity, // infinite vertical growth
layout: [],
@@ -181,7 +215,7 @@ export default class ReactGridLayout extends React.Component<Props, State> {
isResizable: true,
useCSSTransforms: true,
verticalCompact: true,
compactType: 'vertical',
compactType: "vertical",
preventCollision: false,
onLayoutChange: noop,
onDragStart: noop,
@@ -194,22 +228,33 @@ export default class ReactGridLayout extends React.Component<Props, State> {
state: State = {
activeDrag: null,
layout: synchronizeLayoutWithChildren(this.props.layout, this.props.children, this.props.cols,
// Legacy support for verticalCompact: false
this.compactType()),
layout: synchronizeLayoutWithChildren(
this.props.layout,
this.props.children,
this.props.cols,
// Legacy support for verticalCompact: false
this.compactType()
),
mounted: false,
oldDragItem: null,
oldLayout: null,
oldResizeItem: null,
oldResizeItem: null
};
constructor(props: Props, context: any): void {
super(props, context);
autoBindHandlers(this, ['onDragStart', 'onDrag', 'onDragStop', 'onResizeStart', 'onResize', 'onResizeStop']);
autoBindHandlers(this, [
"onDragStart",
"onDrag",
"onDragStop",
"onResizeStart",
"onResize",
"onResizeStop"
]);
}
componentDidMount() {
this.setState({mounted: true});
this.setState({ mounted: true });
// Possibly call back with layout on mount. This should be done after correcting the layout width
// to ensure we don't rerender with the wrong width.
this.onLayoutMaybeChanged(this.state.layout, this.props.layout);
@@ -219,23 +264,28 @@ export default class ReactGridLayout extends React.Component<Props, State> {
let newLayoutBase;
// Legacy support for compactType
// Allow parent to set layout directly.
if (!isEqual(nextProps.layout, this.props.layout) || nextProps.compactType !== this.props.compactType) {
if (
!isEqual(nextProps.layout, this.props.layout) ||
nextProps.compactType !== this.props.compactType
) {
newLayoutBase = nextProps.layout;
}
// If children change, also regenerate the layout. Use our state
// as the base in case because it may be more up to date than
// what is in props.
else if (!childrenEqual(this.props.children, nextProps.children)) {
} else if (!childrenEqual(this.props.children, nextProps.children)) {
// If children change, also regenerate the layout. Use our state
// as the base in case because it may be more up to date than
// what is in props.
newLayoutBase = this.state.layout;
}
// We need to regenerate the layout.
if (newLayoutBase) {
const newLayout = synchronizeLayoutWithChildren(newLayoutBase, nextProps.children,
nextProps.cols, this.compactType(nextProps));
const newLayout = synchronizeLayoutWithChildren(
newLayoutBase,
nextProps.children,
nextProps.cols,
this.compactType(nextProps)
);
const oldLayout = this.state.layout;
this.setState({layout: newLayout});
this.setState({ layout: newLayout });
this.onLayoutMaybeChanged(newLayout, oldLayout);
}
}
@@ -247,8 +297,15 @@ export default class ReactGridLayout extends React.Component<Props, State> {
containerHeight() {
if (!this.props.autoSize) return;
const nbRow = bottom(this.state.layout);
const containerPaddingY = this.props.containerPadding ? this.props.containerPadding[1] : this.props.margin[1];
return nbRow * this.props.rowHeight + (nbRow - 1) * this.props.margin[1] + containerPaddingY * 2 + 'px';
const containerPaddingY = this.props.containerPadding
? this.props.containerPadding[1]
: this.props.margin[1];
return (
nbRow * this.props.rowHeight +
(nbRow - 1) * this.props.margin[1] +
containerPaddingY * 2 +
"px"
);
}
compactType(props: ?Object): CompactType {
@@ -264,14 +321,17 @@ export default class ReactGridLayout extends React.Component<Props, State> {
* @param {Event} e The mousedown event
* @param {Element} node The current dragging DOM element
*/
onDragStart(i:string, x:number, y:number, {e, node}: GridDragEvent) {
const {layout} = this.state;
onDragStart(i: string, x: number, y: number, { e, node }: GridDragEvent) {
const { layout } = this.state;
var l = getLayoutItem(layout, i);
if (!l) return;
this.setState({oldDragItem: cloneLayoutItem(l), oldLayout: this.state.layout});
this.setState({
oldDragItem: cloneLayoutItem(l),
oldLayout: this.state.layout
});
this.props.onDragStart(layout, l, l, null, e, node);
return this.props.onDragStart(layout, l, l, null, e, node);
}
/**
@@ -282,21 +342,35 @@ export default class ReactGridLayout extends React.Component<Props, State> {
* @param {Event} e The mousedown event
* @param {Element} node The current dragging DOM element
*/
onDrag(i:string, x:number, y:number, {e, node}: GridDragEvent) {
const {oldDragItem} = this.state;
let {layout} = this.state;
const {cols} = this.props;
onDrag(i: string, x: number, y: number, { e, node }: GridDragEvent) {
const { oldDragItem } = this.state;
let { layout } = this.state;
const { cols } = this.props;
var l = getLayoutItem(layout, i);
if (!l) return;
// Create placeholder (display only)
var placeholder = {
w: l.w, h: l.h, x: l.x, y: l.y, placeholder: true, i: i
w: l.w,
h: l.h,
x: l.x,
y: l.y,
placeholder: true,
i: i
};
// Move the element to the dragged location.
const isUserAction = true;
layout = moveElement(layout, l, x, y, isUserAction, this.props.preventCollision, this.compactType(), cols);
layout = moveElement(
layout,
l,
x,
y,
isUserAction,
this.props.preventCollision,
this.compactType(),
cols
);
this.props.onDrag(layout, oldDragItem, l, placeholder, e, node);
@@ -314,27 +388,36 @@ export default class ReactGridLayout extends React.Component<Props, State> {
* @param {Event} e The mousedown event
* @param {Element} node The current dragging DOM element
*/
onDragStop(i:string, x:number, y:number, {e, node}: GridDragEvent) {
const {oldDragItem} = this.state;
let {layout} = this.state;
const {cols, preventCollision} = this.props;
onDragStop(i: string, x: number, y: number, { e, node }: GridDragEvent) {
const { oldDragItem } = this.state;
let { layout } = this.state;
const { cols, preventCollision } = this.props;
const l = getLayoutItem(layout, i);
if (!l) return;
// Move the element here
const isUserAction = true;
layout = moveElement(layout, l, x, y, isUserAction, preventCollision, this.compactType(), cols);
layout = moveElement(
layout,
l,
x,
y,
isUserAction,
preventCollision,
this.compactType(),
cols
);
this.props.onDragStop(layout, oldDragItem, l, null, e, node);
// Set state
const newLayout = compact(layout, this.compactType(), cols);
const {oldLayout} = this.state;
const { oldLayout } = this.state;
this.setState({
activeDrag: null,
layout: newLayout,
oldDragItem: null,
oldLayout: null,
oldLayout: null
});
this.onLayoutMaybeChanged(newLayout, oldLayout);
@@ -347,8 +430,8 @@ export default class ReactGridLayout extends React.Component<Props, State> {
}
}
onResizeStart(i:string, w:number, h:number, {e, node}: GridResizeEvent) {
const {layout} = this.state;
onResizeStart(i: string, w: number, h: number, { e, node }: GridResizeEvent) {
const { layout } = this.state;
var l = getLayoutItem(layout, i);
if (!l) return;
@@ -360,24 +443,47 @@ export default class ReactGridLayout extends React.Component<Props, State> {
this.props.onResizeStart(layout, l, l, null, e, node);
}
onResize(i:string, w:number, h:number, {e, node}: GridResizeEvent) {
const {layout, oldResizeItem} = this.state;
const {cols, preventCollision} = this.props;
var l = getLayoutItem(layout, i);
onResize(i: string, w: number, h: number, { e, node }: GridResizeEvent) {
const { layout, oldResizeItem } = this.state;
const { cols, preventCollision } = this.props;
const l: ?LayoutItem = getLayoutItem(layout, i);
if (!l) return;
// Short circuit if there is a collision in no rearrangement mode.
if (preventCollision && getFirstCollision(layout, {...l, w, h})) {
return;
// Something like quad tree should be used
// to find collisions faster
let hasCollisions;
if (preventCollision) {
const collisions = getAllCollisions(layout, { ...l, w, h }).filter((layoutItem) => layoutItem.i !== l.i);
hasCollisions = collisions.length > 0;
// If we're colliding, we need adjust the placeholder.
if (hasCollisions) {
// adjust w && h to maximum allowed space
let leastX = Infinity, leastY = Infinity;
collisions.forEach((layoutItem) => {
if (layoutItem.x > l.x) leastX = Math.min(leastX, layoutItem.x);
if (layoutItem.y > l.y) leastY = Math.min(leastY, layoutItem.y);
});
if (Number.isFinite(leastX)) l.w = leastX - l.x;
if (Number.isFinite(leastY)) l.h = leastY - l.y;
}
}
// Set new width and height.
l.w = w;
l.h = h;
if (!hasCollisions) {
// Set new width and height.
l.w = w;
l.h = h;
}
// Create placeholder element (display only)
var placeholder = {
w: w, h: h, x: l.x, y: l.y, static: true, i: i
w: l.w,
h: l.h,
x: l.x,
y: l.y,
static: true,
i: i
};
this.props.onResize(layout, oldResizeItem, l, placeholder, e, node);
@@ -389,16 +495,16 @@ export default class ReactGridLayout extends React.Component<Props, State> {
});
}
onResizeStop(i:string, w:number, h:number, {e, node}: GridResizeEvent) {
const {layout, oldResizeItem} = this.state;
const {cols} = this.props;
onResizeStop(i: string, w: number, h: number, { e, node }: GridResizeEvent) {
const { layout, oldResizeItem } = this.state;
const { cols } = this.props;
var l = getLayoutItem(layout, i);
this.props.onResizeStop(layout, oldResizeItem, l, null, e, node);
// Set state
const newLayout = compact(layout, this.compactType(), cols);
const {oldLayout} = this.state;
const { oldLayout } = this.state;
this.setState({
activeDrag: null,
layout: newLayout,
@@ -414,9 +520,17 @@ export default class ReactGridLayout extends React.Component<Props, State> {
* @return {Element} Placeholder div.
*/
placeholder(): ?ReactElement<any> {
const {activeDrag} = this.state;
const { activeDrag } = this.state;
if (!activeDrag) return null;
const {width, cols, margin, containerPadding, rowHeight, maxRows, useCSSTransforms} = this.props;
const {
width,
cols,
margin,
containerPadding,
rowHeight,
maxRows,
useCSSTransforms
} = this.props;
// {...this.state.activeDrag} is pretty slow, actually
return (
@@ -435,7 +549,8 @@ export default class ReactGridLayout extends React.Component<Props, State> {
rowHeight={rowHeight}
isDraggable={false}
isResizable={false}
useCSSTransforms={useCSSTransforms}>
useCSSTransforms={useCSSTransforms}
>
<div />
</GridItem>
);
@@ -447,17 +562,31 @@ export default class ReactGridLayout extends React.Component<Props, State> {
* @return {Element} Element wrapped in draggable and properly placed.
*/
processGridItem(child: ReactElement<any>): ?ReactElement<any> {
if (!child.key) return;
if (!child || !child.key) return;
const l = getLayoutItem(this.state.layout, String(child.key));
if (!l) return null;
const {width, cols, margin, containerPadding, rowHeight,
maxRows, isDraggable, isResizable, useCSSTransforms,
draggableCancel, draggableHandle} = this.props;
const {mounted} = this.state;
const {
width,
cols,
margin,
containerPadding,
rowHeight,
maxRows,
isDraggable,
isResizable,
useCSSTransforms,
draggableCancel,
draggableHandle
} = this.props;
const { mounted } = this.state;
// Parse 'static'. Any properties defined directly on the grid item will take precedence.
const draggable = Boolean(!l.static && isDraggable && (l.isDraggable || l.isDraggable == null));
const resizable = Boolean(!l.static && isResizable && (l.isResizable || l.isResizable == null));
const draggable = Boolean(
!l.static && isDraggable && (l.isDraggable || l.isDraggable == null)
);
const resizable = Boolean(
!l.static && isResizable && (l.isResizable || l.isResizable == null)
);
return (
<GridItem
@@ -479,7 +608,6 @@ export default class ReactGridLayout extends React.Component<Props, State> {
isResizable={resizable}
useCSSTransforms={useCSSTransforms && mounted}
usePercentages={!mounted}
w={l.w}
h={l.h}
x={l.x}
@@ -490,26 +618,26 @@ export default class ReactGridLayout extends React.Component<Props, State> {
maxH={l.maxH}
maxW={l.maxW}
static={l.static}
>
>
{child}
</GridItem>
);
}
render() {
const {className, style} = this.props;
const { className, style } = this.props;
const mergedClassName = classNames("react-grid-layout", className);
const mergedStyle = {
height: this.containerHeight(),
...style
};
return (
<div className={classNames('react-grid-layout', className)} style={mergedStyle}>
{
// $FlowIgnore: Appears to think map calls back w/array
React.Children.map(this.props.children, (child) => this.processGridItem(child))
}
<div className={mergedClassName} style={mergedStyle}>
{React.Children.map(this.props.children, child =>
this.processGridItem(child)
)}
{this.placeholder()}
</div>
);

View File

@@ -1,26 +1,26 @@
'use strict';
"use strict";
exports.__esModule = true;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _react = require('react');
var _react = require("react");
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes = require("prop-types");
var _propTypes2 = _interopRequireDefault(_propTypes);
var _lodash = require('lodash.isequal');
var _lodash = require("lodash.isequal");
var _lodash2 = _interopRequireDefault(_lodash);
var _utils = require('./utils');
var _utils = require("./utils");
var _responsiveUtils = require('./responsiveUtils');
var _responsiveUtils = require("./responsiveUtils");
var _ReactGridLayout = require('./ReactGridLayout');
var _ReactGridLayout = require("./ReactGridLayout");
var _ReactGridLayout2 = _interopRequireDefault(_ReactGridLayout);
@@ -34,7 +34,6 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var noop = function noop() {};
var type = function type(obj) {
return Object.prototype.toString.call(obj);
};
@@ -57,7 +56,6 @@ var ResponsiveReactGridLayout = function (_React$Component) {
_this.props.onLayoutChange(layout, _extends({}, _this.props.layouts, (_extends2 = {}, _extends2[_this.state.breakpoint] = layout, _extends2)));
}, _temp), _possibleConstructorReturn(_this, _ret);
}
// This should only include propTypes needed in this code; RGL itself
// will do validation of the rest props passed to it.
@@ -85,24 +83,21 @@ var ResponsiveReactGridLayout = function (_React$Component) {
};
ResponsiveReactGridLayout.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
// Allow parent to set width or breakpoint directly.
if (nextProps.width != this.props.width || nextProps.breakpoint !== this.props.breakpoint || !(0, _lodash2.default)(nextProps.breakpoints, this.props.breakpoints) || !(0, _lodash2.default)(nextProps.cols, this.props.cols)) {
this.onWidthChange(nextProps);
} else if (!(0, _lodash2.default)(nextProps.layouts, this.props.layouts)) {
// Allow parent to set layouts directly.
var _state = this.state,
_breakpoint = _state.breakpoint,
_cols = _state.cols;
// Since we're setting an entirely new layout object, we must generate a new responsive layout
// if one does not exist.
var newLayout = (0, _responsiveUtils.findOrGenerateResponsiveLayout)(nextProps.layouts, nextProps.breakpoints, _breakpoint, _breakpoint, _cols, nextProps.compactType);
this.setState({ layout: newLayout });
}
// Allow parent to set layouts directly.
else if (!(0, _lodash2.default)(nextProps.layouts, this.props.layouts)) {
var _state = this.state,
_breakpoint = _state.breakpoint,
_cols = _state.cols;
// Since we're setting an entirely new layout object, we must generate a new responsive layout
// if one does not exist.
var newLayout = (0, _responsiveUtils.findOrGenerateResponsiveLayout)(nextProps.layouts, nextProps.breakpoints, _breakpoint, _breakpoint, _cols, nextProps.compactType);
this.setState({ layout: newLayout });
}
};
// wrap layouts so we do not need to pass layouts to child
@@ -142,12 +137,16 @@ var ResponsiveReactGridLayout = function (_React$Component) {
this.props.onBreakpointChange(newBreakpoint, newCols);
this.props.onWidthChange(nextProps.width, nextProps.margin, newCols, nextProps.containerPadding);
this.setState({ breakpoint: newBreakpoint, layout: _layout, cols: newCols });
this.setState({
breakpoint: newBreakpoint,
layout: _layout,
cols: newCols
});
}
};
ResponsiveReactGridLayout.prototype.render = function render() {
// eslint-disable-next-line no-unused-vars
/* eslint-disable no-unused-vars */
var _props2 = this.props,
breakpoint = _props2.breakpoint,
breakpoints = _props2.breakpoints,
@@ -156,7 +155,8 @@ var ResponsiveReactGridLayout = function (_React$Component) {
onBreakpointChange = _props2.onBreakpointChange,
onLayoutChange = _props2.onLayoutChange,
onWidthChange = _props2.onWidthChange,
other = _objectWithoutProperties(_props2, ['breakpoint', 'breakpoints', 'cols', 'layouts', 'onBreakpointChange', 'onLayoutChange', 'onWidthChange']);
other = _objectWithoutProperties(_props2, ["breakpoint", "breakpoints", "cols", "layouts", "onBreakpointChange", "onLayoutChange", "onWidthChange"]);
/* eslint-enable no-unused-vars */
return _react2.default.createElement(_ReactGridLayout2.default, _extends({}, other, {
onLayoutChange: this.onLayoutChange,
@@ -169,7 +169,6 @@ var ResponsiveReactGridLayout = function (_React$Component) {
}(_react2.default.Component);
ResponsiveReactGridLayout.propTypes = {
//
// Basic props
//
@@ -187,14 +186,14 @@ ResponsiveReactGridLayout.propTypes = {
// layouts is an object mapping breakpoints to layouts.
// e.g. {lg: Layout, md: Layout, ...}
layouts: function layouts(props, propName) {
if (type(props[propName]) !== '[object Object]') {
throw new Error('Layout property must be an object. Received: ' + type(props[propName]));
if (type(props[propName]) !== "[object Object]") {
throw new Error("Layout property must be an object. Received: " + type(props[propName]));
}
Object.keys(props[propName]).forEach(function (key) {
if (!(key in props.breakpoints)) {
throw new Error('Each key in layouts must align with a key in breakpoints.');
throw new Error("Each key in layouts must align with a key in breakpoints.");
}
(0, _utils.validateLayout)(props.layouts[key], 'layouts.' + key);
(0, _utils.validateLayout)(props.layouts[key], "layouts." + key);
});
},
@@ -221,8 +220,8 @@ ResponsiveReactGridLayout.defaultProps = {
breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 },
cols: { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 },
layouts: {},
onBreakpointChange: noop,
onLayoutChange: noop,
onWidthChange: noop
onBreakpointChange: _utils.noop,
onLayoutChange: _utils.noop,
onWidthChange: _utils.noop
};
exports.default = ResponsiveReactGridLayout;

View File

@@ -1,16 +1,24 @@
// @flow
import React from 'react';
import PropTypes from 'prop-types';
import isEqual from 'lodash.isequal';
import React from "react";
import PropTypes from "prop-types";
import isEqual from "lodash.isequal";
import {cloneLayout, synchronizeLayoutWithChildren, validateLayout} from './utils';
import {getBreakpointFromWidth, getColsFromBreakpoint, findOrGenerateResponsiveLayout} from './responsiveUtils';
import ReactGridLayout from './ReactGridLayout';
import type {Props as RGLProps} from './ReactGridLayout';
import type {Layout} from './utils';
import {
cloneLayout,
synchronizeLayoutWithChildren,
validateLayout,
noop
} from "./utils";
import {
getBreakpointFromWidth,
getColsFromBreakpoint,
findOrGenerateResponsiveLayout
} from "./responsiveUtils";
import ReactGridLayout from "./ReactGridLayout";
import type { Props as RGLProps } from "./ReactGridLayout";
import type { Layout } from "./utils";
const noop = function(){};
const type = (obj) => Object.prototype.toString.call(obj);
const type = obj => Object.prototype.toString.call(obj);
type State = {
layout: Layout,
@@ -23,24 +31,29 @@ type Props<Breakpoint: string = string> = {
// Responsive config
breakpoint: Breakpoint,
breakpoints: {[key: Breakpoint]: number},
cols: {[key: Breakpoint]: number},
layouts: {[key: Breakpoint]: Layout},
breakpoints: { [key: Breakpoint]: number },
cols: { [key: Breakpoint]: number },
layouts: { [key: Breakpoint]: Layout },
width: number,
// Callbacks
onBreakpointChange: (Breakpoint, cols: number) => void,
onLayoutChange: (Layout, {[key: Breakpoint]: Layout}) => void,
onWidthChange:
(containerWidth: number, margin: [number, number], cols: number, containerPadding: [number, number]) => void
onLayoutChange: (Layout, { [key: Breakpoint]: Layout }) => void,
onWidthChange: (
containerWidth: number,
margin: [number, number],
cols: number,
containerPadding: [number, number] | null
) => void
};
export default class ResponsiveReactGridLayout extends React.Component<Props<>, State> {
export default class ResponsiveReactGridLayout extends React.Component<
Props<>,
State
> {
// This should only include propTypes needed in this code; RGL itself
// will do validation of the rest props passed to it.
static propTypes = {
//
// Basic props
//
@@ -57,15 +70,20 @@ export default class ResponsiveReactGridLayout extends React.Component<Props<>,
// layouts is an object mapping breakpoints to layouts.
// e.g. {lg: Layout, md: Layout, ...}
layouts(props, propName) {
if (type(props[propName]) !== '[object Object]') {
throw new Error('Layout property must be an object. Received: ' + type(props[propName]));
layouts(props: Props<>, propName: string) {
if (type(props[propName]) !== "[object Object]") {
throw new Error(
"Layout property must be an object. Received: " +
type(props[propName])
);
}
Object.keys(props[propName]).forEach((key) => {
Object.keys(props[propName]).forEach(key => {
if (!(key in props.breakpoints)) {
throw new Error('Each key in layouts must align with a key in breakpoints.');
throw new Error(
"Each key in layouts must align with a key in breakpoints."
);
}
validateLayout(props.layouts[key], 'layouts.' + key);
validateLayout(props.layouts[key], "layouts." + key);
});
},
@@ -89,26 +107,33 @@ export default class ResponsiveReactGridLayout extends React.Component<Props<>,
};
static defaultProps = {
breakpoints: {lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0},
cols: {lg: 12, md: 10, sm: 6, xs: 4, xxs: 2},
breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 },
cols: { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 },
layouts: {},
onBreakpointChange: noop,
onLayoutChange: noop,
onWidthChange: noop,
onWidthChange: noop
};
state = this.generateInitialState();
generateInitialState(): State {
const {width, breakpoints, layouts, cols} = this.props;
const { width, breakpoints, layouts, cols } = this.props;
const breakpoint = getBreakpointFromWidth(breakpoints, width);
const colNo = getColsFromBreakpoint(breakpoint, cols);
// verticalCompact compatibility, now deprecated
const compactType = this.props.verticalCompact === false ? null : this.props.compactType;
const compactType =
this.props.verticalCompact === false ? null : this.props.compactType;
// Get the initial layout. This can tricky; we try to generate one however possible if one doesn't exist
// for this layout.
const initialLayout = findOrGenerateResponsiveLayout(layouts, breakpoints, breakpoint,
breakpoint, colNo, compactType);
const initialLayout = findOrGenerateResponsiveLayout(
layouts,
breakpoints,
breakpoint,
breakpoint,
colNo,
compactType
);
return {
layout: initialLayout,
@@ -118,34 +143,38 @@ export default class ResponsiveReactGridLayout extends React.Component<Props<>,
}
componentWillReceiveProps(nextProps: Props<*>) {
// Allow parent to set width or breakpoint directly.
if (
nextProps.width != this.props.width
|| nextProps.breakpoint !== this.props.breakpoint
|| !isEqual(nextProps.breakpoints, this.props.breakpoints)
|| !isEqual(nextProps.cols, this.props.cols)
nextProps.width != this.props.width ||
nextProps.breakpoint !== this.props.breakpoint ||
!isEqual(nextProps.breakpoints, this.props.breakpoints) ||
!isEqual(nextProps.cols, this.props.cols)
) {
this.onWidthChange(nextProps);
}
// Allow parent to set layouts directly.
else if (!isEqual(nextProps.layouts, this.props.layouts)) {
const {breakpoint, cols} = this.state;
} else if (!isEqual(nextProps.layouts, this.props.layouts)) {
// Allow parent to set layouts directly.
const { breakpoint, cols } = this.state;
// Since we're setting an entirely new layout object, we must generate a new responsive layout
// if one does not exist.
const newLayout = findOrGenerateResponsiveLayout(
nextProps.layouts, nextProps.breakpoints,
breakpoint, breakpoint, cols, nextProps.compactType
nextProps.layouts,
nextProps.breakpoints,
breakpoint,
breakpoint,
cols,
nextProps.compactType
);
this.setState({layout: newLayout});
this.setState({ layout: newLayout });
}
}
// wrap layouts so we do not need to pass layouts to child
onLayoutChange = (layout: Layout) => {
this.props.onLayoutChange(layout, {...this.props.layouts, [this.state.breakpoint]: layout});
this.props.onLayoutChange(layout, {
...this.props.layouts,
[this.state.breakpoint]: layout
});
};
/**
@@ -153,23 +182,41 @@ export default class ResponsiveReactGridLayout extends React.Component<Props<>,
* Width changes are necessary to figure out the widget widths.
*/
onWidthChange(nextProps: Props<*>) {
const {breakpoints, cols, layouts, compactType} = nextProps;
const newBreakpoint = nextProps.breakpoint || getBreakpointFromWidth(nextProps.breakpoints, nextProps.width);
const { breakpoints, cols, layouts, compactType } = nextProps;
const newBreakpoint =
nextProps.breakpoint ||
getBreakpointFromWidth(nextProps.breakpoints, nextProps.width);
const lastBreakpoint = this.state.breakpoint;
// Breakpoint change
if (lastBreakpoint !== newBreakpoint || this.props.breakpoints !== breakpoints || this.props.cols !== cols) {
if (
lastBreakpoint !== newBreakpoint ||
this.props.breakpoints !== breakpoints ||
this.props.cols !== cols
) {
// Preserve the current layout if the current breakpoint is not present in the next layouts.
if (!(lastBreakpoint in layouts)) layouts[lastBreakpoint] = cloneLayout(this.state.layout);
if (!(lastBreakpoint in layouts))
layouts[lastBreakpoint] = cloneLayout(this.state.layout);
// Find or generate a new layout.
const newCols: number = getColsFromBreakpoint(newBreakpoint, cols);
let layout = findOrGenerateResponsiveLayout(layouts, breakpoints, newBreakpoint,
lastBreakpoint, newCols, compactType);
let layout = findOrGenerateResponsiveLayout(
layouts,
breakpoints,
newBreakpoint,
lastBreakpoint,
newCols,
compactType
);
// This adds missing items.
layout = synchronizeLayoutWithChildren(layout, nextProps.children, newCols, compactType);
layout = synchronizeLayoutWithChildren(
layout,
nextProps.children,
newCols,
compactType
);
// Store the new layout.
layouts[newBreakpoint] = layout;
@@ -177,16 +224,34 @@ export default class ResponsiveReactGridLayout extends React.Component<Props<>,
// callbacks
this.props.onLayoutChange(layout, layouts);
this.props.onBreakpointChange(newBreakpoint, newCols);
this.props.onWidthChange(nextProps.width, nextProps.margin, newCols, nextProps.containerPadding);
this.props.onWidthChange(
nextProps.width,
nextProps.margin,
newCols,
nextProps.containerPadding
);
this.setState({breakpoint: newBreakpoint, layout: layout, cols: newCols});
this.setState({
breakpoint: newBreakpoint,
layout: layout,
cols: newCols
});
}
}
render() {
// eslint-disable-next-line no-unused-vars
const {breakpoint, breakpoints, cols, layouts, onBreakpointChange, onLayoutChange, onWidthChange,
...other} = this.props;
/* eslint-disable no-unused-vars */
const {
breakpoint,
breakpoints,
cols,
layouts,
onBreakpointChange,
onLayoutChange,
onWidthChange,
...other
} = this.props;
/* eslint-enable no-unused-vars */
return (
<ReactGridLayout

View File

@@ -1,23 +1,27 @@
'use strict';
"use strict";
exports.__esModule = true;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _react = require('react');
exports.default = WidthProvider;
var _react = require("react");
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes = require("prop-types");
var _propTypes2 = _interopRequireDefault(_propTypes);
var _reactDom = require('react-dom');
var _reactDom = require("react-dom");
var _reactDom2 = _interopRequireDefault(_reactDom);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
@@ -27,16 +31,16 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function"
/*
* A simple HOC that provides facility for listening to container resizes.
*/
var WidthProvider = function WidthProvider(ComposedComponent) {
function WidthProvider(ComposedComponent) {
var _class, _temp2;
return _temp2 = _class = function (_React$Component) {
_inherits(_class, _React$Component);
_inherits(WidthProvider, _React$Component);
function _class() {
function WidthProvider() {
var _temp, _this, _ret;
_classCallCheck(this, _class);
_classCallCheck(this, WidthProvider);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
@@ -44,37 +48,42 @@ var WidthProvider = function WidthProvider(ComposedComponent) {
return _ret = (_temp = (_this = _possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this), _this.state = {
width: 1280
}, _this.mounted = false, _this.onWindowResize = function (_event) {
}, _this.mounted = false, _this.onWindowResize = function () {
if (!_this.mounted) return;
// eslint-disable-next-line
var node = _reactDom2.default.findDOMNode(_this); // Flow casts this to Text | Element
if (node instanceof HTMLElement) _this.setState({ width: node.offsetWidth });
}, _temp), _possibleConstructorReturn(_this, _ret);
}
_class.prototype.componentDidMount = function componentDidMount() {
WidthProvider.prototype.componentDidMount = function componentDidMount() {
this.mounted = true;
window.addEventListener('resize', this.onWindowResize);
window.addEventListener("resize", this.onWindowResize);
// Call to properly set the breakpoint and resize the elements.
// Note that if you're doing a full-width element, this can get a little wonky if a scrollbar
// appears because of the grid. In that case, fire your own resize event, or set `overflow: scroll` on your body.
this.onWindowResize();
};
_class.prototype.componentWillUnmount = function componentWillUnmount() {
WidthProvider.prototype.componentWillUnmount = function componentWillUnmount() {
this.mounted = false;
window.removeEventListener('resize', this.onWindowResize);
window.removeEventListener("resize", this.onWindowResize);
};
_class.prototype.render = function render() {
if (this.props.measureBeforeMount && !this.mounted) {
return _react2.default.createElement('div', { className: this.props.className, style: this.props.style });
WidthProvider.prototype.render = function render() {
var _props = this.props,
measureBeforeMount = _props.measureBeforeMount,
rest = _objectWithoutProperties(_props, ["measureBeforeMount"]);
if (measureBeforeMount && !this.mounted) {
return _react2.default.createElement("div", { className: this.props.className, style: this.props.style });
}
return _react2.default.createElement(ComposedComponent, _extends({}, this.props, this.state));
return _react2.default.createElement(ComposedComponent, _extends({}, rest, this.state));
};
return _class;
return WidthProvider;
}(_react2.default.Component), _class.defaultProps = {
measureBeforeMount: false
}, _class.propTypes = {
@@ -82,6 +91,4 @@ var WidthProvider = function WidthProvider(ComposedComponent) {
// rendering, to prevent any unsightly resizing.
measureBeforeMount: _propTypes2.default.bool
}, _temp2;
};
exports.default = WidthProvider;
}

View File

@@ -1,69 +1,77 @@
// @flow
import React from "react";
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import type {ComponentType as ReactComponentType} from 'react';
import PropTypes from "prop-types";
import ReactDOM from "react-dom";
import type { ComponentType as ReactComponentType } from "react";
type Props = {
type WPProps = {
className?: string,
measureBeforeMount: boolean,
style?: Object,
style?: Object
};
type State = {
type WPState = {
width: number
};
/*
* A simple HOC that provides facility for listening to container resizes.
*/
type ProviderT = (ComposedComponent: ReactComponentType<any>) => ReactComponentType<any>;
const WidthProvider: ProviderT = (ComposedComponent) => class extends React.Component<Props, State> {
export default function WidthProvider<
Props,
ComposedProps: { ...Props, ...WPProps }
>(
ComposedComponent: ReactComponentType<Props>
): ReactComponentType<ComposedProps> {
return class WidthProvider extends React.Component<ComposedProps, WPState> {
static defaultProps = {
measureBeforeMount: false
};
static defaultProps = {
measureBeforeMount: false
};
static propTypes = {
// If true, will not render children until mounted. Useful for getting the exact width before
// rendering, to prevent any unsightly resizing.
measureBeforeMount: PropTypes.bool
};
static propTypes = {
// If true, will not render children until mounted. Useful for getting the exact width before
// rendering, to prevent any unsightly resizing.
measureBeforeMount: PropTypes.bool
};
state = {
width: 1280
};
state: State = {
width: 1280
};
mounted: boolean = false;
mounted: boolean = false;
componentDidMount() {
this.mounted = true;
componentDidMount() {
this.mounted = true;
window.addEventListener('resize', this.onWindowResize);
// Call to properly set the breakpoint and resize the elements.
// Note that if you're doing a full-width element, this can get a little wonky if a scrollbar
// appears because of the grid. In that case, fire your own resize event, or set `overflow: scroll` on your body.
this.onWindowResize();
}
componentWillUnmount() {
this.mounted = false;
window.removeEventListener('resize', this.onWindowResize);
}
onWindowResize = (_event: ?Event) => {
if (!this.mounted) return;
const node = ReactDOM.findDOMNode(this); // Flow casts this to Text | Element
if (node instanceof HTMLElement) this.setState({width: node.offsetWidth});
}
render() {
if (this.props.measureBeforeMount && !this.mounted) {
return <div className={this.props.className} style={this.props.style} />;
window.addEventListener("resize", this.onWindowResize);
// Call to properly set the breakpoint and resize the elements.
// Note that if you're doing a full-width element, this can get a little wonky if a scrollbar
// appears because of the grid. In that case, fire your own resize event, or set `overflow: scroll` on your body.
this.onWindowResize();
}
return <ComposedComponent {...this.props} {...this.state} />;
}
};
componentWillUnmount() {
this.mounted = false;
window.removeEventListener("resize", this.onWindowResize);
}
export default WidthProvider;
onWindowResize = () => {
if (!this.mounted) return;
// eslint-disable-next-line
const node = ReactDOM.findDOMNode(this); // Flow casts this to Text | Element
if (node instanceof HTMLElement)
this.setState({ width: node.offsetWidth });
};
render() {
const { measureBeforeMount, ...rest } = this.props;
if (measureBeforeMount && !this.mounted) {
return (
<div className={this.props.className} style={this.props.style} />
);
}
return <ComposedComponent {...rest} {...this.state} />;
}
};
}

View File

@@ -1,4 +1,4 @@
'use strict';
"use strict";
exports.__esModule = true;
exports.getBreakpointFromWidth = getBreakpointFromWidth;
@@ -6,7 +6,7 @@ exports.getColsFromBreakpoint = getColsFromBreakpoint;
exports.findOrGenerateResponsiveLayout = findOrGenerateResponsiveLayout;
exports.sortBreakpoints = sortBreakpoints;
var _utils = require('./utils');
var _utils = require("./utils");
/**
* Given a width, find the highest breakpoint that matches is valid for it (width > breakpoint).

View File

@@ -1,11 +1,26 @@
// @flow
import {cloneLayout, compact, correctBounds} from './utils';
import { cloneLayout, compact, correctBounds } from "./utils";
import type { CompactType, Layout } from "./utils";
export type ResponsiveLayout = {
lg?: Layout,
md?: Layout,
sm?: Layout,
xs?: Layout,
xxs?: Layout
};
import type {CompactType, Layout} from './utils';
export type ResponsiveLayout = {lg?: Layout, md?: Layout, sm?: Layout, xs?: Layout, xxs?: Layout};
type Breakpoint = string;
type Breakpoints = {lg?: number, md?: number, sm?: number, xs?: number, xxs?: number};
type Breakpoints = {
lg?: number,
md?: number,
sm?: number,
xs?: number,
xxs?: number
};
/**
* Given a width, find the highest breakpoint that matches is valid for it (width > breakpoint).
@@ -14,7 +29,10 @@ type Breakpoints = {lg?: number, md?: number, sm?: number, xs?: number, xxs?: nu
* @param {Number} width Screen width.
* @return {String} Highest breakpoint that is less than width.
*/
export function getBreakpointFromWidth(breakpoints: Breakpoints, width: number): Breakpoint {
export function getBreakpointFromWidth(
breakpoints: Breakpoints,
width: number
): Breakpoint {
const sorted = sortBreakpoints(breakpoints);
let matching = sorted[0];
for (let i = 1, len = sorted.length; i < len; i++) {
@@ -24,16 +42,22 @@ export function getBreakpointFromWidth(breakpoints: Breakpoints, width: number):
return matching;
}
/**
* Given a breakpoint, get the # of cols set for it.
* @param {String} breakpoint Breakpoint name.
* @param {Object} cols Map of breakpoints to cols.
* @return {Number} Number of cols.
*/
export function getColsFromBreakpoint(breakpoint: Breakpoint, cols: Breakpoints): number {
export function getColsFromBreakpoint(
breakpoint: Breakpoint,
cols: Breakpoints
): number {
if (!cols[breakpoint]) {
throw new Error("ResponsiveReactGridLayout: `cols` entry for breakpoint " + breakpoint + " is missing!");
throw new Error(
"ResponsiveReactGridLayout: `cols` entry for breakpoint " +
breakpoint +
" is missing!"
);
}
return cols[breakpoint];
}
@@ -52,15 +76,22 @@ export function getColsFromBreakpoint(breakpoint: Breakpoint, cols: Breakpoints)
* vertically.
* @return {Array} New layout.
*/
export function findOrGenerateResponsiveLayout(layouts: ResponsiveLayout, breakpoints: Breakpoints,
breakpoint: Breakpoint, lastBreakpoint: Breakpoint,
cols: number, compactType: CompactType): Layout {
export function findOrGenerateResponsiveLayout(
layouts: ResponsiveLayout,
breakpoints: Breakpoints,
breakpoint: Breakpoint,
lastBreakpoint: Breakpoint,
cols: number,
compactType: CompactType
): Layout {
// If it already exists, just return it.
if (layouts[breakpoint]) return cloneLayout(layouts[breakpoint]);
// Find or generate the next layout
let layout = layouts[lastBreakpoint];
const breakpointsSorted = sortBreakpoints(breakpoints);
const breakpointsAbove = breakpointsSorted.slice(breakpointsSorted.indexOf(breakpoint));
const breakpointsAbove = breakpointsSorted.slice(
breakpointsSorted.indexOf(breakpoint)
);
for (let i = 0, len = breakpointsAbove.length; i < len; i++) {
const b = breakpointsAbove[i];
if (layouts[b]) {
@@ -69,7 +100,7 @@ export function findOrGenerateResponsiveLayout(layouts: ResponsiveLayout, breakp
}
}
layout = cloneLayout(layout || []); // clone layout so we don't modify existing items
return compact(correctBounds(layout, {cols: cols}), compactType, cols);
return compact(correctBounds(layout, { cols: cols }), compactType, cols);
}
/**

View File

@@ -1,6 +1,7 @@
'use strict';
"use strict";
exports.__esModule = true;
exports.noop = undefined;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
@@ -28,18 +29,19 @@ exports.synchronizeLayoutWithChildren = synchronizeLayoutWithChildren;
exports.validateLayout = validateLayout;
exports.autoBindHandlers = autoBindHandlers;
var _lodash = require('lodash.isequal');
var _lodash = require("lodash.isequal");
var _lodash2 = _interopRequireDefault(_lodash);
var _react = require('react');
var _react = require("react");
var _react2 = _interopRequireDefault(_react);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// All callbacks are of the signature (layout, oldItem, newItem, placeholder, e).
var isProduction = process.env.NODE_ENV === 'production';
var isProduction = process.env.NODE_ENV === "production";
var DEBUG = false;
/**
* Return the bottom coordinate of the layout.
@@ -68,11 +70,20 @@ function cloneLayout(layout) {
// Fast path to cloning, since this is monomorphic
function cloneLayoutItem(layoutItem) {
return {
w: layoutItem.w, h: layoutItem.h, x: layoutItem.x, y: layoutItem.y, i: layoutItem.i,
minW: layoutItem.minW, maxW: layoutItem.maxW, minH: layoutItem.minH, maxH: layoutItem.maxH,
moved: Boolean(layoutItem.moved), static: Boolean(layoutItem.static),
w: layoutItem.w,
h: layoutItem.h,
x: layoutItem.x,
y: layoutItem.y,
i: layoutItem.i,
minW: layoutItem.minW,
maxW: layoutItem.maxW,
minH: layoutItem.minH,
maxH: layoutItem.maxH,
moved: Boolean(layoutItem.moved),
static: Boolean(layoutItem.static),
// These can be null
isDraggable: layoutItem.isDraggable, isResizable: layoutItem.isResizable
isDraggable: layoutItem.isDraggable,
isResizable: layoutItem.isResizable
};
}
@@ -81,7 +92,6 @@ function cloneLayoutItem(layoutItem) {
* This will catch differences in keys, order, and length.
*/
function childrenEqual(a, b) {
// $FlowIgnore: Appears to think map calls back w/array
return (0, _lodash2.default)(_react2.default.Children.map(a, function (c) {
return c.key;
}), _react2.default.Children.map(b, function (c) {
@@ -123,7 +133,7 @@ function compact(layout, compactType, cols) {
// Don't move static elements
if (!l.static) {
l = compactItem(compareWith, l, compactType, cols);
l = compactItem(compareWith, l, compactType, cols, sorted);
// Add to comparison array. We only collide with items before this one.
// Statics are already in this array.
@@ -140,12 +150,36 @@ function compact(layout, compactType, cols) {
return out;
}
var heightWidth = { x: "w", y: "h" };
/**
* Before moving item down, it will check if the movement will cause collisions and move those items down before.
*/
function resolveCompactionCollision(layout, item, moveToCoord, axis) {
var sizeProp = heightWidth[axis];
item[axis] += 1;
var itemIndex = layout.indexOf(item);
// Go through each item we collide with.
for (var _i4 = itemIndex + 1; _i4 < layout.length; _i4++) {
var otherItem = layout[_i4];
// Ignore static items
if (otherItem.static) continue;
if (collides(item, otherItem)) {
resolveCompactionCollision(layout, otherItem, moveToCoord + item[sizeProp], axis);
}
}
item[axis] = moveToCoord;
}
/**
* Compact an item in the layout.
*/
function compactItem(compareWith, l, compactType, cols) {
var compactV = compactType === 'vertical';
var compactH = compactType === 'horizontal';
function compactItem(compareWith, l, compactType, cols, fullLayout) {
var compactV = compactType === "vertical";
var compactH = compactType === "horizontal";
if (compactV) {
// Bottom 'y' possible is the bottom of the layout.
// This allows you to do nice stuff like specify {y: Infinity}
@@ -167,9 +201,9 @@ function compactItem(compareWith, l, compactType, cols) {
var collides = void 0;
while (collides = getFirstCollision(compareWith, l)) {
if (compactH) {
l.x = collides.x + collides.w;
resolveCompactionCollision(fullLayout, l, collides.x + collides.w, "x");
} else {
l.y = collides.y + collides.h;
resolveCompactionCollision(fullLayout, l, collides.y + collides.h, "y");
}
// Since we can't grow without bounds horizontally, if we've overflown, let's move it down and try again.
if (compactH && l.x + l.w > cols) {
@@ -188,8 +222,8 @@ function compactItem(compareWith, l, compactType, cols) {
*/
function correctBounds(layout, bounds) {
var collidesWith = getStatics(layout);
for (var _i4 = 0, len = layout.length; _i4 < len; _i4++) {
var l = layout[_i4];
for (var _i5 = 0, len = layout.length; _i5 < len; _i5++) {
var l = layout[_i5];
// Overflows right
if (l.x + l.w > bounds.cols) l.x = bounds.cols - l.w;
// Overflows left
@@ -216,8 +250,8 @@ function correctBounds(layout, bounds) {
* @return {LayoutItem} Item at ID.
*/
function getLayoutItem(layout, id) {
for (var _i5 = 0, len = layout.length; _i5 < len; _i5++) {
if (layout[_i5].i === id) return layout[_i5];
for (var _i6 = 0, len = layout.length; _i6 < len; _i6++) {
if (layout[_i6].i === id) return layout[_i6];
}
}
@@ -230,8 +264,8 @@ function getLayoutItem(layout, id) {
* @return {Object|undefined} A colliding layout item, or undefined.
*/
function getFirstCollision(layout, layoutItem) {
for (var _i6 = 0, len = layout.length; _i6 < len; _i6++) {
if (collides(layout[_i6], layoutItem)) return layout[_i6];
for (var _i7 = 0, len = layout.length; _i7 < len; _i7++) {
if (collides(layout[_i7], layoutItem)) return layout[_i7];
}
}
@@ -259,11 +293,10 @@ function getStatics(layout) {
* @param {LayoutItem} l element to move.
* @param {Number} [x] X position in grid units.
* @param {Number} [y] Y position in grid units.
* @param {Boolean} [isUserAction] If true, designates that the item we're moving is
* being dragged/resized by the user.
*/
function moveElement(layout, l, x, y, isUserAction, preventCollision, compactType, cols) {
if (l.static) return layout;
log("Moving element " + l.i + " to [" + x + "," + y + "] from [" + l.x + "," + l.y + "]");
// Short-circuit if nothing to do.
if (l.y === y && l.x === x) return layout;
@@ -271,10 +304,9 @@ function moveElement(layout, l, x, y, isUserAction, preventCollision, compactTyp
var oldX = l.x;
var oldY = l.y;
var movingUp = y && l.y > y;
// This is quite a bit faster than extending the object
if (typeof x === 'number') l.x = x;
if (typeof y === 'number') l.y = y;
l.x = x;
l.y = y;
l.moved = true;
// If this collides with anything, move it.
@@ -282,11 +314,13 @@ function moveElement(layout, l, x, y, isUserAction, preventCollision, compactTyp
// to ensure, in the case of multiple collisions, that we're getting the
// nearest collision.
var sorted = sortLayoutItems(layout, compactType);
var movingUp = compactType === "vertical" ? oldY >= y : compactType === "horizontal" ? oldX >= x : false;
if (movingUp) sorted = sorted.reverse();
var collisions = getAllCollisions(sorted, l);
// There was a collision; abort
if (preventCollision && collisions.length) {
log("Collision prevented on " + l.i + ", reverting.");
l.x = oldX;
l.y = oldY;
l.moved = false;
@@ -294,17 +328,13 @@ function moveElement(layout, l, x, y, isUserAction, preventCollision, compactTyp
}
// Move each item that collides away from this element.
for (var _i7 = 0, len = collisions.length; _i7 < len; _i7++) {
var collision = collisions[_i7];
// console.log('resolving collision between', l.i, 'at', l.y, 'and', collision.i, 'at', collision.y);
for (var _i8 = 0, len = collisions.length; _i8 < len; _i8++) {
var collision = collisions[_i8];
log("Resolving collision between " + l.i + " at [" + l.x + "," + l.y + "] and " + collision.i + " at [" + collision.x + "," + collision.y + "]");
// Short circuit so we can't infinite loop
if (collision.moved) continue;
// This makes it feel a bit more precise by waiting to swap for just a bit when moving up.
if (l.y > collision.y && l.y - collision.y > collision.h / 4) continue;
if (l.x > collision.x && l.x - collision.x > collision.w / 4) continue;
// Don't move static items - we have to move *this* element away
if (collision.static) {
layout = moveElementAwayFromCollision(layout, collision, l, isUserAction, compactType, cols);
@@ -323,35 +353,36 @@ function moveElement(layout, l, x, y, isUserAction, preventCollision, compactTyp
* @param {Array} layout Full layout to modify.
* @param {LayoutItem} collidesWith Layout item we're colliding with.
* @param {LayoutItem} itemToMove Layout item we're moving.
* @param {Boolean} [isUserAction] If true, designates that the item we're moving is being dragged/resized
* by the user.
*/
function moveElementAwayFromCollision(layout, collidesWith, itemToMove, isUserAction, compactType, cols) {
var compactH = compactType === 'horizontal';
var compactV = compactType === 'vertical';
var compactH = compactType === "horizontal";
var compactV = compactType === "vertical";
var preventCollision = false; // we're already colliding
// If there is enough space above the collision to put this element, move it there.
// We only do this on the main collision as this can get funky in cascades and cause
// unwanted swapping behavior.
if (isUserAction) {
// Reset isUserAction flag because we're not in the main collision anymore.
isUserAction = false;
// Make a mock item so we don't modify the item here, only modify in moveElement.
var fakeItem = {
x: compactH ? Math.max(collidesWith.x - itemToMove.w, 0) : itemToMove.x,
y: !compactH ? Math.max(collidesWith.y - itemToMove.h, 0) : itemToMove.y,
w: itemToMove.w,
h: itemToMove.h,
i: '-1'
i: "-1"
};
// No collision? If so, we can go up there; otherwise, we'll end up moving down as normal
if (!getFirstCollision(layout, fakeItem)) {
return moveElement(layout, itemToMove, compactH ? fakeItem.x : undefined, compactV ? fakeItem.y + 1 : undefined, isUserAction, preventCollision, compactType, cols);
log("Doing reverse collision on " + itemToMove.i + " up to [" + fakeItem.x + "," + fakeItem.y + "].");
return moveElement(layout, itemToMove, fakeItem.x, fakeItem.y, isUserAction, preventCollision, compactType, cols);
}
}
// Previously this was optimized to move below the collision directly, but this can cause problems
// with cascading moves, as an item may actually leapflog a collision and cause a reversal in order.
return moveElement(layout, itemToMove, compactH ? itemToMove.x + 1 : undefined, compactV ? itemToMove.y + 1 : undefined, isUserAction, preventCollision, compactType, cols);
return moveElement(layout, itemToMove, compactH ? collidesWith.x + collidesWith.w : itemToMove.x, compactV ? collidesWith.y + collidesWith.h : itemToMove.y, isUserAction, preventCollision, compactType, cols);
}
/**
@@ -361,7 +392,7 @@ function moveElementAwayFromCollision(layout, collidesWith, itemToMove, isUserAc
* @return {String} That number as a percentage.
*/
function perc(num) {
return num * 100 + '%';
return num * 100 + "%";
}
function setTransform(_ref) {
@@ -371,16 +402,16 @@ function setTransform(_ref) {
height = _ref.height;
// Replace unitless items with px
var translate = 'translate(' + left + 'px,' + top + 'px)';
var translate = "translate(" + left + "px," + top + "px)";
return {
transform: translate,
WebkitTransform: translate,
MozTransform: translate,
msTransform: translate,
OTransform: translate,
width: width + 'px',
height: height + 'px',
position: 'absolute'
width: width + "px",
height: height + "px",
position: "absolute"
};
}
@@ -391,11 +422,11 @@ function setTopLeft(_ref2) {
height = _ref2.height;
return {
top: top + 'px',
left: left + 'px',
width: width + 'px',
height: height + 'px',
position: 'absolute'
top: top + "px",
left: left + "px",
width: width + "px",
height: height + "px",
position: "absolute"
};
}
@@ -406,7 +437,7 @@ function setTopLeft(_ref2) {
* @return {Array} Layout, sorted static items first.
*/
function sortLayoutItems(layout, compactType) {
if (compactType === 'horizontal') return sortLayoutItemsByColRow(layout);else return sortLayoutItemsByRowCol(layout);
if (compactType === "horizontal") return sortLayoutItemsByColRow(layout);else return sortLayoutItemsByRowCol(layout);
}
function sortLayoutItemsByRowCol(layout) {
@@ -451,20 +482,26 @@ function synchronizeLayoutWithChildren(initialLayout, children, cols, compactTyp
layout[i] = cloneLayoutItem(exists);
} else {
if (!isProduction && child.props._grid) {
console.warn('`_grid` properties on children have been deprecated as of React 15.2. ' + // eslint-disable-line
'Please use `data-grid` or add your properties directly to the `layout`.');
console.warn("`_grid` properties on children have been deprecated as of React 15.2. " + // eslint-disable-line
"Please use `data-grid` or add your properties directly to the `layout`.");
}
var g = child.props['data-grid'] || child.props._grid;
var g = child.props["data-grid"] || child.props._grid;
// Hey, this item has a data-grid property, use it.
if (g) {
if (!isProduction) {
validateLayout([g], 'ReactGridLayout.children');
validateLayout([g], "ReactGridLayout.children");
}
layout[i] = cloneLayoutItem(_extends({}, g, { i: child.key }));
} else {
// Nothing provided: ensure this is added to the bottom
layout[i] = cloneLayoutItem({ w: 1, h: 1, x: 0, y: bottom(layout), i: String(child.key) });
layout[i] = cloneLayoutItem({
w: 1,
h: 1,
x: 0,
y: bottom(layout),
i: String(child.key)
});
}
}
});
@@ -483,22 +520,23 @@ function synchronizeLayoutWithChildren(initialLayout, children, cols, compactTyp
* @param {String} [contextName] Context name for errors.
* @throw {Error} Validation error.
*/
function validateLayout(layout, contextName) {
contextName = contextName || "Layout";
var subProps = ['x', 'y', 'w', 'h'];
function validateLayout(layout) {
var contextName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "Layout";
var subProps = ["x", "y", "w", "h"];
if (!Array.isArray(layout)) throw new Error(contextName + " must be an array!");
for (var _i8 = 0, len = layout.length; _i8 < len; _i8++) {
var item = layout[_i8];
for (var _i9 = 0, len = layout.length; _i9 < len; _i9++) {
var item = layout[_i9];
for (var j = 0; j < subProps.length; j++) {
if (typeof item[subProps[j]] !== 'number') {
throw new Error('ReactGridLayout: ' + contextName + '[' + _i8 + '].' + subProps[j] + ' must be a number!');
if (typeof item[subProps[j]] !== "number") {
throw new Error("ReactGridLayout: " + contextName + "[" + _i9 + "]." + subProps[j] + " must be a number!");
}
}
if (item.i && typeof item.i !== 'string') {
throw new Error('ReactGridLayout: ' + contextName + '[' + _i8 + '].i must be a string!');
if (item.i && typeof item.i !== "string") {
throw new Error("ReactGridLayout: " + contextName + "[" + _i9 + "].i must be a string!");
}
if (item.static !== undefined && typeof item.static !== 'boolean') {
throw new Error('ReactGridLayout: ' + contextName + '[' + _i8 + '].static must be a boolean!');
if (item.static !== undefined && typeof item.static !== "boolean") {
throw new Error("ReactGridLayout: " + contextName + "[" + _i9 + "].static must be a boolean!");
}
}
}
@@ -508,4 +546,14 @@ function autoBindHandlers(el, fns) {
fns.forEach(function (key) {
return el[key] = el[key].bind(el);
});
}
}
function log() {
var _console;
if (!DEBUG) return;
// eslint-disable-next-line no-console
(_console = console).log.apply(_console, arguments);
}
var noop = exports.noop = function noop() {};

View File

@@ -1,36 +1,67 @@
// @flow
import isEqual from 'lodash.isequal';
import React from 'react';
import type {ChildrenArray as ReactChildrenArray, Element as ReactElement} from 'react';
import isEqual from "lodash.isequal";
import React from "react";
import type {
ChildrenArray as ReactChildrenArray,
Element as ReactElement
} from "react";
export type LayoutItem = {
w: number, h: number, x: number, y: number, i: string,
minW?: number, minH?: number, maxW?: number, maxH?: number,
moved?: boolean, static?: boolean,
isDraggable?: ?boolean, isResizable?: ?boolean
w: number,
h: number,
x: number,
y: number,
i: string,
minW?: number,
minH?: number,
maxW?: number,
maxH?: number,
moved?: boolean,
static?: boolean,
isDraggable?: ?boolean,
isResizable?: ?boolean
};
export type Layout = Array<LayoutItem>;
export type Position = {left: number, top: number, width: number, height: number};
export type Position = {
left: number,
top: number,
width: number,
height: number
};
export type ReactDraggableCallbackData = {
node: HTMLElement,
x: number, y: number,
deltaX: number, deltaY: number,
lastX: number, lastY: number
x: number,
y: number,
deltaX: number,
deltaY: number,
lastX: number,
lastY: number
};
export type PartialPosition = {left: number, top: number};
export type Size = {width: number, height: number};
export type GridDragEvent = {e: Event, node: HTMLElement, newPosition: PartialPosition};
export type GridResizeEvent = {e: Event, node: HTMLElement, size: Size};
export type PartialPosition = { left: number, top: number };
export type Size = { width: number, height: number };
export type GridDragEvent = {
e: Event,
node: HTMLElement,
newPosition: PartialPosition
};
export type GridResizeEvent = { e: Event, node: HTMLElement, size: Size };
type REl = ReactElement<any>;
export type ReactChildren = ReactChildrenArray<REl>;
// All callbacks are of the signature (layout, oldItem, newItem, placeholder, e).
export type EventCallback =
(Layout, oldItem: ?LayoutItem, newItem: ?LayoutItem, placeholder: ?LayoutItem, Event, ?HTMLElement) => void;
export type CompactType = ?('horizontal' | 'vertical');
export type EventCallback = (
Layout,
oldItem: ?LayoutItem,
newItem: ?LayoutItem,
placeholder: ?LayoutItem,
Event,
?HTMLElement
) => void;
export type CompactType = ?("horizontal" | "vertical");
const isProduction = process.env.NODE_ENV === 'production';
const isProduction = process.env.NODE_ENV === "production";
const DEBUG = false;
/**
* Return the bottom coordinate of the layout.
@@ -39,7 +70,8 @@ const isProduction = process.env.NODE_ENV === 'production';
* @return {Number} Bottom coordinate.
*/
export function bottom(layout: Layout): number {
let max = 0, bottomY;
let max = 0,
bottomY;
for (let i = 0, len = layout.length; i < len; i++) {
bottomY = layout[i].y + layout[i].h;
if (bottomY > max) max = bottomY;
@@ -58,11 +90,20 @@ export function cloneLayout(layout: Layout): Layout {
// Fast path to cloning, since this is monomorphic
export function cloneLayoutItem(layoutItem: LayoutItem): LayoutItem {
return {
w: layoutItem.w, h: layoutItem.h, x: layoutItem.x, y: layoutItem.y, i: layoutItem.i,
minW: layoutItem.minW, maxW: layoutItem.maxW, minH: layoutItem.minH, maxH: layoutItem.maxH,
moved: Boolean(layoutItem.moved), static: Boolean(layoutItem.static),
w: layoutItem.w,
h: layoutItem.h,
x: layoutItem.x,
y: layoutItem.y,
i: layoutItem.i,
minW: layoutItem.minW,
maxW: layoutItem.maxW,
minH: layoutItem.minH,
maxH: layoutItem.maxH,
moved: Boolean(layoutItem.moved),
static: Boolean(layoutItem.static),
// These can be null
isDraggable: layoutItem.isDraggable, isResizable: layoutItem.isResizable
isDraggable: layoutItem.isDraggable,
isResizable: layoutItem.isResizable
};
}
@@ -71,8 +112,10 @@ export function cloneLayoutItem(layoutItem: LayoutItem): LayoutItem {
* This will catch differences in keys, order, and length.
*/
export function childrenEqual(a: ReactChildren, b: ReactChildren): boolean {
// $FlowIgnore: Appears to think map calls back w/array
return isEqual(React.Children.map(a, (c) => c.key), React.Children.map(b, (c) => c.key));
return isEqual(
React.Children.map(a, c => c.key),
React.Children.map(b, c => c.key)
);
}
/**
@@ -96,7 +139,11 @@ export function collides(l1: LayoutItem, l2: LayoutItem): boolean {
* vertically.
* @return {Array} Compacted Layout.
*/
export function compact(layout: Layout, compactType: CompactType, cols: number): Layout {
export function compact(
layout: Layout,
compactType: CompactType,
cols: number
): Layout {
// Statics go in the compareWith array right away so items flow around them.
const compareWith = getStatics(layout);
// We go through the items by row and column.
@@ -109,7 +156,7 @@ export function compact(layout: Layout, compactType: CompactType, cols: number):
// Don't move static elements
if (!l.static) {
l = compactItem(compareWith, l, compactType, cols);
l = compactItem(compareWith, l, compactType, cols, sorted);
// Add to comparison array. We only collide with items before this one.
// Statics are already in this array.
@@ -126,12 +173,52 @@ export function compact(layout: Layout, compactType: CompactType, cols: number):
return out;
}
const heightWidth = { x: "w", y: "h" };
/**
* Before moving item down, it will check if the movement will cause collisions and move those items down before.
*/
function resolveCompactionCollision(
layout: Layout,
item: LayoutItem,
moveToCoord: number,
axis: "x" | "y"
) {
const sizeProp = heightWidth[axis];
item[axis] += 1;
const itemIndex = layout.indexOf(item);
// Go through each item we collide with.
for (let i = itemIndex + 1; i < layout.length; i++) {
const otherItem = layout[i];
// Ignore static items
if (otherItem.static) continue;
if (collides(item, otherItem)) {
resolveCompactionCollision(
layout,
otherItem,
moveToCoord + item[sizeProp],
axis
);
}
}
item[axis] = moveToCoord;
}
/**
* Compact an item in the layout.
*/
export function compactItem(compareWith: Layout, l: LayoutItem, compactType: CompactType, cols: number): LayoutItem {
const compactV = compactType === 'vertical';
const compactH = compactType === 'horizontal';
export function compactItem(
compareWith: Layout,
l: LayoutItem,
compactType: CompactType,
cols: number,
fullLayout: Layout
): LayoutItem {
const compactV = compactType === "vertical";
const compactH = compactType === "horizontal";
if (compactV) {
// Bottom 'y' possible is the bottom of the layout.
// This allows you to do nice stuff like specify {y: Infinity}
@@ -151,11 +238,11 @@ export function compactItem(compareWith: Layout, l: LayoutItem, compactType: Com
// Move it down, and keep moving it down if it's colliding.
let collides;
while((collides = getFirstCollision(compareWith, l))) {
while ((collides = getFirstCollision(compareWith, l))) {
if (compactH) {
l.x = collides.x + collides.w;
resolveCompactionCollision(fullLayout, l, collides.x + collides.w, "x");
} else {
l.y = collides.y + collides.h;
resolveCompactionCollision(fullLayout, l, collides.y + collides.h, "y");
}
// Since we can't grow without bounds horizontally, if we've overflown, let's move it down and try again.
if (compactH && l.x + l.w > cols) {
@@ -172,7 +259,10 @@ export function compactItem(compareWith: Layout, l: LayoutItem, compactType: Com
* @param {Array} layout Layout array.
* @param {Number} bounds Number of columns.
*/
export function correctBounds(layout: Layout, bounds: {cols: number}): Layout {
export function correctBounds(
layout: Layout,
bounds: { cols: number }
): Layout {
const collidesWith = getStatics(layout);
for (let i = 0, len = layout.length; i < len; i++) {
const l = layout[i];
@@ -187,7 +277,7 @@ export function correctBounds(layout: Layout, bounds: {cols: number}): Layout {
else {
// If this is static and collides with other statics, we must move it down.
// We have to do something nicer than just letting them overlap.
while(getFirstCollision(collidesWith, l)) {
while (getFirstCollision(collidesWith, l)) {
l.y++;
}
}
@@ -216,14 +306,20 @@ export function getLayoutItem(layout: Layout, id: string): ?LayoutItem {
* @param {Object} layoutItem Layout item.
* @return {Object|undefined} A colliding layout item, or undefined.
*/
export function getFirstCollision(layout: Layout, layoutItem: LayoutItem): ?LayoutItem {
export function getFirstCollision(
layout: Layout,
layoutItem: LayoutItem
): ?LayoutItem {
for (let i = 0, len = layout.length; i < len; i++) {
if (collides(layout[i], layoutItem)) return layout[i];
}
}
export function getAllCollisions(layout: Layout, layoutItem: LayoutItem): Array<LayoutItem> {
return layout.filter((l) => collides(l, layoutItem));
export function getAllCollisions(
layout: Layout,
layoutItem: LayoutItem
): Array<LayoutItem> {
return layout.filter(l => collides(l, layoutItem));
}
/**
@@ -232,7 +328,7 @@ export function getAllCollisions(layout: Layout, layoutItem: LayoutItem): Array<
* @return {Array} Array of static layout items..
*/
export function getStatics(layout: Layout): Array<LayoutItem> {
return layout.filter((l) => l.static);
return layout.filter(l => l.static);
}
/**
@@ -242,13 +338,19 @@ export function getStatics(layout: Layout): Array<LayoutItem> {
* @param {LayoutItem} l element to move.
* @param {Number} [x] X position in grid units.
* @param {Number} [y] Y position in grid units.
* @param {Boolean} [isUserAction] If true, designates that the item we're moving is
* being dragged/resized by the user.
*/
export function moveElement(layout: Layout, l: LayoutItem, x: ?number, y: ?number,
isUserAction: ?boolean, preventCollision: ?boolean,
compactType: CompactType, cols: number): Layout {
export function moveElement(
layout: Layout,
l: LayoutItem,
x: number,
y: number,
isUserAction: ?boolean,
preventCollision: ?boolean,
compactType: CompactType,
cols: number
): Layout {
if (l.static) return layout;
log(`Moving element ${l.i} to [${x},${y}] from [${l.x},${l.y}]`);
// Short-circuit if nothing to do.
if (l.y === y && l.x === x) return layout;
@@ -256,10 +358,9 @@ export function moveElement(layout: Layout, l: LayoutItem, x: ?number, y: ?numbe
const oldX = l.x;
const oldY = l.y;
const movingUp = y && l.y > y;
// This is quite a bit faster than extending the object
if (typeof x === 'number') l.x = x;
if (typeof y === 'number') l.y = y;
l.x = x;
l.y = y;
l.moved = true;
// If this collides with anything, move it.
@@ -267,11 +368,16 @@ export function moveElement(layout: Layout, l: LayoutItem, x: ?number, y: ?numbe
// to ensure, in the case of multiple collisions, that we're getting the
// nearest collision.
let sorted = sortLayoutItems(layout, compactType);
const movingUp =
compactType === "vertical"
? oldY >= y
: compactType === "horizontal" ? oldX >= x : false;
if (movingUp) sorted = sorted.reverse();
const collisions = getAllCollisions(sorted, l);
// There was a collision; abort
if (preventCollision && collisions.length) {
log(`Collision prevented on ${l.i}, reverting.`);
l.x = oldX;
l.y = oldY;
l.moved = false;
@@ -281,20 +387,34 @@ export function moveElement(layout: Layout, l: LayoutItem, x: ?number, y: ?numbe
// Move each item that collides away from this element.
for (let i = 0, len = collisions.length; i < len; i++) {
const collision = collisions[i];
// console.log('resolving collision between', l.i, 'at', l.y, 'and', collision.i, 'at', collision.y);
log(
`Resolving collision between ${l.i} at [${l.x},${l.y}] and ${
collision.i
} at [${collision.x},${collision.y}]`
);
// Short circuit so we can't infinite loop
if (collision.moved) continue;
// This makes it feel a bit more precise by waiting to swap for just a bit when moving up.
if (l.y > collision.y && l.y - collision.y > collision.h / 4) continue;
if (l.x > collision.x && l.x - collision.x > collision.w / 4) continue;
// Don't move static items - we have to move *this* element away
if (collision.static) {
layout = moveElementAwayFromCollision(layout, collision, l, isUserAction, compactType, cols);
layout = moveElementAwayFromCollision(
layout,
collision,
l,
isUserAction,
compactType,
cols
);
} else {
layout = moveElementAwayFromCollision(layout, l, collision, isUserAction, compactType, cols);
layout = moveElementAwayFromCollision(
layout,
l,
collision,
isUserAction,
compactType,
cols
);
}
}
@@ -308,34 +428,47 @@ export function moveElement(layout: Layout, l: LayoutItem, x: ?number, y: ?numbe
* @param {Array} layout Full layout to modify.
* @param {LayoutItem} collidesWith Layout item we're colliding with.
* @param {LayoutItem} itemToMove Layout item we're moving.
* @param {Boolean} [isUserAction] If true, designates that the item we're moving is being dragged/resized
* by the user.
*/
export function moveElementAwayFromCollision(layout: Layout, collidesWith: LayoutItem, itemToMove: LayoutItem,
isUserAction: ?boolean, compactType: CompactType, cols: number): Layout {
const compactH = compactType === 'horizontal';
const compactV = compactType === 'vertical';
export function moveElementAwayFromCollision(
layout: Layout,
collidesWith: LayoutItem,
itemToMove: LayoutItem,
isUserAction: ?boolean,
compactType: CompactType,
cols: number
): Layout {
const compactH = compactType === "horizontal";
const compactV = compactType === "vertical";
const preventCollision = false; // we're already colliding
// If there is enough space above the collision to put this element, move it there.
// We only do this on the main collision as this can get funky in cascades and cause
// unwanted swapping behavior.
if (isUserAction) {
// Reset isUserAction flag because we're not in the main collision anymore.
isUserAction = false;
// Make a mock item so we don't modify the item here, only modify in moveElement.
const fakeItem: LayoutItem = {
x: compactH ? Math.max(collidesWith.x - itemToMove.w, 0) : itemToMove.x,
x: compactH ? Math.max(collidesWith.x - itemToMove.w, 0) : itemToMove.x,
y: !compactH ? Math.max(collidesWith.y - itemToMove.h, 0) : itemToMove.y,
w: itemToMove.w,
h: itemToMove.h,
i: '-1'
i: "-1"
};
// No collision? If so, we can go up there; otherwise, we'll end up moving down as normal
if (!getFirstCollision(layout, fakeItem)) {
log(
`Doing reverse collision on ${itemToMove.i} up to [${fakeItem.x},${
fakeItem.y
}].`
);
return moveElement(
layout,
itemToMove,
compactH ? fakeItem.x : undefined,
compactV ? fakeItem.y + 1 : undefined,
fakeItem.x,
fakeItem.y,
isUserAction,
preventCollision,
compactType,
@@ -344,13 +477,11 @@ export function moveElementAwayFromCollision(layout: Layout, collidesWith: Layou
}
}
// Previously this was optimized to move below the collision directly, but this can cause problems
// with cascading moves, as an item may actually leapflog a collision and cause a reversal in order.
return moveElement(
layout,
itemToMove,
compactH ? itemToMove.x + 1 : undefined,
compactV ? itemToMove.y + 1 : undefined,
compactH ? collidesWith.x + collidesWith.w : itemToMove.x,
compactV ? collidesWith.y + collidesWith.h : itemToMove.y,
isUserAction,
preventCollision,
compactType,
@@ -365,10 +496,10 @@ export function moveElementAwayFromCollision(layout: Layout, collidesWith: Layou
* @return {String} That number as a percentage.
*/
export function perc(num: number): string {
return num * 100 + '%';
return num * 100 + "%";
}
export function setTransform({top, left, width, height}: Position): Object {
export function setTransform({ top, left, width, height }: Position): Object {
// Replace unitless items with px
const translate = `translate(${left}px,${top}px)`;
return {
@@ -379,17 +510,17 @@ export function setTransform({top, left, width, height}: Position): Object {
OTransform: translate,
width: `${width}px`,
height: `${height}px`,
position: 'absolute'
position: "absolute"
};
}
export function setTopLeft({top, left, width, height}: Position): Object {
export function setTopLeft({ top, left, width, height }: Position): Object {
return {
top: `${top}px`,
left: `${left}px`,
width: `${width}px`,
height: `${height}px`,
position: 'absolute'
position: "absolute"
};
}
@@ -399,8 +530,11 @@ export function setTopLeft({top, left, width, height}: Position): Object {
* @return {Array} Array of layout objects.
* @return {Array} Layout, sorted static items first.
*/
export function sortLayoutItems(layout: Layout, compactType: CompactType): Layout {
if (compactType === 'horizontal') return sortLayoutItemsByColRow(layout);
export function sortLayoutItems(
layout: Layout,
compactType: CompactType
): Layout {
if (compactType === "horizontal") return sortLayoutItemsByColRow(layout);
else return sortLayoutItemsByRowCol(layout);
}
@@ -434,8 +568,12 @@ export function sortLayoutItemsByColRow(layout: Layout): Layout {
* @param {?String} compact Compaction option.
* @return {Array} Working layout.
*/
export function synchronizeLayoutWithChildren(initialLayout: Layout, children: ReactChildren,
cols: number, compactType: CompactType): Layout {
export function synchronizeLayoutWithChildren(
initialLayout: Layout,
children: ReactChildren,
cols: number,
compactType: CompactType
): Layout {
initialLayout = initialLayout || [];
// Generate one layout item per child.
@@ -447,26 +585,34 @@ export function synchronizeLayoutWithChildren(initialLayout: Layout, children: R
layout[i] = cloneLayoutItem(exists);
} else {
if (!isProduction && child.props._grid) {
console.warn('`_grid` properties on children have been deprecated as of React 15.2. ' + // eslint-disable-line
'Please use `data-grid` or add your properties directly to the `layout`.');
console.warn(
"`_grid` properties on children have been deprecated as of React 15.2. " + // eslint-disable-line
"Please use `data-grid` or add your properties directly to the `layout`."
);
}
const g = child.props['data-grid'] || child.props._grid;
const g = child.props["data-grid"] || child.props._grid;
// Hey, this item has a data-grid property, use it.
if (g) {
if (!isProduction) {
validateLayout([g], 'ReactGridLayout.children');
validateLayout([g], "ReactGridLayout.children");
}
layout[i] = cloneLayoutItem({...g, i: child.key});
layout[i] = cloneLayoutItem({ ...g, i: child.key });
} else {
// Nothing provided: ensure this is added to the bottom
layout[i] = cloneLayoutItem({w: 1, h: 1, x: 0, y: bottom(layout), i: String(child.key)});
layout[i] = cloneLayoutItem({
w: 1,
h: 1,
x: 0,
y: bottom(layout),
i: String(child.key)
});
}
}
});
// Correct the layout.
layout = correctBounds(layout, {cols: cols});
layout = correctBounds(layout, { cols: cols });
layout = compact(layout, compactType, cols);
return layout;
@@ -479,27 +625,54 @@ export function synchronizeLayoutWithChildren(initialLayout: Layout, children: R
* @param {String} [contextName] Context name for errors.
* @throw {Error} Validation error.
*/
export function validateLayout(layout: Layout, contextName: string): void {
contextName = contextName || "Layout";
const subProps = ['x', 'y', 'w', 'h'];
if (!Array.isArray(layout)) throw new Error(contextName + " must be an array!");
export function validateLayout(
layout: Layout,
contextName: string = "Layout"
): void {
const subProps = ["x", "y", "w", "h"];
if (!Array.isArray(layout))
throw new Error(contextName + " must be an array!");
for (let i = 0, len = layout.length; i < len; i++) {
const item = layout[i];
for (let j = 0; j < subProps.length; j++) {
if (typeof item[subProps[j]] !== 'number') {
throw new Error('ReactGridLayout: ' + contextName + '[' + i + '].' + subProps[j] + ' must be a number!');
if (typeof item[subProps[j]] !== "number") {
throw new Error(
"ReactGridLayout: " +
contextName +
"[" +
i +
"]." +
subProps[j] +
" must be a number!"
);
}
}
if (item.i && typeof item.i !== 'string') {
throw new Error('ReactGridLayout: ' + contextName + '[' + i + '].i must be a string!');
if (item.i && typeof item.i !== "string") {
throw new Error(
"ReactGridLayout: " + contextName + "[" + i + "].i must be a string!"
);
}
if (item.static !== undefined && typeof item.static !== 'boolean') {
throw new Error('ReactGridLayout: ' + contextName + '[' + i + '].static must be a boolean!');
if (item.static !== undefined && typeof item.static !== "boolean") {
throw new Error(
"ReactGridLayout: " +
contextName +
"[" +
i +
"].static must be a boolean!"
);
}
}
}
// Flow can't really figure this out, so we just use Object
export function autoBindHandlers(el: Object, fns: Array<string>): void {
fns.forEach((key) => el[key] = el[key].bind(el));
fns.forEach(key => (el[key] = el[key].bind(el)));
}
function log(...args) {
if (!DEBUG) return;
// eslint-disable-next-line no-console
console.log(...args);
}
export const noop = () => {};