UNPKG

@teaui/core

Version:

A high-level terminal UI library for Node

294 lines (292 loc) 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Accordion = void 0; const Container_1 = require("../Container"); const geometry_1 = require("../geometry"); const Text_1 = require("./Text"); const events_1 = require("../events"); const Style_1 = require("../Style"); const util_1 = require("../util"); // accordion = new Accordion() // accordion.addSection('title1', section1) // accordion.addSection(new Text({text: 'title2', style: …}), section2) // // accordion = Accordion.create([ // ['title1', section1], // ['title2', section2], // Accordion.Section('title3', section3), // ]) // // accordion.add(new Section()) // well behaved // accordion.add(new View()) // undefined behaviour class Accordion extends Container_1.Container { static Section; #multiple = false; static create(sections, extraProps = {}) { const accordion = new Accordion(extraProps); for (const section of sections) { if (section instanceof Section) { accordion.addSection(section); } else { const [title, view] = section; accordion.addSection(title, view); } } return accordion; } constructor(props = {}) { super(props); this.#update(props); } update(props) { this.#update(props); super.update(props); } #update({ multiple }) { this.#multiple = multiple ?? false; } get sections() { return this.children.filter(view => view instanceof Section); } #sectionDidChange(toggleSection, isOpen) { if (this.#multiple || !isOpen) { return; } for (const section of this.sections) { if (toggleSection === section) { continue; } section.close(); } } addSection(titleOrSection, view) { let sectionView; if (titleOrSection instanceof Section) { sectionView = titleOrSection; } else { sectionView = Section.create(titleOrSection, view); } this.add(sectionView); } add(child, at) { if (child instanceof Section) { child.onClick = this.#sectionDidChange.bind(this); if (!this.#multiple && child.isOpen) { this.sections.forEach(section => (section.isOpen = false)); } } super.add(child, at); } naturalSize(available) { let remainingSize = available.mutableCopy(); return this.sections.reduce((size, section) => { const sectionSize = section.naturalSize(remainingSize); remainingSize.height = Math.max(0, remainingSize.height - sectionSize.height); return size.growHeight(sectionSize); }, geometry_1.Size.zero.mutableCopy()); } render(viewport) { const remainingSize = viewport.contentSize.mutableCopy(); let y = 0; for (const section of this.sections) { if (y >= viewport.contentSize.height) { break; } const sectionSize = section.naturalSize(remainingSize); remainingSize.height -= sectionSize.height; viewport.clipped(new geometry_1.Rect([0, y], [remainingSize.width, sectionSize.height]), inner => { section.render(inner); }); y += sectionSize.height; } } } exports.Accordion = Accordion; class Section extends Container_1.Container { #isOpen = false; onClick; #currentViewHeight = 0; #actualViewHeight = 0; #titleView; static create(title, child, extraProps = {}) { return new Section({ title, child, ...extraProps }); } constructor({ title, isOpen, ...props }) { super(props); this.#isOpen = isOpen ?? false; this.#titleView = new Text_1.Text({ text: title ?? '', style: this.titleStyle, }); this.#update({ isOpen }); this.add(this.#titleView); (0, util_1.define)(this, 'title', { enumerable: true }); } get title() { return this.#titleView.text; } set title(value) { this.#titleView.text = value; } get isOpen() { return this.#isOpen; } set isOpen(value) { if (this.#isOpen === value) { return; } this.#isOpen = value; this.#titleView.style = this.titleStyle; this.onClick?.(this, value); } get titleStyle() { return new Style_1.Style({ underline: true, bold: this.#isOpen || this.isHover }); } open() { this.isOpen = true; } close() { this.isOpen = false; } update(props) { this.#update(props); super.update(props); } #update({ isOpen, onClick }) { this.#isOpen = isOpen ?? false; this.onClick = onClick; } naturalSize(available) { // 4 => left margin, right space/arrow/space // 1 => bottom separator const collapsedSize = this.#titleView.naturalSize(available).grow(4, 1); const remainingSize = available.shrink(0, collapsedSize.height); // 1 => left margin (no right margin) let width = 0; let height = 0; const children = this.children.filter(child => child !== this.#titleView); for (const child of children) { if (!child.isVisible) { continue; } const naturalSize = child.naturalSize(remainingSize); width = Math.max(width, naturalSize.width); height = Math.max(height, naturalSize.height); } width += 1; const viewSize = new geometry_1.Size(width, height); return this.#currentSize(collapsedSize, viewSize); } #currentSize(collapsedSize, viewSize) { if (this.#actualViewHeight === 0) { this.#currentViewHeight = this.#isOpen ? viewSize.height : 0; } this.#actualViewHeight = viewSize.height; return new geometry_1.Size(Math.max(viewSize.width, collapsedSize.width), collapsedSize.height + Math.round(this.#currentViewHeight)); } receiveMouse(event, system) { super.receiveMouse(event, system); if ((0, events_1.isMouseClicked)(event)) { this.#isOpen = !this.#isOpen; this.onClick?.(this, this.#isOpen); } this.#titleView.style = this.titleStyle; } receiveTick(dt) { if (this.#actualViewHeight === 0) { this.#currentViewHeight = 0; return false; } const amount = dt / 25; let nextHeight; if (this.#isOpen) { nextHeight = Math.min(this.#actualViewHeight, this.#currentViewHeight + amount); } else { nextHeight = Math.max(0, this.#currentViewHeight - amount); } this.#currentViewHeight = nextHeight; this.invalidateSize(); return true; } render(viewport) { if (this.#currentViewHeight !== (this.#isOpen ? this.#actualViewHeight : 0)) { viewport.registerTick(); } viewport.registerMouse(['mouse.button.left', 'mouse.move']); const textStyle = this.theme.text(); const textSize = this.#titleView .naturalSize(viewport.contentSize) .mutableCopy(); textSize.width = Math.max(0, Math.min(viewport.contentSize.width - 4, textSize.width)); viewport.clipped(geometry_1.Rect.zero.atX(1).withSize(viewport.contentSize.width, textSize.height), inner => { this.#titleView.render(inner); }); if (this.#currentViewHeight > 0) { viewport.clipped(geometry_1.Rect.zero .atY(textSize.height) .withSize(viewport.contentSize.width, this.#currentViewHeight), inner => { const children = this.children.filter(child => child !== this.#titleView); for (const child of children) { if (!child.isVisible) { continue; } child.render(viewport); } }); } viewport.clipped(geometry_1.Rect.zero .at(viewport.contentSize.width - 3, 0) .withSize(viewport.contentSize.width, 1), textStyle, inner => { if (this.#currentViewHeight !== (this.#isOpen ? this.#actualViewHeight : 0)) { const arrows = this.isHover ? ARROWS.animateHover : ARROWS.animate; const index = Math.round((0, geometry_1.interpolate)(this.#currentViewHeight, [0, this.#actualViewHeight], [0, arrows.length - 1])); if (arrows[index]) { inner.write(arrows[index], new geometry_1.Point(1, 0)); } } else if (this.#isOpen) { inner.write(this.isHover ? ARROWS.openHover : ARROWS.open, new geometry_1.Point(1, 0)); } else { inner.write(this.isHover ? ARROWS.closedHover : ARROWS.closed, new geometry_1.Point(1, 0)); } }); viewport.clipped(geometry_1.Rect.zero .at(0, viewport.contentSize.height - 1) .withSize(viewport.contentSize.width, 1), textStyle, inner => { inner.write(SEPARATOR.left, new geometry_1.Point(0, 0)); if (inner.contentSize.width > 2) { const middle = SEPARATOR.middle.repeat(inner.contentSize.width - 2); inner.write(middle, new geometry_1.Point(1, 0)); } inner.write(SEPARATOR.right, new geometry_1.Point(inner.contentSize.width - 1, 0)); }); } } Accordion.Section = Section; const ARROWS = { open: '△', openHover: '▲', closed: '▽', closedHover: '▼', animate: ['▽', '◁', '△'], animateHover: ['▼', '◀︎', '▲'], }; const SEPARATOR = { left: '╶', middle: '─', right: '╴' }; /* ▷▶︎ ◀︎◁ ▼▽ ▲△ S͟e͟c͟t͟i͟o͟n͟ ͟1 ▽ ╶─────────────╴ S͟e͟c͟t͟i͟o͟n͟ ͟2 ▼ // hover ╶─────────────╴ S͟e͟c͟t͟i͟o͟n͟ ͟3 △ // open Content........ goes here...... ╶─────────────╴ S͟e͟c͟t͟i͟o͟n͟ ͟3 ▽ ◺ ◁ ◸ △ // animation ╶─────────────╴ */ //# sourceMappingURL=Accordion.js.map