Remove payments API routing initialization
[ProtonMail-WebClient.git] / packages / components / containers / items / useItemsDraggable.ts
blob4e29093920ccc83a251d194c0c8d517afffb84c6
1 import type { DragEvent } from 'react';
2 import { useCallback, useEffect, useRef, useState } from 'react';
4 import useHandler from '@proton/components/hooks/useHandler';
5 import generateUID from '@proton/utils/generateUID';
7 import { DRAG_ITEM_ID_KEY, DRAG_ITEM_KEY } from './constants';
9 import './items.scss';
11 type AbstractItem = { ID?: string };
13 /**
14  * Implement the draggable logic for an item
15  * Linked to the selection logic to drag the currently selected elements
16  * or to restore the selection after the drag
17  * Also take care of rendering the drag element and including the needed data in the transfer
18  * Items can be any object containing an ID
19  * @param items List of all items in the list
20  * @param checkedIDs List of the currently checked IDs
21  * @param onCheck Check handler to update selection
22  * @param getDragHtml Callback to return HTML content of the drag element
23  * @param selectAll Use select all feature so that we know how many items to drag
24  * @returns Currently dragged ids and drag handler to pass to items
25  */
26 const useItemsDraggable = <Item extends AbstractItem>(
27     items: Item[],
28     checkedIDs: string[],
29     onCheck: (IDs: string[], checked: boolean, replace: boolean) => void,
30     getDragHtml: (draggedIDs: string[]) => string,
31     selectAll?: boolean
32 ) => {
33     // HTML reference to the drag element
34     const dragElementRef = useRef<HTMLDivElement>();
36     // List of currently dragged item ids
37     const [draggedIDs, setDraggedIDs] = useState<string[]>([]);
39     // Saved selection when dragging an item not selected
40     const [savedCheck, setSavedCheck] = useState<string[]>();
42     useEffect(() => {
43         setDraggedIDs([]);
44     }, [items]);
46     const clearDragElement = () => {
47         if (dragElementRef.current) {
48             document.body.removeChild(dragElementRef.current);
49             dragElementRef.current = undefined;
50         }
51     };
53     const handleDragCanceled = useHandler(() => {
54         setDraggedIDs([]);
56         if (savedCheck) {
57             onCheck(savedCheck, true, true);
58             setSavedCheck(undefined);
59         }
60     });
62     /**
63      * Drag end handler to use on the draggable element
64      */
65     const handleDragEnd = useCallback((event: DragEvent) => {
66         // Always clear the drag element no matter why the drag has ended
67         clearDragElement();
69         // We discover that Chrome initialize the dropEffect to 'copy' and only set it to 'none' just after
70         // We don't use 'copy' at all so both 'none' and 'copy' effects can be considered as canceled drags
71         if (event.dataTransfer.dropEffect === 'none' || event.dataTransfer.dropEffect === 'copy') {
72             return handleDragCanceled();
73         }
74     }, []);
76     const handleDragSucceed = useHandler((action: string | undefined) => {
77         clearDragElement();
79         setDraggedIDs([]);
81         if (savedCheck) {
82             if (action === 'link') {
83                 // Labels
84                 onCheck(savedCheck, true, true);
85             }
86             setSavedCheck(undefined);
87         }
88     });
90     /**
91      * Drag start handler to use on the draggable element
92      */
93     const handleDragStart = useCallback(
94         (event: DragEvent, item: Item) => {
95             clearDragElement();
97             const ID = item.ID || '';
98             const dragInSelection = checkedIDs.includes(ID);
99             const selection = dragInSelection ? checkedIDs : [ID];
101             setDraggedIDs(selection);
102             setSavedCheck(checkedIDs);
104             if (!dragInSelection) {
105                 onCheck([], true, true);
106             }
108             const dragElement = document.createElement('div');
109             dragElement.innerHTML = getDragHtml(selection);
110             dragElement.className = 'drag-element p-4 border rounded';
111             dragElement.style.insetInlineStart = '-9999px';
112             dragElement.id = generateUID(DRAG_ITEM_ID_KEY);
113             // Wiring the dragend event on the drag element because the one from drag start is not reliable
114             dragElement.addEventListener('dragend', (event) => handleDragSucceed(event.dataTransfer?.dropEffect));
115             document.body.appendChild(dragElement);
116             event.dataTransfer.setDragImage(dragElement, 0, 0);
117             event.dataTransfer.setData(DRAG_ITEM_KEY, JSON.stringify(selection));
118             event.dataTransfer.setData(DRAG_ITEM_ID_KEY, dragElement.id);
120             dragElementRef.current = dragElement;
121         },
122         [checkedIDs, onCheck, selectAll]
123     );
125     return { draggedIDs, handleDragStart, handleDragEnd };
128 export default useItemsDraggable;