@@ -4,7 +4,7 @@ import type TitleLevel from '@ui5/webcomponents/dist/types/TitleLevel.js';
44import { useStylesheet } from '@ui5/webcomponents-react-base' ;
55import { clsx } from 'clsx' ;
66import type { ReactNode , FocusEventHandler , KeyboardEventHandler } from 'react' ;
7- import { Children , isValidElement , forwardRef } from 'react' ;
7+ import { Children , isValidElement , forwardRef , useMemo } from 'react' ;
88import type { CommonProps } from '../../types/index.js' ;
99import { navigateSections } from '../ObjectPage/ObjectPageUtils.js' ;
1010import { classNames , styleData } from './ObjectPageSection.module.css.js' ;
@@ -57,6 +57,18 @@ export interface ObjectPageSectionPropTypes extends CommonProps {
5757 header ?: ReactNode ;
5858}
5959
60+ function recursiveSetTabIndexOnSubSection ( el : HTMLElement | null , currentTarget : HTMLElement ) : void {
61+ if ( ! el || el === currentTarget ) {
62+ return ;
63+ }
64+
65+ if ( el . dataset . componentName === 'ObjectPageSubSection' ) {
66+ el . tabIndex = 0 ;
67+ return ;
68+ }
69+ return recursiveSetTabIndexOnSubSection ( el . parentElement , currentTarget ) ;
70+ }
71+
6072/**
6173 * Top-level information container of an `ObjectPage`.
6274 */
@@ -77,6 +89,14 @@ const ObjectPageSection = forwardRef<HTMLElement, ObjectPageSectionPropTypes>((p
7789 useStylesheet ( styleData , ObjectPageSection . displayName ) ;
7890 const htmlId = `ObjectPageSection-${ id } ` ;
7991 const titleClasses = clsx ( classNames . title , titleTextUppercase && classNames . uppercase ) ;
92+ const hasSubSection = useMemo (
93+ ( ) =>
94+ Children . toArray ( children ) . some (
95+ // @ts -expect-error: if type is string, then it's not a subcomponent
96+ ( child ) => isValidElement ( child ) && child . type ?. displayName === 'ObjectPageSubSection' ,
97+ ) ,
98+ [ children ] ,
99+ ) ;
80100
81101 const handleFocus : FocusEventHandler < HTMLElement > = ( e ) => {
82102 if ( typeof props . onFocus === 'function' ) {
@@ -88,27 +108,28 @@ const ObjectPageSection = forwardRef<HTMLElement, ObjectPageSectionPropTypes>((p
88108 el . tabIndex = - 1 ;
89109 }
90110 } ) ;
111+
91112 e . currentTarget . tabIndex = 0 ;
92- const hasSubSection = Children . toArray ( children ) . some (
93- // @ts -expect-error: if type is string, then it's not a subcomponent
94- ( child ) => isValidElement ( child ) && child . type ?. displayName === 'ObjectPageSubSection' ,
95- ) ;
96- if ( hasSubSection && e . target === e . currentTarget ) {
113+ // if section has subsections, the first subsection should be next in the tab-chain
114+ if ( e . target === e . currentTarget && hasSubSection ) {
97115 const opSubSection : HTMLElement = e . currentTarget . querySelector ( '[data-component-name="ObjectPageSubSection"]' ) ;
98116 if ( opSubSection ) {
99117 opSubSection . tabIndex = 0 ;
100118 }
119+ // if the target is a subsection, the section should be the previous element in the tab-chain
120+ } else if ( e . target . dataset . componentName === 'ObjectPageSubSection' ) {
121+ e . target . tabIndex = 0 ;
122+ // if the target is an interactive element inside a subsection, the subsection should be the next element in the tab-chain
123+ } else if ( hasSubSection ) {
124+ recursiveSetTabIndexOnSubSection ( e . target , e . currentTarget ) ;
101125 }
102126 } ;
103127
104128 const handleBlur : FocusEventHandler < HTMLElement > = ( e ) => {
105129 if ( typeof props . onBlur === 'function' ) {
106130 props . onBlur ( e ) ;
107131 }
108- const hasSubSection = Children . toArray ( children ) . some (
109- // @ts -expect-error: if type is string, then it's not a subcomponent
110- ( child ) => isValidElement ( child ) && child . type ?. displayName === 'ObjectPageSubSection' ,
111- ) ;
132+
112133 if ( hasSubSection && e . target === e . currentTarget ) {
113134 const allSubSections : NodeListOf < HTMLElement > = e . currentTarget . querySelectorAll (
114135 '[data-component-name="ObjectPageSubSection"]' ,
0 commit comments