-
-
Notifications
You must be signed in to change notification settings - Fork 303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ability to set hotkeys for multiselect #234
base: master
Are you sure you want to change the base?
Changes from all commits
67cd548
ff65bd4
f06026a
f0d6f9b
760b308
626e4c5
58d570f
d08aff4
59f9c27
aaaa5db
94bcfc1
63561e0
3725cab
2cc9ee5
126c66f
a831315
27ed003
eb38dc6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,5 +3,6 @@ script: | |
- npm run build | ||
- npm test | ||
node_js: | ||
- "stable" | ||
- "8" | ||
- "6" | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ class MultiselectPrompt extends Prompt { | |
this.showMinError = false; | ||
this.maxChoices = opts.max; | ||
this.instructions = opts.instructions; | ||
this.hotkeys = opts.hotkeys || {}; | ||
this.optionsPerPage = opts.optionsPerPage || 10; | ||
this.value = opts.choices.map((ch, idx) => { | ||
if (typeof ch === 'string') | ||
|
@@ -150,11 +151,27 @@ class MultiselectPrompt extends Prompt { | |
this.render(); | ||
} | ||
|
||
_(c, key) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This var was unused. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice one! |
||
_(c) { | ||
if (c === ' ') { | ||
this.handleSpaceToggle(); | ||
} else if (c === 'a') { | ||
this.toggleAll(); | ||
} else if (Object.keys(this.hotkeys).includes(c)) { | ||
const hotkeyResponse = this.hotkeys[c].handle() || {}; | ||
if (hotkeyResponse.answers) { | ||
this.value.forEach(value => { | ||
const answerForValue = hotkeyResponse.answers[value.title]; | ||
if (answerForValue !== undefined) { | ||
value.selected = answerForValue; | ||
this.render(); | ||
} | ||
}); | ||
} | ||
|
||
if (['abort', 'submit'].includes(hotkeyResponse.command)) { | ||
this[hotkeyResponse.command](); | ||
} | ||
|
||
} else { | ||
return this.bell(); | ||
} | ||
|
@@ -165,10 +182,17 @@ class MultiselectPrompt extends Prompt { | |
if (typeof this.instructions === 'string') { | ||
return this.instructions; | ||
} | ||
|
||
let hotkeyInstructions = Object.keys(this.hotkeys) | ||
.map(key => [key, this.hotkeys[key]]) | ||
.reduce((instructions, [hotkeyChar, hotkeyDescriptor]) => | ||
instructions += ` ${hotkeyChar}: ${hotkeyDescriptor.instruction}\n`, '') | ||
|
||
return '\nInstructions:\n' | ||
+ ` ${figures.arrowUp}/${figures.arrowDown}: Highlight option\n` | ||
+ ` ${figures.arrowLeft}/${figures.arrowRight}/[space]: Toggle selection\n` | ||
+ (this.maxChoices === undefined ? ` a: Toggle all\n` : '') | ||
+ hotkeyInstructions | ||
+ ` enter/return: Complete answer`; | ||
} | ||
return ''; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
<p align="center"> | ||
<img src="https://github.com/terkelg/prompts/raw/master/prompts.png" alt="Prompts" width="500" /> | ||
<img src="./prompts.png" alt="Prompts" width="500" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice one! How does this work with the readme shown on NPM? @lukeed you spent some time researching this right? I would be nice to have these readme updates in their own PR There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The images still work on the npm registry page. For example, see: https://www.npmjs.com/package/list-maintainers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm happy to split these changes out. I assume you only want the image href updates split out, but the docs for this feature should stay in this PR? |
||
</p> | ||
|
||
<h1 align="center">❯ Prompts</h1> | ||
|
@@ -36,7 +36,7 @@ | |
* **Unified**: consistent experience across all [prompts](#-types). | ||
|
||
|
||
![split](https://github.com/terkelg/prompts/raw/master/media/split.png) | ||
![split](./media/split.png) | ||
|
||
|
||
## ❯ Install | ||
|
@@ -47,11 +47,11 @@ $ npm install --save prompts | |
|
||
> This package supports Node 6 and above | ||
|
||
![split](https://github.com/terkelg/prompts/raw/master/media/split.png) | ||
![split](./media/split.png) | ||
|
||
## ❯ Usage | ||
|
||
<img src="https://github.com/terkelg/prompts/raw/master/media/example.gif" alt="example prompt" width="499" height="103" /> | ||
<img src="./media/example.gif" alt="example prompt" width="499" height="103" /> | ||
|
||
```js | ||
const prompts = require('prompts'); | ||
|
@@ -71,7 +71,7 @@ const prompts = require('prompts'); | |
> See [`example.js`](https://github.com/terkelg/prompts/blob/master/example.js) for more options. | ||
|
||
|
||
![split](https://github.com/terkelg/prompts/raw/master/media/split.png) | ||
![split](./media/split.png) | ||
|
||
|
||
## ❯ Examples | ||
|
@@ -155,7 +155,7 @@ const questions = [ | |
``` | ||
|
||
|
||
![split](https://github.com/terkelg/prompts/raw/master/media/split.png) | ||
![split](./media/split.png) | ||
|
||
|
||
## ❯ API | ||
|
@@ -299,7 +299,7 @@ prompts.inject([ '@terkelg', ['#ff0000', '#0000ff'] ]); | |
})(); | ||
``` | ||
|
||
![split](https://github.com/terkelg/prompts/raw/master/media/split.png) | ||
![split](./media/split.png) | ||
|
||
|
||
## ❯ Prompt Objects | ||
|
@@ -418,7 +418,7 @@ The function signature is `(state)` where `state` is an object with a snapshot o | |
The state object has two properties `value` and `aborted`. E.g `{ value: 'This is ', aborted: false }` | ||
|
||
|
||
![split](https://github.com/terkelg/prompts/raw/master/media/split.png) | ||
![split](./media/split.png) | ||
|
||
|
||
## ❯ Types | ||
|
@@ -444,7 +444,7 @@ The state object has two properties `value` and `aborted`. E.g `{ value: 'This i | |
Hit <kbd>tab</kbd> to autocomplete to `initial` value when provided. | ||
|
||
#### Example | ||
<img src="https://github.com/terkelg/prompts/raw/master/media/text.gif" alt="text prompt" width="499" height="103" /> | ||
<img src="./media/text.gif" alt="text prompt" width="499" height="103" /> | ||
|
||
```js | ||
{ | ||
|
@@ -475,7 +475,7 @@ Hit <kbd>tab</kbd> to autocomplete to `initial` value when provided. | |
This prompt is a similar to a prompt of type `'text'` with `style` set to `'password'`. | ||
|
||
#### Example | ||
<img src="https://github.com/terkelg/prompts/raw/master/media/password.gif" alt="password prompt" width="499" height="103" /> | ||
<img src="./media/password.gif" alt="password prompt" width="499" height="103" /> | ||
|
||
```js | ||
{ | ||
|
@@ -506,7 +506,7 @@ This prompt is working like `sudo` where the input is invisible. | |
This prompt is a similar to a prompt of type `'text'` with style set to `'invisible'`. | ||
|
||
#### Example | ||
<img src="https://github.com/terkelg/prompts/raw/master/media/invisible.gif" alt="invisible prompt" width="499" height="103" /> | ||
<img src="./media/invisible.gif" alt="invisible prompt" width="499" height="103" /> | ||
|
||
```js | ||
{ | ||
|
@@ -536,7 +536,7 @@ This prompt is a similar to a prompt of type `'text'` with style set to `'invisi | |
You can type in numbers and use <kbd>up</kbd>/<kbd>down</kbd> to increase/decrease the value. Only numbers are allowed as input. Hit <kbd>tab</kbd> to autocomplete to `initial` value when provided. | ||
|
||
#### Example | ||
<img src="https://github.com/terkelg/prompts/raw/master/media/number.gif" alt="number prompt" width="499" height="103" /> | ||
<img src="./media/number.gif" alt="number prompt" width="499" height="103" /> | ||
|
||
```js | ||
{ | ||
|
@@ -576,7 +576,7 @@ You can type in numbers and use <kbd>up</kbd>/<kbd>down</kbd> to increase/decrea | |
Hit <kbd>y</kbd> or <kbd>n</kbd> to confirm/reject. | ||
|
||
#### Example | ||
<img src="https://github.com/terkelg/prompts/raw/master/media/confirm.gif" alt="confirm prompt" width="499" height="103" /> | ||
<img src="./media/confirm.gif" alt="confirm prompt" width="499" height="103" /> | ||
|
||
```js | ||
{ | ||
|
@@ -617,7 +617,7 @@ string separated by `separator`. | |
} | ||
``` | ||
|
||
<img src="https://github.com/terkelg/prompts/raw/master/media/list.gif" alt="list prompt" width="499" height="103" /> | ||
<img src="./media/list.gif" alt="list prompt" width="499" height="103" /> | ||
|
||
|
||
| Param | Type | Description | | ||
|
@@ -639,7 +639,7 @@ string separated by `separator`. | |
Use tab or <kbd>arrow keys</kbd>/<kbd>tab</kbd>/<kbd>space</kbd> to switch between options. | ||
|
||
#### Example | ||
<img src="https://github.com/terkelg/prompts/raw/master/media/toggle.gif" alt="toggle prompt" width="499" height="103" /> | ||
<img src="./media/toggle.gif" alt="toggle prompt" width="499" height="103" /> | ||
|
||
```js | ||
{ | ||
|
@@ -673,7 +673,7 @@ Use tab or <kbd>arrow keys</kbd>/<kbd>tab</kbd>/<kbd>space</kbd> to switch betwe | |
Use <kbd>up</kbd>/<kbd>down</kbd> to navigate. Use <kbd>tab</kbd> to cycle the list. | ||
|
||
#### Example | ||
<img src="https://github.com/terkelg/prompts/raw/master/media/select.gif" alt="select prompt" width="499" height="130" /> | ||
<img src="./media/select.gif" alt="select prompt" width="499" height="130" /> | ||
|
||
```js | ||
{ | ||
|
@@ -714,7 +714,7 @@ Use <kbd>space</kbd> to toggle select/unselect and <kbd>up</kbd>/<kbd>down</kbd> | |
By default this prompt returns an `array` containing the **values** of the selected items - not their display title. | ||
|
||
#### Example | ||
<img src="https://github.com/terkelg/prompts/raw/master/media/multiselect.gif" alt="multiselect prompt" width="499" height="130" /> | ||
<img src="./media/multiselect.gif" alt="multiselect prompt" width="499" height="130" /> | ||
|
||
```js | ||
{ | ||
|
@@ -727,6 +727,17 @@ By default this prompt returns an `array` containing the **values** of the selec | |
{ title: 'Blue', value: '#0000ff', selected: true } | ||
], | ||
max: 2, | ||
hotkeys: { | ||
d: { | ||
handle() { | ||
return { | ||
answer: {Red: true, Green: false}, | ||
command: 'submit' | ||
} | ||
}, | ||
instruction: 'Enable red, disable green, and submit the answer.' | ||
} | ||
} | ||
hint: '- Space to select. Return to submit' | ||
} | ||
``` | ||
|
@@ -745,10 +756,35 @@ By default this prompt returns an `array` containing the **values** of the selec | |
| warn | `string` | Message to display when selecting a disabled option | | ||
| onRender | `function` | On render callback. Keyword `this` refers to the current prompt | | ||
| onState | `function` | On state change callback. Function signature is an `object` with two properties: `value` and `aborted` | | ||
| hotkeys | `{[hotkeyCharacter]: hotkeyDescriptor}` | Define hotkeys that can select multiple answers at once. | | ||
|
||
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`. | ||
|
||
The type of `hotkeyDescriptor` is: | ||
|
||
```ts | ||
{ | ||
// You can do whatever you want in this function (e.g. trigger a side effect). You can also customize the choices object. | ||
handle: () => { | ||
|
||
// Pass "submit" to have this question be submitted when the user presses the hotkey. | ||
// Pass "abort" to have the question be aborted (as if the user had hit control+c). | ||
command?: 'submit' | 'abort', | ||
|
||
answers: { | ||
// The key in this object is the `title` field in the `choices` array passed above. | ||
// The value is whether this choice will be selected after the hotkey is pressed. | ||
// Pass true to select the choice. False deselects the choice. Undefined leaves the choice as-is. | ||
[answerTitle]: boolean | ||
} | ||
}, | ||
instruction: string | ||
} | ||
``` | ||
|
||
See the [multiselect test fixture](./test/fixtures/multiselect.js) for more examples of using hotkeys. | ||
|
||
**↑ back to:** [Prompt types](#-types) | ||
|
||
*** | ||
|
@@ -763,7 +799,7 @@ The default suggests function is sorting based on the `title` property of the ch | |
You can overwrite how choices are being filtered by passing your own suggest function. | ||
|
||
#### Example | ||
<img src="https://github.com/terkelg/prompts/raw/master/media/autocomplete.gif" alt="auto complete prompt" width="499" height="163" /> | ||
<img src="./media/autocomplete.gif" alt="auto complete prompt" width="499" height="163" /> | ||
|
||
```js | ||
{ | ||
|
@@ -810,7 +846,7 @@ const suggestByTitle = (input, choices) => | |
Use <kbd>left</kbd>/<kbd>right</kbd>/<kbd>tab</kbd> to navigate. Use <kbd>up</kbd>/<kbd>down</kbd> to change date. | ||
|
||
#### Example | ||
<img src="https://github.com/terkelg/prompts/raw/master/media/date.gif" alt="date prompt" width="499" height="103" /> | ||
<img src="./media/date.gif" alt="date prompt" width="499" height="103" /> | ||
|
||
```js | ||
{ | ||
|
@@ -835,7 +871,7 @@ Use <kbd>left</kbd>/<kbd>right</kbd>/<kbd>tab</kbd> to navigate. Use <kbd>up</kb | |
|
||
Default locales: | ||
|
||
```javascript | ||
```js | ||
{ | ||
months: [ | ||
'January', 'February', 'March', 'April', | ||
|
@@ -857,7 +893,7 @@ Default locales: | |
``` | ||
>**Formatting**: See full list of formatting options in the [wiki](https://github.com/terkelg/prompts/wiki/Date-Time-Formatting) | ||
|
||
![split](https://github.com/terkelg/prompts/raw/master/media/split.png) | ||
![split](./media/split.png) | ||
|
||
**↑ back to:** [Prompt types](#-types) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
const prompts = require('../..'); | ||
|
||
prompts([ | ||
{ | ||
type: 'multiselect', | ||
name: 'color', | ||
message: 'Pick colors', | ||
choices: [ | ||
{ title: 'Red', value: '#ff0000' }, | ||
{ title: 'Green', value: '#00ff00' }, | ||
{ title: 'Blue', value: '#0000ff' } | ||
], | ||
hotkeys: { | ||
r: { | ||
handle() { | ||
return {answers: {Red: true, Green: true}}; | ||
}, | ||
instruction: 'Choose Red and Green' | ||
}, | ||
d: { | ||
handle() { | ||
return {command: 'abort'}; | ||
}, | ||
instruction: 'Abort' | ||
}, | ||
e: { | ||
handle() { | ||
return {answers: {Green: true, Blue: true}, command: 'submit'} | ||
}, | ||
instruction: 'Select Green and Blue, and move on the to the next question' | ||
}, | ||
f: { | ||
handle() { | ||
return {answers: {Red: false, Blue: true}, command: 'submit'} | ||
}, | ||
instruction: 'Pay respect. Also, enable Blue and disable Red.' | ||
} | ||
} | ||
} | ||
]).then(response => console.error(JSON.stringify(response, null, 2))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Node 6 was already unsupported, because
async
/await
was being used.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you revert this please 👍