@kdsoft/lit-mvvm-components
Version:
Webcomponents based on @kdsoft-lit-mvvm
217 lines (187 loc) • 5.68 kB
JavaScript
import { LitMvvmElement, html, nothing, css } from '@kdsoft/lit-mvvm';
function isChildOf(parent, child) {
if (!child) {
return false;
}
while (child.parentNode !== parent) {
child = child.parentNode;
if (child == null) {
return false;
}
}
return true;
}
export default class KdsDropdown extends LitMvvmElement {
get searchbox() { return this.hasAttribute('searchbox'); }
set searchbox(val) {
if (val) this.setAttribute('searchbox', '');
else this.removeAttribute('searchbox');
}
// Observed attributes will trigger an attributeChangedCallback, which in turn will cause a re-render to be scheduled!
static get observedAttributes() {
return [...super.observedAttributes, 'searchbox'];
}
_hostLostFocus(e) {
// we use this flag to handle intermediate lost focus events when clicking
// the dropdown button that should not close the dropdown up
if (this._controlActive) return;
this.model.dropped = false;
}
_seltextFocused(e) {
e.preventDefault();
if (this.searchbox) {
const seltext = e.currentTarget;
const searchbox = e.currentTarget.nextElementSibling;
// because of this, seltext will not receive the click and mouseup events
seltext.setAttribute('hidden', '');
searchbox.removeAttribute('hidden');
searchbox.focus();
} else {
this.model.dropped = true;
}
}
_seltextLostFocus(e) {
e.preventDefault();
}
_searchboxFocused(e) {
e.preventDefault();
this.model.dropped = true;
}
_searchboxLostFocus(e) {
e.preventDefault();
const seltext = e.currentTarget.previousElementSibling;
seltext.removeAttribute('hidden');
e.currentTarget.setAttribute('hidden', '');
}
_searchTextChanged(e) {
this.model.searchText = e.currentTarget.value;
}
_dropdownButtonDown(e) {
this._controlActive = true;
}
_dropdownButtonUp(e) {
this._controlActive = false;
// in case the button does not become focused focused because the slot contains a non-focusable node
e.currentTarget.focus();
this.model.dropped = !this.model.dropped;
}
_dropdownButtonCancel(e) {
this._controlActive = false;
}
_dropDownFocused(e) {
// prevent the _hostLostFocus() event from closing up the drop down
this.model.dropped = true;
}
_dropDownDown(e) {
// prevent the _hostLostFocus() event from closing up the drop down
e.preventDefault();
}
_slotKeydown(e) {
if (e.key === 'Enter') {
e.preventDefault();
this.model.dropped = false;
const btn = this.renderRoot.getElementById('dropDownButton');
btn.focus();
}
}
/* eslint-disable indent, no-else-return */
connectedCallback() {
super.connectedCallback();
this.renderRoot.host.addEventListener('focusout', this._hostLostFocus);
}
disconnectedCallback() {
super.disconnectedCallback();
// necessary?
this.renderRoot.host.removeEventListener('focusout', this._hostLostFocus);
}
static get styles() {
return [
css`
:host {
display: inline-block;
line-height: 1.25em;
}
#container {
display: flex;
align-items: baseline;
justify-items: flex-end;
}
#dropdown {
position: relative;
z-index: 50;
/* height: 0; */
}
#mainslot::slotted(*) {
position: absolute;
left: 0;
top: 0;
}
#seltext {
pointer-events: auto;
margin-top: auto;
margin-bottom: auto;
flex-grow: 1;
white-space: nowrap;
overflow-x: hidden;
}
/* empty span should have a non-zero height */
#seltext::before {
content: '\u200b'; /* unicode zero width space character */
}
#searchbox {
margin-top: auto;
margin-bottom: auto;
flex-grow: 1;
}
`
];
}
render() {
const selText = this.model.selectedText;
const result = html`
<div id="container" part="container">
<span id="seltext" part="seltext"
tabindex="1"
title="${selText}"
@focus="${this._seltextFocused}"
@blur="${this._seltextLostFocus}"
>
${selText}
</span>
${this.searchbox
? html`
<input id="searchbox" part="searchbox"
type="text"
tabindex="1"
placeholder="search unselected entries"
@focus="${this._searchboxFocused}"
@blur="${this._searchboxLostFocus}"
@input="${this._searchTextChanged}"
hidden />
`
: nothing
}
<button id="dropDownButton" part="dropDownButton"
type="button"
tabindex="3"
@pointerdown="${this._dropdownButtonDown}"
@pointerup="${this._dropdownButtonUp}"
@pointercancel="${this._dropdownButtonCancel}"
>
<slot name="dropDownButtonIcon">
<span>V</span>
</slot>
</button>
</div>
<div id="dropdown"
@focusin="${this._dropDownFocused}"
@pointerdown="${this._dropDownDown}"
?hidden=${!this.model.dropped}
>
<slot id="mainslot" tabindex="2" @keydown=${this._slotKeydown}>No dropdown content provided.</slot>
</div>
`;
return result;
}
}
window.customElements.define('kds-dropdown', KdsDropdown);