171 lines
5.5 KiB
TypeScript
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;
|
|
}
|
|
} |