UNPKG

@angular/cdk

Version:

Angular Material Component Development Kit

273 lines (270 loc) 8.47 kB
import { signal, QueryList, isSignal, effect } from '@angular/core'; import { Subscription, Subject } from 'rxjs'; import { Typeahead } from './_typeahead-chunk.mjs'; import { hasModifierKey } from './keycodes.mjs'; import { PAGE_DOWN, PAGE_UP, END, HOME, LEFT_ARROW, RIGHT_ARROW, UP_ARROW, DOWN_ARROW, TAB } from './_keycodes-chunk.mjs'; class ListKeyManager { _items; _activeItemIndex = signal(-1, ...(ngDevMode ? [{ debugName: "_activeItemIndex" }] : [])); _activeItem = signal(null, ...(ngDevMode ? [{ debugName: "_activeItem" }] : [])); _wrap = false; _typeaheadSubscription = Subscription.EMPTY; _itemChangesSubscription; _vertical = true; _horizontal; _allowedModifierKeys = []; _homeAndEnd = false; _pageUpAndDown = { enabled: false, delta: 10 }; _effectRef; _typeahead; _skipPredicateFn = item => item.disabled; constructor(_items, injector) { this._items = _items; if (_items instanceof QueryList) { this._itemChangesSubscription = _items.changes.subscribe(newItems => this._itemsChanged(newItems.toArray())); } else if (isSignal(_items)) { if (!injector && (typeof ngDevMode === 'undefined' || ngDevMode)) { throw new Error('ListKeyManager constructed with a signal must receive an injector'); } this._effectRef = effect(() => this._itemsChanged(_items()), ...(ngDevMode ? [{ debugName: "_effectRef", injector }] : [{ injector }])); } } tabOut = new Subject(); change = new Subject(); skipPredicate(predicate) { this._skipPredicateFn = predicate; return this; } withWrap(shouldWrap = true) { this._wrap = shouldWrap; return this; } withVerticalOrientation(enabled = true) { this._vertical = enabled; return this; } withHorizontalOrientation(direction) { this._horizontal = direction; return this; } withAllowedModifierKeys(keys) { this._allowedModifierKeys = keys; return this; } withTypeAhead(debounceInterval = 200) { if (typeof ngDevMode === 'undefined' || ngDevMode) { const items = this._getItemsArray(); if (items.length > 0 && items.some(item => typeof item.getLabel !== 'function')) { throw Error('ListKeyManager items in typeahead mode must implement the `getLabel` method.'); } } this._typeaheadSubscription.unsubscribe(); const items = this._getItemsArray(); this._typeahead = new Typeahead(items, { debounceInterval: typeof debounceInterval === 'number' ? debounceInterval : undefined, skipPredicate: item => this._skipPredicateFn(item) }); this._typeaheadSubscription = this._typeahead.selectedItem.subscribe(item => { this.setActiveItem(item); }); return this; } cancelTypeahead() { this._typeahead?.reset(); return this; } withHomeAndEnd(enabled = true) { this._homeAndEnd = enabled; return this; } withPageUpDown(enabled = true, delta = 10) { this._pageUpAndDown = { enabled, delta }; return this; } setActiveItem(item) { const previousActiveItem = this._activeItem(); this.updateActiveItem(item); if (this._activeItem() !== previousActiveItem) { this.change.next(this._activeItemIndex()); } } onKeydown(event) { const keyCode = event.keyCode; const modifiers = ['altKey', 'ctrlKey', 'metaKey', 'shiftKey']; const isModifierAllowed = modifiers.every(modifier => { return !event[modifier] || this._allowedModifierKeys.indexOf(modifier) > -1; }); switch (keyCode) { case TAB: this.tabOut.next(); return; case DOWN_ARROW: if (this._vertical && isModifierAllowed) { this.setNextItemActive(); break; } else { return; } case UP_ARROW: if (this._vertical && isModifierAllowed) { this.setPreviousItemActive(); break; } else { return; } case RIGHT_ARROW: if (this._horizontal && isModifierAllowed) { this._horizontal === 'rtl' ? this.setPreviousItemActive() : this.setNextItemActive(); break; } else { return; } case LEFT_ARROW: if (this._horizontal && isModifierAllowed) { this._horizontal === 'rtl' ? this.setNextItemActive() : this.setPreviousItemActive(); break; } else { return; } case HOME: if (this._homeAndEnd && isModifierAllowed) { this.setFirstItemActive(); break; } else { return; } case END: if (this._homeAndEnd && isModifierAllowed) { this.setLastItemActive(); break; } else { return; } case PAGE_UP: if (this._pageUpAndDown.enabled && isModifierAllowed) { const targetIndex = this._activeItemIndex() - this._pageUpAndDown.delta; this._setActiveItemByIndex(targetIndex > 0 ? targetIndex : 0, 1); break; } else { return; } case PAGE_DOWN: if (this._pageUpAndDown.enabled && isModifierAllowed) { const targetIndex = this._activeItemIndex() + this._pageUpAndDown.delta; const itemsLength = this._getItemsArray().length; this._setActiveItemByIndex(targetIndex < itemsLength ? targetIndex : itemsLength - 1, -1); break; } else { return; } default: if (isModifierAllowed || hasModifierKey(event, 'shiftKey')) { this._typeahead?.handleKey(event); } return; } this._typeahead?.reset(); event.preventDefault(); } get activeItemIndex() { return this._activeItemIndex(); } get activeItem() { return this._activeItem(); } isTyping() { return !!this._typeahead && this._typeahead.isTyping(); } setFirstItemActive() { this._setActiveItemByIndex(0, 1); } setLastItemActive() { this._setActiveItemByIndex(this._getItemsArray().length - 1, -1); } setNextItemActive() { this._activeItemIndex() < 0 ? this.setFirstItemActive() : this._setActiveItemByDelta(1); } setPreviousItemActive() { this._activeItemIndex() < 0 && this._wrap ? this.setLastItemActive() : this._setActiveItemByDelta(-1); } updateActiveItem(item) { const itemArray = this._getItemsArray(); const index = typeof item === 'number' ? item : itemArray.indexOf(item); const activeItem = itemArray[index]; this._activeItem.set(activeItem == null ? null : activeItem); this._activeItemIndex.set(index); this._typeahead?.setCurrentSelectedItemIndex(index); } destroy() { this._typeaheadSubscription.unsubscribe(); this._itemChangesSubscription?.unsubscribe(); this._effectRef?.destroy(); this._typeahead?.destroy(); this.tabOut.complete(); this.change.complete(); } _setActiveItemByDelta(delta) { this._wrap ? this._setActiveInWrapMode(delta) : this._setActiveInDefaultMode(delta); } _setActiveInWrapMode(delta) { const items = this._getItemsArray(); for (let i = 1; i <= items.length; i++) { const index = (this._activeItemIndex() + delta * i + items.length) % items.length; const item = items[index]; if (!this._skipPredicateFn(item)) { this.setActiveItem(index); return; } } } _setActiveInDefaultMode(delta) { this._setActiveItemByIndex(this._activeItemIndex() + delta, delta); } _setActiveItemByIndex(index, fallbackDelta) { const items = this._getItemsArray(); if (!items[index]) { return; } while (this._skipPredicateFn(items[index])) { index += fallbackDelta; if (!items[index]) { return; } } this.setActiveItem(index); } _getItemsArray() { if (isSignal(this._items)) { return this._items(); } return this._items instanceof QueryList ? this._items.toArray() : this._items; } _itemsChanged(newItems) { this._typeahead?.setItems(newItems); const activeItem = this._activeItem(); if (activeItem) { const newIndex = newItems.indexOf(activeItem); if (newIndex > -1 && newIndex !== this._activeItemIndex()) { this._activeItemIndex.set(newIndex); this._typeahead?.setCurrentSelectedItemIndex(newIndex); } } } } export { ListKeyManager }; //# sourceMappingURL=_list-key-manager-chunk.mjs.map