UNPKG

@teaui/core

Version:

A high-level terminal UI library for Node

133 lines 4.47 kB
import { Point, Rect, Size } from '../geometry.js'; import { Notification } from './Notification.js'; import { Box } from './Box.js'; import { Modal } from './Modal.js'; import { Container as BaseContainer } from '../Container.js'; /** * Centers its child, constraining the available width to max(MIN_WIDTH, available / 3). */ class AlertLayout extends BaseContainer { naturalSize(available) { return available; } render(viewport) { const child = this.children[0]; if (!child || viewport.isEmpty) { return; } const screenWidth = viewport.contentSize.width; const preferredWidth = Math.max(MIN_ALERT_WIDTH, Math.floor(screenWidth / 3)); // First pass: compute natural size at preferred width const naturalSize = child.naturalSize(new Size(preferredWidth, viewport.contentSize.height)); // Use the wider of preferred and natural, capped at screen width const finalWidth = Math.min(Math.max(preferredWidth, naturalSize.width), screenWidth); const childSize = child.naturalSize(new Size(finalWidth, viewport.contentSize.height)); const x = Math.max(0, Math.floor((viewport.contentSize.width - childSize.width) / 2)); const y = Math.max(0, Math.floor((viewport.contentSize.height - childSize.height) / 2)); viewport.clipped(new Rect(new Point(x, y), childSize), inside => { child.render(inside); }); } } /** * A notification meant to be presented in a modal overlay, drawn in a * rounded-corner Box. * * Call `alert.presentFrom(owner)` to add the alert to a container and present * it as a modal. The alert renders as zero-size in the layout; when visible, * it presents a Modal overlay during `render()`. * * If the owner is removed from the tree, the alert is automatically removed * too — no modal will be presented. * * Usage (core): * const alert = new Alert({ * title: 'Confirm Delete', * purpose: 'cancel', * dismissOnEsc: true, * onDismiss() { console.info('dismissed') }, * children: [ * new Text({text: 'Are you sure?'}), * new Button({title: 'Cancel', onClick() { alert.dismiss() }}), * ], * }) * * new Button({ * title: 'Delete', * onClick() { alert.presentFrom(layout) }, * }) */ export class Alert extends Notification { #box; #centerLayout; #modal; #visible; #owner; #onDismiss; constructor(props = {}) { super(props); this.#visible = props.visible ?? false; this.#onDismiss = props.onDismiss; this.#box = new Box({ border: 'rounded', padding: 1 }); this.#box.add(this.contentStack); this.#centerLayout = new AlertLayout({ children: [this.#box] }); this.#modal = new Modal({ dim: props.dim ?? true, dismissOnEsc: props.dismissOnEsc ?? true, dismissOnClick: props.dismissOnClick ?? true, onDismiss: () => { this.dismiss(); }, children: [this.#centerLayout], }); } update(props) { this.#onDismiss = props.onDismiss; this.#visible = props.visible ?? false; this.#modal.dim = props.dim ?? true; this.#modal.dismissOnEsc = props.dismissOnEsc ?? true; this.#modal.dismissOnClick = props.dismissOnClick ?? true; super.update(props); } get visible() { return this.#visible; } set visible(value) { if (value === this.#visible) { return; } this.#visible = value; this.invalidateSize(); } /** * Present this alert as a modal, adding it to the given owner container. * The alert is removed from the owner when dismissed. */ presentFrom(owner) { this.#owner = owner; owner.add(this); this.visible = true; } /** * Dismiss this alert, removing it from its owner container. */ dismiss() { this.#visible = false; if (this.#owner) { this.#owner.removeChild(this); this.#owner.invalidateRender(); this.#owner = undefined; } this.#onDismiss?.(); } naturalSize(_available) { return Size.zero; } render(viewport) { if (this.#visible) { viewport.requestModal(this.#modal); } } } const MIN_ALERT_WIDTH = 40; //# sourceMappingURL=Alert.js.map