@khanacademy/math-input 15.1.0 → 16.0.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/CHANGELOG.md +11 -0
- package/dist/components/keypad/index.d.ts +1 -1
- package/dist/components/keypad/mobile-keypad-internals.d.ts +49 -0
- package/dist/components/keypad/mobile-keypad.d.ts +4 -48
- package/dist/es/index.js +168 -4740
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +166 -4722
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/integration.test.tsx +2 -3
- package/src/components/keypad/__tests__/mobile-keypad.test.tsx +8 -8
- package/src/components/keypad/index.tsx +1 -1
- package/src/components/keypad/mobile-keypad-internals.tsx +240 -0
- package/src/components/keypad/mobile-keypad.tsx +21 -234
- package/src/full-mobile-input.stories.tsx +0 -1
- package/src/index.ts +1 -1
- package/tsconfig-build.tsbuildinfo +1 -1
- package/dist/components/keypad-legacy/compute-layout-parameters.d.ts +0 -28
- package/dist/components/keypad-legacy/corner-decal.d.ts +0 -12
- package/dist/components/keypad-legacy/echo-manager.d.ts +0 -17
- package/dist/components/keypad-legacy/empty-keypad-button.d.ts +0 -13
- package/dist/components/keypad-legacy/expression-keypad.d.ts +0 -21
- package/dist/components/keypad-legacy/fraction-keypad.d.ts +0 -21
- package/dist/components/keypad-legacy/gesture-manager.d.ts +0 -86
- package/dist/components/keypad-legacy/gesture-state-machine.d.ts +0 -105
- package/dist/components/keypad-legacy/icon.d.ts +0 -15
- package/dist/components/keypad-legacy/index.d.ts +0 -1
- package/dist/components/keypad-legacy/keypad-button.d.ts +0 -53
- package/dist/components/keypad-legacy/keypad-container.d.ts +0 -41
- package/dist/components/keypad-legacy/keypad.d.ts +0 -31
- package/dist/components/keypad-legacy/many-keypad-button.d.ts +0 -15
- package/dist/components/keypad-legacy/math-icon.d.ts +0 -16
- package/dist/components/keypad-legacy/multi-symbol-grid.d.ts +0 -14
- package/dist/components/keypad-legacy/multi-symbol-popover.d.ts +0 -12
- package/dist/components/keypad-legacy/navigation-pad.d.ts +0 -14
- package/dist/components/keypad-legacy/node-manager.d.ts +0 -49
- package/dist/components/keypad-legacy/popover-manager.d.ts +0 -13
- package/dist/components/keypad-legacy/popover-state-machine.d.ts +0 -68
- package/dist/components/keypad-legacy/provided-keypad.d.ts +0 -28
- package/dist/components/keypad-legacy/store/actions.d.ts +0 -55
- package/dist/components/keypad-legacy/store/echo-reducer.d.ts +0 -4
- package/dist/components/keypad-legacy/store/index.d.ts +0 -9
- package/dist/components/keypad-legacy/store/input-reducer.d.ts +0 -4
- package/dist/components/keypad-legacy/store/keypad-reducer.d.ts +0 -4
- package/dist/components/keypad-legacy/store/layout-reducer.d.ts +0 -4
- package/dist/components/keypad-legacy/store/shared.d.ts +0 -7
- package/dist/components/keypad-legacy/store/types.d.ts +0 -47
- package/dist/components/keypad-legacy/styles.d.ts +0 -5
- package/dist/components/keypad-legacy/svg-icon.d.ts +0 -12
- package/dist/components/keypad-legacy/text-icon.d.ts +0 -13
- package/dist/components/keypad-legacy/touchable-keypad-button.d.ts +0 -37
- package/dist/components/keypad-legacy/two-page-keypad.d.ts +0 -21
- package/dist/components/keypad-legacy/z-indexes.d.ts +0 -7
- package/dist/components/keypad-switch.d.ts +0 -12
- package/src/components/keypad-legacy/__tests__/gesture-state-machine.test.ts +0 -441
- package/src/components/keypad-legacy/__tests__/node-manager.test.ts +0 -89
- package/src/components/keypad-legacy/compute-layout-parameters.ts +0 -205
- package/src/components/keypad-legacy/corner-decal.tsx +0 -56
- package/src/components/keypad-legacy/echo-manager.tsx +0 -152
- package/src/components/keypad-legacy/empty-keypad-button.tsx +0 -58
- package/src/components/keypad-legacy/expression-keypad.tsx +0 -315
- package/src/components/keypad-legacy/fraction-keypad.tsx +0 -180
- package/src/components/keypad-legacy/gesture-manager.ts +0 -255
- package/src/components/keypad-legacy/gesture-state-machine.ts +0 -329
- package/src/components/keypad-legacy/icon.tsx +0 -72
- package/src/components/keypad-legacy/iconography/arrow.js +0 -22
- package/src/components/keypad-legacy/iconography/backspace.js +0 -29
- package/src/components/keypad-legacy/iconography/cdot.js +0 -29
- package/src/components/keypad-legacy/iconography/cos.js +0 -30
- package/src/components/keypad-legacy/iconography/cube-root.js +0 -36
- package/src/components/keypad-legacy/iconography/dismiss.js +0 -25
- package/src/components/keypad-legacy/iconography/divide.js +0 -34
- package/src/components/keypad-legacy/iconography/down.js +0 -16
- package/src/components/keypad-legacy/iconography/equal.js +0 -33
- package/src/components/keypad-legacy/iconography/exp-2.js +0 -29
- package/src/components/keypad-legacy/iconography/exp-3.js +0 -29
- package/src/components/keypad-legacy/iconography/exp.js +0 -29
- package/src/components/keypad-legacy/iconography/frac.js +0 -44
- package/src/components/keypad-legacy/iconography/geq.js +0 -33
- package/src/components/keypad-legacy/iconography/gt.js +0 -33
- package/src/components/keypad-legacy/iconography/index.js +0 -45
- package/src/components/keypad-legacy/iconography/jump-into-numerator.js +0 -41
- package/src/components/keypad-legacy/iconography/jump-out-base.js +0 -30
- package/src/components/keypad-legacy/iconography/jump-out-denominator.js +0 -41
- package/src/components/keypad-legacy/iconography/jump-out-exponent.js +0 -30
- package/src/components/keypad-legacy/iconography/jump-out-numerator.js +0 -41
- package/src/components/keypad-legacy/iconography/jump-out-parentheses.js +0 -33
- package/src/components/keypad-legacy/iconography/left-paren.js +0 -33
- package/src/components/keypad-legacy/iconography/left.js +0 -16
- package/src/components/keypad-legacy/iconography/leq.js +0 -33
- package/src/components/keypad-legacy/iconography/ln.js +0 -29
- package/src/components/keypad-legacy/iconography/log-n.js +0 -29
- package/src/components/keypad-legacy/iconography/log.js +0 -29
- package/src/components/keypad-legacy/iconography/lt.js +0 -33
- package/src/components/keypad-legacy/iconography/minus.js +0 -32
- package/src/components/keypad-legacy/iconography/neq.js +0 -33
- package/src/components/keypad-legacy/iconography/parens.js +0 -33
- package/src/components/keypad-legacy/iconography/percent.js +0 -49
- package/src/components/keypad-legacy/iconography/period.js +0 -26
- package/src/components/keypad-legacy/iconography/plus.js +0 -32
- package/src/components/keypad-legacy/iconography/radical.js +0 -36
- package/src/components/keypad-legacy/iconography/right-paren.js +0 -33
- package/src/components/keypad-legacy/iconography/right.js +0 -16
- package/src/components/keypad-legacy/iconography/sin.js +0 -30
- package/src/components/keypad-legacy/iconography/sqrt.js +0 -32
- package/src/components/keypad-legacy/iconography/tan.js +0 -30
- package/src/components/keypad-legacy/iconography/times.js +0 -33
- package/src/components/keypad-legacy/iconography/up.js +0 -16
- package/src/components/keypad-legacy/index.ts +0 -1
- package/src/components/keypad-legacy/keypad-button.tsx +0 -368
- package/src/components/keypad-legacy/keypad-container.tsx +0 -358
- package/src/components/keypad-legacy/keypad.tsx +0 -162
- package/src/components/keypad-legacy/many-keypad-button.tsx +0 -54
- package/src/components/keypad-legacy/math-icon.tsx +0 -66
- package/src/components/keypad-legacy/multi-symbol-grid.tsx +0 -182
- package/src/components/keypad-legacy/multi-symbol-popover.tsx +0 -58
- package/src/components/keypad-legacy/navigation-pad.tsx +0 -140
- package/src/components/keypad-legacy/node-manager.ts +0 -133
- package/src/components/keypad-legacy/popover-manager.tsx +0 -73
- package/src/components/keypad-legacy/popover-state-machine.ts +0 -184
- package/src/components/keypad-legacy/provided-keypad.tsx +0 -136
- package/src/components/keypad-legacy/store/actions.ts +0 -155
- package/src/components/keypad-legacy/store/echo-reducer.ts +0 -57
- package/src/components/keypad-legacy/store/index.ts +0 -110
- package/src/components/keypad-legacy/store/input-reducer.ts +0 -55
- package/src/components/keypad-legacy/store/keypad-reducer.ts +0 -58
- package/src/components/keypad-legacy/store/layout-reducer.test.ts +0 -171
- package/src/components/keypad-legacy/store/layout-reducer.ts +0 -129
- package/src/components/keypad-legacy/store/shared.ts +0 -12
- package/src/components/keypad-legacy/store/types.ts +0 -78
- package/src/components/keypad-legacy/styles.ts +0 -38
- package/src/components/keypad-legacy/svg-icon.tsx +0 -24
- package/src/components/keypad-legacy/text-icon.tsx +0 -53
- package/src/components/keypad-legacy/touchable-keypad-button.tsx +0 -163
- package/src/components/keypad-legacy/two-page-keypad.tsx +0 -115
- package/src/components/keypad-legacy/z-indexes.ts +0 -8
- package/src/components/keypad-switch.tsx +0 -42
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A high-level manager for our gesture system. In particular, this class
|
|
3
|
-
* connects our various bits of logic for managing gestures and interactions,
|
|
4
|
-
* and links them together.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import GestureStateMachine from "./gesture-state-machine";
|
|
8
|
-
import NodeManager from "./node-manager";
|
|
9
|
-
import PopoverStateMachine from "./popover-state-machine";
|
|
10
|
-
|
|
11
|
-
import type Key from "../../data/keys";
|
|
12
|
-
import type {ActiveNodesObj, LayoutProps} from "../../types";
|
|
13
|
-
import type * as React from "react";
|
|
14
|
-
|
|
15
|
-
const coordsForEvent = (evt) => {
|
|
16
|
-
return [evt.changedTouches[0].clientX, evt.changedTouches[0].clientY];
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type Options = {
|
|
20
|
-
swipeEnabled: boolean;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
type Handlers = {
|
|
24
|
-
onSwipeChange?: (dx: number) => void;
|
|
25
|
-
onSwipeEnd?: (dx: number) => void;
|
|
26
|
-
onActiveNodesChanged: (activeNodes: ActiveNodesObj) => void;
|
|
27
|
-
onClick: (key: Key, layoutProps: LayoutProps, inPopover: boolean) => void;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
class GestureManager {
|
|
31
|
-
swipeEnabled: boolean;
|
|
32
|
-
trackEvents: boolean;
|
|
33
|
-
nodeManager: NodeManager;
|
|
34
|
-
popoverStateMachine: PopoverStateMachine;
|
|
35
|
-
gestureStateMachine: GestureStateMachine;
|
|
36
|
-
|
|
37
|
-
constructor(
|
|
38
|
-
options: Options,
|
|
39
|
-
handlers: Handlers,
|
|
40
|
-
disabledSwipeKeys: ReadonlyArray<Key>,
|
|
41
|
-
multiPressableKeys: ReadonlyArray<Key>,
|
|
42
|
-
) {
|
|
43
|
-
const {swipeEnabled} = options;
|
|
44
|
-
|
|
45
|
-
this.swipeEnabled = swipeEnabled;
|
|
46
|
-
|
|
47
|
-
// Events aren't tracked until event tracking is enabled.
|
|
48
|
-
this.trackEvents = false;
|
|
49
|
-
|
|
50
|
-
this.nodeManager = new NodeManager();
|
|
51
|
-
this.popoverStateMachine = new PopoverStateMachine({
|
|
52
|
-
onActiveNodesChanged: (activeNodes) => {
|
|
53
|
-
const {popover, ...rest} = activeNodes;
|
|
54
|
-
handlers.onActiveNodesChanged({
|
|
55
|
-
popover: popover && {
|
|
56
|
-
parentId: popover.parentId,
|
|
57
|
-
bounds: this.nodeManager.layoutPropsForId(
|
|
58
|
-
popover.parentId,
|
|
59
|
-
).initialBounds,
|
|
60
|
-
childKeyIds: popover.childIds,
|
|
61
|
-
},
|
|
62
|
-
...rest,
|
|
63
|
-
});
|
|
64
|
-
},
|
|
65
|
-
/**
|
|
66
|
-
* `onClick` takes two arguments:
|
|
67
|
-
*
|
|
68
|
-
* @param {string} keyId - the identifier key that should initiate
|
|
69
|
-
* a click
|
|
70
|
-
* @param {string} domNodeId - the identifier of the DOM node on
|
|
71
|
-
* which the click should be considered
|
|
72
|
-
* to have occurred
|
|
73
|
-
* @param {bool} inPopover - whether the key was contained within a
|
|
74
|
-
* popover
|
|
75
|
-
*
|
|
76
|
-
* These two parameters will often be equivalent. They will differ,
|
|
77
|
-
* though, when a popover button is itself clicked, in which case
|
|
78
|
-
* we need to mimic the effects of clicking on its 'primary' child
|
|
79
|
-
* key, but animate the click on the popover button.
|
|
80
|
-
*/
|
|
81
|
-
onClick: (keyId, domNodeId, inPopover) => {
|
|
82
|
-
handlers.onClick(
|
|
83
|
-
keyId,
|
|
84
|
-
this.nodeManager.layoutPropsForId(domNodeId),
|
|
85
|
-
inPopover,
|
|
86
|
-
);
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
this.gestureStateMachine = new GestureStateMachine(
|
|
90
|
-
{
|
|
91
|
-
onFocus: (id) => {
|
|
92
|
-
this.popoverStateMachine.onFocus(id);
|
|
93
|
-
},
|
|
94
|
-
onLongPress: (id) => {
|
|
95
|
-
this.popoverStateMachine.onLongPress(id);
|
|
96
|
-
},
|
|
97
|
-
onTouchEnd: (id) => {
|
|
98
|
-
this.popoverStateMachine.onTouchEnd(id);
|
|
99
|
-
},
|
|
100
|
-
onBlur: () => {
|
|
101
|
-
this.popoverStateMachine.onBlur();
|
|
102
|
-
},
|
|
103
|
-
onSwipeChange: handlers.onSwipeChange,
|
|
104
|
-
onSwipeEnd: handlers.onSwipeEnd,
|
|
105
|
-
onTrigger: (id) => {
|
|
106
|
-
this.popoverStateMachine.onTrigger(id);
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
{},
|
|
110
|
-
disabledSwipeKeys,
|
|
111
|
-
multiPressableKeys,
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Handle a touch-start event that originated in a node registered with the
|
|
117
|
-
* gesture system.
|
|
118
|
-
*
|
|
119
|
-
* @param {React.TouchEvent<HTMLDivElement>} evt - the raw touch event from the browser
|
|
120
|
-
* @param {string} id - the identifier of the DOM node in which the touch
|
|
121
|
-
* occurred
|
|
122
|
-
*/
|
|
123
|
-
onTouchStart(
|
|
124
|
-
evt: React.TouchEvent<HTMLDivElement>,
|
|
125
|
-
id?: string | undefined,
|
|
126
|
-
) {
|
|
127
|
-
if (!this.trackEvents) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const [x] = coordsForEvent(evt);
|
|
132
|
-
|
|
133
|
-
// TODO(charlie): It doesn't seem to be guaranteed that every touch
|
|
134
|
-
// event on `changedTouches` originates from the node through which this
|
|
135
|
-
// touch event was sent. In that case, we'd be inappropriately reporting
|
|
136
|
-
// the starting node ID.
|
|
137
|
-
for (let i = 0; i < evt.changedTouches.length; i++) {
|
|
138
|
-
this.gestureStateMachine.onTouchStart(
|
|
139
|
-
() => id,
|
|
140
|
-
evt.changedTouches[i].identifier,
|
|
141
|
-
x,
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// If an event started in a view that we're managing, we'll handle it
|
|
146
|
-
// all the way through.
|
|
147
|
-
evt.preventDefault();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Handle a touch-move event that originated in a node registered with the
|
|
152
|
-
* gesture system.
|
|
153
|
-
*
|
|
154
|
-
* @param {React.TouchEvent<HTMLDivElement>} evt - the raw touch event from the browser
|
|
155
|
-
*/
|
|
156
|
-
onTouchMove(evt: React.TouchEvent<HTMLDivElement>) {
|
|
157
|
-
if (!this.trackEvents) {
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const swipeLocked = this.popoverStateMachine.isPopoverVisible();
|
|
162
|
-
const swipeEnabled = this.swipeEnabled && !swipeLocked;
|
|
163
|
-
const [x, y] = coordsForEvent(evt);
|
|
164
|
-
for (let i = 0; i < evt.changedTouches.length; i++) {
|
|
165
|
-
this.gestureStateMachine.onTouchMove(
|
|
166
|
-
() => this.nodeManager.idForCoords(x, y),
|
|
167
|
-
evt.changedTouches[i].identifier,
|
|
168
|
-
x,
|
|
169
|
-
swipeEnabled,
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Handle a touch-end event that originated in a node registered with the
|
|
176
|
-
* gesture system.
|
|
177
|
-
*
|
|
178
|
-
* @param {React.TouchEvent<HTMLDivElement>} evt - the raw touch event from the browser
|
|
179
|
-
*/
|
|
180
|
-
onTouchEnd(evt: React.TouchEvent<HTMLDivElement>) {
|
|
181
|
-
if (!this.trackEvents) {
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const [x, y] = coordsForEvent(evt);
|
|
186
|
-
for (let i = 0; i < evt.changedTouches.length; i++) {
|
|
187
|
-
this.gestureStateMachine.onTouchEnd(
|
|
188
|
-
() => this.nodeManager.idForCoords(x, y),
|
|
189
|
-
evt.changedTouches[i].identifier,
|
|
190
|
-
x,
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Handle a touch-cancel event that originated in a node registered with the
|
|
197
|
-
* gesture system.
|
|
198
|
-
*
|
|
199
|
-
* @param {React.TouchEvent<HTMLDivElement>} evt - the raw touch event from the browser
|
|
200
|
-
*/
|
|
201
|
-
onTouchCancel(evt: React.TouchEvent<HTMLDivElement>) {
|
|
202
|
-
if (!this.trackEvents) {
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
for (let i = 0; i < evt.changedTouches.length; i++) {
|
|
207
|
-
this.gestureStateMachine.onTouchCancel(
|
|
208
|
-
evt.changedTouches[i].identifier,
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Register a DOM node with a given identifier.
|
|
215
|
-
*
|
|
216
|
-
* @param {string} id - the identifier of the given node
|
|
217
|
-
* @param {node} domNode - the DOM node linked to the identifier
|
|
218
|
-
* @param {string[]} childIds - the identifiers of any DOM nodes that
|
|
219
|
-
* should be considered children of this node,
|
|
220
|
-
* in that they should take priority when
|
|
221
|
-
* intercepting touch events
|
|
222
|
-
* @param {object} borders - an opaque object describing the node's borders
|
|
223
|
-
*/
|
|
224
|
-
registerDOMNode(id, domNode, childIds) {
|
|
225
|
-
this.nodeManager.registerDOMNode(id, domNode, childIds);
|
|
226
|
-
this.popoverStateMachine.registerPopover(id, childIds);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Unregister the DOM node with the given identifier.
|
|
231
|
-
*
|
|
232
|
-
* @param {string} id - the identifier of the node to unregister
|
|
233
|
-
*/
|
|
234
|
-
unregisterDOMNode(id) {
|
|
235
|
-
this.nodeManager.unregisterDOMNode(id);
|
|
236
|
-
this.popoverStateMachine.unregisterPopover(id);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Enable event tracking for the gesture manager.
|
|
241
|
-
*/
|
|
242
|
-
enableEventTracking() {
|
|
243
|
-
this.trackEvents = true;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Disable event tracking for the gesture manager. When called, the gesture
|
|
248
|
-
* manager will drop any events received by managed nodes.
|
|
249
|
-
*/
|
|
250
|
-
disableEventTracking() {
|
|
251
|
-
this.trackEvents = false;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
export default GestureManager;
|
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import type Key from "../../data/keys";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The state machine that backs our gesture system. In particular, this state
|
|
5
|
-
* machine manages the interplay between focuses, touch ups, and swiping.
|
|
6
|
-
* It is entirely ignorant of the existence of popovers and the positions of
|
|
7
|
-
* DOM nodes, operating solely on IDs. The state machine does accommodate for
|
|
8
|
-
* multi-touch interactions, tracking gesture state on a per-touch basis.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// exported for tests
|
|
12
|
-
export type Handlers = {
|
|
13
|
-
onFocus: (id: string) => void;
|
|
14
|
-
onBlur: () => void;
|
|
15
|
-
onTrigger: (id: string) => void;
|
|
16
|
-
onLongPress: (id: string) => void;
|
|
17
|
-
onSwipeChange?: (x: number) => void;
|
|
18
|
-
onSwipeEnd?: (x: number) => void;
|
|
19
|
-
onTouchEnd: (id: string) => void;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
type Options = {
|
|
23
|
-
longPressWaitTimeMs: number;
|
|
24
|
-
swipeThresholdPx: number;
|
|
25
|
-
holdIntervalMs: number;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
type TouchState = {
|
|
29
|
-
activeNodeId: Key;
|
|
30
|
-
pressAndHoldIntervalId: number | null;
|
|
31
|
-
longPressTimeoutId: number | null;
|
|
32
|
-
swipeLocked: boolean;
|
|
33
|
-
startX: number;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
type TouchStateMap = Record<Key, TouchState>;
|
|
37
|
-
|
|
38
|
-
type SwipeState = {
|
|
39
|
-
touchId: Key;
|
|
40
|
-
startX: number;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const defaultOptions: Options = {
|
|
44
|
-
longPressWaitTimeMs: 50,
|
|
45
|
-
swipeThresholdPx: 20,
|
|
46
|
-
holdIntervalMs: 250,
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
class GestureStateMachine {
|
|
50
|
-
handlers: Handlers;
|
|
51
|
-
options: Options;
|
|
52
|
-
swipeDisabledNodeIds: ReadonlyArray<Key>;
|
|
53
|
-
multiPressableKeys: ReadonlyArray<Key>;
|
|
54
|
-
touchState: Partial<TouchStateMap>;
|
|
55
|
-
swipeState: SwipeState | null;
|
|
56
|
-
|
|
57
|
-
constructor(
|
|
58
|
-
handlers: Handlers,
|
|
59
|
-
options: Partial<Options>,
|
|
60
|
-
swipeDisabledNodeIds?: ReadonlyArray<Key>,
|
|
61
|
-
multiPressableKeys?: ReadonlyArray<Key>,
|
|
62
|
-
) {
|
|
63
|
-
this.handlers = handlers;
|
|
64
|
-
this.options = {
|
|
65
|
-
...defaultOptions,
|
|
66
|
-
...options,
|
|
67
|
-
};
|
|
68
|
-
this.swipeDisabledNodeIds = swipeDisabledNodeIds || [];
|
|
69
|
-
this.multiPressableKeys = multiPressableKeys || [];
|
|
70
|
-
|
|
71
|
-
// TODO(charlie): Add types for this file. It's not great that we're now
|
|
72
|
-
// passing around these opaque state objects.
|
|
73
|
-
this.touchState = {};
|
|
74
|
-
this.swipeState = null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
_maybeCancelLongPressForTouch(touchId) {
|
|
78
|
-
const {longPressTimeoutId} = this.touchState[touchId];
|
|
79
|
-
if (longPressTimeoutId) {
|
|
80
|
-
clearTimeout(longPressTimeoutId);
|
|
81
|
-
this.touchState[touchId] = {
|
|
82
|
-
...this.touchState[touchId],
|
|
83
|
-
longPressTimeoutId: null,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
_maybeCancelPressAndHoldForTouch(touchId) {
|
|
89
|
-
const {pressAndHoldIntervalId} = this.touchState[touchId];
|
|
90
|
-
if (pressAndHoldIntervalId) {
|
|
91
|
-
// If there was an interval set to detect holds, clear it out.
|
|
92
|
-
clearInterval(pressAndHoldIntervalId);
|
|
93
|
-
this.touchState[touchId] = {
|
|
94
|
-
...this.touchState[touchId],
|
|
95
|
-
pressAndHoldIntervalId: null,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
_cleanupTouchEvent(touchId) {
|
|
101
|
-
this._maybeCancelLongPressForTouch(touchId);
|
|
102
|
-
this._maybeCancelPressAndHoldForTouch(touchId);
|
|
103
|
-
delete this.touchState[touchId];
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Handle a focus event on the node with the given identifier, which may be
|
|
108
|
-
* `null` to indicate that the user has dragged their finger off of any
|
|
109
|
-
* registered nodes, but is still in the middle of a gesture.
|
|
110
|
-
*
|
|
111
|
-
* @param {string|null} id - the identifier of the newly focused node, or
|
|
112
|
-
* `null` if no node is focused
|
|
113
|
-
* @param {number} touchId - a unique identifier associated with the touch
|
|
114
|
-
*/
|
|
115
|
-
_onFocus(id, touchId) {
|
|
116
|
-
// If we're in the middle of a long-press, cancel it.
|
|
117
|
-
this._maybeCancelLongPressForTouch(touchId);
|
|
118
|
-
|
|
119
|
-
// Reset any existing hold-detecting interval.
|
|
120
|
-
this._maybeCancelPressAndHoldForTouch(touchId);
|
|
121
|
-
|
|
122
|
-
// Set the focused node ID and handle the focus event.
|
|
123
|
-
// Note: we can call `onFocus` with `null` IDs. The semantics of an
|
|
124
|
-
// `onFocus` with a `null` ID differs from that of `onBlur`. The former
|
|
125
|
-
// indicates that a gesture that can focus future nodes is still in
|
|
126
|
-
// progress, but that no node is currently focused. The latter
|
|
127
|
-
// indicates that the gesture has ended and nothing will be focused.
|
|
128
|
-
this.touchState[touchId] = {
|
|
129
|
-
...this.touchState[touchId],
|
|
130
|
-
activeNodeId: id,
|
|
131
|
-
};
|
|
132
|
-
this.handlers.onFocus(id);
|
|
133
|
-
|
|
134
|
-
if (id) {
|
|
135
|
-
// Handle logic for repeating button presses.
|
|
136
|
-
if (this.multiPressableKeys.includes(id)) {
|
|
137
|
-
// Start by triggering a click, iOS style.
|
|
138
|
-
this.handlers.onTrigger(id);
|
|
139
|
-
|
|
140
|
-
// Set up a new hold detector for the current button.
|
|
141
|
-
this.touchState[touchId] = {
|
|
142
|
-
...this.touchState[touchId],
|
|
143
|
-
pressAndHoldIntervalId: setInterval(() => {
|
|
144
|
-
// On every cycle, trigger the click handler.
|
|
145
|
-
this.handlers.onTrigger(id);
|
|
146
|
-
}, this.options.holdIntervalMs),
|
|
147
|
-
};
|
|
148
|
-
} else {
|
|
149
|
-
// Set up a new hold detector for the current button.
|
|
150
|
-
this.touchState[touchId] = {
|
|
151
|
-
...this.touchState[touchId],
|
|
152
|
-
longPressTimeoutId: setTimeout(() => {
|
|
153
|
-
this.handlers.onLongPress(id);
|
|
154
|
-
this.touchState[touchId] = {
|
|
155
|
-
...this.touchState[touchId],
|
|
156
|
-
longPressTimeoutId: null,
|
|
157
|
-
};
|
|
158
|
-
}, this.options.longPressWaitTimeMs),
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Clear out all active gesture information.
|
|
166
|
-
*/
|
|
167
|
-
_onSwipeStart() {
|
|
168
|
-
for (const activeTouchId of Object.keys(this.touchState)) {
|
|
169
|
-
this._maybeCancelLongPressForTouch(activeTouchId);
|
|
170
|
-
this._maybeCancelPressAndHoldForTouch(activeTouchId);
|
|
171
|
-
}
|
|
172
|
-
this.touchState = {};
|
|
173
|
-
this.handlers.onBlur();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* A function that returns the identifier of the node over which the touch
|
|
178
|
-
* event occurred. This is provided as a piece of lazy computation, as
|
|
179
|
-
* computing the DOM node for a given point is expensive, and the state
|
|
180
|
-
* machine won't always need that information. For example, if the user is
|
|
181
|
-
* swiping, then `onTouchMove` needs to be performant and doesn't care about
|
|
182
|
-
* the node over which the touch occurred.
|
|
183
|
-
*
|
|
184
|
-
* @typedef idComputation
|
|
185
|
-
* @returns {DOMNode} - the identifier of the node over which the touch
|
|
186
|
-
* occurred
|
|
187
|
-
*/
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Handle a touch-start event on the node with the given identifer.
|
|
191
|
-
*
|
|
192
|
-
* @param {idComputation} getId - a function that returns identifier of the
|
|
193
|
-
* node over which the start event occurred
|
|
194
|
-
* @param {number} touchId - a unique identifier associated with the touch
|
|
195
|
-
*/
|
|
196
|
-
onTouchStart(getId, touchId, pageX) {
|
|
197
|
-
// Ignore any touch events that start mid-swipe.
|
|
198
|
-
if (this.swipeState) {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (this.touchState[touchId]) {
|
|
203
|
-
// It turns out we can get multiple touch starts with no
|
|
204
|
-
// intervening move, end, or cancel events in Android WebViews.
|
|
205
|
-
// TODO(benkomalo): it's not entirely clear why this happens, but
|
|
206
|
-
// it seems to happen with the backspace button. It may be related
|
|
207
|
-
// to FastClick (https://github.com/ftlabs/fastclick/issues/71)
|
|
208
|
-
// though I haven't verified, and it's probably good to be robust
|
|
209
|
-
// here anyways.
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const startingNodeId = getId();
|
|
214
|
-
this.touchState[touchId] = {
|
|
215
|
-
swipeLocked: this.swipeDisabledNodeIds.includes(startingNodeId),
|
|
216
|
-
startX: pageX,
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
this._onFocus(startingNodeId, touchId);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Handle a touch-move event on the node with the given identifer.
|
|
224
|
-
*
|
|
225
|
-
* @param {idComputation} getId - a function that returns identifier of the
|
|
226
|
-
* node over which the move event occurred
|
|
227
|
-
* @param {number} touchId - a unique identifier associated with the touch
|
|
228
|
-
* @param {number} pageX - the x coordinate of the touch
|
|
229
|
-
* @param {boolean} swipeEnabled - whether the system should allow for
|
|
230
|
-
* transitions into a swiping state
|
|
231
|
-
*/
|
|
232
|
-
onTouchMove(getId, touchId, pageX, swipeEnabled) {
|
|
233
|
-
if (this.swipeState) {
|
|
234
|
-
// Only respect the finger that started a swipe. Any other lingering
|
|
235
|
-
// gestures are ignored.
|
|
236
|
-
if (this.swipeState.touchId === touchId) {
|
|
237
|
-
this.handlers.onSwipeChange?.(pageX - this.swipeState.startX);
|
|
238
|
-
}
|
|
239
|
-
} else if (this.touchState[touchId]) {
|
|
240
|
-
// It could be touch events started outside the keypad and
|
|
241
|
-
// moved into it; ignore them.
|
|
242
|
-
const {activeNodeId, startX, swipeLocked} =
|
|
243
|
-
this.touchState[touchId];
|
|
244
|
-
|
|
245
|
-
const dx = pageX - startX;
|
|
246
|
-
const shouldBeginSwiping =
|
|
247
|
-
swipeEnabled &&
|
|
248
|
-
!swipeLocked &&
|
|
249
|
-
Math.abs(dx) > this.options.swipeThresholdPx;
|
|
250
|
-
|
|
251
|
-
if (shouldBeginSwiping) {
|
|
252
|
-
this._onSwipeStart();
|
|
253
|
-
|
|
254
|
-
// Trigger the swipe.
|
|
255
|
-
this.swipeState = {
|
|
256
|
-
touchId,
|
|
257
|
-
startX,
|
|
258
|
-
};
|
|
259
|
-
this.handlers.onSwipeChange?.(pageX - this.swipeState.startX);
|
|
260
|
-
} else {
|
|
261
|
-
const id = getId();
|
|
262
|
-
if (id !== activeNodeId) {
|
|
263
|
-
this._onFocus(id, touchId);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Handle a touch-end event on the node with the given identifer.
|
|
271
|
-
*
|
|
272
|
-
* @param {idComputation} getId - a function that returns identifier of the
|
|
273
|
-
* node over which the end event occurred
|
|
274
|
-
* @param {number} touchId - a unique identifier associated with the touch
|
|
275
|
-
* @param {number} pageX - the x coordinate of the touch
|
|
276
|
-
*/
|
|
277
|
-
onTouchEnd(getId, touchId, pageX) {
|
|
278
|
-
if (this.swipeState) {
|
|
279
|
-
// Only respect the finger that started a swipe. Any other lingering
|
|
280
|
-
// gestures are ignored.
|
|
281
|
-
if (this.swipeState.touchId === touchId) {
|
|
282
|
-
this.handlers.onSwipeEnd?.(pageX - this.swipeState.startX);
|
|
283
|
-
this.swipeState = null;
|
|
284
|
-
}
|
|
285
|
-
} else if (this.touchState[touchId]) {
|
|
286
|
-
// It could be touch events started outside the keypad and
|
|
287
|
-
// moved into it; ignore them.
|
|
288
|
-
const {activeNodeId, pressAndHoldIntervalId} =
|
|
289
|
-
this.touchState[touchId];
|
|
290
|
-
|
|
291
|
-
this._cleanupTouchEvent(touchId);
|
|
292
|
-
|
|
293
|
-
const didPressAndHold = !!pressAndHoldIntervalId;
|
|
294
|
-
if (didPressAndHold) {
|
|
295
|
-
// We don't trigger a touch end if there was a press and hold,
|
|
296
|
-
// because the key has been triggered at least once and calling
|
|
297
|
-
// the onTouchEnd handler would add an extra trigger.
|
|
298
|
-
this.handlers.onBlur();
|
|
299
|
-
} else {
|
|
300
|
-
// Trigger a touch-end. There's no need to notify clients of a
|
|
301
|
-
// blur as clients are responsible for handling any cleanup in
|
|
302
|
-
// their touch-end handlers.
|
|
303
|
-
this.handlers.onTouchEnd(activeNodeId);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Handle a touch-cancel event.
|
|
310
|
-
*/
|
|
311
|
-
onTouchCancel(touchId) {
|
|
312
|
-
// If a touch is cancelled and we're swiping, end the swipe with no
|
|
313
|
-
// displacement.
|
|
314
|
-
if (this.swipeState) {
|
|
315
|
-
if (this.swipeState.touchId === touchId) {
|
|
316
|
-
this.handlers.onSwipeEnd?.(0);
|
|
317
|
-
this.swipeState = null;
|
|
318
|
-
}
|
|
319
|
-
} else if (this.touchState[touchId]) {
|
|
320
|
-
// Otherwise, trigger a full blur. We don't want to trigger a
|
|
321
|
-
// touch-up, since the cancellation means that the user probably
|
|
322
|
-
// didn't release over a key intentionally.
|
|
323
|
-
this._cleanupTouchEvent(touchId);
|
|
324
|
-
this.handlers.onBlur();
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
export default GestureStateMachine;
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A component that renders an icon for a symbol with the given name.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {StyleSheet} from "aphrodite";
|
|
6
|
-
import * as React from "react";
|
|
7
|
-
|
|
8
|
-
import {IconType} from "../../enums";
|
|
9
|
-
import {offBlack} from "../common-style";
|
|
10
|
-
|
|
11
|
-
import MathIcon from "./math-icon";
|
|
12
|
-
import SvgIcon from "./svg-icon";
|
|
13
|
-
import TextIcon from "./text-icon";
|
|
14
|
-
|
|
15
|
-
import type {IconConfig} from "../../types";
|
|
16
|
-
import type {StyleType} from "@khanacademy/wonder-blocks-core";
|
|
17
|
-
|
|
18
|
-
const focusedColor = "#FFF";
|
|
19
|
-
const unfocusedColor = offBlack;
|
|
20
|
-
|
|
21
|
-
type Props = {
|
|
22
|
-
focused: boolean;
|
|
23
|
-
icon: IconConfig;
|
|
24
|
-
style?: StyleType;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
class Icon extends React.PureComponent<Props> {
|
|
28
|
-
render() {
|
|
29
|
-
const {focused, icon, style} = this.props;
|
|
30
|
-
|
|
31
|
-
const styleWithFocus: StyleType = [
|
|
32
|
-
focused ? styles.focused : styles.unfocused,
|
|
33
|
-
...(Array.isArray(style) ? style : [style]),
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
switch (icon.type) {
|
|
37
|
-
case IconType.MATH:
|
|
38
|
-
return <MathIcon math={icon.data} style={styleWithFocus} />;
|
|
39
|
-
|
|
40
|
-
case IconType.SVG:
|
|
41
|
-
// TODO(charlie): Support passing style objects to `SvgIcon`.
|
|
42
|
-
// This will require migrating the individual icons to use
|
|
43
|
-
// `currentColor` and accept a `className` prop, rather than
|
|
44
|
-
// relying on an explicit color prop.
|
|
45
|
-
return (
|
|
46
|
-
<SvgIcon
|
|
47
|
-
name={icon.data}
|
|
48
|
-
color={focused ? focusedColor : unfocusedColor}
|
|
49
|
-
/>
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
case IconType.TEXT:
|
|
53
|
-
return (
|
|
54
|
-
<TextIcon character={icon.data} style={styleWithFocus} />
|
|
55
|
-
);
|
|
56
|
-
default:
|
|
57
|
-
throw new Error("No icon or symbol provided");
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const styles = StyleSheet.create({
|
|
63
|
-
unfocused: {
|
|
64
|
-
color: unfocusedColor,
|
|
65
|
-
},
|
|
66
|
-
|
|
67
|
-
focused: {
|
|
68
|
-
color: focusedColor,
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
export default Icon;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* An arrow icon, used by the other navigational keys.
|
|
3
|
-
*/
|
|
4
|
-
import * as React from "react";
|
|
5
|
-
|
|
6
|
-
const Arrow = (props) => {
|
|
7
|
-
return (
|
|
8
|
-
<g fill="none" fillRule="evenodd" {...props}>
|
|
9
|
-
<path fill="none" d="M0 0h48v48H0z" />
|
|
10
|
-
<path fill="none" d="M12 12h24v24H12z" />
|
|
11
|
-
<path
|
|
12
|
-
stroke="#888D93"
|
|
13
|
-
strokeWidth="2"
|
|
14
|
-
strokeLinecap="round"
|
|
15
|
-
strokeLinejoin="round"
|
|
16
|
-
d="M22 18l-6 6 6 6M16 24h16"
|
|
17
|
-
/>
|
|
18
|
-
</g>
|
|
19
|
-
);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export default Arrow;
|