fluid-dnd
Version:
An agnostic drag and drop library to sort all kind of lists. With current support for vue, react and svelte
202 lines (201 loc) • 7.74 kB
JavaScript
import { HORIZONTAL, VERTICAL } from '..';
import { DRAGGABLE_CLASS } from './classes';
import { containClass } from './dom/classList';
export const getWindowScroll = () => {
const { scrollX, scrollY } = window;
return { scrollX, scrollY };
};
export const parseFloatEmpty = (value) => {
if (!value || value.trim().length == 0 || value == 'normal') {
return 0;
}
return parseFloat(value);
};
export const parseIntEmpty = (value) => {
if (!value) {
return -1;
}
return parseInt(value);
};
export const getTransform = (element) => {
const style = getComputedStyle(element);
const matrix = new DOMMatrixReadOnly(style.transform);
return {
x: matrix.m41,
y: matrix.m42
};
};
const intersection = (firstInterval, secondInterval) => {
if (firstInterval.x1 > secondInterval.x1) {
return intersection(secondInterval, firstInterval);
}
if (firstInterval.x2 < secondInterval.x1) {
return 0;
}
if (firstInterval.x2 >= secondInterval.x2) {
return firstInterval.x2 - firstInterval.x1;
}
return firstInterval.x2 - secondInterval.x1;
};
export const draggableIsOutside = (draggable, droppable) => {
return !hasIntersection(draggable, droppable);
};
const hasIntersection = (element1, element2) => {
const { intersectionX, intersectionY, rect1, rect2 } = getIntersection(element1, element2);
return (intersectionY >= Math.min(rect1.height, rect2.height) / 2 &&
intersectionX >= Math.min(rect1.width, rect2.width) / 2);
};
export const draggableIsCompleteOutside = (draggable, droppable) => {
return !hasCompleteIntersection(draggable, droppable);
};
const hasCompleteIntersection = (element1, element2) => {
const { intersectionX, intersectionY } = getIntersection(element1, element2);
return intersectionY >= 0 && intersectionX >= 0;
};
const getIntersection = (element1, element2) => {
const rect1 = getRect(element1);
const rect2 = getRect(element2);
const intersectionY = intersectionByDirection(rect1, rect2, VERTICAL);
const intersectionX = intersectionByDirection(rect1, rect2, HORIZONTAL);
return {
rect1,
rect2,
intersectionX,
intersectionY
};
};
const intersectionByDirection = (rect1, rect2, direction) => {
const { before, distance } = getPropByDirection(direction);
return intersection({
x1: rect1[before],
x2: rect1[before] + rect1[distance]
}, {
x1: rect2[before],
x2: rect2[before] + rect2[distance]
});
};
export const getValueFromProperty = (element, property) => {
if (element) {
return parseFloatEmpty(getComputedStyle(element)[property]);
}
return 0;
};
export const getScrollElement = (element) => {
const { scrollLeft, scrollTop } = element;
return { scrollLeft, scrollTop };
};
export const getRect = (element) => {
return element.getBoundingClientRect();
};
export const getDistanceValue = (direction, translation) => {
const { distance } = getPropByDirection(direction);
return [translation[distance], distance];
};
export const getAxisValue = (direction, coordinate) => {
const { axis } = getPropByDirection(direction);
return coordinate[axis];
};
export const getBorderBeforeWidthValue = (direction, element) => {
const { borderBeforeWidth } = getPropByDirection(direction);
return getValueFromProperty(element, borderBeforeWidth);
};
export const getBeforeMarginValue = (direction, element) => {
const { beforeMargin } = getPropByDirection(direction);
return getValueFromProperty(element, beforeMargin);
};
export const getBeforeMargin = (direction, element) => {
const { beforeMargin } = getPropByDirection(direction);
return getValueFromProperty(element, beforeMargin);
};
export const getAfterMargin = (direction, element) => {
const { afterMargin } = getPropByDirection(direction);
return getValueFromProperty(element, afterMargin);
};
export const getBefore = (direction, elementPosition) => {
const { before } = getPropByDirection(direction);
return elementPosition[before];
};
export const getScrollElementValue = (direction, element) => {
const { scrollElement } = getPropByDirection(direction);
return [element[scrollElement], scrollElement];
};
export const getScrollValue = (direction, elementScroll) => {
const { scroll } = getPropByDirection(direction);
return elementScroll[scroll];
};
export const getInnerDistance = (direction, innerElement) => {
const { inner } = getPropByDirection(direction);
return innerElement[inner];
};
export const getPageValue = (direction, event) => {
const { page } = getPropByDirection(direction);
return event[page];
};
export const getOffsetValue = (direction, event) => {
const { offset } = getPropByDirection(direction);
return event[offset];
};
export const getPropByDirection = (direction) => {
const ifHorizontal = direction == HORIZONTAL;
return {
beforeMargin: ifHorizontal ? 'marginLeft' : 'marginTop',
afterMargin: ifHorizontal ? 'marginRight' : 'marginBottom',
borderBeforeWidth: ifHorizontal ? 'borderLeftWidth' : 'borderTopWidth',
before: ifHorizontal ? 'left' : 'top',
gap: ifHorizontal ? 'columnGap' : 'rowGap',
distance: ifHorizontal ? 'width' : 'height',
axis: ifHorizontal ? 'x' : 'y',
offset: ifHorizontal ? 'offsetX' : 'offsetY',
scroll: ifHorizontal ? 'scrollX' : 'scrollY',
scrollElement: ifHorizontal ? 'scrollLeft' : 'scrollTop',
page: ifHorizontal ? 'pageX' : 'pageY',
inner: ifHorizontal ? 'innerWidth' : 'innerHeight',
scrollDistance: ifHorizontal ? 'scrollWidth' : 'scrollHeight',
clientDistance: ifHorizontal ? 'clientWidth' : 'clientHeight',
paddingBefore: ifHorizontal ? 'paddingLeft' : 'paddingTop',
getRect
};
};
export const getSiblings = (current, parent) => {
return getSiblingsByParent(current, parent);
};
export const getGroupDroppables = (currentDroppable, droppableGroup) => {
if (!droppableGroup) {
return [currentDroppable];
}
return Array.from(document.querySelectorAll(`.droppable-group-${droppableGroup}`));
};
export const getParentDraggableChildren = (parent) => {
const siblings = [...parent.children].filter((child) => containClass(child, DRAGGABLE_CLASS));
return siblings;
};
export const getSiblingsByParent = (current, parent) => {
const siblings = [...parent.children]
.filter((child) => containClass(child, DRAGGABLE_CLASS) && !child.isEqualNode(current))
.toReversed();
const positionOnDroppable = [...parent.children].findLastIndex((child) => child.isEqualNode(current));
return [siblings, positionOnDroppable, parent];
};
const getNearestParentWithTranslate = (element) => {
let parent = element.parentElement;
while (parent) {
const computedStyles = window.getComputedStyle(parent);
if (computedStyles.translate !== 'none' || computedStyles.willChange === 'transform') {
return parent;
}
parent = parent.parentElement;
}
return null; // No fixed parent found
};
export const getNearestParentPositionWithTranslate = (element, direction) => {
const fixedParent = getNearestParentWithTranslate(element);
return fixedParent
? getBefore(direction, getRect(fixedParent)) + getBorderBeforeWidthValue(direction, fixedParent)
: 0;
};
export const isSameNode = (element1, element2) => {
return element1?.isSameNode(element2);
};
export const hasTransform = (element) => {
return element && getComputedStyle(element).transform !== 'none';
};