Skip to content
Source
Sign inSign up
Open History to see the last commit

menu-placement.ts

53 lines2.2 KB

1import type { VerticalHinge } from "./layout";
2​
3/** Keep menus on their anchor's side of a creased hinge. */
4export function deriveMenuHorizontalPlacement(input: {
5 readonly anchorX: number;
6 readonly anchorWidth: number;
7 readonly overlayWidth: number;
8 readonly desiredWidth: number;
9 readonly margin: number;
10 /** In the overlay's coordinates. */
11 readonly hinge: VerticalHinge | null;
12}) {
13 const center = input.anchorX + input.anchorWidth / 2;
14 let start = 0;
15 let end = input.overlayWidth;
16 if (input.hinge) {
17 if (center < input.hinge.x) end = input.hinge.x;
18 else start = input.hinge.x + input.hinge.width;
19 }
20 const width = Math.min(input.desiredWidth, Math.max(0, end - start - 2 * input.margin));
21 const preferredLeft =
22 center <= (start + end) / 2 ? input.anchorX : input.anchorX + input.anchorWidth - width;
23 const left = Math.min(Math.max(preferredLeft, start + input.margin), end - width - input.margin);
24 return { left, width };
26​
27/** In the overlay's coordinates; `bottom` is measured up from the overlay's bottom edge. */
28export function deriveMenuVerticalPlacement(input: {
29 readonly anchorY: number;
30 readonly anchorHeight: number;
31 readonly overlayHeight: number;
32 readonly keyboardInset: number;
33 readonly gap: number;
34 readonly margin: number;
35 readonly minHeight: number;
36}) {
37 const usableBottom = input.overlayHeight - input.keyboardInset;
38 const spaceBelow = usableBottom - (input.anchorY + input.anchorHeight) - input.gap - input.margin;
39 const spaceAbove = input.anchorY - input.gap - input.margin;
40 const opensDown = spaceBelow >= 280 || spaceBelow >= spaceAbove;
41 const maxHeight = Math.min(opensDown ? spaceBelow : spaceAbove, 480);
42 if (maxHeight < input.minHeight) {
43 // Neither side fits (e.g. a code block taller than the screen): overlap the anchor instead.
44 const top = Math.max(
45 input.margin,
46 Math.min(input.anchorY, usableBottom - input.margin - input.minHeight),
47 );
48 return { maxHeight: Math.min(usableBottom - input.margin - top, 480), top };
49 }
50 return opensDown
51 ? { maxHeight, top: input.anchorY + input.anchorHeight + input.gap }
52 : { maxHeight, bottom: input.overlayHeight - input.anchorY + input.gap };