Skip to content

Commit de346c9

Browse files
committed
Bubble all events from InPortal to OutPortal
1 parent 3426281 commit de346c9

File tree

2 files changed

+63
-1
lines changed

2 files changed

+63
-1
lines changed

src/index.tsx

+19-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ interface PortalNodeBase<C extends Component<any>> {
3131
// If an expected placeholder is provided, only unmount if that's still that was the
3232
// latest placeholder we replaced. This avoids some race conditions.
3333
unmount(expectedPlaceholder?: Node): void;
34+
3435
}
3536
export interface HtmlPortalNode<C extends Component<any> = Component<any>> extends PortalNodeBase<C> {
3637
element: HTMLElement;
@@ -159,8 +160,25 @@ class InPortal extends React.PureComponent<InPortalProps, { nodeProps: {} }> {
159160
this.addPropsChannel();
160161
}
161162

162-
componentDidUpdate() {
163+
componentDidUpdate(previousProps: InPortalProps) {
163164
this.addPropsChannel();
165+
if(previousProps.node.element !== this.props.node.element){
166+
Object.keys(window).forEach(key => {
167+
if (/^on/.test(key)) {
168+
const eventType = key.slice(2);
169+
this.props.node.element.addEventListener(eventType, this.onEventHandler);
170+
if(previousProps.node.element){
171+
previousProps.node.element.removeEventListener(eventType, this.onEventHandler);
172+
}
173+
}
174+
});
175+
176+
}
177+
}
178+
179+
onEventHandler(e:any){
180+
e.stopPropagation();
181+
this.props.node.element.dispatchEvent(e);
164182
}
165183

166184
render() {

stories/html.stories.js

+44
Original file line numberDiff line numberDiff line change
@@ -349,5 +349,49 @@ storiesOf('Portals', module)
349349
</div>;
350350
}
351351

352+
return <MyComponent componentToShow='component-a' />
353+
}).add('Events bubbling from PortalOut', () => {
354+
const MyExpensiveComponent = () => <div onMouseDown={() => console.log('expensive')}>expensive!</div>;
355+
356+
const MyComponent = () => {
357+
const portalNode = React.useMemo(() => createHtmlPortalNode(), []);
358+
359+
return <div>
360+
{/*
361+
Create the content that you want to move around.
362+
InPortals render as normal, but to detached DOM.
363+
Until this is used MyExpensiveComponent will not
364+
appear anywhere in the page.
365+
*/}
366+
<InPortal node={portalNode}>
367+
<MyExpensiveComponent
368+
// Optionally provide props to use before this enters the DOM
369+
myProp={"defaultValue"}
370+
/>
371+
</InPortal>
372+
373+
{/* ... The rest of your UI ... */}
374+
375+
{/* Pass the node to whoever might want to show it: */}
376+
<ComponentA portalNode={portalNode} />
377+
</div>;
378+
}
379+
380+
const ComponentA = (props) => {
381+
return <div
382+
onClick={() => alert('Div clicked')}
383+
onMouseDown={() => console.log('Mouse Down')}
384+
onMouseEnter={() => console.log('Mouse enter')}
385+
>
386+
{/* ... Some more UI ... */}
387+
388+
A:
389+
390+
<OutPortal
391+
node={props.portalNode} // Show the content from this node here
392+
/>
393+
</div>;
394+
}
395+
352396
return <MyComponent componentToShow='component-a' />
353397
});

0 commit comments

Comments
 (0)