@pie-element/match-list 7.2.2-beta.2 → 7.2.2
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 +41 -24
- package/controller/CHANGELOG.md +6 -38
- package/controller/package.json +1 -1
- package/lib/answer-area.js +46 -28
- package/lib/answer-area.js.map +1 -1
- package/lib/answer.js +159 -29
- package/lib/answer.js.map +1 -1
- package/lib/arrow.js +8 -3
- package/lib/arrow.js.map +1 -1
- package/lib/choices-list.js +13 -4
- package/lib/choices-list.js.map +1 -1
- package/lib/droppable-placeholder.js +12 -1
- package/lib/droppable-placeholder.js.map +1 -1
- package/lib/keyboard-coordinates.js +154 -0
- package/lib/keyboard-coordinates.js.map +1 -0
- package/lib/main.js +240 -22
- package/lib/main.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.closestDroppableKeyboardCoordinates = void 0;
|
|
7
|
+
var _core = require("@dnd-kit/core");
|
|
8
|
+
const ARROW_STEP = 25;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Custom keyboard coordinate getter for non-sortable drag-and-drop.
|
|
12
|
+
*
|
|
13
|
+
* Tab/Shift+Tab cycle the dragged item directly onto the next/previous droppable
|
|
14
|
+
* (response area or the choices pool), in DOM order, placing the dragged item's own
|
|
15
|
+
* top-left corner at the target's center-left point. Arrow keys are left doing plain
|
|
16
|
+
* free-form movement (nudging the dragged item by a fixed step, same as dnd-kit's own
|
|
17
|
+
* default keyboard behavior) rather than jumping between droppables — collision
|
|
18
|
+
* detection (rectIntersection) still picks up whatever the item ends up actually
|
|
19
|
+
* overlapping.
|
|
20
|
+
*/
|
|
21
|
+
const closestDroppableKeyboardCoordinates = (event, {
|
|
22
|
+
active,
|
|
23
|
+
context,
|
|
24
|
+
currentCoordinates
|
|
25
|
+
}) => {
|
|
26
|
+
const {
|
|
27
|
+
code
|
|
28
|
+
} = event;
|
|
29
|
+
const isTab = code === 'Tab';
|
|
30
|
+
const isArrow = code === _core.KeyboardCode.Down || code === _core.KeyboardCode.Up || code === _core.KeyboardCode.Left || code === _core.KeyboardCode.Right;
|
|
31
|
+
if (!isTab && !isArrow) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
if (isArrow) {
|
|
36
|
+
switch (code) {
|
|
37
|
+
case _core.KeyboardCode.Down:
|
|
38
|
+
return {
|
|
39
|
+
...currentCoordinates,
|
|
40
|
+
y: currentCoordinates.y + ARROW_STEP
|
|
41
|
+
};
|
|
42
|
+
case _core.KeyboardCode.Up:
|
|
43
|
+
return {
|
|
44
|
+
...currentCoordinates,
|
|
45
|
+
y: currentCoordinates.y - ARROW_STEP
|
|
46
|
+
};
|
|
47
|
+
case _core.KeyboardCode.Right:
|
|
48
|
+
return {
|
|
49
|
+
...currentCoordinates,
|
|
50
|
+
x: currentCoordinates.x + ARROW_STEP
|
|
51
|
+
};
|
|
52
|
+
case _core.KeyboardCode.Left:
|
|
53
|
+
return {
|
|
54
|
+
...currentCoordinates,
|
|
55
|
+
x: currentCoordinates.x - ARROW_STEP
|
|
56
|
+
};
|
|
57
|
+
default:
|
|
58
|
+
return currentCoordinates;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const {
|
|
62
|
+
droppableRects,
|
|
63
|
+
droppableContainers,
|
|
64
|
+
collisionRect
|
|
65
|
+
} = context;
|
|
66
|
+
if (!droppableRects || droppableRects.size === 0) {
|
|
67
|
+
return currentCoordinates;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// A placed answer ("target") is itself a droppable for its own prompt slot
|
|
71
|
+
// ("drop-{promptId}"). That self drop-zone must never be treated as a navigable
|
|
72
|
+
// target: it sits under the dragged item, so a tiny (even sub-pixel) discrepancy
|
|
73
|
+
// between its measured rect and the dragged item's own rect is enough to make it
|
|
74
|
+
// register as the "closest" candidate, which reads as the drag being stuck.
|
|
75
|
+
// Exclude it outright rather than relying on a distance threshold.
|
|
76
|
+
const activeData = active?.data?.current;
|
|
77
|
+
const ownDropId = activeData?.promptId != null ? `drop-${activeData.promptId}` : undefined;
|
|
78
|
+
|
|
79
|
+
// Collect rect, top-left and center of all enabled droppable containers
|
|
80
|
+
const targets = [];
|
|
81
|
+
for (const [id, container] of droppableContainers) {
|
|
82
|
+
if (container?.disabled) continue;
|
|
83
|
+
if (id === ownDropId) continue;
|
|
84
|
+
const rect = droppableRects.get(id);
|
|
85
|
+
if (!rect) continue;
|
|
86
|
+
const center = {
|
|
87
|
+
x: rect.left + rect.width / 2,
|
|
88
|
+
y: rect.top + rect.height / 2
|
|
89
|
+
};
|
|
90
|
+
// Land the dragged item's own top-left corner at the target's center-left point,
|
|
91
|
+
// rather than at the target's own top-left corner.
|
|
92
|
+
const dropPosition = {
|
|
93
|
+
x: rect.left,
|
|
94
|
+
y: rect.top + rect.height / 2
|
|
95
|
+
};
|
|
96
|
+
targets.push({
|
|
97
|
+
id,
|
|
98
|
+
rect,
|
|
99
|
+
dropPosition,
|
|
100
|
+
center
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (targets.length === 0) {
|
|
104
|
+
return currentCoordinates;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Tab/Shift+Tab: cycle through targets in DOM order (sorted top-to-bottom, left-to-right)
|
|
108
|
+
const reverse = event.shiftKey;
|
|
109
|
+
|
|
110
|
+
// Sort targets by position (top to bottom, then left to right)
|
|
111
|
+
targets.sort((a, b) => {
|
|
112
|
+
if (Math.abs(a.center.y - b.center.y) > 10) return a.center.y - b.center.y;
|
|
113
|
+
return a.center.x - b.center.x;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Find the current target. Whichever target the dragged item's own center point
|
|
117
|
+
// actually falls inside of is the current target — this is what "currently sitting
|
|
118
|
+
// on a target" means, and it holds regardless of how the dragged item's size compares
|
|
119
|
+
// to the target's: a compact answer tile landed (per `dropPosition` above) at a wide
|
|
120
|
+
// response row's left edge is still contained within that row's rect.
|
|
121
|
+
//
|
|
122
|
+
// Comparing distances between the dragged item's *center* and each target's *center*
|
|
123
|
+
// (as this used to) breaks down for exactly that shape (a small tile in a much wider
|
|
124
|
+
// row): the dragged item's own center sits well short of the row's true center, by
|
|
125
|
+
// roughly half the width difference — enough that a completely different droppable
|
|
126
|
+
// (e.g. the choices pool) can end up "closer", so once the item is actually sitting on
|
|
127
|
+
// a target, the next press re-matches the position as if it were still somewhere
|
|
128
|
+
// else, and keeps recomputing the same move instead of advancing.
|
|
129
|
+
const draggedCenter = collisionRect ? {
|
|
130
|
+
x: collisionRect.left + collisionRect.width / 2,
|
|
131
|
+
y: collisionRect.top + collisionRect.height / 2
|
|
132
|
+
} : currentCoordinates;
|
|
133
|
+
let currentIndex = targets.findIndex(t => draggedCenter.x >= t.rect.left && draggedCenter.x <= t.rect.right && draggedCenter.y >= t.rect.top && draggedCenter.y <= t.rect.bottom);
|
|
134
|
+
|
|
135
|
+
// Fall back to nearest-by-dropPosition if the dragged item's center isn't strictly
|
|
136
|
+
// inside any target (e.g. mid-flight after a free arrow-key move).
|
|
137
|
+
if (currentIndex === -1) {
|
|
138
|
+
let minDist = Infinity;
|
|
139
|
+
for (let i = 0; i < targets.length; i++) {
|
|
140
|
+
const dist = distance(currentCoordinates, targets[i].dropPosition);
|
|
141
|
+
if (dist < minDist) {
|
|
142
|
+
minDist = dist;
|
|
143
|
+
currentIndex = i;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Move to next/previous
|
|
149
|
+
const nextIndex = reverse ? (currentIndex - 1 + targets.length) % targets.length : (currentIndex + 1) % targets.length;
|
|
150
|
+
return targets[nextIndex].dropPosition;
|
|
151
|
+
};
|
|
152
|
+
exports.closestDroppableKeyboardCoordinates = closestDroppableKeyboardCoordinates;
|
|
153
|
+
const distance = (a, b) => Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
|
|
154
|
+
//# sourceMappingURL=keyboard-coordinates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard-coordinates.js","names":["_core","require","ARROW_STEP","closestDroppableKeyboardCoordinates","event","active","context","currentCoordinates","code","isTab","isArrow","KeyboardCode","Down","Up","Left","Right","undefined","preventDefault","y","x","droppableRects","droppableContainers","collisionRect","size","activeData","data","current","ownDropId","promptId","targets","id","container","disabled","rect","get","center","left","width","top","height","dropPosition","push","length","reverse","shiftKey","sort","a","b","Math","abs","draggedCenter","currentIndex","findIndex","t","right","bottom","minDist","Infinity","i","dist","distance","nextIndex","exports","sqrt"],"sources":["../src/keyboard-coordinates.js"],"sourcesContent":["import { KeyboardCode } from '@dnd-kit/core';\n\nconst ARROW_STEP = 25;\n\n/**\n * Custom keyboard coordinate getter for non-sortable drag-and-drop.\n *\n * Tab/Shift+Tab cycle the dragged item directly onto the next/previous droppable\n * (response area or the choices pool), in DOM order, placing the dragged item's own\n * top-left corner at the target's center-left point. Arrow keys are left doing plain\n * free-form movement (nudging the dragged item by a fixed step, same as dnd-kit's own\n * default keyboard behavior) rather than jumping between droppables — collision\n * detection (rectIntersection) still picks up whatever the item ends up actually\n * overlapping.\n */\nexport const closestDroppableKeyboardCoordinates = (event, { active, context, currentCoordinates }) => {\n const { code } = event;\n const isTab = code === 'Tab';\n const isArrow =\n code === KeyboardCode.Down || code === KeyboardCode.Up || code === KeyboardCode.Left || code === KeyboardCode.Right;\n\n if (!isTab && !isArrow) {\n return undefined;\n }\n\n event.preventDefault();\n\n if (isArrow) {\n switch (code) {\n case KeyboardCode.Down:\n return { ...currentCoordinates, y: currentCoordinates.y + ARROW_STEP };\n\n case KeyboardCode.Up:\n return { ...currentCoordinates, y: currentCoordinates.y - ARROW_STEP };\n\n case KeyboardCode.Right:\n return { ...currentCoordinates, x: currentCoordinates.x + ARROW_STEP };\n\n case KeyboardCode.Left:\n return { ...currentCoordinates, x: currentCoordinates.x - ARROW_STEP };\n\n default:\n return currentCoordinates;\n }\n }\n\n const { droppableRects, droppableContainers, collisionRect } = context;\n\n if (!droppableRects || droppableRects.size === 0) {\n return currentCoordinates;\n }\n\n // A placed answer (\"target\") is itself a droppable for its own prompt slot\n // (\"drop-{promptId}\"). That self drop-zone must never be treated as a navigable\n // target: it sits under the dragged item, so a tiny (even sub-pixel) discrepancy\n // between its measured rect and the dragged item's own rect is enough to make it\n // register as the \"closest\" candidate, which reads as the drag being stuck.\n // Exclude it outright rather than relying on a distance threshold.\n const activeData = active?.data?.current;\n const ownDropId = activeData?.promptId != null ? `drop-${activeData.promptId}` : undefined;\n\n // Collect rect, top-left and center of all enabled droppable containers\n const targets = [];\n\n for (const [id, container] of droppableContainers) {\n if (container?.disabled) continue;\n\n if (id === ownDropId) continue;\n\n const rect = droppableRects.get(id);\n\n if (!rect) continue;\n\n const center = {\n x: rect.left + rect.width / 2,\n y: rect.top + rect.height / 2,\n };\n // Land the dragged item's own top-left corner at the target's center-left point,\n // rather than at the target's own top-left corner.\n const dropPosition = {\n x: rect.left,\n y: rect.top + rect.height / 2,\n };\n\n targets.push({ id, rect, dropPosition, center });\n }\n\n if (targets.length === 0) {\n return currentCoordinates;\n }\n\n // Tab/Shift+Tab: cycle through targets in DOM order (sorted top-to-bottom, left-to-right)\n const reverse = event.shiftKey;\n\n // Sort targets by position (top to bottom, then left to right)\n targets.sort((a, b) => {\n if (Math.abs(a.center.y - b.center.y) > 10) return a.center.y - b.center.y;\n return a.center.x - b.center.x;\n });\n\n // Find the current target. Whichever target the dragged item's own center point\n // actually falls inside of is the current target — this is what \"currently sitting\n // on a target\" means, and it holds regardless of how the dragged item's size compares\n // to the target's: a compact answer tile landed (per `dropPosition` above) at a wide\n // response row's left edge is still contained within that row's rect.\n //\n // Comparing distances between the dragged item's *center* and each target's *center*\n // (as this used to) breaks down for exactly that shape (a small tile in a much wider\n // row): the dragged item's own center sits well short of the row's true center, by\n // roughly half the width difference — enough that a completely different droppable\n // (e.g. the choices pool) can end up \"closer\", so once the item is actually sitting on\n // a target, the next press re-matches the position as if it were still somewhere\n // else, and keeps recomputing the same move instead of advancing.\n const draggedCenter = collisionRect\n ? { x: collisionRect.left + collisionRect.width / 2, y: collisionRect.top + collisionRect.height / 2 }\n : currentCoordinates;\n\n let currentIndex = targets.findIndex(\n (t) => draggedCenter.x >= t.rect.left && draggedCenter.x <= t.rect.right && draggedCenter.y >= t.rect.top && draggedCenter.y <= t.rect.bottom,\n );\n\n // Fall back to nearest-by-dropPosition if the dragged item's center isn't strictly\n // inside any target (e.g. mid-flight after a free arrow-key move).\n if (currentIndex === -1) {\n let minDist = Infinity;\n\n for (let i = 0; i < targets.length; i++) {\n const dist = distance(currentCoordinates, targets[i].dropPosition);\n\n if (dist < minDist) {\n minDist = dist;\n currentIndex = i;\n }\n }\n }\n\n // Move to next/previous\n const nextIndex = reverse\n ? (currentIndex - 1 + targets.length) % targets.length\n : (currentIndex + 1) % targets.length;\n\n return targets[nextIndex].dropPosition;\n};\n\nconst distance = (a, b) => Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);\n"],"mappings":"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","ignoreList":[]}
|
package/lib/main.js
CHANGED
|
@@ -11,6 +11,7 @@ var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
|
11
11
|
var _drag = require("@pie-lib/drag");
|
|
12
12
|
var _core = require("@dnd-kit/core");
|
|
13
13
|
var _modifiers = require("@dnd-kit/modifiers");
|
|
14
|
+
var _keyboardCoordinates = require("./keyboard-coordinates");
|
|
14
15
|
var _correctAnswerToggle = _interopRequireDefault(require("@pie-lib/correct-answer-toggle"));
|
|
15
16
|
var _renderUi = require("@pie-lib/render-ui");
|
|
16
17
|
var _styles = require("@mui/material/styles");
|
|
@@ -18,6 +19,39 @@ var _lodashEs = require("lodash-es");
|
|
|
18
19
|
var _answerArea = _interopRequireDefault(require("./answer-area"));
|
|
19
20
|
var _choicesList = _interopRequireDefault(require("./choices-list"));
|
|
20
21
|
var _answer = require("./answer");
|
|
22
|
+
// A click that lands right after a real drag gesture ends (pointer drag-and-drop, or
|
|
23
|
+
// the browser's own synthetic click for a keyboard Space/Enter) must be ignored by the
|
|
24
|
+
// new click-to-select/click-to-place handlers below, or it would immediately reopen or
|
|
25
|
+
// re-trigger a selection for a drag that just completed.
|
|
26
|
+
const CLICK_AFTER_DRAG_GUARD_MS = 250;
|
|
27
|
+
const sensors = [
|
|
28
|
+
// Without an activationConstraint, dnd-kit's PointerSensor calls its internal
|
|
29
|
+
// handleStart() synchronously on pointerdown, before any movement — meaning a plain
|
|
30
|
+
// click is itself "activated" as a drag. Once activated, dnd-kit adds a capture-phase
|
|
31
|
+
// document click listener that calls stopPropagation() (to suppress the native
|
|
32
|
+
// "ghost click" a real drag leaves behind), which also swallows the click for a
|
|
33
|
+
// gesture with zero movement, before it ever reaches our own onClick handlers below.
|
|
34
|
+
// Requiring 8px of movement (matching @pie-lib/drag's DragProvider convention used
|
|
35
|
+
// elsewhere in this codebase) defers activation until an actual drag gesture is
|
|
36
|
+
// underway, so a plain click passes through untouched.
|
|
37
|
+
{
|
|
38
|
+
sensor: _core.PointerSensor,
|
|
39
|
+
options: {
|
|
40
|
+
activationConstraint: {
|
|
41
|
+
distance: 8
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}, {
|
|
45
|
+
sensor: _core.KeyboardSensor,
|
|
46
|
+
options: {
|
|
47
|
+
coordinateGetter: _keyboardCoordinates.closestDroppableKeyboardCoordinates,
|
|
48
|
+
keyboardCodes: {
|
|
49
|
+
start: [_core.KeyboardCode.Space, _core.KeyboardCode.Enter],
|
|
50
|
+
cancel: [_core.KeyboardCode.Esc],
|
|
51
|
+
end: [_core.KeyboardCode.Space, _core.KeyboardCode.Enter]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}];
|
|
21
55
|
const MainContainer = (0, _styles.styled)('div')({
|
|
22
56
|
display: 'flex',
|
|
23
57
|
flexDirection: 'column',
|
|
@@ -25,6 +59,19 @@ const MainContainer = (0, _styles.styled)('div')({
|
|
|
25
59
|
color: _renderUi.color.text(),
|
|
26
60
|
backgroundColor: _renderUi.color.background()
|
|
27
61
|
});
|
|
62
|
+
const InteractiveRegion = (0, _styles.styled)('div')({
|
|
63
|
+
width: '100%',
|
|
64
|
+
overflowX: 'auto',
|
|
65
|
+
overflowY: 'hidden'
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// A block child of a scroll port is sized to the scroll port, so it has to opt out explicitly for
|
|
69
|
+
// the content to be able to overflow. min-content keeps the rows and the pool the same width.
|
|
70
|
+
const InteractiveRegionContent = (0, _styles.styled)('div')({
|
|
71
|
+
display: 'flex',
|
|
72
|
+
flexDirection: 'column',
|
|
73
|
+
minWidth: 'min-content'
|
|
74
|
+
});
|
|
28
75
|
class Main extends _react.default.Component {
|
|
29
76
|
constructor(props) {
|
|
30
77
|
super(props);
|
|
@@ -51,22 +98,19 @@ class Main extends _react.default.Component {
|
|
|
51
98
|
rect
|
|
52
99
|
}
|
|
53
100
|
});
|
|
101
|
+
this.selectAnswer(active.data.current);
|
|
54
102
|
}
|
|
55
103
|
});
|
|
56
|
-
(0, _defineProperty2.default)(this, "
|
|
104
|
+
(0, _defineProperty2.default)(this, "onDragCancel", () => {
|
|
57
105
|
this.setState({
|
|
58
106
|
draggingElement: null
|
|
59
107
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
const activeData = active.data.current;
|
|
68
|
-
const overData = over?.data.current;
|
|
69
|
-
if (!activeData) {
|
|
108
|
+
this.cancelSelection();
|
|
109
|
+
this.lastDragEndAt = Date.now();
|
|
110
|
+
});
|
|
111
|
+
// Pure placement logic
|
|
112
|
+
(0, _defineProperty2.default)(this, "placeAnswer", (activeData, overData) => {
|
|
113
|
+
if (!activeData || !overData) {
|
|
70
114
|
return;
|
|
71
115
|
}
|
|
72
116
|
const {
|
|
@@ -93,9 +137,9 @@ class Main extends _react.default.Component {
|
|
|
93
137
|
const sourcePromptId = activeData.promptId;
|
|
94
138
|
|
|
95
139
|
// Handle dropping onto a drop zone
|
|
96
|
-
if (overData
|
|
140
|
+
if (overData.type === 'drop-zone' && overData.promptId != null) {
|
|
97
141
|
const targetPromptId = overData.promptId;
|
|
98
|
-
if (activeData.type === 'choice' &&
|
|
142
|
+
if (activeData.type === 'choice' && targetPromptId !== undefined) {
|
|
99
143
|
// check if this choice is already placed somewhere
|
|
100
144
|
const existingPlacement = (0, _lodashEs.findKey)(session.value, val => val === answerId);
|
|
101
145
|
if (existingPlacement && !duplicates) {
|
|
@@ -112,12 +156,20 @@ class Main extends _react.default.Component {
|
|
|
112
156
|
if (sourcePromptId !== targetPromptId) {
|
|
113
157
|
const targetHasItem = session.value[targetPromptId] != null;
|
|
114
158
|
if (targetHasItem && !duplicates) {
|
|
115
|
-
//
|
|
159
|
+
// Without duplicates each choice is placed at most once, so trading the two
|
|
160
|
+
// answers is the only way to fill the target without dropping the answer it
|
|
161
|
+
// already holds.
|
|
116
162
|
const temp = session.value[targetPromptId];
|
|
117
163
|
session.value[targetPromptId] = answerId;
|
|
118
164
|
session.value[sourcePromptId] = temp;
|
|
119
|
-
} else
|
|
120
|
-
//
|
|
165
|
+
} else {
|
|
166
|
+
// Empty target, or duplicates allowed: the moved answer takes the target and
|
|
167
|
+
// the source area is vacated. Any answer displaced from the target is simply
|
|
168
|
+
// available in the pool again — which with duplicates enabled always lists
|
|
169
|
+
// every choice anyway — matching how the choice -> occupied target path above
|
|
170
|
+
// already behaves. Leaving this case unhandled (as it was) made moving a
|
|
171
|
+
// placed answer onto an occupied area a silent no-op whenever duplicates
|
|
172
|
+
// were enabled.
|
|
121
173
|
session.value[targetPromptId] = answerId;
|
|
122
174
|
delete session.value[sourcePromptId];
|
|
123
175
|
}
|
|
@@ -126,6 +178,88 @@ class Main extends _react.default.Component {
|
|
|
126
178
|
onSessionChange(session);
|
|
127
179
|
}
|
|
128
180
|
});
|
|
181
|
+
(0, _defineProperty2.default)(this, "onPlaceAnswer", event => {
|
|
182
|
+
this.setState({
|
|
183
|
+
draggingElement: null
|
|
184
|
+
});
|
|
185
|
+
const {
|
|
186
|
+
active,
|
|
187
|
+
over
|
|
188
|
+
} = event;
|
|
189
|
+
if (!active) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const activeData = active.data.current;
|
|
193
|
+
const overData = over?.data.current;
|
|
194
|
+
if (!activeData) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
this.placeAnswer(activeData, overData);
|
|
198
|
+
this.cancelSelection();
|
|
199
|
+
this.lastDragEndAt = Date.now();
|
|
200
|
+
});
|
|
201
|
+
(0, _defineProperty2.default)(this, "isSameAnswer", (a, b) => !!a && !!b && a.type === b.type && a.id === b.id && a.promptId === b.promptId);
|
|
202
|
+
// Unconditionally selects (used by the drag-start mirror, and internally when
|
|
203
|
+
// switching from one choice to another).
|
|
204
|
+
(0, _defineProperty2.default)(this, "selectAnswer", data => {
|
|
205
|
+
this.setState({
|
|
206
|
+
selectedAnswer: data
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
// Click-to-select semantics: selecting the currently-selected answer again clears
|
|
210
|
+
// the selection instead of re-selecting it.
|
|
211
|
+
(0, _defineProperty2.default)(this, "toggleAnswerSelection", data => {
|
|
212
|
+
this.setState(state => ({
|
|
213
|
+
selectedAnswer: this.isSameAnswer(state.selectedAnswer, data) ? null : data
|
|
214
|
+
}));
|
|
215
|
+
});
|
|
216
|
+
(0, _defineProperty2.default)(this, "cancelSelection", () => {
|
|
217
|
+
this.setState({
|
|
218
|
+
selectedAnswer: null
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
// If a real dnd-kit drag (started via keyboard Space/Enter) is still live when a
|
|
222
|
+
// click completes the placement below, it needs to be cleanly ended — otherwise
|
|
223
|
+
// dnd-kit would still think a drag is in progress (still listening for Tab/arrow/
|
|
224
|
+
// Space/Escape, still showing the drag overlay) for a placement the click already
|
|
225
|
+
// performed. Escape is already configured as this sensor's cancel key, and
|
|
226
|
+
// dispatching it as a real DOM KeyboardEvent is how dnd-kit's own document-level
|
|
227
|
+
// listener is reached from outside its sensor. onDragCancel is intentionally not
|
|
228
|
+
// wired to redo any placement — it only resets local UI state — so this is safe to
|
|
229
|
+
// call unconditionally, including when no drag is actually live (dnd-kit simply has
|
|
230
|
+
// no listener attached in that case, and the dispatch is a no-op).
|
|
231
|
+
(0, _defineProperty2.default)(this, "endAnyLiveKeyboardDrag", () => {
|
|
232
|
+
document.dispatchEvent(new KeyboardEvent('keydown', {
|
|
233
|
+
code: 'Escape',
|
|
234
|
+
bubbles: true,
|
|
235
|
+
cancelable: true
|
|
236
|
+
}));
|
|
237
|
+
});
|
|
238
|
+
(0, _defineProperty2.default)(this, "placeSelectedAnswer", overData => {
|
|
239
|
+
const {
|
|
240
|
+
selectedAnswer
|
|
241
|
+
} = this.state;
|
|
242
|
+
if (!selectedAnswer) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
this.placeAnswer(selectedAnswer, overData);
|
|
246
|
+
this.cancelSelection();
|
|
247
|
+
this.endAnyLiveKeyboardDrag();
|
|
248
|
+
this.lastDragEndAt = Date.now();
|
|
249
|
+
});
|
|
250
|
+
(0, _defineProperty2.default)(this, "isClickSoonAfterDragEnd", () => Date.now() - this.lastDragEndAt < CLICK_AFTER_DRAG_GUARD_MS);
|
|
251
|
+
(0, _defineProperty2.default)(this, "onChoiceClick", data => {
|
|
252
|
+
if (this.isClickSoonAfterDragEnd()) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
this.toggleAnswerSelection(data);
|
|
256
|
+
});
|
|
257
|
+
(0, _defineProperty2.default)(this, "onPlacementClick", overData => {
|
|
258
|
+
if (this.isClickSoonAfterDragEnd()) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
this.placeSelectedAnswer(overData);
|
|
262
|
+
});
|
|
129
263
|
(0, _defineProperty2.default)(this, "toggleShowCorrect", () => {
|
|
130
264
|
this.setState({
|
|
131
265
|
showCorrectAnswer: !this.state.showCorrectAnswer
|
|
@@ -151,8 +285,10 @@ class Main extends _react.default.Component {
|
|
|
151
285
|
this.instanceId = (0, _lodashEs.uniqueId)();
|
|
152
286
|
this.state = {
|
|
153
287
|
showCorrectAnswer: false,
|
|
154
|
-
draggingElement: null
|
|
288
|
+
draggingElement: null,
|
|
289
|
+
selectedAnswer: null
|
|
155
290
|
};
|
|
291
|
+
this.lastDragEndAt = 0;
|
|
156
292
|
}
|
|
157
293
|
onRemoveAnswer(id) {
|
|
158
294
|
const {
|
|
@@ -178,10 +314,86 @@ class Main extends _react.default.Component {
|
|
|
178
314
|
prompt,
|
|
179
315
|
language
|
|
180
316
|
} = config;
|
|
317
|
+
|
|
318
|
+
// Helpers for accessible announcements
|
|
319
|
+
const getChoiceLabel = answerId => {
|
|
320
|
+
const answer = config.answers.find(a => String(a.id) === String(answerId));
|
|
321
|
+
if (answer?.title) {
|
|
322
|
+
// Strip HTML tags for screen reader
|
|
323
|
+
const text = answer.title.replace(/<[^>]*>/g, '').trim();
|
|
324
|
+
return text || `Answer ${answerId}`;
|
|
325
|
+
}
|
|
326
|
+
return `Answer ${answerId}`;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Read the dragged choice off the tile's own drag data rather than parsing it back
|
|
330
|
+
// out of the drag id: a drag id identifies the tile (a response area, for a placed
|
|
331
|
+
// answer — see buildDragId in ./answer), not the choice the tile holds.
|
|
332
|
+
const getDraggedLabel = active => getChoiceLabel(active?.data?.current?.id);
|
|
333
|
+
const getDropTargetLabel = over => {
|
|
334
|
+
const overData = over?.data?.current;
|
|
335
|
+
if (overData?.type === 'choices-pool' || over?.id === 'choices-pool') {
|
|
336
|
+
return {
|
|
337
|
+
label: 'Choices list',
|
|
338
|
+
choiceId: null
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Prefer the droppable's own data; fall back to the id for a droppable registered
|
|
343
|
+
// without any (the id shape is "drop-{promptId}").
|
|
344
|
+
const promptId = overData?.promptId != null ? overData.promptId : String(over?.id).replace(/^drop-/, '');
|
|
345
|
+
const promptItem = config.prompts.find(p => String(p.id) === String(promptId));
|
|
346
|
+
const label = promptItem?.title ? `Response area for ${promptItem.title.replace(/<[^>]*>/g, '').trim()}` : `Response area ${promptId}`;
|
|
347
|
+
const choiceId = session.value?.[promptId];
|
|
348
|
+
return {
|
|
349
|
+
label,
|
|
350
|
+
choiceId: choiceId != null ? choiceId : null
|
|
351
|
+
};
|
|
352
|
+
};
|
|
353
|
+
const announcements = {
|
|
354
|
+
onDragStart({
|
|
355
|
+
active
|
|
356
|
+
}) {
|
|
357
|
+
return `Picked up ${getDraggedLabel(active)}. Use Tab to move between response areas, then press Space or Enter to drop.`;
|
|
358
|
+
},
|
|
359
|
+
onDragOver({
|
|
360
|
+
active,
|
|
361
|
+
over
|
|
362
|
+
}) {
|
|
363
|
+
if (!over) {
|
|
364
|
+
return `${getDraggedLabel(active)} is not over a response area.`;
|
|
365
|
+
}
|
|
366
|
+
const target = getDropTargetLabel(over);
|
|
367
|
+
const content = target.choiceId ? `Currently contains ${getChoiceLabel(target.choiceId)}.` : 'Currently empty.';
|
|
368
|
+
return `Over ${target.label}. ${content}`;
|
|
369
|
+
},
|
|
370
|
+
onDragEnd({
|
|
371
|
+
active,
|
|
372
|
+
over
|
|
373
|
+
}) {
|
|
374
|
+
if (!over) {
|
|
375
|
+
return `${getDraggedLabel(active)} was returned to its original position.`;
|
|
376
|
+
}
|
|
377
|
+
return `Dropped ${getDraggedLabel(active)} in ${getDropTargetLabel(over).label}.`;
|
|
378
|
+
},
|
|
379
|
+
onDragCancel({
|
|
380
|
+
active
|
|
381
|
+
}) {
|
|
382
|
+
return `Cancelled. ${getDraggedLabel(active)} was returned to its original position.`;
|
|
383
|
+
}
|
|
384
|
+
};
|
|
181
385
|
return /*#__PURE__*/_react.default.createElement(_core.DndContext, {
|
|
386
|
+
sensors: sensors,
|
|
387
|
+
collisionDetection: _core.rectIntersection,
|
|
182
388
|
onDragStart: this.onDragStart,
|
|
183
389
|
onDragEnd: this.onPlaceAnswer,
|
|
184
|
-
|
|
390
|
+
onDragCancel: this.onDragCancel,
|
|
391
|
+
modifiers: [_modifiers.restrictToFirstScrollableAncestor],
|
|
392
|
+
accessibility: {
|
|
393
|
+
screenReaderInstructions: {
|
|
394
|
+
draggable: 'Press Space or Enter to pick up this answer choice. Once picked up, use Tab or Shift+Tab to cycle through response areas, or use arrow keys to move it freely. Press Space or Enter to drop, or Escape to cancel. You can also click an answer choice to select it, then click a response area to place it there.'
|
|
395
|
+
}
|
|
396
|
+
}
|
|
185
397
|
}, /*#__PURE__*/_react.default.createElement(MainContainer, null, /*#__PURE__*/_react.default.createElement(_renderUi.PreviewPrompt, {
|
|
186
398
|
className: "prompt",
|
|
187
399
|
prompt: prompt
|
|
@@ -190,20 +402,26 @@ class Main extends _react.default.Component {
|
|
|
190
402
|
toggled: showCorrectAnswer,
|
|
191
403
|
onToggle: this.toggleShowCorrect,
|
|
192
404
|
language: language
|
|
193
|
-
}), /*#__PURE__*/_react.default.createElement(_answerArea.default, {
|
|
405
|
+
}), /*#__PURE__*/_react.default.createElement(InteractiveRegion, null, /*#__PURE__*/_react.default.createElement(InteractiveRegionContent, null, /*#__PURE__*/_react.default.createElement(_answerArea.default, {
|
|
194
406
|
instanceId: this.instanceId,
|
|
195
407
|
model: model,
|
|
196
408
|
session: session,
|
|
197
409
|
onRemoveAnswer: id => this.onRemoveAnswer(id),
|
|
198
410
|
disabled: mode !== 'gather',
|
|
199
|
-
showCorrect: showCorrectAnswer
|
|
411
|
+
showCorrect: showCorrectAnswer,
|
|
412
|
+
selectedAnswer: this.state.selectedAnswer,
|
|
413
|
+
onChoiceClick: this.onChoiceClick,
|
|
414
|
+
onPlacementClick: this.onPlacementClick
|
|
200
415
|
}), /*#__PURE__*/_react.default.createElement(_choicesList.default, {
|
|
201
416
|
instanceId: this.instanceId,
|
|
202
417
|
model: model,
|
|
203
418
|
session: session,
|
|
204
419
|
disabled: mode !== 'gather',
|
|
205
|
-
onRemoveAnswer: id => this.onRemoveAnswer(id)
|
|
206
|
-
|
|
420
|
+
onRemoveAnswer: id => this.onRemoveAnswer(id),
|
|
421
|
+
selectedAnswer: this.state.selectedAnswer,
|
|
422
|
+
onChoiceClick: this.onChoiceClick,
|
|
423
|
+
onPlacementClick: this.onPlacementClick
|
|
424
|
+
}))), model.correctness && model.feedback && !showCorrectAnswer && /*#__PURE__*/_react.default.createElement(_renderUi.Feedback, {
|
|
207
425
|
correctness: model.correctness.correctness,
|
|
208
426
|
feedback: model.feedback
|
|
209
427
|
})), /*#__PURE__*/_react.default.createElement(_core.DragOverlay, null, this.renderDragOverlay()));
|
package/lib/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","names":["_react","_interopRequireDefault","require","_propTypes","_drag","_core","_modifiers","_correctAnswerToggle","_renderUi","_styles","_lodashEs","_answerArea","_choicesList","_answer","MainContainer","styled","display","flexDirection","justifyContent","color","text","backgroundColor","background","Main","React","Component","constructor","props","_defineProperty2","default","event","active","data","current","rect","node","width","height","getBoundingClientRect","setState","draggingElement","over","activeData","overData","session","onSessionChange","model","config","duplicates","isUndefined","value","type","promptId","undefined","answerId","id","sourcePromptId","targetPromptId","existingPlacement","findKey","val","swap","targetHasItem","temp","showCorrectAnswer","state","createElement","Answer","title","disabled","isDragging","style","boxSizing","instanceId","uniqueId","onRemoveAnswer","render","mode","prompt","language","DndContext","onDragStart","onDragEnd","onPlaceAnswer","modifiers","restrictToFirstScrollableAncestor","PreviewPrompt","className","show","toggled","onToggle","toggleShowCorrect","showCorrect","correctness","feedback","Feedback","DragOverlay","renderDragOverlay","exports","PropTypes","object","isRequired","func","string","_default"],"sources":["../src/main.jsx"],"sourcesContent":["import React from 'react';\nimport PropTypes from 'prop-types';\nimport { swap } from '@pie-lib/drag';\nimport { DndContext, DragOverlay } from '@dnd-kit/core';\nimport { restrictToFirstScrollableAncestor } from '@dnd-kit/modifiers';\nimport CorrectAnswerToggle from '@pie-lib/correct-answer-toggle';\nimport { color, Feedback, PreviewPrompt } from '@pie-lib/render-ui';\nimport { styled } from '@mui/material/styles';\nimport { findKey, isUndefined, uniqueId } from 'lodash-es';\nimport AnswerArea from './answer-area';\nimport ChoicesList from './choices-list';\nimport { Answer } from './answer';\n\nconst MainContainer = styled('div')({\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'center',\n color: color.text(),\n backgroundColor: color.background(),\n});\n\nexport class Main extends React.Component {\n static propTypes = {\n session: PropTypes.object.isRequired,\n onSessionChange: PropTypes.func,\n model: PropTypes.object.isRequired,\n prompt: PropTypes.string,\n };\n\n constructor(props) {\n super(props);\n\n this.instanceId = uniqueId();\n this.state = {\n showCorrectAnswer: false,\n draggingElement: null,\n };\n }\n\n onRemoveAnswer(id) {\n const { session, onSessionChange } = this.props;\n\n session.value[id] = undefined;\n\n onSessionChange(session);\n }\n\n onDragStart = (event) => {\n const { active } = event;\n\n if (active?.data?.current) {\n let rect = null;\n const node = active.node?.current;\n\n if (node) {\n const { width, height } = node.getBoundingClientRect();\n rect = { width, height };\n }\n\n this.setState({\n draggingElement: { ...active.data.current, rect },\n });\n }\n };\n\n onPlaceAnswer = (event) => {\n this.setState({ draggingElement: null });\n const { active, over } = event;\n\n if (!active) {\n return;\n }\n\n const activeData = active.data.current;\n const overData = over?.data.current;\n\n if (!activeData) {\n return;\n }\n\n const { session, onSessionChange, model } = this.props;\n const {\n config: { duplicates },\n } = model;\n\n if (isUndefined(session.value)) {\n session.value = {};\n }\n\n // dropping a placed answer back to the choices pool = remove it\n if (overData.type === 'choices-pool' && activeData.promptId !== undefined) {\n session.value[activeData.promptId] = undefined;\n onSessionChange(session);\n return;\n }\n\n const answerId = activeData.id;\n const sourcePromptId = activeData.promptId;\n\n // Handle dropping onto a drop zone\n if (overData && overData.type === 'drop-zone' && overData.promptId != null) {\n const targetPromptId = overData.promptId;\n\n if (activeData.type === 'choice' && overData.type === 'drop-zone' && targetPromptId !== undefined) {\n // check if this choice is already placed somewhere\n const existingPlacement = findKey(session.value, (val) => val === answerId);\n\n if (existingPlacement && !duplicates) {\n // swap if duplicates not allowed\n session.value = swap(session.value, existingPlacement, targetPromptId);\n } else {\n // place answer\n session.value[targetPromptId] = answerId;\n }\n }\n // Handle moving a placed item (target) to another drop zone\n else if (activeData.type === 'target' && sourcePromptId != null) {\n // If moving to a different placeholder\n if (sourcePromptId !== targetPromptId) {\n const targetHasItem = session.value[targetPromptId] != null;\n\n if (targetHasItem && !duplicates) {\n // swap items between placeholders\n const temp = session.value[targetPromptId];\n session.value[targetPromptId] = answerId;\n session.value[sourcePromptId] = temp;\n } else if (!targetHasItem) {\n // move item to empty placeholder\n session.value[targetPromptId] = answerId;\n delete session.value[sourcePromptId];\n }\n }\n }\n\n onSessionChange(session);\n }\n };\n\n toggleShowCorrect = () => {\n this.setState({ showCorrectAnswer: !this.state.showCorrectAnswer });\n };\n\n renderDragOverlay = () => {\n const { draggingElement } = this.state;\n\n if (!draggingElement) return null;\n\n return (\n <Answer\n id={draggingElement.id}\n title={draggingElement.value}\n disabled={false}\n isDragging={false}\n style={\n draggingElement.rect\n ? { width: draggingElement.rect.width, height: draggingElement.rect.height, boxSizing: 'border-box' }\n : {}\n }\n />\n );\n };\n\n render() {\n const { showCorrectAnswer } = this.state;\n const { model, session } = this.props;\n const { config, mode } = model;\n const { prompt, language } = config;\n\n return (\n <DndContext\n onDragStart={this.onDragStart}\n onDragEnd={this.onPlaceAnswer}\n modifiers={[restrictToFirstScrollableAncestor]}\n >\n <MainContainer>\n <PreviewPrompt className=\"prompt\" prompt={prompt} />\n\n <CorrectAnswerToggle\n show={mode === 'evaluate'}\n toggled={showCorrectAnswer}\n onToggle={this.toggleShowCorrect}\n language={language}\n />\n\n <AnswerArea\n instanceId={this.instanceId}\n model={model}\n session={session}\n onRemoveAnswer={(id) => this.onRemoveAnswer(id)}\n disabled={mode !== 'gather'}\n showCorrect={showCorrectAnswer}\n />\n\n <ChoicesList\n instanceId={this.instanceId}\n model={model}\n session={session}\n disabled={mode !== 'gather'}\n onRemoveAnswer={(id) => this.onRemoveAnswer(id)}\n />\n\n {model.correctness && model.feedback && !showCorrectAnswer && (\n <Feedback correctness={model.correctness.correctness} feedback={model.feedback} />\n )}\n </MainContainer>\n <DragOverlay>{this.renderDragOverlay()}</DragOverlay>\n </DndContext>\n );\n }\n}\n\nexport default Main;\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"main.js","names":["_react","_interopRequireDefault","require","_propTypes","_drag","_core","_modifiers","_keyboardCoordinates","_correctAnswerToggle","_renderUi","_styles","_lodashEs","_answerArea","_choicesList","_answer","CLICK_AFTER_DRAG_GUARD_MS","sensors","sensor","PointerSensor","options","activationConstraint","distance","KeyboardSensor","coordinateGetter","closestDroppableKeyboardCoordinates","keyboardCodes","start","KeyboardCode","Space","Enter","cancel","Esc","end","MainContainer","styled","display","flexDirection","justifyContent","color","text","backgroundColor","background","InteractiveRegion","width","overflowX","overflowY","InteractiveRegionContent","minWidth","Main","React","Component","constructor","props","_defineProperty2","default","event","active","data","current","rect","node","height","getBoundingClientRect","setState","draggingElement","selectAnswer","cancelSelection","lastDragEndAt","Date","now","activeData","overData","session","onSessionChange","model","config","duplicates","isUndefined","value","type","promptId","undefined","answerId","id","sourcePromptId","targetPromptId","existingPlacement","findKey","val","swap","targetHasItem","temp","over","placeAnswer","a","b","selectedAnswer","state","isSameAnswer","document","dispatchEvent","KeyboardEvent","code","bubbles","cancelable","endAnyLiveKeyboardDrag","isClickSoonAfterDragEnd","toggleAnswerSelection","placeSelectedAnswer","showCorrectAnswer","createElement","Answer","title","disabled","isDragging","style","boxSizing","instanceId","uniqueId","onRemoveAnswer","render","mode","prompt","language","getChoiceLabel","answer","answers","find","String","replace","trim","getDraggedLabel","getDropTargetLabel","label","choiceId","promptItem","prompts","p","announcements","onDragStart","onDragOver","target","content","onDragEnd","onDragCancel","DndContext","collisionDetection","rectIntersection","onPlaceAnswer","modifiers","restrictToFirstScrollableAncestor","accessibility","screenReaderInstructions","draggable","PreviewPrompt","className","show","toggled","onToggle","toggleShowCorrect","showCorrect","onChoiceClick","onPlacementClick","correctness","feedback","Feedback","DragOverlay","renderDragOverlay","exports","PropTypes","object","isRequired","func","string","_default"],"sources":["../src/main.jsx"],"sourcesContent":["import React from 'react';\nimport PropTypes from 'prop-types';\nimport { swap } from '@pie-lib/drag';\nimport { DndContext, DragOverlay, PointerSensor, KeyboardSensor, KeyboardCode, rectIntersection } from '@dnd-kit/core';\nimport { restrictToFirstScrollableAncestor } from '@dnd-kit/modifiers';\nimport { closestDroppableKeyboardCoordinates } from './keyboard-coordinates';\nimport CorrectAnswerToggle from '@pie-lib/correct-answer-toggle';\nimport { color, Feedback, PreviewPrompt } from '@pie-lib/render-ui';\nimport { styled } from '@mui/material/styles';\nimport { findKey, isUndefined, uniqueId } from 'lodash-es';\nimport AnswerArea from './answer-area';\nimport ChoicesList from './choices-list';\nimport { Answer } from './answer';\n\n// A click that lands right after a real drag gesture ends (pointer drag-and-drop, or\n// the browser's own synthetic click for a keyboard Space/Enter) must be ignored by the\n// new click-to-select/click-to-place handlers below, or it would immediately reopen or\n// re-trigger a selection for a drag that just completed.\nconst CLICK_AFTER_DRAG_GUARD_MS = 250;\n\nconst sensors = [\n // Without an activationConstraint, dnd-kit's PointerSensor calls its internal\n // handleStart() synchronously on pointerdown, before any movement — meaning a plain\n // click is itself \"activated\" as a drag. Once activated, dnd-kit adds a capture-phase\n // document click listener that calls stopPropagation() (to suppress the native\n // \"ghost click\" a real drag leaves behind), which also swallows the click for a\n // gesture with zero movement, before it ever reaches our own onClick handlers below.\n // Requiring 8px of movement (matching @pie-lib/drag's DragProvider convention used\n // elsewhere in this codebase) defers activation until an actual drag gesture is\n // underway, so a plain click passes through untouched.\n { sensor: PointerSensor, options: { activationConstraint: { distance: 8 } } },\n {\n sensor: KeyboardSensor,\n options: {\n coordinateGetter: closestDroppableKeyboardCoordinates,\n keyboardCodes: {\n start: [KeyboardCode.Space, KeyboardCode.Enter],\n cancel: [KeyboardCode.Esc],\n end: [KeyboardCode.Space, KeyboardCode.Enter],\n },\n },\n },\n];\n\nconst MainContainer = styled('div')({\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'center',\n color: color.text(),\n backgroundColor: color.background(),\n});\n\nconst InteractiveRegion = styled('div')({\n width: '100%',\n overflowX: 'auto',\n overflowY: 'hidden',\n});\n\n// A block child of a scroll port is sized to the scroll port, so it has to opt out explicitly for\n// the content to be able to overflow. min-content keeps the rows and the pool the same width.\nconst InteractiveRegionContent = styled('div')({\n display: 'flex',\n flexDirection: 'column',\n minWidth: 'min-content',\n});\n\nexport class Main extends React.Component {\n static propTypes = {\n session: PropTypes.object.isRequired,\n onSessionChange: PropTypes.func,\n model: PropTypes.object.isRequired,\n prompt: PropTypes.string,\n };\n\n constructor(props) {\n super(props);\n\n this.instanceId = uniqueId();\n this.state = {\n showCorrectAnswer: false,\n draggingElement: null,\n selectedAnswer: null,\n };\n this.lastDragEndAt = 0;\n }\n\n onRemoveAnswer(id) {\n const { session, onSessionChange } = this.props;\n\n session.value[id] = undefined;\n\n onSessionChange(session);\n }\n\n onDragStart = (event) => {\n const { active } = event;\n\n if (active?.data?.current) {\n let rect = null;\n const node = active.node?.current;\n\n if (node) {\n const { width, height } = node.getBoundingClientRect();\n rect = { width, height };\n }\n\n this.setState({\n draggingElement: { ...active.data.current, rect },\n });\n this.selectAnswer(active.data.current);\n }\n };\n\n onDragCancel = () => {\n this.setState({ draggingElement: null });\n this.cancelSelection();\n this.lastDragEndAt = Date.now();\n };\n\n // Pure placement logic\n placeAnswer = (activeData, overData) => {\n if (!activeData || !overData) {\n return;\n }\n\n const { session, onSessionChange, model } = this.props;\n const {\n config: { duplicates },\n } = model;\n\n if (isUndefined(session.value)) {\n session.value = {};\n }\n\n // dropping a placed answer back to the choices pool = remove it\n if (overData.type === 'choices-pool' && activeData.promptId !== undefined) {\n session.value[activeData.promptId] = undefined;\n onSessionChange(session);\n return;\n }\n\n const answerId = activeData.id;\n const sourcePromptId = activeData.promptId;\n\n // Handle dropping onto a drop zone\n if (overData.type === 'drop-zone' && overData.promptId != null) {\n const targetPromptId = overData.promptId;\n\n if (activeData.type === 'choice' && targetPromptId !== undefined) {\n // check if this choice is already placed somewhere\n const existingPlacement = findKey(session.value, (val) => val === answerId);\n\n if (existingPlacement && !duplicates) {\n // swap if duplicates not allowed\n session.value = swap(session.value, existingPlacement, targetPromptId);\n } else {\n // place answer\n session.value[targetPromptId] = answerId;\n }\n }\n // Handle moving a placed item (target) to another drop zone\n else if (activeData.type === 'target' && sourcePromptId != null) {\n // If moving to a different placeholder\n if (sourcePromptId !== targetPromptId) {\n const targetHasItem = session.value[targetPromptId] != null;\n\n if (targetHasItem && !duplicates) {\n // Without duplicates each choice is placed at most once, so trading the two\n // answers is the only way to fill the target without dropping the answer it\n // already holds.\n const temp = session.value[targetPromptId];\n session.value[targetPromptId] = answerId;\n session.value[sourcePromptId] = temp;\n } else {\n // Empty target, or duplicates allowed: the moved answer takes the target and\n // the source area is vacated. Any answer displaced from the target is simply\n // available in the pool again — which with duplicates enabled always lists\n // every choice anyway — matching how the choice -> occupied target path above\n // already behaves. Leaving this case unhandled (as it was) made moving a\n // placed answer onto an occupied area a silent no-op whenever duplicates\n // were enabled.\n session.value[targetPromptId] = answerId;\n delete session.value[sourcePromptId];\n }\n }\n }\n\n onSessionChange(session);\n }\n };\n\n onPlaceAnswer = (event) => {\n this.setState({ draggingElement: null });\n const { active, over } = event;\n\n if (!active) {\n return;\n }\n\n const activeData = active.data.current;\n const overData = over?.data.current;\n\n if (!activeData) {\n return;\n }\n\n this.placeAnswer(activeData, overData);\n this.cancelSelection();\n this.lastDragEndAt = Date.now();\n };\n\n isSameAnswer = (a, b) => !!a && !!b && a.type === b.type && a.id === b.id && a.promptId === b.promptId;\n\n // Unconditionally selects (used by the drag-start mirror, and internally when\n // switching from one choice to another).\n selectAnswer = (data) => {\n this.setState({ selectedAnswer: data });\n };\n\n // Click-to-select semantics: selecting the currently-selected answer again clears\n // the selection instead of re-selecting it.\n toggleAnswerSelection = (data) => {\n this.setState((state) => ({\n selectedAnswer: this.isSameAnswer(state.selectedAnswer, data) ? null : data,\n }));\n };\n\n cancelSelection = () => {\n this.setState({ selectedAnswer: null });\n };\n\n // If a real dnd-kit drag (started via keyboard Space/Enter) is still live when a\n // click completes the placement below, it needs to be cleanly ended — otherwise\n // dnd-kit would still think a drag is in progress (still listening for Tab/arrow/\n // Space/Escape, still showing the drag overlay) for a placement the click already\n // performed. Escape is already configured as this sensor's cancel key, and\n // dispatching it as a real DOM KeyboardEvent is how dnd-kit's own document-level\n // listener is reached from outside its sensor. onDragCancel is intentionally not\n // wired to redo any placement — it only resets local UI state — so this is safe to\n // call unconditionally, including when no drag is actually live (dnd-kit simply has\n // no listener attached in that case, and the dispatch is a no-op).\n endAnyLiveKeyboardDrag = () => {\n document.dispatchEvent(new KeyboardEvent('keydown', { code: 'Escape', bubbles: true, cancelable: true }));\n };\n\n placeSelectedAnswer = (overData) => {\n const { selectedAnswer } = this.state;\n\n if (!selectedAnswer) {\n return;\n }\n\n this.placeAnswer(selectedAnswer, overData);\n this.cancelSelection();\n this.endAnyLiveKeyboardDrag();\n this.lastDragEndAt = Date.now();\n };\n\n isClickSoonAfterDragEnd = () => Date.now() - this.lastDragEndAt < CLICK_AFTER_DRAG_GUARD_MS;\n\n onChoiceClick = (data) => {\n if (this.isClickSoonAfterDragEnd()) {\n return;\n }\n\n this.toggleAnswerSelection(data);\n };\n\n onPlacementClick = (overData) => {\n if (this.isClickSoonAfterDragEnd()) {\n return;\n }\n\n this.placeSelectedAnswer(overData);\n };\n\n toggleShowCorrect = () => {\n this.setState({ showCorrectAnswer: !this.state.showCorrectAnswer });\n };\n\n renderDragOverlay = () => {\n const { draggingElement } = this.state;\n\n if (!draggingElement) return null;\n\n return (\n <Answer\n id={draggingElement.id}\n title={draggingElement.value}\n disabled={false}\n isDragging={false}\n style={\n draggingElement.rect\n ? { width: draggingElement.rect.width, height: draggingElement.rect.height, boxSizing: 'border-box' }\n : {}\n }\n />\n );\n };\n\n render() {\n const { showCorrectAnswer } = this.state;\n const { model, session } = this.props;\n const { config, mode } = model;\n const { prompt, language } = config;\n\n // Helpers for accessible announcements\n const getChoiceLabel = (answerId) => {\n const answer = config.answers.find((a) => String(a.id) === String(answerId));\n\n if (answer?.title) {\n // Strip HTML tags for screen reader\n const text = answer.title.replace(/<[^>]*>/g, '').trim();\n return text || `Answer ${answerId}`;\n }\n\n return `Answer ${answerId}`;\n };\n\n // Read the dragged choice off the tile's own drag data rather than parsing it back\n // out of the drag id: a drag id identifies the tile (a response area, for a placed\n // answer — see buildDragId in ./answer), not the choice the tile holds.\n const getDraggedLabel = (active) => getChoiceLabel(active?.data?.current?.id);\n\n const getDropTargetLabel = (over) => {\n const overData = over?.data?.current;\n\n if (overData?.type === 'choices-pool' || over?.id === 'choices-pool') {\n return { label: 'Choices list', choiceId: null };\n }\n\n // Prefer the droppable's own data; fall back to the id for a droppable registered\n // without any (the id shape is \"drop-{promptId}\").\n const promptId = overData?.promptId != null ? overData.promptId : String(over?.id).replace(/^drop-/, '');\n const promptItem = config.prompts.find((p) => String(p.id) === String(promptId));\n const label = promptItem?.title\n ? `Response area for ${promptItem.title.replace(/<[^>]*>/g, '').trim()}`\n : `Response area ${promptId}`;\n const choiceId = session.value?.[promptId];\n\n return { label, choiceId: choiceId != null ? choiceId : null };\n };\n\n const announcements = {\n onDragStart({ active }) {\n return `Picked up ${getDraggedLabel(active)}. Use Tab to move between response areas, then press Space or Enter to drop.`;\n },\n\n onDragOver({ active, over }) {\n if (!over) {\n return `${getDraggedLabel(active)} is not over a response area.`;\n }\n\n const target = getDropTargetLabel(over);\n const content = target.choiceId ? `Currently contains ${getChoiceLabel(target.choiceId)}.` : 'Currently empty.';\n\n return `Over ${target.label}. ${content}`;\n },\n\n onDragEnd({ active, over }) {\n if (!over) {\n return `${getDraggedLabel(active)} was returned to its original position.`;\n }\n\n return `Dropped ${getDraggedLabel(active)} in ${getDropTargetLabel(over).label}.`;\n },\n\n onDragCancel({ active }) {\n return `Cancelled. ${getDraggedLabel(active)} was returned to its original position.`;\n },\n };\n\n return (\n <DndContext\n sensors={sensors}\n collisionDetection={rectIntersection}\n onDragStart={this.onDragStart}\n onDragEnd={this.onPlaceAnswer}\n onDragCancel={this.onDragCancel}\n modifiers={[restrictToFirstScrollableAncestor]}\n accessibility={{\n screenReaderInstructions: {\n draggable:\n 'Press Space or Enter to pick up this answer choice. Once picked up, use Tab or Shift+Tab to cycle through response areas, or use arrow keys to move it freely. Press Space or Enter to drop, or Escape to cancel. You can also click an answer choice to select it, then click a response area to place it there.',\n },\n }}\n >\n <MainContainer>\n <PreviewPrompt className=\"prompt\" prompt={prompt} />\n\n <CorrectAnswerToggle\n show={mode === 'evaluate'}\n toggled={showCorrectAnswer}\n onToggle={this.toggleShowCorrect}\n language={language}\n />\n\n <InteractiveRegion>\n <InteractiveRegionContent>\n <AnswerArea\n instanceId={this.instanceId}\n model={model}\n session={session}\n onRemoveAnswer={(id) => this.onRemoveAnswer(id)}\n disabled={mode !== 'gather'}\n showCorrect={showCorrectAnswer}\n selectedAnswer={this.state.selectedAnswer}\n onChoiceClick={this.onChoiceClick}\n onPlacementClick={this.onPlacementClick}\n />\n\n <ChoicesList\n instanceId={this.instanceId}\n model={model}\n session={session}\n disabled={mode !== 'gather'}\n onRemoveAnswer={(id) => this.onRemoveAnswer(id)}\n selectedAnswer={this.state.selectedAnswer}\n onChoiceClick={this.onChoiceClick}\n onPlacementClick={this.onPlacementClick}\n />\n </InteractiveRegionContent>\n </InteractiveRegion>\n\n {model.correctness && model.feedback && !showCorrectAnswer && (\n <Feedback correctness={model.correctness.correctness} feedback={model.feedback} />\n )}\n </MainContainer>\n <DragOverlay>{this.renderDragOverlay()}</DragOverlay>\n </DndContext>\n );\n }\n}\n\nexport default Main;\n"],"mappings":"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","ignoreList":[]}
|