import { computed, reactive, ref } from 'vue'; type ElRefs = Array; const refSets = reactive({ monthYear: [] as ElRefs, calendar: [] as ElRefs[], time: [] as ElRefs, actionRow: [] as ElRefs, selectionGrid: [] as ElRefs[], timePicker: { '0': [] as ElRefs[], '1': [] as ElRefs[], }, monthPicker: [] as ElRefs[], }); const timePickerBackRef = ref(null); const isSelectionGrid = ref(false); const isTimePicker = ref(false); const isMonthPicker = ref(false); const isTimePickerMode = ref(false); const selectedIndex = ref(0); const activeRow = ref(0); export const useArrowNavigation = () => { const matrix = computed((): ElRefs[] => { if (isSelectionGrid.value) return [...refSets.selectionGrid, refSets.actionRow].filter((set) => set.length); if (isTimePicker.value) { return [ ...refSets.timePicker[0], ...refSets.timePicker[1], isTimePickerMode.value ? [] : [timePickerBackRef.value], refSets.actionRow, ].filter((set) => set.length); } if (isMonthPicker.value) return [...refSets.monthPicker, refSets.actionRow]; return [refSets.monthYear, ...refSets.calendar, refSets.time, refSets.actionRow].filter((set) => set.length); }); // Handles left and right arrow const handleSelectionIndexX = (increment?: boolean): void => { selectedIndex.value = increment ? selectedIndex.value + 1 : selectedIndex.value - 1; let el = null; if (matrix.value[activeRow.value]) { el = matrix.value[activeRow.value][selectedIndex.value]; } if (!el) { selectedIndex.value = increment ? selectedIndex.value - 1 : selectedIndex.value + 1; } }; // Handles up and down arrow const handleSelectionIndexY = (increment?: boolean): void => { if ((activeRow.value === 0 && !increment) || (activeRow.value === matrix.value.length && increment)) return; activeRow.value = increment ? activeRow.value + 1 : activeRow.value - 1; const el = matrix.value[activeRow.value]; if (!el) { activeRow.value = increment ? activeRow.value - 1 : activeRow.value + 1; } else { if ( matrix.value[activeRow.value] && !matrix.value[activeRow.value][selectedIndex.value] && selectedIndex.value !== 0 ) { selectedIndex.value = matrix.value[activeRow.value].length - 1; } } }; const handleElFocus = (increment: boolean): void => { let el = null; if (matrix.value[activeRow.value]) { el = matrix.value[activeRow.value][selectedIndex.value]; } if (el) { el.focus({ preventScroll: !isSelectionGrid.value }); } else { selectedIndex.value = increment ? selectedIndex.value - 1 : selectedIndex.value + 1; } }; const arrowRight = (): void => { handleSelectionIndexX(true); handleElFocus(true); }; const arrowLeft = (): void => { handleSelectionIndexX(false); handleElFocus(false); }; const arrowUp = (): void => { handleSelectionIndexY(false); handleElFocus(true); }; const arrowDown = (): void => { handleSelectionIndexY(true); handleElFocus(true); }; /** * Add values per page, holds the ref values of the focusable elements * Build top to bottom */ const buildMatrix = (elements: Array, set: 'monthYear' | 'time' | 'actionRow'): void => { refSets[set] = elements; }; const buildMultiLevelMatrix = ( elements: HTMLElement[][], set: 'calendar' | 'selectionGrid' | 'monthPicker', ): void => { refSets[set] = elements; }; const resetNavigation = (): void => { selectedIndex.value = 0; activeRow.value = 0; }; const setMonthPicker = (value: boolean): void => { isMonthPicker.value = value; resetNavigation(); }; /** * For selection grid, things are handled per grid */ const setSelectionGrid = (value: boolean): void => { isSelectionGrid.value = value; resetNavigation(); if (!value) { refSets.selectionGrid = []; } }; const setTimePicker = (value: boolean, mode = false) => { isTimePicker.value = value; isTimePickerMode.value = mode; resetNavigation(); if (!value) { refSets.timePicker[0] = []; refSets.timePicker[1] = []; } }; const setTimePickerElements = (elements: HTMLElement[][], order: 0 | 1 = 0): void => { refSets.timePicker[order] = elements; }; const setTimePickerBackRef = (el: HTMLElement | null): void => { timePickerBackRef.value = el; }; const clearArrowNav = (): void => { refSets.monthYear = []; refSets.calendar = []; refSets.time = []; refSets.actionRow = []; refSets.selectionGrid = []; refSets.timePicker[0] = []; refSets.timePicker[1] = []; isSelectionGrid.value = false; isTimePicker.value = false; isTimePickerMode.value = false; isMonthPicker.value = false; resetNavigation(); timePickerBackRef.value = null; }; return { buildMatrix, buildMultiLevelMatrix, setTimePickerBackRef, setSelectionGrid, setTimePicker, setTimePickerElements, arrowRight, arrowLeft, arrowUp, arrowDown, clearArrowNav, setMonthPicker, refSets, // exposed for testing }; };