-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ShadowDOM] Modal doesn't work well with ShadowRoot #16223
Comments
And ReactDOM.createPortal works well. <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<body>
<div id="root"></div>
<div id="portalhost"></div>
</body>
<script>
var shadow = root.attachShadow({ mode: 'closed' })
var portal = portalhost.attachShadow({ mode: 'closed' })
</script>
<script type="text/babel">
ReactDOM.render(<App />, shadow)
function App() {
return (
<article onClick={() => alert('article')}>
Hello, world
<br />
{ReactDOM.createPortal(<Component />, portal)}
</article>
)
}
function Component() {
return <h1 onClick={() => alert('h1')}>Click me</h1>
}
</script> |
We have a similar issue in #16191. |
Okay, I resolved this by more hacks. But still nice to see this problem get resolved. <body>
<div id="root"></div>
<div id="portalhost"></div>
</body>
<script>
var shadow = root.attachShadow({ mode: 'closed' })
var portal = portalhost.attachShadow({ mode: 'closed' })
{
// ? Hack for React, let event go through ShadowDom
const hackingEvents = new WeakMap()
function hack(eventName, shadowRoot) {
shadowRoot.addEventListener(eventName, e => {
if (hackingEvents.has(e)) return
const path = e.composedPath()
var e2 = new e.constructor(e.type, e)
hackingEvents.set(e2, path)
portal.dispatchEvent(e2)
e.stopPropagation()
e.stopImmediatePropagation()
})
}
hack('click', portal)
var nativeTarget = Object.getOwnPropertyDescriptor(Event.prototype, 'target').get
Object.defineProperty(Event.prototype, 'target', {
get() {
if (hackingEvents.has(this)) return hackingEvents.get(this)[0]
return nativeTarget.call(this)
}
})
}
// ? Mui use ReactDOM.findDOMNode
// it doesn't work well with ShadowRoot
// This is a hack
Object.defineProperty(ShadowRoot.prototype, 'nodeType', {
get() {
if (this === portal) return 1
else return Object.getOwnPropertyDescriptor(Node.prototype, 'nodeType').get.call(this)
}
})
Object.defineProperty(ShadowRoot.prototype, 'tagName', {
get() {
if (this === portal) return 'div'
else return undefined
}
})
// ? Mui set .style on the container and it isn't exists on ShadowRoot
// Forwarding it to the host of the ShadowRoot
Object.defineProperty(ShadowRoot.prototype, 'style', {
get() {
if (this === portal) return this.host
else return undefined
}
})
</script>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/@material-ui/core@latest/umd/material-ui.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
var { Dialog } = MaterialUI
ReactDOM.render(<App />, shadow)
function App() {
return (
<article onClick={e => console.log('article', e)}>
Hello, world
<br />
<Dialog open container={portal} onClick={e => console.log('dialog', e)}>
<Component />
</Dialog>
</article>
)
}
function Component() {
return <h1 onClick={e => console.log('h1', e)}>Click me</h1>
}
</script> |
It's related to #17473 |
Having this issue right now. Got trouble understanding and applying the solution of @Jack-Works . Would appreciate any form of help |
@Dizzzmas Hi, my example is cheating on the ShadowRoot DOM object and let it behave like a normal HTMLElement so the React will accept to render on it. Which part you don't understand? |
@Jack-Works
I don't really understand when the above code is supposed to trigger and what is its purpose. I tried putting your solution into a React component's I would really be grateful if you could provide a Here's the code that I was trying to use:
The Dialog is properly placed into the |
@Dizzzmas here is our production level code https://github.com/DimensionDev/Maskbook/tree/master/src/utils/jss to render everything in ShadowDom, with JSS styling (injected into ShadowDom) suppoort |
Updated link to at least some of the source code @Jack-Works referenced - https://github.com/DimensionDev/Maskbook/tree/master/packages/maskbook/src/utils/shadow-root Looks like we are missing some of the utility functions, not sure their package "maskbook-shared" is open source. |
Hi! We refactored our application a lot, the latest code is in https://github.com/DimensionDev/Maskbook/tree/master/packages/shared/src/ShadowRoot. It's ready for both JSS and emotion. |
https://codesandbox.io/s/nostalgic-cloud-d2ny7?file=/src/Demo.tsx All UI in the code sandbox above is rendered in the ShadowRoot. You can verify that by the devtools.
const picker = usePortalShadowRoot((container) => (
<DatePicker
DialogProps={{ container }}
PopperProps={{ container }}
value={new Date()}
onChange={() => {}}
renderInput={(props) => <TextField {...props} />}
/>
)) Note: Our application is licensed in AGPLv3, you may not be able to reuse this code otherwise the license will spread into your project. This solution is quite hacky and might have a potential performance problem. |
Expected Behavior 🤔
Everything works well.
Current Behavior 😯
With 3 hacks, it partially works. But all events lost (like onClick)
(In this example, CSS also lost too but it is not the key problem. I have resolved this problem in my context, by implementing a custom JSS renderer to render the style into the ShadowRoot)
Steps to Reproduce 🕹
Context 🔦
I'm developing a crypto-related browser extension. I need to render everything in ShadowRoot to prevent the host webpage get any info from my extension.
Your Environment 🌎
The text was updated successfully, but these errors were encountered: