anki/ts/sveltelib/position.ts
Henrik Giesel 88217c5e7d
Replace (some) Bootstrap dropdowns with Floating UI (#1695)
* Implement a first version of WithFloating and Portal

* Add outside slot for Portal

* Execute computePosition from WithFloating

* Set up a first example of new WithFloating with the Latex menu

* Use autoUpdate in WithFloating

* Create sveltelib/position

* Add event-store

* Use event-store in close-on-click

* Implement subscribeToUpdates

* Introduce sass/elevation

* Split close-on-click to closing-click and subscribe-trigger

* Have closing-* stores return a symbol

- This way they act more of an EventEmitter than a store

* Allow passing show store

* Remove styling on float on updatePosition removal

* Implement a nice border for dropdowns

* Apply different border and box-shadow to Popover in dark/light theme

* Fix Ctrl+Shift+T not working

* Satisfy formatters and tests

* Add copyright header

* move copyright header to top (dae)
2022-03-02 14:21:19 +10:00

83 lines
1.9 KiB
TypeScript

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { Placement } from "@floating-ui/dom";
import { arrow, autoUpdate, computePosition, offset, shift } from "@floating-ui/dom";
interface PositionArgs {
/**
* The floating element which is positioned relative to `reference`.
*/
floating: HTMLElement | null;
placement: Placement;
arrow: HTMLElement;
}
function position(
reference: HTMLElement,
positionArgs: PositionArgs,
): { update(args: PositionArgs): void; destroy(): void } {
let args = positionArgs;
async function updateInner(): Promise<void> {
const { x, y, middlewareData } = await computePosition(
reference,
args.floating!,
{
middleware: [
offset(5),
shift({ padding: 5 }),
arrow({ element: args.arrow }),
],
placement: args.placement,
},
);
const arrowX = middlewareData.arrow?.x ?? "";
Object.assign(args.arrow.style, {
left: `${arrowX}px`,
top: `-5px`,
});
Object.assign(args.floating!.style, {
left: `${x}px`,
top: `${y}px`,
});
}
let cleanup: (() => void) | null = null;
function destroy(): void {
cleanup?.();
cleanup = null;
if (!args.floating) {
return;
}
args.floating.style.removeProperty("left");
args.floating.style.removeProperty("top");
}
function update(updateArgs: PositionArgs): void {
destroy();
args = updateArgs;
if (!args.floating) {
return;
}
cleanup = autoUpdate(reference, args.floating, updateInner);
}
update(args);
return {
update,
destroy,
};
}
export default position;