UNPKG

@teaui/core

Version:

A high-level terminal UI library for Node

165 lines 6.12 kB
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