Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,10 @@ export class IgxCellCrudState {
let activeElement;
if (cellNode) {
const document = cellNode.getRootNode() as Document | ShadowRoot;
activeElement = document.activeElement as HTMLElement;
activeElement.blur();
if (cellNode.contains(document.activeElement)) {
activeElement = document.activeElement as HTMLElement;
this.grid.tbody.nativeElement.focus();
}
}

const formControl = this.grid.validation.getFormControl(this.cell.id.rowID, this.cell.column.field);
Expand Down
45 changes: 31 additions & 14 deletions projects/igniteui-angular/src/lib/grids/grid-base.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3606,16 +3606,14 @@ export abstract class IgxGridBaseDirective implements GridType,
public _setupListeners() {
const destructor = takeUntil<any>(this.destroy$);
fromEvent(this.nativeElement, 'focusout').pipe(filter(() => !!this.navigation.activeNode), destructor).subscribe((event) => {
if (!this.crudService.cell &&
!!this.navigation.activeNode &&
((event.target === this.tbody.nativeElement && this.navigation.activeNode.row >= 0 &&
this.navigation.activeNode.row < this.dataView.length)
|| (event.target === this.theadRow.nativeElement && this.navigation.activeNode.row === -1)
|| (event.target === this.tfoot.nativeElement && this.navigation.activeNode.row === this.dataView.length)) &&
const activeNode = this.navigation.activeNode;
if (!this.crudService.cell && !!activeNode &&
((event.target === this.tbody.nativeElement && activeNode.row >= 0 &&
activeNode.row < this.dataView.length)
|| (event.target === this.theadRow.nativeElement && activeNode.row === -1)
|| (event.target === this.tfoot.nativeElement && activeNode.row === this.dataView.length)) &&
!(this.rowEditable && this.crudService.rowEditingBlocked && this.crudService.rowInEditMode)) {
this.navigation.lastActiveNode = this.navigation.activeNode;
this.navigation.activeNode = {} as IActiveNode;
this.notifyChanges();
this.clearActiveNode();
}
});
this.rowAddedNotifier.pipe(destructor).subscribe(args => this.refreshGridState(args));
Expand Down Expand Up @@ -6067,10 +6065,7 @@ export abstract class IgxGridBaseDirective implements GridType,
return true;
}

const activeCell = this.gridAPI.grid.navigation.activeNode;
if (activeCell && activeCell.row !== -1) {
this.tbody.nativeElement.focus();
}
this.navigation.restoreActiveNodeFocus();
}

/**
Expand Down Expand Up @@ -6257,7 +6252,20 @@ export abstract class IgxGridBaseDirective implements GridType,
// TODO: do not remove this, as it is used in rowEditTemplate, but mark is as internal and hidden
/* blazorCSSuppress */
public endEdit(commit = true, event?: Event): boolean {
return this.crudService.endEdit(commit, event);
const document = this.nativeElement?.getRootNode() as Document | ShadowRoot;
const focusWithin = this.nativeElement?.contains(document.activeElement);

const success = this.crudService.endEdit(commit, event);

if (focusWithin) {
// restore focus for navigation
this.navigation.restoreActiveNodeFocus();
} else if (this.navigation.activeNode) {
// grid already lost focus, clear active node
this.clearActiveNode();
}

return success;
}

/**
Expand Down Expand Up @@ -7842,4 +7850,13 @@ export abstract class IgxGridBaseDirective implements GridType,
if (!newData || !newData.length) return false;
return Object.keys(oldData[0]).join() !== Object.keys(newData[0]).join();
}

/**
* Clears the current navigation service active node
*/
private clearActiveNode() {
this.navigation.lastActiveNode = this.navigation.activeNode;
this.navigation.activeNode = {} as IActiveNode;
this.notifyChanges();
}
}
17 changes: 17 additions & 0 deletions projects/igniteui-angular/src/lib/grids/grid-navigation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,23 @@ export class IgxGridNavigationService {
return isChanged;
}

/** Focus the Grid section (header, body, footer) depending on the current activeNode */
public restoreActiveNodeFocus() {
if (!this.activeNode || !Object.keys(this.activeNode).length) {
return;
}

if (this.activeNode.row >= 0 && this.activeNode.row < this.grid.dataView.length) {
this.grid.tbody.nativeElement.focus();
}
if (this.activeNode.row === -1) {
this.grid.theadRow.nativeElement.focus();
}
if (this.activeNode.row === this.grid.dataView.length) {
this.grid.tfoot.nativeElement.focus();
}
}

protected getNextPosition(rowIndex: number, colIndex: number, key: string, shift: boolean, ctrl: boolean, event: KeyboardEvent) {
if (!this.isDataRow(rowIndex, true) && (key.indexOf('down') < 0 || key.indexOf('up') < 0) && ctrl) {
return { rowIndex, colIndex };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ describe('IgxGrid - Cell Editing #grid', () => {
expect(cell.value).toBeNull();
});

it('Should not revert cell\' value when doubleClick while in editMode', fakeAsync(() => {
it('Should not revert cell\' value when doubleClick while in editMode', fakeAsync(() => {
const cellElem = fixture.debugElement.query(By.css(CELL_CSS_CLASS));
const firstCell = grid.gridAPI.get_cell_by_index(0, 'fullName');

Expand Down Expand Up @@ -618,7 +618,7 @@ describe('IgxGrid - Cell Editing #grid', () => {
it(`Should properly emit 'cellEditEnter' event`, () => {
spyOn(grid.cellEditEnter, 'emit').and.callThrough();
const cell = grid.gridAPI.get_cell_by_index(0, 'fullName');
let initialRowData = {...cell.row.data};
let initialRowData = { ...cell.row.data };
expect(cell.editMode).toBeFalsy();

UIInteractions.simulateDoubleClickAndSelectEvent(cell);
Expand Down Expand Up @@ -647,7 +647,7 @@ describe('IgxGrid - Cell Editing #grid', () => {

expect(cell.editMode).toBeFalsy();
const cell2 = grid.getCellByColumn(0, 'age');
initialRowData = {...cell2.row.data};
initialRowData = { ...cell2.row.data };
cellArgs = {
cellID: cell2.id,
rowID: cell2.row.key,
Expand All @@ -672,7 +672,7 @@ describe('IgxGrid - Cell Editing #grid', () => {
e.cancel = true;
});
let cell = grid.gridAPI.get_cell_by_index(0, 'fullName');
let initialRowData = {...cell.row.data};
let initialRowData = { ...cell.row.data };
expect(cell.editMode).toBeFalsy();

UIInteractions.simulateDoubleClickAndSelectEvent(cell);
Expand All @@ -682,7 +682,7 @@ describe('IgxGrid - Cell Editing #grid', () => {
cellID: cell.cellID,
rowKey: cell.row.key,
rowID: cell.row.key,
primaryKey: cell.row.key,
primaryKey: cell.row.key,
rowData: initialRowData,
oldValue: 'John Brown',
cancel: true,
Expand All @@ -697,7 +697,7 @@ describe('IgxGrid - Cell Editing #grid', () => {

// press enter on a cell
cell = grid.gridAPI.get_cell_by_index(0, 'age');
initialRowData = {...cell.row.data};
initialRowData = { ...cell.row.data };
UIInteractions.simulateClickAndSelectEvent(cell);
fixture.detectChanges();

Expand Down Expand Up @@ -725,7 +725,7 @@ describe('IgxGrid - Cell Editing #grid', () => {
it(`Should properly emit 'cellEditExit' event`, () => {
spyOn(grid.cellEditExit, 'emit').and.callThrough();
let cell = grid.gridAPI.get_cell_by_index(0, 'fullName');
let initialRowData = {...cell.row.data};
let initialRowData = { ...cell.row.data };
expect(cell.editMode).toBeFalsy();

UIInteractions.simulateDoubleClickAndSelectEvent(cell);
Expand Down Expand Up @@ -758,7 +758,7 @@ describe('IgxGrid - Cell Editing #grid', () => {

expect(cell.editMode).toBeFalsy();
cell = grid.gridAPI.get_cell_by_index(0, 'age');
initialRowData = {...cell.row.data};
initialRowData = { ...cell.row.data };
cellArgs = {
cellID: cell.cellID,
rowKey: cell.row.key,
Expand Down Expand Up @@ -846,7 +846,7 @@ describe('IgxGrid - Cell Editing #grid', () => {
e.cancel = true;
});
const cell = grid.gridAPI.get_cell_by_index(0, 'fullName');
const initialRowData = {...cell.row.data};
const initialRowData = { ...cell.row.data };

UIInteractions.simulateDoubleClickAndSelectEvent(cell);
fixture.detectChanges();
Expand Down Expand Up @@ -965,7 +965,7 @@ describe('IgxGrid - Cell Editing #grid', () => {
grid.cellEdit.subscribe((e: IGridEditEventArgs) => {
const rowIndex: number = e.cellID.rowIndex;
const row = grid.gridAPI.get_row_by_index(rowIndex);
grid.updateRow({[(row as any).columns[e.cellID.columnID].field]: e.newValue}, row.key);
grid.updateRow({ [(row as any).columns[e.cellID.columnID].field]: e.newValue }, row.key);
e.cancel = true;
});

Expand Down Expand Up @@ -1026,7 +1026,7 @@ describe('IgxGrid - Cell Editing #grid', () => {
it(`Should properly emit 'cellEditExit' event`, () => {
spyOn(grid.cellEditExit, 'emit').and.callThrough();
const cell = grid.gridAPI.get_cell_by_index(0, 'fullName');
const initialRowData = {...cell.row.data};
const initialRowData = { ...cell.row.data };

UIInteractions.simulateDoubleClickAndSelectEvent(cell);
fixture.detectChanges();
Expand Down Expand Up @@ -1276,6 +1276,38 @@ describe('IgxGrid - Cell Editing #grid', () => {
expect(cell.value).toBe('Rick Gilmore');
expect(grid.gridAPI.crudService.cell).toBeNull();
});

it('should clean active state when endEdit on focusout of the grid', async () => {
const handleFocusOut = ($event: FocusEvent) => {
if (!$event.relatedTarget || !grid.nativeElement.contains($event.relatedTarget as Node)) {
grid.endEdit(true);
grid.clearCellSelection();
}
};
grid.nativeElement.addEventListener('focusout', handleFocusOut);
const cell = grid.gridAPI.get_cell_by_index(0, 'fullName');
const cellDom = fixture.debugElement.queryAll(By.css(CELL_CSS_CLASS))[0];

UIInteractions.simulateDoubleClickAndSelectEvent(cell);
fixture.detectChanges();
await wait(16 /* igxFocus raf */);
expect(cell.editMode).toBe(true);

const editTemplate = cellDom.query(By.css('input'));
expect(document.activeElement).toBe(editTemplate.nativeElement);

UIInteractions.clickAndSendInputElementValue(editTemplate, 'Edit Cell');
fixture.detectChanges();

editTemplate.nativeElement.blur();
fixture.detectChanges();

expect(cell.editMode).toBe(false);
expect(cell.value).toBe('Edit Cell');
expect(Object.keys(grid.navigation.activeNode).length).toBe(0);

grid.nativeElement.removeEventListener('focusout', handleFocusOut);
});
});

it('Cell editing (when rowEditable=false) - default column editable value is false', fakeAsync(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2244,7 +2244,7 @@ describe('IgxGrid - Row Editing #grid', () => {
const gridContent = GridFunctions.getGridContent(fix);

const grid = fix.componentInstance.grid;
let cellElem = grid.gridAPI.get_cell_by_index(0, 'ProductName');
const cellElem = grid.gridAPI.get_cell_by_index(0, 'ProductName');
spyOn(grid.gridAPI.crudService, 'endEdit').and.callThrough();
UIInteractions.simulateDoubleClickAndSelectEvent(cellElem);
fix.detectChanges();
Expand All @@ -2254,12 +2254,15 @@ describe('IgxGrid - Row Editing #grid', () => {
UIInteractions.triggerEventHandlerKeyDown('tab', gridContent);
fix.detectChanges();

cellElem = grid.gridAPI.get_cell_by_index(0, 'ReorderLevel');
expect(parseInt(GridFunctions.getRowEditingBannerText(fix), 10)).toEqual(1);

fix.componentInstance.buttons.last.element.nativeElement.click();
expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalled();
expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalledTimes(1);

fix.detectChanges();
expect(cellElem.active).toBeTruthy();
expect(grid.nativeElement.contains(document.activeElement)).toBeTrue();
});

it('Empty template', () => {
Expand Down
45 changes: 33 additions & 12 deletions src/app/grid-cellEditing/grid-cellEditing.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ <h4 class="sample-title">Grid with primary key ProductID</h4>
<div class="density-chooser">
<igx-buttongroup [values]="sizes" (selected)="selectDensity($event)"></igx-buttongroup>
</div>
<igx-grid (cellEdit)='cellEdit($event)' #grid1 [filterMode]="'excelStyleFilter'" [moving]="true" locale="fr-FR" [data]="data" [primaryKey]="'ProductID'" [rowEditable]="true" width="1500px" height="550px" [rowSelection]="selectionMode">
<igx-grid (focusout)="handleFocusOut($event)" (cellEdit)='cellEdit($event)' #grid1 [filterMode]="'excelStyleFilter'" [moving]="true" locale="fr-FR" [data]="data" [primaryKey]="'ProductID'" [rowEditable]="true" width="1500px" height="550px" [rowSelection]="selectionMode">
<igx-column field="OrderDate" width="200px" [dataType]="'date'" [hidden]="orderDateHidden" [filterable]="true" [hasSummary]="true" [summaries]="earliest" [editable]="true" [resizable]="true">
<!-- <ng-template igxCell let-cell="cell" let-val>
{{val | date}}
Expand All @@ -24,24 +24,45 @@ <h4 class="sample-title">Grid with primary key ProductID</h4>
</igx-column> -->
<igx-column field="ReorderLevel" width="200px" [sortable]="true" [filterable]="true" [editable]="true" [dataType]="'number'" [hasSummary]="false">
</igx-column>
<igx-column field="ProductName" width="200px" [groupable]="kk" [header]="pname" [sortable]="true" [dataType]="'string'" [editable]="true" [resizable]="true">
<igx-column field="ProductName" width="200px" [groupable]="groupable" [header]="pname" [sortable]="true" [dataType]="'string'" [editable]="true" [resizable]="true">
</igx-column>
<igx-column required field="UnitsInStock" header="UnitsInStock" width="200px" [dataType]="'number'" [editable]="true" [sortable]="true" [hasSummary]="false">
</igx-column>
<igx-column field="Discontinued" header="Discontinued" [dataType]="'boolean'" width="200px" [hasSummary]="true" [editable]="true" [resizable]="true">
</igx-column>
<igx-paginator></igx-paginator>
</igx-grid>
<input igxButton="contained" id="updBtn" type="button" (click)="updRecord()" value="Update cell/record">
<button igxButton="contained" (click)="addRow()">Add Row</button>
<button igxButton="contained" (click)="updateCell()">Update Cell</button>
<button igxButton="contained" (click)="pin()">Pin/Unpin</button>
<button igxButton="contained" (click)="hideColumn()">Hide/Show OrderDate</button>
<button igxButton="contained" (click)="updateSpecificRow()">Update Row by ID</button>
<button igxButton="contained" (click)="enDisSummaries()">Enable/DisableSummaries</button>
<button igxButton="contained" (click)="kk = !kk">groupable</button>
<button igxButton="contained" (click)="changeFormatOptions()">Change formatting</button>
<input type="text" [(ngModel)]="pname">
<div class="sample-actions">
<input igxButton="contained" id="updBtn" type="button" (click)="updRecord()" value="Update cell/record">
<button igxButton="contained" (click)="addRow()">Add Row</button>
<button igxButton="contained" (click)="updateCell()">Update Cell</button>
<button igxButton="contained" (click)="pin()">Pin/Unpin</button>
<button igxButton="contained" (click)="hideColumn()">Hide/Show OrderDate</button>
<button igxButton="contained" (click)="updateSpecificRow()">Update Row by ID</button>
<button igxButton="contained" (click)="enDisSummaries()">Enable/DisableSummaries</button>
<button igxButton="contained" (click)="groupable = !groupable">groupable</button>
<button igxButton="contained" (click)="changeFormatOptions()">Change formatting</button>
<input type="text" [(ngModel)]="pname">
</div>
<!-- TODO: Props panel -->
<div class="sample-actions">
<igx-switch [(ngModel)]="exitEditOnBlur">Exit Edit On Blur</igx-switch>

<igx-select [(ngModel)]="grid1.cellSelection" type="border">
<label igxLabel>Cell Selection</label>
@for (item of selectionModes; track item) {
<igx-select-item [value]="item">{{ item }}</igx-select-item>
}
</igx-select>

<igx-select [(ngModel)]="grid1.rowSelection" type="border">
<label igxLabel>Row Selection</label>
@for (item of selectionModes; track item) {
<igx-select-item [value]="item">{{ item }}</igx-select-item>
}
</igx-select>
</div>

<h4 class="sample-title">Grid without PK</h4>
<igx-grid (cellEdit)='cellEdit($event)' #grid [data]="dataWithoutPK" class="grid-size" width="800px" height="550px" [moving]="true" [rowSelection]="selectionMode">
<igx-column [pinned]="true">
Expand Down
19 changes: 5 additions & 14 deletions src/app/grid-cellEditing/grid-cellEditing.component.scss
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
button {
margin: 0.2rem;
}

[igxButton="contained"] {
margin: 0.5rem 0.5rem 0.5rem 0;

&:nth-child(12) {
margin-bottom: 3rem;
}

&:last-child {
margin-bottom: 3rem;
}
.sample-actions {
display: flex;
flex-wrap: wrap;
margin: 1rem 0;
gap: 0.5rem;
}

.density-chooser {
Expand Down
Loading
Loading