@khanacademy/perseus 19.1.4 → 19.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/components/graphie.d.ts +9 -1
- package/dist/es/index.js +255 -122
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +261 -126
- package/dist/index.js.map +1 -1
- package/dist/interactive2/interactive-util.d.ts +1 -1
- package/dist/interactive2/movable-point-options.d.ts +5 -4
- package/dist/interactive2/movable.d.ts +90 -13
- package/dist/interactive2/types.d.ts +6 -0
- package/dist/util/graphie.d.ts +5 -0
- package/dist/widgets/expression.d.ts +4 -4
- package/package.json +3 -3
- package/src/components/graphie.tsx +9 -1
- package/src/interactive2/__tests__/movable-mock.ts +42 -30
- package/src/interactive2/movable-point-options.ts +31 -19
- package/src/interactive2/movable.test.ts +249 -0
- package/src/interactive2/movable.ts +289 -131
- package/src/interactive2/types.ts +15 -0
- package/src/interactive2.ts +1 -1
- package/src/util/graphie.ts +10 -0
- package/src/util/interactive.ts +4 -2
- package/tsconfig-build.tsbuildinfo +1 -1
- package/src/interactive2/__tests__/movable.test.ts +0 -157
|
@@ -13,17 +13,19 @@ import {point as kpoint} from "@khanacademy/kmath";
|
|
|
13
13
|
import $ from "jquery";
|
|
14
14
|
import _ from "underscore";
|
|
15
15
|
|
|
16
|
+
import {Errors} from "../logging/log";
|
|
17
|
+
import {PerseusError} from "../perseus-error";
|
|
18
|
+
|
|
16
19
|
import InteractiveUtil from "./interactive-util";
|
|
17
20
|
|
|
18
|
-
import type {Coord} from "./types";
|
|
21
|
+
import type {Constraint, ConstraintCallbacks, Coord} from "./types";
|
|
19
22
|
import type {Graphie} from "../util/graphie";
|
|
20
23
|
|
|
21
24
|
const normalizeOptions = InteractiveUtil.normalizeOptions;
|
|
22
25
|
|
|
23
26
|
const assert = InteractiveUtil.assert;
|
|
24
27
|
|
|
25
|
-
// Default "props" and "state". Both are added to this.state
|
|
26
|
-
// receive magic getter methods (this.isHovering() etc).
|
|
28
|
+
// Default "props" and "state". Both are added to this.state.
|
|
27
29
|
// However, properties in DEFAULT_PROPS are updated on `modify()`,
|
|
28
30
|
// while those in DEFAULT_STATE persist and are not updated.
|
|
29
31
|
// Things that the user might want to change should be on "props",
|
|
@@ -44,48 +46,51 @@ export interface State {
|
|
|
44
46
|
isHovering?: boolean;
|
|
45
47
|
isMouseOver?: boolean;
|
|
46
48
|
isDragging?: boolean;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
mouseTarget?: {
|
|
50
|
+
toFront(): void;
|
|
51
|
+
toBack(): void;
|
|
52
|
+
remove(): void;
|
|
53
|
+
getMouseTarget(): Element;
|
|
54
|
+
} | null;
|
|
55
|
+
// TODO(benchristel): I don't think cursor is used
|
|
56
|
+
cursor?: null;
|
|
51
57
|
id: string;
|
|
52
|
-
add
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
add?: (() => void)[];
|
|
59
|
+
// TODO(benchristel): I don't see any evidence that implementers of
|
|
60
|
+
// modify do anything with the second argument. Remove it?
|
|
61
|
+
modify?: ((state: State, prevState?: State) => void)[];
|
|
62
|
+
draw?: ((state: State, prevState?: State) => void)[];
|
|
63
|
+
remove?: (() => void)[];
|
|
64
|
+
// TODO(benchristel): callers pass the position twice to onMoveStart.
|
|
65
|
+
// But I don't see any implementers expecting two arguments. Remove the
|
|
66
|
+
// second argument.
|
|
67
|
+
onMoveStart?: ((position: Coord, positionAgain: Coord) => void)[];
|
|
68
|
+
onMove?: ((end: Coord, start: Coord) => void)[];
|
|
69
|
+
onMoveEnd?: ((end: Coord, start: Coord) => void)[];
|
|
70
|
+
onClick?: ((position: Coord, start: Coord) => void)[];
|
|
71
|
+
constraints?: Constraint[];
|
|
60
72
|
}
|
|
61
73
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
export class Movable<Options extends Record<string, any>> {
|
|
75
|
+
graphie: Graphie;
|
|
76
|
+
state: State;
|
|
77
|
+
prevState: State | undefined;
|
|
78
|
+
// TODO(benchristel): delete _listenerMap; it's unused
|
|
79
|
+
_listenerMap: Record<string, number> = {};
|
|
80
|
+
|
|
81
|
+
constructor(graphie: Graphie, options: Options) {
|
|
82
|
+
this.graphie = graphie;
|
|
83
|
+
this.state = {
|
|
68
84
|
id: _.uniqueId("movable"),
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
// We only set DEFAULT_STATE once, here
|
|
73
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
74
|
-
this.modify(_.extend({}, DEFAULT_STATE, options));
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
InteractiveUtil.createGettersFor(
|
|
78
|
-
Movable,
|
|
79
|
-
_.extend({}, DEFAULT_PROPS, DEFAULT_STATE),
|
|
80
|
-
);
|
|
81
|
-
InteractiveUtil.addMovableHelperMethodsTo(Movable);
|
|
82
|
-
|
|
83
|
-
_.extend(Movable.prototype, {
|
|
84
|
-
cloneState: function (): State {
|
|
85
|
-
return {...this.state};
|
|
86
|
-
},
|
|
85
|
+
};
|
|
86
|
+
this.modify({...DEFAULT_STATE, ...options});
|
|
87
|
+
}
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
modify(options: Options) {
|
|
90
|
+
this.update({...this._createDefaultState(), ...options});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
_createDefaultState() {
|
|
89
94
|
return {
|
|
90
95
|
id: this.state.id,
|
|
91
96
|
add: [],
|
|
@@ -101,73 +106,13 @@ _.extend(Movable.prototype, {
|
|
|
101
106
|
// be persistent, and updated appropriately in modify()
|
|
102
107
|
...DEFAULT_PROPS,
|
|
103
108
|
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Resets the object to its state as if it were constructed with
|
|
108
|
-
* `options` originally. The only state maintained is `state.id`
|
|
109
|
-
*
|
|
110
|
-
* Analogous to React.js's replaceProps
|
|
111
|
-
*/
|
|
112
|
-
modify: function (options: Partial<State>) {
|
|
113
|
-
this.update({...this._createDefaultState(), ...options});
|
|
114
|
-
},
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Simulates a mouse grab event on the movable object.
|
|
118
|
-
*/
|
|
119
|
-
grab: function (coord: Coord) {
|
|
120
|
-
assert(kpoint.is(coord));
|
|
121
|
-
const self = this;
|
|
122
|
-
const graphie = self.graphie;
|
|
123
|
-
const state: State = self.state;
|
|
109
|
+
}
|
|
124
110
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
graphie.isDragging = true;
|
|
128
|
-
|
|
129
|
-
const startMouseCoord = coord;
|
|
130
|
-
let prevMouseCoord = startMouseCoord;
|
|
131
|
-
self._fireEvent(state.onMoveStart, startMouseCoord, startMouseCoord);
|
|
132
|
-
|
|
133
|
-
const moveHandler = function (e: any) {
|
|
134
|
-
e.preventDefault();
|
|
111
|
+
update(options: Options) {
|
|
112
|
+
const graphie = this.graphie;
|
|
135
113
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
self.draw();
|
|
139
|
-
prevMouseCoord = mouseCoord;
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const upHandler = function (e: any) {
|
|
143
|
-
$(document).unbind("vmousemove", moveHandler);
|
|
144
|
-
$(document).unbind("vmouseup", upHandler);
|
|
145
|
-
if (state.isHovering) {
|
|
146
|
-
self._fireEvent(state.onClick, prevMouseCoord, startMouseCoord);
|
|
147
|
-
}
|
|
148
|
-
state.isHovering = self.state.isMouseOver;
|
|
149
|
-
state.isDragging = false;
|
|
150
|
-
graphie.isDragging = false;
|
|
151
|
-
self._fireEvent(state.onMoveEnd, prevMouseCoord, startMouseCoord);
|
|
152
|
-
self.draw();
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
$(document).bind("vmousemove", moveHandler);
|
|
156
|
-
$(document).bind("vmouseup", upHandler);
|
|
157
|
-
},
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Adjusts constructor parameters without changing previous settings
|
|
161
|
-
* for any option not specified
|
|
162
|
-
*
|
|
163
|
-
* Analogous to React.js's setProps
|
|
164
|
-
*/
|
|
165
|
-
update: function (options: State) {
|
|
166
|
-
const self = this;
|
|
167
|
-
const graphie = self.graphie;
|
|
168
|
-
|
|
169
|
-
const prevState = self.cloneState();
|
|
170
|
-
const state = _.extend(self.state, normalizeOptions(options));
|
|
114
|
+
const prevState = this.cloneState();
|
|
115
|
+
const state = Object.assign(this.state, normalizeOptions(options));
|
|
171
116
|
|
|
172
117
|
// the invisible shape in front of the point that gets mouse events
|
|
173
118
|
if (state.mouseTarget && !prevState.mouseTarget) {
|
|
@@ -181,25 +126,25 @@ _.extend(Movable.prototype, {
|
|
|
181
126
|
const isMouse = !("ontouchstart" in window);
|
|
182
127
|
|
|
183
128
|
if (isMouse) {
|
|
184
|
-
$mouseTarget.on("vmouseover",
|
|
129
|
+
$mouseTarget.on("vmouseover", () => {
|
|
185
130
|
state.isMouseOver = true;
|
|
186
131
|
if (!graphie.isDragging) {
|
|
187
132
|
state.isHovering = true;
|
|
188
133
|
}
|
|
189
|
-
if (
|
|
134
|
+
if (this.state.added) {
|
|
190
135
|
// Avoid drawing if the point has been removed
|
|
191
|
-
|
|
136
|
+
this.draw();
|
|
192
137
|
}
|
|
193
138
|
});
|
|
194
139
|
|
|
195
|
-
$mouseTarget.on("vmouseout",
|
|
140
|
+
$mouseTarget.on("vmouseout", () => {
|
|
196
141
|
state.isMouseOver = false;
|
|
197
142
|
if (!state.isDragging) {
|
|
198
143
|
state.isHovering = false;
|
|
199
144
|
}
|
|
200
|
-
if (
|
|
145
|
+
if (this.state.added) {
|
|
201
146
|
// Avoid drawing if the point has been removed
|
|
202
|
-
|
|
147
|
+
this.draw();
|
|
203
148
|
}
|
|
204
149
|
});
|
|
205
150
|
}
|
|
@@ -214,14 +159,14 @@ _.extend(Movable.prototype, {
|
|
|
214
159
|
{passive: false},
|
|
215
160
|
);
|
|
216
161
|
|
|
217
|
-
$mouseTarget.on("vmousedown",
|
|
162
|
+
$mouseTarget.on("vmousedown", (e) => {
|
|
218
163
|
if (e.which !== 0 && e.which !== 1) {
|
|
219
164
|
return;
|
|
220
165
|
}
|
|
221
166
|
e.preventDefault();
|
|
222
167
|
|
|
223
168
|
const mouseCoord = graphie.getMouseCoord(e);
|
|
224
|
-
|
|
169
|
+
this.grab(mouseCoord);
|
|
225
170
|
});
|
|
226
171
|
}
|
|
227
172
|
|
|
@@ -239,41 +184,254 @@ _.extend(Movable.prototype, {
|
|
|
239
184
|
|
|
240
185
|
// Trigger an add event if this hasn't been added before
|
|
241
186
|
if (!state.added) {
|
|
242
|
-
|
|
187
|
+
// TODO(benchristel): No one seems to handle the modify event. Can
|
|
188
|
+
// we delete this?
|
|
189
|
+
// @ts-expect-error - TS2345: Argument of type '{}' is not assignable to parameter of type 'State'.
|
|
190
|
+
this._fireEvent(state.modify, this.cloneState(), {});
|
|
243
191
|
state.added = true;
|
|
244
192
|
|
|
245
193
|
// Update the state for `added` and in case the add event
|
|
246
194
|
// changed it
|
|
247
|
-
|
|
195
|
+
this.prevState = this.cloneState();
|
|
248
196
|
}
|
|
249
197
|
|
|
250
198
|
// Trigger a modify event
|
|
251
|
-
|
|
252
|
-
}
|
|
199
|
+
this._fireEvent(state.modify, this.cloneState(), this.prevState);
|
|
200
|
+
}
|
|
253
201
|
|
|
254
|
-
|
|
255
|
-
this.state
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
202
|
+
cloneState(): State {
|
|
203
|
+
return {...this.state};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Fire an onSomething type event to all functions in listeners
|
|
208
|
+
*/
|
|
209
|
+
_fireEvent<F extends (...args: any[]) => any>(
|
|
210
|
+
listeners: F[] | undefined,
|
|
211
|
+
...args: Parameters<F>
|
|
212
|
+
) {
|
|
213
|
+
if (listeners == null) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
for (const listener of listeners) {
|
|
217
|
+
listener.call(this, ...args);
|
|
261
218
|
}
|
|
262
|
-
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Call all draw functions, and update our prevState for the next
|
|
223
|
+
* draw function
|
|
224
|
+
*/
|
|
225
|
+
draw() {
|
|
226
|
+
const currState = this.cloneState();
|
|
227
|
+
this._fireEvent(this.state.draw, currState, this.prevState);
|
|
228
|
+
this.prevState = currState;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Simulates a mouse grab event on the movable object.
|
|
233
|
+
*/
|
|
234
|
+
grab(coord: Coord) {
|
|
235
|
+
assert(kpoint.is(coord));
|
|
236
|
+
const graphie = this.graphie;
|
|
237
|
+
const state: State = this.state;
|
|
238
|
+
|
|
239
|
+
state.isHovering = true;
|
|
240
|
+
state.isDragging = true;
|
|
241
|
+
graphie.isDragging = true;
|
|
242
|
+
|
|
243
|
+
const startMouseCoord = coord;
|
|
244
|
+
let prevMouseCoord = startMouseCoord;
|
|
245
|
+
this._fireEvent(state.onMoveStart, startMouseCoord, startMouseCoord);
|
|
246
|
+
|
|
247
|
+
const moveHandler = (
|
|
248
|
+
e: Readonly<{
|
|
249
|
+
pageX?: number;
|
|
250
|
+
pageY?: number;
|
|
251
|
+
preventDefault(): void;
|
|
252
|
+
}>,
|
|
253
|
+
) => {
|
|
254
|
+
e.preventDefault();
|
|
255
|
+
|
|
256
|
+
const mouseCoord = graphie.getMouseCoord(e);
|
|
257
|
+
this._fireEvent(state.onMove, mouseCoord, prevMouseCoord);
|
|
258
|
+
this.draw();
|
|
259
|
+
prevMouseCoord = mouseCoord;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const upHandler = () => {
|
|
263
|
+
$(document).unbind("vmousemove", moveHandler);
|
|
264
|
+
$(document).unbind("vmouseup", upHandler);
|
|
265
|
+
if (state.isHovering) {
|
|
266
|
+
this._fireEvent(state.onClick, prevMouseCoord, startMouseCoord);
|
|
267
|
+
}
|
|
268
|
+
state.isHovering = this.state.isMouseOver;
|
|
269
|
+
state.isDragging = false;
|
|
270
|
+
graphie.isDragging = false;
|
|
271
|
+
this._fireEvent(state.onMoveEnd, prevMouseCoord, startMouseCoord);
|
|
272
|
+
this.draw();
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
$(document).bind("vmousemove", moveHandler);
|
|
276
|
+
$(document).bind("vmouseup", upHandler);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
_applyConstraints(
|
|
280
|
+
current: Coord,
|
|
281
|
+
previous: Coord,
|
|
282
|
+
extraOptions?: ConstraintCallbacks,
|
|
283
|
+
) {
|
|
284
|
+
let skipRemaining = false;
|
|
285
|
+
|
|
286
|
+
return (this.state.constraints ?? []).reduce(
|
|
287
|
+
(memo: Coord | false, constraint) => {
|
|
288
|
+
// A move that has been cancelled won't be propagated to later
|
|
289
|
+
// constraints calls
|
|
290
|
+
if (memo === false) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (skipRemaining) {
|
|
295
|
+
return memo;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const result = constraint.call(this, memo, previous, {
|
|
299
|
+
onSkipRemaining: () => {
|
|
300
|
+
skipRemaining = true;
|
|
301
|
+
},
|
|
302
|
+
...extraOptions,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
if (result === false) {
|
|
306
|
+
// Returning false cancels the move
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
if (kpoint.is(result, 2)) {
|
|
310
|
+
// Returning a coord from constraints overrides the move
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
if (result === true || result == null) {
|
|
314
|
+
// Returning true or undefined allow the move to occur
|
|
315
|
+
return memo;
|
|
316
|
+
}
|
|
317
|
+
// Anything else is an error
|
|
318
|
+
throw new PerseusError(
|
|
319
|
+
"Constraint returned invalid result: " + result,
|
|
320
|
+
Errors.Internal,
|
|
321
|
+
);
|
|
322
|
+
},
|
|
323
|
+
current,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
263
326
|
|
|
264
327
|
// Change z-order to back
|
|
265
|
-
toBack
|
|
328
|
+
toBack() {
|
|
266
329
|
if (this.state.mouseTarget) {
|
|
267
330
|
this.state.mouseTarget.toBack();
|
|
268
331
|
}
|
|
269
|
-
}
|
|
332
|
+
}
|
|
270
333
|
|
|
271
334
|
// Change z-order to front
|
|
272
|
-
toFront
|
|
335
|
+
toFront() {
|
|
273
336
|
if (this.state.mouseTarget) {
|
|
274
337
|
this.state.mouseTarget.toFront();
|
|
275
338
|
}
|
|
276
|
-
}
|
|
277
|
-
});
|
|
339
|
+
}
|
|
278
340
|
|
|
279
|
-
|
|
341
|
+
/**
|
|
342
|
+
* Add a listener to any event: startMove, constraints, onMove, onMoveEnd,
|
|
343
|
+
* etc. If a listener is already bound to the given eventName and id, then
|
|
344
|
+
* it is overwritten by func.
|
|
345
|
+
*
|
|
346
|
+
* eventName: the string name of the event to listen to. one of:
|
|
347
|
+
* "onMoveStart", "onMove", "onMoveEnd", "draw", "remove"
|
|
348
|
+
*
|
|
349
|
+
* id: a string id that can be used to remove this event at a later time
|
|
350
|
+
* note: adding multiple listeners with the same id is undefined behavior
|
|
351
|
+
*
|
|
352
|
+
* func: the function to call when the event happens, which is called
|
|
353
|
+
* with the event's standard parameters [usually (coord, prevCoord) or
|
|
354
|
+
* (state, prevState)]
|
|
355
|
+
*/
|
|
356
|
+
listen(eventName: string, id: string, func: () => unknown) {
|
|
357
|
+
this._listenerMap = this._listenerMap || {};
|
|
358
|
+
|
|
359
|
+
// If there's an existing handler, replace it by using its index in
|
|
360
|
+
// `this.state[eventName]`; otherwise, add this handler to the end
|
|
361
|
+
const key = getKey(eventName, id);
|
|
362
|
+
const index = (this._listenerMap[key] =
|
|
363
|
+
this._listenerMap[key] || this.state[eventName].length);
|
|
364
|
+
this.state[eventName][index] = func;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Remove a previously added listener, by the id specified in the
|
|
369
|
+
* corresponding listen() call
|
|
370
|
+
*
|
|
371
|
+
* If the given id has not been registered already, this is a no-op
|
|
372
|
+
*/
|
|
373
|
+
unlisten(eventName: string, id: string) {
|
|
374
|
+
this._listenerMap = this._listenerMap || {};
|
|
375
|
+
|
|
376
|
+
const key = getKey(eventName, id);
|
|
377
|
+
const index = this._listenerMap[key];
|
|
378
|
+
if (index !== undefined) {
|
|
379
|
+
// Remove handler from list of event handlers and listenerMap
|
|
380
|
+
this.state[eventName].splice(index, 1);
|
|
381
|
+
delete this._listenerMap[key];
|
|
382
|
+
|
|
383
|
+
// Re-index existing events: if they occur after `index`, decrement
|
|
384
|
+
const keys = _.keys(this._listenerMap);
|
|
385
|
+
keys.forEach((key) => {
|
|
386
|
+
if (
|
|
387
|
+
getEventName(key) === eventName &&
|
|
388
|
+
this._listenerMap[key] > index
|
|
389
|
+
) {
|
|
390
|
+
this._listenerMap[key]--;
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
remove() {
|
|
397
|
+
this.state.added = false;
|
|
398
|
+
this._fireEvent(this.state.remove);
|
|
399
|
+
if (this.state.mouseTarget) {
|
|
400
|
+
$(this.state.mouseTarget).off();
|
|
401
|
+
this.state.mouseTarget.remove();
|
|
402
|
+
this.state.mouseTarget = null;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
cursor() {
|
|
407
|
+
return this.state.cursor;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
added() {
|
|
411
|
+
return this.state.added;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
isHovering() {
|
|
415
|
+
return this.state.isHovering;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
isMouseOver() {
|
|
419
|
+
return this.state.isMouseOver;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
isDragging() {
|
|
423
|
+
return this.state.isDragging;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
mouseTarget() {
|
|
427
|
+
return this.state.mouseTarget;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function getKey(eventName: string, id: string): string {
|
|
432
|
+
return eventName + ":" + id;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function getEventName(key: string): string {
|
|
436
|
+
return key.split(":")[0];
|
|
437
|
+
}
|
|
@@ -1,2 +1,17 @@
|
|
|
1
|
+
import type {Movable} from "./movable";
|
|
2
|
+
|
|
1
3
|
export type Coord = [number, number]; // x, y
|
|
2
4
|
export type Line = [Coord, Coord]; // start, end
|
|
5
|
+
export type Constraint = (
|
|
6
|
+
this: Movable<any>,
|
|
7
|
+
current: Coord | boolean,
|
|
8
|
+
previous: Coord,
|
|
9
|
+
options: ConstraintCallbacks,
|
|
10
|
+
) => Coord | boolean | undefined;
|
|
11
|
+
|
|
12
|
+
// TODO(benchristel): these callbacks feel overcomplicated. Can we
|
|
13
|
+
// get rid of them?
|
|
14
|
+
export type ConstraintCallbacks = {
|
|
15
|
+
onSkipRemaining(): void;
|
|
16
|
+
onOutOfBounds?: () => void;
|
|
17
|
+
};
|
package/src/interactive2.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Movable from "./interactive2/movable";
|
|
1
|
+
import {Movable} from "./interactive2/movable";
|
|
2
2
|
import MovableLine from "./interactive2/movable-line";
|
|
3
3
|
import MovablePoint from "./interactive2/movable-point";
|
|
4
4
|
import MovablePolygon from "./interactive2/movable-polygon";
|
package/src/util/graphie.ts
CHANGED
|
@@ -111,6 +111,11 @@ export class Graphie {
|
|
|
111
111
|
xpixels?: number;
|
|
112
112
|
ypixels?: number;
|
|
113
113
|
|
|
114
|
+
// set by Movables
|
|
115
|
+
// TODO(benchristel): consider putting this state on the Movable instead of
|
|
116
|
+
// on the Graphie
|
|
117
|
+
isDragging?: boolean;
|
|
118
|
+
|
|
114
119
|
constructor(el: Element) {
|
|
115
120
|
this.el = el;
|
|
116
121
|
$(el).css("position", "relative");
|
|
@@ -1401,6 +1406,11 @@ export class Graphie {
|
|
|
1401
1406
|
|
|
1402
1407
|
return processed;
|
|
1403
1408
|
}
|
|
1409
|
+
|
|
1410
|
+
// This is a stub that's overridden in interactive.ts
|
|
1411
|
+
getMouseCoord(event: Readonly<{pageX?: number; pageY?: number}>): Coord {
|
|
1412
|
+
throw new Error("getMouseCoord is a stub, and is not implemented");
|
|
1413
|
+
}
|
|
1404
1414
|
}
|
|
1405
1415
|
|
|
1406
1416
|
const labelDirections = {
|
package/src/util/interactive.ts
CHANGED
|
@@ -198,7 +198,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
198
198
|
/**
|
|
199
199
|
* Get mouse coordinates in pixels
|
|
200
200
|
*/
|
|
201
|
-
getMousePx: function (event) {
|
|
201
|
+
getMousePx: function (event: Readonly<{pageX?: number; pageY?: number}>) {
|
|
202
202
|
const graphie = this;
|
|
203
203
|
|
|
204
204
|
const mouseX =
|
|
@@ -214,7 +214,9 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
214
214
|
/**
|
|
215
215
|
* Get mouse coordinates in graph coordinates
|
|
216
216
|
*/
|
|
217
|
-
getMouseCoord: function (
|
|
217
|
+
getMouseCoord: function (
|
|
218
|
+
event: Readonly<{pageX?: number; pageY?: number}>,
|
|
219
|
+
): Coord {
|
|
218
220
|
return this.unscalePoint(this.getMousePx(event));
|
|
219
221
|
},
|
|
220
222
|
|