From 05641b60309c2ac7f66b0d2c7ba0916cd37efa73 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 10 May 2024 17:16:59 +0100 Subject: [PATCH 01/12] Allow selecting all options in `multiselect` --- src/FormBuilder.php | 2 +- src/MultiSelectPrompt.php | 31 +++++++++++++++++++++++++++++++ src/helpers.php | 2 +- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/FormBuilder.php b/src/FormBuilder.php index 621c19b8..f6de91c5 100644 --- a/src/FormBuilder.php +++ b/src/FormBuilder.php @@ -163,7 +163,7 @@ public function search(string $label, Closure $options, string $placeholder = '' * * @param Closure(string): array $options */ - public function multisearch(string $label, Closure $options, string $placeholder = '', int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.', ?string $name = null): self + public function multisearch(string $label, Closure $options, string $placeholder = '', int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.', ?string $name = null, bool $canSelectAll = false): self { return $this->runPrompt(multisearch(...), get_defined_vars()); } diff --git a/src/MultiSelectPrompt.php b/src/MultiSelectPrompt.php index b2f0c704..2794ff97 100644 --- a/src/MultiSelectPrompt.php +++ b/src/MultiSelectPrompt.php @@ -43,11 +43,18 @@ public function __construct( public bool|string $required = false, public mixed $validate = null, public string $hint = '', + public bool $canSelectAll = false, ) { $this->options = $options instanceof Collection ? $options->all() : $options; $this->default = $default instanceof Collection ? $default->all() : $default; $this->values = $this->default; + if ($this->canSelectAll) { + $this->options = array_is_list($this->options) + ? ['All', ...$this->options] + : ['All' => 'All', ...$this->options]; + } + $this->initializeScrolling(0); $this->on('key', fn ($key) => match ($key) { @@ -129,5 +136,29 @@ protected function toggleHighlighted(): void } else { $this->values[] = $value; } + + if ($this->canSelectAll) { + $this->handleSelectAll($value); + } + } + + protected function handleSelectAll(string $option): void + { + // When selecting "All", select all options. + if ($option === 'All' && in_array('All', $this->values)) { + $this->values = array_is_list($this->options) + ? array_values($this->options) + : array_keys($this->options); + } + + // When deselecting "All", deselect all options. + if ($option === 'All' && ! in_array('All', $this->values)) { + $this->values = []; + } + + // When deselecting one of the options, deselect "All". + if ($option !== 'All' && in_array('All', $this->values)) { + $this->values = array_filter($this->values, fn ($value) => $value !== 'All'); + } } } diff --git a/src/helpers.php b/src/helpers.php index d1454ad2..741063ca 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -47,7 +47,7 @@ function select(string $label, array|Collection $options, int|string|null $defau * @param array|Collection $default * @return array */ -function multiselect(string $label, array|Collection $options, array|Collection $default = [], int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.'): array +function multiselect(string $label, array|Collection $options, array|Collection $default = [], int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.', bool $canSelectAll = false): array { return (new MultiSelectPrompt(...func_get_args()))->prompt(); } From 33a4844c618aabab70e6726218c6d6fd5738e131 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 10 May 2024 17:17:05 +0100 Subject: [PATCH 02/12] Add test --- tests/Feature/MultiSelectPromptTest.php | 62 +++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tests/Feature/MultiSelectPromptTest.php b/tests/Feature/MultiSelectPromptTest.php index 53b9cbc8..edfe077c 100644 --- a/tests/Feature/MultiSelectPromptTest.php +++ b/tests/Feature/MultiSelectPromptTest.php @@ -231,3 +231,65 @@ Prompt::validateUsing(fn () => null); }); + +it('supports select all option', function () { + // Ensures all options are selected when the "All" option is selected. + Prompt::fake([Key::SPACE, Key::ENTER]); + + $result = multiselect( + label: 'What are your favorite colors?', + options: [ + 'Red', + 'Green', + 'Blue', + ], + canSelectAll: true + ); + + expect($result)->toBe(['All', 'Red', 'Green', 'Blue']); + + Prompt::assertStrippedOutputContains('│ All'); + Prompt::assertStrippedOutputContains('│ Red'); + Prompt::assertStrippedOutputContains('│ Green'); + Prompt::assertStrippedOutputContains('│ Blue'); + + // Ensures all options are deselected when the "All" option is deselected. + Prompt::fake([Key::SPACE, Key::SPACE, Key::ENTER]); + + $result = multiselect( + label: 'What are your favorite colors?', + options: [ + 'Red', + 'Green', + 'Blue', + ], + canSelectAll: true + ); + + expect($result)->toBe([]); + + Prompt::assertStrippedOutputDoesntContain('│ All'); + Prompt::assertStrippedOutputDoesntContain('│ Red'); + Prompt::assertStrippedOutputDoesntContain('│ Green'); + Prompt::assertStrippedOutputDoesntContain('│ Blue'); + + // Ensures the "All" option is deselected when a single option is deselected. + Prompt::fake([Key::SPACE, Key::DOWN, Key::SPACE, Key::ENTER]); + + $result = multiselect( + label: 'What are your favorite colors?', + options: [ + 'Red', + 'Green', + 'Blue', + ], + canSelectAll: true + ); + + expect($result)->toBe(['Green', 'Blue']); + + Prompt::assertStrippedOutputDoesntContain('│ All'); + Prompt::assertStrippedOutputDoesntContain('│ Red'); + Prompt::assertStrippedOutputContains('│ Green'); + Prompt::assertStrippedOutputContains('│ Blue'); +}); From e1bb0521adf0a24bc71fe939114b7b43a9bccdaa Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 10 May 2024 17:45:13 +0100 Subject: [PATCH 03/12] Update type hint --- src/MultiSelectPrompt.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MultiSelectPrompt.php b/src/MultiSelectPrompt.php index 2794ff97..5fccc534 100644 --- a/src/MultiSelectPrompt.php +++ b/src/MultiSelectPrompt.php @@ -142,7 +142,7 @@ protected function toggleHighlighted(): void } } - protected function handleSelectAll(string $option): void + protected function handleSelectAll(int|string $option): void { // When selecting "All", select all options. if ($option === 'All' && in_array('All', $this->values)) { From 823968a456d8386d3874c4c85564f3157c3e1870 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Mon, 20 May 2024 16:54:03 +0100 Subject: [PATCH 04/12] Make it possible to pass custom label to select all option --- src/FormBuilder.php | 2 +- src/MultiSelectPrompt.php | 22 +++++++++++++--------- src/helpers.php | 2 +- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/FormBuilder.php b/src/FormBuilder.php index f6de91c5..c3b3b0a9 100644 --- a/src/FormBuilder.php +++ b/src/FormBuilder.php @@ -163,7 +163,7 @@ public function search(string $label, Closure $options, string $placeholder = '' * * @param Closure(string): array $options */ - public function multisearch(string $label, Closure $options, string $placeholder = '', int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.', ?string $name = null, bool $canSelectAll = false): self + public function multisearch(string $label, Closure $options, string $placeholder = '', int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.', ?string $name = null, bool|string $selectAll = false): self { return $this->runPrompt(multisearch(...), get_defined_vars()); } diff --git a/src/MultiSelectPrompt.php b/src/MultiSelectPrompt.php index 5fccc534..2448184e 100644 --- a/src/MultiSelectPrompt.php +++ b/src/MultiSelectPrompt.php @@ -43,16 +43,18 @@ public function __construct( public bool|string $required = false, public mixed $validate = null, public string $hint = '', - public bool $canSelectAll = false, + public bool|string $selectAll = false, ) { $this->options = $options instanceof Collection ? $options->all() : $options; $this->default = $default instanceof Collection ? $default->all() : $default; $this->values = $this->default; - if ($this->canSelectAll) { + if ($this->selectAll) { + $selectAllOption = is_string($this->selectAll) ? $this->selectAll : 'All'; + $this->options = array_is_list($this->options) - ? ['All', ...$this->options] - : ['All' => 'All', ...$this->options]; + ? [$selectAllOption, ...$this->options] + : [$selectAllOption => $selectAllOption, ...$this->options]; } $this->initializeScrolling(0); @@ -137,28 +139,30 @@ protected function toggleHighlighted(): void $this->values[] = $value; } - if ($this->canSelectAll) { + if ($this->selectAll) { $this->handleSelectAll($value); } } protected function handleSelectAll(int|string $option): void { + $selectAllOption = is_string($this->selectAll) ? $this->selectAll : 'All'; + // When selecting "All", select all options. - if ($option === 'All' && in_array('All', $this->values)) { + if ($option === $selectAllOption && in_array($selectAllOption, $this->values)) { $this->values = array_is_list($this->options) ? array_values($this->options) : array_keys($this->options); } // When deselecting "All", deselect all options. - if ($option === 'All' && ! in_array('All', $this->values)) { + if ($option === $selectAllOption && ! in_array($selectAllOption, $this->values)) { $this->values = []; } // When deselecting one of the options, deselect "All". - if ($option !== 'All' && in_array('All', $this->values)) { - $this->values = array_filter($this->values, fn ($value) => $value !== 'All'); + if ($option !== $selectAllOption && in_array($selectAllOption, $this->values)) { + $this->values = array_filter($this->values, fn ($value) => $value !== $selectAllOption); } } } diff --git a/src/helpers.php b/src/helpers.php index e4b484b4..1aa87e02 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -56,7 +56,7 @@ function select(string $label, array|Collection $options, int|string|null $defau * @param array|Collection $default * @return array */ - function multiselect(string $label, array|Collection $options, array|Collection $default = [], int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.', bool $canSelectAll = false): array + function multiselect(string $label, array|Collection $options, array|Collection $default = [], int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.', bool|string $selectAll = false): array { return (new MultiSelectPrompt(...func_get_args()))->prompt(); } From 267282c17587c5e0c5061565f9b728f3bd135ccc Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 23 May 2024 10:57:59 +0100 Subject: [PATCH 05/12] Back out of this approach --- src/FormBuilder.php | 2 +- src/MultiSelectPrompt.php | 35 -------------- src/helpers.php | 2 +- tests/Feature/MultiSelectPromptTest.php | 62 ------------------------- 4 files changed, 2 insertions(+), 99 deletions(-) diff --git a/src/FormBuilder.php b/src/FormBuilder.php index c3b3b0a9..621c19b8 100644 --- a/src/FormBuilder.php +++ b/src/FormBuilder.php @@ -163,7 +163,7 @@ public function search(string $label, Closure $options, string $placeholder = '' * * @param Closure(string): array $options */ - public function multisearch(string $label, Closure $options, string $placeholder = '', int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.', ?string $name = null, bool|string $selectAll = false): self + public function multisearch(string $label, Closure $options, string $placeholder = '', int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.', ?string $name = null): self { return $this->runPrompt(multisearch(...), get_defined_vars()); } diff --git a/src/MultiSelectPrompt.php b/src/MultiSelectPrompt.php index 2448184e..b2f0c704 100644 --- a/src/MultiSelectPrompt.php +++ b/src/MultiSelectPrompt.php @@ -43,20 +43,11 @@ public function __construct( public bool|string $required = false, public mixed $validate = null, public string $hint = '', - public bool|string $selectAll = false, ) { $this->options = $options instanceof Collection ? $options->all() : $options; $this->default = $default instanceof Collection ? $default->all() : $default; $this->values = $this->default; - if ($this->selectAll) { - $selectAllOption = is_string($this->selectAll) ? $this->selectAll : 'All'; - - $this->options = array_is_list($this->options) - ? [$selectAllOption, ...$this->options] - : [$selectAllOption => $selectAllOption, ...$this->options]; - } - $this->initializeScrolling(0); $this->on('key', fn ($key) => match ($key) { @@ -138,31 +129,5 @@ protected function toggleHighlighted(): void } else { $this->values[] = $value; } - - if ($this->selectAll) { - $this->handleSelectAll($value); - } - } - - protected function handleSelectAll(int|string $option): void - { - $selectAllOption = is_string($this->selectAll) ? $this->selectAll : 'All'; - - // When selecting "All", select all options. - if ($option === $selectAllOption && in_array($selectAllOption, $this->values)) { - $this->values = array_is_list($this->options) - ? array_values($this->options) - : array_keys($this->options); - } - - // When deselecting "All", deselect all options. - if ($option === $selectAllOption && ! in_array($selectAllOption, $this->values)) { - $this->values = []; - } - - // When deselecting one of the options, deselect "All". - if ($option !== $selectAllOption && in_array($selectAllOption, $this->values)) { - $this->values = array_filter($this->values, fn ($value) => $value !== $selectAllOption); - } } } diff --git a/src/helpers.php b/src/helpers.php index 1aa87e02..f49a9912 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -56,7 +56,7 @@ function select(string $label, array|Collection $options, int|string|null $defau * @param array|Collection $default * @return array */ - function multiselect(string $label, array|Collection $options, array|Collection $default = [], int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.', bool|string $selectAll = false): array + function multiselect(string $label, array|Collection $options, array|Collection $default = [], int $scroll = 5, bool|string $required = false, mixed $validate = null, string $hint = 'Use the space bar to select options.'): array { return (new MultiSelectPrompt(...func_get_args()))->prompt(); } diff --git a/tests/Feature/MultiSelectPromptTest.php b/tests/Feature/MultiSelectPromptTest.php index edfe077c..53b9cbc8 100644 --- a/tests/Feature/MultiSelectPromptTest.php +++ b/tests/Feature/MultiSelectPromptTest.php @@ -231,65 +231,3 @@ Prompt::validateUsing(fn () => null); }); - -it('supports select all option', function () { - // Ensures all options are selected when the "All" option is selected. - Prompt::fake([Key::SPACE, Key::ENTER]); - - $result = multiselect( - label: 'What are your favorite colors?', - options: [ - 'Red', - 'Green', - 'Blue', - ], - canSelectAll: true - ); - - expect($result)->toBe(['All', 'Red', 'Green', 'Blue']); - - Prompt::assertStrippedOutputContains('│ All'); - Prompt::assertStrippedOutputContains('│ Red'); - Prompt::assertStrippedOutputContains('│ Green'); - Prompt::assertStrippedOutputContains('│ Blue'); - - // Ensures all options are deselected when the "All" option is deselected. - Prompt::fake([Key::SPACE, Key::SPACE, Key::ENTER]); - - $result = multiselect( - label: 'What are your favorite colors?', - options: [ - 'Red', - 'Green', - 'Blue', - ], - canSelectAll: true - ); - - expect($result)->toBe([]); - - Prompt::assertStrippedOutputDoesntContain('│ All'); - Prompt::assertStrippedOutputDoesntContain('│ Red'); - Prompt::assertStrippedOutputDoesntContain('│ Green'); - Prompt::assertStrippedOutputDoesntContain('│ Blue'); - - // Ensures the "All" option is deselected when a single option is deselected. - Prompt::fake([Key::SPACE, Key::DOWN, Key::SPACE, Key::ENTER]); - - $result = multiselect( - label: 'What are your favorite colors?', - options: [ - 'Red', - 'Green', - 'Blue', - ], - canSelectAll: true - ); - - expect($result)->toBe(['Green', 'Blue']); - - Prompt::assertStrippedOutputDoesntContain('│ All'); - Prompt::assertStrippedOutputDoesntContain('│ Red'); - Prompt::assertStrippedOutputContains('│ Green'); - Prompt::assertStrippedOutputContains('│ Blue'); -}); From d95856cb566fca0823e30c441a9f092597db300c Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 23 May 2024 11:25:18 +0100 Subject: [PATCH 06/12] Multiselect: Ctrl+A should select all options --- src/MultiSelectPrompt.php | 17 ++++++++++++++- tests/Feature/MultiSelectPromptTest.php | 28 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/MultiSelectPrompt.php b/src/MultiSelectPrompt.php index b2f0c704..3f1316ea 100644 --- a/src/MultiSelectPrompt.php +++ b/src/MultiSelectPrompt.php @@ -53,7 +53,8 @@ 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::CTRL_A => $this->toggleAll(), + Key::oneOf([Key::HOME], $key) => $this->highlight(0), Key::oneOf([Key::END, Key::CTRL_E], $key) => $this->highlight(count($this->options) - 1), Key::SPACE => $this->toggleHighlighted(), Key::ENTER => $this->submit(), @@ -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/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); From 1fffce6aa850a0b5f946b5bd2c0b8f5feba6e07c Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 23 May 2024 12:00:51 +0100 Subject: [PATCH 07/12] Multisearch: Ctrl+A should select all options Although... Ctrl+A should only select all options when the user is in the context of the options, not when they're in the context of searching. --- src/MultiSearchPrompt.php | 20 +++++++++++++++--- tests/Feature/MultiSearchPromptTest.php | 28 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/MultiSearchPrompt.php b/src/MultiSearchPrompt.php index 86ea336c..6150085d 100644 --- a/src/MultiSearchPrompt.php +++ b/src/MultiSearchPrompt.php @@ -43,15 +43,15 @@ public function __construct( public mixed $validate = null, public string $hint = '', ) { - $this->trackTypedValue(submit: false, ignore: fn ($key) => Key::oneOf([Key::SPACE, Key::HOME, Key::END, Key::CTRL_A, Key::CTRL_E], $key) && $this->highlighted !== null); + $this->trackTypedValue(submit: false, ignore: fn ($key) => Key::oneOf([Key::SPACE, Key::HOME, Key::END, Key::CTRL_A], $key) && $this->highlighted !== null); $this->initializeScrolling(null); $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::CTRL_A => $this->highlighted !== null ? $this->toggleAll() : null, + Key::CTRL_E => null, Key::SPACE => $this->highlighted !== null ? $this->toggleHighlighted() : null, Key::ENTER => $this->submit(), Key::LEFT, Key::LEFT_ARROW, Key::RIGHT, Key::RIGHT_ARROW => $this->highlighted = null, @@ -132,6 +132,20 @@ public function visible(): array return array_slice($this->matches(), $this->firstVisible, $this->scroll, preserve_keys: true); } + /** + * Toggle all options. + */ + protected function toggleAll(): void + { + if (count($this->values) === count($this->matches)) { + $this->values = []; + } else { + $this->values = $this->isList() + ? array_combine(array_values($this->matches), array_values($this->matches)) + : array_combine(array_keys($this->matches), array_values($this->matches)); + } + } + /** * 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([]); +}); From 489e7ea20fc1829d2a0edcef3c98458480d8c163 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 23 May 2024 12:20:50 +0100 Subject: [PATCH 08/12] Add back Home & End keybindings --- src/MultiSearchPrompt.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MultiSearchPrompt.php b/src/MultiSearchPrompt.php index 6150085d..d635ed32 100644 --- a/src/MultiSearchPrompt.php +++ b/src/MultiSearchPrompt.php @@ -52,6 +52,8 @@ public function __construct( Key::DOWN, Key::DOWN_ARROW, Key::TAB => $this->highlightNext(count($this->matches), true), Key::CTRL_A => $this->highlighted !== null ? $this->toggleAll() : null, Key::CTRL_E => 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::ENTER => $this->submit(), Key::LEFT, Key::LEFT_ARROW, Key::RIGHT, Key::RIGHT_ARROW => $this->highlighted = null, From 1a28356bcffdee48ac9359792abf01531471fc17 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 23 May 2024 12:21:40 +0100 Subject: [PATCH 09/12] This change wasn't needed. --- src/MultiSearchPrompt.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MultiSearchPrompt.php b/src/MultiSearchPrompt.php index d635ed32..fe6dcbdb 100644 --- a/src/MultiSearchPrompt.php +++ b/src/MultiSearchPrompt.php @@ -43,7 +43,7 @@ public function __construct( public mixed $validate = null, public string $hint = '', ) { - $this->trackTypedValue(submit: false, ignore: fn ($key) => Key::oneOf([Key::SPACE, Key::HOME, Key::END, Key::CTRL_A], $key) && $this->highlighted !== null); + $this->trackTypedValue(submit: false, ignore: fn ($key) => Key::oneOf([Key::SPACE, Key::HOME, Key::END, Key::CTRL_A, Key::CTRL_E], $key) && $this->highlighted !== null); $this->initializeScrolling(null); From 7f0bb927f24e1db148d5e4df82f31980799ce3fe Mon Sep 17 00:00:00 2001 From: Jess Archer Date: Fri, 24 May 2024 10:43:20 +1000 Subject: [PATCH 10/12] Formatting --- src/MultiSearchPrompt.php | 6 +++--- src/MultiSelectPrompt.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/MultiSearchPrompt.php b/src/MultiSearchPrompt.php index fe6dcbdb..e6928e95 100644 --- a/src/MultiSearchPrompt.php +++ b/src/MultiSearchPrompt.php @@ -50,11 +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) => $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::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::ENTER => $this->submit(), Key::LEFT, Key::LEFT_ARROW, Key::RIGHT, Key::RIGHT_ARROW => $this->highlighted = null, default => $this->search(), diff --git a/src/MultiSelectPrompt.php b/src/MultiSelectPrompt.php index 3f1316ea..2dee5285 100644 --- a/src/MultiSelectPrompt.php +++ b/src/MultiSelectPrompt.php @@ -53,10 +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::CTRL_A => $this->toggleAll(), - Key::oneOf([Key::HOME], $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, }); From db6679af070649aca25c80597030b1de5525d6e5 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Fri, 24 May 2024 22:29:52 +0100 Subject: [PATCH 11/12] Check if current matches are selected and only filter out current matches --- src/MultiSearchPrompt.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/MultiSearchPrompt.php b/src/MultiSearchPrompt.php index fe6dcbdb..f62e3ead 100644 --- a/src/MultiSearchPrompt.php +++ b/src/MultiSearchPrompt.php @@ -139,12 +139,19 @@ public function visible(): array */ protected function toggleAll(): void { - if (count($this->values) === count($this->matches)) { - $this->values = []; + $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() + ? ! array_key_exists($value, $this->matches) + : ! array_key_exists(array_search($value, $this->matches), $this->matches) + ); } else { $this->values = $this->isList() - ? array_combine(array_values($this->matches), array_values($this->matches)) - : array_combine(array_keys($this->matches), array_values($this->matches)); + ? 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))); } } From 6bedd7a2fa9b35252c656925d13da38c93073466 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Mon, 27 May 2024 10:59:29 +0100 Subject: [PATCH 12/12] Fix "unselect all" behaviour when options are a list Co-authored-by: Jess Archer --- src/MultiSearchPrompt.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MultiSearchPrompt.php b/src/MultiSearchPrompt.php index 404ac0c5..5aac82b5 100644 --- a/src/MultiSearchPrompt.php +++ b/src/MultiSearchPrompt.php @@ -145,7 +145,7 @@ protected function toggleAll(): void if ($allMatchesSelected) { $this->values = array_filter($this->values, fn ($value) => $this->isList() - ? ! array_key_exists($value, $this->matches) + ? ! in_array($value, $this->matches) : ! array_key_exists(array_search($value, $this->matches), $this->matches) ); } else {