@khanacademy/perseus 19.1.3 → 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 +21 -0
- package/dist/components/graphie.d.ts +9 -1
- package/dist/es/index.js +275 -174
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +289 -158
- package/dist/index.js.map +1 -1
- package/dist/interactive2/interactive-util.d.ts +2 -2
- package/dist/interactive2/movable-point-options.d.ts +5 -4
- package/dist/interactive2/movable.d.ts +98 -2
- package/dist/interactive2/types.d.ts +6 -0
- package/dist/util/graphie.d.ts +6 -1
- 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 +43 -30
- package/src/interactive2/interactive-util.test.ts +93 -0
- package/src/interactive2/interactive-util.ts +19 -15
- package/src/interactive2/movable-helper-methods.ts +7 -2
- package/src/interactive2/movable-line.ts +1 -12
- package/src/interactive2/movable-point-options.ts +31 -19
- package/src/interactive2/movable-point.tsx +1 -9
- package/src/interactive2/movable-polygon.ts +1 -9
- package/src/interactive2/movable.test.ts +249 -0
- package/src/interactive2/movable.ts +313 -150
- package/src/interactive2/types.ts +15 -0
- package/src/interactive2.ts +1 -1
- package/src/util/graphie.ts +13 -1
- package/src/util/interactive.ts +4 -2
- package/tsconfig-build.tsbuildinfo +1 -1
|
@@ -13,27 +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
|
|
|
21
|
+
import type {Constraint, ConstraintCallbacks, Coord} from "./types";
|
|
22
|
+
import type {Graphie} from "../util/graphie";
|
|
23
|
+
|
|
18
24
|
const normalizeOptions = InteractiveUtil.normalizeOptions;
|
|
19
25
|
|
|
20
26
|
const assert = InteractiveUtil.assert;
|
|
21
27
|
|
|
22
|
-
//
|
|
23
|
-
// functions
|
|
24
|
-
const FUNCTION_ARRAY_OPTIONS = [
|
|
25
|
-
"add",
|
|
26
|
-
"modify",
|
|
27
|
-
"draw",
|
|
28
|
-
"remove",
|
|
29
|
-
"onMoveStart",
|
|
30
|
-
"onMove",
|
|
31
|
-
"onMoveEnd",
|
|
32
|
-
"onClick",
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
// Default "props" and "state". Both are added to this.state and
|
|
36
|
-
// receive magic getter methods (this.isHovering() etc).
|
|
28
|
+
// Default "props" and "state". Both are added to this.state.
|
|
37
29
|
// However, properties in DEFAULT_PROPS are updated on `modify()`,
|
|
38
30
|
// while those in DEFAULT_STATE persist and are not updated.
|
|
39
31
|
// Things that the user might want to change should be on "props",
|
|
@@ -49,120 +41,78 @@ const DEFAULT_STATE = {
|
|
|
49
41
|
mouseTarget: null,
|
|
50
42
|
} as const;
|
|
51
43
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
export interface State {
|
|
45
|
+
added?: boolean;
|
|
46
|
+
isHovering?: boolean;
|
|
47
|
+
isMouseOver?: boolean;
|
|
48
|
+
isDragging?: boolean;
|
|
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;
|
|
57
|
+
id: string;
|
|
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[];
|
|
72
|
+
}
|
|
73
|
+
|
|
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 = {
|
|
58
84
|
id: _.uniqueId("movable"),
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// We only set DEFAULT_STATE once, here
|
|
63
|
-
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
64
|
-
this.modify(_.extend({}, DEFAULT_STATE, options));
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
InteractiveUtil.createGettersFor(
|
|
68
|
-
Movable,
|
|
69
|
-
_.extend({}, DEFAULT_PROPS, DEFAULT_STATE),
|
|
70
|
-
);
|
|
71
|
-
InteractiveUtil.addMovableHelperMethodsTo(Movable);
|
|
72
|
-
|
|
73
|
-
_.extend(Movable.prototype, {
|
|
74
|
-
cloneState: function () {
|
|
75
|
-
return _.clone(this.state);
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
_createDefaultState: function () {
|
|
79
|
-
return _.extend(
|
|
80
|
-
{
|
|
81
|
-
id: this.state.id,
|
|
82
|
-
add: [],
|
|
83
|
-
modify: [],
|
|
84
|
-
draw: [],
|
|
85
|
-
remove: [],
|
|
86
|
-
onMoveStart: [],
|
|
87
|
-
onMove: [],
|
|
88
|
-
onMoveEnd: [],
|
|
89
|
-
onClick: [],
|
|
90
|
-
|
|
91
|
-
// We only update props here, because we want things on state to
|
|
92
|
-
// be persistent, and updated appropriately in modify()
|
|
93
|
-
},
|
|
94
|
-
DEFAULT_PROPS,
|
|
95
|
-
);
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Resets the object to its state as if it were constructed with
|
|
100
|
-
* `options` originally. The only state maintained is `state.id`
|
|
101
|
-
*
|
|
102
|
-
* Analogous to React.js's replaceProps
|
|
103
|
-
*/
|
|
104
|
-
modify: function (options) {
|
|
105
|
-
this.update(_.extend({}, this._createDefaultState(), options));
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Simulates a mouse grab event on the movable object.
|
|
110
|
-
*/
|
|
111
|
-
grab: function (coord) {
|
|
112
|
-
assert(kpoint.is(coord));
|
|
113
|
-
const self = this;
|
|
114
|
-
const graphie = self.graphie;
|
|
115
|
-
const state = self.state;
|
|
116
|
-
|
|
117
|
-
state.isHovering = true;
|
|
118
|
-
state.isDragging = true;
|
|
119
|
-
graphie.isDragging = true;
|
|
120
|
-
|
|
121
|
-
const startMouseCoord = coord;
|
|
122
|
-
let prevMouseCoord = startMouseCoord;
|
|
123
|
-
self._fireEvent(state.onMoveStart, startMouseCoord, startMouseCoord);
|
|
124
|
-
|
|
125
|
-
const moveHandler = function (e: any) {
|
|
126
|
-
e.preventDefault();
|
|
127
|
-
|
|
128
|
-
const mouseCoord = graphie.getMouseCoord(e);
|
|
129
|
-
self._fireEvent(state.onMove, mouseCoord, prevMouseCoord);
|
|
130
|
-
self.draw();
|
|
131
|
-
prevMouseCoord = mouseCoord;
|
|
132
85
|
};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
86
|
+
this.modify({...DEFAULT_STATE, ...options});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
modify(options: Options) {
|
|
90
|
+
this.update({...this._createDefaultState(), ...options});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
_createDefaultState() {
|
|
94
|
+
return {
|
|
95
|
+
id: this.state.id,
|
|
96
|
+
add: [],
|
|
97
|
+
modify: [],
|
|
98
|
+
draw: [],
|
|
99
|
+
remove: [],
|
|
100
|
+
onMoveStart: [],
|
|
101
|
+
onMove: [],
|
|
102
|
+
onMoveEnd: [],
|
|
103
|
+
onClick: [],
|
|
104
|
+
|
|
105
|
+
// We only update props here, because we want things on state to
|
|
106
|
+
// be persistent, and updated appropriately in modify()
|
|
107
|
+
...DEFAULT_PROPS,
|
|
145
108
|
};
|
|
109
|
+
}
|
|
146
110
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
},
|
|
111
|
+
update(options: Options) {
|
|
112
|
+
const graphie = this.graphie;
|
|
150
113
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
* for any option not specified
|
|
154
|
-
*
|
|
155
|
-
* Analogous to React.js's setProps
|
|
156
|
-
*/
|
|
157
|
-
update: function (options) {
|
|
158
|
-
const self = this;
|
|
159
|
-
const graphie = self.graphie;
|
|
160
|
-
|
|
161
|
-
const prevState = self.cloneState();
|
|
162
|
-
const state = _.extend(
|
|
163
|
-
self.state,
|
|
164
|
-
normalizeOptions(FUNCTION_ARRAY_OPTIONS, options),
|
|
165
|
-
);
|
|
114
|
+
const prevState = this.cloneState();
|
|
115
|
+
const state = Object.assign(this.state, normalizeOptions(options));
|
|
166
116
|
|
|
167
117
|
// the invisible shape in front of the point that gets mouse events
|
|
168
118
|
if (state.mouseTarget && !prevState.mouseTarget) {
|
|
@@ -176,25 +126,25 @@ _.extend(Movable.prototype, {
|
|
|
176
126
|
const isMouse = !("ontouchstart" in window);
|
|
177
127
|
|
|
178
128
|
if (isMouse) {
|
|
179
|
-
$mouseTarget.on("vmouseover",
|
|
129
|
+
$mouseTarget.on("vmouseover", () => {
|
|
180
130
|
state.isMouseOver = true;
|
|
181
131
|
if (!graphie.isDragging) {
|
|
182
132
|
state.isHovering = true;
|
|
183
133
|
}
|
|
184
|
-
if (
|
|
134
|
+
if (this.state.added) {
|
|
185
135
|
// Avoid drawing if the point has been removed
|
|
186
|
-
|
|
136
|
+
this.draw();
|
|
187
137
|
}
|
|
188
138
|
});
|
|
189
139
|
|
|
190
|
-
$mouseTarget.on("vmouseout",
|
|
140
|
+
$mouseTarget.on("vmouseout", () => {
|
|
191
141
|
state.isMouseOver = false;
|
|
192
142
|
if (!state.isDragging) {
|
|
193
143
|
state.isHovering = false;
|
|
194
144
|
}
|
|
195
|
-
if (
|
|
145
|
+
if (this.state.added) {
|
|
196
146
|
// Avoid drawing if the point has been removed
|
|
197
|
-
|
|
147
|
+
this.draw();
|
|
198
148
|
}
|
|
199
149
|
});
|
|
200
150
|
}
|
|
@@ -209,14 +159,14 @@ _.extend(Movable.prototype, {
|
|
|
209
159
|
{passive: false},
|
|
210
160
|
);
|
|
211
161
|
|
|
212
|
-
$mouseTarget.on("vmousedown",
|
|
162
|
+
$mouseTarget.on("vmousedown", (e) => {
|
|
213
163
|
if (e.which !== 0 && e.which !== 1) {
|
|
214
164
|
return;
|
|
215
165
|
}
|
|
216
166
|
e.preventDefault();
|
|
217
167
|
|
|
218
168
|
const mouseCoord = graphie.getMouseCoord(e);
|
|
219
|
-
|
|
169
|
+
this.grab(mouseCoord);
|
|
220
170
|
});
|
|
221
171
|
}
|
|
222
172
|
|
|
@@ -234,41 +184,254 @@ _.extend(Movable.prototype, {
|
|
|
234
184
|
|
|
235
185
|
// Trigger an add event if this hasn't been added before
|
|
236
186
|
if (!state.added) {
|
|
237
|
-
|
|
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(), {});
|
|
238
191
|
state.added = true;
|
|
239
192
|
|
|
240
193
|
// Update the state for `added` and in case the add event
|
|
241
194
|
// changed it
|
|
242
|
-
|
|
195
|
+
this.prevState = this.cloneState();
|
|
243
196
|
}
|
|
244
197
|
|
|
245
198
|
// Trigger a modify event
|
|
246
|
-
|
|
247
|
-
}
|
|
199
|
+
this._fireEvent(state.modify, this.cloneState(), this.prevState);
|
|
200
|
+
}
|
|
248
201
|
|
|
249
|
-
|
|
250
|
-
this.state
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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;
|
|
256
215
|
}
|
|
257
|
-
|
|
216
|
+
for (const listener of listeners) {
|
|
217
|
+
listener.call(this, ...args);
|
|
218
|
+
}
|
|
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
|
+
}
|
|
258
326
|
|
|
259
327
|
// Change z-order to back
|
|
260
|
-
toBack
|
|
328
|
+
toBack() {
|
|
261
329
|
if (this.state.mouseTarget) {
|
|
262
330
|
this.state.mouseTarget.toBack();
|
|
263
331
|
}
|
|
264
|
-
}
|
|
332
|
+
}
|
|
265
333
|
|
|
266
334
|
// Change z-order to front
|
|
267
|
-
toFront
|
|
335
|
+
toFront() {
|
|
268
336
|
if (this.state.mouseTarget) {
|
|
269
337
|
this.state.mouseTarget.toFront();
|
|
270
338
|
}
|
|
271
|
-
}
|
|
272
|
-
|
|
339
|
+
}
|
|
340
|
+
|
|
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
|
+
}
|
|
273
434
|
|
|
274
|
-
|
|
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");
|
|
@@ -121,13 +126,15 @@ export class Graphie {
|
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
init(options: {
|
|
124
|
-
range
|
|
129
|
+
range: [Interval, Interval];
|
|
125
130
|
scale?: number | Coord;
|
|
126
131
|
isMobile?: boolean;
|
|
127
132
|
}) {
|
|
128
133
|
let scale = options.scale || [40, 40];
|
|
129
134
|
scale = typeof scale === "number" ? [scale, scale] : scale;
|
|
130
135
|
|
|
136
|
+
// TODO(jeremy): This can be removed once we're confident that callers
|
|
137
|
+
// obey the Typescript types.
|
|
131
138
|
if (options.range == null) {
|
|
132
139
|
throw new PerseusError(
|
|
133
140
|
"range should be specified in graph init",
|
|
@@ -1399,6 +1406,11 @@ export class Graphie {
|
|
|
1399
1406
|
|
|
1400
1407
|
return processed;
|
|
1401
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
|
+
}
|
|
1402
1414
|
}
|
|
1403
1415
|
|
|
1404
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
|
|