@pie-lib/mask-markup 2.0.0-beta.1 → 2.0.0-next.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.
Files changed (67) hide show
  1. package/CHANGELOG.json +1 -871
  2. package/CHANGELOG.md +434 -32
  3. package/LICENSE.md +5 -0
  4. package/NEXT.CHANGELOG.json +1 -0
  5. package/lib/choices/choice.js +101 -129
  6. package/lib/choices/choice.js.map +1 -1
  7. package/lib/choices/index.js +28 -48
  8. package/lib/choices/index.js.map +1 -1
  9. package/lib/componentize.js +2 -6
  10. package/lib/componentize.js.map +1 -1
  11. package/lib/components/blank.js +315 -246
  12. package/lib/components/blank.js.map +1 -1
  13. package/lib/components/correct-input.js +47 -66
  14. package/lib/components/correct-input.js.map +1 -1
  15. package/lib/components/dropdown.js +399 -156
  16. package/lib/components/dropdown.js.map +1 -1
  17. package/lib/components/input.js +15 -19
  18. package/lib/components/input.js.map +1 -1
  19. package/lib/constructed-response.js +81 -28
  20. package/lib/constructed-response.js.map +1 -1
  21. package/lib/customizable.js +44 -0
  22. package/lib/customizable.js.map +1 -0
  23. package/lib/drag-in-the-blank.js +160 -96
  24. package/lib/drag-in-the-blank.js.map +1 -1
  25. package/lib/index.js +8 -7
  26. package/lib/index.js.map +1 -1
  27. package/lib/inline-dropdown.js +10 -14
  28. package/lib/inline-dropdown.js.map +1 -1
  29. package/lib/mask.js +93 -101
  30. package/lib/mask.js.map +1 -1
  31. package/lib/serialization.js +36 -81
  32. package/lib/serialization.js.map +1 -1
  33. package/lib/with-mask.js +53 -49
  34. package/lib/with-mask.js.map +1 -1
  35. package/package.json +26 -15
  36. package/src/__tests__/drag-in-the-blank.test.js +111 -0
  37. package/src/__tests__/index.test.js +39 -0
  38. package/src/__tests__/mask.test.js +187 -0
  39. package/src/__tests__/serialization.test.js +54 -0
  40. package/src/__tests__/utils.js +1 -0
  41. package/src/__tests__/with-mask.test.js +76 -0
  42. package/src/choices/__tests__/index.test.js +75 -0
  43. package/src/choices/choice.jsx +84 -83
  44. package/src/choices/index.jsx +25 -15
  45. package/src/components/__tests__/blank.test.js +138 -0
  46. package/src/components/__tests__/correct-input.test.js +90 -0
  47. package/src/components/__tests__/dropdown.test.js +93 -0
  48. package/src/components/__tests__/input.test.js +102 -0
  49. package/src/components/blank.jsx +319 -195
  50. package/src/components/correct-input.jsx +45 -46
  51. package/src/components/dropdown.jsx +374 -139
  52. package/src/components/input.jsx +6 -3
  53. package/src/constructed-response.jsx +81 -18
  54. package/src/customizable.jsx +35 -0
  55. package/src/drag-in-the-blank.jsx +159 -47
  56. package/src/index.js +3 -1
  57. package/src/inline-dropdown.jsx +6 -3
  58. package/src/mask.jsx +75 -30
  59. package/src/serialization.js +37 -44
  60. package/src/with-mask.jsx +36 -3
  61. package/README.md +0 -14
  62. package/lib/new-serialization.js +0 -320
  63. package/lib/parse-html.js +0 -16
  64. package/lib/test-serializer.js +0 -215
  65. package/src/new-serialization.jsx +0 -291
  66. package/src/parse-html.js +0 -8
  67. package/src/test-serializer.js +0 -163
@@ -1,10 +1,152 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import Select from '@material-ui/core/Select';
4
- import MenuItem from '@material-ui/core/MenuItem';
5
- import CorrectInput from './correct-input';
6
- import { withStyles } from '@material-ui/core/styles';
3
+ import Button from '@mui/material/Button';
4
+ import InputLabel from '@mui/material/InputLabel';
5
+ import Menu from '@mui/material/Menu';
6
+ import MenuItem from '@mui/material/MenuItem';
7
+ import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
8
+ import ArrowDropUpIcon from '@mui/icons-material/ArrowDropUp';
9
+ import Close from '@mui/icons-material/Close';
10
+ import Check from '@mui/icons-material/Check';
11
+ import { styled } from '@mui/material/styles';
12
+
7
13
  import { color } from '@pie-lib/render-ui';
14
+ import { renderMath } from '@pie-lib/math-rendering';
15
+
16
+ const StyledButton = styled(Button)(() => ({
17
+ color: color.text(),
18
+ border: `1px solid ${color.borderGray()}`,
19
+ borderRadius: '4px',
20
+ justifyContent: 'space-between',
21
+ backgroundColor: color.background(),
22
+ position: 'relative',
23
+ height: '45px',
24
+ width: 'fit-content',
25
+ margin: '2px',
26
+ textTransform: 'none',
27
+ '& span': {
28
+ paddingRight: '5px',
29
+ },
30
+ '& svg': {
31
+ position: 'absolute',
32
+ right: 0,
33
+ top: 'calc(50% - 12px)',
34
+ pointerEvents: 'none',
35
+ color: color.text(),
36
+ marginLeft: '5px',
37
+ },
38
+ '&.Mui-focused': {
39
+ outline: `3px solid ${color.tertiary()}`,
40
+ outlineOffset: '2px',
41
+ borderWidth: '3px',
42
+ },
43
+ '&.disabledCorrect': {
44
+ borderWidth: '2px',
45
+ borderColor: color.correct(),
46
+ color: `${color.text()} !important`,
47
+ },
48
+ '&.disabledIncorrect': {
49
+ borderWidth: '2px',
50
+ borderColor: color.incorrectWithIcon(),
51
+ color: `${color.text()} !important`,
52
+ },
53
+ }));
54
+
55
+ const StyledMenu = styled(Menu)(() => ({
56
+ backgroundColor: color.background(),
57
+ border: `1px solid ${color.correct()} !important`,
58
+ '&:hover': {
59
+ border: `1px solid ${color.text()} `,
60
+ borderColor: 'initial',
61
+ },
62
+ '&:focus': {
63
+ border: `1px solid ${color.text()}`,
64
+ borderColor: 'initial',
65
+ },
66
+ // remove default padding on the inner list
67
+ '& .MuiList-root': {
68
+ padding: 0,
69
+ },
70
+ }));
71
+
72
+ const StyledMenuItem = styled(MenuItem)(() => ({
73
+ color: color.text(),
74
+ backgroundColor: color.background(),
75
+ '&.Mui-focused': {
76
+ outline: `3px solid ${color.tertiary()}`,
77
+ outlineOffset: '-1px', // keeps it inside the item
78
+ color: color.text(),
79
+ backgroundColor: color.background(),
80
+ },
81
+ '&:hover': {
82
+ color: color.text(),
83
+ backgroundColor: color.dropdownBackground(),
84
+ },
85
+ boxSizing: 'border-box',
86
+ padding: '25px',
87
+ borderRadius: '4px',
88
+ '&.selected': {
89
+ color: `${color.text()} !important`,
90
+ backgroundColor: `${color.background()} !important`,
91
+ '&:hover': {
92
+ color: color.text(),
93
+ backgroundColor: `${color.dropdownBackground()} !important`,
94
+ },
95
+ },
96
+ }));
97
+
98
+ const StyledLabel = styled('span')(() => ({
99
+ fontSize: 'max(1rem, 14px)',
100
+ }));
101
+
102
+ const StyledSelectedIndicator = styled('span')(() => ({
103
+ fontSize: 'max(1rem, 14px)',
104
+ position: 'absolute',
105
+ right: '10px',
106
+ }));
107
+
108
+ const StyledInputLabel = styled(InputLabel)(() => ({
109
+ position: 'absolute',
110
+ left: '-10000px',
111
+ top: 'auto',
112
+ width: '1px',
113
+ height: '1px',
114
+ overflow: 'hidden',
115
+ }));
116
+
117
+ const StyledCorrectnessIcon = styled(Check)(() => ({
118
+ color: `${color.white()} !important`,
119
+ position: 'absolute',
120
+ top: '-8px !important',
121
+ left: '-8px',
122
+ marginLeft: '0 !important',
123
+ borderRadius: '50%',
124
+ fontSize: '16px',
125
+ padding: '2px',
126
+ '&.correct': {
127
+ backgroundColor: color.correct(),
128
+ },
129
+ '&.incorrect': {
130
+ backgroundColor: color.incorrectWithIcon(),
131
+ },
132
+ }));
133
+
134
+ const StyledIncorrectnessIcon = styled(Close)(() => ({
135
+ color: `${color.white()} !important`,
136
+ position: 'absolute',
137
+ top: '-8px !important',
138
+ left: '-8px',
139
+ marginLeft: '0 !important',
140
+ borderRadius: '50%',
141
+ fontSize: '16px',
142
+ padding: '2px',
143
+ '&.correct': {
144
+ backgroundColor: color.correct(),
145
+ },
146
+ '&.incorrect': {
147
+ backgroundColor: color.incorrectWithIcon(),
148
+ },
149
+ }));
8
150
 
9
151
  class Dropdown extends React.Component {
10
152
  static propTypes = {
@@ -12,160 +154,253 @@ class Dropdown extends React.Component {
12
154
  value: PropTypes.string,
13
155
  disabled: PropTypes.bool,
14
156
  onChange: PropTypes.func,
15
- classes: PropTypes.object,
16
157
  correct: PropTypes.bool,
17
- choices: PropTypes.arrayOf(
18
- PropTypes.shape({ value: PropTypes.string, label: PropTypes.string })
19
- ),
20
- showCorrectAnswer: PropTypes.bool
158
+ choices: PropTypes.arrayOf(PropTypes.shape({ value: PropTypes.string, label: PropTypes.string })),
159
+ showCorrectAnswer: PropTypes.bool,
160
+ singleQuery: PropTypes.bool,
161
+ correctValue: PropTypes.string,
21
162
  };
22
163
 
23
164
  constructor(props) {
24
165
  super(props);
25
166
 
26
167
  this.state = {
27
- showCheckmark: false,
28
- open: false
168
+ anchorEl: null,
169
+ highlightedOptionId: null,
170
+ menuWidth: null,
171
+ previewValue: null,
29
172
  };
173
+ this.hiddenRef = React.createRef();
174
+ this.buttonRef = React.createRef();
175
+ this.previewRef = React.createRef();
176
+ this.elementRefs = [];
30
177
  }
31
178
 
32
- showCheckmarkAndOpen = () => {
33
- this.setState({
34
- showCheckmark: true,
35
- open: true
36
- });
179
+ componentDidMount() {
180
+ // measure hidden menu width once
181
+ if (this.hiddenRef.current && this.state.menuWidth === null) {
182
+ this.setState({ menuWidth: this.hiddenRef.current.clientWidth });
183
+ }
184
+ }
185
+
186
+ componentDidUpdate(prevProps, prevState) {
187
+ const hiddenEl = this.hiddenRef.current;
188
+
189
+ const dropdownJustOpened = !prevState.anchorEl && this.state.anchorEl;
190
+ if (dropdownJustOpened) {
191
+ this.elementRefs.forEach((ref) => {
192
+ if (!ref) return;
193
+
194
+ const containsLatex = ref.querySelector('[data-latex], [data-raw]');
195
+ const hasMathJax = ref.querySelector('mjx-container');
196
+ const mathHandled = ref.querySelector('[data-math-handled="true"]');
197
+
198
+ if (containsLatex && (!mathHandled || !hasMathJax)) {
199
+ renderMath(ref);
200
+ }
201
+ });
202
+ }
203
+
204
+ if (hiddenEl) {
205
+ const newWidth = hiddenEl.clientWidth;
206
+ if (newWidth !== this.state.menuWidth) {
207
+ this.elementRefs.forEach((ref) => {
208
+ if (ref) renderMath(ref);
209
+ });
210
+
211
+ renderMath(hiddenEl);
212
+ this.setState({ menuWidth: newWidth });
213
+ }
214
+ }
215
+ }
216
+
217
+ handleClick = (event) => this.setState({ anchorEl: event.currentTarget });
218
+
219
+ handleClose = () => {
220
+ const { value } = this.props;
221
+ this.setState({ anchorEl: null, previewValue: null, highlightedOptionId: null });
222
+ // clear displayed preview if no selection
223
+ if (!value && this.previewRef.current) {
224
+ this.previewRef.current.innerHTML = '';
225
+ }
37
226
  };
38
227
 
39
- hideCheckmarkAndClose = () => {
40
- this.setState({
41
- showCheckmark: false,
42
- open: false
228
+ handleHighlight = (index) => {
229
+ const highlightedOptionId = `dropdown-option-${this.props.id}-${index}`;
230
+
231
+ // preview on hover if nothing selected
232
+ const stateUpdate = { highlightedOptionId };
233
+ if (!this.props.value) {
234
+ stateUpdate.previewValue = this.props.choices[index].value;
235
+ }
236
+ this.setState(stateUpdate);
237
+ };
238
+
239
+ handleSelect = (value, index) => {
240
+ this.props.onChange(this.props.id, value);
241
+ this.handleHighlight(index);
242
+ this.handleClose();
243
+ };
244
+
245
+ handleHover = (index) => {
246
+ const selectedValue = this.props.value;
247
+
248
+ if (selectedValue) return;
249
+
250
+ const highlightedOptionId = `dropdown-option-${this.props.id}-${index}`;
251
+ const previewValue = this.state.previewValue;
252
+
253
+ this.setState({ highlightedOptionId, previewValue }, () => {
254
+ // On hover, preview the math-rendered content inside the button if no value is selected.
255
+ const ref = this.elementRefs[index];
256
+ const preview = this.previewRef.current;
257
+
258
+ if (ref && preview) {
259
+ preview.innerHTML = ref.innerHTML;
260
+ }
43
261
  });
44
262
  };
45
263
 
46
- render() {
47
- const {
48
- classes,
49
- id,
50
- correct,
51
- disabled,
52
- value,
53
- onChange,
54
- choices,
55
- showCorrectAnswer
56
- } = this.props;
57
-
58
- const { showCheckmark, open } = this.state;
264
+ getLabel(choices, value) {
265
+ const found = (choices || []).find((choice) => choice.value === value);
59
266
 
60
- return (
61
- <Select
62
- classes={{
63
- root: classes.root,
64
- icon: classes.icon,
65
- selectMenu: classes.selectMenu,
66
- select: classes.select
67
- }}
68
- disabled={disabled}
69
- value={value || ''}
70
- onOpen={this.showCheckmarkAndOpen}
71
- onClose={this.hideCheckmarkAndClose}
72
- open={open}
73
- input={<CorrectInput correct={showCorrectAnswer || correct} />}
74
- MenuProps={{
75
- keepMounted: true,
76
- disablePortal: true
77
- }}
78
- onChange={e => {
79
- onChange(id, e.target.value);
80
- }}
81
- >
82
- {(choices || []).map((c, index) => (
83
- <MenuItem
84
- classes={{ root: classes.menuRoot, selected: classes.selected }}
85
- key={`${c.label}-${index}`}
86
- value={c.value}
87
- >
88
- <span
89
- className={classes.label}
90
- dangerouslySetInnerHTML={{
91
- __html: c.label
92
- }}
93
- />
94
- {showCheckmark && (
95
- <span
96
- className={classes.label}
97
- dangerouslySetInnerHTML={{ __html: c.value === value ? ' &check;' : '' }}
98
- />
99
- )}
100
- </MenuItem>
101
- ))}
102
- </Select>
103
- );
267
+ return found ? found.label.trim() : undefined;
104
268
  }
105
- }
106
269
 
107
- const styles = () => ({
108
- root: {
109
- color: color.text(),
110
- backgroundColor: color.background(),
111
- borderColor: color.secondaryLight(),
112
- '& ul': {
113
- paddingTop: 0,
114
- paddingBottom: 0,
115
- border: `1px solid ${color.text()}`,
116
- borderRadius: '5px',
117
- color: color.text(),
118
- backgroundColor: color.background()
119
- }
120
- },
121
- select: {
122
- '&:focus': {
123
- borderRadius: '4px'
124
- }
125
- },
126
- selectMenu: {
127
- backgroundColor: color.background(),
128
- '&:hover': {
129
- borderColor: 'initial'
130
- },
131
- '&:focus': {
132
- borderColor: 'initial'
133
- }
134
- },
135
- icon: {
136
- color: color.text()
137
- },
138
- selected: {
139
- color: `${color.text()} !important`,
140
- backgroundColor: `${color.background()} !important`,
141
- '&:hover': {
142
- color: color.text(),
143
- backgroundColor: `${color.secondaryLight()} !important`
270
+ render() {
271
+ const { id, correct, disabled, value, choices, showCorrectAnswer, singleQuery, correctValue } = this.props;
272
+ const { anchorEl } = this.state;
273
+ const open = Boolean(anchorEl);
274
+ const buttonId = `dropdown-button-${id}`;
275
+ const menuId = `dropdown-menu-${id}`;
276
+ const valueDisplayId = `dropdown-value-${id}`;
277
+
278
+ // Determine the class for disabled state, view mode and evaluate mode
279
+ let disabledClass;
280
+ // Reset elementRefs before each render to avoid stale references
281
+ this.elementRefs = [];
282
+
283
+ if (disabled && correct !== undefined) {
284
+ disabledClass = correct || showCorrectAnswer ? 'disabledCorrect' : 'disabledIncorrect';
144
285
  }
145
- },
146
- menuRoot: {
147
- color: color.text(),
148
- backgroundColor: color.background(),
149
- '&:focus': {
150
- color: color.text(),
151
- backgroundColor: color.background()
152
- },
153
- '&:hover': {
154
- color: color.text(),
155
- backgroundColor: color.secondaryLight()
156
- },
157
- boxSizing: 'border-box',
158
- padding: '25px',
159
- '&:first-of-type': {
160
- borderRadius: '3px 3px 0 0'
161
- },
162
- '&:last-of-type': {
163
- borderRadius: '0 0 3px 3px'
286
+
287
+ // Create distinct, visually hidden labels for each dropdown
288
+ const incrementedId = parseInt(id, 10) + 1;
289
+ const labelId = singleQuery ? 'Query-label' : `Query-label-${incrementedId}`;
290
+ const labelText = singleQuery ? 'Query' : `Query ${incrementedId}`;
291
+
292
+ // Changed from Select to Button for dropdown to enhance accessibility. This modification offers explicit control over aria attributes and focuses management, ensuring the dropdown is compliant with accessibility standards. The use of Button and Menu components allows for better handling of keyboard interactions and provides accessible labels and menus, aligning with WCAG guidelines and improving usability for assistive technology users.
293
+ let correctnessIcon = null;
294
+ if (disabled && correct !== undefined) {
295
+ correctnessIcon =
296
+ correct || showCorrectAnswer ? (
297
+ <StyledCorrectnessIcon className="correct" />
298
+ ) : (
299
+ <StyledIncorrectnessIcon className="incorrect" />
300
+ );
164
301
  }
165
- },
166
- label: {
167
- fontSize: 'max(1rem, 14px)'
302
+
303
+ return (
304
+ <>
305
+ <div
306
+ ref={this.hiddenRef}
307
+ style={{ position: 'absolute', visibility: 'hidden', top: 0, left: 0 }}
308
+ tabIndex={-1}
309
+ aria-hidden="true"
310
+ >
311
+ {(choices || []).map((c, index) => (
312
+ <StyledMenuItem
313
+ key={index}
314
+ tabIndex={-1}
315
+ aria-hidden="true"
316
+ >
317
+ <StyledLabel dangerouslySetInnerHTML={{ __html: c.label }} />
318
+ </StyledMenuItem>
319
+ ))}
320
+ </div>
321
+ <StyledInputLabel id={labelId} tabIndex={-1} aria-hidden="true">
322
+ {labelText}
323
+ </StyledInputLabel>
324
+ <StyledButton
325
+ ref={this.buttonRef}
326
+ style={{
327
+ ...(this.state.menuWidth && { minWidth: `calc(${this.state.menuWidth}px + 8px)` }),
328
+ borderWidth: open ? '2px' : '1px',
329
+ transition: 'border-width 0.2s ease-in-out',
330
+ }}
331
+ aria-controls={open ? menuId : undefined}
332
+ aria-haspopup="listbox"
333
+ aria-expanded={open ? 'true' : undefined}
334
+ aria-activedescendant={this.state.highlightedOptionId}
335
+ onClick={this.handleClick}
336
+ className={disabledClass}
337
+ disabled={disabled}
338
+ id={buttonId}
339
+ role="combobox"
340
+ aria-label={`Select an option for ${labelText}`}
341
+ aria-labelledby={valueDisplayId}
342
+ >
343
+ {correctnessIcon}
344
+ <StyledLabel
345
+ id={valueDisplayId}
346
+ ref={this.previewRef}
347
+ dangerouslySetInnerHTML={{
348
+ __html: correctValue
349
+ ? correctValue
350
+ : open && this.state.previewValue
351
+ ? this.getLabel(choices, this.state.previewValue)
352
+ : this.getLabel(choices, value) || '',
353
+ }}
354
+ />
355
+ {open ? <ArrowDropUpIcon /> : <ArrowDropDownIcon />}
356
+ </StyledButton>
357
+ <StyledMenu
358
+ id={menuId}
359
+ anchorEl={anchorEl}
360
+ keepMounted
361
+ open={open}
362
+ onClose={this.handleClose}
363
+ getContentAnchorEl={null}
364
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
365
+ transformOrigin={{ vertical: 'top', horizontal: 'left' }}
366
+ transitionDuration={{ enter: 225, exit: 195 }}
367
+ slotProps={{
368
+ paper: this.state.menuWidth ? { style: { minWidth: this.state.menuWidth, padding: '4px' } } : undefined,
369
+ list: {
370
+ 'aria-labelledby': buttonId,
371
+ role: 'listbox',
372
+ disablePadding: true,
373
+ },
374
+ }}
375
+ >
376
+ {(choices || []).map((c, index) => {
377
+ const optionId = `dropdown-option-${id}-${index}`;
378
+
379
+ return (
380
+ <StyledMenuItem
381
+ id={optionId}
382
+ className={c.value === value ? 'selected' : ''}
383
+ key={`${c.label}-${index}`}
384
+ value={c.value}
385
+ onClick={() => this.handleSelect(c.value, index)}
386
+ role="option"
387
+ aria-selected={this.state.highlightedOptionId === optionId ? 'true' : undefined}
388
+ onMouseOver={() => this.handleHover(index)}
389
+ >
390
+ <StyledLabel
391
+ ref={(ref) => (this.elementRefs[index] = ref)}
392
+ dangerouslySetInnerHTML={{ __html: c.label }}
393
+ />
394
+ <StyledSelectedIndicator
395
+ dangerouslySetInnerHTML={{ __html: c.value === value ? ' &check;' : '' }}
396
+ />
397
+ </StyledMenuItem>
398
+ );
399
+ })}
400
+ </StyledMenu>
401
+ </>
402
+ );
168
403
  }
169
- });
404
+ }
170
405
 
171
- export default withStyles(styles)(Dropdown);
406
+ export default Dropdown;
@@ -12,7 +12,7 @@ const Input = ({
12
12
  onChange,
13
13
  showCorrectAnswer,
14
14
  spellCheck,
15
- width
15
+ width,
16
16
  }) => {
17
17
  return (
18
18
  <CorrectInput
@@ -25,7 +25,7 @@ const Input = ({
25
25
  spellCheck={spellCheck}
26
26
  isBox={true}
27
27
  width={width}
28
- onChange={e => {
28
+ onChange={(e) => {
29
29
  onChange(id, e.target.value);
30
30
  }}
31
31
  />
@@ -39,7 +39,10 @@ Input.propTypes = {
39
39
  disabled: PropTypes.bool,
40
40
  spellCheck: PropTypes.bool,
41
41
  correct: PropTypes.bool,
42
- showCorrectAnswer: PropTypes.bool
42
+ showCorrectAnswer: PropTypes.bool,
43
+ charactersLimit: PropTypes.number,
44
+ width: PropTypes.number,
45
+ isConstructedResponse: PropTypes.bool,
43
46
  };
44
47
 
45
48
  export default Input;
@@ -1,33 +1,96 @@
1
1
  import React from 'react';
2
- import Input from './components/input';
2
+ import { styled } from '@mui/material/styles';
3
+ import classnames from 'classnames';
4
+
5
+ import { color } from '@pie-lib/render-ui';
3
6
  import { withMask } from './with-mask';
7
+ //import EditableHtml from '@pie-lib/editable-html';
4
8
 
5
- export default withMask('input', props => (node, data, onChange) => {
6
- const dataset = node.data ? node.data.dataset || {} : {};
7
- if (dataset.component === 'input') {
8
- const { adjustedLimit, disabled, feedback, showCorrectAnswer, maxLength, spellCheck } = props;
9
+ let EditableHtml;
10
+ let StyledEditableHtml;
11
+
12
+ // - mathquill error window not defined
13
+ if (typeof window !== 'undefined') {
14
+ EditableHtml = require('@pie-lib/editable-html')['default'];
15
+ StyledEditableHtml = styled(EditableHtml)(() => ({
16
+ display: 'inline-block',
17
+ verticalAlign: 'middle',
18
+ margin: '4px',
19
+ borderRadius: '4px',
20
+ border: `1px solid ${color.black()}`,
21
+ '&.correct': {
22
+ border: `1px solid ${color.correct()}`,
23
+ },
24
+ '&.incorrect': {
25
+ border: `1px solid ${color.incorrect()}`,
26
+ },
27
+ }));
28
+ }
9
29
 
10
- // the first answer is the correct one
30
+ const MaskedInput = (props) => (node, data) => {
31
+ const {
32
+ adjustedLimit,
33
+ disabled,
34
+ feedback,
35
+ showCorrectAnswer,
36
+ maxLength,
37
+ spellCheck,
38
+ pluginProps,
39
+ onChange,
40
+ } = props;
41
+ const dataset = node.data?.dataset || {};
42
+
43
+ if (dataset.component === 'input') {
11
44
  const correctAnswer = ((props.choices && dataset && props.choices[dataset.id]) || [])[0];
12
- const finalValue = showCorrectAnswer
13
- ? correctAnswer && correctAnswer.label
14
- : data[dataset.id] || '';
45
+ const finalValue = showCorrectAnswer ? correctAnswer && correctAnswer.label : data[dataset.id] || '';
15
46
  const width = maxLength && maxLength[dataset.id];
47
+ const feedbackStatus = feedback && feedback[dataset.id];
48
+ const isCorrect = showCorrectAnswer || feedbackStatus === 'correct';
49
+ const isIncorrect = !showCorrectAnswer && feedbackStatus === 'incorrect';
50
+
51
+ const handleInputChange = (newValue) => {
52
+ const updatedValue = {
53
+ ...data,
54
+ [dataset.id]: newValue,
55
+ };
56
+ onChange(updatedValue);
57
+ };
58
+
59
+ const handleKeyDown = (event) => {
60
+ // the keyCode value for the Enter/Return key is 13
61
+ if (event.key === 'Enter' || event.keyCode === 13) {
62
+ return false;
63
+ }
64
+ };
16
65
 
17
66
  return (
18
- <Input
67
+ <StyledEditableHtml
68
+ id={dataset.id}
19
69
  key={`${node.type}-input-${dataset.id}`}
20
- correct={feedback && feedback[dataset.id] && feedback[dataset.id] === 'correct'}
21
70
  disabled={showCorrectAnswer || disabled}
22
- value={finalValue}
23
- id={dataset.id}
24
- onChange={onChange}
25
- showCorrectAnswer={showCorrectAnswer}
26
- width={width}
71
+ disableUnderline
72
+ onChange={handleInputChange}
73
+ markup={finalValue || ''}
27
74
  charactersLimit={adjustedLimit ? width : 25}
28
- isConstructedResponse={true}
75
+ activePlugins={['languageCharacters']}
76
+ pluginProps={pluginProps}
77
+ languageCharactersProps={[{ language: 'spanish' }]}
29
78
  spellCheck={spellCheck}
79
+ width={`calc(${width}em + 42px)`} // added 42px for left and right padding of editable-html
80
+ onKeyDown={handleKeyDown}
81
+ autoWidthToolbar
82
+ toolbarOpts={{
83
+ minWidth: 'auto',
84
+ noBorder: true,
85
+ isHidden: !!pluginProps?.characters?.disabled,
86
+ }}
87
+ className={classnames({
88
+ correct: isCorrect,
89
+ incorrect: isIncorrect,
90
+ })}
30
91
  />
31
92
  );
32
93
  }
33
- });
94
+ };
95
+
96
+ export default withMask('input', MaskedInput);