Skip to content
Merged
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
48 changes: 36 additions & 12 deletions packages/react/src/collapsible/panel/useCollapsiblePanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect
import { useStableCallback } from '@base-ui-components/utils/useStableCallback';
import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs';
import { useOnMount } from '@base-ui-components/utils/useOnMount';
import { AnimationFrame } from '@base-ui-components/utils/useAnimationFrame';
import { AnimationFrame, useAnimationFrame } from '@base-ui-components/utils/useAnimationFrame';
import { warn } from '@base-ui-components/utils/warn';
import { HTMLProps } from '../../utils/types';
import { createChangeEventDetails } from '../../utils/createBaseUIEventDetails';
Expand Down Expand Up @@ -43,6 +43,8 @@ export function useCollapsiblePanel(
const shouldCancelInitialOpenAnimationRef = React.useRef(open);
const shouldCancelInitialOpenTransitionRef = React.useRef(open);

const endingStyleFrame = useAnimationFrame();

/**
* When opening, the `hidden` attribute is removed immediately.
* When closing, the `hidden` attribute is set after any exit animations runs.
Expand Down Expand Up @@ -208,25 +210,47 @@ export function useCollapsiblePanel(
/* closing */
setDimensions({ height: panel.scrollHeight, width: panel.scrollWidth });

abortControllerRef.current = new AbortController();
const signal = abortControllerRef.current.signal;
const abortController = new AbortController();
abortControllerRef.current = abortController;
const signal = abortController.signal;

let attributeObserver: MutationObserver | null = null;

let frame2 = -1;
const frame1 = AnimationFrame.request(() => {
// Wait until the `[data-ending-style]` attribute is added.
frame2 = AnimationFrame.request(() => {
const endingStyleAttribute = CollapsiblePanelDataAttributes.endingStyle;

// Wait for `[data-ending-style]` to be applied.
attributeObserver = new MutationObserver((mutationList) => {
const hasEndingStyle = mutationList.some(
(mutation) =>
mutation.type === 'attributes' && mutation.attributeName === endingStyleAttribute,
);

if (hasEndingStyle) {
attributeObserver?.disconnect();
attributeObserver = null;
runOnceAnimationsFinish(() => {
setDimensions({ height: 0, width: 0 });
panel.style.removeProperty('content-visibility');
setMounted(false);
abortControllerRef.current = null;
if (abortControllerRef.current === abortController) {
abortControllerRef.current = null;
}
}, signal);
});
}
});

attributeObserver.observe(panel, {
attributes: true,
attributeFilter: [endingStyleAttribute],
});

return () => {
AnimationFrame.cancel(frame1);
AnimationFrame.cancel(frame2);
attributeObserver?.disconnect();
endingStyleFrame.cancel();
if (abortControllerRef.current === abortController) {
abortController.abort();
abortControllerRef.current = null;
}
};
}

Expand All @@ -236,6 +260,7 @@ export function useCollapsiblePanel(
}, [
abortControllerRef,
animationTypeRef,
endingStyleFrame,
hiddenUntilFound,
keepMounted,
mounted,
Expand All @@ -244,7 +269,6 @@ export function useCollapsiblePanel(
runOnceAnimationsFinish,
setDimensions,
setMounted,
transitionDimensionRef,
]);

useIsoLayoutEffect(() => {
Expand Down
Loading