UNPKG

@hello-pangea/dnd

Version:

Beautiful and accessible drag and drop for lists with React

1,736 lines (1,673 loc) 213 kB
'use strict'; var React = require('react'); var ReactDOM = require('react-dom'); var redux = require('redux'); var reactRedux = require('react-redux'); var useMemoOne = require('use-memo-one'); var cssBoxModel = require('css-box-model'); var memoizeOne = require('memoize-one'); var rafSchd = require('raf-schd'); var _extends = require('@babel/runtime/helpers/extends'); const isProduction$1 = process.env.NODE_ENV === 'production'; const spacesAndTabs = /[ \t]{2,}/g; const lineStartWithSpaces = /^[ \t]*/gm; const clean$2 = value => value.replace(spacesAndTabs, ' ').replace(lineStartWithSpaces, '').trim(); const getDevMessage = message => clean$2(` %c@hello-pangea/dnd %c${clean$2(message)} %c👷‍ This is a development only message. It will be removed in production builds. `); const getFormattedMessage = message => [getDevMessage(message), 'color: #00C584; font-size: 1.2em; font-weight: bold;', 'line-height: 1.5', 'color: #723874;']; const isDisabledFlag = '__@hello-pangea/dnd-disable-dev-warnings'; function log(type, message) { if (isProduction$1) { return; } if (typeof window !== 'undefined' && window[isDisabledFlag]) { return; } console[type](...getFormattedMessage(message)); } const warning = log.bind(null, 'warn'); const error = log.bind(null, 'error'); function noop$2() {} function getOptions(shared, fromBinding) { return { ...shared, ...fromBinding }; } function bindEvents(el, bindings, sharedOptions) { const unbindings = bindings.map(binding => { const options = getOptions(sharedOptions, binding.options); el.addEventListener(binding.eventName, binding.fn, options); return function unbind() { el.removeEventListener(binding.eventName, binding.fn, options); }; }); return function unbindAll() { unbindings.forEach(unbind => { unbind(); }); }; } const isProduction = process.env.NODE_ENV === 'production'; const prefix$1 = 'Invariant failed'; class RbdInvariant extends Error {} RbdInvariant.prototype.toString = function toString() { return this.message; }; function invariant(condition, message) { if (isProduction) { throw new RbdInvariant(prefix$1); } else { throw new RbdInvariant(`${prefix$1}: ${message || ''}`); } } class ErrorBoundary extends React.Component { constructor(...args) { super(...args); this.callbacks = null; this.unbind = noop$2; this.onWindowError = event => { const callbacks = this.getCallbacks(); if (callbacks.isDragging()) { callbacks.tryAbort(); process.env.NODE_ENV !== "production" ? warning(` An error was caught by our window 'error' event listener while a drag was occurring. The active drag has been aborted. `) : void 0; } const err = event.error; if (err instanceof RbdInvariant) { event.preventDefault(); if (process.env.NODE_ENV !== 'production') { error(err.message); } } }; this.getCallbacks = () => { if (!this.callbacks) { throw new Error('Unable to find AppCallbacks in <ErrorBoundary/>'); } return this.callbacks; }; this.setCallbacks = callbacks => { this.callbacks = callbacks; }; } componentDidMount() { this.unbind = bindEvents(window, [{ eventName: 'error', fn: this.onWindowError }]); } componentDidCatch(err) { if (err instanceof RbdInvariant) { if (process.env.NODE_ENV !== 'production') { error(err.message); } this.setState({}); return; } throw err; } componentWillUnmount() { this.unbind(); } render() { return this.props.children(this.setCallbacks); } } const dragHandleUsageInstructions = ` Press space bar to start a drag. When dragging you can use the arrow keys to move the item around and escape to cancel. Some screen readers may require you to be in focus mode or to use your pass through key `; const position = index => index + 1; const onDragStart = start => ` You have lifted an item in position ${position(start.source.index)} `; const withLocation = (source, destination) => { const isInHomeList = source.droppableId === destination.droppableId; const startPosition = position(source.index); const endPosition = position(destination.index); if (isInHomeList) { return ` You have moved the item from position ${startPosition} to position ${endPosition} `; } return ` You have moved the item from position ${startPosition} in list ${source.droppableId} to list ${destination.droppableId} in position ${endPosition} `; }; const withCombine = (id, source, combine) => { const inHomeList = source.droppableId === combine.droppableId; if (inHomeList) { return ` The item ${id} has been combined with ${combine.draggableId}`; } return ` The item ${id} in list ${source.droppableId} has been combined with ${combine.draggableId} in list ${combine.droppableId} `; }; const onDragUpdate = update => { const location = update.destination; if (location) { return withLocation(update.source, location); } const combine = update.combine; if (combine) { return withCombine(update.draggableId, update.source, combine); } return 'You are over an area that cannot be dropped on'; }; const returnedToStart = source => ` The item has returned to its starting position of ${position(source.index)} `; const onDragEnd = result => { if (result.reason === 'CANCEL') { return ` Movement cancelled. ${returnedToStart(result.source)} `; } const location = result.destination; const combine = result.combine; if (location) { return ` You have dropped the item. ${withLocation(result.source, location)} `; } if (combine) { return ` You have dropped the item. ${withCombine(result.draggableId, result.source, combine)} `; } return ` The item has been dropped while not over a drop area. ${returnedToStart(result.source)} `; }; const preset = { dragHandleUsageInstructions, onDragStart, onDragUpdate, onDragEnd }; const origin = { x: 0, y: 0 }; const add = (point1, point2) => ({ x: point1.x + point2.x, y: point1.y + point2.y }); const subtract = (point1, point2) => ({ x: point1.x - point2.x, y: point1.y - point2.y }); const isEqual$1 = (point1, point2) => point1.x === point2.x && point1.y === point2.y; const negate = point => ({ x: point.x !== 0 ? -point.x : 0, y: point.y !== 0 ? -point.y : 0 }); const patch = (line, value, otherValue = 0) => { if (line === 'x') { return { x: value, y: otherValue }; } return { x: otherValue, y: value }; }; const distance = (point1, point2) => Math.sqrt((point2.x - point1.x) ** 2 + (point2.y - point1.y) ** 2); const closest$1 = (target, points) => Math.min(...points.map(point => distance(target, point))); const apply = fn => point => ({ x: fn(point.x), y: fn(point.y) }); var executeClip = (frame, subject) => { const result = cssBoxModel.getRect({ top: Math.max(subject.top, frame.top), right: Math.min(subject.right, frame.right), bottom: Math.min(subject.bottom, frame.bottom), left: Math.max(subject.left, frame.left) }); if (result.width <= 0 || result.height <= 0) { return null; } return result; }; const offsetByPosition = (spacing, point) => ({ top: spacing.top + point.y, left: spacing.left + point.x, bottom: spacing.bottom + point.y, right: spacing.right + point.x }); const getCorners = spacing => [{ x: spacing.left, y: spacing.top }, { x: spacing.right, y: spacing.top }, { x: spacing.left, y: spacing.bottom }, { x: spacing.right, y: spacing.bottom }]; const noSpacing = { top: 0, right: 0, bottom: 0, left: 0 }; const scroll$1 = (target, frame) => { if (!frame) { return target; } return offsetByPosition(target, frame.scroll.diff.displacement); }; const increase = (target, axis, withPlaceholder) => { if (withPlaceholder && withPlaceholder.increasedBy) { return { ...target, [axis.end]: target[axis.end] + withPlaceholder.increasedBy[axis.line] }; } return target; }; const clip = (target, frame) => { if (frame && frame.shouldClipSubject) { return executeClip(frame.pageMarginBox, target); } return cssBoxModel.getRect(target); }; var getSubject = ({ page, withPlaceholder, axis, frame }) => { const scrolled = scroll$1(page.marginBox, frame); const increased = increase(scrolled, axis, withPlaceholder); const clipped = clip(increased, frame); return { page, withPlaceholder, active: clipped }; }; var scrollDroppable = (droppable, newScroll) => { !droppable.frame ? process.env.NODE_ENV !== "production" ? invariant() : invariant() : void 0; const scrollable = droppable.frame; const scrollDiff = subtract(newScroll, scrollable.scroll.initial); const scrollDisplacement = negate(scrollDiff); const frame = { ...scrollable, scroll: { initial: scrollable.scroll.initial, current: newScroll, diff: { value: scrollDiff, displacement: scrollDisplacement }, max: scrollable.scroll.max } }; const subject = getSubject({ page: droppable.subject.page, withPlaceholder: droppable.subject.withPlaceholder, axis: droppable.axis, frame }); const result = { ...droppable, frame, subject }; return result; }; const toDroppableMap = memoizeOne(droppables => droppables.reduce((previous, current) => { previous[current.descriptor.id] = current; return previous; }, {})); const toDraggableMap = memoizeOne(draggables => draggables.reduce((previous, current) => { previous[current.descriptor.id] = current; return previous; }, {})); const toDroppableList = memoizeOne(droppables => Object.values(droppables)); const toDraggableList = memoizeOne(draggables => Object.values(draggables)); var getDraggablesInsideDroppable = memoizeOne((droppableId, draggables) => { const result = toDraggableList(draggables).filter(draggable => droppableId === draggable.descriptor.droppableId).sort((a, b) => a.descriptor.index - b.descriptor.index); return result; }); function tryGetDestination(impact) { if (impact.at && impact.at.type === 'REORDER') { return impact.at.destination; } return null; } function tryGetCombine(impact) { if (impact.at && impact.at.type === 'COMBINE') { return impact.at.combine; } return null; } var removeDraggableFromList = memoizeOne((remove, list) => list.filter(item => item.descriptor.id !== remove.descriptor.id)); var moveToNextCombine = ({ isMovingForward, draggable, destination, insideDestination, previousImpact }) => { if (!destination.isCombineEnabled) { return null; } const location = tryGetDestination(previousImpact); if (!location) { return null; } function getImpact(target) { const at = { type: 'COMBINE', combine: { draggableId: target, droppableId: destination.descriptor.id } }; return { ...previousImpact, at }; } const all = previousImpact.displaced.all; const closestId = all.length ? all[0] : null; if (isMovingForward) { return closestId ? getImpact(closestId) : null; } const withoutDraggable = removeDraggableFromList(draggable, insideDestination); if (!closestId) { if (!withoutDraggable.length) { return null; } const last = withoutDraggable[withoutDraggable.length - 1]; return getImpact(last.descriptor.id); } const indexOfClosest = withoutDraggable.findIndex(d => d.descriptor.id === closestId); !(indexOfClosest !== -1) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Could not find displaced item in set') : invariant() : void 0; const proposedIndex = indexOfClosest - 1; if (proposedIndex < 0) { return null; } const before = withoutDraggable[proposedIndex]; return getImpact(before.descriptor.id); }; var isHomeOf = (draggable, destination) => draggable.descriptor.droppableId === destination.descriptor.id; const noDisplacedBy = { point: origin, value: 0 }; const emptyGroups = { invisible: {}, visible: {}, all: [] }; const noImpact = { displaced: emptyGroups, displacedBy: noDisplacedBy, at: null }; var isWithin = (lowerBound, upperBound) => value => lowerBound <= value && value <= upperBound; var isPartiallyVisibleThroughFrame = frame => { const isWithinVertical = isWithin(frame.top, frame.bottom); const isWithinHorizontal = isWithin(frame.left, frame.right); return subject => { const isContained = isWithinVertical(subject.top) && isWithinVertical(subject.bottom) && isWithinHorizontal(subject.left) && isWithinHorizontal(subject.right); if (isContained) { return true; } const isPartiallyVisibleVertically = isWithinVertical(subject.top) || isWithinVertical(subject.bottom); const isPartiallyVisibleHorizontally = isWithinHorizontal(subject.left) || isWithinHorizontal(subject.right); const isPartiallyContained = isPartiallyVisibleVertically && isPartiallyVisibleHorizontally; if (isPartiallyContained) { return true; } const isBiggerVertically = subject.top < frame.top && subject.bottom > frame.bottom; const isBiggerHorizontally = subject.left < frame.left && subject.right > frame.right; const isTargetBiggerThanFrame = isBiggerVertically && isBiggerHorizontally; if (isTargetBiggerThanFrame) { return true; } const isTargetBiggerOnOneAxis = isBiggerVertically && isPartiallyVisibleHorizontally || isBiggerHorizontally && isPartiallyVisibleVertically; return isTargetBiggerOnOneAxis; }; }; var isTotallyVisibleThroughFrame = frame => { const isWithinVertical = isWithin(frame.top, frame.bottom); const isWithinHorizontal = isWithin(frame.left, frame.right); return subject => { const isContained = isWithinVertical(subject.top) && isWithinVertical(subject.bottom) && isWithinHorizontal(subject.left) && isWithinHorizontal(subject.right); return isContained; }; }; const vertical = { direction: 'vertical', line: 'y', crossAxisLine: 'x', start: 'top', end: 'bottom', size: 'height', crossAxisStart: 'left', crossAxisEnd: 'right', crossAxisSize: 'width' }; const horizontal = { direction: 'horizontal', line: 'x', crossAxisLine: 'y', start: 'left', end: 'right', size: 'width', crossAxisStart: 'top', crossAxisEnd: 'bottom', crossAxisSize: 'height' }; var isTotallyVisibleThroughFrameOnAxis = axis => frame => { const isWithinVertical = isWithin(frame.top, frame.bottom); const isWithinHorizontal = isWithin(frame.left, frame.right); return subject => { if (axis === vertical) { return isWithinVertical(subject.top) && isWithinVertical(subject.bottom); } return isWithinHorizontal(subject.left) && isWithinHorizontal(subject.right); }; }; const getDroppableDisplaced = (target, destination) => { const displacement = destination.frame ? destination.frame.scroll.diff.displacement : origin; return offsetByPosition(target, displacement); }; const isVisibleInDroppable = (target, destination, isVisibleThroughFrameFn) => { if (!destination.subject.active) { return false; } return isVisibleThroughFrameFn(destination.subject.active)(target); }; const isVisibleInViewport = (target, viewport, isVisibleThroughFrameFn) => isVisibleThroughFrameFn(viewport)(target); const isVisible$1 = ({ target: toBeDisplaced, destination, viewport, withDroppableDisplacement, isVisibleThroughFrameFn }) => { const displacedTarget = withDroppableDisplacement ? getDroppableDisplaced(toBeDisplaced, destination) : toBeDisplaced; return isVisibleInDroppable(displacedTarget, destination, isVisibleThroughFrameFn) && isVisibleInViewport(displacedTarget, viewport, isVisibleThroughFrameFn); }; const isPartiallyVisible = args => isVisible$1({ ...args, isVisibleThroughFrameFn: isPartiallyVisibleThroughFrame }); const isTotallyVisible = args => isVisible$1({ ...args, isVisibleThroughFrameFn: isTotallyVisibleThroughFrame }); const isTotallyVisibleOnAxis = args => isVisible$1({ ...args, isVisibleThroughFrameFn: isTotallyVisibleThroughFrameOnAxis(args.destination.axis) }); const getShouldAnimate = (id, last, forceShouldAnimate) => { if (typeof forceShouldAnimate === 'boolean') { return forceShouldAnimate; } if (!last) { return true; } const { invisible, visible } = last; if (invisible[id]) { return false; } const previous = visible[id]; return previous ? previous.shouldAnimate : true; }; function getTarget(draggable, displacedBy) { const marginBox = draggable.page.marginBox; const expandBy = { top: displacedBy.point.y, right: 0, bottom: 0, left: displacedBy.point.x }; return cssBoxModel.getRect(cssBoxModel.expand(marginBox, expandBy)); } function getDisplacementGroups({ afterDragging, destination, displacedBy, viewport, forceShouldAnimate, last }) { return afterDragging.reduce(function process(groups, draggable) { const target = getTarget(draggable, displacedBy); const id = draggable.descriptor.id; groups.all.push(id); const isVisible = isPartiallyVisible({ target, destination, viewport, withDroppableDisplacement: true }); if (!isVisible) { groups.invisible[draggable.descriptor.id] = true; return groups; } const shouldAnimate = getShouldAnimate(id, last, forceShouldAnimate); const displacement = { draggableId: id, shouldAnimate }; groups.visible[id] = displacement; return groups; }, { all: [], visible: {}, invisible: {} }); } function getIndexOfLastItem(draggables, options) { if (!draggables.length) { return 0; } const indexOfLastItem = draggables[draggables.length - 1].descriptor.index; return options.inHomeList ? indexOfLastItem : indexOfLastItem + 1; } function goAtEnd({ insideDestination, inHomeList, displacedBy, destination }) { const newIndex = getIndexOfLastItem(insideDestination, { inHomeList }); return { displaced: emptyGroups, displacedBy, at: { type: 'REORDER', destination: { droppableId: destination.descriptor.id, index: newIndex } } }; } function calculateReorderImpact({ draggable, insideDestination, destination, viewport, displacedBy, last, index, forceShouldAnimate }) { const inHomeList = isHomeOf(draggable, destination); if (index == null) { return goAtEnd({ insideDestination, inHomeList, displacedBy, destination }); } const match = insideDestination.find(item => item.descriptor.index === index); if (!match) { return goAtEnd({ insideDestination, inHomeList, displacedBy, destination }); } const withoutDragging = removeDraggableFromList(draggable, insideDestination); const sliceFrom = insideDestination.indexOf(match); const impacted = withoutDragging.slice(sliceFrom); const displaced = getDisplacementGroups({ afterDragging: impacted, destination, displacedBy, last, viewport: viewport.frame, forceShouldAnimate }); return { displaced, displacedBy, at: { type: 'REORDER', destination: { droppableId: destination.descriptor.id, index } } }; } function didStartAfterCritical(draggableId, afterCritical) { return Boolean(afterCritical.effected[draggableId]); } var fromCombine = ({ isMovingForward, destination, draggables, combine, afterCritical }) => { if (!destination.isCombineEnabled) { return null; } const combineId = combine.draggableId; const combineWith = draggables[combineId]; const combineWithIndex = combineWith.descriptor.index; const didCombineWithStartAfterCritical = didStartAfterCritical(combineId, afterCritical); if (didCombineWithStartAfterCritical) { if (isMovingForward) { return combineWithIndex; } return combineWithIndex - 1; } if (isMovingForward) { return combineWithIndex + 1; } return combineWithIndex; }; var fromReorder = ({ isMovingForward, isInHomeList, insideDestination, location }) => { if (!insideDestination.length) { return null; } const currentIndex = location.index; const proposedIndex = isMovingForward ? currentIndex + 1 : currentIndex - 1; const firstIndex = insideDestination[0].descriptor.index; const lastIndex = insideDestination[insideDestination.length - 1].descriptor.index; const upperBound = isInHomeList ? lastIndex : lastIndex + 1; if (proposedIndex < firstIndex) { return null; } if (proposedIndex > upperBound) { return null; } return proposedIndex; }; var moveToNextIndex = ({ isMovingForward, isInHomeList, draggable, draggables, destination, insideDestination, previousImpact, viewport, afterCritical }) => { const wasAt = previousImpact.at; !wasAt ? process.env.NODE_ENV !== "production" ? invariant(false, 'Cannot move in direction without previous impact location') : invariant() : void 0; if (wasAt.type === 'REORDER') { const newIndex = fromReorder({ isMovingForward, isInHomeList, location: wasAt.destination, insideDestination }); if (newIndex == null) { return null; } return calculateReorderImpact({ draggable, insideDestination, destination, viewport, last: previousImpact.displaced, displacedBy: previousImpact.displacedBy, index: newIndex }); } const newIndex = fromCombine({ isMovingForward, destination, displaced: previousImpact.displaced, draggables, combine: wasAt.combine, afterCritical }); if (newIndex == null) { return null; } return calculateReorderImpact({ draggable, insideDestination, destination, viewport, last: previousImpact.displaced, displacedBy: previousImpact.displacedBy, index: newIndex }); }; var getCombinedItemDisplacement = ({ displaced, afterCritical, combineWith, displacedBy }) => { const isDisplaced = Boolean(displaced.visible[combineWith] || displaced.invisible[combineWith]); if (didStartAfterCritical(combineWith, afterCritical)) { return isDisplaced ? origin : negate(displacedBy.point); } return isDisplaced ? displacedBy.point : origin; }; var whenCombining = ({ afterCritical, impact, draggables }) => { const combine = tryGetCombine(impact); !combine ? process.env.NODE_ENV !== "production" ? invariant() : invariant() : void 0; const combineWith = combine.draggableId; const center = draggables[combineWith].page.borderBox.center; const displaceBy = getCombinedItemDisplacement({ displaced: impact.displaced, afterCritical, combineWith, displacedBy: impact.displacedBy }); return add(center, displaceBy); }; const distanceFromStartToBorderBoxCenter = (axis, box) => box.margin[axis.start] + box.borderBox[axis.size] / 2; const distanceFromEndToBorderBoxCenter = (axis, box) => box.margin[axis.end] + box.borderBox[axis.size] / 2; const getCrossAxisBorderBoxCenter = (axis, target, isMoving) => target[axis.crossAxisStart] + isMoving.margin[axis.crossAxisStart] + isMoving.borderBox[axis.crossAxisSize] / 2; const goAfter = ({ axis, moveRelativeTo, isMoving }) => patch(axis.line, moveRelativeTo.marginBox[axis.end] + distanceFromStartToBorderBoxCenter(axis, isMoving), getCrossAxisBorderBoxCenter(axis, moveRelativeTo.marginBox, isMoving)); const goBefore = ({ axis, moveRelativeTo, isMoving }) => patch(axis.line, moveRelativeTo.marginBox[axis.start] - distanceFromEndToBorderBoxCenter(axis, isMoving), getCrossAxisBorderBoxCenter(axis, moveRelativeTo.marginBox, isMoving)); const goIntoStart = ({ axis, moveInto, isMoving }) => patch(axis.line, moveInto.contentBox[axis.start] + distanceFromStartToBorderBoxCenter(axis, isMoving), getCrossAxisBorderBoxCenter(axis, moveInto.contentBox, isMoving)); var whenReordering = ({ impact, draggable, draggables, droppable, afterCritical }) => { const insideDestination = getDraggablesInsideDroppable(droppable.descriptor.id, draggables); const draggablePage = draggable.page; const axis = droppable.axis; if (!insideDestination.length) { return goIntoStart({ axis, moveInto: droppable.page, isMoving: draggablePage }); } const { displaced, displacedBy } = impact; const closestAfter = displaced.all[0]; if (closestAfter) { const closest = draggables[closestAfter]; if (didStartAfterCritical(closestAfter, afterCritical)) { return goBefore({ axis, moveRelativeTo: closest.page, isMoving: draggablePage }); } const withDisplacement = cssBoxModel.offset(closest.page, displacedBy.point); return goBefore({ axis, moveRelativeTo: withDisplacement, isMoving: draggablePage }); } const last = insideDestination[insideDestination.length - 1]; if (last.descriptor.id === draggable.descriptor.id) { return draggablePage.borderBox.center; } if (didStartAfterCritical(last.descriptor.id, afterCritical)) { const page = cssBoxModel.offset(last.page, negate(afterCritical.displacedBy.point)); return goAfter({ axis, moveRelativeTo: page, isMoving: draggablePage }); } return goAfter({ axis, moveRelativeTo: last.page, isMoving: draggablePage }); }; var withDroppableDisplacement = (droppable, point) => { const frame = droppable.frame; if (!frame) { return point; } return add(point, frame.scroll.diff.displacement); }; const getResultWithoutDroppableDisplacement = ({ impact, draggable, droppable, draggables, afterCritical }) => { const original = draggable.page.borderBox.center; const at = impact.at; if (!droppable) { return original; } if (!at) { return original; } if (at.type === 'REORDER') { return whenReordering({ impact, draggable, draggables, droppable, afterCritical }); } return whenCombining({ impact, draggables, afterCritical }); }; var getPageBorderBoxCenterFromImpact = args => { const withoutDisplacement = getResultWithoutDroppableDisplacement(args); const droppable = args.droppable; const withDisplacement = droppable ? withDroppableDisplacement(droppable, withoutDisplacement) : withoutDisplacement; return withDisplacement; }; var scrollViewport = (viewport, newScroll) => { const diff = subtract(newScroll, viewport.scroll.initial); const displacement = negate(diff); const frame = cssBoxModel.getRect({ top: newScroll.y, bottom: newScroll.y + viewport.frame.height, left: newScroll.x, right: newScroll.x + viewport.frame.width }); const updated = { frame, scroll: { initial: viewport.scroll.initial, max: viewport.scroll.max, current: newScroll, diff: { value: diff, displacement } } }; return updated; }; function getDraggables$1(ids, draggables) { return ids.map(id => draggables[id]); } function tryGetVisible(id, groups) { for (let i = 0; i < groups.length; i++) { const displacement = groups[i].visible[id]; if (displacement) { return displacement; } } return null; } var speculativelyIncrease = ({ impact, viewport, destination, draggables, maxScrollChange }) => { const scrolledViewport = scrollViewport(viewport, add(viewport.scroll.current, maxScrollChange)); const scrolledDroppable = destination.frame ? scrollDroppable(destination, add(destination.frame.scroll.current, maxScrollChange)) : destination; const last = impact.displaced; const withViewportScroll = getDisplacementGroups({ afterDragging: getDraggables$1(last.all, draggables), destination, displacedBy: impact.displacedBy, viewport: scrolledViewport.frame, last, forceShouldAnimate: false }); const withDroppableScroll = getDisplacementGroups({ afterDragging: getDraggables$1(last.all, draggables), destination: scrolledDroppable, displacedBy: impact.displacedBy, viewport: viewport.frame, last, forceShouldAnimate: false }); const invisible = {}; const visible = {}; const groups = [last, withViewportScroll, withDroppableScroll]; last.all.forEach(id => { const displacement = tryGetVisible(id, groups); if (displacement) { visible[id] = displacement; return; } invisible[id] = true; }); const newImpact = { ...impact, displaced: { all: last.all, invisible, visible } }; return newImpact; }; var withViewportDisplacement = (viewport, point) => add(viewport.scroll.diff.displacement, point); var getClientFromPageBorderBoxCenter = ({ pageBorderBoxCenter, draggable, viewport }) => { const withoutPageScrollChange = withViewportDisplacement(viewport, pageBorderBoxCenter); const offset = subtract(withoutPageScrollChange, draggable.page.borderBox.center); return add(draggable.client.borderBox.center, offset); }; var isTotallyVisibleInNewLocation = ({ draggable, destination, newPageBorderBoxCenter, viewport, withDroppableDisplacement, onlyOnMainAxis = false }) => { const changeNeeded = subtract(newPageBorderBoxCenter, draggable.page.borderBox.center); const shifted = offsetByPosition(draggable.page.borderBox, changeNeeded); const args = { target: shifted, destination, withDroppableDisplacement, viewport }; return onlyOnMainAxis ? isTotallyVisibleOnAxis(args) : isTotallyVisible(args); }; var moveToNextPlace = ({ isMovingForward, draggable, destination, draggables, previousImpact, viewport, previousPageBorderBoxCenter, previousClientSelection, afterCritical }) => { if (!destination.isEnabled) { return null; } const insideDestination = getDraggablesInsideDroppable(destination.descriptor.id, draggables); const isInHomeList = isHomeOf(draggable, destination); const impact = moveToNextCombine({ isMovingForward, draggable, destination, insideDestination, previousImpact }) || moveToNextIndex({ isMovingForward, isInHomeList, draggable, draggables, destination, insideDestination, previousImpact, viewport, afterCritical }); if (!impact) { return null; } const pageBorderBoxCenter = getPageBorderBoxCenterFromImpact({ impact, draggable, droppable: destination, draggables, afterCritical }); const isVisibleInNewLocation = isTotallyVisibleInNewLocation({ draggable, destination, newPageBorderBoxCenter: pageBorderBoxCenter, viewport: viewport.frame, withDroppableDisplacement: false, onlyOnMainAxis: true }); if (isVisibleInNewLocation) { const clientSelection = getClientFromPageBorderBoxCenter({ pageBorderBoxCenter, draggable, viewport }); return { clientSelection, impact, scrollJumpRequest: null }; } const distance = subtract(pageBorderBoxCenter, previousPageBorderBoxCenter); const cautious = speculativelyIncrease({ impact, viewport, destination, draggables, maxScrollChange: distance }); return { clientSelection: previousClientSelection, impact: cautious, scrollJumpRequest: distance }; }; const getKnownActive = droppable => { const rect = droppable.subject.active; !rect ? process.env.NODE_ENV !== "production" ? invariant(false, 'Cannot get clipped area from droppable') : invariant() : void 0; return rect; }; var getBestCrossAxisDroppable = ({ isMovingForward, pageBorderBoxCenter, source, droppables, viewport }) => { const active = source.subject.active; if (!active) { return null; } const axis = source.axis; const isBetweenSourceClipped = isWithin(active[axis.start], active[axis.end]); const candidates = toDroppableList(droppables).filter(droppable => droppable !== source).filter(droppable => droppable.isEnabled).filter(droppable => Boolean(droppable.subject.active)).filter(droppable => isPartiallyVisibleThroughFrame(viewport.frame)(getKnownActive(droppable))).filter(droppable => { const activeOfTarget = getKnownActive(droppable); if (isMovingForward) { return active[axis.crossAxisEnd] < activeOfTarget[axis.crossAxisEnd]; } return activeOfTarget[axis.crossAxisStart] < active[axis.crossAxisStart]; }).filter(droppable => { const activeOfTarget = getKnownActive(droppable); const isBetweenDestinationClipped = isWithin(activeOfTarget[axis.start], activeOfTarget[axis.end]); return isBetweenSourceClipped(activeOfTarget[axis.start]) || isBetweenSourceClipped(activeOfTarget[axis.end]) || isBetweenDestinationClipped(active[axis.start]) || isBetweenDestinationClipped(active[axis.end]); }).sort((a, b) => { const first = getKnownActive(a)[axis.crossAxisStart]; const second = getKnownActive(b)[axis.crossAxisStart]; if (isMovingForward) { return first - second; } return second - first; }).filter((droppable, index, array) => getKnownActive(droppable)[axis.crossAxisStart] === getKnownActive(array[0])[axis.crossAxisStart]); if (!candidates.length) { return null; } if (candidates.length === 1) { return candidates[0]; } const contains = candidates.filter(droppable => { const isWithinDroppable = isWithin(getKnownActive(droppable)[axis.start], getKnownActive(droppable)[axis.end]); return isWithinDroppable(pageBorderBoxCenter[axis.line]); }); if (contains.length === 1) { return contains[0]; } if (contains.length > 1) { return contains.sort((a, b) => getKnownActive(a)[axis.start] - getKnownActive(b)[axis.start])[0]; } return candidates.sort((a, b) => { const first = closest$1(pageBorderBoxCenter, getCorners(getKnownActive(a))); const second = closest$1(pageBorderBoxCenter, getCorners(getKnownActive(b))); if (first !== second) { return first - second; } return getKnownActive(a)[axis.start] - getKnownActive(b)[axis.start]; })[0]; }; const getCurrentPageBorderBoxCenter = (draggable, afterCritical) => { const original = draggable.page.borderBox.center; return didStartAfterCritical(draggable.descriptor.id, afterCritical) ? subtract(original, afterCritical.displacedBy.point) : original; }; const getCurrentPageBorderBox = (draggable, afterCritical) => { const original = draggable.page.borderBox; return didStartAfterCritical(draggable.descriptor.id, afterCritical) ? offsetByPosition(original, negate(afterCritical.displacedBy.point)) : original; }; var getClosestDraggable = ({ pageBorderBoxCenter, viewport, destination, insideDestination, afterCritical }) => { const sorted = insideDestination.filter(draggable => isTotallyVisible({ target: getCurrentPageBorderBox(draggable, afterCritical), destination, viewport: viewport.frame, withDroppableDisplacement: true })).sort((a, b) => { const distanceToA = distance(pageBorderBoxCenter, withDroppableDisplacement(destination, getCurrentPageBorderBoxCenter(a, afterCritical))); const distanceToB = distance(pageBorderBoxCenter, withDroppableDisplacement(destination, getCurrentPageBorderBoxCenter(b, afterCritical))); if (distanceToA < distanceToB) { return -1; } if (distanceToB < distanceToA) { return 1; } return a.descriptor.index - b.descriptor.index; }); return sorted[0] || null; }; var getDisplacedBy = memoizeOne(function getDisplacedBy(axis, displaceBy) { const displacement = displaceBy[axis.line]; return { value: displacement, point: patch(axis.line, displacement) }; }); const getRequiredGrowthForPlaceholder = (droppable, placeholderSize, draggables) => { const axis = droppable.axis; if (droppable.descriptor.mode === 'virtual') { return patch(axis.line, placeholderSize[axis.line]); } const availableSpace = droppable.subject.page.contentBox[axis.size]; const insideDroppable = getDraggablesInsideDroppable(droppable.descriptor.id, draggables); const spaceUsed = insideDroppable.reduce((sum, dimension) => sum + dimension.client.marginBox[axis.size], 0); const requiredSpace = spaceUsed + placeholderSize[axis.line]; const needsToGrowBy = requiredSpace - availableSpace; if (needsToGrowBy <= 0) { return null; } return patch(axis.line, needsToGrowBy); }; const withMaxScroll = (frame, max) => ({ ...frame, scroll: { ...frame.scroll, max } }); const addPlaceholder = (droppable, draggable, draggables) => { const frame = droppable.frame; !!isHomeOf(draggable, droppable) ? process.env.NODE_ENV !== "production" ? invariant(false, 'Should not add placeholder space to home list') : invariant() : void 0; !!droppable.subject.withPlaceholder ? process.env.NODE_ENV !== "production" ? invariant(false, 'Cannot add placeholder size to a subject when it already has one') : invariant() : void 0; const placeholderSize = getDisplacedBy(droppable.axis, draggable.displaceBy).point; const requiredGrowth = getRequiredGrowthForPlaceholder(droppable, placeholderSize, draggables); const added = { placeholderSize, increasedBy: requiredGrowth, oldFrameMaxScroll: droppable.frame ? droppable.frame.scroll.max : null }; if (!frame) { const subject = getSubject({ page: droppable.subject.page, withPlaceholder: added, axis: droppable.axis, frame: droppable.frame }); return { ...droppable, subject }; } const maxScroll = requiredGrowth ? add(frame.scroll.max, requiredGrowth) : frame.scroll.max; const newFrame = withMaxScroll(frame, maxScroll); const subject = getSubject({ page: droppable.subject.page, withPlaceholder: added, axis: droppable.axis, frame: newFrame }); return { ...droppable, subject, frame: newFrame }; }; const removePlaceholder = droppable => { const added = droppable.subject.withPlaceholder; !added ? process.env.NODE_ENV !== "production" ? invariant(false, 'Cannot remove placeholder form subject when there was none') : invariant() : void 0; const frame = droppable.frame; if (!frame) { const subject = getSubject({ page: droppable.subject.page, axis: droppable.axis, frame: null, withPlaceholder: null }); return { ...droppable, subject }; } const oldMaxScroll = added.oldFrameMaxScroll; !oldMaxScroll ? process.env.NODE_ENV !== "production" ? invariant(false, 'Expected droppable with frame to have old max frame scroll when removing placeholder') : invariant() : void 0; const newFrame = withMaxScroll(frame, oldMaxScroll); const subject = getSubject({ page: droppable.subject.page, axis: droppable.axis, frame: newFrame, withPlaceholder: null }); return { ...droppable, subject, frame: newFrame }; }; var moveToNewDroppable = ({ previousPageBorderBoxCenter, moveRelativeTo, insideDestination, draggable, draggables, destination, viewport, afterCritical }) => { if (!moveRelativeTo) { if (insideDestination.length) { return null; } const proposed = { displaced: emptyGroups, displacedBy: noDisplacedBy, at: { type: 'REORDER', destination: { droppableId: destination.descriptor.id, index: 0 } } }; const proposedPageBorderBoxCenter = getPageBorderBoxCenterFromImpact({ impact: proposed, draggable, droppable: destination, draggables, afterCritical }); const withPlaceholder = isHomeOf(draggable, destination) ? destination : addPlaceholder(destination, draggable, draggables); const isVisibleInNewLocation = isTotallyVisibleInNewLocation({ draggable, destination: withPlaceholder, newPageBorderBoxCenter: proposedPageBorderBoxCenter, viewport: viewport.frame, withDroppableDisplacement: false, onlyOnMainAxis: true }); return isVisibleInNewLocation ? proposed : null; } const isGoingBeforeTarget = Boolean(previousPageBorderBoxCenter[destination.axis.line] <= moveRelativeTo.page.borderBox.center[destination.axis.line]); const proposedIndex = (() => { const relativeTo = moveRelativeTo.descriptor.index; if (moveRelativeTo.descriptor.id === draggable.descriptor.id) { return relativeTo; } if (isGoingBeforeTarget) { return relativeTo; } return relativeTo + 1; })(); const displacedBy = getDisplacedBy(destination.axis, draggable.displaceBy); return calculateReorderImpact({ draggable, insideDestination, destination, viewport, displacedBy, last: emptyGroups, index: proposedIndex }); }; var moveCrossAxis = ({ isMovingForward, previousPageBorderBoxCenter, draggable, isOver, draggables, droppables, viewport, afterCritical }) => { const destination = getBestCrossAxisDroppable({ isMovingForward, pageBorderBoxCenter: previousPageBorderBoxCenter, source: isOver, droppables, viewport }); if (!destination) { return null; } const insideDestination = getDraggablesInsideDroppable(destination.descriptor.id, draggables); const moveRelativeTo = getClosestDraggable({ pageBorderBoxCenter: previousPageBorderBoxCenter, viewport, destination, insideDestination, afterCritical }); const impact = moveToNewDroppable({ previousPageBorderBoxCenter, destination, draggable, draggables, moveRelativeTo, insideDestination, viewport, afterCritical }); if (!impact) { return null; } const pageBorderBoxCenter = getPageBorderBoxCenterFromImpact({ impact, draggable, droppable: destination, draggables, afterCritical }); const clientSelection = getClientFromPageBorderBoxCenter({ pageBorderBoxCenter, draggable, viewport }); return { clientSelection, impact, scrollJumpRequest: null }; }; var whatIsDraggedOver = impact => { const at = impact.at; if (!at) { return null; } if (at.type === 'REORDER') { return at.destination.droppableId; } return at.combine.droppableId; }; const getDroppableOver$1 = (impact, droppables) => { const id = whatIsDraggedOver(impact); return id ? droppables[id] : null; }; var moveInDirection = ({ state, type }) => { const isActuallyOver = getDroppableOver$1(state.impact, state.dimensions.droppables); const isMainAxisMovementAllowed = Boolean(isActuallyOver); const home = state.dimensions.droppables[state.critical.droppable.id]; const isOver = isActuallyOver || home; const direction = isOver.axis.direction; const isMovingOnMainAxis = direction === 'vertical' && (type === 'MOVE_UP' || type === 'MOVE_DOWN') || direction === 'horizontal' && (type === 'MOVE_LEFT' || type === 'MOVE_RIGHT'); if (isMovingOnMainAxis && !isMainAxisMovementAllowed) { return null; } const isMovingForward = type === 'MOVE_DOWN' || type === 'MOVE_RIGHT'; const draggable = state.dimensions.draggables[state.critical.draggable.id]; const previousPageBorderBoxCenter = state.current.page.borderBoxCenter; const { draggables, droppables } = state.dimensions; return isMovingOnMainAxis ? moveToNextPlace({ isMovingForward, previousPageBorderBoxCenter, draggable, destination: isOver, draggables, viewport: state.viewport, previousClientSelection: state.current.client.selection, previousImpact: state.impact, afterCritical: state.afterCritical }) : moveCrossAxis({ isMovingForward, previousPageBorderBoxCenter, draggable, isOver, draggables, droppables, viewport: state.viewport, afterCritical: state.afterCritical }); }; function isMovementAllowed(state) { return state.phase === 'DRAGGING' || state.phase === 'COLLECTING'; } function isPositionInFrame(frame) { const isWithinVertical = isWithin(frame.top, frame.bottom); const isWithinHorizontal = isWithin(frame.left, frame.right); return function run(point) { return isWithinVertical(point.y) && isWithinHorizontal(point.x); }; } function getHasOverlap(first, second) { return first.left < second.right && first.right > second.left && first.top < second.bottom && first.bottom > second.top; } function getFurthestAway({ pageBorderBox, draggable, candidates }) { const startCenter = draggable.page.borderBox.center; const sorted = candidates.map(candidate => { const axis = candidate.axis; const target = patch(candidate.axis.line, pageBorderBox.center[axis.line], candidate.page.borderBox.center[axis.crossAxisLine]); return { id: candidate.descriptor.id, distance: distance(startCenter, target) }; }).sort((a, b) => b.distance - a.distance); return sorted[0] ? sorted[0].id : null; } function getDroppableOver({ pageBorderBox, draggable, droppables }) { const candidates = toDroppableList(droppables).filter(item => { if (!item.isEnabled) { return false; } const active = item.subject.active; if (!active) { return false; } if (!getHasOverlap(pageBorderBox, active)) { return false; } if (isPositionInFrame(active)(pageBorderBox.center)) { return true; } const axis = item.axis; const childCenter = active.center[axis.crossAxisLine]; const crossAxisStart = pageBorderBox[axis.crossAxisStart]; const crossAxisEnd = pageBorderBox[axis.crossAxisEnd]; const isContained = isWithin(active[axis.crossAxisStart], active[axis.crossAxisEnd]); const isStartContained = isContained(crossAxisStart); const isEndContained = isContained(crossAxisEnd); if (!isStartContained && !isEndContained) { return true; } if (isStartContained) { return crossAxisStart < childCenter; } return crossAxisEnd > childCenter; }); if (!candidates.length) { return null; } if (candidates.length === 1) { return candidates[0].descriptor.id; } return getFurthestAway({ pageBorderBox, draggable, candidates }); } const offsetRectByPosition = (rect, point) => cssBoxModel.getRect(offsetByPosition(rect, point)); var withDroppableScroll = (droppable, area) => { const frame = droppable.frame; if (!frame) { return area; } return offsetRectByPosition(area, frame.scroll.diff.value); }; function getIsDisplaced({ displaced, id }) { return Boolean(displaced.visible[id] || displaced.invisible[id]); } function atIndex({ draggable, closest, inHomeList }) { if (!closest) { return null; } if (!inHomeList) { return closest.descriptor.index; } if (closest.descriptor.index > draggable.descriptor.index) { return closest.descriptor.index - 1; } return closest.descriptor.index; } var getReorderImpact = ({ pageBorderBoxWithDroppableScroll: targetRect, draggable, destination, insideDestination, last, viewport, afterCritical }) => { const axis = destination.axis; const displacedBy = getDisplacedBy(destination.axis, draggable.displaceBy); const displacement = displacedBy.value; const targetStart = targetRect[axis.start]; const targetEnd = targetRect[axis.end]; const withoutDragging = removeDraggableFromList(draggable, insideDestination); const closest = withoutDragging.find(child => { const id = child.descriptor.id; const childCenter = child.page.borderBox.center[axis.line]; const didStartAfterCritical$1 = didStartAfterCritical(id, afterCritical); const isDisplaced = getIsDisplaced({ displaced: last, id }); if (didStartAfterCritical$1) { if (isDisplaced) { return targetEnd <= childCenter; } return targetStart < childCenter - displacement; } if (isDisplaced) { return targetEnd <= childCenter + displacement; } return targetStart < childCenter; }) || null; const newIndex = atIndex({ draggable, closest, inHomeList: isHomeOf(draggable, destination) }); return calculateReorderImpact({ draggable, insideDestination, destination, viewport, last, displacedBy, index: newIndex }); }; const combineThresholdDivisor = 4; var getCombineImpact = ({ draggable, pageBorderBoxWithDroppableScroll: targetRect, previousImpact, destination, insideDestination, afterCritical }) => { if (!destination.isCombineEnabled) { return null; } const axis = destination.axis; const displacedBy = getDisplacedBy(destination.axis, draggable.displaceBy); const displacement = displacedBy.value; const targetStart = targetRect[axis.start]; const targetEnd = targetRect[axis.end]; const withoutDragging = removeDraggableFromList(draggable, insideDestination); const combineWith = withoutDragging.find(child => { const id = child.descriptor.id; const childRect = child.page.borderBox; const childSize = childRect[axis.size]; const threshold = childSize / combineThresholdDivisor; const didStartAfterCritical$1 = didStartAfterCritical(id, afterCritical); const isDisplaced = getIsDisplaced({ displaced: previousImpact.displaced, id }); if (didStartAfterCritical$1) { if (isDisplaced) { return targetEnd > childRect[axis.start] + threshold && targetEnd < childRect[axis.end] - threshold; } return targetStart > childRect[axis.start] - displacement + threshold && targetStart < childRect[axis.end] - displacement - threshold; } if (isDisplaced) { return targetEnd > childRect[axis.start] + displacement + threshold && targetEnd < childRect[axis.end] + displacement - threshold;