diff --git a/.changeset/sour-bears-fail.md b/.changeset/sour-bears-fail.md new file mode 100644 index 0000000000..536e5e2478 --- /dev/null +++ b/.changeset/sour-bears-fail.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus": minor +--- + +Extend argument object type for APIOptions.trackInteraction callback to support the arbitrary data each widget may add diff --git a/packages/perseus/src/interaction-tracker.ts b/packages/perseus/src/interaction-tracker.ts index 2f25863909..97ba5615cf 100644 --- a/packages/perseus/src/interaction-tracker.ts +++ b/packages/perseus/src/interaction-tracker.ts @@ -1,4 +1,4 @@ -import type {APIOptions} from "./types"; +import type {APIOptions, Tracking} from "./types"; /** * This alternate version of `.track` does nothing as an optimization. @@ -12,7 +12,7 @@ class InteractionTracker { // @ts-expect-error [FEI-5003] - TS2564 - Property '_tracked' has no initializer and is not definitely assigned in the constructor. _tracked: boolean; // @ts-expect-error [FEI-5003] - TS2564 - Property 'setting' has no initializer and is not definitely assigned in the constructor. - setting: string; + setting: Tracking; track: (extraData?: any) => void; trackApi: any; // @ts-expect-error [FEI-5003] - TS2564 - Property 'widgetID' has no initializer and is not definitely assigned in the constructor. @@ -24,7 +24,7 @@ class InteractionTracker { trackApi: APIOptions["trackInteraction"], widgetType: string, widgetID: string, - setting: "" | "all", // "" means track once + setting: Tracking, ) { if (!trackApi) { this.track = _noop; diff --git a/packages/perseus/src/types.ts b/packages/perseus/src/types.ts index 9baa2f28f8..6bcf623bcd 100644 --- a/packages/perseus/src/types.ts +++ b/packages/perseus/src/types.ts @@ -179,15 +179,18 @@ export type APIOptions = Readonly<{ // Function that takes dimensions and returns a React component // to display while an image is loading imagePreloader?: (dimensions: Dimensions) => React.ReactNode; - // Function that takes an object argument. The object should - // include type and id, both strings, at least and can optionally - // include a boolean "correct" value. This is used for keeping - // track of widget interactions. - trackInteraction?: (args: { - type: string; - id: string; - correct?: boolean; - }) => void; + // A function that is called when the user has interacted with a widget. It + // also includes any extra parameters that the originating widget provided. + // This is used for keeping track of widget interactions. + trackInteraction?: ( + args: { + // The widget type that this interaction originates from + type: string; + // The widget id that this interaction originates from + id: string; + correct?: boolean; + } & Record, + ) => void; // A boolean that indicates whether or not a custom keypad is // being used. For mobile web this will be the ProvidedKeypad // component. In this situation we use the MathInput component @@ -375,7 +378,12 @@ export type LinterContextProps = { // additional properties can be added to the context by widgets }; -export type Tracking = "" | "all"; +export type Tracking = + // Track interactions once + | "" + // Track all interactions + | "all"; + export type Alignment = | "default" | "block" diff --git a/packages/perseus/src/widgets/sequence.tsx b/packages/perseus/src/widgets/sequence.tsx index ce5855044b..13a586a882 100644 --- a/packages/perseus/src/widgets/sequence.tsx +++ b/packages/perseus/src/widgets/sequence.tsx @@ -1,36 +1,33 @@ -import { - linterContextProps, - linterContextDefault, -} from "@khanacademy/perseus-linter"; -import PropTypes from "prop-types"; +import {linterContextDefault} from "@khanacademy/perseus-linter"; import * as React from "react"; import _ from "underscore"; import InlineIcon from "../components/inline-icon"; import {iconOk} from "../icon-paths"; import * as Changeable from "../mixins/changeable"; -import {ApiOptions} from "../perseus-api"; +import {PerseusSequenceWidgetOptions} from "../perseus-types"; import Renderer from "../renderer"; import Util from "../util"; -import type {WidgetExports} from "../types"; - -class Sequence extends React.Component { - static propTypes = { - ...Changeable.propTypes, - apiOptions: ApiOptions.propTypes, - json: PropTypes.arrayOf( - PropTypes.shape({ - content: PropTypes.string, - images: PropTypes.objectOf(PropTypes.any), - widgets: PropTypes.objectOf(PropTypes.any), - }), - ), - trackInteraction: PropTypes.func.isRequired, - linterContext: linterContextProps, - }; +import type {WidgetExports, WidgetProps} from "../types"; + +type Rubric = PerseusSequenceWidgetOptions; + +type ExternalProps = WidgetProps; + +type Props = ExternalProps; + +type DefaultProps = { + json: Props["json"]; + linterContext: Props["linterContext"]; +}; + +type State = { + visible: number; +}; - static defaultProps: any = { +class Sequence extends React.Component { + static defaultProps: DefaultProps = { json: [ { content: "", @@ -41,7 +38,7 @@ class Sequence extends React.Component { linterContext: linterContextDefault, }; - state: any = { + state = { visible: 1, };