@khanacademy/perseus-editor 0.0.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/.babelrc.js +1 -1
  2. package/CHANGELOG.md +26 -0
  3. package/dist/es/index.css +1020 -0
  4. package/dist/es/index.js +9 -1
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.css +1020 -0
  7. package/dist/index.js +9 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/strings.js +6 -1
  10. package/package.json +16 -4
  11. package/src/README.md +7 -0
  12. package/src/__stories__/editor.stories.js +33 -0
  13. package/src/__stories__/section-control-button.stories.jsx +27 -0
  14. package/src/__testdata__/input-number_testdata.js +28 -0
  15. package/src/__tests__/editor_test.jsx +123 -0
  16. package/src/__tests__/i18n_test.jsx +585 -0
  17. package/src/__tests__/traversal_test.jsx +378 -0
  18. package/src/all-editors.js +85 -0
  19. package/src/all-editors_test.js +17 -0
  20. package/src/all-widgets.js +10 -0
  21. package/src/article-editor.jsx +437 -0
  22. package/src/components/__stories__/blur-input.stories.jsx +24 -0
  23. package/src/components/blur-input.jsx +62 -0
  24. package/src/components/checkbox.jsx +194 -0
  25. package/src/components/device-framer.jsx +91 -0
  26. package/src/components/drag-target.jsx +88 -0
  27. package/src/components/dropdown-option.jsx +318 -0
  28. package/src/components/form-wrapped-text-field.jsx +248 -0
  29. package/src/components/graph-settings.jsx +631 -0
  30. package/src/components/hover-behavior.jsx +178 -0
  31. package/src/components/json-editor.jsx +121 -0
  32. package/src/components/link.jsx +173 -0
  33. package/src/components/simple-button.jsx +86 -0
  34. package/src/components/sortable.jsx +186 -0
  35. package/src/components/util.js +39 -0
  36. package/src/components/viewport-resizer.jsx +73 -0
  37. package/src/diffs/__stories__/perseus-diff-wrapper.jsx +8 -0
  38. package/src/diffs/__stories__/structured-item-diff.stories.jsx +855 -0
  39. package/src/diffs/__stories__/tags-diff.stories.jsx +38 -0
  40. package/src/diffs/__stories__/text-diff.stories.jsx +37 -0
  41. package/src/diffs/__tests__/split-diff_test.js +34 -0
  42. package/src/diffs/__tests__/string-array-diff_test.js +74 -0
  43. package/src/diffs/__tests__/structured-item-diff_test.js +432 -0
  44. package/src/diffs/__tests__/widget-diff-performer_test.js +44 -0
  45. package/src/diffs/article-diff.jsx +70 -0
  46. package/src/diffs/item-diff.jsx +77 -0
  47. package/src/diffs/renderer-diff.jsx +143 -0
  48. package/src/diffs/split-diff.jsx +30 -0
  49. package/src/diffs/string-array-diff.jsx +75 -0
  50. package/src/diffs/structured-item-diff.jsx +269 -0
  51. package/src/diffs/tags-diff.jsx +50 -0
  52. package/src/diffs/text-diff.jsx +194 -0
  53. package/src/diffs/widget-diff-performer.jsx +74 -0
  54. package/src/diffs/widget-diff.jsx +291 -0
  55. package/src/editor-page.jsx +351 -0
  56. package/src/editor.jsx +1364 -0
  57. package/src/hint-editor.jsx +447 -0
  58. package/src/i18n.jsx +139 -0
  59. package/src/iframe-content-renderer.jsx +201 -0
  60. package/src/index.js +27 -8
  61. package/src/item-editor.jsx +159 -0
  62. package/src/item-extras-editor.jsx +150 -0
  63. package/src/katex-error-view.jsx +87 -0
  64. package/src/multirenderer-editor.jsx +1074 -0
  65. package/src/section-control-button.jsx +53 -0
  66. package/src/stateful-article-editor.jsx +176 -0
  67. package/src/stateful-editor-page.jsx +90 -0
  68. package/src/styles/perseus-editor.less +1339 -0
  69. package/src/testing-widgets.js +12 -0
  70. package/src/util/register-all-widgets-and-editors-for-testing.js +15 -0
  71. package/src/util/register-all-widgets-and-editors-for-testing_test.js +18 -0
  72. package/src/widgets/__stories__/label-image-editor.stories.jsx +97 -0
  73. package/src/widgets/categorizer-editor.jsx +103 -0
  74. package/src/widgets/cs-program-editor.jsx +258 -0
  75. package/src/widgets/definition-editor.jsx +75 -0
  76. package/src/widgets/dropdown-editor.jsx +201 -0
  77. package/src/widgets/example-graphie-widget-editor.jsx +70 -0
  78. package/src/widgets/example-graphie-widget.jsx +175 -0
  79. package/src/widgets/example-widget-editor.jsx +60 -0
  80. package/src/widgets/example-widget.jsx +153 -0
  81. package/src/widgets/explanation-editor.jsx +89 -0
  82. package/src/widgets/expression-editor.jsx +560 -0
  83. package/src/widgets/graded-group-editor.jsx +162 -0
  84. package/src/widgets/graded-group-set-editor.jsx +101 -0
  85. package/src/widgets/grapher-editor.jsx +154 -0
  86. package/src/widgets/group-editor.jsx +83 -0
  87. package/src/widgets/iframe-editor.jsx +200 -0
  88. package/src/widgets/image-editor.jsx +326 -0
  89. package/src/widgets/input-number-editor.jsx +269 -0
  90. package/src/widgets/interaction/constraint-editor.jsx +147 -0
  91. package/src/widgets/interaction-editor.jsx +1389 -0
  92. package/src/widgets/interactive-graph-editor.jsx +322 -0
  93. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +81 -0
  94. package/src/widgets/label-image/__stories__/behavior.stories.jsx +32 -0
  95. package/src/widgets/label-image/__stories__/marker.stories.jsx +76 -0
  96. package/src/widgets/label-image/__stories__/question-markers.stories.jsx +106 -0
  97. package/src/widgets/label-image/__stories__/select-image.stories.jsx +60 -0
  98. package/src/widgets/label-image/answer-choices.jsx +231 -0
  99. package/src/widgets/label-image/behavior.jsx +92 -0
  100. package/src/widgets/label-image/marker.jsx +292 -0
  101. package/src/widgets/label-image/question-markers.jsx +181 -0
  102. package/src/widgets/label-image/select-image.jsx +86 -0
  103. package/src/widgets/label-image-editor.jsx +257 -0
  104. package/src/widgets/lights-puzzle-editor.jsx +267 -0
  105. package/src/widgets/matcher-editor.jsx +147 -0
  106. package/src/widgets/matrix-editor.jsx +155 -0
  107. package/src/widgets/measurer-editor.jsx +245 -0
  108. package/src/widgets/molecule-editor.jsx +63 -0
  109. package/src/widgets/number-line-editor.jsx +498 -0
  110. package/src/widgets/numeric-input-editor.jsx +554 -0
  111. package/src/widgets/orderer-editor.jsx +166 -0
  112. package/src/widgets/passage-editor.jsx +115 -0
  113. package/src/widgets/passage-ref-editor.jsx +77 -0
  114. package/src/widgets/passage-ref-target-editor.jsx +47 -0
  115. package/src/widgets/plotter-editor.jsx +537 -0
  116. package/src/widgets/radio/editor.jsx +398 -0
  117. package/src/widgets/reaction-diagram-editor.jsx +127 -0
  118. package/src/widgets/sequence-editor.jsx +205 -0
  119. package/src/widgets/simple-markdown-tester-editor.jsx +84 -0
  120. package/src/widgets/simple-markdown-tester.jsx +91 -0
  121. package/src/widgets/simulator-editor.jsx +144 -0
  122. package/src/widgets/sorter-editor.jsx +100 -0
  123. package/src/widgets/table-editor.jsx +164 -0
  124. package/src/widgets/transformer-editor.jsx +1006 -0
  125. package/src/widgets/unit-editor.jsx +293 -0
  126. package/src/widgets/video-editor.jsx +74 -0
  127. package/src/__tests__/index.test.js +0 -15
@@ -0,0 +1,498 @@
1
+ // @flow
2
+ import {number as knumber} from "@khanacademy/kmath";
3
+ import {components, EditorJsonify} from "@khanacademy/perseus";
4
+ import PropTypes from "prop-types";
5
+ import * as React from "react";
6
+ import _ from "underscore";
7
+
8
+ const {ButtonGroup, InfoTip, NumberInput, PropCheckBox, RangeInput} =
9
+ components;
10
+
11
+ type Range = [number, number];
12
+
13
+ const bound = (x: number, gt: number, lt: number): number =>
14
+ Math.min(Math.max(x, gt), lt);
15
+
16
+ const EN_DASH = "\u2013";
17
+
18
+ type Props = $FlowFixMe;
19
+
20
+ class NumberLineEditor extends React.Component<Props> {
21
+ static propTypes = {
22
+ range: PropTypes.arrayOf(PropTypes.number).isRequired,
23
+
24
+ labelRange: PropTypes.arrayOf(PropTypes.number).isRequired,
25
+ labelStyle: PropTypes.string.isRequired,
26
+ labelTicks: PropTypes.bool,
27
+
28
+ divisionRange: PropTypes.arrayOf(PropTypes.number).isRequired,
29
+ numDivisions: PropTypes.number.isRequired,
30
+ snapDivisions: PropTypes.number,
31
+
32
+ tickStep: PropTypes.number,
33
+ correctRel: PropTypes.oneOf(["lt", "gt", "le", "ge", "eq"]),
34
+ correctX: PropTypes.number,
35
+ initialX: PropTypes.number,
36
+ isTickCtrl: PropTypes.bool,
37
+
38
+ onChange: PropTypes.func.isRequired,
39
+
40
+ static: PropTypes.bool,
41
+ showTooltips: PropTypes.bool,
42
+ };
43
+
44
+ static widgetName: "number-line" = "number-line";
45
+
46
+ static defaultProps: Props = {
47
+ range: [0, 10],
48
+
49
+ labelRange: [null, null],
50
+ labelStyle: "decimal",
51
+ labelTicks: true,
52
+
53
+ divisionRange: [1, 12],
54
+ numDivisions: 5,
55
+ snapDivisions: 2,
56
+
57
+ tickStep: null,
58
+ correctRel: "eq",
59
+ correctX: null,
60
+ initialX: null,
61
+
62
+ showTooltips: false,
63
+ };
64
+
65
+ onRangeChange: (Range) => void = (range) => {
66
+ // Changing the range constrains the initial position, as well as the
67
+ // position of the answer and labels. Atm, it just marks them as
68
+ // invalid and prevents the number line from showing; it was annoying
69
+ // to change it for them, because if they're typing in fractions,
70
+ // it registers one-at-a-time and messes things up.
71
+ this.props.onChange({range: range});
72
+ };
73
+
74
+ onLabelRangeChange: (number, number) => void = (i, num) => {
75
+ let labelRange = this.props.labelRange.slice();
76
+ const otherNum = labelRange[1 - i];
77
+
78
+ if (num == null || otherNum == null) {
79
+ labelRange[i] = num;
80
+ } else {
81
+ // If both labels have values, this updates the "appropriate" one.
82
+ // It enforces that the position of the left label <= right label.
83
+ // If left otherwise, it makes certain aspects of validation hard.
84
+ labelRange = [Math.min(num, otherNum), Math.max(num, otherNum)];
85
+ }
86
+
87
+ this.props.onChange({labelRange: labelRange});
88
+ };
89
+
90
+ onDivisionRangeChange: (Range) => void = (divisionRange) => {
91
+ let numDivisions = this.props.numDivisions;
92
+ numDivisions = bound(numDivisions, divisionRange[0], divisionRange[1]);
93
+ this.props.onChange({
94
+ divisionRange: divisionRange,
95
+ numDivisions: numDivisions,
96
+ });
97
+ };
98
+
99
+ onNumChange: ("correctX" | "initialX" | "snapDivisions", number) => void = (
100
+ key,
101
+ value,
102
+ ) => {
103
+ const opts = {};
104
+ opts[key] = value;
105
+ this.props.onChange(opts);
106
+ };
107
+
108
+ onNumDivisionsChange: (number) => void = (numDivisions) => {
109
+ const divRange = this.props.divisionRange.slice();
110
+
111
+ // Don't allow a fraction for the number of divisions
112
+ numDivisions = _.isFinite(numDivisions) ? Math.round(numDivisions) : 0;
113
+
114
+ // Don't allow negative numbers for the number of divisions
115
+ numDivisions = numDivisions < 0 ? numDivisions * -1 : numDivisions;
116
+
117
+ // If the number of divisions isn't blank, update the number line
118
+ if (numDivisions) {
119
+ // Constrain numDivisions to be within the allowed range
120
+ numDivisions = Math.min(
121
+ divRange[1],
122
+ Math.max(divRange[0], numDivisions),
123
+ );
124
+
125
+ this.props.onChange({
126
+ tickStep: null,
127
+ divisionRange: divRange,
128
+ numDivisions: numDivisions,
129
+ });
130
+ }
131
+ };
132
+
133
+ onTickStepChange: (number) => void = (tickStep) => {
134
+ this.props.onChange({
135
+ numDivisions: null,
136
+ tickStep: tickStep,
137
+ });
138
+ };
139
+
140
+ onChangeRelation: (SyntheticInputEvent<>) => void = (e) => {
141
+ const value = e.target.value;
142
+ this.props.onChange({
143
+ correctRel: value,
144
+ isInequality: value !== "eq",
145
+ });
146
+ };
147
+
148
+ onLabelStyleChange: ($FlowFixMe) => void = (labelStyle) => {
149
+ this.props.onChange({
150
+ labelStyle: labelStyle,
151
+ });
152
+ };
153
+
154
+ serialize: () => $FlowFixMe = () => {
155
+ return EditorJsonify.serialize.call(this);
156
+ };
157
+
158
+ render(): React.Node {
159
+ const range = this.props.range;
160
+ const labelRange = this.props.labelRange;
161
+ const divisionRange = this.props.divisionRange;
162
+
163
+ range[0] = +range[0];
164
+ range[1] = +range[1];
165
+
166
+ const width = range[1] - range[0];
167
+ const numDivisions = this.props.numDivisions;
168
+ const snapDivisions = this.props.snapDivisions;
169
+ const tickStep = this.props.tickStep;
170
+ const isTickCtrl = this.props.isTickCtrl;
171
+
172
+ let step;
173
+ if (!isTickCtrl) {
174
+ // this will help constrain the answer to what is reachable
175
+ step = tickStep
176
+ ? tickStep / snapDivisions
177
+ : width / numDivisions / snapDivisions;
178
+ } else {
179
+ // but if tickCtrl is on, the range of what is reachable is
180
+ // rather large, and it becomes obnoxious to check for this
181
+ step = null;
182
+ }
183
+
184
+ const labelStyleEditorButtons = [
185
+ {
186
+ value: "decimal",
187
+ content: "0.75",
188
+ title: "Decimals",
189
+ },
190
+ {
191
+ value: "improper",
192
+ content: "\u2077\u2044\u2084",
193
+ title: "Improper fractions",
194
+ },
195
+ {
196
+ value: "mixed",
197
+ content: "1\u00BE",
198
+ title: "Mixed numbers",
199
+ },
200
+ {
201
+ value: "non-reduced",
202
+ content: "\u2078\u2044\u2084",
203
+ title: "Non-reduced",
204
+ },
205
+ ];
206
+
207
+ return (
208
+ <div className="perseus-widget-number-line-editor">
209
+ <div className="perseus-widget-row">
210
+ Correct x{" "}
211
+ <select
212
+ value={this.props.correctRel}
213
+ onChange={this.onChangeRelation}
214
+ >
215
+ <option value="eq"> = </option>
216
+ <option value="lt"> &lt; </option>
217
+ <option value="gt"> &gt; </option>
218
+ <option value="le"> &le; </option>
219
+ <option value="ge"> &ge; </option>
220
+ </select>{" "}
221
+ <NumberInput
222
+ value={this.props.correctX}
223
+ format={this.props.labelStyle}
224
+ // eslint-disable-next-line react/jsx-no-bind
225
+ onChange={this.onNumChange.bind(this, "correctX")}
226
+ checkValidity={(val) =>
227
+ val >= range[0] &&
228
+ val <= range[1] &&
229
+ (!step ||
230
+ knumber.isInteger((val - range[0]) / step))
231
+ }
232
+ placeholder="answer"
233
+ size="normal"
234
+ useArrowKeys={true}
235
+ />
236
+ <InfoTip>
237
+ <p>
238
+ This is the correct answer. The answer is validated
239
+ (as right or wrong) by using only the end position
240
+ of the point and the relation (=, &lt;, &gt;, &le;,
241
+ &ge;).
242
+ </p>
243
+ </InfoTip>
244
+ </div>
245
+
246
+ <div className="perseus-widget-row">
247
+ {this.props.static ? (
248
+ <label>
249
+ {/* Don't display initial position input in
250
+ static mode since it isn't used. */}
251
+ Range:
252
+ </label>
253
+ ) : (
254
+ <label>
255
+ Position:{" "}
256
+ <NumberInput
257
+ value={this.props.initialX}
258
+ format={this.props.labelStyle}
259
+ // eslint-disable-next-line react/jsx-no-bind
260
+ onChange={this.onNumChange.bind(
261
+ this,
262
+ "initialX",
263
+ )}
264
+ placeholder={range[0]}
265
+ checkValidity={(val) => {
266
+ return val >= range[0] && val <= range[1];
267
+ }}
268
+ useArrowKeys={true}
269
+ />
270
+ {" \u2208 "}
271
+ </label>
272
+ )}
273
+
274
+ <RangeInput
275
+ value={range}
276
+ onChange={this.onRangeChange}
277
+ format={this.props.labelStyle}
278
+ useArrowKeys={true}
279
+ />
280
+ <InfoTip>
281
+ <p>
282
+ This controls the initial position of the point
283
+ along the number line and the
284
+ <strong>range</strong>, the position of the
285
+ endpoints of the number line. Setting the range
286
+ constrains the position of the answer and the
287
+ labels.
288
+ </p>
289
+ <p>
290
+ In static mode, the initial position of the point is
291
+ determined by Correct x instead of position.
292
+ </p>
293
+ </InfoTip>
294
+ </div>
295
+ <div className="perseus-widget-row">
296
+ <div className="perseus-widget-left-col">
297
+ Labels:{" "}
298
+ <NumberInput
299
+ value={labelRange[0]}
300
+ placeholder={range[0]}
301
+ format={this.props.labelStyle}
302
+ checkValidity={(val) =>
303
+ val >= range[0] && val <= range[1]
304
+ }
305
+ // eslint-disable-next-line react/jsx-no-bind
306
+ onChange={this.onLabelRangeChange.bind(this, 0)}
307
+ useArrowKeys={true}
308
+ />
309
+ <span> &amp; </span>
310
+ <NumberInput
311
+ value={labelRange[1]}
312
+ placeholder={range[1]}
313
+ format={this.props.labelStyle}
314
+ checkValidity={(val) =>
315
+ val >= range[0] && val <= range[1]
316
+ }
317
+ // eslint-disable-next-line react/jsx-no-bind
318
+ onChange={this.onLabelRangeChange.bind(this, 1)}
319
+ useArrowKeys={true}
320
+ />
321
+ <InfoTip>
322
+ <p>
323
+ This controls the position of the left / right
324
+ labels. By default, the labels are set by the
325
+ range <br />
326
+ <strong>Note:</strong> Ensure that the labels
327
+ line up with the tick marks, or it may be
328
+ confusing for users.
329
+ </p>
330
+ </InfoTip>
331
+ </div>
332
+ </div>
333
+ <div className="perseus-widget-row">
334
+ Style:{" "}
335
+ <ButtonGroup
336
+ allowEmpty={false}
337
+ value={this.props.labelStyle}
338
+ buttons={labelStyleEditorButtons}
339
+ onChange={this.onLabelStyleChange}
340
+ />
341
+ <InfoTip>
342
+ <p>
343
+ This controls the styling of the labels for the two
344
+ main labels as well as all the tick mark labels, if
345
+ applicable. Your choices are decimal, improper
346
+ fractions, mixed fractions, and non-reduced
347
+ fractions.
348
+ </p>
349
+ </InfoTip>
350
+ </div>
351
+ <div className="perseus-widget-row">
352
+ {!this.props.static && (
353
+ <div className="perseus-widget-left-col">
354
+ <PropCheckBox
355
+ label="Show tick controller"
356
+ isTickCtrl={this.props.isTickCtrl}
357
+ onChange={this.props.onChange}
358
+ />
359
+ </div>
360
+ )}
361
+ <div className="perseus-widget-right-col">
362
+ <PropCheckBox
363
+ label="Show label ticks"
364
+ labelTicks={this.props.labelTicks}
365
+ onChange={this.props.onChange}
366
+ />
367
+ </div>
368
+ </div>
369
+
370
+ <div className="perseus-widget-row">
371
+ {!this.props.static && (
372
+ <PropCheckBox
373
+ label="Show tooltips"
374
+ showTooltips={this.props.showTooltips}
375
+ onChange={this.props.onChange}
376
+ />
377
+ )}
378
+ </div>
379
+ <div className="perseus-widget-row">
380
+ {isTickCtrl && (
381
+ <span>
382
+ <label>
383
+ Start num divisions at{" "}
384
+ <NumberInput
385
+ value={this.props.numDivisions || null}
386
+ format="decimal"
387
+ onChange={this.onNumDivisionsChange}
388
+ checkValidity={(val) => {
389
+ return (
390
+ val >= divisionRange[0] &&
391
+ val <= divisionRange[1]
392
+ );
393
+ }}
394
+ placeholder={width / this.props.tickStep}
395
+ useArrowKeys={true}
396
+ />
397
+ </label>
398
+ <InfoTip>
399
+ <p>
400
+ This controls the number (and position) of
401
+ the tick marks. The number of divisions is
402
+ constrained to
403
+ {" " +
404
+ divisionRange[0] +
405
+ EN_DASH +
406
+ divisionRange[1]}
407
+ .
408
+ <br />
409
+ <strong>Note:</strong> The user will be able
410
+ to specify the number of divisions in a
411
+ number input.
412
+ </p>
413
+ </InfoTip>
414
+ </span>
415
+ )}
416
+ {!isTickCtrl && (
417
+ <span>
418
+ <label>
419
+ Num divisions:{" "}
420
+ <NumberInput
421
+ value={this.props.numDivisions || null}
422
+ format="decimal"
423
+ onChange={this.onNumDivisionsChange}
424
+ checkValidity={(val) => {
425
+ return (
426
+ val >= divisionRange[0] &&
427
+ val <= divisionRange[1]
428
+ );
429
+ }}
430
+ placeholder={width / this.props.tickStep}
431
+ useArrowKeys={true}
432
+ />
433
+ </label>{" "}
434
+ <label>
435
+ or tick step:{" "}
436
+ <NumberInput
437
+ value={this.props.tickStep || null}
438
+ format={this.props.labelStyle}
439
+ onChange={this.onTickStepChange}
440
+ checkValidity={(val) => {
441
+ return val > 0 && val <= width;
442
+ }}
443
+ placeholder={
444
+ width / this.props.numDivisions
445
+ }
446
+ useArrowKeys={true}
447
+ />
448
+ </label>
449
+ <InfoTip>
450
+ <p>
451
+ This controls the number (and position) of
452
+ the tick marks; you can either set the
453
+ number of divisions (2 divisions would split
454
+ the entire range in two halves), or the tick
455
+ step (the distance between ticks) and the
456
+ other value will be updated accordingly.{" "}
457
+ <br />
458
+ <strong>Note:</strong> There is no check to
459
+ see if labels coordinate with the tick
460
+ marks, which may be confusing for users if
461
+ the blue labels and black ticks are
462
+ off-step.
463
+ </p>
464
+ </InfoTip>
465
+ </span>
466
+ )}
467
+ </div>
468
+ <div className="perseus-widget-row">
469
+ <label>
470
+ Snap increments per tick:{" "}
471
+ <NumberInput
472
+ value={snapDivisions}
473
+ checkValidity={(val) => val > 0}
474
+ format={this.props.labelStyle}
475
+ // eslint-disable-next-line react/jsx-no-bind
476
+ onChange={this.onNumChange.bind(
477
+ this,
478
+ "snapDivisions",
479
+ )}
480
+ useArrowKeys={true}
481
+ />
482
+ </label>
483
+ <InfoTip>
484
+ <p>
485
+ This determines the number of different places the
486
+ point will snap between two adjacent tick marks.{" "}
487
+ <br />
488
+ <strong>Note:</strong>Ensure the required number of
489
+ snap increments is provided to answer the question.
490
+ </p>
491
+ </InfoTip>
492
+ </div>
493
+ </div>
494
+ );
495
+ }
496
+ }
497
+
498
+ export default NumberLineEditor;