import type { KeyCode } from "../keycode/key_code.ts";import { GenericPrompt } from "./_generic_prompt.ts";import { GenericSuggestions, GenericSuggestionsKeys, GenericSuggestionsOptions, GenericSuggestionsSettings,} from "./_generic_suggestions.ts";import { parseNumber } from "./_utils.ts";
type UnsupportedOptions = "files";
export interface NumberOptions extends Omit<GenericSuggestionsOptions<number, string>, UnsupportedOptions> { keys?: NumberKeys; min?: number; max?: number; float?: boolean; round?: number;}
interface NumberSettings extends GenericSuggestionsSettings<number, string> { min: number; max: number; float: boolean; round: number; keys?: NumberKeys;}
export interface NumberKeys extends GenericSuggestionsKeys { increaseValue?: string[]; decreaseValue?: string[];}
export class Number extends GenericSuggestions<number, string> { protected readonly settings: NumberSettings;
public static prompt(options: string | NumberOptions): Promise<number> { return new this(options).prompt(); }
public static inject(value: string): void { GenericPrompt.inject(value); }
constructor(options: string | NumberOptions) { super(); if (typeof options === "string") { options = { message: options }; } this.settings = this.getDefaultSettings(options); }
public getDefaultSettings(options: NumberOptions): NumberSettings { const settings = super.getDefaultSettings(options); return { ...settings, min: options.min ?? -Infinity, max: options.max ?? Infinity, float: options.float ?? false, round: options.round ?? 2, files: false, keys: { increaseValue: ["up", "u", "+"], decreaseValue: ["down", "d", "-"], ...(settings.keys ?? {}), }, }; }
protected success(value: number): string | undefined { this.saveSuggestions(value); return super.success(value); }
protected async handleEvent(event: KeyCode): Promise<void> { switch (true) { case this.settings.suggestions && this.isKey(this.settings.keys, "next", event): if (this.settings.list) { this.selectPreviousSuggestion(); } else { this.selectNextSuggestion(); } break; case this.settings.suggestions && this.isKey(this.settings.keys, "previous", event): if (this.settings.list) { this.selectNextSuggestion(); } else { this.selectPreviousSuggestion(); } break; case this.isKey(this.settings.keys, "increaseValue", event): this.increaseValue(); break; case this.isKey(this.settings.keys, "decreaseValue", event): this.decreaseValue(); break; default: await super.handleEvent(event); } }
public increaseValue() { this.manipulateIndex(false); }
public decreaseValue() { this.manipulateIndex(true); }
protected manipulateIndex(decrease?: boolean) { if (this.inputValue[this.inputIndex] === "-") { this.inputIndex++; }
if ( this.inputValue.length && (this.inputIndex > this.inputValue.length - 1) ) { this.inputIndex--; }
const decimalIndex: number = this.inputValue.indexOf("."); const [abs, dec] = this.inputValue.split(".");
if (dec && this.inputIndex === decimalIndex) { this.inputIndex--; }
const inDecimal: boolean = decimalIndex !== -1 && this.inputIndex > decimalIndex; let value: string = (inDecimal ? dec : abs) || "0"; const oldLength: number = this.inputValue.length; const index: number = inDecimal ? this.inputIndex - decimalIndex - 1 : this.inputIndex; const increaseValue = Math.pow(10, value.length - index - 1);
value = (parseInt(value) + (decrease ? -increaseValue : increaseValue)) .toString();
this.inputValue = !dec ? value : (this.inputIndex > decimalIndex ? abs + "." + value : value + "." + dec);
if (this.inputValue.length > oldLength) { this.inputIndex++; } else if ( this.inputValue.length < oldLength && this.inputValue[this.inputIndex - 1] !== "-" ) { this.inputIndex--; }
this.inputIndex = Math.max( 0, Math.min(this.inputIndex, this.inputValue.length - 1), ); }
protected addChar(char: string): void { if (isNumeric(char)) { super.addChar(char); } else if ( this.settings.float && char === "." && this.inputValue.indexOf(".") === -1 && (this.inputValue[0] === "-" ? this.inputIndex > 1 : this.inputIndex > 0) ) { super.addChar(char); } }
protected validate(value: string): boolean | string { if (!isNumeric(value)) { return false; }
const val: number = parseFloat(value);
if (val > this.settings.max) { return `Value must be lower or equal than ${this.settings.max}`; }
if (val < this.settings.min) { return `Value must be greater or equal than ${this.settings.min}`; }
return true; }
protected transform(value: string): number | undefined { const val: number = parseFloat(value);
if (this.settings.float) { return parseFloat(val.toFixed(this.settings.round)); }
return val; }
protected format(value: number): string { return value.toString(); }
protected getValue(): string { return this.inputValue; }}
function isNumeric(value: string | number): value is number | string { return typeof value === "number" || (!!value && !isNaN(parseNumber(value)));}