UNPKG

react-grid-layout

Version:

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

1,517 lines (1,515 loc) 44.3 kB
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