diff --git a/CHANGELOG.md b/CHANGELOG.md index fcec5bb04c2..55823a2974a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ All notable changes for each version of this project will be documented in this ### Bug Fixes +- `IgxGridComponent`, `IgxTreeGridComponent`, `IgxHierarchicalGridComponent` + - Cells in an uncommitted new row now apply column formatting after leaving the cell editor, while empty cells retain their column-header placeholders. +- **Forms** + - `igx-checkbox`, `igx-switch` and `igx-radio-group` no longer turn invalid while the bound Signal Forms field is untouched and pristine, e.g. right after `reset()` or when a cross-field rule makes a pristine field required. The invalid state now shows only once the field is touched or dirty, as with reactive and template-driven forms. - `IgxAccordionComponent` - When `singleBranchExpand` is set initially and the panels are rendered with `@for`, the panels collapsed on init no longer keep `aria-expanded="true"` and no `ExpressionChangedAfterItHasBeenCheckedError` is thrown in development mode. - `IgxCarouselComponent` diff --git a/projects/igniteui-angular/grids/core/src/cell.component.html b/projects/igniteui-angular/grids/core/src/cell.component.html index 7764bc012d8..c8478205197 100644 --- a/projects/igniteui-angular/grids/core/src/cell.component.html +++ b/projects/igniteui-angular/grids/core/src/cell.component.html @@ -128,7 +128,30 @@ [containerClass]="'igx-grid__td-text'" [metadata]="searchMetadata" > - {{ !isEmptyAddRowCell ? value : column.header || column.field }} + {{ !isEmptyAddRowCell + ? (formatter + ? (value | columnFormatter: formatter : rowData) + : column.dataType === 'number' + ? (value | number: column.pipeArgs.digitsInfo : grid.locale) + : column.dataType === 'date' || + column.dataType === 'time' || + column.dataType === 'dateTime' + ? (value + | date + : column.pipeArgs.format + : column.pipeArgs.timezone + : grid.locale) + : column.dataType === 'currency' + ? (value + | currency + : currencyCode + : column.pipeArgs.display + : column.pipeArgs.digitsInfo + : grid.locale) + : column.dataType === 'percent' + ? (value | percent: column.pipeArgs.digitsInfo : grid.locale) + : value) + : column.header || column.field }} } diff --git a/projects/igniteui-angular/grids/grid/src/expandable-cell.component.html b/projects/igniteui-angular/grids/grid/src/expandable-cell.component.html index 30ac3ff452e..1e4b688f1b8 100644 --- a/projects/igniteui-angular/grids/grid/src/expandable-cell.component.html +++ b/projects/igniteui-angular/grids/grid/src/expandable-cell.component.html @@ -134,7 +134,30 @@ [containerClass]="'igx-grid__td-text'" [metadata]="searchMetadata" > - {{ value ? value : column.header || column.field }} + {{ !isEmptyAddRowCell + ? (formatter + ? (value | columnFormatter: formatter : rowData) + : column.dataType === 'number' + ? (value | number: column.pipeArgs.digitsInfo : grid.locale) + : column.dataType === 'date' || + column.dataType === 'time' || + column.dataType === 'dateTime' + ? (value + | date + : column.pipeArgs.format + : column.pipeArgs.timezone + : grid.locale) + : column.dataType === 'currency' + ? (value + | currency + : currencyCode + : column.pipeArgs.display + : column.pipeArgs.digitsInfo + : grid.locale) + : column.dataType === 'percent' + ? (value | percent: column.pipeArgs.digitsInfo : grid.locale) + : value) + : column.header || column.field }} } diff --git a/projects/igniteui-angular/grids/grid/src/grid-add-row.spec.ts b/projects/igniteui-angular/grids/grid/src/grid-add-row.spec.ts index 78c220a1e99..c5893aaf08f 100644 --- a/projects/igniteui-angular/grids/grid/src/grid-add-row.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/grid-add-row.spec.ts @@ -15,7 +15,7 @@ import { advance, useFakeClock } from '../../../test-utils/helper-utils.spec'; import { IgxGridRowComponent } from './grid-row.component'; import { takeUntil, first } from 'rxjs/operators'; import { Subject } from 'rxjs'; -import { DefaultSortingStrategy, IgxStringFilteringOperand, SortingDirection, TransactionType } from 'igniteui-angular/core'; +import { DefaultSortingStrategy, GridColumnDataType, IgxStringFilteringOperand, SortingDirection, TransactionType } from 'igniteui-angular/core'; import { IgxGridMRLNavigationService } from 'igniteui-angular/grids/core'; import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { DefaultGridMasterDetailComponent, ColumnLayoutTestComponent } from '../../../test-utils/grid-shared-components.spec'; @@ -66,6 +66,37 @@ describe('IgxGrid - Row Adding #grid', () => { actionStrip = fixture.componentInstance.actionStrip; }); + [ + { dataType: GridColumnDataType.Date, value: new Date(2024, 0, 23), pipeArgs: { format: 'dd/MM/yyyy' }, expected: '23/01/2024' }, + { dataType: GridColumnDataType.Number, value: 0, pipeArgs: { digitsInfo: '1.2-2' }, expected: '0.00' }, + { dataType: GridColumnDataType.Percent, value: 0.25, pipeArgs: { digitsInfo: '1.1-1' }, expected: '25.0%' }, + { dataType: GridColumnDataType.Currency, value: 12.5, pipeArgs: { currencyCode: 'USD', digitsInfo: '1.2-2' }, expected: '$12.50' } + ].forEach(({ dataType, value, pipeArgs, expected }) => { + it(`Should format ${dataType} cells after leaving the editor in an uncommitted add row`, () => { + fixture.componentInstance.data = []; + const column = grid.getColumnByName('ContactName'); + column.dataType = dataType; + column.pipeArgs = pipeArgs; + fixture.detectChanges(); + + grid.beginAddRowById(null); + fixture.detectChanges(); + const cell = grid.gridAPI.get_cell_by_index(0, 'ContactName'); + expect(cell.nativeElement?.textContent.trim()).toBe('ContactName'); + + cell.setEditMode(true); + fixture.detectChanges(); + cell.editValue = value; + grid.gridAPI.get_cell_by_index(0, 'CompanyName').setEditMode(true); + fixture.detectChanges(); + + expect(grid.gridAPI.get_row_by_index(0).addRowUI).toBe(true); + expect(cell.editMode).toBe(false); + expect(cell.value).toEqual(value); + expect(cell.nativeElement?.textContent.trim()).toBe(expected); + }); + }); + it('Should be able to enter add row mode on action strip click', () => { const row = grid.rowList.first; actionStrip.show(row); @@ -928,6 +959,32 @@ describe('IgxGrid - Row Adding #grid', () => { expect(row.expanded).toBe(false); expect(grid.gridAPI.get_row_by_index(1).addRowUI).toBe(true); }); + + it('Should format the expandable first column cell after leaving the editor in an uncommitted add row', () => { + grid.rowEditable = true; + fixture.componentInstance.data = []; + const column = grid.getColumnByName('ContactName'); + column.dataType = GridColumnDataType.Number; + column.pipeArgs = { digitsInfo: '1.2-2' }; + fixture.detectChanges(); + + grid.beginAddRowById(null); + fixture.detectChanges(); + const cell = grid.gridAPI.get_cell_by_index(0, 'ContactName'); + expect(cell.nativeElement?.tagName.toLowerCase()).toBe('igx-expandable-grid-cell'); + expect(cell.nativeElement?.querySelector('.igx-grid__td-text')?.textContent.trim()).toBe('ContactName'); + + cell.setEditMode(true); + fixture.detectChanges(); + cell.editValue = 0; + grid.gridAPI.get_cell_by_index(0, 'CompanyName').setEditMode(true); + fixture.detectChanges(); + + expect(grid.gridAPI.get_row_by_index(0).addRowUI).toBe(true); + expect(cell.editMode).toBe(false); + expect(cell.value).toBe(0); + expect(cell.nativeElement?.querySelector('.igx-grid__td-text')?.textContent.trim()).toBe('0.00'); + }); }); describe('Row Adding - MRL tests', () => { diff --git a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid-add-row.spec.ts b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid-add-row.spec.ts index c48fe37a39e..52b4c6482ed 100644 --- a/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid-add-row.spec.ts +++ b/projects/igniteui-angular/grids/hierarchical-grid/src/hierarchical-grid-add-row.spec.ts @@ -6,6 +6,7 @@ import { wait } from '../../../test-utils/ui-interactions.spec'; import { By } from '@angular/platform-browser'; import { IgxHierarchicalGridComponent } from './hierarchical-grid.component'; import { IgxGridNavigationService } from 'igniteui-angular/grids/core'; +import { GridColumnDataType } from 'igniteui-angular/core'; import { describe, it, expect, beforeEach } from 'vitest'; describe('IgxHierarchicalGrid - Add Row UI #hGrid', () => { @@ -38,6 +39,29 @@ describe('IgxHierarchicalGrid - Add Row UI #hGrid', () => { _actionStrip = fixture.componentInstance.actionStrip; }); + it('Should format dates after leaving the editor in an uncommitted add row', () => { + const column = hierarchicalGrid.getColumnByName('ProductName'); + column.dataType = GridColumnDataType.Date; + column.pipeArgs = { format: 'dd/MM/yyyy' }; + fixture.componentInstance.data = []; + fixture.detectChanges(); + + hierarchicalGrid.beginAddRowById(null); + fixture.detectChanges(); + const cell = hierarchicalGrid.gridAPI.get_cell_by_index(0, 'ProductName'); + expect(cell.nativeElement?.textContent.trim()).toBe('ProductName'); + + cell.setEditMode(true); + fixture.detectChanges(); + cell.editValue = new Date(2024, 0, 23); + hierarchicalGrid.gridAPI.get_cell_by_index(0, 'ChildLevels').setEditMode(true); + fixture.detectChanges(); + + expect(hierarchicalGrid.gridAPI.get_row_by_index(0).addRowUI).toBe(true); + expect(cell.editMode).toBe(false); + expect(cell.nativeElement?.textContent.trim()).toBe('23/01/2024'); + }); + it('Should collapse an expanded record when beginAddRow is called for it', () => { const row = hierarchicalGrid.rowList.first; hierarchicalGrid.expandRow(row.key); diff --git a/projects/igniteui-angular/grids/tree-grid/src/tree-cell.component.html b/projects/igniteui-angular/grids/tree-grid/src/tree-cell.component.html index 5307822ece4..31b7df4c020 100644 --- a/projects/igniteui-angular/grids/tree-grid/src/tree-cell.component.html +++ b/projects/igniteui-angular/grids/tree-grid/src/tree-cell.component.html @@ -126,7 +126,30 @@ [containerClass]="'igx-grid__td-text'" [metadata]="searchMetadata" > - {{ !isEmptyAddRowCell ? value : column.header || column.field }} + {{ !isEmptyAddRowCell + ? (formatter + ? (value | columnFormatter: formatter : rowData) + : column.dataType === 'number' + ? (value | number: column.pipeArgs.digitsInfo : grid.locale) + : column.dataType === 'date' || + column.dataType === 'time' || + column.dataType === 'dateTime' + ? (value + | date + : column.pipeArgs.format + : column.pipeArgs.timezone + : grid.locale) + : column.dataType === 'currency' + ? (value + | currency + : currencyCode + : column.pipeArgs.display + : column.pipeArgs.digitsInfo + : grid.locale) + : column.dataType === 'percent' + ? (value | percent: column.pipeArgs.digitsInfo : grid.locale) + : value) + : column.header || column.field }} } diff --git a/projects/igniteui-angular/grids/tree-grid/src/tree-grid-add-row-ui.spec.ts b/projects/igniteui-angular/grids/tree-grid/src/tree-grid-add-row-ui.spec.ts index 048ea1152d2..991589d9826 100644 --- a/projects/igniteui-angular/grids/tree-grid/src/tree-grid-add-row-ui.spec.ts +++ b/projects/igniteui-angular/grids/tree-grid/src/tree-grid-add-row-ui.spec.ts @@ -9,6 +9,7 @@ import { IgxTreeGridRowComponent } from './tree-grid-row.component'; import { first } from 'rxjs/operators'; import { wait } from '../../../test-utils/ui-interactions.spec'; import { IRowDataCancelableEventArgs } from 'igniteui-angular/grids/core'; +import { GridColumnDataType } from 'igniteui-angular/core'; import { describe, it, expect, beforeEach, vi } from 'vitest'; describe('IgxTreeGrid - Add Row UI #tGrid', () => { @@ -40,6 +41,32 @@ describe('IgxTreeGrid - Add Row UI #tGrid', () => { actionStrip = fix.componentInstance.actionStrip; }); + it('should format dates after leaving the editor in an uncommitted add row', () => { + fix.componentInstance.data = []; + treeGrid.getColumnByName('ID').hidden = true; + treeGrid.getColumnByName('ParentID').hidden = true; + const column = treeGrid.getColumnByName('Name'); + column.dataType = GridColumnDataType.Date; + column.pipeArgs = { format: 'dd/MM/yyyy' }; + fix.detectChanges(); + + treeGrid.beginAddRowById(null); + fix.detectChanges(); + const cell = treeGrid.gridAPI.get_cell_by_index(0, 'Name'); + expect(cell.nativeElement?.tagName.toLowerCase()).toBe('igx-tree-grid-cell'); + expect(cell.nativeElement.querySelector('.igx-grid__td-text').textContent.trim()).toBe('Name'); + + cell.setEditMode(true); + fix.detectChanges(); + cell.editValue = new Date(2024, 0, 23); + treeGrid.gridAPI.get_cell_by_index(0, 'JobTitle').setEditMode(true); + fix.detectChanges(); + + expect(treeGrid.gridAPI.get_row_by_index(0).addRowUI).toBe(true); + expect(cell.editMode).toBe(false); + expect(cell.nativeElement.querySelector('.igx-grid__td-text').textContent.trim()).toBe('23/01/2024'); + }); + it('should show action strip "add row" button only for root level rows.', () => { actionStrip.show(treeGrid.rowList.first); fix.detectChanges(); diff --git a/src/app/grid-add-row/grid-add-row.sample.html b/src/app/grid-add-row/grid-add-row.sample.html index abd3b0fe7b3..b79ee0bbf09 100644 --- a/src/app/grid-add-row/grid-add-row.sample.html +++ b/src/app/grid-add-row/grid-add-row.sample.html @@ -5,7 +5,7 @@ [data]="data" [width]="'800px'" [height]="'500px'" - + [primaryKey]="'ID'" (mouseleave)="onMouseLeave(actionstrip)" (mouseover)="onMouseOver($event, grid, actionstrip)" [rowEditable]="true" diff --git a/src/app/grid-add-row/grid-add-row.sample.ts b/src/app/grid-add-row/grid-add-row.sample.ts index 4a2caa04259..9d9802a7b06 100644 --- a/src/app/grid-add-row/grid-add-row.sample.ts +++ b/src/app/grid-add-row/grid-add-row.sample.ts @@ -77,7 +77,12 @@ export class GridAddRowSampleComponent implements OnInit { } public add() { - this.grid.addRow({ ID: 'ALFKI', CompanyName: 'Alfreds Futterkiste', ContactName: 'Maria Anders', ContactTitle: 'Sales Representative'}); + let id = 'TEST'; + let suffix = 1; + while (this.grid.data.some(record => record.ID === id)) { + id = `TEST-${suffix++}`; + } + this.grid.addRow({ ID: id, CompanyName: 'Test Company Name', ContactName: 'Test Contact Name', ContactTitle: 'Test Title'}); } public beginAddRowAtIndex(index: string) {