Skip to content

Commit 01ab881

Browse files
committed
Merge branch 'master' of https://github.com/IgniteUI/igniteui-angular into sstoychev/mrl-next-prev-cell-master
2 parents 052160c + 002a950 commit 01ab881

File tree

15 files changed

+692
-222
lines changed

15 files changed

+692
-222
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ All notable changes for each version of this project will be documented in this
1414
```typescript
1515
public pinningConfiguration: IPinningConfig = { columns: ColumnPinningPosition.End };
1616
```
17+
- `IgxCombo`:
18+
- Added `autoFocusSearch` input that allows to manipulate the combo's opening behavior. When the property is `true` (by default), the combo's search input is focused on open. When set to `false`, the focus goes to the combo items container, which can be used to prevent the software keyboard from activating on mobile devices when opening the combo.
1719

1820
### RTL Support
1921
- `igxSlider` have full right-to-left (RTL) support.
@@ -2487,4 +2489,3 @@ export class IgxCustomFilteringOperand extends IgxFilteringOperand {
24872489
- `IgxDraggableDirective` moved inside `../directives/dragdrop/` folder
24882490
- `IgxRippleDirective` moved inside `../directives/ripple/` folder
24892491
- Folder `"./navigation/nav-service"` renamed to `"./navigation/nav.service"`
2490-

projects/igniteui-angular/src/lib/calendar/calendar-base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
620620
* Deselects date(s) (based on the selection type).
621621
*/
622622
public deselectDate(value?: Date | Date[]) {
623-
if (this.selectedDates === null || this.selectedDates.length === 0) {
623+
if (!this.selectedDates || this.selectedDates.length === 0) {
624624
return;
625625
}
626626

projects/igniteui-angular/src/lib/combo/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ Setting `[displayDensity]` affects the control's items' and inputs' css properti
314314
| `type` | Combo style. - "line", "box", "border", "search" | string |
315315
| `valid` | gets if control is valid, when used in a form | boolean |
316316
| `overlaySettings` | gets/sets the custom overlay settings that control how the drop-down list displays | OverlaySettings |
317+
| `autoFocusSearch` | controls whether the search input should be focused when the combo is opened | boolean |
317318

318319
### Getters
319320
| Name | Description | Type |

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

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,24 @@ describe('igxCombo', () => {
149149
expect(combo.dropdown.toggle).toHaveBeenCalledTimes(1);
150150
expect(combo.collapsed).toBe(false);
151151
});
152+
it(`should not focus search input when property autoFocusSearch=false`, () => {
153+
combo = new IgxComboComponent({ nativeElement: null }, mockCdr, mockSelection as any, mockComboService, null, mockInjector);
154+
const dropdownContainer = { nativeElement: { focus: () => {}}};
155+
combo['dropdownContainer'] = dropdownContainer;
156+
spyOn(combo, 'focusSearchInput');
157+
158+
combo.autoFocusSearch = false;
159+
combo.handleOpened();
160+
expect(combo.focusSearchInput).toHaveBeenCalledTimes(0);
161+
162+
combo.autoFocusSearch = true;
163+
combo.handleOpened();
164+
expect(combo.focusSearchInput).toHaveBeenCalledTimes(1);
165+
166+
combo.autoFocusSearch = false;
167+
combo.handleOpened();
168+
expect(combo.focusSearchInput).toHaveBeenCalledTimes(1);
169+
});
152170
it('should call dropdown toggle with correct overlaySettings', () => {
153171
combo = new IgxComboComponent({ nativeElement: null }, mockCdr, mockSelection as any, mockComboService, null, mockInjector);
154172
const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['toggle']);
@@ -788,6 +806,19 @@ describe('igxCombo', () => {
788806
fixture.detectChanges();
789807
expect(combo.searchInput).toBeFalsy();
790808
});
809+
it('should focus search input', fakeAsync(() => {
810+
combo.toggle();
811+
tick();
812+
fixture.detectChanges();
813+
expect(document.activeElement).toEqual(combo.searchInput.nativeElement);
814+
}));
815+
it('should not focus search input, when autoFocusSearch=false', fakeAsync(() => {
816+
combo.autoFocusSearch = false;
817+
combo.toggle();
818+
tick();
819+
fixture.detectChanges();
820+
expect(document.activeElement).not.toEqual(combo.searchInput.nativeElement);
821+
}));
791822
it('should properly initialize templates', () => {
792823
expect(combo).toBeDefined();
793824
expect(combo.footerTemplate).toBeDefined();
@@ -983,7 +1014,7 @@ describe('igxCombo', () => {
9831014

9841015
productIndex = 485;
9851016
combo.virtualScrollContainer.scrollTo(productIndex);
986-
await wait();
1017+
await wait(30);
9871018
fixture.detectChanges();
9881019
verifyComboData();
9891020
expect(combo.virtualizationState.startIndex + combo.virtualizationState.chunkSize - 1)

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,14 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
813813
@Input()
814814
public type = 'box';
815815

816+
/**
817+
* An @Input property that controls whether the combo's search box
818+
* should be focused after the `onOpened` event is called
819+
* When `false`, the combo's list item container will be focused instead
820+
*/
821+
@Input()
822+
public autoFocusSearch = true;
823+
816824
/**
817825
* Gets if control is valid, when used in a form
818826
*
@@ -1480,7 +1488,15 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas
14801488
*/
14811489
public handleOpened() {
14821490
this.triggerCheck();
1483-
this.focusSearchInput(true);
1491+
1492+
// Disabling focus of the search input should happen only when drop down opens.
1493+
// During keyboard navigation input should receive focus, even the autoFocusSearch is disabled.
1494+
// That is why in such cases focusing of the dropdownContainer happens outside focusSearchInput method.
1495+
if (this.autoFocusSearch) {
1496+
this.focusSearchInput(true);
1497+
} else {
1498+
this.dropdownContainer.nativeElement.focus();
1499+
}
14841500
this.onOpened.emit();
14851501
}
14861502

projects/igniteui-angular/src/lib/date-picker/date-picker.component.html

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,42 @@
11
<ng-template #readOnlyDatePickerTemplate>
2-
<igx-input-group (click)="openDialog()">
2+
<igx-input-group (click)="openDialog()" (mousedown)="mouseDown($event)">
33
<igx-prefix>
44
<igx-icon>today</igx-icon>
55
</igx-prefix>
66
<label *ngIf="labelVisibility" igxLabel>{{label}}</label>
7-
<input #readonlyInput class="igx-date-picker__input-date" igxInput [value]="displayData || ''"
8-
[disabled]="disabled" readonly />
7+
<input
8+
class="igx-date-picker__input-date"
9+
igxInput
10+
[value]="displayData || ''"
11+
[disabled]="disabled"
12+
(blur)="onBlur($event)"
13+
readonly
14+
/>
915
</igx-input-group>
1016
</ng-template>
1117

1218
<ng-template #editableDatePickerTemplate>
13-
<igx-input-group #editableInputGroup [supressInputAutofocus]="true">
19+
<igx-input-group #editableInputGroup [supressInputAutofocus]="true" (mousedown)="mouseDown($event)">
1420
<igx-prefix (click)="openDialog(editableInputGroup.element.nativeElement)">
1521
<igx-icon>today</igx-icon>
1622
</igx-prefix>
1723
<label *ngIf="labelVisibility" igxLabel>{{label}}</label>
18-
<input #editableInput class="igx-date-picker__input-date" igxInput [igxTextSelection]="true"
19-
type="text" [value]="transformedDate"
20-
[igxMask]="inputMask" [placeholder]="mask" [disabled]="disabled" [displayValuePipe]="displayValuePipe"
21-
[focusedValuePipe]="inputValuePipe" (blur)="onBlur($event)" (wheel)="onWheel($event)"
22-
(input)="onInput($event)" (focus)="onFocus()" />
24+
<input
25+
class="igx-date-picker__input-date"
26+
igxInput
27+
[igxTextSelection]="true"
28+
type="text"
29+
[value]="transformedDate"
30+
[igxMask]="inputMask"
31+
[placeholder]="mask"
32+
[disabled]="disabled"
33+
[displayValuePipe]="displayValuePipe"
34+
[focusedValuePipe]="inputValuePipe"
35+
(blur)="onBlur($event)"
36+
(wheel)="onWheel($event)"
37+
(input)="onInput($event)"
38+
(focus)="onFocus()"
39+
/>
2340
<igx-suffix *ngIf="!isEmpty" (click)="clear()">
2441
<igx-icon>clear</igx-icon>
2542
</igx-suffix>

projects/igniteui-angular/src/lib/date-picker/date-picker.component.spec.ts

Lines changed: 128 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
1-
import { Component, ViewChild, ElementRef } from '@angular/core';
1+
import { Component, ViewChild, ElementRef, EventEmitter } from '@angular/core';
22
import { async, fakeAsync, TestBed, tick, flush, ComponentFixture } from '@angular/core/testing';
3-
import { FormsModule, FormGroup, FormBuilder, FormControl, ReactiveFormsModule } from '@angular/forms';
3+
import { FormsModule, FormGroup, FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
44
import { By } from '@angular/platform-browser';
55
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
66
import { IgxDatePickerComponent, IgxDatePickerModule } from './date-picker.component';
77
import { IgxLabelDirective } from '../directives/label/label.directive';
8-
import { IgxInputDirective } from '../directives/input/input.directive';
8+
import { IgxInputDirective, IgxInputState } from '../directives/input/input.directive';
99
import { UIInteractions, wait } from '../test-utils/ui-interactions.spec';
10-
import { IgxInputGroupModule } from '../input-group';
10+
import { IgxInputGroupModule, IgxInputGroupComponent } from '../input-group';
1111
import { IgxTextSelectionModule } from '../directives/text-selection/text-selection.directive';
1212
import { configureTestSuite } from '../test-utils/configure-suite';
1313
import { IgxButtonModule } from '../directives/button/button.directive';
1414
import { IgxCalendarModule } from '../calendar';
1515
import { InteractionMode } from '../core/enums';
1616
import { DateRangeType } from '../core/dates/dateRange';
17+
import { OverlayCancelableEventArgs, OverlayEventArgs, OverlayClosingEventArgs, HorizontalAlignment, VerticalAlignment } from '../services';
1718

1819
describe('IgxDatePicker', () => {
1920
configureTestSuite();
@@ -1260,38 +1261,133 @@ describe('IgxDatePicker', () => {
12601261

12611262
describe('Reactive form', () => {
12621263
let fixture: ComponentFixture<IgxDatePickerReactiveFormComponent>;
1263-
let datePicker: IgxDatePickerComponent;
1264+
let datePickerOnChangeComponent: IgxDatePickerComponent;
1265+
let datePickerOnBlurComponent: IgxDatePickerComponent;
12641266

12651267
beforeEach(() => {
12661268
fixture = TestBed.createComponent(IgxDatePickerReactiveFormComponent);
1267-
datePicker = fixture.componentInstance.datePicker;
1269+
datePickerOnChangeComponent = fixture.componentInstance.datePickerOnChangeComponent;
1270+
datePickerOnBlurComponent = fixture.componentInstance.datePickerOnBlurComponent;
12681271
fixture.detectChanges();
12691272
});
12701273

1271-
// Bug #6025 Date picker does not disable in reactive form
1272-
it('Should disable when form is disabled', fakeAsync(() => {
1274+
it('Should set date picker status to invalid when it is required and has no value', fakeAsync(() => {
1275+
const inputGroupsElements = fixture.debugElement.queryAll(By.directive(IgxInputDirective));
1276+
const inputGroupElement = inputGroupsElements.find(d => d.componentInstance === datePickerOnChangeComponent);
1277+
const inputDirective = inputGroupElement.injector.get(IgxInputDirective) as IgxInputDirective;
1278+
1279+
expect(inputDirective.valid).toEqual(IgxInputState.INITIAL);
1280+
1281+
datePickerOnChangeComponent.value = null;
12731282
fixture.detectChanges();
1274-
const formGroup: FormGroup = fixture.componentInstance.reactiveForm;
1275-
const inputGroup = fixture.debugElement.query(By.css('.igx-input-group'));
12761283

1277-
inputGroup.nativeElement.click();
1278-
tick();
1284+
expect(inputDirective.valid).toEqual(IgxInputState.INVALID);
1285+
}));
1286+
1287+
it('Should set date picker status to invalid when it is required and has no value onBlur', fakeAsync(() => {
1288+
datePickerOnBlurComponent.mode = InteractionMode.DropDown;
1289+
datePickerOnBlurComponent.mask = 'dd/mm/yyyy';
1290+
datePickerOnBlurComponent.inputMask = 'dd/mm/yyyy';
12791291
fixture.detectChanges();
1280-
expect(datePicker.collapsed).toBeFalsy();
12811292

1282-
datePicker.closeCalendar();
1293+
const inputDirectiveElements = fixture.debugElement.queryAll(By.directive(IgxInputDirective));
1294+
const inputDirectiveElement = inputDirectiveElements.find(d => d.componentInstance === datePickerOnBlurComponent);
1295+
const inputDirective = inputDirectiveElement.injector.get(IgxInputDirective) as IgxInputDirective;
1296+
1297+
expect(inputDirective.valid).toEqual(IgxInputState.INITIAL);
1298+
1299+
inputDirectiveElement.triggerEventHandler('focus', {});
12831300
fixture.detectChanges();
12841301

1285-
formGroup.disable();
1286-
tick();
1302+
expect(inputDirective.valid).toEqual(IgxInputState.INITIAL);
1303+
1304+
datePickerOnBlurComponent.value = null;
12871305
fixture.detectChanges();
1306+
expect(inputDirective.valid).toEqual(IgxInputState.INITIAL);
12881307

1289-
inputGroup.nativeElement.click();
1290-
tick();
1308+
inputDirectiveElement.triggerEventHandler('blur', { target: { value: ''}});
12911309
fixture.detectChanges();
1292-
const dateDropDown = document.getElementsByClassName('igx-date-picker--dropdown');
1293-
expect(dateDropDown.length).toEqual(0);
1310+
1311+
expect(inputDirective.valid).toEqual(IgxInputState.INVALID);
12941312
}));
1313+
1314+
// Bug #6025 Date picker does not disable in reactive form
1315+
it('Should disable when form is disabled', () => {
1316+
const formGroup: FormGroup = fixture.componentInstance.reactiveForm;
1317+
const inputGroupsElements = fixture.debugElement.queryAll(By.directive(IgxInputDirective));
1318+
const inputGroupElement = inputGroupsElements.find(d => d.componentInstance === datePickerOnBlurComponent);
1319+
const inputDirective = inputGroupElement.injector.get(IgxInputDirective) as IgxInputDirective;
1320+
expect(inputDirective.disabled).toBeFalsy();
1321+
1322+
formGroup.disable();
1323+
fixture.detectChanges();
1324+
expect(inputDirective.disabled).toBeTruthy();
1325+
});
1326+
});
1327+
1328+
describe('Control value accessor unit tests', () => {
1329+
let ngModel;
1330+
let overlay;
1331+
let element;
1332+
let cdr;
1333+
let moduleRef;
1334+
let injector;
1335+
let inputGroup: IgxInputGroupComponent;
1336+
1337+
beforeEach(() => {
1338+
ngModel = {
1339+
control: { touched: false, dirty: false, validator: null },
1340+
valid: false,
1341+
statusChanges: new EventEmitter(),
1342+
};
1343+
overlay = {
1344+
onOpening: new EventEmitter<OverlayCancelableEventArgs>(),
1345+
onOpened: new EventEmitter<OverlayEventArgs>(),
1346+
onClosed: new EventEmitter<OverlayEventArgs>(),
1347+
onClosing: new EventEmitter<OverlayClosingEventArgs>()
1348+
};
1349+
element = {};
1350+
cdr = {
1351+
markForCheck: () => {},
1352+
detectChanges: () => {},
1353+
detach: () => {},
1354+
reattach: () => {}
1355+
};
1356+
moduleRef = {};
1357+
injector = { get: () => ngModel };
1358+
inputGroup = new IgxInputGroupComponent(null, null);
1359+
});
1360+
1361+
it('should initialize date picker with required correctly', () => {
1362+
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
1363+
datePicker['inputGroup'] = inputGroup;
1364+
ngModel.control.validator = () => ({ required: true});
1365+
datePicker.ngOnInit();
1366+
datePicker.ngAfterViewInit();
1367+
datePicker.ngAfterViewChecked();
1368+
1369+
expect(datePicker).toBeDefined();
1370+
expect(inputGroup.isRequired).toBeTruthy();
1371+
});
1372+
1373+
it('should update inputGroup isRequired correctly', () => {
1374+
const datePicker = new IgxDatePickerComponent(overlay, element, cdr, moduleRef, injector);
1375+
datePicker['inputGroup'] = inputGroup;
1376+
datePicker.ngOnInit();
1377+
datePicker.ngAfterViewInit();
1378+
datePicker.ngAfterViewChecked();
1379+
1380+
expect(datePicker).toBeDefined();
1381+
expect(inputGroup.isRequired).toBeFalsy();
1382+
1383+
ngModel.control.validator = () => ({ required: true});
1384+
ngModel.statusChanges.emit();
1385+
expect(inputGroup.isRequired).toBeTruthy();
1386+
1387+
ngModel.control.validator = () => ({ required: false});
1388+
ngModel.statusChanges.emit();
1389+
expect(inputGroup.isRequired).toBeFalsy();
1390+
});
12951391
});
12961392
});
12971393

@@ -1442,23 +1538,27 @@ export class IgxDatePickerOpeningComponent {
14421538
@Component({
14431539
template: `
14441540
<form [formGroup]="reactiveForm">
1445-
<p>
1446-
<igx-date-picker formControlName="datePickerReactive" #datePickerReactive></igx-date-picker>
1447-
</p>
1448-
</form>
1541+
<igx-date-picker formControlName="datePickerOnChange" #datePickerOnChangeComponent></igx-date-picker>
1542+
<igx-date-picker formControlName="datePickerOnBlur" #datePickerOnBlurComponent></igx-date-picker>
1543+
</form>
14491544
`
14501545
})
14511546
class IgxDatePickerReactiveFormComponent {
1452-
@ViewChild('datePickerReactive', { read: IgxDatePickerComponent, static: true })
1453-
public datePicker: IgxDatePickerComponent;
1547+
@ViewChild('datePickerOnChangeComponent', { read: IgxDatePickerComponent, static: true })
1548+
public datePickerOnChangeComponent: IgxDatePickerComponent;
1549+
1550+
@ViewChild('datePickerOnBlurComponent', { read: IgxDatePickerComponent, static: true })
1551+
public datePickerOnBlurComponent: IgxDatePickerComponent;
1552+
14541553
reactiveForm: FormGroup;
14551554

14561555
constructor(fb: FormBuilder) {
1556+
const date = new Date(2000, 10, 15);
14571557
this.reactiveForm = fb.group({
1458-
datePickerReactive: new FormControl(''),
1558+
datePickerOnChange: [date, Validators.required],
1559+
datePickerOnBlur: [date, { updateOn: 'blur', validators: Validators.required}]
14591560
});
14601561
}
1461-
onSubmitReactive() { }
14621562
}
14631563

14641564
@Component({

0 commit comments

Comments
 (0)