From b38b22f49db022ac4b361251c1da3eed05b00d72 Mon Sep 17 00:00:00 2001
From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
Date: Mon, 25 Oct 2021 07:58:09 -0700
Subject: [PATCH] Use focus trap for CallingLobby
---
ts/components/CallingLobby.tsx | 141 +++++++++++++------------
ts/components/Tooltip.tsx | 59 ++---------
ts/components/TooltipEventWrapper.tsx | 69 ++++++++++++
ts/state/smart/TooltipEventWrapper.tsx | 29 +++++
ts/util/lint/exceptions.json | 13 +--
5 files changed, 179 insertions(+), 132 deletions(-)
create mode 100644 ts/components/TooltipEventWrapper.tsx
create mode 100644 ts/state/smart/TooltipEventWrapper.tsx
diff --git a/ts/components/CallingLobby.tsx b/ts/components/CallingLobby.tsx
index f5d47b30f8e..bd138f99548 100644
--- a/ts/components/CallingLobby.tsx
+++ b/ts/components/CallingLobby.tsx
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
+import FocusTrap from 'focus-trap-react';
import classNames from 'classnames';
import {
SetLocalAudioType,
@@ -203,83 +204,85 @@ export const CallingLobby = ({
}
return (
-
- {shouldShowLocalVideo ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
+
+ {shouldShowLocalVideo ? (
+
+ ) : (
+
)}
- >
- {i18n('calling__your-video-is-off')}
-
-
-
-
-
+ {i18n('calling__your-video-is-off')}
+
+
+
+
+
+
+
+
+
{
+ setIsCallConnecting(true);
+ onJoinCall();
+ }}
+ variant={callingLobbyJoinButtonVariant}
/>
-
-
{
- setIsCallConnecting(true);
- onJoinCall();
- }}
- variant={callingLobbyJoinButtonVariant}
- />
-
+
);
};
diff --git a/ts/components/Tooltip.tsx b/ts/components/Tooltip.tsx
index 4ccba759eb4..a2536b85a6a 100644
--- a/ts/components/Tooltip.tsx
+++ b/ts/components/Tooltip.tsx
@@ -3,62 +3,12 @@
import React from 'react';
import classNames from 'classnames';
-import { noop } from 'lodash';
import { Manager, Reference, Popper } from 'react-popper';
import type { StrictModifiers } from '@popperjs/core';
import { Theme, themeClassName } from '../util/theme';
-import { refMerger } from '../util/refMerger';
import { offsetDistanceModifier } from '../util/popperUtil';
-type EventWrapperPropsType = {
- children: React.ReactNode;
- onHoverChanged: (_: boolean) => void;
-};
-
-// React doesn't reliably fire `onMouseLeave` or `onMouseOut` events if wrapping a
-// disabled button. This uses native browser events to avoid that.
-//
-// See .
-const TooltipEventWrapper = React.forwardRef<
- HTMLSpanElement,
- EventWrapperPropsType
->(({ onHoverChanged, children }, ref) => {
- const wrapperRef = React.useRef(null);
-
- const on = React.useCallback(() => {
- onHoverChanged(true);
- }, [onHoverChanged]);
-
- const off = React.useCallback(() => {
- onHoverChanged(false);
- }, [onHoverChanged]);
-
- React.useEffect(() => {
- const wrapperEl = wrapperRef.current;
-
- if (!wrapperEl) {
- return noop;
- }
-
- wrapperEl.addEventListener('mouseenter', on);
- wrapperEl.addEventListener('mouseleave', off);
-
- return () => {
- wrapperEl.removeEventListener('mouseenter', on);
- wrapperEl.removeEventListener('mouseleave', off);
- };
- }, [on, off]);
-
- return (
- (ref, wrapperRef)}
- >
- {children}
-
- );
-});
+import { SmartTooltipEventWrapper } from '../state/smart/TooltipEventWrapper';
export enum TooltipPlacement {
Top = 'top',
@@ -97,9 +47,12 @@ export const Tooltip: React.FC = ({
{({ ref }) => (
-
+
{children}
-
+
)}
;
+ onHoverChanged: (_: boolean) => void;
+};
+
+// React doesn't reliably fire `onMouseLeave` or `onMouseOut` events if wrapping a
+// disabled button. This uses native browser events to avoid that.
+//
+// See .
+export const TooltipEventWrapper: React.FC = ({
+ onHoverChanged,
+ children,
+ interactionMode,
+ innerRef,
+}) => {
+ const wrapperRef = useRef(null);
+
+ const on = useCallback(() => {
+ onHoverChanged(true);
+ }, [onHoverChanged]);
+
+ const off = useCallback(() => {
+ onHoverChanged(false);
+ }, [onHoverChanged]);
+
+ const onFocus = useCallback(() => {
+ if (interactionMode === 'keyboard') {
+ on();
+ }
+ }, [on, interactionMode]);
+
+ useEffect(() => {
+ const wrapperEl = wrapperRef.current;
+
+ if (!wrapperEl) {
+ return noop;
+ }
+
+ wrapperEl.addEventListener('mouseenter', on);
+ wrapperEl.addEventListener('mouseleave', off);
+
+ return () => {
+ wrapperEl.removeEventListener('mouseenter', on);
+ wrapperEl.removeEventListener('mouseleave', off);
+ };
+ }, [on, off]);
+
+ return (
+ (innerRef, wrapperRef)}
+ >
+ {children}
+
+ );
+};
diff --git a/ts/state/smart/TooltipEventWrapper.tsx b/ts/state/smart/TooltipEventWrapper.tsx
new file mode 100644
index 00000000000..393e5bcae60
--- /dev/null
+++ b/ts/state/smart/TooltipEventWrapper.tsx
@@ -0,0 +1,29 @@
+// Copyright 2021 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import React, { Ref } from 'react';
+import { connect } from 'react-redux';
+
+import { mapDispatchToProps } from '../actions';
+import { StateType } from '../reducer';
+
+import { TooltipEventWrapper } from '../../components/TooltipEventWrapper';
+import { getInteractionMode } from '../selectors/user';
+
+type ExternalProps = {
+ // Matches Popper's RefHandler type
+ innerRef: Ref;
+ children: React.ReactNode;
+ onHoverChanged: (_: boolean) => void;
+};
+
+const mapStateToProps = (state: StateType, props: ExternalProps) => {
+ return {
+ ...props,
+ interactionMode: getInteractionMode(state),
+ };
+};
+
+const smart = connect(mapStateToProps, mapDispatchToProps);
+
+export const SmartTooltipEventWrapper = smart(TooltipEventWrapper);
diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json
index 8debd745776..7ed3778bd9b 100644
--- a/ts/util/lint/exceptions.json
+++ b/ts/util/lint/exceptions.json
@@ -12763,19 +12763,12 @@
},
{
"rule": "React-useRef",
- "path": "ts/components/Tooltip.js",
- "line": " const wrapperRef = react_1.default.useRef(null);",
+ "path": "ts/components/TooltipEventWrapper.tsx",
+ "line": " const wrapperRef = useRef(null);",
"reasonCategory": "usageTrusted",
- "updated": "2020-12-04T00:11:08.128Z",
+ "updated": "2021-10-21T16:10:14.143Z",
"reasonDetail": "Used to add (and remove) event listeners."
},
- {
- "rule": "React-useRef",
- "path": "ts/components/Tooltip.tsx",
- "line": " const wrapperRef = React.useRef(null);",
- "reasonCategory": "usageTrusted",
- "updated": "2021-07-30T16:57:33.618Z"
- },
{
"rule": "React-createRef",
"path": "ts/components/conversation/ConversationHeader.js",