66
77from textual .app import ComposeResult
88from textual .binding import Binding
9+ from textual .containers import Container , Horizontal
910from textual .screen import ModalScreen
10- from textual .widgets import Input , Static
11+ from textual .widgets import Button , Input , Static
1112
1213from sqlit .shared .ui .widgets import Dialog
1314
@@ -22,6 +23,8 @@ class PasswordInputScreen(ModalScreen):
2223 BINDINGS = [
2324 Binding ("escape" , "cancel" , "Cancel" , priority = True ),
2425 Binding ("enter" , "submit" , "Submit" , show = False ),
26+ Binding ("tab" , "focus_next" , "Next field" , show = False , priority = True ),
27+ Binding ("shift+tab" , "focus_prev" , "Previous field" , show = False , priority = True ),
2528 ]
2629
2730 CSS = """
@@ -70,6 +73,24 @@ class PasswordInputScreen(ModalScreen):
7073 border: none;
7174 background-tint: $foreground 5%;
7275 }
76+
77+ #password-row {
78+ width: 100%;
79+ height: 1;
80+ }
81+
82+ #password-row Input {
83+ width: 1fr;
84+ }
85+
86+ #password-toggle {
87+ width: 6;
88+ min-width: 6;
89+ height: 1;
90+ border: none;
91+ margin-left: 1;
92+ padding: 0;
93+ }
7394 """
7495
7596 def __init__ (
@@ -106,17 +127,20 @@ def compose(self) -> ComposeResult:
106127 shortcuts : list [tuple [str , str ]] = [("Submit" , "<enter>" ), ("Cancel" , "<esc>" )]
107128 with Dialog (id = "password-dialog" , title = self .title_text , shortcuts = shortcuts ):
108129 yield Static (self .description , id = "password-description" )
109- from textual .containers import Container
110-
111130 container = Container (id = "password-container" )
112131 container .border_title = "Password"
113132 with container :
114- yield Input (
115- value = "" ,
116- placeholder = "" ,
117- id = "password-input" ,
118- password = False ,
133+ row = Horizontal (id = "password-row" )
134+ row .compose_add_child (
135+ Input (
136+ value = "" ,
137+ placeholder = "" ,
138+ id = "password-input" ,
139+ password = True ,
140+ )
119141 )
142+ row .compose_add_child (Button ("Show" , id = "password-toggle" ))
143+ yield row
120144
121145 def on_mount (self ) -> None :
122146 self .query_one ("#password-input" , Input ).focus ()
@@ -131,6 +155,25 @@ def on_input_submitted(self, event: Input.Submitted) -> None:
131155 self ._log_submit ("input_submitted" , event .value )
132156 self .dismiss (event .value )
133157
158+ def on_button_pressed (self , event : Button .Pressed ) -> None :
159+ if event .button .id != "password-toggle" :
160+ return
161+ input_widget = self .query_one ("#password-input" , Input )
162+ input_widget .password = not input_widget .password
163+ event .button .label = "Hide" if not input_widget .password else "Show"
164+ input_widget .focus ()
165+
166+ def action_focus_next (self ) -> None :
167+ input_widget = self .query_one ("#password-input" , Input )
168+ toggle_btn = self .query_one ("#password-toggle" , Button )
169+ if self .focused is input_widget :
170+ toggle_btn .focus ()
171+ else :
172+ input_widget .focus ()
173+
174+ def action_focus_prev (self ) -> None :
175+ self .action_focus_next ()
176+
134177 def on_descendant_focus (self , event : Any ) -> None :
135178 try :
136179 container = self .query_one ("#password-container" )
0 commit comments