private/Controls/New-PasswordInputWithPeek.ps1
|
function New-PasswordInputWithPeek { <# .SYNOPSIS Creates a password input with optional peek button. #> [CmdletBinding()] param( [string]$DefaultValue = '', [switch]$NoPeek, [int]$Height = 32 ) $colors = Get-ThemeColors $passBox = [System.Windows.Controls.PasswordBox]@{ Password = $DefaultValue Height = $Height } Set-TextBoxStyle -PasswordBox $passBox # Without peek button, just return the password box directly if ($NoPeek) { return @{ Container = $passBox; PasswordBox = $passBox } } $peekWrapper = [System.Windows.Controls.Grid]::new() $inputCol = [System.Windows.Controls.ColumnDefinition]::new() $inputCol.Width = [System.Windows.GridLength]::new(1, [System.Windows.GridUnitType]::Star) [void]$peekWrapper.ColumnDefinitions.Add($inputCol) $btnCol = [System.Windows.Controls.ColumnDefinition]::new() $btnCol.Width = [System.Windows.GridLength]::Auto [void]$peekWrapper.ColumnDefinitions.Add($btnCol) [System.Windows.Controls.Grid]::SetColumn($passBox, 0) [void]$peekWrapper.Children.Add($passBox) # Create reveal TextBox (hidden initially, overlays PasswordBox) $revealBox = [System.Windows.Controls.TextBox]@{ Height = $Height Visibility = 'Collapsed' IsReadOnly = $true IsHitTestVisible = $false Focusable = $false } Set-TextBoxStyle -TextBox $revealBox [System.Windows.Controls.Grid]::SetColumn($revealBox, 0) [void]$peekWrapper.Children.Add($revealBox) $peekBtn = [System.Windows.Controls.Border]@{ Width = 28 Height = $Height Margin = [System.Windows.Thickness]::new(4, 0, 0, 0) Cursor = [System.Windows.Input.Cursors]::Hand ToolTip = 'Hold to reveal password' Background = ConvertTo-UiBrush $colors.ButtonBg BorderBrush = ConvertTo-UiBrush $colors.Border BorderThickness = [System.Windows.Thickness]::new(1) CornerRadius = [System.Windows.CornerRadius]::new(3) Tag = 'ButtonBgBrush' } [PsUi.ThemeEngine]::RegisterElement($peekBtn) $eyeIcon = [System.Windows.Controls.TextBlock]@{ Text = [PsUi.ModuleContext]::GetIcon('DarkEye') FontFamily = [System.Windows.Media.FontFamily]::new('Segoe MDL2 Assets') FontSize = 14 Foreground = ConvertTo-UiBrush $colors.ButtonFg HorizontalAlignment = 'Center' VerticalAlignment = 'Center' IsHitTestVisible = $false Tag = 'ButtonFgBrush' } [PsUi.ThemeEngine]::RegisterElement($eyeIcon) $peekBtn.Child = $eyeIcon # Store control refs on the button so handlers can access them via $sender.DataContext. # .GetNewClosure() breaks in New-UiWindow because private functions get re-injected as # strings into a fresh runspace, so captured variables don't survive the round-trip. $peekBtn.DataContext = @{ PasswordBox = $passBox RevealBox = $revealBox } # Mouse down: reveal password text in the overlay TextBox $peekBtn.Add_MouseLeftButtonDown({ param($sender, $eventArgs) $state = $sender.DataContext $pwb = $state.PasswordBox if ($null -eq $pwb) { return } # Read .Password directly -- SecureString marshaling needs try/finally which crashes # inside string-injected handlers. A brief peek doesn't benefit from SecureString anyway. $state.RevealBox.Text = $pwb.Password $state.PasswordBox.Visibility = [System.Windows.Visibility]::Collapsed $state.RevealBox.Visibility = [System.Windows.Visibility]::Visible }) # Mouse up: hide password $peekBtn.Add_MouseLeftButtonUp({ param($sender, $eventArgs) $state = $sender.DataContext $state.PasswordBox.Visibility = [System.Windows.Visibility]::Visible $state.RevealBox.Visibility = [System.Windows.Visibility]::Collapsed $state.RevealBox.Text = '' }) # Mouse leave: also hide (handles drag-off-button while holding) $peekBtn.Add_MouseLeave({ param($sender, $eventArgs) $state = $sender.DataContext if ($state.RevealBox.Visibility -eq [System.Windows.Visibility]::Visible) { $state.PasswordBox.Visibility = [System.Windows.Visibility]::Visible $state.RevealBox.Visibility = [System.Windows.Visibility]::Collapsed $state.RevealBox.Text = '' } }) [System.Windows.Controls.Grid]::SetColumn($peekBtn, 1) [void]$peekWrapper.Children.Add($peekBtn) return @{ Container = $peekWrapper PasswordBox = $passBox } } |