[READ-ONLY] Mirror of https://github.com/bombshell-dev/clack. Effortlessly build beautiful command-line apps bomb.sh/docs/clack/basics/getting-started/
cli command-line command-line-app node prompt prompts
5

Configure Feed

Select the types of activity you want to include in your feed.

feat(prompts): support maxItems in groupMultiselect (#528)

authored by

Bjorn Lu and committed by
GitHub
(May 8, 2026, 10:29 AM +0100) 284677ea ab58ce54

+788 -98
+5
.changeset/tame-beds-kiss.md
··· 1 + --- 2 + "@clack/prompts": minor 3 + --- 4 + 5 + Support scrolling and `maxItems` option for `groupMultiselect`, and removes indent when `withGuide` is set to `false`
+55 -54
packages/prompts/src/group-multi-select.ts
··· 9 9 S_CHECKBOX_SELECTED, 10 10 symbol, 11 11 } from './common.js'; 12 + import { limitOptions } from './limit-options.js'; 12 13 import type { Option } from './select.js'; 13 14 14 15 export interface GroupMultiSelectOptions<Value> extends CommonOptions { 15 16 message: string; 16 17 options: Record<string, Option<Value>[]>; 17 18 initialValues?: Value[]; 19 + maxItems?: number; 18 20 required?: boolean; 19 21 cursorAt?: Value; 20 22 selectableGroups?: boolean; ··· 42 44 const prefix = isItem ? (selectableGroups ? `${isLast ? S_BAR_END : S_BAR} ` : ' ') : ''; 43 45 let spacingPrefix = ''; 44 46 if (groupSpacing > 0 && !isItem) { 45 - const spacingPrefixText = `\n${styleText('cyan', S_BAR)}`; 46 - spacingPrefix = `${spacingPrefixText.repeat(groupSpacing - 1)}${spacingPrefixText} `; 47 + spacingPrefix = '\n'.repeat(groupSpacing); 47 48 } 48 49 49 50 if (state === 'active') { ··· 108 109 const title = `${hasGuide ? `${styleText('gray', S_BAR)}\n` : ''}${symbol(this.state)} ${opts.message}\n`; 109 110 const value = this.value ?? []; 110 111 112 + const styleOption = ( 113 + option: Option<Value> & { group: string | boolean }, 114 + active: boolean 115 + ) => { 116 + const options = this.options; 117 + const selected = 118 + value.includes(option.value) || 119 + (option.group === true && this.isGroupSelected(`${option.value}`)); 120 + const groupActive = 121 + !active && 122 + typeof option.group === 'string' && 123 + this.options[this.cursor].value === option.group; 124 + if (groupActive) { 125 + return opt(option, selected ? 'group-active-selected' : 'group-active', options); 126 + } 127 + if (active && selected) { 128 + return opt(option, 'active-selected', options); 129 + } 130 + if (selected) { 131 + return opt(option, 'selected', options); 132 + } 133 + return opt(option, active ? 'active' : 'inactive', options); 134 + }; 135 + 111 136 switch (this.state) { 112 137 case 'submit': { 113 138 const selectedOptions = this.options ··· 127 152 }`; 128 153 } 129 154 case 'error': { 155 + const guidePrefix = hasGuide ? `${styleText('yellow', S_BAR)} ` : ''; 130 156 const footer = this.error 131 157 .split('\n') 132 158 .map((ln, i) => ··· 135 161 : ` ${ln}` 136 162 ) 137 163 .join('\n'); 138 - return `${title}${hasGuide ? `${styleText('yellow', S_BAR)} ` : ''}${this.options 139 - .map((option, i, options) => { 140 - const selected = 141 - value.includes(option.value) || 142 - (option.group === true && this.isGroupSelected(`${option.value}`)); 143 - const active = i === this.cursor; 144 - const groupActive = 145 - !active && 146 - typeof option.group === 'string' && 147 - this.options[this.cursor].value === option.group; 148 - if (groupActive) { 149 - return opt(option, selected ? 'group-active-selected' : 'group-active', options); 150 - } 151 - if (active && selected) { 152 - return opt(option, 'active-selected', options); 153 - } 154 - if (selected) { 155 - return opt(option, 'selected', options); 156 - } 157 - return opt(option, active ? 'active' : 'inactive', options); 158 - }) 159 - .join(`\n${hasGuide ? `${styleText('yellow', S_BAR)} ` : ''}`)}\n${footer}\n`; 164 + // Calculate rowPadding: title lines + footer lines (error message + trailing newline) 165 + const titleLineCount = title.split('\n').length; 166 + const footerLineCount = footer.split('\n').length + 1; // footer + trailing newline 167 + const optionsText = limitOptions({ 168 + output: opts.output, 169 + options: this.options, 170 + cursor: this.cursor, 171 + maxItems: opts.maxItems, 172 + columnPadding: guidePrefix.length, 173 + rowPadding: titleLineCount + footerLineCount, 174 + style: styleOption, 175 + }).join(`\n${guidePrefix}`); 176 + return `${title}${guidePrefix}${optionsText}\n${footer}\n`; 160 177 } 161 178 default: { 162 - const optionsText = this.options 163 - .map((option, i, options) => { 164 - const selected = 165 - value.includes(option.value) || 166 - (option.group === true && this.isGroupSelected(`${option.value}`)); 167 - const active = i === this.cursor; 168 - const groupActive = 169 - !active && 170 - typeof option.group === 'string' && 171 - this.options[this.cursor].value === option.group; 172 - let optionText = ''; 173 - if (groupActive) { 174 - optionText = opt( 175 - option, 176 - selected ? 'group-active-selected' : 'group-active', 177 - options 178 - ); 179 - } else if (active && selected) { 180 - optionText = opt(option, 'active-selected', options); 181 - } else if (selected) { 182 - optionText = opt(option, 'selected', options); 183 - } else { 184 - optionText = opt(option, active ? 'active' : 'inactive', options); 185 - } 186 - const prefix = i !== 0 && !optionText.startsWith('\n') ? ' ' : ''; 187 - return `${prefix}${optionText}`; 188 - }) 189 - .join(`\n${hasGuide ? styleText('cyan', S_BAR) : ''}`); 190 - const optionsPrefix = optionsText.startsWith('\n') ? '' : ' '; 191 - return `${title}${hasGuide ? styleText('cyan', S_BAR) : ''}${optionsPrefix}${optionsText}\n${ 179 + const guidePrefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : ''; 180 + // Calculate rowPadding: title lines + footer lines (S_BAR_END + trailing newline) 181 + const titleLineCount = title.split('\n').length; 182 + const footerLineCount = (hasGuide ? 1 : 0) + 1; // guide line + trailing newline 183 + const optionsText = limitOptions({ 184 + output: opts.output, 185 + options: this.options, 186 + cursor: this.cursor, 187 + maxItems: opts.maxItems, 188 + columnPadding: guidePrefix.length, 189 + rowPadding: titleLineCount + footerLineCount, 190 + style: styleOption, 191 + }).join(`\n${guidePrefix}`); 192 + return `${title}${guidePrefix}${optionsText}\n${ 192 193 hasGuide ? styleText('cyan', S_BAR_END) : '' 193 194 }\n`; 194 195 }
+658 -44
packages/prompts/test/__snapshots__/group-multi-select.test.ts.snap
··· 252 252 [ 253 253 "<cursor.hide>", 254 254 "◆ foo 255 - ◻ group1 256 - │ ◻ group1value0 257 - └ ◻ group1value1 255 + ◻ group1 256 + │ ◻ group1value0 257 + └ ◻ group1value1 258 258 259 259 ", 260 260 "<cursor.backward count=999><cursor.up count=5>", 261 261 "<cursor.down count=1>", 262 262 "<erase.down>", 263 - " ◻ group1 264 - │ ◻ group1value0 265 - └ ◻ group1value1 263 + "◻ group1 264 + │ ◻ group1value0 265 + └ ◻ group1value1 266 266 267 267 ", 268 268 "<cursor.backward count=999><cursor.up count=5>", 269 269 "<cursor.down count=2>", 270 270 "<erase.line><cursor.left count=1>", 271 - " │ ◼ group1value0", 271 + "│ ◼ group1value0", 272 272 "<cursor.down count=3>", 273 273 "<cursor.backward count=999><cursor.up count=5>", 274 274 "<erase.down>", ··· 325 325 "<cursor.hide>", 326 326 "│ 327 327 ◆ foo 328 - │ 329 - │ 328 + │ 329 + │ 330 330 │ ◻ group1 331 331 │ └ ◻ group1value0 332 - │ 333 - │ 332 + │ 333 + │ 334 334 │ ◻ group2 335 335 │ └ ◻ group2value0 336 336 └ ··· 340 340 "<erase.down>", 341 341 "│ ◻ group1 342 342 │ └ ◻ group1value0 343 - │ 344 - │ 343 + │ 344 + │ 345 345 │ ◻ group2 346 346 │ └ ◻ group2value0 347 347 └ ··· 351 351 "<erase.down>", 352 352 "│ ◼ group1 353 353 │ └ ◼ group1value0 354 - │ 355 - │ 354 + │ 355 + │ 356 356 │ ◻ group2 357 357 │ └ ◻ group2value0 358 358 └ ··· 389 389 ] 390 390 `; 391 391 392 + exports[`groupMultiselect (isCI = false) > maxItems renders a sliding window 1`] = ` 393 + [ 394 + "<cursor.hide>", 395 + "│ 396 + ◆ foo 397 + │ ◻ group1 398 + │ │ ◻ group1value0 399 + │ │ ◻ group1value1 400 + │ │ ◻ group1value2 401 + │ │ ◻ group1value3 402 + │ ... 403 + └ 404 + ", 405 + "<cursor.backward count=999><cursor.up count=9>", 406 + "<cursor.down count=2>", 407 + "<erase.down>", 408 + "│ ◻ group1 409 + │ │ ◻ group1value0 410 + │ │ ◻ group1value1 411 + │ │ ◻ group1value2 412 + │ │ ◻ group1value3 413 + │ ... 414 + └ 415 + ", 416 + "<cursor.backward count=999><cursor.up count=9>", 417 + "<cursor.down count=3>", 418 + "<erase.down>", 419 + "│ │ ◻ group1value0 420 + │ │ ◻ group1value1 421 + │ │ ◻ group1value2 422 + │ │ ◻ group1value3 423 + │ ... 424 + └ 425 + ", 426 + "<cursor.backward count=999><cursor.up count=9>", 427 + "<cursor.down count=4>", 428 + "<erase.down>", 429 + "│ │ ◻ group1value1 430 + │ │ ◻ group1value2 431 + │ │ ◻ group1value3 432 + │ ... 433 + └ 434 + ", 435 + "<cursor.backward count=999><cursor.up count=9>", 436 + "<cursor.down count=2>", 437 + "<erase.down>", 438 + "│ ... 439 + │ │ ◻ group1value1 440 + │ │ ◻ group1value2 441 + │ │ ◻ group1value3 442 + │ │ ◻ group1value4 443 + │ ... 444 + └ 445 + ", 446 + "<cursor.backward count=999><cursor.up count=9>", 447 + "<cursor.down count=3>", 448 + "<erase.down>", 449 + "│ │ ◻ group1value2 450 + │ │ ◻ group1value3 451 + │ │ ◻ group1value4 452 + │ └ ◻ group1value5 453 + │ ... 454 + └ 455 + ", 456 + "<cursor.backward count=999><cursor.up count=9>", 457 + "<cursor.down count=3>", 458 + "<erase.down>", 459 + "│ │ ◻ group1value3 460 + │ │ ◻ group1value4 461 + │ └ ◻ group1value5 462 + │ ◻ group2 463 + │ ... 464 + └ 465 + ", 466 + "<cursor.backward count=999><cursor.up count=9>", 467 + "<cursor.down count=5>", 468 + "<erase.line><cursor.left count=1>", 469 + "│ └ ◼ group1value5", 470 + "<cursor.down count=4>", 471 + "<cursor.backward count=999><cursor.up count=9>", 472 + "<cursor.down count=1>", 473 + "<erase.down>", 474 + "◇ foo 475 + │ group1value5", 476 + " 477 + ", 478 + "<cursor.show>", 479 + ] 480 + `; 481 + 392 482 exports[`groupMultiselect (isCI = false) > renders error when nothing selected 1`] = ` 393 483 [ 394 484 "<cursor.hide>", ··· 536 626 ] 537 627 `; 538 628 629 + exports[`groupMultiselect (isCI = false) > sliding window loops downwards 1`] = ` 630 + [ 631 + "<cursor.hide>", 632 + "│ 633 + ◆ foo 634 + │ ◻ group1 635 + │ │ ◻ group1value0 636 + │ │ ◻ group1value1 637 + │ │ ◻ group1value2 638 + │ │ ◻ group1value3 639 + │ ... 640 + └ 641 + ", 642 + "<cursor.backward count=999><cursor.up count=9>", 643 + "<cursor.down count=2>", 644 + "<erase.down>", 645 + "│ ◻ group1 646 + │ │ ◻ group1value0 647 + │ │ ◻ group1value1 648 + │ │ ◻ group1value2 649 + │ │ ◻ group1value3 650 + │ ... 651 + └ 652 + ", 653 + "<cursor.backward count=999><cursor.up count=9>", 654 + "<cursor.down count=3>", 655 + "<erase.down>", 656 + "│ │ ◻ group1value0 657 + │ │ ◻ group1value1 658 + │ │ ◻ group1value2 659 + │ │ ◻ group1value3 660 + │ ... 661 + └ 662 + ", 663 + "<cursor.backward count=999><cursor.up count=9>", 664 + "<cursor.down count=4>", 665 + "<erase.down>", 666 + "│ │ ◻ group1value1 667 + │ │ ◻ group1value2 668 + │ │ ◻ group1value3 669 + │ ... 670 + └ 671 + ", 672 + "<cursor.backward count=999><cursor.up count=9>", 673 + "<cursor.down count=2>", 674 + "<erase.down>", 675 + "│ ... 676 + │ │ ◻ group1value1 677 + │ │ ◻ group1value2 678 + │ │ ◻ group1value3 679 + │ │ ◻ group1value4 680 + │ ... 681 + └ 682 + ", 683 + "<cursor.backward count=999><cursor.up count=9>", 684 + "<cursor.down count=3>", 685 + "<erase.down>", 686 + "│ │ ◻ group1value2 687 + │ │ ◻ group1value3 688 + │ │ ◻ group1value4 689 + │ └ ◻ group1value5 690 + │ ... 691 + └ 692 + ", 693 + "<cursor.backward count=999><cursor.up count=9>", 694 + "<cursor.down count=3>", 695 + "<erase.down>", 696 + "│ │ ◻ group1value3 697 + │ │ ◻ group1value4 698 + │ └ ◻ group1value5 699 + │ ◻ group2 700 + │ ... 701 + └ 702 + ", 703 + "<cursor.backward count=999><cursor.up count=9>", 704 + "<cursor.down count=3>", 705 + "<erase.down>", 706 + "│ │ ◻ group1value4 707 + │ └ ◻ group1value5 708 + │ ◻ group2 709 + │ │ ◻ group2value0 710 + │ ... 711 + └ 712 + ", 713 + "<cursor.backward count=999><cursor.up count=9>", 714 + "<cursor.down count=3>", 715 + "<erase.down>", 716 + "│ └ ◻ group1value5 717 + │ ◻ group2 718 + │ │ ◻ group2value0 719 + │ │ ◻ group2value1 720 + │ ... 721 + └ 722 + ", 723 + "<cursor.backward count=999><cursor.up count=9>", 724 + "<cursor.down count=3>", 725 + "<erase.down>", 726 + "│ ◻ group2 727 + │ │ ◻ group2value0 728 + │ │ ◻ group2value1 729 + │ │ ◻ group2value2 730 + │ ... 731 + └ 732 + ", 733 + "<cursor.backward count=999><cursor.up count=9>", 734 + "<cursor.down count=3>", 735 + "<erase.down>", 736 + "│ │ ◻ group2value0 737 + │ │ ◻ group2value1 738 + │ │ ◻ group2value2 739 + │ │ ◻ group2value3 740 + │ ... 741 + └ 742 + ", 743 + "<cursor.backward count=999><cursor.up count=9>", 744 + "<cursor.down count=3>", 745 + "<erase.down>", 746 + "│ │ ◻ group2value1 747 + │ │ ◻ group2value2 748 + │ │ ◻ group2value3 749 + │ │ ◻ group2value4 750 + │ └ ◻ group2value5 751 + └ 752 + ", 753 + "<cursor.backward count=999><cursor.up count=9>", 754 + "<cursor.down count=5>", 755 + "<erase.down>", 756 + "│ │ ◻ group2value3 757 + │ │ ◻ group2value4 758 + │ └ ◻ group2value5 759 + └ 760 + ", 761 + "<cursor.backward count=999><cursor.up count=9>", 762 + "<cursor.down count=6>", 763 + "<erase.down>", 764 + "│ │ ◻ group2value4 765 + │ └ ◻ group2value5 766 + └ 767 + ", 768 + "<cursor.backward count=999><cursor.up count=9>", 769 + "<cursor.down count=2>", 770 + "<erase.down>", 771 + "│ ◻ group1 772 + │ │ ◻ group1value0 773 + │ │ ◻ group1value1 774 + │ │ ◻ group1value2 775 + │ │ ◻ group1value3 776 + │ ... 777 + └ 778 + ", 779 + "<cursor.backward count=999><cursor.up count=9>", 780 + "<cursor.down count=2>", 781 + "<erase.down>", 782 + "│ ◻ group1 783 + │ │ ◻ group1value0 784 + │ │ ◻ group1value1 785 + │ │ ◻ group1value2 786 + │ │ ◻ group1value3 787 + │ ... 788 + └ 789 + ", 790 + "<cursor.backward count=999><cursor.up count=9>", 791 + "<cursor.down count=3>", 792 + "<erase.line><cursor.left count=1>", 793 + "│ │ ◼ group1value0", 794 + "<cursor.down count=6>", 795 + "<cursor.backward count=999><cursor.up count=9>", 796 + "<cursor.down count=1>", 797 + "<erase.down>", 798 + "◇ foo 799 + │ group1value0", 800 + " 801 + ", 802 + "<cursor.show>", 803 + ] 804 + `; 805 + 806 + exports[`groupMultiselect (isCI = false) > sliding window loops upwards 1`] = ` 807 + [ 808 + "<cursor.hide>", 809 + "│ 810 + ◆ foo 811 + │ ◻ group1 812 + │ │ ◻ group1value0 813 + │ │ ◻ group1value1 814 + │ │ ◻ group1value2 815 + │ │ ◻ group1value3 816 + │ ... 817 + └ 818 + ", 819 + "<cursor.backward count=999><cursor.up count=9>", 820 + "<cursor.down count=2>", 821 + "<erase.down>", 822 + "│ ... 823 + │ │ ◻ group2value1 824 + │ │ ◻ group2value2 825 + │ │ ◻ group2value3 826 + │ │ ◻ group2value4 827 + │ └ ◻ group2value5 828 + └ 829 + ", 830 + "<cursor.backward count=999><cursor.up count=9>", 831 + "<cursor.down count=7>", 832 + "<erase.line><cursor.left count=1>", 833 + "│ └ ◼ group2value5", 834 + "<cursor.down count=2>", 835 + "<cursor.backward count=999><cursor.up count=9>", 836 + "<cursor.down count=1>", 837 + "<erase.down>", 838 + "◇ foo 839 + │ group2value5", 840 + " 841 + ", 842 + "<cursor.show>", 843 + ] 844 + `; 845 + 539 846 exports[`groupMultiselect (isCI = false) > values can be non-primitive 1`] = ` 540 847 [ 541 848 "<cursor.hide>", ··· 574 881 [ 575 882 "<cursor.hide>", 576 883 "◆ foo 577 - ◻ group1 578 - │ ◻ group1value0 579 - └ ◻ group1value1 884 + ◻ group1 885 + │ ◻ group1value0 886 + └ ◻ group1value1 580 887 581 888 ", 582 889 "<cursor.backward count=999><cursor.up count=5>", 583 890 "<cursor.down count=1>", 584 891 "<erase.down>", 585 - " ◻ group1 586 - │ ◻ group1value0 587 - └ ◻ group1value1 892 + "◻ group1 893 + │ ◻ group1value0 894 + └ ◻ group1value1 588 895 589 896 ", 590 897 "<cursor.backward count=999><cursor.up count=5>", 591 898 "<cursor.down count=2>", 592 899 "<erase.line><cursor.left count=1>", 593 - " │ ◼ group1value0", 900 + "│ ◼ group1value0", 594 901 "<cursor.down count=3>", 595 902 "<cursor.backward count=999><cursor.up count=5>", 596 903 "<erase.down>", ··· 854 1161 [ 855 1162 "<cursor.hide>", 856 1163 "◆ foo 857 - ◻ group1 858 - │ ◻ group1value0 859 - └ ◻ group1value1 1164 + ◻ group1 1165 + │ ◻ group1value0 1166 + └ ◻ group1value1 860 1167 861 1168 ", 862 1169 "<cursor.backward count=999><cursor.up count=5>", 863 1170 "<cursor.down count=1>", 864 1171 "<erase.down>", 865 - " ◻ group1 866 - │ ◻ group1value0 867 - └ ◻ group1value1 1172 + "◻ group1 1173 + │ ◻ group1value0 1174 + └ ◻ group1value1 868 1175 869 1176 ", 870 1177 "<cursor.backward count=999><cursor.up count=5>", 871 1178 "<cursor.down count=2>", 872 1179 "<erase.line><cursor.left count=1>", 873 - " │ ◼ group1value0", 1180 + "│ ◼ group1value0", 874 1181 "<cursor.down count=3>", 875 1182 "<cursor.backward count=999><cursor.up count=5>", 876 1183 "<erase.down>", ··· 927 1234 "<cursor.hide>", 928 1235 "│ 929 1236 ◆ foo 930 - │ 931 - │ 1237 + │ 1238 + │ 932 1239 │ ◻ group1 933 1240 │ └ ◻ group1value0 934 - │ 935 - │ 1241 + │ 1242 + │ 936 1243 │ ◻ group2 937 1244 │ └ ◻ group2value0 938 1245 └ ··· 942 1249 "<erase.down>", 943 1250 "│ ◻ group1 944 1251 │ └ ◻ group1value0 945 - │ 946 - │ 1252 + │ 1253 + │ 947 1254 │ ◻ group2 948 1255 │ └ ◻ group2value0 949 1256 └ ··· 953 1260 "<erase.down>", 954 1261 "│ ◼ group1 955 1262 │ └ ◼ group1value0 956 - │ 957 - │ 1263 + │ 1264 + │ 958 1265 │ ◻ group2 959 1266 │ └ ◻ group2value0 960 1267 └ ··· 991 1298 ] 992 1299 `; 993 1300 1301 + exports[`groupMultiselect (isCI = true) > maxItems renders a sliding window 1`] = ` 1302 + [ 1303 + "<cursor.hide>", 1304 + "│ 1305 + ◆ foo 1306 + │ ◻ group1 1307 + │ │ ◻ group1value0 1308 + │ │ ◻ group1value1 1309 + │ │ ◻ group1value2 1310 + │ │ ◻ group1value3 1311 + │ ... 1312 + └ 1313 + ", 1314 + "<cursor.backward count=999><cursor.up count=9>", 1315 + "<cursor.down count=2>", 1316 + "<erase.down>", 1317 + "│ ◻ group1 1318 + │ │ ◻ group1value0 1319 + │ │ ◻ group1value1 1320 + │ │ ◻ group1value2 1321 + │ │ ◻ group1value3 1322 + │ ... 1323 + └ 1324 + ", 1325 + "<cursor.backward count=999><cursor.up count=9>", 1326 + "<cursor.down count=3>", 1327 + "<erase.down>", 1328 + "│ │ ◻ group1value0 1329 + │ │ ◻ group1value1 1330 + │ │ ◻ group1value2 1331 + │ │ ◻ group1value3 1332 + │ ... 1333 + └ 1334 + ", 1335 + "<cursor.backward count=999><cursor.up count=9>", 1336 + "<cursor.down count=4>", 1337 + "<erase.down>", 1338 + "│ │ ◻ group1value1 1339 + │ │ ◻ group1value2 1340 + │ │ ◻ group1value3 1341 + │ ... 1342 + └ 1343 + ", 1344 + "<cursor.backward count=999><cursor.up count=9>", 1345 + "<cursor.down count=2>", 1346 + "<erase.down>", 1347 + "│ ... 1348 + │ │ ◻ group1value1 1349 + │ │ ◻ group1value2 1350 + │ │ ◻ group1value3 1351 + │ │ ◻ group1value4 1352 + │ ... 1353 + └ 1354 + ", 1355 + "<cursor.backward count=999><cursor.up count=9>", 1356 + "<cursor.down count=3>", 1357 + "<erase.down>", 1358 + "│ │ ◻ group1value2 1359 + │ │ ◻ group1value3 1360 + │ │ ◻ group1value4 1361 + │ └ ◻ group1value5 1362 + │ ... 1363 + └ 1364 + ", 1365 + "<cursor.backward count=999><cursor.up count=9>", 1366 + "<cursor.down count=3>", 1367 + "<erase.down>", 1368 + "│ │ ◻ group1value3 1369 + │ │ ◻ group1value4 1370 + │ └ ◻ group1value5 1371 + │ ◻ group2 1372 + │ ... 1373 + └ 1374 + ", 1375 + "<cursor.backward count=999><cursor.up count=9>", 1376 + "<cursor.down count=5>", 1377 + "<erase.line><cursor.left count=1>", 1378 + "│ └ ◼ group1value5", 1379 + "<cursor.down count=4>", 1380 + "<cursor.backward count=999><cursor.up count=9>", 1381 + "<cursor.down count=1>", 1382 + "<erase.down>", 1383 + "◇ foo 1384 + │ group1value5", 1385 + " 1386 + ", 1387 + "<cursor.show>", 1388 + ] 1389 + `; 1390 + 994 1391 exports[`groupMultiselect (isCI = true) > renders error when nothing selected 1`] = ` 995 1392 [ 996 1393 "<cursor.hide>", ··· 1138 1535 ] 1139 1536 `; 1140 1537 1538 + exports[`groupMultiselect (isCI = true) > sliding window loops downwards 1`] = ` 1539 + [ 1540 + "<cursor.hide>", 1541 + "│ 1542 + ◆ foo 1543 + │ ◻ group1 1544 + │ │ ◻ group1value0 1545 + │ │ ◻ group1value1 1546 + │ │ ◻ group1value2 1547 + │ │ ◻ group1value3 1548 + │ ... 1549 + └ 1550 + ", 1551 + "<cursor.backward count=999><cursor.up count=9>", 1552 + "<cursor.down count=2>", 1553 + "<erase.down>", 1554 + "│ ◻ group1 1555 + │ │ ◻ group1value0 1556 + │ │ ◻ group1value1 1557 + │ │ ◻ group1value2 1558 + │ │ ◻ group1value3 1559 + │ ... 1560 + └ 1561 + ", 1562 + "<cursor.backward count=999><cursor.up count=9>", 1563 + "<cursor.down count=3>", 1564 + "<erase.down>", 1565 + "│ │ ◻ group1value0 1566 + │ │ ◻ group1value1 1567 + │ │ ◻ group1value2 1568 + │ │ ◻ group1value3 1569 + │ ... 1570 + └ 1571 + ", 1572 + "<cursor.backward count=999><cursor.up count=9>", 1573 + "<cursor.down count=4>", 1574 + "<erase.down>", 1575 + "│ │ ◻ group1value1 1576 + │ │ ◻ group1value2 1577 + │ │ ◻ group1value3 1578 + │ ... 1579 + └ 1580 + ", 1581 + "<cursor.backward count=999><cursor.up count=9>", 1582 + "<cursor.down count=2>", 1583 + "<erase.down>", 1584 + "│ ... 1585 + │ │ ◻ group1value1 1586 + │ │ ◻ group1value2 1587 + │ │ ◻ group1value3 1588 + │ │ ◻ group1value4 1589 + │ ... 1590 + └ 1591 + ", 1592 + "<cursor.backward count=999><cursor.up count=9>", 1593 + "<cursor.down count=3>", 1594 + "<erase.down>", 1595 + "│ │ ◻ group1value2 1596 + │ │ ◻ group1value3 1597 + │ │ ◻ group1value4 1598 + │ └ ◻ group1value5 1599 + │ ... 1600 + └ 1601 + ", 1602 + "<cursor.backward count=999><cursor.up count=9>", 1603 + "<cursor.down count=3>", 1604 + "<erase.down>", 1605 + "│ │ ◻ group1value3 1606 + │ │ ◻ group1value4 1607 + │ └ ◻ group1value5 1608 + │ ◻ group2 1609 + │ ... 1610 + └ 1611 + ", 1612 + "<cursor.backward count=999><cursor.up count=9>", 1613 + "<cursor.down count=3>", 1614 + "<erase.down>", 1615 + "│ │ ◻ group1value4 1616 + │ └ ◻ group1value5 1617 + │ ◻ group2 1618 + │ │ ◻ group2value0 1619 + │ ... 1620 + └ 1621 + ", 1622 + "<cursor.backward count=999><cursor.up count=9>", 1623 + "<cursor.down count=3>", 1624 + "<erase.down>", 1625 + "│ └ ◻ group1value5 1626 + │ ◻ group2 1627 + │ │ ◻ group2value0 1628 + │ │ ◻ group2value1 1629 + │ ... 1630 + └ 1631 + ", 1632 + "<cursor.backward count=999><cursor.up count=9>", 1633 + "<cursor.down count=3>", 1634 + "<erase.down>", 1635 + "│ ◻ group2 1636 + │ │ ◻ group2value0 1637 + │ │ ◻ group2value1 1638 + │ │ ◻ group2value2 1639 + │ ... 1640 + └ 1641 + ", 1642 + "<cursor.backward count=999><cursor.up count=9>", 1643 + "<cursor.down count=3>", 1644 + "<erase.down>", 1645 + "│ │ ◻ group2value0 1646 + │ │ ◻ group2value1 1647 + │ │ ◻ group2value2 1648 + │ │ ◻ group2value3 1649 + │ ... 1650 + └ 1651 + ", 1652 + "<cursor.backward count=999><cursor.up count=9>", 1653 + "<cursor.down count=3>", 1654 + "<erase.down>", 1655 + "│ │ ◻ group2value1 1656 + │ │ ◻ group2value2 1657 + │ │ ◻ group2value3 1658 + │ │ ◻ group2value4 1659 + │ └ ◻ group2value5 1660 + └ 1661 + ", 1662 + "<cursor.backward count=999><cursor.up count=9>", 1663 + "<cursor.down count=5>", 1664 + "<erase.down>", 1665 + "│ │ ◻ group2value3 1666 + │ │ ◻ group2value4 1667 + │ └ ◻ group2value5 1668 + └ 1669 + ", 1670 + "<cursor.backward count=999><cursor.up count=9>", 1671 + "<cursor.down count=6>", 1672 + "<erase.down>", 1673 + "│ │ ◻ group2value4 1674 + │ └ ◻ group2value5 1675 + └ 1676 + ", 1677 + "<cursor.backward count=999><cursor.up count=9>", 1678 + "<cursor.down count=2>", 1679 + "<erase.down>", 1680 + "│ ◻ group1 1681 + │ │ ◻ group1value0 1682 + │ │ ◻ group1value1 1683 + │ │ ◻ group1value2 1684 + │ │ ◻ group1value3 1685 + │ ... 1686 + └ 1687 + ", 1688 + "<cursor.backward count=999><cursor.up count=9>", 1689 + "<cursor.down count=2>", 1690 + "<erase.down>", 1691 + "│ ◻ group1 1692 + │ │ ◻ group1value0 1693 + │ │ ◻ group1value1 1694 + │ │ ◻ group1value2 1695 + │ │ ◻ group1value3 1696 + │ ... 1697 + └ 1698 + ", 1699 + "<cursor.backward count=999><cursor.up count=9>", 1700 + "<cursor.down count=3>", 1701 + "<erase.line><cursor.left count=1>", 1702 + "│ │ ◼ group1value0", 1703 + "<cursor.down count=6>", 1704 + "<cursor.backward count=999><cursor.up count=9>", 1705 + "<cursor.down count=1>", 1706 + "<erase.down>", 1707 + "◇ foo 1708 + │ group1value0", 1709 + " 1710 + ", 1711 + "<cursor.show>", 1712 + ] 1713 + `; 1714 + 1715 + exports[`groupMultiselect (isCI = true) > sliding window loops upwards 1`] = ` 1716 + [ 1717 + "<cursor.hide>", 1718 + "│ 1719 + ◆ foo 1720 + │ ◻ group1 1721 + │ │ ◻ group1value0 1722 + │ │ ◻ group1value1 1723 + │ │ ◻ group1value2 1724 + │ │ ◻ group1value3 1725 + │ ... 1726 + └ 1727 + ", 1728 + "<cursor.backward count=999><cursor.up count=9>", 1729 + "<cursor.down count=2>", 1730 + "<erase.down>", 1731 + "│ ... 1732 + │ │ ◻ group2value1 1733 + │ │ ◻ group2value2 1734 + │ │ ◻ group2value3 1735 + │ │ ◻ group2value4 1736 + │ └ ◻ group2value5 1737 + └ 1738 + ", 1739 + "<cursor.backward count=999><cursor.up count=9>", 1740 + "<cursor.down count=7>", 1741 + "<erase.line><cursor.left count=1>", 1742 + "│ └ ◼ group2value5", 1743 + "<cursor.down count=2>", 1744 + "<cursor.backward count=999><cursor.up count=9>", 1745 + "<cursor.down count=1>", 1746 + "<erase.down>", 1747 + "◇ foo 1748 + │ group2value5", 1749 + " 1750 + ", 1751 + "<cursor.show>", 1752 + ] 1753 + `; 1754 + 1141 1755 exports[`groupMultiselect (isCI = true) > values can be non-primitive 1`] = ` 1142 1756 [ 1143 1757 "<cursor.hide>", ··· 1176 1790 [ 1177 1791 "<cursor.hide>", 1178 1792 "◆ foo 1179 - ◻ group1 1180 - │ ◻ group1value0 1181 - └ ◻ group1value1 1793 + ◻ group1 1794 + │ ◻ group1value0 1795 + └ ◻ group1value1 1182 1796 1183 1797 ", 1184 1798 "<cursor.backward count=999><cursor.up count=5>", 1185 1799 "<cursor.down count=1>", 1186 1800 "<erase.down>", 1187 - " ◻ group1 1188 - │ ◻ group1value0 1189 - └ ◻ group1value1 1801 + "◻ group1 1802 + │ ◻ group1value0 1803 + └ ◻ group1value1 1190 1804 1191 1805 ", 1192 1806 "<cursor.backward count=999><cursor.up count=5>", 1193 1807 "<cursor.down count=2>", 1194 1808 "<erase.line><cursor.left count=1>", 1195 - " │ ◼ group1value0", 1809 + "│ ◼ group1value0", 1196 1810 "<cursor.down count=3>", 1197 1811 "<cursor.backward count=999><cursor.up count=5>", 1198 1812 "<erase.down>",
+70
packages/prompts/test/group-multi-select.test.ts
··· 306 306 expect(output.buffer).toMatchSnapshot(); 307 307 }); 308 308 309 + test('maxItems renders a sliding window', async () => { 310 + const result = prompts.groupMultiselect({ 311 + message: 'foo', 312 + input, 313 + output, 314 + options: { 315 + group1: [...Array(6).keys()].map((k) => ({ value: `group1value${k}` })), 316 + group2: [...Array(6).keys()].map((k) => ({ value: `group2value${k}` })), 317 + }, 318 + maxItems: 6, 319 + }); 320 + 321 + for (let i = 0; i < 6; i++) { 322 + input.emit('keypress', '', { name: 'down' }); 323 + } 324 + input.emit('keypress', '', { name: 'space' }); 325 + input.emit('keypress', '', { name: 'return' }); 326 + 327 + const value = await result; 328 + 329 + expect(value).toEqual(['group1value5']); 330 + expect(output.buffer).toMatchSnapshot(); 331 + }); 332 + 333 + test('sliding window loops upwards', async () => { 334 + const result = prompts.groupMultiselect({ 335 + message: 'foo', 336 + input, 337 + output, 338 + options: { 339 + group1: [...Array(6).keys()].map((k) => ({ value: `group1value${k}` })), 340 + group2: [...Array(6).keys()].map((k) => ({ value: `group2value${k}` })), 341 + }, 342 + maxItems: 6, 343 + }); 344 + 345 + input.emit('keypress', '', { name: 'up' }); 346 + input.emit('keypress', '', { name: 'space' }); 347 + input.emit('keypress', '', { name: 'return' }); 348 + 349 + const value = await result; 350 + 351 + expect(value).toEqual(['group2value5']); 352 + expect(output.buffer).toMatchSnapshot(); 353 + }); 354 + 355 + test('sliding window loops downwards', async () => { 356 + const result = prompts.groupMultiselect({ 357 + message: 'foo', 358 + input, 359 + output, 360 + options: { 361 + group1: [...Array(6).keys()].map((k) => ({ value: `group1value${k}` })), 362 + group2: [...Array(6).keys()].map((k) => ({ value: `group2value${k}` })), 363 + }, 364 + maxItems: 6, 365 + }); 366 + 367 + for (let i = 0; i < 15; i++) { 368 + input.emit('keypress', '', { name: 'down' }); 369 + } 370 + input.emit('keypress', '', { name: 'space' }); 371 + input.emit('keypress', '', { name: 'return' }); 372 + 373 + const value = await result; 374 + 375 + expect(value).toEqual(['group1value0']); 376 + expect(output.buffer).toMatchSnapshot(); 377 + }); 378 + 309 379 describe('groupSpacing', () => { 310 380 test('renders spaced groups', async () => { 311 381 const result = prompts.groupMultiselect({