Skip to content

Commit 1e0a109

Browse files
authored
Merge branch 'master' into mkirova/fix-7277
2 parents 97b7d2f + e7eda38 commit 1e0a109

26 files changed

+275
-156
lines changed

CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ All notable changes for each version of this project will be documented in this
145145
- The first cell in the first body row;
146146
- The first cell in column summary if exists;
147147
- Pager UI;
148-
- `onGridKeydown` event is deprecated. Now you can directly bind to keydown on the IgxGrid component in order to perform custom keyboard navigation.
149148

150149
- `IgxCombo`:
151150
- Added `autoFocusSearch` input that allows to manipulate the combo's opening behavior. When the property is `true` (by default), the combo's search input is focused on open. When set to `false`, the focus goes to the combo items container, which can be used to prevent the software keyboard from activating on mobile devices when opening the combo.

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/columns/column.component.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ export class IgxColumnComponent implements AfterContentInit {
432432
* let columnMaxWidth = this.column.width;
433433
* ```
434434
* ```html
435-
* <igx-column [maxWidth] = "'75%'"></igx-column>
435+
* <igx-column [maxWidth] = "'150px'"></igx-column>
436436
* ```
437437
* @memberof IgxColumnComponent
438438
*/
@@ -446,7 +446,7 @@ export class IgxColumnComponent implements AfterContentInit {
446446
* let columnMinWidth = this.column.minWidth;
447447
* ```
448448
* ```html
449-
* <igx-column [minWidth] = "'15%'"></igx-column>
449+
* <igx-column [minWidth] = "'100px'"></igx-column>
450450
* ```
451451
* @memberof IgxColumnComponent
452452
*/
@@ -1736,7 +1736,6 @@ export class IgxColumnComponent implements AfterContentInit {
17361736
*/
17371737
public autosize() {
17381738
if (!this.columnGroup) {
1739-
17401739
this.width = this.getLargestCellWidth();
17411740
this.grid.reflow();
17421741
}

projects/igniteui-angular/src/lib/grids/common/events.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { IgxGridCellComponent } from '../cell.component';
77
import { IgxColumnComponent } from '../columns/column.component';
88
import { IgxGridBaseDirective } from '../grid-base.directive';
99
import { IgxRowDirective } from '../row.directive';
10-
10+
export { GridSelectionRange } from '../selection/selection.service';
1111

1212
export interface IGridClipboardEvent {
1313
data: any[];

projects/igniteui-angular/src/lib/grids/grid-base.directive.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ import { IgxGridColumnResizerComponent } from './resizing/resizer.component';
100100
import { IgxGridFilteringRowComponent } from './filtering/base/grid-filtering-row.component';
101101
import { CharSeparatedValueData } from '../services/csv/char-separated-value-data';
102102
import { IgxColumnResizingService } from './resizing/resizing.service';
103-
import { DeprecateProperty } from '../core/deprecateDecorators';
104103
import { IFilteringStrategy } from '../data-operations/filtering-strategy';
105104
import {
106105
IgxRowExpandedIndicatorDirective, IgxRowCollapsedIndicatorDirective,
@@ -1342,7 +1341,6 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
13421341
* <igx-grid (onGridKeydown)="customKeydown($event)"></igx-grid>
13431342
* ```
13441343
*/
1345-
@DeprecateProperty('onGridKeydown event is deprecated. Now you can directly bind to keydown on the IgxGrid component.')
13461344
@Output()
13471345
public onGridKeydown = new EventEmitter<IGridKeydownEventArgs>();
13481346

@@ -2806,7 +2804,6 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
28062804

28072805
_setupListeners() {
28082806
const destructor = takeUntil<any>(this.destroy$);
2809-
28102807
this.onRowAdded.pipe(destructor).subscribe(args => this.refreshGridState(args));
28112808
this.onRowDeleted.pipe(destructor).subscribe(args => {
28122809
this.summaryService.deleteOperation = true;
@@ -5683,10 +5680,21 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
56835680
const shouldScrollVertically = this.navigation.shouldPerformVerticalScroll(rowIndex, visibleColIndex);
56845681
const shouldScrollHorizontally = this.navigation.shouldPerformHorizontalScroll(visibleColIndex, rowIndex);
56855682
if (shouldScrollVertically) {
5686-
this.navigation.performVerticalScrollToCell(rowIndex, visibleColIndex,
5687-
() => { this.navigateTo(rowIndex, visibleColIndex, cb); });
5683+
this.navigation.performVerticalScrollToCell(rowIndex, visibleColIndex, () => {
5684+
if (shouldScrollHorizontally) {
5685+
this.navigation.performHorizontalScrollToCell(visibleColIndex, () =>
5686+
this.executeCallback(rowIndex, visibleColIndex, cb));
5687+
} else {
5688+
this.executeCallback(rowIndex, visibleColIndex, cb);
5689+
}});
56885690
} else if (shouldScrollHorizontally) {
5689-
this.navigation.performHorizontalScrollToCell(visibleColIndex, () => { this.navigateTo(rowIndex, visibleColIndex, cb); });
5691+
this.navigation.performHorizontalScrollToCell(visibleColIndex, () => {
5692+
if (shouldScrollVertically) {
5693+
this.navigation.performVerticalScrollToCell(rowIndex, visibleColIndex, () =>
5694+
this.executeCallback(rowIndex, visibleColIndex, cb));
5695+
} else {
5696+
this.executeCallback(rowIndex, visibleColIndex, cb);
5697+
}});
56905698
} else {
56915699
this.executeCallback(rowIndex, visibleColIndex, cb);
56925700
}

projects/igniteui-angular/src/lib/grids/grid-mrl-navigation.service.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { IgxGridBaseDirective } from './grid-base.directive';
33
import { first } from 'rxjs/operators';
44
import { IgxColumnComponent } from './columns/column.component';
55
import { IgxGridNavigationService } from './grid-navigation.service';
6-
import { HORIZONTAL_NAV_KEYS } from '../core/utils';
6+
import { HORIZONTAL_NAV_KEYS, HEADER_KEYS } from '../core/utils';
77

88
/** @hidden */
99
@Injectable()
@@ -35,11 +35,11 @@ export class IgxGridMRLNavigationService extends IgxGridNavigationService {
3535
break;
3636
case 'arrowleft':
3737
case 'left':
38-
colIndex = ctrl ? this.firstIndexPerRow : this.getNextHorizontalCellPositon(true).column;
38+
colIndex = ctrl ? this.firstIndexPerRow : this.getNextHorizontalCellPosition(true).column;
3939
break;
4040
case 'arrowright':
4141
case 'right':
42-
colIndex = ctrl ? this.lastIndexPerRow : this.getNextHorizontalCellPositon().column;
42+
colIndex = ctrl ? this.lastIndexPerRow : this.getNextHorizontalCellPosition().column;
4343
break;
4444
case 'arrowup':
4545
case 'up':
@@ -162,7 +162,7 @@ export class IgxGridMRLNavigationService extends IgxGridNavigationService {
162162
});
163163
}
164164

165-
getNextHorizontalCellPositon(previous = false) {
165+
getNextHorizontalCellPosition(previous = false) {
166166
const parent = this.parentByChildIndex(this.activeNode.column);
167167
if (!this.hasNextHorizontalPosition(previous, parent)) {
168168
return { row: this.activeNode.row, column: this.activeNode.column };
@@ -214,11 +214,15 @@ export class IgxGridMRLNavigationService extends IgxGridNavigationService {
214214

215215
headerNavigation(event: KeyboardEvent) {
216216
const key = event.key.toLowerCase();
217+
if (!HEADER_KEYS.has(key)) { return; }
218+
event.preventDefault();
217219
if (!this.activeNode.layout) {
218220
this.activeNode.layout = this.layout(this.activeNode.column || 0);
219221
}
220-
if (key.includes('down') || key.includes('up')) {
221-
event.preventDefault();
222+
const alt = event.altKey;
223+
const ctrl = event.ctrlKey;
224+
this.performHeaderKeyCombination(this.grid.getColumnByVisibleIndex(this.activeNode.column), key, event.shiftKey, ctrl, alt);
225+
if (!ctrl && !alt && (key.includes('down') || key.includes('up'))) {
222226
const children = this.parentByChildIndex(this.activeNode.column).children;
223227
const col = key.includes('down') ? this.getNextRowIndex(children, false) : this.getPreviousRowIndex(children, false);
224228
if (!col) { return; }
@@ -232,14 +236,13 @@ export class IgxGridMRLNavigationService extends IgxGridNavigationService {
232236

233237
protected horizontalNav(event: KeyboardEvent, key: string, rowIndex: number) {
234238
const ctrl = event.ctrlKey;
235-
if (!HORIZONTAL_NAV_KEYS.has(key)) { return; }
236-
event.preventDefault();
239+
if (!HORIZONTAL_NAV_KEYS.has(key) || event.altKey) { return; }
237240
this.activeNode.row = rowIndex;
238241
if ((key.includes('left') || key === 'home') && this.activeNode.column > 0) {
239-
this.activeNode.column = ctrl || key === 'home' ? this.firstIndexPerRow : this.getNextHorizontalCellPositon(true).column;
242+
this.activeNode.column = ctrl || key === 'home' ? this.firstIndexPerRow : this.getNextHorizontalCellPosition(true).column;
240243
}
241244
if ((key.includes('right') || key === 'end') && this.activeNode.column !== this.lastIndexPerRow) {
242-
this.activeNode.column = ctrl || key === 'end' ? this.lastIndexPerRow : this.getNextHorizontalCellPositon().column;
245+
this.activeNode.column = ctrl || key === 'end' ? this.lastIndexPerRow : this.getNextHorizontalCellPosition().column;
243246
}
244247
const newLayout = this.layout(this.activeNode.column);
245248
Object.assign(this.activeNode.layout, {colStart: newLayout.colStart, rowEnd: newLayout.rowEnd});

0 commit comments

Comments
 (0)