feat: refactor MultiPickScreen to use SelectionList for multi-choice selection

This commit is contained in:
Даниил Грабарь
2026-05-23 12:14:07 +03:00
parent 3da290a4f3
commit 490f8823a5
+16 -17
View File
@@ -8,7 +8,8 @@ from textual import on
from textual.app import ComposeResult from textual.app import ComposeResult
from textual.binding import Binding from textual.binding import Binding
from textual.screen import Screen from textual.screen import Screen
from textual.widgets import Checkbox, Footer, Input, LoadingIndicator, ProgressBar, RadioButton, RadioSet, RichLog, Static from textual.widgets import Footer, Input, LoadingIndicator, ProgressBar, RadioButton, RadioSet, RichLog, SelectionList, Static
from textual.widgets.selection_list import Selection
# Shared CSS applied to every screen in the app. # Shared CSS applied to every screen in the app.
# Общий CSS, применяемый ко всем экранам приложения. # Общий CSS, применяемый ко всем экранам приложения.
@@ -78,13 +79,12 @@ RunScreen #hint {
color: $text-muted; color: $text-muted;
text-style: dim; text-style: dim;
} }
MultiPickScreen #choices { MultiPickScreen SelectionList {
height: auto; height: auto;
border: none;
padding: 0;
margin-bottom: 1; margin-bottom: 1;
} }
MultiPickScreen Checkbox {
margin: 0;
}
""" """
@@ -306,8 +306,10 @@ class LogScreen(Screen[bool]):
# Экран с чекбоксами для множественного выбора. # Экран с чекбоксами для множественного выбора.
# Пользователь переключает пункты пробелом, подтверждает Enter, отменяет Escape. # Пользователь переключает пункты пробелом, подтверждает Enter, отменяет Escape.
class MultiPickScreen(Screen[Optional[List[str]]]): class MultiPickScreen(Screen[Optional[List[str]]]):
"""Multi-choice checkbox screen. Returns the list of selected option strings, or None on Escape. """Multi-choice screen using SelectionList. Navigate with arrows, toggle with Space,
Экран множественного выбора с чекбоксами. Возвращает список выбранных строк или None при Escape. confirm with Enter, cancel with Escape. Returns selected option strings or None.
Экран множественного выбора через SelectionList. Стрелки — навигация, пробел — выбор,
Enter — подтверждение, Escape — отмена. Возвращает выбранные строки или None.
""" """
BINDINGS = [ BINDINGS = [
Binding("enter", "submit", "Confirm", priority=True), Binding("enter", "submit", "Confirm", priority=True),
@@ -320,8 +322,8 @@ class MultiPickScreen(Screen[Optional[List[str]]]):
self._step = step self._step = step
self._question = question self._question = question
self._options = options self._options = options
# All options are checked by default when defaults is None. # All options are selected by default when defaults is None.
# Все пункты отмечены по умолчанию если defaults не передан. # Все пункты выбраны по умолчанию если defaults не передан.
self._defaults = set(defaults) if defaults is not None else set(options) self._defaults = set(defaults) if defaults is not None else set(options)
self._note = note self._note = note
@@ -330,19 +332,16 @@ class MultiPickScreen(Screen[Optional[List[str]]]):
yield Static(self._question, id="question") yield Static(self._question, id="question")
if self._note: if self._note:
yield Static(self._note, id="note") yield Static(self._note, id="note")
with Static(id="choices"): yield SelectionList(
for opt in self._options: *[Selection(opt, opt, opt in self._defaults) for opt in self._options]
yield Checkbox(opt, value=(opt in self._defaults)) )
yield Footer() yield Footer()
def on_mount(self) -> None: def on_mount(self) -> None:
checkboxes = list(self.query(Checkbox)) self.query_one(SelectionList).focus()
if checkboxes:
checkboxes[0].focus()
def action_submit(self) -> None: def action_submit(self) -> None:
selected = [str(cb.label) for cb in self.query(Checkbox) if cb.value] self.dismiss(list(self.query_one(SelectionList).selected))
self.dismiss(selected)
def action_abort(self) -> None: def action_abort(self) -> None:
self.app.exit(None) self.app.exit(None)