Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
742 lines
26 KiB
Plaintext
742 lines
26 KiB
Plaintext
'use client';
|
|
|
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
import { dequal } from 'dequal/lite'; // lite: no need for Map and Set support
|
|
import { useRouter } from 'next/navigation.js';
|
|
import { serialize } from 'object-to-formdata';
|
|
import { deepCopyObjectSimpleWithoutReactComponents, getDataByPath as getDataByPathFunc, getSiblingData as getSiblingDataFunc, hasDraftValidationEnabled, reduceFieldsToValues, wait } from 'payload/shared';
|
|
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react';
|
|
import { toast } from 'sonner';
|
|
import { FieldErrorsToast } from '../../elements/Toasts/fieldErrors.js';
|
|
import { useDebouncedEffect } from '../../hooks/useDebouncedEffect.js';
|
|
import { useEffectEvent } from '../../hooks/useEffectEvent.js';
|
|
import { useQueue } from '../../hooks/useQueue.js';
|
|
import { useThrottledEffect } from '../../hooks/useThrottledEffect.js';
|
|
import { useAuth } from '../../providers/Auth/index.js';
|
|
import { useConfig } from '../../providers/Config/index.js';
|
|
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js';
|
|
import { useLocale } from '../../providers/Locale/index.js';
|
|
import { useOperation } from '../../providers/Operation/index.js';
|
|
import { useRouteTransition } from '../../providers/RouteTransition/index.js';
|
|
import { useServerFunctions } from '../../providers/ServerFunctions/index.js';
|
|
import { useTranslation } from '../../providers/Translation/index.js';
|
|
import { useUploadHandlers } from '../../providers/UploadHandlers/index.js';
|
|
import { abortAndIgnore, handleAbortRef } from '../../utilities/abortAndIgnore.js';
|
|
import { requests } from '../../utilities/api.js';
|
|
import { BackgroundProcessingContext, DocumentFormContext, FormContext, FormFieldsContext, FormWatchContext, InitializingContext, ModifiedContext, ProcessingContext, SubmittedContext, useDocumentForm } from './context.js';
|
|
import { errorMessages } from './errorMessages.js';
|
|
import { fieldReducer } from './fieldReducer.js';
|
|
import { initContextState } from './initContextState.js';
|
|
const baseClass = 'form';
|
|
export const Form = props => {
|
|
const {
|
|
id,
|
|
collectionSlug,
|
|
docConfig,
|
|
docPermissions,
|
|
getDocPreferences,
|
|
globalSlug
|
|
} = useDocumentInfo();
|
|
const validateDrafts = hasDraftValidationEnabled(docConfig);
|
|
const {
|
|
action,
|
|
beforeSubmit,
|
|
children,
|
|
className,
|
|
disabled: disabledFromProps,
|
|
disableSuccessStatus,
|
|
disableValidationOnSubmit,
|
|
// fields: fieldsFromProps = collection?.fields || global?.fields,
|
|
el,
|
|
handleResponse,
|
|
initialState,
|
|
isDocumentForm,
|
|
isInitializing: initializingFromProps,
|
|
onChange,
|
|
onSubmit,
|
|
onSuccess,
|
|
redirect,
|
|
submitted: submittedFromProps,
|
|
uuid,
|
|
waitForAutocomplete
|
|
} = props;
|
|
const method = 'method' in props ? props?.method : undefined;
|
|
const router = useRouter();
|
|
const documentForm = useDocumentForm();
|
|
const {
|
|
code: locale
|
|
} = useLocale();
|
|
const {
|
|
i18n,
|
|
t
|
|
} = useTranslation();
|
|
const {
|
|
refreshCookie,
|
|
user
|
|
} = useAuth();
|
|
const operation = useOperation();
|
|
const {
|
|
queueTask
|
|
} = useQueue();
|
|
const {
|
|
getFormState
|
|
} = useServerFunctions();
|
|
const {
|
|
startRouteTransition
|
|
} = useRouteTransition();
|
|
const {
|
|
getUploadHandler
|
|
} = useUploadHandlers();
|
|
const {
|
|
config
|
|
} = useConfig();
|
|
const [disabled, setDisabled] = useState(disabledFromProps || false);
|
|
const [isMounted, setIsMounted] = useState(false);
|
|
const [submitted, setSubmitted] = useState(false);
|
|
/**
|
|
* Tracks wether the form state passes validation.
|
|
* For example the state could be submitted but invalid as field errors have been returned.
|
|
*/
|
|
const [isValid, setIsValid] = useState(true);
|
|
const [initializing, setInitializing] = useState(initializingFromProps);
|
|
const [processing, setProcessing] = useState(false);
|
|
/**
|
|
* Determines whether the form is processing asynchronously in the background, e.g. autosave is running.
|
|
* Useful to determine whether to disable the form or queue other processes while in flight, e.g. disable manual submits while an autosave is running.
|
|
*/
|
|
const [backgroundProcessing, _setBackgroundProcessing] = useState(false);
|
|
/**
|
|
* A ref that can be read within the `setModified` interceptor.
|
|
* Dependents of this state can read it immediately without needing to wait for a render cycle.
|
|
*/
|
|
const backgroundProcessingRef = useRef(backgroundProcessing);
|
|
/**
|
|
* Flag to track if the form was modified _during a submission_, e.g. while autosave is running.
|
|
* Useful in order to avoid resetting `modified` to false wrongfully after a submit.
|
|
* For example, if the user modifies a field while the a background process (autosave) is running,
|
|
* we need to ensure that after the submit completes, the `modified` state remains true.
|
|
*/
|
|
const modifiedWhileProcessingRef = useRef(false);
|
|
/**
|
|
* Intercept the `setBackgroundProcessing` method to keep the ref in sync.
|
|
* See the `backgroundProcessingRef` for more details.
|
|
*/
|
|
const setBackgroundProcessing = useCallback(backgroundProcessing_0 => {
|
|
backgroundProcessingRef.current = backgroundProcessing_0;
|
|
_setBackgroundProcessing(backgroundProcessing_0);
|
|
}, []);
|
|
const [modified, _setModified] = useState(false);
|
|
/**
|
|
* Intercept the `setModified` method to track whether the event happened during background processing.
|
|
* See the `modifiedWhileProcessingRef` ref for more details.
|
|
*/
|
|
const setModified = useCallback(modified_0 => {
|
|
if (backgroundProcessingRef.current) {
|
|
modifiedWhileProcessingRef.current = true;
|
|
}
|
|
_setModified(modified_0);
|
|
}, []);
|
|
const formRef = useRef(null);
|
|
const contextRef = useRef({});
|
|
const abortResetFormRef = useRef(null);
|
|
const isFirstRenderRef = useRef(true);
|
|
const fieldsReducer = useReducer(fieldReducer, {}, () => initialState);
|
|
const [formState, dispatchFields] = fieldsReducer;
|
|
contextRef.current.fields = formState;
|
|
const prevFormState = useRef(formState);
|
|
const validateForm = useCallback(async () => {
|
|
const validatedFieldState = {};
|
|
let isValid_0 = true;
|
|
const data = contextRef.current.getData();
|
|
const validationPromises = Object.entries(contextRef.current.fields).map(async ([path, field]) => {
|
|
const validatedField = field;
|
|
const pathSegments = path ? path.split('.') : [];
|
|
if (field.passesCondition !== false) {
|
|
let validationResult = validatedField.valid;
|
|
if ('validate' in field && typeof field.validate === 'function') {
|
|
let valueToValidate = field.value;
|
|
if (field?.rows && Array.isArray(field.rows)) {
|
|
valueToValidate = contextRef.current.getDataByPath(path);
|
|
}
|
|
validationResult = await field.validate(valueToValidate, {
|
|
...field,
|
|
id,
|
|
collectionSlug,
|
|
// If there is a parent document form, we can get the data from that form
|
|
blockData: undefined,
|
|
data: documentForm?.getData ? documentForm.getData() : data,
|
|
event: 'submit',
|
|
operation,
|
|
path: pathSegments,
|
|
preferences: {},
|
|
req: {
|
|
payload: {
|
|
config
|
|
},
|
|
t,
|
|
user
|
|
},
|
|
siblingData: contextRef.current.getSiblingData(path)
|
|
});
|
|
if (typeof validationResult === 'string') {
|
|
validatedField.errorMessage = validationResult;
|
|
validatedField.valid = false;
|
|
} else {
|
|
validatedField.valid = true;
|
|
validatedField.errorMessage = undefined;
|
|
}
|
|
}
|
|
if (validatedField.valid === false) {
|
|
isValid_0 = false;
|
|
}
|
|
}
|
|
validatedFieldState[path] = validatedField;
|
|
});
|
|
await Promise.all(validationPromises);
|
|
if (!dequal(contextRef.current.fields, validatedFieldState)) {
|
|
dispatchFields({
|
|
type: 'REPLACE_STATE',
|
|
state: validatedFieldState
|
|
});
|
|
}
|
|
setIsValid(isValid_0);
|
|
return isValid_0;
|
|
}, [collectionSlug, config, dispatchFields, id, operation, t, user, documentForm]);
|
|
const submit = useCallback(async (options, e) => {
|
|
const {
|
|
acceptValues = true,
|
|
action: actionArg = action,
|
|
context,
|
|
disableFormWhileProcessing = true,
|
|
disableSuccessStatus: disableSuccessStatusFromArgs,
|
|
method: methodToUse = method,
|
|
overrides: overridesFromArgs = {},
|
|
skipValidation
|
|
} = options || {};
|
|
const disableToast = disableSuccessStatusFromArgs ?? disableSuccessStatus;
|
|
if (disabled) {
|
|
if (e) {
|
|
e.preventDefault();
|
|
}
|
|
return;
|
|
}
|
|
// create new toast promise which will resolve manually later
|
|
let errorToast, successToast;
|
|
const promise = new Promise((resolve, reject) => {
|
|
successToast = resolve;
|
|
errorToast = reject;
|
|
});
|
|
const hasFormSubmitAction = actionArg || typeof action === 'string' || typeof action === 'function';
|
|
if (redirect || disableToast || !hasFormSubmitAction) {
|
|
// Do not show submitting toast, as the promise toast may never disappear under these conditions.
|
|
// Instead, make successToast() or errorToast() throw toast.success / toast.error
|
|
successToast = data_0 => toast.success(data_0);
|
|
errorToast = data_1 => toast.error(data_1);
|
|
} else {
|
|
toast.promise(promise, {
|
|
error: data_2 => {
|
|
return data_2;
|
|
},
|
|
loading: t('general:submitting'),
|
|
success: data_3 => {
|
|
return data_3;
|
|
}
|
|
});
|
|
}
|
|
if (e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
}
|
|
if (disableFormWhileProcessing) {
|
|
setProcessing(true);
|
|
setDisabled(true);
|
|
}
|
|
if (waitForAutocomplete) {
|
|
await wait(100);
|
|
}
|
|
const data_4 = reduceFieldsToValues(contextRef.current.fields, true);
|
|
const serializableFormState = deepCopyObjectSimpleWithoutReactComponents(contextRef.current.fields, {
|
|
excludeFiles: true
|
|
});
|
|
// Execute server side validations
|
|
if (Array.isArray(beforeSubmit)) {
|
|
let revalidatedFormState;
|
|
await beforeSubmit.reduce(async (priorOnChange, beforeSubmitFn) => {
|
|
await priorOnChange;
|
|
const result = await beforeSubmitFn({
|
|
formState: serializableFormState
|
|
});
|
|
revalidatedFormState = result;
|
|
}, Promise.resolve());
|
|
const isValid_1 = Object.entries(revalidatedFormState).every(([, field_0]) => field_0.valid !== false);
|
|
setIsValid(isValid_1);
|
|
if (!isValid_1) {
|
|
setProcessing(false);
|
|
setSubmitted(true);
|
|
setDisabled(false);
|
|
return dispatchFields({
|
|
type: 'REPLACE_STATE',
|
|
state: revalidatedFormState
|
|
});
|
|
}
|
|
}
|
|
const isValid_2 = skipValidation || disableValidationOnSubmit ? true : await contextRef.current.validateForm();
|
|
setIsValid(isValid_2);
|
|
// If not valid, prevent submission
|
|
if (!isValid_2) {
|
|
errorToast(t('error:correctInvalidFields'));
|
|
setProcessing(false);
|
|
setSubmitted(true);
|
|
setDisabled(false);
|
|
return;
|
|
}
|
|
let overrides = {};
|
|
if (typeof overridesFromArgs === 'function') {
|
|
overrides = overridesFromArgs(contextRef.current.fields);
|
|
} else if (typeof overridesFromArgs === 'object') {
|
|
overrides = overridesFromArgs;
|
|
}
|
|
// If submit handler comes through via props, run that
|
|
if (onSubmit) {
|
|
for (const [key, value] of Object.entries(overrides)) {
|
|
data_4[key] = value;
|
|
}
|
|
onSubmit(contextRef.current.fields, data_4);
|
|
}
|
|
if (!hasFormSubmitAction) {
|
|
// No action provided, so we should return. An example where this happens are lexical link drawers. Upon submitting the drawer, we
|
|
// want to close it without submitting the form. Stuff like validation would be handled by lexical before this, through beforeSubmit
|
|
setProcessing(false);
|
|
setSubmitted(true);
|
|
setDisabled(false);
|
|
return;
|
|
}
|
|
try {
|
|
const formData = await contextRef.current.createFormData(overrides, {
|
|
data: data_4,
|
|
mergeOverrideData: Boolean(typeof overridesFromArgs !== 'function')
|
|
});
|
|
let res;
|
|
if (typeof actionArg === 'string') {
|
|
res = await requests[methodToUse.toLowerCase()](actionArg, {
|
|
body: formData,
|
|
headers: {
|
|
'Accept-Language': i18n.language
|
|
}
|
|
});
|
|
} else if (typeof action === 'function') {
|
|
res = await action(formData);
|
|
}
|
|
if (!modifiedWhileProcessingRef.current) {
|
|
setModified(false);
|
|
} else {
|
|
modifiedWhileProcessingRef.current = false;
|
|
}
|
|
setDisabled(false);
|
|
if (typeof handleResponse === 'function') {
|
|
handleResponse(res, successToast, errorToast);
|
|
return;
|
|
}
|
|
const contentType = res.headers.get('content-type');
|
|
const isJSON = contentType && contentType.indexOf('application/json') !== -1;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
let json = {};
|
|
if (isJSON) {
|
|
json = await res.json();
|
|
}
|
|
if (res.status < 400) {
|
|
if (typeof onSuccess === 'function') {
|
|
const newFormState = await onSuccess(json, {
|
|
context,
|
|
formState: serializableFormState
|
|
});
|
|
if (newFormState) {
|
|
dispatchFields({
|
|
type: 'MERGE_SERVER_STATE',
|
|
acceptValues,
|
|
prevStateRef: prevFormState,
|
|
serverState: newFormState
|
|
});
|
|
}
|
|
}
|
|
setSubmitted(false);
|
|
setProcessing(false);
|
|
if (redirect) {
|
|
startRouteTransition(() => router.push(redirect));
|
|
} else if (!disableToast) {
|
|
successToast(json.message || t('general:submissionSuccessful'));
|
|
}
|
|
} else {
|
|
setProcessing(false);
|
|
setSubmitted(true);
|
|
// When there was an error submitting a draft,
|
|
// set the form state to unsubmitted, to not trigger visible form validation on changes after the failed submit.
|
|
// Also keep the form as modified so the save button remains enabled for retry.
|
|
if (overridesFromArgs['_status'] === 'draft') {
|
|
setModified(true);
|
|
if (!validateDrafts) {
|
|
setSubmitted(false);
|
|
}
|
|
}
|
|
contextRef.current = {
|
|
...contextRef.current
|
|
}; // triggers rerender of all components that subscribe to form
|
|
if (json.message) {
|
|
errorToast(json.message);
|
|
return;
|
|
}
|
|
if (Array.isArray(json.errors)) {
|
|
const [fieldErrors, nonFieldErrors] = json.errors.reduce(([fieldErrs, nonFieldErrs], err_0) => {
|
|
const newFieldErrs = [];
|
|
const newNonFieldErrs = [];
|
|
if (err_0?.message) {
|
|
newNonFieldErrs.push(err_0);
|
|
}
|
|
if (Array.isArray(err_0?.data?.errors)) {
|
|
err_0.data?.errors.forEach(dataError => {
|
|
if (dataError?.path) {
|
|
newFieldErrs.push(dataError);
|
|
} else {
|
|
newNonFieldErrs.push(dataError);
|
|
}
|
|
});
|
|
}
|
|
return [[...fieldErrs, ...newFieldErrs], [...nonFieldErrs, ...newNonFieldErrs]];
|
|
}, [[], []]);
|
|
setIsValid(false);
|
|
dispatchFields({
|
|
type: 'ADD_SERVER_ERRORS',
|
|
errors: fieldErrors
|
|
});
|
|
nonFieldErrors.forEach(err_1 => {
|
|
errorToast(/*#__PURE__*/_jsx(FieldErrorsToast, {
|
|
errorMessage: err_1.message || t('error:unknown')
|
|
}));
|
|
});
|
|
return;
|
|
}
|
|
const message = errorMessages?.[res.status] || res?.statusText || t('error:unknown');
|
|
errorToast(message);
|
|
}
|
|
return {
|
|
formState: contextRef.current.fields,
|
|
res
|
|
};
|
|
} catch (err) {
|
|
console.error('Error submitting form', err); // eslint-disable-line no-console
|
|
setProcessing(false);
|
|
setSubmitted(true);
|
|
setDisabled(false);
|
|
errorToast(err.message);
|
|
}
|
|
}, [beforeSubmit, startRouteTransition, action, disableSuccessStatus, disableValidationOnSubmit, disabled, dispatchFields, handleResponse, method, onSubmit, onSuccess, redirect, router, t, i18n, validateDrafts, waitForAutocomplete, setModified, setSubmitted]);
|
|
const getFields = useCallback(() => contextRef.current.fields, []);
|
|
const getField = useCallback(path_0 => contextRef.current.fields[path_0], []);
|
|
const getData = useCallback(() => reduceFieldsToValues(contextRef.current.fields, true), []);
|
|
const getSiblingData = useCallback(path_1 => getSiblingDataFunc(contextRef.current.fields, path_1), []);
|
|
const getDataByPath = useCallback(path_2 => getDataByPathFunc(contextRef.current.fields, path_2), []);
|
|
const createFormData = useCallback(async (overrides_0, {
|
|
data: dataFromArgs,
|
|
mergeOverrideData = true
|
|
}) => {
|
|
let data_5 = dataFromArgs || reduceFieldsToValues(contextRef.current.fields, true);
|
|
let file = data_5?.file;
|
|
if (docConfig && 'upload' in docConfig && docConfig.upload && file) {
|
|
delete data_5.file;
|
|
const handler = getUploadHandler({
|
|
collectionSlug
|
|
});
|
|
if (typeof handler === 'function') {
|
|
let filename = file.name;
|
|
const clientUploadContext = await handler({
|
|
file,
|
|
updateFilename: value_0 => {
|
|
filename = value_0;
|
|
}
|
|
});
|
|
file = JSON.stringify({
|
|
clientUploadContext,
|
|
collectionSlug,
|
|
filename,
|
|
mimeType: file.type,
|
|
size: file.size
|
|
});
|
|
}
|
|
}
|
|
if (mergeOverrideData) {
|
|
data_5 = {
|
|
...data_5,
|
|
...overrides_0
|
|
};
|
|
} else {
|
|
data_5 = overrides_0;
|
|
}
|
|
const dataToSerialize = {
|
|
_payload: JSON.stringify(data_5)
|
|
};
|
|
if (docConfig && 'upload' in docConfig && docConfig.upload && file) {
|
|
dataToSerialize.file = file;
|
|
}
|
|
// nullAsUndefineds is important to allow uploads and relationship fields to clear themselves
|
|
const formData_0 = serialize(dataToSerialize, {
|
|
indices: true,
|
|
nullsAsUndefineds: false
|
|
});
|
|
return formData_0;
|
|
}, [collectionSlug, docConfig, getUploadHandler]);
|
|
const reset = useCallback(async data_6 => {
|
|
const controller = handleAbortRef(abortResetFormRef);
|
|
const docPreferences = await getDocPreferences();
|
|
const {
|
|
state: newState
|
|
} = await getFormState({
|
|
id,
|
|
collectionSlug,
|
|
data: data_6,
|
|
docPermissions,
|
|
docPreferences,
|
|
globalSlug,
|
|
locale,
|
|
operation,
|
|
renderAllFields: true,
|
|
schemaPath: collectionSlug ? collectionSlug : globalSlug,
|
|
signal: controller.signal,
|
|
skipValidation: true
|
|
});
|
|
contextRef.current = {
|
|
...initContextState
|
|
};
|
|
setModified(false);
|
|
dispatchFields({
|
|
type: 'REPLACE_STATE',
|
|
state: newState
|
|
});
|
|
abortResetFormRef.current = null;
|
|
}, [collectionSlug, dispatchFields, globalSlug, id, operation, getFormState, docPermissions, getDocPreferences, locale, setModified]);
|
|
const replaceState = useCallback(state => {
|
|
contextRef.current = {
|
|
...initContextState
|
|
};
|
|
setModified(false);
|
|
dispatchFields({
|
|
type: 'REPLACE_STATE',
|
|
state
|
|
});
|
|
}, [dispatchFields, setModified]);
|
|
const addFieldRow = useCallback(({
|
|
blockType,
|
|
path: path_3,
|
|
rowIndex: rowIndexArg,
|
|
subFieldState
|
|
}) => {
|
|
const newRows = getDataByPath(path_3) || [];
|
|
const rowIndex = rowIndexArg === undefined ? newRows.length : rowIndexArg;
|
|
// dispatch ADD_ROW adds a blank row to local form state.
|
|
// This performs no form state request, as the debounced onChange effect will do that for us.
|
|
dispatchFields({
|
|
type: 'ADD_ROW',
|
|
blockType,
|
|
path: path_3,
|
|
rowIndex,
|
|
subFieldState
|
|
});
|
|
setModified(true);
|
|
}, [dispatchFields, getDataByPath, setModified]);
|
|
const moveFieldRow = useCallback(({
|
|
moveFromIndex,
|
|
moveToIndex,
|
|
path: path_4
|
|
}) => {
|
|
dispatchFields({
|
|
type: 'MOVE_ROW',
|
|
moveFromIndex,
|
|
moveToIndex,
|
|
path: path_4
|
|
});
|
|
setModified(true);
|
|
}, [dispatchFields, setModified]);
|
|
const removeFieldRow = useCallback(({
|
|
path: path_5,
|
|
rowIndex: rowIndex_0
|
|
}) => {
|
|
dispatchFields({
|
|
type: 'REMOVE_ROW',
|
|
path: path_5,
|
|
rowIndex: rowIndex_0
|
|
});
|
|
setModified(true);
|
|
}, [dispatchFields, setModified]);
|
|
const replaceFieldRow = useCallback(({
|
|
blockType: blockType_0,
|
|
path: path_6,
|
|
rowIndex: rowIndexArg_0,
|
|
subFieldState: subFieldState_0
|
|
}) => {
|
|
const currentRows = getDataByPath(path_6);
|
|
const rowIndex_1 = rowIndexArg_0 === undefined ? currentRows.length : rowIndexArg_0;
|
|
dispatchFields({
|
|
type: 'REPLACE_ROW',
|
|
blockType: blockType_0,
|
|
path: path_6,
|
|
rowIndex: rowIndex_1,
|
|
subFieldState: subFieldState_0
|
|
});
|
|
setModified(true);
|
|
}, [dispatchFields, getDataByPath, setModified]);
|
|
useEffect(() => {
|
|
const abortOnChange = abortResetFormRef.current;
|
|
return () => {
|
|
abortAndIgnore(abortOnChange);
|
|
};
|
|
}, []);
|
|
useEffect(() => {
|
|
if (initializingFromProps !== undefined) {
|
|
setInitializing(initializingFromProps);
|
|
}
|
|
}, [initializingFromProps]);
|
|
contextRef.current.submit = submit;
|
|
contextRef.current.getFields = getFields;
|
|
contextRef.current.getField = getField;
|
|
contextRef.current.getData = getData;
|
|
contextRef.current.getSiblingData = getSiblingData;
|
|
contextRef.current.getDataByPath = getDataByPath;
|
|
contextRef.current.validateForm = validateForm;
|
|
contextRef.current.createFormData = createFormData;
|
|
contextRef.current.setModified = setModified;
|
|
contextRef.current.setProcessing = setProcessing;
|
|
contextRef.current.setBackgroundProcessing = setBackgroundProcessing;
|
|
contextRef.current.setSubmitted = setSubmitted;
|
|
contextRef.current.setIsValid = setIsValid;
|
|
contextRef.current.disabled = disabled;
|
|
contextRef.current.setDisabled = setDisabled;
|
|
contextRef.current.formRef = formRef;
|
|
contextRef.current.reset = reset;
|
|
contextRef.current.replaceState = replaceState;
|
|
contextRef.current.dispatchFields = dispatchFields;
|
|
contextRef.current.addFieldRow = addFieldRow;
|
|
contextRef.current.removeFieldRow = removeFieldRow;
|
|
contextRef.current.moveFieldRow = moveFieldRow;
|
|
contextRef.current.replaceFieldRow = replaceFieldRow;
|
|
contextRef.current.uuid = uuid;
|
|
contextRef.current.initializing = initializing;
|
|
contextRef.current.isValid = isValid;
|
|
useEffect(() => {
|
|
setIsMounted(true);
|
|
}, []);
|
|
useEffect(() => {
|
|
if (typeof disabledFromProps === 'boolean') {
|
|
setDisabled(disabledFromProps);
|
|
}
|
|
}, [disabledFromProps]);
|
|
useEffect(() => {
|
|
if (typeof submittedFromProps === 'boolean') {
|
|
setSubmitted(submittedFromProps);
|
|
}
|
|
}, [submittedFromProps]);
|
|
useEffect(() => {
|
|
if (initialState) {
|
|
contextRef.current = {
|
|
...initContextState
|
|
};
|
|
dispatchFields({
|
|
type: 'REPLACE_STATE',
|
|
optimize: false,
|
|
sanitize: true,
|
|
state: initialState
|
|
});
|
|
}
|
|
}, [initialState, dispatchFields]);
|
|
useThrottledEffect(() => {
|
|
refreshCookie();
|
|
}, 15000, [formState]);
|
|
const handleLocaleChange = useEffectEvent(() => {
|
|
contextRef.current = {
|
|
...contextRef.current
|
|
}; // triggers rerender of all components that subscribe to form
|
|
setModified(false);
|
|
});
|
|
useEffect(() => {
|
|
handleLocaleChange();
|
|
}, [locale]);
|
|
const classes = [className, baseClass].filter(Boolean).join(' ');
|
|
const executeOnChange = useEffectEvent(submitted_0 => {
|
|
queueTask(async () => {
|
|
if (Array.isArray(onChange)) {
|
|
let serverState;
|
|
for (const onChangeFn of onChange) {
|
|
// Edit view default onChange is in packages/ui/src/views/Edit/index.tsx. This onChange usually sends a form state request
|
|
serverState = await onChangeFn({
|
|
formState: deepCopyObjectSimpleWithoutReactComponents(formState, {
|
|
excludeFiles: true
|
|
}),
|
|
submitted: submitted_0
|
|
});
|
|
}
|
|
dispatchFields({
|
|
type: 'MERGE_SERVER_STATE',
|
|
prevStateRef: prevFormState,
|
|
serverState
|
|
});
|
|
}
|
|
});
|
|
});
|
|
useDebouncedEffect(() => {
|
|
if ((isFirstRenderRef.current || !dequal(formState, prevFormState.current)) && modified) {
|
|
executeOnChange(submitted);
|
|
}
|
|
prevFormState.current = formState;
|
|
isFirstRenderRef.current = false;
|
|
}, [modified, submitted, formState], 250);
|
|
const DocumentFormContextComponent = isDocumentForm ? DocumentFormContext : React.Fragment;
|
|
const documentFormContextProps = isDocumentForm ? {
|
|
value: contextRef.current
|
|
} : {};
|
|
const El = el || 'form';
|
|
return /*#__PURE__*/_jsx(El, {
|
|
action: typeof action === 'function' ? void action : action,
|
|
className: classes,
|
|
/**
|
|
* data-form-ready signals if the form is ready to be used. This is used by our e2e tests
|
|
* to wait for the form to be ready before interacting with it, reducing flakiness if the test is run in
|
|
* slow network conditions.
|
|
*/
|
|
"data-form-ready": !processing && isMounted && !initializing,
|
|
method: method,
|
|
noValidate: true,
|
|
onSubmit: e_0 => void contextRef.current.submit({}, e_0),
|
|
ref: formRef,
|
|
children: /*#__PURE__*/_jsx(DocumentFormContextComponent, {
|
|
...documentFormContextProps,
|
|
children: /*#__PURE__*/_jsx(FormContext, {
|
|
value: contextRef.current,
|
|
children: /*#__PURE__*/_jsx(FormWatchContext, {
|
|
value: {
|
|
fields: formState,
|
|
...contextRef.current
|
|
},
|
|
children: /*#__PURE__*/_jsx(SubmittedContext, {
|
|
value: submitted,
|
|
children: /*#__PURE__*/_jsx(InitializingContext, {
|
|
value: !isMounted || isMounted && initializing,
|
|
children: /*#__PURE__*/_jsx(ProcessingContext, {
|
|
value: processing,
|
|
children: /*#__PURE__*/_jsx(BackgroundProcessingContext, {
|
|
value: backgroundProcessing,
|
|
children: /*#__PURE__*/_jsx(ModifiedContext, {
|
|
value: modified,
|
|
children: /*#__PURE__*/_jsx(FormFieldsContext.Provider, {
|
|
value: fieldsReducer,
|
|
children: children
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
});
|
|
};
|
|
export { DocumentFormContext, FormContext, FormFieldsContext, FormWatchContext, ModifiedContext, ProcessingContext, SubmittedContext, useAllFormFields, useDocumentForm, useForm, useFormFields, useFormModified, useFormProcessing, useFormSubmitted, useWatchForm } from './context.js';
|
|
//# sourceMappingURL=index.js.map |