public/controls/inputs/New-UiCredential.ps1

function New-UiCredential {
    <#
    .SYNOPSIS
        Creates a credential input consisting of username and password fields.
    .DESCRIPTION
        Creates a pair of input fields for capturing credentials. The username field
        is a standard text input, while the password field is masked and carries the
        same eye button as New-UiInput -Password (hold to reveal, -NoPeek turns it
        off). In -Action blocks, the hydrated variable contains a PSCredential
        object.
    .PARAMETER Variable
        The variable name to register. The hydrated variable contains PSCredential.
    .PARAMETER Label
        Optional label displayed above the credential fields. Defaults to "Credentials".
    .PARAMETER UserLabel
        Label for the username field. Defaults to "Username".
    .PARAMETER PasswordLabel
        Label for the password field. Defaults to "Password".
    .PARAMETER DefaultUsername
        Default value for the username field.
    .PARAMETER NoPeek
        Hide the peek button on the password field. By default an eye icon next to the
        field reveals the password while held.
    .PARAMETER EnabledWhen
        Conditional enabling based on another control's state. Accepts either:
        - A variable name string (e.g., 'evalVCSA') - enables when that control is truthy
        - A scriptblock (e.g., { $evalPhoton -or $evalAlma }) - enables when expression is true
 
        Truthy values: CheckBox=checked, TextBox=non-empty, ComboBox=has selection.
    .PARAMETER ClearIfDisabled
        Accepted alongside -EnabledWhen, but the clear currently never fires. The credential
        fields keep their values when disabled either way.
    .PARAMETER SubmitButton
        Name of a registered button to trigger when Enter is pressed in the password field.
        The button must be created with -Variable to register it for lookup.
        Works with both New-UiButton and New-UiActionCard buttons.
    .PARAMETER WPFProperties
        Hashtable of additional WPF properties to apply to the container.
    .EXAMPLE
        New-UiCredential -Variable 'creds' -Label 'Remote Computer Credentials'
        # In -Action: $creds contains PSCredential
    .EXAMPLE
        New-UiCredential -Variable 'adminCreds' -DefaultUsername 'Administrator'
    .EXAMPLE
        New-UiCredential -Variable 'sshCreds' -Label 'SSH Credentials' -EnabledWhen 'useSSH'
        # Enabled only when the 'useSSH' toggle is checked
    .EXAMPLE
        New-UiCredential -Variable 'kioskCreds' -NoPeek
        # No reveal button - for tools that run on a screen other people watch
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Variable,

        [string]$Label = 'Credentials',

        [string]$UserLabel = 'Username',

        [string]$PasswordLabel = 'Password',

        [string]$DefaultUsername = '',

        [switch]$NoPeek,

        [object]$EnabledWhen,

        [switch]$ClearIfDisabled,

        [Parameter()]
        [string]$SubmitButton,

        [hashtable]$WPFProperties
    )

    $session = Assert-UiSession -CallerName 'New-UiCredential'
    Write-Debug "Creating credential input '$Variable'"
    $colors  = Get-ThemeColors

    $container = [System.Windows.Controls.StackPanel]::new()
    $container.Orientation = 'Vertical'

    # Has to stay with with New-UiInput's margin or the block sits 4px left of the inputs around it
    $container.Margin = [System.Windows.Thickness]::new(4, 4, 4, 8)

    if ($Label) {
        $groupLabel = [System.Windows.Controls.TextBlock]::new()
        $groupLabel.Text = $Label
        $groupLabel.FontWeight = 'SemiBold'
        $groupLabel.Foreground = ConvertTo-UiBrush $colors.ControlFg
        $groupLabel.Margin = [System.Windows.Thickness]::new(0, 0, 0, 8)
        $groupLabel.Tag = 'ControlFgBrush'
        [PsUi.ThemeEngine]::RegisterElement($groupLabel)
        [void]$container.Children.Add($groupLabel)
    }

    $fieldsPanel = [System.Windows.Controls.Grid]::new()
    $fieldsPanel.ColumnDefinitions.Add([System.Windows.Controls.ColumnDefinition]::new())
    $fieldsPanel.ColumnDefinitions.Add([System.Windows.Controls.ColumnDefinition]::new())
    $fieldsPanel.ColumnDefinitions[0].Width = [System.Windows.GridLength]::new(1, 'Star')
    $fieldsPanel.ColumnDefinitions[1].Width = [System.Windows.GridLength]::new(1, 'Star')

    $userContainer = [System.Windows.Controls.StackPanel]::new()
    $userContainer.Orientation = 'Vertical'
    $userContainer.Margin = [System.Windows.Thickness]::new(0, 0, 8, 0)
    [System.Windows.Controls.Grid]::SetColumn($userContainer, 0)

    $userLabelCtrl = [System.Windows.Controls.TextBlock]::new()
    $userLabelCtrl.Text = $UserLabel
    $userLabelCtrl.Foreground = ConvertTo-UiBrush $colors.ControlFg
    $userLabelCtrl.Margin = [System.Windows.Thickness]::new(0, 0, 0, 4)
    $userLabelCtrl.Tag = 'ControlFgBrush'
    [PsUi.ThemeEngine]::RegisterElement($userLabelCtrl)
    [void]$userContainer.Children.Add($userLabelCtrl)

    $userBox = [System.Windows.Controls.TextBox]::new()
    $userBox.Text = $DefaultUsername
    $userBox.Height = 32
    Set-TextBoxStyle -TextBox $userBox
    [void]$userContainer.Children.Add($userBox)

    $passContainer = [System.Windows.Controls.StackPanel]::new()
    $passContainer.Orientation = 'Vertical'
    $passContainer.Margin = [System.Windows.Thickness]::new(8, 0, 0, 0)
    [System.Windows.Controls.Grid]::SetColumn($passContainer, 1)

    $passLabelCtrl = [System.Windows.Controls.TextBlock]::new()
    $passLabelCtrl.Text = $PasswordLabel
    $passLabelCtrl.Foreground = ConvertTo-UiBrush $colors.ControlFg
    $passLabelCtrl.Margin = [System.Windows.Thickness]::new(0, 0, 0, 4)
    $passLabelCtrl.Tag = 'ControlFgBrush'
    [PsUi.ThemeEngine]::RegisterElement($passLabelCtrl)
    [void]$passContainer.Children.Add($passLabelCtrl)

    # Same hold to reveal treatment as New-UiInput -Password. The Grid the helper returns carries the eye button.
    $peekResult = New-PasswordInputWithPeek -NoPeek:$NoPeek
    $passBox    = $peekResult.PasswordBox
    [void]$passContainer.Children.Add($peekResult.Container)

    [void]$fieldsPanel.Children.Add($userContainer)
    [void]$fieldsPanel.Children.Add($passContainer)
    [void]$container.Children.Add($fieldsPanel)

    # Tag wrapper for FormLayout unwrapping (when label exists)
    if ($Label) {
        Set-UiFormControlTag -Wrapper $container -Label $groupLabel -Control $fieldsPanel
    }

    # Wrapper that holds references to both controls for Get-UiValue
    $credentialWrapper = [PSCustomObject]@{
        PSTypeName   = 'PsUi.CredentialControl'
        UsernameBox  = $userBox
        PasswordBox  = $passBox
        VariableName = $Variable
    }

    # Store the wrapper in Variables (AddControlSafe requires FrameworkElement)
    # Validation and hydration check for PsUi.CredentialControl type
    Write-Debug "Storing credential wrapper as '$Variable'"
    $session.Variables[$Variable] = $credentialWrapper

    # Register conditional enabling if specified
    if ($EnabledWhen) {
        Register-UiCondition -TargetControl $container -Condition $EnabledWhen -ClearIfDisabled:$ClearIfDisabled
    }

    # Enter on the password field triggers the submit button
    if ($SubmitButton) {
        $btnName        = $SubmitButton
        $ownerSessionId = $session.SessionId
        $passBox.Add_KeyDown({
            param($sender, $keyArgs)
            if ($keyArgs.Key -eq [System.Windows.Input.Key]::Return) {
                # Only trigger if password has content
                if ([string]::IsNullOrWhiteSpace($sender.Password)) { return }

                $sess = [PsUi.SessionManager]::GetSession($ownerSessionId)
                if (!$sess) { return }
                
                # Look up registered button and trigger its click
                $btn = $sess.GetRegisteredButton($btnName)
                if ($btn -and $btn.IsEnabled) {
                    $btn.RaiseEvent([System.Windows.RoutedEventArgs]::new([System.Windows.Controls.Primitives.ButtonBase]::ClickEvent))
                    $keyArgs.Handled = $true
                }
            }
        }.GetNewClosure())
    }

    # Apply custom WPF properties if specified
    if ($WPFProperties) {
        Set-UiProperties -Control $container -Properties $WPFProperties
    }

    # Add to current parent
    Write-Debug "Adding credential fields to parent"
    [void]$session.CurrentParent.Children.Add($container)

    return $container
}