From 63f8d151dc5da375d7f8f1c82c52dbe0d2cdeff6 Mon Sep 17 00:00:00 2001 From: Siarhei_Dzeraviannik Date: Mon, 26 May 2025 15:09:46 +0300 Subject: [PATCH 1/2] [DatePicker]: improved focus retention, input and body synchronization --- changelog.md | 2 +- uui/components/datePickers/DatePicker.tsx | 49 +++++++++++++++++------ 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/changelog.md b/changelog.md index 034c078178..f85cb63fc3 100644 --- a/changelog.md +++ b/changelog.md @@ -1,7 +1,7 @@ # 6.x.x - xx.xx.2025 **What's New** * [RangeDatePicker]: improved a11y, updated so that when a date is typed in the input fields, the calendar body immediately reflects and selects the new value, providing instant feedback and better usability. -* [DatePicker]: improved a11y. +* [DatePicker]: improved a11y, updated so that when a date is typed in the input fields, the calendar body immediately reflects and selects the new value, providing instant feedback and better usability. * [@epam/uui-test-utils]: added global mock for "getBoundingClientRect" to "setupJsDom" **What's Fixed** diff --git a/uui/components/datePickers/DatePicker.tsx b/uui/components/datePickers/DatePicker.tsx index 8864034eec..556aea2667 100644 --- a/uui/components/datePickers/DatePicker.tsx +++ b/uui/components/datePickers/DatePicker.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useImperativeHandle, useMemo, useState } from 'react'; import { offset } from '@floating-ui/react'; import { DatePickerProps as CoreDatePickerProps, DropdownBodyProps, cx, useUuiContext, uuiMod, IDropdownTogglerProps, Overwrite, @@ -35,6 +35,29 @@ export function DatePickerComponent(props: DatePickerProps, ref: React.Forwarded const context = useUuiContext(); const [inputValue, setInputValue] = useState(toCustomDateFormat(value, format)); const [isBodyOpen, setBodyIsOpen] = useState(false); + const targetRef = React.useRef(null); + + useImperativeHandle(ref, () => targetRef.current); + + const onOpenChange = (isOpen: boolean) => { + setBodyIsOpen(isOpen); + if (isOpen && targetRef.current) { + const inputElement = targetRef.current.querySelector('.uui-input'); + inputElement?.focus(); + } + }; + + const onInputChange = (newValue: string | null) => { + if (isValidDate(newValue, format, props.filter)) { + const newDateValue = toValueDateFormat(newValue, format); + if (value !== newDateValue) { + setInputValue(toCustomDateFormat(newDateValue, format)); + onValueChange(newDateValue); + } + } else { + setInputValue(newValue || ''); + } + }; useEffect(() => { setInputValue(toCustomDateFormat(value, format)); @@ -54,7 +77,7 @@ export function DatePickerComponent(props: DatePickerProps, ref: React.Forwarded const onBodyValueChange = (newValue: string | null) => { setInputValue(toCustomDateFormat(newValue, format)); onValueChange(newValue); - setBodyIsOpen(false); + onOpenChange(false); }; const onBlur = (e: React.FocusEvent) => { @@ -72,7 +95,7 @@ export function DatePickerComponent(props: DatePickerProps, ref: React.Forwarded const onInputKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { - setBodyIsOpen(true); + onOpenChange(true); e.preventDefault(); } }; @@ -89,9 +112,7 @@ export function DatePickerComponent(props: DatePickerProps, ref: React.Forwarded placeholder={ props.placeholder ? props.placeholder : format } size={ size as TextInputProps['size'] } value={ inputValue || undefined } - onValueChange={ (v) => { - setInputValue(v || ''); - } } + onValueChange={ onInputChange } onCancel={ allowClear ? () => { if (!props.disableClear && !!inputValue) { onValueChange(null); @@ -109,13 +130,19 @@ export function DatePickerComponent(props: DatePickerProps, ref: React.Forwarded mode={ props.mode || defaultMode } rawProps={ props.rawProps?.input } id={ props.id } + ref={ (node) => { (renderProps as any).ref(node); targetRef.current = node; } } + onClick={ () => renderProps.toggleDropdownOpening(true) } /> ); }; - const renderBody = useMemo(() => (renderProps: DropdownBodyProps) => { + const renderBody = useMemo(() => function (renderProps: DropdownBodyProps) { return ( - + onOpenChange(v) } middleware={ [offset(6)] } placement={ props.placement } - ref={ ref } - onValueChange={ (v) => { - setBodyIsOpen(v); - } } renderTarget={ (renderProps) => { return props.renderTarget?.(renderProps) || renderInput(renderProps); } } From caed0e6d95fcb446f118f0d471fc4fce7783f72e Mon Sep 17 00:00:00 2001 From: Siarhei_Dzeraviannik Date: Wed, 28 May 2025 14:10:17 +0300 Subject: [PATCH 2/2] [DatePicker]: refactored "onOpenChange" method --- uui/components/datePickers/DatePicker.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/uui/components/datePickers/DatePicker.tsx b/uui/components/datePickers/DatePicker.tsx index 556aea2667..3c9f6d5494 100644 --- a/uui/components/datePickers/DatePicker.tsx +++ b/uui/components/datePickers/DatePicker.tsx @@ -41,10 +41,6 @@ export function DatePickerComponent(props: DatePickerProps, ref: React.Forwarded const onOpenChange = (isOpen: boolean) => { setBodyIsOpen(isOpen); - if (isOpen && targetRef.current) { - const inputElement = targetRef.current.querySelector('.uui-input'); - inputElement?.focus(); - } }; const onInputChange = (newValue: string | null) => { @@ -137,11 +133,13 @@ export function DatePickerComponent(props: DatePickerProps, ref: React.Forwarded }; const renderBody = useMemo(() => function (renderProps: DropdownBodyProps) { + const shards = targetRef.current ? [targetRef.current] : []; return (