@prosopo/procaptcha-common 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 +18 -18
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +74 -0
- package/dist/callbacks/defaultCallbacks.d.ts.map +1 -1
- package/dist/callbacks/defaultCallbacks.js +1 -4
- package/dist/callbacks/defaultCallbacks.js.map +1 -1
- package/dist/cjs/callbacks/defaultCallbacks.cjs +1 -4
- package/dist/cjs/elements/window.cjs +5 -2
- package/dist/cjs/reactComponents/ChallengeSurface.cjs +44 -1
- package/dist/cjs/reactComponents/Checkbox.cjs +29 -3
- package/dist/elements/window.d.ts +3 -1
- package/dist/elements/window.d.ts.map +1 -1
- package/dist/elements/window.js +5 -2
- package/dist/elements/window.js.map +1 -1
- 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 +9 -9
- package/src/callbacks/defaultCallbacks.ts +5 -7
- package/src/elements/window.ts +13 -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,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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"challengeSurfaceRender.unit.test.js","sourceRoot":"","sources":["../../src/tests/challengeSurfaceRender.unit.test.tsx"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"challengeSurfaceRender.unit.test.js","sourceRoot":"","sources":["../../src/tests/challengeSurfaceRender.unit.test.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prosopo/procaptcha-common",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.1",
|
|
4
4
|
"author": "PROSOPO LIMITED <info@prosopo.io>",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,19 +34,19 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@emotion/react": "11.14.0",
|
|
36
36
|
"@emotion/styled": "11.14.1",
|
|
37
|
-
"@prosopo/account": "2.8.
|
|
38
|
-
"@prosopo/load-balancer": "2.10.
|
|
39
|
-
"@prosopo/types": "5.8.
|
|
37
|
+
"@prosopo/account": "2.8.93",
|
|
38
|
+
"@prosopo/load-balancer": "2.10.45",
|
|
39
|
+
"@prosopo/types": "5.8.4",
|
|
40
40
|
"@prosopo/widget-skeleton": "2.8.7",
|
|
41
|
-
"react": "19.
|
|
42
|
-
"react-dom": "19.
|
|
41
|
+
"react": "19.3.0",
|
|
42
|
+
"react-dom": "19.3.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@prosopo/config": "3.3.
|
|
45
|
+
"@prosopo/config": "3.3.17",
|
|
46
46
|
"@types/jsdom": "30.0.0",
|
|
47
47
|
"@types/node": "22.10.2",
|
|
48
|
-
"@types/react": "19.
|
|
49
|
-
"@types/react-dom": "19.
|
|
48
|
+
"@types/react": "19.3.0",
|
|
49
|
+
"@types/react-dom": "19.3.0",
|
|
50
50
|
"@vitest/coverage-v8": "4.1.11",
|
|
51
51
|
"concurrently": "9.0.1",
|
|
52
52
|
"del-cli": "6.0.0",
|
|
@@ -58,17 +58,14 @@ export const getDefaultCallbacks = (element?: Element): Callbacks => ({
|
|
|
58
58
|
/**
|
|
59
59
|
* Set the a user callback function for an element. Data tags take precedence over renderOptions.
|
|
60
60
|
*/
|
|
61
|
-
const getUserCallback = (
|
|
61
|
+
const getUserCallback = <Args extends unknown[]>(
|
|
62
62
|
callback: string,
|
|
63
63
|
element: Element,
|
|
64
|
-
callbackFnOrName: string | ((
|
|
65
|
-
) => {
|
|
64
|
+
callbackFnOrName: string | ((...args: Args) => void) | undefined,
|
|
65
|
+
): ((...args: Args) => unknown) | undefined => {
|
|
66
66
|
const callbackFnName = element.getAttribute(`data-${callback}`);
|
|
67
67
|
if (callbackFnName) {
|
|
68
|
-
|
|
69
|
-
if (callbackFn) {
|
|
70
|
-
return callbackFn;
|
|
71
|
-
}
|
|
68
|
+
return getWindowCallback(callbackFnName);
|
|
72
69
|
}
|
|
73
70
|
if (typeof callbackFnOrName === "function") {
|
|
74
71
|
return callbackFnOrName;
|
|
@@ -76,6 +73,7 @@ const getUserCallback = (
|
|
|
76
73
|
if (typeof callbackFnOrName === "string") {
|
|
77
74
|
return getWindowCallback(callbackFnOrName);
|
|
78
75
|
}
|
|
76
|
+
return undefined;
|
|
79
77
|
};
|
|
80
78
|
|
|
81
79
|
export function setUserCallbacks(
|
package/src/elements/window.ts
CHANGED
|
@@ -12,10 +12,19 @@
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
type WindowCallback = (...args: unknown[]) => unknown;
|
|
16
|
+
|
|
17
|
+
const WINDOW_PREFIX = "window.";
|
|
18
|
+
|
|
19
|
+
const isWindowCallback = (value: unknown): value is WindowCallback =>
|
|
20
|
+
typeof value === "function";
|
|
21
|
+
|
|
22
|
+
export const getWindowCallback = (callbackName: string): WindowCallback => {
|
|
23
|
+
const name = callbackName.startsWith(WINDOW_PREFIX)
|
|
24
|
+
? callbackName.slice(WINDOW_PREFIX.length)
|
|
25
|
+
: callbackName;
|
|
26
|
+
const fn: unknown = Reflect.get(window, name);
|
|
27
|
+
if (!isWindowCallback(fn)) {
|
|
19
28
|
throw new Error(
|
|
20
29
|
`Callback ${callbackName} is not defined on the window object`,
|
|
21
30
|
);
|
|
@@ -43,8 +43,27 @@ interface ChallengeSurfaceProps {
|
|
|
43
43
|
/** Called on Escape, and on an outside click when floating. */
|
|
44
44
|
onDismiss?: () => void;
|
|
45
45
|
className?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Accessible name for the panel. Supplying one turns the panel into a modal
|
|
48
|
+
* dialog: it takes focus when it opens, keeps Tab inside itself while it is
|
|
49
|
+
* open, and hands focus back to whatever opened it on close. Challenges
|
|
50
|
+
* that have nothing focusable to offer leave it unset and stay inert.
|
|
51
|
+
*/
|
|
52
|
+
dialogLabel?: string;
|
|
46
53
|
}
|
|
47
54
|
|
|
55
|
+
const FOCUSABLE_SELECTOR = [
|
|
56
|
+
"a[href]",
|
|
57
|
+
"button:not([disabled])",
|
|
58
|
+
"input:not([disabled])",
|
|
59
|
+
"select:not([disabled])",
|
|
60
|
+
"textarea:not([disabled])",
|
|
61
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
62
|
+
].join(",");
|
|
63
|
+
|
|
64
|
+
const focusableWithin = (root: HTMLElement): HTMLElement[] =>
|
|
65
|
+
Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));
|
|
66
|
+
|
|
48
67
|
const SURFACE_Z_INDEX = 2147483646;
|
|
49
68
|
const CONTENT_Z_INDEX = 2147483647;
|
|
50
69
|
|
|
@@ -90,6 +109,7 @@ const ChallengeSurface = React.memo((props: ChallengeSurfaceProps) => {
|
|
|
90
109
|
scrim = "none",
|
|
91
110
|
onDismiss,
|
|
92
111
|
className,
|
|
112
|
+
dialogLabel,
|
|
93
113
|
} = props;
|
|
94
114
|
|
|
95
115
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
@@ -158,6 +178,53 @@ const ChallengeSurface = React.memo((props: ChallengeSurfaceProps) => {
|
|
|
158
178
|
return () => document.removeEventListener("keydown", onKeyDown);
|
|
159
179
|
}, [show, onDismiss]);
|
|
160
180
|
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (!show || !dialogLabel) return;
|
|
183
|
+
|
|
184
|
+
const content = contentRef.current;
|
|
185
|
+
if (!content) return;
|
|
186
|
+
|
|
187
|
+
const opener =
|
|
188
|
+
document.activeElement instanceof HTMLElement
|
|
189
|
+
? document.activeElement
|
|
190
|
+
: null;
|
|
191
|
+
// The panel itself is the fallback so the dialog's name is still
|
|
192
|
+
// announced when it holds nothing focusable.
|
|
193
|
+
(focusableWithin(content)[0] ?? content).focus();
|
|
194
|
+
|
|
195
|
+
return () => opener?.focus();
|
|
196
|
+
}, [show, dialogLabel]);
|
|
197
|
+
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
if (!show || !dialogLabel) return;
|
|
200
|
+
|
|
201
|
+
// Tabbing off either end wraps back inside. A modal hides the rest of
|
|
202
|
+
// the page from assistive tech, so focus landing out there leaves the
|
|
203
|
+
// user somewhere they have no way to perceive or get back from.
|
|
204
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
205
|
+
if (event.key !== "Tab") return;
|
|
206
|
+
const content = contentRef.current;
|
|
207
|
+
if (!content) return;
|
|
208
|
+
|
|
209
|
+
const focusable = focusableWithin(content);
|
|
210
|
+
const first = focusable[0] ?? content;
|
|
211
|
+
const last = focusable[focusable.length - 1] ?? content;
|
|
212
|
+
const active = document.activeElement;
|
|
213
|
+
const outside = !content.contains(active);
|
|
214
|
+
|
|
215
|
+
if (event.shiftKey && (outside || active === first)) {
|
|
216
|
+
event.preventDefault();
|
|
217
|
+
last.focus();
|
|
218
|
+
} else if (!event.shiftKey && (outside || active === last)) {
|
|
219
|
+
event.preventDefault();
|
|
220
|
+
first.focus();
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
document.addEventListener("keydown", onKeyDown);
|
|
225
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
226
|
+
}, [show, dialogLabel]);
|
|
227
|
+
|
|
161
228
|
useEffect(() => {
|
|
162
229
|
if (!show || !isFloating || !onDismiss) return;
|
|
163
230
|
|
|
@@ -244,10 +311,17 @@ const ChallengeSurface = React.memo((props: ChallengeSurfaceProps) => {
|
|
|
244
311
|
.join(" ")}
|
|
245
312
|
style={layerStyle}
|
|
246
313
|
>
|
|
314
|
+
{/* biome-ignore lint/a11y/useSemanticElements: <dialog> brings its own
|
|
315
|
+
top-layer and backdrop, which would fight the portal's own
|
|
316
|
+
stacking and the float placement computed above. */}
|
|
247
317
|
<div
|
|
248
318
|
ref={contentRef}
|
|
249
319
|
className="prosopo-challenge-content"
|
|
250
320
|
style={contentStyle}
|
|
321
|
+
role={dialogLabel ? "dialog" : undefined}
|
|
322
|
+
aria-modal={dialogLabel ? true : undefined}
|
|
323
|
+
aria-label={dialogLabel}
|
|
324
|
+
tabIndex={dialogLabel ? -1 : undefined}
|
|
251
325
|
>
|
|
252
326
|
{children}
|
|
253
327
|
</div>
|
|
@@ -23,7 +23,9 @@ import {
|
|
|
23
23
|
type ButtonHTMLAttributes,
|
|
24
24
|
type CSSProperties,
|
|
25
25
|
type FC,
|
|
26
|
+
useEffect,
|
|
26
27
|
useMemo,
|
|
28
|
+
useRef,
|
|
27
29
|
useState,
|
|
28
30
|
} from "react";
|
|
29
31
|
import { isEventTrusted } from "../events/trust.js";
|
|
@@ -36,6 +38,8 @@ interface CheckboxProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
36
38
|
labelText: string;
|
|
37
39
|
error?: string;
|
|
38
40
|
loading: boolean;
|
|
41
|
+
/** Name for the spinner that stands in for the box while it is working. */
|
|
42
|
+
loadingText?: string;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
const checkboxBefore = css`{
|
|
@@ -79,6 +83,15 @@ const baseStyle: CSSProperties = {
|
|
|
79
83
|
|
|
80
84
|
const ID_LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
81
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Whether nothing on the page holds focus, which is where the browser leaves it
|
|
88
|
+
* when the focused element is removed.
|
|
89
|
+
*/
|
|
90
|
+
const focusIsStranded = (element: HTMLElement): boolean => {
|
|
91
|
+
const { activeElement, body } = element.ownerDocument;
|
|
92
|
+
return activeElement === null || activeElement === body;
|
|
93
|
+
};
|
|
94
|
+
|
|
82
95
|
const FAQ_LINK = process.env.PROSOPO_DOCS_URL
|
|
83
96
|
? `${new URL(`${process.env.PROSOPO_DOCS_URL}/en/basics/faq/`).href}/`
|
|
84
97
|
: "https://docs.prosopo.io/en/basics/faq/";
|
|
@@ -101,8 +114,32 @@ export const Checkbox: FC<CheckboxProps> = ({
|
|
|
101
114
|
labelText,
|
|
102
115
|
error,
|
|
103
116
|
loading,
|
|
117
|
+
loadingText = "Checking that you are human",
|
|
104
118
|
}: CheckboxProps) => {
|
|
105
119
|
const [hover, setHover] = useState(false);
|
|
120
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
121
|
+
const spinnerRef = useRef<HTMLDivElement>(null);
|
|
122
|
+
// Removing a focused element drops focus to the body without firing blur,
|
|
123
|
+
// so this survives the swap and says whether the swap cost the user their
|
|
124
|
+
// place on the page.
|
|
125
|
+
const hadFocus = useRef(false);
|
|
126
|
+
|
|
127
|
+
// The spinner replaces the box in the DOM rather than covering it, which
|
|
128
|
+
// strands a keyboard user at the top of the page for as long as the check
|
|
129
|
+
// runs, with nothing said about why the box vanished. Handing focus across
|
|
130
|
+
// the swap and back keeps them where they were and gets each side's name
|
|
131
|
+
// read out as it arrives.
|
|
132
|
+
//
|
|
133
|
+
// Only focus the swap itself stranded is claimed back. The check lasts as
|
|
134
|
+
// long as the network does, and a user who spent that time moving on to the
|
|
135
|
+
// host page's own fields would otherwise be dragged out of them, mid
|
|
136
|
+
// keystroke, by a widget they had finished with.
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (!hadFocus.current) return;
|
|
139
|
+
const target = loading ? spinnerRef.current : inputRef.current;
|
|
140
|
+
if (!target || !focusIsStranded(target)) return;
|
|
141
|
+
target.focus();
|
|
142
|
+
}, [loading]);
|
|
106
143
|
|
|
107
144
|
// M3 focus indicator: a 3dp outline offset by 2dp, drawn only for keyboard
|
|
108
145
|
// focus. The control previously had no focus affordance at all.
|
|
@@ -188,18 +225,30 @@ export const Checkbox: FC<CheckboxProps> = ({
|
|
|
188
225
|
>
|
|
189
226
|
{loading ? (
|
|
190
227
|
<div
|
|
228
|
+
ref={spinnerRef}
|
|
191
229
|
className={WIDGET_CHECKBOX_SPINNER_CSS_CLASS}
|
|
192
|
-
|
|
230
|
+
role="status"
|
|
231
|
+
// Focusable only programmatically: it stands in for the input it
|
|
232
|
+
// replaced, so the name below is what a screen reader reads when
|
|
233
|
+
// focus is handed over.
|
|
234
|
+
tabIndex={-1}
|
|
235
|
+
aria-label={loadingText}
|
|
193
236
|
/>
|
|
194
237
|
) : (
|
|
195
238
|
<input
|
|
239
|
+
ref={inputRef}
|
|
196
240
|
name={id}
|
|
197
241
|
id={id}
|
|
198
242
|
onMouseEnter={() => setHover(true)}
|
|
199
243
|
onMouseLeave={() => setHover(false)}
|
|
244
|
+
onFocus={() => {
|
|
245
|
+
hadFocus.current = true;
|
|
246
|
+
}}
|
|
247
|
+
onBlur={() => {
|
|
248
|
+
hadFocus.current = false;
|
|
249
|
+
}}
|
|
200
250
|
css={[checkboxBefore, checkboxForcedColors, checkboxFocus]}
|
|
201
251
|
type={"checkbox"}
|
|
202
|
-
aria-live={"assertive"}
|
|
203
252
|
aria-label={labelText}
|
|
204
253
|
onKeyDown={(e) => {
|
|
205
254
|
if (!isEventTrusted(e)) {
|
|
@@ -35,6 +35,8 @@ interface RenderArgs {
|
|
|
35
35
|
withAnchor?: boolean;
|
|
36
36
|
onDismiss?: () => void;
|
|
37
37
|
show?: boolean;
|
|
38
|
+
dialogLabel?: string;
|
|
39
|
+
focusableChildren?: number;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
const render = ({
|
|
@@ -42,6 +44,8 @@ const render = ({
|
|
|
42
44
|
withAnchor = true,
|
|
43
45
|
onDismiss,
|
|
44
46
|
show = true,
|
|
47
|
+
dialogLabel,
|
|
48
|
+
focusableChildren = 0,
|
|
45
49
|
}: RenderArgs): void => {
|
|
46
50
|
act(() => {
|
|
47
51
|
root.render(
|
|
@@ -50,13 +54,34 @@ const render = ({
|
|
|
50
54
|
placement={placement}
|
|
51
55
|
anchor={withAnchor ? anchor : null}
|
|
52
56
|
onDismiss={onDismiss}
|
|
57
|
+
dialogLabel={dialogLabel}
|
|
53
58
|
>
|
|
54
59
|
<div data-testid="challenge">challenge</div>
|
|
60
|
+
{Array.from(
|
|
61
|
+
{ length: focusableChildren },
|
|
62
|
+
(_, index) => `button-${index}`,
|
|
63
|
+
).map((id) => (
|
|
64
|
+
<button key={id} type="button">
|
|
65
|
+
{id}
|
|
66
|
+
</button>
|
|
67
|
+
))}
|
|
55
68
|
</ChallengeSurface>,
|
|
56
69
|
);
|
|
57
70
|
});
|
|
58
71
|
};
|
|
59
72
|
|
|
73
|
+
/** Scoped to the panel: tests put buttons on the page around it too. */
|
|
74
|
+
const buttons = (): HTMLButtonElement[] =>
|
|
75
|
+
Array.from(content()?.querySelectorAll("button") ?? []);
|
|
76
|
+
|
|
77
|
+
const pressTab = (shiftKey = false): void => {
|
|
78
|
+
act(() => {
|
|
79
|
+
document.dispatchEvent(
|
|
80
|
+
new KeyboardEvent("keydown", { key: "Tab", bubbles: true, shiftKey }),
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
60
85
|
beforeEach(() => {
|
|
61
86
|
container = document.createElement("div");
|
|
62
87
|
anchor = document.createElement("div");
|
|
@@ -228,3 +253,89 @@ describe("dismissing with the keyboard", () => {
|
|
|
228
253
|
expect(onDismiss).not.toHaveBeenCalled();
|
|
229
254
|
});
|
|
230
255
|
});
|
|
256
|
+
|
|
257
|
+
describe("as a dialog", () => {
|
|
258
|
+
it("stays inert for a challenge that does not name itself", () => {
|
|
259
|
+
render({});
|
|
260
|
+
|
|
261
|
+
expect(content()?.getAttribute("role")).toBeNull();
|
|
262
|
+
expect(content()?.getAttribute("aria-modal")).toBeNull();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("names itself to assistive tech once a label is given", () => {
|
|
266
|
+
render({ dialogLabel: "Puzzle challenge" });
|
|
267
|
+
|
|
268
|
+
expect(content()?.getAttribute("role")).toBe("dialog");
|
|
269
|
+
expect(content()?.getAttribute("aria-modal")).toBe("true");
|
|
270
|
+
expect(content()?.getAttribute("aria-label")).toBe("Puzzle challenge");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("takes focus so a screen reader lands on the challenge", () => {
|
|
274
|
+
render({ dialogLabel: "Puzzle challenge", focusableChildren: 2 });
|
|
275
|
+
|
|
276
|
+
expect(document.activeElement).toBe(buttons()[0]);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("falls back to the panel when it holds nothing focusable", () => {
|
|
280
|
+
render({ dialogLabel: "Puzzle challenge" });
|
|
281
|
+
|
|
282
|
+
expect(document.activeElement).toBe(content());
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("gives focus back to whatever opened it", () => {
|
|
286
|
+
const opener = document.createElement("button");
|
|
287
|
+
document.body.appendChild(opener);
|
|
288
|
+
opener.focus();
|
|
289
|
+
|
|
290
|
+
render({ dialogLabel: "Puzzle challenge", focusableChildren: 1 });
|
|
291
|
+
expect(document.activeElement).not.toBe(opener);
|
|
292
|
+
|
|
293
|
+
act(() => root.unmount());
|
|
294
|
+
expect(document.activeElement).toBe(opener);
|
|
295
|
+
|
|
296
|
+
opener.remove();
|
|
297
|
+
root = createRoot(container);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("wraps tab from the last control back to the first", () => {
|
|
301
|
+
render({ dialogLabel: "Puzzle challenge", focusableChildren: 2 });
|
|
302
|
+
const [first, last] = buttons();
|
|
303
|
+
last?.focus();
|
|
304
|
+
|
|
305
|
+
pressTab();
|
|
306
|
+
|
|
307
|
+
expect(document.activeElement).toBe(first);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("wraps shift-tab from the first control back to the last", () => {
|
|
311
|
+
render({ dialogLabel: "Puzzle challenge", focusableChildren: 2 });
|
|
312
|
+
const [first, last] = buttons();
|
|
313
|
+
first?.focus();
|
|
314
|
+
|
|
315
|
+
pressTab(true);
|
|
316
|
+
|
|
317
|
+
expect(document.activeElement).toBe(last);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("pulls focus back in if it has escaped to the page", () => {
|
|
321
|
+
const outside = document.createElement("button");
|
|
322
|
+
document.body.appendChild(outside);
|
|
323
|
+
render({ dialogLabel: "Puzzle challenge", focusableChildren: 2 });
|
|
324
|
+
outside.focus();
|
|
325
|
+
|
|
326
|
+
pressTab();
|
|
327
|
+
|
|
328
|
+
expect(document.activeElement).toBe(buttons()[0]);
|
|
329
|
+
outside.remove();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("leaves tab alone for a challenge that is not a dialog", () => {
|
|
333
|
+
render({ focusableChildren: 2 });
|
|
334
|
+
const [, last] = buttons();
|
|
335
|
+
last?.focus();
|
|
336
|
+
|
|
337
|
+
pressTab();
|
|
338
|
+
|
|
339
|
+
expect(document.activeElement).toBe(last);
|
|
340
|
+
});
|
|
341
|
+
});
|