lion/packages/button/src/LionButton.js

196 lines
4.7 KiB
JavaScript

import { css, html, DelegateMixin, SlotMixin } from '@lion/core';
import { LionLitElement } from '@lion/core/src/LionLitElement.js';
export class LionButton extends DelegateMixin(SlotMixin(LionLitElement)) {
static get properties() {
return {
disabled: {
type: Boolean,
reflect: true,
},
role: {
type: String,
reflect: true,
},
tabindex: {
type: Number,
reflect: true,
},
};
}
render() {
return html`
<div class="btn">
<slot></slot>
<slot name="_button"></slot>
<div class="click-area" @click="${this.__clickDelegationHandler}"></div>
</div>
`;
}
static get styles() {
return [
css`
:host {
display: inline-block;
padding-top: 2px;
padding-bottom: 2px;
height: 40px; /* src = https://www.smashingmagazine.com/2012/02/finger-friendly-design-ideal-mobile-touchscreen-target-sizes/ */
outline: 0;
background-color: transparent;
box-sizing: border-box;
}
.btn {
height: 24px;
display: flex;
align-items: center;
position: relative;
border: 1px solid black;
border-radius: 8px;
background: whitesmoke;
color: black;
padding: 7px 15px;
}
:host .btn ::slotted(button) {
position: absolute;
visibility: hidden;
}
.click-area {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: -3px -1px;
padding: 0;
}
:host(:focus) {
outline: none;
}
:host(:focus) .btn {
border-color: lightblue;
box-shadow: 0 0 8px lightblue, 0 0 0 1px lightblue;
}
:host(:hover) .btn {
background: black;
color: whitesmoke;
}
:host(:hover) .btn ::slotted(lion-icon) {
fill: whitesmoke;
}
:host(:active) .btn,
.btn[active] {
background: grey;
}
:host([disabled]) {
pointer-events: none;
}
:host([disabled]) .btn {
background: lightgray;
color: gray;
fill: gray;
border-color: gray;
}
`,
];
}
_requestUpdate(name, oldValue) {
super._requestUpdate(name, oldValue);
if (name === 'disabled') {
this.__onDisabledChanged(oldValue);
}
}
get delegations() {
return {
...super.delegations,
target: () => this.$$slot('_button'),
attributes: ['type'],
};
}
get slots() {
return {
...super.slots,
_button: () => {
if (!this.constructor._button) {
this.constructor._button = document.createElement('button');
this.constructor._button.setAttribute('slot', '_button');
this.constructor._button.setAttribute('tabindex', '-1');
}
return this.constructor._button.cloneNode();
},
};
}
constructor() {
super();
this.disabled = false;
this.role = 'button';
this.tabindex = 0;
this.__keydownDelegationHandler = this.__keydownDelegationHandler.bind(this);
}
connectedCallback() {
super.connectedCallback();
this.__setupDelegation();
}
disconnectedCallback() {
super.disconnectedCallback();
this.__teardownDelegation();
}
__clickDelegationHandler(e) {
e.stopPropagation(); // prevent click on the fake element and cause click on the native button
this.$$slot('_button').click();
}
__setupDelegation() {
this.addEventListener('keydown', this.__keydownDelegationHandler);
this.addEventListener('keyup', this.__keyupDelegationHandler);
}
__teardownDelegation() {
this.removeEventListener('keydown', this.__keydownDelegationHandler);
this.removeEventListener('keyup', this.__keyupDelegationHandler);
}
__keydownDelegationHandler(e) {
if (e.keyCode === 32 /* space */ || e.keyCode === 13 /* enter */) {
e.preventDefault();
this.shadowRoot.querySelector('.btn').setAttribute('active', '');
}
}
__keyupDelegationHandler(e) {
// Makes the real button the trigger in forms (will submit form, as opposed to paper-button)
// and make click handlers on button work on space and enter
if (e.keyCode === 32 /* space */ || e.keyCode === 13 /* enter */) {
e.preventDefault();
this.shadowRoot.querySelector('.btn').removeAttribute('active');
this.$$slot('_button').click();
}
}
__onDisabledChanged() {
if (this.disabled) {
this.__originalTabIndex = this.tabindex;
this.tabindex = -1;
} else {
this.tabindex = this.__originalTabIndex;
}
}
}