run method
T?
run({
- required List<
T> buildItems(), - DynamicAction onPrimary(
- T item,
- int index
- DynamicAction onSecondary(
- T item,
- int index
- DynamicAction onToggle(
- T item,
- int index
- required void renderItem(
- FrameContext ctx,
- T item,
- int absoluteIndex,
- bool isFocused,
- void beforeItems(
- FrameContext ctx
- KeyBindings? extraBindings,
- KeyBindings? customBindings,
Runs the dynamic list prompt.
buildItems - Function that returns current list of items.
Called on every action to rebuild the list.
onPrimary - Called when Enter/Right is pressed on an item.
Return DynamicAction to control behavior.
onSecondary - Called when Left is pressed on an item.
Return DynamicAction to control behavior.
onToggle - Called when Space is pressed on an item.
Return DynamicAction to control behavior.
renderItem - Custom item renderer.
beforeItems - Content to render before the list.
extraBindings - Additional key bindings.
customBindings - Completely replace default bindings.
Returns selected item on confirm, null on cancel.
Implementation
T? run({
required List<T> Function() buildItems,
DynamicAction Function(T item, int index)? onPrimary,
DynamicAction Function(T item, int index)? onSecondary,
DynamicAction Function(T item, int index)? onToggle,
required void Function(
FrameContext ctx,
T item,
int absoluteIndex,
bool isFocused,
) renderItem,
void Function(FrameContext ctx)? beforeItems,
KeyBindings? extraBindings,
KeyBindings? customBindings,
}) {
_initState(buildItems);
void rebuild() {
_items = buildItems();
_nav.itemCount = _items.length;
}
DynamicAction handleAction(DynamicAction Function(T, int)? handler) {
if (handler == null || _items.isEmpty) return DynamicAction.none;
final item = _items[_nav.selectedIndex];
return handler(item, _nav.selectedIndex);
}
// Create bindings
if (customBindings != null) {
_bindings = customBindings;
} else {
_bindings = KeyBindings.verticalNavigation(
onUp: () => _nav.moveUp(),
onDown: () => _nav.moveDown(),
) +
KeyBindings([
// Primary action (Enter/Right)
KeyBinding.multi(
{KeyEventType.enter, KeyEventType.arrowRight},
(event) {
final action = handleAction(onPrimary);
switch (action) {
case DynamicAction.confirm:
if (_items.isNotEmpty) {
_result = _items[_nav.selectedIndex];
}
return KeyActionResult.confirmed;
case DynamicAction.rebuild:
rebuild();
return KeyActionResult.handled;
case DynamicAction.rebuildAndReset:
rebuild();
_nav.reset();
return KeyActionResult.handled;
case DynamicAction.cancel:
_cancelled = true;
return KeyActionResult.cancelled;
case DynamicAction.none:
return KeyActionResult.handled;
}
},
hintLabel: '→/Enter',
hintDescription: 'select/enter',
),
// Secondary action (Left)
KeyBinding.single(
KeyEventType.arrowLeft,
(event) {
final action = handleAction(onSecondary);
switch (action) {
case DynamicAction.confirm:
if (_items.isNotEmpty) {
_result = _items[_nav.selectedIndex];
}
return KeyActionResult.confirmed;
case DynamicAction.rebuild:
rebuild();
return KeyActionResult.handled;
case DynamicAction.rebuildAndReset:
rebuild();
_nav.reset();
return KeyActionResult.handled;
case DynamicAction.cancel:
_cancelled = true;
return KeyActionResult.cancelled;
case DynamicAction.none:
return KeyActionResult.handled;
}
},
hintLabel: '←',
hintDescription: 'back/collapse',
),
// Toggle action (Space)
if (onToggle != null)
KeyBinding.single(
KeyEventType.space,
(event) {
final action = handleAction(onToggle);
switch (action) {
case DynamicAction.confirm:
if (_items.isNotEmpty) {
_result = _items[_nav.selectedIndex];
}
return KeyActionResult.confirmed;
case DynamicAction.rebuild:
rebuild();
return KeyActionResult.handled;
case DynamicAction.rebuildAndReset:
rebuild();
_nav.reset();
return KeyActionResult.handled;
case DynamicAction.cancel:
_cancelled = true;
return KeyActionResult.cancelled;
case DynamicAction.none:
return KeyActionResult.handled;
}
},
hintLabel: 'Space',
hintDescription: 'toggle',
),
]) +
KeyBindings.cancel(onCancel: () => _cancelled = true);
}
if (extraBindings != null) {
_bindings = _bindings + extraBindings;
}
final frame = FrameView(
title: title,
theme: theme,
bindings: _bindings,
);
void render(RenderOutput out) {
// Rebuild items on each render to catch state changes
rebuild();
frame.render(out, (ctx) {
// Before items hook
beforeItems?.call(ctx);
// Visible window
final window = _nav.visibleWindow(_items);
ctx.listWindow(
window,
selectedIndex: _nav.selectedIndex,
renderItem: (T item, int index, bool isFocused) {
renderItem(ctx, item, index, isFocused);
},
);
if (_items.isEmpty) {
ctx.emptyMessage('empty');
}
});
}
final runner = PromptRunner(hideCursor: true);
runner.runWithBindings(
render: render,
bindings: _bindings,
);
return _cancelled ? null : _result;
}