Skip to content

Commit 23eec76

Browse files
Fix VideoEffects Pane reseting focus when an item is selected (#4618)
1 parent 50a6335 commit 23eec76

31 files changed

Lines changed: 49 additions & 34 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "patch",
3+
"area": "fix",
4+
"workstream": "",
5+
"comment": "Fix VideoEffects Pane reseting focus when an item is selected",
6+
"packageName": "@azure/communication-react",
7+
"email": "2684369+JamesBurnside@users.noreply.github.com",
8+
"dependentChangeType": "patch"
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "patch",
3+
"area": "fix",
4+
"workstream": "",
5+
"comment": "Fix VideoEffects Pane reseting focus when an item is selected",
6+
"packageName": "@azure/communication-react",
7+
"email": "2684369+JamesBurnside@users.noreply.github.com",
8+
"dependentChangeType": "patch"
9+
}

packages/react-components/src/components/VideoEffects/VideoBackgroundEffectsPicker.test.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,19 @@ const TEST_PICKER_OPTIONS = [
4545

4646
describe('Background Picker should have the correct number of tiles', () => {
4747
test('Background Picker should have 7 tiles when items are set to wrap', async () => {
48-
const { container } = render(
49-
<_VideoBackgroundEffectsPicker options={TEST_PICKER_OPTIONS} itemsPerRow="wrap" ignoreFocusOnMount={true} />
50-
);
48+
const { container } = render(<_VideoBackgroundEffectsPicker options={TEST_PICKER_OPTIONS} itemsPerRow="wrap" />);
5149
expect(container.querySelectorAll('[data-ui-id="video-effects-item"]').length).toBe(TEST_PICKER_OPTIONS.length);
5250
});
5351

5452
test('Background Picker should have 7 tiles and 2 hidden tiles spanning 3 rows when items per row is set to 3', async () => {
55-
const { container } = render(
56-
<_VideoBackgroundEffectsPicker options={TEST_PICKER_OPTIONS} itemsPerRow={3} ignoreFocusOnMount={true} />
57-
);
53+
const { container } = render(<_VideoBackgroundEffectsPicker options={TEST_PICKER_OPTIONS} itemsPerRow={3} />);
5854
expect(container.querySelectorAll('[data-ui-id="video-effects-item"]').length).toBe(7);
5955
expect(container.querySelectorAll('[data-ui-id="video-effects-hidden-item"]').length).toBe(2);
6056
expect(container.querySelectorAll('[data-ui-id="video-effects-picker-row"]').length).toBe(3);
6157
});
6258

6359
test('Background Picker should have 7 tiles and 1 hidden tile spanning 4 rows when items per row is set to 2', async () => {
64-
const { container } = render(
65-
<_VideoBackgroundEffectsPicker options={TEST_PICKER_OPTIONS} itemsPerRow={2} ignoreFocusOnMount={true} />
66-
);
60+
const { container } = render(<_VideoBackgroundEffectsPicker options={TEST_PICKER_OPTIONS} itemsPerRow={2} />);
6761
expect(container.querySelectorAll('[data-ui-id="video-effects-item"]').length).toBe(7);
6862
expect(container.querySelectorAll('[data-ui-id="video-effects-hidden-item"]').length).toBe(1);
6963
expect(container.querySelectorAll('[data-ui-id="video-effects-picker-row"]').length).toBe(4);

packages/react-components/src/components/VideoEffects/VideoBackgroundEffectsPicker.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,11 @@ export interface _VideoBackgroundEffectsPickerProps {
5454
styles?: _VideoBackgroundEffectsPickerStyles;
5555

5656
/**
57-
* Used only for jest tests. If `true`, the autofocus-on-mount will be ignored when the component mounts.
58-
* If not, jest tries to cleanup after the first render pass, but the component throws an error because
59-
* it tries to display a tooltip after that render pass and after the component is cleaned up.
60-
*
61-
* This can be removed once: https://github.com/microsoft/fluentui/issues/30896 is fixed.
57+
* Imperative handle for calling focus()
6258
*/
63-
ignoreFocusOnMount?: boolean;
59+
componentRef?: React.RefObject<{
60+
focus: () => void;
61+
}>;
6462
}
6563

6664
/**
@@ -151,7 +149,7 @@ export const _VideoBackgroundEffectsPicker = (props: _VideoBackgroundEffectsPick
151149
{...option}
152150
itemKey={option.itemKey}
153151
key={option.itemKey}
154-
focusOnMount={true && !props.ignoreFocusOnMount}
152+
componentRef={props.componentRef}
155153
/>
156154
);
157155
}

packages/react-components/src/components/VideoEffects/VideoEffectsItem.tsx

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
TooltipHost,
1515
useTheme
1616
} from '@fluentui/react';
17-
import React, { useCallback, useEffect } from 'react';
17+
import React, { useCallback } from 'react';
1818
import { videoEffectsItemContainerStyles } from './VideoEffectsItem.styles';
1919

2020
/**
@@ -87,9 +87,11 @@ export interface _VideoEffectsItemProps {
8787
styles?: _VideoEffectsItemStyles;
8888

8989
/**
90-
* Should focus on mounting of the picker item
90+
* Imperative handle for calling focus()
9191
*/
92-
focusOnMount?: boolean;
92+
componentRef?: React.RefObject<{
93+
focus: () => void;
94+
}>;
9395
}
9496

9597
/**
@@ -138,14 +140,6 @@ export const _VideoEffectsItem = (props: _VideoEffectsItemProps): JSX.Element =>
138140
[backgroundImage, disabled, isSelected, theme]
139141
);
140142

141-
const componentRef = React.createRef<IButton>();
142-
143-
useEffect(() => {
144-
if (props.focusOnMount && componentRef.current) {
145-
componentRef.current.focus();
146-
}
147-
}, [componentRef, props.focusOnMount]);
148-
149143
return (
150144
<TooltipHost {...props.tooltipProps}>
151145
<Stack
@@ -160,8 +154,7 @@ export const _VideoEffectsItem = (props: _VideoEffectsItemProps): JSX.Element =>
160154
<DefaultButton
161155
styles={containerStyles()}
162156
onClick={disabled ? undefined : () => props.onSelect?.(props.itemKey)}
163-
componentRef={componentRef}
164-
autoFocus={props.focusOnMount}
157+
componentRef={props.componentRef as React.RefObject<IButton>}
165158
>
166159
<Stack horizontalAlign={'center'}>
167160
{props.iconProps && (

packages/react-composites/src/composites/CallComposite/components/SidePane/useVideoEffectsPane.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import React, { useCallback, useEffect, useMemo } from 'react';
4+
import React, { createRef, useCallback, useEffect, useMemo } from 'react';
55
import { SidePaneRenderer, useIsParticularSidePaneOpen } from './SidePaneProvider';
66
import { SidePaneHeader } from '../../../common/SidePaneHeader';
77

@@ -52,6 +52,7 @@ export const useVideoEffectsPane = (
5252
}, [closePane, locale.strings, mobileView]);
5353

5454
const latestVideoEffectError = latestErrors.find((error) => error.type === 'unableToStartVideoEffect');
55+
const updateFocusHandle = useMemo(() => createRef<{ focus: () => void }>(), []);
5556

5657
const onRenderContent = useCallback((): JSX.Element => {
5758
return (
@@ -63,9 +64,10 @@ export const useVideoEffectsPane = (
6364

6465
latestVideoEffectError && onDismissError?.(latestVideoEffectError);
6566
}}
67+
updateFocusHandle={updateFocusHandle}
6668
/>
6769
);
68-
}, [latestVideoEffectError, onDismissError]);
70+
}, [latestVideoEffectError, onDismissError, updateFocusHandle]);
6971

7072
const sidePaneRenderer: SidePaneRenderer = useMemo(
7173
() => ({
@@ -78,7 +80,10 @@ export const useVideoEffectsPane = (
7880

7981
const openPane = useCallback(() => {
8082
updateSidePaneRenderer(sidePaneRenderer);
81-
}, [sidePaneRenderer, updateSidePaneRenderer]);
83+
84+
// Run in a setTimeout as it must be called only once the imperative handle is available
85+
setTimeout(() => updateFocusHandle.current?.focus(), 0);
86+
}, [sidePaneRenderer, updateSidePaneRenderer, updateFocusHandle]);
8287

8388
const isOpen = useIsParticularSidePaneOpen(VIDEO_EFFECTS_SIDE_PANE_ID);
8489

packages/react-composites/src/composites/common/VideoEffectsPane.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ export const VideoEffectsPaneContent = (props: {
3737
activeVideoEffectError?: ActiveErrorMessage;
3838
onDismissError: (error: ActiveErrorMessage) => void;
3939
activeVideoEffectChange: (effect: ActiveVideoEffect) => void;
40+
updateFocusHandle: React.RefObject<{
41+
focus: () => void;
42+
}>;
4043
}): JSX.Element => {
4144
const {
4245
onDismissError,
@@ -140,18 +143,21 @@ export const VideoEffectsPaneContent = (props: {
140143
};
141144
adapter.updateSelectedVideoBackgroundEffect(noneEffect);
142145
}
146+
143147
return VideoEffectsPaneTrampoline(
144148
onDismissError,
149+
props.updateFocusHandle,
145150
activeVideoEffectError,
146-
147151
selectableVideoEffects,
148-
149152
onEffectChange
150153
);
151154
};
152155

153156
const VideoEffectsPaneTrampoline = (
154157
onDismissError: (error: ActiveErrorMessage) => void,
158+
updateFocusHandle: React.RefObject<{
159+
focus: () => void;
160+
}>,
155161
activeVideoEffectError?: ActiveErrorMessage,
156162
selectableVideoEffects?: _VideoEffectsItemProps[],
157163
onEffectChange?: (effectKey: string) => Promise<void>
@@ -182,6 +188,7 @@ const VideoEffectsPaneTrampoline = (
182188
options={selectableVideoEffects ?? []}
183189
onChange={onEffectChange}
184190
selectedEffectKey={selectedEffect}
191+
componentRef={updateFocusHandle}
185192
/>
186193
</Stack>
187194
);

0 commit comments

Comments
 (0)