Skip to content

Commit 1cf5539

Browse files
Merge pull request #3311 from bcgov/fix/hamed-enter-key-row-selection-3270
Fix: Allow Row Selection with Enter Key - 3270
2 parents e13db93 + 89e7d3c commit 1cf5539

File tree

3 files changed

+146
-5
lines changed

3 files changed

+146
-5
lines changed

frontend/src/components/BCDataGrid/BCGridBase.jsx

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,15 @@ import { useSearchParams } from 'react-router-dom'
2020
const ROW_HEIGHT = 45
2121

2222
export const BCGridBase = forwardRef(
23-
({ autoSizeStrategy, autoHeight, ...props }, forwardedRef) => {
23+
(
24+
{
25+
autoSizeStrategy,
26+
autoHeight,
27+
onRowClicked,
28+
...props
29+
},
30+
forwardedRef
31+
) => {
2432
ModuleRegistry.registerModules([ClientSideRowModelModule, CsvExportModule])
2533
const [searchParams] = useSearchParams()
2634
const highlightedId = searchParams.get('hid')
@@ -91,6 +99,41 @@ export const BCGridBase = forwardRef(
9199
}
92100
}, [])
93101

102+
// Handle keyboard events for row navigation
103+
const onCellKeyDown = useCallback(
104+
(params) => {
105+
const e = params.event
106+
107+
if (e.code === 'Enter' && params.node) {
108+
const cellEl = e.target.closest('.ag-cell')
109+
if (!cellEl) return
110+
111+
const link = cellEl.querySelector('a[href]')
112+
if (link) {
113+
e.preventDefault()
114+
e.stopPropagation()
115+
link.click()
116+
return
117+
}
118+
119+
const focusables = cellEl.querySelectorAll(
120+
'button, [href], :not(.ag-hidden) > input, select, textarea, [tabindex]:not([tabindex="-1"])'
121+
)
122+
if (focusables.length === 0 && onRowClicked) {
123+
e.preventDefault()
124+
e.stopPropagation()
125+
onRowClicked({ ...params, event: e })
126+
return
127+
}
128+
}
129+
130+
if (props.onCellKeyDown) {
131+
props.onCellKeyDown(params)
132+
}
133+
},
134+
[props.onCellKeyDown, onRowClicked]
135+
)
136+
94137
// Expose clearFilters method through ref
95138
useImperativeHandle(forwardedRef, () => ({
96139
...ref.current,
@@ -138,7 +181,9 @@ export const BCGridBase = forwardRef(
138181
rowHeight={ROW_HEIGHT}
139182
headerHeight={40}
140183
{...props}
184+
onCellKeyDown={onCellKeyDown}
141185
onGridReady={onGridReady}
186+
onRowClicked={onRowClicked}
142187
/>
143188
)
144189
}

frontend/src/utils/grid/__tests__/eventHandlers.test.js

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it, expect, beforeEach } from 'vitest'
1+
import { describe, it, expect, beforeEach, vi } from 'vitest'
22
import { suppressKeyboardEvent, isEqual } from '../eventHandlers'
33

44
// Helper to build a basic ag-cell DOM structure
@@ -55,6 +55,86 @@ describe('suppressKeyboardEvent', () => {
5555
event = { code: 'Tab', srcElement: input1, shiftKey: false, key: 'Tab' }
5656
expect(suppressKeyboardEvent({ event })).toBe(true)
5757
})
58+
59+
it('triggers row navigation when Enter is pressed and keyboard navigation is enabled', () => {
60+
const cell = createCell()
61+
const event = { code: 'Enter', srcElement: cell }
62+
const mockOnRowClicked = vi.fn()
63+
const mockParams = {
64+
event,
65+
node: { id: 'test-row' },
66+
data: { id: 1 }
67+
}
68+
69+
const result = suppressKeyboardEvent(mockParams, {
70+
enableKeyboardRowNavigation: true,
71+
onRowClicked: mockOnRowClicked
72+
})
73+
74+
expect(result).toBe(true) // Should suppress default behavior
75+
expect(mockOnRowClicked).toHaveBeenCalledWith({
76+
...mockParams,
77+
event: expect.objectContaining({
78+
target: cell,
79+
currentTarget: cell
80+
})
81+
})
82+
})
83+
84+
it('does not trigger row navigation when Enter is pressed but keyboard navigation is disabled', () => {
85+
const cell = createCell()
86+
const event = { code: 'Enter', srcElement: cell }
87+
const mockOnRowClicked = vi.fn()
88+
const mockParams = {
89+
event,
90+
node: { id: 'test-row' },
91+
data: { id: 1 }
92+
}
93+
94+
const result = suppressKeyboardEvent(mockParams, {
95+
enableKeyboardRowNavigation: false,
96+
onRowClicked: mockOnRowClicked
97+
})
98+
99+
expect(result).toBe(false) // Should allow default behavior
100+
expect(mockOnRowClicked).not.toHaveBeenCalled()
101+
})
102+
103+
it('does not trigger row navigation when Enter is pressed but no onRowClicked handler is provided', () => {
104+
const cell = createCell()
105+
const event = { code: 'Enter', srcElement: cell }
106+
const mockParams = {
107+
event,
108+
node: { id: 'test-row' },
109+
data: { id: 1 }
110+
}
111+
112+
const result = suppressKeyboardEvent(mockParams, {
113+
enableKeyboardRowNavigation: true,
114+
onRowClicked: null
115+
})
116+
117+
expect(result).toBe(false) // Should allow default behavior
118+
})
119+
120+
it('does not trigger row navigation when Enter is pressed but no node is provided', () => {
121+
const cell = createCell()
122+
const event = { code: 'Enter', srcElement: cell }
123+
const mockOnRowClicked = vi.fn()
124+
const mockParams = {
125+
event,
126+
node: null,
127+
data: { id: 1 }
128+
}
129+
130+
const result = suppressKeyboardEvent(mockParams, {
131+
enableKeyboardRowNavigation: true,
132+
onRowClicked: mockOnRowClicked
133+
})
134+
135+
expect(result).toBe(false) // Should allow default behavior
136+
expect(mockOnRowClicked).not.toHaveBeenCalled()
137+
})
58138
})
59139

60140
describe('isEqual utility', () => {

frontend/src/utils/grid/eventHandlers.jsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
1-
export const suppressKeyboardEvent = (params) => {
1+
export const suppressKeyboardEvent = (params, options = {}) => {
22
const e = params.event
3+
const { enableKeyboardRowNavigation = false, onRowClicked } = options
4+
35
if (e.code === 'Enter') {
46
const focusableChildrenOfParent = e.srcElement
57
.closest('.ag-cell')
68
.querySelectorAll(
79
'button, [href], :not(.ag-hidden) > input, select, textarea, [tabindex]:not([tabindex="-1"])'
810
)
9-
if (focusableChildrenOfParent.length === 0) return false
10-
else return true
11+
12+
if (focusableChildrenOfParent.length === 0) {
13+
if (enableKeyboardRowNavigation && onRowClicked && params.node) {
14+
const mockClickEvent = {
15+
...e,
16+
target: e.srcElement,
17+
currentTarget: e.srcElement
18+
}
19+
onRowClicked({
20+
...params,
21+
event: mockClickEvent
22+
})
23+
return true
24+
}
25+
return false
26+
} else return true
1127
}
1228
if (e.code === 'Tab' || e.key === 'Tab' || e.code === 'ShiftLeft') {
1329
// get focusable children of parent cell

0 commit comments

Comments
 (0)