const pinInput = document.createElement('template'); pinInput.innerHTML = `
`; customElements.define('pin-input', class extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }).append(pinInput.content.cloneNode(true)) this.pinDigits = 4 this.arrayOfInput = []; this.container = this.shadowRoot.querySelector('.pin-container'); this.toggleButton = this.shadowRoot.querySelector('button') } set value(val) { this.arrayOfInput.forEach((input, index) => input.value = val[index] ? val[index] : '') } get value() { return this.getValue() } set pinLength(val) { this.pinDigits = val this.setAttribute('pin-length', val) this.style.setProperty('--pin-length', val) this.render() } get isValid() { return this.arrayOfInput.every(input => input.value.trim().length) } clear = () => { this.value = '' } focusIn = () => { this.arrayOfInput[0].focus(); } getValue = () => { return this.arrayOfInput.reduce((acc, val) => { return acc += val.value }, '') } render = () => { this.container.innerHTML = '' const frag = document.createDocumentFragment(); for (let i = 0; i < this.pinDigits; i++) { const inputBox = document.createElement('input') inputBox.setAttribute('type', 'password') inputBox.setAttribute('inputmode', 'numeric') inputBox.setAttribute('maxlength', '1') inputBox.setAttribute('required', '') this.arrayOfInput.push(inputBox); frag.append(inputBox); } this.container.append(frag); } handleKeydown = (e) => { const activeInput = e.target.closest('input') if (/[0-9]/.test(e.key)) { if (activeInput.value.trim().length > 2) { e.preventDefault(); } else { if (activeInput.value.trim().length === 1) { activeInput.value = e.key } if (activeInput.nextElementSibling) { setTimeout(() => { activeInput.nextElementSibling.focus(); }, 0) } } } else if (e.key === "Backspace") { if (activeInput.previousElementSibling) setTimeout(() => { activeInput.previousElementSibling.focus(); }, 0) } else if (e.key.length === 1 && !/[0-9]/.test(e.key)) { e.preventDefault(); } } handleInput = () => { if (this.isValid) { this.fireEvent(this.getValue()) } } fireEvent = (value) => { let event = new CustomEvent('pincomplete', { bubbles: true, cancelable: true, composed: true, detail: { value } }); this.dispatchEvent(event); } toggleVisibility = () => { if (this.arrayOfInput[0].getAttribute('type') === 'password') { this.toggleButton.innerHTML = ` Hide ` this.arrayOfInput.forEach(input => input.setAttribute('type', 'text')) } else { this.toggleButton.innerHTML = ` Show ` this.arrayOfInput.forEach(input => input.setAttribute('type', 'password')) } } connectedCallback() { if (this.hasAttribute('pin-length')) { const pinLength = parseInt(this.getAttribute('pin-length')) this.pinDigits = pinLength this.style.setProperty('--pin-length', pinLength) } this.render() this.toggleButton.addEventListener('click', this.toggleVisibility) this.container.addEventListener('input', this.handleInput); this.container.addEventListener('keydown', this.handleKeydown); } disconnectedCallback() { this.toggleButton.removeEventListener('click', this.toggleVisibility) this.container.removeEventListener('input', this.handleInput); this.container.removeEventListener('keydown', this.handleKeydown); } })