Skip to content

Commit

Permalink
[Popover] New stick behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
atomiks committed Dec 26, 2024
1 parent e014564 commit 48c6735
Showing 1 changed file with 43 additions and 21 deletions.
64 changes: 43 additions & 21 deletions packages/react/src/popover/root/usePopoverRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ export function usePopoverRoot(params: usePopoverRoot.Parameters): usePopoverRoo
const [triggerElement, setTriggerElement] = React.useState<Element | null>(null);
const [positionerElement, setPositionerElement] = React.useState<HTMLElement | null>(null);
const [openReason, setOpenReason] = React.useState<OpenChangeReason | null>(null);
const [clickEnabled, setClickEnabled] = React.useState(true);
const [hoverEnabled, setHoverEnabled] = React.useState(false);
const [stickIfOpen, setStickIfOpen] = React.useState(true);

const popupRef = React.useRef<HTMLElement>(null);
const clickEnabledTimeoutRef = React.useRef(-1);
const stickIfOpenTimeoutRef = React.useRef(-1);

const [open, setOpenUnwrapped] = useControlled({
controlled: externalOpen,
Expand All @@ -56,8 +57,14 @@ export function usePopoverRoot(params: usePopoverRoot.Parameters): usePopoverRoo
state: 'open',
});

if (!open && !clickEnabled) {
setClickEnabled(true);
if (!open) {
if (!stickIfOpen) {
setStickIfOpen(true);
}

if (!hoverEnabled) {
setHoverEnabled(true);
}
}

const onOpenChange = useEventCallback(onOpenChangeProp);
Expand Down Expand Up @@ -86,7 +93,7 @@ export function usePopoverRoot(params: usePopoverRoot.Parameters): usePopoverRoo

React.useEffect(() => {
return () => {
clearTimeout(clickEnabledTimeoutRef.current);
clearTimeout(stickIfOpenTimeoutRef.current);
};
}, []);

Expand All @@ -103,11 +110,11 @@ export function usePopoverRoot(params: usePopoverRoot.Parameters): usePopoverRoo
}

if (isHover) {
// Prevent impatient clicks from unexpectedly closing the popover.
setClickEnabled(false);
clearTimeout(clickEnabledTimeoutRef.current);
clickEnabledTimeoutRef.current = window.setTimeout(() => {
setClickEnabled(true);
// Only allow "patient" clicks to close the popover if it's open.
// If they clicked within 500ms of the popover opening, keep it open.
clearTimeout(stickIfOpenTimeoutRef.current);
stickIfOpenTimeoutRef.current = window.setTimeout(() => {
setStickIfOpen(false);
}, PATIENT_CLICK_THRESHOLD);

ReactDOM.flushSync(changeState);
Expand All @@ -126,7 +133,7 @@ export function usePopoverRoot(params: usePopoverRoot.Parameters): usePopoverRoo
const computedRestMs = delayWithDefault;

const hover = useHover(context, {
enabled: openOnHover,
enabled: openOnHover && hoverEnabled,
mouseOnly: true,
move: false,
handleClose: safePolygon(),
Expand All @@ -137,8 +144,7 @@ export function usePopoverRoot(params: usePopoverRoot.Parameters): usePopoverRoo
});

const click = useClick(context, {
enabled: clickEnabled,
stickIfOpen: false,
stickIfOpen,
});

const dismiss = useDismiss(context);
Expand All @@ -149,6 +155,24 @@ export function usePopoverRoot(params: usePopoverRoot.Parameters): usePopoverRoo

const { openMethod, triggerProps } = useOpenInteractionType(open);

const getTriggerProps = React.useCallback(
(externalProps?: GenericHTMLProps) =>
getReferenceProps(mergeReactProps(externalProps, triggerProps)),
[getReferenceProps, triggerProps],
);

const getPopupProps = React.useCallback(
(externalProps?: GenericHTMLProps) =>
getFloatingProps(
mergeReactProps(externalProps, {
onPointerDown() {
setHoverEnabled(false);
},
}),
),
[getFloatingProps],
);

return React.useMemo(
() => ({
open,
Expand All @@ -164,29 +188,27 @@ export function usePopoverRoot(params: usePopoverRoot.Parameters): usePopoverRoo
setTitleId,
descriptionId,
setDescriptionId,
getRootTriggerProps: (externalProps?: React.HTMLProps<Element>) =>
getReferenceProps(mergeReactProps(externalProps, triggerProps)),
getRootPopupProps: getFloatingProps,
getRootTriggerProps: getTriggerProps,
getRootPopupProps: getPopupProps,
floatingRootContext: context,
instantType,
openMethod,
openReason,
}),
[
mounted,
open,
setMounted,
setOpen,
mounted,
setMounted,
transitionStatus,
positionerElement,
titleId,
descriptionId,
getReferenceProps,
getFloatingProps,
getTriggerProps,
getPopupProps,
context,
instantType,
openMethod,
triggerProps,
openReason,
],
);
Expand Down

0 comments on commit 48c6735

Please sign in to comment.