@prosopo/procaptcha-common 2.13.3 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$cjs.log +4 -4
- package/.turbo/turbo-build$colon$tsc.log +18 -18
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +61 -0
- package/dist/cjs/reactComponents/ChallengeSurface.cjs +44 -1
- package/dist/cjs/reactComponents/Checkbox.cjs +29 -3
- package/dist/reactComponents/ChallengeSurface.d.ts +1 -0
- package/dist/reactComponents/ChallengeSurface.d.ts.map +1 -1
- package/dist/reactComponents/ChallengeSurface.js +44 -1
- package/dist/reactComponents/ChallengeSurface.js.map +1 -1
- package/dist/reactComponents/Checkbox.d.ts +1 -0
- package/dist/reactComponents/Checkbox.d.ts.map +1 -1
- package/dist/reactComponents/Checkbox.js +30 -4
- package/dist/reactComponents/Checkbox.js.map +1 -1
- package/dist/tests/challengeSurfaceRender.unit.test.js +71 -3
- package/dist/tests/challengeSurfaceRender.unit.test.js.map +1 -1
- package/package.json +4 -4
- package/src/reactComponents/ChallengeSurface.tsx +74 -0
- package/src/reactComponents/Checkbox.tsx +51 -2
- package/src/tests/challengeSurfaceRender.unit.test.tsx +111 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/procaptcha-common@2.
|
|
2
|
+
> @prosopo/procaptcha-common@2.14.0 build:cjs
|
|
3
3
|
> NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV
|
|
4
4
|
|
|
5
5
|
ViteCommonJSConfig: .
|
|
@@ -57,7 +57,7 @@ dist/cjs/index.cjs 2.45 kB │ gzip: 0.63 kB
|
|
|
57
57
|
dist/cjs/reactComponents/Reload.cjs 2.94 kB │ gzip: 1.30 kB
|
|
58
58
|
dist/cjs/state/builder.cjs 3.13 kB │ gzip: 0.84 kB
|
|
59
59
|
dist/cjs/callbacks/defaultCallbacks.cjs 3.84 kB │ gzip: 1.03 kB
|
|
60
|
-
dist/cjs/reactComponents/
|
|
61
|
-
dist/cjs/reactComponents/
|
|
60
|
+
dist/cjs/reactComponents/Checkbox.cjs 6.21 kB │ gzip: 2.42 kB
|
|
61
|
+
dist/cjs/reactComponents/ChallengeSurface.cjs 6.72 kB │ gzip: 2.29 kB
|
|
62
62
|
|
|
63
|
-
[32m✓ built in
|
|
63
|
+
[32m✓ built in 97ms[39m
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/procaptcha-common@2.
|
|
2
|
+
> @prosopo/procaptcha-common@2.14.0 build:tsc
|
|
3
3
|
> tsc --build --verbose
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
5:39:09 AM - Projects in this build:
|
|
6
6
|
* ../../dev/config/tsconfig.json
|
|
7
7
|
* ../locale/tsconfig.json
|
|
8
8
|
* ../util/tsconfig.json
|
|
@@ -18,35 +18,35 @@
|
|
|
18
18
|
* ../account/tsconfig.json
|
|
19
19
|
* tsconfig.json
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
5:39:09 AM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/webpack/webpack.config.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
5:39:09 AM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/translationKey.ts' is older than output '../locale/tsconfig.tsbuildinfo'
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
5:39:09 AM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
5:39:09 AM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/index.ts' is older than output '../logger/tsconfig.tsbuildinfo'
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
5:39:09 AM - Project '../util-crypto/tsconfig.json' is up to date because newest input '../util-crypto/src/types.ts' is older than output '../util-crypto/tsconfig.tsbuildinfo'
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
5:39:09 AM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/types.test-d.ts' is older than output '../types/tsconfig.tsbuildinfo'
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
5:39:09 AM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/index.ts' is older than output '../common/tsconfig.tsbuildinfo'
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
5:39:09 AM - Project '../load-balancer/tsconfig.json' is up to date because newest input '../load-balancer/src/tests/productionProviderList.unit.test.ts' is older than output '../load-balancer/tsconfig.tsbuildinfo'
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
5:39:09 AM - Project '../widget-skeleton/tsconfig.json' is up to date because newest input '../widget-skeleton/src/webComponent/createWidget.ts' is older than output '../widget-skeleton/tsconfig.tsbuildinfo'
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
5:39:09 AM - Project '../fingerprintjs/tsconfig.json' is up to date because newest input '../fingerprintjs/src/utils/browser.ts' is older than output '../fingerprintjs/tsconfig.tsbuildinfo'
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
5:39:09 AM - Project '../fingerprint/tsconfig.json' is up to date because newest input '../fingerprint/src/index.ts' is older than output '../fingerprint/tsconfig.tsbuildinfo'
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
5:39:09 AM - Project '../keyring/tsconfig.json' is up to date because newest input '../keyring/src/keyring/keyring.test.ts' is older than output '../keyring/tsconfig.tsbuildinfo'
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
5:39:09 AM - Project '../account/tsconfig.json' is out of date because output file '../account/tsconfig.tsbuildinfo' does not exist
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
5:39:09 AM - Building project '/home/runner/work/captcha/captcha/packages/account/tsconfig.json'...
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
5:39:12 AM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
5:39:12 AM - Building project '/home/runner/work/captcha/captcha/packages/procaptcha-common/tsconfig.json'...
|
|
52
52
|
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
> @prosopo/procaptcha-common@2.
|
|
2
|
+
> @prosopo/procaptcha-common@2.14.0 build
|
|
3
3
|
> npm run build:cross-env -- --mode ${NODE_ENV:-development}
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
> @prosopo/procaptcha-common@2.
|
|
6
|
+
> @prosopo/procaptcha-common@2.14.0 build:cross-env
|
|
7
7
|
> vite build --config vite.esm.config.ts --mode production
|
|
8
8
|
|
|
9
9
|
ViteEsmConfig: .
|
|
@@ -61,7 +61,7 @@ dist/reactComponents/Honeypot.js 1.86 kB │ gzip: 0.89 kB
|
|
|
61
61
|
dist/reactComponents/Reload.js 2.69 kB │ gzip: 1.26 kB
|
|
62
62
|
dist/state/builder.js 3.09 kB │ gzip: 0.84 kB
|
|
63
63
|
dist/callbacks/defaultCallbacks.js 3.64 kB │ gzip: 1.00 kB
|
|
64
|
-
dist/reactComponents/
|
|
65
|
-
dist/reactComponents/
|
|
64
|
+
dist/reactComponents/Checkbox.js 5.66 kB │ gzip: 2.34 kB
|
|
65
|
+
dist/reactComponents/ChallengeSurface.js 6.39 kB │ gzip: 2.21 kB
|
|
66
66
|
|
|
67
|
-
[32m✓ built in
|
|
67
|
+
[32m✓ built in 75ms[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# @prosopo/procaptcha-common
|
|
2
2
|
|
|
3
|
+
## 2.14.0
|
|
4
|
+
### Minor Changes
|
|
5
|
+
|
|
6
|
+
- 864ddde: Make the challenges usable with a keyboard and a screen reader.
|
|
7
|
+
|
|
8
|
+
The puzzle could only be solved by dragging with a mouse or a finger. The piece
|
|
9
|
+
was a plain `div`, so it could not be tabbed to, had no name or role, and a
|
|
10
|
+
screen reader announced nothing at all — a user on assistive tech could tick "I
|
|
11
|
+
am human", get a silent overlay, and have no way forward. The image captcha had
|
|
12
|
+
the same problem in its tiles.
|
|
13
|
+
|
|
14
|
+
What changed:
|
|
15
|
+
|
|
16
|
+
- The puzzle piece can now be focused and moved with the arrow keys (hold shift
|
|
17
|
+
for smaller steps, Home to start over, Enter or Space to submit). It has a
|
|
18
|
+
name, a role, and a visible focus ring.
|
|
19
|
+
- The puzzle announces its state as you go: where the piece is, as a percentage
|
|
20
|
+
across and down the board; that an answer is being checked; and that a failed
|
|
21
|
+
go has been replaced by a fresh puzzle.
|
|
22
|
+
- The image captcha tiles are now buttons rather than clickable `div`s, so they
|
|
23
|
+
can be tabbed to and activated with Enter or Space, and they report whether
|
|
24
|
+
they are selected instead of only looking selected.
|
|
25
|
+
- Both challenges now open as a proper dialog: it takes focus when it opens,
|
|
26
|
+
keeps Tab inside itself, and gives focus back to the checkbox on close.
|
|
27
|
+
- The spinner that replaces the checkbox while a check runs used to drop focus
|
|
28
|
+
to the top of the page without saying why. It now takes focus in the
|
|
29
|
+
checkbox's place, names itself, and hands focus back when the check finishes.
|
|
30
|
+
This affects the pow, image and puzzle flows.
|
|
31
|
+
- The puzzle's on-screen text was hardcoded English. It now goes through the
|
|
32
|
+
locale package, and the new strings are translated into all 32 locales.
|
|
33
|
+
|
|
34
|
+
A visual puzzle still cannot be solved by someone who cannot see it — the widget
|
|
35
|
+
is never told where the target is, so there is nothing it could describe. Sites
|
|
36
|
+
that need a challenge a blind user can complete should use the pow captcha type,
|
|
37
|
+
which needs no interaction beyond the checkbox.
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- 028a158: Stop the widget taking focus off the form the user is filling in.
|
|
42
|
+
|
|
43
|
+
While the widget checks a user, its checkbox is replaced by a spinner, and the
|
|
44
|
+
box takes focus back when the check finishes so a keyboard user is not left
|
|
45
|
+
stranded at the top of the page. It did that unconditionally, so if the user
|
|
46
|
+
spent the wait typing into the page's own form, the box grabbed focus off
|
|
47
|
+
whatever field they were in — and the keystroke that arrived with it went
|
|
48
|
+
nowhere. It now only claims focus back when nothing else holds it, which is
|
|
49
|
+
where removing the spinner leaves it.
|
|
50
|
+
|
|
51
|
+
This is what made the `puzzle` cypress spec flaky: it fills the signup form as
|
|
52
|
+
soon as the puzzle is solved, and lost the race with the widget often enough to
|
|
53
|
+
fail CI.
|
|
54
|
+
|
|
55
|
+
Covered by a new unit test in `procaptcha-react` that focuses an element outside
|
|
56
|
+
the widget mid-check and asserts focus is still there afterwards.
|
|
57
|
+
- Updated dependencies [028a158]
|
|
58
|
+
- Updated dependencies [3958046]
|
|
59
|
+
- Updated dependencies [028a158]
|
|
60
|
+
- @prosopo/types@5.8.3
|
|
61
|
+
- @prosopo/account@2.8.92
|
|
62
|
+
- @prosopo/load-balancer@2.10.44
|
|
63
|
+
|
|
3
64
|
## 2.13.3
|
|
4
65
|
### Patch Changes
|
|
5
66
|
|
|
@@ -5,6 +5,15 @@ react = require_runtime.__toESM(react, 1);
|
|
|
5
5
|
let _emotion_react_jsx_runtime = require("@emotion/react/jsx-runtime");
|
|
6
6
|
let react_dom = require("react-dom");
|
|
7
7
|
//#region src/reactComponents/ChallengeSurface.tsx
|
|
8
|
+
var FOCUSABLE_SELECTOR = [
|
|
9
|
+
"a[href]",
|
|
10
|
+
"button:not([disabled])",
|
|
11
|
+
"input:not([disabled])",
|
|
12
|
+
"select:not([disabled])",
|
|
13
|
+
"textarea:not([disabled])",
|
|
14
|
+
"[tabindex]:not([tabindex=\"-1\"])"
|
|
15
|
+
].join(",");
|
|
16
|
+
var focusableWithin = (root) => Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR));
|
|
8
17
|
var SURFACE_Z_INDEX = 2147483646;
|
|
9
18
|
var CONTENT_Z_INDEX = 2147483647;
|
|
10
19
|
var POPUP_EDGE_GAP_PX = 8;
|
|
@@ -24,7 +33,7 @@ var computeFloatPosition = (anchorRect, panelHeight, scrollX, scrollY) => ({
|
|
|
24
33
|
left: anchorRect.left + scrollX
|
|
25
34
|
});
|
|
26
35
|
var ChallengeSurface = react.default.memo((props) => {
|
|
27
|
-
const { show, children, placement = _prosopo_types.PlacementEnum.popup, anchor, scrim = "none", onDismiss, className } = props;
|
|
36
|
+
const { show, children, placement = _prosopo_types.PlacementEnum.popup, anchor, scrim = "none", onDismiss, className, dialogLabel } = props;
|
|
28
37
|
const contentRef = (0, react.useRef)(null);
|
|
29
38
|
const [floatPosition, setFloatPosition] = (0, react.useState)(null);
|
|
30
39
|
const isFloating = placement === _prosopo_types.PlacementEnum.float && !!anchor;
|
|
@@ -69,6 +78,36 @@ var ChallengeSurface = react.default.memo((props) => {
|
|
|
69
78
|
document.addEventListener("keydown", onKeyDown);
|
|
70
79
|
return () => document.removeEventListener("keydown", onKeyDown);
|
|
71
80
|
}, [show, onDismiss]);
|
|
81
|
+
(0, react.useEffect)(() => {
|
|
82
|
+
if (!show || !dialogLabel) return;
|
|
83
|
+
const content = contentRef.current;
|
|
84
|
+
if (!content) return;
|
|
85
|
+
const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
86
|
+
(focusableWithin(content)[0] ?? content).focus();
|
|
87
|
+
return () => opener?.focus();
|
|
88
|
+
}, [show, dialogLabel]);
|
|
89
|
+
(0, react.useEffect)(() => {
|
|
90
|
+
if (!show || !dialogLabel) return;
|
|
91
|
+
const onKeyDown = (event) => {
|
|
92
|
+
if (event.key !== "Tab") return;
|
|
93
|
+
const content = contentRef.current;
|
|
94
|
+
if (!content) return;
|
|
95
|
+
const focusable = focusableWithin(content);
|
|
96
|
+
const first = focusable[0] ?? content;
|
|
97
|
+
const last = focusable[focusable.length - 1] ?? content;
|
|
98
|
+
const active = document.activeElement;
|
|
99
|
+
const outside = !content.contains(active);
|
|
100
|
+
if (event.shiftKey && (outside || active === first)) {
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
last.focus();
|
|
103
|
+
} else if (!event.shiftKey && (outside || active === last)) {
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
first.focus();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
document.addEventListener("keydown", onKeyDown);
|
|
109
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
110
|
+
}, [show, dialogLabel]);
|
|
72
111
|
(0, react.useEffect)(() => {
|
|
73
112
|
if (!show || !isFloating || !onDismiss) return;
|
|
74
113
|
const onPointerDown = (event) => {
|
|
@@ -136,6 +175,10 @@ var ChallengeSurface = react.default.memo((props) => {
|
|
|
136
175
|
ref: contentRef,
|
|
137
176
|
className: "prosopo-challenge-content",
|
|
138
177
|
style: contentStyle,
|
|
178
|
+
role: dialogLabel ? "dialog" : void 0,
|
|
179
|
+
"aria-modal": dialogLabel ? true : void 0,
|
|
180
|
+
"aria-label": dialogLabel,
|
|
181
|
+
tabIndex: dialogLabel ? -1 : void 0,
|
|
139
182
|
children
|
|
140
183
|
})
|
|
141
184
|
}), document.body);
|
|
@@ -37,12 +37,29 @@ var baseStyle = {
|
|
|
37
37
|
borderWidth: "2px"
|
|
38
38
|
};
|
|
39
39
|
var ID_LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
40
|
+
/**
|
|
41
|
+
* Whether nothing on the page holds focus, which is where the browser leaves it
|
|
42
|
+
* when the focused element is removed.
|
|
43
|
+
*/
|
|
44
|
+
var focusIsStranded = (element) => {
|
|
45
|
+
const { activeElement, body } = element.ownerDocument;
|
|
46
|
+
return activeElement === null || activeElement === body;
|
|
47
|
+
};
|
|
40
48
|
var FAQ_LINK = process.env.PROSOPO_DOCS_URL ? `${new URL(`${process.env.PROSOPO_DOCS_URL}/en/basics/faq/`).href}/` : "https://docs.prosopo.io/en/basics/faq/";
|
|
41
49
|
var generateRandomId = () => {
|
|
42
50
|
return Array.from({ length: 8 }, () => ID_LETTERS[Math.floor(Math.random() * ID_LETTERS.length)]).join("");
|
|
43
51
|
};
|
|
44
|
-
var Checkbox = ({ theme, onChange, checked, labelText, error, loading }) => {
|
|
52
|
+
var Checkbox = ({ theme, onChange, checked, labelText, error, loading, loadingText = "Checking that you are human" }) => {
|
|
45
53
|
const [hover, setHover] = (0, react.useState)(false);
|
|
54
|
+
const inputRef = (0, react.useRef)(null);
|
|
55
|
+
const spinnerRef = (0, react.useRef)(null);
|
|
56
|
+
const hadFocus = (0, react.useRef)(false);
|
|
57
|
+
(0, react.useEffect)(() => {
|
|
58
|
+
if (!hadFocus.current) return;
|
|
59
|
+
const target = loading ? spinnerRef.current : inputRef.current;
|
|
60
|
+
if (!target || !focusIsStranded(target)) return;
|
|
61
|
+
target.focus();
|
|
62
|
+
}, [loading]);
|
|
46
63
|
const checkboxFocus = (0, react.useMemo)(() => _emotion_react.css`
|
|
47
64
|
&:focus-visible {
|
|
48
65
|
outline: 3px solid ${theme.palette.primary.main};
|
|
@@ -104,20 +121,29 @@ var Checkbox = ({ theme, onChange, checked, labelText, error, loading }) => {
|
|
|
104
121
|
minHeight: "58px"
|
|
105
122
|
},
|
|
106
123
|
children: [loading ? /* @__PURE__ */ (0, _emotion_react_jsx_runtime.jsx)("div", {
|
|
124
|
+
ref: spinnerRef,
|
|
107
125
|
className: _prosopo_widget_skeleton.WIDGET_CHECKBOX_SPINNER_CSS_CLASS,
|
|
108
|
-
|
|
126
|
+
role: "status",
|
|
127
|
+
tabIndex: -1,
|
|
128
|
+
"aria-label": loadingText
|
|
109
129
|
}) : /* @__PURE__ */ (0, _emotion_react_jsx_runtime.jsx)("input", {
|
|
130
|
+
ref: inputRef,
|
|
110
131
|
name: id,
|
|
111
132
|
id,
|
|
112
133
|
onMouseEnter: () => setHover(true),
|
|
113
134
|
onMouseLeave: () => setHover(false),
|
|
135
|
+
onFocus: () => {
|
|
136
|
+
hadFocus.current = true;
|
|
137
|
+
},
|
|
138
|
+
onBlur: () => {
|
|
139
|
+
hadFocus.current = false;
|
|
140
|
+
},
|
|
114
141
|
css: [
|
|
115
142
|
checkboxBefore,
|
|
116
143
|
checkboxForcedColors,
|
|
117
144
|
checkboxFocus
|
|
118
145
|
],
|
|
119
146
|
type: "checkbox",
|
|
120
|
-
"aria-live": "assertive",
|
|
121
147
|
"aria-label": labelText,
|
|
122
148
|
onKeyDown: (e) => {
|
|
123
149
|
if (!require_trust.isEventTrusted(e)) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChallengeSurface.d.ts","sourceRoot":"","sources":["../../src/reactComponents/ChallengeSurface.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,KAAK,EAAE,EAEb,KAAK,SAAS,EAMd,MAAM,OAAO,CAAC;AAUf,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,CAAC;AAE1C,UAAU,qBAAqB;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,aAAa,CAAC;IAE1B,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,EAAE,YAAY,CAAC;IAErB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ChallengeSurface.d.ts","sourceRoot":"","sources":["../../src/reactComponents/ChallengeSurface.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,KAAK,EAAE,EAEb,KAAK,SAAS,EAMd,MAAM,OAAO,CAAC;AAUf,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,CAAC;AAE1C,UAAU,qBAAqB;IAC9B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,aAAa,CAAC;IAE1B,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,EAAE,YAAY,CAAC;IAErB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IAOnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAwBD,UAAU,aAAa;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACb;AAWD,QAAA,MAAM,oBAAoB,eACb,OAAO,eACN,MAAM,WACV,MAAM,WACN,MAAM,KACb,aAKD,CAAC;AAEH,QAAA,MAAM,gBAAgB,oCAAsB,qBAAqB,8BAoO/D,CAAC;AAIH,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -3,6 +3,15 @@ import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from
|
|
|
3
3
|
import { jsx } from "@emotion/react/jsx-runtime";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
5
|
//#region src/reactComponents/ChallengeSurface.tsx
|
|
6
|
+
var FOCUSABLE_SELECTOR = [
|
|
7
|
+
"a[href]",
|
|
8
|
+
"button:not([disabled])",
|
|
9
|
+
"input:not([disabled])",
|
|
10
|
+
"select:not([disabled])",
|
|
11
|
+
"textarea:not([disabled])",
|
|
12
|
+
"[tabindex]:not([tabindex=\"-1\"])"
|
|
13
|
+
].join(",");
|
|
14
|
+
var focusableWithin = (root) => Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR));
|
|
6
15
|
var SURFACE_Z_INDEX = 2147483646;
|
|
7
16
|
var CONTENT_Z_INDEX = 2147483647;
|
|
8
17
|
var POPUP_EDGE_GAP_PX = 8;
|
|
@@ -22,7 +31,7 @@ var computeFloatPosition = (anchorRect, panelHeight, scrollX, scrollY) => ({
|
|
|
22
31
|
left: anchorRect.left + scrollX
|
|
23
32
|
});
|
|
24
33
|
var ChallengeSurface = React.memo((props) => {
|
|
25
|
-
const { show, children, placement = PlacementEnum.popup, anchor, scrim = "none", onDismiss, className } = props;
|
|
34
|
+
const { show, children, placement = PlacementEnum.popup, anchor, scrim = "none", onDismiss, className, dialogLabel } = props;
|
|
26
35
|
const contentRef = useRef(null);
|
|
27
36
|
const [floatPosition, setFloatPosition] = useState(null);
|
|
28
37
|
const isFloating = placement === PlacementEnum.float && !!anchor;
|
|
@@ -67,6 +76,36 @@ var ChallengeSurface = React.memo((props) => {
|
|
|
67
76
|
document.addEventListener("keydown", onKeyDown);
|
|
68
77
|
return () => document.removeEventListener("keydown", onKeyDown);
|
|
69
78
|
}, [show, onDismiss]);
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (!show || !dialogLabel) return;
|
|
81
|
+
const content = contentRef.current;
|
|
82
|
+
if (!content) return;
|
|
83
|
+
const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
84
|
+
(focusableWithin(content)[0] ?? content).focus();
|
|
85
|
+
return () => opener?.focus();
|
|
86
|
+
}, [show, dialogLabel]);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (!show || !dialogLabel) return;
|
|
89
|
+
const onKeyDown = (event) => {
|
|
90
|
+
if (event.key !== "Tab") return;
|
|
91
|
+
const content = contentRef.current;
|
|
92
|
+
if (!content) return;
|
|
93
|
+
const focusable = focusableWithin(content);
|
|
94
|
+
const first = focusable[0] ?? content;
|
|
95
|
+
const last = focusable[focusable.length - 1] ?? content;
|
|
96
|
+
const active = document.activeElement;
|
|
97
|
+
const outside = !content.contains(active);
|
|
98
|
+
if (event.shiftKey && (outside || active === first)) {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
last.focus();
|
|
101
|
+
} else if (!event.shiftKey && (outside || active === last)) {
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
first.focus();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
document.addEventListener("keydown", onKeyDown);
|
|
107
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
108
|
+
}, [show, dialogLabel]);
|
|
70
109
|
useEffect(() => {
|
|
71
110
|
if (!show || !isFloating || !onDismiss) return;
|
|
72
111
|
const onPointerDown = (event) => {
|
|
@@ -134,6 +173,10 @@ var ChallengeSurface = React.memo((props) => {
|
|
|
134
173
|
ref: contentRef,
|
|
135
174
|
className: "prosopo-challenge-content",
|
|
136
175
|
style: contentStyle,
|
|
176
|
+
role: dialogLabel ? "dialog" : void 0,
|
|
177
|
+
"aria-modal": dialogLabel ? true : void 0,
|
|
178
|
+
"aria-label": dialogLabel,
|
|
179
|
+
tabIndex: dialogLabel ? -1 : void 0,
|
|
137
180
|
children
|
|
138
181
|
})
|
|
139
182
|
}), document.body);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChallengeSurface.js","sourceRoot":"","sources":["../../src/reactComponents/ChallengeSurface.tsx"],"names":[],"mappings":";AAcA,OAAO,EAAE,aAAa,EAAsB,MAAM,gBAAgB,CAAC;AACnE,OAAO,KAAK,EAAE,EAGb,WAAW,EACX,SAAS,EACT,eAAe,EACf,MAAM,EACN,QAAQ,GACR,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"ChallengeSurface.js","sourceRoot":"","sources":["../../src/reactComponents/ChallengeSurface.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.d.ts","sourceRoot":"","sources":["../../src/reactComponents/Checkbox.tsx"],"names":[],"mappings":"AAgBA,OAAO,EACN,KAAK,KAAK,EAGV,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACN,KAAK,oBAAoB,EAEzB,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"Checkbox.d.ts","sourceRoot":"","sources":["../../src/reactComponents/Checkbox.tsx"],"names":[],"mappings":"AAgBA,OAAO,EACN,KAAK,KAAK,EAGV,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACN,KAAK,oBAAoB,EAEzB,KAAK,EAAE,EAKP,MAAM,OAAO,CAAC;AAGf,UAAU,aAAc,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;IACtE,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IAEjB,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IAEjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAmED,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC,aAAa,CA8LtC,CAAC;AACF,eAAe,QAAQ,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isEventTrusted } from "../events/trust.js";
|
|
2
2
|
import { WIDGET_CHECKBOX_SPINNER_CSS_CLASS, withAlpha } from "@prosopo/widget-skeleton";
|
|
3
|
-
import { useMemo, useState } from "react";
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { jsx, jsxs } from "@emotion/react/jsx-runtime";
|
|
5
5
|
import { css } from "@emotion/react";
|
|
6
6
|
import styled from "@emotion/styled";
|
|
@@ -35,12 +35,29 @@ var baseStyle = {
|
|
|
35
35
|
borderWidth: "2px"
|
|
36
36
|
};
|
|
37
37
|
var ID_LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
38
|
+
/**
|
|
39
|
+
* Whether nothing on the page holds focus, which is where the browser leaves it
|
|
40
|
+
* when the focused element is removed.
|
|
41
|
+
*/
|
|
42
|
+
var focusIsStranded = (element) => {
|
|
43
|
+
const { activeElement, body } = element.ownerDocument;
|
|
44
|
+
return activeElement === null || activeElement === body;
|
|
45
|
+
};
|
|
38
46
|
var FAQ_LINK = process.env.PROSOPO_DOCS_URL ? `${new URL(`${process.env.PROSOPO_DOCS_URL}/en/basics/faq/`).href}/` : "https://docs.prosopo.io/en/basics/faq/";
|
|
39
47
|
var generateRandomId = () => {
|
|
40
48
|
return Array.from({ length: 8 }, () => ID_LETTERS[Math.floor(Math.random() * ID_LETTERS.length)]).join("");
|
|
41
49
|
};
|
|
42
|
-
var Checkbox = ({ theme, onChange, checked, labelText, error, loading }) => {
|
|
50
|
+
var Checkbox = ({ theme, onChange, checked, labelText, error, loading, loadingText = "Checking that you are human" }) => {
|
|
43
51
|
const [hover, setHover] = useState(false);
|
|
52
|
+
const inputRef = useRef(null);
|
|
53
|
+
const spinnerRef = useRef(null);
|
|
54
|
+
const hadFocus = useRef(false);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!hadFocus.current) return;
|
|
57
|
+
const target = loading ? spinnerRef.current : inputRef.current;
|
|
58
|
+
if (!target || !focusIsStranded(target)) return;
|
|
59
|
+
target.focus();
|
|
60
|
+
}, [loading]);
|
|
44
61
|
const checkboxFocus = useMemo(() => css`
|
|
45
62
|
&:focus-visible {
|
|
46
63
|
outline: 3px solid ${theme.palette.primary.main};
|
|
@@ -102,20 +119,29 @@ var Checkbox = ({ theme, onChange, checked, labelText, error, loading }) => {
|
|
|
102
119
|
minHeight: "58px"
|
|
103
120
|
},
|
|
104
121
|
children: [loading ? /* @__PURE__ */ jsx("div", {
|
|
122
|
+
ref: spinnerRef,
|
|
105
123
|
className: WIDGET_CHECKBOX_SPINNER_CSS_CLASS,
|
|
106
|
-
|
|
124
|
+
role: "status",
|
|
125
|
+
tabIndex: -1,
|
|
126
|
+
"aria-label": loadingText
|
|
107
127
|
}) : /* @__PURE__ */ jsx("input", {
|
|
128
|
+
ref: inputRef,
|
|
108
129
|
name: id,
|
|
109
130
|
id,
|
|
110
131
|
onMouseEnter: () => setHover(true),
|
|
111
132
|
onMouseLeave: () => setHover(false),
|
|
133
|
+
onFocus: () => {
|
|
134
|
+
hadFocus.current = true;
|
|
135
|
+
},
|
|
136
|
+
onBlur: () => {
|
|
137
|
+
hadFocus.current = false;
|
|
138
|
+
},
|
|
112
139
|
css: [
|
|
113
140
|
checkboxBefore,
|
|
114
141
|
checkboxForcedColors,
|
|
115
142
|
checkboxFocus
|
|
116
143
|
],
|
|
117
144
|
type: "checkbox",
|
|
118
|
-
"aria-live": "assertive",
|
|
119
145
|
"aria-label": labelText,
|
|
120
146
|
onKeyDown: (e) => {
|
|
121
147
|
if (!isEventTrusted(e)) return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../src/reactComponents/Checkbox.tsx"],"names":[],"mappings":";AAcA,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,MAAM,MAAM,iBAAiB,CAAC;AACrC,OAAO,EAEN,iCAAiC,EACjC,SAAS,GACT,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAIN,OAAO,EACP,QAAQ,GACR,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"Checkbox.js","sourceRoot":"","sources":["../../src/reactComponents/Checkbox.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "@emotion/react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
|
|
2
2
|
import { PlacementEnum } from "@prosopo/types";
|
|
3
3
|
import { createRoot } from "react-dom/client";
|
|
4
4
|
import { act } from "react-dom/test-utils";
|
|
@@ -9,9 +9,15 @@ let root;
|
|
|
9
9
|
let anchor;
|
|
10
10
|
const layer = () => document.querySelector(".prosopo-challenge-surface");
|
|
11
11
|
const content = () => document.querySelector(".prosopo-challenge-content");
|
|
12
|
-
const render = ({ placement, withAnchor = true, onDismiss, show = true, }) => {
|
|
12
|
+
const render = ({ placement, withAnchor = true, onDismiss, show = true, dialogLabel, focusableChildren = 0, }) => {
|
|
13
13
|
act(() => {
|
|
14
|
-
root.render(
|
|
14
|
+
root.render(_jsxs(ChallengeSurface, { show: show, placement: placement, anchor: withAnchor ? anchor : null, onDismiss: onDismiss, dialogLabel: dialogLabel, children: [_jsx("div", { "data-testid": "challenge", children: "challenge" }), Array.from({ length: focusableChildren }, (_, index) => `button-${index}`).map((id) => (_jsx("button", { type: "button", children: id }, id)))] }));
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
const buttons = () => Array.from(content()?.querySelectorAll("button") ?? []);
|
|
18
|
+
const pressTab = (shiftKey = false) => {
|
|
19
|
+
act(() => {
|
|
20
|
+
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Tab", bubbles: true, shiftKey }));
|
|
15
21
|
});
|
|
16
22
|
};
|
|
17
23
|
beforeEach(() => {
|
|
@@ -132,4 +138,66 @@ describe("dismissing with the keyboard", () => {
|
|
|
132
138
|
expect(onDismiss).not.toHaveBeenCalled();
|
|
133
139
|
});
|
|
134
140
|
});
|
|
141
|
+
describe("as a dialog", () => {
|
|
142
|
+
it("stays inert for a challenge that does not name itself", () => {
|
|
143
|
+
render({});
|
|
144
|
+
expect(content()?.getAttribute("role")).toBeNull();
|
|
145
|
+
expect(content()?.getAttribute("aria-modal")).toBeNull();
|
|
146
|
+
});
|
|
147
|
+
it("names itself to assistive tech once a label is given", () => {
|
|
148
|
+
render({ dialogLabel: "Puzzle challenge" });
|
|
149
|
+
expect(content()?.getAttribute("role")).toBe("dialog");
|
|
150
|
+
expect(content()?.getAttribute("aria-modal")).toBe("true");
|
|
151
|
+
expect(content()?.getAttribute("aria-label")).toBe("Puzzle challenge");
|
|
152
|
+
});
|
|
153
|
+
it("takes focus so a screen reader lands on the challenge", () => {
|
|
154
|
+
render({ dialogLabel: "Puzzle challenge", focusableChildren: 2 });
|
|
155
|
+
expect(document.activeElement).toBe(buttons()[0]);
|
|
156
|
+
});
|
|
157
|
+
it("falls back to the panel when it holds nothing focusable", () => {
|
|
158
|
+
render({ dialogLabel: "Puzzle challenge" });
|
|
159
|
+
expect(document.activeElement).toBe(content());
|
|
160
|
+
});
|
|
161
|
+
it("gives focus back to whatever opened it", () => {
|
|
162
|
+
const opener = document.createElement("button");
|
|
163
|
+
document.body.appendChild(opener);
|
|
164
|
+
opener.focus();
|
|
165
|
+
render({ dialogLabel: "Puzzle challenge", focusableChildren: 1 });
|
|
166
|
+
expect(document.activeElement).not.toBe(opener);
|
|
167
|
+
act(() => root.unmount());
|
|
168
|
+
expect(document.activeElement).toBe(opener);
|
|
169
|
+
opener.remove();
|
|
170
|
+
root = createRoot(container);
|
|
171
|
+
});
|
|
172
|
+
it("wraps tab from the last control back to the first", () => {
|
|
173
|
+
render({ dialogLabel: "Puzzle challenge", focusableChildren: 2 });
|
|
174
|
+
const [first, last] = buttons();
|
|
175
|
+
last?.focus();
|
|
176
|
+
pressTab();
|
|
177
|
+
expect(document.activeElement).toBe(first);
|
|
178
|
+
});
|
|
179
|
+
it("wraps shift-tab from the first control back to the last", () => {
|
|
180
|
+
render({ dialogLabel: "Puzzle challenge", focusableChildren: 2 });
|
|
181
|
+
const [first, last] = buttons();
|
|
182
|
+
first?.focus();
|
|
183
|
+
pressTab(true);
|
|
184
|
+
expect(document.activeElement).toBe(last);
|
|
185
|
+
});
|
|
186
|
+
it("pulls focus back in if it has escaped to the page", () => {
|
|
187
|
+
const outside = document.createElement("button");
|
|
188
|
+
document.body.appendChild(outside);
|
|
189
|
+
render({ dialogLabel: "Puzzle challenge", focusableChildren: 2 });
|
|
190
|
+
outside.focus();
|
|
191
|
+
pressTab();
|
|
192
|
+
expect(document.activeElement).toBe(buttons()[0]);
|
|
193
|
+
outside.remove();
|
|
194
|
+
});
|
|
195
|
+
it("leaves tab alone for a challenge that is not a dialog", () => {
|
|
196
|
+
render({ focusableChildren: 2 });
|
|
197
|
+
const [, last] = buttons();
|
|
198
|
+
last?.focus();
|
|
199
|
+
pressTab();
|
|
200
|
+
expect(document.activeElement).toBe(last);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
135
203
|
//# sourceMappingURL=challengeSurfaceRender.unit.test.js.map
|