@khanacademy/perseus 17.3.0 → 17.3.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.
@@ -4,6 +4,7 @@ type Props = {
4
4
  translationIndex: number;
5
5
  paragraphIndex?: number;
6
6
  children?: React.ReactNode;
7
+ inline?: boolean;
7
8
  };
8
9
  declare class QuestionParagraph extends React.Component<Props> {
9
10
  render(): React.ReactNode;
@@ -59,6 +59,7 @@ type Props = Partial<React.ContextType<typeof DependenciesContext>> & {
59
59
  linterContext: LinterContextProps;
60
60
  legacyPerseusLint?: ReadonlyArray<string>;
61
61
  widgets: PerseusRenderer["widgets"];
62
+ inline?: boolean;
62
63
  };
63
64
  type State = {
64
65
  translationLintErrors: ReadonlyArray<string>;
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": "17.3.0",
6
+ "version": "17.3.1",
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.8",
27
- "@khanacademy/math-input": "^16.3.0",
27
+ "@khanacademy/math-input": "^16.4.0",
28
28
  "@khanacademy/perseus-core": "1.4.1",
29
29
  "@khanacademy/perseus-linter": "^0.3.10",
30
30
  "@khanacademy/pure-markdown": "^0.2.13",
@@ -5,6 +5,7 @@ type Props = {
5
5
  translationIndex: number;
6
6
  paragraphIndex?: number;
7
7
  children?: React.ReactNode;
8
+ inline?: boolean;
8
9
  };
9
10
 
10
11
  class QuestionParagraph extends React.Component<Props> {
@@ -16,7 +17,11 @@ class QuestionParagraph extends React.Component<Props> {
16
17
  // to attach some metadata to top-level QuestionParagraphs:
17
18
  return (
18
19
  <div
19
- className={className}
20
+ className={
21
+ this.props.inline
22
+ ? this.props.className ?? undefined
23
+ : className
24
+ }
20
25
  data-perseus-component-index={this.props.translationIndex}
21
26
  data-perseus-paragraph-index={this.props.paragraphIndex}
22
27
  >
package/src/renderer.tsx CHANGED
@@ -172,6 +172,8 @@ type Props = Partial<React.ContextType<typeof DependenciesContext>> & {
172
172
  linterContext: LinterContextProps;
173
173
  legacyPerseusLint?: ReadonlyArray<string>;
174
174
  widgets: PerseusRenderer["widgets"];
175
+ // Skip adding paragraph class
176
+ inline?: boolean;
175
177
  };
176
178
 
177
179
  type State = {
@@ -1060,6 +1062,7 @@ class Renderer extends React.Component<Props, State> {
1060
1062
  className={className}
1061
1063
  translationIndex={this.translationIndex}
1062
1064
  paragraphIndex={state.paragraphIndex}
1065
+ inline={this.props.inline}
1063
1066
  >
1064
1067
  <ErrorBoundary>{output}</ErrorBoundary>
1065
1068
  </QuestionParagraph>
@@ -1919,13 +1922,17 @@ class Renderer extends React.Component<Props, State> {
1919
1922
  this._isTwoColumn = false;
1920
1923
 
1921
1924
  // Parse the string of markdown to a parse tree
1922
- const parsedMarkdown = PerseusMarkdown.parse(content, {
1923
- // Recognize crowdin IDs while translating articles
1924
- // (This should never be hit by exercises, though if you
1925
- // decide you want to add a check that this is an article,
1926
- // go for it.)
1927
- isJipt: this.translationIndex != null,
1928
- });
1925
+ const parsedMarkdown = this.props.inline
1926
+ ? PerseusMarkdown.parseInline(content, {
1927
+ // Recognize crowdin IDs while translating articles
1928
+ // (This should never be hit by exercises, though if you
1929
+ // decide you want to add a check that this is an article,
1930
+ // go for it.)
1931
+ isJipt: this.translationIndex != null,
1932
+ })
1933
+ : PerseusMarkdown.parse(content, {
1934
+ isJipt: this.translationIndex != null,
1935
+ });
1929
1936
 
1930
1937
  // Optionally apply the linter to the parse tree
1931
1938
  if (this.props.linterContext.highlightLint) {
@@ -5,35 +5,18 @@
5
5
  margin: 0;
6
6
  }
7
7
  }
8
- button[id*="perseus-label-image-widget-answer-pill"],
9
- div[id*="perseus-label-image-widget-answer-pill"] {
10
- // Reset text content.
11
- div.paragraph {
12
- all: revert;
13
- }
14
- // Allow for padding around math content. Accounting for the negative margin here:
15
- // https://github.com/Khan/perseus/blob/bafd35daee723456bf259a39b88e7638d91c8c3b/packages/perseus/src/renderer.tsx#L1176
16
- div.perseus-block-math-inner {
17
- margin: -5px 0 !important;
18
- }
19
- }
20
8
  }
21
9
 
22
10
  // Reset mobile text content for answer pills.
23
11
  @media (max-width: 767px) {
24
12
  [id*="perseus-label-image-widget-answer-pill"] {
25
- div.paragraph {
26
- all: revert;
27
- }
28
13
  > div.perseus-renderer-responsive {
29
14
  all: revert;
30
15
  }
31
16
  }
32
-
33
17
  .framework-perseus.perseus-mobile
34
18
  [id*="perseus-label-image-widget-answer-pill"] {
35
- .perseus-renderer > .paragraph,
36
- .perseus-renderer > .paragraph .paragraph {
19
+ mjx-container {
37
20
  all: revert;
38
21
  }
39
22
  }
@@ -5,6 +5,8 @@ import {
5
5
  textQuestion,
6
6
  mathQuestion,
7
7
  numberline,
8
+ longTextFromArticle,
9
+ mixedContentQuestion,
8
10
  } from "../__testdata__/label-image.testdata";
9
11
 
10
12
  import type {PerseusRenderer} from "../../perseus-types";
@@ -59,6 +61,21 @@ export const LabelWidgetWithText = (args: StoryArgs): React.ReactElement => {
59
61
  );
60
62
  };
61
63
 
64
+ export const LabelWidgetWithLongText = (
65
+ args: StoryArgs,
66
+ ): React.ReactElement => {
67
+ const apiOptions: APIOptions = {
68
+ isMobile: args.isMobile,
69
+ };
70
+
71
+ return (
72
+ <RendererWithDebugUI
73
+ question={applyStoryArgs(longTextFromArticle, args)}
74
+ apiOptions={apiOptions}
75
+ />
76
+ );
77
+ };
78
+
62
79
  export const LabelWidgetWithMath = (args: StoryArgs): React.ReactElement => {
63
80
  const apiOptions: APIOptions = {
64
81
  isMobile: args.isMobile,
@@ -85,6 +102,19 @@ export const LabelImageNumberline = (args: StoryArgs): React.ReactElement => {
85
102
  );
86
103
  };
87
104
 
105
+ export const LabelImageMixedContent = (args: StoryArgs): React.ReactElement => {
106
+ const apiOptions: APIOptions = {
107
+ isMobile: args.isMobile,
108
+ };
109
+
110
+ return (
111
+ <RendererWithDebugUI
112
+ question={applyStoryArgs(mixedContentQuestion, args)}
113
+ apiOptions={apiOptions}
114
+ />
115
+ );
116
+ };
117
+
88
118
  export default {
89
119
  title: "Perseus/Widgets/Label Image",
90
120
  args: {
@@ -178,3 +178,131 @@ export const numberline: PerseusRenderer = {
178
178
  },
179
179
  },
180
180
  };
181
+
182
+ export const longTextFromArticle: PerseusRenderer = {
183
+ content: "[[☃ label-image 1]]",
184
+ images: {},
185
+ widgets: {
186
+ "label-image 1": {
187
+ type: "label-image",
188
+ alignment: "default",
189
+ static: false,
190
+ graded: true,
191
+ options: {
192
+ static: false,
193
+ choices: [
194
+ "long wavelength & low frequency",
195
+ "short wavelength & high frequency",
196
+ ],
197
+ imageAlt:
198
+ "Section of a rainbow in the sky. From left to right, the colors appear as violet, indigo, blue, green, yellow, orange, and red.",
199
+ imageUrl:
200
+ "https://cdn.kastatic.org/ka-content-images/227d402cb09ebc1b67f197467212fa4ab3ced5b3.jpg",
201
+ imageWidth: 400,
202
+ imageHeight: 289,
203
+ markers: [
204
+ {
205
+ answers: ["short wavelength & high frequency"],
206
+ label: "Violet side of the rainbow",
207
+ x: 35.8,
208
+ y: 13,
209
+ },
210
+ {
211
+ answers: ["long wavelength & low frequency"],
212
+ label: "Red side of the rainbow",
213
+ x: 71.8,
214
+ y: 50.5,
215
+ },
216
+ ],
217
+ multipleAnswers: false,
218
+ hideChoicesFromInstructions: true,
219
+ },
220
+ version: {
221
+ major: 0,
222
+ minor: 0,
223
+ },
224
+ },
225
+ },
226
+ };
227
+
228
+ export const mixedContentQuestion: PerseusRenderer = {
229
+ content:
230
+ "A beach ball floats on water. The ball is at rest.\n\n[[☃ image 1]]\n\n**Complete the free body diagram by labeling the forces acting on the ball.**\n\n[[☃ label-image 1]]",
231
+ images: {
232
+ "https://cdn.kastatic.org/ka-content-images/0fa7eefde0067d986d2e6dc377e8d2db3e1d466f.svg":
233
+ {
234
+ width: 150,
235
+ height: 300,
236
+ },
237
+ },
238
+ widgets: {
239
+ "image 1": {
240
+ type: "image",
241
+ alignment: "block",
242
+ static: false,
243
+ graded: true,
244
+ options: {
245
+ static: false,
246
+ title: "",
247
+ range: [
248
+ [0, 10],
249
+ [0, 10],
250
+ ],
251
+ box: [350, 219],
252
+ backgroundImage: {
253
+ url: "https://cdn.kastatic.org/ka-content-images/581c7d3928ba5f81e9dc5720291ec895764cc7e8.png",
254
+ width: 350,
255
+ height: 219,
256
+ },
257
+ labels: [],
258
+ alt: "An inflated beach ball floats on the surface of water. Only a small portion of the ball is underwater.",
259
+ caption: "",
260
+ },
261
+ version: {
262
+ major: 0,
263
+ minor: 0,
264
+ },
265
+ },
266
+ "label-image 1": {
267
+ type: "label-image",
268
+ alignment: "default",
269
+ static: false,
270
+ graded: true,
271
+ options: {
272
+ static: false,
273
+ choices: [
274
+ "lift $(F_\\text{L})$",
275
+ "buoyant force $(F_\\text{B})$",
276
+ "gravity $(F_\\text{g})$",
277
+ "normal force $(F_\\text{N})$",
278
+ ],
279
+ imageAlt:
280
+ "A model shows a square with two arrows pointing out of it. One arrow points up and another arrow points down.",
281
+ imageUrl:
282
+ "https://cdn.kastatic.org/ka-content-images/b24e40b5a36890130acfb7bd237c4bf0d85c7b44.svg",
283
+ imageWidth: 150,
284
+ imageHeight: 300,
285
+ markers: [
286
+ {
287
+ answers: ["buoyant force $(F_\\text{B})$"],
288
+ label: "An arrow that points upward from the square",
289
+ x: 64.2,
290
+ y: 17.6,
291
+ },
292
+ {
293
+ answers: ["gravity $(F_\\text{g})$"],
294
+ label: "An arrow that points downward from the square",
295
+ x: 66.2,
296
+ y: 81.8,
297
+ },
298
+ ],
299
+ multipleAnswers: false,
300
+ hideChoicesFromInstructions: true,
301
+ },
302
+ version: {
303
+ major: 0,
304
+ minor: 0,
305
+ },
306
+ },
307
+ },
308
+ };
@@ -121,6 +121,35 @@ describe("expression mobile", () => {
121
121
  );
122
122
  });
123
123
 
124
+ it("shows keypad after focused input interaction", async () => {
125
+ render(<ConnectedRenderer />);
126
+
127
+ const input = screen.getByLabelText(
128
+ "Math input box Tap with one or two fingers to open keyboard",
129
+ );
130
+
131
+ fireEvent.touchStart(input);
132
+
133
+ // waiting because `visibility` is animated
134
+ await waitFor(() =>
135
+ expect(screen.getByRole("button", {name: "1"})).toBeVisible(),
136
+ );
137
+
138
+ userEvent.click(screen.getByRole("tab", {name: "Dismiss"}));
139
+
140
+ // wait for the keypad to close
141
+ await waitFor(() =>
142
+ expect(screen.queryByRole("button", {name: "1"})).toBeNull(),
143
+ );
144
+
145
+ fireEvent.touchStart(input);
146
+
147
+ // confirm that the keypad has reopened
148
+ await waitFor(() =>
149
+ expect(screen.getByRole("button", {name: "1"})).toBeVisible(),
150
+ );
151
+ });
152
+
124
153
  it("is possible to use the keypad", async () => {
125
154
  render(<ConnectedRenderer />);
126
155
 
@@ -22,7 +22,7 @@ export default story;
22
22
  export const SingleAnswer = {
23
23
  args: {
24
24
  id: "1",
25
- selectedAnswers: ["Answer Pill!"],
25
+ selectedAnswers: ["Answer Pill"],
26
26
  markerRef: null,
27
27
  side: "top",
28
28
  onClick: () => {},
@@ -42,7 +42,7 @@ export const MultipleAnswers = {
42
42
  export const Correct = {
43
43
  args: {
44
44
  id: "1",
45
- selectedAnswers: ["Right Answer!"],
45
+ selectedAnswers: ["Right Answer"],
46
46
  showCorrectness: "correct",
47
47
  markerRef: null,
48
48
  side: "top",
@@ -53,7 +53,7 @@ export const Correct = {
53
53
  export const Incorrect = {
54
54
  args: {
55
55
  id: "1",
56
- selectedAnswers: ["Wrong Answer!"],
56
+ selectedAnswers: ["Wrong Answer"],
57
57
  showCorrectness: "incorrect",
58
58
  markerRef: null,
59
59
  side: "top",
@@ -72,7 +72,7 @@ export const AnswerPill = (props: {
72
72
  incorrect && styles.incorrect,
73
73
  ]}
74
74
  >
75
- <Renderer content={answerString} />
75
+ <Renderer content={answerString} inline />
76
76
  </Pill>
77
77
  )}
78
78
  </Popper>
@@ -667,7 +667,7 @@ export class LabelImage extends React.Component<
667
667
  [`margin${
668
668
  markerPosition.charAt(0).toUpperCase() +
669
669
  markerPosition.slice(1)
670
- }`]: 5, // move pill further from marker
670
+ }`]: 15, // move pill further from marker
671
671
  };
672
672
 
673
673
  const showAnswerChoice =
@@ -50,8 +50,11 @@ class PassageRefTarget extends React.Component<any> {
50
50
  return (
51
51
  <Renderer
52
52
  content={this.props.content}
53
- // @ts-expect-error - TS2322 - Type '{ content: any; inline: boolean; apiOptions: any; linterContext: any; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Renderer> & Pick<Readonly<Props> & Readonly<{ children?: ReactNode; }>, "children" | ... 3 more ... | "legacyPerseusLint"> & InexactPartial<...> & InexactPartial<...>'.
54
- inline={true}
53
+ // This was already here before inline was recently added (#873)
54
+ // It was for a different use case a long time ago:
55
+ // https://phabricator.khanacademy.org/D12113
56
+ // Commenting out to prevent unanticipated side effects
57
+ // inline={true}
55
58
  apiOptions={this.props.apiOptions}
56
59
  linterContext={this.props.linterContext}
57
60
  />