/** @license Copyright (c) 2022 trading_peter This program is available under Apache License Version 2.0 */ import '@tp/tp-spinner/tp-spinner.js'; import '@tp/tp-icon/tp-icon.js'; import { LitElement, html, css, svg } from 'lit'; import { EventHelpers } from '@tp/helpers/event-helpers.js'; import { closest } from '@tp/helpers/closest.js'; class TpButton extends EventHelpers(LitElement) { static get styles() { return css` :host { display: inline-block; outline: none; border-radius: var(--tp-button-border-radius, 3px); background: var(--tp-button-bg, #0277bd); line-height: var(--tp-button-icon-height, 24px); color: var(--tp-button-color, #ffffff); user-select: none; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; } :host([disabled]) { cursor: auto; pointer-events: none; background: var(--tp-button-bg-disabled, #9E9E9E); color: var(--tp-button-color-disabled, rgba(255,255,255, 0.5)); } :host([locked]) { pointer-events: none; } :host(:hover) { background: var(--tp-button-bg-hover, #039BE5); color: var(--tp-button-color-hover, #ffffff); } :host(:focus), :host(:active) { background: var(--tp-button-bg-focus, #039BE5); color: var(--tp-button-color-focus, #ffffff); } :host(:hover) .wrap { box-shadow: var(--tp-button-box-shadow-hover, none); } :host(:focus) .wrap, :host(:active) .wrap { box-shadow: var(--tp-button-box-shadow-focus, none); } .wrap { position: relative; background: transparent; display: flex; align-items: center; justify-content: center; padding: var(--tp-button-padding, 6px 10px); border-radius: inherit; box-shadow: var(--tp-button-box-shadow, none); transition: background var(--tp-button-animation-duration, 300ms) ease-in-out, box-shadow var(--tp-button-animation-duration, 300ms) ease-in-out; cursor: pointer; } .wrap.success-bg { background: var(--tp-button-bg-success, green); box-shadow: var(--tp-button-box-shadow-success, 0 0 10px rgba(0, 128, 0, 0.83)) !important; } .wrap.error-bg { background: var(--tp-button-bg-error, red); box-shadow: var(--tp-button-box-shadow-error, 0 0 10px rgba(255, 0, 0, 0.83)) !important; } .label { display: flex; flex-direction: row; align-items: center; text-align: center; transition: opacity var(--tp-button-animation-duration, 300ms) ease-in-out; } .success, .error, .spinner { position: absolute; margin: auto; opacity: 0; transition: opacity var(--tp-button-animation-duration, 300ms) ease-in-out; } .spinner { --tp-spinner-width: var(--tp-button-spinner-width, 15px); --tp-spinner-height: var(--tp-button-spinner-height, 15px); --tp-spinner-color1: var(--tp-button-spinner-color1, #81D4FA); --tp-spinner-color2: var(--tp-button-spinner-color2, #039BE5); --tp-spinner-border-width: var(--tp-button-spinner-border-width; 3px); } .fade-out, .fade-in { opacity: 0; } .fade-in { opacity: 1; } tp-icon { --tp-icon-height: var(--tp-button-icon-height, 24px); --tp-icon-width: var(--tp-button-icon-width, 24px); } `; } render() { return html`
${this.extended ? html` ` : null}
`; } static get properties() { return { submit: { type: Boolean }, extended: { type: Boolean }, locked: { type: Boolean, reflect: true }, }; } static get successIcon() { return svg``; } static get errorIcon() { return svg``; } get _wrapEl() { return this.shadowRoot.querySelector('.wrap'); } get _labelEl() { return this.shadowRoot.querySelector('.label'); } get _successEl() { return this.shadowRoot.querySelector('.success'); } get _errorEl() { return this.shadowRoot.querySelector('.error'); } get _spinnerEl() { return this.shadowRoot.querySelector('.spinner'); } constructor() { super(); this.submit = false; this._queue = []; } connectedCallback() { super.connectedCallback(); this.duration = parseInt(getComputedStyle(this).getPropertyValue('--tp-button-animation-duration'), 10) || 200; this.pause = parseInt(getComputedStyle(this).getPropertyValue('--tp-button-animation-pause'), 10) || 300; if (!this.hasAttribute('tabindex')) { this.setAttribute('tabindex', '0'); } if (!this.hasAttribute('role')) { this.setAttribute('role', 'button'); } } disconnectedCallback() { super.disconnectedCallback(); this.unlisten(this, 'keypress', '_keyPressed'); } shouldUpdate(changes) { if (changes.has('submit') && this.submit) { this.extended = true; } return true; } updated(changes) { if (changes.has('submit') && this.submit) { this._form = this._findSubmitTarget(); if (this._form === undefined) { console.warn(this.tagName + ': No parent form found!'); this.submit = false; } else { this.listen(this, 'click', '_submitOnTap'); this.listen(this._form, 'submit', '_onFormSubmit'); this.listen(this._form, 'invalid', '_onFormFailed'); this.listen(this._form, 'response', '_onFormSuccess'); } } } async showSuccess() { if (!this.extended) { console.warn(this.tagName + ': Is not in extended mode!'); return; } if (this._isAnimating) { this._queue.push('showSuccess'); return; } this._wrapEl.classList.add('success-bg'); await this._switch(this._successEl); await this._wait(this.pause); this._wrapEl.classList.remove('success-bg'); await this._switch(this._labelEl); this.locked = false; } async showError() { if (!this.extended) { console.warn(this.tagName + ': Is not in extended mode!'); return; } if (this._isAnimating) { this._queue.push('showError'); return; } this._wrapEl.classList.add('error-bg'); await this._switch(this._errorEl); await this._wait(this.pause); this._wrapEl.classList.remove('error-bg'); await this._switch(this._labelEl); this.locked = false; } showSpinner() { if (!this.extended) { console.warn(this.tagName + ': Is not in extended mode!'); return; } if (this._isAnimating) { this._queue.push('showSpinner'); return; } this.locked = true; this._switch(this._spinnerEl); } hideSpinner() { if (!this.extended) { console.warn(this.tagName + ': Is not in extended mode!'); return; } if (this._isAnimating) { this._queue.push('hideSpinner'); return; } this._switch(this._labelEl); this.locked = false; } async _switch(el) { this._isAnimating = true; await this.updateComplete; const visEl = this._visEl || this._labelEl; visEl.classList.remove('fade-in'); visEl.classList.add('fade-out'); await this._wait(this.duration); el.classList.remove('fade-out'); el.classList.add('fade-in'); await this._wait(this.duration); this._visEl = el; this._isAnimating = false; if (this._queue.length > 0) { const cmd = this._queue.shift(); this[cmd](); } } _wait(time) { return new Promise(resolve => { setTimeout(() => { resolve(); }, time); }); } _keyPressed(e) { if (e.keyCode === 13) { if (this._form) { this._submitOnTap(); } else { this.click(); } } } _submitOnTap() { if (this.submit && !this.isSubmitting) { this._form.submitButton = this; this._form.submit(); } } _onFormSubmit() { this.showSpinner(); } _onFormSuccess() { this.isSubmitting(false); this.showSuccess(); } _onFormFailed(e) { // Only react if this button instance was the actual pressed button. // We have to check this in case the parent form has multiple submit buttons. if (this._form.submitButton === this) { if (e) { if (e.composedPath()[0] !== this._form) { return; } } this.isSubmitting = false; this.showError(); } } _findSubmitTarget() { let target; const root = this.getRootNode() || document; if (this.submit.length > 0) { target = root.querySelector(this.submit); } if (!target) { target = closest(this, 'tp-form', true); } return target; } } window.customElements.define('tp-button', TpButton);