Skip to content

Commit

Permalink
Fix issues via ResettableTextField
Browse files Browse the repository at this point in the history
  • Loading branch information
sdirix committed Jun 21, 2022
1 parent 8b5cbfd commit 3e01b86
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 95 deletions.
20 changes: 12 additions & 8 deletions packages/material/src/controls/MaterialDateControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
rankWith,
} from '@jsonforms/core';
import { withJsonFormsControlProps } from '@jsonforms/react';
import { FormHelperText, Hidden, TextField } from '@mui/material';
import { FormHelperText, Hidden } from '@mui/material';
import {
DatePicker,
LocalizationProvider
Expand All @@ -41,8 +41,8 @@ import AdapterDayjs from '@mui/lab/AdapterDayjs';
import {
createOnChangeHandler,
getData,
ResettableTextField,
useFocus,
useParsedDateSynchronizer,
} from '../util';

export const MaterialDateControl = (props: ControlProps)=> {
Expand Down Expand Up @@ -84,14 +84,16 @@ export const MaterialDateControl = (props: ControlProps)=> {
handleChange,
saveFormat
),[path, handleChange, saveFormat]);
const parsedDateSynchronizer = useParsedDateSynchronizer({ data, onBlur });

const value = getData(data, saveFormat);
const valueInInputFormat = value ? value.format(format) : '';

return (
<Hidden xsUp={!visible}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
label={label}
value={getData(data, saveFormat)}
value={value}
clearable
onChange={onChange}
inputFormat={format}
Expand All @@ -102,8 +104,12 @@ export const MaterialDateControl = (props: ControlProps)=> {
clearText={appliedUiSchemaOptions.clearLabel}
okText={appliedUiSchemaOptions.okLabel}
renderInput={params => (
<TextField
<ResettableTextField
{...params}
rawValue={data}
dayjsValueIsValid={value !== null}
valueInInputFormat={valueInInputFormat}
focused={focused}
id={id + '-input'}
required={required && !appliedUiSchemaOptions.hideRequiredAsterisk}
autoFocus={appliedUiSchemaOptions.focus}
Expand All @@ -112,12 +118,10 @@ export const MaterialDateControl = (props: ControlProps)=> {
inputProps={{
...params.inputProps,
type: 'text',
value: parsedDateSynchronizer.value,
onChange: parsedDateSynchronizer.createOnChangeHandler(params.inputProps.onChange),
}}
InputLabelProps={data ? { shrink: true } : undefined}
onFocus={onFocus}
onBlur={parsedDateSynchronizer.onBlur}
onBlur={onBlur}
variant={'standard'}
/>
)}
Expand Down
21 changes: 12 additions & 9 deletions packages/material/src/controls/MaterialDateTimeControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
rankWith
} from '@jsonforms/core';
import { withJsonFormsControlProps } from '@jsonforms/react';
import { FormHelperText, Hidden, TextField } from '@mui/material';
import { FormHelperText, Hidden } from '@mui/material';
import {
DateTimePicker,
LocalizationProvider
Expand All @@ -41,8 +41,8 @@ import AdapterDayjs from '@mui/lab/AdapterDayjs';
import {
createOnChangeHandler,
getData,
useFocus,
useParsedDateSynchronizer,
ResettableTextField,
useFocus
} from '../util';

export const MaterialDateTimeControl = (props: ControlProps) => {
Expand Down Expand Up @@ -87,14 +87,15 @@ export const MaterialDateTimeControl = (props: ControlProps) => {
saveFormat
),[path, handleChange, saveFormat]);

const parsedDateSynchronizer = useParsedDateSynchronizer({ data, onBlur });
const value = getData(data, saveFormat);
const valueInInputFormat = value ? value.format(format) : '';

return (
<Hidden xsUp={!visible}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DateTimePicker
label={label}
value={getData(data, saveFormat)}
value={value}
clearable
onChange={onChange}
inputFormat={format}
Expand All @@ -106,8 +107,12 @@ export const MaterialDateTimeControl = (props: ControlProps) => {
clearText={appliedUiSchemaOptions.clearLabel}
okText={appliedUiSchemaOptions.okLabel}
renderInput={params => (
<TextField
<ResettableTextField
{...params}
rawValue={data}
dayjsValueIsValid={value !== null}
valueInInputFormat={valueInInputFormat}
focused={focused}
id={id + '-input'}
required={required && !appliedUiSchemaOptions.hideRequiredAsterisk}
autoFocus={appliedUiSchemaOptions.focus}
Expand All @@ -116,12 +121,10 @@ export const MaterialDateTimeControl = (props: ControlProps) => {
inputProps={{
...params.inputProps,
type: 'text',
value: parsedDateSynchronizer.value,
onChange: parsedDateSynchronizer.createOnChangeHandler(params.inputProps.onChange),
}}
InputLabelProps={data ? { shrink: true } : undefined}
onFocus={onFocus}
onBlur={parsedDateSynchronizer.onBlur}
onBlur={onBlur}
variant={'standard'}
/>
)}
Expand Down
23 changes: 13 additions & 10 deletions packages/material/src/controls/MaterialTimeControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
rankWith
} from '@jsonforms/core';
import { withJsonFormsControlProps } from '@jsonforms/react';
import { FormHelperText, Hidden, TextField } from '@mui/material';
import { FormHelperText, Hidden } from '@mui/material';
import {
TimePicker,
LocalizationProvider
Expand All @@ -41,8 +41,8 @@ import AdapterDayjs from '@mui/lab/AdapterDayjs';
import {
createOnChangeHandler,
getData,
useFocus,
useParsedDateSynchronizer,
ResettableTextField,
useFocus
} from '../util';

export const MaterialTimeControl = (props: ControlProps) => {
Expand Down Expand Up @@ -87,14 +87,15 @@ export const MaterialTimeControl = (props: ControlProps) => {
saveFormat
),[path, handleChange, saveFormat]);

const parsedDateSynchronizer = useParsedDateSynchronizer({ data, onBlur });
const value = getData(data, saveFormat);
const valueInInputFormat = value ? value.format(format) : '';

return (
<Hidden xsUp={!visible}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<TimePicker
label={label}
value={getData(data, saveFormat)}
value={value}
clearable
onChange={onChange}
inputFormat={format}
Expand All @@ -106,22 +107,24 @@ export const MaterialTimeControl = (props: ControlProps) => {
clearText={appliedUiSchemaOptions.clearLabel}
okText={appliedUiSchemaOptions.okLabel}
renderInput={params => (
<TextField
<ResettableTextField
{...params}
rawValue={data}
dayjsValueIsValid={value !== null}
valueInInputFormat={valueInInputFormat}
focused={focused}
id={id + '-input'}
required={required && !appliedUiSchemaOptions.hideRequiredAsterisk}
autoFocus={appliedUiSchemaOptions.focus}
error={!isValid}
fullWidth={!appliedUiSchemaOptions.trim}
inputProps={{
...params.inputProps,
type: 'text',
value: parsedDateSynchronizer.value,
onChange: parsedDateSynchronizer.createOnChangeHandler(params.inputProps.onChange),
type: 'text'
}}
InputLabelProps={data ? { shrink: true } : undefined}
onFocus={onFocus}
onBlur={parsedDateSynchronizer.onBlur}
onBlur={onBlur}
variant={'standard'}
/>
)}
Expand Down
68 changes: 0 additions & 68 deletions packages/material/src/util/datejs.ts

This file was deleted.

73 changes: 73 additions & 0 deletions packages/material/src/util/datejs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { TextField, TextFieldProps } from '@mui/material';
import dayjs from 'dayjs';
import customParsing from 'dayjs/plugin/customParseFormat';
import React, { useRef} from 'react';

// required for the custom save formats in the date, time and date-time pickers
dayjs.extend(customParsing);

export const createOnChangeHandler = (
path: string,
handleChange: (path: string, value: any) => void,
saveFormat: string | undefined
) => (time: dayjs.Dayjs, textInputValue: string) => {
if (!time) {
handleChange(path, undefined);
return;
}
const result = dayjs(time).format(saveFormat);
handleChange(path, result === 'Invalid Date' ? textInputValue : result);
};

export const getData = (
data: any,
saveFormat: string | undefined
): dayjs.Dayjs | null => {
if (!data) {
return null;
}
const dayjsData = dayjs(data, saveFormat);
if (dayjsData.toString() === 'Invalid Date') {
return null;
}
return dayjsData;
};


interface InputRef {
lastInput: string;
toShow: string;
}

type ResettableTextFieldProps = TextFieldProps & {
rawValue: any;
dayjsValueIsValid: boolean;
valueInInputFormat: string;
focused: boolean;
}

/**
* The dayjs formatter/parser is very lenient and for example ignores additional digits and/or characters.
* In these cases the input text can look vastly different than the actual value stored in the data.
* The 'ResettableTextField' component adjusts the text field to reflect the actual value stored in the data
* once it's no longer 'focused', i.e. when the user stops editing.
*/
export const ResettableTextField: React.FC<ResettableTextFieldProps> = ({ rawValue, dayjsValueIsValid, valueInInputFormat, focused, inputProps, ...props }) => {
const value = useRef<InputRef>({ lastInput: inputProps?.value, toShow: inputProps?.value });
if (!focused) {
// The input text is not focused, therefore let's show the value actually stored in the data
if (!dayjsValueIsValid) {
// pass through the "raw" value in case it can't be formatted by dayjs
value.current.toShow = typeof rawValue === 'string' || rawValue === null || rawValue === undefined ? rawValue : JSON.stringify(rawValue)
} else {
// otherwise use the specified format
value.current.toShow = valueInInputFormat;
}
}
if (focused && inputProps?.value !== value.current.lastInput) {
// Show the current text the user is typing into the text input
value.current.lastInput = inputProps?.value;
value.current.toShow = inputProps?.value;
}
return <TextField {...props} inputProps={{ ...inputProps, value: value.current.toShow || '' }} />
}

0 comments on commit 3e01b86

Please sign in to comment.