diff --git a/src/state/move-in-direction/index.js b/src/state/move-in-direction/index.js new file mode 100644 index 0000000000..430a682246 --- /dev/null +++ b/src/state/move-in-direction/index.js @@ -0,0 +1,121 @@ +// @flow +import type { Position } from 'css-box-model'; +import { subtract } from '../position'; +import getHomeLocation from '../get-home-location'; +import moveCrossAxis from './move-cross-axis'; +import type { Result as MoveCrossAxisResult } from './move-cross-axis/move-cross-axis-types'; +import moveToNextIndex from './move-to-next-index'; +import type { Result as MoveToNextIndexResult } from './move-to-next-index/move-to-next-index-types'; +import type { + DraggingState, + DragImpact, + DraggableLocation, + Direction, +} from '../../types'; + +type Args = {| + state: DraggingState, + action: 'MOVE_UP' | 'MOVE_RIGHT' | 'MOVE_DOWN' | 'MOVE_LEFT', +|}; + +export type Result = {| + clientSelection: Position, + impact: DragImpact, + scrollJumpRequest: ?Position, +|}; + +export default ({ state, action }: Args): ?Result => { + const { droppable, isMainAxisMovementAllowed } = (() => { + if (state.impact.destination) { + return { + droppable: + state.dimensions.droppables[state.impact.destination.droppableId], + isMainAxisMovementAllowed: true, + }; + } + + // No destination - this can happen when lifting an a disabled droppable + // In this case we want to allow movement out of the list with a keyboard + // but not within the list + return { + droppable: state.dimensions.droppables[state.critical.droppable.id], + isMainAxisMovementAllowed: false, + }; + })(); + + const direction: Direction = droppable.axis.direction; + const isMovingOnMainAxis: boolean = + (direction === 'vertical' && + (action === 'MOVE_UP' || action === 'MOVE_DOWN')) || + (direction === 'horizontal' && + (action === 'MOVE_LEFT' || action === 'MOVE_RIGHT')); + + // This movement is not permitted right now + if (isMovingOnMainAxis && !isMainAxisMovementAllowed) { + return null; + } + + const isMovingForward: boolean = + action === 'MOVE_DOWN' || action === 'MOVE_RIGHT'; + + if (isMovingOnMainAxis) { + const result: ?MoveToNextIndexResult = moveToNextIndex({ + isMovingForward, + draggableId: state.critical.draggable.id, + droppable, + draggables: state.dimensions.draggables, + previousPageBorderBoxCenter: state.current.page.borderBoxCenter, + previousImpact: state.impact, + viewport: state.viewport, + }); + + // Cannot move (at the beginning or end of a list) + if (!result) { + return null; + } + + const impact: DragImpact = result.impact; + const pageBorderBoxCenter: Position = result.pageBorderBoxCenter; + // TODO: not sure if this is correct + const clientBorderBoxCenter: Position = subtract( + pageBorderBoxCenter, + state.viewport.scroll.current, + ); + + return { + impact, + clientSelection: clientBorderBoxCenter, + scrollJumpRequest: result.scrollJumpRequest, + }; + } + + // moving on cross axis + const home: DraggableLocation = getHomeLocation(state.critical); + + const result: ?MoveCrossAxisResult = moveCrossAxis({ + isMovingForward, + pageBorderBoxCenter: state.current.page.borderBoxCenter, + draggableId: state.critical.draggable.id, + droppableId: droppable.descriptor.id, + home, + draggables: state.dimensions.draggables, + droppables: state.dimensions.droppables, + previousImpact: state.impact, + viewport: state.viewport, + }); + + if (!result) { + return null; + } + + const clientSelection: Position = subtract( + result.pageBorderBoxCenter, + state.viewport.scroll.current, + ); + + return { + clientSelection, + impact: result.impact, + scrollJumpRequest: null, + }; +}; diff --git a/src/state/move-cross-axis/get-best-cross-axis-droppable.js b/src/state/move-in-direction/move-cross-axis/get-best-cross-axis-droppable.js similarity index 89% rename from src/state/move-cross-axis/get-best-cross-axis-droppable.js rename to src/state/move-in-direction/move-cross-axis/get-best-cross-axis-droppable.js index d6127ce9e8..eca7229a21 100644 --- a/src/state/move-cross-axis/get-best-cross-axis-droppable.js +++ b/src/state/move-in-direction/move-cross-axis/get-best-cross-axis-droppable.js @@ -1,17 +1,17 @@ // @flow import invariant from 'tiny-invariant'; import { type Position, type Rect } from 'css-box-model'; -import { closest } from '../position'; -import isWithin from '../is-within'; -import { getCorners } from '../spacing'; -import isPartiallyVisibleThroughFrame from '../visibility/is-partially-visible-through-frame'; -import { toDroppableList } from '../dimension-structures'; +import { closest } from '../../position'; +import isWithin from '../../is-within'; +import { getCorners } from '../../spacing'; +import isPartiallyVisibleThroughFrame from '../../visibility/is-partially-visible-through-frame'; +import { toDroppableList } from '../../dimension-structures'; import type { Axis, DroppableDimension, DroppableDimensionMap, Viewport, -} from '../../types'; +} from '../../../types'; type GetBestDroppableArgs = {| isMovingForward: boolean, @@ -74,16 +74,16 @@ export default ({ (droppable: DroppableDimension): boolean => { const targetClipped: Rect = getSafeClipped(droppable); + // is the target in front of the source on the cross axis? if (isMovingForward) { - // is the droppable in front of the source on the cross axis? return ( - sourceClipped[axis.crossAxisEnd] <= - targetClipped[axis.crossAxisStart] + sourceClipped[axis.crossAxisEnd] < targetClipped[axis.crossAxisEnd] ); } - // is the droppable behind the source on the cross axis? + // is the target behind the source on the cross axis? return ( - targetClipped[axis.crossAxisEnd] <= sourceClipped[axis.crossAxisStart] + targetClipped[axis.crossAxisStart] < + sourceClipped[axis.crossAxisStart] ); }, ) diff --git a/src/state/move-cross-axis/get-closest-draggable.js b/src/state/move-in-direction/move-cross-axis/get-closest-draggable.js similarity index 90% rename from src/state/move-cross-axis/get-closest-draggable.js rename to src/state/move-in-direction/move-cross-axis/get-closest-draggable.js index d276733b9e..87a4edf70e 100644 --- a/src/state/move-cross-axis/get-closest-draggable.js +++ b/src/state/move-in-direction/move-cross-axis/get-closest-draggable.js @@ -1,14 +1,14 @@ // @flow import { type Position } from 'css-box-model'; -import { distance } from '../position'; -import { isTotallyVisible } from '../visibility/is-visible'; -import withDroppableDisplacement from '../with-droppable-displacement'; +import { distance } from '../../position'; +import { isTotallyVisible } from '../../visibility/is-visible'; +import withDroppableDisplacement from '../../with-droppable-displacement'; import type { Viewport, Axis, DraggableDimension, DroppableDimension, -} from '../../types'; +} from '../../../types'; type Args = {| axis: Axis, diff --git a/src/state/move-cross-axis/index.js b/src/state/move-in-direction/move-cross-axis/index.js similarity index 94% rename from src/state/move-cross-axis/index.js rename to src/state/move-in-direction/move-cross-axis/index.js index 5ce27f1603..cf1d173f78 100644 --- a/src/state/move-cross-axis/index.js +++ b/src/state/move-in-direction/move-cross-axis/index.js @@ -3,8 +3,8 @@ import { type Position } from 'css-box-model'; import getBestCrossAxisDroppable from './get-best-cross-axis-droppable'; import getClosestDraggable from './get-closest-draggable'; import moveToNewDroppable from './move-to-new-droppable'; -import noImpact from '../no-impact'; -import getDraggablesInsideDroppable from '../get-draggables-inside-droppable'; +import noImpact from '../../no-impact'; +import getDraggablesInsideDroppable from '../../get-draggables-inside-droppable'; import type { Result } from './move-cross-axis-types'; import type { DraggableId, @@ -16,7 +16,7 @@ import type { DraggableLocation, DragImpact, Viewport, -} from '../../types'; +} from '../../../types'; type Args = {| isMovingForward: boolean, diff --git a/src/state/move-cross-axis/move-cross-axis-types.js b/src/state/move-in-direction/move-cross-axis/move-cross-axis-types.js similarity index 82% rename from src/state/move-cross-axis/move-cross-axis-types.js rename to src/state/move-in-direction/move-cross-axis/move-cross-axis-types.js index 4087daeab7..9c2c2cbe2b 100644 --- a/src/state/move-cross-axis/move-cross-axis-types.js +++ b/src/state/move-in-direction/move-cross-axis/move-cross-axis-types.js @@ -1,6 +1,6 @@ // @flow import { type Position } from 'css-box-model'; -import type { DragImpact } from '../../types'; +import type { DragImpact } from '../../../types'; export type Result = {| // how far the draggable needs to move to be in its new home diff --git a/src/state/move-cross-axis/move-to-new-droppable/index.js b/src/state/move-in-direction/move-cross-axis/move-to-new-droppable/index.js similarity index 96% rename from src/state/move-cross-axis/move-to-new-droppable/index.js rename to src/state/move-in-direction/move-cross-axis/move-to-new-droppable/index.js index 4a5dde20db..785b91d405 100644 --- a/src/state/move-cross-axis/move-to-new-droppable/index.js +++ b/src/state/move-in-direction/move-cross-axis/move-to-new-droppable/index.js @@ -3,7 +3,7 @@ import invariant from 'tiny-invariant'; import { type Position } from 'css-box-model'; import toHomeList from './to-home-list'; import toForeignList from './to-foreign-list'; -import { patch } from '../../position'; +import { patch } from '../../../position'; import type { Result } from '../move-cross-axis-types'; import type { DraggableDimension, @@ -11,7 +11,7 @@ import type { DraggableLocation, DragImpact, Viewport, -} from '../../../types'; +} from '../../../../types'; type Args = {| // the current center position of the draggable diff --git a/src/state/move-cross-axis/move-to-new-droppable/to-foreign-list.js b/src/state/move-in-direction/move-cross-axis/move-to-new-droppable/to-foreign-list.js similarity index 93% rename from src/state/move-cross-axis/move-to-new-droppable/to-foreign-list.js rename to src/state/move-in-direction/move-cross-axis/move-to-new-droppable/to-foreign-list.js index 4398cfb0bf..a8dbf7e1b0 100644 --- a/src/state/move-cross-axis/move-to-new-droppable/to-foreign-list.js +++ b/src/state/move-in-direction/move-cross-axis/move-to-new-droppable/to-foreign-list.js @@ -1,10 +1,10 @@ // @flow import invariant from 'tiny-invariant'; import { type Position } from 'css-box-model'; -import moveToEdge from '../../move-to-edge'; +import moveToEdge from '../../../move-to-edge'; import type { Result } from '../move-cross-axis-types'; -import getDisplacement from '../../get-displacement'; -import withDroppableDisplacement from '../../with-droppable-displacement'; +import getDisplacement from '../../../get-displacement'; +import withDroppableDisplacement from '../../../with-droppable-displacement'; import type { Axis, DragImpact, @@ -12,7 +12,7 @@ import type { DroppableDimension, Displacement, Viewport, -} from '../../../types'; +} from '../../../../types'; type Args = {| amount: Position, diff --git a/src/state/move-cross-axis/move-to-new-droppable/to-home-list.js b/src/state/move-in-direction/move-cross-axis/move-to-new-droppable/to-home-list.js similarity index 93% rename from src/state/move-cross-axis/move-to-new-droppable/to-home-list.js rename to src/state/move-in-direction/move-cross-axis/move-to-new-droppable/to-home-list.js index 52981b2fc0..b154672d6e 100644 --- a/src/state/move-cross-axis/move-to-new-droppable/to-home-list.js +++ b/src/state/move-in-direction/move-cross-axis/move-to-new-droppable/to-home-list.js @@ -1,10 +1,10 @@ // @flow import invariant from 'tiny-invariant'; import { type Position } from 'css-box-model'; -import moveToEdge from '../../move-to-edge'; -import getDisplacement from '../../get-displacement'; -import withDroppableDisplacement from '../../with-droppable-displacement'; -import type { Edge } from '../../move-to-edge'; +import moveToEdge from '../../../move-to-edge'; +import getDisplacement from '../../../get-displacement'; +import withDroppableDisplacement from '../../../with-droppable-displacement'; +import type { Edge } from '../../../move-to-edge'; import type { Result } from '../move-cross-axis-types'; import type { Axis, @@ -13,7 +13,7 @@ import type { DragImpact, DraggableDimension, DroppableDimension, -} from '../../../types'; +} from '../../../../types'; type Args = {| amount: Position, diff --git a/src/state/move-to-next-index/get-forced-displacement.js b/src/state/move-in-direction/move-to-next-index/get-forced-displacement.js similarity index 97% rename from src/state/move-to-next-index/get-forced-displacement.js rename to src/state/move-in-direction/move-to-next-index/get-forced-displacement.js index 068c789cb4..23bbbabde7 100644 --- a/src/state/move-to-next-index/get-forced-displacement.js +++ b/src/state/move-in-direction/move-to-next-index/get-forced-displacement.js @@ -1,6 +1,6 @@ // @flow import invariant from 'tiny-invariant'; -import getDisplacement from '../get-displacement'; +import getDisplacement from '../../get-displacement'; import type { Viewport, Axis, @@ -10,7 +10,7 @@ import type { DroppableDimension, DraggableDimension, Displacement, -} from '../../types'; +} from '../../../types'; type WithAdded = {| add: DraggableId, diff --git a/src/state/move-to-next-index/in-foreign-list.js b/src/state/move-in-direction/move-to-next-index/in-foreign-list.js similarity index 92% rename from src/state/move-to-next-index/in-foreign-list.js rename to src/state/move-in-direction/move-to-next-index/in-foreign-list.js index 85fcc8a5eb..0810f60996 100644 --- a/src/state/move-to-next-index/in-foreign-list.js +++ b/src/state/move-in-direction/move-to-next-index/in-foreign-list.js @@ -1,13 +1,13 @@ // @flow import invariant from 'tiny-invariant'; import { type Position } from 'css-box-model'; -import getDraggablesInsideDroppable from '../get-draggables-inside-droppable'; -import { patch, subtract } from '../position'; -import withDroppableDisplacement from '../with-droppable-displacement'; -import moveToEdge from '../move-to-edge'; +import getDraggablesInsideDroppable from '../../get-draggables-inside-droppable'; +import { patch, subtract } from '../../position'; +import withDroppableDisplacement from '../../with-droppable-displacement'; +import moveToEdge from '../../move-to-edge'; import isTotallyVisibleInNewLocation from './is-totally-visible-in-new-location'; import { withFirstAdded, withFirstRemoved } from './get-forced-displacement'; -import type { Edge } from '../move-to-edge'; +import type { Edge } from '../../move-to-edge'; import type { Args, Result } from './move-to-next-index-types'; import type { DraggableLocation, @@ -15,7 +15,7 @@ import type { Axis, DragImpact, Displacement, -} from '../../types'; +} from '../../../types'; export default ({ isMovingForward, diff --git a/src/state/move-to-next-index/in-home-list.js b/src/state/move-in-direction/move-to-next-index/in-home-list.js similarity index 91% rename from src/state/move-to-next-index/in-home-list.js rename to src/state/move-in-direction/move-to-next-index/in-home-list.js index 88cfde276e..d42e95199b 100644 --- a/src/state/move-to-next-index/in-home-list.js +++ b/src/state/move-in-direction/move-to-next-index/in-home-list.js @@ -1,13 +1,13 @@ // @flow import invariant from 'tiny-invariant'; import { type Position } from 'css-box-model'; -import getDraggablesInsideDroppable from '../get-draggables-inside-droppable'; -import { patch, subtract } from '../position'; -import withDroppableDisplacement from '../with-droppable-displacement'; +import getDraggablesInsideDroppable from '../../get-draggables-inside-droppable'; +import { patch, subtract } from '../../position'; +import withDroppableDisplacement from '../../with-droppable-displacement'; import isTotallyVisibleInNewLocation from './is-totally-visible-in-new-location'; -import moveToEdge from '../move-to-edge'; +import moveToEdge from '../../move-to-edge'; import { withFirstAdded, withFirstRemoved } from './get-forced-displacement'; -import type { Edge } from '../move-to-edge'; +import type { Edge } from '../../move-to-edge'; import type { Args, Result } from './move-to-next-index-types'; import type { DraggableLocation, @@ -15,7 +15,7 @@ import type { Displacement, Axis, DragImpact, -} from '../../types'; +} from '../../../types'; export default ({ isMovingForward, diff --git a/src/state/move-to-next-index/index.js b/src/state/move-in-direction/move-to-next-index/index.js similarity index 91% rename from src/state/move-to-next-index/index.js rename to src/state/move-in-direction/move-to-next-index/index.js index 5b2962de02..6eeed6ad79 100644 --- a/src/state/move-to-next-index/index.js +++ b/src/state/move-in-direction/move-to-next-index/index.js @@ -2,7 +2,7 @@ import inHomeList from './in-home-list'; import inForeignList from './in-foreign-list'; import type { Args, Result } from './move-to-next-index-types'; -import type { DraggableDimension } from '../../types'; +import type { DraggableDimension } from '../../../types'; export default (args: Args): ?Result => { const { draggableId, draggables, droppable } = args; diff --git a/src/state/move-to-next-index/is-totally-visible-in-new-location.js b/src/state/move-in-direction/move-to-next-index/is-totally-visible-in-new-location.js similarity index 85% rename from src/state/move-to-next-index/is-totally-visible-in-new-location.js rename to src/state/move-in-direction/move-to-next-index/is-totally-visible-in-new-location.js index e3857623a4..2d8fe3c260 100644 --- a/src/state/move-to-next-index/is-totally-visible-in-new-location.js +++ b/src/state/move-in-direction/move-to-next-index/is-totally-visible-in-new-location.js @@ -1,9 +1,9 @@ // @flow import { type Position, type Rect, type Spacing } from 'css-box-model'; -import { subtract } from '../position'; -import { offsetByPosition } from '../spacing'; -import { isTotallyVisible } from '../visibility/is-visible'; -import type { DraggableDimension, DroppableDimension } from '../../types'; +import { subtract } from '../../position'; +import { offsetByPosition } from '../../spacing'; +import { isTotallyVisible } from '../../visibility/is-visible'; +import type { DraggableDimension, DroppableDimension } from '../../../types'; type Args = {| draggable: DraggableDimension, diff --git a/src/state/move-to-next-index/move-to-next-index-types.js b/src/state/move-in-direction/move-to-next-index/move-to-next-index-types.js similarity index 96% rename from src/state/move-to-next-index/move-to-next-index-types.js rename to src/state/move-in-direction/move-to-next-index/move-to-next-index-types.js index e4b3c8f3ad..6bdb458d0a 100644 --- a/src/state/move-to-next-index/move-to-next-index-types.js +++ b/src/state/move-in-direction/move-to-next-index/move-to-next-index-types.js @@ -6,7 +6,7 @@ import type { DroppableDimension, DraggableDimensionMap, Viewport, -} from '../../types'; +} from '../../../types'; export type Args = {| isMovingForward: boolean, diff --git a/src/state/reducer.js b/src/state/reducer.js index 0fa119d97a..2dca3f0174 100644 --- a/src/state/reducer.js +++ b/src/state/reducer.js @@ -3,9 +3,10 @@ import type { Position } from 'css-box-model'; import invariant from 'tiny-invariant'; import { scrollDroppable } from './droppable-dimension'; import getDragImpact from './get-drag-impact'; -import moveCrossAxis from './move-cross-axis'; -import moveToNextIndex from './move-to-next-index'; import publish from './publish'; +import moveInDirection, { + type Result as MoveInDirectionResult, +} from './move-in-direction'; import { add, isEqual, subtract } from './position'; import scrollViewport from './scroll-viewport'; import getHomeImpact from './get-home-impact'; @@ -13,7 +14,6 @@ import getPageItemPositions from './get-page-item-positions'; import type { State, DroppableDimension, - DraggableLocation, PendingDrop, IdleState, PreparingState, @@ -27,11 +27,8 @@ import type { Viewport, DimensionMap, DropReason, - Direction, } from '../types'; import type { Action } from './store-types'; -import type { Result as MoveToNextResult } from './move-to-next-index/move-to-next-index-types'; -import type { Result as MoveCrossAxisResult } from './move-cross-axis/move-cross-axis-types'; const origin: Position = { x: 0, y: 0 }; @@ -223,6 +220,14 @@ export default (state: State = idle, action: Action): State => { const { client, shouldAnimate } = action.payload; + // nothing needs to be done + if ( + state.shouldAnimate === shouldAnimate && + isEqual(client, state.current.client.selection) + ) { + return state; + } + // If we are jump scrolling - manual movements should not update the impact const impact: ?DragImpact = state.autoScrollMode === 'JUMP' ? state.impact : null; @@ -370,6 +375,7 @@ export default (state: State = idle, action: Action): State => { const newScroll: Position = action.payload.scroll; + // nothing needs to be done if (isEqual(state.viewport.scroll.current, newScroll)) { return state; } @@ -405,107 +411,22 @@ export default (state: State = idle, action: Action): State => { `${action.type} received while not in DRAGGING phase`, ); - const { droppable, isMainAxisMovementAllowed } = (() => { - // appeasing flow - invariant(state.phase === 'DRAGGING'); - - if (state.impact.destination) { - return { - droppable: - state.dimensions.droppables[state.impact.destination.droppableId], - isMainAxisMovementAllowed: true, - }; - } - - // No destination - this can happen when lifting an a disabled droppable - // In this case we want to allow movement out of the list with a keyboard - return { - droppable: state.dimensions.droppables[state.critical.droppable.id], - isMainAxisMovementAllowed: false, - }; - })(); - - const direction: Direction = droppable.axis.direction; - const isMovingOnMainAxis: boolean = - (direction === 'vertical' && - (action.type === 'MOVE_UP' || action.type === 'MOVE_DOWN')) || - (direction === 'horizontal' && - (action.type === 'MOVE_LEFT' || action.type === 'MOVE_RIGHT')); - - // This movement is not permitted right now - if (isMovingOnMainAxis && !isMainAxisMovementAllowed) { - return state; - } - - const isMovingForward: boolean = - action.type === 'MOVE_DOWN' || action.type === 'MOVE_RIGHT'; - - if (isMovingOnMainAxis) { - const result: ?MoveToNextResult = moveToNextIndex({ - isMovingForward, - draggableId: state.critical.draggable.id, - droppable, - draggables: state.dimensions.draggables, - previousPageBorderBoxCenter: state.current.page.borderBoxCenter, - previousImpact: state.impact, - viewport: state.viewport, - }); - - // Cannot move (at the beginning or end of a list) - if (!result) { - return state; - } - - const impact: DragImpact = result.impact; - const pageBorderBoxCenter: Position = result.pageBorderBoxCenter; - // TODO: not sure if this is correct - const clientBorderBoxCenter: Position = subtract( - pageBorderBoxCenter, - state.viewport.scroll.current, - ); - - return moveWithPositionUpdates({ - state, - impact, - clientSelection: clientBorderBoxCenter, - shouldAnimate: true, - scrollJumpRequest: result.scrollJumpRequest, - }); - } - - // moving on cross axis - - const home: DraggableLocation = { - index: state.critical.draggable.index, - droppableId: state.critical.droppable.id, - }; - - const result: ?MoveCrossAxisResult = moveCrossAxis({ - isMovingForward, - pageBorderBoxCenter: state.current.page.borderBoxCenter, - draggableId: state.critical.draggable.id, - droppableId: droppable.descriptor.id, - home, - draggables: state.dimensions.draggables, - droppables: state.dimensions.droppables, - previousImpact: state.impact, - viewport: state.viewport, + const result: ?MoveInDirectionResult = moveInDirection({ + state, + action: action.type, }); + // cannot mov in that direction if (!result) { return state; } - const clientSelection: Position = subtract( - result.pageBorderBoxCenter, - state.viewport.scroll.current, - ); - return moveWithPositionUpdates({ state, - clientSelection, impact: result.impact, + clientSelection: result.clientSelection, shouldAnimate: true, + scrollJumpRequest: result.scrollJumpRequest, }); } diff --git a/test/unit/state/move-cross-axis/get-best-cross-axis-droppable.spec.js b/test/unit/state/move-in-direction/move-cross-axis/get-best-cross-axis-droppable.spec.js similarity index 75% rename from test/unit/state/move-cross-axis/get-best-cross-axis-droppable.spec.js rename to test/unit/state/move-in-direction/move-cross-axis/get-best-cross-axis-droppable.spec.js index 7ae153a60e..25fa7a8866 100644 --- a/test/unit/state/move-cross-axis/get-best-cross-axis-droppable.spec.js +++ b/test/unit/state/move-in-direction/move-cross-axis/get-best-cross-axis-droppable.spec.js @@ -1,16 +1,16 @@ // @flow -import { type Position } from 'css-box-model'; -import getBestCrossAxisDroppable from '../../../../src/state/move-cross-axis/get-best-cross-axis-droppable'; -import { getDroppableDimension } from '../../../utils/dimension'; -import { add } from '../../../../src/state/position'; -import { horizontal, vertical } from '../../../../src/state/axis'; -import getViewport from '../../../../src/view/window/get-viewport'; +import { type Position, type Spacing } from 'css-box-model'; +import getBestCrossAxisDroppable from '../../../../../src/state/move-in-direction/move-cross-axis/get-best-cross-axis-droppable'; +import { getDroppableDimension } from '../../../../utils/dimension'; +import { add } from '../../../../../src/state/position'; +import { horizontal, vertical } from '../../../../../src/state/axis'; +import getViewport from '../../../../../src/view/window/get-viewport'; import type { Viewport, Axis, DroppableDimension, DroppableDimensionMap, -} from '../../../../src/types'; +} from '../../../../../src/types'; const viewport: Viewport = getViewport(); @@ -903,4 +903,264 @@ describe('get best cross axis droppable', () => { }); }); }); + + describe('overlap', () => { + const axis: Axis = vertical; + + describe('moving forward', () => { + it('allow overlap as long as the end of the target is after the end of the source', () => { + const box: Spacing = { + top: 0, + left: 20, + right: 30, + bottom: 10, + }; + const source = getDroppableDimension({ + descriptor: { + id: 'source', + type: 'TYPE', + }, + direction: axis.direction, + borderBox: box, + }); + const forward = getDroppableDimension({ + descriptor: { + id: 'forward', + type: 'TYPE', + }, + direction: axis.direction, + borderBox: { + ...box, + right: box.right + 1, + }, + }); + const droppables: DroppableDimensionMap = { + [source.descriptor.id]: source, + [forward.descriptor.id]: forward, + }; + + const result: ?DroppableDimension = getBestCrossAxisDroppable({ + isMovingForward: true, + pageBorderBoxCenter: source.page.borderBox.center, + source, + droppables, + viewport, + }); + + expect(result).toBe(forward); + }); + + it('should not allow movement when the droppables are on top of each other', () => { + const box: Spacing = { + top: 0, + left: 20, + right: 30, + bottom: 10, + }; + const source = getDroppableDimension({ + descriptor: { + id: 'source', + type: 'TYPE', + }, + direction: axis.direction, + borderBox: box, + }); + const forward = getDroppableDimension({ + descriptor: { + id: 'forward', + type: 'TYPE', + }, + direction: axis.direction, + borderBox: box, + }); + const droppables: DroppableDimensionMap = { + [source.descriptor.id]: source, + [forward.descriptor.id]: forward, + }; + + const result: ?DroppableDimension = getBestCrossAxisDroppable({ + isMovingForward: true, + pageBorderBoxCenter: source.page.borderBox.center, + source, + droppables, + viewport, + }); + + expect(result).toBe(null); + }); + + it('should not allow movement the right edge of the source is not greater than the right edge of the target', () => { + const box: Spacing = { + top: 0, + left: 20, + right: 30, + bottom: 10, + }; + const source = getDroppableDimension({ + descriptor: { + id: 'source', + type: 'TYPE', + }, + direction: axis.direction, + borderBox: box, + }); + const forward = getDroppableDimension({ + descriptor: { + id: 'forward', + type: 'TYPE', + }, + direction: axis.direction, + borderBox: { + ...box, + // forward a little bit + left: box.left + 1, + // not far enough + right: box.right - 1, + }, + }); + const droppables: DroppableDimensionMap = { + [source.descriptor.id]: source, + [forward.descriptor.id]: forward, + }; + + const result: ?DroppableDimension = getBestCrossAxisDroppable({ + isMovingForward: true, + pageBorderBoxCenter: source.page.borderBox.center, + source, + droppables, + viewport, + }); + + expect(result).toBe(null); + }); + }); + + describe('moving backwards', () => { + it('should allow overlap as long as the start of the target is before the start of the source', () => { + const box: Spacing = { + top: 0, + left: 20, + right: 30, + bottom: 10, + }; + const source = getDroppableDimension({ + descriptor: { + id: 'source', + type: 'TYPE', + }, + direction: axis.direction, + borderBox: box, + }); + const backwards = getDroppableDimension({ + descriptor: { + id: 'forward', + type: 'TYPE', + }, + direction: axis.direction, + borderBox: { + ...box, + left: box.left - 1, + }, + }); + const droppables: DroppableDimensionMap = { + [source.descriptor.id]: source, + [backwards.descriptor.id]: backwards, + }; + + const result: ?DroppableDimension = getBestCrossAxisDroppable({ + isMovingForward: false, + pageBorderBoxCenter: source.page.borderBox.center, + source, + droppables, + viewport, + }); + + expect(result).toBe(backwards); + }); + + it('should not allow movement when the droppables are on top of each other', () => { + const box: Spacing = { + top: 0, + left: 20, + right: 30, + bottom: 10, + }; + const source = getDroppableDimension({ + descriptor: { + id: 'source', + type: 'TYPE', + }, + direction: axis.direction, + borderBox: box, + }); + const backward = getDroppableDimension({ + descriptor: { + id: 'forward', + type: 'TYPE', + }, + direction: axis.direction, + borderBox: box, + }); + const droppables: DroppableDimensionMap = { + [source.descriptor.id]: source, + [backward.descriptor.id]: backward, + }; + + const result: ?DroppableDimension = getBestCrossAxisDroppable({ + isMovingForward: false, + pageBorderBoxCenter: source.page.borderBox.center, + source, + droppables, + viewport, + }); + + expect(result).toBe(null); + }); + + it('should not allow movement left edge of the source is not greater than the left edge of the target', () => { + const box: Spacing = { + top: 0, + left: 20, + right: 30, + bottom: 10, + }; + const source = getDroppableDimension({ + descriptor: { + id: 'source', + type: 'TYPE', + }, + direction: axis.direction, + borderBox: box, + }); + const backward = getDroppableDimension({ + descriptor: { + id: 'forward', + type: 'TYPE', + }, + direction: axis.direction, + borderBox: { + ...box, + // backward a little bit + right: box.right - 1, + // not far enough + left: box.left, + }, + }); + const droppables: DroppableDimensionMap = { + [source.descriptor.id]: source, + [backward.descriptor.id]: backward, + }; + + const result: ?DroppableDimension = getBestCrossAxisDroppable({ + isMovingForward: true, + pageBorderBoxCenter: source.page.borderBox.center, + source, + droppables, + viewport, + }); + + expect(result).toBe(null); + }); + }); + }); }); diff --git a/test/unit/state/move-cross-axis/get-closest-draggable.spec.js b/test/unit/state/move-in-direction/move-cross-axis/get-closest-draggable.spec.js similarity index 95% rename from test/unit/state/move-cross-axis/get-closest-draggable.spec.js rename to test/unit/state/move-in-direction/move-cross-axis/get-closest-draggable.spec.js index b84a76155e..1625073ed8 100644 --- a/test/unit/state/move-cross-axis/get-closest-draggable.spec.js +++ b/test/unit/state/move-in-direction/move-cross-axis/get-closest-draggable.spec.js @@ -1,22 +1,22 @@ // @flow import { getRect, type Rect, type Position } from 'css-box-model'; -import getClosestDraggable from '../../../../src/state/move-cross-axis/get-closest-draggable'; -import { scrollDroppable } from '../../../../src/state/droppable-dimension'; -import { add, distance, patch } from '../../../../src/state/position'; +import getClosestDraggable from '../../../../../src/state/move-in-direction/move-cross-axis/get-closest-draggable'; +import { scrollDroppable } from '../../../../../src/state/droppable-dimension'; +import { add, distance, patch } from '../../../../../src/state/position'; import { getDroppableDimension, getDraggableDimension, withAssortedSpacing, -} from '../../../utils/dimension'; -import { expandByPosition } from '../../../../src/state/spacing'; -import { horizontal, vertical } from '../../../../src/state/axis'; -import getViewport from '../../../../src/view/window/get-viewport'; +} from '../../../../utils/dimension'; +import { expandByPosition } from '../../../../../src/state/spacing'; +import { horizontal, vertical } from '../../../../../src/state/axis'; +import getViewport from '../../../../../src/view/window/get-viewport'; import type { Axis, DraggableDimension, DroppableDimension, Viewport, -} from '../../../../src/types'; +} from '../../../../../src/types'; const viewport: Viewport = getViewport(); diff --git a/test/unit/state/move-cross-axis/move-cross-axis.spec.js b/test/unit/state/move-in-direction/move-cross-axis/move-cross-axis.spec.js similarity index 87% rename from test/unit/state/move-cross-axis/move-cross-axis.spec.js rename to test/unit/state/move-in-direction/move-cross-axis/move-cross-axis.spec.js index 4ca6f861cf..87b0c55637 100644 --- a/test/unit/state/move-cross-axis/move-cross-axis.spec.js +++ b/test/unit/state/move-in-direction/move-cross-axis/move-cross-axis.spec.js @@ -1,20 +1,20 @@ // @flow -import moveCrossAxis from '../../../../src/state/move-cross-axis'; -import noImpact from '../../../../src/state/no-impact'; -import getViewport from '../../../../src/view/window/get-viewport'; +import moveCrossAxis from '../../../../../src/state/move-in-direction/move-cross-axis'; +import noImpact from '../../../../../src/state/no-impact'; +import getViewport from '../../../../../src/view/window/get-viewport'; import { getPreset, getDroppableDimension, getDraggableDimension, -} from '../../../utils/dimension'; -import type { Result } from '../../../../src/state/move-cross-axis/move-cross-axis-types'; +} from '../../../../utils/dimension'; +import type { Result } from '../../../../../src/state/move-in-direction/move-cross-axis/move-cross-axis-types'; import type { Viewport, DraggableDimension, DroppableDimension, DraggableDimensionMap, DroppableDimensionMap, -} from '../../../../src/types'; +} from '../../../../../src/types'; const preset = getPreset(); const viewport: Viewport = getViewport(); diff --git a/test/unit/state/move-cross-axis/move-to-new-droppable.spec.js b/test/unit/state/move-in-direction/move-cross-axis/move-to-new-droppable.spec.js similarity index 98% rename from test/unit/state/move-cross-axis/move-to-new-droppable.spec.js rename to test/unit/state/move-in-direction/move-cross-axis/move-to-new-droppable.spec.js index 5abe9c08ee..334279bd16 100644 --- a/test/unit/state/move-cross-axis/move-to-new-droppable.spec.js +++ b/test/unit/state/move-in-direction/move-cross-axis/move-to-new-droppable.spec.js @@ -1,26 +1,26 @@ // @flow import { type Position } from 'css-box-model'; -import moveToNewDroppable from '../../../../src/state/move-cross-axis/move-to-new-droppable'; -import type { Result } from '../../../../src/state/move-cross-axis/move-cross-axis-types'; -import { scrollDroppable } from '../../../../src/state/droppable-dimension'; -import moveToEdge from '../../../../src/state/move-to-edge'; -import { add, negate, patch } from '../../../../src/state/position'; -import { horizontal, vertical } from '../../../../src/state/axis'; +import moveToNewDroppable from '../../../../../src/state/move-in-direction/move-cross-axis/move-to-new-droppable'; +import type { Result } from '../../../../../src/state/move-in-direction/move-cross-axis/move-cross-axis-types'; +import { scrollDroppable } from '../../../../../src/state/droppable-dimension'; +import moveToEdge from '../../../../../src/state/move-to-edge'; +import { add, negate, patch } from '../../../../../src/state/position'; +import { horizontal, vertical } from '../../../../../src/state/axis'; import { getPreset, makeScrollable, getDraggableDimension, getDroppableDimension, -} from '../../../utils/dimension'; -import noImpact from '../../../../src/state/no-impact'; -import getViewport from '../../../../src/view/window/get-viewport'; +} from '../../../../utils/dimension'; +import noImpact from '../../../../../src/state/no-impact'; +import getViewport from '../../../../../src/view/window/get-viewport'; import type { Viewport, Axis, DragImpact, DraggableDimension, DroppableDimension, -} from '../../../../src/types'; +} from '../../../../../src/types'; const dontCare: Position = { x: 0, y: 0 }; const viewport: Viewport = getViewport(); diff --git a/test/unit/state/move-in-direction/move-in-direction.spec.js b/test/unit/state/move-in-direction/move-in-direction.spec.js new file mode 100644 index 0000000000..ff35648429 --- /dev/null +++ b/test/unit/state/move-in-direction/move-in-direction.spec.js @@ -0,0 +1,131 @@ +// @flow +import invariant from 'tiny-invariant'; +import type { DraggableLocation } from '../../../../src/types'; +import { vertical, horizontal } from '../../../../src/state/axis'; +import { getPreset } from '../../../utils/dimension'; +import getStatePreset from '../../../utils/get-simple-state-preset'; +import moveInDirection, { + type Result, +} from '../../../../src/state/move-in-direction'; + +describe('on the vertical axis', () => { + const preset = getPreset(vertical); + const state = getStatePreset(vertical); + + it('should move forward on a MOVE_DOWN', () => { + const result: ?Result = moveInDirection({ + state: state.dragging(), + action: 'MOVE_DOWN', + }); + + invariant(result, 'expected a result'); + const expected: DraggableLocation = { + droppableId: preset.home.descriptor.id, + index: 1, + }; + expect(result.impact.destination).toEqual(expected); + }); + + it('should move backwards on a MOVE_UP', () => { + const result: ?Result = moveInDirection({ + state: state.dragging(preset.inHome2.descriptor.id), + action: 'MOVE_UP', + }); + + invariant(result, 'expected a result'); + const expected: DraggableLocation = { + droppableId: preset.home.descriptor.id, + index: 0, + }; + expect(result.impact.destination).toEqual(expected); + }); + + it('should move cross axis forwards on a MOVE_RIGHT', () => { + const result: ?Result = moveInDirection({ + state: state.dragging(), + action: 'MOVE_RIGHT', + }); + + invariant(result, 'expected a result'); + const expected: DraggableLocation = { + droppableId: preset.foreign.descriptor.id, + index: 1, + }; + expect(result.impact.destination).toEqual(expected); + }); + + it('should move cross axis backwards on a MOVE_LEFT', () => { + const result: ?Result = moveInDirection({ + state: state.dragging(preset.inForeign1.descriptor.id), + action: 'MOVE_LEFT', + }); + + invariant(result, 'expected a result'); + const expected: DraggableLocation = { + droppableId: preset.home.descriptor.id, + index: 1, + }; + expect(result.impact.destination).toEqual(expected); + }); +}); + +describe('on the horizontal axis', () => { + const preset = getPreset(horizontal); + const state = getStatePreset(horizontal); + + it('should move forward on a MOVE_RIGHT', () => { + const result: ?Result = moveInDirection({ + state: state.dragging(), + action: 'MOVE_RIGHT', + }); + + invariant(result, 'expected a result'); + const expected: DraggableLocation = { + droppableId: preset.home.descriptor.id, + index: 1, + }; + expect(result.impact.destination).toEqual(expected); + }); + + it('should move backwards on a MOVE_LEFT', () => { + const result: ?Result = moveInDirection({ + state: state.dragging(preset.inHome2.descriptor.id), + action: 'MOVE_LEFT', + }); + + invariant(result, 'expected a result'); + const expected: DraggableLocation = { + droppableId: preset.home.descriptor.id, + index: 0, + }; + expect(result.impact.destination).toEqual(expected); + }); + + it('should move cross axis forwards on a MOVE_DOWN', () => { + const result: ?Result = moveInDirection({ + state: state.dragging(), + action: 'MOVE_DOWN', + }); + + invariant(result, 'expected a result'); + const expected: DraggableLocation = { + droppableId: preset.foreign.descriptor.id, + index: 1, + }; + expect(result.impact.destination).toEqual(expected); + }); + + it('should move cross axis backwards on a MOVE_UP', () => { + const result: ?Result = moveInDirection({ + state: state.dragging(preset.inForeign1.descriptor.id), + action: 'MOVE_UP', + }); + + invariant(result, 'expected a result'); + const expected: DraggableLocation = { + droppableId: preset.home.descriptor.id, + index: 1, + }; + expect(result.impact.destination).toEqual(expected); + }); +}); diff --git a/test/unit/state/move-to-next-index.spec.js b/test/unit/state/move-in-direction/move-to-next-index.spec.js similarity index 99% rename from test/unit/state/move-to-next-index.spec.js rename to test/unit/state/move-in-direction/move-to-next-index.spec.js index 22f581b88b..869ed7c531 100644 --- a/test/unit/state/move-to-next-index.spec.js +++ b/test/unit/state/move-in-direction/move-to-next-index.spec.js @@ -1,21 +1,21 @@ // @flow import { getRect, type Position } from 'css-box-model'; -import moveToNextIndex from '../../../src/state/move-to-next-index'; -import type { Result } from '../../../src/state/move-to-next-index/move-to-next-index-types'; -import { scrollDroppable } from '../../../src/state/droppable-dimension'; +import moveToNextIndex from '../../../../src/state/move-in-direction/move-to-next-index'; +import type { Result } from '../../../../src/state/move-in-direction/move-to-next-index/move-to-next-index-types'; +import { scrollDroppable } from '../../../../src/state/droppable-dimension'; import { getPreset, disableDroppable, getClosestScrollable, getDroppableDimension, getDraggableDimension, -} from '../../utils/dimension'; -import moveToEdge from '../../../src/state/move-to-edge'; -import noImpact, { noMovement } from '../../../src/state/no-impact'; -import { patch, subtract } from '../../../src/state/position'; -import { vertical, horizontal } from '../../../src/state/axis'; -import { isPartiallyVisible } from '../../../src/state/visibility/is-visible'; -import { createViewport } from '../../utils/viewport'; +} from '../../../utils/dimension'; +import moveToEdge from '../../../../src/state/move-to-edge'; +import noImpact, { noMovement } from '../../../../src/state/no-impact'; +import { patch, subtract } from '../../../../src/state/position'; +import { vertical, horizontal } from '../../../../src/state/axis'; +import { isPartiallyVisible } from '../../../../src/state/visibility/is-visible'; +import { createViewport } from '../../../utils/viewport'; import type { Viewport, Axis, @@ -24,7 +24,7 @@ import type { DraggableDimensionMap, DroppableDimension, DraggableLocation, -} from '../../../src/types'; +} from '../../../../src/types'; const origin: Position = { x: 0, y: 0 };