Skip to content

Commit 6e57d82

Browse files
authored
UI x-radio: fix keyboard navigation when value of first/last radio option is null (#4308)
* Add failing tests * Fix keyboard navigation bug * Refactoring
1 parent 3c0cbea commit 6e57d82

File tree

2 files changed

+47
-4
lines changed

2 files changed

+47
-4
lines changed

packages/ui/src/radio.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,17 @@ function handleRoot(el, Alpine) {
106106
__focusOptionNext() {
107107
let option = this.__active
108108
let all = this.__optionValues.filter(i => !this.__disabledOptions.has(i))
109-
let next = all[this.__optionValues.indexOf(option) + 1]
110-
next = next || all[0]
109+
let index = all.indexOf(option)
110+
let next = all[(index + 1 + all.length) % all.length]
111111

112112
this.__optionElsByValue.get(next).focus()
113113
this.__change(next)
114114
},
115115
__focusOptionPrev() {
116116
let option = this.__active
117117
let all = this.__optionValues.filter(i => !this.__disabledOptions.has(i))
118-
let prev = all[all.indexOf(option) - 1]
119-
prev = prev || all.slice(-1)[0]
118+
let index = all.indexOf(option)
119+
let prev = all[(index - 1 + all.length) % all.length]
120120

121121
this.__optionElsByValue.get(prev).focus()
122122
this.__change(prev)

tests/cypress/integration/plugins/ui/radio.spec.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,49 @@ test('keyboard navigation works',
253253
},
254254
)
255255

256+
test('keyboard navigation works when first option has null as value',
257+
[html`
258+
<main x-data="{ active: null }">
259+
<div x-radio x-model="active">
260+
<div x-radio:option option="option-null" :value="null"></div>
261+
<div x-radio:option option="option-1" value="1"></div>
262+
<div x-radio:option option="option-2" value="2"></div>
263+
</div>
264+
265+
<input x-model="active" type="hidden">
266+
</main>
267+
`],
268+
({ get }) => {
269+
get('[option="option-1"]').focus().type('{downarrow}')
270+
get('[option="option-2"]').should(haveFocus()).type('{uparrow}')
271+
get('[option="option-1"]').should(haveFocus()).type('{uparrow}')
272+
get('[option="option-null"]').should(haveFocus()).type('{uparrow}')
273+
get('[option="option-2"]').should(haveFocus()).type('{downarrow}')
274+
get('[option="option-null"]').should(haveFocus())
275+
},
276+
)
277+
278+
test('keyboard navigation works when last option has null as value',
279+
[html`
280+
<main x-data="{ active: null }">
281+
<div x-radio x-model="active">
282+
<div x-radio:option option="option-1" value="1"></div>
283+
<div x-radio:option option="option-2" value="2"></div>
284+
<div x-radio:option option="option-null" :value="null"></div>
285+
</div>
286+
287+
<input x-model="active" type="hidden">
288+
</main>
289+
`],
290+
({ get }) => {
291+
get('[option="option-1"]').focus().type('{downarrow}')
292+
get('[option="option-2"]').should(haveFocus()).type('{downarrow}')
293+
get('[option="option-null"]').should(haveFocus()).type('{downarrow}')
294+
get('[option="option-1"]').should(haveFocus()).type('{uparrow}')
295+
get('[option="option-null"]').should(haveFocus())
296+
},
297+
)
298+
256299
test('has accessibility attributes',
257300
[html`
258301
<main x-data="{ active: null, access: [

0 commit comments

Comments
 (0)