UNPKG

melt

Version:

The next generation of Melt UI. Built for Svelte 5.

105 lines (104 loc) 4.23 kB
import { arrow, autoUpdate, computePosition, flip, offset, shift, size, } from "@floating-ui/dom"; import { extract } from "./extract"; import { isFunction, isHtmlElement } from "./is"; import { deepMerge } from "./merge"; const ARROW_TRANSFORM = { bottom: "rotate(45deg)", left: "rotate(135deg)", top: "rotate(225deg)", right: "rotate(315deg)", }; export function useFloating(args) { const nodeEl = $derived(extract(args.node)); const floatingEl = $derived(extract(args.floating)); const config = $derived(extract(args.config, {})); let data = $state(); const compute = () => { const arrowEl = floatingEl.querySelector("[data-arrow]"); const arrowOffset = isHtmlElement(arrowEl) ? arrowEl.offsetHeight / 2 : 0; const arrowMiddleware = isHtmlElement(arrowEl) ? arrow({ element: arrowEl, ...config.arrow }) : undefined; const baseOptions = { middleware: [ shift(config.shift), flip(config.flip), arrowMiddleware, offset(typeof config.offset === "number" ? config.offset : { mainAxis: 8 + arrowOffset, ...config.offset }), config.sameWidth ? size({ apply({ rects, elements }) { Object.assign(elements.floating?.style ?? {}, { width: `${rects.reference.width}px`, minWidth: `${rects.reference.width}px`, }); }, }) : undefined, ], }; computePosition(nodeEl, floatingEl, deepMerge(baseOptions, config.computePosition ?? {})).then((returned) => { data = returned; const { x, y, placement, middlewareData, strategy } = returned; const floatingApply = (el = floatingEl) => { const transformOriginMap = { top: "bottom center", "top-start": "bottom left", "top-end": "bottom right", bottom: "top center", "bottom-start": "top left", "bottom-end": "top right", left: "center center", "left-start": "top left", "left-end": "bottom left", right: "center center", "right-start": "top right", "right-end": "bottom right", }; Object.assign(el.style, { position: strategy, left: `${x}px`, top: `${y}px`, }); const [side, align = "center"] = placement.split("-"); el.style.transformOrigin = transformOriginMap[placement]; el.dataset.side = side; el.dataset.align = align; }; const arrowApply = (el = undefined) => { const actualEl = el ?? arrowEl; if (!isHtmlElement(actualEl) || !middlewareData.arrow) return; const { x, y } = middlewareData.arrow; const dir = placement.split("-")[0]; Object.assign(actualEl.style, { position: "absolute", left: x ? `${x}px` : "", top: y ? `${y}px` : "", [dir]: `calc(100% - ${arrowOffset}px)`, transform: ARROW_TRANSFORM[dir], backgroundColor: "inherit", zIndex: "inherit", }); actualEl.dataset.side = dir; }; if (isFunction(config.onCompute)) { config.onCompute({ ...returned, arrowApply, floatingApply }); } else if (config.onCompute === undefined) { floatingApply(); arrowApply(); } }); }; $effect(() => { return autoUpdate(nodeEl, floatingEl, compute); }); return { get data() { return data; }, }; }