@pie-element/math-templated 7.1.1-next.12 → 7.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 (98) hide show
  1. package/CHANGELOG.json +1 -0
  2. package/CHANGELOG.md +1144 -0
  3. package/LICENSE.md +5 -0
  4. package/README.md +0 -0
  5. package/configure/CHANGELOG.json +1 -0
  6. package/configure/CHANGELOG.md +1021 -0
  7. package/configure/lib/defaults.js +172 -0
  8. package/configure/lib/defaults.js.map +1 -0
  9. package/configure/lib/design.js +546 -0
  10. package/configure/lib/design.js.map +1 -0
  11. package/configure/lib/index.js +169 -0
  12. package/configure/lib/index.js.map +1 -0
  13. package/configure/lib/markupUtils.js +39 -0
  14. package/configure/lib/markupUtils.js.map +1 -0
  15. package/configure/lib/response.js +394 -0
  16. package/configure/lib/response.js.map +1 -0
  17. package/configure/lib/utils.js +15 -0
  18. package/configure/lib/utils.js.map +1 -0
  19. package/configure/package.json +26 -0
  20. package/controller/CHANGELOG.json +1 -0
  21. package/controller/CHANGELOG.md +874 -0
  22. package/controller/lib/defaults.js +28 -0
  23. package/controller/lib/defaults.js.map +1 -0
  24. package/controller/lib/index.js +360 -0
  25. package/controller/lib/index.js.map +1 -0
  26. package/controller/package.json +17 -0
  27. package/docs/config-schema.json +2251 -0
  28. package/docs/config-schema.json.md +1658 -0
  29. package/docs/demo/config.js +8 -0
  30. package/docs/demo/generate.js +42 -0
  31. package/docs/demo/index.html +1 -0
  32. package/docs/demo/session.js +14 -0
  33. package/docs/pie-schema.json +1231 -0
  34. package/docs/pie-schema.json.md +894 -0
  35. package/lib/index.js +76 -0
  36. package/lib/index.js.map +1 -0
  37. package/lib/main.js +877 -0
  38. package/lib/main.js.map +1 -0
  39. package/lib/print.js +88 -0
  40. package/lib/print.js.map +1 -0
  41. package/module/configure.js +1 -0
  42. package/module/controller.js +3711 -0
  43. package/module/demo.js +67 -0
  44. package/module/element.js +1 -0
  45. package/module/index.html +21 -0
  46. package/module/manifest.json +22 -0
  47. package/module/print-demo.js +105 -0
  48. package/module/print.html +18 -0
  49. package/module/print.js +1 -0
  50. package/package.json +22 -93
  51. package/configure.js +0 -2
  52. package/controller.js +0 -1
  53. package/dist/author/defaults.d.ts +0 -173
  54. package/dist/author/defaults.js +0 -123
  55. package/dist/author/design.d.ts +0 -35
  56. package/dist/author/design.js +0 -360
  57. package/dist/author/index.d.ts +0 -42
  58. package/dist/author/index.js +0 -97
  59. package/dist/author/markupUtils.d.ts +0 -11
  60. package/dist/author/markupUtils.js +0 -10
  61. package/dist/author/response.d.ts +0 -41
  62. package/dist/author/response.js +0 -238
  63. package/dist/author/utils.d.ts +0 -9
  64. package/dist/author/utils.js +0 -7
  65. package/dist/browser/author/index.js +0 -31954
  66. package/dist/browser/author/index.js.map +0 -1
  67. package/dist/browser/browser-DJA4OL8j.js +0 -331
  68. package/dist/browser/browser-DJA4OL8j.js.map +0 -1
  69. package/dist/browser/controller/index.js +0 -37731
  70. package/dist/browser/controller/index.js.map +0 -1
  71. package/dist/browser/delivery/index.js +0 -81
  72. package/dist/browser/delivery/index.js.map +0 -1
  73. package/dist/browser/dist-Csid0rBE.js +0 -17046
  74. package/dist/browser/dist-Csid0rBE.js.map +0 -1
  75. package/dist/browser/dist-D9ARZhQk.js +0 -1426
  76. package/dist/browser/dist-D9ARZhQk.js.map +0 -1
  77. package/dist/browser/main-I_wWeaur.js +0 -1274
  78. package/dist/browser/main-I_wWeaur.js.map +0 -1
  79. package/dist/browser/math-templated.css +0 -2
  80. package/dist/browser/print/index.js +0 -47
  81. package/dist/browser/print/index.js.map +0 -1
  82. package/dist/controller/defaults.d.ts +0 -29
  83. package/dist/controller/defaults.js +0 -23
  84. package/dist/controller/index.d.ts +0 -45
  85. package/dist/controller/index.js +0 -154
  86. package/dist/delivery/index.d.ts +0 -20
  87. package/dist/delivery/index.js +0 -51
  88. package/dist/delivery/main.d.ts +0 -40
  89. package/dist/delivery/main.js +0 -492
  90. package/dist/index.d.ts +0 -1
  91. package/dist/index.iife.d.ts +0 -8
  92. package/dist/index.iife.js +0 -205
  93. package/dist/index.js +0 -2
  94. package/dist/math-templated.css +0 -2
  95. package/dist/print/index.d.ts +0 -15
  96. package/dist/print/index.js +0 -47
  97. package/dist/runtime-support.d.ts +0 -12
  98. package/dist/runtime-support.js +0 -12
package/lib/main.js ADDED
@@ -0,0 +1,877 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = exports.Main = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _react = _interopRequireDefault(require("react"));
10
+ var _propTypes = _interopRequireDefault(require("prop-types"));
11
+ var _lodashEs = require("lodash-es");
12
+ var _styles = require("@mui/material/styles");
13
+ var _Tooltip = _interopRequireDefault(require("@mui/material/Tooltip"));
14
+ var _mathInput = require("@pie-lib/math-input");
15
+ var _renderUi = require("@pie-lib/render-ui");
16
+ var _mathRendering = require("@pie-lib/math-rendering");
17
+ var _maskMarkup = require("@pie-lib/mask-markup");
18
+ var _correctAnswerToggle = _interopRequireDefault(require("@pie-lib/correct-answer-toggle"));
19
+ var _reactDom = _interopRequireDefault(require("react-dom"));
20
+ const StyledUiLayout = (0, _styles.styled)(_renderUi.UiLayout)({
21
+ color: _renderUi.color.text(),
22
+ backgroundColor: _renderUi.color.background(),
23
+ display: 'inline-block'
24
+ });
25
+ const MainContainer = (0, _styles.styled)('div')({
26
+ width: '100%',
27
+ position: 'relative',
28
+ backgroundColor: _renderUi.color.background(),
29
+ color: _renderUi.color.text()
30
+ });
31
+ const PromptContainer = (0, _styles.styled)('div')(({
32
+ theme
33
+ }) => ({
34
+ marginBottom: theme.spacing(2)
35
+ }));
36
+ const CollapsibleContainer = (0, _styles.styled)('div')(({
37
+ theme
38
+ }) => ({
39
+ marginBottom: theme.spacing(2)
40
+ }));
41
+ const InputAndKeypadContainer = (0, _styles.styled)('div')({
42
+ position: 'relative',
43
+ '& > div > div': {
44
+ display: 'flex',
45
+ alignItems: 'baseline',
46
+ flexWrap: 'wrap'
47
+ },
48
+ '& .mq-overarrow-inner': {
49
+ border: 'none !important',
50
+ padding: '0 !important'
51
+ },
52
+ '& .mq-overarrow-inner-right': {
53
+ display: 'none !important'
54
+ },
55
+ '& .mq-overarrow-inner-left': {
56
+ display: 'none !important'
57
+ },
58
+ '& .mq-overarrow.mq-arrow-both': {
59
+ minWidth: '1.23em',
60
+ '& *': {
61
+ lineHeight: '1 !important'
62
+ },
63
+ '&:before': {
64
+ top: '-0.4em',
65
+ left: '-1px'
66
+ },
67
+ '&:after': {
68
+ top: '-2.4em',
69
+ right: '-1px'
70
+ },
71
+ '&.mq-empty:after': {
72
+ top: '-0.45em'
73
+ }
74
+ },
75
+ '& .mq-overarrow.mq-arrow-right': {
76
+ '&:before': {
77
+ top: '-0.4em',
78
+ right: '-1px'
79
+ }
80
+ },
81
+ '& .mq-longdiv-inner': {
82
+ borderTop: '1px solid !important',
83
+ paddingTop: '1.5px !important'
84
+ },
85
+ '& .mq-parallelogram': {
86
+ lineHeight: 0.85
87
+ }
88
+ });
89
+ const Expression = (0, _styles.styled)('div')(({
90
+ theme,
91
+ $incorrect,
92
+ $correct,
93
+ $showCorrectness,
94
+ $correctAnswerShown
95
+ }) => ({
96
+ maxWidth: 'fit-content',
97
+ ...($incorrect && {
98
+ borderColor: `${_renderUi.color.incorrect()} !important`
99
+ }),
100
+ ...($correct && {
101
+ borderColor: `${_renderUi.color.correct()} !important`
102
+ }),
103
+ ...($showCorrectness && {
104
+ border: '2px solid'
105
+ }),
106
+ ...($correctAnswerShown && {
107
+ padding: theme.spacing(1),
108
+ letterSpacing: '0.5px'
109
+ }),
110
+ '& > .mq-math-mode': {
111
+ '& > .mq-root-block': {
112
+ '& > .mq-editable-field': {
113
+ minWidth: '10px',
114
+ padding: theme.spacing(0.25)
115
+ }
116
+ },
117
+ '& sup': {
118
+ top: 0
119
+ }
120
+ }
121
+ }));
122
+ const StyledStatic = (0, _styles.styled)(_mathInput.mq.Static)({
123
+ '& > .mq-root-block': {
124
+ '& > .mq-editable-field': {
125
+ borderColor: _renderUi.color.text()
126
+ }
127
+ }
128
+ });
129
+
130
+ // Tooltip styling is handled via slotProps, no need for styled component
131
+
132
+ const ResponseContainer = (0, _styles.styled)('div')(({
133
+ theme
134
+ }) => ({
135
+ zIndex: 10,
136
+ minWidth: '400px',
137
+ marginTop: theme.spacing(2)
138
+ }));
139
+ const PrintContainer = (0, _styles.styled)('div')(({
140
+ theme
141
+ }) => ({
142
+ marginBottom: theme.spacing(1),
143
+ pointerEvents: 'none'
144
+ }));
145
+ const SrOnly = (0, _styles.styled)('h2')({
146
+ position: 'absolute',
147
+ left: '-10000px',
148
+ top: 'auto',
149
+ width: '1px',
150
+ height: '1px',
151
+ overflow: 'hidden'
152
+ });
153
+ const Note = (0, _styles.styled)('div')(({
154
+ theme
155
+ }) => ({
156
+ marginBottom: theme.spacing(2)
157
+ }));
158
+ const StyledToggle = (0, _styles.styled)(_correctAnswerToggle.default)(({
159
+ theme
160
+ }) => ({
161
+ color: _renderUi.color.text(),
162
+ marginBottom: theme.spacing(2)
163
+ }));
164
+
165
+ // CSS for classes used in HTML strings and classList operations
166
+ if (typeof document !== 'undefined') {
167
+ const styleId = 'math-templated-main-styles';
168
+ if (!document.getElementById(styleId)) {
169
+ const style = document.createElement('style');
170
+ style.id = styleId;
171
+ style.textContent = `
172
+ .block-container {
173
+ margin: 8px !important;
174
+ display: inline-flex;
175
+ border: 2px solid grey !important;
176
+ }
177
+ .block-response {
178
+ flex: 2;
179
+ color: grey;
180
+ background: rgba(0, 0, 0, 0.04);
181
+ font-size: 0.8rem !important;
182
+ padding: 4px !important;
183
+ display: flex;
184
+ align-items: center;
185
+ justify-content: center;
186
+ border-right: 2px solid #bdbdbd !important;
187
+ }
188
+ .block-math {
189
+ color: ${_renderUi.color.text()};
190
+ background-color: ${_renderUi.color.background()};
191
+ padding: 4px !important;
192
+ display: flex;
193
+ align-items: center;
194
+ justify-content: center;
195
+ flex: 8;
196
+ }
197
+ .block-math > .mq-math-mode > .mq-hasCursor > .mq-cursor {
198
+ display: none;
199
+ }
200
+ .correct {
201
+ border-color: ${_renderUi.color.correct()} !important;
202
+ }
203
+ .incorrect {
204
+ border-color: ${_renderUi.color.incorrect()} !important;
205
+ }
206
+ `;
207
+ document.head.appendChild(style);
208
+ }
209
+ }
210
+ let registered = false;
211
+
212
+ // Define a regex pattern to match {{number}}
213
+ const REGEX = /(\{\{\d+\}\})/gm;
214
+ const DEFAULT_KEYPAD_VARIANT = 6;
215
+ const KEYPAD_VIEWPORT_PADDING = 8;
216
+
217
+ // !!! If you're using Chrome but have selected the "iPad" device in Chrome Developer Tools, the navigator.userAgent string may still report as
218
+ // Safari because Chrome on iOS actually uses the Safari rendering engine under the hood due to Apple's restrictions on third-party browser engines.
219
+ // When you select the "iPad" device in Chrome Developer Tools, you're essentially emulating the behavior of Safari on an iPad, including the user agent string.
220
+ // Therefore, even though you're using Chrome, the user agent string will resemble that of Safari on an iPad.
221
+ // Since the regular expression /^((?!chrome|android).)*safari/i checks for the presence of "safari" in the user agent string while excluding "chrome" and "android",
222
+ // it will return true in this case because "safari" is present in the user agent string emulated by Chrome when in iPad mode.
223
+ const IS_SAFARI = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
224
+ function generateAdditionalKeys(keyData = []) {
225
+ return keyData.map(key => ({
226
+ name: key,
227
+ latex: key,
228
+ write: key,
229
+ label: key
230
+ }));
231
+ }
232
+ function getKeyPadWidth(additionalKeys = [], equationEditor) {
233
+ return Math.floor(additionalKeys.length / 5) * 30 + (equationEditor === 'miscellaneous' ? 600 : 500);
234
+ }
235
+ function splitByParts(text) {
236
+ // Use the regex pattern to split the text
237
+ const parts = text.split(REGEX);
238
+ // Filter out empty strings that might result from splitting
239
+ return parts.filter(part => part);
240
+ }
241
+ function prepareForStatic(model, state) {
242
+ const {
243
+ responses,
244
+ disabled,
245
+ markup,
246
+ printMode,
247
+ alwaysShowCorrect
248
+ } = model || {};
249
+ if (markup) {
250
+ if (state.showCorrect) {
251
+ return Object.keys(responses || {}).reduce((acc, responseKey) => {
252
+ acc[responseKey] = responses?.[responseKey]?.answer;
253
+ return acc;
254
+ }, {});
255
+ }
256
+ const splitted = splitByParts(markup);
257
+ return splitted.reduce((acc, split) => {
258
+ if (split.match(REGEX)) {
259
+ const responseKey = Main.getResponseKey(split);
260
+ const answer = state.session.answers[`r${responseKey}`];
261
+ if (printMode && !alwaysShowCorrect) {
262
+ const blankSpace = '\\ \\ '.repeat(30) + '\\embed{newLine}[] ';
263
+ return {
264
+ ...acc,
265
+ [responseKey]: `\\MathQuillMathField[r${responseKey}]{${blankSpace.repeat(3)}}`
266
+ };
267
+ }
268
+ if (disabled) {
269
+ return {
270
+ ...acc,
271
+ [responseKey]: `\\embed{answerBlock}[r${responseKey}]`
272
+ };
273
+ }
274
+ return {
275
+ ...acc,
276
+ [responseKey]: `\\MathQuillMathField[r${responseKey}]{${answer && answer.value || ''}}`
277
+ };
278
+ }
279
+ return acc;
280
+ }, {});
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Popper.js v2 modifier that implements precise horizontal placement:
286
+ *
287
+ * 1. Default: left-align the keypad with the left edge of the response area.
288
+ *
289
+ * 2. Overflow: if left-aligning would push the right edge of the keypad past
290
+ * the viewport's right edge, right-align the keypad so that its right edge
291
+ * sits exactly at the left edge of the response area.
292
+ *
293
+ * In both cases we compute offsets.x from first principles (rather than
294
+ * adjusting whatever Popper already set) to avoid any upstream skew.
295
+ *
296
+ * Coordinate note:
297
+ * state.rects.reference.x — reference's left edge in offset-parent coords.
298
+ * getBoundingClientRect() — viewport-relative coords.
299
+ * Both describe the same physical point, so the difference between them is
300
+ * the constant offset needed to convert viewport ↔ offset-parent coords.
301
+ */
302
+ const smartHorizontalPlacementModifier = {
303
+ name: 'smartHorizontalPlacement',
304
+ enabled: true,
305
+ phase: 'main',
306
+ requires: ['popperOffsets'],
307
+ fn: ({
308
+ state
309
+ }) => {
310
+ const offsets = state.modifiersData.popperOffsets;
311
+ if (!offsets) return;
312
+ const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
313
+ const popperWidth = state.rects.popper.width;
314
+ const referenceViewportLeft = state.elements.reference.getBoundingClientRect().left;
315
+ const offsetParentEl = state.elements.popper?.offsetParent;
316
+ const offsetParentRect = offsetParentEl?.getBoundingClientRect?.() || {
317
+ left: 0
318
+ };
319
+ const offsetParentScrollLeft = offsetParentEl?.scrollLeft || 0;
320
+ const referenceOffsetParentX = referenceViewportLeft - offsetParentRect.left + offsetParentScrollLeft;
321
+ const viewportLeftInOffsetParent = referenceOffsetParentX - referenceViewportLeft;
322
+ const minX = viewportLeftInOffsetParent + KEYPAD_VIEWPORT_PADDING;
323
+ const maxX = viewportLeftInOffsetParent + viewportWidth - popperWidth - KEYPAD_VIEWPORT_PADDING;
324
+ if (referenceViewportLeft + popperWidth <= viewportWidth) {
325
+ offsets.x = referenceOffsetParentX;
326
+ } else {
327
+ offsets.x = referenceOffsetParentX - popperWidth;
328
+ }
329
+ if (maxX < minX) {
330
+ offsets.x = minX;
331
+ } else {
332
+ offsets.x = Math.min(Math.max(offsets.x, minX), maxX);
333
+ }
334
+ }
335
+ };
336
+ class Main extends _react.default.Component {
337
+ constructor(props) {
338
+ super(props);
339
+ (0, _defineProperty2.default)(this, "handleAnswerBlockDomUpdate", () => {
340
+ const {
341
+ model
342
+ } = this.props;
343
+ const {
344
+ session,
345
+ showCorrect
346
+ } = this.state;
347
+ const answers = session.answers;
348
+ if (this.root && model.disabled && !showCorrect) {
349
+ Object.keys(answers).forEach(answerId => {
350
+ const el = this.root.querySelector(`#${answerId}`);
351
+ const indexEl = this.root.querySelector(`#${answerId}Index`);
352
+ if (el) {
353
+ const answer = answers[answerId];
354
+ (0, _mathInput.applyStaticMath)(el, answer && answer.value || '');
355
+ if (model.view) {
356
+ el.parentElement.parentElement.classList.remove('correct');
357
+ el.parentElement.parentElement.classList.remove('incorrect');
358
+ }
359
+ indexEl.textContent = 'R';
360
+ }
361
+ });
362
+ }
363
+ (0, _mathRendering.renderMath)(this.root);
364
+ });
365
+ (0, _defineProperty2.default)(this, "onSubFieldFocus", id => {
366
+ const editableFields = Array.from(this.root?.querySelectorAll('.mq-editable-field') || []);
367
+ const savedScroll = editableFields.map(el => ({
368
+ el,
369
+ left: el.scrollLeft,
370
+ top: el.scrollTop
371
+ }));
372
+ const restoreEditableScroll = () => {
373
+ savedScroll.forEach(({
374
+ el,
375
+ left,
376
+ top
377
+ }) => {
378
+ el.scrollLeft = left;
379
+ el.scrollTop = top;
380
+ });
381
+ };
382
+ this.setState({
383
+ activeAnswerBlock: id
384
+ }, () => {
385
+ restoreEditableScroll();
386
+ requestAnimationFrame(restoreEditableScroll);
387
+ setTimeout(restoreEditableScroll, 0);
388
+ });
389
+ });
390
+ (0, _defineProperty2.default)(this, "toNodeData", data => {
391
+ if (!data) {
392
+ return;
393
+ }
394
+ const {
395
+ type,
396
+ value
397
+ } = data;
398
+ if (type === 'command' || type === 'cursor') return data;
399
+ if (type === 'answer') return {
400
+ type: 'answer',
401
+ ...data
402
+ };
403
+ if (value === 'clear') return {
404
+ type: 'clear'
405
+ };
406
+ return {
407
+ type: 'write',
408
+ value
409
+ };
410
+ });
411
+ (0, _defineProperty2.default)(this, "setInput", input => {
412
+ this.input = input;
413
+ });
414
+ (0, _defineProperty2.default)(this, "onClick", data => {
415
+ const c = this.toNodeData(data);
416
+ if (c.type === 'clear') {
417
+ this.input.clear();
418
+ } else if (c.type === 'command') {
419
+ if (Array.isArray(c.value)) {
420
+ c.value.forEach(vv => {
421
+ this.input.cmd(vv);
422
+ });
423
+ } else {
424
+ this.input.cmd(c.value);
425
+ }
426
+ } else if (c.type === 'cursor') {
427
+ this.input.keystroke(c.value);
428
+ } else {
429
+ this.input.write(c.value);
430
+ }
431
+
432
+ // Keep all relevant scroll containers stable when refocusing after keypad
433
+ // click. Browser "focus into view" can scroll horizontally back to start.
434
+ const fieldElement = this.input?.el?.() || this.root;
435
+ const scrollTargets = [];
436
+ let node = fieldElement;
437
+ while (node && node !== document.body && node !== document.documentElement) {
438
+ const isScrollable = node.scrollWidth > node.clientWidth || node.scrollHeight > node.clientHeight;
439
+ if (isScrollable) {
440
+ scrollTargets.push(node);
441
+ }
442
+ node = node.parentElement;
443
+ }
444
+ if (document.scrollingElement) {
445
+ scrollTargets.push(document.scrollingElement);
446
+ }
447
+ const savedScroll = scrollTargets.map(el => ({
448
+ el,
449
+ left: el.scrollLeft,
450
+ top: el.scrollTop
451
+ }));
452
+ const windowScroll = {
453
+ x: window.scrollX,
454
+ y: window.scrollY
455
+ };
456
+ const restoreScroll = () => {
457
+ savedScroll.forEach(({
458
+ el,
459
+ left,
460
+ top
461
+ }) => {
462
+ el.scrollLeft = left;
463
+ el.scrollTop = top;
464
+ });
465
+ window.scrollTo(windowScroll.x, windowScroll.y);
466
+ };
467
+ this.input.focus();
468
+ restoreScroll();
469
+ requestAnimationFrame(restoreScroll);
470
+ setTimeout(restoreScroll, 0);
471
+ });
472
+ (0, _defineProperty2.default)(this, "callOnSessionChange", () => {
473
+ const {
474
+ onSessionChange
475
+ } = this.props;
476
+ if (onSessionChange) {
477
+ onSessionChange(this.state.session);
478
+ }
479
+ });
480
+ (0, _defineProperty2.default)(this, "toggleShowCorrect", show => {
481
+ this.setState({
482
+ showCorrect: show
483
+ }, this.handleAnswerBlockDomUpdate);
484
+ });
485
+ (0, _defineProperty2.default)(this, "subFieldChanged", (name, subfieldValue) => {
486
+ (0, _mathInput.updateSpans)();
487
+ if (name) {
488
+ this.setState(state => ({
489
+ session: {
490
+ ...state.session,
491
+ answers: {
492
+ ...state.session.answers,
493
+ [name]: {
494
+ value: subfieldValue
495
+ }
496
+ }
497
+ }
498
+ }), this.callOnSessionChange);
499
+ }
500
+ });
501
+ (0, _defineProperty2.default)(this, "getFieldName", (changeField, fields) => {
502
+ const {
503
+ answers
504
+ } = this.state.session;
505
+ if (Object.keys(answers || {}).length) {
506
+ const keys = Object.keys(answers);
507
+ return keys.find(k => {
508
+ const tf = fields[k];
509
+ return tf && tf.id == changeField.id;
510
+ });
511
+ }
512
+ });
513
+ (0, _defineProperty2.default)(this, "onBlur", e => {
514
+ const {
515
+ relatedTarget,
516
+ currentTarget
517
+ } = e || {};
518
+ function getParentWithRoleTooltip(element, depth = 0) {
519
+ // only run this max 16 times
520
+ if (!element || depth >= 16) return null;
521
+ const parent = element.offsetParent;
522
+ if (!parent) return null;
523
+ if (parent.getAttribute('role') === 'tooltip') {
524
+ return parent;
525
+ }
526
+ return getParentWithRoleTooltip(parent, depth + 1);
527
+ }
528
+ function getDeepChildDataKeypad(element, depth = 0) {
529
+ // only run this max 4 times
530
+ if (!element || depth >= 4) return null;
531
+ const child = element?.children?.[0];
532
+ if (!child) return null;
533
+ if (child.attributes && child.attributes['data-keypad']) {
534
+ return child;
535
+ }
536
+ return getDeepChildDataKeypad(child, depth + 1);
537
+ }
538
+ const parentWithTooltipRole = getParentWithRoleTooltip(relatedTarget);
539
+ const childWithDataKeypad = parentWithTooltipRole ? getDeepChildDataKeypad(parentWithTooltipRole) : null;
540
+ if (!relatedTarget || !currentTarget || !childWithDataKeypad?.attributes['data-keypad']) {
541
+ this.setState({
542
+ activeAnswerBlock: ''
543
+ });
544
+ }
545
+ });
546
+ (0, _defineProperty2.default)(this, "renderTeacherInstructions", () => {
547
+ const {
548
+ model
549
+ } = this.props;
550
+ const {
551
+ teacherInstructions,
552
+ animationsDisabled
553
+ } = model || {};
554
+ const showTeacherInstructions = teacherInstructions && ((0, _renderUi.hasText)(teacherInstructions) || (0, _renderUi.hasMedia)(teacherInstructions));
555
+ const teacherInstructionsDiv = /*#__PURE__*/_react.default.createElement(_renderUi.PreviewPrompt, {
556
+ defaultClassName: "teacher-instructions",
557
+ prompt: teacherInstructions
558
+ });
559
+ return showTeacherInstructions && /*#__PURE__*/_react.default.createElement(CollapsibleContainer, null, !animationsDisabled ? /*#__PURE__*/_react.default.createElement(_renderUi.Collapsible, {
560
+ labels: {
561
+ hidden: 'Show Teacher Instructions',
562
+ visible: 'Hide Teacher Instructions'
563
+ }
564
+ }, teacherInstructionsDiv) : teacherInstructionsDiv);
565
+ });
566
+ (0, _defineProperty2.default)(this, "renderRationale", () => {
567
+ const {
568
+ model
569
+ } = this.props;
570
+ const {
571
+ rationale,
572
+ animationsDisabled
573
+ } = model || {};
574
+ const rationaleDiv = /*#__PURE__*/_react.default.createElement(_renderUi.PreviewPrompt, {
575
+ prompt: rationale
576
+ });
577
+ const showRationale = rationale && ((0, _renderUi.hasText)(rationale) || (0, _renderUi.hasMedia)(rationale));
578
+ return showRationale && /*#__PURE__*/_react.default.createElement(CollapsibleContainer, null, !animationsDisabled ? /*#__PURE__*/_react.default.createElement(_renderUi.Collapsible, {
579
+ labels: {
580
+ hidden: 'Show Rationale',
581
+ visible: 'Hide Rationale'
582
+ }
583
+ }, rationaleDiv) : rationaleDiv);
584
+ });
585
+ (0, _defineProperty2.default)(this, "renderPlayerContent", () => {
586
+ const {
587
+ model
588
+ } = this.props;
589
+ const {
590
+ activeAnswerBlock,
591
+ showCorrect,
592
+ session,
593
+ tooltipContainerRef
594
+ } = this.state;
595
+ const {
596
+ correctness,
597
+ disabled,
598
+ view,
599
+ responses,
600
+ equationEditor,
601
+ customKeys,
602
+ feedback,
603
+ env: {
604
+ mode
605
+ } = {},
606
+ printMode,
607
+ alwaysShowCorrect
608
+ } = model || {};
609
+ const emptyResponse = (0, _lodashEs.isEmpty)(responses);
610
+ const additionalKeys = generateAdditionalKeys(customKeys);
611
+ const statics = prepareForStatic(model, this.state) || '';
612
+ const studentPrintMode = printMode && !alwaysShowCorrect;
613
+ return /*#__PURE__*/_react.default.createElement(InputAndKeypadContainer, null, /*#__PURE__*/_react.default.createElement(_maskMarkup.Customizable, {
614
+ disabled: disabled,
615
+ markup: model.markup
616
+ // TODO remove the need of value?
617
+ ,
618
+ value: {},
619
+ customMarkMarkupComponent: id => {
620
+ const responseIsCorrect = mode === 'evaluate' && feedback && feedback[id];
621
+ const MQStatic = /*#__PURE__*/_react.default.createElement(StyledStatic, {
622
+ ref: mqStatic => {
623
+ this.mqStatic = mqStatic || this.mqStatic;
624
+ },
625
+ latex: statics[id],
626
+ onSubFieldChange: this.subFieldChanged,
627
+ getFieldName: this.getFieldName,
628
+ setInput: this.setInput,
629
+ onSubFieldFocus: this.onSubFieldFocus,
630
+ onBlur: this.onBlur
631
+ });
632
+ return /*#__PURE__*/_react.default.createElement(Expression, {
633
+ ref: tooltipContainerRef,
634
+ $incorrect: !emptyResponse && !responseIsCorrect && !showCorrect,
635
+ $correct: !emptyResponse && (responseIsCorrect || showCorrect),
636
+ $showCorrectness: !emptyResponse && disabled && correctness && !view,
637
+ $correctAnswerShown: showCorrect
638
+ }, /*#__PURE__*/_react.default.createElement(_Tooltip.default, {
639
+ ref: ref => this.setTooltipRef(ref),
640
+ enterTouchDelay: 0,
641
+ interactive: true,
642
+ open: activeAnswerBlock === `r${id}`,
643
+ slotProps: {
644
+ popper: {
645
+ container: tooltipContainerRef?.current || undefined,
646
+ // 'bottom-start' left-aligns the keypad with the left edge of the
647
+ // response area by default. The smartHorizontalPlacement modifier
648
+ // below overrides this when the keypad would overflow the viewport.
649
+ placement: 'bottom-start',
650
+ sx: {
651
+ backgroundColor: 'transparent',
652
+ width: 'auto',
653
+ opacity: 1,
654
+ '& .MuiTooltip-arrow': {
655
+ display: 'none'
656
+ }
657
+ },
658
+ modifiers: [{
659
+ name: 'preventOverflow',
660
+ enabled: false
661
+ }, {
662
+ name: 'flip',
663
+ enabled: false
664
+ }, smartHorizontalPlacementModifier]
665
+ },
666
+ tooltip: {
667
+ sx: {
668
+ fontSize: 'initial',
669
+ backgroundColor: 'transparent',
670
+ width: 'auto',
671
+ maxWidth: 'none',
672
+ marginTop: 0,
673
+ paddingTop: 0,
674
+ boxShadow: 'none'
675
+ }
676
+ }
677
+ },
678
+ title: Object.keys(session.answers).map(answerId => answerId === activeAnswerBlock && !(showCorrect || disabled) && /*#__PURE__*/_react.default.createElement(ResponseContainer, {
679
+ "data-keypad": true,
680
+ key: answerId,
681
+ style: {
682
+ width: getKeyPadWidth(additionalKeys, equationEditor)
683
+ }
684
+ }, /*#__PURE__*/_react.default.createElement(_mathInput.HorizontalKeypad, {
685
+ additionalKeys: additionalKeys,
686
+ mode: equationEditor || DEFAULT_KEYPAD_VARIANT,
687
+ onClick: this.onClick
688
+ })) || null)
689
+ }, studentPrintMode ? /*#__PURE__*/_react.default.createElement(PrintContainer, null, MQStatic) : /*#__PURE__*/_react.default.createElement("div", null, MQStatic)));
690
+ }
691
+ }));
692
+ });
693
+ const _answers = {};
694
+ const {
695
+ model: _model,
696
+ session: _session
697
+ } = props || {};
698
+ const {
699
+ markup,
700
+ alwaysShowCorrect: _alwaysShowCorrect
701
+ } = _model || {};
702
+ const {
703
+ answers: sessionAnswers
704
+ } = _session || {};
705
+ if (markup) {
706
+ (markup || '').replace(REGEX, response => {
707
+ const responseKey = Main.getResponseKey(response);
708
+ const sessionAnswerForResponse = sessionAnswers && sessionAnswers[`r${responseKey}`];
709
+ _answers[`r${responseKey}`] = {
710
+ value: sessionAnswerForResponse?.value || ''
711
+ };
712
+ });
713
+ }
714
+ this.state = {
715
+ session: {
716
+ ...props.session,
717
+ answers: _answers
718
+ },
719
+ activeAnswerBlock: '',
720
+ showCorrect: _alwaysShowCorrect || false,
721
+ tooltipContainerRef: /*#__PURE__*/_react.default.createRef()
722
+ };
723
+ }
724
+ UNSAFE_componentWillMount() {
725
+ if (typeof window !== 'undefined') {
726
+ if (!registered) {
727
+ (0, _mathInput.registerEmbed)('answerBlock', data => ({
728
+ htmlString: `<div class="block-container">
729
+ <div class="block-response" id="${data}Index">R</div>
730
+ <div class="block-math">
731
+ <span id="${data}"></span>
732
+ </div>
733
+ </div>`,
734
+ text: () => 'text',
735
+ latex: () => `\\embed{answerBlock}[${data}]`
736
+ }));
737
+ registered = true;
738
+ }
739
+ }
740
+ }
741
+ UNSAFE_componentWillReceiveProps(nextProps) {
742
+ const {
743
+ model
744
+ } = this.props;
745
+ const {
746
+ model: nextModel = {}
747
+ } = nextProps || {};
748
+ const {
749
+ markup = '',
750
+ env = {}
751
+ } = model || {};
752
+ const {
753
+ markup: nextMarkup = '',
754
+ env: nextEnv = {},
755
+ alwaysShowCorrect: nextAlwaysShowCorrect = false
756
+ } = nextModel;
757
+ const isEvaluateMode = env => env && env.mode === 'evaluate';
758
+ if (!isEvaluateMode(env) || !isEvaluateMode(nextEnv)) {
759
+ this.setState(prevState => ({
760
+ ...prevState.session,
761
+ showCorrect: false
762
+ }));
763
+ }
764
+ if (nextAlwaysShowCorrect) {
765
+ this.setState({
766
+ showCorrect: true
767
+ });
768
+ }
769
+ const matches = markup.match(REGEX);
770
+ const nextMatches = nextMarkup.match(REGEX);
771
+
772
+ // If markup changed, and we no longer have the same response area, the session needs to be updated
773
+ if (!(0, _lodashEs.isEqual)(matches, nextMatches)) {
774
+ const newAnswers = {};
775
+ const answers = this.state.session.answers;
776
+ (nextMatches || []).forEach(nextMatch => {
777
+ const responseKey = Main.getResponseKey(nextMatch);
778
+ const sessionAnswerForResponse = answers && answers[`r${responseKey}`];
779
+
780
+ // build out local state model using responses declared in markup
781
+ newAnswers[`r${responseKey}`] = {
782
+ value: sessionAnswerForResponse?.value || ''
783
+ };
784
+ });
785
+ this.setState(state => ({
786
+ session: {
787
+ ...state.session,
788
+ answers: newAnswers
789
+ }
790
+ }), () => {
791
+ this.props.onSessionChange(this.state.session);
792
+ this.handleAnswerBlockDomUpdate();
793
+ });
794
+ }
795
+ }
796
+ shouldComponentUpdate(nextProps, nextState) {
797
+ const sameModel = (0, _lodashEs.isEqual)(this.props.model, nextProps.model);
798
+ const sameState = (0, _lodashEs.isEqual)(this.state, nextState);
799
+ return !sameModel || !sameState;
800
+ }
801
+ componentDidMount() {
802
+ this.handleAnswerBlockDomUpdate();
803
+ setTimeout(() => (0, _mathRendering.renderMath)(this.root), 100);
804
+ }
805
+ componentDidUpdate() {
806
+ this.handleAnswerBlockDomUpdate();
807
+ }
808
+ // function for tooltip
809
+ setTooltipRef(ref) {
810
+ // Safari Hack: https://stackoverflow.com/a/42764495/5757635
811
+ setTimeout(() => {
812
+ if (ref && IS_SAFARI) {
813
+ const div = document.querySelector("[role='tooltip']");
814
+ if (div) {
815
+ const el = div.firstChild;
816
+ el.setAttribute('tabindex', '-1');
817
+ }
818
+ }
819
+ }, 1);
820
+ }
821
+ render() {
822
+ const {
823
+ model
824
+ } = this.props;
825
+ const {
826
+ showCorrect
827
+ } = this.state;
828
+ const {
829
+ prompt,
830
+ env: {
831
+ mode,
832
+ role
833
+ } = {},
834
+ extraCSSRules,
835
+ correctness,
836
+ responses,
837
+ language,
838
+ showNote,
839
+ note
840
+ } = model || {};
841
+ const emptyResponse = (0, _lodashEs.isEmpty)(responses);
842
+ const showCorrectAnswerToggle = !emptyResponse && correctness && correctness.correctness !== 'correct';
843
+ const displayNote = (showCorrect || mode === 'view' && role === 'instructor') && showNote && note;
844
+ return /*#__PURE__*/_react.default.createElement(StyledUiLayout, {
845
+ extraCSSRules: extraCSSRules,
846
+ ref: r => {
847
+ // eslint-disable-next-line react/no-find-dom-node
848
+ const domNode = _reactDom.default.findDOMNode(r);
849
+ this.root = domNode || this.root;
850
+ }
851
+ }, /*#__PURE__*/_react.default.createElement(MainContainer, null, mode === 'gather' && /*#__PURE__*/_react.default.createElement(SrOnly, null, "Math Equation Response Question"), /*#__PURE__*/_react.default.createElement(MainContainer, null, showCorrectAnswerToggle && /*#__PURE__*/_react.default.createElement(StyledToggle, {
852
+ language: language,
853
+ show: true,
854
+ toggled: showCorrect,
855
+ onToggle: this.toggleShowCorrect
856
+ })), this.renderTeacherInstructions(), prompt && /*#__PURE__*/_react.default.createElement(PromptContainer, null, /*#__PURE__*/_react.default.createElement(_renderUi.PreviewPrompt, {
857
+ prompt: prompt
858
+ })), /*#__PURE__*/_react.default.createElement(_renderUi.Readable, {
859
+ false: true
860
+ }, this.renderPlayerContent()), displayNote && /*#__PURE__*/_react.default.createElement(Note, {
861
+ className: "note",
862
+ dangerouslySetInnerHTML: {
863
+ __html: note
864
+ }
865
+ }), this.renderRationale()));
866
+ }
867
+ }
868
+ exports.Main = Main;
869
+ // removes {{ and }} and returns only key response. Eg: {{0}} => 0
870
+ (0, _defineProperty2.default)(Main, "getResponseKey", response => (response || '').replaceAll('{{', '').replaceAll('}}', ''));
871
+ (0, _defineProperty2.default)(Main, "propTypes", {
872
+ session: _propTypes.default.object.isRequired,
873
+ onSessionChange: _propTypes.default.func,
874
+ model: _propTypes.default.object.isRequired
875
+ });
876
+ var _default = exports.default = Main;
877
+ //# sourceMappingURL=main.js.map