|
| 1 | +/** |
| 2 | + * Pure placement decisions for the model dropdown cascade panel. |
| 3 | + * |
| 4 | + * The rules mirror the flip/size middleware of floating-positioning |
| 5 | + * libraries: prefer the anchor's inline-start edge and flip only when the |
| 6 | + * panel would overflow the viewport there, and cap the panel height to the |
| 7 | + * space available above the toolbar. Keeping the decisions pure (no DOM |
| 8 | + * access) makes them unit-testable without a layout engine; the selector |
| 9 | + * component only measures and applies the results. |
| 10 | + */ |
| 11 | + |
| 12 | +export interface ModelDropdownAnchorRect { |
| 13 | + left: number; |
| 14 | + right: number; |
| 15 | + top: number; |
| 16 | +} |
| 17 | + |
| 18 | +/** Breathing room kept between the panel and the viewport edge. */ |
| 19 | +export const MODEL_DROPDOWN_VIEWPORT_MARGIN = 8; |
| 20 | +/** Repo-wide popover height convention (see nav TOC popover). */ |
| 21 | +export const MODEL_DROPDOWN_FALLBACK_MAX_HEIGHT = 420; |
| 22 | +/** Floor so the panel stays usable even in very short windows. */ |
| 23 | +export const MODEL_DROPDOWN_MIN_HEIGHT = 160; |
| 24 | + |
| 25 | +/** |
| 26 | + * Decides whether the panel should anchor to the inline-end edge of its |
| 27 | + * toolbar instead of the inline-start edge. Start alignment keeps the panel |
| 28 | + * flush with the trigger button; flipping is a last resort for anchors whose |
| 29 | + * start side cannot fit the panel. |
| 30 | + */ |
| 31 | +export function shouldFlipModelDropdown( |
| 32 | + anchor: ModelDropdownAnchorRect, |
| 33 | + panelWidth: number, |
| 34 | + viewportWidth: number, |
| 35 | +): boolean { |
| 36 | + const margin = MODEL_DROPDOWN_VIEWPORT_MARGIN; |
| 37 | + const fitsAtStart = anchor.left + panelWidth <= viewportWidth - margin; |
| 38 | + const fitsAtEnd = anchor.right - panelWidth >= margin; |
| 39 | + return !fitsAtStart && fitsAtEnd; |
| 40 | +} |
| 41 | + |
| 42 | +/** |
| 43 | + * Caps the panel to the vertical space above the toolbar so the upward |
| 44 | + * growing dropdown never covers the viewport top, without exceeding the |
| 45 | + * repo-wide 420px popover convention. |
| 46 | + */ |
| 47 | +export function modelDropdownMaxHeight(anchorTop: number): number { |
| 48 | + const available = anchorTop - MODEL_DROPDOWN_VIEWPORT_MARGIN; |
| 49 | + if (!Number.isFinite(available)) return MODEL_DROPDOWN_FALLBACK_MAX_HEIGHT; |
| 50 | + return Math.max( |
| 51 | + MODEL_DROPDOWN_MIN_HEIGHT, |
| 52 | + Math.min(available, MODEL_DROPDOWN_FALLBACK_MAX_HEIGHT), |
| 53 | + ); |
| 54 | +} |
| 55 | + |
| 56 | +/** |
| 57 | + * Anchors the compact editor card to its edited row like an IDE flyout, |
| 58 | + * clamped so the card never escapes the visible list area. |
| 59 | + */ |
| 60 | +export function modelEditorPaneOffset( |
| 61 | + rowVisibleTop: number, |
| 62 | + editorHeight: number, |
| 63 | + listVisibleHeight: number, |
| 64 | +): number { |
| 65 | + if (!Number.isFinite(rowVisibleTop) |
| 66 | + || !Number.isFinite(editorHeight) |
| 67 | + || !Number.isFinite(listVisibleHeight)) { |
| 68 | + return 0; |
| 69 | + } |
| 70 | + return Math.max(0, Math.min(rowVisibleTop, listVisibleHeight - editorHeight)); |
| 71 | +} |
0 commit comments