@udibo/oauth2 0.3.1 → 0.4.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.
@@ -28,7 +28,6 @@ export declare function AuthFormShell<V extends AuthFormValues>(props: {
28
28
  method?: "get" | "post";
29
29
  formRef?: Ref<HTMLFormElement>;
30
30
  }): ReactNode;
31
- /** The actions row and its submit button, disabled while the form is in flight. */
32
31
  export declare function SubmitButton(props: {
33
32
  isSubmitting: boolean;
34
33
  label: string;
@@ -51,8 +50,8 @@ export declare function ErrorSummary(props: {
51
50
  /**
52
51
  * Moves keyboard focus to the first field that came back with an error after a
53
52
  * failed submit, so a screen-reader / keyboard user lands on the control to fix
54
- * (its label + error are read via `aria-describedby`) instead of being stranded
55
- * on the disabled submit button. No-ops on the initial render and when the form
53
+ * (its label + error are read via `aria-describedby`) instead of being left on
54
+ * the submit button. No-ops on the initial render and when the form
56
55
  * carries a form-level error ({@link ErrorSummary} owns that focus). Call it
57
56
  * once, unconditionally, from a form component built on {@link useAuthForm}.
58
57
  */
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useEffect, useRef } from "react";
2
+ import { useEffect, useRef, } from "react";
3
3
  import { AuthFormClassNamesScope, useAuthFormClassNames, } from "./class-names.js";
4
4
  /**
5
5
  * The single exit every default form component returns through — it owns both
@@ -18,11 +18,13 @@ export function AuthFormShell(props) {
18
18
  const { name, form, className, classNames, render, rootProps, aboveForm, succeeded, children, action, method, formRef, } = props;
19
19
  return (_jsx(AuthFormClassNamesScope, { classNames: classNames, children: render ? render(form) : (_jsxs("div", { "data-oauth2-form": name, ...rootProps, className: combineClassNames(className, classNames.root), children: [aboveForm, succeeded ?? (_jsxs("form", { onSubmit: form.handleSubmit, action: action, method: method ?? (action ? "post" : undefined), ref: formRef, noValidate: true, "data-oauth2-form-element": "", className: classNames.form, children: [_jsx(ErrorSummary, { error: form.formError, focusTrigger: form.submitCount }), children] }))] })) }));
20
20
  }
21
- /** The actions row and its submit button, disabled while the form is in flight. */
21
+ function ignoreActivation(event) {
22
+ event.preventDefault();
23
+ }
22
24
  export function SubmitButton(props) {
23
25
  const { isSubmitting, label, submittingLabel } = props;
24
26
  const classNames = useAuthFormClassNames();
25
- return (_jsx("div", { "data-oauth2-actions": "", className: classNames.actions, children: _jsx("button", { type: "submit", disabled: isSubmitting, "data-oauth2-submit": "", className: classNames.submit, children: isSubmitting ? submittingLabel : label }) }));
27
+ return (_jsx("div", { "data-oauth2-actions": "", className: classNames.actions, children: _jsx("button", { type: "submit", "aria-disabled": isSubmitting ? true : undefined, onClick: isSubmitting ? ignoreActivation : undefined, "data-oauth2-submit": "", className: classNames.submit, children: isSubmitting ? submittingLabel : label }) }));
26
28
  }
27
29
  export function TextField(props) {
28
30
  const { fieldProps, label, type = "text", autoComplete, required, error, inputRef, children, } = props;
@@ -46,8 +48,8 @@ export function ErrorSummary(props) {
46
48
  /**
47
49
  * Moves keyboard focus to the first field that came back with an error after a
48
50
  * failed submit, so a screen-reader / keyboard user lands on the control to fix
49
- * (its label + error are read via `aria-describedby`) instead of being stranded
50
- * on the disabled submit button. No-ops on the initial render and when the form
51
+ * (its label + error are read via `aria-describedby`) instead of being left on
52
+ * the submit button. No-ops on the initial render and when the form
51
53
  * carries a form-level error ({@link ErrorSummary} owns that focus). Call it
52
54
  * once, unconditionally, from a form component built on {@link useAuthForm}.
53
55
  */
@@ -113,7 +113,10 @@ export interface AuthFormClassNames extends BaseClassNames {
113
113
  hint?: string;
114
114
  /** The actions row wrapping the submit button (`data-oauth2-actions`). */
115
115
  actions?: string;
116
- /** The submit `<button>` (`data-oauth2-submit`). */
116
+ /**
117
+ * The submit `<button>` (`data-oauth2-submit`). It carries
118
+ * `aria-disabled="true"` while a submit is in flight.
119
+ */
117
120
  submit?: string;
118
121
  /**
119
122
  * A secondary action below the submit button (`data-oauth2-secondary`) — the
@@ -70,7 +70,11 @@ export interface AuthFormState<V extends AuthFormValues> {
70
70
  formError: string | null;
71
71
  /** Lifecycle status. */
72
72
  status: AuthFormStatus;
73
- /** `true` while a submit is in flight (`status === "submitting"`). */
73
+ /**
74
+ * `true` while a submit is in flight (`status === "submitting"`). Mark the
75
+ * submit button `aria-disabled` with it, not `disabled`: disabling the
76
+ * focused button drops keyboard focus to `<body>`.
77
+ */
74
78
  isSubmitting: boolean;
75
79
  /** `true` once a submit has completed successfully. */
76
80
  succeeded: boolean;
@@ -82,7 +86,10 @@ export interface AuthFormState<V extends AuthFormValues> {
82
86
  setValues(next: Partial<V>): void;
83
87
  /** Reset values to `initialValues` and clear all errors/status. */
84
88
  reset(): void;
85
- /** Submit handler for a `<form onSubmit>` (calls `preventDefault`). */
89
+ /**
90
+ * Submit handler for a `<form onSubmit>`. It always calls `preventDefault`,
91
+ * and a submit that arrives while one is in flight is ignored.
92
+ */
86
93
  handleSubmit(event?: {
87
94
  preventDefault(): void;
88
95
  }): Promise<void>;
@@ -110,7 +117,9 @@ export interface AuthFormState<V extends AuthFormValues> {
110
117
  * <input {...form.getFieldProps("identifier")} />
111
118
  * <input {...form.getFieldProps("password")} type="password" />
112
119
  * {form.formError ? <p role="alert">{form.formError}</p> : null}
113
- * <button type="submit" disabled={form.isSubmitting}>Sign in</button>
120
+ * <button type="submit" aria-disabled={form.isSubmitting || undefined}>
121
+ * Sign in
122
+ * </button>
114
123
  * </form>
115
124
  * );
116
125
  * ```
@@ -34,7 +34,9 @@ import { useCallback, useId, useMemo, useRef, useState, } from "react";
34
34
  * <input {...form.getFieldProps("identifier")} />
35
35
  * <input {...form.getFieldProps("password")} type="password" />
36
36
  * {form.formError ? <p role="alert">{form.formError}</p> : null}
37
- * <button type="submit" disabled={form.isSubmitting}>Sign in</button>
37
+ * <button type="submit" aria-disabled={form.isSubmitting || undefined}>
38
+ * Sign in
39
+ * </button>
38
40
  * </form>
39
41
  * );
40
42
  * ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@udibo/oauth2",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "A flexible OAuth2 toolkit: framework-agnostic authorization & resource servers, a typed client, Hono adapters (including a backend-for-frontend), and React bindings.",
5
5
  "repository": {
6
6
  "type": "git",