Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 93f761f

Browse filesBrowse files
authored
Merge pull request plotly#7357 from plotly/alex/shadow-click
Correctly assign click event target in shadow dom
2 parents 24478b5 + 89d6404 commit 93f761f
Copy full SHA for 93f761f

File tree

6 files changed

+118
-25
lines changed
Filter options

6 files changed

+118
-25
lines changed

‎draftlogs/7357_fix.md

Copy file name to clipboard
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Fix click event handling for plots in shadow DOM elements [[#7357](https://github.com/plotly/plotly.js/pull/7357)]

‎src/components/dragelement/index.js

Copy file name to clipboardExpand all lines: src/components/dragelement/index.js
+26-18Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -221,30 +221,38 @@ dragElement.init = function init(options) {
221221
if(gd._dragged) {
222222
if(options.doneFn) options.doneFn();
223223
} else {
224-
if(options.clickFn) options.clickFn(numClicks, initialEvent);
224+
// If you're in a shadow DOM the target here gets pushed
225+
// up to the container in the main DOM. (why only here? IDK)
226+
// Don't make an event at all, just an object that looks like one,
227+
// since the shadow DOM puts restrictions on what can go in the event,
228+
// but copy as much as possible since it will be passed on to
229+
// plotly_click handlers
230+
var clickEvent;
231+
if (initialEvent.target === initialTarget) {
232+
clickEvent = initialEvent;
233+
} else {
234+
clickEvent = {
235+
target: initialTarget,
236+
srcElement: initialTarget,
237+
toElement: initialTarget
238+
};
239+
Object.keys(initialEvent)
240+
.concat(Object.keys(initialEvent.__proto__))
241+
.forEach(k => {
242+
var v = initialEvent[k];
243+
if (!clickEvent[k] && (typeof v !== 'function')) {
244+
clickEvent[k] = v;
245+
}
246+
});
247+
}
248+
if(options.clickFn) options.clickFn(numClicks, clickEvent);
225249

226250
// If we haven't dragged, this should be a click. But because of the
227251
// coverSlip changing the element, the natural system might not generate one,
228252
// so we need to make our own. But right clicks don't normally generate
229253
// click events, only contextmenu events, which happen on mousedown.
230254
if(!rightClick) {
231-
var e2;
232-
233-
try {
234-
e2 = new MouseEvent('click', e);
235-
} catch(err) {
236-
var offset = pointerOffset(e);
237-
e2 = document.createEvent('MouseEvents');
238-
e2.initMouseEvent('click',
239-
e.bubbles, e.cancelable,
240-
e.view, e.detail,
241-
e.screenX, e.screenY,
242-
offset[0], offset[1],
243-
e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
244-
e.button, e.relatedTarget);
245-
}
246-
247-
initialTarget.dispatchEvent(e2);
255+
initialTarget.dispatchEvent(new MouseEvent('click', e));
248256
}
249257
}
250258

+26Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
3+
module.exports = function createShadowGraphDiv() {
4+
var container = document.createElement('div');
5+
container.id = 'shadowcontainer';
6+
document.body.appendChild(container);
7+
var root = container.attachShadow({mode: 'open'});
8+
var gd = document.createElement('div');
9+
gd.id = 'graph2';
10+
root.appendChild(gd);
11+
12+
// force the shadow container to be at position 0,0 no matter what
13+
container.style.position = 'fixed';
14+
container.style.left = 0;
15+
container.style.top = 0;
16+
17+
var style = document.createElement('style');
18+
root.appendChild(style);
19+
20+
for (var plotlyStyle of document.querySelectorAll('[id^="plotly.js-"]')) {
21+
for (var rule of plotlyStyle.sheet.rules) {
22+
style.sheet.insertRule(rule.cssText);
23+
}
24+
}
25+
return gd;
26+
};
+5-3Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
'use strict';
22

33
module.exports = function destroyGraphDiv() {
4-
var gd = document.getElementById('graph');
5-
6-
if(gd) document.body.removeChild(gd);
4+
// remove both plain graphs and shadow DOM graph containers
5+
['graph', 'shadowcontainer'].forEach(function(id) {
6+
var el = document.getElementById(id);
7+
if(el) document.body.removeChild(el);
8+
});
79
};

‎test/jasmine/assets/mouse_event.js

Copy file name to clipboardExpand all lines: test/jasmine/assets/mouse_event.js
+6-1Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ module.exports = function(type, x, y, opts) {
3333
fullOpts.shiftKey = opts.shiftKey;
3434
}
3535

36-
var el = (opts && opts.element) || document.elementFromPoint(x, y);
36+
var shadowContainer = document.getElementById('shadowcontainer');
37+
var elementRoot = (opts && opts.elementRoot) || (
38+
shadowContainer ? shadowContainer.shadowRoot : document
39+
);
40+
41+
var el = (opts && opts.element) || elementRoot.elementFromPoint(x, y);
3742
var ev;
3843

3944
if(type === 'scroll' || type === 'wheel') {

‎test/jasmine/tests/click_test.js

Copy file name to clipboardExpand all lines: test/jasmine/tests/click_test.js
+54-3Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var DBLCLICKDELAY = require('../../../src/plot_api/plot_config').dfltConfig.doub
77
var d3Select = require('../../strict-d3').select;
88
var d3SelectAll = require('../../strict-d3').selectAll;
99
var createGraphDiv = require('../assets/create_graph_div');
10+
var createShadowGraphDiv = require('../assets/create_shadow_graph_div');
1011
var destroyGraphDiv = require('../assets/destroy_graph_div');
1112

1213
var mouseEvent = require('../assets/mouse_event');
@@ -1059,17 +1060,17 @@ describe('Test click interactions:', function() {
10591060
width: 600,
10601061
height: 600
10611062
}).then(function() {
1062-
expect(gd.layout.xaxis.range).toBeCloseToArray([1, 2.068]);
1063+
expect(gd.layout.xaxis.range).toBeCloseToArray([1, 2.068], 1);
10631064

10641065
return doubleClick(300, 300);
10651066
})
10661067
.then(function() {
1067-
expect(gd.layout.xaxis.range).toBeCloseToArray([-0.2019, 3.249]);
1068+
expect(gd.layout.xaxis.range).toBeCloseToArray([-0.2019, 3.249], 1);
10681069

10691070
return doubleClick(300, 300);
10701071
})
10711072
.then(function() {
1072-
expect(gd.layout.xaxis.range).toBeCloseToArray([1, 2.068]);
1073+
expect(gd.layout.xaxis.range).toBeCloseToArray([1, 2.068], 1);
10731074
})
10741075
.then(done, done.fail);
10751076
});
@@ -1164,6 +1165,56 @@ describe('Test click interactions:', function() {
11641165
});
11651166
});
11661167

1168+
describe('Click events in Shadow DOM', function() {
1169+
afterEach(destroyGraphDiv);
1170+
1171+
function fig() {
1172+
var x = [];
1173+
var y = [];
1174+
for (var i = 0; i <= 20; i++) {
1175+
for (var j = 0; j <= 20; j++) {
1176+
x.push(i);
1177+
y.push(j);
1178+
}
1179+
}
1180+
return {
1181+
data: [{x: x, y: y, mode: 'markers'}],
1182+
layout: {
1183+
width: 400,
1184+
height: 400,
1185+
margin: {l: 100, r: 100, t: 100, b: 100},
1186+
xaxis: {range: [0, 20]},
1187+
yaxis: {range: [0, 20]},
1188+
}
1189+
};
1190+
}
1191+
1192+
it('should select the same point in regular and shadow DOM', function(done) {
1193+
var clickData;
1194+
var clickX = 120;
1195+
var clickY = 150;
1196+
var expectedX = 2; // counts up 1 every 10px from 0 at 100px
1197+
var expectedY = 15; // counts down 1 every 10px from 20 at 100px
1198+
1199+
function check(gd) {
1200+
gd.on('plotly_click', function(event) { clickData = event; });
1201+
click(clickX, clickY);
1202+
expect(clickData.points.length).toBe(1);
1203+
var pt = clickData.points[0];
1204+
expect(pt.x).toBe(expectedX);
1205+
expect(pt.y).toBe(expectedY);
1206+
clickData = null;
1207+
}
1208+
1209+
Plotly.newPlot(createGraphDiv(), fig())
1210+
.then(check)
1211+
.then(destroyGraphDiv)
1212+
.then(function() { return Plotly.newPlot(createShadowGraphDiv(), fig()) })
1213+
.then(check)
1214+
.then(done, done.fail);
1215+
});
1216+
});
1217+
11671218

11681219
describe('dragbox', function() {
11691220
afterEach(destroyGraphDiv);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.