diff --git a/change/@fluentui-react-message-bar-92344a29-0bc4-4716-a66c-be75a48e33b1.json b/change/@fluentui-react-message-bar-92344a29-0bc4-4716-a66c-be75a48e33b1.json
new file mode 100644
index 00000000000000..d111f8d0ba8ed0
--- /dev/null
+++ b/change/@fluentui-react-message-bar-92344a29-0bc4-4716-a66c-be75a48e33b1.json
@@ -0,0 +1,7 @@
+{
+ "type": "patch",
+ "comment": "fix(MessageBar): v9 MessageBar Resize Flicker Fix",
+ "packageName": "@fluentui/react-message-bar",
+ "email": "jiangemma@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/react-components/react-message-bar/library/src/components/MessageBar/MessageBar.test.tsx b/packages/react-components/react-message-bar/library/src/components/MessageBar/MessageBar.test.tsx
index 1fac6cac4f3815..389a4cdc6de0ff 100644
--- a/packages/react-components/react-message-bar/library/src/components/MessageBar/MessageBar.test.tsx
+++ b/packages/react-components/react-message-bar/library/src/components/MessageBar/MessageBar.test.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { render } from '@testing-library/react';
+import { render, act } from '@testing-library/react';
import { isConformant } from '../../testing/isConformant';
import { MessageBar } from './MessageBar';
import { AnnounceProvider } from '@fluentui/react-shared-contexts';
@@ -8,6 +8,7 @@ import { MessageBarTitle } from '../MessageBarTitle/MessageBarTitle';
import { MessageBarActions } from '../MessageBarActions/MessageBarActions';
import { resetIdsForTests } from '@fluentui/react-utilities';
import type { MessageBarProps } from './MessageBar.types';
+import { messageBarClassNames } from './useMessageBarStyles.styles';
describe('MessageBar', () => {
beforeAll(() => {
@@ -92,4 +93,118 @@ describe('MessageBar', () => {
expect(announce).toHaveBeenCalledTimes(1);
expect(announce).toHaveBeenCalledWith('TitleBody,Action 1Action 2', expect.anything());
});
+
+ describe('should not flicker during reflow', () => {
+ let originalResizeObserver: typeof ResizeObserver;
+ let resizeCallback: ResizeObserverCallback | undefined;
+ let observedElement: HTMLElement | undefined;
+
+ beforeAll(() => {
+ originalResizeObserver = global.ResizeObserver;
+ global.ResizeObserver = class MockResizeObserver {
+ constructor(cb: ResizeObserverCallback) {
+ resizeCallback = cb;
+ }
+ public observe(el: Element) {
+ observedElement = el as HTMLElement;
+ Object.defineProperty(observedElement, 'scrollWidth', {
+ configurable: true,
+ get() {
+ return 640;
+ },
+ });
+ }
+ public unobserve() {
+ /* noop */
+ }
+ public disconnect() {
+ /* noop */
+ }
+ } as unknown as typeof ResizeObserver;
+ });
+
+ afterAll(() => {
+ global.ResizeObserver = originalResizeObserver;
+ });
+
+ beforeEach(() => {
+ resizeCallback = undefined;
+ observedElement = undefined;
+ });
+
+ const renderMessageBar = () =>
+ render(
+
+
+ Title
+ This message bar body is long enough that it needs to reflow to multiple lines.
+
+ ,
+ );
+
+ const isReflowing = (container: HTMLElement) =>
+ container.querySelector(`.${messageBarClassNames.bottomReflowSpacer}`) !== null;
+
+ // Simulate the container being resized (e.g. dragging the window / page) to a given width.
+ const resizeTo = (inlineSize: number) =>
+ act(() => {
+ resizeCallback?.(
+ [
+ {
+ target: observedElement,
+ borderBoxSize: [{ inlineSize, blockSize: 0 }],
+ } as unknown as ResizeObserverEntry,
+ ],
+ {} as ResizeObserver,
+ );
+ });
+
+ it('reflows to multiline when the container is narrower than the content', () => {
+ const { container } = renderMessageBar();
+ const singleLineWidth = observedElement!.scrollWidth;
+
+ // Wider than the content - stays single line.
+ resizeTo(singleLineWidth + 100);
+ expect(isReflowing(container)).toBe(false);
+
+ // Narrower than the content - reflows to multiline.
+ resizeTo(singleLineWidth - 100);
+ expect(isReflowing(container)).toBe(true);
+ });
+
+ it('does not flicker while the container width changes during a drag resize', () => {
+ const { container } = renderMessageBar();
+ const singleLineWidth = observedElement!.scrollWidth;
+
+ // Narrow enough to reflow.
+ resizeTo(singleLineWidth - 100);
+ expect(isReflowing(container)).toBe(true);
+
+ // Simulate a drag where the width changes but always stays below the width of the single line layout.
+ const observed: boolean[] = [];
+ for (const width of [singleLineWidth - 120, singleLineWidth - 40, singleLineWidth - 80, singleLineWidth - 10]) {
+ resizeTo(width);
+ observed.push(isReflowing(container));
+ }
+
+ // It must stay multiline the entire time - no toggling back to single line.
+ expect(observed).toEqual([true, true, true, true]);
+ });
+
+ it('returns to single line only once there is room for the content again', () => {
+ const { container } = renderMessageBar();
+ const singleLineWidth = observedElement!.scrollWidth;
+
+ resizeTo(singleLineWidth - 100);
+ expect(isReflowing(container)).toBe(true);
+
+ // Growing but still not enough room - stays reflowed.
+ resizeTo(singleLineWidth - 1);
+ expect(isReflowing(container)).toBe(true);
+
+ // Enough room for the single line layout again - returns to single line.
+ resizeTo(singleLineWidth);
+ expect(isReflowing(container)).toBe(false);
+ });
+ });
});
diff --git a/packages/react-components/react-message-bar/library/src/components/MessageBar/useMessageBarReflow.ts b/packages/react-components/react-message-bar/library/src/components/MessageBar/useMessageBarReflow.ts
index b8272479c1ccc2..17f9e7a880ab28 100644
--- a/packages/react-components/react-message-bar/library/src/components/MessageBar/useMessageBarReflow.ts
+++ b/packages/react-components/react-message-bar/library/src/components/MessageBar/useMessageBarReflow.ts
@@ -14,7 +14,7 @@ export function useMessageBarReflow(enabled: boolean = false): {
// TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286
const resizeObserverRef = React.useRef(null);
- const prevInlineSizeRef = React.useRef(-1);
+ const singleLineWidthRef = React.useRef(Number.POSITIVE_INFINITY);
const handleResize: ResizeObserverCallback = React.useCallback(
entries => {
@@ -47,21 +47,18 @@ export function useMessageBarReflow(enabled: boolean = false): {
let nextReflowing: boolean | undefined;
- // No easy way to really determine when the single line layout will fit
- // Just keep try to set single line layout as long as the size is growing
- // Will cause flickering when size is being adjusted gradually (i.e. drag) - but this should not be a common case
if (reflowingRef.current) {
- if (prevInlineSizeRef.current < inlineSize) {
+ if (inlineSize >= singleLineWidthRef.current) {
nextReflowing = false;
}
} else {
const scrollWidth = target.scrollWidth;
if (inlineSize < scrollWidth) {
nextReflowing = true;
+ singleLineWidthRef.current = scrollWidth;
}
}
- prevInlineSizeRef.current = inlineSize;
if (typeof nextReflowing !== 'undefined' && reflowingRef.current !== nextReflowing) {
reflowingRef.current = nextReflowing;
forceUpdate();