| /** |
| * @license |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| import '@polymer/paper-button/paper-button'; |
| import {spinnerStyles} from '../../../styles/gr-spinner-styles'; |
| import {votingStyles} from '../../../styles/gr-voting-styles'; |
| import {css, html, LitElement, PropertyValues} from 'lit'; |
| import {customElement, property} from 'lit/decorators'; |
| import {getEventPath, modifierPressed} from '../../../utils/dom-util'; |
| import {appContext} from '../../../services/app-context'; |
| import {ReportingService} from '../../../services/gr-reporting/gr-reporting'; |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'gr-button': GrButton; |
| } |
| } |
| |
| @customElement('gr-button') |
| export class GrButton extends LitElement { |
| private readonly reporting: ReportingService = appContext.reportingService; |
| |
| /** |
| * Should this button be rendered as a vote chip? Then we are applying |
| * the .voteChip class (see gr-voting-styles) to the paper-button. |
| */ |
| @property({type: Boolean, reflect: true}) |
| voteChip = false; |
| |
| // Note: don't assign a value to this, since constructor is called |
| // after created, the initial value maybe overridden by this |
| private initialTabindex?: string; |
| |
| @property({type: Boolean, reflect: true, attribute: 'down-arrow'}) |
| downArrow = false; |
| |
| @property({type: Boolean, reflect: true}) |
| link = false; |
| |
| @property({type: Boolean, reflect: true}) |
| loading = false; |
| |
| @property({type: Boolean, reflect: true}) |
| disabled: boolean | null = null; |
| |
| static override get styles() { |
| return [ |
| votingStyles, |
| spinnerStyles, |
| css` |
| /* general styles for all buttons */ |
| :host { |
| --background-color: var( |
| --button-background-color, |
| var(--default-button-background-color) |
| ); |
| --text-color: var( |
| --gr-button-text-color, |
| var(--default-button-text-color) |
| ); |
| display: inline-block; |
| position: relative; |
| } |
| :host([hidden]) { |
| display: none; |
| } |
| :host([no-uppercase]) paper-button { |
| text-transform: none; |
| } |
| paper-button { |
| /* paper-button sets this to anti-aliased, which appears different than |
| bold font elsewhere on macOS. */ |
| -webkit-font-smoothing: initial; |
| align-items: center; |
| background-color: var(--background-color); |
| color: var(--text-color); |
| display: flex; |
| font-family: var(--font-family, inherit); |
| /** Without this '.keyboard-focus' buttons will get bolded. */ |
| font-weight: var(--font-weight-normal, inherit); |
| justify-content: center; |
| margin: var(--margin, 0); |
| min-width: var(--border, 0); |
| padding: var(--gr-button-padding, var(--spacing-s) var(--spacing-m)); |
| } |
| paper-button[elevation='1'] { |
| box-shadow: var(--elevation-level-1); |
| } |
| paper-button[elevation='2'] { |
| box-shadow: var(--elevation-level-2); |
| } |
| paper-button[elevation='3'] { |
| box-shadow: var(--elevation-level-3); |
| } |
| paper-button[elevation='4'] { |
| box-shadow: var(--elevation-level-4); |
| } |
| paper-button[elevation='5'] { |
| box-shadow: var(--elevation-level-5); |
| } |
| paper-button:hover { |
| background: linear-gradient(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.12)), |
| var(--background-color); |
| } |
| |
| /* Some mobile browsers treat focused element as hovered element. |
| As a result, element remains hovered after click (has grey background in default theme). |
| Use @media (hover:none) to remove background if |
| user's primary input mechanism can't hover over elements. |
| See: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/hover |
| |
| Note 1: not all browsers support this media query |
| (see https://caniuse.com/#feat=css-media-interaction). |
| If browser doesn't support it, then the whole content of @media .. is ignored. |
| This is why the default behavior is placed outside of @media. |
| */ |
| @media (hover: none) { |
| paper-button:hover { |
| background: transparent; |
| } |
| } |
| |
| :host([primary]) { |
| --background-color: var(--primary-button-background-color); |
| --text-color: var(--primary-button-text-color); |
| } |
| :host([link][primary]) { |
| --text-color: var(--primary-button-background-color); |
| } |
| |
| /* Keep below color definition for primary so that this takes precedence |
| when disabled. */ |
| :host([disabled]), |
| :host([loading]) { |
| --background-color: var(--disabled-button-background-color); |
| --text-color: var(--deemphasized-text-color); |
| cursor: default; |
| } |
| |
| /* Styles for link buttons specifically */ |
| :host([link]) { |
| --background-color: transparent; |
| --margin: 0; |
| } |
| :host([link]) paper-button { |
| padding: var(--gr-button-padding, var(--spacing-s)); |
| } |
| :host([disabled][link]), |
| :host([loading][link]) { |
| --background-color: transparent; |
| --text-color: var(--deemphasized-text-color); |
| cursor: default; |
| } |
| |
| /* Styles for the optional down arrow */ |
| :host(:not([down-arrow])) .downArrow { |
| display: none; |
| } |
| :host([down-arrow]) .downArrow { |
| border-top: 0.36em solid #ccc; |
| border-left: 0.36em solid transparent; |
| border-right: 0.36em solid transparent; |
| margin-bottom: var(--spacing-xxs); |
| margin-left: var(--spacing-m); |
| transition: border-top-color 200ms; |
| } |
| :host([down-arrow]) paper-button:hover .downArrow { |
| border-top-color: var(--deemphasized-text-color); |
| } |
| `, |
| ]; |
| } |
| |
| override render() { |
| return html`<paper-button |
| ?raised="${!this.link}" |
| ?disabled="${this.disabled || this.loading}" |
| role="button" |
| tabindex="-1" |
| part="paper-button" |
| class="${this.voteChip ? 'voteChip' : ''}" |
| > |
| ${this.loading ? html`<span class="loadingSpin"></span>` : ''} |
| <slot></slot> |
| <i class="downArrow"></i> |
| </paper-button>`; |
| } |
| |
| constructor() { |
| super(); |
| this.initialTabindex = this.getAttribute('tabindex') || '0'; |
| this.addEventListener('click', e => this._handleAction(e)); |
| this.addEventListener('keydown', e => this._handleKeydown(e)); |
| } |
| |
| override updated(changedProperties: PropertyValues) { |
| if (changedProperties.has('disabled')) { |
| this.setAttribute( |
| 'tabindex', |
| this.disabled ? '-1' : this.initialTabindex || '0' |
| ); |
| } |
| if (changedProperties.has('loading') || changedProperties.has('disabled')) { |
| this.setAttribute( |
| 'aria-disabled', |
| this.disabled || this.loading ? 'true' : 'false' |
| ); |
| } |
| } |
| |
| override connectedCallback() { |
| super.connectedCallback(); |
| if (!this.getAttribute('role')) { |
| this.setAttribute('role', 'button'); |
| } |
| if (!this.getAttribute('tabindex')) { |
| this.setAttribute('tabindex', '0'); |
| } |
| } |
| |
| _handleAction(e: MouseEvent) { |
| if (this.disabled || this.loading) { |
| e.preventDefault(); |
| e.stopPropagation(); |
| e.stopImmediatePropagation(); |
| return; |
| } |
| |
| this.reporting.reportInteraction('button-click', {path: getEventPath(e)}); |
| } |
| |
| _handleKeydown(e: KeyboardEvent) { |
| if (modifierPressed(e)) return; |
| // Handle `enter`, `space`. |
| if (e.keyCode === 13 || e.keyCode === 32) { |
| e.preventDefault(); |
| e.stopPropagation(); |
| this.click(); |
| } |
| } |
| } |