@lion/ui
Version:
A package of extendable web components
181 lines (160 loc) • 4.27 kB
JavaScript
import { html, css, LitElement } from 'lit';
import { DisabledWithTabIndexMixin } from '@lion/ui/core.js';
/**
* `LionSwitchButton` is a custom switch button component. It's a private component used within LionSwitch
*
* @customElement lion-switch-button
*/
export class LionSwitchButton extends DisabledWithTabIndexMixin(LitElement) {
static get properties() {
return {
checked: {
type: Boolean,
reflect: true,
},
};
}
static get styles() {
return [
css`
:host {
display: inline-block;
position: relative;
width: 36px;
height: 16px;
outline: 0;
}
:host([hidden]) {
display: none;
}
.btn {
position: relative;
height: 100%;
outline: 0;
}
:host(:focus:not([disabled])) .switch-button__thumb {
/* if you extend, please overwrite */
outline: 2px solid #bde4ff;
}
.switch-button__track {
background: #eee;
width: 100%;
height: 100%;
}
.switch-button__thumb {
background: #ccc;
width: 50%;
height: 100%;
position: absolute;
top: 0;
}
:host([checked]) .switch-button__thumb {
right: 0;
}
`,
];
}
render() {
return html`
<div class="btn">
<div class="switch-button__track"></div>
<div class="switch-button__thumb"></div>
</div>
`;
}
constructor() {
super();
// inputNode = this, which always requires a value prop
this.value = '';
this.checked = false;
this.__initialized = false;
/** @protected */
this._toggleChecked = this._toggleChecked.bind(this);
/** @private */
this.__handleKeydown = this._handleKeydown.bind(this);
/** @private */
this.__handleKeyup = this._handleKeyup.bind(this);
}
connectedCallback() {
super.connectedCallback();
this.setAttribute('role', 'switch');
this.setAttribute('aria-checked', `${this.checked}`);
this.addEventListener('click', this._toggleChecked);
this.addEventListener('keydown', this.__handleKeydown);
this.addEventListener('keyup', this.__handleKeyup);
}
disconnectedCallback() {
super.disconnectedCallback();
this.removeEventListener('click', this._toggleChecked);
this.removeEventListener('keydown', this.__handleKeydown);
this.removeEventListener('keyup', this.__handleKeyup);
}
/** @protected */
_toggleChecked() {
if (this.disabled) {
return;
}
// Force IE11 to focus the component.
this.focus();
this.checked = !this.checked;
}
/** @private */
__checkedStateChange() {
this.dispatchEvent(
new Event('checked-changed', {
bubbles: true,
}),
);
this.setAttribute('aria-checked', `${this.checked}`);
}
/**
* @param {KeyboardEvent} ev
* @protected
*/
// eslint-disable-next-line class-methods-use-this
_handleKeydown(ev) {
// prevent "space" scrolling on "macOS"
if (ev.key === ' ') {
ev.preventDefault();
}
}
/**
* @param {KeyboardEvent} ev
* @protected
*/
_handleKeyup(ev) {
if ([' ', 'Enter'].includes(ev.key)) {
this._toggleChecked();
}
}
/** @param {import('lit').PropertyValues } changedProperties */
updated(changedProperties) {
super.updated(changedProperties);
if (changedProperties.has('disabled')) {
this.setAttribute('aria-disabled', `${this.disabled}`); // create mixin if we need it in more places
}
}
/**
* @param {string} [name]
* @param {unknown} [oldValue]
* @param {import('lit').PropertyDeclaration} [options]
* @returns {void}
*/
requestUpdate(name, oldValue, options) {
super.requestUpdate(name, oldValue, options);
if (
this.__initialized &&
this.isConnected &&
name === 'checked' &&
this.checked !== oldValue &&
!this.disabled
) {
this.__checkedStateChange();
}
}
/** @param {import('lit').PropertyValues } changedProperties */
firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this.__initialized = true;
}
}