mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-01-24 09:50:24 +00:00
120 lines
3.9 KiB
TypeScript
120 lines
3.9 KiB
TypeScript
|
import * as _ from "underscore";
|
||
|
import * as ko from "knockout";
|
||
|
|
||
|
enum ScrollPosition {
|
||
|
Top,
|
||
|
Bottom
|
||
|
}
|
||
|
|
||
|
export class AccessibleVerticalList {
|
||
|
private items: any[] = [];
|
||
|
private onSelect: (item: any) => void;
|
||
|
|
||
|
public currentItemIndex: ko.Observable<number>;
|
||
|
public currentItem: ko.Computed<any>;
|
||
|
|
||
|
constructor(initialSetOfItems: any[]) {
|
||
|
this.items = initialSetOfItems;
|
||
|
this.currentItemIndex = this.items != null && this.items.length > 0 ? ko.observable(0) : ko.observable(-1);
|
||
|
this.currentItem = ko.computed<any>(() => this.items[this.currentItemIndex()]);
|
||
|
}
|
||
|
|
||
|
public setOnSelect(onSelect: (item: any) => void): void {
|
||
|
this.onSelect = onSelect;
|
||
|
}
|
||
|
|
||
|
public onKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||
|
const targetContainer: Element = <Element>event.target;
|
||
|
if (this.items == null || this.items.length === 0) {
|
||
|
// no items so this should be a noop
|
||
|
return true;
|
||
|
}
|
||
|
if (event.keyCode === 32 || event.keyCode === 13) {
|
||
|
// on space or enter keydown
|
||
|
this.onSelect && this.onSelect(this.currentItem());
|
||
|
event.stopPropagation();
|
||
|
return false;
|
||
|
}
|
||
|
if (event.keyCode === 38) {
|
||
|
// on UpArrow keydown
|
||
|
event.preventDefault();
|
||
|
this.selectPreviousItem();
|
||
|
const targetElement = targetContainer
|
||
|
.getElementsByClassName("accessibleListElement")
|
||
|
.item(this.currentItemIndex());
|
||
|
this.scrollElementIntoContainerViewIfNeeded(targetElement, targetContainer, ScrollPosition.Top);
|
||
|
return false;
|
||
|
}
|
||
|
if (event.keyCode === 40) {
|
||
|
// on DownArrow keydown
|
||
|
event.preventDefault();
|
||
|
this.selectNextItem();
|
||
|
const targetElement = targetContainer
|
||
|
.getElementsByClassName("accessibleListElement")
|
||
|
.item(this.currentItemIndex());
|
||
|
this.scrollElementIntoContainerViewIfNeeded(targetElement, targetContainer, ScrollPosition.Bottom);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
public updateItemList(newItemList: any[]) {
|
||
|
if (newItemList == null || newItemList.length === 0) {
|
||
|
this.currentItemIndex(-1);
|
||
|
this.items = [];
|
||
|
return;
|
||
|
} else if (this.currentItemIndex() < 0) {
|
||
|
this.currentItemIndex(0);
|
||
|
}
|
||
|
this.items = newItemList;
|
||
|
}
|
||
|
|
||
|
public updateCurrentItem(item: any) {
|
||
|
const updatedIndex: number = this.isItemListEmpty() ? -1 : _.indexOf(this.items, item);
|
||
|
this.currentItemIndex(updatedIndex);
|
||
|
}
|
||
|
|
||
|
private isElementVisibleInContainer(element: Element, container: Element): boolean {
|
||
|
const elementTop = element.getBoundingClientRect().top;
|
||
|
const elementBottom = element.getBoundingClientRect().bottom;
|
||
|
const containerTop = container.getBoundingClientRect().top;
|
||
|
const containerBottom = container.getBoundingClientRect().bottom;
|
||
|
|
||
|
return elementTop >= containerTop && elementBottom <= containerBottom;
|
||
|
}
|
||
|
|
||
|
private scrollElementIntoContainerViewIfNeeded(
|
||
|
element: Element,
|
||
|
container: Element,
|
||
|
scrollPosition: ScrollPosition
|
||
|
): void {
|
||
|
if (!this.isElementVisibleInContainer(element, container)) {
|
||
|
if (scrollPosition === ScrollPosition.Top) {
|
||
|
container.scrollTop =
|
||
|
element.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
|
||
|
} else {
|
||
|
container.scrollTop =
|
||
|
element.getBoundingClientRect().bottom - element.getBoundingClientRect().top + container.scrollTop;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private selectPreviousItem(): void {
|
||
|
if (this.currentItemIndex() <= 0 || this.isItemListEmpty()) {
|
||
|
return;
|
||
|
}
|
||
|
this.currentItemIndex(this.currentItemIndex() - 1);
|
||
|
}
|
||
|
|
||
|
private selectNextItem(): void {
|
||
|
if (this.isItemListEmpty() || this.currentItemIndex() === this.items.length - 1) {
|
||
|
return;
|
||
|
}
|
||
|
this.currentItemIndex(this.currentItemIndex() + 1);
|
||
|
}
|
||
|
|
||
|
private isItemListEmpty(): boolean {
|
||
|
return this.items == null || this.items.length === 0;
|
||
|
}
|
||
|
}
|