Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

hanji

drizzle-team220.5kISC0.0.5TypeScript support: included

Designless command line user interface builder

readme

Hanji is a designless command line user interface builder for nodejs+typescript. by @_alexblokh

You can implement prompts by extending Prompt class
Below is an example of how to implement Select with utility SelectData bundle provided from the library
I will provide more view agnostic datasets to make implementing custom views like input a breath

import color from "kleur";
import { ITerminal, Prompt, render, SelectData } from "hanji";

export class Select extends Prompt<{ index: number; value: string }> {
  private readonly data: SelectState<{ label: string; value: string }>;
  private readonly spinner: () => string;
  private timeout: NodeJS.Timer | undefined;

  constructor(items: string[]) {
    super();
    this.on("attach", (terminal) => terminal.toggleCursor("hide"));
    this.on("detach", () => clearInterval(this.timeout));

    this.data = new SelectState(
      items.map((it) => ({ label: it, value: `${it}-value` }))
    );
    this.data.bind(this);
  }

  render(status: "idle" | "submitted" | "aborted"): string {
    if (status === "submitted" || status === "aborted") {
      return "";
    }

    let text = "";
    this.data.items.forEach((it, idx) => {
      text +=
        idx === this.data.selectedIdx
          ? `${color.green("❯ " + it.label)}`
          : `  ${it.label}`;
      text += idx != this.data.items.length - 1 ? "\n" : "";
    });

    return text;
  }

  result() {
    return {
      index: this.data.selectedIdx,
      value: this.data.items[this.data.selectedIdx]!.value!,
    };
  }
}

 const { status, data } = await render(
  new Select(["user1", "user2", "user3", "user4"])
);
if (status === "aborted") return;
console.log(data);
// { index: 0, value: 'users1' }

changelog

v0.0.2

Added global onTermite callback to gracefully handle CLI termination

import { onTerminate } from "hanji";

onTerminate((stdin: ReadStream, stdout: WriteStream) => {
  stdout.write(`\ngracefully abort\n^C\n`);
  process.exit(0);
});

32ms throttle for render events

Removed onAttach & onDetach callback functions to be overriden, now we have more node-like api with this.on("attach", (term) => ...). That also provides StateBundles like SelectState ability to directly subscribe to lifecycle events.

Made SelectState generic, not string specific. That provides an ability to write any SelectState you need like:

private readonly data: SelectState<{ label: string; value: string }>;
// or
private readonly data: SelectState<{ label: string; value: {...} }>;

Provided type for keypress events and now render() returns a bundled object

type Prompted<T> =
  | {
      data: undefined;
      status: "aborted";
    }
  | {
      data: T;
      status: "submitted";
    };

so you now can explicitly handle rejections:

const result = await render(new Select(["user1", "user2", "user3", "user4"]));
if (result.status === "aborted") return;
console.log(result.data)

// or you can destructure
const { status, data} = await render(new Select(["user1", "user2", "user3", "user4"]));
if (status === "aborted") return;
console.log(data)

Removed View it's redundant