UNPKG

gov-gui

Version:

Gov UI Component Library Typscript Build

535 lines (534 loc) 20.8 kB
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