Skip to content

Commit

Permalink
refactor: minor update in date picker input
Browse files Browse the repository at this point in the history
  • Loading branch information
motss committed Nov 26, 2021
1 parent a11a61a commit b4609b6
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 16 deletions.
7 changes: 6 additions & 1 deletion src/__demo__/demo-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import { customElement, queryAsync } from 'lit/decorators.js';
import type { AppDatePicker } from '../date-picker/app-date-picker.js';
import type { AppDatePickerDialog } from '../date-picker-dialog/app-date-picker-dialog.js';
import { appDatePickerDialogName } from '../date-picker-dialog/constants.js';
import type { AppDatePickerInput } from '../date-picker-input/app-date-picker-input.js';
import { appDatePickerInputName } from '../date-picker-input/constants.js';
import { RootElement } from '../root-element/root-element.js';
import type { CustomEventDetail } from '../typings.js';

@customElement('demo-app')
export class DemoApp extends RootElement {
@queryAsync(appDatePickerDialogName) dialog!: Promise<AppDatePickerDialog>;
@queryAsync(appDatePickerInputName) input!: Promise<AppDatePickerInput>;

public static override styles = [
css`
Expand Down Expand Up @@ -56,9 +59,11 @@ export class DemoApp extends RootElement {
></app-date-picker>
<app-date-picker-input
.label=${'DOB'}
?outlined=${true}
.label=${'DOB'}
.placeholder=${'Select your date of birth'}
.max=${'2100-12-31'}
.min=${'1970-01-01'}
.value=${'2020-02-02'}
></app-date-picker-input>
Expand Down
24 changes: 17 additions & 7 deletions src/__tests__/date-picker-input/app-date-picker-input.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,17 @@ describe(appDatePickerInputName, () => {

let datePickerInputSurface = el.query<AppDatePickerInputSurface>(elementSelectors.datePickerInputSurface);
let datePicker = el.query<AppDatePicker>(elementSelectors.datePicker);
const activeElement = queryDeepActiveElement();
const yearDropdown = datePicker?.query<Button>(elementSelectors.yearDropdown);

/**
* NOTE: Webkit requires more time to await `datePicker` and `datePickerInputSurface` to complete
* their updates before it can find the active element in the document.
*/
await datePicker?.updateComplete;
await datePickerInputSurface?.updateComplete;

const activeElement = queryDeepActiveElement();

expect(datePickerInputSurface).exist;
expect(datePicker).exist;

Expand All @@ -173,8 +181,8 @@ describe(appDatePickerInputName, () => {
datePickerInputSurface = el.query<AppDatePickerInputSurface>(elementSelectors.datePickerInputSurface);
datePicker = el.query<AppDatePicker>(elementSelectors.datePicker);

expect(datePickerInputSurface).not.exist;
expect(datePicker).not.exist;
expect(datePickerInputSurface).exist;
expect(datePicker).exist;
});

it('closes date picker by clicking outside of input surface', async () => {
Expand Down Expand Up @@ -216,8 +224,8 @@ describe(appDatePickerInputName, () => {
datePickerInputSurface = el.query<AppDatePickerInputSurface>(elementSelectors.datePickerInputSurface);
datePicker = el.query<AppDatePicker>(elementSelectors.datePicker);

expect(datePickerInputSurface).not.exist;
expect(datePicker).not.exist;
expect(datePickerInputSurface).exist;
expect(datePicker).exist;
});

type A2 = typeof keyEnter | typeof keySpace;
Expand Down Expand Up @@ -273,8 +281,10 @@ describe(appDatePickerInputName, () => {
></app-date-picker-input>`
);

let mdcTextFieldInput = el.query<HTMLInputElement>(elementSelectors.mdcTextFieldInput);
const mdcTextFieldIconTrailing = el.query<Button>(elementSelectors.mdcTextFieldIconTrailing);
let mdcTextFieldInput =
el.query<HTMLInputElement>(elementSelectors.mdcTextFieldInput);
const mdcTextFieldIconTrailing =
el.query<Button>(elementSelectors.mdcTextFieldIconTrailing);

expect(mdcTextFieldInput).exist;
expect(mdcTextFieldIconTrailing).exist;
Expand Down
48 changes: 40 additions & 8 deletions src/date-picker-input/date-picker-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM
@queryAsync('.mdc-text-field__input') protected $input!: Promise<HTMLInputElement | null>;
@queryAsync(appDatePickerInputSurfaceName) protected $inputSurface!: Promise<AppDatePickerInputSurface | null>;
@state() private _open = false;
@state() private _rendered = false;
@state() private _valueText = '';

#disconnect: () => void = () => undefined;
#focusElement: HTMLElement | undefined = undefined;
#isClearAction = false;
#picker: AppDatePicker | undefined = undefined;
#selectedDate!: Date;
#valueFormatter = this.$toValueFormatter();

public static override styles = [
Expand Down Expand Up @@ -88,16 +91,20 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM
if (changedProperties.has('value') && this.value) {
this._valueText = this.#valueFormatter.format(toResolvedDate(this.value));
}

if (!this._rendered && this._open) {
this._rendered = true;
}
}

public override render(): TemplateResult {
return html`
${super.render()}
${
this._open ?
this._rendered ?
html`
<app-date-picker-input-surface
?open=${true}
?open=${this._open}
?stayOpenOnBodyClick=${true}
.anchor=${this as HTMLElement}
@closed=${this.#onClosed}
Expand Down Expand Up @@ -194,7 +201,9 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM
});
}

#onClearClick(): void {
#onClearClick() {
this.#isClearAction = true;

this.value = this._valueText = '';
}

Expand All @@ -209,27 +218,50 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM
valueAsDate,
} = ev.detail;

this.value = this.#valueFormatter.format(valueAsDate);
/**
* NOTE: When the input date is cleared, the date picker's `date` will be updated and `#isClearAction`
* is used to prevent `valueAsDate` from updating empty `value`.
*
* The flow of execution is as follows:
*
* 1. Clear input value
* 2. Set `#isClearAction=true`
* 3. `date` updated but `#isClearAction` is true so do nothing and reset `#isClearAction`
* 4. At this point, new value can be set via keyboard or mouse
*/
if (!this.#isClearAction) {
this.#selectedDate = valueAsDate;

if (!isKeypress || (key === keyEnter || key === keySpace)) {
this.value = this.#valueFormatter.format(this.#selectedDate);

if (isKeypress && (key === keyEnter || key === keySpace)) {
(await this.$inputSurface)?.close();
isKeypress && (await this.$inputSurface)?.close();
}
} else {
this.#isClearAction = false;
}
}

#onDatePickerFirstUpdated({
currentTarget,
detail: {
focusableElements: [focusableElement],
valueAsDate,
},
}: CustomEvent<CustomEventDetail['first-updated']['detail']>): void {
this.#picker = currentTarget as AppDatePicker;
this.#focusElement = focusableElement;
this.#picker = currentTarget as AppDatePicker;
this.#selectedDate = valueAsDate;
}

async #onOpened(): Promise<void> {
(await this.#picker)?.updateComplete;
await this.#picker?.updateComplete;
await this.updateComplete;

this.#focusElement?.focus();
}
}

// FIXME: Esc does not close input surface
// FIXME: No focus trap in input surface or close input surface when focus is outside
// FIXME: Support valueAsDate:null and valueAsNumber:NaN just like native input[type=date]

0 comments on commit b4609b6

Please sign in to comment.