@openmrs/esm-form-engine-lib 4.2.2-pre.2543 → 4.2.2-pre.2550
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/processor-factory/form-processor-factory.component.js +111 -39
- package/dist/components/renderer/custom-hooks-renderer.component.js +2 -2
- package/dist/form-engine.component.js +5 -3
- package/dist/hooks/useFormFieldValidators.js +22 -12
- package/dist/hooks/useFormFieldValueAdapters.js +22 -15
- package/dist/hooks/useFormFields.js +35 -42
- package/dist/hooks/useInitialValues.js +12 -5
- package/dist/hooks/useProcessorDependencies.js +2 -2
- package/dist/processors/encounter/encounter-form-processor.js +10 -20
- package/dist/processors/form-processor.js +4 -1
- package/dist/src/components/processor-factory/form-processor-factory.component.d.ts.map +1 -1
- package/dist/src/components/renderer/custom-hooks-renderer.component.d.ts +5 -8
- package/dist/src/components/renderer/custom-hooks-renderer.component.d.ts.map +1 -1
- package/dist/src/form-engine.component.d.ts.map +1 -1
- package/dist/src/hooks/useFormFieldValidators.d.ts.map +1 -1
- package/dist/src/hooks/useFormFieldValueAdapters.d.ts.map +1 -1
- package/dist/src/hooks/useFormFields.d.ts.map +1 -1
- package/dist/src/hooks/useInitialValues.d.ts.map +1 -1
- package/dist/src/hooks/useProcessorDependencies.d.ts +2 -2
- package/dist/src/hooks/useProcessorDependencies.d.ts.map +1 -1
- package/dist/src/processors/encounter/encounter-form-processor.d.ts +6 -3
- package/dist/src/processors/encounter/encounter-form-processor.d.ts.map +1 -1
- package/dist/src/processors/form-processor.d.ts +21 -2
- package/dist/src/processors/form-processor.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/processor-factory/form-processor-factory.component.tsx +151 -35
- package/src/components/renderer/custom-hooks-renderer.component.tsx +6 -8
- package/src/form-engine.component.tsx +6 -4
- package/src/hooks/useFormFieldValidators.ts +23 -13
- package/src/hooks/useFormFieldValueAdapters.ts +23 -16
- package/src/hooks/useFormFields.ts +28 -41
- package/src/hooks/useInitialValues.ts +15 -6
- package/src/hooks/useProcessorDependencies.ts +4 -3
- package/src/processors/encounter/encounter-form-processor.ts +12 -21
- package/src/processors/form-processor.ts +27 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encounter-form-processor.d.ts","sourceRoot":"","sources":["../../../../src/processors/encounter/encounter-form-processor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,eAAe,EAA+B,MAAM,wBAAwB,CAAC;AAc3F,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,yBAAyB,EAC9B,KAAK,UAAU,EAEf,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"encounter-form-processor.d.ts","sourceRoot":"","sources":["../../../../src/processors/encounter/encounter-form-processor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,eAAe,EAA+B,MAAM,wBAAwB,CAAC;AAc3F,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,yBAAyB,EAC9B,KAAK,UAAU,EAEf,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,aAAa,EAAE,KAAK,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAKpF,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAOrE,iBAAS,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,yBAAyB,CAAC;;;;;;;;;;;;EAwBlE;AAkBD,qBAAa,sBAAuB,SAAQ,aAAa;IACvD,iBAAiB,CAAC,MAAM,EAAE,UAAU;IAuC9B,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe;IAiInF,cAAc;;;IAIR,gBAAgB,CAAC,OAAO,EAAE,yBAAyB;IAsFnD,gBAAgB,CACpB,OAAO,EAAE,gBAAgB,EACzB,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,yBAAyB,CAAC,CAAC,EAC5E,EAAE,4BAA4B,EAAE,EAAE,2BAA2B;IAQzD,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;CA0BhG"}
|
|
@@ -2,12 +2,27 @@ import { type OpenmrsResource } from '@openmrs/esm-framework';
|
|
|
2
2
|
import { type FormContextProps } from '../provider/form-provider';
|
|
3
3
|
import { type ValueAndDisplay, type FormField, type FormSchema, type FormProcessorContextProps } from '../types';
|
|
4
4
|
export type FormProcessorConstructor = new (...args: ConstructorParameters<typeof FormProcessor>) => FormProcessor;
|
|
5
|
+
/**
|
|
6
|
+
* The setters a processor uses to feed its results back into the processor context. These are the
|
|
7
|
+
* only three properties it can change; the rest of {@link FormProcessorContextProps} is derived by
|
|
8
|
+
* the FormProcessorFactory. They supersede the `setContext` passed alongside them, which is shaped
|
|
9
|
+
* like a `setState` over the whole context but silently discards everything else.
|
|
10
|
+
*/
|
|
11
|
+
export interface FormProcessorContextSetters {
|
|
12
|
+
setDomainObjectValue: (value: OpenmrsResource) => void;
|
|
13
|
+
setPreviousDomainObjectValue: (value: OpenmrsResource) => void;
|
|
14
|
+
setCustomDependencies: (dependencies: Record<string, any> | ((previous: Record<string, any>) => Record<string, any>)) => void;
|
|
15
|
+
}
|
|
5
16
|
export type GetCustomHooksResponse = {
|
|
6
17
|
useCustomHooks: (context: Partial<FormProcessorContextProps>) => {
|
|
7
18
|
data: any;
|
|
8
19
|
isLoading: boolean;
|
|
9
20
|
error: any;
|
|
10
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Called once the hook has finished loading, to merge its results into the processor context.
|
|
23
|
+
* `setContext` is deprecated; use `setters`.
|
|
24
|
+
*/
|
|
25
|
+
updateContext: (setContext: React.Dispatch<React.SetStateAction<FormProcessorContextProps>>, setters: FormProcessorContextSetters) => void;
|
|
11
26
|
};
|
|
12
27
|
};
|
|
13
28
|
export declare abstract class FormProcessor {
|
|
@@ -15,7 +30,11 @@ export declare abstract class FormProcessor {
|
|
|
15
30
|
domainObjectValue: OpenmrsResource;
|
|
16
31
|
constructor(formJson: FormSchema);
|
|
17
32
|
getDomainObject(): OpenmrsResource;
|
|
18
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Loads whatever the processor needs before the form can render, merging the results into the
|
|
35
|
+
* processor context. `setContext` is deprecated; use `setters`.
|
|
36
|
+
*/
|
|
37
|
+
loadDependencies(context: Partial<FormProcessorContextProps>, setContext: React.Dispatch<React.SetStateAction<FormProcessorContextProps>>, setters: FormProcessorContextSetters): Promise<Record<string, any>>;
|
|
19
38
|
abstract getHistoricalValue(field: FormField, context: FormContextProps): Promise<ValueAndDisplay>;
|
|
20
39
|
abstract processSubmission(context: FormContextProps, abortController: AbortController): Promise<OpenmrsResource>;
|
|
21
40
|
abstract getInitialValues(context: FormProcessorContextProps): Promise<Record<string, any>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-processor.d.ts","sourceRoot":"","sources":["../../../src/processors/form-processor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAEjH,MAAM,MAAM,wBAAwB,GAAG,KAAK,GAAG,IAAI,EAAE,qBAAqB,CAAC,OAAO,aAAa,CAAC,KAAK,aAAa,CAAC;AAEnH,MAAM,MAAM,sBAAsB,GAAG;IACnC,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,yBAAyB,CAAC,KAAK;QAC/D,IAAI,EAAE,GAAG,CAAC;QACV,SAAS,EAAE,OAAO,CAAC;QACnB,KAAK,EAAE,GAAG,CAAC;QACX,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"form-processor.d.ts","sourceRoot":"","sources":["../../../src/processors/form-processor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAEjH,MAAM,MAAM,wBAAwB,GAAG,KAAK,GAAG,IAAI,EAAE,qBAAqB,CAAC,OAAO,aAAa,CAAC,KAAK,aAAa,CAAC;AAEnH;;;;;GAKG;AACH,MAAM,WAAW,2BAA2B;IAC1C,oBAAoB,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IACvD,4BAA4B,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAC/D,qBAAqB,EAAE,CACrB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,KACzF,IAAI,CAAC;CACX;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,yBAAyB,CAAC,KAAK;QAC/D,IAAI,EAAE,GAAG,CAAC;QACV,SAAS,EAAE,OAAO,CAAC;QACnB,KAAK,EAAE,GAAG,CAAC;QACX;;;WAGG;QACH,aAAa,EAAE,CACb,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,yBAAyB,CAAC,CAAC,EAC3E,OAAO,EAAE,2BAA2B,KACjC,IAAI,CAAC;KACX,CAAC;CACH,CAAC;AAEF,8BAAsB,aAAa;IACjC,QAAQ,EAAE,UAAU,CAAC;IACrB,iBAAiB,EAAE,eAAe,CAAC;gBAEvB,QAAQ,EAAE,UAAU;IAIhC,eAAe;IAIf;;;OAGG;IACG,gBAAgB,CACpB,OAAO,EAAE,OAAO,CAAC,yBAAyB,CAAC,EAC3C,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,yBAAyB,CAAC,CAAC,EAC3E,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAI/B,QAAQ,CAAC,kBAAkB,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAClG,QAAQ,CAAC,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;IACjH,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3F,QAAQ,CAAC,cAAc,IAAI,sBAAsB;IACjD,QAAQ,CAAC,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU;CAC3D"}
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { type OpenmrsResource } from '@openmrs/esm-framework';
|
|
2
3
|
import useProcessorDependencies from '../../hooks/useProcessorDependencies';
|
|
3
4
|
import useInitialValues from '../../hooks/useInitialValues';
|
|
4
5
|
import { FormRenderer } from '../renderer/form/form-renderer.component';
|
|
@@ -11,6 +12,7 @@ import { useFormFieldsMeta } from '../../hooks/useFormFieldsMeta';
|
|
|
11
12
|
import { useFormFactory } from '../../provider/form-factory-provider';
|
|
12
13
|
import { useFormFieldValueAdapters } from '../../hooks/useFormFieldValueAdapters';
|
|
13
14
|
import { EncounterFormProcessor } from '../../processors/encounter/encounter-form-processor';
|
|
15
|
+
import { type FormProcessorContextSetters } from '../../processors/form-processor';
|
|
14
16
|
import { reportError } from '../../utils/error-utils';
|
|
15
17
|
import { useTranslation } from 'react-i18next';
|
|
16
18
|
import Loader from '../loaders/loader.component';
|
|
@@ -22,12 +24,48 @@ interface FormProcessorFactoryProps {
|
|
|
22
24
|
setIsLoadingFormDependencies: (isLoading: boolean) => void;
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
// Mutable parts of the context that can be updated by processors/hooks
|
|
28
|
+
interface MutableContextState {
|
|
29
|
+
domainObjectValue?: OpenmrsResource;
|
|
30
|
+
previousDomainObjectValue?: OpenmrsResource;
|
|
31
|
+
customDependencies?: Record<string, any>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Context properties this component derives; a processor that sets one of these is ignored. */
|
|
35
|
+
const factoryOwnedContextKeys = [
|
|
36
|
+
'patient',
|
|
37
|
+
'formJson',
|
|
38
|
+
'visit',
|
|
39
|
+
'sessionMode',
|
|
40
|
+
'sessionDate',
|
|
41
|
+
'location',
|
|
42
|
+
'currentProvider',
|
|
43
|
+
'layoutType',
|
|
44
|
+
'processor',
|
|
45
|
+
'formFields',
|
|
46
|
+
'formFieldAdapters',
|
|
47
|
+
'formFieldValidators',
|
|
48
|
+
] as const satisfies ReadonlyArray<keyof FormProcessorContextProps>;
|
|
49
|
+
|
|
50
|
+
function warnAboutIgnoredContextKeys(previous: FormProcessorContextProps, next: FormProcessorContextProps) {
|
|
51
|
+
const ignored = factoryOwnedContextKeys.filter((key) => key in next && next[key] !== previous[key]);
|
|
52
|
+
if (ignored.length) {
|
|
53
|
+
console.warn(
|
|
54
|
+
`A form processor tried to set the context propert${ignored.length === 1 ? 'y' : 'ies'} ` +
|
|
55
|
+
`${ignored.join(', ')}, which the form engine derives itself. ${
|
|
56
|
+
ignored.length === 1 ? 'It was' : 'They were'
|
|
57
|
+
} ignored. Use the setters passed alongside setContext to update the domain object or custom dependencies.`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
25
62
|
const FormProcessorFactory = ({
|
|
26
63
|
formJson,
|
|
27
64
|
isSubForm = false,
|
|
28
65
|
setIsLoadingFormDependencies,
|
|
29
66
|
}: FormProcessorFactoryProps) => {
|
|
30
67
|
const { patient, sessionMode, formProcessors, layoutType, location, provider, sessionDate, visit } = useFormFactory();
|
|
68
|
+
const { t } = useTranslation();
|
|
31
69
|
|
|
32
70
|
const processor = useMemo(() => {
|
|
33
71
|
const ProcessorClass = formProcessors[formJson.processor];
|
|
@@ -42,58 +80,135 @@ const FormProcessorFactory = ({
|
|
|
42
80
|
return processorInstance;
|
|
43
81
|
}, [formProcessors, formJson.processor]);
|
|
44
82
|
|
|
45
|
-
|
|
46
|
-
patient,
|
|
47
|
-
formJson,
|
|
48
|
-
sessionMode,
|
|
49
|
-
layoutType,
|
|
50
|
-
location,
|
|
51
|
-
currentProvider: provider,
|
|
52
|
-
processor,
|
|
53
|
-
sessionDate,
|
|
54
|
-
visit,
|
|
55
|
-
formFields: [],
|
|
56
|
-
formFieldAdapters: {},
|
|
57
|
-
formFieldValidators: {},
|
|
58
|
-
});
|
|
59
|
-
const { t } = useTranslation();
|
|
83
|
+
// Derive form fields and related data
|
|
60
84
|
const { formFields: rawFormFields, conceptReferences } = useFormFields(formJson);
|
|
61
85
|
const { concepts: formFieldsConcepts, isLoading: isLoadingConcepts } = useConcepts(Array.from(conceptReferences));
|
|
62
86
|
const formFieldsWithMeta = useFormFieldsMeta(rawFormFields, formFieldsConcepts);
|
|
63
87
|
const formFieldAdapters = useFormFieldValueAdapters(rawFormFields);
|
|
64
88
|
const formFieldValidators = useFormFieldValidators(rawFormFields);
|
|
65
|
-
|
|
89
|
+
|
|
90
|
+
const formFields = useMemo(
|
|
91
|
+
() => (formFieldsWithMeta?.length ? formFieldsWithMeta : rawFormFields ?? []),
|
|
92
|
+
[formFieldsWithMeta, rawFormFields],
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// We divide the context into the "mutable" parts, which can be changed by custom hooks
|
|
96
|
+
// and the "static" parts
|
|
97
|
+
const [mutableContext, setMutableContext] = useState<MutableContextState>({});
|
|
98
|
+
|
|
99
|
+
// Create the "static" part of the context
|
|
100
|
+
const baseContext = useMemo<Omit<FormProcessorContextProps, keyof MutableContextState>>(
|
|
101
|
+
() => ({
|
|
102
|
+
patient,
|
|
103
|
+
formJson,
|
|
104
|
+
sessionMode,
|
|
105
|
+
layoutType,
|
|
106
|
+
location,
|
|
107
|
+
currentProvider: provider,
|
|
108
|
+
processor,
|
|
109
|
+
sessionDate,
|
|
110
|
+
visit,
|
|
111
|
+
formFields,
|
|
112
|
+
formFieldAdapters: formFieldAdapters ?? {},
|
|
113
|
+
formFieldValidators: formFieldValidators ?? {},
|
|
114
|
+
}),
|
|
115
|
+
[
|
|
116
|
+
patient,
|
|
117
|
+
formJson,
|
|
118
|
+
sessionMode,
|
|
119
|
+
layoutType,
|
|
120
|
+
location,
|
|
121
|
+
provider,
|
|
122
|
+
processor,
|
|
123
|
+
sessionDate,
|
|
124
|
+
visit,
|
|
125
|
+
formFields,
|
|
126
|
+
formFieldAdapters,
|
|
127
|
+
formFieldValidators,
|
|
128
|
+
],
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// re-create the full processor context
|
|
132
|
+
const processorContext = useMemo<FormProcessorContextProps>(
|
|
133
|
+
() => ({
|
|
134
|
+
...baseContext,
|
|
135
|
+
...mutableContext,
|
|
136
|
+
}),
|
|
137
|
+
[baseContext, mutableContext],
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// callback to update the mutable part of the context
|
|
141
|
+
const setProcessorContext = useCallback(
|
|
142
|
+
(updater: FormProcessorContextProps | ((prev: FormProcessorContextProps) => FormProcessorContextProps)) => {
|
|
143
|
+
setMutableContext((prevMutable) => {
|
|
144
|
+
// Build the "previous" full context to pass to the updater
|
|
145
|
+
const prevFull: FormProcessorContextProps = {
|
|
146
|
+
...baseContext,
|
|
147
|
+
...prevMutable,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const newFull = typeof updater === 'function' ? updater(prevFull) : updater;
|
|
151
|
+
|
|
152
|
+
// Only the updater form sees prevFull; a bare object may be a stale context, whose
|
|
153
|
+
// differences aren't attempted writes.
|
|
154
|
+
if (typeof updater === 'function') {
|
|
155
|
+
warnAboutIgnoredContextKeys(prevFull, newFull);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Extract only the mutable parts from the result
|
|
159
|
+
return {
|
|
160
|
+
domainObjectValue: newFull.domainObjectValue,
|
|
161
|
+
previousDomainObjectValue: newFull.previousDomainObjectValue,
|
|
162
|
+
customDependencies: newFull.customDependencies,
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
[baseContext],
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const processorSetters = useMemo<FormProcessorContextSetters>(
|
|
170
|
+
() => ({
|
|
171
|
+
setDomainObjectValue: (value) => setMutableContext((prev) => ({ ...prev, domainObjectValue: value })),
|
|
172
|
+
setPreviousDomainObjectValue: (value) =>
|
|
173
|
+
setMutableContext((prev) => ({ ...prev, previousDomainObjectValue: value })),
|
|
174
|
+
setCustomDependencies: (dependencies) =>
|
|
175
|
+
setMutableContext((prev) => ({
|
|
176
|
+
...prev,
|
|
177
|
+
customDependencies:
|
|
178
|
+
typeof dependencies === 'function' ? dependencies(prev.customDependencies ?? {}) : dependencies,
|
|
179
|
+
})),
|
|
180
|
+
}),
|
|
181
|
+
[],
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const { isLoading: isLoadingCustomDeps } = useProcessorDependencies(
|
|
185
|
+
processor,
|
|
186
|
+
processorContext,
|
|
187
|
+
setProcessorContext,
|
|
188
|
+
processorSetters,
|
|
189
|
+
);
|
|
66
190
|
const useCustomHooks = processor.getCustomHooks().useCustomHooks;
|
|
67
191
|
const [isLoadingCustomHooks, setIsLoadingCustomHooks] = useState(!!useCustomHooks);
|
|
68
|
-
const [isLoadingProcessorDependencies, setIsLoadingProcessorDependencies] = useState(true);
|
|
69
192
|
const {
|
|
70
193
|
isLoadingInitialValues,
|
|
71
194
|
initialValues,
|
|
72
195
|
error: initialValuesError,
|
|
73
196
|
} = useInitialValues(processor, isLoadingCustomDeps || isLoadingCustomHooks || isLoadingConcepts, processorContext);
|
|
74
197
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
198
|
+
// Derive loading state with useMemo to avoid effect-based state updates
|
|
199
|
+
const isLoadingProcessorDependencies = useMemo(
|
|
200
|
+
() => isLoadingCustomDeps || isLoadingCustomHooks || isLoadingConcepts || isLoadingInitialValues,
|
|
201
|
+
[isLoadingCustomDeps, isLoadingCustomHooks, isLoadingConcepts, isLoadingInitialValues],
|
|
202
|
+
);
|
|
80
203
|
|
|
204
|
+
// Notify parent of loading state changes
|
|
81
205
|
useEffect(() => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
...(formFieldAdapters && { formFieldAdapters }),
|
|
85
|
-
...(formFieldValidators && { formFieldValidators }),
|
|
86
|
-
...(formFieldsWithMeta?.length
|
|
87
|
-
? { formFields: formFieldsWithMeta }
|
|
88
|
-
: rawFormFields?.length
|
|
89
|
-
? { formFields: rawFormFields }
|
|
90
|
-
: {}),
|
|
91
|
-
}));
|
|
92
|
-
}, [formFieldAdapters, formFieldValidators, rawFormFields, formFieldsWithMeta]);
|
|
206
|
+
setIsLoadingFormDependencies(isLoadingProcessorDependencies);
|
|
207
|
+
}, [isLoadingProcessorDependencies, setIsLoadingFormDependencies]);
|
|
93
208
|
|
|
94
209
|
useEffect(() => {
|
|
95
210
|
reportError(initialValuesError, t('errorLoadingInitialValues', 'Error loading initial values'));
|
|
96
|
-
}, [initialValuesError]);
|
|
211
|
+
}, [initialValuesError, t]);
|
|
97
212
|
|
|
98
213
|
useEffect(() => {
|
|
99
214
|
if (formFieldAdapters) {
|
|
@@ -107,6 +222,7 @@ const FormProcessorFactory = ({
|
|
|
107
222
|
<CustomHooksRenderer
|
|
108
223
|
context={processorContext}
|
|
109
224
|
setContext={setProcessorContext}
|
|
225
|
+
setters={processorSetters}
|
|
110
226
|
useCustomHooks={useCustomHooks}
|
|
111
227
|
setIsLoadingCustomHooks={setIsLoadingCustomHooks}
|
|
112
228
|
/>
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
import { useEffect } from 'react';
|
|
2
2
|
import { type FormProcessorContextProps } from '../../types';
|
|
3
|
+
import { type FormProcessorContextSetters, type GetCustomHooksResponse } from '../../processors/form-processor';
|
|
3
4
|
|
|
4
5
|
export const CustomHooksRenderer = ({
|
|
5
6
|
context,
|
|
6
7
|
setContext,
|
|
8
|
+
setters,
|
|
7
9
|
useCustomHooks,
|
|
8
10
|
setIsLoadingCustomHooks,
|
|
9
11
|
}: {
|
|
10
12
|
context: FormProcessorContextProps;
|
|
11
|
-
setContext:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
isLoading: boolean;
|
|
15
|
-
error: any;
|
|
16
|
-
updateContext: (setContext: React.Dispatch<React.SetStateAction<FormProcessorContextProps>>) => void;
|
|
17
|
-
};
|
|
13
|
+
setContext: React.Dispatch<React.SetStateAction<FormProcessorContextProps>>;
|
|
14
|
+
setters: FormProcessorContextSetters;
|
|
15
|
+
useCustomHooks: GetCustomHooksResponse['useCustomHooks'];
|
|
18
16
|
setIsLoadingCustomHooks: (isLoading: boolean) => void;
|
|
19
17
|
}) => {
|
|
20
18
|
const { isLoading = false, error = null, data, updateContext } = useCustomHooks(context);
|
|
21
19
|
|
|
22
20
|
useEffect(() => {
|
|
23
21
|
if (!isLoading && updateContext) {
|
|
24
|
-
updateContext(setContext);
|
|
22
|
+
updateContext(setContext, setters);
|
|
25
23
|
setIsLoadingCustomHooks(false);
|
|
26
24
|
}
|
|
27
25
|
}, [isLoading]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import React, { Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { Button, ButtonSet, InlineLoading } from '@carbon/react';
|
|
4
4
|
import { I18nextProvider, useTranslation } from 'react-i18next';
|
|
@@ -217,9 +217,11 @@ const FormEngine = ({
|
|
|
217
217
|
|
|
218
218
|
function I18FormEngine(props: FormEngineProps) {
|
|
219
219
|
return (
|
|
220
|
-
<
|
|
221
|
-
<
|
|
222
|
-
|
|
220
|
+
<Suspense fallback={<Loader />}>
|
|
221
|
+
<I18nextProvider i18n={window.i18next} defaultNS={formEngineAppName}>
|
|
222
|
+
<FormEngine {...props} />
|
|
223
|
+
</I18nextProvider>
|
|
224
|
+
</Suspense>
|
|
223
225
|
);
|
|
224
226
|
}
|
|
225
227
|
|
|
@@ -1,22 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import useSWRImmutable from 'swr/immutable';
|
|
2
3
|
import { getRegisteredValidator } from '../registry/registry';
|
|
3
4
|
import { type FormField, type FormFieldValidator } from '../types';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
const [validators, setValidators] = useState<Record<string, FormFieldValidator>>();
|
|
6
|
+
const EMPTY_VALIDATORS: Record<string, FormFieldValidator> = {};
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
export function useFormFieldValidators(fields: FormField[]) {
|
|
9
|
+
const validatorTypesKey = useMemo(() => {
|
|
10
|
+
const uniqueTypes = new Set<string>();
|
|
10
11
|
fields.forEach((field) => {
|
|
11
|
-
field.validators?.forEach((validator) =>
|
|
12
|
-
});
|
|
13
|
-
const supportedTypesArray = Array.from(supportedTypes);
|
|
14
|
-
Promise.all(supportedTypesArray.map((type) => getRegisteredValidator(type))).then((validators) => {
|
|
15
|
-
setValidators(
|
|
16
|
-
Object.assign({}, ...validators.map((validator, index) => ({ [supportedTypesArray[index]]: validator }))),
|
|
17
|
-
);
|
|
12
|
+
field.validators?.forEach((validator) => uniqueTypes.add(validator.type));
|
|
18
13
|
});
|
|
14
|
+
return Array.from(uniqueTypes).sort().join(',');
|
|
19
15
|
}, [fields]);
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
const { data: validators } = useSWRImmutable(
|
|
18
|
+
validatorTypesKey ? ['formFieldValidators', validatorTypesKey] : null,
|
|
19
|
+
async ([, key]) => {
|
|
20
|
+
const types = key.split(',');
|
|
21
|
+
const loadedValidators = await Promise.all(types.map((type) => getRegisteredValidator(type)));
|
|
22
|
+
const validatorsByType: Record<string, FormFieldValidator> = {};
|
|
23
|
+
types.forEach((type, index) => {
|
|
24
|
+
validatorsByType[type] = loadedValidators[index];
|
|
25
|
+
});
|
|
26
|
+
return validatorsByType;
|
|
27
|
+
},
|
|
28
|
+
{ keepPreviousData: true },
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
return validators ?? EMPTY_VALIDATORS;
|
|
22
32
|
}
|
|
@@ -1,23 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import useSWRImmutable from 'swr/immutable';
|
|
2
3
|
import { type FormField, type FormFieldValueAdapter } from '../types';
|
|
3
4
|
import { getRegisteredFieldValueAdapter } from '../registry/registry';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
const [adapters, setAdapters] = useState<Record<string, FormFieldValueAdapter>>({});
|
|
6
|
+
const EMPTY_ADAPTERS: Record<string, FormFieldValueAdapter> = {};
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const supportedTypesArray = Array.from(supportedTypes);
|
|
14
|
-
Promise.all(supportedTypesArray.map((type) => getRegisteredFieldValueAdapter(type))).then((adapters) => {
|
|
15
|
-
const adaptersByType = supportedTypesArray.map((type, index) => ({
|
|
16
|
-
[type]: adapters[index],
|
|
17
|
-
}));
|
|
18
|
-
setAdapters(Object.assign({}, ...adaptersByType));
|
|
19
|
-
});
|
|
8
|
+
export const useFormFieldValueAdapters = (fields: FormField[]) => {
|
|
9
|
+
const typesKey = useMemo(() => {
|
|
10
|
+
const uniqueTypes = new Set<string>();
|
|
11
|
+
fields.forEach((field) => uniqueTypes.add(field.type));
|
|
12
|
+
return Array.from(uniqueTypes).sort().join(',');
|
|
20
13
|
}, [fields]);
|
|
21
14
|
|
|
22
|
-
|
|
15
|
+
const { data: adapters } = useSWRImmutable(
|
|
16
|
+
typesKey ? ['formFieldValueAdapters', typesKey] : null,
|
|
17
|
+
async ([, key]) => {
|
|
18
|
+
const types = key.split(',');
|
|
19
|
+
const loadedAdapters = await Promise.all(types.map((type) => getRegisteredFieldValueAdapter(type)));
|
|
20
|
+
const adaptersByType: Record<string, FormFieldValueAdapter> = {};
|
|
21
|
+
types.forEach((type, index) => {
|
|
22
|
+
adaptersByType[type] = loadedAdapters[index];
|
|
23
|
+
});
|
|
24
|
+
return adaptersByType;
|
|
25
|
+
},
|
|
26
|
+
{ keepPreviousData: true },
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return adapters ?? EMPTY_ADAPTERS;
|
|
23
30
|
};
|
|
@@ -2,64 +2,51 @@ import { useMemo } from 'react';
|
|
|
2
2
|
import { type FormField, type FormSchema } from '../types';
|
|
3
3
|
|
|
4
4
|
export function useFormFields(form: FormSchema): { formFields: FormField[]; conceptReferences: Set<string> } {
|
|
5
|
-
const [flattenedFields,
|
|
5
|
+
const [flattenedFields, conceptReferencesKey] = useMemo(() => {
|
|
6
6
|
const flattenedFieldsTemp: FormField[] = [];
|
|
7
7
|
const conceptReferencesTemp = new Set<string>();
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
fields
|
|
11
|
-
|
|
12
|
-
flattenedFields: FormField[];
|
|
13
|
-
conceptReferences: Set<string>;
|
|
14
|
-
} => {
|
|
15
|
-
const flattenedFields: FormField[] = [];
|
|
16
|
-
const conceptReferences = new Set<string>();
|
|
9
|
+
const processField = (field: FormField, parentGroupId?: string) => {
|
|
10
|
+
// Add group ID to nested fields if applicable
|
|
11
|
+
const processedField = parentGroupId ? { ...field, meta: { ...field.meta, groupId: parentGroupId } } : field;
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const processedField = parentGroupId ? { ...field, meta: { ...field.meta, groupId: parentGroupId } } : field;
|
|
13
|
+
// Add field to flattened list
|
|
14
|
+
flattenedFieldsTemp.push(processedField);
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
// Collect concept references
|
|
17
|
+
if (processedField.questionOptions?.concept) {
|
|
18
|
+
conceptReferencesTemp.add(processedField.questionOptions.concept);
|
|
19
|
+
}
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
// Collect concept references from answers
|
|
22
|
+
processedField.questionOptions?.answers?.forEach((answer) => {
|
|
23
|
+
if (answer.concept) {
|
|
24
|
+
conceptReferencesTemp.add(answer.concept);
|
|
28
25
|
}
|
|
26
|
+
});
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
28
|
+
// Recursively process nested questions for obsGroup
|
|
29
|
+
if (processedField.type === 'obsGroup' && processedField.questions) {
|
|
30
|
+
processedField.questions.forEach((nestedField) => {
|
|
31
|
+
processField(nestedField, processedField.id);
|
|
35
32
|
});
|
|
36
|
-
|
|
37
|
-
// Recursively process nested questions for obsGroup
|
|
38
|
-
if (processedField.type === 'obsGroup' && processedField.questions) {
|
|
39
|
-
processedField.questions.forEach((nestedField) => {
|
|
40
|
-
processField(nestedField, processedField.id);
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// Process all input fields
|
|
46
|
-
fields.forEach((field) => processField(field));
|
|
47
|
-
|
|
48
|
-
return { flattenedFields, conceptReferences };
|
|
33
|
+
}
|
|
49
34
|
};
|
|
50
35
|
|
|
36
|
+
// Process all input fields
|
|
51
37
|
form.pages?.forEach((page) =>
|
|
52
38
|
page.sections?.forEach((section) => {
|
|
53
|
-
|
|
54
|
-
const { flattenedFields, conceptReferences } = processFlattenedFields(section.questions);
|
|
55
|
-
flattenedFieldsTemp.push(...flattenedFields);
|
|
56
|
-
conceptReferences.forEach((conceptReference) => conceptReferencesTemp.add(conceptReference));
|
|
57
|
-
}
|
|
39
|
+
section.questions?.forEach((field) => processField(field));
|
|
58
40
|
}),
|
|
59
41
|
);
|
|
60
42
|
|
|
61
|
-
|
|
43
|
+
// Serialised so the set below is rebuilt only when the references change. JSON rather than a
|
|
44
|
+
// delimiter because mapping codes may contain any character, commas included.
|
|
45
|
+
const sortedRefs = Array.from(conceptReferencesTemp).sort();
|
|
46
|
+
return [flattenedFieldsTemp, JSON.stringify(sortedRefs)];
|
|
62
47
|
}, [form]);
|
|
63
48
|
|
|
49
|
+
const conceptReferences = useMemo(() => new Set<string>(JSON.parse(conceptReferencesKey)), [conceptReferencesKey]);
|
|
50
|
+
|
|
64
51
|
return { formFields: flattenedFields, conceptReferences };
|
|
65
52
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { type FormProcessorContextProps } from '../types';
|
|
3
3
|
import { type FormProcessor } from '../processors/form-processor';
|
|
4
4
|
|
|
@@ -11,16 +11,25 @@ const useInitialValues = (
|
|
|
11
11
|
const [initialValues, setInitialValues] = useState({});
|
|
12
12
|
const [error, setError] = useState(null);
|
|
13
13
|
|
|
14
|
+
const contextRef = useRef(context);
|
|
15
|
+
contextRef.current = context;
|
|
16
|
+
|
|
17
|
+
const hasStartedLoading = useRef(false);
|
|
18
|
+
const hasFormFields = context.formFields?.length > 0;
|
|
19
|
+
const hasFormFieldAdapters = Object.keys(context.formFieldAdapters ?? {}).length > 0;
|
|
20
|
+
|
|
14
21
|
useEffect(() => {
|
|
15
22
|
if (
|
|
16
23
|
formProcessor &&
|
|
17
24
|
!isLoadingContextDependencies &&
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
!
|
|
25
|
+
hasFormFields &&
|
|
26
|
+
hasFormFieldAdapters &&
|
|
27
|
+
!hasStartedLoading.current
|
|
21
28
|
) {
|
|
29
|
+
hasStartedLoading.current = true;
|
|
30
|
+
const currentContext = contextRef.current;
|
|
22
31
|
formProcessor
|
|
23
|
-
.getInitialValues(
|
|
32
|
+
.getInitialValues(currentContext)
|
|
24
33
|
.then((values) => {
|
|
25
34
|
setInitialValues(values);
|
|
26
35
|
setIsLoadingInitialValues(false);
|
|
@@ -31,7 +40,7 @@ const useInitialValues = (
|
|
|
31
40
|
setIsLoadingInitialValues(false);
|
|
32
41
|
});
|
|
33
42
|
}
|
|
34
|
-
}, [formProcessor, isLoadingContextDependencies,
|
|
43
|
+
}, [formProcessor, isLoadingContextDependencies, hasFormFields, hasFormFieldAdapters]);
|
|
35
44
|
|
|
36
45
|
return { isLoadingInitialValues, initialValues, error };
|
|
37
46
|
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import { type FormProcessorContextProps } from '../types';
|
|
3
|
-
import { type FormProcessor } from '../processors/form-processor';
|
|
3
|
+
import { type FormProcessor, type FormProcessorContextSetters } from '../processors/form-processor';
|
|
4
4
|
import { reportError } from '../utils/error-utils';
|
|
5
5
|
|
|
6
6
|
const useProcessorDependencies = (
|
|
7
7
|
formProcessor: FormProcessor,
|
|
8
8
|
context: Partial<FormProcessorContextProps>,
|
|
9
|
-
setContext:
|
|
9
|
+
setContext: React.Dispatch<React.SetStateAction<FormProcessorContextProps>>,
|
|
10
|
+
setters: FormProcessorContextSetters,
|
|
10
11
|
) => {
|
|
11
12
|
const [isLoading, setIsLoading] = useState(false);
|
|
12
13
|
const [error, setError] = useState('');
|
|
@@ -17,7 +18,7 @@ const useProcessorDependencies = (
|
|
|
17
18
|
|
|
18
19
|
if (loadDependencies) {
|
|
19
20
|
setIsLoading(true);
|
|
20
|
-
loadDependencies(context, setContext)
|
|
21
|
+
loadDependencies(context, setContext, setters)
|
|
21
22
|
.then(() => {
|
|
22
23
|
if (!ignore) {
|
|
23
24
|
setIsLoading(false);
|