@keycloakify/angular 0.0.4 → 0.0.6

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.
Files changed (177) hide show
  1. package/account/DefaultPage/DefaultPage.d.ts +2 -30
  2. package/esm2022/account/containers/template/template.component.mjs +6 -6
  3. package/esm2022/account/directives/kc-class/kc-class.directive.mjs +3 -3
  4. package/esm2022/account/pages/account/account.component.mjs +3 -3
  5. package/esm2022/account/pages/applications/applications.component.mjs +3 -3
  6. package/esm2022/account/pages/federatedIdentity/federatedIdentity.component.mjs +3 -3
  7. package/esm2022/account/pages/log/log.component.mjs +3 -3
  8. package/esm2022/account/pages/password/password.component.mjs +3 -3
  9. package/esm2022/account/pages/sessions/sessions.component.mjs +3 -3
  10. package/esm2022/account/pages/totp/totp.component.mjs +3 -3
  11. package/esm2022/account/services/account-resource-injector/account-resource-injector.service.mjs +3 -3
  12. package/esm2022/account/services/i18n/i18n.service.mjs +3 -3
  13. package/esm2022/lib/directives/attributes/attributes.directive.mjs +3 -3
  14. package/esm2022/lib/pipes/input-type/input-type.pipe.mjs +3 -3
  15. package/esm2022/lib/pipes/is-array-with-empty-object/is-array-with-empty-object.pipe.mjs +3 -3
  16. package/esm2022/lib/pipes/kc-sanitize/kc-sanitize.pipe.mjs +3 -3
  17. package/esm2022/lib/pipes/to-array/to-array.pipe.mjs +3 -3
  18. package/esm2022/lib/pipes/to-number/to-number.pipe.mjs +3 -3
  19. package/esm2022/lib/services/resource-injector/resource-injector.service.mjs +3 -3
  20. package/esm2022/login/components/add-remove-buttons-multi-valued-attribute/add-remove-buttons-multi-valued-attribute.component.mjs +7 -53
  21. package/esm2022/login/components/field-errors/field-errors.component.mjs +3 -3
  22. package/esm2022/login/components/group-label/group-label.component.mjs +3 -3
  23. package/esm2022/login/components/input-field-by-type/input-field-by-type.component.mjs +3 -3
  24. package/esm2022/login/components/input-tag/input-tag.component.mjs +3 -3
  25. package/esm2022/login/components/input-tag-selects/input-tag-selects.component.mjs +3 -3
  26. package/esm2022/login/components/logout-other-sessions/logout-other-sessions.component.mjs +3 -3
  27. package/esm2022/login/components/password-wrapper/password-wrapper.component.mjs +3 -3
  28. package/esm2022/login/components/select-tag/select-tag.component.mjs +3 -3
  29. package/esm2022/login/components/textarea-tag/textarea-tag.component.mjs +3 -3
  30. package/esm2022/login/components/user-profile-form-fields/user-profile-form-fields.component.mjs +15 -22
  31. package/esm2022/login/containers/template/template.component.mjs +6 -6
  32. package/esm2022/login/directives/kc-class/kc-class.directive.mjs +3 -3
  33. package/esm2022/login/pages/code/code.component.mjs +3 -3
  34. package/esm2022/login/pages/delete-account-confirm/delete-account-confirm.component.mjs +3 -3
  35. package/esm2022/login/pages/delete-credential/delete-credential.component.mjs +3 -3
  36. package/esm2022/login/pages/error/error.component.mjs +3 -3
  37. package/esm2022/login/pages/frontchannel-logout/frontchannel-logout.component.mjs +3 -3
  38. package/esm2022/login/pages/idp-review-user-profile/idp-review-user-profile.component.mjs +13 -15
  39. package/esm2022/login/pages/info/info.component.mjs +3 -3
  40. package/esm2022/login/pages/login/login.component.mjs +3 -3
  41. package/esm2022/login/pages/login-config-totp/login-config-totp.component.mjs +3 -3
  42. package/esm2022/login/pages/login-idp-link-confirm/login-idp-link-confirm.component.mjs +3 -3
  43. package/esm2022/login/pages/login-idp-link-confirm-override/login-idp-link-confirm-override.component.mjs +3 -3
  44. package/esm2022/login/pages/login-idp-link-email/login-idp-link-email.component.mjs +3 -3
  45. package/esm2022/login/pages/login-oauth-grant/login-oauth-grant.component.mjs +3 -3
  46. package/esm2022/login/pages/login-oauth2-device-verify-user-code/login-oauth2-device-verify-user-code.component.mjs +3 -3
  47. package/esm2022/login/pages/login-otp/login-otp.component.mjs +3 -3
  48. package/esm2022/login/pages/login-page-expired/login-page-expired.component.mjs +3 -3
  49. package/esm2022/login/pages/login-passkeys-conditional-authenticate/login-passkeys-conditional-authenticate.component.mjs +3 -3
  50. package/esm2022/login/pages/login-password/login-password.component.mjs +3 -3
  51. package/esm2022/login/pages/login-recovery-authn-code-config/login-recovery-authn-code-config.component.mjs +125 -6
  52. package/esm2022/login/pages/login-recovery-authn-code-input/login-recovery-authn-code-input.component.mjs +3 -3
  53. package/esm2022/login/pages/login-reset-otp/login-reset-otp.component.mjs +3 -3
  54. package/esm2022/login/pages/login-reset-password/login-reset-password.component.mjs +3 -3
  55. package/esm2022/login/pages/login-update-password/login-update-password.component.mjs +3 -3
  56. package/esm2022/login/pages/login-update-profile/login-update-profile.component.mjs +13 -15
  57. package/esm2022/login/pages/login-username/login-username.component.mjs +3 -3
  58. package/esm2022/login/pages/login-verify-email/login-verify-email.component.mjs +3 -3
  59. package/esm2022/login/pages/login-x509-info/login-x509-info.component.mjs +3 -3
  60. package/esm2022/login/pages/logout-confirm/logout-confirm.component.mjs +3 -3
  61. package/esm2022/login/pages/register/register.component.mjs +12 -14
  62. package/esm2022/login/pages/saml-post-form/saml-post-form.component.mjs +3 -3
  63. package/esm2022/login/pages/select-authenticator/select-authenticator.component.mjs +3 -3
  64. package/esm2022/login/pages/terms/terms.component.mjs +3 -3
  65. package/esm2022/login/pages/update-email/update-email.component.mjs +13 -15
  66. package/esm2022/login/pages/webauthn-authenticate/webauthn-authenticate.component.mjs +3 -3
  67. package/esm2022/login/pages/webauthn-error/webauthn-error.component.mjs +3 -3
  68. package/esm2022/login/pages/webauthn-register/webauthn-register.component.mjs +3 -3
  69. package/esm2022/login/services/i18n/i18n.service.mjs +3 -3
  70. package/esm2022/login/services/login-resource-injector/login-resource-injector.service.mjs +3 -3
  71. package/esm2022/login/services/user-profile-form/user-profile-form.service.mjs +56 -1004
  72. package/fesm2022/keycloakify-angular-account-containers-template.mjs +6 -6
  73. package/fesm2022/keycloakify-angular-account-directives-kc-class.mjs +3 -3
  74. package/fesm2022/keycloakify-angular-account-pages-account.mjs +3 -3
  75. package/fesm2022/keycloakify-angular-account-pages-applications.mjs +3 -3
  76. package/fesm2022/keycloakify-angular-account-pages-federatedIdentity.mjs +3 -3
  77. package/fesm2022/keycloakify-angular-account-pages-log.mjs +3 -3
  78. package/fesm2022/keycloakify-angular-account-pages-password.mjs +3 -3
  79. package/fesm2022/keycloakify-angular-account-pages-sessions.mjs +3 -3
  80. package/fesm2022/keycloakify-angular-account-pages-totp.mjs +3 -3
  81. package/fesm2022/keycloakify-angular-account-services-account-resource-injector.mjs +3 -3
  82. package/fesm2022/keycloakify-angular-account-services-i18n.mjs +3 -3
  83. package/fesm2022/keycloakify-angular-lib-directives-attributes.mjs +3 -3
  84. package/fesm2022/keycloakify-angular-lib-pipes-input-type.mjs +3 -3
  85. package/fesm2022/keycloakify-angular-lib-pipes-is-array-with-empty-object.mjs +3 -3
  86. package/fesm2022/keycloakify-angular-lib-pipes-kc-sanitize.mjs +3 -3
  87. package/fesm2022/keycloakify-angular-lib-pipes-to-array.mjs +3 -3
  88. package/fesm2022/keycloakify-angular-lib-pipes-to-number.mjs +3 -3
  89. package/fesm2022/keycloakify-angular-lib-services-resource-injector.mjs +3 -3
  90. package/fesm2022/keycloakify-angular-login-components-add-remove-buttons-multi-valued-attribute.mjs +6 -52
  91. package/fesm2022/keycloakify-angular-login-components-add-remove-buttons-multi-valued-attribute.mjs.map +1 -1
  92. package/fesm2022/keycloakify-angular-login-components-field-errors.mjs +3 -3
  93. package/fesm2022/keycloakify-angular-login-components-group-label.mjs +3 -3
  94. package/fesm2022/keycloakify-angular-login-components-input-field-by-type.mjs +3 -3
  95. package/fesm2022/keycloakify-angular-login-components-input-tag-selects.mjs +3 -3
  96. package/fesm2022/keycloakify-angular-login-components-input-tag.mjs +3 -3
  97. package/fesm2022/keycloakify-angular-login-components-logout-other-sessions.mjs +3 -3
  98. package/fesm2022/keycloakify-angular-login-components-password-wrapper.mjs +3 -3
  99. package/fesm2022/keycloakify-angular-login-components-select-tag.mjs +3 -3
  100. package/fesm2022/keycloakify-angular-login-components-textarea-tag.mjs +3 -3
  101. package/fesm2022/keycloakify-angular-login-components-user-profile-form-fields.mjs +14 -21
  102. package/fesm2022/keycloakify-angular-login-components-user-profile-form-fields.mjs.map +1 -1
  103. package/fesm2022/keycloakify-angular-login-containers-template.mjs +6 -6
  104. package/fesm2022/keycloakify-angular-login-directives-kc-class.mjs +3 -3
  105. package/fesm2022/keycloakify-angular-login-pages-code.mjs +3 -3
  106. package/fesm2022/keycloakify-angular-login-pages-delete-account-confirm.mjs +3 -3
  107. package/fesm2022/keycloakify-angular-login-pages-delete-credential.mjs +3 -3
  108. package/fesm2022/keycloakify-angular-login-pages-error.mjs +3 -3
  109. package/fesm2022/keycloakify-angular-login-pages-frontchannel-logout.mjs +3 -3
  110. package/fesm2022/keycloakify-angular-login-pages-idp-review-user-profile.mjs +12 -14
  111. package/fesm2022/keycloakify-angular-login-pages-idp-review-user-profile.mjs.map +1 -1
  112. package/fesm2022/keycloakify-angular-login-pages-info.mjs +3 -3
  113. package/fesm2022/keycloakify-angular-login-pages-login-config-totp.mjs +3 -3
  114. package/fesm2022/keycloakify-angular-login-pages-login-idp-link-confirm-override.mjs +3 -3
  115. package/fesm2022/keycloakify-angular-login-pages-login-idp-link-confirm.mjs +3 -3
  116. package/fesm2022/keycloakify-angular-login-pages-login-idp-link-email.mjs +3 -3
  117. package/fesm2022/keycloakify-angular-login-pages-login-oauth-grant.mjs +3 -3
  118. package/fesm2022/keycloakify-angular-login-pages-login-oauth2-device-verify-user-code.mjs +3 -3
  119. package/fesm2022/keycloakify-angular-login-pages-login-otp.mjs +3 -3
  120. package/fesm2022/keycloakify-angular-login-pages-login-page-expired.mjs +3 -3
  121. package/fesm2022/keycloakify-angular-login-pages-login-passkeys-conditional-authenticate.mjs +3 -3
  122. package/fesm2022/keycloakify-angular-login-pages-login-password.mjs +3 -3
  123. package/fesm2022/keycloakify-angular-login-pages-login-recovery-authn-code-config.mjs +124 -5
  124. package/fesm2022/keycloakify-angular-login-pages-login-recovery-authn-code-config.mjs.map +1 -1
  125. package/fesm2022/keycloakify-angular-login-pages-login-recovery-authn-code-input.mjs +3 -3
  126. package/fesm2022/keycloakify-angular-login-pages-login-reset-otp.mjs +3 -3
  127. package/fesm2022/keycloakify-angular-login-pages-login-reset-password.mjs +3 -3
  128. package/fesm2022/keycloakify-angular-login-pages-login-update-password.mjs +3 -3
  129. package/fesm2022/keycloakify-angular-login-pages-login-update-profile.mjs +12 -14
  130. package/fesm2022/keycloakify-angular-login-pages-login-update-profile.mjs.map +1 -1
  131. package/fesm2022/keycloakify-angular-login-pages-login-username.mjs +3 -3
  132. package/fesm2022/keycloakify-angular-login-pages-login-verify-email.mjs +3 -3
  133. package/fesm2022/keycloakify-angular-login-pages-login-x509-info.mjs +3 -3
  134. package/fesm2022/keycloakify-angular-login-pages-login.mjs +3 -3
  135. package/fesm2022/keycloakify-angular-login-pages-logout-confirm.mjs +3 -3
  136. package/fesm2022/keycloakify-angular-login-pages-register.mjs +11 -13
  137. package/fesm2022/keycloakify-angular-login-pages-register.mjs.map +1 -1
  138. package/fesm2022/keycloakify-angular-login-pages-saml-post-form.mjs +3 -3
  139. package/fesm2022/keycloakify-angular-login-pages-select-authenticator.mjs +3 -3
  140. package/fesm2022/keycloakify-angular-login-pages-terms.mjs +3 -3
  141. package/fesm2022/keycloakify-angular-login-pages-update-email.mjs +12 -14
  142. package/fesm2022/keycloakify-angular-login-pages-update-email.mjs.map +1 -1
  143. package/fesm2022/keycloakify-angular-login-pages-webauthn-authenticate.mjs +3 -3
  144. package/fesm2022/keycloakify-angular-login-pages-webauthn-error.mjs +3 -3
  145. package/fesm2022/keycloakify-angular-login-pages-webauthn-register.mjs +3 -3
  146. package/fesm2022/keycloakify-angular-login-services-i18n.mjs +3 -3
  147. package/fesm2022/keycloakify-angular-login-services-login-resource-injector.mjs +3 -3
  148. package/fesm2022/keycloakify-angular-login-services-user-profile-form.mjs +55 -1003
  149. package/fesm2022/keycloakify-angular-login-services-user-profile-form.mjs.map +1 -1
  150. package/login/components/add-remove-buttons-multi-valued-attribute/add-remove-buttons-multi-valued-attribute.component.d.ts +0 -1
  151. package/login/components/user-profile-form-fields/user-profile-form-fields.component.d.ts +3 -13
  152. package/login/pages/idp-review-user-profile/idp-review-user-profile.component.d.ts +1 -2
  153. package/login/pages/login-recovery-authn-code-config/login-recovery-authn-code-config.component.d.ts +3 -0
  154. package/login/pages/login-update-profile/login-update-profile.component.d.ts +1 -2
  155. package/login/pages/register/register.component.d.ts +1 -2
  156. package/login/pages/update-email/update-email.component.d.ts +1 -2
  157. package/login/services/user-profile-form/user-profile-form.service.d.ts +20 -41
  158. package/package.json +8 -14
  159. package/src/login/components/add-remove-buttons-multi-valued-attribute/add-remove-buttons-multi-valued-attribute.component.ts +3 -69
  160. package/src/login/components/user-profile-form-fields/user-profile-form-fields.component.html +71 -68
  161. package/src/login/components/user-profile-form-fields/user-profile-form-fields.component.ts +6 -21
  162. package/src/login/pages/idp-review-user-profile/idp-review-user-profile.component.ts +6 -13
  163. package/src/login/pages/login-recovery-authn-code-config/login-recovery-authn-code-config.component.ts +123 -0
  164. package/src/login/pages/login-update-profile/login-update-profile.component.ts +6 -12
  165. package/src/login/pages/register/register.component.ts +5 -10
  166. package/src/login/pages/update-email/update-email.component.ts +6 -12
  167. package/src/login/services/user-profile-form/user-profile-form.service.ts +103 -1433
  168. package/esm2022/login/services/submit/keycloakify-angular-login-services-submit.mjs +0 -5
  169. package/esm2022/login/services/submit/public-api.mjs +0 -2
  170. package/esm2022/login/services/submit/submit.service.mjs +0 -20
  171. package/fesm2022/keycloakify-angular-login-services-submit.mjs +0 -27
  172. package/fesm2022/keycloakify-angular-login-services-submit.mjs.map +0 -1
  173. package/login/services/submit/index.d.ts +0 -5
  174. package/login/services/submit/public-api.d.ts +0 -1
  175. package/login/services/submit/submit.service.d.ts +0 -9
  176. package/src/login/services/submit/index.ts +0 -1
  177. package/src/login/services/submit/submit.service.ts +0 -12
@@ -1,78 +1,81 @@
1
- @let formFieldStates = formState().formFieldStates;
1
+ @let formState = formState$ | async;
2
+ @if (formState) {
3
+ @let formFieldStates = formState.formFieldStates;
2
4
 
3
- @for (fieldState of formFieldStates; track fieldState.attribute) {
4
- <kc-group-label [attribute]="fieldState.attribute" />
5
+ @for (fieldState of formFieldStates; track fieldState.attribute) {
6
+ <kc-group-label [attribute]="fieldState.attribute" />
5
7
 
6
- @if (beforeField) {
7
- <ng-container
8
- [ngTemplateOutlet]="beforeField"
9
- [ngTemplateOutletContext]="{
10
- attribute: fieldState.attribute,
11
- valueOrValues: fieldState.valueOrValues,
12
- displayableErrors: fieldState.displayableErrors
13
- }"
14
- />
15
- }
16
-
17
- <div
18
- [kcClass]="'kcFormGroupClass'"
19
- [style.display]="fieldState.attribute.name === 'password-confirm' && !doMakeUserConfirmPassword ? 'none' : 'block'"
20
- >
21
- <div [kcClass]="'kcLabelWrapperClass'">
22
- <label
23
- [for]="fieldState.attribute.name"
24
- [kcClass]="'kcLabelClass'"
25
- >
26
- {{ i18n.advancedMsgStr(fieldState.attribute.displayName ?? '') }}
27
- @if (fieldState.attribute.required) {
28
- *
29
- }
30
- </label>
31
- </div>
8
+ @if (beforeField) {
9
+ <ng-container
10
+ [ngTemplateOutlet]="beforeField"
11
+ [ngTemplateOutletContext]="{
12
+ attribute: fieldState.attribute,
13
+ valueOrValues: fieldState.valueOrValues,
14
+ displayableErrors: fieldState.displayableErrors
15
+ }"
16
+ />
17
+ }
32
18
 
33
- <div [kcClass]="'kcInputWrapperClass'">
34
- @if (fieldState.attribute.annotations.inputHelperTextBefore) {
35
- <div
36
- aria-live="polite"
37
- [kcClass]="'kcInputHelperTextBeforeClass'"
38
- [id]="'form-help-text-before-' + fieldState.attribute.name"
19
+ <div
20
+ [kcClass]="'kcFormGroupClass'"
21
+ [style.display]="fieldState.attribute.name === 'password-confirm' && !doMakeUserConfirmPassword ? 'none' : 'block'"
22
+ >
23
+ <div [kcClass]="'kcLabelWrapperClass'">
24
+ <label
25
+ [for]="fieldState.attribute.name"
26
+ [kcClass]="'kcLabelClass'"
39
27
  >
40
- {{ i18n.advancedMsgStr(fieldState.attribute.annotations.inputHelperTextBefore) }}
41
- </div>
42
- }
28
+ {{ i18n.advancedMsgStr(fieldState.attribute.displayName ?? '') }}
29
+ @if (fieldState.attribute.required) {
30
+ *
31
+ }
32
+ </label>
33
+ </div>
43
34
 
44
- <kc-input-field-by-type
45
- [attribute]="fieldState.attribute"
46
- [valueOrValues]="fieldState.valueOrValues"
47
- [displayableErrors]="fieldState.displayableErrors"
48
- (dispatchFormAction)="onDispatch($event)"
49
- />
35
+ <div [kcClass]="'kcInputWrapperClass'">
36
+ @if (fieldState.attribute.annotations.inputHelperTextBefore) {
37
+ <div
38
+ aria-live="polite"
39
+ [kcClass]="'kcInputHelperTextBeforeClass'"
40
+ [id]="'form-help-text-before-' + fieldState.attribute.name"
41
+ >
42
+ {{ i18n.advancedMsgStr(fieldState.attribute.annotations.inputHelperTextBefore) }}
43
+ </div>
44
+ }
50
45
 
51
- <kc-field-errors
52
- [attribute]="fieldState.attribute"
53
- [displayableErrors]="fieldState.displayableErrors"
54
- />
55
- @if (fieldState.attribute.annotations.inputHelperTextAfter) {
56
- <div
57
- aria-live="polite"
58
- [kcClass]="'kcInputHelperTextAfterClass'"
59
- [id]="'form-help-text-after-' + fieldState.attribute.name"
60
- >
61
- {{ i18n.advancedMsgStr(fieldState.attribute.annotations.inputHelperTextAfter) }}
62
- </div>
63
- }
46
+ <kc-input-field-by-type
47
+ [attribute]="fieldState.attribute"
48
+ [valueOrValues]="fieldState.valueOrValues"
49
+ [displayableErrors]="fieldState.displayableErrors"
50
+ (dispatchFormAction)="onDispatch($event)"
51
+ />
64
52
 
65
- @if (afterField) {
66
- <ng-container
67
- [ngTemplateOutlet]="afterField"
68
- [ngTemplateOutletContext]="{
69
- attribute: fieldState.attribute,
70
- valueOrValues: fieldState.valueOrValues,
71
- displayableErrors: fieldState.displayableErrors
72
- }"
53
+ <kc-field-errors
54
+ [attribute]="fieldState.attribute"
55
+ [displayableErrors]="fieldState.displayableErrors"
73
56
  />
74
- }
75
- <!-- NOTE: Downloading of html5DataAnnotations scripts is done in the useUserProfileForm hook -->
57
+ @if (fieldState.attribute.annotations.inputHelperTextAfter) {
58
+ <div
59
+ aria-live="polite"
60
+ [kcClass]="'kcInputHelperTextAfterClass'"
61
+ [id]="'form-help-text-after-' + fieldState.attribute.name"
62
+ >
63
+ {{ i18n.advancedMsgStr(fieldState.attribute.annotations.inputHelperTextAfter) }}
64
+ </div>
65
+ }
66
+
67
+ @if (afterField) {
68
+ <ng-container
69
+ [ngTemplateOutlet]="afterField"
70
+ [ngTemplateOutletContext]="{
71
+ attribute: fieldState.attribute,
72
+ valueOrValues: fieldState.valueOrValues,
73
+ displayableErrors: fieldState.displayableErrors
74
+ }"
75
+ />
76
+ }
77
+ <!-- NOTE: Downloading of html5DataAnnotations scripts is done in the useUserProfileForm hook -->
78
+ </div>
76
79
  </div>
77
- </div>
80
+ }
78
81
  }
@@ -1,5 +1,5 @@
1
- import { NgTemplateOutlet } from '@angular/common';
2
- import { ChangeDetectionStrategy, Component, ContentChild, effect, forwardRef, inject, output, TemplateRef } from '@angular/core';
1
+ import { AsyncPipe, NgTemplateOutlet } from '@angular/common';
2
+ import { ChangeDetectionStrategy, Component, ContentChild, forwardRef, inject, TemplateRef } from '@angular/core';
3
3
  import { USE_DEFAULT_CSS } from '@keycloakify/angular/lib/tokens/use-default-css';
4
4
  import { ComponentReference } from '@keycloakify/angular/login/classes/component-reference';
5
5
  import { FieldErrorsComponent } from '@keycloakify/angular/login/components/field-errors';
@@ -8,7 +8,6 @@ import { InputFieldByTypeComponent } from '@keycloakify/angular/login/components
8
8
  import { KcClassDirective } from '@keycloakify/angular/login/directives/kc-class';
9
9
  import type { I18n } from '@keycloakify/angular/login/i18n';
10
10
  import type { KcContext } from '@keycloakify/angular/login/KcContext';
11
- import { SubmitService } from '@keycloakify/angular/login/services/submit';
12
11
  import { type FormAction, UserProfileFormService } from '@keycloakify/angular/login/services/user-profile-form';
13
12
  import { LOGIN_CLASSES } from '@keycloakify/angular/login/tokens/classes';
14
13
  import { LOGIN_I18N } from '@keycloakify/angular/login/tokens/i18n';
@@ -25,7 +24,7 @@ import type { ClassKey } from 'keycloakify/login/lib/kcClsx';
25
24
  }
26
25
  `
27
26
  ],
28
- imports: [KcClassDirective, FieldErrorsComponent, InputFieldByTypeComponent, GroupLabelComponent, NgTemplateOutlet],
27
+ imports: [KcClassDirective, FieldErrorsComponent, InputFieldByTypeComponent, GroupLabelComponent, NgTemplateOutlet, AsyncPipe],
29
28
  selector: 'kc-user-profile-form-fields',
30
29
  templateUrl: 'user-profile-form-fields.component.html',
31
30
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -40,31 +39,17 @@ import type { ClassKey } from 'keycloakify/login/lib/kcClsx';
40
39
  export class UserProfileFormFieldsComponent extends ComponentReference {
41
40
  i18n = inject<I18n>(LOGIN_I18N);
42
41
  kcContext = inject<KcContext>(KC_LOGIN_CONTEXT);
43
- userProfileFormService = inject(UserProfileFormService);
44
- #submitService = inject(SubmitService);
42
+ #userProfileFormService = inject(UserProfileFormService);
45
43
  doMakeUserConfirmPassword = inject(DO_MAKE_USER_CONFIRM_PASSWORD);
46
44
  override doUseDefaultCss = inject<boolean>(USE_DEFAULT_CSS);
47
45
  override classes = inject<Partial<Record<ClassKey, string>>>(LOGIN_CLASSES);
48
46
 
49
- onIsFormSubmittable = output<boolean>();
50
-
51
- formState = this.userProfileFormService.formState;
47
+ formState$ = this.#userProfileFormService.formState$;
52
48
 
53
49
  @ContentChild('beforField') beforeField: TemplateRef<unknown> | undefined;
54
50
  @ContentChild('afterField') afterField: TemplateRef<unknown> | undefined;
55
51
 
56
- constructor() {
57
- super();
58
- effect(
59
- () => {
60
- const isFormSubmittable = this.formState().isFormSubmittable;
61
- this.#submitService.setIsSubmittable(isFormSubmittable);
62
- },
63
- { allowSignalWrites: true }
64
- );
65
- }
66
-
67
52
  onDispatch(formAction: FormAction) {
68
- this.userProfileFormService.dispatchFormAction(formAction);
53
+ this.#userProfileFormService.dispatchFormAction(formAction);
69
54
  }
70
55
  }
@@ -1,17 +1,18 @@
1
1
  import { NgComponentOutlet } from '@angular/common';
2
- import { ChangeDetectionStrategy, Component, forwardRef, inject, input, signal, type TemplateRef, Type, viewChild } from '@angular/core';
3
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
2
+ import { ChangeDetectionStrategy, Component, forwardRef, inject, input, type TemplateRef, Type, viewChild } from '@angular/core';
3
+ import { toSignal } from '@angular/core/rxjs-interop';
4
4
  import { USE_DEFAULT_CSS } from '@keycloakify/angular/lib/tokens/use-default-css';
5
5
  import { ComponentReference } from '@keycloakify/angular/login/classes/component-reference';
6
6
  import type { UserProfileFormFieldsComponent } from '@keycloakify/angular/login/components/user-profile-form-fields';
7
7
  import { KcClassDirective } from '@keycloakify/angular/login/directives/kc-class';
8
8
  import type { I18n } from '@keycloakify/angular/login/i18n';
9
9
  import type { KcContext } from '@keycloakify/angular/login/KcContext';
10
- import { SubmitService } from '@keycloakify/angular/login/services/submit';
10
+ import { UserProfileFormService } from '@keycloakify/angular/login/services/user-profile-form';
11
11
  import { LOGIN_CLASSES } from '@keycloakify/angular/login/tokens/classes';
12
12
  import { LOGIN_I18N } from '@keycloakify/angular/login/tokens/i18n';
13
13
  import { KC_LOGIN_CONTEXT } from '@keycloakify/angular/login/tokens/kc-context';
14
14
  import type { ClassKey } from 'keycloakify/login/lib/kcClsx';
15
+ import { map } from 'rxjs';
15
16
 
16
17
  @Component({
17
18
  standalone: true,
@@ -27,10 +28,9 @@ import type { ClassKey } from 'keycloakify/login/lib/kcClsx';
27
28
  ]
28
29
  })
29
30
  export class IdpReviewUserProfileComponent extends ComponentReference {
30
- #submitService = inject(SubmitService);
31
31
  kcContext = inject<Extract<KcContext, { pageId: 'idp-review-user-profile.ftl' }>>(KC_LOGIN_CONTEXT);
32
32
  i18n = inject<I18n>(LOGIN_I18N);
33
-
33
+ #userProfileFormService = inject(UserProfileFormService);
34
34
  override doUseDefaultCss = inject<boolean>(USE_DEFAULT_CSS);
35
35
  override classes = inject<Partial<Record<ClassKey, string>>>(LOGIN_CLASSES);
36
36
 
@@ -46,14 +46,7 @@ export class IdpReviewUserProfileComponent extends ComponentReference {
46
46
  socialProvidersNode = viewChild<TemplateRef<HTMLElement>>('socialProvidersNode');
47
47
 
48
48
  userProfileFormFields = input<Type<UserProfileFormFieldsComponent>>();
49
- isFormSubmittable = signal(false);
50
-
51
- constructor() {
52
- super();
53
- this.#submitService.isSubmittable.pipe(takeUntilDestroyed()).subscribe(submittable => {
54
- this.isFormSubmittable.set(submittable);
55
- });
56
- }
49
+ isFormSubmittable = toSignal(this.#userProfileFormService.formState$.pipe(map(s => s.isFormSubmittable)), { initialValue: false });
57
50
 
58
51
  onCallback() {
59
52
  (document.getElementById('kc-register-form') as HTMLFormElement).submit();
@@ -5,6 +5,7 @@ import { LogoutOtherSessionsComponent } from '@keycloakify/angular/login/compone
5
5
  import { KcClassDirective } from '@keycloakify/angular/login/directives/kc-class';
6
6
  import type { I18n } from '@keycloakify/angular/login/i18n';
7
7
  import type { KcContext } from '@keycloakify/angular/login/KcContext';
8
+ import { LoginResourceInjectorService } from '@keycloakify/angular/login/services/login-resource-injector';
8
9
  import { LOGIN_CLASSES } from '@keycloakify/angular/login/tokens/classes';
9
10
  import { LOGIN_I18N } from '@keycloakify/angular/login/tokens/i18n';
10
11
  import { KC_LOGIN_CONTEXT } from '@keycloakify/angular/login/tokens/kc-context';
@@ -26,6 +27,7 @@ import type { ClassKey } from 'keycloakify/login/lib/kcClsx';
26
27
  export class LoginRecoveryAuthnCodeConfigComponent extends ComponentReference {
27
28
  kcContext = inject<Extract<KcContext, { pageId: 'login-recovery-authn-code-config.ftl' }>>(KC_LOGIN_CONTEXT);
28
29
  i18n = inject<I18n>(LOGIN_I18N);
30
+ loginResourceInjectorService = inject(LoginResourceInjectorService);
29
31
 
30
32
  override doUseDefaultCss = inject<boolean>(USE_DEFAULT_CSS);
31
33
  override classes = inject<Partial<Record<ClassKey, string>>>(LOGIN_CLASSES);
@@ -44,4 +46,125 @@ export class LoginRecoveryAuthnCodeConfigComponent extends ComponentReference {
44
46
  toggleRecoveryCodesConfirmation = signal(false);
45
47
 
46
48
  olRecoveryCodesListId = 'kc-recovery-codes-list';
49
+
50
+ constructor() {
51
+ super();
52
+ this.loginResourceInjectorService.insertAdditionalScripts([
53
+ {
54
+ type: 'text/javascript',
55
+ id: `${this.olRecoveryCodesListId}-script`,
56
+ textContent: `
57
+ /* copy recovery codes */
58
+ function copyRecoveryCodes() {
59
+ var tmpTextarea = document.createElement("textarea");
60
+ var codes = document.querySelectorAll("#${this.olRecoveryCodesListId} li");
61
+ for (i = 0; i < codes.length; i++) {
62
+ tmpTextarea.value = tmpTextarea.value + codes[i].innerText + "\\n";
63
+ }
64
+ document.body.appendChild(tmpTextarea);
65
+ tmpTextarea.select();
66
+ document.execCommand("copy");
67
+ document.body.removeChild(tmpTextarea);
68
+ }
69
+
70
+ var copyButton = document.getElementById("copyRecoveryCodes");
71
+ copyButton && copyButton.addEventListener("click", function () {
72
+ copyRecoveryCodes();
73
+ });
74
+
75
+ /* download recovery codes */
76
+ function formatCurrentDateTime() {
77
+ var dt = new Date();
78
+ var options = {
79
+ month: 'long',
80
+ day: 'numeric',
81
+ year: 'numeric',
82
+ hour: 'numeric',
83
+ minute: 'numeric',
84
+ timeZoneName: 'short'
85
+ };
86
+
87
+ return dt.toLocaleString('en-US', options);
88
+ }
89
+
90
+ function parseRecoveryCodeList() {
91
+ var recoveryCodes = document.querySelectorAll("#${this.olRecoveryCodesListId} li");
92
+ var recoveryCodeList = "";
93
+
94
+ for (var i = 0; i < recoveryCodes.length; i++) {
95
+ var recoveryCodeLiElement = recoveryCodes[i].innerText;
96
+ recoveryCodeList += recoveryCodeLiElement + "\\r\\n";
97
+ }
98
+
99
+ return recoveryCodeList;
100
+ }
101
+
102
+ function buildDownloadContent() {
103
+ var recoveryCodeList = parseRecoveryCodeList();
104
+ var dt = new Date();
105
+ var options = {
106
+ month: 'long',
107
+ day: 'numeric',
108
+ year: 'numeric',
109
+ hour: 'numeric',
110
+ minute: 'numeric',
111
+ timeZoneName: 'short'
112
+ };
113
+
114
+ return fileBodyContent =
115
+ ${JSON.stringify(this.i18n.msgStr('recovery-codes-download-file-header'))} + "\\n\\n" +
116
+ recoveryCodeList + "\\n" +
117
+ ${JSON.stringify(this.i18n.msgStr('recovery-codes-download-file-description'))} + "\\n\\n" +
118
+ ${JSON.stringify(this.i18n.msgStr('recovery-codes-download-file-date'))} + " " + formatCurrentDateTime();
119
+ }
120
+
121
+ function setUpDownloadLinkAndDownload(filename, text) {
122
+ var el = document.createElement('a');
123
+ el.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
124
+ el.setAttribute('download', filename);
125
+ el.style.display = 'none';
126
+ document.body.appendChild(el);
127
+ el.click();
128
+ document.body.removeChild(el);
129
+ }
130
+
131
+ function downloadRecoveryCodes() {
132
+ setUpDownloadLinkAndDownload('kc-download-recovery-codes.txt', buildDownloadContent());
133
+ }
134
+
135
+ var downloadButton = document.getElementById("downloadRecoveryCodes");
136
+ downloadButton && downloadButton.addEventListener("click", downloadRecoveryCodes);
137
+
138
+ /* print recovery codes */
139
+ function buildPrintContent() {
140
+ var recoveryCodeListHTML = document.getElementById('${this.olRecoveryCodesListId}').innerHTML;
141
+ var styles =
142
+ \`@page { size: auto; margin-top: 0; }
143
+ body { width: 480px; }
144
+ div { list-style-type: none; font-family: monospace }
145
+ p:first-of-type { margin-top: 48px }\`;
146
+
147
+ return printFileContent =
148
+ "<html><style>" + styles + "</style><body>" +
149
+ "<title>kc-download-recovery-codes</title>" +
150
+ "<p>" + ${JSON.stringify(this.i18n.msgStr('recovery-codes-download-file-header'))} + "</p>" +
151
+ "<div>" + recoveryCodeListHTML + "</div>" +
152
+ "<p>" + ${JSON.stringify(this.i18n.msgStr('recovery-codes-download-file-description'))} + "</p>" +
153
+ "<p>" + ${JSON.stringify(this.i18n.msgStr('recovery-codes-download-file-date'))} + " " + formatCurrentDateTime() + "</p>" +
154
+ "</body></html>";
155
+ }
156
+
157
+ function printRecoveryCodes() {
158
+ var w = window.open();
159
+ w.document.write(buildPrintContent());
160
+ w.print();
161
+ w.close();
162
+ }
163
+
164
+ var printButton = document.getElementById("printRecoveryCodes");
165
+ printButton && printButton.addEventListener("click", printRecoveryCodes);
166
+ `
167
+ }
168
+ ]);
169
+ }
47
170
  }
@@ -1,17 +1,18 @@
1
1
  import { NgComponentOutlet } from '@angular/common';
2
- import { ChangeDetectionStrategy, Component, forwardRef, inject, input, signal, type TemplateRef, Type, viewChild } from '@angular/core';
3
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
2
+ import { ChangeDetectionStrategy, Component, forwardRef, inject, input, type TemplateRef, Type, viewChild } from '@angular/core';
3
+ import { toSignal } from '@angular/core/rxjs-interop';
4
4
  import { USE_DEFAULT_CSS } from '@keycloakify/angular/lib/tokens/use-default-css';
5
5
  import { ComponentReference } from '@keycloakify/angular/login/classes/component-reference';
6
6
  import { UserProfileFormFieldsComponent } from '@keycloakify/angular/login/components/user-profile-form-fields';
7
7
  import { KcClassDirective } from '@keycloakify/angular/login/directives/kc-class';
8
8
  import type { I18n } from '@keycloakify/angular/login/i18n';
9
9
  import type { KcContext } from '@keycloakify/angular/login/KcContext';
10
- import { SubmitService } from '@keycloakify/angular/login/services/submit';
10
+ import { UserProfileFormService } from '@keycloakify/angular/login/services/user-profile-form';
11
11
  import { LOGIN_CLASSES } from '@keycloakify/angular/login/tokens/classes';
12
12
  import { LOGIN_I18N } from '@keycloakify/angular/login/tokens/i18n';
13
13
  import { KC_LOGIN_CONTEXT } from '@keycloakify/angular/login/tokens/kc-context';
14
14
  import type { ClassKey } from 'keycloakify/login/lib/kcClsx';
15
+ import { map } from 'rxjs';
15
16
 
16
17
  @Component({
17
18
  standalone: true,
@@ -27,7 +28,7 @@ import type { ClassKey } from 'keycloakify/login/lib/kcClsx';
27
28
  ]
28
29
  })
29
30
  export class LoginUpdateProfileComponent extends ComponentReference {
30
- #submitService = inject(SubmitService);
31
+ #userProfileFormService = inject(UserProfileFormService);
31
32
  kcContext = inject<Extract<KcContext, { pageId: 'login-update-profile.ftl' }>>(KC_LOGIN_CONTEXT);
32
33
  i18n = inject<I18n>(LOGIN_I18N);
33
34
 
@@ -45,13 +46,6 @@ export class LoginUpdateProfileComponent extends ComponentReference {
45
46
  infoNode = viewChild<TemplateRef<HTMLElement>>('infoNode');
46
47
  socialProvidersNode = viewChild<TemplateRef<HTMLElement>>('socialProvidersNode');
47
48
 
48
- isFormSubmittable = signal(false);
49
+ isFormSubmittable = toSignal(this.#userProfileFormService.formState$.pipe(map(s => s.isFormSubmittable)), { initialValue: false });
49
50
  userProfileFormFields = input<Type<UserProfileFormFieldsComponent>>();
50
-
51
- constructor() {
52
- super();
53
- this.#submitService.isSubmittable.pipe(takeUntilDestroyed()).subscribe(submittable => {
54
- this.isFormSubmittable.set(submittable);
55
- });
56
- }
57
51
  }
@@ -1,6 +1,6 @@
1
1
  import { AsyncPipe, NgClass, NgComponentOutlet } from '@angular/common';
2
2
  import { ChangeDetectionStrategy, Component, forwardRef, inject, input, signal, type TemplateRef, Type, viewChild } from '@angular/core';
3
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
3
+ import { toSignal } from '@angular/core/rxjs-interop';
4
4
  import { KcSanitizePipe } from '@keycloakify/angular/lib/pipes/kc-sanitize';
5
5
  import { USE_DEFAULT_CSS } from '@keycloakify/angular/lib/tokens/use-default-css';
6
6
  import { ComponentReference } from '@keycloakify/angular/login/classes/component-reference';
@@ -8,11 +8,12 @@ import { UserProfileFormFieldsComponent } from '@keycloakify/angular/login/compo
8
8
  import { KcClassDirective } from '@keycloakify/angular/login/directives/kc-class';
9
9
  import type { I18n } from '@keycloakify/angular/login/i18n';
10
10
  import type { KcContext } from '@keycloakify/angular/login/KcContext';
11
- import { SubmitService } from '@keycloakify/angular/login/services/submit';
11
+ import { UserProfileFormService } from '@keycloakify/angular/login/services/user-profile-form';
12
12
  import { LOGIN_CLASSES } from '@keycloakify/angular/login/tokens/classes';
13
13
  import { LOGIN_I18N } from '@keycloakify/angular/login/tokens/i18n';
14
14
  import { KC_LOGIN_CONTEXT } from '@keycloakify/angular/login/tokens/kc-context';
15
15
  import type { ClassKey } from 'keycloakify/login/lib/kcClsx';
16
+ import { map } from 'rxjs';
16
17
 
17
18
  @Component({
18
19
  selector: 'kc-register',
@@ -28,7 +29,7 @@ import type { ClassKey } from 'keycloakify/login/lib/kcClsx';
28
29
  ]
29
30
  })
30
31
  export class RegisterComponent extends ComponentReference {
31
- #submitService = inject(SubmitService);
32
+ #userProfileFormService = inject(UserProfileFormService);
32
33
  kcContext = inject<Extract<KcContext, { pageId: 'register.ftl' }>>(KC_LOGIN_CONTEXT);
33
34
  i18n = inject<I18n>(LOGIN_I18N);
34
35
 
@@ -46,16 +47,10 @@ export class RegisterComponent extends ComponentReference {
46
47
  infoNode = viewChild<TemplateRef<HTMLElement>>('infoNode');
47
48
  socialProvidersNode = viewChild<TemplateRef<HTMLElement>>('socialProvidersNode');
48
49
 
49
- isFormSubmittable = signal(false);
50
+ isFormSubmittable = toSignal(this.#userProfileFormService.formState$.pipe(map(s => s.isFormSubmittable)), { initialValue: false });
50
51
  areTermsAccepted = signal(false);
51
52
  userProfileFormFields = input<Type<UserProfileFormFieldsComponent>>();
52
53
 
53
- constructor() {
54
- super();
55
- this.#submitService.isSubmittable.pipe(takeUntilDestroyed()).subscribe(submittable => {
56
- this.isFormSubmittable.set(submittable);
57
- });
58
- }
59
54
  onCallback() {
60
55
  (document.getElementById('kc-register-form') as HTMLFormElement).submit();
61
56
  }
@@ -1,15 +1,16 @@
1
1
  import { NgComponentOutlet } from '@angular/common';
2
- import { ChangeDetectionStrategy, Component, forwardRef, inject, input, signal, type TemplateRef, Type, viewChild } from '@angular/core';
3
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
2
+ import { ChangeDetectionStrategy, Component, forwardRef, inject, input, type TemplateRef, Type, viewChild } from '@angular/core';
3
+ import { toSignal } from '@angular/core/rxjs-interop';
4
4
  import { ComponentReference } from '@keycloakify/angular/login/classes/component-reference';
5
5
  import { LogoutOtherSessionsComponent } from '@keycloakify/angular/login/components/logout-other-sessions';
6
6
  import { UserProfileFormFieldsComponent } from '@keycloakify/angular/login/components/user-profile-form-fields';
7
7
  import { KcClassDirective } from '@keycloakify/angular/login/directives/kc-class';
8
8
  import type { I18n } from '@keycloakify/angular/login/i18n';
9
9
  import type { KcContext } from '@keycloakify/angular/login/KcContext';
10
- import { SubmitService } from '@keycloakify/angular/login/services/submit';
10
+ import { UserProfileFormService } from '@keycloakify/angular/login/services/user-profile-form';
11
11
  import { LOGIN_I18N } from '@keycloakify/angular/login/tokens/i18n';
12
12
  import { KC_LOGIN_CONTEXT } from '@keycloakify/angular/login/tokens/kc-context';
13
+ import { map } from 'rxjs';
13
14
 
14
15
  @Component({
15
16
  standalone: true,
@@ -25,7 +26,7 @@ import { KC_LOGIN_CONTEXT } from '@keycloakify/angular/login/tokens/kc-context';
25
26
  ]
26
27
  })
27
28
  export class UpdateEmailComponent extends ComponentReference {
28
- #submitService = inject(SubmitService);
29
+ #userProfileFormService = inject(UserProfileFormService);
29
30
  kcContext = inject<Extract<KcContext, { pageId: 'update-email.ftl' }>>(KC_LOGIN_CONTEXT);
30
31
  i18n = inject<I18n>(LOGIN_I18N);
31
32
 
@@ -41,12 +42,5 @@ export class UpdateEmailComponent extends ComponentReference {
41
42
  socialProvidersNode = viewChild<TemplateRef<HTMLElement>>('socialProvidersNode');
42
43
 
43
44
  userProfileFormFields = input<Type<UserProfileFormFieldsComponent>>();
44
- isFormSubmittable = signal(false);
45
-
46
- constructor() {
47
- super();
48
- this.#submitService.isSubmittable.pipe(takeUntilDestroyed()).subscribe(submittable => {
49
- this.isFormSubmittable.set(submittable);
50
- });
51
- }
45
+ isFormSubmittable = toSignal(this.#userProfileFormService.formState$.pipe(map(s => s.isFormSubmittable)), { initialValue: false });
52
46
  }