Skip to content

Commit d0b0151

Browse files
authored
Merge branch '10.0.x' into valadzhov/textselection-directive-in-timepicker-6827-10x
2 parents 0ed5c24 + 9529bd1 commit d0b0151

File tree

9 files changed

+184
-31
lines changed

9 files changed

+184
-31
lines changed

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

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

19531953
/**
@@ -4655,14 +4655,37 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
46554655
return 0;
46564656
}
46574657

4658+
/**
4659+
* @hidden
4660+
*/
4661+
protected getComputedHeight(elem) {
4662+
return elem.offsetHeight ? parseFloat(this.document.defaultView.getComputedStyle(elem).getPropertyValue('height')) : 0;
4663+
}
4664+
/**
4665+
* @hidden
4666+
*/
4667+
protected getFooterHeight(): number {
4668+
return this.summariesHeight || this.getComputedHeight(this.tfoot.nativeElement);
4669+
}
4670+
/**
4671+
* @hidden
4672+
*/
4673+
protected getTheadRowHeight(): number {
4674+
const height = this.getComputedHeight(this.theadRow.nativeElement);
4675+
return (!this.allowFiltering || (this.allowFiltering && this.filterMode !== FilterMode.quickFilter)) ?
4676+
height - this.getFilterCellHeight() :
4677+
height;
4678+
}
4679+
46584680
/**
46594681
* @hidden
46604682
*/
46614683
protected getToolbarHeight(): number {
46624684
let toolbarHeight = 0;
46634685
if (this.showToolbar && this.toolbarHtml != null) {
4686+
const height = this.getComputedHeight(this.toolbarHtml.nativeElement);
46644687
toolbarHeight = this.toolbarHtml.nativeElement.firstElementChild ?
4665-
this.toolbarHtml.nativeElement.offsetHeight : 0;
4688+
height : 0;
46664689
}
46674690
return toolbarHeight;
46684691
}
@@ -4673,8 +4696,9 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
46734696
protected getPagingFooterHeight(): number {
46744697
let pagingHeight = 0;
46754698
if (this.footer) {
4699+
const height = this.getComputedHeight(this.footer.nativeElement);
46764700
pagingHeight = this.footer.nativeElement.firstElementChild ?
4677-
this.footer.nativeElement.offsetHeight : 0;
4701+
height : 0;
46784702
}
46794703
return pagingHeight;
46804704
}
@@ -4697,17 +4721,15 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
46974721
if (!this._height) {
46984722
return null;
46994723
}
4700-
4701-
const actualTheadRow = (!this.allowFiltering || (this.allowFiltering && this.filterMode !== FilterMode.quickFilter)) ?
4702-
this.theadRow.nativeElement.offsetHeight - this.getFilterCellHeight() :
4703-
this.theadRow.nativeElement.offsetHeight;
4704-
const footerHeight = this.summariesHeight || this.tfoot.nativeElement.offsetHeight - this.tfoot.nativeElement.clientHeight;
4724+
const actualTheadRow = this.getTheadRowHeight();
4725+
const footerHeight = this.getFooterHeight();
47054726
const toolbarHeight = this.getToolbarHeight();
47064727
const pagingHeight = this.getPagingFooterHeight();
47074728
const groupAreaHeight = this.getGroupAreaHeight();
4729+
const scrHeight = this.getComputedHeight(this.scr.nativeElement);
47084730
const renderedHeight = toolbarHeight + actualTheadRow +
47094731
footerHeight + pagingHeight + groupAreaHeight +
4710-
this.scr.nativeElement.clientHeight;
4732+
scrHeight;
47114733

47124734
let gridHeight = 0;
47134735

@@ -4718,13 +4740,13 @@ export class IgxGridBaseDirective extends DisplayDensityBase implements
47184740
const bodyHeight = this.getDataBasedBodyHeight();
47194741
return bodyHeight > 0 ? bodyHeight : null;
47204742
}
4721-
gridHeight = parseInt(computed, 10);
4743+
gridHeight = parseFloat(computed);
47224744
} else {
47234745
gridHeight = parseInt(this._height, 10);
47244746
}
47254747
const height = Math.abs(gridHeight - renderedHeight);
47264748

4727-
if (height === 0 || isNaN(gridHeight)) {
4749+
if (Math.round(height) === 0 || isNaN(gridHeight)) {
47284750
const bodyHeight = this.defaultTargetBodyHeight;
47294751
return bodyHeight > 0 ? bodyHeight : null;
47304752
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,7 @@ export class IgxGridComponent extends IgxGridBaseDirective implements GridType,
915915
* @hidden @internal
916916
*/
917917
protected getGroupAreaHeight(): number {
918-
return this.groupArea ? this.groupArea.nativeElement.offsetHeight : 0;
918+
return this.groupArea ? this.getComputedHeight(this.groupArea.nativeElement) : 0;
919919
}
920920

921921
/**

projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.selection.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,36 @@ describe('IgxHierarchicalGrid selection #hGrid', () => {
674674
// check row is not selected
675675
GridSelectionFunctions.verifyRowSelected(firstRow, false);
676676
});
677+
678+
it('Should not clear root selection state when changing selection mode of child grid', () => {
679+
rowIsland1.rowSelection = GridSelectionMode.multiple;
680+
const firstRow = hierarchicalGrid.getRowByIndex(0) as IgxHierarchicalRowComponent;
681+
const secondRow = hierarchicalGrid.getRowByIndex(1) as IgxHierarchicalRowComponent;
682+
GridSelectionFunctions.clickRowCheckbox(firstRow);
683+
GridSelectionFunctions.clickRowCheckbox(secondRow);
684+
685+
fix.detectChanges();
686+
expect(hierarchicalGrid.getRowByKey('0').selected).toBeTrue();
687+
688+
const thirdRow = hierarchicalGrid.getRowByIndex(2) as IgxHierarchicalRowComponent;
689+
thirdRow.toggle();
690+
fix.detectChanges();
691+
692+
const childGrid = rowIsland1.rowIslandAPI.getChildGrids()[0];
693+
const firstChildRow = childGrid.getRowByIndex(0) as IgxHierarchicalRowComponent;
694+
const secondChildRow = childGrid.getRowByIndex(1) as IgxHierarchicalRowComponent;
695+
GridSelectionFunctions.clickRowCheckbox(firstChildRow);
696+
GridSelectionFunctions.clickRowCheckbox(secondChildRow);
697+
fix.detectChanges();
698+
699+
expect(hierarchicalGrid.selectedRows().length).toEqual(2);
700+
expect(childGrid.selectedRows().length).toEqual(2);
701+
702+
rowIsland1.rowSelection = GridSelectionMode.single;
703+
fix.detectChanges();
704+
expect(hierarchicalGrid.selectedRows().length).toEqual(2);
705+
expect(childGrid.selectedRows().length).toEqual(0);
706+
});
677707
});
678708

679709
describe('Row Selection CRUD', () => {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ export interface IGridCreatedEventArgs extends IBaseEventArgs {
5151
changeDetection: ChangeDetectionStrategy.OnPush,
5252
selector: 'igx-row-island',
5353
template: ``,
54-
providers: [IgxRowIslandAPIService]
54+
providers: [IgxRowIslandAPIService,
55+
IgxGridSelectionService]
5556
})
5657
export class IgxRowIslandComponent extends IgxHierarchicalGridBaseDirective
5758
implements AfterContentInit, AfterViewInit, OnChanges, OnInit, OnDestroy, DoCheck {

projects/igniteui-angular/src/lib/navigation-drawer/navigation-drawer.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,10 @@ export class IgxNavigationDrawerComponent implements
495495
this.setDrawerWidth(changes.width.currentValue);
496496
}
497497

498+
if (changes.isOpen) {
499+
this.setDrawerWidth(this.isOpen ? this.width : (this.miniTemplate ? this.miniWidth : ''));
500+
}
501+
498502
if (changes.miniWidth) {
499503
if (!this.isOpen) {
500504
this.setDrawerWidth(changes.miniWidth.currentValue);

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';
@@ -455,15 +457,15 @@ describe('igxSelect', () => {
455457
describe('Form tests: ', () => {
456458
it('Should properly initialize when used as a reactive form control - with validators', fakeAsync(() => {
457459
const fix = TestBed.createComponent(IgxSelectReactiveFormComponent);
458-
const inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
460+
const inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
459461
fix.detectChanges();
460462
const selectComp = fix.componentInstance.select;
461463
const selectFormReference = fix.componentInstance.reactiveForm.controls.optionsSelect;
462464
expect(selectFormReference).toBeDefined();
463465
expect(selectComp).toBeDefined();
464466
expect(selectComp.selectedItem).toBeUndefined();
465467
expect(selectComp.value).toEqual('');
466-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
468+
expect(inputGroupIsRequiredClass).toBeDefined();
467469
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
468470

469471
selectComp.toggle();
@@ -491,18 +493,57 @@ describe('igxSelect', () => {
491493

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

508549
selectComp.onBlur();
@@ -522,18 +563,42 @@ describe('igxSelect', () => {
522563

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

566+
// Re-add all Validators
525567
fix.componentInstance.addValidators(formGroup);
526568
fix.detectChanges();
527-
inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
528-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
529569

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

532597

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

536-
let inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
601+
let inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
537602
fix.detectChanges();
538603
const selectComp = fix.componentInstance.select;
539604
const selectFormReference = fix.componentInstance.ngForm.form;
@@ -543,7 +608,7 @@ describe('igxSelect', () => {
543608
fix.detectChanges();
544609
expect(selectComp.selectedItem).toBeUndefined();
545610
expect(selectComp.value).toBeNull();
546-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
611+
expect(inputGroupIsRequiredClass).toBeDefined();
547612
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
548613

549614
selectComp.toggle();
@@ -569,14 +634,14 @@ describe('igxSelect', () => {
569634

570635
fix.componentInstance.isRequired = false;
571636
fix.detectChanges();
572-
inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
573-
expect(inputGroupWithRequiredAsterisk).toBeNull();
637+
inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
638+
expect(inputGroupIsRequiredClass).toBeNull();
574639
}));
575640

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

579-
let inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
644+
let inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
580645
fix.detectChanges();
581646
const selectComp = fix.componentInstance.select;
582647
const selectFormReference = fix.componentInstance.ngForm.form;
@@ -585,7 +650,7 @@ describe('igxSelect', () => {
585650
expect(selectComp).toBeDefined();
586651
expect(selectComp.selectedItem).toBeUndefined();
587652
expect(selectComp.value).toBeUndefined();
588-
expect(inputGroupWithRequiredAsterisk).toBeNull();
653+
expect(inputGroupIsRequiredClass).toBeNull();
589654
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
590655

591656
selectComp.onBlur();
@@ -607,8 +672,8 @@ describe('igxSelect', () => {
607672

608673
fix.componentInstance.isRequired = true;
609674
fix.detectChanges();
610-
inputGroupWithRequiredAsterisk = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
611-
expect(inputGroupWithRequiredAsterisk).toBeDefined();
675+
inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
676+
expect(inputGroupIsRequiredClass).toBeDefined();
612677
}));
613678

614679
it('Should have correctly bound focus and blur handlers', () => {
@@ -2821,6 +2886,17 @@ class IgxSelectReactiveFormComponent {
28212886
form.get(key).updateValueAndValidity();
28222887
}
28232888
}
2889+
2890+
public markAsTouched() {
2891+
if (!this.reactiveForm.valid) {
2892+
for (const key in this.reactiveForm.controls) {
2893+
if (this.reactiveForm.controls[key]) {
2894+
this.reactiveForm.controls[key].markAsTouched();
2895+
this.reactiveForm.controls[key].updateValueAndValidity();
2896+
}
2897+
}
2898+
}
2899+
}
28242900
}
28252901

28262902
@Component({

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,11 +402,19 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec
402402
}
403403

404404
protected manageRequiredAsterisk(): void {
405+
const hasRequiredHTMLAttribute = this.elementRef.nativeElement.hasAttribute('required');
405406
if (this.ngControl && this.ngControl.control.validator) {
406407
// Run the validation with empty object to check if required is enabled.
407408
const error = this.ngControl.control.validator({} as AbstractControl);
408409
this.inputGroup.isRequired = error && error.required;
409410
this.cdr.markForCheck();
411+
412+
// If validator is dynamically cleared and no required HTML attribute is set,
413+
// reset label's required class(asterisk) and IgxInputState #6896
414+
} else if (this.inputGroup.isRequired && this.ngControl && !this.ngControl.control.validator && !hasRequiredHTMLAttribute) {
415+
this.input.valid = IgxInputState.INITIAL;
416+
this.inputGroup.isRequired = false;
417+
this.cdr.markForCheck();
410418
}
411419
}
412420
private setSelection(item: IgxDropDownItemBaseDirective) {

src/app/grid-column-moving/grid-column-moving.sample.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,14 @@
1010
margin-bottom: 16px;
1111
max-width: 900px;
1212
}
13+
14+
.references {
15+
font-size: .75em;
16+
}
17+
18+
.references > p {
19+
margin: 0;
20+
letter-spacing: initial;
21+
line-height: initial;
22+
font-size: 1em;
23+
}

0 commit comments

Comments
 (0)