diff --git a/src/login/kcContext/KcContext.ts b/src/login/kcContext/KcContext.ts index 6d03545a..0a338215 100644 --- a/src/login/kcContext/KcContext.ts +++ b/src/login/kcContext/KcContext.ts @@ -535,7 +535,9 @@ export type Attribute = { name: string; displayDescription?: string; }; - html5DataAnnotations: Record; + html5DataAnnotations: { + kcNumberFormat?: string; + }; readOnly: boolean; validators: Validators; annotations: { diff --git a/src/login/lib/useUserProfileForm.tsx b/src/login/lib/useUserProfileForm.tsx index 8763c23e..2e44079d 100644 --- a/src/login/lib/useUserProfileForm.tsx +++ b/src/login/lib/useUserProfileForm.tsx @@ -8,6 +8,8 @@ import { emailRegexp } from "keycloakify/tools/emailRegExp"; import type { KcContext, PasswordPolicies } from "keycloakify/login/kcContext/KcContext"; import { assert, type Equals } from "tsafe/assert"; import type { I18n } from "../i18n"; +import { formatNumber } from "keycloakify/tools/formatNumber"; +import { usePrepareTemplate } from "keycloakify/lib/usePrepareTemplate"; export type FormFieldError = { errorMessage: JSX.Element; @@ -66,10 +68,14 @@ export type KcContextLike = { messagesPerField: Pick; profile: { attributes: Attribute[]; + html5DataAnnotations: Record; }; passwordRequired?: boolean; realm: { registrationEmailAsUsername: boolean }; passwordPolicies?: PasswordPolicies; + url: { + resourcesPath: string; + }; }; export type ParamsOfUseUserProfileForm = { @@ -103,6 +109,24 @@ namespace internal { export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTypeOfUseUserProfileForm { const { kcContext, i18n, doMakeUserConfirmPassword } = params; + usePrepareTemplate({ + "styles": [], + // NOTE: The ?? {} is for compat with Keycloak version prior to 24 + "scripts": Object.keys(kcContext.profile.html5DataAnnotations ?? {}) + .filter(key => key !== "kcMultivalued" && key !== "kcNumberFormat") // NOTE: Keycloakify handles it. + .map(key => ({ + "isModule": true, + "source": { + "type": "url", + "src": `${kcContext.url.resourcesPath}/js/${key}.js` + } + })), + "htmlClassName": undefined, + "bodyClassName": undefined, + "htmlLangProperty": undefined, + "documentTitle": undefined + }); + const attributesWithPassword = useMemo(() => { const attributesWithPassword: Attribute[] = []; @@ -164,6 +188,23 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy case "update": formFieldState.valueOrValues = params.valueOrValues; + apply_formatters: { + const { attribute } = formFieldState; + + // NOTE: The `?? {}` is for compat with Keycloak version prior to 24 + const { kcNumberFormat } = attribute.html5DataAnnotations ?? {}; + + if (kcNumberFormat === undefined) { + break apply_formatters; + } + + if (formFieldState.valueOrValues instanceof Array) { + formFieldState.valueOrValues = formFieldState.valueOrValues.map(value => formatNumber(value, kcNumberFormat)); + } else { + formFieldState.valueOrValues = formatNumber(formFieldState.valueOrValues, kcNumberFormat); + } + } + formFieldState.errors = getErrors({ "attributeName": params.name, "formFieldStates": state.formFieldStates