@jakubmazanec/cli 0.1.0-bc209e5 → 0.1.0-dee6883

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ import { parseArguments } from '@jakubmazanec/args';
3
3
  /**
4
4
  * Command encapsulates piece of a CLI's logic and is executed when {@link Program} instance parses that command from the command line arguments.
5
5
  */ export class Command {
6
- /* implements ProgramCommand*/ name;
6
+ name;
7
7
  description;
8
8
  parameters;
9
9
  options;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/program/Command.ts"],"sourcesContent":["import {\n type Argv,\n parseArguments,\n type OptionsConfig,\n type ParametersConfig,\n type Arguments,\n} from '@jakubmazanec/args';\n\nimport {type CommandAction} from './CommandAction.js';\nimport {type CommandOptions} from './CommandOptions.js';\nimport {type Program} from './Program.js';\n// import {type ProgramCommand} from './ProgramCommand.js';\n\n/**\n * Command encapsulates piece of a CLI's logic and is executed when {@link Program} instance parses that command from the command line arguments.\n */\nexport class Command<\n A extends string,\n const B extends ParametersConfig | undefined = undefined,\n const C extends OptionsConfig | undefined = undefined,\n D extends boolean = false,\n E extends boolean = false,\n> {\n /* implements ProgramCommand*/\n readonly name: A;\n readonly description?: string;\n readonly parameters: B extends ParametersConfig ? B : null;\n readonly options: C extends OptionsConfig ? C : null;\n readonly allowUnknownOptions: D;\n readonly allowUnknownParameters: E;\n private readonly action: CommandAction<A, B, C, D, E>;\n\n /**\n * @param name Command name, ie. space-separated words consisting only of letters, numbers (but not at the start of the word) or dashes.\n * @param action Function that runs when command is executed.\n * @param options Additionl command options.\n */\n constructor(\n name: A,\n action: CommandAction<A, B, C, D, E>,\n {\n description,\n parameters,\n options,\n allowUnknownOptions,\n allowUnknownParameters,\n }: CommandOptions<NonNullable<B>, NonNullable<C>, D, E>,\n ) {\n this.name = name;\n this.action = action;\n\n if (description) {\n this.description = description;\n }\n\n if (parameters) {\n this.parameters = parameters as B extends ParametersConfig ? B : null;\n } else {\n this.parameters = null as B extends ParametersConfig ? B : null;\n }\n\n if (options) {\n this.options = options as C extends OptionsConfig ? C : null;\n } else {\n this.options = null as C extends OptionsConfig ? C : null;\n }\n\n if (allowUnknownOptions) {\n this.allowUnknownOptions = allowUnknownOptions;\n } else {\n this.allowUnknownOptions = false as D;\n }\n\n if (allowUnknownParameters) {\n this.allowUnknownParameters = allowUnknownParameters;\n } else {\n this.allowUnknownParameters = false as E;\n }\n }\n\n /**\n * Parses a list of command line arguments into an arguments object. Parser is pre-configured based on this command.\n */\n parseArguments(argv: Argv): Arguments<{\n commands: readonly [A];\n parameters: B;\n options: C;\n allowUnknownOptions: D;\n allowUnknownParameters: E;\n }> {\n return parseArguments(argv, {\n commands: [this.name],\n parameters: this.parameters ?? undefined,\n options: this.options ?? undefined,\n allowUnknownOptions: this.allowUnknownOptions,\n allowUnknownParameters: this.allowUnknownParameters,\n } as {\n commands: readonly [A];\n parameters: B;\n options: C;\n allowUnknownOptions: D;\n allowUnknownParameters: E;\n });\n }\n\n /**\n * Executes command action with provided parsed arguments and {@link Program} instance.\n */\n async run(\n parsedArgs: Arguments<{\n commands: readonly [A];\n parameters: B;\n options: C;\n allowUnknownOptions: D;\n allowUnknownParameters: E;\n }>,\n program: Program,\n ): Promise<void> {\n await this.action(parsedArgs, program);\n }\n}\n"],"names":["parseArguments","Command","name","description","parameters","options","allowUnknownOptions","allowUnknownParameters","action","constructor","argv","commands","undefined","run","parsedArgs","program"],"mappings":"AAAA,SAEEA,cAAc,QAIT,qBAAqB;AAK5B,2DAA2D;AAE3D;;CAEC,GACD,OAAO,MAAMC;IAOX,4BAA4B,GAC5B,AAASC,KAAQ;IACRC,YAAqB;IACrBC,WAAkD;IAClDC,QAA4C;IAC5CC,oBAAuB;IACvBC,uBAA0B;IAClBC,OAAqC;IAEtD;;;;GAIC,GACDC,YACEP,IAAO,EACPM,MAAoC,EACpC,EACEL,WAAW,EACXC,UAAU,EACVC,OAAO,EACPC,mBAAmB,EACnBC,sBAAsB,EAC+B,CACvD;QACA,IAAI,CAACL,IAAI,GAAGA;QACZ,IAAI,CAACM,MAAM,GAAGA;QAEd,IAAIL,aAAa;YACf,IAAI,CAACA,WAAW,GAAGA;QACrB;QAEA,IAAIC,YAAY;YACd,IAAI,CAACA,UAAU,GAAGA;QACpB,OAAO;YACL,IAAI,CAACA,UAAU,GAAG;QACpB;QAEA,IAAIC,SAAS;YACX,IAAI,CAACA,OAAO,GAAGA;QACjB,OAAO;YACL,IAAI,CAACA,OAAO,GAAG;QACjB;QAEA,IAAIC,qBAAqB;YACvB,IAAI,CAACA,mBAAmB,GAAGA;QAC7B,OAAO;YACL,IAAI,CAACA,mBAAmB,GAAG;QAC7B;QAEA,IAAIC,wBAAwB;YAC1B,IAAI,CAACA,sBAAsB,GAAGA;QAChC,OAAO;YACL,IAAI,CAACA,sBAAsB,GAAG;QAChC;IACF;IAEA;;GAEC,GACDP,eAAeU,IAAU,EAMtB;QACD,OAAOV,eAAeU,MAAM;YAC1BC,UAAU;gBAAC,IAAI,CAACT,IAAI;aAAC;YACrBE,YAAY,IAAI,CAACA,UAAU,IAAIQ;YAC/BP,SAAS,IAAI,CAACA,OAAO,IAAIO;YACzBN,qBAAqB,IAAI,CAACA,mBAAmB;YAC7CC,wBAAwB,IAAI,CAACA,sBAAsB;QACrD;IAOF;IAEA;;GAEC,GACD,MAAMM,IACJC,UAME,EACFC,OAAgB,EACD;QACf,MAAM,IAAI,CAACP,MAAM,CAACM,YAAYC;IAChC;AACF"}
1
+ {"version":3,"sources":["../../src/program/Command.ts"],"sourcesContent":["import {\n type Argv,\n parseArguments,\n type OptionsConfig,\n type ParametersConfig,\n type Arguments,\n} from '@jakubmazanec/args';\n\nimport {type CommandAction} from './CommandAction.js';\nimport {type CommandOptions} from './CommandOptions.js';\nimport {type Program} from './Program.js';\n// import {type ProgramCommand} from './ProgramCommand.js';\n\n/**\n * Command encapsulates piece of a CLI's logic and is executed when {@link Program} instance parses that command from the command line arguments.\n */\nexport class Command<\n A extends string,\n const B extends ParametersConfig | undefined = undefined,\n const C extends OptionsConfig | undefined = undefined,\n D extends boolean = false,\n E extends boolean = false,\n> {\n readonly name: A;\n readonly description?: string;\n readonly parameters: B extends ParametersConfig ? B : null;\n readonly options: C extends OptionsConfig ? C : null;\n readonly allowUnknownOptions: D;\n readonly allowUnknownParameters: E;\n private readonly action: CommandAction<A, B, C, D, E>;\n\n /**\n * @param name Command name, ie. space-separated words consisting only of letters, numbers (but not at the start of the word) or dashes.\n * @param action Function that runs when command is executed.\n * @param options Additionl command options.\n */\n constructor(\n name: A,\n action: CommandAction<A, B, C, D, E>,\n {\n description,\n parameters,\n options,\n allowUnknownOptions,\n allowUnknownParameters,\n }: CommandOptions<NonNullable<B>, NonNullable<C>, D, E>,\n ) {\n this.name = name;\n this.action = action;\n\n if (description) {\n this.description = description;\n }\n\n if (parameters) {\n this.parameters = parameters as B extends ParametersConfig ? B : null;\n } else {\n this.parameters = null as B extends ParametersConfig ? B : null;\n }\n\n if (options) {\n this.options = options as C extends OptionsConfig ? C : null;\n } else {\n this.options = null as C extends OptionsConfig ? C : null;\n }\n\n if (allowUnknownOptions) {\n this.allowUnknownOptions = allowUnknownOptions;\n } else {\n this.allowUnknownOptions = false as D;\n }\n\n if (allowUnknownParameters) {\n this.allowUnknownParameters = allowUnknownParameters;\n } else {\n this.allowUnknownParameters = false as E;\n }\n }\n\n /**\n * Parses a list of command line arguments into an arguments object. Parser is pre-configured based on this command.\n */\n parseArguments(argv: Argv): Arguments<{\n commands: readonly [A];\n parameters: B;\n options: C;\n allowUnknownOptions: D;\n allowUnknownParameters: E;\n }> {\n return parseArguments(argv, {\n commands: [this.name],\n parameters: this.parameters ?? undefined,\n options: this.options ?? undefined,\n allowUnknownOptions: this.allowUnknownOptions,\n allowUnknownParameters: this.allowUnknownParameters,\n } as {\n commands: readonly [A];\n parameters: B;\n options: C;\n allowUnknownOptions: D;\n allowUnknownParameters: E;\n });\n }\n\n /**\n * Executes command action with provided parsed arguments and {@link Program} instance.\n */\n async run(\n parsedArgs: Arguments<{\n commands: readonly [A];\n parameters: B;\n options: C;\n allowUnknownOptions: D;\n allowUnknownParameters: E;\n }>,\n program: Program,\n ): Promise<void> {\n await this.action(parsedArgs, program);\n }\n}\n"],"names":["parseArguments","Command","name","description","parameters","options","allowUnknownOptions","allowUnknownParameters","action","constructor","argv","commands","undefined","run","parsedArgs","program"],"mappings":"AAAA,SAEEA,cAAc,QAIT,qBAAqB;AAK5B,2DAA2D;AAE3D;;CAEC,GACD,OAAO,MAAMC;IAOFC,KAAQ;IACRC,YAAqB;IACrBC,WAAkD;IAClDC,QAA4C;IAC5CC,oBAAuB;IACvBC,uBAA0B;IAClBC,OAAqC;IAEtD;;;;GAIC,GACDC,YACEP,IAAO,EACPM,MAAoC,EACpC,EACEL,WAAW,EACXC,UAAU,EACVC,OAAO,EACPC,mBAAmB,EACnBC,sBAAsB,EAC+B,CACvD;QACA,IAAI,CAACL,IAAI,GAAGA;QACZ,IAAI,CAACM,MAAM,GAAGA;QAEd,IAAIL,aAAa;YACf,IAAI,CAACA,WAAW,GAAGA;QACrB;QAEA,IAAIC,YAAY;YACd,IAAI,CAACA,UAAU,GAAGA;QACpB,OAAO;YACL,IAAI,CAACA,UAAU,GAAG;QACpB;QAEA,IAAIC,SAAS;YACX,IAAI,CAACA,OAAO,GAAGA;QACjB,OAAO;YACL,IAAI,CAACA,OAAO,GAAG;QACjB;QAEA,IAAIC,qBAAqB;YACvB,IAAI,CAACA,mBAAmB,GAAGA;QAC7B,OAAO;YACL,IAAI,CAACA,mBAAmB,GAAG;QAC7B;QAEA,IAAIC,wBAAwB;YAC1B,IAAI,CAACA,sBAAsB,GAAGA;QAChC,OAAO;YACL,IAAI,CAACA,sBAAsB,GAAG;QAChC;IACF;IAEA;;GAEC,GACDP,eAAeU,IAAU,EAMtB;QACD,OAAOV,eAAeU,MAAM;YAC1BC,UAAU;gBAAC,IAAI,CAACT,IAAI;aAAC;YACrBE,YAAY,IAAI,CAACA,UAAU,IAAIQ;YAC/BP,SAAS,IAAI,CAACA,OAAO,IAAIO;YACzBN,qBAAqB,IAAI,CAACA,mBAAmB;YAC7CC,wBAAwB,IAAI,CAACA,sBAAsB;QACrD;IAOF;IAEA;;GAEC,GACD,MAAMM,IACJC,UAME,EACFC,OAAgB,EACD;QACf,MAAM,IAAI,CAACP,MAAM,CAACM,YAAYC;IAChC;AACF"}
@@ -1,6 +1,6 @@
1
1
  import { type OptionsConfig, type ParametersConfig } from '@jakubmazanec/args';
2
2
  /**
3
- * New command options.
3
+ * {@link Command.constructor | Command constructor} options parameter.
4
4
  */
5
5
  export type CommandOptions<B extends ParametersConfig, C extends OptionsConfig, D extends boolean, E extends boolean> = {
6
6
  description?: string;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/program/CommandOptions.ts"],"sourcesContent":["import {type OptionsConfig, type ParametersConfig} from '@jakubmazanec/args';\n\n/**\n * New command options.\n */\nexport type CommandOptions<\n B extends ParametersConfig,\n C extends OptionsConfig,\n D extends boolean,\n E extends boolean,\n> = {\n description?: string;\n parameters?: B | undefined;\n options?: C | undefined;\n allowUnknownOptions?: D | undefined;\n allowUnknownParameters?: E | undefined;\n};\n"],"names":[],"mappings":"AAAA,WAgBE"}
1
+ {"version":3,"sources":["../../src/program/CommandOptions.ts"],"sourcesContent":["import {type OptionsConfig, type ParametersConfig} from '@jakubmazanec/args';\n\n/**\n * {@link Command.constructor | Command constructor} options parameter.\n */\nexport type CommandOptions<\n B extends ParametersConfig,\n C extends OptionsConfig,\n D extends boolean,\n E extends boolean,\n> = {\n description?: string;\n parameters?: B | undefined;\n options?: C | undefined;\n allowUnknownOptions?: D | undefined;\n allowUnknownParameters?: E | undefined;\n};\n"],"names":[],"mappings":"AAAA,WAgBE"}
@@ -32,6 +32,8 @@ export declare class Program {
32
32
  addCommand(command: Command<any, any, any, any, any>): this;
33
33
  /**
34
34
  * Renders a React element using Ink and outputs to the configured streams.
35
+ *
36
+ * @param element The React element to render.
35
37
  */
36
38
  renderElement(element: React.ReactElement): Promise<void>;
37
39
  /**
@@ -47,6 +47,8 @@ import { Failure, Help } from '../ui.js';
47
47
  }
48
48
  /**
49
49
  * Renders a React element using Ink and outputs to the configured streams.
50
+ *
51
+ * @param element The React element to render.
50
52
  */ async renderElement(element) {
51
53
  if (this.isRendering) {
52
54
  throw new ProgramError('NO_NESTED_RENDER');
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/program/Program.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any -- needed */\nimport {type ParametersConfig, type Argv, type OptionsConfig} from '@jakubmazanec/args';\nimport {type ComponentType} from 'react';\nimport {type FallbackProps} from 'react-error-boundary';\n\nimport {type Command} from './Command.js';\nimport {ProgramError} from './ProgramError.js';\nimport {type ProgramOptions} from './ProgramOptions.js';\nimport {type ProgramStreams} from './ProgramStreams.js';\nimport {Wrapper} from '../ui/internals.js';\nimport {Failure, Help} from '../ui.js';\n\n/**\n * Program manages and executes {@link Command} instances attached to it based on parsing command line arguments.\n */\nexport class Program {\n /** Program name. */\n readonly name?: string;\n\n /** Program version. */\n readonly version?: string;\n\n /** Program description. */\n readonly description?: string;\n\n /** Program executable name. */\n readonly bin?: string;\n\n /** Program commands */\n readonly commands: Array<\n Command<string, ParametersConfig | undefined, OptionsConfig | undefined, boolean, boolean>\n > = [];\n\n protected isRendering = false;\n\n readonly streams: ProgramStreams = {\n stderr: process.stderr,\n stdin: process.stdin,\n stdout: process.stdout,\n };\n\n /** Custom error boundary fallback component */\n ErrorBoundaryFallbackComponent: ComponentType<FallbackProps>;\n\n constructor({name, version, description, bin, ErrorBoundaryFallbackComponent}: ProgramOptions) {\n if (name) {\n this.name = name;\n }\n\n if (version) {\n this.version = version;\n }\n\n if (description) {\n this.description = description;\n }\n\n if (bin) {\n this.bin = bin;\n }\n\n if (ErrorBoundaryFallbackComponent) {\n this.ErrorBoundaryFallbackComponent = ErrorBoundaryFallbackComponent;\n } else {\n this.ErrorBoundaryFallbackComponent = Failure;\n }\n }\n\n /**\n * Adds command to the program.\n *\n * @param command Command to add.\n * @returns Program instance.\n */\n addCommand(command: Command<any, any, any, any, any>): this {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- it doesn't matter here; `any` is used to make TypeScript happy in this method, but isn't propagated further.\n this.commands.push(command);\n\n return this;\n }\n\n /**\n * Renders a React element using Ink and outputs to the configured streams.\n */\n async renderElement(element: React.ReactElement): Promise<void> {\n if (this.isRendering) {\n throw new ProgramError('NO_NESTED_RENDER');\n } else {\n this.isRendering = true;\n }\n\n let {render} = await import('ink');\n let {createElement} = await import('react');\n let {stdin, stdout, stderr} = this.streams;\n\n try {\n render(createElement(Wrapper, {program: this}, element), {\n exitOnCtrlC: true,\n patchConsole: true,\n stderr,\n stdin,\n stdout,\n });\n } catch (error: unknown) {\n if (error instanceof Error) {\n await this.renderElement(\n createElement(this.ErrorBoundaryFallbackComponent, {error, resetErrorBoundary: () => {}}),\n );\n }\n } finally {\n this.isRendering = false;\n }\n }\n\n /**\n * Runs the program.\n *\n * @param argv Unparsed command line arguments.\n */\n async run(argv: Argv): Promise<void> {\n let hasCommandRun = false;\n\n for (let command of this.commands) {\n let parsedArgs = command.parseArguments(argv);\n\n if (parsedArgs.command) {\n hasCommandRun = true;\n\n if (parsedArgs.errors.length) {\n let {createElement} = await import('react');\n\n for (let error of parsedArgs.errors) {\n await this.renderElement(\n createElement(this.ErrorBoundaryFallbackComponent, {\n error,\n resetErrorBoundary: () => {},\n }),\n );\n }\n } else {\n try {\n await command.run(parsedArgs, this);\n } catch (error: unknown) {\n let {createElement} = await import('react');\n\n if (error instanceof Error) {\n await this.renderElement(\n createElement(this.ErrorBoundaryFallbackComponent, {\n error,\n resetErrorBoundary: () => {},\n }),\n );\n }\n }\n\n break;\n }\n }\n }\n\n if (!hasCommandRun) {\n let {createElement} = await import('react');\n\n await this.renderElement(createElement(Help, {program: this}));\n }\n }\n}\n"],"names":["ProgramError","Wrapper","Failure","Help","Program","name","version","description","bin","commands","isRendering","streams","stderr","process","stdin","stdout","ErrorBoundaryFallbackComponent","constructor","addCommand","command","push","renderElement","element","render","createElement","program","exitOnCtrlC","patchConsole","error","Error","resetErrorBoundary","run","argv","hasCommandRun","parsedArgs","parseArguments","errors","length"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/program/Program.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any -- needed */\nimport {type ParametersConfig, type Argv, type OptionsConfig} from '@jakubmazanec/args';\nimport {type ComponentType} from 'react';\nimport {type FallbackProps} from 'react-error-boundary';\n\nimport {type Command} from './Command.js';\nimport {ProgramError} from './ProgramError.js';\nimport {type ProgramOptions} from './ProgramOptions.js';\nimport {type ProgramStreams} from './ProgramStreams.js';\nimport {Wrapper} from '../ui/internals.js';\nimport {Failure, Help} from '../ui.js';\n\n/**\n * Program manages and executes {@link Command} instances attached to it based on parsing command line arguments.\n */\nexport class Program {\n /** Program name. */\n readonly name?: string;\n\n /** Program version. */\n readonly version?: string;\n\n /** Program description. */\n readonly description?: string;\n\n /** Program executable name. */\n readonly bin?: string;\n\n /** Program commands */\n readonly commands: Array<\n Command<string, ParametersConfig | undefined, OptionsConfig | undefined, boolean, boolean>\n > = [];\n\n protected isRendering = false;\n\n readonly streams: ProgramStreams = {\n stderr: process.stderr,\n stdin: process.stdin,\n stdout: process.stdout,\n };\n\n /** Custom error boundary fallback component */\n ErrorBoundaryFallbackComponent: ComponentType<FallbackProps>;\n\n constructor({name, version, description, bin, ErrorBoundaryFallbackComponent}: ProgramOptions) {\n if (name) {\n this.name = name;\n }\n\n if (version) {\n this.version = version;\n }\n\n if (description) {\n this.description = description;\n }\n\n if (bin) {\n this.bin = bin;\n }\n\n if (ErrorBoundaryFallbackComponent) {\n this.ErrorBoundaryFallbackComponent = ErrorBoundaryFallbackComponent;\n } else {\n this.ErrorBoundaryFallbackComponent = Failure;\n }\n }\n\n /**\n * Adds command to the program.\n *\n * @param command Command to add.\n * @returns Program instance.\n */\n addCommand(command: Command<any, any, any, any, any>): this {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- it doesn't matter here; `any` is used to make TypeScript happy in this method, but isn't propagated further.\n this.commands.push(command);\n\n return this;\n }\n\n /**\n * Renders a React element using Ink and outputs to the configured streams.\n *\n * @param element The React element to render.\n */\n async renderElement(element: React.ReactElement): Promise<void> {\n if (this.isRendering) {\n throw new ProgramError('NO_NESTED_RENDER');\n } else {\n this.isRendering = true;\n }\n\n let {render} = await import('ink');\n let {createElement} = await import('react');\n let {stdin, stdout, stderr} = this.streams;\n\n try {\n render(createElement(Wrapper, {program: this}, element), {\n exitOnCtrlC: true,\n patchConsole: true,\n stderr,\n stdin,\n stdout,\n });\n } catch (error: unknown) {\n if (error instanceof Error) {\n await this.renderElement(\n createElement(this.ErrorBoundaryFallbackComponent, {error, resetErrorBoundary: () => {}}),\n );\n }\n } finally {\n this.isRendering = false;\n }\n }\n\n /**\n * Runs the program.\n *\n * @param argv Unparsed command line arguments.\n */\n async run(argv: Argv): Promise<void> {\n let hasCommandRun = false;\n\n for (let command of this.commands) {\n let parsedArgs = command.parseArguments(argv);\n\n if (parsedArgs.command) {\n hasCommandRun = true;\n\n if (parsedArgs.errors.length) {\n let {createElement} = await import('react');\n\n for (let error of parsedArgs.errors) {\n await this.renderElement(\n createElement(this.ErrorBoundaryFallbackComponent, {\n error,\n resetErrorBoundary: () => {},\n }),\n );\n }\n } else {\n try {\n await command.run(parsedArgs, this);\n } catch (error: unknown) {\n let {createElement} = await import('react');\n\n if (error instanceof Error) {\n await this.renderElement(\n createElement(this.ErrorBoundaryFallbackComponent, {\n error,\n resetErrorBoundary: () => {},\n }),\n );\n }\n }\n\n break;\n }\n }\n }\n\n if (!hasCommandRun) {\n let {createElement} = await import('react');\n\n await this.renderElement(createElement(Help, {program: this}));\n }\n }\n}\n"],"names":["ProgramError","Wrapper","Failure","Help","Program","name","version","description","bin","commands","isRendering","streams","stderr","process","stdin","stdout","ErrorBoundaryFallbackComponent","constructor","addCommand","command","push","renderElement","element","render","createElement","program","exitOnCtrlC","patchConsole","error","Error","resetErrorBoundary","run","argv","hasCommandRun","parsedArgs","parseArguments","errors","length"],"mappings":"AAAA,+DAA+D,GAM/D,SAAQA,YAAY,QAAO,oBAAoB;AAG/C,SAAQC,OAAO,QAAO,qBAAqB;AAC3C,SAAQC,OAAO,EAAEC,IAAI,QAAO,WAAW;AAEvC;;CAEC,GACD,OAAO,MAAMC;IACX,kBAAkB,GAClB,AAASC,KAAc;IAEvB,qBAAqB,GACrB,AAASC,QAAiB;IAE1B,yBAAyB,GACzB,AAASC,YAAqB;IAE9B,6BAA6B,GAC7B,AAASC,IAAa;IAEtB,qBAAqB,GACrB,AAASC,WAEL,EAAE,CAAC;IAEGC,cAAc,MAAM;IAErBC,UAA0B;QACjCC,QAAQC,QAAQD,MAAM;QACtBE,OAAOD,QAAQC,KAAK;QACpBC,QAAQF,QAAQE,MAAM;IACxB,EAAE;IAEF,6CAA6C,GAC7CC,+BAA6D;IAE7DC,YAAY,EAACZ,IAAI,EAAEC,OAAO,EAAEC,WAAW,EAAEC,GAAG,EAAEQ,8BAA8B,EAAiB,CAAE;QAC7F,IAAIX,MAAM;YACR,IAAI,CAACA,IAAI,GAAGA;QACd;QAEA,IAAIC,SAAS;YACX,IAAI,CAACA,OAAO,GAAGA;QACjB;QAEA,IAAIC,aAAa;YACf,IAAI,CAACA,WAAW,GAAGA;QACrB;QAEA,IAAIC,KAAK;YACP,IAAI,CAACA,GAAG,GAAGA;QACb;QAEA,IAAIQ,gCAAgC;YAClC,IAAI,CAACA,8BAA8B,GAAGA;QACxC,OAAO;YACL,IAAI,CAACA,8BAA8B,GAAGd;QACxC;IACF;IAEA;;;;;GAKC,GACDgB,WAAWC,OAAyC,EAAQ;QAC1D,iLAAiL;QACjL,IAAI,CAACV,QAAQ,CAACW,IAAI,CAACD;QAEnB,OAAO,IAAI;IACb;IAEA;;;;GAIC,GACD,MAAME,cAAcC,OAA2B,EAAiB;QAC9D,IAAI,IAAI,CAACZ,WAAW,EAAE;YACpB,MAAM,IAAIV,aAAa;QACzB,OAAO;YACL,IAAI,CAACU,WAAW,GAAG;QACrB;QAEA,IAAI,EAACa,MAAM,EAAC,GAAG,MAAM,MAAM,CAAC;QAC5B,IAAI,EAACC,aAAa,EAAC,GAAG,MAAM,MAAM,CAAC;QACnC,IAAI,EAACV,KAAK,EAAEC,MAAM,EAAEH,MAAM,EAAC,GAAG,IAAI,CAACD,OAAO;QAE1C,IAAI;YACFY,OAAOC,cAAcvB,SAAS;gBAACwB,SAAS,IAAI;YAAA,GAAGH,UAAU;gBACvDI,aAAa;gBACbC,cAAc;gBACdf;gBACAE;gBACAC;YACF;QACF,EAAE,OAAOa,OAAgB;YACvB,IAAIA,iBAAiBC,OAAO;gBAC1B,MAAM,IAAI,CAACR,aAAa,CACtBG,cAAc,IAAI,CAACR,8BAA8B,EAAE;oBAACY;oBAAOE,oBAAoB,KAAO;gBAAC;YAE3F;QACF,SAAU;YACR,IAAI,CAACpB,WAAW,GAAG;QACrB;IACF;IAEA;;;;GAIC,GACD,MAAMqB,IAAIC,IAAU,EAAiB;QACnC,IAAIC,gBAAgB;QAEpB,KAAK,IAAId,WAAW,IAAI,CAACV,QAAQ,CAAE;YACjC,IAAIyB,aAAaf,QAAQgB,cAAc,CAACH;YAExC,IAAIE,WAAWf,OAAO,EAAE;gBACtBc,gBAAgB;gBAEhB,IAAIC,WAAWE,MAAM,CAACC,MAAM,EAAE;oBAC5B,IAAI,EAACb,aAAa,EAAC,GAAG,MAAM,MAAM,CAAC;oBAEnC,KAAK,IAAII,SAASM,WAAWE,MAAM,CAAE;wBACnC,MAAM,IAAI,CAACf,aAAa,CACtBG,cAAc,IAAI,CAACR,8BAA8B,EAAE;4BACjDY;4BACAE,oBAAoB,KAAO;wBAC7B;oBAEJ;gBACF,OAAO;oBACL,IAAI;wBACF,MAAMX,QAAQY,GAAG,CAACG,YAAY,IAAI;oBACpC,EAAE,OAAON,OAAgB;wBACvB,IAAI,EAACJ,aAAa,EAAC,GAAG,MAAM,MAAM,CAAC;wBAEnC,IAAII,iBAAiBC,OAAO;4BAC1B,MAAM,IAAI,CAACR,aAAa,CACtBG,cAAc,IAAI,CAACR,8BAA8B,EAAE;gCACjDY;gCACAE,oBAAoB,KAAO;4BAC7B;wBAEJ;oBACF;oBAEA;gBACF;YACF;QACF;QAEA,IAAI,CAACG,eAAe;YAClB,IAAI,EAACT,aAAa,EAAC,GAAG,MAAM,MAAM,CAAC;YAEnC,MAAM,IAAI,CAACH,aAAa,CAACG,cAAcrB,MAAM;gBAACsB,SAAS,IAAI;YAAA;QAC7D;IACF;AACF"}
@@ -1,7 +1,7 @@
1
1
  import { type ComponentType } from 'react';
2
2
  import { type FallbackProps } from 'react-error-boundary';
3
3
  /**
4
- * Options for creating new {@link Program} instance.
4
+ * {@link Project.constructor | Program constructor} options parameter.
5
5
  */
6
6
  export type ProgramOptions = {
7
7
  /** Program name. */
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/program/ProgramOptions.ts"],"sourcesContent":["import {type ComponentType} from 'react';\nimport {type FallbackProps} from 'react-error-boundary';\n\n/**\n * Options for creating new {@link Program} instance.\n */\nexport type ProgramOptions = {\n /** Program name. */\n name?: string | undefined;\n\n /** Program version. */\n version?: string | undefined;\n\n /** Program description. */\n description?: string | undefined;\n\n /** Program executable name. */\n bin?: string | undefined;\n\n /** Custom error boundary fallback component */\n ErrorBoundaryFallbackComponent?: ComponentType<FallbackProps>;\n};\n"],"names":[],"mappings":"AAAA,WAqBE"}
1
+ {"version":3,"sources":["../../src/program/ProgramOptions.ts"],"sourcesContent":["import {type ComponentType} from 'react';\nimport {type FallbackProps} from 'react-error-boundary';\n\n/**\n * {@link Project.constructor | Program constructor} options parameter.\n */\nexport type ProgramOptions = {\n /** Program name. */\n name?: string | undefined;\n\n /** Program version. */\n version?: string | undefined;\n\n /** Program description. */\n description?: string | undefined;\n\n /** Program executable name. */\n bin?: string | undefined;\n\n /** Custom error boundary fallback component */\n ErrorBoundaryFallbackComponent?: ComponentType<FallbackProps>;\n};\n"],"names":[],"mappings":"AAAA,WAqBE"}
@@ -2,14 +2,14 @@
2
2
  import { type BoxProps } from 'ink';
3
3
  import { type SelectInputItem } from './SelectInputItem.js';
4
4
  /**
5
- * Select input component props.
5
+ * {@linkcode SelectInput} component props.
6
6
  */
7
7
  type SelectInputProps = BoxProps & {
8
8
  /** Text to display as a label. */
9
9
  label: string;
10
10
  /** Items to display in a list. */
11
11
  items: SelectInputItem[];
12
- /** Should component listen to Listen to user's input? Useful in case there are multiple input components at the same time and input must be "routed" to a specific component. */
12
+ /** Should component listen to user's input? Useful in case there are multiple input components at the same time and input must be handled only by a specific component. */
13
13
  focus?: boolean | undefined;
14
14
  /** Index of initially selected item. */
15
15
  initialIndex?: number | undefined;
@@ -25,5 +25,5 @@ type SelectInputProps = BoxProps & {
25
25
  /**
26
26
  * Select input component.
27
27
  */
28
- export declare function SelectInput({ label, items, focus, initialIndex, limit: customLimit, onSelect, onHighlight, isComplete, ...boxProps }: SelectInputProps): JSX.Element;
28
+ export declare function SelectInput({ label, items, focus, initialIndex, limit: userDefinedLimit, onSelect, onHighlight, isComplete, ...boxProps }: SelectInputProps): JSX.Element;
29
29
  export {};
@@ -1,38 +1,38 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text, useInput } from 'ink';
3
3
  import _ from 'lodash';
4
- import { useState, useEffect, useRef, useCallback } from 'react';
4
+ import { useState, useEffect, useRef } from 'react';
5
5
  import * as colors from '../colors.js';
6
6
  import * as icons from '../icons.js';
7
7
  import { rotate } from '../internals.js';
8
8
  /**
9
9
  * Select input component.
10
- */ export function SelectInput({ label, items = [], focus = true, initialIndex = 0, limit: customLimit, onSelect, onHighlight, isComplete, ...boxProps }) {
10
+ */ export function SelectInput({ label, items = [], focus = true, initialIndex = 0, limit: userDefinedLimit, onSelect, onHighlight, isComplete, ...boxProps }) {
11
11
  var _items_selectedIndex, _items_selectedIndex1;
12
- let [rotateIndex, setRotateIndex] = useState(0);
12
+ let [rotatedIndex, setRotatedIndex] = useState(0);
13
13
  let [selectedIndex, setSelectedIndex] = useState(initialIndex);
14
- let hasLimit = typeof customLimit === 'number' && items.length > customLimit;
15
- let limit = hasLimit ? Math.min(customLimit, items.length) : items.length;
14
+ let hasLimit = typeof userDefinedLimit === 'number' && items.length > userDefinedLimit;
15
+ let limit = hasLimit ? Math.min(userDefinedLimit, items.length) : items.length;
16
16
  let previousItems = useRef(items);
17
17
  useEffect(()=>{
18
18
  if (!_.isEqual(previousItems.current.map((item)=>item.value), items.map((item)=>item.value))) {
19
- setRotateIndex(0);
19
+ setRotatedIndex(0);
20
20
  setSelectedIndex(0);
21
21
  }
22
22
  previousItems.current = items;
23
23
  }, [
24
24
  items
25
25
  ]);
26
- let inputHandler = useCallback((input, key)=>{
26
+ useInput((input, key)=>{
27
27
  if (input === 'k' || key.upArrow) {
28
28
  let lastIndex = (hasLimit ? limit : items.length) - 1;
29
- let atFirstIndex = selectedIndex === 0;
29
+ let isAtFirstIndex = selectedIndex === 0;
30
30
  let nextIndex = hasLimit ? selectedIndex : lastIndex;
31
- let nextRotateIndex = atFirstIndex ? rotateIndex + 1 : rotateIndex;
32
- let nextSelectedIndex = atFirstIndex ? nextIndex : selectedIndex - 1;
33
- setRotateIndex(nextRotateIndex);
31
+ let nextRotatedIndex = isAtFirstIndex ? rotatedIndex + 1 : rotatedIndex;
32
+ let nextSelectedIndex = isAtFirstIndex ? nextIndex : selectedIndex - 1;
33
+ setRotatedIndex(nextRotatedIndex);
34
34
  setSelectedIndex(nextSelectedIndex);
35
- let slicedItems = hasLimit ? rotate(items, nextRotateIndex).slice(0, limit) : items;
35
+ let slicedItems = hasLimit ? rotate(items, nextRotatedIndex).slice(0, limit) : items;
36
36
  if (typeof onHighlight === 'function') {
37
37
  onHighlight(slicedItems[nextSelectedIndex]);
38
38
  }
@@ -40,34 +40,25 @@ import { rotate } from '../internals.js';
40
40
  if (input === 'j' || key.downArrow) {
41
41
  let isAtLastIndex = selectedIndex === (hasLimit ? limit : items.length) - 1;
42
42
  let nextIndex = hasLimit ? selectedIndex : 0;
43
- let nextRotateIndex = isAtLastIndex ? rotateIndex - 1 : rotateIndex;
43
+ let nextRotatedIndex = isAtLastIndex ? rotatedIndex - 1 : rotatedIndex;
44
44
  let nextSelectedIndex = isAtLastIndex ? nextIndex : selectedIndex + 1;
45
- setRotateIndex(nextRotateIndex);
45
+ setRotatedIndex(nextRotatedIndex);
46
46
  setSelectedIndex(nextSelectedIndex);
47
- let slicedItems = hasLimit ? rotate(items, nextRotateIndex).slice(0, limit) : items;
47
+ let slicedItems = hasLimit ? rotate(items, nextRotatedIndex).slice(0, limit) : items;
48
48
  if (typeof onHighlight === 'function') {
49
49
  onHighlight(slicedItems[nextSelectedIndex]);
50
50
  }
51
51
  }
52
52
  if (key.return) {
53
- let slicedItems = hasLimit ? rotate(items, rotateIndex).slice(0, limit) : items;
53
+ let slicedItems = hasLimit ? rotate(items, rotatedIndex).slice(0, limit) : items;
54
54
  if (typeof onSelect === 'function') {
55
55
  onSelect(slicedItems[selectedIndex]);
56
56
  }
57
57
  }
58
- }, [
59
- hasLimit,
60
- limit,
61
- rotateIndex,
62
- selectedIndex,
63
- items,
64
- onSelect,
65
- onHighlight
66
- ]);
67
- useInput(inputHandler, {
58
+ }, {
68
59
  isActive: focus
69
60
  });
70
- let visibleItem = hasLimit ? rotate(items, rotateIndex).slice(0, limit) : items;
61
+ let visibleItems = hasLimit ? rotate(items, rotatedIndex).slice(0, limit) : items;
71
62
  return /*#__PURE__*/ _jsxs(Box, {
72
63
  ...boxProps,
73
64
  children: [
@@ -92,7 +83,7 @@ import { rotate } from '../internals.js';
92
83
  children: ((_items_selectedIndex = items[selectedIndex]) === null || _items_selectedIndex === void 0 ? void 0 : _items_selectedIndex.label) ?? String((_items_selectedIndex1 = items[selectedIndex]) === null || _items_selectedIndex1 === void 0 ? void 0 : _items_selectedIndex1.value)
93
84
  }) : /*#__PURE__*/ _jsx(Box, {
94
85
  flexDirection: "column",
95
- children: visibleItem.map((item, index)=>{
86
+ children: visibleItems.map((item, index)=>{
96
87
  let isSelected = index === selectedIndex;
97
88
  return /*#__PURE__*/ _jsxs(Box, {
98
89
  children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/form/SelectInput.tsx"],"sourcesContent":["import {Box, type BoxProps, Text, useInput, type Key} from 'ink';\nimport _ from 'lodash';\nimport {useState, useEffect, useRef, useCallback} from 'react';\n\nimport {type SelectInputItem} from './SelectInputItem.js';\nimport * as colors from '../colors.js';\nimport * as icons from '../icons.js';\nimport {rotate} from '../internals.js';\n\n/**\n * Select input component props.\n */\ntype SelectInputProps = BoxProps & {\n /** Text to display as a label. */\n label: string;\n\n /** Items to display in a list. */\n items: SelectInputItem[];\n\n /** Should component listen to Listen to user's input? Useful in case there are multiple input components at the same time and input must be \"routed\" to a specific component. */\n focus?: boolean | undefined;\n\n /** Index of initially selected item. */\n initialIndex?: number | undefined;\n\n /** Number of items to display. */\n limit?: number | undefined;\n\n /** Handler function that is called when item is selected. */\n onSelect?: ((item: SelectInputItem) => void) | undefined;\n\n /** Handler function that is called when item is highlighted. */\n onHighlight?: ((item: SelectInputItem) => void) | undefined;\n\n /** Indicates whether value has been accepted. Component then doesn't accept more input. */\n isComplete?: boolean | undefined;\n};\n\n/**\n * Select input component.\n */\nexport function SelectInput({\n label,\n items = [],\n focus = true,\n initialIndex = 0,\n limit: customLimit,\n onSelect,\n onHighlight,\n isComplete,\n ...boxProps\n}: SelectInputProps): JSX.Element {\n let [rotateIndex, setRotateIndex] = useState(0);\n let [selectedIndex, setSelectedIndex] = useState(initialIndex);\n let hasLimit = typeof customLimit === 'number' && items.length > customLimit;\n let limit = hasLimit ? Math.min(customLimit as number, items.length) : items.length;\n let previousItems = useRef<SelectInputItem[]>(items);\n\n useEffect(() => {\n if (\n !_.isEqual(\n previousItems.current.map((item) => item.value),\n items.map((item) => item.value),\n )\n ) {\n setRotateIndex(0);\n setSelectedIndex(0);\n }\n\n previousItems.current = items;\n }, [items]);\n\n let inputHandler = useCallback(\n (input: string, key: Key) => {\n if (input === 'k' || key.upArrow) {\n let lastIndex = (hasLimit ? limit : items.length) - 1;\n let atFirstIndex = selectedIndex === 0;\n let nextIndex = hasLimit ? selectedIndex : lastIndex;\n let nextRotateIndex = atFirstIndex ? rotateIndex + 1 : rotateIndex;\n let nextSelectedIndex = atFirstIndex ? nextIndex : selectedIndex - 1;\n\n setRotateIndex(nextRotateIndex);\n setSelectedIndex(nextSelectedIndex);\n\n let slicedItems = hasLimit ? rotate(items, nextRotateIndex).slice(0, limit) : items;\n\n if (typeof onHighlight === 'function') {\n onHighlight(slicedItems[nextSelectedIndex] as SelectInputItem);\n }\n }\n\n if (input === 'j' || key.downArrow) {\n let isAtLastIndex = selectedIndex === (hasLimit ? limit : items.length) - 1;\n let nextIndex = hasLimit ? selectedIndex : 0;\n let nextRotateIndex = isAtLastIndex ? rotateIndex - 1 : rotateIndex;\n let nextSelectedIndex = isAtLastIndex ? nextIndex : selectedIndex + 1;\n\n setRotateIndex(nextRotateIndex);\n setSelectedIndex(nextSelectedIndex);\n\n let slicedItems = hasLimit ? rotate(items, nextRotateIndex).slice(0, limit) : items;\n\n if (typeof onHighlight === 'function') {\n onHighlight(slicedItems[nextSelectedIndex] as SelectInputItem);\n }\n }\n\n if (key.return) {\n let slicedItems = hasLimit ? rotate(items, rotateIndex).slice(0, limit) : items;\n\n if (typeof onSelect === 'function') {\n onSelect(slicedItems[selectedIndex] as SelectInputItem);\n }\n }\n },\n [hasLimit, limit, rotateIndex, selectedIndex, items, onSelect, onHighlight],\n );\n\n useInput(inputHandler, {isActive: focus});\n\n let visibleItem: SelectInputItem[] = hasLimit\n ? rotate(items, rotateIndex).slice(0, limit)\n : items;\n\n return (\n <Box {...boxProps}>\n <Box marginRight={1}>\n <Text>{isComplete ? icons.success : icons.question}</Text>\n </Box>\n\n <Box flexDirection=\"column\">\n <Box marginRight={1}>\n <Text color={colors.highlighted}>{label}</Text>\n </Box>\n {isComplete ? (\n <Text color={colors.body}>\n {items[selectedIndex]?.label ?? String(items[selectedIndex]?.value)}\n </Text>\n ) : (\n <Box flexDirection=\"column\">\n {visibleItem.map((item, index) => {\n let isSelected = index === selectedIndex;\n\n return (\n <Box key={item.value}>\n <Box marginRight={1}>\n {isSelected ? (\n <Text color={colors.selected}>{icons.selectedCircle}</Text>\n ) : (\n <Text color={colors.unselected}>{icons.unselectedCircle}</Text>\n )}\n </Box>\n <Text color={isSelected ? colors.highlighted : colors.muted}>{item.label}</Text>\n </Box>\n );\n })}\n </Box>\n )}\n </Box>\n </Box>\n );\n}\n"],"names":["Box","Text","useInput","_","useState","useEffect","useRef","useCallback","colors","icons","rotate","SelectInput","label","items","focus","initialIndex","limit","customLimit","onSelect","onHighlight","isComplete","boxProps","rotateIndex","setRotateIndex","selectedIndex","setSelectedIndex","hasLimit","length","Math","min","previousItems","isEqual","current","map","item","value","inputHandler","input","key","upArrow","lastIndex","atFirstIndex","nextIndex","nextRotateIndex","nextSelectedIndex","slicedItems","slice","downArrow","isAtLastIndex","return","isActive","visibleItem","marginRight","success","question","flexDirection","color","highlighted","body","String","index","isSelected","selected","selectedCircle","unselected","unselectedCircle","muted"],"mappings":";AAAA,SAAQA,GAAG,EAAiBC,IAAI,EAAEC,QAAQ,QAAiB,MAAM;AACjE,OAAOC,OAAO,SAAS;AACvB,SAAQC,QAAQ,EAAEC,SAAS,EAAEC,MAAM,EAAEC,WAAW,QAAO,QAAQ;AAG/D,YAAYC,YAAY,eAAe;AACvC,YAAYC,WAAW,cAAc;AACrC,SAAQC,MAAM,QAAO,kBAAkB;AA+BvC;;CAEC,GACD,OAAO,SAASC,YAAY,EAC1BC,KAAK,EACLC,QAAQ,EAAE,EACVC,QAAQ,IAAI,EACZC,eAAe,CAAC,EAChBC,OAAOC,WAAW,EAClBC,QAAQ,EACRC,WAAW,EACXC,UAAU,EACV,GAAGC,UACc;QAqFNR,sBAAsCA;IApFjD,IAAI,CAACS,aAAaC,eAAe,GAAGnB,SAAS;IAC7C,IAAI,CAACoB,eAAeC,iBAAiB,GAAGrB,SAASW;IACjD,IAAIW,WAAW,OAAOT,gBAAgB,YAAYJ,MAAMc,MAAM,GAAGV;IACjE,IAAID,QAAQU,WAAWE,KAAKC,GAAG,CAACZ,aAAuBJ,MAAMc,MAAM,IAAId,MAAMc,MAAM;IACnF,IAAIG,gBAAgBxB,OAA0BO;IAE9CR,UAAU;QACR,IACE,CAACF,EAAE4B,OAAO,CACRD,cAAcE,OAAO,CAACC,GAAG,CAAC,CAACC,OAASA,KAAKC,KAAK,GAC9CtB,MAAMoB,GAAG,CAAC,CAACC,OAASA,KAAKC,KAAK,IAEhC;YACAZ,eAAe;YACfE,iBAAiB;QACnB;QAEAK,cAAcE,OAAO,GAAGnB;IAC1B,GAAG;QAACA;KAAM;IAEV,IAAIuB,eAAe7B,YACjB,CAAC8B,OAAeC;QACd,IAAID,UAAU,OAAOC,IAAIC,OAAO,EAAE;YAChC,IAAIC,YAAY,AAACd,CAAAA,WAAWV,QAAQH,MAAMc,MAAM,AAAD,IAAK;YACpD,IAAIc,eAAejB,kBAAkB;YACrC,IAAIkB,YAAYhB,WAAWF,gBAAgBgB;YAC3C,IAAIG,kBAAkBF,eAAenB,cAAc,IAAIA;YACvD,IAAIsB,oBAAoBH,eAAeC,YAAYlB,gBAAgB;YAEnED,eAAeoB;YACflB,iBAAiBmB;YAEjB,IAAIC,cAAcnB,WAAWhB,OAAOG,OAAO8B,iBAAiBG,KAAK,CAAC,GAAG9B,SAASH;YAE9E,IAAI,OAAOM,gBAAgB,YAAY;gBACrCA,YAAY0B,WAAW,CAACD,kBAAkB;YAC5C;QACF;QAEA,IAAIP,UAAU,OAAOC,IAAIS,SAAS,EAAE;YAClC,IAAIC,gBAAgBxB,kBAAkB,AAACE,CAAAA,WAAWV,QAAQH,MAAMc,MAAM,AAAD,IAAK;YAC1E,IAAIe,YAAYhB,WAAWF,gBAAgB;YAC3C,IAAImB,kBAAkBK,gBAAgB1B,cAAc,IAAIA;YACxD,IAAIsB,oBAAoBI,gBAAgBN,YAAYlB,gBAAgB;YAEpED,eAAeoB;YACflB,iBAAiBmB;YAEjB,IAAIC,cAAcnB,WAAWhB,OAAOG,OAAO8B,iBAAiBG,KAAK,CAAC,GAAG9B,SAASH;YAE9E,IAAI,OAAOM,gBAAgB,YAAY;gBACrCA,YAAY0B,WAAW,CAACD,kBAAkB;YAC5C;QACF;QAEA,IAAIN,IAAIW,MAAM,EAAE;YACd,IAAIJ,cAAcnB,WAAWhB,OAAOG,OAAOS,aAAawB,KAAK,CAAC,GAAG9B,SAASH;YAE1E,IAAI,OAAOK,aAAa,YAAY;gBAClCA,SAAS2B,WAAW,CAACrB,cAAc;YACrC;QACF;IACF,GACA;QAACE;QAAUV;QAAOM;QAAaE;QAAeX;QAAOK;QAAUC;KAAY;IAG7EjB,SAASkC,cAAc;QAACc,UAAUpC;IAAK;IAEvC,IAAIqC,cAAiCzB,WACjChB,OAAOG,OAAOS,aAAawB,KAAK,CAAC,GAAG9B,SACpCH;IAEJ,qBACE,MAACb;QAAK,GAAGqB,QAAQ;;0BACf,KAACrB;gBAAIoD,aAAa;0BAChB,cAAA,KAACnD;8BAAMmB,aAAaX,MAAM4C,OAAO,GAAG5C,MAAM6C,QAAQ;;;0BAGpD,MAACtD;gBAAIuD,eAAc;;kCACjB,KAACvD;wBAAIoD,aAAa;kCAChB,cAAA,KAACnD;4BAAKuD,OAAOhD,OAAOiD,WAAW;sCAAG7C;;;oBAEnCQ,2BACC,KAACnB;wBAAKuD,OAAOhD,OAAOkD,IAAI;kCACrB7C,EAAAA,uBAAAA,KAAK,CAACW,cAAc,cAApBX,2CAAAA,qBAAsBD,KAAK,KAAI+C,QAAO9C,wBAAAA,KAAK,CAACW,cAAc,cAApBX,4CAAAA,sBAAsBsB,KAAK;uCAGpE,KAACnC;wBAAIuD,eAAc;kCAChBJ,YAAYlB,GAAG,CAAC,CAACC,MAAM0B;4BACtB,IAAIC,aAAaD,UAAUpC;4BAE3B,qBACE,MAACxB;;kDACC,KAACA;wCAAIoD,aAAa;kDACfS,2BACC,KAAC5D;4CAAKuD,OAAOhD,OAAOsD,QAAQ;sDAAGrD,MAAMsD,cAAc;2DAEnD,KAAC9D;4CAAKuD,OAAOhD,OAAOwD,UAAU;sDAAGvD,MAAMwD,gBAAgB;;;kDAG3D,KAAChE;wCAAKuD,OAAOK,aAAarD,OAAOiD,WAAW,GAAGjD,OAAO0D,KAAK;kDAAGhC,KAAKtB,KAAK;;;+BARhEsB,KAAKC,KAAK;wBAWxB;;;;;;AAMZ"}
1
+ {"version":3,"sources":["../../../src/ui/form/SelectInput.tsx"],"sourcesContent":["import {Box, type BoxProps, Text, useInput, type Key} from 'ink';\nimport _ from 'lodash';\nimport {useState, useEffect, useRef} from 'react';\n\nimport {type SelectInputItem} from './SelectInputItem.js';\nimport * as colors from '../colors.js';\nimport * as icons from '../icons.js';\nimport {rotate} from '../internals.js';\n\n/**\n * {@linkcode SelectInput} component props.\n */\ntype SelectInputProps = BoxProps & {\n /** Text to display as a label. */\n label: string;\n\n /** Items to display in a list. */\n items: SelectInputItem[];\n\n /** Should component listen to user's input? Useful in case there are multiple input components at the same time and input must be handled only by a specific component. */\n focus?: boolean | undefined;\n\n /** Index of initially selected item. */\n initialIndex?: number | undefined;\n\n /** Number of items to display. */\n limit?: number | undefined;\n\n /** Handler function that is called when item is selected. */\n onSelect?: ((item: SelectInputItem) => void) | undefined;\n\n /** Handler function that is called when item is highlighted. */\n onHighlight?: ((item: SelectInputItem) => void) | undefined;\n\n /** Indicates whether value has been accepted. Component then doesn't accept more input. */\n isComplete?: boolean | undefined;\n};\n\n/**\n * Select input component.\n */\nexport function SelectInput({\n label,\n items = [],\n focus = true,\n initialIndex = 0,\n limit: userDefinedLimit,\n onSelect,\n onHighlight,\n isComplete,\n ...boxProps\n}: SelectInputProps): JSX.Element {\n let [rotatedIndex, setRotatedIndex] = useState(0);\n let [selectedIndex, setSelectedIndex] = useState(initialIndex);\n let hasLimit = typeof userDefinedLimit === 'number' && items.length > userDefinedLimit;\n let limit = hasLimit ? Math.min(userDefinedLimit as number, items.length) : items.length;\n let previousItems = useRef<SelectInputItem[]>(items);\n\n useEffect(() => {\n if (\n !_.isEqual(\n previousItems.current.map((item) => item.value),\n items.map((item) => item.value),\n )\n ) {\n setRotatedIndex(0);\n setSelectedIndex(0);\n }\n\n previousItems.current = items;\n }, [items]);\n\n useInput(\n (input: string, key: Key) => {\n if (input === 'k' || key.upArrow) {\n let lastIndex = (hasLimit ? limit : items.length) - 1;\n let isAtFirstIndex = selectedIndex === 0;\n let nextIndex = hasLimit ? selectedIndex : lastIndex;\n let nextRotatedIndex = isAtFirstIndex ? rotatedIndex + 1 : rotatedIndex;\n let nextSelectedIndex = isAtFirstIndex ? nextIndex : selectedIndex - 1;\n\n setRotatedIndex(nextRotatedIndex);\n setSelectedIndex(nextSelectedIndex);\n\n let slicedItems = hasLimit ? rotate(items, nextRotatedIndex).slice(0, limit) : items;\n\n if (typeof onHighlight === 'function') {\n onHighlight(slicedItems[nextSelectedIndex] as SelectInputItem);\n }\n }\n\n if (input === 'j' || key.downArrow) {\n let isAtLastIndex = selectedIndex === (hasLimit ? limit : items.length) - 1;\n let nextIndex = hasLimit ? selectedIndex : 0;\n let nextRotatedIndex = isAtLastIndex ? rotatedIndex - 1 : rotatedIndex;\n let nextSelectedIndex = isAtLastIndex ? nextIndex : selectedIndex + 1;\n\n setRotatedIndex(nextRotatedIndex);\n setSelectedIndex(nextSelectedIndex);\n\n let slicedItems = hasLimit ? rotate(items, nextRotatedIndex).slice(0, limit) : items;\n\n if (typeof onHighlight === 'function') {\n onHighlight(slicedItems[nextSelectedIndex] as SelectInputItem);\n }\n }\n\n if (key.return) {\n let slicedItems = hasLimit ? rotate(items, rotatedIndex).slice(0, limit) : items;\n\n if (typeof onSelect === 'function') {\n onSelect(slicedItems[selectedIndex] as SelectInputItem);\n }\n }\n },\n {isActive: focus},\n );\n\n let visibleItems: SelectInputItem[] = hasLimit\n ? rotate(items, rotatedIndex).slice(0, limit)\n : items;\n\n return (\n <Box {...boxProps}>\n <Box marginRight={1}>\n <Text>{isComplete ? icons.success : icons.question}</Text>\n </Box>\n\n <Box flexDirection=\"column\">\n <Box marginRight={1}>\n <Text color={colors.highlighted}>{label}</Text>\n </Box>\n {isComplete ? (\n <Text color={colors.body}>\n {items[selectedIndex]?.label ?? String(items[selectedIndex]?.value)}\n </Text>\n ) : (\n <Box flexDirection=\"column\">\n {visibleItems.map((item, index) => {\n let isSelected = index === selectedIndex;\n\n return (\n <Box key={item.value}>\n <Box marginRight={1}>\n {isSelected ? (\n <Text color={colors.selected}>{icons.selectedCircle}</Text>\n ) : (\n <Text color={colors.unselected}>{icons.unselectedCircle}</Text>\n )}\n </Box>\n <Text color={isSelected ? colors.highlighted : colors.muted}>{item.label}</Text>\n </Box>\n );\n })}\n </Box>\n )}\n </Box>\n </Box>\n );\n}\n"],"names":["Box","Text","useInput","_","useState","useEffect","useRef","colors","icons","rotate","SelectInput","label","items","focus","initialIndex","limit","userDefinedLimit","onSelect","onHighlight","isComplete","boxProps","rotatedIndex","setRotatedIndex","selectedIndex","setSelectedIndex","hasLimit","length","Math","min","previousItems","isEqual","current","map","item","value","input","key","upArrow","lastIndex","isAtFirstIndex","nextIndex","nextRotatedIndex","nextSelectedIndex","slicedItems","slice","downArrow","isAtLastIndex","return","isActive","visibleItems","marginRight","success","question","flexDirection","color","highlighted","body","String","index","isSelected","selected","selectedCircle","unselected","unselectedCircle","muted"],"mappings":";AAAA,SAAQA,GAAG,EAAiBC,IAAI,EAAEC,QAAQ,QAAiB,MAAM;AACjE,OAAOC,OAAO,SAAS;AACvB,SAAQC,QAAQ,EAAEC,SAAS,EAAEC,MAAM,QAAO,QAAQ;AAGlD,YAAYC,YAAY,eAAe;AACvC,YAAYC,WAAW,cAAc;AACrC,SAAQC,MAAM,QAAO,kBAAkB;AA+BvC;;CAEC,GACD,OAAO,SAASC,YAAY,EAC1BC,KAAK,EACLC,QAAQ,EAAE,EACVC,QAAQ,IAAI,EACZC,eAAe,CAAC,EAChBC,OAAOC,gBAAgB,EACvBC,QAAQ,EACRC,WAAW,EACXC,UAAU,EACV,GAAGC,UACc;QAmFNR,sBAAsCA;IAlFjD,IAAI,CAACS,cAAcC,gBAAgB,GAAGlB,SAAS;IAC/C,IAAI,CAACmB,eAAeC,iBAAiB,GAAGpB,SAASU;IACjD,IAAIW,WAAW,OAAOT,qBAAqB,YAAYJ,MAAMc,MAAM,GAAGV;IACtE,IAAID,QAAQU,WAAWE,KAAKC,GAAG,CAACZ,kBAA4BJ,MAAMc,MAAM,IAAId,MAAMc,MAAM;IACxF,IAAIG,gBAAgBvB,OAA0BM;IAE9CP,UAAU;QACR,IACE,CAACF,EAAE2B,OAAO,CACRD,cAAcE,OAAO,CAACC,GAAG,CAAC,CAACC,OAASA,KAAKC,KAAK,GAC9CtB,MAAMoB,GAAG,CAAC,CAACC,OAASA,KAAKC,KAAK,IAEhC;YACAZ,gBAAgB;YAChBE,iBAAiB;QACnB;QAEAK,cAAcE,OAAO,GAAGnB;IAC1B,GAAG;QAACA;KAAM;IAEVV,SACE,CAACiC,OAAeC;QACd,IAAID,UAAU,OAAOC,IAAIC,OAAO,EAAE;YAChC,IAAIC,YAAY,AAACb,CAAAA,WAAWV,QAAQH,MAAMc,MAAM,AAAD,IAAK;YACpD,IAAIa,iBAAiBhB,kBAAkB;YACvC,IAAIiB,YAAYf,WAAWF,gBAAgBe;YAC3C,IAAIG,mBAAmBF,iBAAiBlB,eAAe,IAAIA;YAC3D,IAAIqB,oBAAoBH,iBAAiBC,YAAYjB,gBAAgB;YAErED,gBAAgBmB;YAChBjB,iBAAiBkB;YAEjB,IAAIC,cAAclB,WAAWhB,OAAOG,OAAO6B,kBAAkBG,KAAK,CAAC,GAAG7B,SAASH;YAE/E,IAAI,OAAOM,gBAAgB,YAAY;gBACrCA,YAAYyB,WAAW,CAACD,kBAAkB;YAC5C;QACF;QAEA,IAAIP,UAAU,OAAOC,IAAIS,SAAS,EAAE;YAClC,IAAIC,gBAAgBvB,kBAAkB,AAACE,CAAAA,WAAWV,QAAQH,MAAMc,MAAM,AAAD,IAAK;YAC1E,IAAIc,YAAYf,WAAWF,gBAAgB;YAC3C,IAAIkB,mBAAmBK,gBAAgBzB,eAAe,IAAIA;YAC1D,IAAIqB,oBAAoBI,gBAAgBN,YAAYjB,gBAAgB;YAEpED,gBAAgBmB;YAChBjB,iBAAiBkB;YAEjB,IAAIC,cAAclB,WAAWhB,OAAOG,OAAO6B,kBAAkBG,KAAK,CAAC,GAAG7B,SAASH;YAE/E,IAAI,OAAOM,gBAAgB,YAAY;gBACrCA,YAAYyB,WAAW,CAACD,kBAAkB;YAC5C;QACF;QAEA,IAAIN,IAAIW,MAAM,EAAE;YACd,IAAIJ,cAAclB,WAAWhB,OAAOG,OAAOS,cAAcuB,KAAK,CAAC,GAAG7B,SAASH;YAE3E,IAAI,OAAOK,aAAa,YAAY;gBAClCA,SAAS0B,WAAW,CAACpB,cAAc;YACrC;QACF;IACF,GACA;QAACyB,UAAUnC;IAAK;IAGlB,IAAIoC,eAAkCxB,WAClChB,OAAOG,OAAOS,cAAcuB,KAAK,CAAC,GAAG7B,SACrCH;IAEJ,qBACE,MAACZ;QAAK,GAAGoB,QAAQ;;0BACf,KAACpB;gBAAIkD,aAAa;0BAChB,cAAA,KAACjD;8BAAMkB,aAAaX,MAAM2C,OAAO,GAAG3C,MAAM4C,QAAQ;;;0BAGpD,MAACpD;gBAAIqD,eAAc;;kCACjB,KAACrD;wBAAIkD,aAAa;kCAChB,cAAA,KAACjD;4BAAKqD,OAAO/C,OAAOgD,WAAW;sCAAG5C;;;oBAEnCQ,2BACC,KAAClB;wBAAKqD,OAAO/C,OAAOiD,IAAI;kCACrB5C,EAAAA,uBAAAA,KAAK,CAACW,cAAc,cAApBX,2CAAAA,qBAAsBD,KAAK,KAAI8C,QAAO7C,wBAAAA,KAAK,CAACW,cAAc,cAApBX,4CAAAA,sBAAsBsB,KAAK;uCAGpE,KAAClC;wBAAIqD,eAAc;kCAChBJ,aAAajB,GAAG,CAAC,CAACC,MAAMyB;4BACvB,IAAIC,aAAaD,UAAUnC;4BAE3B,qBACE,MAACvB;;kDACC,KAACA;wCAAIkD,aAAa;kDACfS,2BACC,KAAC1D;4CAAKqD,OAAO/C,OAAOqD,QAAQ;sDAAGpD,MAAMqD,cAAc;2DAEnD,KAAC5D;4CAAKqD,OAAO/C,OAAOuD,UAAU;sDAAGtD,MAAMuD,gBAAgB;;;kDAG3D,KAAC9D;wCAAKqD,OAAOK,aAAapD,OAAOgD,WAAW,GAAGhD,OAAOyD,KAAK;kDAAG/B,KAAKtB,KAAK;;;+BARhEsB,KAAKC,KAAK;wBAWxB;;;;;;AAMZ"}
@@ -9,7 +9,7 @@ export type TextInputProps = BoxProps & {
9
9
  placeholder?: string | undefined;
10
10
  /** Value. */
11
11
  value: string;
12
- /** Should component listen to Listen to user's input? Useful in case there are multiple input components at the same time and input must be "routed" to a specific component. */
12
+ /** Should component listen to Listen to user's input? Useful in case there are multiple input components at the same time and input must be handled only by a specific component. */
13
13
  focus?: boolean | undefined;
14
14
  /** Replace all chars and mask the value. Useful for password inputs. */
15
15
  mask?: string | undefined;
@@ -35,18 +35,15 @@ import * as icons from '../icons.js';
35
35
  let value = mask ? mask.repeat(originalValue.length) : originalValue;
36
36
  let renderedValue = value;
37
37
  let renderedPlaceholder = placeholder ? chalk[colors.muted](placeholder) : undefined;
38
- // fake mouse cursor; dealing with with actual cursor and ANSI escape codes is inconvinient
39
38
  if (showCursor && focus) {
40
39
  renderedPlaceholder = placeholder.length > 0 ? chalk.inverse(placeholder[0]) + chalk[colors.muted](placeholder.slice(1)) : chalk.inverse(' ');
41
40
  renderedValue = value.length > 0 ? '' : chalk.inverse(' ');
42
- let i = 0;
43
- for (let char of value){
44
- if (i >= state.cursorOffset && i <= state.cursorOffset) {
45
- renderedValue += chalk.inverse(char);
41
+ for (let [i, character] of Object.entries(value)){
42
+ if (Number(i) >= state.cursorOffset && Number(i) <= state.cursorOffset) {
43
+ renderedValue += chalk.inverse(character);
46
44
  } else {
47
- renderedValue += char;
45
+ renderedValue += character;
48
46
  }
49
- i += 1;
50
47
  }
51
48
  if (value.length > 0 && state.cursorOffset === value.length) {
52
49
  renderedValue += chalk.inverse(' ');
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/form/TextInput.tsx"],"sourcesContent":["// TODO: resolve\n/* eslint-disable complexity -- TODO */\nimport chalk from 'chalk';\nimport {Box, type BoxProps, Text, useInput} from 'ink';\nimport {useEffect, useState} from 'react';\n\nimport * as colors from '../colors.js';\nimport * as icons from '../icons.js';\n\n/**\n * Text input component props.\n */\nexport type TextInputProps = BoxProps & {\n /** Text to display as a label. */\n label: string;\n\n /** Text to display when `value` is empty. */\n placeholder?: string | undefined;\n\n /** Value. */\n value: string;\n\n /** Should component listen to Listen to user's input? Useful in case there are multiple input components at the same time and input must be \"routed\" to a specific component. */\n focus?: boolean | undefined;\n\n /** Replace all chars and mask the value. Useful for password inputs. */\n mask?: string | undefined;\n\n /** Show cursor and allow navigation using arrow keys. */\n showCursor?: boolean | undefined;\n\n /** Validation function that is called when value changes. If this function throws an error*/\n validate?: ((value: string) => void) | undefined;\n\n /** Handler function that is called when value changes. */\n onChange: (value: string) => void;\n\n /** Handler function that is called when enter is pressed. */\n onSubmit?: ((value: string) => void) | undefined;\n\n /** Indicates whether value has been accepted. Component then doesn't accept more input. */\n isComplete?: boolean | undefined;\n};\n\n/**\n * Text input component.\n */\nexport function TextInput({\n label,\n validate,\n isComplete,\n value: originalValue,\n placeholder = '',\n focus = true,\n mask,\n showCursor = true,\n onChange,\n onSubmit,\n ...boxProps\n}: TextInputProps) {\n let [validationError, setValidationError] = useState('');\n let [state, setState] = useState({\n cursorOffset: (originalValue || '').length,\n cursorWidth: 0,\n });\n\n useEffect(() => {\n setState((previousState) => {\n if (!focus || !showCursor) {\n return previousState;\n }\n\n let newValue = originalValue || '';\n\n if (previousState.cursorOffset > newValue.length - 1) {\n return {\n cursorOffset: newValue.length,\n cursorWidth: 0,\n };\n }\n\n return previousState;\n });\n }, [originalValue, focus, showCursor]);\n\n let value = mask ? mask.repeat(originalValue.length) : originalValue;\n let renderedValue = value;\n let renderedPlaceholder = placeholder ? chalk[colors.muted](placeholder) : undefined;\n\n // fake mouse cursor; dealing with with actual cursor and ANSI escape codes is inconvinient\n if (showCursor && focus) {\n renderedPlaceholder =\n placeholder.length > 0\n ? chalk.inverse(placeholder[0]) + chalk[colors.muted](placeholder.slice(1))\n : chalk.inverse(' ');\n\n renderedValue = value.length > 0 ? '' : chalk.inverse(' ');\n\n let i = 0;\n\n for (let char of value) {\n if (i >= state.cursorOffset && i <= state.cursorOffset) {\n renderedValue += chalk.inverse(char);\n } else {\n renderedValue += char;\n }\n\n i += 1;\n }\n\n if (value.length > 0 && state.cursorOffset === value.length) {\n renderedValue += chalk.inverse(' ');\n }\n }\n\n useInput(\n (input, key) => {\n if (\n key.upArrow ||\n key.downArrow ||\n (key.ctrl && input === 'c') ||\n (key.shift && key.tab) ||\n key.tab\n ) {\n return;\n }\n\n if (key.return) {\n if (onSubmit) {\n if (validate) {\n try {\n validate(originalValue);\n } catch (error: unknown) {\n setValidationError((error as Error).message);\n\n return;\n }\n }\n\n if (validationError) {\n setValidationError('');\n } else {\n onSubmit(originalValue);\n }\n }\n\n return;\n }\n\n let nextCursorOffset = state.cursorOffset;\n let nextValue = originalValue;\n let nextCursorWidth = 0;\n\n if (key.leftArrow) {\n if (showCursor) {\n nextCursorOffset -= 1;\n }\n } else if (key.rightArrow) {\n if (showCursor) {\n nextCursorOffset += 1;\n }\n } else if (key.backspace || key.delete) {\n if (state.cursorOffset > 0) {\n nextValue =\n originalValue.slice(0, state.cursorOffset - 1) +\n originalValue.slice(state.cursorOffset, originalValue.length);\n\n nextCursorOffset -= 1;\n }\n } else {\n nextValue =\n originalValue.slice(0, state.cursorOffset) +\n input +\n originalValue.slice(state.cursorOffset, originalValue.length);\n\n nextCursorOffset += input.length;\n\n if (input.length > 1) {\n nextCursorWidth = input.length;\n }\n }\n\n if (state.cursorOffset < 0) {\n nextCursorOffset = 0;\n }\n\n if (state.cursorOffset > originalValue.length) {\n nextCursorOffset = originalValue.length;\n }\n\n setState({\n cursorOffset: nextCursorOffset,\n cursorWidth: nextCursorWidth,\n });\n\n if (nextValue !== originalValue) {\n onChange(nextValue);\n }\n },\n {isActive: focus},\n );\n\n return (\n <Box {...boxProps}>\n <Box marginRight={1}>\n <Text>{isComplete ? icons.success : icons.question}</Text>\n </Box>\n\n <Box flexDirection=\"column\">\n <Box marginRight={1}>\n <Text color={colors.highlighted}>{label}</Text>\n </Box>\n {isComplete ? (\n <Text color={colors.body}>{value}</Text>\n ) : (\n <Text color={colors.highlighted}>\n {placeholder ? (value.length > 0 ? renderedValue : renderedPlaceholder) : renderedValue}\n </Text>\n )}\n {validationError ? (\n <Box marginTop={0}>\n <Text color={colors.warning}>{validationError}</Text>\n </Box>\n ) : null}\n </Box>\n </Box>\n );\n}\n"],"names":["chalk","Box","Text","useInput","useEffect","useState","colors","icons","TextInput","label","validate","isComplete","value","originalValue","placeholder","focus","mask","showCursor","onChange","onSubmit","boxProps","validationError","setValidationError","state","setState","cursorOffset","length","cursorWidth","previousState","newValue","repeat","renderedValue","renderedPlaceholder","muted","undefined","inverse","slice","i","char","input","key","upArrow","downArrow","ctrl","shift","tab","return","error","message","nextCursorOffset","nextValue","nextCursorWidth","leftArrow","rightArrow","backspace","delete","isActive","marginRight","success","question","flexDirection","color","highlighted","body","marginTop","warning"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/ui/form/TextInput.tsx"],"sourcesContent":["// TODO: resolve\n/* eslint-disable complexity -- TODO */\nimport chalk from 'chalk';\nimport {Box, type BoxProps, Text, useInput} from 'ink';\nimport {useEffect, useState} from 'react';\n\nimport * as colors from '../colors.js';\nimport * as icons from '../icons.js';\n\n/**\n * Text input component props.\n */\nexport type TextInputProps = BoxProps & {\n /** Text to display as a label. */\n label: string;\n\n /** Text to display when `value` is empty. */\n placeholder?: string | undefined;\n\n /** Value. */\n value: string;\n\n /** Should component listen to Listen to user's input? Useful in case there are multiple input components at the same time and input must be handled only by a specific component. */\n focus?: boolean | undefined;\n\n /** Replace all chars and mask the value. Useful for password inputs. */\n mask?: string | undefined;\n\n /** Show cursor and allow navigation using arrow keys. */\n showCursor?: boolean | undefined;\n\n /** Validation function that is called when value changes. If this function throws an error*/\n validate?: ((value: string) => void) | undefined;\n\n /** Handler function that is called when value changes. */\n onChange: (value: string) => void;\n\n /** Handler function that is called when enter is pressed. */\n onSubmit?: ((value: string) => void) | undefined;\n\n /** Indicates whether value has been accepted. Component then doesn't accept more input. */\n isComplete?: boolean | undefined;\n};\n\n/**\n * Text input component.\n */\nexport function TextInput({\n label,\n validate,\n isComplete,\n value: originalValue,\n placeholder = '',\n focus = true,\n mask,\n showCursor = true,\n onChange,\n onSubmit,\n ...boxProps\n}: TextInputProps) {\n let [validationError, setValidationError] = useState('');\n let [state, setState] = useState({\n cursorOffset: (originalValue || '').length,\n cursorWidth: 0,\n });\n\n useEffect(() => {\n setState((previousState) => {\n if (!focus || !showCursor) {\n return previousState;\n }\n\n let newValue = originalValue || '';\n\n if (previousState.cursorOffset > newValue.length - 1) {\n return {\n cursorOffset: newValue.length,\n cursorWidth: 0,\n };\n }\n\n return previousState;\n });\n }, [originalValue, focus, showCursor]);\n\n let value = mask ? mask.repeat(originalValue.length) : originalValue;\n let renderedValue = value;\n let renderedPlaceholder = placeholder ? chalk[colors.muted](placeholder) : undefined;\n\n if (showCursor && focus) {\n renderedPlaceholder =\n placeholder.length > 0\n ? chalk.inverse(placeholder[0]) + chalk[colors.muted](placeholder.slice(1))\n : chalk.inverse(' ');\n\n renderedValue = value.length > 0 ? '' : chalk.inverse(' ');\n\n for (let [i, character] of Object.entries(value)) {\n if (Number(i) >= state.cursorOffset && Number(i) <= state.cursorOffset) {\n renderedValue += chalk.inverse(character);\n } else {\n renderedValue += character;\n }\n }\n\n if (value.length > 0 && state.cursorOffset === value.length) {\n renderedValue += chalk.inverse(' ');\n }\n }\n\n useInput(\n (input, key) => {\n if (\n key.upArrow ||\n key.downArrow ||\n (key.ctrl && input === 'c') ||\n (key.shift && key.tab) ||\n key.tab\n ) {\n return;\n }\n\n if (key.return) {\n if (onSubmit) {\n if (validate) {\n try {\n validate(originalValue);\n } catch (error: unknown) {\n setValidationError((error as Error).message);\n\n return;\n }\n }\n\n if (validationError) {\n setValidationError('');\n } else {\n onSubmit(originalValue);\n }\n }\n\n return;\n }\n\n let nextCursorOffset = state.cursorOffset;\n let nextValue = originalValue;\n let nextCursorWidth = 0;\n\n if (key.leftArrow) {\n if (showCursor) {\n nextCursorOffset -= 1;\n }\n } else if (key.rightArrow) {\n if (showCursor) {\n nextCursorOffset += 1;\n }\n } else if (key.backspace || key.delete) {\n if (state.cursorOffset > 0) {\n nextValue =\n originalValue.slice(0, state.cursorOffset - 1) +\n originalValue.slice(state.cursorOffset, originalValue.length);\n\n nextCursorOffset -= 1;\n }\n } else {\n nextValue =\n originalValue.slice(0, state.cursorOffset) +\n input +\n originalValue.slice(state.cursorOffset, originalValue.length);\n\n nextCursorOffset += input.length;\n\n if (input.length > 1) {\n nextCursorWidth = input.length;\n }\n }\n\n if (state.cursorOffset < 0) {\n nextCursorOffset = 0;\n }\n\n if (state.cursorOffset > originalValue.length) {\n nextCursorOffset = originalValue.length;\n }\n\n setState({\n cursorOffset: nextCursorOffset,\n cursorWidth: nextCursorWidth,\n });\n\n if (nextValue !== originalValue) {\n onChange(nextValue);\n }\n },\n {isActive: focus},\n );\n\n return (\n <Box {...boxProps}>\n <Box marginRight={1}>\n <Text>{isComplete ? icons.success : icons.question}</Text>\n </Box>\n\n <Box flexDirection=\"column\">\n <Box marginRight={1}>\n <Text color={colors.highlighted}>{label}</Text>\n </Box>\n {isComplete ? (\n <Text color={colors.body}>{value}</Text>\n ) : (\n <Text color={colors.highlighted}>\n {placeholder ? (value.length > 0 ? renderedValue : renderedPlaceholder) : renderedValue}\n </Text>\n )}\n {validationError ? (\n <Box marginTop={0}>\n <Text color={colors.warning}>{validationError}</Text>\n </Box>\n ) : null}\n </Box>\n </Box>\n );\n}\n"],"names":["chalk","Box","Text","useInput","useEffect","useState","colors","icons","TextInput","label","validate","isComplete","value","originalValue","placeholder","focus","mask","showCursor","onChange","onSubmit","boxProps","validationError","setValidationError","state","setState","cursorOffset","length","cursorWidth","previousState","newValue","repeat","renderedValue","renderedPlaceholder","muted","undefined","inverse","slice","i","character","Object","entries","Number","input","key","upArrow","downArrow","ctrl","shift","tab","return","error","message","nextCursorOffset","nextValue","nextCursorWidth","leftArrow","rightArrow","backspace","delete","isActive","marginRight","success","question","flexDirection","color","highlighted","body","marginTop","warning"],"mappings":"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"}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@jakubmazanec/cli",
3
- "version": "0.1.0-bc209e5",
3
+ "version": "0.1.0-dee6883",
4
4
  "description": "TypeScript-first library for building CLIs.",
5
5
  "repository": {
6
6
  "type": "git",
7
- "url": "https://github.com/jakubmazanec/js-tools",
7
+ "url": "git@github.com:jakubmazanec/js-tools.git",
8
8
  "directory": "packages/cli"
9
9
  },
10
10
  "license": "LGPL-3.0-only",
@@ -27,10 +27,10 @@
27
27
  "typecheck": "tsc --project tsconfig.typecheck.json"
28
28
  },
29
29
  "dependencies": {
30
- "@jakubmazanec/args": "1.0.1-bc209e5",
31
- "@jakubmazanec/error": "2.0.1-bc209e5",
32
- "@jakubmazanec/ts-utils": "2.1.0-bc209e5",
33
- "@jakubmazanec/zod-utils": "1.0.0-bc209e5",
30
+ "@jakubmazanec/args": "1.0.1-dee6883",
31
+ "@jakubmazanec/error": "2.0.1-dee6883",
32
+ "@jakubmazanec/ts-utils": "2.1.0-dee6883",
33
+ "@jakubmazanec/zod-utils": "1.0.0-dee6883",
34
34
  "chalk": "^5.2.0",
35
35
  "lodash": "^4.17.21",
36
36
  "react-error-boundary": "^4.0.7",
@@ -21,7 +21,6 @@ export class Command<
21
21
  D extends boolean = false,
22
22
  E extends boolean = false,
23
23
  > {
24
- /* implements ProgramCommand*/
25
24
  readonly name: A;
26
25
  readonly description?: string;
27
26
  readonly parameters: B extends ParametersConfig ? B : null;
@@ -1,7 +1,7 @@
1
1
  import {type OptionsConfig, type ParametersConfig} from '@jakubmazanec/args';
2
2
 
3
3
  /**
4
- * New command options.
4
+ * {@link Command.constructor | Command constructor} options parameter.
5
5
  */
6
6
  export type CommandOptions<
7
7
  B extends ParametersConfig,
@@ -81,6 +81,8 @@ export class Program {
81
81
 
82
82
  /**
83
83
  * Renders a React element using Ink and outputs to the configured streams.
84
+ *
85
+ * @param element The React element to render.
84
86
  */
85
87
  async renderElement(element: React.ReactElement): Promise<void> {
86
88
  if (this.isRendering) {
@@ -2,7 +2,7 @@ import {type ComponentType} from 'react';
2
2
  import {type FallbackProps} from 'react-error-boundary';
3
3
 
4
4
  /**
5
- * Options for creating new {@link Program} instance.
5
+ * {@link Project.constructor | Program constructor} options parameter.
6
6
  */
7
7
  export type ProgramOptions = {
8
8
  /** Program name. */
@@ -1,6 +1,6 @@
1
1
  import {Box, type BoxProps, Text, useInput, type Key} from 'ink';
2
2
  import _ from 'lodash';
3
- import {useState, useEffect, useRef, useCallback} from 'react';
3
+ import {useState, useEffect, useRef} from 'react';
4
4
 
5
5
  import {type SelectInputItem} from './SelectInputItem.js';
6
6
  import * as colors from '../colors.js';
@@ -8,7 +8,7 @@ import * as icons from '../icons.js';
8
8
  import {rotate} from '../internals.js';
9
9
 
10
10
  /**
11
- * Select input component props.
11
+ * {@linkcode SelectInput} component props.
12
12
  */
13
13
  type SelectInputProps = BoxProps & {
14
14
  /** Text to display as a label. */
@@ -17,7 +17,7 @@ type SelectInputProps = BoxProps & {
17
17
  /** Items to display in a list. */
18
18
  items: SelectInputItem[];
19
19
 
20
- /** Should component listen to Listen to user's input? Useful in case there are multiple input components at the same time and input must be "routed" to a specific component. */
20
+ /** Should component listen to user's input? Useful in case there are multiple input components at the same time and input must be handled only by a specific component. */
21
21
  focus?: boolean | undefined;
22
22
 
23
23
  /** Index of initially selected item. */
@@ -44,16 +44,16 @@ export function SelectInput({
44
44
  items = [],
45
45
  focus = true,
46
46
  initialIndex = 0,
47
- limit: customLimit,
47
+ limit: userDefinedLimit,
48
48
  onSelect,
49
49
  onHighlight,
50
50
  isComplete,
51
51
  ...boxProps
52
52
  }: SelectInputProps): JSX.Element {
53
- let [rotateIndex, setRotateIndex] = useState(0);
53
+ let [rotatedIndex, setRotatedIndex] = useState(0);
54
54
  let [selectedIndex, setSelectedIndex] = useState(initialIndex);
55
- let hasLimit = typeof customLimit === 'number' && items.length > customLimit;
56
- let limit = hasLimit ? Math.min(customLimit as number, items.length) : items.length;
55
+ let hasLimit = typeof userDefinedLimit === 'number' && items.length > userDefinedLimit;
56
+ let limit = hasLimit ? Math.min(userDefinedLimit as number, items.length) : items.length;
57
57
  let previousItems = useRef<SelectInputItem[]>(items);
58
58
 
59
59
  useEffect(() => {
@@ -63,26 +63,26 @@ export function SelectInput({
63
63
  items.map((item) => item.value),
64
64
  )
65
65
  ) {
66
- setRotateIndex(0);
66
+ setRotatedIndex(0);
67
67
  setSelectedIndex(0);
68
68
  }
69
69
 
70
70
  previousItems.current = items;
71
71
  }, [items]);
72
72
 
73
- let inputHandler = useCallback(
73
+ useInput(
74
74
  (input: string, key: Key) => {
75
75
  if (input === 'k' || key.upArrow) {
76
76
  let lastIndex = (hasLimit ? limit : items.length) - 1;
77
- let atFirstIndex = selectedIndex === 0;
77
+ let isAtFirstIndex = selectedIndex === 0;
78
78
  let nextIndex = hasLimit ? selectedIndex : lastIndex;
79
- let nextRotateIndex = atFirstIndex ? rotateIndex + 1 : rotateIndex;
80
- let nextSelectedIndex = atFirstIndex ? nextIndex : selectedIndex - 1;
79
+ let nextRotatedIndex = isAtFirstIndex ? rotatedIndex + 1 : rotatedIndex;
80
+ let nextSelectedIndex = isAtFirstIndex ? nextIndex : selectedIndex - 1;
81
81
 
82
- setRotateIndex(nextRotateIndex);
82
+ setRotatedIndex(nextRotatedIndex);
83
83
  setSelectedIndex(nextSelectedIndex);
84
84
 
85
- let slicedItems = hasLimit ? rotate(items, nextRotateIndex).slice(0, limit) : items;
85
+ let slicedItems = hasLimit ? rotate(items, nextRotatedIndex).slice(0, limit) : items;
86
86
 
87
87
  if (typeof onHighlight === 'function') {
88
88
  onHighlight(slicedItems[nextSelectedIndex] as SelectInputItem);
@@ -92,13 +92,13 @@ export function SelectInput({
92
92
  if (input === 'j' || key.downArrow) {
93
93
  let isAtLastIndex = selectedIndex === (hasLimit ? limit : items.length) - 1;
94
94
  let nextIndex = hasLimit ? selectedIndex : 0;
95
- let nextRotateIndex = isAtLastIndex ? rotateIndex - 1 : rotateIndex;
95
+ let nextRotatedIndex = isAtLastIndex ? rotatedIndex - 1 : rotatedIndex;
96
96
  let nextSelectedIndex = isAtLastIndex ? nextIndex : selectedIndex + 1;
97
97
 
98
- setRotateIndex(nextRotateIndex);
98
+ setRotatedIndex(nextRotatedIndex);
99
99
  setSelectedIndex(nextSelectedIndex);
100
100
 
101
- let slicedItems = hasLimit ? rotate(items, nextRotateIndex).slice(0, limit) : items;
101
+ let slicedItems = hasLimit ? rotate(items, nextRotatedIndex).slice(0, limit) : items;
102
102
 
103
103
  if (typeof onHighlight === 'function') {
104
104
  onHighlight(slicedItems[nextSelectedIndex] as SelectInputItem);
@@ -106,20 +106,18 @@ export function SelectInput({
106
106
  }
107
107
 
108
108
  if (key.return) {
109
- let slicedItems = hasLimit ? rotate(items, rotateIndex).slice(0, limit) : items;
109
+ let slicedItems = hasLimit ? rotate(items, rotatedIndex).slice(0, limit) : items;
110
110
 
111
111
  if (typeof onSelect === 'function') {
112
112
  onSelect(slicedItems[selectedIndex] as SelectInputItem);
113
113
  }
114
114
  }
115
115
  },
116
- [hasLimit, limit, rotateIndex, selectedIndex, items, onSelect, onHighlight],
116
+ {isActive: focus},
117
117
  );
118
118
 
119
- useInput(inputHandler, {isActive: focus});
120
-
121
- let visibleItem: SelectInputItem[] = hasLimit
122
- ? rotate(items, rotateIndex).slice(0, limit)
119
+ let visibleItems: SelectInputItem[] = hasLimit
120
+ ? rotate(items, rotatedIndex).slice(0, limit)
123
121
  : items;
124
122
 
125
123
  return (
@@ -138,7 +136,7 @@ export function SelectInput({
138
136
  </Text>
139
137
  ) : (
140
138
  <Box flexDirection="column">
141
- {visibleItem.map((item, index) => {
139
+ {visibleItems.map((item, index) => {
142
140
  let isSelected = index === selectedIndex;
143
141
 
144
142
  return (
@@ -20,7 +20,7 @@ export type TextInputProps = BoxProps & {
20
20
  /** Value. */
21
21
  value: string;
22
22
 
23
- /** Should component listen to Listen to user's input? Useful in case there are multiple input components at the same time and input must be "routed" to a specific component. */
23
+ /** Should component listen to Listen to user's input? Useful in case there are multiple input components at the same time and input must be handled only by a specific component. */
24
24
  focus?: boolean | undefined;
25
25
 
26
26
  /** Replace all chars and mask the value. Useful for password inputs. */
@@ -87,7 +87,6 @@ export function TextInput({
87
87
  let renderedValue = value;
88
88
  let renderedPlaceholder = placeholder ? chalk[colors.muted](placeholder) : undefined;
89
89
 
90
- // fake mouse cursor; dealing with with actual cursor and ANSI escape codes is inconvinient
91
90
  if (showCursor && focus) {
92
91
  renderedPlaceholder =
93
92
  placeholder.length > 0
@@ -96,16 +95,12 @@ export function TextInput({
96
95
 
97
96
  renderedValue = value.length > 0 ? '' : chalk.inverse(' ');
98
97
 
99
- let i = 0;
100
-
101
- for (let char of value) {
102
- if (i >= state.cursorOffset && i <= state.cursorOffset) {
103
- renderedValue += chalk.inverse(char);
98
+ for (let [i, character] of Object.entries(value)) {
99
+ if (Number(i) >= state.cursorOffset && Number(i) <= state.cursorOffset) {
100
+ renderedValue += chalk.inverse(character);
104
101
  } else {
105
- renderedValue += char;
102
+ renderedValue += character;
106
103
  }
107
-
108
- i += 1;
109
104
  }
110
105
 
111
106
  if (value.length > 0 && state.cursorOffset === value.length) {