diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts index 13a9bc9d72..30740dfc46 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -374,8 +374,6 @@ export default { fileSecurityValidationFailure: 'One or more file security validations have failed', moveToSameFolderFailed: 'Parent and destination folders cannot be the same', uploadNotAllowed: 'Upload is not allowed in this location.', - noticeExtensionsServerOverride: - 'Regardless of the allowed file types, the following limitations apply system-wide due to the server configuration:', }, member: { '2fa': 'Two-Factor Authentication', @@ -899,7 +897,6 @@ export default { retrieve: 'Retrieve', retry: 'Retry', rights: 'Permissions', - serverConfiguration: 'Server Configuration', scheduledPublishing: 'Scheduled Publishing', umbracoInfo: 'Umbraco info', search: 'Search', diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.ts index 6549183dae..a130588749 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.controller.ts @@ -48,8 +48,9 @@ export class UmbContextProviderController< public override destroy(): void { if (this.#host) { - this.#host.removeUmbController(this); + const host = this.#host; (this.#host as unknown) = undefined; + host.removeUmbController(this); } super.destroy(); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts index f13bfebce7..cab190f715 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts @@ -173,12 +173,13 @@ export class UmbPropertyEditorUIBlockListElement this.observe( this.#managerContext.layouts, (layouts) => { + const validationMessagesToRemove: string[] = []; const contentKeys = layouts.map((x) => x.contentKey); this.#validationContext.messages.getMessagesOfPathAndDescendant('$.contentData').forEach((message) => { // get the KEY from this string: $.contentData[?(@.key == 'KEY')] const key = extractJsonQueryProps(message.path).key; if (key && contentKeys.indexOf(key) === -1) { - this.#validationContext.messages.removeMessageByKey(message.key); + validationMessagesToRemove.push(message.key); } }); @@ -187,9 +188,12 @@ export class UmbPropertyEditorUIBlockListElement // get the key from this string: $.settingsData[?(@.key == 'KEY')] const key = extractJsonQueryProps(message.path).key; if (key && settingsKeys.indexOf(key) === -1) { - this.#validationContext.messages.removeMessageByKey(message.key); + validationMessagesToRemove.push(message.key); } }); + + // Remove the messages after the loop to prevent changing the array while iterating over it. + this.#validationContext.messages.removeMessageByKeys(validationMessagesToRemove); }, null, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/context/validation-messages.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/context/validation-messages.manager.ts index dbd64593e2..6fc5897222 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/context/validation-messages.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/context/validation-messages.manager.ts @@ -33,6 +33,26 @@ export class UmbValidationMessagesManager { this.messages.subscribe((x) => console.log(logName, x)); } + getMessages(): Array { + return this.#messages.getValue(); + } + + #updateLock = 0; + initiateChange() { + this.#updateLock++; + this.#messages.mute(); + // TODO: When ready enable this code will enable handling a finish automatically by this implementation 'using myState.initiatePropertyValueChange()' (Relies on TS support of Using) [NL] + /*return { + [Symbol.dispose]: this.finishPropertyValueChange, + };*/ + } + finishChange() { + this.#updateLock--; + if (this.#updateLock === 0) { + this.#messages.unmute(); + } + } + getHasAnyMessages(): boolean { return this.#messages.getValue().length !== 0; } @@ -75,7 +95,9 @@ export class UmbValidationMessagesManager { if (this.#messages.getValue().find((x) => x.type === type && x.path === path && x.body === body)) { return; } + this.initiateChange(); this.#messages.appendOne({ type, key, path, body: body }); + this.finishChange(); } addMessages(type: UmbValidationMessageType, path: string, bodies: Array): void { @@ -86,27 +108,42 @@ export class UmbValidationMessagesManager { const newBodies = bodies.filter( (message) => existingMessages.find((x) => x.type === type && x.path === path && x.body === message) === undefined, ); + this.initiateChange(); this.#messages.append(newBodies.map((body) => ({ type, key: UmbId.new(), path, body }))); + this.finishChange(); } removeMessageByKey(key: string): void { + this.initiateChange(); this.#messages.removeOne(key); + this.finishChange(); } removeMessageByKeys(keys: Array): void { + if (keys.length === 0) return; + this.initiateChange(); this.#messages.filter((x) => keys.indexOf(x.key) === -1); + this.finishChange(); } removeMessagesByType(type: UmbValidationMessageType): void { + this.initiateChange(); this.#messages.filter((x) => x.type !== type); + this.finishChange(); } removeMessagesByPath(path: string): void { + this.initiateChange(); this.#messages.filter((x) => x.path !== path); + this.finishChange(); } removeMessagesAndDescendantsByPath(path: string): void { + this.initiateChange(); this.#messages.filter((x) => MatchPathOrDescendantPath(x.path, path)); + this.finishChange(); } removeMessagesByTypeAndPath(type: UmbValidationMessageType, path: string): void { //path = path.toLowerCase(); + this.initiateChange(); this.#messages.filter((x) => !(x.type === type && x.path === path)); + this.finishChange(); } #translatePath(path: string): string | undefined { @@ -124,6 +161,7 @@ export class UmbValidationMessagesManager { #translators: Array = []; addTranslator(translator: UmbValidationMessageTranslator): void { + this.initiateChange(); if (this.#translators.indexOf(translator) === -1) { this.#translators.push(translator); } @@ -137,6 +175,7 @@ export class UmbValidationMessagesManager { this.#messages.updateOne(msg.key, { path: newPath }); } } + this.finishChange(); } removeTranslator(translator: UmbValidationMessageTranslator): void { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/form-control-validator.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/form-control-validator.controller.ts index 743b94bc41..9cccd5f1b8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/form-control-validator.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/form-control-validator.controller.ts @@ -35,8 +35,6 @@ export class UmbFormControlValidator extends UmbControllerBase implements UmbVal } }); this.#control = formControl; - this.#control.addEventListener(UmbValidationInvalidEvent.TYPE, this.#setInvalid); - this.#control.addEventListener(UmbValidationValidEvent.TYPE, this.#setValid); } get isValid(): boolean { @@ -82,12 +80,18 @@ export class UmbFormControlValidator extends UmbControllerBase implements UmbVal override hostConnected(): void { super.hostConnected(); + this.#control.addEventListener(UmbValidationInvalidEvent.TYPE, this.#setInvalid); + this.#control.addEventListener(UmbValidationValidEvent.TYPE, this.#setValid); if (this.#context) { this.#context.addValidator(this); } } override hostDisconnected(): void { super.hostDisconnected(); + if (this.#control) { + this.#control.removeEventListener(UmbValidationInvalidEvent.TYPE, this.#setInvalid); + this.#control.removeEventListener(UmbValidationValidEvent.TYPE, this.#setValid); + } if (this.#context) { this.#context.removeValidator(this); // Remove any messages that this validator has added: @@ -99,11 +103,9 @@ export class UmbFormControlValidator extends UmbControllerBase implements UmbVal } override destroy(): void { + super.destroy(); if (this.#control) { - this.#control.removeEventListener(UmbValidationInvalidEvent.TYPE, this.#setInvalid); - this.#control.removeEventListener(UmbValidationValidEvent.TYPE, this.#setValid); this.#control = undefined as any; } - super.destroy(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts index 87a7d04db7..377e9612ec 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts @@ -122,11 +122,12 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal this.observe( parent.messages.messagesOfPathAndDescendant(dataPath), (msgs) => { + this.messages.initiateChange(); //this.messages.appendMessages(msgs); if (this.#parentMessages) { // Remove the local messages that does not exist in the parent anymore: const toRemove = this.#parentMessages.filter((msg) => !msgs.find((m) => m.key === msg.key)); - this.#parent!.messages.removeMessageByKeys(toRemove.map((msg) => msg.key)); + this.messages.removeMessageByKeys(toRemove.map((msg) => msg.key)); } this.#parentMessages = msgs; msgs.forEach((msg) => { @@ -139,6 +140,7 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal // Notice, the local message uses the same key. [NL] this.messages.addMessage(msg.type, path, msg.body, msg.key); }); + this.messages.finishChange(); }, 'observeParentMessages', ); @@ -147,6 +149,9 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal this.messages.messages, (msgs) => { if (!this.#parent) return; + + this.#parent!.messages.initiateChange(); + //this.messages.appendMessages(msgs); if (this.#localMessages) { // Remove the parent messages that does not exist locally anymore: @@ -165,6 +170,8 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal // Notice, the parent message uses the same key. [NL] this.#parent!.messages.addMessage(msg.type, path, msg.body, msg.key); }); + + this.#parent!.messages.finishChange(); }, 'observeLocalMessages', ); @@ -172,6 +179,19 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal // Notice skipHost ^^, this is because we do not want it to consume it self, as this would be a match for this consumption, instead we will look at the parent and above. [NL] } + override hostConnected(): void { + super.hostConnected(); + if (this.#parent) { + this.#parent.addValidator(this); + } + } + override hostDisconnected(): void { + super.hostDisconnected(); + if (this.#parent) { + this.#parent.removeValidator(this); + } + } + /** * Get if this context is valid. * Notice this does not verify the validity. @@ -229,18 +249,27 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal () => false, ); - if (!this.messages) { + /*if (this.#validators.length === 0 && resultsStatus === false) { + throw new Error('No validators to validate, but validation failed'); + }*/ + + if (this.messages === undefined) { // This Context has been destroyed while is was validating, so we should not continue. return Promise.reject(); } + const hasMessages = this.messages.getHasAnyMessages(); + // If we have any messages then we are not valid, otherwise lets check the validation results: [NL] // This enables us to keep client validations though UI is not present anymore — because the client validations got defined as messages. [NL] - const isValid = this.messages.getHasAnyMessages() ? false : resultsStatus; + const isValid = hasMessages ? false : resultsStatus; this.#isValid = isValid; if (isValid === false) { + /*if (hasMessages === false && resultsStatus === false) { + throw new Error('Missing validation messages to represent why a child validation context is invalid.'); + }*/ // Focus first invalid element: this.focusFirstInvalidElement(); return Promise.reject(); @@ -278,6 +307,7 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal } override destroy(): void { + this.#providerCtrl?.destroy(); this.#providerCtrl = undefined; if (this.#parent) { this.#parent.removeValidator(this); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.function.ts index 40ceb3f9da..c33b1fe3bb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.function.ts @@ -4,8 +4,13 @@ * @param {string} path - the JSON path to the value that should be found * @returns {unknown} - the found value. */ -export function GetValueByJsonPath(data: unknown, path: string): unknown { +export function GetValueByJsonPath(data: unknown, path: string): ReturnType | undefined { + if (path === '$') return data as ReturnType; // strip $ from the path: + if (path.startsWith('$[')) { + return _GetNextArrayEntryFromPath(data as Array, path.slice(2)); + } + const strippedPath = path.startsWith('$.') ? path.slice(2) : path; // get value from the path: return GetNextPropertyValueFromPath(data, strippedPath); @@ -33,49 +38,62 @@ function GetNextPropertyValueFromPath(data: any, path: string): any { const value = data[key]; // if there is no rest of the path, return the value: if (rest === undefined) return value; + // if the value is an array, get the value at the index: if (Array.isArray(value)) { - // get the value until the next ']', the value can be anything in between the brackets: - const lookupEnd = rest.match(/\]/); - if (!lookupEnd) return undefined; - // get everything before the match: - const entryPointer = rest.slice(0, lookupEnd.index); - - // check if the entryPointer is a JSON Path Filter ( starting with ?( and ending with ) ): - if (entryPointer.startsWith('?(') && entryPointer.endsWith(')')) { - // get the filter from the entryPointer: - // get the filter as a function: - const jsFilter = JsFilterFromJsonPathFilter(entryPointer); - // find the index of the value that matches the filter: - const index = value.findIndex(jsFilter[0]); - // if the index is -1, return undefined: - if (index === -1) return undefined; - // get the value at the index: - const data = value[index]; - // Check for safety: - if (lookupEnd.index === undefined || lookupEnd.index + 1 >= rest.length) { - return data; - } - // continue with the rest of the path: - return GetNextPropertyValueFromPath(data, rest.slice(lookupEnd.index + 2)) ?? data; - } else { - // get the value at the index: - const indexAsNumber = parseInt(entryPointer); - if (isNaN(indexAsNumber)) return undefined; - const data = value[indexAsNumber]; - // Check for safety: - if (lookupEnd.index === undefined || lookupEnd.index + 1 >= rest.length) { - return data; - } - // continue with the rest of the path: - return GetNextPropertyValueFromPath(data, rest.slice(lookupEnd.index + 2)) ?? data; - } + return _GetNextArrayEntryFromPath(value, rest); } else { // continue with the rest of the path: return GetNextPropertyValueFromPath(value, rest); } } +/** + * @private + * @param {object} array - object to traverse for the value. + * @param {string} path - the JSON path to the value that should be found, notice without the starting '[' + * @returns {unknown} - the found value. + */ +function _GetNextArrayEntryFromPath(array: Array, path: string): any { + if (!array) return undefined; + + // get the value until the next ']', the value can be anything in between the brackets: + const lookupEnd = path.match(/\]/); + if (!lookupEnd) return undefined; + // get everything before the match: + const entryPointer = path.slice(0, lookupEnd.index); + + // check if the entryPointer is a JSON Path Filter ( starting with ?( and ending with ) ): + if (entryPointer.startsWith('?(') && entryPointer.endsWith(')')) { + // get the filter from the entryPointer: + // get the filter as a function: + const jsFilter = JsFilterFromJsonPathFilter(entryPointer); + // find the index of the value that matches the filter: + const index = array.findIndex(jsFilter[0]); + // if the index is -1, return undefined: + if (index === -1) return undefined; + // get the value at the index: + const entryData = array[index]; + // Check for safety: + if (lookupEnd.index === undefined || lookupEnd.index + 1 >= path.length) { + return entryData; + } + // continue with the rest of the path: + return GetNextPropertyValueFromPath(entryData, path.slice(lookupEnd.index + 2)) ?? entryData; + } else { + // get the value at the index: + const indexAsNumber = parseInt(entryPointer); + if (isNaN(indexAsNumber)) return undefined; + const entryData = array[indexAsNumber]; + // Check for safety: + if (lookupEnd.index === undefined || lookupEnd.index + 1 >= path.length) { + return entryData; + } + // continue with the rest of the path: + return GetNextPropertyValueFromPath(entryData, path.slice(lookupEnd.index + 2)) ?? entryData; + } +} + /** * @param {string} filter - A JSON Query, limited to filtering features. Do not support other JSON PATH Query features. * @returns {Array<(queryFilter: any) => boolean>} - An array of methods that returns true if the given items property value matches the value of the query. diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.test.ts index 906463e1e5..e8faaf3868 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.test.ts @@ -2,6 +2,14 @@ import { expect } from '@open-wc/testing'; import { GetValueByJsonPath } from './json-path.function.js'; describe('UmbJsonPathFunctions', () => { + it('retrieves root when path is root', () => { + const data = { value: 'test' }; + const result = GetValueByJsonPath(data, '$') as any; + + expect(result).to.eq(data); + expect(result.value).to.eq('test'); + }); + it('retrieve property value', () => { const result = GetValueByJsonPath({ value: 'test' }, '$.value'); @@ -34,4 +42,10 @@ describe('UmbJsonPathFunctions', () => { expect(result).to.eq('test'); }); + + it('query of array in root', () => { + const result = GetValueByJsonPath([{ id: '123', value: 'test' }], "$[?(@.id == '123')].value"); + + expect(result).to.eq('test'); + }); }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/replace-start-of-path.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/replace-start-of-path.test.ts index 7b90efc2cc..aa054b6be3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/replace-start-of-path.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/replace-start-of-path.test.ts @@ -19,4 +19,10 @@ describe('ReplaceStartOfPath', () => { expect(result).to.eq('$'); }); + + it('replaces the root character with root character', () => { + const result = ReplaceStartOfPath('$.start.test', '$', '$'); + + expect(result).to.eq('$.start.test'); + }); }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/submittable/submittable-workspace-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/submittable/submittable-workspace-context-base.ts index 80bfa5740b..9727767c07 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/submittable/submittable-workspace-context-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/submittable/submittable-workspace-context-base.ts @@ -74,7 +74,6 @@ export abstract class UmbSubmittableWorkspaceContextBase * @returns Promise that resolves to void when the validation is complete. */ public async validate(): Promise> { - //return this.validation.validate(); return Promise.all(this.#validationContexts.map((context) => context.validate())); } @@ -97,7 +96,15 @@ export abstract class UmbSubmittableWorkspaceContextBase async () => { onValid().then(this.#completeSubmit, this.#rejectSubmit); }, - async () => { + async (/*error*/) => { + /*if (error) { + throw new Error(error); + }*/ + // TODO: Implement developer-mode logging here. [NL] + console.warn( + 'Validation failed because of these validation messages still begin present: ', + this.#validationContexts.flatMap((x) => x.messages.getMessages()), + ); onInvalid().then(this.#resolveSubmit, this.#rejectSubmit); }, ); @@ -105,7 +112,10 @@ export abstract class UmbSubmittableWorkspaceContextBase return this.#submitPromise; } - #rejectSubmit = () => { + #rejectSubmit = (/*error: any*/) => { + /*if (error) { + throw new Error(error); + }*/ if (this.#submitPromise) { // TODO: Capture the validation contexts messages on open, and then reset to them in this case. [NL] diff --git a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/property-editor/property-editor-ui-multi-url-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/property-editor/property-editor-ui-multi-url-picker.element.ts index 74e253e53b..2f5bc75aa5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/property-editor/property-editor-ui-multi-url-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/property-editor/property-editor-ui-multi-url-picker.element.ts @@ -11,15 +11,16 @@ import type { import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui'; import '../components/input-multi-url/index.js'; +import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; /** * @element umb-property-editor-ui-multi-url-picker */ @customElement('umb-property-editor-ui-multi-url-picker') -export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement implements UmbPropertyEditorUiElement { - @property({ type: Array }) - value: Array = []; - +export class UmbPropertyEditorUIMultiUrlPickerElement + extends UmbFormControlMixin, typeof UmbLitElement, undefined>(UmbLitElement) + implements UmbPropertyEditorUiElement +{ public set config(config: UmbPropertyEditorConfigCollection | undefined) { if (!config) return; @@ -81,6 +82,7 @@ export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement impl this, ); } + this.addFormControlElement(this.shadowRoot!.querySelector('umb-input-multi-url')!); } #onChange(event: CustomEvent & { target: UmbInputMultiUrlElement }) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/accepted-types/property-editor-ui-accepted-upload-types.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/accepted-types/property-editor-ui-accepted-upload-types.element.ts index 9a85fa2b83..e28b478dca 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/accepted-types/property-editor-ui-accepted-upload-types.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/accepted-types/property-editor-ui-accepted-upload-types.element.ts @@ -1,6 +1,5 @@ import { UmbPropertyEditorUIMultipleTextStringElement } from '../multiple-text-string/property-editor-ui-multiple-text-string.element.js'; -import { css, customElement, html, nothing, state, when } from '@umbraco-cms/backoffice/external/lit'; -import { formatBytes } from '@umbraco-cms/backoffice/utils'; +import { css, customElement, html, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbTemporaryFileConfigRepository } from '@umbraco-cms/backoffice/temporary-file'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor'; import type { UmbTemporaryFileConfigurationModel } from '@umbraco-cms/backoffice/temporary-file'; @@ -40,7 +39,8 @@ export class UmbPropertyEditorUIAcceptedUploadTypesElement } #addValidators(config: UmbTemporaryFileConfigurationModel) { - this._inputElement?.addValidator( + const inputElement = this.shadowRoot?.querySelector('umb-input-multiple-text-string'); + inputElement?.addValidator( 'badInput', () => { let message = this.localize.term('validation_invalidExtensions'); @@ -53,7 +53,7 @@ export class UmbPropertyEditorUIAcceptedUploadTypesElement return message; }, () => { - const extensions = this._inputElement?.items; + const extensions = inputElement?.items; if (!extensions) return false; if ( config.allowedUploadedFileExtensions.length && @@ -69,49 +69,8 @@ export class UmbPropertyEditorUIAcceptedUploadTypesElement ); } - #renderAcceptedTypes() { - if (!this._acceptedTypes.length && !this._disallowedTypes.length && !this._maxFileSize) { - return nothing; - } - - return html` - -

- ${when( - this._acceptedTypes.length, - () => html` -

- - ${this._acceptedTypes.join(', ')} -

- `, - )} - ${when( - this._disallowedTypes.length, - () => html` -

- - ${this._disallowedTypes.join(', ')} -

- `, - )} - ${when( - this._maxFileSize, - () => html` -

- ${this.localize.term('media_maxFileSize')} - ${formatBytes(this._maxFileSize!, { decimals: 2 })}. -

- `, - )} -
- `; - } - override render() { - return html`${this.#renderAcceptedTypes()} ${super.render()}`; + return html`${super.render()}`; } static override readonly styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/multiple-text-string/property-editor-ui-multiple-text-string.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/multiple-text-string/property-editor-ui-multiple-text-string.element.ts index 269630d09c..c25323b3a5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/multiple-text-string/property-editor-ui-multiple-text-string.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/multiple-text-string/property-editor-ui-multiple-text-string.element.ts @@ -1,12 +1,8 @@ -import { customElement, html, property, query, state } from '@umbraco-cms/backoffice/external/lit'; -import { umbBindToValidation, UmbValidationContext } from '@umbraco-cms/backoffice/validation'; +import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { umbBindToValidation, UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; -import { - UMB_SUBMITTABLE_WORKSPACE_CONTEXT, - UmbSubmittableWorkspaceContextBase, -} from '@umbraco-cms/backoffice/workspace'; import type { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbInputMultipleTextStringElement } from '@umbraco-cms/backoffice/components'; import type { @@ -18,10 +14,10 @@ import type { * @element umb-property-editor-ui-multiple-text-string */ @customElement('umb-property-editor-ui-multiple-text-string') -export class UmbPropertyEditorUIMultipleTextStringElement extends UmbLitElement implements UmbPropertyEditorUiElement { - @property({ type: Array }) - value?: Array; - +export class UmbPropertyEditorUIMultipleTextStringElement + extends UmbFormControlMixin, typeof UmbLitElement, undefined>(UmbLitElement) + implements UmbPropertyEditorUiElement +{ public set config(config: UmbPropertyEditorConfigCollection | undefined) { if (!config) return; @@ -65,23 +61,12 @@ export class UmbPropertyEditorUIMultipleTextStringElement extends UmbLitElement @state() private _max = Infinity; - @query('#input', true) - protected _inputElement?: UmbInputMultipleTextStringElement; - - protected _validationContext = new UmbValidationContext(this); - constructor() { super(); this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => { this._label = context.getLabel(); }); - - this.consumeContext(UMB_SUBMITTABLE_WORKSPACE_CONTEXT, (context) => { - if (context instanceof UmbSubmittableWorkspaceContextBase) { - context.addValidationContext(this._validationContext); - } - }); } protected override firstUpdated() { @@ -91,6 +76,7 @@ export class UmbPropertyEditorUIMultipleTextStringElement extends UmbLitElement this, ); } + this.addFormControlElement(this.shadowRoot!.querySelector('umb-input-multiple-text-string')!); } #onChange(event: UmbChangeEvent) { @@ -100,31 +86,18 @@ export class UmbPropertyEditorUIMultipleTextStringElement extends UmbLitElement this.dispatchEvent(new UmbPropertyValueChangeEvent()); } - // Prevent valid events from bubbling outside the message element - #onValid(event: Event) { - event.stopPropagation(); - } - - // Prevent invalid events from bubbling outside the message element - #onInvalid(event: Event) { - event.stopPropagation(); - } - override render() { return html` - - - - + + `; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/property-editor-ui-tiptap.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/property-editor-ui-tiptap.element.ts index b6f0708bf2..3323f33f06 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/property-editor-ui-tiptap.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/property-editor-ui-tiptap.element.ts @@ -12,7 +12,7 @@ export class UmbPropertyEditorUiTiptapElement extends UmbPropertyEditorUiRteElem protected override firstUpdated(_changedProperties: PropertyValueMap | Map): void { super.firstUpdated(_changedProperties); - this.addFormControlElement(this.shadowRoot?.querySelector('umb-input-tiptap') as UmbInputTiptapElement); + this.addFormControlElement(this.shadowRoot!.querySelector('umb-input-tiptap') as UmbInputTiptapElement); } #onChange(event: CustomEvent & { target: UmbInputTiptapElement }) {