ng-virtual-list 14.7.13 → 14.7.15

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.
@@ -82,6 +82,20 @@ var SnappingMethods;
82
82
  SnappingMethods["ADVANCED"] = "advanced";
83
83
  })(SnappingMethods || (SnappingMethods = {}));
84
84
 
85
+ /**
86
+ * Focus Alignments.
87
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/14.x/projects/ng-virtual-list/src/lib/enums/focus-alignments.ts
88
+ * @author Evgenii Grebennikov
89
+ * @email djonnyx@gmail.com
90
+ */
91
+ var FocusAlignments;
92
+ (function (FocusAlignments) {
93
+ FocusAlignments["NONE"] = "none";
94
+ FocusAlignments["START"] = "start";
95
+ FocusAlignments["CENTER"] = "center";
96
+ FocusAlignments["END"] = "end";
97
+ })(FocusAlignments || (FocusAlignments = {}));
98
+
85
99
  const DEFAULT_ITEM_SIZE = 24;
86
100
  const DEFAULT_BUFFER_SIZE = 2;
87
101
  const DEFAULT_MAX_BUFFER_SIZE = 10;
@@ -98,6 +112,7 @@ const DISPLAY_OBJECTS_LENGTH_MESUREMENT_ERROR = 1;
98
112
  const MAX_SCROLL_TO_ITERATIONS = 5;
99
113
  const DEFAULT_SNAPPING_METHOD = SnappingMethods.NORMAL;
100
114
  const DEFAULT_SELECT_METHOD = MethodsForSelecting.NONE;
115
+ const DEFAULT_SCREEN_READER_MESSAGE = 'Showing items $1 to $2';
101
116
  // presets
102
117
  const BEHAVIOR_AUTO = 'auto';
103
118
  const BEHAVIOR_INSTANT = 'instant';
@@ -241,7 +256,9 @@ class NgVirtualListService {
241
256
  this.selectByClick = DEFAULT_SELECT_BY_CLICK;
242
257
  this.collapseByClick = DEFAULT_COLLAPSE_BY_CLICK;
243
258
  this.listElement = null;
244
- this.collection = [];
259
+ this._$displayItems = new BehaviorSubject([]);
260
+ this.$displayItems = this._$displayItems.asObservable();
261
+ this._collection = [];
245
262
  this._$methodOfSelecting.pipe(takeUntil(this._$unsubscribe), tap(v => {
246
263
  switch (v) {
247
264
  case MethodsForSelectingTypes.SELECT: {
@@ -269,6 +286,14 @@ class NgVirtualListService {
269
286
  this._$methodOfSelecting.next(v);
270
287
  }
271
288
  get focusedId() { return this._$focusedId.getValue(); }
289
+ set collection(v) {
290
+ if (this._collection === v) {
291
+ return;
292
+ }
293
+ this._collection = v;
294
+ this._$displayItems.next(v);
295
+ }
296
+ get collection() { return this._collection; }
272
297
  setSelectedIds(ids) {
273
298
  if (JSON.stringify(this._$selectedIds.getValue()) !== JSON.stringify(ids)) {
274
299
  this._$selectedIds.next(ids);
@@ -385,11 +410,11 @@ class NgVirtualListService {
385
410
  initialize(trackBox) {
386
411
  this._trackBox = trackBox;
387
412
  }
388
- focus(element) {
413
+ focus(element, align = FocusAlignments.CENTER) {
389
414
  element.focus({ preventScroll: true });
390
415
  if (element.parentElement) {
391
416
  const pos = parseFloat(element.parentElement?.getAttribute('position') ?? '0');
392
- this.itemToFocus?.(element, pos);
417
+ this.itemToFocus?.(element, pos, align);
393
418
  }
394
419
  }
395
420
  areaFocus(id) {
@@ -415,7 +440,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImpor
415
440
  }]
416
441
  }], ctorParameters: function () { return []; } });
417
442
 
418
- const ATTR_AREA_SELECTED = 'area-selected', TABINDEX = 'ng-vl-index', KEY_SPACE = " ", KEY_ARR_LEFT = "ArrowLeft", KEY_ARR_UP = "ArrowUp", KEY_ARR_RIGHT = "ArrowRight", KEY_ARR_DOWN = "ArrowDown", EVENT_FOCUS_IN = 'focusin', EVENT_FOCUS_OUT = 'focusout', EVENT_KEY_DOWN = 'keydown';
443
+ const ATTR_AREA_SELECTED = 'area-selected', TABINDEX = 'ng-vl-index', POSITION = 'position', POSITION_ZERO = '0', ID = 'item-id', KEY_SPACE = " ", KEY_ARR_LEFT = "ArrowLeft", KEY_ARR_UP = "ArrowUp", KEY_ARR_RIGHT = "ArrowRight", KEY_ARR_DOWN = "ArrowDown", EVENT_FOCUS_IN = 'focusin', EVENT_FOCUS_OUT = 'focusout', EVENT_KEY_DOWN = 'keydown';
419
444
  const getElementByIndex = (index) => {
420
445
  return `[${TABINDEX}="${index}"]`;
421
446
  };
@@ -438,8 +463,8 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
438
463
  this._$config = new BehaviorSubject$1({});
439
464
  this.$config = this._$config.asObservable();
440
465
  this.measures = new BehaviorSubject$1(undefined);
441
- this._$focus = new BehaviorSubject$1(false);
442
- this.$focus = this._$focus.asObservable();
466
+ this._$focused = new BehaviorSubject$1(false);
467
+ this.$focused = this._$focused.asObservable();
443
468
  this._$part = new BehaviorSubject$1(PART_DEFAULT_ITEM);
444
469
  this.$part = this._$part.asObservable();
445
470
  this.regular = false;
@@ -472,19 +497,26 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
472
497
  }
473
498
  this._service.collapse(data, collapsed);
474
499
  };
500
+ this._focusHandler = () =>
501
+ /**
502
+ * Focus a list item
503
+ */
504
+ (align = FocusAlignments.CENTER) => {
505
+ this.focus(align);
506
+ };
475
507
  this._id = this._service.generateComponentId();
476
- const $data = this.$data, $focus = this.$focus;
508
+ const $data = this.$data, $focus = this.$focused;
477
509
  this._elementRef.nativeElement.setAttribute('id', String(this._id));
478
510
  $focus.pipe(takeUntil$1(this._$unsubscribe), tap$1(v => {
479
511
  this._service.areaFocus(v ? this._id : this._service.focusedId === this._id ? null : this._service.focusedId);
480
512
  })).subscribe();
481
513
  fromEvent(this.element, EVENT_FOCUS_IN).pipe(takeUntil$1(this._$unsubscribe), tap$1(e => {
482
- this._$focus.next(true);
514
+ this._$focused.next(true);
483
515
  this.updateConfig(this.data);
484
516
  this.updatePartStr(this.data, this._isSelected, this._isCollapsed);
485
517
  })).subscribe(),
486
518
  fromEvent(this.element, EVENT_FOCUS_OUT).pipe(takeUntil$1(this._$unsubscribe), tap$1(e => {
487
- this._$focus.next(false);
519
+ this._$focused.next(false);
488
520
  this.updateConfig(this.data);
489
521
  this.updatePartStr(this.data, this._isSelected, this._isCollapsed);
490
522
  })).subscribe(),
@@ -627,18 +659,29 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
627
659
  }
628
660
  }
629
661
  }
662
+ focus(align = FocusAlignments.CENTER) {
663
+ if (this._service.listElement) {
664
+ const tabIndex = this.data?.config?.tabIndex ?? 0;
665
+ let index = tabIndex;
666
+ const el = this._service.listElement.querySelector(getElementByIndex(index));
667
+ if (el) {
668
+ this._service.focus(el, align);
669
+ }
670
+ }
671
+ }
630
672
  updateMeasures(v) {
631
673
  this.measures.next(v?.measures ? { ...v.measures } : undefined);
632
674
  }
633
675
  updateConfig(v) {
634
676
  this._$config.next({
635
- ...v?.config || {}, selected: this._isSelected, collapsed: this._isCollapsed, focus: this._$focus.getValue(),
636
- collapse: this._collapseHandler(v), select: this._selectHandler(v)
677
+ ...v?.config || {}, selected: this._isSelected, collapsed: this._isCollapsed, focused: this._$focused.getValue(),
678
+ collapse: this._collapseHandler(v), select: this._selectHandler(v), focus: this._focusHandler(),
637
679
  });
638
680
  }
639
681
  update() {
640
682
  const data = this.data, regular = this.regular, length = this._regularLength;
641
683
  if (data) {
684
+ this._elementRef.nativeElement.setAttribute(ID, `${data.id}`);
642
685
  const styles = this._elementRef.nativeElement.style;
643
686
  styles.zIndex = data.config.zIndex;
644
687
  if (data.config.snapped) {
@@ -662,6 +705,9 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
662
705
  styles.height = data.config.isVertical ? data.config.dynamic ? SIZE_AUTO : `${data.measures.height}${PX}` : regular ? length : SIZE_100_PERSENT;
663
706
  styles.width = data.config.isVertical ? regular ? length : SIZE_100_PERSENT : data.config.dynamic ? SIZE_AUTO : `${data.measures.width}${PX}`;
664
707
  }
708
+ else {
709
+ this._elementRef.nativeElement.removeAttribute(ID);
710
+ }
665
711
  this._cdr.markForCheck();
666
712
  }
667
713
  updatePartStr(v, isSelected, isCollapsed) {
@@ -683,7 +729,7 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
683
729
  if (v ? v.config.new : false) {
684
730
  part += PART_ITEM_NEW;
685
731
  }
686
- if (this._$focus.getValue()) {
732
+ if (this._$focused.getValue()) {
687
733
  part += PART_ITEM_FOCUSED;
688
734
  }
689
735
  this._$part.next(part);
@@ -737,13 +783,13 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
737
783
  }
738
784
  }
739
785
  NgVirtualListItemComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgVirtualListItemComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: NgVirtualListService }], target: i0.ɵɵFactoryTarget.Component });
740
- NgVirtualListItemComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NgVirtualListItemComponent, selector: "ng-virtual-list-item", host: { attributes: { "role": "listitem" }, classAttribute: "ngvl__item" }, usesInheritance: true, ngImport: i0, template: "<ng-container *ngIf=\"data\">\r\n <div #listItem [part]=\"$part | async\" [attr.ng-vl-index]=\"data.config.tabIndex || -1\" tabindex=\"0\" class=\"ngvl-item__container\" [ngClass]=\"{'snapped': data.config.snapped,\r\n 'snapped-out': data.config.snappedOut, 'focus': $focus | async}\" (click)=\"onClickHandler()\">\r\n <ng-container *ngIf=\"itemRenderer\">\r\n <ng-container [ngTemplateOutlet]=\"itemRenderer\"\r\n [ngTemplateOutletContext]=\"{data: data.data || {}, measures: measures | async, config: $config | async}\"></ng-container>\r\n </ng-container>\r\n </div>\r\n</ng-container>", styles: [":host{display:block;position:absolute;left:0;top:0;box-sizing:border-box;overflow:hidden}.ngvl-item__container{margin:0;padding:0;overflow:hidden;background-color:#fff;width:inherit;height:inherit;box-sizing:border-box}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
786
+ NgVirtualListItemComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NgVirtualListItemComponent, selector: "ng-virtual-list-item", host: { attributes: { "role": "listitem" }, classAttribute: "ngvl__item" }, usesInheritance: true, ngImport: i0, template: "<ng-container *ngIf=\"data\">\r\n <div #listItem [part]=\"$part | async\" [attr.ng-vl-index]=\"data.config.tabIndex || -1\" tabindex=\"0\" class=\"ngvl-item__container\" [ngClass]=\"{'snapped': data.config.snapped,\r\n 'snapped-out': data.config.snappedOut, 'focus': $focused | async}\" (click)=\"onClickHandler()\">\r\n <ng-container *ngIf=\"itemRenderer\">\r\n <ng-container [ngTemplateOutlet]=\"itemRenderer\"\r\n [ngTemplateOutletContext]=\"{data: data.data || {}, measures: measures | async, config: $config | async}\"></ng-container>\r\n </ng-container>\r\n </div>\r\n</ng-container>", styles: [":host{display:block;position:absolute;left:0;top:0;box-sizing:border-box;overflow:hidden}.ngvl-item__container{margin:0;padding:0;overflow:hidden;background-color:#fff;width:inherit;height:inherit;box-sizing:border-box}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
741
787
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgVirtualListItemComponent, decorators: [{
742
788
  type: Component,
743
789
  args: [{ selector: 'ng-virtual-list-item', host: {
744
790
  'class': 'ngvl__item',
745
791
  'role': 'listitem',
746
- }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *ngIf=\"data\">\r\n <div #listItem [part]=\"$part | async\" [attr.ng-vl-index]=\"data.config.tabIndex || -1\" tabindex=\"0\" class=\"ngvl-item__container\" [ngClass]=\"{'snapped': data.config.snapped,\r\n 'snapped-out': data.config.snappedOut, 'focus': $focus | async}\" (click)=\"onClickHandler()\">\r\n <ng-container *ngIf=\"itemRenderer\">\r\n <ng-container [ngTemplateOutlet]=\"itemRenderer\"\r\n [ngTemplateOutletContext]=\"{data: data.data || {}, measures: measures | async, config: $config | async}\"></ng-container>\r\n </ng-container>\r\n </div>\r\n</ng-container>", styles: [":host{display:block;position:absolute;left:0;top:0;box-sizing:border-box;overflow:hidden}.ngvl-item__container{margin:0;padding:0;overflow:hidden;background-color:#fff;width:inherit;height:inherit;box-sizing:border-box}\n"] }]
792
+ }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *ngIf=\"data\">\r\n <div #listItem [part]=\"$part | async\" [attr.ng-vl-index]=\"data.config.tabIndex || -1\" tabindex=\"0\" class=\"ngvl-item__container\" [ngClass]=\"{'snapped': data.config.snapped,\r\n 'snapped-out': data.config.snappedOut, 'focus': $focused | async}\" (click)=\"onClickHandler()\">\r\n <ng-container *ngIf=\"itemRenderer\">\r\n <ng-container [ngTemplateOutlet]=\"itemRenderer\"\r\n [ngTemplateOutletContext]=\"{data: data.data || {}, measures: measures | async, config: $config | async}\"></ng-container>\r\n </ng-container>\r\n </div>\r\n</ng-container>", styles: [":host{display:block;position:absolute;left:0;top:0;box-sizing:border-box;overflow:hidden}.ngvl-item__container{margin:0;padding:0;overflow:hidden;background-color:#fff;width:inherit;height:inherit;box-sizing:border-box}\n"] }]
747
793
  }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: NgVirtualListService }]; } });
748
794
 
749
795
  /**
@@ -2202,7 +2248,7 @@ const isCollectionMode = (src, expected) => {
2202
2248
  return NORMAL_ALIASES.includes(src);
2203
2249
  };
2204
2250
 
2205
- const ROLE_LIST = 'list', ROLE_LIST_BOX = 'listbox';
2251
+ const ROLE_LIST = 'list', ROLE_LIST_BOX = 'listbox', ITEM_ID = 'item-id', ITEM_CONTAINER = 'ngvl-item__container';
2206
2252
  const validateScrollIteration = (value) => {
2207
2253
  return Number.isNaN(value) || (value < 0) ? 0 : value > MAX_SCROLL_TO_ITERATIONS ? MAX_SCROLL_TO_ITERATIONS : value;
2208
2254
  }, validateId = (id) => {
@@ -2221,6 +2267,35 @@ const validateScrollIteration = (value) => {
2221
2267
  if (!valid) {
2222
2268
  throw Error('The "iteration" parameter must be of type `number`.');
2223
2269
  }
2270
+ }, validateFocusAlignment = (align) => {
2271
+ const valid = validateString(align) && (align === 'none' || align === 'start' || align === 'center' || align === 'end');
2272
+ if (!valid) {
2273
+ throw Error('The "align" parameter must have the value `none`, `start`, `center` or `end`.');
2274
+ }
2275
+ };
2276
+ const formatScreenReaderMessage = (items, messagePattern, scrollSize, isVertical, bounds) => {
2277
+ if (!messagePattern) {
2278
+ return '';
2279
+ }
2280
+ const list = items ?? [], size = isVertical ? bounds.height : bounds.width;
2281
+ let start = Number.NaN, end = Number.NaN, prevItem;
2282
+ for (let i = 0, l = list.length; i < l; i++) {
2283
+ const item = list[i], position = isVertical ? item.measures.y : item.measures.x, itemSize = isVertical ? item.measures.height : item.measures.width;
2284
+ if (((position + itemSize) >= scrollSize) && Number.isNaN(start)) {
2285
+ start = item.index + 1;
2286
+ }
2287
+ if ((position >= (scrollSize + size)) && Number.isNaN(end) && prevItem) {
2288
+ end = prevItem.index + 1;
2289
+ }
2290
+ prevItem = item;
2291
+ }
2292
+ if (Number.isNaN(start) || Number.isNaN(end)) {
2293
+ return '';
2294
+ }
2295
+ let formatted = messagePattern ?? '';
2296
+ formatted = formatted.replace('$1', `${start}`);
2297
+ formatted = formatted.replace('$2', `${end}`);
2298
+ return formatted;
2224
2299
  };
2225
2300
  /**
2226
2301
  * Virtual list component.
@@ -2508,6 +2583,18 @@ class NgVirtualListComponent extends DisposableComponent {
2508
2583
  }
2509
2584
  return v;
2510
2585
  };
2586
+ this._$screenReaderMessage = new BehaviorSubject$1(DEFAULT_SCREEN_READER_MESSAGE);
2587
+ this.$screenReaderMessage = this._$screenReaderMessage.asObservable();
2588
+ this._screenReaderMessageTransform = (v) => {
2589
+ const valid = validateString(v);
2590
+ if (!valid) {
2591
+ console.error('The "screenReaderMessage" parameter must be of type `string`.');
2592
+ return DEFAULT_SCREEN_READER_MESSAGE;
2593
+ }
2594
+ return v;
2595
+ };
2596
+ this._$screenReaderFormattedMessage = new BehaviorSubject$1(DEFAULT_SCREEN_READER_MESSAGE);
2597
+ this.$screenReaderFormattedMessage = this._$screenReaderFormattedMessage.asObservable();
2511
2598
  this._isVertical = this.getIsVertical();
2512
2599
  this._isLazy = this.getIsLazy();
2513
2600
  this._$focusedElement = new BehaviorSubject$1(undefined);
@@ -2577,12 +2664,33 @@ class NgVirtualListComponent extends DisposableComponent {
2577
2664
  this._$scrollSize.next(scrollSize);
2578
2665
  }
2579
2666
  };
2580
- this.itemToFocus = (element, position) => {
2667
+ this.itemToFocus = (element, position, align = FocusAlignments.CENTER) => {
2581
2668
  const container = this._container?.nativeElement;
2582
2669
  if (container) {
2583
- const { width, height } = this._$bounds.getValue(), { width: elementWidth, height: elementHeight } = element.getBoundingClientRect(), isVertical = this._isVertical, pos = isVertical ? position - (height - elementHeight) * .5 : position - (width - elementWidth) * .5;
2584
- const params = { [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: pos, behavior: 'instant' };
2585
- container.scrollTo(params);
2670
+ const { width, height } = this._$bounds.getValue(), { width: elementWidth, height: elementHeight } = element.getBoundingClientRect(), isVertical = this._isVertical;
2671
+ let pos = Number.NaN;
2672
+ switch (align) {
2673
+ case FocusAlignments.START: {
2674
+ pos = isVertical ? position : position;
2675
+ break;
2676
+ }
2677
+ case FocusAlignments.CENTER: {
2678
+ pos = isVertical ? position - (height - elementHeight) * .5 : position - (width - elementWidth) * .5;
2679
+ break;
2680
+ }
2681
+ case FocusAlignments.END: {
2682
+ pos = isVertical ? position - (height - elementHeight) : position - (width - elementWidth);
2683
+ break;
2684
+ }
2685
+ case FocusAlignments.NONE:
2686
+ default: {
2687
+ break;
2688
+ }
2689
+ }
2690
+ if (!Number.isNaN(pos)) {
2691
+ const params = { [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: pos, behavior: 'instant' };
2692
+ container.scrollTo(params);
2693
+ }
2586
2694
  }
2587
2695
  };
2588
2696
  this._$initialized = new BehaviorSubject$1(false);
@@ -2681,7 +2789,10 @@ class NgVirtualListComponent extends DisposableComponent {
2681
2789
  $trackBy.pipe(takeUntil(this._$unsubscribe), tap(v => {
2682
2790
  this._trackBox.trackingPropertyName = v;
2683
2791
  })).subscribe();
2684
- const $bounds = this._$bounds.asObservable().pipe(filter(b => !!b)), $items = this.$items.pipe(map$1(i => !i ? [] : i)), $scrollSize = this._$scrollSize.asObservable(), $itemSize = this.$itemSize.pipe(map$1(v => v <= 0 ? DEFAULT_ITEM_SIZE : v)), $bufferSize = this.$bufferSize.pipe(map$1(v => v < 0 ? DEFAULT_BUFFER_SIZE : v)), $maxBufferSize = this.$maxBufferSize.pipe(map$1(v => v < 0 ? DEFAULT_MAX_BUFFER_SIZE : v)), $itemConfigMap = this.$itemConfigMap.pipe(map$1(v => !v ? {} : v)), $snap = this.$snap, $isVertical = this.$direction.pipe(map$1(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $isLazy = this.$collectionMode.pipe(map$1(v => this.getIsLazy(v || DEFAULT_COLLECTION_MODE))), $dynamicSize = this.$dynamicSize, $enabledBufferOptimization = this.$enabledBufferOptimization, $snappingMethod = this.$snappingMethod.pipe(map$1(v => this.getIsSnappingMethodAdvanced(v || DEFAULT_SNAPPING_METHOD))), $methodForSelecting = this.$methodForSelecting, $selectedIds = this.$selectedIds, $collapsedIds = this.$collapsedIds.pipe(map$1(v => Array.isArray(v) ? v : [])), $collapsedItemIds = this._$collapsedItemIds.asObservable().pipe(map$1(v => Array.isArray(v) ? v : [])), $actualItems = this._$actualItems.asObservable(), $cacheVersion = this.$cacheVersion;
2792
+ const $bounds = this._$bounds.asObservable().pipe(filter(b => !!b)), $items = this.$items.pipe(map$1(i => !i ? [] : i)), $scrollSize = this._$scrollSize.asObservable(), $itemSize = this.$itemSize.pipe(map$1(v => v <= 0 ? DEFAULT_ITEM_SIZE : v)), $bufferSize = this.$bufferSize.pipe(map$1(v => v < 0 ? DEFAULT_BUFFER_SIZE : v)), $maxBufferSize = this.$maxBufferSize.pipe(map$1(v => v < 0 ? DEFAULT_MAX_BUFFER_SIZE : v)), $itemConfigMap = this.$itemConfigMap.pipe(map$1(v => !v ? {} : v)), $snap = this.$snap, $isVertical = this.$direction.pipe(map$1(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $isLazy = this.$collectionMode.pipe(map$1(v => this.getIsLazy(v || DEFAULT_COLLECTION_MODE))), $dynamicSize = this.$dynamicSize, $enabledBufferOptimization = this.$enabledBufferOptimization, $snappingMethod = this.$snappingMethod.pipe(map$1(v => this.getIsSnappingMethodAdvanced(v || DEFAULT_SNAPPING_METHOD))), $methodForSelecting = this.$methodForSelecting, $selectedIds = this.$selectedIds, $collapsedIds = this.$collapsedIds.pipe(map$1(v => Array.isArray(v) ? v : [])), $collapsedItemIds = this._$collapsedItemIds.asObservable().pipe(map$1(v => Array.isArray(v) ? v : [])), $actualItems = this._$actualItems.asObservable(), $screenReaderMessage = this.$screenReaderMessage, $displayItems = this._service.$displayItems, $cacheVersion = this.$cacheVersion;
2793
+ combineLatest([$displayItems, $screenReaderMessage, $isVertical, $scrollSize, $bounds]).pipe(takeUntil(this._$unsubscribe), distinctUntilChanged(), debounceTime(100), tap(([items, screenReaderMessage, isVertical, scrollSize, bounds]) => {
2794
+ this._$screenReaderFormattedMessage.next(formatScreenReaderMessage(items, screenReaderMessage, scrollSize, isVertical, bounds));
2795
+ })).subscribe();
2685
2796
  $isLazy.pipe(takeUntil(this._$unsubscribe), tap(v => {
2686
2797
  this._trackBox.isLazy = v;
2687
2798
  })).subscribe();
@@ -3078,6 +3189,21 @@ class NgVirtualListComponent extends DisposableComponent {
3078
3189
  }
3079
3190
  ;
3080
3191
  get trackBy() { return this._$trackBy.getValue(); }
3192
+ /**
3193
+ * Message for screen reader.
3194
+ * The message format is: "some text $1 some text $2",
3195
+ * where $1 is the number of the first element of the screen collection,
3196
+ * $2 is the number of the last element of the screen collection.
3197
+ */
3198
+ set screenReaderMessage(v) {
3199
+ if (this._$screenReaderMessage.getValue() === v) {
3200
+ return;
3201
+ }
3202
+ const transformedValue = this._screenReaderMessageTransform(v);
3203
+ this._$screenReaderMessage.next(transformedValue);
3204
+ }
3205
+ ;
3206
+ get screenReaderMessage() { return this._$screenReaderMessage.getValue(); }
3081
3207
  get orientation() {
3082
3208
  return this._isVertical ? Directions.VERTICAL : Directions.HORIZONTAL;
3083
3209
  }
@@ -3190,6 +3316,20 @@ class NgVirtualListComponent extends DisposableComponent {
3190
3316
  validateId(id);
3191
3317
  return this._trackBox.getItemBounds(id);
3192
3318
  }
3319
+ /**
3320
+ * Focus an list item by a given id.
3321
+ */
3322
+ focus(id, align = FocusAlignments.NONE) {
3323
+ validateId(id);
3324
+ validateFocusAlignment(align);
3325
+ const el = this._list?.nativeElement.querySelector(`[${ITEM_ID}="${id}"]`);
3326
+ if (el) {
3327
+ const focusedEl = el.querySelector(`.${ITEM_CONTAINER}`);
3328
+ if (focusedEl) {
3329
+ this._service.focus(focusedEl, align);
3330
+ }
3331
+ }
3332
+ }
3193
3333
  /**
3194
3334
  * The method scrolls the list to the element with the given id and returns the value of the scrolled area.
3195
3335
  * Behavior accepts the values ​​"auto", "instant" and "smooth".
@@ -3330,12 +3470,12 @@ class NgVirtualListComponent extends DisposableComponent {
3330
3470
  }
3331
3471
  NgVirtualListComponent.__nextId = 0;
3332
3472
  NgVirtualListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgVirtualListComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: NgVirtualListService }], target: i0.ɵɵFactoryTarget.Component });
3333
- NgVirtualListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NgVirtualListComponent, selector: "ng-virtual-list", inputs: { items: "items", selectedIds: "selectedIds", collapsedIds: "collapsedIds", selectByClick: "selectByClick", collapseByClick: "collapseByClick", snap: "snap", enabledBufferOptimization: "enabledBufferOptimization", itemRenderer: "itemRenderer", itemConfigMap: "itemConfigMap", itemSize: "itemSize", dynamicSize: "dynamicSize", direction: "direction", collectionMode: "collectionMode", bufferSize: "bufferSize", maxBufferSize: "maxBufferSize", snappingMethod: "snappingMethod", methodForSelecting: "methodForSelecting", trackBy: "trackBy" }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd", onViewportChange: "onViewportChange", onItemClick: "onItemClick", onSelect: "onSelect", onCollapse: "onCollapse", onScrollReachStart: "onScrollReachStart", onScrollReachEnd: "onScrollReachEnd" }, host: { styleAttribute: "position: relative;" }, providers: [NgVirtualListService], viewQueries: [{ propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "_container", first: true, predicate: ["container"], descendants: true, read: (ElementRef) }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, read: (ElementRef) }, { propertyName: "_snapContainerRef", first: true, predicate: ["snapRendererContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "_snappedContainer", first: true, predicate: ["snapped"], descendants: true, read: ViewContainerRef }], usesInheritance: true, ngImport: i0, template: "<div *ngIf=\"snap\" #snapped part=\"snapped-item\" class=\"ngvl__list-snapper\">\r\n <ng-container #snapRendererContainer></ng-container>\r\n</div>\r\n<div #container part=\"scroller\" class=\"ngvl__scroller\">\r\n <div [attr.aria-orientation]=\"orientation\" [attr.aria-activedescendant]=\"$focusedElement | async\" #list part=\"list\"\r\n class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </div>\r\n</div>", styles: [":host{position:relative;display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.horizontal) .ngvl__list{display:inline-flex}:host(.horizontal) .ngvl__scroller{overflow:auto hidden}:host(.vertical) .ngvl__scroller{overflow:hidden auto}:host(.vertical){height:320px}.ngvl__scroller{overflow:auto;width:100%;height:100%}.ngvl__list-snapper{pointer-events:none;position:absolute;list-style:none;left:0;top:0;z-index:1}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"], dependencies: [{ kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
3473
+ NgVirtualListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NgVirtualListComponent, selector: "ng-virtual-list", inputs: { items: "items", selectedIds: "selectedIds", collapsedIds: "collapsedIds", selectByClick: "selectByClick", collapseByClick: "collapseByClick", snap: "snap", enabledBufferOptimization: "enabledBufferOptimization", itemRenderer: "itemRenderer", itemConfigMap: "itemConfigMap", itemSize: "itemSize", dynamicSize: "dynamicSize", direction: "direction", collectionMode: "collectionMode", bufferSize: "bufferSize", maxBufferSize: "maxBufferSize", snappingMethod: "snappingMethod", methodForSelecting: "methodForSelecting", trackBy: "trackBy", screenReaderMessage: "screenReaderMessage" }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd", onViewportChange: "onViewportChange", onItemClick: "onItemClick", onSelect: "onSelect", onCollapse: "onCollapse", onScrollReachStart: "onScrollReachStart", onScrollReachEnd: "onScrollReachEnd" }, host: { styleAttribute: "position: relative;" }, providers: [NgVirtualListService], viewQueries: [{ propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "_container", first: true, predicate: ["container"], descendants: true, read: (ElementRef) }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, read: (ElementRef) }, { propertyName: "_snapContainerRef", first: true, predicate: ["snapRendererContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "_snappedContainer", first: true, predicate: ["snapped"], descendants: true, read: ViewContainerRef }], usesInheritance: true, ngImport: i0, template: "<div aria-live=\"polite\" aria-atomic=\"true\" class=\"ngvl__screen-reader\">\r\n {{ $screenReaderFormattedMessage | async}}\r\n</div>\r\n<div *ngIf=\"snap\" #snapped part=\"snapped-item\" class=\"ngvl__list-snapper\">\r\n <ng-container #snapRendererContainer></ng-container>\r\n</div>\r\n<div #container part=\"scroller\" class=\"ngvl__scroller\">\r\n <div [attr.aria-orientation]=\"orientation\" [attr.aria-activedescendant]=\"$focusedElement | async\" #list part=\"list\"\r\n class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </div>\r\n</div>", styles: [":host{position:relative;display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.horizontal) .ngvl__list{display:inline-flex}:host(.horizontal) .ngvl__scroller{overflow:auto hidden}:host(.vertical) .ngvl__scroller{overflow:hidden auto}:host(.vertical){height:320px}.ngvl__scroller{overflow:auto;width:100%;height:100%}.ngvl__list-snapper{pointer-events:none;position:absolute;list-style:none;left:0;top:0;z-index:1}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}.ngvl__screen-reader{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
3334
3474
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgVirtualListComponent, decorators: [{
3335
3475
  type: Component,
3336
3476
  args: [{ selector: 'ng-virtual-list', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, providers: [NgVirtualListService], host: {
3337
3477
  'style': 'position: relative;'
3338
- }, template: "<div *ngIf=\"snap\" #snapped part=\"snapped-item\" class=\"ngvl__list-snapper\">\r\n <ng-container #snapRendererContainer></ng-container>\r\n</div>\r\n<div #container part=\"scroller\" class=\"ngvl__scroller\">\r\n <div [attr.aria-orientation]=\"orientation\" [attr.aria-activedescendant]=\"$focusedElement | async\" #list part=\"list\"\r\n class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </div>\r\n</div>", styles: [":host{position:relative;display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.horizontal) .ngvl__list{display:inline-flex}:host(.horizontal) .ngvl__scroller{overflow:auto hidden}:host(.vertical) .ngvl__scroller{overflow:hidden auto}:host(.vertical){height:320px}.ngvl__scroller{overflow:auto;width:100%;height:100%}.ngvl__list-snapper{pointer-events:none;position:absolute;list-style:none;left:0;top:0;z-index:1}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"] }]
3478
+ }, template: "<div aria-live=\"polite\" aria-atomic=\"true\" class=\"ngvl__screen-reader\">\r\n {{ $screenReaderFormattedMessage | async}}\r\n</div>\r\n<div *ngIf=\"snap\" #snapped part=\"snapped-item\" class=\"ngvl__list-snapper\">\r\n <ng-container #snapRendererContainer></ng-container>\r\n</div>\r\n<div #container part=\"scroller\" class=\"ngvl__scroller\">\r\n <div [attr.aria-orientation]=\"orientation\" [attr.aria-activedescendant]=\"$focusedElement | async\" #list part=\"list\"\r\n class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </div>\r\n</div>", styles: [":host{position:relative;display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.horizontal) .ngvl__list{display:inline-flex}:host(.horizontal) .ngvl__scroller{overflow:auto hidden}:host(.vertical) .ngvl__scroller{overflow:hidden auto}:host(.vertical){height:320px}.ngvl__scroller{overflow:auto;width:100%;height:100%}.ngvl__list-snapper{pointer-events:none;position:absolute;list-style:none;left:0;top:0;z-index:1}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}.ngvl__screen-reader{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"] }]
3339
3479
  }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: NgVirtualListService }]; }, propDecorators: { _listContainerRef: [{
3340
3480
  type: ViewChild,
3341
3481
  args: ['renderersContainer', { read: ViewContainerRef }]
@@ -3403,6 +3543,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImpor
3403
3543
  type: Input
3404
3544
  }], trackBy: [{
3405
3545
  type: Input
3546
+ }], screenReaderMessage: [{
3547
+ type: Input
3406
3548
  }] } });
3407
3549
 
3408
3550
  class NgVirtualListModule {
@@ -3428,5 +3570,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImpor
3428
3570
  * Generated bundle index. Do not edit.
3429
3571
  */
3430
3572
 
3431
- export { CollectionModes, Directions, MethodsForSelecting, NgVirtualListComponent, NgVirtualListItemComponent, NgVirtualListModule, SnappingMethods };
3573
+ export { CollectionModes, Directions, FocusAlignments, MethodsForSelecting, NgVirtualListComponent, NgVirtualListItemComponent, NgVirtualListModule, SnappingMethods };
3432
3574
  //# sourceMappingURL=ng-virtual-list.mjs.map