Skip to content

Commit

Permalink
feat: support disabled/ readonly in DatePickerInput, update tests
Browse files Browse the repository at this point in the history
Signed-off-by: Rong Sen Ng (motss) <wes.ngrongsen@gmail.com>
  • Loading branch information
motss committed Apr 2, 2022
1 parent dd3077c commit dcc4043
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 30 deletions.
19 changes: 12 additions & 7 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -87,30 +87,34 @@
--shadow: #000000;

/** Custom app CSS Variables */
--disabled: rgba(255, 255, 255, .38);
--mwc-primary: #6200ee;
--white: #fff;
--a: rgba(255, 255, 255, .55);
--e: #6200ee;
--white55: rgba(255, 255, 255, .55);

/** Override MDC CSS Variables */
--mdc-text-field-disabled-ink-color: var(--disabled);
--mdc-text-field-disabled-line-color: var(--disabled);
--mdc-text-field-ink-color: var(--white);
--mdc-text-field-label-ink-color: var(--a);
--mdc-text-field-label-ink-color: var(--white55);
--mdc-text-field-outlined-disabled-border-color: var(--disabled);
--mdc-text-field-outlined-hover-border-color: var(--secondary);
--mdc-text-field-outlined-idle-border-color: var(--a);
--mdc-text-field-outlined-idle-border-color: var(--white55);
--mdc-theme-on-surface: var(--on-surface);
--mdc-theme-primary: var(--primary);
--mdc-theme-surface: var(--surface);

/** Override app CSS Variables */
--app-focus: var(--inverse-primary);
--app-hover : var(--primary);
--app-on-disabled: var(--a);
--app-on-disabled: var(--white55);
--app-on-focus: var(--primary);
--app-on-hover : var(--primary);
--app-on-primary: var(--on-primary);
--app-on-surface: var(--on-surface);
--app-on-today: var(--white);
--app-on-week-number: var(--a);
--app-on-weekday: var(--a);
--app-on-week-number: var(--white55);
--app-on-weekday: var(--white55);
--app-primary: var(--primary);
--app-selected-focus: var(--inverse-primary);
--app-selected-hover: var(--primary);
Expand All @@ -119,6 +123,7 @@
--app-surface: var(--surface);
--app-today: var(--white);
--date-picker-input-icon: var(--white);
--date-picker-input-disabled-icon: var(--disabled);
}
}

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@
"prepublishOnly": "npm run lint:build && npm run build",
"serve": "npm x -y @web/dev-server@latest -- wds --node-resolve -dw -p 3030 -a index.html",
"test": "wtr --config wtr.config.mjs --update-snapshots",
"test:dev": "COVERAGE=true TEST_HELPERS=true npm t && COVERAGE=true npm t",
"test:dev": "npm run test:helpers && npm run test:el",
"test:el": "COVERAGE=true npm t",
"test:helpers": "COVERAGE=true TEST_HELPERS=true npm t",
"version": "sh $(npm root)/@reallyland/tools/generate-changelogs.sh && git add *CHANGELOG.md",
"watch": "npm run clean && tsc --watch",
"wtr": "node --max-old-space-size=8192 --trace-deprecation $(npm bin)/wtr --config wtr.config.mjs"
Expand Down
26 changes: 25 additions & 1 deletion src/__demo__/demo-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,36 @@ export class DemoApp extends RootElement {
<app-date-picker-input
id="datePickerInput1"
?outlined=${true}
.label=${'DOB'}
.label=${'DOB (yearGrid)'}
.placeholder=${'Select your date of birth'}
.max=${'2100-12-31'}
.min=${'1970-01-01'}
.value=${'2020-02-02'}
.startView=${'yearGrid'}
></app-date-picker-input>
<app-date-picker-input
id="datePickerInput1"
?outlined=${true}
.label=${'Disabled DOB'}
.placeholder=${'Select your date of birth'}
.max=${'2100-12-31'}
.min=${'1970-01-01'}
.value=${'2020-02-02'}
.startView=${'yearGrid'}
.disabled=${true}
></app-date-picker-input>
<app-date-picker-input
id="datePickerInput1"
?outlined=${true}
.label=${'Readonly DOB'}
.placeholder=${'Select your date of birth'}
.max=${'2100-12-31'}
.min=${'1970-01-01'}
.value=${'2020-02-02'}
.startView=${'yearGrid'}
.readOnly=${true}
></app-date-picker-input>
<button data-id="datePickerDialog1" @click=${this.#showDialog}>Open</button>
Expand Down
84 changes: 84 additions & 0 deletions src/__tests__/date-picker-input/app-date-picker-input.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { appYearGridName } from '../../year-grid/constants';
import { eventOnce } from '../test-utils/event-once';
import { messageFormatter } from '../test-utils/message-formatter';
import { queryDeepActiveElement } from '../test-utils/query-deep-active-element';
import type { DatePickerInputProperties } from '../../date-picker-input/typings';

describe(appDatePickerInputName, () => {
const elementSelectors = {
Expand Down Expand Up @@ -573,4 +574,87 @@ describe(appDatePickerInputName, () => {
expect(yearGrid).exist;
});

type CaseRenderAndTriggerNothing = keyof Pick<DatePickerInputProperties, 'disabled' | 'readOnly'>;
const casesRenderAndTriggerNothing: CaseRenderAndTriggerNothing[] = [
'disabled',
'readOnly'
];
casesRenderAndTriggerNothing.forEach((a) => {
it(`renders correctly and does not trigger anything event handler when '${a}' is set to true`, async () => {
const el = await fixture<AppDatePickerInput>(
html`<app-date-picker-input
.label=${label}
.max=${max}
.min=${min}
.placeholder=${placeholder}
.startView=${'yearGrid'}
.value=${value}
.disabled=${a === 'disabled' ? true : false}
.readOnly=${a === 'readOnly' ? true : false}
></app-date-picker-input>`
);

await el.updateComplete;

const initialValue = el.value;
const initialValueText = el.query<HTMLInputElement>(elementSelectors.mdcTextFieldInput)?.value;

const tasks = [
async () => {
/**
* Call `.showPicker()`
*/
el.showPicker();
},
async () => {
// Trigger key event to open date picker
const input = el.query<HTMLInputElement>(elementSelectors.mdcTextFieldInput);

input?.click();
input?.focus();
await sendKeys({ press: 'Enter' });
},
async () => {
// Trigger click event to open date picker
el.focus();
el.click();
},
async () => {
// Trigger key event to close date picker
await sendKeys({ press: keyEscape });
},
() => {
// Click reset icon button to reset value
const mdcTextFieldIconTrailing =
el.query<Button>(elementSelectors.mdcTextFieldIconTrailing);

mdcTextFieldIconTrailing?.focus();
mdcTextFieldIconTrailing?.click();
},
() => {
/**
* Call `.reset()`
*/
el.reset();
},
] as const;
for (const task of tasks) {
const openedTask = eventOnce<
typeof el,
'opened',
CustomEvent<unknown>>(el, 'opened');

await task();
await openedTask;
await el.updateComplete;

const datePickerInputSurface = el.query<AppDatePickerInputSurface>(elementSelectors.datePickerInputSurface);
const updatedValueText = el.query<HTMLInputElement>(elementSelectors.mdcTextFieldInput)?.value;

expect(datePickerInputSurface).not.exist;
expect(el.value).equal(initialValue);
expect(updatedValueText).equal(initialValueText);
}
});
});
});
13 changes: 10 additions & 3 deletions src/__tests__/test-utils/event-once.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function eventOnce<
timeout?: number
): Promise<ResolvedCustomEvent | undefined> {
return new Promise<ResolvedCustomEvent | undefined>((resolve) => {
node.addEventListener(eventName as unknown as keyof HTMLElementEventMap, (
const handler: (this: HTMLElement, ev: HTMLElementEventMap[keyof HTMLElementEventMap]) => unknown = (
ev
) => {
/**
Expand All @@ -37,12 +37,19 @@ export function eventOnce<
});
});

node.removeEventListener(eventName as unknown as keyof HTMLElementEventMap, handler);

resolve(resolvedEvent as ResolvedCustomEvent);
});
}

node.addEventListener(eventName as unknown as keyof HTMLElementEventMap, handler);

// Race with event listener
globalThis.setTimeout(
() => resolve(undefined),
() => {
node.removeEventListener(eventName as unknown as keyof HTMLElementEventMap, handler);
resolve(undefined);
},
timeout ?? promiseTimeout
);
});
Expand Down
29 changes: 24 additions & 5 deletions src/date-picker-input/date-picker-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ import { keyEnter, keyEscape, keySpace, keyTab } from '../key-values.js';
import { DatePickerMinMaxMixin } from '../mixins/date-picker-min-max-mixin.js';
import { DatePickerMixin } from '../mixins/date-picker-mixin.js';
import { ElementMixin } from '../mixins/element-mixin.js';
import type { DatePickerMixinProperties } from '../mixins/typings.js';
import { baseStyling } from '../stylings.js';
import type { ChangedProperties, CustomEventDetail, DatePickerProperties } from '../typings.js';
import type { ChangedProperties, CustomEventDetail } from '../typings.js';
import { appDatePickerInputClearLabel, appDatePickerInputType } from './constants.js';
import { datePickerInputStyling } from './stylings.js';
import type { DatePickerInputProperties } from './typings.js';

export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinMaxMixin(TextField))) implements DatePickerMixinProperties {
export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinMaxMixin(TextField))) implements DatePickerInputProperties {
public override iconTrailing = 'clear';
public override type = appDatePickerInputType;

Expand All @@ -46,6 +46,7 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM
@queryAsync('.mdc-text-field__input') protected $input!: Promise<HTMLInputElement | null>;
@queryAsync(appDatePickerInputSurfaceName) protected $inputSurface!: Promise<AppDatePickerInputSurface | null>;
@queryAsync(appDatePickerName) protected $picker!: Promise<AppDatePicker | null>;
@state() private _disabled = false;
@state() private _lazyLoaded = false;
@state() private _open = false;
@state() private _valueText = '';
Expand Down Expand Up @@ -75,6 +76,8 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM
const input = await this.$input;
if (input) {
const onBodyKeyup = async (ev: KeyboardEvent) => {
if (this._disabled) return;

if (ev.key === keyEscape) {
this.closePicker();
} else if (ev.key === keyTab) {
Expand All @@ -87,8 +90,13 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM
if (!isTabInsideInputSurface) this.closePicker();
}
};
const onClick = () => this._open = true;
const onClick = () => {
if (this._disabled) return;

this._open = true;
};
const onKeyup = (ev: KeyboardEvent) => {
if (this._disabled) return;
if ([keySpace, keyEnter].some(n => n === ev.key)) {
onClick();
}
Expand All @@ -106,7 +114,7 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM
}
}

public override willUpdate(changedProperties: ChangedProperties<DatePickerProperties>): void {
public override willUpdate(changedProperties: ChangedProperties<DatePickerInputProperties>): void {
super.willUpdate(changedProperties);

if (changedProperties.has('locale')) {
Expand All @@ -123,6 +131,10 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM
if (changedProperties.has('value')) {
this.#updateValues(this.value);
}

if (changedProperties.has('disabled') || changedProperties.has('readOnly')) {
this._disabled = this.disabled || this.readOnly;
}
}

public override async updated(): Promise<void> {
Expand Down Expand Up @@ -152,11 +164,15 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM
}

public reset(): void {
if (this._disabled) return;

this.#valueAsDate = undefined;
this.value = this._valueText = '';
}

public showPicker(): void {
if (this._disabled) return;

this._open = true;
}

Expand Down Expand Up @@ -200,6 +216,7 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM
protected override renderTrailingIcon(): TemplateResult {
return html`
<app-icon-button
.disabled=${this._disabled}
@click=${this.#onResetClick}
aria-label=${this.clearLabel}
class="mdc-text-field__icon mdc-text-field__icon--trailing"
Expand Down Expand Up @@ -333,6 +350,8 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM
/* c8 ignore stop */

#onResetClick = (ev: MouseEvent): void => {
if (this._disabled) return;

/**
* NOTE(motss): To prevent triggering the `focus` event of `TextField` element.
*/
Expand Down
14 changes: 1 addition & 13 deletions src/date-picker-input/stylings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,7 @@ import { css } from 'lit';

export const datePickerInputStyling = css`
:host {
/* mwc-textfield CSS variables: */
/* --mdc-text-field-filled-border-radius: 4px 4px 0 0 */
/* --mdc-text-field-idle-line-color: rgba(0, 0, 0, 0.42) */
/* --mdc-text-field-hover-line-color: rgba(0, 0, 0, 0.87) */
/* --mdc-text-field-disabled-line-color: rgba(0, 0, 0, 0.06) */
/* --mdc-text-field-outlined-idle-border-color: rgba(0, 0, 0, 0.38) */
/* --mdc-text-field-outlined-hover-border-color: rgba(0, 0, 0, 0.87) */
/* --mdc-text-field-outlined-disabled-border-color: rgba(0, 0, 0, 0.06) */
/* --mdc-text-field-fill-color: rgb(245, 245, 245) */
/* --mdc-text-field-disabled-fill-color: rgb(250, 250, 250) */
/* --mdc-text-field-ink-color: rgba(0, 0, 0, 0.87) */
/* --mdc-text-field-label-ink-color: rgba(0, 0, 0, 0.6) */
/* --mdc-text-field-disabled-ink-color: rgba(0, 0, 0, 0.37) */
--mdc-theme-text-disabled-on-light: var(--date-picker-input-disabled-icon);
position: relative;
}
Expand Down
5 changes: 5 additions & 0 deletions src/date-picker-input/typings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { TextField } from '@material/mwc-textfield';

import type { DatePickerProperties } from '../typings';

export interface DatePickerInputProperties extends DatePickerProperties, Omit<TextField, keyof DatePickerProperties> {}

0 comments on commit dcc4043

Please sign in to comment.