react-material: date/time pickers wire `autoFocus`, `onFocus` and `onBlur` to an aria-hidden input
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- react, typescript
Research direction
Start with packages/material-renderers/MaterialDateControl.tsx, MaterialDateTimeControl.tsx, and MaterialTimeControl.tsx, then inspect createOnBlurHandler in util/datejs.tsx and the existing date, time, and date-time tests. Exercise the picker through its section spans and component props rather than the hidden input. Done means focus, blur, autofocus, and description behavior work on the accessible field, with no dead control wiring and regression coverage for the three controls.
Written by the indexing model from the issue text.
Description
Describe the bug
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.activeElementis thearia-hidden/tabindex="-1"input, not a date section. - Focusing and blurring the section spans (what a user reaches with Tab) fires neither
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 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.onFocusandonBlurfire when the user enters and leaves the field, soshowUnfocusedDescriptionbehaves 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
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 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.
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 423
- Avg merge
- 17d 8h
- Merged PRs (30d)
- 1
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from eclipsesource/jsonforms
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
eclipsesource/jsonforms#2624 ·
-
react
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
eclipsesource/jsonforms#2616 · 1 comment ·
-
core
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
eclipsesource/jsonforms#2570 · 1 comment ·
-
angular core react vue
Difficulty 4/5 3-5 days Newbie friendliness 64/100
eclipsesource/jsonforms#2627 ·
-
Difficulty 3/5 1-2 days Newbie friendliness 45/100
eclipsesource/jsonforms#2599 · 1 comment ·
All issues in eclipsesource/jsonforms
Similar issues
-
S: triage
Difficulty 1/5 Under an hour Newbie friendliness 85/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
fix(errors): EHOSTUNREACH from a happy-eyeballs connect is reported as a resolver error (STAMP-80) Open
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
snapshot-labs/stamp#666 ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
GauravKarakoti/SecureFlow#1070 · 1 comment ·
-
feature:Languages/Translations good first issue ready Web
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
digitalfabrik/integreat-app#4394 ·