@khanacademy/math-input 19.1.0 → 19.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.
@@ -1,13 +1,13 @@
1
1
  import * as React from "react";
2
2
  import type { KeypadPageType } from "../../types";
3
3
  export type ItemState = "active" | "inactive" | "disabled";
4
- type Props = {
4
+ type TabItemProps = {
5
5
  onClick: () => void;
6
6
  itemState: ItemState;
7
7
  itemType: KeypadPageType;
8
+ focus?: boolean;
9
+ role: "tab" | "button";
8
10
  };
9
- declare class TabbarItem extends React.Component<Props> {
10
- render(): React.ReactNode;
11
- }
11
+ declare function TabbarItem(props: TabItemProps): React.ReactElement;
12
12
  export declare const TabbarItemForTesting: typeof TabbarItem;
13
13
  export default TabbarItem;
package/dist/es/index.js CHANGED
@@ -3,7 +3,7 @@ import { color } from '@khanacademy/wonder-blocks-tokens';
3
3
  import { entries } from '@khanacademy/wonder-stuff-core';
4
4
  import { StyleSheet, css } from 'aphrodite';
5
5
  import * as React from 'react';
6
- import { useContext, useState, useMemo, useEffect } from 'react';
6
+ import { useContext, useState, useMemo, useRef, useEffect } from 'react';
7
7
  import ReactDOM from 'react-dom';
8
8
  import { SpeechRuleEngine } from '@khanacademy/mathjax-renderer';
9
9
  import MathQuill from 'mathquill';
@@ -15,7 +15,7 @@ import PropTypes from 'prop-types';
15
15
 
16
16
  // This file is processed by a Rollup plugin (replace) to inject the production
17
17
  const libName = "@khanacademy/math-input";
18
- const libVersion = "19.1.0";
18
+ const libVersion = "19.2.0";
19
19
  addLibraryVersionToPerseusDebug(libName, libVersion);
20
20
 
21
21
  function _extends() {
@@ -2599,40 +2599,64 @@ function imageTintColor(itemState, hovered, focused, pressed) {
2599
2599
  }
2600
2600
  return color.offBlack64;
2601
2601
  }
2602
- class TabbarItem extends React.Component {
2603
- render() {
2604
- const {
2605
- onClick,
2606
- itemType,
2607
- itemState
2608
- } = this.props;
2609
- return /*#__PURE__*/React.createElement(Clickable, {
2610
- onClick: onClick,
2611
- disabled: itemState === "disabled",
2612
- "aria-label": itemType,
2613
- style: styles$6.clickable,
2614
- "aria-selected": itemState === "active",
2615
- role: "tab"
2616
- }, ({
2617
- hovered,
2618
- focused,
2619
- pressed
2620
- }) => {
2621
- const tintColor = imageTintColor(itemState, hovered, focused, pressed);
2622
- return /*#__PURE__*/React.createElement(View$1, {
2623
- style: [styles$6.base, itemState !== "disabled" && hovered && styles$6.hovered, focused && styles$6.focused, pressed && styles$6.pressed]
2624
- }, /*#__PURE__*/React.createElement(View$1, {
2625
- style: [styles$6.innerBox, pressed && styles$6.innerBoxPressed]
2626
- }, /*#__PURE__*/React.createElement(IconAsset, {
2627
- type: itemType,
2628
- tintColor: tintColor
2629
- })), itemState === "active" && /*#__PURE__*/React.createElement(View$1, {
2630
- style: [styles$6.activeIndicator, {
2631
- backgroundColor: tintColor
2632
- }]
2633
- }));
2634
- });
2635
- }
2602
+ function TabbarItem(props) {
2603
+ const {
2604
+ onClick,
2605
+ itemType,
2606
+ itemState,
2607
+ focus,
2608
+ role
2609
+ } = props;
2610
+ const tabRef = useRef(null);
2611
+ useEffect(() => {
2612
+ let timeout;
2613
+ if (role === "tab" && focus) {
2614
+ /**
2615
+ * When tabs are within a WonderBlocks Popover component, the
2616
+ * manner in which the component is rendered and moved causes
2617
+ * focus to snap to the bottom of the page on first focus.
2618
+ *
2619
+ * This timeout moves around that by delaying the focus enough
2620
+ * to wait for the WonderBlock Popover to move to the correct
2621
+ * location and scroll the user to the correct location.
2622
+ * */
2623
+ timeout = setTimeout(() => {
2624
+ if (tabRef != null && tabRef.current) {
2625
+ // Move element into view when it is focused
2626
+ tabRef == null || tabRef.current.focus();
2627
+ }
2628
+ }, 0);
2629
+ }
2630
+ return () => clearTimeout(timeout);
2631
+ }, [role, focus, tabRef]);
2632
+ return /*#__PURE__*/React.createElement(Clickable, {
2633
+ onClick: onClick,
2634
+ disabled: itemState === "disabled",
2635
+ "aria-label": itemType,
2636
+ style: styles$6.clickable,
2637
+ "aria-selected": itemState === "active",
2638
+ tabIndex: role === "button" ? 0 : focus ? 0 : -1,
2639
+ role: role,
2640
+ ref: tabRef
2641
+ }, ({
2642
+ hovered,
2643
+ focused,
2644
+ pressed
2645
+ }) => {
2646
+ const tintColor = imageTintColor(itemState, hovered, focused, pressed);
2647
+ return /*#__PURE__*/React.createElement(View$1, {
2648
+ style: [styles$6.base, itemState !== "disabled" && hovered && styles$6.hovered, focused && styles$6.focused, pressed && styles$6.pressed]
2649
+ }, /*#__PURE__*/React.createElement(View$1, {
2650
+ style: [styles$6.innerBox, pressed && styles$6.innerBoxPressed]
2651
+ }, /*#__PURE__*/React.createElement(IconAsset, {
2652
+ type: itemType,
2653
+ tintColor: tintColor
2654
+ })), itemState === "active" && /*#__PURE__*/React.createElement(View$1, {
2655
+ style: [styles$6.activeIndicator, {
2656
+ backgroundColor: tintColor
2657
+ }]
2658
+ }));
2659
+ });
2636
2660
  }
2637
2661
 
2638
2662
  const styles$5 = StyleSheet.create({
@@ -2656,19 +2680,42 @@ function Tabbar(props) {
2656
2680
  onSelectItem,
2657
2681
  style
2658
2682
  } = props;
2683
+ const selectedIndex = items.indexOf(selectedItem);
2684
+ const [focus, setFocus] = useState(selectedIndex === -1 ? 0 : selectedIndex);
2685
+ /**
2686
+ * Custom function to handle arrow key navigation for the TabBar for each TabItem.
2687
+ * This implementation also circular in that if the user goes past the end of
2688
+ * the list they will go back to the beginning and vise versa.
2689
+ * This is the recommended pattern per WCAG implementation:
2690
+ * https://www.w3.org/WAI/ARIA/apg/patterns/tabs/examples/tabs-manual/
2691
+ * @param e - onKeyDown event data.
2692
+ */
2693
+ const onArrowKeyFocus = e => {
2694
+ if (e.keyCode === 39) {
2695
+ // Right arrow
2696
+ setFocus(focus === items.length - 1 ? 0 : focus + 1);
2697
+ } else if (e.keyCode === 37) {
2698
+ // Left arrow
2699
+ setFocus(focus === 0 ? items.length - 1 : focus - 1);
2700
+ }
2701
+ };
2659
2702
  return /*#__PURE__*/React.createElement(View$1, {
2660
- style: [styles$5.tabbar, style],
2661
- role: "tablist"
2662
- }, /*#__PURE__*/React.createElement(View$1, {
2663
- style: [styles$5.pages]
2664
- }, items.map(item => /*#__PURE__*/React.createElement(TabbarItem, {
2703
+ style: [styles$5.tabbar, style]
2704
+ }, items.length > 0 && /*#__PURE__*/React.createElement(View$1, {
2705
+ style: [styles$5.pages],
2706
+ role: "tablist",
2707
+ onKeyDown: onArrowKeyFocus
2708
+ }, items.map((item, index) => /*#__PURE__*/React.createElement(TabbarItem, {
2709
+ role: "tab",
2665
2710
  key: `tabbar-item-${item}`,
2666
2711
  itemState: item === selectedItem ? "active" : "inactive",
2667
2712
  itemType: item,
2713
+ focus: focus === index,
2668
2714
  onClick: () => {
2669
2715
  onSelectItem(item);
2670
2716
  }
2671
2717
  }))), /*#__PURE__*/React.createElement(View$1, null, onClickClose && /*#__PURE__*/React.createElement(TabbarItem, {
2718
+ role: "button",
2672
2719
  itemState: "inactive",
2673
2720
  itemType: "Dismiss",
2674
2721
  onClick: onClickClose
@@ -5282,7 +5329,6 @@ function Keypad(props) {
5282
5329
  style: styles$1.keypadInnerContainer
5283
5330
  }, /*#__PURE__*/React.createElement(View$1, {
5284
5331
  style: [styles$1.keypadGrid, gridStyle],
5285
- tabIndex: 0,
5286
5332
  "aria-label": "Keypad"
5287
5333
  }, selectedPage === "Fractions" && /*#__PURE__*/React.createElement(FractionsPage, {
5288
5334
  onClickKey: onClickKey,