Files
notebrook-notes/frontend/src/ui/list.ts
2024-09-03 14:50:33 +02:00

171 lines
5.5 KiB
TypeScript

import { UINode } from "./node";
export class List extends UINode {
public children: UINode[];
protected listElement: HTMLUListElement;
private focused: number;
protected selectCallback?: (id: number) => void;
public constructor(title: string) {
super(title);
this.children = [];
this.listElement = document.createElement("ul");
this.listElement.setAttribute("role", "listbox");
this.listElement.style.listStyle = "none";
this.element.appendChild(this.listElement);
this.element.setAttribute("aria-label", this.title);
this.focused = 0;
}
public add(node: UINode) {
this.children.push(node);
node._onConnect();
this.listElement.appendChild(node.render());
if (this.children.length === 1) this.calculateTabIndex();
node.onFocus(() => this.calculateFocused(node));
return this;
}
public addNodeAtIndex(node: UINode, index: number) {
index = Math.max(0, Math.min(index, this.children.length));
this.children.splice(index, 0, node);
node._onConnect();
this.listElement.insertBefore(node.render(), this.listElement.children[index]);
if (this.children.length === 1) this.calculateTabIndex();
node.onFocus(() => this.calculateFocused(node));
return this;
}
public remove(node: UINode) {
const idx = this.children.indexOf(node);
this.children.splice(idx, 1);
node._onDisconnect();
this.listElement.removeChild(node.render());
if (idx === this.focused) {
if (this.focused > 0) this.focused--;
this.calculateTabIndex();
}
return this;
}
public _onFocus() {
super._onFocus();
this.children[this.focused].focus();
return this;
}
public _onClick() {
// this.children[this.focused]._onClick();
}
public _onSelect(id: number) {
if (this.selectCallback) this.selectCallback(id);
}
protected calculateStyle(): void {
super.calculateStyle();
this.element.style.overflowY = "scroll";
this.listElement.style.overflowY = "scroll";
}
public _onKeydown(key: string, alt: boolean = false, shift: boolean = false, ctrl: boolean = false): boolean {
switch (key) {
case "ArrowUp":
this.children[this.focused].setTabbable(false);
this.focused = Math.max(0, this.focused - 1);
this.children[this.focused].setTabbable(true);
this.children[this.focused].focus();
return true;
break;
case "ArrowDown":
this.children[this.focused].setTabbable(false);
this.focused = Math.min(this.children.length - 1, this.focused + 1);
this.children[this.focused].setTabbable(true);
this.children[this.focused].focus();
return true;
break;
case "Enter":
this.children[this.focused].click();
return true;
break;
case "Home":
this.children[this.focused].setTabbable(false);
this.focused = 0;
this.children[this.focused].setTabbable(true);
this.children[this.focused].focus();
return true;
break;
case "End":
this.children[this.focused].setTabbable(false);
this.focused = this.children.length - 1;
this.children[this.focused].setTabbable(true);
this.children[this.focused].focus();
return true;
break;
default:
return this.children[this.focused]._onKeydown(key);
break;
}
return false;
}
protected renderAsListItem(node: UINode) {
let li = document.createElement("li");
li.appendChild(node.render());
return li;
}
public getElement(): HTMLElement {
return this.listElement;
}
public isItemFocused(): boolean {
const has = this.children.find((child) => child.isFocused);
if (has) {
return true;
}
return false;
}
private calculateTabIndex() {
if (this.children.length < 1) return;
this.children[this.focused].setTabbable(true);
}
public clear() {
this.children.forEach((child) => this.remove(child));
this.children = [];
this.listElement.innerHTML = '';
this.focused = 0;
return this;
}
public getFocusedChild() {
return this.children[this.focused];
}
public getFocus() {
return this.focused;
}
public onSelect(f: (id: number) => void) {
this.selectCallback = f;
return this;
}
protected calculateFocused(node: UINode) {
const idx = this.children.indexOf(node);
this._onSelect(idx);
this.focused = idx;
}
public scrollToBottom() {
this.children.forEach((child) => child.setTabbable(false));
const node = this.children[this.children.length - 1];
node.getElement().scrollIntoView();
// set the focused element for tab index without focusing directly.
this.focused = this.children.length - 1;
this.children[this.focused].setTabbable(true);
return this;
}
}