@khanacademy/perseus 0.3.4 → 0.4.0

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 (50) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/es/index.js +8232 -5591
  3. package/dist/es/index.js.map +1 -1
  4. package/dist/index.js +17876 -15237
  5. package/dist/index.js.map +1 -1
  6. package/dist/strings.js +0 -1
  7. package/package.json +9 -8
  8. package/src/__tests__/__snapshots__/item-renderer_test.jsx.snap +2 -3
  9. package/src/__tests__/__snapshots__/server-item-renderer_test.jsx.snap +2 -3
  10. package/src/__tests__/renderer-api_test.jsx +3 -27
  11. package/src/components/__stories__/svg-image.stories.jsx +11 -0
  12. package/src/components/fixed-to-responsive.jsx +1 -0
  13. package/src/components/input-with-examples.jsx +3 -1
  14. package/src/components/svg-image.jsx +10 -7
  15. package/src/components/text-input.jsx +65 -16
  16. package/src/definition-context.js +59 -0
  17. package/src/index.js +5 -1
  18. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +438 -523
  19. package/src/multi-items/__tests__/multi-renderer_test.js +9 -9
  20. package/src/renderer.jsx +13 -6
  21. package/src/types.js +3 -4
  22. package/src/widgets/__stories__/definition.stories.jsx +44 -0
  23. package/src/widgets/__tests__/__snapshots__/categorizer_test.jsx.snap +76 -8
  24. package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt_test.jsx.snap +6 -9
  25. package/src/widgets/__tests__/__snapshots__/graded-group-set_test.jsx.snap +2 -3
  26. package/src/widgets/__tests__/__snapshots__/grapher_test.jsx.snap +19 -2
  27. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +338 -390
  28. package/src/widgets/__tests__/__snapshots__/image_test.jsx.snap +38 -4
  29. package/src/widgets/__tests__/__snapshots__/interactive-graph_test.jsx.snap +114 -12
  30. package/src/widgets/__tests__/__snapshots__/matrix_test.jsx.snap +120 -48
  31. package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +8 -12
  32. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +3615 -4517
  33. package/src/widgets/__tests__/graded-group-set-jipt_test.jsx +0 -1
  34. package/src/widgets/__tests__/grapher.cypress.jsx +236 -176
  35. package/src/widgets/__tests__/group_test.jsx +4 -4
  36. package/src/widgets/__tests__/interactive-graph.cypress.jsx +371 -291
  37. package/src/widgets/__tests__/radio_test.jsx +73 -114
  38. package/src/widgets/definition.jsx +34 -29
  39. package/src/widgets/numeric-input.jsx +2 -15
  40. package/src/widgets/radio/base-radio.jsx +1 -1
  41. package/src/widgets/radio/choice-icon.jsx +11 -10
  42. package/src/widgets/radio/choice.jsx +164 -396
  43. package/src/widgets/radio/cross-out-button.jsx +3 -5
  44. package/src/widgets/radio/option-status.jsx +4 -5
  45. package/src/widgets/radio/__stories__/cross-out-menu.stories.jsx +0 -59
  46. package/src/widgets/radio/__stories__/cross-out-tooltip.stories.jsx +0 -59
  47. package/src/widgets/radio/__stories__/toggleable-radio-button.stories.jsx +0 -28
  48. package/src/widgets/radio/cross-out-menu-wrapper.jsx +0 -492
  49. package/src/widgets/radio/cross-out-tooltip.jsx +0 -129
  50. package/src/widgets/radio/toggleable-radio-button.jsx +0 -133
package/dist/strings.js CHANGED
@@ -98,7 +98,6 @@ module.exports = [
98
98
  i18n._("Note: For this question, the shape will not move.", {}),
99
99
  i18n._("Number of divisions:", {}),
100
100
  i18n._("Number of trials:", {}),
101
- i18n._("Open menu for Choice %(letter)s", {}),
102
101
  i18n._("Parameter", {}),
103
102
  i18n._("Please choose the correct number of answers.", {}),
104
103
  i18n.$_("Please make sure the number of divisions is in the range %(divRangeString)s.", {}),
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": "0.3.4",
6
+ "version": "0.4.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -21,12 +21,13 @@
21
21
  "test": "bash -c 'yarn --silent --cwd \"../..\" test ${@:0} $($([[ ${@: -1} = -* ]] || [[ ${@: -1} = bash ]]) && echo $PWD)'"
22
22
  },
23
23
  "dependencies": {
24
- "@khanacademy/kmath": "^0.0.5",
25
- "@khanacademy/kas": "^0.2.4",
26
- "@khanacademy/math-input": "^0.5.5",
27
- "@khanacademy/perseus-linter": "^0.2.0",
28
- "@khanacademy/pure-markdown": "^0.1.0",
29
- "@khanacademy/simple-markdown": "^0.8.3",
24
+ "@khanacademy/kas": "^0.2.5",
25
+ "@khanacademy/kmath": "^0.0.6",
26
+ "@khanacademy/math-input": "^0.5.6",
27
+ "@khanacademy/perseus-linter": "^0.2.1",
28
+ "@khanacademy/pure-markdown": "^0.1.1",
29
+ "@khanacademy/simple-markdown": "^0.8.4",
30
+ "@khanacademy/wonder-blocks-form": "^2.4.5",
30
31
  "mathquill": "git+https://git@github.com/Khan/mathquill.git#a9ae54e057c5c1acc8244a5627acbff29901d992"
31
32
  },
32
33
  "devDependencies": {
@@ -53,7 +54,7 @@
53
54
  "intersection-observer": "^0.12.0",
54
55
  "jquery": "^2.1.1",
55
56
  "lodash.debounce": "^4.0.8",
56
- "perseus-build-settings": "^0.0.2",
57
+ "perseus-build-settings": "^0.0.3",
57
58
  "prop-types": "^15.6.1",
58
59
  "react": "16.14.0",
59
60
  "react-dom": "16.14.0",
@@ -46,12 +46,11 @@ exports[`item renderer should snapshot: initial render 1`] = `
46
46
  <span>
47
47
  <div>
48
48
  <input
49
- aria-describedby="input-with-examples-aW5wdXQtbnVtYmVyIDE"
50
49
  autocapitalize="off"
51
50
  autocomplete="off"
52
51
  autocorrect="off"
53
- class="perseus-input perseus-interactive perseus-input-size-normal"
54
- spellcheck="false"
52
+ class="input_1t4b01j-o_O-LabelMedium_1rew30o-o_O-default_1r2dwp3"
53
+ id="input-with-examples-aW5wdXQtbnVtYmVyIDE"
55
54
  type="text"
56
55
  value=""
57
56
  />
@@ -44,12 +44,11 @@ exports[`server item renderer should snapshot: initial render 1`] = `
44
44
  <span>
45
45
  <div>
46
46
  <input
47
- aria-describedby="input-with-examples-aW5wdXQtbnVtYmVyIDE"
48
47
  autocapitalize="off"
49
48
  autocomplete="off"
50
49
  autocorrect="off"
51
- class="perseus-input perseus-interactive perseus-input-size-normal"
52
- spellcheck="false"
50
+ class="input_1t4b01j-o_O-LabelMedium_1rew30o-o_O-default_1r2dwp3"
51
+ id="input-with-examples-aW5wdXQtbnVtYmVyIDE"
53
52
  type="text"
54
53
  value=""
55
54
  />
@@ -149,30 +149,6 @@ describe("Perseus API", function () {
149
149
  });
150
150
 
151
151
  describe("CSS ClassNames", function () {
152
- describe("perseus-input", function () {
153
- it("should be on the `input` element of an input-number", function () {
154
- // Feel free to change this if you change the class name,
155
- // but if you do, you must up the perseus api [major]
156
- // version
157
- expect(ClassNames.INPUT).toBe("perseus-input");
158
-
159
- const renderer = renderQuestionArea(inputNumber1Item);
160
-
161
- const input =
162
- // $FlowFixMe[prop-missing]
163
- // $FlowFixMe[incompatible-use]
164
- ReactDOM.findDOMNode(renderer).querySelector("input");
165
- expect($(input).hasClass(ClassNames.INPUT)).toBe(true);
166
-
167
- const perseusInput = ReactDOM.findDOMNode(
168
- renderer,
169
- // $FlowFixMe[incompatible-use]
170
- // $FlowFixMe[prop-missing]
171
- ).querySelector(".perseus-input");
172
- expect(input).toBe(perseusInput);
173
- });
174
- });
175
-
176
152
  describe("perseus-focused", function () {
177
153
  it("should be on an input-number exactly when focused", function () {
178
154
  // Feel free to change this if you change the class name,
@@ -190,13 +166,13 @@ describe("Perseus API", function () {
190
166
  // $FlowFixMe[prop-missing]
191
167
  ReactDOM.findDOMNode(renderer).querySelector("input");
192
168
 
193
- expect($(input).hasClass(ClassNames.FOCUSED)).toBe(false);
169
+ expect($(input).attr("class").includes("focused")).toBe(false);
194
170
 
195
171
  TestUtils.Simulate.focus(input);
196
- expect($(input).hasClass(ClassNames.FOCUSED)).toBe(true);
172
+ expect($(input).attr("class").includes("focused")).toBe(true);
197
173
 
198
174
  TestUtils.Simulate.blur(input);
199
- expect($(input).hasClass(ClassNames.FOCUSED)).toBe(false);
175
+ expect($(input).attr("class").includes("focused")).toBe(false);
200
176
  });
201
177
  });
202
178
  });
@@ -22,6 +22,17 @@ export const MostlyEmptyPropsObject = (args: StoryArgs): React.Node => {
22
22
  return <SvgImage alt="ALT" />;
23
23
  };
24
24
 
25
+ export const SvgImageThatDoesntLoad = (args: StoryArgs): React.Node => {
26
+ return (
27
+ <SvgImage
28
+ alt="ALT"
29
+ height={100}
30
+ width={500}
31
+ src={"http://httpstat.us/200?sleep=1000000"}
32
+ />
33
+ );
34
+ };
35
+
25
36
  export const SvgImageBasic = (args: StoryArgs): React.Node => {
26
37
  return <SvgImage src={svgUrl} alt="ALT" />;
27
38
  };
@@ -128,6 +128,7 @@ const FixedToResponsive: $FlowFixMe = createReactClass({
128
128
  maxHeight: height,
129
129
  };
130
130
 
131
+ // NOTE(jeremy): This depends on styles defined in perseus-renderer.less
131
132
  const className = classNames(
132
133
  "fixed-to-responsive",
133
134
  this.props.className,
@@ -78,8 +78,10 @@ class InputWithExamples extends React.Component<$FlowFixMe, $FlowFixMe> {
78
78
 
79
79
  _getPropsForInputType: () => $FlowFixMe = () => {
80
80
  // Minimal set of props, used by each input type
81
+ const id = this._getUniqueId();
81
82
  const inputProps = {
82
- "aria-describedby": this._getUniqueId(),
83
+ id: id,
84
+ "aria-describedby": id,
83
85
  ref: "input",
84
86
  className: this._getInputClassName(),
85
87
  labelText: this.props.labelText,
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable no-useless-escape, react/no-unsafe */
2
2
  // @flow
3
+ import {CircularSpinner} from "@khanacademy/wonder-blocks-progress-spinner";
3
4
  import classNames from "classnames";
4
5
  import $ from "jquery";
5
6
  import * as React from "react";
@@ -119,14 +120,19 @@ function defaultPreloader(dimensions: Dimensions) {
119
120
  return (
120
121
  <span
121
122
  style={{
122
- background: "url(/images/spinner.gif) no-repeat",
123
- backgroundPosition: "center",
123
+ top: 0,
124
+ left: 0,
124
125
  width: "100%",
125
126
  height: "100%",
126
127
  position: "absolute",
127
128
  minWidth: "20px",
129
+ display: "flex",
130
+ justifyContent: "center",
131
+ alignContent: "center",
128
132
  }}
129
- />
133
+ >
134
+ <CircularSpinner size="medium" />
135
+ </span>
130
136
  );
131
137
  }
132
138
 
@@ -591,10 +597,7 @@ class SvgImage extends React.Component<Props, State> {
591
597
  const width = this.props.width && this.props.width * this.props.scale;
592
598
  const height =
593
599
  this.props.height && this.props.height * this.props.scale;
594
- const dimensions = {
595
- width: width,
596
- height: height,
597
- };
600
+ const dimensions = {width, height};
598
601
 
599
602
  // To make an image responsive, we need to know what its width and
600
603
  // height are in advance (before inserting it into the DOM) so that we
@@ -1,33 +1,82 @@
1
1
  /* eslint-disable react/sort-comp */
2
2
  // @flow
3
- import PropTypes from "prop-types";
3
+ import {TextField} from "@khanacademy/wonder-blocks-form";
4
4
  import * as React from "react";
5
5
  import ReactDOM from "react-dom";
6
6
 
7
- class TextInput extends React.Component<$FlowFixMe> {
8
- static propTypes = {
9
- value: PropTypes.string,
10
- onChange: PropTypes.func.isRequired,
11
- className: PropTypes.string,
12
- labelText: PropTypes.string,
13
- onFocus: PropTypes.func,
14
- onBlur: PropTypes.func,
15
- disabled: PropTypes.bool,
16
- };
7
+ type Props = {|
8
+ value: string | number | null,
9
+ onChange: (any) => void,
10
+ className?: string,
11
+ labelText?: string,
12
+ onFocus?: () => void,
13
+ onBlur?: () => void,
14
+ disabled?: boolean,
15
+ id?: string,
16
+ placeholder?: string,
17
+ onKeyDown?: () => void,
18
+
19
+ // TODO: Remove this
20
+ style?: $FlowFixMe,
21
+ |};
22
+
23
+ type DefaultProps = {|
24
+ value: Props["value"],
25
+ disabled: Props["disabled"],
26
+ |};
27
+
28
+ let lastId = 0;
29
+ function uniqueIdForInput(prefix: string = "input-") {
30
+ lastId++;
31
+ return `${prefix}${lastId}`;
32
+ }
17
33
 
18
- static defaultProps: $FlowFixMe = {
34
+ class TextInput extends React.Component<Props> {
35
+ static defaultProps: DefaultProps = {
19
36
  value: "",
20
37
  disabled: false,
21
38
  };
22
39
 
40
+ id: string;
41
+
42
+ constructor(props: Props) {
43
+ super(props);
44
+ if (props.id) {
45
+ this.id = props.id;
46
+ } else {
47
+ this.id = uniqueIdForInput();
48
+ }
49
+ }
50
+
23
51
  render(): React.Node {
24
- const {labelText, ...props} = this.props;
52
+ const {
53
+ labelText,
54
+ value,
55
+ onFocus,
56
+ onBlur,
57
+ disabled,
58
+ placeholder,
59
+ onKeyDown,
60
+ } = this.props;
61
+
62
+ const formattedValue = value === null ? "" : value.toString();
63
+
25
64
  return (
26
- <input
27
- {...props}
65
+ // $FlowIgnore
66
+ <TextField
67
+ disabled={disabled}
68
+ id={this.id}
69
+ value={formattedValue}
28
70
  type="text"
29
71
  aria-label={labelText}
30
- onChange={(e) => this.props.onChange(e.target.value)}
72
+ onChange={(value) => this.props.onChange(value)}
73
+ placeholder={placeholder}
74
+ onFocus={onFocus}
75
+ onBlur={onBlur}
76
+ onKeyDown={onKeyDown}
77
+ autoCorrect="off"
78
+ autoCapitalize="off"
79
+ autoComplete="off"
31
80
  />
32
81
  );
33
82
  }
@@ -0,0 +1,59 @@
1
+ // @flow
2
+ /**
3
+ * A React context for ensuring there is one and only one definition showing at a time
4
+ */
5
+
6
+ import * as React from "react";
7
+
8
+ type DefintionContext = {|
9
+ activeDefinitionId: ?string,
10
+ setActiveDefinitionId: (?string) => void,
11
+ |};
12
+
13
+ const defaultContext: DefintionContext = {
14
+ activeDefinitionId: null,
15
+ setActiveDefinitionId: () => {},
16
+ };
17
+
18
+ const DefinitionContext: React.Context<DefintionContext> =
19
+ React.createContext(defaultContext);
20
+
21
+ type ProviderState = {
22
+ activeDefinitionId: ?string,
23
+ };
24
+
25
+ export type ProviderProps = {|children: any|};
26
+ export class DefinitionProvider extends React.Component<
27
+ ProviderProps,
28
+ ProviderState,
29
+ > {
30
+ // Context state
31
+ state: ProviderState = {
32
+ activeDefinitionId: null,
33
+ };
34
+
35
+ // Method to update state
36
+ // $FlowFixMe[signature-verification-failure]
37
+ setActiveDefinitionId = (activeDefinitionId: ?string): void => {
38
+ this.setState((prevState: ProviderState) => ({activeDefinitionId}));
39
+ };
40
+
41
+ render(): React.Node {
42
+ const {children} = this.props;
43
+ const {activeDefinitionId} = this.state;
44
+ const {setActiveDefinitionId} = this;
45
+
46
+ return (
47
+ <DefinitionContext.Provider
48
+ value={{
49
+ activeDefinitionId,
50
+ setActiveDefinitionId,
51
+ }}
52
+ >
53
+ {children}
54
+ </DefinitionContext.Provider>
55
+ );
56
+ }
57
+ }
58
+
59
+ export const DefinitionConsumer = DefinitionContext.Consumer;
package/src/index.js CHANGED
@@ -120,7 +120,11 @@ export type {
120
120
  WidgetExports,
121
121
  WidgetInfo,
122
122
  } from "./types.js";
123
- export type {InputNumberWidget, PerseusRenderer} from "./perseus-types.js";
123
+ export type {
124
+ InputNumberWidget,
125
+ PerseusRadioWidgetOptions,
126
+ PerseusRenderer,
127
+ } from "./perseus-types.js";
124
128
  export type {Format} from "./util/math.js";
125
129
  export type {Coord} from "./interactive2/types.js";
126
130
  export type {MarkerType} from "./widgets/label-image/types.js";