Skip to content

Commit 4c53aaa

Browse files
authored
Merge pull request #1748 from Hypnosphi/free-the-mouse-3
Free the mouse take 3
2 parents 93ffe78 + b2b5d7b commit 4c53aaa

File tree

5 files changed

+147
-44
lines changed

5 files changed

+147
-44
lines changed

packages/victory-brush-container/src/brush-helpers.js

+27-25
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,14 @@ const Helpers = {
151151
};
152152
},
153153

154+
constrainPoint(point, fullDomainBox) {
155+
const { x1, y1, x2, y2 } = mapValues(fullDomainBox, Number);
156+
return {
157+
x: Math.min(Math.max(point.x, x1), x2),
158+
y: Math.min(Math.max(point.y, y1), y2)
159+
};
160+
},
161+
154162
hasMoved(props) {
155163
const { x1, x2, y1, y2, mouseMoveThreshold } = props;
156164
const brushDimension = this.getDimension(props);
@@ -262,11 +270,7 @@ const Helpers = {
262270
},
263271

264272
// eslint-disable-next-line max-statements, complexity
265-
onMouseMove(evt, targetProps) {
266-
// if a panning or selection has not been started, ignore the event
267-
if (!targetProps.isPanning && !targetProps.isSelecting) {
268-
return {};
269-
}
273+
onGlobalMouseMove(evt, targetProps) {
270274
const {
271275
scale,
272276
isPanning,
@@ -276,15 +280,13 @@ const Helpers = {
276280
allowResize,
277281
allowDrag,
278282
horizontal,
279-
mouseMoveThreshold
283+
mouseMoveThreshold,
284+
parentSVG
280285
} = targetProps;
281286
const brushDimension = this.getDimension(targetProps);
282-
const parentSVG = targetProps.parentSVG || Selection.getParentSVG(evt);
283287
const { x, y } = Selection.getSVGEventCoordinates(evt, parentSVG);
284-
// Ignore events that occur outside of the maximum domain region
285288
if (
286289
(!allowResize && !allowDrag) ||
287-
!this.withinBounds({ x, y }, fullDomainBox) ||
288290
(mouseMoveThreshold > 0 && !this.hasMoved({ ...targetProps, x2: x, y2: y }))
289291
) {
290292
return {};
@@ -312,8 +314,13 @@ const Helpers = {
312314
}
313315
];
314316
} else if (allowResize && isSelecting) {
315-
const x2 = brushDimension !== "y" ? x : targetProps.x2;
316-
const y2 = brushDimension !== "x" ? y : targetProps.y2;
317+
const { x: x2, y: y2 } = this.constrainPoint(
318+
{
319+
x: brushDimension !== "y" ? x : targetProps.x2,
320+
y: brushDimension !== "x" ? y : targetProps.y2
321+
},
322+
fullDomainBox
323+
);
317324
const currentDomain = Selection.getBounds({
318325
x2,
319326
y2,
@@ -337,7 +344,12 @@ const Helpers = {
337344
return {};
338345
},
339346

340-
onMouseUp(evt, targetProps) {
347+
// eslint-disable-next-line complexity
348+
onGlobalMouseUp(evt, targetProps) {
349+
// if a panning or selection has not been started, ignore the event
350+
if (!targetProps.isPanning && !targetProps.isSelecting) {
351+
return {};
352+
}
341353
// eslint-disable-line max-statements, complexity
342354
const {
343355
x1,
@@ -384,25 +396,15 @@ const Helpers = {
384396
mutation: () => mutatedProps
385397
}
386398
];
387-
},
388-
389-
onMouseLeave() {
390-
return [
391-
{
392-
target: "parent",
393-
mutation: () => ({ isPanning: false, isSelecting: false })
394-
}
395-
];
396399
}
397400
};
398401

399402
export default {
400403
...Helpers,
401404
onMouseDown: Helpers.onMouseDown.bind(Helpers),
402-
onMouseUp: Helpers.onMouseUp.bind(Helpers),
403-
onMouseLeave: Helpers.onMouseLeave.bind(Helpers),
404-
onMouseMove: throttle(
405-
Helpers.onMouseMove.bind(Helpers),
405+
onGlobalMouseUp: Helpers.onGlobalMouseUp.bind(Helpers),
406+
onGlobalMouseMove: throttle(
407+
Helpers.onGlobalMouseMove.bind(Helpers),
406408
16, // eslint-disable-line no-magic-numbers
407409
{ leading: true, trailing: false }
408410
)

packages/victory-brush-container/src/victory-brush-container.js

+14-13
Original file line numberDiff line numberDiff line change
@@ -60,23 +60,24 @@ export const brushContainerMixin = (base) =>
6060
onTouchStart: (evt, targetProps) => {
6161
return props.disable ? {} : BrushHelpers.onMouseDown(evt, targetProps);
6262
},
63-
onMouseMove: (evt, targetProps) => {
64-
return props.disable ? {} : BrushHelpers.onMouseMove(evt, targetProps);
63+
onGlobalMouseMove: (evt, targetProps) => {
64+
return props.disable || (!targetProps.isPanning && !targetProps.isSelecting)
65+
? {}
66+
: BrushHelpers.onGlobalMouseMove(evt, targetProps);
6567
},
66-
onTouchMove: (evt, targetProps) => {
67-
return props.disable ? {} : BrushHelpers.onMouseMove(evt, targetProps);
68+
onGlobalTouchMove: (evt, targetProps) => {
69+
return props.disable || (!targetProps.isPanning && !targetProps.isSelecting)
70+
? {}
71+
: BrushHelpers.onGlobalMouseMove(evt, targetProps);
6872
},
69-
onMouseUp: (evt, targetProps) => {
70-
return props.disable ? {} : BrushHelpers.onMouseUp(evt, targetProps);
73+
onGlobalMouseUp: (evt, targetProps) => {
74+
return props.disable ? {} : BrushHelpers.onGlobalMouseUp(evt, targetProps);
7175
},
72-
onTouchEnd: (evt, targetProps) => {
73-
return props.disable ? {} : BrushHelpers.onMouseUp(evt, targetProps);
76+
onGlobalTouchEnd: (evt, targetProps) => {
77+
return props.disable ? {} : BrushHelpers.onGlobalMouseUp(evt, targetProps);
7478
},
75-
onMouseLeave: (evt, targetProps) => {
76-
return props.disable ? {} : BrushHelpers.onMouseLeave(evt, targetProps);
77-
},
78-
onTouchCancel: (evt, targetProps) => {
79-
return props.disable ? {} : BrushHelpers.onMouseLeave(evt, targetProps);
79+
onGlobalTouchCancel: (evt, targetProps) => {
80+
return props.disable ? {} : BrushHelpers.onGlobalMouseUp(evt, targetProps);
8081
}
8182
}
8283
}

packages/victory-core/src/victory-util/add-events.js

+51-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1+
/*global window:false */
12
import React from "react";
2-
import { defaults, assign, keys, isFunction, pick, without, isEmpty, isNil } from "lodash";
3+
import {
4+
defaults,
5+
assign,
6+
keys,
7+
isFunction,
8+
pick,
9+
without,
10+
isEmpty,
11+
isNil,
12+
difference
13+
} from "lodash";
314
import Events from "./events";
415
import isEqual from "react-fast-compare";
516
import VictoryTransition from "../victory-transition/victory-transition";
@@ -30,6 +41,9 @@ export default (WrappedComponent, options) => {
3041
this.cacheValues(calculatedValues);
3142
this.externalMutations = this.getExternalMutations(props);
3243
this.calculatedState = this.getStateChanges(props);
44+
this.globalEvents = {};
45+
this.prevGlobalEventKeys = [];
46+
this.boundGlobalEvents = {};
3347
}
3448

3549
shouldComponentUpdate(nextProps) {
@@ -54,9 +68,41 @@ export default (WrappedComponent, options) => {
5468
return false;
5569
}
5670

71+
componentDidMount() {
72+
const globalEventKeys = keys(this.globalEvents);
73+
globalEventKeys.forEach((key) => this.addGlobalListener(key));
74+
this.prevGlobalEventKeys = globalEventKeys;
75+
}
76+
5777
componentDidUpdate(prevProps) {
5878
const calculatedState = this.getStateChanges(prevProps);
5979
this.calculatedState = calculatedState;
80+
const globalEventKeys = keys(this.globalEvents);
81+
const removedGlobalEventKeys = difference(this.prevGlobalEventKeys, globalEventKeys);
82+
removedGlobalEventKeys.forEach((key) => this.removeGlobalListener(key));
83+
const addedGlobalEventKeys = difference(globalEventKeys, this.prevGlobalEventKeys);
84+
addedGlobalEventKeys.forEach((key) => this.addGlobalListener(key));
85+
this.prevGlobalEventKeys = globalEventKeys;
86+
}
87+
88+
componentWillUnmount() {
89+
this.prevGlobalEventKeys.forEach((key) => this.removeGlobalListener(key));
90+
}
91+
92+
addGlobalListener(key) {
93+
const boundListener = (event) => {
94+
const listener = this.globalEvents[key];
95+
return listener && listener(Events.emulateReactEvent(event));
96+
};
97+
this.boundGlobalEvents[key] = boundListener;
98+
window.addEventListener(Events.getGlobalEventNameFromKey(key), boundListener);
99+
}
100+
101+
removeGlobalListener(key) {
102+
window.removeEventListener(
103+
Events.getGlobalEventNameFromKey(key),
104+
this.boundGlobalEvents[key]
105+
);
60106
}
61107

62108
// compile all state changes from own and parent state. Order doesn't matter, as any state
@@ -202,6 +248,10 @@ export default (WrappedComponent, options) => {
202248
renderContainer(component, children) {
203249
const isContainer = component.type && component.type.role === "container";
204250
const parentProps = isContainer ? this.getComponentProps(component, "parent", "parent") : {};
251+
if (parentProps.events) {
252+
this.globalEvents = Events.getGlobalEvents(parentProps.events);
253+
parentProps.events = Events.omitGlobalEvents(parentProps.events);
254+
}
205255
return React.cloneElement(component, parentProps, children);
206256
}
207257

packages/victory-core/src/victory-util/events.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { assign, isEmpty, isFunction, without, pickBy, uniq, includes, keys } from "lodash";
1+
import { assign, isEmpty, isFunction, without, pickBy, omitBy, uniq, includes, keys } from "lodash";
2+
3+
const GLOBAL_EVENT_REGEX = /^onGlobal(.*)$/;
24

35
export default {
46
/* Returns all own and shared events that should be attached to a single target element,
@@ -372,5 +374,15 @@ export default {
372374
return memo;
373375
}, []);
374376
return events && events.length ? events : undefined;
375-
}
377+
},
378+
379+
getGlobalEventNameFromKey(key) {
380+
const match = key.match(GLOBAL_EVENT_REGEX);
381+
return match && match[1] && match[1].toLowerCase();
382+
},
383+
384+
getGlobalEvents: (events) => pickBy(events, (_, key) => GLOBAL_EVENT_REGEX.test(key)),
385+
omitGlobalEvents: (events) => omitBy(events, (_, key) => GLOBAL_EVENT_REGEX.test(key)),
386+
387+
emulateReactEvent: (event) => assign(event, { nativeEvent: event })
376388
};

packages/victory-shared-events/src/victory-shared-events.js

+41-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { assign, isFunction, defaults, isEmpty, fromPairs, keys } from "lodash";
1+
/*global window:false */
2+
import { assign, isFunction, defaults, isEmpty, fromPairs, keys, difference } from "lodash";
23
import React from "react";
34
import PropTypes from "prop-types";
45
import { PropTypes as CustomPropTypes, Events, Helpers, TimerContext } from "victory-core";
@@ -61,6 +62,9 @@ export default class VictorySharedEvents extends React.Component {
6162
this.getEventState = Events.getEventState.bind(this);
6263
this.baseProps = this.getBaseProps(props);
6364
this.sharedEventsCache = {};
65+
this.globalEvents = {};
66+
this.prevGlobalEventKeys = [];
67+
this.boundGlobalEvents = {};
6468
}
6569

6670
shouldComponentUpdate(nextProps) {
@@ -72,6 +76,38 @@ export default class VictorySharedEvents extends React.Component {
7276
return true;
7377
}
7478

79+
componentDidMount() {
80+
const globalEventKeys = keys(this.globalEvents);
81+
globalEventKeys.forEach((key) => this.addGlobalListener(key));
82+
this.prevGlobalEventKeys = globalEventKeys;
83+
}
84+
85+
componentDidUpdate() {
86+
const globalEventKeys = keys(this.globalEvents);
87+
const removedGlobalEventKeys = difference(this.prevGlobalEventKeys, globalEventKeys);
88+
removedGlobalEventKeys.forEach((key) => this.removeGlobalListener(key));
89+
const addedGlobalEventKeys = difference(globalEventKeys, this.prevGlobalEventKeys);
90+
addedGlobalEventKeys.forEach((key) => this.addGlobalListener(key));
91+
this.prevGlobalEventKeys = globalEventKeys;
92+
}
93+
94+
componentWillUnmount() {
95+
this.prevGlobalEventKeys.forEach((key) => this.removeGlobalListener(key));
96+
}
97+
98+
addGlobalListener(key) {
99+
const boundListener = (event) => {
100+
const listener = this.globalEvents[key];
101+
return listener && listener(Events.emulateReactEvent(event));
102+
};
103+
this.boundGlobalEvents[key] = boundListener;
104+
window.addEventListener(Events.getGlobalEventNameFromKey(key), boundListener);
105+
}
106+
107+
removeGlobalListener(key) {
108+
window.removeEventListener(Events.getGlobalEventNameFromKey(key), this.boundGlobalEvents[key]);
109+
}
110+
75111
getAllEvents(props) {
76112
const components = ["container", "groupComponent"];
77113
const componentEvents = Events.getComponentEvents(props, components);
@@ -231,9 +267,11 @@ export default class VictorySharedEvents extends React.Component {
231267
Events.getPartialEvents(parentEvents, "parent", parentProps),
232268
containerProps.events
233269
);
270+
this.globalEvents = Events.getGlobalEvents(containerEvents);
271+
const localEvents = Events.omitGlobalEvents(containerEvents);
234272
return role === "container"
235-
? React.cloneElement(container, assign({}, parentProps, { events: containerEvents }))
236-
: React.cloneElement(container, containerEvents, children);
273+
? React.cloneElement(container, assign({}, parentProps, { events: localEvents }))
274+
: React.cloneElement(container, localEvents, children);
237275
}
238276

239277
render() {

0 commit comments

Comments
 (0)