diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts index 368ad64743..21185774e0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts @@ -25,6 +25,7 @@ export * from './input-radio-button-list/index.js'; export * from './input-slider/index.js'; export * from './input-toggle/index.js'; export * from './input-upload-field/index.js'; +export * from './input-with-alias/input-with-alias.element.js'; export * from './multiple-color-picker-input/index.js'; export * from './multiple-text-string-input/index.js'; export * from './popover-layout/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-with-alias/input-with-alias.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-with-alias/input-with-alias.element.ts new file mode 100644 index 0000000000..8db01ae06d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-with-alias/input-with-alias.element.ts @@ -0,0 +1,117 @@ +import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { type UUIInputElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; +import { generateAlias } from '@umbraco-cms/backoffice/utils'; + +@customElement('umb-input-with-alias') +export class UmbInputWithAliasElement extends UmbFormControlMixin(UmbLitElement) { + @property({ type: String }) + label: string = ''; + + @property({ type: String, reflect: false }) + alias?: string; + + @state() + private _aliasLocked = true; + + firstUpdated() { + this.shadowRoot?.querySelectorAll('uui-input').forEach((x) => this.addFormControlElement(x)); + } + + focus() { + return this.shadowRoot?.querySelector('uui-input')?.focus(); + } + + #onNameChange(event: UUIInputEvent) { + if (event instanceof UUIInputEvent) { + const target = event.composedPath()[0] as UUIInputElement; + + if (typeof target?.value === 'string') { + const oldName = this.value; + const oldAlias = this.alias ?? ''; + this.value = event.target.value.toString(); + if (this._aliasLocked) { + // If locked we will update the alias, but only if it matches the generated alias of the old name [NL] + const expectedOldAlias = generateAlias(oldName ?? ''); + // Only update the alias if the alias matches a generated alias of the old name (otherwise the alias is considered one written by the user.) [NL] + if (expectedOldAlias === oldAlias) { + this.alias = generateAlias(this.value); + } + } + this.dispatchEvent(new UmbChangeEvent()); + } + } + } + + #onAliasChange(e: UUIInputEvent) { + if (event instanceof UUIInputEvent) { + const target = event.composedPath()[0] as UUIInputElement; + if (typeof target?.value === 'string') { + this.alias = target.value; + this.dispatchEvent(new UmbChangeEvent()); + } + } + e.stopPropagation(); + } + + #onToggleAliasLock() { + this._aliasLocked = !this._aliasLocked; + } + + render() { + // Localizations: [NL] + return html` + + + + +
''} id="alias-lock" slot="prepend"> + +
+
+
+ `; + } + + static styles = css` + #name { + width: 100%; + flex: 1 1 auto; + align-items: center; + } + + :host(:invalid:not([pristine])) { + color: var(--uui-color-danger); + } + :host(:invalid:not([pristine])) > uui-input { + border-color: var(--uui-color-danger); + } + + #alias-lock { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + } + #alias-lock uui-icon { + margin-bottom: 2px; + } + `; +} + +export default UmbInputWithAliasElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-input-with-alias': UmbInputWithAliasElement; + } +}