@prosopo/procaptcha-puzzle 2.13.3 → 2.14.1
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/.turbo/turbo-build$colon$cjs.log +6 -6
- package/.turbo/turbo-build$colon$tsc.log +20 -20
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +66 -0
- package/dist/cjs/components/procaptchaPuzzle.cjs +22 -0
- package/dist/cjs/components/procaptchaWidget.cjs +225 -0
- package/dist/cjs/components/puzzleCanvas.cjs +451 -0
- package/dist/cjs/index.cjs +7 -2
- package/dist/components/procaptchaPuzzle.d.ts +7 -0
- package/dist/components/procaptchaPuzzle.d.ts.map +1 -0
- package/dist/components/procaptchaPuzzle.js +21 -0
- package/dist/components/procaptchaPuzzle.js.map +1 -0
- package/dist/components/procaptchaWidget.d.ts +7 -0
- package/dist/components/procaptchaWidget.d.ts.map +1 -0
- package/dist/components/procaptchaWidget.js +225 -0
- package/dist/components/procaptchaWidget.js.map +1 -0
- package/dist/components/{PuzzleCanvas.d.ts → puzzleCanvas.d.ts} +6 -4
- package/dist/components/puzzleCanvas.d.ts.map +1 -0
- package/dist/components/puzzleCanvas.js +451 -0
- package/dist/components/puzzleCanvas.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/tests/domHarness.d.ts +20 -0
- package/dist/tests/domHarness.d.ts.map +1 -0
- package/dist/tests/domHarness.js +56 -0
- package/dist/tests/domHarness.js.map +1 -0
- package/dist/tests/invisibleCanvasVisibility.unit.test.d.ts +2 -0
- package/dist/tests/invisibleCanvasVisibility.unit.test.d.ts.map +1 -0
- package/dist/tests/invisibleCanvasVisibility.unit.test.js +102 -0
- package/dist/tests/invisibleCanvasVisibility.unit.test.js.map +1 -0
- package/dist/tests/procaptchaPuzzle.test-d.js +13 -10
- package/dist/tests/procaptchaPuzzle.test-d.js.map +1 -1
- package/dist/tests/procaptchaPuzzle.unit.test.js +44 -33
- package/dist/tests/procaptchaPuzzle.unit.test.js.map +1 -1
- package/dist/tests/procaptchaWidget.unit.test.js +108 -175
- package/dist/tests/procaptchaWidget.unit.test.js.map +1 -1
- package/dist/tests/puzzleCanvas.unit.test.js +56 -82
- package/dist/tests/puzzleCanvas.unit.test.js.map +1 -1
- package/package.json +10 -14
- package/src/components/procaptchaPuzzle.ts +54 -0
- package/src/components/procaptchaWidget.ts +392 -0
- package/src/components/puzzleCanvas.ts +658 -0
- package/src/index.ts +3 -2
- package/src/tests/domHarness.ts +122 -0
- package/src/tests/invisibleCanvasVisibility.unit.test.ts +179 -0
- package/src/tests/procaptchaPuzzle.test-d.ts +33 -17
- package/src/tests/procaptchaPuzzle.unit.test.ts +59 -44
- package/src/tests/procaptchaWidget.unit.test.ts +145 -258
- package/src/tests/puzzleCanvas.unit.test.ts +94 -122
- package/tsconfig.cjs.json +1 -2
- package/tsconfig.json +1 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/vite.test.config.ts +3 -4
- package/dist/cjs/components/ProcaptchaPuzzle.cjs +0 -7
- package/dist/cjs/components/ProcaptchaWidget.cjs +0 -206
- package/dist/cjs/components/PuzzleCanvas.cjs +0 -476
- package/dist/components/ProcaptchaPuzzle.d.ts +0 -3
- package/dist/components/ProcaptchaPuzzle.d.ts.map +0 -1
- package/dist/components/ProcaptchaPuzzle.js +0 -7
- package/dist/components/ProcaptchaPuzzle.js.map +0 -1
- package/dist/components/ProcaptchaWidget.d.ts +0 -4
- package/dist/components/ProcaptchaWidget.d.ts.map +0 -1
- package/dist/components/ProcaptchaWidget.js +0 -207
- package/dist/components/ProcaptchaWidget.js.map +0 -1
- package/dist/components/PuzzleCanvas.d.ts.map +0 -1
- package/dist/components/PuzzleCanvas.js +0 -476
- package/dist/components/PuzzleCanvas.js.map +0 -1
- package/dist/tests/setup.d.ts +0 -5
- package/dist/tests/setup.d.ts.map +0 -1
- package/dist/tests/setup.js +0 -3
- package/dist/tests/setup.js.map +0 -1
- package/src/components/ProcaptchaPuzzle.tsx +0 -36
- package/src/components/ProcaptchaWidget.tsx +0 -320
- package/src/components/PuzzleCanvas.tsx +0 -638
- package/src/tests/setup.ts +0 -26
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
let _prosopo_procaptcha_common = require("@prosopo/procaptcha-common");
|
|
2
|
+
//#region src/components/puzzleCanvas.ts
|
|
3
|
+
var CONTAINER_WIDTH = 300;
|
|
4
|
+
var CONTAINER_HEIGHT = 200;
|
|
5
|
+
var PIECE_CSS_CLASS = "prosopo-puzzle-piece";
|
|
6
|
+
var STEP_PX = 10;
|
|
7
|
+
var FINE_STEP_PX = 2;
|
|
8
|
+
var ANNOUNCE_DEBOUNCE_MS = 400;
|
|
9
|
+
var VISUALLY_HIDDEN = {
|
|
10
|
+
position: "absolute",
|
|
11
|
+
width: "1px",
|
|
12
|
+
height: "1px",
|
|
13
|
+
padding: 0,
|
|
14
|
+
margin: "-1px",
|
|
15
|
+
overflow: "hidden",
|
|
16
|
+
clip: "rect(0, 0, 0, 0)",
|
|
17
|
+
clipPath: "inset(50%)",
|
|
18
|
+
whiteSpace: "nowrap",
|
|
19
|
+
border: 0
|
|
20
|
+
};
|
|
21
|
+
var stylesheet = (focusRingColor) => `
|
|
22
|
+
@keyframes prosopo-puzzle-shake {
|
|
23
|
+
0%, 100% { transform: translateX(0); }
|
|
24
|
+
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
|
|
25
|
+
20%, 40%, 60%, 80% { transform: translateX(4px); }
|
|
26
|
+
}
|
|
27
|
+
.${PIECE_CSS_CLASS}:focus-visible {
|
|
28
|
+
outline: 3px solid ${focusRingColor};
|
|
29
|
+
outline-offset: 2px;
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
var KALEIDOSCOPE_TILES = [
|
|
33
|
+
[
|
|
34
|
+
0,
|
|
35
|
+
0,
|
|
36
|
+
-1,
|
|
37
|
+
-1
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
1,
|
|
41
|
+
0,
|
|
42
|
+
1,
|
|
43
|
+
-1
|
|
44
|
+
],
|
|
45
|
+
[
|
|
46
|
+
2,
|
|
47
|
+
0,
|
|
48
|
+
-1,
|
|
49
|
+
-1
|
|
50
|
+
],
|
|
51
|
+
[
|
|
52
|
+
0,
|
|
53
|
+
1,
|
|
54
|
+
-1,
|
|
55
|
+
1
|
|
56
|
+
],
|
|
57
|
+
[
|
|
58
|
+
1,
|
|
59
|
+
1,
|
|
60
|
+
1,
|
|
61
|
+
1
|
|
62
|
+
],
|
|
63
|
+
[
|
|
64
|
+
2,
|
|
65
|
+
1,
|
|
66
|
+
-1,
|
|
67
|
+
1
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
0,
|
|
71
|
+
2,
|
|
72
|
+
-1,
|
|
73
|
+
-1
|
|
74
|
+
],
|
|
75
|
+
[
|
|
76
|
+
1,
|
|
77
|
+
2,
|
|
78
|
+
1,
|
|
79
|
+
-1
|
|
80
|
+
],
|
|
81
|
+
[
|
|
82
|
+
2,
|
|
83
|
+
2,
|
|
84
|
+
-1,
|
|
85
|
+
-1
|
|
86
|
+
]
|
|
87
|
+
];
|
|
88
|
+
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
89
|
+
var instanceCount = 0;
|
|
90
|
+
var mountPuzzleCanvas = (initialProps) => {
|
|
91
|
+
const teardown = new _prosopo_procaptcha_common.Teardown();
|
|
92
|
+
let props = initialProps;
|
|
93
|
+
let posX = props.originX;
|
|
94
|
+
let posY = props.originY;
|
|
95
|
+
let dragging = false;
|
|
96
|
+
let puzzleEvents = [];
|
|
97
|
+
let dragOffset = {
|
|
98
|
+
x: 0,
|
|
99
|
+
y: 0
|
|
100
|
+
};
|
|
101
|
+
let visible = false;
|
|
102
|
+
let shaking = false;
|
|
103
|
+
let shakeTimer;
|
|
104
|
+
let announceTimer;
|
|
105
|
+
let keyboardDragging = false;
|
|
106
|
+
const baseId = `prosopo-puzzle-${instanceCount++}`;
|
|
107
|
+
const instructionId = `${baseId}-instruction`;
|
|
108
|
+
const keyboardHintId = `${baseId}-keyboard-hint`;
|
|
109
|
+
const t = (key, options) => props.translator.t(key, options);
|
|
110
|
+
const style = (0, _prosopo_procaptcha_common.createElement)("style", { text: stylesheet(props.theme.palette.primary.main) });
|
|
111
|
+
const announcer = (0, _prosopo_procaptcha_common.createElement)("output", {
|
|
112
|
+
style: VISUALLY_HIDDEN,
|
|
113
|
+
attributes: {
|
|
114
|
+
"aria-live": "polite",
|
|
115
|
+
"aria-atomic": "true"
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const keyboardHint = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
119
|
+
style: VISUALLY_HIDDEN,
|
|
120
|
+
attributes: { id: keyboardHintId }
|
|
121
|
+
});
|
|
122
|
+
const backgroundTiles = KALEIDOSCOPE_TILES.map(([column, row, sx, sy]) => (0, _prosopo_procaptcha_common.createElement)("img", {
|
|
123
|
+
style: {
|
|
124
|
+
position: "absolute",
|
|
125
|
+
left: `${column * CONTAINER_WIDTH}px`,
|
|
126
|
+
top: `${row * CONTAINER_HEIGHT}px`,
|
|
127
|
+
width: `${CONTAINER_WIDTH}px`,
|
|
128
|
+
height: `${CONTAINER_HEIGHT}px`,
|
|
129
|
+
transform: `scale(${sx}, ${sy})`,
|
|
130
|
+
pointerEvents: "none",
|
|
131
|
+
userSelect: "none"
|
|
132
|
+
},
|
|
133
|
+
attributes: {
|
|
134
|
+
alt: "",
|
|
135
|
+
draggable: false
|
|
136
|
+
}
|
|
137
|
+
}));
|
|
138
|
+
const backgroundLayer = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
139
|
+
style: {
|
|
140
|
+
left: `-${CONTAINER_WIDTH}px`,
|
|
141
|
+
top: `-${CONTAINER_HEIGHT}px`,
|
|
142
|
+
position: "absolute",
|
|
143
|
+
width: `${CONTAINER_WIDTH * 3}px`,
|
|
144
|
+
height: `${CONTAINER_HEIGHT * 3}px`,
|
|
145
|
+
pointerEvents: "none",
|
|
146
|
+
userSelect: "none",
|
|
147
|
+
transformOrigin: "center center",
|
|
148
|
+
willChange: "transform, filter"
|
|
149
|
+
},
|
|
150
|
+
children: backgroundTiles
|
|
151
|
+
});
|
|
152
|
+
const piece = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
153
|
+
className: PIECE_CSS_CLASS,
|
|
154
|
+
style: {
|
|
155
|
+
position: "absolute",
|
|
156
|
+
backgroundSize: "100% 100%",
|
|
157
|
+
touchAction: "none"
|
|
158
|
+
},
|
|
159
|
+
attributes: {
|
|
160
|
+
role: "application",
|
|
161
|
+
"aria-describedby": `${instructionId} ${keyboardHintId}`
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
if ("production" !== process.env.NODE_ENV) (0, _prosopo_procaptcha_common.applyAttributes)(piece, { "data-cy": "prosopo-puzzle-piece" });
|
|
165
|
+
const area = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
166
|
+
style: {
|
|
167
|
+
position: "relative",
|
|
168
|
+
width: `${CONTAINER_WIDTH}px`,
|
|
169
|
+
height: `${CONTAINER_HEIGHT}px`,
|
|
170
|
+
borderRadius: "0 0 20px 20px",
|
|
171
|
+
overflow: "hidden",
|
|
172
|
+
userSelect: "none",
|
|
173
|
+
transition: "opacity 0.2s ease"
|
|
174
|
+
},
|
|
175
|
+
children: [backgroundLayer, piece]
|
|
176
|
+
});
|
|
177
|
+
const instruction = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
178
|
+
attributes: { id: instructionId },
|
|
179
|
+
style: {
|
|
180
|
+
borderRadius: "20px 20px 0 0",
|
|
181
|
+
padding: "12px 20px",
|
|
182
|
+
width: `${CONTAINER_WIDTH}px`,
|
|
183
|
+
boxSizing: "border-box",
|
|
184
|
+
textAlign: "center",
|
|
185
|
+
fontSize: "14px",
|
|
186
|
+
fontWeight: 500,
|
|
187
|
+
transition: "color 0.3s ease, border-color 0.3s ease"
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
const panel = (0, _prosopo_procaptcha_common.createElement)("div", {
|
|
191
|
+
style: {
|
|
192
|
+
display: "flex",
|
|
193
|
+
flexDirection: "column",
|
|
194
|
+
alignItems: "center",
|
|
195
|
+
gap: "0",
|
|
196
|
+
transition: "opacity 0.3s ease, transform 0.3s ease"
|
|
197
|
+
},
|
|
198
|
+
children: [instruction, area]
|
|
199
|
+
});
|
|
200
|
+
const surfaceProps = () => ({
|
|
201
|
+
show: true,
|
|
202
|
+
placement: props.placement,
|
|
203
|
+
anchor: props.anchor,
|
|
204
|
+
onDismiss: props.onDismiss,
|
|
205
|
+
scrim: visible ? "dim" : "none",
|
|
206
|
+
dialogLabel: t("WIDGET.PUZZLE.DIALOG_LABEL", { defaultValue: "Puzzle challenge" })
|
|
207
|
+
});
|
|
208
|
+
const surface = (0, _prosopo_procaptcha_common.mountChallengeSurface)(surfaceProps());
|
|
209
|
+
surface.content.append(style, announcer, keyboardHint, panel);
|
|
210
|
+
const announce = (message, delayMs = 0) => {
|
|
211
|
+
if (void 0 !== announceTimer) {
|
|
212
|
+
clearTimeout(announceTimer);
|
|
213
|
+
announceTimer = void 0;
|
|
214
|
+
}
|
|
215
|
+
if (0 === delayMs) {
|
|
216
|
+
announcer.textContent = message;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
announceTimer = setTimeout(() => {
|
|
220
|
+
announcer.textContent = message;
|
|
221
|
+
}, delayMs);
|
|
222
|
+
};
|
|
223
|
+
const describePosition = (x, y) => t("WIDGET.PUZZLE.POSITION", {
|
|
224
|
+
defaultValue: "{{x}} percent across, {{y}} percent down",
|
|
225
|
+
x: Math.round(x / CONTAINER_WIDTH * 100),
|
|
226
|
+
y: Math.round(y / CONTAINER_HEIGHT * 100)
|
|
227
|
+
});
|
|
228
|
+
const applyPiecePosition = () => {
|
|
229
|
+
(0, _prosopo_procaptcha_common.applyStyles)(piece, {
|
|
230
|
+
left: `${posX - props.pieceSize / 2}px`,
|
|
231
|
+
top: `${posY - props.pieceSize / 2}px`
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
const applyPieceChrome = () => {
|
|
235
|
+
(0, _prosopo_procaptcha_common.applyStyles)(piece, {
|
|
236
|
+
width: `${props.pieceSize}px`,
|
|
237
|
+
height: `${props.pieceSize}px`,
|
|
238
|
+
backgroundImage: `url(${props.piece})`,
|
|
239
|
+
cursor: props.submitting ? "default" : dragging ? "grabbing" : "grab",
|
|
240
|
+
filter: dragging ? "drop-shadow(0 4px 10px rgba(0, 0, 0, 0.45))" : "drop-shadow(0 2px 5px rgba(0, 0, 0, 0.35))",
|
|
241
|
+
transition: dragging ? "none" : "filter 0.2s ease, left 0.3s ease, top 0.3s ease"
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
const PARALLAX_FACTOR = .08;
|
|
245
|
+
const applyBackgroundMotion = () => {
|
|
246
|
+
const deltaX = posX - props.originX;
|
|
247
|
+
const deltaY = posY - props.originY;
|
|
248
|
+
const dragDistance = Math.hypot(deltaX, deltaY);
|
|
249
|
+
const translateX = dragging ? -deltaX * PARALLAX_FACTOR : 0;
|
|
250
|
+
const translateY = dragging ? -deltaY * PARALLAX_FACTOR : 0;
|
|
251
|
+
const scale = dragging ? 1.03 : 1;
|
|
252
|
+
const hueShift = dragging ? clamp(deltaX * .12, -18, 18) : 0;
|
|
253
|
+
const satBoost = dragging ? 1 + Math.min(.25, dragDistance * .0035) : 1;
|
|
254
|
+
const brightness = dragging ? 1 - Math.min(.06, dragDistance * 8e-4) : 1;
|
|
255
|
+
(0, _prosopo_procaptcha_common.applyStyles)(backgroundLayer, {
|
|
256
|
+
transform: `translate(${translateX.toFixed(2)}px, ${translateY.toFixed(2)}px) scale(${scale.toFixed(3)})`,
|
|
257
|
+
filter: `hue-rotate(${hueShift.toFixed(2)}deg) saturate(${satBoost.toFixed(3)}) brightness(${brightness.toFixed(3)})`,
|
|
258
|
+
transition: dragging ? "none" : "transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), filter 0.35s ease"
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
const render = () => {
|
|
262
|
+
const { theme } = props;
|
|
263
|
+
(0, _prosopo_procaptcha_common.applyStyles)(panel, {
|
|
264
|
+
opacity: visible ? 1 : 0,
|
|
265
|
+
transform: visible ? "scale(1)" : "scale(0.9)",
|
|
266
|
+
animation: shaking ? "prosopo-puzzle-shake 0.5s ease" : "none"
|
|
267
|
+
});
|
|
268
|
+
instruction.textContent = props.showRetry ? t("WIDGET.PUZZLE.RETRY", { defaultValue: "Not quite — try again" }) : t("WIDGET.PUZZLE.DRAG", { defaultValue: "Drag the piece to the target" });
|
|
269
|
+
keyboardHint.textContent = t("WIDGET.PUZZLE.KEYBOARD_HINT", { defaultValue: "Use the arrow keys to move the piece, holding shift for smaller steps. Press Enter to submit it, Home to put it back at the start, or Escape to cancel." });
|
|
270
|
+
(0, _prosopo_procaptcha_common.applyStyles)(instruction, {
|
|
271
|
+
backgroundColor: theme.palette.surface,
|
|
272
|
+
fontFamily: theme.font.fontFamily,
|
|
273
|
+
color: props.showRetry ? theme.palette.error.main : theme.palette.onSurface,
|
|
274
|
+
borderBottom: `2px solid ${props.showRetry ? theme.palette.error.main : "transparent"}`
|
|
275
|
+
});
|
|
276
|
+
(0, _prosopo_procaptcha_common.applyStyles)(area, {
|
|
277
|
+
background: `linear-gradient(135deg, ${theme.palette.surface} 0%, ${theme.palette.primaryContainer.main} 50%, ${theme.palette.surface} 100%)`,
|
|
278
|
+
opacity: props.submitting ? .6 : 1,
|
|
279
|
+
pointerEvents: props.submitting ? "none" : "auto"
|
|
280
|
+
});
|
|
281
|
+
for (const tile of backgroundTiles) tile.src = props.background;
|
|
282
|
+
(0, _prosopo_procaptcha_common.applyAttributes)(piece, {
|
|
283
|
+
tabindex: props.submitting ? -1 : 0,
|
|
284
|
+
"aria-roledescription": t("WIDGET.PUZZLE.PIECE_ROLE", { defaultValue: "draggable puzzle piece" }),
|
|
285
|
+
"aria-label": t("WIDGET.PUZZLE.PIECE_LABEL", { defaultValue: "Puzzle piece" })
|
|
286
|
+
});
|
|
287
|
+
applyPiecePosition();
|
|
288
|
+
applyPieceChrome();
|
|
289
|
+
applyBackgroundMotion();
|
|
290
|
+
surface.update(surfaceProps());
|
|
291
|
+
};
|
|
292
|
+
const containerOffset = () => {
|
|
293
|
+
const rect = area.getBoundingClientRect();
|
|
294
|
+
return {
|
|
295
|
+
x: rect.left,
|
|
296
|
+
y: rect.top
|
|
297
|
+
};
|
|
298
|
+
};
|
|
299
|
+
const complete = (finalX, finalY) => {
|
|
300
|
+
props.onComplete(finalX, finalY, [...puzzleEvents]);
|
|
301
|
+
};
|
|
302
|
+
const handleMove = (clientX, clientY) => {
|
|
303
|
+
if (!dragging) return;
|
|
304
|
+
const offset = containerOffset();
|
|
305
|
+
posX = clamp(clientX - offset.x - dragOffset.x, 0, CONTAINER_WIDTH);
|
|
306
|
+
posY = clamp(clientY - offset.y - dragOffset.y, 0, CONTAINER_HEIGHT);
|
|
307
|
+
applyPiecePosition();
|
|
308
|
+
applyBackgroundMotion();
|
|
309
|
+
puzzleEvents.push({
|
|
310
|
+
x: posX,
|
|
311
|
+
y: posY,
|
|
312
|
+
t: Date.now()
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
const handleEnd = () => {
|
|
316
|
+
if (!dragging) return;
|
|
317
|
+
dragging = false;
|
|
318
|
+
applyPieceChrome();
|
|
319
|
+
applyBackgroundMotion();
|
|
320
|
+
const lastEvent = puzzleEvents[puzzleEvents.length - 1];
|
|
321
|
+
if (!lastEvent) return;
|
|
322
|
+
complete(lastEvent.x, lastEvent.y);
|
|
323
|
+
};
|
|
324
|
+
const beginDrag = (clientX, clientY) => {
|
|
325
|
+
if (props.submitting) return;
|
|
326
|
+
dragging = true;
|
|
327
|
+
keyboardDragging = false;
|
|
328
|
+
puzzleEvents = [];
|
|
329
|
+
const offset = containerOffset();
|
|
330
|
+
dragOffset = {
|
|
331
|
+
x: clientX - offset.x - posX,
|
|
332
|
+
y: clientY - offset.y - posY
|
|
333
|
+
};
|
|
334
|
+
applyPieceChrome();
|
|
335
|
+
applyBackgroundMotion();
|
|
336
|
+
};
|
|
337
|
+
const moveByKeyboard = (deltaX, deltaY) => {
|
|
338
|
+
if (!keyboardDragging) {
|
|
339
|
+
keyboardDragging = true;
|
|
340
|
+
puzzleEvents = [];
|
|
341
|
+
}
|
|
342
|
+
posX = clamp(posX + deltaX, 0, CONTAINER_WIDTH);
|
|
343
|
+
posY = clamp(posY + deltaY, 0, CONTAINER_HEIGHT);
|
|
344
|
+
applyPiecePosition();
|
|
345
|
+
puzzleEvents.push({
|
|
346
|
+
x: posX,
|
|
347
|
+
y: posY,
|
|
348
|
+
t: Date.now()
|
|
349
|
+
});
|
|
350
|
+
announce(describePosition(posX, posY), ANNOUNCE_DEBOUNCE_MS);
|
|
351
|
+
};
|
|
352
|
+
const keyboardMove = (key, step) => {
|
|
353
|
+
switch (key) {
|
|
354
|
+
case "ArrowLeft": return [-step, 0];
|
|
355
|
+
case "ArrowRight": return [step, 0];
|
|
356
|
+
case "ArrowUp": return [0, -step];
|
|
357
|
+
case "ArrowDown": return [0, step];
|
|
358
|
+
case "Home": return [props.originX - posX, props.originY - posY];
|
|
359
|
+
default: return;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
teardown.addEventListener(piece, "keydown", (event) => {
|
|
363
|
+
if (props.submitting || !(0, _prosopo_procaptcha_common.isEventTrusted)(event)) return;
|
|
364
|
+
const keyboardEvent = event;
|
|
365
|
+
const step = keyboardEvent.shiftKey ? FINE_STEP_PX : STEP_PX;
|
|
366
|
+
const move = keyboardMove(keyboardEvent.key, step);
|
|
367
|
+
if (move) {
|
|
368
|
+
keyboardEvent.preventDefault();
|
|
369
|
+
moveByKeyboard(move[0], move[1]);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if ("Enter" === keyboardEvent.key || " " === keyboardEvent.key) {
|
|
373
|
+
keyboardEvent.preventDefault();
|
|
374
|
+
keyboardDragging = false;
|
|
375
|
+
complete(posX, posY);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
teardown.addEventListener(piece, "focus", () => {
|
|
379
|
+
announce(describePosition(posX, posY));
|
|
380
|
+
});
|
|
381
|
+
teardown.addEventListener(piece, "mousedown", (event) => {
|
|
382
|
+
const mouseEvent = event;
|
|
383
|
+
beginDrag(mouseEvent.clientX, mouseEvent.clientY);
|
|
384
|
+
});
|
|
385
|
+
teardown.addEventListener(piece, "touchstart", (event) => {
|
|
386
|
+
const touch = event.touches[0];
|
|
387
|
+
if (touch) beginDrag(touch.clientX, touch.clientY);
|
|
388
|
+
});
|
|
389
|
+
teardown.addEventListener(document, "mousemove", (event) => {
|
|
390
|
+
const mouseEvent = event;
|
|
391
|
+
handleMove(mouseEvent.clientX, mouseEvent.clientY);
|
|
392
|
+
});
|
|
393
|
+
teardown.addEventListener(document, "mouseup", handleEnd);
|
|
394
|
+
teardown.addEventListener(document, "touchmove", (event) => {
|
|
395
|
+
const touch = event.touches[0];
|
|
396
|
+
if (touch) handleMove(touch.clientX, touch.clientY);
|
|
397
|
+
});
|
|
398
|
+
teardown.addEventListener(document, "touchend", handleEnd);
|
|
399
|
+
render();
|
|
400
|
+
const frame = requestAnimationFrame(() => {
|
|
401
|
+
visible = true;
|
|
402
|
+
render();
|
|
403
|
+
});
|
|
404
|
+
teardown.add(() => cancelAnimationFrame(frame));
|
|
405
|
+
const startShake = () => {
|
|
406
|
+
shaking = true;
|
|
407
|
+
render();
|
|
408
|
+
if (void 0 !== shakeTimer) clearTimeout(shakeTimer);
|
|
409
|
+
shakeTimer = setTimeout(() => {
|
|
410
|
+
shaking = false;
|
|
411
|
+
render();
|
|
412
|
+
}, 500);
|
|
413
|
+
};
|
|
414
|
+
teardown.add(() => {
|
|
415
|
+
if (void 0 !== shakeTimer) clearTimeout(shakeTimer);
|
|
416
|
+
if (void 0 !== announceTimer) clearTimeout(announceTimer);
|
|
417
|
+
});
|
|
418
|
+
const announceSubmitting = () => {
|
|
419
|
+
announce(t("WIDGET.PUZZLE.CHECKING", { defaultValue: "Checking your answer" }));
|
|
420
|
+
};
|
|
421
|
+
const announceRetry = () => {
|
|
422
|
+
announce(t("WIDGET.PUZZLE.RETRY_ANNOUNCEMENT", { defaultValue: "Not quite. A new puzzle has loaded and the piece is back at the start." }));
|
|
423
|
+
};
|
|
424
|
+
if (props.submitting) announceSubmitting();
|
|
425
|
+
else if (props.showRetry) announceRetry();
|
|
426
|
+
if (props.showRetry) startShake();
|
|
427
|
+
return {
|
|
428
|
+
update: (nextProps) => {
|
|
429
|
+
const previous = props;
|
|
430
|
+
props = nextProps;
|
|
431
|
+
if (nextProps.originX !== previous.originX || nextProps.originY !== previous.originY) {
|
|
432
|
+
posX = nextProps.originX;
|
|
433
|
+
posY = nextProps.originY;
|
|
434
|
+
keyboardDragging = false;
|
|
435
|
+
}
|
|
436
|
+
if (nextProps.submitting && !previous.submitting) announceSubmitting();
|
|
437
|
+
if (nextProps.showRetry && !previous.showRetry) announceRetry();
|
|
438
|
+
if (nextProps.showRetry && !previous.showRetry) {
|
|
439
|
+
startShake();
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
render();
|
|
443
|
+
},
|
|
444
|
+
destroy: () => {
|
|
445
|
+
teardown.run();
|
|
446
|
+
surface.destroy();
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
};
|
|
450
|
+
//#endregion
|
|
451
|
+
exports.mountPuzzleCanvas = mountPuzzleCanvas;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
|
|
2
|
+
const require_puzzleCanvas = require("./components/puzzleCanvas.cjs");
|
|
3
|
+
const require_procaptchaWidget = require("./components/procaptchaWidget.cjs");
|
|
4
|
+
const require_procaptchaPuzzle = require("./components/procaptchaPuzzle.cjs");
|
|
5
|
+
exports.loadProcaptchaPuzzle = require_procaptchaPuzzle.loadProcaptchaPuzzle;
|
|
6
|
+
exports.mountProcaptchaPuzzle = require_procaptchaPuzzle.mountProcaptchaPuzzle;
|
|
7
|
+
exports.mountProcaptchaPuzzleWidget = require_procaptchaWidget.default;
|
|
8
|
+
exports.mountPuzzleCanvas = require_puzzleCanvas.mountPuzzleCanvas;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProcaptchaProps } from "@prosopo/types";
|
|
2
|
+
import type { ProcaptchaPuzzleHandle } from "./procaptchaWidget.js";
|
|
3
|
+
export type { ProcaptchaPuzzleHandle };
|
|
4
|
+
export type ProcaptchaPuzzleMountFn = (container: HTMLElement, props: ProcaptchaProps) => ProcaptchaPuzzleHandle;
|
|
5
|
+
export declare const loadProcaptchaPuzzle: () => Promise<ProcaptchaPuzzleMountFn>;
|
|
6
|
+
export declare const mountProcaptchaPuzzle: (container: HTMLElement, props: ProcaptchaProps) => ProcaptchaPuzzleHandle;
|
|
7
|
+
//# sourceMappingURL=procaptchaPuzzle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procaptchaPuzzle.d.ts","sourceRoot":"","sources":["../../src/components/procaptchaPuzzle.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAEpE,YAAY,EAAE,sBAAsB,EAAE,CAAC;AAEvC,MAAM,MAAM,uBAAuB,GAAG,CACrC,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,eAAe,KAClB,sBAAsB,CAAC;AAM5B,eAAO,MAAM,oBAAoB,QACtB,OAAO,CAAC,uBAAuB,CAC2B,CAAC;AAEtE,eAAO,MAAM,qBAAqB,cACtB,WAAW,SACf,eAAe,KACpB,sBAkBF,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/components/procaptchaPuzzle.ts
|
|
2
|
+
/**
|
|
3
|
+
* Dynamic import so the puzzle widget lands in its own chunk, replacing the
|
|
4
|
+
* `lazy()` + `<Suspense>` pair that used to provide the split.
|
|
5
|
+
*/
|
|
6
|
+
var loadProcaptchaPuzzle = async () => (await import("./procaptchaWidget.js")).mountProcaptchaPuzzleWidget;
|
|
7
|
+
var mountProcaptchaPuzzle = (container, props) => {
|
|
8
|
+
let destroyed = false;
|
|
9
|
+
let inner;
|
|
10
|
+
loadProcaptchaPuzzle().then((mount) => {
|
|
11
|
+
if (destroyed) return;
|
|
12
|
+
inner = mount(container, props);
|
|
13
|
+
});
|
|
14
|
+
return { destroy: () => {
|
|
15
|
+
destroyed = true;
|
|
16
|
+
inner?.destroy();
|
|
17
|
+
inner = void 0;
|
|
18
|
+
} };
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { loadProcaptchaPuzzle, mountProcaptchaPuzzle };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procaptchaPuzzle.js","sourceRoot":"","sources":["../../src/components/procaptchaPuzzle.ts"],"names":[],"mappings":"AA4BA,MAAM,CAAC,MAAM,oBAAoB,GAChC,KAAK,IAAsC,EAAE,CAC5C,CAAC,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC,CAAC,2BAA2B,CAAC;AAEtE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACpC,SAAsB,EACtB,KAAsB,EACG,EAAE;IAC3B,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,KAAyC,CAAC;IAE9C,KAAK,oBAAoB,EAAE,CAAC,IAAI,CAAC,CAAC,KAA8B,EAAE,EAAE;QACnE,IAAI,SAAS,EAAE,CAAC;YACf,OAAO;QACR,CAAC;QACD,KAAK,GAAG,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,OAAO;QACN,OAAO,EAAE,GAAG,EAAE;YACb,SAAS,GAAG,IAAI,CAAC;YACjB,KAAK,EAAE,OAAO,EAAE,CAAC;YACjB,KAAK,GAAG,SAAS,CAAC;QACnB,CAAC;KACD,CAAC;AACH,CAAC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ProcaptchaProps } from "@prosopo/types";
|
|
2
|
+
export interface ProcaptchaPuzzleHandle {
|
|
3
|
+
destroy(): void;
|
|
4
|
+
}
|
|
5
|
+
export declare const mountProcaptchaPuzzleWidget: (container: HTMLElement, props: ProcaptchaProps) => ProcaptchaPuzzleHandle;
|
|
6
|
+
export default mountProcaptchaPuzzleWidget;
|
|
7
|
+
//# sourceMappingURL=procaptchaWidget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procaptchaWidget.d.ts","sourceRoot":"","sources":["../../src/components/procaptchaWidget.ts"],"names":[],"mappings":"AA6BA,OAAO,EAGN,KAAK,eAAe,EAGpB,MAAM,gBAAgB,CAAC;AAUxB,MAAM,WAAW,sBAAsB;IACtC,OAAO,IAAI,IAAI,CAAC;CAChB;AAED,eAAO,MAAM,2BAA2B,cAC5B,WAAW,SACf,eAAe,KACpB,sBAiVF,CAAC;AAEF,eAAe,2BAA2B,CAAC"}
|