@arcblock/did-connect-core 4.1.53 → 4.1.54

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.
@@ -381,12 +381,19 @@ var messages = {
381
381
  emailExplanation: "We will send you a verification code. No password needed.",
382
382
  otherMethods: "Other ways to sign in",
383
383
  passkeyCreateAccount: "Create an account with Passkey",
384
- lastUsed: "Last used: {method}",
384
+ lastUsed: "Last used",
385
385
  passkeyExplanation: "Create a new account using your fingerprint, face, or device unlock. Your device will ask you to save a Passkey. Already have an account? Go back to sign in.",
386
386
  continueWith: "Continue with {provider}",
387
387
  oauthWaiting: "Continue in the {provider} window",
388
388
  oauthIncomplete: "Sign-in is not complete. Reopen the window or choose another method.",
389
389
  oauthReopen: "Reopen window",
390
+ oauthUnconfirmed: "We could not confirm sign-in. Reload this page to check your session before trying again.",
391
+ oauthCheckSession: "Reload and check sign-in",
392
+ oauthCancelled: "Stopped waiting for sign-in. If the provider window is still open, close it before retrying.",
393
+ oauthDenied: "Authorization was declined. Try again or choose another sign-in method.",
394
+ oauthBlocked: "Your browser blocked the sign-in window. Reopen it or continue on this page.",
395
+ oauthTimedOut: "Sign-in timed out. Reopen the window to start again.",
396
+ oauthFailed: "We could not complete sign-in. Try again or choose another method.",
390
397
  oauthFullPage: "Continue on this page",
391
398
  emailEdit: "Change email",
392
399
  emailCooldown: "Resend in {seconds}s",
@@ -447,12 +454,19 @@ var messages = {
447
454
  emailExplanation: "\u6211\u4EEC\u4F1A\u53D1\u9001\u9A8C\u8BC1\u7801\uFF0C\u65E0\u9700\u5BC6\u7801\u3002",
448
455
  otherMethods: "\u5176\u4ED6\u767B\u5F55\u65B9\u5F0F",
449
456
  passkeyCreateAccount: "\u4F7F\u7528 Passkey \u521B\u5EFA\u8D26\u6237",
450
- lastUsed: "\u4E0A\u6B21\u4F7F\u7528\uFF1A{method}",
457
+ lastUsed: "\u4E0A\u6B21\u4F7F\u7528",
451
458
  passkeyExplanation: "\u4F7F\u7528\u6307\u7EB9\u3001\u9762\u5BB9\u6216\u8BBE\u5907\u89E3\u9501\u65B9\u5F0F\u521B\u5EFA\u65B0\u8D26\u6237\uFF0C\u65E0\u9700\u5BC6\u7801\u3002\u8BBE\u5907\u4F1A\u63D0\u793A\u4FDD\u5B58 Passkey\u3002\u5DF2\u6709\u8D26\u6237\uFF1F\u8BF7\u8FD4\u56DE\u767B\u5F55\u3002",
452
459
  continueWith: "\u4F7F\u7528 {provider} \u7EE7\u7EED",
453
460
  oauthWaiting: "\u8BF7\u5728 {provider} \u7A97\u53E3\u4E2D\u7EE7\u7EED",
454
461
  oauthIncomplete: "\u767B\u5F55\u5C1A\u672A\u5B8C\u6210\u3002\u60A8\u53EF\u4EE5\u91CD\u65B0\u6253\u5F00\u7A97\u53E3\u6216\u9009\u62E9\u5176\u4ED6\u65B9\u5F0F\u3002",
455
462
  oauthReopen: "\u91CD\u65B0\u6253\u5F00\u7A97\u53E3",
463
+ oauthUnconfirmed: "\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u767B\u5F55\u7ED3\u679C\u3002\u8BF7\u5148\u91CD\u65B0\u52A0\u8F7D\u9875\u9762\u68C0\u67E5\u767B\u5F55\u72B6\u6001\uFF0C\u518D\u51B3\u5B9A\u662F\u5426\u91CD\u8BD5\u3002",
464
+ oauthCheckSession: "\u91CD\u65B0\u52A0\u8F7D\u5E76\u68C0\u67E5\u767B\u5F55\u72B6\u6001",
465
+ oauthCancelled: "\u5DF2\u505C\u6B62\u7B49\u5F85\u767B\u5F55\u3002\u82E5\u6388\u6743\u7A97\u53E3\u4ECD\u7136\u6253\u5F00\uFF0C\u8BF7\u5173\u95ED\u540E\u518D\u91CD\u8BD5\u3002",
466
+ oauthDenied: "\u60A8\u672A\u540C\u610F\u6388\u6743\u3002\u53EF\u4EE5\u91CD\u8BD5\u6216\u9009\u62E9\u5176\u4ED6\u767B\u5F55\u65B9\u5F0F\u3002",
467
+ oauthBlocked: "\u6D4F\u89C8\u5668\u62E6\u622A\u4E86\u767B\u5F55\u7A97\u53E3\u3002\u8BF7\u91CD\u65B0\u6253\u5F00\uFF0C\u6216\u5728\u5F53\u524D\u9875\u9762\u7EE7\u7EED\u3002",
468
+ oauthTimedOut: "\u767B\u5F55\u7B49\u5F85\u5DF2\u8D85\u65F6\u3002\u8BF7\u91CD\u65B0\u6253\u5F00\u7A97\u53E3\u518D\u8BD5\u3002",
469
+ oauthFailed: "\u672A\u80FD\u5B8C\u6210\u767B\u5F55\u3002\u8BF7\u91CD\u8BD5\u6216\u9009\u62E9\u5176\u4ED6\u65B9\u5F0F\u3002",
456
470
  oauthFullPage: "\u5728\u5F53\u524D\u9875\u9762\u7EE7\u7EED",
457
471
  emailEdit: "\u4FEE\u6539\u90AE\u7BB1",
458
472
  emailCooldown: "{seconds} \u79D2\u540E\u91CD\u53D1",
@@ -513,12 +527,19 @@ var messages = {
513
527
  emailExplanation: "\u6211\u5011\u6703\u50B3\u9001\u9A57\u8B49\u78BC\uFF0C\u7121\u9700\u5BC6\u78BC\u3002",
514
528
  otherMethods: "\u5176\u4ED6\u767B\u5165\u65B9\u5F0F",
515
529
  passkeyCreateAccount: "\u4F7F\u7528 Passkey \u5EFA\u7ACB\u5E33\u6236",
516
- lastUsed: "\u4E0A\u6B21\u4F7F\u7528\uFF1A{method}",
530
+ lastUsed: "\u4E0A\u6B21\u4F7F\u7528",
517
531
  passkeyExplanation: "\u4F7F\u7528\u6307\u7D0B\u3001\u9762\u5BB9\u6216\u88DD\u7F6E\u89E3\u9396\u65B9\u5F0F\u5EFA\u7ACB\u65B0\u5E33\u6236\uFF0C\u7121\u9700\u5BC6\u78BC\u3002\u88DD\u7F6E\u6703\u63D0\u793A\u5132\u5B58 Passkey\u3002\u5DF2\u6709\u5E33\u6236\uFF1F\u8ACB\u8FD4\u56DE\u767B\u5165\u3002",
518
532
  continueWith: "\u4F7F\u7528 {provider} \u7E7C\u7E8C",
519
533
  oauthWaiting: "\u8ACB\u5728 {provider} \u8996\u7A97\u4E2D\u7E7C\u7E8C",
520
534
  oauthIncomplete: "\u767B\u5165\u5C1A\u672A\u5B8C\u6210\u3002\u60A8\u53EF\u4EE5\u91CD\u65B0\u958B\u555F\u8996\u7A97\u6216\u9078\u64C7\u5176\u4ED6\u65B9\u5F0F\u3002",
521
535
  oauthReopen: "\u91CD\u65B0\u958B\u555F\u8996\u7A97",
536
+ oauthUnconfirmed: "\u66AB\u6642\u7121\u6CD5\u78BA\u8A8D\u767B\u5165\u7D50\u679C\u3002\u8ACB\u5148\u91CD\u65B0\u8F09\u5165\u9801\u9762\u6AA2\u67E5\u767B\u5165\u72C0\u614B\uFF0C\u518D\u6C7A\u5B9A\u662F\u5426\u91CD\u8A66\u3002",
537
+ oauthCheckSession: "\u91CD\u65B0\u8F09\u5165\u4E26\u6AA2\u67E5\u767B\u5165\u72C0\u614B",
538
+ oauthCancelled: "\u5DF2\u505C\u6B62\u7B49\u5F85\u767B\u5165\u3002\u82E5\u6388\u6B0A\u8996\u7A97\u4ECD\u7136\u958B\u555F\uFF0C\u8ACB\u95DC\u9589\u5F8C\u518D\u91CD\u8A66\u3002",
539
+ oauthDenied: "\u60A8\u672A\u540C\u610F\u6388\u6B0A\u3002\u53EF\u4EE5\u91CD\u8A66\u6216\u9078\u64C7\u5176\u4ED6\u767B\u5165\u65B9\u5F0F\u3002",
540
+ oauthBlocked: "\u700F\u89BD\u5668\u5C01\u9396\u4E86\u767B\u5165\u8996\u7A97\u3002\u8ACB\u91CD\u65B0\u958B\u555F\uFF0C\u6216\u5728\u76EE\u524D\u9801\u9762\u7E7C\u7E8C\u3002",
541
+ oauthTimedOut: "\u767B\u5165\u7B49\u5F85\u5DF2\u903E\u6642\u3002\u8ACB\u91CD\u65B0\u958B\u555F\u8996\u7A97\u518D\u8A66\u3002",
542
+ oauthFailed: "\u672A\u80FD\u5B8C\u6210\u767B\u5165\u3002\u8ACB\u91CD\u8A66\u6216\u9078\u64C7\u5176\u4ED6\u65B9\u5F0F\u3002",
522
543
  oauthFullPage: "\u5728\u76EE\u524D\u9801\u9762\u7E7C\u7E8C",
523
544
  emailEdit: "\u4FEE\u6539\u96FB\u5B50\u90F5\u4EF6",
524
545
  emailCooldown: "{seconds} \u79D2\u5F8C\u91CD\u65B0\u50B3\u9001",
@@ -579,12 +600,19 @@ var messages = {
579
600
  emailExplanation: "\u78BA\u8A8D\u30B3\u30FC\u30C9\u3092\u9001\u4FE1\u3057\u307E\u3059\u3002\u30D1\u30B9\u30EF\u30FC\u30C9\u306F\u4E0D\u8981\u3067\u3059\u3002",
580
601
  otherMethods: "\u4ED6\u306E\u30ED\u30B0\u30A4\u30F3\u65B9\u6CD5",
581
602
  passkeyCreateAccount: "Passkey \u3067\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F5C\u6210",
582
- lastUsed: "\u524D\u56DE\u306E\u65B9\u6CD5\uFF1A{method}",
603
+ lastUsed: "\u524D\u56DE\u4F7F\u7528",
583
604
  passkeyExplanation: "\u6307\u7D0B\u3001\u9854\u8A8D\u8A3C\u3001\u307E\u305F\u306F\u30C7\u30D0\u30A4\u30B9\u306E\u30ED\u30C3\u30AF\u89E3\u9664\u3067\u65B0\u3057\u3044\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F5C\u6210\u3057\u307E\u3059\u3002\u30D1\u30B9\u30EF\u30FC\u30C9\u306F\u4E0D\u8981\u3067\u3059\u3002\u30C7\u30D0\u30A4\u30B9\u304C Passkey \u306E\u4FDD\u5B58\u3092\u6C42\u3081\u307E\u3059\u3002\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u304A\u6301\u3061\u3067\u3059\u304B\uFF1F\u30ED\u30B0\u30A4\u30F3\u306B\u623B\u3063\u3066\u304F\u3060\u3055\u3044\u3002",
584
605
  continueWith: "{provider} \u3067\u7D9A\u3051\u308B",
585
606
  oauthWaiting: "{provider} \u306E\u30A6\u30A3\u30F3\u30C9\u30A6\u3067\u7D9A\u3051\u3066\u304F\u3060\u3055\u3044",
586
607
  oauthIncomplete: "\u30ED\u30B0\u30A4\u30F3\u306F\u307E\u3060\u5B8C\u4E86\u3057\u3066\u3044\u307E\u305B\u3093\u3002\u30A6\u30A3\u30F3\u30C9\u30A6\u3092\u958B\u304D\u76F4\u3059\u304B\u3001\u5225\u306E\u65B9\u6CD5\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002",
587
608
  oauthReopen: "\u30A6\u30A3\u30F3\u30C9\u30A6\u3092\u958B\u304D\u76F4\u3059",
609
+ oauthUnconfirmed: "\u30ED\u30B0\u30A4\u30F3\u7D50\u679C\u3092\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u518D\u8A66\u884C\u306E\u524D\u306B\u30DA\u30FC\u30B8\u3092\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u72B6\u614B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
610
+ oauthCheckSession: "\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u3066\u30ED\u30B0\u30A4\u30F3\u3092\u78BA\u8A8D",
611
+ oauthCancelled: "\u30ED\u30B0\u30A4\u30F3\u306E\u5F85\u6A5F\u3092\u7D42\u4E86\u3057\u307E\u3057\u305F\u3002\u8A8D\u8A3C\u30A6\u30A3\u30F3\u30C9\u30A6\u304C\u958B\u3044\u3066\u3044\u308B\u5834\u5408\u306F\u3001\u9589\u3058\u3066\u304B\u3089\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
612
+ oauthDenied: "\u8A8D\u8A3C\u304C\u8A31\u53EF\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u518D\u8A66\u884C\u3059\u308B\u304B\u3001\u5225\u306E\u65B9\u6CD5\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002",
613
+ oauthBlocked: "\u30D6\u30E9\u30A6\u30B6\u304C\u30ED\u30B0\u30A4\u30F3\u753B\u9762\u3092\u30D6\u30ED\u30C3\u30AF\u3057\u307E\u3057\u305F\u3002\u958B\u304D\u76F4\u3059\u304B\u3001\u3053\u306E\u30DA\u30FC\u30B8\u3067\u7D9A\u3051\u3066\u304F\u3060\u3055\u3044\u3002",
614
+ oauthTimedOut: "\u30ED\u30B0\u30A4\u30F3\u304C\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u3057\u307E\u3057\u305F\u3002\u30A6\u30A3\u30F3\u30C9\u30A6\u3092\u958B\u304D\u76F4\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
615
+ oauthFailed: "\u30ED\u30B0\u30A4\u30F3\u3092\u5B8C\u4E86\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u518D\u8A66\u884C\u3059\u308B\u304B\u3001\u5225\u306E\u65B9\u6CD5\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002",
588
616
  oauthFullPage: "\u3053\u306E\u30DA\u30FC\u30B8\u3067\u7D9A\u3051\u308B",
589
617
  emailEdit: "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5909\u66F4",
590
618
  emailCooldown: "{seconds} \u79D2\u5F8C\u306B\u518D\u9001\u4FE1",
@@ -2980,28 +3008,41 @@ h1 {
2980
3008
  border-color: var(--border-strong);
2981
3009
  }
2982
3010
  .locale-trigger svg { width: 14px; height: 14px; flex-shrink: 0; }
3011
+ /* One compact menu surface; rows are choices, not nested pill buttons. */
2983
3012
  .locale-menu {
2984
- display: none;
2985
- flex-direction: column;
2986
- align-items: stretch;
2987
- gap: 4px;
2988
- margin-top: 4px;
3013
+ display: none; position: absolute; top: calc(100% + 6px); right: 0; z-index: 4;
3014
+ min-width: 144px; width: max-content; max-width: min(240px,calc(100vw - 64px));
3015
+ padding: 4px; background: var(--bg-card); border: 1px solid var(--border);
3016
+ border-radius: 10px; box-shadow: var(--shadow-lg);
2989
3017
  }
2990
- .locale-switcher.open .locale-menu {
2991
- display: flex;
3018
+ .locale-switcher.open .locale-menu { display: flex; flex-direction: column; }
3019
+ .locale-menu .locale-btn {
3020
+ width: 100%; min-height: 36px; height: auto; padding: 7px 10px; gap: 8px;
3021
+ border: 0; border-radius: 6px; background: transparent; text-align: left;
3022
+ font-size: 13px; line-height: 1.4; overflow-wrap: anywhere;
2992
3023
  }
2993
- .locale-btn[aria-checked="true"] {
2994
- color: var(--text);
2995
- border-color: var(--border-strong);
2996
- }
2997
- .locale-check { font-size: 11px; line-height: 1; }
3024
+ .locale-menu .locale-btn:hover, .locale-menu .locale-btn:focus-visible { background: var(--bg-hover); color: var(--text); }
3025
+ .locale-menu .locale-btn[aria-checked="true"] { color: var(--text); font-weight: 600; }
3026
+ .locale-check { width: 14px; flex: 0 0 14px; font-size: 12px; line-height: 1; }
3027
+ @media (pointer: coarse) { .locale-menu .locale-btn { min-height: 44px; } }
3028
+ .has-last-used { flex-wrap: wrap; }
3029
+ .last-used { display: inline-block; font-size: 11px; font-weight: 400; line-height: 1.4;
3030
+ padding: 2px 6px; border: 1px solid currentColor; border-radius: 4px; opacity: .72; white-space: nowrap; }
2998
3031
  .card { position: relative; }
2999
3032
 
3000
3033
  /* Long localized method labels remain readable on narrow screens. */
3001
3034
  .methods-grid { grid-template-columns: 1fr; }
3002
3035
  .method-btn { min-height: 48px; white-space: normal; }
3003
3036
  .passkey-alt { min-height: 44px; font-weight: 600; }
3004
- .oauth-recovery { font-size: 14px; line-height: 1.5; margin: 12px 0; }
3037
+ .oauth-recovery { font-size: 14px; line-height: 1.5; margin: 12px 0; padding: 12px;
3038
+ border: 1px solid var(--border); border-radius: var(--radius); color: var(--text-secondary); text-align: left; }
3039
+ .oauth-recovery p { margin: 0 0 8px; overflow-wrap: anywhere; }
3040
+ .oauth-recovery-actions { display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: center; }
3041
+ .oauth-recovery-actions button, .oauth-recovery-actions a { font: inherit; font-size: 13px; min-height: 44px;
3042
+ display: inline-flex; align-items: center; padding: 4px 0; color: var(--text); border: 0; background: none; cursor: pointer; text-decoration: underline; text-underline-offset: 3px; }
3043
+ .oauth-recovery-actions[hidden], .oauth-recovery-actions [hidden] { display: none; }
3044
+ .oauth-recovery-actions :focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }
3045
+
3005
3046
  @media (max-width: 480px) { .card { padding: 24px 20px; width: 100%; box-sizing: border-box; } h1 { font-size: 24px; } }
3006
3047
 
3007
3048
  .passkey-create-entry { display: flex; justify-content: center; align-items: center; width: 100%; box-sizing: border-box; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-surface); color: var(--text); padding: 10px 14px; }
@@ -3014,11 +3055,7 @@ h1 {
3014
3055
  .card-modal .locale-switcher { min-width: 0; max-width: 100%; }
3015
3056
  .card-modal .locale-trigger { max-width: 100%; height: 36px; }
3016
3057
  .locale-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3017
- .card-modal .locale-menu { position: absolute; top: calc(100% + 8px); left: 0; z-index: 4; margin-top: 0;
3018
- width: max-content; max-width: min(260px,calc(100vw - 80px)); padding: 6px;
3019
- background: var(--bg-card); border: 1px solid var(--border-strong); border-radius: 10px; box-shadow: var(--shadow-lg); }
3020
- .card-modal .locale-menu .locale-btn { min-height: 40px; height: auto; text-align: left; border-radius: 6px; }
3021
- .card-modal .locale-menu .locale-btn span { overflow-wrap: anywhere; }
3058
+ .card-modal .locale-menu { left: 0; right: auto; }
3022
3059
 
3023
3060
  .card { padding: 52px 24px 24px; box-sizing: border-box; }
3024
3061
  .card-modal { width: 100%; }
@@ -3027,7 +3064,13 @@ h1 {
3027
3064
  .card.card-modal, :host([data-theme="light"]) .card.card-modal { box-shadow: none; }
3028
3065
  /* A translateY entrance expands an auto scroller by 4px until it finishes. */
3029
3066
  .card-modal[data-view] { animation: none; }
3030
- .card-modal .back-btn { position: static; display: flex; width: max-content; max-width: 100%; margin: 0 0 16px; }
3067
+ /* Sub-step navigation shares the close button's center line in both modes. */
3068
+ .back-btn { top: 8px; left: 24px; height: 44px; max-width: calc(100% - 160px); line-height: 1.4; text-align: left; }
3069
+ .back-btn:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; border-radius: 4px; }
3070
+ .card-modal .back-btn { position: absolute; top: 8px; left: 24px; margin: 0; }
3071
+ .card-modal:has(.back-btn) .card-tools { left: auto; right: 64px; max-width: calc(100% - 160px); }
3072
+ .passkey-creating > .home-link { display: none; }
3073
+ @media (max-width:480px) { .back-btn, .card-modal .back-btn { left: 18px; } }
3031
3074
  .card-modal h1 { display: block; overflow: visible; -webkit-line-clamp: unset; line-clamp: unset; line-height: 1.4; padding-block: 2px; }
3032
3075
  .card-modal h1[tabindex="-1"]:focus { outline: none; }
3033
3076
  .card-modal h1, .card-modal h2, .card-modal .subtitle, .card-modal .status { overflow-wrap: anywhere; }
@@ -3474,6 +3517,8 @@ var LoginPage = class {
3474
3517
  }
3475
3518
  /** Locale sits on the card's top-right row, level with Back / Cancel. */
3476
3519
  renderCardTools(card) {
3520
+ if (!(this.options.showLocaleSelector ?? this.options.mode !== "dialog"))
3521
+ return;
3477
3522
  const tools = document.createElement("div");
3478
3523
  tools.className = "card-tools";
3479
3524
  this.renderLocaleSwitcher(tools);
@@ -3531,6 +3576,8 @@ var LoginPage = class {
3531
3576
  trigger.addEventListener("click", (event) => {
3532
3577
  event.stopPropagation();
3533
3578
  setOpen(menu.hidden);
3579
+ if (!menu.hidden)
3580
+ menu.querySelector('[aria-checked="true"]')?.focus();
3534
3581
  });
3535
3582
  for (const item of locales) {
3536
3583
  const checked = item.code === current.code;
@@ -3540,12 +3587,13 @@ var LoginPage = class {
3540
3587
  btn.setAttribute("role", "menuitemradio");
3541
3588
  btn.setAttribute("aria-checked", checked ? "true" : "false");
3542
3589
  btn.dataset.locale = item.code;
3590
+ btn.tabIndex = -1;
3543
3591
  btn.title = item.label;
3544
- if (checked) {
3592
+ {
3545
3593
  const mark = document.createElement("span");
3546
3594
  mark.className = "locale-check";
3547
3595
  mark.setAttribute("aria-hidden", "true");
3548
- mark.textContent = "\u2713";
3596
+ mark.textContent = checked ? "\u2713" : "";
3549
3597
  btn.appendChild(mark);
3550
3598
  }
3551
3599
  const label = document.createElement("span");
@@ -3557,6 +3605,24 @@ var LoginPage = class {
3557
3605
  });
3558
3606
  menu.appendChild(btn);
3559
3607
  }
3608
+ wrapper.addEventListener("keydown", (event) => {
3609
+ const items = [...menu.querySelectorAll(".locale-btn")];
3610
+ const active = this.shadow?.activeElement ?? document.activeElement;
3611
+ const index = items.indexOf(active);
3612
+ if (event.key === "Escape") {
3613
+ event.preventDefault();
3614
+ event.stopPropagation();
3615
+ setOpen(false);
3616
+ trigger.focus();
3617
+ } else if (event.key === "Tab")
3618
+ setOpen(false);
3619
+ else if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
3620
+ event.preventDefault();
3621
+ setOpen(true);
3622
+ const next = event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : index < 0 ? event.key === "ArrowUp" ? items.length - 1 : 0 : (index + (event.key === "ArrowUp" ? -1 : 1) + items.length) % items.length;
3623
+ items[next]?.focus();
3624
+ }
3625
+ });
3560
3626
  const onOutsideClick = (event) => {
3561
3627
  const path = event.composedPath();
3562
3628
  if (path.includes(wrapper))
@@ -3607,25 +3673,25 @@ var LoginPage = class {
3607
3673
  break;
3608
3674
  }
3609
3675
  }
3676
+ /** Keep recognition next to the action, without a second page subtitle. */
3677
+ markLastUsed(button, method, locale) {
3678
+ if (!this.options.preferenceKey)
3679
+ return;
3680
+ try {
3681
+ if (localStorage.getItem(`did-login-method:${this.options.preferenceKey}`) !== method)
3682
+ return;
3683
+ const hint = document.createElement("span");
3684
+ hint.className = "last-used";
3685
+ hint.textContent = t("lastUsed", locale);
3686
+ button.classList.add("has-last-used");
3687
+ button.appendChild(hint);
3688
+ } catch {
3689
+ }
3690
+ }
3610
3691
  renderMethodsView(card, locale, methods) {
3611
3692
  if (this.options.mode !== "dialog")
3612
3693
  this.renderHomeLink(card, locale);
3613
3694
  this.renderBrandHeader(card, locale);
3614
- if (this.options.preferenceKey) {
3615
- try {
3616
- const last = localStorage.getItem(`did-login-method:${this.options.preferenceKey}`);
3617
- const labels = { passkey: "Passkey", oauth: "OAuth", email: t("emailButton", locale), "did-connect": "DID Wallet" };
3618
- const provider = last?.startsWith("oauth:") ? this.options.oauthProviders?.find((p) => p.enabled && `oauth:${p.id}` === last) : void 0;
3619
- const label = provider?.name ?? (last ? labels[last] : void 0);
3620
- if (last && label && (methods.includes(last) || provider && methods.includes("oauth"))) {
3621
- const hint = document.createElement("p");
3622
- hint.className = "subtitle";
3623
- hint.textContent = t("lastUsed", locale, { method: label });
3624
- card.appendChild(hint);
3625
- }
3626
- } catch {
3627
- }
3628
- }
3629
3695
  const requestedPasskey = this.options.methods.includes("passkey");
3630
3696
  if (requestedPasskey && !methods.includes("passkey")) {
3631
3697
  const warn = document.createElement("div");
@@ -3895,11 +3961,12 @@ var LoginPage = class {
3895
3961
  }
3896
3962
  });
3897
3963
  }
3898
- renderBackButton(parent, locale) {
3964
+ renderBackButton(parent, locale, onBack = () => this.switchView("methods")) {
3899
3965
  const btn = document.createElement("button");
3966
+ btn.type = "button";
3900
3967
  btn.className = "back-btn";
3901
3968
  btn.innerHTML = `${ICON_BACK_ARROW} ${escapeHtml(t("back", locale))}`;
3902
- btn.addEventListener("click", () => this.switchView("methods"));
3969
+ btn.addEventListener("click", onBack);
3903
3970
  parent.appendChild(btn);
3904
3971
  }
3905
3972
  /**
@@ -4107,11 +4174,8 @@ var LoginPage = class {
4107
4174
  createBtn.id = "passkey-btn";
4108
4175
  createBtn.innerHTML = `${ICON_PASSKEY} ${escapeHtml(t("passkeyCreate", locale))}`;
4109
4176
  section.appendChild(createBtn);
4110
- const backLink = document.createElement("button");
4111
- backLink.type = "button";
4112
- backLink.className = "passkey-alt";
4113
- backLink.textContent = t("passkeyBackToSignIn", locale);
4114
- section.appendChild(backLink);
4177
+ parent.querySelector(".back-btn")?.remove();
4178
+ this.renderBackButton(parent, locale, () => renderSignInState());
4115
4179
  const doCreate = async () => {
4116
4180
  if (createBtn.disabled)
4117
4181
  return;
@@ -4141,7 +4205,6 @@ var LoginPage = class {
4141
4205
  if (e.key === "Enter")
4142
4206
  doCreate();
4143
4207
  });
4144
- backLink.addEventListener("click", () => renderSignInState());
4145
4208
  createBtn.focus();
4146
4209
  };
4147
4210
  const doSignIn = async () => {
@@ -4176,6 +4239,7 @@ var LoginPage = class {
4176
4239
  };
4177
4240
  const renderSignInState = () => {
4178
4241
  this.cardEl?.classList.remove("passkey-creating");
4242
+ parent.querySelector(".back-btn")?.remove();
4179
4243
  this.abort?.abort();
4180
4244
  this.generation++;
4181
4245
  const heading = this.cardEl?.querySelector("h1");
@@ -4187,6 +4251,7 @@ var LoginPage = class {
4187
4251
  signInBtn.className = this.cardEl?.classList.contains("social-first") ? "btn btn-secondary" : "btn";
4188
4252
  signInBtn.id = "passkey-btn";
4189
4253
  signInBtn.innerHTML = `${ICON_PASSKEY} ${escapeHtml(t("passkeySignIn", locale))}`;
4254
+ this.markLastUsed(signInBtn, "passkey", locale);
4190
4255
  section.appendChild(signInBtn);
4191
4256
  signInBtn.addEventListener("click", () => doSignIn());
4192
4257
  const createLink = document.createElement("button");
@@ -4208,159 +4273,240 @@ var LoginPage = class {
4208
4273
  startOAuthPopup(providerId, btn) {
4209
4274
  this.oauthCleanup?.();
4210
4275
  const epoch = this.generation;
4276
+ const locale = this.options.locale;
4211
4277
  const servicePrefix = this.config.servicePrefix ?? "/.well-known/service";
4212
4278
  const oauthBase = this.options.masterOAuthOrigin ? `${this.options.masterOAuthOrigin}${servicePrefix}` : servicePrefix;
4213
4279
  const oauthOrigin = new URL(oauthBase, window.location.href).origin;
4214
- const loginUrl = `${oauthBase}/api/oauth/${encodeURIComponent(providerId)}/login?returnUrl=${encodeURIComponent(window.location.href)}`;
4215
- const width = 500;
4216
- const height = 620;
4217
- const left = window.screenX + (window.innerWidth - width) / 2;
4218
- const top = window.screenY + (window.innerHeight - height) / 2;
4219
- const popup = window.open(loginUrl, "oauth-login:popup", `left=${left},top=${top},width=${width},height=${height},resizable,scrollbars=yes,status=1,popup`);
4220
- if (!popup) {
4221
- this.options.onBeforeRedirect?.();
4222
- window.location.href = loginUrl;
4223
- return;
4280
+ let returnUrl = window.location.href;
4281
+ if (this.options.mode !== "dialog" && window.location.pathname === `${servicePrefix}/login`) {
4282
+ const requested = new URLSearchParams(window.location.search).get("return_to");
4283
+ try {
4284
+ const target = new URL(requested || "/", window.location.origin);
4285
+ returnUrl = target.origin === window.location.origin && !/^\/[/\\]/.test(target.pathname) ? target.href : `${window.location.origin}/`;
4286
+ } catch {
4287
+ returnUrl = `${window.location.origin}/`;
4288
+ }
4224
4289
  }
4290
+ const loginUrl = `${oauthBase}/api/oauth/${encodeURIComponent(providerId)}/login?returnUrl=${encodeURIComponent(returnUrl)}`;
4291
+ const provider = this.options.oauthProviders?.find((p) => p.id === providerId)?.name ?? providerId;
4225
4292
  const originalHTML = btn.innerHTML;
4226
- btn.textContent = t("oauthWaiting", this.options.locale, { provider: this.options.oauthProviders?.find((p) => p.id === providerId)?.name ?? providerId });
4227
- btn.disabled = true;
4228
- btn.classList.add("oauth-btn-loading");
4293
+ let popup = null;
4294
+ let settled = false;
4295
+ let exchanging = false;
4296
+ let probing = false;
4297
+ let pollTimer;
4298
+ let sessionTimer;
4299
+ let abandonTimer;
4300
+ const current = () => !settled && this.mounted && epoch === this.generation;
4301
+ const locked = [];
4302
+ const lockMethods = () => {
4303
+ for (const button of this.cardEl?.querySelectorAll(".method-btn, .passkey-create-entry, #passkey-btn, .locale-trigger, .locale-btn") ?? []) {
4304
+ locked.push({ button, disabled: button.disabled });
4305
+ button.disabled = true;
4306
+ }
4307
+ };
4229
4308
  const recovery = document.createElement("div");
4230
4309
  recovery.className = "oauth-recovery";
4231
4310
  recovery.setAttribute("role", "status");
4232
4311
  const hint = document.createElement("p");
4233
- hint.textContent = t("oauthIncomplete", this.options.locale);
4312
+ const actions = document.createElement("div");
4313
+ actions.className = "oauth-recovery-actions";
4314
+ const reopen = document.createElement("button");
4315
+ reopen.type = "button";
4316
+ reopen.className = "oauth-retry";
4317
+ reopen.textContent = t("oauthReopen", locale);
4318
+ reopen.hidden = true;
4319
+ reopen.onclick = () => {
4320
+ this.select("oauth");
4321
+ this.activeProvider = providerId;
4322
+ this.startOAuthPopup(providerId, btn);
4323
+ };
4234
4324
  const fullPage = document.createElement("a");
4235
4325
  fullPage.href = loginUrl;
4236
- fullPage.textContent = t("oauthFullPage", this.options.locale);
4237
- fullPage.addEventListener("click", () => {
4326
+ fullPage.textContent = t("oauthFullPage", locale);
4327
+ fullPage.addEventListener("click", (event) => {
4328
+ const reconcile = recovery.dataset.state === "oauthUnconfirmed";
4329
+ if (exchanging && !settled) {
4330
+ event.preventDefault();
4331
+ return;
4332
+ }
4333
+ if (reconcile)
4334
+ event.preventDefault();
4238
4335
  this.options.onBeforeRedirect?.();
4239
4336
  this.oauthCleanup?.();
4337
+ if (reconcile)
4338
+ window.location.reload();
4240
4339
  });
4241
- recovery.append(hint, fullPage);
4242
- recovery.hidden = true;
4340
+ const cancel = document.createElement("button");
4341
+ cancel.type = "button";
4342
+ cancel.className = "oauth-cancel";
4343
+ cancel.textContent = t("cancel", locale);
4344
+ actions.append(reopen, fullPage, cancel);
4345
+ recovery.append(hint, actions);
4243
4346
  btn.parentElement?.after(recovery);
4244
- const restore = () => {
4245
- recovery.hidden = false;
4246
- this.emit("incomplete");
4347
+ const restoreButton = () => {
4247
4348
  btn.innerHTML = originalHTML;
4248
4349
  btn.disabled = false;
4249
4350
  btn.classList.remove("oauth-btn-loading");
4250
4351
  };
4251
- let settled = false;
4252
- let sessionPollTimer;
4352
+ const closePopup = () => {
4353
+ try {
4354
+ popup?.close();
4355
+ } catch {
4356
+ }
4357
+ };
4358
+ const cleanup = () => {
4359
+ settled = true;
4360
+ clearInterval(pollTimer);
4361
+ clearInterval(sessionTimer);
4362
+ clearTimeout(abandonTimer);
4363
+ window.removeEventListener("message", onMessage);
4364
+ for (const item of locked.splice(0))
4365
+ item.button.disabled = item.disabled;
4366
+ restoreButton();
4367
+ };
4368
+ const showRecovery = (key) => {
4369
+ hint.textContent = t(key, locale, { provider });
4370
+ recovery.dataset.state = key;
4371
+ actions.hidden = false;
4372
+ reopen.hidden = false;
4373
+ cancel.hidden = true;
4374
+ restoreButton();
4375
+ };
4376
+ const fail = (key) => {
4377
+ if (!current())
4378
+ return;
4379
+ cleanup();
4380
+ closePopup();
4381
+ showRecovery(key);
4382
+ this.emit("incomplete");
4383
+ };
4253
4384
  const succeed = () => {
4254
- if (settled || !this.mounted || epoch !== this.generation)
4385
+ if (!current())
4255
4386
  return;
4256
- settled = true;
4257
4387
  cleanup();
4258
- btn.textContent = t("success", this.options.locale);
4388
+ closePopup();
4389
+ recovery.remove();
4390
+ btn.textContent = t("success", locale);
4391
+ btn.disabled = true;
4259
4392
  this.handleSuccess();
4260
4393
  };
4261
- const onMessage = async (event) => {
4262
- if (event.origin !== oauthOrigin || event.source !== popup)
4394
+ cancel.onclick = () => {
4395
+ if (exchanging)
4263
4396
  return;
4264
- if (!event.data || event.data.type !== "authorization_response")
4397
+ fail("oauthCancelled");
4398
+ btn.focus();
4399
+ };
4400
+ const onMessage = async (event) => {
4401
+ if (event.origin !== oauthOrigin || event.source !== popup || !popup)
4265
4402
  return;
4266
- if (settled || !this.mounted || epoch !== this.generation)
4403
+ if (!event.data || event.data.type !== "authorization_response" || !current() || exchanging)
4267
4404
  return;
4268
- if (pollTimer !== void 0) {
4269
- clearInterval(pollTimer);
4270
- pollTimer = void 0;
4271
- }
4272
- try {
4273
- popup.close();
4274
- } catch {
4275
- }
4405
+ clearInterval(pollTimer);
4406
+ clearInterval(sessionTimer);
4276
4407
  if (event.data.error) {
4277
- restore();
4278
- cleanup();
4408
+ fail(event.data.error === "access_denied" ? "oauthDenied" : event.data.error === "cancelled" ? "oauthCancelled" : "oauthFailed");
4279
4409
  return;
4280
4410
  }
4281
4411
  const { code, state } = event.data.response ?? event.data;
4282
- if (!code) {
4283
- restore();
4284
- cleanup();
4412
+ if (typeof code !== "string" || !code || typeof state !== "string" || !state) {
4413
+ fail("oauthFailed");
4285
4414
  return;
4286
4415
  }
4287
- btn.textContent = t("verifying", this.options.locale);
4416
+ exchanging = true;
4417
+ lockMethods();
4418
+ actions.hidden = true;
4419
+ closePopup();
4420
+ hint.textContent = t("verifying", locale);
4421
+ btn.textContent = t("verifying", locale);
4288
4422
  btn.disabled = true;
4289
4423
  try {
4290
4424
  const oauthBody = { provider: providerId, code, state };
4291
4425
  if (this.options.invitationId)
4292
4426
  oauthBody.invitationId = this.options.invitationId;
4293
- await this.http.post(`${servicePrefix}/api/oauth/login`, oauthBody);
4427
+ await this.http.post(`${servicePrefix}/api/oauth/login`, oauthBody, { signal: this.abort?.signal });
4294
4428
  succeed();
4295
- } catch (oauthErr) {
4296
- if (settled || !this.mounted || epoch !== this.generation)
4429
+ } catch (err) {
4430
+ if (!current())
4297
4431
  return;
4298
- const errMsg = oauthErr instanceof Error ? oauthErr.message : "";
4299
- const errCode = oauthErr instanceof Error && "code" in oauthErr ? oauthErr.code : "";
4300
- if (errCode === "FORBIDDEN" || errMsg === "registration_closed" || errCode === "registration_closed") {
4301
- settled = true;
4432
+ const code2 = err instanceof Error && "code" in err ? String(err.code) : "";
4433
+ const message = err instanceof Error ? err.message : "";
4434
+ if (code2 === "FORBIDDEN" || code2 === "registration_closed" || message === "registration_closed") {
4302
4435
  cleanup();
4303
- this.showFullError(friendlyError(oauthErr, this.options.locale));
4304
- } else {
4305
- restore();
4306
- }
4436
+ recovery.remove();
4437
+ this.showFullError(friendlyError(err, locale));
4438
+ } else
4439
+ fail("oauthFailed");
4307
4440
  }
4308
4441
  };
4309
- let pollTimer = setInterval(() => {
4442
+ this.oauthCleanup = () => {
4443
+ cleanup();
4444
+ recovery.remove();
4445
+ closePopup();
4446
+ };
4447
+ window.addEventListener("message", onMessage);
4448
+ try {
4449
+ popup = window.open(loginUrl, "oauth-login:popup", `left=${window.screenX + (window.innerWidth - 500) / 2},top=${window.screenY + (window.innerHeight - 620) / 2},width=500,height=620,resizable,scrollbars=yes,status=1,popup`);
4450
+ } catch {
4451
+ }
4452
+ if (!popup) {
4453
+ fail("oauthBlocked");
4454
+ return;
4455
+ }
4456
+ hint.textContent = t("oauthWaiting", locale, { provider });
4457
+ btn.textContent = t("oauthWaiting", locale, { provider });
4458
+ btn.disabled = true;
4459
+ btn.classList.add("oauth-btn-loading");
4460
+ const checkSession = async () => {
4461
+ if (!current() || exchanging || probing)
4462
+ return;
4463
+ probing = true;
4464
+ try {
4465
+ const session = await this.http.get(`${servicePrefix}/api/did/session`);
4466
+ if (current() && !exchanging && session?.authenticated === true)
4467
+ succeed();
4468
+ } catch {
4469
+ } finally {
4470
+ probing = false;
4471
+ }
4472
+ };
4473
+ pollTimer = setInterval(() => {
4310
4474
  let closed = false;
4311
4475
  try {
4312
- closed = popup.closed;
4476
+ closed = !!popup?.closed;
4313
4477
  } catch {
4314
4478
  return;
4315
4479
  }
4316
- if (!closed)
4480
+ if (!closed || !current())
4317
4481
  return;
4318
- if (pollTimer !== void 0) {
4319
- clearInterval(pollTimer);
4320
- pollTimer = void 0;
4321
- }
4322
- restore();
4323
- const checkSession = () => {
4324
- void this.http.get(`${servicePrefix}/api/did/session`).then((session) => {
4325
- if (session?.authenticated)
4326
- succeed();
4327
- }).catch(() => {
4328
- });
4329
- };
4330
- checkSession();
4331
- sessionPollTimer = setInterval(checkSession, 500);
4482
+ clearInterval(pollTimer);
4483
+ showRecovery("oauthIncomplete");
4484
+ cancel.hidden = false;
4485
+ void checkSession();
4486
+ sessionTimer = setInterval(() => {
4487
+ void checkSession();
4488
+ }, 2e3);
4332
4489
  }, 500);
4333
- const abandonTimer = setTimeout(() => {
4334
- if (settled)
4490
+ abandonTimer = setTimeout(() => {
4491
+ if (!current())
4492
+ return;
4493
+ if (!exchanging) {
4494
+ fail("oauthTimedOut");
4335
4495
  return;
4336
- cleanup();
4337
- restore();
4338
- }, 5 * 60 * 1e3);
4339
- const cleanup = () => {
4340
- settled = true;
4341
- btn.innerHTML = originalHTML;
4342
- btn.disabled = false;
4343
- btn.classList.remove("oauth-btn-loading");
4344
- if (pollTimer !== void 0) {
4345
- clearInterval(pollTimer);
4346
- pollTimer = void 0;
4347
- }
4348
- if (sessionPollTimer !== void 0) {
4349
- clearInterval(sessionPollTimer);
4350
- sessionPollTimer = void 0;
4351
4496
  }
4352
- clearTimeout(abandonTimer);
4353
- window.removeEventListener("message", onMessage);
4354
- };
4355
- this.oauthCleanup = () => {
4497
+ this.abort?.abort();
4356
4498
  cleanup();
4357
- recovery.remove();
4358
- try {
4359
- popup.close();
4360
- } catch {
4361
- }
4362
- };
4363
- window.addEventListener("message", onMessage);
4499
+ closePopup();
4500
+ lockMethods();
4501
+ hint.textContent = t("oauthUnconfirmed", locale);
4502
+ recovery.dataset.state = "oauthUnconfirmed";
4503
+ actions.hidden = false;
4504
+ reopen.hidden = true;
4505
+ cancel.hidden = true;
4506
+ fullPage.href = window.location.href;
4507
+ fullPage.textContent = t("oauthCheckSession", locale);
4508
+ this.emit("incomplete");
4509
+ }, 5 * 60 * 1e3);
4364
4510
  }
4365
4511
  // (removed dead code: renderEmailButton — email now renders through renderSecondaryGrid)
4366
4512
  /**
@@ -4383,6 +4529,7 @@ var LoginPage = class {
4383
4529
  btn.dataset.provider = provider.id;
4384
4530
  const icon = getOAuthIcon(provider.id);
4385
4531
  btn.innerHTML = `${icon} ${escapeHtml(t("continueWith", locale, { provider: provider.name }))}`;
4532
+ this.markLastUsed(btn, `oauth:${provider.id}`, locale);
4386
4533
  grid.appendChild(btn);
4387
4534
  btn.addEventListener("click", () => {
4388
4535
  this.select("oauth");
@@ -4396,6 +4543,7 @@ var LoginPage = class {
4396
4543
  btn.className = "method-btn";
4397
4544
  btn.id = "email-btn";
4398
4545
  btn.innerHTML = `${ICON_EMAIL} ${escapeHtml(t("emailButton", locale))}`;
4546
+ this.markLastUsed(btn, "email", locale);
4399
4547
  grid.appendChild(btn);
4400
4548
  btn.addEventListener("click", () => {
4401
4549
  this.select("email");
@@ -4408,6 +4556,7 @@ var LoginPage = class {
4408
4556
  btn.id = "did-wallet-btn";
4409
4557
  const walletLabel = grid.children.length > 0 ? t("didWalletButtonShort", locale) : t("didWalletButton", locale);
4410
4558
  btn.innerHTML = `${ICON_DID_WALLET} ${escapeHtml(walletLabel)}`;
4559
+ this.markLastUsed(btn, "did-connect", locale);
4411
4560
  grid.appendChild(btn);
4412
4561
  btn.addEventListener("click", () => {
4413
4562
  this.select("did-connect");
@@ -4710,6 +4859,9 @@ var LoginDialog = class {
4710
4859
  mount() {
4711
4860
  if (this.dialog)
4712
4861
  return;
4862
+ const hostLocale = document.documentElement.lang;
4863
+ if (hostLocale)
4864
+ this.options.locale = hostLocale;
4713
4865
  let focused = document.activeElement;
4714
4866
  while (focused?.shadowRoot?.activeElement)
4715
4867
  focused = focused.shadowRoot.activeElement;