runCustom method

List<T> runCustom({
  1. void beforeItems(
    1. FrameContext ctx
    )?,
  2. required void renderItem(
    1. FrameContext ctx,
    2. T item,
    3. int absoluteIndex,
    4. bool isFocused,
    5. bool isSelected,
    ),
  3. void afterItems(
    1. FrameContext ctx
    )?,
  4. KeyBindings? extraBindings,
  5. void onBeforeRender()?,
})

Runs the prompt with full customization hooks.

beforeItems: Content to render before the item list (e.g., search bar, summary). renderItem: Called for each visible item. afterItems: Content to render after the item list (e.g., footer, stats). extraBindings: Additional key bindings to merge. onBeforeRender: Called before each render (e.g., update nav.maxVisible).

Returns selected items on confirm, empty list on cancel.

Example:

final selected = prompt.runCustom(
  beforeItems: (ctx) {
    ctx.searchLine(queryInput.text, enabled: searchEnabled);
    ctx.writeConnector();
  },
  renderItem: (ctx, item, index, focused, selected) {
    ctx.checkboxItem(item.toString(), focused: focused, checked: selected);
  },
  afterItems: (ctx) {
    if (items.isEmpty) ctx.emptyMessage('no matches');
  },
);

Implementation

List<T> runCustom({
  void Function(FrameContext ctx)? beforeItems,
  required void Function(
    FrameContext ctx,
    T item,
    int absoluteIndex,
    bool isFocused,
    bool isSelected,
  ) renderItem,
  void Function(FrameContext ctx)? afterItems,
  KeyBindings? extraBindings,
  void Function()? onBeforeRender,
}) {
  if (items.isEmpty) return [];

  _initState();

  if (_pendingExtraBindings != null) {
    _bindings = _bindings + _pendingExtraBindings!;
  }
  if (extraBindings != null) {
    _bindings = _bindings + extraBindings;
  }

  final frame = FrameView(
    title: title,
    theme: theme,
    bindings: _bindings,
  );

  void render(RenderOutput out) {
    // Hook for pre-render updates
    onBeforeRender?.call();

    // Responsive viewport adjustment
    _nav.maxVisible =
        (TerminalInfo.rows - reservedLines).clamp(5, maxVisible);

    frame.render(out, (ctx) {
      // Before items hook
      beforeItems?.call(ctx);

      // Get visible window
      final window = _nav.visibleWindow(items);

      // Overflow above indicator
      if (window.hasOverflowAbove) {
        ctx.overflowIndicator();
      }

      // Render visible items
      for (var i = 0; i < window.items.length; i++) {
        final absoluteIndex = window.start + i;
        final isFocused = _nav.isSelected(absoluteIndex);
        final isSelected = _selection.isSelected(absoluteIndex);
        renderItem(
            ctx, window.items[i], absoluteIndex, isFocused, isSelected);
      }

      // Overflow below indicator
      if (window.hasOverflowBelow) {
        ctx.overflowIndicator();
      }

      // After items hook
      afterItems?.call(ctx);
    });
  }

  final runner = PromptRunner(hideCursor: true);
  final result = runner.runWithBindings(
    render: render,
    bindings: _bindings,
  );

  if (_cancelled || result == PromptResult.cancelled) {
    return [];
  }

  return _selection.getSelectedMany(
    items,
    fallbackIndex: _nav.selectedIndex,
  );
}