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.1 KiB
64 lines
2.1 KiB
|
3 months ago
|
// offset — shifts the floating element along the main / cross / alignment axes.
|
||
|
|
// PURE. Reimplemented from floating-ui's `offset` (spec).
|
||
|
|
|
||
|
3 months ago
|
import type { Placement } from '../placement'
|
||
|
3 months ago
|
import { getAlignment, getSide, getSideAxis } from '../geometry'
|
||
|
|
import type { Coords, Middleware, MiddlewareState } from '../types'
|
||
|
|
|
||
|
|
export type OffsetOptions =
|
||
|
|
| number
|
||
|
|
| { mainAxis?: number; crossAxis?: number; alignmentAxis?: number | null }
|
||
|
|
|
||
|
|
const ORIGIN_SIDES = new Set<string>(['left', 'top'])
|
||
|
|
|
||
|
|
function convertValueToCoords(state: MiddlewareState, options: OffsetOptions): Coords {
|
||
|
|
const { placement, rtl } = state
|
||
|
|
const side = getSide(placement)
|
||
|
|
const alignment = getAlignment(placement)
|
||
|
|
const isVertical = getSideAxis(placement) === 'y'
|
||
|
|
const mainAxisMulti = ORIGIN_SIDES.has(side) ? -1 : 1
|
||
|
|
const crossAxisMulti = rtl && isVertical ? -1 : 1
|
||
|
|
|
||
|
|
let mainAxis: number
|
||
|
|
let crossAxis: number
|
||
|
|
let alignmentAxis: number | null
|
||
|
|
if (typeof options === 'number') {
|
||
|
|
mainAxis = options
|
||
|
|
crossAxis = 0
|
||
|
|
alignmentAxis = null
|
||
|
|
} else {
|
||
|
|
mainAxis = options.mainAxis || 0
|
||
|
|
crossAxis = options.crossAxis || 0
|
||
|
|
alignmentAxis = options.alignmentAxis ?? null
|
||
|
|
}
|
||
|
|
|
||
|
|
if (alignment && typeof alignmentAxis === 'number') {
|
||
|
|
crossAxis = alignment === 'end' ? alignmentAxis * -1 : alignmentAxis
|
||
|
|
}
|
||
|
|
|
||
|
|
return isVertical
|
||
|
|
? { x: crossAxis * crossAxisMulti, y: mainAxis * mainAxisMulti }
|
||
|
|
: { x: mainAxis * mainAxisMulti, y: crossAxis * crossAxisMulti }
|
||
|
|
}
|
||
|
|
|
||
|
|
export function offset(options: OffsetOptions = 0): Middleware {
|
||
|
|
return {
|
||
|
|
name: 'offset',
|
||
|
|
options,
|
||
|
|
fn(state) {
|
||
|
|
const { x, y, placement, middlewareData } = state
|
||
|
|
const diff = convertValueToCoords(state, options)
|
||
|
|
|
||
|
|
// If the placement is unchanged and the arrow applied an alignment
|
||
|
|
// offset, leave the coords alone (offset already ran for this placement).
|
||
|
|
const offsetData = middlewareData.offset as { placement?: Placement } | undefined
|
||
|
|
const arrowData = middlewareData.arrow as { alignmentOffset?: number } | undefined
|
||
|
|
if (placement === offsetData?.placement && arrowData?.alignmentOffset) {
|
||
|
|
return {}
|
||
|
|
}
|
||
|
|
|
||
|
|
return { x: x + diff.x, y: y + diff.y, data: { ...diff, placement } }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|