Skip to content
Open
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 .changeset/selector-presentation-hold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: Selector and MultiSelector keep the presentation an opening chose, and a closing sheet mounted until its focus handoff, through the layer runtime's presentation hold (Layer/presentationHold.ts). No behavior change.

@cixzhang
47 changes: 32 additions & 15 deletions packages/core/src/Selector/useSelectorPresentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import {
type ResolvedAdaptivePresentation,
} from '../hooks/useAdaptivePresentation';
import {useFocusReturnVisibility} from '../hooks/useFocusReturnVisibility';
import {
createPresentationHold,
usePresentationHold,
} from '../Layer/presentationHold';

interface UseSelectorPresentationOptions {
presentation: AdaptivePresentation;
Expand Down Expand Up @@ -57,14 +61,20 @@ export function useSelectorPresentation({
triggerRef,
}: UseSelectorPresentationOptions): SelectorPresentationController {
const resolvedPresentation = useAdaptivePresentation(presentation);
const activePresentationRef =
useRef<ResolvedAdaptivePresentation>(resolvedPresentation);
// The layer runtime's presentation holds (Layer/presentationHold): the
// presentation an opening chose, taken as it shows; and the sheet, held from
// the render that opens it until its close settles. BottomSheet owns its
// exit animation and final-focus handoff, so the sheet stays mounted after
// the controlled open state turns false until that handoff reaches the
// trigger; then consumers may remove the hidden surface tree.
const [activePresentationHold] = useState(() =>
createPresentationHold<ResolvedAdaptivePresentation>(),
);
const [isSheetOpen, setIsSheetOpen] = useState(false);
const isSheetOpenRef = useRef(false);
// BottomSheet owns its exit animation and final-focus handoff. Keep it mounted
// after the controlled open state turns false until that handoff reaches the
// trigger, then consumers may remove the hidden surface tree.
const [isSheetPresented, setIsSheetPresented] = useState(false);
const {isHeld: isSheetPresented, release: releaseSheet} = usePresentationHold(
{isOpen: isSheetOpen, presentation: true},
);
const onHideRef = useRef(onHide);
const onShowRef = useRef(onShow);
const didShowPopoverRef = useRef(false);
Expand Down Expand Up @@ -95,20 +105,25 @@ export function useSelectorPresentation({
const {hide: hidePopover, show: showPopover} = popover;

const show = useCallback((): boolean => {
activePresentationRef.current = resolvedPresentation;
activePresentationHold.take(resolvedPresentation);
if (resolvedPresentation === 'bottom-sheet') {
resetFocusReturn();
onShowRef.current();
isSheetOpenRef.current = true;
setIsSheetPresented(true);
setIsSheetOpen(true);
return true;
}
setIsSheetPresented(false);
releaseSheet();
didShowPopoverRef.current = false;
showPopover();
return didShowPopoverRef.current;
}, [resetFocusReturn, resolvedPresentation, showPopover]);
}, [
activePresentationHold,
releaseSheet,
resetFocusReturn,
resolvedPresentation,
showPopover,
]);

const hide = useCallback(() => {
if (isSheetOpenRef.current) {
Expand All @@ -126,15 +141,17 @@ export function useSelectorPresentation({
onFocusReturnTargetFocus();
if (
!isSheetOpenRef.current &&
activePresentationRef.current === 'bottom-sheet'
activePresentationHold.current() === 'bottom-sheet'
) {
// BottomSheet focuses finalFocusRef only after its exit completes. That
// focus event is the lifecycle signal that the retained sheet can now
// leave the tree without dropping focus to the document body.
setIsSheetPresented(false);
// leave the tree without dropping focus to the document body: its
// close has settled.
activePresentationHold.release();
releaseSheet();
}
},
[onFocusReturnTargetFocus],
[activePresentationHold, onFocusReturnTargetFocus, releaseSheet],
);

const handleSheetOpenChange = useCallback(
Expand All @@ -151,7 +168,7 @@ export function useSelectorPresentation({
const isOpen = popover.isOpen || isSheetOpen;
const activePresentation =
isOpen || isSheetPresented
? activePresentationRef.current
? (activePresentationHold.current() ?? resolvedPresentation)
: resolvedPresentation;

return {
Expand Down