@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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @prosopo/procaptcha-common@2.13.3 build:cjs
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/ChallengeSurface.cjs 5.12 kB │ gzip: 1.87 kB
61
- dist/cjs/reactComponents/Checkbox.cjs 5.39 kB │ gzip: 2.12 kB
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
- ✓ built in 87ms
63
+ ✓ built in 97ms
@@ -1,8 +1,8 @@
1
1
 
2
- > @prosopo/procaptcha-common@2.13.3 build:tsc
2
+ > @prosopo/procaptcha-common@2.14.0 build:tsc
3
3
  > tsc --build --verbose
4
4
 
5
- 1:49:05 PM - Projects in this build:
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
- 1:49:05 PM - 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'
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
- 1:49:05 PM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/translationKey.ts' is older than output '../locale/tsconfig.tsbuildinfo'
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
- 1:49:05 PM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
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
- 1:49:05 PM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/logger.ts' is older than output '../logger/tsconfig.tsbuildinfo'
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
- 1:49:05 PM - 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'
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
- 1:49:06 PM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/types.test-d.ts' is older than output '../types/tsconfig.tsbuildinfo'
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
- 1:49:06 PM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/tests/utils/batches.unit.test.ts' is older than output '../common/tsconfig.tsbuildinfo'
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
- 1:49:06 PM - Project '../load-balancer/tsconfig.json' is up to date because newest input '../load-balancer/src/balancer.ts' is older than output '../load-balancer/tsconfig.tsbuildinfo'
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
- 1:49:06 PM - 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'
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
- 1:49:06 PM - Project '../fingerprintjs/tsconfig.json' is up to date because newest input '../fingerprintjs/src/utils/browser.ts' is older than output '../fingerprintjs/tsconfig.tsbuildinfo'
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
- 1:49:06 PM - Project '../fingerprint/tsconfig.json' is up to date because newest input '../fingerprint/src/merkle.ts' is older than output '../fingerprint/tsconfig.tsbuildinfo'
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
- 1:49:06 PM - Project '../keyring/tsconfig.json' is up to date because newest input '../keyring/src/index.ts' is older than output '../keyring/tsconfig.tsbuildinfo'
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
- 1:49:06 PM - Project '../account/tsconfig.json' is out of date because output file '../account/tsconfig.tsbuildinfo' does not exist
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
- 1:49:06 PM - Building project '/home/runner/work/captcha/captcha/packages/account/tsconfig.json'...
47
+ 5:39:09 AM - Building project '/home/runner/work/captcha/captcha/packages/account/tsconfig.json'...
48
48
 
49
- 1:49:09 PM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
49
+ 5:39:12 AM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
50
50
 
51
- 1:49:09 PM - Building project '/home/runner/work/captcha/captcha/packages/procaptcha-common/tsconfig.json'...
51
+ 5:39:12 AM - Building project '/home/runner/work/captcha/captcha/packages/procaptcha-common/tsconfig.json'...
52
52
 
@@ -1,9 +1,9 @@
1
1
 
2
- > @prosopo/procaptcha-common@2.13.3 build
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.13.3 build:cross-env
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/ChallengeSurface.js 4.81 kB │ gzip: 1.79 kB
65
- dist/reactComponents/Checkbox.js 4.87 kB │ gzip: 2.04 kB
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
- ✓ built in 98ms
67
+ ✓ built in 75ms
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
- "aria-label": "Loading spinner"
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;
@@ -9,6 +9,7 @@ interface ChallengeSurfaceProps {
9
9
  scrim?: SurfaceScrim;
10
10
  onDismiss?: () => void;
11
11
  className?: string;
12
+ dialogLabel?: string;
12
13
  }
13
14
  interface FloatPosition {
14
15
  top: number;
@@ -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;CACnB;AAYD,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,8BA6K/D,CAAC;AAIH,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,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":"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"}
1
+ {"version":3,"file":"ChallengeSurface.js","sourceRoot":"","sources":["../../src/reactComponents/ChallengeSurface.tsx"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ interface CheckboxProps extends ButtonHTMLAttributes<HTMLButtonElement> {
7
7
  labelText: string;
8
8
  error?: string;
9
9
  loading: boolean;
10
+ loadingText?: string;
10
11
  }
11
12
  export declare const Checkbox: FC<CheckboxProps>;
12
13
  export default Checkbox;
@@ -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,EAGP,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;CACjB;AA0DD,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC,aAAa,CA0JtC,CAAC;AACF,eAAe,QAAQ,CAAC"}
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
- "aria-label": "Loading spinner"
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":"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"}
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(_jsx(ChallengeSurface, { show: show, placement: placement, anchor: withAnchor ? anchor : null, onDismiss: onDismiss, children: _jsx("div", { "data-testid": "challenge", children: "challenge" }) }));
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