@onehat/ui 0.4.107 → 0.4.109

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.
@@ -1,1019 +0,0 @@
1
- import React, { useEffect, useState, useRef, isValidElement, } from 'react';
2
- import {
3
- Box,
4
- HStack,
5
- Icon,
6
- ScrollView,
7
- Text,
8
- TextNative,
9
- VStack,
10
- VStackNative,
11
- } from '@project-components/Gluestack';
12
- import clsx from 'clsx';
13
- import { View, } from 'react-native';
14
- import {
15
- EDITOR_TYPE__INLINE,
16
- EDITOR_TYPE__WINDOWED,
17
- EDITOR_TYPE__SIDE,
18
- EDITOR_TYPE__SMART,
19
- EDITOR_TYPE__PLAIN,
20
- EDITOR_MODE__VIEW,
21
- EDITOR_MODE__ADD,
22
- EDITOR_MODE__EDIT,
23
- } from '../../../Constants/Editor.js';
24
- import { Form, Formik, Field } from "formik"; // https://formik.org/docs/overview
25
- import { useForm, Controller } from 'react-hook-form'; // https://react-hook-form.com/api/
26
- import * as yup from 'yup'; // https://github.com/jquense/yup#string
27
- import { yupResolver } from '@hookform/resolvers/yup';
28
- import useForceUpdate from '../../../Hooks/useForceUpdate.js';
29
- import UiGlobals from '../../../UiGlobals.js';
30
- import withAlert from '../../../Hoc/withAlert.js';
31
- import withComponent from '../../../Hoc/withComponent.js';
32
- import withEditor from '../../../Hoc/withEditor.js';
33
- import withPdfButton from '../../../Hoc/withPdfButton.js';
34
- import inArray from '../../../Functions/inArray.js';
35
- import getComponentFromType from '../../../Functions/getComponentFromType.js';
36
- import buildAdditionalButtons from '../../../Functions/buildAdditionalButtons.js';
37
- import Button from '../../../Buttons/Button.js';
38
- import IconButton from '../../../Buttons/IconButton.js';
39
- import AngleLeft from '../../../Icons/AngleLeft.js';
40
- import Eye from '../../../Icons/Eye.js';
41
- import Rotate from '../../../Icons/Rotate.js';
42
- import Pencil from '../../../Icons/Pencil.js';
43
- import Footer from '../../../Layout/Footer.js';
44
- import Label from '../../../Form/Label.js';
45
- import _ from 'lodash';
46
-
47
- // TODO: memoize field Components
48
-
49
- // Modes:
50
- // EDITOR_TYPE__INLINE
51
- // Form is a single scrollable row, based on columnsConfig and Repository
52
- //
53
- // EDITOR_TYPE__WINDOWED
54
- // EDITOR_TYPE__SIDE
55
- // Form is a popup or side window, used for editing items in a grid. Integrated with Repository
56
- //
57
- // EDITOR_TYPE__SMART
58
- // Form is a standalone editor
59
- //
60
- // EDITOR_TYPE__PLAIN
61
- // Form is embedded on screen in some other way. Mainly use startingValues, items, validator
62
-
63
- function FormikForm(props) {
64
- const
65
- {
66
- editorType = EDITOR_TYPE__WINDOWED, // EDITOR_TYPE__INLINE | EDITOR_TYPE__WINDOWED | EDITOR_TYPE__SIDE | EDITOR_TYPE__SMART | EDITOR_TYPE__PLAIN
67
- startingValues = {},
68
- items = [], // Columns, FieldSets, Fields, etc to define the form
69
- ancillaryItems = [], // additional items which are not controllable form elements, but should appear in the form
70
- columnDefaults = {}, // defaults for each Column defined in items (above)
71
- columnsConfig, // Which columns are shown in Grid, so the inline editor can match. Used only for EDITOR_TYPE__INLINE
72
- validator, // custom validator, mainly for EDITOR_TYPE__PLAIN
73
- footerProps = {},
74
- buttonGroupProps = {}, // buttons in footer
75
- checkIsEditingDisabled = true,
76
- disableLabels = false,
77
- disableDirtyIcon = false,
78
- onBack,
79
- onReset,
80
- onInit,
81
- onViewMode,
82
- submitBtnLabel,
83
- onSubmit,
84
- formSetup, // this fn will be executed after the form setup is complete
85
- additionalEditButtons,
86
- useAdditionalEditButtons = true,
87
- additionalFooterButtons,
88
-
89
- // sizing of outer container
90
- h,
91
- maxHeight,
92
- minHeight = 0,
93
- w,
94
- maxWidth,
95
- flex,
96
- onLayout, // onLayout handler for main view
97
-
98
- // withComponent
99
- self,
100
-
101
- // withData
102
- Repository,
103
-
104
- // withEditor
105
- isEditorViewOnly = false,
106
- isSaving = false,
107
- editorMode,
108
- onCancel,
109
- onSave,
110
- onClose,
111
- onDelete,
112
- editorStateRef,
113
- disableView,
114
-
115
- // parent container
116
- selectorId,
117
- selectorSelected,
118
-
119
- // withAlert
120
- alert,
121
- } = props,
122
- formRef = useRef(),
123
- styles = UiGlobals.styles,
124
- record = props.record?.length === 1 ? props.record[0] : props.record;
125
- let skipAll = false;
126
- if (record?.isDestroyed) {
127
- skipAll = true; // if record is destroyed, skip render, but allow hooks to still be called
128
- if (self?.parent?.parent?.setIsEditorShown) {
129
- self.parent.parent.setIsEditorShown(false); // close the editor
130
- }
131
- }
132
- const
133
- isMultiple = _.isArray(record),
134
- isSingle = !isMultiple, // for convenience
135
- isPhantom = !skipAll && !!record?.isPhantom, //
136
- forceUpdate = useForceUpdate(),
137
- [previousRecord, setPreviousRecord] = useState(record),
138
- [containerWidth, setContainerWidth] = useState(),
139
- initialValues = _.merge(startingValues, (record && !record.isDestroyed ? record.submitValues : {})),
140
- defaultValues = isMultiple ? getNullFieldValues(initialValues, Repository) : initialValues, // when multiple entities, set all default values to null
141
- validatorToUse = validator || (isMultiple ? disableRequiredYupFields(Repository?.schema?.model?.validator) : Repository?.schema?.model?.validator) || yup.object(),
142
- {
143
- control,
144
- formState,
145
- handleSubmit,
146
- // register,
147
- // unregister,
148
- reset,
149
- // watch,
150
- // resetField,
151
- // setError,
152
- // clearErrors,
153
- setValue: formSetValue,
154
- // setFocus,
155
- getValues: formGetValues,
156
- // getFieldState,
157
- trigger,
158
- } = useForm({
159
- mode: 'onChange', // onChange | onBlur | onSubmit | onTouched | all
160
- // reValidateMode: 'onChange', // onChange | onBlur | onSubmit
161
- defaultValues,
162
- // values: defaultValues,
163
- // resetOptions: {
164
- // keepDirtyValues: false, // user-interacted input will be retained
165
- // keepErrors: false, // input errors will be retained with value update
166
- // },
167
- // criteriaMode: 'firstError', // firstError | all
168
- // shouldFocusError: false,
169
- // delayError: 0,
170
- // shouldUnregister: false,
171
- // shouldUseNativeValidation: false,
172
- resolver: yupResolver(validatorToUse),
173
- context: { isPhantom },
174
- }),
175
- buildFromColumnsConfig = () => {
176
- // For InlineEditor
177
- // Build the fields that match the current columnsConfig in the grid
178
- const
179
- model = Repository.getSchema().model,
180
- elements = [],
181
- columnProps = {
182
- justifyContent: 'center',
183
- alignItems: 'center',
184
- borderRightWidth: 1,
185
- borderRightColor: 'grey-200',
186
- px: 1,
187
- };
188
-
189
- if (editorType === EDITOR_TYPE__INLINE) {
190
- columnProps.minWidth = styles.INLINE_EDITOR_MIN_WIDTH;
191
- }
192
-
193
- _.each(columnsConfig, (config, ix) => {
194
- let {
195
- fieldName,
196
- isEditable,
197
- editor,
198
- renderer,
199
- w,
200
- flex,
201
- useSelectorId = false,
202
- } = config;
203
-
204
- if (!isEditable) {
205
- let renderedValue = renderer ? renderer(record) : record[fieldName];
206
- if (_.isBoolean(renderedValue)) {
207
- renderedValue = renderedValue.toString();
208
- }
209
- renderedValue += "\n(not editable)";
210
- elements.push(<Box key={ix} {...columnProps} className={` flex-${flex} w-${w} `}>
211
- <TextNative numberOfLines={1} ellipsizeMode="head">{renderedValue}</TextNative>
212
- </Box>);
213
- } else {
214
- elements.push(<Controller
215
- key={'controller-' + ix}
216
- name={fieldName}
217
- // rules={rules}
218
- control={control}
219
- render={(args) => {
220
- const {
221
- field,
222
- fieldState,
223
- // formState,
224
- } = args,
225
- {
226
- onChange,
227
- onBlur,
228
- name,
229
- value,
230
- // ref,
231
- } = field,
232
- {
233
- isTouched,
234
- isDirty,
235
- error,
236
- } = fieldState;
237
- let _editor = {};
238
- if (!editor) {
239
- const propertyDef = fieldName && Repository?.getSchema().getPropertyDefinition(fieldName);
240
- editor = propertyDef && propertyDef[fieldName].editorType;
241
- if (_.isPlainObject(editor)) {
242
- const {
243
- type,
244
- onChange: onEditorChange,
245
- ...p
246
- } = editor;
247
- _editor = p;
248
- editor = type;
249
- }
250
- }
251
- const Element = getComponentFromType(editor);
252
-
253
- if (useSelectorId) {
254
- _editor.selectorId = selectorId;
255
- _editor.selectorSelected = _editor;
256
- }
257
-
258
- let element = <Element
259
- name={name}
260
- value={value}
261
- setValue={(newValue) => {
262
- onChange(newValue);
263
- if (onEditorChange) {
264
- onEditorChange(newValue, formSetValue, formGetValues, formState);
265
- }
266
- }}
267
- onBlur={onBlur}
268
- flex={1}
269
- {..._editor}
270
- parent={self}
271
- reference={fieldName}
272
- // {...defaults}
273
- // {...propsToPass}
274
- />;
275
-
276
- // element = <Tooltip key={ix} label={header} placement="bottom">
277
- // {element}
278
- // </Tooltip>;
279
- // if (error) {
280
- // element = <VStack className="pt-1 flex-1">
281
- // {element}
282
- // <Text color="#f00">{error.message}</Text>
283
- // </VStack>;
284
- // }
285
-
286
- const dirtyIcon = isDirty && !disableDirtyIcon ? <Icon
287
- as={Pencil}
288
- size="2xs"
289
- className="text-grey-300 absolute top-[2px] left-[2px]"
290
- /> : null;
291
- return <HStack
292
- key={ix}
293
- {...columnProps}
294
- className={` flex-${flex} w-${w} ${error ? "bg-[#fdd]" : "bg-white"} `}
295
- >{dirtyIcon}{element}</HStack>;
296
- }}
297
- />);
298
- }
299
- });
300
- return <HStack>{elements}</HStack>;
301
- },
302
- buildFromItems = () => {
303
- return _.map(items, (item, ix) => buildFromItem(item, ix, columnDefaults));
304
- },
305
- buildFromItem = (item, ix, defaults) => {
306
- if (!item) {
307
- return null;
308
- }
309
- if (React.isValidElement(item)) {
310
- return item;
311
- }
312
- let {
313
- type,
314
- title,
315
- name,
316
- isEditable = true,
317
- label,
318
- items,
319
- onChange: onEditorChange,
320
- useSelectorId = false,
321
- isHidden = false,
322
- getDynamicProps,
323
- getIsRequired,
324
- ...propsToPass
325
- } = item,
326
- editorTypeProps = {};
327
-
328
- if (isHidden) {
329
- return null;
330
- }
331
- if (type === 'DisplayField') {
332
- isEditable = false;
333
- }
334
- const propertyDef = name && Repository?.getSchema().getPropertyDefinition(name);
335
- if (!useAdditionalEditButtons) {
336
- item = _.omit(item, 'additionalEditButtons');
337
- }
338
- if (propertyDef?.isEditingDisabled && checkIsEditingDisabled) {
339
- isEditable = false;
340
- }
341
- if (!type) {
342
- if (isEditable) {
343
- const
344
- {
345
- type: t,
346
- ...p
347
- } = propertyDef?.editorType;
348
- type = t;
349
- editorTypeProps = p;
350
- } else if (propertyDef?.viewerType) {
351
- const
352
- {
353
- type: t,
354
- ...p
355
- } = propertyDef?.viewerType;
356
- type = t;
357
- } else {
358
- type = 'Text';
359
- }
360
- }
361
- const isCombo = type?.match && type.match(/Combo/);
362
- if (item.hasOwnProperty('autoLoad')) {
363
- editorTypeProps.autoLoad = item.autoLoad;
364
- } else {
365
- if (isCombo && Repository?.isRemote && !Repository?.isLoaded) {
366
- editorTypeProps.autoLoad = true;
367
- }
368
- }
369
- if (isCombo) {
370
- // editorTypeProps.showEyeButton = true;
371
- if (_.isNil(propsToPass.showXButton)) {
372
- editorTypeProps.showXButton = true;
373
- }
374
- }
375
- const Element = getComponentFromType(type);
376
- let children;
377
-
378
- if (inArray(type, ['Column', 'Row', 'FieldSet'])) {
379
- if (_.isEmpty(items)) {
380
- return null;
381
- }
382
- if (type === 'Column') {
383
- if (containerWidth < styles.FORM_ONE_COLUMN_THRESHOLD) {
384
- // everything is in one column
385
- if (propsToPass.hasOwnProperty('flex')) {
386
- delete propsToPass.flex;
387
- }
388
- if (propsToPass.hasOwnProperty('width')) {
389
- delete propsToPass.width;
390
- }
391
- if (propsToPass.hasOwnProperty('w')) {
392
- delete propsToPass.w;
393
- }
394
- propsToPass.w = '100%';
395
- propsToPass.mb = 1;
396
- }
397
- propsToPass.pl = 3;
398
- }
399
- if (type === 'Row') {
400
- propsToPass.w = '100%';
401
- }
402
- const itemDefaults = item.defaults;
403
- children = _.map(items, (item, ix) => {
404
- return buildFromItem(item, ix, itemDefaults);
405
- });
406
- return <Element key={ix} title={title} {...itemDefaults} {...propsToPass} {...editorTypeProps}>{children}</Element>;
407
- }
408
-
409
- if (!label && Repository && propertyDef?.title) {
410
- label = propertyDef.title;
411
- }
412
-
413
- if (isEditorViewOnly || !isEditable) {
414
- let value = null;
415
- if (record?.properties && record.properties[name]) {
416
- value = record.properties[name].displayValue;
417
- }
418
- if (_.isNil(value) && record && record[name]) {
419
- value = record[name];
420
- }
421
- if (_.isNil(value) && startingValues && startingValues[name]) {
422
- value = startingValues[name];
423
- }
424
-
425
- let element = <Element
426
- value={value}
427
- parent={self}
428
- reference={name}
429
- {...propsToPass}
430
- />;
431
- if (!disableLabels && label) {
432
- const labelProps = {};
433
- if (defaults?.labelWidth) {
434
- labelProps.w = defaults.labelWidth;
435
- }
436
- if (containerWidth > styles.FORM_STACK_ROW_THRESHOLD) {
437
- element = <><Label {...labelProps}>{label}</Label>{element}</>;
438
- } else {
439
- element = <VStack><Label {...labelProps}>{label}</Label>{element}</VStack>;
440
- }
441
- }
442
- return <HStack key={ix} className="px-2 pb-1">{element}</HStack>;
443
- }
444
-
445
-
446
-
447
- // // These rules are for fields *outside* the model
448
- // // but which want validation on the form anyway.
449
- // // The useForm() resolver disables this
450
- // const
451
- // rules = {},
452
- // rulesToCheck = [
453
- // 'required',
454
- // 'min',
455
- // 'max',
456
- // 'minLength',
457
- // 'maxLength',
458
- // 'pattern',
459
- // 'validate',
460
- // ];
461
- // _.each(rulesToCheck, (rule) => {
462
- // if (item.hasOwnProperty(rule)) {
463
- // rules[rule] = item[rule];
464
- // }
465
- // });
466
-
467
- return <Controller
468
- key={'controller-' + ix}
469
- name={name}
470
- // rules={rules}
471
- control={control}
472
- render={(args) => {
473
- const {
474
- field,
475
- fieldState,
476
- // formState,
477
- } = args,
478
- {
479
- onChange,
480
- onBlur,
481
- name,
482
- value,
483
- // ref,
484
- } = field,
485
- {
486
- isTouched,
487
- isDirty,
488
- error,
489
- } = fieldState;
490
- if (isValidElement(Element)) {
491
- throw new Error('Should not yet be valid React element. Did you use <Element> instead of () => <Element> when defining it?')
492
- }
493
-
494
- if (useSelectorId) { // This causes the whole form to use selectorId
495
- editorTypeProps.selectorId = selectorId;
496
- }
497
- if (propsToPass.selectorId || editorTypeProps.selectorId) { // editorTypeProps.selectorId causes just this one field to use selectorId
498
- if (_.isNil(propsToPass.selectorSelected)) {
499
- editorTypeProps.selectorSelected = record;
500
- }
501
- }
502
- let dynamicProps = {};
503
- if (getDynamicProps) {
504
- dynamicProps = getDynamicProps({ fieldState, formSetValue, formGetValues, formState });
505
- }
506
- let element = <Element
507
- name={name}
508
- value={value}
509
- onChangeValue={(newValue) => {
510
- if (newValue === undefined) {
511
- newValue = null; // React Hook Form doesn't respond well when setting value to undefined
512
- }
513
- onChange(newValue);
514
- if (onEditorChange) {
515
- onEditorChange(newValue, formSetValue, formGetValues, formState, trigger);
516
- }
517
- }}
518
- onBlur={onBlur}
519
- flex={1}
520
- parent={self}
521
- reference={name}
522
- {...defaults}
523
- {...propsToPass}
524
- {...editorTypeProps}
525
- {...dynamicProps}
526
- />;
527
- if (editorType !== EDITOR_TYPE__INLINE) {
528
- let message = null;
529
- if (error) {
530
- message = error.message;
531
- if (label && error.ref?.name) {
532
- message = message.replace(error.ref.name, label);
533
- }
534
- }
535
- if (message) {
536
- message = <Text className="text-[#f00]">{message}</Text>;
537
- }
538
- element = <VStack className="pt-1 flex-1">
539
- {element}
540
- {message}
541
- </VStack>;
542
- }
543
-
544
- if (item.additionalEditButtons) {
545
- const buttons = buildAdditionalButtons(item.additionalEditButtons, self, { fieldState, formSetValue, formGetValues, formState });
546
- if (containerWidth > styles.FORM_STACK_ROW_THRESHOLD) {
547
- element = <HStack className="flex-1 flex-wrap">
548
- {element}
549
- {buttons}
550
- </HStack>;
551
- } else {
552
- element = <VStack className="flex-1 w-full">
553
- {element}
554
- <HStack className="flex-1 w-full mt-2 flex-wrap">
555
- {buttons}
556
- </HStack>
557
- </VStack>;
558
- }
559
- }
560
-
561
- let isRequired = false,
562
- requiredIndicator = null;
563
- if (!isMultiple) { // Don't require fields if editing multiple records
564
- if (getIsRequired) {
565
- isRequired = getIsRequired(formGetValues, formState);
566
- } else if (validatorToUse?.fields && validatorToUse.fields[name]?.exclusiveTests?.required) {
567
- // submitted validator
568
- isRequired = true;
569
- } else if ((propertyDef?.validator?.spec && !propertyDef.validator.spec.optional) ||
570
- (propertyDef?.requiredIfPhantom && isPhantom) ||
571
- (propertyDef?.requiredIfNotPhantom && !isPhantom)) {
572
- // property definition
573
- isRequired = true;
574
- }
575
- if (isRequired) {
576
- requiredIndicator = <Text className="text-[#f00] text-[30px] pr-1">*</Text>;
577
- }
578
- }
579
- if (!disableLabels && label && editorType !== EDITOR_TYPE__INLINE) {
580
- const labelProps = {};
581
- if (defaults?.labelWidth) {
582
- labelProps.w = defaults.labelWidth;
583
- }
584
- if (containerWidth > styles.FORM_STACK_ROW_THRESHOLD) {
585
- element = <HStack className="w-full py-1">
586
- <Label {...labelProps}>{requiredIndicator}{label}</Label>
587
- {element}
588
- </HStack>;
589
- } else {
590
- element = <VStack className="w-full py-1 mt-3">
591
- <Label {...labelProps}>{requiredIndicator}{label}</Label>
592
- {element}
593
- </VStack>;
594
- }
595
- } else if (disableLabels && requiredIndicator) {
596
- element = <HStack className="w-full py-1">
597
- {requiredIndicator}
598
- {element}
599
- </HStack>;
600
- }
601
-
602
- const dirtyIcon = isDirty && !disableDirtyIcon ? <Icon
603
- as={Pencil}
604
- size="2xs"
605
- className="text-grey-300 absolute top-[2px] left-[2px]" /> : null;
606
- return (
607
- <HStack
608
- key={ix}
609
- className={` ${error ? "bg-[#fdd]" : "bg-[null]"} px-2 pb-1 `}>{dirtyIcon}{element}</HStack>
610
- );
611
- }}
612
- />;
613
- },
614
- buildAncillary = () => {
615
- const components = [];
616
- if (ancillaryItems.length) {
617
- _.each(ancillaryItems, (item, ix) => {
618
- let {
619
- type,
620
- title = null,
621
- description = null,
622
- selectorId,
623
- ...propsToPass
624
- } = item;
625
- if (isMultiple && type !== 'Attachments') {
626
- return;
627
- }
628
- if (!propsToPass.h) {
629
- propsToPass.h = 400;
630
- }
631
- const
632
- Element = getComponentFromType(type),
633
- element = <Element
634
- selectorId={selectorId}
635
- selectorSelected={selectorSelected || record}
636
- flex={1}
637
- uniqueRepository={true}
638
- parent={self}
639
- {...propsToPass}
640
- />;
641
- if (title) {
642
- if (record?.displayValue) {
643
- title += ' for ' + record.displayValue;
644
- }
645
- title = <Text
646
- className={` ${styles.FORM_ANCILLARY_TITLE_CLASSNAME} font-bold `}
647
- >{title}</Text>;
648
- }
649
- if (description) {
650
- description = <Text
651
- className={` ${styles.FORM_ANCILLARY_DESCRIPTION_CLASSNAME} italic-italic `}
652
- >{description}</Text>;
653
- }
654
- components.push(<VStack key={'ancillary-' + ix} className="mx-1 my-3">{title}{description}{element}</VStack>);
655
- });
656
- }
657
- return components;
658
- },
659
- onSubmitError = (errors, e) => {
660
- if (editorType === EDITOR_TYPE__INLINE) {
661
- alert(errors.message);
662
- }
663
- },
664
- doReset = (values) => {
665
- reset(values);
666
- if (onReset) {
667
- onReset(values, formSetValue, formGetValues);
668
- }
669
- },
670
- onSaveDecorated = async (data, e) => {
671
- // reset the form after a save
672
- const result = await onSave(data, e);
673
- if (result) {
674
- const values = record.submitValues;
675
- doReset(values);
676
- }
677
- },
678
- onSubmitDecorated = async (data, e) => {
679
- const result = await onSubmit(data, e);
680
- if (result) {
681
- const values = record.submitValues;
682
- doReset(values);
683
- }
684
- },
685
- onLayoutDecorated = (e) => {
686
- if (onLayout) {
687
- onLayout(e);
688
- }
689
-
690
- setContainerWidth(e.nativeEvent.layout.width);
691
- };
692
-
693
- useEffect(() => {
694
- if (skipAll) {
695
- return;
696
- }
697
- if (record === previousRecord) {
698
- if (onInit) {
699
- onInit(initialValues, formSetValue, formGetValues);
700
- }
701
- } else {
702
- setPreviousRecord(record);
703
- doReset(defaultValues);
704
- }
705
- if (formSetup) {
706
- formSetup(formSetValue, formGetValues, formState)
707
- }
708
- }, [record]);
709
-
710
- useEffect(() => {
711
- if (skipAll) {
712
- return;
713
- }
714
- if (!Repository) {
715
- return () => {
716
- if (!_.isNil(editorStateRef)) {
717
- editorStateRef.current = null; // clean up the editorStateRef on unmount
718
- }
719
- };
720
- }
721
-
722
- Repository.ons(['changeData', 'change'], forceUpdate);
723
-
724
- return () => {
725
- Repository.offs(['changeData', 'change'], forceUpdate);
726
- if (!_.isNil(editorStateRef)) {
727
- editorStateRef.current = null; // clean up the editorStateRef on unmount
728
- }
729
- };
730
- }, [Repository]);
731
-
732
- if (skipAll) {
733
- return null;
734
- }
735
-
736
- // if (Repository && (!record || _.isEmpty(record) || record.isDestroyed)) {
737
- // return null;
738
- // }
739
-
740
- if (!_.isNil(editorStateRef)) {
741
- editorStateRef.current = formState; // Update state so HOC can know what's going on
742
- }
743
-
744
- if (self) {
745
- self.ref = formRef;
746
- self.formState = formState;
747
- self.formSetValue = formSetValue;
748
- self.formGetValues = formGetValues;
749
- }
750
-
751
- const sizeProps = {};
752
- if (!flex && !h && !w) {
753
- sizeProps.flex = 1;
754
- } else {
755
- if (h) {
756
- sizeProps.h = h;
757
- }
758
- if (w) {
759
- sizeProps.w = w;
760
- }
761
- if (flex) {
762
- sizeProps.flex = flex;
763
- }
764
- }
765
- if (maxWidth) {
766
- sizeProps.maxWidth = maxWidth;
767
- }
768
- if (maxHeight) {
769
- sizeProps.maxHeight = maxHeight;
770
- }
771
-
772
- const formButtons = [];
773
- let formComponents,
774
- editor,
775
- additionalButtons,
776
- isSaveDisabled = false,
777
- isSubmitDisabled = false,
778
- savingProps = {},
779
-
780
- showDeleteBtn = false,
781
- showResetBtn = false,
782
- showCloseBtn = false,
783
- showCancelBtn = false,
784
- showSaveBtn = false,
785
- showSubmitBtn = false;
786
-
787
- if (containerWidth) { // we need to render this component twice in order to get the container width. Skip this on first render
788
-
789
- if (isSaving) {
790
- savingProps.borderTopWidth = 2;
791
- savingProps.borderTopColor = '#f00';
792
- }
793
-
794
- if (editorType === EDITOR_TYPE__INLINE) {
795
- editor = buildFromColumnsConfig();
796
- // } else if (editorType === EDITOR_TYPE__PLAIN) {
797
- // formComponents = buildFromItems();
798
- // const formAncillaryComponents = buildAncillary();
799
- // editor = <>
800
- // <VStack className="p-4">{formComponents}</VStack>
801
- // <VStack className="pt-4">{formAncillaryComponents}</VStack>
802
- // </>;
803
- } else {
804
- formComponents = buildFromItems();
805
- const formAncillaryComponents = buildAncillary();
806
- editor = <>
807
- {containerWidth >= styles.FORM_ONE_COLUMN_THRESHOLD ? <HStack className="p-4 pl-0">{formComponents}</HStack> : null}
808
- {containerWidth < styles.FORM_ONE_COLUMN_THRESHOLD ? <VStack className="p-4">{formComponents}</VStack> : null}
809
- <VStack className="m-2 pt-4 px-2">{formAncillaryComponents}</VStack>
810
- </>;
811
-
812
- additionalButtons = buildAdditionalButtons(additionalEditButtons);
813
-
814
- formButtons.push(<HStack key="buttonsRow" className="px-4 pt-4 items-center justify-end">
815
- {isSingle && editorMode === EDITOR_MODE__EDIT && onBack &&
816
- <Button
817
- key="backBtn"
818
- onPress={onBack}
819
- leftIcon={<Icon as={AngleLeft} size="sm" className="text-white" />}
820
- color="#fff"
821
- >Back</Button>}
822
- {isSingle && editorMode === EDITOR_MODE__EDIT && onViewMode && !disableView &&
823
- <Button
824
- key="viewBtn"
825
- onPress={onViewMode}
826
- leftIcon={<Icon as={Eye} size="sm" className="text-white" />}
827
- color="#fff"
828
- >To View</Button>}
829
- </HStack>);
830
- if (editorMode === EDITOR_MODE__EDIT && !_.isEmpty(additionalButtons)) {
831
- formButtons.push(<HStack
832
- key="additionalButtonsRow"
833
- className="p-[4px] items-center justify-end flex-wrap"
834
- >
835
- {additionalButtons}
836
- </HStack>)
837
- }
838
- }
839
-
840
- if (!formState.isValid) {
841
- isSaveDisabled = true;
842
- isSubmitDisabled = true;
843
- }
844
- if (_.isEmpty(formState.dirtyFields) && !isPhantom) {
845
- isSaveDisabled = true;
846
- }
847
-
848
- if (editorType === EDITOR_TYPE__INLINE) {
849
- buttonGroupProps.position = 'fixed';
850
- buttonGroupProps.left = 10; // TODO: I would prefer to have this be centered, but it's a lot more complex than just making it stick to the left
851
- footerProps.alignItems = 'flex-start';
852
- }
853
-
854
- if (onDelete && editorMode === EDITOR_MODE__EDIT && isSingle) {
855
- showDeleteBtn = true;
856
- }
857
- if (!isEditorViewOnly) {
858
- showResetBtn = true;
859
- }
860
- if (editorType !== EDITOR_TYPE__SIDE) { // side editor won't show either close or cancel buttons!
861
- // determine whether we should show the close or cancel button
862
- if (isEditorViewOnly) {
863
- showCloseBtn = true;
864
- } else {
865
- const formIsDirty = formState.isDirty;
866
- // console.log('formIsDirty', formIsDirty);
867
- // console.log('isPhantom', isPhantom);
868
- if (formIsDirty || isPhantom) {
869
- if (isSingle && onCancel) {
870
- showCancelBtn = true;
871
- }
872
- } else {
873
- if (onClose) {
874
- showCloseBtn = true;
875
- }
876
- }
877
- }
878
- }
879
- if (!isEditorViewOnly && onSave) {
880
- showSaveBtn = true;
881
- }
882
- if (!!onSubmit) {
883
- showSubmitBtn = true;
884
- }
885
- }
886
-
887
- return <VStackNative
888
- {...sizeProps}
889
- onLayout={onLayoutDecorated}
890
- ref={formRef}
891
- >
892
- {!!containerWidth && <>
893
- {editorType === EDITOR_TYPE__INLINE &&
894
- <ScrollView
895
- horizontal={true}
896
- className="flex-1 bg-white py-1 border-t-[3px] border-b-[5px] border-t-primary-100 border-b-primary-100">{editor}</ScrollView>}
897
- {editorType !== EDITOR_TYPE__INLINE &&
898
- <ScrollView _web={{ minHeight, }} className="w-full pb-1">
899
- {formButtons}
900
- {editor}
901
- </ScrollView>}
902
-
903
- <Footer className="justify-end" {...footerProps} {...savingProps}>
904
- {onDelete && editorMode === EDITOR_MODE__EDIT && isSingle &&
905
-
906
- <HStack className="flex-1 justify-start">
907
- <Button
908
- key="deleteBtn"
909
- onPress={onDelete}
910
- bg="warning"
911
- _hover={{
912
- bg: 'warningHover',
913
- }}
914
- color="#fff"
915
- >Delete</Button>
916
- </HStack>}
917
-
918
- {showResetBtn &&
919
- <IconButton
920
- key="resetBtn"
921
- onPress={() => doReset()}
922
- icon={Rotate}
923
- _icon={{
924
- color: !formState.isDirty ? 'grey-400' : '#000',
925
- }}
926
- isDisabled={!formState.isDirty}
927
- mr={2}
928
- />}
929
-
930
- {showCancelBtn &&
931
- <Button
932
- key="cancelBtn"
933
- variant="outline"
934
- onPress={onCancel}
935
- color="#fff"
936
- >Cancel</Button>}
937
-
938
- {showCloseBtn &&
939
- <Button
940
- key="closeBtn"
941
- variant="outline"
942
- onPress={onClose}
943
- color="#fff"
944
- >Close</Button>}
945
-
946
- {showSaveBtn &&
947
- <Button
948
- key="saveBtn"
949
- onPress={(e) => handleSubmit(onSaveDecorated, onSubmitError)(e)}
950
- isDisabled={isSaveDisabled}
951
- color="#fff"
952
- >{editorMode === EDITOR_MODE__ADD ? 'Add' : 'Save'}</Button>}
953
-
954
- {showSubmitBtn &&
955
- <Button
956
- key="submitBtn"
957
- onPress={(e) => handleSubmit(onSubmitDecorated, onSubmitError)(e)}
958
- isDisabled={isSubmitDisabled}
959
- color="#fff"
960
- >{submitBtnLabel || 'Submit'}</Button>}
961
-
962
- {additionalFooterButtons && _.map(additionalFooterButtons, (props) => {
963
- return <Button
964
- {...props}
965
- onPress={(e) => handleSubmit(props.onPress, onSubmitError)(e)}
966
- >{props.text}</Button>;
967
- })}
968
- </Footer>
969
- </>}
970
- </VStackNative>;
971
- }
972
-
973
- // helper fns
974
- function disableRequiredYupFields(validator) {
975
- // based on https://github.com/jquense/yup/issues/1466#issuecomment-944386480
976
- if (!validator) {
977
- return null;
978
- }
979
-
980
- const nextSchema = validator.clone();
981
- return nextSchema.withMutation((next) => {
982
- if (typeof next.fields === 'object' && next.fields != null) {
983
- for (const key in next.fields) {
984
- const nestedField = next.fields[key];
985
-
986
- let nestedFieldNext = nestedField.notRequired();
987
-
988
- if (Array.isArray(nestedField.conditions) && nestedField.conditions.length > 0) {
989
- // Next is done to disable required() inside a condition
990
- // https://github.com/jquense/yup/issues/1002
991
- nestedFieldNext = nestedFieldNext.when('whatever', (unused, schema) => {
992
- return schema.notRequired();
993
- });
994
- }
995
-
996
- next.fields[key] = nestedFieldNext;
997
- }
998
- }
999
- });
1000
- }
1001
- function getNullFieldValues(initialValues, Repository) {
1002
- const ret = {};
1003
- if (Repository) {
1004
- const properties = Repository.getSchema().model.properties;
1005
- _.each(properties, (propertyDef) => {
1006
- ret[propertyDef.name] = null;
1007
- });
1008
- } else {
1009
- // takes a JSON object of fieldValues and sets them all to null
1010
- _.each(initialValues, (value, field) => {
1011
- ret[field] = null;
1012
- });
1013
- }
1014
- return ret;
1015
- }
1016
-
1017
- export const FormEditor = withComponent(withAlert(withEditor(withPdfButton(FormikForm))));
1018
-
1019
- export default withComponent(withAlert(withPdfButton(FormikForm)));