@khanacademy/perseus 23.6.0 → 24.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/index.css +29 -1
- package/dist/es/index.js +401 -45
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +29 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +409 -50
- package/dist/index.js.map +1 -1
- package/dist/util/svg.d.ts +5 -0
- package/dist/widgets/interactive-graph.d.ts +2 -1
- package/dist/widgets/interactive-graphs/graphs/use-draggable.d.ts +20 -3
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +12 -3
- package/dist/widgets/interactive-graphs/mafs-graph.d.ts +2 -0
- package/dist/widgets/interactive-graphs/protractor.d.ts +5 -0
- package/dist/widgets/interactive-graphs/utils.d.ts +7 -1
- package/package.json +2 -1
package/dist/es/index.css
CHANGED
|
@@ -3518,7 +3518,7 @@ body.mobile .framework-perseus table.perseus-widget-table-of-values.non-markdown
|
|
|
3518
3518
|
font-size: 18px;
|
|
3519
3519
|
}
|
|
3520
3520
|
}
|
|
3521
|
-
.perseus-widget-editor-content .
|
|
3521
|
+
.perseus-widget-editor-content .perseus-editor-accordion h2 {
|
|
3522
3522
|
padding-top: 0;
|
|
3523
3523
|
}
|
|
3524
3524
|
.keypad-input {
|
|
@@ -4143,6 +4143,34 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
|
|
|
4143
4143
|
}
|
|
4144
4144
|
|
|
4145
4145
|
|
|
4146
|
+
.MafsView .protractor-rotation-handle {
|
|
4147
|
+
cursor: grab;
|
|
4148
|
+
/* `pointer-events: all` is needed to make the hit target the size of the
|
|
4149
|
+
* entire <g> element and not just its visible subelements */
|
|
4150
|
+
pointer-events: all;
|
|
4151
|
+
transition: transform 0.1s ease-out;
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
.MafsView .protractor-rotation-handle:hover {
|
|
4155
|
+
transform: scale(1.3);
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
.MafsView .protractor-rotation-handle-arrow-arc {
|
|
4159
|
+
fill: none;
|
|
4160
|
+
stroke: var(--mafs-blue);
|
|
4161
|
+
stroke-width: 8;
|
|
4162
|
+
/* `strokeLinecap: square` prevents a hairline crack from appearing
|
|
4163
|
+
* between the arc and the arrowheads, by making the arc extend a bit
|
|
4164
|
+
* beyond its start and end coordinates.
|
|
4165
|
+
*/
|
|
4166
|
+
stroke-linecap: square;
|
|
4167
|
+
}
|
|
4168
|
+
|
|
4169
|
+
.MafsView .protractor-rotation-handle-arrowhead {
|
|
4170
|
+
fill: var(--mafs-blue);
|
|
4171
|
+
stroke: none;
|
|
4172
|
+
}
|
|
4173
|
+
|
|
4146
4174
|
.MafsView {
|
|
4147
4175
|
display: block;
|
|
4148
4176
|
background: var(--mafs-bg);
|
package/dist/es/index.js
CHANGED
|
@@ -29,7 +29,8 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
|
|
|
29
29
|
import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
|
|
30
30
|
import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
|
|
31
31
|
import debounce$2 from 'lodash.debounce';
|
|
32
|
-
import { vec, Ellipse, Line as Line$4, Point as Point$2, Polygon,
|
|
32
|
+
import { vec, Ellipse, Line as Line$4, Point as Point$2, Polygon, useTransformContext, Text as Text$1, Plot as Plot$3, Coordinates, Mafs } from 'mafs';
|
|
33
|
+
import { useDrag } from '@use-gesture/react';
|
|
33
34
|
import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
|
|
34
35
|
import { Body, LabelSmall, LabelMedium } from '@khanacademy/wonder-blocks-typography';
|
|
35
36
|
import questionIcon from '@phosphor-icons/core/regular/question.svg';
|
|
@@ -520,8 +521,7 @@ class InnerMathInput extends React.Component {
|
|
|
520
521
|
active: false
|
|
521
522
|
}) : /*#__PURE__*/React.createElement(Clickable, {
|
|
522
523
|
"aria-label": this.state.keypadOpen ? this.context.strings.closeKeypad : this.context.strings.openKeypad,
|
|
523
|
-
"
|
|
524
|
-
role: "switch",
|
|
524
|
+
role: "button",
|
|
525
525
|
onClick: () => this.state.keypadOpen ? this.closeKeypad() : this.openKeypad()
|
|
526
526
|
}, props => /*#__PURE__*/React.createElement(MathInputIcon, _extends({
|
|
527
527
|
active: this.state.keypadOpen
|
|
@@ -13086,7 +13086,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
13086
13086
|
}(),
|
|
13087
13087
|
protractor: function (center) {
|
|
13088
13088
|
// @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
|
|
13089
|
-
return new Protractor(this, center);
|
|
13089
|
+
return new Protractor$1(this, center);
|
|
13090
13090
|
},
|
|
13091
13091
|
ruler: function (options) {
|
|
13092
13092
|
// @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
|
|
@@ -13094,7 +13094,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
13094
13094
|
},
|
|
13095
13095
|
addPoints: vector$1.add
|
|
13096
13096
|
});
|
|
13097
|
-
function Protractor(graph, center) {
|
|
13097
|
+
function Protractor$1(graph, center) {
|
|
13098
13098
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
13099
13099
|
this.set = graph.raphael.set();
|
|
13100
13100
|
|
|
@@ -28558,7 +28558,24 @@ class PathBuilder {
|
|
|
28558
28558
|
move(x, y) {
|
|
28559
28559
|
this.path.push({
|
|
28560
28560
|
action: "M",
|
|
28561
|
-
|
|
28561
|
+
args: [x, y]
|
|
28562
|
+
});
|
|
28563
|
+
return this;
|
|
28564
|
+
}
|
|
28565
|
+
line(x, y) {
|
|
28566
|
+
this.path.push({
|
|
28567
|
+
action: "L",
|
|
28568
|
+
args: [x, y]
|
|
28569
|
+
});
|
|
28570
|
+
return this;
|
|
28571
|
+
}
|
|
28572
|
+
circularArc(radius, toX, toY, {
|
|
28573
|
+
sweep = false,
|
|
28574
|
+
largeArc = false
|
|
28575
|
+
} = {}) {
|
|
28576
|
+
this.path.push({
|
|
28577
|
+
action: "A",
|
|
28578
|
+
args: [radius, radius, 0 /* rotation */, largeArc ? 1 : 0, sweep ? 1 : 0, toX, toY]
|
|
28562
28579
|
});
|
|
28563
28580
|
return this;
|
|
28564
28581
|
}
|
|
@@ -28567,7 +28584,7 @@ class PathBuilder {
|
|
|
28567
28584
|
curve(control1X, control1Y, control2X, control2Y, endX, endY) {
|
|
28568
28585
|
this.path.push({
|
|
28569
28586
|
action: "C",
|
|
28570
|
-
|
|
28587
|
+
args: [control1X, control1Y, control2X, control2Y, endX, endY]
|
|
28571
28588
|
});
|
|
28572
28589
|
return this;
|
|
28573
28590
|
}
|
|
@@ -28579,12 +28596,38 @@ class PathBuilder {
|
|
|
28579
28596
|
}
|
|
28580
28597
|
}
|
|
28581
28598
|
function commandToString(command) {
|
|
28582
|
-
return `${command.action}${command.
|
|
28599
|
+
return `${command.action}${command.args.join(" ")}`;
|
|
28583
28600
|
}
|
|
28584
28601
|
function scaleCommandBy(scaleFactor) {
|
|
28585
|
-
return command =>
|
|
28586
|
-
|
|
28587
|
-
|
|
28602
|
+
return command => {
|
|
28603
|
+
switch (command.action) {
|
|
28604
|
+
case "A":
|
|
28605
|
+
// TODO(benchristel): do we want to refactor this switch to
|
|
28606
|
+
// use polymorphism?
|
|
28607
|
+
// Arc command
|
|
28608
|
+
return _extends({}, command, {
|
|
28609
|
+
args: [command.args[0] * scaleFactor,
|
|
28610
|
+
// x radius
|
|
28611
|
+
command.args[1] * scaleFactor,
|
|
28612
|
+
// y radius
|
|
28613
|
+
command.args[2],
|
|
28614
|
+
// rotation
|
|
28615
|
+
command.args[3],
|
|
28616
|
+
// largeArc flag
|
|
28617
|
+
command.args[4],
|
|
28618
|
+
// sweep flag
|
|
28619
|
+
command.args[5] * scaleFactor,
|
|
28620
|
+
// end x
|
|
28621
|
+
command.args[6] * scaleFactor // end y
|
|
28622
|
+
]
|
|
28623
|
+
});
|
|
28624
|
+
|
|
28625
|
+
default:
|
|
28626
|
+
return _extends({}, command, {
|
|
28627
|
+
args: command.args.map(c => c * scaleFactor)
|
|
28628
|
+
});
|
|
28629
|
+
}
|
|
28630
|
+
};
|
|
28588
28631
|
}
|
|
28589
28632
|
|
|
28590
28633
|
// We use the pathBuilder here to scale up the SVG path coordinates used
|
|
@@ -28972,8 +29015,169 @@ function snap(snapStep, point) {
|
|
|
28972
29015
|
return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
|
|
28973
29016
|
}
|
|
28974
29017
|
|
|
28975
|
-
|
|
28976
|
-
|
|
29018
|
+
// Returns the closest point to the given `point` that is within the graph
|
|
29019
|
+
// bounds given in `state`.
|
|
29020
|
+
function bound$1({
|
|
29021
|
+
snapStep,
|
|
29022
|
+
range,
|
|
29023
|
+
point
|
|
29024
|
+
}) {
|
|
29025
|
+
const [requestedX, requestedY] = point;
|
|
29026
|
+
const [snapX, snapY] = snapStep;
|
|
29027
|
+
const [[minX, maxX], [minY, maxY]] = range;
|
|
29028
|
+
return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
|
|
29029
|
+
}
|
|
29030
|
+
function clamp(value, min, max) {
|
|
29031
|
+
if (value < min) {
|
|
29032
|
+
return min;
|
|
29033
|
+
}
|
|
29034
|
+
if (value > max) {
|
|
29035
|
+
return max;
|
|
29036
|
+
}
|
|
29037
|
+
return value;
|
|
29038
|
+
}
|
|
29039
|
+
|
|
29040
|
+
var isProduction = process.env.NODE_ENV === 'production';
|
|
29041
|
+
var prefix = 'Invariant failed';
|
|
29042
|
+
function invariant(condition, message) {
|
|
29043
|
+
if (condition) {
|
|
29044
|
+
return;
|
|
29045
|
+
}
|
|
29046
|
+
if (isProduction) {
|
|
29047
|
+
throw new Error(prefix);
|
|
29048
|
+
}
|
|
29049
|
+
var provided = typeof message === 'function' ? message() : message;
|
|
29050
|
+
var value = provided ? "".concat(prefix, ": ").concat(provided) : prefix;
|
|
29051
|
+
throw new Error(value);
|
|
29052
|
+
}
|
|
29053
|
+
|
|
29054
|
+
/**
|
|
29055
|
+
* Code in this file is derived from
|
|
29056
|
+
* https://github.com/stevenpetryk/mafs/blob/4520319379a2cc2df8148d8baaef1f85db117103/src/interaction/useMovable.tsx#L20-L83
|
|
29057
|
+
* and copied here under the terms of the MIT license.
|
|
29058
|
+
*
|
|
29059
|
+
* Copyright 2021 Steven Petryk
|
|
29060
|
+
*
|
|
29061
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
29062
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
29063
|
+
* in the Software without restriction, including without limitation the rights
|
|
29064
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
29065
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
29066
|
+
* furnished to do so, subject to the following conditions:
|
|
29067
|
+
*
|
|
29068
|
+
* The above copyright notice and this permission notice shall be included in
|
|
29069
|
+
* all copies or substantial portions of the Software.
|
|
29070
|
+
*/
|
|
29071
|
+
|
|
29072
|
+
function useDraggable(args) {
|
|
29073
|
+
const {
|
|
29074
|
+
gestureTarget: target,
|
|
29075
|
+
onMove,
|
|
29076
|
+
point,
|
|
29077
|
+
constrain
|
|
29078
|
+
} = args;
|
|
29079
|
+
const [dragging, setDragging] = React.useState(false);
|
|
29080
|
+
const {
|
|
29081
|
+
xSpan,
|
|
29082
|
+
ySpan
|
|
29083
|
+
} = useSpanContext();
|
|
29084
|
+
const {
|
|
29085
|
+
viewTransform,
|
|
29086
|
+
userTransform
|
|
29087
|
+
} = useTransformContext();
|
|
29088
|
+
const inverseViewTransform = vec.matrixInvert(viewTransform);
|
|
29089
|
+
invariant(inverseViewTransform, "The view transform must be invertible.");
|
|
29090
|
+
const inverseTransform = React.useMemo(() => getInverseTransform(userTransform), [userTransform]);
|
|
29091
|
+
const pickup = React.useRef([0, 0]);
|
|
29092
|
+
useDrag(state => {
|
|
29093
|
+
const {
|
|
29094
|
+
type,
|
|
29095
|
+
event
|
|
29096
|
+
} = state;
|
|
29097
|
+
event == null || event.stopPropagation();
|
|
29098
|
+
const isKeyboard = type.includes("key");
|
|
29099
|
+
if (isKeyboard) {
|
|
29100
|
+
event == null || event.preventDefault();
|
|
29101
|
+
const {
|
|
29102
|
+
direction: yDownDirection,
|
|
29103
|
+
altKey,
|
|
29104
|
+
metaKey,
|
|
29105
|
+
shiftKey
|
|
29106
|
+
} = state;
|
|
29107
|
+
const direction = [yDownDirection[0], -yDownDirection[1]];
|
|
29108
|
+
const span = Math.abs(direction[0]) ? xSpan : ySpan;
|
|
29109
|
+
let divisions = 50;
|
|
29110
|
+
if (altKey || metaKey) {
|
|
29111
|
+
divisions = 200;
|
|
29112
|
+
}
|
|
29113
|
+
if (shiftKey) {
|
|
29114
|
+
divisions = 10;
|
|
29115
|
+
}
|
|
29116
|
+
const min = span / (divisions * 2);
|
|
29117
|
+
const tests = range(span / divisions, span / 2, span / divisions);
|
|
29118
|
+
for (const dx of tests) {
|
|
29119
|
+
// Transform the test back into the point's coordinate system
|
|
29120
|
+
const testMovement = vec.scale(direction, dx);
|
|
29121
|
+
const testPoint = constrain(vec.transform(vec.add(vec.transform(point, userTransform), testMovement), inverseTransform));
|
|
29122
|
+
if (vec.dist(testPoint, point) > min) {
|
|
29123
|
+
onMove(testPoint);
|
|
29124
|
+
break;
|
|
29125
|
+
}
|
|
29126
|
+
}
|
|
29127
|
+
} else {
|
|
29128
|
+
const {
|
|
29129
|
+
last,
|
|
29130
|
+
movement: pixelMovement,
|
|
29131
|
+
first
|
|
29132
|
+
} = state;
|
|
29133
|
+
setDragging(!last);
|
|
29134
|
+
if (first) {
|
|
29135
|
+
pickup.current = vec.transform(point, userTransform);
|
|
29136
|
+
}
|
|
29137
|
+
if (vec.mag(pixelMovement) === 0) {
|
|
29138
|
+
return;
|
|
29139
|
+
}
|
|
29140
|
+
const movement = vec.transform(pixelMovement, inverseViewTransform);
|
|
29141
|
+
onMove(constrain(vec.transform(vec.add(pickup.current, movement), inverseTransform)));
|
|
29142
|
+
}
|
|
29143
|
+
}, {
|
|
29144
|
+
target,
|
|
29145
|
+
eventOptions: {
|
|
29146
|
+
passive: false
|
|
29147
|
+
}
|
|
29148
|
+
});
|
|
29149
|
+
return {
|
|
29150
|
+
dragging
|
|
29151
|
+
};
|
|
29152
|
+
}
|
|
29153
|
+
function getInverseTransform(transform) {
|
|
29154
|
+
const invert = vec.matrixInvert(transform);
|
|
29155
|
+
invariant(invert !== null, "Could not invert transform matrix. A parent transformation matrix might be degenerative (mapping 2D space to a line).");
|
|
29156
|
+
return invert;
|
|
29157
|
+
}
|
|
29158
|
+
function useSpanContext() {
|
|
29159
|
+
const {
|
|
29160
|
+
range: [[xMin, xMax], [yMin, yMax]]
|
|
29161
|
+
} = useGraphConfig();
|
|
29162
|
+
const xSpan = xMax - xMin;
|
|
29163
|
+
const ySpan = yMax - yMin;
|
|
29164
|
+
return {
|
|
29165
|
+
xSpan,
|
|
29166
|
+
ySpan
|
|
29167
|
+
};
|
|
29168
|
+
}
|
|
29169
|
+
function range(min, max, step = 1) {
|
|
29170
|
+
const result = [];
|
|
29171
|
+
for (let i = min; i < max - step / 2; i += step) {
|
|
29172
|
+
result.push(i);
|
|
29173
|
+
}
|
|
29174
|
+
const computedMax = result[result.length - 1] + step;
|
|
29175
|
+
if (Math.abs(max - computedMax) < step / 1e-6) {
|
|
29176
|
+
result.push(max);
|
|
29177
|
+
} else {
|
|
29178
|
+
result.push(computedMax);
|
|
29179
|
+
}
|
|
29180
|
+
return result;
|
|
28977
29181
|
}
|
|
28978
29182
|
|
|
28979
29183
|
// The hitbox size of 48px by 48px is preserved from the legacy interactive
|
|
@@ -30996,6 +31200,154 @@ const LegacyGrid = ({
|
|
|
30996
31200
|
return null;
|
|
30997
31201
|
};
|
|
30998
31202
|
|
|
31203
|
+
const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
|
|
31204
|
+
|
|
31205
|
+
// The vector from the center of the protractor to the top left corner of the
|
|
31206
|
+
// protractor image, in pixels. Used for positioning.
|
|
31207
|
+
const centerToTopLeft = [-180, -170];
|
|
31208
|
+
|
|
31209
|
+
// The vector from the center of the protractor to the center of the rotation
|
|
31210
|
+
// handle.
|
|
31211
|
+
const centerToRotationHandle = [-176, -15];
|
|
31212
|
+
function Protractor() {
|
|
31213
|
+
const {
|
|
31214
|
+
range,
|
|
31215
|
+
snapStep
|
|
31216
|
+
} = useGraphConfig();
|
|
31217
|
+
const [[xMin, xMax], [yMin, yMax]] = range;
|
|
31218
|
+
// Position the protractor in the center of the graph (horizontally), and
|
|
31219
|
+
// 95% of the way to the bottom of the graph (vertically).
|
|
31220
|
+
const initialCenter = [lerp(xMin, xMax, 0.5), lerp(yMin, yMax, 0.05)];
|
|
31221
|
+
const [center, setCenter] = useState(initialCenter);
|
|
31222
|
+
const [rotationHandleOffset, setRotationHandleOffset] = useState(centerToRotationHandle);
|
|
31223
|
+
const draggableRef = useRef(null);
|
|
31224
|
+
useDraggable({
|
|
31225
|
+
gestureTarget: draggableRef,
|
|
31226
|
+
onMove: setCenter,
|
|
31227
|
+
point: center,
|
|
31228
|
+
constrain: point => bound$1({
|
|
31229
|
+
snapStep,
|
|
31230
|
+
range,
|
|
31231
|
+
point
|
|
31232
|
+
})
|
|
31233
|
+
});
|
|
31234
|
+
const rotationHandleRef = useRef(null);
|
|
31235
|
+
useDraggablePx({
|
|
31236
|
+
gestureTarget: rotationHandleRef,
|
|
31237
|
+
onMove: setRotationHandleOffset,
|
|
31238
|
+
point: rotationHandleOffset,
|
|
31239
|
+
constrain: constrainToCircle
|
|
31240
|
+
});
|
|
31241
|
+
const [centerPx] = useTransformVectorsToPixels(center);
|
|
31242
|
+
const topLeftPx = vec.add(centerPx, centerToTopLeft);
|
|
31243
|
+
const angle = calculateAngleInDegrees(rotationHandleOffset) - calculateAngleInDegrees(centerToRotationHandle);
|
|
31244
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
31245
|
+
ref: draggableRef,
|
|
31246
|
+
transform: `translate(${topLeftPx[0]}, ${topLeftPx[1]}), rotate(${angle})`,
|
|
31247
|
+
style: {
|
|
31248
|
+
transformOrigin: `${-centerToTopLeft[0]}px ${-centerToTopLeft[1]}px`
|
|
31249
|
+
}
|
|
31250
|
+
}, /*#__PURE__*/React.createElement("image", {
|
|
31251
|
+
href: protractorImage
|
|
31252
|
+
}), /*#__PURE__*/React.createElement("g", {
|
|
31253
|
+
transform: `translate(5, ${-centerToTopLeft[1]})`,
|
|
31254
|
+
ref: rotationHandleRef
|
|
31255
|
+
}, /*#__PURE__*/React.createElement(RotationArrow, null)));
|
|
31256
|
+
}
|
|
31257
|
+
function RotationArrow() {
|
|
31258
|
+
const radius = 175;
|
|
31259
|
+
const angleDeg = 10;
|
|
31260
|
+
const angleRad = degreesToRadians(angleDeg);
|
|
31261
|
+
const endX = radius * (1 - Math.cos(angleRad));
|
|
31262
|
+
const endY = radius * -Math.sin(angleRad);
|
|
31263
|
+
const rotationArrow = pathBuilder().move(0, 0).circularArc(radius, endX, endY, {
|
|
31264
|
+
sweep: true
|
|
31265
|
+
}).build();
|
|
31266
|
+
const arrowhead = pathBuilder().move(-8, 0).line(0, 10).line(8, 0).build();
|
|
31267
|
+
const targetRadius = TARGET_SIZE / 2;
|
|
31268
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
31269
|
+
className: "protractor-rotation-handle"
|
|
31270
|
+
}, /*#__PURE__*/React.createElement("path", {
|
|
31271
|
+
className: "protractor-rotation-handle-arrow-arc",
|
|
31272
|
+
d: rotationArrow
|
|
31273
|
+
}), /*#__PURE__*/React.createElement("path", {
|
|
31274
|
+
className: "protractor-rotation-handle-arrowhead",
|
|
31275
|
+
d: arrowhead
|
|
31276
|
+
}), /*#__PURE__*/React.createElement("path", {
|
|
31277
|
+
className: "protractor-rotation-handle-arrowhead",
|
|
31278
|
+
d: arrowhead,
|
|
31279
|
+
transform: `translate(${endX}, ${endY}), rotate(${180 + angleDeg})`
|
|
31280
|
+
}), /*#__PURE__*/React.createElement("ellipse", {
|
|
31281
|
+
cx: "0px",
|
|
31282
|
+
cy: "-15px",
|
|
31283
|
+
rx: targetRadius,
|
|
31284
|
+
ry: targetRadius,
|
|
31285
|
+
fill: "none"
|
|
31286
|
+
}));
|
|
31287
|
+
}
|
|
31288
|
+
const protractorRadius = vec.mag(centerToRotationHandle);
|
|
31289
|
+
function constrainToCircle(edgePoint) {
|
|
31290
|
+
return vec.withMag(edgePoint, protractorRadius);
|
|
31291
|
+
}
|
|
31292
|
+
|
|
31293
|
+
/* The implementation of useDraggablePx is derived from
|
|
31294
|
+
* https://github.com/stevenpetryk/mafs/blob/4520319379a2cc2df8148d8baaef1f85db117103/src/interaction/useMovable.tsx#L20-L83
|
|
31295
|
+
* and copied here under the terms of the MIT license.
|
|
31296
|
+
*
|
|
31297
|
+
* Copyright 2021 Steven Petryk
|
|
31298
|
+
*
|
|
31299
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
31300
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
31301
|
+
* in the Software without restriction, including without limitation the rights
|
|
31302
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
31303
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
31304
|
+
* furnished to do so, subject to the following conditions:
|
|
31305
|
+
*
|
|
31306
|
+
* The above copyright notice and this permission notice shall be included in
|
|
31307
|
+
* all copies or substantial portions of the Software.
|
|
31308
|
+
*/
|
|
31309
|
+
function useDraggablePx(args) {
|
|
31310
|
+
const {
|
|
31311
|
+
gestureTarget: target,
|
|
31312
|
+
onMove,
|
|
31313
|
+
point,
|
|
31314
|
+
constrain = p => p
|
|
31315
|
+
} = args;
|
|
31316
|
+
const pickupPx = React.useRef([0, 0]);
|
|
31317
|
+
useDrag(state => {
|
|
31318
|
+
const {
|
|
31319
|
+
event,
|
|
31320
|
+
first,
|
|
31321
|
+
movement: pixelMovement
|
|
31322
|
+
} = state;
|
|
31323
|
+
event == null || event.stopPropagation();
|
|
31324
|
+
if (first) {
|
|
31325
|
+
pickupPx.current = point;
|
|
31326
|
+
}
|
|
31327
|
+
if (vec.mag(pixelMovement) === 0) {
|
|
31328
|
+
return;
|
|
31329
|
+
}
|
|
31330
|
+
onMove == null || onMove(constrain(vec.add(pickupPx.current, pixelMovement)));
|
|
31331
|
+
}, {
|
|
31332
|
+
target,
|
|
31333
|
+
eventOptions: {
|
|
31334
|
+
passive: false
|
|
31335
|
+
}
|
|
31336
|
+
});
|
|
31337
|
+
}
|
|
31338
|
+
|
|
31339
|
+
// [L]inear Int[erp]olation: gets the weighted average of two values `a` and
|
|
31340
|
+
// `b`, with the given `fraction` specifying how much weight `a` and `b` get:
|
|
31341
|
+
// - if `fraction` is 0, `lerp` returns `a`.
|
|
31342
|
+
// - if `fraction` is 0.5, `lerp` returns the average of `a` and `b`.
|
|
31343
|
+
// - if `fraction` is 1, `lerp` returns `b`.
|
|
31344
|
+
function lerp(a, b, fraction) {
|
|
31345
|
+
return (b - a) * fraction + a;
|
|
31346
|
+
}
|
|
31347
|
+
function degreesToRadians(degrees) {
|
|
31348
|
+
return degrees / 180 * Math.PI;
|
|
31349
|
+
}
|
|
31350
|
+
|
|
30999
31351
|
function initializeGraphState(params) {
|
|
31000
31352
|
var _graph$snapTo;
|
|
31001
31353
|
const {
|
|
@@ -31678,19 +32030,6 @@ function boundAndSnapToSides(destinationPoint, {
|
|
|
31678
32030
|
return vector$1.add(coordsCopy[rel(-1)], offset);
|
|
31679
32031
|
}
|
|
31680
32032
|
|
|
31681
|
-
// Returns the closest point to the given `point` that is within the graph
|
|
31682
|
-
// bounds given in `state`.
|
|
31683
|
-
function bound$1({
|
|
31684
|
-
snapStep,
|
|
31685
|
-
range,
|
|
31686
|
-
point
|
|
31687
|
-
}) {
|
|
31688
|
-
const [requestedX, requestedY] = point;
|
|
31689
|
-
const [snapX, snapY] = snapStep;
|
|
31690
|
-
const [[minX, maxX], [minY, maxY]] = range;
|
|
31691
|
-
return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
|
|
31692
|
-
}
|
|
31693
|
-
|
|
31694
32033
|
// Returns the vector from the given point to the top-right corner of the graph when snapped to the grid
|
|
31695
32034
|
function maxMove({
|
|
31696
32035
|
snapStep,
|
|
@@ -31718,15 +32057,6 @@ function minMove({
|
|
|
31718
32057
|
});
|
|
31719
32058
|
return vec.sub(bottomLeft, point);
|
|
31720
32059
|
}
|
|
31721
|
-
function clamp(value, min, max) {
|
|
31722
|
-
if (value < min) {
|
|
31723
|
-
return min;
|
|
31724
|
-
}
|
|
31725
|
-
if (value > max) {
|
|
31726
|
-
return max;
|
|
31727
|
-
}
|
|
31728
|
-
return value;
|
|
31729
|
-
}
|
|
31730
32060
|
const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && vector$1.equal(coord, c)));
|
|
31731
32061
|
function updateAtIndex(args) {
|
|
31732
32062
|
const {
|
|
@@ -31856,6 +32186,10 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
31856
32186
|
dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
|
|
31857
32187
|
}, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
|
|
31858
32188
|
const numSegments = graph.type === "segment" ? graph.numSegments : null;
|
|
32189
|
+
const numSides = graph.type === "polygon" ? graph.numSides : null;
|
|
32190
|
+
const snapTo = graph.type === "polygon" ? graph.snapTo : null;
|
|
32191
|
+
const showAngles = graph.type === "polygon" ? graph.showAngles : null;
|
|
32192
|
+
const showSides = graph.type === "polygon" ? graph.showSides : null;
|
|
31859
32193
|
const originalPropsRef = useRef(props);
|
|
31860
32194
|
const latestPropsRef = useLatestRef(props);
|
|
31861
32195
|
useEffect(() => {
|
|
@@ -31866,7 +32200,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
31866
32200
|
if (latestPropsRef.current !== originalPropsRef.current) {
|
|
31867
32201
|
dispatch(reinitialize(latestPropsRef.current));
|
|
31868
32202
|
}
|
|
31869
|
-
}, [graph.type, numSegments, latestPropsRef]);
|
|
32203
|
+
}, [graph.type, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef]);
|
|
31870
32204
|
return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
|
|
31871
32205
|
state: state,
|
|
31872
32206
|
dispatch: dispatch
|
|
@@ -31933,7 +32267,7 @@ const MafsGraph = props => {
|
|
|
31933
32267
|
}), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
|
|
31934
32268
|
lockedFigures: props.lockedFigures,
|
|
31935
32269
|
range: state.range
|
|
31936
|
-
}), renderGraph({
|
|
32270
|
+
}), "/* Protractor */", props.showProtractor && /*#__PURE__*/React.createElement(Protractor, null), renderGraph({
|
|
31937
32271
|
state,
|
|
31938
32272
|
dispatch
|
|
31939
32273
|
})))));
|
|
@@ -33241,14 +33575,9 @@ class InteractiveGraph extends React.Component {
|
|
|
33241
33575
|
}
|
|
33242
33576
|
render() {
|
|
33243
33577
|
var _this$props$apiOption;
|
|
33244
|
-
if (this.props.graph.type === "polygon" && this.props.graph.numSides === "unlimited") {
|
|
33245
|
-
return /*#__PURE__*/React.createElement(LegacyInteractiveGraph, _extends({
|
|
33246
|
-
ref: this.legacyGraphRef
|
|
33247
|
-
}, this.props));
|
|
33248
|
-
}
|
|
33249
|
-
|
|
33250
33578
|
// Mafs shim
|
|
33251
|
-
|
|
33579
|
+
const mafsFlags = (_this$props$apiOption = this.props.apiOptions) == null || (_this$props$apiOption = _this$props$apiOption.flags) == null ? void 0 : _this$props$apiOption["mafs"];
|
|
33580
|
+
if (shouldUseMafs(mafsFlags, this.props.graph)) {
|
|
33252
33581
|
const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
|
|
33253
33582
|
const gridStep = this.props.gridStep || Util.getGridStep(this.props.range, this.props.step, box[0]);
|
|
33254
33583
|
const snapStep = this.props.snapStep || Util.snapStepFromGridStep(gridStep);
|
|
@@ -33809,6 +34138,8 @@ class InteractiveGraph extends React.Component {
|
|
|
33809
34138
|
return props.graph;
|
|
33810
34139
|
}
|
|
33811
34140
|
}
|
|
34141
|
+
|
|
34142
|
+
// exported for testing
|
|
33812
34143
|
InteractiveGraph.defaultProps = {
|
|
33813
34144
|
labels: ["x", "y"],
|
|
33814
34145
|
range: [[-10, 10], [-10, 10]],
|
|
@@ -33824,6 +34155,31 @@ InteractiveGraph.defaultProps = {
|
|
|
33824
34155
|
type: "linear"
|
|
33825
34156
|
}
|
|
33826
34157
|
};
|
|
34158
|
+
function shouldUseMafs(mafsFlags, graph) {
|
|
34159
|
+
if (typeof mafsFlags === "boolean" || typeof mafsFlags === "undefined") {
|
|
34160
|
+
return false;
|
|
34161
|
+
}
|
|
34162
|
+
switch (graph.type) {
|
|
34163
|
+
case "point":
|
|
34164
|
+
if (graph.numPoints === UNLIMITED) {
|
|
34165
|
+
// TODO(benchristel): add a feature flag for the "unlimited"
|
|
34166
|
+
// case once we've implemented point graphs with unlimited
|
|
34167
|
+
// points
|
|
34168
|
+
return false;
|
|
34169
|
+
}
|
|
34170
|
+
return Boolean(mafsFlags["point"]);
|
|
34171
|
+
case "polygon":
|
|
34172
|
+
if (graph.numSides === UNLIMITED) {
|
|
34173
|
+
// TODO(benchristel): add a feature flag for the "unlimited"
|
|
34174
|
+
// case once we've implemented polygon graphs with unlimited
|
|
34175
|
+
// sides
|
|
34176
|
+
return false;
|
|
34177
|
+
}
|
|
34178
|
+
return Boolean(mafsFlags["polygon"]);
|
|
34179
|
+
default:
|
|
34180
|
+
return Boolean(mafsFlags[graph.type]);
|
|
34181
|
+
}
|
|
34182
|
+
}
|
|
33827
34183
|
var InteractiveGraph$1 = {
|
|
33828
34184
|
name: "interactive-graph",
|
|
33829
34185
|
displayName: "Interactive graph",
|
|
@@ -44039,7 +44395,7 @@ const init = function init(options) {
|
|
|
44039
44395
|
|
|
44040
44396
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44041
44397
|
const libName = "@khanacademy/perseus";
|
|
44042
|
-
const libVersion = "
|
|
44398
|
+
const libVersion = "24.1.0";
|
|
44043
44399
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44044
44400
|
|
|
44045
44401
|
const apiVersion = {
|