mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
Avoid recursive event replay loops (#8738)
* chart._lastEvent = null while processing onHover * Pass replay flag to external tooltip * Add test for replay * cc
This commit is contained in:
parent
396cbcb979
commit
b2c7baf10d
@ -1075,8 +1075,7 @@ class Chart {
|
|||||||
*/
|
*/
|
||||||
_handleEvent(e, replay) {
|
_handleEvent(e, replay) {
|
||||||
const me = this;
|
const me = this;
|
||||||
const lastActive = me._active || [];
|
const {_active: lastActive = [], options} = me;
|
||||||
const options = me.options;
|
|
||||||
const hoverOptions = options.hover;
|
const hoverOptions = options.hover;
|
||||||
|
|
||||||
// If the event is replayed from `update`, we should evaluate with the final positions.
|
// If the event is replayed from `update`, we should evaluate with the final positions.
|
||||||
@ -1096,14 +1095,16 @@ class Chart {
|
|||||||
|
|
||||||
let active = [];
|
let active = [];
|
||||||
let changed = false;
|
let changed = false;
|
||||||
|
let lastEvent = null;
|
||||||
|
|
||||||
// Find Active Elements for hover and tooltips
|
// Find Active Elements for hover and tooltips
|
||||||
if (e.type === 'mouseout') {
|
if (e.type !== 'mouseout') {
|
||||||
me._lastEvent = null;
|
|
||||||
} else {
|
|
||||||
active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
|
active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
|
||||||
me._lastEvent = e.type === 'click' ? me._lastEvent : e;
|
lastEvent = e.type === 'click' ? me._lastEvent : e;
|
||||||
}
|
}
|
||||||
|
// Set _lastEvent to null while we are processing the event handlers.
|
||||||
|
// This prevents recursion if the handler calls chart.update()
|
||||||
|
me._lastEvent = null;
|
||||||
|
|
||||||
// Invoke onHover hook
|
// Invoke onHover hook
|
||||||
callCallback(options.onHover || hoverOptions.onHover, [e, active, me], me);
|
callCallback(options.onHover || hoverOptions.onHover, [e, active, me], me);
|
||||||
@ -1120,6 +1121,8 @@ class Chart {
|
|||||||
me._updateHoverStyles(active, lastActive, replay);
|
me._updateHoverStyles(active, lastActive, replay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
me._lastEvent = lastEvent;
|
||||||
|
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -524,7 +524,7 @@ export class Tooltip extends Element {
|
|||||||
return tooltipItems;
|
return tooltipItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(changed) {
|
update(changed, replay) {
|
||||||
const me = this;
|
const me = this;
|
||||||
const options = me.options.setContext(me.getContext());
|
const options = me.options.setContext(me.getContext());
|
||||||
const active = me._active;
|
const active = me._active;
|
||||||
@ -574,7 +574,7 @@ export class Tooltip extends Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (changed && options.external) {
|
if (changed && options.external) {
|
||||||
options.external.call(me, {chart: me._chart, tooltip: me});
|
options.external.call(me, {chart: me._chart, tooltip: me, replay});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1023,7 +1023,7 @@ export class Tooltip extends Element {
|
|||||||
y: e.y
|
y: e.y
|
||||||
};
|
};
|
||||||
|
|
||||||
me.update(true);
|
me.update(true, replay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
50
test/fixtures/controller.doughnut/event-replay.js
vendored
Normal file
50
test/fixtures/controller.doughnut/event-replay.js
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
function drawMousePoint(ctx, center) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(center.x, center.y, 8, 0, Math.PI * 2);
|
||||||
|
ctx.fillStyle = 'yellow';
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = 512;
|
||||||
|
canvas.height = 512;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
config: {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
datasets: [{
|
||||||
|
backgroundColor: ['red', 'green', 'blue'],
|
||||||
|
hoverBackgroundColor: 'black',
|
||||||
|
data: [1, 1, 1]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
canvas: {
|
||||||
|
width: 512,
|
||||||
|
height: 512
|
||||||
|
},
|
||||||
|
async run(chart) {
|
||||||
|
ctx.drawImage(chart.canvas, 0, 0, 256, 256);
|
||||||
|
|
||||||
|
const arc = chart.getDatasetMeta(0).data[0];
|
||||||
|
const center = arc.getCenterPoint();
|
||||||
|
await jasmine.triggerMouseEvent(chart, 'mousemove', arc);
|
||||||
|
drawMousePoint(chart.ctx, center);
|
||||||
|
ctx.drawImage(chart.canvas, 256, 0, 256, 256);
|
||||||
|
|
||||||
|
chart.toggleDataVisibility(0);
|
||||||
|
chart.update();
|
||||||
|
drawMousePoint(chart.ctx, center);
|
||||||
|
ctx.drawImage(chart.canvas, 0, 256, 256, 256);
|
||||||
|
|
||||||
|
await jasmine.triggerMouseEvent(chart, 'mouseout', arc);
|
||||||
|
ctx.drawImage(chart.canvas, 256, 256, 256, 256);
|
||||||
|
|
||||||
|
Chart.helpers.clearCanvas(chart.canvas);
|
||||||
|
chart.ctx.drawImage(canvas, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
BIN
test/fixtures/controller.doughnut/event-replay.png
vendored
Normal file
BIN
test/fixtures/controller.doughnut/event-replay.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
@ -917,7 +917,7 @@ describe('Plugin.Tooltip', function() {
|
|||||||
|
|
||||||
// First dispatch change event, should update tooltip
|
// First dispatch change event, should update tooltip
|
||||||
await jasmine.triggerMouseEvent(chart, 'mousemove', firstPoint);
|
await jasmine.triggerMouseEvent(chart, 'mousemove', firstPoint);
|
||||||
expect(tooltip.update).toHaveBeenCalledWith(true);
|
expect(tooltip.update).toHaveBeenCalledWith(true, undefined);
|
||||||
|
|
||||||
// Reset calls
|
// Reset calls
|
||||||
tooltip.update.calls.reset();
|
tooltip.update.calls.reset();
|
||||||
@ -980,7 +980,7 @@ describe('Plugin.Tooltip', function() {
|
|||||||
|
|
||||||
// First dispatch change event, should update tooltip
|
// First dispatch change event, should update tooltip
|
||||||
await jasmine.triggerMouseEvent(chart, 'mousemove', firstPoint);
|
await jasmine.triggerMouseEvent(chart, 'mousemove', firstPoint);
|
||||||
expect(tooltip.update).toHaveBeenCalledWith(true);
|
expect(tooltip.update).toHaveBeenCalledWith(true, undefined);
|
||||||
|
|
||||||
// Reset calls
|
// Reset calls
|
||||||
tooltip.update.calls.reset();
|
tooltip.update.calls.reset();
|
||||||
@ -988,7 +988,7 @@ describe('Plugin.Tooltip', function() {
|
|||||||
// Second dispatch change event (same event), should update tooltip
|
// Second dispatch change event (same event), should update tooltip
|
||||||
// because position mode is 'nearest'
|
// because position mode is 'nearest'
|
||||||
await jasmine.triggerMouseEvent(chart, 'mousemove', secondPoint);
|
await jasmine.triggerMouseEvent(chart, 'mousemove', secondPoint);
|
||||||
expect(tooltip.update).toHaveBeenCalledWith(true);
|
expect(tooltip.update).toHaveBeenCalledWith(true, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('positioners', function() {
|
describe('positioners', function() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user