@khanacademy/perseus 49.0.0 → 49.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
@@ -8993,13 +8993,10 @@ const reactRootCache = new WeakMap();
8993
8993
  */
8994
8994
  function render(element, container) {
8995
8995
  /**
8996
- * `RenderStateRoot` is responsible for tracking whether it's
8997
- * the initial render or subsequent renders. It's used by
8998
- * `useUniqueId` and will be used by `UniqueIDProvider` and
8999
- * `WithSSRPlaceholder` in the future. We're placing it as
9000
- * high up in the render tree as possible to ensure that any
9001
- * components using that hook or those components will function
9002
- * correctly.
8996
+ * `RenderStateRoot` is responsible for tracking whether it's the initial
8997
+ * render or subsequent renders. It's used by `WithSSRPlaceholder`. We're
8998
+ * placing it as high up in the render tree as possible to ensure that any
8999
+ * components using that hook or those components will function correctly.
9003
9000
  */
9004
9001
  const children = /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.RenderStateRoot, {
9005
9002
  throwIfNested: false
@@ -27638,41 +27635,33 @@ class Dropdown extends React__namespace.Component {
27638
27635
  value: String(i + 1),
27639
27636
  label: choice
27640
27637
  }))];
27641
- return (
27642
- /*#__PURE__*/
27643
- // TODO(WB-1812, somewhatabstract): Migrate to Id or useId
27644
- // eslint-disable-next-line no-restricted-syntax
27645
- React__namespace.createElement(wonderBlocksCore.UniqueIDProvider, {
27646
- scope: "dropdown-widget",
27647
- mockOnFirstRender: true
27648
- }, ids => /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View
27649
- // NOTE(jared): These are required to prevent weird behavior
27650
- // When there's a dropdown in a zoomable table.
27651
- , {
27652
- onClick: e => {
27653
- e.stopPropagation();
27654
- },
27655
- onTouchStart: e => {
27656
- e.stopPropagation();
27657
- }
27658
- }, this.props.visibleLabel && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, {
27659
- tag: "label",
27660
- htmlFor: ids.get("dropdown")
27661
- }, this.props.visibleLabel), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
27662
- id: ids.get("dropdown"),
27663
- placeholder: "",
27664
- onChange: value => this._handleChange(parseInt(value)),
27665
- selectedValue: String(this.props.selected),
27666
- disabled: this.props.apiOptions.readOnly,
27667
- "aria-label": this.props.ariaLabel || this.props.visibleLabel || this.context.strings.selectAnAnswer
27668
- // This is currently necessary for SRs to read the labels properly.
27669
- // However, WB is working on a change to add the "combobox" role to
27670
- // all dropdowns.
27671
- // See https://khanacademy.atlassian.net/browse/WB-1671
27672
- ,
27673
- role: "combobox"
27674
- }, children)))
27675
- );
27638
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.Id, null, dropdownId => /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View
27639
+ // NOTE(jared): These are required to prevent weird behavior
27640
+ // When there's a dropdown in a zoomable table.
27641
+ , {
27642
+ onClick: e => {
27643
+ e.stopPropagation();
27644
+ },
27645
+ onTouchStart: e => {
27646
+ e.stopPropagation();
27647
+ }
27648
+ }, this.props.visibleLabel && /*#__PURE__*/React__namespace.createElement(wonderBlocksTypography.LabelLarge, {
27649
+ tag: "label",
27650
+ htmlFor: dropdownId
27651
+ }, this.props.visibleLabel), /*#__PURE__*/React__namespace.createElement(wonderBlocksDropdown.SingleSelect, {
27652
+ id: dropdownId,
27653
+ placeholder: "",
27654
+ onChange: value => this._handleChange(parseInt(value)),
27655
+ selectedValue: String(this.props.selected),
27656
+ disabled: this.props.apiOptions.readOnly,
27657
+ "aria-label": this.props.ariaLabel || this.props.visibleLabel || this.context.strings.selectAnAnswer
27658
+ // This is currently necessary for SRs to read the labels properly.
27659
+ // However, WB is working on a change to add the "combobox" role to
27660
+ // all dropdowns.
27661
+ // See https://khanacademy.atlassian.net/browse/WB-1671
27662
+ ,
27663
+ role: "combobox"
27664
+ }, children)));
27676
27665
  }
27677
27666
  }
27678
27667
  const optionsTransform = widgetOptions => {
@@ -27780,37 +27769,29 @@ class Explanation extends React__namespace.Component {
27780
27769
  "white-space": "normal"
27781
27770
  };
27782
27771
  const contentStyling = [styles$k.content, this.state.expanded ? styles$k.contentExpanded : styles$k.contentCollapsed, allowTransition && (this.state.expanded ? styles$k.transitionExpanded : styles$k.transitionCollapsed)];
27783
- return (
27784
- /*#__PURE__*/
27785
- // TODO(WB-1812, somewhatabstract): Migrate to Id or useId
27786
- // eslint-disable-next-line no-restricted-syntax
27787
- React__namespace.createElement(wonderBlocksCore.UniqueIDProvider, {
27788
- mockOnFirstRender: true,
27789
- scope: "explanation-widget"
27790
- }, ids => /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
27791
- "aria-expanded": this.state.expanded,
27792
- "aria-controls": ids.get("content"),
27793
- endIcon: caretIcon,
27794
- kind: "tertiary",
27795
- labelStyle: labelStyle,
27796
- onClick: this._onClick,
27797
- size: "small",
27798
- style: buttonStyleOverrides
27799
- }, promptText), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
27800
- id: ids.get("content"),
27801
- style: contentStyling,
27802
- "aria-hidden": !this.state.expanded,
27803
- testId: "content-container"
27804
- }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
27805
- style: styles$k.contentWrapper
27806
- }, /*#__PURE__*/React__namespace.createElement(Renderer, {
27807
- apiOptions: this.props.apiOptions,
27808
- content: this.props.explanation,
27809
- widgets: this.props.widgets,
27810
- linterContext: this.props.linterContext,
27811
- strings: this.context.strings
27812
- })))))
27813
- );
27772
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.Id, null, contentId => /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
27773
+ "aria-expanded": this.state.expanded,
27774
+ "aria-controls": contentId,
27775
+ endIcon: caretIcon,
27776
+ kind: "tertiary",
27777
+ labelStyle: labelStyle,
27778
+ onClick: this._onClick,
27779
+ size: "small",
27780
+ style: buttonStyleOverrides
27781
+ }, promptText), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
27782
+ id: contentId,
27783
+ style: contentStyling,
27784
+ "aria-hidden": !this.state.expanded,
27785
+ testId: "content-container"
27786
+ }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
27787
+ style: styles$k.contentWrapper
27788
+ }, /*#__PURE__*/React__namespace.createElement(Renderer, {
27789
+ apiOptions: this.props.apiOptions,
27790
+ content: this.props.explanation,
27791
+ widgets: this.props.widgets,
27792
+ linterContext: this.props.linterContext,
27793
+ strings: this.context.strings
27794
+ })))));
27814
27795
  }
27815
27796
  }
27816
27797
  const leftBorderSpacing = 23;
@@ -32998,6 +32979,20 @@ const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
32998
32979
  function isBetween(x, low, high) {
32999
32980
  return x >= low && x <= high;
33000
32981
  }
32982
+ function getArrayWithoutDuplicates(array) {
32983
+ const returnArray = [];
32984
+ for (const coordPair of array) {
32985
+ if (
32986
+ // Check if the coordPair is not already in the returnArray
32987
+ !returnArray.some(_ref => {
32988
+ let [x, y] = _ref;
32989
+ return x === coordPair[0] && y === coordPair[1];
32990
+ })) {
32991
+ returnArray.push(coordPair);
32992
+ }
32993
+ }
32994
+ return returnArray;
32995
+ }
33001
32996
 
33002
32997
  const LockedLine = props => {
33003
32998
  const {
@@ -34424,6 +34419,13 @@ function getGradableGraph(state, initialGraph) {
34424
34419
  };
34425
34420
  }
34426
34421
  if (state.type === "polygon" && initialGraph.type === "polygon") {
34422
+ // Unless the polygon is closed it is not considered score-able.
34423
+ if (state.numSides === "unlimited" && !state.closedPolygon) {
34424
+ return {
34425
+ ...initialGraph,
34426
+ coords: null
34427
+ };
34428
+ }
34427
34429
  return {
34428
34430
  ...initialGraph,
34429
34431
  coords: state.coords
@@ -34944,6 +34946,15 @@ const PolygonGraph = props => {
34944
34946
  pointsRef.current[focusedIndex]?.focus();
34945
34947
  }
34946
34948
  }, [props.graphState.focusedPointIndex, pointsRef]);
34949
+
34950
+ // If the unlimited polygon is rendered with 3 or more coordinates
34951
+ // Close the polygon, but only on first render.
34952
+ React__namespace.useEffect(() => {
34953
+ if (numSides === "unlimited" && props.graphState.coords.length > 2) {
34954
+ dispatch(actions.polygon.closePolygon());
34955
+ }
34956
+ // eslint-disable-next-line react-hooks/exhaustive-deps
34957
+ }, []);
34947
34958
  const statefulProps = {
34948
34959
  ...props,
34949
34960
  graphConfig,
@@ -35087,7 +35098,16 @@ const UnlimitedPolygonGraph = statefulProps => {
35087
35098
  } = graphConfig;
35088
35099
  const widthPx = graphDimensionsInPixels[0];
35089
35100
  const heightPx = graphDimensionsInPixels[1];
35090
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
35101
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Polyline, {
35102
+ points: [...points],
35103
+ color: "var(--movable-line-stroke-color)",
35104
+ svgPolylineProps: {
35105
+ strokeWidth: "var(--movable-line-stroke-weight)",
35106
+ style: {
35107
+ fill: "transparent"
35108
+ }
35109
+ }
35110
+ }), /*#__PURE__*/React__namespace.createElement("rect", {
35091
35111
  style: {
35092
35112
  fill: "rgba(0,0,0,0)",
35093
35113
  cursor: "crosshair"
@@ -35103,15 +35123,6 @@ const UnlimitedPolygonGraph = statefulProps => {
35103
35123
  const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
35104
35124
  dispatch(actions.polygon.addPoint(graphCoordinates[0]));
35105
35125
  }
35106
- }), /*#__PURE__*/React__namespace.createElement(mafs.Polyline, {
35107
- points: [...points],
35108
- color: "var(--movable-line-stroke-color)",
35109
- svgPolylineProps: {
35110
- strokeWidth: "var(--movable-line-stroke-weight)",
35111
- style: {
35112
- fill: "transparent"
35113
- }
35114
- }
35115
35126
  }), coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
35116
35127
  key: i,
35117
35128
  point: point,
@@ -35125,9 +35136,10 @@ const UnlimitedPolygonGraph = statefulProps => {
35125
35136
  },
35126
35137
  onClick: () => {
35127
35138
  // If the point being clicked is the first point and
35128
- // there's enough points to form a polygon (3 or more)
35129
- // Close the shape before setting focus.
35130
- if (i === 0 && coords.length >= 3) {
35139
+ // there's enough non-duplicated points to form
35140
+ // a polygon (3 or more), close the shape before
35141
+ // setting focus.
35142
+ if (i === 0 && getArrayWithoutDuplicates(coords).length >= 3) {
35131
35143
  dispatch(actions.polygon.closePolygon());
35132
35144
  }
35133
35145
  dispatch(actions.polygon.clickPoint(i));
@@ -35746,7 +35758,7 @@ const renderPolygonGraphControls = props => {
35746
35758
 
35747
35759
  // We want to disable the closePolygon button when
35748
35760
  // there are not enough coords to make a polygon
35749
- const disableCloseButton = coords.length < 3;
35761
+ const disableCloseButton = getArrayWithoutDuplicates(coords).length < 3;
35750
35762
 
35751
35763
  // If polygon is closed, show open button.
35752
35764
  // If polygon is open, show close button.
@@ -36471,8 +36483,14 @@ function doClickPoint(state, action) {
36471
36483
  }
36472
36484
  function doClosePolygon(state) {
36473
36485
  if (isUnlimitedGraphState(state) && state.type === "polygon") {
36486
+ // We want to remove any duplicate points when closing the polygon to
36487
+ // (1) prevent the polygon from sides with length zero, and
36488
+ // (2) make sure the question is can be marked correct if the polygon
36489
+ // LOOKS correct, even if two of the points are at the same coords.
36490
+ const noDupedPoints = getArrayWithoutDuplicates(state.coords);
36474
36491
  return {
36475
36492
  ...state,
36493
+ coords: noDupedPoints,
36476
36494
  closedPolygon: true
36477
36495
  };
36478
36496
  }
@@ -39261,11 +39279,8 @@ const AnswerChoices = props => {
39261
39279
  };
39262
39280
 
39263
39281
  const HideAnswersToggle = props => {
39264
- // TODO(WB-1812, somewhatabstract): Migrate to Id or useId
39265
- // eslint-disable-next-line no-restricted-syntax
39266
- const idFactory = wonderBlocksCore.useUniqueIdWithMock();
39267
- const switchId = idFactory.get("hide-answers-toggle");
39268
- const labelId = idFactory.get("hide-answers-label");
39282
+ const switchId = React.useId();
39283
+ const labelId = React.useId();
39269
39284
  const {
39270
39285
  strings
39271
39286
  } = usePerseusI18n();
@@ -39308,10 +39323,7 @@ const AnswerPill = props => {
39308
39323
  focused,
39309
39324
  hovered
39310
39325
  } = props;
39311
-
39312
- // TODO(WB-1812, somewhatabstract): Migrate to Id or useId
39313
- // eslint-disable-next-line no-restricted-syntax
39314
- const idFactory = wonderBlocksCore.useUniqueIdWithMock();
39326
+ const pillId = React.useId();
39315
39327
  const {
39316
39328
  strings
39317
39329
  } = usePerseusI18n();
@@ -39340,7 +39352,7 @@ const AnswerPill = props => {
39340
39352
  return /*#__PURE__*/React__namespace.createElement(Pill__default["default"], {
39341
39353
  size: "large",
39342
39354
  kind: "accent",
39343
- id: idFactory.get("perseus-label-image-widget-answer-pill"),
39355
+ id: pillId,
39344
39356
  onClick: correct ? undefined : onClick,
39345
39357
  ref: ref,
39346
39358
  style: [style, popperStyle, styles$b.pill, correct && styles$b.correct, incorrect && styles$b.incorrect, (focused || hovered) && BringToFront]
@@ -45658,7 +45670,7 @@ const init = function () {
45658
45670
 
45659
45671
  // This file is processed by a Rollup plugin (replace) to inject the production
45660
45672
  const libName = "@khanacademy/perseus";
45661
- const libVersion = "49.0.0";
45673
+ const libVersion = "49.0.1";
45662
45674
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45663
45675
 
45664
45676
  const apiVersion = {