react-grid-layout
Version:
A draggable and resizable grid layout with responsive breakpoints, for React.
635 lines (546 loc) • 24.8 kB
JavaScript
"use strict";
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _reactDraggable = require("react-draggable");
var _reactResizable = require("react-resizable");
var _utils = require("./utils");
var _calculateUtils = require("./calculateUtils");
var _ReactGridLayoutPropTypes = require("./ReactGridLayoutPropTypes");
var _classnames = _interopRequireDefault(require("classnames"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
* An individual item within a ReactGridLayout.
*/
var GridItem = /*#__PURE__*/function (_React$Component) {
_inherits(GridItem, _React$Component);
var _super = _createSuper(GridItem);
function GridItem() {
var _this;
_classCallCheck(this, GridItem);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = _super.call.apply(_super, [this].concat(args));
_defineProperty(_assertThisInitialized(_this), "state", {
resizing: null,
dragging: null,
className: ""
});
_defineProperty(_assertThisInitialized(_this), "elementRef", /*#__PURE__*/_react.default.createRef());
_defineProperty(_assertThisInitialized(_this), "onDragStart", function (e, _ref) {
var node = _ref.node;
var _this$props = _this.props,
onDragStart = _this$props.onDragStart,
transformScale = _this$props.transformScale;
if (!onDragStart) return;
var newPosition
/*: PartialPosition*/
= {
top: 0,
left: 0
}; // TODO: this wont work on nested parents
var offsetParent = node.offsetParent;
if (!offsetParent) return;
var parentRect = offsetParent.getBoundingClientRect();
var clientRect = node.getBoundingClientRect();
var cLeft = clientRect.left / transformScale;
var pLeft = parentRect.left / transformScale;
var cTop = clientRect.top / transformScale;
var pTop = parentRect.top / transformScale;
newPosition.left = cLeft - pLeft + offsetParent.scrollLeft;
newPosition.top = cTop - pTop + offsetParent.scrollTop;
_this.setState({
dragging: newPosition
}); // Call callback with this data
var _calcXY = (0, _calculateUtils.calcXY)(_this.getPositionParams(), newPosition.top, newPosition.left, _this.props.w, _this.props.h),
x = _calcXY.x,
y = _calcXY.y;
return onDragStart.call(_assertThisInitialized(_this), _this.props.i, x, y, {
e: e,
node: node,
newPosition: newPosition
});
});
_defineProperty(_assertThisInitialized(_this), "onDrag", function (e, _ref2) {
var node = _ref2.node,
deltaX = _ref2.deltaX,
deltaY = _ref2.deltaY;
var onDrag = _this.props.onDrag;
if (!onDrag) return;
if (!_this.state.dragging) {
throw new Error("onDrag called before onDragStart.");
}
var top = _this.state.dragging.top + deltaY;
var left = _this.state.dragging.left + deltaX;
var _this$props2 = _this.props,
isBounded = _this$props2.isBounded,
i = _this$props2.i,
w = _this$props2.w,
h = _this$props2.h,
containerWidth = _this$props2.containerWidth;
var positionParams = _this.getPositionParams(); // Boundary calculations; keeps items within the grid
if (isBounded) {
var offsetParent = node.offsetParent;
if (offsetParent) {
var _this$props3 = _this.props,
margin = _this$props3.margin,
rowHeight = _this$props3.rowHeight;
var bottomBoundary = offsetParent.clientHeight - (0, _calculateUtils.calcGridItemWHPx)(h, rowHeight, margin[1]);
top = (0, _calculateUtils.clamp)(top, 0, bottomBoundary);
var colWidth = (0, _calculateUtils.calcGridColWidth)(positionParams);
var rightBoundary = containerWidth - (0, _calculateUtils.calcGridItemWHPx)(w, colWidth, margin[0]);
left = (0, _calculateUtils.clamp)(left, 0, rightBoundary);
}
}
var newPosition
/*: PartialPosition*/
= {
top: top,
left: left
};
_this.setState({
dragging: newPosition
}); // Call callback with this data
var _calcXY2 = (0, _calculateUtils.calcXY)(positionParams, top, left, w, h),
x = _calcXY2.x,
y = _calcXY2.y;
return onDrag.call(_assertThisInitialized(_this), i, x, y, {
e: e,
node: node,
newPosition: newPosition
});
});
_defineProperty(_assertThisInitialized(_this), "onDragStop", function (e, _ref3) {
var node = _ref3.node;
var onDragStop = _this.props.onDragStop;
if (!onDragStop) return;
if (!_this.state.dragging) {
throw new Error("onDragEnd called before onDragStart.");
}
var _this$props4 = _this.props,
w = _this$props4.w,
h = _this$props4.h,
i = _this$props4.i;
var _this$state$dragging = _this.state.dragging,
left = _this$state$dragging.left,
top = _this$state$dragging.top;
var newPosition
/*: PartialPosition*/
= {
top: top,
left: left
};
_this.setState({
dragging: null
});
var _calcXY3 = (0, _calculateUtils.calcXY)(_this.getPositionParams(), top, left, w, h),
x = _calcXY3.x,
y = _calcXY3.y;
return onDragStop.call(_assertThisInitialized(_this), i, x, y, {
e: e,
node: node,
newPosition: newPosition
});
});
_defineProperty(_assertThisInitialized(_this), "onResizeStop", function (e, callbackData) {
_this.onResizeHandler(e, callbackData, "onResizeStop");
});
_defineProperty(_assertThisInitialized(_this), "onResizeStart", function (e, callbackData) {
_this.onResizeHandler(e, callbackData, "onResizeStart");
});
_defineProperty(_assertThisInitialized(_this), "onResize", function (e, callbackData) {
_this.onResizeHandler(e, callbackData, "onResize");
});
return _this;
}
_createClass(GridItem, [{
key: "shouldComponentUpdate",
value: function shouldComponentUpdate(nextProps
/*: Props*/
, nextState
/*: State*/
)
/*: boolean*/
{
// We can't deeply compare children. If the developer memoizes them, we can
// use this optimization.
if (this.props.children !== nextProps.children) return true;
if (this.props.droppingPosition !== nextProps.droppingPosition) return true; // TODO memoize these calculations so they don't take so long?
var oldPosition = (0, _calculateUtils.calcGridItemPosition)(this.getPositionParams(this.props), this.props.x, this.props.y, this.props.w, this.props.h, this.state);
var newPosition = (0, _calculateUtils.calcGridItemPosition)(this.getPositionParams(nextProps), nextProps.x, nextProps.y, nextProps.w, nextProps.h, nextState);
return !(0, _utils.fastPositionEqual)(oldPosition, newPosition) || this.props.useCSSTransforms !== nextProps.useCSSTransforms;
}
}, {
key: "componentDidMount",
value: function componentDidMount() {
this.moveDroppingItem({});
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps
/*: Props*/
) {
this.moveDroppingItem(prevProps);
} // When a droppingPosition is present, this means we should fire a move event, as if we had moved
// this element by `x, y` pixels.
}, {
key: "moveDroppingItem",
value: function moveDroppingItem(prevProps
/*: Props*/
) {
var droppingPosition = this.props.droppingPosition;
if (!droppingPosition) return;
var node = this.elementRef.current; // Can't find DOM node (are we unmounted?)
if (!node) return;
var prevDroppingPosition = prevProps.droppingPosition || {
left: 0,
top: 0
};
var dragging = this.state.dragging;
var shouldDrag = dragging && droppingPosition.left !== prevDroppingPosition.left || droppingPosition.top !== prevDroppingPosition.top;
if (!dragging) {
this.onDragStart(droppingPosition.e, {
node: node,
deltaX: droppingPosition.left,
deltaY: droppingPosition.top
});
} else if (shouldDrag) {
var deltaX = droppingPosition.left - dragging.left;
var deltaY = droppingPosition.top - dragging.top;
this.onDrag(droppingPosition.e, {
node: node,
deltaX: deltaX,
deltaY: deltaY
});
}
}
}, {
key: "getPositionParams",
value: function getPositionParams()
/*: PositionParams*/
{
var props
/*: Props*/
= arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.props;
return {
cols: props.cols,
containerPadding: props.containerPadding,
containerWidth: props.containerWidth,
margin: props.margin,
maxRows: props.maxRows,
rowHeight: props.rowHeight
};
}
/**
* This is where we set the grid item's absolute placement. It gets a little tricky because we want to do it
* well when server rendering, and the only way to do that properly is to use percentage width/left because
* we don't know exactly what the browser viewport is.
* Unfortunately, CSS Transforms, which are great for performance, break in this instance because a percentage
* left is relative to the item itself, not its container! So we cannot use them on the server rendering pass.
*
* @param {Object} pos Position object with width, height, left, top.
* @return {Object} Style object.
*/
}, {
key: "createStyle",
value: function createStyle(pos
/*: Position*/
)
/*: { [key: string]: ?string }*/
{
var _this$props5 = this.props,
usePercentages = _this$props5.usePercentages,
containerWidth = _this$props5.containerWidth,
useCSSTransforms = _this$props5.useCSSTransforms;
var style; // CSS Transforms support (default)
if (useCSSTransforms) {
style = (0, _utils.setTransform)(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);
}
}
return style;
}
/**
* Mix a Draggable instance into a child.
* @param {Element} child Child element.
* @return {Element} Child wrapped in Draggable.
*/
}, {
key: "mixinDraggable",
value: function mixinDraggable(child
/*: ReactElement<any>*/
, isDraggable
/*: boolean*/
)
/*: ReactElement<any>*/
{
return /*#__PURE__*/_react.default.createElement(_reactDraggable.DraggableCore, {
disabled: !isDraggable,
onStart: this.onDragStart,
onDrag: this.onDrag,
onStop: this.onDragStop,
handle: this.props.handle,
cancel: ".react-resizable-handle" + (this.props.cancel ? "," + this.props.cancel : ""),
scale: this.props.transformScale,
nodeRef: this.elementRef
}, child);
}
/**
* Mix a Resizable instance into a child.
* @param {Element} child Child element.
* @param {Object} position Position object (pixel values)
* @return {Element} Child wrapped in Resizable.
*/
}, {
key: "mixinResizable",
value: function mixinResizable(child
/*: ReactElement<any>*/
, position
/*: Position*/
, isResizable
/*: boolean*/
)
/*: ReactElement<any>*/
{
var _this$props6 = this.props,
cols = _this$props6.cols,
x = _this$props6.x,
minW = _this$props6.minW,
minH = _this$props6.minH,
maxW = _this$props6.maxW,
maxH = _this$props6.maxH,
transformScale = _this$props6.transformScale,
resizeHandles = _this$props6.resizeHandles,
resizeHandle = _this$props6.resizeHandle;
var positionParams = this.getPositionParams(); // This is the max possible width - doesn't go to infinity because of the width of the window
var maxWidth = (0, _calculateUtils.calcGridItemPosition)(positionParams, 0, 0, cols - x, 0).width; // Calculate min/max constraints using our min & maxes
var mins = (0, _calculateUtils.calcGridItemPosition)(positionParams, 0, 0, minW, minH);
var maxes = (0, _calculateUtils.calcGridItemPosition)(positionParams, 0, 0, maxW, maxH);
var minConstraints = [mins.width, mins.height];
var maxConstraints = [Math.min(maxes.width, maxWidth), Math.min(maxes.height, Infinity)];
return /*#__PURE__*/_react.default.createElement(_reactResizable.Resizable, {
draggableOpts: {
disabled: !isResizable,
nodeRef: this.elementRef
},
className: isResizable ? undefined : "react-resizable-hide",
width: position.width,
height: position.height,
minConstraints: minConstraints,
maxConstraints: maxConstraints,
onResizeStop: this.onResizeStop,
onResizeStart: this.onResizeStart,
onResize: this.onResize,
transformScale: transformScale,
resizeHandles: resizeHandles,
handle: resizeHandle
}, child);
}
/**
* onDragStart event handler
* @param {Event} e event data
* @param {Object} callbackData an object with node, delta and position information
*/
}, {
key: "onResizeHandler",
value:
/**
* Wrapper around drag events to provide more useful data.
* All drag events call the function with the given handler name,
* with the signature (index, x, y).
*
* @param {String} handlerName Handler name to wrap.
* @return {Function} Handler function.
*/
function onResizeHandler(e
/*: Event*/
, _ref4, handlerName
/*: string*/
)
/*: void*/
{
var node = _ref4.node,
size = _ref4.size;
var handler = this.props[handlerName];
if (!handler) return;
var _this$props7 = this.props,
cols = _this$props7.cols,
x = _this$props7.x,
y = _this$props7.y,
i = _this$props7.i,
maxH = _this$props7.maxH,
minH = _this$props7.minH;
var _this$props8 = this.props,
minW = _this$props8.minW,
maxW = _this$props8.maxW; // Get new XY
var _calcWH = (0, _calculateUtils.calcWH)(this.getPositionParams(), size.width, size.height, x, y),
w = _calcWH.w,
h = _calcWH.h; // minW should be at least 1 (TODO propTypes validation?)
minW = Math.max(minW, 1); // maxW should be at most (cols - x)
maxW = Math.min(maxW, cols - x); // Min/max capping
w = (0, _calculateUtils.clamp)(w, minW, maxW);
h = (0, _calculateUtils.clamp)(h, minH, maxH);
this.setState({
resizing: handlerName === "onResizeStop" ? null : size
});
handler.call(this, i, w, h, {
e: e,
node: node,
size: size
});
}
}, {
key: "render",
value: function render()
/*: ReactNode*/
{
var _this$props9 = this.props,
x = _this$props9.x,
y = _this$props9.y,
w = _this$props9.w,
h = _this$props9.h,
isDraggable = _this$props9.isDraggable,
isResizable = _this$props9.isResizable,
droppingPosition = _this$props9.droppingPosition,
useCSSTransforms = _this$props9.useCSSTransforms;
var pos = (0, _calculateUtils.calcGridItemPosition)(this.getPositionParams(), x, y, w, h, this.state);
var child = _react.default.Children.only(this.props.children); // Create the child element. We clone the existing element but modify its className and style.
var newChild = /*#__PURE__*/_react.default.cloneElement(child, {
ref: this.elementRef,
className: (0, _classnames.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),
dropping: Boolean(droppingPosition),
cssTransforms: useCSSTransforms
}),
// We can set the width and height on the child, but unfortunately we can't set the position.
style: _objectSpread(_objectSpread(_objectSpread({}, this.props.style), child.props.style), this.createStyle(pos))
}); // Resizable support. This is usually on but the user can toggle it off.
newChild = this.mixinResizable(newChild, pos, isResizable); // Draggable support. This is always on, except for with placeholders.
newChild = this.mixinDraggable(newChild, isDraggable);
return newChild;
}
}]);
return GridItem;
}(_react.default.Component);
exports.default = GridItem;
_defineProperty(GridItem, "propTypes", {
// Children must be only a single element
children: _propTypes.default.element,
// General grid attributes
cols: _propTypes.default.number.isRequired,
containerWidth: _propTypes.default.number.isRequired,
rowHeight: _propTypes.default.number.isRequired,
margin: _propTypes.default.array.isRequired,
maxRows: _propTypes.default.number.isRequired,
containerPadding: _propTypes.default.array.isRequired,
// These are all in grid units
x: _propTypes.default.number.isRequired,
y: _propTypes.default.number.isRequired,
w: _propTypes.default.number.isRequired,
h: _propTypes.default.number.isRequired,
// All optional
minW: function minW(props
/*: Props*/
, propName
/*: string*/
) {
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");
},
maxW: function maxW(props
/*: Props*/
, propName
/*: string*/
) {
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");
},
minH: function minH(props
/*: Props*/
, propName
/*: string*/
) {
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");
},
maxH: function maxH(props
/*: Props*/
, propName
/*: string*/
) {
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");
},
// ID is nice to have for callbacks
i: _propTypes.default.string.isRequired,
// Resize handle options
resizeHandles: _ReactGridLayoutPropTypes.resizeHandlesType,
resizeHandle: _ReactGridLayoutPropTypes.resizeHandleType,
// Functions
onDragStop: _propTypes.default.func,
onDragStart: _propTypes.default.func,
onDrag: _propTypes.default.func,
onResizeStop: _propTypes.default.func,
onResizeStart: _propTypes.default.func,
onResize: _propTypes.default.func,
// Flags
isDraggable: _propTypes.default.bool.isRequired,
isResizable: _propTypes.default.bool.isRequired,
isBounded: _propTypes.default.bool.isRequired,
static: _propTypes.default.bool,
// Use CSS transforms instead of top/left
useCSSTransforms: _propTypes.default.bool.isRequired,
transformScale: _propTypes.default.number,
// Others
className: _propTypes.default.string,
// Selector for draggable handle
handle: _propTypes.default.string,
// Selector for draggable cancel (see react-draggable)
cancel: _propTypes.default.string,
// Current position of a dropping element
droppingPosition: _propTypes.default.shape({
e: _propTypes.default.object.isRequired,
left: _propTypes.default.number.isRequired,
top: _propTypes.default.number.isRequired
})
});
_defineProperty(GridItem, "defaultProps", {
className: "",
cancel: "",
handle: "",
minH: 1,
minW: 1,
maxH: Infinity,
maxW: Infinity,
transformScale: 1
});