mirror of
https://github.com/phranck/TUIkit.git
synced 2026-06-20 09:54:37 +00:00
Feat: RadioButtonGroup wrapping navigation for seamless cycling
Behavior: - Vertical groups: Up/Down wrap around (down at end → first, up at start → last) - Horizontal groups: Left/Right wrap around (right at end → first, left at start → last) - Always return true (event consumed) — never falls through to FocusManager - Enables smooth cycling through options without leaving the group Implementation: - Modified handleKeyEvent to use ternary wrap logic - All navigation keys now always consumed (return true) - Tab still exits group to next focusable element Testing: - Updated 'boundaryNavigation' test to verify wrapping behavior - Test now checks that Up at start wraps to last item - All 591 tests passing User Experience: - Stay in group when cycling options with arrow keys - Tab exits to next group (standard radio group UX)
This commit is contained in:
@@ -203,42 +203,34 @@ extension RadioButtonGroupHandler {
|
||||
case .up:
|
||||
// Up/Down only work for vertical orientation
|
||||
guard orientation == .vertical else { return false }
|
||||
if focusedIndex > 0 {
|
||||
focusedIndex -= 1
|
||||
selection.wrappedValue = itemValues[focusedIndex]
|
||||
return true
|
||||
}
|
||||
return false
|
||||
// Wrap around: go to last item if at beginning
|
||||
focusedIndex = focusedIndex > 0 ? focusedIndex - 1 : itemValues.count - 1
|
||||
selection.wrappedValue = itemValues[focusedIndex]
|
||||
return true
|
||||
|
||||
case .down:
|
||||
// Up/Down only work for vertical orientation
|
||||
guard orientation == .vertical else { return false }
|
||||
if focusedIndex < itemValues.count - 1 {
|
||||
focusedIndex += 1
|
||||
selection.wrappedValue = itemValues[focusedIndex]
|
||||
return true
|
||||
}
|
||||
return false
|
||||
// Wrap around: go to first item if at end
|
||||
focusedIndex = focusedIndex < itemValues.count - 1 ? focusedIndex + 1 : 0
|
||||
selection.wrappedValue = itemValues[focusedIndex]
|
||||
return true
|
||||
|
||||
case .left:
|
||||
// Left/Right only work for horizontal orientation
|
||||
guard orientation == .horizontal else { return false }
|
||||
if focusedIndex > 0 {
|
||||
focusedIndex -= 1
|
||||
selection.wrappedValue = itemValues[focusedIndex]
|
||||
return true
|
||||
}
|
||||
return false
|
||||
// Wrap around: go to last item if at beginning
|
||||
focusedIndex = focusedIndex > 0 ? focusedIndex - 1 : itemValues.count - 1
|
||||
selection.wrappedValue = itemValues[focusedIndex]
|
||||
return true
|
||||
|
||||
case .right:
|
||||
// Left/Right only work for horizontal orientation
|
||||
guard orientation == .horizontal else { return false }
|
||||
if focusedIndex < itemValues.count - 1 {
|
||||
focusedIndex += 1
|
||||
selection.wrappedValue = itemValues[focusedIndex]
|
||||
return true
|
||||
}
|
||||
return false
|
||||
// Wrap around: go to first item if at end
|
||||
focusedIndex = focusedIndex < itemValues.count - 1 ? focusedIndex + 1 : 0
|
||||
selection.wrappedValue = itemValues[focusedIndex]
|
||||
return true
|
||||
|
||||
case .enter, .character(" "):
|
||||
// Select current focused item
|
||||
|
||||
@@ -386,7 +386,7 @@ struct RadioButtonGroupHandlerTests {
|
||||
#expect(selection == AnyHashable("y"))
|
||||
}
|
||||
|
||||
@Test("Handler ignores boundary navigation")
|
||||
@Test("Handler wraps navigation at boundaries")
|
||||
func boundaryNavigation() {
|
||||
var selection = AnyHashable("opt1")
|
||||
let binding = Binding(
|
||||
@@ -403,12 +403,13 @@ struct RadioButtonGroupHandlerTests {
|
||||
canBeFocused: true
|
||||
)
|
||||
|
||||
// Try to go up from first item
|
||||
// Try to go up from first item — should wrap to last
|
||||
let upEvent = KeyEvent(key: .up)
|
||||
let handled = handler.handleKeyEvent(upEvent)
|
||||
|
||||
#expect(handled == false)
|
||||
#expect(handler.focusedIndex == 0)
|
||||
#expect(handled == true)
|
||||
#expect(handler.focusedIndex == 1) // Wrapped to last item
|
||||
#expect(selection == AnyHashable("opt2"))
|
||||
}
|
||||
|
||||
@Test("Handler respects canBeFocused property")
|
||||
|
||||
Reference in New Issue
Block a user