Skip to content

Commit

Permalink
Feat(input): Add theme.validationFailureMode option to control behavi…
Browse files Browse the repository at this point in the history
…or on validation failure. Fix #374
  • Loading branch information
SBoudrias committed Dec 7, 2024
1 parent 506d97b commit 2942263
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 5 deletions.
3 changes: 3 additions & 0 deletions packages/input/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,12 @@ type Theme = {
error: (text: string) => string;
defaultAnswer: (text: string) => string;
};
validationFailureMode: 'keep' | 'clear';
};
```

`validationFailureMode` defines the behavior of the prompt when the value submitted is invalid. By default, we'll keep the value allowing the user to edit it. When the theme option is set to `clear`, we'll remove and reset to an empty string.

# License

Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
Expand Down
28 changes: 28 additions & 0 deletions packages/input/input.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,34 @@ describe('input prompt', () => {
await expect(answer).resolves.toEqual('2');
});

it('can clear value when validation fail', async () => {
const { answer, events, getScreen } = await render(input, {
message: 'Answer 2 ===',
validate: (value: string) => value === '2',
theme: {
validationFailureMode: 'clear',
},
});

expect(getScreen()).toMatchInlineSnapshot(`"? Answer 2 ==="`);

events.type('1');
expect(getScreen()).toMatchInlineSnapshot(`"? Answer 2 === 1"`);

events.keypress('enter');
await Promise.resolve();
expect(getScreen()).toMatchInlineSnapshot(`
"? Answer 2 ===
> You must provide a valid value"
`);

events.type('2');
expect(getScreen()).toMatchInlineSnapshot(`"? Answer 2 === 2"`);

events.keypress('enter');
await expect(answer).resolves.toEqual('2');
});

it('handle asynchronous validation', async () => {
const { answer, events, getScreen } = await render(input, {
message: 'Answer 2 ===',
Expand Down
22 changes: 17 additions & 5 deletions packages/input/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,26 @@ import {
} from '@inquirer/core';
import type { PartialDeep } from '@inquirer/type';

type InputTheme = {
validationFailureMode: 'keep' | 'clear';
};

const inputTheme: InputTheme = {
validationFailureMode: 'keep',
};

type InputConfig = {
message: string;
default?: string;
required?: boolean;
transformer?: (value: string, { isFinal }: { isFinal: boolean }) => string;
validate?: (value: string) => boolean | string | Promise<string | boolean>;
theme?: PartialDeep<Theme>;
theme?: PartialDeep<Theme<InputTheme>>;
};

export default createPrompt<string, InputConfig>((config, done) => {
const { required, validate = () => true } = config;
const theme = makeTheme(config.theme);
const theme = makeTheme<InputTheme>(inputTheme, config.theme);
const [status, setStatus] = useState<Status>('idle');
const [defaultValue = '', setDefaultValue] = useState<string>(config.default);
const [errorMsg, setError] = useState<string>();
Expand All @@ -47,9 +55,13 @@ export default createPrompt<string, InputConfig>((config, done) => {
setStatus('done');
done(answer);
} else {
// Reset the readline line value to the previous value. On line event, the value
// get cleared, forcing the user to re-enter the value instead of fixing it.
rl.write(value);
if (theme.validationFailureMode === 'clear') {
setValue('');
} else {
// Reset the readline line value to the previous value. On line event, the value
// get cleared, forcing the user to re-enter the value instead of fixing it.
rl.write(value);
}
setError(isValid || 'You must provide a valid value');
setStatus('idle');
}
Expand Down

0 comments on commit 2942263

Please sign in to comment.