Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

droppable dimensions include the size of the draggable placeholder if… #132

Merged
merged 4 commits into from
Oct 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/state/get-drag-impact/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,24 @@ type Args = {|
draggable: DraggableDimension,
// all dimensions in system
draggables: DraggableDimensionMap,
droppables: DroppableDimensionMap
droppables: DroppableDimensionMap,
previousDroppableOverId: ?DroppableId,
|}

export default ({
pageCenter,
draggable,
draggables,
droppables,
previousDroppableOverId,
}: Args): DragImpact => {
const destinationId: ?DroppableId = getDroppableOver(
pageCenter, droppables,
);
const destinationId: ?DroppableId = getDroppableOver({
target: pageCenter,
draggable,
draggables,
droppables,
previousDroppableOverId,
});

// not dragging over anything
if (!destinationId) {
Expand Down
144 changes: 136 additions & 8 deletions src/state/get-droppable-over.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,150 @@
// @flow
import { isPointWithin } from './is-within-visible-bounds-of-droppable';
import memoizeOne from 'memoize-one';
import { getFragment } from './dimension';
import getClientRect from './get-client-rect';
import getDraggablesInsideDroppable from './get-draggables-inside-droppable';
import { isPointWithinDroppable } from './is-within-visible-bounds-of-droppable';
import { patch } from './position';
import { addPosition } from './spacing';
import type {
DimensionFragment,
DraggableDimension,
DraggableDimensionMap,
DroppableDimension,
DroppableDimensionMap,
DroppableId,
Position,
DroppableDimensionMap,
DroppableDimension,
} from '../types';

export default (
const noBuffer: Position = { x: 0, y: 0 };

const bufferDimensionFragment = (buffer: Position) => (fragment: DimensionFragment) => (
getFragment(getClientRect(addPosition(fragment, buffer)))
);

const addBufferToDroppableDimension = memoizeOne((
buffer: Position,
droppable: DroppableDimension
): DroppableDimension => {
const { id, axis, isEnabled, client, container, page } = droppable;
const withBuffer = bufferDimensionFragment(buffer);

const newClient = {
withoutMargin: withBuffer(client.withoutMargin),
withMargin: withBuffer(client.withMargin),
withMarginAndPadding: withBuffer(client.withMarginAndPadding),
};

const newPage = {
withoutMargin: withBuffer(page.withoutMargin),
withMargin: withBuffer(page.withMargin),
withMarginAndPadding: withBuffer(page.withMarginAndPadding),
};

// We only want to add the buffer to the container dimensions
// if the droppable isn't clipped by a scroll container
const shouldBufferContainer = droppable.page.withMargin[droppable.axis.size] <=
droppable.container.bounds[droppable.axis.size];
const newContainerBounds = shouldBufferContainer
? withBuffer(container.bounds)
: { ...container.bounds };

return {
id,
axis,
isEnabled,
client: newClient,
page: newPage,
container: {
scroll: container.scroll,
bounds: newContainerBounds,
},
};
});

const calculateBufferSize = memoizeOne((
draggable: DraggableDimension,
draggables: DraggableDimensionMap,
droppable: DroppableDimension,
) => {
// We can't always simply add the placeholder size to the droppable size.
// If a droppable has a min-height there will be scenarios where it has
// some items in it, but not enough to completely fill its size.
// In this case - when the droppable already contains excess space - we
// don't need to add the full placeholder size.

const draggablesInDroppable = getDraggablesInsideDroppable(droppable, draggables);

if (!draggablesInDroppable.length) {
return noBuffer;
}
const excessSpace = droppable.page.withMargin[droppable.axis.end] -
draggablesInDroppable[draggablesInDroppable.length - 1]
.page.withMargin[droppable.axis.end];
const bufferSize = Math.max(
draggable.page.withMargin[droppable.axis.size] - excessSpace,
0
);

const buffer = patch(droppable.axis.line, bufferSize);

return buffer;
});

type GetBufferedDroppableArgs = {
draggable: DraggableDimension,
draggables: DraggableDimensionMap,
droppable: DroppableDimension,
previousDroppableOverId: ?DroppableId,
};

const bufferDroppable = ({
draggable,
draggables,
droppable,
previousDroppableOverId,
}: GetBufferedDroppableArgs): DroppableDimension => {
const isHomeDroppable = draggable.droppableId === droppable.id;
const isCurrentlyHovered = previousDroppableOverId &&
previousDroppableOverId === droppable.id;

// We only include the placeholder size if it's a
// foreign list and is currently being hovered over
if (isHomeDroppable || !isCurrentlyHovered) {
return droppable;
}

const buffer = calculateBufferSize(draggable, draggables, droppable);

return addBufferToDroppableDimension(buffer, droppable);
};

type Args = {
target: Position,
draggable: DraggableDimension,
draggables: DraggableDimensionMap,
droppables: DroppableDimensionMap,
): ?DroppableId => {
previousDroppableOverId: ?DroppableId,
};

export default ({
target,
draggable,
draggables,
droppables,
previousDroppableOverId,
}: Args): ?DroppableId => {
const maybe: ?DroppableDimension =
Object.keys(droppables)
.map((id: DroppableId): DroppableDimension => droppables[id])
.find((droppable: DroppableDimension): boolean => (
isPointWithin(droppable)(target)
));
.find((droppable: DroppableDimension): boolean => {
// Add the size of a placeholder to a droppable's dimensions (if necessary)
const bufferedDroppable = bufferDroppable({
draggable, draggables, droppable, previousDroppableOverId,
});

return isPointWithinDroppable(bufferedDroppable)(target);
});

return maybe ? maybe.id : null;
};
18 changes: 13 additions & 5 deletions src/state/is-within-visible-bounds-of-droppable.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type {
Spacing,
} from '../types';

export const isPointWithin = (droppable: DroppableDimension) => {
const getVisibleBounds = (droppable: DroppableDimension): Spacing => {
const { scroll, bounds: containerBounds } = droppable.container;

// Calculate the mid-drag scroll ∆ of the scroll container
Expand All @@ -29,17 +29,25 @@ export const isPointWithin = (droppable: DroppableDimension) => {
left: Math.max(droppableBounds.left, containerBounds.left),
};

const isWithinHorizontal = isWithin(clippedBounds.left, clippedBounds.right);
const isWithinVertical = isWithin(clippedBounds.top, clippedBounds.bottom);
return clippedBounds;
};

const isPointWithin = (bounds: Spacing) => {
const isWithinHorizontal = isWithin(bounds.left, bounds.right);
const isWithinVertical = isWithin(bounds.top, bounds.bottom);

return (point: Position): boolean => (
isWithinHorizontal(point.x) &&
isWithinVertical(point.y)
);
};

export const isDraggableWithin = (droppable: DroppableDimension) => {
const { top, right, bottom, left } = droppable.container.bounds;
export const isPointWithinDroppable = (droppable: DroppableDimension) => (
isPointWithin(getVisibleBounds(droppable))
);

export const isDraggableWithin = (bounds: Spacing) => {
const { top, right, bottom, left } = bounds;

// There are some extremely minor inaccuracy in the calculations of margins (~0.001)
// To allow for this inaccuracy we make the dimension slightly bigger for this calculation
Expand Down
2 changes: 1 addition & 1 deletion src/state/move-cross-axis/get-closest-draggable.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default ({
return null;
}

const isWithinDestination = isDraggableWithin(destination);
const isWithinDestination = isDraggableWithin(destination.container.bounds);

const result: DraggableDimension[] = insideDestination
// Remove any options that are hidden by overflow
Expand Down
4 changes: 2 additions & 2 deletions src/state/move-to-next-index/in-foreign-list.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow
import getDraggablesInsideDroppable from '../get-draggables-inside-droppable';
import { isPointWithin } from '../is-within-visible-bounds-of-droppable';
import { isPointWithinDroppable } from '../is-within-visible-bounds-of-droppable';
import { patch } from '../position';
import moveToEdge from '../move-to-edge';
import type { Edge } from '../move-to-edge';
Expand Down Expand Up @@ -84,7 +84,7 @@ export default ({
return true;
}

return isPointWithin(droppable)(newCenter);
return isPointWithinDroppable(droppable)(newCenter);
})();

if (!isVisible) {
Expand Down
5 changes: 2 additions & 3 deletions src/state/move-to-next-index/in-home-list.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow
import memoizeOne from 'memoize-one';
import getDraggablesInsideDroppable from '../get-draggables-inside-droppable';
import { isPointWithin } from '../is-within-visible-bounds-of-droppable';
import { isPointWithinDroppable } from '../is-within-visible-bounds-of-droppable';
import { patch } from '../position';
import moveToEdge from '../move-to-edge';
import type { Edge } from '../move-to-edge';
Expand Down Expand Up @@ -83,8 +83,7 @@ export default ({
});

// Currently not supporting moving a draggable outside the visibility bounds of a droppable

const isVisible: boolean = isPointWithin(droppable)(newCenter);
const isVisible: boolean = isPointWithinDroppable(droppable)(newCenter);

if (!isVisible) {
return null;
Expand Down
8 changes: 8 additions & 0 deletions src/state/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,25 @@ const move = ({
windowScroll: currentWindowScroll,
};

const previousDroppableOverId: ?DroppableId = state.drag && state.drag.impact.destination
? state.drag.impact.destination.droppableId
: null;

const newImpact: DragImpact = (impact || getDragImpact({
pageCenter: page.center,
draggable: state.dimension.draggable[current.id],
draggables: state.dimension.draggable,
droppables: state.dimension.droppable,
previousDroppableOverId,
}));

const drag: DragState = {
initial,
impact: newImpact,
current,
previous: {
droppableOverId: previousDroppableOverId,
},
};

return {
Expand Down
6 changes: 6 additions & 0 deletions src/state/spacing.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ export const add = (spacing1: Spacing, spacing2: Spacing): Spacing => ({
bottom: spacing1.bottom + spacing2.bottom,
});

export const addPosition = (spacing: Spacing, position: Position): Spacing => ({
...spacing,
right: spacing.right + position.x,
bottom: spacing.bottom + position.y,
});

export const isEqual = (spacing1: Spacing, spacing2: Spacing): boolean => (
spacing1.top === spacing2.top &&
spacing1.right === spacing2.right &&
Expand Down
5 changes: 5 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ export type CurrentDrag = {|
shouldAnimate: boolean,
|}

type PreviousDrag = {
droppableOverId: ?DroppableId,
};

// published when a drag starts
export type DragStart = {|
draggableId: DraggableId,
Expand All @@ -192,6 +196,7 @@ export type DragState = {|
initial: InitialDrag,
current: CurrentDrag,
impact: DragImpact,
previous?: PreviousDrag,
|}

export type DropTrigger = 'DROP' | 'CANCEL';
Expand Down
3 changes: 2 additions & 1 deletion stories/src/primatives/quote-list.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ const DropZone = styled.div`
`;

const ScrollContainer = styled.div`
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
max-height: 300px;
`;

Expand Down
Loading