1- import React , { useState , useEffect } from 'react' ;
1+ import React , { useState , useEffect , useRef } from 'react' ;
22import PropTypes from 'prop-types' ;
33import { defineMessages , useIntl } from 'react-intl' ;
44import loadable from '@loadable/component' ;
@@ -85,16 +85,22 @@ const DatetimeWidgetComponent = (props) => {
8585 noPastDates : propNoPastDates ,
8686 isDisabled,
8787 formData,
88+ required,
8889 } = props ;
8990
9091 const intl = useIntl ( ) ;
9192 const lang = intl . locale ;
9293
94+ // timeInputRef: for aria-required (rc-time-picker has no aria props)
95+ const timeInputRef = useRef ( null ) ;
96+
9397 const [ focused , setFocused ] = useState ( false ) ;
9498 const [ isDefault , setIsDefault ] = useState ( false ) ;
9599
96100 const { SingleDatePicker } = reactDates ;
97101
102+ const renderWidget = ! ( id === 'end' && formData ?. open_end ) ;
103+
98104 useEffect ( ( ) => {
99105 const parsedDateTime = parseDateTime (
100106 toBackendLang ( lang ) ,
@@ -107,11 +113,6 @@ const DatetimeWidgetComponent = (props) => {
107113 ) ;
108114 } , [ value , lang , moment ] ) ;
109115
110- // If open_end is checked and this is the end field, don't render
111- if ( id === 'end' && formData ?. open_end ) {
112- return null ;
113- }
114-
115116 const getInternalValue = ( ) => {
116117 return parseDateTime ( toBackendLang ( lang ) , value , undefined , moment . default ) ;
117118 } ;
@@ -165,68 +166,101 @@ const DatetimeWidgetComponent = (props) => {
165166 const datetime = getInternalValue ( ) ;
166167 const isDateOnly = getDateOnly ( ) ;
167168
169+ // aria-required for the time input (rc-time-picker is lazy-loaded,
170+ // so MutationObserver is needed to catch when it mounts its input)
171+
172+ // rc-time-picker does not have aria props, so we need to set aria-required
173+ // manually on the input element when the required prop changes
174+
175+ useEffect ( ( ) => {
176+ if ( ! renderWidget || isDateOnly ) return ;
177+
178+ function applyTimeAria ( ) {
179+ const input = timeInputRef . current ?. querySelector ( 'input' ) ;
180+ if ( ! input ) return ;
181+ if ( required ) input . setAttribute ( 'aria-required' , 'true' ) ;
182+ else input . removeAttribute ( 'aria-required' ) ;
183+ }
184+
185+ applyTimeAria ( ) ;
186+
187+ const observer = new MutationObserver ( applyTimeAria ) ;
188+ if ( timeInputRef . current ) {
189+ observer . observe ( timeInputRef . current , {
190+ childList : true ,
191+ subtree : true ,
192+ } ) ;
193+ }
194+
195+ return ( ) => observer . disconnect ( ) ;
196+ } , [ required , isDateOnly , renderWidget ] ) ;
197+
168198 return (
169199 < FormFieldWrapper { ...props } >
170- < div className = "date-time-widget-wrapper" >
171- < div
172- className = { cx ( 'ui input date-input' , {
173- 'default-date' : isDefault ,
174- } ) }
175- >
176- < SingleDatePicker
177- date = { datetime }
178- disabled = { isDisabled }
179- onDateChange = { onDateChange }
180- focused = { focused }
181- numberOfMonths = { 1 }
182- { ...( noPastDates ? { } : { isOutsideRange : ( ) => false } ) }
183- onFocusChange = { onFocusChange }
184- noBorder
185- displayFormat = { moment . default
186- . localeData ( toBackendLang ( lang ) )
187- . longDateFormat ( 'L' ) }
188- navPrev = { < PrevIcon /> }
189- navNext = { < NextIcon /> }
190- id = { `${ id } -date` }
191- placeholder = { intl . formatMessage ( messages . date ) }
192- />
193- </ div >
194- { ! isDateOnly && (
200+ { renderWidget && (
201+ < div className = "date-time-widget-wrapper" >
195202 < div
196- className = { cx ( 'ui input time -input' , {
203+ className = { cx ( 'ui input date -input' , {
197204 'default-date' : isDefault ,
198205 } ) }
199206 >
200- < TimePicker
207+ < SingleDatePicker
208+ date = { datetime }
201209 disabled = { isDisabled }
202- defaultValue = { datetime }
203- value = { datetime }
204- onChange = { onTimeChange }
205- allowEmpty = { false }
206- showSecond = { false }
207- use12Hours = { lang === 'en' }
208- id = { ` ${ id } -time` }
209- format = { moment . default
210+ onDateChange = { onDateChange }
211+ focused = { focused }
212+ numberOfMonths = { 1 }
213+ { ... ( noPastDates ? { } : { isOutsideRange : ( ) => false } ) }
214+ onFocusChange = { onFocusChange }
215+ noBorder
216+ required = { required }
217+ displayFormat = { moment . default
210218 . localeData ( toBackendLang ( lang ) )
211- . longDateFormat ( 'LT' ) }
212- placeholder = { intl . formatMessage ( messages . time ) }
213- focusOnOpen
214- placement = "bottomRight"
219+ . longDateFormat ( 'L' ) }
220+ navPrev = { < PrevIcon /> }
221+ navNext = { < NextIcon /> }
222+ id = { `${ id } -date` }
223+ placeholder = { intl . formatMessage ( messages . date ) }
215224 />
216225 </ div >
217- ) }
218- { resettable && (
219- < button
220- type = "button"
221- disabled = { isDisabled || ! datetime }
222- onClick = { onResetDates }
223- className = "item ui noborder button"
224- aria-label = { intl . formatMessage ( messages . clearDateTime ) }
225- >
226- < Icon name = { clearSVG } size = "24px" className = "close" />
227- </ button >
228- ) }
229- </ div >
226+ { ! isDateOnly && (
227+ < div
228+ ref = { timeInputRef }
229+ className = { cx ( 'ui input time-input' , {
230+ 'default-date' : isDefault ,
231+ } ) }
232+ >
233+ < TimePicker
234+ disabled = { isDisabled }
235+ defaultValue = { datetime }
236+ value = { datetime }
237+ onChange = { onTimeChange }
238+ allowEmpty = { false }
239+ showSecond = { false }
240+ use12Hours = { lang === 'en' }
241+ id = { `${ id } -time` }
242+ format = { moment . default
243+ . localeData ( toBackendLang ( lang ) )
244+ . longDateFormat ( 'LT' ) }
245+ placeholder = { intl . formatMessage ( messages . time ) }
246+ focusOnOpen
247+ placement = "bottomRight"
248+ />
249+ </ div >
250+ ) }
251+ { resettable && (
252+ < button
253+ type = "button"
254+ disabled = { isDisabled || ! datetime }
255+ onClick = { onResetDates }
256+ className = "item ui noborder button"
257+ aria-label = { intl . formatMessage ( messages . clearDateTime ) }
258+ >
259+ < Icon name = { clearSVG } size = "24px" className = "close" />
260+ </ button >
261+ ) }
262+ </ div >
263+ ) }
230264 </ FormFieldWrapper >
231265 ) ;
232266} ;
0 commit comments