Skip to content

Commit cbb9468

Browse files
authored
Merge branch '9.1.x' into valadzhov/textselection-directive-in-timepicker-6827-91x
2 parents 675b75d + bd720ae commit cbb9468

File tree

11 files changed

+179
-36
lines changed

11 files changed

+179
-36
lines changed

projects/igniteui-angular/src/lib/core/styles/components/column-hiding/_column-hiding-theme.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
display: flex;
8585
flex-flow: column nowrap;
8686
overflow-y: auto;
87+
outline-style: none;
8788
}
8889

8990
%column-hiding-item {

projects/igniteui-angular/src/lib/grids/grid-base.directive.ts

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1945,7 +1945,7 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
19451945
/**
19461946
* @hidden @internal
19471947
*/
1948-
@ContentChildren(IgxRowEditTabStopDirective)
1948+
@ContentChildren(IgxRowEditTabStopDirective, { descendants: true })
19491949
public rowEditTabsCUSTOM: QueryList<IgxRowEditTabStopDirective>;
19501950

19511951
/**
@@ -4635,14 +4635,37 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
46354635
return 0;
46364636
}
46374637

4638+
/**
4639+
* @hidden
4640+
*/
4641+
protected getComputedHeight(elem) {
4642+
return elem.offsetHeight ? parseFloat(this.document.defaultView.getComputedStyle(elem).getPropertyValue('height')) : 0;
4643+
}
4644+
/**
4645+
* @hidden
4646+
*/
4647+
protected getFooterHeight(): number {
4648+
return this.summariesHeight || this.getComputedHeight(this.tfoot.nativeElement);
4649+
}
4650+
/**
4651+
* @hidden
4652+
*/
4653+
protected getTheadRowHeight(): number {
4654+
const height = this.getComputedHeight(this.theadRow.nativeElement);
4655+
return (!this.allowFiltering || (this.allowFiltering && this.filterMode !== FilterMode.quickFilter)) ?
4656+
height - this.getFilterCellHeight() :
4657+
height;
4658+
}
4659+
46384660
/**
46394661
* @hidden
46404662
*/
46414663
protected getToolbarHeight(): number {
46424664
let toolbarHeight = 0;
46434665
if (this.showToolbar && this.toolbarHtml != null) {
4666+
const height = this.getComputedHeight(this.toolbarHtml.nativeElement);
46444667
toolbarHeight = this.toolbarHtml.nativeElement.firstElementChild ?
4645-
this.toolbarHtml.nativeElement.offsetHeight : 0;
4668+
height : 0;
46464669
}
46474670
return toolbarHeight;
46484671
}
@@ -4653,8 +4676,9 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
46534676
protected getPagingFooterHeight(): number {
46544677
let pagingHeight = 0;
46554678
if (this.footer) {
4679+
const height = this.getComputedHeight(this.footer.nativeElement);
46564680
pagingHeight = this.footer.nativeElement.firstElementChild ?
4657-
this.footer.nativeElement.offsetHeight : 0;
4681+
height : 0;
46584682
}
46594683
return pagingHeight;
46604684
}
@@ -4677,17 +4701,15 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
46774701
if (!this._height) {
46784702
return null;
46794703
}
4680-
4681-
const actualTheadRow = (!this.allowFiltering || (this.allowFiltering && this.filterMode !== FilterMode.quickFilter)) ?
4682-
this.theadRow.nativeElement.offsetHeight - this.getFilterCellHeight() :
4683-
this.theadRow.nativeElement.offsetHeight;
4684-
const footerHeight = this.summariesHeight || this.tfoot.nativeElement.offsetHeight - this.tfoot.nativeElement.clientHeight;
4704+
const actualTheadRow = this.getTheadRowHeight();
4705+
const footerHeight = this.getFooterHeight();
46854706
const toolbarHeight = this.getToolbarHeight();
46864707
const pagingHeight = this.getPagingFooterHeight();
46874708
const groupAreaHeight = this.getGroupAreaHeight();
4709+
const scrHeight = this.getComputedHeight(this.scr.nativeElement);
46884710
const renderedHeight = toolbarHeight + actualTheadRow +
46894711
footerHeight + pagingHeight + groupAreaHeight +
4690-
this.scr.nativeElement.clientHeight;
4712+
scrHeight;
46914713

46924714
let gridHeight = 0;
46934715

@@ -4698,13 +4720,13 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
46984720
const bodyHeight = this.getDataBasedBodyHeight();
46994721
return bodyHeight > 0 ? bodyHeight : null;
47004722
}
4701-
gridHeight = parseInt(computed, 10);
4723+
gridHeight = parseFloat(computed);
47024724
} else {
47034725
gridHeight = parseInt(this._height, 10);
47044726
}
47054727
const height = Math.abs(gridHeight - renderedHeight);
47064728

4707-
if (height === 0 || isNaN(gridHeight)) {
4729+
if (Math.round(height) === 0 || isNaN(gridHeight)) {
47084730
const bodyHeight = this.defaultTargetBodyHeight;
47094731
return bodyHeight > 0 ? bodyHeight : null;
47104732
}

projects/igniteui-angular/src/lib/grids/grid/grid.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,7 @@ export class IgxGridComponent extends IgxGridBaseDirective implements GridType,
906906
* @hidden @internal
907907
*/
908908
protected getGroupAreaHeight(): number {
909-
return this.groupArea ? this.groupArea.nativeElement.offsetHeight : 0;
909+
return this.groupArea ? this.getComputedHeight(this.groupArea.nativeElement) : 0;
910910
}
911911

912912
/**

projects/igniteui-angular/src/lib/grids/hiding/column-hiding.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<div class="igx-column-hiding__header">
1+
<div class="igx-column-hiding__header" >
22
<h4 class="igx-column-hiding__header-title" *ngIf="title">{{ title }}</h4>
33

44
<igx-input-group class="igx-column-hiding__header-input" *ngIf="!disableFilter">
@@ -10,7 +10,7 @@ <h4 class="igx-column-hiding__header-title" *ngIf="title">{{ title }}</h4>
1010
</igx-input-group>
1111
</div>
1212

13-
<div class="igx-column-hiding__columns"
13+
<div class="igx-column-hiding__columns" tabindex="0"
1414
[style.max-height]="columnsAreaMaxHeight">
1515
<igx-checkbox
1616
*ngFor="let columnItem of hidableColumns"

projects/igniteui-angular/src/lib/grids/pinning/column-pinning.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ <h4 class="igx-column-hiding__header-title" *ngIf="title">{{ title }}</h4>
1111
</igx-input-group>
1212
</div>
1313

14-
<div class="igx-column-hiding__columns"
14+
<div class="igx-column-hiding__columns" tabindex="0"
1515
[style.max-height]="columnsAreaMaxHeight">
1616
<igx-checkbox igxColumnPinningItem
1717
*ngFor="let columnItem of pinnableColumns"

projects/igniteui-angular/src/lib/grids/toolbar/grid-toolbar.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
<span>{{ grid.hiddenColumnsText }}</span>
3232
</div>
3333
</button>
34-
<igx-drop-down #columnHidingDropdown>
34+
<igx-drop-down #columnHidingDropdown (onClosing)="onClosingColumnHiding($event)">
3535
<igx-column-hiding
3636
[columns]="grid.columns"
3737
[title]="grid.columnHidingTitle"
@@ -52,7 +52,7 @@
5252
<span></span>
5353
</div>
5454
</button>
55-
<igx-drop-down #columnPinningDropdown>
55+
<igx-drop-down #columnPinningDropdown (onClosing)="onClosingColumnPinning($event)">
5656
<igx-column-pinning
5757
[columns]="grid.columns"
5858
[title]="grid.columnPinningTitle"

projects/igniteui-angular/src/lib/grids/toolbar/grid-toolbar.component.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ export class IgxGridToolbarComponent extends DisplayDensityBase implements After
236236
@Optional() public csvExporter: IgxCsvExporterService,
237237
@Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions,
238238
private iconService: IgxIconService) {
239-
super(_displayDensityOptions);
239+
super(_displayDensityOptions);
240240
}
241241

242242
private _positionSettings: PositionSettings = {
@@ -410,4 +410,28 @@ export class IgxGridToolbarComponent extends DisplayDensityBase implements After
410410
}
411411
}
412412
}
413+
414+
/**
415+
* @hidden @internal
416+
*/
417+
public onClosingColumnHiding(args) {
418+
const activeElem = document.activeElement;
419+
420+
if (!args.event && activeElem !== this.grid.nativeElement &&
421+
!this.columnHidingButton.nativeElement.contains(activeElem)) {
422+
args.cancel = true;
423+
}
424+
}
425+
426+
/**
427+
* @hidden @internal
428+
*/
429+
public onClosingColumnPinning(args) {
430+
const activeElem = document.activeElement;
431+
432+
if (!args.event && activeElem !== this.grid.nativeElement &&
433+
!this.columnPinningButton.nativeElement.contains(activeElem)) {
434+
args.cancel = true;
435+
}
436+
}
413437
}

projects/igniteui-angular/src/lib/select/select.component.spec.ts

Lines changed: 91 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const CSS_CLASS_DISABLED_ITEM = 'igx-drop-down__item--disabled';
2828
const CSS_CLASS_FOCUSED_ITEM = 'igx-drop-down__item--focused';
2929
const CSS_CLASS_INPUT_GROUP_BOX = 'igx-input-group--box';
3030
const CSS_CLASS_INPUT_GROUP_REQUIRED = 'igx-input-group--required';
31+
const CSS_CLASS_INPUT_GROUP_INVALID = 'igx-input-group--invalid ';
32+
const CSS_CLASS_INPUT_GROUP_LABEL = 'igx-input-group__label';
3133
const CSS_CLASS_INPUT_GROUP_BORDER = 'igx-input-group--border';
3234
const CSS_CLASS_INPUT_GROUP_COMFORTABLE = 'igx-input-group--comfortable';
3335
const CSS_CLASS_INPUT_GROUP_COSY = 'igx-input-group--cosy';
@@ -456,15 +458,15 @@ describe('igxSelect', () => {
456458
describe('Form tests: ', () => {
457459
it('Should properly initialize when used as a reactive form control - with validators', fakeAsync(() => {
458460
const fix = TestBed.createComponent(IgxSelectReactiveFormComponent);
459-
const inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
461+
const inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
460462
fix.detectChanges();
461463
const selectComp = fix.componentInstance.select;
462464
const selectFormReference = fix.componentInstance.reactiveForm.controls.optionsSelect;
463465
expect(selectFormReference).toBeDefined();
464466
expect(selectComp).toBeDefined();
465467
expect(selectComp.selectedItem).toBeUndefined();
466468
expect(selectComp.value).toEqual('');
467-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
469+
expect(inputGroupIsRequiredClass).toBeDefined();
468470
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
469471

470472
selectComp.toggle();
@@ -492,18 +494,57 @@ describe('igxSelect', () => {
492494

493495
it('Should properly initialize when used as a reactive form control - without initial validators', fakeAsync(() => {
494496
const fix = TestBed.createComponent(IgxSelectReactiveFormComponent);
495-
496-
let inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
497497
fix.detectChanges();
498+
// 1) check if label's --required class and its asterisk are applied
499+
const dom = fix.debugElement;
498500
const selectComp = fix.componentInstance.select;
499501
const formGroup: FormGroup = fix.componentInstance.reactiveForm;
502+
let inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
503+
let inputGroupInvalidClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_INVALID));
504+
// interaction test - expect actual asterisk
505+
// The only way to get a pseudo elements like :before OR :after is to use getComputedStyle(element [, pseudoElt]),
506+
// as these are not in the actual DOM
507+
let asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
508+
expect(asterisk).toBe('"*"');
509+
expect(inputGroupIsRequiredClass).toBeDefined();
510+
expect(inputGroupIsRequiredClass).not.toBeNull();
511+
512+
// 2) check that input group's --invalid class is NOT applied
513+
expect(inputGroupInvalidClass).toBeNull();
514+
515+
// interaction test - markAsTouched + open&close so the --invalid and --required classes are applied
516+
fix.debugElement.componentInstance.markAsTouched();
517+
const inputGroup = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP));
518+
inputGroup.nativeElement.click();
519+
const toggleBtn = fix.debugElement.query(By.css('.' + CSS_CLASS_TOGGLE_BUTTON));
520+
toggleBtn.nativeElement.click();
521+
tick();
522+
fix.detectChanges();
523+
expect(selectComp.collapsed).toEqual(true);
524+
525+
inputGroupInvalidClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_INVALID));
526+
expect(inputGroupInvalidClass).not.toBeNull();
527+
expect(inputGroupInvalidClass).not.toBeUndefined();
528+
529+
inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
530+
expect(inputGroupIsRequiredClass).not.toBeNull();
531+
expect(inputGroupIsRequiredClass).not.toBeUndefined();
532+
533+
// 3) Check if the input group's --invalid and --required classes are removed when validator is dynamically cleared
500534
fix.componentInstance.removeValidators(formGroup);
535+
fix.detectChanges();
536+
tick();
537+
538+
inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
501539
const selectFormReference = fix.componentInstance.reactiveForm.controls.optionsSelect;
540+
// interaction test - expect no asterisk
541+
asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
502542
expect(selectFormReference).toBeDefined();
503543
expect(selectComp).toBeDefined();
504544
expect(selectComp.selectedItem).toBeUndefined();
505545
expect(selectComp.value).toEqual('');
506-
expect(inputGroupWithRequiredAsterisk).toBeNull();
546+
expect(inputGroupIsRequiredClass).toBeNull();
547+
expect(asterisk).toBe('none');
507548
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
508549

509550
selectComp.onBlur();
@@ -523,18 +564,42 @@ describe('igxSelect', () => {
523564

524565
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
525566

567+
// Re-add all Validators
526568
fix.componentInstance.addValidators(formGroup);
527569
fix.detectChanges();
528-
inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
529-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
530570

571+
inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
572+
expect(inputGroupIsRequiredClass).toBeDefined();
573+
expect(inputGroupIsRequiredClass).not.toBeNull();
574+
expect(inputGroupIsRequiredClass).not.toBeUndefined();
575+
// interaction test - expect actual asterisk
576+
asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
577+
expect(asterisk).toBe('"*"');
578+
579+
// 4) Should NOT remove asterisk, when remove validators on igxSelect with required HTML attribute set(edge case)
580+
// set required HTML attribute
581+
inputGroup.parent.nativeElement.setAttribute('required', '');
582+
// Re-add all Validators
583+
fix.componentInstance.addValidators(formGroup);
584+
fix.detectChanges();
585+
// update and clear validators
586+
fix.componentInstance.removeValidators(formGroup);
587+
fix.detectChanges();
588+
tick();
589+
// expect asterisk
590+
asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
591+
expect(asterisk).toBe('"*"');
592+
inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
593+
expect(inputGroupIsRequiredClass).toBeDefined();
594+
expect(inputGroupIsRequiredClass).not.toBeNull();
595+
expect(inputGroupIsRequiredClass).not.toBeUndefined();
531596
}));
532597

533598

534599
it('Should properly initialize when used as a form control - with initial validators', fakeAsync(() => {
535600
const fix = TestBed.createComponent(IgxSelectTemplateFormComponent);
536601

537-
let inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
602+
let inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
538603
fix.detectChanges();
539604
const selectComp = fix.componentInstance.select;
540605
const selectFormReference = fix.componentInstance.ngForm.form;
@@ -544,7 +609,7 @@ describe('igxSelect', () => {
544609
fix.detectChanges();
545610
expect(selectComp.selectedItem).toBeUndefined();
546611
expect(selectComp.value).toBeNull();
547-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
612+
expect(inputGroupIsRequiredClass).toBeDefined();
548613
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
549614

550615
selectComp.toggle();
@@ -570,14 +635,14 @@ describe('igxSelect', () => {
570635

571636
fix.componentInstance.isRequired = false;
572637
fix.detectChanges();
573-
inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
574-
expect(inputGroupWithRequiredAsterisk).toBeNull();
638+
inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
639+
expect(inputGroupIsRequiredClass).toBeNull();
575640
}));
576641

577642
it('Should properly initialize when used as a form control - without initial validators', fakeAsync(() => {
578643
const fix = TestBed.createComponent(IgxSelectTemplateFormComponent);
579644

580-
let inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
645+
let inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
581646
fix.detectChanges();
582647
const selectComp = fix.componentInstance.select;
583648
const selectFormReference = fix.componentInstance.ngForm.form;
@@ -586,7 +651,7 @@ describe('igxSelect', () => {
586651
expect(selectComp).toBeDefined();
587652
expect(selectComp.selectedItem).toBeUndefined();
588653
expect(selectComp.value).toBeUndefined();
589-
expect(inputGroupWithRequiredAsterisk).toBeNull();
654+
expect(inputGroupIsRequiredClass).toBeNull();
590655
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
591656

592657
selectComp.onBlur();
@@ -608,8 +673,8 @@ describe('igxSelect', () => {
608673

609674
fix.componentInstance.isRequired = true;
610675
fix.detectChanges();
611-
inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
612-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
676+
inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
677+
expect(inputGroupIsRequiredClass).toBeDefined();
613678
}));
614679

615680
it('Should have correctly bound focus and blur handlers', () => {
@@ -2796,6 +2861,17 @@ class IgxSelectReactiveFormComponent {
27962861
form.get(key).updateValueAndValidity();
27972862
}
27982863
}
2864+
2865+
public markAsTouched() {
2866+
if (!this.reactiveForm.valid) {
2867+
for (const key in this.reactiveForm.controls) {
2868+
if (this.reactiveForm.controls[key]) {
2869+
this.reactiveForm.controls[key].markAsTouched();
2870+
this.reactiveForm.controls[key].updateValueAndValidity();
2871+
}
2872+
}
2873+
}
2874+
}
27992875
}
28002876

28012877
@Component({

0 commit comments

Comments
 (0)