@pie-lib/mask-markup 1.42.0-mui-update.0 → 1.45.0-mui-update.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/lib/choices/choice.js +30 -5
- package/lib/choices/choice.js.map +1 -1
- package/lib/choices/index.js +2 -1
- package/lib/choices/index.js.map +1 -1
- package/lib/components/blank.js +46 -46
- package/lib/components/blank.js.map +1 -1
- package/lib/drag-in-the-blank.js +62 -48
- package/lib/drag-in-the-blank.js.map +1 -1
- package/lib/mask.js +14 -6
- package/lib/mask.js.map +1 -1
- package/package.json +6 -6
- package/src/choices/choice.jsx +35 -6
- package/src/choices/index.jsx +1 -1
- package/src/components/blank.jsx +49 -58
- package/src/drag-in-the-blank.jsx +66 -35
- package/src/mask.jsx +13 -1
package/src/components/blank.jsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React, { useRef, useState, useEffect } from 'react';
|
|
2
|
-
import ReactDOM from 'react-dom';
|
|
3
2
|
import PropTypes from 'prop-types';
|
|
4
3
|
import { renderMath } from '@pie-lib/math-rendering';
|
|
5
4
|
import debug from 'debug';
|
|
@@ -18,7 +17,7 @@ const StyledContent = styled('span')(() => ({
|
|
|
18
17
|
minWidth: '200px',
|
|
19
18
|
touchAction: 'none',
|
|
20
19
|
overflow: 'hidden',
|
|
21
|
-
whiteSpace: 'nowrap',
|
|
20
|
+
whiteSpace: 'nowrap',
|
|
22
21
|
'&.over': {
|
|
23
22
|
whiteSpace: 'nowrap',
|
|
24
23
|
overflow: 'hidden',
|
|
@@ -84,17 +83,19 @@ const StyledChipLabel = styled('span')(() => ({
|
|
|
84
83
|
},
|
|
85
84
|
}));
|
|
86
85
|
|
|
87
|
-
function BlankContent({
|
|
88
|
-
disabled,
|
|
89
|
-
choice,
|
|
90
|
-
isOver,
|
|
91
|
-
|
|
86
|
+
function BlankContent({
|
|
87
|
+
disabled,
|
|
88
|
+
choice,
|
|
89
|
+
isOver,
|
|
90
|
+
isDragging,
|
|
91
|
+
dragItem,
|
|
92
92
|
correct,
|
|
93
93
|
emptyResponseAreaWidth,
|
|
94
94
|
emptyResponseAreaHeight,
|
|
95
95
|
}) {
|
|
96
96
|
const rootRef = useRef(null);
|
|
97
97
|
const spanRef = useRef(null);
|
|
98
|
+
const frozenRef = useRef(null); // to use during dragging to prevent flickering
|
|
98
99
|
const [dimensions, setDimensions] = useState({ height: 0, width: 0 });
|
|
99
100
|
|
|
100
101
|
const handleImageLoad = () => {
|
|
@@ -140,16 +141,6 @@ function BlankContent({
|
|
|
140
141
|
}
|
|
141
142
|
};
|
|
142
143
|
|
|
143
|
-
const addDraggableFalseAttributes = (parent) => {
|
|
144
|
-
if (parent && parent.childNodes) {
|
|
145
|
-
parent.childNodes.forEach((elem) => {
|
|
146
|
-
if (elem instanceof Element || elem instanceof HTMLDocument) {
|
|
147
|
-
elem.setAttribute('draggable', false);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
|
|
153
144
|
const getRootDimensions = () => {
|
|
154
145
|
// Handle potential non-numeric values
|
|
155
146
|
const responseAreaWidth = !isNaN(parseFloat(emptyResponseAreaWidth))
|
|
@@ -176,63 +167,65 @@ function BlankContent({
|
|
|
176
167
|
handleElements();
|
|
177
168
|
}, []);
|
|
178
169
|
|
|
170
|
+
// Render math for the placeholder/preview when dragging over
|
|
179
171
|
useEffect(() => {
|
|
180
172
|
if (rootRef.current) {
|
|
181
173
|
renderMath(rootRef.current);
|
|
182
174
|
}
|
|
183
|
-
});
|
|
175
|
+
}, [isOver, dragItem?.choice?.value]);
|
|
184
176
|
|
|
185
177
|
useEffect(() => {
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
handleElements();
|
|
178
|
+
if (!choice) {
|
|
179
|
+
setDimensions({ height: 0, width: 0 });
|
|
180
|
+
return;
|
|
192
181
|
}
|
|
182
|
+
handleElements();
|
|
193
183
|
}, [choice]);
|
|
194
184
|
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (!isOver && !isDragging) {
|
|
187
|
+
frozenRef.current = {
|
|
188
|
+
width: rootRef.current.offsetWidth,
|
|
189
|
+
height: rootRef.current.offsetHeight,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}, [choice, isOver, isDragging]);
|
|
193
|
+
|
|
195
194
|
const draggedLabel = dragItem && isOver && dragItem.choice && dragItem.choice.value;
|
|
196
195
|
const label = choice && choice.value;
|
|
196
|
+
const style = (isOver || isDragging)
|
|
197
|
+
? {
|
|
198
|
+
width: frozenRef.current?.width,
|
|
199
|
+
height: frozenRef.current?.height,
|
|
200
|
+
}
|
|
201
|
+
: getRootDimensions();
|
|
197
202
|
|
|
198
203
|
return (
|
|
199
204
|
<StyledChip
|
|
200
205
|
clickable={false}
|
|
201
|
-
disabled={
|
|
206
|
+
disabled={disabled}
|
|
202
207
|
ref={rootRef}
|
|
203
208
|
component="span"
|
|
204
209
|
label={
|
|
205
210
|
<React.Fragment>
|
|
206
211
|
<StyledChipLabel
|
|
207
|
-
|
|
212
|
+
ref={spanRef}
|
|
213
|
+
draggable={true}
|
|
214
|
+
className={classnames({
|
|
208
215
|
over: isOver,
|
|
209
216
|
hidden: draggedLabel,
|
|
210
217
|
})}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
spanRef.current = ref;
|
|
214
|
-
ref.innerHTML = label || '';
|
|
215
|
-
addDraggableFalseAttributes(ref);
|
|
216
|
-
}
|
|
217
|
-
}}
|
|
218
|
-
>
|
|
219
|
-
{' '}
|
|
220
|
-
</StyledChipLabel>
|
|
218
|
+
dangerouslySetInnerHTML={{ __html: label || '' }}
|
|
219
|
+
/>
|
|
221
220
|
{draggedLabel && (
|
|
222
221
|
<StyledChipLabel
|
|
222
|
+
draggable={true}
|
|
223
223
|
className={classnames({
|
|
224
224
|
over: isOver,
|
|
225
225
|
dragged: true,
|
|
226
226
|
})}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
ref.innerHTML = draggedLabel || '';
|
|
230
|
-
addDraggableFalseAttributes(ref);
|
|
231
|
-
}
|
|
232
|
-
}}
|
|
233
|
-
>
|
|
234
|
-
{' '}
|
|
235
|
-
</StyledChipLabel>
|
|
227
|
+
dangerouslySetInnerHTML={{ __html: draggedLabel || '' }}
|
|
228
|
+
/>
|
|
236
229
|
)}
|
|
237
230
|
</React.Fragment>
|
|
238
231
|
}
|
|
@@ -243,9 +236,7 @@ function BlankContent({
|
|
|
243
236
|
incorrect: correct !== undefined && !correct,
|
|
244
237
|
})}
|
|
245
238
|
variant={disabled ? 'outlined' : undefined}
|
|
246
|
-
style={
|
|
247
|
-
...getRootDimensions(),
|
|
248
|
-
}}
|
|
239
|
+
style={style}
|
|
249
240
|
/>
|
|
250
241
|
);
|
|
251
242
|
}
|
|
@@ -270,16 +261,16 @@ BlankContent.propTypes = {
|
|
|
270
261
|
};
|
|
271
262
|
|
|
272
263
|
// New functional component using @dnd-kit hooks
|
|
273
|
-
function DragDropBlank({
|
|
274
|
-
id,
|
|
275
|
-
disabled,
|
|
276
|
-
duplicates,
|
|
277
|
-
choice,
|
|
278
|
-
correct,
|
|
279
|
-
onChange,
|
|
280
|
-
emptyResponseAreaWidth,
|
|
264
|
+
function DragDropBlank({
|
|
265
|
+
id,
|
|
266
|
+
disabled,
|
|
267
|
+
duplicates,
|
|
268
|
+
choice,
|
|
269
|
+
correct,
|
|
270
|
+
onChange,
|
|
271
|
+
emptyResponseAreaWidth,
|
|
281
272
|
emptyResponseAreaHeight,
|
|
282
|
-
instanceId
|
|
273
|
+
instanceId
|
|
283
274
|
}) {
|
|
284
275
|
// Setup draggable functionality
|
|
285
276
|
const {
|
|
@@ -326,7 +317,7 @@ function DragDropBlank({
|
|
|
326
317
|
};
|
|
327
318
|
|
|
328
319
|
return (
|
|
329
|
-
<StyledContent
|
|
320
|
+
<StyledContent
|
|
330
321
|
ref={setNodeRef}
|
|
331
322
|
style={style}
|
|
332
323
|
className={isOver ? 'over' : ''}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import { renderMath } from '@pie-lib/math-rendering';
|
|
4
3
|
import { DragProvider } from '@pie-lib/drag';
|
|
4
|
+
import { DragOverlay, closestCenter } from '@dnd-kit/core';
|
|
5
|
+
|
|
5
6
|
import Choices from './choices';
|
|
7
|
+
import Choice from './choices/choice';
|
|
6
8
|
import Blank from './components/blank';
|
|
7
9
|
import { withMask } from './with-mask';
|
|
8
10
|
|
|
@@ -19,6 +21,7 @@ const Masked = withMask('blank', (props) => (node, data, onChange) => {
|
|
|
19
21
|
emptyResponseAreaWidth,
|
|
20
22
|
emptyResponseAreaHeight,
|
|
21
23
|
instanceId,
|
|
24
|
+
isDragging
|
|
22
25
|
} = props;
|
|
23
26
|
const choiceId = showCorrectAnswer ? correctResponse[dataset.id] : data[dataset.id];
|
|
24
27
|
// eslint-disable-next-line react/prop-types
|
|
@@ -44,12 +47,20 @@ const Masked = withMask('blank', (props) => (node, data, onChange) => {
|
|
|
44
47
|
onChange(newData);
|
|
45
48
|
}}
|
|
46
49
|
instanceId={instanceId}
|
|
50
|
+
isDragging={isDragging}
|
|
47
51
|
/>
|
|
48
52
|
);
|
|
49
53
|
}
|
|
50
54
|
});
|
|
51
55
|
|
|
52
56
|
export default class DragInTheBlank extends React.Component {
|
|
57
|
+
constructor(props) {
|
|
58
|
+
super(props);
|
|
59
|
+
this.state = {
|
|
60
|
+
activeDragItem: null,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
static propTypes = {
|
|
54
65
|
markup: PropTypes.string,
|
|
55
66
|
layout: PropTypes.object,
|
|
@@ -71,58 +82,70 @@ export default class DragInTheBlank extends React.Component {
|
|
|
71
82
|
instanceId: 'drag-in-the-blank',
|
|
72
83
|
};
|
|
73
84
|
|
|
85
|
+
handleDragStart = (event) => {
|
|
86
|
+
const { active } = event;
|
|
87
|
+
|
|
88
|
+
if (active?.data?.current) {
|
|
89
|
+
this.setState({
|
|
90
|
+
activeDragItem: active.data.current,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
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
|
+
|
|
74
112
|
handleDragEnd = (event) => {
|
|
75
|
-
console.log('Drag End Event:', event);
|
|
76
113
|
const { active, over } = event;
|
|
77
114
|
const { onChange, value } = this.props;
|
|
78
115
|
|
|
79
116
|
if (!over || !active || !onChange) {
|
|
80
|
-
console.log('Early return - missing data:', { over: !!over, active: !!active, onChange: !!onChange });
|
|
81
117
|
return;
|
|
82
118
|
}
|
|
83
119
|
|
|
84
120
|
const draggedData = active.data.current;
|
|
85
121
|
const dropData = over.data.current;
|
|
86
122
|
|
|
87
|
-
console.log('Drag data:', draggedData);
|
|
88
|
-
console.log('Drop data:', dropData);
|
|
89
|
-
|
|
90
|
-
// Handle drop from choice to blank or blank to blank
|
|
91
123
|
if (draggedData?.type === 'MaskBlank' && dropData?.accepts?.includes('MaskBlank')) {
|
|
92
|
-
console.log('Valid drag/drop types');
|
|
93
124
|
const draggedItem = draggedData;
|
|
94
125
|
const targetId = dropData.id;
|
|
95
126
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
} else {
|
|
115
|
-
console.log('Instance ID mismatch:', draggedItem.instanceId, 'vs', dropData.instanceId);
|
|
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);
|
|
116
144
|
}
|
|
117
|
-
} else {
|
|
118
|
-
console.log('Invalid drag/drop types:', draggedData?.type, dropData?.accepts);
|
|
119
145
|
}
|
|
146
|
+
this.setState({ activeDragItem: null });
|
|
120
147
|
};
|
|
121
148
|
|
|
122
|
-
componentDidUpdate() {
|
|
123
|
-
if (this.rootRef) renderMath(this.rootRef);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
149
|
getPositionDirection = (choicePosition) => {
|
|
127
150
|
let flexDirection;
|
|
128
151
|
let justifyContent;
|
|
@@ -172,7 +195,11 @@ export default class DragInTheBlank extends React.Component {
|
|
|
172
195
|
const style = { display: 'flex', minWidth: '100px', ...this.getPositionDirection(choicePosition) };
|
|
173
196
|
|
|
174
197
|
return (
|
|
175
|
-
<DragProvider
|
|
198
|
+
<DragProvider
|
|
199
|
+
onDragStart={this.handleDragStart}
|
|
200
|
+
onDragEnd={this.handleDragEnd}
|
|
201
|
+
collisionDetection={closestCenter}
|
|
202
|
+
>
|
|
176
203
|
<div ref={(ref) => (this.rootRef = ref)} style={style}>
|
|
177
204
|
<Choices
|
|
178
205
|
choicePosition={choicePosition}
|
|
@@ -197,7 +224,11 @@ export default class DragInTheBlank extends React.Component {
|
|
|
197
224
|
emptyResponseAreaWidth={emptyResponseAreaWidth}
|
|
198
225
|
emptyResponseAreaHeight={emptyResponseAreaHeight}
|
|
199
226
|
instanceId={instanceId}
|
|
227
|
+
isDragging={!!this.state.activeDragItem}
|
|
200
228
|
/>
|
|
229
|
+
<DragOverlay style={{ pointerEvents: "none" }}>
|
|
230
|
+
{this.renderDragOverlay()}
|
|
231
|
+
</DragOverlay>
|
|
201
232
|
</div>
|
|
202
233
|
</DragProvider>
|
|
203
234
|
);
|
package/src/mask.jsx
CHANGED
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import get from 'lodash/get';
|
|
4
4
|
import { styled } from '@mui/material/styles';
|
|
5
|
+
import { renderMath } from '@pie-lib/math-rendering';
|
|
5
6
|
import { MARK_TAGS } from './serialization';
|
|
6
7
|
|
|
7
8
|
const Paragraph = styled('div')(({ theme }) => ({
|
|
@@ -131,6 +132,11 @@ const MaskContainer = styled('div')(() => ({
|
|
|
131
132
|
* Renders a layout that uses the slate.js Value model structure.
|
|
132
133
|
*/
|
|
133
134
|
export default class Mask extends React.Component {
|
|
135
|
+
constructor(props) {
|
|
136
|
+
super(props);
|
|
137
|
+
this.containerRef = React.createRef();
|
|
138
|
+
}
|
|
139
|
+
|
|
134
140
|
static propTypes = {
|
|
135
141
|
renderChildren: PropTypes.func,
|
|
136
142
|
layout: PropTypes.object,
|
|
@@ -139,6 +145,12 @@ export default class Mask extends React.Component {
|
|
|
139
145
|
elementType: PropTypes.string,
|
|
140
146
|
};
|
|
141
147
|
|
|
148
|
+
componentDidMount() {
|
|
149
|
+
if (this.containerRef.current && typeof renderMath === 'function') {
|
|
150
|
+
renderMath(this.containerRef.current);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
142
154
|
handleChange = (id, value) => {
|
|
143
155
|
const data = { ...this.props.value, [id]: value };
|
|
144
156
|
this.props.onChange(data);
|
|
@@ -148,6 +160,6 @@ export default class Mask extends React.Component {
|
|
|
148
160
|
const { value, layout, elementType } = this.props;
|
|
149
161
|
const children = renderChildren(layout, value, this.handleChange, this.props.renderChildren, null, elementType);
|
|
150
162
|
|
|
151
|
-
return <MaskContainer>{children}</MaskContainer>;
|
|
163
|
+
return <MaskContainer ref={this.containerRef}>{children}</MaskContainer>;
|
|
152
164
|
}
|
|
153
165
|
}
|