Skip to content

Commit e6ac87a

Browse files
Pesven-dnvPesven
and
Pesven
authored
Find element in multilevel shadows (#471)
Co-authored-by: Pesven <Per.Svensson@dnvgl.com>
1 parent 8e0422d commit e6ac87a

File tree

4 files changed

+113
-7
lines changed

4 files changed

+113
-7
lines changed

docs/examples/shadow.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,21 @@ const Demo = () => {
5858

5959
export default () => {
6060
React.useEffect(() => {
61+
const wrapperHost = document.createElement('div');
62+
const wrapperShadowRoot = wrapperHost.attachShadow({
63+
mode: 'open',
64+
delegatesFocus: false,
65+
});
66+
document.body.appendChild(wrapperHost);
67+
6168
const host = document.createElement('div');
62-
document.body.appendChild(host);
69+
wrapperShadowRoot.appendChild(host);
6370
host.style.background = 'rgba(255,0,0,0.1)';
6471
const shadowRoot = host.attachShadow({
6572
mode: 'open',
6673
delegatesFocus: false,
6774
});
75+
6876
const container = document.createElement('div');
6977
shadowRoot.appendChild(container);
7078

src/hooks/useWinClick.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ export default function useWinClick(
1919
// Click to hide is special action since click popup element should not hide
2020
React.useEffect(() => {
2121
if (clickToHide && popupEle && (!mask || maskClosable)) {
22-
const onTriggerClose = ({ target }: MouseEvent) => {
23-
if (openRef.current && !inPopupOrChild(target)) {
22+
const onTriggerClose = (e: MouseEvent) => {
23+
if (
24+
openRef.current &&
25+
!inPopupOrChild(e.composedPath?.()?.[0] || e.target)
26+
) {
2427
triggerOpen(false);
2528
}
2629
};

src/index.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -272,18 +272,19 @@ export function generateTrigger(
272272
const originChildProps = child?.props || {};
273273
const cloneProps: typeof originChildProps = {};
274274

275-
const inPopupOrChild = useEvent((ele: any) => {
275+
const inPopupOrChild = useEvent((ele: EventTarget) => {
276276
const childDOM = targetEle;
277277

278278
return (
279-
childDOM?.contains(ele) ||
279+
childDOM?.contains(ele as HTMLElement) ||
280280
getShadowRoot(childDOM)?.host === ele ||
281281
ele === childDOM ||
282-
popupEle?.contains(ele) ||
282+
popupEle?.contains(ele as HTMLElement) ||
283283
getShadowRoot(popupEle)?.host === ele ||
284284
ele === popupEle ||
285285
Object.values(subPopupElements.current).some(
286-
(subPopupEle) => subPopupEle?.contains(ele) || ele === subPopupEle,
286+
(subPopupEle) =>
287+
subPopupEle?.contains(ele as HTMLElement) || ele === subPopupEle,
287288
)
288289
);
289290
});

tests/shadow.test.tsx

+94
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,34 @@ describe('Trigger.Shadow', () => {
5555
return shadowRoot;
5656
};
5757

58+
const renderMultiLevelShadow = (props?: any) => {
59+
const noRelatedSpan = document.createElement('span');
60+
document.body.appendChild(noRelatedSpan);
61+
62+
const wrapperHost = document.createElement('div');
63+
const wrapperShadowRoot = wrapperHost.attachShadow({
64+
mode: 'open',
65+
delegatesFocus: false,
66+
});
67+
document.body.appendChild(wrapperHost);
68+
69+
const host = document.createElement('div');
70+
wrapperShadowRoot.appendChild(host);
71+
72+
const shadowRoot = host.attachShadow({
73+
mode: 'open',
74+
delegatesFocus: false,
75+
});
76+
const container = document.createElement('div');
77+
shadowRoot.appendChild(container);
78+
79+
act(() => {
80+
createRoot(container).render(<Demo {...props} />);
81+
});
82+
83+
return shadowRoot;
84+
};
85+
5886
it('popup not in the same shadow', async () => {
5987
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
6088
const shadowRoot = renderShadow();
@@ -103,4 +131,70 @@ describe('Trigger.Shadow', () => {
103131
expect(errSpy).not.toHaveBeenCalled();
104132
errSpy.mockRestore();
105133
});
134+
135+
it('click on target in shadow should not close popup', async () => {
136+
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
137+
const shadowRoot = renderShadow({
138+
getPopupContainer: (item: HTMLElement) => item.parentElement,
139+
autoDestroy: true,
140+
});
141+
142+
await awaitFakeTimer();
143+
144+
// Click to show
145+
fireEvent.click(shadowRoot.querySelector('.target'));
146+
await awaitFakeTimer();
147+
expect(shadowRoot.querySelector('.bamboo')).toBeTruthy();
148+
149+
// Click on target
150+
fireEvent.mouseDown(shadowRoot.querySelector('.bamboo'));
151+
await awaitFakeTimer();
152+
expect(shadowRoot.querySelector('.bamboo')).toBeTruthy();
153+
154+
expect(errSpy).not.toHaveBeenCalled();
155+
errSpy.mockRestore();
156+
});
157+
158+
it('click on target with multilevel shadows should not close popup', async () => {
159+
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
160+
const shadowRoot = renderMultiLevelShadow({
161+
getPopupContainer: (item: HTMLElement) => item.parentElement,
162+
autoDestroy: true,
163+
});
164+
165+
await awaitFakeTimer();
166+
167+
// Click to show
168+
fireEvent.click(shadowRoot.querySelector('.target'));
169+
await awaitFakeTimer();
170+
expect(shadowRoot.querySelector('.bamboo')).toBeTruthy();
171+
172+
// Click outside to hide
173+
fireEvent.mouseDown(document.body.firstChild);
174+
await awaitFakeTimer();
175+
expect(shadowRoot.querySelector('.bamboo')).toBeFalsy();
176+
177+
// Click to show again
178+
fireEvent.click(shadowRoot.querySelector('.target'));
179+
await awaitFakeTimer();
180+
expect(shadowRoot.querySelector('.bamboo')).toBeTruthy();
181+
182+
// Click in side shadow to hide
183+
fireEvent.mouseDown(shadowRoot.querySelector('.little'));
184+
await awaitFakeTimer();
185+
expect(shadowRoot.querySelector('.bamboo')).toBeFalsy();
186+
187+
// Click to show again
188+
fireEvent.click(shadowRoot.querySelector('.target'));
189+
await awaitFakeTimer();
190+
expect(shadowRoot.querySelector('.bamboo')).toBeTruthy();
191+
192+
// Click on target should not hide
193+
fireEvent.mouseDown(shadowRoot.querySelector('.bamboo'));
194+
await awaitFakeTimer();
195+
expect(shadowRoot.querySelector('.bamboo')).toBeTruthy();
196+
197+
expect(errSpy).not.toHaveBeenCalled();
198+
errSpy.mockRestore();
199+
});
106200
});

0 commit comments

Comments
 (0)