diff --git a/src/MultiSearchPrompt.php b/src/MultiSearchPrompt.php index 86ea336c..5aac82b5 100644 --- a/src/MultiSearchPrompt.php +++ b/src/MultiSearchPrompt.php @@ -50,9 +50,11 @@ public function __construct( $this->on('key', fn ($key) => match ($key) { Key::UP, Key::UP_ARROW, Key::SHIFT_TAB => $this->highlightPrevious(count($this->matches), true), Key::DOWN, Key::DOWN_ARROW, Key::TAB => $this->highlightNext(count($this->matches), true), - Key::oneOf([Key::HOME, Key::CTRL_A], $key) => $this->highlighted !== null ? $this->highlight(0) : null, - Key::oneOf([Key::END, Key::CTRL_E], $key) => $this->highlighted !== null ? $this->highlight(count($this->matches()) - 1) : null, + Key::oneOf(Key::HOME, $key) => $this->highlighted !== null ? $this->highlight(0) : null, + Key::oneOf(Key::END, $key) => $this->highlighted !== null ? $this->highlight(count($this->matches()) - 1) : null, Key::SPACE => $this->highlighted !== null ? $this->toggleHighlighted() : null, + Key::CTRL_A => $this->highlighted !== null ? $this->toggleAll() : null, + Key::CTRL_E => null, Key::ENTER => $this->submit(), Key::LEFT, Key::LEFT_ARROW, Key::RIGHT, Key::RIGHT_ARROW => $this->highlighted = null, default => $this->search(), @@ -132,6 +134,27 @@ public function visible(): array return array_slice($this->matches(), $this->firstVisible, $this->scroll, preserve_keys: true); } + /** + * Toggle all options. + */ + protected function toggleAll(): void + { + $allMatchesSelected = collect($this->matches)->every(fn ($label, $key) => $this->isList() + ? array_key_exists($label, $this->values) + : array_key_exists($key, $this->values)); + + if ($allMatchesSelected) { + $this->values = array_filter($this->values, fn ($value) => $this->isList() + ? ! in_array($value, $this->matches) + : ! array_key_exists(array_search($value, $this->matches), $this->matches) + ); + } else { + $this->values = $this->isList() + ? array_merge($this->values, array_combine(array_values($this->matches), array_values($this->matches))) + : array_merge($this->values, array_combine(array_keys($this->matches), array_values($this->matches))); + } + } + /** * Toggle the highlighted entry. */ diff --git a/src/MultiSelectPrompt.php b/src/MultiSelectPrompt.php index b2f0c704..2dee5285 100644 --- a/src/MultiSelectPrompt.php +++ b/src/MultiSelectPrompt.php @@ -53,9 +53,10 @@ public function __construct( $this->on('key', fn ($key) => match ($key) { Key::UP, Key::UP_ARROW, Key::LEFT, Key::LEFT_ARROW, Key::SHIFT_TAB, Key::CTRL_P, Key::CTRL_B, 'k', 'h' => $this->highlightPrevious(count($this->options)), Key::DOWN, Key::DOWN_ARROW, Key::RIGHT, Key::RIGHT_ARROW, Key::TAB, Key::CTRL_N, Key::CTRL_F, 'j', 'l' => $this->highlightNext(count($this->options)), - Key::oneOf([Key::HOME, Key::CTRL_A], $key) => $this->highlight(0), - Key::oneOf([Key::END, Key::CTRL_E], $key) => $this->highlight(count($this->options) - 1), + Key::oneOf(Key::HOME, $key) => $this->highlight(0), + Key::oneOf(Key::END, $key) => $this->highlight(count($this->options) - 1), Key::SPACE => $this->toggleHighlighted(), + Key::CTRL_A => $this->toggleAll(), Key::ENTER => $this->submit(), default => null, }); @@ -115,6 +116,20 @@ public function isSelected(string $value): bool return in_array($value, $this->values); } + /** + * Toggle all options. + */ + protected function toggleAll(): void + { + if (count($this->values) === count($this->options)) { + $this->values = []; + } else { + $this->values = array_is_list($this->options) + ? array_values($this->options) + : array_keys($this->options); + } + } + /** * Toggle the highlighted entry. */ diff --git a/tests/Feature/MultiSearchPromptTest.php b/tests/Feature/MultiSearchPromptTest.php index d23019e2..5b7a9b31 100644 --- a/tests/Feature/MultiSearchPromptTest.php +++ b/tests/Feature/MultiSearchPromptTest.php @@ -333,3 +333,31 @@ Prompt::validateUsing(fn () => null); }); + +it('supports selecting all options', function () { + Prompt::fake([Key::DOWN, Key::CTRL_A, Key::ENTER]); + + $result = multisearch( + label: 'What are your favorite colors?', + options: fn () => [ + 'red' => 'Red', + 'green' => 'Green', + 'blue' => 'Blue', + ], + ); + + expect($result)->toBe(['red', 'green', 'blue']); + + Prompt::fake([Key::DOWN, Key::CTRL_A, Key::CTRL_A, Key::ENTER]); + + $result = multisearch( + label: 'What are your favorite colors?', + options: fn () => [ + 'red' => 'Red', + 'green' => 'Green', + 'blue' => 'Blue', + ], + ); + + expect($result)->toBe([]); +}); diff --git a/tests/Feature/MultiSelectPromptTest.php b/tests/Feature/MultiSelectPromptTest.php index 53b9cbc8..df834a3f 100644 --- a/tests/Feature/MultiSelectPromptTest.php +++ b/tests/Feature/MultiSelectPromptTest.php @@ -170,6 +170,34 @@ expect($result)->toBe(['blue', 'red']); }); +it('supports selecting all options', function () { + Prompt::fake([Key::CTRL_A, Key::ENTER]); + + $result = multiselect( + label: 'What are your favorite colors?', + options: [ + 'red' => 'Red', + 'green' => 'Green', + 'blue' => 'Blue', + ] + ); + + expect($result)->toBe(['red', 'green', 'blue']); + + Prompt::fake([Key::CTRL_A, Key::CTRL_A, Key::ENTER]); + + $result = multiselect( + label: 'What are your favorite colors?', + options: [ + 'red' => 'Red', + 'green' => 'Green', + 'blue' => 'Blue', + ] + ); + + expect($result)->toBe([]); +}); + it('returns an empty array when non-interactive', function () { Prompt::interactive(false);