Skip to content

Commit 0c29d70

Browse files
iahuafc163
authored andcommitted
fix: click on shadowDOM popup should not close it (#480)
Co-authored-by: afc163 <afc163@gmail.com>
1 parent e4b64b2 commit 0c29d70

File tree

3 files changed

+141
-10
lines changed

3 files changed

+141
-10
lines changed

global.d.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// global.d.ts
2+
declare namespace JSX {
3+
interface IntrinsicElements {
4+
'custom-element': React.DetailedHTMLProps<
5+
React.HTMLAttributes<HTMLElement> & { class?: string },
6+
HTMLElement
7+
>;
8+
}
9+
}

src/index.tsx

+13-10
Original file line numberDiff line numberDiff line change
@@ -272,20 +272,23 @@ export function generateTrigger(
272272
const originChildProps = child?.props || {};
273273
const cloneProps: typeof originChildProps = {};
274274

275+
const inContainer = (target: Element, container: Element) => {
276+
return (
277+
target === container ||
278+
container.contains(target) ||
279+
getShadowRoot(container)?.host === target ||
280+
container.contains(getShadowRoot(target)?.host)
281+
);
282+
};
283+
275284
const inPopupOrChild = useEvent((ele: EventTarget) => {
276285
const childDOM = targetEle;
286+
const eleInContainer = inContainer.bind(null, ele as Element);
277287

278288
return (
279-
childDOM?.contains(ele as HTMLElement) ||
280-
getShadowRoot(childDOM)?.host === ele ||
281-
ele === childDOM ||
282-
popupEle?.contains(ele as HTMLElement) ||
283-
getShadowRoot(popupEle)?.host === ele ||
284-
ele === popupEle ||
285-
Object.values(subPopupElements.current).some(
286-
(subPopupEle) =>
287-
subPopupEle?.contains(ele as HTMLElement) || ele === subPopupEle,
288-
)
289+
eleInContainer(childDOM) ||
290+
eleInContainer(popupEle) ||
291+
Object.values(subPopupElements.current).some(eleInContainer)
289292
);
290293
});
291294

tests/shadow.test.tsx

+119
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,122 @@ describe('Trigger.Shadow', () => {
198198
errSpy.mockRestore();
199199
});
200200
});
201+
202+
describe('Popup.Shadow', () => {
203+
beforeEach(() => {
204+
resetWarned();
205+
jest.useFakeTimers();
206+
});
207+
208+
afterEach(() => {
209+
jest.useRealTimers();
210+
});
211+
212+
class CustomElement extends HTMLElement {
213+
disconnectedCallback() {}
214+
connectedCallback() {
215+
const shadowRoot = this.attachShadow({
216+
mode: 'open',
217+
});
218+
const container = document.createElement('div');
219+
shadowRoot.appendChild(container);
220+
container.classList.add('shadow-container');
221+
container.innerHTML = `<div class="shadow-content">Hello World</div>`;
222+
}
223+
}
224+
225+
customElements.define('custom-element', CustomElement);
226+
227+
it('should not close the popup when click the shadow content in the popup element', async () => {
228+
const container = document.createElement('div');
229+
document.body.appendChild(container);
230+
231+
act(() => {
232+
createRoot(container).render(
233+
<>
234+
<div className="outer">outer</div>
235+
<Trigger
236+
action={['click']}
237+
autoDestroy
238+
popup={<custom-element class="popup" />}
239+
>
240+
<p className="target" />
241+
</Trigger>
242+
</>,
243+
);
244+
});
245+
246+
await awaitFakeTimer();
247+
248+
// Click to show
249+
fireEvent.click(document.querySelector('.target'));
250+
await awaitFakeTimer();
251+
expect(document.querySelector('.popup')).toBeTruthy();
252+
253+
// Click outside to hide
254+
fireEvent.mouseDown(document.querySelector('.outer'));
255+
await awaitFakeTimer();
256+
expect(document.querySelector('.popup')).toBeFalsy();
257+
258+
// Click to show again
259+
fireEvent.click(document.querySelector('.target'));
260+
await awaitFakeTimer();
261+
expect(document.querySelector('.popup')).toBeTruthy();
262+
263+
// Click on popup element should not hide
264+
fireEvent.mouseDown(document.querySelector('.popup'));
265+
await awaitFakeTimer();
266+
expect(document.querySelector('.popup')).toBeTruthy();
267+
268+
// Click on shadow content should not hide
269+
const popup = document.querySelector('.popup');
270+
fireEvent.mouseDown(popup.shadowRoot.querySelector('.shadow-content'));
271+
await awaitFakeTimer();
272+
expect(document.querySelector('.popup')).toBeTruthy();
273+
});
274+
275+
it('should works with custom element trigger', async () => {
276+
const container = document.createElement('div');
277+
document.body.innerHTML = '';
278+
document.body.appendChild(container);
279+
280+
act(() => {
281+
createRoot(container).render(
282+
<>
283+
<div className="outer">outer</div>
284+
<Trigger
285+
action={['click']}
286+
autoDestroy
287+
popup={<custom-element class="popup" />}
288+
>
289+
<custom-element class="target" />
290+
</Trigger>
291+
</>,
292+
);
293+
});
294+
295+
await awaitFakeTimer();
296+
297+
// Click to show
298+
const target = document.querySelector('.target');
299+
fireEvent.click(target);
300+
await awaitFakeTimer();
301+
expect(document.querySelector('.popup')).toBeTruthy();
302+
303+
// Click outside to hide
304+
fireEvent.mouseDown(document.querySelector('.outer'));
305+
await awaitFakeTimer();
306+
expect(document.querySelector('.popup')).toBeFalsy();
307+
308+
// Click shadow content to show
309+
fireEvent.click(target.shadowRoot.querySelector('.shadow-content'));
310+
await awaitFakeTimer();
311+
expect(document.querySelector('.popup')).toBeTruthy();
312+
313+
// Click on shadow content should not hide
314+
const popup = document.querySelector('.popup');
315+
fireEvent.mouseDown(popup.shadowRoot.querySelector('.shadow-content'));
316+
await awaitFakeTimer();
317+
expect(document.querySelector('.popup')).toBeTruthy();
318+
});
319+
});

0 commit comments

Comments
 (0)