|
1 | 1 | import { BaseUnit, UnitLong } from "@macrostrat/api-types"; |
2 | 2 | import { useKeyHandler } from "@macrostrat/ui-components"; |
3 | | -import { useEffect, useRef, useCallback } from "react"; |
| 3 | +import { useEffect, useRef, useCallback, useMemo } from "react"; |
4 | 4 | import type { RectBounds, IUnit } from "../units/types"; |
5 | 5 | import { atom } from "jotai"; |
6 | 6 | import { columnUnitsMapAtom, scope } from "./core"; |
@@ -179,58 +179,64 @@ export function useUnitSelectionTarget( |
179 | 179 | return [ref, selected, onClick]; |
180 | 180 | } |
181 | 181 |
|
182 | | -export function UnitKeyboardNavigation<T extends BaseUnit>({ |
| 182 | +export function UnitKeyboardNavigation({ |
| 183 | + columnData, |
183 | 184 | units, |
| 185 | + allowHorizontalNavigation, |
184 | 186 | }: { |
185 | | - units: T[]; |
| 187 | + columnData?: ColumnData[]; |
| 188 | + units?: UnitLong[]; |
| 189 | + allowHorizontalNavigation?: boolean; |
186 | 190 | }) { |
187 | | - const selectedUnit = useSelectedUnit(); |
188 | | - const selectUnit = useUnitSelectionDispatch(); |
189 | | - |
190 | | - const ix = units.findIndex((unit) => unit.unit_id === selectedUnit?.unit_id); |
| 191 | + if (units == null && columnData == null) { |
| 192 | + throw new Error("Either units or columnData must be provided."); |
| 193 | + } |
191 | 194 |
|
192 | | - const keyMap = { |
193 | | - 38: ix - 1, |
194 | | - 40: ix + 1, |
195 | | - }; |
| 195 | + let _columnData: ColumnData[] = useMemo(() => { |
| 196 | + if (columnData != null) return columnData; |
| 197 | + // Build column data from units |
| 198 | + const colMap = new Map<number, UnitLong[]>(); |
| 199 | + for (const unit of units!) { |
| 200 | + if (!colMap.has(unit.col_id)) { |
| 201 | + colMap.set(unit.col_id, []); |
| 202 | + } |
| 203 | + colMap.get(unit.col_id)!.push(unit); |
| 204 | + } |
| 205 | + const cols: ColumnData[] = []; |
| 206 | + for (const [colID, units] of colMap) { |
| 207 | + cols.push({ columnID: colID, units }); |
| 208 | + } |
| 209 | + // Sort columns by columnID |
| 210 | + cols.sort((a, b) => a.columnID - b.columnID); |
| 211 | + return cols; |
| 212 | + }, [columnData, units]); |
196 | 213 |
|
197 | | - useKeyHandler( |
198 | | - (event) => { |
199 | | - const nextIx = keyMap[event.keyCode]; |
200 | | - if (nextIx == null || nextIx < 0 || nextIx >= units.length) return; |
201 | | - selectUnit(units[nextIx], null); |
202 | | - event.stopPropagation(); |
203 | | - }, |
204 | | - [units, ix], |
205 | | - ); |
206 | | - return null; |
207 | | -} |
| 214 | + // Default to allowing horizontal navigation only if columnData is provided |
| 215 | + const _allowHorizontalNavigation = |
| 216 | + allowHorizontalNavigation ?? columnData != null; |
208 | 217 |
|
209 | | -export function CorrelationChartKeyboardNavigation({ |
210 | | - columnData, |
211 | | -}: { |
212 | | - columnData: ColumnData[]; |
213 | | -}) { |
214 | 218 | const selectedUnit = useSelectedUnit() as UnitLong | null; |
215 | 219 | const selectUnit = useUnitSelectionDispatch(); |
216 | 220 |
|
217 | 221 | const keyMap: Record<number, Direction> = { |
218 | | - 37: "left", |
219 | 222 | 38: "up", |
220 | | - 39: "right", |
221 | 223 | 40: "down", |
222 | 224 | }; |
| 225 | + if (_allowHorizontalNavigation) { |
| 226 | + keyMap[37] = "left"; |
| 227 | + keyMap[39] = "right"; |
| 228 | + } |
223 | 229 |
|
224 | 230 | useKeyHandler( |
225 | 231 | (event) => { |
226 | 232 | const direction = keyMap[event.keyCode]; |
227 | 233 | if (direction == null) return; |
228 | | - const nextUnit = getBestUnit(columnData, selectedUnit, direction); |
| 234 | + const nextUnit = getBestUnit(_columnData, selectedUnit, direction); |
229 | 235 | if (nextUnit == null) return; |
230 | 236 | selectUnit(nextUnit, null); |
231 | 237 | event.stopPropagation(); |
232 | 238 | }, |
233 | | - [selectedUnit], |
| 239 | + [_columnData, selectedUnit], |
234 | 240 | ); |
235 | 241 | return null; |
236 | 242 | } |
|
0 commit comments