@stream-io/video-react-sdk 1.0.7 → 1.0.9

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ### [1.0.9](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.0.8...@stream-io/video-react-sdk-1.0.9) (2024-05-21)
6
+
7
+ ### Dependency Updates
8
+
9
+ * `@stream-io/video-client` updated to version `1.0.7`
10
+ * `@stream-io/video-react-bindings` updated to version `0.4.33`
11
+ ### [1.0.8](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.0.7...@stream-io/video-react-sdk-1.0.8) (2024-05-17)
12
+
13
+ ### Dependency Updates
14
+
15
+ * `@stream-io/video-styling` updated to version `1.0.3`
16
+
17
+ ### Bug Fixes
18
+
19
+ * popup-related UI updates ([#1356](https://github.com/GetStream/stream-video-js/issues/1356)) ([a1a3238](https://github.com/GetStream/stream-video-js/commit/a1a3238370b1ed5b7877f744bebea9f51a843256))
20
+
5
21
  ### [1.0.7](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-1.0.6...@stream-io/video-react-sdk-1.0.7) (2024-05-16)
6
22
 
7
23
  ### Dependency Updates
@@ -2388,7 +2388,7 @@
2388
2388
  display: flex;
2389
2389
  justify-content: center;
2390
2390
  padding: 0.5rem 1rem;
2391
- z-index: 1;
2391
+ z-index: 4;
2392
2392
  max-width: 250px;
2393
2393
  width: max-content;
2394
2394
  white-space: initial;
@@ -2397,7 +2397,7 @@
2397
2397
 
2398
2398
  .str-video {
2399
2399
  /* The border radius used for the borders of the component */
2400
- --str-video__tooltip--border-radius: var(--str-video__border-radius-xxxs);
2400
+ --str-video__tooltip--border-radius: var(--str-video__border-radius-xs);
2401
2401
  /* The text/icon color of the component */
2402
2402
  --str-video__tooltip--color: var(--str-video__text-color3);
2403
2403
  /* The background color of the component */
@@ -2411,7 +2411,7 @@
2411
2411
  /* Right (left in RTL layout) border of the component */
2412
2412
  --str-video__tooltip--border-inline-end: none;
2413
2413
  /* Box shadow applied to the component */
2414
- --str-video__tooltip--box-shadow: 0 0 20px var(--str-video__background-color2);
2414
+ --str-video__tooltip--box-shadow: none;
2415
2415
  }
2416
2416
 
2417
2417
  .str-video__tooltip {
package/dist/index.cjs.js CHANGED
@@ -516,8 +516,10 @@ const MenuPortal = ({ children, refs, }) => {
516
516
  const portalId = react.useMemo(() => `str-video-portal-${Math.random().toString(36).substring(2, 9)}`, []);
517
517
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { id: portalId, className: "str-video__portal" }), jsxRuntime.jsx(react$1.FloatingOverlay, { children: jsxRuntime.jsx(react$1.FloatingPortal, { id: portalId, children: jsxRuntime.jsx("div", { className: "str-video__portal-content", ref: refs.setFloating, children: children }) }) })] }));
518
518
  };
519
- const MenuToggle = ({ ToggleButton, placement = 'top-start', strategy = 'absolute', offset, visualType = exports.MenuVisualType.MENU, children, }) => {
519
+ const MenuToggle = ({ ToggleButton, placement = 'top-start', strategy = 'absolute', offset, visualType = exports.MenuVisualType.MENU, children, onToggle, }) => {
520
520
  const [menuShown, setMenuShown] = react.useState(false);
521
+ const toggleHandler = react.useRef(onToggle);
522
+ toggleHandler.current = onToggle;
521
523
  const { floating, domReference, refs, x, y } = useFloatingUIPreset({
522
524
  placement,
523
525
  strategy,
@@ -527,9 +529,11 @@ const MenuToggle = ({ ToggleButton, placement = 'top-start', strategy = 'absolut
527
529
  const handleClick = (event) => {
528
530
  if (!floating && domReference?.contains(event.target)) {
529
531
  setMenuShown(true);
532
+ toggleHandler.current?.(true);
530
533
  }
531
534
  else if (floating && !floating?.contains(event.target)) {
532
535
  setMenuShown(false);
536
+ toggleHandler.current?.(false);
533
537
  }
534
538
  };
535
539
  const handleKeyDown = (event) => {
@@ -537,6 +541,7 @@ const MenuToggle = ({ ToggleButton, placement = 'top-start', strategy = 'absolut
537
541
  !event.altKey &&
538
542
  !event.ctrlKey) {
539
543
  setMenuShown(false);
544
+ toggleHandler.current?.(false);
540
545
  }
541
546
  };
542
547
  document?.addEventListener('click', handleClick, { capture: true });
@@ -551,7 +556,7 @@ const MenuToggle = ({ ToggleButton, placement = 'top-start', strategy = 'absolut
551
556
  top: y ?? 0,
552
557
  left: x ?? 0,
553
558
  overflowY: 'auto',
554
- }, children: children })) : null })), jsxRuntime.jsx(ToggleButton, { menuShown: menuShown, ref: refs.setReference })] }));
559
+ }, role: "menu", children: children })) : null })), jsxRuntime.jsx(ToggleButton, { menuShown: menuShown, ref: refs.setReference })] }));
555
560
  };
556
561
 
557
562
  const GenericMenu = ({ children, onItemClick, }) => {
@@ -600,7 +605,7 @@ const applyElementToRef = (ref, element) => {
600
605
  ref.current = element;
601
606
  };
602
607
 
603
- const CompositeButton = react.forwardRef(function CompositeButton({ caption, children, className, active, Menu, menuPlacement, menuOffset, title, ToggleMenuButton = DefaultToggleMenuButton, variant, onClick, ...restButtonProps }, ref) {
608
+ const CompositeButton = react.forwardRef(function CompositeButton({ caption, children, className, active, Menu, menuPlacement, menuOffset, title, ToggleMenuButton = DefaultToggleMenuButton, variant, onClick, onMenuToggle, ...restButtonProps }, ref) {
604
609
  return (jsxRuntime.jsxs("div", { className: clsx('str-video__composite-button', className, {
605
610
  'str-video__composite-button--caption': caption,
606
611
  'str-video__composite-button--menu': Menu,
@@ -611,7 +616,7 @@ const CompositeButton = react.forwardRef(function CompositeButton({ caption, chi
611
616
  }), children: [jsxRuntime.jsx("button", { type: "button", className: "str-video__composite-button__button", onClick: (e) => {
612
617
  e.preventDefault();
613
618
  onClick?.(e);
614
- }, ...restButtonProps, children: children }), Menu && (jsxRuntime.jsx(MenuToggle, { offset: menuOffset, placement: menuPlacement, ToggleButton: ToggleMenuButton, children: isComponentType(Menu) ? jsxRuntime.jsx(Menu, {}) : Menu }))] }), caption && (jsxRuntime.jsx("div", { className: "str-video__composite-button__caption", children: caption }))] }));
619
+ }, ...restButtonProps, children: children }), Menu && (jsxRuntime.jsx(MenuToggle, { offset: menuOffset, placement: menuPlacement, ToggleButton: ToggleMenuButton, onToggle: onMenuToggle, children: isComponentType(Menu) ? jsxRuntime.jsx(Menu, {}) : Menu }))] }), caption && (jsxRuntime.jsx("div", { className: "str-video__composite-button__caption", children: caption }))] }));
615
620
  });
616
621
  const DefaultToggleMenuButton = react.forwardRef(function DefaultToggleMenuButton({ menuShown }, ref) {
617
622
  return (jsxRuntime.jsx(IconButton, { className: clsx('str-video__menu-toggle-button', {
@@ -710,6 +715,45 @@ const LoadingIndicator = ({ className, type = 'spinner', text, tooltip, }) => {
710
715
  return (jsxRuntime.jsxs("div", { className: clsx('str-video__loading-indicator', className), title: tooltip, children: [jsxRuntime.jsx("div", { className: clsx('str-video__loading-indicator__icon', type) }), text && jsxRuntime.jsx("p", { className: "str-video__loading-indicator-text", children: text })] }));
711
716
  };
712
717
 
718
+ const Tooltip = ({ children, referenceElement, tooltipClassName, tooltipPlacement = 'top', visible = false, }) => {
719
+ const { refs, x, y, strategy } = useFloatingUIPreset({
720
+ placement: tooltipPlacement,
721
+ strategy: 'absolute',
722
+ });
723
+ react.useEffect(() => {
724
+ refs.setReference(referenceElement);
725
+ }, [referenceElement, refs]);
726
+ if (!visible)
727
+ return null;
728
+ return (jsxRuntime.jsx("div", { className: clsx('str-video__tooltip', tooltipClassName), ref: refs.setFloating, style: {
729
+ position: strategy,
730
+ top: y ?? 0,
731
+ left: x ?? 0,
732
+ overflowY: 'auto',
733
+ }, children: children }));
734
+ };
735
+
736
+ const useEnterLeaveHandlers = ({ onMouseEnter, onMouseLeave, } = {}) => {
737
+ const [tooltipVisible, setTooltipVisible] = react.useState(false);
738
+ const handleMouseEnter = react.useCallback((e) => {
739
+ setTooltipVisible(true);
740
+ onMouseEnter?.(e);
741
+ }, [onMouseEnter]);
742
+ const handleMouseLeave = react.useCallback((e) => {
743
+ setTooltipVisible(false);
744
+ onMouseLeave?.(e);
745
+ }, [onMouseLeave]);
746
+ return { handleMouseEnter, handleMouseLeave, tooltipVisible };
747
+ };
748
+
749
+ // todo: duplicate of CallParticipantList.tsx#MediaIndicator - refactor to a single component
750
+ const WithTooltip = ({ title, tooltipClassName, tooltipPlacement, tooltipDisabled, ...props }) => {
751
+ const { handleMouseEnter, handleMouseLeave, tooltipVisible } = useEnterLeaveHandlers();
752
+ const [tooltipAnchor, setTooltipAnchor] = react.useState(null);
753
+ const tooltipActuallyVisible = !tooltipDisabled && Boolean(title) && tooltipVisible;
754
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Tooltip, { referenceElement: tooltipAnchor, visible: tooltipActuallyVisible, tooltipClassName: tooltipClassName, tooltipPlacement: tooltipPlacement, children: title || '' }), jsxRuntime.jsx("div", { ref: setTooltipAnchor, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, ...props })] }));
755
+ };
756
+
713
757
  const RecordEndConfirmation = () => {
714
758
  const { t } = videoReactBindings.useI18n();
715
759
  const { toggleCallRecording, isAwaitingResponse } = useToggleCallRecording();
@@ -728,15 +772,18 @@ const RecordCallConfirmationButton = ({ caption, }) => {
728
772
  videoClient.OwnCapability.STOP_RECORD_CALL,
729
773
  ], children: jsxRuntime.jsx(MenuToggle, { ToggleButton: ToggleEndRecordingMenuButton, visualType: exports.MenuVisualType.PORTAL, children: jsxRuntime.jsx(RecordEndConfirmation, {}) }) }));
730
774
  }
775
+ const title = isAwaitingResponse
776
+ ? t('Waiting for recording to start...')
777
+ : caption ?? t('Record call');
731
778
  return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [
732
779
  videoClient.OwnCapability.START_RECORD_CALL,
733
780
  videoClient.OwnCapability.STOP_RECORD_CALL,
734
- ], children: jsxRuntime.jsx(CompositeButton, { active: isCallRecordingInProgress, caption: caption, title: caption || t('Record call'), variant: "secondary", "data-testid": "recording-start-button", onClick: isAwaitingResponse ? undefined : toggleCallRecording, children: isAwaitingResponse ? (jsxRuntime.jsx(LoadingIndicator, { tooltip: t('Waiting for recording to start...') })) : (jsxRuntime.jsx(Icon, { icon: "recording-off" })) }) }));
781
+ ], children: jsxRuntime.jsx(WithTooltip, { title: title, children: jsxRuntime.jsx(CompositeButton, { active: isCallRecordingInProgress, caption: caption, variant: "secondary", "data-testid": "recording-start-button", onClick: isAwaitingResponse ? undefined : toggleCallRecording, children: isAwaitingResponse ? (jsxRuntime.jsx(LoadingIndicator, {})) : (jsxRuntime.jsx(Icon, { icon: "recording-off" })) }) }) }));
735
782
  };
736
783
  const RecordCallButton = ({ caption }) => {
737
784
  const { t } = videoReactBindings.useI18n();
738
785
  const { toggleCallRecording, isAwaitingResponse, isCallRecordingInProgress } = useToggleCallRecording();
739
- let title = caption || t('Record call');
786
+ let title = caption ?? t('Record call');
740
787
  if (isAwaitingResponse) {
741
788
  title = isCallRecordingInProgress
742
789
  ? t('Waiting for recording to stop...')
@@ -808,18 +855,48 @@ const ReactionsButton = ({ reactions = defaultReactions, }) => {
808
855
  };
809
856
  const ToggleReactionsMenuButton = react.forwardRef(function ToggleReactionsMenuButton({ menuShown }, ref) {
810
857
  const { t } = videoReactBindings.useI18n();
811
- return (jsxRuntime.jsx(CompositeButton, { ref: ref, active: menuShown, variant: "primary", title: t('Reactions'), children: jsxRuntime.jsx(Icon, { icon: "reactions" }) }));
858
+ return (jsxRuntime.jsx(WithTooltip, { title: t('Reactions'), tooltipDisabled: menuShown, children: jsxRuntime.jsx(CompositeButton, { ref: ref, active: menuShown, variant: "primary", children: jsxRuntime.jsx(Icon, { icon: "reactions" }) }) }));
812
859
  });
813
860
  const DefaultReactionsMenu = ({ reactions, layout = 'horizontal', }) => {
814
861
  const call = videoReactBindings.useCall();
862
+ const { close } = useMenuContext();
815
863
  return (jsxRuntime.jsx("div", { className: clsx('str-video__reactions-menu', {
816
864
  'str-video__reactions-menu--horizontal': layout === 'horizontal',
817
865
  'str-video__reactions-menu--vertical': layout === 'vertical',
818
866
  }), children: reactions.map((reaction) => (jsxRuntime.jsx("button", { type: "button", className: "str-video__reactions-menu__button", onClick: () => {
819
867
  call?.sendReaction(reaction);
868
+ close?.();
820
869
  }, children: reaction.emoji_code && defaultEmojiReactionMap[reaction.emoji_code] }, reaction.emoji_code))) }));
821
870
  };
822
871
 
872
+ /**
873
+ * Wraps an event handler, silencing and logging exceptions (excluding the NotAllowedError
874
+ * DOMException, which is a normal situation handled by the SDK)
875
+ *
876
+ * @param props component props, including the onError callback
877
+ * @param handler event handler to wrap
878
+ */
879
+ const createCallControlHandler = (props, handler) => {
880
+ const logger = videoClient.getLogger(['react-sdk']);
881
+ return async () => {
882
+ try {
883
+ await handler();
884
+ }
885
+ catch (error) {
886
+ if (props.onError) {
887
+ props.onError(error);
888
+ return;
889
+ }
890
+ if (!isNotAllowedError(error)) {
891
+ logger('error', 'Call control handler failed', error);
892
+ }
893
+ }
894
+ };
895
+ };
896
+ function isNotAllowedError(error) {
897
+ return error instanceof DOMException && error.name === 'NotAllowedError';
898
+ }
899
+
823
900
  const ScreenShareButton = (props) => {
824
901
  const { t } = videoReactBindings.useI18n();
825
902
  const { caption } = props;
@@ -832,16 +909,17 @@ const ScreenShareButton = (props) => {
832
909
  const amIScreenSharing = !optimisticIsMute;
833
910
  const disableScreenShareButton = !amIScreenSharing &&
834
911
  (isSomeoneScreenSharing || isScreenSharingAllowed === false);
835
- return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SCREENSHARE], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SCREENSHARE, isAwaitingApproval: isAwaitingPermission, messageApproved: t('You can now share your screen.'), messageAwaitingApproval: t('Awaiting for an approval to share screen.'), messageRevoked: t('You can no longer share your screen.'), children: jsxRuntime.jsx(CompositeButton, { active: isSomeoneScreenSharing || amIScreenSharing, caption: caption, title: caption || t('Share screen'), variant: "primary", "data-testid": isSomeoneScreenSharing
836
- ? 'screen-share-stop-button'
837
- : 'screen-share-start-button', disabled: disableScreenShareButton, onClick: async () => {
838
- if (!hasPermission) {
839
- await requestPermission();
840
- }
841
- else {
842
- await screenShare.toggle();
843
- }
844
- }, children: jsxRuntime.jsx(Icon, { icon: isSomeoneScreenSharing ? 'screen-share-on' : 'screen-share-off' }) }) }) }));
912
+ const handleClick = createCallControlHandler(props, async () => {
913
+ if (!hasPermission) {
914
+ await requestPermission();
915
+ }
916
+ else {
917
+ await screenShare.toggle();
918
+ }
919
+ });
920
+ return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SCREENSHARE], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SCREENSHARE, isAwaitingApproval: isAwaitingPermission, messageApproved: t('You can now share your screen.'), messageAwaitingApproval: t('Awaiting for an approval to share screen.'), messageRevoked: t('You can no longer share your screen.'), children: jsxRuntime.jsx(WithTooltip, { title: caption ?? t('Share screen'), children: jsxRuntime.jsx(CompositeButton, { active: isSomeoneScreenSharing || amIScreenSharing, caption: caption, variant: "primary", "data-testid": isSomeoneScreenSharing
921
+ ? 'screen-share-stop-button'
922
+ : 'screen-share-start-button', disabled: disableScreenShareButton, onClick: handleClick, children: jsxRuntime.jsx(Icon, { icon: isSomeoneScreenSharing ? 'screen-share-on' : 'screen-share-off' }) }) }) }) }));
845
923
  };
846
924
 
847
925
  const SelectContext = react.createContext({});
@@ -923,6 +1001,7 @@ const DeviceSelectorOption = ({ disabled, id, label, onChange, name, selected, d
923
1001
  };
924
1002
  const DeviceSelectorList = (props) => {
925
1003
  const { devices = [], selectedDeviceId: selectedDeviceFromProps, title, type, onChange, } = props;
1004
+ const { close } = useMenuContext();
926
1005
  // sometimes the browser (Chrome) will report the system-default device
927
1006
  // with an id of 'default'. In case when it doesn't, we'll select the first
928
1007
  // available device.
@@ -934,6 +1013,7 @@ const DeviceSelectorList = (props) => {
934
1013
  return (jsxRuntime.jsxs("div", { className: "str-video__device-settings__device-kind", children: [title && (jsxRuntime.jsx("div", { className: "str-video__device-settings__device-selector-title", children: title })), !devices.length ? (jsxRuntime.jsx(DeviceSelectorOption, { id: `${type}--default`, label: "Default", name: type, defaultChecked: true, value: "default" })) : (devices.map((device) => {
935
1014
  return (jsxRuntime.jsx(DeviceSelectorOption, { id: `${type}--${device.deviceId}`, value: device.deviceId, label: device.label, onChange: (e) => {
936
1015
  onChange?.(e.target.value);
1016
+ close?.();
937
1017
  }, name: type, selected: device.deviceId === selectedDeviceId || devices.length === 1 }, device.deviceId));
938
1018
  }))] }));
939
1019
  };
@@ -1007,11 +1087,13 @@ const ToggleAudioPreviewButton = (props) => {
1007
1087
  const { t } = videoReactBindings.useI18n();
1008
1088
  const { useMicrophoneState } = videoReactBindings.useCallStateHooks();
1009
1089
  const { microphone, optimisticIsMute, hasBrowserPermission } = useMicrophoneState();
1010
- return (jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", title: !hasBrowserPermission
1090
+ const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
1091
+ const handleClick = createCallControlHandler(props, () => microphone.toggle());
1092
+ return (jsxRuntime.jsx(WithTooltip, { title: !hasBrowserPermission
1011
1093
  ? t('Check your browser audio permissions')
1012
- : caption || t('Mic'), disabled: !hasBrowserPermission, "data-testid": optimisticIsMute
1013
- ? 'preview-audio-unmute-button'
1014
- : 'preview-audio-mute-button', onClick: () => microphone.toggle(), Menu: Menu, menuPlacement: menuPlacement, ...restCompositeButtonProps, children: [jsxRuntime.jsx(Icon, { icon: !optimisticIsMute ? 'mic' : 'mic-off' }), !hasBrowserPermission && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", title: t('Check your browser audio permissions'), children: "!" }))] }));
1094
+ : caption ?? t('Mic'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", disabled: !hasBrowserPermission, "data-testid": optimisticIsMute
1095
+ ? 'preview-audio-unmute-button'
1096
+ : 'preview-audio-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, onMenuToggle: (shown) => setTooltipDisabled(shown), ...restCompositeButtonProps, children: [jsxRuntime.jsx(Icon, { icon: !optimisticIsMute ? 'mic' : 'mic-off' }), !hasBrowserPermission && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", title: t('Check your browser audio permissions'), children: "!" }))] }) }));
1015
1097
  };
1016
1098
  const ToggleAudioPublishingButton = (props) => {
1017
1099
  const { t } = videoReactBindings.useI18n();
@@ -1019,18 +1101,20 @@ const ToggleAudioPublishingButton = (props) => {
1019
1101
  const { hasPermission, requestPermission, isAwaitingPermission } = useRequestPermission(videoClient.OwnCapability.SEND_AUDIO);
1020
1102
  const { useMicrophoneState } = videoReactBindings.useCallStateHooks();
1021
1103
  const { microphone, optimisticIsMute, hasBrowserPermission } = useMicrophoneState();
1022
- return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SEND_AUDIO], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SEND_AUDIO, isAwaitingApproval: isAwaitingPermission, messageApproved: t('You can now speak.'), messageAwaitingApproval: t('Awaiting for an approval to speak.'), messageRevoked: t('You can no longer speak.'), children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, title: !hasPermission
1104
+ const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
1105
+ const handleClick = createCallControlHandler(props, async () => {
1106
+ if (!hasPermission) {
1107
+ await requestPermission();
1108
+ }
1109
+ else {
1110
+ await microphone.toggle();
1111
+ }
1112
+ });
1113
+ return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SEND_AUDIO], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SEND_AUDIO, isAwaitingApproval: isAwaitingPermission, messageApproved: t('You can now speak.'), messageAwaitingApproval: t('Awaiting for an approval to speak.'), messageRevoked: t('You can no longer speak.'), children: jsxRuntime.jsx(WithTooltip, { title: !hasPermission
1023
1114
  ? t('You have no permission to share your audio')
1024
1115
  : !hasBrowserPermission
1025
1116
  ? t('Check your browser mic permissions')
1026
- : caption || t('Mic'), variant: "secondary", disabled: !hasBrowserPermission || !hasPermission, "data-testid": optimisticIsMute ? 'audio-unmute-button' : 'audio-mute-button', onClick: async () => {
1027
- if (!hasPermission) {
1028
- await requestPermission();
1029
- }
1030
- else {
1031
- await microphone.toggle();
1032
- }
1033
- }, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, ...restCompositeButtonProps, children: [jsxRuntime.jsx(Icon, { icon: optimisticIsMute ? 'mic-off' : 'mic' }), (!hasBrowserPermission || !hasPermission) && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", children: "!" }))] }) }) }));
1117
+ : caption ?? t('Mic'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission || !hasPermission, "data-testid": optimisticIsMute ? 'audio-unmute-button' : 'audio-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, onMenuToggle: (shown) => setTooltipDisabled(shown), ...restCompositeButtonProps, children: [jsxRuntime.jsx(Icon, { icon: optimisticIsMute ? 'mic-off' : 'mic' }), (!hasBrowserPermission || !hasPermission) && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", children: "!" }))] }) }) }) }));
1034
1118
  };
1035
1119
 
1036
1120
  const ToggleVideoPreviewButton = (props) => {
@@ -1038,11 +1122,13 @@ const ToggleVideoPreviewButton = (props) => {
1038
1122
  const { t } = videoReactBindings.useI18n();
1039
1123
  const { useCameraState } = videoReactBindings.useCallStateHooks();
1040
1124
  const { camera, optimisticIsMute, hasBrowserPermission } = useCameraState();
1041
- return (jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), title: !hasBrowserPermission
1125
+ const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
1126
+ const handleClick = createCallControlHandler(props, () => camera.toggle());
1127
+ return (jsxRuntime.jsx(WithTooltip, { title: !hasBrowserPermission
1042
1128
  ? t('Check your browser video permissions')
1043
- : caption || t('Video'), variant: "secondary", "data-testid": optimisticIsMute
1044
- ? 'preview-video-unmute-button'
1045
- : 'preview-video-mute-button', onClick: () => camera.toggle(), disabled: !hasBrowserPermission, Menu: Menu, menuPlacement: menuPlacement, ...restCompositeButtonProps, children: [jsxRuntime.jsx(Icon, { icon: !optimisticIsMute ? 'camera' : 'camera-off' }), !hasBrowserPermission && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", title: t('Check your browser video permissions'), children: "!" }))] }));
1129
+ : caption ?? t('Video'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, className: clsx(!hasBrowserPermission && 'str-video__device-unavailable'), variant: "secondary", "data-testid": optimisticIsMute
1130
+ ? 'preview-video-unmute-button'
1131
+ : 'preview-video-mute-button', onClick: handleClick, disabled: !hasBrowserPermission, Menu: Menu, menuPlacement: menuPlacement, onMenuToggle: (shown) => setTooltipDisabled(shown), ...restCompositeButtonProps, children: [jsxRuntime.jsx(Icon, { icon: !optimisticIsMute ? 'camera' : 'camera-off' }), !hasBrowserPermission && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", title: t('Check your browser video permissions'), children: "!" }))] }) }));
1046
1132
  };
1047
1133
  const ToggleVideoPublishingButton = (props) => {
1048
1134
  const { t } = videoReactBindings.useI18n();
@@ -1052,22 +1138,26 @@ const ToggleVideoPublishingButton = (props) => {
1052
1138
  const { camera, optimisticIsMute, hasBrowserPermission } = useCameraState();
1053
1139
  const callSettings = useCallSettings();
1054
1140
  const isPublishingVideoAllowed = callSettings?.video.enabled;
1055
- return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SEND_VIDEO], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SEND_VIDEO, isAwaitingApproval: isAwaitingPermission, messageApproved: t('You can now share your video.'), messageAwaitingApproval: t('Awaiting for an approval to share your video.'), messageRevoked: t('You can no longer share your video.'), children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, variant: "secondary", title: !hasPermission
1141
+ const [tooltipDisabled, setTooltipDisabled] = react.useState(false);
1142
+ const handleClick = createCallControlHandler(props, async () => {
1143
+ if (!hasPermission) {
1144
+ await requestPermission();
1145
+ }
1146
+ else {
1147
+ await camera.toggle();
1148
+ }
1149
+ });
1150
+ return (jsxRuntime.jsx(videoReactBindings.Restricted, { requiredGrants: [videoClient.OwnCapability.SEND_VIDEO], children: jsxRuntime.jsx(PermissionNotification, { permission: videoClient.OwnCapability.SEND_VIDEO, isAwaitingApproval: isAwaitingPermission, messageApproved: t('You can now share your video.'), messageAwaitingApproval: t('Awaiting for an approval to share your video.'), messageRevoked: t('You can no longer share your video.'), children: jsxRuntime.jsx(WithTooltip, { title: !hasPermission
1056
1151
  ? t('You have no permission to share your video')
1057
1152
  : !hasBrowserPermission
1058
1153
  ? t('Check your browser video permissions')
1059
1154
  : !isPublishingVideoAllowed
1060
1155
  ? t('Video publishing is disabled by the system')
1061
- : caption || t('Video'), disabled: !hasBrowserPermission || !hasPermission || !isPublishingVideoAllowed, "data-testid": optimisticIsMute ? 'video-unmute-button' : 'video-mute-button', onClick: async () => {
1062
- if (!hasPermission) {
1063
- await requestPermission();
1064
- }
1065
- else {
1066
- await camera.toggle();
1067
- }
1068
- }, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, ...restCompositeButtonProps, children: [jsxRuntime.jsx(Icon, { icon: optimisticIsMute ? 'camera-off' : 'camera' }), (!hasBrowserPermission ||
1156
+ : caption || t('Video'), tooltipDisabled: tooltipDisabled, children: jsxRuntime.jsxs(CompositeButton, { active: optimisticIsMute, caption: caption, variant: "secondary", disabled: !hasBrowserPermission ||
1069
1157
  !hasPermission ||
1070
- !isPublishingVideoAllowed) && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", children: "!" }))] }) }) }));
1158
+ !isPublishingVideoAllowed, "data-testid": optimisticIsMute ? 'video-unmute-button' : 'video-mute-button', onClick: handleClick, Menu: Menu, menuPlacement: menuPlacement, menuOffset: 16, onMenuToggle: (shown) => setTooltipDisabled(shown), ...restCompositeButtonProps, children: [jsxRuntime.jsx(Icon, { icon: optimisticIsMute ? 'camera-off' : 'camera' }), (!hasBrowserPermission ||
1159
+ !hasPermission ||
1160
+ !isPublishingVideoAllowed) && (jsxRuntime.jsx("span", { className: "str-video__no-media-permission", children: "!" }))] }) }) }) }));
1071
1161
  };
1072
1162
 
1073
1163
  const EndCallMenu = (props) => {
@@ -1077,7 +1167,7 @@ const EndCallMenu = (props) => {
1077
1167
  };
1078
1168
  const CancelCallToggleMenuButton = react.forwardRef(function CancelCallToggleMenuButton(props, ref) {
1079
1169
  const { t } = videoReactBindings.useI18n();
1080
- return (jsxRuntime.jsx(IconButton, { icon: "call-end", variant: "danger", title: t('Leave call'), "data-testid": "leave-call-button", ref: ref }));
1170
+ return (jsxRuntime.jsx(WithTooltip, { title: t('Leave call'), children: jsxRuntime.jsx(IconButton, { icon: "call-end", variant: "danger", "data-testid": "leave-call-button", ref: ref }) }));
1081
1171
  });
1082
1172
  const CancelCallConfirmButton = ({ onClick, onLeave, }) => {
1083
1173
  const call = videoReactBindings.useCall();
@@ -1101,7 +1191,7 @@ const CancelCallConfirmButton = ({ onClick, onLeave, }) => {
1101
1191
  }, [onClick, onLeave, call]);
1102
1192
  return (jsxRuntime.jsx(MenuToggle, { placement: "top-start", ToggleButton: CancelCallToggleMenuButton, children: jsxRuntime.jsx(EndCallMenu, { onEnd: handleEndCall, onLeave: handleLeave }) }));
1103
1193
  };
1104
- const CancelCallButton = ({ disabled, onClick, onLeave, }) => {
1194
+ const CancelCallButton = ({ disabled, caption, onClick, onLeave, }) => {
1105
1195
  const call = videoReactBindings.useCall();
1106
1196
  const { t } = videoReactBindings.useI18n();
1107
1197
  const handleClick = react.useCallback(async (e) => {
@@ -1113,10 +1203,10 @@ const CancelCallButton = ({ disabled, onClick, onLeave, }) => {
1113
1203
  onLeave?.();
1114
1204
  }
1115
1205
  }, [onClick, onLeave, call]);
1116
- return (jsxRuntime.jsx(IconButton, { disabled: disabled, icon: "call-end", variant: "danger", title: t('Leave call'), "data-testid": "cancel-call-button", onClick: handleClick }));
1206
+ return (jsxRuntime.jsx(IconButton, { disabled: disabled, icon: "call-end", variant: "danger", title: caption ?? t('Leave call'), "data-testid": "cancel-call-button", onClick: handleClick }));
1117
1207
  };
1118
1208
 
1119
- const CallControls = ({ onLeave }) => (jsxRuntime.jsxs("div", { className: "str-video__call-controls", children: [jsxRuntime.jsx(RecordCallButton, {}), jsxRuntime.jsx(ReactionsButton, {}), jsxRuntime.jsx(ScreenShareButton, {}), jsxRuntime.jsx(SpeakingWhileMutedNotification, { children: jsxRuntime.jsx(ToggleAudioPublishingButton, {}) }), jsxRuntime.jsx(ToggleVideoPublishingButton, {}), jsxRuntime.jsx(CancelCallButton, { onLeave: onLeave })] }));
1209
+ const CallControls = ({ onLeave }) => (jsxRuntime.jsxs("div", { className: "str-video__call-controls", children: [jsxRuntime.jsx(SpeakingWhileMutedNotification, { children: jsxRuntime.jsx(ToggleAudioPublishingButton, {}) }), jsxRuntime.jsx(ToggleVideoPublishingButton, {}), jsxRuntime.jsx(ReactionsButton, {}), jsxRuntime.jsx(ScreenShareButton, {}), jsxRuntime.jsx(RecordCallButton, {}), jsxRuntime.jsx(CancelCallButton, { onLeave: onLeave })] }));
1120
1210
 
1121
1211
  chart_js.Chart.register(chart_js.CategoryScale, chart_js.LinearScale, chart_js.LineElement, chart_js.PointElement);
1122
1212
  const CallStatsLatencyChart = (props) => {
@@ -1333,44 +1423,6 @@ const CallParticipantListHeader = ({ onClose, }) => {
1333
1423
  return (jsxRuntime.jsxs("div", { className: "str-video__participant-list-header", children: [jsxRuntime.jsxs("div", { className: "str-video__participant-list-header__title", children: [t('Participants'), ' ', jsxRuntime.jsxs("span", { className: "str-video__participant-list-header__title-count", children: ["[", participants.length, "]"] }), anonymousParticipantCount > 0 && (jsxRuntime.jsx("span", { className: "str-video__participant-list-header__title-anonymous", children: t('Anonymous', { count: anonymousParticipantCount }) }))] }), jsxRuntime.jsx(IconButton, { onClick: onClose, className: "str-video__participant-list-header__close-button", icon: "close" })] }));
1334
1424
  };
1335
1425
 
1336
- const Tooltip = ({ children, referenceElement, tooltipClassName, tooltipPlacement = 'top', visible = false, }) => {
1337
- const { refs, x, y, strategy } = useFloatingUIPreset({
1338
- placement: tooltipPlacement,
1339
- strategy: 'absolute',
1340
- });
1341
- react.useEffect(() => {
1342
- refs.setReference(referenceElement);
1343
- }, [referenceElement, refs]);
1344
- if (!visible)
1345
- return null;
1346
- return (jsxRuntime.jsx("div", { className: clsx('str-video__tooltip', tooltipClassName), ref: refs.setFloating, style: {
1347
- position: strategy,
1348
- top: y ?? 0,
1349
- left: x ?? 0,
1350
- overflowY: 'auto',
1351
- }, children: children }));
1352
- };
1353
-
1354
- const useEnterLeaveHandlers = ({ onMouseEnter, onMouseLeave, } = {}) => {
1355
- const [tooltipVisible, setTooltipVisible] = react.useState(false);
1356
- const handleMouseEnter = react.useCallback((e) => {
1357
- setTooltipVisible(true);
1358
- onMouseEnter?.(e);
1359
- }, [onMouseEnter]);
1360
- const handleMouseLeave = react.useCallback((e) => {
1361
- setTooltipVisible(false);
1362
- onMouseLeave?.(e);
1363
- }, [onMouseLeave]);
1364
- return { handleMouseEnter, handleMouseLeave, tooltipVisible };
1365
- };
1366
-
1367
- // todo: duplicate of CallParticipantList.tsx#MediaIndicator - refactor to a single component
1368
- const WithTooltip = ({ title, tooltipClassName, tooltipPlacement, ...props }) => {
1369
- const { handleMouseEnter, handleMouseLeave, tooltipVisible } = useEnterLeaveHandlers();
1370
- const [tooltipAnchor, setTooltipAnchor] = react.useState(null);
1371
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Tooltip, { referenceElement: tooltipAnchor, visible: tooltipVisible, tooltipClassName: tooltipClassName, tooltipPlacement: tooltipPlacement, children: title || '' }), jsxRuntime.jsx("div", { ref: setTooltipAnchor, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, ...props })] }));
1372
- };
1373
-
1374
1426
  const CallParticipantListingItem = ({ participant, DisplayName = DefaultDisplayName, }) => {
1375
1427
  const isAudioOn = videoClient.hasAudio(participant);
1376
1428
  const isVideoOn = videoClient.hasVideo(participant);
@@ -2515,7 +2567,7 @@ const VerticalScrollButtons = ({ scrollWrapper, }) => {
2515
2567
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [scrollPosition && scrollPosition !== 'top' && (jsxRuntime.jsx(IconButton, { onClick: scrollTopClickHandler, icon: "caret-up", className: "str-video__speaker-layout__participants-bar--button-top" })), scrollPosition && scrollPosition !== 'bottom' && (jsxRuntime.jsx(IconButton, { onClick: scrollBottomClickHandler, icon: "caret-down", className: "str-video__speaker-layout__participants-bar--button-bottom" }))] }));
2516
2568
  };
2517
2569
 
2518
- const [major, minor, patch] = ("1.0.7" ).split('.');
2570
+ const [major, minor, patch] = ("1.0.9" ).split('.');
2519
2571
  videoClient.setSdkInfo({
2520
2572
  type: videoClient.SfuModels.SdkType.REACT,
2521
2573
  major,