UNPKG

react-grid-layout

Version:

A draggable and resizable grid layout with responsive breakpoints, for React.

834 lines (788 loc) 29.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.bottom = bottom; exports.childrenEqual = childrenEqual; exports.cloneLayout = cloneLayout; exports.cloneLayoutItem = cloneLayoutItem; exports.collides = collides; exports.compact = compact; exports.compactItem = compactItem; exports.compactType = compactType; exports.correctBounds = correctBounds; exports.fastPositionEqual = fastPositionEqual; exports.fastRGLPropsEqual = void 0; exports.getAllCollisions = getAllCollisions; exports.getFirstCollision = getFirstCollision; exports.getLayoutItem = getLayoutItem; exports.getStatics = getStatics; exports.modifyLayout = modifyLayout; exports.moveElement = moveElement; exports.moveElementAwayFromCollision = moveElementAwayFromCollision; exports.noop = void 0; exports.perc = perc; exports.resizeItemInDirection = resizeItemInDirection; exports.setTopLeft = setTopLeft; exports.setTransform = setTransform; exports.sortLayoutItems = sortLayoutItems; exports.sortLayoutItemsByColRow = sortLayoutItemsByColRow; exports.sortLayoutItemsByRowCol = sortLayoutItemsByRowCol; exports.synchronizeLayoutWithChildren = synchronizeLayoutWithChildren; exports.validateLayout = validateLayout; exports.withLayoutItem = withLayoutItem; var _fastEquals = require("fast-equals"); var _react = _interopRequireDefault(require("react")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /*:: import type { ChildrenArray as ReactChildrenArray, Element as ReactElement } from "react";*/ /*:: export type ResizeHandleAxis = | "s" | "w" | "e" | "n" | "sw" | "nw" | "se" | "ne";*/ /*:: 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, resizeHandles?: Array<ResizeHandleAxis>, isBounded?: ?boolean };*/ /*:: export type Layout = $ReadOnlyArray<LayoutItem>;*/ /*:: 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 };*/ /*:: export type PartialPosition = { left: number, top: number };*/ /*:: export type DroppingPosition = { left: number, top: number, e: Event };*/ /*:: 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, handle: string };*/ /*:: export type DragOverEvent = MouseEvent & { nativeEvent: { layerX: number, layerY: number, ...Event } };*/ /*:: export type Pick<FromType, Properties: { [string]: 0 }> = $Exact< $ObjMapi<Properties, <K, V>(k: K, v: V) => $ElementType<FromType, K>> >;*/ // Helpful port from TS /*:: type REl = ReactElement<any>;*/ /*:: export type ReactChildren = ReactChildrenArray<REl>;*/ /*:: export type EventCallback = ( Layout, oldItem: ?LayoutItem, newItem: ?LayoutItem, placeholder: ?LayoutItem, Event, ?HTMLElement ) => void;*/ // All callbacks are of the signature (layout, oldItem, newItem, placeholder, e). /*:: export type CompactType = ?("horizontal" | "vertical");*/ const isProduction = process.env.NODE_ENV === "production"; const DEBUG = false; /** * Return the bottom coordinate of the layout. * * @param {Array} layout Layout array. * @return {Number} Bottom coordinate. */ function bottom(layout /*: Layout*/) /*: number*/{ 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; } return max; } function cloneLayout(layout /*: Layout*/) /*: Layout*/{ const newLayout = Array(layout.length); for (let i = 0, len = layout.length; i < len; i++) { newLayout[i] = cloneLayoutItem(layout[i]); } return newLayout; } // Modify a layoutItem inside a layout. Returns a new Layout, // does not mutate. Carries over all other LayoutItems unmodified. function modifyLayout(layout /*: Layout*/, layoutItem /*: LayoutItem*/) /*: Layout*/{ const newLayout = Array(layout.length); for (let i = 0, len = layout.length; i < len; i++) { if (layoutItem.i === layout[i].i) { newLayout[i] = layoutItem; } else { newLayout[i] = layout[i]; } } return newLayout; } // Function to be called to modify a layout item. // Does defensive clones to ensure the layout is not modified. function withLayoutItem(layout /*: Layout*/, itemKey /*: string*/, cb /*: LayoutItem => LayoutItem*/) /*: [Layout, ?LayoutItem]*/{ let item = getLayoutItem(layout, itemKey); if (!item) return [layout, null]; item = cb(cloneLayoutItem(item)); // defensive clone then modify // FIXME could do this faster if we already knew the index layout = modifyLayout(layout, item); return [layout, item]; } // Fast path to cloning, since this is monomorphic 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), // These can be null/undefined isDraggable: layoutItem.isDraggable, isResizable: layoutItem.isResizable, resizeHandles: layoutItem.resizeHandles, isBounded: layoutItem.isBounded }; } /** * Comparing React `children` is a bit difficult. This is a good way to compare them. * This will catch differences in keys, order, and length. */ function childrenEqual(a /*: ReactChildren*/, b /*: ReactChildren*/) /*: boolean*/{ return (0, _fastEquals.deepEqual)(_react.default.Children.map(a, c => c?.key), _react.default.Children.map(b, c => c?.key)) && (0, _fastEquals.deepEqual)(_react.default.Children.map(a, c => c?.props["data-grid"]), _react.default.Children.map(b, c => c?.props["data-grid"])); } /** * See `fastRGLPropsEqual.js`. * We want this to run as fast as possible - it is called often - and to be * resilient to new props that we add. So rather than call lodash.isEqual, * which isn't suited to comparing props very well, we use this specialized * function in conjunction with preval to generate the fastest possible comparison * function, tuned for exactly our props. */ /*:: type FastRGLPropsEqual = (Object, Object, Function) => boolean;*/ const fastRGLPropsEqual /*: FastRGLPropsEqual*/ = exports.fastRGLPropsEqual = require("./fastRGLPropsEqual"); // Like the above, but a lot simpler. function fastPositionEqual(a /*: Position*/, b /*: Position*/) /*: boolean*/{ return a.left === b.left && a.top === b.top && a.width === b.width && a.height === b.height; } /** * Given two layoutitems, check if they collide. */ function collides(l1 /*: LayoutItem*/, l2 /*: LayoutItem*/) /*: boolean*/{ if (l1.i === l2.i) return false; // same element if (l1.x + l1.w <= l2.x) return false; // l1 is left of l2 if (l1.x >= l2.x + l2.w) return false; // l1 is right of l2 if (l1.y + l1.h <= l2.y) return false; // l1 is above l2 if (l1.y >= l2.y + l2.h) return false; // l1 is below l2 return true; // boxes overlap } /** * Given a layout, compact it. This involves going down each y coordinate and removing gaps * between items. * * Does not modify layout items (clones). Creates a new layout array. * * @param {Array} layout Layout. * @param {Boolean} verticalCompact Whether or not to compact the layout * vertically. * @param {Boolean} allowOverlap When `true`, allows overlapping grid items. * @return {Array} Compacted Layout. */ function compact(layout /*: Layout*/, compactType /*: CompactType*/, cols /*: number*/, allowOverlap /*: ?boolean*/) /*: 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. const sorted = sortLayoutItems(layout, compactType); // Holding for new items. const out = Array(layout.length); for (let i = 0, len = sorted.length; i < len; i++) { let l = cloneLayoutItem(sorted[i]); // Don't move static elements if (!l.static) { l = compactItem(compareWith, l, compactType, cols, sorted, allowOverlap); // Add to comparison array. We only collide with items before this one. // Statics are already in this array. compareWith.push(l); } // Add to output array to make sure they still come out in the right order. out[layout.indexOf(sorted[i])] = l; // Clear moved flag, if it exists. l.moved = false; } 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.map(layoutItem => { return layoutItem.i; }).indexOf(item.i); // 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; // Optimization: we can break early if we know we're past this el // We can do this b/c it's a sorted layout if (otherItem.y > item.y + item.h) break; if (collides(item, otherItem)) { resolveCompactionCollision(layout, otherItem, moveToCoord + item[sizeProp], axis); } } item[axis] = moveToCoord; } /** * Compact an item in the layout. * * Modifies item. * */ function compactItem(compareWith /*: Layout*/, l /*: LayoutItem*/, compactType /*: CompactType*/, cols /*: number*/, fullLayout /*: Layout*/, allowOverlap /*: ?boolean*/) /*: 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} // This is here because the layout must be sorted in order to get the correct bottom `y`. l.y = Math.min(bottom(compareWith), l.y); // Move the element up as far as it can go without colliding. while (l.y > 0 && !getFirstCollision(compareWith, l)) { l.y--; } } else if (compactH) { // Move the element left as far as it can go without colliding. while (l.x > 0 && !getFirstCollision(compareWith, l)) { l.x--; } } // Move it down, and keep moving it down if it's colliding. let collides; // Checking the compactType null value to avoid breaking the layout when overlapping is allowed. while ((collides = getFirstCollision(compareWith, l)) && !(compactType === null && allowOverlap)) { if (compactH) { resolveCompactionCollision(fullLayout, l, collides.x + collides.w, "x"); } else { 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) { l.x = cols - l.w; l.y++; // ALso move element as left as we can while (l.x > 0 && !getFirstCollision(compareWith, l)) { l.x--; } } } // Ensure that there are no negative positions l.y = Math.max(l.y, 0); l.x = Math.max(l.x, 0); return l; } /** * Given a layout, make sure all elements fit within its bounds. * * Modifies layout items. * * @param {Array} layout Layout array. * @param {Number} bounds Number of columns. */ 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]; // Overflows right if (l.x + l.w > bounds.cols) l.x = bounds.cols - l.w; // Overflows left if (l.x < 0) { l.x = 0; l.w = bounds.cols; } if (!l.static) collidesWith.push(l);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)) { l.y++; } } } return layout; } /** * Get a layout item by ID. Used so we can override later on if necessary. * * @param {Array} layout Layout array. * @param {String} id ID * @return {LayoutItem} Item at ID. */ function getLayoutItem(layout /*: Layout*/, id /*: string*/) /*: ?LayoutItem*/{ for (let i = 0, len = layout.length; i < len; i++) { if (layout[i].i === id) return layout[i]; } } /** * Returns the first item this layout collides with. * It doesn't appear to matter which order we approach this from, although * perhaps that is the wrong thing to do. * * @param {Object} layoutItem Layout item. * @return {Object|undefined} A colliding layout item, or undefined. */ 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]; } } function getAllCollisions(layout /*: Layout*/, layoutItem /*: LayoutItem*/) /*: Array<LayoutItem>*/{ return layout.filter(l => collides(l, layoutItem)); } /** * Get all static elements. * @param {Array} layout Array of layout objects. * @return {Array} Array of static layout items.. */ function getStatics(layout /*: Layout*/) /*: Array<LayoutItem>*/{ return layout.filter(l => l.static); } /** * Move an element. Responsible for doing cascading movements of other elements. * * Modifies layout items. * * @param {Array} layout Full layout to modify. * @param {LayoutItem} l element to move. * @param {Number} [x] X position in grid units. * @param {Number} [y] Y position in grid units. */ function moveElement(layout /*: Layout*/, l /*: LayoutItem*/, x /*: ?number*/, y /*: ?number*/, isUserAction /*: ?boolean*/, preventCollision /*: ?boolean*/, compactType /*: CompactType*/, cols /*: number*/, allowOverlap /*: ?boolean*/) /*: Layout*/{ // If this is static and not explicitly enabled as draggable, // no move is possible, so we can short-circuit this immediately. if (l.static && l.isDraggable !== true) return layout; // Short-circuit if nothing to do. if (l.y === y && l.x === x) return layout; log(`Moving element ${l.i} to [${String(x)},${String(y)}] from [${l.x},${l.y}]`); const oldX = l.x; const oldY = l.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.moved = true; // If this collides with anything, move it. // When doing this comparison, we have to sort the items we compare with // to ensure, in the case of multiple collisions, that we're getting the // nearest collision. let sorted = sortLayoutItems(layout, compactType); const movingUp = compactType === "vertical" && typeof y === "number" ? oldY >= y : compactType === "horizontal" && typeof x === "number" ? oldX >= x : false; // $FlowIgnore acceptable modification of read-only array as it was recently cloned if (movingUp) sorted = sorted.reverse(); const collisions = getAllCollisions(sorted, l); const hasCollisions = collisions.length > 0; // We may have collisions. We can short-circuit if we've turned off collisions or // allowed overlap. if (hasCollisions && allowOverlap) { // Easy, we don't need to resolve collisions. But we *did* change the layout, // so clone it on the way out. return cloneLayout(layout); } else if (hasCollisions && preventCollision) { // If we are preventing collision but not allowing overlap, we need to // revert the position of this element so it goes to where it came from, rather // than the user's desired location. log(`Collision prevented on ${l.i}, reverting.`); l.x = oldX; l.y = oldY; l.moved = false; return layout; // did not change so don't clone } // Move each item that collides away from this element. for (let i = 0, len = collisions.length; i < len; i++) { const collision = collisions[i]; 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; // Don't move static items - we have to move *this* element away if (collision.static) { layout = moveElementAwayFromCollision(layout, collision, l, isUserAction, compactType, cols); } else { layout = moveElementAwayFromCollision(layout, l, collision, isUserAction, compactType, cols); } } return layout; } /** * This is where the magic needs to happen - given a collision, move an element away from the collision. * We attempt to move it up if there's room, otherwise it goes below. * * @param {Array} layout Full layout to modify. * @param {LayoutItem} collidesWith Layout item we're colliding with. * @param {LayoutItem} itemToMove Layout item we're moving. */ function moveElementAwayFromCollision(layout /*: Layout*/, collidesWith /*: LayoutItem*/, itemToMove /*: LayoutItem*/, isUserAction /*: ?boolean*/, compactType /*: CompactType*/, cols /*: number*/) /*: Layout*/{ const compactH = compactType === "horizontal"; // Compact vertically if not set to horizontal const compactV = compactType === "vertical"; const preventCollision = collidesWith.static; // we're already colliding (not for static items) // 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, y: compactV ? Math.max(collidesWith.y - itemToMove.h, 0) : itemToMove.y, w: itemToMove.w, h: itemToMove.h, i: "-1" }; const firstCollision = getFirstCollision(layout, fakeItem); const collisionNorth = firstCollision && firstCollision.y + firstCollision.h > collidesWith.y; const collisionWest = firstCollision && collidesWith.x + collidesWith.w > firstCollision.x; // No collision? If so, we can go up there; otherwise, we'll end up moving down as normal if (!firstCollision) { log(`Doing reverse collision on ${itemToMove.i} up to [${fakeItem.x},${fakeItem.y}].`); return moveElement(layout, itemToMove, compactH ? fakeItem.x : undefined, compactV ? fakeItem.y : undefined, isUserAction, preventCollision, compactType, cols); } else if (collisionNorth && compactV) { return moveElement(layout, itemToMove, undefined, collidesWith.y + 1, isUserAction, preventCollision, compactType, cols); } else if (collisionNorth && compactType == null) { collidesWith.y = itemToMove.y; itemToMove.y = itemToMove.y + itemToMove.h; return layout; } else if (collisionWest && compactH) { return moveElement(layout, collidesWith, itemToMove.x, undefined, isUserAction, preventCollision, compactType, cols); } } const newX = compactH ? itemToMove.x + 1 : undefined; const newY = compactV ? itemToMove.y + 1 : undefined; if (newX == null && newY == null) { return layout; } return moveElement(layout, itemToMove, compactH ? itemToMove.x + 1 : undefined, compactV ? itemToMove.y + 1 : undefined, isUserAction, preventCollision, compactType, cols); } /** * Helper to convert a number to a percentage string. * * @param {Number} num Any number * @return {String} That number as a percentage. */ function perc(num /*: number*/) /*: string*/{ return num * 100 + "%"; } /** * Helper functions to constrain dimensions of a GridItem */ const constrainWidth = (left /*: number*/, currentWidth /*: number*/, newWidth /*: number*/, containerWidth /*: number*/) => { return left + newWidth > containerWidth ? currentWidth : newWidth; }; const constrainHeight = (top /*: number*/, currentHeight /*: number*/, newHeight /*: number*/) => { return top < 0 ? currentHeight : newHeight; }; const constrainLeft = (left /*: number*/) => Math.max(0, left); const constrainTop = (top /*: number*/) => Math.max(0, top); const resizeNorth = (currentSize, _ref, _containerWidth) => { let { left, height, width } = _ref; const top = currentSize.top - (height - currentSize.height); return { left, width, height: constrainHeight(top, currentSize.height, height), top: constrainTop(top) }; }; const resizeEast = (currentSize, _ref2, containerWidth) => { let { top, left, height, width } = _ref2; return { top, height, width: constrainWidth(currentSize.left, currentSize.width, width, containerWidth), left: constrainLeft(left) }; }; const resizeWest = (currentSize, _ref3, containerWidth) => { let { top, height, width } = _ref3; const left = currentSize.left - (width - currentSize.width); return { height, width: left < 0 ? currentSize.width : constrainWidth(currentSize.left, currentSize.width, width, containerWidth), top: constrainTop(top), left: constrainLeft(left) }; }; const resizeSouth = (currentSize, _ref4, containerWidth) => { let { top, left, height, width } = _ref4; return { width, left, height: constrainHeight(top, currentSize.height, height), top: constrainTop(top) }; }; const resizeNorthEast = function () { return resizeNorth(arguments.length <= 0 ? undefined : arguments[0], resizeEast(...arguments), arguments.length <= 2 ? undefined : arguments[2]); }; const resizeNorthWest = function () { return resizeNorth(arguments.length <= 0 ? undefined : arguments[0], resizeWest(...arguments), arguments.length <= 2 ? undefined : arguments[2]); }; const resizeSouthEast = function () { return resizeSouth(arguments.length <= 0 ? undefined : arguments[0], resizeEast(...arguments), arguments.length <= 2 ? undefined : arguments[2]); }; const resizeSouthWest = function () { return resizeSouth(arguments.length <= 0 ? undefined : arguments[0], resizeWest(...arguments), arguments.length <= 2 ? undefined : arguments[2]); }; const ordinalResizeHandlerMap = { n: resizeNorth, ne: resizeNorthEast, e: resizeEast, se: resizeSouthEast, s: resizeSouth, sw: resizeSouthWest, w: resizeWest, nw: resizeNorthWest }; /** * Helper for clamping width and position when resizing an item. */ function resizeItemInDirection(direction /*: ResizeHandleAxis*/, currentSize /*: Position*/, newSize /*: Position*/, containerWidth /*: number*/) /*: Position*/{ const ordinalHandler = ordinalResizeHandlerMap[direction]; // Shouldn't be possible given types; that said, don't fail hard if (!ordinalHandler) return newSize; return ordinalHandler(currentSize, { ...currentSize, ...newSize }, containerWidth); } function setTransform(_ref5 /*:: */) /*: Object*/{ let { top, left, width, height } /*: Position*/ = _ref5 /*: Position*/; // Replace unitless items with px const 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" }; } function setTopLeft(_ref6 /*:: */) /*: Object*/{ let { top, left, width, height } /*: Position*/ = _ref6 /*: Position*/; return { top: `${top}px`, left: `${left}px`, width: `${width}px`, height: `${height}px`, position: "absolute" }; } /** * Get layout items sorted from top left to right and down. * * @return {Array} Array of layout objects. * @return {Array} Layout, sorted static items first. */ function sortLayoutItems(layout /*: Layout*/, compactType /*: CompactType*/) /*: Layout*/{ if (compactType === "horizontal") return sortLayoutItemsByColRow(layout); if (compactType === "vertical") return sortLayoutItemsByRowCol(layout);else return layout; } /** * Sort layout items by row ascending and column ascending. * * Does not modify Layout. */ function sortLayoutItemsByRowCol(layout /*: Layout*/) /*: Layout*/{ // Slice to clone array as sort modifies return layout.slice(0).sort(function (a, b) { if (a.y > b.y || a.y === b.y && a.x > b.x) { return 1; } else if (a.y === b.y && a.x === b.x) { // Without this, we can get different sort results in IE vs. Chrome/FF return 0; } return -1; }); } /** * Sort layout items by column ascending then row ascending. * * Does not modify Layout. */ function sortLayoutItemsByColRow(layout /*: Layout*/) /*: Layout*/{ return layout.slice(0).sort(function (a, b) { if (a.x > b.x || a.x === b.x && a.y > b.y) { return 1; } return -1; }); } /** * Generate a layout using the initialLayout and children as a template. * Missing entries will be added, extraneous ones will be truncated. * * Does not modify initialLayout. * * @param {Array} initialLayout Layout passed in through props. * @param {String} breakpoint Current responsive breakpoint. * @param {?String} compact Compaction option. * @return {Array} Working layout. */ function synchronizeLayoutWithChildren(initialLayout /*: Layout*/, children /*: ReactChildren*/, cols /*: number*/, compactType /*: CompactType*/, allowOverlap /*: ?boolean*/) /*: Layout*/{ initialLayout = initialLayout || []; // Generate one layout item per child. const layout /*: LayoutItem[]*/ = []; _react.default.Children.forEach(children, (child /*: ReactElement<any>*/) => { // Child may not exist if (child?.key == null) return; const exists = getLayoutItem(initialLayout, String(child.key)); const g = child.props["data-grid"]; // Don't overwrite the layout item if it's already in the initial layout. // If it has a `data-grid` property, prefer that over what's in the layout. if (exists && g == null) { layout.push(cloneLayoutItem(exists)); } else { // Hey, this item has a data-grid property, use it. if (g) { if (!isProduction) { validateLayout([g], "ReactGridLayout.children"); } // FIXME clone not really necessary here layout.push(cloneLayoutItem({ ...g, i: child.key })); } else { // Nothing provided: ensure this is added to the bottom // FIXME clone not really necessary here layout.push(cloneLayoutItem({ w: 1, h: 1, x: 0, y: bottom(layout), i: String(child.key) })); } } }); // Correct the layout. const correctedLayout = correctBounds(layout, { cols: cols }); return allowOverlap ? correctedLayout : compact(correctedLayout, compactType, cols); } /** * Validate a layout. Throws errors. * * @param {Array} layout Array of layout items. * @param {String} [contextName] Context name for errors. * @throw {Error} Validation error. */ function validateLayout(layout /*: Layout*/) /*: void*/{ let contextName /*: string*/ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "Layout"; 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++) { const key = subProps[j]; const value = item[key]; if (typeof value !== "number" || Number.isNaN(value)) { throw new Error(`ReactGridLayout: ${contextName}[${i}].${key} must be a number! Received: ${value} (${typeof value})`); } } if (typeof item.i !== "undefined" && typeof item.i !== "string") { throw new Error(`ReactGridLayout: ${contextName}[${i}].i must be a string! Received: ${item.i} (${typeof item.i})`); } } } // Legacy support for verticalCompact: false function compactType(props /*: ?{ verticalCompact: boolean, compactType: CompactType }*/) /*: CompactType*/{ const { verticalCompact, compactType } = props || {}; return verticalCompact === false ? null : compactType; } function log() { if (!DEBUG) return; // eslint-disable-next-line no-console console.log(...arguments); } const noop = () => {}; exports.noop = noop;