@khanacademy/perseus 65.6.0 → 65.7.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.
@@ -6,12 +6,11 @@ import type { Interval } from "mafs";
6
6
  interface PolygonAngleProps {
7
7
  centerPoint: vec.Vector2;
8
8
  endPoints: [vec.Vector2, vec.Vector2];
9
- polygonLines: readonly CollinearTuple[];
10
- range: [Interval, Interval];
9
+ areEndPointsClockwise: boolean;
11
10
  showAngles: boolean;
12
11
  snapTo: SnapTo;
13
12
  }
14
- export declare const PolygonAngle: ({ centerPoint, endPoints, range, polygonLines, showAngles, snapTo, }: PolygonAngleProps) => React.JSX.Element | null;
13
+ export declare const PolygonAngle: ({ centerPoint, endPoints, showAngles, snapTo, areEndPointsClockwise, }: PolygonAngleProps) => React.JSX.Element | null;
15
14
  interface AngleProps {
16
15
  vertex: vec.Vector2;
17
16
  coords: [vec.Vector2, vec.Vector2];
@@ -30,4 +29,13 @@ export declare const Angle: ({ vertex, coords, showAngles, allowReflexAngles, ra
30
29
  * https://stackoverflow.com/questions/217578/how-can-i-determine-whether-a-2d-point-is-within-a-polygon
31
30
  */
32
31
  export declare const shouldDrawArcOutside: (midpoint: vec.Vector2, vertex: vec.Vector2, range: [Interval, Interval], polygonLines: readonly CollinearTuple[]) => boolean;
32
+ /**
33
+ * Determines if an angle is an convex (false) or concave (true) angle.
34
+ * Uses the cross product to determine if the angle is outside the polygon.
35
+ * If the cross product is positive for a clockwise angle, the angle is concave.
36
+ * NOTE: This always has to take in clockwise endpoints. It cannot determine
37
+ * if the endpoints are clockwise itself - this has to be checked by the
38
+ * caller. This is because of edge cases involving concave polygons.
39
+ */
40
+ export declare function shouldDrawArcOutsidePolygon(centerPoint: vec.Vector2, clockwiseEndpoints: [vec.Vector2, vec.Vector2]): boolean;
33
41
  export {};
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import type { FocusPath, Widget } from "../../types";
2
+ import type { Widget } from "../../types";
3
3
  import type { UnsupportedWidgetPromptJSON } from "../../widget-ai-utils/unsupported-widget";
4
4
  type Props = {
5
5
  programID: string;
@@ -13,7 +13,6 @@ type DefaultProps = {
13
13
  */
14
14
  declare class PythonProgram extends React.Component<Props> implements Widget {
15
15
  static defaultProps: DefaultProps;
16
- getDOMNodeForPath(path: FocusPath): null;
17
16
  getPromptJSON(): UnsupportedWidgetPromptJSON;
18
17
  render(): React.ReactNode;
19
18
  }
@@ -0,0 +1,48 @@
1
+ import { type PerseusRadioWidgetOptions } from "@khanacademy/perseus-core";
2
+ import * as React from "react";
3
+ import type { APIOptions } from "../../types";
4
+ /**
5
+ * Represents a single choice in the MultipleChoiceComponent
6
+ */
7
+ export interface ChoiceType {
8
+ checked: boolean;
9
+ content: React.ReactNode;
10
+ rationale: React.ReactNode;
11
+ hasRationale: boolean;
12
+ showRationale: boolean;
13
+ showCorrectness: boolean;
14
+ correct: boolean;
15
+ isNoneOfTheAbove: boolean;
16
+ previouslyAnswered: boolean;
17
+ revealNoneOfTheAbove: boolean;
18
+ disabled: boolean;
19
+ }
20
+ /**
21
+ * Props for the MultipleChoiceComponent
22
+ */
23
+ interface MultipleChoiceComponentProps {
24
+ apiOptions: APIOptions;
25
+ choices: ReadonlyArray<ChoiceType>;
26
+ countChoices: boolean | null | undefined;
27
+ editMode?: boolean;
28
+ isLastUsedWidget?: boolean;
29
+ labelWrap: boolean;
30
+ multipleSelect?: boolean;
31
+ numCorrect: number;
32
+ onChoiceChange: (choiceIndex: number, newCheckedState: boolean) => void;
33
+ reviewMode: boolean;
34
+ reviewModeRubric?: PerseusRadioWidgetOptions | null;
35
+ }
36
+ /**
37
+ * The MultipleChoiceComponent renders the UI for multiple choice questions.
38
+ *
39
+ * This component handles the presentation of choices, user interactions,
40
+ * and accessibility features, while the MultipleChoiceWidget manages the
41
+ * underlying logic and state.
42
+ *
43
+ * Supports both radio button (single select) and checkbox (multiple select) modes.
44
+ *
45
+ * Created as part of the Radio Revitalization Project (LEMS-2933).
46
+ */
47
+ declare const MultipleChoiceComponent: ({ apiOptions, reviewModeRubric, reviewMode, editMode, multipleSelect, labelWrap, countChoices, numCorrect, isLastUsedWidget, choices, onChoiceChange, }: MultipleChoiceComponentProps) => React.ReactElement;
48
+ export default MultipleChoiceComponent;
@@ -0,0 +1,117 @@
1
+ import * as React from "react";
2
+ import type { ChoiceType } from "./multiple-choice-component.new";
3
+ import type { PerseusStrings } from "../../strings";
4
+ import type { WidgetProps, ChoiceState, Widget } from "../../types";
5
+ import type { RadioPromptJSON } from "../../widget-ai-utils/radio/radio-ai-utils";
6
+ import type { PerseusRadioChoice, PerseusRadioRubric, PerseusRadioUserInput } from "@khanacademy/perseus-core";
7
+ export type RenderProps = {
8
+ numCorrect: number;
9
+ hasNoneOfTheAbove?: boolean;
10
+ multipleSelect?: boolean;
11
+ countChoices?: boolean;
12
+ deselectEnabled?: boolean;
13
+ choices: ReadonlyArray<RadioChoiceWithMetadata>;
14
+ selectedChoices: ReadonlyArray<PerseusRadioChoice["correct"]>;
15
+ choiceStates?: ReadonlyArray<ChoiceState>;
16
+ values?: ReadonlyArray<boolean>;
17
+ strings?: PerseusStrings;
18
+ editMode?: boolean;
19
+ labelWrap?: boolean;
20
+ };
21
+ /**
22
+ * RadioChoiceWithMetadata is used for server-side scoring
23
+ */
24
+ export interface RadioChoiceWithMetadata extends PerseusRadioChoice {
25
+ originalIndex: number;
26
+ correct?: boolean;
27
+ }
28
+ type Props = WidgetProps<RenderProps, PerseusRadioUserInput, PerseusRadioRubric>;
29
+ type DefaultProps = Required<Pick<Props, "choices" | "multipleSelect" | "countChoices" | "deselectEnabled" | "linterContext" | "showSolutions">>;
30
+ /**
31
+ * MultipleChoiceWidget implements the Widget interface for multiple choice questions.
32
+ *
33
+ * It handles Perseus-specific logic and state management while delegating
34
+ * UI rendering to the MultipleChoiceComponent.
35
+ *
36
+ * This class is exported as "Radio" for backwards compatibility with existing content,
37
+ * though it supports both radio-button (single select) and checkbox (multiple select) modes.
38
+ * Eventually, when Content Backfills are set up, we will officially rename the Radio Widget to MultipleChoiceWidget.
39
+ *
40
+ * Created as part of the Radio Revitalization Project (LEMS-2933).
41
+ */
42
+ declare class MultipleChoiceWidget extends React.Component<Props> implements Widget {
43
+ static defaultProps: DefaultProps;
44
+ /**
45
+ * DEPRECATED: Gets the current user input from this widget.
46
+ *
47
+ * Implements the required Widget interface method that Perseus uses
48
+ * to retrieve the user's current selections.
49
+ *
50
+ * @returns The user's selections as {choicesSelected: [boolean]}
51
+ *
52
+ * TODO: remove this when everything is pulling from Renderer state
53
+ * @deprecated get user input from Renderer state
54
+ */
55
+ getUserInput(): PerseusRadioUserInput;
56
+ /**
57
+ * Gets the JSON representation of the User Input for AI assistance.
58
+ *
59
+ * This method is used to provide structured data about the widget's state
60
+ * to AI systems that can help users with answering questions.
61
+ *
62
+ * @returns A structured JSON object representing the widget's prompt
63
+ */
64
+ getPromptJSON(): RadioPromptJSON;
65
+ /**
66
+ * Renders content that may contain nested widgets (currently only passage-refs).
67
+ * Parses the content, extracts any widgets, and returns a Renderer component
68
+ * configured with the appropriate context.
69
+ *
70
+ * @param content - The content to render (defaults to empty string)
71
+ * @returns A React element with the rendered content
72
+ */
73
+ renderContent: (content?: string) => React.ReactNode;
74
+ /**
75
+ * Handles a user's selection of a choice.
76
+ *
77
+ * Updates choice states based on the selection, handles single/multiple
78
+ * selection logic, and notifies Perseus of the interaction.
79
+ *
80
+ * @param choiceIndex - The index of the choice that changed
81
+ * @param newCheckedState - Whether the choice is now selected
82
+ */
83
+ handleChoiceChange: (choiceIndex: number, newCheckedState: boolean) => void;
84
+ /**
85
+ * Transforms choice states into component-ready props for the MultipleChoiceComponent.
86
+ *
87
+ * This method:
88
+ * 1. Processes each choice's content
89
+ * 2. Maps state properties (selected, rationaleShown, etc.) to component props
90
+ * 3. Renders content and rationales with nested widget support
91
+ * 4. Determines correctness display based on choice data or review rubric
92
+ *
93
+ * @param choiceStates - The current state of each choice
94
+ * @returns An array of formatted choice props ready for the MultipleChoiceComponent
95
+ */
96
+ buildChoiceProps: (choiceStates: ReadonlyArray<ChoiceState>) => ReadonlyArray<ChoiceType>;
97
+ /**
98
+ * Prepares the choice props for rendering in the MultipleChoiceComponent.
99
+ *
100
+ * This method:
101
+ * 1. Uses getChoiceStates() to determine the appropriate state for each choice
102
+ * based on widget mode (review/static) and user selections
103
+ * 2. Transforms these states into component props via buildChoiceProps(),
104
+ * including rendered content, correctness indicators, and rationales
105
+ *
106
+ * @returns An array of choice props ready for the component
107
+ */
108
+ prepareChoicesProps: () => readonly ChoiceType[];
109
+ /**
110
+ * Renders the MultipleChoiceComponent with the current props.
111
+ *
112
+ * @returns The rendered React component
113
+ */
114
+ render(): React.ReactNode;
115
+ }
116
+ declare const Radio: typeof MultipleChoiceWidget;
117
+ export default Radio;
@@ -1,6 +1,6 @@
1
- import { type ShowSolutions } from "@khanacademy/perseus-core";
2
1
  import type { ChoiceState } from "../../../types";
3
- import type { RadioChoiceWithMetadata } from "../radio.new";
2
+ import type { RadioChoiceWithMetadata } from "../multiple-choice-widget.new";
3
+ import type { ShowSolutions } from "@khanacademy/perseus-core";
4
4
  interface GetChoiceStatesProps {
5
5
  choices: ReadonlyArray<RadioChoiceWithMetadata>;
6
6
  isStatic?: boolean | null;
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": "65.6.0",
6
+ "version": "65.7.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -42,12 +42,12 @@
42
42
  "tiny-invariant": "1.3.1",
43
43
  "uuid": "^10.0.0",
44
44
  "@khanacademy/kas": "2.0.9",
45
- "@khanacademy/keypad-context": "3.0.25",
46
- "@khanacademy/kmath": "2.0.25",
47
- "@khanacademy/math-input": "26.0.14",
48
- "@khanacademy/perseus-core": "18.0.0",
49
- "@khanacademy/perseus-linter": "4.0.12",
50
- "@khanacademy/perseus-score": "7.1.12",
45
+ "@khanacademy/keypad-context": "3.0.26",
46
+ "@khanacademy/kmath": "2.0.26",
47
+ "@khanacademy/math-input": "26.0.15",
48
+ "@khanacademy/perseus-core": "18.1.0",
49
+ "@khanacademy/perseus-linter": "4.0.13",
50
+ "@khanacademy/perseus-score": "7.1.13",
51
51
  "@khanacademy/perseus-utils": "2.0.5",
52
52
  "@khanacademy/pure-markdown": "2.0.10",
53
53
  "@khanacademy/simple-markdown": "2.0.10"
@@ -1,45 +0,0 @@
1
- import { type PerseusRadioWidgetOptions } from "@khanacademy/perseus-core";
2
- import * as React from "react";
3
- import type { APIOptions } from "../../types";
4
- export interface ChoiceType {
5
- checked: boolean;
6
- content: React.ReactNode;
7
- rationale: React.ReactNode;
8
- hasRationale: boolean;
9
- showRationale: boolean;
10
- showCorrectness: boolean;
11
- correct: boolean;
12
- isNoneOfTheAbove: boolean;
13
- highlighted: boolean;
14
- previouslyAnswered: boolean;
15
- revealNoneOfTheAbove: boolean;
16
- disabled: boolean;
17
- }
18
- interface BaseRadioProps {
19
- apiOptions: APIOptions;
20
- choices: ReadonlyArray<ChoiceType>;
21
- deselectEnabled?: boolean;
22
- editMode?: boolean;
23
- labelWrap: boolean;
24
- countChoices: boolean | null | undefined;
25
- numCorrect: number;
26
- multipleSelect?: boolean;
27
- reviewModeRubric?: PerseusRadioWidgetOptions | null;
28
- reviewMode: boolean;
29
- onChange: (newValues: {
30
- checked: ReadonlyArray<boolean>;
31
- }) => void;
32
- isLastUsedWidget?: boolean;
33
- }
34
- /**
35
- * The BaseRadio component is the core component for the radio widget.
36
- * It is responsible for rendering the radio choices and handling user interactions.
37
- *
38
- * This component is a duplicate of the BaseRadio component in base-radio.tsx for the
39
- * Radio Revitalization Project. (LEMS-2933) This component will eventually replace
40
- * base-radio.tsx when the feature flag is no longer needed.
41
- *
42
- * TODO(LEMS-2994): Clean up this file.
43
- */
44
- declare const BaseRadio: ({ apiOptions, reviewModeRubric, reviewMode, choices, editMode, multipleSelect, labelWrap, countChoices, numCorrect, isLastUsedWidget, onChange, }: BaseRadioProps) => React.ReactElement;
45
- export default BaseRadio;
@@ -1,40 +0,0 @@
1
- import * as React from "react";
2
- import type { WidgetProps, ChoiceState, Widget } from "../../types";
3
- import type { RadioPromptJSON } from "../../widget-ai-utils/radio/radio-ai-utils";
4
- import type { PerseusRadioChoice, PerseusRadioRubric, PerseusRadioUserInput } from "@khanacademy/perseus-core";
5
- export type RenderProps = {
6
- numCorrect: number;
7
- hasNoneOfTheAbove?: boolean;
8
- multipleSelect?: boolean;
9
- countChoices?: boolean;
10
- deselectEnabled?: boolean;
11
- choices: ReadonlyArray<RadioChoiceWithMetadata>;
12
- selectedChoices: ReadonlyArray<PerseusRadioChoice["correct"]>;
13
- choiceStates?: ReadonlyArray<ChoiceState>;
14
- values?: ReadonlyArray<boolean>;
15
- };
16
- type Props = WidgetProps<RenderProps, PerseusRadioUserInput, PerseusRadioRubric>;
17
- type DefaultProps = Required<Pick<Props, "choices" | "multipleSelect" | "countChoices" | "deselectEnabled" | "linterContext" | "showSolutions">>;
18
- export interface RadioChoiceWithMetadata extends PerseusRadioChoice {
19
- originalIndex: number;
20
- correct?: boolean;
21
- }
22
- /**
23
- * This component is holds the class methods for the radio widget, that were split
24
- * out of radio-component.new.tsx for the Radio Revitalization Project. (LEMS-2933)
25
- * This component will be the main entry point for the radio widget when the feature
26
- * flag is no longer needed.
27
- *
28
- * TODO(LEMS-2994): Clean up this file.
29
- */
30
- declare class Radio extends React.Component<Props> implements Widget {
31
- static defaultProps: DefaultProps;
32
- /**
33
- * TODO: remove this when everything is pulling from Renderer state
34
- * @deprecated get user input from Renderer state
35
- */
36
- getUserInput(): PerseusRadioUserInput;
37
- getPromptJSON(): RadioPromptJSON;
38
- render(): React.ReactNode;
39
- }
40
- export default Radio;
@@ -1,30 +0,0 @@
1
- import * as React from "react";
2
- import type { WidgetProps, ChoiceState } from "../../types";
3
- import type { PerseusRadioChoice, ShowSolutions, PerseusRadioRubric, PerseusRadioUserInput } from "@khanacademy/perseus-core";
4
- export interface RadioChoiceWithMetadata extends PerseusRadioChoice {
5
- originalIndex: number;
6
- correct?: boolean;
7
- }
8
- export interface RenderProps {
9
- numCorrect: number;
10
- hasNoneOfTheAbove?: boolean;
11
- multipleSelect?: boolean;
12
- countChoices?: boolean;
13
- deselectEnabled?: boolean;
14
- choices: ReadonlyArray<RadioChoiceWithMetadata>;
15
- selectedChoices: ReadonlyArray<PerseusRadioChoice["correct"]>;
16
- showSolutions?: ShowSolutions;
17
- choiceStates?: ReadonlyArray<ChoiceState>;
18
- values?: ReadonlyArray<boolean>;
19
- }
20
- type RadioComponentProps = WidgetProps<RenderProps, PerseusRadioUserInput, PerseusRadioRubric>;
21
- /**
22
- * This component is a duplicate of the Radio component in radio.tsx
23
- * for the Radio Revitalization Project. (LEMS-2933)
24
- * This component has been split up to move the rendering logic to a new functional component.
25
- * The class methods are now in radio.class.new.tsx.
26
- *
27
- * TODO(LEMS-2994): Clean up this file.
28
- */
29
- declare const RadioComponent: ({ choices, multipleSelect, countChoices, deselectEnabled, linterContext, showSolutions, static: isStatic, reviewModeRubric, reviewMode, apiOptions, isLastUsedWidget, questionCompleted, findWidgets, onChange, trackInteraction, numCorrect, choiceStates, values, }: RadioComponentProps) => React.ReactElement;
30
- export default RadioComponent;