@khanacademy/perseus 7.1.1 → 7.2.1

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 (75) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/es/index.css +61 -61
  3. package/dist/es/index.js +100 -168
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.css +61 -61
  6. package/dist/index.js +89 -126
  7. package/dist/index.js.map +1 -1
  8. package/dist/util.d.ts +7 -12
  9. package/dist/util.graphie.d.ts +6 -0
  10. package/dist/util.graphie.js.flow +14 -0
  11. package/dist/util.js.flow +7 -12
  12. package/dist/widgets/plotter.d.ts +47 -13
  13. package/dist/widgets/plotter.js.flow +47 -13
  14. package/package.json +2 -2
  15. package/src/__tests__/item-renderer.test.tsx +1 -1
  16. package/src/components/__stories__/image-loader.stories.tsx +1 -0
  17. package/src/components/graph.tsx +1 -0
  18. package/src/components/icon.tsx +1 -0
  19. package/src/components/image-loader.tsx +1 -0
  20. package/src/components/number-input.tsx +1 -0
  21. package/src/components/sortable.tsx +1 -0
  22. package/src/components/svg-image.tsx +1 -0
  23. package/src/components/tex-buttons.tsx +1 -0
  24. package/src/components/text-input.tsx +1 -0
  25. package/src/components/text-list-editor.tsx +1 -0
  26. package/src/components/zoomable.tsx +1 -0
  27. package/src/hints-renderer.tsx +1 -0
  28. package/src/index.ts +1 -0
  29. package/src/item-renderer.tsx +1 -0
  30. package/src/mixins/provide-keypad.tsx +1 -0
  31. package/src/multi-items/multi-renderer.tsx +1 -0
  32. package/src/renderer.tsx +1 -0
  33. package/src/server-item-renderer.tsx +1 -0
  34. package/src/styles/articles.less +8 -4
  35. package/src/styles/perseus-renderer.less +47 -38
  36. package/src/styles/util.less +1 -1
  37. package/src/styles/variables.boostrap.less +158 -182
  38. package/src/styles/variables.less +20 -20
  39. package/src/styles/variables.shared.less +162 -168
  40. package/src/styles/widgets/categorizer.less +1 -2
  41. package/src/styles/widgets/expression.less +7 -4
  42. package/src/styles/widgets/grapher.less +2 -1
  43. package/src/styles/widgets/image.less +3 -2
  44. package/src/styles/widgets/interactive-graph.less +4 -2
  45. package/src/styles/widgets/label-image.less +2 -1
  46. package/src/styles/widgets/matrix.less +11 -7
  47. package/src/styles/widgets/orderer.less +2 -1
  48. package/src/styles/widgets/passage.less +2 -1
  49. package/src/styles/widgets/plotter.less +4 -3
  50. package/src/styles/widgets/radio.less +4 -2
  51. package/src/styles/widgets/sortable.less +1 -1
  52. package/src/styles/widgets/table.less +2 -1
  53. package/src/styles/widgets/transformer.less +2 -1
  54. package/src/styles/widgets/unit.less +2 -1
  55. package/src/util.graphie.test.ts +45 -0
  56. package/src/util.graphie.ts +59 -0
  57. package/src/util.ts +8 -63
  58. package/src/widgets/__stories__/plotter.stories.tsx +27 -0
  59. package/src/widgets/__testdata__/plotter.testdata.ts +36 -0
  60. package/src/widgets/__tests__/__snapshots__/plotter.test.tsx.snap +863 -0
  61. package/src/widgets/__tests__/plotter.test.tsx +65 -0
  62. package/src/widgets/categorizer.tsx +1 -0
  63. package/src/widgets/graded-group-set.tsx +1 -0
  64. package/src/widgets/graded-group.tsx +1 -0
  65. package/src/widgets/interaction.tsx +1 -0
  66. package/src/widgets/label-image/marker.tsx +1 -0
  67. package/src/widgets/matcher.tsx +1 -0
  68. package/src/widgets/orderer.tsx +1 -0
  69. package/src/widgets/passage-ref-target.tsx +1 -0
  70. package/src/widgets/plotter.tsx +108 -98
  71. package/src/widgets/radio/base-radio.tsx +1 -0
  72. package/src/widgets/radio/choice-icon/choice-icon.tsx +1 -0
  73. package/src/widgets/sorter.tsx +1 -0
  74. package/src/widgets/transformer.tsx +1 -0
  75. package/tsconfig-build.tsbuildinfo +1 -1
@@ -0,0 +1,65 @@
1
+ import "@testing-library/jest-dom";
2
+ import {screen, render} from "@testing-library/react";
3
+ import React from "react";
4
+
5
+ import {testDependencies} from "../../../../../testing/test-dependencies";
6
+ import {setDependencies} from "../../dependencies";
7
+ import {APIOptions} from "../../types";
8
+ import {question1} from "../__testdata__/plotter.testdata";
9
+ import {Plotter} from "../plotter";
10
+
11
+ import {renderQuestion} from "./renderQuestion";
12
+
13
+ describe("plotter widget", () => {
14
+ beforeEach(() => {
15
+ setDependencies(testDependencies);
16
+ });
17
+
18
+ it("should snapshot basic question", () => {
19
+ // Arrange and Act
20
+ const {container} = renderQuestion(question1);
21
+
22
+ // Assert
23
+ expect(container).toMatchSnapshot("initial render");
24
+ });
25
+
26
+ it("should show drag text when not static", () => {
27
+ // Arrange and Act
28
+ const apiOptions: APIOptions = {isMobile: true};
29
+ render(
30
+ <Plotter
31
+ static={false}
32
+ trackInteraction={() => {}}
33
+ onChange={() => {}}
34
+ apiOptions={apiOptions}
35
+ starting={[0]}
36
+ markings={""}
37
+ />,
38
+ );
39
+
40
+ // Assert
41
+ expect(
42
+ screen.getByText("Drag handles to make graph"),
43
+ ).toBeInTheDocument();
44
+ });
45
+
46
+ it("should not show drag text when static", () => {
47
+ // Arrange and Act
48
+ const apiOptions: APIOptions = {isMobile: true};
49
+ render(
50
+ <Plotter
51
+ static={true}
52
+ trackInteraction={() => {}}
53
+ onChange={() => {}}
54
+ apiOptions={apiOptions}
55
+ starting={[0]}
56
+ markings={""}
57
+ />,
58
+ );
59
+
60
+ // Assert
61
+ expect(
62
+ screen.queryByText("Drag handles to make graph"),
63
+ ).not.toBeInTheDocument();
64
+ });
65
+ });
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
1
2
  /* eslint-disable react/sort-comp */
2
3
  import {linterContextDefault} from "@khanacademy/perseus-linter";
3
4
  import * as i18n from "@khanacademy/wonder-blocks-i18n";
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
1
2
  import {linterContextDefault} from "@khanacademy/perseus-linter";
2
3
  import Color from "@khanacademy/wonder-blocks-color";
3
4
  import * as i18n from "@khanacademy/wonder-blocks-i18n";
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
1
2
  import {linterContextDefault} from "@khanacademy/perseus-linter";
2
3
  import Button from "@khanacademy/wonder-blocks-button";
3
4
  import Color from "@khanacademy/wonder-blocks-color";
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
1
2
  /* eslint-disable @babel/no-invalid-this, react/no-unsafe, react/sort-comp */
2
3
  import * as KAS from "@khanacademy/kas";
3
4
  import {vector as kvector} from "@khanacademy/kmath";
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
1
2
  /**
2
3
  * Controlled question image marker, rendered as a styled button element.
3
4
  *
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
1
2
  /* eslint-disable react/forbid-prop-types, react/sort-comp */
2
3
  import {
3
4
  linterContextProps,
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
1
2
  /* eslint-disable @babel/no-invalid-this, @typescript-eslint/no-unused-vars, one-var, react/no-unsafe, react/sort-comp */
2
3
  import {
3
4
  linterContextProps,
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
1
2
  import {
2
3
  linterContextProps,
3
4
  linterContextDefault,
@@ -1,7 +1,6 @@
1
- /* eslint-disable @babel/no-invalid-this, @typescript-eslint/no-unused-vars, one-var, react/no-unsafe, react/sort-comp */
1
+ /* eslint-disable react/no-unsafe */
2
2
  import * as i18n from "@khanacademy/wonder-blocks-i18n";
3
3
  import $ from "jquery";
4
- import PropTypes from "prop-types";
5
4
  import * as React from "react";
6
5
  import ReactDOM from "react-dom";
7
6
  import _ from "underscore";
@@ -14,38 +13,60 @@ import KhanColors from "../util/colors";
14
13
  import GraphUtils from "../util/graph-utils";
15
14
  import KhanMath from "../util/math";
16
15
 
17
- import type {WidgetExports} from "../types";
16
+ import type {APIOptions, WidgetExports} from "../types";
18
17
 
19
18
  const {deepEq} = Util;
20
19
 
21
- const BAR = "bar",
22
- LINE = "line",
23
- PIC = "pic",
24
- HISTOGRAM = "histogram",
25
- DOTPLOT = "dotplot";
26
-
27
- const widgetPropTypes = {
28
- type: PropTypes.oneOf([BAR, LINE, PIC, HISTOGRAM, DOTPLOT]),
29
- labels: PropTypes.arrayOf(PropTypes.string),
30
- categories: PropTypes.arrayOf(
31
- PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
32
- ),
33
-
34
- scaleY: PropTypes.number,
35
- maxY: PropTypes.number,
36
- snapsPerLine: PropTypes.number,
37
-
38
- picSize: PropTypes.number,
39
- pixBoxHeight: PropTypes.number,
40
- picUrl: PropTypes.string,
41
-
42
- plotDimensions: PropTypes.arrayOf(PropTypes.number),
43
- labelInterval: PropTypes.number,
44
- starting: PropTypes.arrayOf(PropTypes.number),
45
- static: PropTypes.bool,
46
- } as const;
47
-
48
- class Plotter extends React.Component<any, any> {
20
+ const BAR = "bar";
21
+ const LINE = "line";
22
+ const PIC = "pic";
23
+ const HISTOGRAM = "histogram";
24
+ const DOTPLOT = "dotplot";
25
+
26
+ const plotTypes = [BAR, LINE, PIC, HISTOGRAM, DOTPLOT];
27
+ type PlotType = typeof plotTypes[number];
28
+
29
+ type Props = {
30
+ type: PlotType;
31
+ labels: ReadonlyArray<string>;
32
+ categories: ReadonlyArray<string | number>;
33
+ scaleY: number;
34
+ maxY: number;
35
+ snapsPerLine: number;
36
+ picSize: number;
37
+ picBoxHeight: number;
38
+ picUrl: string;
39
+ plotDimensions: ReadonlyArray<number>;
40
+ labelInterval: number;
41
+
42
+ starting: Array<number>;
43
+ static: boolean;
44
+ markings: any;
45
+ onChange: any;
46
+ trackInteraction: () => void;
47
+ apiOptions: APIOptions;
48
+ };
49
+
50
+ type DefaultProps = {
51
+ type: Props["type"];
52
+ labels: Props["labels"];
53
+ categories: Props["categories"];
54
+ scaleY: Props["scaleY"];
55
+ maxY: Props["maxY"];
56
+ snapsPerLine: Props["snapsPerLine"];
57
+ picSize: Props["picSize"];
58
+ picBoxHeight: Props["picBoxHeight"];
59
+ picUrl: Props["picUrl"];
60
+ plotDimensions: Props["plotDimensions"];
61
+ labelInterval: Props["labelInterval"];
62
+ };
63
+
64
+ type State = {
65
+ values: Array<number>;
66
+ categoryHeights: Record<string, number>;
67
+ };
68
+
69
+ export class Plotter extends React.Component<Props, State> {
49
70
  // @ts-expect-error [FEI-5003] - TS2564 - Property 'shouldSetupGraphie' has no initializer and is not definitely assigned in the constructor.
50
71
  shouldSetupGraphie: boolean;
51
72
  _isMounted = false;
@@ -53,14 +74,7 @@ class Plotter extends React.Component<any, any> {
53
74
  hairlineRange: any;
54
75
  graphie: any;
55
76
 
56
- static propTypes = {
57
- onChange: PropTypes.func.isRequired,
58
- trackInteraction: PropTypes.func.isRequired,
59
- // TODO(alex): Figure out why lint chokes on this line
60
- // ...widgetPropTypes,
61
- };
62
-
63
- static defaultProps: any = {
77
+ static defaultProps: DefaultProps = {
64
78
  type: BAR,
65
79
  labels: ["", ""],
66
80
  categories: [""],
@@ -77,7 +91,7 @@ class Plotter extends React.Component<any, any> {
77
91
  labelInterval: 1,
78
92
  };
79
93
 
80
- state: any = {
94
+ state: State = {
81
95
  values: this.props.starting || [1],
82
96
 
83
97
  // The measured rendered height of category strings. Used to calculate
@@ -86,57 +100,13 @@ class Plotter extends React.Component<any, any> {
86
100
  categoryHeights: {},
87
101
  };
88
102
 
89
- DOT_PLOT_POINT_SIZE: () => number = () => {
90
- return this.props.apiOptions.isMobile ? 6 : 4;
91
- };
92
-
93
- DOT_PLOT_POINT_PADDING: () => number = () => {
94
- return 8;
95
- };
96
-
97
- DOT_TICK_POINT_SIZE: () => number = () => {
98
- return 2;
99
- };
100
-
101
- render(): React.ReactNode {
102
- // TODO(kevinb) actually compute the size of the graphie correctly and
103
- // make it that size so we don't have to add extra padding. The value
104
- // was determined by eye-balling the layout. :(
105
- const paddingForBottomLabel = 75;
106
- const style = {
107
- marginBottom: this.props.labels[0] ? paddingForBottomLabel : 0,
108
- } as const;
109
-
110
- return (
111
- <div
112
- className={
113
- "perseus-widget-plotter graphie " +
114
- ApiClassNames.INTERACTIVE
115
- }
116
- // eslint-disable-next-line react/no-string-refs
117
- ref="graphieDiv"
118
- style={style}
119
- />
120
- );
121
- }
122
-
123
- componentDidUpdate(prevProps: any, prevState: any) {
124
- this.shouldSetupGraphie =
125
- this.shouldSetupGraphie ||
126
- !_.isEqual(this.state.categoryHeights, prevState.categoryHeights);
127
-
128
- if (this.shouldSetupGraphie) {
129
- this.setupGraphie(prevState);
130
- }
131
- }
132
-
133
103
  componentDidMount() {
134
104
  this._isMounted = true;
135
105
 
136
106
  this.setupGraphie(this.state);
137
107
  }
138
108
 
139
- UNSAFE_componentWillReceiveProps(nextProps: any) {
109
+ UNSAFE_componentWillReceiveProps(nextProps: Props) {
140
110
  const props = [
141
111
  "type",
142
112
  "labels",
@@ -151,10 +121,7 @@ class Plotter extends React.Component<any, any> {
151
121
 
152
122
  this.shouldSetupGraphie = _.any(
153
123
  props,
154
- function (prop) {
155
- // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
156
- return !_.isEqual(this.props[prop], nextProps[prop]);
157
- },
124
+ (prop) => !_.isEqual(this.props[prop], nextProps[prop]),
158
125
  this,
159
126
  );
160
127
 
@@ -167,10 +134,32 @@ class Plotter extends React.Component<any, any> {
167
134
  }
168
135
  }
169
136
 
137
+ componentDidUpdate(prevProps: Props, prevState: State) {
138
+ this.shouldSetupGraphie =
139
+ this.shouldSetupGraphie ||
140
+ !_.isEqual(this.state.categoryHeights, prevState.categoryHeights);
141
+
142
+ if (this.shouldSetupGraphie) {
143
+ this.setupGraphie(prevState);
144
+ }
145
+ }
146
+
170
147
  componentWillUnmount() {
171
148
  this._isMounted = false;
172
149
  }
173
150
 
151
+ DOT_PLOT_POINT_SIZE: () => number = () => {
152
+ return this.props.apiOptions.isMobile ? 6 : 4;
153
+ };
154
+
155
+ DOT_PLOT_POINT_PADDING: () => number = () => {
156
+ return 8;
157
+ };
158
+
159
+ DOT_TICK_POINT_SIZE: () => number = () => {
160
+ return 2;
161
+ };
162
+
174
163
  setupGraphie: (arg1: any) => void = (prevState) => {
175
164
  const self = this;
176
165
  self.shouldSetupGraphie = false;
@@ -186,11 +175,11 @@ class Plotter extends React.Component<any, any> {
186
175
  self.graphie.pics = [];
187
176
  self.graphie.dotTicks = [];
188
177
 
189
- const isBar = self.props.type === BAR,
190
- isLine = self.props.type === LINE,
191
- isPic = self.props.type === PIC,
192
- isHistogram = self.props.type === HISTOGRAM,
193
- isDotplot = self.props.type === DOTPLOT;
178
+ const isBar = self.props.type === BAR;
179
+ const isLine = self.props.type === LINE;
180
+ const isPic = self.props.type === PIC;
181
+ const isHistogram = self.props.type === HISTOGRAM;
182
+ const isDotplot = self.props.type === DOTPLOT;
194
183
 
195
184
  const isTiledPlot = isPic || isDotplot;
196
185
 
@@ -264,7 +253,6 @@ class Plotter extends React.Component<any, any> {
264
253
  if (isMobile && (isBar || isTiledPlot)) {
265
254
  const maxCategoryHeight = Math.max(
266
255
  0,
267
- // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'unknown' is not assignable to parameter of type 'number'.
268
256
  ...Object.values(self.state.categoryHeights),
269
257
  );
270
258
 
@@ -340,7 +328,7 @@ class Plotter extends React.Component<any, any> {
340
328
  }
341
329
  }
342
330
 
343
- if ((isBar || isLine) && isMobile) {
331
+ if ((isBar || isLine) && isMobile && !this.props.static) {
344
332
  self.graphie.dragPrompt = graphie
345
333
  .label(
346
334
  [c.dimX / 2, c.dimY / 2],
@@ -721,8 +709,8 @@ class Plotter extends React.Component<any, any> {
721
709
 
722
710
  if (isHistogram) {
723
711
  // Scale dividers between buckets
724
- const leftDivider = config.graph.dividers[i - 1],
725
- rightDivider = config.graph.dividers[i];
712
+ const leftDivider = config.graph.dividers[i - 1];
713
+ const rightDivider = config.graph.dividers[i];
726
714
 
727
715
  if (leftDivider) {
728
716
  const divHeight = Math.min(
@@ -1173,6 +1161,28 @@ class Plotter extends React.Component<any, any> {
1173
1161
  // @ts-expect-error [FEI-5003] - TS2339 - Property 'validate' does not exist on type 'typeof Plotter'.
1174
1162
  return Plotter.validate(this.getUserInput(), rubric);
1175
1163
  };
1164
+
1165
+ render(): React.ReactNode {
1166
+ // TODO(kevinb) actually compute the size of the graphie correctly and
1167
+ // make it that size so we don't have to add extra padding. The value
1168
+ // was determined by eye-balling the layout. :(
1169
+ const paddingForBottomLabel = 75;
1170
+ const style = {
1171
+ marginBottom: this.props.labels[0] ? paddingForBottomLabel : 0,
1172
+ } as const;
1173
+
1174
+ return (
1175
+ <div
1176
+ className={
1177
+ "perseus-widget-plotter graphie " +
1178
+ ApiClassNames.INTERACTIVE
1179
+ }
1180
+ // eslint-disable-next-line react/no-string-refs
1181
+ ref="graphieDiv"
1182
+ style={style}
1183
+ />
1184
+ );
1185
+ }
1176
1186
  }
1177
1187
 
1178
1188
  _.extend(Plotter, {
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
1
2
  import * as i18n from "@khanacademy/wonder-blocks-i18n";
2
3
  import {StyleSheet, css} from "aphrodite";
3
4
  import classNames from "classnames";
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
1
2
  /* Component for rendering a letter icon in a library radio choice */
2
3
 
3
4
  import Color from "@khanacademy/wonder-blocks-color";
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
1
2
  import {
2
3
  linterContextProps,
3
4
  linterContextDefault,
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
1
2
  /* eslint-disable @babel/no-invalid-this */
2
3
  /* eslint-disable camelcase */
3
4
  /* eslint-disable react/no-did-update-set-state */