- Remove unused packages

- Consume the kafika-smooth-dnd lib as a sub dir under trello
- update above code to fix any linting errors

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-05-31 11:31:55 -04:00
parent d85768b2ac
commit c7b8df5655
15 changed files with 2920 additions and 8967 deletions

9563
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,6 @@
"i18next": "^23.11.5",
"i18next-browser-languagedetector": "^7.2.1",
"immutability-helper": "^3.1.1",
"kuika-smooth-dnd": "^1.0.0",
"libphonenumber-js": "^1.11.2",
"logrocket": "^8.1.0",
"markerjs2": "^2.32.1",
@@ -62,7 +61,6 @@
"react-redux": "^9.1.2",
"react-resizable": "^3.0.5",
"react-router-dom": "^6.23.1",
"react-scripts": "^5.0.1",
"react-sticky": "^6.0.3",
"react-virtualized": "^9.22.5",
"recharts": "^2.12.7",
@@ -76,16 +74,9 @@
"socket.io-client": "^4.7.5",
"styled-components": "^6.1.11",
"subscriptions-transport-ws": "^0.11.0",
"terser-webpack-plugin": "^5.3.10",
"userpilot": "^1.3.1",
"vite-plugin-ejs": "^1.7.0",
"web-vitals": "^3.5.2",
"workbox-core": "^7.1.0",
"workbox-expiration": "^7.1.0",
"workbox-navigation-preload": "^7.1.0",
"workbox-precaching": "^7.1.0",
"workbox-routing": "^7.1.0",
"workbox-strategies": "^7.1.0"
"web-vitals": "^3.5.2"
},
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",

View File

@@ -1,6 +1,6 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import container, { dropHandlers } from "kuika-smooth-dnd";
import container, { dropHandlers } from "../smooth-dnd";
container.dropHandler = dropHandlers.reactDropHandler().handler;
container.wrapChild = (p) => p; // don't wrap children they will already be wrapped

View File

@@ -1,7 +1,6 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { constants } from "kuika-smooth-dnd";
import { constants } from "../smooth-dnd";
const { wrapperClass } = constants;
class Draggable extends Component {

View File

@@ -0,0 +1,8 @@
import container from './src/container';
import * as constants from './src/constants';
import * as dropHandlers from './src/dropHandlers';
export default container;
export {
constants,
dropHandlers,
};

View File

@@ -0,0 +1,21 @@
export const containerInstance = 'smooth-dnd-container-instance';
export const containersInDraggable = 'smooth-dnd-containers-in-draggable';
export const defaultGroupName = '@@smooth-dnd-default-group@@';
export const wrapperClass = 'smooth-dnd-draggable-wrapper';
export const defaultGrabHandleClass = 'smooth-dnd-default-grap-handle';
export const animationClass = 'animated';
export const translationValue = '__smooth_dnd_draggable_translation_value';
export const visibilityValue = '__smooth_dnd_draggable_visibility_value';
export const ghostClass = 'smooth-dnd-ghost';
export const containerClass = 'smooth-dnd-container';
export const extraSizeForInsertion = 'smooth-dnd-extra-size-for-insertion';
export const stretcherElementClass = 'smooth-dnd-stretcher-element';
export const stretcherElementInstance = 'smooth-dnd-stretcher-instance';
export const isDraggableDetached = 'smoth-dnd-is-draggable-detached';
export const disbaleTouchActions = 'smooth-dnd-disable-touch-action';
export const noUserSelectClass = 'smooth-dnd-no-user-select';

View File

@@ -0,0 +1,61 @@
.smooth-dnd-container *{
box-sizing: border-box;
}
.smooth-dnd-disable-touch-action{
touch-action: none;
}
.smooth-dnd-container{
position: relative;
}
.smooth-dnd-container.vertical{
}
.smooth-dnd-container.horizontal{
white-space: nowrap;
}
.smooth-dnd-container.horizontal .smooth-dnd-draggable-wrapper{
height: 100%;
display: inline-block;
}
.smooth-dnd-draggable-wrapper {
overflow: hidden;
}
.smooth-dnd-draggable-wrapper.animated{
transition: transform ease;
}
.smooth-dnd-ghost {
}
.smooth-dnd-ghost *{
box-sizing: border-box;
}
.smooth-dnd-ghost.animated{
transition: all ease-in-out;
}
/* .smooth-dnd-no-user-select{
user-select: none;
}
.smooth-dnd-stretcher-element{
background-color: transparent;
}
.smooth-dnd-stretcher-element.vertical{
height: 1px;
}
.smooth-dnd-stretcher-element.horizontal{
height: 100%;
display: inline-block;
} */

View File

@@ -0,0 +1,777 @@
import Mediator from './mediator';
import layoutManager from './layoutManager';
import { hasClass, addClass, removeClass, getParent } from './utils';
import { domDropHandler } from './dropHandlers';
import {
defaultGroupName,
wrapperClass,
animationClass,
stretcherElementClass,
stretcherElementInstance,
translationValue,
containerClass,
containerInstance,
containersInDraggable
} from './constants';
const defaultOptions = {
groupName: null,
behaviour: 'move', // move | copy
orientation: 'vertical', // vertical | horizontal
getChildPayload: null,
animationDuration: 250,
autoScrollEnabled: true,
shouldAcceptDrop: null,
shouldAnimateDrop: null
};
function setAnimation(element, add, animationDuration) {
if (add) {
addClass(element, animationClass);
element.style.transitionDuration = animationDuration + 'ms';
} else {
removeClass(element, animationClass);
element.style.removeProperty('transition-duration');
}
}
function getContainer(element) {
return element ? element[containerInstance] : null;
}
function initOptions(props = defaultOptions) {
return Object.assign({}, defaultOptions, props);
}
function isDragRelevant({ element, options }) {
return function(sourceContainer, payload) {
if (options.shouldAcceptDrop) {
return options.shouldAcceptDrop(sourceContainer.getOptions(), payload);
}
const sourceOptions = sourceContainer.getOptions();
if (options.behaviour === 'copy') return false;
const parentWrapper = getParent(element, '.' + wrapperClass);
if (parentWrapper === sourceContainer.element) {
return false;
}
if (sourceContainer.element === element) return true;
if (sourceOptions.groupName && sourceOptions.groupName === options.groupName) return true;
return false;
};
}
function wrapChild(child) {
if (SmoothDnD.wrapChild) {
return SmoothDnD.wrapChild(child);
}
const div = global.document.createElement('div');
div.className = `${wrapperClass}`;
child.parentElement.insertBefore(div, child);
div.appendChild(child);
return div;
}
function wrapChildren(element) {
const draggables = [];
Array.prototype.map.call(element.children, child => {
if (child.nodeType === Node.ELEMENT_NODE) {
let wrapper = child;
if (!hasClass(child, wrapperClass)) {
wrapper = wrapChild(child);
}
wrapper[containersInDraggable] = [];
wrapper[translationValue] = 0;
draggables.push(wrapper);
} else {
if (typeof element.removeChild === "function") {
element.removeChild(child);
}
}
});
return draggables;
}
function unwrapChildren(element) {
Array.prototype.map.call(element.children, child => {
if (child.nodeType === Node.ELEMENT_NODE) {
let wrapper = child;
if (hasClass(child, wrapperClass)) {
element.insertBefore(wrapper, wrapChild.firstElementChild);
element.removeChild(wrapper);
}
}
});
}
function findDraggebleAtPos({ layout }) {
const find = (draggables, pos, startIndex, endIndex, withRespectToMiddlePoints = false) => {
if (endIndex < startIndex) {
return startIndex;
}
// binary serach draggable
if (startIndex === endIndex) {
let { begin, end } = layout.getBeginEnd(draggables[startIndex]);
// mouse pos is inside draggable
// now decide which index to return
if (pos > begin && pos <= end) {
if (withRespectToMiddlePoints) {
return pos < (end + begin) / 2 ? startIndex : startIndex + 1;
} else {
return startIndex;
}
} else {
return null;
}
} else {
const middleIndex = Math.floor((endIndex + startIndex) / 2);
const { begin, end } = layout.getBeginEnd(draggables[middleIndex]);
if (pos < begin) {
return find(draggables, pos, startIndex, middleIndex - 1, withRespectToMiddlePoints);
} else if (pos > end) {
return find(draggables, pos, middleIndex + 1, endIndex, withRespectToMiddlePoints);
} else {
if (withRespectToMiddlePoints) {
return pos < (end + begin) / 2 ? middleIndex : middleIndex + 1;
} else {
return middleIndex;
}
}
}
};
return (draggables, pos, withRespectToMiddlePoints = false) => {
return find(draggables, pos, 0, draggables.length - 1, withRespectToMiddlePoints);
};
}
function resetDraggables({ element, draggables, layout, options }) {
return function() {
draggables.forEach(p => {
setAnimation(p, false);
layout.setTranslation(p, 0);
layout.setVisibility(p, true);
p[containersInDraggable] = [];
});
if (element[stretcherElementInstance]) {
element[stretcherElementInstance].parentNode.removeChild(element[stretcherElementInstance]);
element[stretcherElementInstance] = null;
}
};
}
function setTargetContainer(draggableInfo, element, set = true) {
if (element && set) {
draggableInfo.targetElement = element;
} else {
if (draggableInfo.targetElement === element) {
draggableInfo.targetElement = null;
}
}
}
function handleDrop({ element, draggables, layout, options }) {
const draggablesReset = resetDraggables({ element, draggables, layout, options });
const dropHandler = (SmoothDnD.dropHandler || domDropHandler)({ element, draggables, layout, options });
return function(draggableInfo, { addedIndex, removedIndex }) {
draggablesReset();
// if drop zone is valid => complete drag else do nothing everything will be reverted by draggablesReset()
if (draggableInfo.targetElement || options.removeOnDropOut) {
let actualAddIndex =
addedIndex !== null ? (removedIndex !== null && removedIndex < addedIndex ? addedIndex - 1 : addedIndex) : null;
const dropHandlerParams = {
removedIndex,
addedIndex: actualAddIndex,
payload: draggableInfo.payload,
droppedElement: draggableInfo.element.firstElementChild
};
dropHandler(dropHandlerParams, options.onDrop);
}
};
}
function getContainerProps(element, initialOptions) {
const options = initOptions(initialOptions);
const draggables = wrapChildren(element, options.orientation, options.animationDuration);
// set flex classes before layout is inited for scroll listener
addClass(element, `${containerClass} ${options.orientation}`);
const layout = layoutManager(element, options.orientation, options.animationDuration);
return {
element,
draggables,
options,
layout
};
}
function getRelaventParentContainer(container, relevantContainers) {
let current = container.element;
while (current) {
const containerOfParentElement = getContainer(current.parentElement);
if (containerOfParentElement && relevantContainers.indexOf(containerOfParentElement) > -1) {
return {
container: containerOfParentElement,
draggable: current
};
}
current = current.parentElement;
}
return null;
}
function registerToParentContainer(container, relevantContainers) {
const parentInfo = getRelaventParentContainer(container, relevantContainers);
if (parentInfo) {
parentInfo.container.getChildContainers().push(container);
container.setParentContainer(parentInfo.container);
//current should be draggable
parentInfo.draggable[containersInDraggable].push(container);
}
}
function getRemovedItem({ draggables, element, options }) {
let prevRemovedIndex = null;
return ({ draggableInfo, dragResult }) => {
let removedIndex = prevRemovedIndex;
if (prevRemovedIndex == null && draggableInfo.container.element === element && options.behaviour !== 'copy') {
removedIndex = prevRemovedIndex = draggableInfo.elementIndex;
}
return { removedIndex };
};
}
function setRemovedItemVisibilty({ draggables, layout }) {
return ({ draggableInfo, dragResult }) => {
if (dragResult.removedIndex !== null) {
layout.setVisibility(draggables[dragResult.removedIndex], false);
}
};
}
function getPosition({ element, layout }) {
return ({ draggableInfo }) => {
return {
pos: !getContainer(element).isPosInChildContainer() ? layout.getPosition(draggableInfo.position) : null
};
};
}
function notifyParentOnPositionCapture({ element }) {
let isCaptured = false;
return ({ draggableInfo, dragResult }) => {
if (getContainer(element).getParentContainer() && isCaptured !== (dragResult.pos !== null)) {
isCaptured = dragResult.pos !== null;
getContainer(element)
.getParentContainer()
.onChildPositionCaptured(isCaptured);
}
};
}
function getElementSize({ layout }) {
let elementSize = null;
return ({ draggableInfo, dragResult }) => {
if (dragResult.pos === null) {
return (elementSize = null);
} else {
elementSize = elementSize || layout.getSize(draggableInfo.element);
}
return { elementSize };
};
}
function handleTargetContainer({ element }) {
return ({ draggableInfo, dragResult }) => {
setTargetContainer(draggableInfo, element, !!dragResult.pos);
};
}
function getDragInsertionIndex({ draggables, layout }) {
const findDraggable = findDraggebleAtPos({ layout });
return ({ dragResult: { shadowBeginEnd, pos } }) => {
if (!shadowBeginEnd) {
const index = findDraggable(draggables, pos, true);
return index !== null ? index : draggables.length;
} else {
if (shadowBeginEnd.begin + shadowBeginEnd.beginAdjustment <= pos && shadowBeginEnd.end >= pos) {
// position inside ghost
return null;
}
}
if (pos < shadowBeginEnd.begin + shadowBeginEnd.beginAdjustment) {
return findDraggable(draggables, pos);
} else if (pos > shadowBeginEnd.end) {
return findDraggable(draggables, pos) + 1;
} else {
return draggables.length;
}
};
}
function getDragInsertionIndexForDropZone({ draggables, layout }) {
return ({ dragResult: { pos } }) => {
return pos !== null ? { addedIndex: 0 } : { addedIndex: null };
};
}
function getShadowBeginEndForDropZone({ draggables, layout }) {
let prevAddedIndex = null;
return ({ dragResult: { addedIndex } }) => {
if (addedIndex !== prevAddedIndex) {
prevAddedIndex = addedIndex;
const { begin, end } = layout.getBeginEndOfContainer();
return {
shadowBeginEnd: {
rect: layout.getTopLeftOfElementBegin(begin, end)
}
};
}
};
}
function invalidateShadowBeginEndIfNeeded(params) {
const shadowBoundsGetter = getShadowBeginEnd(params);
return ({ draggableInfo, dragResult }) => {
if (draggableInfo.invalidateShadow) {
return shadowBoundsGetter({ draggableInfo, dragResult });
}
return null;
};
}
function getNextAddedIndex(params) {
const getIndexForPos = getDragInsertionIndex(params);
return ({ dragResult }) => {
let index = null;
if (dragResult.pos !== null) {
index = getIndexForPos({ dragResult });
if (index === null) {
index = dragResult.addedIndex;
}
}
return {
addedIndex: index
};
};
}
function resetShadowAdjustment() {
let lastAddedIndex = null;
return ({ dragResult: { addedIndex, shadowBeginEnd } }) => {
if (addedIndex !== lastAddedIndex && lastAddedIndex !== null && shadowBeginEnd) {
shadowBeginEnd.beginAdjustment = 0;
}
lastAddedIndex = addedIndex;
};
}
function handleInsertionSizeChange({ element, draggables, layout, options }) {
let strectherElement = null;
return function({ dragResult: { addedIndex, removedIndex, elementSize } }) {
if (removedIndex === null) {
if (addedIndex !== null) {
if (!strectherElement) {
const containerBeginEnd = layout.getBeginEndOfContainer();
containerBeginEnd.end = containerBeginEnd.begin + layout.getSize(element);
const hasScrollBar = layout.getScrollSize(element) > layout.getSize(element);
const containerEnd = hasScrollBar
? containerBeginEnd.begin + layout.getScrollSize(element) - layout.getScrollValue(element)
: containerBeginEnd.end;
const lastDraggableEnd =
draggables.length > 0
? layout.getBeginEnd(draggables[draggables.length - 1]).end -
draggables[draggables.length - 1][translationValue]
: containerBeginEnd.begin;
if (lastDraggableEnd + elementSize > containerEnd) {
strectherElement = global.document.createElement('div');
strectherElement.className = stretcherElementClass + ' ' + options.orientation;
const stretcherSize = elementSize + lastDraggableEnd - containerEnd;
layout.setSize(strectherElement.style, `${stretcherSize}px`);
element.appendChild(strectherElement);
element[stretcherElementInstance] = strectherElement;
return {
containerBoxChanged: true
};
}
}
} else {
if (strectherElement) {
layout.setTranslation(strectherElement, 0);
let toRemove = strectherElement;
strectherElement = null;
element.removeChild(toRemove);
element[stretcherElementInstance] = null;
return {
containerBoxChanged: true
};
}
}
}
};
}
function calculateTranslations({ element, draggables, layout }) {
let prevAddedIndex = null;
let prevRemovedIndex = null;
return function({ dragResult: { addedIndex, removedIndex, elementSize } }) {
if (addedIndex !== prevAddedIndex || removedIndex !== prevRemovedIndex) {
for (let index = 0; index < draggables.length; index++) {
if (index !== removedIndex) {
const draggable = draggables[index];
let translate = 0;
if (removedIndex !== null && removedIndex < index) {
translate -= layout.getSize(draggables[removedIndex]);
}
if (addedIndex !== null && addedIndex <= index) {
translate += elementSize;
}
layout.setTranslation(draggable, translate);
}
}
prevAddedIndex = addedIndex;
prevRemovedIndex = removedIndex;
return { addedIndex, removedIndex };
}
};
}
function getShadowBeginEnd({ draggables, layout }) {
let prevAddedIndex = null;
return ({ draggableInfo, dragResult }) => {
const { addedIndex, removedIndex, elementSize, pos, shadowBeginEnd } = dragResult;
if (pos !== null) {
if (addedIndex !== null && (draggableInfo.invalidateShadow || addedIndex !== prevAddedIndex)) {
if (prevAddedIndex) prevAddedIndex = addedIndex;
let beforeIndex = addedIndex - 1;
let begin = 0;
let afterBounds = null;
let beforeBounds = null;
if (beforeIndex === removedIndex) {
beforeIndex--;
}
if (beforeIndex > -1) {
const beforeSize = layout.getSize(draggables[beforeIndex]);
beforeBounds = layout.getBeginEnd(draggables[beforeIndex]);
if (elementSize < beforeSize) {
const threshold = (beforeSize - elementSize) / 2;
begin = beforeBounds.end - threshold;
} else {
begin = beforeBounds.end;
}
} else {
beforeBounds = { end: layout.getBeginEndOfContainer().begin };
}
let end = 10000;
let afterIndex = addedIndex;
if (afterIndex === removedIndex) {
afterIndex++;
}
if (afterIndex < draggables.length) {
const afterSize = layout.getSize(draggables[afterIndex]);
afterBounds = layout.getBeginEnd(draggables[afterIndex]);
if (elementSize < afterSize) {
const threshold = (afterSize - elementSize) / 2;
end = afterBounds.begin + threshold;
} else {
end = afterBounds.begin;
}
} else {
afterBounds = { begin: layout.getContainerRectangles().end };
}
const shadowRectTopLeft =
beforeBounds && afterBounds ? layout.getTopLeftOfElementBegin(beforeBounds.end, afterBounds.begin) : null;
return {
shadowBeginEnd: {
begin,
end,
rect: shadowRectTopLeft,
beginAdjustment: shadowBeginEnd ? shadowBeginEnd.beginAdjustment : 0
}
};
} else {
return null;
}
} else {
prevAddedIndex = null;
return {
shadowBeginEnd: null
};
}
};
}
function handleFirstInsertShadowAdjustment() {
let lastAddedIndex = null;
return ({ dragResult: { pos, addedIndex, shadowBeginEnd }, draggableInfo: { invalidateShadow } }) => {
if (pos !== null) {
if (addedIndex != null && lastAddedIndex === null) {
if (pos < shadowBeginEnd.begin) {
const beginAdjustment = pos - shadowBeginEnd.begin - 5;
shadowBeginEnd.beginAdjustment = beginAdjustment;
}
lastAddedIndex = addedIndex;
}
} else {
lastAddedIndex = null;
}
};
}
function fireDragEnterLeaveEvents({ options }) {
let wasDragIn = false;
return ({ dragResult: { pos } }) => {
const isDragIn = !!pos;
if (isDragIn !== wasDragIn) {
wasDragIn = isDragIn;
if (isDragIn) {
options.onDragEnter && options.onDragEnter();
} else {
options.onDragLeave && options.onDragLeave();
return {
dragLeft: true
};
}
}
};
}
function fireOnDropReady({ options }) {
let lastAddedIndex = null;
return ({ dragResult: { addedIndex, removedIndex }, draggableInfo: { payload, element } }) => {
if (options.onDropReady && lastAddedIndex !== addedIndex) {
lastAddedIndex = addedIndex;
let adjustedAddedIndex = addedIndex;
if (removedIndex !== null && addedIndex > removedIndex) {
adjustedAddedIndex--;
}
options.onDropReady({ addedIndex: adjustedAddedIndex, removedIndex, payload, element: element.firstElementChild });
}
}
}
function getDragHandler(params) {
if (params.options.behaviour === 'drop-zone') {
// sorting is disabled in container, addedIndex will always be 0 if dropped in
return compose(params)(
getRemovedItem,
setRemovedItemVisibilty,
getPosition,
notifyParentOnPositionCapture,
getElementSize,
handleTargetContainer,
getDragInsertionIndexForDropZone,
getShadowBeginEndForDropZone,
fireDragEnterLeaveEvents,
fireOnDropReady
);
} else {
return compose(params)(
getRemovedItem,
setRemovedItemVisibilty,
getPosition,
notifyParentOnPositionCapture,
getElementSize,
handleTargetContainer,
invalidateShadowBeginEndIfNeeded,
getNextAddedIndex,
resetShadowAdjustment,
handleInsertionSizeChange,
calculateTranslations,
getShadowBeginEnd,
handleFirstInsertShadowAdjustment,
fireDragEnterLeaveEvents,
fireOnDropReady
);
}
}
function getDefaultDragResult() {
return {
addedIndex: null,
removedIndex: null,
elementSize: null,
pos: null,
shadowBeginEnd: null
};
}
function compose(params) {
return (...functions) => {
const hydratedFunctions = functions.map(p => p(params));
let result = null;
return draggableInfo => {
result = hydratedFunctions.reduce((dragResult, fn) => {
return Object.assign(dragResult, fn({ draggableInfo, dragResult }));
}, result || getDefaultDragResult());
return result;
};
};
}
// Container definition begin
function Container(element) {
return function(options) {
let dragResult = null;
let lastDraggableInfo = null;
const props = getContainerProps(element, options);
let dragHandler = getDragHandler(props);
let dropHandler = handleDrop(props);
let parentContainer = null;
let posIsInChildContainer = false;
let childContainers = [];
function processLastDraggableInfo() {
if (lastDraggableInfo !== null) {
lastDraggableInfo.invalidateShadow = true;
dragResult = dragHandler(lastDraggableInfo);
lastDraggableInfo.invalidateShadow = false;
}
}
function onChildPositionCaptured(isCaptured) {
posIsInChildContainer = isCaptured;
if (parentContainer) {
parentContainer.onChildPositionCaptured(isCaptured);
if (lastDraggableInfo) {
dragResult = dragHandler(lastDraggableInfo);
}
}
}
function setDraggables(draggables, element, options) {
const newDraggables = wrapChildren(element, options.orientation, options.animationDuration);
for (let i = 0; i < newDraggables.length; i++) {
draggables[i] = newDraggables[i];
}
for (let i = 0; i < draggables.length - newDraggables.length; i++) {
draggables.pop();
}
}
function prepareDrag(container, relevantContainers) {
const element = container.element;
const draggables = props.draggables;
const options = container.getOptions();
setDraggables(draggables, element, options);
container.layout.invalidateRects();
registerToParentContainer(container, relevantContainers);
draggables.forEach(p => setAnimation(p, true, options.animationDuration));
}
props.layout.setScrollListener(function() {
processLastDraggableInfo();
});
function handleDragLeftDeferedTranslation() {
if (dragResult.dragLeft && props.options.behaviour !== 'drop-zone') {
dragResult.dragLeft = false;
setTimeout(() => {
if (dragResult) calculateTranslations(props)({ dragResult });
}, 20);
}
}
function dispose(container) {
unwrapChildren(container.element);
}
return {
element,
draggables: props.draggables,
isDragRelevant: isDragRelevant(props),
getScale: props.layout.getContainerScale,
layout: props.layout,
getChildContainers: () => childContainers,
onChildPositionCaptured,
dispose,
prepareDrag,
isPosInChildContainer: () => posIsInChildContainer,
handleDrag: function(draggableInfo) {
lastDraggableInfo = draggableInfo;
dragResult = dragHandler(draggableInfo);
handleDragLeftDeferedTranslation();
return dragResult;
},
handleDrop: function(draggableInfo) {
lastDraggableInfo = null;
onChildPositionCaptured(false);
dragHandler = getDragHandler(props);
dropHandler(draggableInfo, dragResult);
dragResult = null;
parentContainer = null;
childContainers = [];
},
getDragResult: function() {
return dragResult;
},
getTranslateCalculator: function(...params) {
return calculateTranslations(props)(...params);
},
setParentContainer: e => {
parentContainer = e;
},
getParentContainer: () => parentContainer,
onTranslated: () => {
processLastDraggableInfo();
},
getOptions: () => props.options,
setDraggables: () => {
setDraggables(props.draggables, element, props.options);
}
};
};
}
const options = {
behaviour: 'move',
groupName: 'bla bla', // if not defined => container will not interfere with other containers
orientation: 'vertical',
dragHandleSelector: null,
nonDragAreaSelector: 'some selector',
dragBeginDelay: 0,
animationDuration: 180,
autoScrollEnabled: true,
lockAxis: true,
dragClass: null,
dropClass: null,
onDragStart: (index, payload) => {},
onDrop: ({ removedIndex, addedIndex, payload, element }) => {},
getChildPayload: index => null,
shouldAnimateDrop: (sourceContainerOptions, payload) => true,
shouldAcceptDrop: (sourceContainerOptions, payload) => true,
onDragEnter: () => {},
onDragLeave: () => { },
onDropReady: ({ removedIndex, addedIndex, payload, element }) => { },
};
// exported part of container
function SmoothDnD(element, options) {
const containerIniter = Container(element);
const container = containerIniter(options);
element[containerInstance] = container;
Mediator.register(container);
return {
dispose: function() {
Mediator.unregister(container);
container.layout.dispose();
container.dispose(container);
}
};
}
export default SmoothDnD;

View File

@@ -0,0 +1,208 @@
import { getScrollingAxis, getVisibleRect } from "./utils";
const maxSpeed = 1500; // px/s
// const minSpeed = 20; // px/s
function addScrollValue(element, axis, value) {
if (element) {
if (element !== window) {
if (axis === "x") {
element.scrollLeft += value;
} else {
element.scrollTop += value;
}
} else {
if (axis === "x") {
element.scrollBy(value, 0);
} else {
element.scrollBy(0, value);
}
}
}
}
const createAnimator = (element, axis = "y") => {
let isAnimating = false;
let request = null;
let startTime = null;
let direction = null;
let speed = null;
function animate(_direction, _speed) {
direction = _direction;
speed = _speed;
isAnimating = true;
if (isAnimating) {
start();
}
}
function start() {
if (request === null) {
request = requestAnimationFrame((timestamp) => {
if (startTime === null) {
startTime = timestamp;
}
const timeDiff = timestamp - startTime;
startTime = timestamp;
let distanceDiff = (timeDiff / 1000) * speed;
distanceDiff = direction === "begin" ? 0 - distanceDiff : distanceDiff;
addScrollValue(element, axis, distanceDiff);
request = null;
start();
});
}
}
function stop() {
if (isAnimating) {
cancelAnimationFrame(request);
isAnimating = false;
startTime = null;
request = null;
}
}
return {
animate,
stop
};
};
function getAutoScrollInfo(position, scrollableInfo) {
const { left, right, top, bottom } = scrollableInfo.rect;
const { x, y } = position;
if (x < left || x > right || y < top || y > bottom) {
return null;
}
let begin;
let end;
let pos;
if (scrollableInfo.axis === "x") {
begin = left;
end = right;
pos = x;
} else {
begin = top;
end = bottom;
pos = y;
}
const moveDistance = 100;
if (end - pos < moveDistance) {
return {
direction: "end",
speedFactor: (moveDistance - (end - pos)) / moveDistance
};
} else if (pos - begin < moveDistance) {
// console.log(pos - begin);
return {
direction: "begin",
speedFactor: (moveDistance - (pos - begin)) / moveDistance
};
}
}
function scrollableInfo(element) {
var result = {
element,
rect: getVisibleRect(element, element.getBoundingClientRect()),
descendants: [],
invalidate,
axis: null,
dispose
};
function dispose() {
element.removeEventListener("scroll", invalidate);
}
function invalidate() {
result.rect = getVisibleRect(element, element.getBoundingClientRect());
result.descendants.forEach((p) => p.invalidate());
}
element.addEventListener("scroll", invalidate);
return result;
}
function handleCurrentElement(current, scrollables, firstDescendentScrollable) {
const scrollingAxis = getScrollingAxis(current);
if (scrollingAxis) {
if (!scrollables.some((p) => p.element === current)) {
const info = scrollableInfo(current);
if (firstDescendentScrollable) {
info.descendants.push(firstDescendentScrollable);
}
firstDescendentScrollable = info;
if (scrollingAxis === "xy") {
scrollables.push(Object.assign({}, info, { axis: "x" }));
scrollables.push(Object.assign({}, info, { axis: "y" }, { descendants: [] }));
} else {
scrollables.push(Object.assign({}, info, { axis: scrollingAxis }));
}
}
}
return { current: current.parentElement, firstDescendentScrollable };
}
function getScrollableElements(containerElements) {
const scrollables = [];
let firstDescendentScrollable = null;
containerElements.forEach((el) => {
let current = el;
firstDescendentScrollable = null;
while (current) {
const result = handleCurrentElement(current, scrollables, firstDescendentScrollable);
current = result.current;
firstDescendentScrollable = result.firstDescendentScrollable;
}
});
return scrollables;
}
function getScrollableAnimator(scrollableInfo) {
return Object.assign(scrollableInfo, createAnimator(scrollableInfo.element, scrollableInfo.axis));
}
function getWindowAnimators() {
function getWindowRect() {
return {
left: 0,
right: global.innerWidth,
top: 0,
bottom: global.innerHeight
};
}
return [
Object.assign({ rect: getWindowRect(), axis: "y" }, createAnimator(global)),
Object.assign({ rect: getWindowRect(), axis: "x" }, createAnimator(global, "x"))
];
}
const dragScroller = (containers) => {
const scrollablesInfo = getScrollableElements(containers.map((p) => p.element));
const animators = [...scrollablesInfo.map(getScrollableAnimator), ...getWindowAnimators()];
return ({ draggableInfo, reset }) => {
if (animators.length) {
if (reset) {
animators.forEach((p) => p.stop());
scrollablesInfo.forEach((p) => p.dispose());
return null;
}
animators.forEach((animator) => {
const scrollParams = getAutoScrollInfo(draggableInfo.mousePosition, animator);
if (scrollParams) {
animator.animate(scrollParams.direction, scrollParams.speedFactor * maxSpeed);
} else {
animator.stop();
}
});
}
};
};
export default dragScroller;

View File

@@ -0,0 +1,49 @@
import { addChildAt, removeChildAt } from './utils';
import {
wrapperClass,
animationClass,
containersInDraggable
} from './constants';
export function domDropHandler({ element, draggables, layout, options }) {
return (dropResult, onDrop) => {
const { removedIndex, addedIndex, droppedElement } = dropResult;
let removedWrapper = null;
if (removedIndex !== null) {
removedWrapper = removeChildAt(element, removedIndex);
draggables.splice(removedIndex, 1);
}
if (addedIndex !== null) {
const wrapper = global.document.createElement('div');
wrapper.className = `${wrapperClass}`;
wrapper.appendChild(removedWrapper && removedWrapper.firstElementChild ? removedWrapper.firstElementChild : droppedElement);
wrapper[containersInDraggable] = [];
addChildAt(element, wrapper, addedIndex);
if (addedIndex >= draggables.length) {
draggables.push(wrapper);
} else {
draggables.splice(addedIndex, 0, wrapper);
}
}
if (onDrop) {
onDrop(dropResult);
}
};
}
export function reactDropHandler() {
const handler = ({ element, draggables, layout, options }) => {
return (dropResult, onDrop) => {
if (onDrop) {
onDrop(dropResult);
}
};
};
return {
handler
};
}

View File

@@ -0,0 +1,288 @@
import * as Utils from './utils';
import { translationValue, visibilityValue, extraSizeForInsertion, containersInDraggable } from './constants';
const horizontalMap = {
size: 'offsetWidth',
distanceToParent: 'offsetLeft',
translate: 'transform',
begin: 'left',
end: 'right',
dragPosition: 'x',
scrollSize: 'scrollWidth',
offsetSize: 'offsetWidth',
scrollValue: 'scrollLeft',
scale: 'scaleX',
setSize: 'width',
setters: {
'translate': (val) => `translate3d(${val}px, 0, 0)`
}
};
const verticalMap = {
size: 'offsetHeight',
distanceToParent: 'offsetTop',
translate: 'transform',
begin: 'top',
end: 'bottom',
dragPosition: 'y',
scrollSize: 'scrollHeight',
offsetSize: 'offsetHeight',
scrollValue: 'scrollTop',
scale: 'scaleY',
setSize: 'height',
setters: {
'translate': (val) => `translate3d(0,${val}px, 0)`
}
};
function orientationDependentProps(map) {
function get(obj, prop) {
const mappedProp = map[prop];
return obj[mappedProp || prop];
}
function set(obj, prop, value) {
requestAnimationFrame(() => {
obj[map[prop]] = map.setters[prop] ? map.setters[prop](value) : value;
});
}
return { get, set };
}
export default function layoutManager(containerElement, orientation, _animationDuration) {
containerElement[extraSizeForInsertion] = 0;
const animationDuration = _animationDuration;
const map = orientation === 'horizontal' ? horizontalMap : verticalMap;
const propMapper = orientationDependentProps(map);
const values = {
translation: 0
};
let registeredScrollListener = null;
global.addEventListener('resize', function() {
invalidateContainerRectangles(containerElement);
// invalidateContainerScale(containerElement);
});
setTimeout(() => {
invalidate();
}, 10);
// invalidate();
const scrollListener = Utils.listenScrollParent(containerElement, function() {
invalidateContainerRectangles(containerElement);
registeredScrollListener && registeredScrollListener();
});
function invalidate() {
invalidateContainerRectangles(containerElement);
invalidateContainerScale(containerElement);
}
let visibleRect;
function invalidateContainerRectangles(containerElement) {
values.rect = Utils.getContainerRect(containerElement);
values.visibleRect = Utils.getVisibleRect(containerElement, values.rect);
}
function invalidateContainerScale(containerElement) {
const rect = containerElement.getBoundingClientRect();
values.scaleX = containerElement.offsetWidth ? ((rect.right - rect.left) / containerElement.offsetWidth) : 1;
values.scaleY = containerElement.offsetHeight ? ((rect.bottom - rect.top) / containerElement.offsetHeight) : 1;
}
function getContainerRectangles() {
return {
rect: values.rect,
visibleRect: values.visibleRect
};
}
function getBeginEndOfDOMRect(rect) {
return {
begin: propMapper.get(rect, 'begin'),
end: propMapper.get(rect, 'end')
};
}
function getBeginEndOfContainer() {
const begin = propMapper.get(values.rect, 'begin') + values.translation;
const end = propMapper.get(values.rect, 'end') + values.translation;
return { begin, end };
}
function getBeginEndOfContainerVisibleRect() {
const begin = propMapper.get(values.visibleRect, 'begin') + values.translation;
const end = propMapper.get(values.visibleRect, 'end') + values.translation;
return { begin, end };
}
function getContainerScale() {
return { scaleX: values.scaleX, scaleY: values.scaleY };
}
function getSize(element) {
return propMapper.get(element, 'size') * propMapper.get(values, 'scale');
}
function getDistanceToOffsetParent(element) {
const distance = propMapper.get(element, 'distanceToParent') + (element[translationValue] || 0);
return distance * propMapper.get(values, 'scale');
}
function getBeginEnd(element) {
const begin = getDistanceToOffsetParent(element) + (propMapper.get(values.rect, 'begin') + values.translation) - propMapper.get(containerElement, 'scrollValue');
return {
begin,
end: begin + getSize(element) * propMapper.get(values, 'scale')
};
}
function setSize(element, size) {
propMapper.set(element, 'setSize', size);
}
function getAxisValue(position) {
return propMapper.get(position, 'dragPosition');
}
function updateDescendantContainerRects(container) {
container.layout.invalidateRects();
container.onTranslated();
if (container.getChildContainers()) {
container.getChildContainers().forEach(p => updateDescendantContainerRects(p));
}
}
function setTranslation(element, translation) {
if (!translation) {
element.style.removeProperty('transform');
} else {
propMapper.set(element.style, 'translate', translation);
}
element[translationValue] = translation;
if (element[containersInDraggable]) {
setTimeout(() => {
element[containersInDraggable].forEach(p => {
updateDescendantContainerRects(p);
});
}, animationDuration + 20);
}
}
function getTranslation(element) {
return element[translationValue];
}
function setVisibility(element, isVisible) {
if (element[visibilityValue] === undefined || element[visibilityValue] !== isVisible) {
if (isVisible) {
element.style.removeProperty('visibility');
} else {
element.style.visibility = 'hidden';
}
element[visibilityValue] = isVisible;
}
}
function isVisible(element) {
return element[visibilityValue] === undefined || element[visibilityValue];
}
function isInVisibleRect(x, y) {
let { left, top, right, bottom } = values.visibleRect;
// if there is no wrapper in rect size will be 0 and wont accept any drop
// so make sure at least there is 30px difference
if (bottom - top < 2) {
bottom = top + 30;
}
const containerRect = values.rect;
if (orientation === 'vertical') {
return x > containerRect.left && x < containerRect.right && y > top && y < bottom;
} else {
return x > left && x < right && y > containerRect.top && y < containerRect.bottom;
}
}
function setScrollListener(callback) {
registeredScrollListener = callback;
}
function getTopLeftOfElementBegin(begin) {
let top = 0;
let left = 0;
if (orientation === 'horizontal') {
left = begin;
top = values.rect.top;
} else {
left = values.rect.left;
top = begin;
}
return {
top, left
};
}
function getScrollSize(element) {
return propMapper.get(element, 'scrollSize');
}
function getScrollValue(element) {
return propMapper.get(element, 'scrollValue');
}
function setScrollValue(element, val) {
return propMapper.set(element, 'scrollValue', val);
}
function dispose() {
if (scrollListener) {
scrollListener.dispose();
}
if (visibleRect) {
visibleRect.parentNode.removeChild(visibleRect);
visibleRect = null;
}
}
function getPosition(position) {
return isInVisibleRect(position.x, position.y) ? getAxisValue(position) : null;
}
function invalidateRects() {
invalidateContainerRectangles(containerElement);
}
return {
getSize,
//getDistanceToContainerBegining,
getContainerRectangles,
getBeginEndOfDOMRect,
getBeginEndOfContainer,
getBeginEndOfContainerVisibleRect,
getBeginEnd,
getAxisValue,
setTranslation,
getTranslation,
setVisibility,
isVisible,
isInVisibleRect,
dispose,
getContainerScale,
setScrollListener,
setSize,
getTopLeftOfElementBegin,
getScrollSize,
getScrollValue,
setScrollValue,
invalidate,
invalidateRects,
getPosition,
};
}

View File

@@ -0,0 +1,479 @@
import './polyfills';
import * as Utils from './utils';
import * as constants from './constants';
import { addStyleToHead, addCursorStyleToBody, removeStyle } from './styles';
import dragScroller from './dragscroller';
const grabEvents = ['mousedown', 'touchstart'];
const moveEvents = ['mousemove', 'touchmove'];
const releaseEvents = ['mouseup', 'touchend'];
let dragListeningContainers = null;
let grabbedElement = null;
let ghostInfo = null;
let draggableInfo = null;
let containers = [];
let isDragging = false;
let removedElement = null;
let handleDrag = null;
let handleScroll = null;
let sourceContainer = null;
let sourceContainerLockAxis = null;
let cursorStyleElement = null;
// Utils.addClass(document.body, 'clearfix');
const isMobile = Utils.isMobile();
function listenEvents() {
if (typeof window !== 'undefined') {
addGrabListeners();
}
}
function addGrabListeners() {
grabEvents.forEach(e => {
global.document.addEventListener(e, onMouseDown, { passive: false });
});
}
function addMoveListeners() {
moveEvents.forEach(e => {
global.document.addEventListener(e, onMouseMove, { passive: false });
});
}
function removeMoveListeners() {
moveEvents.forEach(e => {
global.document.removeEventListener(e, onMouseMove, { passive: false });
});
}
function addReleaseListeners() {
releaseEvents.forEach(e => {
global.document.addEventListener(e, onMouseUp, { passive: false });
});
}
function removeReleaseListeners() {
releaseEvents.forEach(e => {
global.document.removeEventListener(e, onMouseUp, { passive: false });
});
}
function getGhostParent() {
if (draggableInfo.ghostParent) {
return draggableInfo.ghostParent;
}
if (grabbedElement) {
return grabbedElement.parentElement || global.document.body;
} else {
return global.document.body;
}
}
function getGhostElement(wrapperElement, { x, y }, container, cursor) {
const { scaleX = 1, scaleY = 1 } = container.getScale();
const { left, top, right, bottom } = wrapperElement.getBoundingClientRect();
const midX = left + (right - left) / 2;
const midY = top + (bottom - top) / 2;
const ghost = wrapperElement.cloneNode(true);
ghost.style.zIndex = 1000;
ghost.style.boxSizing = 'border-box';
ghost.style.position = 'fixed';
ghost.style.left = left + 'px';
ghost.style.top = top + 'px';
ghost.style.width = right - left + 'px';
ghost.style.height = bottom - top + 'px';
ghost.style.overflow = 'visible';
ghost.style.transition = null;
ghost.style.removeProperty('transition');
ghost.style.pointerEvents = 'none';
if (container.getOptions().dragClass) {
setTimeout(() => {
Utils.addClass(ghost.firstElementChild, container.getOptions().dragClass);
const dragCursor = global.getComputedStyle(ghost.firstElementChild).cursor;
cursorStyleElement = addCursorStyleToBody(dragCursor);
});
} else {
cursorStyleElement = addCursorStyleToBody(cursor);
}
Utils.addClass(ghost, container.getOptions().orientation);
Utils.addClass(ghost, constants.ghostClass);
return {
ghost: ghost,
centerDelta: { x: midX - x, y: midY - y },
positionDelta: { left: left - x, top: top - y }
};
}
function getDraggableInfo(draggableElement) {
const container = containers.filter(p => draggableElement.parentElement === p.element)[0];
const draggableIndex = container.draggables.indexOf(draggableElement);
const getGhostParent = container.getOptions().getGhostParent;
return {
container,
element: draggableElement,
elementIndex: draggableIndex,
payload: container.getOptions().getChildPayload
? container.getOptions().getChildPayload(draggableIndex)
: undefined,
targetElement: null,
position: { x: 0, y: 0 },
groupName: container.getOptions().groupName,
ghostParent: getGhostParent ? getGhostParent() : null,
};
}
function handleDropAnimation(callback) {
function endDrop() {
Utils.removeClass(ghostInfo.ghost, 'animated');
ghostInfo.ghost.style.transitionDuration = null;
getGhostParent().removeChild(ghostInfo.ghost);
callback();
}
function animateGhostToPosition({ top, left }, duration, dropClass) {
Utils.addClass(ghostInfo.ghost, 'animated');
if (dropClass) {
Utils.addClass(ghostInfo.ghost.firstElementChild, dropClass);
}
ghostInfo.ghost.style.transitionDuration = duration + 'ms';
ghostInfo.ghost.style.left = left + 'px';
ghostInfo.ghost.style.top = top + 'px';
setTimeout(function() {
endDrop();
}, duration + 20);
}
function shouldAnimateDrop(options) {
return options.shouldAnimateDrop
? options.shouldAnimateDrop(draggableInfo.container.getOptions(), draggableInfo.payload)
: true;
}
if (draggableInfo.targetElement) {
const container = containers.filter(p => p.element === draggableInfo.targetElement)[0];
if (shouldAnimateDrop(container.getOptions())) {
const dragResult = container.getDragResult();
animateGhostToPosition(
dragResult.shadowBeginEnd.rect,
Math.max(150, container.getOptions().animationDuration / 2),
container.getOptions().dropClass
);
} else {
endDrop();
}
} else {
const container = containers.filter(p => p === draggableInfo.container)[0];
const { behaviour, removeOnDropOut } = container.getOptions();
if (behaviour === 'move' && !removeOnDropOut && container.getDragResult()) {
const { removedIndex, elementSize } = container.getDragResult();
const layout = container.layout;
// drag ghost to back
container.getTranslateCalculator({
dragResult: {
removedIndex,
addedIndex: removedIndex,
elementSize
}
});
const prevDraggableEnd =
removedIndex > 0
? layout.getBeginEnd(container.draggables[removedIndex - 1]).end
: layout.getBeginEndOfContainer().begin;
animateGhostToPosition(
layout.getTopLeftOfElementBegin(prevDraggableEnd),
container.getOptions().animationDuration,
container.getOptions().dropClass
);
} else {
Utils.addClass(ghostInfo.ghost, 'animated');
ghostInfo.ghost.style.transitionDuration = container.getOptions().animationDuration + 'ms';
ghostInfo.ghost.style.opacity = '0';
ghostInfo.ghost.style.transform = 'scale(0.90)';
setTimeout(function() {
endDrop();
}, container.getOptions().animationDuration);
}
}
}
const handleDragStartConditions = (function handleDragStartConditions() {
let startEvent;
let delay;
let clb;
let timer = null;
const moveThreshold = 1;
const maxMoveInDelay = 5;
function onMove(event) {
const { clientX: currentX, clientY: currentY } = getPointerEvent(event);
if (!delay) {
if (
Math.abs(startEvent.clientX - currentX) > moveThreshold ||
Math.abs(startEvent.clientY - currentY) > moveThreshold
) {
return callCallback();
}
} else {
if (
Math.abs(startEvent.clientX - currentX) > maxMoveInDelay ||
Math.abs(startEvent.clientY - currentY) > maxMoveInDelay
) {
deregisterEvent();
}
}
}
function onUp() {
deregisterEvent();
}
function onHTMLDrag() {
deregisterEvent();
}
function registerEvents() {
if (delay) {
timer = setTimeout(callCallback, delay);
}
moveEvents.forEach(e => global.document.addEventListener(e, onMove), {
passive: false
});
releaseEvents.forEach(e => global.document.addEventListener(e, onUp), {
passive: false
});
global.document.addEventListener('drag', onHTMLDrag, {
passive: false
});
}
function deregisterEvent() {
clearTimeout(timer);
moveEvents.forEach(e => global.document.removeEventListener(e, onMove), {
passive: false
});
releaseEvents.forEach(e => global.document.removeEventListener(e, onUp), {
passive: false
});
global.document.removeEventListener('drag', onHTMLDrag, {
passive: false
});
}
function callCallback() {
clearTimeout(timer);
deregisterEvent();
clb();
}
return function(_startEvent, _delay, _clb) {
startEvent = getPointerEvent(_startEvent);
delay = (typeof _delay === 'number') ? _delay : (isMobile ? 200 : 0);
clb = _clb;
registerEvents();
};
})();
function onMouseDown(event) {
const e = getPointerEvent(event);
if (!isDragging && (e.button === undefined || e.button === 0)) {
grabbedElement = Utils.getParent(e.target, '.' + constants.wrapperClass);
if (grabbedElement) {
const containerElement = Utils.getParent(grabbedElement, '.' + constants.containerClass);
const container = containers.filter(p => p.element === containerElement)[0];
const dragHandleSelector = container.getOptions().dragHandleSelector;
const nonDragAreaSelector = container.getOptions().nonDragAreaSelector;
let startDrag = true;
if (dragHandleSelector && !Utils.getParent(e.target, dragHandleSelector)) {
startDrag = false;
}
if (nonDragAreaSelector && Utils.getParent(e.target, nonDragAreaSelector)) {
startDrag = false;
}
if (startDrag) {
handleDragStartConditions(e, container.getOptions().dragBeginDelay, () => {
Utils.clearSelection();
initiateDrag(e, Utils.getElementCursor(event.target));
addMoveListeners();
addReleaseListeners();
});
}
}
}
}
function onMouseUp() {
removeMoveListeners();
removeReleaseListeners();
handleScroll({ reset: true });
if (cursorStyleElement) {
removeStyle(cursorStyleElement);
cursorStyleElement = null;
}
if (draggableInfo) {
handleDropAnimation(() => {
Utils.removeClass(global.document.body, constants.disbaleTouchActions);
Utils.removeClass(global.document.body, constants.noUserSelectClass);
fireOnDragStartEnd(false);
(dragListeningContainers || []).forEach(p => {
p.handleDrop(draggableInfo);
});
dragListeningContainers = null;
grabbedElement = null;
ghostInfo = null;
draggableInfo = null;
isDragging = false;
sourceContainer = null;
sourceContainerLockAxis = null;
handleDrag = null;
});
}
}
function getPointerEvent(e) {
return e.touches ? e.touches[0] : e;
}
function dragHandler(dragListeningContainers) {
let targetContainers = dragListeningContainers;
return function(draggableInfo) {
let containerBoxChanged = false;
targetContainers.forEach(p => {
const dragResult = p.handleDrag(draggableInfo);
containerBoxChanged |= dragResult.containerBoxChanged || false;
dragResult.containerBoxChanged = false;
});
handleScroll({ draggableInfo });
if (containerBoxChanged) {
containerBoxChanged = false;
setTimeout(() => {
containers.forEach(p => {
p.layout.invalidateRects();
p.onTranslated();
});
}, 10);
}
};
}
function getScrollHandler(container, dragListeningContainers) {
if (container.getOptions().autoScrollEnabled) {
return dragScroller(dragListeningContainers);
} else {
return () => null;
}
}
function fireOnDragStartEnd(isStart) {
containers.forEach(p => {
const fn = isStart ? p.getOptions().onDragStart : p.getOptions().onDragEnd;
if (fn) {
const options = {
isSource: p === draggableInfo.container,
payload: draggableInfo.payload
};
if (p.isDragRelevant(draggableInfo.container, draggableInfo.payload)) {
options.willAcceptDrop = true;
} else {
options.willAcceptDrop = false;
}
fn(options);
}
});
}
function initiateDrag(position, cursor) {
isDragging = true;
const container = containers.filter(p => grabbedElement.parentElement === p.element)[0];
container.setDraggables();
sourceContainer = container;
sourceContainerLockAxis = container.getOptions().lockAxis ? container.getOptions().lockAxis.toLowerCase() : null;
draggableInfo = getDraggableInfo(grabbedElement);
ghostInfo = getGhostElement(
grabbedElement,
{ x: position.clientX, y: position.clientY },
draggableInfo.container,
cursor
);
draggableInfo.position = {
x: position.clientX + ghostInfo.centerDelta.x,
y: position.clientY + ghostInfo.centerDelta.y
};
draggableInfo.mousePosition = {
x: position.clientX,
y: position.clientY
};
Utils.addClass(global.document.body, constants.disbaleTouchActions);
Utils.addClass(global.document.body, constants.noUserSelectClass);
dragListeningContainers = containers.filter(p => p.isDragRelevant(container, draggableInfo.payload));
handleDrag = dragHandler(dragListeningContainers);
if (handleScroll) {
handleScroll({ reset: true });
}
handleScroll = getScrollHandler(container, dragListeningContainers);
dragListeningContainers.forEach(p => p.prepareDrag(p, dragListeningContainers));
fireOnDragStartEnd(true);
handleDrag(draggableInfo);
getGhostParent().appendChild(ghostInfo.ghost);
}
function onMouseMove(event) {
event.preventDefault();
const e = getPointerEvent(event);
if (!draggableInfo) {
initiateDrag(e, Utils.getElementCursor(event.target));
} else {
// just update ghost position && draggableInfo position
if (sourceContainerLockAxis) {
if (sourceContainerLockAxis === 'y') {
ghostInfo.ghost.style.top = `${e.clientY + ghostInfo.positionDelta.top}px`;
draggableInfo.position.y = e.clientY + ghostInfo.centerDelta.y;
draggableInfo.mousePosition.y = e.clientY;
} else if (sourceContainerLockAxis === 'x') {
ghostInfo.ghost.style.left = `${e.clientX + ghostInfo.positionDelta.left}px`;
draggableInfo.position.x = e.clientX + ghostInfo.centerDelta.x;
draggableInfo.mousePosition.x = e.clientX;
}
} else {
ghostInfo.ghost.style.left = `${e.clientX + ghostInfo.positionDelta.left}px`;
ghostInfo.ghost.style.top = `${e.clientY + ghostInfo.positionDelta.top}px`;
draggableInfo.position.x = e.clientX + ghostInfo.centerDelta.x;
draggableInfo.position.y = e.clientY + ghostInfo.centerDelta.y;
draggableInfo.mousePosition.x = e.clientX;
draggableInfo.mousePosition.y = e.clientY;
}
handleDrag(draggableInfo);
}
}
function Mediator() {
listenEvents();
return {
register: function(container) {
containers.push(container);
},
unregister: function(container) {
containers.splice(containers.indexOf(container), 1);
}
};
}
addStyleToHead();
export default Mediator();

View File

@@ -0,0 +1,17 @@
(function(constructor) {
if (constructor && constructor.prototype && !constructor.prototype.matches) {
constructor.prototype.matches =
constructor.prototype.matchesSelector ||
constructor.prototype.mozMatchesSelector ||
constructor.prototype.msMatchesSelector ||
constructor.prototype.oMatchesSelector ||
constructor.prototype.webkitMatchesSelector ||
function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i = matches.length;
while (--i >= 0 && matches.item(i) !== this) {}
return i > -1;
};
}
})(global.Node || global.Element);

View File

@@ -0,0 +1,118 @@
import * as constants from "./constants";
const verticalWrapperClass = {
overflow: "hidden",
display: "block"
};
const horizontalWrapperClass = {
height: "100%",
display: "inline-block",
"vertical-align": "top",
"white-space": "normal"
};
const stretcherElementHorizontalClass = {
display: "inline-block"
};
const css = {
[`.${constants.containerClass}`]: {
position: "relative"
},
[`.${constants.containerClass} *`]: {
"box-sizing": "border-box"
},
[`.${constants.containerClass}.horizontal`]: {
"white-space": "nowrap"
},
[`.${constants.containerClass}.horizontal > .${constants.stretcherElementClass}`]: stretcherElementHorizontalClass,
[`.${constants.containerClass}.horizontal > .${constants.wrapperClass}`]: horizontalWrapperClass,
[`.${constants.containerClass}.vertical > .${constants.wrapperClass}`]: verticalWrapperClass,
[`.${constants.wrapperClass}`]: {
// 'overflow': 'hidden'
},
[`.${constants.wrapperClass}.horizontal`]: horizontalWrapperClass,
[`.${constants.wrapperClass}.vertical`]: verticalWrapperClass,
[`.${constants.wrapperClass}.animated`]: {
transition: "transform ease"
},
[`.${constants.ghostClass} *`]: {
//'perspective': '800px',
"box-sizing": "border-box"
},
[`.${constants.ghostClass}.animated`]: {
transition: "all ease-in-out"
},
[`.${constants.disbaleTouchActions} *`]: {
"touch-actions": "none",
"-ms-touch-actions": "none"
},
[`.${constants.noUserSelectClass} *`]: {
"-webkit-touch-callout": "none",
"-webkit-user-select": "none",
"-khtml-user-select": "none",
"-moz-user-select": "none",
"-ms-user-select": "none",
"user-select": "none"
}
};
function convertToCssString(css) {
return Object.keys(css).reduce((styleString, propName) => {
const propValue = css[propName];
if (typeof propValue === "object") {
return `${styleString}${propName}{${convertToCssString(propValue)}}`;
}
return `${styleString}${propName}:${propValue};`;
}, "");
}
function addStyleToHead() {
if (typeof window !== "undefined") {
const head = global.document.head || global.document.getElementsByTagName("head")[0];
const style = global.document.createElement("style");
const cssString = convertToCssString(css);
style.type = "text/css";
if (style.styleSheet) {
style.styleSheet.cssText = cssString;
} else {
style.appendChild(global.document.createTextNode(cssString));
}
head.appendChild(style);
}
}
function addCursorStyleToBody(cursor) {
if (cursor && typeof window !== "undefined") {
const head = global.document.head || global.document.getElementsByTagName("head")[0];
const style = global.document.createElement("style");
const cssString = convertToCssString({
"body *": {
cursor: `${cursor} !important`
}
});
style.type = "text/css";
if (style.styleSheet) {
style.styleSheet.cssText = cssString;
} else {
style.appendChild(global.document.createTextNode(cssString));
}
head.appendChild(style);
return style;
}
return null;
}
function removeStyle(styleElement) {
if (styleElement && typeof window !== "undefined") {
const head = global.document.head || global.document.getElementsByTagName("head")[0];
head.removeChild(styleElement);
}
}
export { addStyleToHead, addCursorStyleToBody, removeStyle };

View File

@@ -0,0 +1,282 @@
export const getIntersection = (rect1, rect2) => {
return {
left: Math.max(rect1.left, rect2.left),
top: Math.max(rect1.top, rect2.top),
right: Math.min(rect1.right, rect2.right),
bottom: Math.min(rect1.bottom, rect2.bottom)
};
};
export const getIntersectionOnAxis = (rect1, rect2, axis) => {
if (axis === "x") {
return {
left: Math.max(rect1.left, rect2.left),
top: rect1.top,
right: Math.min(rect1.right, rect2.right),
bottom: rect1.bottom
};
} else {
return {
left: rect1.left,
top: Math.max(rect1.top, rect2.top),
right: rect1.right,
bottom: Math.min(rect1.bottom, rect2.bottom)
};
}
};
export const getContainerRect = element => {
const _rect = element.getBoundingClientRect();
const rect = {
left: _rect.left,
right: _rect.right + 10,
top: _rect.top,
bottom: _rect.bottom
};
if (hasBiggerChild(element, "x") && !isScrollingOrHidden(element, "x")) {
const width = rect.right - rect.left;
rect.right = rect.right + element.scrollWidth - width;
}
if (hasBiggerChild(element, "y") && !isScrollingOrHidden(element, "y")) {
const height = rect.bottom - rect.top;
rect.bottom = rect.bottom + element.scrollHeight - height;
}
return rect;
};
export const getScrollingAxis = element => {
const style = global.getComputedStyle(element);
const overflow = style["overflow"];
const general = overflow === "auto" || overflow === "scroll";
if (general) return "xy";
const overFlowX = style[`overflow-x`];
const xScroll = overFlowX === "auto" || overFlowX === "scroll";
const overFlowY = style[`overflow-y`];
const yScroll = overFlowY === "auto" || overFlowY === "scroll";
return `${xScroll ? "x" : ""}${yScroll ? "y" : ""}` || null;
};
export const isScrolling = (element, axis) => {
const style = global.getComputedStyle(element);
const overflow = style["overflow"];
const overFlowAxis = style[`overflow-${axis}`];
const general = overflow === "auto" || overflow === "scroll";
const dimensionScroll = overFlowAxis === "auto" || overFlowAxis === "scroll";
return general || dimensionScroll;
};
export const isScrollingOrHidden = (element, axis) => {
const style = global.getComputedStyle(element);
const overflow = style["overflow"];
const overFlowAxis = style[`overflow-${axis}`];
const general =
overflow === "auto" || overflow === "scroll" || overflow === "hidden";
const dimensionScroll =
overFlowAxis === "auto" ||
overFlowAxis === "scroll" ||
overFlowAxis === "hidden";
return general || dimensionScroll;
};
export const hasBiggerChild = (element, axis) => {
if (axis === "x") {
return element.scrollWidth > element.clientWidth;
} else {
return element.scrollHeight > element.clientHeight;
}
};
export const hasScrollBar = (element, axis) => {
return hasBiggerChild(element, axis) && isScrolling(element, axis);
};
export const getVisibleRect = (element, elementRect) => {
let currentElement = element;
let rect = elementRect || getContainerRect(element);
currentElement = element.parentElement;
while (currentElement) {
if (
hasBiggerChild(currentElement, "x") &&
isScrollingOrHidden(currentElement, "x")
) {
rect = getIntersectionOnAxis(
rect,
currentElement.getBoundingClientRect(),
"x"
);
}
if (
hasBiggerChild(currentElement, "y") &&
isScrollingOrHidden(currentElement, "y")
) {
rect = getIntersectionOnAxis(
rect,
currentElement.getBoundingClientRect(),
"y"
);
}
currentElement = currentElement.parentElement;
}
return rect;
};
export const listenScrollParent = (element, clb) => {
let scrollers = [];
const dispose = () => {
scrollers.forEach(p => {
p.removeEventListener("scroll", clb);
});
global.removeEventListener("scroll", clb);
};
setTimeout(function() {
let currentElement = element;
while (currentElement) {
if (
isScrolling(currentElement, "x") ||
isScrolling(currentElement, "y")
) {
currentElement.addEventListener("scroll", clb);
scrollers.push(currentElement);
}
currentElement = currentElement.parentElement;
}
global.addEventListener("scroll", clb);
}, 10);
return {
dispose
};
};
export const hasParent = (element, parent) => {
let current = element;
while (current) {
if (current === parent) {
return true;
}
current = current.parentElement;
}
return false;
};
export const getParent = (element, selector) => {
let current = element;
while (current) {
if (current.matches(selector)) {
return current;
}
current = current.parentElement;
}
return null;
};
export const hasClass = (element, cls) => {
return (
element.className
.split(" ")
.map(p => p)
.indexOf(cls) > -1
);
};
export const addClass = (element, cls) => {
if (element) {
element.className = element.className || ''
const classes = element.className.split(" ").filter(p => p);
if (classes.indexOf(cls) === -1) {
classes.unshift(cls);
element.className = classes.join(" ");
}
}
};
export const removeClass = (element, cls) => {
if (element) {
const classes = element.className.split(" ").filter(p => p && p !== cls);
element.className = classes.join(" ");
}
};
export const debounce = (fn, delay, immediate) => {
let timer = null;
return (...params) => {
if (timer) {
clearTimeout(timer);
}
if (immediate && !timer) {
fn.call(this, ...params);
} else {
timer = setTimeout(() => {
timer = null;
fn.call(this, ...params);
}, delay);
}
};
};
export const removeChildAt = (parent, index) => {
return parent.removeChild(parent.children[index]);
};
export const addChildAt = (parent, child, index) => {
if (index >= parent.children.lenght) {
parent.appendChild(child);
} else {
parent.insertBefore(child, parent.children[index]);
}
};
export const isMobile = () => {
if (typeof window !== 'undefined') {
if (
global.navigator.userAgent.match(/Android/i) ||
global.navigator.userAgent.match(/webOS/i) ||
global.navigator.userAgent.match(/iPhone/i) ||
global.navigator.userAgent.match(/iPad/i) ||
global.navigator.userAgent.match(/iPod/i) ||
global.navigator.userAgent.match(/BlackBerry/i) ||
global.navigator.userAgent.match(/Windows Phone/i)
) {
return true;
} else {
return false;
}
}
return false;
};
export const clearSelection = () => {
if (global.getSelection) {
if (global.getSelection().empty) {
// Chrome
global.getSelection().empty();
} else if (global.getSelection().removeAllRanges) {
// Firefox
global.getSelection().removeAllRanges();
}
} else if (global.document.selection) {
// IE?
global.document.selection.empty();
}
};
export const getElementCursor = (element) => {
if (element) {
const style = global.getComputedStyle(element);
if (style) {
return style.cursor;
}
}
return null;
}