renderModal function

String renderModal(
  1. String baseView,
  2. List<String> body, {
  3. ModalChrome chrome = const ModalChrome(),
  4. required int screenW,
  5. required int screenH,
})

Renders body as a centered bordered dialog over baseView.

Lines wider than the box are cut (never wrapped); the box is clamped to the screen. ANSI SGR sequences pass through width accounting.

Implementation

String renderModal(
  String baseView,
  List<String> body, {
  ModalChrome chrome = const ModalChrome(),
  required int screenW,
  required int screenH,
}) {
  if (screenW < 10 || screenH < 5) return baseView;
  final padding = chrome.padding < 0 ? 0 : chrome.padding;
  var contentWidth = 0;
  for (final line in [...body, ...chrome.footer]) {
    final width = Style.visibleLength(line);
    if (width > contentWidth) contentWidth = width;
  }
  var boxWidth = chrome.width > 0
      ? chrome.width
      : contentWidth + padding * 2 + 2; // content + padding + borders
  boxWidth = boxWidth.clamp(4, screenW - 2);
  final innerWidth = boxWidth - 2;
  final usableWidth = (innerWidth - padding * 2).clamp(1, innerWidth);

  final content = <String>[
    for (final line in body) _cut(line, usableWidth),
    if (chrome.footer.isNotEmpty) ...[
      _rule(usableWidth),
      for (final line in chrome.footer) _cut(line, usableWidth),
    ],
  ];
  // Box chrome (borders, title, padding) comes from the Style-driven
  // PanelComponent — never hand-drawn here.
  var boxLines = PanelComponent(
    content: content.join('\n'),
    title: chrome.title.isEmpty ? null : chrome.title,
    padding: padding,
    width: innerWidth + 2,
    renderConfig: RenderConfig(terminalWidth: screenW),
  ).render().split('\n');
  if (chrome.maxHeight > 0 && boxLines.length > chrome.maxHeight) {
    boxLines = boxLines.sublist(0, chrome.maxHeight);
  }
  if (boxLines.length > screenH) {
    boxLines = boxLines.sublist(0, screenH);
  }
  final x = ((screenW - Style.visibleLength(boxLines.first)) ~/ 2).clamp(
    0,
    screenW,
  );
  var y = ((screenH - boxLines.length) ~/ 2).clamp(0, screenH);
  final base = baseView.split('\n');
  while (base.length < screenH) {
    base.add('');
  }
  for (var i = 0; i < boxLines.length && y + i < base.length; i++) {
    if (y + i < 0) continue;
    base[y + i] = _overlayLine(base[y + i], boxLines[i], x, screenW);
  }
  return base.join('\n');
}