@teaui/core
Version:
A high-level terminal UI library for Node
177 lines • 6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Text = void 0;
const sys_1 = require("../sys");
const View_1 = require("../View");
const Style_1 = require("../Style");
const geometry_1 = require("../geometry");
const fonts_1 = require("./fonts");
const util_1 = require("../util");
const DEFAULTS = {
alignment: 'left',
wrap: false,
font: 'default',
};
class Text extends View_1.View {
#style;
#text = '';
#lines = [];
#alignment = DEFAULTS.alignment;
#wrap = DEFAULTS.wrap;
#wrappedLines;
#font = DEFAULTS.font;
constructor(props = {}) {
super(props);
this.#update(props);
(0, util_1.define)(this, 'text', { enumerable: true });
(0, util_1.define)(this, 'font', { enumerable: true });
}
get text() {
return this.#text;
}
set text(value) {
if (this.#text === value) {
return;
}
this.#updateLines(value, value.split('\n'), this.#font);
}
get font() {
return this.#font;
}
set font(value) {
if (this.#font === value) {
return;
}
this.#updateLines(this.#text, undefined, value);
}
get style() {
return this.#style;
}
set style(value) {
if (this.#style === value) {
return;
}
this.#style = value;
this.invalidateRender();
}
update(props) {
this.#update(props);
super.update(props);
}
#update({ text, lines, style, alignment, wrap, font }) {
this.#style = style;
this.#alignment = alignment ?? DEFAULTS.alignment;
this.#wrap = wrap ?? DEFAULTS.wrap;
this.#updateLines(text, lines, font);
}
#updateLines(text, lines, font) {
this.#font = font ?? DEFAULTS.font;
const fontMap = font && fonts_1.FONTS[font];
if (text !== undefined) {
this.#text = text;
lines = text === '' ? [] : text.split('\n');
}
else if (lines !== undefined) {
this.#text = lines.join('\n');
}
else {
this.#text = '';
lines = [];
}
this.#lines = lines.map(line => {
if (fontMap) {
line = [...line].map(c => fontMap.get(c) ?? c).join('');
}
return [line, sys_1.unicode.lineWidth(line)];
});
this.#wrappedLines = undefined;
this.invalidateSize();
}
naturalSize(available) {
if (this.#lines.length === 0) {
return geometry_1.Size.zero;
}
return this.#lines.reduce((size, [, width]) => {
if (this.#wrap) {
const lineHeight = Math.ceil(width / available.width);
size.width = Math.max(size.width, Math.min(width, available.width));
size.height += lineHeight;
return size;
}
size.width = Math.max(size.width, width);
size.height += 1;
return size;
}, geometry_1.Size.zero.mutableCopy());
}
render(viewport) {
if (viewport.isEmpty) {
return;
}
let lines;
if (this.#wrap) {
lines = this.#wrapLines(this.#lines, viewport.contentSize.width);
// cache for future render
this.#wrappedLines = [viewport.contentSize.width, lines];
}
else {
lines = this.#lines;
}
const startingStyle = this.#style ?? Style_1.Style.NONE;
viewport.usingPen(startingStyle, pen => {
const point = new geometry_1.Point(0, 0).mutableCopy();
for (let [line, lineWidth] of lines) {
if (!line.length) {
point.y += 1;
continue;
}
let didWrap = false;
if (this.#wrap) {
lineWidth = Math.min(lineWidth, viewport.contentSize.width);
}
const offsetX = this.#alignment === 'left'
? 0
: this.#alignment === 'center'
? ~~((viewport.contentSize.width - lineWidth) / 2)
: viewport.contentSize.width - lineWidth;
point.x = offsetX;
for (const char of sys_1.unicode.printableChars(line)) {
const charWidth = sys_1.unicode.charWidth(char);
if (charWidth === 0) {
// track the current style regardless of wether we are printing
pen.mergePen(Style_1.Style.fromSGR(char, startingStyle));
continue;
}
if (this.#wrap && point.x >= viewport.contentSize.width) {
didWrap = true;
point.x = 0;
point.y += 1;
}
if (didWrap && char.match(/\s/)) {
continue;
}
didWrap = false;
if (point.x >= viewport.visibleRect.minX() &&
point.x + charWidth - 1 < viewport.visibleRect.maxX()) {
viewport.write(char, point);
}
point.x += charWidth;
// do not early exit when point.x >= maxX. 'line' may contain ANSI codes that
// need to be picked up by mergePen.
}
point.y += 1;
}
});
}
#wrapLines(lines, contentWidth) {
if (this.#wrap &&
this.#wrappedLines &&
this.#wrappedLines[0] === contentWidth) {
return this.#wrappedLines[1];
}
const wrapped = (0, util_1.wrap)(lines, contentWidth);
this.#wrappedLines = [contentWidth, wrapped];
return wrapped;
}
}
exports.Text = Text;
//# sourceMappingURL=Text.js.map