@khanacademy/perseus 7.1.1 → 7.2.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/es/index.css +61 -61
  3. package/dist/es/index.js +100 -168
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.css +61 -61
  6. package/dist/index.js +89 -126
  7. package/dist/index.js.map +1 -1
  8. package/dist/util.d.ts +7 -12
  9. package/dist/util.graphie.d.ts +6 -0
  10. package/dist/util.graphie.js.flow +14 -0
  11. package/dist/util.js.flow +7 -12
  12. package/dist/widgets/plotter.d.ts +47 -13
  13. package/dist/widgets/plotter.js.flow +47 -13
  14. package/package.json +2 -2
  15. package/src/__tests__/item-renderer.test.tsx +1 -1
  16. package/src/components/__stories__/image-loader.stories.tsx +1 -0
  17. package/src/components/graph.tsx +1 -0
  18. package/src/components/icon.tsx +1 -0
  19. package/src/components/image-loader.tsx +1 -0
  20. package/src/components/number-input.tsx +1 -0
  21. package/src/components/sortable.tsx +1 -0
  22. package/src/components/svg-image.tsx +1 -0
  23. package/src/components/tex-buttons.tsx +1 -0
  24. package/src/components/text-input.tsx +1 -0
  25. package/src/components/text-list-editor.tsx +1 -0
  26. package/src/components/zoomable.tsx +1 -0
  27. package/src/hints-renderer.tsx +1 -0
  28. package/src/index.ts +1 -0
  29. package/src/item-renderer.tsx +1 -0
  30. package/src/mixins/provide-keypad.tsx +1 -0
  31. package/src/multi-items/multi-renderer.tsx +1 -0
  32. package/src/renderer.tsx +1 -0
  33. package/src/server-item-renderer.tsx +1 -0
  34. package/src/styles/articles.less +8 -4
  35. package/src/styles/perseus-renderer.less +47 -38
  36. package/src/styles/util.less +1 -1
  37. package/src/styles/variables.boostrap.less +158 -182
  38. package/src/styles/variables.less +20 -20
  39. package/src/styles/variables.shared.less +162 -168
  40. package/src/styles/widgets/categorizer.less +1 -2
  41. package/src/styles/widgets/expression.less +7 -4
  42. package/src/styles/widgets/grapher.less +2 -1
  43. package/src/styles/widgets/image.less +3 -2
  44. package/src/styles/widgets/interactive-graph.less +4 -2
  45. package/src/styles/widgets/label-image.less +2 -1
  46. package/src/styles/widgets/matrix.less +11 -7
  47. package/src/styles/widgets/orderer.less +2 -1
  48. package/src/styles/widgets/passage.less +2 -1
  49. package/src/styles/widgets/plotter.less +4 -3
  50. package/src/styles/widgets/radio.less +4 -2
  51. package/src/styles/widgets/sortable.less +1 -1
  52. package/src/styles/widgets/table.less +2 -1
  53. package/src/styles/widgets/transformer.less +2 -1
  54. package/src/styles/widgets/unit.less +2 -1
  55. package/src/util.graphie.test.ts +45 -0
  56. package/src/util.graphie.ts +59 -0
  57. package/src/util.ts +8 -63
  58. package/src/widgets/__stories__/plotter.stories.tsx +27 -0
  59. package/src/widgets/__testdata__/plotter.testdata.ts +36 -0
  60. package/src/widgets/__tests__/__snapshots__/plotter.test.tsx.snap +863 -0
  61. package/src/widgets/__tests__/plotter.test.tsx +65 -0
  62. package/src/widgets/categorizer.tsx +1 -0
  63. package/src/widgets/graded-group-set.tsx +1 -0
  64. package/src/widgets/graded-group.tsx +1 -0
  65. package/src/widgets/interaction.tsx +1 -0
  66. package/src/widgets/label-image/marker.tsx +1 -0
  67. package/src/widgets/matcher.tsx +1 -0
  68. package/src/widgets/orderer.tsx +1 -0
  69. package/src/widgets/passage-ref-target.tsx +1 -0
  70. package/src/widgets/plotter.tsx +108 -98
  71. package/src/widgets/radio/base-radio.tsx +1 -0
  72. package/src/widgets/radio/choice-icon/choice-icon.tsx +1 -0
  73. package/src/widgets/sorter.tsx +1 -0
  74. package/src/widgets/transformer.tsx +1 -0
  75. package/tsconfig-build.tsbuildinfo +1 -1
package/dist/index.js CHANGED
@@ -3009,8 +3009,6 @@ const defaultContext$2 = {
3009
3009
  };
3010
3010
  const context$1 = /*#__PURE__*/React__namespace.createContext(defaultContext$2);
3011
3011
 
3012
- /* eslint-disable @babel/no-invalid-this, getter-return, one-var */
3013
- let supportsPassive = false;
3014
3012
  const svgLabelsRegex = /^web\+graphie:/;
3015
3013
  // For offline exercises in the mobile app, we download the graphie data
3016
3014
  // (svgs and localized data files) and serve them from the local file
@@ -3018,6 +3016,50 @@ const svgLabelsRegex = /^web\+graphie:/;
3018
3016
  // in the perseus json with this `file+graphie` prefix to indicate that
3019
3017
  // they should have the `file://` protocol instead of `https://`.
3020
3018
  const svgLocalLabelsRegex$1 = /^file\+graphie:/;
3019
+
3020
+ // Sometimes other components want to download the actual image e.g. to
3021
+ // determine its size. Here, we transform an .svg-labels url into the
3022
+ // correct image url, and leave normal image urls alone
3023
+ function getRealImageUrl(graphieUrl) {
3024
+ if (isLabeledSVG(graphieUrl)) {
3025
+ return getSvgUrl(graphieUrl);
3026
+ }
3027
+ return graphieUrl;
3028
+ }
3029
+ function isLabeledSVG(graphieUrl) {
3030
+ return svgLabelsRegex.test(graphieUrl) || svgLocalLabelsRegex$1.test(graphieUrl);
3031
+ }
3032
+
3033
+ // For each graphie, there are two urls we need to download from. This gets
3034
+ // the base url without the suffix, and `getSvgUrl` and `getDataUrl` apply
3035
+ // appropriate suffixes to get the image and other data
3036
+ function getBaseUrl(graphieUrl) {
3037
+ return graphieUrl.replace(svgLabelsRegex, "https:").replace(svgLocalLabelsRegex$1, "file:");
3038
+ }
3039
+ function getSvgUrl(graphieUrl) {
3040
+ return getBaseUrl(graphieUrl) + ".svg";
3041
+ }
3042
+ function getDataUrl(graphieUrl) {
3043
+ return getBaseUrl(graphieUrl) + "-data.json";
3044
+ }
3045
+ async function getImageSizeModern(url) {
3046
+ const image = new Image();
3047
+ return new Promise((resolve, reject) => {
3048
+ // Handle the success case
3049
+ image.onload = () => {
3050
+ resolve([image.naturalWidth, image.naturalHeight]);
3051
+ };
3052
+
3053
+ // Handle the error case
3054
+ image.onerror = reject;
3055
+
3056
+ // Kick off the loading
3057
+ image.src = getRealImageUrl(url);
3058
+ });
3059
+ }
3060
+
3061
+ /* eslint-disable @babel/no-invalid-this, getter-return, one-var */
3062
+ let supportsPassive = false;
3021
3063
  const nestedMap$2 = function (children, func, context) {
3022
3064
  if (Array.isArray(children)) {
3023
3065
  // @ts-expect-error [FEI-5003] - TS2322 - Type '(M | readonly M[])[]' is not assignable to type 'M | readonly M[]'.
@@ -3683,21 +3725,6 @@ const supportsPassiveEvents = () => {
3683
3725
  function captureScratchpadTouchStart$4(e) {
3684
3726
  e.stopPropagation();
3685
3727
  }
3686
- async function getImageSizeModern(url) {
3687
- const image = new Image();
3688
- return new Promise((resolve, reject) => {
3689
- // Handle the success case
3690
- image.onload = () => {
3691
- resolve([image.naturalWidth, image.naturalHeight]);
3692
- };
3693
-
3694
- // Handle the error case
3695
- image.onerror = reject;
3696
-
3697
- // Kick off the loading
3698
- image.src = url;
3699
- });
3700
- }
3701
3728
  function getImageSize(url, callback) {
3702
3729
  const img = new Image();
3703
3730
  img.onload = function () {
@@ -3727,32 +3754,6 @@ function getImageSize(url, callback) {
3727
3754
  img.src = getRealImageUrl(url);
3728
3755
  }
3729
3756
 
3730
- // Sometimes other components want to download the actual image e.g. to
3731
- // determine its size. Here, we transform an .svg-labels url into the
3732
- // correct image url, and leave normal image urls alone
3733
- function getRealImageUrl(url) {
3734
- if (isLabeledSVG(url)) {
3735
- return getSvgUrl(url);
3736
- }
3737
- return url;
3738
- }
3739
- function isLabeledSVG(url) {
3740
- return svgLabelsRegex.test(url) || svgLocalLabelsRegex$1.test(url);
3741
- }
3742
-
3743
- // For each svg+labels, there are two urls we need to download from. This gets
3744
- // the base url without the suffix, and `getSvgUrl` and `getDataUrl` apply
3745
- // appropriate suffixes to get the image and other data
3746
- function getBaseUrl(url) {
3747
- return url.replace(svgLabelsRegex, "https:").replace(svgLocalLabelsRegex$1, "file:");
3748
- }
3749
- function getSvgUrl(url) {
3750
- return getBaseUrl(url) + ".svg";
3751
- }
3752
- function getDataUrl(url) {
3753
- return getBaseUrl(url) + "-data.json";
3754
- }
3755
-
3756
3757
  /**
3757
3758
  * Gets the word right before where the textarea cursor is
3758
3759
  *
@@ -3832,12 +3833,12 @@ const Util = {
3832
3833
  supportsPassiveEvents,
3833
3834
  captureScratchpadTouchStart: captureScratchpadTouchStart$4,
3834
3835
  getImageSize,
3835
- getImageSizeModern,
3836
- getRealImageUrl,
3837
- isLabeledSVG,
3838
- getBaseUrl,
3839
- getSvgUrl,
3840
- getDataUrl,
3836
+ getImageSizeModern: getImageSizeModern,
3837
+ getRealImageUrl: getRealImageUrl,
3838
+ isLabeledSVG: isLabeledSVG,
3839
+ getBaseUrl: getBaseUrl,
3840
+ getSvgUrl: getSvgUrl,
3841
+ getDataUrl: getDataUrl,
3841
3842
  textarea,
3842
3843
  unescapeMathMode: unescapeMathMode$1,
3843
3844
  random: random$1
@@ -38309,26 +38310,11 @@ var PassageRefTarget$1 = {
38309
38310
  const {
38310
38311
  deepEq: deepEq$1
38311
38312
  } = Util;
38312
- const BAR = "bar",
38313
- LINE = "line",
38314
- PIC = "pic",
38315
- HISTOGRAM = "histogram",
38316
- DOTPLOT = "dotplot";
38317
- ({
38318
- type: PropTypes__default["default"].oneOf([BAR, LINE, PIC, HISTOGRAM, DOTPLOT]),
38319
- labels: PropTypes__default["default"].arrayOf(PropTypes__default["default"].string),
38320
- categories: PropTypes__default["default"].arrayOf(PropTypes__default["default"].oneOfType([PropTypes__default["default"].number, PropTypes__default["default"].string])),
38321
- scaleY: PropTypes__default["default"].number,
38322
- maxY: PropTypes__default["default"].number,
38323
- snapsPerLine: PropTypes__default["default"].number,
38324
- picSize: PropTypes__default["default"].number,
38325
- pixBoxHeight: PropTypes__default["default"].number,
38326
- picUrl: PropTypes__default["default"].string,
38327
- plotDimensions: PropTypes__default["default"].arrayOf(PropTypes__default["default"].number),
38328
- labelInterval: PropTypes__default["default"].number,
38329
- starting: PropTypes__default["default"].arrayOf(PropTypes__default["default"].number),
38330
- static: PropTypes__default["default"].bool
38331
- });
38313
+ const BAR = "bar";
38314
+ const LINE = "line";
38315
+ const PIC = "pic";
38316
+ const HISTOGRAM = "histogram";
38317
+ const DOTPLOT = "dotplot";
38332
38318
  class Plotter extends React__namespace.Component {
38333
38319
  constructor() {
38334
38320
  super(...arguments);
@@ -38367,11 +38353,11 @@ class Plotter extends React__namespace.Component {
38367
38353
  self.graphie = graphie;
38368
38354
  self.graphie.pics = [];
38369
38355
  self.graphie.dotTicks = [];
38370
- const isBar = self.props.type === BAR,
38371
- isLine = self.props.type === LINE,
38372
- isPic = self.props.type === PIC,
38373
- isHistogram = self.props.type === HISTOGRAM,
38374
- isDotplot = self.props.type === DOTPLOT;
38356
+ const isBar = self.props.type === BAR;
38357
+ const isLine = self.props.type === LINE;
38358
+ const isPic = self.props.type === PIC;
38359
+ const isHistogram = self.props.type === HISTOGRAM;
38360
+ const isDotplot = self.props.type === DOTPLOT;
38375
38361
  const isTiledPlot = isPic || isDotplot;
38376
38362
  const config = {};
38377
38363
  const c = config; // c for short
@@ -38432,9 +38418,7 @@ class Plotter extends React__namespace.Component {
38432
38418
 
38433
38419
  // TODO(michaelpolyak): Handle other plot types: Dot, Histogram, Line.
38434
38420
  if (isMobile && (isBar || isTiledPlot)) {
38435
- const maxCategoryHeight = Math.max(0,
38436
- // @ts-expect-error [FEI-5003] - TS2345 - Argument of type 'unknown' is not assignable to parameter of type 'number'.
38437
- ...Object.values(self.state.categoryHeights));
38421
+ const maxCategoryHeight = Math.max(0, ...Object.values(self.state.categoryHeights));
38438
38422
  if (maxCategoryHeight) {
38439
38423
  // Account for bottom label position, height.
38440
38424
  let offsetY = 25;
@@ -38487,7 +38471,7 @@ class Plotter extends React__namespace.Component {
38487
38471
  });
38488
38472
  }
38489
38473
  }
38490
- if ((isBar || isLine) && isMobile) {
38474
+ if ((isBar || isLine) && isMobile && !this.props.static) {
38491
38475
  self.graphie.dragPrompt = graphie.label([c.dimX / 2, c.dimY / 2], i18n__namespace._("Drag handles to make graph"), "center", false).css("font-weight", "bold").css("color", KhanColors.KA_GREEN).css("display", "none");
38492
38476
  }
38493
38477
  self.setupCategories(config);
@@ -38739,8 +38723,8 @@ class Plotter extends React__namespace.Component {
38739
38723
  config.graph.bars[i].scale(1, Math.max(isMobile ? 0.2 : 0.01, height / config.scaleY), center[0], center[1]);
38740
38724
  if (isHistogram) {
38741
38725
  // Scale dividers between buckets
38742
- const leftDivider = config.graph.dividers[i - 1],
38743
- rightDivider = config.graph.dividers[i];
38726
+ const leftDivider = config.graph.dividers[i - 1];
38727
+ const rightDivider = config.graph.dividers[i];
38744
38728
  if (leftDivider) {
38745
38729
  const divHeight = Math.min(self.state.values[i - 1], height);
38746
38730
  leftDivider.scale(1, Math.max(0.01, divHeight / config.scaleY), center[0], center[1]);
@@ -39065,38 +39049,13 @@ class Plotter extends React__namespace.Component {
39065
39049
  return Plotter.validate(this.getUserInput(), rubric);
39066
39050
  });
39067
39051
  }
39068
- render() {
39069
- // TODO(kevinb) actually compute the size of the graphie correctly and
39070
- // make it that size so we don't have to add extra padding. The value
39071
- // was determined by eye-balling the layout. :(
39072
- const paddingForBottomLabel = 75;
39073
- const style = {
39074
- marginBottom: this.props.labels[0] ? paddingForBottomLabel : 0
39075
- };
39076
- return /*#__PURE__*/React__namespace.createElement("div", {
39077
- className: "perseus-widget-plotter graphie " + ClassNames.INTERACTIVE
39078
- // eslint-disable-next-line react/no-string-refs
39079
- ,
39080
- ref: "graphieDiv",
39081
- style: style
39082
- });
39083
- }
39084
- componentDidUpdate(prevProps, prevState) {
39085
- this.shouldSetupGraphie = this.shouldSetupGraphie || !___default["default"].isEqual(this.state.categoryHeights, prevState.categoryHeights);
39086
- if (this.shouldSetupGraphie) {
39087
- this.setupGraphie(prevState);
39088
- }
39089
- }
39090
39052
  componentDidMount() {
39091
39053
  this._isMounted = true;
39092
39054
  this.setupGraphie(this.state);
39093
39055
  }
39094
39056
  UNSAFE_componentWillReceiveProps(nextProps) {
39095
39057
  const props = ["type", "labels", "categories", "scaleY", "maxY", "snapsPerLine", "picUrl", "labelInterval", "static"];
39096
- this.shouldSetupGraphie = ___default["default"].any(props, function (prop) {
39097
- // @ts-expect-error [FEI-5003] - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
39098
- return !___default["default"].isEqual(this.props[prop], nextProps[prop]);
39099
- }, this);
39058
+ this.shouldSetupGraphie = ___default["default"].any(props, prop => !___default["default"].isEqual(this.props[prop], nextProps[prop]), this);
39100
39059
  if (!___default["default"].isEqual(this.props.starting, nextProps.starting) && !___default["default"].isEqual(this.state.values, nextProps.starting)) {
39101
39060
  this.shouldSetupGraphie = true;
39102
39061
  this.setState({
@@ -39104,16 +39063,32 @@ class Plotter extends React__namespace.Component {
39104
39063
  });
39105
39064
  }
39106
39065
  }
39066
+ componentDidUpdate(prevProps, prevState) {
39067
+ this.shouldSetupGraphie = this.shouldSetupGraphie || !___default["default"].isEqual(this.state.categoryHeights, prevState.categoryHeights);
39068
+ if (this.shouldSetupGraphie) {
39069
+ this.setupGraphie(prevState);
39070
+ }
39071
+ }
39107
39072
  componentWillUnmount() {
39108
39073
  this._isMounted = false;
39109
39074
  }
39075
+ render() {
39076
+ // TODO(kevinb) actually compute the size of the graphie correctly and
39077
+ // make it that size so we don't have to add extra padding. The value
39078
+ // was determined by eye-balling the layout. :(
39079
+ const paddingForBottomLabel = 75;
39080
+ const style = {
39081
+ marginBottom: this.props.labels[0] ? paddingForBottomLabel : 0
39082
+ };
39083
+ return /*#__PURE__*/React__namespace.createElement("div", {
39084
+ className: "perseus-widget-plotter graphie " + ClassNames.INTERACTIVE
39085
+ // eslint-disable-next-line react/no-string-refs
39086
+ ,
39087
+ ref: "graphieDiv",
39088
+ style: style
39089
+ });
39090
+ }
39110
39091
  }
39111
- _defineProperty(Plotter, "propTypes", {
39112
- onChange: PropTypes__default["default"].func.isRequired,
39113
- trackInteraction: PropTypes__default["default"].func.isRequired
39114
- // TODO(alex): Figure out why lint chokes on this line
39115
- // ...widgetPropTypes,
39116
- });
39117
39092
  _defineProperty(Plotter, "defaultProps", {
39118
39093
  type: BAR,
39119
39094
  labels: ["", ""],
@@ -44146,17 +44121,7 @@ registerWidgets(allWidgets);
44146
44121
  const ItemVersion = getVersionVector();
44147
44122
  ItemVersion["::renderer::"] = version.itemDataVersion;
44148
44123
 
44149
- /**
44150
- * A mixin that renders a custom software keypad in additional to the base
44151
- * component. The base component will receive blur events when the keypad is
44152
- * dismissed and can access the keypad element itself so as to manage its
44153
- * activation and dismissal.
44154
- *
44155
- * TODO(charlie): This would make a nicer higher-order component, except that
44156
- * we need to expose methods on the base component (i.e., `ItemRenderer`). When
44157
- * `ItemRenderer` and friends are written as ES6 Classes, we can have them
44158
- * extend a `ProvideKeypad` component instead of using this mixin.
44159
- */
44124
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
44160
44125
  // NOTE: This is not a real component. It's a collection of methods used to
44161
44126
  // create and manage a Keypad instances.
44162
44127
  // TODO(LP-10789): replace this with a React Context object to pass information
@@ -47077,9 +47042,7 @@ const EditorJsonify = {
47077
47042
  }
47078
47043
  };
47079
47044
 
47080
- /**
47081
- * Main entry point
47082
- */
47045
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
47083
47046
  const apiVersion = version.apiVersion;
47084
47047
  const itemDataVersion = version.itemDataVersion;
47085
47048