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.
197 lines
5.0 KiB
197 lines
5.0 KiB
3 years ago
|
'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;
|