diff --git a/.changeset/weak-clocks-switch.md b/.changeset/weak-clocks-switch.md new file mode 100644 index 00000000..74afdf1c --- /dev/null +++ b/.changeset/weak-clocks-switch.md @@ -0,0 +1,6 @@ +--- +"@clack/prompts": patch +"@clack/core": patch +--- + +Fixed spaces and uppercase characters in multiline prompt diff --git a/packages/core/src/prompts/multi-line.ts b/packages/core/src/prompts/multi-line.ts index d3765fc1..dcec90af 100644 --- a/packages/core/src/prompts/multi-line.ts +++ b/packages/core/src/prompts/multi-line.ts @@ -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(['up', 'down', 'left', 'right']); + export interface MultiLineOptions extends PromptOptions { placeholder?: string; defaultValue?: string; @@ -41,7 +43,7 @@ export default class MultiLinePrompt extends Prompt { 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': @@ -89,8 +91,8 @@ export default class MultiLinePrompt extends Prompt { 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) { diff --git a/packages/core/src/prompts/multi-select.ts b/packages/core/src/prompts/multi-select.ts index a19817a5..f5b04254 100644 --- a/packages/core/src/prompts/multi-select.ts +++ b/packages/core/src/prompts/multi-select.ts @@ -60,11 +60,11 @@ export default class MultiSelectPrompt extends Prompt(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(); } }); diff --git a/packages/core/src/prompts/prompt.ts b/packages/core/src/prompts/prompt.ts index 4333da9d..c48a5181 100644 --- a/packages/core/src/prompts/prompt.ts +++ b/packages/core/src/prompts/prompt.ts @@ -226,7 +226,7 @@ export default class Prompt { } // 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) { diff --git a/packages/core/src/prompts/select-key.ts b/packages/core/src/prompts/select-key.ts index 2e8e0b10..04a49a52 100644 --- a/packages/core/src/prompts/select-key.ts +++ b/packages/core/src/prompts/select-key.ts @@ -19,17 +19,17 @@ export default class SelectKeyPrompt 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; diff --git a/packages/core/test/prompts/multi-line.test.ts b/packages/core/test/prompts/multi-line.test.ts index 53f86a5d..2b92f804 100644 --- a/packages/core/test/prompts/multi-line.test.ts +++ b/packages/core/test/prompts/multi-line.test.ts @@ -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, diff --git a/packages/prompts/test/select-key.test.ts b/packages/prompts/test/select-key.test.ts index 2e143a88..856afde8 100644 --- a/packages/prompts/test/select-key.test.ts +++ b/packages/prompts/test/select-key.test.ts @@ -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; @@ -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;