rintoj / ngx-virtual-scroller

Virtual Scroll displays a virtual, "infinite" list.
https://rintoj.github.io/ngx-virtual-scroller
MIT License
979 stars 295 forks source link

Keyboard navigation support #555

Open weilinzung opened 1 year ago

weilinzung commented 1 year ago

Would possible to add keyboard navigation(arrow up/down) & enter key support?

lincolnthree commented 1 year ago

You can do this using DOM listeners and the "scrollContainer.scrollToPosition" method. Just keep track of which element is selected, then calculate the position of the next/previous one, and call the method

Personally I don't think this is something that should be "built in". But all the methods/data to make it possible are here already.

weilinzung commented 1 year ago

@lincolnthree that sounds workable, do you have an example? we are trying to write our custom method with CDK focusKeyManager, but the logic isn't that simple.

lincolnthree commented 1 year ago

Unfortunately not a good one. It's pretty embedded in my application, but here's the general idea:

This would track the index of the selected item (and presumably highlight it)

      {
        key: ["up"],
        label: "Editor",
        description: "Previous card in list",
        preventDefault: true,
        command: async (e) => {
          if (!InputUtil.isInputFocused() && this.controller.list.isActive()) {
            if (this.controller.list.canShiftIndex(-1)) {
              UIEvents.stopPropagation(e.event);
              this.controller.list.shiftIndex(-1); 
            }
          }
        }
      },
  {
        key: ["down"],
        label: "Editor",
        description: "Next card in list",
        preventDefault: true,
        command: async (e) => {
          if (!InputUtil.isInputFocused() && this.controller.list.isActive()) {
            if (this.controller.list.canShiftIndex(1)) {
              UIEvents.stopPropagation(e.event);
              this.controller.list.shiftIndex(1); 
            }
          }
        }
      }

Some code for figuring out the item height based on our component / wrapper for the virtual list:


    this.list = this.lists.createListManager(CARDS_OVERVIEW_LIST, {
      dimensions: () => {
        return this.grid?.scroll
          ? { childHeight: this.grid.scroll.itemHeight, itemsPerWrapGroup: this.grid.scroll.itemsPerRow }
          : { childHeight: CARD_ITEM_HEIGHT, itemsPerWrapGroup: 1 };
      },
      scrollOnIndexChanged: false,
      scrollTo: async (_x: number, y: number) => {
        this.scrollTo(y + Math.floor(this.grid.scroll._virtualWindow?.nativeElement?.offsetTop ?? 0))
      }
    });

HostListeners for keyboard binding events in Angular:

  @HostListener('document:keydown.ArrowUp', ['$event']) onPrevHandler(_event: KeyboardEvent) {
    if (this.isPrevButtonVisible && !this.isFirstStep()) {
      this.prev();
    }
  }