@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.
@@ -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', // Prevent line wrapping
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
- dragItem,
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 (JSON.stringify(choice) !== JSON.stringify(choice)) {
187
- if (!choice) {
188
- setDimensions({ height: 0, width: 0 });
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={true}
206
+ disabled={disabled}
202
207
  ref={rootRef}
203
208
  component="span"
204
209
  label={
205
210
  <React.Fragment>
206
211
  <StyledChipLabel
207
- className={classnames({
212
+ ref={spanRef}
213
+ draggable={true}
214
+ className={classnames({
208
215
  over: isOver,
209
216
  hidden: draggedLabel,
210
217
  })}
211
- ref={(ref) => {
212
- if (ref) {
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
- ref={(ref) => {
228
- if (ref) {
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
- if (draggedItem.instanceId === dropData.instanceId) {
97
- console.log('Instance IDs match');
98
-
99
- // Handle drop from choice to blank
100
- if (draggedItem.fromChoice === true) {
101
- console.log('Dropping from choice to blank:', targetId);
102
- const newValue = { ...value };
103
- newValue[targetId] = draggedItem.choice.id;
104
- onChange(newValue);
105
- }
106
- // Handle drop from blank to blank
107
- else if (draggedItem.id !== targetId) {
108
- console.log('Moving from blank to blank:', draggedItem.id, '->', targetId);
109
- const newValue = { ...value };
110
- newValue[targetId] = draggedItem.choice.id;
111
- delete newValue[draggedItem.id];
112
- onChange(newValue);
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 onDragEnd={this.handleDragEnd}>
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
  }