Merge pull request #489 from SamR1/calendar-keyboard-navigation
handle keyboard navigation on calendar
This commit is contained in:
		
							
								
								
									
										4
									
								
								fittrackee/dist/index.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								fittrackee/dist/index.html
									
									
									
									
										vendored
									
									
								
							| @@ -7,11 +7,11 @@ | ||||
|     <link rel="stylesheet" href="/static/css/fork-awesome.min.css"/> | ||||
|     <link rel="stylesheet" href="/static/css/leaflet.css"/> | ||||
|     <title>FitTrackee</title> | ||||
|     <script type="module" crossorigin src="/static/index-QmYZ8SSf.js"></script> | ||||
|     <script type="module" crossorigin src="/static/index-SWko0ePt.js"></script> | ||||
|     <link rel="modulepreload" crossorigin href="/static/charts-_RwsDDkL.js"> | ||||
|     <link rel="modulepreload" crossorigin href="/static/maps-ZyuCPqes.js"> | ||||
|     <link rel="stylesheet" crossorigin href="/static/css/maps-B7qTrBCW.css"> | ||||
|     <link rel="stylesheet" crossorigin href="/static/css/index-ZuVoSwX6.css"> | ||||
|     <link rel="stylesheet" crossorigin href="/static/css/index-tZL8_Oa6.css"> | ||||
|   </head> | ||||
|   <body> | ||||
|     <div id="app"></div> | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								fittrackee/dist/static/css/index-tZL8_Oa6.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								fittrackee/dist/static/css/index-tZL8_Oa6.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -15,6 +15,7 @@ | ||||
|           :workouts="filterWorkouts(day, workouts)" | ||||
|           :sports="sports" | ||||
|           :displayHARecord="displayHARecord" | ||||
|           :index="i" | ||||
|         /> | ||||
|         <div class="calendar-cell-day"> | ||||
|           {{ format(day, 'd') }} | ||||
|   | ||||
| @@ -1,9 +1,7 @@ | ||||
| <template> | ||||
|   <div | ||||
|   <router-link | ||||
|     class="calendar-workout" | ||||
|     @click=" | ||||
|       $router.push({ name: 'Workout', params: { workoutId: workout.id } }) | ||||
|     " | ||||
|     :to="{ name: 'Workout', params: { workoutId: workout.id } }" | ||||
|   > | ||||
|     <SportImage | ||||
|       :sport-label="sportLabel" | ||||
| @@ -26,7 +24,7 @@ | ||||
|         " | ||||
|       /> | ||||
|     </sup> | ||||
|   </div> | ||||
|   </router-link> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
|   | ||||
| @@ -21,6 +21,7 @@ | ||||
|           :datasets="chartDatasets" | ||||
|           :colors="colors" | ||||
|           :displayHARecord="displayHARecord" | ||||
|           :index="index" | ||||
|         /> | ||||
|       </div> | ||||
|     </div> | ||||
| @@ -32,6 +33,7 @@ | ||||
|           :datasets="chartDatasets" | ||||
|           :colors="colors" | ||||
|           :displayHARecord="displayHARecord" | ||||
|           :index="index" | ||||
|         /> | ||||
|       </div> | ||||
|     </div> | ||||
| @@ -52,10 +54,11 @@ | ||||
|     displayHARecord: boolean | ||||
|     workouts: IWorkout[] | ||||
|     sports: ISport[] | ||||
|     index: number | ||||
|   } | ||||
|   const props = defineProps<Props>() | ||||
|  | ||||
|   const { displayHARecord, workouts, sports } = toRefs(props) | ||||
|   const { displayHARecord, index, sports, workouts } = toRefs(props) | ||||
|   const chartDatasets = computed(() => getDonutDatasets(props.workouts)) | ||||
|   const colors = computed(() => sportIdColors(props.sports)) | ||||
|   const displayedWorkoutCount = 6 | ||||
|   | ||||
| @@ -1,16 +1,22 @@ | ||||
| <template> | ||||
|   <div class="calendar-workouts-chart"> | ||||
|     <div class="workouts-chart" @click="togglePane"> | ||||
|     <button | ||||
|       class="workouts-chart transparent" | ||||
|       :id="`workouts-donut-${index}`" | ||||
|       @click="togglePane" | ||||
|     > | ||||
|       <div class="workouts-count">{{ workouts.length }}</div> | ||||
|       <DonutChart :datasets="datasets" :colors="colors" /> | ||||
|     </div> | ||||
|     </button> | ||||
|     <div class="workouts-pane" v-if="!isHidden"> | ||||
|       <div class="more-workouts" v-click-outside="togglePane"> | ||||
|         <i | ||||
|           class="fa fa-times calendar-more" | ||||
|           aria-hidden="true" | ||||
|           @click="togglePane" | ||||
|         /> | ||||
|       <div | ||||
|         class="more-workouts" | ||||
|         :id="`workouts-pane-${index}`" | ||||
|         v-click-outside="togglePane" | ||||
|       > | ||||
|         <button class="calendar-more-close transparent" @click="togglePane"> | ||||
|           <i class="fa fa-times" aria-hidden="true" /> | ||||
|         </button> | ||||
|         <CalendarWorkout | ||||
|           v-for="(workout, index) in workouts" | ||||
|           :key="index" | ||||
| @@ -25,7 +31,7 @@ | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
|   import { ref, toRefs } from 'vue' | ||||
|   import { nextTick, onMounted, onUnmounted, ref, toRefs } from 'vue' | ||||
|  | ||||
|   import CalendarWorkout from '@/components/Dashboard/UserCalendar/CalendarWorkout.vue' | ||||
|   import DonutChart from '@/components/Dashboard/UserCalendar/DonutChart.vue' | ||||
| @@ -39,16 +45,71 @@ | ||||
|     sports: ISport[] | ||||
|     workouts: IWorkout[] | ||||
|     displayHARecord: boolean | ||||
|     index: number | ||||
|   } | ||||
|   const props = defineProps<Props>() | ||||
|   let tabbableElementIndex = 0 | ||||
|  | ||||
|   const { colors, datasets, sports, workouts } = toRefs(props) | ||||
|   const { colors, datasets, index, sports, workouts } = toRefs(props) | ||||
|   const isHidden = ref(true) | ||||
|  | ||||
|   function togglePane(event: Event) { | ||||
|   function isWorkoutsMorePaneDisplayed() { | ||||
|     const pane = document.getElementById(`workouts-pane-${index.value}`) | ||||
|     return pane?.children && pane?.children.length > 0 ? pane : null | ||||
|   } | ||||
|  | ||||
|   async function togglePane(event: Event) { | ||||
|     event.preventDefault() | ||||
|     event.stopPropagation() | ||||
|     isHidden.value = !isHidden.value | ||||
|     await nextTick() | ||||
|     const pane = isWorkoutsMorePaneDisplayed() | ||||
|     if (!isHidden.value) { | ||||
|       ;(pane?.children[0] as HTMLButtonElement).focus() | ||||
|     } else { | ||||
|       document.getElementById(`workouts-donut-${index.value}`)?.focus() | ||||
|     } | ||||
|   } | ||||
|   function handleKey(e: KeyboardEvent) { | ||||
|     if (!isHidden.value) { | ||||
|       // focusTrap | ||||
|       if (!isHidden.value && (e.key === 'Tab' || e.keyCode === 9)) { | ||||
|         e.preventDefault() | ||||
|         e.stopPropagation() | ||||
|         const pane = isWorkoutsMorePaneDisplayed() | ||||
|         if (pane) { | ||||
|           if (e.shiftKey) { | ||||
|             tabbableElementIndex -= 1 | ||||
|             if (tabbableElementIndex < 0) { | ||||
|               tabbableElementIndex = pane.children.length - 1 | ||||
|             } | ||||
|           } else { | ||||
|             tabbableElementIndex += 1 | ||||
|             if (tabbableElementIndex >= pane.children.length) { | ||||
|               tabbableElementIndex = 0 | ||||
|             } | ||||
|           } | ||||
|           // children are only links or buttons | ||||
|           ;( | ||||
|             pane.children[tabbableElementIndex] as | ||||
|               | HTMLButtonElement | ||||
|               | HTMLLinkElement | ||||
|           ).focus() | ||||
|         } | ||||
|       } | ||||
|       // close pane on Escape | ||||
|       if (e.key === 'Escape') { | ||||
|         togglePane(e) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   onMounted(() => { | ||||
|     document.addEventListener('keydown', handleKey) | ||||
|   }) | ||||
|   onUnmounted(() => { | ||||
|     document.removeEventListener('keydown', handleKey) | ||||
|   }) | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| @@ -58,6 +119,7 @@ | ||||
|  | ||||
|     .workouts-chart { | ||||
|       position: relative; | ||||
|       padding: 0; | ||||
|       .workouts-count { | ||||
|         display: flex; | ||||
|         justify-content: center; | ||||
| @@ -108,11 +170,12 @@ | ||||
|         flex-wrap: wrap; | ||||
|         z-index: 1000; | ||||
|  | ||||
|         .calendar-more { | ||||
|         .calendar-more-close { | ||||
|           position: absolute; | ||||
|           font-size: 0.9em; | ||||
|           top: 5px; | ||||
|           right: 5px; | ||||
|           padding: 0; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   | ||||
| @@ -95,6 +95,7 @@ button { | ||||
|   &.transparent { | ||||
|     font-family: 'PT Sans', Helvetica, Arial, sans-serif; | ||||
|     font-size: 1em; | ||||
|     background: transparent; | ||||
|     border-color: transparent; | ||||
|     box-shadow: none; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user