[READ-ONLY] Mirror of https://github.com/bombshell-dev/clack. Effortlessly build beautiful command-line apps bomb.sh/docs/clack/basics/getting-started/
cli command-line command-line-app node prompt prompts
5

Configure Feed

Select the types of activity you want to include in your feed.

feat(autocomplete): do not set default filter for options getters (#496)

authored by

James Garbutt and committed by
GitHub
(Mar 23, 2026, 4:58 PM -0700) 417b4517 001351e8

+54 -5
+5
.changeset/silent-sides-call.md
··· 1 + --- 2 + "@clack/core": patch 3 + --- 4 + 5 + Only apply autocomplete default filter if it has been explicitly set or if options is not a getter.
+8 -5
packages/core/src/prompts/autocomplete.ts
··· 71 71 focusedValue: T['value'] | undefined; 72 72 #cursor = 0; 73 73 #lastUserInput = ''; 74 - #filterFn: FilterFunction<T>; 74 + #filterFn: FilterFunction<T> | undefined; 75 75 #options: T[] | (() => T[]); 76 76 #placeholder: string | undefined; 77 77 ··· 106 106 const options = this.options; 107 107 this.filteredOptions = [...options]; 108 108 this.multiple = opts.multiple === true; 109 - this.#filterFn = opts.filter ?? defaultFilter; 109 + this.#filterFn = 110 + typeof opts.options === 'function' ? opts.filter : (opts.filter ?? defaultFilter); 110 111 let initialValues: unknown[] | undefined; 111 112 if (opts.initialValue && Array.isArray(opts.initialValue)) { 112 113 if (this.multiple) { ··· 160 161 const placeholderMatchesOption = 161 162 placeholder !== undefined && 162 163 placeholder !== '' && 163 - options.some((opt) => !opt.disabled && this.#filterFn(placeholder, opt)); 164 + options.some( 165 + (opt) => !opt.disabled && (this.#filterFn ? this.#filterFn(placeholder, opt) : true) 166 + ); 164 167 if (key.name === 'tab' && isEmptyOrOnlyTab && placeholderMatchesOption) { 165 168 if (this.userInput === '\t') { 166 169 this._clearUserInput(); ··· 225 228 226 229 const options = this.options; 227 230 228 - if (value) { 229 - this.filteredOptions = options.filter((opt) => this.#filterFn(value, opt)); 231 + if (value && this.#filterFn) { 232 + this.filteredOptions = options.filter((opt) => this.#filterFn?.(value, opt)); 230 233 } else { 231 234 this.filteredOptions = [...options]; 232 235 }
+41
packages/core/test/prompts/autocomplete.test.ts
··· 216 216 expect(result).to.equal('apple'); 217 217 }); 218 218 219 + test('options as function skips default filter', () => { 220 + const dynamicOptions = [ 221 + { value: 'apple', label: 'Apple' }, 222 + { value: 'banana', label: 'Banana' }, 223 + ]; 224 + const instance = new AutocompletePrompt({ 225 + input, 226 + output, 227 + render: () => 'foo', 228 + options: () => dynamicOptions, 229 + }); 230 + 231 + instance.prompt(); 232 + 233 + input.emit('keypress', 'z', { name: 'z' }); 234 + 235 + expect(instance.filteredOptions).toEqual(dynamicOptions); 236 + }); 237 + 238 + test('options as function applies user-provided filter', () => { 239 + const dynamicOptions = [ 240 + { value: 'apple', label: 'Apple' }, 241 + { value: 'banana', label: 'Banana' }, 242 + { value: 'cherry', label: 'Cherry' }, 243 + ]; 244 + const instance = new AutocompletePrompt({ 245 + input, 246 + output, 247 + render: () => 'foo', 248 + options: () => dynamicOptions, 249 + filter: (search, opt) => (opt.label ?? '').toLowerCase().endsWith(search.toLowerCase()), 250 + }); 251 + 252 + instance.prompt(); 253 + 254 + input.emit('keypress', 'a', { name: 'a' }); 255 + 256 + // 'endsWith' matches Banana but not Apple or Cherry 257 + expect(instance.filteredOptions).toEqual([{ value: 'banana', label: 'Banana' }]); 258 + }); 259 + 219 260 test('Tab with non-matching placeholder does not fill input', async () => { 220 261 const instance = new AutocompletePrompt({ 221 262 input,