@coreui/react-pro
Version:
UI Components Library for React.js
242 lines (238 loc) • 12 kB
JavaScript
;
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