@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
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ // import Input from './components/input';
3
+ import { withMask } from './with-mask';
4
+
5
+ // eslint-disable-next-line react/display-name
6
+ export default withMask('input', (props) => (node, data, onChange) => {
7
+ const dataset = node.data ? node.data.dataset || {} : {};
8
+ if (dataset.component === 'input') {
9
+ // eslint-disable-next-line react/prop-types
10
+ // const { adjustedLimit, disabled, feedback, showCorrectAnswer, maxLength, spellCheck } = props;
11
+
12
+ // the first answer is the correct one
13
+ // eslint-disable-next-line react/prop-types
14
+ // const correctAnswer = ((props.choices && dataset && props.choices[dataset.id]) || [])[0];
15
+ // const finalValue = showCorrectAnswer ? correctAnswer && correctAnswer.label : data[dataset.id] || '';
16
+ // const width = maxLength && maxLength[dataset.id];
17
+
18
+ return props.customMarkMarkupComponent(dataset.id);
19
+ // return (
20
+ // <Input
21
+ // key={`${node.type}-input-${dataset.id}`}
22
+ // correct={feedback && feedback[dataset.id] && feedback[dataset.id] === 'correct'}
23
+ // disabled={showCorrectAnswer || disabled}
24
+ // value={finalValue}
25
+ // id={dataset.id}
26
+ // onChange={onChange}
27
+ // showCorrectAnswer={showCorrectAnswer}
28
+ // width={width}
29
+ // charactersLimit={adjustedLimit ? width : 25}
30
+ // isConstructedResponse={true}
31
+ // spellCheck={spellCheck}
32
+ // />
33
+ // );
34
+ }
35
+ });
@@ -1,17 +1,31 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { renderMath } from '@pie-lib/math-rendering';
3
+ import { DragProvider } from '@pie-lib/drag';
4
+ import { DragOverlay, closestCenter } from '@dnd-kit/core';
5
+
4
6
  import Choices from './choices';
7
+ import Choice from './choices/choice';
5
8
  import Blank from './components/blank';
6
9
  import { withMask } from './with-mask';
7
10
 
8
- const Masked = withMask('blank', props => (node, data, onChange) => {
9
- const dataset = node.data ? node.data.dataset || {} : {};
10
-
11
+ const Masked = withMask('blank', (props) => (node, data, onChange) => {
12
+ const dataset = node.data?.dataset || {};
11
13
  if (dataset.component === 'blank') {
12
- const { disabled, duplicates, correctResponse, feedback, showCorrectAnswer } = props;
14
+ // eslint-disable-next-line react/prop-types
15
+ const {
16
+ disabled,
17
+ duplicates,
18
+ correctResponse,
19
+ feedback,
20
+ showCorrectAnswer,
21
+ emptyResponseAreaWidth,
22
+ emptyResponseAreaHeight,
23
+ instanceId,
24
+ isDragging
25
+ } = props;
13
26
  const choiceId = showCorrectAnswer ? correctResponse[dataset.id] : data[dataset.id];
14
- const choice = choiceId && props.choices.find(c => c.id === choiceId);
27
+ // eslint-disable-next-line react/prop-types
28
+ const choice = choiceId && props.choices.find((c) => c.id === choiceId);
15
29
 
16
30
  return (
17
31
  <Blank
@@ -21,48 +35,131 @@ const Masked = withMask('blank', props => (node, data, onChange) => {
21
35
  duplicates={duplicates}
22
36
  choice={choice}
23
37
  id={dataset.id}
24
- onChange={onChange}
38
+ emptyResponseAreaWidth={emptyResponseAreaWidth}
39
+ emptyResponseAreaHeight={emptyResponseAreaHeight}
40
+ onChange={(id, choiceId) => {
41
+ const newData = { ...data };
42
+ if (choiceId === undefined) {
43
+ delete newData[id];
44
+ } else {
45
+ newData[id] = choiceId;
46
+ }
47
+ onChange(newData);
48
+ }}
49
+ instanceId={instanceId}
50
+ isDragging={isDragging}
25
51
  />
26
52
  );
27
53
  }
28
54
  });
29
55
 
30
56
  export default class DragInTheBlank extends React.Component {
57
+ constructor(props) {
58
+ super(props);
59
+ this.state = {
60
+ activeDragItem: null,
61
+ };
62
+ }
63
+
31
64
  static propTypes = {
32
65
  markup: PropTypes.string,
33
66
  layout: PropTypes.object,
34
67
  choicesPosition: PropTypes.string,
35
- choices: PropTypes.arrayOf(
36
- PropTypes.shape({ label: PropTypes.string, value: PropTypes.string })
37
- ),
68
+ choices: PropTypes.array,
38
69
  value: PropTypes.object,
39
70
  onChange: PropTypes.func,
40
71
  duplicates: PropTypes.bool,
41
72
  disabled: PropTypes.bool,
42
73
  feedback: PropTypes.object,
43
74
  correctResponse: PropTypes.object,
44
- showCorrectAnswer: PropTypes.bool
75
+ showCorrectAnswer: PropTypes.bool,
76
+ emptyResponseAreaWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
77
+ emptyResponseAreaHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
78
+ instanceId: PropTypes.string,
79
+ };
80
+
81
+ static defaultProps = {
82
+ instanceId: 'drag-in-the-blank',
45
83
  };
46
84
 
47
- componentWillReceiveProps() {
48
- if (this.rootRef) {
49
- renderMath(this.rootRef);
85
+ handleDragStart = (event) => {
86
+ const { active } = event;
87
+
88
+ if (active?.data?.current) {
89
+ this.setState({
90
+ activeDragItem: active.data.current,
91
+ });
50
92
  }
51
- }
93
+ };
52
94
 
53
- componentDidUpdate() {
54
- renderMath(this.rootRef);
55
- }
95
+ renderDragOverlay = () => {
96
+ const { activeDragItem } = this.state;
97
+ if (!activeDragItem) return null;
98
+
99
+ if (activeDragItem.type === 'MaskBlank') {
100
+ return (
101
+ <Choice
102
+ disabled={activeDragItem.disabled}
103
+ choice={activeDragItem.choice}
104
+ instanceId={activeDragItem.instanceId}
105
+ />
106
+ );
107
+ }
108
+
109
+ return null;
110
+ };
111
+
112
+ handleDragEnd = (event) => {
113
+ const { active, over } = event;
114
+ const { onChange, value } = this.props;
115
+
116
+ if (!over || !active || !onChange) {
117
+ return;
118
+ }
119
+
120
+ const draggedData = active.data.current;
121
+ const dropData = over.data.current;
122
+
123
+ if (draggedData?.type === 'MaskBlank' && dropData?.accepts?.includes('MaskBlank')) {
124
+ const draggedItem = draggedData;
125
+ const targetId = dropData.id;
56
126
 
57
- getPositionDirection = choicePosition => {
127
+ // drop from choice to blank (placing choice into response)
128
+ if (draggedItem.fromChoice === true) {
129
+ const newValue = { ...value };
130
+ newValue[targetId] = draggedItem.choice.id;
131
+ onChange(newValue);
132
+ } else if (dropData.toChoiceBoard === true) {
133
+ // handle drop from blank to choice board (removal from blank)
134
+ const newValue = { ...value };
135
+ delete newValue[draggedItem.id];
136
+ onChange(newValue);
137
+ }
138
+ // handle drop from blank to blank (changing position)
139
+ else if (draggedItem.id !== targetId) {
140
+ const newValue = { ...value };
141
+ newValue[targetId] = draggedItem.choice.id;
142
+ delete newValue[draggedItem.id];
143
+ onChange(newValue);
144
+ }
145
+ }
146
+ this.setState({ activeDragItem: null });
147
+ };
148
+
149
+ getPositionDirection = (choicePosition) => {
58
150
  let flexDirection;
151
+ let justifyContent;
152
+ let alignItems;
59
153
 
60
154
  switch (choicePosition) {
61
155
  case 'left':
62
156
  flexDirection = 'row';
157
+ alignItems = 'center';
63
158
  break;
64
159
  case 'right':
65
160
  flexDirection = 'row-reverse';
161
+ justifyContent = 'flex-end';
162
+ alignItems = 'center';
66
163
  break;
67
164
  case 'below':
68
165
  flexDirection = 'column-reverse';
@@ -73,14 +170,13 @@ export default class DragInTheBlank extends React.Component {
73
170
  break;
74
171
  }
75
172
 
76
- return flexDirection;
173
+ return { flexDirection, justifyContent, alignItems };
77
174
  };
78
175
 
79
176
  render() {
80
177
  const {
81
178
  markup,
82
179
  duplicates,
83
- layout,
84
180
  value,
85
181
  onChange,
86
182
  choicesPosition,
@@ -88,37 +184,53 @@ export default class DragInTheBlank extends React.Component {
88
184
  correctResponse,
89
185
  disabled,
90
186
  feedback,
91
- showCorrectAnswer
187
+ showCorrectAnswer,
188
+ emptyResponseAreaWidth,
189
+ emptyResponseAreaHeight,
190
+ layout,
191
+ instanceId
92
192
  } = this.props;
93
193
 
94
194
  const choicePosition = choicesPosition || 'below';
95
- const style = {
96
- display: 'flex',
97
- flexDirection: this.getPositionDirection(choicePosition)
98
- };
195
+ const style = { display: 'flex', minWidth: '100px', ...this.getPositionDirection(choicePosition) };
99
196
 
100
197
  return (
101
- <div ref={ref => ref && (this.rootRef = ref)} style={style}>
102
- <Choices
103
- choicePosition={choicePosition}
104
- duplicates={duplicates}
105
- choices={choices}
106
- value={value}
107
- disabled={disabled}
108
- />
109
- <Masked
110
- markup={markup}
111
- layout={layout}
112
- value={value}
113
- choices={choices}
114
- onChange={onChange}
115
- disabled={disabled}
116
- duplicates={duplicates}
117
- feedback={feedback}
118
- correctResponse={correctResponse}
119
- showCorrectAnswer={showCorrectAnswer}
120
- />
121
- </div>
198
+ <DragProvider
199
+ onDragStart={this.handleDragStart}
200
+ onDragEnd={this.handleDragEnd}
201
+ collisionDetection={closestCenter}
202
+ >
203
+ <div ref={(ref) => (this.rootRef = ref)} style={style}>
204
+ <Choices
205
+ choicePosition={choicePosition}
206
+ choices={choices}
207
+ value={value}
208
+ duplicates={duplicates}
209
+ disabled={disabled}
210
+ instanceId={instanceId}
211
+ />
212
+ <Masked
213
+ elementType="drag-in-the-blank"
214
+ markup={markup}
215
+ layout={layout}
216
+ value={value}
217
+ choices={choices}
218
+ onChange={onChange}
219
+ disabled={disabled}
220
+ duplicates={duplicates}
221
+ feedback={feedback}
222
+ correctResponse={correctResponse}
223
+ showCorrectAnswer={showCorrectAnswer}
224
+ emptyResponseAreaWidth={emptyResponseAreaWidth}
225
+ emptyResponseAreaHeight={emptyResponseAreaHeight}
226
+ instanceId={instanceId}
227
+ isDragging={!!this.state.activeDragItem}
228
+ />
229
+ <DragOverlay style={{ pointerEvents: "none" }}>
230
+ {this.renderDragOverlay()}
231
+ </DragOverlay>
232
+ </div>
233
+ </DragProvider>
122
234
  );
123
235
  }
124
236
  }
package/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { withMask, buildLayoutFromMarkup } from './with-mask';
2
2
  import DragInTheBlank from './drag-in-the-blank';
3
3
  import ConstructedResponse from './constructed-response';
4
+ import Customizable from './customizable';
4
5
  import InlineDropdown from './inline-dropdown';
5
6
  import componentize from './componentize';
6
7
 
@@ -10,5 +11,6 @@ export {
10
11
  DragInTheBlank,
11
12
  ConstructedResponse,
12
13
  InlineDropdown,
13
- componentize
14
+ componentize,
15
+ Customizable,
14
16
  };
@@ -2,12 +2,13 @@ import React from 'react';
2
2
  import Dropdown from './components/dropdown';
3
3
  import { withMask } from './with-mask';
4
4
 
5
- export default withMask('dropdown', props => (node, data, onChange) => {
5
+ // eslint-disable-next-line react/display-name
6
+ export default withMask('dropdown', (props) => (node, data, onChange) => {
6
7
  const dataset = node.data ? node.data.dataset || {} : {};
7
8
  if (dataset.component === 'dropdown') {
9
+ // eslint-disable-next-line react/prop-types
8
10
  const { choices, disabled, feedback, showCorrectAnswer } = props;
9
- const correctAnswer =
10
- choices && choices[dataset.id] && choices[dataset.id].find(c => c.correct);
11
+ const correctAnswer = choices && choices[dataset.id] && choices[dataset.id].find((c) => c.correct);
11
12
  const finalChoice = showCorrectAnswer ? correctAnswer && correctAnswer.value : data[dataset.id];
12
13
 
13
14
  return (
@@ -16,10 +17,12 @@ export default withMask('dropdown', props => (node, data, onChange) => {
16
17
  correct={feedback && feedback[dataset.id] && feedback[dataset.id] === 'correct'}
17
18
  disabled={disabled || showCorrectAnswer}
18
19
  value={finalChoice}
20
+ correctValue={showCorrectAnswer ? correctAnswer && correctAnswer.label : undefined}
19
21
  id={dataset.id}
20
22
  onChange={onChange}
21
23
  choices={choices[dataset.id]}
22
24
  showCorrectAnswer={showCorrectAnswer}
25
+ singleQuery={Object.keys(choices).length == 1}
23
26
  />
24
27
  );
25
28
  }
package/src/mask.jsx CHANGED
@@ -1,16 +1,19 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import get from 'lodash/get';
4
- import { Text } from 'slate';
5
- import { withStyles } from '@material-ui/core/styles';
4
+ import { styled } from '@mui/material/styles';
5
+ import { renderMath } from '@pie-lib/math-rendering';
6
6
  import { MARK_TAGS } from './serialization';
7
7
 
8
- const Paragraph = withStyles(theme => ({
9
- para: {
10
- paddingTop: 2 * theme.spacing.unit,
11
- paddingBottom: 2 * theme.spacing.unit
12
- }
13
- }))(props => <div className={props.classes.para}>{props.children}</div>);
8
+ const Paragraph = styled('div')(({ theme }) => ({
9
+ paddingTop: theme.spacing(2),
10
+ paddingBottom: theme.spacing(2),
11
+ }));
12
+
13
+ const Spacer = styled('span')(() => ({
14
+ display: 'inline-block',
15
+ width: '.75em',
16
+ }));
14
17
 
15
18
  const restrictWhitespaceTypes = ['tbody', 'tr'];
16
19
 
@@ -25,25 +28,33 @@ const addText = (parentNode, text) => {
25
28
  }
26
29
  };
27
30
 
28
- const getMark = n => {
29
- const markTags = Object.values(MARK_TAGS);
31
+ const getMark = (n) => {
32
+ const mark = n.leaves.find((leave) => get(leave, 'marks', []).length);
33
+
34
+ if (mark) {
35
+ return mark.marks[0];
36
+ }
30
37
 
31
- return markTags.includes(n.type);
38
+ return null;
32
39
  };
33
40
 
34
- export const renderChildren = (layout, value, onChange, rootRenderChildren, parentNode) => {
41
+ export const renderChildren = (layout, value, onChange, rootRenderChildren, parentNode, elementType) => {
35
42
  if (!value) {
36
43
  return null;
37
44
  }
38
45
 
39
46
  const children = [];
40
47
 
41
- (layout.children || []).forEach((n, index) => {
42
- const key = `${n.type}-${index}`;
48
+ (layout.nodes || []).forEach((n, index) => {
49
+ const key = n.type ? `${n.type}-${index}` : `${index}`;
43
50
 
44
51
  if (n.isMath) {
45
52
  children.push(
46
- <span dangerouslySetInnerHTML={{ __html: `<math displaystyle="true">${n.children[0].innerHTML}</math>` }} />
53
+ <span
54
+ dangerouslySetInnerHTML={{
55
+ __html: `<math displaystyle="true">${n.nodes[0].innerHTML}</math>`,
56
+ }}
57
+ />,
47
58
  );
48
59
  return children;
49
60
  }
@@ -52,12 +63,19 @@ export const renderChildren = (layout, value, onChange, rootRenderChildren, pare
52
63
  const c = rootRenderChildren(n, value, onChange);
53
64
  if (c) {
54
65
  children.push(c);
66
+ if (parentNode?.type !== 'td' && elementType === 'drag-in-the-blank') {
67
+ children.push(<Spacer key={`spacer-${index}`} />);
68
+ }
55
69
  return;
56
70
  }
57
71
  }
58
72
 
59
- if (Text.isText(n)) {
60
- const content = n.text;
73
+ if (n.object === 'text') {
74
+ const content = n.leaves.reduce((acc, l) => {
75
+ const t = l.text;
76
+ const extraText = addText(parentNode, t);
77
+ return extraText ? acc + extraText : acc;
78
+ }, '');
61
79
  const mark = getMark(n);
62
80
 
63
81
  if (mark) {
@@ -67,24 +85,27 @@ export const renderChildren = (layout, value, onChange, rootRenderChildren, pare
67
85
  if (MARK_TAGS[markKey] === mark.type) {
68
86
  const Tag = markKey;
69
87
 
70
- children.push(<Tag>{content}</Tag>);
88
+ children.push(<Tag key={key}>{content}</Tag>);
71
89
  break;
72
90
  }
73
91
  }
74
92
  } else if (content.length > 0) {
75
93
  children.push(content);
94
+ if (parentNode?.type !== 'td' && elementType === 'drag-in-the-blank') {
95
+ children.push(<Spacer key={`spacer-${index}`} />);
96
+ }
76
97
  }
77
98
  } else {
78
- const subNodes = renderChildren(n, value, onChange, rootRenderChildren, n);
99
+ const subNodes = renderChildren(n, value, onChange, rootRenderChildren, n, elementType);
79
100
  if (n.type === 'p' || n.type === 'paragraph') {
80
101
  children.push(<Paragraph key={key}>{subNodes}</Paragraph>);
81
102
  } else {
82
103
  const Tag = n.type;
83
- if (n.children && n.children.length > 0) {
104
+ if (n.nodes && n.nodes.length > 0) {
84
105
  children.push(
85
106
  <Tag key={key} {...n.data.attributes}>
86
107
  {subNodes}
87
- </Tag>
108
+ </Tag>,
88
109
  );
89
110
  } else {
90
111
  children.push(<Tag key={key} {...n.data.attributes} />);
@@ -95,32 +116,56 @@ export const renderChildren = (layout, value, onChange, rootRenderChildren, pare
95
116
  return children;
96
117
  };
97
118
 
98
- const MaskContainer = withStyles(() => ({
99
- main: {
100
- display: 'initial'
101
- }
102
- }))(props => <div className={props.classes.main}>{props.children}</div>);
119
+ const MaskContainer = styled('div')(() => ({
120
+ display: 'initial',
121
+ '&:not(.MathJax) table': {
122
+ borderCollapse: 'collapse',
123
+ },
124
+ // align table content to left as per STAR requirement PD-3687
125
+ '&:not(.MathJax) table td, &:not(.MathJax) table th': {
126
+ padding: '8px 12px',
127
+ textAlign: 'left',
128
+ },
129
+ }));
103
130
 
104
131
  /**
105
132
  * Renders a layout that uses the slate.js Value model structure.
106
133
  */
107
134
  export default class Mask extends React.Component {
135
+ constructor(props) {
136
+ super(props);
137
+ this.internalContainerRef = React.createRef();
138
+ }
139
+
108
140
  static propTypes = {
109
141
  renderChildren: PropTypes.func,
110
142
  layout: PropTypes.object,
111
143
  value: PropTypes.object,
112
- onChange: PropTypes.func
144
+ onChange: PropTypes.func,
145
+ elementType: PropTypes.string,
146
+ containerRef: PropTypes.oneOfType([
147
+ PropTypes.func,
148
+ PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
149
+ ]),
113
150
  };
114
151
 
152
+ componentDidMount() {
153
+ const containerRef = this.props.containerRef || this.internalContainerRef;
154
+ if (containerRef.current && typeof renderMath === 'function') {
155
+ renderMath(containerRef.current);
156
+ }
157
+ }
158
+
115
159
  handleChange = (id, value) => {
116
160
  const data = { ...this.props.value, [id]: value };
117
161
  this.props.onChange(data);
118
162
  };
119
163
 
120
164
  render() {
121
- const { value, layout } = this.props;
122
- const children = renderChildren(layout, value, this.handleChange, this.props.renderChildren);
165
+ const { value, layout, elementType, containerRef } = this.props;
166
+ const children = renderChildren(layout, value, this.handleChange, this.props.renderChildren, null, elementType);
167
+ const ref = containerRef || this.internalContainerRef;
123
168
 
124
- return <MaskContainer>{children}</MaskContainer>;
169
+ return <MaskContainer ref={ref}>{children}</MaskContainer>;
125
170
  }
126
171
  }