@@ -14,6 +14,7 @@ import { supportsTouch } from "@ui5/webcomponents-base/dist/Device.js";
1414import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js" ;
1515import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js" ;
1616import type { UI5CustomEvent } from "@ui5/webcomponents-base" ;
17+ import { getTabbableElements } from "@ui5/webcomponents-base/dist/util/TabbableElements.js" ;
1718import type TitleLevel from "./types/TitleLevel.js" ;
1819import type Button from "./Button.js" ;
1920import type PanelAccessibleRole from "./types/PanelAccessibleRole.js" ;
@@ -203,6 +204,14 @@ class Panel extends UI5Element {
203204 @property ( { type : Boolean } )
204205 _touched = false ;
205206
207+ /**
208+ * Indicates whether the content area should be focusable.
209+ * This is true when content is scrollable and has no focusable children.
210+ * @private
211+ */
212+ @property ( { type : Boolean , noAttribute : true } )
213+ _contentFocusable = false ;
214+
206215 /**
207216 * Defines the component header area.
208217 *
@@ -224,6 +233,10 @@ class Panel extends UI5Element {
224233 this . _hasHeader = ! ! this . header . length ;
225234 }
226235
236+ onAfterRendering ( ) {
237+ this . _updateContentFocusable ( ) ;
238+ }
239+
227240 shouldToggle ( element : HTMLElement ) : boolean {
228241 const customContent = this . header . length ;
229242 if ( customContent ) {
@@ -335,6 +348,50 @@ class Panel extends UI5Element {
335348 return target . classList . contains ( "sapMPanelWrappingDiv" ) ;
336349 }
337350
351+ /**
352+ * Updates the focusability of the content area.
353+ * Content becomes focusable when:
354+ * - Panel is expanded (not collapsed)
355+ * - Content is scrollable (scrollHeight > clientHeight or scrollWidth > clientWidth)
356+ * - No focusable children exist inside
357+ * @private
358+ */
359+ _updateContentFocusable ( ) {
360+ // Not focusable when collapsed
361+ if ( this . collapsed ) {
362+ this . _contentFocusable = false ;
363+ return ;
364+ }
365+
366+ const contentDom = this . shadowRoot ?. querySelector ( ".ui5-panel-content" ) as HTMLElement | null ;
367+ if ( ! contentDom ) {
368+ this . _contentFocusable = false ;
369+ return ;
370+ }
371+
372+ // Check if scrollable (vertical OR horizontal)
373+ const isScrollable = contentDom . scrollHeight > contentDom . clientHeight
374+ || contentDom . scrollWidth > contentDom . clientWidth ;
375+
376+ if ( ! isScrollable ) {
377+ this . _contentFocusable = false ;
378+ return ;
379+ }
380+
381+ // Check for focusable children (synchronous)
382+ const tabbables = getTabbableElements ( contentDom ) ;
383+ this . _contentFocusable = tabbables . length === 0 ;
384+ }
385+
386+ /**
387+ * Returns the tabindex for the content area.
388+ * Returns 0 when content should be focusable, undefined otherwise (removes attribute).
389+ * @private
390+ */
391+ get _contentTabIndex ( ) : number | undefined {
392+ return this . _contentFocusable ? 0 : undefined ;
393+ }
394+
338395 get toggleButtonTitle ( ) {
339396 return Panel . i18nBundle . getText ( PANEL_ICON ) ;
340397 }
0 commit comments