@khanacademy/math-input 0.6.6 → 0.6.8
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 +12 -0
- package/dist/es/index.js +1601 -1609
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +1205 -1213
- package/dist/index.js.map +1 -1
- package/package.json +12 -10
- package/src/components/input/__tests__/context-tracking_test.js +2 -2
- package/src/components/input/__tests__/mathquill_test.js +2 -2
- package/src/components/input/cursor-handle.js +21 -20
- package/src/components/input/math-input.js +108 -58
- package/src/components/keypad/pre-algebra-page.js +1 -1
- package/src/components/keypad-container.js +0 -4
- package/src/fake-react-native-web/view.js +5 -5
- /package/src/components/input/__tests__/{math-wrapper.jsx → test-math-wrapper.jsx} +0 -0
package/dist/index.js
CHANGED
|
@@ -4,12 +4,13 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var Color = require('@khanacademy/wonder-blocks-color');
|
|
6
6
|
var i18n = require('@khanacademy/wonder-blocks-i18n');
|
|
7
|
+
var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
|
|
7
8
|
var aphrodite = require('aphrodite');
|
|
8
|
-
var PropTypes = require('prop-types');
|
|
9
9
|
var React = require('react');
|
|
10
10
|
var ReactDOM = require('react-dom');
|
|
11
11
|
var $ = require('jquery');
|
|
12
12
|
var MathQuill = require('mathquill');
|
|
13
|
+
var PropTypes = require('prop-types');
|
|
13
14
|
var reactRedux = require('react-redux');
|
|
14
15
|
var Redux = require('redux');
|
|
15
16
|
var katex = require('katex');
|
|
@@ -40,11 +41,11 @@ function _interopNamespace(e) {
|
|
|
40
41
|
|
|
41
42
|
var Color__default = /*#__PURE__*/_interopDefaultLegacy(Color);
|
|
42
43
|
var i18n__namespace = /*#__PURE__*/_interopNamespace(i18n);
|
|
43
|
-
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
|
|
44
44
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
45
45
|
var ReactDOM__default = /*#__PURE__*/_interopDefaultLegacy(ReactDOM);
|
|
46
46
|
var $__default = /*#__PURE__*/_interopDefaultLegacy($);
|
|
47
47
|
var MathQuill__default = /*#__PURE__*/_interopDefaultLegacy(MathQuill);
|
|
48
|
+
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
|
|
48
49
|
var Redux__namespace = /*#__PURE__*/_interopNamespace(Redux);
|
|
49
50
|
var katex__default = /*#__PURE__*/_interopDefaultLegacy(katex);
|
|
50
51
|
var Clickable__default = /*#__PURE__*/_interopDefaultLegacy(Clickable);
|
|
@@ -270,6 +271,176 @@ const navigationPadWidthPx = 192; // HACK(charlie): This should be injected by w
|
|
|
270
271
|
|
|
271
272
|
const toolbarHeightPx = 60;
|
|
272
273
|
|
|
274
|
+
const touchTargetRadiusPx = 2 * cursorHandleRadiusPx;
|
|
275
|
+
const touchTargetHeightPx = 2 * touchTargetRadiusPx;
|
|
276
|
+
const touchTargetWidthPx = 2 * touchTargetRadiusPx;
|
|
277
|
+
const cursorRadiusPx = cursorHandleRadiusPx;
|
|
278
|
+
const cursorHeightPx = cursorHandleDistanceMultiplier * (cursorRadiusPx * 4);
|
|
279
|
+
const cursorWidthPx = 4 * cursorRadiusPx;
|
|
280
|
+
|
|
281
|
+
class CursorHandle extends React__namespace.Component {
|
|
282
|
+
render() {
|
|
283
|
+
const {
|
|
284
|
+
x,
|
|
285
|
+
y,
|
|
286
|
+
animateIntoPosition
|
|
287
|
+
} = this.props;
|
|
288
|
+
const animationStyle = animateIntoPosition ? {
|
|
289
|
+
transitionDuration: "100ms",
|
|
290
|
+
transitionProperty: "transform"
|
|
291
|
+
} : {};
|
|
292
|
+
const transformString = "translate(".concat(x, "px, ").concat(y, "px)");
|
|
293
|
+
const outerStyle = {
|
|
294
|
+
position: "absolute",
|
|
295
|
+
// This is essentially webapp's interactiveComponent + 1.
|
|
296
|
+
// TODO(charlie): Pull in those styles somehow to avoid breakages.
|
|
297
|
+
zIndex: 4,
|
|
298
|
+
left: -touchTargetWidthPx / 2,
|
|
299
|
+
top: 0,
|
|
300
|
+
transform: transformString,
|
|
301
|
+
width: touchTargetWidthPx,
|
|
302
|
+
height: touchTargetHeightPx,
|
|
303
|
+
// Touch events that start on the cursor shouldn't be allowed to
|
|
304
|
+
// produce page scrolls.
|
|
305
|
+
touchAction: "none",
|
|
306
|
+
...animationStyle
|
|
307
|
+
};
|
|
308
|
+
return /*#__PURE__*/React__namespace.createElement("span", {
|
|
309
|
+
style: outerStyle,
|
|
310
|
+
onTouchStart: this.props.onTouchStart,
|
|
311
|
+
onTouchMove: this.props.onTouchMove,
|
|
312
|
+
onTouchEnd: this.props.onTouchEnd,
|
|
313
|
+
onTouchCancel: this.props.onTouchCancel
|
|
314
|
+
}, /*#__PURE__*/React__namespace.createElement("svg", {
|
|
315
|
+
fill: "none",
|
|
316
|
+
width: cursorWidthPx,
|
|
317
|
+
height: cursorHeightPx,
|
|
318
|
+
viewBox: "0 0 ".concat(cursorWidthPx, " ").concat(cursorHeightPx)
|
|
319
|
+
}, /*#__PURE__*/React__namespace.createElement("filter", {
|
|
320
|
+
id: "math-input_cursor",
|
|
321
|
+
colorInterpolationFilters: "sRGB",
|
|
322
|
+
filterUnits: "userSpaceOnUse",
|
|
323
|
+
height: cursorHeightPx * 0.87 // ~40
|
|
324
|
+
,
|
|
325
|
+
width: cursorWidthPx * 0.82 // ~36
|
|
326
|
+
,
|
|
327
|
+
x: "4",
|
|
328
|
+
y: "0"
|
|
329
|
+
}, /*#__PURE__*/React__namespace.createElement("feFlood", {
|
|
330
|
+
floodOpacity: "0",
|
|
331
|
+
result: "BackgroundImageFix"
|
|
332
|
+
}), /*#__PURE__*/React__namespace.createElement("feColorMatrix", {
|
|
333
|
+
in: "SourceAlpha",
|
|
334
|
+
type: "matrix",
|
|
335
|
+
values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
|
336
|
+
}), /*#__PURE__*/React__namespace.createElement("feOffset", {
|
|
337
|
+
dy: "4"
|
|
338
|
+
}), /*#__PURE__*/React__namespace.createElement("feGaussianBlur", {
|
|
339
|
+
stdDeviation: "4"
|
|
340
|
+
}), /*#__PURE__*/React__namespace.createElement("feColorMatrix", {
|
|
341
|
+
type: "matrix",
|
|
342
|
+
values: "0 0 0 0 0.129412 0 0 0 0 0.141176 0 0 0 0 0.172549 0 0 0 0.08 0"
|
|
343
|
+
}), /*#__PURE__*/React__namespace.createElement("feBlend", {
|
|
344
|
+
in2: "BackgroundImageFix",
|
|
345
|
+
mode: "normal",
|
|
346
|
+
result: "effect1_dropShadow"
|
|
347
|
+
}), /*#__PURE__*/React__namespace.createElement("feBlend", {
|
|
348
|
+
in: "SourceGraphic",
|
|
349
|
+
in2: "effect1_dropShadow",
|
|
350
|
+
mode: "normal",
|
|
351
|
+
result: "shape"
|
|
352
|
+
})), /*#__PURE__*/React__namespace.createElement("g", {
|
|
353
|
+
filter: "url(#math-input_cursor)"
|
|
354
|
+
}, /*#__PURE__*/React__namespace.createElement("path", {
|
|
355
|
+
d: "m22 4-7.07 7.0284c-1.3988 1.3901-2.3515 3.1615-2.7376 5.09-.3861 1.9284-.1883 3.9274.5685 5.7441s2.0385 3.3694 3.6831 4.4619c1.6445 1.0925 3.5781 1.6756 5.556 1.6756s3.9115-.5831 5.556-1.6756c1.6446-1.0925 2.9263-2.6452 3.6831-4.4619s.9546-3.8157.5685-5.7441c-.3861-1.9285-1.3388-3.6999-2.7376-5.09z",
|
|
356
|
+
fill: "#1865f2"
|
|
357
|
+
})), /*#__PURE__*/React__namespace.createElement("path", {
|
|
358
|
+
d: "m14.9301 10.4841 7.0699-7.06989 7.0699 7.06989.0001.0001c1.3988 1.3984 2.3515 3.1802 2.7376 5.1201s.1883 3.9507-.5685 5.7782c-.7568 1.8274-2.0385 3.3894-3.6831 4.4883-1.6445 1.099-3.5781 1.6855-5.556 1.6855s-3.9115-.5865-5.556-1.6855c-1.6446-1.0989-2.9263-2.6609-3.6831-4.4883-.7568-1.8275-.9546-3.8383-.5685-5.7782s1.3388-3.7217 2.7376-5.1201z",
|
|
359
|
+
stroke: "#fff",
|
|
360
|
+
strokeWidth: "2"
|
|
361
|
+
})));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
_defineProperty(CursorHandle, "defaultProps", {
|
|
367
|
+
animateIntoPosition: false,
|
|
368
|
+
visible: false,
|
|
369
|
+
x: 0,
|
|
370
|
+
y: 0
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* A gesture recognizer that detects 'drags', crudely defined as either scrolls
|
|
375
|
+
* or touches that move a sufficient distance.
|
|
376
|
+
*/
|
|
377
|
+
// The 'slop' factor, after which we consider the use to be dragging. The value
|
|
378
|
+
// is taken from the Android SDK. It won't be robust to page zoom and the like,
|
|
379
|
+
// but it should be good enough for our purposes.
|
|
380
|
+
const touchSlopPx = 8;
|
|
381
|
+
|
|
382
|
+
class DragListener {
|
|
383
|
+
constructor(onDrag, initialEvent) {
|
|
384
|
+
// We detect drags in two ways. First, by listening for the window
|
|
385
|
+
// scroll event (we consider any legitimate scroll to be a drag).
|
|
386
|
+
this._scrollListener = () => {
|
|
387
|
+
onDrag();
|
|
388
|
+
}; // And second, by listening for touch moves and tracking the each
|
|
389
|
+
// finger's displacement. This allows us to track, e.g., when the user
|
|
390
|
+
// scrolls within an individual view.
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
const touchLocationsById = {};
|
|
394
|
+
|
|
395
|
+
for (let i = 0; i < initialEvent.changedTouches.length; i++) {
|
|
396
|
+
const touch = initialEvent.changedTouches[i];
|
|
397
|
+
touchLocationsById[touch.identifier] = [touch.clientX, touch.clientY];
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
this._moveListener = evt => {
|
|
401
|
+
for (let i = 0; i < evt.changedTouches.length; i++) {
|
|
402
|
+
const touch = evt.changedTouches[i];
|
|
403
|
+
const initialTouchLocation = touchLocationsById[touch.identifier];
|
|
404
|
+
|
|
405
|
+
if (initialTouchLocation) {
|
|
406
|
+
const touchLocation = [touch.clientX, touch.clientY];
|
|
407
|
+
const dx = touchLocation[0] - initialTouchLocation[0];
|
|
408
|
+
const dy = touchLocation[1] - initialTouchLocation[1];
|
|
409
|
+
const squaredDist = dx * dx + dy * dy;
|
|
410
|
+
const squaredTouchSlop = touchSlopPx * touchSlopPx;
|
|
411
|
+
|
|
412
|
+
if (squaredDist > squaredTouchSlop) {
|
|
413
|
+
onDrag();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}; // Clean-up any terminated gestures, since some browsers reuse
|
|
418
|
+
// identifiers.
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
this._endAndCancelListener = evt => {
|
|
422
|
+
for (let i = 0; i < evt.changedTouches.length; i++) {
|
|
423
|
+
delete touchLocationsById[evt.changedTouches[i].identifier];
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
attach() {
|
|
429
|
+
window.addEventListener("scroll", this._scrollListener);
|
|
430
|
+
window.addEventListener("touchmove", this._moveListener);
|
|
431
|
+
window.addEventListener("touchend", this._endAndCancelListener);
|
|
432
|
+
window.addEventListener("touchcancel", this._endAndCancelListener);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
detach() {
|
|
436
|
+
window.removeEventListener("scroll", this._scrollListener);
|
|
437
|
+
window.removeEventListener("touchmove", this._moveListener);
|
|
438
|
+
window.removeEventListener("touchend", this._endAndCancelListener);
|
|
439
|
+
window.removeEventListener("touchcancel", this._endAndCancelListener);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
}
|
|
443
|
+
|
|
273
444
|
/**
|
|
274
445
|
* Constants that are shared between multiple files.
|
|
275
446
|
*/
|
|
@@ -343,1193 +514,656 @@ const EchoAnimationTypes = {
|
|
|
343
514
|
const decimalSeparator = i18n.getDecimalSeparator() === "," ? DecimalSeparators.COMMA : DecimalSeparators.PERIOD;
|
|
344
515
|
|
|
345
516
|
/**
|
|
346
|
-
*
|
|
517
|
+
* Constants that define the various contexts in which a cursor can exist. The
|
|
518
|
+
* active context is determined first by looking at the cursor's siblings (e.g.,
|
|
519
|
+
* for the `BEFORE_FRACTION` context), and then at its direct parent. Though a
|
|
520
|
+
* cursor could in theory be nested in multiple contexts, we only care about the
|
|
521
|
+
* immediate context.
|
|
522
|
+
*
|
|
523
|
+
* TODO(charlie): Add a context to represent being inside of a radical. Right
|
|
524
|
+
* now, we show the dismiss button rather than allowing the user to jump out of
|
|
525
|
+
* the radical.
|
|
347
526
|
*/
|
|
348
|
-
|
|
349
|
-
|
|
527
|
+
// TODO: Get rid of these constants in favour of CursorContext type.
|
|
528
|
+
// The cursor is not in any of the other viable contexts.
|
|
529
|
+
const NONE = "NONE"; // The cursor is within a set of parentheses.
|
|
530
|
+
|
|
531
|
+
const IN_PARENS = "IN_PARENS"; // The cursor is within a superscript (e.g., an exponent).
|
|
532
|
+
|
|
533
|
+
const IN_SUPER_SCRIPT = "IN_SUPER_SCRIPT"; // The cursor is within a subscript (e.g., the base of a custom logarithm).
|
|
534
|
+
|
|
535
|
+
const IN_SUB_SCRIPT = "IN_SUB_SCRIPT"; // The cursor is in the numerator of a fraction.
|
|
536
|
+
|
|
537
|
+
const IN_NUMERATOR = "IN_NUMERATOR"; // The cursor is in the denominator of a fraction.
|
|
538
|
+
|
|
539
|
+
const IN_DENOMINATOR = "IN_DENOMINATOR"; // The cursor is sitting before a fraction; that is, the cursor is within
|
|
540
|
+
// what looks to be a mixed number preceding a fraction. This will only be
|
|
541
|
+
// the case when the only math between the cursor and the fraction to its
|
|
542
|
+
// write is non-leaf math (numbers and variables).
|
|
543
|
+
|
|
544
|
+
const BEFORE_FRACTION = "BEFORE_FRACTION";
|
|
545
|
+
|
|
546
|
+
var CursorContexts = /*#__PURE__*/Object.freeze({
|
|
547
|
+
__proto__: null,
|
|
548
|
+
NONE: NONE,
|
|
549
|
+
IN_PARENS: IN_PARENS,
|
|
550
|
+
IN_SUPER_SCRIPT: IN_SUPER_SCRIPT,
|
|
551
|
+
IN_SUB_SCRIPT: IN_SUB_SCRIPT,
|
|
552
|
+
IN_NUMERATOR: IN_NUMERATOR,
|
|
553
|
+
IN_DENOMINATOR: IN_DENOMINATOR,
|
|
554
|
+
BEFORE_FRACTION: BEFORE_FRACTION
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* This file contains a wrapper around MathQuill so that we can provide a
|
|
559
|
+
* more regular interface for the functionality we need while insulating us
|
|
560
|
+
* from MathQuill changes.
|
|
561
|
+
*/
|
|
562
|
+
// If it does not exist, fall back to CommonJS require. - jsatk
|
|
563
|
+
|
|
564
|
+
const decimalSymbol = decimalSeparator === DecimalSeparators.COMMA ? "," : ".";
|
|
565
|
+
const WRITE = "write";
|
|
566
|
+
const CMD = "cmd";
|
|
567
|
+
const KEYSTROKE = "keystroke";
|
|
568
|
+
const MQ_END = 0; // A mapping from keys that can be pressed on a keypad to the way in which
|
|
569
|
+
// MathQuill should modify its input in response to that key-press. Any keys
|
|
570
|
+
// that do not provide explicit actions (like the numeral keys) will merely
|
|
571
|
+
// write their contents to MathQuill.
|
|
572
|
+
|
|
573
|
+
const KeyActions = {
|
|
350
574
|
[Keys.PLUS]: {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
ariaLabel: i18n__namespace._("Plus")
|
|
575
|
+
str: "+",
|
|
576
|
+
fn: WRITE
|
|
354
577
|
},
|
|
355
578
|
[Keys.MINUS]: {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
ariaLabel: i18n__namespace._("Minus")
|
|
579
|
+
str: "-",
|
|
580
|
+
fn: WRITE
|
|
359
581
|
},
|
|
360
582
|
[Keys.NEGATIVE]: {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
ariaLabel: i18n__namespace._("Negative")
|
|
583
|
+
str: "-",
|
|
584
|
+
fn: WRITE
|
|
364
585
|
},
|
|
365
586
|
[Keys.TIMES]: {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
ariaLabel: i18n__namespace._("Multiply")
|
|
587
|
+
str: "\\times",
|
|
588
|
+
fn: WRITE
|
|
369
589
|
},
|
|
370
590
|
[Keys.DIVIDE]: {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
ariaLabel: i18n__namespace._("Divide")
|
|
591
|
+
str: "\\div",
|
|
592
|
+
fn: WRITE
|
|
374
593
|
},
|
|
375
594
|
[Keys.DECIMAL]: {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
ariaLabel: i18n__namespace._("Decimal"),
|
|
379
|
-
icon: decimalSeparator === DecimalSeparators.COMMA ? {
|
|
380
|
-
// TODO(charlie): Get an SVG icon for the comma, or verify with
|
|
381
|
-
// design that the text-rendered version is acceptable.
|
|
382
|
-
type: IconTypes.TEXT,
|
|
383
|
-
data: ","
|
|
384
|
-
} : {
|
|
385
|
-
type: IconTypes.SVG,
|
|
386
|
-
data: Keys.PERIOD
|
|
387
|
-
}
|
|
388
|
-
},
|
|
389
|
-
[Keys.PERCENT]: {
|
|
390
|
-
type: KeyTypes.OPERATOR,
|
|
391
|
-
// I18N: A label for a percent sign.
|
|
392
|
-
ariaLabel: i18n__namespace._("Percent")
|
|
393
|
-
},
|
|
394
|
-
[Keys.CDOT]: {
|
|
395
|
-
type: KeyTypes.OPERATOR,
|
|
396
|
-
// I18N: A label for a multiplication sign (represented as a dot).
|
|
397
|
-
ariaLabel: i18n__namespace._("Multiply")
|
|
595
|
+
str: decimalSymbol,
|
|
596
|
+
fn: WRITE
|
|
398
597
|
},
|
|
399
598
|
[Keys.EQUAL]: {
|
|
400
|
-
|
|
401
|
-
|
|
599
|
+
str: "=",
|
|
600
|
+
fn: WRITE
|
|
402
601
|
},
|
|
403
602
|
[Keys.NEQ]: {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
},
|
|
407
|
-
[Keys.GT]: {
|
|
408
|
-
type: KeyTypes.OPERATOR,
|
|
409
|
-
// I18N: A label for a 'greater than' sign (represented as '>').
|
|
410
|
-
ariaLabel: i18n__namespace._("Greater than sign")
|
|
411
|
-
},
|
|
412
|
-
[Keys.LT]: {
|
|
413
|
-
type: KeyTypes.OPERATOR,
|
|
414
|
-
// I18N: A label for a 'less than' sign (represented as '<').
|
|
415
|
-
ariaLabel: i18n__namespace._("Less than sign")
|
|
416
|
-
},
|
|
417
|
-
[Keys.GEQ]: {
|
|
418
|
-
type: KeyTypes.OPERATOR,
|
|
419
|
-
ariaLabel: i18n__namespace._("Greater than or equal to sign")
|
|
420
|
-
},
|
|
421
|
-
[Keys.LEQ]: {
|
|
422
|
-
type: KeyTypes.OPERATOR,
|
|
423
|
-
ariaLabel: i18n__namespace._("Less than or equal to sign")
|
|
424
|
-
},
|
|
425
|
-
// mobile native
|
|
426
|
-
[Keys.FRAC_INCLUSIVE]: {
|
|
427
|
-
type: KeyTypes.OPERATOR,
|
|
428
|
-
// I18N: A label for a button that creates a new fraction and puts the
|
|
429
|
-
// current expression in the numerator of that fraction.
|
|
430
|
-
ariaLabel: i18n__namespace._("Fraction, with current expression in numerator")
|
|
431
|
-
},
|
|
432
|
-
// mobile native
|
|
433
|
-
[Keys.FRAC_EXCLUSIVE]: {
|
|
434
|
-
type: KeyTypes.OPERATOR,
|
|
435
|
-
// I18N: A label for a button that creates a new fraction next to the
|
|
436
|
-
// cursor.
|
|
437
|
-
ariaLabel: i18n__namespace._("Fraction, excluding the current expression")
|
|
438
|
-
},
|
|
439
|
-
// mobile web
|
|
440
|
-
[Keys.FRAC]: {
|
|
441
|
-
type: KeyTypes.OPERATOR,
|
|
442
|
-
// I18N: A label for a button that creates a new fraction next to the
|
|
443
|
-
// cursor.
|
|
444
|
-
ariaLabel: i18n__namespace._("Fraction, excluding the current expression")
|
|
445
|
-
},
|
|
446
|
-
[Keys.EXP]: {
|
|
447
|
-
type: KeyTypes.OPERATOR,
|
|
448
|
-
// I18N: A label for a button that will allow the user to input a custom
|
|
449
|
-
// exponent.
|
|
450
|
-
ariaLabel: i18n__namespace._("Custom exponent")
|
|
451
|
-
},
|
|
452
|
-
[Keys.EXP_2]: {
|
|
453
|
-
type: KeyTypes.OPERATOR,
|
|
454
|
-
// I18N: A label for a button that will square (take to the second
|
|
455
|
-
// power) some math.
|
|
456
|
-
ariaLabel: i18n__namespace._("Square")
|
|
457
|
-
},
|
|
458
|
-
[Keys.EXP_3]: {
|
|
459
|
-
type: KeyTypes.OPERATOR,
|
|
460
|
-
// I18N: A label for a button that will cube (take to the third power)
|
|
461
|
-
// some math.
|
|
462
|
-
ariaLabel: i18n__namespace._("Cube")
|
|
463
|
-
},
|
|
464
|
-
[Keys.SQRT]: {
|
|
465
|
-
type: KeyTypes.OPERATOR,
|
|
466
|
-
ariaLabel: i18n__namespace._("Square root")
|
|
603
|
+
str: "\\neq",
|
|
604
|
+
fn: WRITE
|
|
467
605
|
},
|
|
468
|
-
[Keys.
|
|
469
|
-
|
|
470
|
-
|
|
606
|
+
[Keys.CDOT]: {
|
|
607
|
+
str: "\\cdot",
|
|
608
|
+
fn: WRITE
|
|
471
609
|
},
|
|
472
|
-
[Keys.
|
|
473
|
-
|
|
474
|
-
|
|
610
|
+
[Keys.PERCENT]: {
|
|
611
|
+
str: "%",
|
|
612
|
+
fn: WRITE
|
|
475
613
|
},
|
|
476
614
|
[Keys.LEFT_PAREN]: {
|
|
477
|
-
|
|
478
|
-
|
|
615
|
+
str: "(",
|
|
616
|
+
fn: CMD
|
|
479
617
|
},
|
|
480
618
|
[Keys.RIGHT_PAREN]: {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
},
|
|
484
|
-
[Keys.LN]: {
|
|
485
|
-
type: KeyTypes.OPERATOR,
|
|
486
|
-
ariaLabel: i18n__namespace._("Natural logarithm")
|
|
487
|
-
},
|
|
488
|
-
[Keys.LOG]: {
|
|
489
|
-
type: KeyTypes.OPERATOR,
|
|
490
|
-
ariaLabel: i18n__namespace._("Logarithm with base 10")
|
|
491
|
-
},
|
|
492
|
-
[Keys.LOG_N]: {
|
|
493
|
-
type: KeyTypes.OPERATOR,
|
|
494
|
-
ariaLabel: i18n__namespace._("Logarithm with custom base")
|
|
495
|
-
},
|
|
496
|
-
[Keys.SIN]: {
|
|
497
|
-
type: KeyTypes.OPERATOR,
|
|
498
|
-
ariaLabel: i18n__namespace._("Sine")
|
|
499
|
-
},
|
|
500
|
-
[Keys.COS]: {
|
|
501
|
-
type: KeyTypes.OPERATOR,
|
|
502
|
-
ariaLabel: i18n__namespace._("Cosine")
|
|
619
|
+
str: ")",
|
|
620
|
+
fn: CMD
|
|
503
621
|
},
|
|
504
|
-
[Keys.
|
|
505
|
-
|
|
506
|
-
|
|
622
|
+
[Keys.SQRT]: {
|
|
623
|
+
str: "sqrt",
|
|
624
|
+
fn: CMD
|
|
507
625
|
},
|
|
508
626
|
[Keys.PI]: {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
icon: {
|
|
512
|
-
type: IconTypes.MATH,
|
|
513
|
-
data: "\\pi"
|
|
514
|
-
}
|
|
627
|
+
str: "pi",
|
|
628
|
+
fn: CMD
|
|
515
629
|
},
|
|
516
630
|
[Keys.THETA]: {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
icon: {
|
|
520
|
-
type: IconTypes.MATH,
|
|
521
|
-
data: "\\theta"
|
|
522
|
-
}
|
|
523
|
-
},
|
|
524
|
-
[Keys.NOOP]: {
|
|
525
|
-
type: KeyTypes.EMPTY
|
|
526
|
-
},
|
|
527
|
-
// Input navigation keys.
|
|
528
|
-
[Keys.UP]: {
|
|
529
|
-
type: KeyTypes.INPUT_NAVIGATION,
|
|
530
|
-
ariaLabel: i18n__namespace._("Up arrow")
|
|
531
|
-
},
|
|
532
|
-
[Keys.RIGHT]: {
|
|
533
|
-
type: KeyTypes.INPUT_NAVIGATION,
|
|
534
|
-
ariaLabel: i18n__namespace._("Right arrow")
|
|
535
|
-
},
|
|
536
|
-
[Keys.DOWN]: {
|
|
537
|
-
type: KeyTypes.INPUT_NAVIGATION,
|
|
538
|
-
ariaLabel: i18n__namespace._("Down arrow")
|
|
539
|
-
},
|
|
540
|
-
[Keys.LEFT]: {
|
|
541
|
-
type: KeyTypes.INPUT_NAVIGATION,
|
|
542
|
-
ariaLabel: i18n__namespace._("Left arrow")
|
|
631
|
+
str: "theta",
|
|
632
|
+
fn: CMD
|
|
543
633
|
},
|
|
544
|
-
[Keys.
|
|
545
|
-
|
|
546
|
-
|
|
634
|
+
[Keys.RADICAL]: {
|
|
635
|
+
str: "nthroot",
|
|
636
|
+
fn: CMD
|
|
547
637
|
},
|
|
548
|
-
[Keys.
|
|
549
|
-
|
|
550
|
-
|
|
638
|
+
[Keys.LT]: {
|
|
639
|
+
str: "<",
|
|
640
|
+
fn: WRITE
|
|
551
641
|
},
|
|
552
|
-
[Keys.
|
|
553
|
-
|
|
554
|
-
|
|
642
|
+
[Keys.LEQ]: {
|
|
643
|
+
str: "\\leq",
|
|
644
|
+
fn: WRITE
|
|
555
645
|
},
|
|
556
|
-
[Keys.
|
|
557
|
-
|
|
558
|
-
|
|
646
|
+
[Keys.GT]: {
|
|
647
|
+
str: ">",
|
|
648
|
+
fn: WRITE
|
|
559
649
|
},
|
|
560
|
-
[Keys.
|
|
561
|
-
|
|
562
|
-
|
|
650
|
+
[Keys.GEQ]: {
|
|
651
|
+
str: "\\geq",
|
|
652
|
+
fn: WRITE
|
|
563
653
|
},
|
|
564
|
-
[Keys.
|
|
565
|
-
|
|
566
|
-
|
|
654
|
+
[Keys.UP]: {
|
|
655
|
+
str: "Up",
|
|
656
|
+
fn: KEYSTROKE
|
|
567
657
|
},
|
|
568
|
-
[Keys.
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
ariaLabel: i18n__namespace._("Delete")
|
|
658
|
+
[Keys.DOWN]: {
|
|
659
|
+
str: "Down",
|
|
660
|
+
fn: KEYSTROKE
|
|
572
661
|
},
|
|
573
|
-
//
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
662
|
+
// The `FRAC_EXCLUSIVE` variant is handled manually, since we may need to do
|
|
663
|
+
// some additional navigation depending on the cursor position.
|
|
664
|
+
[Keys.FRAC_INCLUSIVE]: {
|
|
665
|
+
str: "/",
|
|
666
|
+
fn: CMD
|
|
578
667
|
}
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
668
|
+
};
|
|
669
|
+
const NormalCommands = {
|
|
670
|
+
[Keys.LOG]: "log",
|
|
671
|
+
[Keys.LN]: "ln",
|
|
672
|
+
[Keys.SIN]: "sin",
|
|
673
|
+
[Keys.COS]: "cos",
|
|
674
|
+
[Keys.TAN]: "tan"
|
|
675
|
+
};
|
|
676
|
+
const ArithmeticOperators = ["+", "-", "\\cdot", "\\times", "\\div"];
|
|
677
|
+
const EqualityOperators = ["=", "\\neq", "<", "\\leq", ">", "\\geq"];
|
|
678
|
+
const Numerals = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
|
|
679
|
+
const GreekLetters = ["\\theta", "\\pi"];
|
|
680
|
+
const Letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]; // We only consider numerals, variables, and Greek Letters to be proper
|
|
681
|
+
// leaf nodes.
|
|
589
682
|
|
|
590
|
-
|
|
591
|
-
|
|
683
|
+
const ValidLeaves = [...Numerals, ...GreekLetters, ...Letters.map(letter => letter.toLowerCase()), ...Letters.map(letter => letter.toUpperCase())];
|
|
684
|
+
const KeysForJumpContext = {
|
|
685
|
+
[IN_PARENS]: Keys.JUMP_OUT_PARENTHESES,
|
|
686
|
+
[IN_SUPER_SCRIPT]: Keys.JUMP_OUT_EXPONENT,
|
|
687
|
+
[IN_SUB_SCRIPT]: Keys.JUMP_OUT_BASE,
|
|
688
|
+
[BEFORE_FRACTION]: Keys.JUMP_INTO_NUMERATOR,
|
|
689
|
+
[IN_NUMERATOR]: Keys.JUMP_OUT_NUMERATOR,
|
|
690
|
+
[IN_DENOMINATOR]: Keys.JUMP_OUT_DENOMINATOR
|
|
691
|
+
};
|
|
592
692
|
|
|
593
|
-
|
|
693
|
+
class MathWrapper {
|
|
694
|
+
constructor(element) {
|
|
695
|
+
let callbacks = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
696
|
+
this.MQ = MathQuill__default["default"].getInterface(2);
|
|
697
|
+
this.mathField = this.MQ.MathField(element, {
|
|
698
|
+
// use a span instead of a textarea so that we don't bring up the
|
|
699
|
+
// native keyboard on mobile when selecting the input
|
|
700
|
+
substituteTextarea: function () {
|
|
701
|
+
return document.createElement("span");
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
this.callbacks = callbacks;
|
|
705
|
+
}
|
|
594
706
|
|
|
595
|
-
|
|
707
|
+
focus() {
|
|
708
|
+
// HACK(charlie): We shouldn't reaching into MathQuill internals like
|
|
709
|
+
// this, but it's the easiest way to allow us to manage the focus state
|
|
710
|
+
// ourselves.
|
|
711
|
+
const controller = this.mathField.__controller;
|
|
712
|
+
controller.cursor.show(); // Set MathQuill's internal state to reflect the focus, otherwise it
|
|
713
|
+
// will consistently try to hide the cursor on key-press and introduce
|
|
714
|
+
// layout jank.
|
|
596
715
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
// numeral keys. They can be rendered just as easily with text (though that
|
|
600
|
-
// would mean that we'd be using text beyond the variable key).
|
|
601
|
-
const textRepresentation = "".concat(num);
|
|
602
|
-
KeyConfigs["NUM_".concat(num)] = {
|
|
603
|
-
type: KeyTypes.VALUE,
|
|
604
|
-
ariaLabel: textRepresentation,
|
|
605
|
-
icon: {
|
|
606
|
-
type: IconTypes.TEXT,
|
|
607
|
-
data: textRepresentation
|
|
608
|
-
}
|
|
609
|
-
};
|
|
610
|
-
} // Add in every variable.
|
|
716
|
+
controller.blurred = false;
|
|
717
|
+
}
|
|
611
718
|
|
|
719
|
+
blur() {
|
|
720
|
+
const controller = this.mathField.__controller;
|
|
721
|
+
controller.cursor.hide();
|
|
722
|
+
controller.blurred = true;
|
|
723
|
+
}
|
|
612
724
|
|
|
613
|
-
|
|
725
|
+
_writeNormalFunction(name) {
|
|
726
|
+
this.mathField.write("\\".concat(name, "\\left(\\right)"));
|
|
727
|
+
this.mathField.keystroke("Left");
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Handle a key press and return the resulting cursor state.
|
|
731
|
+
*
|
|
732
|
+
* @param {Key} key - an enum representing the key that was pressed
|
|
733
|
+
* @returns {object} a cursor object, consisting of a cursor context
|
|
734
|
+
*/
|
|
614
735
|
|
|
615
|
-
for (const letter of LETTERS) {
|
|
616
|
-
const lowerCaseVariable = letter.toLowerCase();
|
|
617
|
-
const upperCaseVariable = letter.toUpperCase();
|
|
618
736
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
737
|
+
pressKey(key) {
|
|
738
|
+
const cursor = this.mathField.__controller.cursor;
|
|
739
|
+
|
|
740
|
+
if (key in KeyActions) {
|
|
741
|
+
const {
|
|
742
|
+
str,
|
|
743
|
+
fn
|
|
744
|
+
} = KeyActions[key];
|
|
745
|
+
|
|
746
|
+
if (str && fn) {
|
|
747
|
+
this.mathField[fn](str);
|
|
748
|
+
}
|
|
749
|
+
} else if (Object.keys(NormalCommands).includes(key)) {
|
|
750
|
+
this._writeNormalFunction(NormalCommands[key]);
|
|
751
|
+
} else if (key === Keys.FRAC_EXCLUSIVE) {
|
|
752
|
+
// If there's nothing to the left of the cursor, then we want to
|
|
753
|
+
// leave the cursor to the left of the fraction after creating it.
|
|
754
|
+
const shouldNavigateLeft = cursor[this.MQ.L] === MQ_END;
|
|
755
|
+
this.mathField.cmd("\\frac");
|
|
756
|
+
|
|
757
|
+
if (shouldNavigateLeft) {
|
|
758
|
+
this.mathField.keystroke("Left");
|
|
626
759
|
}
|
|
760
|
+
} else if (key === Keys.FRAC) {
|
|
761
|
+
// eslint-disable-next-line no-unused-vars
|
|
762
|
+
cursor[this.MQ.L] === MQ_END;
|
|
763
|
+
this.mathField.cmd("\\frac");
|
|
764
|
+
} else if (key === Keys.LOG_N) {
|
|
765
|
+
this.mathField.write("log_{ }\\left(\\right)");
|
|
766
|
+
this.mathField.keystroke("Left"); // into parentheses
|
|
767
|
+
|
|
768
|
+
this.mathField.keystroke("Left"); // out of parentheses
|
|
769
|
+
|
|
770
|
+
this.mathField.keystroke("Left"); // into index
|
|
771
|
+
} else if (key === Keys.CUBE_ROOT) {
|
|
772
|
+
this.mathField.write("\\sqrt[3]{}");
|
|
773
|
+
this.mathField.keystroke("Left"); // under the root
|
|
774
|
+
} else if (key === Keys.EXP || key === Keys.EXP_2 || key === Keys.EXP_3) {
|
|
775
|
+
this._handleExponent(cursor, key);
|
|
776
|
+
} else if (key === Keys.JUMP_OUT_PARENTHESES || key === Keys.JUMP_OUT_EXPONENT || key === Keys.JUMP_OUT_BASE || key === Keys.JUMP_INTO_NUMERATOR || key === Keys.JUMP_OUT_NUMERATOR || key === Keys.JUMP_OUT_DENOMINATOR) {
|
|
777
|
+
this._handleJumpOut(cursor, key);
|
|
778
|
+
} else if (key === Keys.BACKSPACE) {
|
|
779
|
+
this._handleBackspace(cursor);
|
|
780
|
+
} else if (key === Keys.LEFT) {
|
|
781
|
+
this._handleLeftArrow(cursor);
|
|
782
|
+
} else if (key === Keys.RIGHT || key === Keys.JUMP_OUT) {
|
|
783
|
+
this._handleRightArrow(cursor);
|
|
784
|
+
} else if (/^[a-zA-Z]$/.test(key)) {
|
|
785
|
+
this.mathField[WRITE](key);
|
|
786
|
+
} else if (/^NUM_\d/.test(key)) {
|
|
787
|
+
this.mathField[WRITE](key[4]);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (!cursor.selection) {
|
|
791
|
+
// don't show the cursor for selections
|
|
792
|
+
cursor.show();
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (this.callbacks.onSelectionChanged) {
|
|
796
|
+
this.callbacks.onSelectionChanged(cursor.selection);
|
|
797
|
+
} // NOTE(charlie): It's insufficient to do this as an `edited` handler
|
|
798
|
+
// on the MathField, as that handler isn't triggered on navigation
|
|
799
|
+
// events.
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
return {
|
|
803
|
+
context: this.contextForCursor(cursor)
|
|
627
804
|
};
|
|
628
805
|
}
|
|
629
|
-
|
|
806
|
+
/**
|
|
807
|
+
* Place the cursor beside the node located at the given coordinates.
|
|
808
|
+
*
|
|
809
|
+
* @param {number} x - the x coordinate in the viewport
|
|
810
|
+
* @param {number} y - the y coordinate in the viewport
|
|
811
|
+
* @param {Node} hitNode - the node next to which the cursor should be
|
|
812
|
+
* placed; if provided, the coordinates will be used
|
|
813
|
+
* to determine on which side of the node the cursor
|
|
814
|
+
* should be placed
|
|
815
|
+
*/
|
|
630
816
|
|
|
631
|
-
for (const key of Object.keys(KeyConfigs)) {
|
|
632
|
-
KeyConfigs[key] = {
|
|
633
|
-
id: key,
|
|
634
|
-
// Default to an SVG icon indexed by the key name.
|
|
635
|
-
icon: {
|
|
636
|
-
type: IconTypes.SVG,
|
|
637
|
-
data: key
|
|
638
|
-
},
|
|
639
|
-
...KeyConfigs[key]
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
817
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
* active context is determined first by looking at the cursor's siblings (e.g.,
|
|
646
|
-
* for the `BEFORE_FRACTION` context), and then at its direct parent. Though a
|
|
647
|
-
* cursor could in theory be nested in multiple contexts, we only care about the
|
|
648
|
-
* immediate context.
|
|
649
|
-
*
|
|
650
|
-
* TODO(charlie): Add a context to represent being inside of a radical. Right
|
|
651
|
-
* now, we show the dismiss button rather than allowing the user to jump out of
|
|
652
|
-
* the radical.
|
|
653
|
-
*/
|
|
654
|
-
// TODO: Get rid of these constants in favour of CursorContext type.
|
|
655
|
-
// The cursor is not in any of the other viable contexts.
|
|
656
|
-
const NONE = "NONE"; // The cursor is within a set of parentheses.
|
|
818
|
+
setCursorPosition(x, y, hitNode) {
|
|
819
|
+
const el = hitNode || document.elementFromPoint(x, y);
|
|
657
820
|
|
|
658
|
-
|
|
821
|
+
if (el) {
|
|
822
|
+
const cursor = this.getCursor();
|
|
659
823
|
|
|
660
|
-
|
|
824
|
+
if (el.hasAttribute("mq-root-block")) {
|
|
825
|
+
// If we're in the empty area place the cursor at the right
|
|
826
|
+
// end of the expression.
|
|
827
|
+
cursor.insAtRightEnd(this.mathField.__controller.root);
|
|
828
|
+
} else {
|
|
829
|
+
// Otherwise place beside the element at x, y.
|
|
830
|
+
const controller = this.mathField.__controller;
|
|
831
|
+
const pageX = x - document.body.scrollLeft;
|
|
832
|
+
const pageY = y - document.body.scrollTop;
|
|
833
|
+
controller.seek($__default["default"](el), pageX, pageY).cursor.startSelection(); // Unless that would leave us mid-command, in which case, we
|
|
834
|
+
// need to adjust and place the cursor inside the parens
|
|
835
|
+
// following the command.
|
|
661
836
|
|
|
662
|
-
const
|
|
837
|
+
const command = this._maybeFindCommand(cursor[this.MQ.L]);
|
|
663
838
|
|
|
664
|
-
|
|
839
|
+
if (command && command.endNode) {
|
|
840
|
+
// NOTE(charlie): endNode should definitely be \left(.
|
|
841
|
+
cursor.insLeftOf(command.endNode);
|
|
842
|
+
this.mathField.keystroke("Right");
|
|
843
|
+
}
|
|
844
|
+
}
|
|
665
845
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
846
|
+
if (this.callbacks.onCursorMove) {
|
|
847
|
+
this.callbacks.onCursorMove({
|
|
848
|
+
context: this.contextForCursor(cursor)
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
670
853
|
|
|
671
|
-
|
|
854
|
+
getCursor() {
|
|
855
|
+
return this.mathField.__controller.cursor;
|
|
856
|
+
}
|
|
672
857
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
IN_PARENS: IN_PARENS,
|
|
677
|
-
IN_SUPER_SCRIPT: IN_SUPER_SCRIPT,
|
|
678
|
-
IN_SUB_SCRIPT: IN_SUB_SCRIPT,
|
|
679
|
-
IN_NUMERATOR: IN_NUMERATOR,
|
|
680
|
-
IN_DENOMINATOR: IN_DENOMINATOR,
|
|
681
|
-
BEFORE_FRACTION: BEFORE_FRACTION
|
|
682
|
-
});
|
|
858
|
+
getSelection() {
|
|
859
|
+
return this.getCursor().selection;
|
|
860
|
+
}
|
|
683
861
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
const iconPropType = PropTypes__default["default"].shape({
|
|
688
|
-
type: PropTypes__default["default"].oneOf(Object.keys(IconTypes)).isRequired,
|
|
689
|
-
data: PropTypes__default["default"].string.isRequired
|
|
690
|
-
});
|
|
691
|
-
const keyIdPropType = PropTypes__default["default"].oneOf(Object.keys(KeyConfigs));
|
|
692
|
-
const keyConfigPropType = PropTypes__default["default"].shape({
|
|
693
|
-
ariaLabel: PropTypes__default["default"].string,
|
|
694
|
-
id: keyIdPropType.isRequired,
|
|
695
|
-
type: PropTypes__default["default"].oneOf(Object.keys(KeyTypes)).isRequired,
|
|
696
|
-
childKeyIds: PropTypes__default["default"].arrayOf(keyIdPropType),
|
|
697
|
-
icon: iconPropType.isRequired
|
|
698
|
-
});
|
|
699
|
-
const keypadConfigurationPropType = PropTypes__default["default"].shape({
|
|
700
|
-
keypadType: PropTypes__default["default"].oneOf(Object.keys(KeypadTypes)).isRequired,
|
|
701
|
-
extraKeys: PropTypes__default["default"].arrayOf(keyIdPropType)
|
|
702
|
-
}); // NOTE(jared): This is no longer guaranteed to be React element
|
|
862
|
+
getContent() {
|
|
863
|
+
return this.mathField.latex();
|
|
864
|
+
}
|
|
703
865
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
dismiss: PropTypes__default["default"].func.isRequired,
|
|
707
|
-
configure: PropTypes__default["default"].func.isRequired,
|
|
708
|
-
setCursor: PropTypes__default["default"].func.isRequired,
|
|
709
|
-
setKeyHandler: PropTypes__default["default"].func.isRequired,
|
|
710
|
-
getDOMNode: PropTypes__default["default"].func.isRequired
|
|
711
|
-
});
|
|
712
|
-
const bordersPropType = PropTypes__default["default"].arrayOf(PropTypes__default["default"].oneOf(Object.keys(BorderDirections)));
|
|
713
|
-
const boundingBoxPropType = PropTypes__default["default"].shape({
|
|
714
|
-
height: PropTypes__default["default"].number,
|
|
715
|
-
width: PropTypes__default["default"].number,
|
|
716
|
-
top: PropTypes__default["default"].number,
|
|
717
|
-
right: PropTypes__default["default"].number,
|
|
718
|
-
bottom: PropTypes__default["default"].number,
|
|
719
|
-
left: PropTypes__default["default"].number
|
|
720
|
-
});
|
|
721
|
-
const echoPropType = PropTypes__default["default"].shape({
|
|
722
|
-
animationId: PropTypes__default["default"].string.isRequired,
|
|
723
|
-
animationType: PropTypes__default["default"].oneOf(Object.keys(EchoAnimationTypes)).isRequired,
|
|
724
|
-
borders: bordersPropType,
|
|
725
|
-
id: keyIdPropType.isRequired,
|
|
726
|
-
initialBounds: boundingBoxPropType.isRequired
|
|
727
|
-
});
|
|
728
|
-
const cursorContextPropType = PropTypes__default["default"].oneOf(Object.keys(CursorContexts));
|
|
729
|
-
const popoverPropType = PropTypes__default["default"].shape({
|
|
730
|
-
parentId: keyIdPropType.isRequired,
|
|
731
|
-
bounds: boundingBoxPropType.isRequired,
|
|
732
|
-
childKeyIds: PropTypes__default["default"].arrayOf(keyIdPropType).isRequired
|
|
733
|
-
});
|
|
734
|
-
PropTypes__default["default"].oneOfType([PropTypes__default["default"].arrayOf(PropTypes__default["default"].node), PropTypes__default["default"].node]);
|
|
735
|
-
|
|
736
|
-
const touchTargetRadiusPx = 2 * cursorHandleRadiusPx;
|
|
737
|
-
const touchTargetHeightPx = 2 * touchTargetRadiusPx;
|
|
738
|
-
const touchTargetWidthPx = 2 * touchTargetRadiusPx;
|
|
739
|
-
const cursorRadiusPx = cursorHandleRadiusPx;
|
|
740
|
-
const cursorHeightPx = cursorHandleDistanceMultiplier * (cursorRadiusPx * 4);
|
|
741
|
-
const cursorWidthPx = 4 * cursorRadiusPx;
|
|
742
|
-
|
|
743
|
-
class CursorHandle extends React__namespace.Component {
|
|
744
|
-
render() {
|
|
745
|
-
const {
|
|
746
|
-
x,
|
|
747
|
-
y,
|
|
748
|
-
animateIntoPosition
|
|
749
|
-
} = this.props;
|
|
750
|
-
const animationStyle = animateIntoPosition ? {
|
|
751
|
-
msTransitionDuration: "100ms",
|
|
752
|
-
WebkitTransitionDuration: "100ms",
|
|
753
|
-
transitionDuration: "100ms",
|
|
754
|
-
msTransitionProperty: "transform",
|
|
755
|
-
WebkitTransitionProperty: "transform",
|
|
756
|
-
transitionProperty: "transform"
|
|
757
|
-
} : {};
|
|
758
|
-
const transformString = "translate(".concat(x, "px, ").concat(y, "px)");
|
|
759
|
-
const outerStyle = {
|
|
760
|
-
position: "absolute",
|
|
761
|
-
// This is essentially webapp's interactiveComponent + 1.
|
|
762
|
-
// TODO(charlie): Pull in those styles somehow to avoid breakages.
|
|
763
|
-
zIndex: 4,
|
|
764
|
-
left: -touchTargetWidthPx / 2,
|
|
765
|
-
top: 0,
|
|
766
|
-
msTransform: transformString,
|
|
767
|
-
WebkitTransform: transformString,
|
|
768
|
-
transform: transformString,
|
|
769
|
-
width: touchTargetWidthPx,
|
|
770
|
-
height: touchTargetHeightPx,
|
|
771
|
-
// Touch events that start on the cursor shouldn't be allowed to
|
|
772
|
-
// produce page scrolls.
|
|
773
|
-
touchAction: "none",
|
|
774
|
-
...animationStyle
|
|
775
|
-
};
|
|
776
|
-
return /*#__PURE__*/React__namespace.createElement("span", {
|
|
777
|
-
style: outerStyle,
|
|
778
|
-
onTouchStart: this.props.onTouchStart,
|
|
779
|
-
onTouchMove: this.props.onTouchMove,
|
|
780
|
-
onTouchEnd: this.props.onTouchEnd,
|
|
781
|
-
onTouchCancel: this.props.onTouchCancel
|
|
782
|
-
}, /*#__PURE__*/React__namespace.createElement("svg", {
|
|
783
|
-
fill: "none",
|
|
784
|
-
width: cursorWidthPx,
|
|
785
|
-
height: cursorHeightPx,
|
|
786
|
-
viewBox: "0 0 ".concat(cursorWidthPx, " ").concat(cursorHeightPx)
|
|
787
|
-
}, /*#__PURE__*/React__namespace.createElement("filter", {
|
|
788
|
-
id: "math-input_cursor",
|
|
789
|
-
colorInterpolationFilters: "sRGB",
|
|
790
|
-
filterUnits: "userSpaceOnUse",
|
|
791
|
-
height: cursorHeightPx * 0.87 // ~40
|
|
792
|
-
,
|
|
793
|
-
width: cursorWidthPx * 0.82 // ~36
|
|
794
|
-
,
|
|
795
|
-
x: "4",
|
|
796
|
-
y: "0"
|
|
797
|
-
}, /*#__PURE__*/React__namespace.createElement("feFlood", {
|
|
798
|
-
floodOpacity: "0",
|
|
799
|
-
result: "BackgroundImageFix"
|
|
800
|
-
}), /*#__PURE__*/React__namespace.createElement("feColorMatrix", {
|
|
801
|
-
in: "SourceAlpha",
|
|
802
|
-
type: "matrix",
|
|
803
|
-
values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
|
804
|
-
}), /*#__PURE__*/React__namespace.createElement("feOffset", {
|
|
805
|
-
dy: "4"
|
|
806
|
-
}), /*#__PURE__*/React__namespace.createElement("feGaussianBlur", {
|
|
807
|
-
stdDeviation: "4"
|
|
808
|
-
}), /*#__PURE__*/React__namespace.createElement("feColorMatrix", {
|
|
809
|
-
type: "matrix",
|
|
810
|
-
values: "0 0 0 0 0.129412 0 0 0 0 0.141176 0 0 0 0 0.172549 0 0 0 0.08 0"
|
|
811
|
-
}), /*#__PURE__*/React__namespace.createElement("feBlend", {
|
|
812
|
-
in2: "BackgroundImageFix",
|
|
813
|
-
mode: "normal",
|
|
814
|
-
result: "effect1_dropShadow"
|
|
815
|
-
}), /*#__PURE__*/React__namespace.createElement("feBlend", {
|
|
816
|
-
in: "SourceGraphic",
|
|
817
|
-
in2: "effect1_dropShadow",
|
|
818
|
-
mode: "normal",
|
|
819
|
-
result: "shape"
|
|
820
|
-
})), /*#__PURE__*/React__namespace.createElement("g", {
|
|
821
|
-
filter: "url(#math-input_cursor)"
|
|
822
|
-
}, /*#__PURE__*/React__namespace.createElement("path", {
|
|
823
|
-
d: "m22 4-7.07 7.0284c-1.3988 1.3901-2.3515 3.1615-2.7376 5.09-.3861 1.9284-.1883 3.9274.5685 5.7441s2.0385 3.3694 3.6831 4.4619c1.6445 1.0925 3.5781 1.6756 5.556 1.6756s3.9115-.5831 5.556-1.6756c1.6446-1.0925 2.9263-2.6452 3.6831-4.4619s.9546-3.8157.5685-5.7441c-.3861-1.9285-1.3388-3.6999-2.7376-5.09z",
|
|
824
|
-
fill: "#1865f2"
|
|
825
|
-
})), /*#__PURE__*/React__namespace.createElement("path", {
|
|
826
|
-
d: "m14.9301 10.4841 7.0699-7.06989 7.0699 7.06989.0001.0001c1.3988 1.3984 2.3515 3.1802 2.7376 5.1201s.1883 3.9507-.5685 5.7782c-.7568 1.8274-2.0385 3.3894-3.6831 4.4883-1.6445 1.099-3.5781 1.6855-5.556 1.6855s-3.9115-.5865-5.556-1.6855c-1.6446-1.0989-2.9263-2.6609-3.6831-4.4883-.7568-1.8275-.9546-3.8383-.5685-5.7782s1.3388-3.7217 2.7376-5.1201z",
|
|
827
|
-
stroke: "#fff",
|
|
828
|
-
strokeWidth: "2"
|
|
829
|
-
})));
|
|
866
|
+
setContent(latex) {
|
|
867
|
+
this.mathField.latex(latex);
|
|
830
868
|
}
|
|
831
869
|
|
|
832
|
-
|
|
870
|
+
isEmpty() {
|
|
871
|
+
const cursor = this.getCursor();
|
|
872
|
+
return cursor.parent.id === 1 && cursor[1] === 0 && cursor[-1] === 0;
|
|
873
|
+
} // Notes about MathQuill
|
|
874
|
+
//
|
|
875
|
+
// MathQuill's stores its layout as nested linked lists. Each node in the
|
|
876
|
+
// list has this.MQ.L '-1' and this.MQ.R '1' properties that define links to
|
|
877
|
+
// the left and right nodes respectively. They also have
|
|
878
|
+
//
|
|
879
|
+
// ctrlSeq: contains the latex code snippet that defines that node.
|
|
880
|
+
// jQ: jQuery object for the DOM node(s) for this MathQuill node.
|
|
881
|
+
// ends: pointers to the nodes at the ends of the container.
|
|
882
|
+
// parent: parent node.
|
|
883
|
+
// blocks: an array containing one or more nodes that make up the node.
|
|
884
|
+
// sub?: subscript node if there is one as is the case in log_n
|
|
885
|
+
//
|
|
886
|
+
// All of the code below is super fragile. Please be especially careful
|
|
887
|
+
// when upgrading MathQuill.
|
|
833
888
|
|
|
834
|
-
_defineProperty(CursorHandle, "propTypes", {
|
|
835
|
-
animateIntoPosition: PropTypes__default["default"].bool,
|
|
836
|
-
onTouchCancel: PropTypes__default["default"].func.isRequired,
|
|
837
|
-
onTouchEnd: PropTypes__default["default"].func.isRequired,
|
|
838
|
-
onTouchMove: PropTypes__default["default"].func.isRequired,
|
|
839
|
-
onTouchStart: PropTypes__default["default"].func.isRequired,
|
|
840
|
-
visible: PropTypes__default["default"].bool.isRequired,
|
|
841
|
-
x: PropTypes__default["default"].number.isRequired,
|
|
842
|
-
y: PropTypes__default["default"].number.isRequired
|
|
843
|
-
});
|
|
844
889
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
visible: false,
|
|
848
|
-
x: 0,
|
|
849
|
-
y: 0
|
|
850
|
-
});
|
|
890
|
+
_handleBackspaceInNthRoot(cursor) {
|
|
891
|
+
const isAtLeftEnd = cursor[this.MQ.L] === MQ_END;
|
|
851
892
|
|
|
852
|
-
|
|
853
|
-
* A gesture recognizer that detects 'drags', crudely defined as either scrolls
|
|
854
|
-
* or touches that move a sufficient distance.
|
|
855
|
-
*/
|
|
856
|
-
// The 'slop' factor, after which we consider the use to be dragging. The value
|
|
857
|
-
// is taken from the Android SDK. It won't be robust to page zoom and the like,
|
|
858
|
-
// but it should be good enough for our purposes.
|
|
859
|
-
const touchSlopPx = 8;
|
|
893
|
+
const isRootEmpty = this._isInsideEmptyNode(cursor.parent.parent.blocks[0].ends);
|
|
860
894
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
|
|
895
|
+
if (isAtLeftEnd) {
|
|
896
|
+
this._selectNode(cursor.parent.parent, cursor);
|
|
897
|
+
|
|
898
|
+
if (isRootEmpty) {
|
|
899
|
+
this.mathField.keystroke("Backspace");
|
|
900
|
+
}
|
|
901
|
+
} else {
|
|
902
|
+
this.mathField.keystroke("Backspace");
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Advances the cursor to the next logical position.
|
|
907
|
+
*
|
|
908
|
+
* @param {cursor} cursor
|
|
909
|
+
* @private
|
|
910
|
+
*/
|
|
870
911
|
|
|
871
912
|
|
|
872
|
-
|
|
913
|
+
_handleJumpOut(cursor, key) {
|
|
914
|
+
const context = this.contextForCursor(cursor); // Validate that the current cursor context matches the key's intent.
|
|
873
915
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
916
|
+
if (KeysForJumpContext[context] !== key) {
|
|
917
|
+
// If we don't have a valid cursor context, yet the user was able
|
|
918
|
+
// to trigger a jump-out key, that's a broken invariant. Rather
|
|
919
|
+
// than throw an error (which would kick the user out of the
|
|
920
|
+
// exercise), we do nothing, as a fallback strategy. The user can
|
|
921
|
+
// still move the cursor manually.
|
|
922
|
+
return;
|
|
877
923
|
}
|
|
878
924
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
925
|
+
switch (context) {
|
|
926
|
+
case IN_PARENS:
|
|
927
|
+
// Insert at the end of the parentheses, and then navigate right
|
|
928
|
+
// once more to get 'beyond' the parentheses.
|
|
929
|
+
cursor.insRightOf(cursor.parent.parent);
|
|
930
|
+
break;
|
|
883
931
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
const squaredDist = dx * dx + dy * dy;
|
|
889
|
-
const squaredTouchSlop = touchSlopPx * touchSlopPx;
|
|
932
|
+
case BEFORE_FRACTION:
|
|
933
|
+
// Find the nearest fraction to the right of the cursor.
|
|
934
|
+
let fractionNode;
|
|
935
|
+
let visitor = cursor;
|
|
890
936
|
|
|
891
|
-
|
|
892
|
-
|
|
937
|
+
while (visitor[this.MQ.R] !== MQ_END) {
|
|
938
|
+
if (this._isFraction(visitor[this.MQ.R])) {
|
|
939
|
+
fractionNode = visitor[this.MQ.R];
|
|
893
940
|
}
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}; // Clean-up any terminated gestures, since some browsers reuse
|
|
897
|
-
// identifiers.
|
|
898
|
-
|
|
899
941
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
delete touchLocationsById[evt.changedTouches[i].identifier];
|
|
903
|
-
}
|
|
904
|
-
};
|
|
905
|
-
}
|
|
942
|
+
visitor = visitor[this.MQ.R];
|
|
943
|
+
} // Jump into it!
|
|
906
944
|
|
|
907
|
-
attach() {
|
|
908
|
-
window.addEventListener("scroll", this._scrollListener);
|
|
909
|
-
window.addEventListener("touchmove", this._moveListener);
|
|
910
|
-
window.addEventListener("touchend", this._endAndCancelListener);
|
|
911
|
-
window.addEventListener("touchcancel", this._endAndCancelListener);
|
|
912
|
-
}
|
|
913
945
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
window.removeEventListener("touchend", this._endAndCancelListener);
|
|
918
|
-
window.removeEventListener("touchcancel", this._endAndCancelListener);
|
|
919
|
-
}
|
|
946
|
+
cursor.insLeftOf(fractionNode);
|
|
947
|
+
this.mathField.keystroke("Right");
|
|
948
|
+
break;
|
|
920
949
|
|
|
921
|
-
|
|
950
|
+
case IN_NUMERATOR:
|
|
951
|
+
// HACK(charlie): I can't find a better way to do this. The goal
|
|
952
|
+
// is to place the cursor at the start of the matching
|
|
953
|
+
// denominator. So, we identify the appropriate node, and
|
|
954
|
+
// continue rightwards until we find ourselves inside of it.
|
|
955
|
+
// It's possible that there are cases in which we don't reach
|
|
956
|
+
// the denominator, though I can't think of any.
|
|
957
|
+
const siblingDenominator = cursor.parent.parent.blocks[1];
|
|
922
958
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
* from MathQuill changes.
|
|
927
|
-
*/
|
|
928
|
-
// If it does not exist, fall back to CommonJS require. - jsatk
|
|
929
|
-
|
|
930
|
-
const decimalSymbol = decimalSeparator === DecimalSeparators.COMMA ? "," : ".";
|
|
931
|
-
const WRITE = "write";
|
|
932
|
-
const CMD = "cmd";
|
|
933
|
-
const KEYSTROKE = "keystroke";
|
|
934
|
-
const MQ_END = 0; // A mapping from keys that can be pressed on a keypad to the way in which
|
|
935
|
-
// MathQuill should modify its input in response to that key-press. Any keys
|
|
936
|
-
// that do not provide explicit actions (like the numeral keys) will merely
|
|
937
|
-
// write their contents to MathQuill.
|
|
959
|
+
while (cursor.parent !== siblingDenominator) {
|
|
960
|
+
this.mathField.keystroke("Right");
|
|
961
|
+
}
|
|
938
962
|
|
|
939
|
-
|
|
940
|
-
[Keys.PLUS]: {
|
|
941
|
-
str: "+",
|
|
942
|
-
fn: WRITE
|
|
943
|
-
},
|
|
944
|
-
[Keys.MINUS]: {
|
|
945
|
-
str: "-",
|
|
946
|
-
fn: WRITE
|
|
947
|
-
},
|
|
948
|
-
[Keys.NEGATIVE]: {
|
|
949
|
-
str: "-",
|
|
950
|
-
fn: WRITE
|
|
951
|
-
},
|
|
952
|
-
[Keys.TIMES]: {
|
|
953
|
-
str: "\\times",
|
|
954
|
-
fn: WRITE
|
|
955
|
-
},
|
|
956
|
-
[Keys.DIVIDE]: {
|
|
957
|
-
str: "\\div",
|
|
958
|
-
fn: WRITE
|
|
959
|
-
},
|
|
960
|
-
[Keys.DECIMAL]: {
|
|
961
|
-
str: decimalSymbol,
|
|
962
|
-
fn: WRITE
|
|
963
|
-
},
|
|
964
|
-
[Keys.EQUAL]: {
|
|
965
|
-
str: "=",
|
|
966
|
-
fn: WRITE
|
|
967
|
-
},
|
|
968
|
-
[Keys.NEQ]: {
|
|
969
|
-
str: "\\neq",
|
|
970
|
-
fn: WRITE
|
|
971
|
-
},
|
|
972
|
-
[Keys.CDOT]: {
|
|
973
|
-
str: "\\cdot",
|
|
974
|
-
fn: WRITE
|
|
975
|
-
},
|
|
976
|
-
[Keys.PERCENT]: {
|
|
977
|
-
str: "%",
|
|
978
|
-
fn: WRITE
|
|
979
|
-
},
|
|
980
|
-
[Keys.LEFT_PAREN]: {
|
|
981
|
-
str: "(",
|
|
982
|
-
fn: CMD
|
|
983
|
-
},
|
|
984
|
-
[Keys.RIGHT_PAREN]: {
|
|
985
|
-
str: ")",
|
|
986
|
-
fn: CMD
|
|
987
|
-
},
|
|
988
|
-
[Keys.SQRT]: {
|
|
989
|
-
str: "sqrt",
|
|
990
|
-
fn: CMD
|
|
991
|
-
},
|
|
992
|
-
[Keys.PI]: {
|
|
993
|
-
str: "pi",
|
|
994
|
-
fn: CMD
|
|
995
|
-
},
|
|
996
|
-
[Keys.THETA]: {
|
|
997
|
-
str: "theta",
|
|
998
|
-
fn: CMD
|
|
999
|
-
},
|
|
1000
|
-
[Keys.RADICAL]: {
|
|
1001
|
-
str: "nthroot",
|
|
1002
|
-
fn: CMD
|
|
1003
|
-
},
|
|
1004
|
-
[Keys.LT]: {
|
|
1005
|
-
str: "<",
|
|
1006
|
-
fn: WRITE
|
|
1007
|
-
},
|
|
1008
|
-
[Keys.LEQ]: {
|
|
1009
|
-
str: "\\leq",
|
|
1010
|
-
fn: WRITE
|
|
1011
|
-
},
|
|
1012
|
-
[Keys.GT]: {
|
|
1013
|
-
str: ">",
|
|
1014
|
-
fn: WRITE
|
|
1015
|
-
},
|
|
1016
|
-
[Keys.GEQ]: {
|
|
1017
|
-
str: "\\geq",
|
|
1018
|
-
fn: WRITE
|
|
1019
|
-
},
|
|
1020
|
-
[Keys.UP]: {
|
|
1021
|
-
str: "Up",
|
|
1022
|
-
fn: KEYSTROKE
|
|
1023
|
-
},
|
|
1024
|
-
[Keys.DOWN]: {
|
|
1025
|
-
str: "Down",
|
|
1026
|
-
fn: KEYSTROKE
|
|
1027
|
-
},
|
|
1028
|
-
// The `FRAC_EXCLUSIVE` variant is handled manually, since we may need to do
|
|
1029
|
-
// some additional navigation depending on the cursor position.
|
|
1030
|
-
[Keys.FRAC_INCLUSIVE]: {
|
|
1031
|
-
str: "/",
|
|
1032
|
-
fn: CMD
|
|
1033
|
-
}
|
|
1034
|
-
};
|
|
1035
|
-
const NormalCommands = {
|
|
1036
|
-
[Keys.LOG]: "log",
|
|
1037
|
-
[Keys.LN]: "ln",
|
|
1038
|
-
[Keys.SIN]: "sin",
|
|
1039
|
-
[Keys.COS]: "cos",
|
|
1040
|
-
[Keys.TAN]: "tan"
|
|
1041
|
-
};
|
|
1042
|
-
const ArithmeticOperators = ["+", "-", "\\cdot", "\\times", "\\div"];
|
|
1043
|
-
const EqualityOperators = ["=", "\\neq", "<", "\\leq", ">", "\\geq"];
|
|
1044
|
-
const Numerals = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
|
|
1045
|
-
const GreekLetters = ["\\theta", "\\pi"];
|
|
1046
|
-
const Letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]; // We only consider numerals, variables, and Greek Letters to be proper
|
|
1047
|
-
// leaf nodes.
|
|
963
|
+
break;
|
|
1048
964
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
[IN_SUPER_SCRIPT]: Keys.JUMP_OUT_EXPONENT,
|
|
1053
|
-
[IN_SUB_SCRIPT]: Keys.JUMP_OUT_BASE,
|
|
1054
|
-
[BEFORE_FRACTION]: Keys.JUMP_INTO_NUMERATOR,
|
|
1055
|
-
[IN_NUMERATOR]: Keys.JUMP_OUT_NUMERATOR,
|
|
1056
|
-
[IN_DENOMINATOR]: Keys.JUMP_OUT_DENOMINATOR
|
|
1057
|
-
};
|
|
965
|
+
case IN_DENOMINATOR:
|
|
966
|
+
cursor.insRightOf(cursor.parent.parent);
|
|
967
|
+
break;
|
|
1058
968
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
// use a span instead of a textarea so that we don't bring up the
|
|
1065
|
-
// native keyboard on mobile when selecting the input
|
|
1066
|
-
substituteTextarea: function () {
|
|
1067
|
-
return document.createElement("span");
|
|
1068
|
-
}
|
|
1069
|
-
});
|
|
1070
|
-
this.callbacks = callbacks;
|
|
1071
|
-
}
|
|
969
|
+
case IN_SUB_SCRIPT:
|
|
970
|
+
// Insert just beyond the superscript.
|
|
971
|
+
cursor.insRightOf(cursor.parent.parent); // Navigate right once more, if we're right before parens. This
|
|
972
|
+
// is to handle the standard case in which the subscript is the
|
|
973
|
+
// base of a custom log.
|
|
1072
974
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
// ourselves.
|
|
1077
|
-
const controller = this.mathField.__controller;
|
|
1078
|
-
controller.cursor.show(); // Set MathQuill's internal state to reflect the focus, otherwise it
|
|
1079
|
-
// will consistently try to hide the cursor on key-press and introduce
|
|
1080
|
-
// layout jank.
|
|
975
|
+
if (this._isParens(cursor[this.MQ.R])) {
|
|
976
|
+
this.mathField.keystroke("Right");
|
|
977
|
+
}
|
|
1081
978
|
|
|
1082
|
-
|
|
1083
|
-
}
|
|
979
|
+
break;
|
|
1084
980
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
}
|
|
981
|
+
case IN_SUPER_SCRIPT:
|
|
982
|
+
// Insert just beyond the superscript.
|
|
983
|
+
cursor.insRightOf(cursor.parent.parent);
|
|
984
|
+
break;
|
|
1090
985
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
986
|
+
default:
|
|
987
|
+
throw new Error("Attempted to 'Jump Out' from node, but found no " + "appropriate cursor context: ".concat(context));
|
|
988
|
+
}
|
|
1094
989
|
}
|
|
1095
990
|
/**
|
|
1096
|
-
*
|
|
991
|
+
* Selects and deletes part of the expression based on the cursor location.
|
|
992
|
+
* See inline comments for precise behavior of different cases.
|
|
1097
993
|
*
|
|
1098
|
-
* @param {
|
|
1099
|
-
* @
|
|
994
|
+
* @param {cursor} cursor
|
|
995
|
+
* @private
|
|
1100
996
|
*/
|
|
1101
997
|
|
|
1102
998
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
const
|
|
1108
|
-
str,
|
|
1109
|
-
fn
|
|
1110
|
-
} = KeyActions[key];
|
|
1111
|
-
|
|
1112
|
-
if (str && fn) {
|
|
1113
|
-
this.mathField[fn](str);
|
|
1114
|
-
}
|
|
1115
|
-
} else if (Object.keys(NormalCommands).includes(key)) {
|
|
1116
|
-
this._writeNormalFunction(NormalCommands[key]);
|
|
1117
|
-
} else if (key === Keys.FRAC_EXCLUSIVE) {
|
|
1118
|
-
// If there's nothing to the left of the cursor, then we want to
|
|
1119
|
-
// leave the cursor to the left of the fraction after creating it.
|
|
1120
|
-
const shouldNavigateLeft = cursor[this.MQ.L] === MQ_END;
|
|
1121
|
-
this.mathField.cmd("\\frac");
|
|
999
|
+
_handleBackspace(cursor) {
|
|
1000
|
+
if (!cursor.selection) {
|
|
1001
|
+
const parent = cursor.parent;
|
|
1002
|
+
const grandparent = parent.parent;
|
|
1003
|
+
const leftNode = cursor[this.MQ.L];
|
|
1122
1004
|
|
|
1123
|
-
if (
|
|
1124
|
-
this.
|
|
1005
|
+
if (this._isFraction(leftNode)) {
|
|
1006
|
+
this._selectNode(leftNode, cursor);
|
|
1007
|
+
} else if (this._isSquareRoot(leftNode)) {
|
|
1008
|
+
this._selectNode(leftNode, cursor);
|
|
1009
|
+
} else if (this._isNthRoot(leftNode)) {
|
|
1010
|
+
this._selectNode(leftNode, cursor);
|
|
1011
|
+
} else if (this._isNthRootIndex(parent)) {
|
|
1012
|
+
this._handleBackspaceInRootIndex(cursor);
|
|
1013
|
+
} else if (leftNode.ctrlSeq === "\\left(") {
|
|
1014
|
+
this._handleBackspaceOutsideParens(cursor);
|
|
1015
|
+
} else if (grandparent.ctrlSeq === "\\left(") {
|
|
1016
|
+
this._handleBackspaceInsideParens(cursor);
|
|
1017
|
+
} else if (this._isInsideLogIndex(cursor)) {
|
|
1018
|
+
this._handleBackspaceInLogIndex(cursor);
|
|
1019
|
+
} else if (leftNode.ctrlSeq === "\\ge " || leftNode.ctrlSeq === "\\le ") {
|
|
1020
|
+
this._handleBackspaceAfterLigaturedSymbol(cursor);
|
|
1021
|
+
} else if (this._isNthRoot(grandparent) && leftNode === MQ_END) {
|
|
1022
|
+
this._handleBackspaceInNthRoot(cursor);
|
|
1023
|
+
} else {
|
|
1024
|
+
this.mathField.keystroke("Backspace");
|
|
1125
1025
|
}
|
|
1126
|
-
} else
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
} else if (key === Keys.LOG_N) {
|
|
1131
|
-
this.mathField.write("log_{ }\\left(\\right)");
|
|
1132
|
-
this.mathField.keystroke("Left"); // into parentheses
|
|
1133
|
-
|
|
1134
|
-
this.mathField.keystroke("Left"); // out of parentheses
|
|
1026
|
+
} else {
|
|
1027
|
+
this.mathField.keystroke("Backspace");
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1135
1030
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
} else if (/^[a-zA-Z]$/.test(key)) {
|
|
1151
|
-
this.mathField[WRITE](key);
|
|
1152
|
-
} else if (/^NUM_\d/.test(key)) {
|
|
1153
|
-
this.mathField[WRITE](key[4]);
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
if (!cursor.selection) {
|
|
1157
|
-
// don't show the cursor for selections
|
|
1158
|
-
cursor.show();
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
if (this.callbacks.onSelectionChanged) {
|
|
1162
|
-
this.callbacks.onSelectionChanged(cursor.selection);
|
|
1163
|
-
} // NOTE(charlie): It's insufficient to do this as an `edited` handler
|
|
1164
|
-
// on the MathField, as that handler isn't triggered on navigation
|
|
1165
|
-
// events.
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
return {
|
|
1169
|
-
context: this.contextForCursor(cursor)
|
|
1170
|
-
};
|
|
1171
|
-
}
|
|
1172
|
-
/**
|
|
1173
|
-
* Place the cursor beside the node located at the given coordinates.
|
|
1174
|
-
*
|
|
1175
|
-
* @param {number} x - the x coordinate in the viewport
|
|
1176
|
-
* @param {number} y - the y coordinate in the viewport
|
|
1177
|
-
* @param {Node} hitNode - the node next to which the cursor should be
|
|
1178
|
-
* placed; if provided, the coordinates will be used
|
|
1179
|
-
* to determine on which side of the node the cursor
|
|
1180
|
-
* should be placed
|
|
1181
|
-
*/
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
setCursorPosition(x, y, hitNode) {
|
|
1185
|
-
const el = hitNode || document.elementFromPoint(x, y);
|
|
1186
|
-
|
|
1187
|
-
if (el) {
|
|
1188
|
-
const cursor = this.getCursor();
|
|
1189
|
-
|
|
1190
|
-
if (el.hasAttribute("mq-root-block")) {
|
|
1191
|
-
// If we're in the empty area place the cursor at the right
|
|
1192
|
-
// end of the expression.
|
|
1193
|
-
cursor.insAtRightEnd(this.mathField.__controller.root);
|
|
1194
|
-
} else {
|
|
1195
|
-
// Otherwise place beside the element at x, y.
|
|
1196
|
-
const controller = this.mathField.__controller;
|
|
1197
|
-
const pageX = x - document.body.scrollLeft;
|
|
1198
|
-
const pageY = y - document.body.scrollTop;
|
|
1199
|
-
controller.seek($__default["default"](el), pageX, pageY).cursor.startSelection(); // Unless that would leave us mid-command, in which case, we
|
|
1200
|
-
// need to adjust and place the cursor inside the parens
|
|
1201
|
-
// following the command.
|
|
1031
|
+
_handleLeftArrow(cursor) {
|
|
1032
|
+
// If we're inside a function, and just after the left parentheses, we
|
|
1033
|
+
// need to skip the entire function name, rather than move the cursor
|
|
1034
|
+
// inside of it. For example, when hitting left from within the
|
|
1035
|
+
// parentheses in `cos()`, we want to place the cursor to the left of
|
|
1036
|
+
// the entire expression, rather than between the `s` and the left
|
|
1037
|
+
// parenthesis.
|
|
1038
|
+
// From the cursor's perspective, this requires that our left node is
|
|
1039
|
+
// the MQ_END node, that our grandparent is the left parenthesis, and
|
|
1040
|
+
// the nodes to the left of our grandparent comprise a valid function
|
|
1041
|
+
// name.
|
|
1042
|
+
if (cursor[this.MQ.L] === MQ_END) {
|
|
1043
|
+
const parent = cursor.parent;
|
|
1044
|
+
const grandparent = parent.parent;
|
|
1202
1045
|
|
|
1203
|
-
|
|
1046
|
+
if (grandparent.ctrlSeq === "\\left(") {
|
|
1047
|
+
const command = this._maybeFindCommandBeforeParens(grandparent);
|
|
1204
1048
|
|
|
1205
|
-
if (command
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
this.mathField.keystroke("Right");
|
|
1049
|
+
if (command) {
|
|
1050
|
+
cursor.insLeftOf(command.startNode);
|
|
1051
|
+
return;
|
|
1209
1052
|
}
|
|
1210
1053
|
}
|
|
1054
|
+
} // Otherwise, we default to the standard MathQull left behavior.
|
|
1211
1055
|
|
|
1212
|
-
if (this.callbacks.onCursorMove) {
|
|
1213
|
-
this.callbacks.onCursorMove({
|
|
1214
|
-
context: this.contextForCursor(cursor)
|
|
1215
|
-
});
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
1056
|
|
|
1220
|
-
|
|
1221
|
-
return this.mathField.__controller.cursor;
|
|
1057
|
+
this.mathField.keystroke("Left");
|
|
1222
1058
|
}
|
|
1223
1059
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
}
|
|
1060
|
+
_handleRightArrow(cursor) {
|
|
1061
|
+
const command = this._maybeFindCommand(cursor[this.MQ.R]);
|
|
1227
1062
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1063
|
+
if (command) {
|
|
1064
|
+
// Similarly, if a function is to our right, then we need to place
|
|
1065
|
+
// the cursor at the start of its parenthetical content, which is
|
|
1066
|
+
// done by putting it to the left of ites parentheses and then
|
|
1067
|
+
// moving right once.
|
|
1068
|
+
cursor.insLeftOf(command.endNode);
|
|
1069
|
+
this.mathField.keystroke("Right");
|
|
1070
|
+
} else {
|
|
1071
|
+
// Otherwise, we default to the standard MathQull right behavior.
|
|
1072
|
+
this.mathField.keystroke("Right");
|
|
1073
|
+
}
|
|
1230
1074
|
}
|
|
1231
1075
|
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1076
|
+
_handleExponent(cursor, key) {
|
|
1077
|
+
// If there's an invalid operator preceding the cursor (anything that
|
|
1078
|
+
// knowingly cannot be raised to a power), add an empty set of
|
|
1079
|
+
// parentheses and apply the exponent to that.
|
|
1080
|
+
const invalidPrefixes = [...ArithmeticOperators, ...EqualityOperators];
|
|
1081
|
+
const precedingNode = cursor[this.MQ.L];
|
|
1082
|
+
const shouldPrefixWithParens = precedingNode === MQ_END || invalidPrefixes.includes(precedingNode.ctrlSeq.trim());
|
|
1235
1083
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
} // Notes about MathQuill
|
|
1240
|
-
//
|
|
1241
|
-
// MathQuill's stores its layout as nested linked lists. Each node in the
|
|
1242
|
-
// list has this.MQ.L '-1' and this.MQ.R '1' properties that define links to
|
|
1243
|
-
// the left and right nodes respectively. They also have
|
|
1244
|
-
//
|
|
1245
|
-
// ctrlSeq: contains the latex code snippet that defines that node.
|
|
1246
|
-
// jQ: jQuery object for the DOM node(s) for this MathQuill node.
|
|
1247
|
-
// ends: pointers to the nodes at the ends of the container.
|
|
1248
|
-
// parent: parent node.
|
|
1249
|
-
// blocks: an array containing one or more nodes that make up the node.
|
|
1250
|
-
// sub?: subscript node if there is one as is the case in log_n
|
|
1251
|
-
//
|
|
1252
|
-
// All of the code below is super fragile. Please be especially careful
|
|
1253
|
-
// when upgrading MathQuill.
|
|
1084
|
+
if (shouldPrefixWithParens) {
|
|
1085
|
+
this.mathField.write("\\left(\\right)");
|
|
1086
|
+
} // Insert the appropriate exponent operator.
|
|
1254
1087
|
|
|
1255
1088
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1089
|
+
switch (key) {
|
|
1090
|
+
case Keys.EXP:
|
|
1091
|
+
this.mathField.cmd("^");
|
|
1092
|
+
break;
|
|
1258
1093
|
|
|
1259
|
-
|
|
1094
|
+
case Keys.EXP_2:
|
|
1095
|
+
case Keys.EXP_3:
|
|
1096
|
+
this.mathField.write("^".concat(key === Keys.EXP_2 ? 2 : 3)); // If we enter a square or a cube, we should leave the cursor
|
|
1097
|
+
// within the newly inserted parens, if they exist. This takes
|
|
1098
|
+
// exactly four left strokes, since the cursor by default would
|
|
1099
|
+
// end up to the right of the exponent.
|
|
1260
1100
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1101
|
+
if (shouldPrefixWithParens) {
|
|
1102
|
+
this.mathField.keystroke("Left");
|
|
1103
|
+
this.mathField.keystroke("Left");
|
|
1104
|
+
this.mathField.keystroke("Left");
|
|
1105
|
+
this.mathField.keystroke("Left");
|
|
1106
|
+
}
|
|
1263
1107
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
this.mathField.keystroke("Backspace");
|
|
1108
|
+
break;
|
|
1109
|
+
|
|
1110
|
+
default:
|
|
1111
|
+
throw new Error("Invalid exponent key: ".concat(key));
|
|
1269
1112
|
}
|
|
1270
1113
|
}
|
|
1271
1114
|
/**
|
|
1272
|
-
*
|
|
1115
|
+
* Return the start node, end node, and full name of the command of which
|
|
1116
|
+
* the initial node is a part, or `null` if the node is not part of a
|
|
1117
|
+
* command.
|
|
1273
1118
|
*
|
|
1274
|
-
* @param {
|
|
1119
|
+
* @param {node} initialNode - the node to included as part of the command
|
|
1120
|
+
* @returns {null|object} - `null` or an object containing the start node
|
|
1121
|
+
* (`startNode`), end node (`endNode`), and full
|
|
1122
|
+
* name (`name`) of the command
|
|
1275
1123
|
* @private
|
|
1276
1124
|
*/
|
|
1277
1125
|
|
|
1278
1126
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1127
|
+
_maybeFindCommand(initialNode) {
|
|
1128
|
+
if (!initialNode) {
|
|
1129
|
+
return null;
|
|
1130
|
+
} // MathQuill stores commands as separate characters so that
|
|
1131
|
+
// users can delete commands one character at a time. We iterate over
|
|
1132
|
+
// the nodes from right to left until we hit a sequence starting with a
|
|
1133
|
+
// '\\', which signifies the start of a command; then we iterate from
|
|
1134
|
+
// left to right until we hit a '\\left(', which signifies the end of a
|
|
1135
|
+
// command. If we encounter any character that doesn't belong in a
|
|
1136
|
+
// command, we return null. We match a single character at a time.
|
|
1137
|
+
// Ex) ['\\l', 'o', 'g ', '\\left(', ...]
|
|
1290
1138
|
|
|
1291
|
-
switch (context) {
|
|
1292
|
-
case IN_PARENS:
|
|
1293
|
-
// Insert at the end of the parentheses, and then navigate right
|
|
1294
|
-
// once more to get 'beyond' the parentheses.
|
|
1295
|
-
cursor.insRightOf(cursor.parent.parent);
|
|
1296
|
-
break;
|
|
1297
1139
|
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1140
|
+
const commandCharRegex = /^[a-z]$/;
|
|
1141
|
+
const commandStartRegex = /^\\[a-z]$/;
|
|
1142
|
+
const commandEndSeq = "\\left("; // Note: We allowlist the set of valid commands, since relying solely on
|
|
1143
|
+
// a command being prefixed with a backslash leads to undesired
|
|
1144
|
+
// behavior. For example, Greek symbols, left parentheses, and square
|
|
1145
|
+
// roots all get treated as commands.
|
|
1302
1146
|
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1147
|
+
const validCommands = ["\\log", "\\ln", "\\cos", "\\sin", "\\tan"];
|
|
1148
|
+
let name = "";
|
|
1149
|
+
let startNode;
|
|
1150
|
+
let endNode; // Collect the portion of the command from the current node, leftwards
|
|
1151
|
+
// until the start of the command.
|
|
1307
1152
|
|
|
1308
|
-
|
|
1309
|
-
} // Jump into it!
|
|
1153
|
+
let node = initialNode;
|
|
1310
1154
|
|
|
1155
|
+
while (node !== 0) {
|
|
1156
|
+
const ctrlSeq = node.ctrlSeq.trim();
|
|
1311
1157
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1158
|
+
if (commandCharRegex.test(ctrlSeq)) {
|
|
1159
|
+
name = ctrlSeq + name;
|
|
1160
|
+
} else if (commandStartRegex.test(ctrlSeq)) {
|
|
1161
|
+
name = ctrlSeq + name;
|
|
1162
|
+
startNode = node;
|
|
1163
|
+
break;
|
|
1164
|
+
} else {
|
|
1314
1165
|
break;
|
|
1315
|
-
|
|
1316
|
-
case IN_NUMERATOR:
|
|
1317
|
-
// HACK(charlie): I can't find a better way to do this. The goal
|
|
1318
|
-
// is to place the cursor at the start of the matching
|
|
1319
|
-
// denominator. So, we identify the appropriate node, and
|
|
1320
|
-
// continue rightwards until we find ourselves inside of it.
|
|
1321
|
-
// It's possible that there are cases in which we don't reach
|
|
1322
|
-
// the denominator, though I can't think of any.
|
|
1323
|
-
const siblingDenominator = cursor.parent.parent.blocks[1];
|
|
1324
|
-
|
|
1325
|
-
while (cursor.parent !== siblingDenominator) {
|
|
1326
|
-
this.mathField.keystroke("Right");
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
break;
|
|
1330
|
-
|
|
1331
|
-
case IN_DENOMINATOR:
|
|
1332
|
-
cursor.insRightOf(cursor.parent.parent);
|
|
1333
|
-
break;
|
|
1334
|
-
|
|
1335
|
-
case IN_SUB_SCRIPT:
|
|
1336
|
-
// Insert just beyond the superscript.
|
|
1337
|
-
cursor.insRightOf(cursor.parent.parent); // Navigate right once more, if we're right before parens. This
|
|
1338
|
-
// is to handle the standard case in which the subscript is the
|
|
1339
|
-
// base of a custom log.
|
|
1340
|
-
|
|
1341
|
-
if (this._isParens(cursor[this.MQ.R])) {
|
|
1342
|
-
this.mathField.keystroke("Right");
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
break;
|
|
1346
|
-
|
|
1347
|
-
case IN_SUPER_SCRIPT:
|
|
1348
|
-
// Insert just beyond the superscript.
|
|
1349
|
-
cursor.insRightOf(cursor.parent.parent);
|
|
1350
|
-
break;
|
|
1351
|
-
|
|
1352
|
-
default:
|
|
1353
|
-
throw new Error("Attempted to 'Jump Out' from node, but found no " + "appropriate cursor context: ".concat(context));
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
/**
|
|
1357
|
-
* Selects and deletes part of the expression based on the cursor location.
|
|
1358
|
-
* See inline comments for precise behavior of different cases.
|
|
1359
|
-
*
|
|
1360
|
-
* @param {cursor} cursor
|
|
1361
|
-
* @private
|
|
1362
|
-
*/
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
_handleBackspace(cursor) {
|
|
1366
|
-
if (!cursor.selection) {
|
|
1367
|
-
const parent = cursor.parent;
|
|
1368
|
-
const grandparent = parent.parent;
|
|
1369
|
-
const leftNode = cursor[this.MQ.L];
|
|
1370
|
-
|
|
1371
|
-
if (this._isFraction(leftNode)) {
|
|
1372
|
-
this._selectNode(leftNode, cursor);
|
|
1373
|
-
} else if (this._isSquareRoot(leftNode)) {
|
|
1374
|
-
this._selectNode(leftNode, cursor);
|
|
1375
|
-
} else if (this._isNthRoot(leftNode)) {
|
|
1376
|
-
this._selectNode(leftNode, cursor);
|
|
1377
|
-
} else if (this._isNthRootIndex(parent)) {
|
|
1378
|
-
this._handleBackspaceInRootIndex(cursor);
|
|
1379
|
-
} else if (leftNode.ctrlSeq === "\\left(") {
|
|
1380
|
-
this._handleBackspaceOutsideParens(cursor);
|
|
1381
|
-
} else if (grandparent.ctrlSeq === "\\left(") {
|
|
1382
|
-
this._handleBackspaceInsideParens(cursor);
|
|
1383
|
-
} else if (this._isInsideLogIndex(cursor)) {
|
|
1384
|
-
this._handleBackspaceInLogIndex(cursor);
|
|
1385
|
-
} else if (leftNode.ctrlSeq === "\\ge " || leftNode.ctrlSeq === "\\le ") {
|
|
1386
|
-
this._handleBackspaceAfterLigaturedSymbol(cursor);
|
|
1387
|
-
} else if (this._isNthRoot(grandparent) && leftNode === MQ_END) {
|
|
1388
|
-
this._handleBackspaceInNthRoot(cursor);
|
|
1389
|
-
} else {
|
|
1390
|
-
this.mathField.keystroke("Backspace");
|
|
1391
|
-
}
|
|
1392
|
-
} else {
|
|
1393
|
-
this.mathField.keystroke("Backspace");
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
_handleLeftArrow(cursor) {
|
|
1398
|
-
// If we're inside a function, and just after the left parentheses, we
|
|
1399
|
-
// need to skip the entire function name, rather than move the cursor
|
|
1400
|
-
// inside of it. For example, when hitting left from within the
|
|
1401
|
-
// parentheses in `cos()`, we want to place the cursor to the left of
|
|
1402
|
-
// the entire expression, rather than between the `s` and the left
|
|
1403
|
-
// parenthesis.
|
|
1404
|
-
// From the cursor's perspective, this requires that our left node is
|
|
1405
|
-
// the MQ_END node, that our grandparent is the left parenthesis, and
|
|
1406
|
-
// the nodes to the left of our grandparent comprise a valid function
|
|
1407
|
-
// name.
|
|
1408
|
-
if (cursor[this.MQ.L] === MQ_END) {
|
|
1409
|
-
const parent = cursor.parent;
|
|
1410
|
-
const grandparent = parent.parent;
|
|
1411
|
-
|
|
1412
|
-
if (grandparent.ctrlSeq === "\\left(") {
|
|
1413
|
-
const command = this._maybeFindCommandBeforeParens(grandparent);
|
|
1414
|
-
|
|
1415
|
-
if (command) {
|
|
1416
|
-
cursor.insLeftOf(command.startNode);
|
|
1417
|
-
return;
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
} // Otherwise, we default to the standard MathQull left behavior.
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
this.mathField.keystroke("Left");
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
_handleRightArrow(cursor) {
|
|
1427
|
-
const command = this._maybeFindCommand(cursor[this.MQ.R]);
|
|
1428
|
-
|
|
1429
|
-
if (command) {
|
|
1430
|
-
// Similarly, if a function is to our right, then we need to place
|
|
1431
|
-
// the cursor at the start of its parenthetical content, which is
|
|
1432
|
-
// done by putting it to the left of ites parentheses and then
|
|
1433
|
-
// moving right once.
|
|
1434
|
-
cursor.insLeftOf(command.endNode);
|
|
1435
|
-
this.mathField.keystroke("Right");
|
|
1436
|
-
} else {
|
|
1437
|
-
// Otherwise, we default to the standard MathQull right behavior.
|
|
1438
|
-
this.mathField.keystroke("Right");
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
_handleExponent(cursor, key) {
|
|
1443
|
-
// If there's an invalid operator preceding the cursor (anything that
|
|
1444
|
-
// knowingly cannot be raised to a power), add an empty set of
|
|
1445
|
-
// parentheses and apply the exponent to that.
|
|
1446
|
-
const invalidPrefixes = [...ArithmeticOperators, ...EqualityOperators];
|
|
1447
|
-
const precedingNode = cursor[this.MQ.L];
|
|
1448
|
-
const shouldPrefixWithParens = precedingNode === MQ_END || invalidPrefixes.includes(precedingNode.ctrlSeq.trim());
|
|
1449
|
-
|
|
1450
|
-
if (shouldPrefixWithParens) {
|
|
1451
|
-
this.mathField.write("\\left(\\right)");
|
|
1452
|
-
} // Insert the appropriate exponent operator.
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
switch (key) {
|
|
1456
|
-
case Keys.EXP:
|
|
1457
|
-
this.mathField.cmd("^");
|
|
1458
|
-
break;
|
|
1459
|
-
|
|
1460
|
-
case Keys.EXP_2:
|
|
1461
|
-
case Keys.EXP_3:
|
|
1462
|
-
this.mathField.write("^".concat(key === Keys.EXP_2 ? 2 : 3)); // If we enter a square or a cube, we should leave the cursor
|
|
1463
|
-
// within the newly inserted parens, if they exist. This takes
|
|
1464
|
-
// exactly four left strokes, since the cursor by default would
|
|
1465
|
-
// end up to the right of the exponent.
|
|
1466
|
-
|
|
1467
|
-
if (shouldPrefixWithParens) {
|
|
1468
|
-
this.mathField.keystroke("Left");
|
|
1469
|
-
this.mathField.keystroke("Left");
|
|
1470
|
-
this.mathField.keystroke("Left");
|
|
1471
|
-
this.mathField.keystroke("Left");
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
break;
|
|
1475
|
-
|
|
1476
|
-
default:
|
|
1477
|
-
throw new Error("Invalid exponent key: ".concat(key));
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
/**
|
|
1481
|
-
* Return the start node, end node, and full name of the command of which
|
|
1482
|
-
* the initial node is a part, or `null` if the node is not part of a
|
|
1483
|
-
* command.
|
|
1484
|
-
*
|
|
1485
|
-
* @param {node} initialNode - the node to included as part of the command
|
|
1486
|
-
* @returns {null|object} - `null` or an object containing the start node
|
|
1487
|
-
* (`startNode`), end node (`endNode`), and full
|
|
1488
|
-
* name (`name`) of the command
|
|
1489
|
-
* @private
|
|
1490
|
-
*/
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
_maybeFindCommand(initialNode) {
|
|
1494
|
-
if (!initialNode) {
|
|
1495
|
-
return null;
|
|
1496
|
-
} // MathQuill stores commands as separate characters so that
|
|
1497
|
-
// users can delete commands one character at a time. We iterate over
|
|
1498
|
-
// the nodes from right to left until we hit a sequence starting with a
|
|
1499
|
-
// '\\', which signifies the start of a command; then we iterate from
|
|
1500
|
-
// left to right until we hit a '\\left(', which signifies the end of a
|
|
1501
|
-
// command. If we encounter any character that doesn't belong in a
|
|
1502
|
-
// command, we return null. We match a single character at a time.
|
|
1503
|
-
// Ex) ['\\l', 'o', 'g ', '\\left(', ...]
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
const commandCharRegex = /^[a-z]$/;
|
|
1507
|
-
const commandStartRegex = /^\\[a-z]$/;
|
|
1508
|
-
const commandEndSeq = "\\left("; // Note: We allowlist the set of valid commands, since relying solely on
|
|
1509
|
-
// a command being prefixed with a backslash leads to undesired
|
|
1510
|
-
// behavior. For example, Greek symbols, left parentheses, and square
|
|
1511
|
-
// roots all get treated as commands.
|
|
1512
|
-
|
|
1513
|
-
const validCommands = ["\\log", "\\ln", "\\cos", "\\sin", "\\tan"];
|
|
1514
|
-
let name = "";
|
|
1515
|
-
let startNode;
|
|
1516
|
-
let endNode; // Collect the portion of the command from the current node, leftwards
|
|
1517
|
-
// until the start of the command.
|
|
1518
|
-
|
|
1519
|
-
let node = initialNode;
|
|
1520
|
-
|
|
1521
|
-
while (node !== 0) {
|
|
1522
|
-
const ctrlSeq = node.ctrlSeq.trim();
|
|
1523
|
-
|
|
1524
|
-
if (commandCharRegex.test(ctrlSeq)) {
|
|
1525
|
-
name = ctrlSeq + name;
|
|
1526
|
-
} else if (commandStartRegex.test(ctrlSeq)) {
|
|
1527
|
-
name = ctrlSeq + name;
|
|
1528
|
-
startNode = node;
|
|
1529
|
-
break;
|
|
1530
|
-
} else {
|
|
1531
|
-
break;
|
|
1532
|
-
}
|
|
1166
|
+
}
|
|
1533
1167
|
|
|
1534
1168
|
node = node[this.MQ.L];
|
|
1535
1169
|
} // If we hit the start of a command, then grab the rest of it by
|
|
@@ -1938,12 +1572,39 @@ const scrollIntoView = (containerNode, keypadNode) => {
|
|
|
1938
1572
|
}
|
|
1939
1573
|
};
|
|
1940
1574
|
|
|
1941
|
-
const constrainingFrictionFactor = 0.8;
|
|
1575
|
+
const constrainingFrictionFactor = 0.8;
|
|
1942
1576
|
|
|
1577
|
+
// eslint-disable-next-line react/no-unsafe
|
|
1943
1578
|
class MathInput extends React__namespace.Component {
|
|
1944
1579
|
constructor() {
|
|
1945
1580
|
super(...arguments);
|
|
1946
1581
|
|
|
1582
|
+
_defineProperty(this, "didTouchOutside", void 0);
|
|
1583
|
+
|
|
1584
|
+
_defineProperty(this, "didScroll", void 0);
|
|
1585
|
+
|
|
1586
|
+
_defineProperty(this, "mathField", void 0);
|
|
1587
|
+
|
|
1588
|
+
_defineProperty(this, "recordTouchStartOutside", void 0);
|
|
1589
|
+
|
|
1590
|
+
_defineProperty(this, "blurOnTouchEndOutside", void 0);
|
|
1591
|
+
|
|
1592
|
+
_defineProperty(this, "dragListener", void 0);
|
|
1593
|
+
|
|
1594
|
+
_defineProperty(this, "inputRef", void 0);
|
|
1595
|
+
|
|
1596
|
+
_defineProperty(this, "_isMounted", void 0);
|
|
1597
|
+
|
|
1598
|
+
_defineProperty(this, "_mathContainer", void 0);
|
|
1599
|
+
|
|
1600
|
+
_defineProperty(this, "_container", void 0);
|
|
1601
|
+
|
|
1602
|
+
_defineProperty(this, "_root", void 0);
|
|
1603
|
+
|
|
1604
|
+
_defineProperty(this, "_containerBounds", void 0);
|
|
1605
|
+
|
|
1606
|
+
_defineProperty(this, "_keypadBounds", void 0);
|
|
1607
|
+
|
|
1947
1608
|
_defineProperty(this, "state", {
|
|
1948
1609
|
focused: false,
|
|
1949
1610
|
handle: {
|
|
@@ -1954,7 +1615,7 @@ class MathInput extends React__namespace.Component {
|
|
|
1954
1615
|
}
|
|
1955
1616
|
});
|
|
1956
1617
|
|
|
1957
|
-
_defineProperty(this, "_clearKeypadBoundsCache",
|
|
1618
|
+
_defineProperty(this, "_clearKeypadBoundsCache", () => {
|
|
1958
1619
|
this._keypadBounds = null;
|
|
1959
1620
|
});
|
|
1960
1621
|
|
|
@@ -2120,6 +1781,8 @@ class MathInput extends React__namespace.Component {
|
|
|
2120
1781
|
const elementsById = {};
|
|
2121
1782
|
|
|
2122
1783
|
for (const element of elements) {
|
|
1784
|
+
// $FlowFixMe[incompatible-use]
|
|
1785
|
+
// $FlowFixMe[prop-missing]
|
|
2123
1786
|
const id = element.getAttribute("mathquill-command-id");
|
|
2124
1787
|
|
|
2125
1788
|
if (id != null) {
|
|
@@ -2141,7 +1804,7 @@ class MathInput extends React__namespace.Component {
|
|
|
2141
1804
|
// TODO(kevinb) consider preferring nodes hit by [x, y].
|
|
2142
1805
|
|
|
2143
1806
|
|
|
2144
|
-
for (const [id, count] of
|
|
1807
|
+
for (const [id, count] of wonderStuffCore.entries(counts)) {
|
|
2145
1808
|
if (count > max) {
|
|
2146
1809
|
max = count;
|
|
2147
1810
|
hitNode = elementsById[id];
|
|
@@ -2456,7 +2119,8 @@ class MathInput extends React__namespace.Component {
|
|
|
2456
2119
|
this._updateInputPadding();
|
|
2457
2120
|
|
|
2458
2121
|
this._container = ReactDOM__default["default"].findDOMNode(this);
|
|
2459
|
-
this._root = this._container.querySelector(".mq-root-block");
|
|
2122
|
+
this._root = this._container.querySelector(".mq-root-block"); // $FlowFixMe[incompatible-use]
|
|
2123
|
+
// $FlowFixMe[prop-missing]
|
|
2460
2124
|
|
|
2461
2125
|
this._root.addEventListener("scroll", this._handleScroll); // Record the initial scroll displacement on touch start. This allows
|
|
2462
2126
|
// us to detect whether a touch event was a scroll and only blur the
|
|
@@ -2464,8 +2128,6 @@ class MathInput extends React__namespace.Component {
|
|
|
2464
2128
|
// frustrating user experience.
|
|
2465
2129
|
|
|
2466
2130
|
|
|
2467
|
-
this.touchStartInitialScroll = null;
|
|
2468
|
-
|
|
2469
2131
|
this.recordTouchStartOutside = evt => {
|
|
2470
2132
|
if (this.state.focused) {
|
|
2471
2133
|
// Only blur if the touch is both outside of the input, and
|
|
@@ -2524,165 +2186,499 @@ class MathInput extends React__namespace.Component {
|
|
|
2524
2186
|
|
|
2525
2187
|
if (this.dragListener) {
|
|
2526
2188
|
this.dragListener.detach();
|
|
2527
|
-
this.removeListeners = null;
|
|
2528
2189
|
}
|
|
2529
2190
|
};
|
|
2530
2191
|
|
|
2531
|
-
window.addEventListener("touchstart", this.recordTouchStartOutside);
|
|
2532
|
-
window.addEventListener("touchend", this.blurOnTouchEndOutside);
|
|
2533
|
-
window.addEventListener("touchcancel", this.blurOnTouchEndOutside); // HACK(benkomalo): if the window resizes, the keypad bounds can
|
|
2534
|
-
// change. That's a bit peeking into the internals of the keypad
|
|
2535
|
-
// itself, since we know bounds can change only when the viewport
|
|
2536
|
-
// changes, but seems like a rare enough thing to get wrong that it's
|
|
2537
|
-
// not worth wiring up extra things for the technical "purity" of
|
|
2538
|
-
// having the keypad notify of changes to us.
|
|
2192
|
+
window.addEventListener("touchstart", this.recordTouchStartOutside);
|
|
2193
|
+
window.addEventListener("touchend", this.blurOnTouchEndOutside);
|
|
2194
|
+
window.addEventListener("touchcancel", this.blurOnTouchEndOutside); // HACK(benkomalo): if the window resizes, the keypad bounds can
|
|
2195
|
+
// change. That's a bit peeking into the internals of the keypad
|
|
2196
|
+
// itself, since we know bounds can change only when the viewport
|
|
2197
|
+
// changes, but seems like a rare enough thing to get wrong that it's
|
|
2198
|
+
// not worth wiring up extra things for the technical "purity" of
|
|
2199
|
+
// having the keypad notify of changes to us.
|
|
2200
|
+
|
|
2201
|
+
window.addEventListener("resize", this._clearKeypadBoundsCache);
|
|
2202
|
+
window.addEventListener("orientationchange", this._clearKeypadBoundsCache);
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
UNSAFE_componentWillReceiveProps(props) {
|
|
2206
|
+
if (this.props.keypadElement !== props.keypadElement) {
|
|
2207
|
+
this._clearKeypadBoundsCache();
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
componentDidUpdate(prevProps, prevState) {
|
|
2212
|
+
if (this.mathField.getContent() !== this.props.value) {
|
|
2213
|
+
this.mathField.setContent(this.props.value);
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
if (prevState.focused !== this.state.focused) {
|
|
2217
|
+
this._updateInputPadding();
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
componentWillUnmount() {
|
|
2222
|
+
this._isMounted = false;
|
|
2223
|
+
window.removeEventListener("touchstart", this.recordTouchStartOutside);
|
|
2224
|
+
window.removeEventListener("touchend", this.blurOnTouchEndOutside);
|
|
2225
|
+
window.removeEventListener("touchcancel", this.blurOnTouchEndOutside);
|
|
2226
|
+
window.removeEventListener("resize", this._clearKeypadBoundsCache());
|
|
2227
|
+
window.removeEventListener("orientationchange", this._clearKeypadBoundsCache());
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
render() {
|
|
2231
|
+
const {
|
|
2232
|
+
focused,
|
|
2233
|
+
handle
|
|
2234
|
+
} = this.state;
|
|
2235
|
+
const {
|
|
2236
|
+
style
|
|
2237
|
+
} = this.props;
|
|
2238
|
+
const innerStyle = { ...inlineStyles$1.innerContainer,
|
|
2239
|
+
borderWidth: this.getBorderWidthPx(),
|
|
2240
|
+
...(focused ? {
|
|
2241
|
+
borderColor: wonderBlocksBlue
|
|
2242
|
+
} : {}),
|
|
2243
|
+
...style
|
|
2244
|
+
}; // NOTE(diedra): This label explicitly refers to tapping because this field
|
|
2245
|
+
// is currently only seen if the user is using a mobile device.
|
|
2246
|
+
// We added the tapping instructions because there is currently a bug where
|
|
2247
|
+
// Android users need to use two fingers to tap the input field to make the
|
|
2248
|
+
// keyboard appear. It should only require one finger, which is how iOS works.
|
|
2249
|
+
// TODO(diedra): Fix the bug that is causing Android to require a two finger tap
|
|
2250
|
+
// to the open the keyboard, and then remove the second half of this label.
|
|
2251
|
+
|
|
2252
|
+
const ariaLabel = i18n__namespace._("Math input box") + " " + i18n__namespace._("Tap with one or two fingers to open keyboard");
|
|
2253
|
+
|
|
2254
|
+
return /*#__PURE__*/React__namespace.createElement(View, {
|
|
2255
|
+
style: styles$e.input,
|
|
2256
|
+
onTouchStart: this.handleTouchStart,
|
|
2257
|
+
onTouchMove: this.handleTouchMove,
|
|
2258
|
+
onTouchEnd: this.handleTouchEnd,
|
|
2259
|
+
onClick: e => e.stopPropagation(),
|
|
2260
|
+
role: "textbox",
|
|
2261
|
+
ariaLabel: ariaLabel
|
|
2262
|
+
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
2263
|
+
className: "keypad-input",
|
|
2264
|
+
tabIndex: "0",
|
|
2265
|
+
ref: node => {
|
|
2266
|
+
this.inputRef = node;
|
|
2267
|
+
},
|
|
2268
|
+
onKeyUp: this.handleKeyUp
|
|
2269
|
+
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
2270
|
+
ref: node => {
|
|
2271
|
+
this._mathContainer = ReactDOM__default["default"].findDOMNode(node);
|
|
2272
|
+
},
|
|
2273
|
+
style: innerStyle
|
|
2274
|
+
})), focused && handle.visible && /*#__PURE__*/React__namespace.createElement(CursorHandle, _extends({}, handle, {
|
|
2275
|
+
onTouchStart: this.onCursorHandleTouchStart,
|
|
2276
|
+
onTouchMove: this.onCursorHandleTouchMove,
|
|
2277
|
+
onTouchEnd: this.onCursorHandleTouchEnd,
|
|
2278
|
+
onTouchCancel: this.onCursorHandleTouchCancel
|
|
2279
|
+
})));
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
_defineProperty(MathInput, "defaultProps", {
|
|
2285
|
+
style: {},
|
|
2286
|
+
value: ""
|
|
2287
|
+
});
|
|
2288
|
+
|
|
2289
|
+
const fontSizePt = 18;
|
|
2290
|
+
const inputMaxWidth = 128; // The height of numerals in Symbola (rendered at 18pt) is about 20px (though
|
|
2291
|
+
// they render at 24px due to padding for ascenders and descenders). We want our
|
|
2292
|
+
// box to be laid out such that there's 12px of padding between a numeral and the
|
|
2293
|
+
// edge of the input, so we use this 20px number as our 'base height' and
|
|
2294
|
+
// account for the ascender and descender padding when computing the additional
|
|
2295
|
+
// padding in our `render` method.
|
|
2296
|
+
|
|
2297
|
+
const numeralHeightPx = 20;
|
|
2298
|
+
const totalDesiredPadding = 12;
|
|
2299
|
+
const minHeightPx = numeralHeightPx + totalDesiredPadding * 2;
|
|
2300
|
+
const minWidthPx = 64;
|
|
2301
|
+
const styles$e = aphrodite.StyleSheet.create({
|
|
2302
|
+
input: {
|
|
2303
|
+
position: "relative",
|
|
2304
|
+
display: "inline-block",
|
|
2305
|
+
verticalAlign: "middle",
|
|
2306
|
+
maxWidth: inputMaxWidth
|
|
2307
|
+
}
|
|
2308
|
+
});
|
|
2309
|
+
const inlineStyles$1 = {
|
|
2310
|
+
// Styles for the inner, MathQuill-ified input element. It's important that
|
|
2311
|
+
// these are done with regular inline styles rather than Aphrodite classes
|
|
2312
|
+
// as MathQuill adds CSS class names to the element outside of the typical
|
|
2313
|
+
// React flow; assigning a class to the element can thus disrupt MathQuill
|
|
2314
|
+
// behavior. For example, if the client provided new styles to be applied
|
|
2315
|
+
// on focus and the styles here were applied with Aphrodite, then Aphrodite
|
|
2316
|
+
// would merge the provided styles with the base styles here, producing a
|
|
2317
|
+
// new CSS class name that we would apply to the element, clobbering any CSS
|
|
2318
|
+
// class names that MathQuill had applied itself.
|
|
2319
|
+
innerContainer: {
|
|
2320
|
+
backgroundColor: "white",
|
|
2321
|
+
minHeight: minHeightPx,
|
|
2322
|
+
minWidth: minWidthPx,
|
|
2323
|
+
maxWidth: inputMaxWidth,
|
|
2324
|
+
boxSizing: "border-box",
|
|
2325
|
+
position: "relative",
|
|
2326
|
+
borderStyle: "solid",
|
|
2327
|
+
borderColor: Color__default["default"].offBlack50,
|
|
2328
|
+
borderRadius: 4,
|
|
2329
|
+
color: offBlack
|
|
2330
|
+
}
|
|
2331
|
+
};
|
|
2332
|
+
|
|
2333
|
+
/**
|
|
2334
|
+
* This file contains configuration settings for the buttons in the keypad.
|
|
2335
|
+
*/
|
|
2336
|
+
const KeyConfigs = {
|
|
2337
|
+
// Basic math keys.
|
|
2338
|
+
[Keys.PLUS]: {
|
|
2339
|
+
type: KeyTypes.OPERATOR,
|
|
2340
|
+
// I18N: A label for a plus sign.
|
|
2341
|
+
ariaLabel: i18n__namespace._("Plus")
|
|
2342
|
+
},
|
|
2343
|
+
[Keys.MINUS]: {
|
|
2344
|
+
type: KeyTypes.OPERATOR,
|
|
2345
|
+
// I18N: A label for a minus sign.
|
|
2346
|
+
ariaLabel: i18n__namespace._("Minus")
|
|
2347
|
+
},
|
|
2348
|
+
[Keys.NEGATIVE]: {
|
|
2349
|
+
type: KeyTypes.VALUE,
|
|
2350
|
+
// I18N: A label for a minus sign.
|
|
2351
|
+
ariaLabel: i18n__namespace._("Negative")
|
|
2352
|
+
},
|
|
2353
|
+
[Keys.TIMES]: {
|
|
2354
|
+
type: KeyTypes.OPERATOR,
|
|
2355
|
+
// I18N: A label for a multiplication sign (represented with an 'x').
|
|
2356
|
+
ariaLabel: i18n__namespace._("Multiply")
|
|
2357
|
+
},
|
|
2358
|
+
[Keys.DIVIDE]: {
|
|
2359
|
+
type: KeyTypes.OPERATOR,
|
|
2360
|
+
// I18N: A label for a division sign.
|
|
2361
|
+
ariaLabel: i18n__namespace._("Divide")
|
|
2362
|
+
},
|
|
2363
|
+
[Keys.DECIMAL]: {
|
|
2364
|
+
type: KeyTypes.VALUE,
|
|
2365
|
+
// I18N: A label for a decimal symbol.
|
|
2366
|
+
ariaLabel: i18n__namespace._("Decimal"),
|
|
2367
|
+
icon: decimalSeparator === DecimalSeparators.COMMA ? {
|
|
2368
|
+
// TODO(charlie): Get an SVG icon for the comma, or verify with
|
|
2369
|
+
// design that the text-rendered version is acceptable.
|
|
2370
|
+
type: IconTypes.TEXT,
|
|
2371
|
+
data: ","
|
|
2372
|
+
} : {
|
|
2373
|
+
type: IconTypes.SVG,
|
|
2374
|
+
data: Keys.PERIOD
|
|
2375
|
+
}
|
|
2376
|
+
},
|
|
2377
|
+
[Keys.PERCENT]: {
|
|
2378
|
+
type: KeyTypes.OPERATOR,
|
|
2379
|
+
// I18N: A label for a percent sign.
|
|
2380
|
+
ariaLabel: i18n__namespace._("Percent")
|
|
2381
|
+
},
|
|
2382
|
+
[Keys.CDOT]: {
|
|
2383
|
+
type: KeyTypes.OPERATOR,
|
|
2384
|
+
// I18N: A label for a multiplication sign (represented as a dot).
|
|
2385
|
+
ariaLabel: i18n__namespace._("Multiply")
|
|
2386
|
+
},
|
|
2387
|
+
[Keys.EQUAL]: {
|
|
2388
|
+
type: KeyTypes.OPERATOR,
|
|
2389
|
+
ariaLabel: i18n__namespace._("Equals sign")
|
|
2390
|
+
},
|
|
2391
|
+
[Keys.NEQ]: {
|
|
2392
|
+
type: KeyTypes.OPERATOR,
|
|
2393
|
+
ariaLabel: i18n__namespace._("Not-equals sign")
|
|
2394
|
+
},
|
|
2395
|
+
[Keys.GT]: {
|
|
2396
|
+
type: KeyTypes.OPERATOR,
|
|
2397
|
+
// I18N: A label for a 'greater than' sign (represented as '>').
|
|
2398
|
+
ariaLabel: i18n__namespace._("Greater than sign")
|
|
2399
|
+
},
|
|
2400
|
+
[Keys.LT]: {
|
|
2401
|
+
type: KeyTypes.OPERATOR,
|
|
2402
|
+
// I18N: A label for a 'less than' sign (represented as '<').
|
|
2403
|
+
ariaLabel: i18n__namespace._("Less than sign")
|
|
2404
|
+
},
|
|
2405
|
+
[Keys.GEQ]: {
|
|
2406
|
+
type: KeyTypes.OPERATOR,
|
|
2407
|
+
ariaLabel: i18n__namespace._("Greater than or equal to sign")
|
|
2408
|
+
},
|
|
2409
|
+
[Keys.LEQ]: {
|
|
2410
|
+
type: KeyTypes.OPERATOR,
|
|
2411
|
+
ariaLabel: i18n__namespace._("Less than or equal to sign")
|
|
2412
|
+
},
|
|
2413
|
+
// mobile native
|
|
2414
|
+
[Keys.FRAC_INCLUSIVE]: {
|
|
2415
|
+
type: KeyTypes.OPERATOR,
|
|
2416
|
+
// I18N: A label for a button that creates a new fraction and puts the
|
|
2417
|
+
// current expression in the numerator of that fraction.
|
|
2418
|
+
ariaLabel: i18n__namespace._("Fraction, with current expression in numerator")
|
|
2419
|
+
},
|
|
2420
|
+
// mobile native
|
|
2421
|
+
[Keys.FRAC_EXCLUSIVE]: {
|
|
2422
|
+
type: KeyTypes.OPERATOR,
|
|
2423
|
+
// I18N: A label for a button that creates a new fraction next to the
|
|
2424
|
+
// cursor.
|
|
2425
|
+
ariaLabel: i18n__namespace._("Fraction, excluding the current expression")
|
|
2426
|
+
},
|
|
2427
|
+
// mobile web
|
|
2428
|
+
[Keys.FRAC]: {
|
|
2429
|
+
type: KeyTypes.OPERATOR,
|
|
2430
|
+
// I18N: A label for a button that creates a new fraction next to the
|
|
2431
|
+
// cursor.
|
|
2432
|
+
ariaLabel: i18n__namespace._("Fraction, excluding the current expression")
|
|
2433
|
+
},
|
|
2434
|
+
[Keys.EXP]: {
|
|
2435
|
+
type: KeyTypes.OPERATOR,
|
|
2436
|
+
// I18N: A label for a button that will allow the user to input a custom
|
|
2437
|
+
// exponent.
|
|
2438
|
+
ariaLabel: i18n__namespace._("Custom exponent")
|
|
2439
|
+
},
|
|
2440
|
+
[Keys.EXP_2]: {
|
|
2441
|
+
type: KeyTypes.OPERATOR,
|
|
2442
|
+
// I18N: A label for a button that will square (take to the second
|
|
2443
|
+
// power) some math.
|
|
2444
|
+
ariaLabel: i18n__namespace._("Square")
|
|
2445
|
+
},
|
|
2446
|
+
[Keys.EXP_3]: {
|
|
2447
|
+
type: KeyTypes.OPERATOR,
|
|
2448
|
+
// I18N: A label for a button that will cube (take to the third power)
|
|
2449
|
+
// some math.
|
|
2450
|
+
ariaLabel: i18n__namespace._("Cube")
|
|
2451
|
+
},
|
|
2452
|
+
[Keys.SQRT]: {
|
|
2453
|
+
type: KeyTypes.OPERATOR,
|
|
2454
|
+
ariaLabel: i18n__namespace._("Square root")
|
|
2455
|
+
},
|
|
2456
|
+
[Keys.CUBE_ROOT]: {
|
|
2457
|
+
type: KeyTypes.OPERATOR,
|
|
2458
|
+
ariaLabel: i18n__namespace._("Cube root")
|
|
2459
|
+
},
|
|
2460
|
+
[Keys.RADICAL]: {
|
|
2461
|
+
type: KeyTypes.OPERATOR,
|
|
2462
|
+
ariaLabel: i18n__namespace._("Radical with custom root")
|
|
2463
|
+
},
|
|
2464
|
+
[Keys.LEFT_PAREN]: {
|
|
2465
|
+
type: KeyTypes.OPERATOR,
|
|
2466
|
+
ariaLabel: i18n__namespace._("Left parenthesis")
|
|
2467
|
+
},
|
|
2468
|
+
[Keys.RIGHT_PAREN]: {
|
|
2469
|
+
type: KeyTypes.OPERATOR,
|
|
2470
|
+
ariaLabel: i18n__namespace._("Right parenthesis")
|
|
2471
|
+
},
|
|
2472
|
+
[Keys.LN]: {
|
|
2473
|
+
type: KeyTypes.OPERATOR,
|
|
2474
|
+
ariaLabel: i18n__namespace._("Natural logarithm")
|
|
2475
|
+
},
|
|
2476
|
+
[Keys.LOG]: {
|
|
2477
|
+
type: KeyTypes.OPERATOR,
|
|
2478
|
+
ariaLabel: i18n__namespace._("Logarithm with base 10")
|
|
2479
|
+
},
|
|
2480
|
+
[Keys.LOG_N]: {
|
|
2481
|
+
type: KeyTypes.OPERATOR,
|
|
2482
|
+
ariaLabel: i18n__namespace._("Logarithm with custom base")
|
|
2483
|
+
},
|
|
2484
|
+
[Keys.SIN]: {
|
|
2485
|
+
type: KeyTypes.OPERATOR,
|
|
2486
|
+
ariaLabel: i18n__namespace._("Sine")
|
|
2487
|
+
},
|
|
2488
|
+
[Keys.COS]: {
|
|
2489
|
+
type: KeyTypes.OPERATOR,
|
|
2490
|
+
ariaLabel: i18n__namespace._("Cosine")
|
|
2491
|
+
},
|
|
2492
|
+
[Keys.TAN]: {
|
|
2493
|
+
type: KeyTypes.OPERATOR,
|
|
2494
|
+
ariaLabel: i18n__namespace._("Tangent")
|
|
2495
|
+
},
|
|
2496
|
+
[Keys.PI]: {
|
|
2497
|
+
type: KeyTypes.VALUE,
|
|
2498
|
+
ariaLabel: i18n__namespace._("Pi"),
|
|
2499
|
+
icon: {
|
|
2500
|
+
type: IconTypes.MATH,
|
|
2501
|
+
data: "\\pi"
|
|
2502
|
+
}
|
|
2503
|
+
},
|
|
2504
|
+
[Keys.THETA]: {
|
|
2505
|
+
type: KeyTypes.VALUE,
|
|
2506
|
+
ariaLabel: i18n__namespace._("Theta"),
|
|
2507
|
+
icon: {
|
|
2508
|
+
type: IconTypes.MATH,
|
|
2509
|
+
data: "\\theta"
|
|
2510
|
+
}
|
|
2511
|
+
},
|
|
2512
|
+
[Keys.NOOP]: {
|
|
2513
|
+
type: KeyTypes.EMPTY
|
|
2514
|
+
},
|
|
2515
|
+
// Input navigation keys.
|
|
2516
|
+
[Keys.UP]: {
|
|
2517
|
+
type: KeyTypes.INPUT_NAVIGATION,
|
|
2518
|
+
ariaLabel: i18n__namespace._("Up arrow")
|
|
2519
|
+
},
|
|
2520
|
+
[Keys.RIGHT]: {
|
|
2521
|
+
type: KeyTypes.INPUT_NAVIGATION,
|
|
2522
|
+
ariaLabel: i18n__namespace._("Right arrow")
|
|
2523
|
+
},
|
|
2524
|
+
[Keys.DOWN]: {
|
|
2525
|
+
type: KeyTypes.INPUT_NAVIGATION,
|
|
2526
|
+
ariaLabel: i18n__namespace._("Down arrow")
|
|
2527
|
+
},
|
|
2528
|
+
[Keys.LEFT]: {
|
|
2529
|
+
type: KeyTypes.INPUT_NAVIGATION,
|
|
2530
|
+
ariaLabel: i18n__namespace._("Left arrow")
|
|
2531
|
+
},
|
|
2532
|
+
[Keys.JUMP_OUT_PARENTHESES]: {
|
|
2533
|
+
type: KeyTypes.INPUT_NAVIGATION,
|
|
2534
|
+
ariaLabel: i18n__namespace._("Navigate right out of a set of parentheses")
|
|
2535
|
+
},
|
|
2536
|
+
[Keys.JUMP_OUT_EXPONENT]: {
|
|
2537
|
+
type: KeyTypes.INPUT_NAVIGATION,
|
|
2538
|
+
ariaLabel: i18n__namespace._("Navigate right out of an exponent")
|
|
2539
|
+
},
|
|
2540
|
+
[Keys.JUMP_OUT_BASE]: {
|
|
2541
|
+
type: KeyTypes.INPUT_NAVIGATION,
|
|
2542
|
+
ariaLabel: i18n__namespace._("Navigate right out of a base")
|
|
2543
|
+
},
|
|
2544
|
+
[Keys.JUMP_INTO_NUMERATOR]: {
|
|
2545
|
+
type: KeyTypes.INPUT_NAVIGATION,
|
|
2546
|
+
ariaLabel: i18n__namespace._("Navigate right into the numerator of a fraction")
|
|
2547
|
+
},
|
|
2548
|
+
[Keys.JUMP_OUT_NUMERATOR]: {
|
|
2549
|
+
type: KeyTypes.INPUT_NAVIGATION,
|
|
2550
|
+
ariaLabel: i18n__namespace._("Navigate right out of the numerator and into the denominator")
|
|
2551
|
+
},
|
|
2552
|
+
[Keys.JUMP_OUT_DENOMINATOR]: {
|
|
2553
|
+
type: KeyTypes.INPUT_NAVIGATION,
|
|
2554
|
+
ariaLabel: i18n__namespace._("Navigate right out of the denominator of a fraction")
|
|
2555
|
+
},
|
|
2556
|
+
[Keys.BACKSPACE]: {
|
|
2557
|
+
type: KeyTypes.INPUT_NAVIGATION,
|
|
2558
|
+
// I18N: A label for a button that will delete some input.
|
|
2559
|
+
ariaLabel: i18n__namespace._("Delete")
|
|
2560
|
+
},
|
|
2561
|
+
// Keypad navigation keys.
|
|
2562
|
+
[Keys.DISMISS]: {
|
|
2563
|
+
type: KeyTypes.KEYPAD_NAVIGATION,
|
|
2564
|
+
// I18N: A label for a button that will dismiss/hide a keypad.
|
|
2565
|
+
ariaLabel: i18n__namespace._("Dismiss")
|
|
2566
|
+
}
|
|
2567
|
+
}; // Add in any multi-function buttons. By default, these keys will mix in any
|
|
2568
|
+
// configuration settings from their default child key (i.e., the first key in
|
|
2569
|
+
// the `childKeyIds` array).
|
|
2570
|
+
// TODO(charlie): Make the multi-function button's long-press interaction
|
|
2571
|
+
// accessible.
|
|
2572
|
+
// NOTE(kevinb): This is only used in the mobile native app.
|
|
2573
|
+
|
|
2574
|
+
KeyConfigs[Keys.FRAC_MULTI] = {
|
|
2575
|
+
childKeyIds: [Keys.FRAC_INCLUSIVE, Keys.FRAC_EXCLUSIVE]
|
|
2576
|
+
}; // TODO(charlie): Use the numeral color for the 'Many' key.
|
|
2539
2577
|
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
}
|
|
2578
|
+
KeyConfigs[Keys.MANY] = {
|
|
2579
|
+
type: KeyTypes.MANY // childKeyIds will be configured by the client.
|
|
2543
2580
|
|
|
2544
|
-
|
|
2545
|
-
if (this.props.keypadElement !== props.keypadElement) {
|
|
2546
|
-
this._clearKeypadBoundsCache();
|
|
2547
|
-
}
|
|
2548
|
-
}
|
|
2581
|
+
}; // Add in every numeral.
|
|
2549
2582
|
|
|
2550
|
-
|
|
2551
|
-
if (this.mathField.getContent() !== this.props.value) {
|
|
2552
|
-
this.mathField.setContent(this.props.value);
|
|
2553
|
-
}
|
|
2583
|
+
const NUMBERS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
2554
2584
|
|
|
2555
|
-
|
|
2556
|
-
|
|
2585
|
+
for (const num of NUMBERS) {
|
|
2586
|
+
// TODO(charlie): Consider removing the SVG icons that we have for the
|
|
2587
|
+
// numeral keys. They can be rendered just as easily with text (though that
|
|
2588
|
+
// would mean that we'd be using text beyond the variable key).
|
|
2589
|
+
const textRepresentation = "".concat(num);
|
|
2590
|
+
KeyConfigs["NUM_".concat(num)] = {
|
|
2591
|
+
type: KeyTypes.VALUE,
|
|
2592
|
+
ariaLabel: textRepresentation,
|
|
2593
|
+
icon: {
|
|
2594
|
+
type: IconTypes.TEXT,
|
|
2595
|
+
data: textRepresentation
|
|
2557
2596
|
}
|
|
2558
|
-
}
|
|
2597
|
+
};
|
|
2598
|
+
} // Add in every variable.
|
|
2559
2599
|
|
|
2560
|
-
componentWillUnmount() {
|
|
2561
|
-
this._isMounted = false;
|
|
2562
|
-
window.removeEventListener("touchstart", this.recordTouchStartOutside);
|
|
2563
|
-
window.removeEventListener("touchend", this.blurOnTouchEndOutside);
|
|
2564
|
-
window.removeEventListener("touchcancel", this.blurOnTouchEndOutside);
|
|
2565
|
-
window.removeEventListener("resize", this._clearKeypadBoundsCache());
|
|
2566
|
-
window.removeEventListener("orientationchange", this._clearKeypadBoundsCache());
|
|
2567
|
-
}
|
|
2568
2600
|
|
|
2569
|
-
|
|
2570
|
-
const {
|
|
2571
|
-
focused,
|
|
2572
|
-
handle
|
|
2573
|
-
} = this.state;
|
|
2574
|
-
const {
|
|
2575
|
-
style
|
|
2576
|
-
} = this.props;
|
|
2577
|
-
const innerStyle = { ...inlineStyles$1.innerContainer,
|
|
2578
|
-
borderWidth: this.getBorderWidthPx(),
|
|
2579
|
-
...(focused ? {
|
|
2580
|
-
borderColor: wonderBlocksBlue
|
|
2581
|
-
} : {}),
|
|
2582
|
-
...style
|
|
2583
|
-
}; // NOTE(diedra): This label explicitly refers to tapping because this field
|
|
2584
|
-
// is currently only seen if the user is using a mobile device.
|
|
2585
|
-
// We added the tapping instructions because there is currently a bug where
|
|
2586
|
-
// Android users need to use two fingers to tap the input field to make the
|
|
2587
|
-
// keyboard appear. It should only require one finger, which is how iOS works.
|
|
2588
|
-
// TODO(diedra): Fix the bug that is causing Android to require a two finger tap
|
|
2589
|
-
// to the open the keyboard, and then remove the second half of this label.
|
|
2601
|
+
const LETTERS = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
|
|
2590
2602
|
|
|
2591
|
-
|
|
2603
|
+
for (const letter of LETTERS) {
|
|
2604
|
+
const lowerCaseVariable = letter.toLowerCase();
|
|
2605
|
+
const upperCaseVariable = letter.toUpperCase();
|
|
2592
2606
|
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
}
|
|
2602
|
-
className: "keypad-input",
|
|
2603
|
-
tabIndex: "0",
|
|
2604
|
-
ref: node => {
|
|
2605
|
-
this.inputRef = node;
|
|
2606
|
-
},
|
|
2607
|
-
onKeyUp: this.handleKeyUp
|
|
2608
|
-
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
2609
|
-
ref: node => {
|
|
2610
|
-
this._mathContainer = ReactDOM__default["default"].findDOMNode(node);
|
|
2611
|
-
},
|
|
2612
|
-
style: innerStyle
|
|
2613
|
-
})), focused && handle.visible && /*#__PURE__*/React__namespace.createElement(CursorHandle, _extends({}, handle, {
|
|
2614
|
-
onTouchStart: this.onCursorHandleTouchStart,
|
|
2615
|
-
onTouchMove: this.onCursorHandleTouchMove,
|
|
2616
|
-
onTouchEnd: this.onCursorHandleTouchEnd,
|
|
2617
|
-
onTouchCancel: this.onCursorHandleTouchCancel
|
|
2618
|
-
})));
|
|
2607
|
+
for (const textRepresentation of [lowerCaseVariable, upperCaseVariable]) {
|
|
2608
|
+
KeyConfigs[textRepresentation] = {
|
|
2609
|
+
type: KeyTypes.VALUE,
|
|
2610
|
+
ariaLabel: textRepresentation,
|
|
2611
|
+
icon: {
|
|
2612
|
+
type: IconTypes.MATH,
|
|
2613
|
+
data: textRepresentation
|
|
2614
|
+
}
|
|
2615
|
+
};
|
|
2619
2616
|
}
|
|
2617
|
+
}
|
|
2620
2618
|
|
|
2619
|
+
for (const key of Object.keys(KeyConfigs)) {
|
|
2620
|
+
KeyConfigs[key] = {
|
|
2621
|
+
id: key,
|
|
2622
|
+
// Default to an SVG icon indexed by the key name.
|
|
2623
|
+
icon: {
|
|
2624
|
+
type: IconTypes.SVG,
|
|
2625
|
+
data: key
|
|
2626
|
+
},
|
|
2627
|
+
...KeyConfigs[key]
|
|
2628
|
+
};
|
|
2621
2629
|
}
|
|
2622
2630
|
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
keypadElement: keypadElementPropType,
|
|
2630
|
-
onBlur: PropTypes__default["default"].func,
|
|
2631
|
-
onChange: PropTypes__default["default"].func.isRequired,
|
|
2632
|
-
onFocus: PropTypes__default["default"].func,
|
|
2633
|
-
// An extra, vanilla style object, to be applied to the math input.
|
|
2634
|
-
style: PropTypes__default["default"].any,
|
|
2635
|
-
value: PropTypes__default["default"].string
|
|
2631
|
+
/**
|
|
2632
|
+
* React PropTypes that may be shared between components.
|
|
2633
|
+
*/
|
|
2634
|
+
const iconPropType = PropTypes__default["default"].shape({
|
|
2635
|
+
type: PropTypes__default["default"].oneOf(Object.keys(IconTypes)).isRequired,
|
|
2636
|
+
data: PropTypes__default["default"].string.isRequired
|
|
2636
2637
|
});
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2638
|
+
const keyIdPropType = PropTypes__default["default"].oneOf(Object.keys(KeyConfigs));
|
|
2639
|
+
const keyConfigPropType = PropTypes__default["default"].shape({
|
|
2640
|
+
ariaLabel: PropTypes__default["default"].string,
|
|
2641
|
+
id: keyIdPropType.isRequired,
|
|
2642
|
+
type: PropTypes__default["default"].oneOf(Object.keys(KeyTypes)).isRequired,
|
|
2643
|
+
childKeyIds: PropTypes__default["default"].arrayOf(keyIdPropType),
|
|
2644
|
+
icon: iconPropType.isRequired
|
|
2641
2645
|
});
|
|
2646
|
+
const keypadConfigurationPropType = PropTypes__default["default"].shape({
|
|
2647
|
+
keypadType: PropTypes__default["default"].oneOf(Object.keys(KeypadTypes)).isRequired,
|
|
2648
|
+
extraKeys: PropTypes__default["default"].arrayOf(keyIdPropType)
|
|
2649
|
+
}); // NOTE(jared): This is no longer guaranteed to be React element
|
|
2642
2650
|
|
|
2643
|
-
const
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
const numeralHeightPx = 20;
|
|
2652
|
-
const totalDesiredPadding = 12;
|
|
2653
|
-
const minHeightPx = numeralHeightPx + totalDesiredPadding * 2;
|
|
2654
|
-
const minWidthPx = 64;
|
|
2655
|
-
const styles$e = aphrodite.StyleSheet.create({
|
|
2656
|
-
input: {
|
|
2657
|
-
position: "relative",
|
|
2658
|
-
display: "inline-block",
|
|
2659
|
-
verticalAlign: "middle",
|
|
2660
|
-
maxWidth: inputMaxWidth
|
|
2661
|
-
}
|
|
2651
|
+
const keypadElementPropType = PropTypes__default["default"].shape({
|
|
2652
|
+
activate: PropTypes__default["default"].func.isRequired,
|
|
2653
|
+
dismiss: PropTypes__default["default"].func.isRequired,
|
|
2654
|
+
configure: PropTypes__default["default"].func.isRequired,
|
|
2655
|
+
setCursor: PropTypes__default["default"].func.isRequired,
|
|
2656
|
+
setKeyHandler: PropTypes__default["default"].func.isRequired,
|
|
2657
|
+
getDOMNode: PropTypes__default["default"].func.isRequired
|
|
2662
2658
|
});
|
|
2663
|
-
const
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2659
|
+
const bordersPropType = PropTypes__default["default"].arrayOf(PropTypes__default["default"].oneOf(Object.keys(BorderDirections)));
|
|
2660
|
+
const boundingBoxPropType = PropTypes__default["default"].shape({
|
|
2661
|
+
height: PropTypes__default["default"].number,
|
|
2662
|
+
width: PropTypes__default["default"].number,
|
|
2663
|
+
top: PropTypes__default["default"].number,
|
|
2664
|
+
right: PropTypes__default["default"].number,
|
|
2665
|
+
bottom: PropTypes__default["default"].number,
|
|
2666
|
+
left: PropTypes__default["default"].number
|
|
2667
|
+
});
|
|
2668
|
+
const echoPropType = PropTypes__default["default"].shape({
|
|
2669
|
+
animationId: PropTypes__default["default"].string.isRequired,
|
|
2670
|
+
animationType: PropTypes__default["default"].oneOf(Object.keys(EchoAnimationTypes)).isRequired,
|
|
2671
|
+
borders: bordersPropType,
|
|
2672
|
+
id: keyIdPropType.isRequired,
|
|
2673
|
+
initialBounds: boundingBoxPropType.isRequired
|
|
2674
|
+
});
|
|
2675
|
+
const cursorContextPropType = PropTypes__default["default"].oneOf(Object.keys(CursorContexts));
|
|
2676
|
+
const popoverPropType = PropTypes__default["default"].shape({
|
|
2677
|
+
parentId: keyIdPropType.isRequired,
|
|
2678
|
+
bounds: boundingBoxPropType.isRequired,
|
|
2679
|
+
childKeyIds: PropTypes__default["default"].arrayOf(keyIdPropType).isRequired
|
|
2680
|
+
});
|
|
2681
|
+
PropTypes__default["default"].oneOfType([PropTypes__default["default"].arrayOf(PropTypes__default["default"].node), PropTypes__default["default"].node]);
|
|
2686
2682
|
|
|
2687
2683
|
// naming convention: verb + noun
|
|
2688
2684
|
// the noun should be one of the other properties in the object that's
|
|
@@ -7649,13 +7645,9 @@ const inlineStyles = {
|
|
|
7649
7645
|
visibility: "hidden"
|
|
7650
7646
|
},
|
|
7651
7647
|
hidden: {
|
|
7652
|
-
msTransform: "translate3d(0, 100%, 0)",
|
|
7653
|
-
WebkitTransform: "translate3d(0, 100%, 0)",
|
|
7654
7648
|
transform: "translate3d(0, 100%, 0)"
|
|
7655
7649
|
},
|
|
7656
7650
|
active: {
|
|
7657
|
-
msTransform: "translate3d(0, 0, 0)",
|
|
7658
|
-
WebkitTransform: "translate3d(0, 0, 0)",
|
|
7659
7651
|
transform: "translate3d(0, 0, 0)"
|
|
7660
7652
|
}
|
|
7661
7653
|
};
|