@@ -36,9 +36,6 @@ export interface CalendarProps extends React.Props<CalendarComponentType> {
3636 */
3737 localTimezone ?: boolean ;
3838
39- /** Tab index of calendar buttons */
40- tabIndex ?: number ;
41-
4239 /**
4340 * Callback for date change events
4441 * */
@@ -67,7 +64,6 @@ export interface CalendarState {
6764export class Calendar extends React . Component < CalendarProps , Partial < CalendarState > > {
6865 static defaultProps = {
6966 localTimezone : true ,
70- tabIndex : - 1 ,
7167 attr : {
7268 container : { } ,
7369 header : { } ,
@@ -84,8 +80,10 @@ export class Calendar extends React.Component<CalendarProps, Partial<CalendarSta
8480 private value : MethodDate ;
8581 private monthNames : string [ ] ;
8682 private dayNames : string [ ] ;
87- private buttons : { [ date : string ] : HTMLButtonElement } ;
88- private buttonIndex : number ;
83+ private _container : HTMLDivElement ;
84+ private nextFocusRow ?: number ;
85+ private nextFocusCol ?: number ;
86+
8987
9088 constructor ( props : CalendarProps ) {
9189 const locale = navigator [ 'userLanguage' ] || ( navigator . language || 'en-us' ) ;
@@ -117,15 +115,10 @@ export class Calendar extends React.Component<CalendarProps, Partial<CalendarSta
117115
118116 this . dayNames = getLocalWeekdays ( locale ) ;
119117
120- this . buttons = { } ;
121- this . buttonIndex = 0 ;
122- this . dayRef = this . dayRef . bind ( this ) ;
123-
118+ this . onPrevMonth = this . onPrevMonth . bind ( this ) ;
119+ this . onNextMonth = this . onNextMonth . bind ( this ) ;
124120 this . onKeyDown = this . onKeyDown . bind ( this ) ;
125- }
126-
127- get focusedButton ( ) : HTMLButtonElement {
128- return this . buttons [ this . state . currentDate . date - 1 ] ;
121+ this . setContainerRef = this . setContainerRef . bind ( this ) ;
129122 }
130123
131124 public startAccessibility ( ) {
@@ -144,28 +137,6 @@ export class Calendar extends React.Component<CalendarProps, Partial<CalendarSta
144137 } ) ;
145138 }
146139
147- dayRef ( element : HTMLButtonElement ) {
148- if ( element ) {
149- this . buttons [ this . buttonIndex ] = element ;
150- this . buttonIndex ++ ;
151- }
152- }
153-
154- componentWillMount ( ) {
155- window . addEventListener ( 'keydown' , this . onKeyDown ) ;
156- }
157-
158- componentWillUnmount ( ) {
159- window . removeEventListener ( 'keydown' , this . onKeyDown ) ;
160- }
161-
162- componentDidUpdate ( oldProps : CalendarProps , oldState : CalendarState ) {
163- if ( this . state . accessibility && this . state . currentDate !== oldState . currentDate ) {
164- this . focusedButton . focus ( ) ;
165- }
166- this . buttonIndex = 0 ;
167- }
168-
169140 componentWillReceiveProps ( newProps : CalendarProps ) {
170141 const date = this . state . currentDate . copy ( ) ;
171142 let update = false ;
@@ -195,75 +166,14 @@ export class Calendar extends React.Component<CalendarProps, Partial<CalendarSta
195166 }
196167 }
197168
198- onKeyDown ( event ) {
199- if ( ! this . state . accessibility ) {
200- return ;
201- }
202- /** So that we don't block any browser shortcuts */
203- if ( event . ctrlKey || event . altKey ) {
204- return ;
205- }
206- if ( document . activeElement === this . focusedButton ) {
207- const date = this . state . currentDate . copy ( ) ;
208- let detached = this . state . detached ;
209- let newDay = date . date ;
210- let newMonth = date . month ;
211- let newYear = date . year ;
212- let weekMove = false ;
213- switch ( event . keyCode ) {
214- case keyCode . left :
215- newDay -= 1 ;
216- break ;
217- case keyCode . right :
218- newDay += 1 ;
219- break ;
220- case keyCode . up :
221- weekMove = true ;
222- newDay -= 7 ;
223- break ;
224- case keyCode . down :
225- weekMove = true ;
226- newDay += 7 ;
227- break ;
228- case keyCode . pageup :
229- if ( event . ctrlKey ) {
230- newYear -= 1 ;
231- } else {
232- newMonth -= 1 ;
233- }
234- break ;
235- case keyCode . pagedown :
236- if ( event . ctrlKey ) {
237- newYear += 1 ;
238- } else {
239- newMonth += 1 ;
240- }
241- break ;
242- case keyCode . home :
243- newDay = 1 ;
244- break ;
245- case keyCode . end :
246- newDay = 0 ;
247- newMonth += 1 ;
248- break ;
249- default :
250- return ;
251- }
252- date . year = newYear ;
253- date . month = newMonth ;
254- date . date = newDay ;
255-
256- if ( newDay > 0 && date . date !== newDay && ! weekMove ) {
257- date . month += 1 ;
258- date . date = 0 ;
169+ componentDidUpdate ( ) {
170+ if ( this . nextFocusRow != null && this . nextFocusCol != null ) {
171+ const nextFocus = this . _container . querySelectorAll ( `[data-row="${ this . nextFocusRow } "][data-col="${ this . nextFocusCol } "]` ) [ 0 ] as HTMLElement ;
172+ if ( nextFocus != null ) {
173+ nextFocus . focus ( ) ;
259174 }
260-
261- event . stopPropagation ( ) ;
262- event . preventDefault ( ) ;
263- this . setState ( {
264- currentDate : date ,
265- detached : detached
266- } ) ;
175+ this . nextFocusRow = undefined ;
176+ this . nextFocusCol = undefined ;
267177 }
268178 }
269179
@@ -289,6 +199,16 @@ export class Calendar extends React.Component<CalendarProps, Partial<CalendarSta
289199 onPrevMonth ( event ) {
290200 event . preventDefault ( ) ;
291201
202+ this . decrementMonth ( ) ;
203+ }
204+
205+ onNextMonth ( event ) {
206+ event . preventDefault ( ) ;
207+
208+ this . incrementMonth ( ) ;
209+ }
210+
211+ decrementMonth ( ) {
292212 /** Dates are mutable so we're going to copy it over */
293213 const newDate = this . state . currentDate . copy ( ) ;
294214 const curDate = newDate . date ;
@@ -303,9 +223,7 @@ export class Calendar extends React.Component<CalendarProps, Partial<CalendarSta
303223 this . setState ( { currentDate : newDate , detached : true } ) ;
304224 }
305225
306- onNextMonth ( event ) {
307- event . preventDefault ( ) ;
308-
226+ incrementMonth ( ) {
309227 /** Dates are mutable so we're going to copy it over */
310228 const newDate = this . state . currentDate . copy ( ) ;
311229 const curDate = newDate . date ;
@@ -319,14 +237,77 @@ export class Calendar extends React.Component<CalendarProps, Partial<CalendarSta
319237 this . setState ( { currentDate : newDate , detached : true } ) ;
320238 }
321239
240+ onKeyDown ( e : React . KeyboardEvent < any > ) {
241+ const element : HTMLElement = e . currentTarget ;
242+ const row = parseInt ( element . getAttribute ( 'data-row' ) , 10 ) ;
243+ const col = parseInt ( element . getAttribute ( 'data-col' ) , 10 ) ;
244+
245+ if ( ! isNaN ( row ) && ! isNaN ( col ) ) {
246+ let nextRow = row ;
247+ let nextCol = col ;
248+ let nextFocus : HTMLElement ;
249+ switch ( e . keyCode ) {
250+ case keyCode . pagedown :
251+ e . preventDefault ( ) ;
252+ e . stopPropagation ( ) ;
253+ this . nextFocusCol = nextCol ;
254+ this . nextFocusRow = nextRow ;
255+ this . incrementMonth ( ) ;
256+ break ;
257+ case keyCode . pageup :
258+ e . preventDefault ( ) ;
259+ e . stopPropagation ( ) ;
260+ this . nextFocusCol = nextCol ;
261+ this . nextFocusRow = nextRow ;
262+ this . decrementMonth ( ) ;
263+ break ;
264+ case keyCode . up :
265+ e . preventDefault ( ) ;
266+ e . stopPropagation ( ) ;
267+ nextRow -= 1 ;
268+ break ;
269+ case keyCode . down :
270+ e . preventDefault ( ) ;
271+ e . stopPropagation ( ) ;
272+ nextRow += 1 ;
273+ break ;
274+ case keyCode . left :
275+ e . preventDefault ( ) ;
276+ e . stopPropagation ( ) ;
277+ nextCol -= 1 ;
278+ if ( nextCol < 0 ) {
279+ nextCol = 6 ;
280+ nextRow -= 1 ;
281+ }
282+ break ;
283+ case keyCode . right :
284+ e . preventDefault ( ) ;
285+ e . stopPropagation ( ) ;
286+ nextCol += 1 ;
287+ if ( nextCol > 6 ) {
288+ nextCol = 0 ;
289+ nextRow += 1 ;
290+ }
291+ break ;
292+ }
293+ nextFocus = this . _container . querySelectorAll ( `[data-row="${ nextRow } "][data-col="${ nextCol } "]` ) [ 0 ] as HTMLElement ;
294+ // if we found the next button to focus on, focus it
295+ if ( nextFocus != null ) {
296+ nextFocus . focus ( ) ;
297+ }
298+ }
299+ }
300+
301+ setContainerRef ( element : HTMLDivElement ) {
302+ this . _container = element ;
303+ }
304+
322305 render ( ) {
323306 const rowClassName = css ( 'calendar-row' ) ;
324307 const colClassName = css ( 'disabled' ) ;
325- const tabIndex = this . props . tabIndex ;
326308
327309 const curYear = this . state . currentDate . year ;
328310 const curMonth = this . state . currentDate . month ;
329- const curDate = this . state . currentDate . date ;
330311
331312 const weekdays = this . dayNames . map ( day => {
332313 return (
@@ -368,6 +349,8 @@ export class Calendar extends React.Component<CalendarProps, Partial<CalendarSta
368349 event . preventDefault ( ) ;
369350 } ;
370351
352+ // TODO aria-label with date
353+
371354 const date = col . date ;
372355 const colMonth = col . month ;
373356 const key = `${ colMonth } -${ date } ` ;
@@ -376,10 +359,12 @@ export class Calendar extends React.Component<CalendarProps, Partial<CalendarSta
376359 return (
377360 < Attr . button
378361 type = 'button'
362+ data-row = { rowIndex }
363+ data-col = { colIndex }
364+ onKeyDown = { this . onKeyDown }
379365 className = { colClassName }
380366 onClick = { onClick }
381367 key = { key }
382- tabIndex = { tabIndex }
383368 attr = { this . props . attr . dateButton }
384369 >
385370 { date }
@@ -399,11 +384,12 @@ export class Calendar extends React.Component<CalendarProps, Partial<CalendarSta
399384 return (
400385 < Attr . button
401386 type = 'button'
387+ data-row = { rowIndex }
388+ data-col = { colIndex }
389+ onKeyDown = { this . onKeyDown }
402390 className = { css ( 'selected' ) }
403391 onClick = { onClick }
404392 key = { key }
405- tabIndex = { tabIndex }
406- methodRef = { this . dayRef }
407393 onFocus = { this . onFocus . bind ( this , date ) }
408394 attr = { this . props . attr . dateButton }
409395 >
@@ -417,10 +403,11 @@ export class Calendar extends React.Component<CalendarProps, Partial<CalendarSta
417403 return (
418404 < Attr . button
419405 type = 'button'
406+ data-row = { rowIndex }
407+ data-col = { colIndex }
408+ onKeyDown = { this . onKeyDown }
420409 onClick = { onClick }
421410 key = { key }
422- tabIndex = { tabIndex }
423- methodRef = { this . dayRef }
424411 onFocus = { this . onFocus . bind ( this , date ) }
425412 attr = { this . props . attr . dateButton }
426413 >
@@ -441,6 +428,7 @@ export class Calendar extends React.Component<CalendarProps, Partial<CalendarSta
441428 } ) ;
442429 return (
443430 < Attr . div
431+ methodRef = { this . setContainerRef }
444432 className = { css ( 'calendar' , this . props . className ) }
445433 attr = { this . props . attr . container }
446434 >
@@ -456,16 +444,14 @@ export class Calendar extends React.Component<CalendarProps, Partial<CalendarSta
456444 </ Attr . div >
457445 < ActionTriggerButton
458446 className = { css ( 'calendar-chevron' ) }
459- onClick = { event => this . onPrevMonth ( event ) }
460- tabIndex = { tabIndex }
447+ onClick = { this . onPrevMonth }
461448 icon = 'chevronUp'
462449 attr = { this . props . attr . prevMonthButton }
463450 />
464451 < ActionTriggerButton
465452 icon = 'chevronDown'
466453 className = { css ( 'calendar-chevron' ) }
467- onClick = { event => this . onNextMonth ( event ) }
468- tabIndex = { tabIndex }
454+ onClick = { this . onNextMonth }
469455 attr = { this . props . attr . nextMonthButton }
470456 />
471457 </ Attr . div >
0 commit comments