Build colorful complex layouts in the console!
- Component based layouts based on the box model
- Precise positioning supporting percentages, relative layouts and 9-point anchoring
- Simple styling and built-in theming
- Keyboard navigation and focus management
- Screen and log swapping
- No dependencies!
npm install deluxe-cli
Reference the Screen Usage example below
The following components are built-in
The root container for the component rendering tree. Screens are what get rendered to the console and can be swapped out to create different views.
import DeluxeCLI, {Screen, Theme, ORIGIN, BORDER} from "deluxe-cli";
const screenMain = new Screen({
id: "screenMain",
position: Screen.DEFAULT_POSITION.extend({
labelOriginX: ORIGIN.X.CENTER //Center the label horizontally
}),
style: Screen.DEFAULT_STYLE.extend({
border: BORDER.DOUBLE //Add a double border around the edge
}),
label: " My Application ",
children: [listTheme, windowLogin],
onSelect: () => {
//Called when enter is pressed on the screen
DeluxeCLI.destroy();
console.log("Back to the terminal.");
}
});
DeluxeCLI.initialize();
DeluxeCLI.clear();
DeluxeCLI.render(screenMain);
//Cascade the theme to the screen and its children
Theme.LavaBit.applyToComponent(screenMain);
- onSelect() | Called when the enter key is pressed on the screen
A container for other components that traps focus. Captures the enter/escape keys and bubbles other key presses to the parent.
import {Window} from "deluxe-cli";
const windowLogin = new Window({
id: "windowLogin",
label: " Login ",
children: [txtHeading, inputUser, inputPass, btnSubmit],
onSelect: () => {
//Called when enter is pressed on the window
//TODO: Login logic
windowLogin.remove();
}
});
- userClosable | Optional default: false | boolean | Whether the escape key should close the window
- onClose() | Called when the escape key is pressed on the window
- onSelect() | Called when the enter key is pressed on the window
A textbox for displaying text and handling word wrapping.
import {Text, ORIGIN} from "deluxe-cli";
const txtHeading = new Text({
id: "txtHeading",
value: "Please enter your credentials",
position: Text.DEFAULT_POSITION.extend({
originX: ORIGIN.X.CENTER //Center the text horizontally
})
});
- value | Optional default: "" | string | The text to display
- wordWrap(
text, charsPerLine
) | Called automatically and returns an array of lines of text that fit within the given width
A focusable text field for capturing user input. Captures characters in the allowed character set as well as backspace and delete. Other key presses bubble to the parent.
import {Input} from "deluxe-cli";
const inputUser = new Input({
id: "inputUser",
label: " Username ",
position: Input.DEFAULT_POSITION.extend({
marginTop: 1,
marginRight: 2,
marginBottom: 1,
marginLeft: 2
}),
onChange: ({value}) => {
//Called when the value of the input changes
}
});
const inputPass = inputUser.extend({
id: "inputPass",
label: " Password ",
mask: Input.DEFAULT_MASK,
onChange: ({value}) => {
//Called when the value of the input changes
}
});
- value | Optional default: "" | string | The initial value of the input
- maxLength | Optional default: 0 | number | The maximum length of the input
- mask | Optional default: null | string | The character to use as a mask for the input
- allowedCharacters | Optional default: Input.DEFAULT_ALLOWED_CHARACTERS | string | The characters allowed in the input
- onChange(
value
) | Called when the value of the input changes
A focusable list of items that can be navigated and selected. Captures up/down arrow keys and the enter key. Arrow keys and other key presses bubble to the parent.
import {List} from "deluxe-cli";
const listTheme = new List({
id: "listTheme",
label: " Theme ",
items: ["Space", "XTree", "Ocean", "LavaBit", "Marble"],
position: List.DEFAULT_POSITION.extend({
marginTop: 1,
marginRight: 2,
marginBottom: 1,
marginLeft: 2,
paddingTop: 1,
paddingRight: 2,
paddingBottom: 1,
paddingLeft: 2
}),
autoSelect: true,
onSelect: ({selectedItem}) => {
Theme[selectedItem.trim()].applyToComponent(screen);
}
});
- items | Required default: [] | string[] | The items to display in the list
- selectedIndex | Optional default: 0 | number | The index of the selected item
- activeIndex | Optional default: 0 | number | The index of the active (focused) item
- autoSelect | Optional default: false | boolean | Whether onSelect should fire when the active item changes
- onSelect(
{selectedIndex, selectedItem}
) | Called when the selected item changes or the enter key is pressed - onChange(
{activeIndex, activeIndex}
) | Called when the active item changes - gotoActivePrevious() | Moves the active item previous
- gotoActiveNext() | Moves the active item next
- gotoActiveIndex(
index
) | Moves the active item to the given index - selectIndex(
index
) | Selects the item at the given index
A focusable vertical scrollbar for text and lists. Captures up/down arrow keys. Other key presses bubble to the parent.
import {Text, ScrollBar} from "deluxe-cli";
const txtTandC = new Text({
id: "txtTandC",
value:
"Terms and Conditions...\n" +
new Array(100)
.fill(1)
.map((_, i) => `Line ${i + 1}`)
.join("\n"),
position: Text.DEFAULT_POSITION.extend({
width: "100%"
//No height 100% because height 0 = auto height
})
});
const sbTxtTandC = new ScrollBar({
id: "sbTxtTandC",
position: ScrollBar.DEFAULT_POSITION.extend({
width: "100%",
height: "10",
marginLeft: 2,
marginRight: 2
}),
label: " Terms and Conditions ",
children: [txtTandC]
});
- scrollPosition | Optional default: 0 | number | The current scroll position between 0 and 1
- If the ScrollBar contains a list, you should set focusable to false on the ScrollBar to avoid a double focus scenario.
A focusable button that will trigger an event on the enter key. Other key presses bubble to the parent.
import {Button} from "deluxe-cli";
const btnSubmit = new Button({
id: "btnSubmit",
value: "Submit",
position: Button.DEFAULT_POSITION.extend({
marginRight: 2,
marginBottom: 1
}),
onSelect: () => {
//Called when enter is pressed on the button
}
});
- value | Optional default: "" | string | The text to display on the button
- onSelect() | Called when the enter key is pressed on the button
The main static interface for rendering components to the console.
- DEFAULT_FPS | 10 | The default frames per second
- DEFAULT_AUTO_UPDATE | true | Whether to automatically re-render on reactive prop updates
- debug | Optional default: false | boolean | Whether to log debug information to the console
- paused | Optional default: false | boolean | Whether to pause rendering
- exitOnEscape | Optional default: true | boolean | Whether to exit the process when the escape key is pressed while focused on the screen component
- initialize(
{fps = DEFAULT_FPS, autoUpdate = DEFAULT_AUTO_UPDATE, loggerOptions = null, exitOnEscape = true} = {}
) | Initializes the console for rendering - destroy() | Destroys the console rendering
- clear() | Clears the screen
- getWindowSize() | Returns the size of the console window
- render(
screen, force = false
) | Renders the sccreen to the console - showLog(
messages = null
) | Shows messages from the render log and hides the screen - hideLog() | Hides the render log and shows the screen
- focus(
component
) | Focuses on a focusable component - focusFirst() | Focuses on the first focusable component
- focusNext() | Focuses on the next focusable component
- exit(
dontKillProcess = false
) | Clear and reset the console and then terminate the process
The following core classes are used to build the components.
The base class for all components.
- id | Required default: null | string | The unique identifier for the component
- label | Optional default: "" | string | The text to display above the component
- focusable | Optional default: false | boolean | Whether the component can be focused
- focusTrap | Optional default: false | boolean | Whether the component traps focus
- focusStyle | Optional default: null | Style | The style to apply when the component is focused
- position | Optional default: new Position() | Position | The position of the component
- style | Optional default: new Style() | Style | The style of the component
- clone() | Returns a new instance of the component with the same properties
- extend(
props
) | Returns a new instance of the component with the given properties merged in - compute(
params, {force = false, delta = 0, debug = false} = {}
) | Recalculates the position and style of the component and its children - computeStyle(
{parentComputedStyle} = {}, overrides = {}
) | Recalculates the style of the component - computePosition(
{parentComputedPosition, previousChildPosition}, overrides = {}
) | Recalculates the position of the component - render(
parent
) | Renders the component to the console - drawBackground() | Draws the background to the console
- drawBorder() | Draws the border to the console
- drawLabel() | Draws the label to the console
- drawString(
str
) | Draws a string to the console - drawSelf() | Draws the component to the console
- renderChildren() | Renders the children of the component to the console
- onFocus() | Called when the component is focused
- onBlur() | Called when the component focus is lost
- onKeyPress(
str, key
) | Called when a key is pressed on a focused component or its children - addChild(
child
) | Adds a child to the component - removeChild(
child
) | Removes a child from the component - remove() | Removes the component itself from its parent
- needsRender(
fromParent, debug = false
) | Returns true if the component needs to be rendered - isRendered() | Returns true if the component is currently part of the rendered tree
The class for defining and computing the position of a component. The position is computed with respect to the parent's position/size and the previous child's position/size. If the previous child's origin is the same as this origin then the positioning is relative to the previous child. Supports percentages for the x/y/width/height. Based on the CSS box model. If the labelOriginX is null then the label follows the originX.
- originX | Optional default: ORIGIN.X.LEFT | string | The horizontal alignment of the component
- originY | Optional default: ORIGIN.Y.TOP | string | The vertical alignment of the component
- x | Optional default: 0 | number | The horizontal position of the component relative to the origin
- y | Optional default: 0 | number | The vertical position of the component relative to the origin
- width | Optional default: 0 | number | The width of the component
- height | Optional default: 0 | number | The height of the component
- marginTop | Optional default: 0 | number | The margin above the component
- marginRight | Optional default: 0 | number | The margin to the right of the component
- marginBottom | Optional default: 0 | number | The margin below the component
- marginLeft | Optional default: 0 | number | The margin to the left of the component
- borderSize | Optional default: 0 | number | The size of the border around the component which gets set automatically if a border is defined
- paddingTop | Optional default: 0 | number | The padding above the component
- paddingRight | Optional default: 0 | number | The padding to the right of the component
- paddingBottom | Optional default: 0 | number | The padding below the component
- paddingLeft | Optional default: 0 | number | The padding to the left of the component
- labelOriginX | Optional default: null | string | The horizontal alignment of the label
- clone(
intoPosition = null
) | Returns a new instance of the position with the same properties - extend(
props, intoPosition = null
) | Returns a new instance of the position with the given properties merged in - compute(
parentPosition, {previousChildPosition = null, intoPosition = null} = {}, overrides = {}
) | Returns a new Position filled with the computed values - calcValue(
input, parentSize
) | Returns the calculated value while parsing percentages - calcDimension(
input, parentSize, margin
) | Returns the calculated x/y dimension while parsing percentages - getScrollContentRange() | Returns the range of the scrollable content
- shouldCopyProp(
prop
) | Returns true if the given property should be copied when cloning. By default ignores methods and properties starting with "_"
The class for defining and computing the style of a component. The style is computed and inherits from the parent's style.
- backgroundColor | Optional default: null | string | The background color of the component
- color | Optional default: null | string | The text color of the component
- border | Optional default: null | string | The border style of the component
- borderBackgroundColor | Optional default: null | string | The background color of the border
- borderColor | Optional default: null | string | The color of the border
- labelBackgroundColor | Optional default: null | string | The background color of the label
- labelColor | Optional default: null | string | The color of the label
- underline | Optional default: false | boolean | Whether the content should be underlined
- clone(
intoStyle = null
) | Returns a new instance of the style with the same properties - extend(
fromStyle, soft = false, intoStyle = null
) | Returns a new instance of the style with the given properties merged in - computecompute(
parentStyle, {intoStyle} = {}, overrides = {}
) | Returns a new Style filled with the computed values - shouldCopyProp(
prop
) | Returns true if the given property should be copied when cloning. By default ignores methods and properties starting with "_"
The class for defining and applying themes to components. Themes consist of a map of component types/ids to styles and focus styles.
- map | Required default: {} | object | A map of component types/ids to styles
- focusMap | Optional default: {} | object | A map of component types/ids to focus styles
- applyToComponent(
component
) | Applies the theme to the given component and its children
The following built-in themes are available
import {Theme} from "deluxe-cli";
Theme.Space.applyToComponent(screen);
import {Theme} from "deluxe-cli";
Theme.XTree.applyToComponent(screen);
import {Theme} from "deluxe-cli";
Theme.Ocean.applyToComponent(screen);
import {Theme} from "deluxe-cli";
Theme.LavaBit.applyToComponent(screen);
import {Theme} from "deluxe-cli";
Theme.Marble.applyToComponent(screen);
The following constants are available
The root component id
import {ROOT} from "deluxe-cli";
console.log(ROOT); //"root"
The position type
import {POSITION} from "deluxe-cli";
- POSITION.RELATIVE | "relative"
- POSITION.ABSOLUTE | "absolute"
Origin positioning
import {ORIGIN} from "deluxe-cli";
- ORIGIN.X.LEFT | "left"
- ORIGIN.X.CENTER | "center"
- ORIGIN.X.RIGHT | "right"
- ORIGIN.Y.TOP | "top"
- ORIGIN.Y.CENTER | "center"
- ORIGIN.Y.BOTTOM | "bottom"
Border styles
import {BORDER} from "deluxe-cli";
- BORDER.NONE | null
- BORDER.SINGLE | Object with characters | {topLeft, topRight, bottomLeft, bottomRight, horizontal, vertical}
- BORDER.DOUBLE | Object with characters | {topLeft, topRight, bottomLeft, bottomRight, horizontal, vertical}
Node cursor styles
import {CURSOR} from "deluxe-cli";
process.stdout.write(CURSOR.RESET);
- CURSOR.RESET | Reset the cursor style
- CURSOR.BOLD | Set the cursor style to bold
- CURSOR.DIM | Set the cursor style to dim
- CURSOR.ITALIC | Set the cursor style to italic
- CURSOR.UNDERLINE | Set the cursor style to underline
- CURSOR.OVERLINE | Set the cursor style to overline
- CURSOR.BLINK | Set the cursor style to blink
- CURSOR.RAPID_BLINK | Set the cursor style to rapid blink
- CURSOR.INVERSE | Set the cursor style to inverse
- CURSOR.HIDDEN | Set the cursor style to hidden
- CURSOR.STRIKETHROUGH | Set the cursor style to strikethrough
Node color styles
import {COLORS} from "deluxe-cli";
Foreground colors
- COLORS.FG.BLACK
- COLORS.FG.RED
- COLORS.FG.GREEN
- COLORS.FG.YELLOW
- COLORS.FG.BLUE
- COLORS.FG.MAGENTA
- COLORS.FG.CYAN
- COLORS.FG.WHITE
- COLORS.FG.BLACK_BRIGHT
- COLORS.FG.RED_BRIGHT
- COLORS.FG.GREEN_BRIGHT
- COLORS.FG.YELLOW_BRIGHT
- COLORS.FG.BLUE_BRIGHT
- COLORS.FG.MAGENTA_BRIGHT
- COLORS.FG.CYAN_BRIGHT
- COLORS.FG.WHITE_BRIGHT
Background colors
- COLORS.BG.BLACK
- COLORS.BG.RED
- COLORS.BG.GREEN
- COLORS.BG.YELLOW
- COLORS.BG.BLUE
- COLORS.BG.MAGENTA
- COLORS.BG.CYAN
- COLORS.BG.WHITE
- COLORS.BG.BLACK_BRIGHT
- COLORS.BG.RED_BRIGHT
- COLORS.BG.GREEN_BRIGHT
- COLORS.BG.YELLOW_BRIGHT
- COLORS.BG.BLUE_BRIGHT
- COLORS.BG.MAGENTA_BRIGHT
- COLORS.BG.CYAN_BRIGHT
- COLORS.BG.WHITE_BRIGHT
The following utility classes are available
This class is used to log messages to memory/console/file with different levels of severity. A singleton is created by DeluxeCLI.initialize() and you can log directly to this instance via static methods. All instance methods are available on the static instance. Use Logger.getInstance() to get the instance if needed for advanced usage.
-
LEVEL | The levels of the logger
- LEVEL.DEBUG = 0
- LEVEL.INFO = 1
- LEVEL.LOG = 2
- LEVEL.WARN = 3
- LEVEL.ERROR = 4
-
OUTPUT | The outputs of the logger
- OUTPUT.MEMORY = 1
- OUTPUT.CONSOLE = 2
- OUTPUT.FILE = 4
-
TOKEN | The tokens for formats
- TOKEN.LEVEL = "level"
- TOKEN.TIMESTAMP = "timestamp"
- TOKEN.MESSAGE = "message"
- TOKEN.STACK = "stack"
-
FORMAT | The formats for the logger
- FORMAT.TIMESTAMP = "YYYY-MM-DD HH:mm:ss.SSS"
- FORMAT.FILENAME = "YYYY-MM-DD.log"
- FORMAT.MEMORY = "level: message"
- FORMAT.CONSOLE = "message"
- FORMAT.FILE = "[timestamp] [level] message\n"
- FORMAT.ERROR_MESSAGE = "message\n stack\n"
- createInstance(
options
) | Create a new instance of the logger - destroyInstance() | Destroy the instance of the logger
- getInstance() | Get the instance of the logger
Note: After creating an instance, the instance methods are available statically on the Logger class.
- output | Required default: Logger.OUTPUT.CONSOLE | number | Bitmask of the log outputs such as Logger.OUTPUT.CONSOLE | Logger.OUTPUT.FILE
- level | Required default: Logger.LEVEL.INFO | number | The level of the logger
- levelMemory | Optional default: null | number | The level of the memory logger
- levelConsole | Optional default: null | number | The level of the console logger
- levelFile | Optional default: null | number | The level of the file logger
- filePath | Optional default: null | string | The file directory to write logs to
- formatFileName | Optional default: "YYYY-MM-DD.log" | string | The format of the generated file name
- formatTimestamp | Optional default: "YYYY-MM-DD HH:mm:ss.SSS" | string | The format of the timestamp
- formatMemory | Optional default: "level: message" | string | The format when logging to memory
- formatConsole | Optional default: "message" | string | The format when logging to the console
- formatFile | Optional default: "[timestamp] [level] message\n" | string | The format when logging to a file
- formatErrorMessage | Optional default: "message\n stack\n" | string | The format when logging an error
- memorySize | Optional default: 100 | number | The number of messages to keep in memory
- debug(
...args
) | Log a debug message - info(
...args
) | Log an info message - log(
...args
) | Log a message - warn(
...args
) | Log a warning message - error(
...args
) | Log an error message - doLog(
level, ...args
) | Log a message with the given level - toMemory(
level, ...args
) | Log a message to memory - toConsole(
level, ...args
) | Log a message to the console - toFile(
level, ...args
) | Log a message to a file - clearMemory() | Clear the memory log
- _formatMessage(
format, level, ...args
) | Format a message - _formatError(
format = Logger.FORMAT.ERROR_MESSAGE, err
) | Format an error message - _formatFileName(
format = Logger.FORMAT.FILENAME
) | Format a file name - _formatTimestamp(
format = Logger.FORMAT.TIMESTAMP
) | Format a timestamp
A class used to measure time differences in milliseconds.
- startTime | The start time in milliseconds
- lastTime | The last time in milliseconds
- elapsed() | Returns the time elapsed since the start time
- tick() | Returns the time elapsed since the last time and sets the last time to the current time
- Add ScrollBar List example
- Add colored Text value example
- Add Checkbox component
- Add Radio component
- Add icons to the project?
- https://symbl.cc/en/
- Example: Unicode Star =
\u2606
-
Windows Terminal resize event does not fire when using snapping controls
- Solution: Manually resize the terminal window
-
Windows Terminal rendering shows vertical gaps between lines when using the font "Cascadia Mono"
- Solution: Change the font or enable the new text renderer "AtlasEngine" in Windows Terminal
- Additional information: