@react-querybuilder/dnd
Version:
Drag-and-drop-enabled version of react-querybuilder (DnD-library-agnostic)
1 lines • 17.5 kB
Source Map (JSON)
{"version":3,"file":"dndLogic-DR8uS_bm.mjs","names":[],"sources":["../src/isHotkeyPressed.ts","../src/dndLogic.ts"],"sourcesContent":["/* oxlint-disable prefer-global-this */\n\n/**\n * Adapted from\n * https://github.com/JohannesKlauss/react-hotkeys-hook/blob/bc55a281f1d212d09de786aeb5cd236c58d9531d/src/isHotkeyPressed.ts\n * and\n * https://github.com/JohannesKlauss/react-hotkeys-hook/blob/bc55a281f1d212d09de786aeb5cd236c58d9531d/src/parseHotkey.ts\n */\n\nimport { lc } from 'react-querybuilder';\n\ntype ModifierKey = 'shift' | 'alt' | 'meta' | 'mod' | 'ctrl';\n\n// #region parseHotkey.ts\nconst reservedModifierKeywords = new Set<ModifierKey>(['shift', 'alt', 'meta', 'mod', 'ctrl']);\n\nconst mappedKeys: Record<string, string> = {\n esc: 'escape',\n return: 'enter',\n '.': 'period',\n ',': 'comma',\n '-': 'slash',\n ' ': 'space',\n '`': 'backquote',\n '#': 'backslash',\n '+': 'bracketright',\n ShiftLeft: 'shift',\n ShiftRight: 'shift',\n AltLeft: 'alt',\n AltRight: 'alt',\n MetaLeft: 'meta',\n MetaRight: 'meta',\n OSLeft: 'meta',\n OSRight: 'meta',\n ControlLeft: 'ctrl',\n ControlRight: 'ctrl',\n};\n\nconst mapKey = (key?: string) =>\n lc(((key && mappedKeys[key]) || key || '').trim()).replace(/key|digit|numpad|arrow/, '');\n\nconst isHotkeyModifier = (key: string) => reservedModifierKeywords.has(key as ModifierKey);\n// #endregion parseHotkey.ts\n\nconst keyAliases: Record<string, string> = {\n '⌘': 'meta',\n cmd: 'meta',\n command: 'meta',\n '⊞': 'meta',\n win: 'meta',\n windows: 'meta',\n '⇧': 'shift',\n '⌥': 'alt',\n '⌃': 'ctrl',\n control: 'ctrl',\n};\n\n// #region isHotkeyPressed.ts\n(() => {\n if (typeof document !== 'undefined') {\n document.addEventListener('keydown', e => {\n if (e.key === undefined) {\n // Synthetic event (e.g., Chrome autofill). Ignore.\n return;\n }\n\n pushToCurrentlyPressedKeys([mapKey(e.key), mapKey(e.code)]);\n });\n\n document.addEventListener('keyup', e => {\n if (e.key === undefined) {\n // Synthetic event (e.g., Chrome autofill). Ignore.\n return;\n }\n\n removeFromCurrentlyPressedKeys([mapKey(e.key), mapKey(e.code)]);\n });\n }\n\n if (typeof window !== 'undefined') {\n window.addEventListener('blur', () => {\n currentlyPressedKeys.clear();\n });\n }\n})();\n\nconst currentlyPressedKeys: Set<string> = new Set<string>();\n\n// https://github.com/microsoft/TypeScript/issues/17002\nconst isReadonlyArray = (value: unknown): value is readonly unknown[] => Array.isArray(value);\n\nexport const isHotkeyPressed = (key: string | readonly string[], splitKey = ','): boolean =>\n (isReadonlyArray(key) ? key : key.split(splitKey)).every(hotkey => {\n const hk = lc(hotkey.trim());\n return currentlyPressedKeys.has(keyAliases[hk] ?? hk);\n });\n\nconst pushToCurrentlyPressedKeys = (key: string | string[]) => {\n const hotkeyArray = Array.isArray(key) ? key : [key];\n\n /*\n Due to a weird behavior on macOS we need to clear the set if the user pressed down the meta key and presses another key.\n https://stackoverflow.com/questions/11818637/why-does-javascript-drop-keyup-events-when-the-metakey-is-pressed-on-mac-browser\n Otherwise the set will hold all ever pressed keys while the meta key is down which leads to wrong results.\n */\n if (currentlyPressedKeys.has('meta')) {\n for (const k of currentlyPressedKeys) {\n if (!isHotkeyModifier(k)) {\n currentlyPressedKeys.delete(lc(k));\n }\n }\n }\n\n for (const hotkey of hotkeyArray) currentlyPressedKeys.add(lc(hotkey));\n};\n\nconst removeFromCurrentlyPressedKeys = (key: string | string[]) => {\n const hotkeyArray = Array.isArray(key) ? key : [key];\n\n /*\n Due to a weird behavior on macOS we need to clear the set if the user pressed down the meta key and presses another key.\n https://stackoverflow.com/questions/11818637/why-does-javascript-drop-keyup-events-when-the-metakey-is-pressed-on-mac-browser\n Otherwise the set will hold all ever pressed keys while the meta key is down which leads to wrong results.\n */\n if (key === 'meta') {\n currentlyPressedKeys.clear();\n } else {\n for (const hotkey of hotkeyArray) currentlyPressedKeys.delete(lc(hotkey));\n }\n};\n// #endregion isHotkeyPressed.ts\n","import type {\n DndDropTargetType,\n DraggedItem,\n DropEffect,\n DropResult,\n Path,\n QueryActions,\n RuleGroupTypeAny,\n RuleType,\n Schema,\n} from 'react-querybuilder';\nimport {\n add,\n findPath,\n getParentPath,\n group,\n insert,\n isAncestor,\n pathsAreEqual,\n} from 'react-querybuilder';\nimport { isHotkeyPressed } from './isHotkeyPressed';\nimport type { CustomCanDropParams, OnRuleDropCallback } from './types';\n\n// #region canDrop validators\n\n/**\n * Determines whether a drag item can be dropped on a rule target.\n */\nexport const canDropOnRule = ({\n dragging,\n path,\n schema,\n canDrop,\n groupModeModifierKey,\n disabled,\n rule,\n}: {\n dragging: DraggedItem;\n path: Path;\n // oxlint-disable-next-line typescript/no-explicit-any\n schema: Schema<any, any>;\n canDrop?: (params: CustomCanDropParams) => boolean;\n groupModeModifierKey: string;\n disabled: boolean;\n rule: RuleType;\n}): boolean => {\n if (\n (isHotkeyPressed(groupModeModifierKey) && disabled) ||\n (dragging &&\n typeof canDrop === 'function' &&\n !canDrop({ dragging, hovering: { ...rule, path, qbId: schema.qbId } }))\n ) {\n return false;\n }\n\n if (schema.qbId !== dragging.qbId) return true;\n\n const parentHoverPath = getParentPath(path);\n const parentItemPath = getParentPath(dragging.path);\n const hoverIndex = path.at(-1);\n const itemIndex = dragging.path.at(-1)!;\n\n // Disallow drop if...\n // prettier-ignore\n return !(\n // 1) item is ancestor of drop target, OR\n isAncestor(dragging.path, path) ||\n // 2) item is hovered over itself, OR\n (pathsAreEqual(path, dragging.path)) ||\n // 3) item is hovered over the previous item AND this is a move, not a group\n (!isHotkeyPressed(groupModeModifierKey) && pathsAreEqual(parentHoverPath, parentItemPath) &&\n (hoverIndex === itemIndex - 1 ||\n (schema.independentCombinators && hoverIndex === itemIndex - 2)))\n );\n};\n\n/**\n * Determines whether a drag item can be dropped on a rule group target.\n */\nexport const canDropOnRuleGroup = ({\n dragging,\n path,\n schema,\n canDrop,\n disabled,\n ruleGroup,\n}: {\n dragging: DraggedItem;\n path: Path;\n // oxlint-disable-next-line typescript/no-explicit-any\n schema: Schema<any, any>;\n canDrop?: (params: CustomCanDropParams) => boolean;\n disabled: boolean;\n ruleGroup: RuleGroupTypeAny;\n}): boolean => {\n if (\n disabled ||\n (dragging &&\n typeof canDrop === 'function' &&\n !canDrop({ dragging, hovering: { ...ruleGroup, path, qbId: schema.qbId } }))\n ) {\n return false;\n }\n\n if (schema.qbId !== dragging.qbId) return true;\n\n const parentItemPath = getParentPath(dragging.path);\n const itemIndex = dragging.path.at(-1);\n // Disallow drop if...\n // prettier-ignore\n return !(\n // 1) item is ancestor of drop target, OR\n isAncestor(dragging.path, path) ||\n // 2) item is first child and is dropped on its own group header, OR\n (pathsAreEqual(path, parentItemPath) && itemIndex === 0) ||\n // 3) the group is dropped on itself\n pathsAreEqual(path, dragging.path)\n );\n};\n\n/**\n * Determines whether a drag item can be dropped on an inline combinator target.\n */\nexport const canDropOnInlineCombinator = ({\n dragging,\n path,\n schema,\n canDrop,\n groupModeModifierKey,\n hoveringItem,\n}: {\n dragging: DraggedItem;\n path: Path;\n // oxlint-disable-next-line typescript/no-explicit-any\n schema: Schema<any, any>;\n canDrop?: (params: CustomCanDropParams) => boolean;\n groupModeModifierKey: string;\n hoveringItem: RuleType | RuleGroupTypeAny;\n}): boolean => {\n const { path: itemPath } = dragging;\n if (\n isHotkeyPressed(groupModeModifierKey) ||\n (dragging &&\n typeof canDrop === 'function' &&\n !canDrop({ dragging, hovering: { ...hoveringItem, path, qbId: schema.qbId } }))\n ) {\n return false;\n }\n const parentHoverPath = getParentPath(path);\n const parentItemPath = getParentPath(itemPath);\n const hoverIndex = path.at(-1)!;\n const itemIndex = itemPath.at(-1)!;\n\n // Disallow drop if...\n // prettier-ignore\n return !(\n // 1) the item is an ancestor of the drop target,\n isAncestor(itemPath, path) ||\n // 2) the item is hovered over itself (which should never\n // happen since combinators don't have drag handles),\n pathsAreEqual(itemPath, path) ||\n (pathsAreEqual(parentHoverPath, parentItemPath) && hoverIndex - 1 === itemIndex) ||\n // 3) independentCombinators is true and the drop target is just above the hovering item\n (schema.independentCombinators &&\n pathsAreEqual(parentHoverPath, parentItemPath) &&\n hoverIndex === itemIndex - 1)\n );\n};\n\n// #endregion\n\n// #region Drop result builders\n\n/**\n * Builds a {@link DropResult} for a given target.\n */\nexport const buildDropResult = ({\n type,\n path,\n schema,\n copyModeModifierKey,\n groupModeModifierKey,\n copyModeOverride,\n groupModeOverride,\n}: {\n type: DndDropTargetType;\n path: Path;\n // oxlint-disable-next-line typescript/no-explicit-any\n schema: Schema<any, any>;\n copyModeModifierKey: string;\n groupModeModifierKey: string;\n copyModeOverride?: boolean;\n groupModeOverride?: boolean;\n}): DropResult => {\n const { qbId, getQuery, dispatchQuery } = schema;\n return {\n type,\n path,\n qbId,\n getQuery,\n dispatchQuery,\n groupItems: groupModeOverride || isHotkeyPressed(groupModeModifierKey),\n dropEffect: copyModeOverride || isHotkeyPressed(copyModeModifierKey) ? 'copy' : 'move',\n };\n};\n\n/**\n * Collects the current drop state from modifier keys.\n */\nexport const collectDropState = (\n copyModeModifierKey: string,\n groupModeModifierKey: string,\n copyModeOverride?: boolean,\n groupModeOverride?: boolean\n): { dropEffect: DropEffect; groupItems: boolean } => ({\n dropEffect: copyModeOverride || isHotkeyPressed(copyModeModifierKey) ? 'copy' : 'move',\n groupItems: groupModeOverride || isHotkeyPressed(groupModeModifierKey),\n});\n\n// #endregion\n\n// #region Drop handling\n\n/**\n * Computes the destination path for a drop operation based on the drop target\n * type and whether grouping mode is active.\n */\nexport const getDestinationPath = (dropResult: DropResult, groupItems: boolean): Path => {\n const parentHoverPath = getParentPath(dropResult.path);\n const hoverIndex = dropResult.path.at(-1)!;\n\n if (groupItems) {\n return dropResult.path;\n }\n if (dropResult.type === 'ruleGroup') {\n return [...dropResult.path, 0];\n }\n if (dropResult.type === 'inlineCombinator') {\n return [...parentHoverPath, hoverIndex];\n }\n // rule\n return [...parentHoverPath, hoverIndex + 1];\n};\n\n/**\n * Handles the actual query mutation when a drop completes.\n * Supports move, copy, group, and cross-query-builder operations.\n */\nexport const handleDrop = ({\n item,\n dropResult,\n schema,\n actions,\n copyModeModifierKey,\n groupModeModifierKey,\n copyModeOverride,\n groupModeOverride,\n onRuleDrop,\n}: {\n item: DraggedItem;\n dropResult: DropResult | null;\n // oxlint-disable-next-line typescript/no-explicit-any\n schema: Schema<any, any>;\n actions: QueryActions;\n copyModeModifierKey: string;\n groupModeModifierKey: string;\n copyModeOverride?: boolean;\n groupModeOverride?: boolean;\n onRuleDrop?: OnRuleDropCallback;\n}): void => {\n if (!dropResult) return;\n\n const dropEffect = copyModeOverride || isHotkeyPressed(copyModeModifierKey) ? 'copy' : 'move';\n const groupItems = groupModeOverride || isHotkeyPressed(groupModeModifierKey);\n const destinationPath = getDestinationPath(dropResult, groupItems);\n const isCrossBuilder = schema.qbId !== dropResult.qbId;\n\n if (!isCrossBuilder) {\n if (groupItems) {\n actions.groupRule(item.path, destinationPath, dropEffect === 'copy');\n } else {\n actions.moveRule(item.path, destinationPath, dropEffect === 'copy');\n }\n } else {\n const otherBuilderQuery = dropResult.getQuery();\n // v8 ignore else\n if (otherBuilderQuery) {\n if (groupItems) {\n dropResult.dispatchQuery(\n group(\n add(otherBuilderQuery, item, []),\n [otherBuilderQuery.rules.length],\n destinationPath,\n { clone: false }\n )\n );\n } else {\n dropResult.dispatchQuery(insert(otherBuilderQuery, item, destinationPath));\n }\n // v8 ignore else\n if (dropEffect !== 'copy') {\n actions.onRuleRemove(item.path);\n }\n }\n }\n\n onRuleDrop?.({\n draggedItem: item,\n sourceQbId: schema.qbId,\n targetQbId: dropResult.qbId,\n sourcePath: item.path,\n targetPath: destinationPath,\n dropEffect: dropEffect as DropEffect,\n groupItems,\n isCrossBuilder,\n });\n};\n\n/**\n * Creates the drag item from the current path and schema, used by\n * drag-start callbacks.\n */\nexport const getDragItem = (\n path: Path,\n // oxlint-disable-next-line typescript/no-explicit-any\n schema: Schema<any, any>\n): DraggedItem => ({\n ...findPath(path, schema.getQuery())!,\n path,\n qbId: schema.qbId,\n});\n\n// #endregion\n"],"mappings":"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"}