Repository navigation
Fix renderWebChat() ignoring clicks and typing inside a shadow root on React 16 - #5982
Eugene (OEvgeny) wants to merge 4 commits into
Conversation
React 16 listens to events on the document unless the container is a document or a document fragment. Inside a shadow root, the document gets events retargeted to the shadow host, so the bundled React DOM 16.8.6 ignored every click and keystroke. renderWebChat() now renders a hidden portal into the shadow root with a no-op handler for every React 16 event, so React 16 also listens on the shadow root.
The carousel of suggested actions put its react-film styles into the document head, so suggested actions inside a shadow root lost their carousel layout and showed a list bullet.
|
|
||
| // React 16 to 18, and Preact. Also the IIFE build, which bundles React DOM 16.8.6 unless `window.ReactDOM` is set. | ||
| ReactDOM.render(children, elementOrRoot); | ||
| ReactDOM.render( |
There was a problem hiding this comment.
What happens if the passed node is nested inside the shadow root instead of shadow root being passed itself?
There was a problem hiding this comment.
That is the case the portal is for: getRootNode() returns the closest shadow root at any depth, so the portal goes into that shadow root and React 16 listens there; shadowRoot.interact.html renders into a <main> inside the shadow root. When the shadow root itself is passed, rootNode === elementOrRoot and there is no portal, because React 16 already listens on a shadow root container (shadowRoot.interact.renderIntoShadowRoot.html).
| @@ -0,0 +1,102 @@ | |||
| // Every event prop of React 16, from its event plugins, except the ones that block scrolling (see below). | |||
| const REACT_16_EVENT_PROP_NAMES = Object.freeze([ | |||
There was a problem hiding this comment.
Any other way of handling this?
There was a problem hiding this comment.
The alternatives were worse: reading the list from React DOM's private __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events at runtime (the spec uses it only to check this list, which matches React DOM 16.8.6 to 16.14), listing only the handlers Web Chat uses (host middleware can use others), a re-dispatch shim like react-shadow-dom-retarget-events (handlers get native events, onChange fired twice for one keystroke, onFocus never fired), or rendering into the shadow root itself (React 16 removes the container's existing children on first render). Bundling React DOM 17+ in webchat.js would also fix it, but changes the 16.8.6 minimum.
There was a problem hiding this comment.
Thinking out loud.
What if we mount into the empty element, the empty root then portal the rest into Shadow DOM.
It's okay to not allow instancing inside Shadow DOM directly as some our tests do, for react 16
There was a problem hiding this comment.
Tried it on React 16.8.6 (3 runs each, React 18 as control): a portal into an element inside the shadow root still gets no events, because React 16 listens on the portal container's ownerDocument unless the container is a fragment (ensureListeningTo in react-dom 16.8.6), so only a portal into the ShadowRoot itself works. That one needs no event list, but Web Chat then becomes a direct child of the shadow root and the host's element and its CSS no longer wrap it (61 px tall instead of the element's 120 px in the probe). The list keeps Web Chat inside the host's element, so I would keep it, unless you prefer requiring that layout for React 16.
Changelog Entry
Fixed
renderWebChat()ignoring clicks and typing inside a shadow root on React 16, related to #5315, in PR #5982, by @OEvgenyDescription
On React 16, the default in
webchat.js, Web Chat in an element inside a shadow root ignored clicks and keystrokes: React 16 listens on the document, which sees the shadow host as the target.renderWebChat()now adds a hidden portal with React 16's event handlers into the shadow root, so React 16 listens there too.Design
react-shadow-dom-retarget-events, which calls handlers with native events, or a shadow root of Web Chat's own, which the host'sstylesRootand CSS would not reach.<ReactWebChat>rendered by the host on React 16.Specific Changes
renderWebChat.tsx: the portal, on React 16 with a container inside a shadow rootreact16EventListenerProps.ts: every React 16 event but touch and wheel (scroll-blocking), unit-tested against React DOM 16.8.6SuggestedActions.tsx: passstylesRootto the carousel, unstyled in a shadow rootcustomElement/shadowRoot.interact.html: React 16, type and send, click a suggested actionshadowRoot.interact.renderIntoShadowRoot.html,shadowRoot.interact.react18.html: same steps, no portalI have added tests and executed them locally
I have added
.changelog/<PR#>-<slug>.mdI have updated documentation
Review Checklist
z-index)package.jsonandpackage-lock.jsonreviewed