Comparing version 2.1.1 to 2.2.0
@@ -107,6 +107,6 @@ 'use strict'; | ||
ansi.clear = (message = '', columns = process.stdout.columns) => { | ||
ansi.clear = (input = '', columns = process.stdout.columns) => { | ||
if (!columns) return erase.line + cursor.to(0); | ||
let width = str => [...colors.unstyle(str)].length; | ||
let lines = message.split(/\r?\n/); | ||
let lines = input.split(/\r?\n/); | ||
let rows = 0; | ||
@@ -113,0 +113,0 @@ for (let line of lines) { |
'use strict'; | ||
const readline = require('readline'); | ||
const actions = require('./actions'); | ||
const combos = require('./combos'); | ||
@@ -197,3 +197,3 @@ /* eslint-disable no-control-regex */ | ||
let on = (buf, key) => onKeypress(String(buf), keypress(buf, key), rl); | ||
let on = (buf, key) => onKeypress(buf, keypress(buf, key), rl); | ||
let isRaw = stream.isRaw; | ||
@@ -216,3 +216,3 @@ | ||
keypress.action = (buf, key, customActions) => { | ||
let obj = { ...actions, ...customActions }; | ||
let obj = { ...combos, ...customActions }; | ||
if (key.ctrl) { | ||
@@ -219,0 +219,0 @@ key.action = obj.ctrl[key.name]; |
'use strict'; | ||
const colors = require('ansi-colors'); | ||
const utils = require('./utils'); | ||
@@ -25,7 +24,18 @@ | ||
let inverse = utils.inverse(prompt.styles.primary); | ||
let reverse = str => inverse(colors.black(str)); | ||
let blinker = str => inverse(prompt.styles.black(str)); | ||
let output = input; | ||
let char = ' '; | ||
let reverse = blinker(char); | ||
if (prompt.blink && prompt.blink.off === true) { | ||
blinker = str => str; | ||
reverse = ''; | ||
} | ||
if (showCursor && pos === 0 && initial === '' && input === '') { | ||
return blinker(char); | ||
} | ||
if (showCursor && pos === 0 && (input === initial || input === '')) { | ||
return reverse(initial[0]) + style(initial.slice(1)); | ||
return blinker(initial[0]) + style(initial.slice(1)); | ||
} | ||
@@ -37,6 +47,6 @@ | ||
let placeholder = initial && initial.startsWith(input) && initial !== input; | ||
let cursor = placeholder ? reverse(initial[input.length]) : inverse(' '); | ||
let cursor = placeholder ? blinker(initial[input.length]) : reverse; | ||
if (pos !== input.length && showCursor === true) { | ||
output = input.slice(0, pos) + reverse(input[pos]) + input.slice(pos + 1); | ||
output = input.slice(0, pos) + blinker(input[pos]) + input.slice(pos + 1); | ||
cursor = ''; | ||
@@ -50,3 +60,3 @@ } | ||
if (placeholder) { | ||
let raw = colors.unstyle(output + cursor); | ||
let raw = prompt.styles.unstyle(output + cursor); | ||
return output + cursor + style(initial.slice(raw.length)); | ||
@@ -53,0 +63,0 @@ } |
@@ -35,2 +35,3 @@ 'use strict'; | ||
this.term = this.options.term || process.env.TERM_PROGRAM; | ||
this.margin = margin(this.options.margin); | ||
this.setMaxListeners(0); | ||
@@ -53,2 +54,3 @@ } | ||
alert() { | ||
delete this.state.alert; | ||
if (this.options.show === false) { | ||
@@ -174,4 +176,12 @@ this.emit('alert'); | ||
async skip() { | ||
this.skipped = false; | ||
if (typeof this.options.skip === 'function') { | ||
this.skipped = await this.options.skip.call(this, this.name, this.value); | ||
} | ||
return this.skipped; | ||
} | ||
async initialize() { | ||
let { format, result } = this; | ||
let { format, options, result } = this; | ||
@@ -181,5 +191,10 @@ this.format = () => format.call(this, this.value); | ||
if (typeof this.options.initial === 'function') { | ||
this.initial = await this.options.initial.call(this); | ||
if (typeof options.initial === 'function') { | ||
this.initial = await options.initial.call(this, this); | ||
} | ||
if (typeof options.onRun === 'function') { | ||
await options.onRun.call(this, this); | ||
} | ||
await this.start(); | ||
@@ -197,4 +212,7 @@ await this.render(); | ||
this.once('cancel', reject); | ||
if (await this.skip()) { | ||
this.render = () => {}; | ||
return this.submit(); | ||
} | ||
await this.initialize(); | ||
if (this.options.onRun) await this.options.onRun.call(this); | ||
this.emit('run'); | ||
@@ -373,3 +391,5 @@ }); | ||
get value() { | ||
return [this.state.value, this.input].find(this.isValue.bind(this)) || this.initial; | ||
let { input, value } = this.state; | ||
let result = [value, input].find(this.isValue.bind(this)); | ||
return this.isValue(result) ? result : this.initial; | ||
} | ||
@@ -382,2 +402,20 @@ | ||
function margin(value) { | ||
if (typeof value === 'number') { | ||
value = [value, value, value, value]; | ||
} | ||
let arr = [].concat(value || []); | ||
let pad = i => i % 2 === 0 ? '\n' : ' '; | ||
let res = []; | ||
for (let i = 0; i < 4; i++) { | ||
let char = pad(i); | ||
if (arr[i]) { | ||
res.push(char.repeat(arr[i])); | ||
} else { | ||
res.push(''); | ||
} | ||
} | ||
return res; | ||
} | ||
module.exports = Prompt; |
@@ -26,1 +26,2 @@ 'use strict'; | ||
define('Text', () => require('./text')); | ||
define('Toggle', () => require('./toggle')); |
@@ -15,6 +15,6 @@ 'use strict'; | ||
this.newline = options.newline || '\n '; | ||
this.margin = toSpaces([options.margin, ' '].find(v => v != null)); | ||
let start = options.startNumber || 1; | ||
if (typeof this.scale === 'number') { | ||
this.scaleKey = false; | ||
this.scale = Array(this.scale).fill(0).map((v, i) => ({ name: i })); | ||
this.scale = Array(this.scale).fill(0).map((v, i) => ({ name: i + start })); | ||
} | ||
@@ -110,6 +110,6 @@ } | ||
renderScaleHeading(max) { | ||
let keys = this.scale.map(ele => ele.name); | ||
if (typeof this.options.renderScaleHeading === 'function') { | ||
return this.options.renderScaleHeading.call(this, max); | ||
keys = this.options.renderScaleHeading.call(this, max); | ||
} | ||
let keys = this.scale.map(ele => ele.name); | ||
let diff = this.scaleLength - keys.join('').length; | ||
@@ -120,3 +120,3 @@ let spacing = Math.round(diff / (keys.length - 1)); | ||
let padding = ' '.repeat(this.widths[0]); | ||
return padding + this.margin + headings; | ||
return this.margin[3] + padding + this.margin[1] + headings; | ||
} | ||
@@ -164,3 +164,3 @@ | ||
let pad = str => str.replace(/\s+$/, '').padEnd(this.widths[0], ' '); | ||
let pad = str => this.margin[3] + str.replace(/\s+$/, '').padEnd(this.widths[0], ' '); | ||
let newline = this.newline; | ||
@@ -170,6 +170,8 @@ let ind = this.indent(choice); | ||
let scale = await this.renderScale(choice, i); | ||
let margin = this.margin[1] + this.margin[3]; | ||
this.scaleLength = colors.unstyle(scale).length; | ||
this.widths[0] = Math.min(this.widths[0], this.width - this.scaleLength - this.margin.length); | ||
this.widths[0] = Math.min(this.widths[0], this.width - this.scaleLength - margin.length); | ||
let msg = utils.wordWrap(message, { width: this.widths[0], newline }); | ||
let lines = msg.split('\n').map(line => pad(line) + this.margin); | ||
let lines = msg.split('\n').map(line => pad(line) + this.margin[1]); | ||
if (focused) { | ||
@@ -181,2 +183,3 @@ scale = this.styles.info(scale); | ||
lines[0] += scale; | ||
if (this.linebreak) lines.push(''); | ||
@@ -192,3 +195,3 @@ return [ind + pointer, lines.join('\n')].filter(Boolean); | ||
let heading = await this.renderScaleHeading(); | ||
return [heading, ...visible.map(v => v.join(' '))].join('\n'); | ||
return this.margin[0] + [heading, ...visible.map(v => v.join(' '))].join('\n'); | ||
} | ||
@@ -226,2 +229,5 @@ | ||
this.write([header, prompt, key, body, footer].filter(Boolean).join('\n')); | ||
if (!this.state.submitted) { | ||
this.write(this.margin[2]); | ||
} | ||
this.restore(); | ||
@@ -239,12 +245,2 @@ } | ||
function toSpaces(value) { | ||
if (typeof value === 'number') { | ||
return ' '.repeat(value); | ||
} | ||
if (typeof value === 'string') { | ||
return value; | ||
} | ||
return ''; | ||
} | ||
module.exports = LikertScale; |
@@ -33,13 +33,10 @@ 'use strict'; | ||
heading(message, choice, i) { | ||
if (typeof this.options.heading === 'function') { | ||
return this.options.heading.call(this, message, choice, i); | ||
choiceMessage(choice, i) { | ||
let message = this.resolve(choice.message, this.state, choice, i); | ||
if (choice.role === 'heading' && !utils.hasColor(message)) { | ||
message = this.styles.strong(message); | ||
} | ||
return this.styles.strong(message); | ||
return this.resolve(message, this.state, choice, i); | ||
} | ||
choiceMessage(choice, i) { | ||
return this.resolve(choice.message, this.state, choice, i); | ||
} | ||
choiceSeparator() { | ||
@@ -63,6 +60,5 @@ return ':'; | ||
let msg = await this.choiceMessage(choice, i); | ||
let line = () => [ind + pointer + check, msg, hint].filter(Boolean).join(' '); | ||
let line = () => [this.margin[3], ind + pointer + check, msg, this.margin[1], hint].filter(Boolean).join(' '); | ||
if (choice.role === 'heading') { | ||
msg = this.heading(msg, choice, i); | ||
return line(); | ||
@@ -77,3 +73,3 @@ } | ||
if (focused) { | ||
msg = this.styles.heading(msg); | ||
msg = this.styles.em(msg); | ||
} | ||
@@ -85,2 +81,5 @@ | ||
async renderChoices() { | ||
if (this.state.loading === 'choices') { | ||
return this.styles.warning('Loading choices'); | ||
} | ||
if (this.state.submitted) return ''; | ||
@@ -90,7 +89,14 @@ let choices = this.visible.map(async(ch, i) => await this.renderChoice(ch, i)); | ||
if (!visible.length) visible.push(this.styles.danger('No matching choices')); | ||
return '\n' + visible.join('\n'); | ||
let result = this.margin[0] + visible.join('\n'); | ||
let header; | ||
if (this.options.choicesHeader) { | ||
header = await this.resolve(this.options.choicesHeader, this.state); | ||
} | ||
return [header, result].filter(Boolean).join('\n'); | ||
} | ||
format() { | ||
if (!this.state.submitted) return this.styles.muted(this.state.hint); | ||
if (!this.state.submitted) return ''; | ||
if (Array.isArray(this.selected)) { | ||
@@ -105,2 +111,4 @@ return this.selected.map(choice => this.styles.primary(choice.name)).join(', '); | ||
let prompt = ''; | ||
let header = await this.header(); | ||
let prefix = await this.prefix(); | ||
@@ -110,3 +118,2 @@ let separator = await this.separator(); | ||
let prompt = ''; | ||
if (this.options.promptLine !== false) { | ||
@@ -117,5 +124,4 @@ prompt = [prefix, message, separator, ''].join(' '); | ||
let header = await this.header(); | ||
let output = await this.format(); | ||
let help = await this.error() || await this.hint(); | ||
let help = (await this.error()) || (await this.hint()); | ||
let body = await this.renderChoices(); | ||
@@ -132,3 +138,4 @@ let footer = await this.footer(); | ||
this.clear(size); | ||
this.write([header, prompt + body, footer].filter(Boolean).join('\n')); | ||
this.write([header, prompt, body, footer].filter(Boolean).join('\n')); | ||
this.write(this.margin[2]); | ||
this.restore(); | ||
@@ -135,0 +142,0 @@ } |
@@ -18,3 +18,4 @@ 'use strict'; | ||
let str = await super.renderChoice(choice, i); | ||
let pre = (this.index === i && this.sorting) ? this.styles.muted('≡ ') : ' '; | ||
let sym = this.symbols.identicalTo + ' '; | ||
let pre = (this.index === i && this.sorting) ? this.styles.muted(sym) : ' '; | ||
if (this.options.drag === false) pre = ''; | ||
@@ -21,0 +22,0 @@ if (this.options.numbered === true) { |
'use strict'; | ||
const utils = require('./utils'); | ||
const roles = { | ||
@@ -9,5 +8,26 @@ default(prompt, choice) { | ||
}, | ||
checkbox(prompt, choice) { | ||
throw new Error('checkbox role is not implemented yet'); | ||
}, | ||
editable(prompt, choice) { | ||
throw new Error('editable role is not implemented yet'); | ||
}, | ||
expandable(prompt, choice) { | ||
throw new Error('expandable role is not implemented yet'); | ||
}, | ||
heading(prompt, choice) { | ||
choice.disabled = ''; | ||
choice.indicator = [choice.indicator, ' '].find(v => v != null); | ||
choice.message = choice.message || ''; | ||
return choice; | ||
}, | ||
input(prompt, choice) { | ||
throw new Error('input role is not implemented yet'); | ||
}, | ||
option(prompt, choice) { | ||
return roles.default(prompt, choice); | ||
}, | ||
radio(prompt, choice) { | ||
throw new Error('radio role is not implemented yet'); | ||
}, | ||
separator(prompt, choice) { | ||
@@ -19,5 +39,2 @@ choice.disabled = ''; | ||
}, | ||
heading(prompt, choice) { | ||
return choice; | ||
}, | ||
spacer(prompt, choice) { | ||
@@ -29,4 +46,4 @@ return choice; | ||
module.exports = (name, options = {}) => { | ||
let obj = utils.merge({}, roles, options.roles); | ||
return obj[name] || obj.option; | ||
let role = utils.merge({}, roles, options.roles); | ||
return role[name] || role.default; | ||
}; |
@@ -51,2 +51,11 @@ 'use strict'; | ||
set loading(value) { | ||
this._loading = value; | ||
} | ||
get loading() { | ||
if (typeof this._loading === 'boolean') return this._loading; | ||
if (this.loadingChoices) return 'choices'; | ||
return false; | ||
} | ||
get status() { | ||
@@ -53,0 +62,0 @@ if (this.cancelled) return 'cancelled'; |
@@ -5,2 +5,3 @@ 'use strict'; | ||
const colors = require('ansi-colors'); | ||
const styles = { | ||
@@ -14,9 +15,14 @@ default: colors.noop, | ||
complementary: utils.complement, | ||
opposite: utils.inverse, | ||
set inverse(custom) { | ||
this._inverse = custom; | ||
}, | ||
get inverse() { | ||
return this.opposite(this.primary); | ||
return this._inverse || utils.inverse(this.primary); | ||
}, | ||
set complement(custom) { | ||
this._complement = custom; | ||
}, | ||
get complement() { | ||
return this.complementary(this.primary); | ||
return this._complement || utils.complement(this.primary); | ||
}, | ||
@@ -41,8 +47,17 @@ | ||
dark: colors.dim.gray, | ||
underline: colors.underline, | ||
set info(custom) { | ||
this._info = custom; | ||
}, | ||
get info() { | ||
return this.primary; | ||
return this._info || this.primary; | ||
}, | ||
get heading() { | ||
return this.primary.underline; | ||
set em(custom) { | ||
this._em = custom; | ||
}, | ||
get em() { | ||
return this._em || this.primary.underline; | ||
}, | ||
@@ -53,10 +68,21 @@ /** | ||
set pending(custom) { | ||
this._pending = custom; | ||
}, | ||
get pending() { | ||
return this.primary; | ||
return this._pending || this.primary; | ||
}, | ||
set submitted(custom) { | ||
this._submitted = custom; | ||
}, | ||
get submitted() { | ||
return this.success; | ||
return this._submitted || this.success; | ||
}, | ||
set cancelled(custom) { | ||
this._cancelled = custom; | ||
}, | ||
get cancelled() { | ||
return this.danger; | ||
return this._cancelled || this.danger; | ||
}, | ||
@@ -68,7 +94,21 @@ | ||
set typing(custom) { | ||
this._typing = custom; | ||
}, | ||
get typing() { | ||
return this._typing || this.dim; | ||
}, | ||
set placeholder(custom) { | ||
this._placeholder = custom; | ||
}, | ||
get placeholder() { | ||
return this.primary.dim; | ||
return this._placeholder || this.primary.dim; | ||
}, | ||
set highlight(custom) { | ||
this._highlight = custom; | ||
}, | ||
get highlight() { | ||
return this.inverse; | ||
return this._highlight || this.inverse; | ||
} | ||
@@ -88,12 +128,13 @@ }; | ||
for (let key of Object.keys(colors)) { | ||
if (!result.hasOwnProperty(key)) { | ||
Reflect.defineProperty(result, key, { get: () => colors[key] }); | ||
} | ||
} | ||
for (let key of Object.keys(colors.styles)) { | ||
if (!result.hasOwnProperty(key)) { | ||
Reflect.defineProperty(result, key, { | ||
get() { | ||
return colors[key]; | ||
} | ||
}); | ||
Reflect.defineProperty(result, key, { get: () => colors[key] }); | ||
} | ||
} | ||
return result; | ||
@@ -100,0 +141,0 @@ }; |
@@ -9,2 +9,5 @@ 'use strict'; | ||
...colors.symbols, | ||
upDownDoubleArrow: '⇕', | ||
upDownDoubleArrow2: '⬍', | ||
upDownArrow: '↕', | ||
asterisk: '*', | ||
@@ -17,2 +20,3 @@ asterism: '⁂', | ||
fullBlock: '█', | ||
identicalTo: '≡', | ||
indicator: colors.symbols.check, | ||
@@ -26,2 +30,6 @@ leftAngle: '‹', | ||
pilcrow: '¶', | ||
pilcrow2: '❡', | ||
pencilUpRight: '✐', | ||
pencilDownRight: '✎', | ||
pencilRight: '✏', | ||
plus: '+', | ||
@@ -49,3 +57,3 @@ plusMinus: '±', | ||
on: isWindows ? '(*)' : '◉', | ||
disabled: isWindows ? '(|)' : 'Ⓘ' //㊀ | ||
disabled: isWindows ? '(|)' : 'Ⓘ' | ||
}, | ||
@@ -52,0 +60,0 @@ numbers: ['⓪', '①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⑪', '⑫', '⑬', '⑭', '⑮', '⑯', '⑰', '⑱', '⑲', '⑳', '㉑', '㉒', '㉓', '㉔', '㉕', '㉖', '㉗', '㉘', '㉙', '㉚', '㉛', '㉜', '㉝', '㉞', '㉟', '㊱', '㊲', '㊳', '㊴', '㊵', '㊶', '㊷', '㊸', '㊹', '㊺', '㊻', '㊼', '㊽', '㊾', '㊿'] |
'use strict'; | ||
const { reorder, scrollUp, scrollDown, isObject, swap, set } = require('../utils'); | ||
const colors = require('ansi-colors'); | ||
const Prompt = require('../prompt'); | ||
const roles = require('../roles'); | ||
const utils = require('../utils'); | ||
const { reorder, scrollUp, scrollDown, isObject, swap } = utils; | ||
class ArrayPrompt extends Prompt { | ||
constructor(options = {}) { | ||
constructor(options) { | ||
super(options); | ||
this.cursorHide(); | ||
this.maxChoices = options.maxChoices || Infinity; | ||
this.maxSelected = options.maxSelected || Infinity; | ||
this.multiple = options.multiple || false; | ||
@@ -29,3 +30,3 @@ this.initial = options.initial || 0; | ||
async reset() { | ||
let { choices, initial, autofocus } = this.options; | ||
let { choices, initial, autofocus, suggest } = this.options; | ||
this.state._choices = []; | ||
@@ -37,2 +38,6 @@ this.state.choices = []; | ||
if (typeof suggest !== 'function' && this.selectable.length === 0) { | ||
throw new Error('At least one choice must be selectable'); | ||
} | ||
if (isObject(initial)) initial = Object.keys(initial); | ||
@@ -58,2 +63,3 @@ if (Array.isArray(initial)) { | ||
async toChoices(value, parent) { | ||
this.state.loadingChoices = true; | ||
let choices = []; | ||
@@ -77,3 +83,7 @@ let index = 0; | ||
return toChoices(value, parent); | ||
return toChoices(value, parent) | ||
.then(choices => { | ||
this.state.loadingChoices = false; | ||
return choices; | ||
}); | ||
} | ||
@@ -92,3 +102,3 @@ | ||
if (typeof ele.disabled === 'string') { | ||
if (typeof ele.disabled === 'string' && !ele.hint) { | ||
ele.hint = ele.disabled; | ||
@@ -102,12 +112,13 @@ ele.disabled = true; | ||
// if the choice was already normalized, return it | ||
if (ele.index != null) return ele; | ||
ele.name = ele.name || ele.key || ele.title || ele.value || ele.message; | ||
ele.message = ele.message || ele.name || ''; | ||
ele.value = ele.value || ele.name; | ||
ele.value = [ele.value, ele.name].find(this.isValue.bind(this)); | ||
ele.cursor = 0; | ||
ele.input = ''; | ||
ele.index = i; | ||
ele.cursor = 0; | ||
ele.parent = parent; | ||
utils.define(ele, 'parent', parent); | ||
ele.level = parent ? parent.level + 1 : 1; | ||
@@ -118,6 +129,2 @@ ele.indent = parent ? parent.indent + ' ' : (ele.indent || ''); | ||
if (typeof ele.initial === 'function') { | ||
ele.initial = await ele.initial.call(this, this.state, ele, i); | ||
} | ||
if (!this.isDisabled(ele)) { | ||
@@ -127,9 +134,16 @@ this.longest = Math.max(this.longest, colors.unstyle(ele.message).length); | ||
let init = { ...ele }; | ||
// shallow clone the choice first | ||
let choice = { ...ele }; | ||
ele.reset = (input = init.input, value = init.value) => { | ||
for (let key of Object.keys(init)) ele[key] = init[key]; | ||
// then allow the choice to be reset using the "original" values | ||
ele.reset = (input = choice.input, value = choice.value) => { | ||
for (let key of Object.keys(choice)) ele[key] = choice[key]; | ||
ele.input = input; | ||
ele.value = value; | ||
}; | ||
if (typeof ele.initial === 'function') { | ||
ele.initial = await ele.initial.call(this, this.state, ele, i); | ||
} | ||
return ele; | ||
@@ -144,8 +158,21 @@ } | ||
async newItem() { | ||
let choices = this.choices.slice(); | ||
choices.push({ name: 'New choice name?', editable: true, newChoice: true }); | ||
this.choices = await this.toChoices(choices); | ||
async addChoice(ele, i, parent) { | ||
let choice = await this.toChoice(ele, i, parent); | ||
this.choices.push(choice); | ||
this.index = this.choices.length - 1; | ||
this.limit = this.choices.length; | ||
return choice; | ||
} | ||
async newItem(item, i, parent) { | ||
let ele = { name: 'New choice name?', editable: true, newChoice: true, ...item }; | ||
let choice = await this.addChoice(ele, i, parent); | ||
choice.updateChoice = () => { | ||
delete choice.newChoice; | ||
choice.name = choice.message = choice.input; | ||
choice.input = ''; | ||
choice.cursor = 0; | ||
}; | ||
return this.render(); | ||
@@ -159,5 +186,3 @@ } | ||
dispatch(s, key) { | ||
if (this.multiple && this[key.name]) { | ||
return this[key.name](); | ||
} | ||
if (this.multiple && this[key.name]) return this[key.name](); | ||
this.alert(); | ||
@@ -168,2 +193,5 @@ } | ||
if (typeof enabled !== 'boolean') enabled = choice.enabled; | ||
if (enabled && !choice.enabled && this.selected.length >= this.maxSelected) { | ||
return this.alert(); | ||
} | ||
this.index = choice.index; | ||
@@ -181,4 +209,4 @@ choice.enabled = enabled && !this.isDisabled(choice); | ||
a() { | ||
let choices = this.choices.filter(ch => !this.isDisabled(ch)); | ||
let enabled = choices.every(ch => ch.enabled); | ||
if (this.maxSelected < this.choices.length) return this.alert(); | ||
let enabled = this.selectable.every(ch => ch.enabled); | ||
this.choices.forEach(ch => (ch.enabled = !enabled)); | ||
@@ -189,2 +217,7 @@ return this.render(); | ||
i() { | ||
// don't allow choices to be inverted if it will result in | ||
// more than the maximum number of allowed selected items. | ||
if (this.choices.length - this.selected.length > this.maxSelected) { | ||
return this.alert(); | ||
} | ||
this.choices.forEach(ch => (ch.enabled = !ch.enabled)); | ||
@@ -201,2 +234,6 @@ return this.render(); | ||
toggle(choice, enabled) { | ||
if (!choice.enabled && this.selected.length >= this.maxSelected) { | ||
return this.alert(); | ||
} | ||
if (typeof enabled !== 'boolean') enabled = !choice.enabled; | ||
@@ -212,3 +249,3 @@ choice.enabled = enabled; | ||
let choices = parent.choices.filter(ch => this.isDisabled(ch)); | ||
parent.enabled = choices.every(ch => ch.enabled); | ||
parent.enabled = choices.every(ch => ch.enabled === true); | ||
parent = parent.parent; | ||
@@ -218,2 +255,3 @@ } | ||
reset(this, this.choices); | ||
this.emit('toggle', choice, this); | ||
return choice; | ||
@@ -223,4 +261,5 @@ } | ||
enable(choice) { | ||
if (this.selected.length >= this.maxSelected) return this.alert(); | ||
choice.enabled = !this.isDisabled(choice); | ||
choice.choices && choice.choices.forEach(ch => this.enable(ch)); | ||
choice.choices && choice.choices.forEach(this.enable.bind(this)); | ||
return choice; | ||
@@ -245,2 +284,6 @@ } | ||
if (!choice.enabled && this.selected.length >= this.maxSelected) { | ||
return this.alert(); | ||
} | ||
if (this.visible.indexOf(choice) === -1) { | ||
@@ -437,3 +480,3 @@ let choices = reorder(this.choices); | ||
isDisabled(choice = this.focused) { | ||
let keys = ['disabled', 'collapsed', 'hidden', 'completing']; | ||
let keys = ['disabled', 'collapsed', 'hidden', 'completing', 'readonly']; | ||
if (choice && keys.some(key => choice[key] === true)) { | ||
@@ -465,2 +508,9 @@ return true; | ||
map(names = [], prop = 'value') { | ||
return [].concat(names || []).reduce((acc, name) => { | ||
acc[name] = this.find(name, prop); | ||
return acc; | ||
}, {}); | ||
} | ||
filter(value, prop) { | ||
@@ -490,18 +540,9 @@ let isChoice = (ele, i) => [ele.name, i].includes(value); | ||
map(names = [], prop = 'value') { | ||
return [].concat(names || []).reduce((acc, name) => { | ||
acc[name] = this.find(name, prop); | ||
return acc; | ||
}, {}); | ||
} | ||
async submit() { | ||
let choice = this.focused; | ||
if (!choice) return this.alert(); | ||
if (choice.newChoice) { | ||
if (!choice.input) return this.alert(); | ||
delete choice.newChoice; | ||
choice.name = choice.message = choice.input; | ||
choice.input = ''; | ||
choice.cursor = 0; | ||
choice.updateChoice(); | ||
return this.render(); | ||
@@ -514,2 +555,3 @@ } | ||
// re-sort choices to original order | ||
if (this.options.reorder !== false && this.options.sort !== true) { | ||
@@ -525,11 +567,3 @@ this.choices = reorder(this.choices); | ||
if (multi && this.choices.some(ch => ch.choices)) { | ||
this.value = {}; | ||
for (let choice of value) set(this.value, choice.path, choice.value); | ||
} else if (multi) { | ||
this.value = value.map(ch => ch.name); | ||
} else { | ||
this.value = value.name; | ||
} | ||
this.value = multi ? value.map(ch => ch.name) : value.name; | ||
return super.submit(); | ||
@@ -597,3 +631,3 @@ } | ||
get enabled() { | ||
return this.filter(choice => this.isEnabled(choice)); | ||
return this.filter(this.isEnabled.bind(this)); | ||
} | ||
@@ -603,3 +637,3 @@ | ||
let choice = this.choices[this.index]; | ||
if (this.state.submitted && this.multiple !== true) { | ||
if (choice && this.state.submitted && this.multiple !== true) { | ||
choice.enabled = true; | ||
@@ -610,2 +644,6 @@ } | ||
get selectable() { | ||
return this.choices.filter(choice => !this.isDisabled(choice)); | ||
} | ||
get selected() { | ||
@@ -617,2 +655,6 @@ return this.multiple ? this.enabled : this.focused; | ||
function reset(prompt, choices) { | ||
if (typeof choices === 'function') { | ||
if (utils.isAsyncFn(choices)) return choices; | ||
choices = choices.call(prompt, prompt); | ||
} | ||
for (let choice of choices) { | ||
@@ -624,3 +666,3 @@ if (choice.choices) { | ||
if (prompt.isDisabled(choice) === true) { | ||
choice.enabled = false; | ||
delete choice.enabled; | ||
} | ||
@@ -627,0 +669,0 @@ } |
'use strict'; | ||
const Prompt = require('../prompt'); | ||
const { isPrimitive } = require('../utils'); | ||
const { isPrimitive, hasColor } = require('../utils'); | ||
class BooleanPrompt extends Prompt { | ||
constructor(options = {}) { | ||
constructor(options) { | ||
super(options); | ||
this.state.input = this.cast(this.initial); | ||
this.cursorHide(); | ||
} | ||
async initialize() { | ||
let initial = await this.resolve(this.initial, this.state); | ||
this.input = await this.cast(initial); | ||
await super.initialize(); | ||
} | ||
dispatch(ch) { | ||
if (!this.isValue(ch)) return this.alert(); | ||
this.state.input = ch; | ||
this.input = ch; | ||
return this.submit(); | ||
} | ||
format(value = this.value) { | ||
format(value) { | ||
let { styles, state } = this; | ||
@@ -36,2 +41,12 @@ return !state.submitted ? styles.primary(value) : styles.success(value); | ||
async hint() { | ||
if (this.state.status === 'pending') { | ||
let hint = await this.element('hint'); | ||
if (!hasColor(hint)) { | ||
return this.styles.muted(hint); | ||
} | ||
return hint; | ||
} | ||
} | ||
isValue(value) { | ||
@@ -45,7 +60,8 @@ return isPrimitive(value) && (this.isTrue(value) || this.isFalse(value)); | ||
let prefix = await this.prefix(); | ||
let separator = await this.separator(); | ||
let message = await this.message(); | ||
let sep = await this.separator(); | ||
let msg = await this.message(); | ||
let hint = this.styles.muted(this.default); | ||
let promptLine = this.state.prompt = [prefix, message, hint, separator].join(' '); | ||
let promptLine = [prefix, msg, hint, sep].filter(Boolean).join(' '); | ||
this.state.prompt = promptLine; | ||
@@ -55,7 +71,7 @@ let header = await this.header(); | ||
let output = await this.format(value); | ||
let help = await this.error() || await this.hint(); | ||
let help = (await this.error()) || (await this.hint()); | ||
let footer = await this.footer(); | ||
if (output || !help) promptLine += ' ' + output; | ||
if (help && !promptLine.includes(help)) promptLine += ' ' + help; | ||
if (help && !promptLine.includes(help)) output += ' ' + help; | ||
promptLine += ' ' + output; | ||
@@ -62,0 +78,0 @@ this.clear(size); |
@@ -80,5 +80,4 @@ 'use strict'; | ||
submit() { | ||
if (this.isValue(this.input)) this.value = this.input; | ||
if (!this.isValue(this.value)) this.value = this.initial; | ||
this.value = this.toNumber(this.value || 0); | ||
let value = [this.input, this.initial].find(v => this.isValue(v)); | ||
this.value = this.toNumber(value || 0); | ||
return super.submit(); | ||
@@ -85,0 +84,0 @@ } |
@@ -8,18 +8,17 @@ 'use strict'; | ||
class StringPrompt extends Prompt { | ||
constructor(options = {}) { | ||
constructor(options) { | ||
super(options); | ||
this.initial = isPrimitive(this.initial) ? String(this.initial) : ''; | ||
if (this.initial) this.cursorHide(); | ||
this.prevKeypress = 0; | ||
this.prevCursor = 0; | ||
this.clipboard = []; | ||
this.typed = false; | ||
this.state.prevCursor = 0; | ||
this.state.clipboard = []; | ||
} | ||
async keypress(char, key) { | ||
this.typed = true; | ||
let prev = this.prevKeypress; | ||
this.prevKeypress = key; | ||
async keypress(char, key = {}) { | ||
let prev = this.state.prevKeypress; | ||
this.state.prevKeypress = key; | ||
if (this.options.multiline === true && key.name === 'return') { | ||
if (!prev || prev.name !== 'return') return this.append('\n', key); | ||
if (!prev || prev.name !== 'return') { | ||
return this.append('\n', key); | ||
} | ||
} | ||
@@ -73,3 +72,3 @@ return super.keypress(char, key); | ||
if (this.input.length <= pos) return this.alert(); | ||
this.clipboard.push(this.input.slice(pos)); | ||
this.state.clipboard.push(this.input.slice(pos)); | ||
this.input = this.input.slice(0, pos); | ||
@@ -85,3 +84,3 @@ this.render(); | ||
let words = before.split(' '); | ||
this.clipboard.push(words.pop()); | ||
this.state.clipboard.push(words.pop()); | ||
this.input = words.join(' '); | ||
@@ -94,4 +93,4 @@ this.cursor = this.input.length; | ||
paste() { | ||
if (!this.clipboard.length) return this.alert(); | ||
this.insert(this.clipboard.pop()); | ||
if (!this.state.clipboard.length) return this.alert(); | ||
this.insert(this.state.clipboard.pop()); | ||
this.render(); | ||
@@ -101,7 +100,7 @@ } | ||
toggleCursor() { | ||
if (this.prevCursor) { | ||
this.cursor = this.prevCursor; | ||
this.prevCursor = 0; | ||
if (this.state.prevCursor) { | ||
this.cursor = this.state.prevCursor; | ||
this.state.prevCursor = 0; | ||
} else { | ||
this.prevCursor = this.cursor; | ||
this.state.prevCursor = this.cursor; | ||
this.cursor = 0; | ||
@@ -159,7 +158,8 @@ } | ||
format(input = this.value) { | ||
async format(input = this.value) { | ||
let initial = await this.resolve(this.initial, this.state); | ||
if (!this.state.submitted) { | ||
return placeholder(this, { input, initial: this.initial, pos: this.cursor }); | ||
return placeholder(this, { input, initial, pos: this.cursor }); | ||
} | ||
return this.styles.submitted(input || this.initial); | ||
return this.styles.submitted(input || initial); | ||
} | ||
@@ -179,3 +179,3 @@ | ||
let output = await this.format(); | ||
let help = await this.error() || await this.hint(); | ||
let help = (await this.error()) || (await this.hint()); | ||
let footer = await this.footer(); | ||
@@ -182,0 +182,0 @@ |
'use strict'; | ||
const toString = Object.prototype.toString; | ||
const colors = require('ansi-colors'); | ||
@@ -28,2 +29,10 @@ let called = false; | ||
exports.nativeType = val => { | ||
return toString.call(val).slice(8, -1).toLowerCase().replace(/\s/g, ''); | ||
}; | ||
exports.isAsyncFn = val => { | ||
return exports.nativeType(val) === 'asyncfunction'; | ||
}; | ||
exports.isPrimitive = val => { | ||
@@ -40,4 +49,4 @@ return val != null && typeof val !== 'object' && typeof val !== 'function'; | ||
exports.scrollDown = choices => [...choices.slice(1), choices[0]]; | ||
exports.scrollUp = choices => [choices.pop(), ...choices]; | ||
exports.scrollDown = (choices = []) => [...choices.slice(1), choices[0]]; | ||
exports.scrollUp = (choices = []) => [choices.pop(), ...choices]; | ||
@@ -63,4 +72,4 @@ exports.reorder = (arr = []) => { | ||
exports.width = (stream, fallback = 80) => { | ||
let columns = stream.columns || fallback; | ||
if (typeof stream.getWindowSize === 'function') { | ||
let columns = (stream && stream.columns) ? stream.columns : fallback; | ||
if (stream && typeof stream.getWindowSize === 'function') { | ||
columns = stream.getWindowSize()[0]; | ||
@@ -75,4 +84,4 @@ } | ||
exports.height = (stream, fallback = 25) => { | ||
let rows = stream.rows || fallback; | ||
if (typeof stream.getWindowSize === 'function') { | ||
let rows = (stream && stream.rows) ? stream.rows : fallback; | ||
if (stream && typeof stream.getWindowSize === 'function') { | ||
rows = stream.getWindowSize()[1]; | ||
@@ -79,0 +88,0 @@ } |
{ | ||
"name": "enquirer", | ||
"description": "Stylish, intuitive and user-friendly prompt system. Fast and lightweight enough for small projects, powerful and extensible enough for the most advanced use cases.", | ||
"version": "2.1.1", | ||
"version": "2.2.0", | ||
"homepage": "https://github.com/enquirer/enquirer", | ||
@@ -100,2 +100,5 @@ "author": "Jon Schlinkert (https://github.com/jonschlinkert)", | ||
], | ||
"helpers": [ | ||
"./docs/helpers.js" | ||
], | ||
"lint": { | ||
@@ -102,0 +105,0 @@ "reflinks": true |
151
README.md
@@ -85,3 +85,3 @@ <h1 align="center">Enquirer</h1> | ||
```sh | ||
$ npm install enquirer | ||
$ npm install enquirer --save | ||
``` | ||
@@ -109,6 +109,6 @@ | ||
name: 'username', | ||
message: 'What is your username?' | ||
message: 'What is your username?' | ||
}); | ||
console.log(response); | ||
console.log(response); | ||
//=> { username: 'jonschlinkert' } | ||
@@ -128,3 +128,3 @@ ``` | ||
name: 'name', | ||
message: 'What is your name?' | ||
message: 'What is your name?' | ||
}, | ||
@@ -134,3 +134,3 @@ { | ||
name: 'username', | ||
message: 'What is your username?' | ||
message: 'What is your username?' | ||
} | ||
@@ -147,3 +147,3 @@ ]); | ||
### Todo | ||
## Todo | ||
@@ -168,3 +168,3 @@ We're currently working on documentation for the following items. Please star and watch the repository for updates! | ||
The main export of this library is the `Enquirer` class. You can add Enquirer to your JavaScript project with following line of code. | ||
Add Enquirer to your JavaScript project with following line of code. | ||
@@ -179,3 +179,3 @@ ```js | ||
Enquirer has methods and features designed to simplify running multiple prompts. | ||
The main export of this library is the `Enquirer` class, which has methods and features designed to simplify running prompts. | ||
@@ -185,11 +185,11 @@ ```js | ||
const question = [ | ||
{ | ||
type: 'input', | ||
name: 'username', | ||
message: 'What is your username?' | ||
{ | ||
type: 'input', | ||
name: 'username', | ||
message: 'What is your username?' | ||
}, | ||
{ | ||
type: 'password', | ||
name: 'password', | ||
message: 'What is your password?' | ||
{ | ||
type: 'password', | ||
name: 'password', | ||
message: 'What is your password?' | ||
} | ||
@@ -218,9 +218,10 @@ ]; | ||
const { Input } = require('enquirer'); | ||
const prompt = new Input({ | ||
name: 'username', | ||
message: 'What is your username?' | ||
const prompt = new Input({ | ||
name: 'username', | ||
message: 'What is your username?' | ||
}); | ||
let answer = await prompt.run(); | ||
console.log('Username:', answer); | ||
prompt.run() | ||
.then(answer => console.log('Username:', answer)) | ||
.catch(console.error); | ||
``` | ||
@@ -326,4 +327,10 @@ | ||
<br> | ||
## ❯ Prompts | ||
In this document you'll learn about Enquirer's prompts: what they look like, how they work, how to run them, available options, and how to customize the prompts or create your own prompt concept. | ||
**Getting started with Enquirer's prompts** | ||
* [Prompt](#prompt) - The base `Prompt` class used by other prompts | ||
@@ -356,3 +363,3 @@ - [Prompt Options](#prompt-options) | ||
// optional | ||
// optional | ||
skip: boolean | function | async function | ||
@@ -407,6 +414,6 @@ initial: string | function | async function | ||
* [List](#list-prompt) | ||
* [Scale](#scale-prompt) | ||
* [MultiSelect](#multiselect-prompt) | ||
* [Number](#number-prompt) | ||
* [Password](#password-prompt) | ||
* [Scale](#scale-prompt) | ||
* [Select](#select-prompt) | ||
@@ -417,2 +424,3 @@ * [Snippet](#snippet-prompt) | ||
* [Text](#input-prompt) (alias for the [Input prompt](#input-prompt)) | ||
* [Toggle](#toggle-prompt) | ||
@@ -516,6 +524,6 @@ ### AutoComplete Prompt | ||
```js | ||
const question = { | ||
type: 'input', | ||
name: 'username', | ||
message: 'What is your username?' | ||
const question = { | ||
type: 'input', | ||
name: 'username', | ||
message: 'What is your username?' | ||
}; | ||
@@ -571,21 +579,2 @@ ``` | ||
### Scale Prompt | ||
Prompt that allows the user to quickly provide feedback using a [Likert Scale](https://en.wikipedia.org/wiki/Likert_scale). | ||
<p align="center"> | ||
<img src="https://raw.githubusercontent.com/enquirer/enquirer/master/media/scale-prompt.gif" alt="Enquirer Scale Prompt" width="750"> | ||
</p> | ||
**Related prompts** | ||
* [AutoComplete](#autocomplete-prompt) | ||
* [Select](#select-prompt) | ||
* [Survey](#survey-prompt) | ||
**↑ back to:** [Getting Started](#-getting-started) · [Prompts](#-prompts) | ||
<br> | ||
<br> | ||
### MultiSelect Prompt | ||
@@ -646,8 +635,8 @@ | ||
### Select Prompt | ||
### Scale Prompt | ||
Prompt that allows the user to select from a list of options. | ||
A more compact version of the [Survey prompt](#survey-prompt), the Scale prompt allows the user to quickly provide feedback using a [Likert Scale](https://en.wikipedia.org/wiki/Likert_scale). | ||
<p align="center"> | ||
<img src="https://raw.githubusercontent.com/enquirer/enquirer/master/media/select-prompt.gif" alt="Enquirer Select Prompt" width="750"> | ||
<img src="https://raw.githubusercontent.com/enquirer/enquirer/master/media/scale-prompt.gif" alt="Enquirer Scale Prompt" width="750"> | ||
</p> | ||
@@ -658,3 +647,4 @@ | ||
* [AutoComplete](#autocomplete-prompt) | ||
* [MultiSelect](#multiselect-prompt) | ||
* [Select](#select-prompt) | ||
* [Survey](#survey-prompt) | ||
@@ -666,8 +656,8 @@ **↑ back to:** [Getting Started](#-getting-started) · [Prompts](#-prompts) | ||
### Snippet Prompt | ||
### Select Prompt | ||
Prompt that allows the user to replace placeholders in a snippet of code or text. | ||
Prompt that allows the user to select from a list of options. | ||
<p align="center"> | ||
<img src="https://raw.githubusercontent.com/enquirer/enquirer/master/media/snippet-prompt.gif" alt="Prompts" width="750"> | ||
<img src="https://raw.githubusercontent.com/enquirer/enquirer/master/media/select-prompt.gif" alt="Enquirer Select Prompt" width="750"> | ||
</p> | ||
@@ -677,4 +667,4 @@ | ||
* [Survey](#survey-prompt) | ||
* [AutoComplete](#autocomplete-prompt) | ||
* [MultiSelect](#multiselect-prompt) | ||
@@ -708,2 +698,20 @@ **↑ back to:** [Getting Started](#-getting-started) · [Prompts](#-prompts) | ||
### Snippet Prompt | ||
Prompt that allows the user to replace placeholders in a snippet of code or text. | ||
<p align="center"> | ||
<img src="https://raw.githubusercontent.com/enquirer/enquirer/master/media/snippet-prompt.gif" alt="Prompts" width="750"> | ||
</p> | ||
**Related prompts** | ||
* [Survey](#survey-prompt) | ||
* [AutoComplete](#autocomplete-prompt) | ||
**↑ back to:** [Getting Started](#-getting-started) · [Prompts](#-prompts) | ||
<br> | ||
<br> | ||
### Survey Prompt | ||
@@ -726,2 +734,23 @@ | ||
### Toggle Prompt | ||
Prompt that allows the user to toggle between two values then returns `true` or `false`. | ||
<p align="center"> | ||
<img src="https://raw.githubusercontent.com/enquirer/enquirer/master/media/toggle-prompt.gif" alt="Enquirer Toggle Prompt" width="750"> | ||
</p> | ||
As with the other prompts, all parts of this prompt are customizable. | ||
**Related prompts** | ||
* [Confirm](#confirm-prompt) | ||
* [Input](#input-prompt) | ||
* [Sort](#sort-prompt) | ||
**↑ back to:** [Getting Started](#-getting-started) · [Prompts](#-prompts) | ||
<br> | ||
<br> | ||
## ❯ Prompt Types | ||
@@ -899,2 +928,4 @@ | ||
<!-- Example: HaiKarate Custom Prompt --> | ||
```js | ||
@@ -922,7 +953,4 @@ const { Prompt } = require('enquirer'); | ||
} | ||
``` | ||
Custom prompts may be used directly by creating an instance of your custom prompt class. | ||
```js | ||
// Use the prompt by creating an instance of your custom prompt class. | ||
const prompt = new HaiKarate({ | ||
@@ -962,3 +990,3 @@ message: 'How many sprays do you want?', | ||
async onSubmit(name, value) { | ||
await spritzer.activate(value); //<= activate drone | ||
await spritzer.activate(value); //<= activate drone | ||
return value; | ||
@@ -970,2 +998,4 @@ } | ||
<br> | ||
## ❯ Key Bindings | ||
@@ -1082,3 +1112,2 @@ | ||
<br> | ||
<br> | ||
@@ -1141,3 +1170,3 @@ ## ❯ Release History | ||
| --- | --- | | ||
| 255 | [jonschlinkert](https://github.com/jonschlinkert) | | ||
| 261 | [jonschlinkert](https://github.com/jonschlinkert) | | ||
| 25 | [doowb](https://github.com/doowb) | | ||
@@ -1144,0 +1173,0 @@ | 15 | [g-plane](https://github.com/g-plane) | |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 2 instances in 1 package
3981
1174
1
171993
45