UNPKG

@react-querybuilder/dnd

Version:

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

213 lines (212 loc) 7.91 kB
import { add, findPath, getParentPath, group, insert, isAncestor, lc, pathsAreEqual } from "react-querybuilder"; //#region src/isHotkeyPressed.ts /** * Adapted from * https://github.com/JohannesKlauss/react-hotkeys-hook/blob/bc55a281f1d212d09de786aeb5cd236c58d9531d/src/isHotkeyPressed.ts * and * https://github.com/JohannesKlauss/react-hotkeys-hook/blob/bc55a281f1d212d09de786aeb5cd236c58d9531d/src/parseHotkey.ts */ const reservedModifierKeywords = new Set([ "shift", "alt", "meta", "mod", "ctrl" ]); const mappedKeys = { esc: "escape", return: "enter", ".": "period", ",": "comma", "-": "slash", " ": "space", "`": "backquote", "#": "backslash", "+": "bracketright", ShiftLeft: "shift", ShiftRight: "shift", AltLeft: "alt", AltRight: "alt", MetaLeft: "meta", MetaRight: "meta", OSLeft: "meta", OSRight: "meta", ControlLeft: "ctrl", ControlRight: "ctrl" }; const mapKey = (key) => lc((key && mappedKeys[key] || key || "").trim()).replace(/key|digit|numpad|arrow/, ""); const isHotkeyModifier = (key) => reservedModifierKeywords.has(key); const keyAliases = { "⌘": "meta", cmd: "meta", command: "meta", "⊞": "meta", win: "meta", windows: "meta", "⇧": "shift", "⌥": "alt", "⌃": "ctrl", control: "ctrl" }; (() => { if (typeof document !== "undefined") { document.addEventListener("keydown", (e) => { if (e.key === void 0) return; pushToCurrentlyPressedKeys([mapKey(e.key), mapKey(e.code)]); }); document.addEventListener("keyup", (e) => { if (e.key === void 0) return; removeFromCurrentlyPressedKeys([mapKey(e.key), mapKey(e.code)]); }); } if (typeof window !== "undefined") window.addEventListener("blur", () => { currentlyPressedKeys.clear(); }); })(); const currentlyPressedKeys = /* @__PURE__ */ new Set(); const isReadonlyArray = (value) => Array.isArray(value); const isHotkeyPressed = (key, splitKey = ",") => (isReadonlyArray(key) ? key : key.split(splitKey)).every((hotkey) => { const hk = lc(hotkey.trim()); return currentlyPressedKeys.has(keyAliases[hk] ?? hk); }); const pushToCurrentlyPressedKeys = (key) => { const hotkeyArray = Array.isArray(key) ? key : [key]; if (currentlyPressedKeys.has("meta")) { for (const k of currentlyPressedKeys) if (!isHotkeyModifier(k)) currentlyPressedKeys.delete(lc(k)); } for (const hotkey of hotkeyArray) currentlyPressedKeys.add(lc(hotkey)); }; const removeFromCurrentlyPressedKeys = (key) => { const hotkeyArray = Array.isArray(key) ? key : [key]; if (key === "meta") currentlyPressedKeys.clear(); else for (const hotkey of hotkeyArray) currentlyPressedKeys.delete(lc(hotkey)); }; //#endregion //#region src/dndLogic.ts /** * Determines whether a drag item can be dropped on a rule target. */ const canDropOnRule = ({ dragging, path, schema, canDrop, groupModeModifierKey, disabled, rule }) => { if (isHotkeyPressed(groupModeModifierKey) && disabled || dragging && typeof canDrop === "function" && !canDrop({ dragging, hovering: { ...rule, path, qbId: schema.qbId } })) return false; if (schema.qbId !== dragging.qbId) return true; const parentHoverPath = getParentPath(path); const parentItemPath = getParentPath(dragging.path); const hoverIndex = path.at(-1); const itemIndex = dragging.path.at(-1); return !(isAncestor(dragging.path, path) || pathsAreEqual(path, dragging.path) || !isHotkeyPressed(groupModeModifierKey) && pathsAreEqual(parentHoverPath, parentItemPath) && (hoverIndex === itemIndex - 1 || schema.independentCombinators && hoverIndex === itemIndex - 2)); }; /** * Determines whether a drag item can be dropped on a rule group target. */ const canDropOnRuleGroup = ({ dragging, path, schema, canDrop, disabled, ruleGroup }) => { if (disabled || dragging && typeof canDrop === "function" && !canDrop({ dragging, hovering: { ...ruleGroup, path, qbId: schema.qbId } })) return false; if (schema.qbId !== dragging.qbId) return true; const parentItemPath = getParentPath(dragging.path); const itemIndex = dragging.path.at(-1); return !(isAncestor(dragging.path, path) || pathsAreEqual(path, parentItemPath) && itemIndex === 0 || pathsAreEqual(path, dragging.path)); }; /** * Determines whether a drag item can be dropped on an inline combinator target. */ const canDropOnInlineCombinator = ({ dragging, path, schema, canDrop, groupModeModifierKey, hoveringItem }) => { const { path: itemPath } = dragging; if (isHotkeyPressed(groupModeModifierKey) || dragging && typeof canDrop === "function" && !canDrop({ dragging, hovering: { ...hoveringItem, path, qbId: schema.qbId } })) return false; const parentHoverPath = getParentPath(path); const parentItemPath = getParentPath(itemPath); const hoverIndex = path.at(-1); const itemIndex = itemPath.at(-1); return !(isAncestor(itemPath, path) || pathsAreEqual(itemPath, path) || pathsAreEqual(parentHoverPath, parentItemPath) && hoverIndex - 1 === itemIndex || schema.independentCombinators && pathsAreEqual(parentHoverPath, parentItemPath) && hoverIndex === itemIndex - 1); }; /** * Builds a {@link DropResult} for a given target. */ const buildDropResult = ({ type, path, schema, copyModeModifierKey, groupModeModifierKey, copyModeOverride, groupModeOverride }) => { const { qbId, getQuery, dispatchQuery } = schema; return { type, path, qbId, getQuery, dispatchQuery, groupItems: groupModeOverride || isHotkeyPressed(groupModeModifierKey), dropEffect: copyModeOverride || isHotkeyPressed(copyModeModifierKey) ? "copy" : "move" }; }; /** * Computes the destination path for a drop operation based on the drop target * type and whether grouping mode is active. */ const getDestinationPath = (dropResult, groupItems) => { const parentHoverPath = getParentPath(dropResult.path); const hoverIndex = dropResult.path.at(-1); if (groupItems) return dropResult.path; if (dropResult.type === "ruleGroup") return [...dropResult.path, 0]; if (dropResult.type === "inlineCombinator") return [...parentHoverPath, hoverIndex]; return [...parentHoverPath, hoverIndex + 1]; }; /** * Handles the actual query mutation when a drop completes. * Supports move, copy, group, and cross-query-builder operations. */ const handleDrop = ({ item, dropResult, schema, actions, copyModeModifierKey, groupModeModifierKey, copyModeOverride, groupModeOverride, onRuleDrop }) => { if (!dropResult) return; const dropEffect = copyModeOverride || isHotkeyPressed(copyModeModifierKey) ? "copy" : "move"; const groupItems = groupModeOverride || isHotkeyPressed(groupModeModifierKey); const destinationPath = getDestinationPath(dropResult, groupItems); const isCrossBuilder = schema.qbId !== dropResult.qbId; if (!isCrossBuilder) if (groupItems) actions.groupRule(item.path, destinationPath, dropEffect === "copy"); else actions.moveRule(item.path, destinationPath, dropEffect === "copy"); else { const otherBuilderQuery = dropResult.getQuery(); // v8 ignore else if (otherBuilderQuery) { if (groupItems) dropResult.dispatchQuery(group(add(otherBuilderQuery, item, []), [otherBuilderQuery.rules.length], destinationPath, { clone: false })); else dropResult.dispatchQuery(insert(otherBuilderQuery, item, destinationPath)); // v8 ignore else if (dropEffect !== "copy") actions.onRuleRemove(item.path); } } onRuleDrop?.({ draggedItem: item, sourceQbId: schema.qbId, targetQbId: dropResult.qbId, sourcePath: item.path, targetPath: destinationPath, dropEffect, groupItems, isCrossBuilder }); }; /** * Creates the drag item from the current path and schema, used by * drag-start callbacks. */ const getDragItem = (path, schema) => ({ ...findPath(path, schema.getQuery()), path, qbId: schema.qbId }); //#endregion export { getDragItem as a, canDropOnRuleGroup as i, canDropOnInlineCombinator as n, handleDrop as o, canDropOnRule as r, isHotkeyPressed as s, buildDropResult as t }; //# sourceMappingURL=dndLogic-Cg0Rq-DI.mjs.map