@jsonforms/material-renderers 3.0.0-beta.4 → 3.0.0-rc.1

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 (65) hide show
  1. package/docs/assets/js/search.json +1 -1
  2. package/docs/globals.html +167 -40
  3. package/docs/index.html +15 -0
  4. package/docs/interfaces/categorizationstate.html +1 -1
  5. package/docs/interfaces/categorizationstepperstate.html +1 -1
  6. package/docs/interfaces/inputref.html +168 -0
  7. package/docs/interfaces/materialcategorizationlayoutrendererprops.html +49 -4
  8. package/docs/interfaces/materialcategorizationstepperlayoutrendererprops.html +46 -1
  9. package/docs/interfaces/materiallabelablelayoutrendererprops.html +328 -0
  10. package/docs/interfaces/materiallayoutrendererprops.html +5 -0
  11. package/docs/interfaces/withoptionlabel.html +3 -3
  12. package/lib/additional/MaterialLabelRenderer.d.ts +3 -3
  13. package/lib/cells/MaterialEnumCell.d.ts +2 -1
  14. package/lib/cells/MaterialOneOfEnumCell.d.ts +2 -1
  15. package/lib/controls/MaterialEnumControl.d.ts +2 -1
  16. package/lib/controls/MaterialOneOfEnumControl.d.ts +2 -1
  17. package/lib/controls/index.d.ts +2 -2
  18. package/lib/jsonforms-react-material.cjs.js +143 -65
  19. package/lib/jsonforms-react-material.cjs.js.map +1 -1
  20. package/lib/jsonforms-react-material.esm.js +137 -57
  21. package/lib/jsonforms-react-material.esm.js.map +1 -1
  22. package/lib/layouts/MaterialCategorizationLayout.d.ts +4 -3
  23. package/lib/layouts/MaterialCategorizationStepperLayout.d.ts +4 -3
  24. package/lib/layouts/MaterialGroupLayout.d.ts +2 -2
  25. package/lib/layouts/MaterialHorizontalLayout.d.ts +1 -1
  26. package/lib/layouts/MaterialVerticalLayout.d.ts +1 -1
  27. package/lib/mui-controls/MuiAutocomplete.d.ts +2 -2
  28. package/lib/mui-controls/MuiSelect.d.ts +2 -1
  29. package/lib/util/datejs.d.ts +17 -1
  30. package/lib/util/i18nDefaults.d.ts +3 -0
  31. package/lib/util/index.d.ts +1 -0
  32. package/lib/util/layout.d.ts +3 -0
  33. package/package.json +9 -9
  34. package/src/additional/MaterialLabelRenderer.tsx +5 -7
  35. package/src/additional/MaterialListWithDetailRenderer.tsx +4 -0
  36. package/src/cells/MaterialEnumCell.tsx +4 -3
  37. package/src/cells/MaterialOneOfEnumCell.tsx +3 -3
  38. package/src/controls/MaterialDateControl.tsx +30 -12
  39. package/src/controls/MaterialDateTimeControl.tsx +32 -13
  40. package/src/controls/MaterialEnumControl.tsx +12 -5
  41. package/src/controls/MaterialOneOfEnumControl.tsx +13 -5
  42. package/src/controls/MaterialRadioGroup.tsx +1 -1
  43. package/src/controls/MaterialTimeControl.tsx +31 -13
  44. package/src/layouts/MaterialCategorizationLayout.tsx +18 -9
  45. package/src/layouts/MaterialCategorizationStepperLayout.tsx +19 -12
  46. package/src/layouts/MaterialGroupLayout.tsx +6 -5
  47. package/src/mui-controls/MuiAutocomplete.tsx +81 -37
  48. package/src/mui-controls/MuiInputText.tsx +4 -1
  49. package/src/mui-controls/MuiSelect.tsx +10 -5
  50. package/src/util/datejs.tsx +73 -0
  51. package/src/util/i18nDefaults.ts +3 -0
  52. package/src/util/index.ts +1 -0
  53. package/src/util/layout.tsx +4 -0
  54. package/stats.html +1 -1
  55. package/test/renderers/MaterialArrayLayout.test.tsx +4 -4
  56. package/test/renderers/MaterialCategorizationLayout.test.tsx +17 -7
  57. package/test/renderers/MaterialCategorizationStepperLayout.test.tsx +21 -11
  58. package/test/renderers/MaterialDateControl.test.tsx +27 -0
  59. package/test/renderers/MaterialDateTimeControl.test.tsx +29 -2
  60. package/test/renderers/MaterialGroupLayout.test.tsx +4 -1
  61. package/test/renderers/MaterialInputControl.test.tsx +4 -0
  62. package/test/renderers/MaterialLabelRenderer.test.tsx +2 -1
  63. package/test/renderers/MaterialTimeControl.test.tsx +28 -1
  64. package/test/renderers/util.ts +5 -0
  65. package/src/util/datejs.ts +0 -32
@@ -22,12 +22,13 @@
22
22
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
23
  THE SOFTWARE.
24
24
  */
25
- import React, {useState} from 'react';
25
+ import React, {useState, useMemo} from 'react';
26
26
  import { AppBar, Hidden, Tab, Tabs } from '@mui/material';
27
27
  import {
28
28
  and,
29
29
  Categorization,
30
30
  Category,
31
+ deriveLabelForUISchemaElement,
31
32
  isVisible,
32
33
  RankedTester,
33
34
  rankWith,
@@ -36,7 +37,7 @@ import {
36
37
  UISchemaElement,
37
38
  uiTypeIs
38
39
  } from '@jsonforms/core';
39
- import { withJsonFormsLayoutProps } from '@jsonforms/react';
40
+ import { TranslateProps, withJsonFormsLayoutProps, withTranslateProps } from '@jsonforms/react';
40
41
  import {
41
42
  AjvProps,
42
43
  MaterialLayoutRenderer,
@@ -68,7 +69,7 @@ export interface CategorizationState {
68
69
  }
69
70
 
70
71
  export interface MaterialCategorizationLayoutRendererProps
71
- extends StatePropsOfLayout, AjvProps {
72
+ extends StatePropsOfLayout, AjvProps, TranslateProps {
72
73
  selected?: number;
73
74
  ownState?: boolean;
74
75
  data?: any;
@@ -87,13 +88,14 @@ export const MaterialCategorizationLayoutRenderer = (props: MaterialCategorizati
87
88
  enabled,
88
89
  selected,
89
90
  onChange,
90
- ajv
91
+ ajv,
92
+ t
91
93
  } = props;
92
94
  const categorization = uischema as Categorization;
93
95
  const [activeCategory, setActiveCategory]= useState<number|undefined>(selected??0);
94
- const categories = categorization.elements.filter((category: Category) =>
96
+ const categories = useMemo(() => categorization.elements.filter((category: Category) =>
95
97
  isVisible(category, data, undefined, ajv)
96
- );
98
+ ),[categorization, data, ajv]);
97
99
  const childProps: MaterialLayoutRendererProps = {
98
100
  elements: categories[activeCategory].elements,
99
101
  schema,
@@ -110,12 +112,19 @@ export const MaterialCategorizationLayoutRenderer = (props: MaterialCategorizati
110
112
  }
111
113
  setActiveCategory(value);
112
114
  };
115
+
116
+ const tabLabels = useMemo(() => {
117
+ return categories.map((e: Category) =>
118
+ deriveLabelForUISchemaElement(e, t)
119
+ )
120
+ }, [categories, t])
121
+
113
122
  return (
114
123
  <Hidden xsUp={!visible}>
115
124
  <AppBar position='static'>
116
125
  <Tabs value={activeCategory} onChange={onTabChange} textColor='inherit' indicatorColor='secondary' variant='scrollable'>
117
- {categories.map((e: Category, idx: number) => (
118
- <Tab key={idx} label={e.label} />
126
+ {categories.map((_, idx: number) => (
127
+ <Tab key={idx} label={tabLabels[idx]} />
119
128
  ))}
120
129
  </Tabs>
121
130
  </AppBar>
@@ -126,4 +135,4 @@ export const MaterialCategorizationLayoutRenderer = (props: MaterialCategorizati
126
135
  );
127
136
  };
128
137
 
129
- export default withJsonFormsLayoutProps(withAjvProps(MaterialCategorizationLayoutRenderer));
138
+ export default withAjvProps(withTranslateProps(withJsonFormsLayoutProps(MaterialCategorizationLayoutRenderer)));
@@ -22,7 +22,7 @@
22
22
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
23
  THE SOFTWARE.
24
24
  */
25
- import React, {useState} from 'react';
25
+ import React, {useState, useMemo} from 'react';
26
26
  import merge from 'lodash/merge';
27
27
  import { Button, Hidden, Step, StepButton, Stepper } from '@mui/material';
28
28
  import {
@@ -30,6 +30,7 @@ import {
30
30
  Categorization,
31
31
  categorizationHasCategory,
32
32
  Category,
33
+ deriveLabelForUISchemaElement,
33
34
  isVisible,
34
35
  optionIs,
35
36
  RankedTester,
@@ -37,7 +38,7 @@ import {
37
38
  StatePropsOfLayout,
38
39
  uiTypeIs
39
40
  } from '@jsonforms/core';
40
- import { withJsonFormsLayoutProps } from '@jsonforms/react';
41
+ import { TranslateProps, withJsonFormsLayoutProps, withTranslateProps } from '@jsonforms/react';
41
42
  import {
42
43
  AjvProps,
43
44
  MaterialLayoutRenderer,
@@ -59,7 +60,7 @@ export interface CategorizationStepperState {
59
60
  }
60
61
 
61
62
  export interface MaterialCategorizationStepperLayoutRendererProps
62
- extends StatePropsOfLayout, AjvProps {
63
+ extends StatePropsOfLayout, AjvProps, TranslateProps {
63
64
  data: any;
64
65
  }
65
66
 
@@ -79,7 +80,8 @@ export const MaterialCategorizationStepperLayoutRenderer = (props: MaterialCateg
79
80
  visible,
80
81
  cells,
81
82
  config,
82
- ajv
83
+ ajv,
84
+ t
83
85
  } = props;
84
86
  const categorization = uischema as Categorization;
85
87
  const appliedUiSchemaOptions = merge({}, config, uischema.options);
@@ -94,9 +96,9 @@ export const MaterialCategorizationStepperLayoutRenderer = (props: MaterialCateg
94
96
  const buttonStyle = {
95
97
  marginRight: '1em'
96
98
  };
97
- const categories = categorization.elements.filter((category: Category) =>
99
+ const categories = useMemo(() => categorization.elements.filter((category: Category) =>
98
100
  isVisible(category, data, undefined, ajv)
99
- );
101
+ ),[categorization, data, ajv]);
100
102
  const childProps: MaterialLayoutRendererProps = {
101
103
  elements: categories[activeCategory].elements,
102
104
  schema,
@@ -106,13 +108,18 @@ export const MaterialCategorizationStepperLayoutRenderer = (props: MaterialCateg
106
108
  renderers,
107
109
  cells
108
110
  };
111
+ const tabLabels = useMemo(() => {
112
+ return categories.map((e: Category) =>
113
+ deriveLabelForUISchemaElement(e, t)
114
+ )
115
+ }, [categories, t])
109
116
  return (
110
117
  <Hidden xsUp={!visible}>
111
118
  <Stepper activeStep={activeCategory} nonLinear>
112
- {categories.map((e: Category, idx: number) => (
113
- <Step key={e.label}>
119
+ {categories.map((_: Category, idx: number) => (
120
+ <Step key={tabLabels[idx]}>
114
121
  <StepButton onClick={() => handleStep(idx)}>
115
- {e.label}
122
+ {tabLabels[idx]}
116
123
  </StepButton>
117
124
  </Step>
118
125
  ))}
@@ -144,6 +151,6 @@ export const MaterialCategorizationStepperLayoutRenderer = (props: MaterialCateg
144
151
  );
145
152
  };
146
153
 
147
- export default withJsonFormsLayoutProps(withAjvProps(
148
- MaterialCategorizationStepperLayoutRenderer
149
- ));
154
+ export default withAjvProps(withTranslateProps(
155
+ withJsonFormsLayoutProps(MaterialCategorizationStepperLayoutRenderer
156
+ )));
@@ -34,21 +34,21 @@ import {
34
34
  withIncreasedRank,
35
35
  } from '@jsonforms/core';
36
36
  import {
37
+ MaterialLabelableLayoutRendererProps,
37
38
  MaterialLayoutRenderer,
38
- MaterialLayoutRendererProps
39
39
  } from '../util/layout';
40
40
  import { withJsonFormsLayoutProps } from '@jsonforms/react';
41
41
 
42
42
  export const groupTester: RankedTester = rankWith(1, uiTypeIs('Group'));
43
43
  const style: { [x: string]: any } = { marginBottom: '10px' };
44
44
 
45
- const GroupComponent = React.memo(({ visible, enabled, uischema, ...props }: MaterialLayoutRendererProps) => {
45
+ const GroupComponent = React.memo(({ visible, enabled, uischema, label, ...props }: MaterialLabelableLayoutRendererProps) => {
46
46
  const groupLayout = uischema as GroupLayout;
47
47
  return (
48
48
  <Hidden xsUp={!visible}>
49
49
  <Card style={style}>
50
- {!isEmpty(groupLayout.label) && (
51
- <CardHeader title={groupLayout.label} />
50
+ {!isEmpty(label) && (
51
+ <CardHeader title={label} />
52
52
  )}
53
53
  <CardContent>
54
54
  <MaterialLayoutRenderer {...props} visible={visible} enabled={enabled} elements={groupLayout.elements} />
@@ -58,7 +58,7 @@ const GroupComponent = React.memo(({ visible, enabled, uischema, ...props }: Mat
58
58
  );
59
59
  });
60
60
 
61
- export const MaterializedGroupLayoutRenderer = ({ uischema, schema, path, visible, enabled, renderers, cells, direction }: LayoutProps) => {
61
+ export const MaterializedGroupLayoutRenderer = ({ uischema, schema, path, visible, enabled, renderers, cells, direction, label }: LayoutProps) => {
62
62
  const groupLayout = uischema as GroupLayout;
63
63
 
64
64
  return (
@@ -72,6 +72,7 @@ export const MaterializedGroupLayoutRenderer = ({ uischema, schema, path, visibl
72
72
  uischema={uischema}
73
73
  renderers={renderers}
74
74
  cells={cells}
75
+ label={label}
75
76
  />
76
77
  );
77
78
  };
@@ -23,15 +23,18 @@
23
23
  THE SOFTWARE.
24
24
  */
25
25
  import React, { ReactNode } from 'react';
26
- import { EnumCellProps, EnumOption, WithClassname } from '@jsonforms/core';
26
+ import { ControlProps, EnumCellProps, EnumOption, isDescriptionHidden, WithClassname } from '@jsonforms/core';
27
27
 
28
28
  import {
29
29
  Autocomplete,
30
30
  AutocompleteRenderOptionState,
31
- Input,
32
- FilterOptionsState
31
+ FilterOptionsState,
32
+ FormHelperText,
33
+ Hidden,
34
+ TextField
33
35
  } from '@mui/material';
34
36
  import merge from 'lodash/merge';
37
+ import { useFocus } from '../util/focus';
35
38
 
36
39
  export interface WithOptionLabel {
37
40
  getOptionLabel?(option: EnumOption) : string;
@@ -39,8 +42,13 @@ export interface WithOptionLabel {
39
42
  filterOptions?(options: EnumOption[], state: FilterOptionsState<EnumOption>) : EnumOption[];
40
43
  }
41
44
 
42
- export const MuiAutocomplete = (props: EnumCellProps & WithClassname & WithOptionLabel) => {
45
+ export const MuiAutocomplete = (props: ControlProps & EnumCellProps & WithClassname & WithOptionLabel) => {
43
46
  const {
47
+ description,
48
+ errors,
49
+ visible,
50
+ required,
51
+ label,
44
52
  data,
45
53
  className,
46
54
  id,
@@ -52,44 +60,80 @@ export const MuiAutocomplete = (props: EnumCellProps & WithClassname & WithOptio
52
60
  config,
53
61
  getOptionLabel,
54
62
  renderOption,
55
- filterOptions
63
+ filterOptions,
64
+ isValid
56
65
  } = props;
57
66
  const appliedUiSchemaOptions = merge({}, config, uischema.options);
58
67
  const [inputValue, setInputValue] = React.useState(data ?? '');
68
+ const [focused, onFocus, onBlur] = useFocus();
59
69
 
60
70
  const findOption = options.find(o => o.value === data) ?? null;
71
+
72
+ const showDescription = !isDescriptionHidden(
73
+ visible,
74
+ description,
75
+ focused,
76
+ appliedUiSchemaOptions.showUnfocusedDescription
77
+ );
78
+
79
+ const firstFormHelperText = showDescription
80
+ ? description
81
+ : !isValid
82
+ ? errors
83
+ : null;
84
+ const secondFormHelperText = showDescription && !isValid ? errors : null;
85
+
61
86
  return (
62
- <Autocomplete
63
- className={className}
64
- id={id}
65
- disabled={!enabled}
66
- value={findOption}
67
- onChange={(_event: any, newValue: EnumOption | null) => {
68
- handleChange(path, newValue?.value);
69
- }}
70
- inputValue={inputValue}
71
- onInputChange={(_event, newInputValue) => {
72
- setInputValue(newInputValue);
73
- }}
74
- autoHighlight
75
- autoSelect
76
- autoComplete
77
- fullWidth
78
- options={options}
79
- getOptionLabel={getOptionLabel || (option => option?.label)}
80
- style={{ marginTop: 16 }}
81
- renderInput={params => (
82
- <Input
83
- style={{ width: '100%' }}
84
- type='text'
85
- inputProps={params.inputProps}
86
- inputRef={params.InputProps.ref}
87
- autoFocus={appliedUiSchemaOptions.focus}
88
- disabled={!enabled}
89
- />
90
- )}
91
- renderOption={renderOption}
92
- filterOptions={filterOptions}
93
- />
87
+ <Hidden xsUp={!visible}>
88
+ <Autocomplete
89
+ className={className}
90
+ id={id}
91
+ disabled={!enabled}
92
+ value={findOption}
93
+ onChange={(_event: any, newValue: EnumOption | null) => {
94
+ handleChange(path, newValue?.value);
95
+ }}
96
+ inputValue={inputValue}
97
+ onInputChange={(_event, newInputValue) => {
98
+ setInputValue(newInputValue);
99
+ }}
100
+ autoHighlight
101
+ autoSelect
102
+ autoComplete
103
+ fullWidth
104
+ options={options}
105
+ getOptionLabel={getOptionLabel || (option => option?.label)}
106
+ freeSolo={false}
107
+ renderInput={params => {
108
+ return(
109
+ <TextField
110
+ label={label}
111
+ variant={'standard'}
112
+ type='text'
113
+ inputProps={params.inputProps}
114
+ inputRef={params.InputProps.ref}
115
+ autoFocus={appliedUiSchemaOptions.focus}
116
+ disabled={!enabled}
117
+ {...params}
118
+ id={id + '-input'}
119
+ required={required && !appliedUiSchemaOptions.hideRequiredAsterisk}
120
+ error={!isValid}
121
+ fullWidth={!appliedUiSchemaOptions.trim}
122
+ InputLabelProps={data ? { shrink: true } : undefined}
123
+ onFocus={onFocus}
124
+ onBlur={onBlur}
125
+ focused={focused}
126
+ />
127
+ )}}
128
+ renderOption={renderOption}
129
+ filterOptions={filterOptions}
130
+ />
131
+ <FormHelperText error={!isValid && !showDescription}>
132
+ {firstFormHelperText}
133
+ </FormHelperText>
134
+ <FormHelperText error={!isValid}>
135
+ {secondFormHelperText}
136
+ </FormHelperText>
137
+ </Hidden>
94
138
  );
95
139
  };
@@ -40,6 +40,9 @@ interface MuiTextInputProps {
40
40
  muiInputProps?: InputProps['inputProps'];
41
41
  inputComponent?: InputProps['inputComponent'];
42
42
  }
43
+
44
+ const eventToValue = (ev:any) => ev.target.value === '' ? undefined : ev.target.value;
45
+
43
46
  export const MuiInputText = React.memo((props: CellProps & WithClassname & MuiTextInputProps) => {
44
47
  const [showAdornment, setShowAdornment] = useState(false);
45
48
  const {
@@ -71,7 +74,7 @@ export const MuiInputText = React.memo((props: CellProps & WithClassname & MuiTe
71
74
  inputProps.size = maxLength;
72
75
  };
73
76
 
74
- const [inputText, onChange, onClear] = useDebouncedChange(handleChange, '', data, path);
77
+ const [inputText, onChange, onClear] = useDebouncedChange(handleChange, '', data, path, eventToValue);
75
78
  const onPointerEnter = () => setShowAdornment(true);
76
79
  const onPointerLeave = () => setShowAdornment(false);
77
80
 
@@ -22,25 +22,30 @@
22
22
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
23
  THE SOFTWARE.
24
24
  */
25
- import React from 'react';
25
+ import React, { useMemo } from 'react';
26
26
  import { EnumCellProps, WithClassname } from '@jsonforms/core';
27
27
 
28
28
  import { MenuItem, Select } from '@mui/material';
29
29
  import merge from 'lodash/merge';
30
+ import { TranslateProps } from '@jsonforms/react';
31
+ import { i18nDefaults } from '../util';
30
32
 
31
- export const MuiSelect = React.memo((props: EnumCellProps & WithClassname) => {
33
+ export const MuiSelect = React.memo((props: EnumCellProps & WithClassname & TranslateProps) => {
32
34
  const {
33
35
  data,
34
36
  className,
35
37
  id,
36
38
  enabled,
39
+ schema,
37
40
  uischema,
38
41
  path,
39
42
  handleChange,
40
43
  options,
41
- config
44
+ config,
45
+ t
42
46
  } = props;
43
47
  const appliedUiSchemaOptions = merge({}, config, uischema.options);
48
+ const noneOptionLabel = useMemo(() => t('enum.none', i18nDefaults['enum.none'], { schema, uischema, path}), [t, schema, uischema, path]);
44
49
 
45
50
  return (
46
51
  <Select
@@ -49,11 +54,11 @@ export const MuiSelect = React.memo((props: EnumCellProps & WithClassname) => {
49
54
  disabled={!enabled}
50
55
  autoFocus={appliedUiSchemaOptions.focus}
51
56
  value={data !== undefined ? data : ''}
52
- onChange={ev => handleChange(path, ev.target.value)}
57
+ onChange={ev =>handleChange(path, ev.target.value || undefined)}
53
58
  fullWidth={true}
54
59
  variant={'standard'}
55
60
  >
56
- {[<MenuItem value='' key={'empty'} />].concat(
61
+ {[<MenuItem value={''} key='jsonforms.enum.none'><em>{noneOptionLabel}</em></MenuItem>].concat(
57
62
  options.map(optionValue => (
58
63
  <MenuItem value={optionValue.value} key={optionValue.value}>
59
64
  {optionValue.label}
@@ -0,0 +1,73 @@
1
+ import { TextField, TextFieldProps } from '@mui/material';
2
+ import dayjs from 'dayjs';
3
+ import customParsing from 'dayjs/plugin/customParseFormat';
4
+ import React, { useRef} from 'react';
5
+
6
+ // required for the custom save formats in the date, time and date-time pickers
7
+ dayjs.extend(customParsing);
8
+
9
+ export const createOnChangeHandler = (
10
+ path: string,
11
+ handleChange: (path: string, value: any) => void,
12
+ saveFormat: string | undefined
13
+ ) => (time: dayjs.Dayjs, textInputValue: string) => {
14
+ if (!time) {
15
+ handleChange(path, undefined);
16
+ return;
17
+ }
18
+ const result = dayjs(time).format(saveFormat);
19
+ handleChange(path, result === 'Invalid Date' ? textInputValue : result);
20
+ };
21
+
22
+ export const getData = (
23
+ data: any,
24
+ saveFormat: string | undefined
25
+ ): dayjs.Dayjs | null => {
26
+ if (!data) {
27
+ return null;
28
+ }
29
+ const dayjsData = dayjs(data, saveFormat);
30
+ if (dayjsData.toString() === 'Invalid Date') {
31
+ return null;
32
+ }
33
+ return dayjsData;
34
+ };
35
+
36
+
37
+ interface InputRef {
38
+ lastInput: string;
39
+ toShow: string;
40
+ }
41
+
42
+ type ResettableTextFieldProps = TextFieldProps & {
43
+ rawValue: any;
44
+ dayjsValueIsValid: boolean;
45
+ valueInInputFormat: string;
46
+ focused: boolean;
47
+ }
48
+
49
+ /**
50
+ * The dayjs formatter/parser is very lenient and for example ignores additional digits and/or characters.
51
+ * In these cases the input text can look vastly different than the actual value stored in the data.
52
+ * The 'ResettableTextField' component adjusts the text field to reflect the actual value stored in the data
53
+ * once it's no longer 'focused', i.e. when the user stops editing.
54
+ */
55
+ export const ResettableTextField: React.FC<ResettableTextFieldProps> = ({ rawValue, dayjsValueIsValid, valueInInputFormat, focused, inputProps, ...props }) => {
56
+ const value = useRef<InputRef>({ lastInput: inputProps?.value, toShow: inputProps?.value });
57
+ if (!focused) {
58
+ // The input text is not focused, therefore let's show the value actually stored in the data
59
+ if (!dayjsValueIsValid) {
60
+ // pass through the "raw" value in case it can't be formatted by dayjs
61
+ value.current.toShow = typeof rawValue === 'string' || rawValue === null || rawValue === undefined ? rawValue : JSON.stringify(rawValue)
62
+ } else {
63
+ // otherwise use the specified format
64
+ value.current.toShow = valueInInputFormat;
65
+ }
66
+ }
67
+ if (focused && inputProps?.value !== value.current.lastInput) {
68
+ // Show the current text the user is typing into the text input
69
+ value.current.lastInput = inputProps?.value;
70
+ value.current.toShow = inputProps?.value;
71
+ }
72
+ return <TextField {...props} inputProps={{ ...inputProps, value: value.current.toShow || '' }} />
73
+ }
@@ -0,0 +1,3 @@
1
+ export const i18nDefaults = {
2
+ 'enum.none': 'None'
3
+ }
package/src/util/index.ts CHANGED
@@ -27,3 +27,4 @@ export * from './layout';
27
27
  export * from './theme';
28
28
  export * from './debounce';
29
29
  export * from './focus';
30
+ export * from './i18nDefaults';
@@ -110,3 +110,7 @@ export const withAjvProps = <P extends {}>(Component: ComponentType<AjvProps & P
110
110
 
111
111
  return (<Component {...props} ajv={ajv} />);
112
112
  };
113
+
114
+ export interface MaterialLabelableLayoutRendererProps extends MaterialLayoutRendererProps {
115
+ label?: string;
116
+ }