ng-virtual-list 18.7.2 → 18.7.3
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/README.md +4 -0
- package/esm2022/lib/components/ng-virtual-list-item.component.mjs +80 -10
- package/esm2022/lib/const/index.mjs +6 -5
- package/esm2022/lib/models/render-item-config.model.mjs +1 -1
- package/esm2022/lib/ng-virtual-list.component.mjs +4 -1
- package/esm2022/lib/ng-virtual-list.service.mjs +2 -1
- package/esm2022/lib/utils/trackBox.mjs +8 -2
- package/fesm2022/ng-virtual-list.mjs +94 -13
- package/fesm2022/ng-virtual-list.mjs.map +1 -1
- package/lib/components/ng-virtual-list-item.component.d.ts +7 -0
- package/lib/const/index.d.ts +5 -4
- package/lib/models/render-item-config.model.d.ts +4 -0
- package/lib/ng-virtual-list.service.d.ts +1 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { Injectable, inject, ChangeDetectorRef, signal, ElementRef, Component, ChangeDetectionStrategy, viewChild, output, input, ViewContainerRef, ViewEncapsulation, ViewChild, NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
|
|
3
|
-
import { Subject, tap, combineLatest, map, filter, distinctUntilChanged, switchMap, of } from 'rxjs';
|
|
3
|
+
import { Subject, tap, fromEvent, combineLatest, map, filter, distinctUntilChanged, switchMap, of } from 'rxjs';
|
|
4
4
|
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
|
5
5
|
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
|
|
6
6
|
import * as i1 from '@angular/common';
|
|
@@ -109,10 +109,11 @@ const CLASS_LIST_VERTICAL = 'vertical';
|
|
|
109
109
|
const CLASS_LIST_HORIZONTAL = 'horizontal';
|
|
110
110
|
// styles
|
|
111
111
|
const PART_DEFAULT_ITEM = 'item';
|
|
112
|
-
const PART_ITEM_ODD = ' odd';
|
|
113
|
-
const PART_ITEM_EVEN = ' even';
|
|
114
|
-
const PART_ITEM_SNAPPED = ' snapped';
|
|
115
|
-
const PART_ITEM_SELECTED = ' selected';
|
|
112
|
+
const PART_ITEM_ODD = ' item-odd';
|
|
113
|
+
const PART_ITEM_EVEN = ' item-even';
|
|
114
|
+
const PART_ITEM_SNAPPED = ' item-snapped';
|
|
115
|
+
const PART_ITEM_SELECTED = ' item-selected';
|
|
116
|
+
const PART_ITEM_FOCUSED = ' item-focused';
|
|
116
117
|
|
|
117
118
|
/**
|
|
118
119
|
* Virtual List Item Interface
|
|
@@ -158,6 +159,7 @@ class NgVirtualListService {
|
|
|
158
159
|
}
|
|
159
160
|
selectByClick = DEFAULT_SELECT_BY_CLICK;
|
|
160
161
|
_trackBox;
|
|
162
|
+
listElement = null;
|
|
161
163
|
constructor() {
|
|
162
164
|
this._$methodOfSelecting.pipe(takeUntilDestroyed(), tap(v => {
|
|
163
165
|
switch (v) {
|
|
@@ -265,7 +267,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
|
|
|
265
267
|
}]
|
|
266
268
|
}], ctorParameters: () => [] });
|
|
267
269
|
|
|
268
|
-
const ATTR_AREA_SELECTED = 'area-selected';
|
|
270
|
+
const ATTR_AREA_SELECTED = 'area-selected', TABINDEX = 'index', KEY_SPACE = " ", KEY_ARR_LEFT = "ArrowLeft", KEY_ARR_UP = "ArrowUp", KEY_ARR_RIGHT = "ArrowRight", KEY_ARR_DOWN = "ArrowDown";
|
|
269
271
|
/**
|
|
270
272
|
* Virtual list item component
|
|
271
273
|
* @link https://github.com/DjonnyX/ng-virtual-list/blob/18.x/projects/ng-virtual-list/src/lib/components/ng-virtual-list-item.component.ts
|
|
@@ -282,6 +284,7 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
|
|
|
282
284
|
_isSelected = false;
|
|
283
285
|
config = signal({});
|
|
284
286
|
measures = signal(undefined);
|
|
287
|
+
focus = signal(false);
|
|
285
288
|
_part = PART_DEFAULT_ITEM;
|
|
286
289
|
get part() { return this._part; }
|
|
287
290
|
regular = false;
|
|
@@ -334,23 +337,71 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
|
|
|
334
337
|
super();
|
|
335
338
|
this._id = this._service.generateComponentId();
|
|
336
339
|
const $data = toObservable(this.data);
|
|
340
|
+
fromEvent(this.element, 'focusin').pipe(takeUntilDestroyed(), tap(e => {
|
|
341
|
+
this.focus.set(true);
|
|
342
|
+
this.updateConfig(this._data);
|
|
343
|
+
this.updatePartStr(this._data, this._isSelected);
|
|
344
|
+
})).subscribe(),
|
|
345
|
+
fromEvent(this.element, 'focusout').pipe(takeUntilDestroyed(), tap(e => {
|
|
346
|
+
this.focus.set(false);
|
|
347
|
+
this.updateConfig(this._data);
|
|
348
|
+
this.updatePartStr(this._data, this._isSelected);
|
|
349
|
+
})).subscribe(),
|
|
350
|
+
fromEvent(this.element, 'keydown').pipe(takeUntilDestroyed(), tap(e => {
|
|
351
|
+
switch (e.key) {
|
|
352
|
+
case KEY_SPACE: {
|
|
353
|
+
e.stopImmediatePropagation();
|
|
354
|
+
e.preventDefault();
|
|
355
|
+
this._service.select(this._data);
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
case KEY_ARR_LEFT:
|
|
359
|
+
if (!this.config().isVertical) {
|
|
360
|
+
e.stopImmediatePropagation();
|
|
361
|
+
e.preventDefault();
|
|
362
|
+
this.focusPrev();
|
|
363
|
+
}
|
|
364
|
+
break;
|
|
365
|
+
case KEY_ARR_UP:
|
|
366
|
+
if (this.config().isVertical) {
|
|
367
|
+
e.stopImmediatePropagation();
|
|
368
|
+
e.preventDefault();
|
|
369
|
+
this.focusPrev();
|
|
370
|
+
}
|
|
371
|
+
break;
|
|
372
|
+
case KEY_ARR_RIGHT:
|
|
373
|
+
if (!this.config().isVertical) {
|
|
374
|
+
e.stopImmediatePropagation();
|
|
375
|
+
e.preventDefault();
|
|
376
|
+
this.focusNext();
|
|
377
|
+
}
|
|
378
|
+
break;
|
|
379
|
+
case KEY_ARR_DOWN:
|
|
380
|
+
if (this.config().isVertical) {
|
|
381
|
+
e.stopImmediatePropagation();
|
|
382
|
+
e.preventDefault();
|
|
383
|
+
this.focusNext();
|
|
384
|
+
}
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
})).subscribe();
|
|
337
388
|
combineLatest([$data, this._service.$methodOfSelecting, this._service.$selectedIds]).pipe(takeUntilDestroyed(), map(([, m, ids]) => ({ method: m, ids })), tap(({ method, ids }) => {
|
|
338
389
|
switch (method) {
|
|
339
390
|
case MethodsForSelectingTypes.SELECT: {
|
|
340
391
|
const id = ids, isSelected = id === this.itemId;
|
|
341
|
-
this.
|
|
392
|
+
this.element.setAttribute(ATTR_AREA_SELECTED, String(isSelected));
|
|
342
393
|
this._isSelected = isSelected;
|
|
343
394
|
break;
|
|
344
395
|
}
|
|
345
396
|
case MethodsForSelectingTypes.MULTI_SELECT: {
|
|
346
397
|
const actualIds = ids, isSelected = this.itemId !== undefined && actualIds && actualIds.includes(this.itemId);
|
|
347
|
-
this.
|
|
398
|
+
this.element.setAttribute(ATTR_AREA_SELECTED, String(isSelected));
|
|
348
399
|
this._isSelected = isSelected;
|
|
349
400
|
break;
|
|
350
401
|
}
|
|
351
402
|
case MethodsForSelectingTypes.NONE:
|
|
352
403
|
default: {
|
|
353
|
-
this.
|
|
404
|
+
this.element.removeAttribute(ATTR_AREA_SELECTED);
|
|
354
405
|
this._isSelected = false;
|
|
355
406
|
break;
|
|
356
407
|
}
|
|
@@ -360,11 +411,29 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
|
|
|
360
411
|
this.updateMeasures(this._data);
|
|
361
412
|
})).subscribe();
|
|
362
413
|
}
|
|
414
|
+
focusNext() {
|
|
415
|
+
const tabIndex = this.config()?.tabIndex ?? 0;
|
|
416
|
+
if (this._service.listElement && tabIndex > 0) {
|
|
417
|
+
const el = this._service.listElement.querySelector(`[${TABINDEX}="${tabIndex + 1}"]`);
|
|
418
|
+
if (el) {
|
|
419
|
+
el.focus();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
focusPrev() {
|
|
424
|
+
const tabIndex = this.config()?.tabIndex ?? 0;
|
|
425
|
+
if (this._service.listElement && tabIndex > 1) {
|
|
426
|
+
const el = this._service.listElement.querySelector(`[${TABINDEX}="${tabIndex - 1}"]`);
|
|
427
|
+
if (el) {
|
|
428
|
+
el.focus();
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
363
432
|
updateMeasures(v) {
|
|
364
433
|
this.measures.set(v?.measures ? { ...v.measures } : undefined);
|
|
365
434
|
}
|
|
366
435
|
updateConfig(v) {
|
|
367
|
-
this.config.set({ ...v?.config || {}, selected: this._isSelected, select: this._selectHandler(v) });
|
|
436
|
+
this.config.set({ ...v?.config || {}, selected: this._isSelected, select: this._selectHandler(v), focus: this.focus() });
|
|
368
437
|
}
|
|
369
438
|
update() {
|
|
370
439
|
const data = this._data, regular = this.regular, length = this._regularLength;
|
|
@@ -405,6 +474,9 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
|
|
|
405
474
|
if (isSelected) {
|
|
406
475
|
part += PART_ITEM_SELECTED;
|
|
407
476
|
}
|
|
477
|
+
if (this.focus()) {
|
|
478
|
+
part += PART_ITEM_FOCUSED;
|
|
479
|
+
}
|
|
408
480
|
this._part = part;
|
|
409
481
|
}
|
|
410
482
|
getBounds() {
|
|
@@ -449,14 +521,14 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
|
|
|
449
521
|
this._service.itemClick(this._data);
|
|
450
522
|
}
|
|
451
523
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgVirtualListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
452
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: NgVirtualListItemComponent, selector: "ng-virtual-list-item", host: { attributes: { "role": "listitem" }, classAttribute: "ngvl__item" }, usesInheritance: true, ngImport: i0, template: "@if (data(); as item) {\r\n <div #listItem [part]=\"part\" class=\"ngvl-item__container\" [ngClass]=\"{'snapped': item.config.snapped,\r\n 'snapped-out': item.config.snappedOut}\" (click)=\"onClickHandler()\">\r\n @if (itemRenderer(); as renderer) {\r\n <ng-container [ngTemplateOutlet]=\"renderer\"\r\n [ngTemplateOutletContext]=\"{data: item.data || {}, measures: measures(), config: config()}\" />\r\n }\r\n </div>\r\n}\r\n", 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}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
524
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: NgVirtualListItemComponent, selector: "ng-virtual-list-item", host: { attributes: { "role": "listitem" }, classAttribute: "ngvl__item" }, usesInheritance: true, ngImport: i0, template: "@if (data(); as item) {\r\n <div #listItem [part]=\"part\" class=\"ngvl-item__container\" [attr.index]=\"item.config.tabIndex\" tabindex=\"1\" [ngClass]=\"{'snapped': item.config.snapped,\r\n 'snapped-out': item.config.snappedOut, 'focus': focus()}\" (click)=\"onClickHandler()\">\r\n @if (itemRenderer(); as renderer) {\r\n <ng-container [ngTemplateOutlet]=\"renderer\"\r\n [ngTemplateOutletContext]=\"{data: item.data || {}, measures: measures(), config: config()}\" />\r\n }\r\n </div>\r\n}\r\n", 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: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
453
525
|
}
|
|
454
526
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: NgVirtualListItemComponent, decorators: [{
|
|
455
527
|
type: Component,
|
|
456
528
|
args: [{ selector: 'ng-virtual-list-item', standalone: false, host: {
|
|
457
529
|
'class': 'ngvl__item',
|
|
458
530
|
'role': 'listitem',
|
|
459
|
-
}, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (data(); as item) {\r\n <div #listItem [part]=\"part\" class=\"ngvl-item__container\" [ngClass]=\"{'snapped': item.config.snapped,\r\n 'snapped-out': item.config.snappedOut}\" (click)=\"onClickHandler()\">\r\n @if (itemRenderer(); as renderer) {\r\n <ng-container [ngTemplateOutlet]=\"renderer\"\r\n [ngTemplateOutletContext]=\"{data: item.data || {}, measures: measures(), config: config()}\" />\r\n }\r\n </div>\r\n}\r\n", 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}\n"] }]
|
|
531
|
+
}, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (data(); as item) {\r\n <div #listItem [part]=\"part\" class=\"ngvl-item__container\" [attr.index]=\"item.config.tabIndex\" tabindex=\"1\" [ngClass]=\"{'snapped': item.config.snapped,\r\n 'snapped-out': item.config.snappedOut, 'focus': focus()}\" (click)=\"onClickHandler()\">\r\n @if (itemRenderer(); as renderer) {\r\n <ng-container [ngTemplateOutlet]=\"renderer\"\r\n [ngTemplateOutletContext]=\"{data: item.data || {}, measures: measures(), config: config()}\" />\r\n }\r\n </div>\r\n}\r\n", 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"] }]
|
|
460
532
|
}], ctorParameters: () => [] });
|
|
461
533
|
|
|
462
534
|
/**
|
|
@@ -1552,7 +1624,7 @@ class TrackBox extends CacheMap {
|
|
|
1552
1624
|
const { width, height, normalizedItemWidth, normalizedItemHeight, dynamicSize, itemsOnDisplayLength, itemsFromStartToScrollEnd, isVertical, renderItems: renderItemsLength, scrollSize, sizeProperty, snap, snippedPos, startPosition, totalLength, startIndex, typicalItemSize, } = metrics, displayItems = [];
|
|
1553
1625
|
if (items.length) {
|
|
1554
1626
|
const actualSnippedPosition = snippedPos, isSnappingMethodAdvanced = this.isSnappingMethodAdvanced, boundsSize = isVertical ? height : width, actualEndSnippedPosition = boundsSize;
|
|
1555
|
-
let pos = startPosition, renderItems = renderItemsLength, stickyItem, nextSticky, stickyItemIndex = -1, stickyItemSize = 0, endStickyItem, nextEndSticky, endStickyItemIndex = -1, endStickyItemSize = 0;
|
|
1627
|
+
let pos = startPosition, renderItems = renderItemsLength, stickyItem, nextSticky, stickyItemIndex = -1, stickyItemSize = 0, endStickyItem, nextEndSticky, endStickyItemIndex = -1, endStickyItemSize = 0, count = 1;
|
|
1556
1628
|
if (snap) {
|
|
1557
1629
|
for (let i = Math.min(itemsFromStartToScrollEnd > 0 ? itemsFromStartToScrollEnd : 0, totalLength - 1); i >= 0; i--) {
|
|
1558
1630
|
if (!items[i]) {
|
|
@@ -1577,6 +1649,7 @@ class TrackBox extends CacheMap {
|
|
|
1577
1649
|
snappedOut: false,
|
|
1578
1650
|
dynamic: dynamicSize,
|
|
1579
1651
|
isSnappingMethodAdvanced,
|
|
1652
|
+
tabIndex: count,
|
|
1580
1653
|
zIndex: '1',
|
|
1581
1654
|
};
|
|
1582
1655
|
const itemData = items[i];
|
|
@@ -1584,6 +1657,7 @@ class TrackBox extends CacheMap {
|
|
|
1584
1657
|
stickyItemIndex = i;
|
|
1585
1658
|
stickyItemSize = size;
|
|
1586
1659
|
displayItems.push(stickyItem);
|
|
1660
|
+
count++;
|
|
1587
1661
|
break;
|
|
1588
1662
|
}
|
|
1589
1663
|
}
|
|
@@ -1615,6 +1689,7 @@ class TrackBox extends CacheMap {
|
|
|
1615
1689
|
snappedOut: false,
|
|
1616
1690
|
dynamic: dynamicSize,
|
|
1617
1691
|
isSnappingMethodAdvanced,
|
|
1692
|
+
tabIndex: count,
|
|
1618
1693
|
zIndex: '1',
|
|
1619
1694
|
};
|
|
1620
1695
|
const itemData = items[i];
|
|
@@ -1622,6 +1697,7 @@ class TrackBox extends CacheMap {
|
|
|
1622
1697
|
endStickyItemIndex = i;
|
|
1623
1698
|
endStickyItemSize = size;
|
|
1624
1699
|
displayItems.push(endStickyItem);
|
|
1700
|
+
count++;
|
|
1625
1701
|
break;
|
|
1626
1702
|
}
|
|
1627
1703
|
}
|
|
@@ -1653,6 +1729,7 @@ class TrackBox extends CacheMap {
|
|
|
1653
1729
|
snappedOut: false,
|
|
1654
1730
|
dynamic: dynamicSize,
|
|
1655
1731
|
isSnappingMethodAdvanced,
|
|
1732
|
+
tabIndex: count,
|
|
1656
1733
|
zIndex: '0',
|
|
1657
1734
|
};
|
|
1658
1735
|
if (snapped) {
|
|
@@ -1677,6 +1754,7 @@ class TrackBox extends CacheMap {
|
|
|
1677
1754
|
nextEndSticky.measures.delta = isVertical ? (item.measures.y - scrollSize) : (item.measures.x - scrollSize);
|
|
1678
1755
|
}
|
|
1679
1756
|
displayItems.push(item);
|
|
1757
|
+
count++;
|
|
1680
1758
|
}
|
|
1681
1759
|
renderItems -= 1;
|
|
1682
1760
|
pos += size;
|
|
@@ -2058,6 +2136,9 @@ class NgVirtualListComponent {
|
|
|
2058
2136
|
this._initialized = signal(false);
|
|
2059
2137
|
this.$initialized = toObservable(this._initialized);
|
|
2060
2138
|
this._trackBox.displayComponents = this._displayComponents;
|
|
2139
|
+
toObservable(this._list).pipe(takeUntilDestroyed(), filter(v => !!v), tap(v => {
|
|
2140
|
+
this._service.listElement = v.nativeElement;
|
|
2141
|
+
})).subscribe();
|
|
2061
2142
|
const $trackBy = toObservable(this.trackBy), $selectByClick = toObservable(this.selectByClick);
|
|
2062
2143
|
$selectByClick.pipe(takeUntilDestroyed(), tap(v => {
|
|
2063
2144
|
this._service.selectByClick = v;
|