import { createRef, useEffect, useReducer, useRef } from "react"; import { uniqueId } from "lodash"; import * as Drag from "../../types/Drag"; function getPosition(draggable: Drag.Draggable) { if (draggable && "ref" in draggable && draggable.ref.current) { return draggable.ref.current.getBoundingClientRect(); } else { return null; } } function hideDraggable( draggable: Drag.Draggable | null, callback: () => Drag.Draggable[] ) { if (draggable && "ref" in draggable && draggable.ref.current) { const prevDisplay = draggable.ref.current.style.display; draggable.ref.current.style.display = "none"; const result = callback(); draggable.ref.current.style.display = prevDisplay; return result; } else { return callback(); } } function insertFiles( state: Drag.Item[], files: Drag.Item[] ): Drag.Item[] { const index = state.indexOf("Files"); if (index === -1 || !files) { return state; } else { return [...state.slice(0, index), ...files, ...state.slice(index + 1)]; } } function dragCollectionReducer( state: Drag.Item[], action: Drag.CollectionAction ): Drag.Item[] { switch (action.type) { case "append": return [...state, ...action.payload]; case "prepend": return [...action.payload, ...state]; case "insertFiles": return insertFiles(state, action.payload); case "update": return state.map((d: Drag.Draggable) => { return d.handle === (action.payload as Drag.Draggable).handle ? action.payload : d; }); case "updatePositions": return hideDraggable(action.payload, () => { return state.map((d: Drag.Draggable) => { return { ...d, rect: getPosition(d) }; }); }); case "remove": return state.filter( (d: Drag.Draggable) => d.handle !== action.payload.handle ); case "replace": return action.payload; case "reorder": return action.payload; default: return state; } } export function createDraggable( record: T ): Drag.Draggable { return { record: record, rect: null, ref: createRef(), handle: uniqueId("draggable") }; } export default function useDragCollection( records: Array ): Drag.Collection { const containerRef = useRef(null); const [draggables, dispatch] = useReducer(dragCollectionReducer, [], () => records.map((r) => createDraggable(r)) ); useEffect(() => { dispatch({ type: "updatePositions" }); }, []); return { ref: containerRef, draggables: draggables, dispatch: dispatch }; }