@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/es/index.js CHANGED
@@ -2,13 +2,13 @@ import * as KAS from '@khanacademy/kas';
2
2
  import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
3
3
  import * as PerseusLinter from '@khanacademy/perseus-linter';
4
4
  import { linterContextDefault, Rule } from '@khanacademy/perseus-linter';
5
- import { View, RenderStateRoot, UniqueIDProvider, useOnMountEffect, useLatestRef, useUniqueIdWithMock, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
5
+ import { View, RenderStateRoot, Id, useOnMountEffect, useLatestRef, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
6
6
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
7
7
  import { HeadingMedium, LabelSmall, LabelLarge, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
8
8
  import { StyleSheet, css } from 'aphrodite';
9
9
  import classNames$1 from 'classnames';
10
10
  import * as React from 'react';
11
- import React__default, { useContext, useState, useEffect, useRef, createContext, forwardRef, useLayoutEffect, useImperativeHandle } from 'react';
11
+ import React__default, { useContext, useState, useEffect, useRef, createContext, forwardRef, useLayoutEffect, useImperativeHandle, useId } from 'react';
12
12
  import * as ReactDOM from 'react-dom';
13
13
  import ReactDOM__default from 'react-dom';
14
14
  import _, { debounce as debounce$1 } from 'underscore';
@@ -8870,13 +8870,10 @@ const reactRootCache = new WeakMap();
8870
8870
  */
8871
8871
  function render(element, container) {
8872
8872
  /**
8873
- * `RenderStateRoot` is responsible for tracking whether it's
8874
- * the initial render or subsequent renders. It's used by
8875
- * `useUniqueId` and will be used by `UniqueIDProvider` and
8876
- * `WithSSRPlaceholder` in the future. We're placing it as
8877
- * high up in the render tree as possible to ensure that any
8878
- * components using that hook or those components will function
8879
- * correctly.
8873
+ * `RenderStateRoot` is responsible for tracking whether it's the initial
8874
+ * render or subsequent renders. It's used by `WithSSRPlaceholder`. We're
8875
+ * placing it as high up in the render tree as possible to ensure that any
8876
+ * components using that hook or those components will function correctly.
8880
8877
  */
8881
8878
  const children = /*#__PURE__*/React.createElement(RenderStateRoot, {
8882
8879
  throwIfNested: false
@@ -27385,41 +27382,33 @@ class Dropdown extends React.Component {
27385
27382
  value: String(i + 1),
27386
27383
  label: choice
27387
27384
  }))];
27388
- return (
27389
- /*#__PURE__*/
27390
- // TODO(WB-1812, somewhatabstract): Migrate to Id or useId
27391
- // eslint-disable-next-line no-restricted-syntax
27392
- React.createElement(UniqueIDProvider, {
27393
- scope: "dropdown-widget",
27394
- mockOnFirstRender: true
27395
- }, ids => /*#__PURE__*/React.createElement(View
27396
- // NOTE(jared): These are required to prevent weird behavior
27397
- // When there's a dropdown in a zoomable table.
27398
- , {
27399
- onClick: e => {
27400
- e.stopPropagation();
27401
- },
27402
- onTouchStart: e => {
27403
- e.stopPropagation();
27404
- }
27405
- }, this.props.visibleLabel && /*#__PURE__*/React.createElement(LabelLarge, {
27406
- tag: "label",
27407
- htmlFor: ids.get("dropdown")
27408
- }, this.props.visibleLabel), /*#__PURE__*/React.createElement(SingleSelect, {
27409
- id: ids.get("dropdown"),
27410
- placeholder: "",
27411
- onChange: value => this._handleChange(parseInt(value)),
27412
- selectedValue: String(this.props.selected),
27413
- disabled: this.props.apiOptions.readOnly,
27414
- "aria-label": this.props.ariaLabel || this.props.visibleLabel || this.context.strings.selectAnAnswer
27415
- // This is currently necessary for SRs to read the labels properly.
27416
- // However, WB is working on a change to add the "combobox" role to
27417
- // all dropdowns.
27418
- // See https://khanacademy.atlassian.net/browse/WB-1671
27419
- ,
27420
- role: "combobox"
27421
- }, children)))
27422
- );
27385
+ return /*#__PURE__*/React.createElement(Id, null, dropdownId => /*#__PURE__*/React.createElement(View
27386
+ // NOTE(jared): These are required to prevent weird behavior
27387
+ // When there's a dropdown in a zoomable table.
27388
+ , {
27389
+ onClick: e => {
27390
+ e.stopPropagation();
27391
+ },
27392
+ onTouchStart: e => {
27393
+ e.stopPropagation();
27394
+ }
27395
+ }, this.props.visibleLabel && /*#__PURE__*/React.createElement(LabelLarge, {
27396
+ tag: "label",
27397
+ htmlFor: dropdownId
27398
+ }, this.props.visibleLabel), /*#__PURE__*/React.createElement(SingleSelect, {
27399
+ id: dropdownId,
27400
+ placeholder: "",
27401
+ onChange: value => this._handleChange(parseInt(value)),
27402
+ selectedValue: String(this.props.selected),
27403
+ disabled: this.props.apiOptions.readOnly,
27404
+ "aria-label": this.props.ariaLabel || this.props.visibleLabel || this.context.strings.selectAnAnswer
27405
+ // This is currently necessary for SRs to read the labels properly.
27406
+ // However, WB is working on a change to add the "combobox" role to
27407
+ // all dropdowns.
27408
+ // See https://khanacademy.atlassian.net/browse/WB-1671
27409
+ ,
27410
+ role: "combobox"
27411
+ }, children)));
27423
27412
  }
27424
27413
  }
27425
27414
  Dropdown.contextType = PerseusI18nContext;
@@ -27522,37 +27511,29 @@ class Explanation extends React.Component {
27522
27511
  "white-space": "normal"
27523
27512
  };
27524
27513
  const contentStyling = [styles$k.content, this.state.expanded ? styles$k.contentExpanded : styles$k.contentCollapsed, allowTransition && (this.state.expanded ? styles$k.transitionExpanded : styles$k.transitionCollapsed)];
27525
- return (
27526
- /*#__PURE__*/
27527
- // TODO(WB-1812, somewhatabstract): Migrate to Id or useId
27528
- // eslint-disable-next-line no-restricted-syntax
27529
- React.createElement(UniqueIDProvider, {
27530
- mockOnFirstRender: true,
27531
- scope: "explanation-widget"
27532
- }, ids => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
27533
- "aria-expanded": this.state.expanded,
27534
- "aria-controls": ids.get("content"),
27535
- endIcon: caretIcon,
27536
- kind: "tertiary",
27537
- labelStyle: labelStyle,
27538
- onClick: this._onClick,
27539
- size: "small",
27540
- style: buttonStyleOverrides
27541
- }, promptText), /*#__PURE__*/React.createElement(View, {
27542
- id: ids.get("content"),
27543
- style: contentStyling,
27544
- "aria-hidden": !this.state.expanded,
27545
- testId: "content-container"
27546
- }, /*#__PURE__*/React.createElement(View, {
27547
- style: styles$k.contentWrapper
27548
- }, /*#__PURE__*/React.createElement(Renderer, {
27549
- apiOptions: this.props.apiOptions,
27550
- content: this.props.explanation,
27551
- widgets: this.props.widgets,
27552
- linterContext: this.props.linterContext,
27553
- strings: this.context.strings
27554
- })))))
27555
- );
27514
+ return /*#__PURE__*/React.createElement(Id, null, contentId => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
27515
+ "aria-expanded": this.state.expanded,
27516
+ "aria-controls": contentId,
27517
+ endIcon: caretIcon,
27518
+ kind: "tertiary",
27519
+ labelStyle: labelStyle,
27520
+ onClick: this._onClick,
27521
+ size: "small",
27522
+ style: buttonStyleOverrides
27523
+ }, promptText), /*#__PURE__*/React.createElement(View, {
27524
+ id: contentId,
27525
+ style: contentStyling,
27526
+ "aria-hidden": !this.state.expanded,
27527
+ testId: "content-container"
27528
+ }, /*#__PURE__*/React.createElement(View, {
27529
+ style: styles$k.contentWrapper
27530
+ }, /*#__PURE__*/React.createElement(Renderer, {
27531
+ apiOptions: this.props.apiOptions,
27532
+ content: this.props.explanation,
27533
+ widgets: this.props.widgets,
27534
+ linterContext: this.props.linterContext,
27535
+ strings: this.context.strings
27536
+ })))));
27556
27537
  }
27557
27538
  }
27558
27539
  Explanation.contextType = PerseusI18nContext;
@@ -32708,6 +32689,17 @@ const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
32708
32689
  function isBetween(x, low, high) {
32709
32690
  return x >= low && x <= high;
32710
32691
  }
32692
+ function getArrayWithoutDuplicates(array) {
32693
+ const returnArray = [];
32694
+ for (const coordPair of array) {
32695
+ if (
32696
+ // Check if the coordPair is not already in the returnArray
32697
+ !returnArray.some(([x, y]) => x === coordPair[0] && y === coordPair[1])) {
32698
+ returnArray.push(coordPair);
32699
+ }
32700
+ }
32701
+ return returnArray;
32702
+ }
32711
32703
 
32712
32704
  const LockedLine = props => {
32713
32705
  var _flags$mafs;
@@ -34118,6 +34110,12 @@ function getGradableGraph(state, initialGraph) {
34118
34110
  });
34119
34111
  }
34120
34112
  if (state.type === "polygon" && initialGraph.type === "polygon") {
34113
+ // Unless the polygon is closed it is not considered score-able.
34114
+ if (state.numSides === "unlimited" && !state.closedPolygon) {
34115
+ return _extends({}, initialGraph, {
34116
+ coords: null
34117
+ });
34118
+ }
34121
34119
  return _extends({}, initialGraph, {
34122
34120
  coords: state.coords
34123
34121
  });
@@ -34629,6 +34627,15 @@ const PolygonGraph = props => {
34629
34627
  (_pointsRef$current$fo = pointsRef.current[focusedIndex]) == null || _pointsRef$current$fo.focus();
34630
34628
  }
34631
34629
  }, [props.graphState.focusedPointIndex, pointsRef]);
34630
+
34631
+ // If the unlimited polygon is rendered with 3 or more coordinates
34632
+ // Close the polygon, but only on first render.
34633
+ React.useEffect(() => {
34634
+ if (numSides === "unlimited" && props.graphState.coords.length > 2) {
34635
+ dispatch(actions.polygon.closePolygon());
34636
+ }
34637
+ // eslint-disable-next-line react-hooks/exhaustive-deps
34638
+ }, []);
34632
34639
  const statefulProps = _extends({}, props, {
34633
34640
  graphConfig,
34634
34641
  polygonRef,
@@ -34769,7 +34776,16 @@ const UnlimitedPolygonGraph = statefulProps => {
34769
34776
  } = graphConfig;
34770
34777
  const widthPx = graphDimensionsInPixels[0];
34771
34778
  const heightPx = graphDimensionsInPixels[1];
34772
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("rect", {
34779
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Polyline, {
34780
+ points: [...points],
34781
+ color: "var(--movable-line-stroke-color)",
34782
+ svgPolylineProps: {
34783
+ strokeWidth: "var(--movable-line-stroke-weight)",
34784
+ style: {
34785
+ fill: "transparent"
34786
+ }
34787
+ }
34788
+ }), /*#__PURE__*/React.createElement("rect", {
34773
34789
  style: {
34774
34790
  fill: "rgba(0,0,0,0)",
34775
34791
  cursor: "crosshair"
@@ -34785,15 +34801,6 @@ const UnlimitedPolygonGraph = statefulProps => {
34785
34801
  const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
34786
34802
  dispatch(actions.polygon.addPoint(graphCoordinates[0]));
34787
34803
  }
34788
- }), /*#__PURE__*/React.createElement(Polyline, {
34789
- points: [...points],
34790
- color: "var(--movable-line-stroke-color)",
34791
- svgPolylineProps: {
34792
- strokeWidth: "var(--movable-line-stroke-weight)",
34793
- style: {
34794
- fill: "transparent"
34795
- }
34796
- }
34797
34804
  }), coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
34798
34805
  key: i,
34799
34806
  point: point,
@@ -34807,9 +34814,10 @@ const UnlimitedPolygonGraph = statefulProps => {
34807
34814
  },
34808
34815
  onClick: () => {
34809
34816
  // If the point being clicked is the first point and
34810
- // there's enough points to form a polygon (3 or more)
34811
- // Close the shape before setting focus.
34812
- if (i === 0 && coords.length >= 3) {
34817
+ // there's enough non-duplicated points to form
34818
+ // a polygon (3 or more), close the shape before
34819
+ // setting focus.
34820
+ if (i === 0 && getArrayWithoutDuplicates(coords).length >= 3) {
34813
34821
  dispatch(actions.polygon.closePolygon());
34814
34822
  }
34815
34823
  dispatch(actions.polygon.clickPoint(i));
@@ -35432,7 +35440,7 @@ const renderPolygonGraphControls = props => {
35432
35440
 
35433
35441
  // We want to disable the closePolygon button when
35434
35442
  // there are not enough coords to make a polygon
35435
- const disableCloseButton = coords.length < 3;
35443
+ const disableCloseButton = getArrayWithoutDuplicates(coords).length < 3;
35436
35444
 
35437
35445
  // If polygon is closed, show open button.
35438
35446
  // If polygon is open, show close button.
@@ -36132,7 +36140,13 @@ function doClickPoint(state, action) {
36132
36140
  }
36133
36141
  function doClosePolygon(state) {
36134
36142
  if (isUnlimitedGraphState(state) && state.type === "polygon") {
36143
+ // We want to remove any duplicate points when closing the polygon to
36144
+ // (1) prevent the polygon from sides with length zero, and
36145
+ // (2) make sure the question is can be marked correct if the polygon
36146
+ // LOOKS correct, even if two of the points are at the same coords.
36147
+ const noDupedPoints = getArrayWithoutDuplicates(state.coords);
36135
36148
  return _extends({}, state, {
36149
+ coords: noDupedPoints,
36136
36150
  closedPolygon: true
36137
36151
  });
36138
36152
  }
@@ -38914,11 +38928,8 @@ const AnswerChoices = props => {
38914
38928
  };
38915
38929
 
38916
38930
  const HideAnswersToggle = props => {
38917
- // TODO(WB-1812, somewhatabstract): Migrate to Id or useId
38918
- // eslint-disable-next-line no-restricted-syntax
38919
- const idFactory = useUniqueIdWithMock();
38920
- const switchId = idFactory.get("hide-answers-toggle");
38921
- const labelId = idFactory.get("hide-answers-label");
38931
+ const switchId = useId();
38932
+ const labelId = useId();
38922
38933
  const {
38923
38934
  strings
38924
38935
  } = usePerseusI18n();
@@ -38961,10 +38972,7 @@ const AnswerPill = props => {
38961
38972
  focused,
38962
38973
  hovered
38963
38974
  } = props;
38964
-
38965
- // TODO(WB-1812, somewhatabstract): Migrate to Id or useId
38966
- // eslint-disable-next-line no-restricted-syntax
38967
- const idFactory = useUniqueIdWithMock();
38975
+ const pillId = useId();
38968
38976
  const {
38969
38977
  strings
38970
38978
  } = usePerseusI18n();
@@ -38991,7 +38999,7 @@ const AnswerPill = props => {
38991
38999
  }) => /*#__PURE__*/React.createElement(Pill, {
38992
39000
  size: "large",
38993
39001
  kind: "accent",
38994
- id: idFactory.get("perseus-label-image-widget-answer-pill"),
39002
+ id: pillId,
38995
39003
  onClick: correct ? undefined : onClick,
38996
39004
  ref: ref,
38997
39005
  style: [style, popperStyle, styles$b.pill, correct && styles$b.correct, incorrect && styles$b.incorrect, (focused || hovered) && BringToFront]
@@ -45307,7 +45315,7 @@ const init = function init() {
45307
45315
 
45308
45316
  // This file is processed by a Rollup plugin (replace) to inject the production
45309
45317
  const libName = "@khanacademy/perseus";
45310
- const libVersion = "49.0.0";
45318
+ const libVersion = "49.0.1";
45311
45319
  addLibraryVersionToPerseusDebug(libName, libVersion);
45312
45320
 
45313
45321
  const apiVersion = {