Skip to content

Commit 9921322

Browse files
authored
Merge branch '12.1.x' into simeonoff/fix-9997-12.1.x
2 parents 2e4ded9 + 2e08303 commit 9921322

17 files changed

+730
-277
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

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

5+
## 12.1.3
6+
7+
### New Features
8+
- `igxGrid`
9+
- Added `headerStyles` and `headerGroupStyles` inputs to the column component.
10+
Similar to `cellStyles` is exposes a way to bind CSS properties and style the grid headers.
11+
512
## 12.1.2
613
- `igxGrid`
714
- The column formatter callback signature now accepts the row data as an additional argument:

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,11 @@ export class IgxCheckboxComponent implements ControlValueAccessor, EditorProvide
454454
this._onTouchedCallback = fn;
455455
}
456456

457+
/** @hidden @internal */
458+
public setDisabledState(isDisabled: boolean) {
459+
this.disabled = isDisabled;
460+
}
461+
457462
/** @hidden @internal */
458463
public getEditElement() {
459464
return this.nativeCheckbox.nativeElement;

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

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { Component, ViewChild, ViewChildren, QueryList, DebugElement } from '@angular/core';
22
import { TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing';
3-
import { FormsModule, FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
3+
import { FormsModule, FormBuilder, ReactiveFormsModule, Validators, FormControl, FormGroup } from '@angular/forms';
44
import { By } from '@angular/platform-browser';
55
import { IgxInputGroupComponent, IgxInputGroupModule } from '../../input-group/input-group.component';
66
import { IgxInputDirective, IgxInputState } from './input.directive';
77
import { configureTestSuite } from '../../test-utils/configure-suite';
88

99
const INPUT_CSS_CLASS = 'igx-input-group__input';
10+
const CSS_CLASS_INPUT_GROUP_LABEL = 'igx-input-group__label';
1011
const TEXTAREA_CSS_CLASS = 'igx-input-group__textarea';
1112

1213
const INPUT_GROUP_FOCUSED_CSS_CLASS = 'igx-input-group--focused';
@@ -35,7 +36,8 @@ describe('IgxInput', () => {
3536
DataBoundDisabledInputWithoutValueComponent,
3637
ReactiveFormComponent,
3738
InputsWithSameNameAttributesComponent,
38-
ToggleRequiredWithNgModelInputComponent
39+
ToggleRequiredWithNgModelInputComponent,
40+
InputReactiveFormComponent
3941
],
4042
imports: [
4143
IgxInputGroupModule,
@@ -521,11 +523,16 @@ describe('IgxInput', () => {
521523
fixture.detectChanges();
522524
const igxInput = fixture.componentInstance.strIgxInput;
523525

526+
expect(igxInput.disabled).toBe(false);
527+
expect(igxInput.inputGroup.disabled).toBe(false);
528+
524529
fixture.componentInstance.form.disable();
525530
expect(igxInput.disabled).toBe(true);
531+
expect(igxInput.inputGroup.disabled).toBe(true);
526532

527533
fixture.componentInstance.form.get('str').enable();
528534
expect(igxInput.disabled).toBe(false);
535+
expect(igxInput.inputGroup.disabled).toBe(false);
529536
}));
530537

531538
it('should style input when required is toggled dynamically.', () => {
@@ -663,6 +670,69 @@ describe('IgxInput', () => {
663670
igxInput.value = 'Test';
664671
expect(igxInput.value).toBe('Test');
665672
});
673+
674+
it('Should properly initialize when used as a reactive form control - toggle validators', fakeAsync(() => {
675+
const fix = TestBed.createComponent(InputReactiveFormComponent);
676+
fix.detectChanges();
677+
// 1) check if label's --required class and its asterisk are applied
678+
const dom = fix.debugElement;
679+
const input = fix.componentInstance.input;
680+
const inputGroup = fix.componentInstance.igxInputGroup.element.nativeElement;
681+
const formGroup: FormGroup = fix.componentInstance.reactiveForm;
682+
683+
// interaction test - expect actual asterisk
684+
// The only way to get a pseudo elements like :before OR :after is to use getComputedStyle(element [, pseudoElt]),
685+
// as these are not in the actual DOM
686+
let asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
687+
expect(asterisk).toBe('"*"');
688+
expect(inputGroup.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(true);
689+
690+
// 2) check that input group's --invalid class is NOT applied
691+
expect(inputGroup.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(false);
692+
693+
// interaction test - focus&blur, so the --invalid and --required classes are applied
694+
// *Use markAsTouched() instead of user interaction ( calling focus + blur) because:
695+
// Angular handles blur and marks the component as touched, however:
696+
// in order to ensure Angular handles blur prior to our blur handler (where we check touched),
697+
// we have to call blur twice.
698+
fix.debugElement.componentInstance.markAsTouched();
699+
tick();
700+
fix.detectChanges();
701+
702+
expect(inputGroup.classList.contains(INPUT_GROUP_INVALID_CSS_CLASS)).toBe(true);
703+
expect(inputGroup.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(true);
704+
705+
// 3) Check if the input group's --invalid and --required classes are removed when validator is dynamically cleared
706+
fix.componentInstance.removeValidators(formGroup);
707+
fix.detectChanges();
708+
tick();
709+
710+
const formReference = fix.componentInstance.reactiveForm.controls.fullName;
711+
// interaction test - expect no asterisk
712+
asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
713+
expect(formReference).toBeDefined();
714+
expect(input).toBeDefined();
715+
expect(input.nativeElement.value).toEqual('');
716+
expect(inputGroup.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toEqual(false);
717+
expect(asterisk).toBe('none');
718+
expect(input.valid).toEqual(IgxInputState.INITIAL);
719+
720+
// interact with the input and expect no changes
721+
input.nativeElement.dispatchEvent(new Event('focus'));
722+
input.nativeElement.dispatchEvent(new Event('blur'));
723+
tick();
724+
fix.detectChanges();
725+
expect(input.valid).toEqual(IgxInputState.INITIAL);
726+
727+
// Re-add all Validators
728+
fix.componentInstance.addValidators(formGroup);
729+
fix.detectChanges();
730+
731+
expect(inputGroup.classList.contains(INPUT_GROUP_REQUIRED_CSS_CLASS)).toBe(true);
732+
// interaction test - expect actual asterisk
733+
asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
734+
expect(asterisk).toBe('"*"');
735+
}));
666736
});
667737

668738
@Component({
@@ -917,6 +987,67 @@ class ToggleRequiredWithNgModelInputComponent {
917987
public data1 = '';
918988
public isRequired = false;
919989
}
990+
@Component({
991+
template: `
992+
<form [formGroup]="reactiveForm" (ngSubmit)="onSubmitReactive()">
993+
<igx-input-group #igxInputGroup>
994+
<input igxInput #inputReactive name="fullName" type="text" formControlName="fullName" />
995+
<label igxLabel for="fullName">Full Name</label>
996+
<igx-suffix>
997+
<igx-icon>person</igx-icon>
998+
</igx-suffix>
999+
</igx-input-group>
1000+
</form>
1001+
`
1002+
})
1003+
1004+
class InputReactiveFormComponent {
1005+
@ViewChild('igxInputGroup', { static: true }) public igxInputGroup: IgxInputGroupComponent;
1006+
@ViewChild('inputReactive', { read: IgxInputDirective }) public input: IgxInputDirective;
1007+
public reactiveForm: FormGroup;
1008+
1009+
public validationType = {
1010+
fullName: [Validators.required]
1011+
};
1012+
1013+
constructor(fb: FormBuilder) {
1014+
this.reactiveForm = fb.group({
1015+
fullName: new FormControl('', Validators.required)
1016+
});
1017+
}
1018+
public onSubmitReactive() { }
1019+
1020+
public removeValidators(form: FormGroup) {
1021+
for (const key in form.controls) {
1022+
if (form.controls.hasOwnProperty(key)) {
1023+
form.get(key).clearValidators();
1024+
form.get(key).updateValueAndValidity();
1025+
}
1026+
}
1027+
}
1028+
1029+
public addValidators(form: FormGroup) {
1030+
for (const key in form.controls) {
1031+
if (form.controls.hasOwnProperty(key)) {
1032+
form.get(key).setValidators(this.validationType[key]);
1033+
form.get(key).updateValueAndValidity();
1034+
}
1035+
}
1036+
}
1037+
1038+
public markAsTouched() {
1039+
if (!this.reactiveForm.valid) {
1040+
for (const key in this.reactiveForm.controls) {
1041+
if (this.reactiveForm.controls.hasOwnProperty(key)) {
1042+
if (this.reactiveForm.controls[key]) {
1043+
this.reactiveForm.controls[key].markAsTouched();
1044+
this.reactiveForm.controls[key].updateValueAndValidity();
1045+
}
1046+
}
1047+
}
1048+
}
1049+
}
1050+
}
9201051

9211052
const testRequiredValidation = (inputElement, fixture) => {
9221053
dispatchInputEvent('focus', inputElement, fixture);

projects/igniteui-angular/src/lib/directives/input/input.directive.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,6 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
174174
* ```
175175
*/
176176
public get disabled() {
177-
if (this.ngControl && this.ngControl.disabled !== null) {
178-
return this.ngControl.disabled;
179-
}
180177
return this._disabled;
181178
}
182179

@@ -268,6 +265,10 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
268265
this.inputGroup.hasPlaceholder = this.nativeElement.hasAttribute(
269266
'placeholder'
270267
);
268+
269+
if (this.ngControl && this.ngControl.disabled !== null) {
270+
this.disabled = this.ngControl.disabled;
271+
}
271272
this.inputGroup.disabled =
272273
this.inputGroup.disabled ||
273274
this.nativeElement.hasAttribute('disabled');
@@ -349,16 +350,23 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
349350
protected updateValidityState() {
350351
if (this.ngControl) {
351352
if (this.ngControl.control.validator || this.ngControl.control.asyncValidator) {
353+
// Run the validation with empty object to check if required is enabled.
354+
const error = this.ngControl.control.validator({} as AbstractControl);
355+
this.inputGroup.isRequired = error && error.required;
352356
if (!this.disabled && (this.ngControl.control.touched || this.ngControl.control.dirty)) {
353357
// the control is not disabled and is touched or dirty
354358
this._valid = this.ngControl.invalid ?
355-
IgxInputState.INVALID : this.focused ? IgxInputState.VALID :
356-
IgxInputState.INITIAL;
359+
IgxInputState.INVALID : this.focused ? IgxInputState.VALID :
360+
IgxInputState.INITIAL;
357361
} else {
358362
// if control is untouched, pristine, or disabled its state is initial. This is when user did not interact
359363
// with the input or when form/control is reset
360364
this._valid = IgxInputState.INITIAL;
361365
}
366+
} else {
367+
// If validator is dynamically cleared, reset label's required class(asterisk) and IgxInputState #10010
368+
this._valid = IgxInputState.INITIAL;
369+
this.inputGroup.isRequired = false;
362370
}
363371
} else {
364372
this.checkNativeValidity();
@@ -459,8 +467,8 @@ export class IgxInputDirective implements AfterViewInit, OnDestroy {
459467
private checkNativeValidity() {
460468
if (!this.disabled && this._hasValidators()) {
461469
this._valid = this.nativeElement.checkValidity() ?
462-
this.focused ? IgxInputState.VALID : IgxInputState.INITIAL :
463-
IgxInputState.INVALID;
470+
this.focused ? IgxInputState.VALID : IgxInputState.INITIAL :
471+
IgxInputState.INVALID;
464472
}
465473
}
466474

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,19 @@ describe('IgxHighlight', () => {
286286
expect(spans.length).toBe(4);
287287
expect(activeSpans.length).toBe(1);
288288
});
289+
290+
it('Should not throw error when active highlight is not set', () => {
291+
const fix = TestBed.createComponent(HighlightLoremIpsumComponent);
292+
fix.detectChanges();
293+
294+
const component: HighlightLoremIpsumComponent = fix.debugElement.componentInstance;
295+
component.highlight.row = undefined;
296+
component.highlight.column = undefined;
297+
component.highlightText('a');
298+
299+
expect(() => component.highlight.activateIfNecessary()).not.toThrowError();
300+
});
301+
289302
});
290303

291304
@Component({

projects/igniteui-angular/src/lib/directives/text-highlight/text-highlight.directive.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ export class IgxTextHighlightDirective implements AfterViewInit, AfterViewChecke
340340
public activateIfNecessary(): void {
341341
const group = IgxTextHighlightDirective.highlightGroupsMap.get(this.groupName);
342342

343-
if (group.column === this.column && group.row === this.row && compareMaps(this.metadata, group.metadata)) {
343+
if (group.index >= 0 && group.column === this.column && group.row === this.row && compareMaps(this.metadata, group.metadata)) {
344344
this.activate(group.index);
345345
}
346346
}

projects/igniteui-angular/src/lib/grids/columns/column.component.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,27 @@ export class IgxColumnComponent implements AfterContentInit, OnDestroy {
517517
@Input()
518518
public headerClasses = '';
519519

520+
/**
521+
* Sets conditional style properties on the column header.
522+
* Similar to `ngStyle` it accepts an object literal where the keys are
523+
* the style properties and the value is the expression to be evaluated.
524+
* ```typescript
525+
* styles = {
526+
* background: 'royalblue',
527+
* color: (column) => column.pinned ? 'red': 'inherit'
528+
* }
529+
* ```
530+
* ```html
531+
* <igx-column [headerStyles]="styles"></igx-column>
532+
* ```
533+
*
534+
* @memberof IgxColumnComponent
535+
*/
536+
@notifyChanges()
537+
@WatchColumnChanges()
538+
@Input()
539+
public headerStyles = null;
540+
520541
/**
521542
* Sets/gets the class selector of the column group header.
522543
* ```typescript
@@ -532,6 +553,28 @@ export class IgxColumnComponent implements AfterContentInit, OnDestroy {
532553
@WatchColumnChanges()
533554
@Input()
534555
public headerGroupClasses = '';
556+
557+
/**
558+
* Sets conditional style properties on the column header group wrapper.
559+
* Similar to `ngStyle` it accepts an object literal where the keys are
560+
* the style properties and the value is the expression to be evaluated.
561+
* ```typescript
562+
* styles = {
563+
* background: 'royalblue',
564+
* color: (column) => column.pinned ? 'red': 'inherit'
565+
* }
566+
* ```
567+
* ```html
568+
* <igx-column [headerGroupStyles]="styles"></igx-column>
569+
* ```
570+
*
571+
* @memberof IgxColumnComponent
572+
*/
573+
@notifyChanges()
574+
@WatchColumnChanges()
575+
@Input()
576+
public headerGroupStyles = null;
577+
535578
/**
536579
* Sets a conditional class selector of the column cells.
537580
* Accepts an object literal, containing key-value pairs,
@@ -562,7 +605,7 @@ export class IgxColumnComponent implements AfterContentInit, OnDestroy {
562605
* ```typescript
563606
* styles = {
564607
* background: 'royalblue',
565-
* color: (rowData, columnKey, cellValue, rowIndex) => value.startsWith('Important') : 'red': 'inherit'
608+
* color: (rowData, columnKey, cellValue, rowIndex) => value.startsWith('Important') ? 'red': 'inherit'
566609
* }
567610
* ```
568611
* ```html

projects/igniteui-angular/src/lib/grids/common/grid-pipes.module.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import {
1919
IgxColumnFormatterPipe,
2020
IgxSummaryFormatterPipe,
2121
IgxGridAddRowPipe,
22-
IgxHeaderGroupWidthPipe
22+
IgxHeaderGroupWidthPipe,
23+
IgxHeaderGroupStylePipe
2324
} from './pipes';
2425

2526
@NgModule({
@@ -42,7 +43,8 @@ import {
4243
IgxGridAddRowPipe,
4344
IgxColumnFormatterPipe,
4445
IgxSummaryFormatterPipe,
45-
IgxHeaderGroupWidthPipe
46+
IgxHeaderGroupWidthPipe,
47+
IgxHeaderGroupStylePipe
4648
],
4749
exports: [
4850
IgxGridFilterConditionPipe,
@@ -63,7 +65,8 @@ import {
6365
IgxGridAddRowPipe,
6466
IgxColumnFormatterPipe,
6567
IgxSummaryFormatterPipe,
66-
IgxHeaderGroupWidthPipe
68+
IgxHeaderGroupWidthPipe,
69+
IgxHeaderGroupStylePipe
6770
],
6871
imports: [
6972
CommonModule

0 commit comments

Comments
 (0)