UNPKG

@formkit/drag-and-drop

Version:

Drag and drop package.

789 lines (784 loc) 25.5 kB
import { RefObject, Dispatch, SetStateAction } from 'react'; interface ParentDragEventData<T> extends ParentEventData<T> { e: DragEvent; } type NativeDragEffects = "link" | "none" | "copy" | "move"; /** * The configuration object for a given parent. */ interface ParentConfig<T> { /** * A function that returns whether a given parent accepts a given node. */ accepts?: (targetParentData: ParentRecord<T>, initialParentData: ParentRecord<T>, currentParentData: ParentRecord<T>, state: BaseDragState<T>) => boolean; activeDescendantClass?: string; /** * The data transfer effect to use for the drag operation. */ dragEffectAllowed: NativeDragEffects; /** * The data transfer effect to use for the drag operation. */ dragDropEffect: NativeDragEffects; /** * A function that returns the image to use for the drag operation. This is * invoked for native operations. The clonedNode is what will be set as the drag * image, but this can be updated. */ dragImage?: (data: NodeDragEventData<T>, draggedNodes: Array<NodeRecord<T>>) => HTMLElement; /** * A flag to disable dragability of all nodes in the parent. */ disabled?: boolean; /** * A selector for the drag handle. Will search at any depth within the node. */ dragHandle?: string; /** * A function that returns whether a given node is draggable. */ draggable?: (child: HTMLElement) => boolean; draggedNodes: (data: NodeEventData<T>) => Array<NodeRecord<T>>; /** * The class to add to a node when it is being dragged. */ draggingClass?: string; /** * Accepts array of "dragged nodes" and applies dragstart classes to them. */ dragstartClasses: (node: NodeRecord<T>, nodes: Array<NodeRecord<T>>, config: ParentConfig<T>, isSynthDrag?: boolean) => void; dragPlaceholderClass?: string; /** * The configuration object for the drop and swap plugin. */ dropSwapConfig?: DropSwapConfig<T>; /** * The class to add to a node when the node is dragged over it. */ dropZoneClass?: string; /** * The class to add to a parent when it is dragged over. */ dropZoneParentClass?: string; /** * A flag to indicate whether the parent itself is a dropZone. */ dropZone?: boolean; /** * The group that the parent belongs to. This is used for allowing multiple * parents to transfer nodes between each other. */ group?: string; handleParentBlur: (data: ParentEventData<T>, state: BaseDragState<T>) => void; handleParentFocus: (data: ParentEventData<T>, state: BaseDragState<T>) => void; handleNodeKeydown: (data: NodeEventData<T>, state: DragState<T>) => void; handleParentKeydown: (data: ParentKeydownEventData<T>, state: DragState<T>) => void; /** * Function that is called when dragend or touchend event occurs. */ handleDragend: (data: NodeDragEventData<T>, state: DragState<T>) => void; /** * Function that is called when dragstart event occurs. */ handleDragstart: (data: NodeDragEventData<T>, state: DragState<T>) => void; handleEnd: (state: DragState<T> | SynthDragState<T>) => void; handleNodeDrop: (data: NodeDragEventData<T>, state: DragState<T>) => void; handleNodePointerup: (data: NodePointerEventData<T>, state: DragState<T>) => void; handleParentScroll: (data: ParentEventData<T>, state: DragState<T> | BaseDragState<T> | SynthDragState<T>) => void; /** * Function that is called when a dragenter event is triggered on the node. */ handleNodeDragenter: (data: NodeDragEventData<T>, state: DragState<T>) => void; /** * Dragleave event on node */ handleNodeDragleave: (data: NodeDragEventData<T>, state: DragState<T>) => void; /** * Function that is called when a dragover event is triggered on the parent. */ handleParentDragover: (data: ParentDragEventData<T>, state: DragState<T>) => void; /** * Drop event on parent */ handleParentDrop: (data: ParentDragEventData<T>, state: DragState<T>) => void; /** * Function that is called when a dragover event is triggered on a node. */ handleNodeDragover: (data: NodeDragEventData<T>, state: DragState<T>) => void; handlePointercancel: (data: NodeDragEventData<T> | NodePointerEventData<T>, state: DragState<T> | SynthDragState<T> | BaseDragState<T>) => void; handleNodePointerdown: (data: NodePointerEventData<T>, state: DragState<T>) => void; /** * Function that is called when either a pointermove or touchmove event is fired * where now the "dragged" node is being moved programatically. */ handleNodePointermove: (data: NodePointerEventData<T>, state: DragState<T>) => void; /** * Function that is called when a node that is being moved by touchmove event * is over a given node (similar to dragover). */ handleNodePointerover: (data: PointeroverNodeEvent<T>, state: SynthDragState<T>) => void; /** * Function that is called when a node that is being moved by touchmove event * is over the parent (similar to dragover). */ handleParentPointerover: (e: PointeroverParentEvent<T>, state: SynthDragState<T>) => void; /** * Config option for insert plugin. */ insertConfig?: InsertConfig<T>; /** * A flag to indicate whether long touch is enabled. */ longPress?: boolean; /** * The class to add to a node when a long touch action is performed. */ longPressClass?: string; /** * The time in milliseconds to wait before a long touch is performed. */ longPressDuration?: number; /** * The name of the parent (used for accepts function for increased specificity). */ name?: string; multiDrag?: boolean; /** * If set to false, the library will not use the native drag and drop API. */ nativeDrag?: boolean; /** * Function that is called when a sort operation is to be performed. */ performSort: ({ parent, draggedNodes, targetNode, }: { parent: ParentRecord<T>; draggedNodes: Array<NodeRecord<T>>; targetNode: NodeRecord<T>; }) => void; /** * Function that is called when a transfer operation is to be performed. */ performTransfer: ({ currentParent, targetParent, initialParent, draggedNodes, initialIndex, state, targetNode, }: { currentParent: ParentRecord<T>; targetParent: ParentRecord<T>; initialParent: ParentRecord<T>; draggedNodes: Array<NodeRecord<T>>; initialIndex: number; state: BaseDragState<T> | DragState<T> | SynthDragState<T>; targetNode?: NodeRecord<T>; }) => void; /** * An array of functions to use for a given parent. */ plugins?: Array<DNDPlugin>; /** * Takes a given node and reapplies the drag classes. */ reapplyDragClasses: (node: Node, parentData: ParentData<T>) => void; /** * Invoked when the remapping of a given parent's nodes is finished. */ remapFinished: (data: ParentData<T>) => void; /** * The root element to use for the parent. */ root: Document | ShadowRoot; selectedClass?: string; /** * Function that is called when a node is set up. */ setupNode: SetupNode; /** * Called when the value of the parent is changed and the nodes are remapped. */ setupNodeRemap: SetupNode; /** * The scroll behavior of the parent. * * If a parent of the dragged element is scrollable, the parent will scroll on its x and y axis. * * I.e. Setting x: 0.9 will begin scrolling the parent when the dragged element is 90% horizontally. * * Scroll Outside determines whether or not the parent will scroll when the dragged element is outside of the parent. */ scrollBehavior: { x: number; y: number; scrollOutside?: boolean; }; /** * Flag for whether or not to allow sorting within a given parent. */ sortable?: boolean; /** * The class to add to a parent when it is dragged over. */ synthDropZoneParentClass?: string; /** * A function that returns the image to use for the drag operation. This is * invoked for synth drag operations operations. The clonedNode is what will * be set as the drag image, but this can be updated. */ synthDragImage?: (data: NodePointerEventData<T>, draggedNodes: Array<NodeRecord<T>>) => HTMLElement; /** * Function that is called when a node is torn down. */ tearDownNode: TearDownNode; /** * Called when the value of the parent is changed and the nodes are remapped. */ tearDownNodeRemap: TearDownNode; /** * Property to identify the parent record who is the ancestor of the current parent. */ treeAncestor?: boolean; /** * Property to identify which group of tree descendants the current parent belongs to. */ treeGroup?: string; /** * The threshold for a drag to be considered a valid sort * operation. */ threshold: { horizontal: number; vertical: number; }; /** * The class to add to a node when it is being synthetically dragged. */ synthDraggingClass?: string; /** * On synth drag start, this is applied to the dragged node(s) (not their * representations being dragged). */ synthDragPlaceholderClass?: string; /** * When hovering over a node, this class is applied to the node. */ synthDropZoneClass?: string; /** * When a node receives focus, this class is applied to the node. */ synthActiveDescendantClass?: string; /** * Config option to allow recursive copying of computed styles of dragged * element to the cloned one that will be dragged (only for synthetic drag). */ deepCopyStyles?: boolean; /** * Callback function for when a sort operation is performed. */ onSort?: SortEvent; /** * Callback function for when a transfer operation is performed. */ onTransfer?: TransferEvent; /** * Fired when a drag is started, whether native drag or synthetic */ onDragstart?: DragstartEvent; /** * Fired when a drag is ended, whether native drag or synthetic */ onDragend?: DragendEvent; } /** * The data assigned to a given parent in the `parents` weakmap. */ interface ParentData<T> { /** * A function that returns the values assigned to the parent. */ getValues: (parent: HTMLElement) => Array<T>; /** * A function that sets the values assigned to the parent. */ setValues: (values: Array<T>, parent: HTMLElement) => void; /** * The configuration object for the parent. */ config: ParentConfig<T>; /** * The nodes that are currently enabled for drag and drop. */ enabledNodes: Array<NodeRecord<T>>; /** * The abort controllers for the parent. */ abortControllers: Record<string, AbortController>; /** * The private classes of the node (used for preventing erroneous removal of * classes). */ privateClasses: Array<string>; /** * Set on parentData indicating that the current parent is nested beneath an ancestor. */ nestedParent?: ParentRecord<T>; emit: (event: string, data: unknown) => void; on: (event: string, callback: CallableFunction) => void; } /** * The data assigned to a given node in the `nodes` weakmap. */ interface NodeData<T> { /** * The index of the in respect to its adjacent enabled nodes. */ index: number; /** * The value of the node. */ value: T; /** * The private classes of the node (used for preventing erroneous removal of * classes). */ privateClasses: Array<string>; /** * The abort controllers for the node. */ abortControllers: Record<string, AbortController>; /** * Set by the insertion plugin to define the coordinates for a given node. */ range?: { ascending?: { y: number[]; x: number[]; vertical: boolean; }; descending?: { y: number[]; x: number[]; vertical: boolean; }; }; } /** * The data passed to the node event listener. */ interface NodeEventData<T> { /** * The event that was triggered. */ e: Event; /** * The data of the target node. */ targetData: NodeTargetData<T>; } /** * The data passed to the node event listener when the event is a drag event. */ interface NodeDragEventData<T> extends NodeEventData<T> { e: DragEvent; } /** * The data passed to the node event listener when the event is a pointer event (not a native drag event). */ interface NodePointerEventData<T> extends NodeEventData<T> { /** * The event that was triggered. */ e: PointerEvent; /** * The data of the target node. */ targetData: NodeTargetData<T>; } interface ParentKeydownEventData<T> extends ParentEventData<T> { /** * The event that was triggered. */ e: KeyboardEvent; /** * The data of the target node. */ targetData: ParentTargetData<T>; } /** * The data passed to the parent event listener. * * @param e - The event that was triggered. * @param targetData - The data of the target parent. */ interface ParentEventData<T> { e: Event; targetData: ParentTargetData<T>; } /** * The target data of the parent involved with the event, whether a node or * parent event. * * @param param - The parent record. */ interface ParentTargetData<T> { parent: ParentRecord<T>; } /** * The target data of the node involved with the event. * * @param node - The node record. * @param parent - The parent record. */ interface NodeTargetData<T> { node: NodeRecord<T>; parent: ParentRecord<T>; } /** * The node record, contains the el and the data in the `nodes` weakmap. */ interface NodeRecord<T> { el: Node; data: NodeData<T>; } /** * The parent record, contains the el and the data in the `parents` weakmap. */ interface ParentRecord<T> { el: HTMLElement; data: ParentData<T>; } /** * The interface for a node in the context of FormKit's Drag and Drop. */ interface Node extends HTMLElement { parentNode: HTMLElement; } /** * The payload of the custom event dispatched when a node is "touched" over a * node. */ interface PointeroverNodeEvent<T> extends Event { detail: { e: PointerEvent; targetData: NodeTargetData<T>; state: SynthDragState<T>; }; } /** * The payload of the custom event dispatched when a node is "touched" over a * parent. */ interface PointeroverParentEvent<T> extends Event { detail: { e: PointerEvent; targetData: ParentTargetData<T>; state: SynthDragState<T>; }; } /** * The interface for a drag and drop plugin. */ interface DNDPluginData { /** * The function to call when the parent is set up. */ setup?: () => void; /** * The function to call when the parent is torn down. */ tearDown?: () => void; /** * Called when entry point function is invoked on parent. */ setupNode?: SetupNode; /** * Called when entry point function is invoked on parent. */ tearDownNode?: TearDownNode; /** * Called anytime the nodes are mutated */ setupNodeRemap?: SetupNode; /** * Called when the parent is dragged over. */ tearDownNodeRemap?: TearDownNode; /** * Called when all nodes have finished remapping for a given parent */ remapFinished?: () => void; } type DNDPlugin = (parent: HTMLElement) => DNDPluginData | undefined; type SetupNode = <T>(data: SetupNodeData<T>) => void; type TearDownNode = <T>(data: TearDownNodeData<T>) => void; /** * The payload of when the setupNode function is called in a given plugin. */ interface SetupNodeData<T> { node: NodeRecord<T>; parent: ParentRecord<T>; } /** * The payload of when the tearDownNode function is called in a given plugin. */ interface TearDownNodeData<T> { node: { el: Node; data?: NodeData<T>; }; parent: ParentRecord<T>; } /** * The state of the current drag. State is only created when a drag start * event is triggered. * * @param activeNode - The node that was most recently clicked (used optionally). * @param affectedNodes - The nodes that will be updated by a drag action * (sorted). * @param ascendingDirection - Indicates whetehr the dragged node is moving to a * node with a higher index or not. * @param clonedDraggedEls - The cloned elements of the dragged node. This is * used primarily for TouchEvents or multi-drag purposes. * @param draggedNode - The node that is being dragged. * @param draggedNodes - The nodes that are being dragged. * @param incomingDirection - The direction that the dragged node is moving into * a dragover node. * @param initialParent - The parent that the dragged node was initially in. * @param currentParent - The parent that the dragged node was most recently in. * @param lastValue - The last value of the dragged node. * @param originalZIndex - The original z-index of the dragged node. * @param preventEnter - A flag to prevent a sort operation from firing until * the mutation observer has had a chance to update the data of the remapped * nodes. * @param swappedNodeValue - The value of the node that was swapped with the * dragged node. * @param targetIndex - The index of the node that the dragged node is moving * into. */ type SynthDragState<T> = SynthDragStateProps & DragState<T>; interface SynthDragStateProps { /** * Element */ clonedDraggedNode: HTMLElement; /** * Direction of the synthetic drag scroll */ synthScrollDirection: "up" | "down" | "left" | "right" | undefined; /** * The display of the synthetic node. */ draggedNodeDisplay: string; /** * Flag indcating whether a scrollable el is being scrolled via. * synthetic drag. */ synthDragScrolling: boolean; /** * Pointer id of dragged el */ pointerId: number; scrollElement: HTMLElement | undefined; animationFrameId: number | undefined; } type DragState<T> = DragStateProps<T> & BaseDragState<T>; type BaseDragState<T> = { activeState?: { node: NodeRecord<T>; parent: ParentRecord<T>; }; /** * The nodes that will be updated by a drag action (sorted). */ affectedNodes: Array<NodeRecord<T>>; /** * The last value the dragged node targeted. */ currentTargetValue: T | undefined; emit: (event: string, data: unknown) => void; on: (event: string, callback: CallableFunction) => void; newActiveDescendant?: NodeRecord<T>; preventSynthDrag: boolean; longPress: boolean; longPressTimeout: number; /** * The original z-index of the dragged node. */ originalZIndex?: string; pointerSelection: boolean; preventEnter: boolean; /** * Flag indicating that the remap just finished. */ remapJustFinished: boolean; selectedState?: { nodes: Array<NodeRecord<T>>; parent: ParentRecord<T>; }; }; interface DragStateProps<T> { /** * The nodes that will be updated by a drag action (sorted). */ affectedNodes: Array<NodeRecord<T>>; /** * Indicates whether the dragged node is moving to a node with a higher index * or not. */ ascendingDirection: boolean; /** * The cloned elements of the dragged node. This is used primarily for * TouchEvents or multi-drag purposes. */ clonedDraggedEls: Array<Element>; /** * The coordinates of the dragged element itself. */ coordinates: { x: number; y: number; }; /** * The parent that the dragged node was most recently in. */ currentParent: ParentRecord<T>; currentTargetValue: T | undefined; /** * The node that is being dragged. */ draggedNode: NodeRecord<T>; /** * The nodes that are being dragged. */ draggedNodes: Array<NodeRecord<T>>; /** * Values to be inserted during sort and transfer operations. */ dynamicValues: Array<T>; /** * The direction that the dragged node is moving into a dragover node. */ incomingDirection: "above" | "below" | "left" | "right" | undefined; /** * The index of the node that the dragged node was initially in. */ initialIndex: number; /** * The parent that the dragged node was initially in. */ initialParent: ParentRecord<T>; /** * longPress - A flag to indicate whether a long press has occurred. */ longPress: boolean; /** * Long press timeout */ longPressTimeout: number; /** * scrollEls */ scrollEls: Array<[HTMLElement, AbortController]>; /** * The top position of pointerdown. */ startTop: number; /** * The left position of the pointerdown. */ startLeft: number; /** * The index of the node that the dragged node is moving into. */ targetIndex: number; /** * Flag indicating that the dragged node was transferred */ transferred: boolean; } type SortEvent = <T>(data: SortEventData<T>) => void; type TransferEvent = <T>(data: TransferEventData<T>) => void; type DragstartEvent = <T>(data: DragstartEventData<T>, state: DragState<T>) => void; type DragendEvent = <T>(data: DragendEventData<T>) => void; interface SortEventData<T> { parent: ParentRecord<T>; previousValues: Array<T>; values: Array<T>; previousNodes: Array<NodeRecord<T>>; nodes: Array<NodeRecord<T>>; draggedNode: NodeRecord<T>; previousPosition: number; position: number; } interface TransferEventData<T> { sourceParent: ParentRecord<T>; targetParent: ParentRecord<T>; initialParent: ParentRecord<T>; draggedNodes: Array<NodeRecord<T>>; targetIndex: number; state: BaseDragState<T> | DragState<T> | SynthDragState<T>; targetNode?: NodeRecord<T>; } interface DragstartEventData<T> { parent: ParentRecord<T>; values: Array<T>; draggedNode: NodeRecord<T>; draggedNodes: Array<NodeRecord<T>>; position: number; } interface DragendEventData<T> { parent: ParentRecord<T>; values: Array<T>; draggedNode: NodeRecord<T>; draggedNodes: Array<NodeRecord<T>>; } interface ShouldSwapData<T> { sourceParent: ParentRecord<T>; targetParent: ParentRecord<T>; draggedNodes: Array<NodeRecord<T>>; targetNodes: Array<NodeRecord<T>>; state: BaseDragState<T> | DragState<T> | SynthDragState<T>; } interface DropSwapConfig<T> { shouldSwap?: (data: ShouldSwapData<T>) => boolean; handleNodeDragover?: (data: NodeDragEventData<unknown>, state: DragState<unknown>) => void; handleParentDragover?: (data: ParentDragEventData<unknown>, state: DragState<unknown>) => void; handleParentPointerover?: (e: PointeroverParentEvent<unknown>, state: DragState<unknown>) => void; handleNodePointerover?: (data: PointeroverNodeEvent<unknown>, state: SynthDragState<unknown>) => void; } interface InsertConfig<T> { insertPoint: (parent: ParentRecord<T>) => HTMLElement; insertEvent?: (data: InsertEvent<T>) => void; handleNodeDragover?: (data: NodeDragEventData<T>, state: DragState<T>) => void; handleParentDragover?: (data: ParentDragEventData<T>, state: DragState<T>) => void; handleParentPointerover?: (data: PointeroverParentEvent<T>) => void; handleNodePointerover?: (data: PointeroverNodeEvent<T>) => void; handleEnd?: (data: NodeDragEventData<T> | NodePointerEventData<T>) => void; } interface InsertEvent<T> { sourceParent: ParentRecord<T>; targetParent: ParentRecord<T>; draggedNodes: Array<NodeRecord<T>>; targetNodes: Array<NodeRecord<T>>; state: BaseDragState<T> | DragState<T> | SynthDragState<T>; } type ReactElement<E extends HTMLElement> = E | RefObject<E>; interface ReactDragAndDropConfig<E extends RefObject<HTMLElement | null> | HTMLElement, ListItems extends unknown[]> extends Partial<ParentConfig<ListItems[number]>> { parent: E; state: [ListItems, React.Dispatch<React.SetStateAction<ListItems>>]; } /** * Entry point for React drag and drop. * * @param data - The drag and drop configuration. * @returns void */ declare function dragAndDrop<E extends HTMLElement, I>(data: ReactDragAndDropConfig<RefObject<E | null> | HTMLElement, I[]> | Array<ReactDragAndDropConfig<RefObject<E | null> | HTMLElement, I[]>>): void; /** * Hook for adding drag and drop/sortable support to a list of items. * * @param list - Initial list of data. * @param options - The drag and drop configuration. * @returns */ declare function useDragAndDrop<E extends HTMLElement, T = unknown>(list: T[], options?: Partial<ParentConfig<T>>): [ RefObject<E>, T[], Dispatch<SetStateAction<T[]>>, (config: Partial<ParentConfig<T>>) => void ]; export { type ReactDragAndDropConfig, type ReactElement, dragAndDrop, useDragAndDrop };