Skip to content

Commit 4100e89

Browse files
authored
Merge branch 'master' into radomirchev-update-roadmap-01-feb-2024
2 parents 53579c2 + f8bf9a9 commit 4100e89

31 files changed

+518
-240
lines changed

CHANGELOG.md

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,27 @@ All notable changes for each version of this project will be documented in this
99
- `IgxTree`
1010
- Added new property `toggleNodeOnClick` that determines whether clicking over a node will change its expanded state or not. Set to `false` by default.
1111
- `IgxPivotGrid`
12-
- `IPivotDimension` interface now exposes a property called `displayName` similar to the one in the `IPivotValue` interface. This property is optional and will be displayed inside the chips for rows and columns in the `IgxPivotGrid`. If the `displayName` proeprty is not set, `memberName` will be used as a fallback.
12+
- `IPivotDimension` interface now exposes a property called `displayName` similar to the one in the `IPivotValue` interface. This property is optional and will be displayed inside the chips for rows and columns in the `IgxPivotGrid`. If the `displayName` property is not set, `memberName` will be used as a fallback.
1313
- New directive - `igxIconButton` directive that provides a way to use an icon as a fully functional button has been added. The new `igxIconButton` comes in three types - flat, outlined and contained (default). All `igxButton`'s with type `icon` will be automatically migrated to the new `igxIconButton`'s with `ng update`.
1414
- `IgxButton`
15-
- **Behavioral Change** `buttonSelected` event is now emitted not only when a button gets selected, but also when it gets deselected. A benefit of that is when updating the value bound to the `selected` input of an `IgxButton`, the button group in which the button resides is now able to update the styling of the button from selected to deselected. If this event was used in a scenario where it is assumed that the button gets selected, it's a good idea the logic to be branched now based on `eventArgs.selected` condition.
15+
- **Behavioral Change** `buttonSelected` event is now emitted not only when a button gets selected, but also when it gets deselected. However, the event is no longer being emitted on initialization. If this event was used in a scenario where it is assumed that the button gets selected, it's a good idea the logic to be branched now based on `eventArgs.selected` condition.
1616

1717
### General
1818
- `igxButton`:
1919
- **Breaking Change** The `raised` type of the `igxButton` directive has been renamed to `contained`. Automatic migrations are available and will be applied on `ng update`.
2020
- The `igxButtonColor` and `igxButtonBackground` input properties have been deprecated and will be removed in a future version.
2121
- `IgxForOf`
2222
- Unified logic for vertical and horizontal virtualization such as - caching, updating, max browser size exceeding.
23-
- Addded new method - `addScroll` that can shift the scroll thumb by the specified amount in pixels (negative number to scroll to previous, positive to scroll next). Similar to `addScrollTop` but works for both vertical and horizontal virtualization.
23+
- Added new method - `addScroll` that can shift the scroll thumb by the specified amount in pixels (negative number to scroll to previous, positive to scroll next). Similar to `addScrollTop` but works for both vertical and horizontal virtualization.
24+
25+
26+
- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid`
27+
- **Breaking Changes**
28+
- `rowAdd` and `rowDelete` events now emit event argument of type `IRowDataCancelableEventArgs` instead of `IGridEditEventArgs`. The two interfaces are still compatible, however redundant for these events properties `cellID`, `newValue`, `oldValue`, `isAddRow` are deprecated in `IRowDataCancelableEventArgs` and will be removed in a future version. Switching to the correct new interfaces should reveal any deprecated use that can be safely removed.
29+
- **Deprecations**
30+
- `rowID` property has been deprecated in the following interfaces: `IGridEditDoneEventArgs`, `IPathSegment`, `IRowToggleEventArgs`, `IPinRowEventArgs`, `IgxAddRowParent` and will be removed in a future version. Use `rowKey` instead.
31+
- `primaryKey` property has been deprecated in the following interfaces: `IRowDataEventArgs`, `IGridEditDoneEventArgs`. Use `rowKey` instead.
32+
- `data` property has been deprecated in the following interfaces: `IRowDataEventArgs`. Use `rowData` instead.
2433

2534
## 17.0.0
2635
### General
@@ -33,14 +42,14 @@ All notable changes for each version of this project will be documented in this
3342
- `igniteui-angular-i18n` is now tree-shakeable
3443
- `igniteui-angular/animations` is now tree-shakeable
3544
- `igniteui-angular` components have improved tree-shaking
36-
- **Breaking Change** `getCurrentResourceStrings` has been removed. Use the specific component string imports instead.
45+
- **Breaking Change** `getCurrentResourceStrings` has been removed. Use the specific component string imports instead.
3746
- E.g. EN strings come from `igniteui-angular`: `import { GridResourceStringsEN } from 'igniteui-angular';`
3847
- E.g. DE or other language strings come from `igniteui-angular-i18n`: `import { GridResourceStringsDE } from 'igniteui-angular-i18n';`
3948
- DisplayDensity token and inputs are deprecated in favor of `--ig-size` theming
4049
- We're working on reducing the library size
4150
- `IgxRadioComponent` size has been reduced in half
4251
- `IgxSwitchComponent` size has been reduced in half
43-
- `IgxRadioComponent`
52+
- `IgxRadioComponent`
4453
- **Breaking Change** `IChangeRadioEventArgs` is now `IChangeCheckboxEventArgs`. `ng update` to `17.0.0` will automatically migrate this for you.
4554
- **Breaking Change** `RadioLabelPosition` is now `LabelPosition`. `ng update` to `17.0.0` will automatically migrate this for you.
4655
- `IgxSwitchComponent`
@@ -49,7 +58,7 @@ All notable changes for each version of this project will be documented in this
4958
- `IgxCombo`
5059
- **Breaking Change** `IComboSelectionChangingEventArgs` properties `newSelection` and `oldSelection` have been renamed to `newValue` and `oldValue` respectively to better reflect their function. Just like Combo's `value`, those will emit either the specified property values or full data items depending on whether `valueKey` is set or not. Automatic migrations are available and will be applied on `ng update`.
5160
- `IComboSelectionChangingEventArgs` exposes two new properties `newSelection` and `oldSelection` in place of the old ones that are no longer affected by `valueKey` and consistently emit items from Combo's `data`.
52-
61+
5362
Note: In remote data scenarios with `valueKey` set, selected items that are not currently part of the loaded data chunk will be emitted a partial item data object with the `valueKey` property.
5463
- **Breaking Change** - `IComboSelectionChangingEventArgs` properties `added` and `removed` now always contain data items, regardless of `valueKey` being set. This aligns them with the updated `newSelection` and `oldSelection` properties, including the same limitation for remote data as described above.
5564
- `IgxSimpleCombo`
@@ -61,11 +70,11 @@ All notable changes for each version of this project will be documented in this
6170
- `IgxCombo`,`IgxSimpleCombo`
6271
- **Breaking Change** The `displayValue` property now returns the display text as expected (instead of display values in array).
6372

73+
=======
6474
## 16.1.5
6575
### General
6676
- `IgxButtonGroup`:
6777
- Reverted cancellable on `selected` and `deselected` events (added in 15.1.24) as it was breaking firing order and related handling.
68-
6978
## 16.1.4
7079
### New Features
7180
- `Themes`:
@@ -107,7 +116,7 @@ All notable changes for each version of this project will be documented in this
107116
```
108117
- `IgxBadge`:
109118
- Material icons extended along with any other custom icon set can now be used inside the badge component.
110-
- Code example:
119+
- Code example:
111120

112121
```typescript
113122
import { IgxBadgeComponent, IgxIconService } from 'igniteui-angular';
@@ -150,7 +159,7 @@ All notable changes for each version of this project will be documented in this
150159
```
151160

152161
```css
153-
/*
162+
/*
154163
Add --ig-size to a component or global file.
155164
Available values are:
156165
compact: --ig-size-small

projects/igniteui-angular/src/lib/action-strip/grid-actions/grid-editing-actions.component.spec.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { IgxTreeGridEditActionsComponent } from '../../test-utils/tree-grid-comp
1515
import { IgxGridEditingActionsComponent } from './grid-editing-actions.component';
1616
import { IgxGridPinningActionsComponent } from './grid-pinning-actions.component';
1717
import { IgxActionStripComponent } from '../action-strip.component';
18-
import { IgxColumnComponent } from '../../grids/public_api';
18+
import { IRowDataCancelableEventArgs, IgxColumnComponent } from '../../grids/public_api';
1919

2020
describe('igxGridEditingActions #grid ', () => {
2121
let fixture;
@@ -203,6 +203,7 @@ describe('igxGridEditingActions #grid ', () => {
203203
expect(grid.rowPinning.emit).toHaveBeenCalledTimes(1);
204204
expect(grid.rowPinning.emit).toHaveBeenCalledWith({
205205
rowID : row.key,
206+
rowKey: row.key,
206207
insertAtIndex: 0,
207208
isPinned: true,
208209
row,
@@ -220,6 +221,7 @@ describe('igxGridEditingActions #grid ', () => {
220221
expect(grid.rowPinning.emit).toHaveBeenCalledTimes(2);
221222
expect(grid.rowPinning.emit).toHaveBeenCalledWith({
222223
rowID : row5.key,
224+
rowKey: row5.key,
223225
insertAtIndex: 1,
224226
isPinned: true,
225227
row: row5,
@@ -355,18 +357,22 @@ describe('igxGridEditingActions #grid ', () => {
355357
expect(editActions[3].componentInstance.iconName).toBe('delete');
356358
const deleteChildBtn = editActions[3].componentInstance;
357359

358-
const rowDeleteArgs = {
360+
const rowDeleteArgs: IRowDataCancelableEventArgs = {
359361
rowID: row.key,
360362
primaryKey: row.key,
363+
rowKey: row.key,
361364
cancel: false,
362365
rowData: treeGrid.getRowData(row.key),
366+
data: treeGrid.getRowData(row.key),
363367
oldValue: null,
364-
owner: treeGrid
368+
owner: treeGrid,
365369
};
366370

367371
const rowDeletedArgs = {
368372
data: treeGrid.getRowData(row.key),
373+
rowData: treeGrid.getRowData(row.key),
369374
primaryKey: row.key,
375+
rowKey: row.key,
370376
owner: treeGrid
371377
};
372378

projects/igniteui-angular/src/lib/buttonGroup/buttonGroup.component.ts

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -302,14 +302,20 @@ export class IgxButtonGroupComponent extends DisplayDensityBase implements After
302302
public selectedIndexes: number[] = [];
303303

304304
protected buttonClickNotifier$ = new Subject<boolean>();
305-
protected buttonSelectedNotifier$ = new Subject<boolean>();
306305
protected queryListNotifier$ = new Subject<boolean>();
307306

308307
private _isVertical: boolean;
309308
private _itemContentCssClass: string;
310309
private _disabled = false;
311310
private _selectionMode: 'single' | 'singleRequired' | 'multi' = 'single';
312311

312+
private mutationObserver: MutationObserver;
313+
private observerConfig: MutationObserverInit = {
314+
attributeFilter: ["data-selected"],
315+
childList: true,
316+
subtree: true,
317+
};
318+
313319
constructor(
314320
private _cdr: ChangeDetectorRef,
315321
private _renderer: Renderer2,
@@ -351,6 +357,8 @@ export class IgxButtonGroupComponent extends DisplayDensityBase implements After
351357
return;
352358
}
353359

360+
this.updateSelected(index);
361+
354362
const button = this.buttons[index];
355363
button.select();
356364
}
@@ -366,25 +374,21 @@ export class IgxButtonGroupComponent extends DisplayDensityBase implements After
366374
this.selectedIndexes.push(index);
367375
}
368376

369-
if (button.selected) {
370-
this._renderer.setAttribute(button.nativeElement, 'aria-pressed', 'true');
371-
this._renderer.addClass(button.nativeElement, 'igx-button-group__item--selected');
377+
this._renderer.setAttribute(button.nativeElement, 'aria-pressed', 'true');
378+
this._renderer.addClass(button.nativeElement, 'igx-button-group__item--selected');
372379

373-
const indexInViewButtons = this.viewButtons.toArray().indexOf(button);
374-
if (indexInViewButtons !== -1) {
380+
const indexInViewButtons = this.viewButtons.toArray().indexOf(button);
381+
if (indexInViewButtons !== -1) {
375382
this.values[indexInViewButtons].selected = true;
376-
}
383+
}
377384

378-
// deselect other buttons if selectionMode is not multi
379-
if (this.selectionMode !== 'multi' && this.selectedIndexes.length > 1) {
380-
this.buttons.forEach((_, i) => {
381-
if (i !== index && this.selectedIndexes.indexOf(i) !== -1) {
382-
this.deselectButton(i);
383-
}
384-
});
385-
}
386-
} else {
387-
this.deselectButton(index);
385+
// deselect other buttons if selectionMode is not multi
386+
if (this.selectionMode !== 'multi' && this.selectedIndexes.length > 1) {
387+
this.buttons.forEach((_, i) => {
388+
if (i !== index && this.selectedIndexes.indexOf(i) !== -1) {
389+
this.deselectButton(i);
390+
}
391+
});
388392
}
389393

390394
}
@@ -455,9 +459,6 @@ export class IgxButtonGroupComponent extends DisplayDensityBase implements After
455459
}
456460

457461
button.buttonClick.pipe(takeUntil(this.buttonClickNotifier$)).subscribe((_) => this._clickHandler(index));
458-
button.buttonSelected
459-
.pipe(takeUntil(this.buttonSelectedNotifier$))
460-
.subscribe((_) => this.updateSelected(index));
461462
});
462463
};
463464

@@ -466,6 +467,10 @@ export class IgxButtonGroupComponent extends DisplayDensityBase implements After
466467
initButtons();
467468

468469
this._cdr.detectChanges();
470+
471+
this.mutationObserver = this.setMutationsObserver();
472+
473+
this.mutationObserver.observe(this._el.nativeElement, this.observerConfig);
469474
}
470475

471476
/**
@@ -475,17 +480,18 @@ export class IgxButtonGroupComponent extends DisplayDensityBase implements After
475480
this.buttonClickNotifier$.next();
476481
this.buttonClickNotifier$.complete();
477482

478-
this.buttonSelectedNotifier$.next();
479-
this.buttonSelectedNotifier$.complete();
480-
481483
this.queryListNotifier$.next();
482484
this.queryListNotifier$.complete();
485+
486+
this.mutationObserver.disconnect();
483487
}
484488

485489
/**
486490
* @hidden
487491
*/
488492
public _clickHandler(index: number) {
493+
this.mutationObserver.disconnect();
494+
489495
const button = this.buttons[index];
490496
const args: IButtonGroupEventArgs = { owner: this, button, index };
491497

@@ -506,6 +512,54 @@ export class IgxButtonGroupComponent extends DisplayDensityBase implements After
506512
this.deselected.emit(args);
507513
}
508514
}
515+
516+
this.mutationObserver.observe(this._el.nativeElement, this.observerConfig);
517+
}
518+
519+
private setMutationsObserver() {
520+
return new MutationObserver((records, observer) => {
521+
// Stop observing while handling changes
522+
observer.disconnect();
523+
524+
const updatedButtons = this.getUpdatedButtons(records);
525+
526+
if (updatedButtons.length > 0) {
527+
updatedButtons.forEach((button) => {
528+
const index = this.buttons.map((b) => b.nativeElement).indexOf(button);
529+
const args: IButtonGroupEventArgs = { owner: this, button: this.buttons[index], index };
530+
531+
this.updateButtonSelectionState(index, args);
532+
});
533+
}
534+
535+
// Watch for changes again
536+
observer.observe(this._el.nativeElement, this.observerConfig);
537+
});
538+
}
539+
540+
private getUpdatedButtons(records: MutationRecord[]) {
541+
const updated: HTMLButtonElement[] = [];
542+
543+
records
544+
.filter((x) => x.type === 'attributes')
545+
.reduce((prev, curr) => {
546+
prev.push(
547+
curr.target as HTMLButtonElement
548+
);
549+
return prev;
550+
}, updated);
551+
552+
return updated;
553+
}
554+
555+
private updateButtonSelectionState(index: number, args: IButtonGroupEventArgs) {
556+
if (this.selectedIndexes.indexOf(index) === -1) {
557+
this.selectButton(index);
558+
this.selected.emit(args);
559+
} else {
560+
this.deselectButton(index);
561+
this.deselected.emit(args);
562+
}
509563
}
510564
}
511565

projects/igniteui-angular/src/lib/buttonGroup/buttongroup.component.spec.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component, OnInit, ViewChild } from '@angular/core';
2-
import { TestBed, waitForAsync } from '@angular/core/testing';
2+
import { TestBed, fakeAsync, flushMicrotasks, waitForAsync } from '@angular/core/testing';
33
import { ButtonGroupAlignment, IgxButtonGroupComponent } from './buttonGroup.component';
44
import { configureTestSuite } from '../test-utils/configure-suite';
55
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@@ -115,7 +115,11 @@ describe('IgxButtonGroup', () => {
115115

116116
const button = fixture.debugElement.nativeElement.querySelector('button');
117117
button.click();
118+
// The first button is already selected, so it should not fire the selected event, but the deselected one.
119+
expect(btnGroupInstance.selected.emit).not.toHaveBeenCalled();
118120

121+
const unselectedButton = fixture.debugElement.nativeElement.querySelector('#unselected');
122+
unselectedButton.click();
119123
expect(btnGroupInstance.selected.emit).toHaveBeenCalled();
120124
});
121125

@@ -360,7 +364,7 @@ describe('IgxButtonGroup', () => {
360364
}
361365
});
362366

363-
it('should style the corresponding button as deselected when the value bound to the selected input changes', () => {
367+
it('should style the corresponding button as deselected when the value bound to the selected input changes', fakeAsync(() => {
364368
const fixture = TestBed.createComponent(ButtonGroupButtonWithBoundSelectedOutputComponent);
365369
fixture.detectChanges();
366370

@@ -370,11 +374,13 @@ describe('IgxButtonGroup', () => {
370374
expect(btnGroupInstance.buttons[1].selected).toBe(true);
371375

372376
fixture.componentInstance.selectedValue = 100;
377+
flushMicrotasks();
373378
fixture.detectChanges();
374379

375-
expect(btnGroupInstance.selectedButtons.length).toBe(0);
376-
expect(btnGroupInstance.buttons[1].selected).toBe(false);
377-
});
380+
btnGroupInstance.buttons.forEach((button) => {
381+
expect(button.selected).toBe(false);
382+
});
383+
}));
378384

379385
});
380386

@@ -492,7 +498,7 @@ class TemplatedButtonGroupDesplayDensityComponent {
492498
template: `
493499
<igx-buttongroup>
494500
<button igxButton [selected]="true">Button 0</button>
495-
<button igxButton>Button 1</button>
501+
<button igxButton id="unselected">Button 1</button>
496502
<button igxButton>Button 2</button>
497503
</igx-buttongroup>
498504
`,

projects/igniteui-angular/src/lib/directives/button/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ this.button.displayDensity = "compact";
3131
| `igxButtonColor` | string | Set the button text color. You can pass any CSS valid color value. |
3232
| `igxButtonBackground` | string | Set the button background color. You can pass any CSS valid color value. |
3333
| `displayDensity` | DisplayDensity | Determines the display density of the button. |
34+
| `buttonSelected` | EventEmitter<IButtonEventArgs> | Emitted only when a button gets selected, or deselected, and not on initialization. |
35+
| `selected` | boolean | Gets or sets whether the button is selected. Mainly used in the IgxButtonGroup component and it will have no effect if set separately. |
3436

3537
# Button types
3638
| Name | Description |

projects/igniteui-angular/src/lib/directives/button/button.directive.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,24 @@ describe('IgxButton', () => {
134134
expect(theButtonNativeEl.classList.length).toEqual(2);
135135
expect(theButtonNativeEl.classList).toContain(classes.flat);
136136
});
137+
138+
it('Should emit the buttonSelected event only on user interaction, not on initialization', () => {
139+
const fixture = TestBed.createComponent(InitButtonComponent);
140+
fixture.detectChanges();
141+
const button = fixture.componentInstance.button;
142+
spyOn(button.buttonSelected, 'emit');
143+
144+
button.ngOnInit();
145+
expect(button.buttonSelected.emit).not.toHaveBeenCalled();
146+
147+
button.nativeElement.click();
148+
fixture.detectChanges();
149+
expect(button.buttonSelected.emit).toHaveBeenCalledTimes(1);
150+
151+
button.nativeElement.click();
152+
fixture.detectChanges();
153+
expect(button.buttonSelected.emit).toHaveBeenCalledTimes(2);
154+
});
137155
});
138156

139157
@Component({

0 commit comments

Comments
 (0)