From c8281a6d28e48557d77e53e14dc23ddb49568f7e Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Sun, 16 Nov 2025 08:06:56 +0700 Subject: [PATCH 01/22] add @fastify/deepmerge --- packages/prompts/package.json | 1 + packages/prompts/src/common.ts | 73 ++++++++++++++++++++++++++++++++ packages/prompts/src/password.ts | 21 +++++---- packages/prompts/src/text.ts | 21 +++++---- 4 files changed, 98 insertions(+), 18 deletions(-) diff --git a/packages/prompts/package.json b/packages/prompts/package.json index 0a636349..aa48da60 100644 --- a/packages/prompts/package.json +++ b/packages/prompts/package.json @@ -58,6 +58,7 @@ "sisteransi": "^1.0.5" }, "devDependencies": { + "@fastify/deepmerge": "^3.1.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "is-unicode-supported": "^1.3.0", diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index 2489a815..4065411d 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -2,6 +2,8 @@ import type { Readable, Writable } from 'node:stream'; import type { State } from '@clack/core'; import isUnicodeSupported from 'is-unicode-supported'; import color from 'picocolors'; +import type { Formatter } from 'picocolors/types.d.ts'; +import deepmerge from '@fastify/deepmerge'; export const unicode = isUnicodeSupported(); export const isCI = (): boolean => process.env.CI === 'true'; @@ -71,4 +73,75 @@ export interface CommonOptions { input?: Readable; output?: Writable; signal?: AbortSignal; + style?: StyleOptions; +} + +export interface StyleOptions { + formatBar?: { + initial?: Formatter; + active?: Formatter; + cancel?: Formatter; + error?: Formatter; + submit?: Formatter; + }; + prefix?: { + initial?: string; + active?: string; + cancel?: string; + error?: string; + submit?: string; + }; + radio?: { + active?: string; + inactive?: string; + }; + checkbox?: { + selected?: { + active?: string; + inactive?: string; + }; + unselected?: { + active?: string; + inactive?: string; + }; + disabled?: string; + }; +} + +const defaultStyle: StyleOptions = { + formatBar: { + initial: color.cyan, + active: color.cyan, + cancel: color.gray, + error: color.yellow, + submit: color.gray, + }, + prefix: { + initial: color.cyan(S_STEP_ACTIVE), + active: color.cyan(S_STEP_ACTIVE), + cancel: color.red(S_STEP_CANCEL), + error: color.yellow(S_STEP_ERROR), + submit: color.green(S_STEP_SUBMIT), + }, + radio: { + active: color.green(S_RADIO_ACTIVE), + inactive: color.dim(S_RADIO_INACTIVE), + }, + checkbox: { + selected: { + active: color.green(S_CHECKBOX_SELECTED), + inactive: color.dim(S_CHECKBOX_INACTIVE), + }, + unselected: { + active: color.cyan(S_CHECKBOX_ACTIVE), + inactive: color.dim(S_CHECKBOX_INACTIVE), + }, + disabled: color.dim(S_CHECKBOX_INACTIVE) + }, +} + +const merge = deepmerge(); + +export const extendStyle = (style?: StyleOptions) => { + return merge(defaultStyle, style); } diff --git a/packages/prompts/src/password.ts b/packages/prompts/src/password.ts index 8010960b..a992d3b8 100644 --- a/packages/prompts/src/password.ts +++ b/packages/prompts/src/password.ts @@ -1,6 +1,6 @@ import { PasswordPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, S_BAR, S_BAR_END, S_PASSWORD_MASK, symbol } from './common.js'; +import { type CommonOptions, S_BAR, S_BAR_END, S_PASSWORD_MASK, extendStyle } from './common.js'; export interface PasswordOptions extends CommonOptions { message: string; @@ -9,6 +9,8 @@ export interface PasswordOptions extends CommonOptions { clearOnError?: boolean; } export const password = (opts: PasswordOptions) => { + const style = extendStyle(opts.style); + return new PasswordPrompt({ validate: opts.validate, mask: opts.mask ?? S_PASSWORD_MASK, @@ -16,7 +18,10 @@ export const password = (opts: PasswordOptions) => { input: opts.input, output: opts.output, render() { - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const bar = style.formatBar[this.state](S_BAR); + const barEnd = style.formatBar[this.state](S_BAR_END); + + const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}\n`; const userInput = this.userInputWithCursor; const masked = this.masked; @@ -26,22 +31,20 @@ export const password = (opts: PasswordOptions) => { if (opts.clearOnError) { this.clear(); } - return `${title.trim()}\n${color.yellow(S_BAR)}${maskedText}\n${color.yellow( - S_BAR_END - )} ${color.yellow(this.error)}\n`; + return `${title.trim()}\n${bar}${maskedText}\n${barEnd} ${style.formatBar[this.state](this.error)}\n`; } case 'submit': { const maskedText = masked ? ` ${color.dim(masked)}` : ''; - return `${title}${color.gray(S_BAR)}${maskedText}`; + return `${title}${bar}${maskedText}`; } case 'cancel': { const maskedText = masked ? ` ${color.strikethrough(color.dim(masked))}` : ''; - return `${title}${color.gray(S_BAR)}${maskedText}${ - masked ? `\n${color.gray(S_BAR)}` : '' + return `${title}${bar}${maskedText}${ + masked ? `\n${bar}` : '' }`; } default: - return `${title}${color.cyan(S_BAR)} ${userInput}\n${color.cyan(S_BAR_END)}\n`; + return `${title}${bar} ${userInput}\n${barEnd}\n`; } }, }).prompt() as Promise; diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index 244f7c8d..5c0526c3 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -1,6 +1,6 @@ import { TextPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, S_BAR, S_BAR_END, symbol } from './common.js'; +import { type CommonOptions, S_BAR, S_BAR_END, extendStyle } from './common.js'; export interface TextOptions extends CommonOptions { message: string; @@ -11,6 +11,8 @@ export interface TextOptions extends CommonOptions { } export const text = (opts: TextOptions) => { + const style = extendStyle(opts.style); + return new TextPrompt({ validate: opts.validate, placeholder: opts.placeholder, @@ -20,7 +22,10 @@ export const text = (opts: TextOptions) => { signal: opts.signal, input: opts.input, render() { - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const bar = style.formatBar[this.state](S_BAR); + const barEnd = style.formatBar[this.state](S_BAR_END); + + const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}\n`; const placeholder = opts.placeholder ? color.inverse(opts.placeholder[0]) + color.dim(opts.placeholder.slice(1)) : color.inverse(color.hidden('_')); @@ -29,21 +34,19 @@ export const text = (opts: TextOptions) => { switch (this.state) { case 'error': { - const errorText = this.error ? ` ${color.yellow(this.error)}` : ''; - return `${title.trim()}\n${color.yellow(S_BAR)} ${userInput}\n${color.yellow( - S_BAR_END - )}${errorText}\n`; + const errorText = this.error ? ` ${style.formatBar[this.state](this.error)}` : ''; + return `${title.trim()}\n${bar} ${userInput}\n${barEnd}${errorText}\n`; } case 'submit': { const valueText = value ? ` ${color.dim(value)}` : ''; - return `${title}${color.gray(S_BAR)}${valueText}`; + return `${title}${bar}${valueText}`; } case 'cancel': { const valueText = value ? ` ${color.strikethrough(color.dim(value))}` : ''; - return `${title}${color.gray(S_BAR)}${valueText}${value.trim() ? `\n${color.gray(S_BAR)}` : ''}`; + return `${title}${bar}${valueText}${value.trim() ? `\n${bar}` : ''}`; } default: - return `${title}${color.cyan(S_BAR)} ${userInput}\n${color.cyan(S_BAR_END)}\n`; + return `${title}${bar} ${userInput}\n${barEnd}\n`; } }, }).prompt() as Promise; From a86acac3edc42f017277faa030896f6706668a90 Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Sun, 16 Nov 2025 08:07:53 +0700 Subject: [PATCH 02/22] add @fastify/deepmerge (and style in previous commit) --- pnpm-lock.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c19acf1..d3e7fa2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,6 +86,9 @@ importers: specifier: ^1.0.5 version: 1.0.5 devDependencies: + '@fastify/deepmerge': + specifier: ^3.1.0 + version: 3.1.0 fast-string-width: specifier: ^1.1.0 version: 1.1.0 @@ -392,6 +395,9 @@ packages: cpu: [x64] os: [win32] + '@fastify/deepmerge@3.1.0': + resolution: {integrity: sha512-lCVONBQINyNhM6LLezB6+2afusgEYR4G8xenMsfe+AT+iZ7Ca6upM5Ha8UkZuYSnuMw3GWl/BiPXnLMi/gSxuQ==} + '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} @@ -2120,6 +2126,8 @@ snapshots: '@esbuild/win32-x64@0.25.8': optional: true + '@fastify/deepmerge@3.1.0': {} + '@jridgewell/sourcemap-codec@1.5.0': {} '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': From 5a87a722fb384b7713421100fb9f0d994c62a303 Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Sun, 16 Nov 2025 08:21:22 +0700 Subject: [PATCH 03/22] add style for confirm --- packages/prompts/src/confirm.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/prompts/src/confirm.ts b/packages/prompts/src/confirm.ts index 4ee30acc..7cf4112f 100644 --- a/packages/prompts/src/confirm.ts +++ b/packages/prompts/src/confirm.ts @@ -4,9 +4,7 @@ import { type CommonOptions, S_BAR, S_BAR_END, - S_RADIO_ACTIVE, - S_RADIO_INACTIVE, - symbol, + extendStyle, } from './common.js'; export interface ConfirmOptions extends CommonOptions { @@ -16,6 +14,7 @@ export interface ConfirmOptions extends CommonOptions { initialValue?: boolean; } export const confirm = (opts: ConfirmOptions) => { + const style = extendStyle(opts.style); const active = opts.active ?? 'Yes'; const inactive = opts.inactive ?? 'No'; return new ConfirmPrompt({ @@ -26,26 +25,29 @@ export const confirm = (opts: ConfirmOptions) => { output: opts.output, initialValue: opts.initialValue ?? true, render() { - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const bar = style.formatBar[this.state](S_BAR); + const barEnd = style.formatBar[this.state](S_BAR_END); + + const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}\n`; const value = this.value ? active : inactive; switch (this.state) { case 'submit': - return `${title}${color.gray(S_BAR)} ${color.dim(value)}`; + return `${title}${bar} ${color.dim(value)}`; case 'cancel': - return `${title}${color.gray(S_BAR)} ${color.strikethrough( + return `${title}${bar} ${color.strikethrough( color.dim(value) - )}\n${color.gray(S_BAR)}`; + )}\n${bar}`; default: { - return `${title}${color.cyan(S_BAR)} ${ + return `${title}${bar} ${ this.value - ? `${color.green(S_RADIO_ACTIVE)} ${active}` - : `${color.dim(S_RADIO_INACTIVE)} ${color.dim(active)}` + ? `${style.radio.active} ${active}` + : `${style.radio.inactive} ${color.dim(active)}` } ${color.dim('/')} ${ !this.value - ? `${color.green(S_RADIO_ACTIVE)} ${inactive}` - : `${color.dim(S_RADIO_INACTIVE)} ${color.dim(inactive)}` - }\n${color.cyan(S_BAR_END)}\n`; + ? `${style.radio.active} ${inactive}` + : `${style.radio.inactive} ${color.dim(inactive)}` + }\n${barEnd}\n`; } } }, From 740126385f5e1539edb72ea17fd2e787b7732f78 Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Sun, 16 Nov 2025 08:47:21 +0700 Subject: [PATCH 04/22] add style to select and default disabled radio --- packages/prompts/src/common.ts | 2 ++ packages/prompts/src/select.ts | 27 +++++++++++++-------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index 4065411d..a34a6525 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -94,6 +94,7 @@ export interface StyleOptions { radio?: { active?: string; inactive?: string; + disabled?: string; }; checkbox?: { selected?: { @@ -126,6 +127,7 @@ const defaultStyle: StyleOptions = { radio: { active: color.green(S_RADIO_ACTIVE), inactive: color.dim(S_RADIO_INACTIVE), + disabled: color.gray(S_RADIO_INACTIVE), }, checkbox: { selected: { diff --git a/packages/prompts/src/select.ts b/packages/prompts/src/select.ts index 466ee0f5..6d4a11e3 100644 --- a/packages/prompts/src/select.ts +++ b/packages/prompts/src/select.ts @@ -4,10 +4,7 @@ import { type CommonOptions, S_BAR, S_BAR_END, - S_RADIO_ACTIVE, - S_RADIO_INACTIVE, - symbol, - symbolBar, + extendStyle, } from './common.js'; import { limitOptions } from './limit-options.js'; @@ -73,6 +70,7 @@ export interface SelectOptions extends CommonOptions { } export const select = (opts: SelectOptions) => { + const style = extendStyle(opts.style); const opt = ( option: Option, state: 'inactive' | 'active' | 'selected' | 'cancelled' | 'disabled' @@ -80,19 +78,19 @@ export const select = (opts: SelectOptions) => { const label = option.label ?? String(option.value); switch (state) { case 'disabled': - return `${color.gray(S_RADIO_INACTIVE)} ${color.gray(label)}${ + return `${style.radio.disabled} ${color.gray(label)}${ option.hint ? ` ${color.dim(`(${option.hint ?? 'disabled'})`)}` : '' }`; case 'selected': return `${color.dim(label)}`; case 'active': - return `${color.green(S_RADIO_ACTIVE)} ${label}${ + return `${style.radio.active} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; case 'cancelled': return `${color.strikethrough(color.dim(label))}`; default: - return `${color.dim(S_RADIO_INACTIVE)} ${color.dim(label)}`; + return `${style.radio.inactive} ${color.dim(label)}`; } }; @@ -103,8 +101,9 @@ export const select = (opts: SelectOptions) => { output: opts.output, initialValue: opts.initialValue, render() { - const titlePrefix = `${symbol(this.state)} `; - const titlePrefixBar = `${symbolBar(this.state)} `; + const bar = style.formatBar[this.state](S_BAR); + const titlePrefix = `${style.prefix[this.state]} `; + const titlePrefixBar = `${bar} `; const messageLines = wrapTextWithPrefix( opts.output, opts.message, @@ -115,7 +114,7 @@ export const select = (opts: SelectOptions) => { switch (this.state) { case 'submit': { - const submitPrefix = `${color.gray(S_BAR)} `; + const submitPrefix = `${bar} `; const wrappedLines = wrapTextWithPrefix( opts.output, opt(this.options[this.cursor], 'selected'), @@ -124,16 +123,16 @@ export const select = (opts: SelectOptions) => { return `${title}${wrappedLines}`; } case 'cancel': { - const cancelPrefix = `${color.gray(S_BAR)} `; + const cancelPrefix = `${bar} `; const wrappedLines = wrapTextWithPrefix( opts.output, opt(this.options[this.cursor], 'cancelled'), cancelPrefix ); - return `${title}${wrappedLines}\n${color.gray(S_BAR)}`; + return `${title}${wrappedLines}\n${bar}`; } default: { - const prefix = `${color.cyan(S_BAR)} `; + const prefix = `${bar} `; return `${title}${prefix}${limitOptions({ output: opts.output, cursor: this.cursor, @@ -142,7 +141,7 @@ export const select = (opts: SelectOptions) => { columnPadding: prefix.length, style: (item, active) => opt(item, item.disabled ? 'disabled' : active ? 'active' : 'inactive'), - }).join(`\n${prefix}`)}\n${color.cyan(S_BAR_END)}\n`; + }).join(`\n${prefix}`)}\n${style.formatBar[this.state](S_BAR_END)}\n`; } } }, From f485888a017a7c6020db2e7c5d2f55f06c1e8321 Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Sun, 16 Nov 2025 09:13:18 +0700 Subject: [PATCH 05/22] add style to multiselect & update default inactive selected style --- packages/prompts/src/common.ts | 2 +- packages/prompts/src/multi-select.ts | 39 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index a34a6525..64e57d5f 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -132,7 +132,7 @@ const defaultStyle: StyleOptions = { checkbox: { selected: { active: color.green(S_CHECKBOX_SELECTED), - inactive: color.dim(S_CHECKBOX_INACTIVE), + inactive: color.green(S_CHECKBOX_SELECTED), }, unselected: { active: color.cyan(S_CHECKBOX_ACTIVE), diff --git a/packages/prompts/src/multi-select.ts b/packages/prompts/src/multi-select.ts index 491b7218..98c02f35 100644 --- a/packages/prompts/src/multi-select.ts +++ b/packages/prompts/src/multi-select.ts @@ -4,11 +4,7 @@ import { type CommonOptions, S_BAR, S_BAR_END, - S_CHECKBOX_ACTIVE, - S_CHECKBOX_INACTIVE, - S_CHECKBOX_SELECTED, - symbol, - symbolBar, + extendStyle, } from './common.js'; import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; @@ -29,6 +25,7 @@ const computeLabel = (label: string, format: (text: string) => string) => { }; export const multiselect = (opts: MultiSelectOptions) => { + const style = extendStyle(opts.style); const opt = ( option: Option, state: @@ -42,17 +39,17 @@ export const multiselect = (opts: MultiSelectOptions) => { ) => { const label = option.label ?? String(option.value); if (state === 'disabled') { - return `${color.gray(S_CHECKBOX_INACTIVE)} ${computeLabel(label, color.gray)}${ + return `${style.checkbox.disabled} ${computeLabel(label, color.gray)}${ option.hint ? ` ${color.dim(`(${option.hint ?? 'disabled'})`)}` : '' }`; } if (state === 'active') { - return `${color.cyan(S_CHECKBOX_ACTIVE)} ${label}${ + return `${style.checkbox.unselected.active} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } if (state === 'selected') { - return `${color.green(S_CHECKBOX_SELECTED)} ${computeLabel(label, color.dim)}${ + return `${style.checkbox.selected.inactive} ${computeLabel(label, color.dim)}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } @@ -60,14 +57,14 @@ export const multiselect = (opts: MultiSelectOptions) => { return `${computeLabel(label, (text) => color.strikethrough(color.dim(text)))}`; } if (state === 'active-selected') { - return `${color.green(S_CHECKBOX_SELECTED)} ${label}${ + return `${style.checkbox.selected.active} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } if (state === 'submitted') { return `${computeLabel(label, color.dim)}`; } - return `${color.dim(S_CHECKBOX_INACTIVE)} ${computeLabel(label, color.dim)}`; + return `${style.checkbox.unselected.inactive} ${computeLabel(label, color.dim)}`; }; const required = opts.required ?? true; @@ -90,11 +87,13 @@ export const multiselect = (opts: MultiSelectOptions) => { )}`; }, render() { + const bar = style.formatBar[this.state](S_BAR); + const barEnd = style.formatBar[this.state](S_BAR_END); const wrappedMessage = wrapTextWithPrefix( opts.output, opts.message, - `${symbolBar(this.state)} `, - `${symbol(this.state)} ` + `${bar} `, + `${style.prefix[this.state]} ` ); const title = `${color.gray(S_BAR)}\n${wrappedMessage}\n`; const value = this.value ?? []; @@ -123,7 +122,7 @@ export const multiselect = (opts: MultiSelectOptions) => { const wrappedSubmitText = wrapTextWithPrefix( opts.output, submitText, - `${color.gray(S_BAR)} ` + `${bar} ` ); return `${title}${wrappedSubmitText}`; } @@ -133,17 +132,17 @@ export const multiselect = (opts: MultiSelectOptions) => { .map((option) => opt(option, 'cancelled')) .join(color.dim(', ')); if (label.trim() === '') { - return `${title}${color.gray(S_BAR)}`; + return `${title}${bar}`; } - const wrappedLabel = wrapTextWithPrefix(opts.output, label, `${color.gray(S_BAR)} `); - return `${title}${wrappedLabel}\n${color.gray(S_BAR)}`; + const wrappedLabel = wrapTextWithPrefix(opts.output, label, `${bar} `); + return `${title}${wrappedLabel}\n${bar}`; } case 'error': { - const prefix = `${color.yellow(S_BAR)} `; + const prefix = `${bar} `; const footer = this.error .split('\n') .map((ln, i) => - i === 0 ? `${color.yellow(S_BAR_END)} ${color.yellow(ln)}` : ` ${ln}` + i === 0 ? `${barEnd} ${style.formatBar[this.state](ln)}` : ` ${ln}` ) .join('\n'); return `${title}${prefix}${limitOptions({ @@ -156,7 +155,7 @@ export const multiselect = (opts: MultiSelectOptions) => { }).join(`\n${prefix}`)}\n${footer}\n`; } default: { - const prefix = `${color.cyan(S_BAR)} `; + const prefix = `${bar} `; return `${title}${prefix}${limitOptions({ output: opts.output, options: this.options, @@ -164,7 +163,7 @@ export const multiselect = (opts: MultiSelectOptions) => { maxItems: opts.maxItems, columnPadding: prefix.length, style: styleOption, - }).join(`\n${prefix}`)}\n${color.cyan(S_BAR_END)}\n`; + }).join(`\n${prefix}`)}\n${barEnd}\n`; } } }, From 1be656642d41bae875b9bbd3f41429e0279ed18a Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Sun, 16 Nov 2025 09:14:40 +0700 Subject: [PATCH 06/22] remove symbolBar --- packages/prompts/src/common.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index 64e57d5f..89303e8f 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -55,20 +55,6 @@ export const symbol = (state: State) => { } }; -export const symbolBar = (state: State) => { - switch (state) { - case 'initial': - case 'active': - return color.cyan(S_BAR); - case 'cancel': - return color.red(S_BAR); - case 'error': - return color.yellow(S_BAR); - case 'submit': - return color.green(S_BAR); - } -}; - export interface CommonOptions { input?: Readable; output?: Writable; From b1e4bf7864908cbbc05adfa1d5bc9be0e9a5558c Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Sun, 16 Nov 2025 09:43:50 +0700 Subject: [PATCH 07/22] add style to group-multiselect --- packages/prompts/src/group-multi-select.ts | 56 ++++++++++++---------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/packages/prompts/src/group-multi-select.ts b/packages/prompts/src/group-multi-select.ts index 4464b074..38f9e000 100644 --- a/packages/prompts/src/group-multi-select.ts +++ b/packages/prompts/src/group-multi-select.ts @@ -8,6 +8,7 @@ import { S_CHECKBOX_INACTIVE, S_CHECKBOX_SELECTED, symbol, + extendStyle, } from './common.js'; import type { Option } from './select.js'; @@ -21,6 +22,7 @@ export interface GroupMultiSelectOptions extends CommonOptions { groupSpacing?: number; } export const groupMultiselect = (opts: GroupMultiSelectOptions) => { + const style = extendStyle(opts.style); const { selectableGroups = true, groupSpacing = 0 } = opts; const opt = ( option: Option & { group: string | boolean }, @@ -33,7 +35,8 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => | 'group-active-selected' | 'submitted' | 'cancelled', - options: (Option & { group: string | boolean })[] = [] + options: (Option & { group: string | boolean })[] = [], + prefixBar: string, ) => { const label = option.label ?? String(option.value); const isItem = typeof option.group === 'string'; @@ -42,23 +45,23 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => const prefix = isItem ? (selectableGroups ? `${isLast ? S_BAR_END : S_BAR} ` : ' ') : ''; let spacingPrefix = ''; if (groupSpacing > 0 && !isItem) { - const spacingPrefixText = `\n${color.cyan(S_BAR)}`; + const spacingPrefixText = `\n${prefixBar}`; spacingPrefix = `${spacingPrefixText.repeat(groupSpacing - 1)}${spacingPrefixText} `; } if (state === 'active') { - return `${spacingPrefix}${color.dim(prefix)}${color.cyan(S_CHECKBOX_ACTIVE)} ${label}${ + return `${spacingPrefix}${color.dim(prefix)}${style.checkbox.unselected.active} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } if (state === 'group-active') { - return `${spacingPrefix}${prefix}${color.cyan(S_CHECKBOX_ACTIVE)} ${color.dim(label)}`; + return `${spacingPrefix}${prefix}${style.checkbox.unselected.active} ${color.dim(label)}`; } if (state === 'group-active-selected') { - return `${spacingPrefix}${prefix}${color.green(S_CHECKBOX_SELECTED)} ${color.dim(label)}`; + return `${spacingPrefix}${prefix}${style.checkbox.selected.inactive} ${color.dim(label)}`; } if (state === 'selected') { - const selectedCheckbox = isItem || selectableGroups ? color.green(S_CHECKBOX_SELECTED) : ''; + const selectedCheckbox = isItem || selectableGroups ? style.checkbox.selected.inactive : ''; return `${spacingPrefix}${color.dim(prefix)}${selectedCheckbox} ${color.dim(label)}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; @@ -67,14 +70,14 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => return `${color.strikethrough(color.dim(label))}`; } if (state === 'active-selected') { - return `${spacingPrefix}${color.dim(prefix)}${color.green(S_CHECKBOX_SELECTED)} ${label}${ + return `${spacingPrefix}${color.dim(prefix)}${style.checkbox.selected.active} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } if (state === 'submitted') { return `${color.dim(label)}`; } - const unselectedCheckbox = isItem || selectableGroups ? color.dim(S_CHECKBOX_INACTIVE) : ''; + const unselectedCheckbox = isItem || selectableGroups ? style.checkbox.unselected.inactive : ''; return `${spacingPrefix}${color.dim(prefix)}${unselectedCheckbox} ${color.dim(label)}`; }; const required = opts.required ?? true; @@ -99,25 +102,28 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => )}`; }, render() { - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const bar = style.formatBar[this.state](S_BAR); + const barEnd = style.formatBar[this.state](S_BAR_END); + + const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}\n`; const value = this.value ?? []; switch (this.state) { case 'submit': { const selectedOptions = this.options .filter(({ value: optionValue }) => value.includes(optionValue)) - .map((option) => opt(option, 'submitted')); + .map((option) => opt(option, 'submitted'), bar); const optionsText = selectedOptions.length === 0 ? '' : ` ${selectedOptions.join(color.dim(', '))}`; - return `${title}${color.gray(S_BAR)}${optionsText}`; + return `${title}${bar}${optionsText}`; } case 'cancel': { const label = this.options .filter(({ value: optionValue }) => value.includes(optionValue)) - .map((option) => opt(option, 'cancelled')) + .map((option) => opt(option, 'cancelled'), bar) .join(color.dim(', ')); - return `${title}${color.gray(S_BAR)} ${ - label.trim() ? `${label}\n${color.gray(S_BAR)}` : '' + return `${title}${bar} ${ + label.trim() ? `${label}\n${bar}` : '' }`; } case 'error': { @@ -127,7 +133,7 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => i === 0 ? `${color.yellow(S_BAR_END)} ${color.yellow(ln)}` : ` ${ln}` ) .join('\n'); - return `${title}${color.yellow(S_BAR)} ${this.options + return `${title}${bar} ${this.options .map((option, i, options) => { const selected = value.includes(option.value) || @@ -138,17 +144,17 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => typeof option.group === 'string' && this.options[this.cursor].value === option.group; if (groupActive) { - return opt(option, selected ? 'group-active-selected' : 'group-active', options); + return opt(option, selected ? 'group-active-selected' : 'group-active', options, bar); } if (active && selected) { - return opt(option, 'active-selected', options); + return opt(option, 'active-selected', options, bar); } if (selected) { - return opt(option, 'selected', options); + return opt(option, 'selected', options, bar); } - return opt(option, active ? 'active' : 'inactive', options); + return opt(option, active ? 'active' : 'inactive', options, bar); }) - .join(`\n${color.yellow(S_BAR)} `)}\n${footer}\n`; + .join(`\n${bar} `)}\n${footer}\n`; } default: { const optionsText = this.options @@ -169,18 +175,18 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => options ); } else if (active && selected) { - optionText = opt(option, 'active-selected', options); + optionText = opt(option, 'active-selected', options, bar); } else if (selected) { - optionText = opt(option, 'selected', options); + optionText = opt(option, 'selected', options, bar); } else { - optionText = opt(option, active ? 'active' : 'inactive', options); + optionText = opt(option, active ? 'active' : 'inactive', options, bar); } const prefix = i !== 0 && !optionText.startsWith('\n') ? ' ' : ''; return `${prefix}${optionText}`; }) - .join(`\n${color.cyan(S_BAR)}`); + .join(`\n${bar}`); const optionsPrefix = optionsText.startsWith('\n') ? '' : ' '; - return `${title}${color.cyan(S_BAR)}${optionsPrefix}${optionsText}\n${color.cyan(S_BAR_END)}\n`; + return `${title}${bar}${optionsPrefix}${optionsText}\n${color.cyan(S_BAR_END)}\n`; } } }, From dda903ef2a920fa504ae8f7b9a71221cbb4b8e3c Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Sun, 16 Nov 2025 12:26:18 +0700 Subject: [PATCH 08/22] add style to autocomplete --- packages/prompts/src/autocomplete.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index e55b285f..543501e3 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -9,6 +9,7 @@ import { S_RADIO_ACTIVE, S_RADIO_INACTIVE, symbol, + extendStyle, } from './common.js'; import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; @@ -76,6 +77,7 @@ export interface AutocompleteOptions extends AutocompleteSharedOptions(opts: AutocompleteOptions) => { + const style = extendStyle(opts.style); const prompt = new AutocompletePrompt({ options: opts.options, initialValue: opts.initialValue ? [opts.initialValue] : undefined, @@ -95,6 +97,7 @@ export const autocomplete = (opts: AutocompleteOptions) => { const options = this.options; const placeholder = opts.placeholder; const showPlaceholder = valueAsString === '' && placeholder !== undefined; + const bar = style.formatBar[this.state](S_BAR); // Handle different states switch (this.state) { @@ -103,12 +106,12 @@ export const autocomplete = (opts: AutocompleteOptions) => { const selected = getSelectedOptions(this.selectedValues, options); const label = selected.length > 0 ? ` ${color.dim(selected.map(getLabel).join(', '))}` : ''; - return `${headings.join('\n')}\n${color.gray(S_BAR)}${label}`; + return `${headings.join('\n')}\n${bar}${label}`; } case 'cancel': { const userInputText = userInput ? ` ${color.strikethrough(color.dim(userInput))}` : ''; - return `${headings.join('\n')}\n${color.gray(S_BAR)}${userInputText}`; + return `${headings.join('\n')}\n${bar}${userInputText}`; } default: { @@ -132,15 +135,15 @@ export const autocomplete = (opts: AutocompleteOptions) => { // No matches message const noResults = this.filteredOptions.length === 0 && userInput - ? [`${color.cyan(S_BAR)} ${color.yellow('No matches found')}`] + ? [`${bar} ${style.formatBar.error('No matches found')}`] : []; const validationError = - this.state === 'error' ? [`${color.yellow(S_BAR)} ${color.yellow(this.error)}`] : []; + this.state === 'error' ? [`${bar} ${style.formatBar[this.state](this.error)}`] : []; headings.push( - `${color.cyan(S_BAR)}`, - `${color.cyan(S_BAR)} ${color.dim('Search:')}${searchText}${matches}`, + `${bar}`, + `${bar} ${color.dim('Search:')}${searchText}${matches}`, ...noResults, ...validationError ); @@ -153,8 +156,8 @@ export const autocomplete = (opts: AutocompleteOptions) => { ]; const footers = [ - `${color.cyan(S_BAR)} ${color.dim(instructions.join(' • '))}`, - `${color.cyan(S_BAR_END)}`, + `${bar} ${color.dim(instructions.join(' • '))}`, + `${style.formatBar[this.state](S_BAR_END)}`, ]; // Render options with selection @@ -174,8 +177,8 @@ export const autocomplete = (opts: AutocompleteOptions) => { : ''; return active - ? `${color.green(S_RADIO_ACTIVE)} ${label}${hint}` - : `${color.dim(S_RADIO_INACTIVE)} ${color.dim(label)}${hint}`; + ? `${style.radio.active} ${label}${hint}` + : `${style.radio.inactive} ${color.dim(label)}${hint}`; }, maxItems: opts.maxItems, output: opts.output, @@ -184,7 +187,7 @@ export const autocomplete = (opts: AutocompleteOptions) => { // Return the formatted prompt return [ ...headings, - ...displayOptions.map((option) => `${color.cyan(S_BAR)} ${option}`), + ...displayOptions.map((option) => `${bar} ${option}`), ...footers, ].join('\n'); } From 73ff7f3ef7f706dc5ce1638e81a6ac5c151b9b61 Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Sun, 16 Nov 2025 12:26:37 +0700 Subject: [PATCH 09/22] fix extendStyle --- packages/prompts/src/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index 89303e8f..4db13633 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -131,5 +131,5 @@ const defaultStyle: StyleOptions = { const merge = deepmerge(); export const extendStyle = (style?: StyleOptions) => { - return merge(defaultStyle, style); + return merge(defaultStyle, style ?? {}); } From b0d15692ff7777a1c297010541358561a200fad1 Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Sun, 16 Nov 2025 12:51:26 +0700 Subject: [PATCH 10/22] add style to autocomplete-multiselect --- packages/prompts/src/autocomplete.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index 543501e3..f8b6f600 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -215,7 +215,8 @@ export interface AutocompleteMultiSelectOptions extends AutocompleteShare * Integrated autocomplete multiselect - combines type-ahead filtering with multiselect in one UI */ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOptions) => { - const formatOption = ( + const style = extendStyle(opts.style); + const formatOption = ( option: Option, active: boolean, selectedValues: Value[], @@ -227,11 +228,13 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti option.hint && focusedValue !== undefined && option.value === focusedValue ? color.dim(` (${option.hint})`) : ''; - const checkbox = isSelected ? color.green(S_CHECKBOX_SELECTED) : color.dim(S_CHECKBOX_INACTIVE); if (active) { + const checkbox = isSelected ? style.checkbox.selected.active : style.checkbox?.unselected?.active; return `${checkbox} ${label}${hint}`; } + + const checkbox = isSelected ? style.checkbox.selected.inactive : style.checkbox.unselected.inactive; return `${checkbox} ${color.dim(label)}`; }; @@ -254,7 +257,8 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti output: opts.output, render() { // Title and symbol - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}`; + const bar = style.formatBar[this.state](S_BAR); // Selection counter const userInput = this.userInput; @@ -279,10 +283,10 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti // Render prompt state switch (this.state) { case 'submit': { - return `${title}${color.gray(S_BAR)} ${color.dim(`${this.selectedValues.length} items selected`)}`; + return `${title}${bar} ${color.dim(`${this.selectedValues.length} items selected`)}`; } case 'cancel': { - return `${title}${color.gray(S_BAR)} ${color.strikethrough(color.dim(userInput))}`; + return `${title}\n${bar} ${color.strikethrough(color.dim(userInput))}`; } default: { // Instructions @@ -296,11 +300,11 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti // No results message const noResults = this.filteredOptions.length === 0 && userInput - ? [`${color.cyan(S_BAR)} ${color.yellow('No matches found')}`] + ? [`${bar} ${style.formatBar.error('No matches found')}`] : []; const errorMessage = - this.state === 'error' ? [`${color.cyan(S_BAR)} ${color.yellow(this.error)}`] : []; + this.state === 'error' ? [`${bar} ${style.formatBar[this.state](this.error)}`] : []; // Get limited options for display const displayOptions = limitOptions({ @@ -315,12 +319,12 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti // Build the prompt display return [ title, - `${color.cyan(S_BAR)} ${color.dim('Search:')} ${searchText}${matches}`, + `${bar} ${color.dim('Search:')} ${searchText}${matches}`, ...noResults, ...errorMessage, - ...displayOptions.map((option) => `${color.cyan(S_BAR)} ${option}`), - `${color.cyan(S_BAR)} ${color.dim(instructions.join(' • '))}`, - `${color.cyan(S_BAR_END)}`, + ...displayOptions.map((option) => `${bar} ${option}`), + `${bar} ${color.dim(instructions.join(' • '))}`, + `${style.formatBar[this.state](S_BAR_END)}`, ].join('\n'); } } From 2b0948dbdd317c5dd64394300f08eb15ef6450f2 Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Sun, 16 Nov 2025 13:19:42 +0700 Subject: [PATCH 11/22] add style to select-key --- packages/prompts/src/select-key.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/prompts/src/select-key.ts b/packages/prompts/src/select-key.ts index f5bbbf69..87e31774 100644 --- a/packages/prompts/src/select-key.ts +++ b/packages/prompts/src/select-key.ts @@ -1,9 +1,10 @@ import { SelectKeyPrompt } from '@clack/core'; import color from 'picocolors'; -import { S_BAR, S_BAR_END, symbol } from './common.js'; +import { S_BAR, S_BAR_END, extendStyle } from './common.js'; import type { Option, SelectOptions } from './select.js'; export const selectKey = (opts: SelectOptions) => { + const style = extendStyle(opts.style); const opt = ( option: Option, state: 'inactive' | 'active' | 'selected' | 'cancelled' = 'inactive' @@ -32,22 +33,21 @@ export const selectKey = (opts: SelectOptions) => { output: opts.output, initialValue: opts.initialValue, render() { - const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`; + const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}\n`; + const bar = style.formatBar[this.state](S_BAR); switch (this.state) { case 'submit': - return `${title}${color.gray(S_BAR)} ${opt( + return `${title}${bar} ${opt( this.options.find((opt) => opt.value === this.value) ?? opts.options[0], 'selected' )}`; case 'cancel': - return `${title}${color.gray(S_BAR)} ${opt(this.options[0], 'cancelled')}\n${color.gray( - S_BAR - )}`; + return `${title}${bar} ${opt(this.options[0], 'cancelled')}\n${bar}`; default: { - return `${title}${color.cyan(S_BAR)} ${this.options + return `${title}${bar} ${this.options .map((option, i) => opt(option, i === this.cursor ? 'active' : 'inactive')) - .join(`\n${color.cyan(S_BAR)} `)}\n${color.cyan(S_BAR_END)}\n`; + .join(`\n${bar} `)}\n${style.formatBar[this.state](S_BAR_END)}\n`; } } }, From d8b1da686ca8ce1618f5a2a9fba73421397d9b1e Mon Sep 17 00:00:00 2001 From: Hyper-Z11 Date: Sun, 16 Nov 2025 13:56:38 +0700 Subject: [PATCH 12/22] format --- packages/prompts/src/autocomplete.ts | 24 +++++++------------ packages/prompts/src/common.ts | 8 +++---- packages/prompts/src/confirm.ts | 11 ++------- packages/prompts/src/group-multi-select.ts | 28 +++++++++------------- packages/prompts/src/multi-select.ts | 13 ++-------- packages/prompts/src/password.ts | 6 ++--- packages/prompts/src/select-key.ts | 2 +- packages/prompts/src/select.ts | 7 +----- packages/prompts/src/text.ts | 2 +- 9 files changed, 33 insertions(+), 68 deletions(-) diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index f8b6f600..0a7433d0 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -1,16 +1,6 @@ import { AutocompletePrompt } from '@clack/core'; import color from 'picocolors'; -import { - type CommonOptions, - S_BAR, - S_BAR_END, - S_CHECKBOX_INACTIVE, - S_CHECKBOX_SELECTED, - S_RADIO_ACTIVE, - S_RADIO_INACTIVE, - symbol, - extendStyle, -} from './common.js'; +import { type CommonOptions, extendStyle, S_BAR, S_BAR_END, symbol } from './common.js'; import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; @@ -216,7 +206,7 @@ export interface AutocompleteMultiSelectOptions extends AutocompleteShare */ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOptions) => { const style = extendStyle(opts.style); - const formatOption = ( + const formatOption = ( option: Option, active: boolean, selectedValues: Value[], @@ -230,11 +220,15 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti : ''; if (active) { - const checkbox = isSelected ? style.checkbox.selected.active : style.checkbox?.unselected?.active; + const checkbox = isSelected + ? style.checkbox.selected.active + : style.checkbox?.unselected?.active; return `${checkbox} ${label}${hint}`; } - const checkbox = isSelected ? style.checkbox.selected.inactive : style.checkbox.unselected.inactive; + const checkbox = isSelected + ? style.checkbox.selected.inactive + : style.checkbox.unselected.inactive; return `${checkbox} ${color.dim(label)}`; }; @@ -258,7 +252,7 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti render() { // Title and symbol const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}`; - const bar = style.formatBar[this.state](S_BAR); + const bar = style.formatBar[this.state](S_BAR); // Selection counter const userInput = this.userInput; diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index 4db13633..87eeda6b 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -1,9 +1,9 @@ import type { Readable, Writable } from 'node:stream'; import type { State } from '@clack/core'; +import deepmerge from '@fastify/deepmerge'; import isUnicodeSupported from 'is-unicode-supported'; import color from 'picocolors'; import type { Formatter } from 'picocolors/types.d.ts'; -import deepmerge from '@fastify/deepmerge'; export const unicode = isUnicodeSupported(); export const isCI = (): boolean => process.env.CI === 'true'; @@ -124,12 +124,12 @@ const defaultStyle: StyleOptions = { active: color.cyan(S_CHECKBOX_ACTIVE), inactive: color.dim(S_CHECKBOX_INACTIVE), }, - disabled: color.dim(S_CHECKBOX_INACTIVE) + disabled: color.dim(S_CHECKBOX_INACTIVE), }, -} +}; const merge = deepmerge(); export const extendStyle = (style?: StyleOptions) => { return merge(defaultStyle, style ?? {}); -} +}; diff --git a/packages/prompts/src/confirm.ts b/packages/prompts/src/confirm.ts index 7cf4112f..8e151402 100644 --- a/packages/prompts/src/confirm.ts +++ b/packages/prompts/src/confirm.ts @@ -1,11 +1,6 @@ import { ConfirmPrompt } from '@clack/core'; import color from 'picocolors'; -import { - type CommonOptions, - S_BAR, - S_BAR_END, - extendStyle, -} from './common.js'; +import { type CommonOptions, extendStyle, S_BAR, S_BAR_END } from './common.js'; export interface ConfirmOptions extends CommonOptions { message: string; @@ -35,9 +30,7 @@ export const confirm = (opts: ConfirmOptions) => { case 'submit': return `${title}${bar} ${color.dim(value)}`; case 'cancel': - return `${title}${bar} ${color.strikethrough( - color.dim(value) - )}\n${bar}`; + return `${title}${bar} ${color.strikethrough(color.dim(value))}\n${bar}`; default: { return `${title}${bar} ${ this.value diff --git a/packages/prompts/src/group-multi-select.ts b/packages/prompts/src/group-multi-select.ts index 38f9e000..dde79b32 100644 --- a/packages/prompts/src/group-multi-select.ts +++ b/packages/prompts/src/group-multi-select.ts @@ -1,15 +1,6 @@ import { GroupMultiSelectPrompt } from '@clack/core'; import color from 'picocolors'; -import { - type CommonOptions, - S_BAR, - S_BAR_END, - S_CHECKBOX_ACTIVE, - S_CHECKBOX_INACTIVE, - S_CHECKBOX_SELECTED, - symbol, - extendStyle, -} from './common.js'; +import { type CommonOptions, extendStyle, S_BAR, S_BAR_END } from './common.js'; import type { Option } from './select.js'; export interface GroupMultiSelectOptions extends CommonOptions { @@ -36,7 +27,7 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => | 'submitted' | 'cancelled', options: (Option & { group: string | boolean })[] = [], - prefixBar: string, + prefixBar: string ) => { const label = option.label ?? String(option.value); const isItem = typeof option.group === 'string'; @@ -103,9 +94,9 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => }, render() { const bar = style.formatBar[this.state](S_BAR); - const barEnd = style.formatBar[this.state](S_BAR_END); + const _barEnd = style.formatBar[this.state](S_BAR_END); - const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}\n`; + const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}\n`; const value = this.value ?? []; switch (this.state) { @@ -122,9 +113,7 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => .filter(({ value: optionValue }) => value.includes(optionValue)) .map((option) => opt(option, 'cancelled'), bar) .join(color.dim(', ')); - return `${title}${bar} ${ - label.trim() ? `${label}\n${bar}` : '' - }`; + return `${title}${bar} ${label.trim() ? `${label}\n${bar}` : ''}`; } case 'error': { const footer = this.error @@ -144,7 +133,12 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => typeof option.group === 'string' && this.options[this.cursor].value === option.group; if (groupActive) { - return opt(option, selected ? 'group-active-selected' : 'group-active', options, bar); + return opt( + option, + selected ? 'group-active-selected' : 'group-active', + options, + bar + ); } if (active && selected) { return opt(option, 'active-selected', options, bar); diff --git a/packages/prompts/src/multi-select.ts b/packages/prompts/src/multi-select.ts index 98c02f35..2aa11dd0 100644 --- a/packages/prompts/src/multi-select.ts +++ b/packages/prompts/src/multi-select.ts @@ -1,11 +1,6 @@ import { MultiSelectPrompt, wrapTextWithPrefix } from '@clack/core'; import color from 'picocolors'; -import { - type CommonOptions, - S_BAR, - S_BAR_END, - extendStyle, -} from './common.js'; +import { type CommonOptions, extendStyle, S_BAR, S_BAR_END } from './common.js'; import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; @@ -119,11 +114,7 @@ export const multiselect = (opts: MultiSelectOptions) => { .filter(({ value: optionValue }) => value.includes(optionValue)) .map((option) => opt(option, 'submitted')) .join(color.dim(', ')) || color.dim('none'); - const wrappedSubmitText = wrapTextWithPrefix( - opts.output, - submitText, - `${bar} ` - ); + const wrappedSubmitText = wrapTextWithPrefix(opts.output, submitText, `${bar} `); return `${title}${wrappedSubmitText}`; } case 'cancel': { diff --git a/packages/prompts/src/password.ts b/packages/prompts/src/password.ts index a992d3b8..b0a7b4c5 100644 --- a/packages/prompts/src/password.ts +++ b/packages/prompts/src/password.ts @@ -1,6 +1,6 @@ import { PasswordPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, S_BAR, S_BAR_END, S_PASSWORD_MASK, extendStyle } from './common.js'; +import { type CommonOptions, extendStyle, S_BAR, S_BAR_END, S_PASSWORD_MASK } from './common.js'; export interface PasswordOptions extends CommonOptions { message: string; @@ -39,9 +39,7 @@ export const password = (opts: PasswordOptions) => { } case 'cancel': { const maskedText = masked ? ` ${color.strikethrough(color.dim(masked))}` : ''; - return `${title}${bar}${maskedText}${ - masked ? `\n${bar}` : '' - }`; + return `${title}${bar}${maskedText}${masked ? `\n${bar}` : ''}`; } default: return `${title}${bar} ${userInput}\n${barEnd}\n`; diff --git a/packages/prompts/src/select-key.ts b/packages/prompts/src/select-key.ts index 87e31774..2ddf228b 100644 --- a/packages/prompts/src/select-key.ts +++ b/packages/prompts/src/select-key.ts @@ -1,6 +1,6 @@ import { SelectKeyPrompt } from '@clack/core'; import color from 'picocolors'; -import { S_BAR, S_BAR_END, extendStyle } from './common.js'; +import { extendStyle, S_BAR, S_BAR_END } from './common.js'; import type { Option, SelectOptions } from './select.js'; export const selectKey = (opts: SelectOptions) => { diff --git a/packages/prompts/src/select.ts b/packages/prompts/src/select.ts index 6d4a11e3..c52570cc 100644 --- a/packages/prompts/src/select.ts +++ b/packages/prompts/src/select.ts @@ -1,11 +1,6 @@ import { SelectPrompt, wrapTextWithPrefix } from '@clack/core'; import color from 'picocolors'; -import { - type CommonOptions, - S_BAR, - S_BAR_END, - extendStyle, -} from './common.js'; +import { type CommonOptions, extendStyle, S_BAR, S_BAR_END } from './common.js'; import { limitOptions } from './limit-options.js'; type Primitive = Readonly; diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index 5c0526c3..4bbae5ad 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -1,6 +1,6 @@ import { TextPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, S_BAR, S_BAR_END, extendStyle } from './common.js'; +import { type CommonOptions, extendStyle, S_BAR, S_BAR_END } from './common.js'; export interface TextOptions extends CommonOptions { message: string; From 0472e3f3f566292de78639430e0fead4bd23fddc Mon Sep 17 00:00:00 2001 From: Hyper-Z11 Date: Sun, 16 Nov 2025 15:10:41 +0700 Subject: [PATCH 13/22] some fixes --- packages/prompts/src/autocomplete.ts | 8 +++--- packages/prompts/src/common.ts | 33 +++++++++------------- packages/prompts/src/confirm.ts | 2 +- packages/prompts/src/group-multi-select.ts | 9 +++--- packages/prompts/src/multi-select.ts | 2 +- packages/prompts/src/password.ts | 2 +- packages/prompts/src/select-key.ts | 2 +- packages/prompts/src/select.ts | 2 +- packages/prompts/src/text.ts | 2 +- 9 files changed, 28 insertions(+), 34 deletions(-) diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index 0a7433d0..34f4176c 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -1,6 +1,6 @@ import { AutocompletePrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, extendStyle, S_BAR, S_BAR_END, symbol } from './common.js'; +import { type CommonOptions, extendStyle, S_BAR, S_BAR_END, } from './common.js'; import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; @@ -67,7 +67,7 @@ export interface AutocompleteOptions extends AutocompleteSharedOptions(opts: AutocompleteOptions) => { - const style = extendStyle(opts.style); + const style = extendStyle(opts.theme); const prompt = new AutocompletePrompt({ options: opts.options, initialValue: opts.initialValue ? [opts.initialValue] : undefined, @@ -81,7 +81,7 @@ export const autocomplete = (opts: AutocompleteOptions) => { validate: opts.validate, render() { // Title and message display - const headings = [`${color.gray(S_BAR)}`, `${symbol(this.state)} ${opts.message}`]; + const headings = [`${color.gray(S_BAR)}`, `${style.prefix[this.state]} ${opts.message}`]; const userInput = this.userInput; const valueAsString = String(this.value ?? ''); const options = this.options; @@ -205,7 +205,7 @@ export interface AutocompleteMultiSelectOptions extends AutocompleteShare * Integrated autocomplete multiselect - combines type-ahead filtering with multiselect in one UI */ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOptions) => { - const style = extendStyle(opts.style); + const style = extendStyle(opts.theme); const formatOption = ( option: Option, active: boolean, diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index 87eeda6b..06939adb 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -3,7 +3,6 @@ import type { State } from '@clack/core'; import deepmerge from '@fastify/deepmerge'; import isUnicodeSupported from 'is-unicode-supported'; import color from 'picocolors'; -import type { Formatter } from 'picocolors/types.d.ts'; export const unicode = isUnicodeSupported(); export const isCI = (): boolean => process.env.CI === 'true'; @@ -59,24 +58,12 @@ export interface CommonOptions { input?: Readable; output?: Writable; signal?: AbortSignal; - style?: StyleOptions; + theme?: ThemeOptions; } -export interface StyleOptions { - formatBar?: { - initial?: Formatter; - active?: Formatter; - cancel?: Formatter; - error?: Formatter; - submit?: Formatter; - }; - prefix?: { - initial?: string; - active?: string; - cancel?: string; - error?: string; - submit?: string; - }; +export interface ThemeOptions { + formatBar?: { [K in State]?: (str: string) => string }; + prefix?: { [K in State]?: string }; radio?: { active?: string; inactive?: string; @@ -95,7 +82,7 @@ export interface StyleOptions { }; } -const defaultStyle: StyleOptions = { +const defaultStyle: ThemeOptions = { formatBar: { initial: color.cyan, active: color.cyan, @@ -128,8 +115,14 @@ const defaultStyle: StyleOptions = { }, }; +type DeepRequired = T extends object + ? T extends (...args: any[]) => any + ? T + : { [K in keyof T]-?: DeepRequired } + : T; + const merge = deepmerge(); -export const extendStyle = (style?: StyleOptions) => { - return merge(defaultStyle, style ?? {}); +export const extendStyle = (style?: ThemeOptions) => { + return merge(defaultStyle, style ?? {}) as DeepRequired; }; diff --git a/packages/prompts/src/confirm.ts b/packages/prompts/src/confirm.ts index 8e151402..290a359a 100644 --- a/packages/prompts/src/confirm.ts +++ b/packages/prompts/src/confirm.ts @@ -9,7 +9,7 @@ export interface ConfirmOptions extends CommonOptions { initialValue?: boolean; } export const confirm = (opts: ConfirmOptions) => { - const style = extendStyle(opts.style); + const style = extendStyle(opts.theme); const active = opts.active ?? 'Yes'; const inactive = opts.inactive ?? 'No'; return new ConfirmPrompt({ diff --git a/packages/prompts/src/group-multi-select.ts b/packages/prompts/src/group-multi-select.ts index dde79b32..7fb5c151 100644 --- a/packages/prompts/src/group-multi-select.ts +++ b/packages/prompts/src/group-multi-select.ts @@ -13,7 +13,7 @@ export interface GroupMultiSelectOptions extends CommonOptions { groupSpacing?: number; } export const groupMultiselect = (opts: GroupMultiSelectOptions) => { - const style = extendStyle(opts.style); + const style = extendStyle(opts.theme); const { selectableGroups = true, groupSpacing = 0 } = opts; const opt = ( option: Option & { group: string | boolean }, @@ -103,7 +103,7 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => case 'submit': { const selectedOptions = this.options .filter(({ value: optionValue }) => value.includes(optionValue)) - .map((option) => opt(option, 'submitted'), bar); + .map((option) => opt(option, 'submitted', this.options, bar)); const optionsText = selectedOptions.length === 0 ? '' : ` ${selectedOptions.join(color.dim(', '))}`; return `${title}${bar}${optionsText}`; @@ -111,7 +111,7 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => case 'cancel': { const label = this.options .filter(({ value: optionValue }) => value.includes(optionValue)) - .map((option) => opt(option, 'cancelled'), bar) + .map((option) => opt(option, 'cancelled', this.options, bar)) .join(color.dim(', ')); return `${title}${bar} ${label.trim() ? `${label}\n${bar}` : ''}`; } @@ -166,7 +166,8 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => optionText = opt( option, selected ? 'group-active-selected' : 'group-active', - options + options, + bar ); } else if (active && selected) { optionText = opt(option, 'active-selected', options, bar); diff --git a/packages/prompts/src/multi-select.ts b/packages/prompts/src/multi-select.ts index 2aa11dd0..b7160b52 100644 --- a/packages/prompts/src/multi-select.ts +++ b/packages/prompts/src/multi-select.ts @@ -20,7 +20,7 @@ const computeLabel = (label: string, format: (text: string) => string) => { }; export const multiselect = (opts: MultiSelectOptions) => { - const style = extendStyle(opts.style); + const style = extendStyle(opts.theme); const opt = ( option: Option, state: diff --git a/packages/prompts/src/password.ts b/packages/prompts/src/password.ts index b0a7b4c5..19fe40ed 100644 --- a/packages/prompts/src/password.ts +++ b/packages/prompts/src/password.ts @@ -9,7 +9,7 @@ export interface PasswordOptions extends CommonOptions { clearOnError?: boolean; } export const password = (opts: PasswordOptions) => { - const style = extendStyle(opts.style); + const style = extendStyle(opts.theme); return new PasswordPrompt({ validate: opts.validate, diff --git a/packages/prompts/src/select-key.ts b/packages/prompts/src/select-key.ts index 2ddf228b..03929a56 100644 --- a/packages/prompts/src/select-key.ts +++ b/packages/prompts/src/select-key.ts @@ -4,7 +4,7 @@ import { extendStyle, S_BAR, S_BAR_END } from './common.js'; import type { Option, SelectOptions } from './select.js'; export const selectKey = (opts: SelectOptions) => { - const style = extendStyle(opts.style); + const style = extendStyle(opts.theme); const opt = ( option: Option, state: 'inactive' | 'active' | 'selected' | 'cancelled' = 'inactive' diff --git a/packages/prompts/src/select.ts b/packages/prompts/src/select.ts index c52570cc..160d6c99 100644 --- a/packages/prompts/src/select.ts +++ b/packages/prompts/src/select.ts @@ -65,7 +65,7 @@ export interface SelectOptions extends CommonOptions { } export const select = (opts: SelectOptions) => { - const style = extendStyle(opts.style); + const style = extendStyle(opts.theme); const opt = ( option: Option, state: 'inactive' | 'active' | 'selected' | 'cancelled' | 'disabled' diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index 4bbae5ad..45a1586b 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -11,7 +11,7 @@ export interface TextOptions extends CommonOptions { } export const text = (opts: TextOptions) => { - const style = extendStyle(opts.style); + const style = extendStyle(opts.theme); return new TextPrompt({ validate: opts.validate, From 972cef042d735d8fd0fc1e1da92aa210da059102 Mon Sep 17 00:00:00 2001 From: Hyper-Z11 Date: Sun, 16 Nov 2025 15:29:23 +0700 Subject: [PATCH 14/22] update common.ts --- packages/prompts/src/common.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index 06939adb..e2798460 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -40,20 +40,6 @@ export const S_SUCCESS = unicodeOr('◆', '*'); export const S_WARN = unicodeOr('▲', '!'); export const S_ERROR = unicodeOr('■', 'x'); -export const symbol = (state: State) => { - switch (state) { - case 'initial': - case 'active': - return color.cyan(S_STEP_ACTIVE); - case 'cancel': - return color.red(S_STEP_CANCEL); - case 'error': - return color.yellow(S_STEP_ERROR); - case 'submit': - return color.green(S_STEP_SUBMIT); - } -}; - export interface CommonOptions { input?: Readable; output?: Writable; @@ -111,7 +97,7 @@ const defaultStyle: ThemeOptions = { active: color.cyan(S_CHECKBOX_ACTIVE), inactive: color.dim(S_CHECKBOX_INACTIVE), }, - disabled: color.dim(S_CHECKBOX_INACTIVE), + disabled: color.gray(S_CHECKBOX_INACTIVE), }, }; From 865ac30049a78e3a5d7e6997a27fdf5d839e08b4 Mon Sep 17 00:00:00 2001 From: Hyper-Z11 Date: Sun, 16 Nov 2025 15:47:33 +0700 Subject: [PATCH 15/22] update snapshots --- packages/prompts/src/autocomplete.ts | 4 +- .../__snapshots__/autocomplete.test.ts.snap | 57 +- .../__snapshots__/multi-select.test.ts.snap | 8 +- .../test/__snapshots__/path.test.ts.snap | 518 +----------------- 4 files changed, 42 insertions(+), 545 deletions(-) diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index 34f4176c..d29f9fdf 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -1,6 +1,6 @@ import { AutocompletePrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, extendStyle, S_BAR, S_BAR_END, } from './common.js'; +import { type CommonOptions, extendStyle, S_BAR, S_BAR_END } from './common.js'; import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; @@ -277,7 +277,7 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti // Render prompt state switch (this.state) { case 'submit': { - return `${title}${bar} ${color.dim(`${this.selectedValues.length} items selected`)}`; + return `${title}\n${bar} ${color.dim(`${this.selectedValues.length} items selected`)}`; } case 'cancel': { return `${title}\n${bar} ${color.strikethrough(color.dim(userInput))}`; diff --git a/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap b/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap index 632967dc..e8fdbbfa 100644 --- a/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap +++ b/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap @@ -341,9 +341,8 @@ exports[`autocompleteMultiselect > can be aborted by a signal 1`] = ` "", "│ ◆ foo - │ Search: _ -│ ◻ Apple +│ ◻ Apple │ ◻ Banana │ ◻ Cherry │ ◻ Grape @@ -361,46 +360,45 @@ exports[`autocompleteMultiselect > can use navigation keys to select options 1`] "", "│ ◆ Select fruits - │ Search: _ -│ ◻ Apple +│ ◻ Apple │ ◻ Banana │ ◻ Cherry │ ◻ Grape │ ◻ Orange │ ↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search └", - "", - "", + "", + "", "", "│ Search:  │ ◻ Apple -│ ◻ Banana +│ ◻ Banana │ ◻ Cherry │ ◻ Grape │ ◻ Orange │ ↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search └", - "", - "", + "", + "", "", "│ ◼ Banana", "", - "", - "", + "", + "", "", "│ ◼ Banana -│ ◻ Cherry +│ ◻ Cherry │ ◻ Grape │ ◻ Orange │ ↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search └", - "", - "", + "", + "", "", "│ ◼ Cherry", "", - "", + "", "", "", "◇ Select fruits @@ -416,34 +414,31 @@ exports[`autocompleteMultiselect > renders error when empty selection & required "", "│ ◆ Select a fruit - │ Search: _ -│ ◻ Apple +│ ◻ Apple │ ◻ Banana │ ◻ Cherry │ ◻ Grape │ ◻ Orange │ ↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search └", - "", + "", "", "", "▲ Select a fruit - -│ Search: _ -│ Please select at least one item -│ ◻ Apple -│ ◻ Banana -│ ◻ Cherry -│ ◻ Grape -│ ◻ Orange -│ ↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search -└", - "", +│ Search: _ +│ Please select at least one item +│ ◻ Apple +│ ◻ Banana +│ ◻ Cherry +│ ◻ Grape +│ ◻ Orange +│ ↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search +└", + "", "", "", "◆ Select a fruit - │ Search: _ │ ◼ Apple │ ◻ Banana @@ -452,7 +447,7 @@ exports[`autocompleteMultiselect > renders error when empty selection & required │ ◻ Orange │ ↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search └", - "", + "", "", "", "◇ Select a fruit diff --git a/packages/prompts/test/__snapshots__/multi-select.test.ts.snap b/packages/prompts/test/__snapshots__/multi-select.test.ts.snap index 0a064065..6891c9d2 100644 --- a/packages/prompts/test/__snapshots__/multi-select.test.ts.snap +++ b/packages/prompts/test/__snapshots__/multi-select.test.ts.snap @@ -691,8 +691,8 @@ exports[`multiselect (isCI = false) > wraps long messages 1`] = ` "", "", "◇ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo │ opt0", " ", @@ -1425,8 +1425,8 @@ exports[`multiselect (isCI = true) > wraps long messages 1`] = ` "", "", "◇ foo foo foo foo foo foo foo -│ foo foo foo foo foo foo -│ foo foo foo foo foo foo foo +│ foo foo foo foo foo foo +│ foo foo foo foo foo foo foo │ opt0", " ", diff --git a/packages/prompts/test/__snapshots__/path.test.ts.snap b/packages/prompts/test/__snapshots__/path.test.ts.snap index f96e4641..396bc11f 100644 --- a/packages/prompts/test/__snapshots__/path.test.ts.snap +++ b/packages/prompts/test/__snapshots__/path.test.ts.snap @@ -7,11 +7,10 @@ exports[`text (isCI = false) > can cancel 1`] = ` ◆ foo │ │ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip +│ No matches found │ ↑/↓ to select • Enter: confirm • Type: to search └", - "", + "", "", "", "■ foo @@ -22,101 +21,21 @@ exports[`text (isCI = false) > can cancel 1`] = ` ] `; -exports[`text (isCI = false) > cannot submit unknown value 1`] = ` +exports[`text (isCI = false) > renders cancelled value if one set 1`] = ` [ "", "│ ◆ foo │ │ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "│ Search: /tmp/_█ │ No matches found │ ↑/↓ to select • Enter: confirm • Type: to search └", "", - "", - "", - "▲ foo -│ -│ Search: /tmp/_█ -│ No matches found -│ Please select a path -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◆ foo -│ -│ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", "", - "", - "│ Search: /tmp/b█ -│ ● /tmp/bar -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -│ /tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = false) > initialValue sets the value 1`] = ` -[ - "", - "│ -◆ foo -│ -│ Search: /tmp/bar█ -│ ● /tmp/bar -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -│ /tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = false) > renders cancelled value if one set 1`] = ` -[ - "", - "│ -◆ foo -│ -│ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", + "", + "│ Search: /tmp/x█", "", - "", - "│ Search: /tmp/x█ -│ No matches found -│ ↑/↓ to select • Enter: confirm • Type: to search -└", "", "", "", @@ -133,174 +52,6 @@ exports[`text (isCI = false) > renders cancelled value if one set 1`] = ` ] `; -exports[`text (isCI = false) > renders message 1`] = ` -[ - "", - "│ -◆ foo -│ -│ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -│ /tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = false) > renders submitted value 1`] = ` -[ - "", - "│ -◆ foo -│ -│ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "│ Search: /tmp/b█ -│ ● /tmp/bar -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "│ Search: /tmp/ba█", - "", - "", - "", - "", - "◇ foo -│ /tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = false) > validation errors render and clear (using Error) 1`] = ` -[ - "", - "│ -◆ foo -│ -│ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "│ Search: /tmp/r█ -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "▲ foo -│ -│ Search: /tmp/r█ -│ should be /tmp/bar -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◆ foo -│ -│ Search: /tmp/█ -│ ○ /tmp/bar -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "│ Search: /tmp/b█ -│ ● /tmp/bar -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -│ /tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = false) > validation errors render and clear 1`] = ` -[ - "", - "│ -◆ foo -│ -│ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "│ Search: /tmp/r█ -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "▲ foo -│ -│ Search: /tmp/r█ -│ should be /tmp/bar -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◆ foo -│ -│ Search: /tmp/█ -│ ○ /tmp/bar -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "│ Search: /tmp/b█ -│ ● /tmp/bar -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -│ /tmp/bar", - " -", - "", -] -`; - exports[`text (isCI = true) > can cancel 1`] = ` [ "", @@ -308,11 +59,10 @@ exports[`text (isCI = true) > can cancel 1`] = ` ◆ foo │ │ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip +│ No matches found │ ↑/↓ to select • Enter: confirm • Type: to search └", - "", + "", "", "", "■ foo @@ -323,101 +73,21 @@ exports[`text (isCI = true) > can cancel 1`] = ` ] `; -exports[`text (isCI = true) > cannot submit unknown value 1`] = ` +exports[`text (isCI = true) > renders cancelled value if one set 1`] = ` [ "", "│ ◆ foo │ │ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "│ Search: /tmp/_█ │ No matches found │ ↑/↓ to select • Enter: confirm • Type: to search └", "", - "", - "", - "▲ foo -│ -│ Search: /tmp/_█ -│ No matches found -│ Please select a path -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◆ foo -│ -│ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", "", - "", - "│ Search: /tmp/b█ -│ ● /tmp/bar -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -│ /tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = true) > initialValue sets the value 1`] = ` -[ - "", - "│ -◆ foo -│ -│ Search: /tmp/bar█ -│ ● /tmp/bar -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -│ /tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = true) > renders cancelled value if one set 1`] = ` -[ - "", - "│ -◆ foo -│ -│ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", + "", + "│ Search: /tmp/x█", "", - "", - "│ Search: /tmp/x█ -│ No matches found -│ ↑/↓ to select • Enter: confirm • Type: to search -└", "", "", "", @@ -433,171 +103,3 @@ exports[`text (isCI = true) > renders cancelled value if one set 1`] = ` "", ] `; - -exports[`text (isCI = true) > renders message 1`] = ` -[ - "", - "│ -◆ foo -│ -│ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -│ /tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = true) > renders submitted value 1`] = ` -[ - "", - "│ -◆ foo -│ -│ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "│ Search: /tmp/b█ -│ ● /tmp/bar -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "│ Search: /tmp/ba█", - "", - "", - "", - "", - "◇ foo -│ /tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = true) > validation errors render and clear (using Error) 1`] = ` -[ - "", - "│ -◆ foo -│ -│ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "│ Search: /tmp/r█ -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "▲ foo -│ -│ Search: /tmp/r█ -│ should be /tmp/bar -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◆ foo -│ -│ Search: /tmp/█ -│ ○ /tmp/bar -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "│ Search: /tmp/b█ -│ ● /tmp/bar -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -│ /tmp/bar", - " -", - "", -] -`; - -exports[`text (isCI = true) > validation errors render and clear 1`] = ` -[ - "", - "│ -◆ foo -│ -│ Search: /tmp/█ -│ ● /tmp/bar -│ ○ /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "│ Search: /tmp/r█ -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "▲ foo -│ -│ Search: /tmp/r█ -│ should be /tmp/bar -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◆ foo -│ -│ Search: /tmp/█ -│ ○ /tmp/bar -│ ● /tmp/root.zip -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "│ Search: /tmp/b█ -│ ● /tmp/bar -│ ↑/↓ to select • Enter: confirm • Type: to search -└", - "", - "", - "", - "◇ foo -│ /tmp/bar", - " -", - "", -] -`; From 3b4d03a39d2d35aff1623dbe430d3be441115d24 Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Sun, 16 Nov 2025 15:51:02 +0700 Subject: [PATCH 16/22] update path snapshot --- .../test/__snapshots__/path.test.ts.snap | 518 +++++++++++++++++- 1 file changed, 508 insertions(+), 10 deletions(-) diff --git a/packages/prompts/test/__snapshots__/path.test.ts.snap b/packages/prompts/test/__snapshots__/path.test.ts.snap index 396bc11f..ff0ceef8 100644 --- a/packages/prompts/test/__snapshots__/path.test.ts.snap +++ b/packages/prompts/test/__snapshots__/path.test.ts.snap @@ -7,10 +7,11 @@ exports[`text (isCI = false) > can cancel 1`] = ` ◆ foo │ │ Search: /tmp/█ -│ No matches found +│ ● /tmp/bar +│ ○ /tmp/root.zip │ ↑/↓ to select • Enter: confirm • Type: to search └", - "", + "", "", "", "■ foo @@ -21,21 +22,101 @@ exports[`text (isCI = false) > can cancel 1`] = ` ] `; -exports[`text (isCI = false) > renders cancelled value if one set 1`] = ` +exports[`text (isCI = false) > cannot submit unknown value 1`] = ` [ "", "│ ◆ foo │ │ Search: /tmp/█ +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: /tmp/_█ │ No matches found │ ↑/↓ to select • Enter: confirm • Type: to search └", "", + "", + "", + "▲ foo +│ +│ Search: /tmp/_█ +│ No matches found +│ Please select a path +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◆ foo +│ +│ Search: /tmp/█ +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", "", - "", - "│ Search: /tmp/x█", + "", + "│ Search: /tmp/b█ +│ ● /tmp/bar +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +│ /tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = false) > initialValue sets the value 1`] = ` +[ + "", + "│ +◆ foo +│ +│ Search: /tmp/bar█ +│ ● /tmp/bar +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +│ /tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = false) > renders cancelled value if one set 1`] = ` +[ + "", + "│ +◆ foo +│ +│ Search: /tmp/█ +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", "", + "", + "│ Search: /tmp/x█ +│ No matches found +│ ↑/↓ to select • Enter: confirm • Type: to search +└", "", "", "", @@ -52,19 +133,188 @@ exports[`text (isCI = false) > renders cancelled value if one set 1`] = ` ] `; -exports[`text (isCI = true) > can cancel 1`] = ` +exports[`text (isCI = false) > renders message 1`] = ` [ "", "│ ◆ foo │ │ Search: /tmp/█ -│ No matches found +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +│ /tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = false) > renders submitted value 1`] = ` +[ + "", + "│ +◆ foo +│ +│ Search: /tmp/█ +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: /tmp/b█ +│ ● /tmp/bar +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: /tmp/ba█", + "", + "", + "", + "", + "◇ foo +│ /tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = false) > validation errors render and clear (using Error) 1`] = ` +[ + "", + "│ +◆ foo +│ +│ Search: /tmp/█ +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: /tmp/r█ +│ ● /tmp/root.zip │ ↑/↓ to select • Enter: confirm • Type: to search └", "", "", "", + "▲ foo +│ +│ Search: /tmp/r█ +│ should be /tmp/bar +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◆ foo +│ +│ Search: /tmp/█ +│ ○ /tmp/bar +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: /tmp/b█ +│ ● /tmp/bar +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +│ /tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = false) > validation errors render and clear 1`] = ` +[ + "", + "│ +◆ foo +│ +│ Search: /tmp/█ +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: /tmp/r█ +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "▲ foo +│ +│ Search: /tmp/r█ +│ should be /tmp/bar +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◆ foo +│ +│ Search: /tmp/█ +│ ○ /tmp/bar +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: /tmp/b█ +│ ● /tmp/bar +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +│ /tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = true) > can cancel 1`] = ` +[ + "", + "│ +◆ foo +│ +│ Search: /tmp/█ +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", "■ foo │ /tmp/", " @@ -73,21 +323,101 @@ exports[`text (isCI = true) > can cancel 1`] = ` ] `; -exports[`text (isCI = true) > renders cancelled value if one set 1`] = ` +exports[`text (isCI = true) > cannot submit unknown value 1`] = ` [ "", "│ ◆ foo │ │ Search: /tmp/█ +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: /tmp/_█ │ No matches found │ ↑/↓ to select • Enter: confirm • Type: to search └", "", + "", + "", + "▲ foo +│ +│ Search: /tmp/_█ +│ No matches found +│ Please select a path +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◆ foo +│ +│ Search: /tmp/█ +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", "", - "", - "│ Search: /tmp/x█", + "", + "│ Search: /tmp/b█ +│ ● /tmp/bar +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +│ /tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = true) > initialValue sets the value 1`] = ` +[ + "", + "│ +◆ foo +│ +│ Search: /tmp/bar█ +│ ● /tmp/bar +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +│ /tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = true) > renders cancelled value if one set 1`] = ` +[ + "", + "│ +◆ foo +│ +│ Search: /tmp/█ +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", "", + "", + "│ Search: /tmp/x█ +│ No matches found +│ ↑/↓ to select • Enter: confirm • Type: to search +└", "", "", "", @@ -103,3 +433,171 @@ exports[`text (isCI = true) > renders cancelled value if one set 1`] = ` "", ] `; + +exports[`text (isCI = true) > renders message 1`] = ` +[ + "", + "│ +◆ foo +│ +│ Search: /tmp/█ +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +│ /tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = true) > renders submitted value 1`] = ` +[ + "", + "│ +◆ foo +│ +│ Search: /tmp/█ +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: /tmp/b█ +│ ● /tmp/bar +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: /tmp/ba█", + "", + "", + "", + "", + "◇ foo +│ /tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = true) > validation errors render and clear (using Error) 1`] = ` +[ + "", + "│ +◆ foo +│ +│ Search: /tmp/█ +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: /tmp/r█ +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "▲ foo +│ +│ Search: /tmp/r█ +│ should be /tmp/bar +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◆ foo +│ +│ Search: /tmp/█ +│ ○ /tmp/bar +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: /tmp/b█ +│ ● /tmp/bar +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +│ /tmp/bar", + " +", + "", +] +`; + +exports[`text (isCI = true) > validation errors render and clear 1`] = ` +[ + "", + "│ +◆ foo +│ +│ Search: /tmp/█ +│ ● /tmp/bar +│ ○ /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: /tmp/r█ +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "▲ foo +│ +│ Search: /tmp/r█ +│ should be /tmp/bar +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◆ foo +│ +│ Search: /tmp/█ +│ ○ /tmp/bar +│ ● /tmp/root.zip +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: /tmp/b█ +│ ● /tmp/bar +│ ↑/↓ to select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ foo +│ /tmp/bar", + " +", + "", +] +`; From d19425225b55aa558b0d14779c0573e06ee1103d Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Sun, 16 Nov 2025 15:58:03 +0700 Subject: [PATCH 17/22] update group-multiselect --- packages/prompts/src/group-multi-select.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/prompts/src/group-multi-select.ts b/packages/prompts/src/group-multi-select.ts index 7fb5c151..22400eca 100644 --- a/packages/prompts/src/group-multi-select.ts +++ b/packages/prompts/src/group-multi-select.ts @@ -94,7 +94,7 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => }, render() { const bar = style.formatBar[this.state](S_BAR); - const _barEnd = style.formatBar[this.state](S_BAR_END); + const barEnd = style.formatBar[this.state](S_BAR_END); const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}\n`; const value = this.value ?? []; @@ -119,7 +119,7 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => const footer = this.error .split('\n') .map((ln, i) => - i === 0 ? `${color.yellow(S_BAR_END)} ${color.yellow(ln)}` : ` ${ln}` + i === 0 ? `${barEnd} ${style.formatBar[this.state](ln)}` : ` ${ln}` ) .join('\n'); return `${title}${bar} ${this.options @@ -181,7 +181,7 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => }) .join(`\n${bar}`); const optionsPrefix = optionsText.startsWith('\n') ? '' : ' '; - return `${title}${bar}${optionsPrefix}${optionsText}\n${color.cyan(S_BAR_END)}\n`; + return `${title}${bar}${optionsPrefix}${optionsText}\n${barEnd}\n`; } } }, From d74196d3da39ad7908fb45310576f157eb63fd62 Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Mon, 17 Nov 2025 14:42:21 +0700 Subject: [PATCH 18/22] update snapshots --- .../test/__snapshots__/autocomplete.test.ts.snap | 5 ++--- .../prompts/test/__snapshots__/select.test.ts.snap | 10 ++++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap b/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap index e8fdbbfa..7ed854de 100644 --- a/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap +++ b/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap @@ -61,7 +61,6 @@ exports[`autocomplete > renders bottom ellipsis when items do not fit 1`] = ` │ ↑/↓ to select • Enter: confirm • Type: to search └", "", - "", "", "◇ Select an option │ Line 0 @@ -136,9 +135,9 @@ exports[`autocomplete > renders top ellipsis when scrolled down and its do not f │ ↑/↓ to select • Enter: confirm • Type: to search └", "", - "", "", - "◇ Select an option + "│ +◇ Select an option │ Option 2", " ", diff --git a/packages/prompts/test/__snapshots__/select.test.ts.snap b/packages/prompts/test/__snapshots__/select.test.ts.snap index 688d900d..4a49135e 100644 --- a/packages/prompts/test/__snapshots__/select.test.ts.snap +++ b/packages/prompts/test/__snapshots__/select.test.ts.snap @@ -80,9 +80,10 @@ exports[`select (isCI = false) > handles mixed size re-renders 1`] = ` └ ", "", - "", "", - "│ ... + "│ +◆ Whatever +│ ... │ ○ Option 0 │ ○ Option 1 │ ○ Option 2 @@ -405,9 +406,10 @@ exports[`select (isCI = true) > handles mixed size re-renders 1`] = ` └ ", "", - "", "", - "│ ... + "│ +◆ Whatever +│ ... │ ○ Option 0 │ ○ Option 1 │ ○ Option 2 From 60ca4c3e103195475a98bd2ac1ac6aded8547158 Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Wed, 19 Nov 2025 05:10:54 +0700 Subject: [PATCH 19/22] inherit --- packages/prompts/src/autocomplete.ts | 12 ++++----- packages/prompts/src/common.ts | 29 ++++++++++++++-------- packages/prompts/src/confirm.ts | 6 ++--- packages/prompts/src/group-multi-select.ts | 6 ++--- packages/prompts/src/multi-select.ts | 6 ++--- packages/prompts/src/password.ts | 4 +-- packages/prompts/src/select-key.ts | 2 +- packages/prompts/src/select.ts | 6 ++--- packages/prompts/src/text.ts | 4 +-- 9 files changed, 41 insertions(+), 34 deletions(-) diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index d29f9fdf..67d40e85 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -1,6 +1,6 @@ import { AutocompletePrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { type CommonOptions, type RadioTheme, type CheckboxTheme, extendStyle, S_BAR, S_BAR_END } from './common.js'; import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; @@ -32,7 +32,7 @@ function getSelectedOptions(values: T[], options: Option[]): Option[] { return results; } -interface AutocompleteSharedOptions extends CommonOptions { +type AutocompleteSharedOptions = CommonOptions & { /** * The message to display to the user. */ @@ -55,7 +55,7 @@ interface AutocompleteSharedOptions extends CommonOptions { validate?: (value: Value | Value[] | undefined) => string | Error | undefined; } -export interface AutocompleteOptions extends AutocompleteSharedOptions { +export interface AutocompleteOptions extends AutocompleteSharedOptions { /** * The initial selected value. */ @@ -67,7 +67,7 @@ export interface AutocompleteOptions extends AutocompleteSharedOptions(opts: AutocompleteOptions) => { - const style = extendStyle(opts.theme); + const style = extendStyle(opts.theme); const prompt = new AutocompletePrompt({ options: opts.options, initialValue: opts.initialValue ? [opts.initialValue] : undefined, @@ -190,7 +190,7 @@ export const autocomplete = (opts: AutocompleteOptions) => { }; // Type definition for the autocompleteMultiselect component -export interface AutocompleteMultiSelectOptions extends AutocompleteSharedOptions { +export interface AutocompleteMultiSelectOptions extends AutocompleteSharedOptions { /** * The initial selected values */ @@ -205,7 +205,7 @@ export interface AutocompleteMultiSelectOptions extends AutocompleteShare * Integrated autocomplete multiselect - combines type-ahead filtering with multiselect in one UI */ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOptions) => { - const style = extendStyle(opts.theme); + const style = extendStyle(opts.theme); const formatOption = ( option: Option, active: boolean, diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index e2798460..041718ca 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -40,21 +40,26 @@ export const S_SUCCESS = unicodeOr('◆', '*'); export const S_WARN = unicodeOr('▲', '!'); export const S_ERROR = unicodeOr('■', 'x'); -export interface CommonOptions { +export type CommonOptions = { input?: Readable; output?: Writable; signal?: AbortSignal; - theme?: ThemeOptions; -} +} & (TStyle extends object ? { + theme?: { + formatBar?: { [K in State]?: (str: string) => string }; + prefix?: { [K in State]?: string }; + } & TStyle; +} : {}); -export interface ThemeOptions { - formatBar?: { [K in State]?: (str: string) => string }; - prefix?: { [K in State]?: string }; +export interface RadioTheme { radio?: { active?: string; inactive?: string; disabled?: string; - }; + } +} + +export interface CheckboxTheme { checkbox?: { selected?: { active?: string; @@ -65,10 +70,10 @@ export interface ThemeOptions { inactive?: string; }; disabled?: string; - }; + } } -const defaultStyle: ThemeOptions = { +const defaultStyle: CommonOptions['theme'] = { formatBar: { initial: color.cyan, active: color.cyan, @@ -107,8 +112,10 @@ type DeepRequired = T extends object : { [K in keyof T]-?: DeepRequired } : T; +type ExtendStyleType = ({ theme: {} } & CommonOptions)['theme']; + const merge = deepmerge(); -export const extendStyle = (style?: ThemeOptions) => { - return merge(defaultStyle, style ?? {}) as DeepRequired; +export const extendStyle = (style?: ExtendStyleType) => { + return merge(defaultStyle, style ?? {}) as DeepRequired>; }; diff --git a/packages/prompts/src/confirm.ts b/packages/prompts/src/confirm.ts index 290a359a..7445178e 100644 --- a/packages/prompts/src/confirm.ts +++ b/packages/prompts/src/confirm.ts @@ -1,15 +1,15 @@ import { ConfirmPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { type CommonOptions, type RadioTheme, extendStyle, S_BAR, S_BAR_END } from './common.js'; -export interface ConfirmOptions extends CommonOptions { +export interface ConfirmOptions extends CommonOptions { message: string; active?: string; inactive?: string; initialValue?: boolean; } export const confirm = (opts: ConfirmOptions) => { - const style = extendStyle(opts.theme); + const style = extendStyle(opts.theme); const active = opts.active ?? 'Yes'; const inactive = opts.inactive ?? 'No'; return new ConfirmPrompt({ diff --git a/packages/prompts/src/group-multi-select.ts b/packages/prompts/src/group-multi-select.ts index 22400eca..abfc653a 100644 --- a/packages/prompts/src/group-multi-select.ts +++ b/packages/prompts/src/group-multi-select.ts @@ -1,9 +1,9 @@ import { GroupMultiSelectPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { type CommonOptions, type CheckboxTheme, extendStyle, S_BAR, S_BAR_END } from './common.js'; import type { Option } from './select.js'; -export interface GroupMultiSelectOptions extends CommonOptions { +export interface GroupMultiSelectOptions extends CommonOptions { message: string; options: Record[]>; initialValues?: Value[]; @@ -13,7 +13,7 @@ export interface GroupMultiSelectOptions extends CommonOptions { groupSpacing?: number; } export const groupMultiselect = (opts: GroupMultiSelectOptions) => { - const style = extendStyle(opts.theme); + const style = extendStyle(opts.theme); const { selectableGroups = true, groupSpacing = 0 } = opts; const opt = ( option: Option & { group: string | boolean }, diff --git a/packages/prompts/src/multi-select.ts b/packages/prompts/src/multi-select.ts index b7160b52..def3f9b4 100644 --- a/packages/prompts/src/multi-select.ts +++ b/packages/prompts/src/multi-select.ts @@ -1,10 +1,10 @@ import { MultiSelectPrompt, wrapTextWithPrefix } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { type CommonOptions, type CheckboxTheme, extendStyle, S_BAR, S_BAR_END } from './common.js'; import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; -export interface MultiSelectOptions extends CommonOptions { +export interface MultiSelectOptions extends CommonOptions { message: string; options: Option[]; initialValues?: Value[]; @@ -20,7 +20,7 @@ const computeLabel = (label: string, format: (text: string) => string) => { }; export const multiselect = (opts: MultiSelectOptions) => { - const style = extendStyle(opts.theme); + const style = extendStyle(opts.theme); const opt = ( option: Option, state: diff --git a/packages/prompts/src/password.ts b/packages/prompts/src/password.ts index 19fe40ed..60dbb262 100644 --- a/packages/prompts/src/password.ts +++ b/packages/prompts/src/password.ts @@ -2,14 +2,14 @@ import { PasswordPrompt } from '@clack/core'; import color from 'picocolors'; import { type CommonOptions, extendStyle, S_BAR, S_BAR_END, S_PASSWORD_MASK } from './common.js'; -export interface PasswordOptions extends CommonOptions { +export interface PasswordOptions extends CommonOptions<{}> { message: string; mask?: string; validate?: (value: string | undefined) => string | Error | undefined; clearOnError?: boolean; } export const password = (opts: PasswordOptions) => { - const style = extendStyle(opts.theme); + const style = extendStyle<{}>(opts.theme); return new PasswordPrompt({ validate: opts.validate, diff --git a/packages/prompts/src/select-key.ts b/packages/prompts/src/select-key.ts index 03929a56..f0d1f4ba 100644 --- a/packages/prompts/src/select-key.ts +++ b/packages/prompts/src/select-key.ts @@ -4,7 +4,7 @@ import { extendStyle, S_BAR, S_BAR_END } from './common.js'; import type { Option, SelectOptions } from './select.js'; export const selectKey = (opts: SelectOptions) => { - const style = extendStyle(opts.theme); + const style = extendStyle<{}>(opts.theme); const opt = ( option: Option, state: 'inactive' | 'active' | 'selected' | 'cancelled' = 'inactive' diff --git a/packages/prompts/src/select.ts b/packages/prompts/src/select.ts index fb73be82..419975ad 100644 --- a/packages/prompts/src/select.ts +++ b/packages/prompts/src/select.ts @@ -1,6 +1,6 @@ import { SelectPrompt, wrapTextWithPrefix } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { type CommonOptions, type RadioTheme, extendStyle, S_BAR, S_BAR_END } from './common.js'; import { limitOptions } from './limit-options.js'; type Primitive = Readonly; @@ -57,7 +57,7 @@ export type Option = Value extends Primitive disabled?: boolean; }; -export interface SelectOptions extends CommonOptions { +export interface SelectOptions extends CommonOptions { message: string; options: Option[]; initialValue?: Value; @@ -75,7 +75,7 @@ const computeLabel = (label: string, format: (text: string) => string) => { }; export const select = (opts: SelectOptions) => { - const style = extendStyle(opts.theme); + const style = extendStyle(opts.theme); const opt = ( option: Option, state: 'inactive' | 'active' | 'selected' | 'cancelled' | 'disabled' diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index 45a1586b..6ea698c5 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -2,7 +2,7 @@ import { TextPrompt } from '@clack/core'; import color from 'picocolors'; import { type CommonOptions, extendStyle, S_BAR, S_BAR_END } from './common.js'; -export interface TextOptions extends CommonOptions { +export interface TextOptions extends CommonOptions<{}> { message: string; placeholder?: string; defaultValue?: string; @@ -11,7 +11,7 @@ export interface TextOptions extends CommonOptions { } export const text = (opts: TextOptions) => { - const style = extendStyle(opts.theme); + const style = extendStyle<{}>(opts.theme); return new TextPrompt({ validate: opts.validate, From d16b1ad146a6a3ae0bdb5b8f9c2fad3f6e52d6b9 Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Fri, 21 Nov 2025 03:25:19 +0700 Subject: [PATCH 20/22] flat theme --- packages/prompts/src/autocomplete.ts | 40 ++++---- packages/prompts/src/common.ts | 105 +++++++++------------ packages/prompts/src/confirm.ts | 19 ++-- packages/prompts/src/group-multi-select.ts | 25 ++--- packages/prompts/src/multi-select.ts | 23 +++-- packages/prompts/src/password.ts | 13 ++- packages/prompts/src/select-key.ts | 11 ++- packages/prompts/src/select.ts | 17 ++-- packages/prompts/src/text.ts | 13 ++- 9 files changed, 139 insertions(+), 127 deletions(-) diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index 67d40e85..ba7e1af1 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -1,6 +1,6 @@ import { AutocompletePrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, type RadioTheme, type CheckboxTheme, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { type CommonOptions, type RadioTheme, type CheckboxTheme, getThemeColor, getThemePrefix, extendStyle, S_BAR, S_BAR_END } from './common.js'; import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; @@ -80,14 +80,17 @@ export const autocomplete = (opts: AutocompleteOptions) => { output: opts.output, validate: opts.validate, render() { + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); + // Title and message display - const headings = [`${color.gray(S_BAR)}`, `${style.prefix[this.state]} ${opts.message}`]; + const headings = [`${color.gray(S_BAR)}`, `${style[themePrefix]} ${opts.message}`]; const userInput = this.userInput; const valueAsString = String(this.value ?? ''); const options = this.options; const placeholder = opts.placeholder; const showPlaceholder = valueAsString === '' && placeholder !== undefined; - const bar = style.formatBar[this.state](S_BAR); + const bar = style[themeColor](S_BAR); // Handle different states switch (this.state) { @@ -125,11 +128,11 @@ export const autocomplete = (opts: AutocompleteOptions) => { // No matches message const noResults = this.filteredOptions.length === 0 && userInput - ? [`${bar} ${style.formatBar.error('No matches found')}`] + ? [`${bar} ${style.colorError('No matches found')}`] : []; const validationError = - this.state === 'error' ? [`${bar} ${style.formatBar[this.state](this.error)}`] : []; + this.state === 'error' ? [`${bar} ${style[themeColor](this.error)}`] : []; headings.push( `${bar}`, @@ -147,7 +150,7 @@ export const autocomplete = (opts: AutocompleteOptions) => { const footers = [ `${bar} ${color.dim(instructions.join(' • '))}`, - `${style.formatBar[this.state](S_BAR_END)}`, + `${style[themeColor](S_BAR_END)}`, ]; // Render options with selection @@ -167,8 +170,8 @@ export const autocomplete = (opts: AutocompleteOptions) => { : ''; return active - ? `${style.radio.active} ${label}${hint}` - : `${style.radio.inactive} ${color.dim(label)}${hint}`; + ? `${style.radioActive} ${label}${hint}` + : `${style.radioInactive} ${color.dim(label)}${hint}`; }, maxItems: opts.maxItems, output: opts.output, @@ -221,14 +224,14 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti if (active) { const checkbox = isSelected - ? style.checkbox.selected.active - : style.checkbox?.unselected?.active; + ? style.checkboxSelectedActive + : style.checkboxUnselectedActive; return `${checkbox} ${label}${hint}`; } const checkbox = isSelected - ? style.checkbox.selected.inactive - : style.checkbox.unselected.inactive; + ? style.checkboxSelectedInactive + : style.checkboxUnselectedInactive; return `${checkbox} ${color.dim(label)}`; }; @@ -250,9 +253,12 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti input: opts.input, output: opts.output, render() { + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); + // Title and symbol - const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}`; - const bar = style.formatBar[this.state](S_BAR); + const title = `${color.gray(S_BAR)}\n${style[themePrefix]} ${opts.message}`; + const bar = style[themeColor](S_BAR); // Selection counter const userInput = this.userInput; @@ -294,11 +300,11 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti // No results message const noResults = this.filteredOptions.length === 0 && userInput - ? [`${bar} ${style.formatBar.error('No matches found')}`] + ? [`${bar} ${style.colorError('No matches found')}`] : []; const errorMessage = - this.state === 'error' ? [`${bar} ${style.formatBar[this.state](this.error)}`] : []; + this.state === 'error' ? [`${bar} ${style[themeColor](this.error)}`] : []; // Get limited options for display const displayOptions = limitOptions({ @@ -318,7 +324,7 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti ...errorMessage, ...displayOptions.map((option) => `${bar} ${option}`), `${bar} ${color.dim(instructions.join(' • '))}`, - `${style.formatBar[this.state](S_BAR_END)}`, + `${style[themeColor](S_BAR_END)}`, ].join('\n'); } } diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index 041718ca..cb82384d 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -1,6 +1,5 @@ import type { Readable, Writable } from 'node:stream'; import type { State } from '@clack/core'; -import deepmerge from '@fastify/deepmerge'; import isUnicodeSupported from 'is-unicode-supported'; import color from 'picocolors'; @@ -40,82 +39,68 @@ export const S_SUCCESS = unicodeOr('◆', '*'); export const S_WARN = unicodeOr('▲', '!'); export const S_ERROR = unicodeOr('■', 'x'); +type ColorState = `color${Capitalize}`; +type PrefixState = `prefix${Capitalize}`; + export type CommonOptions = { input?: Readable; output?: Writable; signal?: AbortSignal; } & (TStyle extends object ? { - theme?: { - formatBar?: { [K in State]?: (str: string) => string }; - prefix?: { [K in State]?: string }; - } & TStyle; + theme?: + { [K in ColorState]?: (str: string) => string } & + { [K in PrefixState]?: string } + & TStyle; } : {}); export interface RadioTheme { - radio?: { - active?: string; - inactive?: string; - disabled?: string; - } + radioActive?: string; + radioInactive?: string; + radioDisabled?: string; } export interface CheckboxTheme { - checkbox?: { - selected?: { - active?: string; - inactive?: string; - }; - unselected?: { - active?: string; - inactive?: string; - }; - disabled?: string; - } + checkboxSelectedActive?: string, + checkboxSelectedInactive?: string, + checkboxUnselectedActive?: string, + checkboxUnselectedInactive?: string, + checkboxDisabled?: string, } const defaultStyle: CommonOptions['theme'] = { - formatBar: { - initial: color.cyan, - active: color.cyan, - cancel: color.gray, - error: color.yellow, - submit: color.gray, - }, - prefix: { - initial: color.cyan(S_STEP_ACTIVE), - active: color.cyan(S_STEP_ACTIVE), - cancel: color.red(S_STEP_CANCEL), - error: color.yellow(S_STEP_ERROR), - submit: color.green(S_STEP_SUBMIT), - }, - radio: { - active: color.green(S_RADIO_ACTIVE), - inactive: color.dim(S_RADIO_INACTIVE), - disabled: color.gray(S_RADIO_INACTIVE), - }, - checkbox: { - selected: { - active: color.green(S_CHECKBOX_SELECTED), - inactive: color.green(S_CHECKBOX_SELECTED), - }, - unselected: { - active: color.cyan(S_CHECKBOX_ACTIVE), - inactive: color.dim(S_CHECKBOX_INACTIVE), - }, - disabled: color.gray(S_CHECKBOX_INACTIVE), - }, -}; + colorInitial: color.cyan, + colorActive: color.cyan, + colorCancel: color.gray, + colorError: color.yellow, + colorSubmit: color.gray, -type DeepRequired = T extends object - ? T extends (...args: any[]) => any - ? T - : { [K in keyof T]-?: DeepRequired } - : T; + prefixInitial: color.cyan(S_STEP_ACTIVE), + prefixActive: color.cyan(S_STEP_ACTIVE), + prefixCancel: color.red(S_STEP_CANCEL), + prefixError: color.yellow(S_STEP_ERROR), + prefixSubmit: color.green(S_STEP_SUBMIT), -type ExtendStyleType = ({ theme: {} } & CommonOptions)['theme']; + radioActive: color.green(S_RADIO_ACTIVE), + radioInactive: color.dim(S_RADIO_INACTIVE), + radioDisabled: color.gray(S_RADIO_INACTIVE), + + checkboxSelectedActive: color.green(S_CHECKBOX_SELECTED), + checkboxSelectedInactive: color.green(S_CHECKBOX_SELECTED), + checkboxUnselectedActive: color.cyan(S_CHECKBOX_ACTIVE), + checkboxUnselectedInactive: color.dim(S_CHECKBOX_INACTIVE), + checkboxDisabled: color.gray(S_CHECKBOX_INACTIVE), +}; -const merge = deepmerge(); +type ExtendStyleType = ({ theme: {} } & CommonOptions)['theme']; export const extendStyle = (style?: ExtendStyleType) => { - return merge(defaultStyle, style ?? {}) as DeepRequired>; + return { + ...defaultStyle, + ...(style ?? {}) + } as Required>; }; + +const capitalize = (str: string): string => str[0].toUpperCase() + str.substring(1); + +export const getThemeColor = (state: State) => (`color${capitalize(state)}`) as ColorState; +export const getThemePrefix = (state: State) => (`prefix${capitalize(state)}`) as PrefixState; diff --git a/packages/prompts/src/confirm.ts b/packages/prompts/src/confirm.ts index 7445178e..5beecbf5 100644 --- a/packages/prompts/src/confirm.ts +++ b/packages/prompts/src/confirm.ts @@ -1,6 +1,6 @@ import { ConfirmPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, type RadioTheme, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { type CommonOptions, type RadioTheme, getThemeColor, getThemePrefix, extendStyle, S_BAR, S_BAR_END } from './common.js'; export interface ConfirmOptions extends CommonOptions { message: string; @@ -20,10 +20,13 @@ export const confirm = (opts: ConfirmOptions) => { output: opts.output, initialValue: opts.initialValue ?? true, render() { - const bar = style.formatBar[this.state](S_BAR); - const barEnd = style.formatBar[this.state](S_BAR_END); + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); - const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}\n`; + const bar = style[themeColor](S_BAR); + const barEnd = style[themeColor](S_BAR_END); + + const title = `${color.gray(S_BAR)}\n${style[themePrefix]} ${opts.message}\n`; const value = this.value ? active : inactive; switch (this.state) { @@ -34,12 +37,12 @@ export const confirm = (opts: ConfirmOptions) => { default: { return `${title}${bar} ${ this.value - ? `${style.radio.active} ${active}` - : `${style.radio.inactive} ${color.dim(active)}` + ? `${style.radioActive} ${active}` + : `${style.radioInactive} ${color.dim(active)}` } ${color.dim('/')} ${ !this.value - ? `${style.radio.active} ${inactive}` - : `${style.radio.inactive} ${color.dim(inactive)}` + ? `${style.radioActive} ${inactive}` + : `${style.radioInactive} ${color.dim(inactive)}` }\n${barEnd}\n`; } } diff --git a/packages/prompts/src/group-multi-select.ts b/packages/prompts/src/group-multi-select.ts index abfc653a..3241d61b 100644 --- a/packages/prompts/src/group-multi-select.ts +++ b/packages/prompts/src/group-multi-select.ts @@ -1,6 +1,6 @@ import { GroupMultiSelectPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, type CheckboxTheme, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { type CommonOptions, type CheckboxTheme, getThemeColor, getThemePrefix, extendStyle, S_BAR, S_BAR_END } from './common.js'; import type { Option } from './select.js'; export interface GroupMultiSelectOptions extends CommonOptions { @@ -41,18 +41,18 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => } if (state === 'active') { - return `${spacingPrefix}${color.dim(prefix)}${style.checkbox.unselected.active} ${label}${ + return `${spacingPrefix}${color.dim(prefix)}${style.checkboxUnselectedActive} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } if (state === 'group-active') { - return `${spacingPrefix}${prefix}${style.checkbox.unselected.active} ${color.dim(label)}`; + return `${spacingPrefix}${prefix}${style.checkboxUnselectedActive} ${color.dim(label)}`; } if (state === 'group-active-selected') { - return `${spacingPrefix}${prefix}${style.checkbox.selected.inactive} ${color.dim(label)}`; + return `${spacingPrefix}${prefix}${style.checkboxSelectedInactive} ${color.dim(label)}`; } if (state === 'selected') { - const selectedCheckbox = isItem || selectableGroups ? style.checkbox.selected.inactive : ''; + const selectedCheckbox = isItem || selectableGroups ? style.checkboxSelectedInactive : ''; return `${spacingPrefix}${color.dim(prefix)}${selectedCheckbox} ${color.dim(label)}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; @@ -61,14 +61,14 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => return `${color.strikethrough(color.dim(label))}`; } if (state === 'active-selected') { - return `${spacingPrefix}${color.dim(prefix)}${style.checkbox.selected.active} ${label}${ + return `${spacingPrefix}${color.dim(prefix)}${style.checkboxSelectedActive} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } if (state === 'submitted') { return `${color.dim(label)}`; } - const unselectedCheckbox = isItem || selectableGroups ? style.checkbox.unselected.inactive : ''; + const unselectedCheckbox = isItem || selectableGroups ? style.checkboxUnselectedInactive : ''; return `${spacingPrefix}${color.dim(prefix)}${unselectedCheckbox} ${color.dim(label)}`; }; const required = opts.required ?? true; @@ -93,10 +93,13 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => )}`; }, render() { - const bar = style.formatBar[this.state](S_BAR); - const barEnd = style.formatBar[this.state](S_BAR_END); + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); - const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}\n`; + const bar = style[themeColor](S_BAR); + const barEnd = style[themeColor](S_BAR_END); + + const title = `${color.gray(S_BAR)}\n${style[themePrefix]} ${opts.message}\n`; const value = this.value ?? []; switch (this.state) { @@ -119,7 +122,7 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => const footer = this.error .split('\n') .map((ln, i) => - i === 0 ? `${barEnd} ${style.formatBar[this.state](ln)}` : ` ${ln}` + i === 0 ? `${barEnd} ${style[themeColor](ln)}` : ` ${ln}` ) .join('\n'); return `${title}${bar} ${this.options diff --git a/packages/prompts/src/multi-select.ts b/packages/prompts/src/multi-select.ts index def3f9b4..7e0661eb 100644 --- a/packages/prompts/src/multi-select.ts +++ b/packages/prompts/src/multi-select.ts @@ -1,6 +1,6 @@ import { MultiSelectPrompt, wrapTextWithPrefix } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, type CheckboxTheme, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { type CommonOptions, type CheckboxTheme, getThemeColor, getThemePrefix, extendStyle, S_BAR, S_BAR_END } from './common.js'; import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; @@ -34,17 +34,17 @@ export const multiselect = (opts: MultiSelectOptions) => { ) => { const label = option.label ?? String(option.value); if (state === 'disabled') { - return `${style.checkbox.disabled} ${computeLabel(label, color.gray)}${ + return `${style.checkboxDisabled} ${computeLabel(label, color.gray)}${ option.hint ? ` ${color.dim(`(${option.hint ?? 'disabled'})`)}` : '' }`; } if (state === 'active') { - return `${style.checkbox.unselected.active} ${label}${ + return `${style.checkboxUnselectedActive} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } if (state === 'selected') { - return `${style.checkbox.selected.inactive} ${computeLabel(label, color.dim)}${ + return `${style.checkboxSelectedInactive} ${computeLabel(label, color.dim)}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } @@ -52,14 +52,14 @@ export const multiselect = (opts: MultiSelectOptions) => { return `${computeLabel(label, (text) => color.strikethrough(color.dim(text)))}`; } if (state === 'active-selected') { - return `${style.checkbox.selected.active} ${label}${ + return `${style.checkboxSelectedActive} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; } if (state === 'submitted') { return `${computeLabel(label, color.dim)}`; } - return `${style.checkbox.unselected.inactive} ${computeLabel(label, color.dim)}`; + return `${style.checkboxUnselectedInactive} ${computeLabel(label, color.dim)}`; }; const required = opts.required ?? true; @@ -82,13 +82,16 @@ export const multiselect = (opts: MultiSelectOptions) => { )}`; }, render() { - const bar = style.formatBar[this.state](S_BAR); - const barEnd = style.formatBar[this.state](S_BAR_END); + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); + + const bar = style[themeColor](S_BAR); + const barEnd = style[themeColor](S_BAR_END); const wrappedMessage = wrapTextWithPrefix( opts.output, opts.message, `${bar} `, - `${style.prefix[this.state]} ` + `${style[themePrefix]} ` ); const title = `${color.gray(S_BAR)}\n${wrappedMessage}\n`; const value = this.value ?? []; @@ -133,7 +136,7 @@ export const multiselect = (opts: MultiSelectOptions) => { const footer = this.error .split('\n') .map((ln, i) => - i === 0 ? `${barEnd} ${style.formatBar[this.state](ln)}` : ` ${ln}` + i === 0 ? `${barEnd} ${style[themeColor](ln)}` : ` ${ln}` ) .join('\n'); return `${title}${prefix}${limitOptions({ diff --git a/packages/prompts/src/password.ts b/packages/prompts/src/password.ts index 60dbb262..1b652e2f 100644 --- a/packages/prompts/src/password.ts +++ b/packages/prompts/src/password.ts @@ -1,6 +1,6 @@ import { PasswordPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, extendStyle, S_BAR, S_BAR_END, S_PASSWORD_MASK } from './common.js'; +import { type CommonOptions, getThemeColor, getThemePrefix, extendStyle, S_BAR, S_BAR_END, S_PASSWORD_MASK } from './common.js'; export interface PasswordOptions extends CommonOptions<{}> { message: string; @@ -18,10 +18,13 @@ export const password = (opts: PasswordOptions) => { input: opts.input, output: opts.output, render() { - const bar = style.formatBar[this.state](S_BAR); - const barEnd = style.formatBar[this.state](S_BAR_END); + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); - const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}\n`; + const bar = style[themeColor](S_BAR); + const barEnd = style[themeColor](S_BAR_END); + + const title = `${color.gray(S_BAR)}\n${style[themePrefix]} ${opts.message}\n`; const userInput = this.userInputWithCursor; const masked = this.masked; @@ -31,7 +34,7 @@ export const password = (opts: PasswordOptions) => { if (opts.clearOnError) { this.clear(); } - return `${title.trim()}\n${bar}${maskedText}\n${barEnd} ${style.formatBar[this.state](this.error)}\n`; + return `${title.trim()}\n${bar}${maskedText}\n${barEnd} ${style[themeColor](this.error)}\n`; } case 'submit': { const maskedText = masked ? ` ${color.dim(masked)}` : ''; diff --git a/packages/prompts/src/select-key.ts b/packages/prompts/src/select-key.ts index f0d1f4ba..61a8977a 100644 --- a/packages/prompts/src/select-key.ts +++ b/packages/prompts/src/select-key.ts @@ -1,6 +1,6 @@ import { SelectKeyPrompt } from '@clack/core'; import color from 'picocolors'; -import { extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { extendStyle, getThemeColor, getThemePrefix, S_BAR, S_BAR_END } from './common.js'; import type { Option, SelectOptions } from './select.js'; export const selectKey = (opts: SelectOptions) => { @@ -33,8 +33,11 @@ export const selectKey = (opts: SelectOptions) => { output: opts.output, initialValue: opts.initialValue, render() { - const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}\n`; - const bar = style.formatBar[this.state](S_BAR); + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); + + const title = `${color.gray(S_BAR)}\n${style[themePrefix]} ${opts.message}\n`; + const bar = style[themeColor](S_BAR); switch (this.state) { case 'submit': @@ -47,7 +50,7 @@ export const selectKey = (opts: SelectOptions) => { default: { return `${title}${bar} ${this.options .map((option, i) => opt(option, i === this.cursor ? 'active' : 'inactive')) - .join(`\n${bar} `)}\n${style.formatBar[this.state](S_BAR_END)}\n`; + .join(`\n${bar} `)}\n${style[themeColor](S_BAR_END)}\n`; } } }, diff --git a/packages/prompts/src/select.ts b/packages/prompts/src/select.ts index 419975ad..4c463a0b 100644 --- a/packages/prompts/src/select.ts +++ b/packages/prompts/src/select.ts @@ -1,6 +1,6 @@ import { SelectPrompt, wrapTextWithPrefix } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, type RadioTheme, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { type CommonOptions, type RadioTheme, getThemeColor, getThemePrefix, extendStyle, S_BAR, S_BAR_END } from './common.js'; import { limitOptions } from './limit-options.js'; type Primitive = Readonly; @@ -83,19 +83,19 @@ export const select = (opts: SelectOptions) => { const label = option.label ?? String(option.value); switch (state) { case 'disabled': - return `${style.radio.disabled} ${computeLabel(label, color.gray)}${ + return `${style.radioDisabled} ${computeLabel(label, color.gray)}${ option.hint ? ` ${color.dim(`(${option.hint ?? 'disabled'})`)}` : '' }`; case 'selected': return `${computeLabel(label, color.dim)}`; case 'active': - return `${style.radio.active} ${label}${ + return `${style.radioActive} ${label}${ option.hint ? ` ${color.dim(`(${option.hint})`)}` : '' }`; case 'cancelled': return `${computeLabel(label, (str) => color.strikethrough(color.dim(str)))}`; default: - return `${style.radio.inactive} ${computeLabel(label, color.dim)}`; + return `${style.radioInactive} ${computeLabel(label, color.dim)}`; } }; @@ -106,8 +106,11 @@ export const select = (opts: SelectOptions) => { output: opts.output, initialValue: opts.initialValue, render() { - const bar = style.formatBar[this.state](S_BAR); - const titlePrefix = `${style.prefix[this.state]} `; + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); + + const bar = style[themeColor](S_BAR); + const titlePrefix = `${style[themePrefix]} `; const titlePrefixBar = `${bar} `; const messageLines = wrapTextWithPrefix( opts.output, @@ -146,7 +149,7 @@ export const select = (opts: SelectOptions) => { columnPadding: prefix.length, style: (item, active) => opt(item, item.disabled ? 'disabled' : active ? 'active' : 'inactive'), - }).join(`\n${prefix}`)}\n${style.formatBar[this.state](S_BAR_END)}\n`; + }).join(`\n${prefix}`)}\n${style[themeColor](S_BAR_END)}\n`; } } }, diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index 6ea698c5..57157cd1 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -1,6 +1,6 @@ import { TextPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { type CommonOptions, getThemeColor, getThemePrefix, extendStyle, S_BAR, S_BAR_END } from './common.js'; export interface TextOptions extends CommonOptions<{}> { message: string; @@ -22,10 +22,13 @@ export const text = (opts: TextOptions) => { signal: opts.signal, input: opts.input, render() { - const bar = style.formatBar[this.state](S_BAR); - const barEnd = style.formatBar[this.state](S_BAR_END); + const themeColor = getThemeColor(this.state); + const themePrefix = getThemePrefix(this.state); - const title = `${color.gray(S_BAR)}\n${style.prefix[this.state]} ${opts.message}\n`; + const bar = style[themeColor](S_BAR); + const barEnd = style[themeColor](S_BAR_END); + + const title = `${color.gray(S_BAR)}\n${style[themePrefix]} ${opts.message}\n`; const placeholder = opts.placeholder ? color.inverse(opts.placeholder[0]) + color.dim(opts.placeholder.slice(1)) : color.inverse(color.hidden('_')); @@ -34,7 +37,7 @@ export const text = (opts: TextOptions) => { switch (this.state) { case 'error': { - const errorText = this.error ? ` ${style.formatBar[this.state](this.error)}` : ''; + const errorText = this.error ? ` ${style[themeColor](this.error)}` : ''; return `${title.trim()}\n${bar} ${userInput}\n${barEnd}${errorText}\n`; } case 'submit': { From 3c4c1d50a8aeb1432fcb5c8d587c2634ede4803a Mon Sep 17 00:00:00 2001 From: hyperz111 Date: Fri, 21 Nov 2025 03:26:03 +0700 Subject: [PATCH 21/22] uninstall @fastify/deepmerge --- packages/prompts/package.json | 1 - pnpm-lock.yaml | 95 ++++++++++++++++++++++++++++++----- 2 files changed, 83 insertions(+), 13 deletions(-) diff --git a/packages/prompts/package.json b/packages/prompts/package.json index aa48da60..0a636349 100644 --- a/packages/prompts/package.json +++ b/packages/prompts/package.json @@ -58,7 +58,6 @@ "sisteransi": "^1.0.5" }, "devDependencies": { - "@fastify/deepmerge": "^3.1.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "is-unicode-supported": "^1.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d3e7fa2a..21d2b4f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,9 +86,6 @@ importers: specifier: ^1.0.5 version: 1.0.5 devDependencies: - '@fastify/deepmerge': - specifier: ^3.1.0 - version: 3.1.0 fast-string-width: specifier: ^1.1.0 version: 1.1.0 @@ -103,10 +100,10 @@ importers: version: 4.17.2 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@24.1.0)(jiti@2.5.0) + version: 3.2.4 vitest-ansi-serializer: specifier: ^0.1.2 - version: 0.1.2(vitest@3.2.4(@types/node@24.1.0)(jiti@2.5.0)) + version: 0.1.2(vitest@3.2.4) packages: @@ -395,9 +392,6 @@ packages: cpu: [x64] os: [win32] - '@fastify/deepmerge@3.1.0': - resolution: {integrity: sha512-lCVONBQINyNhM6LLezB6+2afusgEYR4G8xenMsfe+AT+iZ7Ca6upM5Ha8UkZuYSnuMw3GWl/BiPXnLMi/gSxuQ==} - '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} @@ -2126,8 +2120,6 @@ snapshots: '@esbuild/win32-x64@0.25.8': optional: true - '@fastify/deepmerge@3.1.0': {} - '@jridgewell/sourcemap-codec@1.5.0': {} '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': @@ -2386,6 +2378,14 @@ snapshots: optionalDependencies: vite: 7.0.6(@types/node@24.1.0)(jiti@2.5.0) + '@vitest/mocker@3.2.4(vite@7.0.6)': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 7.0.6 + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 @@ -3409,6 +3409,27 @@ snapshots: util-deprecate@1.0.2: {} + vite-node@3.2.4: + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.0.6 + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite-node@3.2.4(@types/node@24.1.0)(jiti@2.5.0): dependencies: cac: 6.7.14 @@ -3430,6 +3451,17 @@ snapshots: - tsx - yaml + vite@7.0.6: + dependencies: + esbuild: 0.25.8 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.45.1 + tinyglobby: 0.2.14 + optionalDependencies: + fsevents: 2.3.3 + vite@7.0.6(@types/node@24.1.0)(jiti@2.5.0): dependencies: esbuild: 0.25.8 @@ -3443,9 +3475,48 @@ snapshots: fsevents: 2.3.3 jiti: 2.5.0 - vitest-ansi-serializer@0.1.2(vitest@3.2.4(@types/node@24.1.0)(jiti@2.5.0)): + vitest-ansi-serializer@0.1.2(vitest@3.2.4): + dependencies: + vitest: 3.2.4 + + vitest@3.2.4: dependencies: - vitest: 3.2.4(@types/node@24.1.0)(jiti@2.5.0) + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.0.6) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.0 + debug: 4.4.1 + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.0.6 + vite-node: 3.2.4 + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml vitest@3.2.4(@types/node@24.1.0)(jiti@2.5.0): dependencies: From c3ee6bd9a64315716dbdc48b6350cb662e64cad0 Mon Sep 17 00:00:00 2001 From: Hyper-Z11 Date: Sat, 22 Nov 2025 13:51:28 +0700 Subject: [PATCH 22/22] format --- packages/prompts/src/autocomplete.ts | 24 +++++++++++------- packages/prompts/src/common.ts | 29 +++++++++++----------- packages/prompts/src/confirm.ts | 10 +++++++- packages/prompts/src/group-multi-select.ts | 14 ++++++++--- packages/prompts/src/multi-select.ts | 14 ++++++++--- packages/prompts/src/password.ts | 10 +++++++- packages/prompts/src/select.ts | 10 +++++++- packages/prompts/src/text.ts | 9 ++++++- 8 files changed, 85 insertions(+), 35 deletions(-) diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index ba7e1af1..de092097 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -1,6 +1,15 @@ import { AutocompletePrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, type RadioTheme, type CheckboxTheme, getThemeColor, getThemePrefix, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { + type CheckboxTheme, + type CommonOptions, + extendStyle, + getThemeColor, + getThemePrefix, + type RadioTheme, + S_BAR, + S_BAR_END, +} from './common.js'; import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; @@ -53,7 +62,7 @@ type AutocompleteSharedOptions = CommonOptions & { * Validates the value */ validate?: (value: Value | Value[] | undefined) => string | Error | undefined; -} +}; export interface AutocompleteOptions extends AutocompleteSharedOptions { /** @@ -193,7 +202,8 @@ export const autocomplete = (opts: AutocompleteOptions) => { }; // Type definition for the autocompleteMultiselect component -export interface AutocompleteMultiSelectOptions extends AutocompleteSharedOptions { +export interface AutocompleteMultiSelectOptions + extends AutocompleteSharedOptions { /** * The initial selected values */ @@ -223,15 +233,11 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti : ''; if (active) { - const checkbox = isSelected - ? style.checkboxSelectedActive - : style.checkboxUnselectedActive; + const checkbox = isSelected ? style.checkboxSelectedActive : style.checkboxUnselectedActive; return `${checkbox} ${label}${hint}`; } - const checkbox = isSelected - ? style.checkboxSelectedInactive - : style.checkboxUnselectedInactive; + const checkbox = isSelected ? style.checkboxSelectedInactive : style.checkboxUnselectedInactive; return `${checkbox} ${color.dim(label)}`; }; diff --git a/packages/prompts/src/common.ts b/packages/prompts/src/common.ts index cb82384d..248346e2 100644 --- a/packages/prompts/src/common.ts +++ b/packages/prompts/src/common.ts @@ -46,12 +46,13 @@ export type CommonOptions = { input?: Readable; output?: Writable; signal?: AbortSignal; -} & (TStyle extends object ? { - theme?: - { [K in ColorState]?: (str: string) => string } & - { [K in PrefixState]?: string } - & TStyle; -} : {}); +} & (TStyle extends object + ? { + theme?: { [K in ColorState]?: (str: string) => string } & { + [K in PrefixState]?: string; + } & TStyle; + } + : {}); export interface RadioTheme { radioActive?: string; @@ -60,11 +61,11 @@ export interface RadioTheme { } export interface CheckboxTheme { - checkboxSelectedActive?: string, - checkboxSelectedInactive?: string, - checkboxUnselectedActive?: string, - checkboxUnselectedInactive?: string, - checkboxDisabled?: string, + checkboxSelectedActive?: string; + checkboxSelectedInactive?: string; + checkboxUnselectedActive?: string; + checkboxUnselectedInactive?: string; + checkboxDisabled?: string; } const defaultStyle: CommonOptions['theme'] = { @@ -96,11 +97,11 @@ type ExtendStyleType = ({ theme: {} } & CommonOptions)['theme']; export const extendStyle = (style?: ExtendStyleType) => { return { ...defaultStyle, - ...(style ?? {}) + ...(style ?? {}), } as Required>; }; const capitalize = (str: string): string => str[0].toUpperCase() + str.substring(1); -export const getThemeColor = (state: State) => (`color${capitalize(state)}`) as ColorState; -export const getThemePrefix = (state: State) => (`prefix${capitalize(state)}`) as PrefixState; +export const getThemeColor = (state: State) => `color${capitalize(state)}` as ColorState; +export const getThemePrefix = (state: State) => `prefix${capitalize(state)}` as PrefixState; diff --git a/packages/prompts/src/confirm.ts b/packages/prompts/src/confirm.ts index 5beecbf5..0b115f92 100644 --- a/packages/prompts/src/confirm.ts +++ b/packages/prompts/src/confirm.ts @@ -1,6 +1,14 @@ import { ConfirmPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, type RadioTheme, getThemeColor, getThemePrefix, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { + type CommonOptions, + extendStyle, + getThemeColor, + getThemePrefix, + type RadioTheme, + S_BAR, + S_BAR_END, +} from './common.js'; export interface ConfirmOptions extends CommonOptions { message: string; diff --git a/packages/prompts/src/group-multi-select.ts b/packages/prompts/src/group-multi-select.ts index 3241d61b..dd17cc10 100644 --- a/packages/prompts/src/group-multi-select.ts +++ b/packages/prompts/src/group-multi-select.ts @@ -1,6 +1,14 @@ import { GroupMultiSelectPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, type CheckboxTheme, getThemeColor, getThemePrefix, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { + type CheckboxTheme, + type CommonOptions, + extendStyle, + getThemeColor, + getThemePrefix, + S_BAR, + S_BAR_END, +} from './common.js'; import type { Option } from './select.js'; export interface GroupMultiSelectOptions extends CommonOptions { @@ -121,9 +129,7 @@ export const groupMultiselect = (opts: GroupMultiSelectOptions) => case 'error': { const footer = this.error .split('\n') - .map((ln, i) => - i === 0 ? `${barEnd} ${style[themeColor](ln)}` : ` ${ln}` - ) + .map((ln, i) => (i === 0 ? `${barEnd} ${style[themeColor](ln)}` : ` ${ln}`)) .join('\n'); return `${title}${bar} ${this.options .map((option, i, options) => { diff --git a/packages/prompts/src/multi-select.ts b/packages/prompts/src/multi-select.ts index 7e0661eb..4c11eda6 100644 --- a/packages/prompts/src/multi-select.ts +++ b/packages/prompts/src/multi-select.ts @@ -1,6 +1,14 @@ import { MultiSelectPrompt, wrapTextWithPrefix } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, type CheckboxTheme, getThemeColor, getThemePrefix, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { + type CheckboxTheme, + type CommonOptions, + extendStyle, + getThemeColor, + getThemePrefix, + S_BAR, + S_BAR_END, +} from './common.js'; import { limitOptions } from './limit-options.js'; import type { Option } from './select.js'; @@ -135,9 +143,7 @@ export const multiselect = (opts: MultiSelectOptions) => { const prefix = `${bar} `; const footer = this.error .split('\n') - .map((ln, i) => - i === 0 ? `${barEnd} ${style[themeColor](ln)}` : ` ${ln}` - ) + .map((ln, i) => (i === 0 ? `${barEnd} ${style[themeColor](ln)}` : ` ${ln}`)) .join('\n'); return `${title}${prefix}${limitOptions({ output: opts.output, diff --git a/packages/prompts/src/password.ts b/packages/prompts/src/password.ts index 1b652e2f..ad7fcb70 100644 --- a/packages/prompts/src/password.ts +++ b/packages/prompts/src/password.ts @@ -1,6 +1,14 @@ import { PasswordPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, getThemeColor, getThemePrefix, extendStyle, S_BAR, S_BAR_END, S_PASSWORD_MASK } from './common.js'; +import { + type CommonOptions, + extendStyle, + getThemeColor, + getThemePrefix, + S_BAR, + S_BAR_END, + S_PASSWORD_MASK, +} from './common.js'; export interface PasswordOptions extends CommonOptions<{}> { message: string; diff --git a/packages/prompts/src/select.ts b/packages/prompts/src/select.ts index 4c463a0b..d825a5e1 100644 --- a/packages/prompts/src/select.ts +++ b/packages/prompts/src/select.ts @@ -1,6 +1,14 @@ import { SelectPrompt, wrapTextWithPrefix } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, type RadioTheme, getThemeColor, getThemePrefix, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { + type CommonOptions, + extendStyle, + getThemeColor, + getThemePrefix, + type RadioTheme, + S_BAR, + S_BAR_END, +} from './common.js'; import { limitOptions } from './limit-options.js'; type Primitive = Readonly; diff --git a/packages/prompts/src/text.ts b/packages/prompts/src/text.ts index 57157cd1..6d1373cf 100644 --- a/packages/prompts/src/text.ts +++ b/packages/prompts/src/text.ts @@ -1,6 +1,13 @@ import { TextPrompt } from '@clack/core'; import color from 'picocolors'; -import { type CommonOptions, getThemeColor, getThemePrefix, extendStyle, S_BAR, S_BAR_END } from './common.js'; +import { + type CommonOptions, + extendStyle, + getThemeColor, + getThemePrefix, + S_BAR, + S_BAR_END, +} from './common.js'; export interface TextOptions extends CommonOptions<{}> { message: string;