@khanacademy/math-input 14.1.1 → 14.2.0

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
@@ -13,10 +13,11 @@ var $ = require('jquery');
13
13
  var MathQuill = require('mathquill');
14
14
  var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
15
15
  var Clickable = require('@khanacademy/wonder-blocks-clickable');
16
+ var reactTransitionGroup = require('react-transition-group');
17
+ var wonderBlocksTiming = require('@khanacademy/wonder-blocks-timing');
16
18
  var reactRedux = require('react-redux');
17
19
  var katex = require('katex');
18
20
  var PropTypes = require('prop-types');
19
- var reactTransitionGroup = require('react-transition-group');
20
21
  var Redux = require('redux');
21
22
 
22
23
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
@@ -52,7 +53,7 @@ var Redux__namespace = /*#__PURE__*/_interopNamespace(Redux);
52
53
 
53
54
  // This file is processed by a Rollup plugin (replace) to inject the production
54
55
  const libName = "@khanacademy/math-input";
55
- const libVersion = "14.1.1";
56
+ const libVersion = "14.2.0";
56
57
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
57
58
 
58
59
  function _defineProperty(obj, key, value) {
@@ -5389,16 +5390,304 @@ const styles$d = aphrodite.StyleSheet.create({
5389
5390
  }
5390
5391
  });
5391
5392
 
5393
+ function flatten(list) {
5394
+ const result = [];
5395
+ if (!list) {
5396
+ return result;
5397
+ }
5398
+ if (Array.isArray(list)) {
5399
+ for (const item of list) {
5400
+ result.push(...flatten(item));
5401
+ }
5402
+ } else {
5403
+ result.push(list);
5404
+ }
5405
+ return result;
5406
+ }
5407
+ function processStyleType(style) {
5408
+ const stylesheetStyles = [];
5409
+ const inlineStyles = [];
5410
+ if (!style) {
5411
+ return {
5412
+ style: {},
5413
+ className: ""
5414
+ };
5415
+ }
5416
+
5417
+ // Check to see if we should inline all the styles for snapshot tests.
5418
+ const shouldInlineStyles = typeof globalThis !== "undefined" && globalThis.SNAPSHOT_INLINE_APHRODITE;
5419
+ flatten(style).forEach(child => {
5420
+ // Check for aphrodite internal property
5421
+ const _definition = child._definition;
5422
+ if (_definition != null) {
5423
+ if (shouldInlineStyles) {
5424
+ const def = {};
5425
+ // React 16 complains about invalid keys in inline styles.
5426
+ // It doesn't accept kebab-case in media queries and instead
5427
+ // prefers camelCase.
5428
+ for (const [key, value] of wonderStuffCore.entries(_definition)) {
5429
+ // This regex converts all instances of -{lowercaseLetter}
5430
+ // to the uppercase version of that letter, without the
5431
+ // leading dash.
5432
+ def[key.replace(/-[a-z]/g, match => match[1].toUpperCase())] = value;
5433
+ }
5434
+ inlineStyles.push(def);
5435
+ } else {
5436
+ stylesheetStyles.push(child);
5437
+ }
5438
+ } else {
5439
+ inlineStyles.push(child);
5440
+ }
5441
+ });
5442
+ const inlineStylesObject = Object.assign({}, ...inlineStyles);
5443
+
5444
+ // TODO(somewhatabstract): When aphrodite no longer puts "!important" on
5445
+ // all the styles, remove this <ADD JIRA ISSUE HERE IF THIS PASSES REVIEW>
5446
+ // If we're not snapshotting styles, let's create a class for the inline
5447
+ // styles so that they can apply to the element even with aphrodite's
5448
+ // use of !important.
5449
+ if (inlineStyles.length > 0 && !shouldInlineStyles) {
5450
+ const inlineStylesStyleSheet = aphrodite.StyleSheet.create({
5451
+ inlineStyles: inlineStylesObject
5452
+ });
5453
+ stylesheetStyles.push(inlineStylesStyleSheet.inlineStyles);
5454
+ }
5455
+ return {
5456
+ style: shouldInlineStyles ? inlineStylesObject : {},
5457
+ className: aphrodite.css(...stylesheetStyles)
5458
+ };
5459
+ }
5460
+
5461
+ class TransitionChild extends React__namespace.Component {
5462
+ // Each 2-tuple in the queue represents two classnames: one to remove and
5463
+ // one to add (in that order).
5464
+
5465
+ // We keep track of all of the current applied classes so that we can remove
5466
+ // them before a new transition starts in the case of the current transition
5467
+ // being interrupted.
5468
+
5469
+ // The use of getDerivedStateFromProps here is to avoid an extra call to
5470
+ // setState if the component re-enters. This can happen if TransitionGroup
5471
+ // sets `in` from `false` to `true`.
5472
+ // eslint-disable-next-line no-restricted-syntax
5473
+ static getDerivedStateFromProps(_ref, prevState) {
5474
+ let {
5475
+ in: nextIn
5476
+ } = _ref;
5477
+ if (nextIn && prevState.status === "unmounted") {
5478
+ return {
5479
+ status: "mounted"
5480
+ };
5481
+ }
5482
+ return null;
5483
+ }
5484
+ constructor(props) {
5485
+ super(props);
5486
+ _defineProperty(this, "classNameQueue", void 0);
5487
+ _defineProperty(this, "appliedClassNames", void 0);
5488
+ _defineProperty(this, "_isMounted", false);
5489
+ _defineProperty(this, "addClass", (elem, className) => {
5490
+ if (className) {
5491
+ elem.classList.add(className);
5492
+ this.appliedClassNames.add(className);
5493
+ }
5494
+ });
5495
+ _defineProperty(this, "removeClass", (elem, className) => {
5496
+ if (className) {
5497
+ elem.classList.remove(className);
5498
+ this.appliedClassNames.delete(className);
5499
+ }
5500
+ });
5501
+ _defineProperty(this, "flushClassNameQueue", () => {
5502
+ if (this._isMounted) {
5503
+ const node = ReactDOM__default["default"].findDOMNode(this);
5504
+ if (node instanceof Element) {
5505
+ this.classNameQueue.forEach(_ref2 => {
5506
+ let [removeClassName, addClassName] = _ref2;
5507
+ // Remove the old class before adding a new class just
5508
+ // in case the new class is the same as the old one.
5509
+ this.removeClass(node, removeClassName);
5510
+ this.addClass(node, addClassName);
5511
+ });
5512
+ }
5513
+ }
5514
+
5515
+ // Remove all items in the Array.
5516
+ this.classNameQueue.length = 0;
5517
+ });
5518
+ this._isMounted = false;
5519
+ this.classNameQueue = [];
5520
+ this.appliedClassNames = new Set();
5521
+ this.state = {
5522
+ status: "mounted"
5523
+ };
5524
+ }
5525
+ componentDidMount() {
5526
+ this._isMounted = true;
5527
+ if (typeof this.props.appearTimeout === "number") {
5528
+ this.transition("appear", this.props.appearTimeout);
5529
+ } else {
5530
+ this.transition("enter", this.props.enterTimeout);
5531
+ }
5532
+ }
5533
+ componentDidUpdate(oldProps, oldState) {
5534
+ if (oldProps.in && !this.props.in) {
5535
+ this.transition("leave", this.props.leaveTimeout);
5536
+ } else if (!oldProps.in && this.props.in) {
5537
+ this.transition("enter", this.props.enterTimeout);
5538
+ }
5539
+ if (oldState.status !== "mounted" && this.state.status === "mounted") {
5540
+ // Remove the node from the DOM
5541
+ // eslint-disable-next-line react/no-did-update-set-state
5542
+ this.setState({
5543
+ status: "unmounted"
5544
+ });
5545
+ }
5546
+ }
5547
+
5548
+ // NOTE: This will only get called when the parent TransitionGroup becomes
5549
+ // unmounted. This is because that component clones all of its children and
5550
+ // keeps them around so that they can be animated when leaving and also so
5551
+ // that the can be animated when re-rentering if that occurs.
5552
+ componentWillUnmount() {
5553
+ this._isMounted = false;
5554
+ this.props.schedule.clearAll();
5555
+ }
5556
+ removeAllClasses(node) {
5557
+ for (const className of this.appliedClassNames) {
5558
+ this.removeClass(node, className);
5559
+ }
5560
+ }
5561
+ transition(animationType, duration) {
5562
+ const node = ReactDOM__default["default"].findDOMNode(this);
5563
+ if (!(node instanceof Element)) {
5564
+ return;
5565
+ }
5566
+
5567
+ // Remove any classes from previous transitions.
5568
+ this.removeAllClasses(node);
5569
+
5570
+ // A previous transition may still be in progress so clear its timers.
5571
+ this.props.schedule.clearAll();
5572
+ const transitionStyles = typeof this.props.transitionStyles === "function" ? this.props.transitionStyles() : this.props.transitionStyles;
5573
+ const {
5574
+ className
5575
+ } = processStyleType(transitionStyles[animationType]);
5576
+ const {
5577
+ className: activeClassName
5578
+ } = processStyleType([transitionStyles[animationType], transitionStyles[animationType + "Active"]]);
5579
+
5580
+ // Put the node in the starting position.
5581
+ this.addClass(node, className);
5582
+
5583
+ // Queue the component to show the "active" style.
5584
+ this.queueClass(className, activeClassName);
5585
+
5586
+ // Unmount the children after the 'leave' transition has completed.
5587
+ if (animationType === "leave") {
5588
+ this.props.schedule.timeout(() => {
5589
+ if (this._isMounted) {
5590
+ this.setState({
5591
+ status: "unmounted"
5592
+ });
5593
+ }
5594
+ }, duration || 0);
5595
+ }
5596
+ }
5597
+ queueClass(removeClassName, addClassName) {
5598
+ this.classNameQueue.push([removeClassName, addClassName]);
5599
+ this.props.schedule.animationFrame(this.flushClassNameQueue);
5600
+ }
5601
+ render() {
5602
+ const {
5603
+ status
5604
+ } = this.state;
5605
+ if (status === "unmounted") {
5606
+ return null;
5607
+ }
5608
+ return this.props.children;
5609
+ }
5610
+ }
5611
+ var TransitionChild$1 = wonderBlocksTiming.withActionScheduler(TransitionChild);
5612
+
5613
+ /**
5614
+ * Aphrodite doesn't play well with CSSTransition from react-transition-group,
5615
+ * which assumes that you have CSS classes and it can combine them arbitrarily.
5616
+ *
5617
+ * There are also some issue with react-transition-group that make it difficult
5618
+ * to work. Even if the CSS classes are defined ahead of time it makes no
5619
+ * guarantee that the start style will be applied by the browser before the
5620
+ * active style is applied. This can cause the first time a transition runs to
5621
+ * fail.
5622
+ *
5623
+ * AphroditeCSSTransitionGroup provides a wrapper around TransitionGroup to
5624
+ * address these issues.
5625
+ *
5626
+ * There are three types of transitions:
5627
+ * - appear: the time the child is added to the render tree
5628
+ * - enter: whenever the child is added to the render tree after "appear". If
5629
+ * no "appear" transition is specified then the "enter" transition will also
5630
+ * be used for the first time the child is added to the render tree.
5631
+ * - leave: whenever the child is removed from the render tree
5632
+ *
5633
+ * Each transition type has two states:
5634
+ * - base: e.g. css(enter)
5635
+ * - active: e.g. css(enter, enterActive)
5636
+ *
5637
+ * If "done" styles are not provided, the "active" style will remain on the
5638
+ * component after the animation has completed.
5639
+ *
5640
+ * Usage: TBD
5641
+ *
5642
+ * Limitations:
5643
+ * - This component only supports a single child whereas TransitionGroup supports
5644
+ * multiple children.
5645
+ * - We ignore inline styles that are provided as part of AnimationStyles.
5646
+ *
5647
+ * TODOs:
5648
+ * - (FEI-3211): Change the API for AphroditeCSSTransitionGroup so that it makes
5649
+ * bad states impossible.
5650
+ */
5651
+ class AphroditeCSSTransitionGroup extends React__namespace.Component {
5652
+ render() {
5653
+ const {
5654
+ children
5655
+ } = this.props;
5656
+ return (
5657
+ /*#__PURE__*/
5658
+ // `component={null}` prevents wrapping each child with a <div>
5659
+ // which can muck with certain layouts.
5660
+ React__namespace.createElement(reactTransitionGroup.TransitionGroup, {
5661
+ component: null
5662
+ }, React__namespace.Children.map(children, child => /*#__PURE__*/React__namespace.createElement(TransitionChild$1, {
5663
+ transitionStyles: this.props.transitionStyle,
5664
+ appearTimeout: this.props.transitionAppearTimeout,
5665
+ enterTimeout: this.props.transitionEnterTimeout,
5666
+ leaveTimeout: this.props.transitionLeaveTimeout
5667
+ }, child)))
5668
+ );
5669
+ }
5670
+ }
5671
+
5672
+ const AnimationDurationInMS = 200;
5673
+ /**
5674
+ * This is the v2 equivalent of v1's ProvidedKeypad. It follows the same
5675
+ * external API so that it can be hot-swapped with the v1 keypad and
5676
+ * is responsible for connecting the keypad with MathInput and the Renderer.
5677
+ *
5678
+ * Ideally this strategy of attaching methods on the class component for
5679
+ * other components to call will be replaced props/callbacks since React
5680
+ * doesn't support this type of code anymore (functional components
5681
+ * can't have methods attached to them).
5682
+ */
5392
5683
  class MobileKeypad extends React__namespace.Component {
5393
5684
  constructor() {
5394
5685
  super(...arguments);
5395
5686
  _defineProperty(this, "_containerRef", /*#__PURE__*/React__namespace.createRef());
5396
5687
  _defineProperty(this, "_containerResizeObserver", null);
5397
5688
  _defineProperty(this, "_throttleResize", false);
5398
- _defineProperty(this, "hasMounted", false);
5399
5689
  _defineProperty(this, "state", {
5400
- containerWidth: 0,
5401
- hasBeenActivated: false
5690
+ containerWidth: 0
5402
5691
  });
5403
5692
  _defineProperty(this, "_resize", () => {
5404
5693
  var _this$_containerRef$c;
@@ -5419,9 +5708,6 @@ class MobileKeypad extends React__namespace.Component {
5419
5708
  });
5420
5709
  _defineProperty(this, "activate", () => {
5421
5710
  this.props.setKeypadActive(true);
5422
- this.setState({
5423
- hasBeenActivated: true
5424
- });
5425
5711
  });
5426
5712
  _defineProperty(this, "dismiss", () => {
5427
5713
  var _this$props$onDismiss, _this$props;
@@ -5459,6 +5745,7 @@ class MobileKeypad extends React__namespace.Component {
5459
5745
  });
5460
5746
  }
5461
5747
  componentDidMount() {
5748
+ var _this$props$onElement, _this$props2;
5462
5749
  this._resize();
5463
5750
  window.addEventListener("resize", this._throttleResizeHandler);
5464
5751
  window.addEventListener("orientationchange", this._throttleResizeHandler);
@@ -5471,6 +5758,14 @@ class MobileKeypad extends React__namespace.Component {
5471
5758
  this._containerResizeObserver.observe(this._containerRef.current);
5472
5759
  }
5473
5760
  }
5761
+ (_this$props$onElement = (_this$props2 = this.props).onElementMounted) === null || _this$props$onElement === void 0 ? void 0 : _this$props$onElement.call(_this$props2, {
5762
+ activate: this.activate,
5763
+ dismiss: this.dismiss,
5764
+ configure: this.configure,
5765
+ setCursor: this.setCursor,
5766
+ setKeyHandler: this.setKeyHandler,
5767
+ getDOMNode: this.getDOMNode
5768
+ });
5474
5769
  }
5475
5770
  componentWillUnmount() {
5476
5771
  var _this$_containerResiz;
@@ -5495,53 +5790,37 @@ class MobileKeypad extends React__namespace.Component {
5495
5790
  style
5496
5791
  } = this.props;
5497
5792
  const {
5498
- hasBeenActivated,
5499
5793
  containerWidth,
5500
5794
  cursor,
5501
5795
  keypadConfig
5502
5796
  } = this.state;
5503
- const containerStyle = [
5504
- // internal styles
5505
- styles$c.keypadContainer, keypadActive && styles$c.activeKeypadContainer,
5797
+ const containerStyle = [styles$c.keypadContainer,
5506
5798
  // styles passed as props
5507
5799
  ...(Array.isArray(style) ? style : [style])];
5508
-
5509
- // If the keypad is yet to have ever been activated, we keep it invisible
5510
- // so as to avoid, e.g., the keypad flashing at the bottom of the page
5511
- // during the initial render.
5512
- // Done inline (dynamicStyle) since stylesheets might not be loaded yet.
5513
- let dynamicStyle = {};
5514
- if (!keypadActive && !hasBeenActivated) {
5515
- dynamicStyle = {
5516
- visibility: "hidden"
5517
- };
5518
- }
5519
5800
  const isExpression = (keypadConfig === null || keypadConfig === void 0 ? void 0 : keypadConfig.keypadType) === "EXPRESSION";
5520
5801
  const convertDotToTimes = keypadConfig === null || keypadConfig === void 0 ? void 0 : keypadConfig.times;
5521
- return /*#__PURE__*/React__namespace.createElement(View, {
5522
- style: containerStyle,
5523
- dynamicStyle: dynamicStyle,
5524
- forwardRef: this._containerRef,
5525
- ref: element => {
5526
- if (!this.hasMounted && element) {
5527
- var _this$props$onElement, _this$props2;
5528
- // TODO(matthewc)[LC-1081]: clean up this weird
5529
- // object and type the onElementMounted callback
5530
- // Append the dispatch methods that we want to expose
5531
- // externally to the returned React element.
5532
- const elementWithDispatchMethods = {
5533
- ...element,
5534
- activate: this.activate,
5535
- dismiss: this.dismiss,
5536
- configure: this.configure,
5537
- setCursor: this.setCursor,
5538
- setKeyHandler: this.setKeyHandler,
5539
- getDOMNode: this.getDOMNode
5540
- };
5541
- this.hasMounted = true;
5542
- (_this$props$onElement = (_this$props2 = this.props).onElementMounted) === null || _this$props$onElement === void 0 ? void 0 : _this$props$onElement.call(_this$props2, elementWithDispatchMethods);
5802
+ return /*#__PURE__*/React__namespace.createElement(AphroditeCSSTransitionGroup, {
5803
+ transitionEnterTimeout: AnimationDurationInMS,
5804
+ transitionLeaveTimeout: AnimationDurationInMS,
5805
+ transitionStyle: {
5806
+ enter: {
5807
+ transform: "translate3d(0, 100%, 0)",
5808
+ transition: "".concat(AnimationDurationInMS, "ms ease-out")
5809
+ },
5810
+ enterActive: {
5811
+ transform: "translate3d(0, 0, 0)"
5812
+ },
5813
+ leave: {
5814
+ transform: "translate3d(0, 0, 0)",
5815
+ transition: "".concat(AnimationDurationInMS, "ms ease-out")
5816
+ },
5817
+ leaveActive: {
5818
+ transform: "translate3d(0, 100%, 0)"
5543
5819
  }
5544
5820
  }
5821
+ }, keypadActive ? /*#__PURE__*/React__namespace.createElement(View, {
5822
+ style: containerStyle,
5823
+ forwardRef: this._containerRef
5545
5824
  }, /*#__PURE__*/React__namespace.createElement(Keypad$2, {
5546
5825
  onAnalyticsEvent: this.props.onAnalyticsEvent,
5547
5826
  extraKeys: keypadConfig === null || keypadConfig === void 0 ? void 0 : keypadConfig.extraKeys,
@@ -5557,7 +5836,7 @@ class MobileKeypad extends React__namespace.Component {
5557
5836
  advancedRelations: isExpression,
5558
5837
  expandedView: containerWidth > expandedViewThreshold$1,
5559
5838
  showDismiss: true
5560
- }));
5839
+ })) : null);
5561
5840
  }
5562
5841
  }
5563
5842
  const styles$c = aphrodite.StyleSheet.create({
@@ -5565,15 +5844,7 @@ const styles$c = aphrodite.StyleSheet.create({
5565
5844
  bottom: 0,
5566
5845
  left: 0,
5567
5846
  right: 0,
5568
- position: "fixed",
5569
- transitionProperty: "all",
5570
- transition: "200ms ease-out",
5571
- visibility: "hidden",
5572
- transform: "translate3d(0, 100%, 0)"
5573
- },
5574
- activeKeypadContainer: {
5575
- transform: "translate3d(0, 0, 0)",
5576
- visibility: "visible"
5847
+ position: "fixed"
5577
5848
  }
5578
5849
  });
5579
5850