Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: synchronously measure overflowing managers when the overflow menu attaches",
"packageName": "@fluentui/priority-overflow",
"email": "bernardo.sunderhus@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: avoid redundant overflow work during menu teardown and unchanged visibility updates",
"packageName": "@fluentui/react-overflow",
"email": "bernardo.sunderhus@gmail.com",
"dependentChangeType": "patch"
}

This file was deleted.

14 changes: 14 additions & 0 deletions packages/charts/chart-web-components/CHANGELOG.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
{
"name": "@fluentui/chart-web-components",
"entries": [
{
"date": "Wed, 19 Aug 2026 04:08:27 GMT",
"version": "0.0.95",
"tag": "@fluentui/chart-web-components_v0.0.95",
"comments": {
"patch": [
{
"author": "beachball",
"package": "@fluentui/chart-web-components",
"comment": "Bump @fluentui/web-components to v3.1.1"
}
]
}
},
{
"date": "Thu, 13 Aug 2026 04:10:22 GMT",
"version": "0.0.94",
Expand Down
11 changes: 10 additions & 1 deletion packages/charts/chart-web-components/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
# Change Log - @fluentui/chart-web-components

<!-- This log was last generated on Thu, 13 Aug 2026 04:10:22 GMT and should not be manually modified. -->
<!-- This log was last generated on Wed, 19 Aug 2026 04:08:27 GMT and should not be manually modified. -->

<!-- Start content -->

## [0.0.95](https://github.com/microsoft/fluentui/tree/@fluentui/chart-web-components_v0.0.95)

Wed, 19 Aug 2026 04:08:27 GMT
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/chart-web-components_v0.0.94..@fluentui/chart-web-components_v0.0.95)

### Patches

- Bump @fluentui/web-components to v3.1.1 ([commit](https://github.com/microsoft/fluentui/commit/undefined) by beachball)

## [0.0.94](https://github.com/microsoft/fluentui/tree/@fluentui/chart-web-components_v0.0.94)

Thu, 13 Aug 2026 04:10:22 GMT
Expand Down
4 changes: 2 additions & 2 deletions packages/charts/chart-web-components/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@fluentui/chart-web-components",
"description": "A library of Fluent Chart Web Components",
"version": "0.0.94",
"version": "0.0.95",
"author": {
"name": "Microsoft"
},
Expand Down Expand Up @@ -68,7 +68,7 @@
},
"dependencies": {
"@fluentui/tokens": "^1.0.0-alpha.24",
"@fluentui/web-components": "^3.1.0",
"@fluentui/web-components": "^3.1.1",
"@microsoft/fast-web-utilities": "^6.0.0",
"@types/d3-selection": "^3.0.0",
"@types/d3-shape": "^3.0.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/react-components/priority-overflow/src/debounce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
* @param fn - Function to debounce
* @returns debounced function
*/
export function debounce(fn: Function): () => void {
export function debounce(fn: () => void): () => void {
let pending: boolean;

// React testing platforms will often output errors when state updates happen outside `act`
// Since there is nothing obvious to wait for we just avoid debouncing in unit test environments
if (process.env.NODE_ENV === 'test') {
return fn as () => void;
return fn;
}

return () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,53 @@ describe('overflowManager', () => {
expect(onUpdateOverflow).toHaveBeenCalled();
});

it('should synchronously update once when the overflow menu attaches to an overflowing manager', async () => {
const previousNodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
const manager = createOverflowManager(createObserveOptions());
process.env.NODE_ENV = previousNodeEnv;

const container = createContainer(100);
const getClientWidth = jest.fn(() => 100);
Object.defineProperty(container, 'clientWidth', { configurable: true, get: getClientWidth });

manager.addItem({ element: createElementWithSize('button', 60), id: 'a', priority: 1 });
manager.addItem({ element: createElementWithSize('button', 60), id: 'b', priority: 0 });
manager.observe(container);
manager.forceUpdate();
getClientWidth.mockClear();

manager.addOverflowMenu(createElementWithSize('button', 30));
expect(getClientWidth).toHaveBeenCalledTimes(1);

await Promise.resolve();

expect(getClientWidth).toHaveBeenCalledTimes(1);
});

it('should batch the update when the overflow menu attaches without hidden items', async () => {
const previousNodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
const manager = createOverflowManager(createObserveOptions());
process.env.NODE_ENV = previousNodeEnv;

const container = createContainer(100);
const getClientWidth = jest.fn(() => 100);
Object.defineProperty(container, 'clientWidth', { configurable: true, get: getClientWidth });

manager.addItem({ element: createElementWithSize('button', 40), id: 'a', priority: 1 });
manager.observe(container);
manager.forceUpdate();
getClientWidth.mockClear();

manager.addOverflowMenu(createElementWithSize('button', 30));
expect(getClientWidth).not.toHaveBeenCalled();

await Promise.resolve();

expect(getClientWidth).toHaveBeenCalledTimes(1);
});

it('should remove items through removeItem', () => {
const manager = createOverflowManager(createObserveOptions());
const container = createContainer(100);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,11 @@ export function createOverflowManager(initialOptions: Partial<OverflowOptions> =

if (observing) {
forceDispatch = true;
update();
if (invisibleItemQueue.size() > 0) {
forceUpdate();
} else {
update();
}
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { OverflowAxis, OverflowManager } from '@fluentui/priority-overflow'
import { createOverflowManager } from '@fluentui/priority-overflow';
import { render } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import { DATA_OVERFLOW_MENU } from './constants';
import { useOverflowContainer } from './useOverflowContainer';

jest.mock('@fluentui/priority-overflow');
Expand Down Expand Up @@ -77,6 +78,21 @@ describe('useOverflowContainer', () => {
expect(removeItemMock).toHaveBeenCalledWith(overflowItem.id);
});

it('should mark the overflow menu before registering it with the manager', () => {
const addOverflowMenuMock = jest.fn((element: HTMLElement) => {
expect(element.hasAttribute(DATA_OVERFLOW_MENU)).toBe(true);
});
mockOverflowManager({ addOverflowMenu: addOverflowMenuMock });
const { result } = renderHook(() =>
useOverflowContainer(() => undefined, { onUpdateItemVisibility: () => undefined }),
);
const menu = document.createElement('button');

result.current.registerOverflowMenu(menu);

expect(addOverflowMenuMock).toHaveBeenCalledWith(menu);
});

it('should call observe with the container element', () => {
const observeMock = jest.fn();
mockOverflowManager({ observe: observeMock });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ export const useOverflowContainer = <TElement extends HTMLElement>(
}, []);

const registerOverflowMenu = React.useCallback((el: HTMLElement) => {
managerRef.current?.addOverflowMenu(el);
el.setAttribute(DATA_OVERFLOW_MENU, '');
managerRef.current?.addOverflowMenu(el);

return () => {
managerRef.current?.removeOverflowMenu();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import type { OverflowContextValue } from './overflowContext';
import { OverflowProvider } from './overflowContext';
import { Overflow } from './components/Overflow';
import { OverflowItem } from './components/OverflowItem/OverflowItem';
import { useOverflowMenu } from './useOverflowMenu';
Expand Down Expand Up @@ -71,4 +73,36 @@ describe('useOverflowMenu', () => {
),
).not.toThrow();
});

it('delegates menu updates to registration without forcing another update', () => {
const unregisterOverflowMenu = jest.fn();
const registerOverflowMenu = jest.fn(() => unregisterOverflowMenu);
const forceUpdateOverflow = jest.fn();
const contextValue: OverflowContextValue = {
hasOverflow: true,
itemVisibility: { item: false },
groupVisibility: {},
registerItem: () => jest.fn(),
registerOverflowMenu,
registerDivider: () => jest.fn(),
updateOverflow: jest.fn(),
forceUpdateOverflow,
getSnapshot: () => ({ itemVisibility: { item: false }, groupVisibility: {}, invisibleItemCount: 1 }),
subscribe: () => jest.fn(),
};

const { unmount } = render(
<OverflowProvider value={contextValue}>
<Menu />
</OverflowProvider>,
);

expect(registerOverflowMenu).toHaveBeenCalledTimes(1);
expect(forceUpdateOverflow).not.toHaveBeenCalled();

unmount();

expect(unregisterOverflowMenu).toHaveBeenCalledTimes(1);
expect(forceUpdateOverflow).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,15 @@ export function useOverflowMenu<TElement extends HTMLElement>(
): { ref: React.MutableRefObject<TElement | null>; overflowCount: number; isOverflowing: boolean } {
const elementId = useId('overflow-menu', id);
const overflowCount = useOverflowCount();
const { registerOverflowMenu, forceUpdateOverflow } = useOverflowContext();
const { registerOverflowMenu } = useOverflowContext();
const ref = React.useRef<TElement | null>(null);
const isOverflowing = overflowCount > 0;

useIsomorphicLayoutEffect(() => {
if (ref.current) {
const unregister = registerOverflowMenu(ref.current);
if (isOverflowing) {
forceUpdateOverflow();
}
return () => {
unregister();
forceUpdateOverflow();
};
return registerOverflowMenu(ref.current);
}
}, [registerOverflowMenu, forceUpdateOverflow, isOverflowing, elementId]);
}, [registerOverflowMenu, isOverflowing, elementId]);

return { ref, overflowCount, isOverflowing };
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { renderHook } from '@testing-library/react-hooks';
import { act, renderHook } from '@testing-library/react-hooks';
import { useOverflowVisibility } from './useOverflowVisibility';
import type { OverflowContextValue } from './overflowContext';
import { OverflowContext } from './overflowContext';
Expand Down Expand Up @@ -35,4 +35,38 @@ describe('useOverflowVisibility', () => {
expect(result.current.groupVisibility).toEqual(groupVisibility);
expect(result.current.itemVisibility).toEqual({ foo: true, bar: true, baz: false });
});

it('does not render again when the snapshot is unchanged during subscription', () => {
const snapshot = {
itemVisibility: { foo: true },
groupVisibility: {},
invisibleItemCount: 0,
};
let notify: () => void = () => undefined;
const contextValue = {
getSnapshot: () => snapshot,
subscribe: (listener: () => void) => {
notify = listener;
return () => null;
},
} as unknown as OverflowContextValue;
const Wrapper = (props: { children?: React.ReactNode }) => (
<OverflowContext.Provider {...props} value={contextValue} />
);
let renderCount = 0;

renderHook(
() => {
renderCount++;
return useOverflowVisibility();
},
{ wrapper: Wrapper },
);

expect(renderCount).toBe(1);

act(() => notify());

expect(renderCount).toBe(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,12 @@ export function useOverflowVisibility(): {
itemVisibility: Record<string, boolean>;
groupVisibility: Record<string, OverflowGroupState>;
} {
return useOverflowSnapshot(selectVisibility);
const snapshot = useOverflowSnapshot(selectSnapshot);

return {
itemVisibility: snapshot.itemVisibility,
groupVisibility: snapshot.groupVisibility,
};
}

const selectVisibility = (
snapshot: OverflowSnapshot,
): {
itemVisibility: Record<string, boolean>;
groupVisibility: Record<string, OverflowGroupState>;
} => ({
itemVisibility: snapshot.itemVisibility,
groupVisibility: snapshot.groupVisibility,
});
const selectSnapshot = (snapshot: OverflowSnapshot): OverflowSnapshot => snapshot;
15 changes: 15 additions & 0 deletions packages/web-components/CHANGELOG.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
{
"name": "@fluentui/web-components",
"entries": [
{
"date": "Wed, 19 Aug 2026 04:08:27 GMT",
"version": "3.1.1",
"tag": "@fluentui/web-components_v3.1.1",
"comments": {
"patch": [
{
"author": "jibinjose884@gmail.com",
"package": "@fluentui/web-components",
"commit": "1964e7ce05f25771f4a476ec6b2fdbfb3d5e4bf7",
"comment": "fix: remove padding-block-start in multiline dismiss slot to fix alignment"
}
]
}
},
{
"date": "Thu, 13 Aug 2026 04:10:22 GMT",
"version": "3.1.0",
Expand Down
11 changes: 10 additions & 1 deletion packages/web-components/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
# Change Log - @fluentui/web-components

<!-- This log was last generated on Thu, 13 Aug 2026 04:10:22 GMT and should not be manually modified. -->
<!-- This log was last generated on Wed, 19 Aug 2026 04:08:27 GMT and should not be manually modified. -->

<!-- Start content -->

## [3.1.1](https://github.com/microsoft/fluentui/tree/@fluentui/web-components_v3.1.1)

Wed, 19 Aug 2026 04:08:27 GMT
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/web-components_v3.1.0..@fluentui/web-components_v3.1.1)

### Patches

- fix: remove padding-block-start in multiline dismiss slot to fix alignment ([PR #36547](https://github.com/microsoft/fluentui/pull/36547) by jibinjose884@gmail.com)

## [3.1.0](https://github.com/microsoft/fluentui/tree/@fluentui/web-components_v3.1.0)

Thu, 13 Aug 2026 04:10:22 GMT
Expand Down
2 changes: 1 addition & 1 deletion packages/web-components/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@fluentui/web-components",
"description": "A library of Fluent Web Components",
"version": "3.1.0",
"version": "3.1.1",
"author": {
"name": "Microsoft",
"url": "https://discord.gg/FcSNfg4"
Expand Down
Loading
Loading