-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: ensure date/time segments are ordered correctly in RTL #7423
base: main
Are you sure you want to change the base?
Conversation
Hey, Thanks for working on this PR Is this also going to fix keyboard focus management on RTL too? For example
|
@sadeghbarati Yes, the plan is to also correct the keyboard navigation for RTL as well. The PR is still in the early stages so there's a lot that has yet to be implemented. |
@@ -181,6 +181,9 @@ export function useDateField<T extends DateValue>(props: AriaDateFieldOptions<T> | |||
if (props.onKeyUp) { | |||
props.onKeyUp(e); | |||
} | |||
}, | |||
style: { | |||
unicodeBidi: 'isolate' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added for DateRangePicker because things were looking a little funky without it. wraps around each datefield. see codepen for reproduction
i could update this so that it only applies in rtl locales but it also doesn't seem to have any affect for ltr locales.
@@ -170,6 +173,7 @@ function PopoverInner({state, isExiting, UNSTABLE_portalContainer, ...props}: Po | |||
ref={ref} | |||
slot={props.slot || undefined} | |||
style={style} | |||
dir={props.dir} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
popovers are portaled outside which means they don't get the direction of the locale causing the keyboard navigation to behave incorrectly for rac datepickers in rtl. in v3, we wrap the popovers in a provider so this doesn't happen. happy to change the implementation if there's a better way to do this
@@ -104,3 +129,19 @@ export function useDatePickerGroup(state: DatePickerState | DateRangePickerState | |||
|
|||
return mergeProps(pressProps, {onKeyDown}); | |||
} | |||
|
|||
function orderSegments(editableSegments: NodeListOf<Element> | undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adjacent segments might not necessarily be adjacent in the DOM. therefore, we can't just use focusPrevious and focusNext. instead, calculate their positions and use those to determine which segment to move to
@@ -9,6 +9,8 @@ import {usePress} from '@react-aria/interactions'; | |||
export function useDatePickerGroup(state: DatePickerState | DateRangePickerState | DateFieldState, ref: RefObject<Element | null>, disableArrowNavigation?: boolean) { | |||
let {direction} = useLocale(); | |||
let focusManager = useMemo(() => createFocusManager(ref), [ref]); | |||
let editableSegments: NodeListOf<Element> | undefined = ref.current?.querySelectorAll('span[role="spinbutton"], span[role="textbox"]'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this needs to be done in a useLayoutEffect and the result should be stored in a ref as other parts of state will determine if the effects needs to update
let segmentArr = segments.map(node => { | ||
return { | ||
element: node as FocusableElement, | ||
rectX: node?.getBoundingClientRect().left |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you run segments.filter(Boolean).map..., you may not need this ?
might need to do it the line above so you can set the type correctly
let segments = Array.from(editableSegments).filter...
@@ -31,7 +33,21 @@ export function useDatePickerGroup(state: DatePickerState | DateRangePickerState | |||
e.preventDefault(); | |||
e.stopPropagation(); | |||
if (direction === 'rtl') { | |||
focusManager.focusNext(); | |||
if (orderedSegments) { | |||
let button = ref.current?.querySelector('button'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it possible someone could add another button to the Group? such as in RAC?
If so, it's be best to find the element by it's id
if we can get that or some other more unique attribute
@@ -664,4 +664,75 @@ describe('DateField', function () { | |||
}); | |||
}); | |||
}); | |||
|
|||
describe('style', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think all of these tests can be covered by chromatic
these unit tests are likely to break in unhelpful ways, as a change to the styles could result in the correct rendering and behavior, but break here
@@ -2334,4 +2334,70 @@ describe('DatePicker', function () { | |||
}); | |||
}); | |||
}); | |||
|
|||
describe('style', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in general, testing styles in a unit test is not helpful since it doesn't actually render, these are all perfect for chromatic though
let el = this; | ||
|
||
if (el.getAttribute('role') === 'spinbutton') { | ||
if (el.parentElement.getAttribute('data-testid') === 'end-date') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i thought the issue with some of these was that the dom order was not in alignment with the rendered order?
this test is a good base, but should we include some tests where the order differs? I think you could use el.getAttribute('data-type')
to order based on the day/month/hour/etc
@@ -92,11 +92,11 @@ export const DateRangePickerExample = () => ( | |||
<Label style={{display: 'block'}}>Date</Label> | |||
<Group style={{display: 'inline-flex'}}> | |||
<div className={styles.field}> | |||
<DateInput data-testid="date-range-picker-date-input" slot="start" style={{display: 'inline-flex'}}> | |||
<DateInput data-testid="date-range-picker-date-input" slot="start" style={{display: 'inline'}}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sort of a breaking change
what will happen to people who have used this as an example to build their DatePicker's in the past?
Also, why is the style change different from DateField.mdx?
Close #4711
Need to double check that nothing broke in #6562 with these changes.
✅ Pull Request Checklist:
📝 Test Instructions:
Arabic should render in the following order:
TimeZone DayPeriod Hour:Minute Year/Month/Day
Hebrew should render in the following order:
TimeZone DayPeriod Hour:Minute Day/Month/Year
Tab order should be:
Day -> Month -> Year -> Hour -> Minute -> DayPeriod -> TimeZone
Arabic Placeholders:
يوم / شهر / سنة
Hebrew Placeholders:
שנה.חודש.יום
Also be sure to test keyboard navigation
🧢 Your Project: