@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
1,216 lines (1,215 loc) • 44 kB
JavaScript
"use client";
import _JSON$parse from "core-js-pure/stable/json/parse.js";
import _pushInstanceProperty from "core-js-pure/stable/instance/push.js";
import React, { useRef, useMemo, useCallback, useReducer, useEffect, useContext } from 'react';
import pointer from "../../utils/json-pointer/index.js";
import { makeAjvInstance, ajvErrorsToFormErrors, isZodSchema, createZodValidator, zodErrorsToFormErrors } from "../../utils/index.js";
import { debounce, warn } from "../../../../shared/helpers.js";
import FieldPropsProvider from "../../Field/Provider/index.js";
import useUpdateEffect from "../../../../shared/helpers/useUpdateEffect.js";
import GlobalStatusProvider from "../../../../components/global-status/GlobalStatusProvider.js";
import { isAsync } from "../../../../shared/helpers/isAsync.js";
import { createReferenceKey, useSharedState } from "../../../../shared/helpers/useSharedState.js";
import SharedContext from "../../../../shared/Context.js";
import useTranslation from "../../hooks/useTranslation.js";
import { appendPath } from "../../hooks/usePath.js";
import DataContext from "../Context.js";
import { structuredClone } from "../../../../shared/helpers/structuredClone.js";
import { useIsomorphicLayoutEffect as useLayoutEffect } from "../../../../shared/helpers/useIsomorphicLayoutEffect.js";
const isArrayJsonPointer = /^\/\d+(\/|$)/;
export default function Provider(props) {
var _sharedAttachments$da, _translations$resolve;
const [, forceUpdate] = useReducer(() => ({}), {});
const {
id,
globalStatusId = 'main',
defaultData,
emptyData,
data,
schema,
onChange,
onPathChange,
onSubmit,
onSubmitRequest,
onSubmitComplete,
onCommit,
onClear,
onUpdateDataValue,
scrollTopOnSubmit,
minimumAsyncBehaviorTime,
asyncSubmitTimeout,
sessionStorageId,
ajvInstance,
transformIn,
transformOut,
filterSubmitData,
countryCode,
locale,
translations,
required,
errorMessages,
isolate,
children,
...rest
} = props;
if (data !== undefined && sessionStorageId !== undefined) {
throw new Error('Use "defaultData" instead of "data" when using sessionStorageId');
}
const {
hasContext
} = useContext(DataContext) || {};
if (hasContext && !isolate) {
throw new Error('DataContext (Form.Handler) cannot be nested');
}
const formElementRef = useRef(null);
const {
locale: sharedLocale
} = useContext(SharedContext) || {};
const translation = useTranslation().Field;
const ajvRef = useRef();
const getAjvInstance = useCallback((instance = ajvInstance) => {
if (!ajvRef.current) {
ajvRef.current = makeAjvInstance(instance);
}
return ajvRef.current;
}, [ajvInstance]);
useEffect(() => {
if (schema && !isZodSchema(schema) && !ajvInstance) {
warn('Form.Handler received a JSON Schema but no ajvInstance. Provide ajvInstance={makeAjvInstance()} to enable schema validation.');
}
}, [schema, ajvInstance]);
const mountedFieldsRef = useRef(new Map());
const snapshotsRef = useRef(new Map());
const existingFieldsRef = useRef(new Map());
const hasVisibleErrorRef = useRef(new Map());
const errorsRef = useRef();
const addSetShowAllErrorsRef = useRef([]);
const showAllErrorsRef = useRef(false);
const setShowAllErrors = useCallback(showAllErrors => {
showAllErrorsRef.current = showAllErrors ? Date.now() : showAllErrors;
forceUpdate();
addSetShowAllErrorsRef.current.forEach(fn => fn === null || fn === void 0 ? void 0 : fn(showAllErrors));
}, []);
const executeSectionValidators = useCallback(contextErrors => {
if (!sectionSchemasRef.current.size) {
const hasContextErrors = contextErrors && Object.keys(contextErrors).length > 0;
errorsRef.current = hasContextErrors ? contextErrors : undefined;
return errorsRef.current;
}
const sectionErrors = {};
sectionSchemasRef.current.forEach(({
path,
schema,
validator
}) => {
if (!validator) {
return;
}
const normalizedPath = path || '/';
const sectionData = normalizedPath === '/' ? internalDataRef.current : pointer.has(internalDataRef.current, normalizedPath) ? pointer.get(internalDataRef.current, normalizedPath) : undefined;
const validationResult = validator(sectionData);
if (validationResult === true) {
return;
}
let errors = {};
if (isZodSchema(schema)) {
const issues = validator.errors;
if (issues !== null && issues !== void 0 && issues.length) {
errors = zodErrorsToFormErrors(issues);
}
} else {
const ajvValidator = validator;
const ajvErrors = ajvValidator.errors;
if (ajvErrors && ajvErrors.length) {
errors = ajvErrorsToFormErrors(ajvErrors, sectionData);
}
}
Object.entries(errors).forEach(([errorPath, error]) => {
const combinedPath = appendPath(normalizedPath, errorPath);
sectionErrors[combinedPath] = error;
});
});
const hasSectionErrors = Object.keys(sectionErrors).length > 0;
const hasContextErrors = contextErrors && Object.keys(contextErrors).length > 0;
if (!hasSectionErrors && !hasContextErrors) {
errorsRef.current = undefined;
return undefined;
}
errorsRef.current = {
...(contextErrors !== null && contextErrors !== void 0 ? contextErrors : {}),
...(hasSectionErrors ? sectionErrors : {})
};
return errorsRef.current;
}, []);
const revealError = useCallback((path, hasError) => {
if (hasError) {
hasVisibleErrorRef.current.set(path, hasError);
} else {
hasVisibleErrorRef.current.delete(path);
}
forceUpdate();
}, []);
const submitStateRef = useRef({});
const setSubmitState = useCallback(state => {
submitStateRef.current = {
...submitStateRef.current,
...state
};
forceUpdate();
}, []);
const formStateRef = useRef();
const activeSubmitButtonIdRef = useRef();
const keepPending = useRef(false);
const setFormState = useCallback((formState, options = {}) => {
if (typeof (options === null || options === void 0 ? void 0 : options.keepPending) === 'boolean') {
keepPending.current = options === null || options === void 0 ? void 0 : options.keepPending;
}
formStateRef.current = formState;
forceUpdate();
}, []);
const setActiveSubmitButtonId = useCallback(id => {
activeSubmitButtonIdRef.current = id;
forceUpdate();
}, []);
const fieldErrorRef = useRef({});
const fieldStateRef = useRef({});
const validationVersionRef = useRef(0);
const bumpValidationVersionRef = useRef(() => null);
const initialData = useMemo(() => {
if (sessionStorageId && typeof window !== 'undefined') {
var _window$sessionStorag;
const sessionDataJSON = (_window$sessionStorag = window.sessionStorage) === null || _window$sessionStorag === void 0 ? void 0 : _window$sessionStorag.getItem(sessionStorageId);
if (sessionDataJSON) {
try {
return _JSON$parse(sessionDataJSON);
} catch (e) {
var _window$sessionStorag2;
(_window$sessionStorag2 = window.sessionStorage) === null || _window$sessionStorag2 === void 0 || _window$sessionStorag2.removeItem(sessionStorageId);
}
}
}
return data !== null && data !== void 0 ? data : defaultData;
}, []);
const internalDataRef = useRef(initialData);
const isEmptyDataRef = useRef(false);
const ajvValidatorRef = useRef();
const sectionSchemasRef = useRef(new Map());
const sectionSchemaPathsRef = useRef(new Set());
const createUnifiedValidator = useCallback(schema => {
if (isZodSchema(schema)) {
const zodValidator = createZodValidator(schema);
const unifiedValidator = value => {
const result = zodValidator(value);
if (result === true) {
return true;
} else {
unifiedValidator.errors = result.issues;
return false;
}
};
return unifiedValidator;
} else {
var _getAjvInstance;
return (_getAjvInstance = getAjvInstance()) === null || _getAjvInstance === void 0 ? void 0 : _getAjvInstance.compile(schema);
}
}, []);
useMemo(() => {
if (schema) {
ajvValidatorRef.current = createUnifiedValidator(schema);
}
}, [schema, createUnifiedValidator]);
const executeAjvValidator = useCallback(() => {
if (!ajvValidatorRef.current) {
errorsRef.current = undefined;
return undefined;
}
const validationResult = ajvValidatorRef.current(internalDataRef.current);
if (!validationResult) {
const errors = ajvValidatorRef.current.errors;
if (errors && Array.isArray(errors)) {
if (errors.length > 0 && errors[0] && typeof errors[0] === 'object' && 'code' in errors[0]) {
errorsRef.current = zodErrorsToFormErrors(errors);
} else {
errorsRef.current = ajvErrorsToFormErrors(errors, internalDataRef.current);
}
return errorsRef.current;
}
errorsRef.current = undefined;
return undefined;
}
errorsRef.current = undefined;
return undefined;
}, []);
const validateData = useCallback(() => {
const contextErrors = executeAjvValidator();
executeSectionValidators(contextErrors);
forceUpdate();
}, [executeAjvValidator, executeSectionValidators, forceUpdate]);
const checkFieldStateFor = useCallback((path, state) => {
var _errorsRef$current;
return Boolean(state === 'error' ? ((_errorsRef$current = errorsRef.current) === null || _errorsRef$current === void 0 ? void 0 : _errorsRef$current[path]) instanceof Error || fieldErrorRef.current[path] instanceof Error : fieldStateRef.current[path] === state);
}, []);
const hasFieldState = useCallback(state => {
return Array.from(mountedFieldsRef.current.entries()).some(([path, item]) => {
return item.isMounted && checkFieldStateFor(path, state);
});
}, [checkFieldStateFor]);
const hasFieldError = useCallback(path => {
return Array.from(mountedFieldsRef.current.entries()).some(([p, item]) => {
return item.isMounted && p === path && checkFieldStateFor(path, 'error');
});
}, [checkFieldStateFor]);
const hasErrors = useCallback(() => {
return hasFieldState('error');
}, [hasFieldState]);
const setFieldError = useCallback((path, error) => {
if (error) {
fieldErrorRef.current[path] = error;
} else {
delete fieldErrorRef.current[path];
}
bumpValidationVersionRef.current();
for (const item of fieldEventListenersRef.current) {
const {
type,
callback
} = item;
if (type === 'onSetFieldError') {
callback();
}
}
}, []);
const setFieldState = useCallback((path, fieldState) => {
if (fieldState !== fieldStateRef.current[path]) {
fieldStateRef.current[path] = fieldState;
forceUpdate();
bumpValidationVersionRef.current();
}
}, []);
const getDataPathHandlerParameters = useCallback((path, data = internalDataRef.current) => {
var _fieldInternalsRef$cu;
const value = pointer.has(data, path) ? pointer.get(data, path) : undefined;
const {
value: displayValue
} = fieldDisplayValueRef.current[path] || {};
const props = ((_fieldInternalsRef$cu = fieldInternalsRef.current[path]) === null || _fieldInternalsRef$cu === void 0 ? void 0 : _fieldInternalsRef$cu.props) || {};
const label = props === null || props === void 0 ? void 0 : props['label'];
const error = fieldErrorRef.current[path];
return {
path,
value,
displayValue,
label,
props,
data: internalDataRef.current,
error,
internal: {
error
}
};
}, []);
const mutateDataHandler = useCallback((data, handler, {
remove = false,
mutate = true,
fireHandlerWhen = null
} = {}) => {
const freshData = {};
const mutateEntry = (path, result) => {
if (remove) {
if (result === false) {
data = structuredClone(data);
pointer.remove(data, path);
}
} else {
if (typeof result !== 'undefined') {
if (mutate) {
data = structuredClone(data);
pointer.set(data, path, result);
} else {
pointer.set(freshData, path, result);
}
}
}
};
if (typeof handler === 'function') {
const run = path => {
const {
type
} = fieldDisplayValueRef.current[path] || {};
if ((fireHandlerWhen === null || fireHandlerWhen === void 0 ? void 0 : fireHandlerWhen({
type
})) !== false) {
const result = handler(getDataPathHandlerParameters(path, data));
mutateEntry(path, result);
}
};
for (const path in fieldInternalsRef.current) {
const exists = pointer.has(data, path);
if (exists) {
run(path);
}
}
pointer.walk(internalDataRef.current, (value, path) => {
if (fieldInternalsRef.current[path] === undefined) {
run(path);
}
});
if (!mutate) {
return freshData;
}
return data;
} else if (handler) {
const runFilter = ({
path,
condition
}) => {
const exists = pointer.has(data, path);
if (exists) {
const result = typeof condition === 'function' ? condition(getDataPathHandlerParameters(path, data)) : condition;
mutateEntry(path, result);
}
};
const wildcardPaths = [];
Object.entries(handler).forEach(([path, condition]) => {
if (path.includes('*')) {
const parts = path.split(/\/\*/g);
const exists = pointer.has(data, parts[0]);
if (exists) {
const traverse = (subData, subPath, idx) => {
if (idx === parts.length - 1) {
_pushInstanceProperty(wildcardPaths).call(wildcardPaths, {
path: subPath,
condition
});
return;
}
const list = pointer.get(subData, subPath);
if (Array.isArray(list)) {
list.forEach((_, i) => {
traverse(list[i], `${subPath}/${i}${parts[idx + 1]}`, idx + 1);
});
}
};
traverse(data, parts[0], 0);
}
} else {
runFilter({
path,
condition
});
}
});
wildcardPaths.forEach(runFilter);
return data;
}
return data;
}, [getDataPathHandlerParameters]);
const visibleDataHandler = useCallback((data = internalDataRef.current, {
keepPaths,
removePaths
} = {}) => {
const visibleData = {};
mountedFieldsRef.current.forEach((item, path) => {
if (item && item.isVisible !== false && (item.isPreMounted !== false || item.wasStepChange === true) && (removePaths ? !removePaths.includes(path) : true) && pointer.has(data, path)) {
pointer.set(visibleData, path, pointer.get(data, path));
}
});
if (keepPaths) {
keepPaths.forEach(path => {
if (pointer.has(data, path)) {
pointer.set(visibleData, path, pointer.get(data, path));
}
});
}
return visibleData;
}, []);
const filterDataHandler = useCallback((data, filter) => {
if (filter) {
return mutateDataHandler(data, filter, {
remove: true
});
}
return data;
}, [mutateDataHandler]);
const filterData = useCallback((filter, data = internalDataRef.current) => {
return filterDataHandler(data, filter);
}, [filterDataHandler]);
const fieldDisplayValueRef = useRef({});
const fieldConnectionsRef = useRef({});
const fieldStatusRef = useRef({});
const setFieldConnection = useCallback((path, connections) => {
fieldConnectionsRef.current[path] = connections;
}, []);
const fieldInternalsRef = useRef({});
const setFieldInternals = useCallback((path, internals) => {
fieldInternalsRef.current[path] = Object.assign(fieldInternalsRef.current[path] || {}, internals);
}, []);
const valueInternalsRef = useRef({});
const setValueInternals = useCallback((path, props) => {
valueInternalsRef.current[path] = Object.assign(valueInternalsRef.current[path] || {}, {
props
});
}, []);
const hasFieldWithAsyncValidator = useCallback(() => {
for (const path in fieldInternalsRef.current) {
const fieldInternals = fieldInternalsRef.current[path] || {};
const {
enableAsyncMode,
props
} = fieldInternals;
if (enableAsyncMode || isAsync(props === null || props === void 0 ? void 0 : props.onChangeValidator) || isAsync(props === null || props === void 0 ? void 0 : props.onBlurValidator)) {
return true;
}
}
return false;
}, []);
const sharedData = useSharedState(id);
const sharedAttachments = useSharedState(id ? createReferenceKey(id, 'attachments') : undefined);
const sharedDataContext = useSharedState(id ? createReferenceKey(id, 'data-context') : undefined);
const setSharedData = sharedData.set;
const extendSharedData = sharedData.extend;
const extendAttachment = sharedAttachments.extend;
const rerenderUseDataHook = (_sharedAttachments$da = sharedAttachments.data) === null || _sharedAttachments$da === void 0 ? void 0 : _sharedAttachments$da.rerenderUseDataHook;
bumpValidationVersionRef.current = () => {
if (id) {
validationVersionRef.current += 1;
extendAttachment({
validationVersion: validationVersionRef.current
}, {
preventSyncOfSameInstance: true
});
}
};
const hasHydratedFieldErrorRef = useRef(false);
if (!hasHydratedFieldErrorRef.current) {
var _sharedAttachments$da2;
const sharedFieldErrorRef = (_sharedAttachments$da2 = sharedAttachments.data) === null || _sharedAttachments$da2 === void 0 ? void 0 : _sharedAttachments$da2.fieldErrorRef;
if (sharedFieldErrorRef !== null && sharedFieldErrorRef !== void 0 && sharedFieldErrorRef.current) {
fieldErrorRef.current = sharedFieldErrorRef.current;
hasHydratedFieldErrorRef.current = true;
}
}
const cacheRef = useRef({
data,
schema,
shared: sharedData.data,
hasUsedInitialData: false
});
const internalData = useMemo(() => {
if (id && initialData && !sharedData.data) {
sharedData.update(initialData);
}
if (id && initialData && sharedData.data && cacheRef.current.shared === sharedData.data && internalDataRef.current === initialData && typeof internalDataRef.current === 'object') {
return {
...internalDataRef.current,
...(sharedData.data || {})
};
}
if (id && !initialData && !internalDataRef.current && sharedData.data && cacheRef.current.shared === sharedData.data) {
return sharedData.data;
}
if (id && sharedData.data && cacheRef.current.shared !== sharedData.data && sharedData.data !== internalDataRef.current && typeof internalDataRef.current === 'object') {
cacheRef.current.shared = sharedData.data;
if (isEmptyDataRef.current) {
return Array.isArray(internalDataRef.current) ? [] : clearedData;
}
return {
...internalDataRef.current,
...(sharedData.data || {})
};
}
if (data !== cacheRef.current.data) {
cacheRef.current.data = data;
return data;
}
return internalDataRef.current;
}, [id, initialData, sharedData, data]);
internalDataRef.current = props.path && pointer.has(internalData, props.path) ? pointer.get(internalData, props.path) : internalData;
const clearData = useCallback(() => {
var _ref, _requestAnimationFram;
isEmptyDataRef.current = true;
internalDataRef.current = (_ref = typeof emptyData === 'function' ? emptyData(internalDataRef.current) : emptyData) !== null && _ref !== void 0 ? _ref : Array.isArray(internalDataRef.current) ? [] : clearedData;
if (id) {
setSharedData(internalDataRef.current);
}
forceUpdate();
onClear === null || onClear === void 0 || onClear();
(_requestAnimationFram = requestAnimationFrame) === null || _requestAnimationFram === void 0 || _requestAnimationFram(() => {
isEmptyDataRef.current = false;
});
}, [emptyData, id, onClear, setSharedData]);
useLayoutEffect(() => {
const hasNoErrors = errorsRef.current === undefined;
const contextErrors = executeAjvValidator();
executeSectionValidators(contextErrors);
if (hasNoErrors && errorsRef.current !== undefined) {
forceUpdate();
}
}, [internalDataRef.current, executeAjvValidator, executeSectionValidators]);
const registerSectionSchema = useCallback(registration => {
const normalizedPath = registration.path && registration.path !== '/' ? registration.path : '/';
const validator = createUnifiedValidator(registration.schema);
sectionSchemasRef.current.set(registration.id, {
path: normalizedPath,
schema: registration.schema,
validator
});
sectionSchemaPathsRef.current.add(normalizedPath);
validateData();
return () => {
const entry = sectionSchemasRef.current.get(registration.id);
if (!entry) {
return;
}
sectionSchemasRef.current.delete(registration.id);
const stillUsesPath = Array.from(sectionSchemasRef.current.values()).some(item => item.path === entry.path);
if (!stillUsesPath) {
sectionSchemaPathsRef.current.delete(entry.path);
}
validateData();
};
}, [createUnifiedValidator, validateData]);
const storeInSession = useMemo(() => {
return debounce(() => {
var _window$sessionStorag3;
(_window$sessionStorag3 = window.sessionStorage) === null || _window$sessionStorag3 === void 0 || _window$sessionStorag3.setItem(sessionStorageId, JSON.stringify(internalDataRef.current));
}, process.env.NODE_ENV === 'test' ? 1 : 800);
}, [sessionStorageId]);
const setData = useCallback((newData, {
preventUpdate = false
} = {}) => {
if (transformIn) {
newData = mutateDataHandler(newData, transformIn);
}
internalDataRef.current = newData;
if (id) {
extendSharedData(newData, {
preventSyncOfSameInstance: true
});
if (filterSubmitData) {
rerenderUseDataHook === null || rerenderUseDataHook === void 0 || rerenderUseDataHook();
}
}
if (sessionStorageId && typeof window !== 'undefined') {
storeInSession();
}
if (!preventUpdate) {
forceUpdate();
}
}, [extendSharedData, filterSubmitData, id, mutateDataHandler, rerenderUseDataHook, sessionStorageId, storeInSession, transformIn]);
const updateDataValue = useCallback((path, value, {
preventUpdate
} = {}) => {
var _internalDataRef$curr;
if (!path) {
return;
}
const givenData = path === '/' ? value : (_internalDataRef$curr = internalDataRef.current) !== null && _internalDataRef$curr !== void 0 ? _internalDataRef$curr : path.match(isArrayJsonPointer) ? [] : {};
let newData = null;
try {
newData = structuredClone(givenData);
} catch (e) {
newData = givenData;
}
if (path !== '/') {
pointer.set(newData, path, value);
}
setData(newData, {
preventUpdate
});
onUpdateDataValue === null || onUpdateDataValue === void 0 || onUpdateDataValue(path, value, {
preventUpdate
});
}, [onUpdateDataValue, setData]);
const handlePathChangeUnvalidated = useCallback(async (path, value) => {
if (!path) {
return null;
}
updateDataValue(path, value);
if (isAsync(onPathChange)) {
await (onPathChange === null || onPathChange === void 0 ? void 0 : onPathChange(path, value));
} else {
onPathChange === null || onPathChange === void 0 || onPathChange(path, value);
}
for (const itm of fieldEventListenersRef.current) {
if (itm.type === 'onPathChange' && itm.path === path) {
const {
callback
} = itm;
if (isAsync(callback)) {
await callback({
value
});
} else {
callback({
value
});
}
}
}
}, [onPathChange, updateDataValue]);
const handlePathChange = useCallback(async (path, value = '_undefined_') => {
if (!path) {
return null;
}
if (value !== '_undefined_') {
handlePathChangeUnvalidated(path, value);
}
showAllErrorsRef.current = false;
validateData();
const data = internalDataRef.current;
const options = {
filterData
};
const transformedData = transformOut ? mutateDataHandler(data, transformOut) : data;
for (const cb of changeHandlerStackRef.current) {
if (isAsync(onChange)) {
await cb(transformedData, options);
} else {
cb(transformedData, options);
}
}
if (isAsync(onChange)) {
return await onChange(transformedData, options);
}
return onChange === null || onChange === void 0 ? void 0 : onChange(transformedData, options);
}, [filterData, handlePathChangeUnvalidated, mutateDataHandler, onChange, transformOut, validateData]);
const changeHandlerStackRef = useRef([]);
const addOnChangeHandler = useCallback(callback => {
const exists = changeHandlerStackRef.current.some(cb => {
return callback === cb;
});
if (!exists) {
var _context;
_pushInstanceProperty(_context = changeHandlerStackRef.current).call(_context, callback);
}
}, []);
const setMountedFieldState = useCallback((path, state) => {
const currentState = mountedFieldsRef.current.get(path) || {};
mountedFieldsRef.current.set(path, {
...currentState,
...state
});
const hasChanges = Object.keys(state).some(key => {
return currentState[key] !== state[key];
});
if (hasChanges) {
Promise.resolve().then(() => {
bumpValidationVersionRef.current();
});
}
for (const itm of fieldEventListenersRef.current) {
if (itm.type === 'onMount' && itm.path === path) {
const {
callback
} = itm;
callback();
}
}
}, []);
const scrollToTop = useCallback(() => {
if (typeof window !== 'undefined') {
var _window, _window$scrollTo;
(_window = window) === null || _window === void 0 || (_window$scrollTo = _window.scrollTo) === null || _window$scrollTo === void 0 || _window$scrollTo.call(_window, {
top: 0,
behavior: 'smooth'
});
}
}, []);
const resolveStateResult = useCallback(async fn => {
try {
const result = await fn();
if (result instanceof Error) {
throw result;
}
return result;
} catch (error) {
return {
error: error
};
}
}, []);
const applySubmitState = useCallback(result => {
if (result !== null && result !== void 0 && result.error || result !== null && result !== void 0 && result.warning || result !== null && result !== void 0 && result.info || result !== null && result !== void 0 && result.customStatus) {
setSubmitState(result);
}
}, [setSubmitState]);
const handleSubmitCall = useCallback(async args => {
const {
onSubmit,
enableAsyncBehavior,
skipFieldValidation,
skipErrorCheck
} = args;
setSubmitState({
error: undefined,
warning: undefined,
info: undefined,
customStatus: undefined
});
const asyncBehaviorIsEnabled = (skipErrorCheck ? enableAsyncBehavior : !hasErrors() || hasFieldState('pending')) && (enableAsyncBehavior || hasFieldWithAsyncValidator());
if (asyncBehaviorIsEnabled) {
setFormState('pending');
}
if (!skipFieldValidation) {
for (const item of fieldEventListenersRef.current) {
var _mountedFieldsRef$cur;
const {
path,
type,
callback
} = item;
if (type === 'onSubmitCall' && (_mountedFieldsRef$cur = mountedFieldsRef.current.get(path)) !== null && _mountedFieldsRef$cur !== void 0 && _mountedFieldsRef$cur.isMounted) {
if (asyncBehaviorIsEnabled) {
await callback();
} else {
callback();
}
}
}
}
let result;
if (!(skipErrorCheck ? false : hasErrors()) && !hasFieldState('pending') && (skipFieldValidation ? true : !hasFieldState('error'))) {
var _result2;
result = await resolveStateResult(async () => {
if (isolate) {
for (const item of fieldEventListenersRef.current) {
const {
type,
callback
} = item;
if (type === 'onBeforeCommit') {
callback();
}
}
const commitResult = await (onCommit === null || onCommit === void 0 ? void 0 : onCommit(internalDataRef.current, {
clearData
}));
for (const item of fieldEventListenersRef.current) {
const {
type,
callback
} = item;
if (type === 'onAfterCommit') {
callback();
}
}
return commitResult;
} else {
return await onSubmit();
}
});
if (asyncBehaviorIsEnabled) {
var _result;
if ((_result = result) !== null && _result !== void 0 && _result.error) {
setFormState('abort');
} else if (keepPending.current !== true) {
setFormState('complete');
}
}
if ((_result2 = result) !== null && _result2 !== void 0 && _result2['status']) {
var _result3;
setFormState((_result3 = result) === null || _result3 === void 0 ? void 0 : _result3['status']);
}
applySubmitState(result);
} else {
if (asyncBehaviorIsEnabled) {
await new Promise(resolve => window.requestAnimationFrame(resolve));
setFormState(undefined);
if (!skipFieldValidation) {
onSubmitContinueRef.current = () => {
window.requestAnimationFrame(() => {
handleSubmitCall({
...args,
skipFieldValidation: true
});
});
};
}
}
setShowAllErrors(true);
const submitRequestResult = await resolveStateResult(() => onSubmitRequest === null || onSubmitRequest === void 0 ? void 0 : onSubmitRequest({
getErrors: () => Object.keys(fieldErrorRef.current).map(path => {
return getDataPathHandlerParameters(path);
}).filter(({
error
}) => error)
}));
applySubmitState(submitRequestResult);
if (submitRequestResult) {
result = submitRequestResult;
}
for (const itm of fieldEventListenersRef.current) {
if (itm.type === 'onSubmitRequest') {
itm.callback();
}
}
}
return result;
}, [applySubmitState, clearData, getDataPathHandlerParameters, hasErrors, hasFieldState, hasFieldWithAsyncValidator, isolate, onCommit, onSubmitRequest, resolveStateResult, setFormState, setShowAllErrors]);
const getSubmitData = useCallback(() => {
const data = internalDataRef.current;
const mutatedData = transformOut ? mutateDataHandler(data, transformOut) : data;
const filteredData = filterSubmitData ? filterDataHandler(mutatedData, filterSubmitData) : mutatedData;
return filteredData;
}, [filterDataHandler, filterSubmitData, mutateDataHandler, transformOut]);
const getSubmitParams = useCallback(() => {
const reduceToVisibleFields = (data, options) => {
return visibleDataHandler(transformOut ? mutateDataHandler(data, transformOut) : data, options);
};
const transformData = (data, handler) => {
return mutateDataHandler(data, handler, {
mutate: false,
fireHandlerWhen: ({
type
}) => type === 'field'
});
};
const formElement = formElementRef.current;
const params = {
filterData,
reduceToVisibleFields,
transformData,
resetForm: () => {
var _formElement$reset;
formElement === null || formElement === void 0 || (_formElement$reset = formElement.reset) === null || _formElement$reset === void 0 || _formElement$reset.call(formElement);
if (typeof window !== 'undefined') {
if (sessionStorageId) {
window.sessionStorage.removeItem(sessionStorageId);
}
}
forceUpdate();
},
clearData
};
return params;
}, [clearData, filterData, mutateDataHandler, sessionStorageId, transformOut, visibleDataHandler]);
const handleSubmit = useCallback(async () => {
for (const item of fieldEventListenersRef.current) {
const {
type,
callback
} = item;
if (type === 'onBeforeSubmit') {
callback();
}
}
return await handleSubmitCall({
enableAsyncBehavior: isAsync(onSubmit),
onSubmit: async () => {
let stop = false;
const preventSubmit = () => stop = true;
for (const item of fieldEventListenersRef.current) {
const {
type,
callback
} = item;
if (type === 'onSubmit') {
if (isAsync(callback)) {
await callback({
preventSubmit
});
} else {
callback({
preventSubmit
});
}
}
}
if (stop) {
return;
}
const data = getSubmitData();
const options = getSubmitParams();
let result = undefined;
if (isAsync(onSubmit)) {
result = await onSubmit(data, options);
} else {
result = onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(data, options);
}
const completeResult = await (onSubmitComplete === null || onSubmitComplete === void 0 ? void 0 : onSubmitComplete(data, result));
if (completeResult) {
result = Object.keys(result).length > 0 ? {
...result,
...completeResult
} : completeResult;
}
if (scrollTopOnSubmit) {
scrollToTop();
}
return result;
}
});
}, [getSubmitData, getSubmitParams, handleSubmitCall, onSubmit, onSubmitComplete, scrollToTop, scrollTopOnSubmit]);
const fieldEventListenersRef = useRef([]);
const setFieldEventListener = useCallback((path, type, callback, {
remove = false
} = {}) => {
fieldEventListenersRef.current = fieldEventListenersRef.current.filter(({
path: p,
type: t,
callback: c
}) => {
return !(p === path && t === type && c === callback);
});
if (!remove) {
var _context2;
_pushInstanceProperty(_context2 = fieldEventListenersRef.current).call(_context2, {
path,
type,
callback
});
}
}, []);
const onSubmitContinueRef = useRef(null);
if (!hasFieldState('pending')) {
var _onSubmitContinueRef$;
(_onSubmitContinueRef$ = onSubmitContinueRef.current) === null || _onSubmitContinueRef$ === void 0 || _onSubmitContinueRef$.call(onSubmitContinueRef);
onSubmitContinueRef.current = null;
}
useUpdateEffect(() => {
if (schema && schema !== cacheRef.current.schema) {
cacheRef.current.schema = schema;
ajvValidatorRef.current = createUnifiedValidator(schema);
validateData();
forceUpdate();
}
}, [schema, validateData, forceUpdate, createUnifiedValidator]);
const onTimeout = useCallback(() => {
setFormState(undefined);
setSubmitState({
info: undefined,
warning: undefined,
error: undefined,
customStatus: undefined
});
}, [setFormState, setSubmitState]);
useLayoutEffect(() => {
if (id) {
if (initialData && !sharedData.data) {
extendSharedData(initialData, {
preventSyncOfSameInstance: true
});
}
}
}, [id, initialData, extendSharedData, sharedData.data]);
useLayoutEffect(() => {
if (id) {
extendAttachment({
visibleDataHandler,
filterDataHandler,
validationVersion: validationVersionRef.current,
hasErrors,
hasFieldError,
setShowAllErrors,
setSubmitState,
clearData,
setData,
updateDataValue,
fieldConnectionsRef,
fieldStatusRef,
fieldErrorRef,
internalDataRef
}, {
preventSyncOfSameInstance: true
});
if (filterSubmitData) {
rerenderUseDataHook === null || rerenderUseDataHook === void 0 || rerenderUseDataHook();
}
}
}, [clearData, extendAttachment, filterDataHandler, filterSubmitData, hasErrors, hasFieldError, id, rerenderUseDataHook, setData, setShowAllErrors, setSubmitState, updateDataValue, visibleDataHandler]);
const {
bufferedFormState: formState
} = useFormStatusBuffer({
formState: formStateRef.current,
waitFor: hasFieldState('pending'),
minimumAsyncBehaviorTime,
asyncSubmitTimeout,
onTimeout
});
const submitState = submitStateRef.current;
const disabled = typeof (rest === null || rest === void 0 ? void 0 : rest['disabled']) === 'boolean' ? rest === null || rest === void 0 ? void 0 : rest['disabled'] : formState === 'pending' ? true : undefined;
const contextErrorMessages = (errorMessages === null || errorMessages === void 0 ? void 0 : errorMessages[locale !== null && locale !== void 0 ? locale : sharedLocale]) || errorMessages;
const getSourceValue = useCallback(value => {
const data = internalDataRef.current;
if (String(value).startsWith('/') && pointer.has(data, String(value))) {
return pointer.get(data, String(value));
}
return value;
}, []);
const contextValue = {
handlePathChange,
handlePathChangeUnvalidated,
handleSubmit,
setMountedFieldState,
handleSubmitCall,
setFormState,
setSubmitState,
setShowAllErrors,
revealError,
setFieldEventListener,
setFieldState,
setFieldError,
setFieldConnection,
setFieldInternals,
setValueInternals,
hasErrors,
hasFieldError,
hasFieldState,
hasFieldWithAsyncValidator,
validateData,
updateDataValue,
setData,
clearData,
visibleDataHandler,
filterDataHandler,
getSubmitData,
getSubmitParams,
addOnChangeHandler,
scrollToTop,
registerSectionSchema,
schema,
disabled,
required,
formState,
activeSubmitButtonId: activeSubmitButtonIdRef.current,
submitState,
setActiveSubmitButtonId,
contextErrorMessages,
hasContext: true,
errors: errorsRef.current,
showAllErrors: showAllErrorsRef.current,
hasVisibleError: hasVisibleErrorRef.current.size > 0,
addSetShowAllErrorsRef,
fieldConnectionsRef,
fieldDisplayValueRef,
fieldInternalsRef,
valueInternalsRef,
mountedFieldsRef,
snapshotsRef,
existingFieldsRef,
formElementRef,
isEmptyDataRef,
fieldErrorRef,
errorsRef,
sectionSchemaPathsRef,
getAjvInstance,
countryCode: countryCode ? getSourceValue(countryCode) : undefined,
id,
data: internalDataRef.current,
internalDataRef,
props,
...rest
};
if (id) {
sharedDataContext.set(contextValue);
}
const show = Boolean(showAllErrorsRef.current);
const resolvedLocale = locale || sharedLocale;
const customErrorSummaryTitle = translations === null || translations === void 0 || (_translations$resolve = translations[resolvedLocale]) === null || _translations$resolve === void 0 || (_translations$resolve = _translations$resolve.Field) === null || _translations$resolve === void 0 ? void 0 : _translations$resolve.errorSummaryTitle;
const formStatusConfig = useMemo(() => {
var _ref2, _status$stack$0$title, _status$stack$;
const status = show ? GlobalStatusProvider.get(globalStatusId) : null;
return {
globalStatus: {
show,
id: globalStatusId,
title: (_ref2 = (_status$stack$0$title = status === null || status === void 0 || (_status$stack$ = status.stack[0]) === null || _status$stack$ === void 0 ? void 0 : _status$stack$.title) !== null && _status$stack$0$title !== void 0 ? _status$stack$0$title : customErrorSummaryTitle) !== null && _ref2 !== void 0 ? _ref2 : translation.errorSummaryTitle
}
};
}, [globalStatusId, show, customErrorSummaryTitle, translation.errorSummaryTitle]);
return React.createElement(DataContext.Provider, {
value: contextValue
}, React.createElement(FieldPropsProvider, {
FormStatus: formStatusConfig,
formElement: disabled ? {
disabled: true
} : undefined,
locale: locale ? locale : undefined,
translations: translations ? translations : undefined
}, children));
}
function useFormStatusBuffer(props) {
const {
formState,
waitFor,
minimumAsyncBehaviorTime,
asyncSubmitTimeout,
onTimeout
} = props || {};
const [, forceUpdate] = useReducer(() => ({}), {});
const stateRef = useRef();
const nowRef = useRef(null);
const timeoutRef = useRef({});
const setState = useCallback(state => {
stateRef.current = state;
forceUpdate();
}, []);
const clear = useCallback(() => {
for (const key in timeoutRef.current) {
clearTimeout(timeoutRef.current[key]);
}
}, []);
const hadCompleteRef = useRef(false);
const activeElementRef = useRef(null);
useEffect(() => {
const isTest = process.env.NODE_ENV === 'test';
const minimum = minimumAsyncBehaviorTime !== null && minimumAsyncBehaviorTime !== void 0 ? minimumAsyncBehaviorTime : isTest ? 1 : 1000;
if (stateRef.current && formState === 'error') {
clear();
setState(undefined);
return;
}
if (formState === 'abort') {
clear();
setState('abort');
timeoutRef.current.reset = setTimeout(() => {
nowRef.current = 0;
setState(undefined);
}, minimum);
return;
}
if (formState === 'complete') {
hadCompleteRef.current = true;
}
if (formState === 'pending' && stateRef.current !== 'pending') {
activeElementRef.current = document.activeElement;
clear();
nowRef.current = Date.now();
hadCompleteRef.current = false;
setState('pending');
} else if (stateRef.current === 'pending') {
const offset = Math.max(Date.now() - nowRef.current);
const delay = isTest ? minimum : Math.max(minimum - offset, 0);
if (!waitFor) {
timeoutRef.current.complete = setTimeout(() => {
if (hadCompleteRef.current) {
setState('complete');
}
window.requestAnimationFrame(() => {
var _activeElementRef$cur, _activeElementRef$cur2;
(_activeElementRef$cur = activeElementRef.current) === null || _activeElementRef$cur === void 0 || (_activeElementRef$cur2 = _activeElementRef$cur.focus) === null || _activeElementRef$cur2 === void 0 || _activeElementRef$cur2.call(_activeElementRef$cur);
});
}, delay);
timeoutRef.current.reset = setTimeout(() => {
nowRef.current = 0;
setState(undefined);
clear();
}, delay + minimum);
}
}
if (stateRef.current === 'pending') {
timeoutRef.current.timeout = setTimeout(() => {
clear();
setState(undefined);
onTimeout === null || onTimeout === void 0 || onTimeout();
}, asyncSubmitTimeout !== null && asyncSubmitTimeout !== void 0 ? asyncSubmitTimeout : 30000);
}
return clear;
}, [clear, minimumAsyncBehaviorTime, formState, setState, waitFor, asyncSubmitTimeout, onTimeout]);
return {
bufferedFormState: stateRef.current
};
}
export const clearedData = Object.freeze({});
//# sourceMappingURL=Provider.js.map