diff --git a/projects/packages/jetpack-mu-wpcom/changelog/prevent-welcome-tour-keyboard-navigation-from-hijacking-left-right-keys-on-the-editor b/projects/packages/jetpack-mu-wpcom/changelog/prevent-welcome-tour-keyboard-navigation-from-hijacking-left-right-keys-on-the-editor
new file mode 100644
index 0000000000000..b9ff1ac7fed20
--- /dev/null
+++ b/projects/packages/jetpack-mu-wpcom/changelog/prevent-welcome-tour-keyboard-navigation-from-hijacking-left-right-keys-on-the-editor
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Prevent welcome tour keyboard navigation from hijacking left right keys on the editor
diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx
index 7d913d40d3a7f..002a3a3ad050c 100644
--- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx
+++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/keyboard-navigation.tsx
@@ -34,6 +34,7 @@ const KeyboardNavigation: React.FunctionComponent< Props > = ( {
onEscape: onMinimize,
onArrowRight: onNextStepProgression,
onArrowLeft: onPreviousStepProgression,
+ tourContainerRef,
} );
useFocusTrap( tourContainerRef );
@@ -44,7 +45,7 @@ const KeyboardNavigation: React.FunctionComponent< Props > = ( {
* Minimize Tour Nav
*/
function MinimizedTourNav() {
- useKeydownHandler( { onEscape: onDismiss( 'esc-key-minimized' ) } );
+ useKeydownHandler( { onEscape: onDismiss( 'esc-key-minimized' ), tourContainerRef } );
return null;
}
diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx
index e030a37cdda26..0d5aeb3d5b091 100644
--- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx
+++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/components/tour-kit-frame.tsx
@@ -247,6 +247,7 @@ const TourKitFrame: React.FunctionComponent< Props > = ( { config } ) => {
) }
>
{ showArrowIndicator() && (
diff --git a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts
index 1cdf93219b40e..8d88f2435d4e0 100644
--- a/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts
+++ b/projects/packages/jetpack-mu-wpcom/src/common/tour-kit/hooks/use-keydown-handler.ts
@@ -8,12 +8,31 @@ interface Props {
onEscape?: () => void;
onArrowRight?: () => void;
onArrowLeft?: () => void;
+ tourContainerRef: React.MutableRefObject< null | HTMLElement >;
}
/**
* A hook the applies the respective callbacks in response to keydown events.
*/
-const useKeydownHandler = ( { onEscape, onArrowRight, onArrowLeft }: Props ): void => {
+const useKeydownHandler = ( {
+ onEscape,
+ onArrowRight,
+ onArrowLeft,
+ tourContainerRef,
+}: Props ): void => {
+ const isActiveElementOutsideTourContainer = useCallback( (): boolean => {
+ return !! (
+ tourContainerRef.current &&
+ ! tourContainerRef.current.contains( tourContainerRef.current.ownerDocument.activeElement )
+ );
+ }, [ tourContainerRef ] );
+
+ const focusTourContainer = useCallback( () => {
+ (
+ tourContainerRef.current?.querySelector( '.tour-kit-frame__container' ) as HTMLElement
+ )?.focus();
+ }, [ tourContainerRef ] );
+
const handleKeydown = useCallback(
( event: KeyboardEvent ) => {
let handled = false;
@@ -21,18 +40,32 @@ const useKeydownHandler = ( { onEscape, onArrowRight, onArrowLeft }: Props ): vo
switch ( event.key ) {
case 'Escape':
if ( onEscape ) {
+ if ( isActiveElementOutsideTourContainer() ) {
+ return;
+ }
+
onEscape();
+ // focus the container after minimizing so the user can dismiss it
+ focusTourContainer();
handled = true;
}
break;
case 'ArrowRight':
if ( onArrowRight ) {
+ if ( isActiveElementOutsideTourContainer() ) {
+ return;
+ }
+
onArrowRight();
handled = true;
}
break;
case 'ArrowLeft':
if ( onArrowLeft ) {
+ if ( isActiveElementOutsideTourContainer() ) {
+ return;
+ }
+
onArrowLeft();
handled = true;
}
@@ -46,16 +79,40 @@ const useKeydownHandler = ( { onEscape, onArrowRight, onArrowLeft }: Props ): vo
event.stopPropagation();
}
},
- [ onEscape, onArrowRight, onArrowLeft ]
+ [ onEscape, onArrowRight, onArrowLeft, isActiveElementOutsideTourContainer, focusTourContainer ]
+ );
+
+ // when clicking on the container, if the target is not a focusable element,
+ // force focus on the first children so keyboard navigation works
+ const handleTourContainerClick = useCallback(
+ ( event: MouseEvent ) => {
+ const isFocusable = ( element: HTMLElement ) => {
+ const focusableElements = [ 'A', 'INPUT', 'BUTTON', 'TEXTAREA', 'SELECT' ];
+
+ // Check if the element is focusable by its tag or has a tabindex >= 0
+ return focusableElements.includes( element?.tagName ) || element?.tabIndex >= 0;
+ };
+
+ if ( isFocusable( event.target as HTMLElement ) ) {
+ return;
+ }
+
+ focusTourContainer();
+ },
+ [ focusTourContainer ]
);
useEffect( () => {
+ const tourContainer = tourContainerRef.current;
+
document.addEventListener( 'keydown', handleKeydown );
+ tourContainer?.addEventListener( 'click', handleTourContainerClick );
return () => {
document.removeEventListener( 'keydown', handleKeydown );
+ tourContainer?.removeEventListener( 'click', handleTourContainerClick );
};
- }, [ handleKeydown ] );
+ }, [ handleKeydown, handleTourContainerClick, tourContainerRef ] );
};
export default useKeydownHandler;