Skip to content

useDateSegment: typing in a sibling input steals focus into a DateField segment (Firefox) #10259

Description

@doolse

Provide a general summary of the issue here

In Firefox, typing into a regular <input> that is a sibling of a DateField/DatePicker can move focus into one of the date segments mid-keystroke. Subsequent characters are then captured by that segment (e.g. typed digits land in the year segment) instead of the input the user was actually typing in.

🤔 Expected Behavior?

Typing in an unrelated input never moves focus into a date segment. The date field only reacts to input while one of its own segments is focused.

😯 Current Behavior

Focus jumps from the input into a date segment (commonly the last/year segment), and the keystrokes are consumed there.

Root cause is the document-level selectionchange listener in useDateSegment (packages/react-aria/src/datepicker/useDateSegment.ts):

useEvent(documentRef, 'selectionchange', () => {
  let selection = window.getSelection();
  if (selection?.anchorNode && nodeContains(ref.current, selection?.anchorNode)) {
    selection.collapse(ref.current);
  }
});

Two Firefox-specific behaviors combine:

  1. In Firefox the caret/selection inside an <input>/<textarea> is not reflected in the document window.getSelection(); the document selection stays wherever it last was in regular DOM content — which, after the segments render/are interacted with, is inside a date segment. So nodeContains(ref.current, selection.anchorNode) is still true even though focus is in the input.
  2. Typing in the input still fires selectionchange, so the handler runs and calls selection.collapse(ref.current). In Firefox, collapsing the selection onto a contentEditable node moves focus to it (Chromium does not).

Result: focus is pulled into the segment while the user types elsewhere. The handler currently re-collapses the selection regardless of whether the segment is the active element — but its purpose (per the existing comment) is only to keep the selection collapsed within a focused segment during Android-Chrome composition.

Note: user-select: none on the segment container (the workaround suggested for the click-focus issues #3163 / #3164) does not help here, because the focus move comes from a programmatic Selection.collapse(), not a user selection.

💁 Possible Solution

Only re-collapse when the segment is actually the active element:

if (
  selection?.anchorNode &&
  nodeContains(ref.current, selection?.anchorNode) &&
  getActiveElement() === ref.current
) {
  selection.collapse(ref.current);
}

This preserves the Android-Chrome composition fix (which only matters while the segment is focused) and eliminates the cross-element focus steal. Happy to open a PR (with a browser regression test) — see below.

🔦 Context

Hit in a production form where a registration-number text input sits next to a date field; Firefox users couldn't type in the text field because focus kept jumping to the year segment.

💻 Code Sample

import {useRef} from 'react';
import {useDateFieldState} from 'react-stately';
import {useDateField, useDateSegment, useLocale} from 'react-aria';
import {createCalendar} from '@internationalized/date';

function Segment({segment, state}) {
  let ref = useRef(null);
  let {segmentProps} = useDateSegment(segment, state, ref);
  return <div {...segmentProps} ref={ref}>{segment.text}</div>;
}

function Field(props) {
  let {locale} = useLocale();
  let state = useDateFieldState({...props, locale, createCalendar});
  let ref = useRef(null);
  let {fieldProps} = useDateField(props, state, ref);
  return (
    <div {...fieldProps} ref={ref} style={{display: 'flex'}}>
      {state.segments.map((s, i) => <Segment key={i} segment={s} state={state} />)}
    </div>
  );
}

export default function App() {
  return (
    <div>
      <input aria-label="text" placeholder="type digits here" />
      <Field aria-label="date" />
    </div>
  );
}

Steps (Firefox): click into a date segment once (or just let the field render), click into the <input>, then type digits → focus jumps into a segment and the digits are captured there.

🌍 Your Environment

Software Version(s)
react-aria / @react-aria/datepicker Reproduced on @react-aria/datepicker@3.16.0; the selectionchange/collapse code is unchanged on main.
Browser Firefox (latest). Not reproducible in Chromium/WebKit.
Operating System Linux / Windows / macOS

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions