@@ -83,9 +83,6 @@ export interface CalendarProps extends CalendarFormat, Omit<React.HTMLProps<HTML
8383 validators ?: ( ( date : Date ) => boolean ) [ ] ;
8484}
8585
86- // Must be numeric given current header design
87- const yearFormat = ( date : Date ) => date . getFullYear ( ) ;
88-
8986const buildCalendar = ( year : number , month : number , weekStart : number , validators : ( ( date : Date ) => boolean ) [ ] ) => {
9087 const defaultDate = new Date ( year , month ) ;
9188
@@ -164,6 +161,13 @@ export const CalendarMonth = ({
164161 } ;
165162 const initialDate = getInitialDate ( ) ;
166163 const [ focusedDate , setFocusedDate ] = React . useState ( initialDate ) ;
164+
165+ // Must be numeric given current header design
166+ const yearFormat = ( date : Date ) => date . getFullYear ( ) ;
167+ //
168+ const yearFormatted = yearFormat ( focusedDate ) ;
169+ const [ yearInput , setYearInput ] = React . useState ( yearFormatted . toString ( ) ) ;
170+
167171 const [ hoveredDate , setHoveredDate ] = React . useState ( new Date ( focusedDate ) ) ;
168172 const focusRef = React . useRef < HTMLButtonElement > ( ) ;
169173 const [ hiddenMonthId ] = React . useState ( getUniqueId ( 'hidden-month-span' ) ) ;
@@ -191,6 +195,7 @@ export const CalendarMonth = ({
191195 setHoveredDate ( newDate ) ;
192196 setShouldFocus ( false ) ;
193197 onMonthChange ( ev , newDate ) ;
198+ setYearInput ( yearFormat ( newDate ) . toString ( ) ) ;
194199 } ;
195200
196201 const onKeyDown = ( ev : React . KeyboardEvent < HTMLTableSectionElement > ) => {
@@ -217,6 +222,34 @@ export const CalendarMonth = ({
217222 const changeMonth = ( newMonth : number , newYear ?: number ) =>
218223 new Date ( newYear ?? focusedDate . getFullYear ( ) , newMonth , 1 ) ;
219224
225+ const MIN_YEAR = 1900 ;
226+ const MAX_YEAR = 2100 ;
227+
228+ const handleYearInputChange = ( event : React . FormEvent < HTMLInputElement > , yearStr : string ) => {
229+ if ( ! / ^ \d { 0 , 4 } $ / . test ( yearStr ) ) {
230+ return ;
231+ }
232+
233+ setYearInput ( yearStr ) ;
234+
235+ if ( yearStr . length === 4 ) {
236+ const yearNum = Number ( yearStr ) ;
237+
238+ if ( yearNum >= MIN_YEAR && yearNum <= MAX_YEAR ) {
239+ const newDate = changeYear ( yearNum ) ;
240+ setFocusedDate ( newDate ) ;
241+ setHoveredDate ( newDate ) ;
242+ setShouldFocus ( false ) ;
243+
244+ // We need to manually focus the year input in FireFox when the scroll buttons are clicked, as FireFox doesn't place focus automatically
245+ ( event . target as HTMLElement ) . focus ( ) ;
246+ onMonthChange ( event , newDate ) ;
247+ } else {
248+ setYearInput ( yearFormatted . toString ( ) ) ;
249+ }
250+ }
251+ } ;
252+
220253 const addMonth = ( toAdd : - 1 | 1 ) => {
221254 let newMonth = focusedDate . getMonth ( ) + toAdd ;
222255 let newYear = focusedDate . getFullYear ( ) ;
@@ -254,7 +287,6 @@ export const CalendarMonth = ({
254287 }
255288 const isHoveredDateValid = isValidated ( hoveredDate ) ;
256289 const monthFormatted = monthFormat ( focusedDate ) ;
257- const yearFormatted = yearFormat ( focusedDate ) ;
258290
259291 const calendarToRender = (
260292 < div className = { css ( styles . calendarMonth , className ) } { ...props } >
@@ -321,15 +353,8 @@ export const CalendarMonth = ({
321353 < TextInput
322354 aria-label = { yearInputAriaLabel }
323355 type = "number"
324- value = { yearFormatted }
325- onChange = { ( ev : React . FormEvent < HTMLInputElement > , year : string ) => {
326- const newDate = changeYear ( Number ( year ) ) ;
327- setFocusedDate ( newDate ) ;
328- setHoveredDate ( newDate ) ;
329- setShouldFocus ( false ) ;
330- focusRef . current ?. blur ( ) ; // will unfocus a date when changing year via up/down arrows
331- onMonthChange ( ev , newDate ) ;
332- } }
356+ value = { yearInput }
357+ onChange = { handleYearInputChange }
333358 />
334359 </ div >
335360 </ InputGroupItem >
0 commit comments