react-grid-layout
Version:
A draggable and resizable grid layout with responsive breakpoints, for React.
1,517 lines (1,515 loc) • 44.3 kB
JavaScript
import { defaultConstraints, setTransform, setTopLeft, perc, applyPositionConstraints, resizeItemInDirection, applySizeConstraints, defaultPositionStrategy, defaultGridConfig, defaultDragConfig, defaultResizeConfig, defaultDropConfig, getCompactor, getBreakpointFromWidth, getColsFromBreakpoint, findOrGenerateResponsiveLayout, getIndentationValue } from './chunk-XYPIYYYQ.mjs';
import { calcXYRaw, calcGridItemWHPx, clamp, calcGridColWidth, calcWHRaw, calcGridItemPosition, bottom, getLayoutItem, cloneLayoutItem, moveElement, withLayoutItem, getAllCollisions, calcXY, cloneLayout, correctBounds } from './chunk-AWM66AWF.mjs';
import React2, { useState, useRef, useMemo, useCallback, useEffect } from 'react';
import { DraggableCore } from 'react-draggable';
import { Resizable } from 'react-resizable';
import clsx from 'clsx';
import { jsx, jsxs } from 'react/jsx-runtime';
import { deepEqual } from 'fast-equals';
function GridItem(props) {
const {
children,
cols,
containerWidth,
margin,
containerPadding,
rowHeight,
maxRows,
isDraggable,
isResizable,
isBounded,
static: isStatic,
useCSSTransforms = true,
usePercentages = false,
transformScale = 1,
positionStrategy,
dragThreshold = 0,
droppingPosition,
className = "",
style,
handle = "",
cancel = "",
x,
y,
w,
h,
minW = 1,
maxW = Infinity,
minH = 1,
maxH = Infinity,
i,
resizeHandles,
resizeHandle,
constraints = defaultConstraints,
layoutItem,
layout = [],
onDragStart: onDragStartProp,
onDrag: onDragProp,
onDragStop: onDragStopProp,
onResizeStart: onResizeStartProp,
onResize: onResizeProp,
onResizeStop: onResizeStopProp
} = props;
const [dragging, setDragging] = useState(false);
const [resizing, setResizing] = useState(false);
const elementRef = useRef(null);
const dragPositionRef = useRef({ left: 0, top: 0 });
const resizePositionRef = useRef({
top: 0,
left: 0,
width: 0,
height: 0
});
const prevDroppingPositionRef = useRef(
void 0
);
const layoutRef = useRef(layout);
layoutRef.current = layout;
const onDragStartRef = useRef(null);
const onDragRef = useRef(null);
const dragPendingRef = useRef(false);
const initialDragClientRef = useRef({ x: 0, y: 0 });
const thresholdExceededRef = useRef(false);
const positionParams = useMemo(
() => ({
cols,
containerPadding,
containerWidth,
margin,
maxRows,
rowHeight
}),
[cols, containerPadding, containerWidth, margin, maxRows, rowHeight]
);
const constraintContext = useMemo(
() => ({
cols,
maxRows,
containerWidth,
containerHeight: 0,
// Auto-height grids don't have a fixed container height
rowHeight,
margin,
// Use empty layout here - the actual layout will be accessed via layoutRef when needed
// This prevents the context from changing when layout changes, avoiding callback recreation
layout: []
}),
[cols, maxRows, containerWidth, rowHeight, margin]
);
const getConstraintContext = useCallback(
() => ({
...constraintContext,
layout: layoutRef.current
}),
[constraintContext]
);
const effectiveLayoutItem = useMemo(
() => layoutItem ?? {
i,
x,
y,
w,
h,
minW,
maxW,
minH,
maxH
},
[layoutItem, i, x, y, w, h, minW, maxW, minH, maxH]
);
const createStyle = useCallback(
(pos2) => {
if (positionStrategy?.calcStyle) {
return positionStrategy.calcStyle(pos2);
}
if (useCSSTransforms) {
return setTransform(pos2);
}
const styleObj = setTopLeft(pos2);
if (usePercentages) {
return {
...styleObj,
left: perc(pos2.left / containerWidth),
width: perc(pos2.width / containerWidth)
};
}
return styleObj;
},
[positionStrategy, useCSSTransforms, usePercentages, containerWidth]
);
const onDragStart = useCallback(
(e, { node }) => {
if (!onDragStartProp) return;
const { offsetParent } = node;
if (!offsetParent) return;
const parentRect = offsetParent.getBoundingClientRect();
const clientRect = node.getBoundingClientRect();
const cLeft = clientRect.left / transformScale;
const pLeft = parentRect.left / transformScale;
const cTop = clientRect.top / transformScale;
const pTop = parentRect.top / transformScale;
let newPosition;
if (positionStrategy?.calcDragPosition) {
const mouseEvent = e;
newPosition = positionStrategy.calcDragPosition(
mouseEvent.clientX,
mouseEvent.clientY,
mouseEvent.clientX - clientRect.left,
mouseEvent.clientY - clientRect.top
);
} else {
newPosition = {
left: cLeft - pLeft + offsetParent.scrollLeft,
top: cTop - pTop + offsetParent.scrollTop
};
}
dragPositionRef.current = newPosition;
if (dragThreshold > 0) {
const mouseEvent = e;
initialDragClientRef.current = {
x: mouseEvent.clientX,
y: mouseEvent.clientY
};
dragPendingRef.current = true;
thresholdExceededRef.current = false;
setDragging(true);
return;
}
setDragging(true);
const rawPos = calcXYRaw(
positionParams,
newPosition.top,
newPosition.left
);
const { x: newX, y: newY } = applyPositionConstraints(
constraints,
effectiveLayoutItem,
rawPos.x,
rawPos.y,
getConstraintContext()
);
onDragStartProp(i, newX, newY, {
e,
node,
newPosition
});
},
[
onDragStartProp,
transformScale,
positionParams,
positionStrategy,
dragThreshold,
constraints,
effectiveLayoutItem,
getConstraintContext,
i
]
);
const onDrag = useCallback(
(e, { node, deltaX, deltaY }) => {
if (!onDragProp || !dragging) return;
const mouseEvent = e;
if (dragPendingRef.current && !thresholdExceededRef.current) {
const dx = mouseEvent.clientX - initialDragClientRef.current.x;
const dy = mouseEvent.clientY - initialDragClientRef.current.y;
const distance = Math.hypot(dx, dy);
if (distance < dragThreshold) {
return;
}
thresholdExceededRef.current = true;
dragPendingRef.current = false;
if (onDragStartProp) {
const rawPos2 = calcXYRaw(
positionParams,
dragPositionRef.current.top,
dragPositionRef.current.left
);
const { x: startX, y: startY } = applyPositionConstraints(
constraints,
effectiveLayoutItem,
rawPos2.x,
rawPos2.y,
getConstraintContext()
);
onDragStartProp(i, startX, startY, {
e,
node,
newPosition: dragPositionRef.current
});
}
}
let top = dragPositionRef.current.top + deltaY;
let left = dragPositionRef.current.left + deltaX;
if (isBounded) {
const { offsetParent } = node;
if (offsetParent) {
const bottomBoundary = offsetParent.clientHeight - calcGridItemWHPx(h, rowHeight, margin[1]);
top = clamp(top, 0, bottomBoundary);
const colWidth = calcGridColWidth(positionParams);
const rightBoundary = containerWidth - calcGridItemWHPx(w, colWidth, margin[0]);
left = clamp(left, 0, rightBoundary);
}
}
const newPosition = { top, left };
dragPositionRef.current = newPosition;
const rawPos = calcXYRaw(positionParams, top, left);
const { x: newX, y: newY } = applyPositionConstraints(
constraints,
effectiveLayoutItem,
rawPos.x,
rawPos.y,
getConstraintContext()
);
onDragProp(i, newX, newY, {
e,
node,
newPosition
});
},
[
onDragProp,
onDragStartProp,
dragging,
dragThreshold,
isBounded,
h,
rowHeight,
margin,
positionParams,
containerWidth,
w,
i,
constraints,
effectiveLayoutItem,
getConstraintContext
]
);
const onDragStop = useCallback(
(e, { node }) => {
if (!onDragStopProp || !dragging) return;
const wasPending = dragPendingRef.current;
dragPendingRef.current = false;
thresholdExceededRef.current = false;
initialDragClientRef.current = { x: 0, y: 0 };
if (wasPending) {
setDragging(false);
dragPositionRef.current = { left: 0, top: 0 };
return;
}
const { left, top } = dragPositionRef.current;
const newPosition = { top, left };
setDragging(false);
dragPositionRef.current = { left: 0, top: 0 };
const rawPos = calcXYRaw(positionParams, top, left);
const { x: newX, y: newY } = applyPositionConstraints(
constraints,
effectiveLayoutItem,
rawPos.x,
rawPos.y,
getConstraintContext()
);
onDragStopProp(i, newX, newY, {
e,
node,
newPosition
});
},
[
onDragStopProp,
dragging,
positionParams,
constraints,
effectiveLayoutItem,
getConstraintContext,
i
]
);
onDragStartRef.current = onDragStart;
onDragRef.current = onDrag;
const onResizeHandler = useCallback(
(e, { node, size, handle: resizeHandle2 }, position, handlerName) => {
const handler = handlerName === "onResizeStart" ? onResizeStartProp : handlerName === "onResize" ? onResizeProp : onResizeStopProp;
if (!handler) return;
let updatedSize;
if (node) {
updatedSize = resizeItemInDirection(
resizeHandle2,
position,
size,
containerWidth
);
} else {
updatedSize = {
...size,
top: position.top,
left: position.left
};
}
resizePositionRef.current = updatedSize;
const rawSize = calcWHRaw(
positionParams,
updatedSize.width,
updatedSize.height
);
const { w: newW, h: newH } = applySizeConstraints(
constraints,
effectiveLayoutItem,
rawSize.w,
rawSize.h,
resizeHandle2,
getConstraintContext()
);
handler(i, newW, newH, {
e: e.nativeEvent,
node,
size: updatedSize,
handle: resizeHandle2
});
},
[
onResizeStartProp,
onResizeProp,
onResizeStopProp,
containerWidth,
positionParams,
i,
constraints,
effectiveLayoutItem,
getConstraintContext
]
);
const handleResizeStart = useCallback(
(e, data) => {
setResizing(true);
const pos2 = calcGridItemPosition(positionParams, x, y, w, h);
const typedData = {
...data,
handle: data.handle
};
onResizeHandler(e, typedData, pos2, "onResizeStart");
},
[onResizeHandler, positionParams, x, y, w, h]
);
const handleResize = useCallback(
(e, data) => {
const pos2 = calcGridItemPosition(positionParams, x, y, w, h);
const typedData = {
...data,
handle: data.handle
};
onResizeHandler(e, typedData, pos2, "onResize");
},
[onResizeHandler, positionParams, x, y, w, h]
);
const handleResizeStop = useCallback(
(e, data) => {
setResizing(false);
resizePositionRef.current = { top: 0, left: 0, width: 0, height: 0 };
const pos2 = calcGridItemPosition(positionParams, x, y, w, h);
const typedData = {
...data,
handle: data.handle
};
onResizeHandler(e, typedData, pos2, "onResizeStop");
},
[onResizeHandler, positionParams, x, y, w, h]
);
useEffect(() => {
if (!droppingPosition) return;
const node = elementRef.current;
if (!node) return;
const prevDroppingPosition = prevDroppingPositionRef.current || {
left: 0,
top: 0
};
const shouldDrag = dragging && (droppingPosition.left !== prevDroppingPosition.left || droppingPosition.top !== prevDroppingPosition.top);
if (!dragging) {
const fakeData = {
node,
deltaX: droppingPosition.left,
deltaY: droppingPosition.top,
lastX: 0,
lastY: 0,
x: droppingPosition.left,
y: droppingPosition.top
};
onDragStartRef.current?.(
droppingPosition.e,
fakeData
);
} else if (shouldDrag) {
const deltaX = droppingPosition.left - dragPositionRef.current.left;
const deltaY = droppingPosition.top - dragPositionRef.current.top;
const fakeData = {
node,
deltaX,
deltaY,
lastX: dragPositionRef.current.left,
lastY: dragPositionRef.current.top,
x: droppingPosition.left,
y: droppingPosition.top
};
onDragRef.current?.(
droppingPosition.e,
fakeData
);
}
prevDroppingPositionRef.current = droppingPosition;
}, [droppingPosition, dragging, i]);
const pos = calcGridItemPosition(
positionParams,
x,
y,
w,
h,
dragging ? dragPositionRef.current : null,
resizing ? resizePositionRef.current : null
);
const child = React2.Children.only(children);
const minGridUnit = calcGridItemPosition(positionParams, 0, 0, 1, 1);
const minConstraints = [
minGridUnit.width,
minGridUnit.height
];
const maxConstraints = [Infinity, Infinity];
const childProps = child.props;
const childClassName = childProps["className"];
const childStyle = childProps["style"];
let newChild = React2.cloneElement(child, {
ref: elementRef,
className: clsx("react-grid-item", childClassName, className, {
static: isStatic,
resizing,
"react-draggable": isDraggable,
"react-draggable-dragging": dragging,
dropping: Boolean(droppingPosition),
cssTransforms: useCSSTransforms
}),
style: {
...style,
...childStyle,
...createStyle(pos)
}
});
const resizableHandle = resizeHandle;
newChild = /* @__PURE__ */ jsx(
Resizable,
{
draggableOpts: { disabled: !isResizable },
className: isResizable ? void 0 : "react-resizable-hide",
width: pos.width,
height: pos.height,
minConstraints,
maxConstraints,
onResizeStart: handleResizeStart,
onResize: handleResize,
onResizeStop: handleResizeStop,
transformScale,
resizeHandles,
handle: resizableHandle,
children: newChild
}
);
newChild = /* @__PURE__ */ jsx(
DraggableCore,
{
disabled: !isDraggable,
onStart: onDragStart,
onDrag,
onStop: onDragStop,
handle,
cancel: ".react-resizable-handle" + (cancel ? "," + cancel : ""),
scale: transformScale,
nodeRef: elementRef,
children: newChild
}
);
return newChild;
}
var noop = () => {
};
var layoutClassName = "react-grid-layout";
var isFirefox = false;
try {
isFirefox = /firefox/i.test(navigator.userAgent);
} catch {
}
function childrenEqual(a, b) {
const aArr = React2.Children.toArray(a);
const bArr = React2.Children.toArray(b);
if (aArr.length !== bArr.length) return false;
for (let i = 0; i < aArr.length; i++) {
const aChild = aArr[i];
const bChild = bArr[i];
if (aChild?.key !== bChild?.key) return false;
}
return true;
}
function synchronizeLayoutWithChildren(initialLayout, children, cols, compactor) {
const layout = [];
const childKeys = /* @__PURE__ */ new Set();
React2.Children.forEach(children, (child) => {
if (!React2.isValidElement(child) || child.key === null) return;
const key = String(child.key);
childKeys.add(key);
const existingItem = initialLayout.find((l) => l.i === key);
if (existingItem) {
layout.push(cloneLayoutItem(existingItem));
} else {
const childProps = child.props;
const dataGrid = childProps["data-grid"];
if (dataGrid) {
layout.push({
i: key,
x: dataGrid.x ?? 0,
y: dataGrid.y ?? 0,
w: dataGrid.w ?? 1,
h: dataGrid.h ?? 1,
minW: dataGrid.minW,
maxW: dataGrid.maxW,
minH: dataGrid.minH,
maxH: dataGrid.maxH,
static: dataGrid.static,
isDraggable: dataGrid.isDraggable,
isResizable: dataGrid.isResizable,
resizeHandles: dataGrid.resizeHandles,
isBounded: dataGrid.isBounded
});
} else {
layout.push({
i: key,
x: 0,
y: bottom(layout),
w: 1,
h: 1
});
}
}
});
const corrected = correctBounds(layout, { cols });
return compactor.compact(corrected, cols);
}
function GridLayout(props) {
const {
// Required
children,
width,
// Composable config interfaces
gridConfig: gridConfigProp,
dragConfig: dragConfigProp,
resizeConfig: resizeConfigProp,
dropConfig: dropConfigProp,
positionStrategy = defaultPositionStrategy,
compactor: compactorProp,
constraints = defaultConstraints,
// Layout data
layout: propsLayout = [],
droppingItem: droppingItemProp,
// Container props
autoSize = true,
className = "",
style = {},
innerRef,
// Callbacks
onLayoutChange = noop,
onDragStart: onDragStartProp = noop,
onDrag: onDragProp = noop,
onDragStop: onDragStopProp = noop,
onResizeStart: onResizeStartProp = noop,
onResize: onResizeProp = noop,
onResizeStop: onResizeStopProp = noop,
onDrop: onDropProp = noop,
onDropDragOver: onDropDragOverProp = noop
} = props;
const gridConfig = useMemo(
() => ({ ...defaultGridConfig, ...gridConfigProp }),
[gridConfigProp]
);
const dragConfig = useMemo(
() => ({ ...defaultDragConfig, ...dragConfigProp }),
[dragConfigProp]
);
const resizeConfig = useMemo(
() => ({ ...defaultResizeConfig, ...resizeConfigProp }),
[resizeConfigProp]
);
const dropConfig = useMemo(
() => ({ ...defaultDropConfig, ...dropConfigProp }),
[dropConfigProp]
);
const { cols, rowHeight, maxRows, margin, containerPadding } = gridConfig;
const {
enabled: isDraggable,
bounded: isBounded,
handle: draggableHandle,
cancel: draggableCancel,
threshold: dragThreshold
} = dragConfig;
const {
enabled: isResizable,
handles: resizeHandles,
handleComponent: resizeHandle
} = resizeConfig;
const {
enabled: isDroppable,
defaultItem: defaultDropItem,
onDragOver: dropConfigOnDragOver
} = dropConfig;
const compactor = compactorProp ?? getCompactor("vertical");
const compactType = compactor.type;
const allowOverlap = compactor.allowOverlap;
const preventCollision = compactor.preventCollision ?? false;
const droppingItem = useMemo(
() => droppingItemProp ?? {
i: "__dropping-elem__",
...defaultDropItem
},
[droppingItemProp, defaultDropItem]
);
const useCSSTransforms = positionStrategy.type === "transform";
const transformScale = positionStrategy.scale;
const effectiveContainerPadding = containerPadding ?? margin;
const [mounted, setMounted] = useState(false);
const [layout, setLayout] = useState(
() => synchronizeLayoutWithChildren(propsLayout, children, cols, compactor)
);
const [activeDrag, setActiveDrag] = useState(null);
const [resizing, setResizing] = useState(false);
const [droppingDOMNode, setDroppingDOMNode] = useState(
null
);
const [droppingPosition, setDroppingPosition] = useState();
const oldDragItemRef = useRef(null);
const oldResizeItemRef = useRef(null);
const oldLayoutRef = useRef(null);
const dragEnterCounterRef = useRef(0);
const prevLayoutRef = useRef(layout);
const prevPropsLayoutRef = useRef(propsLayout);
const prevChildrenRef = useRef(children);
const prevCompactTypeRef = useRef(compactType);
const layoutRef = useRef(layout);
layoutRef.current = layout;
useEffect(() => {
setMounted(true);
if (!deepEqual(layout, propsLayout)) {
onLayoutChange(layout);
}
}, []);
useEffect(() => {
if (activeDrag) return;
if (droppingDOMNode) return;
const layoutChanged = !deepEqual(propsLayout, prevPropsLayoutRef.current);
const childrenChanged = !childrenEqual(children, prevChildrenRef.current);
const compactTypeChanged = compactType !== prevCompactTypeRef.current;
if (layoutChanged || childrenChanged || compactTypeChanged) {
const baseLayout = layoutChanged ? propsLayout : layout;
const newLayout = synchronizeLayoutWithChildren(
baseLayout,
children,
cols,
compactor
);
if (!deepEqual(newLayout, layout)) {
setLayout(newLayout);
}
}
prevPropsLayoutRef.current = propsLayout;
prevChildrenRef.current = children;
prevCompactTypeRef.current = compactType;
}, [
propsLayout,
children,
cols,
compactType,
compactor,
activeDrag,
droppingDOMNode,
layout
]);
useEffect(() => {
if (!activeDrag && !deepEqual(layout, prevLayoutRef.current)) {
prevLayoutRef.current = layout;
const publicLayout = layout.filter((l) => l.i !== droppingItem.i);
onLayoutChange(publicLayout);
}
}, [layout, activeDrag, onLayoutChange, droppingItem.i]);
const containerHeight = useMemo(() => {
if (!autoSize) return void 0;
const nbRow = bottom(layout);
const containerPaddingY = effectiveContainerPadding[1];
return nbRow * rowHeight + (nbRow - 1) * margin[1] + containerPaddingY * 2 + "px";
}, [autoSize, layout, rowHeight, margin, effectiveContainerPadding]);
const onDragStart = useCallback(
(i, _x, _y, data) => {
const currentLayout = layoutRef.current;
const l = getLayoutItem(currentLayout, i);
if (!l) return;
const placeholder = {
w: l.w,
h: l.h,
x: l.x,
y: l.y,
i
};
oldDragItemRef.current = cloneLayoutItem(l);
oldLayoutRef.current = currentLayout;
setActiveDrag(placeholder);
onDragStartProp(currentLayout, l, l, null, data.e, data.node);
},
[onDragStartProp]
);
const onDrag = useCallback(
(i, x, y, data) => {
const currentLayout = layoutRef.current;
const oldDragItem = oldDragItemRef.current;
const l = getLayoutItem(currentLayout, i);
if (!l) return;
const placeholder = {
w: l.w,
h: l.h,
x: l.x,
y: l.y,
i
};
const newLayout = moveElement(
currentLayout,
l,
x,
y,
true,
preventCollision,
compactType,
cols,
allowOverlap
);
onDragProp(newLayout, oldDragItem, l, placeholder, data.e, data.node);
setLayout(compactor.compact(newLayout, cols));
setActiveDrag(placeholder);
},
[preventCollision, compactType, cols, allowOverlap, compactor, onDragProp]
);
const onDragStop = useCallback(
(i, x, y, data) => {
if (!activeDrag) return;
const currentLayout = layoutRef.current;
const oldDragItem = oldDragItemRef.current;
const l = getLayoutItem(currentLayout, i);
if (!l) return;
const newLayout = moveElement(
currentLayout,
l,
x,
y,
true,
preventCollision,
compactType,
cols,
allowOverlap
);
const finalLayout = compactor.compact(newLayout, cols);
onDragStopProp(finalLayout, oldDragItem, l, null, data.e, data.node);
const oldLayout = oldLayoutRef.current;
oldDragItemRef.current = null;
oldLayoutRef.current = null;
setActiveDrag(null);
setLayout(finalLayout);
if (oldLayout && !deepEqual(oldLayout, finalLayout)) {
onLayoutChange(finalLayout);
}
},
[
activeDrag,
preventCollision,
compactType,
cols,
allowOverlap,
compactor,
onDragStopProp,
onLayoutChange
]
);
const onResizeStart = useCallback(
(i, _w, _h, data) => {
const currentLayout = layoutRef.current;
const l = getLayoutItem(currentLayout, i);
if (!l) return;
oldResizeItemRef.current = cloneLayoutItem(l);
oldLayoutRef.current = currentLayout;
setResizing(true);
onResizeStartProp(currentLayout, l, l, null, data.e, data.node);
},
[onResizeStartProp]
);
const onResize = useCallback(
(i, w, h, data) => {
const currentLayout = layoutRef.current;
const oldResizeItem = oldResizeItemRef.current;
const { handle } = data;
let shouldMoveItem = false;
let newX;
let newY;
const [newLayout, l] = withLayoutItem(currentLayout, i, (item) => {
newX = item.x;
newY = item.y;
if (["sw", "w", "nw", "n", "ne"].includes(handle)) {
if (["sw", "nw", "w"].includes(handle)) {
newX = item.x + (item.w - w);
w = item.x !== newX && newX < 0 ? item.w : w;
newX = newX < 0 ? 0 : newX;
}
if (["ne", "n", "nw"].includes(handle)) {
newY = item.y + (item.h - h);
h = item.y !== newY && newY < 0 ? item.h : h;
newY = newY < 0 ? 0 : newY;
}
shouldMoveItem = true;
}
if (preventCollision && !allowOverlap) {
const collisions = getAllCollisions(currentLayout, {
...item,
w,
h,
x: newX ?? item.x,
y: newY ?? item.y
}).filter((layoutItem) => layoutItem.i !== item.i);
if (collisions.length > 0) {
newY = item.y;
h = item.h;
newX = item.x;
w = item.w;
shouldMoveItem = false;
}
}
item.w = w;
item.h = h;
return item;
});
if (!l) return;
let finalLayout = newLayout;
if (shouldMoveItem && newX !== void 0 && newY !== void 0) {
finalLayout = moveElement(
newLayout,
l,
newX,
newY,
true,
preventCollision,
compactType,
cols,
allowOverlap
);
}
const placeholder = {
w: l.w,
h: l.h,
x: l.x,
y: l.y,
i,
static: true
};
onResizeProp(
finalLayout,
oldResizeItem,
l,
placeholder,
data.e,
data.node
);
setLayout(compactor.compact(finalLayout, cols));
setActiveDrag(placeholder);
},
[preventCollision, compactType, cols, allowOverlap, compactor, onResizeProp]
);
const onResizeStop = useCallback(
(i, _w, _h, data) => {
const currentLayout = layoutRef.current;
const oldResizeItem = oldResizeItemRef.current;
const l = getLayoutItem(currentLayout, i);
const finalLayout = compactor.compact(currentLayout, cols);
onResizeStopProp(
finalLayout,
oldResizeItem,
l ?? null,
null,
data.e,
data.node
);
const oldLayout = oldLayoutRef.current;
oldResizeItemRef.current = null;
oldLayoutRef.current = null;
setActiveDrag(null);
setResizing(false);
setLayout(finalLayout);
if (oldLayout && !deepEqual(oldLayout, finalLayout)) {
onLayoutChange(finalLayout);
}
},
[cols, compactor, onResizeStopProp, onLayoutChange]
);
const removeDroppingPlaceholder = useCallback(() => {
const currentLayout = layoutRef.current;
const hasDroppingItem = currentLayout.some((l) => l.i === droppingItem.i);
if (!hasDroppingItem) {
setDroppingDOMNode(null);
setActiveDrag(null);
setDroppingPosition(void 0);
return;
}
const newLayout = compactor.compact(
currentLayout.filter((l) => l.i !== droppingItem.i),
cols
);
setLayout(newLayout);
setDroppingDOMNode(null);
setActiveDrag(null);
setDroppingPosition(void 0);
}, [droppingItem.i, cols, compactor]);
const handleDragOver = useCallback(
(e) => {
e.preventDefault();
e.stopPropagation();
if (isFirefox && !e.nativeEvent.target?.classList.contains(
layoutClassName
)) {
return false;
}
const rawResult = dropConfigOnDragOver ? dropConfigOnDragOver(e.nativeEvent) : onDropDragOverProp(e);
if (rawResult === false) {
if (droppingDOMNode) {
removeDroppingPlaceholder();
}
return false;
}
const {
dragOffsetX = 0,
dragOffsetY = 0,
...onDragOverResult
} = rawResult ?? {};
const finalDroppingItem = { ...droppingItem, ...onDragOverResult };
const gridRect = e.currentTarget.getBoundingClientRect();
const positionParams = {
cols,
margin,
maxRows,
rowHeight,
containerWidth: width,
containerPadding: effectiveContainerPadding
};
const actualColWidth = calcGridColWidth(positionParams);
const itemPixelWidth = calcGridItemWHPx(
finalDroppingItem.w,
actualColWidth,
margin[0]
);
const itemPixelHeight = calcGridItemWHPx(
finalDroppingItem.h,
rowHeight,
margin[1]
);
const itemCenterOffsetX = itemPixelWidth / 2;
const itemCenterOffsetY = itemPixelHeight / 2;
const rawGridX = e.clientX - gridRect.left + dragOffsetX - itemCenterOffsetX;
const rawGridY = e.clientY - gridRect.top + dragOffsetY - itemCenterOffsetY;
const clampedGridX = Math.max(0, rawGridX);
const clampedGridY = Math.max(0, rawGridY);
const newDroppingPosition = {
left: clampedGridX / transformScale,
top: clampedGridY / transformScale,
e: e.nativeEvent
};
if (!droppingDOMNode) {
const calculatedPosition = calcXY(
positionParams,
clampedGridY,
clampedGridX,
finalDroppingItem.w,
finalDroppingItem.h
);
setDroppingDOMNode(/* @__PURE__ */ jsx("div", {}, finalDroppingItem.i));
setDroppingPosition(newDroppingPosition);
setLayout([
...layoutRef.current,
{
...finalDroppingItem,
x: calculatedPosition.x,
y: calculatedPosition.y,
static: false,
isDraggable: true
}
]);
} else if (droppingPosition) {
const shouldUpdate = droppingPosition.left !== newDroppingPosition.left || droppingPosition.top !== newDroppingPosition.top;
if (shouldUpdate) {
setDroppingPosition(newDroppingPosition);
}
}
},
[
droppingDOMNode,
droppingPosition,
droppingItem,
dropConfigOnDragOver,
onDropDragOverProp,
removeDroppingPlaceholder,
transformScale,
cols,
margin,
maxRows,
rowHeight,
width,
effectiveContainerPadding
]
);
const handleDragLeave = useCallback(
(e) => {
e.preventDefault();
e.stopPropagation();
dragEnterCounterRef.current--;
if (dragEnterCounterRef.current < 0) {
dragEnterCounterRef.current = 0;
}
if (dragEnterCounterRef.current === 0) {
removeDroppingPlaceholder();
}
},
[removeDroppingPlaceholder]
);
const handleDragEnter = useCallback((e) => {
e.preventDefault();
e.stopPropagation();
dragEnterCounterRef.current++;
}, []);
const handleDrop = useCallback(
(e) => {
e.preventDefault();
e.stopPropagation();
const currentLayout = layoutRef.current;
const item = currentLayout.find((l) => l.i === droppingItem.i);
dragEnterCounterRef.current = 0;
removeDroppingPlaceholder();
onDropProp(currentLayout, item, e.nativeEvent);
},
[droppingItem.i, removeDroppingPlaceholder, onDropProp]
);
const processGridItem = useCallback(
(child, isDroppingItem) => {
if (!child || !child.key) return null;
const l = getLayoutItem(layout, String(child.key));
if (!l) return null;
const draggable = typeof l.isDraggable === "boolean" ? l.isDraggable : !l.static && isDraggable;
const resizable = typeof l.isResizable === "boolean" ? l.isResizable : !l.static && isResizable;
const resizeHandlesOptions = l.resizeHandles || [...resizeHandles];
const bounded = draggable && isBounded && l.isBounded !== false;
const resizeHandleElement = resizeHandle;
return /* @__PURE__ */ jsx(
GridItem,
{
containerWidth: width,
cols,
margin,
containerPadding: effectiveContainerPadding,
maxRows,
rowHeight,
cancel: draggableCancel,
handle: draggableHandle,
onDragStart,
onDrag,
onDragStop,
onResizeStart,
onResize,
onResizeStop,
isDraggable: draggable,
isResizable: resizable,
isBounded: bounded,
useCSSTransforms: useCSSTransforms && mounted,
usePercentages: !mounted,
transformScale,
positionStrategy,
dragThreshold,
w: l.w,
h: l.h,
x: l.x,
y: l.y,
i: l.i,
minH: l.minH,
minW: l.minW,
maxH: l.maxH,
maxW: l.maxW,
static: l.static,
droppingPosition: isDroppingItem ? droppingPosition : void 0,
resizeHandles: resizeHandlesOptions,
resizeHandle: resizeHandleElement,
constraints,
layoutItem: l,
layout,
children: child
},
l.i
);
},
[
layout,
width,
cols,
margin,
effectiveContainerPadding,
maxRows,
rowHeight,
draggableCancel,
draggableHandle,
onDragStart,
onDrag,
onDragStop,
onResizeStart,
onResize,
onResizeStop,
isDraggable,
isResizable,
isBounded,
useCSSTransforms,
mounted,
transformScale,
positionStrategy,
dragThreshold,
droppingPosition,
resizeHandles,
resizeHandle,
constraints
]
);
const renderPlaceholder = () => {
if (!activeDrag) return null;
return /* @__PURE__ */ jsx(
GridItem,
{
w: activeDrag.w,
h: activeDrag.h,
x: activeDrag.x,
y: activeDrag.y,
i: activeDrag.i,
className: `react-grid-placeholder ${resizing ? "placeholder-resizing" : ""}`,
containerWidth: width,
cols,
margin,
containerPadding: effectiveContainerPadding,
maxRows,
rowHeight,
isDraggable: false,
isResizable: false,
isBounded: false,
useCSSTransforms,
transformScale,
constraints,
layout,
children: /* @__PURE__ */ jsx("div", {})
}
);
};
const mergedClassName = clsx(layoutClassName, className);
const mergedStyle = {
height: containerHeight,
...style
};
return /* @__PURE__ */ jsxs(
"div",
{
ref: innerRef,
className: mergedClassName,
style: mergedStyle,
onDrop: isDroppable ? handleDrop : void 0,
onDragLeave: isDroppable ? handleDragLeave : void 0,
onDragEnter: isDroppable ? handleDragEnter : void 0,
onDragOver: isDroppable ? handleDragOver : void 0,
children: [
React2.Children.map(children, (child) => {
if (!React2.isValidElement(child)) return null;
return processGridItem(child);
}),
isDroppable && droppingDOMNode && processGridItem(droppingDOMNode, true),
renderPlaceholder()
]
}
);
}
var DEFAULT_BREAKPOINTS = {
lg: 1200,
md: 996,
sm: 768,
xs: 480,
xxs: 0
};
var DEFAULT_COLS = {
lg: 12,
md: 10,
sm: 6,
xs: 4,
xxs: 2
};
var noop2 = () => {
};
function synchronizeLayoutWithChildren2(initialLayout, children, cols, compactor) {
const layout = [];
React2.Children.forEach(children, (child) => {
if (!React2.isValidElement(child) || child.key === null) return;
const key = String(child.key);
const existingItem = initialLayout.find((l) => l.i === key);
if (existingItem) {
layout.push({
...existingItem,
i: key
});
} else {
const childProps = child.props;
const dataGrid = childProps["data-grid"];
if (dataGrid) {
layout.push({
i: key,
x: dataGrid.x ?? 0,
y: dataGrid.y ?? 0,
w: dataGrid.w ?? 1,
h: dataGrid.h ?? 1,
minW: dataGrid.minW,
maxW: dataGrid.maxW,
minH: dataGrid.minH,
maxH: dataGrid.maxH,
static: dataGrid.static,
isDraggable: dataGrid.isDraggable,
isResizable: dataGrid.isResizable,
resizeHandles: dataGrid.resizeHandles,
isBounded: dataGrid.isBounded
});
} else {
layout.push({
i: key,
x: 0,
y: bottom(layout),
w: 1,
h: 1
});
}
}
});
const corrected = correctBounds(layout, { cols });
return compactor.compact(corrected, cols);
}
function ResponsiveGridLayout(props) {
const {
children,
width,
breakpoint: propBreakpoint,
breakpoints = DEFAULT_BREAKPOINTS,
cols: colsConfig = DEFAULT_COLS,
layouts: propsLayouts = {},
rowHeight = 150,
maxRows = Infinity,
margin: propMargin = [10, 10],
containerPadding: propContainerPadding = null,
compactor: compactorProp,
onBreakpointChange = noop2,
onLayoutChange = noop2,
onWidthChange = noop2,
...restProps
} = props;
const compactor = compactorProp ?? getCompactor("vertical");
const compactType = compactor.type;
const allowOverlap = compactor.allowOverlap;
const initialBreakpoint = useMemo(() => {
return propBreakpoint ?? getBreakpointFromWidth(breakpoints, width);
}, []);
const initialCols = useMemo(() => {
return getColsFromBreakpoint(initialBreakpoint, colsConfig);
}, [initialBreakpoint, colsConfig]);
const initialLayout = useMemo(() => {
return findOrGenerateResponsiveLayout(
propsLayouts,
breakpoints,
initialBreakpoint,
initialBreakpoint,
initialCols,
compactType
);
}, []);
const [breakpoint, setBreakpoint] = useState(initialBreakpoint);
const [cols, setCols] = useState(initialCols);
const [layout, setLayout] = useState(initialLayout);
const [layouts, setLayouts] = useState(propsLayouts);
const prevWidthRef = useRef(width);
const prevBreakpointRef = useRef(propBreakpoint);
const prevBreakpointsRef = useRef(breakpoints);
const prevColsRef = useRef(colsConfig);
const prevLayoutsRef = useRef(propsLayouts);
const prevCompactTypeRef = useRef(compactType);
const layoutsRef = useRef(layouts);
useEffect(() => {
layoutsRef.current = layouts;
}, [layouts]);
const derivedLayout = useMemo(() => {
if (!deepEqual(propsLayouts, prevLayoutsRef.current)) {
return findOrGenerateResponsiveLayout(
propsLayouts,
breakpoints,
breakpoint,
breakpoint,
cols,
compactor
);
}
return null;
}, [propsLayouts, breakpoints, breakpoint, cols, compactor]);
const effectiveLayout = derivedLayout ?? layout;
useEffect(() => {
if (derivedLayout !== null) {
setLayout(derivedLayout);
setLayouts(propsLayouts);
layoutsRef.current = propsLayouts;
prevLayoutsRef.current = propsLayouts;
}
}, [derivedLayout, propsLayouts]);
useEffect(() => {
if (compactType !== prevCompactTypeRef.current) {
const newLayout = compactor.compact(cloneLayout(effectiveLayout), cols);
const newLayouts = {
...layoutsRef.current,
[breakpoint]: newLayout
};
setLayout(newLayout);
setLayouts(newLayouts);
layoutsRef.current = newLayouts;
onLayoutChange(newLayout, newLayouts);
prevCompactTypeRef.current = compactType;
}
}, [
compactType,
compactor,
effectiveLayout,
cols,
allowOverlap,
breakpoint,
onLayoutChange
]);
useEffect(() => {
const widthChanged = width !== prevWidthRef.current;
const breakpointPropChanged = propBreakpoint !== prevBreakpointRef.current;
const breakpointsChanged = !deepEqual(
breakpoints,
prevBreakpointsRef.current
);
const colsChanged = !deepEqual(colsConfig, prevColsRef.current);
if (widthChanged || breakpointPropChanged || breakpointsChanged || colsChanged) {
const newBreakpoint = propBreakpoint ?? getBreakpointFromWidth(breakpoints, width);
const newCols = getColsFromBreakpoint(newBreakpoint, colsConfig);
const lastBreakpoint = breakpoint;
if (lastBreakpoint !== newBreakpoint || breakpointsChanged || colsChanged) {
const newLayouts = { ...layoutsRef.current };
if (!newLayouts[lastBreakpoint]) {
newLayouts[lastBreakpoint] = cloneLayout(layout);
}
let newLayout = findOrGenerateResponsiveLayout(
newLayouts,
breakpoints,
newBreakpoint,
lastBreakpoint,
newCols,
compactor
);
newLayout = synchronizeLayoutWithChildren2(
newLayout,
children,
newCols,
compactor
);
newLayouts[newBreakpoint] = newLayout;
setBreakpoint(newBreakpoint);
setCols(newCols);
setLayout(newLayout);
setLayouts(newLayouts);
layoutsRef.current = newLayouts;
onBreakpointChange(newBreakpoint, newCols);
onLayoutChange(newLayout, newLayouts);
}
const currentMargin2 = getIndentationValue(
propMargin,
newBreakpoint
);
const currentPadding = propContainerPadding ? getIndentationValue(
propContainerPadding,
newBreakpoint
) : null;
onWidthChange(width, currentMargin2, newCols, currentPadding);
prevWidthRef.current = width;
prevBreakpointRef.current = propBreakpoint;
prevBreakpointsRef.current = breakpoints;
prevColsRef.current = colsConfig;
}
}, [
width,
propBreakpoint,
breakpoints,
colsConfig,
breakpoint,
cols,
layout,
children,
compactor,
compactType,
allowOverlap,
propMargin,
propContainerPadding,
onBreakpointChange,
onLayoutChange,
onWidthChange
]);
const handleLayoutChange = useCallback(
(newLayout) => {
const currentLayouts = layoutsRef.current;
const newLayouts = {
...currentLayouts,
[breakpoint]: newLayout
};
setLayout(newLayout);
setLayouts(newLayouts);
layoutsRef.current = newLayouts;
onLayoutChange(newLayout, newLayouts);
},
[breakpoint, onLayoutChange]
);
const currentMargin = useMemo(() => {
return getIndentationValue(
propMargin,
breakpoint
);
}, [propMargin, breakpoint]);
const currentContainerPadding = useMemo(() => {
if (propContainerPadding === null) return null;
return getIndentationValue(
propContainerPadding,
breakpoint
);
}, [propContainerPadding, breakpoint]);
const gridConfig = useMemo(
() => ({
cols,
rowHeight,
maxRows,
margin: currentMargin,
containerPadding: currentContainerPadding
}),
[cols, rowHeight, maxRows, currentMargin, currentContainerPadding]
);
return /* @__PURE__ */ jsx(
GridLayout,
{
...restProps,
width,
gridConfig,
compactor,
onLayoutChange: handleLayoutChange,
layout: effectiveLayout,
children
}
);
}
export { GridItem, GridLayout, ResponsiveGridLayout };
//# sourceMappingURL=chunk-XM2M6TC6.mjs.map
//# sourceMappingURL=chunk-XM2M6TC6.mjs.map