diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 808a8bd..a2f91b7 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,8 +2,10 @@ export { default as ConfirmPrompt } from './prompts/confirm'; export { default as GroupMultiSelectPrompt } from './prompts/group-multiselect'; export { default as MultiSelectPrompt } from './prompts/multi-select'; export { default as PasswordPrompt } from './prompts/password'; -export { default as Prompt, isCancel, type State } from './prompts/prompt'; +export { default as Prompt } from './prompts/prompt'; export { default as SelectPrompt } from './prompts/select'; export { default as SelectKeyPrompt } from './prompts/select-key'; export { default as TextPrompt } from './prompts/text'; -export { block, setGlobalAliases } from './utils'; +export type { ClackState as State } from './types'; +export { block, isCancel, setGlobalAliases } from './utils'; + diff --git a/packages/core/src/prompts/prompt.ts b/packages/core/src/prompts/prompt.ts index 139d0c1..dea1403 100644 --- a/packages/core/src/prompts/prompt.ts +++ b/packages/core/src/prompts/prompt.ts @@ -1,36 +1,13 @@ -import type { Key, ReadLine } from 'node:readline'; - import { stdin, stdout } from 'node:process'; -import readline from 'node:readline'; +import readline, { type Key, type ReadLine } from 'node:readline'; import { Readable, Writable } from 'node:stream'; import { WriteStream } from 'node:tty'; import { cursor, erase } from 'sisteransi'; import wrap from 'wrap-ansi'; -import { type InferSetType, aliases, keys, hasAliasKey } from '../utils'; - -function diffLines(a: string, b: string) { - if (a === b) return; - - const aLines = a.split('\n'); - const bLines = b.split('\n'); - const diff: number[] = []; - - for (let i = 0; i < Math.max(aLines.length, bLines.length); i++) { - if (aLines[i] !== bLines[i]) diff.push(i); - } - - return diff; -} +import { ALIASES, CANCEL_SYMBOL, diffLines, hasAliasKey, KEYS, setRawMode } from '../utils'; -const cancel = Symbol('clack:cancel'); -export function isCancel(value: unknown): value is symbol { - return value === cancel; -} - -function setRawMode(input: Readable, value: boolean) { - if ((input as typeof stdin).isTTY) (input as typeof stdin).setRawMode(value); -} +import type { ClackEvents, ClackState, InferSetType } from '../types'; export interface PromptOptions { render(this: Omit): string | void; @@ -42,24 +19,6 @@ export interface PromptOptions { debug?: boolean; } -export type State = 'initial' | 'active' | 'cancel' | 'submit' | 'error'; - -/** - * Typed event emitter for clack - */ -interface ClackHooks { - 'initial': (value?: any) => void; - 'active': (value?: any) => void; - 'cancel': (value?: any) => void; - 'submit': (value?: any) => void; - 'error': (value?: any) => void; - 'cursor': (key?: InferSetType) => void; - 'key': (key?: string) => void; - 'value': (value?: string) => void; - 'confirm': (value?: boolean) => void; - 'finalize': () => void; -} - export default class Prompt { protected input: Readable; protected output: Writable; @@ -72,20 +31,12 @@ export default class Prompt { private _subscribers = new Map any; once?: boolean }[]>(); protected _cursor = 0; - public state: State = 'initial'; + public state: ClackState = 'initial'; public error = ''; public value: any; - constructor( - options: PromptOptions, - trackValue: boolean = true - ) { - const { - input = stdin, - output = stdout, - render, - ...opts - } = options; + constructor(options: PromptOptions, trackValue: boolean = true) { + const { input = stdin, output = stdout, render, ...opts } = options; this.opts = opts; this.onKeypress = this.onKeypress.bind(this); @@ -109,7 +60,10 @@ export default class Prompt { * Set a subscriber with opts * @param event - The event name */ - private setSubscriber(event: T, opts: { cb: ClackHooks[T]; once?: boolean }) { + private setSubscriber( + event: T, + opts: { cb: ClackEvents[T]; once?: boolean } + ) { const params = this._subscribers.get(event) ?? []; params.push(opts); this._subscribers.set(event, params); @@ -120,7 +74,7 @@ export default class Prompt { * @param event - The event name * @param cb - The callback */ - public on(event: T, cb: ClackHooks[T]) { + public on(event: T, cb: ClackEvents[T]) { this.setSubscriber(event, { cb }); } @@ -129,7 +83,7 @@ export default class Prompt { * @param event - The event name * @param cb - The callback */ - public once(event: T, cb: ClackHooks[T]) { + public once(event: T, cb: ClackEvents[T]) { this.setSubscriber(event, { cb, once: true }); } @@ -138,7 +92,7 @@ export default class Prompt { * @param event - The event name * @param data - The data to pass to the callback */ - public emit(event: T, ...data: Parameters) { + public emit(event: T, ...data: Parameters) { const cbs = this._subscribers.get(event) ?? []; const cleanup: (() => void)[] = []; @@ -197,7 +151,7 @@ export default class Prompt { this.output.write(cursor.show); this.output.off('resize', this.render); setRawMode(this.input, false); - resolve(cancel); + resolve(CANCEL_SYMBOL); }); }); } @@ -206,11 +160,11 @@ export default class Prompt { if (this.state === 'error') { this.state = 'active'; } - if (key?.name && !this._track && aliases.has(key.name)) { - this.emit('cursor', aliases.get(key.name)); + if (key?.name && !this._track && ALIASES.has(key.name)) { + this.emit('cursor', ALIASES.get(key.name)); } - if (key?.name && keys.has(key.name as InferSetType)) { - this.emit('cursor', key.name as InferSetType); + if (key?.name && KEYS.has(key.name as InferSetType)) { + this.emit('cursor', key.name as InferSetType); } if (char && (char.toLowerCase() === 'y' || char.toLowerCase() === 'n')) { this.emit('confirm', char.toLowerCase() === 'y'); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts new file mode 100644 index 0000000..4ef4689 --- /dev/null +++ b/packages/core/src/types.ts @@ -0,0 +1,24 @@ +import { KEYS } from './utils'; + +export type InferSetType = T extends Set ? U : never; + +/** + * The state of the prompt + */ +export type ClackState = 'initial' | 'active' | 'cancel' | 'submit' | 'error'; + +/** + * Typed event emitter for clack + */ +export interface ClackEvents { + initial: (value?: any) => void; + active: (value?: any) => void; + cancel: (value?: any) => void; + submit: (value?: any) => void; + error: (value?: any) => void; + cursor: (key?: InferSetType) => void; + key: (key?: string) => void; + value: (value?: string) => void; + confirm: (value?: boolean) => void; + finalize: () => void; +} diff --git a/packages/core/src/utils/aliases.ts b/packages/core/src/utils/aliases.ts index 453ac2e..819dec7 100644 --- a/packages/core/src/utils/aliases.ts +++ b/packages/core/src/utils/aliases.ts @@ -1,10 +1,9 @@ - -export type InferSetType = T extends Set ? U : never; +import type { InferSetType } from '../types'; const DEFAULT_KEYS = ['up', 'down', 'left', 'right', 'space', 'enter', 'cancel'] as const; -export const keys = new Set(DEFAULT_KEYS); +export const KEYS = new Set(DEFAULT_KEYS); -export const aliases = new Map>([ +export const ALIASES = new Map>([ ['k', 'up'], ['j', 'down'], ['h', 'left'], @@ -19,10 +18,10 @@ export const aliases = new Map>([ * @default * new Map([['k', 'up'], ['j', 'down'], ['h', 'left'], ['l', 'right'], ['\x03', 'cancel'],]) */ -export function setGlobalAliases(alias: Array<[string, InferSetType]>) { +export function setGlobalAliases(alias: Array<[string, InferSetType]>) { for (const [newAlias, key] of alias) { - if (!aliases.has(newAlias)) { - aliases.set(newAlias, key); + if (!ALIASES.has(newAlias)) { + ALIASES.set(newAlias, key); } } } @@ -33,13 +32,18 @@ export function setGlobalAliases(alias: Array<[string, InferSetType * @param type - The type of key to check for * @returns boolean */ -export function hasAliasKey(key: string | Array, type: InferSetType) { +export function hasAliasKey( + key: string | Array, + type: InferSetType +) { if (typeof key === 'string') { - return aliases.has(key) && aliases.get(key) === type; + return ALIASES.has(key) && ALIASES.get(key) === type; } - return key.map((n) => { - if (n !== undefined && aliases.has(n) && aliases.get(n) === type) return true; - return false; - }).includes(true); + return key + .map((n) => { + if (n !== undefined && ALIASES.has(n) && ALIASES.get(n) === type) return true; + return false; + }) + .includes(true); } diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 3a49ba7..d3dd94d 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -1,12 +1,27 @@ -import type { Key } from 'node:readline'; - import { stdin, stdout } from 'node:process'; +import type { Key } from 'node:readline'; import * as readline from 'node:readline'; +import type { Readable } from 'node:stream'; import { cursor } from 'sisteransi'; import { hasAliasKey } from './aliases'; const isWindows = globalThis.process.platform.startsWith('win'); +export * from './aliases'; +export * from './string'; + +export const CANCEL_SYMBOL = Symbol('clack:cancel'); + +export function isCancel(value: unknown): value is symbol { + return value === CANCEL_SYMBOL; +} + +export function setRawMode(input: Readable, value: boolean) { + const i = input as typeof stdin; + + if (i.isTTY) i.setRawMode(value); +} + export function block({ input = stdin, output = stdout, @@ -54,5 +69,3 @@ export function block({ rl.close(); }; } - -export * from './aliases'; diff --git a/packages/core/src/utils/string.ts b/packages/core/src/utils/string.ts new file mode 100644 index 0000000..6ec6456 --- /dev/null +++ b/packages/core/src/utils/string.ts @@ -0,0 +1,13 @@ +export function diffLines(a: string, b: string) { + if (a === b) return; + + const aLines = a.split('\n'); + const bLines = b.split('\n'); + const diff: number[] = []; + + for (let i = 0; i < Math.max(aLines.length, bLines.length); i++) { + if (aLines[i] !== bLines[i]) diff.push(i); + } + + return diff; +}