You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
196 lines
5.0 KiB
196 lines
5.0 KiB
'use strict'; |
|
|
|
const colors = require('ansi-colors'); |
|
const SelectPrompt = require('./select'); |
|
const placeholder = require('../placeholder'); |
|
|
|
class FormPrompt extends SelectPrompt { |
|
constructor(options) { |
|
super({ ...options, multiple: true }); |
|
this.type = 'form'; |
|
this.initial = this.options.initial; |
|
this.align = [this.options.align, 'right'].find(v => v != null); |
|
this.emptyError = ''; |
|
this.values = {}; |
|
} |
|
|
|
async reset(first) { |
|
await super.reset(); |
|
if (first === true) this._index = this.index; |
|
this.index = this._index; |
|
this.values = {}; |
|
this.choices.forEach(choice => choice.reset && choice.reset()); |
|
return this.render(); |
|
} |
|
|
|
dispatch(char) { |
|
return !!char && this.append(char); |
|
} |
|
|
|
append(char) { |
|
let choice = this.focused; |
|
if (!choice) return this.alert(); |
|
let { cursor, input } = choice; |
|
choice.value = choice.input = input.slice(0, cursor) + char + input.slice(cursor); |
|
choice.cursor++; |
|
return this.render(); |
|
} |
|
|
|
delete() { |
|
let choice = this.focused; |
|
if (!choice || choice.cursor <= 0) return this.alert(); |
|
let { cursor, input } = choice; |
|
choice.value = choice.input = input.slice(0, cursor - 1) + input.slice(cursor); |
|
choice.cursor--; |
|
return this.render(); |
|
} |
|
|
|
deleteForward() { |
|
let choice = this.focused; |
|
if (!choice) return this.alert(); |
|
let { cursor, input } = choice; |
|
if (input[cursor] === void 0) return this.alert(); |
|
let str = `${input}`.slice(0, cursor) + `${input}`.slice(cursor + 1); |
|
choice.value = choice.input = str; |
|
return this.render(); |
|
} |
|
|
|
right() { |
|
let choice = this.focused; |
|
if (!choice) return this.alert(); |
|
if (choice.cursor >= choice.input.length) return this.alert(); |
|
choice.cursor++; |
|
return this.render(); |
|
} |
|
|
|
left() { |
|
let choice = this.focused; |
|
if (!choice) return this.alert(); |
|
if (choice.cursor <= 0) return this.alert(); |
|
choice.cursor--; |
|
return this.render(); |
|
} |
|
|
|
space(ch, key) { |
|
return this.dispatch(ch, key); |
|
} |
|
|
|
number(ch, key) { |
|
return this.dispatch(ch, key); |
|
} |
|
|
|
next() { |
|
let ch = this.focused; |
|
if (!ch) return this.alert(); |
|
let { initial, input } = ch; |
|
if (initial && initial.startsWith(input) && input !== initial) { |
|
ch.value = ch.input = initial; |
|
ch.cursor = ch.value.length; |
|
return this.render(); |
|
} |
|
return super.next(); |
|
} |
|
|
|
prev() { |
|
let ch = this.focused; |
|
if (!ch) return this.alert(); |
|
if (ch.cursor === 0) return super.prev(); |
|
ch.value = ch.input = ''; |
|
ch.cursor = 0; |
|
return this.render(); |
|
} |
|
|
|
separator() { |
|
return ''; |
|
} |
|
|
|
format(value) { |
|
return !this.state.submitted ? super.format(value) : ''; |
|
} |
|
|
|
pointer() { |
|
return ''; |
|
} |
|
|
|
indicator(choice) { |
|
return choice.input ? '⦿' : '⊙'; |
|
} |
|
|
|
async choiceSeparator(choice, i) { |
|
let sep = await this.resolve(choice.separator, this.state, choice, i) || ':'; |
|
return sep ? ' ' + this.styles.disabled(sep) : ''; |
|
} |
|
|
|
async renderChoice(choice, i) { |
|
await this.onChoice(choice, i); |
|
|
|
let { state, styles } = this; |
|
let { cursor, initial = '', name, hint, input = '' } = choice; |
|
let { muted, submitted, primary, danger } = styles; |
|
|
|
let help = hint; |
|
let focused = this.index === i; |
|
let validate = choice.validate || (() => true); |
|
let sep = await this.choiceSeparator(choice, i); |
|
let msg = choice.message; |
|
|
|
if (this.align === 'right') msg = msg.padStart(this.longest + 1, ' '); |
|
if (this.align === 'left') msg = msg.padEnd(this.longest + 1, ' '); |
|
|
|
// re-populate the form values (answers) object |
|
let value = this.values[name] = (input || initial); |
|
let color = input ? 'success' : 'dark'; |
|
|
|
if ((await validate.call(choice, value, this.state)) !== true) { |
|
color = 'danger'; |
|
} |
|
|
|
let style = styles[color]; |
|
let indicator = style(await this.indicator(choice, i)) + (choice.pad || ''); |
|
|
|
let indent = this.indent(choice); |
|
let line = () => [indent, indicator, msg + sep, input, help].filter(Boolean).join(' '); |
|
|
|
if (state.submitted) { |
|
msg = colors.unstyle(msg); |
|
input = submitted(input); |
|
help = ''; |
|
return line(); |
|
} |
|
|
|
if (choice.format) { |
|
input = await choice.format.call(this, input, choice, i); |
|
} else { |
|
let color = this.styles.muted; |
|
let options = { input, initial, pos: cursor, showCursor: focused, color }; |
|
input = placeholder(this, options); |
|
} |
|
|
|
if (!this.isValue(input)) { |
|
input = this.styles.muted(this.symbols.ellipsis); |
|
} |
|
|
|
if (choice.result) { |
|
this.values[name] = await choice.result.call(this, value, choice, i); |
|
} |
|
|
|
if (focused) { |
|
msg = primary(msg); |
|
} |
|
|
|
if (choice.error) { |
|
input += (input ? ' ' : '') + danger(choice.error.trim()); |
|
} else if (choice.hint) { |
|
input += (input ? ' ' : '') + muted(choice.hint.trim()); |
|
} |
|
|
|
return line(); |
|
} |
|
|
|
async submit() { |
|
this.value = this.values; |
|
return super.base.submit.call(this); |
|
} |
|
} |
|
|
|
module.exports = FormPrompt;
|
|
|