//Input const smInput = document.createElement('template') smInput.innerHTML = `

`; customElements.define('sm-input', class extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }).append(smInput.content.cloneNode(true)); this.inputParent = this.shadowRoot.querySelector('.input'); this.input = this.shadowRoot.querySelector('input'); this.clearBtn = this.shadowRoot.querySelector('.clear'); this.label = this.shadowRoot.querySelector('.label'); this.feedbackText = this.shadowRoot.querySelector('.feedback-text'); this.outerContainer = this.shadowRoot.querySelector('.outer-container'); this.optionList = this.shadowRoot.querySelector('.datalist'); this._helperText = ''; this._errorText = ''; this.isRequired = false; this.datalist = []; this.validationFunction = undefined; this.reflectedAttributes = ['value', 'required', 'disabled', 'type', 'inputmode', 'readonly', 'min', 'max', 'pattern', 'minlength', 'maxlength', 'step', 'list', 'autocomplete']; this.reset = this.reset.bind(this); this.clear = this.clear.bind(this); this.focusIn = this.focusIn.bind(this); this.focusOut = this.focusOut.bind(this); this.fireEvent = this.fireEvent.bind(this); this.checkInput = this.checkInput.bind(this); this.showError = this.showError.bind(this); this.allowOnlyNum = this.allowOnlyNum.bind(this); this.handleOptionClick = this.handleOptionClick.bind(this); this.handleInputNavigation = this.handleInputNavigation.bind(this); this.handleDatalistNavigation = this.handleDatalistNavigation.bind(this); this.handleFocus = this.handleFocus.bind(this); this.handleBlur = this.handleBlur.bind(this); } static get observedAttributes() { return ['value', 'placeholder', 'required', 'disabled', 'type', 'inputmode', 'readonly', 'min', 'max', 'pattern', 'minlength', 'maxlength', 'step', 'helper-text', 'error-text', 'list']; } get value() { return this.input.value; } set value(val) { if (val === this.input.value) return; this.input.value = val; this.checkInput(); } get placeholder() { return this.getAttribute('placeholder'); } set placeholder(val) { this.setAttribute('placeholder', val); } get type() { return this.getAttribute('type'); } set type(val) { this.setAttribute('type', val); } get validity() { return this.input.validity; } get disabled() { return this.hasAttribute('disabled'); } set disabled(value) { if (value) { this.inputParent.classList.add('disabled'); this.setAttribute('disabled', ''); } else { this.inputParent.classList.remove('disabled'); this.removeAttribute('disabled'); } } get readOnly() { return this.hasAttribute('readonly'); } set readOnly(value) { if (value) { this.setAttribute('readonly', ''); } else { this.removeAttribute('readonly'); } } set customValidation(val) { this.validationFunction = val; } set errorText(val) { this._errorText = val; } showError() { this.feedbackText.className = 'feedback-text error'; this.feedbackText.innerHTML = ` ${this._errorText}`; } set helperText(val) { this._helperText = val; } get isValid() { if (this.input.value !== '') { const _isValid = this.input.checkValidity(); let _customValid = true; if (this.validationFunction) { _customValid = Boolean(this.validationFunction(this.input.value)); } if (_isValid && _customValid) { this.feedbackText.className = 'feedback-text success'; this.feedbackText.textContent = ''; } else { if (this._errorText) { this.showError(); } } return (_isValid && _customValid); } } reset() { this.value = ''; } clear() { this.value = ''; this.input.focus(); this.fireEvent(); } focusIn() { this.input.focus(); } focusOut() { this.input.blur(); } fireEvent() { let event = new Event('input', { bubbles: true, cancelable: true, composed: true }); this.dispatchEvent(event); } searchDatalist(searchKey) { const filteredData = this.datalist.filter(item => item.toLowerCase().includes(searchKey.toLowerCase())); // sort the filtered data based on the input value filteredData.sort((a, b) => { const aIndex = a.toLowerCase().indexOf(searchKey.toLowerCase()); const bIndex = b.toLowerCase().indexOf(searchKey.toLowerCase()); return aIndex - bIndex; }); if (filteredData.length) { if (this.optionList.children.length > filteredData.length) { // remove extra options const optionsToRemove = this.optionList.children.length - filteredData.length; for (let i = 0; i < optionsToRemove; i++) { this.optionList.removeChild(this.optionList.lastChild); } } filteredData.forEach((item, index) => { if (this.optionList.children[index]) { this.optionList.children[index].textContent = item; } else { const option = document.createElement('li'); option.textContent = item; option.classList.add('datalist-item'); option.setAttribute('tabindex', '0'); this.optionList.appendChild(option); } }) this.optionList.classList.remove('hidden'); } else { this.optionList.classList.add('hidden'); } } checkInput(e) { if (!this.hasAttribute('readonly')) { if (this.input.value !== '') { this.clearBtn.classList.remove('hidden'); } else { this.clearBtn.classList.add('hidden'); } } if (!this.hasAttribute('placeholder') || this.getAttribute('placeholder').trim() === '') return; if (this.input.value !== '') { if (this.animate) this.inputParent.classList.add('animate-placeholder'); else this.label.classList.add('hidden'); if (this.datalist.length) { // debounce the search if (this.searchTimeout) { clearTimeout(this.searchTimeout); } this.searchTimeout = setTimeout(() => { this.searchDatalist(this.input.value.trim()); }, 100); } } else { if (this.animate) this.inputParent.classList.remove('animate-placeholder'); else this.label.classList.remove('hidden'); this.feedbackText.textContent = ''; if (this.datalist.length) { this.optionList.innerHTML = ''; this.optionList.classList.add('hidden'); } } } allowOnlyNum(e) { if (e.key.length === 1) { if (e.key === '.' && (e.target.value.includes('.') || e.target.value.length === 0)) { e.preventDefault(); } else if (!['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.'].includes(e.key)) { e.preventDefault(); } } } handleOptionClick(e) { this.input.value = e.target.textContent; this.optionList.classList.add('hidden'); this.input.focus(); } // handle arrow key navigation on input handleInputNavigation(e) { if (e.key === 'ArrowDown') { e.preventDefault(); if (this.optionList.children.length) { this.optionList.children[0].focus(); } } else if (e.key === 'ArrowUp') { e.preventDefault(); if (this.optionList.children.length) { this.optionList.children[this.optionList.children.length - 1].focus(); } } } // handle arrow key navigation on datalist handleDatalistNavigation(e) { if (e.key === 'ArrowUp') { e.preventDefault(); this.shadowRoot.activeElement.previousElementSibling ? this.shadowRoot.activeElement.previousElementSibling.focus() : this.input.focus(); } else if (e.key === 'ArrowDown') { e.preventDefault(); this.shadowRoot.activeElement.nextElementSibling ? this.shadowRoot.activeElement.nextElementSibling.focus() : this.input.focus(); } else if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.input.value = e.target.textContent; this.optionList.classList.add('hidden'); this.input.focus(); } } handleFocus(e) { if (this.datalist.length) { this.searchDatalist(this.input.value.trim()); } } handleBlur(e) { if (this.datalist.length) { this.optionList.classList.add('hidden'); } } connectedCallback() { this.animate = this.hasAttribute('animate'); this.setAttribute('role', 'textbox'); this.input.addEventListener('input', this.checkInput); this.clearBtn.addEventListener('click', this.clear); if (this.datalist.length) { this.optionList.addEventListener('click', this.handleOptionClick); this.input.addEventListener('keydown', this.handleInputNavigation); this.optionList.addEventListener('keydown', this.handleDatalistNavigation); } this.input.addEventListener('focusin', this.handleFocus); this.addEventListener('focusout', this.handleBlur); } attributeChangedCallback(name, oldValue, newValue) { if (oldValue !== newValue) { if (this.reflectedAttributes.includes(name)) { if (this.hasAttribute(name)) { this.input.setAttribute(name, this.getAttribute(name) ? this.getAttribute(name) : ''); } else { this.input.removeAttribute(name); } } if (name === 'placeholder') { this.label.textContent = newValue; this.setAttribute('aria-label', newValue); } else if (this.hasAttribute('value')) { this.checkInput(); } else if (name === 'type') { if (this.hasAttribute('type') && this.getAttribute('type') === 'number') { this.input.setAttribute('inputmode', 'decimal'); this.input.addEventListener('keydown', this.allowOnlyNum); } else { this.input.removeEventListener('keydown', this.allowOnlyNum); } } else if (name === 'helper-text') { this._helperText = this.getAttribute('helper-text'); } else if (name === 'error-text') { this._errorText = this.getAttribute('error-text'); } else if (name === 'required') { this.isRequired = this.hasAttribute('required'); if (this.isRequired) { this.setAttribute('aria-required', 'true'); } else { this.setAttribute('aria-required', 'false'); } } else if (name === 'readonly') { if (this.hasAttribute('readonly')) { this.inputParent.classList.add('readonly'); } else { this.inputParent.classList.remove('readonly'); } } else if (name === 'disabled') { if (this.hasAttribute('disabled')) { this.inputParent.classList.add('disabled'); } else { this.inputParent.classList.remove('disabled'); } } else if (name === 'list') { if (this.hasAttribute('list') && this.getAttribute('list').trim() !== '') { this.datalist = this.getAttribute('list').split(','); } } } } disconnectedCallback() { this.input.removeEventListener('input', this.checkInput); this.clearBtn.removeEventListener('click', this.clear); this.input.removeEventListener('keydown', this.allowOnlyNum); this.optionList.removeEventListener('click', this.handleOptionClick); this.input.removeEventListener('keydown', this.handleInputNavigation); this.optionList.removeEventListener('keydown', this.handleDatalistNavigation); this.input.removeEventListener('focusin', this.handleFocus); this.removeEventListener('focusout', this.handleBlur); } })