@khanacademy/perseus 19.1.2 → 19.1.4

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.
Files changed (69) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/all-widgets.d.ts +1 -1
  3. package/dist/basic-widgets.d.ts +1 -1
  4. package/dist/es/index.js +38 -70
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/extra-widgets.d.ts +1 -1
  7. package/dist/index.js +51 -55
  8. package/dist/index.js.map +1 -1
  9. package/dist/interactive2/interactive-util.d.ts +1 -1
  10. package/dist/interactive2/movable.d.ts +20 -1
  11. package/dist/types.d.ts +1 -1
  12. package/dist/util/graphie.d.ts +1 -1
  13. package/dist/widgets/categorizer.d.ts +1 -1
  14. package/dist/widgets/cs-program.d.ts +1 -1
  15. package/dist/widgets/definition.d.ts +1 -1
  16. package/dist/widgets/deprecated-standin/index.d.ts +1 -1
  17. package/dist/widgets/dropdown.d.ts +1 -1
  18. package/dist/widgets/explanation.d.ts +1 -1
  19. package/dist/widgets/expression.d.ts +14 -14
  20. package/dist/widgets/graded-group-set.d.ts +1 -1
  21. package/dist/widgets/graded-group.d.ts +1 -1
  22. package/dist/widgets/grapher.d.ts +1 -1
  23. package/dist/widgets/group.d.ts +1 -1
  24. package/dist/widgets/iframe.d.ts +1 -1
  25. package/dist/widgets/image.d.ts +1 -1
  26. package/dist/widgets/input-number.d.ts +1 -1
  27. package/dist/widgets/interaction.d.ts +1 -1
  28. package/dist/widgets/interactive-graph.d.ts +1 -1
  29. package/dist/widgets/label-image.d.ts +14 -14
  30. package/dist/widgets/lights-puzzle.d.ts +1 -1
  31. package/dist/widgets/matcher.d.ts +1 -1
  32. package/dist/widgets/matrix.d.ts +1 -1
  33. package/dist/widgets/measurer.d.ts +1 -1
  34. package/dist/widgets/molecule.d.ts +1 -1
  35. package/dist/widgets/number-line.d.ts +1 -1
  36. package/dist/widgets/numeric-input.d.ts +1 -1
  37. package/dist/widgets/orderer.d.ts +1 -1
  38. package/dist/widgets/passage-ref-target.d.ts +1 -1
  39. package/dist/widgets/passage-ref.d.ts +1 -1
  40. package/dist/widgets/passage.d.ts +1 -1
  41. package/dist/widgets/plotter.d.ts +1 -1
  42. package/dist/widgets/radio.d.ts +1 -1
  43. package/dist/widgets/reaction-diagram.d.ts +1 -1
  44. package/dist/widgets/sequence.d.ts +1 -1
  45. package/dist/widgets/simulator.d.ts +1 -1
  46. package/dist/widgets/sorter.d.ts +1 -1
  47. package/dist/widgets/table.d.ts +1 -1
  48. package/dist/widgets/unit.d.ts +1 -1
  49. package/dist/widgets/video.d.ts +1 -1
  50. package/package.json +2 -2
  51. package/src/__tests__/init.test.ts +10 -0
  52. package/src/init.ts +1 -4
  53. package/src/interactive2/__tests__/movable-mock.ts +2 -1
  54. package/src/interactive2/__tests__/movable.test.ts +157 -0
  55. package/src/interactive2/interactive-util.test.ts +93 -0
  56. package/src/interactive2/interactive-util.ts +19 -15
  57. package/src/interactive2/movable-helper-methods.ts +7 -2
  58. package/src/interactive2/movable-line.ts +1 -12
  59. package/src/interactive2/movable-point.tsx +1 -9
  60. package/src/interactive2/movable-polygon.ts +1 -9
  61. package/src/interactive2/movable.ts +49 -44
  62. package/src/types.ts +4 -2
  63. package/src/util/graphie.ts +3 -1
  64. package/src/widgets/__tests__/__snapshots__/input-number.test.ts.snap +1 -1
  65. package/src/widgets/__tests__/__snapshots__/numeric-input.test.ts.snap +1 -1
  66. package/src/widgets/label-image/marker.tsx +4 -0
  67. package/src/widgets/label-image.tsx +2 -7
  68. package/src/widgets.ts +4 -1
  69. package/tsconfig-build.tsbuildinfo +1 -1
@@ -58,6 +58,7 @@ declare const _default: Readonly<{
58
58
  name: string;
59
59
  displayName: string;
60
60
  getWidget?: (() => typeof OldUnitInput) | undefined;
61
+ widget: typeof OldUnitInput;
61
62
  accessible?: boolean | ((props: any) => boolean) | undefined;
62
63
  hidden?: boolean | undefined;
63
64
  version?: import("../types").Version | undefined;
@@ -72,6 +73,5 @@ declare const _default: Readonly<{
72
73
  propUpgrades?: {
73
74
  [key: string]: (arg1: any) => any;
74
75
  } | undefined;
75
- widget: typeof OldUnitInput;
76
76
  }>;
77
77
  export default _default;
@@ -29,6 +29,7 @@ declare const _default: Readonly<{
29
29
  name: string;
30
30
  displayName: string;
31
31
  getWidget?: (() => typeof Video) | undefined;
32
+ widget: typeof Video;
32
33
  accessible?: boolean | ((props: any) => boolean) | undefined;
33
34
  hidden?: boolean | undefined;
34
35
  version?: import("../types").Version | undefined;
@@ -43,6 +44,5 @@ declare const _default: Readonly<{
43
44
  propUpgrades?: {
44
45
  [key: string]: (arg1: any) => any;
45
46
  } | undefined;
46
- widget: typeof Video;
47
47
  }>;
48
48
  export default _default;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Core Perseus API (includes renderers and widgets)",
4
4
  "author": "Khan Academy",
5
5
  "license": "MIT",
6
- "version": "19.1.2",
6
+ "version": "19.1.4",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -24,7 +24,7 @@
24
24
  "dependencies": {
25
25
  "@khanacademy/kas": "^0.3.7",
26
26
  "@khanacademy/kmath": "^0.1.10",
27
- "@khanacademy/math-input": "^17.0.1",
27
+ "@khanacademy/math-input": "^17.0.3",
28
28
  "@khanacademy/perseus-core": "1.4.1",
29
29
  "@khanacademy/perseus-linter": "^0.3.10",
30
30
  "@khanacademy/pure-markdown": "^0.2.13",
@@ -0,0 +1,10 @@
1
+ import init from "../init";
2
+ import {getWidget} from "../widgets";
3
+
4
+ describe("init", () => {
5
+ it("should correctly replace the transformer widget", async () => {
6
+ await init({skipMathJax: true});
7
+
8
+ expect(getWidget("transformer")).not.toBeNull();
9
+ });
10
+ });
package/src/init.ts CHANGED
@@ -21,10 +21,7 @@ const init = function (options: PerseusOptions): Promise<undefined> {
21
21
  Widgets.registerWidgets(basicWidgets);
22
22
  Widgets.registerWidgets(extraWidgets);
23
23
 
24
- // This section replaces widgets
25
- // When it's time to replace the transformer widget
26
- // uncomment this line
27
- // Widgets.replaceWidget("transformer", "deprecated-standin");
24
+ Widgets.replaceWidget("transformer", "deprecated-standin");
28
25
 
29
26
  // Pass skipMathJax: true if MathJax is already loaded and configured.
30
27
  const skipMathJax = options.skipMathJax;
@@ -4,10 +4,11 @@
4
4
 
5
5
  import _ from "underscore";
6
6
 
7
+ import {Graphie} from "../../util/graphie";
7
8
  import Movable from "../movable";
8
9
 
9
10
  const createMock = function (): any {
10
- const movable = new Movable(null, {
11
+ const movable = new Movable(new Graphie(document.createElement("div")), {
11
12
  mouseTarget: null,
12
13
  });
13
14
  movable.modify = function (options: any) {
@@ -0,0 +1,157 @@
1
+ /* eslint-disable testing-library/prefer-user-event */
2
+ import {fireEvent} from "@testing-library/react";
3
+
4
+ import {Graphie} from "../../util/graphie";
5
+ import Movable from "../movable";
6
+
7
+ // Yay for side-effect imports!
8
+ // eslint-disable-next-line import/no-unassigned-import
9
+ import "../../util/interactive";
10
+
11
+ describe("Movable", () => {
12
+ it("should be constructable", () => {
13
+ const movable = new Movable(
14
+ new Graphie(document.createElement("div")),
15
+ {},
16
+ );
17
+
18
+ expect(movable).toBeDefined();
19
+ });
20
+
21
+ it("should have accessor properties", () => {
22
+ const movable = new Movable(
23
+ new Graphie(document.createElement("div")),
24
+ {},
25
+ );
26
+
27
+ expect(typeof movable.isHovering).toBe("function");
28
+ expect(typeof movable.cursor).toBe("function");
29
+ expect(typeof movable.added).toBe("function");
30
+ expect(typeof movable.isHovering).toBe("function");
31
+ expect(typeof movable.isMouseOver).toBe("function");
32
+ expect(typeof movable.isDragging).toBe("function");
33
+ expect(typeof movable.mouseTarget).toBe("function");
34
+ });
35
+
36
+ test("cloneState()", () => {
37
+ const movable = new Movable(
38
+ new Graphie(document.createElement("div")),
39
+ {},
40
+ );
41
+
42
+ expect(movable.cloneState()).toEqual(
43
+ expect.objectContaining({
44
+ id: expect.any(String),
45
+ add: [],
46
+ added: true,
47
+ cursor: null,
48
+ draw: [],
49
+ isDragging: false,
50
+ isHovering: false,
51
+ isMouseOver: false,
52
+ modify: [],
53
+ mouseTarget: null,
54
+ onClick: [],
55
+ onMove: [],
56
+ onMoveEnd: [],
57
+ onMoveStart: [],
58
+ remove: [],
59
+ }),
60
+ );
61
+ });
62
+
63
+ test("cloneState() should copy state", () => {
64
+ const movable = new Movable(
65
+ new Graphie(document.createElement("div")),
66
+ {},
67
+ );
68
+
69
+ expect(movable.cloneState()).not.toBe(movable.state);
70
+ });
71
+
72
+ test("modify() resets movable to initial state when no params given", () => {
73
+ const movable = new Movable(
74
+ new Graphie(document.createElement("div")),
75
+ {},
76
+ );
77
+
78
+ const state = movable.cloneState();
79
+ movable.state.added = false;
80
+
81
+ // It seems that all usages of this function in our production code
82
+ // pass the original state + mutations to modify(). However, it _does_
83
+ // work to pass no arguments so we'll keep this Characterization test
84
+ // here for now.
85
+ movable.modify();
86
+
87
+ expect(movable.cloneState()).toEqual(state);
88
+ });
89
+
90
+ test("modify() retains original ID", () => {
91
+ const movable = new Movable(
92
+ new Graphie(document.createElement("div")),
93
+ {},
94
+ );
95
+ const id = movable.state.id;
96
+
97
+ movable.modify({added: true});
98
+
99
+ expect(movable.state.id).toBe(id);
100
+ });
101
+
102
+ test("grab() fires onMoveStart", () => {
103
+ const graphie = new Graphie(document.createElement("div"));
104
+ graphie.init({
105
+ range: [
106
+ [-10, 10],
107
+ [-10, 10],
108
+ ],
109
+ });
110
+ const onMoveStart = jest.fn();
111
+ const movable = new Movable(graphie, {onMoveStart});
112
+
113
+ movable.grab([0, 0]);
114
+
115
+ expect(onMoveStart).toHaveBeenCalledWith([0, 0], [0, 0]);
116
+ });
117
+
118
+ test("grab() fires onMove when mouse moves", () => {
119
+ const graphie = new Graphie(document.createElement("div"));
120
+ graphie.init({
121
+ range: [
122
+ [-10, 10],
123
+ [-10, 10],
124
+ ],
125
+ scale: [10, 10],
126
+ });
127
+ const onMove = jest.fn();
128
+ const movable = new Movable(graphie, {onMove});
129
+
130
+ movable.grab([0, 0]);
131
+ fireEvent.mouseMove(document, {clientX: 120, clientY: 80});
132
+
133
+ expect(onMove).toHaveBeenCalledWith([2, 2], [0, 0]);
134
+ });
135
+
136
+ test("grab() fires onMoveEnd when mouse up", () => {
137
+ const dummyGraphie: any = {};
138
+ const onMoveEnd = jest.fn();
139
+ const movable = new Movable(dummyGraphie, {onMoveEnd});
140
+
141
+ movable.grab([0, 0]);
142
+ fireEvent.mouseUp(document);
143
+
144
+ expect(onMoveEnd).toHaveBeenCalledWith([0, 0], [0, 0]);
145
+ });
146
+
147
+ test("grab() fires onClick if still hovering when mouse up", () => {
148
+ const dummyGraphie: any = {};
149
+ const onClick = jest.fn();
150
+ const movable = new Movable(dummyGraphie, {onClick});
151
+
152
+ movable.grab([0, 0]);
153
+ fireEvent.mouseUp(document);
154
+
155
+ expect(onClick).toHaveBeenCalledWith([0, 0], [0, 0]);
156
+ });
157
+ });
@@ -0,0 +1,93 @@
1
+ import InteractiveUtil from "./interactive-util";
2
+
3
+ describe("normalizeOptions", () => {
4
+ it("wraps single functions in arrays", () => {
5
+ const options = {
6
+ add: () => {},
7
+ modify: () => {},
8
+ draw: () => {},
9
+ remove: () => {},
10
+ constraints: () => {},
11
+ onMoveStart: () => {},
12
+ onMove: () => {},
13
+ onMoveEnd: () => {},
14
+ onClick: () => {},
15
+ };
16
+ expect(InteractiveUtil.normalizeOptions(options)).toEqual({
17
+ add: [options.add],
18
+ modify: [options.modify],
19
+ draw: [options.draw],
20
+ remove: [options.remove],
21
+ constraints: [options.constraints],
22
+ onMoveStart: [options.onMoveStart],
23
+ onMove: [options.onMove],
24
+ onMoveEnd: [options.onMoveEnd],
25
+ onClick: [options.onClick],
26
+ });
27
+ });
28
+
29
+ it("tolerates missing properties", () => {
30
+ const options = {
31
+ add: () => {},
32
+ };
33
+ expect(InteractiveUtil.normalizeOptions(options)).toEqual({
34
+ add: [options.add],
35
+ });
36
+ });
37
+
38
+ it("ignores extra properties", () => {
39
+ const options = {
40
+ foo: () => {},
41
+ };
42
+ expect(InteractiveUtil.normalizeOptions(options)).toEqual({
43
+ foo: options.foo,
44
+ });
45
+ });
46
+
47
+ it("does not modify its argument", () => {
48
+ const options = {
49
+ add: () => {},
50
+ };
51
+ const optionsCopy = {...options};
52
+ InteractiveUtil.normalizeOptions(options);
53
+ expect(options).toEqual(optionsCopy);
54
+ });
55
+
56
+ it("leaves existing arrays of functions alone", () => {
57
+ const options = {
58
+ add: [() => {}],
59
+ modify: [() => {}],
60
+ draw: [() => {}],
61
+ remove: [() => {}],
62
+ onMoveStart: [() => {}],
63
+ onMove: [() => {}],
64
+ onMoveEnd: [() => {}],
65
+ onClick: [() => {}],
66
+ };
67
+ expect(InteractiveUtil.normalizeOptions(options)).toEqual(options);
68
+ });
69
+
70
+ it("flattens arrays", () => {
71
+ const dummyFunction = () => {};
72
+ const options = {
73
+ add: [[dummyFunction]],
74
+ modify: [[dummyFunction]],
75
+ draw: [[dummyFunction]],
76
+ remove: [[dummyFunction]],
77
+ onMoveStart: [[dummyFunction]],
78
+ onMove: [[dummyFunction]],
79
+ onMoveEnd: [[dummyFunction]],
80
+ onClick: [[dummyFunction]],
81
+ };
82
+ expect(InteractiveUtil.normalizeOptions(options)).toEqual({
83
+ add: [dummyFunction],
84
+ modify: [dummyFunction],
85
+ draw: [dummyFunction],
86
+ remove: [dummyFunction],
87
+ onMoveStart: [dummyFunction],
88
+ onMove: [dummyFunction],
89
+ onMoveEnd: [dummyFunction],
90
+ onClick: [dummyFunction],
91
+ });
92
+ });
93
+ });
@@ -52,6 +52,18 @@ function computeCanUse3dTransform(): boolean {
52
52
  return !!el.style[prefix];
53
53
  }
54
54
 
55
+ const FUNCTION_ARRAY_OPTIONS = [
56
+ "add",
57
+ "modify",
58
+ "draw",
59
+ "remove",
60
+ "constraints",
61
+ "onMoveStart",
62
+ "onMove",
63
+ "onMoveEnd",
64
+ "onClick",
65
+ ];
66
+
55
67
  const InteractiveUtil = {
56
68
  assert: function (isTrue: boolean, message?: string) {
57
69
  if (!isTrue) {
@@ -106,24 +118,16 @@ const InteractiveUtil = {
106
118
  /**
107
119
  * Convert all function-or-array arguments to arrays of functions
108
120
  */
109
- normalizeOptions: function (
110
- arrayOptionNames: ReadonlyArray<string>,
111
- options: Record<any, any>,
112
- ): Record<any, any> {
113
- // TODO(jack): Having to clone here is annoying; this
114
- // function should really just modify this.state in place
115
- // (and maybe be a function on MovableHelperMethods to get access
116
- // to this.state), which would also be nicer because we could
117
- // normalizeOptions once in this.modify
118
- const result = _.clone(options);
119
- _.each(arrayOptionNames, function (eventName) {
120
- const funcOrArray = options[eventName];
121
+ normalizeOptions: function (options: Record<any, any>): Record<any, any> {
122
+ const result = {...options};
123
+ FUNCTION_ARRAY_OPTIONS.forEach(function (eventName) {
121
124
  // Only propagate values which were set; not present values
122
125
  // shouldn't be added to options because we'd like them to
123
126
  // fall through to defaults
124
- if (funcOrArray !== undefined) {
125
- const funcArray = InteractiveUtil.arrayify(funcOrArray);
126
- result[eventName] = funcArray;
127
+ if (options[eventName] !== undefined) {
128
+ result[eventName] = InteractiveUtil.arrayify(
129
+ options[eventName],
130
+ );
127
131
  }
128
132
  });
129
133
  return result;
@@ -27,8 +27,13 @@ const MovableHelperMethods: any = {
27
27
  /**
28
28
  * Fire an onSomething type event to all functions in listeners
29
29
  */
30
- _fireEvent: function (listeners, currentValue, previousValue) {
31
- _.invoke(listeners, "call", this, currentValue, previousValue);
30
+ _fireEvent: function <F extends (...args: any[]) => any>(
31
+ listeners: F[],
32
+ ...args: Parameters<F>
33
+ ) {
34
+ for (const listener of listeners) {
35
+ listener.call(this, ...args);
36
+ }
32
37
  },
33
38
 
34
39
  /**
@@ -14,16 +14,6 @@ import WrappedLine from "./wrapped-line";
14
14
  const assert = InteractiveUtil.assert;
15
15
  const normalizeOptions = InteractiveUtil.normalizeOptions;
16
16
 
17
- const FUNCTION_ARRAY_OPTIONS = [
18
- "add",
19
- "draw",
20
- "remove",
21
- "onMoveStart",
22
- "constraints",
23
- "onMove",
24
- "onMoveEnd",
25
- ];
26
-
27
17
  // Default "props" and "state". Both are added to this.state and
28
18
  // receive magic getter methods (this.cursor() etc).
29
19
  // However, properties in DEFAULT_PROPS are updated on `modify()`,
@@ -87,7 +77,6 @@ _.extend(MovableLine.prototype, {
87
77
  id: this.state.id,
88
78
  },
89
79
  normalizeOptions(
90
- FUNCTION_ARRAY_OPTIONS,
91
80
  // Defaults are copied from MovableLineOptions.*.standard
92
81
  // These defaults are set here instead of DEFAULT_PROPS/STATE
93
82
  // because they:
@@ -121,7 +110,7 @@ _.extend(MovableLine.prototype, {
121
110
  const graphie = this.graphie;
122
111
  const state = (self.state = _.extend(
123
112
  self.state,
124
- normalizeOptions(FUNCTION_ARRAY_OPTIONS, options),
113
+ normalizeOptions(options),
125
114
  ));
126
115
 
127
116
  // Default things inside the state.normalStyle object, because
@@ -69,10 +69,6 @@ const normalizeOptions = InteractiveUtil.normalizeOptions;
69
69
 
70
70
  const {processMath} = Tex;
71
71
 
72
- // State parameters that should be converted into an array of
73
- // functions
74
- const FUNCTION_ARRAY_OPTIONS = _.keys(MovablePointOptions);
75
-
76
72
  // Default "props" and "state". Both are added to this.state and
77
73
  // receive magic getter methods (this.coord() etc).
78
74
  // However, properties in DEFAULT_PROPS are updated on `modify()`,
@@ -134,7 +130,6 @@ _.extend(MovablePoint.prototype, {
134
130
  id: this.state.id,
135
131
  },
136
132
  normalizeOptions(
137
- FUNCTION_ARRAY_OPTIONS,
138
133
  // Defaults are copied from MovablePointOptions.*.standard
139
134
  // These defaults are set here instead of DEFAULT_PROPS/STATE
140
135
  // because they:
@@ -216,10 +211,7 @@ _.extend(MovablePoint.prototype, {
216
211
  update: function (options) {
217
212
  const self = this;
218
213
  const graphie = self.graphie;
219
- const state = _.extend(
220
- self.state,
221
- normalizeOptions(FUNCTION_ARRAY_OPTIONS, options),
222
- );
214
+ const state = _.extend(self.state, normalizeOptions(options));
223
215
 
224
216
  assert(kpoint.is(state.coord));
225
217
 
@@ -15,10 +15,6 @@ import objective_ from "./objective_";
15
15
  const assert = InteractiveUtil.assert;
16
16
  const normalizeOptions = InteractiveUtil.normalizeOptions;
17
17
 
18
- // State parameters that should be converted into an array of
19
- // functions
20
- const FUNCTION_ARRAY_OPTIONS = _.keys(MovablePolygonOptions);
21
-
22
18
  // Default "props" and "state". Both are added to this.state and
23
19
  // receive magic getter methods (this.points() etc).
24
20
  // However, properties in DEFAULT_PROPS are updated on `modify()`,
@@ -89,7 +85,6 @@ _.extend(MovablePolygon.prototype, {
89
85
  id: this.state.id,
90
86
  },
91
87
  normalizeOptions(
92
- FUNCTION_ARRAY_OPTIONS,
93
88
  // Defaults are copied from MovablePolygonOptions.*.standard
94
89
  // These defaults are set here instead of DEFAULT_PROPS/STATE
95
90
  // because they:
@@ -124,10 +119,7 @@ _.extend(MovablePolygon.prototype, {
124
119
  update: function (options) {
125
120
  const self = this;
126
121
  const graphie = self.graphie;
127
- const state = _.extend(
128
- self.state,
129
- normalizeOptions(FUNCTION_ARRAY_OPTIONS, options),
130
- );
122
+ const state = _.extend(self.state, normalizeOptions(options));
131
123
 
132
124
  // Default things inside the state.normalStyle object, because
133
125
  // _.extend is not deep.
@@ -15,23 +15,13 @@ import _ from "underscore";
15
15
 
16
16
  import InteractiveUtil from "./interactive-util";
17
17
 
18
+ import type {Coord} from "./types";
19
+ import type {Graphie} from "../util/graphie";
20
+
18
21
  const normalizeOptions = InteractiveUtil.normalizeOptions;
19
22
 
20
23
  const assert = InteractiveUtil.assert;
21
24
 
22
- // state parameters that should be converted into an array of
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
25
  // Default "props" and "state". Both are added to this.state and
36
26
  // receive magic getter methods (this.isHovering() etc).
37
27
  // However, properties in DEFAULT_PROPS are updated on `modify()`,
@@ -49,10 +39,30 @@ const DEFAULT_STATE = {
49
39
  mouseTarget: null,
50
40
  } as const;
51
41
 
52
- const Movable = function (graphie: any, options: any): void {
42
+ export interface State {
43
+ added?: boolean;
44
+ isHovering?: boolean;
45
+ isMouseOver?: boolean;
46
+ isDragging?: boolean;
47
+ // TODO(benchristel): improve types
48
+ mouseTarget?: unknown | null;
49
+ // TODO(benchristel): improve types
50
+ cursor: unknown | null;
51
+ id: string;
52
+ add: (() => void)[];
53
+ modify: (() => void)[];
54
+ draw: (() => void)[];
55
+ remove: (() => void)[];
56
+ onMoveStart: ((position: Coord) => void)[];
57
+ onMove: ((end: Coord, start: Coord) => void)[];
58
+ onMoveEnd: ((end: Coord, start: Coord) => void)[];
59
+ onClick: ((position: Coord, start: Coord) => void)[];
60
+ }
61
+
62
+ const Movable = function (graphie: Graphie, options: any): void {
53
63
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
54
64
  _.extend(this, {
55
- graphie: graphie,
65
+ graphie,
56
66
  state: {
57
67
  // Set here because this must be unique for each instance
58
68
  id: _.uniqueId("movable"),
@@ -71,28 +81,26 @@ InteractiveUtil.createGettersFor(
71
81
  InteractiveUtil.addMovableHelperMethodsTo(Movable);
72
82
 
73
83
  _.extend(Movable.prototype, {
74
- cloneState: function () {
75
- return _.clone(this.state);
84
+ cloneState: function (): State {
85
+ return {...this.state};
76
86
  },
77
87
 
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
- );
88
+ _createDefaultState: function (): State {
89
+ return {
90
+ id: this.state.id,
91
+ add: [],
92
+ modify: [],
93
+ draw: [],
94
+ remove: [],
95
+ onMoveStart: [],
96
+ onMove: [],
97
+ onMoveEnd: [],
98
+ onClick: [],
99
+
100
+ // We only update props here, because we want things on state to
101
+ // be persistent, and updated appropriately in modify()
102
+ ...DEFAULT_PROPS,
103
+ };
96
104
  },
97
105
 
98
106
  /**
@@ -101,18 +109,18 @@ _.extend(Movable.prototype, {
101
109
  *
102
110
  * Analogous to React.js's replaceProps
103
111
  */
104
- modify: function (options) {
105
- this.update(_.extend({}, this._createDefaultState(), options));
112
+ modify: function (options: Partial<State>) {
113
+ this.update({...this._createDefaultState(), ...options});
106
114
  },
107
115
 
108
116
  /**
109
117
  * Simulates a mouse grab event on the movable object.
110
118
  */
111
- grab: function (coord) {
119
+ grab: function (coord: Coord) {
112
120
  assert(kpoint.is(coord));
113
121
  const self = this;
114
122
  const graphie = self.graphie;
115
- const state = self.state;
123
+ const state: State = self.state;
116
124
 
117
125
  state.isHovering = true;
118
126
  state.isDragging = true;
@@ -154,15 +162,12 @@ _.extend(Movable.prototype, {
154
162
  *
155
163
  * Analogous to React.js's setProps
156
164
  */
157
- update: function (options) {
165
+ update: function (options: State) {
158
166
  const self = this;
159
167
  const graphie = self.graphie;
160
168
 
161
169
  const prevState = self.cloneState();
162
- const state = _.extend(
163
- self.state,
164
- normalizeOptions(FUNCTION_ARRAY_OPTIONS, options),
165
- );
170
+ const state = _.extend(self.state, normalizeOptions(options));
166
171
 
167
172
  // the invisible shape in front of the point that gets mouse events
168
173
  if (state.mouseTarget && !prevState.mouseTarget) {
package/src/types.ts CHANGED
@@ -411,9 +411,13 @@ export type WidgetExports<
411
411
  > = Readonly<{
412
412
  name: string;
413
413
  displayName: string;
414
+
415
+ // Widgets should provide _one_ of these two properties only!
414
416
  // TODO: figure out how to ensure that the component returned supports
415
417
  // the `validate` method.
416
418
  getWidget?: () => T;
419
+ widget: T;
420
+
417
421
  accessible?: boolean | ((props: any) => boolean);
418
422
  /** Supresses widget from showing up in the dropdown in the content editor */
419
423
  hidden?: boolean;
@@ -449,8 +453,6 @@ export type WidgetExports<
449
453
  propUpgrades?: {
450
454
  [key: string]: (arg1: any) => any;
451
455
  }; // OldProps => NewProps,
452
-
453
- widget: T;
454
456
  }>;
455
457
 
456
458
  export type PerseusWidgetMap = {