UNPKG

inquirer-ts-checkbox-plus-prompt

Version:
260 lines (257 loc) 9.71 kB
import chalk from 'chalk'; import cliCursor from 'cli-cursor'; import figures from 'figures'; import Choices from 'inquirer/lib/objects/choices'; import Base from 'inquirer/lib/prompts/base'; import observe from 'inquirer/lib/utils/events'; import Paginator from 'inquirer/lib/utils/paginator'; import { map, takeUntil } from 'rxjs/operators'; class CheckboxPlusPrompt extends Base { constructor(questions, rl, answers) { super(questions, rl, answers); this.pointer = 0; if (this.opt.highlight == null) { this.opt.highlight = false; } if (typeof this.opt.searchable == null) { this.opt.searchable = false; } if (typeof this.opt.default == null) { this.opt.default = undefined; } if (this.opt.source == null) { this.throwParamError('source'); } this.pointer = 0; this.firstSourceLoading = true; this.choices = new Choices([], answers); this.checkedChoices = []; this.value = []; this.lastQuery = undefined; this.searching = false; this.lastSourcePromise = undefined; this.default = this.opt.default; this.opt.default = undefined; this.selection = []; this.done = undefined; this.paginator = new Paginator(this.screen); } _run(callback) { this.done = callback; this.executeSource().then(() => { const events = observe(this.rl); const validation = this.handleSubmitEvents(events.line.pipe(map(this.getCurrentValue.bind(this)))); validation.success.forEach(this.onEnd.bind(this)); validation.error.forEach(this.onError.bind(this)); events.normalizedUpKey.pipe(takeUntil(validation.success)).forEach(this.onUpKey.bind(this)); events.normalizedDownKey .pipe(takeUntil(validation.success)) .forEach(this.onDownKey.bind(this)); events.spaceKey.pipe(takeUntil(validation.success)).forEach(this.onSpaceKey.bind(this)); if (this.opt.searchable === false) { events.numberKey.pipe(takeUntil(validation.success)).forEach(this.onNumberKey.bind(this)); events.aKey.pipe(takeUntil(validation.success)).forEach(this.onAllKey.bind(this)); events.iKey.pipe(takeUntil(validation.success)).forEach(this.onInverseKey.bind(this)); } else { events.keypress.pipe(takeUntil(validation.success)).forEach(this.onKeypress.bind(this)); } if (this.rl.line) { this.onKeypress(); } cliCursor.hide(); this.render(); }); return this; } getValue(choice) { if (choice.type === 'separator') { return undefined; } return choice.value; } executeSource() { let sourcePromise; this.rl.line = this.rl.line.trim(); if (this.rl.line === this.lastQuery) { return Promise.resolve(undefined); } if (this.opt.searchable) { sourcePromise = this.opt.source(this.answers, this.rl.line); } else { sourcePromise = this.opt.source(this.answers, undefined); } this.lastQuery = this.rl.line; this.lastSourcePromise = sourcePromise; this.searching = true; sourcePromise.then((choices) => { if (this.lastSourcePromise !== sourcePromise) { return; } this.searching = false; this.choices = new Choices(choices, this.answers); this.choices.forEach((choice) => { if (this.value.some((eachValue) => this.getValue(choice) === eachValue)) { this.toggleChoice(choice, true); } else { this.toggleChoice(choice, false); } if (this.default != null) { if (this.default.some((defaultValue) => this.getValue(choice) === defaultValue)) { this.toggleChoice(choice, true); } } }); this.pointer = 0; this.render(); this.default = undefined; this.firstSourceLoading = false; }); return sourcePromise; } render(error) { let message = this.getQuestion(); let bottomContent = ''; if (this.status === 'answered') { message += chalk.cyan(this.selection.join(', ')); return this.screen.render(message, bottomContent); } if (this.firstSourceLoading) { if (this.opt.searchable) { message += `(Press ${chalk.cyan.bold('<space>')} to select, or type anything to filter the list)`; } else { message += `(Press ${chalk.cyan.bold('<space>')} to select, ${chalk.cyan.bold('<a>')} to toggle all, ${chalk.cyan.bold('<i>')} to invert selection)`; } } if (this.opt.searchable) { message += this.rl.line; } if (this.searching) { message += `\n ${chalk.cyan('Searching...')}`; } else if (!this.choices.length) { message += `\n ${chalk.yellow('No results...')}`; } else { const choicesStr = this.renderChoices(this.choices, this.pointer); const choice = this.choices.getChoice(this.pointer); const indexPosition = this.choices.indexOf(choice); message += `\n${this.paginator.paginate(choicesStr, indexPosition, this.opt.pageSize)}`; } if (error) { bottomContent = chalk.red('>> ') + error; } return this.screen.render(message, bottomContent); } onEnd(state) { this.status = 'answered'; this.render(); this.screen.done(); cliCursor.show(); this.done?.('value' in state ? state.value : undefined); } onError(state) { this.render(state.isValid); } getCurrentValue() { this.selection = this.checkedChoices.map((checkedChoice) => checkedChoice.short); const values = this.checkedChoices.map((checkedChoice) => checkedChoice.value); return values; } onUpKey() { const len = this.choices.realLength; this.pointer = this.pointer > 0 ? this.pointer - 1 : len - 1; this.render(); } onDownKey() { const len = this.choices.realLength; this.pointer = this.pointer < len - 1 ? this.pointer + 1 : 0; this.render(); } onNumberKey(input) { if (input <= this.choices.realLength) { this.pointer = input - 1; this.toggleChoice(this.choices.getChoice(this.pointer)); } this.render(); } onSpaceKey() { if (this.choices.getChoice(this.pointer) == null) { return; } this.toggleChoice(this.choices.getChoice(this.pointer)); this.render(); } onAllKey() { const shouldBeChecked = Boolean(this.choices.find((choice) => { return choice.type !== 'separator' && !choice.checked; })); this.choices.forEach((choice) => { if (choice.type !== 'separator') { choice.checked = shouldBeChecked; } return choice; }); this.render(); } onInverseKey() { this.choices.forEach((choice) => { if (choice.type !== 'separator') { choice.checked = !choice.checked; } }); this.render(); } onKeypress() { this.executeSource(); this.render(); } toggleChoice(choice, nextChecked) { if (choice.type === 'separator') { return; } const checked = nextChecked == null ? !(choice.checked ?? false) : nextChecked; this.value = this.value.filter((eachValue) => eachValue !== choice.value); this.checkedChoices = this.checkedChoices.filter((checkedChoice) => checkedChoice.value !== choice.value); choice.checked = false; if (checked === true) { this.value.push(choice.value); this.checkedChoices.push(choice); choice.checked = true; } } static getCheckboxFigure(checked = false) { return checked ? chalk.green(figures.radioOn) : figures.radioOff; } renderChoices(choices, pointer) { const output = []; let separatorOffset = 0; choices.forEach((choice, index) => { if (choice.type === 'separator') { separatorOffset += 1; output.push(` ${choice}\n`); return; } if (choice.disabled) { separatorOffset += 1; output.push(` - ${choice.name} (${typeof choice.disabled === 'string' ? choice.disabled : 'Disabled'})\n`); return; } if (index - separatorOffset === pointer) { output.push(chalk.cyan(figures.pointer)); output.push(CheckboxPlusPrompt.getCheckboxFigure(choice.checked)); output.push(' '); output.push(this.opt.highlight ? chalk.gray(choice.name) : choice.name); } else { output.push(` ${CheckboxPlusPrompt.getCheckboxFigure(choice.checked)} ${choice.name}`); } output.push('\n'); }); return output.join('').replace(/\n$/, ''); } } export { CheckboxPlusPrompt };