Skip to content

Commit 720f0e8

Browse files
authored
Merge pull request #7269 from IgniteUI/rkaraivanov/keyboard-fixes
fix(for-of): Revert change to small scrolls
2 parents 90ebb4a + 255ce3c commit 720f0e8

13 files changed

+143
-72
lines changed

projects/igniteui-angular/src/lib/combo/combo.component.spec.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,21 +1216,19 @@ describe('igxCombo', () => {
12161216
fixture.detectChanges();
12171217
let items = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`));
12181218
let lastItem = items[items.length - 1].componentInstance;
1219-
let lastItemIndex = lastItem.index;
12201219
expect(lastItem).toBeDefined();
12211220
lastItem.clicked(mockClick);
12221221
await wait(30);
12231222
fixture.detectChanges();
1224-
expect(dropdown.focusedItem.index).toEqual(lastItemIndex);
1223+
expect(dropdown.focusedItem).toEqual(lastItem);
12251224
dropdown.navigateItem(-1);
12261225
await wait(30);
12271226
fixture.detectChanges();
12281227
expect(virtualMockDOWN).toHaveBeenCalledTimes(0);
1229-
lastItemIndex = lastItem.index;
12301228
lastItem.clicked(mockClick);
12311229
await wait(30);
12321230
fixture.detectChanges();
1233-
expect(dropdown.focusedItem.index).toEqual(lastItemIndex);
1231+
expect(dropdown.focusedItem).toEqual(lastItem);
12341232
dropdown.navigateNext();
12351233
await wait(30);
12361234
fixture.detectChanges();

projects/igniteui-angular/src/lib/directives/for-of/for_of.directive.ts

Lines changed: 86 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
TemplateRef,
2323
TrackByFunction,
2424
ViewContainerRef,
25-
ViewRef,
2625
AfterViewInit
2726
} from '@angular/core';
2827

@@ -850,27 +849,89 @@ export class IgxForOfDirective<T> implements OnInit, OnChanges, DoCheck, OnDestr
850849
this.sizesCache,
851850
0
852851
);
852+
853853
if (newStart + this.state.chunkSize > count) {
854854
newStart = count - this.state.chunkSize;
855855
}
856+
857+
const prevStart = this.state.startIndex;
856858
const diff = newStart - this.state.startIndex;
857859
this.state.startIndex = newStart;
860+
858861
if (diff) {
859862
this.onChunkPreload.emit(this.state);
860863
if (!this.isRemote) {
861-
this.fixedApplyScroll();
864+
865+
// recalculate and apply page size.
866+
if (diff && Math.abs(diff) <= this.MAX_PERF_SCROLL_DIFF) {
867+
diff > 0 ? this.moveApplyScrollNext(prevStart) : this.moveApplyScrollPrev(prevStart);
868+
} else {
869+
this.fixedApplyScroll();
870+
}
862871
}
863872
}
873+
864874
return inScrollTop - this.sizesCache[this.state.startIndex];
865875
}
866876

877+
/**
878+
* @hidden
879+
* The function applies an optimized state change for scrolling down/right employing context change with view rearrangement
880+
*/
881+
protected moveApplyScrollNext(prevIndex: number): void {
882+
const start = prevIndex + this.state.chunkSize;
883+
const end = start + this.state.startIndex - prevIndex;
884+
const container = this.dc.instance._vcr as ViewContainerRef;
885+
886+
for (let i = start; i < end && this.igxForOf[i] !== undefined; i++) {
887+
const embView = this._embeddedViews.shift();
888+
this.scrollFocus(embView.rootNodes.find(node => node.nodeType === Node.ELEMENT_NODE)
889+
|| embView.rootNodes[0].nextElementSibling);
890+
const view = container.detach(0);
891+
892+
this.updateTemplateContext(embView.context, i);
893+
container.insert(view);
894+
this._embeddedViews.push(embView);
895+
}
896+
}
897+
898+
/**
899+
* @hidden
900+
* The function applies an optimized state change for scrolling up/left employing context change with view rearrangement
901+
*/
902+
protected moveApplyScrollPrev(prevIndex: number): void {
903+
const container = this.dc.instance._vcr as ViewContainerRef;
904+
for (let i = prevIndex - 1; i >= this.state.startIndex && this.igxForOf[i] !== undefined; i--) {
905+
const embView = this._embeddedViews.pop();
906+
this.scrollFocus(embView.rootNodes.find(node => node.nodeType === Node.ELEMENT_NODE)
907+
|| embView.rootNodes[0].nextElementSibling);
908+
const view = container.detach(container.length - 1);
909+
910+
this.updateTemplateContext(embView.context, i);
911+
container.insert(view, 0);
912+
this._embeddedViews.unshift(embView);
913+
}
914+
}
915+
867916
/**
868917
* @hidden
869918
*/
870919
protected getContextIndex(input) {
871920
return this.isRemote ? this.state.startIndex + this.igxForOf.indexOf(input) : this.igxForOf.indexOf(input);
872921
}
873922

923+
/**
924+
* @hidden
925+
* Function which updates the passed context of an embedded view with the provided index
926+
* from the view container.
927+
* Often, called while handling a scroll event.
928+
*/
929+
protected updateTemplateContext(context: any, index: number = 0): void {
930+
context.$implicit = this.igxForOf[index];
931+
context.index = this.getContextIndex(this.igxForOf[index]);
932+
context.count = this.igxForOf.length;
933+
}
934+
874935
/**
875936
* @hidden
876937
* The function applies an optimized state change through context change for each view
@@ -879,12 +940,28 @@ export class IgxForOfDirective<T> implements OnInit, OnChanges, DoCheck, OnDestr
879940
let j = 0;
880941
const endIndex = this.state.startIndex + this.state.chunkSize;
881942
for (let i = this.state.startIndex; i < endIndex && this.igxForOf[i] !== undefined; i++) {
882-
const input = this.igxForOf[i];
883943
const embView = this._embeddedViews[j++];
884-
const cntx = (embView as EmbeddedViewRef<any>).context;
885-
cntx.$implicit = input;
886-
cntx.index = this.getContextIndex(input);
887-
cntx.count = this.igxForOf.length;
944+
this.updateTemplateContext(embView.context, i);
945+
}
946+
}
947+
948+
/**
949+
* @hidden
950+
* @internal
951+
*
952+
* Clears focus inside the virtualized container on small scroll swaps.
953+
*/
954+
protected scrollFocus(node?: HTMLElement): void {
955+
const activeElement = document.activeElement as HTMLElement;
956+
957+
// Remove focus in case the the active element is inside the view container.
958+
// Otherwise we hit an exception while doing the 'small' scrolls swapping.
959+
// For more information:
960+
//
961+
// https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild
962+
// https://bugs.chromium.org/p/chromium/issues/detail?id=432392
963+
if (node && node.contains(document.activeElement)) {
964+
activeElement.blur();
888965
}
889966
}
890967

@@ -950,12 +1027,8 @@ export class IgxForOfDirective<T> implements OnInit, OnChanges, DoCheck, OnDestr
9501027
endIndex = this.igxForOf.length;
9511028
}
9521029
for (let i = startIndex; i < endIndex && this.igxForOf[i] !== undefined; i++) {
953-
const input = this.igxForOf[i];
9541030
const embView = embeddedViewCopy.shift();
955-
const cntx = (embView as EmbeddedViewRef<any>).context;
956-
cntx.$implicit = input;
957-
cntx.index = this.getContextIndex(input);
958-
cntx.count = this.igxForOf.length;
1031+
this.updateTemplateContext(embView.context, i);
9591032
}
9601033
if (prevChunkSize !== this.state.chunkSize) {
9611034
this.onChunkLoad.emit(this.state);
@@ -1612,12 +1685,8 @@ export class IgxGridForOfDirective<T> extends IgxForOfDirective<T> implements On
16121685
}
16131686

16141687
for (let i = startIndex; i < endIndex && this.igxForOf[i] !== undefined; i++) {
1615-
const input = this.igxForOf[i];
16161688
const embView = embeddedViewCopy.shift();
1617-
const cntx = (embView as EmbeddedViewRef<any>).context;
1618-
cntx.$implicit = input;
1619-
cntx.index = this.getContextIndex(input);
1620-
cntx.count = this.igxForOf.length;
1689+
this.updateTemplateContext(embView.context, i);
16211690
}
16221691
if (prevChunkSize !== this.state.chunkSize) {
16231692
this.onChunkLoad.emit(this.state);

projects/igniteui-angular/src/lib/grids/cell.component.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,21 @@
1111
<ng-template #inlineEditor let-cell="cell">
1212
<ng-container *ngIf="column.dataType === 'string'">
1313
<igx-input-group displayDensity="compact">
14-
<input igxInput [value]="editValue" (input)="editValue = $event.target.value" [igxFocus]="focused" />
14+
<input igxInput [value]="editValue" (input)="editValue = $event.target.value" [igxFocus]="true" />
1515
</igx-input-group>
1616
</ng-container>
1717
<ng-container *ngIf="column.dataType === 'number'">
1818
<igx-input-group displayDensity="compact">
19-
<input igxInput [value]="editValue" (input)="editValue = $event.target.value" [igxFocus]="focused" type="number">
19+
<input igxInput [value]="editValue" (input)="editValue = $event.target.value" [igxFocus]="true" type="number">
2020
</igx-input-group>
2121
</ng-container>
2222
<ng-container *ngIf="column.dataType === 'boolean'">
2323
<igx-checkbox (change)="editValue = $event.checked" [value]="editValue" [checked]="editValue"
24-
[igxFocus]="focused" [disableRipple]="true"></igx-checkbox>
24+
[igxFocus]="true" [disableRipple]="true"></igx-checkbox>
2525
</ng-container>
2626
<ng-container *ngIf="column.dataType === 'date'">
2727
<igx-date-picker [style.width.%]="100" [outlet]="grid.outletDirective" mode="dropdown"
28-
[locale]="grid.locale" [(value)]="editValue" [igxFocus]="focused" [labelVisibility]="false">
28+
[locale]="grid.locale" [(value)]="editValue" [igxFocus]="true" [labelVisibility]="false">
2929
</igx-date-picker>
3030
</ng-container>
3131
</ng-template>

projects/igniteui-angular/src/lib/grids/cell.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy {
666666
}
667667
}
668668
crud.end();
669-
this.grid.tbody.nativeElement.focus();
669+
this.grid.tbody.nativeElement.focus({ preventScroll: true });
670670
this.grid.notifyChanges();
671671
crud.begin(this);
672672
return;

projects/igniteui-angular/src/lib/grids/grid/grid-cell-editing.spec.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
ColumnEditablePropertyTestComponent
1414
} from '../../test-utils/grid-samples.spec';
1515
import { DebugElement } from '@angular/core';
16-
import { setupGridScrollDetection } from '../../test-utils/helper-utils.spec';
1716

1817
const DEBOUNCETIME = 30;
1918
const CELL_CSS_CLASS = '.igx-grid__td';
@@ -382,31 +381,35 @@ describe('IgxGrid - Cell Editing #grid', () => {
382381
});
383382

384383
it('When cell in editMode and try to navigate with `ArrowUp` - focus should remain over the input.', (async () => {
385-
let cell = grid.getCellByColumn(0, 'firstName' );
386-
UIInteractions.simulateClickAndSelectEvent(cell);
387-
fixture.detectChanges();
388-
GridFunctions.simulateGridContentKeydown(fixture, 'ArrowDown', false, false, true);
389-
await wait(100);
384+
const verticalScroll = grid.verticalScrollContainer.getScroll();
385+
let expectedScroll;
386+
let cellElem;
387+
GridFunctions.scrollTop(grid, 1000);
388+
await wait(500);
390389
fixture.detectChanges();
391390

392-
cell = grid.getCellByColumn(8, 'firstName' );
393-
UIInteractions.simulateDoubleClickAndSelectEvent(cell);
394-
await wait(DEBOUNCETIME);
391+
const testCells = grid.getColumnByName('firstName').cells;
392+
const cell = testCells[testCells.length - 1];
393+
cellElem = cell.nativeElement;
394+
395+
cellElem.dispatchEvent(new Event('focus'));
396+
cellElem.dispatchEvent(new MouseEvent('dblclick'));
395397
fixture.detectChanges();
398+
await wait(50);
396399

397-
const inputElem: HTMLInputElement = document.activeElement as HTMLInputElement;
400+
let inputElem: HTMLInputElement = document.activeElement as HTMLInputElement;
398401
expect(cell.editMode).toBeTruthy();
399-
expect(cell.nativeElement.classList.contains(CELL_CLASS_IN_EDIT_MODE)).toBe(true);
400-
const expectedScroll = grid.verticalScrollContainer.getScroll().scrollTop;
402+
expect(cellElem.classList.contains(CELL_CLASS_IN_EDIT_MODE)).toBe(true);
403+
expectedScroll = verticalScroll.scrollTop;
401404

402405
UIInteractions.triggerKeyDownEvtUponElem('ArrowUp', inputElem, true);
403-
await wait(DEBOUNCETIME);
404406
fixture.detectChanges();
407+
await wait(DEBOUNCETIME);
405408

406-
cell = grid.getCellByColumn(8, 'firstName' );
409+
inputElem = document.activeElement as HTMLInputElement;
407410
expect(cell.editMode).toBeTruthy();
408-
expect(cell.nativeElement.classList.contains(CELL_CLASS_IN_EDIT_MODE)).toBe(true);
409-
expect(grid.verticalScrollContainer.getScroll().scrollTop).toBe(expectedScroll);
411+
expect(cellElem.classList.contains(CELL_CLASS_IN_EDIT_MODE)).toBe(true);
412+
expect(verticalScroll.scrollTop).toBe(expectedScroll);
410413
}));
411414

412415
it('When cell in editMode and try to navigate with `ArrowRight` - focus should remain over the input.', (async () => {

projects/igniteui-angular/src/lib/grids/grid/grid-cell-selection.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,7 +1373,7 @@ describe('IgxGrid - Cell selection #grid', () => {
13731373
GridSelectionFunctions.verifyCellSelected(cell);
13741374
expect(grid.selectedCells.length).toBe(1);
13751375

1376-
let row = grid.getRowByIndex(3);
1376+
const row = grid.getRowByIndex(3);
13771377
expect(row instanceof IgxGridGroupByRowComponent).toBe(true);
13781378
expect(row.focused).toBe(true);
13791379

@@ -1399,7 +1399,6 @@ describe('IgxGrid - Cell selection #grid', () => {
13991399
await wait(100);
14001400
fix.detectChanges();
14011401

1402-
row = grid.getRowByIndex(3);
14031402
expect(selectionChangeSpy).toHaveBeenCalledTimes(2);
14041403
expect(grid.selectedCells.length).toBe(4);
14051404
expect(row instanceof IgxGridGroupByRowComponent).toBe(true);

projects/igniteui-angular/src/lib/grids/grid/grid.master-detail.spec.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ describe('IgxGrid Master Detail #grid', () => {
374374
});
375375

376376
it('Should navigate down through a detail view partially out of view by scrolling it so it becomes fully visible.', async() => {
377-
let row = grid.getRowByIndex(4) as IgxGridRowComponent;
377+
const row = grid.getRowByIndex(4) as IgxGridRowComponent;
378378
const targetCellElement = grid.getCellByColumn(4, 'ContactName');
379379
UIInteractions.simulateClickAndSelectEvent(targetCellElement);
380380
fix.detectChanges();
@@ -383,7 +383,6 @@ describe('IgxGrid Master Detail #grid', () => {
383383
await wait(DEBOUNCETIME);
384384
fix.detectChanges();
385385

386-
row = grid.getRowByIndex(4) as IgxGridRowComponent;
387386
const detailRow = GridFunctions.getMasterRowDetail(row);
388387
GridFunctions.verifyMasterDetailRowFocused(detailRow);
389388
expect(GridFunctions.elementInGridView(grid, detailRow)).toBeTruthy();
@@ -396,7 +395,7 @@ describe('IgxGrid Master Detail #grid', () => {
396395
await wait(DEBOUNCETIME);
397396
fix.detectChanges();
398397

399-
let row = grid.getRowByIndex(6) as IgxGridRowComponent;
398+
const row = grid.getRowByIndex(6) as IgxGridRowComponent;
400399
const targetCellElement = grid.getCellByColumn(6, 'ContactName');
401400
UIInteractions.simulateClickAndSelectEvent(targetCellElement);
402401
fix.detectChanges();
@@ -408,7 +407,6 @@ describe('IgxGrid Master Detail #grid', () => {
408407
await wait(DEBOUNCETIME);
409408
fix.detectChanges();
410409

411-
row = grid.getRowByIndex(6) as IgxGridRowComponent;
412410
const detailRow = GridFunctions.getMasterRowDetail(row);
413411
GridFunctions.verifyMasterDetailRowFocused(detailRow);
414412
expect(GridFunctions.elementInGridView(grid, detailRow)).toBeTruthy();

projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.navigation.spec.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { By } from '@angular/platform-browser';
1010
import { IgxHierarchicalRowComponent } from './hierarchical-row.component';
1111
import { setupHierarchicalGridScrollDetection } from '../../test-utils/helper-utils.spec';
1212
import { GridFunctions } from '../../test-utils/grid-functions.spec';
13-
import { IGridCellEventArgs } from '../grid';
13+
import { IgxGridCellComponent, IGridCellEventArgs } from '../grid';
1414
import { IgxChildGridRowComponent } from './child-grid-row.component';
1515

1616
const DEBOUNCE_TIME = 60;
@@ -610,14 +610,17 @@ describe('IgxHierarchicalGrid Basic Navigation #hGrid', () => {
610610
hierarchicalGrid.getColumnByName('ID').hidden = true;
611611
await wait(50);
612612
fixture.detectChanges();
613+
hierarchicalGrid.navigateTo(2);
614+
await wait(DEBOUNCE_TIME);
615+
fixture.detectChanges();
613616

614-
const cell = hierarchicalGrid.getCellByColumn(0, 'ChildLevels');
617+
const cell = hierarchicalGrid.getCellByColumn(2, 'ChildLevels');
615618
UIInteractions.simulateDoubleClickAndSelectEvent(cell);
616619
fixture.detectChanges();
617620
await wait(DEBOUNCE_TIME);
618621
fixture.detectChanges();
619622

620-
UIInteractions.triggerEventHandlerKeyDown('tab', baseHGridContent, false, true, false);
623+
UIInteractions.triggerKeyDownEvtUponElem('tab', cell.nativeElement, true, false, true);
621624
await wait(DEBOUNCE_TIME);
622625
fixture.detectChanges();
623626
const activeEl = document.activeElement;

projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.virtualization.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ it('should update scroll height after expanding/collapsing row in a nested child
334334
expect(childRowComponent.index).toBe(4);
335335

336336
hierarchicalGrid.verticalScrollContainer.scrollNext();
337-
await wait(200);
337+
await wait(100);
338338
fixture.detectChanges();
339339
childRowComponent = fixture.debugElement.query(By.css('igx-child-grid-row')).componentInstance;
340340
expect(childRowComponent.rowData.rowID).toBe('3');

projects/igniteui-angular/src/lib/grids/selection/selection.service.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,6 @@ export class IgxGridCRUDService {
160160
}
161161

162162
begin(cell): void {
163-
// this is necessary beacuse when the cell enters edit mode the focus should be moved to the edit template
164-
// if we constantly retrigger the focus over the edit template we broke the scrolling experience in the hierarchical grid
165-
// This fix should be removed, when the issue #7219 is resolved
166-
cell.focused = true;
167163
const newCell = this.createCell(cell);
168164
newCell.primaryKey = this.primaryKey;
169165
const args = {

0 commit comments

Comments
 (0)