diff --git a/change/@fluentui-react-drawer-4303150f-5c8d-4bf5-a0bc-e7e9d2f3fcb1.json b/change/@fluentui-react-drawer-4303150f-5c8d-4bf5-a0bc-e7e9d2f3fcb1.json
new file mode 100644
index 00000000000000..39de0ab3187ebd
--- /dev/null
+++ b/change/@fluentui-react-drawer-4303150f-5c8d-4bf5-a0bc-e7e9d2f3fcb1.json
@@ -0,0 +1,7 @@
+{
+ "type": "patch",
+ "comment": "fix: update drawer scroll state when content changes",
+ "packageName": "@fluentui/react-drawer",
+ "email": "marcosvmmoura@gmail.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/react-components/react-drawer/library/config/tests.js b/packages/react-components/react-drawer/library/config/tests.js
index 2e211ae9e21420..67640889fffb15 100644
--- a/packages/react-components/react-drawer/library/config/tests.js
+++ b/packages/react-components/react-drawer/library/config/tests.js
@@ -1 +1,24 @@
/** Jest test setup file. */
+
+/**
+ * Mock ResizeObserver for test environment
+ *
+ * Required because DrawerBody component depends on ResizeObserver, but jsdom doesn't provide this browser API
+ */
+global.ResizeObserver = class ResizeObserver {
+ constructor(callback) {
+ this.callback = callback;
+ }
+
+ observe() {
+ // Do nothing in tests - we're only testing component behavior, not resize functionality
+ }
+
+ unobserve() {
+ // Do nothing in tests
+ }
+
+ disconnect() {
+ // Do nothing in tests
+ }
+};
diff --git a/packages/react-components/react-drawer/library/src/components/DrawerBody/DrawerBody.cy.tsx b/packages/react-components/react-drawer/library/src/components/DrawerBody/DrawerBody.cy.tsx
index 6ba117647116c1..eabf044228da6c 100644
--- a/packages/react-components/react-drawer/library/src/components/DrawerBody/DrawerBody.cy.tsx
+++ b/packages/react-components/react-drawer/library/src/components/DrawerBody/DrawerBody.cy.tsx
@@ -5,6 +5,7 @@ import { webLightTheme } from '@fluentui/react-theme';
import { DrawerBody } from './DrawerBody';
import type { JSXElement } from '@fluentui/react-utilities';
+import { DrawerProvider, useDrawerContextValue } from '../../contexts';
const mountFluent = (element: JSXElement) => {
mount({element});
@@ -39,4 +40,48 @@ describe('DrawerBody', () => {
.scrollTo('top')
.should($e => assertScrollPosition($e[0], 0));
});
+
+ it('updates scrollState when children change from short to long', () => {
+ const shortContent = 'Short content';
+ const longContent = Array(50)
+ .fill(
+ 'lorem ipsum dolor sit amet consectetur, adipisicing elit. Corrupti, animi? Quos, eum pariatur. Labore magni vel doloremque reiciendis, consequatur porro explicabo similique harum illo, ad hic, earum nobis accusantium quasi?',
+ )
+ .join(' ');
+
+ const Example = () => {
+ const context = useDrawerContextValue();
+ const [showLong, setShowLong] = React.useState(false);
+
+ return (
+
+ {context.scrollState}
+
+
+ {showLong ? longContent : shortContent}
+
+
+
+
+ );
+ };
+
+ mountFluent();
+
+ // Initially short content should result in 'none'
+ cy.get('#drawer-body').should('exist');
+ cy.get('#scroll-state').should('have.text', 'none');
+
+ // Toggle to long content and assert context scroll state updates to 'top'
+ cy.get('#toggle-content').click();
+ cy.get('#scroll-state').should('have.text', 'top');
+
+ // Scroll to bottom and assert it becomes 'bottom'
+ cy.get('#drawer-body')
+ .scrollTo('bottom')
+ // wait for any rAF-based updates and then assert the scrollState
+ .then(() => cy.get('#scroll-state').should('have.text', 'bottom'));
+ });
});
diff --git a/packages/react-components/react-drawer/library/src/components/DrawerBody/useDrawerBody.ts b/packages/react-components/react-drawer/library/src/components/DrawerBody/useDrawerBody.ts
index b7b16ee71aa49d..7e34a9ca0aa5f5 100644
--- a/packages/react-components/react-drawer/library/src/components/DrawerBody/useDrawerBody.ts
+++ b/packages/react-components/react-drawer/library/src/components/DrawerBody/useDrawerBody.ts
@@ -14,6 +14,7 @@ import { useDrawerContext_unstable } from '../../contexts/drawerContext';
import { DrawerScrollState } from '../../shared/DrawerBase.types';
import type { DrawerBodyProps, DrawerBodyState } from './DrawerBody.types';
+import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
/**
* @internal
@@ -48,10 +49,16 @@ const getScrollState = ({ scrollTop, scrollHeight, clientHeight }: HTMLElement):
* @param ref - reference to root HTMLElement of DrawerBody
*/
export const useDrawerBody_unstable = (props: DrawerBodyProps, ref: React.Ref): DrawerBodyState => {
+ const { targetDocument } = useFluent();
+ const win = targetDocument?.defaultView;
+
const { setScrollState } = useDrawerContext_unstable();
const scrollRef = React.useRef(null);
- const [setAnimationFrame, cancelAnimationFrame] = useAnimationFrame();
+ const mergedRef = useMergedRefs(ref, scrollRef);
+
+ const [setScrollAnimationFrame, cancelScrollAnimationFrame] = useAnimationFrame();
+ const [setResizeAnimationFrame, cancelResizeAnimationFrame] = useAnimationFrame();
const updateScrollState = React.useCallback(() => {
if (!scrollRef.current) {
@@ -62,23 +69,28 @@ export const useDrawerBody_unstable = (props: DrawerBodyProps, ref: React.Ref {
- cancelAnimationFrame();
- setAnimationFrame(() => updateScrollState());
- }, [cancelAnimationFrame, setAnimationFrame, updateScrollState]);
+ cancelScrollAnimationFrame();
+ setScrollAnimationFrame(updateScrollState);
+ }, [cancelScrollAnimationFrame, setScrollAnimationFrame, updateScrollState]);
- useIsomorphicLayoutEffect(() => {
- cancelAnimationFrame();
- setAnimationFrame(() => updateScrollState());
- /* update scroll state when children changes */
- return () => cancelAnimationFrame();
- }, [props.children, cancelAnimationFrame, updateScrollState, setAnimationFrame]);
+ // Update scroll state on children change
+ useIsomorphicLayoutEffect(updateScrollState, [props.children, updateScrollState]);
+ // Update scroll state on mount and when resize occurs
useIsomorphicLayoutEffect(() => {
- cancelAnimationFrame();
- setAnimationFrame(() => updateScrollState());
+ if (!scrollRef.current || !win?.ResizeObserver) {
+ return;
+ }
+
+ const observer = new win.ResizeObserver(() => setResizeAnimationFrame(updateScrollState));
+
+ observer.observe(scrollRef.current);
- return () => cancelAnimationFrame();
- }, [cancelAnimationFrame, updateScrollState, setAnimationFrame]);
+ return () => {
+ observer.disconnect();
+ cancelResizeAnimationFrame();
+ };
+ }, [setResizeAnimationFrame, cancelResizeAnimationFrame, updateScrollState, win]);
return {
components: {
@@ -87,10 +99,7 @@ export const useDrawerBody_unstable = (props: DrawerBodyProps, ref: React.Ref('div', {
- // FIXME:
- // `ref` is wrongly assigned to be `HTMLElement` instead of `HTMLDivElement`
- // but since it would be a breaking change to fix it, we are casting ref to it's proper type
- ref: useMergedRefs(ref as React.Ref, scrollRef),
+ ref: mergedRef,
...props,
onScroll: mergeCallbacks(props.onScroll, onScroll),
}),