);
};
@@ -43,18 +53,14 @@ ImperativePositionUpdate.parameters = {
description: {
story: [
'The `positioningRef` positioning prop provides an [imperative handle](https://reactjs.org/docs/hooks-reference.html#useimperativehandle)',
- 'to reposition the positioned element. This can be useful for scenarios where content is dynamically loaded.',
+ 'to reposition the positioned element.',
+ 'In this example the `updatePosition` command is used to reposition the popover when its target button is',
+ 'dynamically moved.',
'',
- 'In this example, you can move your mouse in the red boundary and the tooltip will follow the mouse cursor',
+ '> ⚠️ In later versions of Fluent UI, position updates are triggered once the target or container dimensions',
+ 'change. This was previously the main use case for imperative position updates. Please think carefully',
+ 'if your scenario needs this pattern in the future.',
].join('\n'),
},
},
};
-
-const Placeholder = () => (
-
-
Dynamic content
-
-
-
-);
diff --git a/packages/react-components/react-positioning/src/createPositionManager.ts b/packages/react-components/react-positioning/src/createPositionManager.ts
index 0b7a354162874f..24978fcd2cafdf 100644
--- a/packages/react-components/react-positioning/src/createPositionManager.ts
+++ b/packages/react-components/react-positioning/src/createPositionManager.ts
@@ -4,6 +4,7 @@ import type { PositionManager, TargetElement } from './types';
import { debounce, writeArrowUpdates, writeContainerUpdates } from './utils';
import { isHTMLElement } from '@fluentui/react-utilities';
import { listScrollParents } from './utils/listScrollParents';
+import { createResizeObserver } from './utils/createResizeObserver';
interface PositionManagerOptions {
/**
@@ -43,18 +44,21 @@ interface PositionManagerOptions {
* @returns manager that handles positioning out of the react lifecycle
*/
export function createPositionManager(options: PositionManagerOptions): PositionManager {
- const { container, target, arrow, strategy, middleware, placement, useTransform = true } = options;
let isDestroyed = false;
- if (!target || !container) {
+ const { container, target, arrow, strategy, middleware, placement, useTransform = true } = options;
+ const targetWindow = container.ownerDocument.defaultView;
+ if (!target || !container || !targetWindow) {
return {
updatePosition: () => undefined,
dispose: () => undefined,
};
}
+ // When the dimensions of the target or the container change - trigger a position update
+ const resizeObserver = createResizeObserver(targetWindow, () => updatePosition());
+
let isFirstUpdate = true;
const scrollParents: Set = new Set();
- const targetWindow = container.ownerDocument.defaultView;
// When the container is first resolved, set position `fixed` to avoid scroll jumps.
// Without this scroll jumps can occur when the element is rendered initially and receives focus
@@ -77,6 +81,11 @@ export function createPositionManager(options: PositionManagerOptions): Position
scrollParent.addEventListener('scroll', updatePosition, { passive: true });
});
+ resizeObserver.observe(container);
+ if (isHTMLElement(target)) {
+ resizeObserver.observe(target);
+ }
+
isFirstUpdate = false;
}
@@ -129,6 +138,8 @@ export function createPositionManager(options: PositionManagerOptions): Position
scrollParent.removeEventListener('scroll', updatePosition);
});
scrollParents.clear();
+
+ resizeObserver.disconnect();
};
if (targetWindow) {
diff --git a/packages/react-components/react-positioning/src/utils/createResizeObserver.ts b/packages/react-components/react-positioning/src/utils/createResizeObserver.ts
new file mode 100644
index 00000000000000..7c2ac5e90ecbff
--- /dev/null
+++ b/packages/react-components/react-positioning/src/utils/createResizeObserver.ts
@@ -0,0 +1,19 @@
+export function createResizeObserver(targetWindow: Window & typeof globalThis, callback: ResizeObserverCallback) {
+ // https://github.com/jsdom/jsdom/issues/3368
+ // Add the polyfill here so it is not needed for all unit tests that leverage positioning
+ if (process.env.NODE_ENV === 'test') {
+ targetWindow.ResizeObserver = class ResizeObserver {
+ public observe() {
+ // do nothing
+ }
+ public unobserve() {
+ // do nothing
+ }
+ public disconnect() {
+ // do nothing
+ }
+ };
+ }
+
+ return new targetWindow.ResizeObserver(callback);
+}