@@ -213,36 +213,46 @@ public extension FocusManager {
213213 }
214214
215215 /// Moves focus to the next element within the active section.
216+ ///
217+ /// Arrow-key navigation: does **not** wrap at the boundary.
216218 func focusNextInSection( ) {
217- moveFocusInSection ( direction: . forward)
219+ moveFocusInSection ( direction: . forward, wrap : false )
218220 }
219221
220222 /// Moves focus to the previous element within the active section.
223+ ///
224+ /// Arrow-key navigation: does **not** wrap at the boundary.
221225 func focusPreviousInSection( ) {
222- moveFocusInSection ( direction: . backward)
226+ moveFocusInSection ( direction: . backward, wrap : false )
223227 }
224228
225229 /// Moves focus to the next focusable element.
226230 ///
227- /// When multiple sections exist, this cycles between sections.
228- /// When only one section exists, this cycles within it.
231+ /// When multiple sections exist, Tab navigates within the current section
232+ /// first. Only when the current element is the last in its section does
233+ /// Tab switch to the next section.
234+ /// When only one section exists, this cycles within it (wrapping).
229235 func focusNext( ) {
230236 if sections. count > 1 {
231- activateNextSection ( )
237+ let moved = moveFocusInSection ( direction: . forward, wrap: false )
238+ if !moved { activateNextSection ( ) }
232239 } else {
233- moveFocusInSection ( direction: . forward)
240+ moveFocusInSection ( direction: . forward, wrap : true )
234241 }
235242 }
236243
237244 /// Moves focus to the previous focusable element.
238245 ///
239- /// When multiple sections exist, this cycles between sections.
240- /// When only one section exists, this cycles within it.
246+ /// When multiple sections exist, Shift+Tab navigates within the current
247+ /// section first. Only when the current element is the first in its section
248+ /// does Shift+Tab switch to the previous section.
249+ /// When only one section exists, this cycles within it (wrapping).
241250 func focusPrevious( ) {
242251 if sections. count > 1 {
243- activatePreviousSection ( )
252+ let moved = moveFocusInSection ( direction: . backward, wrap: false )
253+ if !moved { activatePreviousSection ( ) }
244254 } else {
245- moveFocusInSection ( direction: . backward)
255+ moveFocusInSection ( direction: . backward, wrap : true )
246256 }
247257 }
248258
@@ -463,26 +473,47 @@ private extension FocusManager {
463473 }
464474
465475 /// Moves focus within the active section.
466- func moveFocusInSection( direction: FocusDirection ) {
467- guard let section = activeSection else { return }
476+ ///
477+ /// - Parameters:
478+ /// - direction: The direction in which to move focus.
479+ /// - wrap: When `true`, focus wraps around from the last element to the
480+ /// first (and vice versa). When `false`, focus stops at the boundary
481+ /// and the method returns `false`.
482+ /// - Returns: `true` if focus moved to a new element, `false` if the
483+ /// boundary was reached (and `wrap` is `false`) or no element is available.
484+ @discardableResult
485+ func moveFocusInSection( direction: FocusDirection , wrap: Bool = true ) -> Bool {
486+ guard let section = activeSection else { return false }
468487
469488 let available = section. focusables. filter { $0. canBeFocused }
470- guard !available. isEmpty else { return }
489+ guard !available. isEmpty else { return false }
471490
472491 if let currentID = focusedID,
473492 let currentIndex = available. firstIndex ( where: { $0. focusID == currentID } )
474493 {
475494 let targetIndex : Int
476495 switch direction {
477496 case . forward:
478- targetIndex = ( currentIndex + 1 ) % available. count
497+ if currentIndex == available. count - 1 {
498+ guard wrap else { return false }
499+ targetIndex = 0
500+ } else {
501+ targetIndex = currentIndex + 1
502+ }
479503 case . backward:
480- targetIndex = currentIndex == 0 ? available. count - 1 : currentIndex - 1
504+ if currentIndex == 0 {
505+ guard wrap else { return false }
506+ targetIndex = available. count - 1
507+ } else {
508+ targetIndex = currentIndex - 1
509+ }
481510 }
482511 focus ( available [ targetIndex] )
512+ return true
483513 } else {
484514 let fallbackIndex = direction == . forward ? 0 : available. count - 1
485515 focus ( available [ fallbackIndex] )
516+ return true
486517 }
487518 }
488519
0 commit comments