diff --git a/.changeset/tidy-layers-restore.md b/.changeset/tidy-layers-restore.md new file mode 100644 index 0000000000..115fc8b93e --- /dev/null +++ b/.changeset/tidy-layers-restore.md @@ -0,0 +1,5 @@ +--- +'@radix-ui/react-dismissable-layer': patch +--- + +Fix body pointer-events cleanup when outside pointer events are disabled. diff --git a/packages/react/dismissable-layer/src/dismissable-layer.test.tsx b/packages/react/dismissable-layer/src/dismissable-layer.test.tsx new file mode 100644 index 0000000000..005f238fde --- /dev/null +++ b/packages/react/dismissable-layer/src/dismissable-layer.test.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { cleanup, render } from '@testing-library/react'; +import { afterEach, describe, it, expect } from 'vitest'; +import { DismissableLayer } from './dismissable-layer'; + +describe('DismissableLayer', () => { + afterEach(() => { + cleanup(); + document.body.style.pointerEvents = ''; + }); + + it('restores body pointer events after a disabled layer is removed from the stack', () => { + document.body.style.pointerEvents = 'auto'; + + const { rerender } = render( + , + ); + + expect(document.body.style.pointerEvents).toBe('none'); + + rerender(); + expect(document.body.style.pointerEvents).toBe('none'); + + rerender(); + expect(document.body.style.pointerEvents).toBe('auto'); + }); +}); + +function TestLayers({ + firstLayerDisabled, + secondLayerOpen, +}: { + firstLayerDisabled: boolean; + secondLayerOpen: boolean; +}) { + return ( + <> + + {secondLayerOpen ? : null} + + ); +} diff --git a/packages/react/dismissable-layer/src/dismissable-layer.tsx b/packages/react/dismissable-layer/src/dismissable-layer.tsx index 9869f91992..96aad14f50 100644 --- a/packages/react/dismissable-layer/src/dismissable-layer.tsx +++ b/packages/react/dismissable-layer/src/dismissable-layer.tsx @@ -22,6 +22,15 @@ const DismissableLayerContext = React.createContext({ branches: new Set(), }); +function restoreBodyPointerEvents( + ownerDocument: Document, + context: React.ContextType, +) { + if (context.layersWithOutsidePointerEventsDisabled.size === 0) { + ownerDocument.body.style.pointerEvents = originalBodyPointerEvents; + } +} + type DismissableLayerElement = React.ComponentRef; type PrimitiveDivProps = React.ComponentPropsWithoutRef; interface DismissableLayerProps extends PrimitiveDivProps { @@ -121,11 +130,10 @@ const DismissableLayer = React.forwardRef { - if ( - disableOutsidePointerEvents && - context.layersWithOutsidePointerEventsDisabled.size === 1 - ) { - ownerDocument.body.style.pointerEvents = originalBodyPointerEvents; + if (disableOutsidePointerEvents) { + context.layersWithOutsidePointerEventsDisabled.delete(node); + restoreBodyPointerEvents(ownerDocument, context); + dispatchUpdate(); } }; }, [node, ownerDocument, disableOutsidePointerEvents, context]); @@ -141,9 +149,10 @@ const DismissableLayer = React.forwardRef { const handleUpdate = () => force({});