UNPKG

select-pure

Version:

Custom JavaScript <select> component. Easy-to-use, accessible, mobile friendly and super efficient

345 lines 11.5 kB
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 @change=${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(" ")}" @click="${this.visible ? this.close : this.open}" @keydown="${this.openDropdownIfProperKeyIsPressed}" tabindex="0" > ${this.getDisplayedLabel()} </div> <div class="dropdown${this.visible ? " visible" : ""}"> <slot @slotchange=${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" @click=${(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