@farjs/ui
Version:
Terminal UI React.js components library
193 lines (173 loc) • 4.78 kB
JavaScript
/**
* @typedef {{
* readonly offset: number;
* readonly focused: number;
* readonly length: number;
* readonly viewLength: number;
* updated(offset: number, focused?: number): ListViewport;
* down(): ListViewport;
* up(): ListViewport;
* pagedown(): ListViewport;
* pageup(): ListViewport;
* end(): ListViewport;
* home(): ListViewport;
* onKeypress(keyFull: string): ListViewport | undefined;
* resize(viewLength: number): ListViewport;
* }} ListViewport
*/
/**
* @implements {ListViewport}
*/
class ListViewportImpl {
/**
* @param {number} offset
* @param {number} focused
* @param {number} length
* @param {number} viewLength
*/
constructor(offset, focused, length, viewLength) {
/** @type {number} */ this.offset = offset;
/** @type {number} */ this.focused = focused;
/** @type {number} */ this.length = length;
/** @type {number} */ this.viewLength = viewLength;
}
/**
* @param {number} offset
* @param {number} [focused]
*/
updated(offset, focused) {
const newOffset = Math.max(
Math.min(offset, this.length - this.viewLength),
0
);
const newFocused = Math.max(
Math.min(focused ?? this.focused, this.viewLength - 1),
0
);
if (newOffset === this.offset && newFocused === this.focused) {
return this;
}
return new ListViewportImpl(
newOffset,
newFocused,
this.length,
this.viewLength
);
}
down() {
const maxFocused = Math.max(
Math.min(this.length - this.offset - 1, this.viewLength - 1),
0
);
if (this.focused < maxFocused) {
return this.updated(this.offset, this.focused + 1);
}
if (this.offset < this.length - this.viewLength) {
return this.updated(this.offset + 1);
}
return this;
}
up() {
if (this.focused > 0) return this.updated(this.offset, this.focused - 1);
if (this.offset > 0) return this.updated(this.offset - 1);
return this;
}
pagedown() {
const newOffset = Math.max(
Math.min(this.length - this.viewLength, this.offset + this.viewLength),
0
);
const newFocused =
newOffset === this.offset
? Math.max(
Math.min(this.length - newOffset - 1, this.viewLength - 1),
0
)
: Math.max(Math.min(this.length - newOffset - 1, this.focused), 0);
return this.updated(newOffset, newFocused);
}
pageup() {
const newOffset = Math.max(this.offset - this.viewLength, 0);
const newFocused = newOffset === this.offset ? 0 : this.focused;
return this.updated(newOffset, newFocused);
}
end() {
const newOffset = Math.max(this.length - this.viewLength, 0);
const newFocused = Math.max(
Math.min(this.length - newOffset - 1, this.viewLength - 1),
0
);
return this.updated(newOffset, newFocused);
}
home() {
return this.updated(0, 0);
}
/**
* @param {string} keyFull
* @returns {ListViewport | undefined}
*/
onKeypress(keyFull) {
switch (keyFull) {
case "down":
return this.down();
case "up":
return this.up();
case "pagedown":
return this.pagedown();
case "pageup":
return this.pageup();
case "end":
return this.end();
case "home":
return this.home();
default:
return undefined;
}
}
/**
* @param {number} viewLength
* @returns {ListViewport}
*/
resize(viewLength) {
const newViewLength = Math.max(viewLength, 0);
if (newViewLength === this.viewLength) {
return this;
}
const index = this.offset + this.focused;
const dx =
this.focused >= newViewLength ? this.focused - newViewLength + 1 : 0;
const newOffset = Math.max(
Math.min(this.length - newViewLength, this.offset + dx),
0
);
const newFocused = Math.max(
Math.min(this.length - newOffset - 1, index - newOffset),
0
);
return new ListViewportImpl(
newOffset,
newFocused,
this.length,
newViewLength
);
}
}
/**
* @param {number} index
* @param {number} length
* @param {number} viewLength
* @returns {ListViewport}
*/
export function createListViewport(index, length, viewLength) {
viewLength = Math.max(viewLength, 0);
length = Math.max(length, 0);
index = Math.max(Math.min(length - 1, index), 0);
let offset = 0;
let focused = index;
if (index >= viewLength && viewLength > 0) {
const rawOffset = Math.trunc(index / viewLength) * viewLength;
offset = Math.max(Math.min(length - viewLength, rawOffset), 0);
focused = Math.max(Math.min(length - offset - 1, index - offset), 0);
}
return new ListViewportImpl(offset, focused, length, viewLength);
}