@axinom/mosaic-ui 0.39.0-rc.3 → 0.39.1-feat-gs.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.
Files changed (70) hide show
  1. package/dist/components/FormStation/Create/Create.d.ts +1 -1
  2. package/dist/components/FormStation/Create/Create.d.ts.map +1 -1
  3. package/dist/components/FormStation/Details/Details.d.ts +2 -2
  4. package/dist/components/FormStation/Details/Details.d.ts.map +1 -1
  5. package/dist/components/FormStation/FormContentWrapper/FormContentWrapper.d.ts +11 -0
  6. package/dist/components/FormStation/FormContentWrapper/FormContentWrapper.d.ts.map +1 -0
  7. package/dist/components/FormStation/FormStation.d.ts +8 -15
  8. package/dist/components/FormStation/FormStation.d.ts.map +1 -1
  9. package/dist/components/FormStation/FormStation.models.d.ts +17 -3
  10. package/dist/components/FormStation/FormStation.models.d.ts.map +1 -1
  11. package/dist/components/FormStation/FormStationActions/FormStationActions.d.ts +21 -0
  12. package/dist/components/FormStation/FormStationActions/FormStationActions.d.ts.map +1 -0
  13. package/dist/components/FormStation/FormStationContext/FormStationContext.d.ts +13 -0
  14. package/dist/components/FormStation/FormStationContext/FormStationContext.d.ts.map +1 -0
  15. package/dist/components/FormStation/FormStationContext/FormStationContextProvider.d.ts +10 -0
  16. package/dist/components/FormStation/FormStationContext/FormStationContextProvider.d.ts.map +1 -0
  17. package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts +12 -0
  18. package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -0
  19. package/dist/components/FormStation/helpers/mergeData.d.ts +7 -0
  20. package/dist/components/FormStation/helpers/mergeData.d.ts.map +1 -0
  21. package/dist/components/FormStation/helpers/useChangeSets.d.ts +12 -0
  22. package/dist/components/FormStation/helpers/useChangeSets.d.ts.map +1 -0
  23. package/dist/components/FormStation/helpers/useDataProvider.d.ts +14 -0
  24. package/dist/components/FormStation/helpers/useDataProvider.d.ts.map +1 -0
  25. package/dist/components/FormStation/helpers/useDebouncedFormikValues.d.ts +7 -0
  26. package/dist/components/FormStation/helpers/useDebouncedFormikValues.d.ts.map +1 -0
  27. package/dist/components/FormStation/helpers/useUndo.d.ts +6 -0
  28. package/dist/components/FormStation/helpers/useUndo.d.ts.map +1 -0
  29. package/dist/components/FormStation/{useValidationError.d.ts → helpers/useValidationError.d.ts} +1 -1
  30. package/dist/components/FormStation/helpers/useValidationError.d.ts.map +1 -0
  31. package/dist/components/FormStation/index.d.ts +1 -1
  32. package/dist/components/FormStation/index.d.ts.map +1 -1
  33. package/dist/components/Utils/Postgraphile/getArrayDiff.d.ts.map +1 -1
  34. package/dist/components/Utils/Postgraphile/getFormDiff.d.ts.map +1 -1
  35. package/dist/helpers/testing.d.ts +4 -1
  36. package/dist/helpers/testing.d.ts.map +1 -1
  37. package/dist/index.es.js +4 -4
  38. package/dist/index.es.js.map +1 -1
  39. package/dist/index.js +4 -4
  40. package/dist/index.js.map +1 -1
  41. package/package.json +3 -4
  42. package/src/components/Actions/Action/Action.scss +1 -0
  43. package/src/components/FormElements/Tags/Tags.tsx +3 -3
  44. package/src/components/FormStation/Create/Create.stories.tsx +1 -9
  45. package/src/components/FormStation/Create/Create.tsx +4 -1
  46. package/src/components/FormStation/Details/Details.tsx +5 -2
  47. package/src/components/FormStation/FormContentWrapper/FormContentWrapper.scss +66 -0
  48. package/src/components/FormStation/FormContentWrapper/FormContentWrapper.tsx +77 -0
  49. package/src/components/FormStation/FormStation.models.ts +29 -3
  50. package/src/components/FormStation/FormStation.scss +0 -70
  51. package/src/components/FormStation/FormStation.spec.tsx +2 -1
  52. package/src/components/FormStation/FormStation.stories.tsx +20 -1
  53. package/src/components/FormStation/FormStation.tsx +68 -403
  54. package/src/components/FormStation/FormStationActions/FormStationActions.tsx +132 -0
  55. package/src/components/FormStation/FormStationContext/FormStationContext.ts +22 -0
  56. package/src/components/FormStation/FormStationContext/FormStationContextProvider.tsx +86 -0
  57. package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +85 -0
  58. package/src/components/FormStation/helpers/mergeData.ts +26 -0
  59. package/src/components/FormStation/helpers/useChangeSets.ts +70 -0
  60. package/src/components/FormStation/helpers/useDataProvider.ts +169 -0
  61. package/src/components/FormStation/helpers/useDebouncedFormikValues.ts +22 -0
  62. package/src/components/FormStation/helpers/useUndo.ts +43 -0
  63. package/src/components/FormStation/{useValidationError.tsx → helpers/useValidationError.tsx} +1 -1
  64. package/src/components/FormStation/index.ts +1 -5
  65. package/src/components/Utils/Postgraphile/getArrayDiff.ts +7 -6
  66. package/src/components/Utils/Postgraphile/getFormDiff.ts +2 -1
  67. package/dist/components/FormStation/StationErrorStateType.d.ts +0 -5
  68. package/dist/components/FormStation/StationErrorStateType.d.ts.map +0 -1
  69. package/dist/components/FormStation/useValidationError.d.ts.map +0 -1
  70. package/src/components/FormStation/StationErrorStateType.tsx +0 -5
@@ -1,51 +1,23 @@
1
1
  import clsx from 'clsx';
2
- import {
3
- Form,
4
- Formik,
5
- FormikHelpers,
6
- FormikValues,
7
- useFormikContext,
8
- } from 'formik';
9
- import React, {
10
- PropsWithChildren,
11
- useCallback,
12
- useEffect,
13
- useMemo,
14
- useRef,
15
- useState,
16
- } from 'react';
17
- import { useHistory } from 'react-router-dom';
2
+ import { Formik, FormikValues } from 'formik';
3
+ import React, { PropsWithChildren } from 'react';
18
4
  import { OptionalObjectSchema } from 'yup/lib/object';
19
- import { SaveIndicatorType, setSaveIndicator } from '../../initialize';
20
- import { Data } from '../../types/data';
21
- import { ErrorTypeToStationError } from '../../utils/ErrorTypeToStationError';
22
- import { Actions, ActionsProps } from '../Actions';
23
- import { isNavigationAction } from '../Actions/Action/Action';
24
- import { IconName } from '../Icons';
25
- import { MessageBar } from '../MessageBar';
26
- import {
27
- PageHeader,
28
- PageHeaderActionType,
29
- PageHeaderProps,
30
- } from '../PageHeader';
31
- import { ErrorType, StationMessage } from '../models';
32
- import { FormActionData, InitialFormData } from './FormStation.models';
5
+ import type { Data } from '../../types/data';
6
+ import type { StationMessage } from '../models';
7
+ import { FormContentWrapper } from './FormContentWrapper/FormContentWrapper';
8
+ import type {
9
+ FormActionData,
10
+ InitialFormData,
11
+ ObjectSchemaDefinition,
12
+ SaveDataFunction,
13
+ } from './FormStation.models';
33
14
  import classes from './FormStation.scss';
15
+ import { FormStationActions } from './FormStationActions/FormStationActions';
16
+ import { FormStationContextProvider } from './FormStationContext/FormStationContextProvider';
17
+ import { FormStationHeader } from './FormStationHeader/FormStationHeader';
34
18
  import { SaveOnNavigate } from './SaveOnNavigate/SaveOnNavigate';
35
- import { StationErrorStateType } from './StationErrorStateType';
36
- import { useValidationError } from './useValidationError';
37
-
38
- export type ObjectSchemaDefinition<
39
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
- T extends Data | null = any,
41
- > = {
42
- // TODO: Adding 'any' here since there are a couple of open issues regarding generics in the latest version of yup.
43
- // https://github.com/jquense/yup/issues/1159
44
- // https://github.com/jquense/yup/issues/1247
45
- // Consider revisiting once 'yup' has addressed these issues.
46
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
- [field in keyof T]: any;
48
- };
19
+ import { useDataProvider } from './helpers/useDataProvider';
20
+ import { useValidationError } from './helpers/useValidationError';
49
21
 
50
22
  export interface FormStationProps<
51
23
  TValues extends Data = FormikValues,
@@ -89,28 +61,11 @@ export interface FormStationProps<
89
61
  * Called whenever the form needs to be saved.
90
62
  * This method needs to throw an exception in case the saving did not succeed.
91
63
  */
92
- saveData: (
93
- /** The current values of the form */
94
- values: TValues,
95
- /** The initial values of the form */
96
- initialData: InitialFormData<TValues>,
97
- /** The Formik state helpers */
98
- formikHelpers: FormikHelpers<TValues>,
99
- ) => Promise<TSubmitResponse> | void;
64
+ saveData: SaveDataFunction<TValues, TSubmitResponse>;
100
65
  /** CSS Class name for additional styles */
101
66
  className?: string;
102
- }
103
-
104
- interface FormActionProps<T, Y> extends Omit<ActionsProps, 'actions'> {
105
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
106
- validationSchema?: OptionalObjectSchema<ObjectSchemaDefinition<any>>;
107
- actions?: FormActionData<T, Y>[];
108
- setStationError: (error: StationErrorStateType) => void;
109
- setValidationError: () => void;
110
- submitResponse?: React.MutableRefObject<Y | undefined>;
111
- alwaysSubmitBeforeAction?: boolean;
112
- className?: string;
113
- isFormSubmitting?: boolean;
67
+ /** Periodically calls saveData when the form values change */
68
+ autosave?: boolean;
114
69
  }
115
70
 
116
71
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -131,141 +86,30 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
131
86
  alwaysSubmitBeforeAction = false,
132
87
  stationMessage,
133
88
  className = '',
89
+ autosave = false,
134
90
  }: PropsWithChildren<
135
91
  FormStationProps<TValues, TSubmitResponse>
136
92
  >): JSX.Element => {
137
- const [stationError, setStationError] = useState<StationErrorStateType>();
93
+ const currentValuesRef = React.useRef<TValues>({} as TValues);
138
94
 
139
- const { setValidationError, validationWatcher } = useValidationError(
95
+ const {
96
+ onSubmit,
140
97
  stationError,
141
98
  setStationError,
99
+ isFormSubmitting,
100
+ lastSubmittedResponse,
101
+ initialValues,
102
+ } = useDataProvider<TValues, TSubmitResponse>(
103
+ initialData,
104
+ saveData,
105
+ currentValuesRef,
142
106
  );
143
107
 
144
- const submitResponse = useRef<TSubmitResponse>();
145
- const [isFormSubmitting, setIsFormSubmitting] = useState<boolean>(false);
146
-
147
- const onSubmit = useCallback(
148
- async (
149
- values: TValues,
150
- formikHelpers: FormikHelpers<TValues>,
151
- ): Promise<void> => {
152
- if (isFormSubmitting) {
153
- return;
154
- }
155
-
156
- try {
157
- setIsFormSubmitting(true);
158
- setSaveIndicator(SaveIndicatorType.Saving);
159
- setStationError(undefined);
160
- if (!initialData.loading && saveData) {
161
- const response = await saveData(values, initialData, formikHelpers);
162
- if (response) {
163
- submitResponse.current = response;
164
- }
165
- }
166
- } catch (error) {
167
- setStationError(
168
- ErrorTypeToStationError(
169
- error as ErrorType,
170
- 'An error occurred when trying to save data.',
171
- ),
172
- );
173
-
174
- setSaveIndicator(SaveIndicatorType.Dirty);
175
-
176
- // We still throw the error, to make sure that navigation or action execution
177
- // will not continue after a failed save.
178
- throw error;
179
- } finally {
180
- formikHelpers.setSubmitting(false);
181
- setIsFormSubmitting(false);
182
- }
183
- },
184
- [isFormSubmitting, initialData, saveData, setStationError],
108
+ const { setValidationError, validationWatcher } = useValidationError(
109
+ stationError,
110
+ setStationError,
185
111
  );
186
112
 
187
- useEffect(() => {
188
- if (
189
- initialData.error ||
190
- (initialData.data === null && !initialData.loading) ||
191
- initialData.entityNotFound
192
- ) {
193
- const stationError = {
194
- ...ErrorTypeToStationError(
195
- initialData.error,
196
- 'An error occurred when trying to load data.',
197
- 'Entity not found',
198
- ),
199
- type: 'loading',
200
- };
201
-
202
- setStationError(stationError);
203
- } else {
204
- if (stationError?.type === 'loading') {
205
- // Only clear the error if it is a loading error, which now seems to be cleared.
206
- setStationError(undefined);
207
- }
208
- }
209
- }, [
210
- initialData.loading,
211
- initialData.error,
212
- initialData.entityNotFound,
213
- initialData.data,
214
- stationError?.type,
215
- ]);
216
-
217
- const getContent = (): JSX.Element | undefined => {
218
- if (initialData.loading) {
219
- // TODO: Loading skeleton of the page
220
- return;
221
- }
222
-
223
- if (
224
- initialData.error ||
225
- initialData.data === null ||
226
- initialData.entityNotFound
227
- ) {
228
- // Error on loading - we can't show the form
229
- return <div className={classes.loadingError}></div>;
230
- }
231
-
232
- // Loading successful
233
- return (
234
- <>
235
- <SaveOnNavigate
236
- isSubmitting={isFormSubmitting}
237
- onNavigationCancelled={setValidationError}
238
- />
239
- <div className={classes.main}>
240
- <div
241
- className={clsx(classes.formWrapper, {
242
- [classes.hasMessage]: stationMessage,
243
- })}
244
- >
245
- {stationMessage && (
246
- <MessageBar {...stationMessage}>{stationMessage.body}</MessageBar>
247
- )}
248
- <div
249
- className={clsx(classes.formContainer, {
250
- [classes.paddedContent]: !edgeToEdgeContent,
251
- })}
252
- >
253
- <Form>
254
- <>
255
- {children}
256
- {/* Adding a invisible text input here to prevent the browser from submitting on "Enter" when there is only a single text input field in the form
257
- See: https://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2 */}
258
- <input type="text" style={{ display: 'none' }} />
259
- </>
260
- </Form>
261
- </div>
262
- </div>
263
- {infoPanel}
264
- </div>
265
- </>
266
- );
267
- };
268
-
269
113
  return (
270
114
  <div
271
115
  className={clsx(
@@ -278,229 +122,50 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
278
122
  )}
279
123
  >
280
124
  <Formik
281
- initialValues={initialData.data ?? ({} as TValues)}
125
+ initialValues={initialValues ?? ({} as TValues)}
282
126
  validationSchema={validationSchema}
283
127
  onSubmit={onSubmit}
284
128
  enableReinitialize={true}
285
129
  >
286
130
  <>
287
131
  {validationWatcher}
288
- <FormStationHeader
289
- titleProperty={titleProperty as string}
290
- defaultTitle={defaultTitle}
291
- subtitle={subtitle}
292
- cancelNavigationUrl={cancelNavigationUrl}
293
- />
294
- {stationError && (
295
- <div className={classes.errorMessage}>
296
- <MessageBar
297
- type="error"
298
- title={String(stationError.title)}
299
- onClose={() => setStationError(undefined)}
300
- >
301
- {stationError?.body}
302
- </MessageBar>
303
- </div>
304
- )}
305
- <div className={classes.children}>{getContent()}</div>
306
- {alwaysShowActionsPanel || (actions?.length ?? 0) > 0 ? (
307
- <FormStationAction<TValues, TSubmitResponse>
308
- actions={actions}
309
- width={actionsWidth}
310
- validationSchema={validationSchema}
311
- setStationError={setStationError}
312
- setValidationError={setValidationError}
313
- submitResponse={submitResponse}
314
- alwaysSubmitBeforeAction={alwaysSubmitBeforeAction}
315
- className={classes.actionsPanel}
132
+ <SaveOnNavigate isSubmitting={isFormSubmitting} />
133
+ <FormStationContextProvider<TValues>
134
+ autosave={autosave}
135
+ currentValuesRef={currentValuesRef}
136
+ >
137
+ <FormStationHeader
138
+ titleProperty={titleProperty as string}
139
+ defaultTitle={defaultTitle}
140
+ subtitle={subtitle}
141
+ cancelNavigationUrl={cancelNavigationUrl}
142
+ isFormSubmitting={isFormSubmitting}
316
143
  />
317
- ) : null}
144
+ <FormContentWrapper
145
+ stationMessage={stationMessage}
146
+ edgeToEdgeContent={edgeToEdgeContent}
147
+ infoPanel={infoPanel}
148
+ initialData={initialData}
149
+ stationError={stationError}
150
+ setStationError={setStationError}
151
+ >
152
+ {children}
153
+ </FormContentWrapper>
154
+ {alwaysShowActionsPanel || (actions?.length ?? 0) > 0 ? (
155
+ <FormStationActions<TValues, TSubmitResponse>
156
+ actions={actions}
157
+ width={actionsWidth}
158
+ validationSchema={validationSchema}
159
+ setStationError={setStationError}
160
+ setValidationError={setValidationError}
161
+ submitResponse={lastSubmittedResponse}
162
+ alwaysSubmitBeforeAction={alwaysSubmitBeforeAction}
163
+ className={classes.actionsPanel}
164
+ />
165
+ ) : null}
166
+ </FormStationContextProvider>
318
167
  </>
319
168
  </Formik>
320
169
  </div>
321
170
  );
322
171
  };
323
-
324
- /**
325
- * Saves the form before the action is performed.
326
- */
327
- const FormStationAction = <T, Y>(
328
- props: PropsWithChildren<FormActionProps<T, Y>>,
329
- ): JSX.Element => {
330
- const {
331
- actions,
332
- validationSchema,
333
- setStationError,
334
- setValidationError,
335
- submitResponse,
336
- alwaysSubmitBeforeAction,
337
- className = '',
338
- } = props;
339
- const {
340
- submitForm,
341
- resetForm,
342
- values,
343
- validateForm,
344
- isValid,
345
- dirty,
346
- isSubmitting,
347
- } = useFormikContext<T>();
348
-
349
- const updatedActions = useMemo(() => {
350
- return actions?.map((action) => {
351
- const { onActionSelected } = action;
352
-
353
- return isNavigationAction(action)
354
- ? action
355
- : {
356
- ...action,
357
- isDisabled: action.isDisabled || isSubmitting,
358
- onActionSelected: () => {
359
- (async () => {
360
- //TODO: Busy indicator (disable form?)
361
- if (dirty || alwaysSubmitBeforeAction) {
362
- // We can't rely on 'isValid' alone, since that will only evaluate if the form is touched or 'submitForm' is called.
363
- // On a create station though the from might not be touched but still being invalid.
364
- // Also the validation on submitForm in here will not help, since the value of isValid is already bound to that method and will not change
365
- // anymore while this method executes - even if it changes on the context!
366
- if (
367
- !isValid ||
368
- (await validationSchema?.isValid(values)) === false
369
- ) {
370
- setValidationError();
371
- // Making sure that the fields will actually show the validation messages (they won't if the from was not touched yet - e.g. on a create station)
372
- validateForm();
373
- return;
374
- }
375
-
376
- try {
377
- await submitForm();
378
-
379
- // Committing the changed values to the form to get the current version as the new "reset" state.
380
- resetForm({ values });
381
- } catch (error) {
382
- // we will abort the action if saving is not successful
383
- // a station error is already set by the "onSubmit"
384
- return;
385
- }
386
- }
387
-
388
- try {
389
- const result =
390
- onActionSelected &&
391
- (await onActionSelected({
392
- values,
393
- submitResponse: submitResponse?.current,
394
- }));
395
- if (result !== undefined) {
396
- setStationError(
397
- ErrorTypeToStationError(
398
- result,
399
- 'An error occurred when trying to execute the action.',
400
- ),
401
- );
402
- }
403
- } catch (error) {
404
- const stationError = ErrorTypeToStationError(
405
- error as ErrorType,
406
- 'An error occurred when trying to execute the action.',
407
- );
408
- setStationError(stationError);
409
- }
410
- })();
411
- },
412
- };
413
- });
414
- }, [
415
- actions,
416
- alwaysSubmitBeforeAction,
417
- dirty,
418
- isSubmitting,
419
- isValid,
420
- resetForm,
421
- setStationError,
422
- setValidationError,
423
- submitForm,
424
- submitResponse,
425
- validateForm,
426
- validationSchema,
427
- values,
428
- ]);
429
-
430
- return (
431
- <div className={className}>
432
- <Actions actions={updatedActions} />
433
- </div>
434
- );
435
- };
436
-
437
- /**
438
- * Handles showRefresh and cancel buttons based on form states
439
- */
440
- const FormStationHeader: React.FC<
441
- Omit<PageHeaderProps, 'title'> & {
442
- titleProperty?: string;
443
- defaultTitle?: string;
444
- cancelNavigationUrl?: string;
445
- }
446
- > = ({ titleProperty, defaultTitle, subtitle, cancelNavigationUrl }) => {
447
- const { dirty, resetForm, values } = useFormikContext<FormikValues>();
448
-
449
- useEffect(() => {
450
- // Set the save indicator to dirty depending on the form state
451
- if (dirty) {
452
- setSaveIndicator(SaveIndicatorType.Dirty);
453
- } else {
454
- setSaveIndicator(SaveIndicatorType.Inactive);
455
- }
456
- return () => {
457
- // The form is not always considered "not dirty" after the save
458
- // so this code will make sure that the indicator is set to inactive
459
- // when the station is left.
460
- setSaveIndicator(SaveIndicatorType.Inactive);
461
- };
462
- }, [dirty]);
463
-
464
- const history = useHistory();
465
-
466
- const title =
467
- titleProperty && values[titleProperty] !== ''
468
- ? values[titleProperty]
469
- : defaultTitle ?? '';
470
-
471
- return (
472
- <PageHeader
473
- title={title}
474
- subtitle={subtitle}
475
- actions={[
476
- ...(dirty === true // add undo action if form as been altered
477
- ? [
478
- {
479
- label: 'Undo Changes',
480
- icon: IconName.Undo,
481
- actionType: PageHeaderActionType.Context,
482
- onClick: () => {
483
- resetForm();
484
- },
485
- },
486
- ]
487
- : []),
488
- ...(cancelNavigationUrl // add cancel action if applicable
489
- ? [
490
- {
491
- label: 'Cancel',
492
- icon: IconName.X,
493
- onClick: () => {
494
- resetForm();
495
- // If the form has errors, Navigation needs to be wrapped in a promise or timeout.
496
- Promise.resolve().then(() =>
497
- history.push(cancelNavigationUrl),
498
- );
499
- },
500
- },
501
- ]
502
- : []),
503
- ]}
504
- />
505
- );
506
- };
@@ -0,0 +1,132 @@
1
+ import { useFormikContext } from 'formik';
2
+ import React, { PropsWithChildren, useMemo } from 'react';
3
+ import { OptionalObjectSchema } from 'yup/lib/object';
4
+ import { ErrorTypeToStationError } from '../../../utils/ErrorTypeToStationError';
5
+ import { Actions, ActionsProps } from '../../Actions';
6
+ import { isNavigationAction } from '../../Actions/Action/Action';
7
+ import { ErrorType, StationError } from '../../models';
8
+ import { FormActionData, ObjectSchemaDefinition } from '../FormStation.models';
9
+
10
+ interface FormActionProps<T, Y> extends Omit<ActionsProps, 'actions'> {
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ validationSchema?: OptionalObjectSchema<ObjectSchemaDefinition<any>>;
13
+ actions?: FormActionData<T, Y>[];
14
+ setStationError: (error: StationError) => void;
15
+ setValidationError: () => void;
16
+ submitResponse?: React.MutableRefObject<Y | undefined>;
17
+ alwaysSubmitBeforeAction?: boolean;
18
+ className?: string;
19
+ isFormSubmitting?: boolean;
20
+ }
21
+
22
+ /**
23
+ * Saves the form before the action is performed.
24
+ */
25
+ export const FormStationActions = <T, Y>(
26
+ props: PropsWithChildren<FormActionProps<T, Y>>,
27
+ ): JSX.Element => {
28
+ const {
29
+ actions,
30
+ validationSchema,
31
+ setStationError,
32
+ submitResponse,
33
+ alwaysSubmitBeforeAction,
34
+ className = '',
35
+ } = props;
36
+ const {
37
+ submitForm,
38
+ resetForm,
39
+ values,
40
+ validateForm,
41
+ isValid,
42
+ dirty,
43
+ isSubmitting,
44
+ } = useFormikContext<T>();
45
+
46
+ const updatedActions = useMemo(() => {
47
+ return actions?.map((action) => {
48
+ const { onActionSelected } = action;
49
+
50
+ return isNavigationAction(action)
51
+ ? action
52
+ : {
53
+ ...action,
54
+ isDisabled: action.isDisabled || isSubmitting,
55
+ onActionSelected: () => {
56
+ (async () => {
57
+ //TODO: Busy indicator (disable form?)
58
+ if (dirty || alwaysSubmitBeforeAction) {
59
+ // We can't rely on 'isValid' alone, since that will only evaluate if the form is touched or 'submitForm' is called.
60
+ // On a create station though the from might not be touched but still being invalid.
61
+ // Also the validation on submitForm in here will not help, since the value of isValid is already bound to that method and will not change
62
+ // anymore while this method executes - even if it changes on the context!
63
+ if (
64
+ !isValid ||
65
+ (await validationSchema?.isValid(values)) === false
66
+ ) {
67
+ // eslint-disable-next-line no-console
68
+ console.log('form invalid, action not performed');
69
+ // Making sure that the fields will actually show the validation messages (they won't if the from was not touched yet - e.g. on a create station)
70
+ validateForm();
71
+ return;
72
+ }
73
+
74
+ try {
75
+ await submitForm();
76
+
77
+ // Committing the changed values to the form to get the current version as the new "reset" state.
78
+ resetForm({ values });
79
+ } catch (error) {
80
+ // we will abort the action if saving is not successful
81
+ // a station error is already set by the "onSubmit"
82
+ return;
83
+ }
84
+ }
85
+
86
+ try {
87
+ const result =
88
+ onActionSelected &&
89
+ (await onActionSelected({
90
+ values,
91
+ submitResponse: submitResponse?.current,
92
+ }));
93
+ if (result !== undefined) {
94
+ setStationError(
95
+ ErrorTypeToStationError(
96
+ result,
97
+ 'An error occurred when trying to execute the action.',
98
+ ),
99
+ );
100
+ }
101
+ } catch (error) {
102
+ const stationError = ErrorTypeToStationError(
103
+ error as ErrorType,
104
+ 'An error occurred when trying to execute the action.',
105
+ );
106
+ setStationError(stationError);
107
+ }
108
+ })();
109
+ },
110
+ };
111
+ });
112
+ }, [
113
+ actions,
114
+ alwaysSubmitBeforeAction,
115
+ dirty,
116
+ isSubmitting,
117
+ isValid,
118
+ resetForm,
119
+ setStationError,
120
+ submitForm,
121
+ submitResponse,
122
+ validateForm,
123
+ validationSchema,
124
+ values,
125
+ ]);
126
+
127
+ return (
128
+ <div className={className}>
129
+ <Actions actions={updatedActions} />
130
+ </div>
131
+ );
132
+ };
@@ -0,0 +1,22 @@
1
+ import type { FormikValues } from 'formik';
2
+ import React from 'react';
3
+ import { noop } from '../../../helpers/utils';
4
+ import { ChangeSet } from '../helpers/useChangeSets';
5
+
6
+ export interface FormStationContextType {
7
+ changeSets: ChangeSet<FormikValues>[];
8
+ pushChangeSet: (value: never) => void;
9
+ popChangeSet: () => ChangeSet<FormikValues> | undefined;
10
+ clearChangeSets: () => FormikValues;
11
+ lastUndoneValue: React.MutableRefObject<FormikValues>;
12
+ autosave: boolean;
13
+ }
14
+
15
+ export const FormStationContext = React.createContext<FormStationContextType>({
16
+ changeSets: [],
17
+ pushChangeSet: noop,
18
+ popChangeSet: () => ({ prev: {}, next: {} }),
19
+ clearChangeSets: () => ({}),
20
+ lastUndoneValue: { current: {} },
21
+ autosave: false,
22
+ });