125 lines
2.3 KiB
JavaScript
125 lines
2.3 KiB
JavaScript
import { ref, watch, onBeforeUnmount, unref, nextTick } from "vue";
|
|
|
|
import {
|
|
computePosition,
|
|
autoUpdate,
|
|
offset,
|
|
flip,
|
|
shift
|
|
} from "@floating-ui/dom";
|
|
|
|
export function useFloating(target, floating, options = {}) {
|
|
const x = ref(0);
|
|
const y = ref(0);
|
|
|
|
const strategy = ref("fixed");
|
|
|
|
const placement = ref(
|
|
options.placement ?? "bottom"
|
|
);
|
|
|
|
let cleanup = null;
|
|
|
|
function resolveTarget() {
|
|
const value = unref(target);
|
|
|
|
if (!value)
|
|
return null;
|
|
|
|
if (typeof value === "function")
|
|
return value();
|
|
|
|
if (typeof value === "string")
|
|
return document.querySelector(value);
|
|
|
|
return value;
|
|
}
|
|
|
|
function resolveFloating() {
|
|
return unref(floating);
|
|
}
|
|
|
|
async function update() {
|
|
const reference = resolveTarget();
|
|
const popup = resolveFloating();
|
|
|
|
if (!reference || !popup)
|
|
return;
|
|
|
|
const result = await computePosition(
|
|
reference,
|
|
popup,
|
|
{
|
|
strategy: strategy.value,
|
|
placement: placement.value,
|
|
middleware: [
|
|
offset(
|
|
options.offset ?? 12
|
|
),
|
|
flip(),
|
|
shift({
|
|
padding: 8
|
|
})
|
|
]
|
|
|
|
}
|
|
)
|
|
|
|
x.value = result.x;
|
|
y.value = result.y;
|
|
}
|
|
|
|
function start() {
|
|
cleanup?.();
|
|
const reference = resolveTarget();
|
|
const popup = resolveFloating();
|
|
|
|
if (!reference || !popup)
|
|
return;
|
|
|
|
cleanup = autoUpdate(
|
|
reference,
|
|
popup,
|
|
update
|
|
);
|
|
|
|
update();
|
|
}
|
|
|
|
watch(
|
|
[
|
|
() => resolveTarget(),
|
|
() => resolveFloating()
|
|
],
|
|
() => {
|
|
start()
|
|
},
|
|
{
|
|
immediate:true
|
|
}
|
|
)
|
|
|
|
watch(
|
|
floating,
|
|
async value => {
|
|
|
|
if (!value)
|
|
return
|
|
|
|
await nextTick()
|
|
update()
|
|
}
|
|
)
|
|
|
|
onBeforeUnmount(() => {
|
|
cleanup?.();
|
|
});
|
|
|
|
return {
|
|
x,
|
|
y,
|
|
strategy,
|
|
update
|
|
};
|
|
|
|
} |