@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.
- package/a11y/index.d.ts +283 -2
- package/coercion/private/index.d.ts +9 -0
- package/drag-drop/index.d.ts +12 -1
- package/esm2022/a11y/key-manager/list-key-manager.mjs +18 -38
- package/esm2022/a11y/key-manager/noop-tree-key-manager.mjs +94 -0
- package/esm2022/a11y/key-manager/tree-key-manager-strategy.mjs +9 -0
- package/esm2022/a11y/key-manager/tree-key-manager.mjs +345 -0
- package/esm2022/a11y/key-manager/typeahead.mjs +91 -0
- package/esm2022/a11y/public-api.mjs +4 -1
- package/esm2022/coercion/private/index.mjs +9 -0
- package/esm2022/coercion/private/observable.mjs +19 -0
- package/esm2022/coercion/private/private_public_index.mjs +5 -0
- package/esm2022/drag-drop/directives/drag.mjs +16 -3
- package/esm2022/drag-drop/drag-ref.mjs +8 -2
- package/esm2022/drag-drop/sorting/single-axis-sort-strategy.mjs +4 -3
- package/esm2022/tree/control/base-tree-control.mjs +7 -2
- package/esm2022/tree/control/flat-tree-control.mjs +8 -2
- package/esm2022/tree/control/nested-tree-control.mjs +11 -2
- package/esm2022/tree/control/tree-control.mjs +1 -1
- package/esm2022/tree/nested-node.mjs +6 -15
- package/esm2022/tree/padding.mjs +2 -4
- package/esm2022/tree/toggle.mjs +15 -8
- package/esm2022/tree/tree-errors.mjs +7 -6
- package/esm2022/tree/tree.mjs +817 -63
- package/esm2022/version.mjs +1 -1
- package/fesm2022/a11y.mjs +520 -40
- package/fesm2022/a11y.mjs.map +1 -1
- package/fesm2022/cdk.mjs +1 -1
- package/fesm2022/cdk.mjs.map +1 -1
- package/fesm2022/coercion/private.mjs +19 -0
- package/fesm2022/coercion/private.mjs.map +1 -0
- package/fesm2022/drag-drop.mjs +25 -5
- package/fesm2022/drag-drop.mjs.map +1 -1
- package/fesm2022/tree.mjs +858 -94
- package/fesm2022/tree.mjs.map +1 -1
- package/package.json +9 -3
- package/schematics/ng-add/index.js +1 -1
- package/schematics/ng-add/index.mjs +1 -1
- package/tree/index.d.ts +304 -25
package/esm2022/version.mjs
CHANGED
|
@@ -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.
|
|
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,
|
|
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,
|
|
7
|
-
import {
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|