Skip to content

Commit

Permalink
fix: click on shadowDOM popup should not close it (#480)
Browse files Browse the repository at this point in the history
Co-authored-by: afc163 <afc163@gmail.com>
  • Loading branch information
2 people authored and zombieJ committed Sep 3, 2024
1 parent e4b64b2 commit 0c29d70
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 10 deletions.
9 changes: 9 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// global.d.ts
declare namespace JSX {
interface IntrinsicElements {
'custom-element': React.DetailedHTMLProps<
React.HTMLAttributes<HTMLElement> & { class?: string },
HTMLElement
>;
}
}
23 changes: 13 additions & 10 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
});

Expand Down
119 changes: 119 additions & 0 deletions tests/shadow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `<div class="shadow-content">Hello World</div>`;
}
}

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(
<>
<div className="outer">outer</div>
<Trigger
action={['click']}
autoDestroy
popup={<custom-element class="popup" />}
>
<p className="target" />
</Trigger>
</>,
);
});

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(
<>
<div className="outer">outer</div>
<Trigger
action={['click']}
autoDestroy
popup={<custom-element class="popup" />}
>
<custom-element class="target" />
</Trigger>
</>,
);
});

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();
});
});

0 comments on commit 0c29d70

Please sign in to comment.