UNPKG

@coreui/react-pro

Version:

UI Components Library for React.js

242 lines (238 loc) 12 kB
'use strict'; var tslib_es6 = require('../../node_modules/tslib/tslib.es6.js'); var React = require('react'); var PropTypes = require('prop-types'); var index = require('../../_virtual/index.js'); var CCollapse = require('../collapse/CCollapse.js'); var getNextActiveElement = require('../../utils/getNextActiveElement.js'); const CStepper = React.forwardRef((_a, ref) => { var { activeStepNumber: controlledActiveStepNumber, activeStepIndex: controlledActiveStepIndex, className, defaultActiveStepNumber, defaultActiveStepIndex = 0, layout = 'horizontal', linear = true, onFinish, onReset, onStepChange, onStepValidationComplete, steps = [], stepButtonLayout = 'horizontal', validation = true } = _a, rest = tslib_es6.__rest(_a, ["activeStepNumber", "activeStepIndex", "className", "defaultActiveStepNumber", "defaultActiveStepIndex", "layout", "linear", "onFinish", "onReset", "onStepChange", "onStepValidationComplete", "steps", "stepButtonLayout", "validation"]); const stepperRef = React.useRef(null); const stepsRef = React.useRef(null); const stepButtonRefs = React.useRef([]); const uniqueId = React.useId(); // Handle backward compatibility and determine controlled vs uncontrolled mode const isControlledByNumber = controlledActiveStepNumber !== undefined; const isControlledByIndex = controlledActiveStepIndex !== undefined; const isControlled = isControlledByNumber || isControlledByIndex; // Convert between step numbers (1-based) and indices (0-based) const getDefaultActiveStepIndex = () => { if (defaultActiveStepNumber !== undefined) { return Math.max(0, defaultActiveStepNumber - 1); } return defaultActiveStepIndex; }; const getControlledActiveStepIndex = () => { if (isControlledByNumber) { return Math.max(0, controlledActiveStepNumber - 1); } if (isControlledByIndex) { return controlledActiveStepIndex; } return 0; }; const [internalActiveStepIndex, setInternalActiveStepIndex] = React.useState(isControlled ? getControlledActiveStepIndex() : getDefaultActiveStepIndex()); const [isFinished, setIsFinished] = React.useState(false); const activeStepIndex = isControlled ? getControlledActiveStepIndex() : internalActiveStepIndex; // Sync state if controlled prop changes React.useEffect(() => { if (isControlled) { setInternalActiveStepIndex(getControlledActiveStepIndex()); } }, [controlledActiveStepNumber, controlledActiveStepIndex, isControlled]); // Ensure stepButtonRefs array has the correct size React.useEffect(() => { stepButtonRefs.current = stepButtonRefs.current.slice(0, steps.length); }, [steps.length]); const isStepValid = React.useCallback((index) => { var _a; if (!validation) { return true; } const currentStep = steps[index]; const currentStepData = typeof currentStep === 'string' ? { } : currentStep; const form = (_a = currentStepData === null || currentStepData === void 0 ? void 0 : currentStepData.formRef) === null || _a === void 0 ? void 0 : _a.current; if (form) { const isValid = form.checkValidity(); onStepValidationComplete === null || onStepValidationComplete === void 0 ? void 0 : onStepValidationComplete({ stepNumber: index + 1, isValid, }); if (form && !isValid) { if (!form.noValidate) { form.reportValidity(); } return false; } } return true; }, [steps, validation, onStepValidationComplete]); const setActiveStep = React.useCallback((index, bypassValidation = false) => { if (index < 0 || index >= steps.length || index === activeStepIndex) { return; } // Validate current step before moving forward if (!bypassValidation && index > activeStepIndex && !isStepValid(activeStepIndex)) { return; } if (!isControlled) { setInternalActiveStepIndex(index); } onStepChange === null || onStepChange === void 0 ? void 0 : onStepChange(index + 1); // Always call with step number (1-based) }, [steps.length, activeStepIndex, isStepValid, isControlled, onStepChange]); const handleStepClick = (index) => { if (linear) { setActiveStep(index, index <= activeStepIndex); return; } setActiveStep(index, true); }; const handleNext = () => { if (activeStepIndex < steps.length - 1) { setActiveStep(activeStepIndex + 1); } else { handleFinish(); // Attempt finish if already on last step } }; const handlePrev = () => { if (activeStepIndex > 0) { setActiveStep(activeStepIndex - 1, true); // Bypass validation when going back } }; const handleFinish = () => { if (activeStepIndex === steps.length - 1 && isStepValid(activeStepIndex)) { setIsFinished(true); onFinish === null || onFinish === void 0 ? void 0 : onFinish(); } }; const handleReset = () => { var _a; if (validation) { steps.forEach((step) => { var _a, _b; const stepData = typeof step === 'string' ? { } : step; (_b = (_a = stepData.formRef) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.reset(); }); } const resetIndex = getDefaultActiveStepIndex(); if (!isControlled) { setInternalActiveStepIndex(resetIndex); } setIsFinished(false); onStepChange === null || onStepChange === void 0 ? void 0 : onStepChange(resetIndex + 1); // Call with step number (1-based) onReset === null || onReset === void 0 ? void 0 : onReset(); (_a = stepButtonRefs.current[resetIndex]) === null || _a === void 0 ? void 0 : _a.focus(); }; const handleKeyDown = (event) => { const target = event.target; const currentButton = target.closest('button.stepper-step-button'); if (!currentButton || !stepsRef.current) { return; } const buttons = [ ...stepsRef.current.querySelectorAll('button.stepper-step-button'), ]; let nextElement = null; switch (event.key) { case 'ArrowRight': case 'ArrowDown': { nextElement = getNextActiveElement.default(buttons, currentButton, true, false); break; } case 'ArrowLeft': case 'ArrowUp': { nextElement = getNextActiveElement.default(buttons, currentButton, false, false); break; } case 'Home': { nextElement = buttons[0]; break; } case 'End': { nextElement = buttons.at(-1); break; } default: { return; } } if (nextElement) { event.preventDefault(); nextElement.focus(); } }; // Expose methods via ref React.useImperativeHandle(ref, () => ({ next: handleNext, prev: handlePrev, finish: handleFinish, reset: handleReset, })); const isVertical = layout === 'vertical'; return (React.createElement("div", Object.assign({ className: index.default('stepper', { 'stepper-vertical': isVertical, }, className), ref: stepperRef }, rest), React.createElement("ol", { className: "stepper-steps", "aria-orientation": isVertical ? 'vertical' : 'horizontal', ref: stepsRef, onKeyDown: handleKeyDown, role: "tablist" }, steps.map((step, index$1) => { var _a; const stepData = typeof step === 'string' ? { label: step } : step; const isActive = !isFinished && index$1 === activeStepIndex; const isComplete = isFinished || index$1 < activeStepIndex; const isDisabled = isFinished || (linear && index$1 > activeStepIndex + 1); const stepId = `stepper-${rest.id || uniqueId}-step-${index$1}`; const panelId = `stepper-${rest.id || uniqueId}-panel-${index$1}`; return (React.createElement("li", { key: index$1, className: index.default('stepper-step', stepButtonLayout), role: "presentation" }, React.createElement("button", Object.assign({ type: "button", className: index.default('stepper-step-button', { active: isActive, complete: isComplete, }), disabled: isDisabled, id: stepId, role: "tab", onClick: () => handleStepClick(index$1), ref: (el) => { stepButtonRefs.current[index$1] = el; }, "aria-selected": isActive }, (stepData.content && { 'aria-controls': panelId, }), { tabIndex: isActive ? 0 : -1 }), React.createElement("span", { className: "stepper-step-indicator" }, isComplete ? (React.createElement("span", { className: "stepper-step-indicator-icon" })) : (React.createElement("span", { className: "stepper-step-indicator-text" }, (_a = stepData.indicator) !== null && _a !== void 0 ? _a : index$1 + 1))), React.createElement("span", { className: "stepper-step-label" }, stepData.label)), index$1 < steps.length - 1 && React.createElement("div", { className: "stepper-step-connector" }), stepData.content && isVertical && (React.createElement(CCollapse.CCollapse, { className: "stepper-step-content", id: panelId, role: "tabpanel", visible: isActive, "aria-hidden": !isActive, "aria-labelledby": stepId, "aria-live": "polite" }, stepData.content)))); })), !isVertical && steps.some((step) => { const stepData = typeof step === 'string' ? { } : step; return stepData.content !== undefined && stepData.content !== null; }) && (React.createElement("div", { className: "stepper-content" }, steps.map((step, index$1) => { const stepData = typeof step === 'string' ? { } : step; const isActive = !isFinished && index$1 === activeStepIndex; const stepId = `stepper-${rest.id || uniqueId}-step-${index$1}`; const panelId = `stepper-${rest.id || uniqueId}-panel-${index$1}`; return (React.createElement("div", { key: index$1, className: index.default('stepper-pane', { show: isActive, active: isActive, }), id: panelId, role: "tabpanel", "aria-hidden": !isActive, "aria-labelledby": stepId, "aria-live": "polite" }, stepData.content)); }))))); }); CStepper.displayName = 'CStepper'; CStepper.propTypes = { activeStepNumber: PropTypes.number, activeStepIndex: PropTypes.number, className: PropTypes.string, defaultActiveStepNumber: PropTypes.number, defaultActiveStepIndex: PropTypes.number, layout: PropTypes.oneOf(['horizontal', 'vertical']), linear: PropTypes.bool, onFinish: PropTypes.func, onReset: PropTypes.func, onStepChange: PropTypes.func, onStepValidationComplete: PropTypes.func, steps: PropTypes.arrayOf(PropTypes.oneOfType([ PropTypes.string, PropTypes.shape({ label: PropTypes.node.isRequired, content: PropTypes.node, formRef: PropTypes.object, // Check for object shape might be better }).isRequired, ])).isRequired, stepButtonLayout: PropTypes.oneOf(['horizontal', 'vertical']), validation: PropTypes.bool, }; exports.CStepper = CStepper; //# sourceMappingURL=CStepper.js.map