diff --git a/src/state/get-best-droppable.js b/src/state/get-best-cross-axis-droppable.js similarity index 71% rename from src/state/get-best-droppable.js rename to src/state/get-best-cross-axis-droppable.js index b78c419356..e6546b731a 100644 --- a/src/state/get-best-droppable.js +++ b/src/state/get-best-cross-axis-droppable.js @@ -1,31 +1,18 @@ // @flow import memoizeOne from 'memoize-one'; -import { distance } from './position'; +import { closest } from './position'; import type { Axis, Position, DimensionFragment, - DraggableId, DroppableId, - DraggableDimension, DroppableDimension, - DraggableDimensionMap, DroppableDimensionMap, } from '../types'; -type DroppableCornerMap = {| - [id: DroppableId]: Position[], -|} - -type GetBestDroppableArgs = {| - draggableId: DraggableId, - center: Position, - isMovingForward: boolean, - plane: 'main-axis' | 'cross-axis', - // the droppable the draggable is currently in - droppableId: DroppableId, - droppables: DroppableDimensionMap, - draggables: DraggableDimensionMap, +type DistanceToDroppable = {| + id: DroppableId, + distance: number, |} const sortOnCrossAxis = memoizeOne( @@ -38,23 +25,35 @@ const sortOnCrossAxis = memoizeOne( ) ); -type IsWithResultFn = (number) => boolean; - -const isWithin = (lowerBound: number, upperBound: number): IsWithResultFn => +const isWithin = (lowerBound: number, upperBound: number): ((number) => boolean) => (value: number): boolean => value <= upperBound && value >= lowerBound; +const getCorners = (droppable: DroppableDimension): Position[] => { + const fragment: DimensionFragment = droppable.page.withMargin; + + return [ + { x: fragment.left, y: fragment.top }, + { x: fragment.right, y: fragment.top }, + { x: fragment.left, y: fragment.bottom }, + { x: fragment.right, y: fragment.bottom }, + ]; +}; + +type GetBestDroppableArgs = {| + isMovingForward: boolean, + center: Position, + droppableId: DroppableId, + droppables: DroppableDimensionMap, +|} + export default ({ isMovingForward, - draggableId, center, droppableId, droppables, - draggables, }: GetBestDroppableArgs): ?DroppableId => { - const draggable: DraggableDimension = draggables[draggableId]; const source: DroppableDimension = droppables[droppableId]; const axis: Axis = source.axis; - const sorted: DroppableDimension[] = sortOnCrossAxis(droppables, axis); const candidates: DroppableDimension[] = @@ -80,15 +79,15 @@ export default ({ sourceFragment[axis.start], sourceFragment[axis.end] ); - const isBetweenDestBounds = isWithin( + const isBetweenDestinationBounds = isWithin( destinationFragment[axis.start], destinationFragment[axis.end] ); return isBetweenSourceBounds(destinationFragment[axis.start]) || isBetweenSourceBounds(destinationFragment[axis.end]) || - isBetweenDestBounds(sourceFragment[axis.start]) || - isBetweenDestBounds(sourceFragment[axis.end]); + isBetweenDestinationBounds(sourceFragment[axis.start]) || + isBetweenDestinationBounds(sourceFragment[axis.end]); }) // 4. Find the droppables that have the same cross axis value as the first item .filter((droppable: DroppableDimension, index: number, array: DroppableDimension[]): boolean => @@ -113,19 +112,17 @@ export default ({ // 1. Get the distance to all of the corner points // 2. Find the closest corner to current center // 3. in the event of a tie: choose the corner that is closest to {x: 0, y: 0} - const items: DroppableDimension[] = - candidates.map((droppable: DroppableDimension): DroppableCornerMap => { - const fragment: DimensionFragment = droppable.page.withMargin; - const first: Position = { - x: fragment[axis.crossAxisStart], - y: fragment[axis.start], - }; - const second: Position = { - x: 2, - y: 3, - }; - return { - [droppable.id]: [first, second], - }; - }); + const bestId: DroppableId = + candidates.map((droppable: DroppableDimension): DistanceToDroppable => ({ + id: droppableId, + // two of the corners will be redundant, but it is *way* easier + // to pass every corner than to conditionally grab the right ones + distance: closest(center, getCorners(droppable)), + })) + // the item with the shortest distance will be first + .sort((a: DistanceToDroppable, b: DistanceToDroppable) => a.distance - b.distance) + // TODO: what if there is a tie? + .map(a => a.id)[0]; + + return bestId; }; diff --git a/src/state/get-best-droppable-rules.md b/src/state/get-best-droppable-rules.md deleted file mode 100644 index 0ec7b58094..0000000000 --- a/src/state/get-best-droppable-rules.md +++ /dev/null @@ -1,43 +0,0 @@ - -## Rules for finding the best Droppable: - -### 1. Find lists on the cross axis - -Find the list(s) that are closest on the cross axis - -Conditions -1. The list must have one corner with the size (height: vertical) of the source list -2. The list must be visible to the user - -If more than one list is as close on the cross axis, then: - -### 2. Find the closest corner - -Based on the draggable items current center position, we need to find the list that -has the closest corner point. That is the closest list. - -We do not need to consider the conditions in step 1 as they have already been applied - -## Rules for finding the best location within a Droppable - -### If moving on the main axis -Move into the first / last position depending on if you are leaving the front / back of the Droppable - -nice - -### If moving on the cross axis - -#### Moving to empty list - -Move to the top (vertical list) / left (horizontal list) of the list - -#### Moving to populated list - -1. Find the draggable with the closest center position - -If there is more than one with the closest - choose the the one closest to the top left corner of the page - -2. Move below the item if the Draggables current center position is less than the destination. Otherwise, move above -Below = go below -Above = go above -Equal = go above \ No newline at end of file diff --git a/src/state/get-best-main-axis-droppable.js b/src/state/get-best-main-axis-droppable.js new file mode 100644 index 0000000000..3fa895ad4e --- /dev/null +++ b/src/state/get-best-main-axis-droppable.js @@ -0,0 +1,60 @@ +// @flow +import memoizeOne from 'memoize-one'; +import { closest } from './position'; +import type { + Axis, + Position, + DimensionFragment, + DroppableId, + DroppableDimension, + DroppableDimensionMap, +} from '../types'; + +type DistanceToDroppable = {| + id: DroppableId, + distance: number, +|} + +const sortOnMainAxis = memoizeOne( + (droppables: DroppableDimensionMap, axis: Axis): DroppableDimension[] => + Object.keys(droppables) + .map((key: DroppableId): DroppableDimension => droppables[key]) + .sort((a: DroppableDimension, b: DroppableDimension) => ( + a.page.withMargin[axis.start] - b.page.withMargin[axis.start] + ) + ) +); + +const isWithin = (lowerBound: number, upperBound: number): ((number) => boolean) => + (value: number): boolean => value <= upperBound && value >= lowerBound; + +const getCorners = (droppable: DroppableDimension): Position[] => { + const fragment: DimensionFragment = droppable.page.withMargin; + + return [ + { x: fragment.left, y: fragment.top }, + { x: fragment.right, y: fragment.top }, + { x: fragment.left, y: fragment.bottom }, + { x: fragment.right, y: fragment.bottom }, + ]; +}; + +type GetBestDroppableArgs = {| + isMovingForward: boolean, + center: Position, + droppableId: DroppableId, + droppables: DroppableDimensionMap, +|} + +export default ({ + isMovingForward, + center, + droppableId, + droppables, +}: GetBestDroppableArgs): ?DroppableId => { + const source: DroppableDimension = droppables[droppableId]; + const axis: Axis = source.axis; + const sorted: DroppableDimension[] = sortOnMainAxis(droppables, axis); + + // ... +}; diff --git a/src/state/position.js b/src/state/position.js index f792d736bd..35db5637af 100644 --- a/src/state/position.js +++ b/src/state/position.js @@ -32,3 +32,7 @@ export const distance = (point1: Position, point2: Position): number => Math.pow((point2.x - point1.x), 2) + Math.pow((point2.y - point1.y), 2) ); + +// When given a list of points, it finds the smallest distance to any point +export const closest = (target: Position, points: Position[]): number => + Math.min(...points.map((point: Position) => distance(target, point)));