import { hiddenBySections, sectionOffsets, type SectionRow, sectionStartOf } from '../sections'; import { type Size, Widget } from '../Widget'; import { type StackOptions, VStack } from './Stack'; /** * A column of rows in groups (#290): a `SectionHeader` runs across the padding as a band, the rows * after it stand inside it, and a folded header hides its rows down to the next one. Children are * never stretched along the column – it is meant for a scroll container. */ export class SectionStack extends VStack { /** After a group was folded or unfolded – the owner measures the column again. */ onFold: () => void = () => {}; /** The children a fold hid, so unfolding shows only those again. */ private readonly hiddenByFold = new WeakSet(); constructor(options: StackOptions = {}) { super(options); } refold(): void { this.applyFolds(); this.onFold(); } private applyFolds(): void { const widgets = this.children.filter((c): c is Widget => c instanceof Widget); const hidden = hiddenBySections(widgets.map((w) => sectionStartOf(w)?.sectionCollapsed ?? null)); widgets.forEach((widget, i) => { if (hidden[i] && widget.visible) { widget.visible = false; this.hiddenByFold.add(widget); } else if (!hidden[i] && this.hiddenByFold.has(widget)) { widget.visible = true; this.hiddenByFold.delete(widget); } }); } private rows(sizes: readonly Size[]): SectionRow[] { return this.items.map((item, i) => { const start = sectionStartOf(item); return { h: sizes[i]!.h, start: start !== null, band: start !== null && (start.sectionStart === 'band' || start.sectionCollapsed), }; }); } override measure(): Size { this.applyFolds(); const sizes = this.items.map((item) => item.measure()); const w = Math.max(0, ...sizes.map((size) => size.w)) + this.padding * 2; const { height } = sectionOffsets(this.rows(sizes), this.gap, this.padding); return { w: Math.max(w, this.minWidth), h: Math.max(height, this.minHeight) }; } override layout(): void { this.applyFolds(); const items = this.items; const sizes = items.map((item) => item.measure()); const { ys } = sectionOffsets(this.rows(sizes), this.gap, this.padding); items.forEach((item, i) => { const band = sectionStartOf(item)?.sectionStart === 'band'; item.position.set(band ? 0 : this.padding, Math.round(ys[i]!)); item.resize(Math.max(0, band ? this.w : this.w - this.padding * 2), Math.round(sizes[i]!.h)); }); } }