diff --git a/global.d.ts b/global.d.ts new file mode 100644 index 0000000..0d3572f --- /dev/null +++ b/global.d.ts @@ -0,0 +1,9 @@ +// global.d.ts +declare namespace JSX { + interface IntrinsicElements { + 'custom-element': React.DetailedHTMLProps< + React.HTMLAttributes & { class?: string }, + HTMLElement + >; + } +} diff --git a/src/index.tsx b/src/index.tsx index dce5f96..081282c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -272,20 +272,23 @@ export function generateTrigger( const originChildProps = child?.props || {}; const cloneProps: typeof originChildProps = {}; + const inContainer = (target: Element, container: Element) => { + return ( + target === container || + container.contains(target) || + getShadowRoot(container)?.host === target || + container.contains(getShadowRoot(target)?.host) + ); + }; + const inPopupOrChild = useEvent((ele: EventTarget) => { const childDOM = targetEle; + const eleInContainer = inContainer.bind(null, ele as Element); return ( - childDOM?.contains(ele as HTMLElement) || - getShadowRoot(childDOM)?.host === ele || - ele === childDOM || - popupEle?.contains(ele as HTMLElement) || - getShadowRoot(popupEle)?.host === ele || - ele === popupEle || - Object.values(subPopupElements.current).some( - (subPopupEle) => - subPopupEle?.contains(ele as HTMLElement) || ele === subPopupEle, - ) + eleInContainer(childDOM) || + eleInContainer(popupEle) || + Object.values(subPopupElements.current).some(eleInContainer) ); }); diff --git a/tests/shadow.test.tsx b/tests/shadow.test.tsx index b7e0580..e2cebf4 100644 --- a/tests/shadow.test.tsx +++ b/tests/shadow.test.tsx @@ -198,3 +198,122 @@ describe('Trigger.Shadow', () => { errSpy.mockRestore(); }); }); + +describe('Popup.Shadow', () => { + beforeEach(() => { + resetWarned(); + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + class CustomElement extends HTMLElement { + disconnectedCallback() {} + connectedCallback() { + const shadowRoot = this.attachShadow({ + mode: 'open', + }); + const container = document.createElement('div'); + shadowRoot.appendChild(container); + container.classList.add('shadow-container'); + container.innerHTML = `
Hello World
`; + } + } + + customElements.define('custom-element', CustomElement); + + it('should not close the popup when click the shadow content in the popup element', async () => { + const container = document.createElement('div'); + document.body.appendChild(container); + + act(() => { + createRoot(container).render( + <> +
outer
+ } + > +

+ + , + ); + }); + + await awaitFakeTimer(); + + // Click to show + fireEvent.click(document.querySelector('.target')); + await awaitFakeTimer(); + expect(document.querySelector('.popup')).toBeTruthy(); + + // Click outside to hide + fireEvent.mouseDown(document.querySelector('.outer')); + await awaitFakeTimer(); + expect(document.querySelector('.popup')).toBeFalsy(); + + // Click to show again + fireEvent.click(document.querySelector('.target')); + await awaitFakeTimer(); + expect(document.querySelector('.popup')).toBeTruthy(); + + // Click on popup element should not hide + fireEvent.mouseDown(document.querySelector('.popup')); + await awaitFakeTimer(); + expect(document.querySelector('.popup')).toBeTruthy(); + + // Click on shadow content should not hide + const popup = document.querySelector('.popup'); + fireEvent.mouseDown(popup.shadowRoot.querySelector('.shadow-content')); + await awaitFakeTimer(); + expect(document.querySelector('.popup')).toBeTruthy(); + }); + + it('should works with custom element trigger', async () => { + const container = document.createElement('div'); + document.body.innerHTML = ''; + document.body.appendChild(container); + + act(() => { + createRoot(container).render( + <> +

outer
+ } + > + + + , + ); + }); + + await awaitFakeTimer(); + + // Click to show + const target = document.querySelector('.target'); + fireEvent.click(target); + await awaitFakeTimer(); + expect(document.querySelector('.popup')).toBeTruthy(); + + // Click outside to hide + fireEvent.mouseDown(document.querySelector('.outer')); + await awaitFakeTimer(); + expect(document.querySelector('.popup')).toBeFalsy(); + + // Click shadow content to show + fireEvent.click(target.shadowRoot.querySelector('.shadow-content')); + await awaitFakeTimer(); + expect(document.querySelector('.popup')).toBeTruthy(); + + // Click on shadow content should not hide + const popup = document.querySelector('.popup'); + fireEvent.mouseDown(popup.shadowRoot.querySelector('.shadow-content')); + await awaitFakeTimer(); + expect(document.querySelector('.popup')).toBeTruthy(); + }); +});