Skip to content

Commit

Permalink
move all placeholder buffering logic into getDroppableOver, isolate i…
Browse files Browse the repository at this point in the history
…sPointWithin from worrying about this. memoize runtime buffering logic where possible.
  • Loading branch information
jaredcrowe committed Oct 9, 2017
1 parent 2753a33 commit 40abf03
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 86 deletions.
6 changes: 3 additions & 3 deletions src/state/get-drag-impact/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,22 @@ type Args = {|
// all dimensions in system
draggables: DraggableDimensionMap,
droppables: DroppableDimensionMap,
previousDroppableOver: ?DroppableId,
previousDroppableOverId: ?DroppableId,
|}

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

// not dragging over anything
Expand Down
151 changes: 118 additions & 33 deletions src/state/get-droppable-over.js
Original file line number Diff line number Diff line change
@@ -1,64 +1,149 @@
// @flow
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 getDraggablesInsideDroppable from './get-draggables-inside-droppable';
import { addPosition } from './spacing';
import type {
DroppableId,
Position,
DimensionFragment,
DraggableDimension,
DraggableDimensionMap,
DroppableDimensionMap,
DroppableDimension,
DroppableDimensionMap,
DroppableId,
Position,
} from '../types';

const noPadding: Position = { x: 0, y: 0 };
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,
previousDroppableOver: ?DroppableId,
previousDroppableOverId: ?DroppableId,
};

export default ({
target,
draggable,
draggables,
droppables,
previousDroppableOver,
previousDroppableOverId,
}: Args): ?DroppableId => {
const maybe: ?DroppableDimension =
Object.keys(droppables)
.map((id: DroppableId): DroppableDimension => droppables[id])
.find((droppable: DroppableDimension): boolean => {
const placeholderPadding = (() => {
const isHomeDroppable = draggable.droppableId === droppable.id;
const isCurrentlyHovered = previousDroppableOver &&
previousDroppableOver === droppable.id;

if (isHomeDroppable || !isCurrentlyHovered) {
return noPadding;
}

const draggablesInDroppable = getDraggablesInsideDroppable(droppable, draggables);

if (!draggablesInDroppable.length) {
return noPadding;
}

const excessSpace = droppable.page.withMargin[droppable.axis.end] -
draggablesInDroppable[draggablesInDroppable.length - 1]
.page.withMargin[droppable.axis.end];
const paddingSize = Math.max(
draggable.page.withMargin[droppable.axis.size] - excessSpace,
0
);

return patch(droppable.axis.line, paddingSize);
})();
// Add the size of a placeholder to a droppable's dimensions (if necessary)
const bufferedDroppable = bufferDroppable({
draggable, draggables, droppable, previousDroppableOverId,
});

return isPointWithinDroppable(droppable, placeholderPadding)(target);
return isPointWithinDroppable(bufferedDroppable)(target);
});

return maybe ? maybe.id : null;
Expand Down
22 changes: 6 additions & 16 deletions src/state/is-within-visible-bounds-of-droppable.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow
import isWithin from './is-within';
import { subtract } from './position';
import { addPosition, offset } from './spacing';
import { offset } from './spacing';
import type {
Position,
DraggableDimension,
Expand All @@ -10,13 +10,9 @@ import type {
Spacing,
} from '../types';

const noPadding: Position = { x: 0, y: 0 };

const getVisibleBounds = (
droppable: DroppableDimension,
padding: Position = noPadding
): Spacing => {
const getVisibleBounds = (droppable: DroppableDimension): Spacing => {
const { scroll, bounds: containerBounds } = droppable.container;

// Calculate the mid-drag scroll ∆ of the scroll container
const containerScrollDiff: Position = subtract(scroll.initial, scroll.current);

Expand All @@ -33,13 +29,10 @@ const getVisibleBounds = (
left: Math.max(droppableBounds.left, containerBounds.left),
};

const includingPadding = addPosition(clippedBounds, padding);

return includingPadding;
return clippedBounds;
};

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

Expand All @@ -49,11 +42,8 @@ const isPointWithin = (bounds: Spacing) => {
);
};

export const isPointWithinDroppable = (
droppable: DroppableDimension,
padding: Position = noPadding
) => (
isPointWithin(getVisibleBounds(droppable, padding))
export const isPointWithinDroppable = (droppable: DroppableDimension) => (
isPointWithin(getVisibleBounds(droppable))
);

export const isDraggableWithin = (bounds: Spacing) => {
Expand Down
6 changes: 3 additions & 3 deletions src/state/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ const move = ({
windowScroll: currentWindowScroll,
};

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

Expand All @@ -116,15 +116,15 @@ const move = ({
draggable: state.dimension.draggable[current.id],
draggables: state.dimension.draggable,
droppables: state.dimension.droppable,
previousDroppableOver,
previousDroppableOverId,
}));

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

Expand Down
2 changes: 1 addition & 1 deletion src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export type CurrentDrag = {|
|}

type PreviousDrag = {
droppableOver: ?DroppableId,
droppableOverId: ?DroppableId,
};

// published when a drag starts
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

0 comments on commit 40abf03

Please sign in to comment.