select-pure
Version:
Custom JavaScript <select> component. Easy-to-use, accessible, mobile friendly and super efficient
345 lines • 11.5 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { boundMethod } from "autobind-decorator";
import { LitElement, html } from "lit";
import { ifDefined } from "lit-html/directives/if-defined.js";
import { customElement } from "lit/decorators/custom-element.js";
import { property } from "lit/decorators/property.js";
import { query } from "lit/decorators/query.js";
import { KEYS, defaultOption } from "./../constants";
import { selectStyles } from "./../styles";
let SelectPure = class SelectPure extends LitElement {
constructor() {
super(...arguments);
this.options = [];
this.visible = false;
this.selectedOption = defaultOption;
this._selectedOptions = [];
this.disabled = false;
this.isMultipleSelect = false;
this.name = "";
this._id = "";
this.formName = "";
this.value = "";
this.values = [];
this.defaultLabel = "";
this.totalRenderedChildOptions = -1;
this.form = null;
this.hiddenInput = null;
}
static get styles() {
return selectStyles;
}
connectedCallback() {
super.connectedCallback();
this.disabled = this.getAttribute("disabled") !== null;
this.isMultipleSelect = this.getAttribute("multiple") !== null;
this.name = this.getAttribute("name") || "";
this._id = this.getAttribute("id") || "";
this.formName = this.name || this.id;
this.defaultLabel = this.getAttribute("default-label") || "";
}
open() {
if (this.disabled) {
return;
}
this.visible = true;
this.removeEventListeners();
document.body.addEventListener("click", this.close, true);
}
close(event) {
// @ts-ignore
if (event && this.contains(event.target)) {
return;
}
this.visible = false;
this.removeEventListeners();
}
enable() {
this.disabled = false;
}
disable() {
this.disabled = true;
}
get selectedIndex() {
var _a;
return (_a = this.nativeSelect) === null || _a === void 0 ? void 0 : _a.selectedIndex;
}
set selectedIndex(newSelectedIndex) {
if (!newSelectedIndex && newSelectedIndex !== 0) {
return;
}
this.selectOptionByValue(this.options[newSelectedIndex].value);
}
get selectedOptions() {
var _a;
return (_a = this.nativeSelect) === null || _a === void 0 ? void 0 : _a.selectedOptions;
}
render() {
const labelClassNames = ["label"];
if (this.disabled) {
labelClassNames.push("disabled");
}
if (this.visible) {
labelClassNames.push("visible");
}
return html `
<div class="select-wrapper">
<select
=${this.handleNativeSelectChange}
?disabled=${this.disabled}
?multiple=${this.isMultipleSelect}
name="${ifDefined(this.name || undefined)}"
id=${ifDefined(this.id || undefined)}
size="1"
>
${this.getNativeOptionsHtml()}
</select>
<div class="select">
<div
class="${labelClassNames.join(" ")}"
="${this.visible ? this.close : this.open}"
="${this.openDropdownIfProperKeyIsPressed}"
tabindex="0"
>
${this.getDisplayedLabel()}
</div>
<div class="dropdown${this.visible ? " visible" : ""}">
<slot =${this.initializeSelect}></slot>
</div>
</div>
</div>
`;
}
handleNativeSelectChange() {
var _a;
this.selectedIndex = (_a = this.nativeSelect) === null || _a === void 0 ? void 0 : _a.selectedIndex;
}
getNativeOptionsHtml() {
return this.options.map(this.getSingleNativeOptionHtml);
}
getSingleNativeOptionHtml({ value, label, hidden, disabled }) {
return html `
<option
value=${value}
?selected=${this.isOptionSelected(value)}
?hidden=${hidden}
?disabled=${disabled}
>
${label}
</option>
`;
}
isOptionSelected(value) {
let isOptionSelected = this.selectedOption.value === value;
if (this.isMultipleSelect) {
isOptionSelected = Boolean(this._selectedOptions.find(option => option.value === value));
}
return isOptionSelected;
}
openDropdownIfProperKeyIsPressed(event) {
if (event.key === KEYS.ENTER || event.key === KEYS.TAB) {
this.open();
}
}
getDisplayedLabel() {
if (this.isMultipleSelect && this._selectedOptions.length) {
return this.getMultiSelectLabelHtml();
}
return this.selectedOption.label || this.defaultLabel;
}
getMultiSelectLabelHtml() {
return html `
<div class="multi-selected-wrapper">
${this._selectedOptions.map(this.getMultiSelectSelectedOptionHtml)}
</div>
`;
}
getMultiSelectSelectedOptionHtml({ label, value }) {
return html `
<span class="multi-selected">
${label}
<span
class="cross"
=${(event) => this.fireOnSelectCallback(event, value)}
>
</span>
</span>
`;
}
fireOnSelectCallback(event, value) {
event.stopPropagation();
this.selectOptionByValue(value);
}
initializeSelect() {
this.processChildOptions();
this.selectDefaultOptionIfNoneSelected();
this.appendHiddenInputToClosestForm();
}
processChildOptions() {
const options = this.querySelectorAll("option-pure");
this.totalRenderedChildOptions = options.length;
for (let i = 0; i < options.length; i++) {
this.initializeSingleOption(options[i], i);
}
}
selectDefaultOptionIfNoneSelected() {
const shouldSelectDefaultOption = !this.selectedOption.value && !this.isMultipleSelect && this.options.length;
if (shouldSelectDefaultOption) {
this.selectOptionByValue(this.options[0].value);
}
}
initializeSingleOption(optionElement, optionIndex) {
optionElement.setOnSelectCallback(this.selectOptionByValue);
this.options[optionIndex] = optionElement.getOption();
if (this.options[optionIndex].selected) {
this.selectOptionByValue(this.options[optionIndex].value);
}
}
removeEventListeners() {
document.body.removeEventListener("click", this.close);
}
appendHiddenInputToClosestForm() {
this.form = this.closest("form");
if (!this.form || this.hiddenInput) {
return;
}
this.hiddenInput = document.createElement("input");
this.hiddenInput.setAttribute("type", "hidden");
this.hiddenInput.setAttribute("name", this.formName);
this.form.appendChild(this.hiddenInput);
}
unselectAllOptions() {
for (let i = 0; i < this.options.length; i++) {
this.options[i].unselect();
}
}
selectOptionByValue(newOptionValue) {
const option = this.options.find(({ value }) => value === newOptionValue);
if (!option) {
return;
}
this.setSelectValue(option);
}
setSelectValue(optionToBeSelected) {
if (this.isMultipleSelect) {
this.setMultiSelectValue(optionToBeSelected);
}
else {
this.setSingleSelectValue(optionToBeSelected);
}
this.updateHiddenInputInForm();
this.dispatchChangeEvent();
}
dispatchChangeEvent() {
this.dispatchEvent(new Event("change"));
}
setMultiSelectValue(optionToBeSelected) {
const indexInSelectedOptions = this._selectedOptions.indexOf(optionToBeSelected);
const isAlreadySelected = indexInSelectedOptions !== -1;
if (isAlreadySelected) {
this.values.splice(indexInSelectedOptions, 1);
this._selectedOptions.splice(indexInSelectedOptions, 1);
optionToBeSelected.unselect();
}
else {
this.values.push(optionToBeSelected.value);
this._selectedOptions.push(optionToBeSelected);
optionToBeSelected.select();
}
this.requestUpdate();
}
setSingleSelectValue(optionToBeSelected) {
this.unselectAllOptions();
this.close();
this.selectedOption = optionToBeSelected;
this.value = optionToBeSelected.value;
optionToBeSelected.select();
}
updateHiddenInputInForm() {
if (!this.form || !this.hiddenInput) {
return;
}
this.hiddenInput.value = this.isMultipleSelect ? this.values.join(",") : this.value;
const event = new Event("change", { bubbles: true });
this.hiddenInput.dispatchEvent(event);
}
};
__decorate([
property()
], SelectPure.prototype, "options", void 0);
__decorate([
property()
], SelectPure.prototype, "visible", void 0);
__decorate([
property()
], SelectPure.prototype, "selectedOption", void 0);
__decorate([
property()
], SelectPure.prototype, "_selectedOptions", void 0);
__decorate([
property()
], SelectPure.prototype, "disabled", void 0);
__decorate([
property()
], SelectPure.prototype, "isMultipleSelect", void 0);
__decorate([
property()
], SelectPure.prototype, "name", void 0);
__decorate([
property()
], SelectPure.prototype, "_id", void 0);
__decorate([
property()
], SelectPure.prototype, "formName", void 0);
__decorate([
property()
], SelectPure.prototype, "value", void 0);
__decorate([
property()
], SelectPure.prototype, "values", void 0);
__decorate([
property()
], SelectPure.prototype, "defaultLabel", void 0);
__decorate([
property()
], SelectPure.prototype, "totalRenderedChildOptions", void 0);
__decorate([
query("select")
], SelectPure.prototype, "nativeSelect", void 0);
__decorate([
boundMethod
], SelectPure.prototype, "close", null);
__decorate([
boundMethod
], SelectPure.prototype, "getSingleNativeOptionHtml", null);
__decorate([
boundMethod
], SelectPure.prototype, "getMultiSelectLabelHtml", null);
__decorate([
boundMethod
], SelectPure.prototype, "getMultiSelectSelectedOptionHtml", null);
__decorate([
boundMethod
], SelectPure.prototype, "initializeSelect", null);
__decorate([
boundMethod
], SelectPure.prototype, "initializeSingleOption", null);
__decorate([
boundMethod
], SelectPure.prototype, "removeEventListeners", null);
__decorate([
boundMethod
], SelectPure.prototype, "appendHiddenInputToClosestForm", null);
__decorate([
boundMethod
], SelectPure.prototype, "selectOptionByValue", null);
SelectPure = __decorate([
customElement("select-pure")
], SelectPure);
export { SelectPure };
//# sourceMappingURL=Select.js.map