/** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: Apache-2.0 */ // This is required for @ariaProperty // tslint:disable:no-new-decorators import '../../focus/focus-ring.js'; import '../../ripple/ripple.js'; import {html, LitElement, nothing, TemplateResult} from 'lit'; import {property, query, queryAssignedElements, queryAsync, state} from 'lit/decorators.js'; import {ClassInfo, classMap} from 'lit/directives/class-map.js'; import {when} from 'lit/directives/when.js'; import {dispatchActivationClick, isActivationClick} from '../../controller/events.js'; import {ariaProperty} from '../../decorators/aria-property.js'; import {pointerPress, shouldShowStrongFocus} from '../../focus/strong-focus.js'; import {ripple} from '../../ripple/directive.js'; import {MdRipple} from '../../ripple/ripple.js'; import {ARIAHasPopup} from '../../types/aria.js'; import {ButtonState} from './state.js'; // tslint:disable-next-line:enforce-comments-on-exported-symbols export abstract class Button extends LitElement implements ButtonState { static override shadowRootOptions: ShadowRootInit = {mode: 'open', delegatesFocus: true}; @property({type: String, attribute: 'data-aria-has-popup', noAccessor: true}) @ariaProperty override ariaHasPopup!: ARIAHasPopup; @property({type: String, attribute: 'data-aria-label', noAccessor: true}) @ariaProperty override ariaLabel!: string; /** * Whether or not the button is disabled. */ @property({type: Boolean, reflect: true}) disabled = false; /** * Whether to render the icon at the inline end of the label rather than the * inline start. * * _Note:_ Link buttons cannot have trailing icons. */ @property({type: Boolean, attribute: 'trailingicon'}) trailingIcon = false; // TODO(b/272598771): remove label property /** * The button's visible label. * * @deprecated Set text as content of the button instead. */ @property({type: String}) label = ''; /** * Whether to display the icon or not. */ @property({type: Boolean}) hasIcon = false; /** * Whether `preventDefault()` should be called on the underlying button. * Useful for preventing certain native functionalities like preventing form * submissions. */ @property({type: Boolean}) preventClickDefault = false; @query('.md3-button') protected buttonElement!: HTMLElement; @queryAsync('md-ripple') protected ripple!: Promise; @state() protected showFocusRing = false; @state() protected showRipple = false; @queryAssignedElements({slot: 'icon', flatten: true}) protected assignedIcons!: HTMLElement[]; constructor() { super(); this.addEventListener('click', this.handleActivationClick); } private readonly handleActivationClick = (event: MouseEvent) => { if (!isActivationClick((event))) { return; } this.focus(); dispatchActivationClick(this.buttonElement); }; override focus() { this.buttonElement.focus(); } override blur() { this.buttonElement.blur(); } protected readonly getRipple = () => { this.showRipple = true; return this.ripple; }; protected override render(): TemplateResult { // TODO(b/237283903): Replace ifDefined(... || undefined) with ifTruthy(...) return html` `; } protected getRenderClasses(): ClassInfo { return { 'md3-button--icon-leading': !this.trailingIcon && this.hasIcon, 'md3-button--icon-trailing': this.trailingIcon && this.hasIcon, }; } protected renderTouchTarget(): TemplateResult { return html` `; } protected renderElevation(): TemplateResult { return html``; } protected renderRipple = () => { return html``; }; protected renderOutline(): TemplateResult { return html``; } protected renderFocusRing(): TemplateResult { return html``; } protected renderLabel(): TemplateResult { // TODO(b/272598771): remove the ternary when label property is removed return html`${ this.label ? this.label : html``}`; } protected renderLeadingIcon(): TemplateResult|string { return this.trailingIcon ? '' : this.renderIcon(); } protected renderTrailingIcon(): TemplateResult|string { return this.trailingIcon ? this.renderIcon() : ''; } protected renderIcon(): TemplateResult { return html``; } protected handlePointerDown(e: PointerEvent) { pointerPress(); this.showFocusRing = shouldShowStrongFocus(); } protected handleClick(e: MouseEvent) { if (this.preventClickDefault) { e.preventDefault(); } } protected handleFocus() { this.showFocusRing = shouldShowStrongFocus(); } protected handleBlur() { this.showFocusRing = false; } protected handleSlotChange() { this.hasIcon = this.assignedIcons.length > 0; } }