Skip to content

Commit

Permalink
feat: implement IconButton to overcome a rendering bug in Ripple
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 Feb 20, 2022
1 parent 51eb60f commit 6f18ca2
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 18 deletions.
4 changes: 2 additions & 2 deletions src/__tests__/date-picker/app-date-picker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ describe(appDatePickerName, () => {
calendarDayWithLabel: (label: string) => `.calendar-day[aria-label="${label}"]`,
disabledCalendarDay: '.calendar-day[aria-disabled="true"]',
header: '.header',
nextMonthNavigationButton: 'mwc-icon-button[data-navigation="next"]',
previousMonthNavigationButton: 'mwc-icon-button[data-navigation="previous"]',
nextMonthNavigationButton: 'app-icon-button[data-navigation="next"]',
previousMonthNavigationButton: 'app-icon-button[data-navigation="previous"]',
selectedCalendarDay: '.calendar-day[aria-selected="true"]',
selectedYearMonth: '.selected-year-month',
yearDropdown: '.year-dropdown',
Expand Down
23 changes: 14 additions & 9 deletions src/date-picker-input/date-picker-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import { until } from 'lit/directives/until.js';

import { DateTimeFormat } from '../constants.js';
import type { AppDatePicker } from '../date-picker/app-date-picker.js';
import { appDatePickerName } from '../date-picker/constants.js';
import type { AppDatePickerInputSurface } from '../date-picker-input-surface/app-date-picker-input-surface.js';
import { appDatePickerInputSurfaceName } from '../date-picker-input-surface/constants.js';
import { slotDatePicker } from '../helpers/slot-date-picker.js';
import { toDateString } from '../helpers/to-date-string.js';
import { warnUndefinedElement } from '../helpers/warn-undefined-element.js';
import type { AppIconButton } from '../icon-button/app-icon-button.js';
import { appIconButtonName } from '../icon-button/constants.js';
import { iconClear } from '../icons.js';
import { keyEnter, keyEscape, keySpace, keyTab } from '../key-values.js';
import { DatePickerMinMaxMixin } from '../mixins/date-picker-min-max-mixin.js';
Expand All @@ -39,6 +42,7 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM
@property({ type: String }) public clearLabel = appDatePickerInputClearLabel;
@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 _open = false;
@state() private _rendered = false;
@state() private _valueText = '';
Expand Down Expand Up @@ -123,6 +127,14 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM
}
}

public override async updated() {
if (this._open && this._rendered) {
const picker = await this.$picker;

picker?.queryAll<AppIconButton>(appIconButtonName).forEach(n => n.layout());
}
}

public override render(): TemplateResult {
return html`
${super.render()}
Expand Down Expand Up @@ -181,26 +193,19 @@ export class DatePickerInput extends ElementMixin(DatePickerMixin(DatePickerMinM

protected override renderTrailingIcon(): TemplateResult {
return html`
<mwc-icon-button
<app-icon-button
@click=${this.#onResetClick}
aria-label=${this.clearLabel}
class="mdc-text-field__icon mdc-text-field__icon--trailing"
>
${iconClear}
</mwc-icon-button>
</app-icon-button>
`;
}

protected async $renderContent(): Promise<TemplateResult> {
warnUndefinedElement(appDatePickerInputSurfaceName);

/**
* NOTE(motss): `.updateComplete` is required here to resolve a rendering bug where ripple
* inside a `mwc-icon-button` where the ripple appears to be smaller than expected and
* is placed at the top-left of its parent.
*/
await this.updateComplete;

return html`
<app-date-picker-input-surface
@opened=${this.#onOpened}
Expand Down
15 changes: 8 additions & 7 deletions src/date-picker/date-picker.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import '@material/mwc-icon-button';
import '../month-calendar/app-month-calendar.js';
import '../year-grid/app-year-grid.js';
import '../icon-button/app-icon-button.js';

import type { IconButton } from '@material/mwc-icon-button';
import type { TemplateResult } from 'lit';
import { html, nothing } from 'lit';
import { queryAsync, state } from 'lit/decorators.js';
Expand All @@ -22,6 +21,8 @@ import { toDateString } from '../helpers/to-date-string.js';
import { toFormatters } from '../helpers/to-formatters.js';
import { toResolvedDate } from '../helpers/to-resolved-date.js';
import type { MaybeDate } from '../helpers/typings.js';
import { appIconButtonName } from '../icon-button/constants.js';
import type { IconButton } from '../icon-button/icon-button.js';
import { iconArrowDropdown, iconChevronLeft, iconChevronRight } from '../icons.js';
import { DatePickerMinMaxMixin } from '../mixins/date-picker-min-max-mixin.js';
import { DatePickerMixin } from '../mixins/date-picker-mixin.js';
Expand Down Expand Up @@ -266,12 +267,12 @@ export class DatePicker extends DatePickerMixin(DatePickerMinMaxMixin(RootElemen
<div class=month-and-year-selector>
<p class=selected-year-month>${selectedYearMonth}</p>
<mwc-icon-button
<app-icon-button
.ariaLabel=${label}
@click=${this.#updateStartView}
class=year-dropdown
title=${ifDefined(label)}
>${iconArrowDropdown}</mwc-icon-button>
>${iconArrowDropdown}</app-icon-button>
</div>
${
Expand Down Expand Up @@ -318,7 +319,7 @@ export class DatePicker extends DatePickerMixin(DatePickerMinMaxMixin(RootElemen
#queryAllFocusable = async () : Promise<HTMLElement[]> => {
const isStartViewCalendar = this.startView === 'calendar';
const focusable = [
...this.queryAll('mwc-icon-button'),
...this.queryAll(appIconButtonName),
(await (isStartViewCalendar ? this._monthCalendar : this._yearGrid))
?.query(`.${
isStartViewCalendar ? 'calendar-day' : 'year-grid-button'
Expand Down Expand Up @@ -418,12 +419,12 @@ export class DatePicker extends DatePickerMixin(DatePickerMinMaxMixin(RootElemen
return shouldSkipRender ?
html`<div data-navigation=${navigationType}></div>` :
html`
<mwc-icon-button
<app-icon-button
.ariaLabel=${label}
@click=${this.#navigateMonth}
data-navigation=${navigationType}
title=${ifDefined(label)}
>${isPreviousNavigationType ? iconChevronLeft : iconChevronRight}</mwc-icon-button>
>${isPreviousNavigationType ? iconChevronLeft : iconChevronRight}</app-icon-button>
`;
}

Expand Down
13 changes: 13 additions & 0 deletions src/icon-button/app-icon-button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { customElement } from 'lit/decorators.js';

import { appIconButtonName } from './constants.js';
import { IconButton } from './icon-button.js';

@customElement(appIconButtonName)
export class AppIconButton extends IconButton {}

declare global {
interface HTMLElementTagNameMap {
[appIconButtonName]: AppIconButton;
}
}
1 change: 1 addition & 0 deletions src/icon-button/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const appIconButtonName = 'app-icon-button' as const;
17 changes: 17 additions & 0 deletions src/icon-button/icon-button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IconButton as BaseIconButton } from '@material/mwc-icon-button';
import type { RippleBase } from '@material/mwc-ripple/mwc-ripple-base.js';

export class IconButton extends BaseIconButton {
public async layout() {
const ripple = await this.ripple;

/**
* NOTE(motss): Workaround to force the ripple to update its layout.
* See similar issue at https://github.com/material-components/material-web/issues/1054.
*/
(ripple?.['mdcFoundation'] as RippleBase['mdcFoundation'])?.layout();

await ripple?.updateComplete;
await this.updateComplete;
}
}

0 comments on commit 6f18ca2

Please sign in to comment.