/** * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes * Copyright 2025 by Keenthemes Inc */ import KTData from '../../helpers/data'; import KTDom from '../../helpers/dom'; import KTComponent from '../component'; import { KTStepperInterface, KTStepperConfigInterface } from './types'; declare global { interface Window { KT_STEPPER_INITIALIZED: boolean; KTStepper: typeof KTStepper; } } export class KTStepper extends KTComponent implements KTStepperInterface { protected override _name: string = 'stepper'; protected override _defaultConfig: KTStepperConfigInterface = { hiddenClass: 'hidden', activeStep: 1, }; protected override _config: KTStepperConfigInterface = this._defaultConfig; protected _activeStep: number = 0; protected _nextElement: HTMLElement | null = null; protected _backElement: HTMLElement | null = null; constructor( element: HTMLElement, config: KTStepperConfigInterface | null = null, ) { super(); if (KTData.has(element as HTMLElement, this._name)) return; this._init(element); this._buildConfig(config); if (!this._element) return; this._nextElement = this._element.querySelector('[data-kt-stepper-next]'); this._backElement = this._element.querySelector('[data-kt-stepper-back]'); this._activeStep = 1; if (this._getOption('activeStep') !== this._activeStep) { this._go(this._getOption('activeStep') as number); } this._update(); this._handlers(); } protected _handlers(): void { if (!this._nextElement) { console.error('data-kt-stepper-next not found'); return; } if (this._nextElement) { this._nextElement.addEventListener('click', (event: Event) => { event.preventDefault(); this._goNext(); }); } if (this._backElement) { this._backElement.addEventListener('click', (event: Event) => { event.preventDefault(); this._goBack(); }); } } protected _update(): void { if (!this._element) return; let state = ''; if (this._activeStep === this._getTotalSteps()) { state = 'last'; } else if (this._activeStep === 1) { state = 'first'; } else { state = 'between'; } this._element.classList.remove('first'); this._element.classList.remove('last'); this._element.classList.remove('between'); this._element.classList.add(state); this._getItemElements().forEach((element, index) => { const contentElement = KTDom.getElement( element.getAttribute('data-kt-stepper-item'), ); if (!contentElement) return; element.classList.remove('active'); element.classList.remove('completed'); element.classList.remove('pending'); const numberElement = element.querySelector('[data-kt-stepper-number]'); if (numberElement) numberElement.innerHTML = String(index + 1); if (index + 1 == this._activeStep) { element.classList.add('active'); contentElement.classList.remove( this._getOption('hiddenClass') as string, ); } else { contentElement.classList.add(this._getOption('hiddenClass') as string); if (index + 1 < this._activeStep) { element.classList.add('completed'); } else { element.classList.add('pending'); } } }); } protected _getItemElements(): Array { const elements: Array = []; this._element .querySelectorAll('[data-kt-stepper-item]') .forEach((element) => { if (KTDom.isVisible(element as HTMLElement)) { elements.push(element as HTMLElement); } }); return elements; } protected async _go(step: number): Promise { if (step === this._activeStep || step > this._getTotalSteps() || step < 0) return; const payload = { step: step, cancel: false }; await this._fireEvent('change', payload); this._dispatchEvent('change', payload); if (payload.cancel === true) { return; } this._activeStep = step; this._update(); this._fireEvent('changed'); this._dispatchEvent('changed'); } protected _goTo(itemElement: HTMLElement): void { const step = this._getStep(itemElement); this._go(step); } protected _getStep(itemElement: HTMLElement): number { let step = -1; this._getItemElements().forEach((element, index) => { if (element === itemElement) { step = index + 1; return; } }); return step; } protected _getItemElement(step: number) { return this._getItemElements()[step - 1]; } protected _getTotalSteps(): number { return this._getItemElements().length; } protected _goNext(): void { let step; if (this._getTotalSteps() >= this._activeStep + 1) { step = this._activeStep + 1; } else { step = this._getTotalSteps(); } this._go(step); } protected _goBack(): void { let step; if (this._activeStep - 1 > 1) { step = this._activeStep - 1; } else { step = 1; } this._go(step); } protected _goLast(): void { const step = this._getTotalSteps(); this._go(step); } protected _goFirst(): void { const step = 1; this._go(step); } protected _isLast(): boolean { return this._getTotalSteps() === this._activeStep + 1; } protected _isFirst(): boolean { return this._activeStep === 1; } public isLast(): boolean { return this._isLast(); } public isFirst(): boolean { return this._isFirst(); } public go(step: number): void { this._go(step); } public goTo(itemElement: HTMLElement): void { this.goTo(itemElement); } public goFirst(): void { this._goFirst(); } public goLast(): void { this._goLast(); } public goNext(): void { this._goNext(); } public goBack(): void { this._goBack(); } public update(): void { this._update(); } public getStep(itemElement: HTMLElement): number { return this._getStep(itemElement); } public getItemElement(step: number): HTMLElement { return this._getItemElement(step); } public getTotalSteps(): number { return this._getTotalSteps(); } public getItemElements(): Array { return this._getItemElements(); } public static getInstance(element: HTMLElement): KTStepper { if (!element) return null; if (KTData.has(element, 'stepper')) { return KTData.get(element, 'stepper') as KTStepper; } if (element.getAttribute('data-kt-stepper')) { return new KTStepper(element); } return null; } public static getOrCreateInstance( element: HTMLElement, config?: KTStepperConfigInterface, ): KTStepper | null { return this.getInstance(element) || new KTStepper(element, config); } public static createInstances(): void { const elements = document.querySelectorAll('[data-kt-stepper]'); elements.forEach((element) => { new KTStepper(element as HTMLElement); }); } public static init(): void { KTStepper.createInstances(); } } if (typeof window !== 'undefined') { window.KTStepper = KTStepper; }