Describe the bug
NOTE: The issue only becomes relevant after #2569
NOTE: The same issue was already present when JSON Forms still used MUI v7 and MUI x-date-picker v8.
The React Material date, time and date-time controls pass autoFocus, onFocus and onBlur through slotProps.textField.inputProps, which targets the picker's hidden mirror <input> rather than the field the user interacts with. As a result options.focus and showUnfocusedDescription do not work on these controls, and createOnBlurHandler never runs.
Since @mui/x-date-pickers v8, enableAccessibleFieldDOMStructure defaults to true, so DatePicker renders the accessible field DOM: one contenteditable <span role="spinbutton"> per date section, plus a single <input aria-hidden="true" tabindex="-1"> that only mirrors the value.
<div class="MuiPickersInputBase-root" role="group">
<div class="MuiPickersSectionList-root" tabindex="0">
<span role="spinbutton" contenteditable="true" aria-label="Year">1980</span>
<span role="spinbutton" contenteditable="true" aria-label="Month">06</span>
<span role="spinbutton" contenteditable="true" aria-label="Day">04</span>
</div>
<input
class="MuiPickersInputBase-input"
aria-hidden="true"
tabindex="-1"
value="1980-06-04"
/>
</div>
Rendering MaterialDateControl and driving it with real DOM focus() / blur() on the section spans shows:
- With
options: { focus: true }, document.activeElement is the aria-hidden / tabindex="-1" input, not a date section.
- Focusing and blurring the section spans (what a user reaches with Tab) fires neither
onFocus nor onBlur. The hidden input is a sibling of the sections container, not an ancestor, so focus events never bubble through it.
Concretely this breaks three things:
options.focus does not focus date, time or date-time controls.
showUnfocusedDescription never takes effect, because onFocus never fires and the control stays unfocused as far as isDescriptionHidden is concerned.
createOnBlurHandler is dead code, and so is the key / updateChild remount it drives. The behavior it implemented, clearing the data and resetting the visible field when the typed text does not parse, no longer happens.
Data entry itself still works: field edits reach setValue with the default changeImportance: 'accept', so the picker's onAccept still fires and createOnChangeHandler still commits. Only the focus, description and invalid-input-reset behaviors are affected.
The existing tests do not catch any of this because they locate the hidden input with find('input') and then assert React props on it or simulate events directly on it, so they pass whether or not the wiring reaches the real field.
Expected behavior
options: { focus: true } puts the caret on the first editable date section, the way it focuses the input of a plain text control.
onFocus and onBlur fire when the user enters and leaves the field, so showUnfocusedDescription behaves the same as on other controls.
- No dead focus/blur wiring is left pointing at an element the user cannot reach.
Steps to reproduce the issue
- Start the React Material example app (
packages/material-renderers, pnpm run dev).
- Pick an example containing a date control, or use this UI schema against a
{ "type": "string", "format": "date" } property:
{
"type": "Control",
"scope": "#/properties/birthDate",
"options": { "focus": true, "showUnfocusedDescription": false }
}
- Reload the form. The date field does not receive the caret: no date section is highlighted and typing goes nowhere until you click the field.
- Click into the date field. The control's description does not appear on focus, and it does not disappear again when you Tab away.
- Inspect the field in the browser devtools. The focusable elements are the
MuiPickersSectionList spans, while the <input> carrying the JSON Forms autoFocus / onFocus / onBlur is aria-hidden="true" with tabindex="-1".
Screenshots
No response
Which Version of JSON Forms are you using?
v3.9.0-alpha.0 / v3.9.0-alpha.1
Package
React Material Renderers
Additional context
Use the picker's own autoFocus prop, which resolves to pickerContext.autoFocus && !pickerContext.open and focuses the first editable section, and put onFocus / onBlur on the textField slot itself, where PickersTextField forwards them to the field root. Both were confirmed to reach the section spans. The htmlInput props then only carry type: 'text'.
createOnBlurHandler reads e.target.value, which does not exist on the sections container, so it cannot be rehomed and should be dropped from the three controls. It is exported from the package, so keep and deprecate the util itself rather than deleting it.
Changes for MaterialDateControl.tsx (apply the same to MaterialDateTimeControl.tsx and MaterialTimeControl.tsx)
-import React, { useCallback, useMemo, useState } from 'react';
+import React, { useMemo, useState } from 'react';
...
import {
- createOnBlurHandler,
createOnChangeHandler,
getData,
useFocus,
useInputVariant,
} from '../util';
@@
- const [key, setKey] = useState<number>(0);
const [open, setOpen] = useState<boolean>(false);
@@
- const updateChild = useCallback(() => setKey((key) => key + 1), []);
-
const onChange = useMemo(
() => createOnChangeHandler(path, handleChange, saveFormat),
[path, handleChange, saveFormat]
);
-
- const onBlurHandler = useMemo(
- () =>
- createOnBlurHandler(
- path,
- handleChange,
- format,
- saveFormat,
- updateChild,
- onBlur
- ),
- [path, handleChange, format, saveFormat, updateChild, onBlur]
- );
@@
<DatePicker
open={open}
onOpen={() => setOpen(true)}
onClose={() => setOpen(false)}
- key={key}
label={label}
value={value}
onAccept={onChange}
format={format}
views={views}
disabled={!enabled}
closeOnSelect={closeOnSelect}
+ autoFocus={appliedUiSchemaOptions.focus}
slotProps={{
actionBar: ...,
textField: {
id: id + '-input',
required: required && !appliedUiSchemaOptions.hideRequiredAsterisk,
error: !isValid,
fullWidth: !appliedUiSchemaOptions.trim,
variant: inputVariant,
- inputProps: {
- autoFocus: appliedUiSchemaOptions.focus,
- type: 'text',
- onFocus: onFocus,
- onBlur: onBlurHandler,
- },
+ onFocus: onFocus,
+ onBlur: onBlur,
+ inputProps: { type: 'text' },
InputLabelProps: data ? { shrink: true } : undefined,
},
}}
/>
After #2614 the last two prop lines become slotProps: { htmlInput: { type: 'text' }, inputLabel: data ? { shrink: true } : undefined }. onFocus / onBlur stay at the textField slot level and autoFocus stays on the picker either way.
Deprecate createOnBlurHandler in util/datejs.tsx
+/**
+ * @deprecated Since @mui/x-date-pickers v8 the accessible field DOM structure is the
+ * default, so blur events on the field no longer carry input values and this handler
+ * can no longer be attached to anything the user can reach. Rely on the picker's
+ * onChange/onAccept callbacks instead.
+ */
export const createOnBlurHandler =
(
path: string,
If the invalid-input reset from point 3 is still wanted, it should hook the picker's validation error rather than a blur event, since the field no longer surfaces unparseable text to the outside.
The date, time and date-time tests should be reworked to drive the picker through its component props or the section spans instead of the hidden input, so a regression here fails the build.
Describe the bug
NOTE: The issue only becomes relevant after #2569
NOTE: The same issue was already present when JSON Forms still used MUI v7 and MUI x-date-picker v8.
The React Material date, time and date-time controls pass
autoFocus,onFocusandonBlurthroughslotProps.textField.inputProps, which targets the picker's hidden mirror<input>rather than the field the user interacts with. As a resultoptions.focusandshowUnfocusedDescriptiondo not work on these controls, andcreateOnBlurHandlernever runs.Since
@mui/x-date-pickersv8,enableAccessibleFieldDOMStructuredefaults totrue, soDatePickerrenders the accessible field DOM: one contenteditable<span role="spinbutton">per date section, plus a single<input aria-hidden="true" tabindex="-1">that only mirrors the value.Rendering
MaterialDateControland driving it with real DOMfocus()/blur()on the section spans shows:options: { focus: true },document.activeElementis thearia-hidden/tabindex="-1"input, not a date section.onFocusnoronBlur. The hidden input is a sibling of the sections container, not an ancestor, so focus events never bubble through it.Concretely this breaks three things:
options.focusdoes not focus date, time or date-time controls.showUnfocusedDescriptionnever takes effect, becauseonFocusnever fires and the control stays unfocused as far asisDescriptionHiddenis concerned.createOnBlurHandleris dead code, and so is thekey/updateChildremount it drives. The behavior it implemented, clearing the data and resetting the visible field when the typed text does not parse, no longer happens.Data entry itself still works: field edits reach
setValuewith the defaultchangeImportance: 'accept', so the picker'sonAcceptstill fires andcreateOnChangeHandlerstill commits. Only the focus, description and invalid-input-reset behaviors are affected.The existing tests do not catch any of this because they locate the hidden input with
find('input')and then assert React props on it or simulate events directly on it, so they pass whether or not the wiring reaches the real field.Expected behavior
options: { focus: true }puts the caret on the first editable date section, the way it focuses the input of a plain text control.onFocusandonBlurfire when the user enters and leaves the field, soshowUnfocusedDescriptionbehaves the same as on other controls.Steps to reproduce the issue
packages/material-renderers,pnpm run dev).{ "type": "string", "format": "date" }property:{ "type": "Control", "scope": "#/properties/birthDate", "options": { "focus": true, "showUnfocusedDescription": false } }MuiPickersSectionListspans, while the<input>carrying the JSON FormsautoFocus/onFocus/onBlurisaria-hidden="true"withtabindex="-1".Screenshots
No response
Which Version of JSON Forms are you using?
v3.9.0-alpha.0 / v3.9.0-alpha.1
Package
React Material Renderers
Additional context
Use the picker's own
autoFocusprop, which resolves topickerContext.autoFocus && !pickerContext.openand focuses the first editable section, and putonFocus/onBluron thetextFieldslot itself, wherePickersTextFieldforwards them to the field root. Both were confirmed to reach the section spans. ThehtmlInputprops then only carrytype: 'text'.createOnBlurHandlerreadse.target.value, which does not exist on the sections container, so it cannot be rehomed and should be dropped from the three controls. It is exported from the package, so keep and deprecate the util itself rather than deleting it.Changes for
MaterialDateControl.tsx(apply the same toMaterialDateTimeControl.tsxandMaterialTimeControl.tsx)After #2614 the last two prop lines become
slotProps: { htmlInput: { type: 'text' }, inputLabel: data ? { shrink: true } : undefined }.onFocus/onBlurstay at thetextFieldslot level andautoFocusstays on the picker either way.Deprecate
createOnBlurHandlerinutil/datejs.tsxIf the invalid-input reset from point 3 is still wanted, it should hook the picker's validation error rather than a blur event, since the field no longer surfaces unparseable text to the outside.
The date, time and date-time tests should be reworked to drive the picker through its component props or the section spans instead of the hidden input, so a regression here fails the build.