UNPKG

@teaui/core

Version:

A high-level terminal UI library for Node

182 lines 6.12 kB
import { Container } from '../Container.js'; import { Rect, Point, Size } from '../geometry.js'; function fromShorthand(props, location, extraProps = {}) { if (Array.isArray(props)) { return { children: props, location, ...extraProps }; } else { return { ...props, location, ...extraProps }; } } /** * Positions its children at a fixed location within the available space. * Children are rendered as a ZStack (overlaid on top of each other), then * placed according to `location`. * * Designed for use inside a ZStack to anchor content at edges or corners. * * When `useAvailable` is true, the component uses the viewport's * `availableRect` instead of `contentRect` for sizing and placement. * * ```ts * new ZStack({ * children: [ * new Space({background: '#333'}), * At.topRight([new Text({text: 'top-right'})]), * At.bottomCenter([new Text({text: 'bottom'})]), * ], * }) * ``` */ export class At extends Container { #location; #useAvailable; static topLeft(props = {}, extraProps = {}) { return new At(fromShorthand(props, 'top-left', extraProps)); } static topCenter(props = {}, extraProps = {}) { return new At(fromShorthand(props, 'top-center', extraProps)); } static topRight(props = {}, extraProps = {}) { return new At(fromShorthand(props, 'top-right', extraProps)); } static left(props = {}, extraProps = {}) { return new At(fromShorthand(props, 'left', extraProps)); } static center(props = {}, extraProps = {}) { return new At(fromShorthand(props, 'center', extraProps)); } static right(props = {}, extraProps = {}) { return new At(fromShorthand(props, 'right', extraProps)); } static bottomLeft(props = {}, extraProps = {}) { return new At(fromShorthand(props, 'bottom-left', extraProps)); } static bottomCenter(props = {}, extraProps = {}) { return new At(fromShorthand(props, 'bottom-center', extraProps)); } static bottomRight(props = {}, extraProps = {}) { return new At(fromShorthand(props, 'bottom-right', extraProps)); } constructor({ children, child, location, useAvailable, ...viewProps }) { super(viewProps); this.#location = location ?? 'top-left'; this.#useAvailable = useAvailable ?? false; if (child) { this.add(child); } if (children) { for (const c of children) { this.add(c); } } } get location() { return this.#location; } set location(value) { if (value === this.#location) return; this.#location = value; this.invalidateSize(); } get useAvailable() { return this.#useAvailable; } set useAvailable(value) { if (value === this.#useAvailable) return; this.#useAvailable = value; this.invalidateSize(); } update({ children, child, location, useAvailable, ...props }) { if (location !== undefined) { this.#location = location; } if (useAvailable !== undefined) { this.#useAvailable = useAvailable; } if (child !== undefined || children !== undefined) { this.removeAllChildren(); if (children) { for (const c of children) { this.add(c); } } if (child) { this.add(child); } } super.update(props); } /** * The At component fills all available space — it's meant to be used * inside a ZStack layer. */ naturalSize(available) { return available; } render(viewport) { if (viewport.isEmpty) { return super.render(viewport); } const layoutRect = this.#useAvailable ? viewport.availableRect : viewport.contentRect; // Compute combined children size (ZStack-style: max of all children) const childrenSize = Size.zero.mutableCopy(); for (const child of this.children) { if (!child.isVisible) continue; const childSize = child.naturalSize(layoutRect.size).max(layoutRect.size); childrenSize.width = Math.max(childrenSize.width, childSize.width); childrenSize.height = Math.max(childrenSize.height, childSize.height); } const origin = computeOrigin(this.#location, layoutRect.size, childrenSize); // Offset by layoutRect origin (relevant when using availableRect, which // may have negative origin) const drawOrigin = new Point(origin.x + layoutRect.origin.x, origin.y + layoutRect.origin.y); const drawRect = new Rect(drawOrigin, childrenSize); // Render all children overlaid (ZStack-style) at the computed position viewport.clipped(drawRect, inside => { for (const child of this.children) { if (!child.isVisible) continue; child.render(inside); } }); } } function computeOrigin(location, available, childSize) { let x, y; // horizontal if (location === 'top-left' || location === 'left' || location === 'bottom-left') { x = 0; } else if (location === 'top-center' || location === 'center' || location === 'bottom-center') { x = Math.round((available.width - childSize.width) / 2); } else { x = available.width - childSize.width; } // vertical if (location === 'top-left' || location === 'top-center' || location === 'top-right') { y = 0; } else if (location === 'left' || location === 'center' || location === 'right') { y = Math.round((available.height - childSize.height) / 2); } else { y = available.height - childSize.height; } return new Point(Math.max(0, x), Math.max(0, y)); } //# sourceMappingURL=At.js.map