Skip to content

Commit ddd6b1e

Browse files
Fix: Prevent floating local video being dragged offscreen in VideoGallery (#1725)
1 parent ecaf1df commit ddd6b1e

5 files changed

Lines changed: 59 additions & 21 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Fix floating local video tile going offscreen in the VideoGallery Component",
4+
"packageName": "@internal/react-components",
5+
"email": "2684369+JamesBurnside@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

packages/react-components/src/components/ModalClone/ModalClone.tsx

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ import { useWindow } from '@fluentui/react-window-provider';
4343

4444
// @TODO - need to change this to a panel whenever the breakpoint is under medium (verify the spec)
4545

46+
interface ExtendedIModalProps extends IModalProps {
47+
minDragPosition?: ICoordinates;
48+
maxDragPosition?: ICoordinates;
49+
}
50+
4651
const animationDuration = AnimationVariables.durationValue2;
4752
type ICoordinates = { x: number; y: number };
4853

@@ -66,7 +71,7 @@ interface IModalInternalState {
6671

6772
const ZERO: ICoordinates = { x: 0, y: 0 };
6873

69-
const DEFAULT_PROPS: Partial<IModalProps> = {
74+
const DEFAULT_PROPS: Partial<ExtendedIModalProps> = {
7075
isOpen: false,
7176
isDarkOverlay: true,
7277
className: '',
@@ -103,7 +108,7 @@ const useComponentRef = (props: IModalProps, focusTrapZone: React.RefObject<IFoc
103108
);
104109
};
105110

106-
const ModalBase: React.FunctionComponent<IModalProps> = React.forwardRef<HTMLDivElement, IModalProps>(
111+
const ModalBase: React.FunctionComponent<ExtendedIModalProps> = React.forwardRef<HTMLDivElement, ExtendedIModalProps>(
107112
(propsWithoutDefaults, ref) => {
108113
const props = getPropsWithDefaults(DEFAULT_PROPS, propsWithoutDefaults);
109114
const {
@@ -133,7 +138,9 @@ const ModalBase: React.FunctionComponent<IModalProps> = React.forwardRef<HTMLDiv
133138
onLayerDidMount,
134139
isModeless,
135140
dragOptions,
136-
onDismissed
141+
onDismissed,
142+
minDragPosition,
143+
maxDragPosition
137144
} = props;
138145

139146
const rootRef = React.useRef<HTMLDivElement>(null);
@@ -221,8 +228,8 @@ const ModalBase: React.FunctionComponent<IModalProps> = React.forwardRef<HTMLDiv
221228

222229
if (keepInBounds) {
223230
// x/y are unavailable in IE, so use the equivalent left/top
224-
internalState.minPosition = { x: -modalRectangle.left, y: -modalRectangle.top };
225-
internalState.maxPosition = { x: modalRectangle.left, y: modalRectangle.top };
231+
internalState.minPosition = minDragPosition ?? { x: -modalRectangle.left, y: -modalRectangle.top };
232+
internalState.maxPosition = maxDragPosition ?? { x: modalRectangle.left, y: modalRectangle.top };
226233
}
227234
}
228235
};
@@ -1029,13 +1036,12 @@ const getStyles = (props: IModalStyleProps): IModalStyles => {
10291036
};
10301037

10311038
/** @private */
1032-
export const ModalClone: React.FunctionComponent<IModalProps> = styled<IModalProps, IModalStyleProps, IModalStyles>(
1033-
ModalBase,
1034-
getStyles,
1035-
undefined,
1036-
{
1037-
scope: 'Modal',
1038-
fields: ['theme', 'styles', 'enableAriaHiddenSiblings']
1039-
}
1040-
);
1039+
export const ModalClone: React.FunctionComponent<ExtendedIModalProps> = styled<
1040+
ExtendedIModalProps,
1041+
IModalStyleProps,
1042+
IModalStyles
1043+
>(ModalBase, getStyles, undefined, {
1044+
scope: 'Modal',
1045+
fields: ['theme', 'styles', 'enableAriaHiddenSiblings']
1046+
});
10411047
ModalClone.displayName = 'Modal';

packages/react-components/src/components/ModalClone/README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@
22

33
ModalClone is a direct clone of https://github.com/microsoft/fluentui/blob/b7f17e976f9e058f39c9fce4f0f9bb6eb4dfa577/packages/react/src/components/Modal/Modal.ts
44

5-
The only alteration to these files is that the `FocusTrapZone` inside `ModalBase` has the prop `disabled` set to `true`.
5+
This has two deviations from Fluent's Modal:
6+
1. The `FocusTrapZone` inside `ModalBase` has the prop `disabled` set to `true`.
7+
This is to workaround a Fluent A11y bug where the modal is stealing focus and not letting a user keyboard navigate outside the modal: https://github.com/microsoft/fluentui/issues/18924
8+
2. The Modal interface has been extended to allow setting the min and max draggable bounds of the modal:
9+
```ts
10+
interface ExtendedIModalProps extends IModalProps {
11+
minDragPosition?: ICoordinates;
12+
maxDragPosition?: ICoordinates;
13+
}
14+
```
15+
This is to workaround: https://github.com/microsoft/fluentui/issues/20122. Without this the modal can be dragged offscreen.
616

7-
This is to workaround a Fluent A11y bug where the modal is stealing focus and not letting a user keyboard navigate outside the modal: https://github.com/microsoft/fluentui/issues/18924
8-
9-
Once this is resolved delete both `ModalClone.tsx`.
17+
Once these are resolved `ModalClone.tsx` should be deleted and throughout ouur code we should use Fluent's `<Modal />` component.

packages/react-components/src/components/VideoGallery.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ import {
3131
layerHostStyle,
3232
localVideoTileContainerStyle,
3333
videoGalleryContainerStyle,
34-
videoGalleryOuterDivStyle
34+
videoGalleryOuterDivStyle,
35+
localVideoTileStartPositionPX
3536
} from './styles/VideoGallery.styles';
3637
import { isNarrowWidth, useContainerWidth } from './utils/responsive';
3738
import { LocalScreenShare } from './VideoGallery/LocalScreenShare';
@@ -156,6 +157,12 @@ const DRAG_OPTIONS: IDragOptions = {
156157
keepInBounds: true
157158
};
158159

160+
// Manually override the max position used to keep the modal in the bounds of its container.
161+
// This is a workaround for: https://github.com/microsoft/fluentui/issues/20122
162+
// Because our modal starts in the bottom right corner, we can say that this is the max (i.e. rightmost and bottomost)
163+
// position the modal can be dragged to.
164+
const maxDragPosition = { x: localVideoTileStartPositionPX.bottom, y: localVideoTileStartPositionPX.right };
165+
159166
/**
160167
* VideoGallery represents a layout of video tiles for a specific call.
161168
* It displays a {@link VideoTile} for the local user as well as for each remote participant who has joined the call.
@@ -386,6 +393,7 @@ export const VideoGallery = (props: VideoGalleryProps): JSX.Element => {
386393
dragOptions={DRAG_OPTIONS}
387394
styles={floatingLocalVideoModalStyle(theme, isNarrow)}
388395
layerProps={{ hostId: layerHostId }}
396+
maxDragPosition={maxDragPosition}
389397
>
390398
{localVideoTile}
391399
</ModalClone>

packages/react-components/src/components/styles/VideoGallery.styles.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from '@fluentui/react';
1515
import { VideoTileStylesProps } from '../VideoTile';
1616
import { HorizontalGalleryStyles } from '../HorizontalGallery';
17+
import { _pxToRem } from '@internal/acs-ui-common';
1718

1819
/**
1920
* @private
@@ -67,6 +68,12 @@ export const floatingLocalVideoModalStyle = (
6768
);
6869
};
6970

71+
/** @private */
72+
export const localVideoTileStartPositionPX = {
73+
bottom: 8,
74+
right: 8
75+
};
76+
7077
/**
7178
* @private
7279
*/
@@ -75,10 +82,12 @@ export const localVideoTileContainerStyle = (theme: Theme, isNarrow?: boolean):
7582
minWidth: isNarrow ? `${SMALL_FLOATING_MODAL_SIZE_REM.width}rem` : `${LARGE_FLOATING_MODAL_SIZE_REM.width}rem`,
7683
minHeight: isNarrow ? `${SMALL_FLOATING_MODAL_SIZE_REM.height}rem` : `${LARGE_FLOATING_MODAL_SIZE_REM.height}rem`,
7784
position: 'absolute',
78-
bottom: '0.5rem',
85+
bottom: `${_pxToRem(localVideoTileStartPositionPX.bottom)}`,
7986
borderRadius: theme.effects.roundedCorner4,
8087
overflow: 'hidden',
81-
...(theme.rtl ? { left: '0.5rem' } : { right: '0.5rem' })
88+
...(theme.rtl
89+
? { left: `${_pxToRem(localVideoTileStartPositionPX.right)}` }
90+
: { right: `${_pxToRem(localVideoTileStartPositionPX.right)}` })
8291
};
8392
};
8493

0 commit comments

Comments
 (0)