@jsonforms/material-renderers 3.0.0-beta.4 → 3.0.0-beta.5

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,3 +1,19 @@
1
+ import { TextFieldProps } from '@mui/material';
1
2
  import dayjs from 'dayjs';
2
- export declare const createOnChangeHandler: (path: string, handleChange: (path: string, value: any) => void, saveFormat: string | undefined) => (time: dayjs.Dayjs) => void;
3
+ import React from 'react';
4
+ export declare const createOnChangeHandler: (path: string, handleChange: (path: string, value: any) => void, saveFormat: string | undefined) => (time: dayjs.Dayjs, textInputValue: string) => void;
3
5
  export declare const getData: (data: any, saveFormat: string | undefined) => dayjs.Dayjs | null;
6
+ declare type ResettableTextFieldProps = TextFieldProps & {
7
+ rawValue: any;
8
+ dayjsValueIsValid: boolean;
9
+ valueInInputFormat: string;
10
+ focused: boolean;
11
+ };
12
+ /**
13
+ * The dayjs formatter/parser is very lenient and for example ignores additional digits and/or characters.
14
+ * In these cases the input text can look vastly different than the actual value stored in the data.
15
+ * The 'ResettableTextField' component adjusts the text field to reflect the actual value stored in the data
16
+ * once it's no longer 'focused', i.e. when the user stops editing.
17
+ */
18
+ export declare const ResettableTextField: React.FC<ResettableTextFieldProps>;
19
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonforms/material-renderers",
3
- "version": "3.0.0-beta.4",
3
+ "version": "3.0.0-beta.5",
4
4
  "description": "Material Renderer Set for JSON Forms",
5
5
  "repository": "https://github.com/eclipsesource/jsonforms",
6
6
  "bugs": "https://github.com/eclipsesource/jsonforms/issues",
@@ -76,8 +76,8 @@
76
76
  "peerDependencies": {
77
77
  "@emotion/react": "^11.4.1",
78
78
  "@emotion/styled": "^11.3.0",
79
- "@jsonforms/core": "3.0.0-beta.4",
80
- "@jsonforms/react": "3.0.0-beta.4",
79
+ "@jsonforms/core": "3.0.0-beta.5",
80
+ "@jsonforms/react": "3.0.0-beta.5",
81
81
  "@mui/icons-material": "^5.0.0",
82
82
  "@mui/lab": "^5.0.0-alpha.54",
83
83
  "@mui/material": "^5.0.0"
@@ -85,8 +85,8 @@
85
85
  "devDependencies": {
86
86
  "@emotion/react": "^11.5.0",
87
87
  "@emotion/styled": "^11.3.0",
88
- "@jsonforms/core": "^3.0.0-beta.4",
89
- "@jsonforms/react": "^3.0.0-beta.4",
88
+ "@jsonforms/core": "^3.0.0-beta.5",
89
+ "@jsonforms/react": "^3.0.0-beta.5",
90
90
  "@mui/icons-material": "^5.2.0",
91
91
  "@mui/lab": "^5.0.0-alpha.58",
92
92
  "@mui/material": "^5.2.2",
@@ -114,5 +114,5 @@
114
114
  "webpack-cli": "^3.2.1",
115
115
  "webpack-dev-server": "^3.9.0"
116
116
  },
117
- "gitHead": "413b9767ea8e2c0b6adb3919f5e6b76263d4be31"
117
+ "gitHead": "b66023cf081e56baa3194ac3d6658e23fd267bf9"
118
118
  }
@@ -32,13 +32,18 @@ import {
32
32
  rankWith,
33
33
  } from '@jsonforms/core';
34
34
  import { withJsonFormsControlProps } from '@jsonforms/react';
35
- import { FormHelperText, Hidden, TextField } from '@mui/material';
35
+ import { FormHelperText, Hidden } from '@mui/material';
36
36
  import {
37
37
  DatePicker,
38
38
  LocalizationProvider
39
39
  } from '@mui/lab';
40
40
  import AdapterDayjs from '@mui/lab/AdapterDayjs';
41
- import { createOnChangeHandler, getData, useFocus } from '../util';
41
+ import {
42
+ createOnChangeHandler,
43
+ getData,
44
+ ResettableTextField,
45
+ useFocus,
46
+ } from '../util';
42
47
 
43
48
  export const MaterialDateControl = (props: ControlProps)=> {
44
49
  const [focused, onFocus, onBlur] = useFocus();
@@ -68,6 +73,8 @@ export const MaterialDateControl = (props: ControlProps)=> {
68
73
  const format = appliedUiSchemaOptions.dateFormat ?? 'YYYY-MM-DD';
69
74
  const saveFormat = appliedUiSchemaOptions.dateSaveFormat ?? 'YYYY-MM-DD';
70
75
 
76
+ const views = appliedUiSchemaOptions.views ?? ['year', 'day'];
77
+
71
78
  const firstFormHelperText = showDescription
72
79
  ? description
73
80
  : !isValid
@@ -80,30 +87,40 @@ export const MaterialDateControl = (props: ControlProps)=> {
80
87
  saveFormat
81
88
  ),[path, handleChange, saveFormat]);
82
89
 
90
+ const value = getData(data, saveFormat);
91
+ const valueInInputFormat = value ? value.format(format) : '';
92
+
83
93
  return (
84
94
  <Hidden xsUp={!visible}>
85
95
  <LocalizationProvider dateAdapter={AdapterDayjs}>
86
96
  <DatePicker
87
97
  label={label}
88
- value={getData(data, saveFormat)}
98
+ value={value}
89
99
  clearable
90
100
  onChange={onChange}
91
101
  inputFormat={format}
92
102
  disableMaskedInput
93
- views={appliedUiSchemaOptions.views}
103
+ views={views}
94
104
  disabled={!enabled}
95
105
  cancelText={appliedUiSchemaOptions.cancelLabel}
96
106
  clearText={appliedUiSchemaOptions.clearLabel}
97
107
  okText={appliedUiSchemaOptions.okLabel}
98
108
  renderInput={params => (
99
- <TextField
109
+ <ResettableTextField
100
110
  {...params}
111
+ rawValue={data}
112
+ dayjsValueIsValid={value !== null}
113
+ valueInInputFormat={valueInInputFormat}
114
+ focused={focused}
101
115
  id={id + '-input'}
102
116
  required={required && !appliedUiSchemaOptions.hideRequiredAsterisk}
103
117
  autoFocus={appliedUiSchemaOptions.focus}
104
118
  error={!isValid}
105
119
  fullWidth={!appliedUiSchemaOptions.trim}
106
- inputProps={{ ...params.inputProps, type: 'text' }}
120
+ inputProps={{
121
+ ...params.inputProps,
122
+ type: 'text',
123
+ }}
107
124
  InputLabelProps={data ? { shrink: true } : undefined}
108
125
  onFocus={onFocus}
109
126
  onBlur={onBlur}
@@ -32,13 +32,18 @@ import {
32
32
  rankWith
33
33
  } from '@jsonforms/core';
34
34
  import { withJsonFormsControlProps } from '@jsonforms/react';
35
- import { FormHelperText, Hidden, TextField } from '@mui/material';
35
+ import { FormHelperText, Hidden } from '@mui/material';
36
36
  import {
37
37
  DateTimePicker,
38
38
  LocalizationProvider
39
39
  } from '@mui/lab';
40
40
  import AdapterDayjs from '@mui/lab/AdapterDayjs';
41
- import { createOnChangeHandler, getData, useFocus } from '../util';
41
+ import {
42
+ createOnChangeHandler,
43
+ getData,
44
+ ResettableTextField,
45
+ useFocus
46
+ } from '../util';
42
47
 
43
48
  export const MaterialDateTimeControl = (props: ControlProps) => {
44
49
  const [focused, onFocus, onBlur] = useFocus();
@@ -69,6 +74,8 @@ export const MaterialDateTimeControl = (props: ControlProps) => {
69
74
  const format = appliedUiSchemaOptions.dateTimeFormat ?? 'YYYY-MM-DD HH:mm';
70
75
  const saveFormat = appliedUiSchemaOptions.dateTimeSaveFormat ?? undefined;
71
76
 
77
+ const views = appliedUiSchemaOptions.views ?? ['year', 'day', 'hours', 'minutes'];
78
+
72
79
  const firstFormHelperText = showDescription
73
80
  ? description
74
81
  : !isValid
@@ -82,31 +89,41 @@ export const MaterialDateTimeControl = (props: ControlProps) => {
82
89
  saveFormat
83
90
  ),[path, handleChange, saveFormat]);
84
91
 
92
+ const value = getData(data, saveFormat);
93
+ const valueInInputFormat = value ? value.format(format) : '';
94
+
85
95
  return (
86
96
  <Hidden xsUp={!visible}>
87
97
  <LocalizationProvider dateAdapter={AdapterDayjs}>
88
98
  <DateTimePicker
89
99
  label={label}
90
- value={getData(data, saveFormat)}
100
+ value={value}
91
101
  clearable
92
102
  onChange={onChange}
93
103
  inputFormat={format}
94
104
  disableMaskedInput
95
105
  ampm={!!appliedUiSchemaOptions.ampm}
96
- views={appliedUiSchemaOptions.views}
106
+ views={views}
97
107
  disabled={!enabled}
98
108
  cancelText={appliedUiSchemaOptions.cancelLabel}
99
109
  clearText={appliedUiSchemaOptions.clearLabel}
100
110
  okText={appliedUiSchemaOptions.okLabel}
101
111
  renderInput={params => (
102
- <TextField
112
+ <ResettableTextField
103
113
  {...params}
114
+ rawValue={data}
115
+ dayjsValueIsValid={value !== null}
116
+ valueInInputFormat={valueInInputFormat}
117
+ focused={focused}
104
118
  id={id + '-input'}
105
119
  required={required && !appliedUiSchemaOptions.hideRequiredAsterisk}
106
120
  autoFocus={appliedUiSchemaOptions.focus}
107
121
  error={!isValid}
108
122
  fullWidth={!appliedUiSchemaOptions.trim}
109
- inputProps={{ ...params.inputProps, type: 'text' }}
123
+ inputProps={{
124
+ ...params.inputProps,
125
+ type: 'text',
126
+ }}
110
127
  InputLabelProps={data ? { shrink: true } : undefined}
111
128
  onFocus={onFocus}
112
129
  onBlur={onBlur}
@@ -32,13 +32,18 @@ import {
32
32
  rankWith
33
33
  } from '@jsonforms/core';
34
34
  import { withJsonFormsControlProps } from '@jsonforms/react';
35
- import { FormHelperText, Hidden, TextField } from '@mui/material';
35
+ import { FormHelperText, Hidden } from '@mui/material';
36
36
  import {
37
37
  TimePicker,
38
38
  LocalizationProvider
39
39
  } from '@mui/lab';
40
40
  import AdapterDayjs from '@mui/lab/AdapterDayjs';
41
- import { createOnChangeHandler, getData, useFocus } from '../util';
41
+ import {
42
+ createOnChangeHandler,
43
+ getData,
44
+ ResettableTextField,
45
+ useFocus
46
+ } from '../util';
42
47
 
43
48
  export const MaterialTimeControl = (props: ControlProps) => {
44
49
  const [focused, onFocus, onBlur] = useFocus();
@@ -67,7 +72,9 @@ export const MaterialTimeControl = (props: ControlProps) => {
67
72
  );
68
73
 
69
74
  const format = appliedUiSchemaOptions.timeFormat ?? 'HH:mm';
70
- const saveFormat = appliedUiSchemaOptions.timeSaveFormat ?? 'HH:mm:ss';
75
+ const saveFormat = appliedUiSchemaOptions.timeSaveFormat ?? 'HH:mm:ss';
76
+
77
+ const views = appliedUiSchemaOptions.views ?? ['hours', 'minutes'];
71
78
 
72
79
  const firstFormHelperText = showDescription
73
80
  ? description
@@ -82,31 +89,41 @@ export const MaterialTimeControl = (props: ControlProps) => {
82
89
  saveFormat
83
90
  ),[path, handleChange, saveFormat]);
84
91
 
92
+ const value = getData(data, saveFormat);
93
+ const valueInInputFormat = value ? value.format(format) : '';
94
+
85
95
  return (
86
96
  <Hidden xsUp={!visible}>
87
97
  <LocalizationProvider dateAdapter={AdapterDayjs}>
88
98
  <TimePicker
89
99
  label={label}
90
- value={getData(data, saveFormat)}
100
+ value={value}
91
101
  clearable
92
102
  onChange={onChange}
93
103
  inputFormat={format}
94
104
  disableMaskedInput
95
105
  ampm={!!appliedUiSchemaOptions.ampm}
96
- views={appliedUiSchemaOptions.views}
106
+ views={views}
97
107
  disabled={!enabled}
98
108
  cancelText={appliedUiSchemaOptions.cancelLabel}
99
109
  clearText={appliedUiSchemaOptions.clearLabel}
100
110
  okText={appliedUiSchemaOptions.okLabel}
101
111
  renderInput={params => (
102
- <TextField
112
+ <ResettableTextField
103
113
  {...params}
114
+ rawValue={data}
115
+ dayjsValueIsValid={value !== null}
116
+ valueInInputFormat={valueInInputFormat}
117
+ focused={focused}
104
118
  id={id + '-input'}
105
119
  required={required && !appliedUiSchemaOptions.hideRequiredAsterisk}
106
120
  autoFocus={appliedUiSchemaOptions.focus}
107
121
  error={!isValid}
108
122
  fullWidth={!appliedUiSchemaOptions.trim}
109
- inputProps={{ ...params.inputProps, type: 'text' }}
123
+ inputProps={{
124
+ ...params.inputProps,
125
+ type: 'text'
126
+ }}
110
127
  InputLabelProps={data ? { shrink: true } : undefined}
111
128
  onFocus={onFocus}
112
129
  onBlur={onBlur}
@@ -77,6 +77,7 @@ export const MuiAutocomplete = (props: EnumCellProps & WithClassname & WithOptio
77
77
  fullWidth
78
78
  options={options}
79
79
  getOptionLabel={getOptionLabel || (option => option?.label)}
80
+ freeSolo={false}
80
81
  style={{ marginTop: 16 }}
81
82
  renderInput={params => (
82
83
  <Input
@@ -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
+ }