UNPKG

@coreui/coreui-pro

Version:

The most popular front-end framework for developing responsive, mobile-first projects on the web rewritten by the CoreUI Team

570 lines (553 loc) 20.2 kB
/*! * CoreUI stepper.js v5.23.0 (https://coreui.io) * Copyright 2025 The CoreUI Team (https://github.com/orgs/coreui/people) * Licensed under MIT (https://github.com/coreui/coreui/blob/main/LICENSE) */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('./base-component.js'), require('./dom/event-handler.js'), require('./dom/manipulator.js'), require('./dom/selector-engine.js'), require('./util/index.js')) : typeof define === 'function' && define.amd ? define(['./base-component', './dom/event-handler', './dom/manipulator', './dom/selector-engine', './util/index'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Stepper = factory(global.BaseComponent, global.EventHandler, global.Manipulator, global.SelectorEngine, global.Index)); })(this, (function (BaseComponent, EventHandler, Manipulator, SelectorEngine, index_js) { 'use strict'; /** * -------------------------------------------------------------------------- * CoreUI PRO stepper.js * License (https://coreui.io/pro/license/) * -------------------------------------------------------------------------- */ /** * Constants */ const NAME = 'stepper'; const DATA_KEY = 'coreui.stepper'; const EVENT_KEY = `.${DATA_KEY}`; const EVENT_FINISH = `finish${EVENT_KEY}`; const EVENT_RESET = `reset${EVENT_KEY}`; const EVENT_STEP_CHANGE = `stepChange${EVENT_KEY}`; const EVENT_STEP_VALIDATION_COMPLETE = `stepValidationComplete${EVENT_KEY}`; const EVENT_CLICK_DATA_API = `click${EVENT_KEY}`; const EVENT_KEYDOWN = `keydown${EVENT_KEY}`; const EVENT_LOAD_DATA_API = `load${EVENT_KEY}`; const CLASS_NAME_ACTIVE = 'active'; const CLASS_NAME_COMPLETE = 'complete'; const CLASS_NAME_SHOW = 'show'; const CLASS_NAME_STEPPER_STEP_CONNECTOR = 'stepper-step-connector'; const CLASS_NAME_STEPPER_STEP_INDICATOR_ICON = 'stepper-step-indicator-icon'; const CLASS_NAME_STEPPER_STEP_INDICATOR_TEXT = 'stepper-step-indicator-text'; const SELECTOR_DATA_TOGGLE = '[data-coreui-toggle="stepper"]'; const SELECTOR_STEPPER = '.stepper'; const SELECTOR_STEPPER_ACTION = '[data-coreui-stepper-action]'; const SELECTOR_STEPPER_STEP = '.stepper-step'; const SELECTOR_STEPPER_STEP_BUTTON = '.stepper-step-button'; const SELECTOR_STEPPER_STEP_CONTENT = '.stepper-step-content'; const SELECTOR_STEPPER_STEP_INDICATOR = '.stepper-step-indicator'; const SELECTOR_STEPPER_STEP_INDICATOR_ICON = '.stepper-step-indicator-icon'; const SELECTOR_STEPPER_STEPS = '.stepper-steps'; const SELECTOR_STEPPER_PANE = '.stepper-pane'; const ARROW_LEFT_KEY = 'ArrowLeft'; const ARROW_RIGHT_KEY = 'ArrowRight'; const ARROW_UP_KEY = 'ArrowUp'; const ARROW_DOWN_KEY = 'ArrowDown'; const HOME_KEY = 'Home'; const END_KEY = 'End'; const Default = { linear: true, skipValidation: false }; const DefaultType = { linear: 'boolean', skipValidation: 'boolean' }; /** * Class definition */ class Stepper extends BaseComponent { constructor(element, config) { super(element, config); this._stepButtons = this._getStepButtons(); this._activeStepButton = this._getActiveElem(); this._initialStepButton = this._activeStepButton; this._isFinished = false; this._addStepperConnector(); this._resetPanes(this._getTargetPane(this._activeStepButton)); this._wrapIndicatorText(); this._setInitialComplete(); this._updateStepButtonsDisabledState(); this._setupAccessibilityAttributes(); EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event)); } // Getters static get Default() { return Default; } static get DefaultType() { return DefaultType; } static get NAME() { return NAME; } // Public showStep(buttonOrStepNumber) { let button = buttonOrStepNumber; if (typeof buttonOrStepNumber === 'number') { button = this._stepButtons[buttonOrStepNumber - 1]; } if (!button) { return; } const active = this._getActiveElem(); if (active && !this._isCurrentStepValid(active)) { return; } if (this._elemIsActive(button)) { return; } if (this._config.linear) { const steps = this._getEnabledStepButtons(); const targetIndex = steps.indexOf(button); const activeIndex = steps.indexOf(active); if (targetIndex > activeIndex + 1) { return; } } const index = this._stepButtons.indexOf(button) + 1; EventHandler.trigger(this._element, EVENT_STEP_CHANGE, { index }); this._activeStepButton = button; this._deactivate(active); this._activate(button); this._updateStepButtonsDisabledState(); this._complete(button); } next() { if (this._isFinished) { return; } if (!this._isCurrentStepValid(this._getActiveElem())) { return; } const steps = this._getEnabledStepButtons(); const active = this._getActiveElem(); const index = steps.indexOf(active); const next = steps[index + 1]; if (next) { this.showStep(next); } } prev() { if (this._isFinished) { return; } const steps = this._getEnabledStepButtons(); const active = this._getActiveElem(); const index = steps.indexOf(active); const prev = steps[index - 1]; if (prev) { this.showStep(prev); } } finish() { if (this._isFinished) { return; } if (!this._isCurrentStepValid(this._getActiveElem())) { return; } const steps = this._getEnabledStepButtons(); const active = this._getActiveElem(); const index = steps.indexOf(active); if (index !== steps.length - 1) { const next = steps[index + 1]; if (next) { this.showStep(next); } return; } const finishHandler = () => { active.classList.remove(CLASS_NAME_ACTIVE); this._markAsComplete(active); EventHandler.trigger(this._element, EVENT_FINISH); this._isFinished = true; this._disableStepButtons(); }; const pane = this._getTargetPane(active); const stepContent = active.parentNode.querySelector(SELECTOR_STEPPER_STEP_CONTENT); if (pane) { pane.classList.remove(CLASS_NAME_ACTIVE, CLASS_NAME_SHOW); finishHandler(); } else if (stepContent) { this._animateHeight(stepContent, false, finishHandler); } else { finishHandler(); } } reset() { const steps = this._getEnabledStepButtons(); if (!steps.length) { return; } for (const pane of SelectorEngine.find(SELECTOR_STEPPER_PANE, this._element)) { pane.classList.remove(CLASS_NAME_ACTIVE, CLASS_NAME_SHOW); pane.setAttribute('aria-hidden', 'true'); } for (const content of SelectorEngine.find(SELECTOR_STEPPER_STEP_CONTENT, this._element)) { content.classList.remove(CLASS_NAME_ACTIVE, CLASS_NAME_SHOW); content.setAttribute('aria-hidden', 'true'); } for (const btn of steps) { btn.classList.remove(CLASS_NAME_ACTIVE, CLASS_NAME_COMPLETE); this._removeIndicatorIcon(btn); btn.disabled = false; } for (const form of this._element.querySelectorAll(`${SELECTOR_STEPPER_PANE} form, ${SELECTOR_STEPPER_STEP_CONTENT} form`)) { form.reset(); } const firstStep = this._initialStepButton || steps[0]; firstStep.classList.add(CLASS_NAME_ACTIVE); const pane = this._getTargetPane(firstStep); if (pane) { pane.classList.add(CLASS_NAME_ACTIVE, CLASS_NAME_SHOW); pane.setAttribute('aria-hidden', 'false'); } else { const stepContent = firstStep.parentNode.querySelector(SELECTOR_STEPPER_STEP_CONTENT); if (stepContent) { stepContent.classList.add(CLASS_NAME_ACTIVE, CLASS_NAME_SHOW); stepContent.setAttribute('aria-hidden', 'false'); } } this._updateCompleteStates(this._stepButtons.indexOf(firstStep)); this._activeStepButton = firstStep; this._isFinished = false; this._updateStepButtonsDisabledState(); EventHandler.trigger(this._element, EVENT_RESET); } // Private _getStepButtons() { return SelectorEngine.find(SELECTOR_STEPPER_STEP_BUTTON, this._element); } _getEnabledStepButtons() { return this._getStepButtons().filter(el => !index_js.isDisabled(el)); } _getActiveElem() { return this._stepButtons.find(child => this._elemIsActive(child)) || null; } _getTargetPane(element) { return SelectorEngine.getElementFromSelector(element); } _elemIsActive(elem) { return elem.classList.contains(CLASS_NAME_ACTIVE); } _isCurrentStepValid(element) { if (this._config.skipValidation) { return true; } const pane = this._getTargetPane(element); const target = pane != null ? pane : element.parentNode.querySelector(SELECTOR_STEPPER_STEP_CONTENT); if (!target) { return true; } const form = target.querySelector('form'); if (!form) { return true; } const isValid = form.checkValidity(); EventHandler.trigger(this._element, EVENT_STEP_VALIDATION_COMPLETE, { stepIndex: this._stepButtons.indexOf(element) + 1, isValid }); if (!isValid) { if (form.noValidate) { form.classList.add('was-validated'); } else { form.reportValidity(); } return false; } return true; } _activate(element) { if (!element) { return; } element.classList.add(CLASS_NAME_ACTIVE); element.setAttribute('aria-selected', 'true'); element.setAttribute('tabIndex', '0'); const pane = this._getTargetPane(element); if (pane) { pane.classList.add(CLASS_NAME_ACTIVE, CLASS_NAME_SHOW); pane.setAttribute('aria-hidden', 'false'); } const stepContentElement = SelectorEngine.findOne(SELECTOR_STEPPER_STEP_CONTENT, element.parentNode); if (stepContentElement) { this._animateHeight(stepContentElement, true); } } _deactivate(element) { this._resetPanes(); if (!element) { return; } element.setAttribute('aria-selected', 'false'); element.setAttribute('tabIndex', '-1'); const stepContentElement = SelectorEngine.findOne(SELECTOR_STEPPER_STEP_CONTENT, element.parentNode); if (stepContentElement) { this._animateHeight(stepContentElement, false, () => element.classList.remove(CLASS_NAME_ACTIVE)); } else { element.classList.remove(CLASS_NAME_ACTIVE); } } _complete(activeBtn) { const stepsContainer = activeBtn.closest(SELECTOR_STEPPER_STEPS) || document; const steps = SelectorEngine.find(SELECTOR_STEPPER_STEP, stepsContainer); const activeStepIdx = steps.indexOf(activeBtn.parentNode); if (activeStepIdx === -1) { return; } this._updateCompleteStates(activeStepIdx); } _markAsComplete(button) { const activeStep = button.closest(SELECTOR_STEPPER_STEP); if (activeStep) { const stepButton = SelectorEngine.findOne(SELECTOR_STEPPER_STEP_BUTTON, activeStep); if (stepButton) { stepButton.classList.add(CLASS_NAME_COMPLETE); this._appendIndicatorIcon(stepButton); } } } _updateCompleteStates(activeIndex) { for (const [idx, stepButton] of this._stepButtons.entries()) { const isComplete = idx < activeIndex; stepButton.classList.toggle(CLASS_NAME_COMPLETE, isComplete); if (isComplete) { this._appendIndicatorIcon(stepButton); } else { this._removeIndicatorIcon(stepButton); } } } _setInitialComplete() { const steps = SelectorEngine.find(SELECTOR_STEPPER_STEP, this._element); const activeBtn = this._getActiveElem(); if (!activeBtn) { return; } const activeIdx = steps.indexOf(activeBtn.closest(SELECTOR_STEPPER_STEP)); if (activeIdx === -1) { return; } this._updateCompleteStates(activeIdx); } _appendIndicatorIcon(button) { const indicator = SelectorEngine.findOne(SELECTOR_STEPPER_STEP_INDICATOR, button); if (indicator && !SelectorEngine.findOne(SELECTOR_STEPPER_STEP_INDICATOR_ICON, indicator)) { const icon = document.createElement('span'); icon.classList.add(CLASS_NAME_STEPPER_STEP_INDICATOR_ICON); indicator.append(icon); } } _removeIndicatorIcon(button) { const indicator = SelectorEngine.findOne(SELECTOR_STEPPER_STEP_INDICATOR, button); if (!indicator) { return; } const icon = SelectorEngine.findOne(SELECTOR_STEPPER_STEP_INDICATOR_ICON, indicator); if (icon) { icon.remove(); } } _updateStepButtonsDisabledState() { const activeIndex = this._stepButtons.indexOf(this._activeStepButton); for (const [index, button] of this._stepButtons.entries()) { button.disabled = this._config.linear && index > activeIndex + 1; } } _disableStepButtons() { for (const stepButton of this._stepButtons) { stepButton.disabled = true; } } _animateHeight(element, expand, callback) { const startHeight = expand ? 0 : element.scrollHeight; const endHeight = expand ? element.scrollHeight : 0; element.style.height = `${startHeight}px`; element.style.overflow = 'hidden'; // ensure reflow // eslint-disable-next-line no-unused-expressions element.offsetHeight; requestAnimationFrame(() => { element.style.height = `${endHeight}px`; this._queueCallback(() => { element.style.overflow = 'initial'; if (expand) { element.style.height = 'auto'; } callback == null || callback(); }, element, true); }); } _resetPanes(activePane = null) { for (const pane of SelectorEngine.find(SELECTOR_STEPPER_PANE, this._element)) { const isActive = pane === activePane; pane.classList.toggle(CLASS_NAME_ACTIVE, isActive); pane.classList.toggle(CLASS_NAME_SHOW, isActive); pane.setAttribute('aria-hidden', !isActive); } } _addStepperConnector() { for (const [index, stepButton] of this._stepButtons.entries()) { if (index < this._stepButtons.length - 1) { const next = stepButton.nextElementSibling; if (!next || !next.classList.contains(CLASS_NAME_STEPPER_STEP_CONNECTOR)) { const connectorElement = document.createElement('div'); connectorElement.classList.add(CLASS_NAME_STEPPER_STEP_CONNECTOR); stepButton.after(connectorElement); } } } } _wrapIndicatorText() { for (const stepButton of this._stepButtons) { const indicator = SelectorEngine.findOne(SELECTOR_STEPPER_STEP_INDICATOR, stepButton); if (!indicator) { continue; } const childNodes = Array.from(indicator.childNodes); const visibleNodes = childNodes.filter(node => { if (node.nodeType === Node.TEXT_NODE) { return node.textContent.trim() !== ''; } if (node.nodeType === Node.ELEMENT_NODE) { return true; } return false; }); if (visibleNodes.length !== 1 || visibleNodes[0].nodeType !== Node.TEXT_NODE) { continue; } const textNode = visibleNodes[0]; const wrapper = document.createElement('span'); wrapper.classList.add(CLASS_NAME_STEPPER_STEP_INDICATOR_TEXT); wrapper.textContent = textNode.textContent.trim(); textNode.replaceWith(wrapper); } } _setupAccessibilityAttributes() { const uId = index_js.getUID(this.constructor.NAME).toString(); for (const [index, stepButton] of this._stepButtons.entries()) { const parentStepItem = stepButton.closest(SELECTOR_STEPPER_STEP); if (parentStepItem) { parentStepItem.setAttribute('role', 'presentation'); } stepButton.setAttribute('role', 'tab'); if (!stepButton.id) { stepButton.id = `${uId}${index + 1}`; } const pane = SelectorEngine.getElementFromSelector(stepButton); if (pane) { stepButton.setAttribute('aria-controls', pane.id); pane.setAttribute('role', 'tabpanel'); pane.setAttribute('aria-labelledby', stepButton.id); pane.setAttribute('aria-live', 'polite'); pane.setAttribute('aria-hidden', !this._elemIsActive(stepButton)); } if (this._elemIsActive(stepButton)) { stepButton.setAttribute('aria-selected', 'true'); stepButton.setAttribute('tabIndex', '0'); } else { stepButton.setAttribute('aria-selected', 'false'); stepButton.setAttribute('tabIndex', '-1'); } } } _keydown(event) { var _nextActiveElement; if (![ARROW_LEFT_KEY, ARROW_RIGHT_KEY, ARROW_UP_KEY, ARROW_DOWN_KEY, HOME_KEY, END_KEY].includes(event.key)) { return; } event.stopPropagation(); event.preventDefault(); const children = this._getEnabledStepButtons(); let nextActiveElement; switch (event.key) { case HOME_KEY: { nextActiveElement = children[0]; break; } case END_KEY: { nextActiveElement = children[children.length - 1]; break; } case ARROW_RIGHT_KEY: case ARROW_DOWN_KEY: { nextActiveElement = index_js.getNextActiveElement(children, event.target, true, true); break; } case ARROW_LEFT_KEY: case ARROW_UP_KEY: { nextActiveElement = index_js.getNextActiveElement(children, event.target, false, true); break; } } (_nextActiveElement = nextActiveElement) == null || _nextActiveElement.focus({ preventScroll: true }); } // Static static jQueryInterface(config) { return this.each(function () { const data = Stepper.getOrCreateInstance(this); if (typeof config !== 'string') { return; } if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { throw new TypeError(`No method named "${config}"`); } data[config](); }); } } /** * Data API implementation */ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_STEPPER_STEP_BUTTON, function (event) { if (['A', 'AREA'].includes(this.tagName)) { event.preventDefault(); } if (index_js.isDisabled(this)) { return; } const stepperElement = this.closest(SELECTOR_STEPPER); if (!stepperElement) { return; } const stepper = Stepper.getOrCreateInstance(stepperElement); stepper.showStep(this); }); EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_STEPPER_ACTION, function () { const action = Manipulator.getDataAttribute(this, 'stepper-action'); const stepperElement = this.closest(SELECTOR_STEPPER); if (!stepperElement) { return; } const stepper = Stepper.getOrCreateInstance(stepperElement); if (stepper && typeof stepper[action] === 'function') { stepper[action](); } }); EventHandler.on(window, EVENT_LOAD_DATA_API, () => { for (const element of SelectorEngine.find(SELECTOR_DATA_TOGGLE)) { Stepper.getOrCreateInstance(element); } }); /** * jQuery integration */ index_js.defineJQueryPlugin(Stepper); return Stepper; })); //# sourceMappingURL=stepper.js.map