UNPKG

preline

Version:

Preline UI is an open-source set of prebuilt UI components based on the utility-first Tailwind CSS framework.

203 lines (160 loc) 5.43 kB
/* * HSPinInput * @version: 2.5.0 * @author: Preline Labs Ltd. * @license: Licensed under MIT and Preline UI Fair Use License (https://preline.co/docs/license.html) * Copyright 2024 Preline Labs Ltd. */ import { dispatch } from '../../utils'; import { IPinInputOptions, IPinInput } from '../pin-input/interfaces'; import HSBasePlugin from '../base-plugin'; import { ICollectionItem } from '../../interfaces'; class HSPinInput extends HSBasePlugin<IPinInputOptions> implements IPinInput { private items: NodeListOf<HTMLElement> | null; private currentItem: HTMLInputElement | null; private currentValue: string[] | null; private readonly placeholders: string[] | null; private readonly availableCharsRE: RegExp | null; constructor(el: HTMLElement, options?: IPinInputOptions) { super(el, options); const data = el.getAttribute('data-hs-pin-input'); const dataOptions: IPinInputOptions = data ? JSON.parse(data) : {}; const concatOptions = { ...dataOptions, ...options, }; this.items = this.el.querySelectorAll('[data-hs-pin-input-item]'); this.currentItem = null; this.currentValue = new Array(this.items.length).fill(''); this.placeholders = []; this.availableCharsRE = new RegExp( concatOptions?.availableCharsRE || '^[a-zA-Z0-9]+$', ); // '^[0-9]+$' this.init(); } private init() { this.createCollection(window.$hsPinInputCollection, this); if (this.items.length) this.build(); } private build() { this.buildInputItems(); } private buildInputItems() { this.items.forEach((el, index) => { this.placeholders.push(el.getAttribute('placeholder') || ''); if (el.hasAttribute('autofocus')) this.onFocusIn(index); el.addEventListener('input', (evt) => this.onInput(evt, index)); el.addEventListener('paste', (evt) => this.onPaste(evt)); el.addEventListener('keydown', (evt) => this.onKeydown(evt, index)); el.addEventListener('focusin', () => this.onFocusIn(index)); el.addEventListener('focusout', () => this.onFocusOut(index)); }); } private checkIfNumber(value: string) { return value.match(this.availableCharsRE); } private autoFillAll(text: string) { Array.from(text).forEach((n, i) => { if (!this?.items[i]) return false; (this.items[i] as HTMLInputElement).value = n; this.items[i].dispatchEvent(new Event('input', { bubbles: true })); }); } private setCurrentValue() { this.currentValue = Array.from(this.items).map( (el) => (el as HTMLInputElement).value, ); } private toggleCompleted() { if (!this.currentValue.includes('')) this.el.classList.add('active'); else this.el.classList.remove('active'); } private onInput(evt: Event, index: number) { const originalValue = (evt.target as HTMLInputElement).value; this.currentItem = evt.target as HTMLInputElement; this.currentItem.value = ''; this.currentItem.value = originalValue[originalValue.length - 1]; if (!this.checkIfNumber(this.currentItem.value)) { this.currentItem.value = this.currentValue[index] || ''; return false; } this.setCurrentValue(); if (this.currentItem.value) { if (index < this.items.length - 1) this.items[index + 1].focus(); if (!this.currentValue.includes('')) { const payload = { currentValue: this.currentValue }; this.fireEvent('completed', payload); dispatch('completed.hs.pinInput', this.el, payload); } this.toggleCompleted(); } else { if (index > 0) this.items[index - 1].focus(); } } private onKeydown(evt: KeyboardEvent, index: number) { if (evt.key === 'Backspace' && index > 0) { if ((this.items[index] as HTMLInputElement).value === '') { (this.items[index - 1] as HTMLInputElement).value = ''; (this.items[index - 1] as HTMLInputElement).focus(); } else { (this.items[index] as HTMLInputElement).value = ''; } } this.setCurrentValue(); this.toggleCompleted(); } private onFocusIn(index: number) { this.items[index].setAttribute('placeholder', ''); } private onFocusOut(index: number) { this.items[index].setAttribute('placeholder', this.placeholders[index]); } private onPaste(evt: ClipboardEvent) { evt.preventDefault(); this.items.forEach((el) => { if (document.activeElement === el) this.autoFillAll(evt.clipboardData.getData('text')); }); } // Static method static getInstance(target: HTMLElement | string, isInstance?: boolean) { const elInCollection = window.$hsPinInputCollection.find( (el) => el.element.el === (typeof target === 'string' ? document.querySelector(target) : target), ); return elInCollection ? isInstance ? elInCollection : elInCollection.element : null; } static autoInit() { if (!window.$hsPinInputCollection) window.$hsPinInputCollection = []; document .querySelectorAll('[data-hs-pin-input]:not(.--prevent-on-load-init)') .forEach((el: HTMLElement) => { if ( !window.$hsPinInputCollection.find( (elC) => (elC?.element?.el as HTMLElement) === el, ) ) new HSPinInput(el); }); } } declare global { interface Window { HSPinInput: Function; $hsPinInputCollection: ICollectionItem<HSPinInput>[]; } } window.addEventListener('load', () => { HSPinInput.autoInit(); // Uncomment for debug // console.log('PIN input collection:', window.$hsPinInputCollection); }); if (typeof window !== 'undefined') { window.HSPinInput = HSPinInput; } export default HSPinInput;