diff --git a/packages/@react-aria/datepicker/src/useDateSegment.ts b/packages/@react-aria/datepicker/src/useDateSegment.ts index 4a193ac3a02..36d64372322 100644 --- a/packages/@react-aria/datepicker/src/useDateSegment.ts +++ b/packages/@react-aria/datepicker/src/useDateSegment.ts @@ -388,9 +388,14 @@ export function useDateSegment(segment: DateSegment, state: DateFieldState, ref: let dateSegments = ['day', 'month', 'year']; let segmentStyle : CSSProperties = {caretColor: 'transparent'}; if (direction === 'rtl') { + // While the bidirectional algorithm seems to work properly on inline elements with actual values, it returns different results for placeholder strings. + // To ensure placeholder render in correct format, we apply the CSS equivalent of LRE (left-to-right embedding). See https://www.unicode.org/reports/tr9/#Explicit_Directional_Embeddings. + // However, we apply this to both placeholders and date segments with an actual value because the date segments will shift around when deleting otherwise. if (dateSegments.includes(segment.type)) { segmentStyle = {caretColor: 'transparent', direction: 'ltr', unicodeBidi: 'embed'}; } else if (segment.type === 'timeZoneName') { + // This is needed so that the time zone renders on the left side of the time segments (hour:minute). + // Otherwise, it will render on the right side which is incorrect. segmentStyle = {caretColor: 'transparent', unicodeBidi: 'embed'}; } } diff --git a/packages/@react-spectrum/datepicker/src/utils.tsx b/packages/@react-spectrum/datepicker/src/utils.tsx index 12779ef01ce..1e06a46bbad 100644 --- a/packages/@react-spectrum/datepicker/src/utils.tsx +++ b/packages/@react-spectrum/datepicker/src/utils.tsx @@ -31,7 +31,7 @@ export function useFormatHelpText(props: Pick, 'desc return ( formatter.formatToParts(new Date()).map((s, i) => { if (s.type === 'literal') { - return {s.value}; + return {` ${s.value} `}; } return {displayNames.of(s.type)}; diff --git a/packages/@react-stately/datepicker/src/useDateFieldState.ts b/packages/@react-stately/datepicker/src/useDateFieldState.ts index 1c9660fc612..864c0bf21f3 100644 --- a/packages/@react-stately/datepicker/src/useDateFieldState.ts +++ b/packages/@react-stately/datepicker/src/useDateFieldState.ts @@ -430,7 +430,11 @@ function processSegments(dateValue, validSegments, dateFormatter, resolvedOption isEditable } as DateSegment; + // There is an issue in RTL languages where time fields render (minute:hour) instead of (hour:minute). + // To force an LTR direction on the time field since, we wrap the time segments in LRI (left-to-right) isolate unicode. See https://www.w3.org/International/questions/qa-bidi-unicode-controls. + // These unicode characters will be added to the array of processed segments as literals and will mark the start and end of the embedded direction change. if (segment.type === 'hour') { + // This marks the start of the embedded direction change. processedSegments.push({ type: 'literal', text: '\u2066', @@ -440,6 +444,7 @@ function processSegments(dateValue, validSegments, dateFormatter, resolvedOption isEditable: false }); processedSegments.push(dateSegment); + // This marks the end of the embedded direction change in the case that the granularity it set to "hour". if (segment.type === granularity) { processedSegments.push({ type: 'literal', @@ -450,8 +455,9 @@ function processSegments(dateValue, validSegments, dateFormatter, resolvedOption isEditable: false }); } - } else if (timeValue.includes(granularity) && segment.type === granularity) { + } else if (timeValue.includes(segment.type) && segment.type === granularity) { processedSegments.push(dateSegment); + // This marks the end of the embedded direction change. processedSegments.push({ type: 'literal', text: '\u2069', @@ -461,6 +467,7 @@ function processSegments(dateValue, validSegments, dateFormatter, resolvedOption isEditable: false }); } else { + // We only want to "wrap" the unicode around segments that are hour, minute, or second. If they aren't, just process as normal. processedSegments.push(dateSegment); } } diff --git a/packages/react-aria-components/docs/DateField.mdx b/packages/react-aria-components/docs/DateField.mdx index d934e350d99..d23c93617c8 100644 --- a/packages/react-aria-components/docs/DateField.mdx +++ b/packages/react-aria-components/docs/DateField.mdx @@ -155,6 +155,10 @@ If the date field does not have a visible label, an `aria-label` or `aria-labell Note that most of this anatomy is shared with [TimeField](TimeField.html), so you can reuse many components between them if you have both. +### Internationalization + +To ensure the proper date and time format in RTL locales, `DateInput` must have `display` set to either `inline`, `inline-block`, or `block`. + ### Concepts `DateField` makes use of the following concepts: diff --git a/packages/react-aria-components/docs/DatePicker.mdx b/packages/react-aria-components/docs/DatePicker.mdx index 0230d60cded..746739f11a1 100644 --- a/packages/react-aria-components/docs/DatePicker.mdx +++ b/packages/react-aria-components/docs/DatePicker.mdx @@ -195,6 +195,10 @@ If the date picker does not have a visible label, an `aria-label` or `aria-label Note that most of this anatomy is shared with [DateRangePicker](DateRangePicker.html), so you can reuse many components between them if you have both. +### Internationalization + +To ensure the proper date and time format in RTL locales, `DateInput` must have `display` set to either `inline`, `inline-block`, or `block`. + ### Concepts `DatePicker` makes use of the following concepts: diff --git a/packages/react-aria-components/docs/DateRangePicker.mdx b/packages/react-aria-components/docs/DateRangePicker.mdx index 39e1564f4cd..daad983725b 100644 --- a/packages/react-aria-components/docs/DateRangePicker.mdx +++ b/packages/react-aria-components/docs/DateRangePicker.mdx @@ -232,6 +232,10 @@ If the date range picker does not have a visible label, an `aria-label` or `aria Note that most of this anatomy is shared with [DatePicker](DatePicker.html), so you can reuse many components between them if you have both. +### Internationalization + +To ensure the proper date and time format in RTL locales, `DateInput` must have `display` set to either `inline`, `inline-block`, or `block`. + ### Concepts `DateRangePicker` makes use of the following concepts: