UNPKG

react-beautiful-dnd-next

Version:

Beautiful and accessible drag and drop for lists with React

151 lines (131 loc) 4.81 kB
// @flow import invariant from 'tiny-invariant'; import { withScroll, createBox, type BoxModel, type Spacing, type Rect, } from 'css-box-model'; import getDroppableDimension, { type Closest, } from '../../droppable/get-droppable'; import type { Viewport, DroppableDimension, DroppableDimensionMap, Scrollable, Axis, } from '../../../types'; import { isEqual } from '../../spacing'; import scrollDroppable from '../../droppable/scroll-droppable'; import { removePlaceholder } from '../../droppable/with-placeholder'; import getFrame from '../../get-frame'; import { toDroppableMap } from '../../dimension-structures'; const throwIfSpacingChange = (old: BoxModel, fresh: BoxModel) => { if (process.env.NODE_ENV !== 'production') { const getMessage = (spacingType: string) => `Cannot change the ${spacingType} of a Droppable during a drag`; invariant(isEqual(old.margin, fresh.margin), getMessage('margin')); invariant(isEqual(old.border, fresh.border), getMessage('border')); invariant(isEqual(old.padding, fresh.padding), getMessage('padding')); } }; const adjustBorderBoxSize = (axis: Axis, old: Rect, fresh: Rect): Spacing => ({ // top and left positions cannot change top: old.top, left: old.left, // this is the main logic of this function - the size adjustment right: old.left + fresh.width, bottom: old.top + fresh.height, }); type Args = {| existing: DroppableDimensionMap, modified: DroppableDimension[], viewport: Viewport, |}; export default ({ modified, existing, viewport, }: Args): DroppableDimensionMap => { // dynamically adjusting the client subject and page subject // of a droppable in response to dynamic additions and removals // No existing droppables modified if (!modified.length) { return existing; } const adjusted: DroppableDimension[] = modified.map( (provided: DroppableDimension): DroppableDimension => { const raw: ?DroppableDimension = existing[provided.descriptor.id]; invariant(raw, 'Could not locate droppable in existing droppables'); const hasPlaceholder: boolean = Boolean(raw.subject.withPlaceholder); const dimension: DroppableDimension = hasPlaceholder ? removePlaceholder(raw) : raw; const oldClient: BoxModel = dimension.client; const newClient: BoxModel = provided.client; const oldScrollable: Scrollable = getFrame(dimension); const newScrollable: Scrollable = getFrame(provided); // Extra checks to help with development if (process.env.NODE_ENV !== 'production') { throwIfSpacingChange(dimension.client, provided.client); throwIfSpacingChange( oldScrollable.frameClient, newScrollable.frameClient, ); const isFrameEqual: boolean = oldScrollable.frameClient.borderBox.height === newScrollable.frameClient.borderBox.height && oldScrollable.frameClient.borderBox.width === newScrollable.frameClient.borderBox.width; invariant( isFrameEqual, 'The width and height of your Droppable scroll container cannot change when adding or removing Draggables during a drag', ); } const client: BoxModel = createBox({ borderBox: adjustBorderBoxSize( dimension.axis, oldClient.borderBox, newClient.borderBox, ), margin: oldClient.margin, border: oldClient.border, padding: oldClient.padding, }); const closest: Closest = { // not allowing a change to the scrollable frame size during a drag client: oldScrollable.frameClient, page: withScroll(oldScrollable.frameClient, viewport.scroll.initial), shouldClipSubject: oldScrollable.shouldClipSubject, // the scroll size can change during a drag scrollSize: newScrollable.scrollSize, // using the initial scroll point scroll: oldScrollable.scroll.initial, }; const withSizeChanged: DroppableDimension = getDroppableDimension({ descriptor: provided.descriptor, isEnabled: provided.isEnabled, isCombineEnabled: provided.isCombineEnabled, isFixedOnPage: provided.isFixedOnPage, direction: provided.axis.direction, client, page: withScroll(client, viewport.scroll.initial), closest, }); const scrolled: DroppableDimension = scrollDroppable( withSizeChanged, // TODO: use .initial - i guess both work though.. newScrollable.scroll.current, ); return scrolled; }, ); const result: DroppableDimensionMap = { ...existing, // will override any conflicts with existing ...toDroppableMap(adjusted), }; return result; };