Skip to content

Commit 0406615

Browse files
committed
Determine event target in Shadow DOM correctly
1 parent 86914cb commit 0406615

File tree

5 files changed

+133
-0
lines changed

5 files changed

+133
-0
lines changed

fixtures/dom/src/components/Header.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class Header extends React.Component {
6464
<option value="/event-pooling">Event Pooling</option>
6565
<option value="/custom-elements">Custom Elements</option>
6666
<option value="/media-events">Media Events</option>
67+
<option value="/shadow-dom">Shadow DOM</option>
6768
</select>
6869
</label>
6970
<label htmlFor="react_version">

fixtures/dom/src/components/fixtures/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import ErrorHandling from './error-handling';
1111
import EventPooling from './event-pooling';
1212
import CustomElementFixtures from './custom-elements';
1313
import MediaEventsFixtures from './media-events';
14+
import ShadowDom from './shadow-dom';
1415

1516
const React = window.React;
1617

@@ -46,6 +47,8 @@ function FixturesPage() {
4647
return <CustomElementFixtures />;
4748
case '/media-events':
4849
return <MediaEventsFixtures />;
50+
case '/shadow-dom':
51+
return <ShadowDom />;
4952
default:
5053
return <p>Please select a test fixture.</p>;
5154
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import FixtureSet from '../../FixtureSet';
2+
import TestCase from '../../TestCase';
3+
4+
const SUPPORTS_SHADOW_DOM = HTMLElement.prototype.hasOwnProperty('attachShadow');
5+
6+
const React = window.React;
7+
const ReactDOM = window.ReactDOM;
8+
9+
class SelectFixture extends React.Component {
10+
render() {
11+
if (!SUPPORTS_SHADOW_DOM) {
12+
return <div>Browser does not support Shadow DOM, no tests to execute.</div>;
13+
}
14+
15+
return (
16+
<FixtureSet title="Shadow DOM" description="">
17+
<TestCase title="Event listeners in shadow-dom" relatedIssues="4963">
18+
<TestCase.Steps>
19+
<li>Click on the orange box</li>
20+
</TestCase.Steps>
21+
<TestCase.ExpectedResult>
22+
The box should turn green
23+
</TestCase.ExpectedResult>
24+
<Shadow>
25+
<Box/>
26+
</Shadow>
27+
</TestCase>
28+
</FixtureSet>
29+
);
30+
}
31+
}
32+
33+
class Shadow extends React.Component {
34+
componentDidMount() {
35+
this.ref.attachShadow({mode: 'open'});
36+
const el = document.createElement('div');
37+
this.ref.shadowRoot.appendChild(el);
38+
ReactDOM.render(this.props.children, el);
39+
}
40+
41+
render() {
42+
return <div ref={ref => this.ref = ref}/>;
43+
}
44+
}
45+
46+
class Box extends React.Component {
47+
state = {active: false};
48+
49+
render() {
50+
const style = {
51+
height: 100,
52+
background: this.state.active ? 'green' : 'orange',
53+
color: 'white',
54+
marginBottom: 20
55+
};
56+
return <div onClick={() => this.setState({active: !this.state.active})} style={style}/>
57+
}
58+
}
59+
60+
61+
export default SelectFixture;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Copyright (c) 2016-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @emails react-core
8+
*/
9+
10+
'use strict';
11+
12+
let React;
13+
let ReactDOM;
14+
15+
describe('getEventTarget', () => {
16+
let container;
17+
18+
beforeEach(() => {
19+
React = require('react');
20+
ReactDOM = require('react-dom');
21+
22+
// The container has to be attached for events to fire.
23+
container = document.createElement('div');
24+
document.body.appendChild(container);
25+
});
26+
27+
afterEach(() => {
28+
document.body.removeChild(container);
29+
container = null;
30+
});
31+
32+
describe('event on HTMLElement', () => {
33+
it('has expected target', () => {
34+
let actual;
35+
36+
class Comp extends React.Component {
37+
render() {
38+
return <div onClick={e => actual = e.target}><span /></div>;
39+
}
40+
}
41+
42+
ReactDOM.render(<Comp />, container);
43+
44+
const div = container.firstChild;
45+
const span = div.firstChild;
46+
47+
div.dispatchEvent(new MouseEvent('click', {
48+
bubbles: true,
49+
cancelable: true,
50+
}));
51+
52+
expect(actual).toBe(div);
53+
54+
span.dispatchEvent(new MouseEvent('click', {
55+
bubbles: true,
56+
cancelable: true,
57+
}));
58+
59+
expect(actual).toBe(span);
60+
});
61+
});
62+
});

packages/react-dom/src/events/getEventTarget.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ import {TEXT_NODE} from '../shared/HTMLNodeType';
1717
function getEventTarget(nativeEvent) {
1818
let target = nativeEvent.target || window;
1919

20+
// If composed / inside open shadow-dom use first item of composed path #9242
21+
if (nativeEvent.composed) {
22+
const path = nativeEvent.composedPath();
23+
target = path[0];
24+
}
25+
2026
// Normalize SVG <use> element events #4963
2127
if (target.correspondingUseElement) {
2228
target = target.correspondingUseElement;

0 commit comments

Comments
 (0)