From a015518b6fcca2b3247a71b7fd34dc05e9f9c347 Mon Sep 17 00:00:00 2001 From: Mathias Helsengren Date: Tue, 23 Sep 2025 12:15:58 +0200 Subject: [PATCH 01/21] Added custom validation for missing password and user/email --- src/Umbraco.Web.UI.Login/src/auth-styles.css | 58 ++- src/Umbraco.Web.UI.Login/src/auth.element.ts | 61 ++- .../components/pages/login.page.element.ts | 412 +++++++++--------- .../src/localization/lang/da.ts | 87 ++-- .../src/localization/lang/en-us.ts | 3 + 5 files changed, 345 insertions(+), 276 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/src/auth-styles.css b/src/Umbraco.Web.UI.Login/src/auth-styles.css index f63dfa92e60d..20f2ce72e138 100644 --- a/src/Umbraco.Web.UI.Login/src/auth-styles.css +++ b/src/Umbraco.Web.UI.Login/src/auth-styles.css @@ -1,44 +1,54 @@ +#umb-login-form .validation-message { + color: var(--uui-color-invalid-standalone); + display: none; + margin-top: var(--uui-size-1); +} + +#umb-login-form .validation-message[state='error'] { + display: block; +} + #umb-login-form input { - width: 100%; - height: var(--input-height); - box-sizing: border-box; - display: block; - border: 1px solid var(--uui-color-border); - border-radius: var(--uui-border-radius); - background-color: var(--uui-color-surface); - padding: var(--uui-size-1, 3px) var(--uui-size-space-4, 9px); + width: 100%; + height: var(--input-height); + box-sizing: border-box; + display: block; + border: 1px solid var(--uui-color-border); + border-radius: var(--uui-border-radius); + background-color: var(--uui-color-surface); + padding: var(--uui-size-1, 3px) var(--uui-size-space-4, 9px); } #umb-login-form uui-form-layout-item { - margin-top: var(--uui-size-space-4); - margin-bottom: var(--uui-size-space-4); + margin-top: var(--uui-size-space-4); + margin-bottom: var(--uui-size-space-4); } #umb-login-form input:focus-within { - border-color: var(--uui-input-border-color-focus, var(--uui-color-border-emphasis, #a1a1a1)); - outline: calc(2px * var(--uui-show-focus-outline, 1)) solid var(--uui-color-focus); + border-color: var(--uui-input-border-color-focus, var(--uui-color-border-emphasis, #a1a1a1)); + outline: calc(2px * var(--uui-show-focus-outline, 1)) solid var(--uui-color-focus); } #umb-login-form input:hover:not(:focus-within) { - border-color: var(--uui-input-border-color-hover, var(--uui-color-border-standalone, #c2c2c2)); + border-color: var(--uui-input-border-color-hover, var(--uui-color-border-standalone, #c2c2c2)); } #umb-login-form input::-ms-reveal { - display: none; + display: none; } #umb-login-form input span { - position: absolute; - right: 1px; - top: 50%; - transform: translateY(-50%); - z-index: 100; + position: absolute; + right: 1px; + top: 50%; + transform: translateY(-50%); + z-index: 100; } #umb-login-form input span svg { - background-color: white; - display: block; - padding: .2em; - width: 1.3em; - height: 1.3em; + background-color: white; + display: block; + padding: 0.2em; + width: 1.3em; + height: 1.3em; } diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index b8942fbcf436..02d1b52d642f 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -1,6 +1,10 @@ import { html, customElement, property, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import type { InputType, UUIFormLayoutItemElement } from '@umbraco-cms/backoffice/external/uui'; +import { + UUIFormValidationMessageElement, + type InputType, + type UUIFormLayoutItemElement, +} from '@umbraco-cms/backoffice/external/uui'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UMB_AUTH_CONTEXT, UmbAuthContext } from './contexts'; @@ -45,10 +49,21 @@ const createLabel = (opts: { forId: string; localizeAlias: string; localizeFallb return label; }; -const createFormLayoutItem = (label: HTMLLabelElement, input: HTMLInputElement) => { +const createValidationMessage = (id: string, localizationKey: string) => { + const validationElement = document.createElement('div'); + validationElement.className = 'validation-message'; + validationElement.setAttribute('for', id); + const localizeElement = document.createElement('umb-localize'); + localizeElement.key = localizationKey; + validationElement.appendChild(localizeElement); + return validationElement; +}; + +const createFormLayoutItem = (label: HTMLLabelElement, input: HTMLInputElement, localizationKey: string) => { const formLayoutItem = document.createElement('uui-form-layout-item') as UUIFormLayoutItemElement; formLayoutItem.appendChild(label); formLayoutItem.appendChild(input); + formLayoutItem.appendChild(createValidationMessage(input.id, localizationKey)); return formLayoutItem; }; @@ -60,10 +75,40 @@ const createForm = (elements: HTMLElement[]) => { form.id = 'umb-login-form'; form.name = 'login-form'; form.spellcheck = false; + form.noValidate = true; elements.push(styles); elements.forEach((element) => form.appendChild(element)); + form.addEventListener('submit', (e) => { + e.preventDefault(); + + const form = e.target as HTMLFormElement; + if (!form) return; + + const formData = new FormData(form); + + const username = formData.get('username') as string; + const password = formData.get('password') as string; + + const validationMessages = Array.from(form.querySelectorAll('.validation-message')); + validationMessages.forEach((vm) => vm.removeAttribute('state')); + + if (!username) { + const validationMessage = validationMessages.find( + (vm) => vm.getAttribute('for') === 'username-input' + ) as UUIFormValidationMessageElement; + validationMessage?.setAttribute('state', 'error'); + } + + if (!password) { + const validationMessage = validationMessages.find( + (vm) => vm.getAttribute('for') === 'password-input' + ) as UUIFormValidationMessageElement; + validationMessage?.setAttribute('state', 'error'); + } + }); + return form; }; @@ -192,8 +237,16 @@ export default class UmbAuthElement extends UmbLitElement { localizeFallback: 'Password', }); - this._usernameLayoutItem = createFormLayoutItem(this._usernameLabel, this._usernameInput); - this._passwordLayoutItem = createFormLayoutItem(this._passwordLabel, this._passwordInput); + this._usernameLayoutItem = createFormLayoutItem( + this._usernameLabel, + this._usernameInput, + this.usernameIsEmail ? 'auth_requiredEmailValidationMessage' : 'auth_requiredUsernameValidationMessage' + ); + this._passwordLayoutItem = createFormLayoutItem( + this._passwordLabel, + this._passwordInput, + 'auth_requiredPasswordValidationMessage' + ); this._form = createForm([this._usernameLayoutItem, this._passwordLayoutItem]); diff --git a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts index 315bac444895..4787e912e361 100644 --- a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts +++ b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts @@ -7,248 +7,248 @@ import { UMB_AUTH_CONTEXT } from '../../contexts'; @customElement('umb-login-page') export default class UmbLoginPageElement extends UmbLitElement { @property({type: Boolean, attribute: 'username-is-email'}) - usernameIsEmail = false; + usernameIsEmail = false; @queryAssignedElements({flatten: true}) - protected slottedElements?: HTMLFormElement[]; + protected slottedElements?: HTMLFormElement[]; @property({type: Boolean, attribute: 'allow-password-reset'}) - allowPasswordReset = false; + allowPasswordReset = false; - @state() - private _loginState?: UUIButtonState; + @state() + private _loginState?: UUIButtonState; - @state() - private _loginError = ''; + @state() + private _loginError = ''; - @state() - supportPersistLogin = false; + @state() + supportPersistLogin = false; - #formElement?: HTMLFormElement; + #formElement?: HTMLFormElement; - #authContext?: typeof UMB_AUTH_CONTEXT.TYPE; + #authContext?: typeof UMB_AUTH_CONTEXT.TYPE; - constructor() { - super(); + constructor() { + super(); - this.consumeContext(UMB_AUTH_CONTEXT, (authContext) => { - this.#authContext = authContext; - this.supportPersistLogin = authContext?.supportsPersistLogin ?? false; - }); - } + this.consumeContext(UMB_AUTH_CONTEXT, (authContext) => { + this.#authContext = authContext; + this.supportPersistLogin = authContext?.supportsPersistLogin ?? false; + }); + } - async #onSlotChanged() { - this.#formElement = this.slottedElements?.find((el) => el.id === 'umb-login-form'); + async #onSlotChanged() { + this.#formElement = this.slottedElements?.find((el) => el.id === 'umb-login-form'); - if (!this.#formElement) return; + if (!this.#formElement) return; - // We need to listen for the enter key to submit the form, because the uui-button does not support the native input fields submit event - this.#formElement.addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - this.#onSubmitClick(); - } - }); - this.#formElement.onsubmit = this.#handleSubmit; - } + // We need to listen for the enter key to submit the form, because the uui-button does not support the native input fields submit event + this.#formElement.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.#onSubmitClick(); + } + }); + this.#formElement.onsubmit = this.#handleSubmit; + } - #handleSubmit = async (e: SubmitEvent) => { - e.preventDefault(); + #handleSubmit = async (e: SubmitEvent) => { + e.preventDefault(); - if (!this.#authContext) return; + this._loginError = ''; + this._loginState = undefined; + if (!this.#authContext) return; - const form = e.target as HTMLFormElement; - if (!form) return; + const form = e.target as HTMLFormElement; + if (!form) return; - const formData = new FormData(form); + const formData = new FormData(form); - const username = formData.get('username') as string; - const password = formData.get('password') as string; - const persist = formData.has('persist'); + const username = formData.get('username') as string; + const password = formData.get('password') as string; + const persist = formData.has('persist'); - if (!username || !password) { - this._loginError = this.localize.term('auth_userFailedLogin'); - this._loginState = 'failed'; - return; - } + if (!username || !password) { + return; + } - this._loginState = 'waiting'; + this._loginState = 'waiting'; - const response = await this.#authContext.login({ - username, - password, - persist, - }); + const response = await this.#authContext.login({ + username, + password, + persist, + }); - this._loginError = response.error || ''; - this._loginState = response.error ? 'failed' : 'success'; + this._loginError = response.error || ''; + this._loginState = response.error ? 'failed' : 'success'; - // Check for 402 status code indicating that MFA is required - if (response.status === 402) { - this.#authContext.isMfaEnabled = true; - if (response.twoFactorView) { - this.#authContext.twoFactorView = response.twoFactorView; - } - if (response.twoFactorProviders) { - this.#authContext.mfaProviders = response.twoFactorProviders; - } + // Check for 402 status code indicating that MFA is required + if (response.status === 402) { + this.#authContext.isMfaEnabled = true; + if (response.twoFactorView) { + this.#authContext.twoFactorView = response.twoFactorView; + } + if (response.twoFactorProviders) { + this.#authContext.mfaProviders = response.twoFactorProviders; + } this.dispatchEvent(new CustomEvent('umb-login-flow', {composed: true, detail: {flow: 'mfa'}})); - return; - } - - if (response.error) { - return; - } - - const returnPath = this.#authContext.returnPath; - - if (returnPath) { - location.href = returnPath; - } - }; - - get #greetingLocalizationKey() { - return [ - 'auth_greeting0', - 'auth_greeting1', - 'auth_greeting2', - 'auth_greeting3', - 'auth_greeting4', - 'auth_greeting5', - 'auth_greeting6', - ][new Date().getDay()]; - } - - #onSubmitClick = () => { - this.#formElement?.requestSubmit(); - }; - - render() { - return html` - - -
- ${when( - this.supportPersistLogin, + return; + } + + if (response.error) { + return; + } + + const returnPath = this.#authContext.returnPath; + + if (returnPath) { + location.href = returnPath; + } + }; + + get #greetingLocalizationKey() { + return [ + 'auth_greeting0', + 'auth_greeting1', + 'auth_greeting2', + 'auth_greeting3', + 'auth_greeting4', + 'auth_greeting5', + 'auth_greeting6', + ][new Date().getDay()]; + } + + #onSubmitClick = () => { + this.#formElement?.requestSubmit(); + }; + + render() { + return html` + + +
+ ${when( + this.supportPersistLogin, () => html` - Remember me - - ` - )} - ${when( - this.allowPasswordReset, - () => + Remember me + + ` + )} + ${when( + this.allowPasswordReset, + () => html` ` - )} -
- - - ${this.#renderErrorMessage()} - `; - } - - #renderErrorMessage() { - if (!this._loginError || this._loginState !== 'failed') return nothing; - - return html`${this._loginError}`; - } - - #handleForgottenPassword() { + Forgotten password? + ` + )} +
+ + + ${this.#renderErrorMessage()} + `; + } + + #renderErrorMessage() { + if (!this._loginError || this._loginState !== 'failed') return nothing; + + return html`${this._loginError}`; + } + + #handleForgottenPassword() { this.dispatchEvent(new CustomEvent('umb-login-flow', {composed: true, detail: {flow: 'reset'}})); - } - - static styles: CSSResultGroup = [ - css` - :host { - display: flex; - flex-direction: column; - } - - #header { - text-align: center; - display: flex; - flex-direction: column; - gap: var(--uui-size-space-5); - } - - #header span { - color: var(--uui-color-text-alt); /* TODO Change to uui color when uui gets a muted text variable */ - font-size: 14px; - } - - #greeting { - color: var(--uui-color-interactive); - text-align: center; - font-weight: 400; - font-size: var(--header-font-size); - margin: 0 0 var(--uui-size-layout-1); - line-height: 1.2; - } - - #umb-login-button { - margin-top: var(--uui-size-space-4); - width: 100%; - } - - #forgot-password { - cursor: pointer; - background: none; - border: 0; - height: 1rem; - color: var(--uui-color-text-alt); /* TODO Change to uui color when uui gets a muted text variable */ - gap: var(--uui-size-space-1); - align-self: center; - text-decoration: none; - display: inline-flex; - line-height: 1; - font-size: 14px; + } + + static styles: CSSResultGroup = [ + css` + :host { + display: flex; + flex-direction: column; + } + + #header { + text-align: center; + display: flex; + flex-direction: column; + gap: var(--uui-size-space-5); + } + + #header span { + color: var(--uui-color-text-alt); /* TODO Change to uui color when uui gets a muted text variable */ + font-size: 14px; + } + + #greeting { + color: var(--uui-color-interactive); + text-align: center; + font-weight: 400; + font-size: var(--header-font-size); + margin: 0 0 var(--uui-size-layout-1); + line-height: 1.2; + } + + #umb-login-button { + margin-top: var(--uui-size-space-4); + width: 100%; + } + + #forgot-password { + cursor: pointer; + background: none; + border: 0; + height: 1rem; + color: var(--uui-color-text-alt); /* TODO Change to uui color when uui gets a muted text variable */ + gap: var(--uui-size-space-1); + align-self: center; + text-decoration: none; + display: inline-flex; + line-height: 1; + font-size: 14px; font-family: var(--uui-font-family),sans-serif; - margin-left: auto; - margin-bottom: var(--uui-size-space-3); - } - - #forgot-password:hover { - color: var(--uui-color-interactive-emphasis); - } - - .text-error { - margin-top: var(--uui-size-space-4); - } - - .text-danger { - color: var(--uui-color-danger-standalone); - } - - #secondary-actions { - display: flex; - align-items: center; - justify-content: space-between; - } - `, - ]; + margin-left: auto; + margin-bottom: var(--uui-size-space-3); + } + + #forgot-password:hover { + color: var(--uui-color-interactive-emphasis); + } + + .text-error { + margin-top: var(--uui-size-space-4); + } + + .text-danger { + color: var(--uui-color-danger-standalone); + } + + #secondary-actions { + display: flex; + align-items: center; + justify-content: space-between; + } + `, + ]; } declare global { - interface HTMLElementTagNameMap { - 'umb-login-page': UmbLoginPageElement; - } + interface HTMLElementTagNameMap { + 'umb-login-page': UmbLoginPageElement; + } } diff --git a/src/Umbraco.Web.UI.Login/src/localization/lang/da.ts b/src/Umbraco.Web.UI.Login/src/localization/lang/da.ts index fb15444c0d18..7220369ee4db 100644 --- a/src/Umbraco.Web.UI.Login/src/localization/lang/da.ts +++ b/src/Umbraco.Web.UI.Login/src/localization/lang/da.ts @@ -1,51 +1,54 @@ import type { UmbLocalizationDictionary } from '@umbraco-cms/backoffice/localization-api'; export default { - auth: { - continue: 'Fortsæt', - validate: 'Indsend', - login: 'Log ind', - email: 'E-mail', - username: 'Brugernavn', - password: 'Adgangskode', - submit: 'Indsend', - required: 'Påkrævet', - success: 'Succes', - forgottenPassword: 'Glemt adgangskode?', + auth: { + continue: 'Fortsæt', + validate: 'Indsend', + login: 'Log ind', + email: 'E-mail', + username: 'Brugernavn', + password: 'Adgangskode', + submit: 'Indsend', + required: 'Påkrævet', + success: 'Succes', + forgottenPassword: 'Glemt adgangskode?', forgottenPasswordInstruction: 'En e-mail vil blive sendt til den angivne adresse med et link til at nulstille din adgangskode', requestPasswordResetConfirmation: 'En e-mail med instruktioner for nulstilling af adgangskoden vil blive sendt til den angivne adresse, hvis det matcher vores optegnelser', - setPasswordConfirmation: 'Din adgangskode er blevet opdateret', - rememberMe: 'Husk mig', - error: 'Fejl', - defaultError: 'Der er opstået en ukendt fejl.', - errorInPasswordFormat: 'Kodeordet skal være på minimum %0% tegn og indeholde mindst %1% alfanumeriske tegn.', - passwordMismatch: 'Adgangskoderne er ikke ens.', - passwordMinLength: 'Adgangskoden skal være mindst {0} tegn lang.', - passwordIsBlank: 'Din nye adgangskode kan ikke være tom.', - userFailedLogin: 'Ups! Vi kunne ikke logge dig ind. Tjek at dit brugernavn og adgangskode er korrekt og prøv igen.', - userLockedOut: 'Din konto er blevet låst. Prøv igen senere.', - receivedErrorFromServer: 'Der skete en fejl på serveren', - resetCodeExpired: 'Det link, du har klikket på, er ugyldigt eller udløbet', + setPasswordConfirmation: 'Din adgangskode er blevet opdateret', + rememberMe: 'Husk mig', + error: 'Fejl', + defaultError: 'Der er opstået en ukendt fejl.', + errorInPasswordFormat: 'Kodeordet skal være på minimum %0% tegn og indeholde mindst %1% alfanumeriske tegn.', + passwordMismatch: 'Adgangskoderne er ikke ens.', + passwordMinLength: 'Adgangskoden skal være mindst {0} tegn lang.', + passwordIsBlank: 'Din nye adgangskode kan ikke være tom.', + userFailedLogin: 'Ups! Vi kunne ikke logge dig ind. Tjek at dit brugernavn og adgangskode er korrekt og prøv igen.', + userLockedOut: 'Din konto er blevet låst. Prøv igen senere.', + receivedErrorFromServer: 'Der skete en fejl på serveren', + resetCodeExpired: 'Det link, du har klikket på, er ugyldigt eller udløbet', userInviteWelcomeMessage: 'Hej og velkommen til Umbraco! På bare 1 minut vil du være klar til at komme i gang, vi skal bare have dig til at oprette en adgangskode.', userInviteExpiredMessage: 'Velkommen til Umbraco! Desværre er din invitation udløbet. Kontakt din administrator og bed om at gensende invitationen.', - newPassword: 'Ny adgangskode', - confirmNewPassword: 'Bekræft adgangskode', - greeting0: 'Velkommen', - greeting1: 'Velkommen', - greeting2: 'Velkommen', - greeting3: 'Velkommen', - greeting4: 'Velkommen', - greeting5: 'Velkommen', - greeting6: 'Velkommen', - mfaTitle: 'Sidste skridt!', - mfaCodeInputHelp: 'Indtast venligst bekræftelseskoden', - mfaText: 'Du har aktiveret multi-faktor godkendelse. Du skal nu bekræfte din identitet.', - mfaMultipleText: 'Vælg venligst en godkendelsesmetode', - mfaCodeInput: 'Kode', - mfaInvalidCode: 'Forkert kode indtastet', - signInWith: 'Log ind med {0}', - returnToLogin: 'Tilbage til log ind', + newPassword: 'Ny adgangskode', + confirmNewPassword: 'Bekræft adgangskode', + greeting0: 'Velkommen', + greeting1: 'Velkommen', + greeting2: 'Velkommen', + greeting3: 'Velkommen', + greeting4: 'Velkommen', + greeting5: 'Velkommen', + greeting6: 'Velkommen', + mfaTitle: 'Sidste skridt!', + mfaCodeInputHelp: 'Indtast venligst bekræftelseskoden', + mfaText: 'Du har aktiveret multi-faktor godkendelse. Du skal nu bekræfte din identitet.', + mfaMultipleText: 'Vælg venligst en godkendelsesmetode', + mfaCodeInput: 'Kode', + mfaInvalidCode: 'Forkert kode indtastet', + signInWith: 'Log ind med {0}', + returnToLogin: 'Tilbage til log ind', localLoginDisabled: 'Desværre er det ikke muligt at logge ind direkte. Det er blevet deaktiveret af en login-udbyder.', - friendlyGreeting: 'Hej!', - }, + friendlyGreeting: 'Hej!', + requiredEmailValidationMessage: 'Udfyld venligst en e-mail', + requiredUsernameValidationMessage: 'Udfyld venligst et brugernavn', + requiredPasswordValidationMessage: 'Udfyld venligst en adgangskode', + }, } satisfies UmbLocalizationDictionary; diff --git a/src/Umbraco.Web.UI.Login/src/localization/lang/en-us.ts b/src/Umbraco.Web.UI.Login/src/localization/lang/en-us.ts index e5fab358c3c5..0a096e2c79c8 100644 --- a/src/Umbraco.Web.UI.Login/src/localization/lang/en-us.ts +++ b/src/Umbraco.Web.UI.Login/src/localization/lang/en-us.ts @@ -51,5 +51,8 @@ export default { returnToLogin: 'Return to login', localLoginDisabled: 'Unfortunately, direct login is not possible. It has been disabled by a provider.', friendlyGreeting: 'Hi there', + requiredEmailValidationMessage: 'Please fill out an email', + requiredUsernameValidationMessage: 'Please fill out a username', + requiredPasswordValidationMessage: 'Please fill in a password', }, } satisfies UmbLocalizationDictionary; From dc35c52bd8a153ed983db978e5c16a0ccf5c7c5e Mon Sep 17 00:00:00 2001 From: Mathias Helsengren Date: Thu, 25 Sep 2025 15:00:18 +0200 Subject: [PATCH 02/21] Changed some of the logic behind custom validation, so it now uses aria-errormessage --- src/Umbraco.Web.UI.Login/src/auth-styles.css | 4 +-- src/Umbraco.Web.UI.Login/src/auth.element.ts | 27 ++++++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/src/auth-styles.css b/src/Umbraco.Web.UI.Login/src/auth-styles.css index 20f2ce72e138..7efb55c2e465 100644 --- a/src/Umbraco.Web.UI.Login/src/auth-styles.css +++ b/src/Umbraco.Web.UI.Login/src/auth-styles.css @@ -1,10 +1,10 @@ -#umb-login-form .validation-message { +#umb-login-form .errormessage { color: var(--uui-color-invalid-standalone); display: none; margin-top: var(--uui-size-1); } -#umb-login-form .validation-message[state='error'] { +#umb-login-form [aria-invalid='true'] ~ .errormessage { display: block; } diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index 02d1b52d642f..621dfa76d6d2 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -22,6 +22,7 @@ const createInput = (opts: { name: string; autocomplete: AutoFill; label: string; + errorId: string; inputmode: string; autofocus?: boolean; }) => { @@ -33,7 +34,9 @@ const createInput = (opts: { input.required = true; input.inputMode = opts.inputmode; input.ariaLabel = opts.label; + input.setAttribute('aria-errormessage', opts.errorId); input.autofocus = opts.autofocus || false; + input.className = 'input'; return input; }; @@ -49,21 +52,23 @@ const createLabel = (opts: { forId: string; localizeAlias: string; localizeFallb return label; }; -const createValidationMessage = (id: string, localizationKey: string) => { +const createValidationMessage = (errorId: string, localizationKey: string) => { const validationElement = document.createElement('div'); - validationElement.className = 'validation-message'; - validationElement.setAttribute('for', id); + validationElement.className = 'errormessage'; + validationElement.id = errorId; const localizeElement = document.createElement('umb-localize'); localizeElement.key = localizationKey; validationElement.appendChild(localizeElement); + return validationElement; }; const createFormLayoutItem = (label: HTMLLabelElement, input: HTMLInputElement, localizationKey: string) => { const formLayoutItem = document.createElement('uui-form-layout-item') as UUIFormLayoutItemElement; + const errorId = input.getAttribute('aria-errormessage') || input.id + '-error'; formLayoutItem.appendChild(label); formLayoutItem.appendChild(input); - formLayoutItem.appendChild(createValidationMessage(input.id, localizationKey)); + formLayoutItem.appendChild(createValidationMessage(errorId, localizationKey)); return formLayoutItem; }; @@ -91,21 +96,21 @@ const createForm = (elements: HTMLElement[]) => { const username = formData.get('username') as string; const password = formData.get('password') as string; - const validationMessages = Array.from(form.querySelectorAll('.validation-message')); - validationMessages.forEach((vm) => vm.removeAttribute('state')); + const validationMessages = Array.from(form.querySelectorAll('.input')); + validationMessages.forEach((vm) => vm.removeAttribute('aria-invalid')); if (!username) { const validationMessage = validationMessages.find( - (vm) => vm.getAttribute('for') === 'username-input' + (vm) => vm.getAttribute('id') === 'username-input' ) as UUIFormValidationMessageElement; - validationMessage?.setAttribute('state', 'error'); + validationMessage?.setAttribute('aria-invalid', 'true'); } if (!password) { const validationMessage = validationMessages.find( - (vm) => vm.getAttribute('for') === 'password-input' + (vm) => vm.getAttribute('id') === 'password-input' ) as UUIFormValidationMessageElement; - validationMessage?.setAttribute('state', 'error'); + validationMessage?.setAttribute('aria-invalid', 'true'); } }); @@ -215,6 +220,7 @@ export default class UmbAuthElement extends UmbLitElement { name: 'username', autocomplete: 'username', label: labelUsername, + errorId: 'username-input-error', inputmode: this.usernameIsEmail ? 'email' : '', autofocus: true, }); @@ -224,6 +230,7 @@ export default class UmbAuthElement extends UmbLitElement { name: 'password', autocomplete: 'current-password', label: labelPassword, + errorId: 'password-input-error', inputmode: '', }); this._usernameLabel = createLabel({ From 5eb54d68da6b5be73df2ee5bfa0432025e5f0a4e Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:21:16 +0100 Subject: [PATCH 03/21] fix: imports from src folder instead --- src/Umbraco.Web.UI.Login/{public => src/assets}/closedEye.svg | 0 src/Umbraco.Web.UI.Login/{public => src/assets}/openEye.svg | 0 src/Umbraco.Web.UI.Login/src/auth.element.ts | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/Umbraco.Web.UI.Login/{public => src/assets}/closedEye.svg (100%) rename src/Umbraco.Web.UI.Login/{public => src/assets}/openEye.svg (100%) diff --git a/src/Umbraco.Web.UI.Login/public/closedEye.svg b/src/Umbraco.Web.UI.Login/src/assets/closedEye.svg similarity index 100% rename from src/Umbraco.Web.UI.Login/public/closedEye.svg rename to src/Umbraco.Web.UI.Login/src/assets/closedEye.svg diff --git a/src/Umbraco.Web.UI.Login/public/openEye.svg b/src/Umbraco.Web.UI.Login/src/assets/openEye.svg similarity index 100% rename from src/Umbraco.Web.UI.Login/public/openEye.svg rename to src/Umbraco.Web.UI.Login/src/assets/openEye.svg diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index 36d42a68f6e8..24299fd95910 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -14,8 +14,8 @@ import { UmbSlimBackofficeController } from './controllers'; import authStyles from './auth-styles.css?inline'; // Import the SVG files -import openEyeSVG from '../public/openEye.svg?raw'; -import closedEyeSVG from '../public/closedEye.svg?raw'; +import openEyeSVG from './assets/openEye.svg?raw'; +import closedEyeSVG from './assets/closedEye.svg?raw'; // Import the main bundle import { extensions } from './umbraco-package.js'; From 2e84206f0e8ff827ee9467870286cb0a06728ac0 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:21:57 +0100 Subject: [PATCH 04/21] build(deps-dev): bump vite to 7.2.0 --- src/Umbraco.Web.UI.Login/package-lock.json | 8 ++++---- src/Umbraco.Web.UI.Login/package.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/package-lock.json b/src/Umbraco.Web.UI.Login/package-lock.json index 6c509232c623..752b2312d173 100644 --- a/src/Umbraco.Web.UI.Login/package-lock.json +++ b/src/Umbraco.Web.UI.Login/package-lock.json @@ -10,7 +10,7 @@ "@umbraco-cms/backoffice": "^16.2.0", "msw": "^2.11.3", "typescript": "^5.9.3", - "vite": "^7.1.11" + "vite": "^7.2.0" }, "engines": { "node": ">=22", @@ -4193,9 +4193,9 @@ } }, "node_modules/vite": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", - "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.0.tgz", + "integrity": "sha512-C/Naxf8H0pBx1PA4BdpT+c/5wdqI9ILMdwjSMILw7tVIh3JsxzZqdeTLmmdaoh5MYUEOyBnM9K3o0DzoZ/fe+w==", "dev": true, "license": "MIT", "dependencies": { diff --git a/src/Umbraco.Web.UI.Login/package.json b/src/Umbraco.Web.UI.Login/package.json index c0f863dc50f8..a2ebccd24d39 100644 --- a/src/Umbraco.Web.UI.Login/package.json +++ b/src/Umbraco.Web.UI.Login/package.json @@ -18,11 +18,11 @@ "@umbraco-cms/backoffice": "^16.2.0", "msw": "^2.11.3", "typescript": "^5.9.3", - "vite": "^7.1.11" + "vite": "^7.2.0" }, "msw": { "workerDirectory": [ "public" ] } -} +} \ No newline at end of file From 6e07528a77fff609d9fa33b73fcd4380c881beef Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:23:11 +0100 Subject: [PATCH 05/21] formatting --- .../components/pages/login.page.element.ts | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts index 4787e912e361..7fcdf46a5880 100644 --- a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts +++ b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts @@ -1,18 +1,28 @@ import type { UUIButtonState } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { css, type CSSResultGroup, html, nothing, when, customElement, property, queryAssignedElements, state } from '@umbraco-cms/backoffice/external/lit'; +import { + css, + type CSSResultGroup, + html, + nothing, + when, + customElement, + property, + queryAssignedElements, + state, +} from '@umbraco-cms/backoffice/external/lit'; import { UMB_AUTH_CONTEXT } from '../../contexts'; @customElement('umb-login-page') export default class UmbLoginPageElement extends UmbLitElement { - @property({type: Boolean, attribute: 'username-is-email'}) + @property({ type: Boolean, attribute: 'username-is-email' }) usernameIsEmail = false; - @queryAssignedElements({flatten: true}) + @queryAssignedElements({ flatten: true }) protected slottedElements?: HTMLFormElement[]; - @property({type: Boolean, attribute: 'allow-password-reset'}) + @property({ type: Boolean, attribute: 'allow-password-reset' }) allowPasswordReset = false; @state() @@ -92,7 +102,7 @@ export default class UmbLoginPageElement extends UmbLitElement { this.#authContext.mfaProviders = response.twoFactorProviders; } - this.dispatchEvent(new CustomEvent('umb-login-flow', {composed: true, detail: {flow: 'mfa'}})); + this.dispatchEvent(new CustomEvent('umb-login-flow', { composed: true, detail: { flow: 'mfa' } })); return; } @@ -135,11 +145,8 @@ export default class UmbLoginPageElement extends UmbLitElement {
${when( this.supportPersistLogin, - () => html` - - + () => html` + Remember me ` @@ -147,8 +154,7 @@ export default class UmbLoginPageElement extends UmbLitElement { ${when( this.allowPasswordReset, () => - html` - ` )} @@ -173,7 +179,7 @@ export default class UmbLoginPageElement extends UmbLitElement { } #handleForgottenPassword() { - this.dispatchEvent(new CustomEvent('umb-login-flow', {composed: true, detail: {flow: 'reset'}})); + this.dispatchEvent(new CustomEvent('umb-login-flow', { composed: true, detail: { flow: 'reset' } })); } static styles: CSSResultGroup = [ @@ -221,7 +227,7 @@ export default class UmbLoginPageElement extends UmbLitElement { display: inline-flex; line-height: 1; font-size: 14px; - font-family: var(--uui-font-family),sans-serif; + font-family: var(--uui-font-family), sans-serif; margin-left: auto; margin-bottom: var(--uui-size-space-3); } From 302975b0c486abed93af44149e816b32e2de43ac Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:00:59 +0100 Subject: [PATCH 06/21] fix: moves the form into the login.page.element.ts component to better control submission --- src/Umbraco.Web.UI.Login/src/auth.element.ts | 76 +++++----------- .../components/pages/login.page.element.ts | 86 +++++++++---------- 2 files changed, 62 insertions(+), 100 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index 24299fd95910..3c88a87e9b1d 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -1,4 +1,4 @@ -import { html, customElement, property, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property, ifDefined, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UUIFormValidationMessageElement, @@ -137,50 +137,6 @@ const createFormLayoutPasswordItem = ( return formLayoutItem; }; -const createForm = (elements: HTMLElement[]) => { - const styles = document.createElement('style'); - styles.innerHTML = authStyles; - const form = document.createElement('form'); - form.id = 'umb-login-form'; - form.name = 'login-form'; - form.spellcheck = false; - form.noValidate = true; - - elements.push(styles); - elements.forEach((element) => form.appendChild(element)); - - form.addEventListener('submit', (e) => { - e.preventDefault(); - - const form = e.target as HTMLFormElement; - if (!form) return; - - const formData = new FormData(form); - - const username = formData.get('username') as string; - const password = formData.get('password') as string; - - const validationMessages = Array.from(form.querySelectorAll('.input')); - validationMessages.forEach((vm) => vm.removeAttribute('aria-invalid')); - - if (!username) { - const validationMessage = validationMessages.find( - (vm) => vm.getAttribute('id') === 'username-input' - ) as UUIFormValidationMessageElement; - validationMessage?.setAttribute('aria-invalid', 'true'); - } - - if (!password) { - const validationMessage = validationMessages.find( - (vm) => vm.getAttribute('id') === 'password-input' - ) as UUIFormValidationMessageElement; - validationMessage?.setAttribute('aria-invalid', 'true'); - } - }); - - return form; -}; - @customElement('umb-auth') export default class UmbAuthElement extends UmbLitElement { /** @@ -222,7 +178,6 @@ export default class UmbAuthElement extends UmbLitElement { */ protected flow?: 'mfa' | 'reset-password' | 'invite-user'; - _form?: HTMLFormElement; _usernameLayoutItem?: UUIFormLayoutItemElement; _passwordLayoutItem?: UUIFormLayoutItemElement; _usernameInput?: HTMLInputElement; @@ -254,8 +209,6 @@ export default class UmbAuthElement extends UmbLitElement { // Wait for localization to be ready before loading the form await this.#waitForLocalization(); - - this.#initializeForm(); } async #waitForLocalization(): Promise { @@ -348,9 +301,9 @@ export default class UmbAuthElement extends UmbLitElement { this._passwordShowPasswordToggleItem ); - this._form = createForm([this._usernameLayoutItem, this._passwordLayoutItem]); + return [this._usernameLayoutItem, this._passwordLayoutItem]; - this.insertAdjacentElement('beforeend', this._form); + //this.insertAdjacentElement('beforeend', this._form); } render() { @@ -407,14 +360,25 @@ export default class UmbAuthElement extends UmbLitElement { return html` `; default: - return html` - - - `; + return this.#renderLoginPage(); } } + + #renderLoginPage() { + const elements = this.#initializeForm(); + + return html` + + + ${elements} + + `; + } } declare global { diff --git a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts index 7fcdf46a5880..95be0e20e815 100644 --- a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts +++ b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts @@ -2,7 +2,6 @@ import type { UUIButtonState } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { css, - type CSSResultGroup, html, nothing, when, @@ -10,6 +9,8 @@ import { property, queryAssignedElements, state, + createRef, + ref, } from '@umbraco-cms/backoffice/external/lit'; import { UMB_AUTH_CONTEXT } from '../../contexts'; @@ -34,7 +35,7 @@ export default class UmbLoginPageElement extends UmbLitElement { @state() supportPersistLogin = false; - #formElement?: HTMLFormElement; + private _formRef = createRef(); #authContext?: typeof UMB_AUTH_CONTEXT.TYPE; @@ -48,20 +49,18 @@ export default class UmbLoginPageElement extends UmbLitElement { } async #onSlotChanged() { - this.#formElement = this.slottedElements?.find((el) => el.id === 'umb-login-form'); - - if (!this.#formElement) return; - // We need to listen for the enter key to submit the form, because the uui-button does not support the native input fields submit event - this.#formElement.addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - this.#onSubmitClick(); - } + this.slottedElements?.forEach((input) => { + input.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this._formRef.value?.requestSubmit(); + } + }); }); - this.#formElement.onsubmit = this.#handleSubmit; } #handleSubmit = async (e: SubmitEvent) => { + debugger; e.preventDefault(); this._loginError = ''; @@ -129,10 +128,6 @@ export default class UmbLoginPageElement extends UmbLitElement { ][new Date().getDay()]; } - #onSubmitClick = () => { - this.#formElement?.requestSubmit(); - }; - render() { return html` - -
- ${when( - this.supportPersistLogin, - () => html` - - Remember me - - ` - )} - ${when( - this.allowPasswordReset, - () => - html` ` - )} -
- - - ${this.#renderErrorMessage()} + +
+ +
+ ${when( + this.supportPersistLogin, + () => html` + + Remember me + + ` + )} + ${when( + this.allowPasswordReset, + () => + html` ` + )} +
+ + + ${this.#renderErrorMessage()} +
+
`; } @@ -182,7 +180,7 @@ export default class UmbLoginPageElement extends UmbLitElement { this.dispatchEvent(new CustomEvent('umb-login-flow', { composed: true, detail: { flow: 'reset' } })); } - static styles: CSSResultGroup = [ + static readonly styles = [ css` :host { display: flex; From b87d23b447ab06ecaee4006a23388224b2f1d13b Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:42:19 +0100 Subject: [PATCH 07/21] fix: creates elements globally --- src/Umbraco.Web.UI.Login/src/auth-styles.css | 34 ++++++++--------- src/Umbraco.Web.UI.Login/src/auth.element.ts | 39 ++++++++------------ 2 files changed, 32 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/src/auth-styles.css b/src/Umbraco.Web.UI.Login/src/auth-styles.css index c709edcaf8f7..13425a50ce40 100644 --- a/src/Umbraco.Web.UI.Login/src/auth-styles.css +++ b/src/Umbraco.Web.UI.Login/src/auth-styles.css @@ -1,14 +1,19 @@ -#umb-login-form .errormessage { +.errormessage { color: var(--uui-color-invalid-standalone); display: none; margin-top: var(--uui-size-1); } -#umb-login-form [aria-invalid='true'] ~ .errormessage { +[aria-invalid='true'] ~ .errormessage { display: block; } -#umb-login-form #username-input { +uui-form-layout-item { + margin-top: var(--uui-size-space-4); + margin-bottom: var(--uui-size-space-4); +} + +#username-input { width: 100%; height: var(--input-height); box-sizing: border-box; @@ -19,21 +24,16 @@ padding: var(--uui-size-1, 3px) var(--uui-size-space-4, 9px); } -#umb-login-form uui-form-layout-item { - margin-top: var(--uui-size-space-4); - margin-bottom: var(--uui-size-space-4); -} - -#umb-login-form #username-input:focus-within { +#username-input:focus-within { border-color: var(--uui-input-border-color-focus, var(--uui-color-border-emphasis, #a1a1a1)); outline: calc(2px * var(--uui-show-focus-outline, 1)) solid var(--uui-color-focus); } -#umb-login-form #username-input:hover:not(:focus-within) { +#username-input:hover:not(:focus-within) { border-color: var(--uui-input-border-color-hover, var(--uui-color-border-standalone, #c2c2c2)); } -#umb-login-form #password-input-span button { +#password-show-toggle { color: var(--uui-color-default-standalone); display: inline-flex; justify-content: center; @@ -49,12 +49,12 @@ transition-timing-function: linear; } -#umb-login-form #password-input-span button:hover { +#password-show-toggle:hover { color: var(--uui-color-default-emphasis); cursor: pointer; } -#umb-login-form #password-input-span { +#password-input-span { display: inline-flex; width: 100%; align-items: center; @@ -70,7 +70,7 @@ padding: var(--uui-size-1, 3px) var(--uui-size-space-4, 9px); } -#umb-login-form #password-input-span input { +#password-input { flex-grow: 1; align-self: stretch; min-width: 0; @@ -80,15 +80,15 @@ outline-style: none; } -#umb-login-form #password-input-span:focus-within { +#password-input-span:focus-within { border-color: var(--uui-input-border-color-focus, var(--uui-color-border-emphasis, #a1a1a1)); outline: calc(2px * var(--uui-show-focus-outline, 1)) solid var(--uui-color-focus); } -#umb-login-form #password-input-span:hover:not(:focus-within) { +#password-input-span:hover:not(:focus-within) { border-color: var(--uui-input-border-color-hover, var(--uui-color-border-standalone, #c2c2c2)); } -#umb-login-form input::-ms-reveal { +#password-input::-ms-reveal { display: none; } diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index 3c88a87e9b1d..6b79126973ce 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -1,10 +1,6 @@ import { html, customElement, property, ifDefined, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { - UUIFormValidationMessageElement, - type InputType, - type UUIFormLayoutItemElement, -} from '@umbraco-cms/backoffice/external/uui'; +import type { InputType, UUIFormLayoutItemElement } from '@umbraco-cms/backoffice/external/uui'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UMB_AUTH_CONTEXT, UmbAuthContext } from './contexts'; @@ -209,6 +205,8 @@ export default class UmbAuthElement extends UmbLitElement { // Wait for localization to be ready before loading the form await this.#waitForLocalization(); + + this.#initializeForm(); } async #waitForLocalization(): Promise { @@ -300,10 +298,15 @@ export default class UmbAuthElement extends UmbLitElement { this._passwordInput, this._passwordShowPasswordToggleItem ); + const style = document.createElement('style'); + style.innerHTML = authStyles; + document.head.appendChild(style); - return [this._usernameLayoutItem, this._passwordLayoutItem]; + const elements = [this._usernameLayoutItem, this._passwordLayoutItem]; - //this.insertAdjacentElement('beforeend', this._form); + elements.forEach((el) => { + this.insertAdjacentElement('beforeend', el); + }); } render() { @@ -360,25 +363,13 @@ export default class UmbAuthElement extends UmbLitElement { return html` `; default: - return this.#renderLoginPage(); + return html` + + + + `; } } - - #renderLoginPage() { - const elements = this.#initializeForm(); - - return html` - - - ${elements} - - `; - } } declare global { From 25459d749ab10046b2c9d1d3b1e9fe066177dcaa Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:42:36 +0100 Subject: [PATCH 08/21] fix: adds id back to form --- .../src/components/pages/login.page.element.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts index 95be0e20e815..f707ac7b378a 100644 --- a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts +++ b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts @@ -137,7 +137,13 @@ export default class UmbLoginPageElement extends UmbLitElement { -
+
${when( From e2ff9e7d9df9acfd696c96f5a6e618fc38f67ab4 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:44:27 +0100 Subject: [PATCH 09/21] fix: no need to store references to all form elements --- src/Umbraco.Web.UI.Login/src/auth.element.ts | 49 ++++++-------------- 1 file changed, 14 insertions(+), 35 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index 6b79126973ce..3ba973de18bb 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -174,15 +174,6 @@ export default class UmbAuthElement extends UmbLitElement { */ protected flow?: 'mfa' | 'reset-password' | 'invite-user'; - _usernameLayoutItem?: UUIFormLayoutItemElement; - _passwordLayoutItem?: UUIFormLayoutItemElement; - _usernameInput?: HTMLInputElement; - _passwordInput?: HTMLInputElement; - _usernameLabel?: HTMLLabelElement; - _passwordLabel?: HTMLLabelElement; - _passwordShowPasswordToggleItem?: HTMLSpanElement; - _passwordShowPasswordToggleButton?: HTMLButtonElement; - #authContext = new UmbAuthContext(this, UMB_AUTH_CONTEXT); constructor() { @@ -234,18 +225,6 @@ export default class UmbAuthElement extends UmbLitElement { }); } - disconnectedCallback() { - super.disconnectedCallback(); - this._usernameLayoutItem?.remove(); - this._passwordLayoutItem?.remove(); - this._usernameLabel?.remove(); - this._usernameInput?.remove(); - this._passwordLabel?.remove(); - this._passwordInput?.remove(); - this._passwordShowPasswordToggleItem?.remove(); - this._passwordShowPasswordToggleButton?.remove(); - } - /** * Creates the login form and adds it to the DOM in the default slot. * This is done to avoid having to deal with the shadow DOM, which is not supported in Google Chrome for autocomplete/autofill. @@ -254,7 +233,7 @@ export default class UmbAuthElement extends UmbLitElement { * @private */ #initializeForm() { - this._usernameInput = createInput({ + const usernameInput = createInput({ id: 'username-input', type: 'text', name: 'username', @@ -263,7 +242,7 @@ export default class UmbAuthElement extends UmbLitElement { inputmode: this.usernameIsEmail ? 'email' : '', autofocus: true, }); - this._passwordInput = createInput({ + const passwordInput = createInput({ id: 'password-input', type: 'password', name: 'password', @@ -271,38 +250,38 @@ export default class UmbAuthElement extends UmbLitElement { errorId: 'password-input-error', inputmode: '', }); - this._passwordShowPasswordToggleButton = createShowPasswordToggleButton({ + const passwordShowPasswordToggleButton = createShowPasswordToggleButton({ id: 'password-show-toggle', name: 'password-show-toggle', ariaLabelShowPassword: this.localize.term('auth_showPassword'), ariaLabelHidePassword: this.localize.term('auth_hidePassword'), }); - this._passwordShowPasswordToggleItem = createShowPasswordToggleItem(this._passwordShowPasswordToggleButton); - this._usernameLabel = createLabel({ + const passwordShowPasswordToggleItem = createShowPasswordToggleItem(passwordShowPasswordToggleButton); + const usernameLabel = createLabel({ forId: 'username-input', localizeAlias: this.usernameIsEmail ? 'auth_email' : 'auth_username', localizeFallback: this.usernameIsEmail ? 'Email' : 'Username', }); - this._passwordLabel = createLabel({ + const passwordLabel = createLabel({ forId: 'password-input', localizeAlias: 'auth_password', localizeFallback: 'Password', }); - this._usernameLayoutItem = createFormLayoutItem( - this._usernameLabel, - this._usernameInput, + const usernameLayoutItem = createFormLayoutItem( + usernameLabel, + usernameInput, 'auth_requiredUsernameValidationMessage' ); - this._passwordLayoutItem = createFormLayoutPasswordItem( - this._passwordLabel, - this._passwordInput, - this._passwordShowPasswordToggleItem + const passwordLayoutItem = createFormLayoutPasswordItem( + passwordLabel, + passwordInput, + passwordShowPasswordToggleItem ); const style = document.createElement('style'); style.innerHTML = authStyles; document.head.appendChild(style); - const elements = [this._usernameLayoutItem, this._passwordLayoutItem]; + const elements = [usernameLayoutItem, passwordLayoutItem]; elements.forEach((el) => { this.insertAdjacentElement('beforeend', el); From 9acaf649fbede6588ef64695c46f7b81f5b192fa Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:41:54 +0100 Subject: [PATCH 10/21] fix: errormessage should show with password field in a span as well --- src/Umbraco.Web.UI.Login/src/auth-styles.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Login/src/auth-styles.css b/src/Umbraco.Web.UI.Login/src/auth-styles.css index 13425a50ce40..76c38bc09112 100644 --- a/src/Umbraco.Web.UI.Login/src/auth-styles.css +++ b/src/Umbraco.Web.UI.Login/src/auth-styles.css @@ -4,7 +4,7 @@ margin-top: var(--uui-size-1); } -[aria-invalid='true'] ~ .errormessage { +.errormessage.active { display: block; } From 36ba61d6ff0d335968872d033b01b77b75c0b6f5 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:42:06 +0100 Subject: [PATCH 11/21] fix: checks validity of form --- .../components/pages/login.page.element.ts | 95 +++++++++---------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts index f707ac7b378a..d38cab770f91 100644 --- a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts +++ b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts @@ -9,8 +9,6 @@ import { property, queryAssignedElements, state, - createRef, - ref, } from '@umbraco-cms/backoffice/external/lit'; import { UMB_AUTH_CONTEXT } from '../../contexts'; @@ -35,7 +33,7 @@ export default class UmbLoginPageElement extends UmbLitElement { @state() supportPersistLogin = false; - private _formRef = createRef(); + #formElement?: HTMLFormElement; #authContext?: typeof UMB_AUTH_CONTEXT.TYPE; @@ -49,18 +47,20 @@ export default class UmbLoginPageElement extends UmbLitElement { } async #onSlotChanged() { - // We need to listen for the enter key to submit the form, because the uui-button does not support the native input fields submit event - this.slottedElements?.forEach((input) => { - input.addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - this._formRef.value?.requestSubmit(); - } - }); + this.#formElement = this.slottedElements?.find((el) => el.id === 'umb-login-form'); + + if (!this.#formElement) return; + + this.#formElement.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.#onSubmitClick(); + } }); + + this.#formElement.onsubmit = this.#handleSubmit; } #handleSubmit = async (e: SubmitEvent) => { - debugger; e.preventDefault(); this._loginError = ''; @@ -70,6 +70,10 @@ export default class UmbLoginPageElement extends UmbLitElement { const form = e.target as HTMLFormElement; if (!form) return; + if (!form?.checkValidity()) { + return; + } + const formData = new FormData(form); const username = formData.get('username') as string; @@ -128,6 +132,10 @@ export default class UmbLoginPageElement extends UmbLitElement { ][new Date().getDay()]; } + #onSubmitClick = () => { + this.#formElement?.requestSubmit(); + }; + render() { return html` - - - -
- ${when( - this.supportPersistLogin, - () => html` - - Remember me - - ` - )} - ${when( - this.allowPasswordReset, - () => - html` ` - )} -
- - - ${this.#renderErrorMessage()} - -
+ +
+ ${when( + this.supportPersistLogin, + () => html` + + Remember me + + ` + )} + ${when( + this.allowPasswordReset, + () => + html` ` + )} +
+ + + ${this.#renderErrorMessage()} `; } From 053a4ab53a9b4d59df7ea790b99f838e22effed7 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:43:14 +0100 Subject: [PATCH 12/21] fix: constructs form in auth.element.ts anyway and append localization to validation and add oninput and onblur --- src/Umbraco.Web.UI.Login/src/auth.element.ts | 62 +++++++++++++++----- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index 3ba973de18bb..5c698fe91634 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -1,10 +1,10 @@ -import { html, customElement, property, ifDefined, state } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { InputType, UUIFormLayoutItemElement } from '@umbraco-cms/backoffice/external/uui'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import { UMB_AUTH_CONTEXT, UmbAuthContext } from './contexts'; -import { UmbSlimBackofficeController } from './controllers'; +import { UMB_AUTH_CONTEXT, UmbAuthContext } from './contexts/index.js'; +import { UmbSlimBackofficeController } from './controllers/index.js'; // We import the authStyles here so that we can inline it in the shadow DOM that is created outside of the UmbAuthElement. import authStyles from './auth-styles.css?inline'; @@ -50,14 +50,10 @@ const createLabel = (opts: { forId: string; localizeAlias: string; localizeFallb return label; }; -const createValidationMessage = (errorId: string, localizationKey: string) => { +const createValidationMessage = (errorId: string) => { const validationElement = document.createElement('div'); validationElement.className = 'errormessage'; validationElement.id = errorId; - const localizeElement = document.createElement('umb-localize'); - localizeElement.key = localizationKey; - validationElement.appendChild(localizeElement); - return validationElement; }; @@ -108,7 +104,13 @@ const createFormLayoutItem = (label: HTMLLabelElement, input: HTMLInputElement, formLayoutItem.appendChild(label); formLayoutItem.appendChild(input); - formLayoutItem.appendChild(createValidationMessage(errorId, localizationKey)); + + const validationMessage = createValidationMessage(errorId); + formLayoutItem.appendChild(validationMessage); + + // Bind validation + input.oninput = () => validateInput(input, validationMessage, localizationKey); + input.onblur = () => validateInput(input, validationMessage, localizationKey); return formLayoutItem; }; @@ -116,9 +118,11 @@ const createFormLayoutItem = (label: HTMLLabelElement, input: HTMLInputElement, const createFormLayoutPasswordItem = ( label: HTMLLabelElement, input: HTMLInputElement, - showPasswordToggle: HTMLSpanElement + showPasswordToggle: HTMLSpanElement, + requiredMessageKey: string ) => { const formLayoutItem = document.createElement('uui-form-layout-item') as UUIFormLayoutItemElement; + const errorId = input.getAttribute('aria-errormessage') || input.id + '-error'; formLayoutItem.appendChild(label); @@ -128,11 +132,31 @@ const createFormLayoutPasswordItem = ( span.appendChild(showPasswordToggle); formLayoutItem.appendChild(span); - formLayoutItem.appendChild(createValidationMessage('password-input-error', 'auth_requiredPasswordValidationMessage')); + const validationMessage = createValidationMessage(errorId); + formLayoutItem.appendChild(validationMessage); + + // Bind validation + input.oninput = () => validateInput(input, validationMessage, requiredMessageKey); + input.onblur = () => validateInput(input, validationMessage, requiredMessageKey); return formLayoutItem; }; +const validateInput = (input: HTMLInputElement, validationElement: HTMLElement, requiredMessage = '') => { + if (input.validity.valid) { + input.removeAttribute('aria-invalid'); + validationElement.innerHTML = ''; + validationElement.classList.remove('active'); + } else { + input.setAttribute('aria-invalid', 'true'); + const localizeElement = document.createElement('umb-localize'); + localizeElement.innerHTML = input.validationMessage; + localizeElement.key = requiredMessage; + validationElement.appendChild(localizeElement); + validationElement.classList.add('active'); + } +}; + @customElement('umb-auth') export default class UmbAuthElement extends UmbLitElement { /** @@ -275,17 +299,23 @@ export default class UmbAuthElement extends UmbLitElement { const passwordLayoutItem = createFormLayoutPasswordItem( passwordLabel, passwordInput, - passwordShowPasswordToggleItem + passwordShowPasswordToggleItem, + 'auth_requiredPasswordValidationMessage' ); const style = document.createElement('style'); style.innerHTML = authStyles; document.head.appendChild(style); - const elements = [usernameLayoutItem, passwordLayoutItem]; + const form = document.createElement('form'); + form.id = 'umb-login-form'; + form.name = 'login-form'; + form.spellcheck = false; + form.setAttribute('novalidate', ''); - elements.forEach((el) => { - this.insertAdjacentElement('beforeend', el); - }); + form.appendChild(usernameLayoutItem); + form.appendChild(passwordLayoutItem); + + this.insertAdjacentElement('beforeend', form); } render() { From 2f9ed74d4514c91d940b9ab4f5912955e4456c0c Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:43:22 +0100 Subject: [PATCH 13/21] chore: fixes import paths --- .../src/components/pages/login.page.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts index d38cab770f91..d89f434a8932 100644 --- a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts +++ b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts @@ -11,7 +11,7 @@ import { state, } from '@umbraco-cms/backoffice/external/lit'; -import { UMB_AUTH_CONTEXT } from '../../contexts'; +import { UMB_AUTH_CONTEXT } from '../../contexts/index.js'; @customElement('umb-login-page') export default class UmbLoginPageElement extends UmbLitElement { From c64b567cd52232c35a2a2371de2f3c4ed0d4052b Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:51:12 +0100 Subject: [PATCH 14/21] fix: fixes special case where ?status was not reset --- src/Umbraco.Web.UI.Login/src/auth.element.ts | 10 ++++++++++ .../src/components/back-to-login-button.element.ts | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index 5c698fe91634..90836b6df4a6 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -206,6 +206,16 @@ export default class UmbAuthElement extends UmbLitElement { (this as unknown as EventTarget).addEventListener('umb-login-flow', (e) => { if (e instanceof CustomEvent) { this.flow = e.detail.flow || undefined; + if (typeof e.detail.status !== 'undefined') { + const searchParams = new URLSearchParams(window.location.search); + if (e.detail.status === null) { + searchParams.delete('status'); + } else { + searchParams.set('status', e.detail.status); + } + const newRelativePathQuery = window.location.pathname + '?' + searchParams.toString(); + window.history.pushState(null, '', newRelativePathQuery); + } } this.requestUpdate(); }); diff --git a/src/Umbraco.Web.UI.Login/src/components/back-to-login-button.element.ts b/src/Umbraco.Web.UI.Login/src/components/back-to-login-button.element.ts index 9200baa52d4e..bef64afd1837 100644 --- a/src/Umbraco.Web.UI.Login/src/components/back-to-login-button.element.ts +++ b/src/Umbraco.Web.UI.Login/src/components/back-to-login-button.element.ts @@ -17,7 +17,7 @@ export default class UmbBackToLoginButtonElement extends UmbLitElement { } #handleClick() { - this.dispatchEvent(new CustomEvent('umb-login-flow', { composed: true, detail: { flow: 'login' } })); + this.dispatchEvent(new CustomEvent('umb-login-flow', { composed: true, detail: { flow: 'login', status: null } })); } static styles: CSSResultGroup = [ @@ -39,7 +39,7 @@ export default class UmbBackToLoginButtonElement extends UmbLitElement { display: inline-flex; line-height: 1; font-size: 14px; - font-family: var(--uui-font-family),sans-serif; + font-family: var(--uui-font-family), sans-serif; } button svg { width: 1rem; From d766374bd208efada2f73779cb7c18199c4e5646 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:51:20 +0100 Subject: [PATCH 15/21] fix: changes wording in english --- src/Umbraco.Web.UI.Login/src/localization/lang/en.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/src/localization/lang/en.ts b/src/Umbraco.Web.UI.Login/src/localization/lang/en.ts index f8f7fbc68695..246d78f4696f 100644 --- a/src/Umbraco.Web.UI.Login/src/localization/lang/en.ts +++ b/src/Umbraco.Web.UI.Login/src/localization/lang/en.ts @@ -51,9 +51,9 @@ export default { returnToLogin: 'Return to login', localLoginDisabled: 'Unfortunately, direct login is not possible. It has been disabled by a provider.', friendlyGreeting: 'Hello', - requiredEmailValidationMessage: 'Please fill out an email', - requiredUsernameValidationMessage: 'Please fill out a username', - requiredPasswordValidationMessage: 'Please fill out a password', + requiredEmailValidationMessage: 'Please fill in an email', + requiredUsernameValidationMessage: 'Please fill in a username', + requiredPasswordValidationMessage: 'Please fill in a password', showPassword: 'Show password', hidePassword: 'Hide password', }, From 4f20cbef9074374b655c59b1d1aeb3539c9462f3 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:51:58 +0100 Subject: [PATCH 16/21] fix: removes duplicate en-us keys --- .../src/localization/lang/en-us.ts | 51 ------------------- 1 file changed, 51 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/src/localization/lang/en-us.ts b/src/Umbraco.Web.UI.Login/src/localization/lang/en-us.ts index 0a096e2c79c8..40895f2513f3 100644 --- a/src/Umbraco.Web.UI.Login/src/localization/lang/en-us.ts +++ b/src/Umbraco.Web.UI.Login/src/localization/lang/en-us.ts @@ -2,57 +2,6 @@ import type { UmbLocalizationDictionary } from '@umbraco-cms/backoffice/localiza export default { auth: { - continue: 'Continue', - validate: 'Validate', - login: 'Login', - email: 'E-mail', - username: 'Username', - password: 'Password', - submit: 'Submit', - required: 'Required', - success: 'Success', - forgottenPassword: 'Forgotten password?', - forgottenPasswordInstruction: 'An email will be sent to the address specified with a link to reset your password', - requestPasswordResetConfirmation: - 'An email with password reset instructions will be sent to the specified address if it matched our records', - setPasswordConfirmation: 'Your password has been updated', - rememberMe: 'Remember me', - error: 'Error', - defaultError: 'An error occurred while processing your request.', - errorInPasswordFormat: - 'The password must be at least {0} characters long and contain at least {1} special characters.', - passwordMismatch: 'The confirmed password does not match the new password!', - passwordMinLength: 'The password must be at least {0} characters long.', - passwordIsBlank: 'The password cannot be blank.', - userFailedLogin: "Oops! We couldn't log you in. Please check your credentials and try again.", - userLockedOut: 'Your account has been locked out. Please try again later.', - receivedErrorFromServer: 'Received an error from the server', - resetCodeExpired: 'The link you have clicked on is invalid or has expired', - userInviteWelcomeMessage: - 'Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password.', - userInviteExpiredMessage: - 'Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it.', - newPassword: 'New password', - confirmNewPassword: 'Confirm password', - greeting0: 'Welcome', - greeting1: 'Welcome', - greeting2: 'Welcome', - greeting3: 'Welcome', - greeting4: 'Welcome', - greeting5: 'Welcome', - greeting6: 'Welcome', - mfaTitle: 'One last step', - mfaCodeInputHelp: 'Enter the code from your authenticator app', - mfaText: 'You have enabled 2-factor authentication and must verify your identity.', - mfaMultipleText: 'Please choose a 2-factor provider', - mfaCodeInput: 'Verification code', - mfaInvalidCode: 'Invalid code entered', - signInWith: 'Sign in with {0}', - returnToLogin: 'Return to login', - localLoginDisabled: 'Unfortunately, direct login is not possible. It has been disabled by a provider.', friendlyGreeting: 'Hi there', - requiredEmailValidationMessage: 'Please fill out an email', - requiredUsernameValidationMessage: 'Please fill out a username', - requiredPasswordValidationMessage: 'Please fill in a password', }, } satisfies UmbLocalizationDictionary; From 787d7934acfe53f8063693879f58b4ed82ddc128 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:55:30 +0100 Subject: [PATCH 17/21] feat: adds ariaLive and role attributes --- src/Umbraco.Web.UI.Login/src/auth.element.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index 90836b6df4a6..d6f1a41f61bb 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -54,6 +54,7 @@ const createValidationMessage = (errorId: string) => { const validationElement = document.createElement('div'); validationElement.className = 'errormessage'; validationElement.id = errorId; + validationElement.role = 'alert'; return validationElement; }; @@ -147,13 +148,17 @@ const validateInput = (input: HTMLInputElement, validationElement: HTMLElement, input.removeAttribute('aria-invalid'); validationElement.innerHTML = ''; validationElement.classList.remove('active'); + validationElement.ariaLive = 'off'; } else { input.setAttribute('aria-invalid', 'true'); + const localizeElement = document.createElement('umb-localize'); localizeElement.innerHTML = input.validationMessage; localizeElement.key = requiredMessage; validationElement.appendChild(localizeElement); + validationElement.classList.add('active'); + validationElement.ariaLive = 'assertive'; } }; From 85705877a8e3629cf7a01adb472f586cb07a7a4d Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:58:01 +0100 Subject: [PATCH 18/21] fix: always clears the text --- src/Umbraco.Web.UI.Login/src/auth.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index d6f1a41f61bb..ed6748b92aa1 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -144,9 +144,9 @@ const createFormLayoutPasswordItem = ( }; const validateInput = (input: HTMLInputElement, validationElement: HTMLElement, requiredMessage = '') => { + validationElement.innerHTML = ''; if (input.validity.valid) { input.removeAttribute('aria-invalid'); - validationElement.innerHTML = ''; validationElement.classList.remove('active'); validationElement.ariaLive = 'off'; } else { From 0a1eaa264287aba727766e7133604b840320b2eb Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:22:24 +0100 Subject: [PATCH 19/21] fix: username required validation should switch between username and email --- src/Umbraco.Web.UI.Login/src/auth.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index ed6748b92aa1..0a11678ea736 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -309,7 +309,7 @@ export default class UmbAuthElement extends UmbLitElement { const usernameLayoutItem = createFormLayoutItem( usernameLabel, usernameInput, - 'auth_requiredUsernameValidationMessage' + this.usernameIsEmail ? 'auth_requiredEmailValidationMessage' : 'auth_requiredUsernameValidationMessage' ); const passwordLayoutItem = createFormLayoutPasswordItem( passwordLabel, From 04f29c404f0e4153f69539d7ea238a2e7635117a Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 10 Nov 2025 09:38:26 +0000 Subject: [PATCH 20/21] package-lock.json updated on (re)install --- src/Umbraco.Web.UI.Login/package-lock.json | 170 ++++++++++++++++++--- src/Umbraco.Web.UI.Login/package.json | 54 +++---- 2 files changed, 180 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Web.UI.Login/package-lock.json b/src/Umbraco.Web.UI.Login/package-lock.json index 752b2312d173..3b59e7e12450 100644 --- a/src/Umbraco.Web.UI.Login/package-lock.json +++ b/src/Umbraco.Web.UI.Login/package-lock.json @@ -529,7 +529,6 @@ "integrity": "sha512-LSBHP2/wTF1BnaccHGX1t+0Ss+2VJQxotrLz/0+LK2z8ocuyVZXOYhfBSd7FP8sK78MDJVDBYrPCsBUvNSlH1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@hey-api/codegen-core": "^0.2.0", "@hey-api/json-schema-ref-parser": "1.2.0", @@ -644,7 +643,8 @@ "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz", "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==", "dev": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/@lit/reactive-element": { "version": "2.1.1", @@ -652,6 +652,7 @@ "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@lit-labs/ssr-dom-shim": "^1.4.0" } @@ -704,7 +705,8 @@ "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.47.1", @@ -1007,6 +1009,7 @@ "integrity": "sha512-viQ6AHRhjCYYipKK6ZepBzwZpkuMvO9yhRHeUZDvlSOAh8rvsUTSre0y74nu8QRYUt4a44lJJ6BpphJK7bEgYA==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1021,6 +1024,7 @@ "integrity": "sha512-zCce9PRuTNhadFir71luLo99HERDpGJ0EEflGm7RN8I1SnNi9gD5ooK42BOIQtejGCJqg3hTPZiYDJC2hXvckQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1035,6 +1039,7 @@ "integrity": "sha512-HHakuV4ckYCDOnBbne088FvCEP4YICw+wgPBz/V2dfpiFYQ4WzT0LPK9s7OFMCN+ROraoug+1ryN1Z1KdIgujQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1065,6 +1070,7 @@ "integrity": "sha512-GU9deB1A/Tr4FMPu71CvlcjGKwRhGYz60wQ8m4aM+ELZcVIcZRa1ebR8bExRIEWnvRztQuyRiCQzw2N0xQJ1QQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1079,6 +1085,7 @@ "integrity": "sha512-/TDDOwONl0qEUc4+B6V9NnWtSjz95eg7/8uCb8Y8iRbGvI9vT4/znRKofFxstvKmW4URu/H74/g0ywV57h0B+A==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1094,6 +1101,7 @@ "integrity": "sha512-2P2IZp1NRAE+21mRuFBiP3X2WKfZ6kUC23NJKpn8bcOamY3obYqCt0ltGPhE4eR8n8QAl2fI/3jIgjR07dC8ow==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1108,6 +1116,7 @@ "integrity": "sha512-JkDQU2ZYFOuT5mNYb8OiWGwD1HcjbtmX8tLNugQbToECmz9WvVPqJmn7V/q8VGpP81iEECz/IsyRmuf2kSD4uA==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1123,6 +1132,7 @@ "integrity": "sha512-KOiMZc3PwJS3hR0nSq5d0TJi2jkNZkLZElcT6pCEnhRHzPH6dRMu9GM5Jj798ZRUy0T9UFcKJalFZaDxnmRnpg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1138,6 +1148,7 @@ "integrity": "sha512-d6uStdNKi8kjPlHAyO59M6KGWATNwhLCD7dng0NXfwGndc22fthzIk/6j9F6ltQx30huy5qQram6j3JXwNACoA==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1152,6 +1163,7 @@ "integrity": "sha512-KSzL8WZV3pjJG9ke4RaU70+B5UlYR2S6olNt5UCAawM+fi11mobVztiBoC19xtpSVqIXC1AmXOqUgnuSvmE4ZA==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1166,6 +1178,7 @@ "integrity": "sha512-m6YR1gkkauIDo3PRl0gP+7Oc4n5OqDzcjVh6LvWREmZP8nmi94hfseYbqOXUb6RPHIc0JKF02eiRifT4MSd2nw==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1181,6 +1194,7 @@ "integrity": "sha512-mT6baqOhs/NakgrAeDeed194E/ZJFGL692H0C7f1N7WDRaWxUu2oR0LrnRqSH5OyPjELkzu6nQnNy0+0tFGHHg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1211,6 +1225,7 @@ "integrity": "sha512-pOs6oU4LyGO89IrYE4jbE8ZYsPwMMIiKkYfXcfeD9NtpGNBnjeVXXF5I9ndY2ANrCAgC8k58C3/powDRf0T2yA==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1244,6 +1259,7 @@ "integrity": "sha512-quOXckC73Luc3x+Dcm88YAEBW+Crh3x5uvtQOQtn2GEG91AshrvbnhGRiYnfvEN7UhWIS+FYI5liHFcRKSUKrQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1258,6 +1274,7 @@ "integrity": "sha512-UHKNRxq6TBnXMGFSq91knD6QaHsyyOwLOsXMzupmKM5Su0s+CRXEjfav3qKlbb9e4m7D7S/a0aPm8nC9KIXNhQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1272,6 +1289,7 @@ "integrity": "sha512-UezvM9VDRAVJlX1tykgHWSD1g3MKfVMWWZ+Tg+PE4+kizOwoYkRWznVPgCAxjmyHajxpCKRXgqTZkOxjJ9Kjzg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1302,6 +1320,7 @@ "integrity": "sha512-CkoRH+pAi6MgdCh7K0cVZl4N2uR4pZdabXAnFSoLZRSg6imLvEUmWHfSi1dl3Z7JOvd3a4yZ4NxerQn5MWbJ7g==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1407,6 +1426,7 @@ "integrity": "sha512-p2n8WVMd/2vckdJlol24acaTDIZAhI7qle5cM75bn01sOEZoFlSw6SwINOULrUCzNJsYb43qrLEibZb4j2LeQw==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1436,6 +1456,7 @@ "integrity": "sha512-t9Nc/UkrbCfnSHEUi1gvUQ2ZPzvfdYFT5TExoV2DTiUCkhG6+mecT5bTVFGW3QkPmbToL+nFhGn4ZRMDD0SP3Q==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1560,7 +1581,8 @@ "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/markdown-it": { "version": "14.1.2", @@ -1568,6 +1590,7 @@ "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -1578,7 +1601,8 @@ "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/statuses": { "version": "2.0.5", @@ -1592,7 +1616,8 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@umbraco-cms/backoffice": { "version": "16.2.0", @@ -1734,6 +1759,7 @@ "integrity": "sha512-O0807+bWVWV/rsFihFVKSOkg9wBtLXKCszE5+eZk2KmONm93BFhIAE35rp7eD6X2SuJMHwYzInIxMIMjHzdpUQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-button-group": "1.15.0" @@ -1745,6 +1771,7 @@ "integrity": "sha512-eEX83zwRN3tCiHScKcmkROWAfLu3TgFI9SntlbyxiuSSYfhJxWSZXOf6lVvQ/1CyvKq8XqSbBnN3VKXgcaKOpg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -1755,6 +1782,7 @@ "integrity": "sha512-CGYAFAHgNoQK2UTupP7pO0mwP6t9/Ms6WZ0gIC40a+kPjrGtaDWU52hiPsuXrUcR6WjJwZ5WRrJHOboRpdmM0g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-avatar": "1.15.0", "@umbraco-ui/uui-base": "1.15.0" @@ -1766,6 +1794,7 @@ "integrity": "sha512-9aGmhRvey98kcR7wfsnno0BNftIRwJ0r2lCH6cNK2lhe69enYm0MWjp+4uutnlEWWskTLEI344BOqmqOHH1NPA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -1776,6 +1805,7 @@ "integrity": "sha512-0vtKmjzUOn/tIUHNrsx7aZpy3eq9aRKqV9kkJTrhH92S4WcMy+cOB1iw9t3Fe3xlBPuL3JpszwuxMTIuIqJTgQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "lit": ">=2.8.0" } @@ -1786,6 +1816,7 @@ "integrity": "sha512-LkYX+p44mFVdvlZSliP5ClMcyHoOIVLWI3WVkaMLQdNi2LO9bbfaTneLzu4ruW6v0iF+kLsznr3bl73VHk7oEg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -1796,6 +1827,7 @@ "integrity": "sha512-MhSNcKsVNymD/yt3NFXujuaQmAqMqj5S+CBoDHEk88H7Id9NMw9RStZFJ37mI2CxHWkeHDotNVgOhSBiHJNJnw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-css": "1.15.0" @@ -1807,6 +1839,7 @@ "integrity": "sha512-TaUY+hNB0VIwv9SBi9fDjIFRtrmmkcT7hlhLCJLUVfQ7jJlGLPISAAdypSplNeCPthYvP1cJQ9m28OzscXHZxQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -1817,6 +1850,7 @@ "integrity": "sha512-3Oaqj6Yta/Q/Ndme20YA1XbHdBBL71iNhpqREfTHli2YV4TEcgIiNy0s2op2oPhKjIEQPEfitU2BrruYEEWa7Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-icon-registry-essential": "1.15.0" @@ -1828,6 +1862,7 @@ "integrity": "sha512-MAaJzpwVnlyGJNvLv6qIwrYsI5SaXXiVKgVi47I8+x//QmnArmetCN04766gGzmAb0m2uuC3ht0BXMDv05pxvw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-button": "1.15.0" @@ -1839,6 +1874,7 @@ "integrity": "sha512-YPEnubKNbKmw04eWRH24/3Uxu+zhtLPeJoaT6ykPCyjr/EKc82rSTvn8fwQuP41UokQrXOac2pKn7SncyoST1Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -1849,6 +1885,7 @@ "integrity": "sha512-nLJZ6P5eK1rYgqjP5zCxbZp8g4WJ23RnUZQ49o7QpU/7zoPOK72/fuM3Ky00Iapixm/kAD6dYHO/P+GtNz8/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -1859,6 +1896,7 @@ "integrity": "sha512-pNjpk2iIdSsmTtDdBsWaEr8JX0RcWbl8yKGaqLvo/S7d3bly5z+FjcsgGnX1i1GHo7dqmgVJfbdvN9V1jgn+FA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-checkbox": "1.15.0" @@ -1870,6 +1908,7 @@ "integrity": "sha512-cWag+D0XrogYZesAN8NMPQCCuU7L7uZ4Xz8dmirKQk1gjMrFDC4vYPZRQ/5O3ETTFupfDipVKimgRDsmarbLSQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-card": "1.15.0" @@ -1881,6 +1920,7 @@ "integrity": "sha512-DZ6JYNvGb5wVkhhLShENMm+Y6kTpz37YrApQTJVUUgPXhIABO2CDCnqgpH5tkQX73s9jjVB3Ca7SeivuYv8G9A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-card": "1.15.0", @@ -1893,6 +1933,7 @@ "integrity": "sha512-EzYebWCzR0wHY902NmAcTRSVSscYac3QntCz+xwSulrhzfy4copeOd1qE+Lz7FjHs+ho0IBPZol8sF4W6rK8FQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-card": "1.15.0", @@ -1906,6 +1947,7 @@ "integrity": "sha512-oo7gCs3RGJ4ujFs+LpG9I1DS/XSNkz9gaqvp4BkaR0kBXzw5f2SLLGhA9S3M6M+OPlsXuuJNKlTV1tn2+LF6Ng==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-avatar": "1.15.0", "@umbraco-ui/uui-base": "1.15.0", @@ -1918,6 +1960,7 @@ "integrity": "sha512-cnKP5GeaI028hGabVCki1kPqAVSekFeP7QEwu7lncA+dcX8uvg+ffV6kW9FV0emOhI5Tmxvh8o+UDKlLs28q3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -1928,6 +1971,7 @@ "integrity": "sha512-vPkgrFAPDMvJdJTADIWNj48D8gJWD3dBotucUghg/wHhvJv8h/2MvnwMUNnnSB1REHbanl7hJBVvKcNkoil0gA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-boolean-input": "1.15.0", @@ -1940,6 +1984,7 @@ "integrity": "sha512-k6u//b+s6UYmzKYMizIf2MRGD4kFy1qWdSk1GnIeDdiQxABJuBZEtkACIe66j+lxnonFvZ/assbLbhRiu15ZCw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "colord": "^2.9.3" @@ -1951,6 +1996,7 @@ "integrity": "sha512-I4KGyzZZazjeifcavHp7qnMbP0Jh0dM+gzZhV+YtdPR2JT0o7y6stkbY0f+dOln0K6Bu6BQLV0HLHl/1f/1NDg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-popover-container": "1.15.0", @@ -1963,6 +2009,7 @@ "integrity": "sha512-lpT9kapypGkTelG9COSk169VKs0MSiKweX8menDDn0p6I4RfKQBy0N27HecCcf1RqPsCnTbP3lPr5DJy00KdzA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -1973,6 +2020,7 @@ "integrity": "sha512-1AI0QMr046fKc8xZ4aBO7FDwvggsS9plIpY0W4AGrqQxqGUR2u/mTU49+8xMtboaFOen5RQpJ65DN9hAgeNZ+w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-icon-registry-essential": "1.15.0", @@ -1985,6 +2033,7 @@ "integrity": "sha512-UzlgWdsVHyCM/znFThrfA4A/S/K/R9Nc2KyRYiyy2xgBoP7x2vJ5Rn4mnR02W4bhI3gNgCJ2fqhmyiW4dxyk0Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-color-swatch": "1.15.0" @@ -1996,6 +2045,7 @@ "integrity": "sha512-CKslvVRCKCReMr/ZZh4wc3TKJNvFjKVm/hSIvFqCIoJuSKfC4XuLU9SK9FL1s42NUAUmccSD3hATZJZ9VXqY+Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-button": "1.15.0", @@ -2012,6 +2062,7 @@ "integrity": "sha512-e8IhqU9AC5pOqXuzPzI+BDsW97Ac0u1GU/5MIJqRcBZ+ZttPcH4fsm4u8siCHbK1khCG0Vzo7HiKPZ0IuuOslg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2033,6 +2084,7 @@ "integrity": "sha512-iVsrVVnvBrCCT9uJhyBE7b1kXwWUUKDmimhs/TyF1SFjxWP/U0Z99QqqI1pawdad+BuK3oVCmxYOdaReWDQXkQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-css": "1.15.0" @@ -2044,6 +2096,7 @@ "integrity": "sha512-JdDRIzSGGDnvVqXSIhc+5rDXMdYMO+Hd7s2hqLp+iRSn8IHISN/qT1nfFVO9LMbLdcApanl3JJ4Rru9LN4Q3HA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2054,6 +2107,7 @@ "integrity": "sha512-MhJRkVdDQWKEBvemNRD4bZCuIS0JUll1nNoPK7scA+e6vDmbv25vqPHNXGE/sIpVkChY/L+v+twokzlHn57XMw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-symbol-file-dropzone": "1.15.0" @@ -2065,6 +2119,7 @@ "integrity": "sha512-AHKIdYLC0ga4Wgr68xtW/gG3NDqn+QhD2aus0l2n4lBoq6OAQ5aZiPwD9i1fCD7dgyjKQ6Ov9PJSaqRYQkOlNA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-symbol-file": "1.15.0", @@ -2078,6 +2133,7 @@ "integrity": "sha512-4u9ZryfVBunpb0IL0+TevytrISA6S1+AajiK/PUk0JMJfqMuQMjmpnNPdtYRNVgFFIcQFQKipjT/mrHbDVawxw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2088,6 +2144,7 @@ "integrity": "sha512-fiWGeQpREnl6k+6VNHz9ixNdEmOoFNm7qsgdIYJ1jCDXBGME1mjxJOr2Eq7UWJuzQM8BeyQEcXq5SVIOv21PRw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-form-validation-message": "1.15.0" @@ -2099,6 +2156,7 @@ "integrity": "sha512-RYfwmjPkY0KumjaalLW8gkasW25Mj87YFAzJn7mAYiZigURape9RqGpvrBfwcMmGj3W2/uVuHxpAHrvweQOt4A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2109,6 +2167,7 @@ "integrity": "sha512-e8/W6gu6kwrodH0f0U70LR5rHQhiZGq3NqLqikAQ1rvmwitXUqtkVXIhkGxSf7M6yPhpmoi2qEEZDQH9cvCE5A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2119,6 +2178,7 @@ "integrity": "sha512-nIdzCqoABeRVG6jW045ok649MiAhm5zPdfuMKc1a+TNw9xkKj+vnA1YcjaBN502+AekMhhwnqgj9mLL+mC2VPQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-icon": "1.15.0" @@ -2130,6 +2190,7 @@ "integrity": "sha512-llHFVMlV3Uyg2fHiNt1qfDgRhLthD37uQD2FzlQb0GEYjp+4dE8Jyc/eZW2mqABPweUJACVwbrwBUVrCeQJ1OQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-icon-registry": "1.15.0" @@ -2141,6 +2202,7 @@ "integrity": "sha512-vPc4I/kkQM9RWfHI0F/OQhoTu+KefplbQp0JEQ4gfr6MwxIm6bBTEuw8T5K9t1DQs8EZ7yeLEsSh65FDPepRtg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2151,6 +2213,7 @@ "integrity": "sha512-VVn2FMsflvEWd6fOX0HQ3JaUh7haznqSqCLTSTOduh/H3jE+dVYCW6YC5uTsxArmOwsSBYSfBQNetW23eJim3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-action-bar": "1.15.0", "@umbraco-ui/uui-base": "1.15.0", @@ -2166,6 +2229,7 @@ "integrity": "sha512-AFyVYNeExHXe10b3/5/BLZOmMKyMxzftsO0HKbaQQuxrxL2SCHsQJRUpxSY+/0vAl2JbNdmrk0HTsP1O4Y9zig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-button": "1.15.0", @@ -2179,6 +2243,7 @@ "integrity": "sha512-Pe8lNdHz/6IfbQyWEi0o+pKJ6/zunQ2b8HARCU0a9HFXRDk+XsAuBsn79zQXZl5MvseAUQrnouLwPHpdtMbeMg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-icon-registry-essential": "1.15.0", @@ -2191,6 +2256,7 @@ "integrity": "sha512-8Q/G5Lg6949BbMHQ1BhZ9UpoJjOQ19w1tl2y0d/rP3w/mKnTQaBSf+MQmA/6kQ/Unb2wHXJANr4pAGpUklOg6A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2201,6 +2267,7 @@ "integrity": "sha512-fnmRl+RGUROERvt+Jw0WiW3Btlddg0Xka6F+gR95gy5gr/v8s34uf1/bbPD3hWUXZPukLmxeMjbzyuqMrO8rpQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2211,6 +2278,7 @@ "integrity": "sha512-HQ2zCp2kz45GWQ3wV153ytuYD2KcdeAA5RRUVrN0Zn3GQB3wfG7xMkQQNRAOWMUdnfqmdQHeK+COO7NaET3VBA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2221,6 +2289,7 @@ "integrity": "sha512-4eMeerunFc5yZsJIwpHADn8oGcu0Nn36oyKbSd0qC0mNmmN2i8UOF9w4O+lndd2L0Mhv23FGvBRo7mb5EAvWlg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2231,6 +2300,7 @@ "integrity": "sha512-4rG8UHvyS2qvsjQYEmYjKX01SRwfk60oH8SSSx8r3z2BM62dCOa+4SBhLxqiBciC/u8FtN8X20MIGE0+eMdtoA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2241,6 +2311,7 @@ "integrity": "sha512-BOebCMB/p4TaK4kJYrYgimC6SSGBHN4y1MytK3tyvObbuj3gVqkbwHW5CZrhK4jMaywRgGq96OsuaGfc52HFog==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-loader-bar": "1.15.0", @@ -2253,6 +2324,7 @@ "integrity": "sha512-EDz1Qx+mTXNvOu565IculPCyuuFHwBon2wYnfWDBMoHJ5+P54jBHzg2U/8fUVse7xKPvU21hF4JneAvycNIiGQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2263,6 +2335,7 @@ "integrity": "sha512-sPVs1bApKupNd2JcSMwFS1060Y++Fti1ybJrafcLh1+h4IjmLDIRTHTTL8C+kei5G2Oi3+Z7vGpLu7lrTAmRlw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-button": "1.15.0", @@ -2275,6 +2348,7 @@ "integrity": "sha512-VCHVvO0fd5eL5UvB/RPL/K68UhOgsIpuyr+aXLblaYT/6at2LNosUxR4eRW2r0WOQzOiyE+Nu69pExBKyfT8bw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2285,6 +2359,7 @@ "integrity": "sha512-54M4G0ru8j5ltPAdDGIxogdmos33hxeQeusI/uMFxo2yqHHyMHRi95vvCdcwFmGlEdFd2rsnxZKfNMUKM99GKQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2295,6 +2370,7 @@ "integrity": "sha512-vtGUwHaG4EDLQERkwym51OasoWLj30LQLhcCCYXDJtTL1dG2nIKScEtlSUiVh5qRsI+V+kaBYPGD4TFD5o43tQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2305,6 +2381,7 @@ "integrity": "sha512-5TUF/iWUzbVXvBs9Z273q6s9yLbns8itTiFHCITw5w5fZzDn8R6O5hrOW7tV79kCxAnBSOAVP8v1JhGTwXw19Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2315,6 +2392,7 @@ "integrity": "sha512-HMHVdBoB1O39rojofezee2aXGv6CMn7dUFvNefdF9HxmNrIcpFBYXSL8aBt5QJeziFQMwbCtqyY21aUag0nzfw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2325,6 +2403,7 @@ "integrity": "sha512-w7FZIe5mtsgvsf6hOH5mHKDBzg9Rd/+viyk/xNVs1NeZBn1nWEIHZs0R7YMpv+QxulklhAOpBcbGoUTB8xE+vA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2335,6 +2414,7 @@ "integrity": "sha512-UT65bpUmRlEgVuvn2RlTZ5l2WDF82jH1t8g+6HV6OJctpqTKlOfPkQmd6AluESPhHFEwwTydS/M7x+X3Adkdtw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2345,6 +2425,7 @@ "integrity": "sha512-ybDqIt1cXd7AiZLZsDrSHCMp2zM8I+0lmN599b3NROjm59SZXIvpbY1TS1gJ45htgsc18x2y+S4laInYu2dGUg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-icon": "1.15.0", @@ -2357,6 +2438,7 @@ "integrity": "sha512-59s16558ySCX7b9IT/Sorq0fdFeCRENSTa7DIkQUrvVPaFWqKFz9jCYFEqDnH11jZHGsNiYh5YCmWlF/VNbwJQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-ref-node": "1.15.0" @@ -2368,6 +2450,7 @@ "integrity": "sha512-AWPZPkFGcAkRx4j6JnOi2r3EJxnvZUXfhOWNyWB2/dFRckatPH56+lVkqV+fRC+81emKBSQWkx2NphFzLEMr0A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-ref-node": "1.15.0" @@ -2379,6 +2462,7 @@ "integrity": "sha512-knIIbbfoWtepOvyC54dCo3xF0Vuap6i5uMQPd+wITCmg56a+yiJFuke+dyzatOIeXzgspLgFUngwQZEj5mJnWA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-ref-node": "1.15.0" @@ -2390,6 +2474,7 @@ "integrity": "sha512-pXvL523m2JR3P8OO+E1AE4YAaYhJLc519CtjNXSuctNIk1yWvwxBu8VozLIQV+xrOXGz+SiXwDkoaRPwjTQKtg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-ref-node": "1.15.0" @@ -2401,6 +2486,7 @@ "integrity": "sha512-bQWfZPKJyAf2O/YvOD6fVSSpKaYZMBsrEGT+ydLPv3BNJroYHS8+NEbulZxExWztNApTcs6Vo5T19AUz+vsnLQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-ref-node": "1.15.0" @@ -2412,6 +2498,7 @@ "integrity": "sha512-4GpRzhGedMwjqW1Wk7AvgakNCc6S1edYpHWeO6cfmryIm0hvnCfkU132lzLmB+Ag2QIOI8p4Ak0OQHYWd+XZHw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-ref-node": "1.15.0" @@ -2423,6 +2510,7 @@ "integrity": "sha512-L4qM6GPDqu0/9B2OVb3EljZT3zYxbwp6uOz1nfVYpGAWBxd6INOtNbn8WYdZLq6qqa8NR6qK+su2554nInvQGA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2433,6 +2521,7 @@ "integrity": "sha512-yRx+TlXBB05jM8ShThRooFgCS5nSN7eAAnpttZgBWqY3sccIVy2Knbkz3kXLJE6ld+bO5nUXhsZBZ34MEHkiog==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2443,6 +2532,7 @@ "integrity": "sha512-+OAzOutyUB2WCI+e5aFRoUNsFFuc/hUXnpIjx4P1moOYiggc/NxjaTHz5mxbmkC11yyS+0vpl8lVSZglkLCH5w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2453,6 +2543,7 @@ "integrity": "sha512-6y9rpFfhtuWMnaAamlzrB5Q12dsZ8dprmQaGtKr+g97PTNRPC3/dc5sdROam8VMDAhL9MkfBAZBoS6yAoJsPcQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2463,6 +2554,7 @@ "integrity": "sha512-F0BueWxu6J5P7xyzkp1c/eFZJjStsw65hB3bNEmWBOqkm/jbBKg9+Xs99tov+VwCHYOt8T+DuEDkkKmdxVAxyQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2473,6 +2565,7 @@ "integrity": "sha512-D5DottbukIFxL+YTVEMujHPdqB8Hhw02TKpegfDQb8UGSPC5pCQw4O212TSuyTalKb598niNmCzcjEG5TWNqww==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2483,6 +2576,7 @@ "integrity": "sha512-JLcEVnJqv2LL8VtscPCtPKda9ywWzV4vd0XODHLE3iI1cgHeNwMBhxqgkah0ULuw5w2Hrq8gwQ7/DuPHMSIFxw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2493,6 +2587,7 @@ "integrity": "sha512-CPsr1K5IkdxBgID+xoIcgbumm/z0q+Z/1NPxTO40EL7kx3KOLQ8vwLdOTSW1cTj90JFA9+XuRtOpmMEY0XjICg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2503,6 +2598,7 @@ "integrity": "sha512-5QyDNFjiBeuPgalT9zwPMP220zJUHPpbPvCohWCFLn/2JJsa6IjSMtsAcqxI154ZJ9vYX7vYiYUn8tJTY8CHpA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2513,6 +2609,7 @@ "integrity": "sha512-BQq7BwZ7nCcgKE5tMhG6OVYTrrMEIXpx8kQKec/ULgVfs0/Ws6qeH9u4rGVK/yHU8gecd6DSeUczjjq2iS4djA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2523,6 +2620,7 @@ "integrity": "sha512-5Akw8T0SV2OrwvPk1JSeFr1clvHE4N0DwceSU9bn9f6gLIGGRxvniJAclQDRI/Woe3hm8waMy6cC2fXfSdc6lg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2533,6 +2631,7 @@ "integrity": "sha512-AnPp0QJeI70ucX8ludr3qaFmlxjKZUarX10DI8ieIB8VJiQZo0TjoPcPdSGmZupaPBLiszlpb3rKzGkZhXEIHg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2543,6 +2642,7 @@ "integrity": "sha512-oS0eA5Z8+s+5o2ks3WCED5VGP8AunRLyuB2y7kVdRUfhCfck7B9v83zNfxPVoGoVsTDLtAQM1S4P8SHwNRmk7g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-button": "1.15.0", @@ -2556,6 +2656,7 @@ "integrity": "sha512-PgyZvAiOZmXmiRW4UhfD6Tybx3ft755aKAVqT8ELpskLSvVr1oz4uTI6+QxoeQ1AkrHovenvIdBX+Iwi91SheQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2566,6 +2667,7 @@ "integrity": "sha512-tk/RVzCxs+KPSJ+qH2Xlr9RYxcdrSNulDKk5sBCQR0A9nwOffa15SGreSMKWgq+gYOVYChHBg/WxLWLq3d7Rlg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2576,6 +2678,7 @@ "integrity": "sha512-nz+snpjPFE+ftH5R/ekgZYy9ofGAf51yQYjWCtBwkrQ6D1dIBYA6kynZFdqIefrRwJJ5zHpe25BcS/AyRPc/9Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-button": "1.15.0", @@ -2590,6 +2693,7 @@ "integrity": "sha512-fd5d0DU/x2+u15rP0wrjw29M0oqsDFmnAfbPEdgQoPV+hvq9/SLhxJtzx10ZSNXoyuO9sTK50Q7nYsqOvGCzqg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-toast-notification": "1.15.0" @@ -2601,6 +2705,7 @@ "integrity": "sha512-uf/e/dVN6kqX76vcawiQM3w1nMHa8A+ZTtNwxtmAZi8bNPwjXLNaqKfeSp2thTByCIzFz7imnft56QtYLbksOA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-css": "1.15.0" @@ -2612,6 +2717,7 @@ "integrity": "sha512-WLooENcxuAobbXxN1W2uKGh/cN9k0f3cRmDDtCZdgjeheGlBYWatkc5HQte7zchXHUi0xTrsvBCBa9CsLKN/3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0", "@umbraco-ui/uui-boolean-input": "1.15.0" @@ -2623,6 +2729,7 @@ "integrity": "sha512-vn3dbpYGekAqG944Vkwd0ILQRtTaZtL1BVdsge2UsU8sOsEKwv5YzQal4b+o8yu8nb4vZbWHZ2zRmnpnPgPmjg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@umbraco-ui/uui-base": "1.15.0" } @@ -2848,7 +2955,8 @@ "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/commander": { "version": "13.0.0", @@ -2892,7 +3000,8 @@ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/default-browser": { "version": "5.2.1", @@ -3007,6 +3116,7 @@ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=0.12" }, @@ -3071,6 +3181,7 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -3282,6 +3393,7 @@ "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "uc.micro": "^2.0.0" } @@ -3291,7 +3403,8 @@ "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz", "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lit": { "version": "3.3.1", @@ -3312,6 +3425,7 @@ "integrity": "sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@lit-labs/ssr-dom-shim": "^1.4.0", "@lit/reactive-element": "^2.1.0", @@ -3324,6 +3438,7 @@ "integrity": "sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@types/trusted-types": "^2.0.2" } @@ -3341,6 +3456,7 @@ "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", @@ -3372,7 +3488,8 @@ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/minimist": { "version": "1.2.8", @@ -3532,7 +3649,8 @@ "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/outvariant": { "version": "1.4.3", @@ -3575,7 +3693,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3630,6 +3747,7 @@ "integrity": "sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prosemirror-transform": "^1.0.0" } @@ -3640,6 +3758,7 @@ "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prosemirror-state": "^1.0.0" } @@ -3650,6 +3769,7 @@ "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", @@ -3662,6 +3782,7 @@ "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0", @@ -3674,6 +3795,7 @@ "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prosemirror-keymap": "^1.0.0", "prosemirror-model": "^1.0.0", @@ -3687,6 +3809,7 @@ "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prosemirror-state": "^1.2.2", "prosemirror-transform": "^1.0.0", @@ -3700,6 +3823,7 @@ "integrity": "sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.0.0" @@ -3711,6 +3835,7 @@ "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prosemirror-state": "^1.0.0", "w3c-keyname": "^2.2.0" @@ -3722,6 +3847,7 @@ "integrity": "sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/markdown-it": "^14.0.0", "markdown-it": "^14.0.0", @@ -3734,6 +3860,7 @@ "integrity": "sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "crelt": "^1.0.0", "prosemirror-commands": "^1.0.0", @@ -3758,6 +3885,7 @@ "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prosemirror-model": "^1.25.0" } @@ -3768,6 +3896,7 @@ "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", @@ -3793,6 +3922,7 @@ "integrity": "sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prosemirror-keymap": "^1.2.2", "prosemirror-model": "^1.25.0", @@ -3807,6 +3937,7 @@ "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@remirror/core-constants": "3.0.0", "escape-string-regexp": "^4.0.0" @@ -3823,6 +3954,7 @@ "integrity": "sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prosemirror-model": "^1.21.0" } @@ -3846,6 +3978,7 @@ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -3937,7 +4070,8 @@ "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/run-applescript": { "version": "7.0.0", @@ -4116,7 +4250,8 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/type-fest": { "version": "4.34.1", @@ -4137,7 +4272,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4151,7 +4285,8 @@ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/uglify-js": { "version": "3.19.3", @@ -4272,7 +4407,8 @@ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/wordwrap": { "version": "1.0.0", diff --git a/src/Umbraco.Web.UI.Login/package.json b/src/Umbraco.Web.UI.Login/package.json index a2ebccd24d39..47809d0d1496 100644 --- a/src/Umbraco.Web.UI.Login/package.json +++ b/src/Umbraco.Web.UI.Login/package.json @@ -1,28 +1,28 @@ { - "name": "login", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "watch": "tsc && vite build --watch", - "preview": "vite preview", - "generate:server-api": "openapi-ts" - }, - "engines": { - "node": ">=22", - "npm": ">=10.9" - }, - "devDependencies": { - "@hey-api/openapi-ts": "^0.85.0", - "@umbraco-cms/backoffice": "^16.2.0", - "msw": "^2.11.3", - "typescript": "^5.9.3", - "vite": "^7.2.0" - }, - "msw": { - "workerDirectory": [ - "public" - ] - } -} \ No newline at end of file + "name": "login", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "watch": "tsc && vite build --watch", + "preview": "vite preview", + "generate:server-api": "openapi-ts" + }, + "engines": { + "node": ">=22", + "npm": ">=10.9" + }, + "devDependencies": { + "@hey-api/openapi-ts": "^0.85.0", + "@umbraco-cms/backoffice": "^16.2.0", + "msw": "^2.11.3", + "typescript": "^5.9.3", + "vite": "^7.2.0" + }, + "msw": { + "workerDirectory": [ + "public" + ] + } +} From 77a334418ce728b186b9d0645fbd9cad7ba179fd Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 10 Nov 2025 09:39:13 +0000 Subject: [PATCH 21/21] Renamed SVG eye icon filenames to be conventional and kebab-cased. --- .../src/assets/{closedEye.svg => eye-closed.svg} | 0 .../src/assets/{openEye.svg => eye-open.svg} | 0 src/Umbraco.Web.UI.Login/src/auth.element.ts | 10 +++++----- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/Umbraco.Web.UI.Login/src/assets/{closedEye.svg => eye-closed.svg} (100%) rename src/Umbraco.Web.UI.Login/src/assets/{openEye.svg => eye-open.svg} (100%) diff --git a/src/Umbraco.Web.UI.Login/src/assets/closedEye.svg b/src/Umbraco.Web.UI.Login/src/assets/eye-closed.svg similarity index 100% rename from src/Umbraco.Web.UI.Login/src/assets/closedEye.svg rename to src/Umbraco.Web.UI.Login/src/assets/eye-closed.svg diff --git a/src/Umbraco.Web.UI.Login/src/assets/openEye.svg b/src/Umbraco.Web.UI.Login/src/assets/eye-open.svg similarity index 100% rename from src/Umbraco.Web.UI.Login/src/assets/openEye.svg rename to src/Umbraco.Web.UI.Login/src/assets/eye-open.svg diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index 0a11678ea736..a3ba757cb025 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -10,8 +10,8 @@ import { UmbSlimBackofficeController } from './controllers/index.js'; import authStyles from './auth-styles.css?inline'; // Import the SVG files -import openEyeSVG from './assets/openEye.svg?raw'; -import closedEyeSVG from './assets/closedEye.svg?raw'; +import svgEyeOpen from './assets/eye-open.svg?raw'; +import svgEyeClosed from './assets/eye-closed.svg?raw'; // Import the main bundle import { extensions } from './umbraco-package.js'; @@ -70,7 +70,7 @@ const createShowPasswordToggleButton = (opts: { button.name = opts.name; button.type = 'button'; - button.innerHTML = openEyeSVG; + button.innerHTML = svgEyeOpen; button.onclick = () => { const passwordInput = document.getElementById('password-input') as HTMLInputElement; @@ -78,11 +78,11 @@ const createShowPasswordToggleButton = (opts: { if (passwordInput.type === 'password') { passwordInput.type = 'text'; button.ariaLabel = opts.ariaLabelHidePassword; - button.innerHTML = closedEyeSVG; + button.innerHTML = svgEyeClosed; } else { passwordInput.type = 'password'; button.ariaLabel = opts.ariaLabelShowPassword; - button.innerHTML = openEyeSVG; + button.innerHTML = svgEyeOpen; } passwordInput.focus();