Skip to content

Commit

Permalink
Merge pull request #38 from terkelg/state-change
Browse files Browse the repository at this point in the history
State change
  • Loading branch information
terkelg authored Mar 3, 2018
2 parents a5b9532 + 0ff912a commit 5b54227
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 17 deletions.
2 changes: 1 addition & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const prompts = require('./prompts');

const toArray = val => (Array.isArray(val) ? val : val == null ? [] : [val]);
const ignore = ['suggest', 'format'];
const ignore = ['suggest', 'format', 'onState'];
const noop = () => {};

/**
Expand Down
40 changes: 30 additions & 10 deletions lib/prompts.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
'use strict';

const el = require('./elements');
const noop = () => {};

/**
* Text prompt
* @param {string} message Prompt message to display
* @param {string} [initial] Default string value
* @param {string} [style="default"] Render style ('default', 'password', 'invisible')
* @param {function} [onState] On state change callback
* @returns {Promise} Promise with user input
*/
function text({ message, initial, style }) {
function text({ message, initial, style, onState = noop }) {
if (typeof message !== 'string') throw new Error('message is required');
return new Promise((resolve, reject) => {
const p = new el.TextPrompt({ message, initial, style });
p.on('submit', resolve);
p.on('abort', reject);
p.on('state', onState)
});
}

/**
* Password prompt with masked input
* @param {string} message Prompt message to display
* @param {string} [initial] Default string value
* @param {function} [onState] On state change callback
* @returns {Promise} Promise with user input
*
*/
const password = ({ message, initial }) => text({ message, initial, style: 'password' });
const password = ({ message, initial, onState }) => text({ message, initial, onState, style: 'password' });

/**
* Prompt where input is invisible, like sudo
* @param {string} message Prompt message to display
* @param {string} [initial] Default string value
* @param {function} [onState] On state change callback
* @returns {Promise} Promise with user input
*/
const invisible = ({ message, initial }) => text({ message, initial, style: 'invisible' });
const invisible = ({ message, initial, onState }) => text({ message, initial, onState, style: 'invisible' });

/**
* Number prompt
Expand All @@ -41,29 +47,33 @@ const invisible = ({ message, initial }) => text({ message, initial, style: 'inv
* @param {number} [max] Max value
* @param {number} [min] Min value
* @param {string} [style="default"] Render style ('default', 'password', 'invisible')
* @param {function} [onState] On state change callback
* @returns {Promise} Promise with user input
*/
function number({ message, initial, max, min, style }) {
function number({ message, initial, max, min, style, onState = noop }) {
if (typeof message !== 'string') throw new Error('message is required');
return new Promise((resolve, reject) => {
const p = new el.NumberPrompt({ message, initial, max, min, style });
p.on('submit', resolve);
p.on('abort', reject);
p.on('state', onState)
});
}

/**
* Classic yes/no prompt
* @param {string} message Prompt message to display
* @param {boolean} [initial=false] Default value
* @param {function} [onState] On state change callback
* @returns {Promise} Promise with user input
*/
function confirm({ message, initial }) {
function confirm({ message, initial, onState = noop }) {
if (typeof message !== 'string') throw new Error('message is required');
return new Promise((resolve, reject) => {
const p = new el.ConfirmPrompt({ message, initial });
p.on('submit', resolve);
p.on('abort', reject);
p.on('state', onState);
});
}

Expand All @@ -73,14 +83,16 @@ function confirm({ message, initial }) {
* @param {string} [initial] Default string value
* @param {string} [style="default"] Render style ('default', 'password', 'invisible')
* @param {string} [separator] String separator
* @param {function} [onState] On state change callback
* @returns {Promise} Promise with user input, in form of an `Array`
*/
function list({ message, initial, style, separator = ',' }) {
function list({ message, initial, style, separator = ',', onState = noop }) {
if (typeof message !== 'string') throw new Error('message is required');
return new Promise((resolve, reject) => {
const p = new el.TextPrompt({ message, initial, style });
p.on('submit', str => resolve(str.split(separator).map(s => s.trim())));
p.on('abort', reject);
p.on('state', onState);
});
}

Expand All @@ -90,14 +102,16 @@ function list({ message, initial, style, separator = ',' }) {
* @param {boolean} [initial=false] Default value
* @param {string} [active="on"] Text for `active` state
* @param {string} [inactive="off"] Text for `inactive` state
* @param {function} [onState] On state change callback
* @returns {Promise} Promise with user input
*/
function toggle({ message, initial, active, inactive }) {
function toggle({ message, initial, active, inactive, onState = noop }) {
if (typeof message !== 'string') throw new Error('message is required');
return new Promise((resolve, reject) => {
const p = new el.TogglePrompt({ message, initial, active, inactive });
p.on('submit', resolve);
p.on('abort', reject);
p.on('state', onState);
});
}

Expand All @@ -106,14 +120,16 @@ function toggle({ message, initial, active, inactive }) {
* @param {string} message Prompt message to display
* @param {Array} choices Array of choices objects `[{ title, value }, ...]`
* @param {number} [initial] Index of default value
* @param {function} [onState] On state change callback
* @returns {Promise} Promise with user input
*/
function select({ message, choices, initial }) {
function select({ message, choices, initial, onState = noop }) {
if (typeof message !== 'string') throw new Error('message is required');
return new Promise((resolve, reject) => {
const p = new el.SelectPrompt({ message, choices, initial });
p.on('submit', resolve);
p.on('abort', reject);
p.on('state', onState);
});
}

Expand All @@ -123,9 +139,10 @@ function select({ message, choices, initial }) {
* @param {Array} choices Array of choices objects `[{ title, value, [selected] }, ...]`
* @param {number} [max] Max select
* @param {string} [hint] Hint to display user
* @param {function} [onState] On state change callback
* @returns {Promise} Promise with user input
*/
function multiselect({ message, choices, max, hint }) {
function multiselect({ message, choices, max, hint, onState = noop }) {
if (typeof message !== 'string') throw new Error('message is required');
if (!Array.isArray(choices)) throw new Error('choices array is required');

Expand All @@ -134,6 +151,7 @@ function multiselect({ message, choices, max, hint }) {
const selected = items => items.filter(item => item.selected).map(item => item.value);
p.on('submit', items => resolve(selected(items)));
p.on('abort', items => reject(selected(items)));
p.on('state', onState);
});
}

Expand All @@ -144,9 +162,10 @@ function multiselect({ message, choices, max, hint }) {
* @param {Function} [suggest] Function to filter results based on user input. Defaults to stort by `title`
* @param {number} [limit=10] Max number of results to show
* @param {string} [style="default"] Render style ('default', 'password', 'invisible')
* @param {function} [onState] On state change callback
* @returns {Promise} Promise with user input
*/
function autocomplete({ message, choices, suggest, limit, style }) {
function autocomplete({ message, choices, suggest, limit, style, onState = noop }) {
if (typeof message !== 'string') throw new Error('message is required');
if (!Array.isArray(choices)) throw new Error('choices array is required');
const suggestByTitle = (input, choices) =>
Expand All @@ -160,6 +179,7 @@ function autocomplete({ message, choices, suggest, limit, style }) {
const p = new el.AutocompletePrompt({ message, choices, suggest, limit, style });
p.on('submit', resolve);
p.on('abort', reject);
p.on('state', onState);
});
}

Expand Down
37 changes: 31 additions & 6 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ Almost all prompt objects have the following properties:
name: String || Function,
message: String || Function,
initial: String || Function || Async Function
format: Function
format: Function,
onState: Function
}
```

Expand All @@ -269,6 +270,8 @@ The above prompt will be skipped if the value of the previous prompt is less tha

### type

Type: `String|Function`

Defines the type of prompt to display. See the list of [prompt types](#-types) for valid values.

If `type` is a falsy value the prompter will skip that question.
Expand All @@ -282,21 +285,29 @@ If `type` is a falsy value the prompter will skip that question.

### name

Type: `String|Function`

The response will be saved under this key/property in the returned response object.
In case you have multiple prompts with the same name only the latest response will be stored.

> Make sure to give prompts unique names if you don't want to overwrite previous values.
### message

Type: `String|Function`

The message to be displayed to the user.

### initial

Optional default prompt value.
Type: `String|Function`

Optional default prompt value. Async functions are suported too.

### format

Type: `Function`

Receive the user input and return the formatted value to be used inside the program.
The value returned will be added to the response object.

Expand All @@ -313,6 +324,14 @@ The function signature is `(val, values)`, where `val` is the value from the cur
}
```

### onState

Type: `Function`

Callback for when the state of the current prompt changes.
The function signature is `(state)` where `state` is an object with a snapshot of the current state.
The state object have two properties `value` and `aborted`. E.g `{ value: 'This is ', aborted: false }`


![split](https://github.com/terkelg/prompts/raw/master/media/split.png)

Expand Down Expand Up @@ -342,6 +361,7 @@ The function signature is `(val, values)`, where `val` is the value from the cur
| initial | <code>string</code> | <code>''</code> | Default string value |
| style | <code>string</code> | <code>'default'</code> | Render style (`default`, `password`, `invisible`) |
| format | <code>function</code> | | Receive user input. The returned value will be added to the response object |
| onState | <code>function</code> | | On state change callback |


### password(message, [initial])
Expand All @@ -367,6 +387,7 @@ This prompt is a similar to a prompt of type `'text'` with `style` set to `'pass
| message | <code>string</code> | Prompt message to display |
| initial | <code>string</code> | Default string value |
| format | <code>function</code> | Receive user input. The returned value will be added to the response object |
| onState | <code>function</code> | On state change callback |


### invisible(message, [initial])
Expand All @@ -393,6 +414,7 @@ This prompt is a similar to a prompt of type `'text'` with style set to `'invisi
| message | <code>string</code> | Prompt message to display |
| initial | <code>string</code> | Default string value |
| format | <code>function</code> | Receive user input. The returned value will be added to the response object |
| onState | <code>function</code> | On state change callback |


### number(message, initial, [max], [min], [style])
Expand Down Expand Up @@ -424,7 +446,7 @@ You can type in numbers and use <kbd>up</kbd>/<kbd>down</kbd> to increase/decrea
| max | <code>number</code> | `Infinity` | Max value |
| min | <code>number</code> | `-infinity` | Min value |
| style | <code>string</code> | <code>'default'</code> | Render style (`default`, `password`, `invisible`) |

| onState | <code>function</code> | | On state change callback |

### confirm(message, [initial])
> Classic yes/no prompt.
Expand All @@ -450,7 +472,7 @@ Hit <kbd>y</kbd> or <kbd>n</kbd> to confirm/reject.
| message | <code>string</code> | | Prompt message to display |
| initial | <code>boolean</code> | <code>false</code> | Default value |
| format | <code>function</code> | | Receive user input. The returned value will be added to the response object |

| onState | <code>function</code> | | On state change callback |

### list(message, [initial])
> List prompt that return an array.
Expand All @@ -477,6 +499,7 @@ string separated by `separator`.
| initial | <code>boolean</code> | <code>false</code> | Default value |
| format | <code>function</code> | | Receive user input. The returned value will be added to the response object |
| seperator | <code>string</code> | <code>','</code> | String seperator. Will trim all white-spaces from start and end of string |
| onState | <code>function</code> | | On state change callback |


### toggle(message, [initial], [active], [inactive])
Expand Down Expand Up @@ -506,7 +529,7 @@ Use tab or <kbd>arrow keys</kbd>/<kbd>tab</kbd>/<kbd>space</kbd> to switch betwe
| format | <code>function</code> | | Receive user input. The returned value will be added to the response object |
| active | <code>string</code> | <code>'on'</code> | Text for `active` state |
| inactive | <code>string</code> | <code>'off'</code> | Text for `inactive` state |

| onState | <code>function</code> | | On state change callback |

### select(message, choices, [initial])
> Interactive select prompt.
Expand Down Expand Up @@ -537,6 +560,7 @@ Use <kbd>up</kbd>/<kbd>down</kbd> to navigate. Use <kbd>tab</kbd> to cycle the l
| initial | <code>number</code> | Index of default value |
| format | <code>function</code> | Receive user input. The returned value will be added to the response object |
| choices | <code>Array</code> | Array of choices objects `[{ title, value }, ...]` |
| onState | <code>function</code> | On state change callback |


### multiselect(message, choices, [initial], [max], [hint])
Expand Down Expand Up @@ -572,6 +596,7 @@ By default this prompt returns an `array` containing the **values** of the selec
| choices | <code>Array</code> | Array of choices objects `[{ title, value, [selected] }, ...]` |
| max | <code>number</code> | Max select |
| hint | <code>string</code> | Hint to display user |
| onState | <code>function</code> | On state change callback |

This is one of the few prompts that don't take a initial value.
If you want to predefine selected values, give the choice object an `selected` property of `true`.
Expand Down Expand Up @@ -613,7 +638,7 @@ You can overwrite how choices are being filtered by passing your own suggest fun
| suggest | <code>function</code> | By `title` string | Filter function. Defaults to stort by `title` property. `suggest` should always return a promise |
| limit | <code>number</code> | <code>10</code> | Max number of results to show |
| style | <code>string</code> | `'default'` | Render style (`default`, `password`, `invisible`) |

| onState | <code>function</code> | | On state change callback |

Example on what a `suggest` function might look like:
```js
Expand Down

0 comments on commit 5b54227

Please sign in to comment.