diff --git a/packages/material-renderers/src/controls/MaterialAnyOfStringOrEnumControl.tsx b/packages/material-renderers/src/controls/MaterialAnyOfStringOrEnumControl.tsx index 7cb128f8b..288f84cc6 100644 --- a/packages/material-renderers/src/controls/MaterialAnyOfStringOrEnumControl.tsx +++ b/packages/material-renderers/src/controls/MaterialAnyOfStringOrEnumControl.tsx @@ -38,7 +38,12 @@ import { Control, withJsonFormsControlProps } from '@jsonforms/react'; import { InputBaseComponentProps } from '@mui/material'; import merge from 'lodash/merge'; import React, { useMemo } from 'react'; -import { useDebouncedChange, useInputComponent, WithInputProps } from '../util'; +import { + useDebouncedChange, + useInputComponent, + WithInputProps, + useFocus, +} from '../util'; import { MaterialInputControl } from './MaterialInputControl'; const findEnumSchema = (schemas: JsonSchema[]) => @@ -51,6 +56,7 @@ const findTextSchema = (schemas: JsonSchema[]) => const MuiAutocompleteInputText = ( props: EnumCellProps & WithClassname & WithInputProps ) => { + const [focused, onFocus, onBlur] = useFocus(); const { data, config, @@ -83,12 +89,13 @@ const MuiAutocompleteInputText = ( propMemo.list = props.id + 'datalist'; return propMemo; }, [appliedUiSchemaOptions, props.id]); - const [inputText, onChange] = useDebouncedChange( + const [inputText, onChange] = useDebouncedChange({ handleChange, - '', data, - path - ); + path, + focused, + flushOnBlur: true, + }); const dataList = ( @@ -102,6 +109,8 @@ const MuiAutocompleteInputText = ( type='text' value={inputText} onChange={onChange} + onFocus={onFocus} + onBlur={onBlur} className={className} id={id} label={label} diff --git a/packages/material-renderers/src/controls/MaterialNativeControl.tsx b/packages/material-renderers/src/controls/MaterialNativeControl.tsx index 358d45f71..b538a7e5e 100644 --- a/packages/material-renderers/src/controls/MaterialNativeControl.tsx +++ b/packages/material-renderers/src/controls/MaterialNativeControl.tsx @@ -1,19 +1,19 @@ /* The MIT License - + Copyright (c) 2017-2019 EclipseSource Munich https://github.com/eclipsesource/jsonforms - + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -56,12 +56,11 @@ export const MaterialNativeControl = (props: ControlProps) => { } = props; const isValid = errors.length === 0; const appliedUiSchemaOptions = merge({}, config, props.uischema.options); - const [inputValue, onChange] = useDebouncedChange( + const [inputValue, onChange] = useDebouncedChange({ handleChange, - '', data, - path - ); + path, + }); const fieldType = appliedUiSchemaOptions.format ?? schema.format; const showDescription = !isDescriptionHidden( visible, diff --git a/packages/material-renderers/src/mui-controls/MuiInputInteger.tsx b/packages/material-renderers/src/mui-controls/MuiInputInteger.tsx index 4d2f8f376..2fc2db81d 100644 --- a/packages/material-renderers/src/mui-controls/MuiInputInteger.tsx +++ b/packages/material-renderers/src/mui-controls/MuiInputInteger.tsx @@ -25,7 +25,12 @@ import React from 'react'; import { CellProps, WithClassname } from '@jsonforms/core'; import merge from 'lodash/merge'; -import { useDebouncedChange, useInputComponent, WithInputProps } from '../util'; +import { + useDebouncedChange, + useInputComponent, + WithInputProps, + useFocus, +} from '../util'; const toNumber = (value: string) => value === '' ? undefined : parseInt(value, 10); @@ -34,6 +39,7 @@ const eventToValue = (ev: any) => toNumber(ev.target.value); export const MuiInputInteger = React.memo(function MuiInputInteger( props: CellProps & WithClassname & WithInputProps ) { + const [focused, onFocus, onBlur] = useFocus(); const { data, className, @@ -51,19 +57,22 @@ export const MuiInputInteger = React.memo(function MuiInputInteger( const appliedUiSchemaOptions = merge({}, config, uischema.options); - const [inputValue, onChange] = useDebouncedChange( + const [inputValue, onChange] = useDebouncedChange({ handleChange, - '', data, path, - eventToValue - ); + eventToValue, + focused, + flushOnBlur: true, + }); return ( value === '' ? undefined : parseFloat(value); @@ -33,6 +38,7 @@ const eventToValue = (ev: any) => toNumber(ev.target.value); export const MuiInputNumber = React.memo(function MuiInputNumber( props: CellProps & WithClassname & WithInputProps ) { + const [focused, onFocus, onBlur] = useFocus(); const { data, className, @@ -49,13 +55,14 @@ export const MuiInputNumber = React.memo(function MuiInputNumber( const inputProps = { step: '0.1' }; const appliedUiSchemaOptions = merge({}, config, uischema.options); - const [inputValue, onChange] = useDebouncedChange( + const [inputValue, onChange] = useDebouncedChange({ handleChange, - '', data, path, - eventToValue - ); + eventToValue, + focused, + flushOnBlur: true, + }); return ( & WithInputProps ) { + const [focused, onFocus, onBlur] = useFocus(); const { className, id, @@ -57,19 +63,22 @@ export const MuiInputNumberFormat = React.memo(function MuiInputNumberFormat( (ev: any) => props.fromFormatted(ev.currentTarget.value), [props.fromFormatted] ); - const [inputValue, onChange] = useDebouncedChange( + const [inputValue, onChange] = useDebouncedChange({ handleChange, - '', - formattedNumber, + data: formattedNumber, path, - validStringNumber - ); + eventToValue: validStringNumber, + focused, + flushOnBlur: true, + }); return ( export const MuiInputText = React.memo(function MuiInputText( props: CellProps & WithClassname & MuiTextInputProps & WithInputProps ) { + const [focused, onFocus, onBlur] = useFocus(); const [showAdornment, setShowAdornment] = useState(false); const { data, @@ -83,13 +85,14 @@ export const MuiInputText = React.memo(function MuiInputText( inputProps.size = maxLength; } - const [inputText, onChange, onClear] = useDebouncedChange( + const [inputText, onChange, onClear] = useDebouncedChange({ handleChange, - '', data, path, - eventToValue - ); + eventToValue, + flushOnBlur: true, + focused, + }); const onPointerEnter = () => setShowAdornment(true); const onPointerLeave = () => setShowAdornment(false); @@ -109,6 +112,8 @@ export const MuiInputText = React.memo(function MuiInputText( value={inputText} onChange={onChange} className={className} + onBlur={onBlur} + onFocus={onFocus} id={id} disabled={!enabled} autoFocus={appliedUiSchemaOptions.focus} diff --git a/packages/material-renderers/src/mui-controls/MuiInputTime.tsx b/packages/material-renderers/src/mui-controls/MuiInputTime.tsx index 0a7c1da8e..a5b4483ca 100644 --- a/packages/material-renderers/src/mui-controls/MuiInputTime.tsx +++ b/packages/material-renderers/src/mui-controls/MuiInputTime.tsx @@ -44,12 +44,11 @@ export const MuiInputTime = React.memo(function MuiInputTime( } = props; const InputComponent = useInputComponent(); const appliedUiSchemaOptions = merge({}, config, uischema.options); - const [inputValue, onChange] = useDebouncedChange( + const [inputValue, onChange] = useDebouncedChange({ handleChange, - '', data, - path - ); + path, + }); return ( ev.target.value; -export const useDebouncedChange = ( - handleChange: (path: string, value: any) => void, - defaultValue: any, - data: any, - path: string, - eventToValueFunction: (ev: any) => any = eventToValue, - timeout = 300 -): [any, React.ChangeEventHandler, () => void] => { +const defaultEventToValue = (ev: any) => ev.target.value; +interface DebouncedChangeParams { + handleChange: (path: string, value: any) => void; + data: any; + path: string; + eventToValue?: (ev: any) => any; + defaultValue?: any; + flushOnBlur?: boolean; + focused?: boolean; + timeout?: number; +} +export const useDebouncedChange = ({ + handleChange, + data, + path, + eventToValue = undefined, + defaultValue = '', + flushOnBlur = false, + focused = false, + timeout = 300, +}: DebouncedChangeParams): [any, React.ChangeEventHandler, () => void] => { const [input, setInput] = useState(data ?? defaultValue); useEffect(() => { setInput(data ?? defaultValue); @@ -42,13 +54,20 @@ export const useDebouncedChange = ( debounce((newValue: string) => handleChange(path, newValue), timeout), [handleChange, path, timeout] ); + useEffect(() => { + if (!focused && flushOnBlur) { + debouncedUpdate.flush(); + } + }, [focused, flushOnBlur, debouncedUpdate]); const onChange = useCallback( (ev: any) => { - const newValue = eventToValueFunction(ev); + const newValue = eventToValue + ? eventToValue(ev) + : defaultEventToValue(ev); setInput(newValue ?? defaultValue); debouncedUpdate(newValue); }, - [debouncedUpdate, eventToValueFunction] + [debouncedUpdate, eventToValue] ); const onClear = useCallback(() => { setInput(defaultValue);