Skip to content

Commit cc7b014

Browse files
committed
feat(drp): align activeDate behavior with WC + tests
1 parent 62cfb04 commit cc7b014

File tree

4 files changed

+176
-10
lines changed

4 files changed

+176
-10
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
715715
this.selectMultiple(value);
716716
break;
717717
case CalendarSelection.RANGE:
718-
this.selectRange(value, true);
718+
this.selectRange(value);
719719
break;
720720
}
721721
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,15 @@ describe('IgxDatePicker', () => {
343343
const expectedDate = new Date(2025, 0, 2);
344344
expect(datePicker.value).toEqual(expectedDate);
345345
expect(datePicker.activeDate).toEqual(expectedDate);
346+
347+
const activeDescendantDate = new Date(expectedDate.setHours(0, 0, 0, 0)).getTime().toString();
348+
expect(datePicker['_calendar'].activeDate).toEqual(expectedDate);
349+
expect(datePicker['_calendar'].viewDate.getMonth()).toEqual(expectedDate.getMonth());
350+
expect(datePicker['_calendar'].value).toEqual(expectedDate);
351+
const wrapper = fixture.debugElement.query(By.css('.igx-calendar__wrapper')).nativeElement;
352+
expect(wrapper.getAttribute('aria-activedescendant')).toEqual(activeDescendantDate);
353+
}));
354+
346355
it('should update the calendar view and active date on typing a date that is not in the current view', fakeAsync(() => {
347356
const date = new Date(2025, 0, 1);
348357
datePicker.value = date;

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

Lines changed: 123 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ describe('IgxDateRangePicker', () => {
6363
let mockCalendar: IgxCalendarComponent;
6464
let mockDaysView: any;
6565
let mockAnimationService: AnimationService;
66+
let mockCdr: any;
6667
const elementRef = { nativeElement: null };
6768
const platform = {} as any;
6869
const mockNgControl = jasmine.createSpyObj('NgControl',
@@ -88,6 +89,9 @@ describe('IgxDateRangePicker', () => {
8889
mockInjector = jasmine.createSpyObj('Injector', {
8990
get: mockNgControl
9091
});
92+
mockCdr = jasmine.createSpyObj('ChangeDetectorRef', {
93+
detectChanges: () => { }
94+
});
9195
mockAnimationBuilder = {
9296
build: (a: AnimationMetadata | AnimationMetadata[]) => ({
9397
create: (e: any, opt?: AnimationOptions) => ({
@@ -244,7 +248,7 @@ describe('IgxDateRangePicker', () => {
244248
});
245249

246250
it('should disable calendar dates when min and/or max values as dates are provided', () => {
247-
const dateRange = new IgxDateRangePickerComponent(elementRef, 'en-US', platform, mockInjector, null, overlay);
251+
const dateRange = new IgxDateRangePickerComponent(elementRef, 'en-US', platform, mockInjector, mockCdr, overlay);
248252
dateRange.ngOnInit();
249253

250254
spyOnProperty((dateRange as any), 'calendar').and.returnValue(mockCalendar);
@@ -260,7 +264,7 @@ describe('IgxDateRangePicker', () => {
260264
});
261265

262266
it('should disable calendar dates when min and/or max values as strings are provided', fakeAsync(() => {
263-
const dateRange = new IgxDateRangePickerComponent(elementRef, 'en', platform, mockInjector, null, null, null);
267+
const dateRange = new IgxDateRangePickerComponent(elementRef, 'en', platform, mockInjector, mockCdr, null, null);
264268
dateRange.ngOnInit();
265269

266270
spyOnProperty((dateRange as any), 'calendar').and.returnValue(mockCalendar);
@@ -277,7 +281,7 @@ describe('IgxDateRangePicker', () => {
277281
}));
278282

279283
it('should validate correctly when disabledDates are set', () => {
280-
const dateRange = new IgxDateRangePickerComponent(elementRef, 'en-US', platform, mockInjector, null, null, null);
284+
const dateRange = new IgxDateRangePickerComponent(elementRef, 'en-US', platform, mockInjector, mockCdr, null, null);
281285
dateRange.ngOnInit();
282286

283287
dateRange.registerOnChange(mockNgControl.registerOnChangeCb);
@@ -1317,6 +1321,60 @@ describe('IgxDateRangePicker', () => {
13171321
expect(dateRange.opening.emit).toHaveBeenCalledTimes(0);
13181322
expect(dateRange.opened.emit).toHaveBeenCalledTimes(0);
13191323
}));
1324+
1325+
it('should update the calendar selection on typing', fakeAsync(() => {
1326+
const range = { start: new Date(2025, 0, 16), end: new Date(2025, 0, 20) };
1327+
dateRange.value = range;
1328+
fixture.detectChanges();
1329+
dateRange.open();
1330+
fixture.detectChanges();
1331+
1332+
expect((dateRange['_calendar'].value as Date[]).length).toBe(5);
1333+
1334+
startInput.triggerEventHandler('focus', {});
1335+
fixture.detectChanges();
1336+
UIInteractions.simulateTyping('01/18/2025', startInput);
1337+
1338+
tick(DEBOUNCE_TIME);
1339+
fixture.detectChanges();
1340+
1341+
expect((dateRange['_calendar'].value as Date[]).length).toBe(3);
1342+
1343+
startDate = new Date(2025, 0, 18);
1344+
const expectedRange = { start: startDate, end: new Date(2025, 0, 20) };
1345+
expect(dateRange.value).toEqual(expectedRange);
1346+
expect(dateRange.activeDate).toEqual(expectedRange.start);
1347+
1348+
const activeDescendantDate = new Date(startDate.setHours(0, 0, 0, 0)).getTime().toString();
1349+
expect(dateRange['_calendar'].activeDate).toEqual(startDate);
1350+
expect(dateRange['_calendar'].viewDate.getMonth()).toEqual(startDate.getMonth());
1351+
expect(dateRange['_calendar'].value[0]).toEqual(startDate);
1352+
expect(dateRange['_calendar'].wrapper.nativeElement.getAttribute('aria-activedescendant')).toEqual(activeDescendantDate);
1353+
}));
1354+
1355+
it('should update the calendar view and active date on typing a date that is not in the current view', fakeAsync(() => {
1356+
const range = { start: new Date(2025, 0, 16), end: new Date(2025, 0, 20) };
1357+
dateRange.value = range;
1358+
fixture.detectChanges();
1359+
dateRange.open();
1360+
fixture.detectChanges();
1361+
1362+
expect((dateRange['_calendar'].value as Date[]).length).toBe(5);
1363+
1364+
startInput.triggerEventHandler('focus', {});
1365+
fixture.detectChanges();
1366+
UIInteractions.simulateTyping('11/18/2025', startInput);
1367+
1368+
tick(DEBOUNCE_TIME);
1369+
fixture.detectChanges();
1370+
1371+
startDate = new Date(2025, 10, 18);
1372+
1373+
const activeDescendantDate = new Date(startDate.setHours(0, 0, 0, 0)).getTime().toString();
1374+
expect(dateRange['_calendar'].activeDate).toEqual(startDate);
1375+
expect(dateRange['_calendar'].viewDate.getMonth()).toEqual(startDate.getMonth());
1376+
expect(dateRange['_calendar'].wrapper.nativeElement.getAttribute('aria-activedescendant')).toEqual(activeDescendantDate);
1377+
}));
13201378
});
13211379

13221380
it('should focus the last focused input after the calendar closes - dropdown', fakeAsync(() => {
@@ -1739,13 +1797,70 @@ describe('IgxDateRangePicker', () => {
17391797
fixture.detectChanges();
17401798

17411799
expect(dateRange['_calendar'].disabledDates).toEqual(disabledDates);
1800+
}));
17421801

1743-
// should not allow to select a date from the disabled dates
1744-
startDate = new Date(new Date().getFullYear(), new Date().getMonth(), 4);
1745-
endDate = new Date(new Date().getFullYear(), new Date().getMonth(), 6);
1802+
it('should initialize activeDate with current date, when not set', fakeAsync(() => {
1803+
fixture = TestBed.createComponent(DateRangeDefaultComponent);
1804+
fixture.detectChanges();
1805+
dateRange = fixture.componentInstance.dateRange;
1806+
const todayDate = new Date();
1807+
const today = new Date(todayDate.setHours(0, 0, 0, 0)).getTime().toString();
1808+
1809+
expect(dateRange.activeDate).toEqual(todayDate);
1810+
1811+
dateRange.open();
1812+
fixture.detectChanges();
1813+
1814+
expect(dateRange['_calendar'].activeDate).toEqual(todayDate);
1815+
expect(dateRange['_calendar'].value).toEqual([]);
1816+
const wrapper = fixture.debugElement.query(By.css('.igx-calendar__wrapper')).nativeElement;
1817+
expect(wrapper.getAttribute('aria-activedescendant')).toEqual(today);
1818+
}));
1819+
1820+
it('should initialize activeDate = first defined in value (start/end) when it is not set, but value is', fakeAsync(() => {
1821+
fixture = TestBed.createComponent(DateRangeDefaultComponent);
1822+
fixture.detectChanges();
1823+
dateRange = fixture.componentInstance.dateRange;
1824+
let range = { start: new Date(2025, 0, 1), end: new Date(2025, 0, 5) };
1825+
dateRange.value = range;
1826+
fixture.detectChanges();
1827+
1828+
expect(dateRange.activeDate).toEqual(range.start);
1829+
dateRange.open();
1830+
fixture.detectChanges();
1831+
1832+
const activeDescendantDate = new Date(range.start.setHours(0, 0, 0, 0)).getTime().toString();
1833+
expect(dateRange['_calendar'].activeDate).toEqual(range.start);
1834+
expect(dateRange['_calendar'].value[0]).toEqual(range.start);
1835+
const wrapper = fixture.debugElement.query(By.css('.igx-calendar__wrapper')).nativeElement;
1836+
expect(wrapper.getAttribute('aria-activedescendant')).toEqual(activeDescendantDate);
1837+
1838+
range = { ...range, start: null};
1839+
dateRange.value = range;
1840+
fixture.detectChanges();
1841+
1842+
expect(dateRange.activeDate).toEqual(range.end);
1843+
}));
1844+
1845+
it('should set activeDate correctly', fakeAsync(() => {
1846+
const targetDate = new Date(2025, 11, 1);
1847+
fixture = TestBed.createComponent(DateRangeDefaultComponent);
1848+
fixture.detectChanges();
1849+
dateRange = fixture.componentInstance.dateRange;
1850+
const range = { start: new Date(2025, 0, 1), end: new Date(2025, 0, 5) };
1851+
dateRange.value = range;
1852+
dateRange.activeDate = targetDate;
1853+
fixture.detectChanges();
1854+
1855+
expect(dateRange.activeDate).toEqual(targetDate);
1856+
dateRange.open();
1857+
fixture.detectChanges();
17461858

1747-
selectDateRangeFromCalendar(startDate, endDate);
1748-
expect(dateRange.value).toBeNull();
1859+
const activeDescendantDate = new Date(targetDate.setHours(0, 0, 0, 0)).getTime().toString();
1860+
expect(dateRange['_calendar'].activeDate).toEqual(targetDate);
1861+
expect(dateRange['_calendar'].value[0]).toEqual(range.start);
1862+
const wrapper = fixture.debugElement.query(By.css('.igx-calendar__wrapper')).nativeElement;
1863+
expect(wrapper.getAttribute('aria-activedescendant')).toEqual(activeDescendantDate);
17491864
}));
17501865

17511866
describe('Templated Calendar Header', () => {

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

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,21 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective
387387
|| DateTimeUtil.DEFAULT_INPUT_FORMAT;
388388
}
389389

390+
/**
391+
* Gets/Sets the date which is shown in the calendar picker and is highlighted.
392+
* By default it is the current date, or the value of the picker, if set.
393+
*/
394+
@Input()
395+
public get activeDate(): Date {
396+
const today = new Date(new Date().setHours(0, 0, 0, 0));
397+
const dateValue = DateTimeUtil.isValidDate(this._firstDefinedInRange) ? new Date(this._firstDefinedInRange.setHours(0, 0, 0, 0)) : null;
398+
return this._activeDate ?? dateValue ?? this._calendar?.activeDate ?? today;
399+
}
400+
401+
public set activeDate(value: Date) {
402+
this._activeDate = value;
403+
}
404+
390405
/**
391406
* @example
392407
* ```html
@@ -499,6 +514,14 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective
499514
return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
500515
}
501516

517+
private get _firstDefinedInRange(): Date | null {
518+
if (!this.value) {
519+
return null;
520+
}
521+
const range = this.toRangeOfDates(this.value);
522+
return range?.start ?? range?.end ?? null;
523+
}
524+
502525
private _resourceStrings = getCurrentResourceStrings(DateRangePickerResourceStringsEN);
503526
private _doneButtonText = null;
504527
private _dateSeparator = null;
@@ -513,6 +536,7 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective
513536
private _displayMonthsCount = 2;
514537
private _specialDates: DateRangeDescriptor[] = null;
515538
private _disabledDates: DateRangeDescriptor[] = null;
539+
private _activeDate: Date | null = null;
516540
private _overlaySubFilter:
517541
[MonoTypeOperatorFunction<OverlayEventArgs>, MonoTypeOperatorFunction<OverlayEventArgs | OverlayCancelableEventArgs>] = [
518542
filter(x => x.id === this._overlayId),
@@ -810,6 +834,7 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective
810834
if (this.isDropdown && selectionData?.length > 1) {
811835
this.close();
812836
}
837+
this._setCalendarActiveDate();
813838
}
814839

815840
private handleClosing(e: IBaseCancelableBrowserEventArgs): void {
@@ -970,7 +995,8 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective
970995
} else if (range.length === 0 && this.calendar.monthViews) {
971996
this.calendar.deselectDate();
972997
}
973-
this.calendar.viewDate = range[0] || new Date();
998+
this._setCalendarActiveDate();
999+
this._cdr.detectChanges();
9741000
}
9751001

9761002
private swapEditorDates(): void {
@@ -1031,6 +1057,10 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective
10311057
} else {
10321058
this.value = { start: value, end: null };
10331059
}
1060+
if (this.calendar) {
1061+
this._setCalendarActiveDate(parseDate(value));
1062+
this._cdr.detectChanges();
1063+
}
10341064
});
10351065
end.dateTimeEditor.valueChange
10361066
.pipe(takeUntil(this._destroy$))
@@ -1040,6 +1070,10 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective
10401070
} else {
10411071
this.value = { start: null, end: value as Date };
10421072
}
1073+
if (this.calendar) {
1074+
this._setCalendarActiveDate(parseDate(value));
1075+
this._cdr.detectChanges();
1076+
}
10431077
});
10441078
}
10451079
}
@@ -1157,6 +1191,7 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective
11571191
this._calendar.selected.pipe(takeUntil(this._destroy$)).subscribe((ev: Date[]) => this.handleSelection(ev));
11581192

11591193
this._setDisabledDates();
1194+
this._setCalendarActiveDate();
11601195

11611196
componentInstance.mode = this.mode;
11621197
componentInstance.closeButtonLabel = !this.isDropdown ? this.doneButtonText : null;
@@ -1196,4 +1231,11 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective
11961231
}
11971232
return false;
11981233
}
1234+
1235+
private _setCalendarActiveDate(value = null): void {
1236+
if (this._calendar) {
1237+
this._calendar.activeDate = value ?? this.activeDate;
1238+
this._calendar.viewDate = value ?? this.activeDate;
1239+
}
1240+
}
11991241
}

0 commit comments

Comments
 (0)