* Resolve overlay layout from options. * Returns { width, row, col, maxHeight } for rendering.
( options: OverlayOptions | undefined, overlayHeight: number, termWidth: number, termHeight: number, )
| 905 | * Returns { width, row, col, maxHeight } for rendering. |
| 906 | */ |
| 907 | private resolveOverlayLayout( |
| 908 | options: OverlayOptions | undefined, |
| 909 | overlayHeight: number, |
| 910 | termWidth: number, |
| 911 | termHeight: number, |
| 912 | ): { width: number; row: number; col: number; maxHeight: number | undefined } { |
| 913 | const opt = options ?? {}; |
| 914 | |
| 915 | // Parse margin (clamp to non-negative) |
| 916 | const margin = |
| 917 | typeof opt.margin === "number" |
| 918 | ? { top: opt.margin, right: opt.margin, bottom: opt.margin, left: opt.margin } |
| 919 | : (opt.margin ?? {}); |
| 920 | const marginTop = Math.max(0, margin.top ?? 0); |
| 921 | const marginRight = Math.max(0, margin.right ?? 0); |
| 922 | const marginBottom = Math.max(0, margin.bottom ?? 0); |
| 923 | const marginLeft = Math.max(0, margin.left ?? 0); |
| 924 | |
| 925 | // Available space after margins |
| 926 | const availWidth = Math.max(1, termWidth - marginLeft - marginRight); |
| 927 | const availHeight = Math.max(1, termHeight - marginTop - marginBottom); |
| 928 | |
| 929 | // === Resolve width === |
| 930 | let width = parseSizeValue(opt.width, termWidth) ?? Math.min(80, availWidth); |
| 931 | // Apply minWidth |
| 932 | if (opt.minWidth !== undefined) { |
| 933 | width = Math.max(width, opt.minWidth); |
| 934 | } |
| 935 | // Clamp to available space |
| 936 | width = Math.max(1, Math.min(width, availWidth)); |
| 937 | |
| 938 | // === Resolve maxHeight === |
| 939 | let maxHeight = parseSizeValue(opt.maxHeight, termHeight); |
| 940 | // Clamp to available space |
| 941 | if (maxHeight !== undefined) { |
| 942 | maxHeight = Math.max(1, Math.min(maxHeight, availHeight)); |
| 943 | } |
| 944 | |
| 945 | // Effective overlay height (may be clamped by maxHeight) |
| 946 | const effectiveHeight = maxHeight !== undefined ? Math.min(overlayHeight, maxHeight) : overlayHeight; |
| 947 | |
| 948 | // === Resolve position === |
| 949 | let row: number; |
| 950 | let col: number; |
| 951 | |
| 952 | if (opt.row !== undefined) { |
| 953 | if (typeof opt.row === "string") { |
| 954 | // Percentage: 0% = top, 100% = bottom (overlay stays within bounds) |
| 955 | const match = opt.row.match(/^(\d+(?:\.\d+)?)%$/); |
| 956 | if (match) { |
| 957 | const maxRow = Math.max(0, availHeight - effectiveHeight); |
| 958 | const percent = parseFloat(match[1]!) / 100; |
| 959 | row = marginTop + Math.floor(maxRow * percent); |
| 960 | } else { |
| 961 | // Invalid format, fall back to center |
| 962 | row = this.resolveAnchorRow("center", effectiveHeight, availHeight, marginTop); |
| 963 | } |
| 964 | } else { |
no test coverage detected