multiselect.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. 'use strict';
  2. const color = require('kleur');
  3. const Prompt = require('./prompt');
  4. const { cursor } = require('sisteransi');
  5. const { clear, figures, style } = require('../util');
  6. /**
  7. * MultiselectPrompt Base Element
  8. * @param {Object} opts Options
  9. * @param {String} opts.message Message
  10. * @param {Array} opts.choices Array of choice objects
  11. * @param {String} [opts.hint] Hint to display
  12. * @param {Number} [opts.cursor=0] Cursor start position
  13. * @param {Number} [opts.max] Max choices
  14. */
  15. class MultiselectPrompt extends Prompt {
  16. constructor(opts={}) {
  17. super(opts);
  18. this.msg = opts.message;
  19. this.cursor = opts.cursor || 0;
  20. this.hint = opts.hint || '- Space to select. Return to submit';
  21. this.maxChoices = opts.max;
  22. this.value = opts.choices.map(v => Object.assign({ title: v.value, selected: false }, v));
  23. this.clear = clear('');
  24. this.render(true);
  25. }
  26. reset() {
  27. this.value.map(v => !v.selected);
  28. this.cursor = 0;
  29. this.fire();
  30. this.render();
  31. }
  32. selected() {
  33. return this.value.filter(v => v.selected);
  34. }
  35. abort() {
  36. this.done = this.aborted = true;
  37. this.fire();
  38. this.render();
  39. this.out.write('\n');
  40. this.close();
  41. }
  42. submit() {
  43. this.done = true;
  44. this.aborted = false;
  45. this.fire();
  46. this.render();
  47. this.out.write('\n');
  48. this.close();
  49. }
  50. first() {
  51. this.cursor = 0;
  52. this.render();
  53. }
  54. last() {
  55. this.cursor = this.value.length - 1;
  56. this.render();
  57. }
  58. next() {
  59. this.cursor = (this.cursor + 1) % this.value.length;
  60. this.render();
  61. }
  62. up() {
  63. if (this.cursor === 0) return this.bell();
  64. this.cursor--;
  65. this.render();
  66. }
  67. down() {
  68. if (this.cursor === this.value.length - 1) return this.bell();
  69. this.cursor++;
  70. this.render();
  71. }
  72. left() {
  73. this.value[this.cursor].selected = false;
  74. this.render();
  75. }
  76. right() {
  77. if (this.value.filter(e => e.selected).length >= this.maxChoices) return this.bell();
  78. this.value[this.cursor].selected = true;
  79. this.render();
  80. }
  81. _(c, key) {
  82. if (c !== ' ') return this.bell();
  83. const v = this.value[this.cursor];
  84. if (v.selected) {
  85. v.selected = false;
  86. this.render();
  87. } else if (this.value.filter(e => e.selected).length >= this.maxChoices) {
  88. return this.bell();
  89. } else {
  90. v.selected = true;
  91. this.render();
  92. }
  93. }
  94. render(first) {
  95. if (first) this.out.write(cursor.hide);
  96. // print prompt
  97. const selected = this.value
  98. .filter(e => e.selected)
  99. .map(v => v.title)
  100. .join(', ');
  101. let prompt = [
  102. style.symbol(this.done, this.aborted),
  103. color.bold(this.msg),
  104. style.delimiter(false),
  105. this.done ? selected : color.gray(this.hint)
  106. ].join(' ');
  107. // print choices
  108. if (!this.done) {
  109. const c = this.cursor;
  110. prompt +=
  111. '\n' +
  112. this.value
  113. .map(
  114. (v, i) =>
  115. (v.selected ? color.green(figures.tick) : ' ') +
  116. ' ' +
  117. (c === i ? color.cyan.underline(v.title) : v.title)
  118. )
  119. .join('\n');
  120. }
  121. this.out.write(this.clear + prompt);
  122. this.clear = clear(prompt);
  123. }
  124. }
  125. module.exports = MultiselectPrompt;