@angular/cdk 18.1.1 → 18.2.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/a11y/index.d.ts +283 -2
  2. package/coercion/private/index.d.ts +9 -0
  3. package/drag-drop/index.d.ts +12 -1
  4. package/esm2022/a11y/key-manager/list-key-manager.mjs +18 -38
  5. package/esm2022/a11y/key-manager/noop-tree-key-manager.mjs +94 -0
  6. package/esm2022/a11y/key-manager/tree-key-manager-strategy.mjs +9 -0
  7. package/esm2022/a11y/key-manager/tree-key-manager.mjs +345 -0
  8. package/esm2022/a11y/key-manager/typeahead.mjs +91 -0
  9. package/esm2022/a11y/public-api.mjs +4 -1
  10. package/esm2022/coercion/private/index.mjs +9 -0
  11. package/esm2022/coercion/private/observable.mjs +19 -0
  12. package/esm2022/coercion/private/private_public_index.mjs +5 -0
  13. package/esm2022/drag-drop/directives/drag.mjs +16 -3
  14. package/esm2022/drag-drop/drag-ref.mjs +8 -2
  15. package/esm2022/drag-drop/sorting/single-axis-sort-strategy.mjs +4 -3
  16. package/esm2022/tree/control/base-tree-control.mjs +7 -2
  17. package/esm2022/tree/control/flat-tree-control.mjs +8 -2
  18. package/esm2022/tree/control/nested-tree-control.mjs +11 -2
  19. package/esm2022/tree/control/tree-control.mjs +1 -1
  20. package/esm2022/tree/nested-node.mjs +6 -15
  21. package/esm2022/tree/padding.mjs +2 -4
  22. package/esm2022/tree/toggle.mjs +15 -8
  23. package/esm2022/tree/tree-errors.mjs +7 -6
  24. package/esm2022/tree/tree.mjs +817 -63
  25. package/esm2022/version.mjs +1 -1
  26. package/fesm2022/a11y.mjs +520 -40
  27. package/fesm2022/a11y.mjs.map +1 -1
  28. package/fesm2022/cdk.mjs +1 -1
  29. package/fesm2022/cdk.mjs.map +1 -1
  30. package/fesm2022/coercion/private.mjs +19 -0
  31. package/fesm2022/coercion/private.mjs.map +1 -0
  32. package/fesm2022/drag-drop.mjs +25 -5
  33. package/fesm2022/drag-drop.mjs.map +1 -1
  34. package/fesm2022/tree.mjs +858 -94
  35. package/fesm2022/tree.mjs.map +1 -1
  36. package/package.json +9 -3
  37. package/schematics/ng-add/index.js +1 -1
  38. package/schematics/ng-add/index.mjs +1 -1
  39. package/tree/index.d.ts +304 -25
@@ -7,5 +7,5 @@
7
7
  */
8
8
  import { Version } from '@angular/core';
9
9
  /** Current version of the Angular Component Development Kit. */
10
- export const VERSION = new Version('18.1.1');
10
+ export const VERSION = new Version('18.2.0-next.1');
11
11
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmVyc2lvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9jZGsvdmVyc2lvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7O0dBTUc7QUFFSCxPQUFPLEVBQUMsT0FBTyxFQUFDLE1BQU0sZUFBZSxDQUFDO0FBRXRDLGdFQUFnRTtBQUNoRSxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsSUFBSSxPQUFPLENBQUMsbUJBQW1CLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCBHb29nbGUgTExDIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4gKlxuICogVXNlIG9mIHRoaXMgc291cmNlIGNvZGUgaXMgZ292ZXJuZWQgYnkgYW4gTUlULXN0eWxlIGxpY2Vuc2UgdGhhdCBjYW4gYmVcbiAqIGZvdW5kIGluIHRoZSBMSUNFTlNFIGZpbGUgYXQgaHR0cHM6Ly9hbmd1bGFyLmlvL2xpY2Vuc2VcbiAqL1xuXG5pbXBvcnQge1ZlcnNpb259IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuXG4vKiogQ3VycmVudCB2ZXJzaW9uIG9mIHRoZSBBbmd1bGFyIENvbXBvbmVudCBEZXZlbG9wbWVudCBLaXQuICovXG5leHBvcnQgY29uc3QgVkVSU0lPTiA9IG5ldyBWZXJzaW9uKCcwLjAuMC1QTEFDRUhPTERFUicpO1xuIl19
package/fesm2022/a11y.mjs CHANGED
@@ -1,11 +1,12 @@
1
1
  import { DOCUMENT } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { inject, APP_ID, Injectable, Inject, QueryList, isSignal, effect, afterNextRender, Injector, booleanAttribute, Directive, Input, InjectionToken, Optional, EventEmitter, Output, NgModule } from '@angular/core';
3
+ import { inject, APP_ID, Injectable, Inject, QueryList, isSignal, effect, InjectionToken, afterNextRender, Injector, booleanAttribute, Directive, Input, Optional, EventEmitter, Output, NgModule } from '@angular/core';
4
4
  import * as i1 from '@angular/cdk/platform';
5
5
  import { Platform, _getFocusedElementPierceShadowDom, normalizePassiveListenerOptions, _getEventTarget, _getShadowRoot } from '@angular/cdk/platform';
6
- import { Subject, Subscription, BehaviorSubject, of } from 'rxjs';
7
- import { hasModifierKey, A, Z, ZERO, NINE, PAGE_DOWN, PAGE_UP, END, HOME, LEFT_ARROW, RIGHT_ARROW, UP_ARROW, DOWN_ARROW, TAB, ALT, CONTROL, MAC_META, META, SHIFT } from '@angular/cdk/keycodes';
8
- import { tap, debounceTime, filter, map, skip, distinctUntilChanged, takeUntil } from 'rxjs/operators';
6
+ import { Subject, Subscription, isObservable, of, BehaviorSubject } from 'rxjs';
7
+ import { A, Z, ZERO, NINE, hasModifierKey, PAGE_DOWN, PAGE_UP, END, HOME, LEFT_ARROW, RIGHT_ARROW, UP_ARROW, DOWN_ARROW, TAB, ALT, CONTROL, MAC_META, META, SHIFT } from '@angular/cdk/keycodes';
8
+ import { tap, debounceTime, filter, map, take, skip, distinctUntilChanged, takeUntil } from 'rxjs/operators';
9
+ import { coerceObservable } from '@angular/cdk/coercion/private';
9
10
  import * as i1$1 from '@angular/cdk/observers';
10
11
  import { ObserversModule } from '@angular/cdk/observers';
11
12
  import { coerceElement } from '@angular/cdk/coercion';
@@ -270,6 +271,87 @@ function setMessageId(element, serviceId) {
270
271
  }
271
272
  }
272
273
 
274
+ const DEFAULT_TYPEAHEAD_DEBOUNCE_INTERVAL_MS = 200;
275
+ /**
276
+ * Selects items based on keyboard inputs. Implements the typeahead functionality of
277
+ * `role="listbox"` or `role="tree"` and other related roles.
278
+ */
279
+ class Typeahead {
280
+ constructor(initialItems, config) {
281
+ this._letterKeyStream = new Subject();
282
+ this._items = [];
283
+ this._selectedItemIndex = -1;
284
+ /** Buffer for the letters that the user has pressed */
285
+ this._pressedLetters = [];
286
+ this._selectedItem = new Subject();
287
+ this.selectedItem = this._selectedItem;
288
+ const typeAheadInterval = typeof config?.debounceInterval === 'number'
289
+ ? config.debounceInterval
290
+ : DEFAULT_TYPEAHEAD_DEBOUNCE_INTERVAL_MS;
291
+ if (config?.skipPredicate) {
292
+ this._skipPredicateFn = config.skipPredicate;
293
+ }
294
+ if ((typeof ngDevMode === 'undefined' || ngDevMode) &&
295
+ initialItems.length &&
296
+ initialItems.some(item => typeof item.getLabel !== 'function')) {
297
+ throw new Error('KeyManager items in typeahead mode must implement the `getLabel` method.');
298
+ }
299
+ this.setItems(initialItems);
300
+ this._setupKeyHandler(typeAheadInterval);
301
+ }
302
+ destroy() {
303
+ this._pressedLetters = [];
304
+ this._letterKeyStream.complete();
305
+ this._selectedItem.complete();
306
+ }
307
+ setCurrentSelectedItemIndex(index) {
308
+ this._selectedItemIndex = index;
309
+ }
310
+ setItems(items) {
311
+ this._items = items;
312
+ }
313
+ handleKey(event) {
314
+ const keyCode = event.keyCode;
315
+ // Attempt to use the `event.key` which also maps it to the user's keyboard language,
316
+ // otherwise fall back to resolving alphanumeric characters via the keyCode.
317
+ if (event.key && event.key.length === 1) {
318
+ this._letterKeyStream.next(event.key.toLocaleUpperCase());
319
+ }
320
+ else if ((keyCode >= A && keyCode <= Z) || (keyCode >= ZERO && keyCode <= NINE)) {
321
+ this._letterKeyStream.next(String.fromCharCode(keyCode));
322
+ }
323
+ }
324
+ /** Gets whether the user is currently typing into the manager using the typeahead feature. */
325
+ isTyping() {
326
+ return this._pressedLetters.length > 0;
327
+ }
328
+ /** Resets the currently stored sequence of typed letters. */
329
+ reset() {
330
+ this._pressedLetters = [];
331
+ }
332
+ _setupKeyHandler(typeAheadInterval) {
333
+ // Debounce the presses of non-navigational keys, collect the ones that correspond to letters
334
+ // and convert those letters back into a string. Afterwards find the first item that starts
335
+ // with that string and select it.
336
+ this._letterKeyStream
337
+ .pipe(tap(letter => this._pressedLetters.push(letter)), debounceTime(typeAheadInterval), filter(() => this._pressedLetters.length > 0), map(() => this._pressedLetters.join('').toLocaleUpperCase()))
338
+ .subscribe(inputString => {
339
+ // Start at 1 because we want to start searching at the item immediately
340
+ // following the current active item.
341
+ for (let i = 1; i < this._items.length + 1; i++) {
342
+ const index = (this._selectedItemIndex + i) % this._items.length;
343
+ const item = this._items[index];
344
+ if (!this._skipPredicateFn?.(item) &&
345
+ item.getLabel?.().toLocaleUpperCase().trim().indexOf(inputString) === 0) {
346
+ this._selectedItem.next(item);
347
+ break;
348
+ }
349
+ }
350
+ this._pressedLetters = [];
351
+ });
352
+ }
353
+ }
354
+
273
355
  /**
274
356
  * This class manages keyboard events for selectable lists. If you pass it a query list
275
357
  * of items, it will set the active item correctly when arrow events occur.
@@ -280,7 +362,6 @@ class ListKeyManager {
280
362
  this._activeItemIndex = -1;
281
363
  this._activeItem = null;
282
364
  this._wrap = false;
283
- this._letterKeyStream = new Subject();
284
365
  this._typeaheadSubscription = Subscription.EMPTY;
285
366
  this._vertical = true;
286
367
  this._allowedModifierKeys = [];
@@ -291,8 +372,6 @@ class ListKeyManager {
291
372
  * by the key manager. By default, disabled items are skipped.
292
373
  */
293
374
  this._skipPredicateFn = (item) => item.disabled;
294
- // Buffer for the letters that the user has pressed when the typeahead option is turned on.
295
- this._pressedLetters = [];
296
375
  /**
297
376
  * Stream that emits any time the TAB key is pressed, so components can react
298
377
  * when focus is shifted off of the list.
@@ -368,31 +447,19 @@ class ListKeyManager {
368
447
  }
369
448
  }
370
449
  this._typeaheadSubscription.unsubscribe();
371
- // Debounce the presses of non-navigational keys, collect the ones that correspond to letters
372
- // and convert those letters back into a string. Afterwards find the first item that starts
373
- // with that string and select it.
374
- this._typeaheadSubscription = this._letterKeyStream
375
- .pipe(tap(letter => this._pressedLetters.push(letter)), debounceTime(debounceInterval), filter(() => this._pressedLetters.length > 0), map(() => this._pressedLetters.join('')))
376
- .subscribe(inputString => {
377
- const items = this._getItemsArray();
378
- // Start at 1 because we want to start searching at the item immediately
379
- // following the current active item.
380
- for (let i = 1; i < items.length + 1; i++) {
381
- const index = (this._activeItemIndex + i) % items.length;
382
- const item = items[index];
383
- if (!this._skipPredicateFn(item) &&
384
- item.getLabel().toUpperCase().trim().indexOf(inputString) === 0) {
385
- this.setActiveItem(index);
386
- break;
387
- }
388
- }
389
- this._pressedLetters = [];
450
+ const items = this._getItemsArray();
451
+ this._typeahead = new Typeahead(items, {
452
+ debounceInterval: typeof debounceInterval === 'number' ? debounceInterval : undefined,
453
+ skipPredicate: item => this._skipPredicateFn(item),
454
+ });
455
+ this._typeaheadSubscription = this._typeahead.selectedItem.subscribe(item => {
456
+ this.setActiveItem(item);
390
457
  });
391
458
  return this;
392
459
  }
393
460
  /** Cancels the current typeahead sequence. */
394
461
  cancelTypeahead() {
395
- this._pressedLetters = [];
462
+ this._typeahead?.reset();
396
463
  return this;
397
464
  }
398
465
  /**
@@ -504,20 +571,13 @@ class ListKeyManager {
504
571
  }
505
572
  default:
506
573
  if (isModifierAllowed || hasModifierKey(event, 'shiftKey')) {
507
- // Attempt to use the `event.key` which also maps it to the user's keyboard language,
508
- // otherwise fall back to resolving alphanumeric characters via the keyCode.
509
- if (event.key && event.key.length === 1) {
510
- this._letterKeyStream.next(event.key.toLocaleUpperCase());
511
- }
512
- else if ((keyCode >= A && keyCode <= Z) || (keyCode >= ZERO && keyCode <= NINE)) {
513
- this._letterKeyStream.next(String.fromCharCode(keyCode));
514
- }
574
+ this._typeahead?.handleKey(event);
515
575
  }
516
576
  // Note that we return here, in order to avoid preventing
517
577
  // the default action of non-navigational keys.
518
578
  return;
519
579
  }
520
- this._pressedLetters = [];
580
+ this._typeahead?.reset();
521
581
  event.preventDefault();
522
582
  }
523
583
  /** Index of the currently active item. */
@@ -530,7 +590,7 @@ class ListKeyManager {
530
590
  }
531
591
  /** Gets whether the user is currently typing into the manager using the typeahead feature. */
532
592
  isTyping() {
533
- return this._pressedLetters.length > 0;
593
+ return !!this._typeahead && this._typeahead.isTyping();
534
594
  }
535
595
  /** Sets the active item to the first enabled item in the list. */
536
596
  setFirstItemActive() {
@@ -557,16 +617,16 @@ class ListKeyManager {
557
617
  // Explicitly check for `null` and `undefined` because other falsy values are valid.
558
618
  this._activeItem = activeItem == null ? null : activeItem;
559
619
  this._activeItemIndex = index;
620
+ this._typeahead?.setCurrentSelectedItemIndex(index);
560
621
  }
561
622
  /** Cleans up the key manager. */
562
623
  destroy() {
563
624
  this._typeaheadSubscription.unsubscribe();
564
625
  this._itemChangesSubscription?.unsubscribe();
565
626
  this._effectRef?.destroy();
566
- this._letterKeyStream.complete();
627
+ this._typeahead?.destroy();
567
628
  this.tabOut.complete();
568
629
  this.change.complete();
569
- this._pressedLetters = [];
570
630
  }
571
631
  /**
572
632
  * This method sets the active item, given a list of items and the delta between the
@@ -627,10 +687,12 @@ class ListKeyManager {
627
687
  }
628
688
  /** Callback for when the items have changed. */
629
689
  _itemsChanged(newItems) {
690
+ this._typeahead?.setItems(newItems);
630
691
  if (this._activeItem) {
631
692
  const newIndex = newItems.indexOf(this._activeItem);
632
693
  if (newIndex > -1 && newIndex !== this._activeItemIndex) {
633
694
  this._activeItemIndex = newIndex;
695
+ this._typeahead?.setCurrentSelectedItemIndex(newIndex);
634
696
  }
635
697
  }
636
698
  }
@@ -669,6 +731,424 @@ class FocusKeyManager extends ListKeyManager {
669
731
  }
670
732
  }
671
733
 
734
+ /**
735
+ * This class manages keyboard events for trees. If you pass it a QueryList or other list of tree
736
+ * items, it will set the active item, focus, handle expansion and typeahead correctly when
737
+ * keyboard events occur.
738
+ */
739
+ class TreeKeyManager {
740
+ _initialFocus() {
741
+ if (this._hasInitialFocused) {
742
+ return;
743
+ }
744
+ if (!this._items.length) {
745
+ return;
746
+ }
747
+ let focusIndex = 0;
748
+ for (let i = 0; i < this._items.length; i++) {
749
+ if (!this._skipPredicateFn(this._items[i]) && !this._isItemDisabled(this._items[i])) {
750
+ focusIndex = i;
751
+ break;
752
+ }
753
+ }
754
+ this.focusItem(focusIndex);
755
+ this._hasInitialFocused = true;
756
+ }
757
+ /**
758
+ *
759
+ * @param items List of TreeKeyManager options. Can be synchronous or asynchronous.
760
+ * @param config Optional configuration options. By default, use 'ltr' horizontal orientation. By
761
+ * default, do not skip any nodes. By default, key manager only calls `focus` method when items
762
+ * are focused and does not call `activate`. If `typeaheadDefaultInterval` is `true`, use a
763
+ * default interval of 200ms.
764
+ */
765
+ constructor(items, config) {
766
+ /** The index of the currently active (focused) item. */
767
+ this._activeItemIndex = -1;
768
+ /** The currently active (focused) item. */
769
+ this._activeItem = null;
770
+ /** Whether or not we activate the item when it's focused. */
771
+ this._shouldActivationFollowFocus = false;
772
+ /**
773
+ * The orientation that the tree is laid out in. In `rtl` mode, the behavior of Left and
774
+ * Right arrow are switched.
775
+ */
776
+ this._horizontalOrientation = 'ltr';
777
+ /**
778
+ * Predicate function that can be used to check whether an item should be skipped
779
+ * by the key manager.
780
+ *
781
+ * The default value for this doesn't skip any elements in order to keep tree items focusable
782
+ * when disabled. This aligns with ARIA guidelines:
783
+ * https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols.
784
+ */
785
+ this._skipPredicateFn = (_item) => false;
786
+ /** Function to determine equivalent items. */
787
+ this._trackByFn = (item) => item;
788
+ /** Synchronous cache of the items to manage. */
789
+ this._items = [];
790
+ this._typeaheadSubscription = Subscription.EMPTY;
791
+ this._hasInitialFocused = false;
792
+ /** Stream that emits any time the focused item changes. */
793
+ this.change = new Subject();
794
+ // We allow for the items to be an array or Observable because, in some cases, the consumer may
795
+ // not have access to a QueryList of the items they want to manage (e.g. when the
796
+ // items aren't being collected via `ViewChildren` or `ContentChildren`).
797
+ if (items instanceof QueryList) {
798
+ this._items = items.toArray();
799
+ items.changes.subscribe((newItems) => {
800
+ this._items = newItems.toArray();
801
+ this._typeahead?.setItems(this._items);
802
+ this._updateActiveItemIndex(this._items);
803
+ this._initialFocus();
804
+ });
805
+ }
806
+ else if (isObservable(items)) {
807
+ items.subscribe(newItems => {
808
+ this._items = newItems;
809
+ this._typeahead?.setItems(newItems);
810
+ this._updateActiveItemIndex(newItems);
811
+ this._initialFocus();
812
+ });
813
+ }
814
+ else {
815
+ this._items = items;
816
+ this._initialFocus();
817
+ }
818
+ if (typeof config.shouldActivationFollowFocus === 'boolean') {
819
+ this._shouldActivationFollowFocus = config.shouldActivationFollowFocus;
820
+ }
821
+ if (config.horizontalOrientation) {
822
+ this._horizontalOrientation = config.horizontalOrientation;
823
+ }
824
+ if (config.skipPredicate) {
825
+ this._skipPredicateFn = config.skipPredicate;
826
+ }
827
+ if (config.trackBy) {
828
+ this._trackByFn = config.trackBy;
829
+ }
830
+ if (typeof config.typeAheadDebounceInterval !== 'undefined') {
831
+ this._setTypeAhead(config.typeAheadDebounceInterval);
832
+ }
833
+ }
834
+ /** Cleans up the key manager. */
835
+ destroy() {
836
+ this._typeaheadSubscription.unsubscribe();
837
+ this._typeahead?.destroy();
838
+ this.change.complete();
839
+ }
840
+ /**
841
+ * Handles a keyboard event on the tree.
842
+ * @param event Keyboard event that represents the user interaction with the tree.
843
+ */
844
+ onKeydown(event) {
845
+ const key = event.key;
846
+ switch (key) {
847
+ case 'Tab':
848
+ // Return early here, in order to allow Tab to actually tab out of the tree
849
+ return;
850
+ case 'ArrowDown':
851
+ this._focusNextItem();
852
+ break;
853
+ case 'ArrowUp':
854
+ this._focusPreviousItem();
855
+ break;
856
+ case 'ArrowRight':
857
+ this._horizontalOrientation === 'rtl'
858
+ ? this._collapseCurrentItem()
859
+ : this._expandCurrentItem();
860
+ break;
861
+ case 'ArrowLeft':
862
+ this._horizontalOrientation === 'rtl'
863
+ ? this._expandCurrentItem()
864
+ : this._collapseCurrentItem();
865
+ break;
866
+ case 'Home':
867
+ this._focusFirstItem();
868
+ break;
869
+ case 'End':
870
+ this._focusLastItem();
871
+ break;
872
+ case 'Enter':
873
+ case ' ':
874
+ this._activateCurrentItem();
875
+ break;
876
+ default:
877
+ if (event.key === '*') {
878
+ this._expandAllItemsAtCurrentItemLevel();
879
+ break;
880
+ }
881
+ this._typeahead?.handleKey(event);
882
+ // Return here, in order to avoid preventing the default action of non-navigational
883
+ // keys or resetting the buffer of pressed letters.
884
+ return;
885
+ }
886
+ // Reset the typeahead since the user has used a navigational key.
887
+ this._typeahead?.reset();
888
+ event.preventDefault();
889
+ }
890
+ /** Index of the currently active item. */
891
+ getActiveItemIndex() {
892
+ return this._activeItemIndex;
893
+ }
894
+ /** The currently active item. */
895
+ getActiveItem() {
896
+ return this._activeItem;
897
+ }
898
+ /** Focus the first available item. */
899
+ _focusFirstItem() {
900
+ this.focusItem(this._findNextAvailableItemIndex(-1));
901
+ }
902
+ /** Focus the last available item. */
903
+ _focusLastItem() {
904
+ this.focusItem(this._findPreviousAvailableItemIndex(this._items.length));
905
+ }
906
+ /** Focus the next available item. */
907
+ _focusNextItem() {
908
+ this.focusItem(this._findNextAvailableItemIndex(this._activeItemIndex));
909
+ }
910
+ /** Focus the previous available item. */
911
+ _focusPreviousItem() {
912
+ this.focusItem(this._findPreviousAvailableItemIndex(this._activeItemIndex));
913
+ }
914
+ focusItem(itemOrIndex, options = {}) {
915
+ // Set default options
916
+ options.emitChangeEvent ??= true;
917
+ let index = typeof itemOrIndex === 'number'
918
+ ? itemOrIndex
919
+ : this._items.findIndex(item => this._trackByFn(item) === this._trackByFn(itemOrIndex));
920
+ if (index < 0 || index >= this._items.length) {
921
+ return;
922
+ }
923
+ const activeItem = this._items[index];
924
+ // If we're just setting the same item, don't re-call activate or focus
925
+ if (this._activeItem !== null &&
926
+ this._trackByFn(activeItem) === this._trackByFn(this._activeItem)) {
927
+ return;
928
+ }
929
+ const previousActiveItem = this._activeItem;
930
+ this._activeItem = activeItem ?? null;
931
+ this._activeItemIndex = index;
932
+ this._typeahead?.setCurrentSelectedItemIndex(index);
933
+ this._activeItem?.focus();
934
+ previousActiveItem?.unfocus();
935
+ if (options.emitChangeEvent) {
936
+ this.change.next(this._activeItem);
937
+ }
938
+ if (this._shouldActivationFollowFocus) {
939
+ this._activateCurrentItem();
940
+ }
941
+ }
942
+ _updateActiveItemIndex(newItems) {
943
+ const activeItem = this._activeItem;
944
+ if (!activeItem) {
945
+ return;
946
+ }
947
+ const newIndex = newItems.findIndex(item => this._trackByFn(item) === this._trackByFn(activeItem));
948
+ if (newIndex > -1 && newIndex !== this._activeItemIndex) {
949
+ this._activeItemIndex = newIndex;
950
+ this._typeahead?.setCurrentSelectedItemIndex(newIndex);
951
+ }
952
+ }
953
+ _setTypeAhead(debounceInterval) {
954
+ this._typeahead = new Typeahead(this._items, {
955
+ debounceInterval: typeof debounceInterval === 'number' ? debounceInterval : undefined,
956
+ skipPredicate: item => this._skipPredicateFn(item),
957
+ });
958
+ this._typeaheadSubscription = this._typeahead.selectedItem.subscribe(item => {
959
+ this.focusItem(item);
960
+ });
961
+ }
962
+ _findNextAvailableItemIndex(startingIndex) {
963
+ for (let i = startingIndex + 1; i < this._items.length; i++) {
964
+ if (!this._skipPredicateFn(this._items[i])) {
965
+ return i;
966
+ }
967
+ }
968
+ return startingIndex;
969
+ }
970
+ _findPreviousAvailableItemIndex(startingIndex) {
971
+ for (let i = startingIndex - 1; i >= 0; i--) {
972
+ if (!this._skipPredicateFn(this._items[i])) {
973
+ return i;
974
+ }
975
+ }
976
+ return startingIndex;
977
+ }
978
+ /**
979
+ * If the item is already expanded, we collapse the item. Otherwise, we will focus the parent.
980
+ */
981
+ _collapseCurrentItem() {
982
+ if (!this._activeItem) {
983
+ return;
984
+ }
985
+ if (this._isCurrentItemExpanded()) {
986
+ this._activeItem.collapse();
987
+ }
988
+ else {
989
+ const parent = this._activeItem.getParent();
990
+ if (!parent || this._skipPredicateFn(parent)) {
991
+ return;
992
+ }
993
+ this.focusItem(parent);
994
+ }
995
+ }
996
+ /**
997
+ * If the item is already collapsed, we expand the item. Otherwise, we will focus the first child.
998
+ */
999
+ _expandCurrentItem() {
1000
+ if (!this._activeItem) {
1001
+ return;
1002
+ }
1003
+ if (!this._isCurrentItemExpanded()) {
1004
+ this._activeItem.expand();
1005
+ }
1006
+ else {
1007
+ coerceObservable(this._activeItem.getChildren())
1008
+ .pipe(take(1))
1009
+ .subscribe(children => {
1010
+ const firstChild = children.find(child => !this._skipPredicateFn(child));
1011
+ if (!firstChild) {
1012
+ return;
1013
+ }
1014
+ this.focusItem(firstChild);
1015
+ });
1016
+ }
1017
+ }
1018
+ _isCurrentItemExpanded() {
1019
+ if (!this._activeItem) {
1020
+ return false;
1021
+ }
1022
+ return typeof this._activeItem.isExpanded === 'boolean'
1023
+ ? this._activeItem.isExpanded
1024
+ : this._activeItem.isExpanded();
1025
+ }
1026
+ _isItemDisabled(item) {
1027
+ return typeof item.isDisabled === 'boolean' ? item.isDisabled : item.isDisabled?.();
1028
+ }
1029
+ /** For all items that are the same level as the current item, we expand those items. */
1030
+ _expandAllItemsAtCurrentItemLevel() {
1031
+ if (!this._activeItem) {
1032
+ return;
1033
+ }
1034
+ const parent = this._activeItem.getParent();
1035
+ let itemsToExpand;
1036
+ if (!parent) {
1037
+ itemsToExpand = of(this._items.filter(item => item.getParent() === null));
1038
+ }
1039
+ else {
1040
+ itemsToExpand = coerceObservable(parent.getChildren());
1041
+ }
1042
+ itemsToExpand.pipe(take(1)).subscribe(items => {
1043
+ for (const item of items) {
1044
+ item.expand();
1045
+ }
1046
+ });
1047
+ }
1048
+ _activateCurrentItem() {
1049
+ this._activeItem?.activate();
1050
+ }
1051
+ }
1052
+ /** @docs-private */
1053
+ function TREE_KEY_MANAGER_FACTORY() {
1054
+ return (items, options) => new TreeKeyManager(items, options);
1055
+ }
1056
+ /** Injection token that determines the key manager to use. */
1057
+ const TREE_KEY_MANAGER = new InjectionToken('tree-key-manager', {
1058
+ providedIn: 'root',
1059
+ factory: TREE_KEY_MANAGER_FACTORY,
1060
+ });
1061
+ /** @docs-private */
1062
+ const TREE_KEY_MANAGER_FACTORY_PROVIDER = {
1063
+ provide: TREE_KEY_MANAGER,
1064
+ useFactory: TREE_KEY_MANAGER_FACTORY,
1065
+ };
1066
+
1067
+ // NoopTreeKeyManager is a "noop" implementation of TreeKeyMangerStrategy. Methods are noops. Does
1068
+ // not emit to streams.
1069
+ //
1070
+ // Used for applications built before TreeKeyManager to opt-out of TreeKeyManager and revert to
1071
+ // legacy behavior.
1072
+ /**
1073
+ * @docs-private
1074
+ *
1075
+ * Opt-out of Tree of key manager behavior.
1076
+ *
1077
+ * When provided, Tree has same focus management behavior as before TreeKeyManager was introduced.
1078
+ * - Tree does not respond to keyboard interaction
1079
+ * - Tree node allows tabindex to be set by Input binding
1080
+ * - Tree node allows tabindex to be set by attribute binding
1081
+ *
1082
+ * @deprecated NoopTreeKeyManager deprecated. Use TreeKeyManager or inject a
1083
+ * TreeKeyManagerStrategy instead. To be removed in a future version.
1084
+ *
1085
+ * @breaking-change 21.0.0
1086
+ */
1087
+ class NoopTreeKeyManager {
1088
+ constructor() {
1089
+ this._isNoopTreeKeyManager = true;
1090
+ // Provide change as required by TreeKeyManagerStrategy. NoopTreeKeyManager is a "noop"
1091
+ // implementation that does not emit to streams.
1092
+ this.change = new Subject();
1093
+ }
1094
+ destroy() {
1095
+ this.change.complete();
1096
+ }
1097
+ onKeydown() {
1098
+ // noop
1099
+ }
1100
+ getActiveItemIndex() {
1101
+ // Always return null. NoopTreeKeyManager is a "noop" implementation that does not maintain
1102
+ // the active item.
1103
+ return null;
1104
+ }
1105
+ getActiveItem() {
1106
+ // Always return null. NoopTreeKeyManager is a "noop" implementation that does not maintain
1107
+ // the active item.
1108
+ return null;
1109
+ }
1110
+ focusItem() {
1111
+ // noop
1112
+ }
1113
+ }
1114
+ /**
1115
+ * @docs-private
1116
+ *
1117
+ * Opt-out of Tree of key manager behavior.
1118
+ *
1119
+ * When provided, Tree has same focus management behavior as before TreeKeyManager was introduced.
1120
+ * - Tree does not respond to keyboard interaction
1121
+ * - Tree node allows tabindex to be set by Input binding
1122
+ * - Tree node allows tabindex to be set by attribute binding
1123
+ *
1124
+ * @deprecated NoopTreeKeyManager deprecated. Use TreeKeyManager or inject a
1125
+ * TreeKeyManagerStrategy instead. To be removed in a future version.
1126
+ *
1127
+ * @breaking-change 21.0.0
1128
+ */
1129
+ function NOOP_TREE_KEY_MANAGER_FACTORY() {
1130
+ return () => new NoopTreeKeyManager();
1131
+ }
1132
+ /**
1133
+ * @docs-private
1134
+ *
1135
+ * Opt-out of Tree of key manager behavior.
1136
+ *
1137
+ * When provided, Tree has same focus management behavior as before TreeKeyManager was introduced.
1138
+ * - Tree does not respond to keyboard interaction
1139
+ * - Tree node allows tabindex to be set by Input binding
1140
+ * - Tree node allows tabindex to be set by attribute binding
1141
+ *
1142
+ * @deprecated NoopTreeKeyManager deprecated. Use TreeKeyManager or inject a
1143
+ * TreeKeyManagerStrategy instead. To be removed in a future version.
1144
+ *
1145
+ * @breaking-change 21.0.0
1146
+ */
1147
+ const NOOP_TREE_KEY_MANAGER_FACTORY_PROVIDER = {
1148
+ provide: TREE_KEY_MANAGER,
1149
+ useFactory: NOOP_TREE_KEY_MANAGER_FACTORY,
1150
+ };
1151
+
672
1152
  /**
673
1153
  * Configuration for the isFocusable method.
674
1154
  */
@@ -2424,5 +2904,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImpor
2424
2904
  * Generated bundle index. Do not edit.
2425
2905
  */
2426
2906
 
2427
- export { A11yModule, ActiveDescendantKeyManager, AriaDescriber, CDK_DESCRIBEDBY_HOST_ATTRIBUTE, CDK_DESCRIBEDBY_ID_PREFIX, CdkAriaLive, CdkMonitorFocus, CdkTrapFocus, ConfigurableFocusTrap, ConfigurableFocusTrapFactory, EventListenerFocusTrapInertStrategy, FOCUS_MONITOR_DEFAULT_OPTIONS, FOCUS_TRAP_INERT_STRATEGY, FocusKeyManager, FocusMonitor, FocusMonitorDetectionMode, FocusTrap, FocusTrapFactory, HighContrastMode, HighContrastModeDetector, INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS, INPUT_MODALITY_DETECTOR_OPTIONS, InputModalityDetector, InteractivityChecker, IsFocusableConfig, LIVE_ANNOUNCER_DEFAULT_OPTIONS, LIVE_ANNOUNCER_ELEMENT_TOKEN, LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY, ListKeyManager, LiveAnnouncer, MESSAGES_CONTAINER_ID, addAriaReferencedId, getAriaReferenceIds, isFakeMousedownFromScreenReader, isFakeTouchstartFromScreenReader, removeAriaReferencedId };
2907
+ export { A11yModule, ActiveDescendantKeyManager, AriaDescriber, CDK_DESCRIBEDBY_HOST_ATTRIBUTE, CDK_DESCRIBEDBY_ID_PREFIX, CdkAriaLive, CdkMonitorFocus, CdkTrapFocus, ConfigurableFocusTrap, ConfigurableFocusTrapFactory, EventListenerFocusTrapInertStrategy, FOCUS_MONITOR_DEFAULT_OPTIONS, FOCUS_TRAP_INERT_STRATEGY, FocusKeyManager, FocusMonitor, FocusMonitorDetectionMode, FocusTrap, FocusTrapFactory, HighContrastMode, HighContrastModeDetector, INPUT_MODALITY_DETECTOR_DEFAULT_OPTIONS, INPUT_MODALITY_DETECTOR_OPTIONS, InputModalityDetector, InteractivityChecker, IsFocusableConfig, LIVE_ANNOUNCER_DEFAULT_OPTIONS, LIVE_ANNOUNCER_ELEMENT_TOKEN, LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY, ListKeyManager, LiveAnnouncer, MESSAGES_CONTAINER_ID, NOOP_TREE_KEY_MANAGER_FACTORY, NOOP_TREE_KEY_MANAGER_FACTORY_PROVIDER, NoopTreeKeyManager, TREE_KEY_MANAGER, TREE_KEY_MANAGER_FACTORY, TREE_KEY_MANAGER_FACTORY_PROVIDER, TreeKeyManager, addAriaReferencedId, getAriaReferenceIds, isFakeMousedownFromScreenReader, isFakeTouchstartFromScreenReader, removeAriaReferencedId };
2428
2908
  //# sourceMappingURL=a11y.mjs.map