@@ -9,7 +9,7 @@ import '@webex/components/dist/css/webex-components.css';
99import './WebexMeetings.css' ;
1010
1111/**
12- * Webex meeting widget presents a Webex meeting experience.
12+ * Webex meeting widget presents a Webex meeting experience
1313 *
1414 * @param {string } props.meetingDestination ID of the virtual meeting location
1515 * @param {string } props.meetingPasswordOrPin Password or pin of the virtual meeting location
@@ -27,67 +27,73 @@ class WebexMeetingsWidget extends Component {
2727 constructor ( props ) {
2828 super ( props ) ;
2929 }
30- /**
31- * Temporary custom accessibility fix:
32- * - Redirects focus to the first actionable control inside the meeting
33- * - Makes video layout focusable and supports left/right arrow navigation
34- * - Prevents focus from escaping to the browser URL bar
35- *
36- * NOTE: This is a workaround because the base @webex/components WebexMeeting
37- * does not yet support these accessibility features.
38- * Once the upstream component is fixed, we must remove this custom code
39- * from our repo to avoid duplication and ensure long-term maintainability.
40- */
30+ /**
31+ * Temporary custom accessibility fix:
32+ * - Redirects focus to the first actionable control inside the meeting
33+ * - Makes video layout focusable and supports left/right arrow navigation
34+ * - Prevents focus from escaping to the browser URL bar
35+ *
36+ * NOTE: This is a workaround because the base @webex/components WebexMeeting
37+ * does not yet support these accessibility features.
38+ * Once the upstream component is fixed, we must remove this custom code
39+ * from our repo to avoid duplication and ensure long-term maintainability.
40+ */
4141 componentDidMount ( ) {
4242 // When focus comes to the widget container, move to the correct media container before and after joining
4343 if ( this . widgetDiv ) {
4444 if ( ! this . _mediaContainerTabHandler ) {
45- this . _mediaContainerTabHandler = ( evt ) => {
46- const mediaContainer = evt . currentTarget ;
47- // Only handle if the media container itself is focused
48- if ( ( evt . code === 'Tab' || evt . key === 'Tab' ) && document . activeElement === mediaContainer ) {
49- if ( ! evt . shiftKey ) {
50- evt . preventDefault ( ) ;
51- let joinButton = this . widgetDiv . querySelector ( 'button[aria-label="Join meeting"]' ) ;
52- if ( ! joinButton ) {
53- joinButton = this . widgetDiv . querySelector ( '.wxc-meeting-control button, .wxc-meeting-control [tabindex]:not([tabindex="-1"])' ) ;
54- }
55- if ( joinButton ) {
56- joinButton . focus ( ) ;
57- }
58- } else {
59- evt . preventDefault ( ) ;
60- // Move focus back to the widget container
61- if ( this . widgetDiv ) {
62- this . widgetDiv . tabIndex = 0 ;
63- this . widgetDiv . focus ( ) ;
64- }
65- }
45+ this . _mediaContainerTabHandler = ( evt ) => {
46+ const mediaContainer = evt . currentTarget ;
47+ // Only handle if the media container itself is focused
48+ if ( ( evt . code === 'Tab' || evt . key === 'Tab' ) && document . activeElement === mediaContainer ) {
49+ if ( ! evt . shiftKey ) {
50+ evt . preventDefault ( ) ;
51+ let joinButton = this . widgetDiv . querySelector ( 'button[aria-label="Join meeting"]' ) ;
52+ if ( ! joinButton ) {
53+ joinButton = this . widgetDiv . querySelector (
54+ '.wxc-meeting-control button, .wxc-meeting-control [tabindex]:not([tabindex="-1"])'
55+ ) ;
6656 }
67- } ;
57+ if ( joinButton ) {
58+ joinButton . focus ( ) ;
59+ }
60+ } else {
61+ evt . preventDefault ( ) ;
62+ // Move focus back to the widget container
63+ if ( this . widgetDiv ) {
64+ this . widgetDiv . tabIndex = 0 ;
65+ this . widgetDiv . focus ( ) ;
66+ }
67+ }
68+ }
69+ } ;
6870 }
6971 this . widgetDiv . addEventListener ( 'focus' , ( ) => {
7072 setTimeout ( ( ) => {
7173 // Attach handler to both possible media containers if they exist
7274 const containers = [
73- ...this . widgetDiv . querySelectorAll ( '.wxc-interstitial-meeting__media-container, .wxc-in-meeting__media-container' )
75+ ...this . widgetDiv . querySelectorAll (
76+ '.wxc-interstitial-meeting__media-container, .wxc-in-meeting__media-container'
77+ ) ,
7478 ] ;
75- if ( containers . length > 0 ) {
76- containers . forEach ( ( mediaContainer ) => {
77- mediaContainer . tabIndex = 0 ;
78- mediaContainer . removeEventListener ( 'keydown' , this . _mediaContainerTabHandler , true ) ;
79- mediaContainer . addEventListener ( 'keydown' , this . _mediaContainerTabHandler , true ) ;
80- } ) ;
81- } else {
82- // fallback to Join meeting button or first .wxc-meeting-control button
83- let joinButton = this . widgetDiv . querySelector ( 'button[aria-label="Join meeting"]' ) ;
84- if ( ! joinButton ) {
85- joinButton = this . widgetDiv . querySelector ( '.wxc-meeting-control button, .wxc-meeting-control [tabindex]:not([tabindex="-1"])' ) ;
86- }
87- if ( joinButton ) {
88- joinButton . focus ( ) ;
89- }
79+ if ( containers . length > 0 ) {
80+ containers . forEach ( ( mediaContainer ) => {
81+ mediaContainer . tabIndex = 0 ;
82+ mediaContainer . removeEventListener ( 'keydown' , this . _mediaContainerTabHandler , true ) ;
83+ mediaContainer . addEventListener ( 'keydown' , this . _mediaContainerTabHandler , true ) ;
84+ } ) ;
85+ } else {
86+ // fallback to Join meeting button or first .wxc-meeting-control button
87+ let joinButton = this . widgetDiv . querySelector ( 'button[aria-label="Join meeting"]' ) ;
88+ if ( ! joinButton ) {
89+ joinButton = this . widgetDiv . querySelector (
90+ '.wxc-meeting-control button, .wxc-meeting-control [tabindex]:not([tabindex="-1"])'
91+ ) ;
92+ }
93+ if ( joinButton ) {
94+ joinButton . focus ( ) ;
9095 }
96+ }
9197 } , 0 ) ;
9298 } ) ;
9399
@@ -130,7 +136,7 @@ class WebexMeetingsWidget extends Component {
130136 const observer = new window . MutationObserver ( ( ) => {
131137 attachArrowNav ( ) ;
132138 } ) ;
133- observer . observe ( this . widgetDiv , { childList : true , subtree : true } ) ;
139+ observer . observe ( this . widgetDiv , { childList : true , subtree : true } ) ;
134140 // Clean up observer on unmount
135141 this . _arrowNavObserver = observer ;
136142 }
@@ -200,7 +206,14 @@ class WebexMeetingsWidget extends Component {
200206 }
201207
202208 return (
203- < div className = { `webex-meetings-widget ${ this . props . className } ` } style = { this . props . style } ref = { ( div ) => { this . widgetDiv = div ; } } tabIndex = { 0 } >
209+ < div
210+ className = { `webex-meetings-widget ${ this . props . className } ` }
211+ style = { this . props . style }
212+ ref = { ( div ) => {
213+ this . widgetDiv = div ;
214+ } }
215+ tabIndex = { 0 }
216+ >
204217 { content }
205218 </ div >
206219 ) ;
@@ -255,7 +268,7 @@ export default withAdapter(withMeeting(WebexMeetingsWidget), (props) => {
255268 meetings : {
256269 experimental : {
257270 enableUnifiedMeetings : true ,
258- enableAdhocMeetings : true
271+ enableAdhocMeetings : true ,
259272 } ,
260273 } ,
261274 } ,
0 commit comments