Created a multiselect prompt

This commit is contained in:
2025-12-17 22:35:23 -06:00
parent 1d3722f08e
commit 54da9246b7
5 changed files with 119 additions and 1 deletions

View File

@@ -68,6 +68,7 @@
'Invoke-Text'
'Invoke-Confirm'
'Invoke-Select'
'Invoke-MultiSelect'
'Invoke-Spinner'
)

View File

@@ -0,0 +1,19 @@
function Invoke-MultiSelect {
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$Message,
[Parameter(Mandatory)][string[]]$Options,
[scriptblock]$Validate = $null
)
$initialState = New-PromptState
$initialState.HoveredIndex = 0
$initialState.SelectedIndices = [System.Collections.Generic.HashSet[int]]::new()
$renderFn = { param($s) Render-MultiSelectPrompt -State $s -Message $Message -Options $Options }
$updateFn = { param($s, $k) Update-MultiSelectStateFromKey -State $s -Key $k -Options $Options -Validate $Validate }
$finalState = Invoke-RenderLoop -InitialState $initialState -RenderFunction $renderFn -UpdateFunction $updateFn
if ($finalState.Status -eq [PromptStatus]::Cancelled) { throw "Operation cancelled by user" }
return $finalState.Value
}

View File

@@ -0,0 +1,53 @@
function Render-MultiSelectPrompt {
param([hashtable]$State, [string]$Message, [string[]]$Options)
$symbol = Get-Symbol -State $State
$symbolBar = Get-SymbolColored -Symbol "│" -State $State
$symbolEnd = Get-SymbolColored -Symbol "└" -State $State
$prompt = "$symbol $Message"
$renderOptions = {
param($state)
$out = ""
for ($i = 0; $i -lt $Options.Count; $i++) {
$out += "`n$symbolBar "
$params = @{ Style = 'Dim' }
$box = '◻'
if ($state.SelectedIndices.Contains($i)) {
$params = @{ Foreground = 'Green' }
$box = '◼'
}
elseif ($i -eq $state.HoveredIndex) {
$params = @{ Foreground = 'Cyan' }
}
$out += "$(Format-AnsiText $box @params) "
$params = if ($i -eq $state.HoveredIndex) { @{} } else { @{ Style = 'Dim' } }
$out += "$(Format-AnsiText $Options[$i] @params)"
}
return $out
}
switch ($State.Status) {
Submitted {
$prompt += "`n$symbolBar $(Format-AnsiText $($State.Value -join ", ") -Style 'Dim')"
$prompt += "`n$symbolBar"
}
Cancelled {
$prompt += "`n$symbolBar"
$prompt += "`n$symbolEnd"
}
Error {
$prompt += & $renderOptions $State
$prompt += "`n$symbolEnd $(Format-AnsiText $State.Feedback -Foreground 'Yellow')"
}
Default {
$prompt += & $renderOptions $State
$prompt += "`n$symbolEnd"
}
}
return $prompt
}

View File

@@ -0,0 +1,45 @@
function Update-MultiSelectStateFromKey {
param(
[hashtable]$State,
[System.ConsoleKeyInfo]$Key,
[string[]]$Options,
[scriptblock]$Validate = $null
)
$newState = $State.Clone()
if (-not $newState.ContainsKey('HoveredIndex')) { $newState.HoveredIndex = 0 }
if (-not $newState.ContainsKey('SelectedIndices')) { $newState.SelectedIndices = [System.Collections.Generic.HashSet[int]]::new() }
switch ($Key.Key) {
'UpArrow' { $newState.HoveredIndex = if ($newState.HoveredIndex -gt 0) { $newState.HoveredIndex - 1 } else { $Options.Count - 1 } }
'DownArrow' { $newState.HoveredIndex = if ($newState.HoveredIndex -lt ($Options.Count - 1)) { $newState.HoveredIndex + 1 } else { 0 } }
'Spacebar' {
if ($newState.SelectedIndices.Contains($newState.HoveredIndex)) {
[void]$newState.SelectedIndices.Remove($newState.HoveredIndex)
}
else {
[void]$newState.SelectedIndices.Add($newState.HoveredIndex)
}
}
'Enter' {
$newState.Value = $Options[$newState.SelectedIndices]
if ($Validate) {
$err = & $Validate $newState.Value
if ($err) {
$newState.Status = [PromptStatus]::Error
$newState.Feedback = $err
}
else { $newState.Status = [PromptStatus]::Submitted }
}
else { $newState.Status = [PromptStatus]::Submitted }
}
'Escape' { $newState.Status = [PromptStatus]::Cancelled }
'C' {
if ($Key.Modifiers -band [ConsoleModifiers]::Control) {
$newState.Status = [PromptStatus]::Cancelled
}
}
}
return $newState
}

View File

@@ -14,7 +14,7 @@ PowerShell CLI input prompt library heavily inspired by [github.com/bombshell-de
**New prompts/macros:**
- [ ] Multiselect prompt
- [x] Multiselect prompt
- [ ] Password prompt
- [ ] Logs (simple, styled text); Variants = info, success, step, warn, error, default
- [ ] Tasks (execute multiple tasks with spinners)