@react-querybuilder/dnd
Version:
Drag-and-drop-enabled version of react-querybuilder (DnD-library-agnostic)
553 lines (552 loc) • 19.7 kB
JavaScript
import { a as getDragItem, c as isHotkeyPressed, i as canDropOnRuleGroup, n as canDropOnInlineCombinator, o as handleDrop, r as canDropOnRule, s as _objectSpread2, t as buildDropResult } from "./dndLogic-CX9kFh1l.js";
import { n as computeShadowQuery, r as DragPreviewContext } from "./shadowQuery-B6n89EFI.js";
import * as React from "react";
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "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 = 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] = useState(null);
const activeDragItemRef = useRef(null);
const dragSchemaRef = useRef(null);
const [timerCopyMode, setTimerCopyMode] = useState(false);
const [timerGroupMode, setTimerGroupMode] = useState(false);
const timerCopyModeRef = useRef(false);
const timerGroupModeRef = useRef(false);
const copyTimerIdRef = useRef(null);
const groupTimerIdRef = useRef(null);
const lastHoverTargetIdRef = useRef(null);
const clearHoverTimers = 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 = 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] = useState(null);
const dragPreviewStateRef = useRef(null);
const onDragMoveRef = useRef(void 0);
const lastTargetRef = useRef(null);
const updatePreviewPosition = 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 || isHotkeyPressed(currentPreview.dropEffect === "copy" ? "alt" : "") ? "copy" : "move";
const groupItems = timerGroupModeRef.current || isHotkeyPressed("ctrl");
const result = computeShadowQuery({
originalQuery: currentPreview.originalQuery,
draggedItem: activeDragItemRef.current,
draggedPath: currentPreview.draggedPath,
targetPath,
targetType,
quadrant,
dropEffect,
groupItems
});
if (result) {
var _onDragMoveRef$curren;
const newState = _objectSpread2(_objectSpread2({}, currentPreview), {}, {
shadowQuery: result.shadowQuery,
previewPath: result.previewPath,
dropEffect,
groupItems
});
dragPreviewStateRef.current = newState;
setDragPreviewState(newState);
(_onDragMoveRef$curren = onDragMoveRef.current) === null || _onDragMoveRef$curren === void 0 || _onDragMoveRef$curren.call(onDragMoveRef, {
draggedItem: activeDragItemRef.current,
shadowQuery: result.shadowQuery,
originalQuery: currentPreview.originalQuery,
previewPath: result.previewPath
});
}
}, [updateWhileDragging]);
const commitDrag = 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 = useCallback(() => {
dragSchemaRef.current = null;
dragPreviewStateRef.current = null;
lastTargetRef.current = null;
setDragPreviewState(null);
}, []);
useEffect(() => {
const cleanup = monitorForElements({
onDragStart({ source }) {
const data = source.data;
if (data.__rqbPath && data.__rqbSchema) {
const item = 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 === null || validate === void 0 ? void 0 : validate(dragItem)) {
const getDropResultFn = targetData.__rqbGetDropResult;
handleDrop({
item: dragItem,
dropResult: getDropResultFn === null || getDropResultFn === void 0 ? void 0 : getDropResultFn(),
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 = useMemo(() => ({
activeDragItem,
timerCopyMode,
timerGroupMode
}), [
activeDragItem,
timerCopyMode,
timerGroupMode
]);
const dragPreviewContextValue = useMemo(() => ({
dragPreviewState,
updatePreviewPosition,
commitDrag,
cancelDrag
}), [
dragPreviewState,
updatePreviewPosition,
commitDrag,
cancelDrag
]);
return /* @__PURE__ */ React.createElement(DragStateContext.Provider, { value: dragStateValue }, /* @__PURE__ */ React.createElement(DragPreviewContext.Provider, { value: dragPreviewContextValue }, children));
};
const useRuleDnD = (params) => {
const { activeDragItem, timerCopyMode, timerGroupMode } = useContext(DragStateContext);
const containerNodeRef = useRef(null);
const handleNodeRef = useRef(null);
const [isDragging, setIsDragging] = useState(false);
const [isOver, setIsOver] = useState(false);
const dragId = getDragId("rule", params.path, params.schema.qbId);
const dropId = getDropId("rule", params.path, params.schema.qbId);
const paramsRef = useRef(params);
paramsRef.current = params;
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 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 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 && 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: useCallback((node) => {
containerNodeRef.current = node;
}, []),
dragRef: useCallback((node) => {
handleNodeRef.current = node;
}, []),
dropEffect: timerCopyMode || isHotkeyPressed(params.copyModeModifierKey) ? "copy" : "move",
groupItems: timerGroupMode || isHotkeyPressed(params.groupModeModifierKey),
dropNotAllowed
};
};
const useRuleGroupDnD = (params) => {
const { activeDragItem, timerCopyMode, timerGroupMode } = useContext(DragStateContext);
const previewNodeRef = useRef(null);
const handleNodeRef = useRef(null);
const dropNodeRef = useRef(null);
const [isDragging, setIsDragging] = useState(false);
const [isOver, setIsOver] = 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 = useRef(params);
paramsRef.current = params;
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
]);
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 canDropOnRuleGroup({
dragging,
path: cp.path,
schema: cp.schema,
canDrop: cp.canDrop,
disabled: cp.disabled,
ruleGroup: cp.ruleGroup
});
},
__rqbGetDropResult: () => {
const cp = paramsRef.current;
return 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 && 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 = useCallback((node) => {
previewNodeRef.current = node;
}, []);
const dropRef = useCallback((node) => {
dropNodeRef.current = node;
}, []);
return {
isDragging,
dragMonitorId: dragId,
isOver: validatedIsOver,
dropMonitorId: dropId,
previewRef,
dragRef: useCallback((node) => {
handleNodeRef.current = node;
}, []),
dropRef,
dropEffect: timerCopyMode || isHotkeyPressed(params.copyModeModifierKey) ? "copy" : "move",
groupItems: timerGroupMode || isHotkeyPressed(params.groupModeModifierKey),
dropNotAllowed
};
};
const useInlineCombinatorDnD = (params) => {
var _params$rules;
const { activeDragItem, timerCopyMode } = useContext(DragStateContext);
const { dragPreviewState } = useContext(DragPreviewContext);
const dropNodeRef = useRef(null);
const [isOver, setIsOver] = useState(false);
const dropId = getDropId("inlineCombinator", params.path, params.schema.qbId);
const isUpdateWhileDragging = dragPreviewState !== null;
const hoveringItem = ((_params$rules = params.rules) !== null && _params$rules !== void 0 ? _params$rules :
/* v8 ignore start -- @preserve */ [])[params.path.at(-1) - 1];
const paramsRef = useRef(params);
paramsRef.current = params;
useEffect(() => {
const dropEl = dropNodeRef.current;
if (!dropEl || isUpdateWhileDragging) return void 0;
return dropTargetForElements({
element: dropEl,
getData: () => ({
__rqbType: "inlineCombinator",
__rqbValidate: (dragging) => {
var _cp$rules;
const cp = paramsRef.current;
const hItem = ((_cp$rules = cp.rules) !== null && _cp$rules !== void 0 ? _cp$rules : [])[cp.path.at(-1) - 1];
return canDropOnInlineCombinator({
dragging,
path: cp.path,
schema: cp.schema,
canDrop: cp.canDrop,
groupModeModifierKey: cp.groupModeModifierKey,
hoveringItem: hItem
});
},
__rqbGetDropResult: () => {
const cp = paramsRef.current;
return 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 && 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: useCallback((node) => {
dropNodeRef.current = node;
}, []),
dropMonitorId: dropId,
isOver: validatedIsOver,
dropEffect: timerCopyMode || isHotkeyPressed(params.copyModeModifierKey) ? "copy" : "move",
dropNotAllowed
};
};
return {
DndProvider,
useRuleDnD,
useRuleGroupDnD,
useInlineCombinatorDnD
};
};
//#endregion
export { getQuadrant as n, createPragmaticDndAdapter as t };
//# sourceMappingURL=pragmatic-dnd-D2lWImDV.js.map