@pie-lib/drag 2.22.3-next.2 → 2.22.4-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 (51) hide show
  1. package/CHANGELOG.md +6 -76
  2. package/lib/__tests__/placeholder.test.js +140 -0
  3. package/lib/__tests__/uid-context.test.js +58 -0
  4. package/lib/drag-in-the-blank-dp.js +58 -27
  5. package/lib/drag-in-the-blank-dp.js.map +1 -1
  6. package/lib/drag-provider.js +61 -0
  7. package/lib/drag-provider.js.map +1 -0
  8. package/lib/drag-type.js +3 -4
  9. package/lib/drag-type.js.map +1 -1
  10. package/lib/draggable-choice.js +89 -0
  11. package/lib/draggable-choice.js.map +1 -0
  12. package/lib/droppable-placeholder.js +34 -66
  13. package/lib/droppable-placeholder.js.map +1 -1
  14. package/lib/ica-dp.js +27 -27
  15. package/lib/ica-dp.js.map +1 -1
  16. package/lib/index.js +8 -36
  17. package/lib/index.js.map +1 -1
  18. package/lib/match-list-dp.js +39 -27
  19. package/lib/match-list-dp.js.map +1 -1
  20. package/lib/placeholder.js +78 -100
  21. package/lib/placeholder.js.map +1 -1
  22. package/lib/preview-component.js +68 -108
  23. package/lib/preview-component.js.map +1 -1
  24. package/lib/swap.js +2 -8
  25. package/lib/swap.js.map +1 -1
  26. package/lib/uid-context.js +5 -19
  27. package/lib/uid-context.js.map +1 -1
  28. package/package.json +12 -18
  29. package/src/__tests__/placeholder.test.jsx +84 -25
  30. package/src/__tests__/uid-context.test.jsx +49 -16
  31. package/src/drag-in-the-blank-dp.jsx +62 -16
  32. package/src/drag-provider.jsx +50 -0
  33. package/src/drag-type.js +1 -1
  34. package/src/draggable-choice.jsx +88 -0
  35. package/src/droppable-placeholder.jsx +38 -28
  36. package/src/ica-dp.jsx +20 -18
  37. package/src/index.js +4 -8
  38. package/src/match-list-dp.jsx +41 -18
  39. package/src/placeholder.jsx +68 -72
  40. package/src/preview-component.jsx +62 -70
  41. package/esm/index.css +0 -847
  42. package/esm/index.js +0 -118265
  43. package/esm/index.js.map +0 -1
  44. package/esm/package.json +0 -3
  45. package/lib/choice.js +0 -129
  46. package/lib/choice.js.map +0 -1
  47. package/lib/with-drag-context.js +0 -59
  48. package/lib/with-drag-context.js.map +0 -1
  49. package/src/__tests__/__snapshots__/placeholder.test.jsx.snap +0 -68
  50. package/src/choice.jsx +0 -76
  51. package/src/with-drag-context.js +0 -32
package/src/ica-dp.jsx CHANGED
@@ -1,23 +1,25 @@
1
- import { DropTarget } from 'react-dnd';
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
2
3
  import { DroppablePlaceholder } from './droppable-placeholder';
3
- import dragType from './drag-type';
4
4
 
5
- export const spec = {
6
- canDrop: (props) => {
7
- return !props.disabled;
8
- },
9
- drop: (props, monitor) => {
10
- const item = monitor.getItem();
5
+ // With @dnd-kit, the drop logic is handled in the DragProvider's onDragEnd callback
6
+ // This component now just wraps DroppablePlaceholder with ICA specific logic
11
7
 
12
- if (props.onRemoveAnswer) {
13
- props.onRemoveAnswer(item);
14
- }
15
- },
16
- };
8
+ export function ICADroppable({ id, children, disabled, ...rest }) {
9
+ // The actual drop handling will be managed by the parent component
10
+ // through the DragProvider's onDragEnd callback
11
+
12
+ return (
13
+ <DroppablePlaceholder id={id} disabled={disabled} {...rest}>
14
+ {children}
15
+ </DroppablePlaceholder>
16
+ );
17
+ }
17
18
 
18
- const WithTarget = DropTarget(dragType.types.ica, spec, (connect, monitor) => ({
19
- connectDropTarget: connect.dropTarget(),
20
- isOver: monitor.isOver(),
21
- }))(DroppablePlaceholder);
19
+ ICADroppable.propTypes = {
20
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
21
+ children: PropTypes.node,
22
+ disabled: PropTypes.bool,
23
+ };
22
24
 
23
- export default WithTarget;
25
+ export default ICADroppable;
package/src/index.js CHANGED
@@ -1,8 +1,6 @@
1
- import { DragSource, DropTarget } from 'react-dnd';
2
-
3
1
  import PlaceHolder from './placeholder';
4
- import Choice from './choice';
5
- import withDragContext from './with-drag-context';
2
+ import DraggableChoice from './draggable-choice';
3
+ import DragProvider from './drag-provider';
6
4
  import swap from './swap';
7
5
  import * as uid from './uid-context';
8
6
  import MatchDroppablePlaceholder from './match-list-dp';
@@ -14,10 +12,8 @@ export {
14
12
  MatchDroppablePlaceholder,
15
13
  DragDroppablePlaceholder,
16
14
  ICADroppablePlaceholder,
17
- withDragContext,
18
- Choice,
15
+ DragProvider,
16
+ DraggableChoice,
19
17
  swap,
20
18
  uid,
21
- DragSource,
22
- DropTarget,
23
19
  };
@@ -1,23 +1,46 @@
1
- import { DropTarget } from 'react-dnd';
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
2
3
  import { DroppablePlaceholder } from './droppable-placeholder';
3
- import dragType from './drag-type';
4
4
 
5
- export const spec = {
6
- canDrop: (props) => {
7
- return !props.disabled;
8
- },
9
- drop: (props, monitor) => {
10
- const item = monitor.getItem();
5
+ // With @dnd-kit, the drop logic is handled in the DragProvider's onDragEnd callback
6
+ // This component now just wraps DroppablePlaceholder with match-list specific logic
11
7
 
12
- if (props.onRemoveAnswer) {
13
- props.onRemoveAnswer(item.promptId);
14
- }
15
- },
16
- };
8
+ export function MatchListDroppable({
9
+ id,
10
+ children,
11
+ disabled,
12
+ onRemoveAnswer,
13
+ ...rest
14
+ }) {
15
+ // The actual drop handling will be managed by the parent component
16
+ // through the DragProvider's onDragEnd callback
17
+ // The onRemoveAnswer logic should be handled in the parent's onDragEnd:
18
+ //
19
+ // const handleDragEnd = (event) => {
20
+ // if (event.over && event.active) {
21
+ // const item = event.active.data.current;
22
+ // if (onRemoveAnswer) {
23
+ // onRemoveAnswer(item.promptId);
24
+ // }
25
+ // }
26
+ // };
27
+
28
+ return (
29
+ <DroppablePlaceholder
30
+ id={id}
31
+ disabled={disabled}
32
+ {...rest}
33
+ >
34
+ {children}
35
+ </DroppablePlaceholder>
36
+ );
37
+ }
17
38
 
18
- const WithTarget = DropTarget(dragType.types.ml, spec, (connect, monitor) => ({
19
- connectDropTarget: connect.dropTarget(),
20
- isOver: monitor.isOver(),
21
- }))(DroppablePlaceholder);
39
+ MatchListDroppable.propTypes = {
40
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
41
+ children: PropTypes.node,
42
+ disabled: PropTypes.bool,
43
+ onRemoveAnswer: PropTypes.func,
44
+ };
22
45
 
23
- export default WithTarget;
46
+ export default MatchListDroppable;
@@ -1,14 +1,68 @@
1
1
  import React from 'react';
2
- import { withStyles } from '@material-ui/core/styles';
2
+ import { styled } from '@mui/material/styles';
3
3
  import classNames from 'classnames';
4
4
  import PropTypes from 'prop-types';
5
- import grey from '@material-ui/core/colors/grey';
6
5
  import { color } from '@pie-lib/render-ui';
6
+ import { grey } from '@mui/material/colors';
7
+
8
+ const StyledPlaceholder = styled('div')(({ theme }) => ({
9
+ '&.placeholder': {
10
+ WebkitTouchCallout: 'none',
11
+ WebkitUserSelect: 'none',
12
+ KhtmlUserSelect: 'none',
13
+ MozUserSelect: 'none',
14
+ MsUserSelect: 'none',
15
+ userSelect: 'none',
16
+ width: '100%',
17
+ height: '100%',
18
+ background: color.white(),
19
+ transition: 'background-color 200ms linear, border-color 200ms linear',
20
+ boxSizing: 'border-box',
21
+ display: 'grid',
22
+ gridRowGap: `${theme.spacing(1)}px`,
23
+ gridColumnGap: `${theme.spacing(1)}px`,
24
+ padding: theme.spacing(1),
25
+ border: `2px dashed ${color.black()}`,
26
+ },
27
+ '&.disabled': {
28
+ boxShadow: 'none',
29
+ background: theme.palette.background.paper,
30
+ },
31
+ '&.over': {
32
+ border: `1px solid ${grey[500]}`,
33
+ backgroundColor: `${grey[300]}`,
34
+ },
35
+ '&.board': {
36
+ padding: theme.spacing(1),
37
+ display: 'flex',
38
+ flexWrap: 'wrap',
39
+ alignItems: 'center',
40
+ minHeight: '100px',
41
+ justifyContent: 'center',
42
+ overflow: 'hidden',
43
+ touchAction: 'none',
44
+ backgroundColor: color.backgroundDark(),
45
+ },
46
+ '&.categorizeBoard': {
47
+ padding: theme.spacing(0.5),
48
+ display: 'flex',
49
+ flexWrap: 'wrap',
50
+ alignItems: 'center',
51
+ minHeight: '100px',
52
+ justifyContent: 'center',
53
+ overflow: 'hidden',
54
+ touchAction: 'none',
55
+ backgroundColor: color.backgroundDark(),
56
+ },
57
+ '&.verticalPool': {
58
+ display: 'flex',
59
+ flexFlow: 'column wrap',
60
+ },
61
+ }));
7
62
 
8
63
  export const PlaceHolder = (props) => {
9
64
  const {
10
65
  children,
11
- classes,
12
66
  className,
13
67
  isOver,
14
68
  type,
@@ -18,13 +72,14 @@ export const PlaceHolder = (props) => {
18
72
  isCategorize,
19
73
  isVerticalPool,
20
74
  minHeight,
75
+ extraStyles
21
76
  } = props;
22
77
 
23
78
  const names = classNames(
24
- classes.placeholder,
25
- disabled && classes.disabled,
26
- isOver && classes.over,
27
- classes[type],
79
+ 'placeholder',
80
+ disabled && 'disabled',
81
+ isOver && 'over',
82
+ type,
28
83
  className,
29
84
  );
30
85
 
@@ -49,24 +104,22 @@ export const PlaceHolder = (props) => {
49
104
  style.background = color.backgroundDark();
50
105
  }
51
106
 
52
- const boardStyle = isCategorize ? classes.categorizeBoard : classes.board;
107
+ const boardStyle = isCategorize ? 'categorizeBoard' : 'board';
53
108
 
54
109
  return (
55
- <div
56
- style={{ ...style, minHeight: minHeight }}
110
+ <StyledPlaceholder
111
+ style={{ ...style, minHeight: minHeight, ...extraStyles }}
57
112
  className={classNames(
58
- classes.noSelectStyles,
59
113
  choiceBoard ? boardStyle : names,
60
- isVerticalPool && classes.verticalPool,
114
+ isVerticalPool && 'verticalPool',
61
115
  )}
62
116
  >
63
117
  {children}
64
- </div>
118
+ </StyledPlaceholder>
65
119
  );
66
120
  };
67
121
 
68
122
  PlaceHolder.propTypes = {
69
- classes: PropTypes.object.isRequired,
70
123
  choiceBoard: PropTypes.bool,
71
124
  grid: PropTypes.shape({
72
125
  columns: PropTypes.number,
@@ -85,61 +138,4 @@ PlaceHolder.propTypes = {
85
138
  minHeight: PropTypes.number,
86
139
  };
87
140
 
88
- const styles = (theme) => ({
89
- noSelectStyles: {
90
- WebkitTouchCallout: 'none',
91
- WebkitUserSelect: 'none',
92
- KhtmlUserSelect: 'none',
93
- MozUserSelect: 'none',
94
- MsUserSelect: 'none',
95
- userSelect: 'none',
96
- },
97
- placeholder: {
98
- width: '100%',
99
- height: '100%',
100
- background: color.white(),
101
- transition: 'background-color 200ms linear, border-color 200ms linear',
102
- boxSizing: 'border-box',
103
- display: 'grid',
104
- gridRowGap: `${theme.spacing.unit}px`,
105
- gridColumnGap: `${theme.spacing.unit}px`,
106
- padding: theme.spacing.unit * 1,
107
- border: `2px dashed ${color.black()}`,
108
- },
109
- disabled: {
110
- boxShadow: 'none',
111
- background: theme.palette.background.paper,
112
- },
113
- over: {
114
- border: `1px solid ${grey[500]}`,
115
- backgroundColor: `${grey[300]}`,
116
- },
117
- board: {
118
- padding: theme.spacing.unit,
119
- display: 'flex',
120
- flexWrap: 'wrap',
121
- alignItems: 'center',
122
- minHeight: '100px',
123
- justifyContent: 'center',
124
- overflow: 'hidden',
125
- touchAction: 'none',
126
- backgroundColor: color.backgroundDark(),
127
- },
128
- categorizeBoard: {
129
- padding: theme.spacing.unit / 2,
130
- display: 'flex',
131
- flexWrap: 'wrap',
132
- alignItems: 'center',
133
- minHeight: '100px',
134
- justifyContent: 'center',
135
- overflow: 'hidden',
136
- touchAction: 'none',
137
- backgroundColor: color.backgroundDark(),
138
- },
139
- verticalPool: {
140
- display: 'flex',
141
- flexFlow: 'column wrap',
142
- },
143
- });
144
-
145
- export default withStyles(styles)(PlaceHolder);
141
+ export default PlaceHolder;
@@ -1,5 +1,5 @@
1
- import React, { useEffect, useRef, useState, useCallback } from 'react';
2
- import { usePreview } from 'react-dnd-multi-backend';
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { DragOverlay, useDndContext } from '@dnd-kit/core';
3
3
  import { PreviewPrompt, color } from '@pie-lib/render-ui';
4
4
  import { renderMath } from '@pie-lib/math-rendering';
5
5
 
@@ -54,99 +54,91 @@ const styles = {
54
54
  },
55
55
  };
56
56
 
57
- const getPrompt = (itemType, item) => {
58
- switch (itemType) {
59
- // DRAG-IN-THE-BLANK
57
+ const getPrompt = (dragData) => {
58
+ if (!dragData) return undefined;
59
+
60
+ // Handle different drag data structures based on the component type
61
+ if (dragData.choiceId) {
62
+ // DraggableChoice format
63
+ return dragData.value;
64
+ }
65
+
66
+ // Legacy format support
67
+ switch (dragData.itemType) {
60
68
  case 'MaskBlank':
61
- return item?.choice?.value;
62
- // IMAGE-CLOZE-ASSOCIATION
63
- case 'react-dnd-response':
64
- return item?.value;
65
- // MATCH-LIST
69
+ return dragData.choice?.value;
70
+ case 'dnd-kit-response':
71
+ return dragData.value;
66
72
  case 'Answer':
67
- return item?.value;
68
- // PLACEMENT-ORDERING
73
+ return dragData.value;
69
74
  case 'Tile':
70
- return item?.value;
75
+ return dragData.value;
76
+ case 'categorize':
77
+ return dragData.value;
71
78
  default:
72
- return item?.itemType === 'categorize' ? item?.value : undefined;
79
+ return dragData.value;
73
80
  }
74
81
  };
75
82
 
76
- const getCustomStyle = (itemType, item, touchPosition, style) => {
77
- const transform = `translate(${touchPosition.x}px, ${touchPosition.y}px)`;
78
- const top = style?.top || 0;
79
- const left = style?.left || 0;
80
- const position = style?.position || 'fixed';
83
+ const getCustomStyle = (dragData) => {
84
+ if (!dragData) return {};
81
85
 
82
- return {
83
- position,
84
- top,
85
- left,
86
- transform,
87
- ...(itemType === 'MaskBlank' ? styles.maskBlank : {}),
88
- ...(item?.itemType === 'categorize' ? styles.categorize : {}),
89
- ...(itemType === 'Answer' ? styles.matchList : {}),
90
- ...(itemType === 'Tile' ? styles.placementOrdering : {}),
91
- ...(itemType === 'react-dnd-response' ? styles.ica : {}),
86
+ const baseStyle = {
87
+ cursor: 'grabbing',
88
+ opacity: 0.8,
89
+ transform: 'rotate(5deg)', // Slight rotation for visual feedback
92
90
  };
91
+
92
+ // Apply specific styles based on item type
93
+ if (dragData.itemType === 'MaskBlank') {
94
+ return { ...baseStyle, ...styles.maskBlank };
95
+ }
96
+ if (dragData.itemType === 'categorize') {
97
+ return { ...baseStyle, ...styles.categorize };
98
+ }
99
+ if (dragData.itemType === 'Answer') {
100
+ return { ...baseStyle, ...styles.matchList };
101
+ }
102
+ if (dragData.itemType === 'Tile') {
103
+ return { ...baseStyle, ...styles.placementOrdering };
104
+ }
105
+ if (dragData.itemType === 'dnd-kit-response') {
106
+ return { ...baseStyle, ...styles.ica };
107
+ }
108
+
109
+ // Default style for choice items
110
+ return { ...baseStyle, ...styles.categorize };
93
111
  };
94
112
 
95
113
  const PreviewComponent = () => {
96
- const preview = usePreview();
97
- const { itemType, item, style, display } = preview;
98
- const [touchPosition, setTouchPosition] = useState({ x: 0, y: 0 });
114
+ const { active } = useDndContext();
99
115
  const [zoomLevel, setZoomLevel] = useState(1);
100
-
101
- const handleTouchMove = useCallback(
102
- (event) => {
103
- if (event.touches.length > 0) {
104
- const touch = event.touches[0];
105
- const touchOffset = 1;
106
- setTouchPosition({
107
- x: (touch.clientX + touchOffset) / zoomLevel,
108
- y: (touch.clientY + touchOffset) / zoomLevel,
109
- });
110
- }
111
- },
112
- [zoomLevel],
113
- );
114
-
115
116
  const root = useRef(null);
116
117
 
118
+ const dragData = active?.data?.current;
119
+ const isActive = !!active;
120
+
117
121
  useEffect(() => {
118
- if (display && root.current) {
122
+ if (isActive && root.current) {
119
123
  renderMath(root.current);
120
124
 
121
125
  // Adjusted for precise zoom level calculation in Online Testing, targeting the specific class pattern .asmt-zoomable.asmt-zoom-NR .asmt-question .padding
122
126
  const zoomAffectedElement = document.querySelector('.padding') || document.body;
123
-
124
127
  setZoomLevel(parseFloat(getComputedStyle(zoomAffectedElement).zoom) || 1);
125
128
  }
126
- }, [display, item?.choice?.value, item?.value, itemType, item]);
127
-
128
- useEffect(() => {
129
- const touchMoveListener = (event) => handleTouchMove(event);
130
- if (display) {
131
- window.addEventListener('touchmove', touchMoveListener);
132
- }
133
- return () => {
134
- window.removeEventListener('touchmove', touchMoveListener);
135
- };
136
- }, [display, handleTouchMove]);
137
-
138
- if (!display) {
139
- return null;
140
- }
141
-
142
- const customStyle = getCustomStyle(itemType, item, touchPosition, style);
129
+ }, [isActive, dragData]);
143
130
 
144
- const prompt = getPrompt(itemType, item);
131
+ const customStyle = getCustomStyle(dragData);
132
+ const prompt = getPrompt(dragData);
145
133
 
146
134
  return (
147
- <div ref={root} style={customStyle}>
148
- <PreviewPrompt className="label" prompt={prompt} tagName="span" />
149
- </div>
135
+ <DragOverlay>
136
+ {isActive && prompt && (
137
+ <div ref={root} style={customStyle}>
138
+ <PreviewPrompt className="label" prompt={prompt} tagName="span" />
139
+ </div>
140
+ )}
141
+ </DragOverlay>
150
142
  );
151
143
  };
152
144