|
|
|
|
|
|
|
import { flip, offset, arrow, shift, size, computePosition, autoUpdate, } from '@floating-ui/dom'; |
|
import { isHTMLElement, noop } from '../../helpers/index.js'; |
|
const defaultConfig = { |
|
strategy: 'absolute', |
|
placement: 'top', |
|
gutter: 5, |
|
flip: true, |
|
sameWidth: false, |
|
overflowPadding: 8, |
|
}; |
|
const ARROW_TRANSFORM = { |
|
bottom: 'rotate(45deg)', |
|
left: 'rotate(135deg)', |
|
top: 'rotate(225deg)', |
|
right: 'rotate(315deg)', |
|
}; |
|
const SIDE_OPTIONS = ['top', 'right', 'bottom', 'left']; |
|
const ALIGN_OPTIONS = ['start', 'center', 'end']; |
|
export function useFloating(reference, floating, opts = {}) { |
|
if (!floating || !reference || opts === null) |
|
return { |
|
destroy: noop, |
|
}; |
|
const options = { ...defaultConfig, ...opts }; |
|
const arrowEl = floating.querySelector('[data-arrow=true]'); |
|
const middleware = []; |
|
if (options.flip) { |
|
middleware.push(flip({ |
|
boundary: options.boundary, |
|
padding: options.overflowPadding, |
|
})); |
|
} |
|
const arrowOffset = isHTMLElement(arrowEl) ? arrowEl.offsetHeight / 2 : 0; |
|
if (options.gutter || options.offset) { |
|
const data = options.gutter ? { mainAxis: options.gutter } : options.offset; |
|
if (data?.mainAxis != null) { |
|
data.mainAxis += arrowOffset; |
|
} |
|
middleware.push(offset(data)); |
|
} |
|
middleware.push(shift({ |
|
boundary: options.boundary, |
|
crossAxis: options.overlap, |
|
padding: options.overflowPadding, |
|
})); |
|
if (arrowEl) { |
|
middleware.push(arrow({ element: arrowEl, padding: 8 })); |
|
} |
|
middleware.push(size({ |
|
padding: options.overflowPadding, |
|
apply({ rects, availableHeight, availableWidth }) { |
|
if (options.sameWidth) { |
|
Object.assign(floating.style, { |
|
width: `${Math.round(rects.reference.width)}px`, |
|
minWidth: 'unset', |
|
}); |
|
} |
|
if (options.fitViewport) { |
|
Object.assign(floating.style, { |
|
maxWidth: `${availableWidth}px`, |
|
maxHeight: `${availableHeight}px`, |
|
}); |
|
} |
|
}, |
|
})); |
|
function compute() { |
|
if (!reference || !floating) |
|
return; |
|
|
|
if (isHTMLElement(reference) && !reference.ownerDocument.documentElement.contains(reference)) |
|
return; |
|
const { placement, strategy } = options; |
|
computePosition(reference, floating, { |
|
placement, |
|
middleware, |
|
strategy, |
|
}).then((data) => { |
|
const x = Math.round(data.x); |
|
const y = Math.round(data.y); |
|
|
|
|
|
const [side, align] = getSideAndAlignFromPlacement(data.placement); |
|
floating.setAttribute('data-side', side); |
|
floating.setAttribute('data-align', align); |
|
Object.assign(floating.style, { |
|
position: options.strategy, |
|
top: `${y}px`, |
|
left: `${x}px`, |
|
}); |
|
if (isHTMLElement(arrowEl) && data.middlewareData.arrow) { |
|
const { x, y } = data.middlewareData.arrow; |
|
const dir = data.placement.split('-')[0]; |
|
arrowEl.setAttribute('data-side', dir); |
|
Object.assign(arrowEl.style, { |
|
position: 'absolute', |
|
left: x != null ? `${x}px` : '', |
|
top: y != null ? `${y}px` : '', |
|
[dir]: `calc(100% - ${arrowOffset}px)`, |
|
transform: ARROW_TRANSFORM[dir], |
|
backgroundColor: 'inherit', |
|
zIndex: 'inherit', |
|
}); |
|
} |
|
return data; |
|
}); |
|
} |
|
|
|
Object.assign(floating.style, { |
|
position: options.strategy, |
|
}); |
|
return { |
|
destroy: autoUpdate(reference, floating, compute), |
|
}; |
|
} |
|
function getSideAndAlignFromPlacement(placement) { |
|
const [side, align = 'center'] = placement.split('-'); |
|
return [side, align]; |
|
} |
|
|