fluid-dnd
Version:
An agnostic drag and drop library to sort all kind of lists. With current support for vue, react and svelte
208 lines (207 loc) • 10.7 kB
JavaScript
import { draggableIsOutside, getAxisValue, getBefore, getDistanceValue, getRect, getSiblings, getTransform, getWindowScroll, isSameNode } from '../../utils/GetStyles';
import { moveTranslate, removeTranslateWhitoutTransition } from '../../utils/SetStyles';
import getTranslationByDragging from './getTranslationByDraggingAndEvent';
import getTranslateBeforeDropping from './getTranslateBeforeDropping';
import { DRAG_EVENT, NONE_TRANSLATE, START_DRAG_EVENT, START_DROP_EVENT, TEMP_CHILD_CLASS } from '../../utils';
import { IsHTMLElement } from '../../utils/typesCheckers';
import { removeTempChild } from '../../tempChildren';
import { DRAGGABLE_CLASS, DROPPING_CLASS } from '../../utils/classes';
import { addClass, containClass, getClassesSelector, removeClass } from '../../utils/dom/classList';
import { useChangeDraggableStyles } from '../changeDraggableStyles';
const DELAY_TIME_TO_SWAP = 50;
export default function useDragAndDropEvents(currentConfig, index, parent, droppableGroupClass, handlerPublisher, endDraggingAction) {
let actualIndex = index;
const { onRemoveAtEvent, animationDuration, draggingClass } = currentConfig;
const [removeElementDraggingStyles, toggleDraggingClass, dragEventOverElement] = useChangeDraggableStyles(currentConfig, handlerPublisher, endDraggingAction);
const emitDraggingEvent = (draggedElement, event, droppable, config) => {
const tranlation = getTranslationByDragging(draggedElement, event, config.direction, droppable);
emitDraggingEventToSiblings(draggedElement, event, tranlation, droppable, config);
};
const emitDroppingEvent = (draggedElement, event, droppableConfig, initialWindowScroll, positionOnSourceDroppable) => {
if (!droppableConfig) {
return;
}
const { droppable, scroll, config } = droppableConfig;
const tranlation = getTranslationByDragging(draggedElement, event, config.direction, droppable);
emitDroppingEventToSiblings(draggedElement, event, tranlation, initialWindowScroll, droppable, scroll, config, positionOnSourceDroppable);
};
// #region Drag events
const emitDraggingEventToSiblings = (draggedElement, event, translation, droppable, config) => {
const [siblings] = getSiblings(draggedElement, droppable);
const isOutside = draggableIsOutside(draggedElement, droppable);
const { direction, onDragOver } = config;
onDragOver({
element: draggedElement,
index,
targetIndex: actualIndex,
value: currentConfig.onGetValue(index),
droppable
});
if (siblings.length == 0) {
updateActualIndexBaseOnTranslation(translation, 1, direction, siblings);
}
for (const [index, sibling] of siblings.entries()) {
if (!containClass(sibling, DRAGGABLE_CLASS)) {
continue;
}
const siblingTransition = canChangeDraggable(direction, draggedElement, sibling, translation);
if (!isOutside && siblingTransition) {
translation = siblingTransition;
}
else if (!isOutside) {
continue;
}
const siblingRealIndex = siblings.length - index;
updateActualIndexBaseOnTranslation(translation, siblingRealIndex, direction, siblings);
if (event === START_DRAG_EVENT) {
moveTranslate(sibling, translation);
}
else if (event === DRAG_EVENT) {
dragEventOverElement(sibling, translation);
}
}
};
const canChangeDraggable = (direction, sourceElement, targetElement, translation) => {
const currentBoundingClientRect = getRect(sourceElement);
const targetBoundingClientRect = getRect(targetElement);
const currentPosition = getBefore(direction, currentBoundingClientRect);
const targetPosition = getBefore(direction, targetBoundingClientRect);
const [targetSize] = getDistanceValue(direction, targetBoundingClientRect);
const targetMiddle = targetPosition + targetSize / 2;
const targetTransform = getAxisValue(direction, getTransform(targetElement));
const targetMiddleWithoutTransform = targetMiddle - targetTransform;
if (currentPosition > targetMiddleWithoutTransform) {
return NONE_TRANSLATE;
}
return translation;
};
const updateActualIndexBaseOnTranslation = (translation, siblingIndex, direction, siblings) => {
const itemsCount = siblings.filter((sibling) => containClass(sibling, DRAGGABLE_CLASS)).length;
const [distance] = getDistanceValue(direction, translation);
if (distance == 0) {
actualIndex = Math.max(actualIndex, siblingIndex);
}
else {
actualIndex = Math.min(actualIndex, siblingIndex - 1);
}
actualIndex = Math.min(actualIndex, itemsCount);
};
// #region Drop events
const emitDroppingEventToSiblings = (draggedElement, event, translation, initialWindowScroll, droppable, scroll, config, positionOnSourceDroppable) => {
const [siblings, positionOnDroppable] = getSiblings(draggedElement, droppable);
const allSiblings = siblings.toReversed();
const realPositionOnDroppable = positionOnDroppable === -1 ? allSiblings.length : positionOnDroppable;
allSiblings.splice(realPositionOnDroppable, 0, draggedElement);
const [previousElement, nextElement, targetIndex] = getPreviousAndNextElement(draggedElement, positionOnDroppable, allSiblings, droppable);
translation = getTranslationByDragging(draggedElement, event, config.direction, parent, previousElement, nextElement);
const windowScroll = getWindowScroll();
const draggableTranslation = getTranslateBeforeDropping(config.direction, allSiblings, positionOnDroppable, targetIndex, windowScroll, scroll, initialWindowScroll, droppable, draggedElement);
if (siblings.length == 0) {
startDropEventOverElement(undefined, translation, draggedElement, draggableTranslation);
}
for (const [index, sibling] of siblings.toReversed().entries()) {
let newTranslation = translation;
if (targetIndex - 1 >= index) {
newTranslation = NONE_TRANSLATE;
}
if (event === START_DROP_EVENT && !containClass(sibling, TEMP_CHILD_CLASS)) {
startDropEventOverElement(sibling, newTranslation, draggedElement, draggableTranslation);
}
}
dropEventOverElement(targetIndex, draggedElement, config, droppable, positionOnSourceDroppable);
};
const getPreviousAndNextElement = (draggedElement, elementPosition, allSiblings, droppable) => {
const isOutside = draggableIsOutside(draggedElement, droppable);
const targetIndex = isOutside ? elementPosition : actualIndex;
const getPreviousAndNextElementIndex = () => {
if (elementPosition < targetIndex) {
return [targetIndex, targetIndex + 1];
}
else if (elementPosition > targetIndex) {
return [targetIndex - 1, targetIndex];
}
else {
return [targetIndex - 1, targetIndex + 1];
}
};
const [previousIndex, nextIndex] = getPreviousAndNextElementIndex();
const previousElement = allSiblings[previousIndex] ?? null;
const nextElement = allSiblings[nextIndex] ?? null;
return [previousElement, nextElement, targetIndex];
};
const startDropEventOverElement = (targetElement, translation, element, sourceElementTranlation) => {
moveTranslate(targetElement, translation);
moveTranslate(element, sourceElementTranlation);
};
const dropEventOverElement = (targetIndex, element, config, droppable, positionOnSourceDroppable) => {
const { onInsertEvent, onDragEnd } = config;
addClass(element, DROPPING_CLASS);
removeStytes(element, parent, droppable, () => {
removeClass(element, DROPPING_CLASS);
if (positionOnSourceDroppable != undefined) {
const value = onRemoveAtEvent(positionOnSourceDroppable, true);
if (value != undefined) {
onInsertEvent(targetIndex, value, true);
}
manageDraggingClass(element, () => {
(value != undefined) && onDragEnd({ value, index: targetIndex });
});
clearExcessTranslateStyles();
}
});
};
const clearExcessTranslateStyles = () => {
if (!droppableGroupClass) {
return;
}
var children = document.querySelectorAll(`${getClassesSelector(droppableGroupClass)} > .${DRAGGABLE_CLASS}`);
for (const element of children) {
removeTranslateWhitoutTransition(element);
}
};
const manageDraggingClass = (element, func) => {
setTimeout(() => {
removeClass(element, draggingClass);
func && func();
}, DELAY_TIME_TO_SWAP);
};
const removeStytes = (element, parent, droppable, func) => {
setTimeout(() => {
func && func();
removeTempChildOnDroppables(parent, droppable);
reduceTempchildSize(droppable);
removeElementDraggingStyles(element);
removeTranslateFromSiblings(element, parent);
removeTranslateFromSiblings(element, droppable);
}, animationDuration);
};
const removeTempChildOnDroppables = (parent, droppable) => {
if (isSameNode(parent, droppable)) {
removeTempChild(parent, animationDuration);
}
else {
removeTempChild(parent, animationDuration, true);
removeTempChild(droppable, animationDuration);
}
};
const reduceTempchildSize = (droppable) => {
if (isSameNode(parent, droppable)) {
return;
}
var [lastChildren] = parent.querySelectorAll(`.${TEMP_CHILD_CLASS}`);
if (!lastChildren) {
return;
}
if (IsHTMLElement(lastChildren)) {
lastChildren.style.height = '0px';
lastChildren.style.width = '0px';
}
};
const removeTranslateFromSiblings = (element, parent) => {
const [siblings] = getSiblings(element, parent);
for (const sibling of [...siblings, element]) {
removeTranslateWhitoutTransition(sibling);
}
};
return [emitDraggingEvent, emitDroppingEvent, toggleDraggingClass];
}