@@ -19,6 +19,14 @@ type ExactlyOneKey<T, K extends keyof T = keyof T> = {
1919/** Represents a directional change in the grid, either by row or by column. */
2020type Delta = ExactlyOneKey < { row : - 1 | 1 ; col : - 1 | 1 } > ;
2121
22+ /** */
23+ export const Direction : Record < 'Up' | 'Down' | 'Left' | 'Right' , Delta > = {
24+ Up : { row : - 1 } ,
25+ Down : { row : 1 } ,
26+ Left : { col : - 1 } ,
27+ Right : { col : 1 } ,
28+ } as const ;
29+
2230/** Represents an item in a collection, such as a listbox option, than can be navigated to. */
2331export interface GridNavigationCell extends GridFocusCell { }
2432
@@ -59,47 +67,15 @@ export class GridNavigation<T extends GridNavigationCell> {
5967 return this . inputs . gridFocus . focusCoordinates ( coords ) ;
6068 }
6169
62- /** Gets the coordinates of the cell above the given coordinates. */
63- peekUp ( fromCoords : RowCol ) : RowCol | undefined {
64- return this . _peekDirectional ( { row : - 1 } , fromCoords , this . inputs . rowWrap ( ) ) ;
65- }
66-
67- /** Navigates to the item above the current item. */
68- up ( ) : boolean {
69- const nextCoords = this . peekUp ( this . inputs . gridFocus . activeCoords ( ) ) ;
70- return ! ! nextCoords && this . gotoCoords ( nextCoords ) ;
71- }
72-
73- /** Gets the coordinates of the cell below the given coordinates. */
74- peekDown ( fromCoords : RowCol ) : RowCol | undefined {
75- return this . _peekDirectional ( { row : 1 } , fromCoords , this . inputs . rowWrap ( ) ) ;
76- }
77-
78- /** Navigates to the item below the current item. */
79- down ( ) : boolean {
80- const nextCoords = this . peekDown ( this . inputs . gridFocus . activeCoords ( ) ) ;
81- return ! ! nextCoords && this . gotoCoords ( nextCoords ) ;
82- }
83-
84- /** Gets the coordinates of the cell to the left of the given coordinates. */
85- peekLeft ( fromCoords : RowCol ) : RowCol | undefined {
86- return this . _peekDirectional ( { col : - 1 } , fromCoords , this . inputs . colWrap ( ) ) ;
70+ /** */
71+ peek ( direction : Delta , fromCoords : RowCol ) : RowCol | undefined {
72+ const wrap = direction . row !== undefined ? this . inputs . rowWrap ( ) : this . inputs . colWrap ( ) ;
73+ return this . _peekDirectional ( direction , fromCoords , wrap ) ;
8774 }
8875
89- /** Navigates to the item to the left of the current item. */
90- left ( ) : boolean {
91- const nextCoords = this . peekLeft ( this . inputs . gridFocus . activeCoords ( ) ) ;
92- return ! ! nextCoords && this . gotoCoords ( nextCoords ) ;
93- }
94-
95- /** Gets the coordinates of the cell to the right of the given coordinates. */
96- peekRight ( fromCoords : RowCol ) : RowCol | undefined {
97- return this . _peekDirectional ( { col : 1 } , fromCoords , this . inputs . colWrap ( ) ) ;
98- }
99-
100- /** Navigates to the item to the right of the current item. */
101- right ( ) : boolean {
102- const nextCoords = this . peekRight ( this . inputs . gridFocus . activeCoords ( ) ) ;
76+ /** */
77+ advance ( direction : Delta ) : boolean {
78+ const nextCoords = this . peek ( direction , this . inputs . gridFocus . activeCoords ( ) ) ;
10379 return ! ! nextCoords && this . gotoCoords ( nextCoords ) ;
10480 }
10581
@@ -108,14 +84,13 @@ export class GridNavigation<T extends GridNavigationCell> {
10884 * If a row is not provided, searches the entire grid.
10985 */
11086 peekFirst ( row ?: number ) : RowCol | undefined {
111- const delta : Delta = { col : 1 } ;
112- const startCoords = {
87+ const fromCoords = {
11388 row : row ?? 0 ,
11489 col : - 1 ,
11590 } ;
11691 return row === undefined
117- ? this . _peekContinuous ( delta , startCoords )
118- : this . _peek ( delta , startCoords ) ;
92+ ? this . _peekDirectional ( Direction . Right , fromCoords , 'continuous' )
93+ : this . _peekDirectional ( Direction . Right , fromCoords , 'nowrap' ) ;
11994 }
12095
12196 /**
@@ -132,14 +107,13 @@ export class GridNavigation<T extends GridNavigationCell> {
132107 * If a row is not provided, searches the entire grid.
133108 */
134109 peekLast ( row ?: number ) : RowCol | undefined {
135- const delta : Delta = { col : - 1 } ;
136- const startCoords = {
110+ const fromCoords = {
137111 row : row ?? this . inputs . grid . maxRowCount ( ) - 1 ,
138112 col : this . inputs . grid . maxColCount ( ) ,
139113 } ;
140114 return row === undefined
141- ? this . _peekContinuous ( delta , startCoords )
142- : this . _peek ( delta , startCoords ) ;
115+ ? this . _peekDirectional ( Direction . Left , fromCoords , 'continuous' )
116+ : this . _peekDirectional ( Direction . Left , fromCoords , 'nowrap' ) ;
143117 }
144118
145119 /**
@@ -152,120 +126,56 @@ export class GridNavigation<T extends GridNavigationCell> {
152126 }
153127
154128 /**
155- * Finds the next focusable cell in a given direction, with continuous wrapping.
156- * This means that when the end of a row/column is reached, it wraps to the
157- * beginning of the next/previous row/column.
129+ * Finds the next focusable cell in a given direction based on the wrapping behavior.
158130 */
159- private _peekContinuous ( delta : Delta , startCoords : RowCol ) : RowCol | undefined {
160- const startCell = this . inputs . grid . getCell ( startCoords ) ;
131+ private _peekDirectional (
132+ delta : Delta ,
133+ fromCoords : RowCol ,
134+ wrap : 'continuous' | 'loop' | 'nowrap' ,
135+ ) : RowCol | undefined {
136+ const fromCell = this . inputs . grid . getCell ( fromCoords ) ;
161137 const maxRowCount = this . inputs . grid . maxRowCount ( ) ;
162138 const maxColCount = this . inputs . grid . maxColCount ( ) ;
163139 const rowDelta = delta . row ?? 0 ;
164140 const colDelta = delta . col ?? 0 ;
165141 const generalDelta = delta . row ?? delta . col ;
166- let nextCoords = { ...startCoords } ;
142+ let nextCoords = { ...fromCoords } ;
167143
168144 for ( let step = 0 ; step < this . _maxSteps ( ) ; step ++ ) {
169145 const isWrapping =
170146 nextCoords . col + colDelta < 0 ||
171147 nextCoords . col + colDelta >= maxColCount ||
172148 nextCoords . row + rowDelta < 0 ||
173149 nextCoords . row + rowDelta >= maxRowCount ;
174- const rowStep = isWrapping ? generalDelta : rowDelta ;
175- const colStep = isWrapping ? generalDelta : colDelta ;
176150
177- nextCoords = {
178- row : ( nextCoords . row + rowStep + maxRowCount ) % maxRowCount ,
179- col : ( nextCoords . col + colStep + maxColCount ) % maxColCount ,
180- } ;
151+ if ( wrap === 'nowrap' && isWrapping ) return ;
181152
182- // Back to original coordinates.
183- if ( nextCoords . row === startCoords . row && nextCoords . col === startCoords . col ) {
184- return undefined ;
185- }
153+ if ( wrap === 'continuous' ) {
154+ const rowStep = isWrapping ? generalDelta : rowDelta ;
155+ const colStep = isWrapping ? generalDelta : colDelta ;
186156
187- const nextCell = this . inputs . grid . getCell ( nextCoords ) ;
188- if (
189- nextCell !== undefined &&
190- nextCell !== startCell &&
191- this . inputs . gridFocus . isFocusable ( nextCell )
192- ) {
193- return nextCoords ;
157+ nextCoords = {
158+ row : ( nextCoords . row + rowStep + maxRowCount ) % maxRowCount ,
159+ col : ( nextCoords . col + colStep + maxColCount ) % maxColCount ,
160+ } ;
194161 }
195- }
196162
197- return undefined ;
198- }
199-
200- /**
201- * Finds the next focusable cell in a given direction, with loop wrapping.
202- * This means that when the end of a row/column is reached, it wraps to the
203- * beginning of the same row/column.
204- */
205- private _peekLoop ( delta : Delta , startCoords : RowCol ) : RowCol | undefined {
206- const startCell = this . inputs . grid . getCell ( startCoords ) ;
207- const maxRowCount = this . inputs . grid . maxRowCount ( ) ;
208- const maxColCount = this . inputs . grid . maxColCount ( ) ;
209- const rowDelta = delta . row ?? 0 ;
210- const colDelta = delta . col ?? 0 ;
211- let nextCoords = { ...startCoords } ;
212-
213- for ( let step = 0 ; step < this . _maxSteps ( ) ; step ++ ) {
214- nextCoords = {
215- row : ( nextCoords . row + rowDelta + maxRowCount ) % maxRowCount ,
216- col : ( nextCoords . col + colDelta + maxColCount ) % maxColCount ,
217- } ;
218-
219- // Back to original coordinates.
220- if ( nextCoords . row === startCoords . row && nextCoords . col === startCoords . col ) {
221- return undefined ;
222- }
223-
224- const nextCell = this . inputs . grid . getCell ( nextCoords ) ;
225- if (
226- nextCell !== undefined &&
227- nextCell !== startCell &&
228- this . inputs . gridFocus . isFocusable ( nextCell )
229- ) {
230- return nextCoords ;
163+ if ( wrap === 'loop' ) {
164+ nextCoords = {
165+ row : ( nextCoords . row + rowDelta + maxRowCount ) % maxRowCount ,
166+ col : ( nextCoords . col + colDelta + maxColCount ) % maxColCount ,
167+ } ;
231168 }
232- }
233-
234- return undefined ;
235- }
236-
237- /**
238- * Finds the next focusable cell in a given direction, without wrapping.
239- * This means that when the end of a row/column is reached, it stops.
240- */
241- private _peek ( delta : Delta , startCoords : RowCol ) : RowCol | undefined {
242- const startCell = this . inputs . grid . getCell ( startCoords ) ;
243- const maxRowCount = this . inputs . grid . maxRowCount ( ) ;
244- const maxColCount = this . inputs . grid . maxColCount ( ) ;
245- const rowDelta = delta . row ?? 0 ;
246- const colDelta = delta . col ?? 0 ;
247- let nextCoords = { ...startCoords } ;
248-
249- for ( let step = 0 ; step < this . _maxSteps ( ) ; step ++ ) {
250- nextCoords = {
251- row : nextCoords . row + rowDelta ,
252- col : nextCoords . col + colDelta ,
253- } ;
254169
255- // Out of boundary.
256- if (
257- nextCoords . row < 0 ||
258- nextCoords . row >= maxRowCount ||
259- nextCoords . col < 0 ||
260- nextCoords . col >= maxColCount
261- ) {
170+ // Back to original coordinates.
171+ if ( nextCoords . row === fromCoords . row && nextCoords . col === fromCoords . col ) {
262172 return undefined ;
263173 }
264174
265175 const nextCell = this . inputs . grid . getCell ( nextCoords ) ;
266176 if (
267177 nextCell !== undefined &&
268- nextCell !== startCell &&
178+ nextCell !== fromCell &&
269179 this . inputs . gridFocus . isFocusable ( nextCell )
270180 ) {
271181 return nextCoords ;
@@ -274,22 +184,4 @@ export class GridNavigation<T extends GridNavigationCell> {
274184
275185 return undefined ;
276186 }
277-
278- /**
279- * Finds the next focusable cell in a given direction based on the wrapping behavior.
280- */
281- private _peekDirectional (
282- delta : Delta ,
283- fromCoords : RowCol ,
284- wrap : 'continuous' | 'loop' | 'nowrap' ,
285- ) : RowCol | undefined {
286- switch ( wrap ) {
287- case 'nowrap' :
288- return this . _peek ( delta , fromCoords ) ;
289- case 'loop' :
290- return this . _peekLoop ( delta , fromCoords ) ;
291- case 'continuous' :
292- return this . _peekContinuous ( delta , fromCoords ) ;
293- }
294- }
295187}
0 commit comments