@dloizides/auth-web 1.11.0 → 1.12.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 CHANGED
@@ -1,5 +1,58 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.12.0 (2026-07-17)
4
+
5
+ Completes the demo-credential feature for **BFF-fronted apps**, whose demo pair
6
+ arrives ASYNChronously from `GET /bff/config` rather than a module-level const (a
7
+ const would bake the credentials into a bundle handed to a customer to
8
+ self-deploy). Two gaps closed:
9
+
10
+ 1. `prefillOnMount` was inert for async credentials (1.11.0 bug).
11
+ 2. `useBffLoginConfig().config` dropped the `demo` field the BFF now serves.
12
+
13
+ Requires `@dloizides/auth-client` **>= 4.2.0** (which surfaces `demo` on
14
+ `BffLoginConfig`) — the peer floor is raised accordingly. Additive for existing
15
+ consumers: the new `LoginForm` prop is optional and the synchronous /
16
+ module-const path is byte-identical.
17
+
18
+ ### Fixed
19
+
20
+ - **`prefillOnMount` now honours credentials that arrive AFTER mount.** `useState`
21
+ reads its initial argument on the first render only, so a demo pair landing a
22
+ render later (every BFF app) was silently dropped and the form stayed empty
23
+ beside a populated hint. `useDemoCredentials` now seeds when the resolved pair
24
+ transitions to non-null (or to a genuinely different pair), gated by VALUE so a
25
+ react-query refetch handing back a fresh-but-equal object does not re-seed on a
26
+ loop.
27
+
28
+ ### Added
29
+
30
+ - **`config.demo` carries through `useBffLoginConfig`.** `BffLoginConfig` gains
31
+ `demo?: DemoCredentials | null`, so the intended wiring works end-to-end:
32
+ ```tsx
33
+ const { config } = useBffLoginConfig(bffAuthClient);
34
+ const demo = useDemoCredentials({ credentials: config.demo ?? undefined, prefillOnMount: true });
35
+ ```
36
+ - **`PrefillIntent`** enum + **`LoginForm` gains `prefillIntent`** — the "don't
37
+ fight the user" control. An `Opportunistic` re-seed (a late config fetch) is
38
+ DROPPED unless both fields are still empty, so it never overwrites what a real
39
+ account holder is typing while `/bff/config` is in flight; an `Explicit` re-seed
40
+ (the user tapped "use these credentials") always applies. `useDemoCredentials`
41
+ sets the right intent automatically — most apps never touch this prop.
42
+ - **`DemoCredentials` is now re-exported from `@dloizides/auth-client`** (its
43
+ canonical owner) rather than defined locally, so there is exactly ONE shape
44
+ across the two packages. Structurally identical to 1.11.0 — no consumer change.
45
+
46
+ ### "Don't fight the user" — what the hook can and cannot know
47
+
48
+ `useDemoCredentials` does not own the input fields (`<LoginForm>` does), so it
49
+ cannot observe keystrokes. The interaction test it CAN make is on the field
50
+ values: an opportunistic seed applies only when both fields are still empty
51
+ (pristine). This is intentionally conservative — a user who typed and then
52
+ cleared a field keeps control for the rest of the session (a dropped
53
+ opportunistic bump is not retried). An explicit user request is never subject to
54
+ this test.
55
+
3
56
  ## 1.11.0 (2026-07-17)
4
57
 
5
58
  Additive release adding **public demo-credential support** for products with an
package/README.md CHANGED
@@ -197,16 +197,22 @@ same BFF, same Keycloak, same session cookie, same roles. There is no auth
197
197
  bypass and no credential inside this package: the host supplies the pair, and the
198
198
  demo account's blast radius is whatever the IdP grants it.
199
199
 
200
- `useDemoCredentials` owns the wiring, so apps don't hand-roll it:
200
+ The demo pair comes from the BFF (`GET /bff/config` → `config.demo`), not a
201
+ client const — the BFF is the only thing that knows whether a deployment has a
202
+ demo configured, and a bundled const would ship credentials into every customer's
203
+ self-hosted build. `useBffLoginConfig` already fetches that config, so it also
204
+ carries `demo`. `useDemoCredentials` owns the hint↔form wiring:
201
205
 
202
206
  ```tsx
203
- import { AuthScreen, DemoCredentialsHint, LoginForm, useDemoCredentials } from '@dloizides/auth-web';
204
-
205
- // Hoisted (or memoised): a new object each render re-creates the prop bags.
206
- const DEMO = { username: 'demo', password: 'ZygosDemo!2026.Try' };
207
+ import {
208
+ AuthScreen, DemoCredentialsHint, LoginForm,
209
+ useBffLoginConfig, useDemoCredentials,
210
+ } from '@dloizides/auth-web';
207
211
 
208
212
  function LoginScreen() {
209
- const demo = useDemoCredentials({ credentials: DEMO, prefillOnMount: true });
213
+ const { config } = useBffLoginConfig(bffAuthClient);
214
+ // Credentials arrive ASYNC — prefillOnMount honours a late arrival (see below).
215
+ const demo = useDemoCredentials({ credentials: config.demo ?? undefined, prefillOnMount: true });
210
216
 
211
217
  return (
212
218
  <AuthScreen theme={authTheme}>
@@ -219,7 +225,7 @@ function LoginScreen() {
219
225
  <LoginForm
220
226
  {...demo.loginFormProps}
221
227
  chromeless
222
- client={client}
228
+ client={bffAuthClient}
223
229
  labels={authLabels.login}
224
230
  theme={authTheme}
225
231
  onSuccess={handleSignedIn}
@@ -230,7 +236,25 @@ function LoginScreen() {
230
236
  ```
231
237
 
232
238
  `hintProps` carries `{ credentials, onUse }`; `loginFormProps` carries
233
- `{ initialUsername, initialPassword, prefillNonce }`.
239
+ `{ initialUsername, initialPassword, prefillNonce, prefillIntent }`. A module-const
240
+ demo works too — just pass it as `credentials` instead of `config.demo`.
241
+
242
+ > Requires `@dloizides/auth-client >= 4.2.0`, which surfaces `demo` on
243
+ > `BffLoginConfig` (mapped from the wire's `publishedUsername` /
244
+ > `publishedPassword`). `config.demo` is `DemoCredentials | null`.
245
+
246
+ ### Async credentials: `prefillOnMount` honours a late arrival
247
+
248
+ Because the pair arrives a render or two after mount, `prefillOnMount` cannot lean
249
+ on `useState`'s initial value (React reads it on the first render only). The hook
250
+ seeds the form when `config.demo` resolves to a usable pair — but as an
251
+ **opportunistic** seed: `<LoginForm>` applies it only while both fields are still
252
+ empty, so a late `/bff/config` response never overwrites a username a real account
253
+ holder was typing meanwhile. Tapping *Use these credentials* is an **explicit**
254
+ seed and always applies. Seeding is gated by value, so a react-query refetch
255
+ returning an equal-valued object does not re-seed. `useDemoCredentials` chooses
256
+ the intent for you via the `prefillIntent` prop; you only touch `PrefillIntent`
257
+ if you drive `<LoginForm>` without the hook.
234
258
 
235
259
  **`prefillOnMount`** (default `false`) chooses the trade:
236
260
 
@@ -292,7 +316,7 @@ const route = resolvePostLoginRoute(user, roleRoutes);
292
316
  |--------|------|
293
317
  | `LoginForm`, `ForgotPasswordForm`, `ResetPasswordForm`, `OtpForm`, `PinForm` | Components |
294
318
  | `AuthScreen`, `DemoCredentialsHint`, `PreferredMethodHint` | Composition + login-surface panels |
295
- | `useDemoCredentials`, `resolveDemoCredentials`, `DemoCredentials` | Public demo credentials |
319
+ | `useDemoCredentials`, `resolveDemoCredentials`, `DemoCredentials`, `PrefillIntent` | Public demo credentials |
296
320
  | `AuthThemeProvider`, `useAuthTheme`, `defaultAuthTheme`, `AuthTheme` | Theming |
297
321
  | `DEFAULT_LOGIN_LABELS`, `DEFAULT_FORGOT_PASSWORD_LABELS`, `DEFAULT_RESET_PASSWORD_LABELS`, `DEFAULT_OTP_LABELS`, `DEFAULT_PIN_LABELS` | Label bags |
298
322
  | `useBffAuth`, `useBffForgotPassword`, `useBffResetPassword`, `useResetPasswordForm`, `useOtpLogin`, `usePinLogin` | Headless hooks |
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ReactNode, ReactElement } from 'react';
2
- import { BffAuthClient, BffUser, HttpClient, BffLoginRequest, BffForgotPasswordRequest, BffResetPasswordRequest, BffOtpRequestResult } from '@dloizides/auth-client';
3
- export { BffAuthClient, BffAuthClientOptions, BffForgotPasswordRequest, BffLoginRequest, BffOtpRequestRequest, BffOtpRequestResult, BffOtpVerifyRequest, BffPinLoginRequest, BffRegisterRequest, BffResetPasswordRequest, BffUser, HttpClient, HttpRequest, HttpResponse, createFetchHttpClient } from '@dloizides/auth-client';
2
+ import { BffAuthClient, BffUser, DemoCredentials, HttpClient, BffLoginRequest, BffForgotPasswordRequest, BffResetPasswordRequest, BffOtpRequestResult } from '@dloizides/auth-client';
3
+ export { BffAuthClient, BffAuthClientOptions, BffForgotPasswordRequest, BffLoginRequest, BffOtpRequestRequest, BffOtpRequestResult, BffOtpVerifyRequest, BffPinLoginRequest, BffRegisterRequest, BffResetPasswordRequest, BffUser, DemoCredentials, HttpClient, HttpRequest, HttpResponse, createFetchHttpClient } from '@dloizides/auth-client';
4
4
  import { UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
5
5
 
6
6
  /**
@@ -442,6 +442,27 @@ declare const DEFAULT_PREFERRED_METHOD_HINT_LABELS: PreferredMethodHintLabels;
442
442
  declare const DEFAULT_DEMO_CREDENTIALS_HINT_LABELS: DemoCredentialsHintLabels;
443
443
  declare const DEFAULT_RESET_PASSWORD_LABELS: ResetPasswordFormLabels;
444
444
 
445
+ /**
446
+ * Who asked for a prefill — the difference between "the user requested this" and
447
+ * "the app offered it".
448
+ *
449
+ * A seed that the USER asked for (tapping "use these credentials") must always
450
+ * apply: they can see the fields, they know what is in them, and they asked
451
+ * anyway. A seed the APP offers (a demo pair arriving from `GET /bff/config`
452
+ * mid-typing) must not: it is an unrequested, racing write against a field the
453
+ * user may already be filling in.
454
+ *
455
+ * Collapsing the two into a bare nonce bump is what makes a late-arriving config
456
+ * fetch overwrite the username a real account holder is halfway through typing.
457
+ * `<LoginForm>` switches on this, never on a string literal.
458
+ */
459
+ declare const enum PrefillIntent {
460
+ /** The user asked for it. Always applies, over whatever is in the fields. */
461
+ Explicit = "explicit",
462
+ /** The app offered it. Applies only to untouched, empty fields. */
463
+ Opportunistic = "opportunistic"
464
+ }
465
+
445
466
  /**
446
467
  * `<LoginForm>` — the ready-made, themeable password-login form.
447
468
  *
@@ -498,12 +519,20 @@ interface LoginFormProps {
498
519
  * props alone does nothing once mounted.
499
520
  *
500
521
  * Most apps should not manage this by hand: `useDemoCredentials` returns a
501
- * `loginFormProps` bag carrying all three, ready to spread.
522
+ * `loginFormProps` bag carrying all four, ready to spread.
502
523
  */
503
524
  prefillNonce?: number;
525
+ /**
526
+ * Whether the pending re-seed was requested by the user or offered by the app.
527
+ * An {@link PrefillIntent.Opportunistic} re-seed (e.g. a demo pair arriving
528
+ * from `GET /bff/config` after mount) is DROPPED unless both fields are still
529
+ * empty, so a late fetch never overwrites what someone is typing. Defaults to
530
+ * unconditional — omit it and every nonce bump applies.
531
+ */
532
+ prefillIntent?: PrefillIntent;
504
533
  }
505
534
  /** Themeable password-login form built on `useBffAuth`. */
506
- declare function LoginForm({ client, theme: themeProp, labels: labelsProp, onSuccess, onForgotPassword, onSignUp, testIdPrefix, chromeless, initialUsername, initialPassword, prefillNonce, }: Readonly<LoginFormProps>): ReactElement;
535
+ declare function LoginForm({ client, theme: themeProp, labels: labelsProp, onSuccess, onForgotPassword, onSignUp, testIdPrefix, chromeless, initialUsername, initialPassword, prefillNonce, prefillIntent, }: Readonly<LoginFormProps>): ReactElement;
507
536
 
508
537
  /**
509
538
  * `<ForgotPasswordForm>` — the ready-made, themeable "request a reset link"
@@ -802,6 +831,7 @@ declare const enum DevicePinErrorKey {
802
831
  * error) so the unlock / enrol UIs can route on each one rather than collapsing
803
832
  * them into a single opaque failure.
804
833
  */
834
+
805
835
  /**
806
836
  * The per-device half of `GET /bff/config` — what this device remembers about
807
837
  * the last user, all fields safe-defaulted by the parser when absent.
@@ -824,6 +854,14 @@ interface BffLoginConfig {
824
854
  registrationEnabled: boolean;
825
855
  /** The per-device remembered state. */
826
856
  deviceState: BffDeviceState;
857
+ /**
858
+ * The published demo credentials this deployment advertises, or `null` when
859
+ * none is configured. Feeds `useDemoCredentials` / `<DemoCredentialsHint>`:
860
+ * `useDemoCredentials({ credentials: config.demo ?? undefined })`. `auth-client`
861
+ * 4.2.0 populates it; older clients (and hand-rolled objects) simply omit it,
862
+ * which reads as `undefined` — treated the same as "no demo" by the hint.
863
+ */
864
+ demo?: DemoCredentials | null;
827
865
  }
828
866
  /**
829
867
  * Discriminated result of a device-PIN UNLOCK attempt. The client never rejects;
@@ -1245,23 +1283,6 @@ interface PreferredMethodHintProps {
1245
1283
  /** Themeable login-screen preferred-method hint. Renders `null` when there is nothing to say. */
1246
1284
  declare function PreferredMethodHint({ method, theme: themeProp, labels: labelsProp, testIdPrefix, }: Readonly<PreferredMethodHintProps>): ReactElement | null;
1247
1285
 
1248
- /**
1249
- * Public demo-credential contracts.
1250
- *
1251
- * A "demo credential" is a REAL account that a product publishes openly so a
1252
- * prospective evaluator can sign in without a sign-up step. It is not a bypass:
1253
- * the credentials travel the ordinary login path (same BFF, same Keycloak, same
1254
- * session cookie, same roles). The package only makes them *discoverable* and
1255
- * *one-click fillable* — it never authenticates anything itself.
1256
- */
1257
- /** A publishable demo username + password pair. */
1258
- interface DemoCredentials {
1259
- /** The demo account's username. */
1260
- username: string;
1261
- /** The demo account's password. */
1262
- password: string;
1263
- }
1264
-
1265
1286
  /**
1266
1287
  * `<DemoCredentialsHint>` — the public-demo credentials panel.
1267
1288
  *
@@ -1942,6 +1963,7 @@ interface DemoCredentialsLoginFormProps {
1942
1963
  initialUsername?: string;
1943
1964
  initialPassword?: string;
1944
1965
  prefillNonce: number;
1966
+ prefillIntent: PrefillIntent;
1945
1967
  }
1946
1968
  /** The `<DemoCredentialsHint>` props that carry the credentials + fill action. */
1947
1969
  interface DemoCredentialsHintWiring {
@@ -2231,4 +2253,4 @@ declare function validatePasswordPolicy(password: string): PasswordPolicyError[]
2231
2253
  /** `true` when the password satisfies every policy rule. */
2232
2254
  declare function isPasswordValid(password: string): boolean;
2233
2255
 
2234
- export { AuthScreen, type AuthScreenProps, AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type BffDeviceState, type BffLoginConfig, type BffLogoutClient, type CreateBffAuthClientOptions, DEFAULT_AUTH_CARD_MAX_WIDTH, DEFAULT_DEMO_CREDENTIALS_HINT_LABELS, DEFAULT_DEVICE_PIN_ENROLL_LABELS, DEFAULT_DEVICE_PIN_SETTINGS_LABELS, DEFAULT_DEVICE_PIN_UNLOCK_LABELS, DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS, DEFAULT_FORGOT_PASSWORD_LABELS, DEFAULT_LOGIN_LABELS, DEFAULT_OTP_LABELS, DEFAULT_PASSKEY_LOGIN_LABELS, DEFAULT_PASSKEY_SETTINGS_LABELS, DEFAULT_PIN_LABELS, DEFAULT_PREFERRED_METHOD_HINT_LABELS, DEFAULT_PREFERRED_METHOD_SETTINGS_LABELS, DEFAULT_RESET_PASSWORD_LABELS, DEVICE_PIN_ALLOWED_DIGITS, DEVICE_PIN_DEFAULT_DIGITS, type DemoCredentials, DemoCredentialsHint, type DemoCredentialsHintLabels, type DemoCredentialsHintProps, type DemoCredentialsHintWiring, type DemoCredentialsLoginFormProps, type DevicePinCapableClient, type DevicePinDisableCapableClient, type DevicePinEnrollCapableClient, DevicePinEnrollErrorKey, DevicePinEnrollForm, type DevicePinEnrollFormProps, type DevicePinEnrollLabels, type DevicePinEnrollResult, DevicePinErrorKey, DevicePinInput, type DevicePinInputProps, DevicePinLengthPicker, type DevicePinLengthPickerProps, DevicePinOffer, type DevicePinOfferProps, DevicePinSettingsCard, type DevicePinSettingsCardProps, type DevicePinSettingsLabels, type DevicePinUnlockCapableClient, type DevicePinUnlockLabels, type DevicePinUnlockResult, DevicePinUnlockScreen, type DevicePinUnlockScreenProps, type DevicePinUnlockedUser, ForgotPasswordFields, type ForgotPasswordFieldsLabels, type ForgotPasswordFieldsProps, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, type LoginConfigCapableClient, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PasskeyCeremonyMode, PasskeyCeremonyStatus, type PasskeyErrorCode, PasskeyLoginButton, type PasskeyLoginButtonProps, type PasskeyLoginLabels, PasskeySettingsCard, type PasskeySettingsCardProps, type PasskeySettingsLabels, PasswordPolicyError, type PerformBffLogoutOptions, PinForm, type PinFormLabels, type PinFormProps, type PreferencesClientOptions, type PreferredMethodClient, PreferredMethodHint, type PreferredMethodHintLabels, type PreferredMethodHintProps, PreferredMethodSettingsCard, type PreferredMethodSettingsCardProps, type PreferredMethodSettingsLabels, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffLoginConfigResult, type UseBffResetPasswordOptions, type UseDemoCredentialsOptions, type UseDemoCredentialsResult, type UseDevicePinDisableArgs, type UseDevicePinDisableResult, type UseDevicePinEnrollArgs, type UseDevicePinEnrollResult, type UseDevicePinUnlockArgs, type UseDevicePinUnlockResult, type UseForgotPasswordSubmitArgs, type UseForgotPasswordSubmitResult, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePasskeyCeremonyOptions, type UsePasskeyCeremonyResult, type UsePinLoginOptions, type UsePinLoginResult, type UsePreferredMethodSettingArgs, type UsePreferredMethodSettingResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, cachePreferredMethod, clearCachedPreferredMethod, collectUserRoles, createBffAuthClient, createPreferencesClient, defaultAuthTheme, isAllowedPinDigits, isPasswordValid, isValidForgotPasswordEmail, performBffLogout, readCachedPreferredMethod, readPasskeyError, readPasskeyRegistered, resolveAccessibleRoutes, resolveDemoCredentials, resolvePostLoginRoute, startPasskeyLogin, startPasskeyRegistration, syncPreferredMethodFromServer, useAuthTheme, useBffAuth, useBffForgotPassword, useBffLoginConfig, useBffResetPassword, useDemoCredentials, useDevicePinDisable, useDevicePinEnroll, useDevicePinUnlock, useForgotPasswordSubmit, useOtpLogin, usePasskeyCeremony, usePinLogin, usePreferredMethodSetting, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
2256
+ export { AuthScreen, type AuthScreenProps, AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type BffDeviceState, type BffLoginConfig, type BffLogoutClient, type CreateBffAuthClientOptions, DEFAULT_AUTH_CARD_MAX_WIDTH, DEFAULT_DEMO_CREDENTIALS_HINT_LABELS, DEFAULT_DEVICE_PIN_ENROLL_LABELS, DEFAULT_DEVICE_PIN_SETTINGS_LABELS, DEFAULT_DEVICE_PIN_UNLOCK_LABELS, DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS, DEFAULT_FORGOT_PASSWORD_LABELS, DEFAULT_LOGIN_LABELS, DEFAULT_OTP_LABELS, DEFAULT_PASSKEY_LOGIN_LABELS, DEFAULT_PASSKEY_SETTINGS_LABELS, DEFAULT_PIN_LABELS, DEFAULT_PREFERRED_METHOD_HINT_LABELS, DEFAULT_PREFERRED_METHOD_SETTINGS_LABELS, DEFAULT_RESET_PASSWORD_LABELS, DEVICE_PIN_ALLOWED_DIGITS, DEVICE_PIN_DEFAULT_DIGITS, DemoCredentialsHint, type DemoCredentialsHintLabels, type DemoCredentialsHintProps, type DemoCredentialsHintWiring, type DemoCredentialsLoginFormProps, type DevicePinCapableClient, type DevicePinDisableCapableClient, type DevicePinEnrollCapableClient, DevicePinEnrollErrorKey, DevicePinEnrollForm, type DevicePinEnrollFormProps, type DevicePinEnrollLabels, type DevicePinEnrollResult, DevicePinErrorKey, DevicePinInput, type DevicePinInputProps, DevicePinLengthPicker, type DevicePinLengthPickerProps, DevicePinOffer, type DevicePinOfferProps, DevicePinSettingsCard, type DevicePinSettingsCardProps, type DevicePinSettingsLabels, type DevicePinUnlockCapableClient, type DevicePinUnlockLabels, type DevicePinUnlockResult, DevicePinUnlockScreen, type DevicePinUnlockScreenProps, type DevicePinUnlockedUser, ForgotPasswordFields, type ForgotPasswordFieldsLabels, type ForgotPasswordFieldsProps, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, type LoginConfigCapableClient, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PasskeyCeremonyMode, PasskeyCeremonyStatus, type PasskeyErrorCode, PasskeyLoginButton, type PasskeyLoginButtonProps, type PasskeyLoginLabels, PasskeySettingsCard, type PasskeySettingsCardProps, type PasskeySettingsLabels, PasswordPolicyError, type PerformBffLogoutOptions, PinForm, type PinFormLabels, type PinFormProps, type PreferencesClientOptions, type PreferredMethodClient, PreferredMethodHint, type PreferredMethodHintLabels, type PreferredMethodHintProps, PreferredMethodSettingsCard, type PreferredMethodSettingsCardProps, type PreferredMethodSettingsLabels, PrefillIntent, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffLoginConfigResult, type UseBffResetPasswordOptions, type UseDemoCredentialsOptions, type UseDemoCredentialsResult, type UseDevicePinDisableArgs, type UseDevicePinDisableResult, type UseDevicePinEnrollArgs, type UseDevicePinEnrollResult, type UseDevicePinUnlockArgs, type UseDevicePinUnlockResult, type UseForgotPasswordSubmitArgs, type UseForgotPasswordSubmitResult, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePasskeyCeremonyOptions, type UsePasskeyCeremonyResult, type UsePinLoginOptions, type UsePinLoginResult, type UsePreferredMethodSettingArgs, type UsePreferredMethodSettingResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, cachePreferredMethod, clearCachedPreferredMethod, collectUserRoles, createBffAuthClient, createPreferencesClient, defaultAuthTheme, isAllowedPinDigits, isPasswordValid, isValidForgotPasswordEmail, performBffLogout, readCachedPreferredMethod, readPasskeyError, readPasskeyRegistered, resolveAccessibleRoutes, resolveDemoCredentials, resolvePostLoginRoute, startPasskeyLogin, startPasskeyRegistration, syncPreferredMethodFromServer, useAuthTheme, useBffAuth, useBffForgotPassword, useBffLoginConfig, useBffResetPassword, useDemoCredentials, useDevicePinDisable, useDevicePinEnroll, useDevicePinUnlock, useForgotPasswordSubmit, useOtpLogin, usePasskeyCeremony, usePinLogin, usePreferredMethodSetting, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ReactNode, ReactElement } from 'react';
2
- import { BffAuthClient, BffUser, HttpClient, BffLoginRequest, BffForgotPasswordRequest, BffResetPasswordRequest, BffOtpRequestResult } from '@dloizides/auth-client';
3
- export { BffAuthClient, BffAuthClientOptions, BffForgotPasswordRequest, BffLoginRequest, BffOtpRequestRequest, BffOtpRequestResult, BffOtpVerifyRequest, BffPinLoginRequest, BffRegisterRequest, BffResetPasswordRequest, BffUser, HttpClient, HttpRequest, HttpResponse, createFetchHttpClient } from '@dloizides/auth-client';
2
+ import { BffAuthClient, BffUser, DemoCredentials, HttpClient, BffLoginRequest, BffForgotPasswordRequest, BffResetPasswordRequest, BffOtpRequestResult } from '@dloizides/auth-client';
3
+ export { BffAuthClient, BffAuthClientOptions, BffForgotPasswordRequest, BffLoginRequest, BffOtpRequestRequest, BffOtpRequestResult, BffOtpVerifyRequest, BffPinLoginRequest, BffRegisterRequest, BffResetPasswordRequest, BffUser, DemoCredentials, HttpClient, HttpRequest, HttpResponse, createFetchHttpClient } from '@dloizides/auth-client';
4
4
  import { UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
5
5
 
6
6
  /**
@@ -442,6 +442,27 @@ declare const DEFAULT_PREFERRED_METHOD_HINT_LABELS: PreferredMethodHintLabels;
442
442
  declare const DEFAULT_DEMO_CREDENTIALS_HINT_LABELS: DemoCredentialsHintLabels;
443
443
  declare const DEFAULT_RESET_PASSWORD_LABELS: ResetPasswordFormLabels;
444
444
 
445
+ /**
446
+ * Who asked for a prefill — the difference between "the user requested this" and
447
+ * "the app offered it".
448
+ *
449
+ * A seed that the USER asked for (tapping "use these credentials") must always
450
+ * apply: they can see the fields, they know what is in them, and they asked
451
+ * anyway. A seed the APP offers (a demo pair arriving from `GET /bff/config`
452
+ * mid-typing) must not: it is an unrequested, racing write against a field the
453
+ * user may already be filling in.
454
+ *
455
+ * Collapsing the two into a bare nonce bump is what makes a late-arriving config
456
+ * fetch overwrite the username a real account holder is halfway through typing.
457
+ * `<LoginForm>` switches on this, never on a string literal.
458
+ */
459
+ declare const enum PrefillIntent {
460
+ /** The user asked for it. Always applies, over whatever is in the fields. */
461
+ Explicit = "explicit",
462
+ /** The app offered it. Applies only to untouched, empty fields. */
463
+ Opportunistic = "opportunistic"
464
+ }
465
+
445
466
  /**
446
467
  * `<LoginForm>` — the ready-made, themeable password-login form.
447
468
  *
@@ -498,12 +519,20 @@ interface LoginFormProps {
498
519
  * props alone does nothing once mounted.
499
520
  *
500
521
  * Most apps should not manage this by hand: `useDemoCredentials` returns a
501
- * `loginFormProps` bag carrying all three, ready to spread.
522
+ * `loginFormProps` bag carrying all four, ready to spread.
502
523
  */
503
524
  prefillNonce?: number;
525
+ /**
526
+ * Whether the pending re-seed was requested by the user or offered by the app.
527
+ * An {@link PrefillIntent.Opportunistic} re-seed (e.g. a demo pair arriving
528
+ * from `GET /bff/config` after mount) is DROPPED unless both fields are still
529
+ * empty, so a late fetch never overwrites what someone is typing. Defaults to
530
+ * unconditional — omit it and every nonce bump applies.
531
+ */
532
+ prefillIntent?: PrefillIntent;
504
533
  }
505
534
  /** Themeable password-login form built on `useBffAuth`. */
506
- declare function LoginForm({ client, theme: themeProp, labels: labelsProp, onSuccess, onForgotPassword, onSignUp, testIdPrefix, chromeless, initialUsername, initialPassword, prefillNonce, }: Readonly<LoginFormProps>): ReactElement;
535
+ declare function LoginForm({ client, theme: themeProp, labels: labelsProp, onSuccess, onForgotPassword, onSignUp, testIdPrefix, chromeless, initialUsername, initialPassword, prefillNonce, prefillIntent, }: Readonly<LoginFormProps>): ReactElement;
507
536
 
508
537
  /**
509
538
  * `<ForgotPasswordForm>` — the ready-made, themeable "request a reset link"
@@ -802,6 +831,7 @@ declare const enum DevicePinErrorKey {
802
831
  * error) so the unlock / enrol UIs can route on each one rather than collapsing
803
832
  * them into a single opaque failure.
804
833
  */
834
+
805
835
  /**
806
836
  * The per-device half of `GET /bff/config` — what this device remembers about
807
837
  * the last user, all fields safe-defaulted by the parser when absent.
@@ -824,6 +854,14 @@ interface BffLoginConfig {
824
854
  registrationEnabled: boolean;
825
855
  /** The per-device remembered state. */
826
856
  deviceState: BffDeviceState;
857
+ /**
858
+ * The published demo credentials this deployment advertises, or `null` when
859
+ * none is configured. Feeds `useDemoCredentials` / `<DemoCredentialsHint>`:
860
+ * `useDemoCredentials({ credentials: config.demo ?? undefined })`. `auth-client`
861
+ * 4.2.0 populates it; older clients (and hand-rolled objects) simply omit it,
862
+ * which reads as `undefined` — treated the same as "no demo" by the hint.
863
+ */
864
+ demo?: DemoCredentials | null;
827
865
  }
828
866
  /**
829
867
  * Discriminated result of a device-PIN UNLOCK attempt. The client never rejects;
@@ -1245,23 +1283,6 @@ interface PreferredMethodHintProps {
1245
1283
  /** Themeable login-screen preferred-method hint. Renders `null` when there is nothing to say. */
1246
1284
  declare function PreferredMethodHint({ method, theme: themeProp, labels: labelsProp, testIdPrefix, }: Readonly<PreferredMethodHintProps>): ReactElement | null;
1247
1285
 
1248
- /**
1249
- * Public demo-credential contracts.
1250
- *
1251
- * A "demo credential" is a REAL account that a product publishes openly so a
1252
- * prospective evaluator can sign in without a sign-up step. It is not a bypass:
1253
- * the credentials travel the ordinary login path (same BFF, same Keycloak, same
1254
- * session cookie, same roles). The package only makes them *discoverable* and
1255
- * *one-click fillable* — it never authenticates anything itself.
1256
- */
1257
- /** A publishable demo username + password pair. */
1258
- interface DemoCredentials {
1259
- /** The demo account's username. */
1260
- username: string;
1261
- /** The demo account's password. */
1262
- password: string;
1263
- }
1264
-
1265
1286
  /**
1266
1287
  * `<DemoCredentialsHint>` — the public-demo credentials panel.
1267
1288
  *
@@ -1942,6 +1963,7 @@ interface DemoCredentialsLoginFormProps {
1942
1963
  initialUsername?: string;
1943
1964
  initialPassword?: string;
1944
1965
  prefillNonce: number;
1966
+ prefillIntent: PrefillIntent;
1945
1967
  }
1946
1968
  /** The `<DemoCredentialsHint>` props that carry the credentials + fill action. */
1947
1969
  interface DemoCredentialsHintWiring {
@@ -2231,4 +2253,4 @@ declare function validatePasswordPolicy(password: string): PasswordPolicyError[]
2231
2253
  /** `true` when the password satisfies every policy rule. */
2232
2254
  declare function isPasswordValid(password: string): boolean;
2233
2255
 
2234
- export { AuthScreen, type AuthScreenProps, AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type BffDeviceState, type BffLoginConfig, type BffLogoutClient, type CreateBffAuthClientOptions, DEFAULT_AUTH_CARD_MAX_WIDTH, DEFAULT_DEMO_CREDENTIALS_HINT_LABELS, DEFAULT_DEVICE_PIN_ENROLL_LABELS, DEFAULT_DEVICE_PIN_SETTINGS_LABELS, DEFAULT_DEVICE_PIN_UNLOCK_LABELS, DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS, DEFAULT_FORGOT_PASSWORD_LABELS, DEFAULT_LOGIN_LABELS, DEFAULT_OTP_LABELS, DEFAULT_PASSKEY_LOGIN_LABELS, DEFAULT_PASSKEY_SETTINGS_LABELS, DEFAULT_PIN_LABELS, DEFAULT_PREFERRED_METHOD_HINT_LABELS, DEFAULT_PREFERRED_METHOD_SETTINGS_LABELS, DEFAULT_RESET_PASSWORD_LABELS, DEVICE_PIN_ALLOWED_DIGITS, DEVICE_PIN_DEFAULT_DIGITS, type DemoCredentials, DemoCredentialsHint, type DemoCredentialsHintLabels, type DemoCredentialsHintProps, type DemoCredentialsHintWiring, type DemoCredentialsLoginFormProps, type DevicePinCapableClient, type DevicePinDisableCapableClient, type DevicePinEnrollCapableClient, DevicePinEnrollErrorKey, DevicePinEnrollForm, type DevicePinEnrollFormProps, type DevicePinEnrollLabels, type DevicePinEnrollResult, DevicePinErrorKey, DevicePinInput, type DevicePinInputProps, DevicePinLengthPicker, type DevicePinLengthPickerProps, DevicePinOffer, type DevicePinOfferProps, DevicePinSettingsCard, type DevicePinSettingsCardProps, type DevicePinSettingsLabels, type DevicePinUnlockCapableClient, type DevicePinUnlockLabels, type DevicePinUnlockResult, DevicePinUnlockScreen, type DevicePinUnlockScreenProps, type DevicePinUnlockedUser, ForgotPasswordFields, type ForgotPasswordFieldsLabels, type ForgotPasswordFieldsProps, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, type LoginConfigCapableClient, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PasskeyCeremonyMode, PasskeyCeremonyStatus, type PasskeyErrorCode, PasskeyLoginButton, type PasskeyLoginButtonProps, type PasskeyLoginLabels, PasskeySettingsCard, type PasskeySettingsCardProps, type PasskeySettingsLabels, PasswordPolicyError, type PerformBffLogoutOptions, PinForm, type PinFormLabels, type PinFormProps, type PreferencesClientOptions, type PreferredMethodClient, PreferredMethodHint, type PreferredMethodHintLabels, type PreferredMethodHintProps, PreferredMethodSettingsCard, type PreferredMethodSettingsCardProps, type PreferredMethodSettingsLabels, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffLoginConfigResult, type UseBffResetPasswordOptions, type UseDemoCredentialsOptions, type UseDemoCredentialsResult, type UseDevicePinDisableArgs, type UseDevicePinDisableResult, type UseDevicePinEnrollArgs, type UseDevicePinEnrollResult, type UseDevicePinUnlockArgs, type UseDevicePinUnlockResult, type UseForgotPasswordSubmitArgs, type UseForgotPasswordSubmitResult, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePasskeyCeremonyOptions, type UsePasskeyCeremonyResult, type UsePinLoginOptions, type UsePinLoginResult, type UsePreferredMethodSettingArgs, type UsePreferredMethodSettingResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, cachePreferredMethod, clearCachedPreferredMethod, collectUserRoles, createBffAuthClient, createPreferencesClient, defaultAuthTheme, isAllowedPinDigits, isPasswordValid, isValidForgotPasswordEmail, performBffLogout, readCachedPreferredMethod, readPasskeyError, readPasskeyRegistered, resolveAccessibleRoutes, resolveDemoCredentials, resolvePostLoginRoute, startPasskeyLogin, startPasskeyRegistration, syncPreferredMethodFromServer, useAuthTheme, useBffAuth, useBffForgotPassword, useBffLoginConfig, useBffResetPassword, useDemoCredentials, useDevicePinDisable, useDevicePinEnroll, useDevicePinUnlock, useForgotPasswordSubmit, useOtpLogin, usePasskeyCeremony, usePinLogin, usePreferredMethodSetting, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
2256
+ export { AuthScreen, type AuthScreenProps, AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type BffDeviceState, type BffLoginConfig, type BffLogoutClient, type CreateBffAuthClientOptions, DEFAULT_AUTH_CARD_MAX_WIDTH, DEFAULT_DEMO_CREDENTIALS_HINT_LABELS, DEFAULT_DEVICE_PIN_ENROLL_LABELS, DEFAULT_DEVICE_PIN_SETTINGS_LABELS, DEFAULT_DEVICE_PIN_UNLOCK_LABELS, DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS, DEFAULT_FORGOT_PASSWORD_LABELS, DEFAULT_LOGIN_LABELS, DEFAULT_OTP_LABELS, DEFAULT_PASSKEY_LOGIN_LABELS, DEFAULT_PASSKEY_SETTINGS_LABELS, DEFAULT_PIN_LABELS, DEFAULT_PREFERRED_METHOD_HINT_LABELS, DEFAULT_PREFERRED_METHOD_SETTINGS_LABELS, DEFAULT_RESET_PASSWORD_LABELS, DEVICE_PIN_ALLOWED_DIGITS, DEVICE_PIN_DEFAULT_DIGITS, DemoCredentialsHint, type DemoCredentialsHintLabels, type DemoCredentialsHintProps, type DemoCredentialsHintWiring, type DemoCredentialsLoginFormProps, type DevicePinCapableClient, type DevicePinDisableCapableClient, type DevicePinEnrollCapableClient, DevicePinEnrollErrorKey, DevicePinEnrollForm, type DevicePinEnrollFormProps, type DevicePinEnrollLabels, type DevicePinEnrollResult, DevicePinErrorKey, DevicePinInput, type DevicePinInputProps, DevicePinLengthPicker, type DevicePinLengthPickerProps, DevicePinOffer, type DevicePinOfferProps, DevicePinSettingsCard, type DevicePinSettingsCardProps, type DevicePinSettingsLabels, type DevicePinUnlockCapableClient, type DevicePinUnlockLabels, type DevicePinUnlockResult, DevicePinUnlockScreen, type DevicePinUnlockScreenProps, type DevicePinUnlockedUser, ForgotPasswordFields, type ForgotPasswordFieldsLabels, type ForgotPasswordFieldsProps, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, type LoginConfigCapableClient, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PasskeyCeremonyMode, PasskeyCeremonyStatus, type PasskeyErrorCode, PasskeyLoginButton, type PasskeyLoginButtonProps, type PasskeyLoginLabels, PasskeySettingsCard, type PasskeySettingsCardProps, type PasskeySettingsLabels, PasswordPolicyError, type PerformBffLogoutOptions, PinForm, type PinFormLabels, type PinFormProps, type PreferencesClientOptions, type PreferredMethodClient, PreferredMethodHint, type PreferredMethodHintLabels, type PreferredMethodHintProps, PreferredMethodSettingsCard, type PreferredMethodSettingsCardProps, type PreferredMethodSettingsLabels, PrefillIntent, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffLoginConfigResult, type UseBffResetPasswordOptions, type UseDemoCredentialsOptions, type UseDemoCredentialsResult, type UseDevicePinDisableArgs, type UseDevicePinDisableResult, type UseDevicePinEnrollArgs, type UseDevicePinEnrollResult, type UseDevicePinUnlockArgs, type UseDevicePinUnlockResult, type UseForgotPasswordSubmitArgs, type UseForgotPasswordSubmitResult, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePasskeyCeremonyOptions, type UsePasskeyCeremonyResult, type UsePinLoginOptions, type UsePinLoginResult, type UsePreferredMethodSettingArgs, type UsePreferredMethodSettingResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, cachePreferredMethod, clearCachedPreferredMethod, collectUserRoles, createBffAuthClient, createPreferencesClient, defaultAuthTheme, isAllowedPinDigits, isPasswordValid, isValidForgotPasswordEmail, performBffLogout, readCachedPreferredMethod, readPasskeyError, readPasskeyRegistered, resolveAccessibleRoutes, resolveDemoCredentials, resolvePostLoginRoute, startPasskeyLogin, startPasskeyRegistration, syncPreferredMethodFromServer, useAuthTheme, useBffAuth, useBffForgotPassword, useBffLoginConfig, useBffResetPassword, useDemoCredentials, useDevicePinDisable, useDevicePinEnroll, useDevicePinUnlock, useForgotPasswordSubmit, useOtpLogin, usePasskeyCeremony, usePinLogin, usePreferredMethodSetting, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
package/dist/index.js CHANGED
@@ -494,18 +494,32 @@ function useAuthStyles(theme) {
494
494
  });
495
495
  }, [theme]);
496
496
  }
497
+
498
+ // src/components/PrefillIntent.ts
499
+ var PrefillIntent = /* @__PURE__ */ ((PrefillIntent2) => {
500
+ PrefillIntent2["Explicit"] = "explicit";
501
+ PrefillIntent2["Opportunistic"] = "opportunistic";
502
+ return PrefillIntent2;
503
+ })(PrefillIntent || {});
504
+
505
+ // src/components/useLoginPrefill.ts
497
506
  function useLoginPrefill({
498
507
  initialUsername,
499
508
  initialPassword,
500
- prefillNonce
509
+ prefillNonce,
510
+ prefillIntent
501
511
  }) {
502
512
  const [username, setUsername] = react.useState(initialUsername ?? "");
503
513
  const [password, setPassword] = react.useState(initialPassword ?? "");
504
514
  const [appliedNonce, setAppliedNonce] = react.useState(prefillNonce);
505
515
  if (prefillNonce !== appliedNonce) {
506
516
  setAppliedNonce(prefillNonce);
507
- setUsername(initialUsername ?? "");
508
- setPassword(initialPassword ?? "");
517
+ const isPristine = username.length === 0 && password.length === 0;
518
+ const mayApply = prefillIntent !== "opportunistic" /* Opportunistic */ || isPristine;
519
+ if (mayApply) {
520
+ setUsername(initialUsername ?? "");
521
+ setPassword(initialPassword ?? "");
522
+ }
509
523
  }
510
524
  return { username, password, setUsername, setPassword };
511
525
  }
@@ -623,7 +637,8 @@ function LoginForm({
623
637
  chromeless = false,
624
638
  initialUsername,
625
639
  initialPassword,
626
- prefillNonce
640
+ prefillNonce,
641
+ prefillIntent
627
642
  }) {
628
643
  const theme = useAuthTheme(themeProp);
629
644
  const styles3 = useAuthStyles(theme);
@@ -635,7 +650,8 @@ function LoginForm({
635
650
  const { username, password, setUsername, setPassword } = useLoginPrefill({
636
651
  initialUsername,
637
652
  initialPassword,
638
- prefillNonce
653
+ prefillNonce,
654
+ prefillIntent
639
655
  });
640
656
  const [errorText, setErrorText] = react.useState(null);
641
657
  const runLogin = react.useCallback(async () => {
@@ -3324,7 +3340,8 @@ var EMPTY_CONFIG = {
3324
3340
  hasPin: false,
3325
3341
  pinDigits: null,
3326
3342
  preferredMethod: null
3327
- }
3343
+ },
3344
+ demo: null
3328
3345
  };
3329
3346
  function useBffLoginConfig(client) {
3330
3347
  const [config, setConfig] = react.useState(EMPTY_CONFIG);
@@ -3421,6 +3438,12 @@ async function syncPreferredMethodFromServer(client) {
3421
3438
  return null;
3422
3439
  }
3423
3440
  }
3441
+ function isSamePair(a, b) {
3442
+ if (a === null || b === null) {
3443
+ return a === b;
3444
+ }
3445
+ return a.username === b.username && a.password === b.password;
3446
+ }
3424
3447
  function useDemoCredentials({
3425
3448
  credentials,
3426
3449
  prefillOnMount = false
@@ -3430,23 +3453,38 @@ function useDemoCredentials({
3430
3453
  prefillOnMount ? resolved : null
3431
3454
  );
3432
3455
  const [prefillNonce, setPrefillNonce] = react.useState(0);
3456
+ const [prefillIntent, setPrefillIntent] = react.useState(
3457
+ "opportunistic" /* Opportunistic */
3458
+ );
3459
+ const [autoSeeded, setAutoSeeded] = react.useState(
3460
+ prefillOnMount ? resolved : null
3461
+ );
3462
+ const shouldAutoSeed = prefillOnMount && resolved !== null && !isSamePair(resolved, autoSeeded);
3463
+ if (shouldAutoSeed) {
3464
+ setAutoSeeded(resolved);
3465
+ setFilled(resolved);
3466
+ setPrefillNonce((current) => current + 1);
3467
+ setPrefillIntent("opportunistic" /* Opportunistic */);
3468
+ }
3433
3469
  const handleUse = react.useCallback((next) => {
3434
3470
  setFilled(next);
3435
3471
  setPrefillNonce((current) => current + 1);
3472
+ setPrefillIntent("explicit" /* Explicit */);
3436
3473
  }, []);
3437
3474
  return react.useMemo(
3438
3475
  () => ({
3439
3476
  loginFormProps: {
3440
3477
  initialUsername: filled?.username,
3441
3478
  initialPassword: filled?.password,
3442
- prefillNonce
3479
+ prefillNonce,
3480
+ prefillIntent
3443
3481
  },
3444
3482
  // `credentials` is passed through unresolved on purpose: the hint applies
3445
3483
  // `resolveDemoCredentials` itself, so it fails closed even when it is used
3446
3484
  // standalone, without this hook.
3447
3485
  hintProps: { credentials, onUse: handleUse }
3448
3486
  }),
3449
- [filled, prefillNonce, credentials, handleUse]
3487
+ [filled, prefillNonce, prefillIntent, credentials, handleUse]
3450
3488
  );
3451
3489
  }
3452
3490
  async function lazyFetchHttpClient(request) {
@@ -3588,6 +3626,7 @@ exports.PasswordPolicyError = PasswordPolicyError;
3588
3626
  exports.PinForm = PinForm;
3589
3627
  exports.PreferredMethodHint = PreferredMethodHint;
3590
3628
  exports.PreferredMethodSettingsCard = PreferredMethodSettingsCard;
3629
+ exports.PrefillIntent = PrefillIntent;
3591
3630
  exports.ResetPasswordError = ResetPasswordError;
3592
3631
  exports.ResetPasswordForm = ResetPasswordForm;
3593
3632
  exports.cachePreferredMethod = cachePreferredMethod;