@dloizides/auth-web 1.13.0 → 1.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/CHANGELOG.md +71 -1
- package/dist/index.d.mts +24 -4
- package/dist/index.d.ts +24 -4
- package/dist/index.js +18 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +18 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,76 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 1.
|
|
3
|
+
## 1.14.0 (2026-07-19)
|
|
4
|
+
|
|
5
|
+
Three defects found by measuring the DEPLOYED portals rather than reading the
|
|
6
|
+
source. All three had passing tests over them.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **The auth card no longer vertically centres, so expanding anything stops
|
|
11
|
+
moving the inputs.** Measured on `app.zygos.dloizides.com/login` at 1.13.0:
|
|
12
|
+
expanding the demo disclosure grew the card 445.6 → 589.6px and pulled the
|
|
13
|
+
username input from `y=275.2` to `y=203.2` and the password from `y=368.8` to
|
|
14
|
+
`y=296.8` — **72px UP each**, mid-interaction. `<DemoCredentialsHint>` was
|
|
15
|
+
deliberately mounted BELOW the form to avoid exactly this; centring defeated
|
|
16
|
+
the placement, because a centred card re-centres as it grows and drags
|
|
17
|
+
everything above the growth upward.
|
|
18
|
+
|
|
19
|
+
`useAuthScreenStyles.screen` and `useAuthStyles.screen` are now top-anchored
|
|
20
|
+
(`justifyContent: 'flex-start'` + a 64dp `paddingTop`) instead of centred.
|
|
21
|
+
⚠️ **This is a visible change on every portal**: the login card now sits below
|
|
22
|
+
a fixed gap rather than floating in the middle of tall viewports.
|
|
23
|
+
|
|
24
|
+
Chosen over the alternatives because the defect is not specific to the
|
|
25
|
+
disclosure — ANY growth shifted the fields (a validation error under an input,
|
|
26
|
+
a passkey button arriving after the config fetch, an OTP tab swap), and only
|
|
27
|
+
top-anchoring fixes the whole class. Reserving the disclosure's height would
|
|
28
|
+
have left ~144px of permanent dead space on every login; absolutely
|
|
29
|
+
positioning the expanded panel would have removed it from the scrollable area,
|
|
30
|
+
making the credentials unreachable on a short viewport. Top-anchoring is also
|
|
31
|
+
strictly safer at small viewports than what it replaces: overflow now runs
|
|
32
|
+
downward only, where centring pushed the top of the card past the top edge
|
|
33
|
+
where no scroll could reach it.
|
|
34
|
+
|
|
35
|
+
- **Selection and expansion states are now announced on the web.** Three
|
|
36
|
+
components declared `accessibilityState` and announced nothing:
|
|
37
|
+
`<DemoCredentialsHint>` (`expanded`), `<DevicePinLengthPicker>` (`selected`)
|
|
38
|
+
and `<PreferredMethodSettingsCard>` (`selected`). Each now passes the ARIA
|
|
39
|
+
attribute **explicitly** (`aria-expanded` / `aria-checked`) alongside the RN
|
|
40
|
+
prop, which is kept for native.
|
|
41
|
+
|
|
42
|
+
Root cause is systemic, not a typo. Measured against react-native-web 0.21.2:
|
|
43
|
+
a `<TouchableOpacity>` given `accessibilityLabel`, `accessibilityRole`,
|
|
44
|
+
`accessibilityHint` and `accessibilityState={{ expanded: true, selected: true }}`
|
|
45
|
+
renders `<button role="button" aria-label="…">` — **no `aria-expanded`, no
|
|
46
|
+
`aria-selected`, and no trace of the hint at all**. `accessibilityState` is not
|
|
47
|
+
read by react-native-web; `accessibilityHint` produces zero DOM output.
|
|
48
|
+
|
|
49
|
+
For `<DevicePinLengthPicker>` the practical effect was WCAG 1.4.1: the chosen
|
|
50
|
+
PIN length was distinguishable only by the pill's fill colour. Its options also
|
|
51
|
+
move from `accessibilityRole="button"` to `"radio"` inside a `radiogroup`,
|
|
52
|
+
since `aria-checked` is only meaningful on a checkable role.
|
|
53
|
+
|
|
54
|
+
- **Field labels now speak the shared contract's `field` voice — 13/600, not
|
|
55
|
+
14/600.** The forms contract (mirrored in `@dloizides/ui-forms`'
|
|
56
|
+
`labelVariants.ts`) defines exactly two label voices: `field` (13/600, sentence
|
|
57
|
+
case) and `control` (11/700, uppercase). `defaultAuthTheme.typography.label`
|
|
58
|
+
was 14, which is neither — drift, inherited uniformly by every portal, since
|
|
59
|
+
they all reuse `defaultAuthTheme.typography` verbatim. It read against the 13px
|
|
60
|
+
`Field` labels those same apps use on every non-auth form.
|
|
61
|
+
|
|
62
|
+
### Tests
|
|
63
|
+
|
|
64
|
+
- **New DOM-level guards** (`*.dom.test.tsx`), rendered through the real
|
|
65
|
+
`react-native-web` in a `jsdom` environment, asserting the rendered
|
|
66
|
+
**attribute** rather than the React prop. This is the point: the pre-existing
|
|
67
|
+
suite mocked `react-native` down to host strings and asserted
|
|
68
|
+
`props.accessibilityState` — so it passed, unchanged, against a button that
|
|
69
|
+
announced nothing in every browser. A prop assertion cannot catch this class of
|
|
70
|
+
bug, and did not.
|
|
71
|
+
- `authVisualContract.test.ts` guards the two visual contracts by naming the
|
|
72
|
+
defect they forbid (`justifyContent` must NOT be `'center'`; labels must be
|
|
73
|
+
13/600 written as a literal, not read back from the token under test).
|
|
4
74
|
|
|
5
75
|
`<DemoCredentialsHint>` gets a **discreet** presentation, and it is now the
|
|
6
76
|
default. The demo stops competing with the sign-in form on login pages that real
|
package/dist/index.d.mts
CHANGED
|
@@ -62,7 +62,21 @@ interface AuthThemeTypography {
|
|
|
62
62
|
title: number;
|
|
63
63
|
/** Form subtitle. */
|
|
64
64
|
subtitle: number;
|
|
65
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Field labels.
|
|
67
|
+
*
|
|
68
|
+
* The shared forms contract ("The TWO legitimate label voices", mirrored in
|
|
69
|
+
* `@dloizides/ui-forms`' `labelVariants.ts`) defines exactly two: `field` —
|
|
70
|
+
* 13 / 600 / sentence case, a label that is READ — and `control` — 11 / 700 /
|
|
71
|
+
* uppercase, a label that is SCANNED. Auth field labels are prose addressed to
|
|
72
|
+
* the person signing in, so they are the `field` voice: **13**.
|
|
73
|
+
*
|
|
74
|
+
* Through 1.13.0 this default was 14, which is neither voice — drift. Every
|
|
75
|
+
* portal that renders `<LoginForm>` inherits this token (all of them reuse
|
|
76
|
+
* `defaultAuthTheme.typography` verbatim), so the drift was uniform across
|
|
77
|
+
* zygos, agora, erevna, katalogos and ichnos, and it read against the 13px
|
|
78
|
+
* `Field` labels those same apps use on every non-auth form.
|
|
79
|
+
*/
|
|
66
80
|
label: number;
|
|
67
81
|
/** Input text and button text. */
|
|
68
82
|
body: number;
|
|
@@ -1359,9 +1373,15 @@ declare const enum DemoCredentialsHintVariant {
|
|
|
1359
1373
|
* the same AA-held token as every other piece of helper copy. Discretion is
|
|
1360
1374
|
* carried by size, placement and the absence of chrome.
|
|
1361
1375
|
* - **It does not hide from assistive tech.** The trigger is a real focusable
|
|
1362
|
-
* button carrying `
|
|
1363
|
-
* announces both that it exists and whether it is
|
|
1364
|
-
* not mean absent — a demo nobody can find is a demo
|
|
1376
|
+
* button carrying an explicit `aria-expanded` (plus `accessibilityState` for
|
|
1377
|
+
* native), so a screen reader announces both that it exists and whether it is
|
|
1378
|
+
* open. Visually quiet must not mean absent — a demo nobody can find is a demo
|
|
1379
|
+
* nobody uses.
|
|
1380
|
+
*
|
|
1381
|
+
* ⚠️ Through 1.13.0 this carried ONLY `accessibilityState={{ expanded }}` and
|
|
1382
|
+
* therefore announced nothing on web: react-native-web 0.21.2 does not read
|
|
1383
|
+
* `accessibilityState`, so the rendered button had no `aria-expanded` at all.
|
|
1384
|
+
* The old unit test asserted the React *prop*, so it passed against the bug.
|
|
1365
1385
|
*
|
|
1366
1386
|
* ## Layout stability
|
|
1367
1387
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -62,7 +62,21 @@ interface AuthThemeTypography {
|
|
|
62
62
|
title: number;
|
|
63
63
|
/** Form subtitle. */
|
|
64
64
|
subtitle: number;
|
|
65
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Field labels.
|
|
67
|
+
*
|
|
68
|
+
* The shared forms contract ("The TWO legitimate label voices", mirrored in
|
|
69
|
+
* `@dloizides/ui-forms`' `labelVariants.ts`) defines exactly two: `field` —
|
|
70
|
+
* 13 / 600 / sentence case, a label that is READ — and `control` — 11 / 700 /
|
|
71
|
+
* uppercase, a label that is SCANNED. Auth field labels are prose addressed to
|
|
72
|
+
* the person signing in, so they are the `field` voice: **13**.
|
|
73
|
+
*
|
|
74
|
+
* Through 1.13.0 this default was 14, which is neither voice — drift. Every
|
|
75
|
+
* portal that renders `<LoginForm>` inherits this token (all of them reuse
|
|
76
|
+
* `defaultAuthTheme.typography` verbatim), so the drift was uniform across
|
|
77
|
+
* zygos, agora, erevna, katalogos and ichnos, and it read against the 13px
|
|
78
|
+
* `Field` labels those same apps use on every non-auth form.
|
|
79
|
+
*/
|
|
66
80
|
label: number;
|
|
67
81
|
/** Input text and button text. */
|
|
68
82
|
body: number;
|
|
@@ -1359,9 +1373,15 @@ declare const enum DemoCredentialsHintVariant {
|
|
|
1359
1373
|
* the same AA-held token as every other piece of helper copy. Discretion is
|
|
1360
1374
|
* carried by size, placement and the absence of chrome.
|
|
1361
1375
|
* - **It does not hide from assistive tech.** The trigger is a real focusable
|
|
1362
|
-
* button carrying `
|
|
1363
|
-
* announces both that it exists and whether it is
|
|
1364
|
-
* not mean absent — a demo nobody can find is a demo
|
|
1376
|
+
* button carrying an explicit `aria-expanded` (plus `accessibilityState` for
|
|
1377
|
+
* native), so a screen reader announces both that it exists and whether it is
|
|
1378
|
+
* open. Visually quiet must not mean absent — a demo nobody can find is a demo
|
|
1379
|
+
* nobody uses.
|
|
1380
|
+
*
|
|
1381
|
+
* ⚠️ Through 1.13.0 this carried ONLY `accessibilityState={{ expanded }}` and
|
|
1382
|
+
* therefore announced nothing on web: react-native-web 0.21.2 does not read
|
|
1383
|
+
* `accessibilityState`, so the rendered button had no `aria-expanded` at all.
|
|
1384
|
+
* The old unit test asserted the React *prop*, so it passed against the bug.
|
|
1365
1385
|
*
|
|
1366
1386
|
* ## Layout stability
|
|
1367
1387
|
*
|
package/dist/index.js
CHANGED
|
@@ -9,15 +9,18 @@ var authClient = require('@dloizides/auth-client');
|
|
|
9
9
|
// src/components/AuthScreen.tsx
|
|
10
10
|
var FULL_WIDTH = "100%";
|
|
11
11
|
var CARD_BORDER_WIDTH = 1;
|
|
12
|
+
var CARD_TOP_OFFSET = 64;
|
|
12
13
|
function useAuthScreenStyles(theme) {
|
|
13
14
|
return react.useMemo(() => {
|
|
14
15
|
const { colors, radii, spacing, typography } = theme;
|
|
15
16
|
return reactNative.StyleSheet.create({
|
|
16
17
|
screen: {
|
|
17
18
|
flex: 1,
|
|
18
|
-
justifyContent:
|
|
19
|
+
// Deliberately NOT `justifyContent: 'center'` — see CARD_TOP_OFFSET.
|
|
20
|
+
justifyContent: "flex-start",
|
|
19
21
|
alignItems: "center",
|
|
20
22
|
padding: spacing.md,
|
|
23
|
+
paddingTop: CARD_TOP_OFFSET,
|
|
21
24
|
backgroundColor: colors.background
|
|
22
25
|
},
|
|
23
26
|
card: {
|
|
@@ -75,7 +78,8 @@ var defaultAuthTheme = {
|
|
|
75
78
|
typography: {
|
|
76
79
|
title: 28,
|
|
77
80
|
subtitle: 16,
|
|
78
|
-
|
|
81
|
+
// 13, not 14 — the shared contract's `field` voice. See AuthThemeTypography.label.
|
|
82
|
+
label: 13,
|
|
79
83
|
body: 16,
|
|
80
84
|
caption: 12
|
|
81
85
|
}
|
|
@@ -391,15 +395,21 @@ function withTestIdPrefix(baseId, prefix) {
|
|
|
391
395
|
var FULL_WIDTH2 = "100%";
|
|
392
396
|
var CARD_MAX_WIDTH = 420;
|
|
393
397
|
var BUTTON_VERTICAL_PADDING = 14;
|
|
398
|
+
var SCREEN_TOP_OFFSET = 64;
|
|
394
399
|
function useAuthStyles(theme) {
|
|
395
400
|
return react.useMemo(() => {
|
|
396
401
|
const { colors, radii, spacing, typography } = theme;
|
|
397
402
|
return reactNative.StyleSheet.create({
|
|
398
403
|
screen: {
|
|
399
404
|
flex: 1,
|
|
400
|
-
|
|
405
|
+
// Top-anchored, NOT centred — a centred card re-centres as it grows and
|
|
406
|
+
// drags the inputs up under the cursor. Same reasoning (and the same
|
|
407
|
+
// measurement) as `useAuthScreenStyles`' CARD_TOP_OFFSET; kept in step so
|
|
408
|
+
// a standalone `<LoginForm>` and an `<AuthScreen>`-composed one agree.
|
|
409
|
+
justifyContent: "flex-start",
|
|
401
410
|
alignItems: "center",
|
|
402
411
|
padding: spacing.md,
|
|
412
|
+
paddingTop: SCREEN_TOP_OFFSET,
|
|
403
413
|
backgroundColor: colors.background
|
|
404
414
|
},
|
|
405
415
|
card: {
|
|
@@ -2013,14 +2023,15 @@ function DevicePinLengthPicker({
|
|
|
2013
2023
|
const theme = useAuthTheme(themeProp);
|
|
2014
2024
|
const dynamic = usePickerStyles(theme);
|
|
2015
2025
|
const baseId = withTestIdPrefix(AuthTestIds.devicePinEnrollLength, testIdPrefix);
|
|
2016
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.row, children: DEVICE_PIN_ALLOWED_DIGITS.map((option) => {
|
|
2026
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { accessibilityRole: "radiogroup", role: "radiogroup", style: styles2.row, children: DEVICE_PIN_ALLOWED_DIGITS.map((option) => {
|
|
2017
2027
|
const active = option === selected;
|
|
2018
2028
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2019
2029
|
reactNative.Pressable,
|
|
2020
2030
|
{
|
|
2031
|
+
"aria-checked": active,
|
|
2021
2032
|
accessibilityHint: interpolate(optionHint, { count: option }),
|
|
2022
2033
|
accessibilityLabel: String(option),
|
|
2023
|
-
accessibilityRole: "
|
|
2034
|
+
accessibilityRole: "radio",
|
|
2024
2035
|
accessibilityState: { selected: active },
|
|
2025
2036
|
disabled,
|
|
2026
2037
|
style: [styles2.pill, dynamic.pill, active ? dynamic.active : null],
|
|
@@ -3164,6 +3175,7 @@ function PreferredMethodSettingsCard({
|
|
|
3164
3175
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3165
3176
|
reactNative.TouchableOpacity,
|
|
3166
3177
|
{
|
|
3178
|
+
"aria-checked": isSelected,
|
|
3167
3179
|
accessibilityHint: labels.description,
|
|
3168
3180
|
accessibilityLabel: option.label,
|
|
3169
3181
|
accessibilityRole: "radio",
|
|
@@ -3450,6 +3462,7 @@ function DemoCredentialsHint({
|
|
|
3450
3462
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3451
3463
|
reactNative.TouchableOpacity,
|
|
3452
3464
|
{
|
|
3465
|
+
"aria-expanded": expanded,
|
|
3453
3466
|
accessibilityHint: labels.toggleHint,
|
|
3454
3467
|
accessibilityLabel: labels.toggle,
|
|
3455
3468
|
accessibilityRole: "button",
|