UNPKG

@progress/kendo-angular-listview

Version:

Kendo UI Angular listview component

160 lines (159 loc) 6.37 kB
/**----------------------------------------------------------------------------------------- * 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 }] });