UNPKG

@react-querybuilder/dnd

Version:

Drag-and-drop-enabled version of react-querybuilder (DnD-library-agnostic)

562 lines (561 loc) 20.1 kB
const require_dndLogic = require("./dndLogic-DRubwv4d.js"); const require_shadowQuery = require("./shadowQuery-DXKizE6P.js"); let react = require("react"); react = require_dndLogic.__toESM(react); //#region src/quadrantDetection.ts /** * Determines whether the cursor is in the upper quadrant (top 25%), * lower quadrant (bottom 25%), or middle zone (center 50%) of an element. * * Returns `null` when the cursor is in the middle zone, indicating * no positional change should occur. */ const getQuadrant = (element, clientY) => { const rect = element.getBoundingClientRect(); const quarterHeight = rect.height / 4; if (clientY < rect.top + quarterHeight) return "upper"; if (clientY > rect.bottom - quarterHeight) return "lower"; return null; }; //#endregion //#region src/adapters/pragmatic-dnd.tsx const DragStateContext = (0, react.createContext)({ activeDragItem: null, timerCopyMode: false, timerGroupMode: false }); const getDragId = (type, path, qbId) => `drag-${type}-${qbId}-${path.join("_")}`; const getDropId = (type, path, qbId) => `drop-${type}-${qbId}-${path.join("_")}`; /** * Creates a {@link DndAdapter} backed by `@atlaskit/pragmatic-drag-and-drop`. * * @example * ```tsx * import { QueryBuilderDnD } from '@react-querybuilder/dnd'; * import { createPragmaticDndAdapter } from '@react-querybuilder/dnd/pragmatic-dnd'; * import { draggable, dropTargetForElements, monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'; * import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine'; * * const adapter = createPragmaticDndAdapter({ draggable, dropTargetForElements, monitorForElements, combine }); * * <QueryBuilderDnD dnd={adapter}> * <QueryBuilder /> * </QueryBuilderDnD> * ``` * * @group DnD */ const createPragmaticDndAdapter = (pdndExports) => { const { draggable, dropTargetForElements, monitorForElements, combine } = pdndExports; const DndProvider = ({ children, updateWhileDragging, copyModeAfterHoverMs, groupModeAfterHoverMs }) => { const [activeDragItem, setActiveDragItem] = (0, react.useState)(null); const activeDragItemRef = (0, react.useRef)(null); const dragSchemaRef = (0, react.useRef)(null); const [timerCopyMode, setTimerCopyMode] = (0, react.useState)(false); const [timerGroupMode, setTimerGroupMode] = (0, react.useState)(false); const timerCopyModeRef = (0, react.useRef)(false); const timerGroupModeRef = (0, react.useRef)(false); const copyTimerIdRef = (0, react.useRef)(null); const groupTimerIdRef = (0, react.useRef)(null); const lastHoverTargetIdRef = (0, react.useRef)(null); const clearHoverTimers = (0, react.useCallback)(() => { if (copyTimerIdRef.current !== null) { clearTimeout(copyTimerIdRef.current); copyTimerIdRef.current = null; } if (groupTimerIdRef.current !== null) { clearTimeout(groupTimerIdRef.current); groupTimerIdRef.current = null; } timerCopyModeRef.current = false; timerGroupModeRef.current = false; setTimerCopyMode(false); setTimerGroupMode(false); lastHoverTargetIdRef.current = null; }, []); const startHoverTimers = (0, react.useCallback)((targetId) => { if (lastHoverTargetIdRef.current === targetId) return; clearHoverTimers(); lastHoverTargetIdRef.current = targetId; if (copyModeAfterHoverMs && copyModeAfterHoverMs > 0) copyTimerIdRef.current = setTimeout(() => { timerCopyModeRef.current = true; setTimerCopyMode(true); copyTimerIdRef.current = null; }, copyModeAfterHoverMs); if (groupModeAfterHoverMs && groupModeAfterHoverMs > 0) groupTimerIdRef.current = setTimeout(() => { timerGroupModeRef.current = true; setTimerGroupMode(true); groupTimerIdRef.current = null; }, groupModeAfterHoverMs); }, [ clearHoverTimers, copyModeAfterHoverMs, groupModeAfterHoverMs ]); const [dragPreviewState, setDragPreviewState] = (0, react.useState)(null); const dragPreviewStateRef = (0, react.useRef)(null); const onDragMoveRef = (0, react.useRef)(void 0); const lastTargetRef = (0, react.useRef)(null); const updatePreviewPosition = (0, react.useCallback)((targetPath, targetType, quadrant) => { const currentPreview = dragPreviewStateRef.current; // v8 ignore next if (!currentPreview || !updateWhileDragging) return; const last = lastTargetRef.current; if (last && last.quadrant === quadrant && last.targetType === targetType && last.targetPath.length === targetPath.length && last.targetPath.every((v, i) => v === targetPath[i])) return; lastTargetRef.current = { targetPath, targetType, quadrant }; // v8 ignore next -- hotkey branch already tested in hotkey-specific tests const dropEffect = timerCopyModeRef.current || require_dndLogic.isHotkeyPressed(currentPreview.dropEffect === "copy" ? "alt" : "") ? "copy" : "move"; const groupItems = timerGroupModeRef.current || require_dndLogic.isHotkeyPressed("ctrl"); const result = require_shadowQuery.computeShadowQuery({ originalQuery: currentPreview.originalQuery, draggedItem: activeDragItemRef.current, draggedPath: currentPreview.draggedPath, targetPath, targetType, quadrant, dropEffect, groupItems }); if (result) { const newState = { ...currentPreview, shadowQuery: result.shadowQuery, previewPath: result.previewPath, dropEffect, groupItems }; dragPreviewStateRef.current = newState; setDragPreviewState(newState); onDragMoveRef.current?.({ draggedItem: activeDragItemRef.current, shadowQuery: result.shadowQuery, originalQuery: currentPreview.originalQuery, previewPath: result.previewPath }); } }, [updateWhileDragging]); const commitDrag = (0, react.useCallback)(() => { const preview = dragPreviewStateRef.current; // v8 ignore next if (!preview) return; const schema = dragSchemaRef.current; if (schema && preview.shadowQuery !== preview.originalQuery) schema.dispatchQuery(preview.shadowQuery); dragSchemaRef.current = null; dragPreviewStateRef.current = null; lastTargetRef.current = null; setDragPreviewState(null); }, []); const cancelDrag = (0, react.useCallback)(() => { dragSchemaRef.current = null; dragPreviewStateRef.current = null; lastTargetRef.current = null; setDragPreviewState(null); }, []); (0, react.useEffect)(() => { const cleanup = monitorForElements({ onDragStart({ source }) { const data = source.data; if (data.__rqbPath && data.__rqbSchema) { const item = require_dndLogic.getDragItem(data.__rqbPath, data.__rqbSchema); activeDragItemRef.current = item; setActiveDragItem(item); if (updateWhileDragging) { const schema = data.__rqbSchema; dragSchemaRef.current = schema; const originalQuery = schema.getQuery(); const initialState = { shadowQuery: originalQuery, originalQuery, draggedPath: data.__rqbPath, previewPath: data.__rqbPath, dropEffect: "move", groupItems: false, qbId: schema.qbId }; dragPreviewStateRef.current = initialState; setDragPreviewState(initialState); } } }, onDrag({ location }) { const dropTargets = location.current.dropTargets; if (dropTargets.length > 0) { const target = dropTargets[0]; const targetType = target.data.__rqbType; const targetPath = target.data.__rqbPath; if (targetType && targetPath) startHoverTimers(`${targetType}-${targetPath.join("_")}`); } else clearHoverTimers(); // v8 ignore next if (!updateWhileDragging || !dragPreviewStateRef.current) return; if (dropTargets.length === 0) return; const target = dropTargets[0]; const targetType = target.data.__rqbType; const targetPath = target.data.__rqbPath; if (!targetType || !targetPath) return; const quadrant = targetType === "ruleGroup" ? "upper" : getQuadrant(target.element, location.current.input.clientY); if (!quadrant) return; const dragItem = activeDragItemRef.current; // v8 ignore next if (!dragItem) return; const validate = target.data.__rqbValidate; if (validate && !validate(dragItem)) return; updatePreviewPosition(targetPath, targetType, quadrant); }, onDrop({ source, location }) { const dragItem = activeDragItemRef.current; const sourceData = source.data; const dropTargets = location.current.dropTargets; const copyOverride = timerCopyModeRef.current; const groupOverride = timerGroupModeRef.current; clearHoverTimers(); if (updateWhileDragging && dragPreviewStateRef.current) if (dropTargets.length > 0) commitDrag(); else cancelDrag(); else if (dragItem && dropTargets.length > 0) { const targetData = dropTargets[0].data; const validate = targetData.__rqbValidate; if (validate?.(dragItem)) { const getDropResultFn = targetData.__rqbGetDropResult; const dropResult = getDropResultFn?.(); require_dndLogic.handleDrop({ item: dragItem, dropResult, schema: sourceData.__rqbSchema, actions: sourceData.__rqbActions, copyModeModifierKey: sourceData.__rqbCopyModeModifierKey, groupModeModifierKey: sourceData.__rqbGroupModeModifierKey, copyModeOverride: copyOverride, groupModeOverride: groupOverride, onRuleDrop: sourceData.__rqbOnRuleDrop }); } } activeDragItemRef.current = null; setActiveDragItem(null); } }); return () => { cleanup(); clearHoverTimers(); }; }, [ updateWhileDragging, commitDrag, cancelDrag, updatePreviewPosition, startHoverTimers, clearHoverTimers ]); const dragStateValue = (0, react.useMemo)(() => ({ activeDragItem, timerCopyMode, timerGroupMode }), [ activeDragItem, timerCopyMode, timerGroupMode ]); const dragPreviewContextValue = (0, react.useMemo)(() => ({ dragPreviewState, updatePreviewPosition, commitDrag, cancelDrag }), [ dragPreviewState, updatePreviewPosition, commitDrag, cancelDrag ]); return /* @__PURE__ */ react.createElement(DragStateContext.Provider, { value: dragStateValue }, /* @__PURE__ */ react.createElement(require_shadowQuery.DragPreviewContext.Provider, { value: dragPreviewContextValue }, children)); }; const useRuleDnD = (params) => { const { activeDragItem, timerCopyMode, timerGroupMode } = (0, react.useContext)(DragStateContext); const containerNodeRef = (0, react.useRef)(null); const handleNodeRef = (0, react.useRef)(null); const [isDragging, setIsDragging] = (0, react.useState)(false); const [isOver, setIsOver] = (0, react.useState)(false); const dragId = getDragId("rule", params.path, params.schema.qbId); const dropId = getDropId("rule", params.path, params.schema.qbId); const paramsRef = (0, react.useRef)(params); paramsRef.current = params; (0, react.useEffect)(() => { const container = containerNodeRef.current; const handle = handleNodeRef.current; if (!container || !handle) return void 0; return combine(draggable({ element: container, dragHandle: handle, canDrag: () => !paramsRef.current.disabled, getInitialData: () => ({ __rqbPath: paramsRef.current.path, __rqbSchema: paramsRef.current.schema, __rqbActions: paramsRef.current.actions, __rqbCopyModeModifierKey: paramsRef.current.copyModeModifierKey, __rqbGroupModeModifierKey: paramsRef.current.groupModeModifierKey, __rqbOnRuleDrop: paramsRef.current.onRuleDrop }), onDragStart: () => setIsDragging(true), onDrop: () => setIsDragging(false) }), dropTargetForElements({ element: container, getData: () => ({ __rqbType: "rule", __rqbPath: paramsRef.current.path, __rqbValidate: (dragging) => { const cp = paramsRef.current; return require_dndLogic.canDropOnRule({ dragging, path: cp.path, schema: cp.schema, canDrop: cp.canDrop, groupModeModifierKey: cp.groupModeModifierKey, disabled: cp.disabled, rule: cp.rule }); }, __rqbGetDropResult: () => { const cp = paramsRef.current; return require_dndLogic.buildDropResult({ type: "rule", path: cp.path, schema: cp.schema, copyModeModifierKey: cp.copyModeModifierKey, groupModeModifierKey: cp.groupModeModifierKey }); } }), onDragEnter: () => setIsOver(true), onDragLeave: () => setIsOver(false), onDrop: () => setIsOver(false) })); }, [ params.path, params.schema.qbId, params.disabled ]); const canDropHere = isOver && !!activeDragItem && require_dndLogic.canDropOnRule({ dragging: activeDragItem, path: params.path, schema: params.schema, canDrop: params.canDrop, groupModeModifierKey: params.groupModeModifierKey, disabled: params.disabled, rule: params.rule }); const validatedIsOver = isOver && canDropHere; const dropNotAllowed = isOver && !canDropHere; return { isDragging, dragMonitorId: dragId, isOver: validatedIsOver, dropMonitorId: dropId, dndRef: (0, react.useCallback)((node) => { containerNodeRef.current = node; }, []), dragRef: (0, react.useCallback)((node) => { handleNodeRef.current = node; }, []), dropEffect: timerCopyMode || require_dndLogic.isHotkeyPressed(params.copyModeModifierKey) ? "copy" : "move", groupItems: timerGroupMode || require_dndLogic.isHotkeyPressed(params.groupModeModifierKey), dropNotAllowed }; }; const useRuleGroupDnD = (params) => { const { activeDragItem, timerCopyMode, timerGroupMode } = (0, react.useContext)(DragStateContext); const previewNodeRef = (0, react.useRef)(null); const handleNodeRef = (0, react.useRef)(null); const dropNodeRef = (0, react.useRef)(null); const [isDragging, setIsDragging] = (0, react.useState)(false); const [isOver, setIsOver] = (0, react.useState)(false); const isDragDisabled = params.disabled || params.path.length === 0; const dragId = getDragId("ruleGroup", params.path, params.schema.qbId); const dropId = getDropId("ruleGroup", params.path, params.schema.qbId); const paramsRef = (0, react.useRef)(params); paramsRef.current = params; (0, react.useEffect)(() => { const previewEl = previewNodeRef.current; const handleEl = handleNodeRef.current; if (!previewEl || !handleEl || isDragDisabled) return void 0; return draggable({ element: previewEl, dragHandle: handleEl, canDrag: () => !paramsRef.current.disabled && paramsRef.current.path.length > 0, getInitialData: () => ({ __rqbPath: paramsRef.current.path, __rqbSchema: paramsRef.current.schema, __rqbActions: paramsRef.current.actions, __rqbCopyModeModifierKey: paramsRef.current.copyModeModifierKey, __rqbGroupModeModifierKey: paramsRef.current.groupModeModifierKey, __rqbOnRuleDrop: paramsRef.current.onRuleDrop }), onDragStart: () => setIsDragging(true), onDrop: () => setIsDragging(false) }); }, [ isDragDisabled, params.path, params.schema.qbId ]); (0, react.useEffect)(() => { const dropEl = dropNodeRef.current; if (!dropEl) return void 0; return dropTargetForElements({ element: dropEl, getData: () => ({ __rqbType: "ruleGroup", __rqbPath: paramsRef.current.path, __rqbValidate: (dragging) => { const cp = paramsRef.current; return require_dndLogic.canDropOnRuleGroup({ dragging, path: cp.path, schema: cp.schema, canDrop: cp.canDrop, disabled: cp.disabled, ruleGroup: cp.ruleGroup }); }, __rqbGetDropResult: () => { const cp = paramsRef.current; return require_dndLogic.buildDropResult({ type: "ruleGroup", path: cp.path, schema: cp.schema, copyModeModifierKey: cp.copyModeModifierKey, groupModeModifierKey: cp.groupModeModifierKey }); } }), onDragEnter: () => setIsOver(true), onDragLeave: () => setIsOver(false), onDrop: () => setIsOver(false) }); }, [ params.path, params.schema.qbId, params.disabled ]); const canDropHere = isOver && !!activeDragItem && require_dndLogic.canDropOnRuleGroup({ dragging: activeDragItem, path: params.path, schema: params.schema, canDrop: params.canDrop, disabled: params.disabled, ruleGroup: params.ruleGroup }); const validatedIsOver = isOver && canDropHere; const dropNotAllowed = isOver && !canDropHere; const previewRef = (0, react.useCallback)((node) => { previewNodeRef.current = node; }, []); const dropRef = (0, react.useCallback)((node) => { dropNodeRef.current = node; }, []); return { isDragging, dragMonitorId: dragId, isOver: validatedIsOver, dropMonitorId: dropId, previewRef, dragRef: (0, react.useCallback)((node) => { handleNodeRef.current = node; }, []), dropRef, dropEffect: timerCopyMode || require_dndLogic.isHotkeyPressed(params.copyModeModifierKey) ? "copy" : "move", groupItems: timerGroupMode || require_dndLogic.isHotkeyPressed(params.groupModeModifierKey), dropNotAllowed }; }; const useInlineCombinatorDnD = (params) => { const { activeDragItem, timerCopyMode } = (0, react.useContext)(DragStateContext); const { dragPreviewState } = (0, react.useContext)(require_shadowQuery.DragPreviewContext); const dropNodeRef = (0, react.useRef)(null); const [isOver, setIsOver] = (0, react.useState)(false); const dropId = getDropId("inlineCombinator", params.path, params.schema.qbId); const isUpdateWhileDragging = dragPreviewState !== null; const hoveringItem = (params.rules ?? [])[params.path.at(-1) - 1]; const paramsRef = (0, react.useRef)(params); paramsRef.current = params; (0, react.useEffect)(() => { const dropEl = dropNodeRef.current; if (!dropEl || isUpdateWhileDragging) return void 0; return dropTargetForElements({ element: dropEl, getData: () => ({ __rqbType: "inlineCombinator", __rqbValidate: (dragging) => { const cp = paramsRef.current; const hItem = (cp.rules ?? [])[cp.path.at(-1) - 1]; return require_dndLogic.canDropOnInlineCombinator({ dragging, path: cp.path, schema: cp.schema, canDrop: cp.canDrop, groupModeModifierKey: cp.groupModeModifierKey, hoveringItem: hItem }); }, __rqbGetDropResult: () => { const cp = paramsRef.current; return require_dndLogic.buildDropResult({ type: "inlineCombinator", path: cp.path, schema: cp.schema, copyModeModifierKey: cp.copyModeModifierKey, groupModeModifierKey: cp.groupModeModifierKey }); } }), onDragEnter: () => setIsOver(true), onDragLeave: () => setIsOver(false), onDrop: () => setIsOver(false) }); }, [ params.path, params.schema.qbId, isUpdateWhileDragging ]); const canDropHere = !isUpdateWhileDragging && isOver && !!activeDragItem && require_dndLogic.canDropOnInlineCombinator({ dragging: activeDragItem, path: params.path, schema: params.schema, canDrop: params.canDrop, groupModeModifierKey: params.groupModeModifierKey, hoveringItem }); const validatedIsOver = isOver && canDropHere; const dropNotAllowed = !isUpdateWhileDragging && isOver && !canDropHere; return { dropRef: (0, react.useCallback)((node) => { dropNodeRef.current = node; }, []), dropMonitorId: dropId, isOver: validatedIsOver, dropEffect: timerCopyMode || require_dndLogic.isHotkeyPressed(params.copyModeModifierKey) ? "copy" : "move", dropNotAllowed }; }; return { DndProvider, useRuleDnD, useRuleGroupDnD, useInlineCombinatorDnD }; }; //#endregion Object.defineProperty(exports, "createPragmaticDndAdapter", { enumerable: true, get: function() { return createPragmaticDndAdapter; } }); Object.defineProperty(exports, "getQuadrant", { enumerable: true, get: function() { return getQuadrant; } }); //# sourceMappingURL=pragmatic-dnd--F_UJp4V.js.map