Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/react-doctor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ jobs:
persist-credentials: false
- uses: millionco/react-doctor@0b4f4f4bd248a154e64eb508a48347f71154b3f3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
version: 0.7.4
5 changes: 3 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,8 @@ export function generateTrigger(
popupVisible,
);

const mergedOpen = (internalOpen || false) && !disabled;
const rawOpen = internalOpen || false;
const mergedOpen = rawOpen && !disabled;

// ========================== Children ==========================
const child = React.useMemo(() => {
Expand Down Expand Up @@ -387,7 +388,7 @@ export function generateTrigger(

const internalTriggerOpen = useEvent((nextOpen: boolean) => {
flushSync(() => {
if (mergedOpen !== nextOpen) {
if (rawOpen !== nextOpen) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When disabled is true, mergedOpen is always false. Currently, openRef is assigned to mergedOpen (on lines 386-387):

const openRef = React.useRef(mergedOpen);
openRef.current = mergedOpen;

Because of this, when disabled is true, openRef.current is always false. This causes issues with toggle actions (such as click, touch, and contextMenu) when the trigger is disabled. For example, in the onClick handler:

if (openRef.current && clickToHide) {
  triggerOpen(false);
} else if (!openRef.current && clickToShow) {
  setMousePosByEvent(event);
  triggerOpen(true);
}

Since openRef.current is always false when disabled, clicking the target will always attempt to call triggerOpen(true) and can never call triggerOpen(false). This means the user cannot toggle the underlying open state back to false by clicking while disabled.

To fix this and ensure consistency with the new rawOpen logic, openRef should be updated to track rawOpen instead of mergedOpen:

const openRef = React.useRef(rawOpen);
openRef.current = rawOpen;

setInternalOpen(nextOpen);
onOpenChange?.(nextOpen);
onPopupVisibleChange?.(nextOpen);
Expand Down
29 changes: 29 additions & 0 deletions tests/basic.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,35 @@ describe('Trigger.Basic', () => {
expect(isPopupHidden()).toBeFalsy();
expect(onOpenChange).not.toHaveBeenCalled();
});

it('updates open state when mouse leaves while disabled', () => {
const onOpenChange = jest.fn();
const Demo = ({ disabled = false }) => (
<Trigger
action={['hover']}
disabled={disabled}
onOpenChange={onOpenChange}
popup={<strong>trigger</strong>}
>
<div className="target">hover</div>
</Trigger>
);

const { container, rerender } = render(<Demo />);

trigger(container, '.target', 'mouseEnter');
expect(isPopupHidden()).toBeFalsy();
onOpenChange.mockReset();

rerender(<Demo disabled />);
expect(isPopupHidden()).toBeTruthy();

trigger(container, '.target', 'mouseLeave');
expect(onOpenChange).toHaveBeenCalledWith(false);

rerender(<Demo />);
expect(isPopupHidden()).toBeTruthy();
});
});

it('contextMenu works', () => {
Expand Down
Loading