@nwon/react-mui-hook-form 1.0.0
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/README.md +106 -0
- package/dist/LoadingButton/LoadingButton.d.ts +6 -0
- package/dist/LoadingButton/LoadingButtonInterfaces.d.ts +33 -0
- package/dist/LoadingButton/getLoadingButtonState.d.ts +20 -0
- package/dist/LoadingButton/index.d.ts +4 -0
- package/dist/formFields/HookFormProvider.d.ts +7 -0
- package/dist/formFields/InputEmail/InputEmail.d.ts +11 -0
- package/dist/formFields/InputPasswordSet/InputPasswordSet.d.ts +8 -0
- package/dist/formFields/InputPasswordSet/InputPasswordSetInterfaces.d.ts +15 -0
- package/dist/formFields/TranslatedString.d.ts +13 -0
- package/dist/formFields/baseTypes/InputAutocomplete/InputAutocomplete.d.ts +8 -0
- package/dist/formFields/baseTypes/InputAutocomplete/InputAutocompleteInterfaces.d.ts +22 -0
- package/dist/formFields/baseTypes/InputCheckbox/InputCheckbox.d.ts +16 -0
- package/dist/formFields/baseTypes/InputDateTimePicker/InputDateTimePicker.d.ts +12 -0
- package/dist/formFields/baseTypes/InputRadio/InputRadio.d.ts +10 -0
- package/dist/formFields/baseTypes/InputSelect/InputSelect.d.ts +16 -0
- package/dist/formFields/baseTypes/InputSelectFromEnum/InputSelectFromEnum.d.ts +6 -0
- package/dist/formFields/baseTypes/InputSelectFromEnum/InputSelectFromEnumInterfaces.d.ts +10 -0
- package/dist/formFields/baseTypes/InputText/InputText.d.ts +14 -0
- package/dist/formFields/errorMessages.d.ts +1 -0
- package/dist/formFields/formHelper/getFormError.d.ts +11 -0
- package/dist/formFields/formHelper/syncSubmit.d.ts +15 -0
- package/dist/formFields/formHelper/useHookForm.d.ts +50 -0
- package/dist/formFields/formHelper/useHookFormContext.d.ts +9 -0
- package/dist/formFields/formHelper/useMergedFormField.d.ts +6 -0
- package/dist/formFields/formHelper/useSortedOptionsWithGroup.d.ts +5 -0
- package/dist/formFields/formHelper/useTranslateString.d.ts +8 -0
- package/dist/formFields/index.d.ts +28 -0
- package/dist/formFields/inputTypes.d.ts +76 -0
- package/dist/packageExports.d.ts +7 -0
- package/dist/packageExports.js +727 -0
- package/dist/packageExports.js.map +1 -0
- package/license +9 -0
- package/package.json +1 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
function ___$insertStyle(css) {
|
|
2
|
+
if (!css || typeof window === 'undefined') {
|
|
3
|
+
return;
|
|
4
|
+
}
|
|
5
|
+
const style = document.createElement('style');
|
|
6
|
+
style.setAttribute('type', 'text/css');
|
|
7
|
+
style.innerHTML = css;
|
|
8
|
+
document.head.appendChild(style);
|
|
9
|
+
return css;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
import { LoadingButton as LoadingButton$1 } from '@mui/lab';
|
|
13
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
14
|
+
import { useFormContext, FormProvider, useFormState, Controller, useForm } from 'react-hook-form';
|
|
15
|
+
import { useFormControl, FormControl, InputLabel, OutlinedInput, FormHelperText, Autocomplete, TextField, FormControlLabel, Checkbox, Box, FormLabel, RadioGroup, Radio, Select, MenuItem, Stack, Tooltip, ListSubheader } from '@mui/material';
|
|
16
|
+
import { uppercaseFirstLetter, assertTruthy, objectHasKey, rethrow } from '@nvon/baseline';
|
|
17
|
+
import { useCallback, useMemo, useState, createElement } from 'react';
|
|
18
|
+
import { t } from 'i18next';
|
|
19
|
+
import { DateTimePicker } from '@mui/x-date-pickers';
|
|
20
|
+
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
|
|
21
|
+
import { identity } from 'lodash-es';
|
|
22
|
+
|
|
23
|
+
var LoadingButtonState;
|
|
24
|
+
(function (LoadingButtonState) {
|
|
25
|
+
LoadingButtonState["Idle"] = "idle";
|
|
26
|
+
LoadingButtonState["Loading"] = "loading";
|
|
27
|
+
LoadingButtonState["Success"] = "success";
|
|
28
|
+
})(LoadingButtonState || (LoadingButtonState = {}));
|
|
29
|
+
|
|
30
|
+
const LoadingButton = ({
|
|
31
|
+
buttonText,
|
|
32
|
+
buttonTextSuccess,
|
|
33
|
+
loadingButtonProps,
|
|
34
|
+
loadingButtonPropsPropsSuccess,
|
|
35
|
+
buttonState,
|
|
36
|
+
successIcon
|
|
37
|
+
}) => {
|
|
38
|
+
const isSuccess = buttonState === LoadingButtonState.Success;
|
|
39
|
+
const propsToSpread = isSuccess ? loadingButtonPropsPropsSuccess : loadingButtonProps;
|
|
40
|
+
return /*#__PURE__*/jsx(LoadingButton$1, {
|
|
41
|
+
loading: buttonState === LoadingButtonState.Loading,
|
|
42
|
+
disabled: buttonState !== LoadingButtonState.Idle,
|
|
43
|
+
...propsToSpread,
|
|
44
|
+
children: buttonState === LoadingButtonState.Success ? /*#__PURE__*/jsxs(Fragment, {
|
|
45
|
+
children: [successIcon ? successIcon : null, buttonTextSuccess ? buttonTextSuccess : buttonText]
|
|
46
|
+
}) : buttonText
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get loading button state from react-query mutation status.
|
|
52
|
+
*/
|
|
53
|
+
const loadingButtonStateFromReactQueryMutationStatus = mutationStatus => {
|
|
54
|
+
const mapStatus = {
|
|
55
|
+
idle: LoadingButtonState.Idle,
|
|
56
|
+
error: LoadingButtonState.Idle,
|
|
57
|
+
success: LoadingButtonState.Success,
|
|
58
|
+
loading: LoadingButtonState.Loading
|
|
59
|
+
};
|
|
60
|
+
return mapStatus[mutationStatus];
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Get loading button state from a react-hook-form context.
|
|
64
|
+
*/
|
|
65
|
+
const useLoadingButtonStateFromFormContext = () => {
|
|
66
|
+
const formContext = useFormContext();
|
|
67
|
+
return loadingButtonStateFromFormState(formContext.formState);
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Get loading button state from a react-hook-form state.
|
|
71
|
+
*/
|
|
72
|
+
const loadingButtonStateFromFormState = (
|
|
73
|
+
// we are not interested in the value types FormState holds
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
+
formState) => {
|
|
76
|
+
if (formState.isSubmitting) {
|
|
77
|
+
return LoadingButtonState.Loading;
|
|
78
|
+
}
|
|
79
|
+
if (formState.isDirty) {
|
|
80
|
+
return LoadingButtonState.Idle;
|
|
81
|
+
}
|
|
82
|
+
if (formState.isSubmitSuccessful) {
|
|
83
|
+
return LoadingButtonState.Success;
|
|
84
|
+
}
|
|
85
|
+
return LoadingButtonState.Idle;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Reexport of FormProvider.
|
|
90
|
+
*
|
|
91
|
+
* This became necessary as context is not available when the FormProvider is initialized outside of the package. Most likely this is some kind of instance mismatch.
|
|
92
|
+
*/
|
|
93
|
+
const HookFormProvider = FormProvider;
|
|
94
|
+
|
|
95
|
+
const formContextNull = "FormContext is null. This should not happen. You may need to use the HookFormProvider instead of the FormProvider exported by react-hook-form";
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get the field errors for a (possibly nested) field name.
|
|
99
|
+
*
|
|
100
|
+
* Checking `formState.errors[fieldName]` only works if fieldName is not a path
|
|
101
|
+
* (e.g. Does not include dots).
|
|
102
|
+
*
|
|
103
|
+
* Did not find a built-in way to do this … there might be one though.
|
|
104
|
+
*/
|
|
105
|
+
const getFormError = (errors, fieldName) => {
|
|
106
|
+
const nestedKeys = fieldName.split(".");
|
|
107
|
+
//
|
|
108
|
+
// To my best knowledge, this works as expected.
|
|
109
|
+
//
|
|
110
|
+
// It recursively walks the error object by the given keys
|
|
111
|
+
// until the lowest level is reached, or undefined returned.
|
|
112
|
+
//
|
|
113
|
+
// Typescript is just not very happy with the dynamic nature of this,
|
|
114
|
+
// hence the errors.
|
|
115
|
+
//
|
|
116
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment,@typescript-eslint/no-unsafe-return */
|
|
117
|
+
// @ts-expect-error
|
|
118
|
+
return nestedKeys.reduce(
|
|
119
|
+
// @ts-expect-error
|
|
120
|
+
(errorObj, pathKey) => errorObj?.[pathKey], errors);
|
|
121
|
+
/* eslint-enable */
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Return a function which turns our TranslatedString into a plain string.
|
|
126
|
+
*
|
|
127
|
+
* @throws Error if no t was provided but a translated string.
|
|
128
|
+
*/
|
|
129
|
+
const useTranslateString = () => {
|
|
130
|
+
if (!t) {
|
|
131
|
+
throw Error("If you pass in a translated validate function you need to provide a t");
|
|
132
|
+
}
|
|
133
|
+
return useCallback(input => {
|
|
134
|
+
return typeof input === "string" ? input : input(t);
|
|
135
|
+
}, []);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Merge a form field with defaults.
|
|
140
|
+
*/
|
|
141
|
+
const useMergedFormField = (
|
|
142
|
+
/**
|
|
143
|
+
* Form fields, typically those passed into a component.
|
|
144
|
+
*
|
|
145
|
+
*/
|
|
146
|
+
formField, t,
|
|
147
|
+
/**
|
|
148
|
+
* Additional defaults. Duplicate values will be overridden by the formField.
|
|
149
|
+
*/
|
|
150
|
+
formFieldDefaults) => {
|
|
151
|
+
const translate = useTranslateString();
|
|
152
|
+
const merged = useMemo(() => {
|
|
153
|
+
const builtInDefaults = {
|
|
154
|
+
label: uppercaseFirstLetter(formField.name),
|
|
155
|
+
required: false,
|
|
156
|
+
requiredErrorMessage: "Please fill in required information",
|
|
157
|
+
validate: undefined
|
|
158
|
+
};
|
|
159
|
+
return {
|
|
160
|
+
...builtInDefaults,
|
|
161
|
+
...formFieldDefaults,
|
|
162
|
+
...formField
|
|
163
|
+
};
|
|
164
|
+
}, [formField, formFieldDefaults]);
|
|
165
|
+
const withPlainStrings = useMemo(() => {
|
|
166
|
+
// Translating the validate function is a bit tricky,
|
|
167
|
+
// since it can be an object in both cases.
|
|
168
|
+
const validate = (() => {
|
|
169
|
+
if (typeof merged.validate === "object" && typeof merged.validate.translated === "function") {
|
|
170
|
+
if (!t) {
|
|
171
|
+
throw Error("If you pass in a translated validate function you need to provide a t");
|
|
172
|
+
}
|
|
173
|
+
return merged.validate.translated(t);
|
|
174
|
+
}
|
|
175
|
+
return merged.validate;
|
|
176
|
+
})(); // This should be fine. Not sure why TS is complaining.
|
|
177
|
+
return {
|
|
178
|
+
...merged,
|
|
179
|
+
label: translate(merged.label, t),
|
|
180
|
+
requiredErrorMessage: translate(merged.requiredErrorMessage, t),
|
|
181
|
+
validate
|
|
182
|
+
};
|
|
183
|
+
}, [merged, t, translate]);
|
|
184
|
+
return withPlainStrings;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const InputText = ({
|
|
188
|
+
formField,
|
|
189
|
+
textFieldProps
|
|
190
|
+
}) => {
|
|
191
|
+
const form = useFormContext();
|
|
192
|
+
const muiFormControl = useFormControl();
|
|
193
|
+
const {
|
|
194
|
+
label,
|
|
195
|
+
name,
|
|
196
|
+
required,
|
|
197
|
+
requiredErrorMessage,
|
|
198
|
+
validate
|
|
199
|
+
} = useMergedFormField(formField, formField.t);
|
|
200
|
+
if (!form) {
|
|
201
|
+
throw Error(formContextNull);
|
|
202
|
+
}
|
|
203
|
+
const {
|
|
204
|
+
register,
|
|
205
|
+
formState
|
|
206
|
+
} = form;
|
|
207
|
+
const error = getFormError(formState.errors, formField.name);
|
|
208
|
+
return /*#__PURE__*/jsxs(FormControl, {
|
|
209
|
+
error: !!error,
|
|
210
|
+
required: required,
|
|
211
|
+
disabled: muiFormControl?.disabled,
|
|
212
|
+
children: [/*#__PURE__*/jsx(InputLabel, {
|
|
213
|
+
required: required,
|
|
214
|
+
children: label
|
|
215
|
+
}), /*#__PURE__*/jsx(OutlinedInput, {
|
|
216
|
+
...register(name, {
|
|
217
|
+
required: required ? requiredErrorMessage : false,
|
|
218
|
+
validate,
|
|
219
|
+
disabled: muiFormControl?.disabled
|
|
220
|
+
}),
|
|
221
|
+
required: required,
|
|
222
|
+
label: label,
|
|
223
|
+
...textFieldProps
|
|
224
|
+
}), error?.message && /*#__PURE__*/jsx(FormHelperText, {
|
|
225
|
+
children: error?.message
|
|
226
|
+
})]
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const InputEmail = props => {
|
|
231
|
+
const merged = useMergedFormField(props.formField, props.formField.t, formDefaults);
|
|
232
|
+
return /*#__PURE__*/jsx(InputText, {
|
|
233
|
+
textFieldProps: {
|
|
234
|
+
type: "email",
|
|
235
|
+
...props.textFieldProps
|
|
236
|
+
},
|
|
237
|
+
formField: merged
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
const formDefaults = {
|
|
241
|
+
required: true,
|
|
242
|
+
validate: emailInput => {
|
|
243
|
+
if (emailInput.match(/.@./)) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
return "E-Mail address invalid";
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const InputPasswordSet = ({
|
|
251
|
+
passwordField,
|
|
252
|
+
passwordValidationField,
|
|
253
|
+
doNotMatchError
|
|
254
|
+
}) => {
|
|
255
|
+
const {
|
|
256
|
+
watch
|
|
257
|
+
} = useFormContext();
|
|
258
|
+
const passwordFieldMerged = useMergedFormField(passwordField, passwordField.t, {
|
|
259
|
+
required: true
|
|
260
|
+
});
|
|
261
|
+
const validationFieldMerged = useMergedFormField(passwordValidationField, passwordValidationField.t, {
|
|
262
|
+
required: true
|
|
263
|
+
});
|
|
264
|
+
const validatePasswordMatch = useCallback(value => {
|
|
265
|
+
const passwordValue = watch(passwordFieldMerged.name);
|
|
266
|
+
return value === passwordValue || doNotMatchError;
|
|
267
|
+
}, [doNotMatchError, passwordFieldMerged.name, watch]);
|
|
268
|
+
return /*#__PURE__*/jsxs(Fragment, {
|
|
269
|
+
children: [/*#__PURE__*/jsx(InputText, {
|
|
270
|
+
formField: passwordFieldMerged,
|
|
271
|
+
textFieldProps: {
|
|
272
|
+
type: "password"
|
|
273
|
+
}
|
|
274
|
+
}), /*#__PURE__*/jsx(InputText, {
|
|
275
|
+
formField: {
|
|
276
|
+
...validationFieldMerged,
|
|
277
|
+
validate: validatePasswordMatch,
|
|
278
|
+
t: passwordValidationField.t
|
|
279
|
+
},
|
|
280
|
+
textFieldProps: {
|
|
281
|
+
type: "password"
|
|
282
|
+
}
|
|
283
|
+
})]
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const InputAutocomplete = ({
|
|
288
|
+
formField,
|
|
289
|
+
selectableOptions,
|
|
290
|
+
multiSelect,
|
|
291
|
+
noOptions,
|
|
292
|
+
autoCompleteProps
|
|
293
|
+
}) => {
|
|
294
|
+
const form = useFormContext();
|
|
295
|
+
if (!form) {
|
|
296
|
+
throw Error(formContextNull);
|
|
297
|
+
}
|
|
298
|
+
const {
|
|
299
|
+
register,
|
|
300
|
+
watch,
|
|
301
|
+
formState,
|
|
302
|
+
setValue
|
|
303
|
+
} = form;
|
|
304
|
+
const error = getFormError(formState.errors, formField.name);
|
|
305
|
+
const muiFormControl = useFormControl();
|
|
306
|
+
const {
|
|
307
|
+
label,
|
|
308
|
+
name,
|
|
309
|
+
required,
|
|
310
|
+
requiredErrorMessage,
|
|
311
|
+
validate
|
|
312
|
+
} = useMergedFormField(formField, formField.t);
|
|
313
|
+
const value = watch(name);
|
|
314
|
+
let valueToUse = value;
|
|
315
|
+
if (!value) {
|
|
316
|
+
valueToUse = multiSelect ? [] : "";
|
|
317
|
+
}
|
|
318
|
+
assertTruthy(["string", "number", "object"].includes(typeof valueToUse), "Value for select input has unexpected type");
|
|
319
|
+
const props = register(name, {
|
|
320
|
+
required: required ? requiredErrorMessage : false,
|
|
321
|
+
validate,
|
|
322
|
+
disabled: muiFormControl?.disabled
|
|
323
|
+
});
|
|
324
|
+
const currentValue = useMemo(() => multiSelect ? selectableOptions.filter(o => {
|
|
325
|
+
if (Array.isArray(valueToUse)) {
|
|
326
|
+
return valueToUse.includes(o.value);
|
|
327
|
+
}
|
|
328
|
+
return valueToUse;
|
|
329
|
+
}) : valueToUse, [multiSelect, selectableOptions, valueToUse]);
|
|
330
|
+
return /*#__PURE__*/jsxs(FormControl, {
|
|
331
|
+
error: !!error,
|
|
332
|
+
required: required,
|
|
333
|
+
disabled: muiFormControl?.disabled,
|
|
334
|
+
children: [/*#__PURE__*/jsx(Autocomplete, {
|
|
335
|
+
options: selectableOptions,
|
|
336
|
+
multiple: multiSelect,
|
|
337
|
+
value: currentValue,
|
|
338
|
+
label: label,
|
|
339
|
+
...props,
|
|
340
|
+
getOptionLabel: o => {
|
|
341
|
+
if (objectHasKey(o, "label")) {
|
|
342
|
+
return o.label;
|
|
343
|
+
}
|
|
344
|
+
return "";
|
|
345
|
+
},
|
|
346
|
+
onChange: (e, selectedOption) => {
|
|
347
|
+
if (Array.isArray(selectedOption)) {
|
|
348
|
+
setValue(name, selectedOption.map(o => o.value));
|
|
349
|
+
} else {
|
|
350
|
+
setValue(name, selectedOption.value);
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
noOptionsText: noOptions,
|
|
354
|
+
renderInput: params => /*#__PURE__*/jsx(TextField, {
|
|
355
|
+
...params,
|
|
356
|
+
label: label,
|
|
357
|
+
variant: "outlined"
|
|
358
|
+
})
|
|
359
|
+
// need to parse this as any in order to get no typing errors
|
|
360
|
+
// for multiselect
|
|
361
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
362
|
+
,
|
|
363
|
+
...autoCompleteProps
|
|
364
|
+
}), error?.message && /*#__PURE__*/jsx(FormHelperText, {
|
|
365
|
+
children: error?.message
|
|
366
|
+
})]
|
|
367
|
+
});
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const InputCheckbox = ({
|
|
371
|
+
formField,
|
|
372
|
+
jsxLabel
|
|
373
|
+
}) => {
|
|
374
|
+
const {
|
|
375
|
+
register,
|
|
376
|
+
watch,
|
|
377
|
+
setValue
|
|
378
|
+
} = useFormContext();
|
|
379
|
+
const {
|
|
380
|
+
errors
|
|
381
|
+
} = useFormState();
|
|
382
|
+
const error = getFormError(errors, formField.name);
|
|
383
|
+
const muiFormControl = useFormControl();
|
|
384
|
+
const {
|
|
385
|
+
label,
|
|
386
|
+
name,
|
|
387
|
+
required,
|
|
388
|
+
requiredErrorMessage,
|
|
389
|
+
validate
|
|
390
|
+
} = useMergedFormField(formField);
|
|
391
|
+
const changeEv = useCallback(e => {
|
|
392
|
+
setValue(name, e.target.checked, {
|
|
393
|
+
shouldValidate: true,
|
|
394
|
+
shouldDirty: true,
|
|
395
|
+
shouldTouch: true
|
|
396
|
+
});
|
|
397
|
+
}, [name, setValue]);
|
|
398
|
+
const id = `rvo-input-checkbox-${name}`;
|
|
399
|
+
const registered = register(name, {
|
|
400
|
+
required: required ? requiredErrorMessage : false,
|
|
401
|
+
onChange: changeEv,
|
|
402
|
+
validate,
|
|
403
|
+
disabled: muiFormControl?.disabled
|
|
404
|
+
});
|
|
405
|
+
const value = watch(name, false) ?? false;
|
|
406
|
+
return /*#__PURE__*/jsxs(FormControl, {
|
|
407
|
+
error: !!error,
|
|
408
|
+
disabled: muiFormControl?.disabled,
|
|
409
|
+
children: [/*#__PURE__*/jsx(FormControlLabel, {
|
|
410
|
+
required: jsxLabel ? false : required // If jsxLabel is set, we don't want to show the asterisk.
|
|
411
|
+
,
|
|
412
|
+
control: /*#__PURE__*/jsx(Checkbox, {
|
|
413
|
+
checked: value,
|
|
414
|
+
"aria-labelledby": id,
|
|
415
|
+
...registered
|
|
416
|
+
}),
|
|
417
|
+
label: jsxLabel ?? label
|
|
418
|
+
}), error?.message && /*#__PURE__*/jsx(FormHelperText, {
|
|
419
|
+
children: error?.message
|
|
420
|
+
})]
|
|
421
|
+
});
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const InputDateTimePicker = ({
|
|
425
|
+
formField,
|
|
426
|
+
dateTimePickerProps
|
|
427
|
+
}) => {
|
|
428
|
+
const {
|
|
429
|
+
formState,
|
|
430
|
+
control
|
|
431
|
+
} = useFormContext();
|
|
432
|
+
const error = getFormError(formState.errors, formField.name);
|
|
433
|
+
const muiFormControl = useFormControl();
|
|
434
|
+
const {
|
|
435
|
+
label,
|
|
436
|
+
name,
|
|
437
|
+
required
|
|
438
|
+
} = useMergedFormField(formField);
|
|
439
|
+
const [date, setDate] = useState(new Date());
|
|
440
|
+
return /*#__PURE__*/jsx(Box, {
|
|
441
|
+
children: /*#__PURE__*/jsxs(FormControl, {
|
|
442
|
+
error: !!error,
|
|
443
|
+
required: required,
|
|
444
|
+
disabled: muiFormControl?.disabled,
|
|
445
|
+
children: [/*#__PURE__*/jsx(Controller, {
|
|
446
|
+
name: name,
|
|
447
|
+
defaultValue: date,
|
|
448
|
+
control: control,
|
|
449
|
+
render: ({
|
|
450
|
+
field: {
|
|
451
|
+
onChange,
|
|
452
|
+
...restField
|
|
453
|
+
}
|
|
454
|
+
}) => /*#__PURE__*/jsx(DateTimePicker, {
|
|
455
|
+
label: label,
|
|
456
|
+
ampm: dateTimePickerProps?.ampm ? dateTimePickerProps.ampm : false,
|
|
457
|
+
onChange: event => {
|
|
458
|
+
onChange(event);
|
|
459
|
+
if (event) {
|
|
460
|
+
setDate(event);
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
disabled: muiFormControl?.disabled,
|
|
464
|
+
renderInput: params => /*#__PURE__*/jsx(TextField, {
|
|
465
|
+
required: required,
|
|
466
|
+
...params
|
|
467
|
+
}),
|
|
468
|
+
...restField
|
|
469
|
+
})
|
|
470
|
+
}), error?.message && /*#__PURE__*/jsx(FormHelperText, {
|
|
471
|
+
children: error?.message
|
|
472
|
+
})]
|
|
473
|
+
})
|
|
474
|
+
});
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const InputRadio = ({
|
|
478
|
+
formField,
|
|
479
|
+
selectableOptions
|
|
480
|
+
}) => {
|
|
481
|
+
const {
|
|
482
|
+
register,
|
|
483
|
+
watch,
|
|
484
|
+
setValue
|
|
485
|
+
} = useFormContext();
|
|
486
|
+
const {
|
|
487
|
+
errors
|
|
488
|
+
} = useFormState();
|
|
489
|
+
const error = getFormError(errors, formField.name);
|
|
490
|
+
const muiFormControl = useFormControl();
|
|
491
|
+
const {
|
|
492
|
+
label,
|
|
493
|
+
name,
|
|
494
|
+
required,
|
|
495
|
+
requiredErrorMessage,
|
|
496
|
+
validate
|
|
497
|
+
} = useMergedFormField(formField);
|
|
498
|
+
const changeEv = useCallback(e => {
|
|
499
|
+
setValue(name, e.target.value, {
|
|
500
|
+
shouldValidate: true,
|
|
501
|
+
shouldDirty: true,
|
|
502
|
+
shouldTouch: true
|
|
503
|
+
});
|
|
504
|
+
}, [name, setValue]);
|
|
505
|
+
const value = watch(name) ?? "";
|
|
506
|
+
assertTruthy(["string", "number"].includes(typeof value), "Value for select input has unexpected type");
|
|
507
|
+
const id = `rvo-input-radio-${name}`;
|
|
508
|
+
const registered = register(name, {
|
|
509
|
+
required: required ? requiredErrorMessage : false,
|
|
510
|
+
onChange: changeEv,
|
|
511
|
+
validate,
|
|
512
|
+
disabled: muiFormControl?.disabled
|
|
513
|
+
});
|
|
514
|
+
return /*#__PURE__*/jsxs(FormControl, {
|
|
515
|
+
error: !!error,
|
|
516
|
+
disabled: muiFormControl?.disabled,
|
|
517
|
+
children: [/*#__PURE__*/jsx(FormLabel, {
|
|
518
|
+
id: id,
|
|
519
|
+
required: required,
|
|
520
|
+
children: label
|
|
521
|
+
}), /*#__PURE__*/jsx(RadioGroup, {
|
|
522
|
+
value: value,
|
|
523
|
+
"aria-labelledby": id,
|
|
524
|
+
children: selectableOptions?.map(option => {
|
|
525
|
+
return /*#__PURE__*/createElement(FormControlLabel, {
|
|
526
|
+
...registered,
|
|
527
|
+
key: option.value,
|
|
528
|
+
value: option.value,
|
|
529
|
+
control: /*#__PURE__*/jsx(Radio, {}),
|
|
530
|
+
label: option.label
|
|
531
|
+
});
|
|
532
|
+
})
|
|
533
|
+
}), error?.message && /*#__PURE__*/jsx(FormHelperText, {
|
|
534
|
+
children: error?.message
|
|
535
|
+
})]
|
|
536
|
+
});
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Sorts the options by group and value.
|
|
541
|
+
*/
|
|
542
|
+
const useSortedOptionsWithGroup = selectableOptions => {
|
|
543
|
+
return useMemo(() => selectableOptions.sort((a, b) => {
|
|
544
|
+
if (a.group == undefined && !!b.group) {
|
|
545
|
+
return -1;
|
|
546
|
+
}
|
|
547
|
+
if (a.group && b.group && a.group < b.group) {
|
|
548
|
+
return -1;
|
|
549
|
+
}
|
|
550
|
+
if (a.group == b.group && b.value > a.value) {
|
|
551
|
+
return 1;
|
|
552
|
+
}
|
|
553
|
+
return 0;
|
|
554
|
+
}), [selectableOptions]);
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const InputSelect = ({
|
|
558
|
+
formField,
|
|
559
|
+
selectableOptions,
|
|
560
|
+
placeholderText,
|
|
561
|
+
selectComponentProps
|
|
562
|
+
}) => {
|
|
563
|
+
const form = useFormContext();
|
|
564
|
+
if (!form) {
|
|
565
|
+
throw Error(formContextNull);
|
|
566
|
+
}
|
|
567
|
+
const {
|
|
568
|
+
register,
|
|
569
|
+
watch,
|
|
570
|
+
formState
|
|
571
|
+
} = form;
|
|
572
|
+
const error = getFormError(formState.errors, formField.name);
|
|
573
|
+
const muiFormControl = useFormControl();
|
|
574
|
+
const {
|
|
575
|
+
label,
|
|
576
|
+
name,
|
|
577
|
+
required,
|
|
578
|
+
requiredErrorMessage,
|
|
579
|
+
validate
|
|
580
|
+
} = useMergedFormField(formField, formField.t);
|
|
581
|
+
const value = watch(name) ?? "";
|
|
582
|
+
assertTruthy(["string", "number"].includes(typeof value), "Value for select input has unexpected type");
|
|
583
|
+
const id = `input-select-${name}`;
|
|
584
|
+
const sortedOptions = useSortedOptionsWithGroup(selectableOptions);
|
|
585
|
+
return /*#__PURE__*/jsxs(FormControl, {
|
|
586
|
+
error: !!error,
|
|
587
|
+
required: required,
|
|
588
|
+
disabled: muiFormControl?.disabled,
|
|
589
|
+
children: [/*#__PURE__*/jsx(InputLabel, {
|
|
590
|
+
id: id,
|
|
591
|
+
required: required,
|
|
592
|
+
children: label
|
|
593
|
+
}), /*#__PURE__*/jsxs(Select, {
|
|
594
|
+
labelId: id,
|
|
595
|
+
...selectComponentProps,
|
|
596
|
+
error: !!formState.errors[name],
|
|
597
|
+
label: label,
|
|
598
|
+
value: value,
|
|
599
|
+
...register(name, {
|
|
600
|
+
required: required ? requiredErrorMessage : false,
|
|
601
|
+
validate,
|
|
602
|
+
disabled: muiFormControl?.disabled
|
|
603
|
+
}),
|
|
604
|
+
children: [/*#__PURE__*/jsx(MenuItem, {
|
|
605
|
+
value: "",
|
|
606
|
+
children: /*#__PURE__*/jsx("em", {
|
|
607
|
+
children: placeholderText
|
|
608
|
+
})
|
|
609
|
+
}), sortedOptions?.map((option, index) => {
|
|
610
|
+
const optionBefore = sortedOptions[index - 1];
|
|
611
|
+
const showGroup = (optionBefore || index == 0) && option.group && optionBefore?.group !== option.group;
|
|
612
|
+
const disabledText = typeof option.disabled === "string" ? option.disabled : undefined;
|
|
613
|
+
const isDisabled = !!disabledText || !!option.disabled;
|
|
614
|
+
const menuItem = /*#__PURE__*/jsx(MenuItem, {
|
|
615
|
+
value: option.value,
|
|
616
|
+
disabled: isDisabled,
|
|
617
|
+
title: typeof option.disabled === "string" ? option.disabled : undefined,
|
|
618
|
+
selected: value === option.value,
|
|
619
|
+
children: option.label
|
|
620
|
+
}, option.label);
|
|
621
|
+
const wrappedMenuItem = disabledText ? /*#__PURE__*/jsxs(Box, {
|
|
622
|
+
sx: {
|
|
623
|
+
position: "relative"
|
|
624
|
+
},
|
|
625
|
+
children: [menuItem, /*#__PURE__*/jsx(Stack, {
|
|
626
|
+
direction: "column",
|
|
627
|
+
justifyContent: "center",
|
|
628
|
+
spacing: 1,
|
|
629
|
+
sx: {
|
|
630
|
+
position: "absolute",
|
|
631
|
+
right: th => th.spacing(0.5),
|
|
632
|
+
top: 0,
|
|
633
|
+
bottom: 0
|
|
634
|
+
},
|
|
635
|
+
children: /*#__PURE__*/jsx(Tooltip, {
|
|
636
|
+
title: disabledText,
|
|
637
|
+
children: /*#__PURE__*/jsx(ErrorOutlineIcon, {})
|
|
638
|
+
})
|
|
639
|
+
})]
|
|
640
|
+
}) : menuItem;
|
|
641
|
+
return showGroup ? [/*#__PURE__*/jsx(ListSubheader, {
|
|
642
|
+
children: option.group
|
|
643
|
+
}, option.group), wrappedMenuItem] : wrappedMenuItem;
|
|
644
|
+
})]
|
|
645
|
+
}), error?.message && /*#__PURE__*/jsx(FormHelperText, {
|
|
646
|
+
children: error?.message
|
|
647
|
+
})]
|
|
648
|
+
});
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
const InputSelectFromEnum = ({
|
|
652
|
+
enumData,
|
|
653
|
+
formField,
|
|
654
|
+
placeholderText
|
|
655
|
+
}) => {
|
|
656
|
+
const translateFunction = formField.t;
|
|
657
|
+
const options = useMemo(() => {
|
|
658
|
+
return Object.entries(enumData.enumToUse).map(([, value]) => {
|
|
659
|
+
const label = enumData.labelsForEnum[value];
|
|
660
|
+
assertTruthy(label, "Could not find a label for value" + value);
|
|
661
|
+
if (typeof label === "string") {
|
|
662
|
+
return {
|
|
663
|
+
label,
|
|
664
|
+
value
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
assertTruthy(translateFunction, "If you want to use translated labels you need to provide a translate function in the HookFormProvider.");
|
|
668
|
+
return {
|
|
669
|
+
label: label(translateFunction),
|
|
670
|
+
value
|
|
671
|
+
};
|
|
672
|
+
});
|
|
673
|
+
}, [enumData.enumToUse, enumData.labelsForEnum, translateFunction]);
|
|
674
|
+
return /*#__PURE__*/jsx(InputSelect, {
|
|
675
|
+
selectableOptions: options,
|
|
676
|
+
formField: formField,
|
|
677
|
+
placeholderText: placeholderText
|
|
678
|
+
});
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Turn an async submitHandler into a synchronous one.
|
|
683
|
+
*
|
|
684
|
+
* Reasoning:
|
|
685
|
+
* - we're often talking to the API during form submission. The onSubmit handler will be returning a Promise in this case.
|
|
686
|
+
* - the `form` element, per definition, always expects a synchronous function.
|
|
687
|
+
* - creating on inline function in every form is a bit clumsy, and does not look clean.
|
|
688
|
+
*
|
|
689
|
+
* Then/catch cases should be properly handled by the submit handler itself,
|
|
690
|
+
* the catch supplied here is only a last safety net against an "unhandled promise rejection" error.
|
|
691
|
+
*/
|
|
692
|
+
const syncSubmit = asyncSubmitHandler => {
|
|
693
|
+
return e => {
|
|
694
|
+
asyncSubmitHandler(e).catch(rethrow);
|
|
695
|
+
};
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Wrapper around react-hook-form's useForm.
|
|
700
|
+
*
|
|
701
|
+
* Should behave like the original function, only exposing a "fieldName"
|
|
702
|
+
* function on top which is used to validate field names.
|
|
703
|
+
*/
|
|
704
|
+
const useHookForm = props => {
|
|
705
|
+
const form = useForm(props);
|
|
706
|
+
return {
|
|
707
|
+
...form,
|
|
708
|
+
fieldName: identity
|
|
709
|
+
};
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Wrapper around react-hook-form's useFormContext.
|
|
714
|
+
*
|
|
715
|
+
* Should behave like the original function, only exposing a "fieldName"
|
|
716
|
+
* function on top which is used to validate field names.
|
|
717
|
+
*/
|
|
718
|
+
const useHookFormContext = () => {
|
|
719
|
+
const form = useFormContext();
|
|
720
|
+
return {
|
|
721
|
+
...form,
|
|
722
|
+
fieldName: identity
|
|
723
|
+
};
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
export { HookFormProvider, InputAutocomplete, InputCheckbox, InputDateTimePicker, InputEmail, InputPasswordSet, InputRadio, InputSelect, InputSelectFromEnum, InputText, LoadingButton, getFormError, loadingButtonStateFromFormState, loadingButtonStateFromReactQueryMutationStatus, syncSubmit, useHookForm, useHookFormContext, useLoadingButtonStateFromFormContext, useMergedFormField, useSortedOptionsWithGroup, useTranslateString };
|
|
727
|
+
//# sourceMappingURL=packageExports.js.map
|