@startlibs/form 1.0.12 → 1.1.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 (92) hide show
  1. package/lib/AutoComplete.mjs +550 -0
  2. package/lib/AutoComplete.mjs.map +1 -0
  3. package/lib/Checkbox.mjs +76 -0
  4. package/lib/Checkbox.mjs.map +1 -0
  5. package/lib/Combobox.mjs +544 -0
  6. package/lib/Combobox.mjs.map +1 -0
  7. package/lib/ComboboxWithCustom.mjs +77 -0
  8. package/lib/ComboboxWithCustom.mjs.map +1 -0
  9. package/lib/DatePicker.mjs +625 -0
  10. package/lib/DatePicker.mjs.map +1 -0
  11. package/lib/EditableBox.mjs +496 -0
  12. package/lib/EditableBox.mjs.map +1 -0
  13. package/lib/Errors.mjs +176 -0
  14. package/lib/Errors.mjs.map +1 -0
  15. package/lib/ExpandableBox.mjs +209 -0
  16. package/lib/ExpandableBox.mjs.map +1 -0
  17. package/lib/Field.mjs +65 -0
  18. package/lib/Field.mjs.map +1 -0
  19. package/lib/FieldBox.mjs +80 -0
  20. package/lib/FieldBox.mjs.map +1 -0
  21. package/lib/Form.mjs +322 -0
  22. package/lib/Form.mjs.map +1 -0
  23. package/lib/FormValue.mjs +111 -0
  24. package/lib/FormValue.mjs.map +1 -0
  25. package/lib/InputboxGroup.mjs +104 -0
  26. package/lib/InputboxGroup.mjs.map +1 -0
  27. package/lib/Radiobox.mjs +67 -0
  28. package/lib/Radiobox.mjs.map +1 -0
  29. package/lib/RichText.mjs +634 -0
  30. package/lib/RichText.mjs.map +1 -0
  31. package/lib/TextInput.mjs +359 -0
  32. package/lib/TextInput.mjs.map +1 -0
  33. package/lib/alt/checkbox/SimpleCheckbox.mjs +82 -0
  34. package/lib/alt/checkbox/SimpleCheckbox.mjs.map +1 -0
  35. package/lib/alt/checkbox/ToggleCheckbox.mjs +68 -0
  36. package/lib/alt/checkbox/ToggleCheckbox.mjs.map +1 -0
  37. package/lib/alt/radiobox/CleanTabRadiobox.mjs +44 -0
  38. package/lib/alt/radiobox/CleanTabRadiobox.mjs.map +1 -0
  39. package/lib/alt/radiobox/IconRadioBox.mjs +52 -0
  40. package/lib/alt/radiobox/IconRadioBox.mjs.map +1 -0
  41. package/lib/alt/radiobox/SimpleRadiobox.mjs +61 -0
  42. package/lib/alt/radiobox/SimpleRadiobox.mjs.map +1 -0
  43. package/lib/alt/radiobox/TabRadiobox.mjs +54 -0
  44. package/lib/alt/radiobox/TabRadiobox.mjs.map +1 -0
  45. package/lib/enhanceInput.mjs +131 -0
  46. package/lib/enhanceInput.mjs.map +1 -0
  47. package/lib/index.cjs +5345 -0
  48. package/lib/index.cjs.map +1 -0
  49. package/lib/index.mjs +28 -0
  50. package/lib/index.mjs.map +1 -0
  51. package/lib/useConfirmDialog.mjs +117 -0
  52. package/lib/useConfirmDialog.mjs.map +1 -0
  53. package/lib/useDraftConfirmation.mjs +56 -0
  54. package/lib/useDraftConfirmation.mjs.map +1 -0
  55. package/lib/validation.mjs +91 -0
  56. package/lib/validation.mjs.map +1 -0
  57. package/lib/withFormImageInput.mjs +281 -0
  58. package/lib/withFormImageInput.mjs.map +1 -0
  59. package/package.json +44 -25
  60. package/src/AutoComplete.jsx +642 -0
  61. package/src/Checkbox.jsx +45 -0
  62. package/src/Combobox.jsx +606 -0
  63. package/src/ComboboxWithCustom.jsx +68 -0
  64. package/src/DatePicker.jsx +733 -0
  65. package/src/EditableBox.jsx +502 -0
  66. package/src/Errors.jsx +174 -0
  67. package/src/ExpandableBox.jsx +207 -0
  68. package/src/Field.jsx +76 -0
  69. package/src/FieldBox.jsx +136 -0
  70. package/src/Form.jsx +291 -0
  71. package/src/FormValue.jsx +67 -0
  72. package/src/IconRadioBoxOldHubt.jsx +93 -0
  73. package/src/InputboxGroup.jsx +121 -0
  74. package/src/Radiobox.jsx +33 -0
  75. package/src/RichText.jsx +812 -0
  76. package/src/TextInput.jsx +519 -0
  77. package/src/alt/checkbox/SimpleCheckbox.jsx +162 -0
  78. package/src/alt/checkbox/ToggleCheckbox.jsx +143 -0
  79. package/src/alt/radiobox/CleanTabRadiobox.jsx +46 -0
  80. package/src/alt/radiobox/IconRadioBox.jsx +93 -0
  81. package/src/alt/radiobox/SimpleRadiobox.jsx +153 -0
  82. package/src/alt/radiobox/TabRadiobox.jsx +73 -0
  83. package/src/enhanceInput.jsx +107 -0
  84. package/src/index.js +27 -0
  85. package/src/useConfirmDialog.jsx +105 -0
  86. package/src/useDraftConfirmation.jsx +60 -0
  87. package/src/validation.js +92 -0
  88. package/src/withFormImageInput.jsx +297 -0
  89. package/.babelrc +0 -28
  90. package/lib/index.js +0 -6258
  91. package/rollup.config.js +0 -43
  92. package/yarn-error.log +0 -1607
@@ -0,0 +1,207 @@
1
+ import {darken} from 'polished'
2
+ import React, {useEffect, useRef} from 'react'
3
+ import _ from 'lodash/fp'
4
+ import styled, {css} from 'styled-components'
5
+
6
+ import {callIfFunction, animationTiming, oneWayAnimation} from '@startlibs/utils'
7
+ import {useFormValue} from './Form'
8
+ import {useRefState, useRecycleFocus, useLazyConstant, useToggle} from '@startlibs/core'
9
+ import {willUseFlipSwitch, SwitchDiv} from '@startlibs/components'
10
+ import {media} from '@startlibs/utils';
11
+
12
+ import {Icon} from '@startlibs/components'
13
+ import { constrainEvent, getColor } from '@startlibs/utils';
14
+ import {useTransientErrorHelper} from './Errors'
15
+
16
+
17
+ const shouldPreventExpand = (e,fn) => !(e.target).closest('body,[data-expand="false"],label,input,textarea,button').matches('body,[data-expand="true"]')
18
+
19
+ const ExpandableBoxStyled = styled.div`
20
+ padding: 1rem;
21
+ border-radius: 8px;
22
+ background: ${getColor('lightBluishGray')};
23
+ font-size: 14px;
24
+ outline: none;
25
+ transition: background 0.2s linear;
26
+ position: relative;
27
+ ${props => props.small && `
28
+ padding: 0.875rem 1rem;
29
+ min-height: 3rem;
30
+ `}
31
+ ${props => props.collapsed ? css`
32
+ cursor: pointer;
33
+ padding-right: 3.25rem;
34
+ :hover {
35
+ background: ${props => darken(0.035, getColor('lightBluishGray')(props))};
36
+ }
37
+ :active {
38
+ background: ${props => darken(0.05, getColor('lightBluishGray')(props))};
39
+ transition: none;
40
+ }
41
+ `: css`
42
+ padding: 1.5rem;
43
+ ${media.max(340)`
44
+ padding 1rem;
45
+ `}
46
+ ${props => props.fixMarginBottom && `
47
+ padding-bottom: 0.5rem !important;
48
+ `}
49
+ `}
50
+ & ~ & {
51
+ margin-top: 0.75rem;
52
+ }
53
+ ${SwitchDiv} & {
54
+ margin-top: 0.75rem;
55
+ }
56
+ `
57
+ const EditIcon = styled(Icon).attrs({icon:'edit', className: 'expandable-edit-icon'})`
58
+ position: absolute;
59
+ top: 50%;
60
+ transform: translateY(-50%);
61
+ right: -24px;
62
+ color: rgba(0,0,0,0.25);
63
+ font-size: 20px;
64
+ `
65
+ export const checkErrors = (...checks) => (v,setErrors) => {
66
+ const results = checks.map(c => c(v)).filter(Boolean)
67
+ if (!results.length) { return true }
68
+ const messages = results.filter(_.negate(_.isBoolean))
69
+ if (messages.length) {
70
+ setErrors(...messages)
71
+ }
72
+ return false
73
+ }
74
+
75
+ const timing = animationTiming('0.25s ease-out')
76
+ const switchAnimation = oneWayAnimation('opacity: 1;transform:translateY(0);','opacity: 0;', 'opacity: 0; transform:translateY(30px);')
77
+ const AUTO_OPENED = {}
78
+ let hasAnyExpanded = 0
79
+ export const ExpandableBox = styled(({className, children, overflowEffect, recycleTab, escCancels, enterConfirms, withIcon = true, info, isOpen, openWhen, canExpand = _.identity, path, retargetFilter = (el) => el?.tagName?.toLowerCase() === "button", isValid = () => true, fixMarginBottom, animation = switchAnimation, animationTiming = timing}) => {
80
+ const flipAnimation = useLazyConstant(willUseFlipSwitch)
81
+ const [value,setValue,getValue] = useFormValue(path)
82
+ const [_setErrors,clearErrors] = useTransientErrorHelper()
83
+ const previousValue = useRefState()
84
+ const isAutoOpened = useToggle(isOpen)
85
+ const containerRef = useRef()
86
+
87
+ const setErrors = (...errors) => {
88
+ _setErrors(errors.length ? errors.map(k => _.isString(k) ? [k,''] : k) : [[path,'']])
89
+ return true
90
+ }
91
+
92
+ const expand = useToggle(isOpen,(newV,prevV) => {
93
+ if (newV === 0 || prevV === 0) { return }
94
+ if (newV === false) {
95
+ const result = isValid(getValue(),setErrors)
96
+ if (result === true) {
97
+ clearErrors()
98
+ }
99
+ return result
100
+ }
101
+ })
102
+
103
+ const [firstFocus,lastFocus] = useRecycleFocus(containerRef,recycleTab && expand.isOpen, recycleTab)
104
+
105
+ const openWhenResult = callIfFunction(openWhen)
106
+ useEffect(() => {
107
+ if (openWhenResult) {
108
+ expand.open()
109
+ }
110
+ },[openWhenResult])
111
+
112
+
113
+ useEffect(() => () => {
114
+ if (expand.get() === 0) {
115
+ expand.close()
116
+ }
117
+ },[expand.isOpen])
118
+ useEffect(() => {
119
+ if (expand.get() && !isAutoOpened.get()) {
120
+ previousValue.set(getValue())
121
+ hasAnyExpanded += 1
122
+ return () => {
123
+ hasAnyExpanded -= 1
124
+ }
125
+ }
126
+ },[expand.isOpen,isAutoOpened.isOpen])
127
+
128
+ const cancel = () => {
129
+ setValue(previousValue.get())
130
+ expand.close()
131
+ }
132
+
133
+ const utils = useLazyConstant(()=> ({close: constrainEvent(expand.close),setValue,cancel}))
134
+
135
+ const onBlur = (e) =>{
136
+ if (e.currentTarget !== e.relatedTarget && (!e.relatedTarget || !e.currentTarget.contains(e.relatedTarget))) {
137
+ const retarget = callIfFunction(retargetFilter,e.relatedTarget)
138
+ if (retarget === true) {
139
+ e.target.focus()
140
+ } else if (retarget === false) {
141
+ expand.close()
142
+ }
143
+ }
144
+ }
145
+
146
+ const checkEscEnter = (e) => {
147
+ if (e.key === 'Enter' && enterConfirms) {
148
+ e.stopPropagation()
149
+ e.preventDefault()
150
+ expand.close()
151
+ } else if (e.key === 'Escape' && escCancels) {
152
+ e.stopPropagation()
153
+ e.preventDefault()
154
+ cancel()
155
+ }
156
+ }
157
+
158
+ const onClick = (e) => !shouldPreventExpand(e) && callIfFunction(canExpand,e) && !hasAnyExpanded && expand.open()
159
+
160
+ return <ExpandableBoxStyled
161
+ className={className + (expand.isOpen && (enterConfirms || escCancels) ? " JS-dialog-skip-evts" : "")}
162
+ ref={containerRef}
163
+ tabIndex={-1}
164
+ onBlur={onBlur}
165
+ onFocus={isAutoOpened.close}
166
+ collapsed={!expand.isOpen} fixMarginBottom={fixMarginBottom}
167
+ onClick={onClick}
168
+ onKeyDown={checkEscEnter}
169
+ >
170
+ {expand.isOpen && firstFocus}
171
+ <FlipDiv
172
+ key={expand.isOpen}
173
+ overflowEffect={overflowEffect}
174
+ useAnimation={flipAnimation}
175
+ animation={animation}
176
+ animationTiming={animationTiming}
177
+ >{
178
+ expand.isOpen ? callIfFunction(children,value,utils) : <>{callIfFunction(info, value, utils)}{withIcon && <EditIcon/>}</>
179
+ }</FlipDiv>
180
+ {expand.isOpen && lastFocus}
181
+ </ExpandableBoxStyled>
182
+ })`
183
+
184
+ `
185
+
186
+ const overflowEffect = (element) => {
187
+ if (!element?.offsetParent?.parentElement) { return }
188
+ const parentDom = element.offsetParent.parentElement
189
+ const previousOverflow = parentDom.style.overflow
190
+ parentDom.style.overflow = 'hidden'
191
+ return () => parentDom.style.overflow = previousOverflow
192
+ }
193
+
194
+ export const PseudoExpandableBox = ({children, small, onClick}) => <ExpandableBoxStyled collapsed small={small} onClick={onClick}>
195
+ <FlipDivStyle>
196
+ {children}
197
+ <EditIcon/>
198
+ </FlipDivStyle>
199
+ </ExpandableBoxStyled>
200
+
201
+ const FlipDivStyle = styled.div.attrs({className: 'expandable-flip-div'})`position: relative;`
202
+
203
+ const FlipDiv = ({useAnimation, animation, animationTiming, overflowEffect, ...props}) => {
204
+ const ref = useRef()
205
+ useAnimation(ref,null,null,{switchAnimation:animation,timing:animationTiming,effect:overflowEffect})
206
+ return <FlipDivStyle {...props} ref={ref}/>
207
+ }
package/src/Field.jsx ADDED
@@ -0,0 +1,76 @@
1
+ import styled from 'styled-components'
2
+ import React from 'react'
3
+ import {getMargin, customTheme} from '@startlibs/utils'
4
+
5
+
6
+ const Mandatory = styled.span`
7
+ color: #c3282d;
8
+ margin-left: 1px;
9
+ vertical-align: top;
10
+ line-height: 100%;
11
+ margin-top: -1px;
12
+ ${props => typeof props.mandatory === 'string' && `color: ${props.mandatory};`}
13
+ `
14
+ const Container = styled.div`
15
+ position: relative;
16
+ margin-bottom: ${props => getMargin(props.theme.Field.margins)(props)};
17
+ text-align: left;
18
+ `
19
+ const Label = styled.label`
20
+ font-weight: 550;
21
+ display: inline-block;
22
+ font-size: ${props => props.labelFontSize ? props.labelFontSize : '15'}px;
23
+ margin-bottom: .75rem;
24
+ ${props => props.isOnImage && 'color: #fff;'}
25
+ ${customTheme("FieldLabel")}
26
+ `
27
+
28
+ export const HelpText = styled.span`
29
+ color: rgba(0,0,0,0.4);
30
+ font-weight: 400;
31
+ `
32
+ export const FieldDescription = styled.span`
33
+ color: rgba(0,0,0,0.4);
34
+ display: block;
35
+ font-size: 12px;
36
+ margin: 0.25rem 0 0;
37
+ font-weight: 400;
38
+ `
39
+ export const BellowFieldDescription = styled(FieldDescription)`
40
+ margin: 0.5rem 0 0;
41
+ `
42
+
43
+ export const Field = React.forwardRef((
44
+ {
45
+ label,
46
+ helpText,
47
+ descText,
48
+ bellowDescText,
49
+ className,
50
+ labelClick,
51
+ focused,
52
+ mandatory,
53
+ onImage,
54
+ children,
55
+ labelFontSize,
56
+ ...rest
57
+ }, ref) =>
58
+ <Container ref={ref} {...rest} className={className}>
59
+ {
60
+ label &&
61
+ <Label {...rest} onClick={labelClick} labelFontSize={labelFontSize}>
62
+ {label}{mandatory && <Mandatory mandatory={mandatory}>*</Mandatory>}
63
+ {helpText && <HelpText> {helpText}</HelpText>}
64
+ {descText && <FieldDescription>{descText}</FieldDescription>}
65
+ </Label>
66
+ }
67
+ {children}
68
+ {bellowDescText && <BellowFieldDescription>{bellowDescText}</BellowFieldDescription>}
69
+ </Container>
70
+ )
71
+
72
+ Field.Label = Label
73
+ Field.Container = Container
74
+ Field.HelpText = HelpText
75
+ Field.Description = FieldDescription
76
+ Field.displayName = "Field"
@@ -0,0 +1,136 @@
1
+ import React from 'react'
2
+ import {darken} from 'polished'
3
+ import styled, {css} from 'styled-components'
4
+ import {media, callIfFunction, constrainEvent} from '@startlibs/utils';
5
+ import {Icon, TextButton} from '@startlibs/components'
6
+ import {getColor } from '@startlibs/utils';
7
+ import {usePopupToggle } from '@startlibs/core';
8
+ import {HelpText} from './Field'
9
+
10
+ export const FieldBoxIcon = styled(Icon)`
11
+ font-size: 25px;
12
+ width: 25px;
13
+ text-align: center;
14
+ margin-right: 1rem;
15
+ `
16
+ export const FieldBoxAction = styled.div`
17
+ margin-left: auto;
18
+ color: ${getColor('main')};
19
+ padding-left: 1rem;
20
+ font-size: 13px;
21
+ font-weight: 500;
22
+ `
23
+
24
+ export const FieldBoxHelpText = styled.span`
25
+ font-weight: 400;
26
+ color: rgba(0,0,0,0.4);
27
+ font-size: 12px;
28
+ `
29
+
30
+ const FieldBoxContent = styled.div`
31
+ margin-right: auto;
32
+ min-width: 0;
33
+ `
34
+
35
+ const DeleteButton = styled(Icon).attrs({icon: 'x'})`
36
+ width: 24px;
37
+ height: 24px;
38
+ line-height: 24px;
39
+ border-radius: 50%;
40
+ display: inline-block;
41
+ font-size: 18px;
42
+ margin-left: 0.75rem;
43
+ text-align: center;
44
+ cursor: pointer;
45
+ color: ${getColor('gray120')};
46
+ background-color: rgba(0,0,0,0.08);
47
+ flex-shrink: 0;
48
+ :hover {
49
+ background: rgba(0,0,0,0.11);
50
+ }
51
+ :active {
52
+ background: rgba(0,0,0,0.13);
53
+ }
54
+ `
55
+ const DropdownButton = styled(Icon).attrs({icon: 'arrow-down'})`
56
+ width: 24px;
57
+ height: 24px;
58
+ line-height: 24px;
59
+ border-radius: 50%;
60
+ display: inline-block;
61
+ font-size: 14px;
62
+ margin-left: 0.75rem;
63
+ text-align: center;
64
+ cursor: pointer;
65
+ color: ${getColor('gray120')};
66
+ background-color: rgba(0,0,0,0.08);
67
+ flex-shrink: 0;
68
+ position: relative;
69
+ :hover {
70
+ background: rgba(0,0,0,0.11);
71
+ }
72
+ :active {
73
+ background: rgba(0,0,0,0.13);
74
+ }
75
+ `
76
+
77
+ const DescText = styled.div `
78
+ font-weight: 400;
79
+ color: rgba(0,0,0,0.4);
80
+ font-size: 12px;
81
+ margin-top: 0.25rem;
82
+ `
83
+
84
+
85
+ export const FieldBox = styled(({className,label,helpText,descText,actionLabel,rightContainer,onClick,icon,remove,children,dropDownMenu,...props}) => {
86
+ const menuToggle = usePopupToggle()
87
+ return <div className={className} onClick={onClick} {...props}>
88
+ {icon && <FieldBoxIcon icon={icon}/>}
89
+ <FieldBoxContent>
90
+ {label}{helpText && <HelpText> {helpText}</HelpText>}
91
+ {descText && <DescText>{descText}</DescText>}
92
+ {children}
93
+ </FieldBoxContent>
94
+ {actionLabel && <TextButton>{actionLabel}</TextButton>}
95
+ {rightContainer && <div className="rightContainer">{rightContainer}</div>}
96
+ {remove && <DeleteButton onClick={constrainEvent(remove)}/>}
97
+ {
98
+ dropDownMenu && <DropdownButton onClick={constrainEvent(menuToggle.toggle)}>
99
+ {menuToggle.isOpen && callIfFunction(dropDownMenu)}
100
+ </DropdownButton>
101
+ }
102
+ </div>
103
+ })`
104
+ display: flex;
105
+ align-items: center;
106
+ width: 100%;
107
+ background: ${getColor('lightBluishGray')};
108
+ border-radius: 8px;
109
+ min-height: 42px;
110
+ padding: 1rem 1.25rem;
111
+ align-items: center;
112
+ font-size: 14px;
113
+ color: ${getColor('gray60')};
114
+ position: relative;
115
+ transition: border-radius 0.5s ease;
116
+ ${props => props.onClick && css`
117
+ cursor: pointer;
118
+ :hover {
119
+ background: ${props => darken(0.035, getColor('lightBluishGray')(props))};
120
+ }
121
+ :active {
122
+ background: ${props => darken(0.05, getColor('lightBluishGray')(props))};
123
+ transition: none;
124
+ }
125
+ `}
126
+ & ~ & {
127
+ margin-top: 0.75rem;
128
+ }
129
+ ${media.mobile`
130
+ min-height: 54px;
131
+ font-size: 15px !important;
132
+ `}
133
+ ${TextButton}, .rightContainer {
134
+ margin-left: 1rem;
135
+ }
136
+ `
package/src/Form.jsx ADDED
@@ -0,0 +1,291 @@
1
+ import React, {useState, useMemo, useImperativeHandle, useContext, forwardRef} from 'react'
2
+ import {useConsumerCatalog, useRefState, willUseSpreadState, useToggle, useIsUnmounted, StartlibsContext} from '@startlibs/core'
3
+ import {ifUndefined, _s, callIfFunction} from '@startlibs/utils'
4
+ import {useProvideErrors} from './Errors'
5
+ import {useDraftConfirmation} from './useDraftConfirmation'
6
+ import _ from 'lodash/fp'
7
+
8
+ export const getFormResponse = (setter, path = []) => (_, v) => setter(_s.get(path, v))
9
+ export const getFormValues = (setter, path = []) => (values) => setter(_s.get(path, values))
10
+
11
+ export const FormContext = React.createContext({})
12
+ export const ComponentMetaContext = React.createContext({})
13
+
14
+ const mergeMetas = (metas) => metas.reduce((acc, meta) => ({...acc, ...meta.props}), {})
15
+
16
+ const identityValidation = (props, meta) => ({success: props, meta: meta})
17
+
18
+ const [useProvideValues, useSpreadValue, FormValueContext] = willUseSpreadState()
19
+ const [useProvideMeta, useMeta] = willUseSpreadState()
20
+
21
+ export const useFormValue = useSpreadValue
22
+ export const useFormValueRef = () => {
23
+ const [get, set] = useContext(FormValueContext)
24
+ return [get, set]
25
+ }
26
+ export const useFormMeta = useMeta
27
+
28
+ export const useForm =
29
+ ({
30
+ action,
31
+ alwaysSave = false,
32
+ skipDom = false,
33
+ values: initialValues = {},
34
+ meta = {},
35
+ transform = (v) => v,
36
+ preValidation = () => false,
37
+ loadingBeforeValidate,
38
+ onSuccess = () => {},
39
+ onFailure = () => {},
40
+ onNonPreValidation,
41
+ onChange,
42
+ autoComplete
43
+ }, ref) => {
44
+ const outerConfig = {onSuccess, onFailure, transform, preValidation, alwaysSave, action}
45
+ const currentInitialValues = useRefState(initialValues)
46
+ currentInitialValues.set(initialValues)
47
+ const startlibs = useContext(StartlibsContext)
48
+ const [hasChanged, setHasChangedValue] = useState(false)
49
+ const hasChangedRef = useRefState(false)
50
+ const shouldResubmitForm = useToggle(false, (next, prev) => prev && !next && errorsUtils.removeErrorPath('_shouldResubmitForm'))
51
+ const resubmittingForm = useRefState(false)
52
+ const loading = useToggle()
53
+ const isUnmounted = useIsUnmounted()
54
+ const [ErrorsProvider, errorsContext, errorsUtils] = useProvideErrors()
55
+ const [registeredComponentMetas, componentMetasManager] = useConsumerCatalog()
56
+
57
+ const [confirmDraftContent, draftToggle, draftConfirmation] = useDraftConfirmation(() => submitForm(undefined, {isConfirming: true}))
58
+
59
+ const handleValuesChange = (prev, next) => {
60
+ if (!hasChangedRef.get()) {
61
+ setHasChanged(true)
62
+ }
63
+ callIfFunction(onChange, prev, next, utils)
64
+ }
65
+
66
+ const setHasChanged = (v) => {
67
+ shouldResubmitForm.close()
68
+ if (v !== hasChangedRef.get()) {
69
+ draftToggle.openWith(v)
70
+ setHasChangedValue(v)
71
+ hasChangedRef.set(v)
72
+ }
73
+ }
74
+
75
+ const [ValuesProvider, valuesManager, [getValues, setValues]] = useProvideValues(initialValues, handleValuesChange)
76
+ const [MetaProvider, metaManager, [getMeta, setMeta]] = useProvideMeta(callIfFunction(meta,initialValues), (prev, next) => {
77
+ if (prev.submittingFiles !== 0 && next.submittingFiles === 0) {
78
+ if (resubmittingForm.get()) {
79
+ if (shouldResubmitForm.get()) {
80
+ resubmittingForm.get().confirm()
81
+ } else {
82
+ resubmittingForm.get().cancel()
83
+ }
84
+ resubmittingForm.set(false)
85
+ }
86
+ }
87
+ })
88
+
89
+ const validate = async (values = getValues(), validation = preValidation) => {
90
+ const errors = await callIfFunction(validation, values, utils)
91
+ if (errors) {
92
+ errorsUtils.setErrors(errors)
93
+ }
94
+ return errors
95
+ }
96
+
97
+ const verifyImagesAndSubmitForm = (values) => {
98
+ if (values && getMeta('submittedFiles')) {
99
+ const valuesWithUploadedImages = getMeta('submittedFiles').reduce((acc, {path, file, image}) => {
100
+ if (_s.get(path, acc) === file) {
101
+ return _s.set(path, image, acc)
102
+ } else {
103
+ return acc
104
+ }
105
+ }, values)
106
+ return submitForm(valuesWithUploadedImages)
107
+ } else {
108
+ return submitForm(values)
109
+ }
110
+ }
111
+
112
+ const submitForm = async (optValues, config = {}) => {
113
+ if (loading.get()) {
114
+ return new Promise(() => {})
115
+ }
116
+ const values = ifUndefined(callIfFunction(optValues,getValues()), getValues())
117
+ if (getMeta('submittingFiles')) {
118
+ shouldResubmitForm.open()
119
+ errorsUtils.setError('_shouldResubmitForm', startlibs.FormUploadingFileError || startlibs.FormUploadingImageError)
120
+ if (config.isConfirming) {
121
+ return Promise.reject()
122
+ }
123
+ return new Promise((resolve, reject) => {
124
+ resubmittingForm.set({
125
+ confirm: () => resolve(verifyImagesAndSubmitForm(values)),
126
+ cancel: () => reject()
127
+ })
128
+ })
129
+ }
130
+ const {onSuccess, onFailure, transform, alwaysSave, preValidation, action} = {...outerConfig, ...config}
131
+
132
+ if (preValidation && loadingBeforeValidate) {
133
+ loading.open()
134
+ }
135
+ const errors = await validate(values, preValidation)
136
+ if (errors) {
137
+ loading.close()
138
+ callIfFunction(onNonPreValidation,values,errors)
139
+ throw {unvalidated: errors}
140
+ }
141
+ const request = await transform(values,{meta:getMeta()})
142
+ if (!request) {
143
+ loading.close()
144
+ return {untransformed: true}
145
+ } else {
146
+ loading.open()
147
+ errorsUtils.clearErrors()
148
+ if (!alwaysSave && !hasChangedRef.get() && (!values || values === getValues())) {
149
+ await onSuccess(request)
150
+ if (!isUnmounted()) {
151
+ loading.close()
152
+ }
153
+ return {unrequested: request}
154
+ }
155
+ try {
156
+ const response = await action(request)
157
+ await onSuccess(request, response)
158
+ if (!isUnmounted()) {
159
+ setHasChanged(false)
160
+ loading.close()
161
+ }
162
+ return {request, response}
163
+ } catch (rejection) {
164
+ if (_.get([0,'errors'],rejection)) {
165
+ errorsUtils.setErrors(rejection[0].errors)
166
+ }
167
+ try {
168
+ await onFailure(request, rejection, utils)
169
+ } catch (e) {
170
+ console.log(e)
171
+ }
172
+ if (!isUnmounted()) {
173
+ loading.close()
174
+ }
175
+ throw {request, rejection}
176
+ }
177
+ }
178
+ }
179
+
180
+ const willSubmitForm = (e) => {
181
+ if (e) {
182
+ e.preventDefault()
183
+ e.stopPropagation()
184
+ }
185
+ return submitForm()
186
+ }
187
+
188
+ const inputs = [loading, hasChanged, errorsContext, initialValues, transform, preValidation, onSuccess, onFailure, alwaysSave, action, shouldResubmitForm]
189
+
190
+ const utils = useMemo(() => ({
191
+ setValue: (path, value, hasChanged = true) => {
192
+ setValues(_.isFunction(value) ? _.update(path, value) : _.set(path, value), path, hasChanged ? null : {ignoreCallback: true})
193
+ },
194
+ setValues: (valuesSetter, hasChanged = true) => {
195
+ setValues(valuesSetter, undefined, hasChanged ? null : {ignoreCallback: true})
196
+ },
197
+ resetForm: () => {
198
+ setValues(ifUndefined(currentInitialValues.get(), {}))
199
+ setHasChanged(false)
200
+ },
201
+ resetField: (path) => {
202
+ const initialValue = _s.get(path, currentInitialValues.get())
203
+ if (initialValue !== getValues(path)) {
204
+ setValues(_s.set(path, initialValue))
205
+ }
206
+ },
207
+ getOriginalValues: () => currentInitialValues.get(),
208
+ getOriginalValue: (path) => _s.get(path, currentInitialValues.get()),
209
+ getValues,
210
+ getValue: getValues,
211
+ getTransformedValues: () => transform(getValues()),
212
+ setHasChanged,
213
+ setMeta,
214
+ getMeta,
215
+ confirm: draftConfirmation,
216
+ validate: (props, validation) => !validate(props, validation),
217
+ submitForm,
218
+ willSubmitForm,
219
+ isLoading: loading.isOpen || shouldResubmitForm.isOpen,
220
+ hasChanged,
221
+ getHasChanged: () => hasChangedRef.get(),
222
+ ...errorsUtils
223
+ }),
224
+ inputs
225
+ )
226
+
227
+ useImperativeHandle(ref, () => utils, inputs)
228
+
229
+ return [
230
+ (children) => <FormContext.Provider value={utils}>
231
+ <ComponentMetaContext.Provider value={componentMetasManager}>
232
+ <ValuesProvider value={valuesManager}>
233
+ <MetaProvider value={metaManager}>
234
+ <ErrorsProvider value={errorsContext}>
235
+ {
236
+ !skipDom
237
+ ? <form autoComplete={autoComplete} noValidate onSubmit={willSubmitForm}>{callIfFunction(children, utils)}</form>
238
+ : callIfFunction(children, utils)
239
+ }
240
+ </ErrorsProvider>
241
+ </MetaProvider>
242
+ </ValuesProvider>
243
+ </ComponentMetaContext.Provider>
244
+ {confirmDraftContent}
245
+ </FormContext.Provider>,
246
+ utils
247
+ ]
248
+ }
249
+
250
+ export const WithForm = forwardRef((
251
+ {
252
+ children,
253
+ ...rest
254
+ },
255
+ ref) => {
256
+ return useForm(rest, ref)[0](children)
257
+ })
258
+
259
+ export const Form = ({transform, preValidation, onSuccess, onFailure, children}) => {
260
+ const form = useContext(FormContext)
261
+ const onSubmit = (e) => {
262
+ if (e) e.preventDefault()
263
+ return form.submitForm(
264
+ undefined,
265
+ undefined,
266
+ {transform, preValidation, onSuccess, onFailure}
267
+ )
268
+ }
269
+
270
+ return typeof children === 'function' ?
271
+ children(onSubmit) :
272
+ <form noValidate onSubmit={onSubmit}>
273
+ {children}
274
+ </form>
275
+ }
276
+
277
+ const passProps = (action, props) => action(props)
278
+
279
+ export const withForm = (Component) =>
280
+ React.forwardRef((props, ref) =>
281
+ <WithForm {...props}>
282
+ {form => <Component form={form} {...props} ref={ref}/>}
283
+ </WithForm>
284
+ )
285
+
286
+ export const withForm2 = () => (Component) =>
287
+ React.forwardRef((props, ref) =>
288
+ <WithForm {...props} >
289
+ {form => <Component form={form} {...props} ref={ref}/>}
290
+ </WithForm>
291
+ )