@khanacademy/math-input 0.4.1 → 0.5.2
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/CHANGELOG.md +20 -0
- package/README.md +1 -1
- package/{build/math-input.css → dist/es/index.css} +0 -150
- package/dist/es/index.js +7798 -0
- package/dist/es/index.js.map +1 -0
- package/dist/index.css +586 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7768 -0
- package/dist/index.js.flow +2 -0
- package/dist/index.js.map +1 -0
- package/dist/strings.js +71 -0
- package/index.html +20 -0
- package/less/echo.less +56 -0
- package/less/main.less +5 -0
- package/less/overrides.less +129 -0
- package/less/popover.less +22 -0
- package/less/tabbar.less +6 -0
- package/package.json +60 -89
- package/src/actions/index.js +57 -0
- package/src/components/__tests__/gesture-state-machine_test.js +437 -0
- package/src/components/__tests__/node-manager_test.js +89 -0
- package/src/components/__tests__/two-page-keypad_test.js +42 -0
- package/src/components/app.js +73 -0
- package/src/components/common-style.js +47 -0
- package/src/components/compute-layout-parameters.js +157 -0
- package/src/components/corner-decal.js +56 -0
- package/src/components/echo-manager.js +160 -0
- package/src/components/empty-keypad-button.js +49 -0
- package/src/components/expression-keypad.js +323 -0
- package/src/components/fraction-keypad.js +176 -0
- package/src/components/gesture-manager.js +226 -0
- package/src/components/gesture-state-machine.js +283 -0
- package/src/components/icon.js +74 -0
- package/src/components/iconography/arrow.js +22 -0
- package/src/components/iconography/backspace.js +29 -0
- package/src/components/iconography/cdot.js +29 -0
- package/src/components/iconography/cos.js +30 -0
- package/src/components/iconography/cube-root.js +36 -0
- package/src/components/iconography/dismiss.js +25 -0
- package/src/components/iconography/divide.js +34 -0
- package/src/components/iconography/down.js +16 -0
- package/src/components/iconography/equal.js +33 -0
- package/src/components/iconography/exp-2.js +29 -0
- package/src/components/iconography/exp-3.js +29 -0
- package/src/components/iconography/exp.js +29 -0
- package/src/components/iconography/frac.js +44 -0
- package/src/components/iconography/geq.js +33 -0
- package/src/components/iconography/gt.js +33 -0
- package/src/components/iconography/index.js +45 -0
- package/src/components/iconography/jump-into-numerator.js +41 -0
- package/src/components/iconography/jump-out-base.js +30 -0
- package/src/components/iconography/jump-out-denominator.js +41 -0
- package/src/components/iconography/jump-out-exponent.js +30 -0
- package/src/components/iconography/jump-out-numerator.js +41 -0
- package/src/components/iconography/jump-out-parentheses.js +33 -0
- package/src/components/iconography/left-paren.js +33 -0
- package/src/components/iconography/left.js +16 -0
- package/src/components/iconography/leq.js +33 -0
- package/src/components/iconography/ln.js +29 -0
- package/src/components/iconography/log-n.js +29 -0
- package/src/components/iconography/log.js +29 -0
- package/src/components/iconography/lt.js +33 -0
- package/src/components/iconography/minus.js +32 -0
- package/src/components/iconography/neq.js +33 -0
- package/src/components/iconography/parens.js +33 -0
- package/src/components/iconography/percent.js +49 -0
- package/src/components/iconography/period.js +26 -0
- package/src/components/iconography/plus.js +32 -0
- package/src/components/iconography/radical.js +36 -0
- package/src/components/iconography/right-paren.js +33 -0
- package/src/components/iconography/right.js +16 -0
- package/src/components/iconography/sin.js +30 -0
- package/src/components/iconography/sqrt.js +32 -0
- package/src/components/iconography/tan.js +30 -0
- package/src/components/iconography/times.js +33 -0
- package/src/components/iconography/up.js +16 -0
- package/src/components/input/__tests__/context-tracking_test.js +177 -0
- package/src/components/input/__tests__/math-wrapper.jsx +33 -0
- package/src/components/input/__tests__/mathquill_test.js +747 -0
- package/src/components/input/cursor-contexts.js +29 -0
- package/src/components/input/cursor-handle.js +137 -0
- package/src/components/input/drag-listener.js +75 -0
- package/src/components/input/math-input.js +924 -0
- package/src/components/input/math-wrapper.js +959 -0
- package/src/components/input/scroll-into-view.js +72 -0
- package/src/components/keypad/button-assets.js +492 -0
- package/src/components/keypad/button.js +106 -0
- package/src/components/keypad/button.stories.js +29 -0
- package/src/components/keypad/index.js +64 -0
- package/src/components/keypad/keypad-page-items.js +106 -0
- package/src/components/keypad/keypad-pages.stories.js +32 -0
- package/src/components/keypad/keypad.stories.js +35 -0
- package/src/components/keypad/numeric-input-page.js +100 -0
- package/src/components/keypad/pre-algebra-page.js +98 -0
- package/src/components/keypad/trigonometry-page.js +90 -0
- package/src/components/keypad-button.js +366 -0
- package/src/components/keypad-container.js +303 -0
- package/src/components/keypad.js +154 -0
- package/src/components/many-keypad-button.js +44 -0
- package/src/components/math-icon.js +65 -0
- package/src/components/multi-symbol-grid.js +182 -0
- package/src/components/multi-symbol-popover.js +59 -0
- package/src/components/navigation-pad.js +139 -0
- package/src/components/node-manager.js +129 -0
- package/src/components/popover-manager.js +76 -0
- package/src/components/popover-state-machine.js +173 -0
- package/src/components/prop-types.js +82 -0
- package/src/components/provided-keypad.js +103 -0
- package/src/components/styles.js +38 -0
- package/src/components/svg-icon.js +25 -0
- package/src/components/tabbar/__tests__/tabbar_test.js +65 -0
- package/src/components/tabbar/icons.js +69 -0
- package/src/components/tabbar/item.js +138 -0
- package/src/components/tabbar/tabbar.js +61 -0
- package/src/components/tabbar/tabbar.stories.js +60 -0
- package/src/components/tabbar/types.js +3 -0
- package/src/components/text-icon.js +52 -0
- package/src/components/touchable-keypad-button.js +146 -0
- package/src/components/two-page-keypad.js +99 -0
- package/src/components/velocity-tracker.js +76 -0
- package/src/components/z-indexes.js +9 -0
- package/src/consts.js +74 -0
- package/src/data/key-configs.js +349 -0
- package/src/data/keys.js +72 -0
- package/src/demo.js +8 -0
- package/src/fake-react-native-web/index.js +12 -0
- package/src/fake-react-native-web/text.js +56 -0
- package/src/fake-react-native-web/view.js +91 -0
- package/src/index.js +14 -0
- package/src/native-app.js +84 -0
- package/src/store/index.js +505 -0
- package/src/utils.js +18 -0
- package/tools/svg-to-react/convert.py +111 -0
- package/tools/svg-to-react/icons/math-keypad-icon-0.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-1.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-2.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-3.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-4.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-5.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-6.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-7.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-8.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-9.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-addition.svg +34 -0
- package/tools/svg-to-react/icons/math-keypad-icon-cos.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-delete.svg +36 -0
- package/tools/svg-to-react/icons/math-keypad-icon-dismiss.svg +36 -0
- package/tools/svg-to-react/icons/math-keypad-icon-division.svg +36 -0
- package/tools/svg-to-react/icons/math-keypad-icon-equals-not.svg +50 -0
- package/tools/svg-to-react/icons/math-keypad-icon-equals.svg +48 -0
- package/tools/svg-to-react/icons/math-keypad-icon-exponent-2.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-exponent-3.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-exponent.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-fraction.svg +42 -0
- package/tools/svg-to-react/icons/math-keypad-icon-greater-than.svg +46 -0
- package/tools/svg-to-react/icons/math-keypad-icon-jump-out-base.svg +44 -0
- package/tools/svg-to-react/icons/math-keypad-icon-jump-out-denominator.svg +48 -0
- package/tools/svg-to-react/icons/math-keypad-icon-jump-out-exponent.svg +44 -0
- package/tools/svg-to-react/icons/math-keypad-icon-jump-out-parentheses.svg +44 -0
- package/tools/svg-to-react/icons/math-keypad-icon-less-than.svg +46 -0
- package/tools/svg-to-react/icons/math-keypad-icon-log-10.svg +36 -0
- package/tools/svg-to-react/icons/math-keypad-icon-log-e.svg +36 -0
- package/tools/svg-to-react/icons/math-keypad-icon-log.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-multiplication-cross.svg +40 -0
- package/tools/svg-to-react/icons/math-keypad-icon-multiplication-dot.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-percent.svg +42 -0
- package/tools/svg-to-react/icons/math-keypad-icon-radical-2.svg +36 -0
- package/tools/svg-to-react/icons/math-keypad-icon-radical-3.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-radical.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-radix-character.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-sin.svg +38 -0
- package/tools/svg-to-react/icons/math-keypad-icon-subtraction.svg +32 -0
- package/tools/svg-to-react/icons/math-keypad-icon-tan.svg +38 -0
- package/tools/svg-to-react/symbol_map.py +41 -0
- package/LICENSE.txt +0 -21
- package/build/math-input.js +0 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A keypad component that acts as a container for rows or columns of buttons,
|
|
3
|
+
* and manages the rendering of echo animations on top of those buttons.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import PropTypes from "prop-types";
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import ReactDOM from "react-dom";
|
|
9
|
+
import {connect} from "react-redux";
|
|
10
|
+
|
|
11
|
+
import {removeEcho} from "../actions/index.js";
|
|
12
|
+
import {View} from "../fake-react-native-web/index.js";
|
|
13
|
+
|
|
14
|
+
import EchoManager from "./echo-manager.js";
|
|
15
|
+
import PopoverManager from "./popover-manager.js";
|
|
16
|
+
import {echoPropType, popoverPropType} from "./prop-types.js";
|
|
17
|
+
|
|
18
|
+
// eslint-disable-next-line react/no-unsafe
|
|
19
|
+
class Keypad extends React.Component {
|
|
20
|
+
static propTypes = {
|
|
21
|
+
children: PropTypes.oneOfType([
|
|
22
|
+
PropTypes.arrayOf(PropTypes.node),
|
|
23
|
+
PropTypes.node,
|
|
24
|
+
]),
|
|
25
|
+
removeEcho: PropTypes.func.isRequired,
|
|
26
|
+
style: PropTypes.any,
|
|
27
|
+
|
|
28
|
+
// The props below are injected by redux
|
|
29
|
+
|
|
30
|
+
// Whether the keypad is active, i.e., whether it should be rendered as
|
|
31
|
+
// visible or invisible.
|
|
32
|
+
active: PropTypes.bool,
|
|
33
|
+
echoes: PropTypes.arrayOf(echoPropType).isRequired,
|
|
34
|
+
popover: popoverPropType,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
componentDidMount() {
|
|
38
|
+
this._isMounted = true;
|
|
39
|
+
|
|
40
|
+
window.addEventListener("resize", this._onResize);
|
|
41
|
+
this._updateSizeAndPosition();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
UNSAFE_componentWillReceiveProps(newProps) {
|
|
45
|
+
if (!this._container && (newProps.popover || newProps.echoes.length)) {
|
|
46
|
+
this._computeContainer();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
componentWillUnmount() {
|
|
51
|
+
this._isMounted = false;
|
|
52
|
+
|
|
53
|
+
window.removeEventListener("resize", this._onResize);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
_computeContainer = () => {
|
|
57
|
+
const domNode = ReactDOM.findDOMNode(this);
|
|
58
|
+
this._container = domNode.getBoundingClientRect();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
_updateSizeAndPosition = () => {
|
|
62
|
+
// Mark the container for recalculation next time the keypad is
|
|
63
|
+
// opened.
|
|
64
|
+
// TODO(charlie): Since we're not recalculating the container
|
|
65
|
+
// immediately, if you were to resize the page while a popover were
|
|
66
|
+
// active, you'd likely get unexpected behavior. This seems very
|
|
67
|
+
// difficult to do and, as such, incredibly unlikely, but we may
|
|
68
|
+
// want to reconsider the caching here.
|
|
69
|
+
this._container = null;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
_onResize = () => {
|
|
73
|
+
// Whenever the page resizes, we need to recompute the container's
|
|
74
|
+
// bounding box. This is the only time that the bounding box can change.
|
|
75
|
+
|
|
76
|
+
// Throttle resize events -- taken from:
|
|
77
|
+
// https://developer.mozilla.org/en-US/docs/Web/Events/resize
|
|
78
|
+
if (this._resizeTimeout == null) {
|
|
79
|
+
this._resizeTimeout = setTimeout(() => {
|
|
80
|
+
this._resizeTimeout = null;
|
|
81
|
+
|
|
82
|
+
if (this._isMounted) {
|
|
83
|
+
this._updateSizeAndPosition();
|
|
84
|
+
}
|
|
85
|
+
}, 66);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
render() {
|
|
90
|
+
const {children, echoes, removeEcho, popover, style} = this.props;
|
|
91
|
+
|
|
92
|
+
// Translate the echo boxes, as they'll be positioned absolutely to
|
|
93
|
+
// this relative container.
|
|
94
|
+
const relativeEchoes = echoes.map((echo) => {
|
|
95
|
+
const {initialBounds, ...rest} = echo;
|
|
96
|
+
return {
|
|
97
|
+
...rest,
|
|
98
|
+
initialBounds: {
|
|
99
|
+
top: initialBounds.top - this._container.top,
|
|
100
|
+
right: initialBounds.right - this._container.left,
|
|
101
|
+
bottom: initialBounds.bottom - this._container.top,
|
|
102
|
+
left: initialBounds.left - this._container.left,
|
|
103
|
+
width: initialBounds.width,
|
|
104
|
+
height: initialBounds.height,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Translate the popover bounds from page-absolute to keypad-relative.
|
|
110
|
+
// Note that we only need three bounds, since popovers are anchored to
|
|
111
|
+
// the bottom left corners of the keys over which they appear.
|
|
112
|
+
const relativePopover = popover && {
|
|
113
|
+
...popover,
|
|
114
|
+
bounds: {
|
|
115
|
+
bottom:
|
|
116
|
+
this._container.height -
|
|
117
|
+
(popover.bounds.bottom - this._container.top),
|
|
118
|
+
left: popover.bounds.left - this._container.left,
|
|
119
|
+
width: popover.bounds.width,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<View style={style}>
|
|
125
|
+
{children}
|
|
126
|
+
<EchoManager
|
|
127
|
+
echoes={relativeEchoes}
|
|
128
|
+
onAnimationFinish={removeEcho}
|
|
129
|
+
/>
|
|
130
|
+
<PopoverManager popover={relativePopover} />
|
|
131
|
+
</View>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const mapStateToProps = (state) => {
|
|
137
|
+
return {
|
|
138
|
+
...state.echoes,
|
|
139
|
+
active: state.keypad.active,
|
|
140
|
+
popover: state.gestures.popover,
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const mapDispatchToProps = (dispatch) => {
|
|
145
|
+
return {
|
|
146
|
+
removeEcho: (animationId) => {
|
|
147
|
+
dispatch(removeEcho(animationId));
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export default connect(mapStateToProps, mapDispatchToProps, null, {
|
|
153
|
+
forwardRef: true,
|
|
154
|
+
})(Keypad);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A keypad button that displays an arbitrary number of symbols, with no
|
|
3
|
+
* 'default' symbol.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import PropTypes from "prop-types";
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
|
|
9
|
+
import {KeyTypes} from "../consts.js";
|
|
10
|
+
import KeyConfigs from "../data/key-configs.js";
|
|
11
|
+
import Keys from "../data/keys.js";
|
|
12
|
+
|
|
13
|
+
import EmptyKeypadButton from "./empty-keypad-button.js";
|
|
14
|
+
import {keyIdPropType} from "./prop-types.js";
|
|
15
|
+
import TouchableKeypadButton from "./touchable-keypad-button.js";
|
|
16
|
+
|
|
17
|
+
class ManyKeypadButton extends React.Component {
|
|
18
|
+
static propTypes = {
|
|
19
|
+
keys: PropTypes.arrayOf(keyIdPropType).isRequired,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
render() {
|
|
23
|
+
const {keys, ...rest} = this.props;
|
|
24
|
+
|
|
25
|
+
// If we have no extra symbols, render an empty button. If we have just
|
|
26
|
+
// one, render a standard button. Otherwise, capture them all in a
|
|
27
|
+
// single button.
|
|
28
|
+
if (keys.length === 0) {
|
|
29
|
+
return <EmptyKeypadButton {...rest} />;
|
|
30
|
+
} else if (keys.length === 1) {
|
|
31
|
+
const keyConfig = KeyConfigs[keys[0]];
|
|
32
|
+
return <TouchableKeypadButton keyConfig={keyConfig} {...rest} />;
|
|
33
|
+
} else {
|
|
34
|
+
const keyConfig = {
|
|
35
|
+
id: Keys.MANY,
|
|
36
|
+
type: KeyTypes.MANY,
|
|
37
|
+
childKeyIds: keys,
|
|
38
|
+
};
|
|
39
|
+
return <TouchableKeypadButton keyConfig={keyConfig} {...rest} />;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default ManyKeypadButton;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A component that renders an icon with math (via KaTeX).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {StyleSheet} from "aphrodite";
|
|
6
|
+
import katex from "katex";
|
|
7
|
+
import PropTypes from "prop-types";
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import ReactDOM from "react-dom";
|
|
10
|
+
|
|
11
|
+
import {View} from "../fake-react-native-web/index.js";
|
|
12
|
+
|
|
13
|
+
import {iconSizeHeightPx, iconSizeWidthPx} from "./common-style.js";
|
|
14
|
+
import Styles from "./styles.js";
|
|
15
|
+
|
|
16
|
+
const {row, centered} = Styles;
|
|
17
|
+
|
|
18
|
+
class MathIcon extends React.Component {
|
|
19
|
+
static propTypes = {
|
|
20
|
+
math: PropTypes.string.isRequired,
|
|
21
|
+
style: PropTypes.any,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
componentDidMount() {
|
|
25
|
+
this._renderMath();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
componentDidUpdate(prevProps) {
|
|
29
|
+
if (prevProps.math !== this.props.math) {
|
|
30
|
+
this._renderMath();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_renderMath = () => {
|
|
35
|
+
const {math} = this.props;
|
|
36
|
+
katex.render(math, ReactDOM.findDOMNode(this));
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
render() {
|
|
40
|
+
const {style} = this.props;
|
|
41
|
+
|
|
42
|
+
const containerStyle = [
|
|
43
|
+
row,
|
|
44
|
+
centered,
|
|
45
|
+
styles.size,
|
|
46
|
+
styles.base,
|
|
47
|
+
...(Array.isArray(style) ? style : [style]),
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
return <View style={containerStyle} />;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const styles = StyleSheet.create({
|
|
55
|
+
size: {
|
|
56
|
+
height: iconSizeHeightPx,
|
|
57
|
+
width: iconSizeWidthPx,
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
base: {
|
|
61
|
+
fontSize: 25,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export default MathIcon;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A grid of symbols, rendered as text and positioned based on the number of
|
|
3
|
+
* symbols provided. Up to four symbols will be shown.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {StyleSheet} from "aphrodite";
|
|
7
|
+
import PropTypes from "prop-types";
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
|
|
10
|
+
import {IconTypes} from "../consts.js";
|
|
11
|
+
import {View} from "../fake-react-native-web/index.js";
|
|
12
|
+
|
|
13
|
+
import {iconSizeHeightPx, iconSizeWidthPx} from "./common-style.js";
|
|
14
|
+
import Icon from "./icon.js";
|
|
15
|
+
import {iconPropType} from "./prop-types.js";
|
|
16
|
+
import Styles from "./styles.js";
|
|
17
|
+
|
|
18
|
+
const {row, column, centered, fullWidth} = Styles;
|
|
19
|
+
|
|
20
|
+
class MultiSymbolGrid extends React.Component {
|
|
21
|
+
static propTypes = {
|
|
22
|
+
focused: PropTypes.bool,
|
|
23
|
+
icons: PropTypes.arrayOf(iconPropType).isRequired,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
render() {
|
|
27
|
+
const {focused, icons} = this.props;
|
|
28
|
+
|
|
29
|
+
// Validate that we only received math-based icons. Right now, this
|
|
30
|
+
// component only supports math icons (and it should only be passed
|
|
31
|
+
// variables and Greek letters, which are always rendered as math).
|
|
32
|
+
// Supporting other types of icons is possible but would require
|
|
33
|
+
// some styles coercion and doesn't seem worthwhile right now.
|
|
34
|
+
icons.forEach((icon) => {
|
|
35
|
+
if (icon.type !== IconTypes.MATH) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Received invalid icon: type=${icon.type}, ` +
|
|
38
|
+
`data=${icon.data}`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (icons.length === 1) {
|
|
44
|
+
return <Icon icon={icons[0]} focused={focused} />;
|
|
45
|
+
} else {
|
|
46
|
+
const primaryIconStyle = styles.base;
|
|
47
|
+
const secondaryIconStyle = [styles.base, styles.secondary];
|
|
48
|
+
|
|
49
|
+
if (icons.length === 2) {
|
|
50
|
+
return (
|
|
51
|
+
<View style={[row, styles.size]}>
|
|
52
|
+
<View
|
|
53
|
+
style={[
|
|
54
|
+
column,
|
|
55
|
+
centered,
|
|
56
|
+
fullWidth,
|
|
57
|
+
styles.middleLeft,
|
|
58
|
+
]}
|
|
59
|
+
>
|
|
60
|
+
<Icon
|
|
61
|
+
style={primaryIconStyle}
|
|
62
|
+
icon={icons[0]}
|
|
63
|
+
focused={focused}
|
|
64
|
+
/>
|
|
65
|
+
</View>
|
|
66
|
+
<View
|
|
67
|
+
style={[
|
|
68
|
+
column,
|
|
69
|
+
centered,
|
|
70
|
+
fullWidth,
|
|
71
|
+
styles.middleRight,
|
|
72
|
+
]}
|
|
73
|
+
>
|
|
74
|
+
<Icon
|
|
75
|
+
style={secondaryIconStyle}
|
|
76
|
+
icon={icons[1]}
|
|
77
|
+
focused={focused}
|
|
78
|
+
/>
|
|
79
|
+
</View>
|
|
80
|
+
</View>
|
|
81
|
+
);
|
|
82
|
+
} else if (icons.length >= 3) {
|
|
83
|
+
return (
|
|
84
|
+
<View style={[column, styles.size]}>
|
|
85
|
+
<View style={row}>
|
|
86
|
+
<View style={[centered, fullWidth, styles.topLeft]}>
|
|
87
|
+
<Icon
|
|
88
|
+
style={primaryIconStyle}
|
|
89
|
+
icon={icons[0]}
|
|
90
|
+
focused={focused}
|
|
91
|
+
/>
|
|
92
|
+
</View>
|
|
93
|
+
<View
|
|
94
|
+
style={[centered, fullWidth, styles.topRight]}
|
|
95
|
+
>
|
|
96
|
+
<Icon
|
|
97
|
+
style={secondaryIconStyle}
|
|
98
|
+
icon={icons[1]}
|
|
99
|
+
focused={focused}
|
|
100
|
+
/>
|
|
101
|
+
</View>
|
|
102
|
+
</View>
|
|
103
|
+
<View style={row}>
|
|
104
|
+
<View
|
|
105
|
+
style={[centered, fullWidth, styles.bottomLeft]}
|
|
106
|
+
>
|
|
107
|
+
<Icon
|
|
108
|
+
style={secondaryIconStyle}
|
|
109
|
+
icon={icons[2]}
|
|
110
|
+
focused={focused}
|
|
111
|
+
/>
|
|
112
|
+
</View>
|
|
113
|
+
<View
|
|
114
|
+
style={[
|
|
115
|
+
centered,
|
|
116
|
+
fullWidth,
|
|
117
|
+
styles.bottomRight,
|
|
118
|
+
]}
|
|
119
|
+
>
|
|
120
|
+
{icons[3] && (
|
|
121
|
+
<Icon
|
|
122
|
+
style={secondaryIconStyle}
|
|
123
|
+
icon={icons[3]}
|
|
124
|
+
focused={focused}
|
|
125
|
+
/>
|
|
126
|
+
)}
|
|
127
|
+
</View>
|
|
128
|
+
</View>
|
|
129
|
+
</View>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
throw new Error("Invalid number of icons:", icons.length);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const verticalInsetPx = 2;
|
|
139
|
+
const horizontalInsetPx = 4;
|
|
140
|
+
|
|
141
|
+
const styles = StyleSheet.create({
|
|
142
|
+
size: {
|
|
143
|
+
height: iconSizeHeightPx,
|
|
144
|
+
width: iconSizeWidthPx,
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// For the three- and four-icon layouts.
|
|
148
|
+
bottomLeft: {
|
|
149
|
+
marginBottom: verticalInsetPx,
|
|
150
|
+
marginLeft: horizontalInsetPx,
|
|
151
|
+
},
|
|
152
|
+
topLeft: {
|
|
153
|
+
marginTop: verticalInsetPx,
|
|
154
|
+
marginLeft: horizontalInsetPx,
|
|
155
|
+
},
|
|
156
|
+
topRight: {
|
|
157
|
+
marginTop: verticalInsetPx,
|
|
158
|
+
marginRight: horizontalInsetPx,
|
|
159
|
+
},
|
|
160
|
+
bottomRight: {
|
|
161
|
+
marginBottom: verticalInsetPx,
|
|
162
|
+
marginRight: horizontalInsetPx,
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// For the two-icon layout.
|
|
166
|
+
middleLeft: {
|
|
167
|
+
marginLeft: horizontalInsetPx,
|
|
168
|
+
},
|
|
169
|
+
middleRight: {
|
|
170
|
+
marginRight: horizontalInsetPx,
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
base: {
|
|
174
|
+
fontSize: 18,
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
secondary: {
|
|
178
|
+
opacity: 0.3,
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
export default MultiSymbolGrid;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A popover that renders a set of keys floating above the page.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {StyleSheet} from "aphrodite";
|
|
6
|
+
import PropTypes from "prop-types";
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
|
|
9
|
+
import {BorderStyles} from "../consts.js";
|
|
10
|
+
import {View} from "../fake-react-native-web/index.js";
|
|
11
|
+
|
|
12
|
+
import {keyConfigPropType} from "./prop-types.js";
|
|
13
|
+
import TouchableKeypadButton from "./touchable-keypad-button.js";
|
|
14
|
+
import * as zIndexes from "./z-indexes.js";
|
|
15
|
+
|
|
16
|
+
class MultiSymbolPopover extends React.Component {
|
|
17
|
+
static propTypes = {
|
|
18
|
+
keys: PropTypes.arrayOf(keyConfigPropType),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
render() {
|
|
22
|
+
const {keys} = this.props;
|
|
23
|
+
|
|
24
|
+
// TODO(charlie): We have to require this lazily because of a cyclic
|
|
25
|
+
// dependence in our components.
|
|
26
|
+
return (
|
|
27
|
+
<View style={styles.container}>
|
|
28
|
+
{keys.map((key) => {
|
|
29
|
+
return (
|
|
30
|
+
<TouchableKeypadButton
|
|
31
|
+
key={key.id}
|
|
32
|
+
keyConfig={key}
|
|
33
|
+
borders={BorderStyles.NONE}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
})}
|
|
37
|
+
</View>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const styles = StyleSheet.create({
|
|
43
|
+
container: {
|
|
44
|
+
flexDirection: "column-reverse",
|
|
45
|
+
position: "relative",
|
|
46
|
+
width: "100%",
|
|
47
|
+
borderRadius: 2,
|
|
48
|
+
boxShadow: "0 2px 6px rgba(0, 0, 0, 0.3)",
|
|
49
|
+
zIndex: zIndexes.popover,
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// eslint-disable-next-line react-native/no-unused-styles
|
|
53
|
+
popoverButton: {
|
|
54
|
+
backgroundColor: "#FFF",
|
|
55
|
+
borderWidth: 0,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export default MultiSymbolPopover;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A component that renders a navigation pad, which consists of an arrow for
|
|
3
|
+
* each possible direction.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {StyleSheet} from "aphrodite";
|
|
7
|
+
import PropTypes from "prop-types";
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
|
|
10
|
+
import {BorderStyles} from "../consts.js";
|
|
11
|
+
import KeyConfigs from "../data/key-configs.js";
|
|
12
|
+
import {View} from "../fake-react-native-web/index.js";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
navigationPadWidthPx,
|
|
16
|
+
controlGrey,
|
|
17
|
+
valueGrey,
|
|
18
|
+
offBlack16,
|
|
19
|
+
} from "./common-style.js";
|
|
20
|
+
import Styles from "./styles.js";
|
|
21
|
+
import TouchableKeypadButton from "./touchable-keypad-button.js";
|
|
22
|
+
|
|
23
|
+
const {row, column, centered, stretch, roundedTopLeft} = Styles;
|
|
24
|
+
|
|
25
|
+
class NavigationPad extends React.Component {
|
|
26
|
+
static propTypes = {
|
|
27
|
+
roundTopLeft: PropTypes.bool,
|
|
28
|
+
style: PropTypes.any,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
render() {
|
|
32
|
+
// TODO(charlie): Disable the navigational arrows depending on the
|
|
33
|
+
// cursor context.
|
|
34
|
+
const {roundTopLeft, style} = this.props;
|
|
35
|
+
|
|
36
|
+
const containerStyle = [
|
|
37
|
+
column,
|
|
38
|
+
centered,
|
|
39
|
+
styles.container,
|
|
40
|
+
roundTopLeft && roundedTopLeft,
|
|
41
|
+
...(Array.isArray(style) ? style : [style]),
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<View style={containerStyle}>
|
|
46
|
+
<View style={[row, centered]}>
|
|
47
|
+
<TouchableKeypadButton
|
|
48
|
+
keyConfig={KeyConfigs.UP}
|
|
49
|
+
borders={BorderStyles.NONE}
|
|
50
|
+
style={[styles.navigationKey, styles.topArrow]}
|
|
51
|
+
/>
|
|
52
|
+
</View>
|
|
53
|
+
<View style={[row, centered, stretch]}>
|
|
54
|
+
<TouchableKeypadButton
|
|
55
|
+
keyConfig={KeyConfigs.LEFT}
|
|
56
|
+
borders={BorderStyles.NONE}
|
|
57
|
+
style={[styles.navigationKey, styles.leftArrow]}
|
|
58
|
+
/>
|
|
59
|
+
<View style={styles.horizontalSpacer} />
|
|
60
|
+
<TouchableKeypadButton
|
|
61
|
+
keyConfig={KeyConfigs.RIGHT}
|
|
62
|
+
borders={BorderStyles.NONE}
|
|
63
|
+
style={[styles.navigationKey, styles.rightArrow]}
|
|
64
|
+
/>
|
|
65
|
+
</View>
|
|
66
|
+
<View style={[row, centered]}>
|
|
67
|
+
<TouchableKeypadButton
|
|
68
|
+
keyConfig={KeyConfigs.DOWN}
|
|
69
|
+
borders={BorderStyles.NONE}
|
|
70
|
+
style={[styles.navigationKey, styles.bottomArrow]}
|
|
71
|
+
/>
|
|
72
|
+
</View>
|
|
73
|
+
</View>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const buttonSizePx = 48;
|
|
79
|
+
const borderRadiusPx = 4;
|
|
80
|
+
const borderWidthPx = 1;
|
|
81
|
+
|
|
82
|
+
const styles = StyleSheet.create({
|
|
83
|
+
container: {
|
|
84
|
+
backgroundColor: controlGrey,
|
|
85
|
+
width: navigationPadWidthPx,
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
navigationKey: {
|
|
89
|
+
borderColor: offBlack16,
|
|
90
|
+
backgroundColor: valueGrey,
|
|
91
|
+
width: buttonSizePx,
|
|
92
|
+
height: buttonSizePx,
|
|
93
|
+
|
|
94
|
+
// Override the default box-sizing so that our buttons are
|
|
95
|
+
// `buttonSizePx` exclusive of their borders.
|
|
96
|
+
boxSizing: "content-box",
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
topArrow: {
|
|
100
|
+
borderTopWidth: borderWidthPx,
|
|
101
|
+
borderLeftWidth: borderWidthPx,
|
|
102
|
+
borderRightWidth: borderWidthPx,
|
|
103
|
+
borderTopLeftRadius: borderRadiusPx,
|
|
104
|
+
borderTopRightRadius: borderRadiusPx,
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
rightArrow: {
|
|
108
|
+
borderTopWidth: borderWidthPx,
|
|
109
|
+
borderRightWidth: borderWidthPx,
|
|
110
|
+
borderBottomWidth: borderWidthPx,
|
|
111
|
+
borderTopRightRadius: borderRadiusPx,
|
|
112
|
+
borderBottomRightRadius: borderRadiusPx,
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
bottomArrow: {
|
|
116
|
+
borderBottomWidth: borderWidthPx,
|
|
117
|
+
borderLeftWidth: borderWidthPx,
|
|
118
|
+
borderRightWidth: borderWidthPx,
|
|
119
|
+
borderBottomLeftRadius: borderRadiusPx,
|
|
120
|
+
borderBottomRightRadius: borderRadiusPx,
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
leftArrow: {
|
|
124
|
+
borderTopWidth: borderWidthPx,
|
|
125
|
+
borderBottomWidth: borderWidthPx,
|
|
126
|
+
borderLeftWidth: borderWidthPx,
|
|
127
|
+
borderTopLeftRadius: borderRadiusPx,
|
|
128
|
+
borderBottomLeftRadius: borderRadiusPx,
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
horizontalSpacer: {
|
|
132
|
+
background: valueGrey,
|
|
133
|
+
// No need to set a height -- the spacer will be stretched by its
|
|
134
|
+
// parent.
|
|
135
|
+
width: buttonSizePx,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
export default NavigationPad;
|