Skip to content

Commit

Permalink
refactor: Aligned behaviors with Angular version
Browse files Browse the repository at this point in the history
* The date picker input is now readonly in
dialog mode.
* When the component is in dialog mode, the dialog
is shown on clicking the date picker input field as well.
* When the picker is shown, regardless of mode,
the current selected value/last active date will be
focused.
  • Loading branch information
rkaraivanov committed Jun 6, 2024
1 parent 8628df7 commit b6eff1d
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 21 deletions.
30 changes: 16 additions & 14 deletions src/components/calendar/calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -211,7 +213,7 @@ export default class IgcCalendarComponent extends SizableMixin(
}

if (this.activeView === 'days') {
this.focusActiveDate();
this[focusActiveDate]();
}
break;
case 'PageUp':
Expand All @@ -224,7 +226,7 @@ export default class IgcCalendarComponent extends SizableMixin(
}

if (this.activeView === 'days') {
this.focusActiveDate();
this[focusActiveDate]();
}
break;
case 'Home':
Expand Down Expand Up @@ -253,7 +255,7 @@ export default class IgcCalendarComponent extends SizableMixin(
this.activeDate = date;
}

this.focusActiveDate();
this[focusActiveDate]();
break;
case 'End':
event.preventDefault();
Expand Down Expand Up @@ -284,7 +286,7 @@ export default class IgcCalendarComponent extends SizableMixin(
this.activeDate = date;
}

this.focusActiveDate();
this[focusActiveDate]();
break;
case 'ArrowLeft':
event.preventDefault();
Expand Down Expand Up @@ -314,7 +316,7 @@ export default class IgcCalendarComponent extends SizableMixin(
this.previousYear();
}

this.focusActiveDate();
this[focusActiveDate]();
break;
case 'ArrowRight':
event.preventDefault();
Expand Down Expand Up @@ -346,7 +348,7 @@ export default class IgcCalendarComponent extends SizableMixin(
this.nextYear();
}

this.focusActiveDate();
this[focusActiveDate]();
break;
case 'ArrowUp':
event.preventDefault();
Expand Down Expand Up @@ -384,7 +386,7 @@ export default class IgcCalendarComponent extends SizableMixin(
);
}

this.focusActiveDate();
this[focusActiveDate]();
break;
case 'ArrowDown':
event.preventDefault();
Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -483,23 +485,23 @@ export default class IgcCalendarComponent extends SizableMixin(
this.activeDate = (event.target as IgcMonthsViewComponent).value;
this.activeView = 'days';

this.focusActiveDate();
this[focusActiveDate]();
}

private changeYear(event: CustomEvent<void>) {
event.stopPropagation();
this.activeDate = (event.target as IgcYearsViewComponent).value;
this.activeView = 'months';

this.focusActiveDate();
this[focusActiveDate]();
}

private async switchToMonths(daysViewIndex: number) {
this.activateDaysView(daysViewIndex);
this.activeView = 'months';

await this.updateComplete;
this.focusActiveDate();
this[focusActiveDate]();
}

private async switchToYears(daysViewIndex: number) {
Expand All @@ -509,7 +511,7 @@ export default class IgcCalendarComponent extends SizableMixin(
this.activeView = 'years';

await this.updateComplete;
this.focusActiveDate();
this[focusActiveDate]();
}

private activateDaysView(daysViewIndex: number) {
Expand All @@ -528,7 +530,7 @@ export default class IgcCalendarComponent extends SizableMixin(
this.activeDate = day.date;

if (!day.isCurrentMonth) {
this.focusActiveDate();
this[focusActiveDate]();
}
}

Expand Down
49 changes: 47 additions & 2 deletions src/components/date-picker/date-picker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,18 @@ describe('Date picker', () => {
let picker: IgcDatePickerComponent;
let dateTimeInput: IgcDateTimeInputComponent;
let calendar: IgcCalendarComponent;
let calendarShowIcon: HTMLElement;

beforeEach(async () => {
picker = await fixture<IgcDatePickerComponent>(
html`<igc-date-picker></igc-date-picker>`
);
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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down
40 changes: 35 additions & 5 deletions src/components/date-picker/date-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -614,7 +635,12 @@ export default class IgcDatePickerComponent extends FormAssociatedRequiredMixin(
const state = this.open ? 'calendar-icon-open' : 'calendar-icon';

return html`
<span slot="prefix" part=${state} @click=${this.handleAnchorClick}>
<span
id="anchor-icon"
slot="prefix"
part=${state}
@click=${this.handleAnchorClick}
>
<slot name=${state}>${defaultIcon}</slot>
</span>
`;
Expand Down Expand Up @@ -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`
<igc-date-time-input
id=${id}
Expand All @@ -719,7 +748,7 @@ export default class IgcDatePickerComponent extends FormAssociatedRequiredMixin(
input-format=${ifDefined(this._inputFormat)}
display-format=${ifDefined(format)}
?disabled=${this.disabled}
?readonly=${this.nonEditable || this.readOnly}
?readonly=${readOnly}
?required=${this.required}
.value=${this.value ?? null}
.locale=${this.locale}
Expand All @@ -731,6 +760,7 @@ export default class IgcDatePickerComponent extends FormAssociatedRequiredMixin(
.invalid=${live(this.invalid)}
@igcChange=${this.handleInputChangeEvent}
@igcInput=${this.handleInputEvent}
@click=${this.isDropDown ? nothing : this.handleInputClick}
exportparts="input, label, prefix, suffix"
>
${this.renderCalendarIcon()}
Expand Down

0 comments on commit b6eff1d

Please sign in to comment.