diff --git a/packages/core/components/DraggableComponent/index.tsx b/packages/core/components/DraggableComponent/index.tsx index 4312eaa70..87c3c7535 100644 --- a/packages/core/components/DraggableComponent/index.tsx +++ b/packages/core/components/DraggableComponent/index.tsx @@ -1,12 +1,160 @@ -import { CSSProperties, ReactNode, SyntheticEvent, useEffect } from "react"; -import { Draggable } from "react-beautiful-dnd"; +import { + CSSProperties, + MutableRefObject, + ReactNode, + SyntheticEvent, + useCallback, + useContext, + useEffect, + useRef, + useState, +} from "react"; +import { + DragStart, + DragUpdate, + Draggable, + DraggableProvided, + DraggableStateSnapshot, + DraggingStyle, +} from "react-beautiful-dnd"; import styles from "./styles.module.css"; import getClassNameFactory from "../../lib/get-class-name-factory"; import { Copy, Trash } from "react-feather"; import { useModifierHeld } from "../../lib/use-modifier-held"; +import { dropZoneContext } from "../DropZone"; +import { getItem } from "../../lib/get-item"; +import { getZoneId } from "../../lib/get-zone-id"; +import { DropZoneContext } from "../DropZone/context"; const getClassName = getClassNameFactory("DraggableComponent", styles); +function getAbsCoordinates(elem) { + // crossbrowser version + const box = elem.getBoundingClientRect(); + + const body = document.body; + const docEl = document.documentElement; + + const scrollTop = docEl.scrollTop || body.scrollTop; + const scrollLeft = docEl.scrollLeft || body.scrollLeft; + + const clientTop = docEl.clientTop || body.clientTop || 0; + const clientLeft = docEl.clientLeft || body.clientLeft || 0; + + const top = box.top + scrollTop - clientTop; + const left = box.left + scrollLeft - clientLeft; + + return { top: Math.round(top), left: Math.round(left) }; +} + +export const patchStyles = ({ + provided, + snapshot, + draggedEl, + draggedItem, + droppableSizes, + placeholderStyle, +}: { + provided: DraggableProvided; + snapshot: DraggableStateSnapshot; + draggedEl?: HTMLElement | null; + draggedItem?: DragStart & Partial; + droppableSizes?: Record; + placeholderStyle?: CSSProperties; +}) => { + let additionalStyles: CSSProperties = {}; + + let width: CSSProperties["width"]; + let widthDifference: number = 0; + + let originalWidth: number; + + if (draggedItem?.destination && droppableSizes) { + originalWidth = droppableSizes[draggedItem?.source?.droppableId].width; + + width = + droppableSizes[draggedItem?.destination?.droppableId].width || + originalWidth; + + widthDifference = originalWidth - width; + } + + const destination = draggedItem?.destination; + + const [sourceArea] = getZoneId(draggedItem?.source.droppableId); + const [destinationArea] = getZoneId(draggedItem?.destination?.droppableId); + + // We use custom animations when changing area + const changedArea = sourceArea !== destinationArea; + + if (destination) { + const droppableEl = document.querySelector( + `[data-rbd-droppable-id="${destination.droppableId}"]` + ) as HTMLElement | null; + + if ( + snapshot.isDropAnimating && + snapshot.dropAnimation && + typeof width === "number" + ) { + const { moveTo } = snapshot.dropAnimation; + + if (width) additionalStyles.width = width; + + if (changedArea) { + if (draggedEl) { + let transform = draggedEl.style.transform; + + if (transform) { + const matches = + /translate\((-?\d+\.?\d*)px,\s*(-?\d+\.?\d*)px\)/.exec(transform); + const existingTransformY = matches ? parseFloat(matches[2]) : 0; + + if (draggedEl && droppableEl && placeholderStyle) { + const placeholderTop = parseInt(placeholderStyle.top!.toString()); + + const transformY = -( + draggedEl?.getBoundingClientRect().y - + (placeholderTop + getAbsCoordinates(droppableEl).top) + ); + + additionalStyles.transform = `translate(${moveTo.x}px, ${ + transformY + existingTransformY + }px)`; + } + } + } + } + } else if (snapshot.isDragging) { + let transform = provided.draggableProps.style?.transform; + + if (transform) { + const matches = /translate\((-?\d+\.?\d*)px,\s*(-?\d+\.?\d*)px\)/.exec( + transform + ); + const x = matches ? parseFloat(matches[1]) : 0; + const y = matches ? parseFloat(matches[2]) : 0; + + const updatedX = x + widthDifference / 2; + + const updatedY = changedArea ? y : y; + + // console.log(getAbsCoordinates(draggedEl).top); + // console.log(x, y); + // console.log(draggedEl?.getBoundingClientRect()); + + transform = `translate(${updatedX}px, ${updatedY}px)`; + } + if (width) { + additionalStyles.width = width; + } + additionalStyles.transform = transform; + } + + return additionalStyles; + } +}; + export const DraggableComponent = ({ children, id, @@ -46,7 +194,14 @@ export const DraggableComponent = ({ }) => { const isModifierHeld = useModifierHeld("Alt"); - useEffect(onMount, []); + useEffect(() => { + if (onMount) onMount(); + }, []); + + const { draggedItem, droppableSizes, data, placeholderStyle } = + useContext(dropZoneContext) || {}; + + const ref = useRef(null) as MutableRefObject; return ( - {(provided, snapshot) => ( -
- {debug} -
{children}
- -
-
- {label && ( -
{label}
- )} - - + {(provided, snapshot) => { + const draggedEl = ref.current; + + const additionalStyles = patchStyles({ + provided, + snapshot, + draggedEl, + draggedItem, + droppableSizes, + placeholderStyle, + }); + + return ( +
{ + ref.current = node; + provided.innerRef(node); + }} + {...provided.draggableProps} + {...provided.dragHandleProps} + id={`draggable-component-${id}`} + className={getClassName({ + isSelected, + isModifierHeld, + isDragging: snapshot.isDragging, + isLocked, + forceHover, + indicativeHover, + })} + style={{ + ...style, + ...provided.draggableProps.style, + ...additionalStyles, + cursor: isModifierHeld ? "initial" : "grab", + marginLeft: "auto", + marginRight: "auto", // Adjust the transform property if dragging + // transform, + }} + onMouseOver={onMouseOver} + onMouseOut={onMouseOut} + onClick={onClick} + > + {debug} +
{children}
+ +
+
+ {label && ( +
{label}
+ )} + + +
-
- )} + ); + }} ); }; diff --git a/packages/core/components/DropZone/context.tsx b/packages/core/components/DropZone/context.tsx index 77033d27c..87d9eb7c0 100644 --- a/packages/core/components/DropZone/context.tsx +++ b/packages/core/components/DropZone/context.tsx @@ -41,6 +41,8 @@ export type DropZoneContext = { pathData?: PathData; registerPath?: (selector: ItemSelector) => void; mode?: "edit" | "render"; + parentDragging?: boolean; + droppableSizes?: Record; } | null; export const dropZoneContext = createContext(null); @@ -60,7 +62,7 @@ export const DropZoneProvider = ({ // Hovering component may match area, but areas must always contain zones const [hoveringComponent, setHoveringComponent] = useState(); - const [hoveringAreaDb] = useDebounce(hoveringArea, 75, { leading: false }); + const [hoveringAreaDb] = useDebounce(hoveringArea, 150, { leading: false }); const [areasWithZones, setAreasWithZones] = useState>( {} @@ -164,7 +166,7 @@ export const DropZoneProvider = ({ activeZones, registerPath, pathData, - + parentDragging: false, ...value, }} > diff --git a/packages/core/components/DropZone/index.tsx b/packages/core/components/DropZone/index.tsx index b6c7aecbb..4c5c5a0b0 100644 --- a/packages/core/components/DropZone/index.tsx +++ b/packages/core/components/DropZone/index.tsx @@ -1,5 +1,12 @@ -import { CSSProperties, useContext, useEffect } from "react"; -import { DraggableComponent } from "../DraggableComponent"; +import { + CSSProperties, + MutableRefObject, + ReactNode, + useContext, + useEffect, + useRef, +} from "react"; +import { DraggableComponent, patchStyles } from "../DraggableComponent"; import DroppableStrictMode from "../DroppableStrictMode"; import { getItem } from "../../lib/get-item"; import { setupZone } from "../../lib/setup-zone"; @@ -8,6 +15,7 @@ import { getClassNameFactory } from "../../lib"; import styles from "./styles.module.css"; import { DropZoneProvider, dropZoneContext } from "./context"; import { getZoneId } from "../../lib/get-zone-id"; +import { DraggableStateSnapshot } from "react-beautiful-dnd"; const getClassName = getClassNameFactory("DropZone", styles); @@ -82,6 +90,8 @@ function DropZoneEdit({ zone, style }: DropZoneProps) { const draggingOverArea = userIsDragging && zoneArea === draggedSourceArea; const draggingNewComponent = draggedSourceId === "component-list"; + const draggedRef = useRef(null) as MutableRefObject; + if ( !ctx?.config || !ctx.setHoveringArea || @@ -121,17 +131,36 @@ function DropZoneEdit({ zone, style }: DropZoneProps) { * the specific zone (which increases robustness when using flex * layouts) */ - if (userIsDragging) { - if (draggingNewComponent) { - isEnabled = hoveringOverArea; - } else { - isEnabled = draggingOverArea && hoveringOverZone; - } + if (userIsDragging && !ctx.parentDragging) { + isEnabled = hoveringOverArea && hoveringOverZone; + // if (draggingNewComponent) { + // isEnabled = hoveringOverArea; + // } else { + // isEnabled = draggingOverArea && hoveringOverZone; + // } } const selectedItem = itemSelector ? getItem(itemSelector, data) : null; const isAreaSelected = selectedItem && zoneArea === selectedItem.props.id; + const draggedItemData = draggedItem + ? getItem( + { + zone: draggedSourceId, + index: draggedItem!.source.index, + }, + data + ) + : null; + + const El = !ctx.parentDragging + ? ({ + children, + }: { + children: (provided: object, snapshot: object) => ReactNode; + }) => <>{children({}, {})} + : DroppableStrictMode; + return (
{ + // const Render = config.components[draggedItemData.type] + // ? config.components[draggedItemData.type].render + // : () => ( + //
+ // No configuration for {draggedItemData.type} + //
+ // ); + + // const additionalStyles = patchStyles({ + // provided, + // snapshot, + // draggedItem, + // draggedEl: draggedRef.current, + // droppableSizes: ctx.droppableSizes, + // placeholderStyle, + // }); + + // return ( + //
{ + // draggedRef.current = node; + // provided.innerRef(node); + // }} + // > + //
+ // {/*
Test
*/} + // + //
+ //
+ // ); + // } + // : undefined + // } > {(provided, snapshot) => { return ( @@ -165,6 +246,8 @@ function DropZoneEdit({ zone, style }: DropZoneProps) { setHoveringZone(zoneCompound); }} > + {/* {`${isEnabled}`} */} + {/* {`${hoveringOverArea} ${hoveringArea}`} */} {content.map((item, i) => { const componentId = item.props.id; @@ -177,6 +260,10 @@ function DropZoneEdit({ zone, style }: DropZoneProps) { const isSelected = selectedItem?.props.id === componentId || false; + const isDragging = + draggedItem?.draggableId.split("draggable-")[1] === + componentId; + const containsZone = areasWithZones ? areasWithZones[componentId] : false; @@ -190,13 +277,23 @@ function DropZoneEdit({ zone, style }: DropZoneProps) { ); return ( -
+
+ {/* {isDragging && ( +
+ +
+ )} */} -
- +
+
+ +
+ + {/* {isDragging && ( +
+ +
+ )} */} {userIsDragging && (
); })} - {provided?.placeholder} - {snapshot?.isDraggingOver && ( -
- )} +
+ {provided.placeholder} + {snapshot.isDraggingOver && ( +
+ )} +
); }} diff --git a/packages/core/components/DropZone/styles.module.css b/packages/core/components/DropZone/styles.module.css index 7e66683d9..243007185 100644 --- a/packages/core/components/DropZone/styles.module.css +++ b/packages/core/components/DropZone/styles.module.css @@ -1,7 +1,7 @@ .DropZone { margin-left: auto; margin-right: auto; - zoom: 1.33; + /* zoom: 1.33; */ position: relative; height: 100%; outline-offset: -1px; @@ -17,13 +17,14 @@ pointer-events: all; } -.DropZone--userIsDragging:not(.DropZone--draggingOverArea):not( +/* .DropZone--userIsDragging:not(.DropZone--draggingOverArea):not( .DropZone--draggingNewComponent ) > .DropZone-content { pointer-events: none; -} +} */ +.DropZone--userIsDragging:not(.DropZone--isRootZone), .DropZone--isAreaSelected, .DropZone--draggingOverArea:not(:has(.DropZone--hoveringOverArea)), .DropZone--hoveringOverArea:not(.DropZone--isDisabled):not( @@ -48,6 +49,7 @@ .DropZone-item { position: relative; + /* z-index: 0; */ } .DropZone-item:has(.DropZone--draggingOverArea) { @@ -59,5 +61,5 @@ bottom: -12px; height: 24px; width: 100%; - z-index: 1; + z-index: 2; } diff --git a/packages/core/components/Puck/index copy.tsx b/packages/core/components/Puck/index copy.tsx new file mode 100644 index 000000000..b9ea44108 --- /dev/null +++ b/packages/core/components/Puck/index copy.tsx @@ -0,0 +1,642 @@ +"use client"; + +import { + ReactElement, + ReactNode, + useCallback, + useEffect, + useReducer, + useState, +} from "react"; +import { DragDropContext, DragStart, DragUpdate } from "react-beautiful-dnd"; +import type { Config, Data, Field } from "../../types/Config"; +import { InputOrGroup } from "../InputOrGroup"; +import { ComponentList } from "../ComponentList"; +import { filter } from "../../lib"; +import { Button } from "../Button"; + +import { Plugin } from "../../types/Plugin"; +import { usePlaceholderStyle } from "../../lib/use-placeholder-style"; + +import { SidebarSection } from "../SidebarSection"; +import { Globe, Sidebar } from "react-feather"; +import { Heading } from "../Heading"; +import { IconButton } from "../IconButton/IconButton"; +import { DropZone, DropZoneProvider, dropZoneContext } from "../DropZone"; +import { rootDroppableId } from "../../lib/root-droppable-id"; +import { ItemSelector, getItem } from "../../lib/get-item"; +import { PuckAction, StateReducer, createReducer } from "../../lib/reducer"; +import { LayerTree } from "../LayerTree"; +import { findZonesForArea } from "../../lib/find-zones-for-area"; +import { areaContainsZones } from "../../lib/area-contains-zones"; +import { flushZones } from "../../lib/flush-zones"; +import { + DndContext, + DragStartEvent, + KeyboardSensor, + PointerSensor, + closestCenter, + useSensor, + useSensors, +} from "@dnd-kit/core"; +import { DraggableData } from "../DraggableComponent"; +import { sortableKeyboardCoordinates } from "@dnd-kit/sortable"; + +const Field = () => {}; + +const defaultPageFields: Record = { + title: { type: "text" }, +}; + +const PluginRenderer = ({ + children, + data, + plugins, + renderMethod, +}: { + children: ReactNode; + data: Data; + plugins; + renderMethod: "renderRoot" | "renderRootFields" | "renderFields"; +}) => { + return plugins + .filter((item) => item[renderMethod]) + .map((item) => item[renderMethod]) + .reduce( + (accChildren, Item) => {accChildren}, + children + ); +}; + +export function Puck({ + config, + data: initialData = { content: [], root: { title: "" } }, + onChange, + onPublish, + plugins = [], + renderHeader, + renderHeaderActions, + headerTitle, + headerPath, +}: { + config: Config; + data: Data; + onChange?: (data: Data) => void; + onPublish: (data: Data) => void; + plugins?: Plugin[]; + renderHeader?: (props: { + children: ReactNode; + data: Data; + dispatch: (action: PuckAction) => void; + }) => ReactElement; + renderHeaderActions?: (props: { + data: Data; + dispatch: (action: PuckAction) => void; + }) => ReactElement; + headerTitle?: string; + headerPath?: string; +}) { + const [reducer] = useState(() => createReducer({ config })); + const [data, dispatch] = useReducer( + reducer, + flushZones(initialData) + ); + + const [itemSelector, setItemSelector] = useState(null); + + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ); + + const selectedItem = itemSelector ? getItem(itemSelector, data) : null; + + const Page = useCallback( + (pageProps) => ( + + {config.root?.render + ? config.root?.render({ ...pageProps, editMode: true }) + : pageProps.children} + + ), + [config.root] + ); + + const PageFieldWrapper = useCallback( + (props) => ( + + {props.children} + + ), + [] + ); + + const ComponentFieldWrapper = useCallback( + (props) => ( + + {props.children} + + ), + [] + ); + + const FieldWrapper = itemSelector ? ComponentFieldWrapper : PageFieldWrapper; + + const rootFields = config.root?.fields || defaultPageFields; + + let fields = selectedItem + ? (config.components[selectedItem.type]?.fields as Record< + string, + Field + >) || {} + : rootFields; + + useEffect(() => { + if (onChange) onChange(data); + }, [data]); + + const { onDragStartOrUpdate, placeholderStyle } = usePlaceholderStyle(); + + const [leftSidebarVisible, setLeftSidebarVisible] = useState(true); + + const [dragEvent, setDragEvent] = useState(); + + return ( +
+ { + setDragEvent(event); + }} + // onDragOver={(event) => { + // setDragEvent(event); + // }} + onDragEnd={(event) => { + setDragEvent(undefined); + + if (!event.over?.id) { + return; + } + + console.log("drag end", event); + + // event.activatorEvent.ov + + // New component + // if ( + // droppedItem.source.droppableId === "component-list" && + // droppedItem.destination + // ) { + // dispatch({ + // type: "insert", + // componentType: droppedItem.draggableId, + // destinationIndex: droppedItem.destination!.index, + // destinationZone: droppedItem.destination.droppableId, + // }); + + // setItemSelector({ + // index: droppedItem.destination!.index, + // zone: droppedItem.destination.droppableId, + // }); + + // return; + // } else { + + const { active, over } = event; + const source = (active.data.current as DraggableData) || null; + const destination = (over.data.current as DraggableData) || null; + + if (!source || !destination) { + return; + } + + console.log("source", source); + console.log("destination", destination); + + if (source.zoneCompound === destination.zoneCompound) { + dispatch({ + type: "reorder", + sourceIndex: source.index, + destinationIndex: destination.index, + destinationZone: destination.zoneCompound, + }); + } else { + dispatch({ + type: "move", + sourceZone: source.zoneCompound, + sourceIndex: source.index, + destinationIndex: destination.index, + destinationZone: destination.zoneCompound, + }); + } + + setItemSelector({ + index: destination.index, + zone: destination.zoneCompound, + }); + // } + }} + // onDragUpdate={(update) => { + // setDragEvent({ ...dragEvent, ...update }); + // onDragStartOrUpdate(update); + // }} + // onBeforeDragStart={(start) => { + // onDragStartOrUpdate(start); + // setItemSelector(null); + // }} + // onDragEnd={(droppedItem) => { + // setDragEvent(undefined); + + // // User cancel drag + // if (!droppedItem.destination) { + // return; + // } + + // // New component + // if ( + // droppedItem.source.droppableId === "component-list" && + // droppedItem.destination + // ) { + // dispatch({ + // type: "insert", + // componentType: droppedItem.draggableId, + // destinationIndex: droppedItem.destination!.index, + // destinationZone: droppedItem.destination.droppableId, + // }); + + // setItemSelector({ + // index: droppedItem.destination!.index, + // zone: droppedItem.destination.droppableId, + // }); + + // return; + // } else { + // const { source, destination } = droppedItem; + + // if (source.droppableId === destination.droppableId) { + // dispatch({ + // type: "reorder", + // sourceIndex: source.index, + // destinationIndex: destination.index, + // destinationZone: destination.droppableId, + // }); + // } else { + // dispatch({ + // type: "move", + // sourceZone: source.droppableId, + // sourceIndex: source.index, + // destinationIndex: destination.index, + // destinationZone: destination.droppableId, + // }); + // } + + // setItemSelector({ + // index: destination.index, + // zone: destination.droppableId, + // }); + // } + // }} + > + + + {(ctx) => { + let path = + ctx?.pathData && selectedItem + ? ctx?.pathData[selectedItem?.props.id] + : undefined; + + if (path) { + path = [{ label: "Page", selector: null }, ...path]; + path = path.slice(path.length - 2, path.length - 1); + } + + return ( +
+
+ {renderHeader ? ( + renderHeader({ + children: ( + + ), + data, + dispatch, + }) + ) : ( +
+
+ + setLeftSidebarVisible(!leftSidebarVisible) + } + title="Toggle left sidebar" + > + + +
+
+ + {headerTitle || data.root.title || "Page"} + {headerPath && ( + + {headerPath} + + )} + +
+
+ {renderHeaderActions && + renderHeaderActions({ data, dispatch })} + +
+
+ )} +
+
+ + + + + {/* {ctx?.activeZones && + ctx?.activeZones[rootDroppableId] && ( + + )} + + {Object.entries(findZonesForArea(data, "root")).map( + ([zoneKey, zone]) => { + return ( + + ); + } + )} */} + +
+ +
setItemSelector(null)} + id="puck-frame" + > +
+ + + +
+
+
+ + + setItemSelector(breadcrumb.selector) + } + title={selectedItem ? selectedItem.type : "Page"} + > + {Object.keys(fields).map((fieldName) => { + const field = fields[fieldName]; + + const onChange = (value: any) => { + let currentProps; + let newProps; + + if (selectedItem) { + currentProps = selectedItem.props; + } else { + currentProps = data.root; + } + + if (fieldName === "_data") { + // Reset the link if value is falsey + if (!value) { + const { locked, ..._meta } = + currentProps._meta || {}; + + newProps = { + ...currentProps, + _data: undefined, + _meta: _meta, + }; + } else { + const changedFields = filter( + // filter out anything not supported by this component + value, + Object.keys(fields) + ); + + newProps = { + ...currentProps, + ...changedFields, + _data: value, // TODO perf - this is duplicative and will make payload larger + _meta: { + locked: Object.keys(changedFields), + }, + }; + } + } else { + newProps = { + ...currentProps, + [fieldName]: value, + }; + } + + if (itemSelector) { + dispatch({ + type: "replace", + destinationIndex: itemSelector.index, + destinationZone: + itemSelector.zone || rootDroppableId, + data: { ...selectedItem, props: newProps }, + }); + } else { + dispatch({ + type: "set", + data: { root: newProps }, + }); + } + }; + + if (selectedItem && itemSelector) { + return ( + + -1 + } + value={selectedItem.props[fieldName]} + onChange={onChange} + /> + ); + } else { + return ( + + -1 + } + value={data.root[fieldName]} + onChange={onChange} + /> + ); + } + })} + + +
+
+ ); + }} +
+
+
+
+ ); +} diff --git a/packages/core/components/Puck/index.tsx b/packages/core/components/Puck/index.tsx index b730ecd62..f073a3455 100644 --- a/packages/core/components/Puck/index.tsx +++ b/packages/core/components/Puck/index.tsx @@ -57,6 +57,28 @@ const PluginRenderer = ({ ); }; +function getDroppableSizes() { + // Select all Droppable components on the page using the RBDND attribute + const droppables = document.querySelectorAll( + "[data-rbd-droppable-context-id]" + ); + + // Reduce the NodeList to an object with the droppableId as the key + const sizes = Array.from(droppables).reduce((acc, droppable) => { + const htmlElement = droppable as HTMLElement; + const id = htmlElement.getAttribute("data-rbd-droppable-id"); + if (id) { + acc[id] = { + width: htmlElement.offsetWidth, + height: htmlElement.offsetHeight, + }; + } + return acc; + }, {}); + + return sizes; +} + export function Puck({ config, data: initialData = { content: [], root: { title: "" } }, @@ -159,6 +181,16 @@ export function Puck({ DragStart & Partial >(); + const [droppableSizes, setDroppableSizes] = useState< + Record + >({}); + + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); + + // useEffect(() => { + // return window.onmousemove && window.onmousemove(_, ); + // }, []); + return (
{ onDragStartOrUpdate(start); setItemSelector(null); + + setDroppableSizes(getDroppableSizes()); }} onDragEnd={(droppedItem) => { setDraggedItem(undefined); @@ -234,6 +268,7 @@ export function Puck({ placeholderStyle, mode: "edit", areaId: "root", + droppableSizes, }} > @@ -274,6 +309,7 @@ export function Puck({ borderBottom: "1px solid var(--puck-color-grey-8)", }} > + {JSON.stringify(mousePosition)} {renderHeader ? ( renderHeader({ children: ( @@ -413,7 +449,7 @@ export function Puck({ style={{ border: "1px solid var(--puck-color-grey-8)", boxShadow: "0px 0px 0px 3rem var(--puck-color-grey-10)", - zoom: 0.75, + // zoom: 0.75, }} > diff --git a/packages/core/lib/__tests__/reducer.test.ts b/packages/core/lib/__tests__/reducer.test.ts new file mode 100644 index 000000000..17f115384 --- /dev/null +++ b/packages/core/lib/__tests__/reducer.test.ts @@ -0,0 +1,45 @@ +// import { createReducer, PuckAction } from "../reducer"; + +// describe("createReducer", () => { +// const mockConfig = { +// // Fill in with your mock config +// }; + +// const reducer = createReducer({ config: mockConfig }); + +// test("should handle insert action", () => { +// const action: PuckAction = { +// // fill this with your INSERT action mock +// }; + +// const state = { +// // fill this with your initial state mock +// }; + +// const updatedState = reducer(state, action); +// expect(updatedState).toEqual(/* expected state after inserting item */); +// }); + +// // Repeat this structure for all other action types +// test("should handle replace action", () => { +// /*...*/ +// }); +// test("should handle reorder action", () => { +// /*...*/ +// }); +// test("should handle move action", () => { +// /*...*/ +// }); +// test("should handle remove action", () => { +// /*...*/ +// }); +// test("should handle set action", () => { +// /*...*/ +// }); +// test("should handle registerZone action", () => { +// /*...*/ +// }); +// test("should handle unregisterZone action", () => { +// /*...*/ +// }); +// }); diff --git a/packages/core/lib/calculate-y.ts b/packages/core/lib/calculate-y.ts new file mode 100644 index 000000000..034f8dde1 --- /dev/null +++ b/packages/core/lib/calculate-y.ts @@ -0,0 +1,63 @@ +import { DragStart, DragUpdate } from "react-beautiful-dnd"; + +export const calculateY = (draggedItem: DragStart & Partial) => { + const queryAttr = "data-rbd-drag-handle-draggable-id"; + + const draggableId = draggedItem.draggableId; + const destinationIndex = (draggedItem.destination || draggedItem.source) + .index; + const droppableId = (draggedItem.destination || draggedItem.source) + .droppableId; + + const domQuery = `[${queryAttr}='${draggableId}']`; + const draggedDOM = document.querySelector(domQuery); + + if (!draggedDOM) { + return; + } + + const targetListElement = document.querySelector( + `[data-rbd-droppable-id='${droppableId}']` + ); + + const { clientHeight } = draggedDOM; + + if (!targetListElement) { + return; + } + + let clientY = 0; + + const isSameDroppable = + draggedItem.source.droppableId === draggedItem.destination?.droppableId; + + if (destinationIndex > 0) { + const end = + destinationIndex > draggedItem.source.index && isSameDroppable + ? destinationIndex + 1 + : destinationIndex; + + const children = Array.from(targetListElement.children) + .filter( + (item) => + item !== draggedDOM && + item.getAttributeNames().indexOf("data-puck-placeholder") === -1 && + item + .getAttributeNames() + .indexOf("data-rbd-placeholder-context-id") === -1 + ) + .slice(0, end); + + clientY = children.reduce( + (total, item) => + total + + item.clientHeight + + parseInt(window.getComputedStyle(item).marginTop.replace("px", "")) + + parseInt(window.getComputedStyle(item).marginBottom.replace("px", "")), + + 0 + ); + } + + return { y: clientY, height: clientHeight }; +}; diff --git a/packages/core/lib/use-placeholder-style.ts b/packages/core/lib/use-placeholder-style.ts index 5efb89f29..cc897f16c 100644 --- a/packages/core/lib/use-placeholder-style.ts +++ b/packages/core/lib/use-placeholder-style.ts @@ -1,77 +1,22 @@ import { CSSProperties, useState } from "react"; import { DragStart, DragUpdate } from "react-beautiful-dnd"; +import { calculateY } from "./calculate-y"; export const usePlaceholderStyle = () => { - const queryAttr = "data-rbd-drag-handle-draggable-id"; - const [placeholderStyle, setPlaceholderStyle] = useState(); const onDragStartOrUpdate = ( draggedItem: DragStart & Partial ) => { - const draggableId = draggedItem.draggableId; - const destinationIndex = (draggedItem.destination || draggedItem.source) - .index; - const droppableId = (draggedItem.destination || draggedItem.source) - .droppableId; - - const domQuery = `[${queryAttr}='${draggableId}']`; - const draggedDOM = document.querySelector(domQuery); - - if (!draggedDOM) { - return; - } - - const targetListElement = document.querySelector( - `[data-rbd-droppable-id='${droppableId}']` - ); - - const { clientHeight } = draggedDOM; - - if (!targetListElement) { - return; - } - - let clientY = 0; - - const isSameDroppable = - draggedItem.source.droppableId === draggedItem.destination?.droppableId; - - if (destinationIndex > 0) { - const end = - destinationIndex > draggedItem.source.index && isSameDroppable - ? destinationIndex + 1 - : destinationIndex; - - const children = Array.from(targetListElement.children) - .filter( - (item) => - item !== draggedDOM && - item.getAttributeNames().indexOf("data-puck-placeholder") === -1 && - item - .getAttributeNames() - .indexOf("data-rbd-placeholder-context-id") === -1 - ) - .slice(0, end); - - clientY = children.reduce( - (total, item) => - total + - item.clientHeight + - parseInt(window.getComputedStyle(item).marginTop.replace("px", "")) + - parseInt( - window.getComputedStyle(item).marginBottom.replace("px", "") - ), + const dimensions = calculateY(draggedItem); - 0 - ); - } + if (!dimensions) return; setPlaceholderStyle({ position: "absolute", - top: clientY, + top: dimensions.y, left: 0, - height: clientHeight, + height: dimensions.height, width: "100%", }); };