diff --git a/README.md b/README.md index 1ab27b7..85574f8 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# tp-element +# tp-button diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..cdd649e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,102 @@ +{ + "name": "tp-button", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@lit/reactive-element": { + "version": "1.3.0", + "resolved": "https://verdaccio.codeblob.work/@lit%2freactive-element/-/reactive-element-1.3.0.tgz", + "integrity": "sha512-0TKSIuJHXNLM0k98fi0AdMIdUoHIYlDHTP+0Vruc2SOs4T6vU1FinXgSvYd8mSrkt+8R+qdRAXvjpqrMXMyBgw==" + }, + "@tp/helpers": { + "version": "1.0.0", + "resolved": "https://verdaccio.codeblob.work/@tp%2fhelpers/-/helpers-1.0.0.tgz", + "integrity": "sha512-0RcwkVBsZoa2jaOGwf0QNBHIC1vA/8G1rsvWC1j20tyyzZBOqGGOwvgnLN1TEP3C8zT4+oUMlQbu6DmkpW9T3A==" + }, + "@tp/tp-icon": { + "version": "1.0.0", + "resolved": "https://verdaccio.codeblob.work/@tp%2ftp-icon/-/tp-icon-1.0.0.tgz", + "integrity": "sha512-/ETh6OPsDmU38niE68ngpZEMc/yaGhbvpuvZW67a1noQHiHOjW+kznhiqNYgV/x4AIxuslgvYquiGfWq+00a4Q==", + "requires": { + "@tp/tp-tooltip": "^1.0.0", + "lit": "^2.2.0" + }, + "dependencies": { + "lit": { + "version": "2.2.0", + "resolved": "https://verdaccio.codeblob.work/lit/-/lit-2.2.0.tgz", + "integrity": "sha512-FDyxUuczo6cJJY/2Bkgfh1872U4ikUvmK1Cb6+lYC1CW+QOo8CaWXCpvPKFzYsz0ojUxoruBLVrECc7VI2f1dQ==", + "requires": { + "@lit/reactive-element": "^1.3.0", + "lit-element": "^3.2.0", + "lit-html": "^2.2.0" + } + } + } + }, + "@tp/tp-spinner": { + "version": "1.0.0", + "resolved": "https://verdaccio.codeblob.work/@tp%2ftp-spinner/-/tp-spinner-1.0.0.tgz", + "integrity": "sha512-IFlyrBwA6YuJbWO3YpSSbzniwrIJWwpRqAXMfxB+3X/dHuuOfuONLnU1dOlqKMDOw+iFuSr54NuAw6VZc6aTpA==", + "requires": { + "lit": "^2.2.0" + }, + "dependencies": { + "lit": { + "version": "2.2.0", + "resolved": "https://verdaccio.codeblob.work/lit/-/lit-2.2.0.tgz", + "integrity": "sha512-FDyxUuczo6cJJY/2Bkgfh1872U4ikUvmK1Cb6+lYC1CW+QOo8CaWXCpvPKFzYsz0ojUxoruBLVrECc7VI2f1dQ==", + "requires": { + "@lit/reactive-element": "^1.3.0", + "lit-element": "^3.2.0", + "lit-html": "^2.2.0" + } + } + } + }, + "@tp/tp-tooltip": { + "version": "1.0.0", + "resolved": "https://verdaccio.codeblob.work/@tp%2ftp-tooltip/-/tp-tooltip-1.0.0.tgz", + "integrity": "sha512-UtrIK5KWcEiC+HnHOVbgg90j4RjHn3e9ehOBYPZsm6zO+tT7pQJJYFOtJqBW+DDV7jVfH3AvGKCxtzNiJXYvDw==", + "requires": { + "@tp/helpers": "^1.0.0", + "lit": "^2.2.0" + }, + "dependencies": { + "lit": { + "version": "2.2.0", + "resolved": "https://verdaccio.codeblob.work/lit/-/lit-2.2.0.tgz", + "integrity": "sha512-FDyxUuczo6cJJY/2Bkgfh1872U4ikUvmK1Cb6+lYC1CW+QOo8CaWXCpvPKFzYsz0ojUxoruBLVrECc7VI2f1dQ==", + "requires": { + "@lit/reactive-element": "^1.3.0", + "lit-element": "^3.2.0", + "lit-html": "^2.2.0" + } + } + } + }, + "@types/trusted-types": { + "version": "2.0.2", + "resolved": "https://verdaccio.codeblob.work/@types%2ftrusted-types/-/trusted-types-2.0.2.tgz", + "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" + }, + "lit-element": { + "version": "3.2.0", + "resolved": "https://verdaccio.codeblob.work/lit-element/-/lit-element-3.2.0.tgz", + "integrity": "sha512-HbE7yt2SnUtg5DCrWt028oaU4D5F4k/1cntAFHTkzY8ZIa8N0Wmu92PxSxucsQSOXlODFrICkQ5x/tEshKi13g==", + "requires": { + "@lit/reactive-element": "^1.3.0", + "lit-html": "^2.2.0" + } + }, + "lit-html": { + "version": "2.2.0", + "resolved": "https://verdaccio.codeblob.work/lit-html/-/lit-html-2.2.0.tgz", + "integrity": "sha512-dJnevgV8VkCuOXLWrjQopDE8nSy8CzipZ/ATfYQv7z7Dct4abblcKecf50gkIScuwCTzKvRLgvTgV0zzagW4gA==", + "requires": { + "@types/trusted-types": "^2.0.2" + } + } + } +} diff --git a/package.json b/package.json index 4ecb195..47dd664 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,21 @@ { - "name": "tp-element", - "version": "0.0.1", + "name": "@tp/tp-button", + "version": "1.0.0", "description": "", - "main": "tp-element.js", + "main": "tp-button.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", - "url": "https://gitea.codeblob.work/tp-elements/tp-element.git" + "url": "https://gitea.codeblob.work/tp-elements/tp-button.git" }, "author": "trading_peter", "license": "Apache-2.0", "dependencies": { + "@tp/helpers": "^1.0.0", + "@tp/tp-icon": "^1.0.0", + "@tp/tp-spinner": "^1.0.0", "lit": "^2.2.0" } } diff --git a/tp-button.js b/tp-button.js new file mode 100644 index 0000000..a680054 --- /dev/null +++ b/tp-button.js @@ -0,0 +1,352 @@ +/** +@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); + } + + :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'); + } + } + } + + 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(); + } + + _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); diff --git a/tp-element.js b/tp-element.js deleted file mode 100644 index 6a92a2f..0000000 --- a/tp-element.js +++ /dev/null @@ -1,35 +0,0 @@ -/** -@license -Copyright (c) 2022 trading_peter -This program is available under Apache License Version 2.0 -*/ - -import { LitElement, html, css } from 'lit'; - -class TpElement extends LitElement { - static get styles() { - return [ - css` - :host { - display: block; - } - ` - ]; - } - - render() { - const { } = this; - - return html` - - `; - } - - static get properties() { - return { }; - } - - -} - -window.customElements.define('tp-element', TpElement);