Skip to content

Commit 783104b

Browse files
committed
fix(for-of): Revert change to small scrolls
Handle focus/blur interaction of templates inside the for-of directive itself. Closes #7219, #7230
1 parent 4c71677 commit 783104b

13 files changed

+134
-75
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: 75 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,98 @@ 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+
const container = this.dc.instance._viewContainer.element.nativeElement as HTMLElement;
865+
const activeElement = document.activeElement as HTMLElement;
866+
867+
// Remove focus in case the the active element is inside the view container.
868+
// Otherwise we hit an exception while doing the 'small' scrolls swapping.
869+
// For more information:
870+
//
871+
// https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild
872+
// https://bugs.chromium.org/p/chromium/issues/detail?id=432392
873+
if (container.contains(document.activeElement)) {
874+
activeElement.blur();
875+
}
876+
/*recalculate and apply page size.*/
877+
if (diff > 0 && diff <= this.MAX_PERF_SCROLL_DIFF) {
878+
this.moveApplyScrollNext(prevStart);
879+
} else if (diff < 0 && Math.abs(diff) <= this.MAX_PERF_SCROLL_DIFF) {
880+
this.moveApplyScrollPrev(prevStart);
881+
} else {
882+
this.fixedApplyScroll();
883+
}
862884
}
863885
}
886+
864887
return inScrollTop - this.sizesCache[this.state.startIndex];
865888
}
866889

890+
/**
891+
* @hidden
892+
* The function applies an optimized state change for scrolling down/right employing context change with view rearrangement
893+
*/
894+
protected moveApplyScrollNext(prevIndex: number): void {
895+
const start = prevIndex + this.state.chunkSize;
896+
const end = start + this.state.startIndex - prevIndex;
897+
const container = this.dc.instance._vcr as ViewContainerRef;
898+
899+
for (let i = start; i < end && this.igxForOf[i] !== undefined; i++) {
900+
const embView = this._embeddedViews.shift();
901+
const view = container.detach(0);
902+
903+
this.updateTemplateContext(embView.context, i);
904+
container.insert(view);
905+
this._embeddedViews.push(embView);
906+
}
907+
}
908+
909+
/**
910+
* @hidden
911+
* The function applies an optimized state change for scrolling up/left employing context change with view rearrangement
912+
*/
913+
protected moveApplyScrollPrev(prevIndex: number): void {
914+
const container = this.dc.instance._vcr as ViewContainerRef;
915+
for (let i = prevIndex - 1; i >= this.state.startIndex && this.igxForOf[i] !== undefined; i--) {
916+
const embView = this._embeddedViews.pop();
917+
const view = container.detach(container.length - 1);
918+
919+
this.updateTemplateContext(embView.context, i);
920+
container.insert(view, 0);
921+
this._embeddedViews.unshift(embView);
922+
}
923+
}
924+
867925
/**
868926
* @hidden
869927
*/
870928
protected getContextIndex(input) {
871929
return this.isRemote ? this.state.startIndex + this.igxForOf.indexOf(input) : this.igxForOf.indexOf(input);
872930
}
873931

932+
/**
933+
* @hidden
934+
* Function which updates the passed context of an embedded view with the provided index
935+
* from the view container.
936+
* Often, called while handling a scroll event.
937+
*/
938+
protected updateTemplateContext(context: any, index: number = 0): void {
939+
context.$implicit = this.igxForOf[index];
940+
context.index = this.getContextIndex(this.igxForOf[index]);
941+
context.count = this.igxForOf.length;
942+
}
943+
874944
/**
875945
* @hidden
876946
* The function applies an optimized state change through context change for each view
@@ -879,12 +949,8 @@ export class IgxForOfDirective<T> implements OnInit, OnChanges, DoCheck, OnDestr
879949
let j = 0;
880950
const endIndex = this.state.startIndex + this.state.chunkSize;
881951
for (let i = this.state.startIndex; i < endIndex && this.igxForOf[i] !== undefined; i++) {
882-
const input = this.igxForOf[i];
883952
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;
953+
this.updateTemplateContext(embView.context, i);
888954
}
889955
}
890956

@@ -950,12 +1016,8 @@ export class IgxForOfDirective<T> implements OnInit, OnChanges, DoCheck, OnDestr
9501016
endIndex = this.igxForOf.length;
9511017
}
9521018
for (let i = startIndex; i < endIndex && this.igxForOf[i] !== undefined; i++) {
953-
const input = this.igxForOf[i];
9541019
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;
1020+
this.updateTemplateContext(embView.context, i);
9591021
}
9601022
if (prevChunkSize !== this.state.chunkSize) {
9611023
this.onChunkLoad.emit(this.state);
@@ -1612,12 +1674,8 @@ export class IgxGridForOfDirective<T> extends IgxForOfDirective<T> implements On
16121674
}
16131675

16141676
for (let i = startIndex; i < endIndex && this.igxForOf[i] !== undefined; i++) {
1615-
const input = this.igxForOf[i];
16161677
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;
1678+
this.updateTemplateContext(embView.context, i);
16211679
}
16221680
if (prevChunkSize !== this.state.chunkSize) {
16231681
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/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: 4 additions & 7 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();
@@ -457,16 +455,15 @@ describe('IgxGrid Master Detail #grid', () => {
457455
await wait(DEBOUNCETIME);
458456
fix.detectChanges();
459457

460-
let row = grid.getRowByIndex(2);
458+
const row = grid.getRowByIndex(2);
461459
const targetCellElement = grid.getCellByColumn(2, 'ContactName');
462460
UIInteractions.simulateClickAndSelectEvent(targetCellElement);
463461
fix.detectChanges();
464462

465463
UIInteractions.triggerEventHandlerKeyDown('ArrowUp', gridContent);
466-
await wait(DEBOUNCETIME);
467464
fix.detectChanges();
465+
await wait(DEBOUNCETIME);
468466

469-
row = grid.getRowByIndex(2);
470467
const detailRow = row.element.nativeElement.previousElementSibling as HTMLElement;
471468
GridFunctions.verifyMasterDetailRowFocused(detailRow);
472469
expect(GridFunctions.elementInGridView(grid, detailRow)).toBeTruthy();

projects/igniteui-angular/src/lib/grids/grid/grid.multi-row-layout.integration.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ describe('IgxGrid - multi-row-layout Integration #grid - ', () => {
606606
await wait(100);
607607
fixture.detectChanges();
608608

609-
const lastCell = grid.rowList.first.cells.toArray()[5];
609+
const lastCell = grid.rowList.first.cells.toArray()[6];
610610
expect(lastCell.column.field).toBe('Address');
611611
expect(lastCell.column.parent.field).toBe('group4');
612612
expect(Math.round(lastCell.nativeElement.getBoundingClientRect().right) -

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)