/* * HSInputNumber * @version: 4.1.3 * @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 { IInputNumber, IInputNumberOptions } from "../input-number/interfaces"; import HSBasePlugin from "../base-plugin"; class HSInputNumber extends HSBasePlugin implements IInputNumber { private readonly input: HTMLInputElement | null; private readonly increment: HTMLElement | null; private readonly decrement: HTMLElement | null; private inputValue: number | null; private readonly minInputValue: number | null; private readonly maxInputValue: number | null; private readonly step: number; private readonly forceBlankValue: boolean; private onInputInputListener: () => void; private onIncrementClickListener: () => void; private onDecrementClickListener: () => void; constructor(el: HTMLElement, options?: IInputNumberOptions) { super(el, options); this.input = this.el.querySelector("[data-hs-input-number-input]") || null; this.increment = this.el.querySelector("[data-hs-input-number-increment]") || null; this.decrement = this.el.querySelector("[data-hs-input-number-decrement]") || null; const data = this.el.dataset.hsInputNumber; const dataOptions: IInputNumberOptions = data ? JSON.parse(data) : { step: 1 }; const concatOptions = { ...dataOptions, ...options, }; this.minInputValue = "min" in concatOptions ? concatOptions.min : 0; this.maxInputValue = "max" in concatOptions ? concatOptions.max : null; this.step = "step" in concatOptions && concatOptions.step > 0 ? concatOptions.step : 1; this.forceBlankValue = "forceBlankValue" in concatOptions ? concatOptions.forceBlankValue : false; if (this.input) this.checkIsNumberAndConvert(); this.init(); } private inputInput() { this.changeValue(); } private incrementClick() { this.changeValue("increment"); } private decrementClick() { this.changeValue("decrement"); } private init() { this.createCollection(window.$hsInputNumberCollection, this); if (this.input && this.increment) this.build(); } private checkIsNumberAndConvert() { const value = this.input.value.trim(); const cleanedValue = this.cleanAndExtractNumber(value); if (cleanedValue !== null) { this.inputValue = cleanedValue; this.input.value = cleanedValue.toString(); } else { if (!this.forceBlankValue) { this.inputValue = 0; this.input.value = "0"; } } } private cleanAndExtractNumber(value: string): number | null { const cleanedArray: string[] = []; let decimalFound = false; let negativeFound = false; value.split("").forEach((char, index) => { if (char >= "0" && char <= "9") cleanedArray.push(char); else if (char === "." && !decimalFound) { cleanedArray.push(char); decimalFound = true; } else if (char === "-" && !negativeFound && cleanedArray.length === 0) { cleanedArray.push(char); negativeFound = true; } }); const cleanedValue = cleanedArray.join(""); const number = parseFloat(cleanedValue); return isNaN(number) ? null : number; } private build() { if (this.input) this.buildInput(); if (this.increment) this.buildIncrement(); if (this.decrement) this.buildDecrement(); if (this.inputValue <= this.minInputValue) { this.inputValue = this.minInputValue; this.input.value = `${this.minInputValue}`; } if (this.inputValue <= this.minInputValue) this.changeValue(); if (this.input.hasAttribute("disabled")) this.disableButtons(); } private buildInput() { this.onInputInputListener = () => this.inputInput(); this.input.addEventListener("input", this.onInputInputListener); } private buildIncrement() { this.onIncrementClickListener = () => this.incrementClick(); this.increment.addEventListener("click", this.onIncrementClickListener); } private buildDecrement() { this.onDecrementClickListener = () => this.decrementClick(); this.decrement.addEventListener("click", this.onDecrementClickListener); } private changeValue(event = "none") { const payload = { inputValue: this.inputValue }; const minInputValue = this.minInputValue ?? Number.MIN_SAFE_INTEGER; const maxInputValue = this.maxInputValue ?? Number.MAX_SAFE_INTEGER; this.inputValue = isNaN(this.inputValue) ? 0 : this.inputValue; switch (event) { case "increment": const incrementedResult = this.inputValue + this.step; this.inputValue = incrementedResult >= minInputValue && incrementedResult <= maxInputValue ? incrementedResult : maxInputValue; this.input.value = this.inputValue.toString(); break; case "decrement": const decrementedResult = this.inputValue - this.step; this.inputValue = decrementedResult >= minInputValue && decrementedResult <= maxInputValue ? decrementedResult : minInputValue; this.input.value = this.inputValue.toString(); break; default: const defaultResult = isNaN(parseInt(this.input.value)) ? 0 : parseInt(this.input.value); this.inputValue = defaultResult >= maxInputValue ? maxInputValue : defaultResult <= minInputValue ? minInputValue : defaultResult; this.input.value = this.inputValue.toString(); break; } payload.inputValue = this.inputValue; if (this.inputValue === minInputValue) { this.el.classList.add("disabled"); if (this.decrement) this.disableButtons("decrement"); } else { this.el.classList.remove("disabled"); if (this.decrement) this.enableButtons("decrement"); } if (this.inputValue === maxInputValue) { this.el.classList.add("disabled"); if (this.increment) this.disableButtons("increment"); } else { this.el.classList.remove("disabled"); if (this.increment) this.enableButtons("increment"); } this.fireEvent("change", payload); dispatch("change.hs.inputNumber", this.el, payload); } private disableButtons(mode = "all") { if (mode === "all") { if ( this.increment.tagName === "BUTTON" || this.increment.tagName === "INPUT" ) { this.increment.setAttribute("disabled", "disabled"); } if ( this.decrement.tagName === "BUTTON" || this.decrement.tagName === "INPUT" ) { this.decrement.setAttribute("disabled", "disabled"); } } else if (mode === "increment") { if ( this.increment.tagName === "BUTTON" || this.increment.tagName === "INPUT" ) { this.increment.setAttribute("disabled", "disabled"); } } else if (mode === "decrement") { if ( this.decrement.tagName === "BUTTON" || this.decrement.tagName === "INPUT" ) { this.decrement.setAttribute("disabled", "disabled"); } } } private enableButtons(mode = "all") { if (mode === "all") { if ( this.increment.tagName === "BUTTON" || this.increment.tagName === "INPUT" ) { this.increment.removeAttribute("disabled"); } if ( this.decrement.tagName === "BUTTON" || this.decrement.tagName === "INPUT" ) { this.decrement.removeAttribute("disabled"); } } else if (mode === "increment") { if ( this.increment.tagName === "BUTTON" || this.increment.tagName === "INPUT" ) { this.increment.removeAttribute("disabled"); } } else if (mode === "decrement") { if ( this.decrement.tagName === "BUTTON" || this.decrement.tagName === "INPUT" ) { this.decrement.removeAttribute("disabled"); } } } // Public methods public destroy() { // Remove classes this.el.classList.remove("disabled"); // Remove attributes this.increment.removeAttribute("disabled"); this.decrement.removeAttribute("disabled"); // Remove listeners this.input.removeEventListener("input", this.onInputInputListener); this.increment.removeEventListener("click", this.onIncrementClickListener); this.decrement.removeEventListener("click", this.onDecrementClickListener); window.$hsInputNumberCollection = window.$hsInputNumberCollection.filter( ({ element }) => element.el !== this.el, ); } // Global method static getInstance(target: HTMLElement | string, isInstance?: boolean) { const elInCollection = window.$hsInputNumberCollection.find( (el) => el.element.el === (typeof target === "string" ? document.querySelector(target) : target), ); return elInCollection ? isInstance ? elInCollection : elInCollection.element : null; } static autoInit() { if (!window.$hsInputNumberCollection) window.$hsInputNumberCollection = []; if (window.$hsInputNumberCollection) { window.$hsInputNumberCollection = window.$hsInputNumberCollection.filter( ({ element }) => document.contains(element.el), ); } document .querySelectorAll("[data-hs-input-number]:not(.--prevent-on-load-init)") .forEach((el: HTMLElement) => { if ( !window.$hsInputNumberCollection.find( (elC) => (elC?.element?.el as HTMLElement) === el, ) ) { new HSInputNumber(el); } }); } } export default HSInputNumber;