Skip to content
Draft
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
7 changes: 7 additions & 0 deletions change/@fluentui-priority-overflow-pr4-opt-out.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: apply a forceUpdate requested before observe once observation begins",
"packageName": "@fluentui/priority-overflow",
"email": "bsunderhus@microsoft.com",
"dependentChangeType": "patch"
}
7 changes: 7 additions & 0 deletions change/@fluentui-react-overflow-pr4-opt-out.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: allow opting out of first-paint overflow correctness for hot-path consumers",
"packageName": "@fluentui/react-overflow",
"email": "bsunderhus@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,40 @@ describe('overflowManager', () => {

expect(manager.getSnapshot().itemVisibility).toEqual({});
});

it('applies a forceUpdate requested before observe once observation starts (deferred first paint)', () => {
const manager = createOverflowManager(createObserveOptions());
const container = createContainer(100);
const itemA = createElementWithSize('button', 60);
const itemB = createElementWithSize('button', 60);
const menu = createElementWithSize('button', 30);

manager.addItem({ element: itemA, id: 'a', priority: 1 });
manager.addItem({ element: itemB, id: 'b', priority: 0 });
manager.addOverflowMenu(menu);

// forceUpdate before observe can't compute anything yet; the manager defers it and observe()
// applies it — so overflow is resolved synchronously without passing the forceUpdate option.
manager.forceUpdate();
manager.observe(container);

expect(getVisibleIds(manager)).toEqual(['a']);
expect(getInvisibleIds(manager)).toEqual(['b']);
});

it('does not apply a deferred forceUpdate when the container is not measured', () => {
const manager = createOverflowManager(createObserveOptions());
const container = createContainer(0);
const itemA = createElementWithSize('button', 60);
const itemB = createElementWithSize('button', 60);

manager.addItem({ element: itemA, id: 'a', priority: 1 });
manager.addItem({ element: itemB, id: 'b', priority: 0 });

// Degenerate 0 size — observe() skips the deferred force so nothing collapses.
manager.forceUpdate();
manager.observe(container);

expect(manager.getSnapshot().itemVisibility).toEqual({});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export function createOverflowManager(initialOptions: Partial<OverflowOptions> =
// If true, next update will dispatch to onUpdateOverflow even if queue top states don't change
// Initially true to force dispatch on first mount
let forceDispatch = true;
let forceUpdateOnObserve = false;
const options: Required<OverflowOptions> = { ...DEFAULT_OPTIONS, ...initialOptions };
const overflowItems: Record<string, OverflowItemEntry> = {};
const overflowDividers: Record<string, OverflowDividerEntry> = {};
Expand Down Expand Up @@ -236,6 +237,11 @@ export function createOverflowManager(initialOptions: Partial<OverflowOptions> =
};

const forceUpdate: OverflowManager['forceUpdate'] = () => {
if (!container) {
forceUpdateOnObserve = true;
return;
}

if (processOverflowItems() || forceDispatch) {
forceDispatch = false;
dispatchOverflowUpdate();
Expand Down Expand Up @@ -283,9 +289,10 @@ export function createOverflowManager(initialOptions: Partial<OverflowOptions> =
update();
});

if (shouldForceUpdate && getClientSize(observedContainer) > 0) {
if ((shouldForceUpdate || forceUpdateOnObserve) && getClientSize(observedContainer) > 0) {
forceUpdate();
}
forceUpdateOnObserve = false;
};

const disconnect: OverflowManager['disconnect'] = () => {
Expand All @@ -298,6 +305,7 @@ export function createOverflowManager(initialOptions: Partial<OverflowOptions> =
container = undefined;
observing = false;
forceDispatch = true;
forceUpdateOnObserve = false;

// clear all entries
Object.keys(overflowItems).forEach(itemId => removeItem(itemId));
Expand Down
Loading
Loading