UNPKG

@matthew.ngo/reform

Version:

A flexible and powerful React form management library with advanced validation, state observation, and multi-group support

1,467 lines (1,450 loc) 171 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var React = require('react'); var React__default = _interopDefault(React); var get = _interopDefault(require('lodash/get')); var reactHookForm = require('react-hook-form'); var yup = require('@hookform/resolvers/yup'); var yup$1 = require('yup'); function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); } function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; } function _createForOfIteratorHelperLoose(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (t) return (t = t.call(r)).next.bind(t); if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var o = 0; return function () { return o >= r.length ? { done: !0 } : { done: !1, value: r[o++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } function _inheritsLoose(t, o) { t.prototype = Object.create(o.prototype), t.prototype.constructor = t, _setPrototypeOf(t, o); } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (e.includes(n)) continue; t[n] = r[n]; } return t; } function _regeneratorRuntime() { _regeneratorRuntime = function () { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function (t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == typeof h && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function (t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator.return && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(typeof e + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function (e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function () { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function (e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function (t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function (t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function (t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, catch: function (t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw Error("illegal catch attempt"); }, delegateYield: function (e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; } function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } /** * Recovery strategy for form errors */ var RecoveryStrategy; (function (RecoveryStrategy) { /** Reset the form to its initial state */ RecoveryStrategy["RESET_FORM"] = "reset_form"; /** Retry the last operation that caused the error */ RecoveryStrategy["RETRY_OPERATION"] = "retry_operation"; /** Ignore the error and continue */ RecoveryStrategy["IGNORE"] = "ignore"; /** Custom recovery strategy defined by the user */ RecoveryStrategy["CUSTOM"] = "custom"; })(RecoveryStrategy || (RecoveryStrategy = {})); /** * Default fallback component for form errors */ var DefaultFallback = function DefaultFallback(_ref) { var errorInfo = _ref.errorInfo, resetErrorBoundary = _ref.resetErrorBoundary, recover = _ref.recover; return React__default.createElement("div", { className: "reform-error-boundary" }, React__default.createElement("h2", null, "Something went wrong in the form"), React__default.createElement("details", null, React__default.createElement("summary", null, "Error details"), React__default.createElement("p", null, errorInfo.error.message), React__default.createElement("pre", null, errorInfo.error.stack)), React__default.createElement("div", { className: "reform-error-actions" }, React__default.createElement("button", { onClick: function onClick() { return recover(RecoveryStrategy.RESET_FORM); }, className: "reform-error-reset-btn" }, "Reset Form"), React__default.createElement("button", { onClick: resetErrorBoundary, className: "reform-error-retry-btn" }, "Try Again"))); }; /** * Error boundary component for Reform forms * * Catches errors in form components and displays a fallback UI * * @template T - The type of form data * * @example * // Basic usage * <FormErrorBoundary> * <MyFormComponent /> * </FormErrorBoundary> * * @example * // With custom fallback component * <FormErrorBoundary * FallbackComponent={MyErrorFallback} * onError={(error) => logError(error)} * formGroups={reform.getGroups()} * > * <MyFormComponent /> * </FormErrorBoundary> */ var FormErrorBoundary = /*#__PURE__*/function (_React$Component) { function FormErrorBoundary(props) { var _this; _this = _React$Component.call(this, props) || this; _this.resetErrorBoundary = function () { // Reset the error state _this.setState({ hasError: false, errorInfo: null }); // Call onReset callback if provided if (_this.props.onReset) { _this.props.onReset(); } }; _this.recover = function (strategy) { if (typeof strategy === 'function') { // Custom recovery function strategy(_this.state.errorInfo); _this.resetErrorBoundary(); return; } // Apply the specified recovery strategy switch (strategy) { case RecoveryStrategy.RESET_FORM: // Reset form logic would be implemented by the consumer break; case RecoveryStrategy.RETRY_OPERATION: // Just reset the error boundary to retry break; case RecoveryStrategy.IGNORE: // Just reset the error boundary break; case RecoveryStrategy.CUSTOM: // Call custom recovery handler if provided if (_this.props.onCustomRecovery && _this.state.errorInfo) { _this.props.onCustomRecovery(_this.state.errorInfo); } break; } _this.resetErrorBoundary(); }; _this.state = { hasError: false, errorInfo: null }; return _this; } _inheritsLoose(FormErrorBoundary, _React$Component); FormErrorBoundary.getDerivedStateFromError = function getDerivedStateFromError(error) { // Update state so the next render will show the fallback UI return { hasError: true }; }; var _proto = FormErrorBoundary.prototype; _proto.componentDidCatch = function componentDidCatch(error, errorInfo) { // Create error info object var fullErrorInfo = { error: error, errorInfo: errorInfo, formGroups: this.props.formGroups, context: {} }; // Update state with error info this.setState({ errorInfo: fullErrorInfo }); // Call onError callback if provided if (this.props.onError) { this.props.onError(error, errorInfo); } }; _proto.render = function render() { var _this$state = this.state, hasError = _this$state.hasError, errorInfo = _this$state.errorInfo; var _this$props = this.props, fallback = _this$props.fallback, fallbackRender = _this$props.fallbackRender, _this$props$FallbackC = _this$props.FallbackComponent, FallbackComponent = _this$props$FallbackC === void 0 ? DefaultFallback : _this$props$FallbackC, children = _this$props.children; if (hasError && errorInfo) { var fallbackProps = { errorInfo: errorInfo, resetErrorBoundary: this.resetErrorBoundary, recover: this.recover }; // Render the appropriate fallback UI if (React__default.isValidElement(fallback)) { return fallback; } if (typeof fallbackRender === 'function') { return fallbackRender(fallbackProps); } return React__default.createElement(FallbackComponent, Object.assign({}, fallbackProps)); } return children; }; return FormErrorBoundary; }(React__default.Component); // Add static displayName property FormErrorBoundary.displayName = 'FormErrorBoundary'; /** * Hook to create a form error boundary with the current form groups * * @template T - The type of form data * @param formGroups - Current form groups * @param options - Additional options for the error boundary * @returns A FormErrorBoundary component with the current form groups * * @example * // In a form component * const reform = useReform(config); * const ErrorBoundary = useFormErrorBoundary(reform.getGroups()); * * return ( * <ErrorBoundary> * <MyFormFields /> * </ErrorBoundary> * ); */ var useFormErrorBoundary = function useFormErrorBoundary(formGroups, options) { return React__default.useMemo(function () { // Create the component with children var ErrorBoundaryWrapper = function ErrorBoundaryWrapper(_ref2) { var children = _ref2.children; return React__default.createElement(FormErrorBoundary, Object.assign({ formGroups: formGroups }, options), children); }; // Add display name to the component ErrorBoundaryWrapper.displayName = 'FormErrorBoundaryWrapper'; return ErrorBoundaryWrapper; }, [formGroups, options]); }; /** * Component to display formatted error messages */ var ErrorMessage = function ErrorMessage(_ref) { var error = _ref.error, _ref$className = _ref.className, className = _ref$className === void 0 ? "reform-error" : _ref$className, _ref$showIcon = _ref.showIcon, showIcon = _ref$showIcon === void 0 ? true : _ref$showIcon; if (!error) return null; // Handle string errors (backward compatibility) if (typeof error === "string") { return React__default.createElement("div", { className: className }, showIcon && React__default.createElement("span", { className: "reform-error-icon" }, "\u26A0\uFE0F"), React__default.createElement("span", null, error)); } var content = error.content, format = error.format, meta = error.meta; // Handle different formats switch (format) { case "jsx": return React__default.createElement("div", { className: className, "data-error-meta": JSON.stringify(meta || {}) }, content); case "html": return React__default.createElement("div", { className: className, "data-error-meta": JSON.stringify(meta || {}), dangerouslySetInnerHTML: { __html: content } }); case "markdown": return React__default.createElement("div", { className: className, "data-error-meta": JSON.stringify(meta || {}) }, React__default.createElement("span", null, content)); case "plain": default: return React__default.createElement("div", { className: className, "data-error-meta": JSON.stringify(meta || {}) }, showIcon && React__default.createElement("span", { className: "reform-error-icon" }, "\u26A0\uFE0F"), React__default.createElement("span", null, content)); } }; /** * Hook to create a memoized callback that maintains reference equality * even when dependencies change, but only updates the function when * the result would be different. * * @param callback - The callback function to memoize * @param deps - Dependencies array for the callback * @returns Memoized callback function */ function useMemoizedCallback(callback, deps) { // Store the previous callback and deps var callbackRef = React.useRef(callback); var depsRef = React.useRef(deps); // Check if deps have changed in a way that would affect the callback var hasRelevantChanges = deps.some(function (dep, i) { return !Object.is(dep, depsRef.current[i]); }); // Update refs if there are relevant changes if (hasRelevantChanges) { callbackRef.current = callback; depsRef.current = deps; } // Return a stable callback that uses the latest implementation return React.useCallback(function () { return callbackRef.current.apply(callbackRef, arguments); }, []); } /** * Default error format configuration */ var defaultFormatConfig = { format: "plain", includeLabels: true, includeRuleNames: false }; /** * Hook for formatting form error messages * * @param reform - Reform hook return value * @param initialConfig - Initial error format configuration * @returns Form error formatter methods */ var useErrorFormatter = function useErrorFormatter(reform, initialConfig) { // Memoize the initial config to prevent unnecessary re-renders var memoizedInitialConfig = React.useMemo(function () { return initialConfig; }, [ // Stringify the config to compare by value instead of reference JSON.stringify(initialConfig)]); // Merge default config with initial config var _useState = React.useState(function () { return _extends({}, defaultFormatConfig, memoizedInitialConfig); }), formatConfig = _useState[0], setFormatConfig = _useState[1]; // Store custom transformers var transformersRef = React.useRef({}); /** * Get the appropriate transformer for a field */ var getTransformer = React.useCallback(function (fieldPath) { // Check for field-specific transformer if (transformersRef.current[fieldPath]) { return transformersRef.current[fieldPath]; } // Check for global transformer if (transformersRef.current["*"]) { return transformersRef.current["*"]; } return undefined; }, [] // No dependencies as we're using ref ); /** * Default transformer for error messages */ var defaultTransformer = useMemoizedCallback(function (message, fieldPath, groupIndex) { var groups = reform.getGroups(); var group = groups[groupIndex] || { id: "", data: {} }; var labels = reform.config.labels || {}; // Handle nested labels with lodash get var fieldLabel = typeof fieldPath === "string" ? get(labels, fieldPath) || get(labels, fieldPath) : undefined; var content = message; // Include field label if configured if (formatConfig.includeLabels && fieldLabel) { content = fieldLabel + ": " + content; } return { content: content, format: formatConfig.format, fieldPath: fieldPath, fieldLabel: fieldLabel, meta: { groupId: group.id, groupIndex: groupIndex } }; }, [formatConfig, reform.getGroups, reform.config.labels]); /** * Format a field error message */ var formatFieldError = useMemoizedCallback(function (fieldPath, message, groupIndex) { var transformer = getTransformer(fieldPath); var groups = reform.getGroups(); var group = groups[groupIndex] || { id: "", data: {} }; if (transformer) { return transformer.transform(message, fieldPath, { group: group, groupIndex: groupIndex, labels: reform.config.labels || {}, formatConfig: formatConfig }); } return defaultTransformer(message, fieldPath, groupIndex); }, [defaultTransformer, formatConfig, getTransformer, reform.getGroups, reform.config.labels]); /** * Format a group-level error message */ var formatGroupError = useMemoizedCallback(function (message, groupIndex) { var groups = reform.getGroups(); var group = groups[groupIndex] || { id: "", data: {} }; return { content: message, format: formatConfig.format, meta: { groupId: group.id, groupIndex: groupIndex, isGroupError: true } }; }, [formatConfig.format, reform.getGroups]); /** * Format a form-level error message */ var formatFormError = React.useCallback(function (message) { return { content: message, format: formatConfig.format, meta: { isFormError: true } }; }, [formatConfig.format]); /** * Register a custom error transformer */ var registerTransformer = React.useCallback(function (fieldPath, transformer) { transformersRef.current[fieldPath] = transformer; }, [] // No dependencies as we're using ref ); /** * Update the error format configuration */ var updateFormatConfig = React.useCallback(function (config) { setFormatConfig(function (prev) { return _extends({}, prev, config); }); }, [] // No dependencies as we're using setState with function ); // Return memoized object to prevent unnecessary re-renders return React.useMemo(function () { return { formatFieldError: formatFieldError, formatGroupError: formatGroupError, formatFormError: formatFormError, registerTransformer: registerTransformer, updateFormatConfig: updateFormatConfig }; }, [formatFieldError, formatGroupError, formatFormError, registerTransformer, updateFormatConfig]); }; /** * Hook wrapper for error formatting in Reform forms * * @param reform - Reform hook return value * @param initialConfig - Initial error format configuration * @returns Form error formatter methods * * @example * // Basic usage * const reform = useReform<UserForm>({...}); * const errorFormatter = useReformErrorFormatter(reform); * * // With custom configuration * const errorFormatter = useReformErrorFormatter(reform, { * format: 'jsx', * includeLabels: true * }); * * // Format field error * const error = errorFormatter.formatFieldError('email', 'Invalid email', 0); * * // Use in JSX * <ErrorMessage error={error} /> */ var useReformErrorFormatter = function useReformErrorFormatter(reform, initialConfig) { return useErrorFormatter(reform, initialConfig); }; /** * Default error renderer that returns a simple error message */ var defaultErrorRenderer = function defaultErrorRenderer(_ref) { var error = _ref.error; var errorMessage = Array.isArray(error) ? error[0] : error; return React__default.createElement("div", { className: "reform-error" }, errorMessage); }; /** * Hook for managing custom error renderers in Reform forms * * @template T - The type of form data * @param groups - The form groups * @returns Object with error renderer utilities * * @example * // Basic usage * const reform = useReform(config); * const { registerErrorRenderer, renderError } = useErrorRenderer(reform.getGroups()); * * // Register a custom renderer for email fields * useEffect(() => { * const unregister = registerErrorRenderer({ * field: 'email', * renderer: ({ error }) => <EmailErrorMessage error={error} /> * }); * * return unregister; * }, []); * * // Use the renderer in your form * return ( * <div> * <input {...reform.register(0, 'email')} /> * {renderError('email', reform.getFieldError(0, 'email'), 0)} * </div> * ); */ var useErrorRenderer = function useErrorRenderer(groups) { // State for the error renderer context var _useState = React.useState({ defaultRenderer: defaultErrorRenderer, fieldRenderers: [], showAllErrors: false, contextData: {} }), context = _useState[0], setContext = _useState[1]; /** * Register a field-specific error renderer */ var registerErrorRenderer = useMemoizedCallback(function (config) { var field = config.field, renderer = config.renderer, _config$priority = config.priority, priority = _config$priority === void 0 ? 0 : _config$priority; // Add the new renderer to the list setContext(function (prev) { var newRenderers = [].concat(prev.fieldRenderers, [{ field: field, renderer: renderer, priority: priority }]); // Sort by priority (higher numbers first) newRenderers.sort(function (a, b) { return (b.priority || 0) - (a.priority || 0); }); return _extends({}, prev, { fieldRenderers: newRenderers }); }); // Return a function to unregister the renderer return function () { setContext(function (prev) { return _extends({}, prev, { fieldRenderers: prev.fieldRenderers.filter(function (r) { return r !== config; }) }); }); }; }, []); /** * Set the default error renderer */ var setDefaultRenderer = useMemoizedCallback(function (renderer) { setContext(function (prev) { return _extends({}, prev, { defaultRenderer: renderer }); }); }, []); /** * Check if a field matches a field pattern */ var fieldMatches = useMemoizedCallback(function (fieldPattern, field) { if (typeof fieldPattern === 'function') { return fieldPattern(field); } if (fieldPattern instanceof RegExp) { return fieldPattern.test(String(field)); } return fieldPattern === field; }, []); /** * Get the appropriate renderer for a field */ var getRendererForField = useMemoizedCallback(function (field) { // Find the first matching renderer var matchingConfig = context.fieldRenderers.find(function (config) { return fieldMatches(config.field, field); }); return (matchingConfig == null ? void 0 : matchingConfig.renderer) || context.defaultRenderer; }, [context.fieldRenderers, context.defaultRenderer, fieldMatches]); /** * Render an error for a specific field */ var renderError = useMemoizedCallback(function (field, error, groupIndex) { if (!error) return null; var renderer = getRendererForField(field); var value = groupIndex >= 0 && groupIndex < groups.length ? groups[groupIndex].data[field] : undefined; var props = { field: field, error: error, value: value, groupIndex: groupIndex, groups: groups, meta: context.contextData }; return renderer(props); }, [groups, context.contextData, getRendererForField]); /** * Set whether to show all errors or just the first one */ var setShowAllErrors = useMemoizedCallback(function (showAll) { setContext(function (prev) { return _extends({}, prev, { showAllErrors: showAll }); }); }, []); /** * Update the context data */ var updateContextData = useMemoizedCallback(function (data) { setContext(function (prev) { return _extends({}, prev, { contextData: _extends({}, prev.contextData, data) }); }); }, []); // Return memoized object to prevent unnecessary re-renders return React.useMemo(function () { return { registerErrorRenderer: registerErrorRenderer, setDefaultRenderer: setDefaultRenderer, renderError: renderError, getRendererForField: getRendererForField, setShowAllErrors: setShowAllErrors, updateContextData: updateContextData, context: context }; }, [registerErrorRenderer, setDefaultRenderer, renderError, getRendererForField, setShowAllErrors, updateContextData, context]); }; /** * Hook for managing custom error renderers in Reform forms * * @template T - The type of form data * @param reform - The Reform hook return value * @returns Object with error renderer utilities * * @example * // Basic usage * const reform = useReform(config); * const errorRenderer = useReformErrorRenderer(reform); * * // Register a custom renderer for email fields * useEffect(() => { * const unregister = errorRenderer.registerErrorRenderer({ * field: 'email', * renderer: ({ error }) => <EmailErrorMessage error={error} /> * }); * * return unregister; * }, []); * * // Use the renderer in your form * return ( * <div> * <input {...reform.register(0, 'email')} /> * {errorRenderer.renderFieldError(0, 'email')} * </div> * ); */ var useReformErrorRenderer = function useReformErrorRenderer(reform) { var groups = reform.getGroups(); var errorRenderer = useErrorRenderer(groups); // Enhanced API that integrates with Reform return React.useMemo(function () { return _extends({}, errorRenderer, { /** * Render an error for a specific field using Reform's error state * * @param groupIndex - The group index * @param field - The field path * @returns The rendered error */ renderFieldError: function renderFieldError(groupIndex, field) { var error = reform.getFieldError(groupIndex, field); return errorRenderer.renderError(field, error, groupIndex); }, /** * Render all errors for a group * * @param groupIndex - The group index * @returns Array of rendered errors */ renderGroupErrors: function renderGroupErrors(groupIndex) { var errors = reform.getGroupErrors(groupIndex); return Object.entries(errors).map(function (_ref) { var field = _ref[0], error = _ref[1]; return errorRenderer.renderError(field, error, groupIndex); }); } }); }, [errorRenderer, reform]); }; var useReformErrors = function useReformErrors(reform) { var _reform$formMethods = reform.formMethods, formState = _reform$formMethods.formState, clearFormErrors = _reform$formMethods.clearErrors; React.useEffect(function () { // You can add logging here for debugging if (Object.keys(formState.errors || {}).length > 0) { console.log('Current form errors:', formState.errors); } }, [formState.errors]); // Helper function to extract error message from various error formats var extractErrorMessage = function extractErrorMessage(error) { if (!error) return undefined; // Handle array of errors (like in dateRange) if (Array.isArray(error)) { var _error$; // Return the first error message in the array return (_error$ = error[0]) == null ? void 0 : _error$.message; } // Handle standard error object with message return error.message; }; // Type-safe way to access errors with string keys var getErrorByKey = function getErrorByKey(key) { // Use type assertion to access errors with string keys return formState.errors[key]; }; var getError = function getError(groupIndex, field) { var _formState$errors; // First check if the error is in the flattened format (e.g., "0.users") var flatKey = groupIndex + "." + field; var flatError = getErrorByKey(flatKey); if (flatError) { return extractErrorMessage(flatError); } // If not found in flat format, check in the nested format var fieldError = (_formState$errors = formState.errors) == null || (_formState$errors = _formState$errors.groups) == null || (_formState$errors = _formState$errors[groupIndex]) == null ? void 0 : _formState$errors.data; if (!fieldError) return undefined; return extractErrorMessage(fieldError[field]); }; var getFieldError = function getFieldError(groupIndex, fieldName) { return getError(groupIndex, fieldName); }; var getGroupErrors = function getGroupErrors(groupIndex) { var result = {}; // Check for flattened errors first Object.keys(formState.errors || {}).forEach(function (key) { // Check if the key matches the pattern "groupIndex.fieldName" var match = key.match("^" + groupIndex + "\\.(.+)$"); if (match) { var fieldName = match[1]; var errorMessage = extractErrorMessage(getErrorByKey(key)); if (errorMessage) { result[fieldName] = errorMessage; } } }); // If no flattened errors found, check nested format if (Object.keys(result).length === 0) { var _formState$errors2; var groupData = (_formState$errors2 = formState.errors) == null || (_formState$errors2 = _formState$errors2.groups) == null || (_formState$errors2 = _formState$errors2[groupIndex]) == null ? void 0 : _formState$errors2.data; if (groupData) { Object.keys(groupData).forEach(function (field) { var errorMessage = extractErrorMessage(groupData[field]); if (errorMessage) { result[field] = errorMessage; } }); } } return result; }; var getFieldErrors = function getFieldErrors(index) { var result = {}; // Check for flattened errors first Object.keys(formState.errors || {}).forEach(function (key) { var match = key.match("^" + index + "\\.(.+)$"); if (match) { var fieldName = match[1]; result[fieldName] = extractErrorMessage(getErrorByKey(key)); } }); // If no flattened errors found, check nested format if (Object.keys(result).length === 0) { var _formState$errors3; var groupData = (_formState$errors3 = formState.errors) == null || (_formState$errors3 = _formState$errors3.groups) == null || (_formState$errors3 = _formState$errors3[index]) == null ? void 0 : _formState$errors3.data; if (groupData) { Object.keys(groupData).forEach(function (field) { result[field] = extractErrorMessage(groupData[field]); }); } } return result; }; // Error Clearers var clearErrors = function clearErrors() { clearFormErrors(); }; var clearFieldError = function clearFieldError(groupIndex, fieldName) { // The issue is here - clearFormErrors might only accept a string or undefined // Let's fix by ensuring we're passing the correct type var fieldPath = "groups." + groupIndex + ".data." + fieldName; clearFormErrors(fieldPath); }; var clearGroupError = function clearGroupError(groupIndex) { var groupPath = "groups." + groupIndex; clearFormErrors(groupPath); }; var clearGlobalError = function clearGlobalError() { clearFormErrors('root'); }; // // Error Setters // const setFieldError = ( // groupIndex: number, // fieldName: string, // message: string // ) => { // const fieldPath = `groups.${groupIndex}.data.${fieldName}` as FormDataPath< // T // >; // setError(fieldPath, { // type: 'manual', // message, // }); // }; // const setGroupError = (groupIndex: number, message: string) => { // const groupPath = `groups.${groupIndex}` as FormDataPath<T>; // setError(groupPath, { // type: 'manual', // message, // }); // }; // const setGlobalError = (message: string) => { // setError('root' as FormDataPath<T>, { // type: 'manual', // message, // }); // }; var hasGroupErrors = function hasGroupErrors(groupIndex) { var _formState$errors4, _formState$errors5; // Check for flattened errors first for (var key in formState.errors || {}) { if (key.startsWith(groupIndex + ".")) { return true; } } // If no flattened errors, check nested format return !!((_formState$errors4 = formState.errors) != null && (_formState$errors4 = _formState$errors4.groups) != null && (_formState$errors4 = _formState$errors4[groupIndex]) != null && _formState$errors4.data) && Object.keys(((_formState$errors5 = formState.errors) == null || (_formState$errors5 = _formState$errors5.groups) == null || (_formState$errors5 = _formState$errors5[groupIndex]) == null ? void 0 : _formState$errors5.data) || {}).length > 0; }; var getAllErrors = function getAllErrors() { var _formState$errors6; var allErrors = {}; var errorsRecord = formState.errors; // Include flattened errors Object.keys(formState.errors || {}).forEach(function (key) { // Only include errors that match the pattern "groupIndex.fieldName" if (/^\d+\.\w+$/.test(key)) { allErrors[key] = errorsRecord[key]; } }); // Also check nested format if ((_formState$errors6 = formState.errors) != null && _formState$errors6.groups) { Object.keys(formState.errors.groups).forEach(function (groupIndexStr) { var _formState$errors7; var groupIndex = Number(groupIndexStr); var groupData = (_formState$errors7 = formState.errors) == null || (_formState$errors7 = _formState$errors7.groups) == null || (_formState$errors7 = _formState$errors7[groupIndex]) == null ? void 0 : _formState$errors7.data; if (groupData) { Object.keys(groupData).forEach(function (field) { if (groupData[field]) { var key = groupIndex + "." + field; // Only add if not already added from flattened format if (!allErrors[key]) { allErrors[key] = groupData[field]; } } }); } }); } return allErrors; }; var isGroupValid = function isGroupValid(groupIndex, requiredFields) { if (hasGroupErrors(groupIndex)) { return false; } var group = reform.getGroups()[groupIndex]; if (!group) return false; return requiredFields.every(function (field) { var value = group.data[field]; if (Array.isArray(value)) { return value.length > 0; } if (value === null || value === undefined) { return false; } if (typeof value === 'string') { return value.trim() !== ''; } return true; }); }; var areAllGroupsValid = function areAllGroupsValid(requiredFields) { var groups = reform.getGroups(); return groups.every(function (_, index) { return isGroupValid(index, requiredFields); }); }; var isFormSubmittable = function isFormSubmittable(requiredFields) { return Object.keys(formState.errors || {}).length === 0 && areAllGroupsValid(requiredFields); }; var canAddNewGroup = function canAddNewGroup(requiredFields) { return areAllGroupsValid(requiredFields); }; return { errors: getAllErrors(), hasErrors: Object.keys(formState.errors || {}).length > 0, getFieldError: getFieldError, getGroupErrors: getGroupErrors, getFieldErrors: getFieldErrors, clearErrors: clearErrors, clearFieldError: clearFieldError, clearGroupError: clearGroupError, clearGlobalError: clearGlobalError, // setFieldError, // setGroupError, // setGlobalError, getError: getError, hasGroupErrors: hasGroupErrors, isGroupValid: isGroupValid, areAllGroupsValid: areAllGroupsValid, isFormSubmittable: isFormSubmittable, canAddNewGroup: canAddNewGroup }; }; var useFormController = function useFormController(methods) { return { control: methods.control }; }; /** * Hook for advanced form reset functionality * * @param props - Hook properties * @returns Object with reset methods */ var useFormReset = function useFormReset(_ref) { var methods = _ref.methods, defaultData = _ref.defaultData, minGroups = _ref.minGroups; /** * Resets the form to its initial state or to provided default values */ var resetForm = function resetForm(defaultValues, options) { methods.reset(defaultValues, options); }; /** * Resets the form to a clean state with the specified number of groups * * @param groupCount - Number of groups to initialize (defaults to minGroups) */ var resetToCleanState = function resetToCleanState(groupCount) { if (groupCount === void 0) { groupCount = minGroups; } var cleanGroups = Array.from({ length: groupCount }).map(function () { return { id: Math.random().toString(36).substr(2, 9), data: _extends({}, defaultData) }; }); methods.reset({ groups: cleanGroups }); }; /** * Resets a specific group to its default state * * @param index - Index of the group to reset */ var resetGroup = function resetGroup(index) { var currentGroups = methods.getValues().groups; if (index >= 0 && index < currentGroups.length) { var updatedGroups = [].concat(currentGroups); updatedGroups[index] = { id: updatedGroups[index].id, data: _extends({}, defaultData) }; methods.reset({ groups: updatedGroups }); } }; return { resetForm: resetForm, resetToCleanState: resetToCleanState, resetGroup: resetGroup }; }; var useFormState = function useFormState(methods) { return { fieldState: { isDirty: function isDirty(index, fieldName) { return methods.getFieldState("groups." + index + ".data." + String(fieldName)).isDirty; }, isTouched: function isTouched(index, fieldName) { return methods.getFieldState("groups." + index + ".data." + String(fieldName)).isTouched; }, get: function get(index, fieldName) { return methods.getFieldState("groups." + index + ".data." + String(fieldName)); } }, formState: { isDirty: methods.formState.isDirty, isValid: methods.formState.isValid, touched: Object.keys(methods.formState.touchedFields).length > 0, isSubmitting: methods.formState.isSubmitting, submitCount: methods.formState.submitCount, isValidating: methods.formState.isValidating }, reset: methods.reset, trigger: methods.trigger, watch: methods.watch }; }; // Memoize this function to prevent recreation on each render var createPathString = function createPathString(index, field) { return "groups." + index + ".data." + field; }; var useReformGroups = function useReformGroups(_ref) { var methods = _ref.methods, minGroups = _ref.minGroups, maxGroups = _ref.maxGroups, defaultData = _ref.defaultData, onChange = _ref.onChange; // Memoize defaultData to prevent unnecessary re-renders var memoizedDefaultData = React.useMemo(function () { return defaultData; }, [ // Stringify defaultData to compare by value instead of reference JSON.stringify(defaultData)]); var _useFieldArray = reactHookForm.useFieldArray({ control: methods.control, name: 'groups' }), fields = _useFieldArray.fields, append = _useFieldArray.append, remove = _useFieldArray.remove, move = _useFieldArray.move, update = _useFieldArray.update, replace = _useFieldArray.replace; // Memoize this function to prevent recreation on each render var createNewGroup = React.useCallback(function (data) { if (data === void 0) { data = memoizedDefaultData; } return { id: Math.random().toString(36).substring(2, 9), data: _extends({}, data) }; }, [memoizedDefaultData]); var addGroup = useMemoizedCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee() { var isValid; return _regeneratorRuntime().wrap(function _callee$(_context) { while (1) switch (_context.prev = _context.next) { case 0: if (!(fields.length >= maxGroups)) { _context.next = 2; break; } return _context.abrupt("return", { isValid: false, message: "Maximum " + maxGroups + " groups allowed" }); case 2: append(createNewGroup()); _context.next = 5; re