@pie-lib/drag 4.0.3-next.38 → 4.0.3-next.61
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.json +1 -0
- package/CHANGELOG.md +802 -0
- package/LICENSE.md +5 -0
- package/lib/drag-in-the-blank-dp.js +66 -0
- package/lib/drag-in-the-blank-dp.js.map +1 -0
- package/lib/drag-provider.js +61 -0
- package/lib/drag-provider.js.map +1 -0
- package/lib/drag-type.js +14 -0
- package/lib/drag-type.js.map +1 -0
- package/lib/draggable-choice.js +95 -0
- package/lib/draggable-choice.js.map +1 -0
- package/lib/droppable-placeholder.js +50 -0
- package/lib/droppable-placeholder.js.map +1 -0
- package/lib/ica-dp.js +37 -0
- package/lib/ica-dp.js.map +1 -0
- package/lib/index.js +61 -0
- package/lib/index.js.map +1 -0
- package/lib/match-list-dp.js +49 -0
- package/lib/match-list-dp.js.map +1 -0
- package/lib/placeholder.js +131 -0
- package/lib/placeholder.js.map +1 -0
- package/lib/preview-component.js +154 -0
- package/lib/preview-component.js.map +1 -0
- package/lib/swap.js +18 -0
- package/lib/swap.js.map +1 -0
- package/lib/uid-context.js +26 -0
- package/lib/uid-context.js.map +1 -0
- package/package.json +16 -34
- package/src/__tests__/drag-provider.test.jsx +313 -0
- package/src/__tests__/placeholder.test.jsx +107 -0
- package/src/__tests__/preview-component.test.jsx +537 -0
- package/src/__tests__/swap.test.js +161 -0
- package/src/__tests__/uid-context.test.jsx +54 -0
- package/src/drag-in-the-blank-dp.jsx +65 -0
- package/src/drag-provider.jsx +50 -0
- package/src/drag-type.js +7 -0
- package/src/draggable-choice.jsx +83 -0
- package/src/droppable-placeholder.jsx +41 -0
- package/src/ica-dp.jsx +25 -0
- package/src/index.js +19 -0
- package/src/match-list-dp.jsx +36 -0
- package/src/placeholder.jsx +138 -0
- package/src/preview-component.jsx +145 -0
- package/src/swap.js +14 -0
- package/src/uid-context.js +13 -0
- package/dist/_virtual/_rolldown/runtime.js +0 -11
- package/dist/drag-in-the-blank-dp.d.ts +0 -29
- package/dist/drag-in-the-blank-dp.js +0 -44
- package/dist/drag-provider.d.ts +0 -29
- package/dist/drag-provider.js +0 -31
- package/dist/drag-type.d.ts +0 -16
- package/dist/draggable-choice.d.ts +0 -43
- package/dist/draggable-choice.js +0 -63
- package/dist/droppable-placeholder.d.ts +0 -29
- package/dist/droppable-placeholder.js +0 -36
- package/dist/ica-dp.d.ts +0 -24
- package/dist/ica-dp.js +0 -20
- package/dist/index.d.ts +0 -17
- package/dist/index.js +0 -9
- package/dist/match-list-dp.d.ts +0 -26
- package/dist/match-list-dp.js +0 -21
- package/dist/node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.js +0 -16
- package/dist/placeholder.d.ts +0 -31
- package/dist/placeholder.js +0 -98
- package/dist/preview-component.d.ts +0 -11
- package/dist/swap.d.ts +0 -10
- package/dist/swap.js +0 -9
- package/dist/uid-context.d.ts +0 -13
- package/dist/uid-context.js +0 -15
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { Provider as UidProvider, withUid } from '../uid-context';
|
|
4
|
+
|
|
5
|
+
describe('uid-context', () => {
|
|
6
|
+
describe('withUid', () => {
|
|
7
|
+
it('provides uid to wrapped component', () => {
|
|
8
|
+
const TestComponent = ({ uid }) => <div data-testid="test-uid">{uid}</div>;
|
|
9
|
+
const WrappedComponent = withUid(TestComponent);
|
|
10
|
+
|
|
11
|
+
render(
|
|
12
|
+
<UidProvider value="test-uid-123">
|
|
13
|
+
<WrappedComponent />
|
|
14
|
+
</UidProvider>,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
expect(screen.getByTestId('test-uid')).toHaveTextContent('test-uid-123');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('passes through other props to wrapped component', () => {
|
|
21
|
+
const TestComponent = ({ uid, customProp }) => (
|
|
22
|
+
<div>
|
|
23
|
+
<span data-testid="uid">{uid}</span>
|
|
24
|
+
<span data-testid="custom">{customProp}</span>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
const WrappedComponent = withUid(TestComponent);
|
|
28
|
+
|
|
29
|
+
render(
|
|
30
|
+
<UidProvider value="test-uid">
|
|
31
|
+
<WrappedComponent customProp="custom-value" />
|
|
32
|
+
</UidProvider>,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
expect(screen.getByTestId('uid')).toHaveTextContent('test-uid');
|
|
36
|
+
expect(screen.getByTestId('custom')).toHaveTextContent('custom-value');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('UidProvider', () => {
|
|
41
|
+
it('provides uid context to children', () => {
|
|
42
|
+
const TestComponent = ({ uid }) => <div data-testid="uid-value">{uid}</div>;
|
|
43
|
+
const WrappedComponent = withUid(TestComponent);
|
|
44
|
+
|
|
45
|
+
render(
|
|
46
|
+
<UidProvider value="provider-uid">
|
|
47
|
+
<WrappedComponent />
|
|
48
|
+
</UidProvider>,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(screen.getByTestId('uid-value')).toHaveTextContent('provider-uid');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import PlaceHolder from './placeholder';
|
|
4
|
+
import { useDroppable } from '@dnd-kit/core';
|
|
5
|
+
import { styled } from '@mui/material/styles';
|
|
6
|
+
|
|
7
|
+
// With @dnd-kit, the drop logic is handled in the DragProvider's onDragEnd callback
|
|
8
|
+
// This component now just wraps DroppablePlaceholder with drag-in-the-blank specific logic
|
|
9
|
+
|
|
10
|
+
const DroppablePlaceholderContainer = styled('div')({
|
|
11
|
+
minHeight: '100px',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export function DragInTheBlankDroppable({
|
|
15
|
+
children,
|
|
16
|
+
disabled,
|
|
17
|
+
classes,
|
|
18
|
+
isVerticalPool,
|
|
19
|
+
minHeight,
|
|
20
|
+
instanceId,
|
|
21
|
+
...rest
|
|
22
|
+
}) {
|
|
23
|
+
// The actual drop handling will be managed by the parent component
|
|
24
|
+
// through the DragProvider's onDragEnd callback
|
|
25
|
+
const { setNodeRef, isOver } = useDroppable({
|
|
26
|
+
id: 'drag-in-the-blank-droppable',
|
|
27
|
+
data: {
|
|
28
|
+
type: 'MaskBlank',
|
|
29
|
+
accepts: ['MaskBlank'],
|
|
30
|
+
id: 'drag-in-the-blank-droppable',
|
|
31
|
+
toChoiceBoard: true,
|
|
32
|
+
instanceId,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div ref={setNodeRef}>
|
|
38
|
+
<DroppablePlaceholderContainer>
|
|
39
|
+
<PlaceHolder
|
|
40
|
+
isOver={isOver}
|
|
41
|
+
choiceBoard={true}
|
|
42
|
+
className={classes}
|
|
43
|
+
isVerticalPool={isVerticalPool}
|
|
44
|
+
extraStyles={{
|
|
45
|
+
width: '100%',
|
|
46
|
+
minHeight: minHeight || 100,
|
|
47
|
+
height: 'auto',
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
</PlaceHolder>
|
|
52
|
+
</DroppablePlaceholderContainer>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
DragInTheBlankDroppable.propTypes = {
|
|
58
|
+
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
|
59
|
+
children: PropTypes.node,
|
|
60
|
+
disabled: PropTypes.bool,
|
|
61
|
+
onDrop: PropTypes.func,
|
|
62
|
+
instanceId: PropTypes.string,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default DragInTheBlankDroppable;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
|
4
|
+
|
|
5
|
+
export function DragProvider({ children, onDragEnd, onDragStart, collisionDetection, modifiers, autoScroll }) {
|
|
6
|
+
const [activeId, setActiveId] = useState(null);
|
|
7
|
+
|
|
8
|
+
const sensors = useSensors(
|
|
9
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
|
|
10
|
+
useSensor(KeyboardSensor),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
const handleDragStart = (event) => {
|
|
14
|
+
setActiveId(event.active.id);
|
|
15
|
+
if (onDragStart) {
|
|
16
|
+
onDragStart(event);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const handleDragEnd = (event) => {
|
|
21
|
+
setActiveId(null);
|
|
22
|
+
if (onDragEnd) {
|
|
23
|
+
onDragEnd(event);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<DndContext
|
|
29
|
+
sensors={sensors}
|
|
30
|
+
onDragStart={handleDragStart}
|
|
31
|
+
onDragEnd={handleDragEnd}
|
|
32
|
+
collisionDetection={collisionDetection}
|
|
33
|
+
modifiers={modifiers}
|
|
34
|
+
autoScroll={autoScroll}
|
|
35
|
+
>
|
|
36
|
+
{children}
|
|
37
|
+
</DndContext>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
DragProvider.propTypes = {
|
|
42
|
+
children: PropTypes.node.isRequired,
|
|
43
|
+
onDragEnd: PropTypes.func,
|
|
44
|
+
onDragStart: PropTypes.func,
|
|
45
|
+
collisionDetection: PropTypes.func,
|
|
46
|
+
modifiers: PropTypes.arrayOf(PropTypes.func),
|
|
47
|
+
autoScroll: PropTypes.object,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default DragProvider;
|
package/src/drag-type.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { styled } from '@mui/material/styles';
|
|
4
|
+
import { useDraggable } from '@dnd-kit/core';
|
|
5
|
+
import { grey } from '@mui/material/colors';
|
|
6
|
+
|
|
7
|
+
export const DRAG_TYPE = 'CHOICE';
|
|
8
|
+
|
|
9
|
+
const StyledChoice = styled('div')(({ theme }) => ({
|
|
10
|
+
backgroundColor: theme.palette.background.paper,
|
|
11
|
+
border: `solid 1px ${grey[400]}`,
|
|
12
|
+
padding: theme.spacing(1),
|
|
13
|
+
minHeight: '30px',
|
|
14
|
+
minWidth: theme.spacing(20),
|
|
15
|
+
maxWidth: theme.spacing(75),
|
|
16
|
+
cursor: 'grab',
|
|
17
|
+
'& p': {
|
|
18
|
+
margin: 0,
|
|
19
|
+
},
|
|
20
|
+
'& p *': {
|
|
21
|
+
margin: 0,
|
|
22
|
+
},
|
|
23
|
+
'&:active': {
|
|
24
|
+
cursor: 'grabbing',
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
export function DraggableChoice({
|
|
29
|
+
choice,
|
|
30
|
+
children,
|
|
31
|
+
className,
|
|
32
|
+
disabled,
|
|
33
|
+
category,
|
|
34
|
+
alternateResponseIndex,
|
|
35
|
+
choiceIndex,
|
|
36
|
+
onRemoveChoice,
|
|
37
|
+
type,
|
|
38
|
+
id,
|
|
39
|
+
}) {
|
|
40
|
+
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
|
41
|
+
id: id || choice.id, // id to be used for dnd-kit
|
|
42
|
+
disabled: disabled,
|
|
43
|
+
categoryId: category?.id,
|
|
44
|
+
alternateResponseIndex,
|
|
45
|
+
data: {
|
|
46
|
+
id: choice.id,
|
|
47
|
+
value: choice.value,
|
|
48
|
+
choiceId: choice.id,
|
|
49
|
+
from: category?.id,
|
|
50
|
+
categoryId: category?.id,
|
|
51
|
+
alternateResponseIndex,
|
|
52
|
+
choiceIndex,
|
|
53
|
+
onRemoveChoice,
|
|
54
|
+
type,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<StyledChoice ref={setNodeRef} className={className} isDragging={isDragging} {...attributes} {...listeners}>
|
|
60
|
+
{children}
|
|
61
|
+
</StyledChoice>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
DraggableChoice.propTypes = {
|
|
66
|
+
choice: PropTypes.shape({
|
|
67
|
+
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
|
68
|
+
value: PropTypes.any,
|
|
69
|
+
}).isRequired,
|
|
70
|
+
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
|
|
71
|
+
className: PropTypes.string,
|
|
72
|
+
disabled: PropTypes.bool,
|
|
73
|
+
category: PropTypes.shape({
|
|
74
|
+
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
75
|
+
}),
|
|
76
|
+
alternateResponseIndex: PropTypes.number,
|
|
77
|
+
choiceIndex: PropTypes.number,
|
|
78
|
+
onRemoveChoice: PropTypes.func,
|
|
79
|
+
type: PropTypes.string,
|
|
80
|
+
id: PropTypes.string,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export default DraggableChoice;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PlaceHolder from './placeholder';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import { useDroppable } from '@dnd-kit/core';
|
|
5
|
+
|
|
6
|
+
const preventInteractionStyle = {
|
|
7
|
+
flex: 1,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function DroppablePlaceholder({ id, children, disabled, classes, isVerticalPool, minHeight }) {
|
|
11
|
+
const { setNodeRef, isOver } = useDroppable({
|
|
12
|
+
id,
|
|
13
|
+
disabled,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div ref={setNodeRef} style={preventInteractionStyle}>
|
|
18
|
+
<PlaceHolder
|
|
19
|
+
disabled={disabled}
|
|
20
|
+
isOver={isOver}
|
|
21
|
+
choiceBoard={true}
|
|
22
|
+
className={classes}
|
|
23
|
+
isVerticalPool={isVerticalPool}
|
|
24
|
+
minHeight={minHeight}
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
</PlaceHolder>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
DroppablePlaceholder.propTypes = {
|
|
33
|
+
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
|
34
|
+
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
|
|
35
|
+
disabled: PropTypes.bool,
|
|
36
|
+
classes: PropTypes.object,
|
|
37
|
+
isVerticalPool: PropTypes.bool,
|
|
38
|
+
minHeight: PropTypes.number,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default DroppablePlaceholder;
|
package/src/ica-dp.jsx
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { DroppablePlaceholder } from './droppable-placeholder';
|
|
4
|
+
|
|
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
|
|
7
|
+
|
|
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
|
+
}
|
|
18
|
+
|
|
19
|
+
ICADroppable.propTypes = {
|
|
20
|
+
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
|
21
|
+
children: PropTypes.node,
|
|
22
|
+
disabled: PropTypes.bool,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default ICADroppable;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import PlaceHolder from './placeholder';
|
|
2
|
+
import DraggableChoice from './draggable-choice';
|
|
3
|
+
import DragProvider from './drag-provider';
|
|
4
|
+
import swap from './swap';
|
|
5
|
+
import * as uid from './uid-context';
|
|
6
|
+
import MatchDroppablePlaceholder from './match-list-dp';
|
|
7
|
+
import DragDroppablePlaceholder from './drag-in-the-blank-dp';
|
|
8
|
+
import ICADroppablePlaceholder from './ica-dp';
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
PlaceHolder,
|
|
12
|
+
MatchDroppablePlaceholder,
|
|
13
|
+
DragDroppablePlaceholder,
|
|
14
|
+
ICADroppablePlaceholder,
|
|
15
|
+
DragProvider,
|
|
16
|
+
DraggableChoice,
|
|
17
|
+
swap,
|
|
18
|
+
uid,
|
|
19
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { DroppablePlaceholder } from './droppable-placeholder';
|
|
4
|
+
|
|
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
|
|
7
|
+
|
|
8
|
+
export function MatchListDroppable({ id, children, disabled, onRemoveAnswer, ...rest }) {
|
|
9
|
+
// The actual drop handling will be managed by the parent component
|
|
10
|
+
// through the DragProvider's onDragEnd callback
|
|
11
|
+
// The onRemoveAnswer logic should be handled in the parent's onDragEnd:
|
|
12
|
+
//
|
|
13
|
+
// const handleDragEnd = (event) => {
|
|
14
|
+
// if (event.over && event.active) {
|
|
15
|
+
// const item = event.active.data.current;
|
|
16
|
+
// if (onRemoveAnswer) {
|
|
17
|
+
// onRemoveAnswer(item.promptId);
|
|
18
|
+
// }
|
|
19
|
+
// }
|
|
20
|
+
// };
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<DroppablePlaceholder id={id} disabled={disabled} {...rest}>
|
|
24
|
+
{children}
|
|
25
|
+
</DroppablePlaceholder>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
MatchListDroppable.propTypes = {
|
|
30
|
+
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
|
31
|
+
children: PropTypes.node,
|
|
32
|
+
disabled: PropTypes.bool,
|
|
33
|
+
onRemoveAnswer: PropTypes.func,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default MatchListDroppable;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { styled } from '@mui/material/styles';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
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),
|
|
23
|
+
gridColumnGap: theme.spacing(1),
|
|
24
|
+
padding: theme.spacing(1),
|
|
25
|
+
border: `2px dashed ${color.black()}`,
|
|
26
|
+
},
|
|
27
|
+
'&.placeholderDisabled': {
|
|
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
|
+
}));
|
|
62
|
+
|
|
63
|
+
export const PlaceHolder = (props) => {
|
|
64
|
+
const {
|
|
65
|
+
children,
|
|
66
|
+
className,
|
|
67
|
+
isOver,
|
|
68
|
+
type,
|
|
69
|
+
grid,
|
|
70
|
+
disabled,
|
|
71
|
+
choiceBoard,
|
|
72
|
+
isCategorize,
|
|
73
|
+
isVerticalPool,
|
|
74
|
+
minHeight,
|
|
75
|
+
extraStyles,
|
|
76
|
+
} = props;
|
|
77
|
+
|
|
78
|
+
const names = classNames(
|
|
79
|
+
'placeholder',
|
|
80
|
+
disabled && 'placeholderDisabled',
|
|
81
|
+
isOver && 'over',
|
|
82
|
+
type,
|
|
83
|
+
className,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const style = {};
|
|
87
|
+
|
|
88
|
+
if (grid && grid.columns) {
|
|
89
|
+
style.gridTemplateColumns = `repeat(${grid.columns}, 1fr)`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (grid && grid.rows) {
|
|
93
|
+
const repeatValue = grid.rowsRepeatValue || '1fr';
|
|
94
|
+
|
|
95
|
+
style.gridTemplateRows = `repeat(${grid.rows}, ${repeatValue})`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The "type" is only sent through placement-ordering / placeholder
|
|
99
|
+
// It can be "choice" or "target"
|
|
100
|
+
// We apply a different style for the "choice" type
|
|
101
|
+
// For any other type, use a dashed black border and a white fill
|
|
102
|
+
if (type === 'choice') {
|
|
103
|
+
style.border = `1px solid ${color.borderLight()}`;
|
|
104
|
+
style.background = color.backgroundDark();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const boardStyle = isCategorize ? 'categorizeBoard' : 'board';
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<StyledPlaceholder
|
|
111
|
+
style={{ ...style, minHeight: minHeight, ...extraStyles }}
|
|
112
|
+
className={classNames(choiceBoard ? boardStyle : names, isVerticalPool && 'verticalPool')}
|
|
113
|
+
>
|
|
114
|
+
{children}
|
|
115
|
+
</StyledPlaceholder>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
PlaceHolder.propTypes = {
|
|
120
|
+
choiceBoard: PropTypes.bool,
|
|
121
|
+
grid: PropTypes.shape({
|
|
122
|
+
columns: PropTypes.number,
|
|
123
|
+
rows: PropTypes.number,
|
|
124
|
+
// if a different value then 1fr is wanted
|
|
125
|
+
rowsRepeatValue: PropTypes.string,
|
|
126
|
+
}),
|
|
127
|
+
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
|
|
128
|
+
className: PropTypes.string,
|
|
129
|
+
isOver: PropTypes.bool,
|
|
130
|
+
index: PropTypes.number,
|
|
131
|
+
type: PropTypes.string,
|
|
132
|
+
disabled: PropTypes.bool,
|
|
133
|
+
isCategorize: PropTypes.bool,
|
|
134
|
+
isVerticalPool: PropTypes.bool,
|
|
135
|
+
minHeight: PropTypes.number,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export default PlaceHolder;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { DragOverlay, useDndContext } from '@dnd-kit/core';
|
|
3
|
+
import { color, PreviewPrompt } from '@pie-lib/render-ui';
|
|
4
|
+
import { renderMath } from '@pie-lib/math-rendering';
|
|
5
|
+
|
|
6
|
+
const styles = {
|
|
7
|
+
maskBlank: {
|
|
8
|
+
// this style is applied only on small screens and for touch devices when dragging, for drag-in-the-blank.
|
|
9
|
+
// It is styled to be identical to the drag-in-the-blank chip
|
|
10
|
+
backgroundColor: color.white(),
|
|
11
|
+
border: `1px solid ${color.text()}`,
|
|
12
|
+
color: color.text(),
|
|
13
|
+
alignItems: 'center',
|
|
14
|
+
display: 'inline-flex',
|
|
15
|
+
height: 'initial',
|
|
16
|
+
minHeight: '32px',
|
|
17
|
+
fontSize: 'inherit',
|
|
18
|
+
whiteSpace: 'pre-wrap',
|
|
19
|
+
maxWidth: '374px',
|
|
20
|
+
borderRadius: '3px',
|
|
21
|
+
padding: '12px',
|
|
22
|
+
},
|
|
23
|
+
ica: {
|
|
24
|
+
backgroundColor: color.background(),
|
|
25
|
+
border: `1px solid ${color.borderDark()}`,
|
|
26
|
+
display: 'flex',
|
|
27
|
+
alignItems: 'center',
|
|
28
|
+
justifyContent: 'center',
|
|
29
|
+
minHeight: '28px',
|
|
30
|
+
padding: '0 3px',
|
|
31
|
+
marginLeft: 2,
|
|
32
|
+
marginTop: 2,
|
|
33
|
+
width: 'fit-content',
|
|
34
|
+
},
|
|
35
|
+
categorize: {
|
|
36
|
+
color: color.text(),
|
|
37
|
+
backgroundColor: color.background(),
|
|
38
|
+
padding: '16px',
|
|
39
|
+
borderRadius: '4px',
|
|
40
|
+
border: '1px solid',
|
|
41
|
+
},
|
|
42
|
+
matchList: {
|
|
43
|
+
color: color.text(),
|
|
44
|
+
backgroundColor: color.background(),
|
|
45
|
+
padding: '10px',
|
|
46
|
+
boxSizing: 'border-box',
|
|
47
|
+
border: '1px solid #D1D1D1',
|
|
48
|
+
},
|
|
49
|
+
placementOrdering: {
|
|
50
|
+
padding: '10px',
|
|
51
|
+
boxSizing: 'border-box',
|
|
52
|
+
border: '1px solid #D1D1D1',
|
|
53
|
+
backgroundColor: color.background(),
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
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) {
|
|
68
|
+
case 'MaskBlank':
|
|
69
|
+
return dragData.choice?.value;
|
|
70
|
+
case 'dnd-kit-response':
|
|
71
|
+
return dragData.value;
|
|
72
|
+
case 'Answer':
|
|
73
|
+
return dragData.value;
|
|
74
|
+
case 'Tile':
|
|
75
|
+
return dragData.value;
|
|
76
|
+
case 'categorize':
|
|
77
|
+
return dragData.value;
|
|
78
|
+
default:
|
|
79
|
+
return dragData.value;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const getCustomStyle = (dragData) => {
|
|
84
|
+
if (!dragData) return {};
|
|
85
|
+
|
|
86
|
+
const baseStyle = {
|
|
87
|
+
cursor: 'grabbing',
|
|
88
|
+
opacity: 0.8,
|
|
89
|
+
transform: 'rotate(5deg)', // Slight rotation for visual feedback
|
|
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 };
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const PreviewComponent = () => {
|
|
114
|
+
const { active } = useDndContext();
|
|
115
|
+
const [zoomLevel, setZoomLevel] = useState(1);
|
|
116
|
+
const root = useRef(null);
|
|
117
|
+
|
|
118
|
+
const dragData = active?.data?.current;
|
|
119
|
+
const isActive = !!active;
|
|
120
|
+
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (isActive && root.current) {
|
|
123
|
+
renderMath(root.current);
|
|
124
|
+
|
|
125
|
+
// Adjusted for precise zoom level calculation in Online Testing, targeting the specific class pattern .asmt-zoomable.asmt-zoom-NR .asmt-question .padding
|
|
126
|
+
const zoomAffectedElement = document.querySelector('.padding') || document.body;
|
|
127
|
+
setZoomLevel(parseFloat(getComputedStyle(zoomAffectedElement).zoom) || 1);
|
|
128
|
+
}
|
|
129
|
+
}, [isActive, dragData]);
|
|
130
|
+
|
|
131
|
+
const customStyle = getCustomStyle(dragData);
|
|
132
|
+
const prompt = getPrompt(dragData);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<DragOverlay>
|
|
136
|
+
{isActive && prompt && (
|
|
137
|
+
<div ref={root} style={customStyle}>
|
|
138
|
+
<PreviewPrompt className="prompt-label" prompt={prompt} tagName="span" />
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
</DragOverlay>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export default PreviewComponent;
|
package/src/swap.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { cloneDeep } from 'lodash-es';
|
|
2
|
+
|
|
3
|
+
export default (arr, fromIndex, toIndex) => {
|
|
4
|
+
if (!arr || arr.length <= 1 || fromIndex === undefined || toIndex === undefined) {
|
|
5
|
+
throw new Error(`swap requires a non-empty array, fromIndex, toIndex: ${arr}, ${fromIndex} ${toIndex}`);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const update = cloneDeep(arr);
|
|
9
|
+
const tmp = arr[toIndex];
|
|
10
|
+
update[toIndex] = update[fromIndex];
|
|
11
|
+
update[fromIndex] = tmp;
|
|
12
|
+
|
|
13
|
+
return update;
|
|
14
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const { Consumer, Provider } = React.createContext(-1);
|
|
4
|
+
|
|
5
|
+
export { Provider, Consumer };
|
|
6
|
+
|
|
7
|
+
export const generateId = () => (Math.random() * 1000001).toFixed(0);
|
|
8
|
+
|
|
9
|
+
export const withUid = (Component) => {
|
|
10
|
+
const Wrapped = (props) => <Consumer>{(uid) => <Component {...props} uid={uid} />}</Consumer>;
|
|
11
|
+
|
|
12
|
+
return Wrapped;
|
|
13
|
+
};
|