Comparing version 0.1.6 to 0.1.7
@@ -9,18 +9,29 @@ 'use strict'; | ||
// Get value, with fallback to title | ||
const getVal = (arr, i) => arr[i].value || arr[i].title || arr[i]; | ||
const getVal = (arr, i) => arr[i] && (arr[i].value || arr[i].title || arr[i]); | ||
/** | ||
* A command line prompt with autocompletion | ||
* TextPrompt Base Element | ||
* @param {Object} opts Options | ||
* @param {String} opts.message Message | ||
* @param {Array} opts.choices Array of auto-complete choices objects | ||
* @param {Function} [opts.suggest] Filter function. Defaults to sort by title | ||
* @param {Number} [opts.limit=10] Max number of results to show | ||
* @param {Number} [opts.cursor=0] Cursor start position | ||
* @param {String} [opts.style='default'] Render style | ||
* @param {String} [opts.fallback] Fallback message - initial to default value | ||
* @param {String} [opts.initial] Index of the default value | ||
*/ | ||
class AutocompletePrompt extends Prompt { | ||
constructor({ message, suggest, choices, cursor = 0, limit = 10, style = 'default' }) { | ||
super(); | ||
this.msg = message; | ||
this.suggest = suggest; | ||
this.choices = choices; | ||
constructor(opts={}) { | ||
super(opts); | ||
this.msg = opts.message; | ||
this.suggest = opts.suggest; | ||
this.choices = opts.choices; | ||
this.initial = opts.initial; | ||
this.cursor = opts.initial || opts.cursor || 0; | ||
this.fallback = opts.fallback || opts.initial !== void 0 ? `› ${getVal(this.choices, this.initial)}` : `› no matches found`; | ||
this.suggestions = []; | ||
this.input = ''; | ||
this.limit = limit; | ||
this.cursor = cursor; | ||
this.transform = util.style.render(style); | ||
this.limit = opts.limit || 10; | ||
this.transform = util.style.render(opts.style); | ||
this.render = this.render.bind(this); | ||
@@ -36,3 +47,3 @@ this.complete = this.complete.bind(this); | ||
if (this.suggestions.length > 0) this.value = getVal(this.suggestions, i); | ||
else this.value = null; | ||
else this.value = this.initial !== void 0 ? getVal(this.choices, this.initial) : null; | ||
this.fire(); | ||
@@ -59,3 +70,3 @@ } | ||
this.complete(() => { | ||
this.moveCursor(0); | ||
this.moveCursor(this.initial !== void 0 ? this.initial : 0); | ||
this.render(); | ||
@@ -136,6 +147,8 @@ }); | ||
if (!this.done) { | ||
for (let i = 0; i < this.suggestions.length; i++) { | ||
const s = this.suggestions[i]; | ||
prompt += '\n' + (i === this.cursor ? color.cyan(s.title) : s.title); | ||
} | ||
let suggestions = this.suggestions.map((item, i) => | ||
`\n${i === this.cursor ? color.cyan(item.title) : item.title}`); | ||
prompt += suggestions.length ? | ||
suggestions.reduce((acc, line) => acc+line, '') : | ||
`\n${color.gray(this.fallback)}`; | ||
} | ||
@@ -142,0 +155,0 @@ |
@@ -7,10 +7,13 @@ const color = require('clorox'); | ||
/** | ||
* A CLI classic confirm prompt | ||
* ConfirmPrompt Base Element | ||
* @param {Object} opts Options | ||
* @param {String} opts.message Message | ||
* @param {Boolean} [opts.initial] Default value (true/false) | ||
*/ | ||
class ConfirmPrompt extends Prompt { | ||
constructor({ message, initial = false }) { | ||
super(); | ||
this.msg = message; | ||
this.value = initial; | ||
this.initialValue = initial; | ||
constructor(opts={}) { | ||
super(opts); | ||
this.msg = opts.message; | ||
this.value = opts.initial; | ||
this.initialValue = !!opts.initial; | ||
this.render(true); | ||
@@ -34,2 +37,3 @@ } | ||
submit() { | ||
this.value = this.value || false; | ||
this.done = true; | ||
@@ -36,0 +40,0 @@ this.aborted = false; |
@@ -9,15 +9,18 @@ 'use strict'; | ||
/** | ||
* A prompt to select zero or more items. | ||
* MultiselectPrompt Base Element | ||
* @param {Object} opts Options | ||
* @param {String} opts.message Message | ||
* @param {Array} opts.choices Array of choice objects | ||
* @param {String} [opts.hint] Hint to display | ||
* @param {Number} [opts.cursor=0] Cursor start position | ||
* @param {Number} [opts.max] Max choices | ||
*/ | ||
class MultiselectPrompt extends Prompt { | ||
constructor({ message, choices, max, hint, cursor = 0 }) { | ||
super(); | ||
this.msg = message; | ||
this.hint = hint; | ||
this.cursor = cursor; | ||
this.hint = hint || '- Space to select. Return to submit'; | ||
this.maxChoices = max; | ||
this.value = choices.map(v => { | ||
return Object.assign({ title: v.value, selected: false }, v); | ||
}); | ||
constructor(opts={}) { | ||
super(opts); | ||
this.msg = opts.message; | ||
this.cursor = opts.cursor || 0; | ||
this.hint = opts.hint || '- Space to select. Return to submit'; | ||
this.maxChoices = opts.max; | ||
this.value = opts.choices.map(v => Object.assign({ title: v.value, selected: false }, v)); | ||
this.clear = clear(''); | ||
@@ -24,0 +27,0 @@ this.render(true); |
const color = require('clorox'); | ||
const Prompt = require('./prompt'); | ||
const { cursor, erase } = require('sisteransi'); | ||
const { style: stl, clear } = require('../util'); | ||
const { style, clear } = require('../util'); | ||
const isNumber = /[0-9]/; | ||
const isValidChar = /\.|-/; | ||
const isDef = any => any !== undefined; | ||
const isNull = any => any === null; | ||
const round = (number, precision) => { | ||
let factor = Math.pow(10, precision); | ||
return Math.round(number * factor) / factor; | ||
} | ||
/** | ||
* NumberPrompt Base Element | ||
* @param {Object} opts Options | ||
* @param {String} opts.message Message | ||
* @param {String} [opts.style='default'] Render style | ||
* @param {Number} [opts.initial] Default value | ||
* @param {Number} [opts.max=+Infinity] Max value | ||
* @param {Number} [opts.min=-Infinity] Min value | ||
* @param {Boolean} [opts.float=false] Parse input as floats | ||
* @param {Number} [opts.round=2] Round floats to x decimals | ||
* @param {Number} [opts.increment=1] Number to increment by when using arrow-keys | ||
*/ | ||
class NumberPrompt extends Prompt { | ||
constructor({ message, initial = '', min, max, style = 'default' }) { | ||
super(); | ||
this.msg = message; | ||
this.transform = stl.render(style); | ||
this.min = isDef(min) ? min : -Infinity; | ||
this.max = isDef(max) ? max : Infinity; | ||
this.value = initial; | ||
constructor(opts={}) { | ||
super(opts); | ||
this.transform = style.render(opts.style); | ||
this.msg = opts.message; | ||
this.initial = isDef(opts.initial) ? opts.initial : ''; | ||
this.float = !!opts.float; | ||
this.round = opts.round || 2; | ||
this.inc = opts.increment || 1; | ||
this.min = isDef(opts.min) ? opts.min : -Infinity; | ||
this.max = isDef(opts.max) ? opts.max : Infinity; | ||
this.value = '' | ||
this.typed = ''; | ||
this.lastHit = 0; | ||
this.render(); | ||
} | ||
this.initialValue = this.value; | ||
set value(v) { | ||
if (!v && v !== 0) { | ||
this.placeholder = true; | ||
this.rendered = color.gray(this.transform.render(`${this.initial}`)); | ||
this._value = ''; | ||
} else { | ||
this.placeholder = false; | ||
this.rendered = this.transform.render(`${round(v, this.round)}`); | ||
this._value = round(v, this.round); | ||
} | ||
this.fire(); | ||
} | ||
this.render(); | ||
get value() { | ||
return this._value; | ||
} | ||
parse(x) { | ||
return this.float ? parseFloat(x) : parseInt(x); | ||
} | ||
valid(c) { | ||
return c === '-' || c === '.' && this.float || isNumber.test(c) | ||
} | ||
reset() { | ||
this.typed = ''; | ||
this.value = this.initialValue; | ||
this.value = ''; | ||
this.fire(); | ||
@@ -37,2 +76,3 @@ this.render(); | ||
abort() { | ||
this.value = this.value || this.initial; | ||
this.done = this.aborted = true; | ||
@@ -46,2 +86,3 @@ this.fire(); | ||
submit() { | ||
this.value = this.value || this.initial; | ||
this.done = true; | ||
@@ -58,3 +99,3 @@ this.aborted = false; | ||
if (this.value >= this.max) return this.bell(); | ||
this.value++; | ||
this.value += this.inc; | ||
this.fire(); | ||
@@ -67,3 +108,3 @@ this.render(); | ||
if (this.value <= this.min) return this.bell(); | ||
this.value--; | ||
this.value -= this.inc; | ||
this.fire(); | ||
@@ -76,3 +117,3 @@ this.render(); | ||
if (val.length === 0) return this.bell(); | ||
this.value = parseInt((val = val.slice(0, -1))) || ''; | ||
this.value = this.parse((val = val.slice(0, -1))) || ''; | ||
this.fire(); | ||
@@ -82,4 +123,10 @@ this.render(); | ||
next() { | ||
this.value = this.initial; | ||
this.fire(); | ||
this.render(); | ||
} | ||
_(c, key) { | ||
if (!isNumber.test(c)) return this.bell(); | ||
if (!this.valid(c)) return this.bell(); | ||
@@ -91,3 +138,5 @@ const now = Date.now(); | ||
this.value = Math.min(parseInt(this.typed), this.max); | ||
if (c === '.') return this.fire(); | ||
this.value = Math.min(this.parse(this.typed), this.max); | ||
if (this.value > this.max) this.value = this.max; | ||
@@ -100,5 +149,3 @@ if (this.value < this.min) this.value = this.min; | ||
render() { | ||
let value = this.transform.render(this.value !== null ? this.value : ''); | ||
if (!this.done) value = color.cyan.underline(value); | ||
let underline = !this.done || (!this.done && !this.placeholder); | ||
this.out.write( | ||
@@ -108,6 +155,6 @@ erase.line + | ||
[ | ||
stl.symbol(this.done, this.aborted), | ||
style.symbol(this.done, this.aborted), | ||
color.bold(this.msg), | ||
stl.delimiter(this.done), | ||
value | ||
style.delimiter(this.done), | ||
underline ? color.cyan.underline(this.rendered) : this.rendered | ||
].join(' ') | ||
@@ -114,0 +161,0 @@ ); |
@@ -12,14 +12,12 @@ 'use strict'; | ||
class Prompt extends EventEmitter { | ||
constructor() { | ||
constructor(opts={}) { | ||
super(); | ||
this.in = process.stdin; | ||
this.out = process.stdout; | ||
this.in = opts.in || process.stdin; | ||
this.out = opts.out || process.stdout; | ||
const rl = readline.createInterface(this.in); | ||
readline.emitKeypressEvents(this.in, this.rl); | ||
readline.emitKeypressEvents(this.in, rl); | ||
if (this.in.isTTY) { | ||
this.in.setRawMode(true); | ||
} | ||
if (this.in.isTTY) this.in.setRawMode(true); | ||
@@ -26,0 +24,0 @@ const keypress = (str, key) => { |
@@ -9,14 +9,17 @@ 'use strict'; | ||
/** | ||
* A prompt to an item from a list. | ||
* SelectPrompt Base Element | ||
* @param {Object} opts Options | ||
* @param {String} opts.message Message | ||
* @param {Array} opts.choices Array of choice objects | ||
* @param {String} [opts.hint] Hint to display | ||
* @param {Number} [opts.initial] Index of default value | ||
*/ | ||
class SelectPrompt extends Prompt { | ||
constructor({ message, choices = [], cursor = 0, initial = 0 }) { | ||
super(); | ||
this.msg = message; | ||
this.cursor = initial || cursor; | ||
this.values = choices; | ||
this.value = choices[initial].value; | ||
constructor(opts={}) { | ||
super(opts); | ||
this.msg = opts.message; | ||
this.hint = opts.hint || '- Use arrow-keys. Return to submit.'; | ||
this.cursor = opts.initial || 0; | ||
this.values = opts.choices || []; | ||
this.value = opts.choices[this.cursor].value; | ||
this.clear = clear(''); | ||
@@ -96,3 +99,3 @@ this.render(true); | ||
style.delimiter(false), | ||
this.done ? this.values[this.cursor].title : '' | ||
this.done ? this.values[this.cursor].title : color.gray(this.hint) | ||
].join(' ') | ||
@@ -99,0 +102,0 @@ ); |
const color = require('clorox'); | ||
const Prompt = require('./prompt'); | ||
const { cursor } = require('sisteransi'); | ||
const { style: stl, clear } = require('../util'); | ||
const { style, clear } = require('../util'); | ||
/** | ||
* TextPrompt Base Element | ||
* @param {Object} opts Options | ||
* @param {String} opts.message Message | ||
* @param {String} [opts.style='default'] Render style | ||
* @param {String} [opts.initial] Default value | ||
*/ | ||
class TextPrompt extends Prompt { | ||
constructor({ message, cursor, initial, style = 'default' }) { | ||
super(); | ||
this.msg = message; | ||
this.value = initial || ''; | ||
this.initialValue = this.value; | ||
this.transform = stl.render(style); | ||
constructor(opts={}) { | ||
super(opts); | ||
this.transform = style.render(opts.style); | ||
this.scale = this.transform.scale; | ||
this.rendered = this.transform.render(this.value); | ||
this.cursor = cursor*this.scale || this.rendered.length; | ||
this.msg = opts.message; | ||
this.initial = opts.initial || ''; | ||
this.value = ''; | ||
this.cursor = this.rendered.length; | ||
this.clear = clear(''); | ||
this.render(true); | ||
this.render(); | ||
} | ||
setValue(v) { | ||
this.value = v; | ||
this.rendered = this.transform.render(v); | ||
set value(v) { | ||
if (!v && this.initial) { | ||
this.placeholder = true; | ||
this.rendered = color.gray(this.transform.render(this.initial)); | ||
} else { | ||
this.placeholder = false; | ||
this.rendered = this.transform.render(v); | ||
} | ||
this._value = v; | ||
this.fire(); | ||
} | ||
get value() { | ||
return this._value; | ||
} | ||
reset() { | ||
this.setValue(this.initialValue); | ||
this.value = ''; | ||
this.fire(); | ||
@@ -37,2 +49,3 @@ this.render(); | ||
abort() { | ||
this.value = this.value || this.initial; | ||
this.done = this.aborted = true; | ||
@@ -46,2 +59,3 @@ this.fire(); | ||
submit() { | ||
this.value = this.value || this.initial; | ||
this.done = true; | ||
@@ -55,7 +69,21 @@ this.aborted = false; | ||
next() { | ||
if (!this.placeholder) return this.bell(); | ||
this.value = this.initial; | ||
this.cursor = this.rendered.length; | ||
this.fire(); | ||
this.render(); | ||
} | ||
moveCursor(n) { | ||
if (this.placeholder) return; | ||
this.cursor = this.cursor+n; | ||
} | ||
_(c, key) { | ||
let s1 = this.value.slice(0, this.cursor); | ||
let s2 = this.value.slice(this.cursor); | ||
this.cursor++; | ||
this.setValue(`${s1}${c}${s2}`); | ||
this.moveCursor(1); | ||
this.value = `${s1}${c}${s2}`; | ||
if (this.placeholder) this.cursor = 0; | ||
this.render(); | ||
@@ -66,6 +94,6 @@ } | ||
if (this.value.length === 0) return this.bell(); | ||
this.cursor--; | ||
let s1 = this.value.slice(0, this.cursor); | ||
let s2 = this.value.slice(this.cursor+1); | ||
this.setValue(`${s1}${s2}`); | ||
let s1 = this.value.slice(0, this.cursor-1); | ||
let s2 = this.value.slice(this.cursor); | ||
this.value = `${s1}${s2}`; | ||
this.moveCursor(-1); | ||
this.render(); | ||
@@ -80,3 +108,3 @@ } | ||
last() { | ||
this.cursor = this.rendered.length; | ||
this.cursor = this.value.length; | ||
this.render(); | ||
@@ -86,4 +114,4 @@ } | ||
left() { | ||
if (this.cursor <= 0) return this.bell(); | ||
this.cursor--; | ||
if (this.cursor <= 0 || this.placeholder) return this.bell(); | ||
this.moveCursor(-1); | ||
this.render(); | ||
@@ -93,4 +121,4 @@ } | ||
right() { | ||
if (this.cursor*this.scale >= this.rendered.length) return this.bell(); | ||
this.cursor++; | ||
if (this.cursor*this.scale >= this.rendered.length || this.placeholder) return this.bell(); | ||
this.moveCursor(1); | ||
this.render(); | ||
@@ -101,5 +129,5 @@ } | ||
const prompt = [ | ||
stl.symbol(this.done, this.aborted), | ||
style.symbol(this.done, this.aborted), | ||
color.bold(this.msg), | ||
stl.delimiter(this.done), | ||
style.delimiter(this.done), | ||
this.rendered | ||
@@ -109,3 +137,6 @@ ].join(' '); | ||
this.out.write(this.clear + prompt); | ||
this.out.write(cursor.move(-this.rendered.length + this.cursor*this.scale)); | ||
this.out.write(cursor.move(this.placeholder ? | ||
-this.initial.length*this.scale : | ||
-this.rendered.length + this.cursor*this.scale | ||
)); | ||
@@ -112,0 +143,0 @@ this.clear = clear(prompt); |
@@ -7,14 +7,17 @@ const color = require('clorox'); | ||
/** | ||
* A CLI prompt with an on/of switch | ||
* TogglePrompt Base Element | ||
* @param {Object} opts Options | ||
* @param {String} opts.message Message | ||
* @param {Boolean} [opts.initial=false] Default value | ||
* @param {String} [opts.active='no'] Active label | ||
* @param {String} [opts.inactive='off'] Inactive label | ||
*/ | ||
class TogglePrompt extends Prompt { | ||
constructor({ message, initial = false, active = 'on', inactive = 'off' }) { | ||
super(); | ||
this.value = !!initial; | ||
this.msg = message; | ||
this.active = active; | ||
this.inactive = inactive; | ||
constructor(opts={}) { | ||
super(opts); | ||
this.msg = opts.message; | ||
this.value = !!opts.initial; | ||
this.active = opts.active; | ||
this.inactive = opts.inactive; | ||
this.initialValue = this.value; | ||
this.render(true); | ||
@@ -21,0 +24,0 @@ } |
@@ -42,3 +42,2 @@ 'use strict'; | ||
// skip if type is a falsy value | ||
@@ -45,0 +44,0 @@ if (!type) continue; |
@@ -56,6 +56,9 @@ 'use strict'; | ||
* @param {number} args.initial Default number value | ||
* @param {function} [args.onState] On state change callback | ||
* @param {number} [args.max] Max value | ||
* @param {number} [args.min] Min value | ||
* @param {string} [args.style="default"] Render style ('default', 'password', 'invisible') | ||
* @param {function} [args.onState] On state change callback | ||
* @param {Boolean} [opts.float=false] Parse input as floats | ||
* @param {Number} [opts.round=2] Round floats to x decimals | ||
* @param {Number} [opts.increment=1] Number to increment by when using arrow-keys | ||
* @returns {Promise} Promise with user input | ||
@@ -103,6 +106,7 @@ */ | ||
* Interactive select prompt | ||
* @param {string} arr.message Prompt message to display | ||
* @param {Array} arr.choices Array of choices objects `[{ title, value }, ...]` | ||
* @param {number} [arr.initial] Index of default value | ||
* @param {function} [arr.onState] On state change callback | ||
* @param {string} args.message Prompt message to display | ||
* @param {Array} args.choices Array of choices objects `[{ title, value }, ...]` | ||
* @param {number} [args.initial] Index of default value | ||
* @param {String} [args.hint] Hint to display | ||
* @param {function} [args.onState] On state change callback | ||
* @returns {Promise} Promise with user input | ||
@@ -118,2 +122,3 @@ */ | ||
* @param {string} [args.hint] Hint to display user | ||
* @param {Number} [args.cursor=0] Cursor start position | ||
* @param {function} [args.onState] On state change callback | ||
@@ -136,3 +141,3 @@ * @returns {Promise} Promise with user input | ||
/** | ||
* Interactive multi-select prompt | ||
* Interactive auto-complete prompt | ||
* @param {string} args.message Prompt message to display | ||
@@ -143,2 +148,4 @@ * @param {Array} args.choices Array of auto-complete choices objects `[{ title, value }, ...]` | ||
* @param {string} [args.style="default"] Render style ('default', 'password', 'invisible') | ||
* @param {String} [args.initial] Index of the default value | ||
* @param {String} [args.fallback] Fallback message - defaults to initial value | ||
* @param {function} [args.onState] On state change callback | ||
@@ -145,0 +152,0 @@ * @returns {Promise} Promise with user input |
{ | ||
"name": "prompts", | ||
"version": "0.1.6", | ||
"version": "0.1.7", | ||
"description": "Lightweight, beautiful and user-friendly prompts", | ||
@@ -5,0 +5,0 @@ "homepage": "https://github.com/terkelg/prompts", |
@@ -431,3 +431,3 @@ <p align="center"> | ||
{ | ||
type: 'number' | ||
type: 'number', | ||
name: 'value', | ||
@@ -463,3 +463,3 @@ message: 'How old are you?', | ||
{ | ||
type: 'confirm' | ||
type: 'confirm', | ||
name: 'value', | ||
@@ -488,3 +488,3 @@ message: 'Can you confirm?', | ||
{ | ||
type: 'list' | ||
type: 'list', | ||
name: 'value', | ||
@@ -491,0 +491,0 @@ message: 'Enter keywords', |
67220
1139