Skip to content

Commit cd2b8ca

Browse files
authored
Grid Cell merging (#16024)
1 parent 4ef65d2 commit cd2b8ca

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+10477
-252
lines changed

CHANGELOG.md

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,59 @@
22

33
All notable changes for each version of this project will be documented in this file.
44

5+
56
## 20.1.0
7+
68
### New Features
7-
- `IgxCarousel`
8-
- Added `select` method overload accepting index.
9-
```ts
10-
this.carousel.select(2, Direction.NEXT);
11-
```
9+
1210
- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid`
11+
- Introduced a new cell merging feature that allows you to configure and merge cells in a column based on same data or other custom condition, into a single cell.
12+
13+
It can be enabled on the individual columns:
14+
15+
```html
16+
<igx-column field="field" [merge]="true"></igx-column>
17+
```
18+
The merging can be configured on the grid level to apply either:
19+
- `onSort` - only when the column is sorted.
20+
- `always` - always, regardless of data operations.
21+
22+
```html
23+
<igx-grid [cellMergeMode]="'always'">
24+
</igx-grid>
25+
```
26+
27+
The default `cellMergeMode` is `onSort`.
28+
29+
The functionality can be modified by setting a custom `mergeStrategy` on the grid, in case some other merge conditions or logic is needed for a custom scenario.
30+
31+
It's possible also to set a `mergeComparer` on the individual columns, in case some custom handling is needed for a particular data field.
32+
1333
- Added ability to pin individual columns to a specific side (start or end of the grid), so that you can now have pinning from both sides. This can be done either declaratively by setting the `pinningPosition` property on the column:
1434

15-
```html
16-
<igx-column [field]="'Col1'" [pinned]='true' [pinningPosition]='pinningPosition'>
17-
</igx-column>
18-
```
35+
```html
36+
<igx-column [field]="'Col1'" [pinned]='true' [pinningPosition]='pinningPosition'>
37+
</igx-column>
38+
```
1939

20-
```ts
21-
public pinningPosition = ColumnPinningPosition.End;
22-
```
40+
```ts
41+
public pinningPosition = ColumnPinningPosition.End;
42+
```
2343

24-
Or with the API, via optional parameter:
44+
Or with the API, via optional parameter:
2545

26-
```ts
46+
```ts
2747
grid.pinColumn('Col1', 0, ColumnPinningPosition.End);
2848
grid.pinColumn('Col2', 0, ColumnPinningPosition.Start);
29-
```
49+
```
3050

31-
If property `pinningPosition` is not set on a column, the column will default to the position specified on the grid's `pinning` options for `columns`.
51+
If property `pinningPosition` is not set on a column, the column will default to the position specified on the grid's `pinning` options for `columns`.
52+
53+
- `IgxCarousel`
54+
- Added `select` method overload accepting index.
55+
```ts
56+
this.carousel.select(2, Direction.NEXT);
57+
```
3258

3359
- `IgxDateRangePicker`
3460
- Now has a complete set of properties to customize the calendar:
@@ -75,10 +101,10 @@ All notable changes for each version of this project will be documented in this
75101

76102
- `IgxTooltip`
77103
- The tooltip now remains open while interacting with it.
104+
78105
- `IgxTooltipTarget`
79106
- Introduced several new properties to enhance customization of tooltip content and behavior. Those include `positionSettings`, `hasArrow`, `sticky`, `closeButtonTemplate`. For detailed usage and examples, please refer to the Tooltip [README](https://github.com/IgniteUI/igniteui-angular/blob/master/projects/igniteui-angular/src/lib/directives/tooltip/README.md).
80107

81-
82108
### General
83109
- `IgxDropDown` now exposes a `role` input property, allowing users to customize the role attribute based on the use case. The default is `listbox`.
84110

@@ -187,24 +213,24 @@ All notable changes for each version of this project will be documented in this
187213
- Added the `canCommit`, `commit` and `discard` public methods that allows the user to save/discard the current state of the expression tree.
188214
- Added option to template the search value input:
189215
```
190-
<ng-template igxQueryBuilderSearchValue
216+
<ng-template igxQueryBuilderSearchValue
191217
let-searchValue
192-
let-selectedField = "selectedField"
218+
let-selectedField = "selectedField"
193219
let-selectedCondition = "selectedCondition"
194220
let-defaultSearchValueTemplate = "defaultSearchValueTemplate">
195221
@if (selectedField?.field === 'Id' && selectedCondition === 'equals'){
196222
<input type="text" required [(ngModel)]="searchValue.value"/>
197-
} @else {
223+
} @else {
198224
<ng-container #defaultTemplate *ngTemplateOutlet="defaultSearchValueTemplate"></ ng-container>
199225
}
200-
</ng-template>
226+
</ng-template>
201227
```
202-
- **Behavioral Changes**
228+
- **Behavioral Changes**
203229
- Expression enters edit mode on single click, `Enter` or `Space`.
204230
- Selecting conditions inside the `IgxQueryBuilderComponent` is no longer supported. Grouping/ungrouping expressions is now achieved via the newly exposed Drag & Drop functionality.
205231
- Deleting multiple expressions through the context menu is no longer supported.
206232
- `IgxQueryBuilderHeaderComponent`
207-
- **Behavioral Change**
233+
- **Behavioral Change**
208234
- Legend is no longer shown.
209235
- If the `title` input property is not set, by default it would be empty string.
210236
- **Deprecation**
@@ -286,9 +312,9 @@ All notable changes for each version of this project will be documented in this
286312

287313
### Themes
288314
- **Breaking Change** `Palettes`
289-
- All palette colors have been migrated to the [CSS relative colors syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_colors/Relative_colors). This means that color consumed as CSS variables no longer need to be wrapped in an `hsl` function.
315+
- All palette colors have been migrated to the [CSS relative colors syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_colors/Relative_colors). This means that color consumed as CSS variables no longer need to be wrapped in an `hsl` function.
290316

291-
Example:
317+
Example:
292318
```css
293319
/* 18.1.x and before: */
294320
background: hsl(var(--ig-primary-600));
@@ -299,7 +325,7 @@ All notable changes for each version of this project will be documented in this
299325

300326
This change also opens up the door for declaring the base (500) variants of each color in CSS from any color, including other CSS variables, whereas before the Sass `palette` function was needed to generate color shades from a base color.
301327

302-
Example:
328+
Example:
303329
```scss
304330
/* 18.1.x and before: */
305331
$my-palette: palette($primary: #09f, ...);
@@ -339,7 +365,7 @@ For Firefox users, we provide limited scrollbar styling options through the foll
339365
- `animationType` input property is now of type `CarouselAnimationType`. `HorizontalAnimationType` can also be used, however, to accommodate the new vertical mode, which supports vertical slide animations, it is recommended to use `CarouselAnimationType`.
340366

341367
- **Behavioral Changes** - the `keyboardSupport` input property now defaults to `false`.
342-
- **Deprecation** - the `keyboardSupport` input property has been deprecated and will be removed in a future version. Keyboard navigation with `ArrowLeft`, `ArrowRight`, `Home`, and `End` keys will be supported when focusing the indicators' container via ` Tab`/`Shift+Tab`.
368+
- **Deprecation** - the `keyboardSupport` input property has been deprecated and will be removed in a future version. Keyboard navigation with `ArrowLeft`, `ArrowRight`, `Home`, and `End` keys will be supported when focusing the indicators' container via ` Tab`/`Shift+Tab`.
343369

344370
- `IgxCombo`:
345371
- **Breaking Change** The deprecated `filterable` property is replaced with `disableFiltering`.

projects/igniteui-angular-elements/src/analyzer/elements.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export var registerConfig = [
9595
],
9696
numericProps: ["rowEnd", "colEnd", "rowStart", "colStart"],
9797
boolProps: [
98+
"merge",
9899
"sortable",
99100
"selectable",
100101
"groupable",
@@ -158,6 +159,7 @@ export var registerConfig = [
158159
"expanded",
159160
"searchable",
160161
"hidden",
162+
"merge",
161163
"sortable",
162164
"groupable",
163165
"editable",
@@ -213,6 +215,7 @@ export var registerConfig = [
213215
"collapsible",
214216
"expanded",
215217
"searchable",
218+
"merge",
216219
"sortable",
217220
"groupable",
218221
"editable",

projects/igniteui-angular-elements/src/public_api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { IgxPivotDateDimension } from 'projects/igniteui-angular/src/lib/grids/p
1212
import { PivotDimensionType } from 'projects/igniteui-angular/src/lib/grids/pivot-grid/pivot-grid.interface';
1313
import { IgxDateSummaryOperand, IgxNumberSummaryOperand, IgxSummaryOperand, IgxTimeSummaryOperand } from 'projects/igniteui-angular/src/lib/grids/summaries/grid-summary';
1414
import { HorizontalAlignment, VerticalAlignment } from 'projects/igniteui-angular/src/lib/services/overlay/utilities';
15-
15+
import { ByLevelTreeGridMergeStrategy } from 'projects/igniteui-angular/src/lib/data-operations/merge-strategy';
1616

1717
/** Export Public API, TODO: reorganize, Generate all w/ renames? */
1818
export {
@@ -35,6 +35,7 @@ export {
3535

3636
NoopSortingStrategy as IgcNoopSortingStrategy,
3737
NoopFilteringStrategy as IgcNoopFilteringStrategy,
38+
ByLevelTreeGridMergeStrategy as IgcByLevelTreeGridMergeStrategy,
3839

3940
// Pivot API
4041
IgxPivotDateDimension as IgcPivotDateDimension,

projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-component.scss

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,14 @@
272272
@extend %igx-grid__tr--pinned !optional;
273273
}
274274

275+
@include e(tr, $m: merged) {
276+
@extend %igx-grid__tr--merged !optional;
277+
}
278+
279+
@include e(tr, $m: merged-top) {
280+
@extend %igx-grid__tr--merged-top !optional;
281+
}
282+
275283
@include e(tr, $m: pinned-top) {
276284
@extend %igx-grid__tr--pinned-top !optional;
277285
}
@@ -296,6 +304,22 @@
296304
@extend %igx-grid__td--edited !optional;
297305
}
298306

307+
@include e(td, $m: merged) {
308+
@extend %igx-grid__td--merged !optional;
309+
}
310+
311+
@include e(td, $mods: (merged-selected, merged-hovered)) {
312+
@extend %igx-grid__td--merged-selected-hovered !optional;
313+
}
314+
315+
@include e(td, $m: merged-selected) {
316+
@extend %igx-grid__td--merged-selected !optional;
317+
}
318+
319+
@include e(td, $m: merged-hovered) {
320+
@extend %igx-grid__td--merged-hovered !optional;
321+
}
322+
299323
@include e(td, $m: editing) {
300324
@extend %igx-grid__td--editing !optional;
301325
}

projects/igniteui-angular/src/lib/core/styles/components/grid/_grid-theme.scss

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,7 @@
622622

623623
%grid-row--mrl {
624624
%igx-grid__hierarchical-expander--header,
625+
%igx-grid__hierarchical-expander,
625626
%igx-grid__header-indentation,
626627
%igx-grid__row-indentation,
627628
%grid__cbx-selection {
@@ -754,6 +755,7 @@
754755
}
755756

756757
%grid__cbx-selection,
758+
%igx-grid__hierarchical-expander,
757759
%igx-grid__row-indentation,
758760
%igx-grid__drag-indicator {
759761
border-bottom: rem(1px) solid var-get($theme, 'row-border-color');
@@ -1338,6 +1340,35 @@
13381340
}
13391341
}
13401342

1343+
%igx-grid__tr--merged {
1344+
border-block-end: 0;
1345+
}
1346+
1347+
%igx-grid__tr--merged-top {
1348+
position: absolute;
1349+
width: 100%;
1350+
}
1351+
1352+
%igx-grid__td--merged {
1353+
z-index: 1;
1354+
grid-row: 1 / -1;
1355+
}
1356+
1357+
%igx-grid__td--merged-selected {
1358+
color: var-get($theme, 'row-selected-text-color');
1359+
background: var-get($theme, 'row-selected-background') !important;
1360+
}
1361+
1362+
%igx-grid__td--merged-hovered {
1363+
background: var-get($theme, 'row-hover-background') !important;
1364+
color: var-get($theme, 'row-hover-text-color');
1365+
}
1366+
1367+
%igx-grid__td--merged-selected-hovered {
1368+
background: var-get($theme, 'row-selected-hover-background') !important;
1369+
color: var-get($theme, 'row-selected-hover-text-color');
1370+
}
1371+
13411372
%igx-grid__tr--deleted {
13421373
%grid-cell-text {
13431374
font-style: italic;

projects/igniteui-angular/src/lib/core/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ export function cloneArray<T>(array: T[], deep = false): T[] {
3333
return deep ? (array ?? []).map(cloneValue) : (array ?? []).slice();
3434
}
3535

36+
/**
37+
* @hidden
38+
*/
39+
export function areEqualArrays<T>(arr1: T[], arr2: T[]): boolean {
40+
if (arr1.length !== arr2.length) return false;
41+
for (let i = 0; i < arr1.length; i++) {
42+
if (arr1[i] !== arr2[i]) return false;
43+
}
44+
return true;
45+
}
46+
3647
/**
3748
* Doesn't clone leaf items
3849
*

projects/igniteui-angular/src/lib/data-operations/data-util.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { IGroupingState } from './groupby-state.interface';
88
import { mergeObjects } from '../core/utils';
99
import { Transaction, TransactionType, HierarchicalTransaction } from '../services/transaction/transaction';
1010
import { getHierarchy, isHierarchyMatch } from './operations';
11-
import { GridType } from '../grids/common/grid.interface';
11+
import { ColumnType, GridType } from '../grids/common/grid.interface';
1212
import { ITreeGridRecord } from '../grids/tree-grid/tree-grid.interfaces';
1313
import { ISortingExpression } from './sorting-strategy';
1414
import {
@@ -20,6 +20,7 @@ import {
2020
} from '../grids/common/strategy';
2121
import { DefaultDataCloneStrategy, IDataCloneStrategy } from '../data-operations/data-clone-strategy';
2222
import { IGroupingExpression } from './grouping-expression.interface';
23+
import { DefaultMergeStrategy, IGridMergeStrategy } from './merge-strategy';
2324

2425
/**
2526
* @hidden
@@ -115,6 +116,25 @@ export class DataUtil {
115116
return grouping.groupBy(data, state, grid, groupsRecords, fullResult);
116117
}
117118

119+
public static merge<T>(data: T[], columns: ColumnType[], strategy: IGridMergeStrategy = new DefaultMergeStrategy(), activeRowIndexes = [], grid: GridType = null,
120+
): any[] {
121+
let result = [];
122+
for (const col of columns) {
123+
const isDate = col?.dataType === 'date' || col?.dataType === 'dateTime';
124+
const isTime = col?.dataType === 'time' || col?.dataType === 'dateTime';
125+
strategy.merge(
126+
data,
127+
col.field,
128+
col.mergingComparer,
129+
result,
130+
activeRowIndexes,
131+
isDate,
132+
isTime,
133+
grid);
134+
}
135+
return result;
136+
}
137+
118138
public static page<T>(data: T[], state: IPagingState, dataLength?: number): T[] {
119139
if (!state) {
120140
return data;

0 commit comments

Comments
 (0)