@scenid/react-formulator 0.3.1 → 0.4.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/.eslintignore +1 -0
- package/dist/index.cjs.js +37371 -36912
- package/dist/index.esm.js +37358 -36903
- package/firebase.json +5 -1
- package/functions/.eslintignore +1 -0
- package/functions/.eslintrc.js +29 -0
- package/functions/index.js +32 -0
- package/functions/package-lock.json +6810 -0
- package/functions/package.json +29 -0
- package/package.json +3 -2
- package/src/{HiddenData.jsx → Components/HiddenData.jsx} +0 -0
- package/src/Components/SelectOrCreate.jsx +156 -0
- package/src/Editable/FormAutocomplete/FormAutocomplete.jsx +121 -0
- package/src/Editable/FormAutocomplete/useFetchOptions.js +83 -0
- package/src/Editable/FormBoolean.jsx +1 -1
- package/src/Editable/FormCatalogType.jsx +29 -0
- package/src/Editable/FormField.jsx +5 -3
- package/src/Editable/FormRepeater.jsx +2 -1
- package/src/Editable/FormSelect.jsx +1 -0
- package/src/Editable/FormText.jsx +12 -5
- package/src/FormulatorForm.jsx +2 -2
- package/src/FormulatorFormSection.jsx +5 -5
- package/src/ReadOnly/FormReadOnlyField.jsx +1 -1
- package/src/ReadOnly/FormReadOnlyText.jsx +14 -2
- package/src/index.js +4 -0
- package/stories/CustomRenderField.jsx +3 -3
- package/stories/Forms.stories.jsx +66 -3
- package/stories/forms/types.schemas.js +132 -9
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "functions",
|
|
3
|
+
"description": "Cloud Functions for Firebase",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"lint": "eslint .",
|
|
6
|
+
"serve": "firebase emulators:start --only functions",
|
|
7
|
+
"shell": "firebase functions:shell",
|
|
8
|
+
"start": "npm run shell",
|
|
9
|
+
"deploy": "firebase deploy --only functions",
|
|
10
|
+
"logs": "firebase functions:log"
|
|
11
|
+
},
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": "16"
|
|
14
|
+
},
|
|
15
|
+
"main": "index.js",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"cors": "^2.8.5",
|
|
18
|
+
"eslint-config-standard": "^17.0.0",
|
|
19
|
+
"express": "^4.18.0",
|
|
20
|
+
"firebase-admin": "^10.0.2",
|
|
21
|
+
"firebase-functions": "^3.18.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"eslint": "^8.9.0",
|
|
25
|
+
"eslint-config-google": "^0.14.0",
|
|
26
|
+
"firebase-functions-test": "^0.2.0"
|
|
27
|
+
},
|
|
28
|
+
"private": true
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scenid/react-formulator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"main": "dist/index.cjs.js",
|
|
5
5
|
"module": "dist/index.esm.js",
|
|
6
6
|
"repository": "https://dennykoch@bitbucket.org/scenid/react-formulator.git",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"license": "UNLICENSED",
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "rollup -c",
|
|
11
|
-
"
|
|
11
|
+
"watch": "cross-env STORYBOOK_NODE_ENV=development start-storybook -p 6006",
|
|
12
|
+
"start:dev": "firebase emulators:start",
|
|
12
13
|
"storybook:build": "build-storybook",
|
|
13
14
|
"storybook:deploy": "yarn storybook:build && firebase deploy",
|
|
14
15
|
"prepublishOnly": "yarn build",
|
|
File without changes
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
|
|
4
|
+
import { TextField, InputAdornment, CircularProgress } from '@material-ui/core'
|
|
5
|
+
|
|
6
|
+
import Autocomplete, { createFilterOptions } from '@material-ui/lab/Autocomplete'
|
|
7
|
+
|
|
8
|
+
const filter = createFilterOptions()
|
|
9
|
+
|
|
10
|
+
const renderInput = (
|
|
11
|
+
{
|
|
12
|
+
required,
|
|
13
|
+
label,
|
|
14
|
+
onInputChange,
|
|
15
|
+
variant,
|
|
16
|
+
loading
|
|
17
|
+
},
|
|
18
|
+
params
|
|
19
|
+
) => {
|
|
20
|
+
let InputProps = params.InputProps || {}
|
|
21
|
+
|
|
22
|
+
if (loading) {
|
|
23
|
+
InputProps = {
|
|
24
|
+
endAdornment: (
|
|
25
|
+
<InputAdornment position="end">
|
|
26
|
+
<CircularProgress
|
|
27
|
+
size={20}
|
|
28
|
+
thickness={4}
|
|
29
|
+
/>
|
|
30
|
+
</InputAdornment>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<TextField
|
|
37
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
38
|
+
{...params}
|
|
39
|
+
required={required}
|
|
40
|
+
label={label}
|
|
41
|
+
variant={variant}
|
|
42
|
+
onChange={onInputChange}
|
|
43
|
+
InputProps={InputProps}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const SelectOrCreate = ({
|
|
49
|
+
id,
|
|
50
|
+
label,
|
|
51
|
+
variant,
|
|
52
|
+
loading,
|
|
53
|
+
required,
|
|
54
|
+
disabled,
|
|
55
|
+
allowCreate,
|
|
56
|
+
options,
|
|
57
|
+
searchKey,
|
|
58
|
+
renderOption,
|
|
59
|
+
renderNewOption,
|
|
60
|
+
value,
|
|
61
|
+
onOpen,
|
|
62
|
+
onClose,
|
|
63
|
+
onChange,
|
|
64
|
+
onInputChange
|
|
65
|
+
}) => (
|
|
66
|
+
<Autocomplete
|
|
67
|
+
id={id}
|
|
68
|
+
value={value}
|
|
69
|
+
fullWidth
|
|
70
|
+
disabled={disabled}
|
|
71
|
+
onOpen={onOpen}
|
|
72
|
+
onClose={onClose}
|
|
73
|
+
onChange={(_, newValue) => {
|
|
74
|
+
if (typeof newValue === 'string') {
|
|
75
|
+
if (!allowCreate && Array.isArray(options) && !options.find(o => o.entry === newValue)) onChange(undefined)
|
|
76
|
+
else onChange({ [searchKey]: newValue })
|
|
77
|
+
} else if (allowCreate && newValue && newValue.inputValue) {
|
|
78
|
+
// Create a new value from the user input
|
|
79
|
+
onChange({ [searchKey]: newValue.inputValue })
|
|
80
|
+
} else {
|
|
81
|
+
onChange(newValue)
|
|
82
|
+
}
|
|
83
|
+
}}
|
|
84
|
+
filterOptions={(allOptions, params) => {
|
|
85
|
+
const filtered = filter(allOptions, params)
|
|
86
|
+
|
|
87
|
+
// Suggest the creation of a new value
|
|
88
|
+
if (allowCreate && params.inputValue !== '') {
|
|
89
|
+
filtered.push({
|
|
90
|
+
inputValue: params.inputValue,
|
|
91
|
+
[searchKey]: renderNewOption(params.inputValue)
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return filtered
|
|
96
|
+
}}
|
|
97
|
+
selectOnFocus
|
|
98
|
+
clearOnBlur
|
|
99
|
+
handleHomeEndKeys
|
|
100
|
+
options={options}
|
|
101
|
+
getOptionLabel={option => {
|
|
102
|
+
// Value selected with enter, right from the input
|
|
103
|
+
if (typeof option === 'string') {
|
|
104
|
+
if (!allowCreate && Array.isArray(options) && !options.find(o => o.entry === option)) return ''
|
|
105
|
+
return option
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Add "xxx" option created dynamically
|
|
109
|
+
if (option.inputValue) {
|
|
110
|
+
return option.inputValue
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Regular option
|
|
114
|
+
return option[searchKey]
|
|
115
|
+
}}
|
|
116
|
+
renderOption={renderOption}
|
|
117
|
+
freeSolo
|
|
118
|
+
renderInput={params => (
|
|
119
|
+
renderInput(
|
|
120
|
+
{
|
|
121
|
+
required,
|
|
122
|
+
disabled,
|
|
123
|
+
label,
|
|
124
|
+
onInputChange,
|
|
125
|
+
variant,
|
|
126
|
+
loading
|
|
127
|
+
},
|
|
128
|
+
params
|
|
129
|
+
)
|
|
130
|
+
)}
|
|
131
|
+
/>
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
SelectOrCreate.propTypes = {
|
|
135
|
+
id: PropTypes.string,
|
|
136
|
+
label: PropTypes.string.isRequired,
|
|
137
|
+
variant: PropTypes.string,
|
|
138
|
+
loading: PropTypes.bool,
|
|
139
|
+
required: PropTypes.bool,
|
|
140
|
+
disabled: PropTypes.bool,
|
|
141
|
+
allowCreate: PropTypes.bool,
|
|
142
|
+
options: PropTypes.array.isRequired,
|
|
143
|
+
searchKey: PropTypes.string.isRequired,
|
|
144
|
+
renderOption: PropTypes.func.isRequired,
|
|
145
|
+
renderNewOption: PropTypes.func.isRequired,
|
|
146
|
+
value: PropTypes.oneOfType([
|
|
147
|
+
PropTypes.string,
|
|
148
|
+
PropTypes.object
|
|
149
|
+
]).isRequired,
|
|
150
|
+
onOpen: PropTypes.func,
|
|
151
|
+
onClose: PropTypes.func,
|
|
152
|
+
onChange: PropTypes.func.isRequired,
|
|
153
|
+
onInputChange: PropTypes.func.isRequired
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export default SelectOrCreate
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React, { useCallback } from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
|
|
4
|
+
import { Box, Typography, FormControl, FormHelperText } from '@material-ui/core'
|
|
5
|
+
|
|
6
|
+
import FormField from '../FormField'
|
|
7
|
+
import FormReadOnlyText from '../../ReadOnly/FormReadOnlyText'
|
|
8
|
+
import SelectOrCreate from '../../Components/SelectOrCreate'
|
|
9
|
+
|
|
10
|
+
import useFetchOptions from './useFetchOptions'
|
|
11
|
+
|
|
12
|
+
const FormAutocomplete = ({
|
|
13
|
+
name,
|
|
14
|
+
label,
|
|
15
|
+
value,
|
|
16
|
+
options: optionsArg,
|
|
17
|
+
baererToken,
|
|
18
|
+
allowCreate,
|
|
19
|
+
dirty,
|
|
20
|
+
hasErrors,
|
|
21
|
+
errors,
|
|
22
|
+
variant,
|
|
23
|
+
required,
|
|
24
|
+
disabled,
|
|
25
|
+
readOnly,
|
|
26
|
+
onChange
|
|
27
|
+
}) => {
|
|
28
|
+
const { loading, fetch, abort, options } = useFetchOptions(optionsArg, { token: baererToken })
|
|
29
|
+
|
|
30
|
+
const changeValue = useCallback(newValue => { onChange({ target: { name, value: newValue?.entry || '' } }) }, [onChange])
|
|
31
|
+
|
|
32
|
+
if (readOnly) {
|
|
33
|
+
return (
|
|
34
|
+
<FormField
|
|
35
|
+
component={FormReadOnlyText}
|
|
36
|
+
type="text"
|
|
37
|
+
componentProps={{}}
|
|
38
|
+
name={name}
|
|
39
|
+
value={value}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<FormControl
|
|
46
|
+
error={dirty && hasErrors}
|
|
47
|
+
margin="dense"
|
|
48
|
+
fullWidth
|
|
49
|
+
>
|
|
50
|
+
<SelectOrCreate
|
|
51
|
+
loading={loading}
|
|
52
|
+
label={label}
|
|
53
|
+
variant={variant}
|
|
54
|
+
allowCreate={allowCreate}
|
|
55
|
+
options={options}
|
|
56
|
+
searchKey="entry"
|
|
57
|
+
required={required}
|
|
58
|
+
disabled={disabled}
|
|
59
|
+
renderOption={option => (
|
|
60
|
+
<Box>
|
|
61
|
+
<Typography variant="body1">
|
|
62
|
+
{option.entry}
|
|
63
|
+
</Typography>
|
|
64
|
+
{
|
|
65
|
+
option.count !== undefined
|
|
66
|
+
&& (
|
|
67
|
+
<Typography variant="caption">
|
|
68
|
+
{`${option.count} Einträge`}
|
|
69
|
+
</Typography>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
</Box>
|
|
73
|
+
)}
|
|
74
|
+
renderNewOption={newValue => (
|
|
75
|
+
<Typography variant="body1">
|
|
76
|
+
<strong>{`"${newValue}" `}</strong>
|
|
77
|
+
erstellen
|
|
78
|
+
</Typography>
|
|
79
|
+
)}
|
|
80
|
+
value={value}
|
|
81
|
+
onOpen={() => {
|
|
82
|
+
changeValue({ entry: value })
|
|
83
|
+
fetch()
|
|
84
|
+
}}
|
|
85
|
+
onClose={abort}
|
|
86
|
+
onChange={changeValue}
|
|
87
|
+
/>
|
|
88
|
+
{
|
|
89
|
+
(dirty && hasErrors)
|
|
90
|
+
&& (
|
|
91
|
+
<FormHelperText>
|
|
92
|
+
{errors.map(e => e.message).join('. ')}
|
|
93
|
+
</FormHelperText>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
</FormControl>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
FormAutocomplete.propTypes = {
|
|
101
|
+
name: PropTypes.string,
|
|
102
|
+
label: PropTypes.string,
|
|
103
|
+
value: PropTypes.any,
|
|
104
|
+
options: PropTypes.oneOfType([
|
|
105
|
+
PropTypes.array,
|
|
106
|
+
PropTypes.string,
|
|
107
|
+
PropTypes.func
|
|
108
|
+
]).isRequired,
|
|
109
|
+
baererToken: PropTypes.string,
|
|
110
|
+
allowCreate: PropTypes.bool,
|
|
111
|
+
variant: PropTypes.oneOf(['standard', 'filled', 'outlined']),
|
|
112
|
+
required: PropTypes.bool,
|
|
113
|
+
disabled: PropTypes.bool,
|
|
114
|
+
readOnly: PropTypes.bool,
|
|
115
|
+
dirty: PropTypes.bool,
|
|
116
|
+
hasErrors: PropTypes.bool,
|
|
117
|
+
errors: PropTypes.array,
|
|
118
|
+
onChange: PropTypes.func
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default FormAutocomplete
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
const abortableOAuthFetch = async (url, { token, signal }) => {
|
|
4
|
+
let r, text, result
|
|
5
|
+
try {
|
|
6
|
+
const options = {
|
|
7
|
+
method: 'GET'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (token) {
|
|
11
|
+
options.headers = {
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
Authorization: `Bearer ${token}`
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (signal) {
|
|
18
|
+
options.signal = signal
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
r = await fetch(url, options)
|
|
22
|
+
|
|
23
|
+
text = await r.text()
|
|
24
|
+
result = JSON.parse(text)
|
|
25
|
+
} catch (e) {
|
|
26
|
+
throw new Error(text)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!r.ok) throw new Error(result.error)
|
|
30
|
+
|
|
31
|
+
return result
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const useFetchOptions = (arg, fetchOptions = {}) => {
|
|
35
|
+
const controller = useRef()
|
|
36
|
+
|
|
37
|
+
const [loading, setLoading] = useState(false)
|
|
38
|
+
const [options, setOptions] = useState(Array.isArray(arg) || [])
|
|
39
|
+
|
|
40
|
+
const fetch = useCallback(async () => {
|
|
41
|
+
if (Array.isArray(arg)) return setOptions(arg)
|
|
42
|
+
if (typeof arg !== 'string' && typeof arg !== 'function') return setOptions([])
|
|
43
|
+
|
|
44
|
+
let fetcher = arg
|
|
45
|
+
if (typeof arg === 'string') {
|
|
46
|
+
fetcher = signal => abortableOAuthFetch(arg, { token: fetchOptions.token, signal })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
controller.current = new AbortController()
|
|
50
|
+
setLoading(true)
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const r = await fetcher(controller.current.signal)
|
|
54
|
+
controller.current = undefined
|
|
55
|
+
|
|
56
|
+
if (!Array.isArray(r)) throw new Error('autocomplete fetcher returned a non array value')
|
|
57
|
+
|
|
58
|
+
setOptions(r)
|
|
59
|
+
} catch (e) {
|
|
60
|
+
console.log(e)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setLoading(false)
|
|
64
|
+
}, [arg, controller.current, setLoading, setOptions])
|
|
65
|
+
|
|
66
|
+
const abort = useCallback(() => {
|
|
67
|
+
if (loading) setLoading(false)
|
|
68
|
+
|
|
69
|
+
if (controller.current) {
|
|
70
|
+
controller.current.abort()
|
|
71
|
+
controller.current = undefined
|
|
72
|
+
}
|
|
73
|
+
}, [loading, setLoading, controller.current])
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
loading,
|
|
77
|
+
fetch,
|
|
78
|
+
abort,
|
|
79
|
+
options
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default useFetchOptions
|
|
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
|
|
|
4
4
|
import { Checkbox, Switch } from '@material-ui/core'
|
|
5
5
|
|
|
6
6
|
const isBoolean = val => val === false || val === true
|
|
7
|
-
const isChecked = (value, defaultValue) => isBoolean(value) ? value : (defaultValue || false)
|
|
7
|
+
const isChecked = (value, defaultValue) => (isBoolean(value) ? value : (defaultValue || false))
|
|
8
8
|
|
|
9
9
|
const FormBoolean = ({ variant, name, value, defaultValue, needsToBeTrue, onChange }) => {
|
|
10
10
|
const handleChange = e => {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
|
|
4
|
+
import FormAutocomplete from './FormAutocomplete/FormAutocomplete'
|
|
5
|
+
import FormSelect from './FormSelect'
|
|
6
|
+
|
|
7
|
+
const FormCatalogType = ({ type, options, autocomplete, ...props }) => {
|
|
8
|
+
if (autocomplete === true) {
|
|
9
|
+
return (
|
|
10
|
+
<FormAutocomplete
|
|
11
|
+
type="autocomplete"
|
|
12
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
13
|
+
{...props}
|
|
14
|
+
options={options.map(entry => ({ entry: entry.value }))}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
20
|
+
return <FormSelect type={type} options={options} {...props} />
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
FormCatalogType.propTypes = {
|
|
24
|
+
type: PropTypes.string.isRequired,
|
|
25
|
+
options: PropTypes.array.isRequired,
|
|
26
|
+
autocomplete: PropTypes.bool
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default FormCatalogType
|
|
@@ -27,11 +27,11 @@ const FormControlField = ({
|
|
|
27
27
|
componentProps,
|
|
28
28
|
defaultValue,
|
|
29
29
|
value,
|
|
30
|
-
disabled,
|
|
31
30
|
options,
|
|
32
31
|
hasErrors,
|
|
33
32
|
errors,
|
|
34
33
|
required,
|
|
34
|
+
disabled,
|
|
35
35
|
validating,
|
|
36
36
|
dirty,
|
|
37
37
|
onChange
|
|
@@ -63,11 +63,13 @@ const FormControlField = ({
|
|
|
63
63
|
let control
|
|
64
64
|
if (isRender) {
|
|
65
65
|
try {
|
|
66
|
+
const oldProps = component.props || {}
|
|
66
67
|
control = React.cloneElement(
|
|
67
68
|
component,
|
|
68
69
|
{
|
|
69
|
-
variant,
|
|
70
70
|
...finalProps,
|
|
71
|
+
...oldProps,
|
|
72
|
+
variant,
|
|
71
73
|
label,
|
|
72
74
|
hasErrors,
|
|
73
75
|
errors,
|
|
@@ -143,7 +145,6 @@ FormControlField.propTypes = {
|
|
|
143
145
|
PropTypes.string,
|
|
144
146
|
PropTypes.bool
|
|
145
147
|
]),
|
|
146
|
-
labelPlacement: PropTypes.oneOf(['top', 'start', 'bottom', 'end']),
|
|
147
148
|
defaultValue: PropTypes.oneOfType([
|
|
148
149
|
PropTypes.string,
|
|
149
150
|
PropTypes.number,
|
|
@@ -167,6 +168,7 @@ FormControlField.propTypes = {
|
|
|
167
168
|
validating: PropTypes.bool,
|
|
168
169
|
dirty: PropTypes.bool,
|
|
169
170
|
required: PropTypes.bool,
|
|
171
|
+
disabled: PropTypes.bool,
|
|
170
172
|
onChange: PropTypes.func.isRequired
|
|
171
173
|
}
|
|
172
174
|
|
|
@@ -52,7 +52,8 @@ const FormRepeater = ({ variant, name, value, catalog, onChange }) => {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
useEffect(() => {
|
|
55
|
-
|
|
55
|
+
const newValue = entries.length > 0 ? entries : undefined
|
|
56
|
+
onChange({ target: { name, value: newValue } })
|
|
56
57
|
}, [entries])
|
|
57
58
|
|
|
58
59
|
let blockedOptions = []
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
2
3
|
|
|
3
4
|
import { TextField } from '@material-ui/core'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
const FormText = props => {
|
|
7
|
+
const { type } = props
|
|
7
8
|
const finalProps = { ...props }
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
if (type === 'date' || type === 'datetime-local') {
|
|
11
|
+
finalProps.InputLabelProps = { shrink: true }
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
15
|
+
return <TextField type={type} {...finalProps} />
|
|
13
16
|
}
|
|
17
|
+
|
|
18
|
+
FormText.propTypes = { type: PropTypes.string }
|
|
19
|
+
|
|
20
|
+
export default FormText
|
package/src/FormulatorForm.jsx
CHANGED
|
@@ -8,7 +8,7 @@ import isEqual from 'fast-deep-equal'
|
|
|
8
8
|
import { render as mm } from 'micromustache'
|
|
9
9
|
|
|
10
10
|
import FormulatorFormSection from './FormulatorFormSection'
|
|
11
|
-
import HiddenData from './HiddenData'
|
|
11
|
+
import HiddenData from './Components/HiddenData'
|
|
12
12
|
|
|
13
13
|
import 'highlight.js/styles/shades-of-purple.css'
|
|
14
14
|
|
|
@@ -410,7 +410,7 @@ class FormulatorForm extends React.Component {
|
|
|
410
410
|
renderSchema.groups
|
|
411
411
|
.map(({ id: sectionId, label, description, fields }) => {
|
|
412
412
|
if (readOnly && Array.isArray(obscuredGroups) && obscuredGroups.includes(sectionId)) {
|
|
413
|
-
return <HiddenData subject="Der Bereich" label={label} />
|
|
413
|
+
return <HiddenData key={`hidden-field-${sectionId}`} subject="Der Bereich" label={label} />
|
|
414
414
|
}
|
|
415
415
|
|
|
416
416
|
return (
|
|
@@ -11,7 +11,7 @@ import FormSectionBlock from './FormSectionBlock'
|
|
|
11
11
|
import FormField from './Editable/FormField'
|
|
12
12
|
import FormText from './Editable/FormText'
|
|
13
13
|
import FormNumber from './Editable/FormNumber'
|
|
14
|
-
import
|
|
14
|
+
import FormCatalogType from './Editable/FormCatalogType'
|
|
15
15
|
import FormBoolean from './Editable/FormBoolean'
|
|
16
16
|
import FormRepeater from './Editable/FormRepeater'
|
|
17
17
|
|
|
@@ -23,7 +23,7 @@ import FormReadOnlySelect from './ReadOnly/FormReadOnlySelect'
|
|
|
23
23
|
import FormReadOnlyBoolean from './ReadOnly/FormReadOnlyBoolean'
|
|
24
24
|
import FormReadOnlyRepeater from './ReadOnly/FormReadOnlyRepeater'
|
|
25
25
|
|
|
26
|
-
import HiddenData from './HiddenData'
|
|
26
|
+
import HiddenData from './Components/HiddenData'
|
|
27
27
|
|
|
28
28
|
const formReadOnlyComponentMap = {
|
|
29
29
|
default: FormReadOnlyText,
|
|
@@ -39,7 +39,7 @@ const formComponentMap = {
|
|
|
39
39
|
default: FormText,
|
|
40
40
|
string: FormText,
|
|
41
41
|
number: FormNumber,
|
|
42
|
-
select:
|
|
42
|
+
select: FormCatalogType,
|
|
43
43
|
boolean: FormBoolean,
|
|
44
44
|
array: FormRepeater
|
|
45
45
|
}
|
|
@@ -48,7 +48,7 @@ const apComponentMap = {
|
|
|
48
48
|
default: Input,
|
|
49
49
|
string: Input,
|
|
50
50
|
number: FormNumber,
|
|
51
|
-
select:
|
|
51
|
+
select: FormCatalogType,
|
|
52
52
|
boolean: FormBoolean
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -242,7 +242,7 @@ class FormulatorFormSection extends React.Component {
|
|
|
242
242
|
if (readOnly) {
|
|
243
243
|
if (!value) return false
|
|
244
244
|
if (Array.isArray(obscuredFields) && obscuredFields.includes(name)) {
|
|
245
|
-
return <HiddenData subject="Das Feld" label={label} />
|
|
245
|
+
return <HiddenData key={`hidden-field-${name}`} subject="Das Feld" label={label} />
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
|
|
@@ -28,7 +28,7 @@ const FormControlField = ({
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
let control
|
|
31
|
-
if (type === '@@render') control = React.cloneElement(component, finalProps)
|
|
31
|
+
if (type === '@@render') control = React.cloneElement(component, finalProps)
|
|
32
32
|
else control = React.createElement(component, finalProps)
|
|
33
33
|
|
|
34
34
|
return (
|
|
@@ -5,11 +5,18 @@ import { Typography } from '@material-ui/core'
|
|
|
5
5
|
|
|
6
6
|
import { DateTime } from 'luxon'
|
|
7
7
|
|
|
8
|
-
const FormReadOnlyText = ({ value, type }) => {
|
|
8
|
+
const FormReadOnlyText = ({ value, type, renderFormat }) => {
|
|
9
9
|
let finalValue = value
|
|
10
10
|
|
|
11
11
|
if (type === 'date' || type === 'datetime-local') {
|
|
12
|
-
|
|
12
|
+
let formatter = DateTime.DATETIME_FULL
|
|
13
|
+
|
|
14
|
+
if (renderFormat) {
|
|
15
|
+
if (typeof renderFormat === 'string') formatter = DateTime[renderFormat]
|
|
16
|
+
else formatter = renderFormat
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
finalValue = DateTime.fromISO(value).toLocaleString(formatter)
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
return (
|
|
@@ -23,6 +30,11 @@ FormReadOnlyText.propTypes = {
|
|
|
23
30
|
value: PropTypes.oneOfType([
|
|
24
31
|
PropTypes.number,
|
|
25
32
|
PropTypes.string
|
|
33
|
+
]),
|
|
34
|
+
type: PropTypes.string,
|
|
35
|
+
renderFormat: PropTypes.oneOfType([
|
|
36
|
+
PropTypes.string,
|
|
37
|
+
PropTypes.object
|
|
26
38
|
])
|
|
27
39
|
}
|
|
28
40
|
|
package/src/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export FormNumber from './Editable/FormNumber'
|
|
|
6
6
|
export FormRepeater from './Editable/FormRepeater'
|
|
7
7
|
export FormSelect from './Editable/FormSelect'
|
|
8
8
|
export FormText from './Editable/FormText'
|
|
9
|
+
export FormAutocomplete from './Editable/FormAutocomplete/FormAutocomplete'
|
|
9
10
|
|
|
10
11
|
export FormReadOnlyField from './ReadOnly/FormReadOnlyField'
|
|
11
12
|
export FormReadOnlyBoolean from './ReadOnly/FormReadOnlyBoolean'
|
|
@@ -14,3 +15,6 @@ export FormReadOnlyRepeater from './ReadOnly/FormReadOnlyRepeater'
|
|
|
14
15
|
export FormReadOnlySelect from './ReadOnly/FormReadOnlySelect'
|
|
15
16
|
export FormReadOnlyText from './ReadOnly/FormReadOnlyText'
|
|
16
17
|
export FormReadOnlyMarkdown from './ReadOnly/FormReadOnlyMarkdown'
|
|
18
|
+
|
|
19
|
+
export HiddenData from './Components/HiddenData'
|
|
20
|
+
export SelectOrCreate from './Components/SelectOrCreate'
|
|
@@ -12,7 +12,7 @@ const CustomRenderField = ({ name, value, onChange, ...props }) => {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
useEffect(() => {
|
|
15
|
-
setDeg(value?.length * 10)
|
|
15
|
+
setDeg((value?.length || 0) * 10)
|
|
16
16
|
}, [value])
|
|
17
17
|
|
|
18
18
|
return (
|
|
@@ -38,9 +38,9 @@ const CustomRenderField = ({ name, value, onChange, ...props }) => {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
CustomRenderField.propTypes = {
|
|
41
|
-
name: PropTypes.string
|
|
41
|
+
name: PropTypes.string,
|
|
42
42
|
value: PropTypes.string,
|
|
43
|
-
onChange: PropTypes.func
|
|
43
|
+
onChange: PropTypes.func
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export default CustomRenderField
|