@progress/kendo-angular-listview
Version:
Kendo UI Angular listview component
160 lines (159 loc) • 6.37 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { Keys, normalizeKeys } from '@progress/kendo-angular-common';
import { closestWithMatch, fitIntoRange, getListItemIndex, isListViewItem, isPresent, relatedTarget } from '../utils';
import * as i0 from "@angular/core";
/**
* @hidden
*
* Provided per ListView instance. Keeps the availability, active index and focused state of the current ListView.
* Emits `changes` when any of the aforementioned states changes.
*/
export class NavigationService {
/**
* Emits every time a change in active index/focus/blur/navigation availability occurs.
*/
changes = new Subject();
/**
* Sets or gets if the navigation is enabled.
* When no activeIndex is present, the navigation is inferred as disabled.
* Toggling the service availability clears the current active index or activates the first one.
*/
get isEnabled() {
return isPresent(this.activeIndex);
}
set isEnabled(enabled) {
if (enabled) {
this.activeIndex = 0;
}
else {
this.activeIndex = null;
}
this.changes.next(undefined);
}
/**
* Specifies if a ListView item currently holds focus.
*/
isFocused = false;
/**
* Keeps track of the index of the items that should be the current focus target (tabindex="0").
* When set to `null`/`undefined`, the navigation is disabled and the items should not render a tabindex.
*/
activeIndex = null;
/**
* Shows if the checked index should be the current available focus target (tabindex="0").
*/
isActive(index) {
return index === this.activeIndex;
}
handleKeyDown(event, totalItemsCount) {
// on some keyboards arrow keys, PageUp/Down, and Home/End are mapped to Numpad keys
const code = normalizeKeys(event);
switch (code) {
case Keys.ArrowLeft:
case Keys.ArrowUp:
this.navigateToPrevious();
break;
case Keys.ArrowRight:
case Keys.ArrowDown:
this.navigateToNext(totalItemsCount);
break;
case Keys.Home: {
const firstIndex = 0;
this.navigateTo(firstIndex);
break;
}
case Keys.End: {
const lastIndex = totalItemsCount - 1;
this.navigateTo(lastIndex);
break;
}
default: return;
}
// the following line is executed only if the pressed key matches one of the listview hotkeys -
// they `break`, while the `default` case `return`s
event.preventDefault();
}
handleFocusIn(event) {
const target = event.target;
if (!isListViewItem(target)) {
const listViewItemSelector = '.k-listview-item';
const closestListViewItem = closestWithMatch(target, listViewItemSelector);
const isListViewItemChild = isPresent(closestListViewItem);
if (isListViewItemChild) {
const itemIndex = getListItemIndex(closestListViewItem);
this.setActiveIndex(itemIndex);
}
return;
}
const targetIndex = getListItemIndex(target);
// don't emit if the no change in focused state has occurred and the targeted index is the same as the current activeIndex
if (this.isFocused && targetIndex === this.activeIndex) {
return;
}
this.activeIndex = targetIndex;
this.isFocused = true;
this.changes.next(undefined);
}
handleFocusOut(event) {
// don't emit if the blurred item is not a listview item or if the new focus target is another listview item
if (!isListViewItem(event.target) || isListViewItem(relatedTarget(event))) {
return;
}
this.isFocused = false;
this.changes.next(undefined);
}
/**
* Sets the `activeIndex` and triggers changes without focusing the corresponding ListView item.
*/
setActiveIndex(index) {
if (!this.isEnabled) {
return;
}
if (index === this.activeIndex) {
return;
}
this.activeIndex = index;
this.changes.next(undefined);
}
/**
* Focuses item at the targeted index. If no index is passed, the current `activeIndex` is used.
* The passed target index is normalized to fit the min/max available indices bounds.
*/
focusIndex(index, totalItemsCount) {
if (!this.isEnabled) {
return;
}
const parsed = parseInt(index, 10);
const firstIndex = 0;
const lastIndex = totalItemsCount - 1;
const targetIndex = isNaN(parsed) ? this.activeIndex : fitIntoRange(parsed, firstIndex, lastIndex);
this.navigateTo(targetIndex);
}
navigateTo(index) {
if (this.isFocused && this.activeIndex === index) {
return;
}
this.isFocused = true;
this.activeIndex = index;
this.changes.next(undefined);
}
navigateToPrevious() {
const previousIndex = Math.max(this.activeIndex - 1, 0);
this.navigateTo(previousIndex);
}
navigateToNext(totalItemsCount) {
const lastAvailableIndex = totalItemsCount - 1;
const nextIndex = Math.min(this.activeIndex + 1, lastAvailableIndex);
this.navigateTo(nextIndex);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NavigationService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NavigationService, decorators: [{
type: Injectable
}] });