gov-gui
Version:
Gov UI Component Library Typscript Build
535 lines (534 loc) • 20.8 kB
JavaScript
import { h } from "@stencil/core";
import { getGlobalPropsClasses } from "../../global/global-styles-helper";
import { getAnimationClasses } from "../../global/animation-helpers";
export class GovStepper {
constructor() {
this.currentStep = 0;
this.steps = [];
this.isSubmitting = false;
this.submitStatus = 'idle';
this.stepErrorMessage = ''; // Holds error messages for the current step
this.variant = 'horizontal';
this.nextText = 'Next';
this.prevText = 'Previous';
this.submitText = 'Submit';
this.resetOnSubmit = true; // Reset on submit by default
this.maxStepsVisible = 5; // For horizontal variant
this.animationDelay = '2s';
this.allClasses = '';
}
handleStepChange(newValue) {
this.stepChanged.emit(newValue);
}
//watching for any change in animations to trigger them
watchAnimations() {
this.provideClass();
}
watchAnimationsDelay() {
this.provideClass();
}
watchAnimationsSpeed() {
this.provideClass();
}
componentWillLoad() {
// Collect all step slots (assuming slots are named step-0, step-1, etc.)
const slotElements = this.el.querySelectorAll('[slot^="step-"]');
this.steps = Array.from(slotElements).map((_, index) => `Step ${index + 1}`);
const animationClasses = getAnimationClasses({
animation: this.animation,
animationDelay: this.animationDelay,
animationSpeed: this.animationSpeed
});
this.allClasses = getGlobalPropsClasses({
classes: ' ' + animationClasses,
});
}
//Called on change of any animation related property to trigger change
provideClass() {
const animationClasses = getAnimationClasses({
animation: this.animation,
animationDelay: this.animationDelay,
animationSpeed: this.animationSpeed
});
this.allClasses = getGlobalPropsClasses({
classes: ' ' + animationClasses,
});
}
async handleStepTransition(direction) {
try {
if (direction === 'next' && this.currentStep < this.steps.length - 1) {
// Validate current step's inputs before moving to next step.
if (!this.areInputsValid(this.currentStep))
return;
if (this.validateStep && !(await this.validateStep(this.currentStep)))
return;
// Clear any previous error messages once validation passes
this.stepErrorMessage = '';
this.currentStep += 1;
}
else if (direction === 'prev' && this.currentStep > 0) {
this.currentStep -= 1;
}
}
catch (error) {
this.submitStatus = 'error';
this.stepError.emit(error);
}
}
async handleSubmit() {
try {
this.isSubmitting = true;
// Collect input values from all custom input components.
const formData = this.collectInputData();
console.log('Collected Data:', formData); // Debugging
// Emit the collected data.
this.stepSubmitted.emit(formData);
// Call onSubmit handler if provided.
if (this.onSubmit) {
await this.onSubmit();
}
// Reset inputs if resetOnSubmit is true.
if (this.resetOnSubmit) {
this.resetInputs();
this.currentStep = 0;
this.submitStatus = 'success';
setTimeout(() => {
this.submitStatus = 'idle'; // Reset status after showing success message.
}, 2000);
}
}
catch (error) {
this.submitStatus = 'error';
this.stepError.emit(error);
}
finally {
this.isSubmitting = false;
}
}
/**
* Helper: Returns the current value from a custom component.
* For gov-input and gov-radiobutton, it attempts to use the public 'value' property;
* for gov-checkbox, it returns the public 'checked' property.
*/
getValueFromComponent(component) {
const comp = component;
const tag = component.tagName.toLowerCase();
if (tag === 'gov-input') {
return comp.value !== undefined ? comp.value : component.getAttribute('value') || '';
}
else if (tag === 'gov-radiobutton') {
return comp.value !== undefined ? comp.value : '';
}
else if (tag === 'gov-checkbox') {
return comp.checked;
}
return null;
}
/**
* Collects input data from all custom components (gov-input, gov-radiobutton, gov-checkbox)
* present in the stepper.
*/
collectInputData() {
const components = this.el.querySelectorAll('gov-input, gov-radiobutton, gov-checkbox');
const formData = {};
components.forEach(component => {
const name = component.getAttribute('name');
if (!name)
return;
const value = this.getValueFromComponent(component);
if (value !== null && value !== undefined) {
// For checkboxes, include only if checked.
if (component.tagName.toLowerCase() === 'gov-checkbox') {
if (value) {
formData[name] = component.getAttribute('value') || 'true';
}
}
else {
formData[name] = String(value);
}
}
});
return formData;
}
/**
* Validates all required fields within the current step.
* This queries for custom elements marked with [required] and uses the helper function
* to determine if they have a non-empty value (or are checked, for checkboxes).
* If there are validation errors, an error message is stored in `stepErrorMessage`.
*/
areInputsValid(stepIndex) {
const stepSlot = this.el.querySelector(`[slot="step-${stepIndex}"]`);
if (!stepSlot)
return false;
// Query custom elements that are marked as required.
const requiredComponents = stepSlot.querySelectorAll('gov-input[required], gov-radiobutton[required], gov-checkbox[required]');
let allValid = true;
const messages = [];
requiredComponents.forEach(component => {
const tag = component.tagName.toLowerCase();
// Use the 'name' attribute for a friendly label.
const name = component.getAttribute('name') || 'This field';
const value = this.getValueFromComponent(component);
if (tag === 'gov-input' || tag === 'gov-radiobutton') {
if (!value || (typeof value === 'string' && value.trim() === '')) {
allValid = false;
messages.push(`${name} is required.`);
}
}
else if (tag === 'gov-checkbox') {
if (!value) {
allValid = false;
messages.push(`You must agree to ${name}.`);
}
}
});
// Update the state error message for display in the step.
this.stepErrorMessage = allValid ? '' : messages.join(' ');
return allValid;
}
/**
* Resets all custom input components by calling their reset() method, if available.
*/
resetInputs() {
const components = this.el.querySelectorAll('gov-input, gov-radiobutton, gov-checkbox');
components.forEach(component => {
if (typeof component.reset === 'function') {
component.reset();
}
});
}
render() {
const isLastStep = this.currentStep === this.steps.length - 1;
const statusClass = this.submitStatus !== 'idle' ? 'visible' : '';
return (h("div", { key: '58d4907362a380fc43d5728faa3d5127637c6246', class: `stepper-container ${this.allClasses}` }, this.isSubmitting && (h("div", { key: '29c734572f77034d4c982dea2e53d57d6b9f0b55', class: "loading-overlay" }, h("gov-spinner", { key: 'c3647643af2ee246a83df3aaed92d4a9e71b6705', size: "lg" }))), h("div", { key: '1e31e5b73200a8db98418abd5a0abee91a45ffe8', class: { stepper: true, [this.variant]: true } }, this.steps.map((_, index) => (h("div", { class: {
step: true,
active: index === this.currentStep,
completed: index < this.currentStep,
[this.variant]: true,
} }, h("div", { class: "step-number" }, index + 1), this.variant === 'vertical' && index === this.currentStep && (h("div", { class: "step-content" }, h("slot", { name: `step-${this.currentStep}` }))))))), this.variant === 'horizontal' && (h("div", { key: 'caa3176e98cfcc54eb133258fdf307a02a8fcb27', class: "step-content" }, h("slot", { key: '3061b1af7f62a87caddff6d74fe795ea3eb2d61d', name: `step-${this.currentStep}` }), this.stepErrorMessage && (h("div", { key: 'c7c759a13313c2c0098887a1dd68b1aa99951964', class: "step-error" }, this.stepErrorMessage)))), h("div", { key: 'b8c03eae81fb47a3e86560eec4afd57a8da6485b', class: "stepper-buttons" }, h("gov-button", { key: '0c6f7dbdba895d7752f3021da060b551f72dbe5e', variant: "white", disabled: this.currentStep === 0 || this.isSubmitting, onClick: () => this.handleStepTransition('prev'), label: this.prevText }), h("gov-button", { key: 'bd903668fc6b44740319049c9787a99aa809b859', variant: "primary", disabled: this.isSubmitting, onClick: isLastStep
? () => this.handleSubmit()
: () => this.handleStepTransition('next'), label: isLastStep ? this.submitText : this.nextText })), h("div", { key: '9feafa94a48fb96b4bfd2b8709af8c6d2c299a69', class: `submit-status ${statusClass} status-${this.submitStatus}` }, h("slot", { key: '11e68977d8b24aa0d641a21ed6c0af5b931d20a2', name: this.submitStatus === 'success' ? 'success-message' : 'error-message' }, this.submitStatus === 'success' && 'Form submitted successfully!', this.submitStatus === 'error' && 'Submission failed. Please try again.'))));
}
static get is() { return "gov-stepper"; }
static get encapsulation() { return "shadow"; }
static get originalStyleUrls() {
return {
"$": ["gov-stepper.css"]
};
}
static get styleUrls() {
return {
"$": ["gov-stepper.css"]
};
}
static get properties() {
return {
"variant": {
"type": "string",
"mutable": false,
"complexType": {
"original": "'horizontal' | 'vertical'",
"resolved": "\"horizontal\" | \"vertical\"",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": ""
},
"getter": false,
"setter": false,
"attribute": "variant",
"reflect": false,
"defaultValue": "'horizontal'"
},
"validateStep": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "(step: number) => Promise<boolean> | boolean",
"resolved": "(step: number) => boolean | Promise<boolean>",
"references": {
"Promise": {
"location": "global",
"id": "global::Promise"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": ""
},
"getter": false,
"setter": false
},
"onSubmit": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "() => Promise<void>",
"resolved": "() => Promise<void>",
"references": {
"Promise": {
"location": "global",
"id": "global::Promise"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": ""
},
"getter": false,
"setter": false
},
"nextText": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": ""
},
"getter": false,
"setter": false,
"attribute": "next-text",
"reflect": false,
"defaultValue": "'Next'"
},
"prevText": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": ""
},
"getter": false,
"setter": false,
"attribute": "prev-text",
"reflect": false,
"defaultValue": "'Previous'"
},
"submitText": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": ""
},
"getter": false,
"setter": false,
"attribute": "submit-text",
"reflect": false,
"defaultValue": "'Submit'"
},
"resetOnSubmit": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": ""
},
"getter": false,
"setter": false,
"attribute": "reset-on-submit",
"reflect": false,
"defaultValue": "true"
},
"maxStepsVisible": {
"type": "number",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": ""
},
"getter": false,
"setter": false,
"attribute": "max-steps-visible",
"reflect": false,
"defaultValue": "5"
},
"animation": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": ""
},
"getter": false,
"setter": false,
"attribute": "animation",
"reflect": false
},
"animationDelay": {
"type": "string",
"mutable": false,
"complexType": {
"original": "'2s' | '3s' | '4s' | '5s'",
"resolved": "\"2s\" | \"3s\" | \"4s\" | \"5s\"",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": ""
},
"getter": false,
"setter": false,
"attribute": "animation-delay",
"reflect": false,
"defaultValue": "'2s'"
},
"animationSpeed": {
"type": "string",
"mutable": false,
"complexType": {
"original": "'slow' | 'slower' | 'fast' | 'faster'",
"resolved": "\"fast\" | \"faster\" | \"slow\" | \"slower\"",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": ""
},
"getter": false,
"setter": false,
"attribute": "animation-speed",
"reflect": false
}
};
}
static get states() {
return {
"currentStep": {},
"steps": {},
"isSubmitting": {},
"submitStatus": {},
"stepErrorMessage": {}
};
}
static get events() {
return [{
"method": "stepChanged",
"name": "stepChanged",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": ""
},
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
}
}, {
"method": "stepSubmitted",
"name": "stepSubmitted",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": ""
},
"complexType": {
"original": "Record<string, string>",
"resolved": "{ [x: string]: string; }",
"references": {
"Record": {
"location": "global",
"id": "global::Record"
}
}
}
}, {
"method": "stepError",
"name": "stepError",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": ""
},
"complexType": {
"original": "Error",
"resolved": "Error",
"references": {
"Error": {
"location": "global",
"id": "global::Error"
}
}
}
}];
}
static get elementRef() { return "el"; }
static get watchers() {
return [{
"propName": "currentStep",
"methodName": "handleStepChange"
}, {
"propName": "animation",
"methodName": "watchAnimations"
}, {
"propName": "animationDelay",
"methodName": "watchAnimationsDelay"
}, {
"propName": "animationSpeed",
"methodName": "watchAnimationsSpeed"
}];
}
}
//# sourceMappingURL=gov-stepper.js.map