@teaui/core
Version:
A high-level terminal UI library for Node
165 lines • 6.12 kB
JavaScript
import * as unicode from '@teaui/term';
import { Container } from '../Container.js';
import { Text } from './Text.js';
import { Rect, Point } from '../geometry.js';
import { isMouseClicked, styleTextForHotKey, toHotKeyDef, hotKeyToString, } from '../events/index.js';
import { childPalette } from '../UI.js';
export class Button extends Container {
#align = 'center';
#border = 'default';
#foreground;
#hotKey;
#onClick;
#textView;
#hasFocus = false;
constructor(props) {
super(props);
this.#textView = new Text({ alignment: 'center' });
this.add(this.#textView);
this.#update(props);
}
update(props) {
this.#update(props);
super.update(props);
}
childPalette(view) {
return childPalette(super.childPalette(view), this.isPressed, this.isHover || this.#hasFocus);
}
#hasCustomChildren() {
return this.children.length !== 1 || this.children.at(0) !== this.#textView;
}
#update({ title, align, border, foreground, hotKey, onClick }) {
const styledText = hotKey ? styleTextForHotKey(title ?? '', hotKey) : title;
this.#textView.text = styledText ?? '';
this.#align = align ?? 'center';
this.#border = border ?? 'default';
this.#foreground = foreground;
this.#hotKey = hotKey;
this.#onClick = onClick;
}
naturalSize(available) {
const [left, right] = this.#borderSize(false);
return super.naturalSize(available).grow(left + right, 0);
}
get title() {
return this.#textView.text;
}
set title(value) {
const styledText = this.#hotKey
? styleTextForHotKey(value ?? '', this.#hotKey)
: (value ?? '');
this.#textView.text = styledText;
this.invalidateSize();
}
legendItems() {
if (!this.#hotKey) {
return [];
}
return [{ key: hotKeyToString(this.#hotKey), label: this.title ?? '' }];
}
#borderSize(hasFocus) {
const borders = hasFocus ? BORDERS_FOCUS : BORDERS;
const [left, right] = borders[this.#border];
return [unicode.lineWidth(left), unicode.lineWidth(right)];
}
receiveMouse(event, system) {
super.receiveMouse(event, system);
if (isMouseClicked(event)) {
this.#onClick?.();
}
}
receiveKey(event) {
switch (event.name) {
case 'return':
this.#onClick?.();
break;
}
}
render(viewport) {
const hasFocus = viewport.registerFocus({ isDefault: false });
this.#hasFocus = hasFocus;
if (viewport.isEmpty) {
return super.render(viewport);
}
viewport.registerMouse(['mouse.button.left', 'mouse.move']);
if (this.#hotKey) {
viewport.registerHotKey(toHotKeyDef(this.#hotKey));
}
let textStyle = this.purpose.ui({
isPressed: this.isPressed,
isHover: this.isHover || hasFocus,
});
let topsStyle = this.purpose.ui({
isPressed: this.isPressed,
isHover: this.isHover || hasFocus,
isOrnament: true,
});
if (this.#foreground) {
textStyle = textStyle.merge({ foreground: this.#foreground });
topsStyle = topsStyle.merge({ foreground: this.#foreground });
}
if (this.background && !(this.isHover || hasFocus)) {
textStyle = textStyle.merge({ background: this.background });
topsStyle = topsStyle.merge({ background: this.background });
}
const useEmoji = this.purpose.emoji;
viewport.visibleRect.forEachPoint(pt => {
if (useEmoji && pt.y === 0 && viewport.contentSize.height > 2) {
viewport.write(BUTTON_TOP, pt, topsStyle);
}
else if (useEmoji &&
pt.y === viewport.contentSize.height - 1 &&
viewport.contentSize.height > 2) {
viewport.write(BUTTON_BOTTOM, pt, topsStyle);
}
else {
viewport.write(' ', pt, textStyle);
}
});
const borders = hasFocus ? BORDERS_FOCUS : BORDERS;
let [left, right] = borders[this.#border];
let [leftWidth, rightWidth] = this.#borderSize(hasFocus);
if (this.#hasCustomChildren() && this.#hotKey) {
const hotKey = styleTextForHotKey('', this.#hotKey) + ' ';
left += hotKey;
leftWidth += unicode.lineWidth(hotKey);
}
const naturalSize = super.naturalSize(viewport.contentSize.shrink(leftWidth + rightWidth, 0));
const offsetLeft = this.#align === 'center'
? Math.round((viewport.contentSize.width - naturalSize.width) / 2)
: this.#align === 'left'
? 1
: viewport.contentSize.width - naturalSize.width - 1, offset = new Point(offsetLeft, Math.round((viewport.contentSize.height - naturalSize.height) / 2));
let leftX = offset.x - leftWidth, rightX = offset.x + naturalSize.width;
for (let y = 0; y < naturalSize.height; y++) {
viewport.write(left, new Point(leftX, offset.y + y), textStyle);
viewport.write(right, new Point(rightX, offset.y + y), textStyle);
}
viewport.clipped(new Rect(offset, naturalSize), textStyle, inside => {
super.render(inside);
});
}
add(child, at) {
super.add(child, at);
if (this.#hasCustomChildren()) {
this.#textView.removeFromParent();
}
}
}
const BUTTON_TOP = '▔';
const BUTTON_BOTTOM = '▁';
const BORDERS = {
default: ['[ ', ' ]'],
// arrows: [' ', ' '],
arrows: ['\uE0B3', '\uE0B1'],
none: [' ', ' '],
};
const BORDERS_FOCUS = {
default: ['⟦ ', ' ⟧'],
// arrows: [' ', ' '],
arrows: ['\uE0B2', '\uE0B0'],
none: [' ', ' '],
};
// E0A0 \uE0A0\uE0A1\uE0A2
// E0B0 \uE0B0\uE0B1\uE0B2\uE0B3
//# sourceMappingURL=Button.js.map