UNPKG

form-preview-df

Version:

Resusable Form Preview Components

1,149 lines (1,131 loc) 373 kB
import React, { useState, useCallback, useEffect, useRef, forwardRef, createElement, useLayoutEffect, useMemo, createContext, useContext } from 'react'; import { useTranslation } from 'react-i18next'; import 'react-dom'; var jsxRuntime$1 = {exports: {}}; var reactJsxRuntime_production$1 = {}; /** * @license React * react-jsx-runtime.production.js * * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var hasRequiredReactJsxRuntime_production$1; function requireReactJsxRuntime_production$1 () { if (hasRequiredReactJsxRuntime_production$1) return reactJsxRuntime_production$1; hasRequiredReactJsxRuntime_production$1 = 1; var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"); function jsxProd(type, config, maybeKey) { var key = null; void 0 !== maybeKey && (key = "" + maybeKey); void 0 !== config.key && (key = "" + config.key); if ("key" in config) { maybeKey = {}; for (var propName in config) "key" !== propName && (maybeKey[propName] = config[propName]); } else maybeKey = config; config = maybeKey.ref; return { $$typeof: REACT_ELEMENT_TYPE, type: type, key: key, ref: void 0 !== config ? config : null, props: maybeKey }; } reactJsxRuntime_production$1.Fragment = REACT_FRAGMENT_TYPE; reactJsxRuntime_production$1.jsx = jsxProd; reactJsxRuntime_production$1.jsxs = jsxProd; return reactJsxRuntime_production$1; } var reactJsxRuntime_development$1 = {}; /** * @license React * react-jsx-runtime.development.js * * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var hasRequiredReactJsxRuntime_development$1; function requireReactJsxRuntime_development$1 () { if (hasRequiredReactJsxRuntime_development$1) return reactJsxRuntime_development$1; hasRequiredReactJsxRuntime_development$1 = 1; "production" !== process.env.NODE_ENV && (function () { function getComponentNameFromType(type) { if (null == type) return null; if ("function" === typeof type) return type.$$typeof === REACT_CLIENT_REFERENCE ? null : type.displayName || type.name || null; if ("string" === typeof type) return type; switch (type) { case REACT_FRAGMENT_TYPE: return "Fragment"; case REACT_PROFILER_TYPE: return "Profiler"; case REACT_STRICT_MODE_TYPE: return "StrictMode"; case REACT_SUSPENSE_TYPE: return "Suspense"; case REACT_SUSPENSE_LIST_TYPE: return "SuspenseList"; case REACT_ACTIVITY_TYPE: return "Activity"; } if ("object" === typeof type) switch ( ("number" === typeof type.tag && console.error( "Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue." ), type.$$typeof) ) { case REACT_PORTAL_TYPE: return "Portal"; case REACT_CONTEXT_TYPE: return type.displayName || "Context"; case REACT_CONSUMER_TYPE: return (type._context.displayName || "Context") + ".Consumer"; case REACT_FORWARD_REF_TYPE: var innerType = type.render; type = type.displayName; type || ((type = innerType.displayName || innerType.name || ""), (type = "" !== type ? "ForwardRef(" + type + ")" : "ForwardRef")); return type; case REACT_MEMO_TYPE: return ( (innerType = type.displayName || null), null !== innerType ? innerType : getComponentNameFromType(type.type) || "Memo" ); case REACT_LAZY_TYPE: innerType = type._payload; type = type._init; try { return getComponentNameFromType(type(innerType)); } catch (x) {} } return null; } function testStringCoercion(value) { return "" + value; } function checkKeyStringCoercion(value) { try { testStringCoercion(value); var JSCompiler_inline_result = !1; } catch (e) { JSCompiler_inline_result = !0; } if (JSCompiler_inline_result) { JSCompiler_inline_result = console; var JSCompiler_temp_const = JSCompiler_inline_result.error; var JSCompiler_inline_result$jscomp$0 = ("function" === typeof Symbol && Symbol.toStringTag && value[Symbol.toStringTag]) || value.constructor.name || "Object"; JSCompiler_temp_const.call( JSCompiler_inline_result, "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.", JSCompiler_inline_result$jscomp$0 ); return testStringCoercion(value); } } function getTaskName(type) { if (type === REACT_FRAGMENT_TYPE) return "<>"; if ( "object" === typeof type && null !== type && type.$$typeof === REACT_LAZY_TYPE ) return "<...>"; try { var name = getComponentNameFromType(type); return name ? "<" + name + ">" : "<...>"; } catch (x) { return "<...>"; } } function getOwner() { var dispatcher = ReactSharedInternals.A; return null === dispatcher ? null : dispatcher.getOwner(); } function UnknownOwner() { return Error("react-stack-top-frame"); } function hasValidKey(config) { if (hasOwnProperty.call(config, "key")) { var getter = Object.getOwnPropertyDescriptor(config, "key").get; if (getter && getter.isReactWarning) return !1; } return void 0 !== config.key; } function defineKeyPropWarningGetter(props, displayName) { function warnAboutAccessingKey() { specialPropKeyWarningShown || ((specialPropKeyWarningShown = !0), console.error( "%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)", displayName )); } warnAboutAccessingKey.isReactWarning = !0; Object.defineProperty(props, "key", { get: warnAboutAccessingKey, configurable: !0 }); } function elementRefGetterWithDeprecationWarning() { var componentName = getComponentNameFromType(this.type); didWarnAboutElementRef[componentName] || ((didWarnAboutElementRef[componentName] = !0), console.error( "Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release." )); componentName = this.props.ref; return void 0 !== componentName ? componentName : null; } function ReactElement(type, key, props, owner, debugStack, debugTask) { var refProp = props.ref; type = { $$typeof: REACT_ELEMENT_TYPE, type: type, key: key, props: props, _owner: owner }; null !== (void 0 !== refProp ? refProp : null) ? Object.defineProperty(type, "ref", { enumerable: !1, get: elementRefGetterWithDeprecationWarning }) : Object.defineProperty(type, "ref", { enumerable: !1, value: null }); type._store = {}; Object.defineProperty(type._store, "validated", { configurable: !1, enumerable: !1, writable: !0, value: 0 }); Object.defineProperty(type, "_debugInfo", { configurable: !1, enumerable: !1, writable: !0, value: null }); Object.defineProperty(type, "_debugStack", { configurable: !1, enumerable: !1, writable: !0, value: debugStack }); Object.defineProperty(type, "_debugTask", { configurable: !1, enumerable: !1, writable: !0, value: debugTask }); Object.freeze && (Object.freeze(type.props), Object.freeze(type)); return type; } function jsxDEVImpl( type, config, maybeKey, isStaticChildren, debugStack, debugTask ) { var children = config.children; if (void 0 !== children) if (isStaticChildren) if (isArrayImpl(children)) { for ( isStaticChildren = 0; isStaticChildren < children.length; isStaticChildren++ ) validateChildKeys(children[isStaticChildren]); Object.freeze && Object.freeze(children); } else console.error( "React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead." ); else validateChildKeys(children); if (hasOwnProperty.call(config, "key")) { children = getComponentNameFromType(type); var keys = Object.keys(config).filter(function (k) { return "key" !== k; }); isStaticChildren = 0 < keys.length ? "{key: someKey, " + keys.join(": ..., ") + ": ...}" : "{key: someKey}"; didWarnAboutKeySpread[children + isStaticChildren] || ((keys = 0 < keys.length ? "{" + keys.join(": ..., ") + ": ...}" : "{}"), console.error( 'A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />', isStaticChildren, children, keys, children ), (didWarnAboutKeySpread[children + isStaticChildren] = !0)); } children = null; void 0 !== maybeKey && (checkKeyStringCoercion(maybeKey), (children = "" + maybeKey)); hasValidKey(config) && (checkKeyStringCoercion(config.key), (children = "" + config.key)); if ("key" in config) { maybeKey = {}; for (var propName in config) "key" !== propName && (maybeKey[propName] = config[propName]); } else maybeKey = config; children && defineKeyPropWarningGetter( maybeKey, "function" === typeof type ? type.displayName || type.name || "Unknown" : type ); return ReactElement( type, children, maybeKey, getOwner(), debugStack, debugTask ); } function validateChildKeys(node) { isValidElement(node) ? node._store && (node._store.validated = 1) : "object" === typeof node && null !== node && node.$$typeof === REACT_LAZY_TYPE && ("fulfilled" === node._payload.status ? isValidElement(node._payload.value) && node._payload.value._store && (node._payload.value._store.validated = 1) : node._store && (node._store.validated = 1)); } function isValidElement(object) { return ( "object" === typeof object && null !== object && object.$$typeof === REACT_ELEMENT_TYPE ); } var React$1 = React, REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler"), REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy"), REACT_ACTIVITY_TYPE = Symbol.for("react.activity"), REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), ReactSharedInternals = React$1.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, hasOwnProperty = Object.prototype.hasOwnProperty, isArrayImpl = Array.isArray, createTask = console.createTask ? console.createTask : function () { return null; }; React$1 = { react_stack_bottom_frame: function (callStackForError) { return callStackForError(); } }; var specialPropKeyWarningShown; var didWarnAboutElementRef = {}; var unknownOwnerDebugStack = React$1.react_stack_bottom_frame.bind( React$1, UnknownOwner )(); var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner)); var didWarnAboutKeySpread = {}; reactJsxRuntime_development$1.Fragment = REACT_FRAGMENT_TYPE; reactJsxRuntime_development$1.jsx = function (type, config, maybeKey) { var trackActualOwner = 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++; return jsxDEVImpl( type, config, maybeKey, !1, trackActualOwner ? Error("react-stack-top-frame") : unknownOwnerDebugStack, trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask ); }; reactJsxRuntime_development$1.jsxs = function (type, config, maybeKey) { var trackActualOwner = 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++; return jsxDEVImpl( type, config, maybeKey, !0, trackActualOwner ? Error("react-stack-top-frame") : unknownOwnerDebugStack, trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask ); }; })(); return reactJsxRuntime_development$1; } if (process.env.NODE_ENV === 'production') { jsxRuntime$1.exports = requireReactJsxRuntime_production$1(); } else { jsxRuntime$1.exports = requireReactJsxRuntime_development$1(); } var jsxRuntimeExports$1 = jsxRuntime$1.exports; const DfFormErrorMsg$1 = ({ validationErrors, fieldId, touchedFields, formSubmitted, properties, localValidation, isTouched: localIsTouched, mode, }) => { const getErrorMessage = () => { // Only show error messages in test mode if (mode !== 'test') { return ''; } // Check if field is touched or form is submitted const isTouched = localIsTouched || touchedFields[fieldId] || formSubmitted; if (!isTouched) { return ''; } // Prioritize local validation - if local validation is valid, don't show any errors if (localValidation && localValidation.isValid) { return ''; } // Show local validation errors if validation failed if (localValidation && !localValidation.isValid && Object.keys(localValidation.errors).length > 0) { const errorKeys = Object.keys(localValidation.errors); if (errorKeys.length > 0) { const firstError = errorKeys[0]; return getDefaultErrorMessage(firstError); } } // Only check external validation errors if local validation doesn't exist or is not valid const fieldErrors = validationErrors[fieldId]; if (fieldErrors && typeof fieldErrors === 'string' && fieldErrors.trim() !== '') { // For date/time components, check if we should use mode-specific message const componentName = properties.name || ''; const isDateTimeComponent = componentName === 'date-picker' || componentName === 'datetime-picker'; if (isDateTimeComponent && fieldErrors.includes('valid date')) { const dateTimeMode = properties.basic?.dateTimeMode || (componentName === 'datetime-picker' ? 'datetime' : 'date'); if (dateTimeMode === 'time') { return 'Please select a valid time'; } else if (dateTimeMode === 'datetime') { return 'Please select a valid date and time'; } } return fieldErrors; } return ''; }; const getDefaultErrorMessage = (errorType) => { const fieldLabel = properties.basic.label || 'This field'; // Check if there's a custom validation message const customMessage = properties.validation.customValidationMessage; if (customMessage) { return customMessage; } // For date/time components, check dateTimeMode for appropriate error messages const componentName = properties.name || ''; const isDateTimeComponent = componentName === 'date-picker' || componentName === 'datetime-picker'; const dateTimeMode = isDateTimeComponent ? (properties.basic?.dateTimeMode || (componentName === 'datetime-picker' ? 'datetime' : 'date')) : null; switch (errorType) { case 'required': return `${fieldLabel} is required`; case 'invalidDate': // Show mode-specific error messages for date/time components if (dateTimeMode === 'time') { return 'Please select a valid time'; } else if (dateTimeMode === 'datetime') { return 'Please select a valid date and time'; } else if (dateTimeMode === 'date') { return 'Please select a valid date'; } return `${fieldLabel} is invalid`; case 'minLength': const minLength = properties.validation.minLength; return `${fieldLabel} must be at least ${minLength} characters long`; case 'maxLength': const maxLength = properties.validation.maxLength; return `${fieldLabel} must be no more than ${maxLength} characters long`; case 'min': const min = properties.validation.min; return `${fieldLabel} must be at least ${min}`; case 'max': const max = properties.validation.max; return `${fieldLabel} must be no more than ${max}`; case 'pattern': return `${fieldLabel} format is invalid`; case 'email': return `${fieldLabel} must be a valid email address`; default: return `${fieldLabel} is invalid`; } }; const errorMessage = getErrorMessage(); if (!errorMessage) { return null; } return (jsxRuntimeExports$1.jsx("div", { className: "form-error-msg", children: errorMessage })); }; const DfFormInput$1 = ({ id, properties, validationErrors = {}, formValue = '', inputType = 'text', readonly = false, disabled = false, touchedFields = {}, formSubmitted = false, mode = 'test', onValueChange, onBlur, onFocus, className = '', hideLabel = false }) => { const [value, setValue] = useState(formValue || properties?.basic?.defaultValue || ''); const [isTouched, setIsTouched] = useState(false); const [isFocused, setIsFocused] = useState(false); // Determine input type based on component name const getInputType = () => { if (inputType) return inputType; const name = properties?.name; if (name === 'text-input') return 'text'; if (name === 'number-input') return 'number'; if (name === 'email-input') return 'email'; return 'text'; }; // Build validation rules const buildValidationRules = () => { const rules = {}; if (properties?.validation?.required) { rules.required = true; } if (getInputType() === 'email') { rules.email = true; } if (getInputType() === 'text') { const textValidation = properties.validation; if (textValidation?.minLength) rules.minLength = textValidation.minLength; if (textValidation?.maxLength) rules.maxLength = textValidation.maxLength; } if (getInputType() === 'number') { const numberValidation = properties.validation; if (numberValidation?.min) rules.min = numberValidation.min; if (numberValidation?.max) rules.max = numberValidation.max; if (numberValidation?.minLength) rules.minLength = numberValidation.minLength; if (numberValidation?.maxLength) rules.maxLength = numberValidation.maxLength; } return rules; }; // Validate field const validateField = useCallback((val) => { const rules = buildValidationRules(); const errors = {}; let isValid = true; // Required validation if (rules.required && (!val || val.trim() === '')) { errors.required = true; isValid = false; } // Email validation if (rules.email && val && !/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(val)) { errors.email = true; isValid = false; } // Length validations if (rules.minLength && val && val.length < rules.minLength) { errors.minLength = true; isValid = false; } if (rules.maxLength && val && val.length > rules.maxLength) { errors.maxLength = true; isValid = false; } // Number validations if (getInputType() === 'number' && val) { const numValue = parseFloat(val); if (isNaN(numValue)) { errors.pattern = true; isValid = false; } else { if (rules.min !== undefined && numValue < rules.min) { errors.min = true; isValid = false; } if (rules.max !== undefined && numValue > rules.max) { errors.max = true; isValid = false; } } } return { isValid, errors }; }, [properties, getInputType]); // Handle value changes const handleValueChange = useCallback((newValue) => { setValue(newValue); if (onValueChange) { const validation = validateField(newValue); const change = { id, value: newValue, isValid: validation.isValid, errors: validation.errors, }; onValueChange(change); } }, [id, onValueChange, validateField]); // Handle input change const handleInputChange = (e) => { const newValue = e.target.value; handleValueChange(newValue); }; // Handle focus const handleFocus = () => { setIsFocused(true); if (onFocus) { onFocus(); } }; // Handle blur const handleBlur = () => { setIsFocused(false); setIsTouched(true); // Always trigger validation on blur in test mode for any field with validation rules if (mode === 'test' && onValueChange) { const validation = validateField(value); const change = { id, value: value, isValid: validation.isValid, errors: validation.errors, }; onValueChange(change); } if (onBlur) { onBlur(); } }; // Update touched fields useEffect(() => { if (isTouched) { touchedFields[id] = true; } }, [isTouched, id, touchedFields]); // Reset touched state and value when switching modes useEffect(() => { if (mode === 'edit') { setIsTouched(false); // Reset value to default value when switching to edit mode const defaultValue = properties?.basic?.defaultValue || ''; setValue(defaultValue); } else if (mode === 'test') { setIsTouched(false); // Reset value to empty when switching to test mode for fresh start setValue(''); } }, [mode, properties?.basic?.defaultValue]); // Update value when formValue prop changes (but don't override user input) useEffect(() => { const newValue = formValue || properties?.basic?.defaultValue || ''; // Only update if the new value is different and we're not currently focused // Also, don't override with empty value if user has typed something // CRITICAL FIX: Don't reset user input when formValue becomes empty if (newValue !== value && !isFocused && !(newValue === '' && value.trim() !== '')) { setValue(newValue); } else if (newValue === '' && value.trim() !== '' && !isFocused) ; }, [formValue, value, isFocused, properties?.basic?.defaultValue]); // Mark as touched when form is submitted useEffect(() => { if (formSubmitted) { setIsTouched(true); } }, [formSubmitted]); // Clear value when switching away from test mode useEffect(() => { if (mode !== 'test') { setValue(''); } }, [mode]); // Get label alignment class const getLabelAlignmentClass = () => { const alignment = properties?.styles?.labelAlignment; return alignment === 'left' ? 'label-left' : 'label-top'; }; // Get input classes const getInputClasses = () => { const base = 'form-control'; // Only show validation errors in test mode if (mode === 'test') { const validation = validateField(value); const hasLocalError = !validation.isValid && isTouched; const hasExternalError = validationErrors[id] && (isTouched || formSubmitted); const error = (hasLocalError || hasExternalError) ? 'is-invalid' : ''; return `${base} ${error} ${className}`.trim(); } return `${base} ${className}`.trim(); }; const currentInputType = getInputType(); const shouldDisable = disabled || readonly || properties?.validation?.readonly; return (jsxRuntimeExports$1.jsxs("div", { className: `form-group ${getLabelAlignmentClass()}`, children: [!hideLabel && properties.basic.label && (jsxRuntimeExports$1.jsxs("label", { htmlFor: id, className: "form-label", children: [properties.basic.label, properties.validation.required && (jsxRuntimeExports$1.jsx("span", { className: "required-indicator", children: "*" }))] })), jsxRuntimeExports$1.jsxs("div", { className: "input-wrapper", children: [jsxRuntimeExports$1.jsx("input", { type: currentInputType, id: id, value: value, onChange: handleInputChange, onFocus: handleFocus, onBlur: handleBlur, placeholder: properties.basic.placeholder, disabled: shouldDisable, readOnly: readonly || properties?.validation?.readonly, className: getInputClasses() }), jsxRuntimeExports$1.jsx(DfFormErrorMsg$1, { validationErrors: validationErrors, fieldId: id, touchedFields: touchedFields, formSubmitted: formSubmitted, properties: properties, localValidation: validateField(value), isTouched: isTouched, mode: mode })] })] })); }; const DfFormTextarea = ({ id, properties, validationErrors = {}, formValue = '', readonly = false, disabled = false, touchedFields = {}, formSubmitted = false, mode = 'preview', onValueChange, onBlur, onFocus, className = '', hideLabel = false }) => { const [value, setValue] = useState(formValue); const [isTouched, setIsTouched] = useState(false); // Validate field const validateField = useCallback((val) => { const errors = {}; let isValid = true; // Required validation if (properties?.validation?.required && (!val || val.trim() === '')) { errors.required = true; isValid = false; } // Length validations if (properties?.validation?.minLength && val && val.length < properties.validation.minLength) { errors.minLength = true; isValid = false; } if (properties?.validation?.maxLength && val && val.length > properties.validation.maxLength) { errors.maxLength = true; isValid = false; } return { isValid, errors }; }, [properties]); // Handle value changes const handleValueChange = useCallback((newValue) => { setValue(newValue); if (onValueChange) { const validation = validateField(newValue); const change = { id, value: newValue, isValid: validation.isValid, errors: validation.errors, }; onValueChange(change); } }, [id, onValueChange, validateField]); // Handle textarea change const handleTextareaChange = (e) => { const newValue = e.target.value; handleValueChange(newValue); }; // Handle focus const handleFocus = () => { if (onFocus) { onFocus(); } }; // Handle blur const handleBlur = () => { setIsTouched(true); if (onBlur) { onBlur(); } }; // Update touched fields useEffect(() => { if (isTouched) { touchedFields[id] = true; } }, [isTouched, id, touchedFields]); // Update value when formValue prop changes (only on initial load or when component is reset) useEffect(() => { // Only update if the current value is empty (initial state) or if formValue is explicitly set // This prevents the component from resetting user input when they blur the field if (value === '' && formValue !== '') { setValue(formValue); } }, [formValue]); // Remove value and isFocused from dependencies to prevent reset on blur // Mark as touched when form is submitted useEffect(() => { if (formSubmitted) { setIsTouched(true); } }, [formSubmitted]); // Clear value when switching away from test mode useEffect(() => { if (mode !== 'test') { setValue(''); } }, [mode]); // Get label alignment class const getLabelAlignmentClass = () => { const alignment = properties?.styles?.labelAlignment; return alignment === 'left' ? 'label-left' : 'label-top'; }; // Get textarea classes const getTextareaClasses = () => { const base = 'form-textarea'; const validation = validateField(value); const error = !validation.isValid && isTouched ? 'is-invalid' : ''; return `${base} ${error} ${className}`.trim(); }; const shouldDisable = disabled || readonly || properties?.validation?.readonly; const rows = properties?.validation?.rows || 4; return (jsxRuntimeExports$1.jsxs("div", { className: `form-group ${getLabelAlignmentClass()}`, children: [!hideLabel && properties.basic.label && (jsxRuntimeExports$1.jsxs("label", { htmlFor: id, className: "form-label", children: [properties.basic.label, properties.validation.required && (jsxRuntimeExports$1.jsx("span", { className: "required-indicator", children: "*" }))] })), jsxRuntimeExports$1.jsxs("div", { className: "input-wrapper", children: [jsxRuntimeExports$1.jsx("textarea", { id: id, value: value, onChange: handleTextareaChange, onFocus: handleFocus, onBlur: handleBlur, placeholder: properties.basic.placeholder, disabled: shouldDisable, readOnly: readonly || properties?.validation?.readonly, rows: rows, className: getTextareaClasses() }), jsxRuntimeExports$1.jsx(DfFormErrorMsg$1, { validationErrors: validationErrors, fieldId: id, touchedFields: touchedFields, formSubmitted: formSubmitted, properties: properties, localValidation: validateField(value), isTouched: isTouched, mode: mode })] })] })); }; const DfFormComments = ({ comment = '', onSave, placeholder = 'Enter your reason...', className = '', disabled = false }) => { const [isExpanded, setIsExpanded] = useState(true); const [currentComment, setCurrentComment] = useState(comment); const [hasChanges, setHasChanges] = useState(false); // Update local state when comment prop changes useEffect(() => { setCurrentComment(comment); setHasChanges(false); }, [comment]); // Handle input changes const handleInputChange = useCallback((event) => { const newValue = event.target.value; setCurrentComment(newValue); setHasChanges(newValue !== comment); }, [comment]); // Handle input blur with auto-save const handleInputBlur = useCallback(() => { if (hasChanges && onSave && !disabled) { onSave(currentComment); setHasChanges(false); } }, [hasChanges, onSave, currentComment, disabled]); // Handle input focus const handleInputFocus = useCallback(() => { // Focus handler if needed }, []); // Toggle comments section const toggleComments = useCallback(() => { if (!disabled) { setIsExpanded(!isExpanded); } }, [isExpanded, disabled]); return (jsxRuntimeExports$1.jsxs("div", { className: `df-form-comments ${className}`, children: [jsxRuntimeExports$1.jsxs("div", { className: "df-form-comments__header", children: [jsxRuntimeExports$1.jsx("h3", { className: "df-form-comments__title", children: "Comments" }), jsxRuntimeExports$1.jsx("button", { className: "df-form-comments__toggle", type: "button", onClick: toggleComments, "aria-expanded": isExpanded, "aria-label": "Toggle comments section", disabled: disabled, children: isExpanded ? (jsxRuntimeExports$1.jsx("span", { className: "df-form-comments__toggle-icon", children: "\u25BC" })) : (jsxRuntimeExports$1.jsx("span", { className: "df-form-comments__toggle-icon", children: "\u25B6" })) })] }), jsxRuntimeExports$1.jsx("div", { className: `df-form-comments__content ${isExpanded ? 'df-form-comments__content--expanded' : ''}`, children: jsxRuntimeExports$1.jsx("div", { className: "df-form-comments__input-container", children: jsxRuntimeExports$1.jsx("div", { className: "df-form-comments__input-line", children: jsxRuntimeExports$1.jsx("input", { type: "text", id: "comment-input", className: "df-form-comments__input", value: currentComment, onChange: handleInputChange, onBlur: handleInputBlur, onFocus: handleInputFocus, placeholder: placeholder, disabled: disabled }) }) }) })] })); }; const DfFormSelect = ({ id, properties, validationErrors = {}, formValue = '', readonly = false, disabled = false, touchedFields = {}, formSubmitted = false, mode = 'preview', onValueChange, onBlur, onFocus, className = '', hideLabel = false, showCommentsInPreview = false }) => { const [selectedValue, setSelectedValue] = useState(formValue); const [isTouched, setIsTouched] = useState(false); const [comment, setComment] = useState(''); const [showCommentInput, setShowCommentInput] = useState(false); // Check if multiple selection is enabled const isMultiple = properties?.validation?.multiple || false; // Initialize comment from properties when in preview mode (for form submissions) useEffect(() => { if (mode === 'preview' && showCommentsInPreview && properties?.basic?.comments) { setComment(properties.basic.comments); } }, [mode, showCommentsInPreview, properties]); // Validate field const validateField = useCallback((value) => { const errors = {}; let isValid = true; // Required validation if (properties?.validation?.required) { if (isMultiple) { if (!Array.isArray(value) || value.length === 0) { errors.required = true; isValid = false; } } else { if (!value || (typeof value === 'string' && value.trim() === '')) { errors.required = true; isValid = false; } } } return { isValid, errors }; }, [properties, isMultiple]); // Handle value changes const handleValueChange = useCallback((newValue) => { setSelectedValue(newValue); if (onValueChange) { const validation = validateField(newValue); const change = { id, value: newValue, isValid: validation.isValid, errors: validation.errors, comments: comment }; onValueChange(change); } }, [id, onValueChange, validateField, comment]); // Helper function to check if a value should show red chip (fail/no) const shouldShowRedChip = (value) => { const lowerValue = value.toLowerCase(); return lowerValue === 'no' || lowerValue === 'fail' || lowerValue === 'false'; }; // Check if comments section should be shown const shouldShowCommentsSection = () => { if (isMultiple) { return Array.isArray(selectedValue) && selectedValue.some(value => shouldShowRedChip(value)); } else { return typeof selectedValue === 'string' && shouldShowRedChip(selectedValue); } }; // Check if comment input should be shown const shouldShowCommentInput = () => { // Don't show comment input in preview mode if (mode === 'preview') { return false; } const needsComments = shouldShowCommentsSection(); const hasNoComment = !comment || comment.trim() === ''; return needsComments && (showCommentInput || hasNoComment); }; // Check if comment display should be shown const shouldShowCommentDisplay = () => { // In preview mode, show comments if they exist and showCommentsInPreview is true if (mode === 'preview' && showCommentsInPreview && comment) { return true; } return shouldShowCommentsSection() && !showCommentInput && !!comment; }; // Toggle comment input visibility const toggleCommentInput = () => { // Don't allow toggling in preview mode if (mode === 'preview') { return; } setShowCommentInput(!showCommentInput); }; // Handle comment save const handleCommentSave = (newComment) => { setComment(newComment); setShowCommentInput(false); if (onValueChange) { const validation = validateField(selectedValue); const change = { id, value: selectedValue, isValid: validation.isValid, errors: validation.errors, comments: newComment }; onValueChange(change); } }; // Handle select change const handleSelectChange = (e) => { if (isMultiple) { const selectedOptions = Array.from(e.target.selectedOptions, option => option.value); handleValueChange(selectedOptions); } else { handleValueChange(e.target.value); } }; // Handle focus const handleFocus = () => { if (onFocus) { onFocus(); } }; // Handle blur const handleBlur = () => { setIsTouched(true); if (onBlur) { onBlur(); } }; // Update touched fields useEffect(() => { if (isTouched) { touchedFields[id] = true; } }, [isTouched, id, touchedFields]); // Update value when formValue prop changes useEffect(() => { if (JSON.stringify(formValue) !== JSON.stringify(selectedValue)) { setSelectedValue(formValue); } }, [formValue, selectedValue]); // Mark as touched when form is submitted useEffect(() => { if (formSubmitted) { setIsTouched(true); } }, [formSubmitted]); // Get label alignment class const getLabelAlignmentClass = () => { const alignment = properties?.styles?.labelAlignment; return alignment === 'left' ? 'label-left' : 'label-top'; }; // Get select classes const getSelectClasses = () => { const base = 'form-select'; const validation = validateField(selectedValue); const error = !validation.isValid && isTouched ? 'is-invalid' : ''; return `${base} ${error} ${className}`.trim(); }; const shouldDisable = disabled || readonly || properties?.validation?.readonly; return (jsxRuntimeExports$1.jsxs("div", { className: `form-group ${getLabelAlignmentClass()}`, children: [!hideLabel && properties.basic.label && (jsxRuntimeExports$1.jsxs("label", { htmlFor: id, className: "form-label", children: [properties.basic.label, properties.validation.required && (jsxRuntimeExports$1.jsx("span", { className: "required-indicator", children: "*" }))] })), jsxRuntimeExports$1.jsxs("div", { className: "input-wrapper", children: [jsxRuntimeExports$1.jsxs("select", { id: id, value: isMultiple ? selectedValue : selectedValue, onChange: handleSelectChange, onFocus: handleFocus, onBlur: handleBlur, disabled: shouldDisable, multiple: isMultiple, className: getSelectClasses(), children: [!isMultiple && (jsxRuntimeExports$1.jsx("option", { value: "", children: properties.basic.placeholder || 'Select an option' })), (properties.options || []).map((option, index) => (jsxRuntimeExports$1.jsx("option", { value: option.value, disabled: option.disabled, children: option.label }, index)))] }), jsxRuntimeExports$1.jsx(DfFormErrorMsg$1, { validationErrors: validationErrors, fieldId: id, touchedFields: touchedFields, formSubmitted: formSubmitted, properties: properties, localValidation: validateField(selectedValue), isTouched: isTouched, mode: mode }), (!shouldDisable || (mode === 'preview' && showCommentsInPreview)) && (jsxRuntimeExports$1.jsxs(jsxRuntimeExports$1.Fragment, { children: [shouldShowCommentDisplay() && (jsxRuntimeExports$1.jsx("div", { onClick: mode === 'preview' ? undefined : toggleCommentInput, style: { cursor: mode === 'preview' ? 'default' : 'pointer' }, children: jsxRuntimeExports$1.jsxs("label", { className: "form-label notes-label-text", children: ["Notes: ", comment] }) })), shouldShowCommentInput() && (jsxRuntimeExports$1.jsx(DfFormComments, { comment: comment, onSave: handleCommentSave, placeholder: "Enter your reason...", disabled: shouldDisable }))] }))] })] })); }; const DfFormCheckbox = ({ id, properties, validationErrors = {}, formValue = [], readonly = false, disabled = false, touchedFields = {}, formSubmitted = false, mode = 'preview', onValueChange, onBlur, onFocus, className = '', hideLabel = false, showCommentsInPreview = false }) => { const [isTouched, setIsTouched] = useState(false); const [comment, setComment] = useState(''); const [showCommentInput, setShowCommentInput] = useState(false); // Use formValue directly instead of local state to avoid conflicts const selectedValues = Array.isArray(formValue) ? formValue : []; // Initialize comment from properties when in preview mode (for form submissions) useEffect(() => { if (mode === 'preview' && showCommentsInPreview && properties?.basic?.comments) { setComment(properties.basic.comments); } }, [mode, showCommentsInPreview, properties]); // Validate field const validateField = useCallback((values) => { const errors = {}; let isValid = true; // Required validation if (properties?.validation?.required && values.length === 0) { errors.required = true; isValid = false; } return { isValid, errors }; }, [properties]); // Handle value changes const handleValueChange = useCallback((newValues) => { if (onValueChange) { const validation = validateField(newValues); const change = { id, value: newValues, isValid: validation.isValid, errors: validation.errors, comments: comment }; onValueChange(change); } }, [id, onValueChange, validateField, comment, selectedValues]); // Helper function to check if a value should show red chip (fail/no) const shouldShowRedChip = (value) => { const lowerValue = value.toLowerCase(); return lowerValue === 'no' || lowerValue === 'fail' || lowerValue === 'false'; }; // Check if comments section should be shown const shouldShowCommentsSection = () => { return selectedValues.some(value => shouldShowRedChip(value)); }; // Check if comment input should be shown const shouldShowCommentInput = () => { // Don't show comment input in preview mode if (mode === 'preview') { return false; } const needsComments = shouldShowCommentsSection(); const hasNoComment = !comment || comment.trim() === ''; return needsComments && (showCommentInput || hasNoComment); }; // Check if comment display should be shown const shouldShowCommentDisplay = () => { // In preview mode, show comments if they exist and showCommentsInPreview is true if (mode === 'preview' && showCommentsInPreview && comment) { return true; } return shouldShowCommentsSection() && !showCommentInput && !!comment; }; // Toggle comment input visibility const toggleCommentInput = () => { // Don't allow toggling in preview mode if (mode === 'preview') { return; } setShowCommentInput(!showCommentInput); }; // Handle comment save const handleCommentSave = (newComment) => { setComment(newComment); setShowCommentInput(false); if (onValueChange) { const validation = validateField(selectedValues); const change = { id, value: selectedValues, isValid: validation.isValid, errors: validation.errors, comments: newComment }; onValueChange(change); } }; // Handle checkbox change const handleCheckboxChange = (optionValue, checked) => { let newValues; if (checked) { newValues = [...selectedValues, optionValue]; } else { newValues = selectedValues.filter(value => value !== optionValue); } handleValueChange(newValues); }; // Handle focus const handleFocus = () => { if (onFocus) { onFocus(); } }; // Handle blur const handleBlur = () => { setIsTouched(true); if (onBlur) { onBlur(); } }; // Update touched fields useEffect(() => { if (isTouched) { touchedFields[id] = true; } }, [isTouched, id, touchedFields]); // Log formValue changes for debugging useEffect(() => { }, [formValue, id, selectedValues]); // Mark as touched when form is submitted useEffect(() => { if (formSubmitted) { setIsTouched(true); } }, [formSubmitted]); // Get label alignment class const getLabelAlignmentClass = () => { const alignment = properties?.styles?.labelAlignment; return alignment === 'left' ? 'label-left' : 'label-top'; }; // Get layout class for inline/vertical layout const getLayoutClass = () => { return properties?.basic?.inlineLayout ? 'inline-layout' : 'vertical-layout'; }; const shouldDisable = disabled || readonly || properties?.validation?.readonly; return (jsxRuntimeExports$1.jsxs(jsxRuntimeExports$1.Fragment, { children: [jsxRuntimeExports$1.jsx("div", { className: `form-group checkbox-group ${getLabelAlignmentClass()} ${getLayoutClass()} ${className}`, children: !hideLabel && properties.basic.label ? (jsxRuntimeExports$1.jsxs(jsxRuntimeExports$1.Fragment, { children: [jsxRuntimeExports$1.jsxs("label", { className: "form-label", children: [properties.basic.label, properties.validation.required && (jsxRuntimeExports$1.jsx("span", { className: "required-indicator", children: "*" }))] }), jsxRuntimeExports$1.jsxs("div", { className: "input-wrapper", children: [jsxRuntimeExports$1.jsx("div", { className: `mt-2 ${properties?.basic?.inlineLayout ? 'inline-options-grid' : 'space-y-2'}`, children: (properties.options || []).map((option, index) => (jsxRuntimeExports$1.jsxs("div", { className: "form-check", children: [jsxRuntimeExports$1.jsx("input", { type: "checkbox", id: `${id}-${index}`, name: id, value: option.value, checked: selectedValues.includes(option.value), onChange: (e) => { handleCheckboxChange(option.value, e.target.checked);