@khanacademy/perseus 26.1.0 → 27.0.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.
package/dist/index.js CHANGED
@@ -19195,6 +19195,7 @@ const replaceDeprecatedWidgets = () => {
19195
19195
  replaceWidget("reaction-diagram", "deprecated-standin");
19196
19196
  replaceWidget("sequence", "deprecated-standin");
19197
19197
  replaceWidget("simulator", "deprecated-standin");
19198
+ replaceWidget("unit-input", "deprecated-standin");
19198
19199
  };
19199
19200
  const registerEditors = editorsToRegister => {
19200
19201
  editorsToRegister.forEach(editor => {
@@ -19228,6 +19229,7 @@ const replaceDeprecatedEditors = () => {
19228
19229
  replaceEditor("reaction-diagram", "deprecated-standin");
19229
19230
  replaceEditor("sequence", "deprecated-standin");
19230
19231
  replaceEditor("simulator", "deprecated-standin");
19232
+ replaceEditor("unit-input", "deprecated-standin");
19231
19233
  };
19232
19234
  const getWidget = name => {
19233
19235
  // TODO(alex): Consider referring to these as renderers to avoid
@@ -25294,6 +25296,13 @@ class Explanation extends React__namespace.Component {
25294
25296
  state = {
25295
25297
  expanded: false
25296
25298
  };
25299
+ _mounted = false;
25300
+ componentDidMount() {
25301
+ this._mounted = true;
25302
+ }
25303
+ componentWillUnmount() {
25304
+ this._mounted = false;
25305
+ }
25297
25306
  change = (() => {
25298
25307
  var _this = this;
25299
25308
  return function () {
@@ -25312,7 +25321,7 @@ class Explanation extends React__namespace.Component {
25312
25321
  render() {
25313
25322
  const promptText = this.state.expanded ? this.props.hidePrompt : this.props.showPrompt;
25314
25323
  const caretIcon = this.state.expanded ? caretUp__default["default"] : caretDown__default["default"];
25315
- const allowTransition = mediaQueryIsMatched("(prefers-reduced-motion: no-preference)");
25324
+ const allowTransition = this._mounted && mediaQueryIsMatched("(prefers-reduced-motion: no-preference)");
25316
25325
 
25317
25326
  // Special styling is needed to fit the button in a block of text without throwing off the line spacing.
25318
25327
  // While the button is not normally included in a block of text, it needs to be able to accommodate such a case.
@@ -28588,7 +28597,8 @@ const defaultGraphConfig = {
28588
28597
  graphDimensionsInPixels: [1, 1],
28589
28598
  width: 0,
28590
28599
  height: 0,
28591
- labels: []
28600
+ labels: [],
28601
+ hintMode: false
28592
28602
  };
28593
28603
  const GraphConfigContext = /*#__PURE__*/React.createContext(defaultGraphConfig);
28594
28604
  function useGraphConfig() {
@@ -30073,8 +30083,6 @@ const ItemExtras = [
30073
30083
 
30074
30084
  // prettier-ignore
30075
30085
 
30076
- // prettier-ignore
30077
-
30078
30086
  //prettier-ignore
30079
30087
 
30080
30088
  // A background image applied to various widgets.
@@ -30600,6 +30608,16 @@ function useDraggable(args) {
30600
30608
  const isKeyboard = type.includes("key");
30601
30609
  if (isKeyboard) {
30602
30610
  event?.preventDefault();
30611
+
30612
+ // When a key is held down, we see multiple "keydown" events,
30613
+ // followed by a single "keyup" event.
30614
+ // For a single keypress, we only see a "keydown" event, no
30615
+ // "keyup".
30616
+ // We never want to process the keyup event as an intent to
30617
+ // move so we bail on further processing here.
30618
+ if (type === "keyup") {
30619
+ return;
30620
+ }
30603
30621
  const {
30604
30622
  direction: yDownDirection,
30605
30623
  altKey,
@@ -30700,7 +30718,8 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
30700
30718
  const {
30701
30719
  range,
30702
30720
  markings,
30703
- showTooltips
30721
+ showTooltips,
30722
+ hintMode
30704
30723
  } = useGraphConfig();
30705
30724
  const {
30706
30725
  point,
@@ -30745,7 +30764,7 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
30745
30764
  cursor
30746
30765
  },
30747
30766
  "data-testid": "movable-point",
30748
- tabIndex: tabIndex(focusBehavior)
30767
+ tabIndex: hintMode ? -1 : tabIndex(focusBehavior)
30749
30768
  }, /*#__PURE__*/React__namespace.createElement("circle", {
30750
30769
  className: "movable-point-hitbox",
30751
30770
  r: hitboxSizePx / 2,
@@ -30833,7 +30852,8 @@ const MovableLine = props => {
30833
30852
  };
30834
30853
  function useControlPoint(point, color, onMovePoint) {
30835
30854
  const {
30836
- snapStep
30855
+ snapStep,
30856
+ hintMode
30837
30857
  } = useGraphConfig();
30838
30858
  const [focused, setFocused] = React.useState(false);
30839
30859
  const keyboardHandleRef = React.useRef(null);
@@ -30855,7 +30875,7 @@ function useControlPoint(point, color, onMovePoint) {
30855
30875
  const focusableHandle = /*#__PURE__*/React__namespace.createElement("g", {
30856
30876
  "data-testid": "movable-point__focusable-handle",
30857
30877
  className: "movable-point__focusable-handle",
30858
- tabIndex: 0,
30878
+ tabIndex: hintMode ? -1 : 0,
30859
30879
  ref: keyboardHandleRef,
30860
30880
  onFocus: () => setFocused(true),
30861
30881
  onBlur: () => setFocused(false)
@@ -30888,7 +30908,8 @@ const Line$1 = props => {
30888
30908
  const {
30889
30909
  range,
30890
30910
  graphDimensionsInPixels,
30891
- snapStep
30911
+ snapStep,
30912
+ hintMode
30892
30913
  } = useGraphConfig();
30893
30914
  let startExtend = undefined;
30894
30915
  let endExtend = undefined;
@@ -30910,7 +30931,7 @@ const Line$1 = props => {
30910
30931
  });
30911
30932
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("g", {
30912
30933
  ref: line,
30913
- tabIndex: 0,
30934
+ tabIndex: hintMode ? -1 : 0,
30914
30935
  className: "movable-line",
30915
30936
  "data-testid": "movable-line",
30916
30937
  style: {
@@ -31429,6 +31450,9 @@ const PolygonGraph = props => {
31429
31450
  snapStep,
31430
31451
  snapTo
31431
31452
  } = props.graphState;
31453
+ const {
31454
+ hintMode
31455
+ } = useGraphConfig();
31432
31456
 
31433
31457
  // TODO(benchristel): can the default set of points be removed here? I don't
31434
31458
  // think coords can be null.
@@ -31448,12 +31472,16 @@ const PolygonGraph = props => {
31448
31472
  constrain: p => ["angles", "sides"].includes(snapToValue) ? p : snap(snapStep, p)
31449
31473
  });
31450
31474
  const active = hovered || focused || dragging;
31475
+ const lastMoveTime = React__namespace.useRef(0);
31451
31476
  const lines = getLines(points);
31452
31477
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Polygon, {
31453
31478
  points: [...points],
31454
31479
  color: "var(--movable-line-stroke-color)",
31455
31480
  svgPolygonProps: {
31456
- strokeWidth: active ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)"
31481
+ strokeWidth: active ? "var(--movable-line-stroke-weight-active)" : "var(--movable-line-stroke-weight)",
31482
+ style: {
31483
+ fill: "transparent"
31484
+ }
31457
31485
  }
31458
31486
  }), points.map((point, i) => {
31459
31487
  const pt1 = points.at(i - 1);
@@ -31485,10 +31513,11 @@ const PolygonGraph = props => {
31485
31513
  color: "transparent",
31486
31514
  svgPolygonProps: {
31487
31515
  ref,
31488
- tabIndex: 0,
31516
+ tabIndex: hintMode ? -1 : 0,
31489
31517
  strokeWidth: TARGET_SIZE,
31490
31518
  style: {
31491
- cursor: dragging ? "grabbing" : "grab"
31519
+ cursor: dragging ? "grabbing" : "grab",
31520
+ fill: hovered ? "var(--mafs-blue)" : "transparent"
31492
31521
  },
31493
31522
  onFocus: () => setFocused(true),
31494
31523
  onBlur: () => setFocused(false),
@@ -31500,7 +31529,15 @@ const PolygonGraph = props => {
31500
31529
  key: "point-" + i,
31501
31530
  snapTo: snapTo,
31502
31531
  point: point,
31503
- onMove: destination => dispatch(actions.polygon.movePoint(i, destination))
31532
+ onMove: destination => {
31533
+ const now = Date.now();
31534
+ const targetFPS = 40;
31535
+ const moveThresholdTime = 1000 / targetFPS;
31536
+ if (now - lastMoveTime.current > moveThresholdTime) {
31537
+ dispatch(actions.polygon.movePoint(i, destination));
31538
+ lastMoveTime.current = now;
31539
+ }
31540
+ }
31504
31541
  })));
31505
31542
  };
31506
31543
  function getLines(points) {
@@ -31621,7 +31658,8 @@ function MovableCircle(props) {
31621
31658
  onMove
31622
31659
  } = props;
31623
31660
  const {
31624
- snapStep
31661
+ snapStep,
31662
+ hintMode
31625
31663
  } = useGraphConfig();
31626
31664
  const draggableRef = React.useRef(null);
31627
31665
  const {
@@ -31636,7 +31674,7 @@ function MovableCircle(props) {
31636
31674
  const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
31637
31675
  return /*#__PURE__*/React__namespace.createElement("g", {
31638
31676
  ref: draggableRef,
31639
- tabIndex: 0,
31677
+ tabIndex: hintMode ? -1 : 0,
31640
31678
  className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
31641
31679
  }, /*#__PURE__*/React__namespace.createElement("ellipse", {
31642
31680
  className: "focus-ring",
@@ -32040,7 +32078,8 @@ const MafsGraph = props => {
32040
32078
  const {
32041
32079
  state,
32042
32080
  dispatch,
32043
- labels
32081
+ labels,
32082
+ hintMode
32044
32083
  } = props;
32045
32084
  const [width, height] = props.box;
32046
32085
  const tickStep = props.step;
@@ -32055,7 +32094,8 @@ const MafsGraph = props => {
32055
32094
  graphDimensionsInPixels: props.box,
32056
32095
  width,
32057
32096
  height,
32058
- labels
32097
+ labels,
32098
+ hintMode
32059
32099
  }
32060
32100
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
32061
32101
  className: "mafs-graph",
@@ -32066,7 +32106,8 @@ const MafsGraph = props => {
32066
32106
  padding: "25px 25px 0 0",
32067
32107
  boxSizing: "content-box",
32068
32108
  marginLeft: "20px",
32069
- marginBottom: "20px"
32109
+ marginBottom: "20px",
32110
+ pointerEvents: hintMode ? "none" : "auto"
32070
32111
  }
32071
32112
  }, /*#__PURE__*/React__namespace.createElement(LegacyGrid, {
32072
32113
  box: props.box,
@@ -32097,7 +32138,7 @@ const MafsGraph = props => {
32097
32138
  }), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
32098
32139
  lockedFigures: props.lockedFigures,
32099
32140
  range: state.range
32100
- }), "/* Protractor */", props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), renderGraph({
32141
+ }), props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), renderGraph({
32101
32142
  state,
32102
32143
  dispatch
32103
32144
  })))));
@@ -33198,7 +33239,6 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
33198
33239
  const snapTo = graph.type === "polygon" ? graph.snapTo : null;
33199
33240
  const showAngles = graph.type === "polygon" ? graph.showAngles : null;
33200
33241
  const showSides = graph.type === "polygon" ? graph.showSides : null;
33201
- const startCoords = graph.startCoords ?? null;
33202
33242
  const originalPropsRef = React.useRef(props);
33203
33243
  const latestPropsRef = wonderBlocksCore.useLatestRef(props);
33204
33244
  React.useEffect(() => {
@@ -33209,7 +33249,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
33209
33249
  if (latestPropsRef.current !== originalPropsRef.current) {
33210
33250
  dispatch(reinitialize(latestPropsRef.current));
33211
33251
  }
33212
- }, [graph.type, numSegments, numSides, snapTo, showAngles, showSides, startCoords, latestPropsRef, startCoords]);
33252
+ }, [graph.type, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, graph.startCoords]);
33213
33253
  return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
33214
33254
  state: state,
33215
33255
  dispatch: dispatch
@@ -33300,9 +33340,6 @@ class LegacyInteractiveGraph extends React__namespace.Component {
33300
33340
  markings: "graph",
33301
33341
  showTooltips: false,
33302
33342
  showProtractor: false,
33303
- showRuler: false,
33304
- rulerLabel: "",
33305
- rulerTicks: 10,
33306
33343
  graph: {
33307
33344
  type: "linear"
33308
33345
  }
@@ -34465,9 +34502,6 @@ class LegacyInteractiveGraph extends React__namespace.Component {
34465
34502
  markings: this.props.markings,
34466
34503
  backgroundImage: this.props.backgroundImage,
34467
34504
  showProtractor: this.props.showProtractor,
34468
- showRuler: this.props.showRuler,
34469
- rulerLabel: this.props.rulerLabel,
34470
- rulerTicks: this.props.rulerTicks,
34471
34505
  onMouseDown: onMouseDown,
34472
34506
  onGraphieUpdated: this.setGraphie,
34473
34507
  setDrawingAreaAvailable: this.props.apiOptions.setDrawingAreaAvailable,
@@ -34486,9 +34520,6 @@ class InteractiveGraph extends React__namespace.Component {
34486
34520
  markings: "graph",
34487
34521
  showTooltips: false,
34488
34522
  showProtractor: false,
34489
- showRuler: false,
34490
- rulerLabel: "",
34491
- rulerTicks: 10,
34492
34523
  graph: {
34493
34524
  type: "linear"
34494
34525
  }
@@ -36226,6 +36257,10 @@ const styles$f = aphrodite.StyleSheet.create({
36226
36257
 
36227
36258
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
36228
36259
  function shouldReduceMotion() {
36260
+ // We cannot use matchMedia during SSR.
36261
+ if (typeof window.matchMedia !== "function") {
36262
+ return true;
36263
+ }
36229
36264
  const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
36230
36265
  return !mediaQuery || mediaQuery.matches;
36231
36266
  }
@@ -36234,9 +36269,16 @@ class Marker extends React__namespace.Component {
36234
36269
  static contextType = PerseusI18nContext;
36235
36270
  // The marker icon element.
36236
36271
  _icon;
36272
+ _mounted = false;
36237
36273
  static defaultProps = {
36238
36274
  selected: []
36239
36275
  };
36276
+ componentDidMount() {
36277
+ this._mounted = true;
36278
+ }
36279
+ componentWillUnmount() {
36280
+ this._mounted = false;
36281
+ }
36240
36282
  renderIcon() {
36241
36283
  const {
36242
36284
  selected,
@@ -36279,7 +36321,7 @@ class Marker extends React__namespace.Component {
36279
36321
  size: 8
36280
36322
  };
36281
36323
  } else if (showPulsate) {
36282
- iconStyles = [styles$e.markerPulsateBase, shouldReduceMotion() ? showPulsate && styles$e.markerUnfilledPulsateOnce : showPulsate && styles$e.markerUnfilledPulsateInfinite];
36324
+ iconStyles = [styles$e.markerPulsateBase, this._mounted && shouldReduceMotion() ? showPulsate && styles$e.markerUnfilledPulsateOnce : showPulsate && styles$e.markerUnfilledPulsateInfinite];
36283
36325
  }
36284
36326
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
36285
36327
  style: [styles$e.markerIcon, iconStyles],
@@ -36407,6 +36449,7 @@ class LabelImage extends React__namespace.Component {
36407
36449
  static contextType = PerseusI18nContext;
36408
36450
  // The rendered markers on the question image for labeling.
36409
36451
  _markers;
36452
+ _mounted = false;
36410
36453
  static gradeMarker(marker) {
36411
36454
  const score = {
36412
36455
  hasAnswers: false,
@@ -36644,6 +36687,12 @@ class LabelImage extends React__namespace.Component {
36644
36687
  hideAnswers: false
36645
36688
  };
36646
36689
  }
36690
+ componentDidMount() {
36691
+ this._mounted = true;
36692
+ }
36693
+ componentWillUnmount() {
36694
+ this._mounted = false;
36695
+ }
36647
36696
  simpleValidate(rubric) {
36648
36697
  return LabelImage.validate(this.getUserInput(), rubric);
36649
36698
  }
@@ -36782,13 +36831,14 @@ class LabelImage extends React__namespace.Component {
36782
36831
  markersInteracted
36783
36832
  } = this.state;
36784
36833
 
36834
+ // Determine whether page is rendered in a narrow browser window.
36835
+ const isNarrowPage = this._mounted && window.matchMedia(mediaQueries.xsOrSmaller.replace("@media ", "")).matches;
36836
+
36837
+ // Determine whether the image is wider than it is tall.
36838
+ const isWideImage = this.props.imageWidth / 2 > this.props.imageHeight;
36839
+
36785
36840
  // Render all markers for widget.
36786
36841
  return markers.map((marker, index) => {
36787
- // Determine whether page is rendered in a narrow browser window.
36788
- const isNarrowPage = window.matchMedia(mediaQueries.xsOrSmaller.replace("@media ", "")).matches;
36789
-
36790
- // Determine whether the image is wider than it is tall.
36791
- const isWideImage = this.props.imageWidth / 2 > this.props.imageHeight;
36792
36842
  let side;
36793
36843
  let markerPosition;
36794
36844
  // Position popup closest to the center, preferring it renders
@@ -44207,1320 +44257,405 @@ var Table$1 = {
44207
44257
  isLintable: true
44208
44258
  };
44209
44259
 
44210
- var isObject = function (obj) {
44211
- return obj === Object(obj);
44260
+ /**
44261
+ * This is a component that adds a link to the transcript for embedded videos in articles.
44262
+ */
44263
+ const IS_URL$1 = /^https?:\/\//;
44264
+ const getYoutubeId = url => {
44265
+ const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
44266
+ const match = url.match(regExp);
44267
+ if (match && match[7].length === 11) {
44268
+ return match[7];
44269
+ }
44270
+ return "videoNotFound";
44212
44271
  };
44213
- var merge = function () {
44214
- var obj = {};
44215
- for (var i = 0; i < arguments.length; i++) {
44216
- var source = arguments[i];
44217
- if (source) {
44218
- for (var prop in source) {
44219
- obj[prop] = source[prop];
44272
+
44273
+ // will accept a youtube link or a video's slug name from embedded video and then query accordingly.
44274
+
44275
+ /**
44276
+ * Video Transcript Link Component.
44277
+ */
44278
+ const VideoTranscriptLink = props => {
44279
+ const {
44280
+ location
44281
+ } = props;
44282
+ const {
44283
+ useVideo
44284
+ } = getDependencies();
44285
+ const [id, kind] = IS_URL$1.test(location) ? [getYoutubeId(location), "YOUTUBE_ID"] : [location, "READABLE_ID"];
44286
+ // The result value conforms to the wonder-blocks-data `Result` type
44287
+ // which is used by our GraphQL framework.
44288
+ // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'VideoKind'.
44289
+ const result = useVideo(id, kind);
44290
+ const {
44291
+ strings
44292
+ } = usePerseusI18n();
44293
+ switch (result.status) {
44294
+ case "loading":
44295
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.loading);
44296
+ case "success":
44297
+ {
44298
+ const video = result.data?.video;
44299
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
44300
+ style: styles$4.transcriptLink
44301
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.Text, null, video?.title), /*#__PURE__*/React__namespace.createElement(wonderBlocksLayout.Strut, {
44302
+ size: 10
44303
+ }), /*#__PURE__*/React__namespace.createElement(Link__default["default"], {
44304
+ href: "/transcript/" + (video?.contentId || "videoNotFound"),
44305
+ target: "_blank",
44306
+ className: "visited-no-recolor"
44307
+ }, strings.videoTranscript));
44220
44308
  }
44221
- }
44309
+ case "error":
44310
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
44311
+ case "aborted":
44312
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
44313
+ default:
44314
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
44222
44315
  }
44223
- return obj;
44224
44316
  };
44225
- var clone = function (obj) {
44226
- if (!isObject(obj)) {
44227
- return obj;
44317
+ const styles$4 = aphrodite.StyleSheet.create({
44318
+ transcriptLink: {
44319
+ flexDirection: "row",
44320
+ width: "100%",
44321
+ justifyContent: "center"
44228
44322
  }
44229
- return Array.isArray(obj) ? obj.slice() : merge(obj);
44230
- };
44323
+ });
44231
44324
 
44232
- /* TODO batch *all* mutations
44233
- * idea: freeze / thaw implementations for all types
44234
- * lens constructor thaws, freeze delegates to type's freeze
44325
+ /**
44326
+ * This is a video widget for embedding videos in articles.
44235
44327
  */
44328
+ // Current default is 720p, based on the typical videos we upload currently
44329
+ const DEFAULT_WIDTH = 1280;
44330
+ const DEFAULT_HEIGHT = 720;
44331
+ const KA_EMBED = "{host}/embed_video?slug={slug}" + "&internal_video_only=1";
44332
+ const IS_URL = /^https?:\/\//;
44333
+ const IS_KA_SITE = /(khanacademy\.org|localhost)/;
44334
+ const IS_VIMEO = /(vimeo\.com)/;
44236
44335
 
44237
- // equivalents, without requiring it
44238
- // find the implementation to use for a given object
44239
- var dispatch = function (x) {
44240
- if (Array.isArray(x)) {
44241
- return arr;
44242
- } else if (isObject(x)) {
44243
- return obj;
44244
- } else if (typeof x === "string") {
44245
- return str;
44246
- }
44247
- };
44336
+ // exports has no 'transform'
44248
44337
 
44249
- // This is underscore with a different name
44250
- var lens = function (obj) {
44251
- if (obj instanceof lens) {
44252
- return obj;
44338
+ /**
44339
+ * Video renderer.
44340
+ */
44341
+ class Video extends React__namespace.Component {
44342
+ static contextType = PerseusI18nContext;
44343
+ /**
44344
+ * This is the widget's grading function.
44345
+ * Points for videos are tallied by the embedded video itself, in the case
44346
+ * of Khan Academy videos.
44347
+ */
44348
+ static validate(userInput, rubric) {
44349
+ return {
44350
+ type: "points",
44351
+ earned: 0,
44352
+ total: 0,
44353
+ message: null
44354
+ };
44253
44355
  }
44254
- if (!(this instanceof lens)) {
44255
- return new lens(obj);
44356
+ getUserInput = () => {
44357
+ return null;
44358
+ };
44359
+ simpleValidate = rubric => {
44360
+ return Video.validate(null, rubric);
44361
+ };
44362
+ change = (() => {
44363
+ var _this = this;
44364
+ return function () {
44365
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
44366
+ args[_key] = arguments[_key];
44367
+ }
44368
+ // @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
44369
+ return change.apply(_this, args);
44370
+ };
44371
+ })();
44372
+ render() {
44373
+ const {
44374
+ InitialRequestUrl
44375
+ } = getDependencies();
44376
+ const location = this.props.location;
44377
+ if (!location) {
44378
+ return /*#__PURE__*/React__namespace.createElement("div", null);
44379
+ }
44380
+ let url;
44381
+ if (IS_URL.test(location)) {
44382
+ url = location;
44383
+ if (IS_VIMEO.test(url)) {
44384
+ // If this is a vimeo video, we need to add the query string
44385
+ // parameter "dnt" so that analytics/tracking cookies aren't set.
44386
+ // https://help.vimeo.com/hc/en-us/articles/12426260232977-Player-parameters-overview
44387
+ if (url.indexOf("?") === -1) {
44388
+ url += "?dnt=1";
44389
+ } else {
44390
+ url += "&dnt=1";
44391
+ }
44392
+ }
44393
+ } else {
44394
+ url = KA_EMBED.replace("{slug}", location);
44395
+ let embedHostname = "https://www.khanacademy.org";
44396
+ if (IS_KA_SITE.test(InitialRequestUrl.host)) {
44397
+ embedHostname = InitialRequestUrl.origin;
44398
+ }
44399
+ url = url.replace("{host}", embedHostname);
44400
+ }
44401
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(FixedToResponsive, {
44402
+ width: DEFAULT_WIDTH,
44403
+ height: DEFAULT_HEIGHT
44404
+ // The key is here for the benefit of the editor, to ensure that
44405
+ // any changes cause a re-rendering of the frame.
44406
+ ,
44407
+ key: location + this.props.alignment
44408
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
44409
+ style: a11y.srOnly
44410
+ }, this.context.strings.videoWrapper), /*#__PURE__*/React__namespace.createElement("iframe", {
44411
+ // TODO(joshuan): Consider not using iframes when we're
44412
+ // loading this from webapp. This iframe is problematic
44413
+ // for screenreaders.
44414
+ className: "perseus-video-widget",
44415
+ sandbox: "allow-same-origin allow-scripts",
44416
+ width: DEFAULT_WIDTH,
44417
+ height: DEFAULT_HEIGHT,
44418
+ src: url,
44419
+ allowFullScreen: true
44420
+ })), /*#__PURE__*/React__namespace.createElement(VideoTranscriptLink, {
44421
+ location: location
44422
+ }));
44256
44423
  }
44257
- var ops = dispatch(obj);
44258
- this._wrapped = ops.thaw ? ops.thaw(obj) : obj;
44259
- };
44260
- lens.prototype.freeze = function () {
44261
- var obj = this._wrapped;
44262
- var ops = dispatch(obj);
44263
- return ops.freeze ? ops.freeze(obj) : obj;
44424
+ }
44425
+ var Video$1 = {
44426
+ name: "video",
44427
+ displayName: "Video",
44428
+ defaultAlignment: "block",
44429
+ supportedAlignments: ["block", "float-left", "float-right", "full-width"],
44430
+ widget: Video
44264
44431
  };
44265
- lens.prototype.zoom = function (lensArr) {
44266
- if (this._zoomStack === undefined) {
44267
- this._zoomStack = [];
44268
- }
44269
- this._zoomStack.push({
44270
- zoom: lensArr,
44271
- wrapped: this._wrapped
44272
- });
44273
- this._wrapped = lens(this._wrapped).get(lensArr);
44274
- return this;
44275
- };
44276
- lens.prototype.deZoom = function () {
44277
- var frame = this._zoomStack.pop();
44278
- this._wrapped = lens(frame.wrapped).set(frame.zoom, this._wrapped).freeze();
44279
- return this;
44280
- };
44281
- lens.prototype.get = function (lensArr) {
44282
- var obj = this._wrapped;
44283
- for (var i = 0; i < lensArr.length; i++) {
44284
- obj = dispatch(obj).get(obj, lensArr[i]);
44285
- }
44286
- return obj;
44287
- };
44288
- lens.prototype.mod = function (lensArr, f) {
44289
- var obj = this._wrapped;
44290
- var newObj = clone(obj);
44291
- var ops = dispatch(obj);
44292
- if (lensArr.length === 0) {
44293
- this._wrapped = f(this._wrapped);
44294
- } else if (lensArr.length === 1) {
44295
- this._wrapped = ops.mod(newObj, lensArr[0], f);
44296
- } else {
44297
- var monocle = lensArr[0];
44298
- var shortLens = lensArr.slice(1);
44299
-
44300
- // newObj = ops.mod(obj[monocle], shortLens, f);
44301
44432
 
44302
- newObj[monocle] = lens(obj[monocle]).mod(shortLens, f).freeze();
44303
- this._wrapped = newObj;
44304
- }
44305
- return this;
44306
- };
44433
+ /* eslint-disable import/no-named-as-default */
44434
+ var extraWidgets = [Categorizer$1, CSProgram$1, Dropdown$1, Explanation$1, Definition$1, Grapher$1, GradedGroup$1, GradedGroupSet$1, Group$1, Iframe$1, Image$1, Interactive, InteractiveGraph$1, LabelImage$1, Matrix$1, Matcher$1, Measurer$1, Molecule$1, NumberLine$1, Orderer$1, Passage$1, PassageRef$1, PassageRefTarget$1, Plotter$1, PythonProgram$1, Sorter$1, Table$1, Video$1, DeprecatedStandin$1];
44307
44435
 
44308
- // TODO - move to individual files
44309
- lens.prototype.merge = function (lensArr, props) {
44310
- this._wrapped = lens(this._wrapped).mod(lensArr, function (oldProps) {
44311
- return merge(oldProps, props);
44312
- }).freeze();
44313
- return this;
44314
- };
44436
+ /**
44437
+ * This should be called by all clients, specifying whether extra widgets are
44438
+ * needed via `loadExtraWidgets`. It is idempotent, so it's not a problem to
44439
+ * call it multiple times.
44440
+ *
44441
+ * skipMathJax:
44442
+ * If false/undefined, MathJax will be configured, and the
44443
+ * promise will wait for MathJax to load (if it hasn't already).
44444
+ */
44445
+ const init = function (options) {
44446
+ registerWidgets(basicWidgets);
44447
+ registerWidgets(extraWidgets);
44448
+ replaceDeprecatedWidgets();
44315
44449
 
44316
- // Lens must have length >= 1 or there would be nothing to return
44317
- lens.prototype.del = function (lensArr) {
44318
- var obj = this._wrapped;
44319
- var ops = dispatch(obj);
44320
- if (lensArr.length === 1) {
44321
- this._wrapped = ops.del(obj, lensArr[0]);
44322
- } else {
44323
- var monocle = lensArr[0];
44324
- var shortLens = lensArr.slice(1);
44325
- var newObj = clone(obj);
44326
- newObj[monocle] = lens(obj[monocle]).del(shortLens).freeze();
44327
- this._wrapped = newObj;
44450
+ // Pass skipMathJax: true if MathJax is already loaded and configured.
44451
+ const skipMathJax = options.skipMathJax;
44452
+ if (skipMathJax) {
44453
+ // @ts-expect-error - TS2322 - Type 'Promise<void>' is not assignable to type 'Promise<undefined>'.
44454
+ return Promise.resolve();
44328
44455
  }
44329
- return this;
44330
- };
44331
- lens.prototype.set = function (lensArr, set) {
44332
- return this.mod(lensArr, function () {
44333
- return set;
44456
+ return new Promise((resolve, reject) => {
44457
+ MathJax.Hub.Config({
44458
+ messageStyle: "none",
44459
+ skipStartupTypeset: "none",
44460
+ "HTML-CSS": {
44461
+ availableFonts: ["TeX"],
44462
+ imageFont: null,
44463
+ scale: 100,
44464
+ showMathMenu: false
44465
+ }
44466
+ });
44467
+ MathJax.Hub.Configured();
44468
+ MathJax.Hub.Queue(resolve);
44334
44469
  });
44335
44470
  };
44336
44471
 
44337
- // ARRAY FUNCTIONS
44472
+ // This file is processed by a Rollup plugin (replace) to inject the production
44473
+ const libName = "@khanacademy/perseus";
44474
+ const libVersion = "27.0.1";
44475
+ perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
44338
44476
 
44339
- const arr = {
44340
- get: function (arr, monocle) {
44341
- return arr[monocle];
44342
- },
44343
- set: function (arr, monocle, val) {
44344
- var newArr = arr.splice();
44345
- newArr[monocle] = val;
44346
- return newArr;
44347
- },
44348
- mod: function (arr, monocle, f) {
44349
- var newArr = arr.slice();
44350
- newArr[monocle] = f(arr[monocle]);
44351
- return newArr;
44352
- },
44353
- del: function (arr, monocle) {
44354
- var newArr = arr.slice();
44355
- newArr.splice(monocle, 1);
44356
- return newArr;
44357
- }
44477
+ const apiVersion = {
44478
+ major: 12,
44479
+ minor: 0
44358
44480
  };
44359
-
44360
- /*
44361
- // Lens must point to a member of an array. We'll insert into that array.
44362
- lens.prototype.insertAt = function(lensArr, toInsert) {
44363
- var obj = this._wrapped;
44364
-
44365
- var arrLens = lensArr.slice(0, -1);
44366
- var arr = lens(obj).get(arrLens).slice(); // slice to copy
44367
-
44368
- var arrIdx = lensArr[lensArr.length-1];
44369
- arr.splice(arrIdx, 0, toInsert);
44370
- return lens(obj).set(arrLens, arr);
44481
+ const itemDataVersion = {
44482
+ major: 0,
44483
+ minor: 1
44371
44484
  };
44372
44485
 
44373
- lens.prototype.insertBefore = lens.prototype.insertAt;
44374
- lens.prototype.insertAfter = function(lensArr, toInsert) {
44375
- var newLens = lensArr.slice();
44376
- newLens[newLens.length-1] += 1;
44377
- return lens(this._wrapped).insertAt(newLens, toInsert);
44378
- };
44379
- */
44486
+ var allWidgets = [...basicWidgets, ...extraWidgets];
44380
44487
 
44381
- // OBJECT FUNCTIONS
44488
+ registerWidgets(allWidgets);
44489
+ const ItemVersion = getVersionVector();
44490
+ ItemVersion["::renderer::"] = itemDataVersion;
44382
44491
 
44383
- const obj = {
44384
- get: function (obj, monocle) {
44385
- return obj[monocle];
44386
- },
44387
- set: function (obj, monocle, val) {
44388
- var newObj = clone(obj);
44389
- newObj[monocle] = val;
44390
- return newObj;
44391
- },
44392
- mod: function (obj, monocle, f) {
44393
- var newObj = clone(obj);
44394
- newObj[monocle] = f(obj[monocle]);
44395
- return newObj;
44396
- },
44397
- del: function (obj, monocle) {
44398
- var newObj = clone(obj);
44399
- delete newObj[monocle];
44400
- return newObj;
44492
+ class ArticleRenderer extends React__namespace.Component {
44493
+ static contextType = PerseusI18nContext;
44494
+ _currentFocus;
44495
+ sectionRenderers = [];
44496
+ static defaultProps = {
44497
+ apiOptions: ApiOptions.defaults,
44498
+ useNewStyles: false,
44499
+ linterContext: PerseusLinter__namespace.linterContextDefault
44500
+ };
44501
+ constructor(props) {
44502
+ super(props);
44401
44503
  }
44402
- };
44403
-
44404
- // STRING FUNCTIONS
44405
-
44406
- const str = {
44407
- get: function (arr, monocle) {
44408
- return arr[monocle];
44409
- },
44410
- set: function (arr, monocle, val) {
44411
- var newArr = arr.splice();
44412
- newArr[monocle] = val;
44413
- return newArr;
44414
- },
44415
- mod: function (arr, monocle, f) {
44416
- var newArr = arr.splice();
44417
- newArr[monocle] = f(arr[monocle]);
44418
- return newArr;
44419
- },
44420
- del: function (arr, monocle) {
44421
- var newArr = arr.slice();
44422
- newArr.splice(monocle);
44423
- return newArr;
44504
+ componentDidMount() {
44505
+ this._currentFocus = null;
44424
44506
  }
44425
- };
44426
-
44427
- /* eslint-disable eqeqeq, @typescript-eslint/no-unused-vars */
44428
- /*
44429
- * A class for formatting number to significant digits.
44430
- * Copyright (C) 2001 Stephen Ostermiller
44431
- * http://ostermiller.org/contact.pl?regarding?JavaScript+Significant+Figures
44432
- *
44433
- * This program is free software; you can redistribute it and/or modify
44434
- * it under the terms of the GNU General Public License as published by
44435
- * the Free Software Foundation; either version 2 of the License, or
44436
- * (at your option) any later version.
44437
- *
44438
- * This program is distributed in the hope that it will be useful,
44439
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
44440
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
44441
- * GNU General Public License for more details.
44442
- */
44443
-
44444
- /**
44445
- * An object tho both scans floating points to
44446
- * determine the number of significant figures.
44447
- * and can display a floating point using any number
44448
- * of significant figures.
44449
- *
44450
- * @param s A string representation of a floating point.
44451
- */
44452
- class SignificantFigures {
44453
- order;
44454
- mantissa;
44455
- positive;
44456
- sigFigs;
44457
- sigDecs;
44458
- constructor(s) {
44459
- this.order = parseOrder(s);
44460
- this.mantissa = parseMantissa(s);
44461
- this.positive = parseSign(s);
44462
-
44463
- /**
44464
- * Get the number of significant figures this object uses.
44465
- * Leading zeros are not significant. Traling zeros up to
44466
- * and after the decimal point are significant.
44467
- * Significant figures is good to know when the number is
44468
- * used in multiplication.
44469
- *
44470
- * @return the number of significant figures.
44471
- */
44472
- this.sigFigs = this.mantissa.length;
44473
-
44474
- /**
44475
- * Get the least significant decimal this object uses.
44476
- * This is useful to know if a number is being used
44477
- * in addition.
44478
- * 400 - 2 the hundreds place (10^2) is the least significant decimal.
44479
- * 75 - 0 the ones place (10^0) is the least significant decimal.
44480
- * .543 - -3 the 1/1000's place (10^-3) is the least significant decimal.
44481
- *
44482
- * @return an integer representing the least significant decimal place.
44483
- */
44484
- this.sigDecs = this.order - this.mantissa.length;
44507
+ shouldComponentUpdate(nextProps) {
44508
+ return nextProps !== this.props;
44485
44509
  }
44486
- }
44487
-
44488
- /**
44489
- * Format a floating point for display using the specified
44490
- * number of significant figures and least significant decimal.
44491
- * Scientific notation may used by this method if this
44492
- * object is very small, very large, has many significant
44493
- * figures, or the number of significant figures would be
44494
- * ambiguous in the output if scientific notation were not
44495
- * used.
44496
- *
44497
- * @param f A floating point number that should be displayed
44498
- * @param sigFigs desired number of significant figures (integer).
44499
- * @param sigDecs the least significant decimal place (integer).
44500
- * @param scientific true iff scientific notation should always be used.
44501
- * @return a string of this object formatted correctly.
44502
- */
44503
- function displaySigFigs(f, sigFigs, sigDecs, scientific) {
44504
- const s = "" + f;
44505
- let order = parseOrder(s);
44506
- let mantissa = parseMantissa(s);
44507
- let positive = parseSign(s);
44508
- let add;
44509
- let decAdd;
44510
- let sigAdd;
44511
- let zeroScientific = false;
44512
- if (f == 0 || mantissa == "" || mantissa == "0") {
44513
- mantissa = "";
44514
- for (let i = 0; i < sigFigs; i++) {
44515
- mantissa += "0";
44510
+ _handleFocusChange = (newFocusPath, oldFocusPath) => {
44511
+ // TODO(charlie): DRY this up--some of this logic is repeated in
44512
+ // ItemRenderer.
44513
+ if (newFocusPath) {
44514
+ this._setCurrentFocus(newFocusPath);
44515
+ } else {
44516
+ this._onRendererBlur(oldFocusPath);
44516
44517
  }
44517
- order = sigFigs + sigDecs;
44518
- if (sigDecs < 0 && -sigDecs >= sigFigs) {
44519
- zeroScientific = true;
44520
- }
44521
- } else {
44522
- decAdd = order - mantissa.length - sigDecs;
44523
- sigAdd = sigFigs - mantissa.length;
44524
- add = Math.min(sigAdd, decAdd);
44525
- if (add < 0) {
44526
- let rounded = round(mantissa, -add);
44527
- if (rounded.length > mantissa.length + add) {
44528
- order++;
44529
- if (decAdd > sigAdd) {
44530
- rounded = round(rounded, 1);
44531
- }
44532
- }
44533
- mantissa = rounded;
44534
- } else if (add > 0) {
44535
- for (let i = 0; i < add; i++) {
44536
- mantissa += "0";
44537
- }
44538
- }
44539
- if (mantissa == "" || mantissa == "0") {
44540
- mantissa = "0";
44541
- positive = true;
44542
- order = 1 + sigDecs;
44543
- if (order != 0) {
44544
- zeroScientific = true;
44545
- }
44546
- }
44547
- }
44548
- const useScientific = scientific || mantissa.length > 20 || order > 4 || order < -2 || order - mantissa.length > 0 && trailingZeros(mantissa) > 0 || zeroScientific;
44549
- let returnVal = "";
44550
- if (!positive) {
44551
- returnVal += "-";
44552
- }
44553
- if (useScientific) {
44554
- returnVal += mantissa.charAt(0);
44555
- if (mantissa.length > 1) {
44556
- returnVal += "." + mantissa.substring(1, mantissa.length);
44518
+ };
44519
+ _setCurrentFocus = newFocusPath => {
44520
+ const {
44521
+ keypadElement,
44522
+ apiOptions
44523
+ } = this.props;
44524
+ const {
44525
+ isMobile
44526
+ } = apiOptions;
44527
+ const prevFocusPath = this._currentFocus;
44528
+ this._currentFocus = newFocusPath;
44529
+
44530
+ // Use the section prefix to extract the relevant Renderer's input
44531
+ // paths, so as to check whether the focused path represents an
44532
+ // input.
44533
+ let didFocusInput = false;
44534
+ let focusedInput;
44535
+ if (this._currentFocus) {
44536
+ const [sectionIndex, ...focusPath] = this._currentFocus;
44537
+ const inputPaths = this.sectionRenderers[sectionIndex].getInputPaths();
44538
+ didFocusInput = inputPaths.some(inputPath => {
44539
+ return Util.inputPathsEqual(inputPath, focusPath);
44540
+ });
44541
+ focusedInput = this.sectionRenderers[sectionIndex].getDOMNodeForPath(focusPath);
44557
44542
  }
44558
- if (order - 1 != 0) {
44559
- returnVal += " x 10^" + (order - 1);
44543
+ const {
44544
+ onFocusChange
44545
+ } = this.props.apiOptions;
44546
+ if (onFocusChange) {
44547
+ // Wait for the keypad to mount before getting the height
44548
+ setTimeout(() => {
44549
+ const keypadDomNode = keypadElement?.getDOMNode();
44550
+ const keypadHeight = keypadDomNode && didFocusInput ? keypadDomNode.getBoundingClientRect().height : 0;
44551
+ onFocusChange(this._currentFocus, prevFocusPath, keypadHeight, didFocusInput ? focusedInput : null);
44552
+ }, 0);
44560
44553
  }
44561
- } else {
44562
- let wholePart = "";
44563
- let fractPart = "";
44564
- let needDot = true;
44565
- if (order > 0) {
44566
- if (mantissa.length > order) {
44567
- wholePart = mantissa.substring(0, order);
44568
- fractPart = mantissa.substring(order, mantissa.length);
44554
+ if (keypadElement && isMobile) {
44555
+ if (didFocusInput) {
44556
+ keypadElement.activate();
44569
44557
  } else {
44570
- wholePart = mantissa;
44571
- needDot = trailingZeros(mantissa) != 0;
44572
- for (let i = 0; i < order - mantissa.length; i++) {
44573
- wholePart += "0";
44574
- }
44575
- }
44576
- } else {
44577
- for (let i = 0; i < -order; i++) {
44578
- fractPart += "0";
44558
+ keypadElement.dismiss();
44579
44559
  }
44580
- fractPart += mantissa;
44581
44560
  }
44582
- returnVal += (wholePart == "" ? "0" : wholePart) + (needDot ? "." : "") + fractPart;
44583
- }
44584
- return returnVal;
44585
- }
44561
+ };
44562
+ _onRendererBlur = blurPath => {
44563
+ const blurringFocusPath = this._currentFocus;
44586
44564
 
44587
- /**
44588
- * Count the significant trailing zeros on this object.
44589
- *
44590
- * @return the number of trailing zeros
44591
- */
44592
- function trailingZeros(mantissa) {
44593
- let zeros = 0;
44594
- for (let i = mantissa.length - 1; i >= 0; i--) {
44595
- const c = mantissa.charAt(i);
44596
- if (c == "0") {
44597
- zeros++;
44598
- } else {
44599
- return zeros;
44565
+ // Failsafe: abort if ID is different, because focus probably happened
44566
+ // before blur.
44567
+ if (!Util.inputPathsEqual(blurPath, blurringFocusPath)) {
44568
+ return;
44600
44569
  }
44601
- }
44602
- return zeros;
44603
- }
44604
44570
 
44605
- /**
44606
- * Parse a string representation of a floating point
44607
- * and pull out the sign.
44608
- *
44609
- * @param s the string representation of a floating point.
44610
- * @return true iff this is a positive number
44611
- */
44612
- function parseSign(s) {
44613
- let beginning = true;
44614
- let seenDot = false;
44615
- let seenSomething = false;
44616
- let zeros = "";
44617
- let all = "";
44618
- let pos = true;
44619
- for (let i = 0; i < s.length; i++) {
44620
- const c = s.charAt(i);
44621
- if (c >= "1" && c <= "9") {
44622
- all += zeros + c;
44623
- zeros = "";
44624
- seenSomething = true;
44625
- beginning = false;
44626
- } else if (c == "0") {
44627
- if (seenDot) {
44628
- if (seenSomething) {
44629
- all += zeros + c;
44630
- zeros = "";
44631
- }
44632
- } else {
44633
- if (seenSomething) {
44634
- zeros += c;
44635
- }
44571
+ // Wait until after any new focus events fire this tick before declaring
44572
+ // that nothing is focused, since if there were a focus change across
44573
+ // sections, we could receive the blur before the focus.
44574
+ // TODO(jeff, CP-3128): Use Wonder Blocks Timing API
44575
+ // eslint-disable-next-line no-restricted-syntax
44576
+ setTimeout(() => {
44577
+ if (Util.inputPathsEqual(this._currentFocus, blurringFocusPath)) {
44578
+ this._setCurrentFocus(null);
44636
44579
  }
44637
- beginning = false;
44638
- } else if (!seenDot && c == ".") {
44639
- all += zeros;
44640
- zeros = "";
44641
- seenDot = true;
44642
- beginning = false;
44643
- } else if (c == "e" || c == "E" && i + 1 < s.length) {
44644
- parseInt(s.substring(i + 1, s.length));
44645
- i = s.length;
44646
- } else if (beginning && (c == "+" || c == "-")) {
44647
- if (c == "-") {
44648
- pos = !pos;
44580
+ });
44581
+ };
44582
+ blur = () => {
44583
+ if (this._currentFocus) {
44584
+ const [sectionIndex, ...inputPath] = this._currentFocus;
44585
+ this.sectionRenderers[sectionIndex].blurPath(inputPath);
44586
+ }
44587
+ };
44588
+ _sections = () => {
44589
+ const sections = Array.isArray(this.props.json) ? this.props.json : [this.props.json];
44590
+
44591
+ // In JIPT context we split sections to paragraphs in order match the
44592
+ // translatable strings found on Crowdin when rendering articles in
44593
+ // the WYSIWYG mode for translation. This is needed for the jipt.js
44594
+ // integration in order to attribute the rendered strings on Crowdin.
44595
+ if (getDependencies().JIPT.useJIPT) {
44596
+ const paragraphs = [];
44597
+ for (const section of sections) {
44598
+ JiptParagraphs.parseToArray(section.content).forEach(paragraph => {
44599
+ paragraphs.push({
44600
+ ...section,
44601
+ content: paragraph
44602
+ });
44603
+ });
44649
44604
  }
44605
+ return paragraphs;
44650
44606
  }
44651
- }
44652
- if (all == "") {
44653
- return true;
44654
- }
44655
- return pos;
44656
- }
44657
-
44658
- /**
44659
- * Parse a string representation of a floating point
44660
- * and pull out the mantissa.
44661
- *
44662
- * @param s the string representation of a floating point.
44663
- * @return the mantissa of this number.
44664
- */
44665
- function parseMantissa(s) {
44666
- let seenDot = false;
44667
- let seenSomething = false;
44668
- let zeros = "";
44669
- let leadZeros = "";
44670
- let all = "";
44671
- for (let i = 0; i < s.length; i++) {
44672
- const c = s.charAt(i);
44673
- if (c >= "1" && c <= "9") {
44674
- all += zeros + c;
44675
- zeros = "";
44676
- seenSomething = true;
44677
- } else if (c == "0") {
44678
- if (seenDot) {
44679
- if (seenSomething) {
44680
- all += zeros + c;
44681
- zeros = "";
44682
- } else {
44683
- leadZeros += c;
44684
- }
44685
- } else {
44686
- if (seenSomething) {
44687
- leadZeros += c;
44688
- zeros += c;
44689
- } else {
44690
- leadZeros += c;
44691
- }
44692
- }
44693
- } else if (!seenDot && c == ".") {
44694
- all += zeros;
44695
- zeros = "";
44696
- seenDot = true;
44697
- } else if (c == "e" || c == "E" && i + 1 < s.length) {
44698
- parseInt(s.substring(i + 1, s.length));
44699
- i = s.length;
44700
- } else ;
44701
- }
44702
- if (all == "") {
44703
- return leadZeros;
44704
- }
44705
- return all;
44706
- }
44707
-
44708
- /**
44709
- * Parse a string representation of a floating point
44710
- * and pull out the exponent.
44711
- *
44712
- * @param s the string representation of a floating point.
44713
- * @return (integer) the number after the e.
44714
- */
44715
- function parseOrder(s) {
44716
- let seenDot = false;
44717
- let seenSomething = false;
44718
- let zeros = "";
44719
- let all = "";
44720
- let decPlaces = 0;
44721
- let totalDecs = 0;
44722
- for (let i = 0; i < s.length; i++) {
44723
- const c = s.charAt(i);
44724
- if (c >= "1" && c <= "9") {
44725
- all += zeros + c;
44726
- zeros = "";
44727
- seenSomething = true;
44728
- if (!seenDot) {
44729
- totalDecs++;
44730
- decPlaces++;
44731
- }
44732
- } else if (c == "0") {
44733
- if (seenDot) {
44734
- if (seenSomething) {
44735
- all += zeros + c;
44736
- zeros = "";
44737
- } else {
44738
- decPlaces--;
44739
- }
44740
- } else {
44741
- totalDecs++;
44742
- if (seenSomething) {
44743
- decPlaces++;
44744
- zeros += c;
44745
- }
44746
- }
44747
- } else if (!seenDot && c == ".") {
44748
- all += zeros;
44749
- zeros = "";
44750
- seenDot = true;
44751
- } else if (c == "e" || c == "E" && i + 1 < s.length) {
44752
- const raised = parseInt(s.substring(i + 1, s.length));
44753
- decPlaces += raised;
44754
- totalDecs += raised;
44755
- i = s.length;
44756
- } else ;
44757
- }
44758
- if (all == "") {
44759
- return totalDecs;
44760
- }
44761
- return decPlaces;
44762
- }
44763
-
44764
- /**
44765
- * Remove the specified number of digits from string by
44766
- * rounding. Proper rounding rules for scientific purposes
44767
- * are followed.
44768
- * This method may cause an extra significant figure
44769
- * to be added to the number. For example, if 999999
44770
- * were rounded, A one would carry over and become
44771
- * a significant figure. Those who call this method
44772
- * should check for this and call round again if needed.
44773
- *
44774
- * @param mantissa A string representing an whole number of arbitrary length.
44775
- * @param digits A number of digits to remove
44776
- * @return A string represted the rounded version of mantissa
44777
- */
44778
- function round(mantissa, digits) {
44779
- const last = mantissa.length - digits - 1;
44780
- if (last < 0) {
44781
- return "";
44782
- }
44783
- if (last >= mantissa.length - 1) {
44784
- return mantissa;
44785
- }
44786
- const nextToLast = mantissa.charAt(last + 1);
44787
- const lastChar = mantissa.charAt(last);
44788
- let roundUp = false;
44789
- if (nextToLast > "5") {
44790
- roundUp = true;
44791
- } else if (nextToLast == "5") {
44792
- for (let j = last + 2; j < mantissa.length; j++) {
44793
- if (mantissa.charAt(j) != "0") {
44794
- roundUp = true;
44795
- }
44796
- }
44797
- // @ts-expect-error - TS2362 - The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
44798
- if (lastChar % 2 == 1) {
44799
- roundUp = true;
44800
- }
44801
- }
44802
- let result = "";
44803
- for (let i = last; i >= 0; i--) {
44804
- const c = mantissa.charAt(i);
44805
- if (roundUp) {
44806
- let nextChar;
44807
- if (c == "9") {
44808
- nextChar = "0";
44809
- } else {
44810
- switch (c) {
44811
- case "0":
44812
- nextChar = "1";
44813
- break;
44814
- case "1":
44815
- nextChar = "2";
44816
- break;
44817
- case "2":
44818
- nextChar = "3";
44819
- break;
44820
- case "3":
44821
- nextChar = "4";
44822
- break;
44823
- case "4":
44824
- nextChar = "5";
44825
- break;
44826
- case "5":
44827
- nextChar = "6";
44828
- break;
44829
- case "6":
44830
- nextChar = "7";
44831
- break;
44832
- case "7":
44833
- nextChar = "8";
44834
- break;
44835
- case "8":
44836
- nextChar = "9";
44837
- break;
44838
- }
44839
- roundUp = false;
44840
- }
44841
- result = nextChar + result;
44842
- } else {
44843
- result = c + result;
44844
- }
44845
- }
44846
- if (roundUp) {
44847
- result = "1" + result;
44848
- }
44849
- return result;
44850
- }
44851
-
44852
- // TODO(joel): teach KAS how to accept an answer only if it's expressed in
44853
- const ALL = "all";
44854
- const countSigfigs = function (value) {
44855
- return new SignificantFigures(value).sigFigs;
44856
- };
44857
- /* I just wrote this, but it's old by analogy to `OldExpression`, in that it's
44858
- * the version that non-mathquill platforms get stuck with. Constructed with an
44859
- * <input>, a parser, popsicle sticks, and glue.
44860
- *
44861
- * In the same way as OldExpression, this parses continuously as you type, then
44862
- * shows and hides an error buddy. The error message is only shown after a
44863
- * rolling two second delay, but hidden immediately on further typing.
44864
- */
44865
- class OldUnitInput extends React__namespace.Component {
44866
- static contextType = PerseusI18nContext;
44867
- _errorTimeout;
44868
- static defaultProps = {
44869
- apiOptions: ApiOptions.defaults,
44870
- value: ""
44871
- };
44872
- static validate(userInput, rubric, strings) {
44873
- const answer = KAS__namespace.unitParse(rubric.value).expr;
44874
- const guess = KAS__namespace.unitParse(userInput);
44875
- if (!guess.parsed) {
44876
- return {
44877
- type: "invalid",
44878
- message: strings.dontUnderstandUnits
44879
- };
44880
- }
44881
-
44882
- // Note: we check sigfigs, then numerical correctness, then units, so
44883
- // the most significant things come last, that way the user will see
44884
- // the most important message.
44885
- let message = null;
44886
-
44887
- // did the user specify the right number of sigfigs?
44888
- // TODO(joel) - add a grading mode where the wrong number of sigfigs
44889
- // isn't marked wrong
44890
- const sigfigs = rubric.sigfigs;
44891
- const sigfigsCorrect = countSigfigs(guess.coefficient) === sigfigs;
44892
- if (!sigfigsCorrect) {
44893
- message = strings.checkSigFigs;
44894
- }
44895
-
44896
- // now we need to check that the answer is correct to the precision we
44897
- // require.
44898
- let numericallyCorrect;
44899
- try {
44900
- const x = new KAS__namespace.Var("x");
44901
- const equality = new KAS__namespace.Eq(answer.simplify(), "=", new KAS__namespace.Mul(x, guess.expr.simplify()));
44902
- const conversion = equality.solveLinearEquationForVariable(x);
44903
-
44904
- // Make sure the conversion factor between the user's input answer
44905
- // and the canonical answer is 1, to sigfig places.
44906
- // TODO(joel) is this sound?
44907
- numericallyCorrect = Number(conversion.eval()).toPrecision(sigfigs) === Number(1).toPrecision(sigfigs);
44908
- } catch (e) {
44909
- numericallyCorrect = false;
44910
- }
44911
- if (!numericallyCorrect) {
44912
- message = strings.answerNumericallyIncorrect;
44913
- }
44914
- let kasCorrect;
44915
- const guessUnit = primUnits(guess.expr.simplify());
44916
- const answerUnit = primUnits(answer.simplify());
44917
- if (rubric.accepting === ALL) {
44918
- // We're accepting all units - KAS does the hard work of figuring
44919
- // out if the user's unit is equivalent to the author's unit.
44920
- kasCorrect = KAS__namespace.compare(guessUnit, answerUnit).equal;
44921
- } else {
44922
- // Are any of the accepted units the same as what the user entered?
44923
- kasCorrect = ___default["default"](rubric.acceptingUnits).any(unit => {
44924
- const thisAnswerUnit = primUnits(KAS__namespace.unitParse(unit).unit.simplify());
44925
- return KAS__namespace.compare(thisAnswerUnit, guessUnit
44926
- // TODO(joel) - make this work as intended.
44927
- // { form: true }
44928
- ).equal;
44929
- });
44930
- }
44931
- if (!kasCorrect) {
44932
- message = strings.checkUnits;
44933
- }
44934
- const correct = kasCorrect && numericallyCorrect && sigfigsCorrect;
44935
- return {
44936
- type: "points",
44937
- earned: correct ? 1 : 0,
44938
- total: 1,
44939
- message
44940
- };
44941
- }
44942
- componentDidUpdate() {
44943
- // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
44944
- // eslint-disable-next-line no-restricted-syntax
44945
- // @ts-expect-error - TS2769 - No overload matches this call.
44946
- clearTimeout(this._errorTimeout);
44947
- if (KAS__namespace.unitParse(this.props.value).parsed) {
44948
- this._hideError();
44949
- } else {
44950
- // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
44951
- // eslint-disable-next-line no-restricted-syntax
44952
- // @ts-expect-error - TS2322 - Type 'Timeout' is not assignable to type 'number'.
44953
- this._errorTimeout = setTimeout(this._showError, 2000);
44954
- }
44955
- }
44956
- componentWillUnmount() {
44957
- // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
44958
- // eslint-disable-next-line no-restricted-syntax
44959
- // @ts-expect-error - TS2769 - No overload matches this call.
44960
- clearTimeout(this._errorTimeout);
44961
- }
44962
- _showError = () => {
44963
- if (this.props.value === "") {
44964
- return;
44965
- }
44966
-
44967
- // eslint-disable-next-line react/no-string-refs
44968
- // @ts-expect-error - TS2769 - No overload matches this call.
44969
- const $error = $__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.error));
44970
- // @ts-expect-error - TS2339 - Property 'is' does not exist on type 'JQueryStatic'.
44971
- if (!$error.is(":visible")) {
44972
- $error
44973
- // @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
44974
- .css({
44975
- top: 50,
44976
- opacity: 0.1
44977
- }).show().animate({
44978
- top: 0,
44979
- opacity: 1.0
44980
- }, 300);
44981
- }
44982
- };
44983
- _hideError = () => {
44984
- // eslint-disable-next-line react/no-string-refs
44985
- // @ts-expect-error - TS2769 - No overload matches this call.
44986
- const $error = $__default["default"](ReactDOM__default["default"].findDOMNode(this.refs.error));
44987
- // @ts-expect-error - TS2339 - Property 'is' does not exist on type 'JQueryStatic'.
44988
- if ($error.is(":visible")) {
44989
- // @ts-expect-error - TS2339 - Property 'animate' does not exist on type 'JQueryStatic'.
44990
- $error.animate({
44991
- top: 50,
44992
- opacity: 0.1
44993
- }, 300, function () {
44994
- // eslint-disable-next-line @babel/no-invalid-this
44995
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
44996
- // eslint-disable-next-line @babel/no-invalid-this
44997
- $__default["default"](this).hide();
44998
- });
44999
- }
45000
- };
45001
- change = (() => {
45002
- var _this = this;
45003
- return function () {
45004
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
45005
- args[_key] = arguments[_key];
45006
- }
45007
- // @ts-expect-error - TS2345 - Argument of type 'readonly any[]' is not assignable to parameter of type 'any[]'.
45008
- return change.apply(_this, args);
45009
- };
45010
- })();
45011
- handleBlur = () => {
45012
- this.props.onBlur([]);
45013
- // TODO(jeff, CP-3128): Use Wonder Blocks Timing API.
45014
- // eslint-disable-next-line no-restricted-syntax
45015
- // @ts-expect-error - TS2769 - No overload matches this call.
45016
- clearTimeout(this._errorTimeout);
45017
- if (!KAS__namespace.unitParse(this.props.value).parsed) {
45018
- this._showError();
45019
- }
45020
- };
45021
- handleChange = event => {
45022
- this._hideError();
45023
- this.props.onChange({
45024
- value: event.target.value
45025
- });
45026
- };
45027
- simpleValidate = (rubric, onInputError) => {
45028
- return OldUnitInput.validate(this.getUserInput(), rubric, this.context.strings);
45029
- };
45030
- getUserInput = () => {
45031
- return this.props.value;
45032
- };
45033
-
45034
- // begin mobile stuff
45035
-
45036
- getInputPaths = () => {
45037
- // The widget itself is an input, so we return a single empty list to
45038
- // indicate this.
45039
- return [[]];
45040
- };
45041
- focusInputPath = inputPath => {
45042
- // eslint-disable-next-line react/no-string-refs
45043
- const input = ReactDOM__default["default"].findDOMNode(this.refs.input);
45044
- if (input) {
45045
- /**
45046
- * TODO(somewhatabstract, JIRA-XXXX):
45047
- * Change to using a ref callback so that focus() can be
45048
- * accessed.
45049
- */
45050
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'Element | Text'.
45051
- input.focus();
45052
- }
45053
- };
45054
- handleFocus = () => {
45055
- this.props.onFocus([]);
45056
- };
45057
- blurInputPath = inputPath => {
45058
- // eslint-disable-next-line react/no-string-refs
45059
- const input = ReactDOM__default["default"].findDOMNode(this.refs.input);
45060
- if (input) {
45061
- /**
45062
- * TODO(somewhatabstract, JIRA-XXXX):
45063
- * Change to using a ref callback so that focus() can be
45064
- * accessed.
45065
- */
45066
- // @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'Element | Text'.
45067
- input.blur();
45068
- }
45069
- };
45070
- setInputValue = (path, newValue, cb) => {
45071
- this.props.onChange({
45072
- value: newValue
45073
- }, cb);
45074
- };
45075
- getDOMNodeForPath = () => {
45076
- // eslint-disable-next-line react/no-string-refs
45077
- return ReactDOM__default["default"].findDOMNode(this.refs.input);
45078
- };
45079
- getGrammarTypeForPath = inputPath => {
45080
- return "unit";
45081
- };
45082
-
45083
- // end mobile stuff
45084
-
45085
- // TODO(joel) think about showing the error buddy
45086
- render() {
45087
- return /*#__PURE__*/React__namespace.createElement("div", {
45088
- className: "old-unit-input"
45089
- }, /*#__PURE__*/React__namespace.createElement("input", {
45090
- onChange: this.handleChange,
45091
- ref: "input" // eslint-disable-line react/no-string-refs
45092
- ,
45093
- className: ClassNames.INTERACTIVE,
45094
- value: this.props.value,
45095
- onFocus: this.handleFocus,
45096
- onBlur: this.handleBlur
45097
- }), /*#__PURE__*/React__namespace.createElement("div", {
45098
- ref: "error",
45099
- className: "error",
45100
- style: {
45101
- display: "none"
45102
- }
45103
- }, this.context.strings.dontUnderstand));
45104
- }
45105
- }
45106
-
45107
- // Extract the primitive units from a unit expression. This first simplifies
45108
- // `expr` to a `Mul` like "5 kg m / s^2" then removes the first term.
45109
- const primUnits = function (expr) {
45110
- return expr.simplify().asMul().partition()[1].flatten().simplify();
45111
- };
45112
- var Unit = {
45113
- name: "unit-input",
45114
- displayName: "Unit input",
45115
- defaultAlignment: "inline-block",
45116
- widget: OldUnitInput,
45117
- transform: x => lens(x).del(["value"]).freeze(),
45118
- version: {
45119
- major: 0,
45120
- minor: 1
45121
- },
45122
- hidden: true
45123
- };
45124
-
45125
- /**
45126
- * This is a component that adds a link to the transcript for embedded videos in articles.
45127
- */
45128
- const IS_URL$1 = /^https?:\/\//;
45129
- const getYoutubeId = url => {
45130
- const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
45131
- const match = url.match(regExp);
45132
- if (match && match[7].length === 11) {
45133
- return match[7];
45134
- }
45135
- return "videoNotFound";
45136
- };
45137
-
45138
- // will accept a youtube link or a video's slug name from embedded video and then query accordingly.
45139
-
45140
- /**
45141
- * Video Transcript Link Component.
45142
- */
45143
- const VideoTranscriptLink = props => {
45144
- const {
45145
- location
45146
- } = props;
45147
- const {
45148
- useVideo
45149
- } = getDependencies();
45150
- const [id, kind] = IS_URL$1.test(location) ? [getYoutubeId(location), "YOUTUBE_ID"] : [location, "READABLE_ID"];
45151
- // The result value conforms to the wonder-blocks-data `Result` type
45152
- // which is used by our GraphQL framework.
45153
- // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'VideoKind'.
45154
- const result = useVideo(id, kind);
45155
- const {
45156
- strings
45157
- } = usePerseusI18n();
45158
- switch (result.status) {
45159
- case "loading":
45160
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.loading);
45161
- case "success":
45162
- {
45163
- const video = result.data?.video;
45164
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
45165
- style: styles$4.transcriptLink
45166
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.Text, null, video?.title), /*#__PURE__*/React__namespace.createElement(wonderBlocksLayout.Strut, {
45167
- size: 10
45168
- }), /*#__PURE__*/React__namespace.createElement(Link__default["default"], {
45169
- href: "/transcript/" + (video?.contentId || "videoNotFound"),
45170
- target: "_blank",
45171
- className: "visited-no-recolor"
45172
- }, strings.videoTranscript));
45173
- }
45174
- case "error":
45175
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
45176
- case "aborted":
45177
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
45178
- default:
45179
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
45180
- }
45181
- };
45182
- const styles$4 = aphrodite.StyleSheet.create({
45183
- transcriptLink: {
45184
- flexDirection: "row",
45185
- width: "100%",
45186
- justifyContent: "center"
45187
- }
45188
- });
45189
-
45190
- /**
45191
- * This is a video widget for embedding videos in articles.
45192
- */
45193
- // Current default is 720p, based on the typical videos we upload currently
45194
- const DEFAULT_WIDTH = 1280;
45195
- const DEFAULT_HEIGHT = 720;
45196
- const KA_EMBED = "{host}/embed_video?slug={slug}" + "&internal_video_only=1";
45197
- const IS_URL = /^https?:\/\//;
45198
- const IS_KA_SITE = /(khanacademy\.org|localhost)/;
45199
- const IS_VIMEO = /(vimeo\.com)/;
45200
-
45201
- // exports has no 'transform'
45202
-
45203
- /**
45204
- * Video renderer.
45205
- */
45206
- class Video extends React__namespace.Component {
45207
- static contextType = PerseusI18nContext;
45208
- /**
45209
- * This is the widget's grading function.
45210
- * Points for videos are tallied by the embedded video itself, in the case
45211
- * of Khan Academy videos.
45212
- */
45213
- static validate(userInput, rubric) {
45214
- return {
45215
- type: "points",
45216
- earned: 0,
45217
- total: 0,
45218
- message: null
45219
- };
45220
- }
45221
- getUserInput = () => {
45222
- return null;
45223
- };
45224
- simpleValidate = rubric => {
45225
- return Video.validate(null, rubric);
45226
- };
45227
- change = (() => {
45228
- var _this = this;
45229
- return function () {
45230
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
45231
- args[_key] = arguments[_key];
45232
- }
45233
- // @ts-expect-error - TS2345 - Argument of type 'readonly unknown[]' is not assignable to parameter of type 'any[]'.
45234
- return change.apply(_this, args);
45235
- };
45236
- })();
45237
- render() {
45238
- const {
45239
- InitialRequestUrl
45240
- } = getDependencies();
45241
- const location = this.props.location;
45242
- if (!location) {
45243
- return /*#__PURE__*/React__namespace.createElement("div", null);
45244
- }
45245
- let url;
45246
- if (IS_URL.test(location)) {
45247
- url = location;
45248
- if (IS_VIMEO.test(url)) {
45249
- // If this is a vimeo video, we need to add the query string
45250
- // parameter "dnt" so that analytics/tracking cookies aren't set.
45251
- // https://help.vimeo.com/hc/en-us/articles/12426260232977-Player-parameters-overview
45252
- if (url.indexOf("?") === -1) {
45253
- url += "?dnt=1";
45254
- } else {
45255
- url += "&dnt=1";
45256
- }
45257
- }
45258
- } else {
45259
- url = KA_EMBED.replace("{slug}", location);
45260
- let embedHostname = "https://www.khanacademy.org";
45261
- if (IS_KA_SITE.test(InitialRequestUrl.host)) {
45262
- embedHostname = InitialRequestUrl.origin;
45263
- }
45264
- url = url.replace("{host}", embedHostname);
45265
- }
45266
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(FixedToResponsive, {
45267
- width: DEFAULT_WIDTH,
45268
- height: DEFAULT_HEIGHT
45269
- // The key is here for the benefit of the editor, to ensure that
45270
- // any changes cause a re-rendering of the frame.
45271
- ,
45272
- key: location + this.props.alignment
45273
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
45274
- style: a11y.srOnly
45275
- }, this.context.strings.videoWrapper), /*#__PURE__*/React__namespace.createElement("iframe", {
45276
- // TODO(joshuan): Consider not using iframes when we're
45277
- // loading this from webapp. This iframe is problematic
45278
- // for screenreaders.
45279
- className: "perseus-video-widget",
45280
- sandbox: "allow-same-origin allow-scripts",
45281
- width: DEFAULT_WIDTH,
45282
- height: DEFAULT_HEIGHT,
45283
- src: url,
45284
- allowFullScreen: true
45285
- })), /*#__PURE__*/React__namespace.createElement(VideoTranscriptLink, {
45286
- location: location
45287
- }));
45288
- }
45289
- }
45290
- var Video$1 = {
45291
- name: "video",
45292
- displayName: "Video",
45293
- defaultAlignment: "block",
45294
- supportedAlignments: ["block", "float-left", "float-right", "full-width"],
45295
- widget: Video
45296
- };
45297
-
45298
- /* eslint-disable import/no-named-as-default */
45299
- var extraWidgets = [Categorizer$1, CSProgram$1, Dropdown$1, Explanation$1, Definition$1, Grapher$1, GradedGroup$1, GradedGroupSet$1, Group$1, Iframe$1, Image$1, Interactive, InteractiveGraph$1, LabelImage$1, Matrix$1, Matcher$1, Measurer$1, Molecule$1, NumberLine$1, Orderer$1, Passage$1, PassageRef$1, PassageRefTarget$1, Plotter$1, PythonProgram$1, Sorter$1, Table$1, Unit, Video$1, DeprecatedStandin$1];
45300
-
45301
- /**
45302
- * This should be called by all clients, specifying whether extra widgets are
45303
- * needed via `loadExtraWidgets`. It is idempotent, so it's not a problem to
45304
- * call it multiple times.
45305
- *
45306
- * skipMathJax:
45307
- * If false/undefined, MathJax will be configured, and the
45308
- * promise will wait for MathJax to load (if it hasn't already).
45309
- */
45310
- const init = function (options) {
45311
- registerWidgets(basicWidgets);
45312
- registerWidgets(extraWidgets);
45313
- replaceDeprecatedWidgets();
45314
-
45315
- // Pass skipMathJax: true if MathJax is already loaded and configured.
45316
- const skipMathJax = options.skipMathJax;
45317
- if (skipMathJax) {
45318
- // @ts-expect-error - TS2322 - Type 'Promise<void>' is not assignable to type 'Promise<undefined>'.
45319
- return Promise.resolve();
45320
- }
45321
- return new Promise((resolve, reject) => {
45322
- MathJax.Hub.Config({
45323
- messageStyle: "none",
45324
- skipStartupTypeset: "none",
45325
- "HTML-CSS": {
45326
- availableFonts: ["TeX"],
45327
- imageFont: null,
45328
- scale: 100,
45329
- showMathMenu: false
45330
- }
45331
- });
45332
- MathJax.Hub.Configured();
45333
- MathJax.Hub.Queue(resolve);
45334
- });
45335
- };
45336
-
45337
- // This file is processed by a Rollup plugin (replace) to inject the production
45338
- const libName = "@khanacademy/perseus";
45339
- const libVersion = "26.1.0";
45340
- perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45341
-
45342
- const apiVersion = {
45343
- major: 12,
45344
- minor: 0
45345
- };
45346
- const itemDataVersion = {
45347
- major: 0,
45348
- minor: 1
45349
- };
45350
-
45351
- var allWidgets = [...basicWidgets, ...extraWidgets];
45352
-
45353
- registerWidgets(allWidgets);
45354
- const ItemVersion = getVersionVector();
45355
- ItemVersion["::renderer::"] = itemDataVersion;
45356
-
45357
- class ArticleRenderer extends React__namespace.Component {
45358
- static contextType = PerseusI18nContext;
45359
- _currentFocus;
45360
- sectionRenderers = [];
45361
- static defaultProps = {
45362
- apiOptions: ApiOptions.defaults,
45363
- useNewStyles: false,
45364
- linterContext: PerseusLinter__namespace.linterContextDefault
45365
- };
45366
- constructor(props) {
45367
- super(props);
45368
- }
45369
- componentDidMount() {
45370
- this._currentFocus = null;
45371
- }
45372
- shouldComponentUpdate(nextProps) {
45373
- return nextProps !== this.props;
45374
- }
45375
- _handleFocusChange = (newFocusPath, oldFocusPath) => {
45376
- // TODO(charlie): DRY this up--some of this logic is repeated in
45377
- // ItemRenderer.
45378
- if (newFocusPath) {
45379
- this._setCurrentFocus(newFocusPath);
45380
- } else {
45381
- this._onRendererBlur(oldFocusPath);
45382
- }
45383
- };
45384
- _setCurrentFocus = newFocusPath => {
45385
- const {
45386
- keypadElement,
45387
- apiOptions
45388
- } = this.props;
45389
- const {
45390
- isMobile
45391
- } = apiOptions;
45392
- const prevFocusPath = this._currentFocus;
45393
- this._currentFocus = newFocusPath;
45394
-
45395
- // Use the section prefix to extract the relevant Renderer's input
45396
- // paths, so as to check whether the focused path represents an
45397
- // input.
45398
- let didFocusInput = false;
45399
- let focusedInput;
45400
- if (this._currentFocus) {
45401
- const [sectionIndex, ...focusPath] = this._currentFocus;
45402
- const inputPaths = this.sectionRenderers[sectionIndex].getInputPaths();
45403
- didFocusInput = inputPaths.some(inputPath => {
45404
- return Util.inputPathsEqual(inputPath, focusPath);
45405
- });
45406
- focusedInput = this.sectionRenderers[sectionIndex].getDOMNodeForPath(focusPath);
45407
- }
45408
- const {
45409
- onFocusChange
45410
- } = this.props.apiOptions;
45411
- if (onFocusChange) {
45412
- // Wait for the keypad to mount before getting the height
45413
- setTimeout(() => {
45414
- const keypadDomNode = keypadElement?.getDOMNode();
45415
- const keypadHeight = keypadDomNode && didFocusInput ? keypadDomNode.getBoundingClientRect().height : 0;
45416
- onFocusChange(this._currentFocus, prevFocusPath, keypadHeight, didFocusInput ? focusedInput : null);
45417
- }, 0);
45418
- }
45419
- if (keypadElement && isMobile) {
45420
- if (didFocusInput) {
45421
- keypadElement.activate();
45422
- } else {
45423
- keypadElement.dismiss();
45424
- }
45425
- }
45426
- };
45427
- _onRendererBlur = blurPath => {
45428
- const blurringFocusPath = this._currentFocus;
45429
-
45430
- // Failsafe: abort if ID is different, because focus probably happened
45431
- // before blur.
45432
- if (!Util.inputPathsEqual(blurPath, blurringFocusPath)) {
45433
- return;
45434
- }
45435
-
45436
- // Wait until after any new focus events fire this tick before declaring
45437
- // that nothing is focused, since if there were a focus change across
45438
- // sections, we could receive the blur before the focus.
45439
- // TODO(jeff, CP-3128): Use Wonder Blocks Timing API
45440
- // eslint-disable-next-line no-restricted-syntax
45441
- setTimeout(() => {
45442
- if (Util.inputPathsEqual(this._currentFocus, blurringFocusPath)) {
45443
- this._setCurrentFocus(null);
45444
- }
45445
- });
45446
- };
45447
- blur = () => {
45448
- if (this._currentFocus) {
45449
- const [sectionIndex, ...inputPath] = this._currentFocus;
45450
- this.sectionRenderers[sectionIndex].blurPath(inputPath);
45451
- }
45452
- };
45453
- _sections = () => {
45454
- const sections = Array.isArray(this.props.json) ? this.props.json : [this.props.json];
45455
-
45456
- // In JIPT context we split sections to paragraphs in order match the
45457
- // translatable strings found on Crowdin when rendering articles in
45458
- // the WYSIWYG mode for translation. This is needed for the jipt.js
45459
- // integration in order to attribute the rendered strings on Crowdin.
45460
- if (getDependencies().JIPT.useJIPT) {
45461
- const paragraphs = [];
45462
- for (const section of sections) {
45463
- JiptParagraphs.parseToArray(section.content).forEach(paragraph => {
45464
- paragraphs.push({
45465
- ...section,
45466
- content: paragraph
45467
- });
45468
- });
45469
- }
45470
- return paragraphs;
45471
- }
45472
- return sections;
45473
- };
45474
- render() {
45475
- const apiOptions = {
45476
- ...ApiOptions.defaults,
45477
- ...this.props.apiOptions,
45478
- isArticle: true
45479
- };
45480
- const classes = classNames__default["default"]({
45481
- "framework-perseus": true,
45482
- "perseus-article": true,
45483
- // NOTE(charlie): For exercises, this is applied outside of Perseus
45484
- // (in webapp).
45485
- [ClassNames.MOBILE]: apiOptions.isMobile
45486
- });
45487
-
45488
- // TODO(alex): Add mobile api functions and pass them down here
45489
- // We're using the index as the key here because we don't have a unique
45490
- // identifier for each section. This should be fine as we never remove
45491
- // or reorder sections.
45492
- const sections = this._sections().map((section, sectionIndex) => {
45493
- return /*#__PURE__*/React__namespace.createElement("div", {
45494
- key: sectionIndex,
45495
- className: "clearfix"
45496
- }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, section, {
45497
- ref: elem => {
45498
- if (elem) {
45499
- this.sectionRenderers[sectionIndex] = elem;
45500
- }
45501
- },
45502
- key: sectionIndex,
45503
- key_: sectionIndex,
45504
- keypadElement: this.props.keypadElement,
45505
- apiOptions: {
45506
- ...apiOptions,
45507
- onFocusChange: (newFocusPath, oldFocusPath) => {
45508
- // Prefix the paths with the relevant section index,
45509
- // so as to allow us to distinguish between
45510
- // equivalently-named inputs across Renderers.
45511
- this._handleFocusChange(newFocusPath && [sectionIndex].concat(newFocusPath), oldFocusPath && [sectionIndex].concat(oldFocusPath));
45512
- }
45513
- },
45514
- linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "article"),
45515
- legacyPerseusLint: this.props.legacyPerseusLint,
45516
- strings: this.context.strings
45517
- })));
45518
- });
45519
- return /*#__PURE__*/React__namespace.createElement("div", {
45520
- className: classes
45521
- }, /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
45522
- value: this.props.dependencies
45523
- }, sections));
44607
+ return sections;
44608
+ };
44609
+ render() {
44610
+ const apiOptions = {
44611
+ ...ApiOptions.defaults,
44612
+ ...this.props.apiOptions,
44613
+ isArticle: true
44614
+ };
44615
+ const classes = classNames__default["default"]({
44616
+ "framework-perseus": true,
44617
+ "perseus-article": true,
44618
+ // NOTE(charlie): For exercises, this is applied outside of Perseus
44619
+ // (in webapp).
44620
+ [ClassNames.MOBILE]: apiOptions.isMobile
44621
+ });
44622
+
44623
+ // TODO(alex): Add mobile api functions and pass them down here
44624
+ // We're using the index as the key here because we don't have a unique
44625
+ // identifier for each section. This should be fine as we never remove
44626
+ // or reorder sections.
44627
+ const sections = this._sections().map((section, sectionIndex) => {
44628
+ return /*#__PURE__*/React__namespace.createElement("div", {
44629
+ key: sectionIndex,
44630
+ className: "clearfix"
44631
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, section, {
44632
+ ref: elem => {
44633
+ if (elem) {
44634
+ this.sectionRenderers[sectionIndex] = elem;
44635
+ }
44636
+ },
44637
+ key: sectionIndex,
44638
+ key_: sectionIndex,
44639
+ keypadElement: this.props.keypadElement,
44640
+ apiOptions: {
44641
+ ...apiOptions,
44642
+ onFocusChange: (newFocusPath, oldFocusPath) => {
44643
+ // Prefix the paths with the relevant section index,
44644
+ // so as to allow us to distinguish between
44645
+ // equivalently-named inputs across Renderers.
44646
+ this._handleFocusChange(newFocusPath && [sectionIndex].concat(newFocusPath), oldFocusPath && [sectionIndex].concat(oldFocusPath));
44647
+ }
44648
+ },
44649
+ linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "article"),
44650
+ legacyPerseusLint: this.props.legacyPerseusLint,
44651
+ strings: this.context.strings
44652
+ })));
44653
+ });
44654
+ return /*#__PURE__*/React__namespace.createElement("div", {
44655
+ className: classes
44656
+ }, /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
44657
+ value: this.props.dependencies
44658
+ }, sections));
45524
44659
  }
45525
44660
  }
45526
44661
 
@@ -46869,31 +46004,248 @@ function inferItemTreeShape(node) {
46869
46004
  if (typeof node === "object" && node.__type === "hint") {
46870
46005
  return shapes.hint;
46871
46006
  }
46872
- if (typeof node === "object") {
46873
- const valueShapes = {};
46874
- Object.keys(node).forEach(key => {
46875
- valueShapes[key] = inferItemTreeShape(node[key]);
46876
- });
46877
- return shapes.shape(valueShapes);
46007
+ if (typeof node === "object") {
46008
+ const valueShapes = {};
46009
+ Object.keys(node).forEach(key => {
46010
+ valueShapes[key] = inferItemTreeShape(node[key]);
46011
+ });
46012
+ return shapes.shape(valueShapes);
46013
+ }
46014
+ throw new perseusCore.PerseusError(`unexpected multi-item node ${JSON.stringify(node)}`, perseusCore.Errors.InvalidInput);
46015
+ }
46016
+
46017
+ /**
46018
+ * Convert the given Item to an ItemTree, by unwrapping the `_multi` key.
46019
+ */
46020
+ function itemToTree(item) {
46021
+ return item._multi;
46022
+ }
46023
+
46024
+ /**
46025
+ * Convert the given ItemTree to an Item, by wrapping it in the `_multi` key.
46026
+ */
46027
+ function treeToItem(node) {
46028
+ return {
46029
+ _multi: node
46030
+ };
46031
+ }
46032
+
46033
+ var isObject = function (obj) {
46034
+ return obj === Object(obj);
46035
+ };
46036
+ var merge = function () {
46037
+ var obj = {};
46038
+ for (var i = 0; i < arguments.length; i++) {
46039
+ var source = arguments[i];
46040
+ if (source) {
46041
+ for (var prop in source) {
46042
+ obj[prop] = source[prop];
46043
+ }
46044
+ }
46045
+ }
46046
+ return obj;
46047
+ };
46048
+ var clone = function (obj) {
46049
+ if (!isObject(obj)) {
46050
+ return obj;
46051
+ }
46052
+ return Array.isArray(obj) ? obj.slice() : merge(obj);
46053
+ };
46054
+
46055
+ /* TODO batch *all* mutations
46056
+ * idea: freeze / thaw implementations for all types
46057
+ * lens constructor thaws, freeze delegates to type's freeze
46058
+ */
46059
+
46060
+ // equivalents, without requiring it
46061
+ // find the implementation to use for a given object
46062
+ var dispatch = function (x) {
46063
+ if (Array.isArray(x)) {
46064
+ return arr;
46065
+ } else if (isObject(x)) {
46066
+ return obj;
46067
+ } else if (typeof x === "string") {
46068
+ return str;
46069
+ }
46070
+ };
46071
+
46072
+ // This is underscore with a different name
46073
+ var lens = function (obj) {
46074
+ if (obj instanceof lens) {
46075
+ return obj;
46076
+ }
46077
+ if (!(this instanceof lens)) {
46078
+ return new lens(obj);
46079
+ }
46080
+ var ops = dispatch(obj);
46081
+ this._wrapped = ops.thaw ? ops.thaw(obj) : obj;
46082
+ };
46083
+ lens.prototype.freeze = function () {
46084
+ var obj = this._wrapped;
46085
+ var ops = dispatch(obj);
46086
+ return ops.freeze ? ops.freeze(obj) : obj;
46087
+ };
46088
+ lens.prototype.zoom = function (lensArr) {
46089
+ if (this._zoomStack === undefined) {
46090
+ this._zoomStack = [];
46091
+ }
46092
+ this._zoomStack.push({
46093
+ zoom: lensArr,
46094
+ wrapped: this._wrapped
46095
+ });
46096
+ this._wrapped = lens(this._wrapped).get(lensArr);
46097
+ return this;
46098
+ };
46099
+ lens.prototype.deZoom = function () {
46100
+ var frame = this._zoomStack.pop();
46101
+ this._wrapped = lens(frame.wrapped).set(frame.zoom, this._wrapped).freeze();
46102
+ return this;
46103
+ };
46104
+ lens.prototype.get = function (lensArr) {
46105
+ var obj = this._wrapped;
46106
+ for (var i = 0; i < lensArr.length; i++) {
46107
+ obj = dispatch(obj).get(obj, lensArr[i]);
46108
+ }
46109
+ return obj;
46110
+ };
46111
+ lens.prototype.mod = function (lensArr, f) {
46112
+ var obj = this._wrapped;
46113
+ var newObj = clone(obj);
46114
+ var ops = dispatch(obj);
46115
+ if (lensArr.length === 0) {
46116
+ this._wrapped = f(this._wrapped);
46117
+ } else if (lensArr.length === 1) {
46118
+ this._wrapped = ops.mod(newObj, lensArr[0], f);
46119
+ } else {
46120
+ var monocle = lensArr[0];
46121
+ var shortLens = lensArr.slice(1);
46122
+
46123
+ // newObj = ops.mod(obj[monocle], shortLens, f);
46124
+
46125
+ newObj[monocle] = lens(obj[monocle]).mod(shortLens, f).freeze();
46126
+ this._wrapped = newObj;
46127
+ }
46128
+ return this;
46129
+ };
46130
+
46131
+ // TODO - move to individual files
46132
+ lens.prototype.merge = function (lensArr, props) {
46133
+ this._wrapped = lens(this._wrapped).mod(lensArr, function (oldProps) {
46134
+ return merge(oldProps, props);
46135
+ }).freeze();
46136
+ return this;
46137
+ };
46138
+
46139
+ // Lens must have length >= 1 or there would be nothing to return
46140
+ lens.prototype.del = function (lensArr) {
46141
+ var obj = this._wrapped;
46142
+ var ops = dispatch(obj);
46143
+ if (lensArr.length === 1) {
46144
+ this._wrapped = ops.del(obj, lensArr[0]);
46145
+ } else {
46146
+ var monocle = lensArr[0];
46147
+ var shortLens = lensArr.slice(1);
46148
+ var newObj = clone(obj);
46149
+ newObj[monocle] = lens(obj[monocle]).del(shortLens).freeze();
46150
+ this._wrapped = newObj;
46151
+ }
46152
+ return this;
46153
+ };
46154
+ lens.prototype.set = function (lensArr, set) {
46155
+ return this.mod(lensArr, function () {
46156
+ return set;
46157
+ });
46158
+ };
46159
+
46160
+ // ARRAY FUNCTIONS
46161
+
46162
+ const arr = {
46163
+ get: function (arr, monocle) {
46164
+ return arr[monocle];
46165
+ },
46166
+ set: function (arr, monocle, val) {
46167
+ var newArr = arr.splice();
46168
+ newArr[monocle] = val;
46169
+ return newArr;
46170
+ },
46171
+ mod: function (arr, monocle, f) {
46172
+ var newArr = arr.slice();
46173
+ newArr[monocle] = f(arr[monocle]);
46174
+ return newArr;
46175
+ },
46176
+ del: function (arr, monocle) {
46177
+ var newArr = arr.slice();
46178
+ newArr.splice(monocle, 1);
46179
+ return newArr;
46180
+ }
46181
+ };
46182
+
46183
+ /*
46184
+ // Lens must point to a member of an array. We'll insert into that array.
46185
+ lens.prototype.insertAt = function(lensArr, toInsert) {
46186
+ var obj = this._wrapped;
46187
+
46188
+ var arrLens = lensArr.slice(0, -1);
46189
+ var arr = lens(obj).get(arrLens).slice(); // slice to copy
46190
+
46191
+ var arrIdx = lensArr[lensArr.length-1];
46192
+ arr.splice(arrIdx, 0, toInsert);
46193
+ return lens(obj).set(arrLens, arr);
46194
+ };
46195
+
46196
+ lens.prototype.insertBefore = lens.prototype.insertAt;
46197
+ lens.prototype.insertAfter = function(lensArr, toInsert) {
46198
+ var newLens = lensArr.slice();
46199
+ newLens[newLens.length-1] += 1;
46200
+ return lens(this._wrapped).insertAt(newLens, toInsert);
46201
+ };
46202
+ */
46203
+
46204
+ // OBJECT FUNCTIONS
46205
+
46206
+ const obj = {
46207
+ get: function (obj, monocle) {
46208
+ return obj[monocle];
46209
+ },
46210
+ set: function (obj, monocle, val) {
46211
+ var newObj = clone(obj);
46212
+ newObj[monocle] = val;
46213
+ return newObj;
46214
+ },
46215
+ mod: function (obj, monocle, f) {
46216
+ var newObj = clone(obj);
46217
+ newObj[monocle] = f(obj[monocle]);
46218
+ return newObj;
46219
+ },
46220
+ del: function (obj, monocle) {
46221
+ var newObj = clone(obj);
46222
+ delete newObj[monocle];
46223
+ return newObj;
46878
46224
  }
46879
- throw new perseusCore.PerseusError(`unexpected multi-item node ${JSON.stringify(node)}`, perseusCore.Errors.InvalidInput);
46880
- }
46225
+ };
46881
46226
 
46882
- /**
46883
- * Convert the given Item to an ItemTree, by unwrapping the `_multi` key.
46884
- */
46885
- function itemToTree(item) {
46886
- return item._multi;
46887
- }
46227
+ // STRING FUNCTIONS
46888
46228
 
46889
- /**
46890
- * Convert the given ItemTree to an Item, by wrapping it in the `_multi` key.
46891
- */
46892
- function treeToItem(node) {
46893
- return {
46894
- _multi: node
46895
- };
46896
- }
46229
+ const str = {
46230
+ get: function (arr, monocle) {
46231
+ return arr[monocle];
46232
+ },
46233
+ set: function (arr, monocle, val) {
46234
+ var newArr = arr.splice();
46235
+ newArr[monocle] = val;
46236
+ return newArr;
46237
+ },
46238
+ mod: function (arr, monocle, f) {
46239
+ var newArr = arr.splice();
46240
+ newArr[monocle] = f(arr[monocle]);
46241
+ return newArr;
46242
+ },
46243
+ del: function (arr, monocle) {
46244
+ var newArr = arr.slice();
46245
+ newArr.splice(monocle);
46246
+ return newArr;
46247
+ }
46248
+ };
46897
46249
 
46898
46250
  // TODO(mdr)
46899
46251
  // TODO(mdr)
@@ -47035,467 +46387,833 @@ class MultiRenderer extends React__namespace.Component {
47035
46387
  }
47036
46388
 
47037
46389
  /**
47038
- * Construct a Renderer for the given HintNode, and keep track of the hint
47039
- * itself for future use, too.
46390
+ * Construct a Renderer for the given HintNode, and keep track of the hint
46391
+ * itself for future use, too.
46392
+ */
46393
+ _makeHintRendererData(hint) {
46394
+ // TODO(mdr): Once HintsRenderer supports inter-widget communication,
46395
+ // give it a ref. Until then, leave the ref null forever, to avoid
46396
+ // confusing the findWidgets functions.
46397
+ //
46398
+ // NOTE(davidflanagan): As a partial step toward inter-widget
46399
+ // communication we're going to pass a findExternalWidgets function
46400
+ // (using a dummy data object). This allows passage-ref widgets in
46401
+ // hints to use findWidget() to find the passage widgets they reference.
46402
+ // Note that this is one-way only, however. It does not allow
46403
+ // widgets in the question to find widgets in the hints, for example.
46404
+ const findExternalWidgets = criterion => this._findWidgets({}, criterion);
46405
+ return {
46406
+ hint,
46407
+ findExternalWidgets,
46408
+ // _annotateRendererArray() needs this
46409
+ ref: null,
46410
+ makeRenderer: () => /*#__PURE__*/React__namespace.createElement(HintsRenderer, _extends({}, this._getRendererProps(), {
46411
+ findExternalWidgets: findExternalWidgets,
46412
+ hints: [hint]
46413
+ }))
46414
+ };
46415
+ }
46416
+
46417
+ /**
46418
+ * Construct a tree of interconnected RendererDatas, corresponding to the
46419
+ * given item. Called in `_tryMakeRendererState`, in order to store this
46420
+ * tree in the component state.
46421
+ */
46422
+ _makeRendererDataTree(item, shape) {
46423
+ const itemTree = itemToTree(item);
46424
+ return this.rendererDataTreeMapper.mapTree(itemTree, shape);
46425
+ }
46426
+
46427
+ /**
46428
+ * Return all widgets that meet the given criterion, from all Renderers
46429
+ * except the Renderer that triggered this call.
46430
+ *
46431
+ * This function is provided to each Renderer's `findExternalWidgets` prop,
46432
+ * which enables widgets in different Renderers to discover each other and
46433
+ * communicate.
46434
+ */
46435
+ _findWidgets(callingData, filterCriterion) {
46436
+ const results = [];
46437
+ this._mapRenderers(data => {
46438
+ if (callingData !== data && data.ref) {
46439
+ results.push(...data.ref.findInternalWidgets(filterCriterion));
46440
+ }
46441
+ });
46442
+ return results;
46443
+ }
46444
+
46445
+ /**
46446
+ * Copy the renderer tree, apply the given transformation to the leaf nodes
46447
+ * and the optional given transformation to the array nodes, and return the
46448
+ * result.
46449
+ *
46450
+ * Used to provide structured data to the call site (the Renderer tree on
46451
+ * `render`, the Score tree on `getScores`, etc.), and to traverse the
46452
+ * renderer tree even when we disregard the output (like in
46453
+ * `_findWidgets`).
46454
+ */
46455
+ _mapRenderers(
46456
+ // eslint-disable-next-line no-restricted-syntax
46457
+ leafMapper
46458
+ // @ts-expect-error - TS2315 - Type 'Tree' is not generic.
46459
+ ) {
46460
+ const {
46461
+ rendererDataTree
46462
+ } = this.state;
46463
+ if (!rendererDataTree) {
46464
+ return null;
46465
+ }
46466
+ const mapper = buildMapper().setContentMapper(leafMapper).setHintMapper(leafMapper);
46467
+ return mapper.mapTree(rendererDataTree, this.props.shape);
46468
+ }
46469
+ _scoreFromRef(ref) {
46470
+ if (!ref) {
46471
+ return null;
46472
+ }
46473
+ const [guess, score] = ref.guessAndScore();
46474
+ let state;
46475
+ if (ref.getSerializedState) {
46476
+ state = ref.getSerializedState();
46477
+ }
46478
+ return Util.keScoreFromPerseusScore(score, guess, state);
46479
+ }
46480
+
46481
+ /**
46482
+ * Return a tree in the shape of the multi-item, with scores at each of
46483
+ * the content nodes and `null` at the other leaf nodes.
46484
+ */
46485
+ getScores() {
46486
+ return this._mapRenderers(data => this._scoreFromRef(data.ref));
46487
+ }
46488
+
46489
+ /**
46490
+ * Return a single composite score for all rendered content nodes.
46491
+ * The `guess` is a tree in the shape of the multi-item, with an individual
46492
+ * guess at each content node and `null` at the other leaf nodes.
46493
+ */
46494
+ score() {
46495
+ const scores = [];
46496
+ const state = [];
46497
+ const guess = this._mapRenderers(data => {
46498
+ if (!data.ref) {
46499
+ return null;
46500
+ }
46501
+ if (data.ref.getSerializedState) {
46502
+ state.push(data.ref.getSerializedState());
46503
+ }
46504
+ scores.push(data.ref.score());
46505
+ return data.ref?.getUserInput();
46506
+ });
46507
+ const combinedScore = scores.reduce(Util.combineScores);
46508
+ return Util.keScoreFromPerseusScore(combinedScore, guess, state);
46509
+ }
46510
+
46511
+ /**
46512
+ * Return a tree in the shape of the multi-item, with serialized state at
46513
+ * each of the content nodes and `null` at the other leaf nodes.
46514
+ *
46515
+ * If the lastSerializedState argument is supplied, this function will fill
46516
+ * in the state of not-currently-rendered content and hint nodes with the
46517
+ * values from the previous serialized state. If no lastSerializedState is
46518
+ * supplied, `null` will be returned for not-currently-rendered content and
46519
+ * hint nodes.
46520
+ */
46521
+ _getSerializedState(lastSerializedState) {
46522
+ return this._mapRenderers((data, _, path) => {
46523
+ if (data.ref) {
46524
+ return data.ref.getSerializedState();
46525
+ }
46526
+ if (lastSerializedState) {
46527
+ return lens(lastSerializedState).get(path);
46528
+ }
46529
+ return null;
46530
+ });
46531
+ }
46532
+
46533
+ /**
46534
+ * Given a tree in the shape of the multi-item, with serialized state at
46535
+ * each of the content nodes, restore each state to the corresponding
46536
+ * renderer if currently mounted.
46537
+ */
46538
+ restoreSerializedState(serializedState, callback) {
46539
+ // We want to call our async callback only once all of the childrens'
46540
+ // callbacks have run. We add one to this counter before we call out to
46541
+ // each renderer and decrement it when it runs our callback.
46542
+ let numCallbacks = 0;
46543
+ const countCallback = () => {
46544
+ numCallbacks--;
46545
+ if (callback && numCallbacks === 0) {
46546
+ callback();
46547
+ }
46548
+ };
46549
+ this._mapRenderers((data, _, path) => {
46550
+ if (!data.ref) {
46551
+ return;
46552
+ }
46553
+ const state = lens(serializedState).get(path);
46554
+ if (!state) {
46555
+ return;
46556
+ }
46557
+ numCallbacks++;
46558
+ data.ref?.restoreSerializedState(state, countCallback);
46559
+ });
46560
+ }
46561
+
46562
+ /**
46563
+ * Given an array of renderers, if it happens to be an array of *hint*
46564
+ * renderers, then attach a `firstN` method to the array, which allows the
46565
+ * layout to render the hints together in one HintsRenderer.
47040
46566
  */
47041
- _makeHintRendererData(hint) {
47042
- // TODO(mdr): Once HintsRenderer supports inter-widget communication,
47043
- // give it a ref. Until then, leave the ref null forever, to avoid
47044
- // confusing the findWidgets functions.
47045
- //
47046
- // NOTE(davidflanagan): As a partial step toward inter-widget
47047
- // communication we're going to pass a findExternalWidgets function
47048
- // (using a dummy data object). This allows passage-ref widgets in
47049
- // hints to use findWidget() to find the passage widgets they reference.
47050
- // Note that this is one-way only, however. It does not allow
47051
- // widgets in the question to find widgets in the hints, for example.
47052
- const findExternalWidgets = criterion => this._findWidgets({}, criterion);
47053
- return {
47054
- hint,
47055
- findExternalWidgets,
47056
- // _annotateRendererArray() needs this
47057
- ref: null,
47058
- makeRenderer: () => /*#__PURE__*/React__namespace.createElement(HintsRenderer, _extends({}, this._getRendererProps(), {
47059
- findExternalWidgets: findExternalWidgets,
47060
- hints: [hint]
47061
- }))
47062
- };
46567
+ _annotateRendererArray(renderers, rendererDatas, shape) {
46568
+ if (shape.elementShape.type === "hint") {
46569
+ // The shape says that these are HintRendererDatas, even though
46570
+ // it's not provable at compile time, so perform a cast.
46571
+ const hintRendererDatas = rendererDatas;
46572
+ renderers = [...renderers];
46573
+ renderers.firstN = n => /*#__PURE__*/React__namespace.createElement(HintsRenderer, _extends({}, this._getRendererProps(), {
46574
+ findExternalWidgets: hintRendererDatas[0] ? hintRendererDatas[0].findExternalWidgets : undefined,
46575
+ hints: hintRendererDatas.map(d => d.hint),
46576
+ hintsVisible: n
46577
+ }));
46578
+ }
46579
+ return renderers;
47063
46580
  }
47064
46581
 
47065
46582
  /**
47066
- * Construct a tree of interconnected RendererDatas, corresponding to the
47067
- * given item. Called in `_tryMakeRendererState`, in order to store this
47068
- * tree in the component state.
46583
+ * Return a tree in the shape of the multi-item, with a Renderer at each
46584
+ * content node and a HintRenderer at each hint node.
46585
+ *
46586
+ * This is generated by running each of the `makeRenderer` functions at the
46587
+ * leaf nodes.
47069
46588
  */
47070
- _makeRendererDataTree(item, shape) {
47071
- const itemTree = itemToTree(item);
47072
- return this.rendererDataTreeMapper.mapTree(itemTree, shape);
46589
+ _getRenderers() {
46590
+ return this.getRenderersMapper.mapTree(this.state.rendererDataTree, this.props.shape);
46591
+ }
46592
+ render() {
46593
+ if (this.state.renderError) {
46594
+ return /*#__PURE__*/React__namespace.createElement("div", {
46595
+ className: aphrodite.css(styles.error)
46596
+ }, this.context.strings.errorRendering({
46597
+ error: String(this.state.renderError)
46598
+ }));
46599
+ }
46600
+
46601
+ // Pass the renderer tree to the `children` function, which will
46602
+ // determine the actual content of this component.
46603
+ return /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
46604
+ value: this.props.dependencies
46605
+ }, this.props.children({
46606
+ renderers: this._getRenderers()
46607
+ }));
46608
+ }
46609
+ }
46610
+ const styles = aphrodite.StyleSheet.create({
46611
+ error: {
46612
+ color: "red"
46613
+ }
46614
+ });
46615
+
46616
+ /**
46617
+ * Utility functions to build React PropTypes for multi-items and shapes.
46618
+ *
46619
+ * If you're writing new components, though, consider using the Item and Shape
46620
+ * types instead.
46621
+ */
46622
+
46623
+ /**
46624
+ * Return a PropType that accepts Items of the given shape, and rejects other
46625
+ * objects.
46626
+ *
46627
+ * Usage: `propTypes: {item: buildPropTypeForShape(myShape)}`
46628
+ */
46629
+ function buildPropTypeForShape(shape) {
46630
+ return PropTypes__default["default"].oneOfType([PropTypes__default["default"].shape({
46631
+ _multi: buildTreePropTypeForShape(shape)
46632
+ }), PropTypes__default["default"].oneOf([null, undefined])]);
46633
+ }
46634
+
46635
+ /**
46636
+ * Return a PropType that accepts ItemTrees of the given shape, and rejects
46637
+ * other objects.
46638
+ */
46639
+ function buildTreePropTypeForShape(shape) {
46640
+ if (shape.type === "content") {
46641
+ return PropTypes__default["default"].shape({
46642
+ // TODO(mdr): Remove #LegacyContentNode support.
46643
+ __type: PropTypes__default["default"].oneOf(["content", "item"]).isRequired,
46644
+ content: PropTypes__default["default"].string,
46645
+ images: PropTypes__default["default"].objectOf(PropTypes__default["default"].any),
46646
+ widgets: PropTypes__default["default"].objectOf(PropTypes__default["default"].any)
46647
+ });
46648
+ }
46649
+ if (shape.type === "hint") {
46650
+ return PropTypes__default["default"].shape({
46651
+ __type: PropTypes__default["default"].oneOf(["hint"]).isRequired,
46652
+ content: PropTypes__default["default"].string,
46653
+ images: PropTypes__default["default"].objectOf(PropTypes__default["default"].any),
46654
+ widgets: PropTypes__default["default"].objectOf(PropTypes__default["default"].any),
46655
+ replace: PropTypes__default["default"].bool
46656
+ });
46657
+ }
46658
+ if (shape.type === "tags") {
46659
+ return PropTypes__default["default"].arrayOf(PropTypes__default["default"].string.isRequired);
46660
+ }
46661
+ if (shape.type === "array") {
46662
+ const elementPropType = buildTreePropTypeForShape(shape.elementShape);
46663
+ return PropTypes__default["default"].arrayOf(elementPropType.isRequired);
46664
+ }
46665
+ if (shape.type === "object") {
46666
+ const valueShapes = shape.shape;
46667
+ const propTypeShape = {};
46668
+ Object.keys(valueShapes).forEach(key => {
46669
+ propTypeShape[key] = buildTreePropTypeForShape(valueShapes[key]).isRequired;
46670
+ });
46671
+ return PropTypes__default["default"].shape(propTypeShape);
46672
+ }
46673
+ throw new perseusCore.PerseusError(
46674
+ // @ts-expect-error - TS2339 - Property 'type' does not exist on type 'never'.
46675
+ `unexpected shape type ${shape.type}`, perseusCore.Errors.InvalidInput);
46676
+ }
46677
+
46678
+ /**
46679
+ * This library provides support for Perseus multi-items: structured Perseus
46680
+ * content that content creators can easily create, and that applications can
46681
+ * easily render into different parts of the layout.
46682
+ *
46683
+ * For more details about application and motivation, see:
46684
+ * https://sites.google.com/a/khanacademy.org/forge/for-developers/perseus-items-and-multi-items
46685
+ *
46686
+ * This file primarily exposes the `MultiRenderer` component, which performs
46687
+ * multi-rendering. To multi-render a question, pass in the content of the item
46688
+ * to the `MultiRenderer` component as a props. Then, pass in a function which
46689
+ * takes an object of renderers (in the same structure as the content), and
46690
+ * return a render tree. The `MultiRenderer` component will allow you to
46691
+ * combine scores, serialized state, etc. without having to manually call on
46692
+ * each of the functions. It also handles inter-widgets requests between the
46693
+ * different renderers.
46694
+ * For more details, see `multi-items/multi-renderer.jsx`.
46695
+ *
46696
+ * Example:
46697
+ *
46698
+ * item = {_multi: {
46699
+ * left: <content data>,
46700
+ * right: [<content data>, <content data>],
46701
+ * }}
46702
+ * shape = shapes.shape({
46703
+ * left: shapes.content,
46704
+ * right: shapes.arrayOf(shapes.content),
46705
+ * })
46706
+ *
46707
+ * <MultiRenderer item={item} shape={shape}>
46708
+ * {({renderers}) =>
46709
+ * <div>
46710
+ * <div id="left">{renderers.left}</div>
46711
+ * <ul id="right">
46712
+ * {renderers.right.map(r => <li>{r}</li>)}
46713
+ * </ul>
46714
+ * </div>
46715
+ * }
46716
+ * </MultiRenderer>
46717
+ *
46718
+ * This file also exposes `shapes`, which helps you construct a runtime type
46719
+ * declaration for your particular class of multi-item. This can then be used
46720
+ * to create a MultirendererEditor for your multi-item shape, and to validate
46721
+ * that a multi-item conforms to the shape via `buildPropTypeForShape`.
46722
+ * For more details, see `multi-items/shapes.js`.
46723
+ *
46724
+ * This file also exposes some utility functions for working with generic
46725
+ * multi-items, like `findContentNodesInItem`, `findHintNodesInItem`,
46726
+ * `inferItemShape`, and `buildEmptyItemForShape`.
46727
+ * For more details, see `multi-items/items.js`.
46728
+ */
46729
+ var MultiItems = {
46730
+ // Tools for rendering your multi-items
46731
+ MultiRenderer,
46732
+ // Tools for declaring your multi-item shapes
46733
+ shapes,
46734
+ buildPropTypeForShape,
46735
+ // Tools for generically manipulating multi-items
46736
+ buildEmptyItemForShape,
46737
+ findContentNodesInItem: findContentNodesInItem$2,
46738
+ findHintNodesInItem,
46739
+ inferItemShape: inferItemShape$2
46740
+ };
46741
+
46742
+ /**
46743
+ * Calculates whether a perseus item is renderable by a specific
46744
+ * perseus-item-data version.
46745
+ *
46746
+ * This is done inside of the perseus repo so that it can traverse
46747
+ * widget-specific data that might need to do a sub-traversal.
46748
+ * This supports widgets that contain renderers, such as the
46749
+ * group or sequence widgets.
46750
+ */
46751
+ const {
46752
+ findContentNodesInItem: findContentNodesInItem$1,
46753
+ inferItemShape: inferItemShape$1
46754
+ } = MultiItems;
46755
+ const isUpgradedWidgetInfoRenderableBy = function (widgetInfo, widgetRendererVersion) {
46756
+ if (widgetRendererVersion == null) {
46757
+ // If the widget does not exist in this version, this will
46758
+ // be null, and that version of perseus cannot render the
46759
+ // widget (it doesn't even know the widget exists!)
46760
+ return false;
46761
+ }
46762
+ const widgetVersion = widgetInfo.version || {
46763
+ major: 0,
46764
+ minor: 0
46765
+ };
46766
+ if (widgetRendererVersion.major > widgetVersion.major) {
46767
+ return true;
46768
+ }
46769
+ if (widgetRendererVersion.major < widgetVersion.major) {
46770
+ return false;
46771
+ }
46772
+ // If the major versions are the same, the minor version acts
46773
+ // like a tie-breaker.
46774
+ // For example, input-number 3.2 can render an input-number
46775
+ // 2.4, 3.0, or 3.2, but not an input number 3.3 or 4.0.
46776
+ return widgetRendererVersion.minor >= widgetVersion.minor;
46777
+ };
46778
+ const isRawWidgetInfoRenderableBy = function (widgetInfo, rendererContentVersion) {
46779
+ // Empty/non-existant widgets are always safe to render
46780
+ if (widgetInfo == null || widgetInfo.type == null) {
46781
+ return true;
47073
46782
  }
47074
46783
 
47075
- /**
47076
- * Return all widgets that meet the given criterion, from all Renderers
47077
- * except the Renderer that triggered this call.
47078
- *
47079
- * This function is provided to each Renderer's `findExternalWidgets` prop,
47080
- * which enables widgets in different Renderers to discover each other and
47081
- * communicate.
47082
- */
47083
- _findWidgets(callingData, filterCriterion) {
47084
- const results = [];
47085
- this._mapRenderers(data => {
47086
- if (callingData !== data && data.ref) {
47087
- results.push(...data.ref.findInternalWidgets(filterCriterion));
46784
+ // NOTE: This doesn't modify the widget info if the widget info
46785
+ // is at a later version than is supported.
46786
+ const upgradedWidgetInfo = upgradeWidgetInfoToLatestVersion(widgetInfo);
46787
+ return isUpgradedWidgetInfoRenderableBy(upgradedWidgetInfo, rendererContentVersion[upgradedWidgetInfo.type]);
46788
+ };
46789
+ const isRendererContentRenderableBy = function (rendererOptions, rendererContentVersion) {
46790
+ let isRenderable = true;
46791
+ traverse(rendererOptions, null, function (widgetInfo) {
46792
+ isRenderable = isRenderable && isRawWidgetInfoRenderableBy(widgetInfo, rendererContentVersion);
46793
+ });
46794
+ return isRenderable;
46795
+ };
46796
+ const isItemRenderableByVersion = function (itemData, rendererContentVersion) {
46797
+ if (itemData == null || rendererContentVersion == null) {
46798
+ throw new perseusCore.PerseusError("missing parameter to Perseus.isRenderable.item", perseusCore.Errors.InvalidInput);
46799
+ }
46800
+ if (itemData._multi) {
46801
+ const shape = inferItemShape$1(itemData);
46802
+ let isRenderable = true;
46803
+ findContentNodesInItem$1(itemData, shape, node => {
46804
+ const nodeIsRenderable = isRendererContentRenderableBy(node, rendererContentVersion);
46805
+ if (!nodeIsRenderable) {
46806
+ isRenderable = false;
47088
46807
  }
47089
46808
  });
47090
- return results;
46809
+ return isRenderable;
47091
46810
  }
46811
+ return isRendererContentRenderableBy(itemData.question, rendererContentVersion);
46812
+ };
47092
46813
 
47093
- /**
47094
- * Copy the renderer tree, apply the given transformation to the leaf nodes
47095
- * and the optional given transformation to the array nodes, and return the
47096
- * result.
47097
- *
47098
- * Used to provide structured data to the call site (the Renderer tree on
47099
- * `render`, the Score tree on `getScores`, etc.), and to traverse the
47100
- * renderer tree even when we disregard the output (like in
47101
- * `_findWidgets`).
47102
- */
47103
- _mapRenderers(
47104
- // eslint-disable-next-line no-restricted-syntax
47105
- leafMapper
47106
- // @ts-expect-error - TS2315 - Type 'Tree' is not generic.
47107
- ) {
47108
- const {
47109
- rendererDataTree
47110
- } = this.state;
47111
- if (!rendererDataTree) {
47112
- return null;
47113
- }
47114
- const mapper = buildMapper().setContentMapper(leafMapper).setHintMapper(leafMapper);
47115
- return mapper.mapTree(rendererDataTree, this.props.shape);
47116
- }
47117
- _scoreFromRef(ref) {
47118
- if (!ref) {
47119
- return null;
47120
- }
47121
- const [guess, score] = ref.guessAndScore();
47122
- let state;
47123
- if (ref.getSerializedState) {
47124
- state = ref.getSerializedState();
46814
+ /**
46815
+ * Identifies whether or not a given perseus item requires the use of a mouse
46816
+ * or screen, based on the widgets it contains.
46817
+ */
46818
+ const {
46819
+ findContentNodesInItem,
46820
+ inferItemShape
46821
+ } = MultiItems;
46822
+
46823
+ // Iterate over a single Perseus renderer, mutating `widgets` by appending
46824
+ // violating widget types discovered in this item.
46825
+ function traverseRenderer(itemData, widgets) {
46826
+ traverse(itemData, null, function (info) {
46827
+ if (info.type && !isAccessible(info)) {
46828
+ widgets.push(info.type);
47125
46829
  }
47126
- return Util.keScoreFromPerseusScore(score, guess, state);
47127
- }
46830
+ });
46831
+ }
47128
46832
 
47129
- /**
47130
- * Return a tree in the shape of the multi-item, with scores at each of
47131
- * the content nodes and `null` at the other leaf nodes.
47132
- */
47133
- getScores() {
47134
- return this._mapRenderers(data => this._scoreFromRef(data.ref));
46833
+ // Returns a list of widgets that cause a given perseus item to require
46834
+ // the use of a screen or mouse.
46835
+ //
46836
+ // For now we'll just check the `accessible` field on each of the widgets
46837
+ // in the item data, but in the future we may specify accessibility on
46838
+ // each widget with higher granularity.
46839
+ function violatingWidgets(itemData) {
46840
+ // TODO(jordan): Hints as well
46841
+ const widgets = [];
46842
+ if (itemData._multi) {
46843
+ const shape = inferItemShape(itemData);
46844
+ findContentNodesInItem(itemData, shape, content => traverseRenderer(content, widgets));
46845
+ } else {
46846
+ traverseRenderer(itemData.question, widgets);
47135
46847
  }
47136
46848
 
47137
- /**
47138
- * Return a single composite score for all rendered content nodes.
47139
- * The `guess` is a tree in the shape of the multi-item, with an individual
47140
- * guess at each content node and `null` at the other leaf nodes.
47141
- */
47142
- score() {
47143
- const scores = [];
47144
- const state = [];
47145
- const guess = this._mapRenderers(data => {
47146
- if (!data.ref) {
47147
- return null;
47148
- }
47149
- if (data.ref.getSerializedState) {
47150
- state.push(data.ref.getSerializedState());
47151
- }
47152
- scores.push(data.ref.score());
47153
- return data.ref?.getUserInput();
47154
- });
47155
- const combinedScore = scores.reduce(Util.combineScores);
47156
- return Util.keScoreFromPerseusScore(combinedScore, guess, state);
47157
- }
46849
+ // Uniquify the list of widgets (by type)
46850
+ return ___default["default"].uniq(widgets);
46851
+ }
47158
46852
 
47159
- /**
47160
- * Return a tree in the shape of the multi-item, with serialized state at
47161
- * each of the content nodes and `null` at the other leaf nodes.
47162
- *
47163
- * If the lastSerializedState argument is supplied, this function will fill
47164
- * in the state of not-currently-rendered content and hint nodes with the
47165
- * values from the previous serialized state. If no lastSerializedState is
47166
- * supplied, `null` will be returned for not-currently-rendered content and
47167
- * hint nodes.
47168
- */
47169
- _getSerializedState(lastSerializedState) {
47170
- return this._mapRenderers((data, _, path) => {
47171
- if (data.ref) {
47172
- return data.ref.getSerializedState();
47173
- }
47174
- if (lastSerializedState) {
47175
- return lens(lastSerializedState).get(path);
47176
- }
47177
- return null;
47178
- });
47179
- }
46853
+ /* eslint-disable eqeqeq, @typescript-eslint/no-unused-vars */
47180
46854
 
47181
- /**
47182
- * Given a tree in the shape of the multi-item, with serialized state at
47183
- * each of the content nodes, restore each state to the corresponding
47184
- * renderer if currently mounted.
47185
- */
47186
- restoreSerializedState(serializedState, callback) {
47187
- // We want to call our async callback only once all of the childrens'
47188
- // callbacks have run. We add one to this counter before we call out to
47189
- // each renderer and decrement it when it runs our callback.
47190
- let numCallbacks = 0;
47191
- const countCallback = () => {
47192
- numCallbacks--;
47193
- if (callback && numCallbacks === 0) {
47194
- callback();
46855
+ /**
46856
+ * Format a floating point for display using the specified
46857
+ * number of significant figures and least significant decimal.
46858
+ * Scientific notation may used by this method if this
46859
+ * object is very small, very large, has many significant
46860
+ * figures, or the number of significant figures would be
46861
+ * ambiguous in the output if scientific notation were not
46862
+ * used.
46863
+ *
46864
+ * @param f A floating point number that should be displayed
46865
+ * @param sigFigs desired number of significant figures (integer).
46866
+ * @param sigDecs the least significant decimal place (integer).
46867
+ * @param scientific true iff scientific notation should always be used.
46868
+ * @return a string of this object formatted correctly.
46869
+ */
46870
+ function displaySigFigs(f, sigFigs, sigDecs, scientific) {
46871
+ const s = "" + f;
46872
+ let order = parseOrder(s);
46873
+ let mantissa = parseMantissa(s);
46874
+ let positive = parseSign(s);
46875
+ let add;
46876
+ let decAdd;
46877
+ let sigAdd;
46878
+ let zeroScientific = false;
46879
+ if (f == 0 || mantissa == "" || mantissa == "0") {
46880
+ mantissa = "";
46881
+ for (let i = 0; i < sigFigs; i++) {
46882
+ mantissa += "0";
46883
+ }
46884
+ order = sigFigs + sigDecs;
46885
+ if (sigDecs < 0 && -sigDecs >= sigFigs) {
46886
+ zeroScientific = true;
46887
+ }
46888
+ } else {
46889
+ decAdd = order - mantissa.length - sigDecs;
46890
+ sigAdd = sigFigs - mantissa.length;
46891
+ add = Math.min(sigAdd, decAdd);
46892
+ if (add < 0) {
46893
+ let rounded = round(mantissa, -add);
46894
+ if (rounded.length > mantissa.length + add) {
46895
+ order++;
46896
+ if (decAdd > sigAdd) {
46897
+ rounded = round(rounded, 1);
46898
+ }
47195
46899
  }
47196
- };
47197
- this._mapRenderers((data, _, path) => {
47198
- if (!data.ref) {
47199
- return;
46900
+ mantissa = rounded;
46901
+ } else if (add > 0) {
46902
+ for (let i = 0; i < add; i++) {
46903
+ mantissa += "0";
47200
46904
  }
47201
- const state = lens(serializedState).get(path);
47202
- if (!state) {
47203
- return;
46905
+ }
46906
+ if (mantissa == "" || mantissa == "0") {
46907
+ mantissa = "0";
46908
+ positive = true;
46909
+ order = 1 + sigDecs;
46910
+ if (order != 0) {
46911
+ zeroScientific = true;
47204
46912
  }
47205
- numCallbacks++;
47206
- data.ref?.restoreSerializedState(state, countCallback);
47207
- });
47208
- }
47209
-
47210
- /**
47211
- * Given an array of renderers, if it happens to be an array of *hint*
47212
- * renderers, then attach a `firstN` method to the array, which allows the
47213
- * layout to render the hints together in one HintsRenderer.
47214
- */
47215
- _annotateRendererArray(renderers, rendererDatas, shape) {
47216
- if (shape.elementShape.type === "hint") {
47217
- // The shape says that these are HintRendererDatas, even though
47218
- // it's not provable at compile time, so perform a cast.
47219
- const hintRendererDatas = rendererDatas;
47220
- renderers = [...renderers];
47221
- renderers.firstN = n => /*#__PURE__*/React__namespace.createElement(HintsRenderer, _extends({}, this._getRendererProps(), {
47222
- findExternalWidgets: hintRendererDatas[0] ? hintRendererDatas[0].findExternalWidgets : undefined,
47223
- hints: hintRendererDatas.map(d => d.hint),
47224
- hintsVisible: n
47225
- }));
47226
46913
  }
47227
- return renderers;
47228
46914
  }
47229
-
47230
- /**
47231
- * Return a tree in the shape of the multi-item, with a Renderer at each
47232
- * content node and a HintRenderer at each hint node.
47233
- *
47234
- * This is generated by running each of the `makeRenderer` functions at the
47235
- * leaf nodes.
47236
- */
47237
- _getRenderers() {
47238
- return this.getRenderersMapper.mapTree(this.state.rendererDataTree, this.props.shape);
46915
+ const useScientific = scientific || mantissa.length > 20 || order > 4 || order < -2 || order - mantissa.length > 0 && trailingZeros(mantissa) > 0 || zeroScientific;
46916
+ let returnVal = "";
46917
+ if (!positive) {
46918
+ returnVal += "-";
47239
46919
  }
47240
- render() {
47241
- if (this.state.renderError) {
47242
- return /*#__PURE__*/React__namespace.createElement("div", {
47243
- className: aphrodite.css(styles.error)
47244
- }, this.context.strings.errorRendering({
47245
- error: String(this.state.renderError)
47246
- }));
46920
+ if (useScientific) {
46921
+ returnVal += mantissa.charAt(0);
46922
+ if (mantissa.length > 1) {
46923
+ returnVal += "." + mantissa.substring(1, mantissa.length);
47247
46924
  }
47248
-
47249
- // Pass the renderer tree to the `children` function, which will
47250
- // determine the actual content of this component.
47251
- return /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
47252
- value: this.props.dependencies
47253
- }, this.props.children({
47254
- renderers: this._getRenderers()
47255
- }));
46925
+ if (order - 1 != 0) {
46926
+ returnVal += " x 10^" + (order - 1);
46927
+ }
46928
+ } else {
46929
+ let wholePart = "";
46930
+ let fractPart = "";
46931
+ let needDot = true;
46932
+ if (order > 0) {
46933
+ if (mantissa.length > order) {
46934
+ wholePart = mantissa.substring(0, order);
46935
+ fractPart = mantissa.substring(order, mantissa.length);
46936
+ } else {
46937
+ wholePart = mantissa;
46938
+ needDot = trailingZeros(mantissa) != 0;
46939
+ for (let i = 0; i < order - mantissa.length; i++) {
46940
+ wholePart += "0";
46941
+ }
46942
+ }
46943
+ } else {
46944
+ for (let i = 0; i < -order; i++) {
46945
+ fractPart += "0";
46946
+ }
46947
+ fractPart += mantissa;
46948
+ }
46949
+ returnVal += (wholePart == "" ? "0" : wholePart) + (needDot ? "." : "") + fractPart;
47256
46950
  }
46951
+ return returnVal;
47257
46952
  }
47258
- const styles = aphrodite.StyleSheet.create({
47259
- error: {
47260
- color: "red"
47261
- }
47262
- });
47263
46953
 
47264
46954
  /**
47265
- * Utility functions to build React PropTypes for multi-items and shapes.
46955
+ * Count the significant trailing zeros on this object.
47266
46956
  *
47267
- * If you're writing new components, though, consider using the Item and Shape
47268
- * types instead.
46957
+ * @return the number of trailing zeros
47269
46958
  */
46959
+ function trailingZeros(mantissa) {
46960
+ let zeros = 0;
46961
+ for (let i = mantissa.length - 1; i >= 0; i--) {
46962
+ const c = mantissa.charAt(i);
46963
+ if (c == "0") {
46964
+ zeros++;
46965
+ } else {
46966
+ return zeros;
46967
+ }
46968
+ }
46969
+ return zeros;
46970
+ }
47270
46971
 
47271
46972
  /**
47272
- * Return a PropType that accepts Items of the given shape, and rejects other
47273
- * objects.
46973
+ * Parse a string representation of a floating point
46974
+ * and pull out the sign.
47274
46975
  *
47275
- * Usage: `propTypes: {item: buildPropTypeForShape(myShape)}`
46976
+ * @param s the string representation of a floating point.
46977
+ * @return true iff this is a positive number
47276
46978
  */
47277
- function buildPropTypeForShape(shape) {
47278
- return PropTypes__default["default"].oneOfType([PropTypes__default["default"].shape({
47279
- _multi: buildTreePropTypeForShape(shape)
47280
- }), PropTypes__default["default"].oneOf([null, undefined])]);
46979
+ function parseSign(s) {
46980
+ let beginning = true;
46981
+ let seenDot = false;
46982
+ let seenSomething = false;
46983
+ let zeros = "";
46984
+ let all = "";
46985
+ let pos = true;
46986
+ for (let i = 0; i < s.length; i++) {
46987
+ const c = s.charAt(i);
46988
+ if (c >= "1" && c <= "9") {
46989
+ all += zeros + c;
46990
+ zeros = "";
46991
+ seenSomething = true;
46992
+ beginning = false;
46993
+ } else if (c == "0") {
46994
+ if (seenDot) {
46995
+ if (seenSomething) {
46996
+ all += zeros + c;
46997
+ zeros = "";
46998
+ }
46999
+ } else {
47000
+ if (seenSomething) {
47001
+ zeros += c;
47002
+ }
47003
+ }
47004
+ beginning = false;
47005
+ } else if (!seenDot && c == ".") {
47006
+ all += zeros;
47007
+ zeros = "";
47008
+ seenDot = true;
47009
+ beginning = false;
47010
+ } else if (c == "e" || c == "E" && i + 1 < s.length) {
47011
+ parseInt(s.substring(i + 1, s.length));
47012
+ i = s.length;
47013
+ } else if (beginning && (c == "+" || c == "-")) {
47014
+ if (c == "-") {
47015
+ pos = !pos;
47016
+ }
47017
+ }
47018
+ }
47019
+ if (all == "") {
47020
+ return true;
47021
+ }
47022
+ return pos;
47281
47023
  }
47282
47024
 
47283
47025
  /**
47284
- * Return a PropType that accepts ItemTrees of the given shape, and rejects
47285
- * other objects.
47026
+ * Parse a string representation of a floating point
47027
+ * and pull out the mantissa.
47028
+ *
47029
+ * @param s the string representation of a floating point.
47030
+ * @return the mantissa of this number.
47286
47031
  */
47287
- function buildTreePropTypeForShape(shape) {
47288
- if (shape.type === "content") {
47289
- return PropTypes__default["default"].shape({
47290
- // TODO(mdr): Remove #LegacyContentNode support.
47291
- __type: PropTypes__default["default"].oneOf(["content", "item"]).isRequired,
47292
- content: PropTypes__default["default"].string,
47293
- images: PropTypes__default["default"].objectOf(PropTypes__default["default"].any),
47294
- widgets: PropTypes__default["default"].objectOf(PropTypes__default["default"].any)
47295
- });
47296
- }
47297
- if (shape.type === "hint") {
47298
- return PropTypes__default["default"].shape({
47299
- __type: PropTypes__default["default"].oneOf(["hint"]).isRequired,
47300
- content: PropTypes__default["default"].string,
47301
- images: PropTypes__default["default"].objectOf(PropTypes__default["default"].any),
47302
- widgets: PropTypes__default["default"].objectOf(PropTypes__default["default"].any),
47303
- replace: PropTypes__default["default"].bool
47304
- });
47032
+ function parseMantissa(s) {
47033
+ let seenDot = false;
47034
+ let seenSomething = false;
47035
+ let zeros = "";
47036
+ let leadZeros = "";
47037
+ let all = "";
47038
+ for (let i = 0; i < s.length; i++) {
47039
+ const c = s.charAt(i);
47040
+ if (c >= "1" && c <= "9") {
47041
+ all += zeros + c;
47042
+ zeros = "";
47043
+ seenSomething = true;
47044
+ } else if (c == "0") {
47045
+ if (seenDot) {
47046
+ if (seenSomething) {
47047
+ all += zeros + c;
47048
+ zeros = "";
47049
+ } else {
47050
+ leadZeros += c;
47051
+ }
47052
+ } else {
47053
+ if (seenSomething) {
47054
+ leadZeros += c;
47055
+ zeros += c;
47056
+ } else {
47057
+ leadZeros += c;
47058
+ }
47059
+ }
47060
+ } else if (!seenDot && c == ".") {
47061
+ all += zeros;
47062
+ zeros = "";
47063
+ seenDot = true;
47064
+ } else if (c == "e" || c == "E" && i + 1 < s.length) {
47065
+ parseInt(s.substring(i + 1, s.length));
47066
+ i = s.length;
47067
+ } else ;
47305
47068
  }
47306
- if (shape.type === "tags") {
47307
- return PropTypes__default["default"].arrayOf(PropTypes__default["default"].string.isRequired);
47069
+ if (all == "") {
47070
+ return leadZeros;
47308
47071
  }
47309
- if (shape.type === "array") {
47310
- const elementPropType = buildTreePropTypeForShape(shape.elementShape);
47311
- return PropTypes__default["default"].arrayOf(elementPropType.isRequired);
47072
+ return all;
47073
+ }
47074
+
47075
+ /**
47076
+ * Parse a string representation of a floating point
47077
+ * and pull out the exponent.
47078
+ *
47079
+ * @param s the string representation of a floating point.
47080
+ * @return (integer) the number after the e.
47081
+ */
47082
+ function parseOrder(s) {
47083
+ let seenDot = false;
47084
+ let seenSomething = false;
47085
+ let zeros = "";
47086
+ let all = "";
47087
+ let decPlaces = 0;
47088
+ let totalDecs = 0;
47089
+ for (let i = 0; i < s.length; i++) {
47090
+ const c = s.charAt(i);
47091
+ if (c >= "1" && c <= "9") {
47092
+ all += zeros + c;
47093
+ zeros = "";
47094
+ seenSomething = true;
47095
+ if (!seenDot) {
47096
+ totalDecs++;
47097
+ decPlaces++;
47098
+ }
47099
+ } else if (c == "0") {
47100
+ if (seenDot) {
47101
+ if (seenSomething) {
47102
+ all += zeros + c;
47103
+ zeros = "";
47104
+ } else {
47105
+ decPlaces--;
47106
+ }
47107
+ } else {
47108
+ totalDecs++;
47109
+ if (seenSomething) {
47110
+ decPlaces++;
47111
+ zeros += c;
47112
+ }
47113
+ }
47114
+ } else if (!seenDot && c == ".") {
47115
+ all += zeros;
47116
+ zeros = "";
47117
+ seenDot = true;
47118
+ } else if (c == "e" || c == "E" && i + 1 < s.length) {
47119
+ const raised = parseInt(s.substring(i + 1, s.length));
47120
+ decPlaces += raised;
47121
+ totalDecs += raised;
47122
+ i = s.length;
47123
+ } else ;
47312
47124
  }
47313
- if (shape.type === "object") {
47314
- const valueShapes = shape.shape;
47315
- const propTypeShape = {};
47316
- Object.keys(valueShapes).forEach(key => {
47317
- propTypeShape[key] = buildTreePropTypeForShape(valueShapes[key]).isRequired;
47318
- });
47319
- return PropTypes__default["default"].shape(propTypeShape);
47125
+ if (all == "") {
47126
+ return totalDecs;
47320
47127
  }
47321
- throw new perseusCore.PerseusError(
47322
- // @ts-expect-error - TS2339 - Property 'type' does not exist on type 'never'.
47323
- `unexpected shape type ${shape.type}`, perseusCore.Errors.InvalidInput);
47128
+ return decPlaces;
47324
47129
  }
47325
47130
 
47326
47131
  /**
47327
- * This library provides support for Perseus multi-items: structured Perseus
47328
- * content that content creators can easily create, and that applications can
47329
- * easily render into different parts of the layout.
47330
- *
47331
- * For more details about application and motivation, see:
47332
- * https://sites.google.com/a/khanacademy.org/forge/for-developers/perseus-items-and-multi-items
47333
- *
47334
- * This file primarily exposes the `MultiRenderer` component, which performs
47335
- * multi-rendering. To multi-render a question, pass in the content of the item
47336
- * to the `MultiRenderer` component as a props. Then, pass in a function which
47337
- * takes an object of renderers (in the same structure as the content), and
47338
- * return a render tree. The `MultiRenderer` component will allow you to
47339
- * combine scores, serialized state, etc. without having to manually call on
47340
- * each of the functions. It also handles inter-widgets requests between the
47341
- * different renderers.
47342
- * For more details, see `multi-items/multi-renderer.jsx`.
47343
- *
47344
- * Example:
47345
- *
47346
- * item = {_multi: {
47347
- * left: <content data>,
47348
- * right: [<content data>, <content data>],
47349
- * }}
47350
- * shape = shapes.shape({
47351
- * left: shapes.content,
47352
- * right: shapes.arrayOf(shapes.content),
47353
- * })
47354
- *
47355
- * <MultiRenderer item={item} shape={shape}>
47356
- * {({renderers}) =>
47357
- * <div>
47358
- * <div id="left">{renderers.left}</div>
47359
- * <ul id="right">
47360
- * {renderers.right.map(r => <li>{r}</li>)}
47361
- * </ul>
47362
- * </div>
47363
- * }
47364
- * </MultiRenderer>
47365
- *
47366
- * This file also exposes `shapes`, which helps you construct a runtime type
47367
- * declaration for your particular class of multi-item. This can then be used
47368
- * to create a MultirendererEditor for your multi-item shape, and to validate
47369
- * that a multi-item conforms to the shape via `buildPropTypeForShape`.
47370
- * For more details, see `multi-items/shapes.js`.
47371
- *
47372
- * This file also exposes some utility functions for working with generic
47373
- * multi-items, like `findContentNodesInItem`, `findHintNodesInItem`,
47374
- * `inferItemShape`, and `buildEmptyItemForShape`.
47375
- * For more details, see `multi-items/items.js`.
47376
- */
47377
- var MultiItems = {
47378
- // Tools for rendering your multi-items
47379
- MultiRenderer,
47380
- // Tools for declaring your multi-item shapes
47381
- shapes,
47382
- buildPropTypeForShape,
47383
- // Tools for generically manipulating multi-items
47384
- buildEmptyItemForShape,
47385
- findContentNodesInItem: findContentNodesInItem$2,
47386
- findHintNodesInItem,
47387
- inferItemShape: inferItemShape$2
47388
- };
47389
-
47390
- /**
47391
- * Calculates whether a perseus item is renderable by a specific
47392
- * perseus-item-data version.
47132
+ * Remove the specified number of digits from string by
47133
+ * rounding. Proper rounding rules for scientific purposes
47134
+ * are followed.
47135
+ * This method may cause an extra significant figure
47136
+ * to be added to the number. For example, if 999999
47137
+ * were rounded, A one would carry over and become
47138
+ * a significant figure. Those who call this method
47139
+ * should check for this and call round again if needed.
47393
47140
  *
47394
- * This is done inside of the perseus repo so that it can traverse
47395
- * widget-specific data that might need to do a sub-traversal.
47396
- * This supports widgets that contain renderers, such as the
47397
- * group or sequence widgets.
47141
+ * @param mantissa A string representing an whole number of arbitrary length.
47142
+ * @param digits A number of digits to remove
47143
+ * @return A string represted the rounded version of mantissa
47398
47144
  */
47399
- const {
47400
- findContentNodesInItem: findContentNodesInItem$1,
47401
- inferItemShape: inferItemShape$1
47402
- } = MultiItems;
47403
- const isUpgradedWidgetInfoRenderableBy = function (widgetInfo, widgetRendererVersion) {
47404
- if (widgetRendererVersion == null) {
47405
- // If the widget does not exist in this version, this will
47406
- // be null, and that version of perseus cannot render the
47407
- // widget (it doesn't even know the widget exists!)
47408
- return false;
47409
- }
47410
- const widgetVersion = widgetInfo.version || {
47411
- major: 0,
47412
- minor: 0
47413
- };
47414
- if (widgetRendererVersion.major > widgetVersion.major) {
47415
- return true;
47416
- }
47417
- if (widgetRendererVersion.major < widgetVersion.major) {
47418
- return false;
47419
- }
47420
- // If the major versions are the same, the minor version acts
47421
- // like a tie-breaker.
47422
- // For example, input-number 3.2 can render an input-number
47423
- // 2.4, 3.0, or 3.2, but not an input number 3.3 or 4.0.
47424
- return widgetRendererVersion.minor >= widgetVersion.minor;
47425
- };
47426
- const isRawWidgetInfoRenderableBy = function (widgetInfo, rendererContentVersion) {
47427
- // Empty/non-existant widgets are always safe to render
47428
- if (widgetInfo == null || widgetInfo.type == null) {
47429
- return true;
47145
+ function round(mantissa, digits) {
47146
+ const last = mantissa.length - digits - 1;
47147
+ if (last < 0) {
47148
+ return "";
47430
47149
  }
47431
-
47432
- // NOTE: This doesn't modify the widget info if the widget info
47433
- // is at a later version than is supported.
47434
- const upgradedWidgetInfo = upgradeWidgetInfoToLatestVersion(widgetInfo);
47435
- return isUpgradedWidgetInfoRenderableBy(upgradedWidgetInfo, rendererContentVersion[upgradedWidgetInfo.type]);
47436
- };
47437
- const isRendererContentRenderableBy = function (rendererOptions, rendererContentVersion) {
47438
- let isRenderable = true;
47439
- traverse(rendererOptions, null, function (widgetInfo) {
47440
- isRenderable = isRenderable && isRawWidgetInfoRenderableBy(widgetInfo, rendererContentVersion);
47441
- });
47442
- return isRenderable;
47443
- };
47444
- const isItemRenderableByVersion = function (itemData, rendererContentVersion) {
47445
- if (itemData == null || rendererContentVersion == null) {
47446
- throw new perseusCore.PerseusError("missing parameter to Perseus.isRenderable.item", perseusCore.Errors.InvalidInput);
47150
+ if (last >= mantissa.length - 1) {
47151
+ return mantissa;
47447
47152
  }
47448
- if (itemData._multi) {
47449
- const shape = inferItemShape$1(itemData);
47450
- let isRenderable = true;
47451
- findContentNodesInItem$1(itemData, shape, node => {
47452
- const nodeIsRenderable = isRendererContentRenderableBy(node, rendererContentVersion);
47453
- if (!nodeIsRenderable) {
47454
- isRenderable = false;
47153
+ const nextToLast = mantissa.charAt(last + 1);
47154
+ const lastChar = mantissa.charAt(last);
47155
+ let roundUp = false;
47156
+ if (nextToLast > "5") {
47157
+ roundUp = true;
47158
+ } else if (nextToLast == "5") {
47159
+ for (let j = last + 2; j < mantissa.length; j++) {
47160
+ if (mantissa.charAt(j) != "0") {
47161
+ roundUp = true;
47455
47162
  }
47456
- });
47457
- return isRenderable;
47163
+ }
47164
+ // @ts-expect-error - TS2362 - The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
47165
+ if (lastChar % 2 == 1) {
47166
+ roundUp = true;
47167
+ }
47458
47168
  }
47459
- return isRendererContentRenderableBy(itemData.question, rendererContentVersion);
47460
- };
47461
-
47462
- /**
47463
- * Identifies whether or not a given perseus item requires the use of a mouse
47464
- * or screen, based on the widgets it contains.
47465
- */
47466
- const {
47467
- findContentNodesInItem,
47468
- inferItemShape
47469
- } = MultiItems;
47470
-
47471
- // Iterate over a single Perseus renderer, mutating `widgets` by appending
47472
- // violating widget types discovered in this item.
47473
- function traverseRenderer(itemData, widgets) {
47474
- traverse(itemData, null, function (info) {
47475
- if (info.type && !isAccessible(info)) {
47476
- widgets.push(info.type);
47169
+ let result = "";
47170
+ for (let i = last; i >= 0; i--) {
47171
+ const c = mantissa.charAt(i);
47172
+ if (roundUp) {
47173
+ let nextChar;
47174
+ if (c == "9") {
47175
+ nextChar = "0";
47176
+ } else {
47177
+ switch (c) {
47178
+ case "0":
47179
+ nextChar = "1";
47180
+ break;
47181
+ case "1":
47182
+ nextChar = "2";
47183
+ break;
47184
+ case "2":
47185
+ nextChar = "3";
47186
+ break;
47187
+ case "3":
47188
+ nextChar = "4";
47189
+ break;
47190
+ case "4":
47191
+ nextChar = "5";
47192
+ break;
47193
+ case "5":
47194
+ nextChar = "6";
47195
+ break;
47196
+ case "6":
47197
+ nextChar = "7";
47198
+ break;
47199
+ case "7":
47200
+ nextChar = "8";
47201
+ break;
47202
+ case "8":
47203
+ nextChar = "9";
47204
+ break;
47205
+ }
47206
+ roundUp = false;
47207
+ }
47208
+ result = nextChar + result;
47209
+ } else {
47210
+ result = c + result;
47477
47211
  }
47478
- });
47479
- }
47480
-
47481
- // Returns a list of widgets that cause a given perseus item to require
47482
- // the use of a screen or mouse.
47483
- //
47484
- // For now we'll just check the `accessible` field on each of the widgets
47485
- // in the item data, but in the future we may specify accessibility on
47486
- // each widget with higher granularity.
47487
- function violatingWidgets(itemData) {
47488
- // TODO(jordan): Hints as well
47489
- const widgets = [];
47490
- if (itemData._multi) {
47491
- const shape = inferItemShape(itemData);
47492
- findContentNodesInItem(itemData, shape, content => traverseRenderer(content, widgets));
47493
- } else {
47494
- traverseRenderer(itemData.question, widgets);
47495
47212
  }
47496
-
47497
- // Uniquify the list of widgets (by type)
47498
- return ___default["default"].uniq(widgets);
47213
+ if (roundUp) {
47214
+ result = "1" + result;
47215
+ }
47216
+ return result;
47499
47217
  }
47500
47218
 
47501
47219
  /**
@@ -48219,9 +47937,6 @@ function generateTestInteractiveGraphWidget() {
48219
47937
  markings: "graph",
48220
47938
  labels: ["x", "y"],
48221
47939
  showProtractor: false,
48222
- showRuler: false,
48223
- rulerLabel: "mm",
48224
- rulerTicks: 2,
48225
47940
  range: [[-10, 10], [-10, 10]],
48226
47941
  graph: {
48227
47942
  type: "angle"
@@ -48333,7 +48048,6 @@ const widgetNameToEnum = {
48333
48048
  simulator: "SIMULATOR",
48334
48049
  sorter: "SORTER",
48335
48050
  table: "TABLE",
48336
- "unit-input": "UNIT",
48337
48051
  video: "VIDEO"
48338
48052
  };
48339
48053
  function convertWidgetNameToEnum(name) {
@@ -48415,6 +48129,8 @@ exports.getAnswerFromUserInput = getAnswerFromUserInput;
48415
48129
  exports.getAnswersFromWidgets = getAnswersFromWidgets;
48416
48130
  exports.getCorrectAnswerForWidgetId = getCorrectAnswerForWidgetId;
48417
48131
  exports.getLineCoords = getLineCoords;
48132
+ exports.getLinearSystemCoords = getLinearSystemCoords;
48133
+ exports.getSegmentCoords = getSegmentCoords;
48418
48134
  exports.getValidWidgetIds = getValidWidgetIds;
48419
48135
  exports.getWidgetFromWidgetMap = getWidgetFromWidgetMap;
48420
48136
  exports.getWidgetIdsFromContent = getWidgetIdsFromContent;