You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
64 lines
2.6 KiB
64 lines
2.6 KiB
// detectOverflow — PURE. Compares the proposed element rect against the clipping
|
|
// region that the read phase already walked (`state.clipping`, viewport coords).
|
|
// No DOM read: the offsetParent→viewport conversion is folded into
|
|
// `state.viewportDelta` + `state.offsetScale` upfront. Reimplemented from
|
|
// floating-ui's `detectOverflow` (spec), our idioms.
|
|
|
|
import { getPaddingObject } from './geometry'
|
|
import { rectToClientRect } from './rects'
|
|
import type { MiddlewareState, Padding, SideObject } from './types'
|
|
|
|
export type DetectOverflowOptions = {
|
|
padding?: Padding
|
|
/** Which element's rect to test — `'floating'` at `state.x/y`, or the
|
|
* reference rect. @default 'floating' */
|
|
elementContext?: 'floating' | 'reference'
|
|
/** Test against the OTHER element's clipping region (swaps the boundary
|
|
* source). @default false */
|
|
altBoundary?: boolean
|
|
}
|
|
|
|
/**
|
|
* Per-side overflow of the tested element past its clipping boundary, in CSS px:
|
|
* positive = overflowing by that many pixels, negative = pixels of slack before
|
|
* it would overflow, 0 = flush.
|
|
*/
|
|
export function detectOverflow(
|
|
state: MiddlewareState,
|
|
options: DetectOverflowOptions = {}
|
|
): SideObject {
|
|
const { padding = 0, elementContext = 'floating', altBoundary = false } = options
|
|
const { x, y, rects, clipping, viewportDelta, offsetScale } = state
|
|
|
|
const paddingObject = getPaddingObject(padding)
|
|
const altContext = elementContext === 'floating' ? 'reference' : 'floating'
|
|
const context = altBoundary ? altContext : elementContext
|
|
|
|
// `clipping.reference` is only read when needed; fall back to the floating
|
|
// clip (identical when the boundary is explicit — the soma layer's only case).
|
|
const clip = clipping[context] ?? clipping.floating
|
|
const clippingClientRect = rectToClientRect(clip)
|
|
|
|
// The tested element's rect in offsetParent-relative coords…
|
|
const elementRect =
|
|
elementContext === 'floating'
|
|
? { x, y, width: rects.floating.width, height: rects.floating.height }
|
|
: rects.reference
|
|
// …converted to viewport coords purely via the precomputed delta.
|
|
const elementClientRect = rectToClientRect({
|
|
x: elementRect.x + viewportDelta.x,
|
|
y: elementRect.y + viewportDelta.y,
|
|
width: elementRect.width,
|
|
height: elementRect.height
|
|
})
|
|
|
|
return {
|
|
top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y,
|
|
bottom:
|
|
(elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y,
|
|
left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x,
|
|
right:
|
|
(elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x
|
|
}
|
|
}
|