Skip to content
Merged
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions .changeset/weak-clocks-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@clack/prompts": patch
"@clack/core": patch
---

Fixed spaces and uppercase characters in multiline prompt
10 changes: 6 additions & 4 deletions packages/core/src/prompts/multi-line.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { Key } from 'node:readline';
import { styleText } from 'node:util';
import { findTextCursor } from '../utils/cursor.js';
import { type Action, settings } from '../utils/index.js';
import Prompt, { type PromptOptions } from './prompt.js';

type CursorAction = 'up' | 'down' | 'left' | 'right';
const cursorActions = new Set<CursorAction>(['up', 'down', 'left', 'right']);

export interface MultiLineOptions extends PromptOptions<string, MultiLinePrompt> {
placeholder?: string;
defaultValue?: string;
Expand Down Expand Up @@ -41,7 +43,7 @@ export default class MultiLinePrompt extends Prompt<string> {
this.userInput.slice(0, this.cursor) + char + this.userInput.slice(this.cursor)
);
}
#handleCursor(key?: Action) {
#handleCursor(key?: CursorAction) {
const text = this.value ?? '';
switch (key) {
case 'up':
Expand Down Expand Up @@ -89,8 +91,8 @@ export default class MultiLinePrompt extends Prompt<string> {
this.#showSubmit = opts.showSubmit ?? false;

this.on('key', (char, key) => {
if (key?.name && settings.actions.has(key.name as Action)) {
this.#handleCursor(key.name as Action);
if (key?.name && cursorActions.has(key.name as CursorAction)) {
this.#handleCursor(key.name as CursorAction);
return;
}
if (char === '\t' && this.#showSubmit) {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/prompts/multi-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ export default class MultiSelectPrompt<T extends OptionLike> extends Prompt<T['v
0
);
this.cursor = this.options[cursor].disabled ? findCursor<T>(cursor, 1, this.options) : cursor;
this.on('key', (char) => {
if (char === 'a') {
this.on('key', (_char, key) => {
if (key.name === 'a') {
this.toggleAll();
}
if (char === 'i') {
if (key.name === 'i') {
this.toggleInvert();
}
});
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/prompts/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ export default class Prompt<TValue> {
}

// Call the key event handler and emit the key event
this.emit('key', char?.toLowerCase(), key);
this.emit('key', char, key);

if (key?.name === 'return' && this._shouldSubmit(char, key)) {
if (this.opts.validate) {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/prompts/select-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ export default class SelectKeyPrompt<T extends { value: string }> extends Prompt
});
this.cursor = Math.max(keys.indexOf(opts.initialValue), 0);

this.on('key', (key, keyInfo) => {
this.on('key', (key) => {
if (!key) {
return;
}
const casedKey = caseSensitive && keyInfo.shift ? key.toUpperCase() : key;
const casedKey = caseSensitive ? key : key.toLowerCase();
if (!keys.includes(casedKey)) {
return;
}

const value = this.options.find(({ value: [initial] }) => {
return caseSensitive ? initial === casedKey : initial?.toLowerCase() === key;
return caseSensitive ? initial === casedKey : initial?.toLowerCase() === casedKey;
});
if (value) {
this.value = value.value;
Expand Down
24 changes: 24 additions & 0 deletions packages/core/test/prompts/multi-line.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,30 @@ describe('MultiLinePrompt', () => {
expect(result).to.equal('xy');
});

test('space inserts space', () => {
const instance = new MultiLinePrompt({
input,
output,
render: () => 'foo',
});
instance.prompt();
input.emit('keypress', 'x', { name: 'x' });
input.emit('keypress', ' ', { name: 'space' });
expect(instance.userInput).to.equal('x ');
});

test('shift modifier inserts uppercase characters', () => {
const instance = new MultiLinePrompt({
input,
output,
render: () => 'foo',
});
instance.prompt();
input.emit('keypress', 'x', { name: 'x' });
input.emit('keypress', 'X', { name: 'x', shift: true });
expect(instance.userInput).to.equal('xX');
});

test('backspace deletes previous char', async () => {
const instance = new MultiLinePrompt({
input,
Expand Down
4 changes: 2 additions & 2 deletions packages/prompts/test/select-key.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => {
output,
});

input.emit('keypress', 'a', { name: 'a', shift: true });
input.emit('keypress', 'A', { name: 'a', shift: true });

const value = await result;

Expand Down Expand Up @@ -156,7 +156,7 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => {
output,
});

input.emit('keypress', 'a', { name: 'a', shift: true });
input.emit('keypress', 'A', { name: 'a', shift: true });

const value = await result;

Expand Down
Loading