Files
linkding/bookmarks/frontend/behaviors/position-controller.js
2025-12-21 10:22:39 +01:00

72 lines
1.7 KiB
JavaScript

import {
arrow,
autoUpdate,
computePosition,
flip,
offset,
shift,
} from "@floating-ui/dom";
export class PositionController {
constructor(options) {
this.anchor = options.anchor;
this.overlay = options.overlay;
this.arrow = options.arrow;
this.placement = options.placement || "bottom";
this.offset = options.offset;
this.autoWidth = options.autoWidth || false;
this.autoUpdateCleanup = null;
}
enable() {
if (!this.autoUpdateCleanup) {
this.autoUpdateCleanup = autoUpdate(this.anchor, this.overlay, () =>
this.updatePosition(),
);
}
}
disable() {
if (this.autoUpdateCleanup) {
this.autoUpdateCleanup();
this.autoUpdateCleanup = null;
}
}
updatePosition() {
const middleware = [flip(), shift()];
if (this.arrow) {
middleware.push(arrow({ element: this.arrow }));
}
if (this.offset) {
middleware.push(offset(this.offset));
}
computePosition(this.anchor, this.overlay, {
placement: this.placement,
strategy: "fixed",
middleware,
}).then(({ x, y, placement, middlewareData }) => {
Object.assign(this.overlay.style, {
left: `${x}px`,
top: `${y}px`,
});
this.overlay.classList.remove("top-aligned", "bottom-aligned");
this.overlay.classList.add(`${placement}-aligned`);
if (this.arrow) {
const { x, y } = middlewareData.arrow;
Object.assign(this.arrow.style, {
left: x != null ? `${x}px` : "",
top: y != null ? `${y}px` : "",
});
}
});
if (this.autoWidth) {
const width = this.anchor.offsetWidth;
this.overlay.style.width = `${width}px`;
}
}
}