/**
* Чистая логика окна видимых строк вертикального списка: без Pixi,
* тестируется изолированно (по образцу ListCursor). MenuList использует её
* для скролла длинных списков: курсор всегда в окне, окно — в [0, count].
*/
export class ListScroll {
/** Индекс первой видимой строки. */
offset = 0;
/** Число элементов (из последнего sync) — для clamped. */
private count = 0;
constructor(readonly rows: number) {}
/** Пересчёт окна под выбранный индекс; возвращает offset. */
sync(index: number, count: number): number {
this.count = count;
const max = Math.max(0, count - this.rows);
if (this.offset > max) this.offset = max; // список пересобран короче
if (index < this.offset) this.offset = index; // курсор ушёл выше окна
else if (index >= this.offset + this.rows) {
this.offset = Math.min(max, index - this.rows + 1); // ниже окна
}
return this.offset;
}
/** Индекс первой видимой строки. */
get first(): number {
return this.offset;
}
/** Исключающая граница окна (после последней видимой строки). */
get last(): number {
return Math.min(this.count, this.offset + this.rows);
}
/** Список короче окна: всё видно, прокрутки нет. */
get clamped(): boolean {
return this.count <= this.rows;
}
}