@@ -43,7 +43,6 @@ import usePrevious from './hooks/internal/usePrevious';
4343import useRegisterFocusTranscript from './hooks/internal/useRegisterFocusTranscript' ;
4444import useRegisterScrollTo from './hooks/internal/useRegisterScrollTo' ;
4545import useRegisterScrollToEnd from './hooks/internal/useRegisterScrollToEnd' ;
46- import useUniqueId from './hooks/internal/useUniqueId' ;
4746import useValueRef from './hooks/internal/useValueRef' ;
4847import {
4948 useRegisterScrollRelativeTranscript ,
@@ -60,8 +59,10 @@ import TranscriptFocusComposer from './providers/TranscriptFocus/TranscriptFocus
6059import useActiveDescendantId from './providers/TranscriptFocus/useActiveDescendantId' ;
6160import useFocusByActivityKey from './providers/TranscriptFocus/useFocusByActivityKey' ;
6261import useFocusRelativeActivity from './providers/TranscriptFocus/useFocusRelativeActivity' ;
63- import useFocusedActivityKey from './providers/TranscriptFocus/useFocusedActivityKey ' ;
62+ import useFocusedKey from './providers/TranscriptFocus/useFocusedKey ' ;
6463import useFocusedExplicitly from './providers/TranscriptFocus/useFocusedExplicitly' ;
64+ import { ActivityLogicalGroupingComposer } from './providers/ActivityLogicalGrouping' ;
65+ import { TranscriptFocusArea , TranscriptFocusTerminator } from './Transcript/TranscriptFocus' ;
6566
6667const {
6768 useActivityKeys,
@@ -114,12 +115,11 @@ type InternalTranscriptProps = Readonly<{
114115// TODO: [P1] #4133 Add telemetry for computing how many re-render done so far.
115116const InternalTranscript = forwardRef < HTMLDivElement , InternalTranscriptProps > (
116117 ( { className, terminatorRef } : InternalTranscriptProps , ref ) => {
117- const [ { basicTranscript : basicTranscriptStyleSet } ] = useStyleSet ( ) ;
118118 const [ activeDescendantId ] = useActiveDescendantId ( ) ;
119119 const [ direction ] = useDirection ( ) ;
120- const [ focusedActivityKey ] = useFocusedActivityKey ( ) ;
120+ const [ focusedKey ] = useFocusedKey ( ) ;
121121 const [ focusedExplicitly ] = useFocusedExplicitly ( ) ;
122- const activityElementMapRef = useActivityElementMapRef ( ) ;
122+ const focusElementMapRef = useActivityElementMapRef ( ) ;
123123 const focus = useFocus ( ) ;
124124 const focusByActivityKey = useFocusByActivityKey ( ) ;
125125 const focusRelativeActivity = useFocusRelativeActivity ( ) ;
@@ -128,10 +128,8 @@ const InternalTranscript = forwardRef<HTMLDivElement, InternalTranscriptProps>(
128128 const localize = useLocalizer ( ) ;
129129 const rootClassName = useStyleToEmotionObject ( ) ( ROOT_STYLE ) + '' ;
130130 const rootElementRef = useRef < HTMLDivElement > ( null ) ;
131- const terminatorLabelId = useUniqueId ( 'webchat__basic-transcript__terminator-label' ) ;
132131
133- const focusedActivityKeyRef = useValueRef ( focusedActivityKey ) ;
134- const terminatorText = localize ( 'TRANSCRIPT_TERMINATOR_TEXT' ) ;
132+ const focusedKeyRef = useValueRef ( focusedKey ) ;
135133 const transcriptAriaLabel = localize ( 'TRANSCRIPT_ARIA_LABEL_ALT' ) ;
136134
137135 const callbackRef = useCallback (
@@ -165,7 +163,7 @@ const InternalTranscript = forwardRef<HTMLDivElement, InternalTranscriptProps>(
165163 if ( typeof scrollTop !== 'undefined' ) {
166164 scrollToBottomScrollTo ( scrollTop , { behavior } ) ;
167165 } else if ( typeof activityId !== 'undefined' ) {
168- const activityBoundingBoxElement = activityElementMapRef . current
166+ const activityBoundingBoxElement = focusElementMapRef . current
169167 . get ( getKeyByActivityId ( activityId ) )
170168 ?. querySelector ( '.webchat__basic-transcript__activity-active-descendant' ) ;
171169
@@ -193,7 +191,7 @@ const InternalTranscript = forwardRef<HTMLDivElement, InternalTranscriptProps>(
193191 }
194192 }
195193 } ,
196- [ activityElementMapRef , getKeyByActivityId , rootElementRef , scrollToBottomScrollTo ]
194+ [ focusElementMapRef , getKeyByActivityId , rootElementRef , scrollToBottomScrollTo ]
197195 ) ;
198196
199197 const scrollToEnd = useCallback (
@@ -266,7 +264,7 @@ const InternalTranscript = forwardRef<HTMLDivElement, InternalTranscriptProps>(
266264
267265 // Find the activity just above scroll view bottom.
268266 // If the scroll view is already on top, get the first activity.
269- const activityElements = Array . from ( activityElementMapRef . current . entries ( ) ) ;
267+ const activityElements = Array . from ( focusElementMapRef . current . entries ( ) ) ;
270268 const activityKeyJustAboveScrollBottom : string | undefined = (
271269 scrollableElement . scrollTop
272270 ? activityElements
@@ -293,7 +291,7 @@ const InternalTranscript = forwardRef<HTMLDivElement, InternalTranscriptProps>(
293291 }
294292 } ,
295293 [
296- activityElementMapRef ,
294+ focusElementMapRef ,
297295 dispatchScrollPositionWithActivityId ,
298296 getActivityByKey ,
299297 markActivityKeyAsRead ,
@@ -334,9 +332,10 @@ const InternalTranscript = forwardRef<HTMLDivElement, InternalTranscriptProps>(
334332 // This is capturing plain ENTER.
335333 // When screen reader is not running, or screen reader is running outside of scan mode, the ENTER key will be captured here.
336334 if ( ! fromEndOfTranscriptIndicator ) {
337- const activityFocusTrapTarget : HTMLElement = activityElementMapRef . current
338- . get ( focusedActivityKeyRef . current )
339- ?. querySelector ( '.webchat__basic-transcript__activity-focus-target' ) ;
335+ const focusElement = focusElementMapRef . current ?. get ( focusedKeyRef . current ) ;
336+ const activityFocusTrapTarget : HTMLElement =
337+ focusElement ?. querySelector ( '.webchat__basic-transcript__group-focus-target' ) ??
338+ focusElement ?. querySelector ( '.webchat__basic-transcript__activity-focus-target' ) ;
340339 // TODO: review focus approach:
341340 // It is not clear how to handle focus without introducing something like context.
342341 // Ideally we would want a way to interact with focus outside of React
@@ -367,7 +366,7 @@ const InternalTranscript = forwardRef<HTMLDivElement, InternalTranscriptProps>(
367366 event . stopPropagation ( ) ;
368367 }
369368 } ,
370- [ activityElementMapRef , focus , focusedActivityKeyRef , focusRelativeActivity , terminatorRef ]
369+ [ focusElementMapRef , focus , focusedKeyRef , focusRelativeActivity , terminatorRef ]
371370 ) ;
372371
373372 const handleTranscriptKeyDownCapture = useCallback < KeyboardEventHandler < HTMLDivElement > > (
@@ -399,8 +398,8 @@ const InternalTranscript = forwardRef<HTMLDivElement, InternalTranscriptProps>(
399398 // Dispatch a "transcript focus" event based on user selection.
400399 // We should not dispatch "transcript focus" when a new activity come. Although the selection change, it is not initiated from the user.
401400 useMemo (
402- ( ) => dispatchTranscriptFocusByActivityKey ( focusedExplicitly ? focusedActivityKey : undefined ) ,
403- [ dispatchTranscriptFocusByActivityKey , focusedActivityKey , focusedExplicitly ]
401+ ( ) => dispatchTranscriptFocusByActivityKey ( focusedExplicitly ? focusedKey : undefined ) ,
402+ [ dispatchTranscriptFocusByActivityKey , focusedKey , focusedExplicitly ]
404403 ) ;
405404
406405 // When the transcript is being focused on, we should dispatch a "transcriptfocus" event.
@@ -426,17 +425,12 @@ const InternalTranscript = forwardRef<HTMLDivElement, InternalTranscriptProps>(
426425 const hasAnyChild = ! ! numRenderingActivities ;
427426
428427 return (
429- < div
428+ < TranscriptFocusArea
430429 // Although Android TalkBack 12.1 does not support `aria-activedescendant`, when used, it become buggy and will narrate content twice.
431430 // We are disabling `aria-activedescendant` for Android. See <ActivityRow> for details.
432431 aria-activedescendant = { android ? undefined : activeDescendantId }
433432 aria-label = { transcriptAriaLabel }
434- className = { classNames (
435- 'webchat__basic-transcript' ,
436- basicTranscriptStyleSet + '' ,
437- rootClassName ,
438- ( className || '' ) + ''
439- ) }
433+ className = { classNames ( 'webchat__basic-transcript' , rootClassName , ( className || '' ) + '' ) }
440434 dir = { direction }
441435 onFocus = { handleFocus }
442436 onKeyDown = { handleTranscriptKeyDown }
@@ -450,33 +444,18 @@ const InternalTranscript = forwardRef<HTMLDivElement, InternalTranscriptProps>(
450444 // https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_focus_activedescendant
451445 tabIndex = { 0 }
452446 >
453- < LiveRegionTranscript activityElementMapRef = { activityElementMapRef } />
447+ < LiveRegionTranscript activityElementMapRef = { focusElementMapRef } />
454448 { hasAnyChild && < FocusRedirector redirectRef = { terminatorRef } /> }
455449 < InternalTranscriptScrollable onFocusFiller = { handleFocusFiller } >
456450 { hasAnyChild && < ActivityTree /> }
457451 </ InternalTranscriptScrollable >
458452 { hasAnyChild && (
459453 < Fragment >
460454 < FocusRedirector redirectRef = { rootElementRef } />
461- < div
462- aria-labelledby = { terminatorLabelId }
463- className = "webchat__basic-transcript__terminator"
464- ref = { terminatorRef }
465- role = "note"
466- tabIndex = { 0 }
467- >
468- < div className = "webchat__basic-transcript__terminator-body" >
469- { /* `id` is required for `aria-labelledby` */ }
470- { /* eslint-disable-next-line react/forbid-dom-props */ }
471- < div className = "webchat__basic-transcript__terminator-text" id = { terminatorLabelId } >
472- { terminatorText }
473- </ div >
474- </ div >
475- </ div >
455+ < TranscriptFocusTerminator ref = { terminatorRef } role = "note" tabIndex = { 0 } />
476456 </ Fragment >
477457 ) }
478- < div className = "webchat__basic-transcript__focus-indicator" />
479- </ div >
458+ </ TranscriptFocusArea >
480459 ) ;
481460 }
482461) ;
@@ -667,18 +646,20 @@ const BasicTranscript = ({ className = '' }: BasicTranscriptProps) => {
667646
668647 return (
669648 < ChatHistoryBox className = { className } >
670- < RenderingActivitiesComposer >
671- < TranscriptFocusComposer containerRef = { containerRef } >
672- < ReactScrollToBottomComposer nonce = { nonce } scroller = { scroller } styleOptions = { styleOptions } >
673- < ChatHistoryToolbar >
674- < ScrollToEndButton terminatorRef = { terminatorRef } />
675- </ ChatHistoryToolbar >
676- < GroupedRenderingActivitiesComposer >
677- < InternalTranscript ref = { containerRef } terminatorRef = { terminatorRef } />
678- </ GroupedRenderingActivitiesComposer >
679- </ ReactScrollToBottomComposer >
680- </ TranscriptFocusComposer >
681- </ RenderingActivitiesComposer >
649+ < ActivityLogicalGroupingComposer >
650+ < RenderingActivitiesComposer >
651+ < TranscriptFocusComposer containerRef = { containerRef } >
652+ < ReactScrollToBottomComposer nonce = { nonce } scroller = { scroller } styleOptions = { styleOptions } >
653+ < ChatHistoryToolbar >
654+ < ScrollToEndButton terminatorRef = { terminatorRef } />
655+ </ ChatHistoryToolbar >
656+ < GroupedRenderingActivitiesComposer >
657+ < InternalTranscript ref = { containerRef } terminatorRef = { terminatorRef } />
658+ </ GroupedRenderingActivitiesComposer >
659+ </ ReactScrollToBottomComposer >
660+ </ TranscriptFocusComposer >
661+ </ RenderingActivitiesComposer >
662+ </ ActivityLogicalGroupingComposer >
682663 </ ChatHistoryBox >
683664 ) ;
684665} ;
0 commit comments