mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-17 13:46:47 +02:00
Fixes janky drag and drop behavior and updates the styling of the drag handle focus. The solution uses the same method to prevent oscillation as we do for strategies. To get access to the same context, I've added some extra parameters to the OnMoveItem function and passed along the extra data from the `useDragItem` hook. No new information, just making more of it available, and turning it into an object so that you can declare the properties you need (and get rid of potential wrong ordering of drag/drop indices). For the drag and drop behavior: If the dragged element is the same size or smaller than the element you're dragging over, they will swap places as soon as you enter that space. If the target element is larger, however, they won't swap until you reach the drag/drop handle, even if they could theoretically switch somewhere in the middle. This appears to be a limitation of how the drag/drop event system works. New drag events are only fired when you "dragenter" a new element, so it never fires anywhere in the middle. Technically, we could insert more empty spans inside the drag handle to trigger more events, but I wanna hold off on that because it doesn't sound great. When dragging, only the handle is visible; the rest of the card stays in place. For strategies, we show a "ghost" version of the config you're dragging. However, if you apply the drag handle to the card itself, all of it becomes draggable, but you can no longer select the text inside it, which is unfortunate. Strategies do solev this, though, but I haven't been able to figure out why. If you know, please share! Before:  After: 
114 lines
3.1 KiB
TypeScript
114 lines
3.1 KiB
TypeScript
import { useRef, useEffect, type RefObject } from 'react';
|
|
|
|
type OnMoveItemParams = {
|
|
dragIndex: number;
|
|
dropIndex: number;
|
|
save: boolean;
|
|
event: DragEvent;
|
|
draggedElement: HTMLElement;
|
|
};
|
|
|
|
export type OnMoveItem = (args: OnMoveItemParams) => void;
|
|
|
|
// The element being dragged in the browser.
|
|
let globalDraggedElement: HTMLElement | null;
|
|
|
|
export const useDragItem = <T extends HTMLElement>(
|
|
listItemIndex: number,
|
|
onMoveItem: OnMoveItem,
|
|
handle?: RefObject<HTMLElement>,
|
|
): RefObject<T> => {
|
|
const ref = useRef<T>(null);
|
|
|
|
useEffect(() => {
|
|
if (ref.current) {
|
|
ref.current.dataset.index = String(listItemIndex);
|
|
return addEventListeners(
|
|
ref.current,
|
|
onMoveItem,
|
|
handle?.current ?? undefined,
|
|
);
|
|
}
|
|
}, [listItemIndex, onMoveItem]);
|
|
|
|
return ref;
|
|
};
|
|
|
|
const addEventListeners = (
|
|
el: HTMLElement,
|
|
onMoveItem: OnMoveItem,
|
|
handle?: HTMLElement,
|
|
): (() => void) => {
|
|
const handleEl = handle ?? el;
|
|
|
|
const moveDraggedElement = (save: boolean, event: DragEvent) => {
|
|
if (globalDraggedElement) {
|
|
const dragIndex = Number(globalDraggedElement.dataset.index);
|
|
const dropIndex = Number(el.dataset.index);
|
|
onMoveItem({
|
|
dragIndex,
|
|
dropIndex,
|
|
save,
|
|
event,
|
|
draggedElement: globalDraggedElement,
|
|
});
|
|
}
|
|
};
|
|
|
|
const onMouseEnter = (e: MouseEvent) => {
|
|
if (e.target === handleEl) {
|
|
el.draggable = true;
|
|
}
|
|
};
|
|
|
|
const onMouseLeave = () => {
|
|
el.draggable = false;
|
|
};
|
|
|
|
const onDragStart = () => {
|
|
el.draggable = true;
|
|
globalDraggedElement = el;
|
|
};
|
|
|
|
const onDragEnter = (event: DragEvent) => {
|
|
moveDraggedElement(false, event);
|
|
};
|
|
|
|
const onDragOver = (event: DragEvent) => {
|
|
event.preventDefault();
|
|
};
|
|
|
|
const onDrop = (event: DragEvent) => {
|
|
moveDraggedElement(true, event);
|
|
globalDraggedElement = null;
|
|
};
|
|
|
|
const onDragEnd = () => {
|
|
globalDraggedElement = null;
|
|
};
|
|
|
|
handleEl.addEventListener('mouseenter', onMouseEnter);
|
|
handleEl.addEventListener('mouseleave', onMouseLeave);
|
|
if (handle) {
|
|
el.addEventListener('mouseenter', onMouseLeave);
|
|
}
|
|
el.addEventListener('dragstart', onDragStart);
|
|
el.addEventListener('dragenter', onDragEnter);
|
|
el.addEventListener('dragover', onDragOver);
|
|
el.addEventListener('drop', onDrop);
|
|
el.addEventListener('dragend', onDragEnd);
|
|
|
|
return () => {
|
|
handleEl.removeEventListener('mouseenter', onMouseEnter);
|
|
handleEl.removeEventListener('mouseleave', onMouseLeave);
|
|
if (handle) {
|
|
el.removeEventListener('mouseenter', onMouseLeave);
|
|
}
|
|
el.removeEventListener('dragstart', onDragStart);
|
|
el.removeEventListener('dragenter', onDragEnter);
|
|
el.removeEventListener('dragover', onDragOver);
|
|
el.removeEventListener('drop', onDrop);
|
|
el.removeEventListener('dragend', onDragEnd);
|
|
};
|
|
};
|