Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: checkbox ts type prompt error #1229

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 110 additions & 121 deletions packages/checkbox/src/index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -10,147 +10,136 @@ import {
isNumberKey,
isEnterKey,
Paginator,
AsyncPromptConfig,
} from '@inquirer/core';
import type {} from '@inquirer/type';
import chalk from 'chalk';
import figures from 'figures';
import ansiEscapes from 'ansi-escapes';

export type Choice<Value> = {
type Choice = {
value: string;
name?: string;
value: Value;
disabled?: boolean | string;
checked?: boolean;
};

type Config<Value> = {
type CheckboxConfig = AsyncPromptConfig & {
prefix?: string;
pageSize?: number;
instructions?: string | boolean;
message: string;
choices: ReadonlyArray<Choice<Value>>;
choices: Choice[];
};

export default createPrompt(
<Value extends unknown>(
config: Config<Value>,
done: (value: Array<Value>) => void
): string => {
const { prefix = usePrefix(), instructions } = config;
const paginator = useRef(new Paginator()).current;

const [status, setStatus] = useState('pending');
const [choices, setChoices] = useState<Array<Choice<Value>>>(
config.choices.map((choice) => ({ ...choice }))
);
const [cursorPosition, setCursorPosition] = useState(0);
const [showHelpTip, setShowHelpTip] = useState(true);

useKeypress((key) => {
let newCursorPosition = cursorPosition;
if (isEnterKey(key)) {
setStatus('done');
done(
choices
.filter((choice) => choice.checked && !choice.disabled)
.map((choice) => choice.value)
);
} else if (isUpKey(key) || isDownKey(key)) {
const offset = isUpKey(key) ? -1 : 1;
let selectedOption;

while (!selectedOption || selectedOption.disabled) {
newCursorPosition =
(newCursorPosition + offset + choices.length) % choices.length;
selectedOption = choices[newCursorPosition];
}

setCursorPosition(newCursorPosition);
} else if (isSpaceKey(key)) {
setShowHelpTip(false);
setChoices(
choices.map((choice, i) => {
if (i === cursorPosition) {
return { ...choice, checked: !choice.checked };
}

return choice;
})
);
} else if (key.name === 'a') {
const selectAll = Boolean(choices.find((choice) => !choice.checked));
setChoices(choices.map((choice) => ({ ...choice, checked: selectAll })));
} else if (key.name === 'i') {
setChoices(choices.map((choice) => ({ ...choice, checked: !choice.checked })));
} else if (isNumberKey(key)) {
// Adjust index to start at 1
const position = Number(key.name) - 1;

// Abort if the choice doesn't exists or if disabled
if (!choices[position] || choices[position]?.disabled) {
return;
}

setCursorPosition(position);
setChoices(
choices.map((choice, i) => {
if (i === position) {
return { ...choice, checked: !choice.checked };
}

return choice;
})
);
export default createPrompt<string[], CheckboxConfig>((config, done) => {
const { prefix = usePrefix(), instructions } = config;
const paginator = useRef(new Paginator()).current;

const [status, setStatus] = useState('pending');
const [choices, setChoices] = useState(config.choices.map((choice) => ({ ...choice })));
const [cursorPosition, setCursorPosition] = useState(0);
const [showHelpTip, setShowHelpTip] = useState(true);

useKeypress((key) => {
let newCursorPosition = cursorPosition;
if (isEnterKey(key)) {
setStatus('done');
done(
choices
.filter((choice) => choice.checked && !choice.disabled)
.map((choice) => choice.value)
);
} else if (isUpKey(key) || isDownKey(key)) {
const offset = isUpKey(key) ? -1 : 1;
let selectedOption;

while (!selectedOption || selectedOption.disabled) {
newCursorPosition =
(newCursorPosition + offset + choices.length) % choices.length;
selectedOption = choices[newCursorPosition];
}
});

const message = chalk.bold(config.message);
setCursorPosition(newCursorPosition);
} else if (isSpaceKey(key)) {
setShowHelpTip(false);
setChoices(
choices.map((choice, i) => {
if (i === cursorPosition) {
return { ...choice, checked: !choice.checked };
}

return choice;
})
);
} else if (key.name === 'a') {
const selectAll = Boolean(choices.find((choice) => !choice.checked));
setChoices(choices.map((choice) => ({ ...choice, checked: selectAll })));
} else if (key.name === 'i') {
setChoices(choices.map((choice) => ({ ...choice, checked: !choice.checked })));
} else if (isNumberKey(key)) {
// Adjust index to start at 1
const position = Number(key.name) - 1;

// Abort if the choice doesn't exists or if disabled
if (!choices[position] || choices[position]?.disabled) {
return;
}

if (status === 'done') {
const selection = choices
.filter((choice) => choice.checked)
.map(({ name, value }) => name || value);
return `${prefix} ${message} ${chalk.cyan(selection.join(', '))}`;
}
setCursorPosition(position);
setChoices(
choices.map((choice, i) => {
if (i === position) {
return { ...choice, checked: !choice.checked };
}

let helpTip = '';
if (showHelpTip && (instructions === undefined || instructions)) {
if (typeof instructions === 'string') {
helpTip = instructions;
} else {
const keys = [
`${chalk.cyan.bold('<space>')} to select`,
`${chalk.cyan.bold('<a>')} to toggle all`,
`${chalk.cyan.bold('<i>')} to invert selection`,
`and ${chalk.cyan.bold('<enter>')} to proceed`,
];
helpTip = ` (Press ${keys.join(', ')})`;
}
return choice;
})
);
}
});

const message = chalk.bold(config.message);

const allChoices = choices
.map(({ name, value, checked, disabled }, index) => {
const line = name || value;
if (disabled) {
return chalk.dim(
`- ${line} ${typeof disabled === 'string' ? disabled : '(disabled)'}`
);
}

const checkbox = checked ? chalk.green(figures.circleFilled) : figures.circle;
if (index === cursorPosition) {
return chalk.cyan(`${figures.pointer}${checkbox} ${line}`);
}

return ` ${checkbox} ${line}`;
})
.join('\n');

const windowedChoices = paginator.paginate(
allChoices,
cursorPosition,
config.pageSize
);
return `${prefix} ${message}${helpTip}\n${windowedChoices}${ansiEscapes.cursorHide}`;
if (status === 'done') {
const selection = choices
.filter((choice) => choice.checked)
.map(({ name, value }) => name || value);
return `${prefix} ${message} ${chalk.cyan(selection.join(', '))}`;
}
);

let helpTip = '';
if (showHelpTip && (instructions === undefined || instructions)) {
if (typeof instructions === 'string') {
helpTip = instructions;
} else {
const keys = [
`${chalk.cyan.bold('<space>')} to select`,
`${chalk.cyan.bold('<a>')} to toggle all`,
`${chalk.cyan.bold('<i>')} to invert selection`,
`and ${chalk.cyan.bold('<enter>')} to proceed`,
];
helpTip = ` (Press ${keys.join(', ')})`;
}
}

const allChoices = choices
.map(({ name, value, checked, disabled }, index) => {
const line = name || value;
if (disabled) {
return chalk.dim(
`- ${line} ${typeof disabled === 'string' ? disabled : '(disabled)'}`
);
}

const checkbox = checked ? chalk.green(figures.circleFilled) : figures.circle;
if (index === cursorPosition) {
return chalk.cyan(`${figures.pointer}${checkbox} ${line}`);
}

return ` ${checkbox} ${line}`;
})
.join('\n');

const windowedChoices = paginator.paginate(allChoices, cursorPosition, config.pageSize);
return `${prefix} ${message}${helpTip}\n${windowedChoices}${ansiEscapes.cursorHide}`;
});