import { getBox, withScroll, createBox, expand } from 'css-box-model'; import type { BoxModel, Position, Spacing } from 'css-box-model'; import getDroppableDimension from '../../state/droppable/get-droppable'; import type { Closest } from '../../state/droppable/get-droppable'; import type { Env } from './get-env'; import type { DroppableDimension, DroppableDescriptor, Direction, ScrollSize, } from '../../types'; import getScroll from './get-scroll'; const getClient = ( targetRef: HTMLElement, closestScrollable?: Element | null, ): BoxModel => { const base: BoxModel = getBox(targetRef); // Droppable has no scroll parent if (!closestScrollable) { return base; } // Droppable is not the same as the closest scrollable if (targetRef !== closestScrollable) { return base; } // Droppable is scrollable // Element.getBoundingClient() returns a clipped padding box: // When not scrollable: the full size of the element // When scrollable: the visible size of the element // (which is not the full width of its scrollable content) // So we recalculate the borderBox of a scrollable droppable to give // it its full dimensions. This will be cut to the correct size by the frame // Creating the paddingBox based on scrollWidth / scrollTop // scrollWidth / scrollHeight are based on the paddingBox of an element // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight const top: number = base.paddingBox.top - closestScrollable.scrollTop; const left: number = base.paddingBox.left - closestScrollable.scrollLeft; const bottom: number = top + closestScrollable.scrollHeight; const right: number = left + closestScrollable.scrollWidth; // unclipped padding box const paddingBox: Spacing = { top, right, bottom, left, }; // Creating the borderBox by adding the borders to the paddingBox const borderBox: Spacing = expand(paddingBox, base.border); // We are not accounting for scrollbars // Adjusting for scrollbars is hard because: // - they are different between browsers // - scrollbars can be activated and removed during a drag // We instead account for this slightly in our auto scroller const client: BoxModel = createBox({ borderBox, margin: base.margin, border: base.border, padding: base.padding, }); return client; }; interface Args { ref: HTMLElement; descriptor: DroppableDescriptor; env: Env; windowScroll: Position; direction: Direction; isDropDisabled: boolean; isCombineEnabled: boolean; shouldClipSubject: boolean; } export default ({ ref, descriptor, env, windowScroll, direction, isDropDisabled, isCombineEnabled, shouldClipSubject, }: Args): DroppableDimension => { const closestScrollable: Element | null = env.closestScrollable; const client: BoxModel = getClient(ref, closestScrollable); const page: BoxModel = withScroll(client, windowScroll); const closest: Closest | null = (() => { if (!closestScrollable) { return null; } const frameClient: BoxModel = getBox(closestScrollable); const scrollSize: ScrollSize = { scrollHeight: closestScrollable.scrollHeight, scrollWidth: closestScrollable.scrollWidth, }; return { client: frameClient, page: withScroll(frameClient, windowScroll), scroll: getScroll(closestScrollable), scrollSize, shouldClipSubject, }; })(); const dimension: DroppableDimension = getDroppableDimension({ descriptor, isEnabled: !isDropDisabled, isCombineEnabled, isFixedOnPage: env.isFixedOnPage, direction, client, page, closest, }); return dimension; };