import type { ILogger } from "./logging/log"; import type { SizeClass } from "./util/sizing-utils"; import type { WidgetPromptJSON } from "./widget-ai-utils/prompt-types"; import type { KeypadAPI } from "@khanacademy/math-input"; import type { Hint, PerseusAnswerArea, PerseusFeatureFlags, PerseusWidget, PerseusWidgetsMap, AnalyticsEventHandlerFn, Version, LabelImageMarkerPublicData, PerseusLabelImageMarker, ShowSolutions } from "@khanacademy/perseus-core"; import type { LinterContextProps } from "@khanacademy/perseus-linter"; import type { Result } from "@khanacademy/wonder-blocks-data"; import type $ from "jquery"; import type * as React from "react"; export type FocusPath = ReadonlyArray | null | undefined; export type Dimensions = { width?: number; height?: number; }; export type DeviceType = "phone" | "tablet" | "desktop"; /** * This is the type returned by a widget's `getSerializedState` function. However, * note that in most cases the widgets do _not_ implement these functions. * In that case, the `Renderer` just returns the widget's render props as the * serialized state. */ /** * @deprecated and likely a very broken API * [LEMS-3185] do not trust serializedState */ export type SerializedState = Record; /** * The Widget type represents the common API that the Renderer uses to interact * with all widgets. All widgets must implement the methods in this API, unless * they are marked as optional (?: ...). * * These methods are called on the widget ref and allow the renderer to * communicate with the individual widgets to coordinate actions such as * scoring, state serialization/deserialization, and focus management. */ export interface Widget { /** * don't use isWidget; it's just a dummy property to help TypeScript's weak * typing to recognize non-interactive widgets as Widgets * @deprecated */ isWidget?: true; focus?: () => { id: string; path: FocusPath; } | boolean; getDOMNodeForPath?: (path: FocusPath) => Element | Text | null; /** * If the widget does not implement this function, * the renderer simply returns all of the widget's props. */ /** * @deprecated - do not use in new code. */ getSerializedState?: () => SerializedState; blurInputPath?: (path: FocusPath) => void; focusInputPath?: (path: FocusPath) => void; getInputPaths?: () => ReadonlyArray; getPromptJSON?: () => WidgetPromptJSON; } export type ImageDict = { [url: string]: Dimensions; }; export type EditorMode = "edit" | "preview" | "json"; export type ChoiceState = { selected: boolean; highlighted: boolean; rationaleShown: boolean; correctnessShown: boolean; previouslyAnswered: boolean; readOnly: boolean; }; /** * TODO(LEMS-3245) remove ChangeHandler * * @deprecated */ export type ChangeHandler = (arg1: { hints?: ReadonlyArray; replace?: boolean; content?: string; widgets?: PerseusWidgetsMap; images?: ImageDict; question?: any; answerArea?: PerseusAnswerArea | null; editorMode?: EditorMode; jsonMode?: boolean; choiceStates?: ReadonlyArray; divisionRange?: number[]; markers?: Array; }, callback?: () => void, silent?: boolean) => unknown; export type ImageUploader = (file: File, callback: (url: string) => unknown) => unknown; export type Path = ReadonlyArray; type TrackInteractionArgs = { type: string; id: string; correct?: boolean; } & Partial & Partial; type GenerateUrlContext = "image_loader:image_url" | "python_program:program_url" | "video:video_url"; export type GenerateUrlArgs = { url: string; context: GenerateUrlContext; kaLocale?: string; }; /** * APIOptions provides different ways to customize the behaviour of Perseus. * * @see {@link APIOptionsWithDefaults} */ export type APIOptions = Readonly<{ isArticle?: boolean; onFocusChange?: (newFocusPath: FocusPath, oldFocusPath: FocusPath, keypadHeight?: number, focusedElement?: HTMLElement) => unknown; showAlignmentOptions?: boolean; /** * A boolean that indicates whether the associated problem has been * answered correctly and should no longer be interactive. */ readOnly?: boolean; /** * A boolean that indicates whether the editor interface should be * disabled, preventing content creators from making changes. */ editingDisabled?: boolean; answerableCallback?: (arg1: boolean) => unknown; getAnotherHint?: () => unknown; interactionCallback?: (widgetData: { [widgetId: string]: any; }) => void; /** * If imagePlaceholder is set, Perseus will render the placeholder instead * of the image node. */ imagePlaceholder?: React.ReactNode; /** * If widgetPlaceholder is set, Perseus will render the placeholder instead * of the widget node. */ widgetPlaceholder?: React.ReactNode; /** * Base React elements that can be used in place of the standard DOM * DOM elements. For example, when provided, will be used * in place of . This allows clients to provide pre-styled * components or components with custom behavior. */ baseElements?: { /** * The component provided here must adhere to the same * interface as React's base component. */ Link: React.ComponentType; }; /** * Function that takes dimensions and returns a React component * to display while an image is loading. */ imagePreloader?: (dimensions: Dimensions) => React.ReactNode; /** * 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: TrackInteractionArgs) => 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 * from the math-input repo instead of the existing perseus math * input components. */ customKeypad?: boolean; /** * If this is provided, it is called instead of appending an instance * of `math-input`'s keypad to the body. This is used by the native * apps so they can have the keypad be defined on the native side. * It is called with an function that, when called, blurs the input, * and is expected to return an object of the shape * keypadElementPropType from math-input/src/prop-types.js. */ nativeKeypadProxy?: (blur: () => void) => KeypadAPI; /** Indicates whether or not to use mobile styling. */ isMobile?: boolean; /** Indicates whether or not to use mobile app styling. */ isMobileApp?: boolean; /** A function, called with a bool indicating whether use of the * drawing area (scratchpad) should be allowed/disallowed. * * Previously handled by `Khan.scratchpad.enable/disable` * @deprecated setDrawingAreaAvailable is not used in frontend code. */ setDrawingAreaAvailable?: (arg1: boolean) => unknown; /** The color used for the hint progress indicator (eg. 1 / 3) */ hintProgressColor?: string; /** * Whether this Renderer is allowed to auto-scroll the rest of the * page. For example, if this is enabled, the most recently used * radio widget will attempt to keep the "selected" answer in view * after entering review mode. * * Defaults to `false`. * @deprecated canScrollPage has no effect. */ canScrollPage?: boolean; /** * The value in milliseconds by which the local state of content * in a editor is delayed before propagated to a prop. For example, * when text is typed in the text area of an Editor component, * there will be a delay equal to the value of `editorChangeDelay` * before the change is propagated. This is added for better * responsiveness of the editor when used in certain contexts such * as StructuredItem exercises where constant re-rendering for each * keystroke caused text typed in the text area to appear in it * only after a good few seconds. * @deprecated editorChangeDelay has no effect. */ editorChangeDelay?: number; /** * Feature flags that can be passed from consuming application. * Define the feature flag name in packages/perseus-core/src/feature-flags.ts */ flags?: Record<(typeof PerseusFeatureFlags)[number], boolean>; }>; type TeXProps = { children: string; onClick?: () => unknown; onRender?: (root?: any) => unknown; style?: any; }; export type DomInsertCheckFn = (text: string, node: HTMLElement, attribute?: string, jiptString?: string) => string | false; type JIPT = { useJIPT: boolean; }; /** * A label element returned by graphie.label(). * This is a JQuery element with custom methods attached for positioning * and rendering text/math content. */ export type GraphieLabelElement = ReturnType> & { setPosition: (point: [number, number]) => void; processMath: (math: string, force: boolean) => void; processText: (text: string) => void; }; export type JiptLabelStore = { addLabel: (label?: GraphieLabelElement, useMath?: boolean) => void; }; export interface JiptRenderer { replaceJiptContent: (content: string, paragraphIndex: number) => void; } type JiptTranslationComponents = { addComponent: (renderer: JiptRenderer) => number; removeComponentAtIndex: (index: number) => void; }; export type VideoData = { __typename: "Video"; id: string; title: string | null | undefined; /** * Unique identifier on YouTube. * If this is a dubbed video, this is always the original English version on * YouTube. If the localized version is needed, use translatedYoutubeId. * Example: KL6sMOn7ULo */ youtubeId: string | null | undefined; contentId: string | null | undefined; }; interface StaticUrlFn { (maybeRelativeUrl: string): string; (maybeRelativeUrl?: undefined | null | undefined): undefined | null | undefined; } type InitialRequestUrlInterface = { origin: string; host: string; protocol: string; }; export type VideoKind = "YOUTUBE_ID" | "READABLE_ID"; /** * An object for dependency injection, to allow different clients * to provide different methods for logging, translation, network * requests, etc. * * NOTE: You should avoid adding new dependencies here as this type was added * as a quick fix to get around the fact that some of the dependencies Perseus * needs are used in places where neither `APIOptions` nor a React Context * could be used. Aim to shrink the footprint of PerseusDependencies and try to * use alternative methods where possible. */ export type PerseusDependencies = { JIPT: JIPT; graphieMovablesJiptLabels: JiptLabelStore; svgImageJiptLabels: JiptLabelStore; rendererTranslationComponents: JiptTranslationComponents; TeX: React.ComponentType; /** * @deprecated Use `PerseusDependenciesV2.generateUrl` instead. */ staticUrl: StaticUrlFn; InitialRequestUrl: InitialRequestUrlInterface; Log: ILogger; isDevServer: boolean; kaLocale: string; }; /** * The modern iteration of Perseus Depedndencies. These dependencies are * provided to Perseus through its entrypoints (for example: * ServerItemRenderer) and then attached to the DependenciesContext so they are * available anywhere down the React render tree. * * Prefer using this type over `PerseusDependencies` when possible. */ export interface PerseusDependenciesV2 { analytics: { onAnalyticsEvent: AnalyticsEventHandlerFn; }; /** * A function that takes a URL or partial url and may modify it to return * the full URL. This may be used to request a resource from a different * app to where the widget is rendered, like when embedding a video * cross-domain. */ generateUrl: (args: GenerateUrlArgs) => string; useVideo(id: string, kind: VideoKind): Result<{ video: VideoData | null | undefined; }>; } /** * APIOptionsWithDefaults represents the type that is provided to all widgets. * The Renderer fills in these defaults when providing APIOptions to any * widget. */ export type APIOptionsWithDefaults = Readonly; canScrollPage: NonNullable; editorChangeDelay: NonNullable; isArticle: NonNullable; isMobile: NonNullable; isMobileApp: NonNullable; editingDisabled: NonNullable; onFocusChange: NonNullable; readOnly: NonNullable; setDrawingAreaAvailable: NonNullable; showAlignmentOptions: NonNullable; }>; export type Tracking = "" | "all"; export type TrackingGradedGroupExtraArguments = { status: "correct" | "incorrect" | "invalid"; }; type TrackingSequenceExtraArguments = { visible: number; }; type WidgetOptions = any; export type WidgetExports & Widget = React.ComponentType, TUserInput = Empty> = Readonly<{ name: string; displayName: string; getWidget?: () => T; widget: T; /** Supresses widget from showing up in the dropdown in the content editor */ hidden?: boolean; /** * The widget version. Any time the _major_ version changes, the widget * should provide a new entry in the widget parser to migrate from the * older version to the current (new) version. Minor version changes must * be backwards compatible with previous minor versions widget options. * * This key defaults to `{major: 0, minor: 0}` if not provided. */ version?: Version; isLintable?: boolean; tracking?: Tracking; getOneCorrectAnswerFromRubric?: (rubric: WidgetOptions) => string | null | undefined; /** * @deprecated - do not use in new code. */ getUserInputFromSerializedState?: (serializedState: unknown, widgetOptions?: WidgetOptions) => TUserInput; getCorrectUserInput?: (widgetOptions: WidgetOptions) => TUserInput; getStartUserInput?: (widgetOptions: WidgetOptions, problemNum: number) => TUserInput; }>; export type FilterCriterion = string | ((id: string, widgetInfo: PerseusWidget, widget?: Widget | null | undefined) => boolean); /** * The full set of props provided to all widgets when they are rendered. The * `TWidgetOptions` generic argument are the widget-specific props that originate * from the PerseusItem. */ export type WidgetProps = TWidgetOptions & UniversalWidgetProps; /** * The props passed to every widget, regardless of its `type`. */ type UniversalWidgetProps = { trackInteraction: (extraData?: TrackingExtraArgs) => void; widgetId: string; widgetIndex: number; alignment: string | null | undefined; static: boolean | null | undefined; problemNum: number | null | undefined; apiOptions: APIOptionsWithDefaults; keypadElement?: any; questionCompleted?: boolean; onFocus: (blurPath: FocusPath) => void; onBlur: (blurPath: FocusPath) => void; findWidgets: (criterion: FilterCriterion) => ReadonlyArray; reviewMode: boolean; showSolutions?: ShowSolutions; handleUserInput: (newUserInput: TUserInput, cb?: () => void, silent?: boolean) => void; userInput: TUserInput; linterContext: LinterContextProps; containerSizeClass: SizeClass; }; export type ChangeFn = (newPropsOrSinglePropName: string | { [key: string]: any; }, propValue?: any, callback?: () => unknown) => any; export type SharedRendererProps = { apiOptions: APIOptions; linterContext: LinterContextProps; }; export interface Focusable { focus: () => void; blur: () => void; } export {};