diff --git a/src/components/calendar/calendar.ts b/src/components/calendar/calendar.ts index 263cdbe3e..a292a4b08 100644 --- a/src/components/calendar/calendar.ts +++ b/src/components/calendar/calendar.ts @@ -32,6 +32,8 @@ import { styles } from './themes/calendar.base.css.js'; import { all } from './themes/calendar.js'; import IgcYearsViewComponent from './years-view/years-view.js'; +export const focusActiveDate = Symbol(); + /** * Represents a calendar that lets users * to select a date value in a variety of different ways. @@ -211,7 +213,7 @@ export default class IgcCalendarComponent extends SizableMixin( } if (this.activeView === 'days') { - this.focusActiveDate(); + this[focusActiveDate](); } break; case 'PageUp': @@ -224,7 +226,7 @@ export default class IgcCalendarComponent extends SizableMixin( } if (this.activeView === 'days') { - this.focusActiveDate(); + this[focusActiveDate](); } break; case 'Home': @@ -253,7 +255,7 @@ export default class IgcCalendarComponent extends SizableMixin( this.activeDate = date; } - this.focusActiveDate(); + this[focusActiveDate](); break; case 'End': event.preventDefault(); @@ -284,7 +286,7 @@ export default class IgcCalendarComponent extends SizableMixin( this.activeDate = date; } - this.focusActiveDate(); + this[focusActiveDate](); break; case 'ArrowLeft': event.preventDefault(); @@ -314,7 +316,7 @@ export default class IgcCalendarComponent extends SizableMixin( this.previousYear(); } - this.focusActiveDate(); + this[focusActiveDate](); break; case 'ArrowRight': event.preventDefault(); @@ -346,7 +348,7 @@ export default class IgcCalendarComponent extends SizableMixin( this.nextYear(); } - this.focusActiveDate(); + this[focusActiveDate](); break; case 'ArrowUp': event.preventDefault(); @@ -384,7 +386,7 @@ export default class IgcCalendarComponent extends SizableMixin( ); } - this.focusActiveDate(); + this[focusActiveDate](); break; case 'ArrowDown': event.preventDefault(); @@ -424,12 +426,12 @@ export default class IgcCalendarComponent extends SizableMixin( ); } - this.focusActiveDate(); + this[focusActiveDate](); break; } }; - private async focusActiveDate() { + public async [focusActiveDate]() { await this.updateComplete; if (this.activeView === 'days') { @@ -483,7 +485,7 @@ export default class IgcCalendarComponent extends SizableMixin( this.activeDate = (event.target as IgcMonthsViewComponent).value; this.activeView = 'days'; - this.focusActiveDate(); + this[focusActiveDate](); } private changeYear(event: CustomEvent) { @@ -491,7 +493,7 @@ export default class IgcCalendarComponent extends SizableMixin( this.activeDate = (event.target as IgcYearsViewComponent).value; this.activeView = 'months'; - this.focusActiveDate(); + this[focusActiveDate](); } private async switchToMonths(daysViewIndex: number) { @@ -499,7 +501,7 @@ export default class IgcCalendarComponent extends SizableMixin( this.activeView = 'months'; await this.updateComplete; - this.focusActiveDate(); + this[focusActiveDate](); } private async switchToYears(daysViewIndex: number) { @@ -509,7 +511,7 @@ export default class IgcCalendarComponent extends SizableMixin( this.activeView = 'years'; await this.updateComplete; - this.focusActiveDate(); + this[focusActiveDate](); } private activateDaysView(daysViewIndex: number) { @@ -528,7 +530,7 @@ export default class IgcCalendarComponent extends SizableMixin( this.activeDate = day.date; if (!day.isCurrentMonth) { - this.focusActiveDate(); + this[focusActiveDate](); } } diff --git a/src/components/date-picker/date-picker.spec.ts b/src/components/date-picker/date-picker.spec.ts index 473619666..89a787491 100644 --- a/src/components/date-picker/date-picker.spec.ts +++ b/src/components/date-picker/date-picker.spec.ts @@ -28,16 +28,18 @@ describe('Date picker', () => { let picker: IgcDatePickerComponent; let dateTimeInput: IgcDateTimeInputComponent; let calendar: IgcCalendarComponent; + let calendarShowIcon: HTMLElement; beforeEach(async () => { picker = await fixture( html`` ); - dateTimeInput = picker.shadowRoot!.querySelector( + dateTimeInput = picker.renderRoot.querySelector( IgcDateTimeInputComponent.tagName )!; - calendar = picker.shadowRoot!.querySelector(IgcCalendarComponent.tagName)!; + calendar = picker.renderRoot.querySelector(IgcCalendarComponent.tagName)!; + calendarShowIcon = picker.renderRoot.querySelector('#anchor-icon')!; }); describe('Rendering and initialization', () => { @@ -556,6 +558,15 @@ describe('Date picker', () => { expect(picker.displayFormat).to.equal(picker.inputFormat); }); }); + + it('should set the underlying igc-input into readonly mode when dialog mode is enabled', async () => { + expect(dateTimeInput.readOnly).to.be.false; + + picker.mode = 'dialog'; + await elementUpdated(picker); + + expect(dateTimeInput.readOnly).to.be.true; + }); }); describe('Methods', () => { @@ -783,6 +794,40 @@ describe('Date picker', () => { expect(eventSpy).not.called; expect(picker.value).to.be.null; }); + + it('should open the picker on calendar show icon click in dropdown mode', async () => { + simulateClick(calendarShowIcon); + await elementUpdated(picker); + + expect(picker.open).to.be.true; + }); + + it('should not open the picker when clicking the input in dropdown mode', async () => { + simulateClick(dateTimeInput); + await elementUpdated(picker); + + expect(picker.open).to.be.false; + }); + + it('should open the picker on calendar show icon click in dialog mode', async () => { + picker.mode = 'dialog'; + await elementUpdated(picker); + + simulateClick(calendarShowIcon); + await elementUpdated(picker); + + expect(picker.open).to.be.true; + }); + + it('should open the picker when clicking the input in dialog mode', async () => { + picker.mode = 'dialog'; + await elementUpdated(picker); + + simulateClick(dateTimeInput); + await elementUpdated(picker); + + expect(picker.open).to.be.true; + }); }); describe('Form integration', () => { diff --git a/src/components/date-picker/date-picker.ts b/src/components/date-picker/date-picker.ts index afd52b0df..6920f8ab7 100644 --- a/src/components/date-picker/date-picker.ts +++ b/src/components/date-picker/date-picker.ts @@ -5,7 +5,7 @@ import { live } from 'lit/directives/live.js'; import { themeSymbol, themes } from '../../theming/theming-decorator.js'; import type { Theme } from '../../theming/types.js'; -import IgcCalendarComponent from '../calendar/calendar.js'; +import IgcCalendarComponent, { focusActiveDate } from '../calendar/calendar.js'; import { type DateRangeDescriptor, DateRangeType, @@ -31,7 +31,11 @@ import { IgcBaseComboBoxLikeComponent } from '../common/mixins/combo-box.js'; import type { AbstractConstructor } from '../common/mixins/constructor.js'; import { EventEmitterMixin } from '../common/mixins/event-emitter.js'; import { FormAssociatedRequiredMixin } from '../common/mixins/form-associated-required.js'; -import { createCounter, formatString } from '../common/util.js'; +import { + createCounter, + findElementFromEventPath, + formatString, +} from '../common/util.js'; import { type Validator, maxDateValidator, @@ -454,7 +458,7 @@ export default class IgcDatePickerComponent extends FormAssociatedRequiredMixin( skip: () => this.disabled, bindingDefaults: { preventDefault: true }, }) - .set([altKey, arrowDown], this._show.bind(this, true)) + .set([altKey, arrowDown], this.handleAnchorClick) .set([altKey, arrowUp], this.onEscapeKey) .set(escapeKey, this.onEscapeKey); } @@ -517,6 +521,23 @@ export default class IgcDatePickerComponent extends FormAssociatedRequiredMixin( } } + protected handleInputClick(event: Event) { + if (findElementFromEventPath('#anchor-icon', event)) { + // Click events originating from the calendar show icon are ignored + return; + } + + this.handleAnchorClick(); + } + + protected override async handleAnchorClick() { + this._calendar.activeDate = this.value ?? this._calendar.activeDate; + + super.handleAnchorClick(); + await this.updateComplete; + this._calendar[focusActiveDate](); + } + private async _shouldCloseCalendarDropdown() { if (!this.keepOpenOnSelect && (await this._hide(true))) { this._input.focus(); @@ -614,7 +635,12 @@ export default class IgcDatePickerComponent extends FormAssociatedRequiredMixin( const state = this.open ? 'calendar-icon-open' : 'calendar-icon'; return html` - + ${defaultIcon} `; @@ -711,6 +737,9 @@ export default class IgcDatePickerComponent extends FormAssociatedRequiredMixin( ? `${this._displayFormat}Date` : this._displayFormat; + // Dialog mode is always readonly, rest depends on configuration + const readOnly = !this.isDropDown || this.readOnly || this.nonEditable; + return html` ${this.renderCalendarIcon()}