@inquirer/select 1.2.11 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -9,73 +9,74 @@ Object.defineProperty(exports, "Separator", { enumerable: true, get: function ()
9
9
  const chalk_1 = __importDefault(require("chalk"));
10
10
  const figures_1 = __importDefault(require("figures"));
11
11
  const ansi_escapes_1 = __importDefault(require("ansi-escapes"));
12
- function isSelectableChoice(choice) {
13
- return choice != null && !core_1.Separator.isSeparator(choice) && !choice.disabled;
12
+ function isSelectable(item) {
13
+ return !core_1.Separator.isSeparator(item) && !item.disabled;
14
+ }
15
+ function renderItem({ item, isActive }) {
16
+ if (core_1.Separator.isSeparator(item)) {
17
+ return ` ${item.separator}`;
18
+ }
19
+ const line = item.name || item.value;
20
+ if (item.disabled) {
21
+ const disabledLabel = typeof item.disabled === 'string' ? item.disabled : '(disabled)';
22
+ return chalk_1.default.dim(`- ${line} ${disabledLabel}`);
23
+ }
24
+ const color = isActive ? chalk_1.default.cyan : (x) => x;
25
+ const prefix = isActive ? figures_1.default.pointer : ` `;
26
+ return color(`${prefix} ${line}`);
14
27
  }
15
28
  exports.default = (0, core_1.createPrompt)((config, done) => {
16
- const { choices } = config;
29
+ const { choices: items, loop = true, pageSize } = config;
17
30
  const firstRender = (0, core_1.useRef)(true);
18
31
  const prefix = (0, core_1.usePrefix)();
19
32
  const [status, setStatus] = (0, core_1.useState)('pending');
20
- const [cursorPosition, setCursorPos] = (0, core_1.useState)(() => {
21
- const startIndex = choices.findIndex(isSelectableChoice);
22
- if (startIndex < 0) {
33
+ const bounds = (0, core_1.useMemo)(() => {
34
+ const first = items.findIndex(isSelectable);
35
+ // TODO: Replace with `findLastIndex` when it's available.
36
+ const last = items.length - 1 - [...items].reverse().findIndex(isSelectable);
37
+ if (first < 0)
23
38
  throw new Error('[select prompt] No selectable choices. All choices are disabled.');
24
- }
25
- return startIndex;
26
- });
39
+ return { first, last };
40
+ }, [items]);
41
+ const [active, setActive] = (0, core_1.useState)(bounds.first);
27
42
  // Safe to assume the cursor position always point to a Choice.
28
- const selectedChoice = choices[cursorPosition];
43
+ const selectedChoice = items[active];
29
44
  (0, core_1.useKeypress)((key) => {
30
45
  if ((0, core_1.isEnterKey)(key)) {
31
46
  setStatus('done');
32
47
  done(selectedChoice.value);
33
48
  }
34
49
  else if ((0, core_1.isUpKey)(key) || (0, core_1.isDownKey)(key)) {
35
- let newCursorPosition = cursorPosition;
50
+ if (!loop && active === bounds.first && (0, core_1.isUpKey)(key))
51
+ return;
52
+ if (!loop && active === bounds.last && (0, core_1.isDownKey)(key))
53
+ return;
36
54
  const offset = (0, core_1.isUpKey)(key) ? -1 : 1;
37
- let selectedOption;
38
- while (!isSelectableChoice(selectedOption)) {
39
- newCursorPosition =
40
- (newCursorPosition + offset + choices.length) % choices.length;
41
- selectedOption = choices[newCursorPosition];
42
- }
43
- setCursorPos(newCursorPosition);
55
+ let next = active;
56
+ do {
57
+ next = (next + offset + items.length) % items.length;
58
+ } while (!isSelectable(items[next]));
59
+ setActive(next);
44
60
  }
45
61
  else if ((0, core_1.isNumberKey)(key)) {
46
- // Adjust index to start at 1
47
- const newCursorPosition = Number(key.name) - 1;
48
- // Abort if the choice doesn't exists or if disabled
49
- if (!isSelectableChoice(choices[newCursorPosition])) {
62
+ const position = Number(key.name) - 1;
63
+ const item = items[position];
64
+ if (item == null || !isSelectable(item))
50
65
  return;
51
- }
52
- setCursorPos(newCursorPosition);
66
+ setActive(position);
53
67
  }
54
68
  });
55
69
  let message = chalk_1.default.bold(config.message);
56
70
  if (firstRender.current) {
57
- message += chalk_1.default.dim(' (Use arrow keys)');
58
71
  firstRender.current = false;
72
+ message += chalk_1.default.dim(' (Use arrow keys)');
59
73
  }
60
- const allChoices = choices
61
- .map((choice, index) => {
62
- if (core_1.Separator.isSeparator(choice)) {
63
- return ` ${choice.separator}`;
64
- }
65
- const line = choice.name || choice.value;
66
- if (choice.disabled) {
67
- const disabledLabel = typeof choice.disabled === 'string' ? choice.disabled : '(disabled)';
68
- return chalk_1.default.dim(`- ${line} ${disabledLabel}`);
69
- }
70
- if (index === cursorPosition) {
71
- return chalk_1.default.cyan(`${figures_1.default.pointer} ${line}`);
72
- }
73
- return ` ${line}`;
74
- })
75
- .join('\n');
76
- const windowedChoices = (0, core_1.usePagination)(allChoices, {
77
- active: cursorPosition,
78
- pageSize: config.pageSize,
74
+ const page = (0, core_1.usePagination)({
75
+ items,
76
+ active,
77
+ renderItem,
78
+ pageSize,
79
+ loop,
79
80
  });
80
81
  if (status === 'done') {
81
82
  return `${prefix} ${message} ${chalk_1.default.cyan(selectedChoice.name || selectedChoice.value)}`;
@@ -83,5 +84,5 @@ exports.default = (0, core_1.createPrompt)((config, done) => {
83
84
  const choiceDescription = selectedChoice.description
84
85
  ? `\n${selectedChoice.description}`
85
86
  : ``;
86
- return `${prefix} ${message}\n${windowedChoices}${choiceDescription}${ansi_escapes_1.default.cursorHide}`;
87
+ return `${prefix} ${message}\n${page}${choiceDescription}${ansi_escapes_1.default.cursorHide}`;
87
88
  });
@@ -10,6 +10,7 @@ declare const _default: <Value extends unknown>(config: {
10
10
  message: string | Promise<string> | (() => Promise<string>);
11
11
  choices: readonly (Separator | Choice<Value>)[];
12
12
  pageSize?: number | undefined;
13
+ loop?: boolean | undefined;
13
14
  }, context?: import("@inquirer/type").Context | undefined) => import("@inquirer/type").CancelablePromise<Value>;
14
15
  export default _default;
15
16
  export { Separator };
@@ -1,74 +1,75 @@
1
- import { createPrompt, useState, useKeypress, usePrefix, usePagination, useRef, isEnterKey, isUpKey, isDownKey, isNumberKey, Separator, } from '@inquirer/core';
1
+ import { createPrompt, useState, useKeypress, usePrefix, usePagination, useRef, useMemo, isEnterKey, isUpKey, isDownKey, isNumberKey, Separator, } from '@inquirer/core';
2
2
  import chalk from 'chalk';
3
3
  import figures from 'figures';
4
4
  import ansiEscapes from 'ansi-escapes';
5
- function isSelectableChoice(choice) {
6
- return choice != null && !Separator.isSeparator(choice) && !choice.disabled;
5
+ function isSelectable(item) {
6
+ return !Separator.isSeparator(item) && !item.disabled;
7
+ }
8
+ function renderItem({ item, isActive }) {
9
+ if (Separator.isSeparator(item)) {
10
+ return ` ${item.separator}`;
11
+ }
12
+ const line = item.name || item.value;
13
+ if (item.disabled) {
14
+ const disabledLabel = typeof item.disabled === 'string' ? item.disabled : '(disabled)';
15
+ return chalk.dim(`- ${line} ${disabledLabel}`);
16
+ }
17
+ const color = isActive ? chalk.cyan : (x) => x;
18
+ const prefix = isActive ? figures.pointer : ` `;
19
+ return color(`${prefix} ${line}`);
7
20
  }
8
21
  export default createPrompt((config, done) => {
9
- const { choices } = config;
22
+ const { choices: items, loop = true, pageSize } = config;
10
23
  const firstRender = useRef(true);
11
24
  const prefix = usePrefix();
12
25
  const [status, setStatus] = useState('pending');
13
- const [cursorPosition, setCursorPos] = useState(() => {
14
- const startIndex = choices.findIndex(isSelectableChoice);
15
- if (startIndex < 0) {
26
+ const bounds = useMemo(() => {
27
+ const first = items.findIndex(isSelectable);
28
+ // TODO: Replace with `findLastIndex` when it's available.
29
+ const last = items.length - 1 - [...items].reverse().findIndex(isSelectable);
30
+ if (first < 0)
16
31
  throw new Error('[select prompt] No selectable choices. All choices are disabled.');
17
- }
18
- return startIndex;
19
- });
32
+ return { first, last };
33
+ }, [items]);
34
+ const [active, setActive] = useState(bounds.first);
20
35
  // Safe to assume the cursor position always point to a Choice.
21
- const selectedChoice = choices[cursorPosition];
36
+ const selectedChoice = items[active];
22
37
  useKeypress((key) => {
23
38
  if (isEnterKey(key)) {
24
39
  setStatus('done');
25
40
  done(selectedChoice.value);
26
41
  }
27
42
  else if (isUpKey(key) || isDownKey(key)) {
28
- let newCursorPosition = cursorPosition;
43
+ if (!loop && active === bounds.first && isUpKey(key))
44
+ return;
45
+ if (!loop && active === bounds.last && isDownKey(key))
46
+ return;
29
47
  const offset = isUpKey(key) ? -1 : 1;
30
- let selectedOption;
31
- while (!isSelectableChoice(selectedOption)) {
32
- newCursorPosition =
33
- (newCursorPosition + offset + choices.length) % choices.length;
34
- selectedOption = choices[newCursorPosition];
35
- }
36
- setCursorPos(newCursorPosition);
48
+ let next = active;
49
+ do {
50
+ next = (next + offset + items.length) % items.length;
51
+ } while (!isSelectable(items[next]));
52
+ setActive(next);
37
53
  }
38
54
  else if (isNumberKey(key)) {
39
- // Adjust index to start at 1
40
- const newCursorPosition = Number(key.name) - 1;
41
- // Abort if the choice doesn't exists or if disabled
42
- if (!isSelectableChoice(choices[newCursorPosition])) {
55
+ const position = Number(key.name) - 1;
56
+ const item = items[position];
57
+ if (item == null || !isSelectable(item))
43
58
  return;
44
- }
45
- setCursorPos(newCursorPosition);
59
+ setActive(position);
46
60
  }
47
61
  });
48
62
  let message = chalk.bold(config.message);
49
63
  if (firstRender.current) {
50
- message += chalk.dim(' (Use arrow keys)');
51
64
  firstRender.current = false;
65
+ message += chalk.dim(' (Use arrow keys)');
52
66
  }
53
- const allChoices = choices
54
- .map((choice, index) => {
55
- if (Separator.isSeparator(choice)) {
56
- return ` ${choice.separator}`;
57
- }
58
- const line = choice.name || choice.value;
59
- if (choice.disabled) {
60
- const disabledLabel = typeof choice.disabled === 'string' ? choice.disabled : '(disabled)';
61
- return chalk.dim(`- ${line} ${disabledLabel}`);
62
- }
63
- if (index === cursorPosition) {
64
- return chalk.cyan(`${figures.pointer} ${line}`);
65
- }
66
- return ` ${line}`;
67
- })
68
- .join('\n');
69
- const windowedChoices = usePagination(allChoices, {
70
- active: cursorPosition,
71
- pageSize: config.pageSize,
67
+ const page = usePagination({
68
+ items,
69
+ active,
70
+ renderItem,
71
+ pageSize,
72
+ loop,
72
73
  });
73
74
  if (status === 'done') {
74
75
  return `${prefix} ${message} ${chalk.cyan(selectedChoice.name || selectedChoice.value)}`;
@@ -76,6 +77,6 @@ export default createPrompt((config, done) => {
76
77
  const choiceDescription = selectedChoice.description
77
78
  ? `\n${selectedChoice.description}`
78
79
  : ``;
79
- return `${prefix} ${message}\n${windowedChoices}${choiceDescription}${ansiEscapes.cursorHide}`;
80
+ return `${prefix} ${message}\n${page}${choiceDescription}${ansiEscapes.cursorHide}`;
80
81
  });
81
82
  export { Separator };
@@ -10,6 +10,7 @@ declare const _default: <Value extends unknown>(config: {
10
10
  message: string | Promise<string> | (() => Promise<string>);
11
11
  choices: readonly (Separator | Choice<Value>)[];
12
12
  pageSize?: number | undefined;
13
+ loop?: boolean | undefined;
13
14
  }, context?: import("@inquirer/type").Context | undefined) => import("@inquirer/type").CancelablePromise<Value>;
14
15
  export default _default;
15
16
  export { Separator };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inquirer/select",
3
- "version": "1.2.11",
3
+ "version": "1.3.0",
4
4
  "description": "Inquirer select/list prompt",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "typings": "./dist/cjs/types/index.d.ts",
@@ -54,14 +54,14 @@
54
54
  "license": "MIT",
55
55
  "homepage": "https://github.com/SBoudrias/Inquirer.js/blob/master/packages/select/README.md",
56
56
  "dependencies": {
57
- "@inquirer/core": "^5.0.0",
58
- "@inquirer/type": "^1.1.4",
57
+ "@inquirer/core": "^5.1.0",
58
+ "@inquirer/type": "^1.1.5",
59
59
  "ansi-escapes": "^4.3.2",
60
60
  "chalk": "^4.1.2",
61
61
  "figures": "^3.2.0"
62
62
  },
63
63
  "devDependencies": {
64
- "@inquirer/testing": "^2.1.6"
64
+ "@inquirer/testing": "^2.1.8"
65
65
  },
66
66
  "scripts": {
67
67
  "tsc": "yarn run tsc:esm && yarn run tsc:cjs",
@@ -86,5 +86,5 @@
86
86
  }
87
87
  }
88
88
  },
89
- "gitHead": "c2d1c5fdfd1029f78351fb04e06f1cfb29d55bb6"
89
+ "gitHead": "c88aaca660e58aa0fb079fe656c1004855e029da"
90
90
  }