@navikt/ds-react 8.5.0 → 8.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/cjs/data/table/helpers/table-grid-nav.d.ts +9 -15
  2. package/cjs/data/table/helpers/table-grid-nav.js +18 -25
  3. package/cjs/data/table/helpers/table-grid-nav.js.map +1 -1
  4. package/cjs/data/table/helpers/table-keyboard.d.ts +1 -1
  5. package/cjs/data/table/helpers/table-keyboard.js +1 -6
  6. package/cjs/data/table/helpers/table-keyboard.js.map +1 -1
  7. package/cjs/data/table/root/DataTableRoot.d.ts +14 -4
  8. package/cjs/data/table/root/DataTableRoot.js +4 -6
  9. package/cjs/data/table/root/DataTableRoot.js.map +1 -1
  10. package/cjs/data/table/root/useTableKeyboardNav.d.ts +1 -1
  11. package/cjs/data/table/root/useTableKeyboardNav.js +32 -19
  12. package/cjs/data/table/root/useTableKeyboardNav.js.map +1 -1
  13. package/cjs/data/token-filter/AutoSuggest.d.ts +21 -0
  14. package/cjs/data/token-filter/AutoSuggest.js +129 -0
  15. package/cjs/data/token-filter/AutoSuggest.js.map +1 -0
  16. package/cjs/data/token-filter/TokenFilter.d.ts +11 -0
  17. package/cjs/data/token-filter/TokenFilter.js +91 -0
  18. package/cjs/data/token-filter/TokenFilter.js.map +1 -0
  19. package/cjs/data/token-filter/TokenFilter.types.d.ts +46 -0
  20. package/cjs/data/token-filter/TokenFilter.types.js +3 -0
  21. package/cjs/data/token-filter/TokenFilter.types.js.map +1 -0
  22. package/cjs/data/token-filter/helpers/generate-autocomplete-options.d.ts +70 -0
  23. package/cjs/data/token-filter/helpers/generate-autocomplete-options.js +171 -0
  24. package/cjs/data/token-filter/helpers/generate-autocomplete-options.js.map +1 -0
  25. package/cjs/data/token-filter/helpers/parse-query-text.d.ts +31 -0
  26. package/cjs/data/token-filter/helpers/parse-query-text.js +91 -0
  27. package/cjs/data/token-filter/helpers/parse-query-text.js.map +1 -0
  28. package/cjs/tooltip/Tooltip.js +1 -1
  29. package/cjs/tooltip/Tooltip.js.map +1 -1
  30. package/cjs/utils/i18n/locales/nb.d.ts +75 -154
  31. package/cjs/utils/i18n/locales/nb.js +75 -154
  32. package/cjs/utils/i18n/locales/nb.js.map +1 -1
  33. package/esm/data/table/helpers/table-grid-nav.d.ts +9 -15
  34. package/esm/data/table/helpers/table-grid-nav.js +18 -25
  35. package/esm/data/table/helpers/table-grid-nav.js.map +1 -1
  36. package/esm/data/table/helpers/table-keyboard.d.ts +1 -1
  37. package/esm/data/table/helpers/table-keyboard.js +1 -6
  38. package/esm/data/table/helpers/table-keyboard.js.map +1 -1
  39. package/esm/data/table/root/DataTableRoot.d.ts +14 -4
  40. package/esm/data/table/root/DataTableRoot.js +4 -6
  41. package/esm/data/table/root/DataTableRoot.js.map +1 -1
  42. package/esm/data/table/root/useTableKeyboardNav.d.ts +1 -1
  43. package/esm/data/table/root/useTableKeyboardNav.js +32 -19
  44. package/esm/data/table/root/useTableKeyboardNav.js.map +1 -1
  45. package/esm/data/token-filter/AutoSuggest.d.ts +21 -0
  46. package/esm/data/token-filter/AutoSuggest.js +93 -0
  47. package/esm/data/token-filter/AutoSuggest.js.map +1 -0
  48. package/esm/data/token-filter/TokenFilter.d.ts +11 -0
  49. package/esm/data/token-filter/TokenFilter.js +55 -0
  50. package/esm/data/token-filter/TokenFilter.js.map +1 -0
  51. package/esm/data/token-filter/TokenFilter.types.d.ts +46 -0
  52. package/esm/data/token-filter/TokenFilter.types.js +2 -0
  53. package/esm/data/token-filter/TokenFilter.types.js.map +1 -0
  54. package/esm/data/token-filter/helpers/generate-autocomplete-options.d.ts +70 -0
  55. package/esm/data/token-filter/helpers/generate-autocomplete-options.js +169 -0
  56. package/esm/data/token-filter/helpers/generate-autocomplete-options.js.map +1 -0
  57. package/esm/data/token-filter/helpers/parse-query-text.d.ts +31 -0
  58. package/esm/data/token-filter/helpers/parse-query-text.js +87 -0
  59. package/esm/data/token-filter/helpers/parse-query-text.js.map +1 -0
  60. package/esm/tooltip/Tooltip.js +2 -2
  61. package/esm/tooltip/Tooltip.js.map +1 -1
  62. package/esm/utils/i18n/locales/nb.d.ts +75 -154
  63. package/esm/utils/i18n/locales/nb.js +75 -154
  64. package/esm/utils/i18n/locales/nb.js.map +1 -1
  65. package/package.json +3 -3
  66. package/src/data/table/helpers/table-grid-nav.test.ts +659 -0
  67. package/src/data/table/helpers/table-grid-nav.ts +19 -38
  68. package/src/data/table/helpers/table-keyboard.ts +1 -10
  69. package/src/data/table/root/DataTableRoot.tsx +21 -10
  70. package/src/data/table/root/useTableKeyboardNav.ts +35 -23
  71. package/src/data/token-filter/AutoSuggest.tsx +179 -0
  72. package/src/data/token-filter/TokenFilter.tsx +124 -0
  73. package/src/data/token-filter/TokenFilter.types.ts +79 -0
  74. package/src/data/token-filter/helpers/generate-autocomplete-options.ts +244 -0
  75. package/src/data/token-filter/helpers/parse-query-text.test.ts +410 -0
  76. package/src/data/token-filter/helpers/parse-query-text.ts +148 -0
  77. package/src/tooltip/Tooltip.tsx +3 -3
  78. package/src/utils/i18n/locales/nb.ts +4 -83
@@ -0,0 +1,659 @@
1
+ import { afterEach, describe, expect, test } from "vitest";
2
+ import {
3
+ buildTableGridMap,
4
+ findFirstCell,
5
+ findFirstCellInRow,
6
+ findLastCell,
7
+ findLastCellInRow,
8
+ findNextFocusableCell,
9
+ getNextGridPosition,
10
+ isCellFocusable,
11
+ } from "./table-grid-nav";
12
+
13
+ let container: HTMLDivElement;
14
+
15
+ afterEach(() => {
16
+ container?.parentNode && document.body.removeChild(container);
17
+ });
18
+
19
+ function createTable(html: string): HTMLTableElement {
20
+ container = document.createElement("div");
21
+ container.innerHTML = html;
22
+ document.body.appendChild(container);
23
+ return container.querySelector("table")!;
24
+ }
25
+
26
+ describe("buildTableGridMap", () => {
27
+ test("should build grid for simple 2x2 table without spans", () => {
28
+ const table = createTable(`
29
+ <table>
30
+ <tr>
31
+ <td>A1</td>
32
+ <td>B1</td>
33
+ </tr>
34
+ <tr>
35
+ <td>A2</td>
36
+ <td>B2</td>
37
+ </tr>
38
+ </table>
39
+ `);
40
+
41
+ const { grid, positions } = buildTableGridMap(table);
42
+
43
+ expect(grid.length).toBe(2);
44
+ expect(grid[0].length).toBe(2);
45
+ expect(grid[1].length).toBe(2);
46
+
47
+ const cells = Array.from(table.querySelectorAll("td"));
48
+ expect(grid[0][0]).toBe(cells[0]);
49
+ expect(grid[0][1]).toBe(cells[1]);
50
+ expect(grid[1][0]).toBe(cells[2]);
51
+ expect(grid[1][1]).toBe(cells[3]);
52
+
53
+ expect(positions.get(cells[0])).toEqual({ x: 0, y: 0 });
54
+ expect(positions.get(cells[1])).toEqual({ x: 1, y: 0 });
55
+ expect(positions.get(cells[2])).toEqual({ x: 0, y: 1 });
56
+ expect(positions.get(cells[3])).toEqual({ x: 1, y: 1 });
57
+ });
58
+
59
+ test("should handle colspan correctly", () => {
60
+ const table = createTable(`
61
+ <table>
62
+ <tr>
63
+ <td colspan="2">A1-B1</td>
64
+ </tr>
65
+ <tr>
66
+ <td>A2</td>
67
+ <td>B2</td>
68
+ </tr>
69
+ </table>
70
+ `);
71
+
72
+ const { grid, positions } = buildTableGridMap(table);
73
+
74
+ expect(grid.length).toBe(2);
75
+ expect(grid[0].length).toBe(2);
76
+ expect(grid[1].length).toBe(2);
77
+
78
+ const cells = Array.from(table.querySelectorAll("td"));
79
+ const cellWithColspan = cells[0];
80
+
81
+ expect(grid[0][0]).toBe(cellWithColspan);
82
+ expect(grid[0][1]).toBe(cellWithColspan);
83
+ expect(grid[1][0]).toBe(cells[1]);
84
+ expect(grid[1][1]).toBe(cells[2]);
85
+
86
+ expect(positions.get(cellWithColspan)).toEqual({ x: 0, y: 0 });
87
+ expect(positions.get(cells[1])).toEqual({ x: 0, y: 1 });
88
+ expect(positions.get(cells[2])).toEqual({ x: 1, y: 1 });
89
+ });
90
+
91
+ test("should handle rowspan correctly", () => {
92
+ const table = createTable(`
93
+ <table>
94
+ <tr>
95
+ <td rowspan="2">A1-A2</td>
96
+ <td>B1</td>
97
+ </tr>
98
+ <tr>
99
+ <td>B2</td>
100
+ </tr>
101
+ </table>
102
+ `);
103
+
104
+ const { grid, positions } = buildTableGridMap(table);
105
+
106
+ expect(grid.length).toBe(2);
107
+ expect(grid[0].length).toBe(2);
108
+ expect(grid[1].length).toBe(2);
109
+
110
+ const cells = Array.from(table.querySelectorAll("td"));
111
+ const cellWithRowspan = cells[0];
112
+
113
+ expect(grid[0][0]).toBe(cellWithRowspan);
114
+ expect(grid[0][1]).toBe(cells[1]);
115
+ expect(grid[1][0]).toBe(cellWithRowspan);
116
+ expect(grid[1][1]).toBe(cells[2]);
117
+
118
+ expect(positions.get(cellWithRowspan)).toEqual({ x: 0, y: 0 });
119
+ expect(positions.get(cells[1])).toEqual({ x: 1, y: 0 });
120
+ expect(positions.get(cells[2])).toEqual({ x: 1, y: 1 });
121
+ });
122
+
123
+ test("should handle both colspan and rowspan", () => {
124
+ const table = createTable(`
125
+ <table>
126
+ <tr>
127
+ <td colspan="2" rowspan="2">A1-B1-A2-B2</td>
128
+ <td>C1</td>
129
+ </tr>
130
+ <tr>
131
+ <td>C2</td>
132
+ </tr>
133
+ </table>
134
+ `);
135
+
136
+ const { grid, positions } = buildTableGridMap(table);
137
+
138
+ expect(grid.length).toBe(2);
139
+ expect(grid[0].length).toBe(3);
140
+ expect(grid[1].length).toBe(3);
141
+
142
+ const cells = Array.from(table.querySelectorAll("td"));
143
+ const spanningCell = cells[0];
144
+
145
+ expect(grid[0][0]).toBe(spanningCell);
146
+ expect(grid[0][1]).toBe(spanningCell);
147
+ expect(grid[0][2]).toBe(cells[1]);
148
+ expect(grid[1][0]).toBe(spanningCell);
149
+ expect(grid[1][1]).toBe(spanningCell);
150
+ expect(grid[1][2]).toBe(cells[2]);
151
+
152
+ expect(positions.get(spanningCell)).toEqual({ x: 0, y: 0 });
153
+ expect(positions.get(cells[1])).toEqual({ x: 2, y: 0 });
154
+ expect(positions.get(cells[2])).toEqual({ x: 2, y: 1 });
155
+ });
156
+
157
+ test("should handle complex table with multiple spans", () => {
158
+ const table = createTable(`
159
+ <table>
160
+ <tr>
161
+ <td>A1</td>
162
+ <td colspan="2">B1-C1</td>
163
+ <td>D1</td>
164
+ </tr>
165
+ <tr>
166
+ <td rowspan="2">A2-A3</td>
167
+ <td>B2</td>
168
+ <td>C2</td>
169
+ <td>D2</td>
170
+ </tr>
171
+ <tr>
172
+ <td>B3</td>
173
+ <td colspan="2">C3-D3</td>
174
+ </tr>
175
+ </table>
176
+ `);
177
+
178
+ const { grid, positions } = buildTableGridMap(table);
179
+
180
+ expect(grid.length).toBe(3);
181
+ expect(grid[0].length).toBe(4);
182
+ expect(grid[1].length).toBe(4);
183
+ expect(grid[2].length).toBe(4);
184
+
185
+ const cells = Array.from(table.querySelectorAll("td"));
186
+
187
+ expect(grid[0][0]).toBe(cells[0]);
188
+ expect(grid[0][1]).toBe(cells[1]);
189
+ expect(grid[0][2]).toBe(cells[1]);
190
+ expect(grid[0][3]).toBe(cells[2]);
191
+
192
+ expect(grid[1][0]).toBe(cells[3]);
193
+ expect(grid[1][1]).toBe(cells[4]);
194
+ expect(grid[1][2]).toBe(cells[5]);
195
+ expect(grid[1][3]).toBe(cells[6]);
196
+
197
+ expect(grid[2][0]).toBe(cells[3]);
198
+ expect(grid[2][1]).toBe(cells[7]);
199
+ expect(grid[2][2]).toBe(cells[8]);
200
+ expect(grid[2][3]).toBe(cells[8]);
201
+
202
+ expect(positions.get(cells[0])).toEqual({ x: 0, y: 0 });
203
+ expect(positions.get(cells[1])).toEqual({ x: 1, y: 0 });
204
+ expect(positions.get(cells[2])).toEqual({ x: 3, y: 0 });
205
+ expect(positions.get(cells[3])).toEqual({ x: 0, y: 1 });
206
+ expect(positions.get(cells[4])).toEqual({ x: 1, y: 1 });
207
+ expect(positions.get(cells[5])).toEqual({ x: 2, y: 1 });
208
+ expect(positions.get(cells[6])).toEqual({ x: 3, y: 1 });
209
+ expect(positions.get(cells[7])).toEqual({ x: 1, y: 2 });
210
+ expect(positions.get(cells[8])).toEqual({ x: 2, y: 2 });
211
+ });
212
+
213
+ test("should handle table with thead, tbody, and tfoot", () => {
214
+ const table = createTable(`
215
+ <table>
216
+ <thead>
217
+ <tr>
218
+ <th>Header 1</th>
219
+ <th>Header 2</th>
220
+ </tr>
221
+ </thead>
222
+ <tbody>
223
+ <tr>
224
+ <td>Body 1</td>
225
+ <td>Body 2</td>
226
+ </tr>
227
+ </tbody>
228
+ <tfoot>
229
+ <tr>
230
+ <td>Footer 1</td>
231
+ <td>Footer 2</td>
232
+ </tr>
233
+ </tfoot>
234
+ </table>
235
+ `);
236
+
237
+ const { grid } = buildTableGridMap(table);
238
+
239
+ expect(grid.length).toBe(3);
240
+ expect(grid[0].length).toBe(2);
241
+ expect(grid[1].length).toBe(2);
242
+ expect(grid[2].length).toBe(2);
243
+
244
+ const headerCells = Array.from(table.querySelectorAll("th"));
245
+ const bodyCells = Array.from(table.querySelectorAll("tbody td"));
246
+ const footerCells = Array.from(table.querySelectorAll("tfoot td"));
247
+
248
+ expect(grid[0][0]).toBe(headerCells[0]);
249
+ expect(grid[0][1]).toBe(headerCells[1]);
250
+ expect(grid[1][0]).toBe(bodyCells[0]);
251
+ expect(grid[1][1]).toBe(bodyCells[1]);
252
+ expect(grid[2][0]).toBe(footerCells[0]);
253
+ expect(grid[2][1]).toBe(footerCells[1]);
254
+ });
255
+
256
+ test("should handle empty table", () => {
257
+ const table = createTable("<table></table>");
258
+
259
+ const { grid, positions } = buildTableGridMap(table);
260
+
261
+ expect(grid.length).toBe(0);
262
+ expect(positions.size).toBe(0);
263
+ });
264
+
265
+ test("should handle table with empty row", () => {
266
+ const table = createTable(`
267
+ <table>
268
+ <tr></tr>
269
+ </table>
270
+ `);
271
+
272
+ const { grid, positions } = buildTableGridMap(table);
273
+
274
+ expect(grid.length).toBe(1);
275
+ expect(grid[0].length).toBe(0);
276
+ expect(positions.size).toBe(0);
277
+ });
278
+
279
+ test("should handle colspan=0 and rowspan=0 as 1", () => {
280
+ const table = createTable(`
281
+ <table>
282
+ <tr>
283
+ <td colspan="0">A</td>
284
+ <td>B</td>
285
+ </tr>
286
+ <tr>
287
+ <td rowspan="0">C</td>
288
+ <td>D</td>
289
+ </tr>
290
+ </table>
291
+ `);
292
+
293
+ const { grid } = buildTableGridMap(table);
294
+ const cells = Array.from(table.querySelectorAll("td"));
295
+
296
+ expect(grid[0][0]).toBe(cells[0]);
297
+ expect(grid[0][1]).toBe(cells[1]);
298
+ expect(grid[1][0]).toBe(cells[2]);
299
+ expect(grid[1][1]).toBe(cells[3]);
300
+ });
301
+
302
+ test("should handle negative span values as 1", () => {
303
+ const table = createTable(`
304
+ <table>
305
+ <tr>
306
+ <td colspan="-1">A</td>
307
+ <td>B</td>
308
+ </tr>
309
+ <tr>
310
+ <td rowspan="-2">C</td>
311
+ <td>D</td>
312
+ </tr>
313
+ </table>
314
+ `);
315
+
316
+ const { grid } = buildTableGridMap(table);
317
+ const cells = Array.from(table.querySelectorAll("td"));
318
+
319
+ expect(grid[0][0]).toBe(cells[0]);
320
+ expect(grid[0][1]).toBe(cells[1]);
321
+ expect(grid[1][0]).toBe(cells[2]);
322
+ expect(grid[1][1]).toBe(cells[3]);
323
+ });
324
+
325
+ test("should handle large span values", () => {
326
+ const table = createTable(`
327
+ <table>
328
+ <tr>
329
+ <td colspan="5">Wide cell</td>
330
+ </tr>
331
+ <tr>
332
+ <td>A</td>
333
+ <td>B</td>
334
+ <td>C</td>
335
+ <td>D</td>
336
+ <td>E</td>
337
+ </tr>
338
+ </table>
339
+ `);
340
+
341
+ const { grid, positions } = buildTableGridMap(table);
342
+ const cells = Array.from(table.querySelectorAll("td"));
343
+ const wideCell = cells[0];
344
+
345
+ expect(grid[0].length).toBe(5);
346
+ expect(grid[0][0]).toBe(wideCell);
347
+ expect(grid[0][1]).toBe(wideCell);
348
+ expect(grid[0][2]).toBe(wideCell);
349
+ expect(grid[0][3]).toBe(wideCell);
350
+ expect(grid[0][4]).toBe(wideCell);
351
+
352
+ expect(positions.get(wideCell)).toEqual({ x: 0, y: 0 });
353
+ });
354
+
355
+ test("should skip over slots occupied by previous spans", () => {
356
+ const table = createTable(`
357
+ <table>
358
+ <tr>
359
+ <td rowspan="2">A</td>
360
+ <td>B</td>
361
+ <td>C</td>
362
+ </tr>
363
+ <tr>
364
+ <td>D</td>
365
+ <td>E</td>
366
+ </tr>
367
+ </table>
368
+ `);
369
+
370
+ const { grid, positions } = buildTableGridMap(table);
371
+ const cells = Array.from(table.querySelectorAll("td"));
372
+
373
+ expect(grid[0][0]).toBe(cells[0]);
374
+ expect(grid[0][1]).toBe(cells[1]);
375
+ expect(grid[0][2]).toBe(cells[2]);
376
+ expect(grid[1][0]).toBe(cells[0]);
377
+ expect(grid[1][1]).toBe(cells[3]);
378
+ expect(grid[1][2]).toBe(cells[4]);
379
+
380
+ expect(positions.get(cells[3])).toEqual({ x: 1, y: 1 });
381
+ expect(positions.get(cells[4])).toEqual({ x: 2, y: 1 });
382
+ });
383
+ });
384
+
385
+ describe("getNextGridPosition", () => {
386
+ test("should return null when moving out of bounds", () => {
387
+ const grid = [
388
+ [undefined, undefined],
389
+ [undefined, undefined],
390
+ ];
391
+ const down = getNextGridPosition(grid, { x: 0, y: 1 }, { x: 0, y: 1 });
392
+ const up = getNextGridPosition(grid, { x: 0, y: 0 }, { x: 0, y: -1 });
393
+ const right = getNextGridPosition(grid, { x: 1, y: 0 }, { x: 1, y: 0 });
394
+ const left = getNextGridPosition(grid, { x: 0, y: 0 }, { x: -1, y: 0 });
395
+
396
+ expect(down).toBeNull();
397
+ expect(up).toBeNull();
398
+ expect(right).toBeNull();
399
+ expect(left).toBeNull();
400
+ });
401
+
402
+ test("should handle empty grid", () => {
403
+ const grid: (Element | undefined)[][] = [];
404
+ const result = getNextGridPosition(grid, { x: 0, y: 0 }, { x: 1, y: 0 });
405
+ expect(result).toBeNull();
406
+ });
407
+ });
408
+
409
+ describe("isCellFocusable", () => {
410
+ test("should return false when cell is undefined", () => {
411
+ expect(isCellFocusable(undefined)).toBe(false);
412
+ });
413
+
414
+ test("should return true when cell has focusable elements", () => {
415
+ const table = createTable(`
416
+ <table>
417
+ <tr>
418
+ <td>A</td>
419
+ <td><button>B</button></td>
420
+ </tr>
421
+ </table>
422
+ `);
423
+ const cells = table.querySelectorAll("td");
424
+
425
+ expect(isCellFocusable(cells[1])).toBe(true);
426
+ });
427
+ });
428
+
429
+ describe("findNextFocusableCell", () => {
430
+ test("should find next focusable cell to the right", () => {
431
+ const table = createTable(`
432
+ <table>
433
+ <tr>
434
+ <td><button>A</button></td>
435
+ <td><button>B</button></td>
436
+ <td><button>C</button></td>
437
+ </tr>
438
+ </table>
439
+ `);
440
+
441
+ const { grid, positions } = buildTableGridMap(table);
442
+ const cells = Array.from(table.querySelectorAll("td"));
443
+
444
+ const currentPos = positions.get(cells[0])!;
445
+ const result = findNextFocusableCell(
446
+ grid,
447
+ currentPos,
448
+ { x: 1, y: 0 },
449
+ cells[0],
450
+ );
451
+
452
+ expect(result).toBe(cells[1]);
453
+ });
454
+
455
+ test("should not skip non-focusable cells", () => {
456
+ const table = createTable(`
457
+ <table>
458
+ <tr>
459
+ <td><button>A</button></td>
460
+ <td>B</td>
461
+ <td><button>C</button></td>
462
+ </tr>
463
+ </table>
464
+ `);
465
+
466
+ const { grid, positions } = buildTableGridMap(table);
467
+ const cells = Array.from(table.querySelectorAll("td"));
468
+
469
+ const currentPos = positions.get(cells[0])!;
470
+ const result = findNextFocusableCell(
471
+ grid,
472
+ currentPos,
473
+ { x: 1, y: 0 },
474
+ cells[0],
475
+ );
476
+
477
+ expect(result).toBe(cells[1]);
478
+ });
479
+
480
+ test("should return null when reaching edge of grid", () => {
481
+ const table = createTable(`
482
+ <table>
483
+ <tr>
484
+ <td><button>A</button></td>
485
+ <td>B</td>
486
+ </tr>
487
+ </table>
488
+ `);
489
+
490
+ const { grid, positions } = buildTableGridMap(table);
491
+ const cells = Array.from(table.querySelectorAll("td"));
492
+
493
+ const currentPos = positions.get(cells[1])!;
494
+ const result = findNextFocusableCell(
495
+ grid,
496
+ currentPos,
497
+ { x: 1, y: 0 },
498
+ cells[1],
499
+ );
500
+
501
+ expect(result).toBeNull();
502
+ });
503
+
504
+ test("should find next focusable cell downward", () => {
505
+ const table = createTable(`
506
+ <table>
507
+ <tr><td><button>A</button></td></tr>
508
+ <tr><td><button>B</button></td></tr>
509
+ </table>
510
+ `);
511
+
512
+ const { grid, positions } = buildTableGridMap(table);
513
+ const cells = Array.from(table.querySelectorAll("td"));
514
+
515
+ const currentPos = positions.get(cells[0])!;
516
+ const result = findNextFocusableCell(
517
+ grid,
518
+ currentPos,
519
+ { x: 0, y: 1 },
520
+ cells[0],
521
+ );
522
+
523
+ expect(result).toBe(cells[1]);
524
+ });
525
+ });
526
+
527
+ describe("findFirstCellInRow", () => {
528
+ test("should find first focusable cell in row", () => {
529
+ const table = createTable(`
530
+ <table>
531
+ <tr>
532
+ <td><button>A</button></td>
533
+ <td><button>B</button></td>
534
+ <td><button>C</button></td>
535
+ </tr>
536
+ </table>
537
+ `);
538
+
539
+ const { grid } = buildTableGridMap(table);
540
+ const cells = Array.from(table.querySelectorAll("td"));
541
+
542
+ const result = findFirstCellInRow(grid, 0);
543
+
544
+ expect(result).toBe(cells[0]);
545
+ });
546
+ });
547
+
548
+ describe("findLastCellInRow", () => {
549
+ test("should find last focusable cell in row", () => {
550
+ const table = createTable(`
551
+ <table>
552
+ <tr>
553
+ <td><button>A</button></td>
554
+ <td><button>B</button></td>
555
+ <td><button>C</button></td>
556
+ </tr>
557
+ </table>
558
+ `);
559
+
560
+ const { grid } = buildTableGridMap(table);
561
+ const cells = Array.from(table.querySelectorAll("td"));
562
+
563
+ const result = findLastCellInRow(grid, 0);
564
+
565
+ expect(result).toBe(cells[2]);
566
+ });
567
+ });
568
+
569
+ describe("findFirstCell", () => {
570
+ test("should find first focusable cell in table", () => {
571
+ const table = createTable(`
572
+ <table>
573
+ <tr>
574
+ <td><button>A</button></td>
575
+ <td><button>B</button></td>
576
+ </tr>
577
+ <tr>
578
+ <td><button>C</button></td>
579
+ <td><button>D</button></td>
580
+ </tr>
581
+ </table>
582
+ `);
583
+
584
+ const { grid } = buildTableGridMap(table);
585
+ const cells = Array.from(table.querySelectorAll("td"));
586
+
587
+ const result = findFirstCell(grid);
588
+
589
+ expect(result).toBe(cells[0]);
590
+ });
591
+
592
+ test("should skip non-focusable cells", () => {
593
+ const table = createTable(`
594
+ <table>
595
+ <tr>
596
+ <td style="visibility:hidden;">A</td>
597
+ <td>B</td>
598
+ </tr>
599
+ <tr>
600
+ <td><button>C</button></td>
601
+ <td><button>D</button></td>
602
+ </tr>
603
+ </table>
604
+ `);
605
+
606
+ const { grid } = buildTableGridMap(table);
607
+ const cells = Array.from(table.querySelectorAll("td"));
608
+
609
+ const result = findFirstCell(grid);
610
+
611
+ expect(result).toBe(cells[1]);
612
+ });
613
+ });
614
+
615
+ describe("findLastCell", () => {
616
+ test("should find last focusable cell in table", () => {
617
+ const table = createTable(`
618
+ <table>
619
+ <tr>
620
+ <td><button>A</button></td>
621
+ <td><button>B</button></td>
622
+ </tr>
623
+ <tr>
624
+ <td><button>C</button></td>
625
+ <td><button>D</button></td>
626
+ </tr>
627
+ </table>
628
+ `);
629
+
630
+ const { grid } = buildTableGridMap(table);
631
+ const cells = Array.from(table.querySelectorAll("td"));
632
+
633
+ const result = findLastCell(grid);
634
+
635
+ expect(result).toBe(cells[3]);
636
+ });
637
+
638
+ test("should skip non-focusable cells", () => {
639
+ const table = createTable(`
640
+ <table>
641
+ <tr>
642
+ <td><button>A</button></td>
643
+ <td><button>B</button></td>
644
+ </tr>
645
+ <tr>
646
+ <td><button>C</button></td>
647
+ <td style="visibility:hidden;">D</td>
648
+ </tr>
649
+ </table>
650
+ `);
651
+
652
+ const { grid } = buildTableGridMap(table);
653
+ const cells = Array.from(table.querySelectorAll("td"));
654
+
655
+ const result = findLastCell(grid);
656
+
657
+ expect(result).toBe(cells[2]);
658
+ });
659
+ });