@@ -83,9 +83,6 @@ export interface CalendarProps extends CalendarFormat, Omit<React.HTMLProps<HTML
83
83
validators ?: ( ( date : Date ) => boolean ) [ ] ;
84
84
}
85
85
86
- // Must be numeric given current header design
87
- const yearFormat = ( date : Date ) => date . getFullYear ( ) ;
88
-
89
86
const buildCalendar = ( year : number , month : number , weekStart : number , validators : ( ( date : Date ) => boolean ) [ ] ) => {
90
87
const defaultDate = new Date ( year , month ) ;
91
88
@@ -164,6 +161,13 @@ export const CalendarMonth = ({
164
161
} ;
165
162
const initialDate = getInitialDate ( ) ;
166
163
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
+
167
171
const [ hoveredDate , setHoveredDate ] = React . useState ( new Date ( focusedDate ) ) ;
168
172
const focusRef = React . useRef < HTMLButtonElement > ( ) ;
169
173
const [ hiddenMonthId ] = React . useState ( getUniqueId ( 'hidden-month-span' ) ) ;
@@ -191,6 +195,7 @@ export const CalendarMonth = ({
191
195
setHoveredDate ( newDate ) ;
192
196
setShouldFocus ( false ) ;
193
197
onMonthChange ( ev , newDate ) ;
198
+ setYearInput ( yearFormat ( newDate ) . toString ( ) ) ;
194
199
} ;
195
200
196
201
const onKeyDown = ( ev : React . KeyboardEvent < HTMLTableSectionElement > ) => {
@@ -217,6 +222,34 @@ export const CalendarMonth = ({
217
222
const changeMonth = ( newMonth : number , newYear ?: number ) =>
218
223
new Date ( newYear ?? focusedDate . getFullYear ( ) , newMonth , 1 ) ;
219
224
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
+
220
253
const addMonth = ( toAdd : - 1 | 1 ) => {
221
254
let newMonth = focusedDate . getMonth ( ) + toAdd ;
222
255
let newYear = focusedDate . getFullYear ( ) ;
@@ -254,7 +287,6 @@ export const CalendarMonth = ({
254
287
}
255
288
const isHoveredDateValid = isValidated ( hoveredDate ) ;
256
289
const monthFormatted = monthFormat ( focusedDate ) ;
257
- const yearFormatted = yearFormat ( focusedDate ) ;
258
290
259
291
const calendarToRender = (
260
292
< div className = { css ( styles . calendarMonth , className ) } { ...props } >
@@ -321,15 +353,8 @@ export const CalendarMonth = ({
321
353
< TextInput
322
354
aria-label = { yearInputAriaLabel }
323
355
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 }
333
358
/>
334
359
</ div >
335
360
</ InputGroupItem >
0 commit comments