Skip to content

Commit 5011391

Browse files
authored
Stabilize the scrolling behavior (#2959)
* Stabilize the scrolling behavior --------- Signed-off-by: Porter Nan <jiangnanhello@live.com>
1 parent e08818a commit 5011391

13 files changed

Lines changed: 58 additions & 11 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Stabilize the scrolling behavior",
4+
"packageName": "@azure/communication-react",
5+
"email": "jiangnanhello@live.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Stabilize the scrolling behavior",
4+
"packageName": "@azure/communication-react",
5+
"email": "jiangnanhello@live.com",
6+
"dependentChangeType": "patch"
7+
}

packages/calling-component-bindings/src/captionsSelector.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,12 @@ export const _captionsBannerSelector: _CaptionsBannerSelector = reselect.createS
102102
[getCaptions, getCaptionsStatus],
103103
(captions, isCaptionsFeatureActive) => {
104104
const captionsInfo = captions?.map((c) => {
105+
const userId = c.speaker.identifier ? toFlatCommunicationIdentifier(c.speaker.identifier) : '';
105106
return {
107+
id: c.timestamp.getTime() + userId + c.speaker.displayName,
106108
displayName: c.speaker.displayName ?? 'Unnamed Participant',
107109
captionText: c.captionText ?? '',
108-
userId: c.speaker.identifier ? toFlatCommunicationIdentifier(c.speaker.identifier) : ''
110+
userId
109111
};
110112
});
111113
return {

packages/react-components/review/beta/react-components.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ export interface _CaptionsBannerProps {
388388

389389
// @internal
390390
export type _CaptionsInfo = {
391+
id: string;
391392
displayName: string;
392393
captionText: string;
393394
userId?: string;

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { OnRenderAvatarCallback } from '../types';
1717
* information required for each line of caption
1818
*/
1919
export type _CaptionsInfo = {
20+
id: string;
2021
displayName: string;
2122
captionText: string;
2223
userId?: string;
@@ -85,9 +86,9 @@ export const _CaptionsBanner = (props: _CaptionsBannerProps): JSX.Element => {
8586
<FocusZone as="ul" className={captionsContainerClassName}>
8687
<Ref innerRef={captionsScrollDivRef}>
8788
<Stack verticalAlign="start" className={captionsBannerClassName}>
88-
{captions.map((caption, key) => {
89+
{captions.map((caption) => {
8990
return (
90-
<div key={key} className={captionContainerClassName} data-is-focusable={true}>
91+
<div key={caption.id} className={captionContainerClassName} data-is-focusable={true}>
9192
<_Caption {...caption} onRenderAvatar={onRenderAvatar} />
9293
</div>
9394
);

packages/react-components/src/components/styles/Captions.style.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const captionsContainerClassName = mergeStyles({
4444
export const captionContainerClassName = mergeStyles({
4545
marginTop: _pxToRem(6),
4646
marginBottom: _pxToRem(6),
47-
overflowAnchor: 'none'
47+
overflowAnchor: 'auto'
4848
});
4949

5050
/**

packages/react-components/src/components/utils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,16 @@ export const dismissError = (dismissedErrors: DismissedError[], toDismiss: Activ
5050
}
5151
}
5252

53+
const toDismissTimestamp = toDismiss.timestamp ?? now;
54+
5355
// Record that this error was dismissed for the first time right now.
5456
return [
5557
...dismissedErrors,
5658
{
5759
type: toDismiss.type,
58-
dismissedAt: now,
60+
// the error time could be sometimes later than the button click time, which cause the dismiss not working
61+
// so we set the dismiss time to the later one
62+
dismissedAt: now > toDismissTimestamp ? now : toDismissTimestamp,
5963
activeSince: toDismiss.timestamp
6064
}
6165
];

packages/react-composites/tests/app/call/QueryArgs.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import { Role } from '@internal/react-components';
55
import { CallCompositeOptions } from '../../../src';
66
import { MockCallAdapterState } from '../../common';
7+
import { jsonDateDeserializer } from '../lib/utils';
78

89
export interface QueryArgs {
910
// Defined only for hermetic tests.
@@ -34,7 +35,9 @@ export function parseQueryArgs(): QueryArgs {
3435
const params = Object.fromEntries(urlSearchParams.entries());
3536

3637
return {
37-
mockCallAdapterState: params.mockCallAdapterState ? JSON.parse(params.mockCallAdapterState) : undefined,
38+
mockCallAdapterState: params.mockCallAdapterState
39+
? JSON.parse(params.mockCallAdapterState, jsonDateDeserializer) // json date deserializer is needed because Date objects are serialized as strings by JSON.stringify
40+
: undefined,
3841
useFrLocale: Boolean(params.useFrLocale),
3942
showCallDescription: Boolean(params.showCallDescription),
4043
injectParticipantMenuItems: Boolean(params.injectParticipantMenuItems),

packages/react-composites/tests/app/callwithchat/QueryArgs.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT license.
33

4+
import { jsonDateDeserializer } from '../lib/utils';
45
import type { CallWithChatCompositeOptions, _FakeChatAdapterArgs } from '../../../src';
56
import type { MockCallAdapterState } from '../../common';
67

@@ -58,7 +59,9 @@ export function parseQueryArgs(): QueryArgs {
5859
userId: params.userId ?? '',
5960

6061
fakeChatAdapterArgs: params.fakeChatAdapterArgs ? JSON.parse(params.fakeChatAdapterArgs) : undefined,
61-
mockCallAdapterState: params.mockCallAdapterState ? JSON.parse(params.mockCallAdapterState) : undefined,
62+
mockCallAdapterState: params.mockCallAdapterState
63+
? JSON.parse(params.mockCallAdapterState, jsonDateDeserializer) // json date deserializer is needed because Date objects are serialized as strings by JSON.stringify
64+
: undefined,
6265

6366
customCompositeOptions: params.customCompositeOptions ? JSON.parse(params.customCompositeOptions) : undefined,
6467
injectCustomButtons: params.injectCustomButtons === 'true',

packages/react-composites/tests/app/lib/utils.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,13 @@ export function verifyParamExists<T>(param: T, paramName: string): T {
7575
*/
7676
export const isMobile = (): boolean =>
7777
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
78+
79+
/**
80+
* @private
81+
*/
82+
export const jsonDateDeserializer = (key: unknown, value: unknown): unknown => {
83+
if (key === 'timestamp' && typeof value === 'string') {
84+
return new Date(value);
85+
}
86+
return value;
87+
};

0 commit comments

Comments
 (0)