export interface InputStepperConfig { nonEditableInput?: boolean; min?: number; max?: number; defaultValue?: number; } export const defaultConfig: Required = { nonEditableInput: false, min: 1, max: 999, defaultValue: 1, }; export default class InputStepperStatic { element: HTMLElement; config: Required; input: HTMLInputElement | null; numberDisplay: HTMLElement | null; add: HTMLButtonElement | null; remove: HTMLButtonElement | null; constructor(element: HTMLElement, config?: InputStepperConfig) { this.element = element; this.config = { ...defaultConfig, ...config }; this.input = null; this.numberDisplay = null; this.add = null; this.remove = null; (this.element as any).ODS_InputStepper = this; this.handleButtonClick = this.handleButtonClick.bind(this); this.handleInputClick = this.handleInputClick.bind(this); this.handleInputChange = this.handleInputChange.bind(this); this.handleButtonState = this.handleButtonState.bind(this); this.init(); } handleInputClick(): void { if (this.input) { this.input.select(); } } handleButtonState(): void { const { min, max } = this.config; const value = !this.config.nonEditableInput ? parseInt(this.input?.value ?? "0", 10) : parseInt(this.numberDisplay?.textContent ?? "0", 10); if (this.add && typeof max === "number" && value >= max) { this.add.setAttribute("aria-disabled", "true"); this.add.setAttribute("disabled", "true"); } else if (this.add) { this.add.removeAttribute("aria-disabled"); this.add.removeAttribute("disabled"); } if (this.remove && typeof min === "number" && value <= min) { this.remove.setAttribute("aria-disabled", "true"); this.remove.setAttribute("disabled", "true"); } else if (this.remove) { this.remove.removeAttribute("aria-disabled"); this.remove.removeAttribute("disabled"); } } handleInputChange(e: Event): void { const { min, max } = this.config; const target = e.target as HTMLInputElement; if (target) { if (min && Number(target.value) <= min) { target.value = String(min); } if (max && Number(target.value) >= max) { target.value = String(max); } } } handleButtonClick(e: MouseEvent): void { const { min, max } = this.config; const target = e.target as HTMLElement; let value = !this.config.nonEditableInput ? parseInt(this.input?.value ?? "0", 10) : parseInt(this.numberDisplay?.textContent ?? "0", 10); if (target.classList.contains("input-stepper__button--minus")) { if (min && value <= min) { if (target.getAttribute("disabled") === "true") return; target.setAttribute("aria-disabled", "true"); target.setAttribute("disabled", "true"); return; } value -= 1; } if (target.classList.contains("input-stepper__button--plus")) { if (max && value >= max) { if (target.getAttribute("disabled") === "true") return; target.setAttribute("aria-disabled", "true"); target.setAttribute("disabled", "true"); return; } value += 1; } if (!this.config.nonEditableInput && this.input) { this.input.value = String(value); } else if (this.numberDisplay) { this.numberDisplay.textContent = String(value); } this.handleButtonState(); } init(): void { const configAttr = this.element.getAttribute("data-inputstepper"); let parsedConfig: Partial = {}; try { if (configAttr && configAttr.trim().startsWith("{")) { parsedConfig = JSON.parse(configAttr); } } catch { parsedConfig = {}; } this.config = { ...this.config, ...parsedConfig, }; if (!this.config.nonEditableInput) { this.input = this.element.querySelector( ".input-stepper__field", ) as HTMLInputElement; if (this.input) this.input.value = String(this.config.defaultValue); } else { this.numberDisplay = this.element.querySelector(".input-stepper__number"); if (this.numberDisplay) this.numberDisplay.textContent = String(this.config.defaultValue); } this.add = this.element.querySelector( ".input-stepper__button--plus", ) as HTMLButtonElement; this.remove = this.element.querySelector( ".input-stepper__button--minus", ) as HTMLButtonElement; const { defaultValue } = this.config; if (defaultValue !== undefined) { if (!this.config.nonEditableInput && this.input) { this.input.value = String(defaultValue); } else if (this.numberDisplay) { this.numberDisplay.textContent = String(defaultValue); } } if (!this.element.classList.contains("input-stepper--disabled")) { if (this.add) this.add.addEventListener("click", this.handleButtonClick); if (this.remove) this.remove.addEventListener("click", this.handleButtonClick); if (!this.config.nonEditableInput && this.input) { this.input.addEventListener("click", this.handleInputClick); this.input.addEventListener("change", this.handleInputChange); } this.handleButtonState(); } } update(): void { this.destroy(); this.init(); } destroy(): void { if (!this.config.nonEditableInput && this.input) { this.input.removeEventListener("click", this.handleInputClick); this.input.removeEventListener("change", this.handleInputChange); } if (this.add) this.add.removeEventListener("click", this.handleButtonClick); if (this.remove) this.remove.removeEventListener("click", this.handleButtonClick); } static getInstance(el: HTMLElement): InputStepperStatic | null { return el && (el as any).ODS_InputStepper ? (el as any).ODS_InputStepper : null; } }