diff --git a/src/useRootClose.ts b/src/useRootClose.ts
index 1d5b4ad5..66fc19b6 100644
--- a/src/useRootClose.ts
+++ b/src/useRootClose.ts
@@ -66,7 +66,7 @@ function useRootClose(
!currentTarget ||
isModifiedEvent(e) ||
!isLeftClickEvent(e) ||
- !!contains(currentTarget, e.target);
+ !!contains(currentTarget, e.composedPath?.()[0] ?? e.target);
},
[ref],
);
diff --git a/test/useRootCloseSpec.js b/test/useRootCloseSpec.js
index 3b96b201..c8883bd6 100644
--- a/test/useRootCloseSpec.js
+++ b/test/useRootCloseSpec.js
@@ -7,190 +7,220 @@ import { mount } from 'enzyme';
import useRootClose from '../src/useRootClose';
const escapeKeyCode = 27;
+const configs = [
+ {
+ description: '',
+ useShadowRoot: false,
+ },
+ {
+ description: 'with shadow root',
+ useShadowRoot: true,
+ },
+];
+// Wrap simulant's created event to add composed: true, which is the default
+// for most events.
+const fire = (node, event, params) => {
+ const simulatedEvent = simulant(event, params);
+ const fixedEvent = new simulatedEvent.constructor(simulatedEvent.type, {
+ bubbles: simulatedEvent.bubbles,
+ button: simulatedEvent.button,
+ cancelable: simulatedEvent.cancelable,
+ composed: true,
+ });
+ fixedEvent.keyCode = simulatedEvent.keyCode;
+ node.dispatchEvent(fixedEvent);
+ return fixedEvent;
+};
+
+configs.map((config) =>
+ // eslint-disable-next-line mocha/no-setup-in-describe
+ describe(`useRootClose ${config.description}`, () => {
+ let attachTo, renderRoot, myDiv;
+
+ beforeEach(() => {
+ renderRoot = document.createElement('div');
+ if (config.useShadowRoot) {
+ renderRoot.attachShadow({ mode: 'open' });
+ }
+ document.body.appendChild(renderRoot);
+ attachTo = config.useShadowRoot ? renderRoot.shadowRoot : renderRoot;
+ myDiv = () => attachTo.querySelector('#my-div');
+ });
-describe('useRootClose', () => {
- let attachTo;
+ afterEach(() => {
+ ReactDOM.unmountComponentAtNode(renderRoot);
+ document.body.removeChild(renderRoot);
+ });
- beforeEach(() => {
- attachTo = document.createElement('div');
- document.body.appendChild(attachTo);
- });
+ describe('using default event', () => {
+ // eslint-disable-next-line mocha/no-setup-in-describe
+ shouldCloseOn(undefined, 'click');
+ });
- afterEach(() => {
- ReactDOM.unmountComponentAtNode(attachTo);
- document.body.removeChild(attachTo);
- });
+ describe('using click event', () => {
+ // eslint-disable-next-line mocha/no-setup-in-describe
+ shouldCloseOn('click', 'click');
+ });
- describe('using default event', () => {
- // eslint-disable-next-line mocha/no-setup-in-describe
- shouldCloseOn(undefined, 'click');
- });
+ describe('using mousedown event', () => {
+ // eslint-disable-next-line mocha/no-setup-in-describe
+ shouldCloseOn('mousedown', 'mousedown');
+ });
- describe('using click event', () => {
- // eslint-disable-next-line mocha/no-setup-in-describe
- shouldCloseOn('click', 'click');
- });
+ function shouldCloseOn(clickTrigger, eventName) {
+ function Wrapper({ onRootClose, disabled }) {
+ const ref = useRef();
+ useRootClose(ref, onRootClose, {
+ disabled,
+ clickTrigger,
+ });
- describe('using mousedown event', () => {
- // eslint-disable-next-line mocha/no-setup-in-describe
- shouldCloseOn('mousedown', 'mousedown');
- });
+ return (
+
+ hello there
+
+ );
+ }
+
+ it('should close when clicked outside', () => {
+ let spy = sinon.spy();
+
+ mount(, { attachTo });
- function shouldCloseOn(clickTrigger, eventName) {
- function Wrapper({ onRootClose, disabled }) {
- const ref = useRef();
- useRootClose(ref, onRootClose, {
- disabled,
- clickTrigger,
+ fire(myDiv(), eventName);
+
+ expect(spy).to.not.have.been.called;
+
+ fire(document.body, eventName);
+
+ expect(spy).to.have.been.calledOnce;
+
+ expect(spy.getCall(0).args[0].type).to.be.oneOf(['click', 'mousedown']);
});
- return (
-
- hello there
-
- );
- }
+ it('should not close when right-clicked outside', () => {
+ let spy = sinon.spy();
+ mount(, { attachTo });
- it('should close when clicked outside', () => {
- let spy = sinon.spy();
+ fire(myDiv(), eventName, { button: 1 });
- mount(, { attachTo });
+ expect(spy).to.not.have.been.called;
- simulant.fire(document.getElementById('my-div'), eventName);
+ fire(document.body, eventName, { button: 1 });
- expect(spy).to.not.have.been.called;
+ expect(spy).to.not.have.been.called;
+ });
- simulant.fire(document.body, eventName);
+ it('should not close when disabled', () => {
+ let spy = sinon.spy();
+ mount(, { attachTo });
- expect(spy).to.have.been.calledOnce;
+ fire(myDiv(), eventName);
- expect(spy.getCall(0).args[0].type).to.be.oneOf(['click', 'mousedown']);
- });
+ expect(spy).to.not.have.been.called;
- it('should not close when right-clicked outside', () => {
- let spy = sinon.spy();
- mount(, { attachTo });
+ fire(document.body, eventName);
- simulant.fire(document.getElementById('my-div'), eventName, {
- button: 1,
+ expect(spy).to.not.have.been.called;
});
- expect(spy).to.not.have.been.called;
+ it('should close when inside another RootCloseWrapper', () => {
+ let outerSpy = sinon.spy();
+ let innerSpy = sinon.spy();
- simulant.fire(document.body, eventName, { button: 1 });
+ function Inner() {
+ const ref = useRef();
+ useRootClose(ref, innerSpy, { clickTrigger });
- expect(spy).to.not.have.been.called;
- });
+ return (
+
+ hello there
+
+ );
+ }
- it('should not close when disabled', () => {
- let spy = sinon.spy();
- mount(, { attachTo });
+ function Outer() {
+ const ref = useRef();
+ useRootClose(ref, outerSpy, { clickTrigger });
- simulant.fire(document.getElementById('my-div'), eventName);
+ return (
+
+ );
+ }
- expect(spy).to.not.have.been.called;
+ mount(, { attachTo });
- simulant.fire(document.body, eventName);
+ fire(myDiv(), eventName);
- expect(spy).to.not.have.been.called;
- });
+ expect(outerSpy).to.have.not.been.called;
+ expect(innerSpy).to.have.been.calledOnce;
- it('should close when inside another RootCloseWrapper', () => {
- let outerSpy = sinon.spy();
- let innerSpy = sinon.spy();
+ expect(innerSpy.getCall(0).args[0].type).to.be.oneOf([
+ 'click',
+ 'mousedown',
+ ]);
+ });
+ }
- function Inner() {
+ describe('using keyup event', () => {
+ function Wrapper({ children, onRootClose, event: clickTrigger }) {
const ref = useRef();
- useRootClose(ref, innerSpy, { clickTrigger });
+ useRootClose(ref, onRootClose, { clickTrigger });
return (
-
- hello there
+
+ {children}
);
}
- function Outer() {
- const ref = useRef();
- useRootClose(ref, outerSpy, { clickTrigger });
-
- return (
-
+ it('should close when escape keyup', () => {
+ let spy = sinon.spy();
+ mount(
+
hello there
-
-
+ ,
);
- }
-
- mount(
, { attachTo });
-
- simulant.fire(document.getElementById('my-div'), eventName);
- expect(outerSpy).to.have.not.been.called;
- expect(innerSpy).to.have.been.calledOnce;
+ expect(spy).to.not.have.been.called;
- expect(innerSpy.getCall(0).args[0].type).to.be.oneOf([
- 'click',
- 'mousedown',
- ]);
- });
- }
-
- describe('using keyup event', () => {
- function Wrapper({ children, onRootClose, event: clickTrigger }) {
- const ref = useRef();
- useRootClose(ref, onRootClose, { clickTrigger });
-
- return (
-
- {children}
-
- );
- }
-
- it('should close when escape keyup', () => {
- let spy = sinon.spy();
- mount(
-
- hello there
- ,
- );
-
- expect(spy).to.not.have.been.called;
+ fire(document.body, 'keyup', { keyCode: escapeKeyCode });
- simulant.fire(document.body, 'keyup', { keyCode: escapeKeyCode });
-
- expect(spy).to.have.been.calledOnce;
-
- expect(spy.getCall(0).args.length).to.be.equal(1);
- expect(spy.getCall(0).args[0].keyCode).to.be.equal(escapeKeyCode);
- expect(spy.getCall(0).args[0].type).to.be.equal('keyup');
- });
+ expect(spy).to.have.been.calledOnce;
- it('should close when inside another RootCloseWrapper', () => {
- let outerSpy = sinon.spy();
- let innerSpy = sinon.spy();
+ expect(spy.getCall(0).args.length).to.be.equal(1);
+ expect(spy.getCall(0).args[0].keyCode).to.be.equal(escapeKeyCode);
+ expect(spy.getCall(0).args[0].type).to.be.equal('keyup');
+ });
- mount(
-
-
-
hello there
-
- hello there
-
-
- ,
- );
+ it('should close when inside another RootCloseWrapper', () => {
+ let outerSpy = sinon.spy();
+ let innerSpy = sinon.spy();
+
+ mount(
+
+
+
hello there
+
+ hello there
+
+
+ ,
+ );
- simulant.fire(document.body, 'keyup', { keyCode: escapeKeyCode });
+ fire(document.body, 'keyup', { keyCode: escapeKeyCode });
- // TODO: Update to match expectations.
- // expect(outerSpy).to.have.not.been.called;
- expect(innerSpy).to.have.been.calledOnce;
+ // TODO: Update to match expectations.
+ // expect(outerSpy).to.have.not.been.called;
+ expect(innerSpy).to.have.been.calledOnce;
- expect(innerSpy.getCall(0).args.length).to.be.equal(1);
- expect(innerSpy.getCall(0).args[0].keyCode).to.be.equal(escapeKeyCode);
- expect(innerSpy.getCall(0).args[0].type).to.be.equal('keyup');
+ expect(innerSpy.getCall(0).args.length).to.be.equal(1);
+ expect(innerSpy.getCall(0).args[0].keyCode).to.be.equal(escapeKeyCode);
+ expect(innerSpy.getCall(0).args[0].type).to.be.equal('keyup');
+ });
});
- });
-});
+ }),
+);