@azure/communication-react 1.4.3-alpha-202212070014.0 → 1.4.3-alpha-202212090014.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/dist-cjs/communication-react/index.js +1292 -1285
  2. package/dist/dist-cjs/communication-react/index.js.map +1 -1
  3. package/dist/dist-esm/acs-ui-common/src/telemetryVersion.js +1 -1
  4. package/dist/dist-esm/acs-ui-common/src/telemetryVersion.js.map +1 -1
  5. package/dist/dist-esm/calling-component-bindings/src/utils/videoGalleryUtils.d.ts +12 -0
  6. package/dist/dist-esm/calling-component-bindings/src/utils/videoGalleryUtils.js +16 -0
  7. package/dist/dist-esm/calling-component-bindings/src/utils/videoGalleryUtils.js.map +1 -1
  8. package/dist/dist-esm/calling-component-bindings/src/videoGallerySelector.js +2 -13
  9. package/dist/dist-esm/calling-component-bindings/src/videoGallerySelector.js.map +1 -1
  10. package/dist/dist-esm/react-components/src/components/DevicePermissions/DomainPermissionsScaffolding.js +1 -1
  11. package/dist/dist-esm/react-components/src/components/DevicePermissions/DomainPermissionsScaffolding.js.map +1 -1
  12. package/dist/dist-esm/react-components/src/components/Dialpad/Dialpad.js +8 -0
  13. package/dist/dist-esm/react-components/src/components/Dialpad/Dialpad.js.map +1 -1
  14. package/dist/dist-esm/react-components/src/components/VideoGallery/FloatingLocalVideo.d.ts +12 -0
  15. package/dist/dist-esm/react-components/src/components/VideoGallery/FloatingLocalVideo.js +41 -0
  16. package/dist/dist-esm/react-components/src/components/VideoGallery/FloatingLocalVideo.js.map +1 -0
  17. package/dist/dist-esm/react-components/src/components/VideoGallery/FloatingLocalVideoLayout.d.ts +49 -0
  18. package/dist/dist-esm/react-components/src/components/VideoGallery/FloatingLocalVideoLayout.js +61 -0
  19. package/dist/dist-esm/react-components/src/components/VideoGallery/FloatingLocalVideoLayout.js.map +1 -0
  20. package/dist/dist-esm/react-components/src/components/VideoGallery/styles/FloatingLocalVideo.styles.d.ts +61 -0
  21. package/dist/dist-esm/react-components/src/components/VideoGallery/styles/FloatingLocalVideo.styles.js +117 -0
  22. package/dist/dist-esm/react-components/src/components/VideoGallery/styles/FloatingLocalVideo.styles.js.map +1 -0
  23. package/dist/dist-esm/react-components/src/components/VideoGallery/styles/FloatingLocalVideoLayout.styles.d.ts +14 -0
  24. package/dist/dist-esm/react-components/src/components/VideoGallery/styles/FloatingLocalVideoLayout.styles.js +28 -0
  25. package/dist/dist-esm/react-components/src/components/VideoGallery/styles/FloatingLocalVideoLayout.styles.js.map +1 -0
  26. package/dist/dist-esm/react-components/src/components/VideoGallery/videoGalleryUtils.js +5 -1
  27. package/dist/dist-esm/react-components/src/components/VideoGallery/videoGalleryUtils.js.map +1 -1
  28. package/dist/dist-esm/react-components/src/components/VideoGallery.js +9 -120
  29. package/dist/dist-esm/react-components/src/components/VideoGallery.js.map +1 -1
  30. package/dist/dist-esm/react-components/src/components/styles/VideoGallery.styles.d.ts +1 -95
  31. package/dist/dist-esm/react-components/src/components/styles/VideoGallery.styles.js +3 -138
  32. package/dist/dist-esm/react-components/src/components/styles/VideoGallery.styles.js.map +1 -1
  33. package/package.json +8 -8
@@ -202,7 +202,7 @@ const _toCommunicationIdentifier = (id) => {
202
202
  // Copyright (c) Microsoft Corporation.
203
203
  // Licensed under the MIT license.
204
204
  // GENERATED FILE. DO NOT EDIT MANUALLY.
205
- var telemetryVersion = '1.4.3-alpha-202212070014.0';
205
+ var telemetryVersion = '1.4.3-alpha-202212090014.0';
206
206
 
207
207
  // Copyright (c) Microsoft Corporation.
208
208
  /**
@@ -7828,56 +7828,6 @@ const ParticipantList = (props) => {
7828
7828
  : onRenderParticipantDefault(participant, strings, myUserId, onRenderAvatar, createParticipantMenuItems, participantItemStyles, props.onParticipantClick, showParticipantOverflowTooltip))));
7829
7829
  };
7830
7830
 
7831
- /**
7832
- * Calculates the participants that should be rendered based on the list of dominant
7833
- * speakers and currently rendered participants in a call.
7834
- * @param args - SmartDominantSpeakerParticipantsArgs
7835
- * @returns VideoGalleryRemoteParticipant[] {@link @azure/communication-react#VideoGalleryRemoteParticipant}
7836
- */
7837
- const smartDominantSpeakerParticipants = (args) => {
7838
- const { participants, dominantSpeakers = [], lastVisibleParticipants = [], maxDominantSpeakers } = args;
7839
- // Don't apply any logic if total number of video streams is less than max dominant speakers.
7840
- if (participants.length <= maxDominantSpeakers) {
7841
- return participants;
7842
- }
7843
- const participantsMap = participantsById(participants);
7844
- // Only use the Max allowed dominant speakers that exist in participants
7845
- const dominantSpeakerIds = Array.from(new Set(dominantSpeakers).values())
7846
- .filter((id) => !!participantsMap[id])
7847
- .slice(0, maxDominantSpeakers);
7848
- const lastVisibleParticipantIds = lastVisibleParticipants.map((p) => p.userId);
7849
- const newVisibleParticipantIds = lastVisibleParticipants.map((p) => p.userId).slice(0, maxDominantSpeakers);
7850
- const newDominantSpeakerIds = dominantSpeakerIds.filter((id) => !newVisibleParticipantIds.includes(id));
7851
- // Remove participants that are no longer dominant and replace them with new dominant speakers.
7852
- for (let index = 0; index < maxDominantSpeakers; index++) {
7853
- const newVisibleParticipantId = newVisibleParticipantIds[index];
7854
- if (newVisibleParticipantId === undefined || !dominantSpeakerIds.includes(newVisibleParticipantId)) {
7855
- const replacement = newDominantSpeakerIds.shift();
7856
- if (!replacement) {
7857
- break;
7858
- }
7859
- newVisibleParticipantIds[index] = replacement;
7860
- }
7861
- }
7862
- const removedVisibleParticipantIds = lastVisibleParticipantIds.filter((p) => !newVisibleParticipantIds.includes(p));
7863
- removedVisibleParticipantIds.forEach((p) => newVisibleParticipantIds.push(p));
7864
- const newVisibleParticipantIdSet = new Set(newVisibleParticipantIds);
7865
- const leftoverParticipants = participants.filter((p) => !newVisibleParticipantIdSet.has(p.userId));
7866
- leftoverParticipants.forEach((p) => {
7867
- newVisibleParticipantIds.push(p.userId);
7868
- });
7869
- // newVisibleParticipantIds can contain identifiers for participants that are no longer in the call. So we ignore those IDs.
7870
- const newVisibleParticipants = newVisibleParticipantIds
7871
- .map((participantId) => participantsMap[participantId])
7872
- .filter((p) => !!p);
7873
- return newVisibleParticipants;
7874
- };
7875
- const participantsById = (participants) => {
7876
- const response = {};
7877
- participants.forEach((p) => (response[p.userId] = p));
7878
- return response;
7879
- };
7880
-
7881
7831
  // Copyright (c) Microsoft Corporation.
7882
7832
  /**
7883
7833
  * Helper hook to maintain the video stream lifecycle. This calls onCreateStreamView and onDisposeStreamView
@@ -8386,133 +8336,6 @@ const _RemoteVideoTile = React__default['default'].memo((props) => {
8386
8336
  participantState: props.participantState }));
8387
8337
  });
8388
8338
 
8389
- // Copyright (c) Microsoft Corporation.
8390
- /**
8391
- * @private
8392
- */
8393
- const videoGalleryOuterDivStyle = react.mergeStyles({ position: 'relative', width: '100%', height: '100%' });
8394
- /**
8395
- * @private
8396
- */
8397
- const videoGalleryContainerStyle = {
8398
- root: { position: 'relative', height: '100%', width: '100%', padding: '0.5rem' }
8399
- };
8400
- /**
8401
- * Small floating modal width and height in rem for small screen
8402
- */
8403
- const SMALL_FLOATING_MODAL_SIZE_PX$1 = { width: 64, height: 88 };
8404
- /**
8405
- * Large floating modal width and height in rem for large screen
8406
- */
8407
- const LARGE_FLOATING_MODAL_SIZE_PX$1 = { width: 160, height: 120 };
8408
- /**
8409
- * @private
8410
- * z-index to ensure that the local video tile is above the video gallery.
8411
- */
8412
- const LOCAL_VIDEO_TILE_ZINDEX = 2;
8413
- /**
8414
- * @private
8415
- */
8416
- const floatingLocalVideoModalStyle = (theme, isNarrow) => {
8417
- return react.concatStyleSets({
8418
- main: localVideoTileContainerStyle(theme, isNarrow)
8419
- }, {
8420
- main: {
8421
- boxShadow: theme.effects.elevation8,
8422
- ':focus-within': {
8423
- boxShadow: theme.effects.elevation16,
8424
- border: `${_pxToRem(2)} solid ${theme.palette.neutralPrimary}`
8425
- }
8426
- }
8427
- }, localVideoModalStyles);
8428
- };
8429
- /**
8430
- * Padding equal to the amount the modal should stay inside the bounds of the container.
8431
- * i.e. if this is 8px, the modal should always be at least 8px inside the container at all times on all sides.
8432
- * @private
8433
- */
8434
- const localVideoTileOuterPaddingPX = 8;
8435
- /**
8436
- * @private
8437
- */
8438
- const localVideoTileContainerStyle = (theme, isNarrow) => {
8439
- return Object.assign({ minWidth: isNarrow ? _pxToRem(SMALL_FLOATING_MODAL_SIZE_PX$1.width) : _pxToRem(LARGE_FLOATING_MODAL_SIZE_PX$1.width), minHeight: isNarrow ? _pxToRem(SMALL_FLOATING_MODAL_SIZE_PX$1.height) : _pxToRem(LARGE_FLOATING_MODAL_SIZE_PX$1.height), position: 'absolute', bottom: _pxToRem(localVideoTileOuterPaddingPX), borderRadius: theme.effects.roundedCorner4, overflow: 'hidden' }, (theme.rtl
8440
- ? { left: _pxToRem(localVideoTileOuterPaddingPX) }
8441
- : { right: _pxToRem(localVideoTileOuterPaddingPX) }));
8442
- };
8443
- /**
8444
- * @private
8445
- */
8446
- const localVideoTileWithControlsContainerStyle = (theme, isNarrow) => {
8447
- return react.concatStyleSets(localVideoTileContainerStyle(theme, isNarrow), {
8448
- root: { boxShadow: theme.effects.elevation8 }
8449
- });
8450
- };
8451
- /**
8452
- * @private
8453
- */
8454
- const floatingLocalVideoTileStyle = {
8455
- root: {
8456
- position: 'absolute',
8457
- zIndex: LOCAL_VIDEO_TILE_ZINDEX,
8458
- height: '100%',
8459
- width: '100%'
8460
- }
8461
- };
8462
- /**
8463
- * @private
8464
- */
8465
- const layerHostStyle = {
8466
- position: 'absolute',
8467
- left: 0,
8468
- top: 0,
8469
- width: '100%',
8470
- height: '100%',
8471
- overflow: 'hidden',
8472
- // pointer events for layerHost set to none to make descendants interactive
8473
- pointerEvents: 'none'
8474
- };
8475
- /**
8476
- * @private
8477
- */
8478
- const localVideoCameraCycleButtonStyles = (theme) => {
8479
- return {
8480
- root: {
8481
- position: 'absolute',
8482
- width: _pxToRem(32),
8483
- height: _pxToRem(32),
8484
- right: '0rem',
8485
- top: '0rem',
8486
- color: '#FFFFFF',
8487
- zIndex: 2,
8488
- background: 'rgba(0,0,0,0.4)',
8489
- borderRadius: theme.effects.roundedCorner2
8490
- },
8491
- rootFocused: {
8492
- // styles to remove the unwanted white highlight and blue colour after tapping on button.
8493
- color: '#FFFFFF',
8494
- background: 'rgba(0,0,0,0.4)' // sets opacity of background to be visible on all backdrops in video stream.
8495
- },
8496
- icon: {
8497
- paddingLeft: _pxToRem(3),
8498
- paddingRight: _pxToRem(3),
8499
- margin: 0
8500
- },
8501
- flexContainer: {
8502
- paddingBottom: _pxToRem(8)
8503
- }
8504
- };
8505
- };
8506
- /**
8507
- * Styles for the local video tile modal when it is focused, will cause keyboard move icon to appear over video
8508
- * @private
8509
- */
8510
- const localVideoModalStyles = {
8511
- keyboardMoveIconContainer: {
8512
- zIndex: LOCAL_VIDEO_TILE_ZINDEX + 1 // zIndex to set the keyboard movement Icon above the other layers in the video tile.
8513
- }
8514
- };
8515
-
8516
8339
  // Copyright (c) Microsoft Corporation.
8517
8340
  /**
8518
8341
  * @private
@@ -8619,1053 +8442,1323 @@ const LoadingSpinner = (props) => {
8619
8442
  };
8620
8443
 
8621
8444
  // Copyright (c) Microsoft Corporation.
8622
- const animationDuration = react.AnimationVariables.durationValue2;
8623
- const ZERO = { x: 0, y: 0 };
8624
- const DEFAULT_PROPS = {
8625
- isOpen: false,
8626
- isDarkOverlay: true,
8627
- className: '',
8628
- containerClassName: '',
8629
- enableAriaHiddenSiblings: true
8630
- };
8631
- const getModalClassNames = react.classNamesFunction();
8632
- const getMoveDelta = (ev) => {
8633
- let delta = 10;
8634
- if (ev.shiftKey) {
8635
- if (!ev.ctrlKey) {
8636
- delta = 50;
8445
+ /**
8446
+ * @private
8447
+ */
8448
+ const videoGalleryOuterDivStyle = react.mergeStyles({ position: 'relative', width: '100%', height: '100%' });
8449
+ /**
8450
+ * @private
8451
+ */
8452
+ const localVideoCameraCycleButtonStyles = (theme) => {
8453
+ return {
8454
+ root: {
8455
+ position: 'absolute',
8456
+ width: _pxToRem(32),
8457
+ height: _pxToRem(32),
8458
+ right: '0rem',
8459
+ top: '0rem',
8460
+ color: '#FFFFFF',
8461
+ zIndex: 2,
8462
+ background: 'rgba(0,0,0,0.4)',
8463
+ borderRadius: theme.effects.roundedCorner2
8464
+ },
8465
+ rootFocused: {
8466
+ // styles to remove the unwanted white highlight and blue colour after tapping on button.
8467
+ color: '#FFFFFF',
8468
+ background: 'rgba(0,0,0,0.4)' // sets opacity of background to be visible on all backdrops in video stream.
8469
+ },
8470
+ icon: {
8471
+ paddingLeft: _pxToRem(3),
8472
+ paddingRight: _pxToRem(3),
8473
+ margin: 0
8474
+ },
8475
+ flexContainer: {
8476
+ paddingBottom: _pxToRem(8),
8477
+ height: 'unset'
8637
8478
  }
8638
- }
8639
- else if (ev.ctrlKey) {
8640
- delta = 1;
8641
- }
8642
- return delta;
8479
+ };
8643
8480
  };
8644
- const useComponentRef = (props, focusTrapZone) => {
8645
- React__namespace.useImperativeHandle(props.componentRef, () => ({
8646
- focus() {
8647
- if (focusTrapZone.current) {
8648
- focusTrapZone.current.focus();
8481
+
8482
+ // Copyright (c) Microsoft Corporation.
8483
+ /**
8484
+ * local video tile camera cycle button - for use on mobile screens only.
8485
+ * @internal
8486
+ */
8487
+ const LocalVideoCameraCycleButton = (props) => {
8488
+ const { cameras, selectedCamera, onSelectCamera, label, ariaDescription } = props;
8489
+ const theme = react.useTheme();
8490
+ return (React__default['default'].createElement(react.IconButton, { "data-ui-id": 'local-camera-switcher-button', styles: localVideoCameraCycleButtonStyles(theme), iconProps: { iconName: 'LocalCameraSwitch' }, ariaLabel: label, ariaDescription: ariaDescription, "aria-live": 'polite', onClick: () => {
8491
+ if (cameras && cameras.length > 1 && selectedCamera !== undefined) {
8492
+ const index = cameras.findIndex((camera) => selectedCamera.id === camera.id);
8493
+ const newCamera = cameras[(index + 1) % cameras.length];
8494
+ if (onSelectCamera !== undefined) {
8495
+ onSelectCamera(newCamera);
8496
+ }
8649
8497
  }
8650
- }
8651
- }), [focusTrapZone]);
8498
+ } }));
8652
8499
  };
8653
- const ModalBase = React__namespace.forwardRef((propsWithoutDefaults, ref) => {
8654
- const props = react.getPropsWithDefaults(DEFAULT_PROPS, propsWithoutDefaults);
8655
- const { allowTouchBodyScroll, className, children, containerClassName, scrollableContentClassName, elementToFocusOnDismiss, firstFocusableSelector, forceFocusInsideTrap, ignoreExternalFocusing, isBlocking, isAlert, isClickableOutsideFocusTrap, isDarkOverlay, onDismiss, layerProps, overlay, isOpen, titleAriaId, styles, subtitleAriaId, theme, topOffsetFixed, responsiveMode, onLayerDidMount, isModeless, dragOptions, onDismissed, minDragPosition, maxDragPosition } = props;
8656
- const rootRef = React__namespace.useRef(null);
8657
- const focusTrapZone = React__namespace.useRef(null);
8658
- const focusTrapZoneElm = React__namespace.useRef(null);
8659
- const mergedRef = reactHooks.useMergedRefs(rootRef, ref);
8660
- const modalResponsiveMode = react.useResponsiveMode(mergedRef);
8661
- const focusTrapZoneId = reactHooks.useId('ModalFocusTrapZone');
8662
- const win = reactWindowProvider.useWindow();
8663
- const { setTimeout, clearTimeout } = reactHooks.useSetTimeout();
8664
- const [isModalOpen, setIsModalOpen] = React__namespace.useState(isOpen);
8665
- const [isVisible, setIsVisible] = React__namespace.useState(isOpen);
8666
- const [coordinates, setCoordinates] = React__namespace.useState(ZERO);
8667
- const [modalRectangleTop, setModalRectangleTop] = React__namespace.useState();
8668
- const [isModalMenuOpen, { toggle: toggleModalMenuOpen, setFalse: setModalMenuClose }] = reactHooks.useBoolean(false);
8669
- const internalState = reactHooks.useConst(() => ({
8670
- onModalCloseTimer: 0,
8671
- allowTouchBodyScroll,
8672
- scrollableContent: null,
8673
- lastSetCoordinates: ZERO,
8674
- events: new react.EventGroup({})
8675
- }));
8676
- const { keepInBounds } = dragOptions || {};
8677
- const isAlertRole = isAlert !== null && isAlert !== void 0 ? isAlert : (isBlocking && !isModeless);
8678
- const layerClassName = layerProps === undefined ? '' : layerProps.className;
8679
- const classNames = getModalClassNames(styles, {
8680
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
8681
- theme: theme,
8682
- className,
8683
- containerClassName,
8684
- scrollableContentClassName,
8685
- isOpen,
8686
- isVisible,
8687
- hasBeenOpened: internalState.hasBeenOpened,
8688
- modalRectangleTop,
8689
- topOffsetFixed,
8690
- isModeless,
8691
- layerClassName,
8692
- windowInnerHeight: win === null || win === void 0 ? void 0 : win.innerHeight,
8693
- isDefaultDragHandle: dragOptions && !dragOptions.dragHandleSelector
8694
- });
8695
- const mergedLayerProps = Object.assign(Object.assign({ eventBubblingEnabled: false }, layerProps), { onLayerDidMount: layerProps && layerProps.onLayerDidMount ? layerProps.onLayerDidMount : onLayerDidMount, insertFirst: isModeless, className: classNames.layer });
8696
- // Allow the user to scroll within the modal but not on the body
8697
- const allowScrollOnModal = React__namespace.useCallback((elt) => {
8698
- if (elt) {
8699
- if (internalState.allowTouchBodyScroll) {
8700
- react.allowOverscrollOnElement(elt, internalState.events);
8701
- }
8702
- else {
8703
- react.allowScrollOnElement(elt, internalState.events);
8704
- }
8705
- }
8706
- else {
8707
- internalState.events.off(internalState.scrollableContent);
8708
- }
8709
- internalState.scrollableContent = elt;
8710
- }, [internalState]);
8711
- const registerInitialModalPosition = () => {
8712
- const dialogMain = focusTrapZoneElm.current;
8713
- const modalRectangle = dialogMain === null || dialogMain === void 0 ? void 0 : dialogMain.getBoundingClientRect();
8714
- if (modalRectangle) {
8715
- if (topOffsetFixed) {
8716
- setModalRectangleTop(modalRectangle.top);
8717
- }
8718
- if (keepInBounds) {
8719
- // x/y are unavailable in IE, so use the equivalent left/top
8720
- internalState.minPosition = minDragPosition !== null && minDragPosition !== void 0 ? minDragPosition : { x: -modalRectangle.left, y: -modalRectangle.top };
8721
- internalState.maxPosition = maxDragPosition !== null && maxDragPosition !== void 0 ? maxDragPosition : { x: modalRectangle.left, y: modalRectangle.top };
8722
- // Make sure the initial co-ordinates are within clamp bounds.
8723
- setCoordinates({
8724
- x: getClampedAxis('x', coordinates.x),
8725
- y: getClampedAxis('y', coordinates.y)
8726
- });
8727
- }
8728
- }
8729
- };
8730
- /**
8731
- * Clamps an axis to a specified min and max position.
8732
- *
8733
- * @param axis A string that represents the axis (x/y).
8734
- * @param position The position on the axis.
8735
- */
8736
- const getClampedAxis = React__namespace.useCallback((axis, position) => {
8737
- const { minPosition, maxPosition } = internalState;
8738
- if (keepInBounds && minPosition && maxPosition) {
8739
- position = Math.max(minPosition[axis], position);
8740
- position = Math.min(maxPosition[axis], position);
8741
- }
8742
- return position;
8743
- }, [keepInBounds, internalState]);
8744
- const handleModalClose = () => {
8745
- var _a;
8746
- internalState.lastSetCoordinates = ZERO;
8747
- setModalMenuClose();
8748
- internalState.isInKeyboardMoveMode = false;
8749
- setIsModalOpen(false);
8750
- setCoordinates(ZERO);
8751
- (_a = internalState.disposeOnKeyUp) === null || _a === void 0 ? void 0 : _a.call(internalState);
8752
- onDismissed === null || onDismissed === void 0 ? void 0 : onDismissed();
8753
- };
8754
- const handleDragStart = React__namespace.useCallback(() => {
8755
- setModalMenuClose();
8756
- internalState.isInKeyboardMoveMode = false;
8757
- }, [internalState, setModalMenuClose]);
8758
- const handleDrag = React__namespace.useCallback(
8759
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
8760
- (ev, dragData) => {
8761
- setCoordinates((prevValue) => ({
8762
- x: getClampedAxis('x', prevValue.x + dragData.delta.x),
8763
- y: getClampedAxis('y', prevValue.y + dragData.delta.y)
8764
- }));
8765
- }, [getClampedAxis]);
8766
- const handleDragStop = React__namespace.useCallback(() => {
8767
- if (focusTrapZone.current) {
8768
- focusTrapZone.current.focus();
8769
- }
8770
- }, []);
8771
- const handleEnterKeyboardMoveMode = () => {
8772
- // We need a global handleKeyDown event when we are in the move mode so that we can
8773
- // handle the key presses and the components inside the modal do not get the events
8774
- const handleKeyDown = (ev) => {
8775
- if (ev.altKey && ev.ctrlKey && ev.keyCode === react.KeyCodes.space) {
8776
- // CTRL + ALT + SPACE is handled during keyUp
8777
- ev.preventDefault();
8778
- ev.stopPropagation();
8779
- return;
8780
- }
8781
- const newLocal = ev.altKey || ev.keyCode === react.KeyCodes.escape;
8782
- if (isModalMenuOpen && newLocal) {
8783
- setModalMenuClose();
8784
- }
8785
- if (internalState.isInKeyboardMoveMode && (ev.keyCode === react.KeyCodes.escape || ev.keyCode === react.KeyCodes.enter)) {
8786
- internalState.isInKeyboardMoveMode = false;
8787
- ev.preventDefault();
8788
- ev.stopPropagation();
8789
- }
8790
- if (internalState.isInKeyboardMoveMode) {
8791
- let handledEvent = true;
8792
- const delta = getMoveDelta(ev);
8793
- switch (ev.keyCode) {
8794
- /* eslint-disable no-fallthrough */
8795
- case react.KeyCodes.escape:
8796
- setCoordinates(internalState.lastSetCoordinates);
8797
- case react.KeyCodes.enter: {
8798
- // TODO: determine if fallthrough was intentional
8799
- /* eslint-enable no-fallthrough */
8800
- internalState.lastSetCoordinates = ZERO;
8801
- // setIsInKeyboardMoveMode(false);
8802
- break;
8803
- }
8804
- case react.KeyCodes.up: {
8805
- setCoordinates((prevValue) => ({ x: prevValue.x, y: getClampedAxis('y', prevValue.y - delta) }));
8806
- break;
8807
- }
8808
- case react.KeyCodes.down: {
8809
- setCoordinates((prevValue) => ({ x: prevValue.x, y: getClampedAxis('y', prevValue.y + delta) }));
8810
- break;
8811
- }
8812
- case react.KeyCodes.left: {
8813
- setCoordinates((prevValue) => ({ x: getClampedAxis('x', prevValue.x - delta), y: prevValue.y }));
8814
- break;
8815
- }
8816
- case react.KeyCodes.right: {
8817
- setCoordinates((prevValue) => ({ x: getClampedAxis('x', prevValue.x + delta), y: prevValue.y }));
8818
- break;
8819
- }
8820
- default: {
8821
- handledEvent = false;
8822
- }
8823
- }
8824
- if (handledEvent) {
8825
- ev.preventDefault();
8826
- ev.stopPropagation();
8827
- }
8828
- }
8829
- };
8830
- internalState.lastSetCoordinates = coordinates;
8831
- setModalMenuClose();
8832
- internalState.isInKeyboardMoveMode = true;
8833
- internalState.events.on(win, 'keydown', handleKeyDown, true /* useCapture */);
8834
- internalState.disposeOnKeyDown = () => {
8835
- internalState.events.off(win, 'keydown', handleKeyDown, true /* useCapture */);
8836
- internalState.disposeOnKeyDown = undefined;
8837
- };
8838
- };
8839
- const handleExitKeyboardMoveMode = () => {
8840
- var _a;
8841
- internalState.lastSetCoordinates = ZERO;
8842
- internalState.isInKeyboardMoveMode = false;
8843
- (_a = internalState.disposeOnKeyDown) === null || _a === void 0 ? void 0 : _a.call(internalState);
8844
- };
8845
- const registerForKeyUp = () => {
8846
- const handleKeyUp = (ev) => {
8847
- // Needs to handle the CTRL + ALT + SPACE key during keyup due to FireFox bug:
8848
- // https://bugzilla.mozilla.org/show_bug.cgi?id=1220143
8849
- if (ev.altKey && ev.ctrlKey && ev.keyCode === react.KeyCodes.space) {
8850
- if (react.elementContains(internalState.scrollableContent, ev.target)) {
8851
- toggleModalMenuOpen();
8852
- ev.preventDefault();
8853
- ev.stopPropagation();
8854
- }
8855
- }
8856
- };
8857
- if (!internalState.disposeOnKeyUp) {
8858
- internalState.events.on(win, 'keyup', handleKeyUp, true /* useCapture */);
8859
- internalState.disposeOnKeyUp = () => {
8860
- internalState.events.off(win, 'keyup', handleKeyUp, true /* useCapture */);
8861
- internalState.disposeOnKeyUp = undefined;
8862
- };
8500
+
8501
+ // Copyright (c) Microsoft Corporation.
8502
+ /**
8503
+ * A memoized version of VideoTile for rendering local participant.
8504
+ *
8505
+ * @internal
8506
+ */
8507
+ const _LocalVideoTile = React__default['default'].memo((props) => {
8508
+ const { isAvailable, isMuted, onCreateLocalStreamView, onDisposeLocalStreamView, localVideoViewOptions, renderElement, userId, showLabel, displayName, initialsName, onRenderAvatar, showMuteIndicator, styles, showCameraSwitcherInLocalPreview, localVideoCameraCycleButtonProps, localVideoCameraSwitcherLabel, localVideoSelectedDescription } = props;
8509
+ const localVideoStreamProps = React.useMemo(() => ({
8510
+ isMirrored: localVideoViewOptions === null || localVideoViewOptions === void 0 ? void 0 : localVideoViewOptions.isMirrored,
8511
+ isStreamAvailable: isAvailable,
8512
+ onCreateLocalStreamView,
8513
+ onDisposeLocalStreamView,
8514
+ renderElementExists: !!renderElement,
8515
+ scalingMode: localVideoViewOptions === null || localVideoViewOptions === void 0 ? void 0 : localVideoViewOptions.scalingMode
8516
+ }), [
8517
+ isAvailable,
8518
+ localVideoViewOptions === null || localVideoViewOptions === void 0 ? void 0 : localVideoViewOptions.isMirrored,
8519
+ localVideoViewOptions === null || localVideoViewOptions === void 0 ? void 0 : localVideoViewOptions.scalingMode,
8520
+ onCreateLocalStreamView,
8521
+ onDisposeLocalStreamView,
8522
+ renderElement
8523
+ ]);
8524
+ // Handle creating, destroying and updating the video stream as necessary
8525
+ useLocalVideoStreamLifecycleMaintainer(localVideoStreamProps);
8526
+ const renderVideoStreamElement = React.useMemo(() => {
8527
+ // Checking if renderElement is well defined or not as calling SDK has a number of video streams limitation which
8528
+ // implies that, after their threshold, all streams have no child (blank video)
8529
+ if (!renderElement || !renderElement.childElementCount) {
8530
+ // Returning `undefined` results in the placeholder with avatar being shown
8531
+ return undefined;
8863
8532
  }
8533
+ return (React__default['default'].createElement(React__default['default'].Fragment, null,
8534
+ React__default['default'].createElement(FloatingLocalCameraCycleButton, { showCameraSwitcherInLocalPreview: showCameraSwitcherInLocalPreview !== null && showCameraSwitcherInLocalPreview !== void 0 ? showCameraSwitcherInLocalPreview : false, localVideoCameraCycleButtonProps: localVideoCameraCycleButtonProps, localVideoCameraSwitcherLabel: localVideoCameraSwitcherLabel, localVideoSelectedDescription: localVideoSelectedDescription }),
8535
+ React__default['default'].createElement(StreamMedia, { videoStreamElement: renderElement, isMirrored: true })));
8536
+ }, [
8537
+ localVideoCameraCycleButtonProps,
8538
+ localVideoCameraSwitcherLabel,
8539
+ localVideoSelectedDescription,
8540
+ renderElement,
8541
+ showCameraSwitcherInLocalPreview
8542
+ ]);
8543
+ return (React__default['default'].createElement(VideoTile, { key: userId !== null && userId !== void 0 ? userId : 'local-video-tile', userId: userId, renderElement: renderVideoStreamElement, showLabel: showLabel, displayName: displayName, initialsName: initialsName, styles: styles, onRenderPlaceholder: onRenderAvatar, isMuted: isMuted, showMuteIndicator: showMuteIndicator, personaMinSize: props.personaMinSize }));
8544
+ });
8545
+ const FloatingLocalCameraCycleButton = (props) => {
8546
+ const { showCameraSwitcherInLocalPreview, localVideoCameraCycleButtonProps, localVideoCameraSwitcherLabel, localVideoSelectedDescription } = props;
8547
+ const ariaDescription = (localVideoCameraCycleButtonProps === null || localVideoCameraCycleButtonProps === void 0 ? void 0 : localVideoCameraCycleButtonProps.selectedCamera) &&
8548
+ localVideoSelectedDescription &&
8549
+ _formatString(localVideoSelectedDescription, {
8550
+ cameraName: localVideoCameraCycleButtonProps.selectedCamera.name
8551
+ });
8552
+ return (React__default['default'].createElement(react.Stack, { horizontalAlign: "end" }, showCameraSwitcherInLocalPreview &&
8553
+ (localVideoCameraCycleButtonProps === null || localVideoCameraCycleButtonProps === void 0 ? void 0 : localVideoCameraCycleButtonProps.cameras) !== undefined &&
8554
+ (localVideoCameraCycleButtonProps === null || localVideoCameraCycleButtonProps === void 0 ? void 0 : localVideoCameraCycleButtonProps.selectedCamera) !== undefined &&
8555
+ (localVideoCameraCycleButtonProps === null || localVideoCameraCycleButtonProps === void 0 ? void 0 : localVideoCameraCycleButtonProps.onSelectCamera) !== undefined && (React__default['default'].createElement(LocalVideoCameraCycleButton, { cameras: localVideoCameraCycleButtonProps.cameras, selectedCamera: localVideoCameraCycleButtonProps.selectedCamera, onSelectCamera: localVideoCameraCycleButtonProps.onSelectCamera, label: localVideoCameraSwitcherLabel, ariaDescription: ariaDescription }))));
8556
+ };
8557
+
8558
+ // Copyright (c) Microsoft Corporation.
8559
+ // Licensed under the MIT license.
8560
+ /**
8561
+ * @private
8562
+ */
8563
+ const rootLayoutStyle$1 = {
8564
+ root: { position: 'relative', height: '100%', width: '100%', padding: '0.5rem' }
8565
+ };
8566
+
8567
+ // Copyright (c) Microsoft Corporation.
8568
+ // Licensed under the MIT license.
8569
+ /**
8570
+ * Horizontal Gallery button width in rem
8571
+ */
8572
+ const HORIZONTAL_GALLERY_BUTTON_WIDTH = 1.75;
8573
+ /**
8574
+ * @private
8575
+ */
8576
+ const leftRightButtonStyles = (theme) => {
8577
+ return {
8578
+ background: 'none',
8579
+ padding: 0,
8580
+ height: 'auto',
8581
+ minWidth: `${HORIZONTAL_GALLERY_BUTTON_WIDTH}rem`,
8582
+ maxWidth: `${HORIZONTAL_GALLERY_BUTTON_WIDTH}rem`,
8583
+ border: `1px solid ${theme.palette.neutralLight}`,
8584
+ borderRadius: theme.effects.roundedCorner4
8864
8585
  };
8865
- React__namespace.useEffect(() => {
8866
- clearTimeout(internalState.onModalCloseTimer);
8867
- // Opening the dialog
8868
- if (isOpen) {
8869
- // This must be done after the modal content has rendered
8870
- requestAnimationFrame(() => setTimeout(registerInitialModalPosition, 0));
8871
- setIsModalOpen(true);
8872
- // Add a keyUp handler for all key up events once the dialog is open.
8873
- if (dragOptions) {
8874
- registerForKeyUp();
8875
- }
8876
- internalState.hasBeenOpened = true;
8877
- setIsVisible(true);
8878
- }
8879
- // Closing the dialog
8880
- if (!isOpen && isModalOpen) {
8881
- internalState.onModalCloseTimer = setTimeout(handleModalClose, parseFloat(animationDuration) * 1000);
8882
- setIsVisible(false);
8883
- }
8884
- // eslint-disable-next-line react-hooks/exhaustive-deps -- should only run if isModalOpen or isOpen mutates or if min/max drag bounds are updated.
8885
- }, [isModalOpen, isOpen, minDragPosition, maxDragPosition]);
8886
- reactHooks.useUnmount(() => {
8887
- internalState.events.dispose();
8586
+ };
8587
+ /**
8588
+ * Horizontal Gallery gap size in rem between tiles and buttons
8589
+ */
8590
+ const HORIZONTAL_GALLERY_GAP = 0.5;
8591
+ /**
8592
+ * @private
8593
+ */
8594
+ const rootStyle = {
8595
+ height: '100%',
8596
+ width: '100%',
8597
+ gap: `${HORIZONTAL_GALLERY_GAP}rem`
8598
+ };
8599
+ /**
8600
+ * @private
8601
+ */
8602
+ const childrenContainerStyle = {
8603
+ height: '100%',
8604
+ gap: `${HORIZONTAL_GALLERY_GAP}rem`
8605
+ };
8606
+
8607
+ // Copyright (c) Microsoft Corporation.
8608
+ /**
8609
+ * {@link HorizontalGallery} default children per page
8610
+ */
8611
+ const DEFAULT_CHILDREN_PER_PAGE = 5;
8612
+ /**
8613
+ * Renders a horizontal gallery that parents children horizontally. Handles pagination based on the childrenPerPage prop.
8614
+ * @param props - HorizontalGalleryProps {@link @azure/communication-react#HorizontalGalleryProps}
8615
+ * @returns
8616
+ */
8617
+ const HorizontalGallery = (props) => {
8618
+ var _a, _b;
8619
+ const { children, childrenPerPage = DEFAULT_CHILDREN_PER_PAGE, styles } = props;
8620
+ const ids = useIdentifiers();
8621
+ const [page, setPage] = React.useState(0);
8622
+ const numberOfChildren = React__default['default'].Children.count(children);
8623
+ const lastPage = Math.ceil(numberOfChildren / childrenPerPage) - 1;
8624
+ const paginatedChildren = React.useMemo(() => {
8625
+ return bucketize(React__default['default'].Children.toArray(children), childrenPerPage);
8626
+ }, [children, childrenPerPage]);
8627
+ // If children per page is 0 or less return empty element
8628
+ if (childrenPerPage <= 0) {
8629
+ return React__default['default'].createElement(React__default['default'].Fragment, null);
8630
+ }
8631
+ const firstIndexOfCurrentPage = page * childrenPerPage;
8632
+ const clippedPage = firstIndexOfCurrentPage < numberOfChildren - 1 ? page : lastPage;
8633
+ const childrenOnCurrentPage = paginatedChildren[clippedPage];
8634
+ const showButtons = numberOfChildren > childrenPerPage;
8635
+ const disablePreviousButton = page === 0;
8636
+ const disableNextButton = page === lastPage;
8637
+ return (React__default['default'].createElement(react.Stack, { horizontal: true, className: react.mergeStyles(rootStyle, (_a = props.styles) === null || _a === void 0 ? void 0 : _a.root) },
8638
+ showButtons && (React__default['default'].createElement(HorizontalGalleryNavigationButton, { key: "previous-nav-button", icon: React__default['default'].createElement(react.Icon, { iconName: "HorizontalGalleryLeftButton" }), styles: styles === null || styles === void 0 ? void 0 : styles.previousButton, onClick: () => setPage(Math.max(0, Math.min(lastPage, page - 1))), disabled: disablePreviousButton, identifier: ids.horizontalGalleryLeftNavButton })),
8639
+ React__default['default'].createElement(react.Stack, { horizontal: true, className: react.mergeStyles(childrenContainerStyle, { '> *': (_b = props.styles) === null || _b === void 0 ? void 0 : _b.children }) }, childrenOnCurrentPage),
8640
+ showButtons && (React__default['default'].createElement(HorizontalGalleryNavigationButton, { key: "next-nav-button", icon: React__default['default'].createElement(react.Icon, { iconName: "HorizontalGalleryRightButton" }), styles: styles === null || styles === void 0 ? void 0 : styles.nextButton, onClick: () => setPage(Math.min(lastPage, page + 1)), disabled: disableNextButton, identifier: ids.horizontalGalleryRightNavButton }))));
8641
+ };
8642
+ const HorizontalGalleryNavigationButton = (props) => {
8643
+ const theme = useTheme();
8644
+ return (React__default['default'].createElement(react.DefaultButton, { className: react.mergeStyles(leftRightButtonStyles(theme), props.styles), onClick: props.onClick, disabled: props.disabled, "data-ui-id": props.identifier }, props.icon));
8645
+ };
8646
+ function bucketize(arr, bucketSize) {
8647
+ const bucketArray = [];
8648
+ if (bucketSize <= 0) {
8649
+ return bucketArray;
8650
+ }
8651
+ for (let i = 0; i < arr.length; i += bucketSize) {
8652
+ bucketArray.push(arr.slice(i, i + bucketSize));
8653
+ }
8654
+ return bucketArray;
8655
+ }
8656
+
8657
+ // Copyright (c) Microsoft Corporation.
8658
+ /**
8659
+ * Wrapped HorizontalGallery that adjusts the number of items per page based on the
8660
+ * available width obtained from a ResizeObserver, width per child, gap width, and button width
8661
+ */
8662
+ const ResponsiveHorizontalGallery = (props) => {
8663
+ const { childWidthRem, gapWidthRem, buttonWidthRem = 0 } = props;
8664
+ const containerRef = React.useRef(null);
8665
+ const containerWidth = _useContainerWidth(containerRef);
8666
+ const leftPadding = containerRef.current ? parseFloat(getComputedStyle(containerRef.current).paddingLeft) : 0;
8667
+ const rightPadding = containerRef.current ? parseFloat(getComputedStyle(containerRef.current).paddingRight) : 0;
8668
+ const childrenPerPage = calculateChildrenPerPage({
8669
+ numberOfChildren: React__default['default'].Children.count(props.children),
8670
+ containerWidth: (containerWidth !== null && containerWidth !== void 0 ? containerWidth : 0) - leftPadding - rightPadding,
8671
+ childWidthRem,
8672
+ gapWidthRem,
8673
+ buttonWidthRem
8888
8674
  });
8889
- useComponentRef(props, focusTrapZone);
8890
- const modalContent = (React__namespace.createElement(react.FocusTrapZone, { disabled: true, id: focusTrapZoneId, ref: focusTrapZoneElm, componentRef: focusTrapZone, className: classNames.main, elementToFocusOnDismiss: elementToFocusOnDismiss, isClickableOutsideFocusTrap: isModeless || isClickableOutsideFocusTrap || !isBlocking, ignoreExternalFocusing: ignoreExternalFocusing, forceFocusInsideTrap: forceFocusInsideTrap && !isModeless, firstFocusableSelector: firstFocusableSelector, focusPreviouslyFocusedInnerElement: true, onBlur: internalState.isInKeyboardMoveMode ? handleExitKeyboardMoveMode : undefined },
8891
- dragOptions && internalState.isInKeyboardMoveMode && (React__namespace.createElement("div", { className: classNames.keyboardMoveIconContainer }, dragOptions.keyboardMoveIconProps ? (React__namespace.createElement(react.Icon, Object.assign({}, dragOptions.keyboardMoveIconProps))) : (React__namespace.createElement(react.Icon, { iconName: "move", className: classNames.keyboardMoveIcon })))),
8892
- React__namespace.createElement("div", { ref: allowScrollOnModal, className: classNames.scrollableContent, "data-is-scrollable": true },
8893
- dragOptions && isModalMenuOpen && (React__namespace.createElement(dragOptions.menu, { items: [
8894
- { key: 'move', text: dragOptions.moveMenuItemText, onClick: handleEnterKeyboardMoveMode },
8895
- { key: 'close', text: dragOptions.closeMenuItemText, onClick: handleModalClose }
8896
- ], onDismiss: setModalMenuClose, alignTargetEdge: true, coverTarget: true, directionalHint: react.DirectionalHint.topLeftEdge, directionalHintFixed: true, shouldFocusOnMount: true, target: internalState.scrollableContent })),
8897
- children)));
8898
- return (
8899
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
8900
- (isModalOpen && modalResponsiveMode >= (responsiveMode || react.ResponsiveMode.small) && (React__namespace.createElement(react.Layer, Object.assign({ ref: mergedRef }, mergedLayerProps),
8901
- React__namespace.createElement(react.Popup, { role: isAlertRole ? 'alertdialog' : 'dialog', ariaLabelledBy: titleAriaId, ariaDescribedBy: subtitleAriaId,
8902
- // onDismiss={onDismiss}
8903
- shouldRestoreFocus: !ignoreExternalFocusing, "aria-modal": !isModeless },
8904
- React__namespace.createElement("div", { className: classNames.root, role: !isModeless ? 'document' : undefined },
8905
- !isModeless && (React__namespace.createElement(react.Overlay, Object.assign({ "aria-hidden": true, isDarkThemed: isDarkOverlay, onClick: isBlocking ? undefined : onDismiss, allowTouchBodyScroll: allowTouchBodyScroll }, overlay))),
8906
- dragOptions ? (React__namespace.createElement(DraggableZone, { handleSelector: dragOptions.dragHandleSelector || `#${focusTrapZoneId}`, preventDragSelector: "button", onStart: handleDragStart, onDragChange: handleDrag, onStop: handleDragStop, position: coordinates }, modalContent)) : (modalContent)))))) ||
8907
- null);
8908
- });
8909
- ModalBase.displayName = 'ModalBase';
8910
- const getDraggableZoneClassNames = react.memoizeFunction((className, isDragging) => {
8675
+ return (React__default['default'].createElement("div", { ref: containerRef, className: react.mergeStyles(props.containerStyles) },
8676
+ React__default['default'].createElement(HorizontalGallery, { childrenPerPage: childrenPerPage, styles: props.horizontalGalleryStyles }, props.children)));
8677
+ };
8678
+ /**
8679
+ * Helper function to calculate children per page for HorizontalGallery based on width of container, child, buttons, and
8680
+ * gaps in between
8681
+ */
8682
+ const calculateChildrenPerPage = (args) => {
8683
+ const { numberOfChildren, containerWidth, buttonWidthRem, childWidthRem, gapWidthRem } = args;
8684
+ const childWidth = _convertRemToPx(childWidthRem);
8685
+ const gapWidth = _convertRemToPx(gapWidthRem);
8686
+ /** First check how many children can fit in containerWidth.
8687
+ * __________________________________
8688
+ * | || |
8689
+ * | || |
8690
+ * |________________||________________|
8691
+ * <-----------containerWidth--------->
8692
+ * containerWidth = n * childWidth + (n - 1) * gapWidth. Isolate n and take the floor.
8693
+ */
8694
+ const numberOfChildrenInContainer = Math.floor((containerWidth + gapWidth) / (childWidth + gapWidth));
8695
+ // If all children fit then return numberOfChildrenInContainer
8696
+ if (numberOfChildren <= numberOfChildrenInContainer) {
8697
+ return numberOfChildrenInContainer;
8698
+ }
8699
+ const buttonWidth = _convertRemToPx(buttonWidthRem);
8700
+ /** We know we need to paginate. So we need to subtract the buttonWidth twice and gapWidth twice from
8701
+ * containerWidth to compute childrenSpace
8702
+ * <-----------containerWidth--------->
8703
+ * __________________________________
8704
+ * | || || || |
8705
+ * |<|| || ||>|
8706
+ * |_||_____________||_____________||_|
8707
+ * <-------childrenSpace------>
8708
+ */
8709
+ const childrenSpace = containerWidth - 2 * buttonWidth - 2 * gapWidth;
8710
+ // Now that we have childrenSpace width we can figure out how many children can fit in childrenSpace.
8711
+ // childrenSpace = n * childWidth + (n - 1) * gapWidth. Isolate n and take the floor.
8712
+ return Math.floor((childrenSpace + gapWidth) / (childWidth + gapWidth));
8713
+ };
8714
+
8715
+ // Copyright (c) Microsoft Corporation.
8716
+ /**
8717
+ * Small floating modal width and height in rem for small screen
8718
+ */
8719
+ const SMALL_FLOATING_MODAL_SIZE_PX$1 = { width: 64, height: 88 };
8720
+ /**
8721
+ * Large floating modal width and height in rem for large screen
8722
+ */
8723
+ const LARGE_FLOATING_MODAL_SIZE_PX$1 = { width: 160, height: 120 };
8724
+ /**
8725
+ * @private
8726
+ */
8727
+ const horizontalGalleryContainerStyle = (shouldFloatLocalVideo, isNarrow) => {
8911
8728
  return {
8912
- root: react.mergeStyles(className, isDragging && {
8913
- touchAction: 'none',
8914
- selectors: {
8915
- '& *': {
8916
- userSelect: 'none'
8917
- }
8918
- }
8919
- })
8729
+ minHeight: isNarrow
8730
+ ? `${SMALL_HORIZONTAL_GALLERY_TILE_SIZE_REM.height}rem`
8731
+ : `${LARGE_HORIZONTAL_GALLERY_TILE_SIZE_REM.height}rem`,
8732
+ width: shouldFloatLocalVideo
8733
+ ? isNarrow
8734
+ ? `calc(100% - ${_pxToRem(SMALL_FLOATING_MODAL_SIZE_PX$1.width)})`
8735
+ : `calc(100% - ${_pxToRem(LARGE_FLOATING_MODAL_SIZE_PX$1.width)})`
8736
+ : '100%',
8737
+ paddingRight: '0.5rem'
8920
8738
  };
8921
- });
8922
- const eventMapping = {
8923
- touch: {
8924
- start: 'touchstart',
8925
- move: 'touchmove',
8926
- stop: 'touchend'
8927
- },
8928
- mouse: {
8929
- start: 'mousedown',
8930
- move: 'mousemove',
8931
- stop: 'mouseup'
8932
- }
8933
8739
  };
8934
- class DraggableZone extends React__namespace.Component {
8935
- constructor(props) {
8936
- super(props);
8937
- this._currentEventType = eventMapping.mouse;
8938
- this._events = [];
8939
- this._onMouseDown = (event) => {
8940
- const onMouseDown = React__namespace.Children.only(this.props.children).props.onMouseDown;
8941
- if (onMouseDown) {
8942
- onMouseDown(event);
8943
- }
8944
- this._currentEventType = eventMapping.mouse;
8945
- return this._onDragStart(event);
8946
- };
8947
- this._onMouseUp = (event) => {
8948
- const onMouseUp = React__namespace.Children.only(this.props.children).props.onMouseUp;
8949
- if (onMouseUp) {
8950
- onMouseUp(event);
8951
- }
8952
- this._currentEventType = eventMapping.mouse;
8953
- return this._onDragStop(event);
8954
- };
8955
- this._onTouchStart = (event) => {
8956
- const onTouchStart = React__namespace.Children.only(this.props.children).props.onTouchStart;
8957
- if (onTouchStart) {
8958
- onTouchStart(event);
8959
- }
8960
- this._currentEventType = eventMapping.touch;
8961
- return this._onDragStart(event);
8962
- };
8963
- this._onTouchEnd = (event) => {
8964
- const onTouchEnd = React__namespace.Children.only(this.props.children).props.onTouchEnd;
8965
- if (onTouchEnd) {
8966
- onTouchEnd(event);
8967
- }
8968
- this._currentEventType = eventMapping.touch;
8969
- this._onDragStop(event);
8970
- };
8971
- this._onDragStart = (event) => {
8972
- // Only handle left click for dragging
8973
- if (typeof event.button === 'number' && event.button !== 0) {
8974
- return false;
8975
- }
8976
- // If the target doesn't match the handleSelector OR
8977
- // if the target does match the preventDragSelector, bail out
8978
- if ((this.props.handleSelector && !this._matchesSelector(event.target, this.props.handleSelector)) ||
8979
- (this.props.preventDragSelector &&
8980
- this._matchesSelector(event.target, this.props.preventDragSelector))) {
8981
- return;
8982
- }
8983
- // Remember the touch identifier if this is a touch event so we can
8984
- // distinguish between individual touches in multitouch scenarios
8985
- // by remembering which touch point we were given
8986
- this._touchId = this._getTouchId(event);
8987
- const position = this._getControlPosition(event);
8988
- if (position === undefined) {
8989
- return;
8990
- }
8991
- const dragData = this._createDragDataFromPosition(position);
8992
- this.props.onStart && this.props.onStart(event, dragData);
8993
- this.setState({
8994
- isDragging: true,
8995
- lastPosition: position
8996
- });
8997
- // hook up the appropriate mouse/touch events to the body to ensure
8998
- // smooth dragging
8999
- this._events = [
9000
- react.on(document.body, this._currentEventType.move, this._onDrag, true /* use capture phase */),
9001
- react.on(document.body, this._currentEventType.stop, this._onDragStop, true /* use capture phase */)
9002
- ];
9003
- return;
9004
- };
9005
- this._onDrag = (event) => {
9006
- // Prevent scrolling on mobile devices
9007
- if (event.type === 'touchmove') {
9008
- event.preventDefault();
9009
- }
9010
- const position = this._getControlPosition(event);
9011
- if (!position) {
9012
- return;
9013
- }
9014
- // create the updated drag data from the position data
9015
- const updatedData = this._createUpdatedDragData(this._createDragDataFromPosition(position));
9016
- const updatedPosition = updatedData.position;
9017
- this.props.onDragChange && this.props.onDragChange(event, updatedData);
9018
- this.setState({
9019
- position: updatedPosition,
9020
- lastPosition: position
9021
- });
9022
- };
9023
- this._onDragStop = (event) => {
9024
- if (!this.state.isDragging) {
9025
- return;
9026
- }
9027
- const position = this._getControlPosition(event);
9028
- if (!position) {
9029
- return;
9030
- }
9031
- const baseDragData = this._createDragDataFromPosition(position);
9032
- // Set dragging to false and reset the lastPosition
9033
- this.setState({
9034
- isDragging: false,
9035
- lastPosition: undefined
9036
- });
9037
- this.props.onStop && this.props.onStop(event, baseDragData);
9038
- if (this.props.position) {
9039
- this.setState({
9040
- position: this.props.position
9041
- });
9042
- }
9043
- // Remove event handlers
9044
- this._events.forEach((dispose) => dispose());
9045
- };
9046
- this.state = {
9047
- isDragging: false,
9048
- position: this.props.position || { x: 0, y: 0 },
9049
- lastPosition: undefined
9050
- };
8740
+ /**
8741
+ * @private
8742
+ */
8743
+ const horizontalGalleryStyle = (isNarrow) => {
8744
+ return {
8745
+ children: isNarrow ? SMALL_HORIZONTAL_GALLERY_TILE_STYLE : LARGE_HORIZONTAL_GALLERY_TILE_STYLE
8746
+ };
8747
+ };
8748
+ /**
8749
+ * Small horizontal gallery tile size in rem
8750
+ * @private
8751
+ */
8752
+ const SMALL_HORIZONTAL_GALLERY_TILE_SIZE_REM = { height: 5.5, width: 5.5 };
8753
+ /**
8754
+ * Large horizontal gallery tile size in rem
8755
+ * @private
8756
+ */
8757
+ const LARGE_HORIZONTAL_GALLERY_TILE_SIZE_REM = { height: 7.5, width: 10 };
8758
+ /**
8759
+ * @private
8760
+ */
8761
+ const SMALL_HORIZONTAL_GALLERY_TILE_STYLE = {
8762
+ minHeight: `${SMALL_HORIZONTAL_GALLERY_TILE_SIZE_REM.height}rem`,
8763
+ minWidth: `${SMALL_HORIZONTAL_GALLERY_TILE_SIZE_REM.width}rem`,
8764
+ maxHeight: `${SMALL_HORIZONTAL_GALLERY_TILE_SIZE_REM.height}rem`,
8765
+ maxWidth: `${SMALL_HORIZONTAL_GALLERY_TILE_SIZE_REM.width}rem`
8766
+ };
8767
+ /**
8768
+ * @private
8769
+ */
8770
+ const LARGE_HORIZONTAL_GALLERY_TILE_STYLE = {
8771
+ minHeight: `${LARGE_HORIZONTAL_GALLERY_TILE_SIZE_REM.height}rem`,
8772
+ minWidth: `${LARGE_HORIZONTAL_GALLERY_TILE_SIZE_REM.width}rem`,
8773
+ maxHeight: `${LARGE_HORIZONTAL_GALLERY_TILE_SIZE_REM.height}rem`,
8774
+ maxWidth: `${LARGE_HORIZONTAL_GALLERY_TILE_SIZE_REM.width}rem`
8775
+ };
8776
+
8777
+ // Copyright (c) Microsoft Corporation.
8778
+ /**
8779
+ * A ResponsiveHorizontalGallery styled for the @link{VideoGallery}
8780
+ */
8781
+ const VideoGalleryResponsiveHorizontalGallery = (props) => {
8782
+ const { shouldFloatLocalVideo = false, isNarrow = false, horizontalGalleryElements, styles } = props;
8783
+ const containerStyles = React.useMemo(() => horizontalGalleryContainerStyle(shouldFloatLocalVideo, isNarrow), [shouldFloatLocalVideo, isNarrow]);
8784
+ const galleryStyles = React.useMemo(() => react.concatStyleSets(horizontalGalleryStyle(isNarrow), styles), [isNarrow, styles]);
8785
+ return (React__default['default'].createElement(react.Stack, { styles: { root: { paddingTop: '0.5rem' } } },
8786
+ React__default['default'].createElement(ResponsiveHorizontalGallery, { key: "responsive-horizontal-gallery", containerStyles: containerStyles, horizontalGalleryStyles: galleryStyles, childWidthRem: isNarrow ? SMALL_HORIZONTAL_GALLERY_TILE_SIZE_REM.width : LARGE_HORIZONTAL_GALLERY_TILE_SIZE_REM.width, buttonWidthRem: HORIZONTAL_GALLERY_BUTTON_WIDTH, gapWidthRem: HORIZONTAL_GALLERY_GAP }, horizontalGalleryElements)));
8787
+ };
8788
+
8789
+ /**
8790
+ * Calculates the participants that should be rendered based on the list of dominant
8791
+ * speakers and currently rendered participants in a call.
8792
+ * @param args - SmartDominantSpeakerParticipantsArgs
8793
+ * @returns VideoGalleryRemoteParticipant[] {@link @azure/communication-react#VideoGalleryRemoteParticipant}
8794
+ */
8795
+ const smartDominantSpeakerParticipants = (args) => {
8796
+ const { participants, dominantSpeakers = [], lastVisibleParticipants = [], maxDominantSpeakers } = args;
8797
+ // Don't apply any logic if total number of video streams is less than max dominant speakers.
8798
+ if (participants.length <= maxDominantSpeakers) {
8799
+ return participants;
9051
8800
  }
9052
- componentDidUpdate(prevProps) {
9053
- if (this.props.position && (!prevProps.position || this.props.position !== prevProps.position)) {
9054
- this.setState({ position: this.props.position });
8801
+ const participantsMap = participantsById(participants);
8802
+ // Only use the Max allowed dominant speakers that exist in participants
8803
+ const dominantSpeakerIds = Array.from(new Set(dominantSpeakers).values())
8804
+ .filter((id) => !!participantsMap[id])
8805
+ .slice(0, maxDominantSpeakers);
8806
+ const lastVisibleParticipantIds = lastVisibleParticipants.map((p) => p.userId);
8807
+ const newVisibleParticipantIds = lastVisibleParticipants.map((p) => p.userId).slice(0, maxDominantSpeakers);
8808
+ const newDominantSpeakerIds = dominantSpeakerIds.filter((id) => !newVisibleParticipantIds.includes(id));
8809
+ // Remove participants that are no longer dominant and replace them with new dominant speakers.
8810
+ for (let index = 0; index < maxDominantSpeakers; index++) {
8811
+ const newVisibleParticipantId = newVisibleParticipantIds[index];
8812
+ if (newVisibleParticipantId === undefined || !dominantSpeakerIds.includes(newVisibleParticipantId)) {
8813
+ const replacement = newDominantSpeakerIds.shift();
8814
+ if (!replacement) {
8815
+ break;
8816
+ }
8817
+ newVisibleParticipantIds[index] = replacement;
9055
8818
  }
9056
8819
  }
9057
- componentWillUnmount() {
9058
- this._events.forEach((dispose) => dispose());
9059
- }
9060
- render() {
9061
- const child = React__namespace.Children.only(this.props.children);
9062
- const { props } = child;
9063
- const { position } = this.props;
9064
- const { position: statePosition, isDragging } = this.state;
9065
- let x = statePosition.x;
9066
- let y = statePosition.y;
9067
- if (position && !isDragging) {
9068
- x = position.x;
9069
- y = position.y;
8820
+ const removedVisibleParticipantIds = lastVisibleParticipantIds.filter((p) => !newVisibleParticipantIds.includes(p));
8821
+ removedVisibleParticipantIds.forEach((p) => newVisibleParticipantIds.push(p));
8822
+ const newVisibleParticipantIdSet = new Set(newVisibleParticipantIds);
8823
+ const leftoverParticipants = participants.filter((p) => !newVisibleParticipantIdSet.has(p.userId));
8824
+ leftoverParticipants.forEach((p) => {
8825
+ newVisibleParticipantIds.push(p.userId);
8826
+ });
8827
+ // newVisibleParticipantIds can contain identifiers for participants that are no longer in the call. So we ignore those IDs.
8828
+ const newVisibleParticipants = newVisibleParticipantIds
8829
+ .map((participantId) => participantsMap[participantId])
8830
+ .filter((p) => !!p);
8831
+ return newVisibleParticipants;
8832
+ };
8833
+ const participantsById = (participants) => {
8834
+ const response = {};
8835
+ participants.forEach((p) => (response[p.userId] = p));
8836
+ return response;
8837
+ };
8838
+
8839
+ // Copyright (c) Microsoft Corporation.
8840
+ const DEFAULT_MAX_REMOTE_VIDEOSTREAMS = 4;
8841
+ const DEFAULT_MAX_AUDIO_DOMINANT_SPEAKERS = 6;
8842
+ /**
8843
+ * @private
8844
+ */
8845
+ const useFloatingLocalVideoLayout = (props) => {
8846
+ var _a, _b;
8847
+ const visibleVideoParticipants = React.useRef([]);
8848
+ const visibleAudioParticipants = React.useRef([]);
8849
+ const { remoteParticipants, dominantSpeakers, maxRemoteVideoStreams = DEFAULT_MAX_REMOTE_VIDEOSTREAMS, maxAudioDominantSpeakers = DEFAULT_MAX_AUDIO_DOMINANT_SPEAKERS, isScreenShareActive = false } = props;
8850
+ visibleVideoParticipants.current = smartDominantSpeakerParticipants({
8851
+ participants: (_a = remoteParticipants === null || remoteParticipants === void 0 ? void 0 : remoteParticipants.filter((p) => { var _a; return (_a = p.videoStream) === null || _a === void 0 ? void 0 : _a.isAvailable; })) !== null && _a !== void 0 ? _a : [],
8852
+ dominantSpeakers,
8853
+ lastVisibleParticipants: visibleVideoParticipants.current,
8854
+ maxDominantSpeakers: maxRemoteVideoStreams
8855
+ }).slice(0, maxRemoteVideoStreams);
8856
+ const visibleVideoParticipantsSet = new Set(visibleVideoParticipants.current.map((p) => p.userId));
8857
+ /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
8858
+ const callingParticipants = remoteParticipants.filter((p) => p.state === ('Connecting' ));
8859
+ /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
8860
+ const callingParticipantsSet = new Set(callingParticipants.map((p) => p.userId));
8861
+ visibleAudioParticipants.current = smartDominantSpeakerParticipants({
8862
+ participants: (_b = remoteParticipants === null || remoteParticipants === void 0 ? void 0 : remoteParticipants.filter((p) => !visibleVideoParticipantsSet.has(p.userId) &&
8863
+ /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */ !callingParticipantsSet.has(p.userId))) !== null && _b !== void 0 ? _b : [],
8864
+ dominantSpeakers,
8865
+ lastVisibleParticipants: visibleAudioParticipants.current,
8866
+ maxDominantSpeakers: maxAudioDominantSpeakers
8867
+ });
8868
+ const getGridParticipants = React.useCallback(() => {
8869
+ if (isScreenShareActive) {
8870
+ return [];
9070
8871
  }
9071
- return React__namespace.cloneElement(child, {
9072
- style: Object.assign(Object.assign({}, props.style), { transform: `translate(${x}px, ${y}px)` }),
9073
- className: getDraggableZoneClassNames(props.className, this.state.isDragging).root,
9074
- onMouseDown: this._onMouseDown,
9075
- onMouseUp: this._onMouseUp,
9076
- onTouchStart: this._onTouchStart,
9077
- onTouchEnd: this._onTouchEnd
9078
- });
8872
+ /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
8873
+ return visibleVideoParticipants.current.length > 0
8874
+ ? visibleVideoParticipants.current
8875
+ : visibleAudioParticipants.current.concat(callingParticipants);
8876
+ }, [
8877
+ /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */ callingParticipants,
8878
+ isScreenShareActive
8879
+ ]);
8880
+ const gridParticipants = getGridParticipants();
8881
+ const getHorizontalGalleryRemoteParticipants = React.useCallback(() => {
8882
+ if (isScreenShareActive) {
8883
+ // If screen sharing is active, assign video and audio participants as horizontal gallery participants
8884
+ /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
8885
+ return visibleVideoParticipants.current.concat(visibleAudioParticipants.current.concat(callingParticipants));
8886
+ }
8887
+ else {
8888
+ // If screen sharing is not active, then assign all video tiles as grid tiles.
8889
+ // If there are no video tiles, then assign audio tiles as grid tiles.
8890
+ /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
8891
+ return visibleVideoParticipants.current.length > 0
8892
+ ? visibleAudioParticipants.current.concat(callingParticipants)
8893
+ : [];
8894
+ }
8895
+ }, [
8896
+ /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */ callingParticipants,
8897
+ isScreenShareActive
8898
+ ]);
8899
+ const horizontalGalleryParticipants = getHorizontalGalleryRemoteParticipants();
8900
+ return { gridParticipants, horizontalGalleryParticipants };
8901
+ };
8902
+
8903
+ // Copyright (c) Microsoft Corporation.
8904
+ /**
8905
+ * DefaultLayout displays remote participants, local video component, and screen sharing component in
8906
+ * a grid and horizontal gallery.
8907
+ *
8908
+ * @private
8909
+ */
8910
+ const DefaultLayout = (props) => {
8911
+ const { remoteParticipants = [], dominantSpeakers, localVideoComponent, screenShareComponent, onRenderRemoteParticipant, styles, maxRemoteVideoStreams, parentWidth } = props;
8912
+ const isNarrow = parentWidth ? isNarrowWidth(parentWidth) : false;
8913
+ const floatingLocalVideoLayout = useFloatingLocalVideoLayout({
8914
+ remoteParticipants,
8915
+ dominantSpeakers,
8916
+ maxRemoteVideoStreams,
8917
+ isScreenShareActive: !!screenShareComponent
8918
+ });
8919
+ let activeVideoStreams = 0;
8920
+ const gridTiles = floatingLocalVideoLayout.gridParticipants.map((p) => {
8921
+ var _a, _b;
8922
+ return onRenderRemoteParticipant(p, maxRemoteVideoStreams && maxRemoteVideoStreams >= 0
8923
+ ? ((_a = p.videoStream) === null || _a === void 0 ? void 0 : _a.isAvailable) && activeVideoStreams++ < maxRemoteVideoStreams
8924
+ : (_b = p.videoStream) === null || _b === void 0 ? void 0 : _b.isAvailable);
8925
+ });
8926
+ const horizontalGalleryTiles = floatingLocalVideoLayout.horizontalGalleryParticipants.map((p) => {
8927
+ var _a, _b;
8928
+ return onRenderRemoteParticipant(p, maxRemoteVideoStreams && maxRemoteVideoStreams >= 0
8929
+ ? ((_a = p.videoStream) === null || _a === void 0 ? void 0 : _a.isAvailable) && activeVideoStreams++ < maxRemoteVideoStreams
8930
+ : (_b = p.videoStream) === null || _b === void 0 ? void 0 : _b.isAvailable);
8931
+ });
8932
+ if (localVideoComponent) {
8933
+ gridTiles.push(localVideoComponent);
9079
8934
  }
9080
- /**
9081
- * Get the control position based off the event that fired
9082
- * @param event - The event to get offsets from
9083
- */
9084
- _getControlPosition(event) {
9085
- const touchObj = this._getActiveTouch(event);
9086
- // did we get the right touch?
9087
- if (this._touchId !== undefined && !touchObj) {
9088
- return undefined;
8935
+ return (React__default['default'].createElement(react.Stack, { horizontal: false, styles: rootLayoutStyle$1 },
8936
+ screenShareComponent ? (screenShareComponent) : (React__default['default'].createElement(GridLayout, { key: "grid-layout", styles: styles === null || styles === void 0 ? void 0 : styles.gridLayout }, gridTiles)),
8937
+ horizontalGalleryTiles.length > 0 && (React__default['default'].createElement(VideoGalleryResponsiveHorizontalGallery, { isNarrow: isNarrow, horizontalGalleryElements: horizontalGalleryTiles, styles: styles === null || styles === void 0 ? void 0 : styles.horizontalGallery }))));
8938
+ };
8939
+
8940
+ // Copyright (c) Microsoft Corporation.
8941
+ /**
8942
+ * @private
8943
+ */
8944
+ react.mergeStyles({ position: 'relative', width: '100%', height: '100%' });
8945
+ /**
8946
+ * Small floating modal width and height in rem for small screen
8947
+ */
8948
+ const SMALL_FLOATING_MODAL_SIZE_PX = { width: 64, height: 88 };
8949
+ /**
8950
+ * Large floating modal width and height in rem for large screen
8951
+ */
8952
+ const LARGE_FLOATING_MODAL_SIZE_PX = { width: 160, height: 120 };
8953
+ /**
8954
+ * @private
8955
+ * z-index to ensure that the local video tile is above the video gallery.
8956
+ */
8957
+ const LOCAL_VIDEO_TILE_ZINDEX = 2;
8958
+ /**
8959
+ * @private
8960
+ */
8961
+ const localVideoTileContainerStyle = (theme, isNarrow) => {
8962
+ return Object.assign({ minWidth: isNarrow ? _pxToRem(SMALL_FLOATING_MODAL_SIZE_PX.width) : _pxToRem(LARGE_FLOATING_MODAL_SIZE_PX.width), minHeight: isNarrow ? _pxToRem(SMALL_FLOATING_MODAL_SIZE_PX.height) : _pxToRem(LARGE_FLOATING_MODAL_SIZE_PX.height), position: 'absolute', bottom: _pxToRem(localVideoTileOuterPaddingPX), borderRadius: theme.effects.roundedCorner4, overflow: 'hidden' }, (theme.rtl
8963
+ ? { left: _pxToRem(localVideoTileOuterPaddingPX) }
8964
+ : { right: _pxToRem(localVideoTileOuterPaddingPX) }));
8965
+ };
8966
+ /**
8967
+ * @private
8968
+ */
8969
+ const localVideoTileWithControlsContainerStyle = (theme, isNarrow) => {
8970
+ return react.concatStyleSets(localVideoTileContainerStyle(theme, isNarrow), {
8971
+ root: { boxShadow: theme.effects.elevation8 }
8972
+ });
8973
+ };
8974
+ /**
8975
+ * @private
8976
+ */
8977
+ const floatingLocalVideoModalStyle = (theme, isNarrow) => {
8978
+ return react.concatStyleSets({
8979
+ main: localVideoTileContainerStyle(theme, isNarrow)
8980
+ }, {
8981
+ main: {
8982
+ boxShadow: theme.effects.elevation8,
8983
+ ':focus-within': {
8984
+ boxShadow: theme.effects.elevation16,
8985
+ border: `${_pxToRem(2)} solid ${theme.palette.neutralPrimary}`
8986
+ }
9089
8987
  }
9090
- const eventToGetOffset = touchObj || event;
9091
- return {
9092
- x: eventToGetOffset.clientX,
9093
- y: eventToGetOffset.clientY
9094
- };
8988
+ }, localVideoModalStyles);
8989
+ };
8990
+ /**
8991
+ * Padding equal to the amount the modal should stay inside the bounds of the container.
8992
+ * i.e. if this is 8px, the modal should always be at least 8px inside the container at all times on all sides.
8993
+ * @private
8994
+ */
8995
+ const localVideoTileOuterPaddingPX = 8;
8996
+ /**
8997
+ * @private
8998
+ */
8999
+ const floatingLocalVideoTileStyle = {
9000
+ root: {
9001
+ position: 'absolute',
9002
+ zIndex: LOCAL_VIDEO_TILE_ZINDEX,
9003
+ height: '100%',
9004
+ width: '100%'
9095
9005
  }
9096
- /**
9097
- * Get the active touch point that we have saved from the event's TouchList
9098
- * @param event - The event used to get the TouchList for the active touch point
9099
- */
9100
- _getActiveTouch(event) {
9101
- return ((event.targetTouches && this._findTouchInTouchList(event.targetTouches)) ||
9102
- (event.changedTouches && this._findTouchInTouchList(event.changedTouches)));
9006
+ };
9007
+ /**
9008
+ * Styles for the local video tile modal when it is focused, will cause keyboard move icon to appear over video
9009
+ * @private
9010
+ */
9011
+ const localVideoModalStyles = {
9012
+ keyboardMoveIconContainer: {
9013
+ zIndex: LOCAL_VIDEO_TILE_ZINDEX + 1 // zIndex to set the keyboard movement Icon above the other layers in the video tile.
9103
9014
  }
9104
- /**
9105
- * Get the initial touch identifier associated with the given event
9106
- * @param event - The event that contains the TouchList
9107
- */
9108
- _getTouchId(event) {
9109
- const touch = (event.targetTouches && event.targetTouches[0]) || (event.changedTouches && event.changedTouches[0]);
9110
- if (touch) {
9111
- return touch.identifier;
9015
+ };
9016
+
9017
+ // Copyright (c) Microsoft Corporation.
9018
+ const animationDuration = react.AnimationVariables.durationValue2;
9019
+ const ZERO = { x: 0, y: 0 };
9020
+ const DEFAULT_PROPS = {
9021
+ isOpen: false,
9022
+ isDarkOverlay: true,
9023
+ className: '',
9024
+ containerClassName: '',
9025
+ enableAriaHiddenSiblings: true
9026
+ };
9027
+ const getModalClassNames = react.classNamesFunction();
9028
+ const getMoveDelta = (ev) => {
9029
+ let delta = 10;
9030
+ if (ev.shiftKey) {
9031
+ if (!ev.ctrlKey) {
9032
+ delta = 50;
9112
9033
  }
9113
- return;
9114
9034
  }
9115
- /**
9116
- * Returns if an element (or any of the element's parents) match the given selector
9117
- */
9118
- _matchesSelector(element, selector) {
9119
- if (!element || element === document.body) {
9120
- return false;
9035
+ else if (ev.ctrlKey) {
9036
+ delta = 1;
9037
+ }
9038
+ return delta;
9039
+ };
9040
+ const useComponentRef = (props, focusTrapZone) => {
9041
+ React__namespace.useImperativeHandle(props.componentRef, () => ({
9042
+ focus() {
9043
+ if (focusTrapZone.current) {
9044
+ focusTrapZone.current.focus();
9045
+ }
9121
9046
  }
9122
- // eslint-disable-next-line @typescript-eslint/ban-types
9123
- const matchesSelectorFn = element.matches || element.webkitMatchesSelector || element.msMatchesSelector; /* for IE */
9124
- if (!matchesSelectorFn) {
9125
- return false;
9047
+ }), [focusTrapZone]);
9048
+ };
9049
+ const ModalBase = React__namespace.forwardRef((propsWithoutDefaults, ref) => {
9050
+ const props = react.getPropsWithDefaults(DEFAULT_PROPS, propsWithoutDefaults);
9051
+ const { allowTouchBodyScroll, className, children, containerClassName, scrollableContentClassName, elementToFocusOnDismiss, firstFocusableSelector, forceFocusInsideTrap, ignoreExternalFocusing, isBlocking, isAlert, isClickableOutsideFocusTrap, isDarkOverlay, onDismiss, layerProps, overlay, isOpen, titleAriaId, styles, subtitleAriaId, theme, topOffsetFixed, responsiveMode, onLayerDidMount, isModeless, dragOptions, onDismissed, minDragPosition, maxDragPosition } = props;
9052
+ const rootRef = React__namespace.useRef(null);
9053
+ const focusTrapZone = React__namespace.useRef(null);
9054
+ const focusTrapZoneElm = React__namespace.useRef(null);
9055
+ const mergedRef = reactHooks.useMergedRefs(rootRef, ref);
9056
+ const modalResponsiveMode = react.useResponsiveMode(mergedRef);
9057
+ const focusTrapZoneId = reactHooks.useId('ModalFocusTrapZone');
9058
+ const win = reactWindowProvider.useWindow();
9059
+ const { setTimeout, clearTimeout } = reactHooks.useSetTimeout();
9060
+ const [isModalOpen, setIsModalOpen] = React__namespace.useState(isOpen);
9061
+ const [isVisible, setIsVisible] = React__namespace.useState(isOpen);
9062
+ const [coordinates, setCoordinates] = React__namespace.useState(ZERO);
9063
+ const [modalRectangleTop, setModalRectangleTop] = React__namespace.useState();
9064
+ const [isModalMenuOpen, { toggle: toggleModalMenuOpen, setFalse: setModalMenuClose }] = reactHooks.useBoolean(false);
9065
+ const internalState = reactHooks.useConst(() => ({
9066
+ onModalCloseTimer: 0,
9067
+ allowTouchBodyScroll,
9068
+ scrollableContent: null,
9069
+ lastSetCoordinates: ZERO,
9070
+ events: new react.EventGroup({})
9071
+ }));
9072
+ const { keepInBounds } = dragOptions || {};
9073
+ const isAlertRole = isAlert !== null && isAlert !== void 0 ? isAlert : (isBlocking && !isModeless);
9074
+ const layerClassName = layerProps === undefined ? '' : layerProps.className;
9075
+ const classNames = getModalClassNames(styles, {
9076
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
9077
+ theme: theme,
9078
+ className,
9079
+ containerClassName,
9080
+ scrollableContentClassName,
9081
+ isOpen,
9082
+ isVisible,
9083
+ hasBeenOpened: internalState.hasBeenOpened,
9084
+ modalRectangleTop,
9085
+ topOffsetFixed,
9086
+ isModeless,
9087
+ layerClassName,
9088
+ windowInnerHeight: win === null || win === void 0 ? void 0 : win.innerHeight,
9089
+ isDefaultDragHandle: dragOptions && !dragOptions.dragHandleSelector
9090
+ });
9091
+ const mergedLayerProps = Object.assign(Object.assign({ eventBubblingEnabled: false }, layerProps), { onLayerDidMount: layerProps && layerProps.onLayerDidMount ? layerProps.onLayerDidMount : onLayerDidMount, insertFirst: isModeless, className: classNames.layer });
9092
+ // Allow the user to scroll within the modal but not on the body
9093
+ const allowScrollOnModal = React__namespace.useCallback((elt) => {
9094
+ if (elt) {
9095
+ if (internalState.allowTouchBodyScroll) {
9096
+ react.allowOverscrollOnElement(elt, internalState.events);
9097
+ }
9098
+ else {
9099
+ react.allowScrollOnElement(elt, internalState.events);
9100
+ }
9126
9101
  }
9127
- return matchesSelectorFn.call(element, selector) || this._matchesSelector(element.parentElement, selector);
9128
- }
9129
- /**
9130
- * Attempts to find the Touch that matches the identifier we stored in dragStart
9131
- * @param touchList The TouchList to look for the stored identifier from dragStart
9132
- */
9133
- _findTouchInTouchList(touchList) {
9134
- if (this._touchId === undefined) {
9135
- return;
9102
+ else {
9103
+ internalState.events.off(internalState.scrollableContent);
9136
9104
  }
9137
- for (let i = 0; i < touchList.length; i++) {
9138
- if (touchList[i].identifier === this._touchId) {
9139
- return touchList[i];
9105
+ internalState.scrollableContent = elt;
9106
+ }, [internalState]);
9107
+ const registerInitialModalPosition = () => {
9108
+ const dialogMain = focusTrapZoneElm.current;
9109
+ const modalRectangle = dialogMain === null || dialogMain === void 0 ? void 0 : dialogMain.getBoundingClientRect();
9110
+ if (modalRectangle) {
9111
+ if (topOffsetFixed) {
9112
+ setModalRectangleTop(modalRectangle.top);
9113
+ }
9114
+ if (keepInBounds) {
9115
+ // x/y are unavailable in IE, so use the equivalent left/top
9116
+ internalState.minPosition = minDragPosition !== null && minDragPosition !== void 0 ? minDragPosition : { x: -modalRectangle.left, y: -modalRectangle.top };
9117
+ internalState.maxPosition = maxDragPosition !== null && maxDragPosition !== void 0 ? maxDragPosition : { x: modalRectangle.left, y: modalRectangle.top };
9118
+ // Make sure the initial co-ordinates are within clamp bounds.
9119
+ setCoordinates({
9120
+ x: getClampedAxis('x', coordinates.x),
9121
+ y: getClampedAxis('y', coordinates.y)
9122
+ });
9140
9123
  }
9141
9124
  }
9142
- return undefined;
9143
- }
9125
+ };
9144
9126
  /**
9145
- * Create DragData based off of the last known position and the new position passed in
9146
- * @param position The new position as part of the drag
9127
+ * Clamps an axis to a specified min and max position.
9128
+ *
9129
+ * @param axis A string that represents the axis (x/y).
9130
+ * @param position The position on the axis.
9147
9131
  */
9148
- _createDragDataFromPosition(position) {
9149
- const { lastPosition } = this.state;
9150
- // If we have no lastPosition, use the given position
9151
- // for last position
9152
- if (lastPosition === undefined) {
9153
- return {
9154
- delta: { x: 0, y: 0 },
9155
- lastPosition: position,
9156
- position
9157
- };
9132
+ const getClampedAxis = React__namespace.useCallback((axis, position) => {
9133
+ const { minPosition, maxPosition } = internalState;
9134
+ if (keepInBounds && minPosition && maxPosition) {
9135
+ position = Math.max(minPosition[axis], position);
9136
+ position = Math.min(maxPosition[axis], position);
9158
9137
  }
9159
- return {
9160
- delta: {
9161
- x: position.x - lastPosition.x,
9162
- y: position.y - lastPosition.y
9163
- },
9164
- lastPosition,
9165
- position
9138
+ return position;
9139
+ }, [keepInBounds, internalState]);
9140
+ const handleModalClose = () => {
9141
+ var _a;
9142
+ internalState.lastSetCoordinates = ZERO;
9143
+ setModalMenuClose();
9144
+ internalState.isInKeyboardMoveMode = false;
9145
+ setIsModalOpen(false);
9146
+ setCoordinates(ZERO);
9147
+ (_a = internalState.disposeOnKeyUp) === null || _a === void 0 ? void 0 : _a.call(internalState);
9148
+ onDismissed === null || onDismissed === void 0 ? void 0 : onDismissed();
9149
+ };
9150
+ const handleDragStart = React__namespace.useCallback(() => {
9151
+ setModalMenuClose();
9152
+ internalState.isInKeyboardMoveMode = false;
9153
+ }, [internalState, setModalMenuClose]);
9154
+ const handleDrag = React__namespace.useCallback(
9155
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9156
+ (ev, dragData) => {
9157
+ setCoordinates((prevValue) => ({
9158
+ x: getClampedAxis('x', prevValue.x + dragData.delta.x),
9159
+ y: getClampedAxis('y', prevValue.y + dragData.delta.y)
9160
+ }));
9161
+ }, [getClampedAxis]);
9162
+ const handleDragStop = React__namespace.useCallback(() => {
9163
+ if (focusTrapZone.current) {
9164
+ focusTrapZone.current.focus();
9165
+ }
9166
+ }, []);
9167
+ const handleEnterKeyboardMoveMode = () => {
9168
+ // We need a global handleKeyDown event when we are in the move mode so that we can
9169
+ // handle the key presses and the components inside the modal do not get the events
9170
+ const handleKeyDown = (ev) => {
9171
+ if (ev.altKey && ev.ctrlKey && ev.keyCode === react.KeyCodes.space) {
9172
+ // CTRL + ALT + SPACE is handled during keyUp
9173
+ ev.preventDefault();
9174
+ ev.stopPropagation();
9175
+ return;
9176
+ }
9177
+ const newLocal = ev.altKey || ev.keyCode === react.KeyCodes.escape;
9178
+ if (isModalMenuOpen && newLocal) {
9179
+ setModalMenuClose();
9180
+ }
9181
+ if (internalState.isInKeyboardMoveMode && (ev.keyCode === react.KeyCodes.escape || ev.keyCode === react.KeyCodes.enter)) {
9182
+ internalState.isInKeyboardMoveMode = false;
9183
+ ev.preventDefault();
9184
+ ev.stopPropagation();
9185
+ }
9186
+ if (internalState.isInKeyboardMoveMode) {
9187
+ let handledEvent = true;
9188
+ const delta = getMoveDelta(ev);
9189
+ switch (ev.keyCode) {
9190
+ /* eslint-disable no-fallthrough */
9191
+ case react.KeyCodes.escape:
9192
+ setCoordinates(internalState.lastSetCoordinates);
9193
+ case react.KeyCodes.enter: {
9194
+ // TODO: determine if fallthrough was intentional
9195
+ /* eslint-enable no-fallthrough */
9196
+ internalState.lastSetCoordinates = ZERO;
9197
+ // setIsInKeyboardMoveMode(false);
9198
+ break;
9199
+ }
9200
+ case react.KeyCodes.up: {
9201
+ setCoordinates((prevValue) => ({ x: prevValue.x, y: getClampedAxis('y', prevValue.y - delta) }));
9202
+ break;
9203
+ }
9204
+ case react.KeyCodes.down: {
9205
+ setCoordinates((prevValue) => ({ x: prevValue.x, y: getClampedAxis('y', prevValue.y + delta) }));
9206
+ break;
9207
+ }
9208
+ case react.KeyCodes.left: {
9209
+ setCoordinates((prevValue) => ({ x: getClampedAxis('x', prevValue.x - delta), y: prevValue.y }));
9210
+ break;
9211
+ }
9212
+ case react.KeyCodes.right: {
9213
+ setCoordinates((prevValue) => ({ x: getClampedAxis('x', prevValue.x + delta), y: prevValue.y }));
9214
+ break;
9215
+ }
9216
+ default: {
9217
+ handledEvent = false;
9218
+ }
9219
+ }
9220
+ if (handledEvent) {
9221
+ ev.preventDefault();
9222
+ ev.stopPropagation();
9223
+ }
9224
+ }
9166
9225
  };
9167
- }
9168
- /**
9169
- * Creates an updated DragData based off the current position and given baseDragData
9170
- * @param baseDragData The base DragData (from _createDragDataFromPosition) used to calculate the updated positions
9171
- */
9172
- _createUpdatedDragData(baseDragData) {
9173
- const { position } = this.state;
9174
- return {
9175
- position: {
9176
- x: position.x + baseDragData.delta.x,
9177
- y: position.y + baseDragData.delta.y
9178
- },
9179
- delta: baseDragData.delta,
9180
- lastPosition: position
9226
+ internalState.lastSetCoordinates = coordinates;
9227
+ setModalMenuClose();
9228
+ internalState.isInKeyboardMoveMode = true;
9229
+ internalState.events.on(win, 'keydown', handleKeyDown, true /* useCapture */);
9230
+ internalState.disposeOnKeyDown = () => {
9231
+ internalState.events.off(win, 'keydown', handleKeyDown, true /* useCapture */);
9232
+ internalState.disposeOnKeyDown = undefined;
9181
9233
  };
9182
- }
9183
- }
9184
- const globalClassNames = {
9185
- root: 'ms-Modal',
9186
- main: 'ms-Dialog-main',
9187
- scrollableContent: 'ms-Modal-scrollableContent',
9188
- isOpen: 'is-open',
9189
- layer: 'ms-Modal-Layer'
9190
- };
9191
- const getStyles = (props) => {
9192
- const { className, containerClassName, scrollableContentClassName, isOpen, isVisible, hasBeenOpened, modalRectangleTop, theme, topOffsetFixed, isModeless, layerClassName, isDefaultDragHandle, windowInnerHeight } = props;
9193
- const { palette, effects, fonts } = theme;
9194
- const classNames = react.getGlobalClassNames(globalClassNames, theme);
9195
- return {
9196
- root: [
9197
- classNames.root,
9198
- fonts.medium,
9199
- {
9200
- backgroundColor: 'transparent',
9201
- position: isModeless ? 'absolute' : 'fixed',
9202
- height: '100%',
9203
- width: '100%',
9204
- display: 'flex',
9205
- alignItems: 'center',
9206
- justifyContent: 'center',
9207
- opacity: 0,
9208
- pointerEvents: 'none',
9209
- transition: `opacity ${animationDuration}`
9210
- },
9211
- topOffsetFixed &&
9212
- typeof modalRectangleTop === 'number' &&
9213
- hasBeenOpened && {
9214
- alignItems: 'flex-start'
9215
- },
9216
- isOpen && classNames.isOpen,
9217
- isVisible && {
9218
- opacity: 1,
9219
- pointerEvents: 'auto'
9220
- },
9221
- className
9222
- ],
9223
- main: [
9224
- classNames.main,
9225
- {
9226
- boxShadow: effects.elevation64,
9227
- borderRadius: effects.roundedCorner2,
9228
- backgroundColor: palette.white,
9229
- boxSizing: 'border-box',
9230
- position: 'relative',
9231
- textAlign: 'left',
9232
- outline: '3px solid transparent',
9233
- maxHeight: 'calc(100% - 32px)',
9234
- maxWidth: 'calc(100% - 32px)',
9235
- minHeight: '176px',
9236
- minWidth: '288px',
9237
- overflowY: 'auto',
9238
- zIndex: isModeless ? react.ZIndexes.Layer : undefined
9239
- },
9240
- topOffsetFixed &&
9241
- typeof modalRectangleTop === 'number' &&
9242
- hasBeenOpened && {
9243
- top: modalRectangleTop
9244
- },
9245
- isDefaultDragHandle && {
9246
- cursor: 'move'
9247
- },
9248
- containerClassName
9249
- ],
9250
- scrollableContent: [
9251
- classNames.scrollableContent,
9252
- {
9253
- overflowY: 'auto',
9254
- flexGrow: 1,
9255
- maxHeight: '100vh',
9256
- selectors: {
9257
- ['@supports (-webkit-overflow-scrolling: touch)']: {
9258
- maxHeight: windowInnerHeight
9259
- }
9234
+ };
9235
+ const handleExitKeyboardMoveMode = () => {
9236
+ var _a;
9237
+ internalState.lastSetCoordinates = ZERO;
9238
+ internalState.isInKeyboardMoveMode = false;
9239
+ (_a = internalState.disposeOnKeyDown) === null || _a === void 0 ? void 0 : _a.call(internalState);
9240
+ };
9241
+ const registerForKeyUp = () => {
9242
+ const handleKeyUp = (ev) => {
9243
+ // Needs to handle the CTRL + ALT + SPACE key during keyup due to FireFox bug:
9244
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1220143
9245
+ if (ev.altKey && ev.ctrlKey && ev.keyCode === react.KeyCodes.space) {
9246
+ if (react.elementContains(internalState.scrollableContent, ev.target)) {
9247
+ toggleModalMenuOpen();
9248
+ ev.preventDefault();
9249
+ ev.stopPropagation();
9260
9250
  }
9261
- },
9262
- scrollableContentClassName
9263
- ],
9264
- layer: isModeless && [
9265
- layerClassName,
9266
- classNames.layer,
9267
- {
9268
- position: 'static',
9269
- width: 'unset',
9270
- height: 'unset'
9271
9251
  }
9272
- ],
9273
- keyboardMoveIconContainer: {
9274
- position: 'absolute',
9275
- display: 'flex',
9276
- justifyContent: 'center',
9277
- width: '100%',
9278
- padding: '3px 0px'
9279
- },
9280
- keyboardMoveIcon: {
9281
- fontSize: fonts.xLargePlus.fontSize,
9282
- width: '24px'
9252
+ };
9253
+ if (!internalState.disposeOnKeyUp) {
9254
+ internalState.events.on(win, 'keyup', handleKeyUp, true /* useCapture */);
9255
+ internalState.disposeOnKeyUp = () => {
9256
+ internalState.events.off(win, 'keyup', handleKeyUp, true /* useCapture */);
9257
+ internalState.disposeOnKeyUp = undefined;
9258
+ };
9283
9259
  }
9284
9260
  };
9285
- };
9286
- /** @internal */
9287
- const _ModalClone = react.styled(ModalBase, getStyles, undefined, {
9288
- scope: 'Modal',
9289
- fields: ['theme', 'styles', 'enableAriaHiddenSiblings']
9290
- });
9291
- _ModalClone.displayName = 'Modal';
9292
-
9293
- // Copyright (c) Microsoft Corporation.
9294
- /**
9295
- * local video tile camera cycle button - for use on mobile screens only.
9296
- * @internal
9297
- */
9298
- const LocalVideoCameraCycleButton = (props) => {
9299
- const { cameras, selectedCamera, onSelectCamera, label, ariaDescription } = props;
9300
- const theme = react.useTheme();
9301
- return (React__default['default'].createElement(react.IconButton, { "data-ui-id": 'local-camera-switcher-button', styles: localVideoCameraCycleButtonStyles(theme), iconProps: { iconName: 'LocalCameraSwitch' }, ariaLabel: label, ariaDescription: ariaDescription, "aria-live": 'polite', onClick: () => {
9302
- if (cameras && cameras.length > 1 && selectedCamera !== undefined) {
9303
- const index = cameras.findIndex((camera) => selectedCamera.id === camera.id);
9304
- const newCamera = cameras[(index + 1) % cameras.length];
9305
- if (onSelectCamera !== undefined) {
9306
- onSelectCamera(newCamera);
9307
- }
9261
+ React__namespace.useEffect(() => {
9262
+ clearTimeout(internalState.onModalCloseTimer);
9263
+ // Opening the dialog
9264
+ if (isOpen) {
9265
+ // This must be done after the modal content has rendered
9266
+ requestAnimationFrame(() => setTimeout(registerInitialModalPosition, 0));
9267
+ setIsModalOpen(true);
9268
+ // Add a keyUp handler for all key up events once the dialog is open.
9269
+ if (dragOptions) {
9270
+ registerForKeyUp();
9308
9271
  }
9309
- } }));
9310
- };
9311
-
9312
- // Copyright (c) Microsoft Corporation.
9313
- /**
9314
- * A memoized version of VideoTile for rendering local participant.
9315
- *
9316
- * @internal
9317
- */
9318
- const _LocalVideoTile = React__default['default'].memo((props) => {
9319
- const { isAvailable, isMuted, onCreateLocalStreamView, onDisposeLocalStreamView, localVideoViewOptions, renderElement, userId, showLabel, displayName, initialsName, onRenderAvatar, showMuteIndicator, styles, showCameraSwitcherInLocalPreview, localVideoCameraCycleButtonProps, localVideoCameraSwitcherLabel, localVideoSelectedDescription } = props;
9320
- const localVideoStreamProps = React.useMemo(() => ({
9321
- isMirrored: localVideoViewOptions === null || localVideoViewOptions === void 0 ? void 0 : localVideoViewOptions.isMirrored,
9322
- isStreamAvailable: isAvailable,
9323
- onCreateLocalStreamView,
9324
- onDisposeLocalStreamView,
9325
- renderElementExists: !!renderElement,
9326
- scalingMode: localVideoViewOptions === null || localVideoViewOptions === void 0 ? void 0 : localVideoViewOptions.scalingMode
9327
- }), [
9328
- isAvailable,
9329
- localVideoViewOptions === null || localVideoViewOptions === void 0 ? void 0 : localVideoViewOptions.isMirrored,
9330
- localVideoViewOptions === null || localVideoViewOptions === void 0 ? void 0 : localVideoViewOptions.scalingMode,
9331
- onCreateLocalStreamView,
9332
- onDisposeLocalStreamView,
9333
- renderElement
9334
- ]);
9335
- // Handle creating, destroying and updating the video stream as necessary
9336
- useLocalVideoStreamLifecycleMaintainer(localVideoStreamProps);
9337
- const renderVideoStreamElement = React.useMemo(() => {
9338
- // Checking if renderElement is well defined or not as calling SDK has a number of video streams limitation which
9339
- // implies that, after their threshold, all streams have no child (blank video)
9340
- if (!renderElement || !renderElement.childElementCount) {
9341
- // Returning `undefined` results in the placeholder with avatar being shown
9342
- return undefined;
9272
+ internalState.hasBeenOpened = true;
9273
+ setIsVisible(true);
9343
9274
  }
9344
- return (React__default['default'].createElement(React__default['default'].Fragment, null,
9345
- React__default['default'].createElement(FloatingLocalCameraCycleButton, { showCameraSwitcherInLocalPreview: showCameraSwitcherInLocalPreview !== null && showCameraSwitcherInLocalPreview !== void 0 ? showCameraSwitcherInLocalPreview : false, localVideoCameraCycleButtonProps: localVideoCameraCycleButtonProps, localVideoCameraSwitcherLabel: localVideoCameraSwitcherLabel, localVideoSelectedDescription: localVideoSelectedDescription }),
9346
- React__default['default'].createElement(StreamMedia, { videoStreamElement: renderElement, isMirrored: true })));
9347
- }, [
9348
- localVideoCameraCycleButtonProps,
9349
- localVideoCameraSwitcherLabel,
9350
- localVideoSelectedDescription,
9351
- renderElement,
9352
- showCameraSwitcherInLocalPreview
9353
- ]);
9354
- return (React__default['default'].createElement(VideoTile, { key: userId !== null && userId !== void 0 ? userId : 'local-video-tile', userId: userId, renderElement: renderVideoStreamElement, showLabel: showLabel, displayName: displayName, initialsName: initialsName, styles: styles, onRenderPlaceholder: onRenderAvatar, isMuted: isMuted, showMuteIndicator: showMuteIndicator, personaMinSize: props.personaMinSize }));
9275
+ // Closing the dialog
9276
+ if (!isOpen && isModalOpen) {
9277
+ internalState.onModalCloseTimer = setTimeout(handleModalClose, parseFloat(animationDuration) * 1000);
9278
+ setIsVisible(false);
9279
+ }
9280
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- should only run if isModalOpen or isOpen mutates or if min/max drag bounds are updated.
9281
+ }, [isModalOpen, isOpen, minDragPosition, maxDragPosition]);
9282
+ reactHooks.useUnmount(() => {
9283
+ internalState.events.dispose();
9284
+ });
9285
+ useComponentRef(props, focusTrapZone);
9286
+ const modalContent = (React__namespace.createElement(react.FocusTrapZone, { disabled: true, id: focusTrapZoneId, ref: focusTrapZoneElm, componentRef: focusTrapZone, className: classNames.main, elementToFocusOnDismiss: elementToFocusOnDismiss, isClickableOutsideFocusTrap: isModeless || isClickableOutsideFocusTrap || !isBlocking, ignoreExternalFocusing: ignoreExternalFocusing, forceFocusInsideTrap: forceFocusInsideTrap && !isModeless, firstFocusableSelector: firstFocusableSelector, focusPreviouslyFocusedInnerElement: true, onBlur: internalState.isInKeyboardMoveMode ? handleExitKeyboardMoveMode : undefined },
9287
+ dragOptions && internalState.isInKeyboardMoveMode && (React__namespace.createElement("div", { className: classNames.keyboardMoveIconContainer }, dragOptions.keyboardMoveIconProps ? (React__namespace.createElement(react.Icon, Object.assign({}, dragOptions.keyboardMoveIconProps))) : (React__namespace.createElement(react.Icon, { iconName: "move", className: classNames.keyboardMoveIcon })))),
9288
+ React__namespace.createElement("div", { ref: allowScrollOnModal, className: classNames.scrollableContent, "data-is-scrollable": true },
9289
+ dragOptions && isModalMenuOpen && (React__namespace.createElement(dragOptions.menu, { items: [
9290
+ { key: 'move', text: dragOptions.moveMenuItemText, onClick: handleEnterKeyboardMoveMode },
9291
+ { key: 'close', text: dragOptions.closeMenuItemText, onClick: handleModalClose }
9292
+ ], onDismiss: setModalMenuClose, alignTargetEdge: true, coverTarget: true, directionalHint: react.DirectionalHint.topLeftEdge, directionalHintFixed: true, shouldFocusOnMount: true, target: internalState.scrollableContent })),
9293
+ children)));
9294
+ return (
9295
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
9296
+ (isModalOpen && modalResponsiveMode >= (responsiveMode || react.ResponsiveMode.small) && (React__namespace.createElement(react.Layer, Object.assign({ ref: mergedRef }, mergedLayerProps),
9297
+ React__namespace.createElement(react.Popup, { role: isAlertRole ? 'alertdialog' : 'dialog', ariaLabelledBy: titleAriaId, ariaDescribedBy: subtitleAriaId,
9298
+ // onDismiss={onDismiss}
9299
+ shouldRestoreFocus: !ignoreExternalFocusing, "aria-modal": !isModeless },
9300
+ React__namespace.createElement("div", { className: classNames.root, role: !isModeless ? 'document' : undefined },
9301
+ !isModeless && (React__namespace.createElement(react.Overlay, Object.assign({ "aria-hidden": true, isDarkThemed: isDarkOverlay, onClick: isBlocking ? undefined : onDismiss, allowTouchBodyScroll: allowTouchBodyScroll }, overlay))),
9302
+ dragOptions ? (React__namespace.createElement(DraggableZone, { handleSelector: dragOptions.dragHandleSelector || `#${focusTrapZoneId}`, preventDragSelector: "button", onStart: handleDragStart, onDragChange: handleDrag, onStop: handleDragStop, position: coordinates }, modalContent)) : (modalContent)))))) ||
9303
+ null);
9355
9304
  });
9356
- const FloatingLocalCameraCycleButton = (props) => {
9357
- const { showCameraSwitcherInLocalPreview, localVideoCameraCycleButtonProps, localVideoCameraSwitcherLabel, localVideoSelectedDescription } = props;
9358
- const ariaDescription = (localVideoCameraCycleButtonProps === null || localVideoCameraCycleButtonProps === void 0 ? void 0 : localVideoCameraCycleButtonProps.selectedCamera) &&
9359
- localVideoSelectedDescription &&
9360
- _formatString(localVideoSelectedDescription, {
9361
- cameraName: localVideoCameraCycleButtonProps.selectedCamera.name
9362
- });
9363
- return (React__default['default'].createElement(react.Stack, { horizontalAlign: "end" }, showCameraSwitcherInLocalPreview &&
9364
- (localVideoCameraCycleButtonProps === null || localVideoCameraCycleButtonProps === void 0 ? void 0 : localVideoCameraCycleButtonProps.cameras) !== undefined &&
9365
- (localVideoCameraCycleButtonProps === null || localVideoCameraCycleButtonProps === void 0 ? void 0 : localVideoCameraCycleButtonProps.selectedCamera) !== undefined &&
9366
- (localVideoCameraCycleButtonProps === null || localVideoCameraCycleButtonProps === void 0 ? void 0 : localVideoCameraCycleButtonProps.onSelectCamera) !== undefined && (React__default['default'].createElement(LocalVideoCameraCycleButton, { cameras: localVideoCameraCycleButtonProps.cameras, selectedCamera: localVideoCameraCycleButtonProps.selectedCamera, onSelectCamera: localVideoCameraCycleButtonProps.onSelectCamera, label: localVideoCameraSwitcherLabel, ariaDescription: ariaDescription }))));
9367
- };
9368
-
9369
- // Copyright (c) Microsoft Corporation.
9370
- // Licensed under the MIT license.
9371
- /**
9372
- * @private
9373
- */
9374
- const rootLayoutStyle = {
9375
- root: { position: 'relative', height: '100%', width: '100%', padding: '0.5rem' }
9376
- };
9377
-
9378
- // Copyright (c) Microsoft Corporation.
9379
- // Licensed under the MIT license.
9380
- /**
9381
- * Horizontal Gallery button width in rem
9382
- */
9383
- const HORIZONTAL_GALLERY_BUTTON_WIDTH = 1.75;
9384
- /**
9385
- * @private
9386
- */
9387
- const leftRightButtonStyles = (theme) => {
9305
+ ModalBase.displayName = 'ModalBase';
9306
+ const getDraggableZoneClassNames = react.memoizeFunction((className, isDragging) => {
9388
9307
  return {
9389
- background: 'none',
9390
- padding: 0,
9391
- height: 'auto',
9392
- minWidth: `${HORIZONTAL_GALLERY_BUTTON_WIDTH}rem`,
9393
- maxWidth: `${HORIZONTAL_GALLERY_BUTTON_WIDTH}rem`,
9394
- border: `1px solid ${theme.palette.neutralLight}`,
9395
- borderRadius: theme.effects.roundedCorner4
9308
+ root: react.mergeStyles(className, isDragging && {
9309
+ touchAction: 'none',
9310
+ selectors: {
9311
+ '& *': {
9312
+ userSelect: 'none'
9313
+ }
9314
+ }
9315
+ })
9396
9316
  };
9317
+ });
9318
+ const eventMapping = {
9319
+ touch: {
9320
+ start: 'touchstart',
9321
+ move: 'touchmove',
9322
+ stop: 'touchend'
9323
+ },
9324
+ mouse: {
9325
+ start: 'mousedown',
9326
+ move: 'mousemove',
9327
+ stop: 'mouseup'
9328
+ }
9397
9329
  };
9398
- /**
9399
- * Horizontal Gallery gap size in rem between tiles and buttons
9400
- */
9401
- const HORIZONTAL_GALLERY_GAP = 0.5;
9402
- /**
9403
- * @private
9404
- */
9405
- const rootStyle = {
9406
- height: '100%',
9407
- width: '100%',
9408
- gap: `${HORIZONTAL_GALLERY_GAP}rem`
9409
- };
9410
- /**
9411
- * @private
9412
- */
9413
- const childrenContainerStyle = {
9414
- height: '100%',
9415
- gap: `${HORIZONTAL_GALLERY_GAP}rem`
9416
- };
9417
-
9418
- // Copyright (c) Microsoft Corporation.
9419
- /**
9420
- * {@link HorizontalGallery} default children per page
9421
- */
9422
- const DEFAULT_CHILDREN_PER_PAGE = 5;
9423
- /**
9424
- * Renders a horizontal gallery that parents children horizontally. Handles pagination based on the childrenPerPage prop.
9425
- * @param props - HorizontalGalleryProps {@link @azure/communication-react#HorizontalGalleryProps}
9426
- * @returns
9427
- */
9428
- const HorizontalGallery = (props) => {
9429
- var _a, _b;
9430
- const { children, childrenPerPage = DEFAULT_CHILDREN_PER_PAGE, styles } = props;
9431
- const ids = useIdentifiers();
9432
- const [page, setPage] = React.useState(0);
9433
- const numberOfChildren = React__default['default'].Children.count(children);
9434
- const lastPage = Math.ceil(numberOfChildren / childrenPerPage) - 1;
9435
- const paginatedChildren = React.useMemo(() => {
9436
- return bucketize(React__default['default'].Children.toArray(children), childrenPerPage);
9437
- }, [children, childrenPerPage]);
9438
- // If children per page is 0 or less return empty element
9439
- if (childrenPerPage <= 0) {
9440
- return React__default['default'].createElement(React__default['default'].Fragment, null);
9330
+ class DraggableZone extends React__namespace.Component {
9331
+ constructor(props) {
9332
+ super(props);
9333
+ this._currentEventType = eventMapping.mouse;
9334
+ this._events = [];
9335
+ this._onMouseDown = (event) => {
9336
+ const onMouseDown = React__namespace.Children.only(this.props.children).props.onMouseDown;
9337
+ if (onMouseDown) {
9338
+ onMouseDown(event);
9339
+ }
9340
+ this._currentEventType = eventMapping.mouse;
9341
+ return this._onDragStart(event);
9342
+ };
9343
+ this._onMouseUp = (event) => {
9344
+ const onMouseUp = React__namespace.Children.only(this.props.children).props.onMouseUp;
9345
+ if (onMouseUp) {
9346
+ onMouseUp(event);
9347
+ }
9348
+ this._currentEventType = eventMapping.mouse;
9349
+ return this._onDragStop(event);
9350
+ };
9351
+ this._onTouchStart = (event) => {
9352
+ const onTouchStart = React__namespace.Children.only(this.props.children).props.onTouchStart;
9353
+ if (onTouchStart) {
9354
+ onTouchStart(event);
9355
+ }
9356
+ this._currentEventType = eventMapping.touch;
9357
+ return this._onDragStart(event);
9358
+ };
9359
+ this._onTouchEnd = (event) => {
9360
+ const onTouchEnd = React__namespace.Children.only(this.props.children).props.onTouchEnd;
9361
+ if (onTouchEnd) {
9362
+ onTouchEnd(event);
9363
+ }
9364
+ this._currentEventType = eventMapping.touch;
9365
+ this._onDragStop(event);
9366
+ };
9367
+ this._onDragStart = (event) => {
9368
+ // Only handle left click for dragging
9369
+ if (typeof event.button === 'number' && event.button !== 0) {
9370
+ return false;
9371
+ }
9372
+ // If the target doesn't match the handleSelector OR
9373
+ // if the target does match the preventDragSelector, bail out
9374
+ if ((this.props.handleSelector && !this._matchesSelector(event.target, this.props.handleSelector)) ||
9375
+ (this.props.preventDragSelector &&
9376
+ this._matchesSelector(event.target, this.props.preventDragSelector))) {
9377
+ return;
9378
+ }
9379
+ // Remember the touch identifier if this is a touch event so we can
9380
+ // distinguish between individual touches in multitouch scenarios
9381
+ // by remembering which touch point we were given
9382
+ this._touchId = this._getTouchId(event);
9383
+ const position = this._getControlPosition(event);
9384
+ if (position === undefined) {
9385
+ return;
9386
+ }
9387
+ const dragData = this._createDragDataFromPosition(position);
9388
+ this.props.onStart && this.props.onStart(event, dragData);
9389
+ this.setState({
9390
+ isDragging: true,
9391
+ lastPosition: position
9392
+ });
9393
+ // hook up the appropriate mouse/touch events to the body to ensure
9394
+ // smooth dragging
9395
+ this._events = [
9396
+ react.on(document.body, this._currentEventType.move, this._onDrag, true /* use capture phase */),
9397
+ react.on(document.body, this._currentEventType.stop, this._onDragStop, true /* use capture phase */)
9398
+ ];
9399
+ return;
9400
+ };
9401
+ this._onDrag = (event) => {
9402
+ // Prevent scrolling on mobile devices
9403
+ if (event.type === 'touchmove') {
9404
+ event.preventDefault();
9405
+ }
9406
+ const position = this._getControlPosition(event);
9407
+ if (!position) {
9408
+ return;
9409
+ }
9410
+ // create the updated drag data from the position data
9411
+ const updatedData = this._createUpdatedDragData(this._createDragDataFromPosition(position));
9412
+ const updatedPosition = updatedData.position;
9413
+ this.props.onDragChange && this.props.onDragChange(event, updatedData);
9414
+ this.setState({
9415
+ position: updatedPosition,
9416
+ lastPosition: position
9417
+ });
9418
+ };
9419
+ this._onDragStop = (event) => {
9420
+ if (!this.state.isDragging) {
9421
+ return;
9422
+ }
9423
+ const position = this._getControlPosition(event);
9424
+ if (!position) {
9425
+ return;
9426
+ }
9427
+ const baseDragData = this._createDragDataFromPosition(position);
9428
+ // Set dragging to false and reset the lastPosition
9429
+ this.setState({
9430
+ isDragging: false,
9431
+ lastPosition: undefined
9432
+ });
9433
+ this.props.onStop && this.props.onStop(event, baseDragData);
9434
+ if (this.props.position) {
9435
+ this.setState({
9436
+ position: this.props.position
9437
+ });
9438
+ }
9439
+ // Remove event handlers
9440
+ this._events.forEach((dispose) => dispose());
9441
+ };
9442
+ this.state = {
9443
+ isDragging: false,
9444
+ position: this.props.position || { x: 0, y: 0 },
9445
+ lastPosition: undefined
9446
+ };
9447
+ }
9448
+ componentDidUpdate(prevProps) {
9449
+ if (this.props.position && (!prevProps.position || this.props.position !== prevProps.position)) {
9450
+ this.setState({ position: this.props.position });
9451
+ }
9452
+ }
9453
+ componentWillUnmount() {
9454
+ this._events.forEach((dispose) => dispose());
9455
+ }
9456
+ render() {
9457
+ const child = React__namespace.Children.only(this.props.children);
9458
+ const { props } = child;
9459
+ const { position } = this.props;
9460
+ const { position: statePosition, isDragging } = this.state;
9461
+ let x = statePosition.x;
9462
+ let y = statePosition.y;
9463
+ if (position && !isDragging) {
9464
+ x = position.x;
9465
+ y = position.y;
9466
+ }
9467
+ return React__namespace.cloneElement(child, {
9468
+ style: Object.assign(Object.assign({}, props.style), { transform: `translate(${x}px, ${y}px)` }),
9469
+ className: getDraggableZoneClassNames(props.className, this.state.isDragging).root,
9470
+ onMouseDown: this._onMouseDown,
9471
+ onMouseUp: this._onMouseUp,
9472
+ onTouchStart: this._onTouchStart,
9473
+ onTouchEnd: this._onTouchEnd
9474
+ });
9441
9475
  }
9442
- const firstIndexOfCurrentPage = page * childrenPerPage;
9443
- const clippedPage = firstIndexOfCurrentPage < numberOfChildren - 1 ? page : lastPage;
9444
- const childrenOnCurrentPage = paginatedChildren[clippedPage];
9445
- const showButtons = numberOfChildren > childrenPerPage;
9446
- const disablePreviousButton = page === 0;
9447
- const disableNextButton = page === lastPage;
9448
- return (React__default['default'].createElement(react.Stack, { horizontal: true, className: react.mergeStyles(rootStyle, (_a = props.styles) === null || _a === void 0 ? void 0 : _a.root) },
9449
- showButtons && (React__default['default'].createElement(HorizontalGalleryNavigationButton, { key: "previous-nav-button", icon: React__default['default'].createElement(react.Icon, { iconName: "HorizontalGalleryLeftButton" }), styles: styles === null || styles === void 0 ? void 0 : styles.previousButton, onClick: () => setPage(Math.max(0, Math.min(lastPage, page - 1))), disabled: disablePreviousButton, identifier: ids.horizontalGalleryLeftNavButton })),
9450
- React__default['default'].createElement(react.Stack, { horizontal: true, className: react.mergeStyles(childrenContainerStyle, { '> *': (_b = props.styles) === null || _b === void 0 ? void 0 : _b.children }) }, childrenOnCurrentPage),
9451
- showButtons && (React__default['default'].createElement(HorizontalGalleryNavigationButton, { key: "next-nav-button", icon: React__default['default'].createElement(react.Icon, { iconName: "HorizontalGalleryRightButton" }), styles: styles === null || styles === void 0 ? void 0 : styles.nextButton, onClick: () => setPage(Math.min(lastPage, page + 1)), disabled: disableNextButton, identifier: ids.horizontalGalleryRightNavButton }))));
9452
- };
9453
- const HorizontalGalleryNavigationButton = (props) => {
9454
- const theme = useTheme();
9455
- return (React__default['default'].createElement(react.DefaultButton, { className: react.mergeStyles(leftRightButtonStyles(theme), props.styles), onClick: props.onClick, disabled: props.disabled, "data-ui-id": props.identifier }, props.icon));
9456
- };
9457
- function bucketize(arr, bucketSize) {
9458
- const bucketArray = [];
9459
- if (bucketSize <= 0) {
9460
- return bucketArray;
9476
+ /**
9477
+ * Get the control position based off the event that fired
9478
+ * @param event - The event to get offsets from
9479
+ */
9480
+ _getControlPosition(event) {
9481
+ const touchObj = this._getActiveTouch(event);
9482
+ // did we get the right touch?
9483
+ if (this._touchId !== undefined && !touchObj) {
9484
+ return undefined;
9485
+ }
9486
+ const eventToGetOffset = touchObj || event;
9487
+ return {
9488
+ x: eventToGetOffset.clientX,
9489
+ y: eventToGetOffset.clientY
9490
+ };
9461
9491
  }
9462
- for (let i = 0; i < arr.length; i += bucketSize) {
9463
- bucketArray.push(arr.slice(i, i + bucketSize));
9492
+ /**
9493
+ * Get the active touch point that we have saved from the event's TouchList
9494
+ * @param event - The event used to get the TouchList for the active touch point
9495
+ */
9496
+ _getActiveTouch(event) {
9497
+ return ((event.targetTouches && this._findTouchInTouchList(event.targetTouches)) ||
9498
+ (event.changedTouches && this._findTouchInTouchList(event.changedTouches)));
9464
9499
  }
9465
- return bucketArray;
9466
- }
9467
-
9468
- // Copyright (c) Microsoft Corporation.
9469
- /**
9470
- * Wrapped HorizontalGallery that adjusts the number of items per page based on the
9471
- * available width obtained from a ResizeObserver, width per child, gap width, and button width
9472
- */
9473
- const ResponsiveHorizontalGallery = (props) => {
9474
- const { childWidthRem, gapWidthRem, buttonWidthRem = 0 } = props;
9475
- const containerRef = React.useRef(null);
9476
- const containerWidth = _useContainerWidth(containerRef);
9477
- const leftPadding = containerRef.current ? parseFloat(getComputedStyle(containerRef.current).paddingLeft) : 0;
9478
- const rightPadding = containerRef.current ? parseFloat(getComputedStyle(containerRef.current).paddingRight) : 0;
9479
- const childrenPerPage = calculateChildrenPerPage({
9480
- numberOfChildren: React__default['default'].Children.count(props.children),
9481
- containerWidth: (containerWidth !== null && containerWidth !== void 0 ? containerWidth : 0) - leftPadding - rightPadding,
9482
- childWidthRem,
9483
- gapWidthRem,
9484
- buttonWidthRem
9485
- });
9486
- return (React__default['default'].createElement("div", { ref: containerRef, className: react.mergeStyles(props.containerStyles) },
9487
- React__default['default'].createElement(HorizontalGallery, { childrenPerPage: childrenPerPage, styles: props.horizontalGalleryStyles }, props.children)));
9488
- };
9489
- /**
9490
- * Helper function to calculate children per page for HorizontalGallery based on width of container, child, buttons, and
9491
- * gaps in between
9492
- */
9493
- const calculateChildrenPerPage = (args) => {
9494
- const { numberOfChildren, containerWidth, buttonWidthRem, childWidthRem, gapWidthRem } = args;
9495
- const childWidth = _convertRemToPx(childWidthRem);
9496
- const gapWidth = _convertRemToPx(gapWidthRem);
9497
- /** First check how many children can fit in containerWidth.
9498
- * __________________________________
9499
- * | || |
9500
- * | || |
9501
- * |________________||________________|
9502
- * <-----------containerWidth--------->
9503
- * containerWidth = n * childWidth + (n - 1) * gapWidth. Isolate n and take the floor.
9500
+ /**
9501
+ * Get the initial touch identifier associated with the given event
9502
+ * @param event - The event that contains the TouchList
9504
9503
  */
9505
- const numberOfChildrenInContainer = Math.floor((containerWidth + gapWidth) / (childWidth + gapWidth));
9506
- // If all children fit then return numberOfChildrenInContainer
9507
- if (numberOfChildren <= numberOfChildrenInContainer) {
9508
- return numberOfChildrenInContainer;
9504
+ _getTouchId(event) {
9505
+ const touch = (event.targetTouches && event.targetTouches[0]) || (event.changedTouches && event.changedTouches[0]);
9506
+ if (touch) {
9507
+ return touch.identifier;
9508
+ }
9509
+ return;
9509
9510
  }
9510
- const buttonWidth = _convertRemToPx(buttonWidthRem);
9511
- /** We know we need to paginate. So we need to subtract the buttonWidth twice and gapWidth twice from
9512
- * containerWidth to compute childrenSpace
9513
- * <-----------containerWidth--------->
9514
- * __________________________________
9515
- * | || || || |
9516
- * |<|| || ||>|
9517
- * |_||_____________||_____________||_|
9518
- * <-------childrenSpace------>
9511
+ /**
9512
+ * Returns if an element (or any of the element's parents) match the given selector
9519
9513
  */
9520
- const childrenSpace = containerWidth - 2 * buttonWidth - 2 * gapWidth;
9521
- // Now that we have childrenSpace width we can figure out how many children can fit in childrenSpace.
9522
- // childrenSpace = n * childWidth + (n - 1) * gapWidth. Isolate n and take the floor.
9523
- return Math.floor((childrenSpace + gapWidth) / (childWidth + gapWidth));
9514
+ _matchesSelector(element, selector) {
9515
+ if (!element || element === document.body) {
9516
+ return false;
9517
+ }
9518
+ // eslint-disable-next-line @typescript-eslint/ban-types
9519
+ const matchesSelectorFn = element.matches || element.webkitMatchesSelector || element.msMatchesSelector; /* for IE */
9520
+ if (!matchesSelectorFn) {
9521
+ return false;
9522
+ }
9523
+ return matchesSelectorFn.call(element, selector) || this._matchesSelector(element.parentElement, selector);
9524
+ }
9525
+ /**
9526
+ * Attempts to find the Touch that matches the identifier we stored in dragStart
9527
+ * @param touchList The TouchList to look for the stored identifier from dragStart
9528
+ */
9529
+ _findTouchInTouchList(touchList) {
9530
+ if (this._touchId === undefined) {
9531
+ return;
9532
+ }
9533
+ for (let i = 0; i < touchList.length; i++) {
9534
+ if (touchList[i].identifier === this._touchId) {
9535
+ return touchList[i];
9536
+ }
9537
+ }
9538
+ return undefined;
9539
+ }
9540
+ /**
9541
+ * Create DragData based off of the last known position and the new position passed in
9542
+ * @param position The new position as part of the drag
9543
+ */
9544
+ _createDragDataFromPosition(position) {
9545
+ const { lastPosition } = this.state;
9546
+ // If we have no lastPosition, use the given position
9547
+ // for last position
9548
+ if (lastPosition === undefined) {
9549
+ return {
9550
+ delta: { x: 0, y: 0 },
9551
+ lastPosition: position,
9552
+ position
9553
+ };
9554
+ }
9555
+ return {
9556
+ delta: {
9557
+ x: position.x - lastPosition.x,
9558
+ y: position.y - lastPosition.y
9559
+ },
9560
+ lastPosition,
9561
+ position
9562
+ };
9563
+ }
9564
+ /**
9565
+ * Creates an updated DragData based off the current position and given baseDragData
9566
+ * @param baseDragData The base DragData (from _createDragDataFromPosition) used to calculate the updated positions
9567
+ */
9568
+ _createUpdatedDragData(baseDragData) {
9569
+ const { position } = this.state;
9570
+ return {
9571
+ position: {
9572
+ x: position.x + baseDragData.delta.x,
9573
+ y: position.y + baseDragData.delta.y
9574
+ },
9575
+ delta: baseDragData.delta,
9576
+ lastPosition: position
9577
+ };
9578
+ }
9579
+ }
9580
+ const globalClassNames = {
9581
+ root: 'ms-Modal',
9582
+ main: 'ms-Dialog-main',
9583
+ scrollableContent: 'ms-Modal-scrollableContent',
9584
+ isOpen: 'is-open',
9585
+ layer: 'ms-Modal-Layer'
9586
+ };
9587
+ const getStyles = (props) => {
9588
+ const { className, containerClassName, scrollableContentClassName, isOpen, isVisible, hasBeenOpened, modalRectangleTop, theme, topOffsetFixed, isModeless, layerClassName, isDefaultDragHandle, windowInnerHeight } = props;
9589
+ const { palette, effects, fonts } = theme;
9590
+ const classNames = react.getGlobalClassNames(globalClassNames, theme);
9591
+ return {
9592
+ root: [
9593
+ classNames.root,
9594
+ fonts.medium,
9595
+ {
9596
+ backgroundColor: 'transparent',
9597
+ position: isModeless ? 'absolute' : 'fixed',
9598
+ height: '100%',
9599
+ width: '100%',
9600
+ display: 'flex',
9601
+ alignItems: 'center',
9602
+ justifyContent: 'center',
9603
+ opacity: 0,
9604
+ pointerEvents: 'none',
9605
+ transition: `opacity ${animationDuration}`
9606
+ },
9607
+ topOffsetFixed &&
9608
+ typeof modalRectangleTop === 'number' &&
9609
+ hasBeenOpened && {
9610
+ alignItems: 'flex-start'
9611
+ },
9612
+ isOpen && classNames.isOpen,
9613
+ isVisible && {
9614
+ opacity: 1,
9615
+ pointerEvents: 'auto'
9616
+ },
9617
+ className
9618
+ ],
9619
+ main: [
9620
+ classNames.main,
9621
+ {
9622
+ boxShadow: effects.elevation64,
9623
+ borderRadius: effects.roundedCorner2,
9624
+ backgroundColor: palette.white,
9625
+ boxSizing: 'border-box',
9626
+ position: 'relative',
9627
+ textAlign: 'left',
9628
+ outline: '3px solid transparent',
9629
+ maxHeight: 'calc(100% - 32px)',
9630
+ maxWidth: 'calc(100% - 32px)',
9631
+ minHeight: '176px',
9632
+ minWidth: '288px',
9633
+ overflowY: 'auto',
9634
+ zIndex: isModeless ? react.ZIndexes.Layer : undefined
9635
+ },
9636
+ topOffsetFixed &&
9637
+ typeof modalRectangleTop === 'number' &&
9638
+ hasBeenOpened && {
9639
+ top: modalRectangleTop
9640
+ },
9641
+ isDefaultDragHandle && {
9642
+ cursor: 'move'
9643
+ },
9644
+ containerClassName
9645
+ ],
9646
+ scrollableContent: [
9647
+ classNames.scrollableContent,
9648
+ {
9649
+ overflowY: 'auto',
9650
+ flexGrow: 1,
9651
+ maxHeight: '100vh',
9652
+ selectors: {
9653
+ ['@supports (-webkit-overflow-scrolling: touch)']: {
9654
+ maxHeight: windowInnerHeight
9655
+ }
9656
+ }
9657
+ },
9658
+ scrollableContentClassName
9659
+ ],
9660
+ layer: isModeless && [
9661
+ layerClassName,
9662
+ classNames.layer,
9663
+ {
9664
+ position: 'static',
9665
+ width: 'unset',
9666
+ height: 'unset'
9667
+ }
9668
+ ],
9669
+ keyboardMoveIconContainer: {
9670
+ position: 'absolute',
9671
+ display: 'flex',
9672
+ justifyContent: 'center',
9673
+ width: '100%',
9674
+ padding: '3px 0px'
9675
+ },
9676
+ keyboardMoveIcon: {
9677
+ fontSize: fonts.xLargePlus.fontSize,
9678
+ width: '24px'
9679
+ }
9680
+ };
9524
9681
  };
9682
+ /** @internal */
9683
+ const _ModalClone = react.styled(ModalBase, getStyles, undefined, {
9684
+ scope: 'Modal',
9685
+ fields: ['theme', 'styles', 'enableAriaHiddenSiblings']
9686
+ });
9687
+ _ModalClone.displayName = 'Modal';
9525
9688
 
9526
9689
  // Copyright (c) Microsoft Corporation.
9527
- /**
9528
- * Small floating modal width and height in rem for small screen
9529
- */
9530
- const SMALL_FLOATING_MODAL_SIZE_PX = { width: 64, height: 88 };
9531
- /**
9532
- * Large floating modal width and height in rem for large screen
9533
- */
9534
- const LARGE_FLOATING_MODAL_SIZE_PX = { width: 160, height: 120 };
9535
- /**
9536
- * @private
9537
- */
9538
- const horizontalGalleryContainerStyle = (shouldFloatLocalVideo, isNarrow) => {
9539
- return {
9540
- minHeight: isNarrow
9541
- ? `${SMALL_HORIZONTAL_GALLERY_TILE_SIZE_REM.height}rem`
9542
- : `${LARGE_HORIZONTAL_GALLERY_TILE_SIZE_REM.height}rem`,
9543
- width: shouldFloatLocalVideo
9544
- ? isNarrow
9545
- ? `calc(100% - ${_pxToRem(SMALL_FLOATING_MODAL_SIZE_PX.width)})`
9546
- : `calc(100% - ${_pxToRem(LARGE_FLOATING_MODAL_SIZE_PX.width)})`
9547
- : '100%',
9548
- paddingRight: '0.5rem'
9549
- };
9690
+ const DRAG_OPTIONS$1 = {
9691
+ moveMenuItemText: 'Move',
9692
+ closeMenuItemText: 'Close',
9693
+ menu: react.ContextualMenu,
9694
+ keepInBounds: true
9550
9695
  };
9696
+ // Manually override the max position used to keep the modal in the bounds of its container.
9697
+ // This is a workaround for: https://github.com/microsoft/fluentui/issues/20122
9698
+ // Because our modal starts in the bottom right corner, we can say that this is the max (i.e. rightmost and bottomost)
9699
+ // position the modal can be dragged to.
9700
+ const modalMaxDragPosition = { x: localVideoTileOuterPaddingPX, y: localVideoTileOuterPaddingPX };
9551
9701
  /**
9552
9702
  * @private
9553
9703
  */
9554
- const horizontalGalleryStyle = (isNarrow) => {
9555
- return {
9556
- children: isNarrow ? SMALL_HORIZONTAL_GALLERY_TILE_STYLE : LARGE_HORIZONTAL_GALLERY_TILE_STYLE
9557
- };
9704
+ const FloatingLocalVideo = (props) => {
9705
+ const { localVideoComponent, layerHostId, isNarrow, parentWidth, parentHeight } = props;
9706
+ const theme = useTheme();
9707
+ const modalWidth = isNarrow ? SMALL_FLOATING_MODAL_SIZE_PX.width : LARGE_FLOATING_MODAL_SIZE_PX.width;
9708
+ const modalHeight = isNarrow ? SMALL_FLOATING_MODAL_SIZE_PX.height : LARGE_FLOATING_MODAL_SIZE_PX.height;
9709
+ // The minimum drag position is the top left of the video gallery. i.e. the modal (PiP) should not be able
9710
+ // to be dragged offscreen and these are the top and left bounds of that calculation.
9711
+ const modalMinDragPosition = React.useMemo(() => parentWidth && parentHeight
9712
+ ? {
9713
+ // We use -parentWidth/Height because our modal is positioned to start in the bottom right,
9714
+ // hence (0,0) is the bottom right of the video gallery.
9715
+ x: -parentWidth + modalWidth + localVideoTileOuterPaddingPX,
9716
+ y: -parentHeight + modalHeight + localVideoTileOuterPaddingPX
9717
+ }
9718
+ : undefined, [parentHeight, parentWidth, modalHeight, modalWidth]);
9719
+ const modalStyles = React.useMemo(() => floatingLocalVideoModalStyle(theme, isNarrow), [theme, isNarrow]);
9720
+ const layerProps = React.useMemo(() => ({ hostId: layerHostId }), [layerHostId]);
9721
+ return (React__default['default'].createElement(_ModalClone, { isOpen: true, isModeless: true, dragOptions: DRAG_OPTIONS$1, styles: modalStyles, layerProps: layerProps, maxDragPosition: modalMaxDragPosition, minDragPosition: modalMinDragPosition }, localVideoComponent));
9558
9722
  };
9559
- /**
9560
- * Small horizontal gallery tile size in rem
9561
- * @private
9562
- */
9563
- const SMALL_HORIZONTAL_GALLERY_TILE_SIZE_REM = { height: 5.5, width: 5.5 };
9564
- /**
9565
- * Large horizontal gallery tile size in rem
9566
- * @private
9567
- */
9568
- const LARGE_HORIZONTAL_GALLERY_TILE_SIZE_REM = { height: 7.5, width: 10 };
9723
+
9724
+ // Copyright (c) Microsoft Corporation.
9725
+ // Licensed under the MIT license.
9569
9726
  /**
9570
9727
  * @private
9571
9728
  */
9572
- const SMALL_HORIZONTAL_GALLERY_TILE_STYLE = {
9573
- minHeight: `${SMALL_HORIZONTAL_GALLERY_TILE_SIZE_REM.height}rem`,
9574
- minWidth: `${SMALL_HORIZONTAL_GALLERY_TILE_SIZE_REM.width}rem`,
9575
- maxHeight: `${SMALL_HORIZONTAL_GALLERY_TILE_SIZE_REM.height}rem`,
9576
- maxWidth: `${SMALL_HORIZONTAL_GALLERY_TILE_SIZE_REM.width}rem`
9729
+ const rootLayoutStyle = {
9730
+ root: { position: 'relative', height: '100%', width: '100%' }
9577
9731
  };
9578
9732
  /**
9579
9733
  * @private
9580
9734
  */
9581
- const LARGE_HORIZONTAL_GALLERY_TILE_STYLE = {
9582
- minHeight: `${LARGE_HORIZONTAL_GALLERY_TILE_SIZE_REM.height}rem`,
9583
- minWidth: `${LARGE_HORIZONTAL_GALLERY_TILE_SIZE_REM.width}rem`,
9584
- maxHeight: `${LARGE_HORIZONTAL_GALLERY_TILE_SIZE_REM.height}rem`,
9585
- maxWidth: `${LARGE_HORIZONTAL_GALLERY_TILE_SIZE_REM.width}rem`
9586
- };
9587
-
9588
- // Copyright (c) Microsoft Corporation.
9589
- /**
9590
- * A ResponsiveHorizontalGallery styled for the @link{VideoGallery}
9591
- */
9592
- const VideoGalleryResponsiveHorizontalGallery = (props) => {
9593
- const { shouldFloatLocalVideo = false, isNarrow = false, horizontalGalleryElements, styles } = props;
9594
- const containerStyles = React.useMemo(() => horizontalGalleryContainerStyle(shouldFloatLocalVideo, isNarrow), [shouldFloatLocalVideo, isNarrow]);
9595
- const galleryStyles = React.useMemo(() => react.concatStyleSets(horizontalGalleryStyle(isNarrow), styles), [isNarrow, styles]);
9596
- return (React__default['default'].createElement(react.Stack, { styles: { root: { paddingTop: '0.5rem' } } },
9597
- React__default['default'].createElement(ResponsiveHorizontalGallery, { key: "responsive-horizontal-gallery", containerStyles: containerStyles, horizontalGalleryStyles: galleryStyles, childWidthRem: isNarrow ? SMALL_HORIZONTAL_GALLERY_TILE_SIZE_REM.width : LARGE_HORIZONTAL_GALLERY_TILE_SIZE_REM.width, buttonWidthRem: HORIZONTAL_GALLERY_BUTTON_WIDTH, gapWidthRem: HORIZONTAL_GALLERY_GAP }, horizontalGalleryElements)));
9735
+ const innerLayoutStyle = {
9736
+ root: { position: 'relative', height: '100%', width: '100%', padding: '0.5rem' }
9598
9737
  };
9599
-
9600
- // Copyright (c) Microsoft Corporation.
9601
- const DEFAULT_MAX_REMOTE_VIDEOSTREAMS = 4;
9602
- const DEFAULT_MAX_AUDIO_DOMINANT_SPEAKERS = 6;
9603
9738
  /**
9604
9739
  * @private
9605
9740
  */
9606
- const useFloatingLocalVideoLayout = (props) => {
9607
- var _a, _b;
9608
- const visibleVideoParticipants = React.useRef([]);
9609
- const visibleAudioParticipants = React.useRef([]);
9610
- const { remoteParticipants, dominantSpeakers, maxRemoteVideoStreams = DEFAULT_MAX_REMOTE_VIDEOSTREAMS, maxAudioDominantSpeakers = DEFAULT_MAX_AUDIO_DOMINANT_SPEAKERS, isScreenShareActive = false } = props;
9611
- visibleVideoParticipants.current = smartDominantSpeakerParticipants({
9612
- participants: (_a = remoteParticipants === null || remoteParticipants === void 0 ? void 0 : remoteParticipants.filter((p) => { var _a; return (_a = p.videoStream) === null || _a === void 0 ? void 0 : _a.isAvailable; })) !== null && _a !== void 0 ? _a : [],
9613
- dominantSpeakers,
9614
- lastVisibleParticipants: visibleVideoParticipants.current,
9615
- maxDominantSpeakers: maxRemoteVideoStreams
9616
- }).slice(0, maxRemoteVideoStreams);
9617
- const visibleVideoParticipantsSet = new Set(visibleVideoParticipants.current.map((p) => p.userId));
9618
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
9619
- const callingParticipants = remoteParticipants.filter((p) => p.state === ('Connecting' ));
9620
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
9621
- const callingParticipantsSet = new Set(callingParticipants.map((p) => p.userId));
9622
- visibleAudioParticipants.current = smartDominantSpeakerParticipants({
9623
- participants: (_b = remoteParticipants === null || remoteParticipants === void 0 ? void 0 : remoteParticipants.filter((p) => !visibleVideoParticipantsSet.has(p.userId) &&
9624
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */ !callingParticipantsSet.has(p.userId))) !== null && _b !== void 0 ? _b : [],
9625
- dominantSpeakers,
9626
- lastVisibleParticipants: visibleAudioParticipants.current,
9627
- maxDominantSpeakers: maxAudioDominantSpeakers
9628
- });
9629
- const getGridParticipants = React.useCallback(() => {
9630
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
9631
- return visibleVideoParticipants.current.length > 0
9632
- ? visibleVideoParticipants.current
9633
- : visibleAudioParticipants.current.concat(callingParticipants);
9634
- }, [
9635
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */ callingParticipants
9636
- ]);
9637
- const gridParticipants = getGridParticipants();
9638
- const getHorizontalGalleryRemoteParticipants = React.useCallback(() => {
9639
- if (isScreenShareActive) {
9640
- // If screen sharing is active, assign video and audio participants as horizontal gallery participants
9641
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
9642
- return visibleVideoParticipants.current.concat(visibleAudioParticipants.current.concat(callingParticipants));
9643
- }
9644
- else {
9645
- // If screen sharing is not active, then assign all video tiles as grid tiles.
9646
- // If there are no video tiles, then assign audio tiles as grid tiles.
9647
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
9648
- return visibleVideoParticipants.current.length > 0
9649
- ? visibleAudioParticipants.current.concat(callingParticipants)
9650
- : [];
9651
- }
9652
- }, [
9653
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */ callingParticipants,
9654
- isScreenShareActive
9655
- ]);
9656
- const horizontalGalleryParticipants = getHorizontalGalleryRemoteParticipants();
9657
- return { gridParticipants, horizontalGalleryParticipants };
9741
+ const layerHostStyle = {
9742
+ position: 'absolute',
9743
+ left: 0,
9744
+ top: 0,
9745
+ width: '100%',
9746
+ height: '100%',
9747
+ overflow: 'hidden',
9748
+ // pointer events for layerHost set to none to make descendants interactive
9749
+ pointerEvents: 'none'
9658
9750
  };
9659
9751
 
9660
9752
  // Copyright (c) Microsoft Corporation.
9661
9753
  /**
9662
- * DefaultLayout displays remote participants, local video component, and screen sharing component in
9663
- * a grid and horizontal gallery.
9754
+ * FloatingLocalVideoLayout displays remote participants and a screen sharing component in
9755
+ * a grid and horizontal gallery while floating the local video
9664
9756
  *
9665
9757
  * @private
9666
9758
  */
9667
- const DefaultLayout = (props) => {
9668
- const { remoteParticipants = [], dominantSpeakers, localVideoComponent, screenShareComponent, onRenderRemoteParticipant, styles, maxRemoteVideoStreams, parentWidth } = props;
9759
+ const FloatingLocalVideoLayout = (props) => {
9760
+ const { remoteParticipants = [], dominantSpeakers, localVideoComponent, screenShareComponent, onRenderRemoteParticipant, styles, maxRemoteVideoStreams, showCameraSwitcherInLocalPreview, parentWidth, parentHeight } = props;
9761
+ const theme = useTheme();
9669
9762
  const isNarrow = parentWidth ? isNarrowWidth(parentWidth) : false;
9670
9763
  const floatingLocalVideoLayout = useFloatingLocalVideoLayout({
9671
9764
  remoteParticipants,
@@ -9680,18 +9773,29 @@ const DefaultLayout = (props) => {
9680
9773
  ? ((_a = p.videoStream) === null || _a === void 0 ? void 0 : _a.isAvailable) && activeVideoStreams++ < maxRemoteVideoStreams
9681
9774
  : (_b = p.videoStream) === null || _b === void 0 ? void 0 : _b.isAvailable);
9682
9775
  });
9776
+ const shouldFloatLocalVideo = remoteParticipants.length > 0;
9777
+ if (!shouldFloatLocalVideo && localVideoComponent) {
9778
+ gridTiles.push(localVideoComponent);
9779
+ }
9683
9780
  const horizontalGalleryTiles = floatingLocalVideoLayout.horizontalGalleryParticipants.map((p) => {
9684
9781
  var _a, _b;
9685
9782
  return onRenderRemoteParticipant(p, maxRemoteVideoStreams && maxRemoteVideoStreams >= 0
9686
9783
  ? ((_a = p.videoStream) === null || _a === void 0 ? void 0 : _a.isAvailable) && activeVideoStreams++ < maxRemoteVideoStreams
9687
9784
  : (_b = p.videoStream) === null || _b === void 0 ? void 0 : _b.isAvailable);
9688
9785
  });
9689
- if (localVideoComponent) {
9690
- gridTiles.push(localVideoComponent);
9691
- }
9692
- return (React__default['default'].createElement(react.Stack, { horizontal: false, styles: rootLayoutStyle },
9693
- screenShareComponent ? (screenShareComponent) : (React__default['default'].createElement(GridLayout, { key: "grid-layout", styles: styles === null || styles === void 0 ? void 0 : styles.gridLayout }, gridTiles)),
9694
- horizontalGalleryTiles.length > 0 && (React__default['default'].createElement(VideoGalleryResponsiveHorizontalGallery, { isNarrow: isNarrow, horizontalGalleryElements: horizontalGalleryTiles, styles: styles === null || styles === void 0 ? void 0 : styles.horizontalGallery }))));
9786
+ const layerHostId = reactHooks.useId('layerhost');
9787
+ const wrappedLocalVideoComponent = localVideoComponent && shouldFloatLocalVideo ? (
9788
+ // When we use showCameraSwitcherInLocalPreview it disables dragging to allow keyboard navigation.
9789
+ showCameraSwitcherInLocalPreview ? (React__default['default'].createElement(react.Stack, { className: react.mergeStyles(localVideoTileWithControlsContainerStyle(theme, isNarrow), {
9790
+ boxShadow: theme.effects.elevation8,
9791
+ zIndex: LOCAL_VIDEO_TILE_ZINDEX
9792
+ }) }, localVideoComponent)) : horizontalGalleryTiles.length > 0 ? (React__default['default'].createElement(react.Stack, { className: react.mergeStyles(localVideoTileContainerStyle(theme, isNarrow)) }, localVideoComponent)) : (React__default['default'].createElement(FloatingLocalVideo, { localVideoComponent: localVideoComponent, layerHostId: layerHostId, isNarrow: isNarrow, parentWidth: parentWidth, parentHeight: parentHeight }))) : undefined;
9793
+ return (React__default['default'].createElement(react.Stack, { styles: rootLayoutStyle },
9794
+ wrappedLocalVideoComponent,
9795
+ React__default['default'].createElement(react.Stack, { horizontal: false, styles: innerLayoutStyle },
9796
+ screenShareComponent ? (screenShareComponent) : (React__default['default'].createElement(GridLayout, { key: "grid-layout", styles: styles === null || styles === void 0 ? void 0 : styles.gridLayout }, gridTiles)),
9797
+ horizontalGalleryTiles.length > 0 && (React__default['default'].createElement(VideoGalleryResponsiveHorizontalGallery, { isNarrow: isNarrow, shouldFloatLocalVideo: true, horizontalGalleryElements: horizontalGalleryTiles, styles: styles === null || styles === void 0 ? void 0 : styles.horizontalGallery })),
9798
+ React__default['default'].createElement(react.LayerHost, { id: layerHostId, className: react.mergeStyles(layerHostStyle) }))));
9695
9799
  };
9696
9800
 
9697
9801
  // Copyright (c) Microsoft Corporation.
@@ -9700,22 +9804,6 @@ const DefaultLayout = (props) => {
9700
9804
  * Currently the Calling JS SDK supports up to 4 remote video streams
9701
9805
  */
9702
9806
  const DEFAULT_MAX_REMOTE_VIDEO_STREAMS = 4;
9703
- /**
9704
- * @private
9705
- * Set aside only 6 dominant speakers for remaining audio participants
9706
- */
9707
- const MAX_AUDIO_DOMINANT_SPEAKERS = 6;
9708
- const DRAG_OPTIONS$1 = {
9709
- moveMenuItemText: 'Move',
9710
- closeMenuItemText: 'Close',
9711
- menu: react.ContextualMenu,
9712
- keepInBounds: true
9713
- };
9714
- // Manually override the max position used to keep the modal in the bounds of its container.
9715
- // This is a workaround for: https://github.com/microsoft/fluentui/issues/20122
9716
- // Because our modal starts in the bottom right corner, we can say that this is the max (i.e. rightmost and bottomost)
9717
- // position the modal can be dragged to.
9718
- const modalMaxDragPosition = { x: localVideoTileOuterPaddingPX, y: localVideoTileOuterPaddingPX };
9719
9807
  /**
9720
9808
  * VideoGallery represents a layout of video tiles for a specific call.
9721
9809
  * It displays a {@link VideoTile} for the local user as well as for each remote participant who has joined the call.
@@ -9723,53 +9811,17 @@ const modalMaxDragPosition = { x: localVideoTileOuterPaddingPX, y: localVideoTil
9723
9811
  * @public
9724
9812
  */
9725
9813
  const VideoGallery = (props) => {
9726
- var _a, _b, _c, _d, _e;
9814
+ var _a, _b;
9727
9815
  const { localParticipant, remoteParticipants = [], localVideoViewOptions, remoteVideoViewOptions, dominantSpeakers, onRenderLocalVideoTile, onRenderRemoteVideoTile, onCreateLocalStreamView, onDisposeLocalStreamView, onCreateRemoteStreamView, onDisposeRemoteStreamView, styles, layout, onRenderAvatar, showMuteIndicator, maxRemoteVideoStreams = DEFAULT_MAX_REMOTE_VIDEO_STREAMS, showCameraSwitcherInLocalPreview, localVideoCameraCycleButtonProps } = props;
9728
9816
  const ids = useIdentifiers();
9729
9817
  const theme = useTheme();
9730
9818
  const localeStrings = useLocale$1().strings.videoGallery;
9731
9819
  const strings = Object.assign(Object.assign({}, localeStrings), props.strings);
9732
9820
  const shouldFloatLocalVideo = !!(layout === 'floatingLocalVideo' && remoteParticipants.length > 0);
9733
- const shouldFloatNonDraggableLocalVideo = !!(showCameraSwitcherInLocalPreview && shouldFloatLocalVideo);
9734
9821
  const containerRef = React.useRef(null);
9735
9822
  const containerWidth = _useContainerWidth(containerRef);
9736
9823
  const containerHeight = _useContainerHeight(containerRef);
9737
9824
  const isNarrow = containerWidth ? isNarrowWidth(containerWidth) : false;
9738
- const visibleVideoParticipants = React.useRef([]);
9739
- const visibleAudioParticipants = React.useRef([]);
9740
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
9741
- const visibleCallingParticipants = React.useRef([]);
9742
- const modalWidth = isNarrow ? SMALL_FLOATING_MODAL_SIZE_PX$1.width : LARGE_FLOATING_MODAL_SIZE_PX$1.width;
9743
- const modalHeight = isNarrow ? SMALL_FLOATING_MODAL_SIZE_PX$1.height : LARGE_FLOATING_MODAL_SIZE_PX$1.height;
9744
- // The minimum drag position is the top left of the video gallery. i.e. the modal (PiP) should not be able
9745
- // to be dragged offscreen and these are the top and left bounds of that calculation.
9746
- const modalMinDragPosition = React.useMemo(() => containerWidth && containerHeight
9747
- ? {
9748
- // We use -containerWidth/Height because our modal is positioned to start in the bottom right,
9749
- // hence (0,0) is the bottom right of the video gallery.
9750
- x: -containerWidth + modalWidth + localVideoTileOuterPaddingPX,
9751
- y: -containerHeight + modalHeight + localVideoTileOuterPaddingPX
9752
- }
9753
- : undefined, [containerHeight, containerWidth, modalHeight, modalWidth]);
9754
- visibleVideoParticipants.current = smartDominantSpeakerParticipants({
9755
- participants: (_a = remoteParticipants === null || remoteParticipants === void 0 ? void 0 : remoteParticipants.filter((p) => { var _a; return (_a = p.videoStream) === null || _a === void 0 ? void 0 : _a.isAvailable; })) !== null && _a !== void 0 ? _a : [],
9756
- dominantSpeakers,
9757
- lastVisibleParticipants: visibleVideoParticipants.current,
9758
- maxDominantSpeakers: maxRemoteVideoStreams
9759
- }).slice(0, maxRemoteVideoStreams);
9760
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
9761
- visibleCallingParticipants.current = (_b = remoteParticipants === null || remoteParticipants === void 0 ? void 0 : remoteParticipants.filter((p) => p.state === ('Connecting' ))) !== null && _b !== void 0 ? _b : [];
9762
- // This set will be used to filter out participants already in visibleVideoParticipants
9763
- const visibleVideoParticipantsSet = new Set(visibleVideoParticipants.current.map((p) => p.userId));
9764
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
9765
- const visibleCallingParticipantsSet = new Set(visibleCallingParticipants.current.map((p) => p.userId));
9766
- visibleAudioParticipants.current = smartDominantSpeakerParticipants({
9767
- participants: (_c = remoteParticipants === null || remoteParticipants === void 0 ? void 0 : remoteParticipants.filter((p) => !visibleVideoParticipantsSet.has(p.userId) &&
9768
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */ !visibleCallingParticipantsSet.has(p.userId))) !== null && _c !== void 0 ? _c : [],
9769
- dominantSpeakers,
9770
- lastVisibleParticipants: visibleAudioParticipants.current,
9771
- maxDominantSpeakers: MAX_AUDIO_DOMINANT_SPEAKERS
9772
- });
9773
9825
  /* @conditional-compile-remove(rooms) */
9774
9826
  const permissions = _usePermissions();
9775
9827
  /**
@@ -9817,73 +9869,16 @@ const VideoGallery = (props) => {
9817
9869
  /* @conditional-compile-remove(PSTN-calls) */
9818
9870
  participantState: participant.state })));
9819
9871
  }, [onCreateRemoteStreamView, onDisposeRemoteStreamView, remoteVideoViewOptions, onRenderAvatar, showMuteIndicator]);
9820
- const videoTiles = onRenderRemoteVideoTile
9821
- ? visibleVideoParticipants.current.map((participant) => onRenderRemoteVideoTile(participant))
9822
- : visibleVideoParticipants.current.map((participant) => {
9823
- return defaultOnRenderVideoTile(participant, true);
9824
- });
9825
- const audioTiles = onRenderRemoteVideoTile
9826
- ? visibleAudioParticipants.current.map((participant) => onRenderRemoteVideoTile(participant))
9827
- : visibleAudioParticipants.current.map((participant) => {
9828
- return defaultOnRenderVideoTile(participant, false);
9829
- });
9830
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
9831
- const callingTiles = onRenderRemoteVideoTile
9832
- ? visibleCallingParticipants.current.map((participant) => onRenderRemoteVideoTile(participant))
9833
- : visibleCallingParticipants.current.map((participant) => {
9834
- return defaultOnRenderVideoTile(participant, false);
9835
- });
9836
9872
  const screenShareParticipant = remoteParticipants.find((participant) => { var _a; return (_a = participant.screenShareStream) === null || _a === void 0 ? void 0 : _a.isAvailable; });
9837
- const screenShareActive = screenShareParticipant || (localParticipant === null || localParticipant === void 0 ? void 0 : localParticipant.isScreenSharingOn);
9838
- const createGridTiles = () => {
9839
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
9840
- return videoTiles.length > 0 ? videoTiles : audioTiles.concat(callingTiles);
9841
- };
9842
- const gridTiles = createGridTiles();
9843
- const createHorizontalGalleryTiles = () => {
9844
- if (screenShareActive) {
9845
- // If screen sharing is active, assign video and audio participants as horizontal gallery participants
9846
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
9847
- return videoTiles.concat(audioTiles.concat(callingTiles));
9848
- }
9849
- else {
9850
- // If screen sharing is not active, then assign all video tiles as grid tiles.
9851
- // If there are no video tiles, then assign audio tiles as grid tiles.
9852
- /* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
9853
- return videoTiles.length > 0 ? audioTiles.concat(callingTiles) : [];
9854
- }
9855
- };
9856
- const horizontalGalleryTiles = createHorizontalGalleryTiles();
9857
- if (!shouldFloatLocalVideo && localVideoTile) {
9858
- gridTiles.push(localVideoTile);
9859
- }
9860
9873
  const localScreenShareStreamComponent = React__default['default'].createElement(LocalScreenShare, { localParticipant: localParticipant });
9861
- const remoteScreenShareComponent = screenShareParticipant && (React__default['default'].createElement(RemoteScreenShare, Object.assign({}, screenShareParticipant, { renderElement: (_d = screenShareParticipant.screenShareStream) === null || _d === void 0 ? void 0 : _d.renderElement, onCreateRemoteStreamView: onCreateRemoteStreamView, onDisposeRemoteStreamView: onDisposeRemoteStreamView, isReceiving: (_e = screenShareParticipant.screenShareStream) === null || _e === void 0 ? void 0 : _e.isReceiving })));
9862
- const horizontalGalleryPresent = horizontalGalleryTiles && horizontalGalleryTiles.length > 0;
9863
- const layerHostId = reactHooks.useId('layerhost');
9864
- if (layout === 'floatingLocalVideo') {
9865
- return (React__default['default'].createElement("div", { "data-ui-id": ids.videoGallery, ref: containerRef, className: react.mergeStyles(videoGalleryOuterDivStyle, styles === null || styles === void 0 ? void 0 : styles.root) },
9866
- shouldFloatLocalVideo &&
9867
- !shouldFloatNonDraggableLocalVideo &&
9868
- localVideoTile &&
9869
- (horizontalGalleryPresent ? (React__default['default'].createElement(react.Stack, { className: react.mergeStyles(localVideoTileContainerStyle(theme, isNarrow)) }, localVideoTile)) : (React__default['default'].createElement(_ModalClone, { isOpen: true, isModeless: true, dragOptions: DRAG_OPTIONS$1, styles: floatingLocalVideoModalStyle(theme, isNarrow), layerProps: { hostId: layerHostId }, maxDragPosition: modalMaxDragPosition, minDragPosition: modalMinDragPosition }, localVideoTile))),
9870
- // When we use showCameraSwitcherInLocalPreview it disables dragging to allow keyboard navigation.
9871
- shouldFloatNonDraggableLocalVideo && localVideoTile && remoteParticipants.length > 0 && (React__default['default'].createElement(react.Stack, { className: react.mergeStyles(localVideoTileWithControlsContainerStyle(theme, isNarrow), {
9872
- boxShadow: theme.effects.elevation8,
9873
- zIndex: LOCAL_VIDEO_TILE_ZINDEX
9874
- }) }, localVideoTile)),
9875
- React__default['default'].createElement(react.Stack, { horizontal: false, styles: videoGalleryContainerStyle },
9876
- screenShareParticipant ? (remoteScreenShareComponent) : (localParticipant === null || localParticipant === void 0 ? void 0 : localParticipant.isScreenSharingOn) ? (localScreenShareStreamComponent) : (React__default['default'].createElement(GridLayout, { key: "grid-layout", styles: styles === null || styles === void 0 ? void 0 : styles.gridLayout }, gridTiles)),
9877
- horizontalGalleryPresent && (React__default['default'].createElement(VideoGalleryResponsiveHorizontalGallery, { shouldFloatLocalVideo: true, isNarrow: isNarrow, horizontalGalleryElements: horizontalGalleryTiles, styles: styles === null || styles === void 0 ? void 0 : styles.horizontalGallery })),
9878
- React__default['default'].createElement(react.LayerHost, { id: layerHostId, className: react.mergeStyles(layerHostStyle) }))));
9879
- }
9874
+ const remoteScreenShareComponent = screenShareParticipant && (React__default['default'].createElement(RemoteScreenShare, Object.assign({}, screenShareParticipant, { renderElement: (_a = screenShareParticipant.screenShareStream) === null || _a === void 0 ? void 0 : _a.renderElement, onCreateRemoteStreamView: onCreateRemoteStreamView, onDisposeRemoteStreamView: onDisposeRemoteStreamView, isReceiving: (_b = screenShareParticipant.screenShareStream) === null || _b === void 0 ? void 0 : _b.isReceiving })));
9880
9875
  const screenShareComponent = remoteScreenShareComponent
9881
9876
  ? remoteScreenShareComponent
9882
9877
  : localParticipant.isScreenSharingOn
9883
9878
  ? localScreenShareStreamComponent
9884
9879
  : undefined;
9885
- return (React__default['default'].createElement("div", { "data-ui-id": ids.videoGallery, ref: containerRef, className: react.mergeStyles(videoGalleryOuterDivStyle, styles === null || styles === void 0 ? void 0 : styles.root) },
9886
- React__default['default'].createElement(DefaultLayout, { remoteParticipants: remoteParticipants, onRenderRemoteParticipant: onRenderRemoteVideoTile !== null && onRenderRemoteVideoTile !== void 0 ? onRenderRemoteVideoTile : defaultOnRenderVideoTile, localVideoComponent: localVideoTile, screenShareComponent: screenShareComponent, maxRemoteVideoStreams: maxRemoteVideoStreams, dominantSpeakers: dominantSpeakers, parentWidth: containerWidth, styles: styles })));
9880
+ const videoGalleryLayout = layout === 'floatingLocalVideo' ? (React__default['default'].createElement(FloatingLocalVideoLayout, { remoteParticipants: remoteParticipants, onRenderRemoteParticipant: onRenderRemoteVideoTile !== null && onRenderRemoteVideoTile !== void 0 ? onRenderRemoteVideoTile : defaultOnRenderVideoTile, localVideoComponent: localVideoTile, screenShareComponent: screenShareComponent, showCameraSwitcherInLocalPreview: showCameraSwitcherInLocalPreview, maxRemoteVideoStreams: maxRemoteVideoStreams, dominantSpeakers: dominantSpeakers, parentWidth: containerWidth, parentHeight: containerHeight, styles: styles })) : (React__default['default'].createElement(DefaultLayout, { remoteParticipants: remoteParticipants, onRenderRemoteParticipant: onRenderRemoteVideoTile !== null && onRenderRemoteVideoTile !== void 0 ? onRenderRemoteVideoTile : defaultOnRenderVideoTile, localVideoComponent: localVideoTile, screenShareComponent: screenShareComponent, maxRemoteVideoStreams: maxRemoteVideoStreams, dominantSpeakers: dominantSpeakers, parentWidth: containerWidth, styles: styles }));
9881
+ return (React__default['default'].createElement("div", { "data-ui-id": ids.videoGallery, ref: containerRef, className: react.mergeStyles(videoGalleryOuterDivStyle, styles === null || styles === void 0 ? void 0 : styles.root) }, videoGalleryLayout));
9887
9882
  };
9888
9883
 
9889
9884
  // Copyright (c) Microsoft Corporation.
@@ -10582,7 +10577,7 @@ const DomainPermissionsContainer = (props) => {
10582
10577
  var _a, _b;
10583
10578
  const { appName, onTroubleshootingClick, onPrimaryButtonClick, strings } = props;
10584
10579
  const theme = react.useTheme();
10585
- return (React__default['default'].createElement(react.Stack, { style: { padding: '2rem', maxWidth: '25.375rem' }, "aria-label": strings === null || strings === void 0 ? void 0 : strings.ariaLabel },
10580
+ return (React__default['default'].createElement(react.Stack, { style: { padding: '2rem', maxWidth: '25.375rem', alignSelf: 'center' }, "aria-label": strings === null || strings === void 0 ? void 0 : strings.ariaLabel },
10586
10581
  React__default['default'].createElement(react.Stack, { styles: iconBannerContainerStyles, horizontal: true, horizontalAlign: 'center', verticalFill: true, tokens: tokens },
10587
10582
  props.cameraIconName && (React__default['default'].createElement(react.Stack, null,
10588
10583
  React__default['default'].createElement(react.Icon, { styles: iconPrimaryStyles, iconName: props.cameraIconName }))),
@@ -11582,6 +11577,14 @@ const DialpadContainer = (props) => {
11582
11577
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
11583
11578
  onChange: (e) => {
11584
11579
  setText(e.target.value);
11580
+ },
11581
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11582
+ onClick: (e) => {
11583
+ const input = e.target;
11584
+ const end = input.value.length;
11585
+ // Move focus to end of input field
11586
+ input.setSelectionRange(end, end);
11587
+ input.focus();
11585
11588
  }, placeholder: props.strings.placeholderText, "data-test-id": "dialpad-input", onRenderSuffix: () => {
11586
11589
  var _a;
11587
11590
  return (React__default['default'].createElement(React__default['default'].Fragment, null, showDeleteButton && plainTextValue.length !== 0 && (React__default['default'].createElement(react.IconButton, { ariaLabel: props.strings.deleteButtonAriaLabel, onClick: deleteNumbers, styles: react.concatStyleSets(iconButtonStyles(theme), (_a = props.styles) === null || _a === void 0 ? void 0 : _a.deleteIcon), iconProps: { iconName: 'DialpadBackspace' } }))));
@@ -11945,6 +11948,21 @@ const convertRemoteVideoStreamToVideoGalleryStream = (stream) => {
11945
11948
  scalingMode: (_c = stream.view) === null || _c === void 0 ? void 0 : _c.scalingMode
11946
11949
  };
11947
11950
  };
11951
+ /** @private */
11952
+ const memoizeLocalParticipant = memoizeOne__default['default']((identifier, displayName, isMuted, isScreenSharingOn, localVideoStream) => {
11953
+ var _a, _b;
11954
+ return ({
11955
+ userId: identifier,
11956
+ displayName: displayName !== null && displayName !== void 0 ? displayName : '',
11957
+ isMuted: isMuted,
11958
+ isScreenSharingOn: isScreenSharingOn,
11959
+ videoStream: {
11960
+ isAvailable: !!localVideoStream,
11961
+ isMirrored: (_a = localVideoStream === null || localVideoStream === void 0 ? void 0 : localVideoStream.view) === null || _a === void 0 ? void 0 : _a.isMirrored,
11962
+ renderElement: (_b = localVideoStream === null || localVideoStream === void 0 ? void 0 : localVideoStream.view) === null || _b === void 0 ? void 0 : _b.target
11963
+ }
11964
+ });
11965
+ });
11948
11966
 
11949
11967
  // Copyright (c) Microsoft Corporation.
11950
11968
  /**
@@ -11961,7 +11979,6 @@ const videoGallerySelector = reselect.createSelector([
11961
11979
  getIdentifier,
11962
11980
  getDominantSpeakers
11963
11981
  ], (screenShareRemoteParticipantId, remoteParticipants, localVideoStreams, isMuted, isScreenSharingOn, displayName, identifier, dominantSpeakers) => {
11964
- var _a, _b;
11965
11982
  const screenShareRemoteParticipant = screenShareRemoteParticipantId && remoteParticipants
11966
11983
  ? remoteParticipants[screenShareRemoteParticipantId]
11967
11984
  : undefined;
@@ -11973,17 +11990,7 @@ const videoGallerySelector = reselect.createSelector([
11973
11990
  screenShareParticipant: screenShareRemoteParticipant
11974
11991
  ? convertRemoteParticipantToVideoGalleryRemoteParticipant(toFlatCommunicationIdentifier(screenShareRemoteParticipant.identifier), screenShareRemoteParticipant.isMuted, checkIsSpeaking(screenShareRemoteParticipant), screenShareRemoteParticipant.videoStreams, screenShareRemoteParticipant.state, screenShareRemoteParticipant.displayName)
11975
11992
  : undefined,
11976
- localParticipant: {
11977
- userId: identifier,
11978
- displayName: displayName !== null && displayName !== void 0 ? displayName : '',
11979
- isMuted: isMuted,
11980
- isScreenSharingOn: isScreenSharingOn,
11981
- videoStream: {
11982
- isAvailable: !!localVideoStream,
11983
- isMirrored: (_a = localVideoStream === null || localVideoStream === void 0 ? void 0 : localVideoStream.view) === null || _a === void 0 ? void 0 : _a.isMirrored,
11984
- renderElement: (_b = localVideoStream === null || localVideoStream === void 0 ? void 0 : localVideoStream.view) === null || _b === void 0 ? void 0 : _b.target
11985
- }
11986
- },
11993
+ localParticipant: memoizeLocalParticipant(identifier, displayName, isMuted, isScreenSharingOn, localVideoStream),
11987
11994
  remoteParticipants: _videoGalleryRemoteParticipantsMemo(updateUserDisplayNamesTrampoline$2(remoteParticipants ? Object.values(remoteParticipants) : noRemoteParticipants)),
11988
11995
  dominantSpeakers: dominantSpeakerIds
11989
11996
  };