Skip to content

Commit

Permalink
Find element in multilevel shadows (#471)
Browse files Browse the repository at this point in the history
Co-authored-by: Pesven <Per.Svensson@dnvgl.com>
  • Loading branch information
Psvensso and Pesven authored Aug 9, 2024
1 parent 8e0422d commit e6ac87a
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 7 deletions.
10 changes: 9 additions & 1 deletion docs/examples/shadow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,21 @@ const Demo = () => {

export default () => {
React.useEffect(() => {
const wrapperHost = document.createElement('div');
const wrapperShadowRoot = wrapperHost.attachShadow({
mode: 'open',
delegatesFocus: false,
});
document.body.appendChild(wrapperHost);

const host = document.createElement('div');
document.body.appendChild(host);
wrapperShadowRoot.appendChild(host);
host.style.background = 'rgba(255,0,0,0.1)';
const shadowRoot = host.attachShadow({
mode: 'open',
delegatesFocus: false,
});

const container = document.createElement('div');
shadowRoot.appendChild(container);

Expand Down
7 changes: 5 additions & 2 deletions src/hooks/useWinClick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ export default function useWinClick(
// Click to hide is special action since click popup element should not hide
React.useEffect(() => {
if (clickToHide && popupEle && (!mask || maskClosable)) {
const onTriggerClose = ({ target }: MouseEvent) => {
if (openRef.current && !inPopupOrChild(target)) {
const onTriggerClose = (e: MouseEvent) => {
if (
openRef.current &&
!inPopupOrChild(e.composedPath?.()?.[0] || e.target)
) {
triggerOpen(false);
}
};
Expand Down
9 changes: 5 additions & 4 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,18 +272,19 @@ export function generateTrigger(
const originChildProps = child?.props || {};
const cloneProps: typeof originChildProps = {};

const inPopupOrChild = useEvent((ele: any) => {
const inPopupOrChild = useEvent((ele: EventTarget) => {
const childDOM = targetEle;

return (
childDOM?.contains(ele) ||
childDOM?.contains(ele as HTMLElement) ||
getShadowRoot(childDOM)?.host === ele ||
ele === childDOM ||
popupEle?.contains(ele) ||
popupEle?.contains(ele as HTMLElement) ||
getShadowRoot(popupEle)?.host === ele ||
ele === popupEle ||
Object.values(subPopupElements.current).some(
(subPopupEle) => subPopupEle?.contains(ele) || ele === subPopupEle,
(subPopupEle) =>
subPopupEle?.contains(ele as HTMLElement) || ele === subPopupEle,
)
);
});
Expand Down
94 changes: 94 additions & 0 deletions tests/shadow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,34 @@ describe('Trigger.Shadow', () => {
return shadowRoot;
};

const renderMultiLevelShadow = (props?: any) => {
const noRelatedSpan = document.createElement('span');
document.body.appendChild(noRelatedSpan);

const wrapperHost = document.createElement('div');
const wrapperShadowRoot = wrapperHost.attachShadow({
mode: 'open',
delegatesFocus: false,
});
document.body.appendChild(wrapperHost);

const host = document.createElement('div');
wrapperShadowRoot.appendChild(host);

const shadowRoot = host.attachShadow({
mode: 'open',
delegatesFocus: false,
});
const container = document.createElement('div');
shadowRoot.appendChild(container);

act(() => {
createRoot(container).render(<Demo {...props} />);
});

return shadowRoot;
};

it('popup not in the same shadow', async () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const shadowRoot = renderShadow();
Expand Down Expand Up @@ -103,4 +131,70 @@ describe('Trigger.Shadow', () => {
expect(errSpy).not.toHaveBeenCalled();
errSpy.mockRestore();
});

it('click on target in shadow should not close popup', async () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const shadowRoot = renderShadow({
getPopupContainer: (item: HTMLElement) => item.parentElement,
autoDestroy: true,
});

await awaitFakeTimer();

// Click to show
fireEvent.click(shadowRoot.querySelector('.target'));
await awaitFakeTimer();
expect(shadowRoot.querySelector('.bamboo')).toBeTruthy();

// Click on target
fireEvent.mouseDown(shadowRoot.querySelector('.bamboo'));
await awaitFakeTimer();
expect(shadowRoot.querySelector('.bamboo')).toBeTruthy();

expect(errSpy).not.toHaveBeenCalled();
errSpy.mockRestore();
});

it('click on target with multilevel shadows should not close popup', async () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const shadowRoot = renderMultiLevelShadow({
getPopupContainer: (item: HTMLElement) => item.parentElement,
autoDestroy: true,
});

await awaitFakeTimer();

// Click to show
fireEvent.click(shadowRoot.querySelector('.target'));
await awaitFakeTimer();
expect(shadowRoot.querySelector('.bamboo')).toBeTruthy();

// Click outside to hide
fireEvent.mouseDown(document.body.firstChild);
await awaitFakeTimer();
expect(shadowRoot.querySelector('.bamboo')).toBeFalsy();

// Click to show again
fireEvent.click(shadowRoot.querySelector('.target'));
await awaitFakeTimer();
expect(shadowRoot.querySelector('.bamboo')).toBeTruthy();

// Click in side shadow to hide
fireEvent.mouseDown(shadowRoot.querySelector('.little'));
await awaitFakeTimer();
expect(shadowRoot.querySelector('.bamboo')).toBeFalsy();

// Click to show again
fireEvent.click(shadowRoot.querySelector('.target'));
await awaitFakeTimer();
expect(shadowRoot.querySelector('.bamboo')).toBeTruthy();

// Click on target should not hide
fireEvent.mouseDown(shadowRoot.querySelector('.bamboo'));
await awaitFakeTimer();
expect(shadowRoot.querySelector('.bamboo')).toBeTruthy();

expect(errSpy).not.toHaveBeenCalled();
errSpy.mockRestore();
});
});

0 comments on commit e6ac87a

Please sign in to comment.